From 0d5abe0537b97704fd6c808fac71a0bb3d37bbec Mon Sep 17 00:00:00 2001 From: Tinyu-Zhao Date: Fri, 21 Feb 2025 17:59:32 +0800 Subject: [PATCH 001/322] docs: Normative Unit ASR Documentation. Signed-off-by: Tinyu-Zhao --- docs/en/units/asr.rst | 168 ++------ docs/locales/zh_CN/LC_MESSAGES/units/asr.po | 423 ++++++++++++++++++++ m5stack/libs/unit/asr.py | 288 +++++++------ 3 files changed, 619 insertions(+), 260 deletions(-) create mode 100644 docs/locales/zh_CN/LC_MESSAGES/units/asr.po diff --git a/docs/en/units/asr.rst b/docs/en/units/asr.rst index 910d2655..0cc9a6cb 100644 --- a/docs/en/units/asr.rst +++ b/docs/en/units/asr.rst @@ -10,161 +10,49 @@ Support the following products: |ASRUnit| -Micropython Example: - .. literalinclude:: ../../../examples/unit/asr/asr_cores3_example.py - :language: python - :linenos: - -UIFLOW2 Example: - - |example.png| - -.. only:: builder_html - - |asr_cores3_example.m5f2| - -class ASRUnit -------------- - -Constructors ------------- - -.. class:: ASRUnit(id, port) - - Initialize the ASRUnit object with UART configuration and set up the command handler. - - :param int id: The UART port ID for communication, default is 1. - :param port: A list or tuple containing the TX and RX pins for UART communication. - - UIFLOW2: - - |init.png| - - -Methods -------- - -.. method:: ASRUnit.get_received_status() - - Get the status of the received message. - - :returns: True if a message is received, False otherwise. - - UIFLOW2: - - |get_received_status.png| - -.. method:: ASRUnit.send_message(command_num) - - Send a message with a specified command number via UART. - - :param int command_num: The command number to send in the message. - - UIFLOW2: - - |send_message.png| - -.. method:: ASRUnit.get_current_raw_message() - - Get the raw message received in hexadecimal format. - - - :returns: The raw message as a string in hexadecimal format. - - UIFLOW2: - - |get_current_raw_message.png| +UiFlow2 Example +--------------- -.. method:: ASRUnit.get_current_command_word() +ASR Example +^^^^^^^^^^^^^ - Get the command word corresponding to the current command number. +Open the |asr_cores3_example.m5f2| project in UiFlow2. +This example shows how to use Unit ASR to get the current command word, command number, and trigger an event when you say hello to do something you want to do. - :returns: The command word as a string. +UiFlow2 Code Block: - UIFLOW2: - - |get_current_command_word.png| - -.. method:: ASRUnit.get_current_command_num() - - Get the current command number. - - :returns: The command number. - - UIFLOW2: - - |get_current_command_num.png| - -.. method:: ASRUnit.get_command_handler() - - Check if the current command has an associated handler. - - :returns: True if a handler exists for the current command, - - UIFLOW2: - - |get_command_handler.png| - -.. method:: ASRUnit.add_command_word(command_num, command_word, event_handler) - - Add a new command word and its handler to the command list. - - :param int command_num: The command number (must be between 0 and 255). - :param str command_word: The command word to associate with the command number. - :param event_handler: An optional event handler function to be called for the command. - - UIFLOW2: - - |add_command_word.png| - - |event.png| - -.. method:: ASRUnit.remove_command_word(command_word) - - Remove a command word from the command list by its word. - - :param str command_word: The command word to remove. - - UIFLOW2: - - |remove_command_word.png| - -.. method:: ASRUnit.search_command_num(command_word) - - Search for the command number associated with a command word. - - :param str command_word: The command word to search for. - - :returns: The command number if found, otherwise -1. - - UIFLOW2: - - |search_command_num.png| - -.. method:: ASRUnit.search_command_word(command_num) + |example.png| - Search for the command word associated with a command number. +Example output: - :param int command_num: The command number to search for. + None - :returns: The command word if found, otherwise "Unknown command +MicroPython Example +------------------- - UIFLOW2: +ASR Example +^^^^^^^^^^^^^ - |search_command_word.png| +This example shows how to use Unit ASR to get the current command word, command number, and trigger an event when you say hello to do something you want to do. -.. method:: ASRUnit.get_command_list() +MicroPython Code Block: - Get the list of all commands and their associated handlers. + .. literalinclude:: ../../../examples/unit/asr/asr_cores3_example.py + :language: python + :linenos: - :returns: A dictionary of command numbers and their corresponding command words and handlers. +Example output: - UIFLOW2: + None - |get_command_list.png| +**API** +------- -.. method:: ASRUnit.check_tick_callback() +ASRUnit +^^^^^^^^^ - Check if a handler is defined for the current command and schedule its execution. \ No newline at end of file +.. autoclass:: unit.asr.ASRUnit + :members: + :member-order: bysource \ No newline at end of file diff --git a/docs/locales/zh_CN/LC_MESSAGES/units/asr.po b/docs/locales/zh_CN/LC_MESSAGES/units/asr.po new file mode 100644 index 00000000..94a13811 --- /dev/null +++ b/docs/locales/zh_CN/LC_MESSAGES/units/asr.po @@ -0,0 +1,423 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2016 - 2025 M5Stack Technology Co., Ltd +# This file is distributed under the same license as the UIFlow2 Programming +# Guide package. +# FIRST AUTHOR , 2025. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: UIFlow2 Programming Guide \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-02-21 17:41+0800\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: zh_CN\n" +"Language-Team: zh_CN \n" +"Plural-Forms: nplurals=1; plural=0;\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.16.0\n" + +#: ../../en/units/asr.rst:3 f37d63c69a584e9ea39930851f2a3520 +msgid "ASR Unit" +msgstr "" + +#: ../../en/units/asr.rst:7 860e3d2d8c014db4b108e6c0a4f9e894 +msgid "" +"**Unit ASR** is an **AI** offline speech recognition unit, featuring the " +"built-in AI smart offline speech module **CI-03T**. This unit offers " +"powerful functions such as speech recognition, voiceprint recognition, " +"speech enhancement, and speech detection. It supports AEC (Acoustic Echo " +"Cancellation) to effectively eliminate echoes and noise interference, " +"improving the accuracy of speech recognition. Additionally, it supports " +"mid-speech interruption, allowing for flexible interruption during the " +"recognition process and quick response to new commands. The product is " +"pre-configured with wake-up words and feedback commands at the factory. " +"The device uses **UART** serial communication for data transmission and " +"also supports waking up the device via UART or voice keywords. This unit " +"supports user customization of the **wake-up** recognition word and can " +"recognize up to 300 command words. It is equipped with a **microphone** " +"for clear audio capture and includes a **speaker** for high-quality audio" +" feedback. This product is widely used in AI assistants, smart homes, " +"security monitoring, automotive systems, robotics, smart hardware, " +"healthcare, and other fields, making it an ideal choice for realizing " +"smart voice interactions." +msgstr "Unit ASR 是一款 AI 离线语音识别单元,内置 CI-03T AI 智能离线语音模块。该单元具备强大的语音识别、声纹识别、语音增强和语音检测等功能。支持 AEC(回声消除)功能,有效去除回声和噪声干扰,提升语音识别准确性。同时支持中途语音打断功能,允许在语音识别过程中灵活打断并快速响应新的指令。产品出厂时已预设了42条英文唤醒词和反馈命令词,该设备采用 UART 串口通信进行数据传输,同时支持通过 UART 或语音关键词唤醒设备。该单元支持用户自定义修改多语言唤醒识别词,用户可以通过重新生成固件来修改唤醒词,最多支持 300 条命令词的识别。配备麦克风用于清晰音频采集,并内置扬声器提供高质量的音频反馈。该产品广泛应用于 AI 助手、智能家居、安防监控、车载系统、机器人与智能硬件、医疗健康等领域,是实现智能语音交互的理想选择。" + +#: ../../en/units/asr.rst:9 2acc860d43684c67a541749241c0c727 +msgid "Support the following products:" +msgstr "支持下列产品:" + +#: ../../en/units/asr.rst:11 022c38b9b17b4cb39df1d92bcf73e79a +msgid "|ASRUnit|" +msgstr "" + +#: ../../en/refs/unit.asr.ref ../../en/units/asr.rst:54 +#: 34ae9547fbc04a5898b3274b1b9bb383 eb4b024ca9ff4ddf88baf871ac239d5f +msgid "ASRUnit" +msgstr "" + +#: ../../en/units/asr.rst:15 1e8bb5ad91c94fd2bb33de0411fd9b0b +msgid "UiFlow2 Example" +msgstr "UiFlow2 应用示例" + +#: ../../en/units/asr.rst:18 ../../en/units/asr.rst:36 +#: 63c6ad8322ef4157addba76427ca4724 6cc897947d7c4bfd94bfa1cf3ab902eb +msgid "ASR Example" +msgstr "ASR 示例程序" + +#: ../../en/units/asr.rst:20 79024d849b844842a75ab929bca4f48b +msgid "Open the |asr_cores3_example.m5f2| project in UiFlow2." +msgstr "在 UiFlow2 上打开 |asr_cores3_example.m5f2| 项目。" + +#: ../../en/units/asr.rst:22 ../../en/units/asr.rst:38 +#: 0afd082a0c9f44029ac89af24d1e713c 54b4b296144347ff806287d953a0a24f +msgid "" +"This example shows how to use Unit ASR to get the current command word, " +"command number, and trigger an event when you say hello to do something " +"you want to do." +msgstr "本例展示了如何使用 Unit ASR 获取当前命令词、命令编号,并在说出”你好“时触发事件,以执行你想做的事情。" + +#: ../../en/units/asr.rst:24 2f8cff4cfc29445c82d752bb52a4737a +#: 325a64686d744004b1fc809835e8ed49 424557d65590499cab6acbf91a6ee4cc +#: 44201833fd3a404fa3b9f3c4b06ac54b 654219451b104bbd8e34e801b1e91db3 +#: 65468c5c68de4ea3976077c3e1a140ab 907d9a55fb344bfb97e289c2315a91b8 +#: b2066e77ae464fe2a84e2f2fb5af6570 b8f8f6a2eb044ef1886f54db5dda8ee4 +#: e0266b322df74c1a86610fded3b293cc e1ee046aa8be4a8c817a1b0b32d412fa +#: eabcb069de6c43a39f0a0ff1a3c25315 ed4723e635d9440097c94ef4e9377f77 of +#: unit.asr.ASRUnit:6 unit.asr.ASRUnit.add_command_word:7 +#: unit.asr.ASRUnit.get_command_handler:6 unit.asr.ASRUnit.get_command_list:6 +#: unit.asr.ASRUnit.get_current_command_num:6 +#: unit.asr.ASRUnit.get_current_command_word:6 +#: unit.asr.ASRUnit.get_current_raw_message:6 +#: unit.asr.ASRUnit.get_received_status:6 +#: unit.asr.ASRUnit.remove_command_word:5 unit.asr.ASRUnit.search_command_num:7 +#: unit.asr.ASRUnit.search_command_word:7 unit.asr.ASRUnit.send_message:5 +msgid "UiFlow2 Code Block:" +msgstr "UiFlow2 代码块:" + +#: ../../en/units/asr.rst:26 cb176a5070df4557b0381a7a553e117a +msgid "|example.png|" +msgstr "" + +#: ../../en/refs/unit.asr.ref:22 9a31ff20aed74ce6bccd468c8d8b50a0 +msgid "example.png" +msgstr "" + +#: ../../en/units/asr.rst:28 ../../en/units/asr.rst:46 +#: 865ece5b1c504abe957cf96ebbd3cda3 e92848a465c04d5aa71f40b5c057f837 +msgid "Example output:" +msgstr "示例输出:" + +#: ../../en/units/asr.rst:30 ../../en/units/asr.rst:48 +#: 4e5817fc34e14b6fb715f374b22b2900 a7c573ba750c4744b1b60a1db186afb0 +msgid "None" +msgstr "" + +#: ../../en/units/asr.rst:33 dc836e8c51754b3fb743216df5fb8d93 +msgid "MicroPython Example" +msgstr "MicroPython 应用示例" + +#: ../../en/units/asr.rst:40 0d880730a6204a62a449ad404f633f4c +#: 244b40a12cef41b7a84f6eb6abaf2be8 3dd4242ded154b8589e77f713177030d +#: 4423926ca16b4613acea8cd0229df1c9 44850c319d7245fd9b7917aab2af9680 +#: 58ce20765e4b4e45a66dd0c13b90421e 5f42e828e4174cbe8aaa62ab2ebc9d4a +#: 6371770eb5664fbe9ad454d1e64134c5 7a4328bc24ac47769d8130248adef6ac +#: 9eb93f5a26c746d3a1e5bb858eee8f4f af3ebce0568449bc81f417381173c2b7 +#: db5b524794d34949b11e63d6487cfe69 de58cde862b442d39c654f5c1527b8af +#: f925c04eaeda49e697a4501162b07cc6 of unit.asr.ASRUnit:10 +#: unit.asr.ASRUnit.add_command_word:11 unit.asr.ASRUnit.check_tick_callback:6 +#: unit.asr.ASRUnit.get_command_handler:10 unit.asr.ASRUnit.get_command_list:10 +#: unit.asr.ASRUnit.get_current_command_num:10 +#: unit.asr.ASRUnit.get_current_command_word:10 +#: unit.asr.ASRUnit.get_current_raw_message:10 +#: unit.asr.ASRUnit.get_received_status:10 +#: unit.asr.ASRUnit.remove_command_word:9 +#: unit.asr.ASRUnit.search_command_num:11 +#: unit.asr.ASRUnit.search_command_word:11 unit.asr.ASRUnit.send_message:9 +msgid "MicroPython Code Block:" +msgstr "MicroPython 代码块:" + +#: ../../en/units/asr.rst:51 eceb9dc54a2b4f73a01c6ddb475efc97 +msgid "**API**" +msgstr "" + +#: 49e32ad6f09345919994d9e52410cee6 of unit.asr.ASRUnit:1 +msgid "Bases: :py:class:`object`" +msgstr "" + +#: 26d61c37609e4780b9abe8d0b7aa6b71 of unit.asr.ASRUnit:1 +msgid "Voice recognition hardware module." +msgstr "语音识别硬件模块" + +#: ../../en/units/asr.rst 47dda7f469c846feb0f732c69b16993c +#: 56bc70efdeda4350a4e20d2ffa21d816 82423eabccfc42aaa55660ca091ceb5c +#: b61dcd2c502249649d6dd25c895dd224 b9eaf1b44e524fee89d4391f5335a36e +#: ba9df2ff47db4f729027b711893c4969 of unit.asr.ASRUnit.add_command_word +#: unit.asr.ASRUnit.remove_command_word unit.asr.ASRUnit.search_command_num +#: unit.asr.ASRUnit.search_command_word unit.asr.ASRUnit.send_message +msgid "Parameters" +msgstr "" + +#: cc07b0e485794cf6af74b9e2e891f5c5 of unit.asr.ASRUnit:3 +msgid "UART port ID for communication. Default is 1." +msgstr "用于通信的 UART 端口 ID, 默认为 1。" + +#: fd15238ec553424c9ab6e264741ce6c4 of unit.asr.ASRUnit:4 +msgid "Tuple containing TX and RX pin numbers." +msgstr "包含 TX 和 RX 引脚编号的元组。" + +#: 459da676ef9841709b9d8c381f9e6077 of unit.asr.ASRUnit:8 +msgid "|init.png|" +msgstr "" + +#: ../../en/refs/unit.asr.ref:7 14bf0089a0824233b04e8068f521ce95 +msgid "init.png" +msgstr "" + +#: 896d8d29372042f9b7ae4eac8b9d3d5d of unit.asr.ASRUnit.get_received_status:1 +msgid "Get message reception status." +msgstr "获取信息接收状态。" + +#: 01395ffbcf7140d88a6ec03cd883d9ce 04fe87e9b302464f997dc70f4b464890 +#: 2b3fc393727a4690b9c9b4871c316fd8 a10a64ac9dd4410e8b757c7d9d7a07e0 +#: a6bc0a43f81443179d97979199b074f3 a8033a3cce4f44e5b5e5bffcc36824f1 +#: ba0ee10b31014081b3015b6fd5f323fd f1202713a69144f1b28cb889af9be8d6 +#: fe4c147412a74afd94acc17bbf4d9494 of unit.asr.ASRUnit.check_tick_callback +#: unit.asr.ASRUnit.get_command_handler unit.asr.ASRUnit.get_command_list +#: unit.asr.ASRUnit.get_current_command_num +#: unit.asr.ASRUnit.get_current_command_word +#: unit.asr.ASRUnit.get_current_raw_message +#: unit.asr.ASRUnit.get_received_status unit.asr.ASRUnit.search_command_num +#: unit.asr.ASRUnit.search_command_word +msgid "Returns" +msgstr "" + +#: dcf5d52f9b9a47aab29247147651bc05 of unit.asr.ASRUnit.get_received_status:3 +msgid "True if a message is received, False otherwise." +msgstr "如果收到信息则为 True,否则为 False。" + +#: 1fb83583846c4263a94b69dbde6f7aa5 49d8f0c89a8549ef873c7cc4f0e2c421 +#: 58d08474d2c44ede87a3866dc8e51d16 8422f16ab9324173a21aed71c8612891 +#: a39a0a471dd241828aa3e505ea9d095b a43c05ac074d4a4098c41d98765eb738 +#: a521e333def14a6cbc1e303c31f23e71 c5018538ad83470d93d58aca11b05c76 +#: cabc32f45af44ce1af813a3af123223c dc395d12902441e79373b9f7e1eeaf95 +#: dfef2d8d3bb14bcbb767e4c52721cac5 eec729815add4b14bf6b7fc06b06d611 of +#: unit.asr.ASRUnit.add_command_word unit.asr.ASRUnit.check_tick_callback +#: unit.asr.ASRUnit.get_command_handler unit.asr.ASRUnit.get_command_list +#: unit.asr.ASRUnit.get_current_command_num +#: unit.asr.ASRUnit.get_current_command_word +#: unit.asr.ASRUnit.get_current_raw_message +#: unit.asr.ASRUnit.get_received_status unit.asr.ASRUnit.remove_command_word +#: unit.asr.ASRUnit.search_command_num unit.asr.ASRUnit.search_command_word +#: unit.asr.ASRUnit.send_message +msgid "Return type" +msgstr "" + +#: 31177f63ec204f2db9d8a1d6a2f05411 of unit.asr.ASRUnit.get_received_status:8 +msgid "|get_received_status.png|" +msgstr "" + +#: ../../en/refs/unit.asr.ref:9 cb8b9bca0ce34d5cb16ae7ec454532db +msgid "get_received_status.png" +msgstr "" + +#: 5bd967283aaf449f97d4f1ff0040130c of unit.asr.ASRUnit.send_message:1 +msgid "Send command via UART." +msgstr "通过 UART 发送命令。" + +#: 6f832df02ef2496293cd8716c697d685 of unit.asr.ASRUnit.send_message:3 +msgid "Command number to send (0-255)" +msgstr "要发送的命令编号(0-255)" + +#: fa88cfe31b7140d098670beaa24644be of unit.asr.ASRUnit.send_message:7 +msgid "|send_message.png|" +msgstr "" + +#: ../../en/refs/unit.asr.ref:10 61d3b1dd65e64036aecd0a82d5020969 +msgid "send_message.png" +msgstr "" + +#: 890383416df9469eba27d2d6885a0121 of +#: unit.asr.ASRUnit.get_current_raw_message:1 +msgid "Get the raw message received in hexadecimal format." +msgstr "以十六进制格式获取接收到的原始信息。" + +#: 016275abda3f4a1388f1c8359abc2d37 of +#: unit.asr.ASRUnit.get_current_raw_message:3 +msgid "The raw message as a string in hexadecimal format." +msgstr "十六进制格式的原始报文字符串。" + +#: 22bf36161577440da9e8a3b1619619e5 of +#: unit.asr.ASRUnit.get_current_raw_message:8 +msgid "|get_current_raw_message.png|" +msgstr "" + +#: ../../en/refs/unit.asr.ref:11 5d91d246f8864f0d95a6635dd1548575 +msgid "get_current_raw_message.png" +msgstr "" + +#: 346aebf986f041c08ca07a8185246258 of +#: unit.asr.ASRUnit.get_current_command_word:1 +msgid "Get the command word corresponding to the current command number." +msgstr "获取与当前命令编号相对应的命令词。" + +#: ae1207bedd7a45b88298eb58c22977f0 of +#: unit.asr.ASRUnit.get_current_command_word:3 +msgid "The command word as a string." +msgstr "以字符串形式显示的命令词。" + +#: a6dcd7fba1f64e009aa859d1d897ac86 of +#: unit.asr.ASRUnit.get_current_command_word:8 +msgid "|get_current_command_word.png|" +msgstr "" + +#: ../../en/refs/unit.asr.ref:12 5694e7ca0dd24ad5abc7f02c6f11f0bf +msgid "get_current_command_word.png" +msgstr "" + +#: ed8e447c49a24a37a0529b138dc09bfb of +#: unit.asr.ASRUnit.get_current_command_num:1 +msgid "Get the current command number." +msgstr "获取当前命令编号。" + +#: 4dfa3409321e42c3a92007cc2470c67a of +#: unit.asr.ASRUnit.get_current_command_num:3 +msgid "The current command number as a string." +msgstr "字符串形式的当前命令编号。" + +#: 5338f55b0810459197b16a5a2c7d8f07 of +#: unit.asr.ASRUnit.get_current_command_num:8 +msgid "|get_current_command_num.png|" +msgstr "" + +#: ../../en/refs/unit.asr.ref:13 d6d81daccfc346bebbffc36b3377ece5 +msgid "get_current_command_num.png" +msgstr "" + +#: 700156e486a04c4ba72dffb048941f48 of unit.asr.ASRUnit.get_command_handler:1 +msgid "Check if the current command has an associated handler." +msgstr "检查当前命令是否有关联回调函数。" + +#: af795a24ef404e2a9383927982f246c3 of unit.asr.ASRUnit.get_command_handler:3 +msgid "True if the command has an associated handler, False otherwise." +msgstr "如果命令有关联回调函数,则为 True;否则为 False。" + +#: f547c377cb794f4b990566392cebe9d5 of unit.asr.ASRUnit.get_command_handler:8 +msgid "|get_command_handler.png|" +msgstr "" + +#: ../../en/refs/unit.asr.ref:14 5ce977b8ddf146a9956640f24717bcad +msgid "get_command_handler.png" +msgstr "" + +#: 92fa83a8574b4b67b68688be4492df85 of unit.asr.ASRUnit.add_command_word:1 +msgid "Register custom command and handler." +msgstr "注册自定义命令和回调函数。" + +#: 9112f977fca84aaf9a8ddda453d6cabb of unit.asr.ASRUnit.add_command_word:3 +msgid "Command number (0-255)" +msgstr "命令编号(0-255)" + +#: 2664862fa1b148abb39f5eedc5cefc8b of unit.asr.ASRUnit.add_command_word:4 +msgid "Voice command text" +msgstr "语音命令文本" + +#: 11b6ebd8304c45759b4e1ff3a081de74 of unit.asr.ASRUnit.add_command_word:5 +msgid "Handler function" +msgstr "回调函数功能" + +#: dff0f9eaa6a841a7855d4edb148b8385 of unit.asr.ASRUnit.add_command_word:9 +msgid "|add_command_word.png|" +msgstr "" + +#: ../../en/refs/unit.asr.ref:15 cfaa1d15f54e46c8a3daeb1533229ae2 +msgid "add_command_word.png" +msgstr "" + +#: 03156fb991c44896a20645d530b5ad9c of unit.asr.ASRUnit.remove_command_word:1 +msgid "Remove a command word from the command list by its word." +msgstr "从命令列表中删除命令词。" + +#: 0c3cb11b915540bcb9745047f38b4c7f of unit.asr.ASRUnit.remove_command_word:3 +msgid "Command word to remove" +msgstr "要删除的命令词" + +#: 118331f5065a4bb8ad280daac5e67adb of unit.asr.ASRUnit.remove_command_word:7 +msgid "|remove_command_word.png|" +msgstr "" + +#: ../../en/refs/unit.asr.ref:17 3630e0ffd3fb4fcfa1e234ba00bb774b +msgid "remove_command_word.png" +msgstr "" + +#: 066f87f7d3864fbe9a88a41d22c391ad of unit.asr.ASRUnit.search_command_num:1 +msgid "Search for the command number associated with a command word." +msgstr "搜索与命令词相关的命令编号。" + +#: 459c0585898a47c98456858f4653e0bd of unit.asr.ASRUnit.search_command_num:3 +msgid "Command word to search for" +msgstr "要搜索的命令词" + +#: b3f6a881f86a45cea2880649428c7b7b of unit.asr.ASRUnit.search_command_num:4 +msgid "The command number if found, otherwise -1" +msgstr "如果找到相应的命令词,则返回命令编号,否则返回 -1" + +#: c3844d27f9114696937dea51d38aaeb3 of unit.asr.ASRUnit.search_command_num:9 +msgid "|search_command_num.png|" +msgstr "" + +#: ../../en/refs/unit.asr.ref:18 8bc49d90d0c247dba8cf4b96f22ea6c7 +msgid "search_command_num.png" +msgstr "" + +#: f0f295cfae3f4c51a91f9668fc1d722f of unit.asr.ASRUnit.search_command_word:1 +msgid "Search for the command word associated with a command number." +msgstr "搜索与命令编号相关的命令词。" + +#: da5dc837ac264bec99d70fbec8084e73 of unit.asr.ASRUnit.search_command_word:3 +msgid "Command number to search for" +msgstr "要搜索的命令编号" + +#: 5812d4f921aa40da8293a7435a76e36d of unit.asr.ASRUnit.search_command_word:4 +msgid "The command word if found, otherwise \"Unknown command word\"" +msgstr "如果找到相应的命令变化,则返回命令词,否则返回为 "\未知命令词"" + +#: cd2932684a784b5da58fe9731c749de0 of unit.asr.ASRUnit.search_command_word:9 +msgid "|search_command_word.png|" +msgstr "" + +#: ../../en/refs/unit.asr.ref:19 a621d46458c24dc2bb3c61f0851d1b0a +msgid "search_command_word.png" +msgstr "" + +#: 39938304e426432e8cbd7057bee77c07 of unit.asr.ASRUnit.get_command_list:1 +msgid "Get the list of all commands and their associated handlers." +msgstr "获取所有命令词及其相关回调函数的列表。" + +#: 422bf3e2fc6a4178b16593ac277e4cc7 of unit.asr.ASRUnit.get_command_list:3 +msgid "" +"A dictionary of command numbers and their corresponding command words and" +" handlers." +msgstr "包含命令编号及其相应命令词和回调函数的字典。" + +#: 795c1796775940e7aea60ddf9b92dac5 of unit.asr.ASRUnit.get_command_list:8 +msgid "|get_command_list.png|" +msgstr "" + +#: ../../en/refs/unit.asr.ref:20 b840bb1b79b945f7812c3a7396b5b515 +msgid "get_command_list.png" +msgstr "" + +#: 4c2565a5ee71475d82ddc7b298a4ce4a of unit.asr.ASRUnit.check_tick_callback:1 +msgid "" +"Check if a handler is defined for the current command and schedule its " +"execution." +msgstr "检查当前命令是否定义了回调函数,并安排其执行。" + +#: f14b891f68fd40aabc2136f55802bb65 of unit.asr.ASRUnit.check_tick_callback:3 +msgid "The handler if defined, otherwise None" +msgstr "回调函数(如果已定义),否则无" + diff --git a/m5stack/libs/unit/asr.py b/m5stack/libs/unit/asr.py index cf3f4d25..a7a816e3 100644 --- a/m5stack/libs/unit/asr.py +++ b/m5stack/libs/unit/asr.py @@ -10,24 +10,31 @@ class ASRUnit: - """ - note: - en: ASRUnit is a hardware module designed for voice command recognition. It communicates via UART and supports command parsing, handling, and custom command registration. The module provides functions for receiving, sending messages, and managing commands. + """Voice recognition hardware module. + + :param int id: UART port ID for communication. Default is 1. + :param list|tuple port: Tuple containing TX and RX pin numbers. + + UiFlow2 Code Block: + + |init.png| + + MicroPython Code Block: - details: - link: https://docs.m5stack.com/en/unit/asr - image: https://static-cdn.m5stack.com/resource/docs/products/unit/asr/asr_01.webp - category: Unit + .. code-block:: python - example: - - ../../../examples/unit/asr/asr_cores3_example.py + from unit import ASRUnit - m5f2: - - unit/asr/asr_cores3_example.m5f2 + # Initialize with UART1, TX on pin 2, RX on pin 1 + asr = ASRUnit(id=1, port=(1, 2)) """ DEBUG = True + myprint = print if DEBUG else lambda *_, **__: None + """ + :meta private: + """ _COMMAND_LIST = { 0x01: ["up", None], @@ -76,16 +83,6 @@ class ASRUnit: } def __init__(self, id: Literal[0, 1, 2] = 1, port: list | tuple = None): - """ - note: - en: Initialize the ASRUnit object with UART configuration and set up the command handler. - - params: - id: - note: The UART port ID for communication, default is 1. - port: - note: A list or tuple containing the TX and RX pins for UART communication. - """ self.uart = UART(id, tx=port[1], rx=port[0]) self.uart.init(115200, bits=8, parity=None, stop=1) self.uart.irq(handler=self._handler, trigger=UART.IRQ_RXIDLE) @@ -94,19 +91,10 @@ def __init__(self, id: Literal[0, 1, 2] = 1, port: list | tuple = None): self.is_recieved = False def _handler(self, uart) -> None: - """ - note: - en: UART interrupt handler to process incoming data and parse the command number. - - params: - uart: - note: The UART object receiving the data. - """ data = uart.readline() - if data is not None and len(data) >= 5: # 至少包含帧头、命令、帧尾 + if data is not None and len(data) >= 5: self.myprint(("Received data: ", data)) - # 校验帧头和帧尾 if data[0] == 0xAA and data[1] == 0x55 and data[-2] == 0x55 and data[-1] == 0xAA: self.is_recieved = True self.raw_message = " ".join(f"0x{byte:02X}" for byte in data) @@ -119,15 +107,20 @@ def _handler(self, uart) -> None: uart.read() def get_received_status(self) -> bool: - """ - note: - en: Get the status of the received message. + """Get message reception status. + + :returns: True if a message is received, False otherwise. + :rtype: bool - params: - note: + UiFlow2 Code Block: - returns: - note: True if a message is received, False otherwise. + |get_received_status.png| + + MicroPython Code Block: + + .. code-block:: python + + asr.get_received_status() """ status = self.is_recieved self.is_recieved = False @@ -137,13 +130,19 @@ def send_message( self, command_num: int, ) -> None: - """ - note: - en: Send a message with a specified command number via UART. + """Send command via UART. + + :param int command_num: Command number to send (0-255) - params: - command_num: - note: The command number to send in the message. + UiFlow2 Code Block: + + |send_message.png| + + MicroPython Code Block: + + .. code-block:: python + + asr.send_message(0x30) """ message: list[int] = [0xAA, 0x55, command_num, 0x55, 0xAA] buf = bytes(message) @@ -151,54 +150,74 @@ def send_message( self.uart.write(buf) def get_current_raw_message(self) -> str: - """ - note: - en: Get the raw message received in hexadecimal format. + """Get the raw message received in hexadecimal format. - params: - note: + :returns: The raw message as a string in hexadecimal format. + :rtype: str - returns: - note: The raw message as a string in hexadecimal format. + UiFlow2 Code Block: + + |get_current_raw_message.png| + + MicroPython Code Block: + + .. code-block:: python + + asr.get_current_raw_message() """ return self.raw_message def get_current_command_word(self) -> str: - """ - note: - en: Get the command word corresponding to the current command number. + """Get the command word corresponding to the current command number. - params: - note: + :returns: The command word as a string. + :rtype: str - returns: - note: The command word as a string. + UiFlow2 Code Block: + + |get_current_command_word.png| + + MicroPython Code Block: + + .. code-block:: python + + asr.get_current_command_word() """ return self._COMMAND_LIST.get(self.command_num, ["Unknown command word", None])[0] def get_current_command_num(self) -> str: - """ - note: - en: Get the current command number. + """Get the current command number. + + :returns: The current command number as a string. + :rtype: str + + UiFlow2 Code Block: + + |get_current_command_num.png| + + MicroPython Code Block: - params: - note: + .. code-block:: python - returns: - note: The command number. + asr.get_current_command_num() """ return "0x{:X}".format(self.command_num) def get_command_handler(self) -> bool: - """ - note: - en: Check if the current command has an associated handler. + """Check if the current command has an associated handler. + + :returns: True if the command has an associated handler, False otherwise. + :rtype: bool + + UiFlow2 Code Block: + + |get_command_handler.png| + + MicroPython Code Block: - params: - note: + .. code-block:: python - returns: - note: True if a handler exists for the current command, False otherwise. + asr.get_command_handler() """ return self._COMMAND_LIST.get(self.command_num, ["", None])[1] is not None @@ -208,30 +227,43 @@ def add_command_word( command_word: str, event_handler=None, ) -> None: - """ - note: - en: Add a new command word and its handler to the command list. - - params: - command_num: - note: The command number (must be between 0 and 255). - command_word: - note: The command word to associate with the command number. - event_handler: - note: An optional event handler function to be called for the command. + """Register custom command and handler. + + :param int command_num: Command number (0-255) + :param str command_word: Voice command text + :param callable event_handler: Handler function + + UiFlow2 Code Block: + + |add_command_word.png| + + MicroPython Code Block: + + .. code-block:: python + + def custom_handler(unit): + print("Custom command detected!") + + asr.add_command_word(0x50, "custom command", custom_handler) """ if not (0 <= command_num <= 255): raise ValueError("Command number must be between 0 and 255") self._COMMAND_LIST[command_num] = [command_word, event_handler] def remove_command_word(self, command_word: str) -> None: - """ - note: - en: Remove a command word from the command list by its word. + """Remove a command word from the command list by its word. + + :param str command_word: Command word to remove + + UiFlow2 Code Block: + + |remove_command_word.png| + + MicroPython Code Block: + + .. code-block:: python - params: - command_word: - note: The command word to remove. + asr.remove_command_word("custom command") """ command_num = self.search_command_num(command_word) if command_num != -1: @@ -240,56 +272,72 @@ def remove_command_word(self, command_word: str) -> None: raise ValueError("Command word not found") def search_command_num(self, command_word: str) -> int: - """ - note: - en: Search for the command number associated with a command word. + """Search for the command number associated with a command word. + + :param str command_word: Command word to search for + :returns: The command number if found, otherwise -1 + :rtype: int + + UiFlow2 Code Block: + + |search_command_num.png| + + MicroPython Code Block: - params: - command_word: - note: The command word to search for. + .. code-block:: python - returns: - note: The command number if found, otherwise -1. + asr.search_command_num("custom command") """ - for key, value in self._COMMAND_LIST.items(): - if value[0] == command_word: - return key return -1 def search_command_word(self, command_num: int) -> str: - """ - note: - en: Search for the command word associated with a command number. + """Search for the command word associated with a command number. + + :param int command_num: Command number to search for + :returns: The command word if found, otherwise "Unknown command word" + :rtype: str + + UiFlow2 Code Block: + + |search_command_word.png| + + MicroPython Code Block: - params: - command_num: - note: The command number to search for. + .. code-block:: python - returns: - note: The command word if found, otherwise "Unknown command word". + asr.search_command_word(0x50) """ return self._COMMAND_LIST.get(command_num, ["Unknown command word", None])[0] def get_command_list(self) -> dict: - """ - note: - en: Get the list of all commands and their associated handlers. + """Get the list of all commands and their associated handlers. + + :returns: A dictionary of command numbers and their corresponding command words and handlers. + :rtype: dict + + UiFlow2 Code Block: + + |get_command_list.png| + + MicroPython Code Block: - params: - note: + .. code-block:: python - returns: - note: A dictionary of command numbers and their corresponding command words and handlers. + asr.get_command_list() """ return self._COMMAND_LIST def check_tick_callback(self): - """ - note: - en: Check if a handler is defined for the current command and schedule its execution. + """Check if a handler is defined for the current command and schedule its execution. + + :returns: The handler if defined, otherwise None + :rtype: None + + MicroPython Code Block: + + .. code-block:: python - params: - note: + asr.check_tick_callback() """ handler = self._COMMAND_LIST.get(self.command_num, ["", None])[1] self.myprint(("handler: ", handler)) From 50c300806145ff6d9fdba631386d9c2bc87ef66f Mon Sep 17 00:00:00 2001 From: lbuque Date: Mon, 24 Feb 2025 14:54:21 +0800 Subject: [PATCH 002/322] libs/base: Add Atomic Display Base support. Signed-off-by: lbuque --- docs/en/base/display.rst | 125 +++++++++ docs/en/base/index.rst | 1 + docs/en/refs/base.display.ref | 36 +++ .../locales/zh_CN/LC_MESSAGES/base/display.po | 251 ++++++++++++++++++ .../display/atoms3_draw_image_example.m5f2 | 1 + .../base/display/atoms3_draw_image_example.py | 55 ++++ .../display/atoms3_draw_text_example.m5f2 | 1 + .../base/display/atoms3_draw_text_example.py | 53 ++++ .../components/M5Unified/mpy_m5unified.cpp | 48 ++++ m5stack/libs/base/__init__.py | 1 + m5stack/libs/base/display.py | 61 +++++ m5stack/libs/base/manifest.py | 1 + 12 files changed, 634 insertions(+) create mode 100644 docs/en/base/display.rst create mode 100644 docs/en/refs/base.display.ref create mode 100644 docs/locales/zh_CN/LC_MESSAGES/base/display.po create mode 100644 examples/base/display/atoms3_draw_image_example.m5f2 create mode 100644 examples/base/display/atoms3_draw_image_example.py create mode 100644 examples/base/display/atoms3_draw_text_example.m5f2 create mode 100644 examples/base/display/atoms3_draw_text_example.py create mode 100644 m5stack/libs/base/display.py diff --git a/docs/en/base/display.rst b/docs/en/base/display.rst new file mode 100644 index 00000000..6f946c8e --- /dev/null +++ b/docs/en/base/display.rst @@ -0,0 +1,125 @@ +Atomic Display Base +=================== + +.. sku: A115 K115 K115-B + +.. include:: ../refs/base.display.ref + +The is the class of the Atomic Display Base, which is used to display images and text on the screen. + +Support the following products: + + ====================== ====================== ====================== + |Atomic Display Base| |Atom Display| |Atom Display-Lite| + ====================== ====================== ====================== + +Below is the detailed support for Atomic Display Base on the host: + +.. table:: + :widths: auto + :align: center + + +-----------------+---------+ + |Controller | Status | + +=================+=========+ + | Atom Echo | |O| | + +-----------------+---------+ + | Atom Lite | |S| | + +-----------------+---------+ + | Atom Matrix | |S| | + +-----------------+---------+ + | AtomS3 | |S| | + +-----------------+---------+ + | AtomS3 Lite | |S| | + +-----------------+---------+ + | AtomS3R | |S| | + +-----------------+---------+ + | AtomS3R-CAM | |S| | + +-----------------+---------+ + | AtomS3R-Ext | |S| | + +-----------------+---------+ + +.. |S| unicode:: U+2705 +.. |O| unicode:: U+2B55 + +- |S|: Supported. + +- |O|: Optional, It conflicts with some internal resource of the host. + + +UiFlow2 Example +--------------- + +Draw Text +^^^^^^^^^^^^^^^ + +Open the |atoms3_draw_text_example.m5f2| project in UiFlow2. + +This example displays the text "M5Stack" on the screen. + +UiFlow2 Code Block: + + |example_draw_text.png| + +Example output: + + None + + +Draw Image +^^^^^^^^^^^^^^^ + +Open the |atoms3_draw_text_example.m5f2| project in UiFlow2. + +This example displays the image on the screen. + +UiFlow2 Code Block: + + |example_draw_image.png| + +Example output: + + None + + +MicroPython Example +------------------- + +Draw Text +^^^^^^^^^^^^^^^ + +This example displays the text "M5Stack" on the screen. + +MicroPython Code Block: + + .. literalinclude:: ../../../examples/base/display/atoms3_draw_text_example.py + :language: python + :linenos: + +Example output: + + None + + +Draw Image +^^^^^^^^^^^^^^^ + +This example displays the image on the screen. + +MicroPython Code Block: + + .. literalinclude:: ../../../examples/base/display/atoms3_draw_image_example.py + +Example output: + + None + + +**API** +------- + +AtomicDisplayBase +^^^^^^^^^^^^^^^^^^ + +.. autoclass:: base.display.AtomicDisplayBase + :members: diff --git a/docs/en/base/index.rst b/docs/en/base/index.rst index 67a1eb8d..d0f4a609 100644 --- a/docs/en/base/index.rst +++ b/docs/en/base/index.rst @@ -6,6 +6,7 @@ Base atom_can.rst atom_socket.rst + display.rst echo.rst motion.rst rs232.rst diff --git a/docs/en/refs/base.display.ref b/docs/en/refs/base.display.ref new file mode 100644 index 00000000..38f2f928 --- /dev/null +++ b/docs/en/refs/base.display.ref @@ -0,0 +1,36 @@ +.. |Atomic Display Base| image:: https://m5stack.oss-cn-shenzhen.aliyuncs.com/resource/docs/products/atom/Atomic%20Display%20Base/4.webp + :target: https://docs.m5stack.com/en/atom/Atomic%20Display%20Base + :height: 200px + :width: 200px + +.. |Atom Display| image:: https://static-cdn.m5stack.com/resource/docs/products/atom/atom_display/atom_display_01.webp + :target: https://docs.m5stack.com/en/atom/atom_display + :height: 200px + :width: 200px + +.. |Atom Display-Lite| image:: https://static-cdn.m5stack.com/resource/docs/products/atom/atom_display_lite/atom_display_lite_01.webp + :target: https://docs.m5stack.com/en/atom/atom_display_lite + :height: 200px + :width: 200px + +.. |init.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/display/init.png +.. |example_draw_text.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/display/example_draw_text.png +.. |example_draw_image.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/display/example_draw_image.png + +.. |atoms3_draw_text_example.m5f2| raw:: html + + + atoms3_draw_text_example.m5f2 + + +.. |atoms3_draw_image_example.m5f2| raw:: html + + + atoms3_draw_image_example.m5f2 + diff --git a/docs/locales/zh_CN/LC_MESSAGES/base/display.po b/docs/locales/zh_CN/LC_MESSAGES/base/display.po new file mode 100644 index 00000000..fdcea37b --- /dev/null +++ b/docs/locales/zh_CN/LC_MESSAGES/base/display.po @@ -0,0 +1,251 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2016 - 2025 M5Stack Technology Co., Ltd +# This file is distributed under the same license as the UIFlow2 Programming +# Guide package. +# FIRST AUTHOR , 2025. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: UIFlow2 Programming Guide \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-02-26 17:08+0800\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: zh_CN\n" +"Language-Team: zh_CN \n" +"Plural-Forms: nplurals=1; plural=0;\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.16.0\n" + +#: ../../en/base/display.rst:2 ../../en/refs/base.display.ref +#: 6b9a1269a4ce4e32adcc54de7a8b17e0 c80823ae96ee40568d65bc23c7b95bd3 +msgid "Atomic Display Base" +msgstr "" + +#: ../../en/base/display.rst:8 8e8ca515fb284cca9faab18af4c3ed70 +msgid "" +"The is the class of the Atomic Display Base, which is used to display " +"images and text on the screen." +msgstr "这是 Atomic Display Base 类,用于在屏幕上显示图像和文本。" + +#: ../../en/base/display.rst:10 2931980b38214c55b7a7fdaebf7f473d +msgid "Support the following products:" +msgstr "支持以下产品:" + +#: ../../en/base/display.rst:13 6184298596524f2fa0ca687545c465a9 +msgid "|Atomic Display Base|" +msgstr "" + +#: ../../en/base/display.rst:13 0dc38e1d2fea4e7da248379a560bb9f2 +msgid "|Atom Display|" +msgstr "" + +#: ../../en/refs/base.display.ref 19a05a547c0c4b29a4c0a4ccedd5d564 +msgid "Atom Display" +msgstr "" + +#: ../../en/base/display.rst:13 744e7ad5c5664e4393611ef8a4d6638e +msgid "|Atom Display-Lite|" +msgstr "" + +#: ../../en/refs/base.display.ref a4fe4f84055e42c3a6c1a8786a4bccc2 +msgid "Atom Display-Lite" +msgstr "" + +#: ../../en/base/display.rst:16 409d38f0ce9b45078bfda8b292e47801 +msgid "Below is the detailed support for Atomic Display Base on the host:" +msgstr "以下是主机对Atomic Display Base的详细支持:" + +#: ../../en/base/display.rst:23 9f4ee1cc74694cd1b669683143c14730 +msgid "Controller" +msgstr "主机" + +#: ../../en/base/display.rst:23 88701da956534034b142af8f2969661c +msgid "Status" +msgstr "状态" + +#: ../../en/base/display.rst:25 10a9a01a980a466b9610c1bf43c41135 +msgid "Atom Echo" +msgstr "" + +#: ../../en/base/display.rst:25 297bef2397364488a328bb0ec4481d48 +msgid "|O|" +msgstr "" + +#: ../../en/base/display.rst:27 098b341a9e6a48c584be00269e0d519b +msgid "Atom Lite" +msgstr "" + +#: ../../en/base/display.rst:27 ../../en/base/display.rst:29 +#: ../../en/base/display.rst:31 ../../en/base/display.rst:33 +#: ../../en/base/display.rst:35 ../../en/base/display.rst:37 +#: ../../en/base/display.rst:39 844f6f72a1b2402e8381697e46654c97 +msgid "|S|" +msgstr "" + +#: ../../en/base/display.rst:29 9fad62c3c7f842ec8e4cd4fed12a0654 +msgid "Atom Matrix" +msgstr "" + +#: ../../en/base/display.rst:31 38cd6f06a2104da3b55c46e2fff0d993 +msgid "AtomS3" +msgstr "" + +#: ../../en/base/display.rst:33 3bb31c0b84c54f70aa3d362d6ade33b4 +msgid "AtomS3 Lite" +msgstr "" + +#: ../../en/base/display.rst:35 8617e6afca65449cade8f17b77f807b4 +msgid "AtomS3R" +msgstr "" + +#: ../../en/base/display.rst:37 fa31cb4758c34f4fa0b05ec1d7d1e3ef +msgid "AtomS3R-CAM" +msgstr "" + +#: ../../en/base/display.rst:39 ba30ab557cb948da96b339282a6f70e6 +msgid "AtomS3R-Ext" +msgstr "" + +#: ../../en/base/display.rst:45 c90abaccae354e6495b6c88869e9c177 +msgid "|S|: Supported." +msgstr "|S|: 支持" + +#: ../../en/base/display.rst:47 7f94355f4b914d34ab1e3c85d248bd23 +msgid "|O|: Optional, It conflicts with some internal resource of the host." +msgstr "|O|: 可选,它与主机的一些内部资源冲突。" + +#: ../../en/base/display.rst:51 a7f602efdaaa4c35adf460b21114704b +msgid "UiFlow2 Example" +msgstr "UiFlow2 应用示例" + +#: ../../en/base/display.rst:54 ../../en/base/display.rst:89 +#: 8df7b97819ce4a9f9a1e1d3558f8eb8d ed907a3c56ed42a395f0eb7f3ca01343 +msgid "Draw Text" +msgstr "绘制文本" + +#: ../../en/base/display.rst:56 ../../en/base/display.rst:72 +#: 005c478c8d104c60b95dbc4c30dc721b +msgid "Open the |atoms3_draw_text_example.m5f2| project in UiFlow2." +msgstr "在 UiFlow2 中打开 |atoms3_draw_text_example.m5f2| 项目。" + +#: ../../en/base/display.rst:58 ../../en/base/display.rst:91 +#: b089c8f0feff4d78bea6a5d1599ce3fb +msgid "This example displays the text \"M5Stack\" on the screen." +msgstr "此示例在屏幕上显示文本“M5Stack”。" + +#: ../../en/base/display.rst:60 ../../en/base/display.rst:76 +#: base.display.AtomicDisplayBase:12 d5c3efe3d69c4d45b85484074e40e3e4 of +msgid "UiFlow2 Code Block:" +msgstr "UiFlow2 代码块:" + +#: ../../en/base/display.rst:62 3b60afe73a48423fa6299246ecaddab1 +msgid "|example_draw_text.png|" +msgstr "" + +#: ../../en/refs/base.display.ref:17 9581dfc4351848098b5846e751efcfee +msgid "example_draw_text.png" +msgstr "" + +#: ../../en/base/display.rst:64 ../../en/base/display.rst:80 +#: ../../en/base/display.rst:99 ../../en/base/display.rst:113 +#: 71405296bff44f6aa4112738919f5f37 +msgid "Example output:" +msgstr "示例输出:" + +#: ../../en/base/display.rst:66 ../../en/base/display.rst:82 +#: ../../en/base/display.rst:101 ../../en/base/display.rst:115 +#: 8353119b8dea478b9f01f01712d446ae +msgid "None" +msgstr "" + +#: ../../en/base/display.rst:70 ../../en/base/display.rst:105 +#: 5476d81e5f774298929022b6af9c15f2 c6d3f223ea5d454ebf04dd9b12d6c09b +msgid "Draw Image" +msgstr "绘制图像" + +#: ../../en/base/display.rst:74 ../../en/base/display.rst:107 +#: b089c8f0feff4d78bea6a5d1599ce3fb +msgid "This example displays the image on the screen." +msgstr "此示例在屏幕上显示图像。" + +#: ../../en/base/display.rst:78 eba24a0fbe9b40f6a4e338e02d7bb918 +msgid "|example_draw_image.png|" +msgstr "" + +#: ../../en/refs/base.display.ref:18 9227a06eb513423091e9ab8735027f10 +msgid "example_draw_image.png" +msgstr "" + +#: ../../en/base/display.rst:86 f47a46f53a55496e883829adab13074d +msgid "MicroPython Example" +msgstr "" + +#: ../../en/base/display.rst:93 ../../en/base/display.rst:109 +#: af3327c84e5b46f9a7032c33ab7f0563 base.display.AtomicDisplayBase:16 of +msgid "MicroPython Code Block:" +msgstr "MicroPython 应用示例" + +#: ../../en/base/display.rst:119 3150ae65a3d04e1cadbcb5d431636095 +msgid "**API**" +msgstr "API参考" + +#: ../../en/base/display.rst:122 ee1922f3f89848ae8ac2869a3586e9c4 +msgid "AtomicDisplayBase" +msgstr "" + +#: base.display.AtomicDisplayBase:1 e47f501ab6244bd3b9c3b172a18b3571 of +msgid "Bases: :py:class:`object`" +msgstr "" + +#: 386efeb6dbba4a61b712e448f9d5277b base.display.AtomicDisplayBase:1 of +msgid "Initialize the Atomic Display Base." +msgstr "初始化 Atomic Display Base。" + +#: base.display.AtomicDisplayBase bfe8a6c827ae4061ac2da3f9474fbbae of +msgid "Parameters" +msgstr "" + +#: 8e8ca515fb284cca9faab18af4c3ed70 base.display.AtomicDisplayBase:3 of +msgid "The logical width of the Atomic Display Base. Default is 1280px." +msgstr "Atomic Display Base 的逻辑宽度。默认值为 1280px。" + +#: ae255d0226c44c759f0d609c5062dd60 base.display.AtomicDisplayBase:4 of +msgid "The logical height of the Atomic Display Base. Default is 720px." +msgstr "Atomic Display Base 的逻辑高度。默认值为 720px。" + +#: 8e8ca515fb284cca9faab18af4c3ed70 base.display.AtomicDisplayBase:5 of +msgid "The refresh rate of the Atomic Display Base. Default is 60Hz." +msgstr "Atomic Display Base 的刷新率。默认为 60Hz。" + +#: 8e8ca515fb284cca9faab18af4c3ed70 base.display.AtomicDisplayBase:6 of +msgid "The width of the output of the Atomic Display Base. Default is 1280px." +msgstr "Atomic Display Base 输出的宽度。默认为 1280px。" + +#: 8e8ca515fb284cca9faab18af4c3ed70 base.display.AtomicDisplayBase:7 of +msgid "The height of the output of the Atomic Display Base. Default is 720px." +msgstr "Atomic Display Base 输出的高度。默认为 720px。" + +#: 8e8ca515fb284cca9faab18af4c3ed70 base.display.AtomicDisplayBase:8 of +msgid "The scale width of the Atomic Display Base. Default is 1." +msgstr "Atomic Display Base 的宽度缩放比例因子。默认值为 1。" + +#: 8e8ca515fb284cca9faab18af4c3ed70 base.display.AtomicDisplayBase:9 of +msgid "The scale height of the Atomic Display Base. Default is 1." +msgstr "Atomic Display Base 的高度缩放比例因子。默认值为 1。" + +#: 8e8ca515fb284cca9faab18af4c3ed70 base.display.AtomicDisplayBase:10 of +msgid "The pixel clock of the Atomic Display Base. Default is 74250000." +msgstr "Atomic Display Base 的像素时钟。默认值为 74250000。" + +#: 22d2591127284c5ebe070b40311d32dc base.display.AtomicDisplayBase:14 of +msgid "|init.png|" +msgstr "" + +#: ../../en/refs/base.display.ref:16 b3777a1d02094eae8bf61f8cdaacc67d +msgid "init.png" +msgstr "" + diff --git a/examples/base/display/atoms3_draw_image_example.m5f2 b/examples/base/display/atoms3_draw_image_example.m5f2 new file mode 100644 index 00000000..6cb849ad --- /dev/null +++ b/examples/base/display/atoms3_draw_image_example.m5f2 @@ -0,0 +1 @@ +{"version":"V2.0","versionNumber":"V2.2.2","type":"atoms3","components":[{"name":"screen","type":"screen","layer":0,"screenId":"builtin","screenName":"","id":"__atoms3_screen","createTime":1740557542829,"x":0,"y":0,"width":128,"height":128,"backgroundColor":"#000000","size":0,"isSelected":true},{"name":"screen","type":"screen","layer":0,"screenId":"y%sAFJ36@%nK%Lq=","screenName":"base_display","id":"__display_screen","createTime":1740557552820,"x":0,"y":0,"width":1280,"height":720,"backgroundColor":"#000000","size":0,"isSelected":true},{"name":"image0","type":"image","layer":3,"screenId":"builtin","screenName":"","id":"iKHSAR^Ci09f=Ara","createTime":1740558166503,"x":51,"y":51,"imagePath":"default.jpg","visibility":true,"scaleX":1,"scaleY":1,"isSelected":false},{"name":"image1","type":"image","layer":3,"screenId":"y%sAFJ36@%nK%Lq=","screenName":"base_display","id":"trB$1B_CSC&pJ&WG","createTime":1740558193941,"x":443,"y":213,"imagePath":"default.jpg","visibility":true,"scaleX":"10","scaleY":"10","isSelected":false}],"resources":[{"hardware":["hardware_button","hardware_pin_button","imu","ir"]},{"base":["base_display"]}],"units":[],"hats":[],"bases":[{"type":"base_display","name":"base_display","id":"z1QJmf+v2TGYYZOs","createTime":1740557546796,"initBlockType":"base_display_init","initBlockId":"~hBEW^iORHqm|5#.:ayd"}],"i2cs":[],"blockly":"truetrue12807201280720601174250000image0default.jpgimage1default.jpgtrue","screen":[{"simulationName":"Built-in","type":"builtin","width":128,"height":128,"scale":1.3,"screenName":"","blockId":"","screenColorType":0,"id":"builtin","createTime":1740557542826},{"simulationName":"display_0","type":"display","width":1280,"height":720,"scale":0.234,"screenName":"base_display","blockId":"~hBEW^iORHqm|5#.:ayd","screenColorType":0,"id":"y%sAFJ36@%nK%Lq=","createTime":1740557552803,"fullZoom":1.976}],"logicWhenNum":0,"customList":[]} \ No newline at end of file diff --git a/examples/base/display/atoms3_draw_image_example.py b/examples/base/display/atoms3_draw_image_example.py new file mode 100644 index 00000000..c3696f75 --- /dev/null +++ b/examples/base/display/atoms3_draw_image_example.py @@ -0,0 +1,55 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import os, sys, io +import M5 +from M5 import * +from base import AtomicDisplayBase + + +image0 = None +image1 = None +base_display = None + + +def setup(): + global image0, image1, base_display + + M5.begin() + image0 = Widgets.Image("res/img/default.jpg", 51, 51, scale_x=1, scale_y=1) + + base_display = AtomicDisplayBase( + width=1280, + height=720, + refresh_rate=60, + output_width=1280, + output_height=720, + scale_w=1, + scale_h=1, + pixel_clock=74250000, + ) + image1 = Widgets.Image( + "res/img/default.jpg", 443, 213, scale_x=10, scale_y=10, parent=base_display + ) + image0.setImage("res/img/default.jpg") + image1.setImage("res/img/default.jpg") + + +def loop(): + global image0, image1, base_display + M5.update() + + +if __name__ == "__main__": + try: + setup() + while True: + loop() + except (Exception, KeyboardInterrupt) as e: + try: + from utility import print_error_msg + + print_error_msg(e) + except ImportError: + print("please update to latest firmware") diff --git a/examples/base/display/atoms3_draw_text_example.m5f2 b/examples/base/display/atoms3_draw_text_example.m5f2 new file mode 100644 index 00000000..ad378f56 --- /dev/null +++ b/examples/base/display/atoms3_draw_text_example.m5f2 @@ -0,0 +1 @@ +{"version":"V2.0","versionNumber":"V2.2.2","type":"atoms3","components":[{"name":"screen","type":"screen","layer":0,"screenId":"builtin","screenName":"","id":"__atoms3_screen","createTime":1740557542829,"x":0,"y":0,"width":128,"height":128,"backgroundColor":"#000000","size":0,"isSelected":true},{"name":"screen","type":"screen","layer":0,"screenId":"y%sAFJ36@%nK%Lq=","screenName":"base_display","id":"__display_screen","createTime":1740557552820,"x":0,"y":0,"width":1280,"height":720,"backgroundColor":"#000000","size":0,"isSelected":true},{"name":"label0","type":"label","layer":1,"screenId":"y%sAFJ36@%nK%Lq=","screenName":"base_display","id":"mQDbLlBPKLkGg5qq","createTime":1740557555516,"x":466,"y":318,"color":"#ffffff","backgroundColor":"#222222","text":"M5STACK","engine":"gfx","font":"Widgets.FONTS.DejaVu72","rotation":0,"isSelected":false},{"name":"label1","type":"label","layer":2,"screenId":"builtin","screenName":"","id":"q3w!bRZOHZUyI@5_","createTime":1740557965567,"x":23,"y":53,"color":"#ffffff","backgroundColor":"#222222","text":"M5Stack","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false}],"resources":[{"hardware":["hardware_button","hardware_pin_button","imu","ir"]},{"base":["base_display"]}],"units":[],"hats":[],"bases":[{"type":"base_display","name":"base_display","id":"z1QJmf+v2TGYYZOs","createTime":1740557546796,"initBlockType":"base_display_init","initBlockId":"~hBEW^iORHqm|5#.:ayd"}],"i2cs":[],"blockly":"truefalse12807201280720601174250000true","screen":[{"simulationName":"Built-in","type":"builtin","width":128,"height":128,"scale":1.3,"screenName":"","blockId":"","screenColorType":0,"id":"builtin","createTime":1740557542826},{"simulationName":"display_0","type":"display","width":1280,"height":720,"scale":0.234,"screenName":"base_display","blockId":"~hBEW^iORHqm|5#.:ayd","screenColorType":0,"id":"y%sAFJ36@%nK%Lq=","createTime":1740557552803,"fullZoom":1.976}],"logicWhenNum":0,"customList":[]} \ No newline at end of file diff --git a/examples/base/display/atoms3_draw_text_example.py b/examples/base/display/atoms3_draw_text_example.py new file mode 100644 index 00000000..6696c15f --- /dev/null +++ b/examples/base/display/atoms3_draw_text_example.py @@ -0,0 +1,53 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import os, sys, io +import M5 +from M5 import * +from base import AtomicDisplayBase + + +label0 = None +label1 = None +base_display = None + + +def setup(): + global label0, label1, base_display + + M5.begin() + label1 = Widgets.Label("M5Stack", 23, 53, 1.0, 0xFFFFFF, 0x222222, Widgets.FONTS.DejaVu18) + + base_display = AtomicDisplayBase( + width=1280, + height=720, + refresh_rate=60, + output_width=1280, + output_height=720, + scale_w=1, + scale_h=1, + pixel_clock=74250000, + ) + label0 = Widgets.Label( + "M5STACK", 466, 318, 1.0, 0xFFFFFF, 0x222222, Widgets.FONTS.DejaVu72, base_display + ) + + +def loop(): + global label0, label1, base_display + M5.update() + + +if __name__ == "__main__": + try: + setup() + while True: + loop() + except (Exception, KeyboardInterrupt) as e: + try: + from utility import print_error_msg + + print_error_msg(e) + except ImportError: + print("please update to latest firmware") diff --git a/m5stack/components/M5Unified/mpy_m5unified.cpp b/m5stack/components/M5Unified/mpy_m5unified.cpp index bc8dfa86..994bfe09 100644 --- a/m5stack/components/M5Unified/mpy_m5unified.cpp +++ b/m5stack/components/M5Unified/mpy_m5unified.cpp @@ -108,6 +108,44 @@ static void m5_config_helper_module_display(mp_obj_t config_obj, m5::M5Unified:: } } +static void m5_config_helper_atom_display(mp_obj_t config_obj, m5::M5Unified::config_t &cfg) { + if (!MP_OBJ_IS_TYPE(config_obj, &mp_type_dict)) { + mp_raise_TypeError("atom_display must be a dict"); + } + + cfg.atom_display = M5AtomDisplay::config_t(); + mp_map_t *config_map = mp_obj_dict_get_map(config_obj); + + for (size_t i = 0; i < config_map->alloc; i++) { + if (MP_MAP_SLOT_IS_FILLED(config_map, i)) { + mp_obj_t key = config_map->table[i].key; + mp_obj_t value = config_map->table[i].value; + + const char *key_str = mp_obj_str_get_str(key); + + if (strcmp(key_str, "enabled") == 0) { + cfg.external_display.atom_display = mp_obj_is_true(value); + } else if (strcmp(key_str, "width") == 0) { + cfg.atom_display.logical_width = mp_obj_get_int(value); + } else if (strcmp(key_str, "height") == 0) { + cfg.atom_display.logical_height = mp_obj_get_int(value); + } else if (strcmp(key_str, "refresh_rate") == 0) { + cfg.atom_display.refresh_rate = mp_obj_get_int(value); + } else if (strcmp(key_str, "output_width") == 0) { + cfg.atom_display.output_width = mp_obj_get_int(value); + } else if (strcmp(key_str, "output_height") == 0) { + cfg.atom_display.output_height = mp_obj_get_int(value); + } else if (strcmp(key_str, "scale_w") == 0) { + cfg.atom_display.scale_w = mp_obj_get_int(value); + } else if (strcmp(key_str, "scale_h") == 0) { + cfg.atom_display.scale_h = mp_obj_get_int(value); + } else if (strcmp(key_str, "pixel_clock") == 0) { + cfg.atom_display.pixel_clock = mp_obj_get_int(value); + } + } + } +} + #if CONFIG_IDF_TARGET_ESP32 static void m5_config_helper_module_rca(mp_obj_t config_obj, m5::M5Unified::config_t &cfg) { if (!MP_OBJ_IS_TYPE(config_obj, &mp_type_dict)) { @@ -219,6 +257,8 @@ static void m5_config_helper(mp_obj_t config_obj, m5::M5Unified::config_t &cfg, if (strcmp(key_str, "module_display") == 0) { m5_config_helper_module_display(value, cfg); + } else if (strcmp(key_str, "atom_display") == 0) { + m5_config_helper_atom_display(value, cfg); } #if CONFIG_IDF_TARGET_ESP32 else if (strcmp(key_str, "module_rca") == 0) { @@ -279,6 +319,14 @@ mp_obj_t m5_add_display(mp_obj_t i2c_bus_in, mp_obj_t addr_in, mp_obj_t dict) { } else { mp_raise_NotImplementedError("module_display is not supported on this board"); } + } else if (cfg.external_display.atom_display) { + M5AtomDisplay dsp(cfg.atom_display); + if (dsp.init_without_reset()) { + M5.addDisplay(dsp); + mp_printf(&mp_plat_print, "atom_display added\n"); + } else { + mp_raise_msg_varg(&mp_type_OSError, MP_ERROR_TEXT("AtomDisplay init failed")); + } } #if CONFIG_IDF_TARGET_ESP32 else if (cfg.external_display.module_rca) { diff --git a/m5stack/libs/base/__init__.py b/m5stack/libs/base/__init__.py index 85603158..44093a46 100644 --- a/m5stack/libs/base/__init__.py +++ b/m5stack/libs/base/__init__.py @@ -6,6 +6,7 @@ "ATOMCANBase": "atom_can", "ATOMSocketBase": "atom_socket", "ATOMEchoBase": "echo", + "AtomicDisplayBase": "display", "Motion": "motion", "MotionBase": "motion", "AtomRS232": "rs232", diff --git a/m5stack/libs/base/display.py b/m5stack/libs/base/display.py new file mode 100644 index 00000000..41add199 --- /dev/null +++ b/m5stack/libs/base/display.py @@ -0,0 +1,61 @@ +# SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + + +import M5 + + +class AtomicDisplayBase: + """Initialize the Atomic Display Base. + + :param int width: The logical width of the Atomic Display Base. Default is 1280px. + :param int height: The logical height of the Atomic Display Base. Default is 720px. + :param int refresh_rate: The refresh rate of the Atomic Display Base. Default is 60Hz. + :param int output_width: The width of the output of the Atomic Display Base. Default is 1280px. + :param int output_height: The height of the output of the Atomic Display Base. Default is 720px. + :param int scale_w: The scale width of the Atomic Display Base. Default is 1. + :param int scale_h: The scale height of the Atomic Display Base. Default is 1. + :param int pixel_clock: The pixel clock of the Atomic Display Base. Default is 74250000. + + UiFlow2 Code Block: + + |init.png| + + MicroPython Code Block: + + .. code-block:: python + + from base import AtomicDisplayBase + atom_display = AtomicDisplayBase(1280, 720, 60, 1280, 720, 1, 1, 74250000) + """ + + def __new__( + cls, + width: int = 1280, + height: int = 720, + refresh_rate: int = 60, + output_width: int = 1280, + output_height: int = 720, + scale_w: int = 1, + scale_h: int = 1, + pixel_clock: int = 74250000, + ) -> None: + return M5.addDisplay( + None, + 0, + { + "atom_display": { + "enabled": True, + # see to M5AtomDisplay::config_t + "width": width, + "height": height, + "refresh_rate": refresh_rate, + "output_width": output_width, + "output_height": output_height, + "scale_w": scale_w, + "scale_h": scale_h, + "pixel_clock": pixel_clock, + } + }, + ) diff --git a/m5stack/libs/base/manifest.py b/m5stack/libs/base/manifest.py index 8b7fc5b5..28a17eed 100644 --- a/m5stack/libs/base/manifest.py +++ b/m5stack/libs/base/manifest.py @@ -7,6 +7,7 @@ "__init__.py", "atom_can.py", "atom_socket.py", + "display.py", "echo.py", "motion.py", "rs232.py", From 85aafc32ec70e93c8ceb441918a8fb7ee604bf85 Mon Sep 17 00:00:00 2001 From: Tinyu-Zhao Date: Thu, 27 Feb 2025 10:48:49 +0800 Subject: [PATCH 003/322] docs: Updated Atom CAN Base usage examples and documentation. Signed-off-by: Tinyu-Zhao --- docs/en/base/atom_can.rst | 91 +++----- docs/en/refs/base.can.ref | 22 +- .../zh_CN/LC_MESSAGES/base/atom_can.po | 202 +++++++----------- examples/base/can/atoms3_can_example.m5f2 | 1 + examples/base/can/atoms3_can_example.py | 55 +++++ .../module/pwrcan/pwrcan_cores3_example.m5f2 | 2 +- .../module/pwrcan/pwrcan_cores3_example.py | 14 +- m5stack/libs/base/atom_can.py | 24 ++- 8 files changed, 206 insertions(+), 205 deletions(-) create mode 100644 examples/base/can/atoms3_can_example.m5f2 create mode 100644 examples/base/can/atoms3_can_example.py diff --git a/docs/en/base/atom_can.rst b/docs/en/base/atom_can.rst index 4e8b1229..28227864 100644 --- a/docs/en/base/atom_can.rst +++ b/docs/en/base/atom_can.rst @@ -1,89 +1,62 @@ ATOM CAN Base ============== +.. sku: A103/KO57 + .. include:: ../refs/base.can.ref -The following products are supported: +This is the driver library for the ATOM CAN Base to accept and send data from the CAN module. + +Support the following products: ================== ================== |Atom CAN| |Atomic CAN Base| ================== ================== -Micropython TX Example: - - .. literalinclude:: ../../../examples/unit/can/stickc_plus2_can_tx_example.py - :language: python - :linenos: +UiFlow2 Example +--------------- +CAN communication +^^^^^^^^^^^^^^^^^ -Micropython RX Example: - - .. literalinclude:: ../../../examples/unit/can/dial_can_rx_example.py - :language: python - :linenos: - +Open the |atoms3_can_example.m5f2| project in UiFlow2. -UIFLOW2 TX Example: +This example shows how to receive and send data using the Atom CAN Base. - |tx_example.png| +UiFlow2 Code Block: + |example.png| -UIFLOW2 RX Example: +Example output: - |rx_example.png| + Output of received CAN message data via serial port. +MicroPython Example +------------------- -.. only:: builder_html +CAN communication +^^^^^^^^^^^^^^^^^^ - |stickc_plus2_can_tx_example.m5f2| +This example shows how to receive and send data using the Atom CAN Base. - |dial_can_rx_example.m5f2| +MicroPython Code Block: -class ATOMCANBase ------------------ - -Constructors ------------- - -.. class:: ATOMCANBase(id, tx, rx, mode, baudrate) - - Create an ATOMCANBase object. - - parameter is: - - - ``id`` is the ID of the CAN bus - - ``tx`` is the pin to use for transmitting data - - ``rx`` is the pin to use for receiving data - - ``mode`` is one of: NORMAL, NO_ACKNOWLEDGE, LISTEN_ONLY - - ``baudrate`` is the baudrate of ATOMCANBase - - UIFLOW2: + .. literalinclude:: ../../../examples/base/can/atoms3_can_example.py + :language: python + :linenos: - |init.png| +Example output: -.. class:: ATOMCANBase(id, tx, rx, mode, prescaler, sjw, bs1, bs2, triple_sampling=False) - :no-index: - - Initialise the CAN bus with the given parameters: + Output of received CAN message data via serial port. - - ``id`` is the ID of the CAN bus - - ``tx`` is the pin to use for transmitting data - - ``rx`` is the pin to use for receiving data - - ``mode`` is one of: NORMAL, NO_ACKNOWLEDGE, LISTEN_ONLY - - ``prescaler`` is the value by which the CAN input clock is divided to generate the - nominal bit time quanta. The prescaler can be a value between 1 and 1024 inclusive - for classic CAN. - - ``sjw`` is the resynchronisation jump width in units of time quanta for nominal bits; - it can be a value between 1 and 4 inclusive for classic CAN. - - ``bs1`` defines the location of the sample point in units of the time quanta for nominal bits; - it can be a value between 1 and 16 inclusive for classic CAN. - - ``bs2`` defines the location of the transmit point in units of the time quanta for nominal bits; - it can be a value between 1 and 8 inclusive for classic CAN. - - ``triple_sampling`` is Enables triple sampling when the TWAI controller samples a bit +**API** +------- - UIFLOW2: +ATOMCANBase +^^^^^^^^^^^ - |init_advanced.png| +.. autoclass:: base.atom_can.ATOMCANBase + :members: ATOMCANBase class inherits CAN class, See :ref:`hardware.CAN ` for more details. diff --git a/docs/en/refs/base.can.ref b/docs/en/refs/base.can.ref index a3b881c5..89ef13a3 100644 --- a/docs/en/refs/base.can.ref +++ b/docs/en/refs/base.can.ref @@ -10,27 +10,13 @@ .. |init.png| image:: https://m5stack.oss-cn-shenzhen.aliyuncs.com/mpy_docs/base/can/init.png -.. |init_advanced.png| image:: https://m5stack.oss-cn-shenzhen.aliyuncs.com/mpy_docs/base/can/init_advanced.png +.. |example.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/can/example.png - -.. |tx_example.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/can/tx_example.png - -.. |stickc_plus2_can_tx_example.m5f2| raw:: html - - - stickc_plus2_can_tx_example.m5f2 - - -.. |rx_example.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/can/rx_example.png - -.. |dial_can_rx_example.m5f2| raw:: html +.. |atoms3_can_example.m5f2| raw:: html - dial_can_rx_example.m5f2 + atoms3_can_example.m5f2 \ No newline at end of file diff --git a/docs/locales/zh_CN/LC_MESSAGES/base/atom_can.po b/docs/locales/zh_CN/LC_MESSAGES/base/atom_can.po index ed69524e..007af123 100644 --- a/docs/locales/zh_CN/LC_MESSAGES/base/atom_can.po +++ b/docs/locales/zh_CN/LC_MESSAGES/base/atom_can.po @@ -9,7 +9,7 @@ msgid "" msgstr "" "Project-Id-Version: UIFlow2 Programming Guide \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-01-16 15:35+0800\n" +"POT-Creation-Date: 2025-02-27 10:42+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language: zh_CN\n" @@ -20,172 +20,134 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.16.0\n" -#: ../../en/base/atom_can.rst:2 8f4a89cae73240c7bab6fb13269c3f8a +#: ../../en/base/atom_can.rst:2 3497f684611a4999913631a952d2884a msgid "ATOM CAN Base" msgstr "" -#: ../../en/base/atom_can.rst:6 fccbc2111dac487381ecf1a76a9bb462 -msgid "The following products are supported:" -msgstr "" +#: ../../en/base/atom_can.rst:8 c02f250c268e4453b639a8218138a8da +msgid "" +"This is the driver library for the ATOM CAN Base to accept and send data " +"from the CAN module." +msgstr "这是 ATOM CAN Base 的驱动程序库,用于从 CAN 模块接受和发送数据。" + +#: ../../en/base/atom_can.rst:10 ea89d5c61f5246cf97315f53b588b80d +msgid "Support the following products:" +msgstr "支持以下产品:" -#: ../../en/base/atom_can.rst:9 aaa20f3cecc64da4805bfa83b7906a16 +#: ../../en/base/atom_can.rst:13 086e5e12ea6f4a5bac81f44a203124cb msgid "|Atom CAN|" msgstr "" -#: ../../en/refs/base.can.ref 004805b4ffac40bba30aa699cfb6be72 +#: ../../en/refs/base.can.ref b65458c8b6fa4cc88ff761875f435731 msgid "Atom CAN" msgstr "" -#: ../../en/base/atom_can.rst:9 600ab112c2eb4438b185fdb5c9f1849b +#: ../../en/base/atom_can.rst:13 3cd238e49d914e55981968c19ae028b8 msgid "|Atomic CAN Base|" msgstr "" -#: ../../en/refs/base.can.ref dbb776a88cca4d6683fba8c77321a65d +#: ../../en/refs/base.can.ref 74dc394dfcac47b1a3eae00394413c69 msgid "Atomic CAN Base" msgstr "" -#: ../../en/base/atom_can.rst:13 612e9cd2ad194f1a8ddf26d8b63b5002 -msgid "Micropython TX Example:" -msgstr "" +#: ../../en/base/atom_can.rst:18 502ae5c47de645ceb1e42d24ccdf0eaa +msgid "UiFlow2 Example" +msgstr "UIFLOW2 应用示例:" -#: ../../en/base/atom_can.rst:20 8fc1658abd4b4e248dcf2ce891f2767b -msgid "Micropython RX Example:" -msgstr "" +#: ../../en/base/atom_can.rst:21 ../../en/base/atom_can.rst:39 +#: 2e83a695c8bc4505a81ed870dcc9d469 +msgid "CAN communication" +msgstr "CAN 通讯" -#: ../../en/base/atom_can.rst:27 5961d99655814d5b8c765d75ccc88bb8 -msgid "UIFLOW2 TX Example:" -msgstr "" +#: ../../en/base/atom_can.rst:23 1c26c8c40e4c4b1c9bd9213d0d795018 +msgid "Open the |atoms3_can_example.m5f2| project in UiFlow2." +msgstr "在UiFlow2 中打开 |atoms3_can_example.m5f2| 项目。" -#: ../../en/base/atom_can.rst:29 a76dc0d70c8248a890e856453d9cd20f -msgid "|tx_example.png|" -msgstr "" +#: ../../en/base/atom_can.rst:25 ../../en/base/atom_can.rst:41 +#: 77ee4b221bde42809a6fb2d59d6b9661 +msgid "This example shows how to receive and send data using the Atom CAN Base." +msgstr "本示例演示如何使用 Atom CAN Base 接收和发送数据。" -#: ../../en/refs/base.can.ref:16 27b9295feb374010b85cdea45188b4d3 -msgid "tx_example.png" -msgstr "" +#: ../../en/base/atom_can.rst:27 0468680d4f4640399d5292156a6f0fba +#: base.atom_can.ATOMCANBase:9 of +msgid "UiFlow2 Code Block:" +msgstr "UiFlow2 代码块:" -#: ../../en/base/atom_can.rst:32 f47702f823264de4a7ac3c0cf083249d -msgid "UIFLOW2 RX Example:" +#: ../../en/base/atom_can.rst:29 af7198e3c7b846ad9f45a6a1a0804786 +msgid "|example.png|" msgstr "" -#: ../../en/base/atom_can.rst:34 5cd93c07af484e0e954831c0f992fc5e -msgid "|rx_example.png|" +#: ../../en/refs/base.can.ref:13 60ce6c7198e44e37acce71bd652c659d +msgid "example.png" msgstr "" -#: ../../en/refs/base.can.ref:27 9e359cc074a24e8c9d89f412e18d6ecd -msgid "rx_example.png" -msgstr "" +#: ../../en/base/atom_can.rst:31 ../../en/base/atom_can.rst:49 +#: 1ba9c05d25674a73812f3c8490e4addd +msgid "Example output:" +msgstr "示例输出:" -#: ../../en/base/atom_can.rst:39 46035fba3ad5449d91d2f7e1b318f90c -msgid "|stickc_plus2_can_tx_example.m5f2|" -msgstr "" +#: ../../en/base/atom_can.rst:33 ../../en/base/atom_can.rst:51 +#: 1fc543f451b44786bbf3677d6ab05fae e858f1882d0c41ebb04341d3cff6fc40 +msgid "Output of received CAN message data via serial port." +msgstr "通过串行端口输出接收到的 CAN 通讯数据。" -#: ../../en/base/atom_can.rst:41 1843c35bceb445acaecf7e30f5015346 -msgid "|dial_can_rx_example.m5f2|" -msgstr "" +#: ../../en/base/atom_can.rst:36 5dedb1e55d3b4c0f98cb4df2dbfa4903 +msgid "MicroPython Example" +msgstr "MicroPython 应用示例:" -#: ../../en/base/atom_can.rst:44 3dea2e0b9e3d455297b715e0851d610e -msgid "class ATOMCANBase" -msgstr "" +#: ../../en/base/atom_can.rst:43 5dedb1e55d3b4c0f98cb4df2dbfa4903 +#: base.atom_can.ATOMCANBase:13 of +msgid "MicroPython Code Block:" +msgstr "MicroPython 代码块:" -#: ../../en/base/atom_can.rst:47 82cb56db438e4c39b84bfd564f1d276f -msgid "Constructors" +#: ../../en/base/atom_can.rst:54 d255e6250d644684a26fc7bafe559ad2 +msgid "**API**" msgstr "" -#: ../../en/base/atom_can.rst:51 ee76969285504f5bb0cdd0e22eb215bf -msgid "Create an ATOMCANBase object." +#: ../../en/base/atom_can.rst:57 045ffa87322a48288254b73ef0e12464 +msgid "ATOMCANBase" msgstr "" -#: ../../en/base/atom_can.rst:53 57427169a60b46b79755bae3ae309480 -msgid "parameter is:" +#: base.atom_can.ATOMCANBase:1 bc2a7cf8bea3452e923dc2754f730592 of +msgid "Bases: :py:class:`~m5can.CAN`" msgstr "" -#: ../../en/base/atom_can.rst:55 ../../en/base/atom_can.rst:70 -#: 2996b4031a014d95ab91866585ac93b5 8a42ba5ab0ad447390d3b4dba0fe9776 -msgid "``id`` is the ID of the CAN bus" -msgstr "" +#: ac6ee4c7a7ad415a8182770911bba837 base.atom_can.ATOMCANBase:1 of +msgid "Create an ATOMCANBase object" +msgstr "创建 ATOMCANBase 对象。" -#: ../../en/base/atom_can.rst:56 ../../en/base/atom_can.rst:71 -#: 1a9d0e0e415440e2b6cedf7f20ffb2a8 68de2445607c48dabaaa193c4342ab39 -msgid "``tx`` is the pin to use for transmitting data" -msgstr "" +#: base.atom_can.ATOMCANBase bbf4ea362fa442baaf051e7d0279875d of +msgid "Parameters" +msgstr "参数" -#: ../../en/base/atom_can.rst:57 ../../en/base/atom_can.rst:72 -#: 073e63164e2749d8b87f960c812d12b1 22bccf1d4a524c509031b769bd25e7c9 -msgid "``rx`` is the pin to use for receiving data" -msgstr "" +#: 56daed2b74a042aeb5fed675194b8768 base.atom_can.ATOMCANBase:3 of +msgid "The CAN ID to use, Default is 0." +msgstr "CAN 通讯使用的 ID 号, 默认为0" -#: ../../en/base/atom_can.rst:58 ../../en/base/atom_can.rst:73 -#: bbff82a238b647c28c0fe2a57cddce54 fd4a211b745848c2aa44ddd2b87207a3 -msgid "``mode`` is one of: NORMAL, NO_ACKNOWLEDGE, LISTEN_ONLY" -msgstr "" +#: 4dc96c0227964a008126b7b398781711 base.atom_can.ATOMCANBase:4 of +msgid "A list or tuple containing the TX and RX pin numbers." +msgstr "包含 TX 和 RX 引脚编号的列表或元组。" -#: ../../en/base/atom_can.rst:59 404cfacf4b6c42519091337adba5883f -msgid "``baudrate`` is the baudrate of ATOMCANBase" -msgstr "" +#: 095a0ab0ef754842999c491628844929 base.atom_can.ATOMCANBase:6 of +msgid "" +"The CAN mode to use(NORMAL, NO_ACKNOWLEDGE, LISTEN_ONLY), Default is " +"NORMAL." +msgstr "使用的 CAN 模式(NORMAL、NO_ACKNOWLEDGE、LISTEN_ONLY),默认为 NORMAL。" -#: ../../en/base/atom_can.rst:61 ../../en/base/atom_can.rst:85 -#: 6ed61da91e234d31b308cc55093c35eb c2181e88bf964b0aaf22ab59c70b52a7 -msgid "UIFLOW2:" -msgstr "" +#: 705fa9fd527045b296731bb06ff2f312 base.atom_can.ATOMCANBase:7 of +msgid "The baudrate to use, Default is 1000000." +msgstr "使用的波特率,默认为 1000000。" -#: ../../en/base/atom_can.rst:63 364e9092aca1481497a605ccbbbeb6d2 +#: af7198e3c7b846ad9f45a6a1a0804786 base.atom_can.ATOMCANBase:11 of msgid "|init.png|" msgstr "" -#: ../../en/refs/base.can.ref:11 ff5cee4b87334dec9fb746c65937a997 +#: ../../en/refs/base.can.ref:11 1e7e9392cfb24494b38ebdd14473654c msgid "init.png" msgstr "" -#: ../../en/base/atom_can.rst:68 f9d7b34b26d44603bb016238cc0474fe -msgid "Initialise the CAN bus with the given parameters:" -msgstr "" - -#: ../../en/base/atom_can.rst:74 f38cd9743fa549888a31aaf578d139b7 -msgid "" -"``prescaler`` is the value by which the CAN input clock is divided to " -"generate the nominal bit time quanta. The prescaler can be a value " -"between 1 and 1024 inclusive for classic CAN." -msgstr "" - -#: ../../en/base/atom_can.rst:77 2a30add7765343a68dabf23189198f57 -msgid "" -"``sjw`` is the resynchronisation jump width in units of time quanta for " -"nominal bits; it can be a value between 1 and 4 inclusive for classic " -"CAN." -msgstr "" - -#: ../../en/base/atom_can.rst:79 64a82dc042c842c396165585ed4af5dc -msgid "" -"``bs1`` defines the location of the sample point in units of the time " -"quanta for nominal bits; it can be a value between 1 and 16 inclusive for" -" classic CAN." -msgstr "" - -#: ../../en/base/atom_can.rst:81 dd1120bb2f974962ad0cf54f91d63d5c -msgid "" -"``bs2`` defines the location of the transmit point in units of the time " -"quanta for nominal bits; it can be a value between 1 and 8 inclusive for " -"classic CAN." -msgstr "" - -#: ../../en/base/atom_can.rst:83 fc65c52917b7458ea13c1c6497316b8e -msgid "" -"``triple_sampling`` is Enables triple sampling when the TWAI controller " -"samples a bit" -msgstr "" - -#: ../../en/base/atom_can.rst:87 c29e5f22b85041bc8660fd68d968852a -msgid "|init_advanced.png|" -msgstr "" - -#: ../../en/refs/base.can.ref:13 1c69b9c04d8b4da584e63c7b3f026bbe -msgid "init_advanced.png" -msgstr "" - -#: ../../en/base/atom_can.rst:89 2a1e3a7fa6c84025a4a8b8c656bb9057 +#: ../../en/base/atom_can.rst:62 50f3d420d34241b596cb539ca7b905f0 msgid "" "ATOMCANBase class inherits CAN class, See :ref:`hardware.CAN " "` for more details." -msgstr "" - +msgstr "ATOMCANBase 类继承了 CAN 类,详情请参见 :ref:`hardware.CAN `。" \ No newline at end of file diff --git a/examples/base/can/atoms3_can_example.m5f2 b/examples/base/can/atoms3_can_example.m5f2 new file mode 100644 index 00000000..10e36d00 --- /dev/null +++ b/examples/base/can/atoms3_can_example.m5f2 @@ -0,0 +1 @@ +{"version":"V2.0","versionNumber":"V2.2.2","type":"atoms3","components":[{"name":"screen","type":"screen","layer":0,"screenId":"builtin","screenName":"","id":"__atoms3_screen","createTime":1740621384530,"x":0,"y":0,"width":128,"height":128,"backgroundColor":"#000000","size":0,"isSelected":true},{"name":"title0","type":"title","layer":1,"screenId":"builtin","screenName":"","id":"nJZmD892Cp%0EmJy","createTime":1740621834328,"x":0,"y":0,"color":"#ffffff","backgroundColor":"#0000FF","text":"CAN Base","textOffset":3,"font":"Widgets.FONTS.DejaVu18","isSelected":false},{"name":"label0","type":"label","layer":2,"screenId":"builtin","screenName":"","id":"sRl+N1_dvy2B&m4c","createTime":1740621857556,"x":1,"y":38,"color":"#ffffff","backgroundColor":"#222222","text":"TX:","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false,"width":31,"height":21},{"name":"label1","type":"label","layer":3,"screenId":"builtin","screenName":"","id":"y`eHISCNxq6FCrUn","createTime":1740621861575,"x":1,"y":68,"color":"#ffffff","backgroundColor":"#222222","text":"RX:","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false,"width":32,"height":21}],"resources":[{"hardware":["hardware_button","hardware_pin_button","imu","ir"]},{"base":["base_can"]}],"units":[],"hats":[],"bases":[{"type":"base_can","name":"base_can","id":"wQb%wkV6-O1wyWUe","createTime":1740621387748,"initBlockType":"base_can_init","initBlockId":"-{]._7K{ykwQC=S+UT#{"}],"i2cs":[],"blockly":"true0NORMAL561000000trueBtnAlabel0TX: SendFalseFalseuiflow2001label0TX: Not Sendlabel1RX: Rechello M550001label1RX: Not Rec","screen":[{"simulationName":"Built-in","type":"builtin","width":128,"height":128,"scale":1.3,"screenName":"","blockId":"","screenColorType":0,"id":"builtin","createTime":1740621384527}],"logicWhenNum":0,"customList":[]} \ No newline at end of file diff --git a/examples/base/can/atoms3_can_example.py b/examples/base/can/atoms3_can_example.py new file mode 100644 index 00000000..f72c1388 --- /dev/null +++ b/examples/base/can/atoms3_can_example.py @@ -0,0 +1,55 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import os, sys, io +import M5 +from M5 import * +from base import ATOMCANBase +import time + + +title0 = None +label0 = None +label1 = None +base_can = None + + +def setup(): + global title0, label0, label1, base_can + + M5.begin() + title0 = Widgets.Title("CAN Base", 3, 0xFFFFFF, 0x0000FF, Widgets.FONTS.DejaVu18) + label0 = Widgets.Label("TX:", 1, 38, 1.0, 0xFFFFFF, 0x222222, Widgets.FONTS.DejaVu18) + label1 = Widgets.Label("RX:", 1, 68, 1.0, 0xFFFFFF, 0x222222, Widgets.FONTS.DejaVu18) + + base_can = ATOMCANBase(0, (6, 5), ATOMCANBase.NORMAL, baudrate=1000000) + + +def loop(): + global title0, label0, label1, base_can + M5.update() + if BtnA.isPressed(): + label0.setText(str("TX: Send")) + base_can.send("uiflow2", 0, timeout=0, rtr=False, extframe=False) + time.sleep(1) + label0.setText(str("TX: Not Send")) + if base_can.any(0): + label1.setText(str("RX: Rec")) + print(base_can.recv(0, timeout=5000)) + time.sleep(1) + label1.setText(str("RX: Not Rec")) + + +if __name__ == "__main__": + try: + setup() + while True: + loop() + except (Exception, KeyboardInterrupt) as e: + try: + from utility import print_error_msg + + print_error_msg(e) + except ImportError: + print("please update to latest firmware") diff --git a/examples/module/pwrcan/pwrcan_cores3_example.m5f2 b/examples/module/pwrcan/pwrcan_cores3_example.m5f2 index bc3a7921..bf872170 100644 --- a/examples/module/pwrcan/pwrcan_cores3_example.m5f2 +++ b/examples/module/pwrcan/pwrcan_cores3_example.m5f2 @@ -1 +1 @@ -{"version":"V2.0","versionNumber":"V2.2.0","type":"cores3","components":[{"name":"screen","type":"screen","layer":0,"screenId":"builtin","screenName":"","id":"__cores3_screen","createTime":1736136182238,"x":0,"y":0,"width":320,"height":240,"backgroundColor":"#222222","size":0,"isSelected":true},{"name":"title0","type":"title","layer":1,"screenId":"builtin","screenName":"","id":"o6O0oYNPbH`+mY+X","createTime":1736144155742,"x":0,"y":0,"color":"#ffffff","backgroundColor":"#0000FF","text":"PwrCANModule CoreS3 Example","textOffset":3,"font":"Widgets.FONTS.DejaVu18","isSelected":false},{"name":"label0","type":"label","layer":2,"screenId":"builtin","screenName":"","id":"xU2H2cIp9-OL*p9i","createTime":1736144217374,"x":2,"y":65,"color":"#ffffff","backgroundColor":"#222222","text":"CAN Message State: ","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false,"width":187,"height":20},{"name":"label1","type":"label","layer":3,"screenId":"builtin","screenName":"","id":"aPOVB+N3**S&YjMu","createTime":1736144298750,"x":2,"y":118,"color":"#ffffff","backgroundColor":"#222222","text":"RS485 Message State: ","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false,"width":207,"height":20},{"name":"label2","type":"label","layer":4,"screenId":"builtin","screenName":"","id":"kx%7GYy0myNith6k","createTime":1736144405088,"x":2,"y":171,"color":"#ffffff","backgroundColor":"#222222","text":"RS485 Rec:","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false,"width":148,"height":20}],"resources":[{"hardware":["hardware_button","hardware_pin_button","imu","speaker","touch","als","mic"]},{"module":["module_pwrcan"]},{"unit":["unit_rs485"]}],"units":[{"type":"unit_rs485","name":"rs485_0","portList":["A","B","C","Custom"],"portType":"A","userPort":[22,21],"id":"gbpA2lpbRyRFv7Oj","createTime":1736137781595,"initBlockId":"5rGUcp{6S#fa:~,bINYt"}],"hats":[],"bases":[],"i2cs":[],"blockly":"truepwrcan_0canFalse0NORMAL17181000000pwrcan_1rs485False11152008None1137rs485_0false21152008None1truepwrcan_0uiflow200FalseFalselabel0CAN Message State: Sendpwrcan_1RS485_uiflow2label1RS485 Message State: Send1label0CAN Message State: Not Sendlabel1RS485 Message State: Not Sendrs485_0label2CAN Message State: Not SendRS485 Rec:rs485_0","screen":[{"simulationName":"Built-in","type":"builtin","width":320,"height":240,"scale":0.78,"screenName":"","blockId":"","screenColorType":0,"id":"builtin","createTime":1736136182235}],"logicWhenNum":0,"customList":[]} \ No newline at end of file +{"version":"V2.0","versionNumber":"V2.2.2","type":"cores3","components":[{"name":"screen","type":"screen","layer":0,"screenId":"builtin","screenName":"","id":"__cores3_screen","createTime":1736136182238,"x":0,"y":0,"width":320,"height":240,"backgroundColor":"#222222","size":0,"isSelected":true},{"name":"title0","type":"title","layer":1,"screenId":"builtin","screenName":"","id":"o6O0oYNPbH`+mY+X","createTime":1736144155742,"x":0,"y":0,"color":"#ffffff","backgroundColor":"#0000FF","text":"PwrCANModule CoreS3 Example","textOffset":3,"font":"Widgets.FONTS.DejaVu18","isSelected":false},{"name":"label0","type":"label","layer":2,"screenId":"builtin","screenName":"","id":"xU2H2cIp9-OL*p9i","createTime":1736144217374,"x":0,"y":49,"color":"#ffffff","backgroundColor":"#222222","text":"CAN Message State: ","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false,"width":198,"height":20},{"name":"label1","type":"label","layer":3,"screenId":"builtin","screenName":"","id":"aPOVB+N3**S&YjMu","createTime":1736144298750,"x":0,"y":138,"color":"#ffffff","backgroundColor":"#222222","text":"RS485 Message State: ","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false,"width":217,"height":20},{"name":"label2","type":"label","layer":4,"screenId":"builtin","screenName":"","id":"kx%7GYy0myNith6k","createTime":1736144405088,"x":0,"y":179,"color":"#ffffff","backgroundColor":"#222222","text":"RS485 Rec:","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false,"width":108,"height":20},{"name":"label3","type":"label","layer":1,"screenId":"builtin","screenName":"","id":"j=Ba=SUQf$Ob!IJ0","createTime":1740620874014,"x":0,"y":95,"color":"#ffffff","backgroundColor":"#222222","text":"CAN Rec:","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false,"width":87,"height":20}],"resources":[{"hardware":["hardware_button","hardware_pin_button","imu","speaker","touch","als","mic"]},{"module":["module_pwrcan"]},{"unit":["unit_rs485"]}],"units":[{"type":"unit_rs485","name":"rs485_0","portList":["A","B","C","Custom"],"portType":"A","userPort":[22,21],"id":"gbpA2lpbRyRFv7Oj","createTime":1740620580803,"initBlockId":"5rGUcp{6S#fa:~,bINYt"}],"hats":[],"bases":[],"i2cs":[],"blockly":"truepwrcan_0canFalse0NORMAL17181000000pwrcan_1rs485False11152008None1137rs485_0false21152008None1truepwrcan_0uiflow200FalseFalselabel0CAN Message State: Sendpwrcan_1RS485_uiflow2label1RS485 Message State: Send1label0CAN Message State: Not Sendlabel1RS485 Message State: Not Sendpwrcan_0label3CAN Message State: Not SendCAN Rec:pwrcan_05000rs485_0label2CAN Message State: Not SendRS485 Rec:rs485_0","screen":[{"simulationName":"Built-in","type":"builtin","width":320,"height":240,"scale":0.78,"screenName":"","blockId":"","screenColorType":0,"id":"builtin","createTime":1736136182235}],"logicWhenNum":0,"customList":[]} \ No newline at end of file diff --git a/examples/module/pwrcan/pwrcan_cores3_example.py b/examples/module/pwrcan/pwrcan_cores3_example.py index 12004d2b..3fb7b269 100644 --- a/examples/module/pwrcan/pwrcan_cores3_example.py +++ b/examples/module/pwrcan/pwrcan_cores3_example.py @@ -12,6 +12,7 @@ title0 = None +label3 = None label0 = None label1 = None label2 = None @@ -21,20 +22,21 @@ def setup(): - global title0, label0, label1, label2, pwrcan_0, pwrcan_1, rs485_0 + global title0, label3, label0, label1, label2, pwrcan_0, pwrcan_1, rs485_0 M5.begin() Widgets.fillScreen(0x222222) title0 = Widgets.Title( "PwrCANModule CoreS3 Example", 3, 0xFFFFFF, 0x0000FF, Widgets.FONTS.DejaVu18 ) + label3 = Widgets.Label("CAN Rec:", 0, 95, 1.0, 0xFFFFFF, 0x222222, Widgets.FONTS.DejaVu18) label0 = Widgets.Label( - "CAN Message State: ", 2, 65, 1.0, 0xFFFFFF, 0x222222, Widgets.FONTS.DejaVu18 + "CAN Message State: ", 0, 49, 1.0, 0xFFFFFF, 0x222222, Widgets.FONTS.DejaVu18 ) label1 = Widgets.Label( - "RS485 Message State: ", 2, 118, 1.0, 0xFFFFFF, 0x222222, Widgets.FONTS.DejaVu18 + "RS485 Message State: ", 0, 138, 1.0, 0xFFFFFF, 0x222222, Widgets.FONTS.DejaVu18 ) - label2 = Widgets.Label("RS485 Rec:", 2, 171, 1.0, 0xFFFFFF, 0x222222, Widgets.FONTS.DejaVu18) + label2 = Widgets.Label("RS485 Rec:", 0, 179, 1.0, 0xFFFFFF, 0x222222, Widgets.FONTS.DejaVu18) pwrcan_0 = PwrCANModule(0, 17, 18, PwrCANModule.NORMAL, baudrate=1000000) pwrcan_1 = PwrCANModuleRS485(1, baudrate=115200, bits=8, parity=None, stop=1, tx=13, rx=7) @@ -55,7 +57,7 @@ def setup(): def loop(): - global title0, label0, label1, label2, pwrcan_0, pwrcan_1, rs485_0 + global title0, label3, label0, label1, label2, pwrcan_0, pwrcan_1, rs485_0 M5.update() if M5.Touch.getCount(): pwrcan_0.send("uiflow2", 0, timeout=0, rtr=False, extframe=False) @@ -66,6 +68,8 @@ def loop(): else: label0.setText(str("CAN Message State: Not Send")) label1.setText(str("RS485 Message State: Not Send")) + if pwrcan_0.any(0): + label3.setText(str((str("CAN Rec:") + str((pwrcan_0.recv(0, timeout=5000)))))) if rs485_0.any(): label2.setText(str((str("RS485 Rec:") + str((rs485_0.read()))))) diff --git a/m5stack/libs/base/atom_can.py b/m5stack/libs/base/atom_can.py index 61704589..68435fdc 100644 --- a/m5stack/libs/base/atom_can.py +++ b/m5stack/libs/base/atom_can.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD # # SPDX-License-Identifier: MIT @@ -7,10 +7,30 @@ if sys.platform != "esp32": from typing import Literal -from micropython import const class ATOMCANBase(m5can.CAN): + """Create an ATOMCANBase object + + :param int id: The CAN ID to use, Default is 0. + :param port: A list or tuple containing the TX and RX pin numbers. + :type port: list | tuple + :param int mode: The CAN mode to use(NORMAL, NO_ACKNOWLEDGE, LISTEN_ONLY), Default is NORMAL. + :param int baudrate: The baudrate to use, Default is 1000000. + + UiFlow2 Code Block: + + |init.png| + + MicroPython Code Block: + + .. code-block:: python + + from base import ATOMCANBase + + base_can = ATOMCANBase(0, (6, 5), ATOMCANBase.NORMAL, baudrate=1000000) + """ + _timing_table = { # prescaler, sjw, bs1, bs2, triple_sampling 25000: (128, 3, 16, 8, False), From a771b13cb2a07f93a1dad17f21bdb8e8fe7f4caf Mon Sep 17 00:00:00 2001 From: Tinyu-Zhao Date: Fri, 28 Feb 2025 14:38:21 +0800 Subject: [PATCH 004/322] docs: Updated Module Fan v1.1 picture and example url. Signed-off-by: Tinyu-Zhao --- docs/en/refs/module.fan.ref | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/en/refs/module.fan.ref b/docs/en/refs/module.fan.ref index dea41179..d4c8b17f 100644 --- a/docs/en/refs/module.fan.ref +++ b/docs/en/refs/module.fan.ref @@ -1,5 +1,5 @@ -.. |FAN| image:: https://static-cdn.m5stack.com/resource/docs/products/module/fan/fan_01.webp - :target: https://docs.m5stack.com/en/module/fan_v11 +.. |FAN| image:: https://m5stack-doc.oss-cn-shenzhen.aliyuncs.com/1125/M013-V11_01.webp + :target: https://docs.m5stack.com/en/module/Module%20Fan%20v1.1 :height: 200px :width: 200px .. |example.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/fan_v11/example.png @@ -22,7 +22,7 @@ .. |fan_cores3_example.m5f2| raw:: html fan_cores3_example.m5f2 From 7c5a7d1075fe8574713639076ecdaeea24388357 Mon Sep 17 00:00:00 2001 From: Tinyu-Zhao Date: Fri, 28 Feb 2025 15:55:31 +0800 Subject: [PATCH 005/322] base: Add atom gps base. Signed-off-by: Tinyu-Zhao --- docs/en/base/atom_gps.rst | 62 +++ docs/en/base/index.rst | 1 + docs/en/conf.py | 1 + docs/en/refs/base.gps.ref | 37 ++ .../zh_CN/LC_MESSAGES/base/atom_gps.po | 501 ++++++++++++++++++ examples/base/gps/ atoms3_gps_example.m5f2 | 1 + examples/base/gps/atoms3_gps_example.py | 54 ++ m5stack/libs/base/__init__.py | 1 + m5stack/libs/base/atom_gps.py | 456 ++++++++++++++++ m5stack/libs/base/manifest.py | 1 + 10 files changed, 1115 insertions(+) create mode 100644 docs/en/base/atom_gps.rst create mode 100644 docs/en/refs/base.gps.ref create mode 100644 docs/locales/zh_CN/LC_MESSAGES/base/atom_gps.po create mode 100644 examples/base/gps/ atoms3_gps_example.m5f2 create mode 100644 examples/base/gps/atoms3_gps_example.py create mode 100644 m5stack/libs/base/atom_gps.py diff --git a/docs/en/base/atom_gps.rst b/docs/en/base/atom_gps.rst new file mode 100644 index 00000000..7001b1b4 --- /dev/null +++ b/docs/en/base/atom_gps.rst @@ -0,0 +1,62 @@ +ATOM GPS Base +============== + +.. sku: A134/K043 + +.. include:: ../refs/base.gps.ref + +This is the driver library of ATOM GPS Base, which is used to obtain data from the +GPS module. + +Support the following products: + + ================== ================== + |ATOM GPS| |ATOM GPS Base| + ================== ================== + + +UiFlow2 Example +--------------- + +get gps data +^^^^^^^^^^^^^ + +Open the |atoms3_gps_example.m5f2| project in UiFlow2. + +This example gets the GPS data of the ATOM GPS Base and displays it on the serial monitor. + +UiFlow2 Code Block: + + |example.png| + +Example output: + + None + +MicroPython Example +------------------- + +get gps data +^^^^^^^^^^^^^^^ + +This example gets the GPS data of the ATOM GPS Base and displays it on the serial monitor. + +MicroPython Code Block: + + .. literalinclude:: ../../../examples/base/gps/atoms3_gps_example.py + :language: python + :linenos: + +Example output: + + None + + +**API** +------- + +ATOMGPSBase +^^^^^^^^^^^ + +.. autoclass:: base.atom_gps.ATOMGPSBase + :members: diff --git a/docs/en/base/index.rst b/docs/en/base/index.rst index d0f4a609..5e081368 100644 --- a/docs/en/base/index.rst +++ b/docs/en/base/index.rst @@ -5,6 +5,7 @@ Base :maxdepth: 1 atom_can.rst + atom_gps.rst atom_socket.rst display.rst echo.rst diff --git a/docs/en/conf.py b/docs/en/conf.py index 2bb4daca..6b287a6f 100644 --- a/docs/en/conf.py +++ b/docs/en/conf.py @@ -51,6 +51,7 @@ "M5", "module.mbus", "network", + "m5can" ] autodoc_default_options = { diff --git a/docs/en/refs/base.gps.ref b/docs/en/refs/base.gps.ref new file mode 100644 index 00000000..d338aab7 --- /dev/null +++ b/docs/en/refs/base.gps.ref @@ -0,0 +1,37 @@ +.. |ATOM GPS| image:: https://static-cdn.m5stack.com/resource/docs/products/atom/atomicgps/atomicgps_01.webp + :target: https://docs.m5stack.com/en/atom/atomicgps + :height: 200px + :width: 200px + +.. |ATOM GPS Base| image:: https://static-cdn.m5stack.com/resource/docs/products/atom/Atomic%20GPS%20Base/img-3f6d1129-0dde-4b67-adc6-69535a8d1052.webp + :target: https://docs.m5stack.com/en/atom/Atomic%20GPS%20Base + :height: 200px + :width: 200px + +.. |init.png| image:: https://m5stack.oss-cn-shenzhen.aliyuncs.com/mpy_docs/base/gps/init.png +.. |get_antenna_state.png| image:: https://m5stack.oss-cn-shenzhen.aliyuncs.com/mpy_docs/base/gps/get_antenna_state.png +.. |get_gps_time.png| image:: https://m5stack.oss-cn-shenzhen.aliyuncs.com/mpy_docs/base/gps/get_gps_time.png +.. |get_gps_date.png| image:: https://m5stack.oss-cn-shenzhen.aliyuncs.com/mpy_docs/base/gps/get_gps_date.png +.. |get_gps_date_time.png| image:: https://m5stack.oss-cn-shenzhen.aliyuncs.com/mpy_docs/base/gps/get_gps_date_time.png +.. |get_timestamp.png| image:: https://m5stack.oss-cn-shenzhen.aliyuncs.com/mpy_docs/base/gps/get_timestamp.png +.. |get_latitude.png| image:: https://m5stack.oss-cn-shenzhen.aliyuncs.com/mpy_docs/base/gps/get_latitude.png +.. |get_longitude.png| image:: https://m5stack.oss-cn-shenzhen.aliyuncs.com/mpy_docs/base/gps/get_longitude.png +.. |get_altitude.png| image:: https://m5stack.oss-cn-shenzhen.aliyuncs.com/mpy_docs/base/gps/get_altitude.png +.. |get_satellite_num.png| image:: https://m5stack.oss-cn-shenzhen.aliyuncs.com/mpy_docs/base/gps/get_satellite_num.png +.. |get_pos_quality.png| image:: https://m5stack.oss-cn-shenzhen.aliyuncs.com/mpy_docs/base/gps/get_pos_quality.png +.. |get_corse_over_ground.png| image:: https://m5stack.oss-cn-shenzhen.aliyuncs.com/mpy_docs/base/gps/get_corse_over_ground.png +.. |get_speed_over_ground.png| image:: https://m5stack.oss-cn-shenzhen.aliyuncs.com/mpy_docs/base/gps/get_speed_over_ground.png +.. |set_time_zone.png| image:: https://m5stack.oss-cn-shenzhen.aliyuncs.com/mpy_docs/base/gps/set_time_zone.png +.. |get_time_zone.png| image:: https://m5stack.oss-cn-shenzhen.aliyuncs.com/mpy_docs/base/gps/get_time_zone.png +.. |deinit.png| image:: https://m5stack.oss-cn-shenzhen.aliyuncs.com/mpy_docs/base/gps/deinit.png + +.. |example.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/gps/example.png + +.. |atoms3_gps_example.m5f2| raw:: html + + + atoms3_gps_example.m5f2 + \ No newline at end of file diff --git a/docs/locales/zh_CN/LC_MESSAGES/base/atom_gps.po b/docs/locales/zh_CN/LC_MESSAGES/base/atom_gps.po new file mode 100644 index 00000000..3608e515 --- /dev/null +++ b/docs/locales/zh_CN/LC_MESSAGES/base/atom_gps.po @@ -0,0 +1,501 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2016 - 2025 M5Stack Technology Co., Ltd +# This file is distributed under the same license as the UIFlow2 Programming +# Guide package. +# FIRST AUTHOR , 2025. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: UIFlow2 Programming Guide \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-02-26 12:27+0800\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: zh_CN\n" +"Language-Team: zh_CN \n" +"Plural-Forms: nplurals=1; plural=0;\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.16.0\n" + +#: ../../en/base/atom_gps.rst:2 ../../en/refs/base.gps.ref +#: ae26b16a24d94c84a7293460de125856 d2896f4066de4fa1b6486476595e8006 +msgid "ATOM GPS Base" +msgstr "" + +#: ../../en/base/atom_gps.rst:8 087fbc2ff5934fc890dbd4d2dd076a4f +msgid "" +"This is the driver library of ATOM GPS Base, which is used to obtain data" +" from the GPS module." +msgstr "这是 ATOM GPS Base 的驱动程序库,用于从 GPS 模块获取数据。" + +#: ../../en/base/atom_gps.rst:11 66133002dbfd41e98ff614ebd7636d15 +msgid "Support the following products:" +msgstr "支持以下产品:" + +#: ../../en/base/atom_gps.rst:14 97bb503b70da4ae49645896711697de3 +msgid "|ATOM GPS|" +msgstr "" + +#: ../../en/refs/base.gps.ref 2959460dd10e4ece826cff7106249c7f +msgid "ATOM GPS" +msgstr "" + +#: ../../en/base/atom_gps.rst:14 d255f92c81104fc6b30ee0c14a80f210 +msgid "|ATOM GPS Base|" +msgstr "" + +#: ../../en/base/atom_gps.rst:19 cb1416a182c946ae9891d6067b0a75ea +msgid "UiFlow2 Example" +msgstr "UIFLOW2 应用示例:" + +#: ../../en/base/atom_gps.rst:22 ../../en/base/atom_gps.rst:40 +#: 3a88477a43b94993a3ecbc2cd1f305f2 96ea060cd8524c749384a711ce3625af +msgid "get gps data" +msgstr "获取 GPS 数据" + +#: ../../en/base/atom_gps.rst:24 38a0f91a46b24113b9596df9a1032522 +msgid "Open the |atoms3_gps_example.m5f2| project in UiFlow2." +msgstr "在UiFlow2 中打开 |atoms3_gps_example.m5f2| 项目。" + +#: ../../en/base/atom_gps.rst:26 ../../en/base/atom_gps.rst:42 +#: 82ff92fb68ef4d04b74764e6863bedc6 9711921da3d947d5b859fd23d9a0d78f +msgid "" +"This example gets the GPS data of the ATOM GPS Base and displays it on " +"the serial monitor." +msgstr "本示例获取 ATOM GPS Base 的 GPS 数据并将其显示在串口监视器中。" + +#: ../../en/base/atom_gps.rst:28 13e81fc2700544e0948427687a159410 +#: 3104be6b4999403f87a51b1a757e3042 3a60008e98204b40956d53fa7e20c3a7 +#: 51c8c12b95444fc4b16d147a1654e23d 53e259eae67f4814a9c176e2330e9c21 +#: 7aea5244f8974d7782e078c9b75d9f34 84c0c780c0bd4d7b926b298249cdcce4 +#: 9358d310d064464da1b343fe63b99591 9ef19b5dfa164e6987fc48d25c748b56 +#: 9f668bb8b540409ca5397fe5692ab2c3 a033d09a54134a458025cf03f1ace335 +#: a2d1f668f8ea4e7f998b31d7f7a321fc a6460e00e2d34f80b68a1901006bd6eb +#: abb84ff5971847128fd96bbc6bf187dc b5e88f318900446ab7a0da5e845741c6 +#: base.atom_gps.ATOMGPSBase:8 base.atom_gps.ATOMGPSBase.deinit:3 +#: base.atom_gps.ATOMGPSBase.get_altitude:6 +#: base.atom_gps.ATOMGPSBase.get_antenna_state:6 +#: base.atom_gps.ATOMGPSBase.get_corse_over_ground:6 +#: base.atom_gps.ATOMGPSBase.get_gps_date:6 +#: base.atom_gps.ATOMGPSBase.get_gps_date_time:6 +#: base.atom_gps.ATOMGPSBase.get_gps_time:6 +#: base.atom_gps.ATOMGPSBase.get_latitude:6 +#: base.atom_gps.ATOMGPSBase.get_longitude:6 +#: base.atom_gps.ATOMGPSBase.get_pos_quality:6 +#: base.atom_gps.ATOMGPSBase.get_satellite_num:6 +#: base.atom_gps.ATOMGPSBase.get_speed_over_ground:6 +#: base.atom_gps.ATOMGPSBase.get_time_zone:6 +#: base.atom_gps.ATOMGPSBase.get_timestamp:6 +#: base.atom_gps.ATOMGPSBase.set_time_zone:5 d39b92fd99854b47a3400f4255cae1d2 +#: d7dc4fb641cf4bfb929c42b06b42d3e6 of +msgid "UiFlow2 Code Block:" +msgstr "UiFlow2 代码块:" + +#: ../../en/base/atom_gps.rst:30 367cd78e8da64f5782cb8a2ae3058ea5 +msgid "|example.png|" +msgstr "" + +#: ../../en/refs/base.gps.ref:28 40d27002d4d74bd3924fb94dcfe02646 +msgid "example.png" +msgstr "" + +#: ../../en/base/atom_gps.rst:32 ../../en/base/atom_gps.rst:50 +#: 77823a9eeac14d88980d28f6aabbe4c5 7e72e0c966804a95b784529eef402546 +msgid "Example output:" +msgstr "示例输出:" + +#: ../../en/base/atom_gps.rst:34 ../../en/base/atom_gps.rst:52 +#: 6b4dbb4b640c496690f55ac20b660cd5 765afd85fd0f4411a9abde657402221b +msgid "None" +msgstr "" + +#: ../../en/base/atom_gps.rst:37 41cd0a3aa72e4957a7b039cd13bc6584 +msgid "MicroPython Example" +msgstr "MicroPython 应用示例:" + +#: ../../en/base/atom_gps.rst:44 048885f83d8f4f65ae3d5abe4485fe6e +#: 050b6c9716034e43b1bb190f9d28c3a2 1a30f417d3274c82b78819c2aa834200 +#: 1ef770333d3d47e1acee616b194db5a7 2a999dfb54144b55a23b1df6bff84b25 +#: 357012164ff041e4b64438720aea6c68 4f6eafd292894d4b9a61ad904c906a65 +#: 5a3dda96331e417c96a353a23714ac1a 8a4c2c65864d4b61a4b24d841051160a +#: 8b6620a63bde41289d1ea4ef2c8954d1 98bb71b1a82647949ef8febee74099b7 +#: 9f5101c120c54c1b87fbea33fe1c981a b27211e92a324e0b976b528fedb4da02 +#: b98dd29c84d148d7b888b53f1daab7e3 base.atom_gps.ATOMGPSBase:12 +#: base.atom_gps.ATOMGPSBase.deinit:7 base.atom_gps.ATOMGPSBase.get_altitude:10 +#: base.atom_gps.ATOMGPSBase.get_antenna_state:10 +#: base.atom_gps.ATOMGPSBase.get_corse_over_ground:10 +#: base.atom_gps.ATOMGPSBase.get_gps_date:10 +#: base.atom_gps.ATOMGPSBase.get_gps_date_time:10 +#: base.atom_gps.ATOMGPSBase.get_gps_time:10 +#: base.atom_gps.ATOMGPSBase.get_latitude:10 +#: base.atom_gps.ATOMGPSBase.get_longitude:10 +#: base.atom_gps.ATOMGPSBase.get_pos_quality:10 +#: base.atom_gps.ATOMGPSBase.get_satellite_num:10 +#: base.atom_gps.ATOMGPSBase.get_speed_over_ground:10 +#: base.atom_gps.ATOMGPSBase.get_time_zone:10 +#: base.atom_gps.ATOMGPSBase.get_timestamp:10 +#: base.atom_gps.ATOMGPSBase.set_time_zone:9 c1388af8a05f40cfa540817b963f8711 +#: c5c36bb92ba949d29275cc65e0bd7624 ff33ea1d25924de7a05be5304638b3e7 of +msgid "MicroPython Code Block:" +msgstr "MicroPython 代码块:" + +#: ../../en/base/atom_gps.rst:56 6bd51aefdbbf497b9b1ab99fd22af814 +msgid "**API**" +msgstr "" + +#: ../../en/base/atom_gps.rst:59 6d35355e9dbd4788b2acf836145b447a +msgid "ATOMGPSBase" +msgstr "" + +#: base.atom_gps.ATOMGPSBase:1 d10eb819084046b18d43b3d64c5669f6 of +msgid "Bases: :py:class:`object`" +msgstr "" + +#: 331e93929781462ead9f1277fdb44868 base.atom_gps.ATOMGPSBase:1 of +msgid "Create an ATOMGPSBase object." +msgstr "创建 ATOMGPSBase 对象。" + +#: ../../en/base/atom_gps.rst 03585621341949d596c274f310acdbd3 +#: 26a3ec31c255447a98c2b48d383906f5 base.atom_gps.ATOMGPSBase.set_time_zone of +msgid "Parameters" +msgstr "参数" + +#: 15f8f669a30b4080a187b6d2d3fafa95 base.atom_gps.ATOMGPSBase:3 of +msgid "The UART ID to use (0, 1, or 2). Default is 2." +msgstr "UART ID 号可使用 0, 1, 2, 默认使用2。" + +#: 4269dbb2279b48c08cc15a916311f700 base.atom_gps.ATOMGPSBase:4 of +msgid "A list or tuple containing the TX and RX pin numbers." +msgstr "包含 TX 和 RX 引脚编号的列表或元组。" + +#: base.atom_gps.ATOMGPSBase:6 d747e7f33e0440928288dc9398ec8fef of +msgid "Whether to enable debug mode. Default is False." +msgstr "是否启用调试模式, 默认为不开启。" + +#: 5a1a3ec0029a43879fb7bbf8e8ea7a8c base.atom_gps.ATOMGPSBase:10 of +msgid "|init.png|" +msgstr "" + +#: ../../en/refs/base.gps.ref:11 e5fd76a4b9f3458cafff9d97fe618ae6 +msgid "init.png" +msgstr "" + +#: base.atom_gps.ATOMGPSBase.deinit:1 ee9a5b1e3e44470bab287ba831670c7a of +msgid "" +"Deinitialize the GPS unit, stopping any running tasks and releasing " +"resources." +msgstr "取消 GPS 设备的初始化,停止正在运行的任务并释放资源。" + +#: 6ea970e318614e4ca4418133e3ff15c8 base.atom_gps.ATOMGPSBase.deinit:5 of +msgid "|deinit.png|" +msgstr "" + +#: ../../en/refs/base.gps.ref:26 076fa6176488403996331203dcb589d9 +msgid "deinit.png" +msgstr "" + +#: 09898585002b44218a54ba582e8e0493 108c2887936f4f0f9428f0d6332aeba5 +#: 115a2941b6c3463b88a40541c010ffba 29b6a3672d614d328addab9cbf893a2c +#: 31cfc2a7f67d4199b1c028e9f29e51ef 4607ee80996f450bbcc98d90b7b45b13 +#: 4d6520c6f9a44b55a4fbc9ce2918e4a7 8ffe8626046b4fb19cc698195605c1c7 +#: 9afc97a1ac2145af863101339a494fb1 base.atom_gps.ATOMGPSBase.deinit +#: base.atom_gps.ATOMGPSBase.get_altitude +#: base.atom_gps.ATOMGPSBase.get_antenna_state +#: base.atom_gps.ATOMGPSBase.get_corse_over_ground +#: base.atom_gps.ATOMGPSBase.get_gps_date +#: base.atom_gps.ATOMGPSBase.get_gps_date_time +#: base.atom_gps.ATOMGPSBase.get_gps_time +#: base.atom_gps.ATOMGPSBase.get_latitude +#: base.atom_gps.ATOMGPSBase.get_longitude +#: base.atom_gps.ATOMGPSBase.get_pos_quality +#: base.atom_gps.ATOMGPSBase.get_satellite_num +#: base.atom_gps.ATOMGPSBase.get_speed_over_ground +#: base.atom_gps.ATOMGPSBase.get_time_zone +#: base.atom_gps.ATOMGPSBase.get_timestamp +#: base.atom_gps.ATOMGPSBase.set_time_zone c056553bce8a41138d6826c3f23d3d33 +#: c896b37b76644b3eab005c6db1307342 d2b550c0b2d24c0090426719c6c6778b +#: e547ee63abc542bc951eafe605eb39af fb75237af2e14016ba60cf1e6eefeccf +#: fcdc848aac204c148179e1803d008993 of +msgid "Return type" +msgstr "返回类型" + +#: 720e251c6eea431baca3ec03eae17694 base.atom_gps.ATOMGPSBase.get_altitude:1 of +msgid "Get the current altitude." +msgstr "获取当前的海拔高度。" + +#: 17b0b660aba9469295ee82f0bd75b6aa 330f865411a046368498c037e62f6969 +#: 4730e0a4ffb44931a37a298eacffff0b 51dd39b34a69404d9dee89105d4a5b0a +#: 5a049269aacc41a184503e9d88354171 5d938d428a41460a965d3861ca3c24ee +#: 5db58bf9517b43f08e45c35a0af1b4f2 71723bc0f42e4ec0af3c7ee400cdc593 +#: 7f464998851d4cf4ad34cfe1c5d1c449 98e963e37126405ca9a996478a8fc8d2 +#: 9fc92b42bbb0428ca1230fa20e129314 a42a4d1869444688a7c9ab6849985bfe +#: base.atom_gps.ATOMGPSBase.get_altitude +#: base.atom_gps.ATOMGPSBase.get_antenna_state +#: base.atom_gps.ATOMGPSBase.get_corse_over_ground +#: base.atom_gps.ATOMGPSBase.get_gps_date +#: base.atom_gps.ATOMGPSBase.get_gps_date_time +#: base.atom_gps.ATOMGPSBase.get_gps_time +#: base.atom_gps.ATOMGPSBase.get_latitude +#: base.atom_gps.ATOMGPSBase.get_longitude +#: base.atom_gps.ATOMGPSBase.get_pos_quality +#: base.atom_gps.ATOMGPSBase.get_satellite_num +#: base.atom_gps.ATOMGPSBase.get_speed_over_ground +#: base.atom_gps.ATOMGPSBase.get_time_zone +#: base.atom_gps.ATOMGPSBase.get_timestamp da6fa238bd274caea42a8f29506ad347 of +msgid "Returns" +msgstr "返回值" + +#: 726b6100c1c04a539e50a254cc4a0cd5 base.atom_gps.ATOMGPSBase.get_altitude:3 of +msgid "The current altitude." +msgstr "获取当前的海拔高度。" + +#: 959889e786c6491eae2c3a7df40e4d76 base.atom_gps.ATOMGPSBase.get_altitude:8 of +msgid "|get_altitude.png|" +msgstr "" + +#: ../../en/refs/base.gps.ref:19 914abf1f34bc4ad9a5baa21523a36cf8 +msgid "get_altitude.png" +msgstr "" + +#: 2ce191cfedc649dfac2df0d3e668d757 +#: base.atom_gps.ATOMGPSBase.get_antenna_state:1 of +msgid "Get the state of the antenna." +msgstr "获取当前的天线状态。" + +#: base.atom_gps.ATOMGPSBase.get_antenna_state:3 +#: e732a9dfd8444086b018028471396a48 of +msgid "The current antenna state." +msgstr "当前的天线状态。" + +#: base.atom_gps.ATOMGPSBase.get_antenna_state:8 +#: d897114b5d704acea37463569b2c5fe9 of +msgid "|get_antenna_state.png|" +msgstr "" + +#: ../../en/refs/base.gps.ref:12 25aa6fc782b34154afb86feec28d1793 +msgid "get_antenna_state.png" +msgstr "" + +#: 3ec328e0fbb847c782747c249cbd630f +#: base.atom_gps.ATOMGPSBase.get_corse_over_ground:1 of +msgid "Get the course over ground (COG)." +msgstr "获取当前相对地面真航向。" + +#: 4b914f47474240e8ab7742b84a12711f +#: base.atom_gps.ATOMGPSBase.get_corse_over_ground:3 of +msgid "The course over ground in degrees." +msgstr "相对地面真航向。" + +#: base.atom_gps.ATOMGPSBase.get_corse_over_ground:8 +#: ce964f8e68e14cd0acdde5237f9a9104 of +msgid "|get_corse_over_ground.png|" +msgstr "" + +#: ../../en/refs/base.gps.ref:22 b0c9740d7c1340039e364560cf3e7773 +msgid "get_corse_over_ground.png" +msgstr "" + +#: 557cc3391f5d4d1f85f59d7dfd15ed88 base.atom_gps.ATOMGPSBase.get_gps_date:1 of +msgid "Get the current GPS date." +msgstr "获取当前的 GPS 日期。" + +#: base.atom_gps.ATOMGPSBase.get_gps_date:3 dead6936ee464f4480cab19f79afb459 of +msgid "The GPS date as a list of strings [day, month, year]." +msgstr "以字符串 [日、月、年] 列表形式显示的 GPS 日期。" + +#: 228fa69c32ce41a8a1b481aa09d8d92a base.atom_gps.ATOMGPSBase.get_gps_date:8 of +msgid "|get_gps_date.png|" +msgstr "" + +#: ../../en/refs/base.gps.ref:14 eb257b84232d41e8a3869341464b1972 +msgid "get_gps_date.png" +msgstr "" + +#: 29a6456fe7bb4a9e9ffc7945275b25a1 +#: base.atom_gps.ATOMGPSBase.get_gps_date_time:1 of +msgid "Get the current GPS date and time combined." +msgstr "获取当前 GPS 日期和时间。" + +#: 47ecc9363f784d5496e5d68ac70601ac +#: base.atom_gps.ATOMGPSBase.get_gps_date_time:3 of +msgid "" +"The GPS date and time as a list of strings [year, month, day, hour, " +"minute, second]." +msgstr "GPS 日期和时间的字符串列表 [年、月、日、时、分、秒]。" + +#: a34ef74ff0ca45abb309e03c7ed01da2 +#: base.atom_gps.ATOMGPSBase.get_gps_date_time:8 of +msgid "|get_gps_date_time.png|" +msgstr "" + +#: ../../en/refs/base.gps.ref:15 19a270d194dc4af395d3488f94dbc432 +msgid "get_gps_date_time.png" +msgstr "" + +#: base.atom_gps.ATOMGPSBase.get_gps_time:1 f531990d1bfc48e3b2081177abc192c9 of +msgid "Get the current GPS time." +msgstr "获取当前 GPS 时间。" + +#: 84493e6ecc9743cda453658a2e8e6722 base.atom_gps.ATOMGPSBase.get_gps_time:3 of +msgid "The GPS time as a list of strings [hour, minute, second]." +msgstr "以 [时、分、秒] 字符串列表形式显示的 GPS 时间。" + +#: 57d068ea736d43dd9d3f5c560db160dd base.atom_gps.ATOMGPSBase.get_gps_time:8 of +msgid "|get_gps_time.png|" +msgstr "" + +#: ../../en/refs/base.gps.ref:13 a9878dcea77e4690b1336305e1f60e19 +msgid "get_gps_time.png" +msgstr "" + +#: 446012fd8993421299eb4c71b2b593d1 base.atom_gps.ATOMGPSBase.get_latitude:1 of +msgid "Get the current latitude." +msgstr 获取当前的海拔高度"" + +#: a29a1497806443e884fd7129b2820434 base.atom_gps.ATOMGPSBase.get_latitude:3 of +msgid "The current latitude." +msgstr "当前的海拔高度。" + +#: 486e0d9de051479fbd2a2cbb9cb8d512 base.atom_gps.ATOMGPSBase.get_latitude:8 of +msgid "|get_latitude.png|" +msgstr "" + +#: ../../en/refs/base.gps.ref:17 8e201015417248f2b2f466bba2930286 +msgid "get_latitude.png" +msgstr "" + +#: 434ee14c762e48febe6f0aaa98032876 base.atom_gps.ATOMGPSBase.get_longitude:1 +#: of +msgid "Get the current longitude." +msgstr "获取当前纬度。" + +#: 468b23c4bf694152a2675e208b97ef15 base.atom_gps.ATOMGPSBase.get_longitude:3 +#: of +msgid "The current longitude." +msgstr "获取当前纬度。" + +#: 783dfce7b87f438c8cd35542e76eb493 base.atom_gps.ATOMGPSBase.get_longitude:8 +#: of +msgid "|get_longitude.png|" +msgstr "" + +#: ../../en/refs/base.gps.ref:18 3ba59dcb7bc841439cb28dca77c05efa +msgid "get_longitude.png" +msgstr "" + +#: base.atom_gps.ATOMGPSBase.get_pos_quality:1 eedaf69284d64bcf93dec4712d85473a +#: of +msgid "Get the quality of the GPS position." +msgstr "获取 GPS 定位质量。" + +#: 61f402d5d85e4f9eae22c78898b0ba80 base.atom_gps.ATOMGPSBase.get_pos_quality:3 +#: of +msgid "The position quality indicator." +msgstr "位置质量指标。" + +#: 57afbd466c444eff8a34707470bb07c1 base.atom_gps.ATOMGPSBase.get_pos_quality:8 +#: of +msgid "|get_pos_quality.png|" +msgstr "" + +#: ../../en/refs/base.gps.ref:21 9dc11224817842f5bb23ec03a3d48bb4 +msgid "get_pos_quality.png" +msgstr "" + +#: 42c972240dcb418dbb8ba65cb8f0ff40 +#: base.atom_gps.ATOMGPSBase.get_satellite_num:1 of +msgid "Get the number of satellites used for positioning." +msgstr "获取用于定位的卫星数量。" + +#: base.atom_gps.ATOMGPSBase.get_satellite_num:3 +#: c7b31f9793964c748ed4d65bd0e6e859 of +msgid "The number of satellites." +msgstr "卫星数量。" + +#: 4ba321178c5b45378fb57d23fc4af27c +#: base.atom_gps.ATOMGPSBase.get_satellite_num:8 of +msgid "|get_satellite_num.png|" +msgstr "" + +#: ../../en/refs/base.gps.ref:20 6b1e43bdb5ca4743945821119a83fc5b +msgid "get_satellite_num.png" +msgstr "" + +#: 44ede812e16748fc89491efcdfe0c54b +#: base.atom_gps.ATOMGPSBase.get_speed_over_ground:1 of +msgid "Get the speed over ground (SOG)." +msgstr "获取获取地面速度。" + +#: 15a64a344b6a4b96a0156218538847a7 +#: base.atom_gps.ATOMGPSBase.get_speed_over_ground:3 of +msgid "The speed over ground in knots." +msgstr "对地面速度,单位为节。" + +#: 2659e1f8497b4fc0abe086e8d41eb5e1 +#: base.atom_gps.ATOMGPSBase.get_speed_over_ground:8 of +msgid "|get_speed_over_ground.png|" +msgstr "" + +#: ../../en/refs/base.gps.ref:23 86a1795ff90e435da82e0f7aae5cb6bb +msgid "get_speed_over_ground.png" +msgstr "" + +#: b4ceebc8cb6241a39fb1079a45796d4a base.atom_gps.ATOMGPSBase.get_time_zone:1 +#: of +msgid "Get the current time zone offset." +msgstr "获取当前时区偏移。" + +#: 0e8d89e27feb42689ebea5f7879cd039 base.atom_gps.ATOMGPSBase.get_time_zone:3 +#: of +msgid "The current time zone offset." +msgstr "当前时区偏移。" + +#: 5dcc2dd2904c4f5d951050eeb8ea9907 base.atom_gps.ATOMGPSBase.get_time_zone:8 +#: of +msgid "|get_time_zone.png|" +msgstr "" + +#: ../../en/refs/base.gps.ref:25 fc4913211ce04b3c93fe459ac7ab3eee +msgid "get_time_zone.png" +msgstr "" + +#: 5f11758ac891478681795f0182948484 base.atom_gps.ATOMGPSBase.get_timestamp:1 +#: of +msgid "Get the timestamp of the current GPS time." +msgstr "获取当前 GPS 时间的时间戳。" + +#: 68c6632aa41944ce883ec9b691e736ee base.atom_gps.ATOMGPSBase.get_timestamp:3 +#: of +msgid "The timestamp representing the current GPS time." +msgstr "代表当前 GPS 时间的时间戳。" + +#: base.atom_gps.ATOMGPSBase.get_timestamp:8 d62421f2eef146a297a8d25c1c41240d +#: of +msgid "|get_timestamp.png|" +msgstr "" + +#: ../../en/refs/base.gps.ref:16 274ccc6eb5334b75aeec2a3127200eb1 +msgid "get_timestamp.png" +msgstr "" + +#: base.atom_gps.ATOMGPSBase.set_time_zone:1 fd41f168af0f453883694b1014e66c53 +#: of +msgid "Set the time zone offset for the GPS time." +msgstr "设置 GPS 时间的时区偏移。" + +#: 18a4984a456d45439d8d1e40ae086eb2 base.atom_gps.ATOMGPSBase.set_time_zone:3 +#: of +msgid "The time zone offset value to set." +msgstr "要设置的时区偏移值。" + +#: 7f6b8b74fbb0466d8479f20ed822d91a base.atom_gps.ATOMGPSBase.set_time_zone:7 +#: of +msgid "|set_time_zone.png|" +msgstr "" + +#: ../../en/refs/base.gps.ref:24 f88edec2b13d47efb69c360740baadd9 +msgid "set_time_zone.png" +msgstr "" \ No newline at end of file diff --git a/examples/base/gps/ atoms3_gps_example.m5f2 b/examples/base/gps/ atoms3_gps_example.m5f2 new file mode 100644 index 00000000..a6acc3cd --- /dev/null +++ b/examples/base/gps/ atoms3_gps_example.m5f2 @@ -0,0 +1 @@ +{"version":"V2.0","versionNumber":"V2.2.2","type":"atoms3","components":[{"name":"screen","type":"screen","layer":0,"screenId":"builtin","screenName":"","id":"__atoms3_screen","createTime":1740726539550,"x":0,"y":0,"width":128,"height":128,"backgroundColor":"#000000","size":0,"isSelected":true},{"name":"title0","type":"title","layer":1,"screenId":"builtin","screenName":"","id":"fDf0w`$C-XYV3h^6","createTime":1740726629116,"x":0,"y":0,"color":"#ffffff","backgroundColor":"#0000FF","text":"GPS Base","textOffset":3,"font":"Widgets.FONTS.DejaVu18","isSelected":false}],"resources":[{"hardware":["hardware_button","hardware_pin_button","imu","ir"]},{"base":["base_gps"]}],"units":[],"hats":[],"bases":[{"type":"base_gps","name":"base_gps","id":"e1VZoV-wnR9p2UBX","createTime":1740726556301,"initBlockType":"base_gps_init","initBlockId":"K.1qj=0FiuFjvs3[6Pob"}],"i2cs":[],"blockly":"true2560true1","screen":[{"simulationName":"Built-in","type":"builtin","width":128,"height":128,"scale":1.3,"screenName":"","blockId":"","screenColorType":0,"id":"builtin","createTime":1740726539550}],"logicWhenNum":0,"customList":[]} \ No newline at end of file diff --git a/examples/base/gps/atoms3_gps_example.py b/examples/base/gps/atoms3_gps_example.py new file mode 100644 index 00000000..b5db056d --- /dev/null +++ b/examples/base/gps/atoms3_gps_example.py @@ -0,0 +1,54 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import os, sys, io +import M5 +from M5 import * +from base import ATOMGPSBase +import time + + +title0 = None +base_gps = None + + +def setup(): + global title0, base_gps + + M5.begin() + title0 = Widgets.Title("GPS Base", 3, 0xFFFFFF, 0x0000FF, Widgets.FONTS.DejaVu18) + + base_gps = ATOMGPSBase(2, port=(5, 6)) + base_gps.set_time_zone(0) + + +def loop(): + global title0, base_gps + M5.update() + print(base_gps.get_gps_time()) + print(base_gps.get_gps_date()) + print(base_gps.get_gps_date_time()) + print(base_gps.get_timestamp()) + print(base_gps.get_latitude()) + print(base_gps.get_longitude()) + print(base_gps.get_altitude()) + print(base_gps.get_satellite_num()) + print(base_gps.get_pos_quality()) + print(base_gps.get_corse_over_ground()) + print(base_gps.get_speed_over_ground()) + time.sleep(1) + + +if __name__ == "__main__": + try: + setup() + while True: + loop() + except (Exception, KeyboardInterrupt) as e: + try: + from utility import print_error_msg + + print_error_msg(e) + except ImportError: + print("please update to latest firmware") diff --git a/m5stack/libs/base/__init__.py b/m5stack/libs/base/__init__.py index 44093a46..810acdb0 100644 --- a/m5stack/libs/base/__init__.py +++ b/m5stack/libs/base/__init__.py @@ -4,6 +4,7 @@ _attrs = { "ATOMCANBase": "atom_can", + "ATOMGPSBase": "atom_gps", "ATOMSocketBase": "atom_socket", "ATOMEchoBase": "echo", "AtomicDisplayBase": "display", diff --git a/m5stack/libs/base/atom_gps.py b/m5stack/libs/base/atom_gps.py new file mode 100644 index 00000000..87af6bde --- /dev/null +++ b/m5stack/libs/base/atom_gps.py @@ -0,0 +1,456 @@ +# SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT +from machine import UART +import time +from driver.timer_thread import TimerThread +import sys + +if sys.platform != "esp32": + from typing import Literal + +timTh = TimerThread() + + +class ATOMGPSBase: + """Create an ATOMGPSBase object. + + :param int id: The UART ID to use (0, 1, or 2). Default is 2. + :param port: A list or tuple containing the TX and RX pin numbers. + :type port: list | tuple + :param bool debug: Whether to enable debug mode. Default is False. + + UiFlow2 Code Block: + + |init.png| + + MicroPython Code Block: + + .. code-block:: python + + from machine import UART + from base import ATOMGPSBase + + gps = ATOMGPSBase(id=1, port=(16, 17), debug=True) + """ + + def __init__(self, id: Literal[0, 1, 2] = 1, port: list | tuple = None, debug=False): + self.debug = debug + self.antenna_state = "ANTENNA OPEN" + self.gps_time = ["00", "00", "00"] + self.gps_date = ["00", "00", "00"] + self.gps_date_time = ["00", "00", "00", "00", "00", "00"] + self.timestamp = 0 + self.latitude = "0N" + self.longitude = "0E" + self.altitude = "0" + self.satellite_num = "0" + self.pos_quality = "0" + self.corse_ground_degree = "0" + self.speed_ground_knot = "0" + self.time_offset = 0 + self.uart = UART(id, tx=port[1], rx=port[0]) + self.uart.init(9600, bits=8, parity=None, stop=1, rxbuf=1024) + self._timer = timTh.add_timer(200, timTh.PERIODIC, self._monitor) + print("debug", self.debug) + + def get_antenna_state(self) -> str: + """Get the state of the antenna. + + :returns: The current antenna state. + :rtype: str + + UiFlow2 Code Block: + + |get_antenna_state.png| + + MicroPython Code Block: + + .. code-block:: python + + gps.get_antenna_state() + """ + return self.antenna_state + + def get_gps_time(self) -> list[str]: + """Get the current GPS time. + + :returns: The GPS time as a list of strings [hour, minute, second]. + :rtype: list[str] + + UiFlow2 Code Block: + + |get_gps_time.png| + + MicroPython Code Block: + + .. code-block:: python + + gps.get_gps_time() + """ + return self.gps_time + + def get_gps_date(self) -> list[str]: + """Get the current GPS date. + + :returns: The GPS date as a list of strings [day, month, year]. + :rtype: list[str] + + UiFlow2 Code Block: + + |get_gps_date.png| + + MicroPython Code Block: + + .. code-block:: python + + gps.get_gps_date() + """ + return self.gps_date + + def get_gps_date_time(self) -> list[str]: + """Get the current GPS date and time combined. + + :returns: The GPS date and time as a list of strings [year, month, day, hour, minute, second]. + :rtype: list[str] + + UiFlow2 Code Block: + + |get_gps_date_time.png| + + MicroPython Code Block: + + .. code-block:: python + + gps.get_gps_date_time() + """ + return self.gps_date_time + + def get_timestamp(self) -> int | float: + """Get the timestamp of the current GPS time. + + :returns: The timestamp representing the current GPS time. + :rtype: int | float + + UiFlow2 Code Block: + + |get_timestamp.png| + + MicroPython Code Block: + + .. code-block:: python + + gps.get_timestamp() + """ + return self.timestamp + + def get_latitude(self) -> str: + """Get the current latitude. + + :returns: The current latitude. + :rtype: str + + UiFlow2 Code Block: + + |get_latitude.png| + + MicroPython Code Block: + + .. code-block:: python + + gps.get_latitude() + """ + return self.latitude + + def get_longitude(self) -> str: + """Get the current longitude. + + :returns: The current longitude. + :rtype: str + + UiFlow2 Code Block: + + |get_longitude.png| + + MicroPython Code Block: + + .. code-block:: python + + gps.get_longitude() + """ + return self.longitude + + def get_altitude(self) -> str: + """Get the current altitude. + + :returns: The current altitude. + :rtype: str + + UiFlow2 Code Block: + + |get_altitude.png| + + MicroPython Code Block: + + .. code-block:: python + + gps.get_altitude() + """ + return self.altitude + + def get_satellite_num(self) -> str: + """Get the number of satellites used for positioning. + + :returns: The number of satellites. + :rtype: str + + UiFlow2 Code Block: + + |get_satellite_num.png| + + MicroPython Code Block: + + .. code-block:: python + + gps.get_satellite_num() + """ + return self.satellite_num + + def get_pos_quality(self) -> str: + """Get the quality of the GPS position. + + :returns: The position quality indicator. + :rtype: str + + UiFlow2 Code Block: + + |get_pos_quality.png| + + MicroPython Code Block: + + .. code-block:: python + + gps.get_pos_quality() + """ + return self.pos_quality + + def get_corse_over_ground(self) -> str: + """Get the course over ground (COG). + + :returns: The course over ground in degrees. + :rtype: str + + UiFlow2 Code Block: + + |get_corse_over_ground.png| + + MicroPython Code Block: + + .. code-block:: python + + gps.get_corse_over_ground() + """ + return self.corse_ground_degree + + def get_speed_over_ground(self) -> str: + """Get the speed over ground (SOG). + + :returns: The speed over ground in knots. + :rtype: str + + UiFlow2 Code Block: + + |get_speed_over_ground.png| + + MicroPython Code Block: + + .. code-block:: python + + gps.get_speed_over_ground() + """ + return self.speed_ground_knot + + def set_time_zone(self, value: int) -> None: + """Set the time zone offset for the GPS time. + + :param int value: The time zone offset value to set. + + UiFlow2 Code Block: + + |set_time_zone.png| + + MicroPython Code Block: + + .. code-block:: python + + gps.set_time_zone(8) + """ + self.time_offset = value + + def get_time_zone(self) -> int: + """Get the current time zone offset. + + :returns: The current time zone offset. + :rtype: int + + UiFlow2 Code Block: + + |get_time_zone.png| + + MicroPython Code Block: + + .. code-block:: python + + gps.get_time_zone() + """ + return self.time_offset + + def deinit(self) -> None: + """Deinitialize the GPS unit, stopping any running tasks and releasing resources. + + UiFlow2 Code Block: + + |deinit.png| + + MicroPython Code Block: + + .. code-block:: python + + gps.deinit() + """ + self._timer.deinit() + try: + self.uart.deinit() + except: + pass + + def _add_checksum(self, message: str) -> str: + """ + note: + en: Add checksum to the message for communication with the GPS module. + + params: + message: + note: The message to which the checksum will be added. + + return: + note: The message with added checksum. + """ + checksum = 0 + for char in message: + checksum ^= ord(char) + return f"${message}*{checksum:02X}\r\n" + + def _decode_gga(self, data: str): + """ + note: + en: Decode the GGA sentence to extract GPS quality, number of satellites, and altitude. + + params: + data: + note: The GGA sentence to decode. + """ + gps_list = data.split(",") + self.pos_quality = gps_list[6] + if self.pos_quality == "0": + return + self.satellite_num = gps_list[7] + if gps_list[9]: + self.altitude = gps_list[9] + gps_list[10] + + def _decode_rmc(self, data: str): + """ + note: + en: Decode the RMC sentence to extract GPS time, latitude, longitude, speed, course, and date. + + params: + data: + note: The RMC sentence to decode. + """ + gps_list = data.split(",") + if gps_list[2] == "A": + time_buf = gps_list[1] + self.gps_time = [ + int(time_buf[0:2]) + self.time_offset, + int(time_buf[2:4]), + int(time_buf[4:6]), + ] + self.latitude = self._convert_to_decimal(gps_list[3], gps_list[4]) + self.longitude = self._convert_to_decimal(gps_list[5], gps_list[6], False) + self.speed_ground_knot = gps_list[7] + self.corse_ground_degree = gps_list[8] + data_buf = gps_list[9] + self.gps_date = [int(data_buf[4:7]) + 2000, int(data_buf[2:4]), int(data_buf[0:2])] + t = ( + self.gps_date[0], + self.gps_date[1], + self.gps_date[2], + self.gps_time[0] - self.time_offset, + self.gps_time[1], + self.gps_time[2], + 0, + 0, + ) + self.gps_date_time = self.gps_date + self.gps_time + buf = time.mktime(t) + self.timestamp = buf + + def _convert_to_decimal(self, degrees_minutes, direction, latitude: bool = True) -> float: + """ + note: + en: Convert latitude or longitude from degrees minutes format to decimal format. + + params: + degrees_minutes: + note: Latitude or Longitude in DDMM.MMMM format (e.g., "2242.10772"). + direction: + note: Direction of the coordinate ("N", "S", "E", "W"). + latitude: + note: True if latitude, False if longitude. + + returns: + note: The decimal value of the coordinate. + """ + if latitude: + degrees = degrees_minutes[:2] # First two digits are degrees + minutes = degrees_minutes[2:] # The rest are minutes + else: + degrees = degrees_minutes[:3] + minutes = degrees_minutes[3:] + decimal = int(degrees) + round(float(minutes) / 60.0, 6) + if direction in ["S", "W"]: + decimal = -decimal + return decimal + + def _decode_txt(self, data: str): + """ + note: + en: Decode the TXT sentence to extract antenna state. + + params: + data: + note: The TXT sentence to decode. + """ + gps_list = data.split(",") + if gps_list[4] is not None and gps_list[4][0:7] == "ANTENNA": + self.antenna_state = gps_list[4][8:].split("*")[0] + + def _monitor(self): + """ + note: + en: Monitor the GPS data and decode incoming sentences. + """ + + while True: + if self.uart.any() < 25: + break + gps_data = self.uart.readline() + if self.debug: + print(f"gps_data: {gps_data}") + if gps_data is not None and isinstance(gps_data, bytes): + if gps_data[3:6] == b"GGA" and gps_data[-1] == b"\n"[0]: + self._decode_gga(gps_data.decode()) + elif gps_data[3:6] == b"RMC" and gps_data[-1] == b"\n"[0]: + self._decode_rmc(gps_data.decode()) + elif gps_data[3:6] == b"TXT" and gps_data[-1] == b"\n"[0]: + self._decode_txt(gps_data.decode()) diff --git a/m5stack/libs/base/manifest.py b/m5stack/libs/base/manifest.py index 28a17eed..0e3b6afc 100644 --- a/m5stack/libs/base/manifest.py +++ b/m5stack/libs/base/manifest.py @@ -6,6 +6,7 @@ ( "__init__.py", "atom_can.py", + "atom_gps.py", "atom_socket.py", "display.py", "echo.py", From 941f51c7e18eb58c671dcf9d64d5920a3e74736d Mon Sep 17 00:00:00 2001 From: hlym123 Date: Fri, 28 Feb 2025 16:12:47 +0800 Subject: [PATCH 006/322] libs/base: Add support for PWM Base. Signed-off-by: hlym123 --- docs/en/base/index.rst | 1 + docs/en/base/pwm.rst | 55 +++++ docs/en/refs/base.pwm.ref | 20 ++ docs/locales/zh_CN/LC_MESSAGES/base/pwm.po | 209 ++++++++++++++++++ .../base/pwm/atoms3_pwm_base_example.m5f2 | 1 + examples/base/pwm/atoms3r_pwm_base_example.py | 57 +++++ m5stack/libs/base/__init__.py | 1 + m5stack/libs/base/manifest.py | 1 + m5stack/libs/base/pwm.py | 100 +++++++++ 9 files changed, 445 insertions(+) create mode 100644 docs/en/base/pwm.rst create mode 100644 docs/en/refs/base.pwm.ref create mode 100644 docs/locales/zh_CN/LC_MESSAGES/base/pwm.po create mode 100644 examples/base/pwm/atoms3_pwm_base_example.m5f2 create mode 100644 examples/base/pwm/atoms3r_pwm_base_example.py create mode 100644 m5stack/libs/base/pwm.py diff --git a/docs/en/base/index.rst b/docs/en/base/index.rst index 5e081368..e816fa2b 100644 --- a/docs/en/base/index.rst +++ b/docs/en/base/index.rst @@ -10,6 +10,7 @@ Base display.rst echo.rst motion.rst + pwm.rst rs232.rst rs485.rst speaker.rst diff --git a/docs/en/base/pwm.rst b/docs/en/base/pwm.rst new file mode 100644 index 00000000..6054ed79 --- /dev/null +++ b/docs/en/base/pwm.rst @@ -0,0 +1,55 @@ +Atomic PWM Base +============================ + +.. sku: A114 + +.. include:: ../refs/base.pwm.ref + +Support the following products: + + |Atomic PWM Base| + +UiFlow2 Example: +-------------------------- + +PWM output control +^^^^^^^^^^^^^^^^^^^^^^^^ + +Open the |atoms3r_pwm_base_example.m5f2| project in UiFlow2. + +The example demonstrates controlling the PWM signal's duty cycle to fluctuate between low to high and high to low. + +UiFlow2 Code Block: + + |atoms3r_pwm_base_example.png| + +Example output: + + None + +MicroPython Example: +-------------------------- + +PWM output control +^^^^^^^^^^^^^^^^^^^^^^^^ + +The example demonstrates controlling the PWM signal's duty cycle to fluctuate between low to high and high to low. + +MicroPython Code Block: + + .. literalinclude:: ../../../examples/base/pwm/atoms3r_pwm_base_example.py + :language: python + :linenos: + +Example output: + + None + +**API** +-------------------------- + +PWM +^^^^^^^^^^^^^^^^^^^^^^^^ + +.. autoclass:: base.pwm.AtomicPWMBase + :members: diff --git a/docs/en/refs/base.pwm.ref b/docs/en/refs/base.pwm.ref new file mode 100644 index 00000000..e1dbcaf2 --- /dev/null +++ b/docs/en/refs/base.pwm.ref @@ -0,0 +1,20 @@ +.. |Atomic PWM Base| image:: https://static-cdn.m5stack.com/resource/docs/products/atom/Atomic%20PWM%20Base/img-44bfbe9c-c370-4bfd-b439-8cfd0bf3fd6d.webp + :target: https://docs.m5stack.com/zh_CN/atom/Atomic%20PWM%20Base + :height: 200px + :width: 200px + +.. |init.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/pwm/init.png +.. |set_freq.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/pwm/set_freq.png +.. |get_freq.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/pwm/get_freq.png +.. |set_duty_u16.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/pwm/set_duty_u16.png +.. |get_duty_u16.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/pwm/get_duty_u16.png +.. |atoms3r_pwm_base_example.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/pwm/example.png + +.. |atoms3r_pwm_base_example.m5f2| raw:: html + + + atoms3r_pwm_base_example.m5f2 + diff --git a/docs/locales/zh_CN/LC_MESSAGES/base/pwm.po b/docs/locales/zh_CN/LC_MESSAGES/base/pwm.po new file mode 100644 index 00000000..941037ca --- /dev/null +++ b/docs/locales/zh_CN/LC_MESSAGES/base/pwm.po @@ -0,0 +1,209 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2016 - 2025 M5Stack Technology Co., Ltd +# This file is distributed under the same license as the UIFlow2 Programming +# Guide package. +# FIRST AUTHOR , 2025. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: UIFlow2 Programming Guide \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-02-28 16:00+0800\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: zh_CN\n" +"Language-Team: zh_CN \n" +"Plural-Forms: nplurals=1; plural=0;\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.16.0\n" + +#: ../../en/base/pwm.rst:2 ../../en/refs/base.pwm.ref +#: 0876ab4fd3e940c7b82bd832d4ba3308 548a69ca56a540569155b9316264af97 +msgid "Atomic PWM Base" +msgstr "" + +#: ../../en/base/pwm.rst:8 92bfaa40a9b04d36a769f44ba3c4bea7 +msgid "Support the following products:" +msgstr "支持以下产品:" + +#: ../../en/base/pwm.rst:10 a496fff97cef4c768c74ed600abbbabc +msgid "|Atomic PWM Base|" +msgstr "" + +#: ../../en/base/pwm.rst:13 46938817d9b84a71b96bc32e3bd7553d +msgid "UiFlow2 Example:" +msgstr "UiFlow2 应用示例:" + +#: ../../en/base/pwm.rst:16 ../../en/base/pwm.rst:34 +#: 3e199a247af44f9faa041697a660911b 7888abfc326f460ab216e8968af33a5b +msgid "PWM output control" +msgstr "PWM 输出控制" + +#: ../../en/base/pwm.rst:18 9dd8a2f593b14d9089c36198cb5b5b88 +msgid "Open the |atoms3r_pwm_base_example.m5f2| project in UiFlow2." +msgstr "在 UiFlow2 上打开 |atoms3r_pwm_base_example.m5f2| 项目。" + +#: ../../en/base/pwm.rst:20 ../../en/base/pwm.rst:36 +#: 4417d327dae84d96a6468ede0d8154f1 87fc646aa72e4f5b9bc54649bcb83907 +msgid "" +"The example demonstrates controlling the PWM signal's duty cycle to " +"fluctuate between low to high and high to low." +msgstr "案例演示控制 PWM 信号的占空比在低到高、高到低之间来回变化。" + +#: ../../en/base/pwm.rst:22 5307632988264414a34893165de87511 +#: a942fa30dd94448d9a09384d84f4617d base.pwm.AtomicPWMBase:6 +#: base.pwm.AtomicPWMBase.get_duty_u16:6 base.pwm.AtomicPWMBase.get_freq:6 +#: base.pwm.AtomicPWMBase.set_duty_u16:7 base.pwm.AtomicPWMBase.set_freq:5 of +msgid "UiFlow2 Code Block:" +msgstr "UiFlow2 代码块:" + +#: ../../en/base/pwm.rst:24 38d7a31959f146e5848b679a3df541ed +msgid "|atoms3r_pwm_base_example.png|" +msgstr "" + +#: ../../en/refs/base.pwm.ref:11 27d1a81d3b0446328b7744e4ef7cd3f5 +msgid "atoms3r_pwm_base_example.png" +msgstr "" + +#: ../../en/base/pwm.rst:26 ../../en/base/pwm.rst:44 +#: 4c87c95931394de6b7b569cc8686efce +msgid "Example output:" +msgstr "示例输出:" + +#: ../../en/base/pwm.rst:28 ../../en/base/pwm.rst:46 +#: f685493c1eeb4c5990c1f710564f0bfa +msgid "None" +msgstr "无" + +#: ../../en/base/pwm.rst:31 fd82b33aece7497db669c84eb8fadf5d +msgid "MicroPython Example:" +msgstr "MicroPython 应用示例:" + +#: ../../en/base/pwm.rst:38 base.pwm.AtomicPWMBase:10 +#: base.pwm.AtomicPWMBase.get_duty_u16:10 base.pwm.AtomicPWMBase.get_freq:10 +#: base.pwm.AtomicPWMBase.set_duty_u16:11 base.pwm.AtomicPWMBase.set_freq:9 +#: e9a19c5c3d2748179266f9bb573d51b8 of +msgid "MicroPython Code Block:" +msgstr "MicroPython 代码块:" + +#: ../../en/base/pwm.rst:49 967e4e9affff4eca9c9d98145b16da77 +msgid "**API**" +msgstr "API应用" + +#: ../../en/base/pwm.rst:52 6ca490616e40434fa891d3be5a6c27c4 +msgid "PWM" +msgstr "" + +#: 1fb73946edf74f7794d39bd556580dba base.pwm.AtomicPWMBase:1 of +msgid "Bases: :py:class:`object`" +msgstr "" + +#: 6af683623f664437bde8c2a156f5a3c3 base.pwm.AtomicPWMBase:1 of +#, fuzzy +msgid "Create an AtomicPWMBase object." +msgstr "创建一个 PWM 对象。" + +#: ../../en/base/pwm.rst 0a4fb099859949868a6ddab4e9e6d55b +#: base.pwm.AtomicPWMBase.set_duty_u16 base.pwm.AtomicPWMBase.set_freq of +msgid "Parameters" +msgstr "" + +#: base.pwm.AtomicPWMBase:3 dbf8b30697e94fe4992d5b8e002dd567 of +msgid "The PWM output pin. Default is 5." +msgstr "PWM 输出引脚,默认为 5。" + +#: base.pwm.AtomicPWMBase:4 base.pwm.AtomicPWMBase.set_freq:3 +#: bcb660b0adcf4ade8ec1eabe346d4996 of +msgid "The PWM frequency. Default is 1000." +msgstr "PWM 频率,默认为 1000Hz。" + +#: base.pwm.AtomicPWMBase:8 ce498f1d593f41d8b9bd127ce56b9ea6 of +msgid "|init.png|" +msgstr "" + +#: ../../en/refs/base.pwm.ref:6 b28d99097448446e9f42d2a43cfe79f7 +msgid "init.png" +msgstr "" + +#: 4d59bbe1c9b0422296ea436fc19e1ba4 base.pwm.AtomicPWMBase.get_duty_u16:1 of +msgid "Get PWM duty cycle." +msgstr "获取 PWM 占空比。" + +#: base.pwm.AtomicPWMBase.get_duty_u16 base.pwm.AtomicPWMBase.get_freq +#: c09f6a9753994a3f9368322271d57900 of +msgid "Returns" +msgstr "" + +#: b5b85015c853491aae6625205a651322 base.pwm.AtomicPWMBase.get_duty_u16:3 of +msgid "PWM duty cycle. Range: 0~65535." +msgstr "PWM 占空比,范围:0~65535。" + +#: 15cbf308e80743619ff2606c17a8c2db base.pwm.AtomicPWMBase.get_duty_u16 +#: base.pwm.AtomicPWMBase.get_freq base.pwm.AtomicPWMBase.set_freq of +msgid "Return type" +msgstr "" + +#: 3f9877b5d96f41109e1d72dfc6019a52 base.pwm.AtomicPWMBase.get_duty_u16:8 of +msgid "|get_duty_u16.png|" +msgstr "" + +#: ../../en/refs/base.pwm.ref:10 9fc6724ff36b48338db1f1e0fa0d60e8 +msgid "get_duty_u16.png" +msgstr "" + +#: 299803f28eed4662955275b1aa73637e base.pwm.AtomicPWMBase.get_freq:1 of +msgid "Get PWM frequency." +msgstr "获取 PWM 频率。" + +#: 4ff2ae14e9464faf8da446b4ffaffd53 base.pwm.AtomicPWMBase.get_freq:3 of +msgid "PWM frequency." +msgstr "PWM 频率。" + +#: base.pwm.AtomicPWMBase.get_freq:8 f1bac7901be0439ab122d972265b0b4b of +msgid "|get_freq.png|" +msgstr "" + +#: ../../en/refs/base.pwm.ref:8 46fb35ebcf9542fdb12fab102b5e45a1 +msgid "get_freq.png" +msgstr "" + +#: base.pwm.AtomicPWMBase.set_duty_u16:1 cd1ef69fe46b4c3c94f6a5d73fccf421 of +msgid "Set PWM duty cycle." +msgstr "设置 PWM 占空比。" + +#: 8e0b3dc036b24f5fa48ef47550638007 base.pwm.AtomicPWMBase.set_duty_u16:3 of +msgid "" +"set the current duty cycle of the PWM output, as an unsigned 16-bit value" +" in the range 0 to 65535 inclusive." +msgstr "设置 PWM 输出的当前占空比,作为无符号 16 位值,范围为 0 到 65535(含 0 和 65535)。" + +#: 7de87b16ef034f9c9534bc0f18ac982a base.pwm.AtomicPWMBase.set_duty_u16:5 of +msgid "The PWM duty cycle. Range: 0 ~ 65535. Default is 0." +msgstr "PWM 占空比,范围:0~65535,默认为 0。" + +#: base.pwm.AtomicPWMBase.set_duty_u16:9 e809a20486cf45dbb654e786aa418fad of +msgid "|set_duty_u16.png|" +msgstr "" + +#: ../../en/refs/base.pwm.ref:9 27d1a81d3b0446328b7744e4ef7cd3f5 +msgid "set_duty_u16.png" +msgstr "" + +#: base.pwm.AtomicPWMBase.set_freq:1 ead77d1e60eb4cadadbac54bd6b14dc0 of +msgid "Set PWM frequency." +msgstr "设置 PWM 频率。" + +#: 6604a58e2b49494abae91d4c3cca10c2 base.pwm.AtomicPWMBase.set_freq:7 of +msgid "|set_freq.png|" +msgstr "" + +#: ../../en/refs/base.pwm.ref:7 96a8add2c40a4a72ac25b083aea1a39d +msgid "set_freq.png" +msgstr "" + +#~ msgid "PWM Base" +#~ msgstr "" + diff --git a/examples/base/pwm/atoms3_pwm_base_example.m5f2 b/examples/base/pwm/atoms3_pwm_base_example.m5f2 new file mode 100644 index 00000000..0722ea62 --- /dev/null +++ b/examples/base/pwm/atoms3_pwm_base_example.m5f2 @@ -0,0 +1 @@ +{"version":"V2.0","versionNumber":"V2.2.2","type":"atoms3r","components":[{"name":"screen","type":"screen","layer":0,"screenId":"builtin","screenName":"","id":"__atoms3r_screen","createTime":1740726439318,"x":0,"y":0,"width":128,"height":128,"backgroundColor":"#000000","size":0,"isSelected":true},{"name":"title0","type":"title","layer":1,"screenId":"builtin","screenName":"","id":"kM#pMvB2LwpGBiGp","createTime":1740726489914,"x":0,"y":0,"color":"#ffffff","backgroundColor":"#0000FF","text":"PWM Control","textOffset":0,"font":"Widgets.FONTS.DejaVu18","isSelected":false},{"name":"label0","type":"label","layer":2,"screenId":"builtin","screenName":"","id":"i3M8t3hGxnHVhCsw","createTime":1740726519596,"x":1,"y":35,"color":"#ffffff","backgroundColor":"#000000","text":"freq:","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false},{"name":"label1","type":"label","layer":3,"screenId":"builtin","screenName":"","id":"pDcY+p*J8o=h&SO0","createTime":1740726521235,"x":2,"y":65,"color":"#ffffff","backgroundColor":"#000000","text":"duty:","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false},{"name":"label_freq","type":"label","layer":4,"screenId":"builtin","screenName":"","id":"qPL4@HcHg4K!&Ebr","createTime":1740726575417,"x":47,"y":35,"color":"#ffffff","backgroundColor":"#000000","text":"1000Hz","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false},{"name":"label_duty","type":"label","layer":5,"screenId":"builtin","screenName":"","id":"oz21ydMh1$S9br_k","createTime":1740726577156,"x":55,"y":65,"color":"#ffffff","backgroundColor":"#000000","text":"0","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false}],"resources":[{"hardware":["hardware_button","hardware_pin_button","imu","ir"]},{"base":["base_pwm"]}],"units":[],"hats":[],"bases":[{"type":"base_pwm","name":"base_pwm","id":"va9=wFK%vhb6L`*8","createTime":1740730093228,"initBlockType":"base_pwm_init","initBlockId":"UW2!4d7LHOcK%,f)/)t4"}],"i2cs":[],"blockly":"itrue51000label_freqLabelhello M5Hztruei1000MULTIPLY1i150label_dutyLabel40i1000MINUS150001MULTIPLY1i150label_dutyLabel40","screen":[{"simulationName":"Built-in","type":"builtin","width":128,"height":128,"scale":1.3,"screenName":"","blockId":"","screenColorType":0,"id":"builtin","createTime":1740726439317}],"logicWhenNum":0,"customList":[]} \ No newline at end of file diff --git a/examples/base/pwm/atoms3r_pwm_base_example.py b/examples/base/pwm/atoms3r_pwm_base_example.py new file mode 100644 index 00000000..aa395028 --- /dev/null +++ b/examples/base/pwm/atoms3r_pwm_base_example.py @@ -0,0 +1,57 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import os, sys, io +import M5 +from M5 import * +from base import AtomicPWMBase +import time + + +title0 = None +label0 = None +label1 = None +label_freq = None +label_duty = None +base_pwm = None +i = None + + +def setup(): + global title0, label0, label1, label_freq, label_duty, base_pwm, i + M5.begin() + title0 = Widgets.Title("PWM Control", 0, 0xFFFFFF, 0x0000FF, Widgets.FONTS.DejaVu18) + label0 = Widgets.Label("freq:", 1, 35, 1.0, 0xFFFFFF, 0x000000, Widgets.FONTS.DejaVu18) + label1 = Widgets.Label("duty:", 2, 65, 1.0, 0xFFFFFF, 0x000000, Widgets.FONTS.DejaVu18) + label_freq = Widgets.Label("1000Hz", 47, 35, 1.0, 0xFFFFFF, 0x000000, Widgets.FONTS.DejaVu18) + label_duty = Widgets.Label("0", 55, 65, 1.0, 0xFFFFFF, 0x000000, Widgets.FONTS.DejaVu18) + base_pwm = AtomicPWMBase(5, 1000) + label_freq.setText(str((str((base_pwm.get_freq())) + str("Hz")))) + + +def loop(): + global title0, label0, label1, label_freq, label_duty, base_pwm, i + M5.update() + for i in range(100): + base_pwm.set_duty_u16(i * 150) + label_duty.setText(str(base_pwm.get_duty_u16())) + time.sleep_ms(40) + for i in range(100): + base_pwm.set_duty_u16(15000 - i * 150) + label_duty.setText(str(base_pwm.get_duty_u16())) + time.sleep_ms(40) + + +if __name__ == "__main__": + try: + setup() + while True: + loop() + except (Exception, KeyboardInterrupt) as e: + try: + from utility import print_error_msg + + print_error_msg(e) + except ImportError: + print("please update to latest firmware") diff --git a/m5stack/libs/base/__init__.py b/m5stack/libs/base/__init__.py index 810acdb0..f5c7a412 100644 --- a/m5stack/libs/base/__init__.py +++ b/m5stack/libs/base/__init__.py @@ -10,6 +10,7 @@ "AtomicDisplayBase": "display", "Motion": "motion", "MotionBase": "motion", + "AtomicPWMBase": "pwm", "AtomRS232": "rs232", "AtomRS485": "rs232", "SpeakerBase": "speaker", diff --git a/m5stack/libs/base/manifest.py b/m5stack/libs/base/manifest.py index 0e3b6afc..f7f6a454 100644 --- a/m5stack/libs/base/manifest.py +++ b/m5stack/libs/base/manifest.py @@ -11,6 +11,7 @@ "display.py", "echo.py", "motion.py", + "pwm.py", "rs232.py", "speaker.py", ), diff --git a/m5stack/libs/base/pwm.py b/m5stack/libs/base/pwm.py new file mode 100644 index 00000000..d67f245e --- /dev/null +++ b/m5stack/libs/base/pwm.py @@ -0,0 +1,100 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import machine + + +class AtomicPWMBase: + """Create an AtomicPWMBase object. + + :param int out_pin: The PWM output pin. Default is 5. + :param int freq: The PWM frequency. Default is 1000. + + UiFlow2 Code Block: + + |init.png| + + MicroPython Code Block: + + .. code-block:: python + + from base import AtomicPWMBase + + base_pwm = AtomicPWMBase(out_pin=5, freq=1000) + """ + + def __init__(self, out_pin: int = 5, freq: int = 1000) -> None: + self.pwm = machine.PWM(machine.Pin(out_pin), freq=freq, duty=0) + + def set_freq(self, freq: int = 1000) -> None: + """Set PWM frequency. + + :param int freq: The PWM frequency. Default is 1000. + + UiFlow2 Code Block: + + |set_freq.png| + + MicroPython Code Block: + + .. code-block:: python + + base_pwm.set_freq() + """ + self.pwm.freq(freq) + + def get_freq(self) -> int: + """Get PWM frequency. + + :returns: PWM frequency. + :rtype: int + + UiFlow2 Code Block: + + |get_freq.png| + + MicroPython Code Block: + + .. code-block:: python + + base_pwm.get_freq() + """ + return self.pwm.freq() + + def set_duty_u16(self, duty: int = 0): + """Set PWM duty cycle. + + set the current duty cycle of the PWM output, as an unsigned 16-bit value in the range 0 to 65535 inclusive. + + :param int duty: The PWM duty cycle. Range: 0 ~ 65535. Default is 0. + + UiFlow2 Code Block: + + |set_duty_u16.png| + + MicroPython Code Block: + + .. code-block:: python + + base_pwm.set_duty_u16() + """ + self.pwm.duty_u16(duty) + + def get_duty_u16(self) -> int: + """Get PWM duty cycle. + + :returns: PWM duty cycle. Range: 0~65535. + :rtype: int + + UiFlow2 Code Block: + + |get_duty_u16.png| + + MicroPython Code Block: + + .. code-block:: python + + base_pwm.get_duty_u16() + """ + return self.pwm.duty_u16() From 948e041cb528fe6e9c7796f6426b59451754302d Mon Sep 17 00:00:00 2001 From: hlym123 Date: Thu, 6 Mar 2025 09:39:11 +0800 Subject: [PATCH 007/322] libs/base: Add support for HDriver Base. Signed-off-by: hlym123 --- docs/en/base/hdriver.rst | 55 ++++ docs/en/base/index.rst | 1 + docs/en/refs/base.hdriver.ref | 21 ++ .../locales/zh_CN/LC_MESSAGES/base/hdriver.po | 255 ++++++++++++++++++ .../hdriver/atoms3r_hdriver_base_example.m5f2 | 1 + .../hdriver/atoms3r_hdriver_base_example.py | 71 +++++ m5stack/libs/base/__init__.py | 1 + m5stack/libs/base/hdriver.py | 137 ++++++++++ m5stack/libs/base/manifest.py | 1 + 9 files changed, 543 insertions(+) create mode 100644 docs/en/base/hdriver.rst create mode 100644 docs/en/refs/base.hdriver.ref create mode 100644 docs/locales/zh_CN/LC_MESSAGES/base/hdriver.po create mode 100644 examples/base/hdriver/atoms3r_hdriver_base_example.m5f2 create mode 100644 examples/base/hdriver/atoms3r_hdriver_base_example.py create mode 100644 m5stack/libs/base/hdriver.py diff --git a/docs/en/base/hdriver.rst b/docs/en/base/hdriver.rst new file mode 100644 index 00000000..7bac10ba --- /dev/null +++ b/docs/en/base/hdriver.rst @@ -0,0 +1,55 @@ +Atomic HDriver Base +============================ + +.. sku: A092 + +.. include:: ../refs/base.hdriver.ref + +Support the following products: + + |Atomic HDriver Base| + +UiFlow2 Example: +-------------------------- + +Motor speed control +^^^^^^^^^^^^^^^^^^^^^^^^ + +Open the |atoms3r_hdriver_base_example.m5f2| project in UiFlow2. + +The example demonstrates the motor speed changing from low to high, high to low, and then reversing, changing from low to high and high to low. + +UiFlow2 Code Block: + + |atoms3r_hdriver_base_example.png| + +Example output: + + None + +MicroPython Example: +-------------------------- + +Motor speed control +^^^^^^^^^^^^^^^^^^^^^^^^ + +The example demonstrates the motor speed changing from low to high, high to low, and then reversing, changing from low to high and high to low. + +MicroPython Code Block: + + .. literalinclude:: ../../../examples/base/hdriver/atoms3r_hdriver_base_example.py + :language: python + :linenos: + +Example output: + + None + +**API** +-------------------------- + +AtomicHDriverBase +^^^^^^^^^^^^^^^^^^^^^^^^ + +.. autoclass:: base.hdriver.AtomicHDriverBase + :members: diff --git a/docs/en/base/index.rst b/docs/en/base/index.rst index e816fa2b..f5781435 100644 --- a/docs/en/base/index.rst +++ b/docs/en/base/index.rst @@ -9,6 +9,7 @@ Base atom_socket.rst display.rst echo.rst + hdriver.rst motion.rst pwm.rst rs232.rst diff --git a/docs/en/refs/base.hdriver.ref b/docs/en/refs/base.hdriver.ref new file mode 100644 index 00000000..f4d934c3 --- /dev/null +++ b/docs/en/refs/base.hdriver.ref @@ -0,0 +1,21 @@ +.. |Atomic HDriver Base| image:: https://static-cdn.m5stack.com/resource/docs/products/atom/Atomic%20H-Driver%20Base/img-ee0353ff-f8a5-40c0-929e-455844999d21.webp + :target: https://docs.m5stack.com/zh_CN/atom/Atomic%20H-Driver%20Base + :height: 200px + :width: 200px + +.. |init.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/hdriver/init.png +.. |set_freq.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/hdriver/set_freq.png +.. |get_freq.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/hdriver/get_freq.png +.. |set_speed.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/hdriver/set_speed.png +.. |get_status.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/hdriver/get_status.png +.. |get_voltage.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/hdriver/get_voltage.png +.. |atoms3r_hdriver_base_example.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/hdriver/example.png + +.. |atoms3r_hdriver_base_example.m5f2| raw:: html + + + atoms3r_hdriver_base_example.m5f2 + diff --git a/docs/locales/zh_CN/LC_MESSAGES/base/hdriver.po b/docs/locales/zh_CN/LC_MESSAGES/base/hdriver.po new file mode 100644 index 00000000..9109a217 --- /dev/null +++ b/docs/locales/zh_CN/LC_MESSAGES/base/hdriver.po @@ -0,0 +1,255 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2016 - 2025 M5Stack Technology Co., Ltd +# This file is distributed under the same license as the UIFlow2 Programming +# Guide package. +# FIRST AUTHOR , 2025. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: UIFlow2 Programming Guide \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-02-28 17:21+0800\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: zh_CN\n" +"Language-Team: zh_CN \n" +"Plural-Forms: nplurals=1; plural=0;\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.16.0\n" + +#: ../../en/base/hdriver.rst:2 ../../en/refs/base.hdriver.ref +#: 123ed0601b144e81af7d48fe3cbe6222 cc34299d9e254f8bbd663f1e87efcbb5 +msgid "Atomic HDriver Base" +msgstr "" + +#: ../../en/base/hdriver.rst:8 00c7698b3cb34288a801a8541f70b3b9 +msgid "Support the following products:" +msgstr "支持以下产品:" + +#: ../../en/base/hdriver.rst:10 fba4281020e24e5d9d20f88430e3b604 +msgid "|Atomic HDriver Base|" +msgstr "" + +#: ../../en/base/hdriver.rst:13 6dc0db78d88e42819e71bf396faf25dc +msgid "UiFlow2 Example:" +msgstr "UiFlow2 应用示例:" + +#: ../../en/base/hdriver.rst:16 ../../en/base/hdriver.rst:34 +#: 3e88a66675344d44aad194933f25d215 f87102487c134bd1bd1147fa7459d10a +msgid "Motor speed control" +msgstr "电机速度控制" + +#: ../../en/base/hdriver.rst:18 1273b4a44e8c41549135c1a428d6b137 +msgid "Open the |atoms3r_hdriver_base_example.m5f2| project in UiFlow2." +msgstr "在 UiFlow2 上打开 |atoms3r_hdriver_base_example.m5f2| 项目。" + +#: ../../en/base/hdriver.rst:20 ../../en/base/hdriver.rst:36 +#: 9c5a9f08d59d4b66aaad4d072c924e50 df30a652929643298800759059a20494 +msgid "" +"The example demonstrates the motor speed changing from low to high, high " +"to low, and then reversing, changing from low to high and high to low." +msgstr "案例演示电机转速从低到高、高到低,并进行反向控制,从低到高、高到低。" + +#: ../../en/base/hdriver.rst:22 555e6897971643fbaf33e6bd28088550 +#: base.hdriver.AtomicHDriverBase:9 base.hdriver.AtomicHDriverBase.get_freq:6 +#: base.hdriver.AtomicHDriverBase.get_status:6 +#: base.hdriver.AtomicHDriverBase.get_voltage:6 +#: base.hdriver.AtomicHDriverBase.set_freq:5 +#: base.hdriver.AtomicHDriverBase.set_speed:5 of +msgid "UiFlow2 Code Block:" +msgstr "UiFlow2 代码块:" + +#: ../../en/base/hdriver.rst:24 455cf72de52f470d94ef31ee85a91f21 +msgid "|atoms3r_hdriver_base_example.png|" +msgstr "" + +#: ../../en/refs/base.hdriver.ref:12 69ed7e46ae1145408c6cfc27b58d68b8 +msgid "atoms3r_hdriver_base_example.png" +msgstr "" + +#: ../../en/base/hdriver.rst:26 ../../en/base/hdriver.rst:44 +#: fe83a7d83e984e3c804a12f82fe0bf4e +msgid "Example output:" +msgstr "示例输出:" + +#: ../../en/base/hdriver.rst:28 ../../en/base/hdriver.rst:46 +#: 9c0bd7c651274cae8483ca95553404e5 +msgid "None" +msgstr "无" + +#: ../../en/base/hdriver.rst:31 0b8832506243437694a6d7caac8798d1 +msgid "MicroPython Example:" +msgstr "MicroPython 应用示例:" + +#: ../../en/base/hdriver.rst:38 685974c074d043e49ce65eccfe49c493 +#: base.hdriver.AtomicHDriverBase:13 base.hdriver.AtomicHDriverBase.get_freq:10 +#: base.hdriver.AtomicHDriverBase.get_status:10 +#: base.hdriver.AtomicHDriverBase.get_voltage:10 +#: base.hdriver.AtomicHDriverBase.set_freq:9 +#: base.hdriver.AtomicHDriverBase.set_speed:9 of +msgid "MicroPython Code Block:" +msgstr "MicroPython 代码块:" + +#: ../../en/base/hdriver.rst:49 c7567aeedbe84869b7cacc13a808e722 +msgid "**API**" +msgstr "API应用" + +#: ../../en/base/hdriver.rst:52 062d436b5efe406f8eed04361e46d66b +msgid "AtomicHDriverBase" +msgstr "" + +#: base.hdriver.AtomicHDriverBase:1 f4ff144ba20c490e91c8120a6ab37bbf of +msgid "Bases: :py:class:`object`" +msgstr "" + +#: 123ed0601b144e81af7d48fe3cbe6222 base.hdriver.AtomicHDriverBase:1 of +msgid "Create an AtomicHDriverBase object." +msgstr "创建一个 AtomicHDriverBase 对象。" + +#: ../../en/base/hdriver.rst a2b6699ce3714f6db6124bc441c3845f +#: base.hdriver.AtomicHDriverBase.set_freq +#: base.hdriver.AtomicHDriverBase.set_speed of +msgid "Parameters" +msgstr "" + +#: base.hdriver.AtomicHDriverBase:3 cbf2696ee8f84bf4850b991c9ef54e90 of +msgid "PWM control pin1." +msgstr "PWM 控制引脚1" + +#: 266a3eb6ac1349dc82c037dd43390838 base.hdriver.AtomicHDriverBase:4 of +msgid "PWM control pin2." +msgstr "PWM 控制引脚2" + +#: 486b0068fd31449f929c06d5d11abcaa base.hdriver.AtomicHDriverBase:5 of +msgid "driver status." +msgstr "驱动状态。" + +#: 02d30be4300747ed9b314ec6e0907886 base.hdriver.AtomicHDriverBase:6 of +msgid "driver input voltage detect." +msgstr "驱动输入电压检测。" + +#: base.hdriver.AtomicHDriverBase:7 f0bb2360f97c43c2a8e2cbe2154caf5e of +msgid "The PWM frequency." +msgstr "PWM 频率。" + +#: 3b32784c0aae4fc8994d891dcdc67593 base.hdriver.AtomicHDriverBase:11 of +msgid "|init.png|" +msgstr "" + +#: ../../en/refs/base.hdriver.ref:6 69ed7e46ae1145408c6cfc27b58d68b8 +msgid "init.png" +msgstr "" + +#: base.hdriver.AtomicHDriverBase.get_freq:1 fa19705831d247e4a9ebfc6aaa39783d +#: of +msgid "Get PWM frequency." +msgstr "获取 PWM 频率。" + +#: 7eadcb681a214d41a08cd4502c3c506b base.hdriver.AtomicHDriverBase.get_freq +#: base.hdriver.AtomicHDriverBase.get_status +#: base.hdriver.AtomicHDriverBase.get_voltage of +msgid "Returns" +msgstr "" + +#: base.hdriver.AtomicHDriverBase.get_freq:3 c33611bb1c6f4462ac5350c918c10ce5 +#: of +msgid "PWM frequency." +msgstr "PWM 频率。" + +#: 9b9747b061114e0395d43bccde3c0650 base.hdriver.AtomicHDriverBase.get_freq +#: base.hdriver.AtomicHDriverBase.get_status +#: base.hdriver.AtomicHDriverBase.get_voltage +#: base.hdriver.AtomicHDriverBase.set_freq +#: base.hdriver.AtomicHDriverBase.set_speed of +msgid "Return type" +msgstr "" + +#: 01fd12d8bd8440888c8dcb36f0bb2a36 base.hdriver.AtomicHDriverBase.get_freq:8 +#: of +msgid "|get_freq.png|" +msgstr "" + +#: ../../en/refs/base.hdriver.ref:8 69ed7e46ae1145408c6cfc27b58d68b8 +msgid "get_freq.png" +msgstr "" + +#: 981b6bb6fb5e45e492573564af8a11f6 base.hdriver.AtomicHDriverBase.get_status:1 +#: of +msgid "Get driver status." +msgstr "获取驱动状态。" + +#: 39d3b68199804332929653218d4f636e base.hdriver.AtomicHDriverBase.get_status:3 +#: of +msgid "" +"The driver status. Returns True if the driver is operating normally, or " +"False if a fault is detected." +msgstr "驱动状态。驱动正常工作返回 True,异常返回 False。" + +#: 0c6b08af59f64b74b6e1ff9f90ee8c5d base.hdriver.AtomicHDriverBase.get_status:8 +#: of +msgid "|get_status.png|" +msgstr "" + +#: ../../en/refs/base.hdriver.ref:10 69ed7e46ae1145408c6cfc27b58d68b8 +msgid "get_status.png" +msgstr "" + +#: 311489dece3c4961af0df8652b61411f +#: base.hdriver.AtomicHDriverBase.get_voltage:1 of +msgid "Get voltage." +msgstr "获取电压。" + +#: b096cbd1ba7644568e98288e69c89bf6 +#: base.hdriver.AtomicHDriverBase.get_voltage:3 of +msgid "The driver input voltage. unit: V" +msgstr "驱动输入电压,单位:V。" + +#: base.hdriver.AtomicHDriverBase.get_voltage:8 +#: c9b4d3c6329147a8a6303013bd6d5f01 of +msgid "|get_voltage.png|" +msgstr "" + +#: ../../en/refs/base.hdriver.ref:11 69ed7e46ae1145408c6cfc27b58d68b8 +msgid "get_voltage.png" +msgstr "" + +#: 577412a97b1f41f88e089b4a5578910b base.hdriver.AtomicHDriverBase.set_freq:1 +#: of +msgid "Set PWM frequency." +msgstr "设置 PWM 频率。" + +#: 9b05ba4736c34462a9b8a54f6fa6c5a6 base.hdriver.AtomicHDriverBase.set_freq:3 +#: of +msgid "The PWM frequency. Default is 1000." +msgstr "PWM 频率,默认为1000。" + +#: base.hdriver.AtomicHDriverBase.set_freq:7 d3bebd155b1549c1bb443cd771f7bb18 +#: of +msgid "|set_freq.png|" +msgstr "" + +#: ../../en/refs/base.hdriver.ref:7 69ed7e46ae1145408c6cfc27b58d68b8 +msgid "set_freq.png" +msgstr "" + +#: 845a363603a3482aa177cfc4a39dd9db base.hdriver.AtomicHDriverBase.set_speed:1 +#: of +msgid "Set motor speed." +msgstr "设置电机速度。" + +#: 2b1380498941405fa348416f274b2728 base.hdriver.AtomicHDriverBase.set_speed:3 +#: of +msgid "The motor speed. Range -100~100. Default is 0." +msgstr "电机速度,范围:-100~100,默认为0。" + +#: 0faffa24161a4551965b1ee81da4f9ea base.hdriver.AtomicHDriverBase.set_speed:7 +#: of +msgid "|set_speed.png|" +msgstr "" + +#: ../../en/refs/base.hdriver.ref:9 69ed7e46ae1145408c6cfc27b58d68b8 +msgid "set_speed.png" +msgstr "" diff --git a/examples/base/hdriver/atoms3r_hdriver_base_example.m5f2 b/examples/base/hdriver/atoms3r_hdriver_base_example.m5f2 new file mode 100644 index 00000000..9eb4d65d --- /dev/null +++ b/examples/base/hdriver/atoms3r_hdriver_base_example.m5f2 @@ -0,0 +1 @@ +{"version":"V2.0","versionNumber":"V2.2.2","type":"atoms3r","components":[{"name":"screen","type":"screen","layer":0,"screenId":"builtin","screenName":"","id":"__atoms3r_screen","createTime":1740731912689,"x":0,"y":0,"width":128,"height":128,"backgroundColor":"#000000","size":0,"isSelected":true},{"name":"title0","type":"title","layer":1,"screenId":"builtin","screenName":"","id":"vg%k1v7S6y9#5^@h","createTime":1740731974093,"x":0,"y":0,"color":"#ffffff","backgroundColor":"#0000FF","text":"Speed Ctrl","textOffset":3,"font":"Widgets.FONTS.DejaVu18","isSelected":false},{"name":"label0","type":"label","layer":2,"screenId":"builtin","screenName":"","id":"gjIDl*7-vhXo61EG","createTime":1740731998450,"x":5,"y":65,"color":"#ffffff","backgroundColor":"#000000","text":"speed:","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false,"width":63,"height":21},{"name":"label1","type":"label","layer":3,"screenId":"builtin","screenName":"","id":"fZlUKS=W9RBUrVp!","createTime":1740731999797,"x":5,"y":35,"color":"#ffffff","backgroundColor":"#000000","text":"vol:","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false,"width":34,"height":21},{"name":"label_vol","type":"label","layer":4,"screenId":"builtin","screenName":"","id":"uXB=2oUSGGzq0ya*","createTime":1740732046212,"x":45,"y":35,"color":"#ffffff","backgroundColor":"#000000","text":"12.0V","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false,"width":55,"height":21},{"name":"label_speed","type":"label","layer":5,"screenId":"builtin","screenName":"","id":"kKDSL%T7jNwUXhIt","createTime":1740732050828,"x":70,"y":65,"color":"#ffffff","backgroundColor":"#000000","text":"0","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false,"width":12,"height":21}],"resources":[{"hardware":["hardware_button","hardware_pin_button","imu","ir"]},{"base":["base_hdriver"]}],"units":[],"hats":[],"bases":[{"type":"base_hdriver","name":"base_hdriver","id":"uFoxgHKEjo5rAVP&","createTime":1740731940700,"initBlockType":"base_hdriver_init","initBlockId":"j-[a4+G5aVfYWMs3Gd!V"}],"i2cs":[],"blockly":"ispeedtrue67581000label_volLabelhello M5Vspeed0truei50speedi0speedlabel_speedLabelspeed40i50speedMINUS501i0speedlabel_speedLabelspeed40i50speedMINUS11i0speedlabel_speedLabelspeed40i50speedMINUS50i500speedlabel_speedLabelspeed40","screen":[{"simulationName":"Built-in","type":"builtin","width":128,"height":128,"scale":1.3,"screenName":"","blockId":"","screenColorType":0,"id":"builtin","createTime":1740731912688}],"logicWhenNum":0,"customList":[]} \ No newline at end of file diff --git a/examples/base/hdriver/atoms3r_hdriver_base_example.py b/examples/base/hdriver/atoms3r_hdriver_base_example.py new file mode 100644 index 00000000..7798e1cc --- /dev/null +++ b/examples/base/hdriver/atoms3r_hdriver_base_example.py @@ -0,0 +1,71 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import os, sys, io +import M5 +from M5 import * +from base import AtomicHDriverBase +import time + + +title0 = None +label0 = None +label1 = None +label_vol = None +label_speed = None +base_hdriver = None +i = None +speed = None + + +def setup(): + global title0, label0, label1, label_vol, label_speed, base_hdriver, speed, i + M5.begin() + title0 = Widgets.Title("Speed Ctrl", 3, 0xFFFFFF, 0x0000FF, Widgets.FONTS.DejaVu18) + label0 = Widgets.Label("speed:", 5, 65, 1.0, 0xFFFFFF, 0x000000, Widgets.FONTS.DejaVu18) + label1 = Widgets.Label("vol:", 5, 35, 1.0, 0xFFFFFF, 0x000000, Widgets.FONTS.DejaVu18) + label_vol = Widgets.Label("12.0V", 45, 35, 1.0, 0xFFFFFF, 0x000000, Widgets.FONTS.DejaVu18) + label_speed = Widgets.Label("0", 70, 65, 1.0, 0xFFFFFF, 0x000000, Widgets.FONTS.DejaVu18) + base_hdriver = AtomicHDriverBase(6, 7, 5, 8, 1000) + label_vol.setText(str((str((base_hdriver.get_voltage())) + str("V")))) + speed = 0 + + +def loop(): + global title0, label0, label1, label_vol, label_speed, base_hdriver, speed, i + M5.update() + for i in range(50): + speed = i + base_hdriver.set_speed(speed) + label_speed.setText(str(speed)) + time.sleep_ms(40) + for i in range(50): + speed = 50 - i + base_hdriver.set_speed(speed) + label_speed.setText(str(speed)) + time.sleep_ms(40) + for i in range(50): + speed = 1 - i + base_hdriver.set_speed(speed) + label_speed.setText(str(speed)) + time.sleep_ms(40) + for i in range(50): + speed = i - 50 + base_hdriver.set_speed(speed) + label_speed.setText(str(speed)) + time.sleep_ms(40) + + +if __name__ == "__main__": + try: + setup() + while True: + loop() + except (Exception, KeyboardInterrupt) as e: + try: + from utility import print_error_msg + + print_error_msg(e) + except ImportError: + print("please update to latest firmware") diff --git a/m5stack/libs/base/__init__.py b/m5stack/libs/base/__init__.py index f5c7a412..a73f8c38 100644 --- a/m5stack/libs/base/__init__.py +++ b/m5stack/libs/base/__init__.py @@ -8,6 +8,7 @@ "ATOMSocketBase": "atom_socket", "ATOMEchoBase": "echo", "AtomicDisplayBase": "display", + "AtomicHDriverBase": "hdriver", "Motion": "motion", "MotionBase": "motion", "AtomicPWMBase": "pwm", diff --git a/m5stack/libs/base/hdriver.py b/m5stack/libs/base/hdriver.py new file mode 100644 index 00000000..88255b4d --- /dev/null +++ b/m5stack/libs/base/hdriver.py @@ -0,0 +1,137 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import machine + + +class AtomicHDriverBase: + """Create an AtomicHDriverBase object. + + :param int in1: PWM control pin1. + :param int in2: PWM control pin2. + :param int fault: driver status. + :param int vin: driver input voltage detect. + :param int freq: The PWM frequency. + + UiFlow2 Code Block: + + |init.png| + + MicroPython Code Block: + + .. code-block:: python + + from base import AtomicHDriverBase + + base_hdriver = AtomicHDriverBase(in1 = 6, in2 = 7, fault = 5, vin = 8, freq = 1000) + """ + + def __init__( + self, in1: int = 6, in2: int = 7, fault: int = 5, vin: int = 8, freq: int = 1000 + ) -> None: + self.pwm_freq = freq + self.pwm_in1 = machine.PWM(machine.Pin(in1), freq=self.pwm_freq, duty=0) + self.pwm_in2 = machine.PWM(machine.Pin(in2), freq=self.pwm_freq, duty=0) + self.fault = machine.Pin(fault, machine.Pin.IN) + self.adc = machine.ADC(machine.Pin(vin), atten=machine.ADC.ATTN_11DB) + self.adc.atten(machine.ADC.ATTN_6DB) # 设置 ADC 采样范围(输入电压为:9~24V 的 1/10) + + def set_freq(self, freq: int = 1000) -> None: + """Set PWM frequency. + + :param int freq: The PWM frequency. Default is 1000. + + UiFlow2 Code Block: + + |set_freq.png| + + MicroPython Code Block: + + .. code-block:: python + + base_hdriver.set_freq() + """ + self.pwm_freq = freq + self.pwm_in1.freq(self.pwm_freq) + self.pwm_in2.freq(self.pwm_freq) + + def get_freq(self) -> int: + """Get PWM frequency. + + :returns: PWM frequency. + :rtype: int + + UiFlow2 Code Block: + + |get_freq.png| + + MicroPython Code Block: + + .. code-block:: python + + base_hdriver.get_freq() + """ + return self.pwm_freq + + def set_speed(self, speed: float = 0) -> None: + """Set motor speed. + + :param float speed: The motor speed. Range -100~100. Default is 0. + + UiFlow2 Code Block: + + |set_speed.png| + + MicroPython Code Block: + + .. code-block:: python + + base_hdriver.set_speed() + """ + speed = max(-100, min(speed, 100)) + if speed > 0: + self.pwm_in1.duty_u16(int(speed / 100.0 * 65535)) + self.pwm_in2.duty_u16(0) + elif speed < 0: + self.pwm_in1.duty_u16(0) + self.pwm_in2.duty_u16(int(-speed / 100.0 * 65535)) + else: + self.pwm_in1.duty_u16(0) + self.pwm_in2.duty_u16(0) + + def get_status(self) -> bool: + """Get driver status. + + :returns: The driver status. Returns True if the driver is operating normally, or False if a fault is detected. + :rtype: bool + + UiFlow2 Code Block: + + |get_status.png| + + MicroPython Code Block: + + .. code-block:: python + + base_hdriver.get_status() + """ + return self.fault.value() != 0 # 在故障状况期间下拉为低电平。 + + def get_voltage(self) -> float: + """Get voltage. + + :returns: The driver input voltage. unit: V + :rtype: float + + UiFlow2 Code Block: + + |get_voltage.png| + + MicroPython Code Block: + + .. code-block:: python + + base_hdriver.get_voltage() + """ + return self.adc.read_uv() * 10 / 1_000_000.0 # x10 是因为输入为 VIN 的 1/10 diff --git a/m5stack/libs/base/manifest.py b/m5stack/libs/base/manifest.py index f7f6a454..e93d1127 100644 --- a/m5stack/libs/base/manifest.py +++ b/m5stack/libs/base/manifest.py @@ -10,6 +10,7 @@ "atom_socket.py", "display.py", "echo.py", + "hdriver.py", "motion.py", "pwm.py", "rs232.py", From fcb5a4317de7668be47f151b8d1ab5787dc24629 Mon Sep 17 00:00:00 2001 From: hlym123 Date: Fri, 28 Feb 2025 18:13:00 +0800 Subject: [PATCH 008/322] libs/base: Add support for Stepmotor Base. Signed-off-by: hlym123 --- docs/en/base/index.rst | 1 + docs/en/base/stepmotor.rst | 87 ++++ docs/en/refs/base.stepmotor.ref | 36 ++ .../zh_CN/LC_MESSAGES/base/stepmotor.po | 370 ++++++++++++++++++ ...r_stepmotor_direction_control_example.m5f2 | 1 + ...s3r_stepmotor_direction_control_example.py | 55 +++ ...ms3r_stepmotor_rotate_control_example.m5f2 | 1 + ...toms3r_stepmotor_rotate_control_example.py | 76 ++++ m5stack/libs/base/__init__.py | 1 + m5stack/libs/base/manifest.py | 1 + m5stack/libs/base/stepmotor.py | 197 ++++++++++ 11 files changed, 826 insertions(+) create mode 100644 docs/en/base/stepmotor.rst create mode 100644 docs/en/refs/base.stepmotor.ref create mode 100644 docs/locales/zh_CN/LC_MESSAGES/base/stepmotor.po create mode 100644 examples/base/stepmotor/atoms3r_stepmotor_direction_control_example.m5f2 create mode 100644 examples/base/stepmotor/atoms3r_stepmotor_direction_control_example.py create mode 100644 examples/base/stepmotor/atoms3r_stepmotor_rotate_control_example.m5f2 create mode 100644 examples/base/stepmotor/atoms3r_stepmotor_rotate_control_example.py create mode 100644 m5stack/libs/base/stepmotor.py diff --git a/docs/en/base/index.rst b/docs/en/base/index.rst index f5781435..85631125 100644 --- a/docs/en/base/index.rst +++ b/docs/en/base/index.rst @@ -15,3 +15,4 @@ Base rs232.rst rs485.rst speaker.rst + stepmotor.rst diff --git a/docs/en/base/stepmotor.rst b/docs/en/base/stepmotor.rst new file mode 100644 index 00000000..09c43ac0 --- /dev/null +++ b/docs/en/base/stepmotor.rst @@ -0,0 +1,87 @@ +Atomic Stepmotor Base +============================ + +.. sku: A132 + +.. include:: ../refs/base.stepmotor.ref + + +Support the following products: + + |Atomic Stepmotor Base| + + +UiFlow2 Example: +-------------------------- + +Direction control +^^^^^^^^^^^^^^^^^^^^^^^^ + +Open the |atoms3r_stepmotor_direction_control_example.m5f2| project in UiFlow2. + +The example demonstrates motor direction control. Pressing the screen button toggles the rotation direction. + +UiFlow2 Code Block: + + |atoms3r_stepmotor_direction_control_example.png| + +Example output: + + None + +Rotate control +^^^^^^^^^^^^^^^^^^^^^^^^ + +Open the |atoms3r_stepmotor_rotate_control_example.m5f2| project in UiFlow2. + +The example demonstrates the motor continuously rotating for multiple turns, then reversing for multiple turns, and repeating the cycle after a 2-second pause. + +UiFlow2 Code Block: + + |atoms3r_stepmotor_rotate_control_example.png| + +Example output: + + None + +MicroPython Example: +-------------------------- + +Direction control +^^^^^^^^^^^^^^^^^^^^^^^^ + +The example demonstrates motor direction control. Pressing the screen button toggles the rotation direction. + +MicroPython Code Block: + + .. literalinclude:: ../../../examples/base/stepmotor/atoms3r_stepmotor_direction_control_example.py + :language: python + :linenos: + +Example output: + + None + +Rotate control +^^^^^^^^^^^^^^^^^^^^^^^^ + +The example demonstrates the motor continuously rotating for multiple turns, then reversing for multiple turns, and repeating the cycle after a 2-second pause. + +MicroPython Code Block: + + .. literalinclude:: ../../../examples/base/stepmotor/atoms3r_stepmotor_rotate_control_example.py + :language: python + :linenos: + +Example output: + + None + +**API** +-------------------------- + +AtomicStepmotorBase +^^^^^^^^^^^^^^^^^^^^^^^^ + +.. autoclass:: base.stepmotor.AtomicStepmotorBase + :members: diff --git a/docs/en/refs/base.stepmotor.ref b/docs/en/refs/base.stepmotor.ref new file mode 100644 index 00000000..0d0a8824 --- /dev/null +++ b/docs/en/refs/base.stepmotor.ref @@ -0,0 +1,36 @@ + +.. |Atomic Stepmotor Base| image:: https://static-cdn.m5stack.com/resource/docs/products/atom/Atomic%20Stepmotor%20Base/img-59dd9dee-7858-48d9-a134-2ddf4965616e.webp + :target: https://docs.m5stack.com/zh_CN/atom/Atomic%20Stepmotor%20Base + :height: 200px + :width: 200px + +.. |init.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/stepmotor/init.png +.. |enable.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/stepmotor/enable.png +.. |disable.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/stepmotor/disable.png +.. |set_direction.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/stepmotor/set_direction.png +.. |step.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/stepmotor/step.png +.. |rotate.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/stepmotor/rotate.png +.. |stop.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/stepmotor/stop.png +.. |get_status.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/stepmotor/get_status.png +.. |reset.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/stepmotor/reset.png +.. |get_voltage.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/stepmotor/get_voltage.png +.. |atoms3r_stepmotor_direction_control_example.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/stepmotor/direction_control_example.png +.. |atoms3r_stepmotor_rotate_control_example.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/stepmotor/rotate_control_example.png + +.. |atoms3r_stepmotor_direction_control_example.m5f2| raw:: html + + + atoms3r_stepmotor_direction_control_example.m5f2 + + +.. |atoms3r_stepmotor_rotate_control_example.m5f2| raw:: html + + + atoms3r_stepmotor_rotate_control_example.m5f2 + diff --git a/docs/locales/zh_CN/LC_MESSAGES/base/stepmotor.po b/docs/locales/zh_CN/LC_MESSAGES/base/stepmotor.po new file mode 100644 index 00000000..102d1b49 --- /dev/null +++ b/docs/locales/zh_CN/LC_MESSAGES/base/stepmotor.po @@ -0,0 +1,370 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2016 - 2025 M5Stack Technology Co., Ltd +# This file is distributed under the same license as the UIFlow2 Programming +# Guide package. +# FIRST AUTHOR , 2025. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: UIFlow2 Programming Guide \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-02-28 18:06+0800\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: zh_CN\n" +"Language-Team: zh_CN \n" +"Plural-Forms: nplurals=1; plural=0;\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.16.0\n" + +#: ../../en/base/stepmotor.rst:2 ../../en/refs/base.stepmotor.ref +#: 0760d96173f948af9f06a4ce25e8be9c 0e9f6255029e4d319da8238ec5b76707 +#, fuzzy +msgid "Atomic Stepmotor Base" +msgstr "创建一个 Stepmotor 对象。" + +#: ../../en/base/stepmotor.rst:9 f3d588e6c2994b4095ebb3b3ee122e91 +msgid "Support the following products:" +msgstr "支持以下产品:" + +#: ../../en/base/stepmotor.rst:11 1b19666dfcfb49e4b9ad38efe5f8a532 +msgid "|Atomic Stepmotor Base|" +msgstr "" + +#: ../../en/base/stepmotor.rst:15 c6988c41ec3b4204913eb821e09d519f +msgid "UiFlow2 Example:" +msgstr "UiFlow2 应用示例:" + +#: ../../en/base/stepmotor.rst:18 ../../en/base/stepmotor.rst:51 +#: f39989ba3f39496c984bcd711cfaf434 +msgid "Direction control" +msgstr "方向控制" + +#: ../../en/base/stepmotor.rst:20 8729b223faea46acb1d3abbb01535f4a +msgid "" +"Open the |atoms3r_stepmotor_direction_control_example.m5f2| project in " +"UiFlow2." +msgstr "在 UiFlow2 上打开 |atoms3r_stepmotor_direction_control_example.m5f2| 项目。" + +#: ../../en/base/stepmotor.rst:22 ../../en/base/stepmotor.rst:53 +#: 35002b2a22ab4c84a5806681613410dc +msgid "" +"The example demonstrates motor direction control. Pressing the screen " +"button toggles the rotation direction." +msgstr "示例程序演示电机旋转方向控制,按下屏幕按键,切换旋转方向" + +#: ../../en/base/stepmotor.rst:24 ../../en/base/stepmotor.rst:39 +#: 0d45dbc88a5d4dc9903c786af65f9016 base.stepmotor.AtomicStepmotorBase:9 +#: base.stepmotor.AtomicStepmotorBase.disable:3 +#: base.stepmotor.AtomicStepmotorBase.enable:3 +#: base.stepmotor.AtomicStepmotorBase.get_status:6 +#: base.stepmotor.AtomicStepmotorBase.get_voltage:6 +#: base.stepmotor.AtomicStepmotorBase.reset:3 +#: base.stepmotor.AtomicStepmotorBase.rotate:9 +#: base.stepmotor.AtomicStepmotorBase.set_direction:5 +#: base.stepmotor.AtomicStepmotorBase.step:3 +#: base.stepmotor.AtomicStepmotorBase.stop:3 of +msgid "UiFlow2 Code Block:" +msgstr "UiFlow2 代码块:" + +#: ../../en/base/stepmotor.rst:26 ab3e3763a95b41bb8f732dfab2d7d7cf +msgid "|atoms3r_stepmotor_direction_control_example.png|" +msgstr "" + +#: ../../en/refs/base.stepmotor.ref:17 02afcc4751664593bea876f6002344d5 +msgid "atoms3r_stepmotor_direction_control_example.png" +msgstr "" + +#: ../../en/base/stepmotor.rst:28 ../../en/base/stepmotor.rst:43 +#: ../../en/base/stepmotor.rst:61 ../../en/base/stepmotor.rst:76 +#: 53befa15fbba414fae94b4666799adcb +msgid "Example output:" +msgstr "示例输出:" + +#: ../../en/base/stepmotor.rst:30 ../../en/base/stepmotor.rst:45 +#: ../../en/base/stepmotor.rst:63 ../../en/base/stepmotor.rst:78 +#: 491e7d3d321f4a6b809ba99f08dc1228 +msgid "None" +msgstr "无" + +#: ../../en/base/stepmotor.rst:33 ../../en/base/stepmotor.rst:66 +#: 8f0e8a7ac03844998e1458cb5ce6fd8c +msgid "Rotate control" +msgstr "旋转控制" + +#: ../../en/base/stepmotor.rst:35 8729b223faea46acb1d3abbb01535f4a +msgid "" +"Open the |atoms3r_stepmotor_rotate_control_example.m5f2| project in " +"UiFlow2." +msgstr "在 UiFlow2 上打开 |atoms3r_stepmotor_rotate_control_example.m5f2| 项目。" + +#: ../../en/base/stepmotor.rst:37 ../../en/base/stepmotor.rst:68 +#: 9399b52d3a544f39b8cd492e7064d705 +msgid "" +"The example demonstrates the motor continuously rotating for multiple " +"turns, then reversing for multiple turns, and repeating the cycle after a" +" 2-second pause." +msgstr "示例程序演示电机连续旋转控制多圈,再反向旋转多圈,等待2秒后重复。 " + +#: ../../en/base/stepmotor.rst:41 7daa32ac857d49f59f8efc7f5cd558b5 +msgid "|atoms3r_stepmotor_rotate_control_example.png|" +msgstr "" + +#: ../../en/refs/base.stepmotor.ref:18 c8764e1d822f4e51b11420b4c9c3766f +msgid "atoms3r_stepmotor_rotate_control_example.png" +msgstr "" + +#: ../../en/base/stepmotor.rst:48 11a6b4dd714d489f909ca9e00fdd76f3 +msgid "MicroPython Example:" +msgstr "MicroPython 应用示例:" + +#: ../../en/base/stepmotor.rst:55 ../../en/base/stepmotor.rst:70 +#: 0663e2b02ab946d8bef40857721f7681 base.stepmotor.AtomicStepmotorBase:13 +#: base.stepmotor.AtomicStepmotorBase.disable:7 +#: base.stepmotor.AtomicStepmotorBase.enable:7 +#: base.stepmotor.AtomicStepmotorBase.get_status:10 +#: base.stepmotor.AtomicStepmotorBase.get_voltage:10 +#: base.stepmotor.AtomicStepmotorBase.reset:7 +#: base.stepmotor.AtomicStepmotorBase.rotate:13 +#: base.stepmotor.AtomicStepmotorBase.set_direction:9 +#: base.stepmotor.AtomicStepmotorBase.step:7 +#: base.stepmotor.AtomicStepmotorBase.stop:7 of +msgid "MicroPython Code Block:" +msgstr "MicroPython 代码块:" + +#: ../../en/base/stepmotor.rst:81 f1921bde992f45ca8f705b1865f4a90d +msgid "**API**" +msgstr "API应用" + +#: base.stepmotor.AtomicStepmotorBase:1 e6ddec277e8f4ffd8c27649b8850a489 of +msgid "AtomicStepmotorBase" +msgstr "" + +#: base.stepmotor.AtomicStepmotorBase:1 c8eb7e1c7c3e4962a34fda30400394f8 of +msgid "Bases: :py:class:`object`" +msgstr "" + +#: base.stepmotor.AtomicStepmotorBase:1 e6ddec277e8f4ffd8c27649b8850a489 of +msgid "Create an AtomicStepmotorBase object." +msgstr "创建一个 AtomicStepmotorBase 对象。" + +#: ../../en/base/stepmotor.rst 82d6c9722e9d4148bad28eb830a0d17f +#: base.stepmotor.AtomicStepmotorBase.rotate +#: base.stepmotor.AtomicStepmotorBase.set_direction of +msgid "Parameters" +msgstr "" + +#: 956e1f8c62c443f780658ca63d22cec7 base.stepmotor.AtomicStepmotorBase:3 of +msgid "Enable pin, used to enable or disable the stepper motor." +msgstr "使能引脚,用于启用或禁用步进电机。" + +#: base.stepmotor.AtomicStepmotorBase:4 d4944507ad5c4e52baec38d8b0c95e13 of +msgid "Direction pin, used to control the rotation direction of the motor." +msgstr "方向引脚,用于控制电机的旋转方向。" + +#: base.stepmotor.AtomicStepmotorBase:5 c99e9343f1124ad6b7d517d9ffd0c568 of +msgid "Step pin, used for step control of the motor." +msgstr "步进引脚,用于步进控制。" + +#: 695ed6f18cd4437d80116f3cabc35952 base.stepmotor.AtomicStepmotorBase:6 of +msgid "Fault pin, used to monitor the motor's fault status." +msgstr "故障引脚,用于监控电机故障状态。" + +#: 0494fe5d9b8447918e9d45366bea3e63 base.stepmotor.AtomicStepmotorBase:7 of +msgid "Reset pin, used to reset the motor driver." +msgstr "复位引脚,用于复位电机驱动器。" + +#: 02c1c594f76847a68adbcea45370ab35 base.stepmotor.AtomicStepmotorBase:11 of +msgid "|init.png|" +msgstr "" + +#: ../../en/refs/base.stepmotor.ref:7 8afc8f9b69e740079fb4bd041c2843a7 +msgid "init.png" +msgstr "" + +#: base.stepmotor.AtomicStepmotorBase.disable:1 +#: cd8ad572fec8430ea18fa56a0737fd5b of +msgid "Disable the stepper motor driver." +msgstr "禁用电机驱动。" + +#: 05117074a45e48d9b3dd29eeb9cc5c2c +#: base.stepmotor.AtomicStepmotorBase.disable:5 of +msgid "|disable.png|" +msgstr "" + +#: ../../en/refs/base.stepmotor.ref:9 630a952504c1413abaa4d700ec290e30 +msgid "disable.png" +msgstr "" + +#: 1554d9427c334840a92c8bd727f32dae base.stepmotor.AtomicStepmotorBase.enable:1 +#: of +msgid "Enable the stepper motor driver." +msgstr "启用电机驱动。" + +#: b03032626afb4e29911c8a24a32e1414 base.stepmotor.AtomicStepmotorBase.enable:5 +#: of +msgid "|enable.png|" +msgstr "" + +#: ../../en/refs/base.stepmotor.ref:8 a8730defa5904c3694a7ace61c021e39 +msgid "enable.png" +msgstr "" + +#: 719977b7033849e18d056f786715da18 base.stepmotor.AtomicStepmotorBase.enable +#: base.stepmotor.AtomicStepmotorBase.get_status +#: base.stepmotor.AtomicStepmotorBase.get_voltage +#: base.stepmotor.AtomicStepmotorBase.reset +#: base.stepmotor.AtomicStepmotorBase.rotate +#: base.stepmotor.AtomicStepmotorBase.set_direction +#: base.stepmotor.AtomicStepmotorBase.step +#: base.stepmotor.AtomicStepmotorBase.stop of +msgid "Return type" +msgstr "" + +#: 814b3f08d42744c5886200fa941e0668 +#: base.stepmotor.AtomicStepmotorBase.get_status:1 of +msgid "Get motor driver status." +msgstr "获取电机驱动状态" + +#: 41e39715d3804e7094ed383720dfa8ec +#: base.stepmotor.AtomicStepmotorBase.get_status +#: base.stepmotor.AtomicStepmotorBase.get_voltage of +msgid "Returns" +msgstr "" + +#: 08b9d473d1904987928596fd29d45194 +#: base.stepmotor.AtomicStepmotorBase.get_status:3 of +msgid "" +"Returns True if the driver is operating normally, or False if a fault is " +"detected." +msgstr "正常工作时返回 True,故障时返回 False。 " + +#: 5c86fca2044c43ada204d1bc191fc411 +#: base.stepmotor.AtomicStepmotorBase.get_status:8 of +msgid "|get_status.png|" +msgstr "" + +#: ../../en/refs/base.stepmotor.ref:14 359adc03d5574bb5bea82ce964c7d2e2 +msgid "get_status.png" +msgstr "" + +#: 09121e271dbe449495a6f2585b6251c4 +#: base.stepmotor.AtomicStepmotorBase.get_voltage:1 of +msgid "Get voltage." +msgstr "获取电压" + +#: 5fe1c99937bd46ea9f62a95cff9b9601 +#: base.stepmotor.AtomicStepmotorBase.get_voltage:3 of +msgid "The driver input voltage. unit: V" +msgstr "驱动输入电压,单位:V。" + +#: 3b15a2eecbd94724aa78ecbd8cda47e6 +#: base.stepmotor.AtomicStepmotorBase.get_voltage:8 of +msgid "|get_voltage.png|" +msgstr "" + +#: ../../en/refs/base.stepmotor.ref:16 caee51cc65bc42c9954af7945167ca29 +msgid "get_voltage.png" +msgstr "" + +#: b9cc0761da5843e39781e569e48af880 base.stepmotor.AtomicStepmotorBase.reset:1 +#: of +msgid "Reset the stepper motor driver." +msgstr "复位电机驱动。" + +#: base.stepmotor.AtomicStepmotorBase.reset:5 df229ad0ad8c428880d5b6c9d02b9727 +#: of +msgid "|reset.png|" +msgstr "" + +#: ../../en/refs/base.stepmotor.ref:15 d513ba7d45a64ce280d85749c4d12fb7 +msgid "reset.png" +msgstr "" + +#: 6c18afbe4f2c42d4bd4b4ab91c228f20 base.stepmotor.AtomicStepmotorBase.rotate:1 +#: of +msgid "Rotate the stepper motor for a specified number of steps." +msgstr "旋转步进电机一定的步数。" + +#: 6bf481d425e64c20875c16095a16b98e base.stepmotor.AtomicStepmotorBase.rotate:3 +#: of +msgid "Number of steps to rotate." +msgstr "转动步数。" + +#: base.stepmotor.AtomicStepmotorBase.rotate:4 bdf00014e91b4d0591917c99d4b60aa9 +#: of +msgid "Delay between steps (in milliseconds), default is 0ms." +msgstr "步间延时(ms),默认为 0 ms。" + +#: 46cb58fc0f5d488fbc281414428a5210 base.stepmotor.AtomicStepmotorBase.rotate:5 +#: of +msgid "Rotation direction (True or False)." +msgstr "旋转方向" + +#: 5d8512f584284d6dad2505ed96016f69 base.stepmotor.AtomicStepmotorBase.rotate:7 +#: of +msgid "" +"The actual rotation direction (clockwise or counterclockwise) depends on " +"the motor wiring." +msgstr "实际旋转方向(顺时针或逆时针)取决于电机的接线方式。" + +#: a76b1da5a85f4d25ba780a70784a5acf +#: base.stepmotor.AtomicStepmotorBase.rotate:11 of +msgid "|rotate.png|" +msgstr "" + +#: ../../en/refs/base.stepmotor.ref:12 68f2b93a75f048fe92075cfbab18c9ad +msgid "rotate.png" +msgstr "" + +#: 14d6bcbed2634f479005974785a4d60a +#: base.stepmotor.AtomicStepmotorBase.set_direction:1 of +msgid "Set direction." +msgstr "设置方向。" + +#: base.stepmotor.AtomicStepmotorBase.set_direction:3 +#: c1e0f16f17b54e41845e4e90c2272bc9 of +msgid "Rotation direction. True or False." +msgstr "旋转方向 True 或 False。" + +#: 854d2de00ee346239c8d3719f1a4a7fe +#: base.stepmotor.AtomicStepmotorBase.set_direction:7 of +msgid "|set_direction.png|" +msgstr "" + +#: ../../en/refs/base.stepmotor.ref:10 26b7682884864ad5b0b27bf5f90a987c +msgid "set_direction.png" +msgstr "" + +#: base.stepmotor.AtomicStepmotorBase.step:1 fbf2512e755a46c8b481237424ab7713 +#: of +msgid "Move the stepper motor one step." +msgstr "电机步进一步" + +#: b6c229bacdf044d98359e138473abb14 base.stepmotor.AtomicStepmotorBase.step:5 +#: of +msgid "|step.png|" +msgstr "" + +#: ../../en/refs/base.stepmotor.ref:11 55c2574f2716453f921292e2f1e7c655 +msgid "step.png" +msgstr "" + +#: base.stepmotor.AtomicStepmotorBase.stop:1 e50a34281aa34d669b43780891c95491 +#: of +msgid "Stop motor." +msgstr "停止电机。" + +#: 5a91a575bd9149638caa25c07e1e68e0 base.stepmotor.AtomicStepmotorBase.stop:5 +#: of +msgid "|stop.png|" +msgstr "" + +#: ../../en/refs/base.stepmotor.ref:13 cd1c5470354c49688ea2884d928161cb +msgid "stop.png" +msgstr "" + + \ No newline at end of file diff --git a/examples/base/stepmotor/atoms3r_stepmotor_direction_control_example.m5f2 b/examples/base/stepmotor/atoms3r_stepmotor_direction_control_example.m5f2 new file mode 100644 index 00000000..3bc1f8bb --- /dev/null +++ b/examples/base/stepmotor/atoms3r_stepmotor_direction_control_example.m5f2 @@ -0,0 +1 @@ +{"version":"V2.0","versionNumber":"V2.2.2","type":"atoms3r","components":[{"name":"screen","type":"screen","layer":0,"screenId":"builtin","screenName":"","id":"__atoms3r_screen","createTime":1740735576486,"x":0,"y":0,"width":128,"height":128,"backgroundColor":"#000000","size":0,"isSelected":true},{"name":"title0","type":"title","layer":1,"screenId":"builtin","screenName":"","id":"j!zIttWWxtgzlq`x","createTime":1740736169832,"x":0,"y":0,"color":"#ffffff","backgroundColor":"#0000FF","text":"Steps Ctrl","textOffset":3,"font":"Widgets.FONTS.DejaVu18","isSelected":false},{"name":"label0","type":"label","layer":2,"screenId":"builtin","screenName":"","id":"nLEPjZXSscSdg*RF","createTime":1740736186436,"x":5,"y":35,"color":"#ffffff","backgroundColor":"#000000","text":"vol:","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false,"width":34,"height":21},{"name":"label_vol","type":"label","layer":3,"screenId":"builtin","screenName":"","id":"hkttkqz!C#ErJJKv","createTime":1740736192580,"x":43,"y":35,"color":"#ffffff","backgroundColor":"#000000","text":"12.0V","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false,"width":83,"height":21}],"resources":[{"hardware":["hardware_button","hardware_pin_button","imu","ir"]},{"base":["base_stepmotor"]}],"units":[],"hats":[],"bases":[{"type":"base_stepmotor","name":"base_stepmotor","id":"g^j+lkCs+^ClCb5Y","createTime":1740735581899,"initBlockType":"base_stepmotor_init","initBlockId":"9A|M.A3U1za_:=:aKv:["}],"i2cs":[],"blockly":"directiontrue5763839label_volLabelhello M5VdirectionTRUEBtnAWAS_CLICKEDdirectiondirectionFalsedirectiontrue1","screen":[{"simulationName":"Built-in","type":"builtin","width":128,"height":128,"scale":1.3,"screenName":"","blockId":"","screenColorType":0,"id":"builtin","createTime":1740735576485}],"logicWhenNum":0,"customList":[]} \ No newline at end of file diff --git a/examples/base/stepmotor/atoms3r_stepmotor_direction_control_example.py b/examples/base/stepmotor/atoms3r_stepmotor_direction_control_example.py new file mode 100644 index 00000000..98f15f3d --- /dev/null +++ b/examples/base/stepmotor/atoms3r_stepmotor_direction_control_example.py @@ -0,0 +1,55 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import os, sys, io +import M5 +from M5 import * +from base import AtomicStepmotorBase +import time + + +title0 = None +label0 = None +label_vol = None +base_stepmotor = None +direction = None + + +def btn_was_clicked_event(state): + global title0, label0, label_vol, base_stepmotor, direction + direction = not direction + base_stepmotor.set_direction(direction) + + +def setup(): + global title0, label0, label_vol, base_stepmotor, direction + M5.begin() + title0 = Widgets.Title("Steps Ctrl", 3, 0xFFFFFF, 0x0000FF, Widgets.FONTS.DejaVu18) + label0 = Widgets.Label("vol:", 5, 35, 1.0, 0xFFFFFF, 0x000000, Widgets.FONTS.DejaVu18) + label_vol = Widgets.Label("12.0V", 43, 35, 1.0, 0xFFFFFF, 0x000000, Widgets.FONTS.DejaVu18) + BtnA.setCallback(type=BtnA.CB_TYPE.WAS_CLICKED, cb=btn_was_clicked_event) + base_stepmotor = AtomicStepmotorBase(5, 7, 6, 38, 39) + label_vol.setText(str((str((base_stepmotor.get_voltage())) + str("V")))) + direction = True + + +def loop(): + global title0, label0, label_vol, base_stepmotor, direction + M5.update() + base_stepmotor.step() + time.sleep_ms(1) + + +if __name__ == "__main__": + try: + setup() + while True: + loop() + except (Exception, KeyboardInterrupt) as e: + try: + from utility import print_error_msg + + print_error_msg(e) + except ImportError: + print("please update to latest firmware") diff --git a/examples/base/stepmotor/atoms3r_stepmotor_rotate_control_example.m5f2 b/examples/base/stepmotor/atoms3r_stepmotor_rotate_control_example.m5f2 new file mode 100644 index 00000000..d3f69f3e --- /dev/null +++ b/examples/base/stepmotor/atoms3r_stepmotor_rotate_control_example.m5f2 @@ -0,0 +1 @@ +{"version":"V2.0","versionNumber":"V2.2.2","type":"atoms3r","components":[{"name":"screen","type":"screen","layer":0,"screenId":"builtin","screenName":"","id":"__atoms3r_screen","createTime":1740735576486,"x":0,"y":0,"width":128,"height":128,"backgroundColor":"#000000","size":0,"isSelected":true},{"name":"title0","type":"title","layer":1,"screenId":"builtin","screenName":"","id":"j!zIttWWxtgzlq`x","createTime":1740736169832,"x":0,"y":0,"color":"#ffffff","backgroundColor":"#0000FF","text":"Steps Ctrl","textOffset":3,"font":"Widgets.FONTS.DejaVu18","isSelected":false},{"name":"label0","type":"label","layer":2,"screenId":"builtin","screenName":"","id":"nLEPjZXSscSdg*RF","createTime":1740736186436,"x":5,"y":35,"color":"#ffffff","backgroundColor":"#000000","text":"vol:","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false,"width":34,"height":21},{"name":"label_vol","type":"label","layer":3,"screenId":"builtin","screenName":"","id":"hkttkqz!C#ErJJKv","createTime":1740736192580,"x":43,"y":35,"color":"#ffffff","backgroundColor":"#000000","text":"12.0V","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false,"width":83,"height":21}],"resources":[{"hardware":["hardware_button","hardware_pin_button","imu","ir"]},{"base":["base_stepmotor"]}],"units":[],"hats":[],"bases":[{"type":"base_stepmotor","name":"base_stepmotor","id":"g^j+lkCs+^ClCb5Y","createTime":1740735581899,"initBlockType":"base_stepmotor_init","initBlockId":"9A|M.A3U1za_:=:aKv:["}],"i2cs":[],"blockly":"step_per_revmicrosteptotal_stepsrotate_circletrue5763839step_per_rev200microstepDIVIDE12rotate_circle5total_stepsMULTIPLY1DIVIDE1step_per_rev1microstep1rotate_circlelabel_volLabelhello M5Vtruehello M50total_steps1True1000total_steps1False100label_volLabelhello M5V2000","screen":[{"simulationName":"Built-in","type":"builtin","width":128,"height":128,"scale":1.3,"screenName":"","blockId":"","screenColorType":0,"id":"builtin","createTime":1740735576485}],"logicWhenNum":0,"customList":[]} \ No newline at end of file diff --git a/examples/base/stepmotor/atoms3r_stepmotor_rotate_control_example.py b/examples/base/stepmotor/atoms3r_stepmotor_rotate_control_example.py new file mode 100644 index 00000000..4642f345 --- /dev/null +++ b/examples/base/stepmotor/atoms3r_stepmotor_rotate_control_example.py @@ -0,0 +1,76 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import os, sys, io +import M5 +from M5 import * +from base import AtomicStepmotorBase +import time + + +title0 = None +label0 = None +label_vol = None +base_stepmotor = None +step_per_rev = None +microstep = None +total_steps = None +rotate_circle = None + + +def setup(): + global \ + title0, \ + label0, \ + label_vol, \ + base_stepmotor, \ + step_per_rev, \ + microstep, \ + total_steps, \ + rotate_circle + + M5.begin() + title0 = Widgets.Title("Steps Ctrl", 3, 0xFFFFFF, 0x0000FF, Widgets.FONTS.DejaVu18) + label0 = Widgets.Label("vol:", 5, 35, 1.0, 0xFFFFFF, 0x000000, Widgets.FONTS.DejaVu18) + label_vol = Widgets.Label("12.0V", 43, 35, 1.0, 0xFFFFFF, 0x000000, Widgets.FONTS.DejaVu18) + base_stepmotor = AtomicStepmotorBase(5, 7, 6, 38, 39) + step_per_rev = 200 + microstep = 1 / 2 + rotate_circle = 5 + total_steps = (step_per_rev / microstep) * rotate_circle + label_vol.setText(str((str((base_stepmotor.get_voltage())) + str("V")))) + + +def loop(): + global \ + title0, \ + label0, \ + label_vol, \ + base_stepmotor, \ + step_per_rev, \ + microstep, \ + total_steps, \ + rotate_circle + M5.update() + print(base_stepmotor.get_voltage()) + base_stepmotor.rotate(total_steps, 1, True) + time.sleep_ms(100) + base_stepmotor.rotate(total_steps, 1, False) + time.sleep_ms(100) + label_vol.setText(str((str((base_stepmotor.get_voltage())) + str("V")))) + time.sleep_ms(2000) + + +if __name__ == "__main__": + try: + setup() + while True: + loop() + except (Exception, KeyboardInterrupt) as e: + try: + from utility import print_error_msg + + print_error_msg(e) + except ImportError: + print("please update to latest firmware") diff --git a/m5stack/libs/base/__init__.py b/m5stack/libs/base/__init__.py index a73f8c38..1b698c78 100644 --- a/m5stack/libs/base/__init__.py +++ b/m5stack/libs/base/__init__.py @@ -15,6 +15,7 @@ "AtomRS232": "rs232", "AtomRS485": "rs232", "SpeakerBase": "speaker", + "AtomicStepmotorBase": "stepmotor", } diff --git a/m5stack/libs/base/manifest.py b/m5stack/libs/base/manifest.py index e93d1127..90e861f0 100644 --- a/m5stack/libs/base/manifest.py +++ b/m5stack/libs/base/manifest.py @@ -15,6 +15,7 @@ "pwm.py", "rs232.py", "speaker.py", + "stepmotor.py", ), base_path="..", opt=3, diff --git a/m5stack/libs/base/stepmotor.py b/m5stack/libs/base/stepmotor.py new file mode 100644 index 00000000..8a7fe8bd --- /dev/null +++ b/m5stack/libs/base/stepmotor.py @@ -0,0 +1,197 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import time +import machine + + +class AtomicStepmotorBase: + """Create an AtomicStepmotorBase object. + + :param int en: Enable pin, used to enable or disable the stepper motor. + :param int dir: Direction pin, used to control the rotation direction of the motor. + :param int stp: Step pin, used for step control of the motor. + :param int flt: Fault pin, used to monitor the motor's fault status. + :param int rst: Reset pin, used to reset the motor driver. + + UiFlow2 Code Block: + + |init.png| + + MicroPython Code Block: + + .. code-block:: python + + from base import AtomicStepmotorBase + + base_stepmotor = AtomicStepmotorBase(en=5, dir=7, stp=6, flt=38, rst=39) + """ + + def __init__(self, en: int = 5, dir: int = 7, stp: int = 6, flt: int = 38, rst: int = 39): + self.en_pin = machine.Pin(en, machine.Pin.OUT) + self.dir_pin = machine.Pin(dir, machine.Pin.OUT) + self.stp_pin = machine.Pin(stp, machine.Pin.OUT) + self.flt_pin = machine.Pin(flt, machine.Pin.IN) if flt else None + self.rst_pin = machine.Pin(rst, machine.Pin.OUT) if rst else None + self.adc = machine.ADC(machine.Pin(8), atten=machine.ADC.ATTN_11DB) + self.enable() + + def enable(self) -> None: + """Enable the stepper motor driver. + + UiFlow2 Code Block: + + |enable.png| + + MicroPython Code Block: + + .. code-block:: python + + base_stepmotor.enable() + """ + self.en_pin.value(0) + + def disable(self): + """Disable the stepper motor driver. + + UiFlow2 Code Block: + + |disable.png| + + MicroPython Code Block: + + .. code-block:: python + + base_stepmotor.disable() + """ + self.en_pin.value(1) + + def set_direction(self, direction: bool = True) -> None: + """Set direction. + + :param bool direction: Rotation direction. True or False. + + UiFlow2 Code Block: + + |set_direction.png| + + MicroPython Code Block: + + .. code-block:: python + + base_stepmotor.set_direction(direction) + """ + self.dir_pin.value(1 if direction else 0) + + def step(self) -> None: + """Move the stepper motor one step. + + UiFlow2 Code Block: + + |step.png| + + MicroPython Code Block: + + .. code-block:: python + + base_stepmotor.step() + """ + self.stp_pin.value(1) + time.sleep_us(500) + self.stp_pin.value(0) + time.sleep_us(500) + + def rotate(self, steps: int, delay_ms: int = 0, direction: bool = True) -> None: + """Rotate the stepper motor for a specified number of steps. + + :param int steps: Number of steps to rotate. + :param int delay_ms: Delay between steps (in milliseconds), default is 0ms. + :param bool direction: Rotation direction (True or False). + + The actual rotation direction (clockwise or counterclockwise) depends on the motor wiring. + + UiFlow2 Code Block: + + |rotate.png| + + MicroPython Code Block: + + .. code-block:: python + + base_stepmotor.rotate(steps, delay_ms, direction) + """ + self.set_direction(direction) + for _ in range(steps): + self.step() + if delay_ms > 0: + time.sleep_ms(delay_ms) + + def stop(self) -> None: + """Stop motor. + + UiFlow2 Code Block: + + |stop.png| + + MicroPython Code Block: + + .. code-block:: python + + base_stepmotor.stop() + """ + self.stp_pin.value(0) + + def get_status(self) -> bool: + """Get motor driver status. + + :returns: Returns True if the driver is operating normally, or False if a fault is detected. + :rtype: bool + + UiFlow2 Code Block: + + |get_status.png| + + MicroPython Code Block: + + .. code-block:: python + + base_stepmotor.get_status() + """ + return (self.flt_pin.value() != 0) if self.flt_pin else True + + def reset(self) -> None: + """Reset the stepper motor driver. + + UiFlow2 Code Block: + + |reset.png| + + MicroPython Code Block: + + .. code-block:: python + + base_stepmotor.reset() + """ + if self.rst_pin: + self.rst_pin.value(0) + time.sleep_ms(10) + self.rst_pin.value(1) + + def get_voltage(self) -> float: + """Get voltage. + + :returns: The driver input voltage. unit: V + :rtype: float + + UiFlow2 Code Block: + + |get_voltage.png| + + MicroPython Code Block: + + .. code-block:: python + + base_stepmotor.get_voltage() + """ + return self.adc.read_uv() * (6 / 1_000_000.0) # x6 是因为输入为 VIN 的 1/ 6 From d257b0edbc6b09e6a4c9f6552fe47fddbd574651 Mon Sep 17 00:00:00 2001 From: lbuque Date: Thu, 13 Feb 2025 15:29:28 +0800 Subject: [PATCH 009/322] boards: Add StampPLC support. Signed-off-by: lbuque --- docs/en/controllers/index.rst | 1 + docs/en/controllers/stampplc.rst | 96 +++ m5stack/CMakeListsDefault.cmake | 1 + m5stack/Makefile | 6 +- m5stack/boards/M5STACK_StampPLC/board.json | 18 + m5stack/boards/M5STACK_StampPLC/manifest.py | 5 + .../M5STACK_StampPLC/mpconfigboard.cmake | 40 + .../boards/M5STACK_StampPLC/mpconfigboard.h | 21 + .../boards/M5STACK_StampPLC/sdkconfig.board | 30 + m5stack/cmodules/m5unified/m5unified.c | 1 + m5stack/components/M5Unified/M5GFX | 2 +- m5stack/components/M5Unified/M5Unified | 2 +- m5stack/fs/system/stampplc/applist.jpeg | Bin 0 -> 3718 bytes m5stack/fs/system/stampplc/apprun.jpeg | Bin 0 -> 3879 bytes .../stampplc/apprun/run_always_select.jpeg | Bin 0 -> 2747 bytes .../stampplc/apprun/run_always_unselect.jpeg | Bin 0 -> 1582 bytes .../fs/system/stampplc/apprun/run_info.jpeg | Bin 0 -> 753 bytes .../stampplc/apprun/run_once_select.jpeg | Bin 0 -> 2446 bytes .../stampplc/apprun/run_once_unselect.jpeg | Bin 0 -> 1433 bytes .../stampplc/common/card_228x32_select.jpeg | Bin 0 -> 2321 bytes .../stampplc/common/card_228x32_unselect.jpeg | Bin 0 -> 508 bytes m5stack/fs/system/stampplc/develop.jpeg | Bin 0 -> 4002 bytes .../fs/system/stampplc/develop/private.jpeg | Bin 0 -> 16897 bytes .../fs/system/stampplc/develop/public.jpeg | Bin 0 -> 15756 bytes m5stack/fs/system/stampplc/ezdata.jpeg | Bin 0 -> 3329 bytes m5stack/fs/system/stampplc/ico/a.jpeg | Bin 0 -> 1344 bytes m5stack/fs/system/stampplc/ico/b.jpeg | Bin 0 -> 1357 bytes m5stack/fs/system/stampplc/ico/c.jpeg | Bin 0 -> 1259 bytes m5stack/fs/system/stampplc/ico/d.jpeg | Bin 0 -> 1167 bytes m5stack/fs/system/stampplc/ico/e.jpeg | Bin 0 -> 1287 bytes m5stack/fs/system/stampplc/ico/f.jpeg | Bin 0 -> 1279 bytes m5stack/fs/system/stampplc/ico/g.jpeg | Bin 0 -> 1266 bytes m5stack/fs/system/stampplc/ico/h.jpeg | Bin 0 -> 1166 bytes m5stack/fs/system/stampplc/ico/i.jpeg | Bin 0 -> 1119 bytes m5stack/fs/system/stampplc/ico/j.jpeg | Bin 0 -> 1286 bytes m5stack/fs/system/stampplc/ico/k.jpeg | Bin 0 -> 1236 bytes m5stack/fs/system/stampplc/ico/l.jpeg | Bin 0 -> 1054 bytes m5stack/fs/system/stampplc/ico/m.jpeg | Bin 0 -> 1341 bytes m5stack/fs/system/stampplc/ico/n.jpeg | Bin 0 -> 1369 bytes m5stack/fs/system/stampplc/ico/o.jpeg | Bin 0 -> 1253 bytes m5stack/fs/system/stampplc/ico/p.jpeg | Bin 0 -> 1121 bytes m5stack/fs/system/stampplc/ico/q.jpeg | Bin 0 -> 1365 bytes m5stack/fs/system/stampplc/ico/r.jpeg | Bin 0 -> 1373 bytes m5stack/fs/system/stampplc/ico/s.jpeg | Bin 0 -> 1244 bytes m5stack/fs/system/stampplc/ico/t.jpeg | Bin 0 -> 1108 bytes m5stack/fs/system/stampplc/ico/u.jpeg | Bin 0 -> 1333 bytes m5stack/fs/system/stampplc/ico/v.jpeg | Bin 0 -> 1333 bytes m5stack/fs/system/stampplc/ico/w.jpeg | Bin 0 -> 1295 bytes m5stack/fs/system/stampplc/ico/x.jpeg | Bin 0 -> 1185 bytes m5stack/fs/system/stampplc/ico/y.jpeg | Bin 0 -> 1246 bytes m5stack/fs/system/stampplc/ico/z.jpeg | Bin 0 -> 1348 bytes m5stack/fs/system/stampplc/left.jpeg | Bin 0 -> 733 bytes m5stack/fs/system/stampplc/right.jpeg | Bin 0 -> 685 bytes m5stack/fs/system/stampplc/setting.jpeg | Bin 0 -> 4074 bytes .../system/stampplc/setting/caret_right.jpeg | Bin 0 -> 584 bytes .../fs/system/stampplc/setting/general.jpeg | Bin 0 -> 1112 bytes .../stampplc/setting/general/disable.jpeg | Bin 0 -> 546 bytes .../stampplc/setting/general/enable.jpeg | Bin 0 -> 839 bytes m5stack/fs/system/stampplc/setting/wlan.jpeg | Bin 0 -> 1423 bytes .../stampplc/setting/wlan/input_default.jpeg | Bin 0 -> 6673 bytes .../stampplc/setting/wlan/input_psk.jpeg | Bin 0 -> 6725 bytes .../stampplc/setting/wlan/input_server.jpeg | Bin 0 -> 6546 bytes .../stampplc/setting/wlan/input_ssid.jpeg | Bin 0 -> 6751 bytes .../stampplc/setting/wlan/submit_select.jpeg | Bin 0 -> 2367 bytes .../setting/wlan/submit_unselect.jpeg | Bin 0 -> 1263 bytes .../stampplc/statusbar/battery/black.jpeg | Bin 0 -> 1108 bytes .../statusbar/battery/black_charge.jpeg | Bin 0 -> 1240 bytes .../stampplc/statusbar/battery/green.jpeg | Bin 0 -> 932 bytes .../statusbar/battery/green_charge.jpeg | Bin 0 -> 1044 bytes .../stampplc/statusbar/battery/red.jpeg | Bin 0 -> 945 bytes .../statusbar/battery/red_charge.jpeg | Bin 0 -> 1051 bytes .../stampplc/statusbar/cloud/empty.jpeg | Bin 0 -> 621 bytes .../stampplc/statusbar/cloud/error.jpeg | Bin 0 -> 585 bytes .../stampplc/statusbar/cloud/green.jpeg | Bin 0 -> 640 bytes .../system/stampplc/statusbar/title_blue.jpeg | Bin 0 -> 1956 bytes .../stampplc/statusbar/wifi/disconnected.jpeg | Bin 0 -> 582 bytes .../system/stampplc/statusbar/wifi/empty.jpeg | Bin 0 -> 620 bytes .../system/stampplc/statusbar/wifi/good.jpeg | Bin 0 -> 583 bytes .../system/stampplc/statusbar/wifi/mid.jpeg | Bin 0 -> 572 bytes .../system/stampplc/statusbar/wifi/worse.jpeg | Bin 0 -> 547 bytes m5stack/modules/startup/__init__.py | 6 + m5stack/modules/startup/manifest_stampplc.py | 23 + m5stack/modules/startup/stampplc/__init__.py | 46 ++ m5stack/modules/startup/stampplc/app_base.py | 93 +++ .../modules/startup/stampplc/apps/app_list.py | 297 +++++++ .../modules/startup/stampplc/apps/app_run.py | 176 +++++ m5stack/modules/startup/stampplc/apps/dev.py | 189 +++++ .../modules/startup/stampplc/apps/ezdata.py | 40 + .../modules/startup/stampplc/apps/launcher.py | 164 ++++ .../modules/startup/stampplc/apps/settings.py | 740 ++++++++++++++++++ .../startup/stampplc/apps/statusbar.py | 199 +++++ m5stack/modules/startup/stampplc/framework.py | 134 ++++ m5stack/modules/startup/stampplc/res.py | 74 ++ 93 files changed, 2400 insertions(+), 4 deletions(-) create mode 100644 docs/en/controllers/stampplc.rst create mode 100644 m5stack/boards/M5STACK_StampPLC/board.json create mode 100644 m5stack/boards/M5STACK_StampPLC/manifest.py create mode 100644 m5stack/boards/M5STACK_StampPLC/mpconfigboard.cmake create mode 100644 m5stack/boards/M5STACK_StampPLC/mpconfigboard.h create mode 100644 m5stack/boards/M5STACK_StampPLC/sdkconfig.board create mode 100644 m5stack/fs/system/stampplc/applist.jpeg create mode 100644 m5stack/fs/system/stampplc/apprun.jpeg create mode 100644 m5stack/fs/system/stampplc/apprun/run_always_select.jpeg create mode 100644 m5stack/fs/system/stampplc/apprun/run_always_unselect.jpeg create mode 100644 m5stack/fs/system/stampplc/apprun/run_info.jpeg create mode 100644 m5stack/fs/system/stampplc/apprun/run_once_select.jpeg create mode 100644 m5stack/fs/system/stampplc/apprun/run_once_unselect.jpeg create mode 100644 m5stack/fs/system/stampplc/common/card_228x32_select.jpeg create mode 100644 m5stack/fs/system/stampplc/common/card_228x32_unselect.jpeg create mode 100644 m5stack/fs/system/stampplc/develop.jpeg create mode 100644 m5stack/fs/system/stampplc/develop/private.jpeg create mode 100644 m5stack/fs/system/stampplc/develop/public.jpeg create mode 100644 m5stack/fs/system/stampplc/ezdata.jpeg create mode 100644 m5stack/fs/system/stampplc/ico/a.jpeg create mode 100644 m5stack/fs/system/stampplc/ico/b.jpeg create mode 100644 m5stack/fs/system/stampplc/ico/c.jpeg create mode 100644 m5stack/fs/system/stampplc/ico/d.jpeg create mode 100644 m5stack/fs/system/stampplc/ico/e.jpeg create mode 100644 m5stack/fs/system/stampplc/ico/f.jpeg create mode 100644 m5stack/fs/system/stampplc/ico/g.jpeg create mode 100644 m5stack/fs/system/stampplc/ico/h.jpeg create mode 100644 m5stack/fs/system/stampplc/ico/i.jpeg create mode 100644 m5stack/fs/system/stampplc/ico/j.jpeg create mode 100644 m5stack/fs/system/stampplc/ico/k.jpeg create mode 100644 m5stack/fs/system/stampplc/ico/l.jpeg create mode 100644 m5stack/fs/system/stampplc/ico/m.jpeg create mode 100644 m5stack/fs/system/stampplc/ico/n.jpeg create mode 100644 m5stack/fs/system/stampplc/ico/o.jpeg create mode 100644 m5stack/fs/system/stampplc/ico/p.jpeg create mode 100644 m5stack/fs/system/stampplc/ico/q.jpeg create mode 100644 m5stack/fs/system/stampplc/ico/r.jpeg create mode 100644 m5stack/fs/system/stampplc/ico/s.jpeg create mode 100644 m5stack/fs/system/stampplc/ico/t.jpeg create mode 100644 m5stack/fs/system/stampplc/ico/u.jpeg create mode 100644 m5stack/fs/system/stampplc/ico/v.jpeg create mode 100644 m5stack/fs/system/stampplc/ico/w.jpeg create mode 100644 m5stack/fs/system/stampplc/ico/x.jpeg create mode 100644 m5stack/fs/system/stampplc/ico/y.jpeg create mode 100644 m5stack/fs/system/stampplc/ico/z.jpeg create mode 100644 m5stack/fs/system/stampplc/left.jpeg create mode 100644 m5stack/fs/system/stampplc/right.jpeg create mode 100644 m5stack/fs/system/stampplc/setting.jpeg create mode 100644 m5stack/fs/system/stampplc/setting/caret_right.jpeg create mode 100644 m5stack/fs/system/stampplc/setting/general.jpeg create mode 100644 m5stack/fs/system/stampplc/setting/general/disable.jpeg create mode 100644 m5stack/fs/system/stampplc/setting/general/enable.jpeg create mode 100644 m5stack/fs/system/stampplc/setting/wlan.jpeg create mode 100644 m5stack/fs/system/stampplc/setting/wlan/input_default.jpeg create mode 100644 m5stack/fs/system/stampplc/setting/wlan/input_psk.jpeg create mode 100644 m5stack/fs/system/stampplc/setting/wlan/input_server.jpeg create mode 100644 m5stack/fs/system/stampplc/setting/wlan/input_ssid.jpeg create mode 100644 m5stack/fs/system/stampplc/setting/wlan/submit_select.jpeg create mode 100644 m5stack/fs/system/stampplc/setting/wlan/submit_unselect.jpeg create mode 100644 m5stack/fs/system/stampplc/statusbar/battery/black.jpeg create mode 100644 m5stack/fs/system/stampplc/statusbar/battery/black_charge.jpeg create mode 100644 m5stack/fs/system/stampplc/statusbar/battery/green.jpeg create mode 100644 m5stack/fs/system/stampplc/statusbar/battery/green_charge.jpeg create mode 100644 m5stack/fs/system/stampplc/statusbar/battery/red.jpeg create mode 100644 m5stack/fs/system/stampplc/statusbar/battery/red_charge.jpeg create mode 100644 m5stack/fs/system/stampplc/statusbar/cloud/empty.jpeg create mode 100644 m5stack/fs/system/stampplc/statusbar/cloud/error.jpeg create mode 100644 m5stack/fs/system/stampplc/statusbar/cloud/green.jpeg create mode 100644 m5stack/fs/system/stampplc/statusbar/title_blue.jpeg create mode 100644 m5stack/fs/system/stampplc/statusbar/wifi/disconnected.jpeg create mode 100644 m5stack/fs/system/stampplc/statusbar/wifi/empty.jpeg create mode 100644 m5stack/fs/system/stampplc/statusbar/wifi/good.jpeg create mode 100644 m5stack/fs/system/stampplc/statusbar/wifi/mid.jpeg create mode 100644 m5stack/fs/system/stampplc/statusbar/wifi/worse.jpeg create mode 100644 m5stack/modules/startup/manifest_stampplc.py create mode 100644 m5stack/modules/startup/stampplc/__init__.py create mode 100644 m5stack/modules/startup/stampplc/app_base.py create mode 100644 m5stack/modules/startup/stampplc/apps/app_list.py create mode 100644 m5stack/modules/startup/stampplc/apps/app_run.py create mode 100644 m5stack/modules/startup/stampplc/apps/dev.py create mode 100644 m5stack/modules/startup/stampplc/apps/ezdata.py create mode 100644 m5stack/modules/startup/stampplc/apps/launcher.py create mode 100644 m5stack/modules/startup/stampplc/apps/settings.py create mode 100644 m5stack/modules/startup/stampplc/apps/statusbar.py create mode 100644 m5stack/modules/startup/stampplc/framework.py create mode 100644 m5stack/modules/startup/stampplc/res.py diff --git a/docs/en/controllers/index.rst b/docs/en/controllers/index.rst index cf98ea67..cb009e2d 100644 --- a/docs/en/controllers/index.rst +++ b/docs/en/controllers/index.rst @@ -14,3 +14,4 @@ Controllers airq.rst paper.rst dinmeter.rst + stampplc.rst diff --git a/docs/en/controllers/stampplc.rst b/docs/en/controllers/stampplc.rst new file mode 100644 index 00000000..bc500a6a --- /dev/null +++ b/docs/en/controllers/stampplc.rst @@ -0,0 +1,96 @@ +********** +StampPLC +********** + +.. TODO: Image resources are not ready + +.. include:: ../refs/controllers.cardputer.ref + + +Startup UI +========== + +.. NOTE:: + 1. ``BtnA`` and ``BtnC`` is used for menu selection. + 2. ``BtnA`` is used to execute menu items. + 3. Long press ``BtnA`` to exit the application. + 4. ``CardKB Unit`` is used for input. + +Launcher +-------- + +|launcher.png| + +``BtnA`` and ``BtnC`` cycle through the available applications. + +``BtnB`` launches the selected application. + + +Setting +------- + +|setting.png| + +``BtnA`` and ``BtnC`` cycle through the available applications. + +``BtnB`` launches the selected application. + +Long press ``BtnB`` to exit the application. + + +WLAN +^^^^ + +|wlan.png| + +``BtnA`` and ``BtnC`` select the corresponding input box or button. + +``BtnB`` performs the corresponding action. + +Long press ``BtnB`` to exit the application. + + +Gerneral +^^^^^^^^ + +|general.png| + +``BtnA`` and ``BtnC`` keys select menus. + +``BtnB`` set. + +Long press ``BtnB`` to exit the application. + + +Develop +------- + +|develop.png| + +Long press ``BtnB`` to exit the application. + + +APP.RUN +------- + +|apprun.png| + +``BtnA`` and ``BtnC`` select the corresponding execution action. + +``BtnB`` click to run main.py according to the corresponding action. + +Long press ``BtnB`` to exit the application. + + +APP.LIST +-------- + +|applist.png| + +``BtnA`` and ``BtnC`` cycle through the available applications. + +``BtnB`` click to run the application once. + +Double click ``BtnB`` to always run the application. + +Long press ``BtnB`` to exit the application. diff --git a/m5stack/CMakeListsDefault.cmake b/m5stack/CMakeListsDefault.cmake index 6f95a202..f43cc71c 100644 --- a/m5stack/CMakeListsDefault.cmake +++ b/m5stack/CMakeListsDefault.cmake @@ -137,6 +137,7 @@ if ( OR BOARD_TYPE STREQUAL "fire" OR BOARD_TYPE STREQUAL "capsule" OR BOARD_TYPE STREQUAL "tough" + OR BOARD_TYPE STREQUAL "stampplc" ) LIST(APPEND MICROPY_SOURCE_PORT ${PROJECT_DIR}/machine_hw_spi.c) LIST(APPEND MICROPY_SOURCE_PORT ${PROJECT_DIR}/machine_sdcard.c) diff --git a/m5stack/Makefile b/m5stack/Makefile index 258755e2..9ac760c6 100644 --- a/m5stack/Makefile +++ b/m5stack/Makefile @@ -39,7 +39,8 @@ boards := \ M5STACK_Atom_Matrix:atommatrix \ M5STACK_AtomU:atomu \ M5STACK_Atom_Echo:atomecho \ - M5STACK_AtomS3R:atoms3r + M5STACK_AtomS3R:atoms3r \ + M5STACK_StampPLC:stampplc define find_board $(if $(filter $(1):%,$(boards)),$(word 2,$(subst :, ,$(filter $(1):%,$(boards)))),none) @@ -75,7 +76,8 @@ BOARD_TYPE_DEF := \ atommatrix \ atomu \ atomecho \ - atoms3r + atoms3r \ + stampplc # Select the board type to build, default is None # This value affects which folder in the "./fs/system/" directory is pack into "fs-system.bin" diff --git a/m5stack/boards/M5STACK_StampPLC/board.json b/m5stack/boards/M5STACK_StampPLC/board.json new file mode 100644 index 00000000..096f750e --- /dev/null +++ b/m5stack/boards/M5STACK_StampPLC/board.json @@ -0,0 +1,18 @@ +{ + "deploy": [ + "../deploy_s3.md" + ], + "docs": "", + "features": [ + "BLE", + "WiFi" + ], + "images": [ + "generic_s3.jpg" + ], + "mcu": "esp32s3", + "product": "M5Stack S3 Serials", + "thumbnail": "", + "url": "https://www.espressif.com/en/products/modules", + "vendor": "M5Stack" +} diff --git a/m5stack/boards/M5STACK_StampPLC/manifest.py b/m5stack/boards/M5STACK_StampPLC/manifest.py new file mode 100644 index 00000000..6e3f97cc --- /dev/null +++ b/m5stack/boards/M5STACK_StampPLC/manifest.py @@ -0,0 +1,5 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +include("$(MPY_DIR)/../m5stack/modules/startup/manifest_stampplc.py") diff --git a/m5stack/boards/M5STACK_StampPLC/mpconfigboard.cmake b/m5stack/boards/M5STACK_StampPLC/mpconfigboard.cmake new file mode 100644 index 00000000..b62ad2cf --- /dev/null +++ b/m5stack/boards/M5STACK_StampPLC/mpconfigboard.cmake @@ -0,0 +1,40 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +set(IDF_TARGET esp32s3) + +# stampplc https://github.com/m5stack/m5stack-board-id/blob/main/board.csv#L29 +set(BOARD_ID 21) + +set(SDKCONFIG_DEFAULTS + ./boards/sdkconfig.base + ${SDKCONFIG_IDF_VERSION_SPECIFIC} + ./boards/sdkconfig.240mhz + ./boards/sdkconfig.disable_iram + ./boards/sdkconfig.ble + ./boards/sdkconfig.usb + ./boards/sdkconfig.usb_cdc + ./boards/sdkconfig.flash_8mb + ./boards/sdkconfig.freertos + ./boards/M5STACK_StampPLC/sdkconfig.board +) + +# If not enable LVGL, ignore this... +set(LV_CFLAGS -DLV_COLOR_DEPTH=16 -DLV_COLOR_16_SWAP=0) + +if(NOT MICROPY_FROZEN_MANIFEST) + set(MICROPY_FROZEN_MANIFEST ${CMAKE_SOURCE_DIR}/boards/manifest.py) +endif() + +# NOTE: 这里的配置是无效的,仅为了兼容ADF,保证编译通过 +set(ADF_COMPS "$ENV{ADF_PATH}/components") +set(ADF_BOARD_DIR "$ENV{ADF_PATH}/components/audio_board/esp32_s3_box_3") + +list(APPEND EXTRA_COMPONENT_DIRS + $ENV{ADF_PATH}/components/audio_pipeline + $ENV{ADF_PATH}/components/audio_sal + $ENV{ADF_PATH}/components/esp-adf-libs + $ENV{ADF_PATH}/components/esp-sr + ${CMAKE_SOURCE_DIR}/boards +) \ No newline at end of file diff --git a/m5stack/boards/M5STACK_StampPLC/mpconfigboard.h b/m5stack/boards/M5STACK_StampPLC/mpconfigboard.h new file mode 100644 index 00000000..cb159258 --- /dev/null +++ b/m5stack/boards/M5STACK_StampPLC/mpconfigboard.h @@ -0,0 +1,21 @@ +/* +* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD +* +* SPDX-License-Identifier: MIT +*/ + +#define MICROPY_HW_BOARD_NAME "M5STACK StampPLC" +#define MICROPY_HW_MCU_NAME "ESP32-S3-FN8" + +#define MICROPY_PY_MACHINE_DAC (0) + +// Enable UART REPL for modules that have an external USB-UART and don't use native USB. +#define MICROPY_HW_ENABLE_UART_REPL (0) + +#define MICROPY_HW_I2C0_SCL (9) +#define MICROPY_HW_I2C0_SDA (8) + +#define MICROPY_HW_USB_CDC_INTERFACE_STRING "M5Stack StampPLC(UiFlow2)" + +// If not enable LVGL, ignore this... +#include "./../mpconfiglvgl.h" diff --git a/m5stack/boards/M5STACK_StampPLC/sdkconfig.board b/m5stack/boards/M5STACK_StampPLC/sdkconfig.board new file mode 100644 index 00000000..deacab52 --- /dev/null +++ b/m5stack/boards/M5STACK_StampPLC/sdkconfig.board @@ -0,0 +1,30 @@ +# SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +CONFIG_FLASHMODE_DIO=y # QIO mode has some problem when mount fs +CONFIG_ESPTOOLPY_FLASHMODE_DIO=y +CONFIG_ESPTOOLPY_FLASHMODE="dio" +CONFIG_ESPTOOLPY_FLASHFREQ_80M=y +CONFIG_ESPTOOLPY_FLASHSIZE_DETECT=y +CONFIG_ESPTOOLPY_AFTER_NORESET=y + +CONFIG_SPIRAM_MEMTEST= +# CONFIG_FREERTOS_UNICORE=y + +# M5STACK UiFlow USB description +CONFIG_TINYUSB_DESC_CDC_STRING="M5Stack StampPLC(UiFlow2)" + +# SSL +CONFIG_MBEDTLS_AES_USE_INTERRUPT=n + +CONFIG_FREERTOS_ENABLE_BACKWARD_COMPATIBILITY=y + +# Console UART +#CONFIG_CONSOLE_UART_CUSTOM=y +#CONFIG_CONSOLE_UART=y +#CONFIG_CONSOLE_UART_CUSTOM_NUM_0=y +#CONFIG_CONSOLE_UART_NUM=0 +#CONFIG_CONSOLE_UART_TX_GPIO=4 +#CONFIG_CONSOLE_UART_RX_GPIO=5 +#CONFIG_CONSOLE_UART_BAUDRATE=115200 diff --git a/m5stack/cmodules/m5unified/m5unified.c b/m5stack/cmodules/m5unified/m5unified.c index 2f62767a..68f1dc06 100644 --- a/m5stack/cmodules/m5unified/m5unified.c +++ b/m5stack/cmodules/m5unified/m5unified.c @@ -28,6 +28,7 @@ static const mp_rom_map_elem_t m5_board_member_table[] = { { MP_ROM_QSTR(MP_QSTR_M5AirQ), MP_ROM_INT(15) }, { MP_ROM_QSTR(MP_QSTR_M5AtomS3R), MP_ROM_INT(18) }, { MP_ROM_QSTR(MP_QSTR_M5PaperS3), MP_ROM_INT(19) }, + { MP_ROM_QSTR(MP_QSTR_M5StampPLC), MP_ROM_INT(21) }, // non display boards { MP_ROM_QSTR(MP_QSTR_M5Atom), MP_ROM_INT(128) }, { MP_ROM_QSTR(MP_QSTR_M5AtomPsram), MP_ROM_INT(129) }, diff --git a/m5stack/components/M5Unified/M5GFX b/m5stack/components/M5Unified/M5GFX index aa7403e0..44e867a5 160000 --- a/m5stack/components/M5Unified/M5GFX +++ b/m5stack/components/M5Unified/M5GFX @@ -1 +1 @@ -Subproject commit aa7403e0159df547a679e679f63f45f39f2ecb49 +Subproject commit 44e867a579885ebaeeec575bb47417bae21316d5 diff --git a/m5stack/components/M5Unified/M5Unified b/m5stack/components/M5Unified/M5Unified index c1a498e0..83c32ad1 160000 --- a/m5stack/components/M5Unified/M5Unified +++ b/m5stack/components/M5Unified/M5Unified @@ -1 +1 @@ -Subproject commit c1a498e081d344a307840d5b6d1f09ac7f4585bc +Subproject commit 83c32ad1e627f2a09d02f6bef160f1f9c9907ede diff --git a/m5stack/fs/system/stampplc/applist.jpeg b/m5stack/fs/system/stampplc/applist.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..77e8bcb12096901b933a3bb67c2b6dbbd9c71be9 GIT binary patch literal 3718 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 Zd7c>IVV@;f5S0Wk%AMp%Y^siYW& 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 Fe*@0z!(9LX literal 0 HcmV?d00001 diff --git a/m5stack/fs/system/stampplc/apprun/run_always_unselect.jpeg b/m5stack/fs/system/stampplc/apprun/run_always_unselect.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..675dd6e8f31b76abdbcbd89b4e59dff629a549ac GIT binary patch literal 1582 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 L&j|!)@z{R=$5Syy literal 0 HcmV?d00001 diff --git a/m5stack/fs/system/stampplc/apprun/run_once_unselect.jpeg b/m5stack/fs/system/stampplc/apprun/run_once_unselect.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..0b273a46b1a017bf69e5ae499cae8521e2d052ee GIT binary patch literal 1433 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 literal 0 HcmV?d00001 diff --git a/m5stack/fs/system/stampplc/develop/private.jpeg b/m5stack/fs/system/stampplc/develop/private.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..822b74c6585aaa254e7e90d29a373cca5f1d3e4a GIT binary patch literal 16897 zcmb?>V{|3av+v0y6Wg|J+qP|=*v?FB8x!-y_QbaBOzdRhH~+iteQVv%?^S=<)wR2J zRrT7nyX#k9t6#eSWLZfWNdOoa008#y0(@-%#Qt&rZ}cA}|8E-pJ^j@OK!FC>0gk}I zNCDqaz`#+!z6JqA0Km6@1pd#|{~>4?SV$=F@82Nc{xd-y82|Go?K0RPXKZz$j> z03kpl#_^5IYk+qno4HUVs)0VW18>(b5f<$r$jXZ((woK%I(8vb*uaT#x%4rgaOg}!NgXV3!XLt$X_f_#WpN_j=nh!}5`;d` z{JFjWGk?ASUx40oB(~VZ!85k0<8^z&j|L)%j4MT?=*rWI~C20 zzu8>(gG=Av-8g#?_-2-Du<;-i2~eSa8Yz55I!cAU+V5ErtX0Q?@0*^BStp@7SfieM z*}6N0*Q-EzD!d6hoGwT& zS&zvtwA#t*NmjMU70;tFol@G3QwWSVtD!PaQ?TKCE{X}M(MUO{dbzG`bnQSFHjcnW z@90`|CM)|l2Y0nC8~bIW4|z<`j#s)A>ZwQG-5CAN%j-yzzB)|MBu#N0MEB5~c5Tkd z86l0vrU&{k~ZS?5!{p}DBt7fR|`8Wq_uOeFmvgq`ybbhN=GS4)K z$F;+bY)aEBic7(c4wza^(eaSv;?GJ9dN`{v`d?v6ekQgu$Ek-?R6^DKaB8Hvj$cmB zXc5>RHs`xlZG0r02vnJgO3lyI`Fs-^$#J+JXxS18$DE%aY#3%O-O`bE3WX zn#Bu|ifE%$hfj;*p0jDF5Hbj9YM_uwTbWA}qln|LM@UU{h5L~Z4BxjpK&TR{%6f8^ zyv#pg{ajKFC7SX~D-$#n^N=*m5|xxSgxltzk-ANDnJci(n~}D)kZ=_=6x^xJahX<8 z9|BXmZHF0kOw!2+vl~3KMk@N2VNRkLnuG2>LN?k@&Vj4PYFfW5*TSK%I1SdnfBpu! z&^mUm!>2py&_deFWO%Ndt&lXyaY5$FKsFsoYAMcA7b1$K&6UYY|G30`jzT`2!i$m7 z5&TGZ+3EG^;r^OW=$Y2sd@WM4<=kS|44eWb`9ziKv#*iMqfuArSbDYJ4(BI+*BmbTFd2=Sko3bdR>w^IV; z08e|-)szHlcco(~iEZ0!rCZs<60yaubp_cf0kwTYABB7<9}PaHlMR!+Q`;$=!$D8| z+czf1+q{yknYI*zUIdrqW@A5HxuPc^2YqxSXjtdP8K)>}P*3^^eS_BCWL0}Y`U{X9 zlkMlnHfzrTiIcf@`DEv-7N=9Qb=4#{0Y`7yNH$fxf8(%w#BDHp79~Cy?c|qE7$<#^ zftHLEOK~D;uCk)8-DAf@n{J_xXFJ+J3C2imge{WguZpgSs&3AL`E{k^$w)oc^Ob({CD5ZBZ6Zjbc_ES={muAKDB6EQaGj^mkF zN~AI;zo|S@en?LQQW`45n4&~V+Um4f`;CE+6x|oVvZw9~Fe147IetEa9^j0&CE;J? z>mh}<;oVV20m=qx)EoVA!Wx*AGm+PK*0L?#!a2^3yLSlSZ=|CLZP`a>LvabE_yXip z*RKJC5s&`{4@rgdW@Vc@)4p*xy2Q?Q)>e1cw@-3@#-K8SOb%L5E5T6-rOc^s_k^>O z%d+rfZ&)gyW`9yl5XeN#xJSt(C(lqzQ?olqc@@MP*wI|Xe3M5b=MD+O5;&snDqyc% zbh(K(4iLN8B?yIm=0*%Zog$1vuY8c9cT20Z(~@nV9UKeK_9!C22KmoGAUYkyR~Wyxlk8oP$cfM>P#@0E|XA?F35Im0QvN7d~*G4e=jSyY$O zPZ26)vk^ppBg->gb3MMp9uRk)Tz5c1qxkpvYIKi1Gg@t)>CIs1-hRj1?)kiJU zG7n0*JwCb|iSw^#iM?nSegSr31698OS+!FY#X_l3Odo$&9+_SeFSOpT)@H8N90Qk@ zaEr!;s(7$IqjuhVBug4+pj1m*_K~|A0tvyUe(!*-}m(4{(N^Z%59D zonO0je8ozPZ)a$(dx|Jj)BS=AAKO17X|n;H!j?KX5dDVA<-a66tZKv)AaQ)Pd2cZ1SYS2)Nat@_A~BJ)^w1M&7`3zkG z8=INm(wb~17mY?d9L1N0e|RShu~ydWx+=XVb|@aYmwPYj&Z=UOgah318m%8~hYqRR z&Ju#?Xp~m#YE+yfS}yk_8AxGtq{q|2~1wTB+fP2 z(T_yf=0_r3G_><1Ds;OYgXi!g>K8-z29zDik5GqbwYe1q_nxN@F4_EYtBGQL0!jh{jO4auLN>+74H~$>XF{uz3q|~u?Wnom#9x^(oQ(T}Kyl`3 z<9;n+pX;n)Sg9V$cxQ?pAO)fh+&k6+KM`My|o#l z+RHh~(>T}lwY}UD60JNZaD5fXwbCw}7ncIpsvX>r@5UC6$miM+HCfAw5-(|4#;m4L z>N(Gc|7<=PBYhORgq3QH@)XWxnC7gpXu9t?a+TBjqXn14ml?Ba8Wd#f8{dmstJya~ z+|6QU6m+3OH!!z+y=ewJU}IBZ(cc)q^e>f%=Lr(BFOqy<1&;B&!G3HLN$lJy2K4uO zCPyAeW6b$XRpJXOnkh#Og_U&U*li|X{7Gowb`0pL)?%Ie*x zDI&i&-@K}Fysxd(=H@$}Wp#N7%CuMPo6cA+0q<*QCcH&0m$mj{VHhmeOkC3F@)#5> z=kOMww=?VcSq3v0alRTz9TVS{O2#l4&T#DVgKt0|rb9@K80f4ho}IX0;DTNp{!;)w znGfS{viyh8z{WlVK^P%3Ou#OQF~4UKUQ4_wMzunUI+PT51e1enBIn%;y%R^ zEUbt*FJHBc-fR1~o#z89VAZx@-o$R4gEa`J-xWfo(tX{l!op=>hytNHJPZ^5hmvUc zH@wE3XqYlf9#nYhiG(urLFr2i_yUX5Ji=uHjYb|%+Y7X8Y=-EhmS|r)eeqJ%ns`LZ z;WgPz$A%6m9@TnzQVfTMkzsw~hzzY|s?Z7(vvrK6Frt+nc zQQaJo4Ks{0DndrIgDAJAlWC&NrU^wTx!+qTsE$$*!n3@ODTb{WrK9d;K*g_AjP)Bw zzomF@ax>!nh4`OA?TFRu8`j-I6I9Q+ujG=PChds%~VFo3m%;n3emMKhQVF+{!4sT zDw$}ytL`og9~={?b8O9>%I)s`^QN&`^6GsvrQ(UoH|beXd(aE*lQ&m&0_*IAmwCQs zbS~#x98^-V=ySLSZm7*Vt_;cBq|!_xeoo=C!FPM;&Xv~A!gt{ zY|5CGdRKLqMgl&GHXa!ciW1oBEA-s@kh^{G1m%p_f{m3_;CI_%!r3pOtgG5Fa$=F1 z4!45Q)zdwt6!1ovPkj}&g=4o7%7KM_+|DeUzb6G%q@j|Xw^cMYQYUEDDH8!}Q2YhV zQpv=jrIat4St1l@fVdaPR7hvJ$Md-ZT_v)Rk&R!#c#!(YZ8zvnIB6R68mX6iJ7W@+%7o~9kDxS{@5C6Q<( znewzmzo$=rc`>QFVNt>Fzm#%v*36TApQo!Js({t%%gI##MM){DVToS9cv(%&9!VCa zz>+abhDJl|E1e|8e1sM$IG!p{qTD(@y6Tt;>v=vE7qv79R%sC@8!nC~=vwq+?N>~e zAvrZA#CSHloLZr%2)A^`8%z+9~zG%$5pRT&HcuRw%R#2%tmq!!Dkc{{A@2g`CBCA%xi8sLX~4iQYLNGUf+9d|{1HlpUv*Wd#Cj zTikj}3fFXPqs1iYYEx&*7l2n>Pts>r=7=1AhO{YS0jIbk;B%1VSMFbG^rcEAVeuZ# z4q9x%91v_vDzCOMZS3ULkS$kQWqoC;=l&JJk{_LTkKLuXmT?|hy+>}cqc)X2s|j41 zLf#yJ`SkV|pJBCz@i5|~lQSDC?MN?8tA0XRnP6&5m>MwpR8D5Qb^72>0s-A7ujNZO zJ;LhsZBxK$&3PZ&(TL;eKt#l>dvT!)RNu>vJ|tW^^`REUxSHbuQ~Pa4e0XN8(Oi88 zdON3F#`53aosbF{!{+>@Ya23VC89LHMuaML?2QyDZ=~_ ztlfU;A?*oYnD4Z~6X{r{TVpOwRT0z3dIGO5J3urZPk;8#${=%!FL;`Goz9>0d{54= ztKB->_wo)o+!2rMG4o_29rIHdZR)6dIAwL_3?L?SltV{8xTDUFg1)#rkU~zLK=YO) z%UyhQ&2|0RdwX7lh)+)u6hqicuP`co=$P@0@&&L?x%PP!xc2m9!oSH)L{s*BF!%zH zpIp#AdpEoNWIVA~={;PDF`mcBso+s$2^mQ!u{y{xBN~c%7rLA|8#$lmPx}IJ=Y0X7 zK0A$;i^ez6{v;q`1b-N1U3MnmXRs??|ZsbUj!?8z>Z%6)sZIe-iHFEGNyZYeKuZo zqrL#1fe+Ge5MO`+$-XbZ!}?vM;O=X5U6ba|M|QNFXU)51jk`@GP(&tP6;QM8+exw_ zz6l$&6yS|zCo>E=tnPkHt<45T661~vdt~-t)stt#=RATpFta>NSgVGwRYjK?V<0Vc z#-W40qHWBUegF;&Iub&}#^!HVxs!!Z$QNMJ!u!Ke!y#AWsp1z6dw^@=MeF>6N|U>r zWeq&Ii6xDlHnm0=GAs9wOo3W`(mCkAb_nA(51h{{{hviPhOY5~qvtsf!FF2}?Vdy+ zBw0^`OV92Lyo-F-tPcDt!XbL?rPRMHOsOeiaS!*3d_2wvRJ7tA%UuP<@5<0LF+C0h1UjCLu!n)QQ$hNECQS(F z!(FJ%c_V?PaycOaC)#}B_o$VZE*w>RjIwx{Pcq{eX<5PQ&XjkLAnQMO{w%fS@1E0d zST)xx9|X(mlnW|Ub542<29Z3a55jY9Vo(>UxqNP6!HM=}M%8^Gq`ZrBFh7&l>ti}Q z1){Fq+=wt$AJdvw))zL4ftT7dqUQ?w;t`dIGt2iTB)TrOjnBphHJZTYagBriWx&VUBh*7!q}^!S|(wjQ84I6bAc4-Oe0)y=Kf zVy_>n5UBG*7c~>afC8v6d_TP@uD@(_j@12AGhMaHhi6yAD`1yoEvd^IE`EaL+oBh! zSBu+?^X7+@)>~}-Sl>q9RRhI-;9oei=|lxFiqoOWdqkYTyre9s;%wlq*j;`Bj0x-5 z?KXtVYP<8-Iy!;&jEkOewR`6gWbJmT^(~RL*mV)4P7>qt;Ig!WBnz&JGA`(AHB!XQ zf@QAVo*`=JME)DR#B?NuR+WUY)$)-omYVu?lhn8)%`EW8mJ)obc>~u0voD&N^*(y#4CrXt zlfny+8ya`7k}c|LA-SS^=pmxIyAvV%EFqvw8a+9sf#YGn_AkJn>n{NH_Hp2ArajWd z>NQqwJjnm(ej#WEsJQxYS!PJb@}LE;Ppwnj&Fd#l@xqP9odzO2xGUn^C-A&9Py!oa_lkwIAHKyY$c4|@G)}L=e_==|EH}L0gHOG!@MnxwtO@xFYYu*IPp$_ zcrr4OO`l4m+VpMCM~?sM)Px>$-S= zTl@#9b=#cYtFxKl^?TLm%coKz5nGffMg@dxYDHIk&8mmCi_fIwE>}D8viSK6nEm|J z^qRy|MzG`+8WUB)I6CH}LTc4bXw)Jx?>T1A_WT9tIV zi&c7e5VE1Va}o`xE10lLH7PC`$|atfXr&iC7g%YVrBoQL?G5$`RJ?pNuakRGgHWl0 z+^19HapevibR9(FEczfdQ9VaCp!hHC7K?N<9enu2!6zDm?3zoT-Jg*Y7R8@>qrY0H z>EW~%IZKU$Lct$tCMPXAZ|<8=nNd)XZ3a|O_}YER631p}yKX8r^1LKme0fGYHY#?6 zZj{oAm}VT4Y~&m6pGMnk?|3UGTC2hUt+45^T@^mL)pUEZZHt%4#Rl4@cN3c7mf}^_ z_@jG)6mjl9=1bbYk7UG!JbwZD$X|RYA4`ri3PKcmND6Ku?Y5jm3jyq^w}l0Y?doKK z&HTbYELv&hBNep68fnT7>LX%!aMo1Ul#s{{;ZhA#HB`z|HLf)@nS`uTU7p_LcD8sf zx|0=46uFHo&J@Sid_AAul4T~h#c}Ct6wjmAY)#y_Ji~ZRz+M_F+Xz>TXz>Mn>OwJ) zf`x6+sxi@03#N~4$tLL!U=7I{*8LvPhDF7?(e6CPn!>`kffQz>8HDt&lIzVTZ4!@> ztDq5SYPevd*uSfyd!17jdt4h*!s^YObA9`8N0t?{ah$lH}X?=QeD z+Oze|KRk3fi~k?l6b%dv3}^B0`@R#sUIyyM^Z5PWA+DY5gT}d;9RpsI<7>2bZ~GHa zHcx$tOnvxYig@0DkgO}`Xo*o&B}LHre!{S?A4*Z$Lxf?qcQU=Q>9%bYn9%BfQphb> z&}TUDM7dsJ*!-xeF>Ua4-AHqPfs^RnBSDMBX2-0pUM&|*i$BJ4L`PpSj5Dqo;~kK1 z;?wHla7B80DK{e}M1OS|Tn^#@nFeCQEXhZJY7E_br{b^KcdIS|+m%?s4fz5z3=A@&3UZOIw&^l;heChp04z5olV>eXJ44^V6o z*IQQOA1Z8q4>v|O{xz&SRde?MZ+@~$Y~+84*qDkPCa`Tx|4Yr#C|J3eCnrs%c3#z220CEtLsODT4sc z1v1$#fdtBGoN?KTlhZU{3$gc(j@FTg(^)a>gM`rOguu7ZEf!5%rp*^^QMp-&4p`Ol zut={6DaoM#?%s0gML6m*yp(fes_7hB21k6avQJ?1h?0-MDC1lWoNit1*im#0$b=d5 zg*>f&+-83?4RlxzL$}SYdbA9qF!;$m)f*|bVj%(F{YWt)c#%Y4QOvC{hS*cWbTYnf zqkNKUbM@CUa#QaMur|^lFm_%VT^+eZ8bbvvQ}w`?BYvZA4Ws^PQ<^}>6Cy^xoA?D# zKp{|zCf{fu*9F=RKpMq#-^%Tf+w$(L5nlA%=fpV#0##ob8JLVjFo5e0)t$jj7d{t7 zK(tuqCg_6W7E3YUhy^8{&0q6!9)h!jFAyZ#I8nW$_-m3M)h%;L%7+B~lLP@fF~lz>zPZ)sU}d>{F4h|1w`-#l zL%iRaV3Lq6TYa?!Z=Xms4-Tu6-Cod-YCq}@Vt2|m3Ma6(*CJ+96;AxbH1!;l(sTvS z^6m{on7rVNFTm1M-;4DGb2zA{pdp=9F4p%TB*-!V;LK>2w=A_+|B z=`l+Ow0nvpKfjXhtH8Qm-=6-C^)J9#Ymo;~OK~aOj%=<<>vF_>Atna@gR@&LJpyC> zAsYLMFklvbF+9r#xVPAq8LerM&_p>vRA^AaMVPeEGthke`Yx(CTjPtklzEklCC93$3!%4Xy zS_r7jKsEx`6BB;5U88{(o4r6AR(UulhI%=@Xy8!uB-z-Y<;JRJT*Cm);As$A)_&X% zH7ia{0iHwTy%cDuVOR35c;cT8&r-~wX{J5h#gRPd+tc5MPuj5DF$Yohr;M(6xauv` z9Tbr$*pylc6T0}QcC;Bl&G-EvoG=4 z`yR3p{!p%_RYBKdG4@KtZzx7`Z&&pF%}MNeiq>_)UqTx%zRhDJYCg$%dK(wdhF*ID zf0vv00t1Hx;YO}MdJ$L!32ml=J1c#T4^K&vNK!bOUEI530t)OI;T8z;V}qJg2Ig2J1K zp*M5t)Xo1N<2ttluTOT4ZWqY*_UHW1{QW`k=m<7M=CRm}W0iKQCuAb=%*JaTAZIQs z(h&}NdG%x4J%C6c)aWs#0dDDb#-Rm#%=@B`q)!0yqr8`7bNhOz#JV&4y`&gMAqOmQ zG-r=loDJpseq5 z`@B#RVED0+xB2Gx1wjAku;J%_2pdagcY{fWl0jkAr*5!-$#|ea9UAiw+wyZO8y?T% zd121gEfRcyN!sH+m(|x%#a45u-MaHUIeL3UApA${bN{td37rK#kv=?nK~gzA|92S6 z5pGnQ5^Eyga6yo4SF2L)j=~zde)%+j4A4CPT}XDECO=o`I84Y24l~XA&>r73pgqmn zd_bs*R8wDe{(x9=I+lfH5&W2G`$u|^i?cRExSI6CzXKT7`v{z0=d$Z$0QiAlF*JROc2wSInzvrHMKRJ zR9cZaT`e3X=im$AOQ=~?^itdK^po=&$OULQHnX{96IuGuTl3-zTOG?VtGf;Z&!x3Z z8O5(qMr&zZ@3Ppp)NZREQ;DYVzQgGp(lkG3nSYrVTSuGaF?UV3;?cIo*;46jon`wh zyIkbq7m@Zp94q-9vq?Qi?Fc$~uv|=%lKZ_)(=;g~mb+Co=KAae-o1Q-AR!;(4f2@w z3lM-DK(;+?9|=ol_G!~mH(p<{BiSs2)7!VRroNT24AlxDbg&x8hPl}#1ypI}QhS|D zM_LU?dqUHgW&f-YaAPZY%+1%*i-M|!J0?~?5E+%(;XqTBv&Wa5K^u@Bp^nL(8r)8a4TXti5y?O_ zwi{Peine|8seUCXMZ@s0webvBA*AK}kW}A>)pe;Xw{-TaZide0fchtdmZp~IV?148 zIxXz4^PMD`GE+q@}}K7 zACiTdo56SmvSrT#YHhQe1fznI;UrRU9d@){iVTWLP+C1~$YKRUEgZ0<=;T#)8b@xY zT*&jy#T}}$LrNxnyIH|XtP0mwJ_v!q%Pk;MMRnAmn>uY+n zu*G;TR8D2nq-niT0_K_LrG8uCGd0{3&g9A-avH0S3<7t5T}YLXq>@hF=tFv=q7PBK zJBsBY?zbf`%ym!$_onm%FhaFOuwSxr@IWj)fPMBjf&_*N3~?Y3dsy`t<5m&ZR>JQ; zziB0j-9wen1LRE)eJw-3SA{Py=BYEW-5@bJm097)V8%UAP`tNE~TmZNgf58f=*sS&Abb3JT8nCBfPdtp+5oXH9~Ut+t|<}Aj`i;+N-QLWn)E=2S0&oo-b0$DfI==PiKcCi)YD7kU*s9CP2v=0WqkUh7 z9H||G-TmvZk7A>CaocES`uib%Ch5&fo`V#XC*1YVoN5^dl!?71?Figdoo;z#w&r`i zHM>P~fJK^&cctYJEJvwX156$w^xvR{h{>!svI9yv&{5FMouG6_>K1gI#*uzbVJ&Tq zp#}zqTB_m1xbsFFpDlsh{adfl%#NH}fDJ^}&S8TumQ8M{Fg8C>wncsyLaI0bQlF+{ zjQBg`(#R?WZpBP3%#=MSrTxXMXvBjzCn2&i;9%T}eFdR(fOErZHSNu2bxAwn`uphe zUVbHuOyfw0CYna5VO2;~obtK8beW-tf`@B~#fU}2v_jZ|bHd19ZzN(POwy;*s~W>i zT2fDqCkb`OClw965ngwunWInqK=OC5>yj%Zu0^~|==z1H&fo`o`-_3Fi|Lj8L_6kO zCp(Q{bSJN~KYa?zdy5L)nA{14A!V#&%=g#)>69FF3)L=HU1dw8szK9D618!gBA(1` z@#wJ}zf0)`eRlQEUx9$DFVzW?VW@r23eR&6=#?so8ngzvb3RktK_WZ?Zam)OOLyHx@!l&-?ifAXdm$k92JM$EQ!MMgT zci6f}^(Xu)N7=+k0=f-`n0sW6Uo{OY`KYNp7oo+!mRKmZ7$wAq^8nC%z~X5Z=e_O$ z9uvv~$8OtYrfJR6a7O2>orW&HsOjdZFL;e?1Cu9PI3!z=<>oK(!qSJ2~CV3Ac zW)`HTLSBe^UP1m_qXbJkU^+kTmho4%o^|IxFUoNrer+T$wpA`@E(FO<#qxJy+8rCI z8fjO2%h9WSPhx$ulVo1d(s}$K`ax9s6tktH#$w#1)k=8X{vBnhkmjUX^-yb_T9qZv zkI;ed2x~?=k&yS4^_*e9C-}uenNXB&4k2$Bnk|G4!J(QN zJrCJt38IvjlLUHFF=dApLiM%6Z;@3%-N&eQFy_)l36X^I2)`U2sBI`l1jfHoW1l@@!9e~=2#T221Gp=X`dOJpFm2HyO_va zV?C654tlXSkAxVK(&k?8*>ZyX8H3+aKF>^c7es2EwB1wq_>B2>(H^~5QwofKX31Kz z@u3~Fg_@_D^{Yi`7)LZM+G37Wp~ob(wayZ&kec&MfIodJtyteggIluqD&DIfT^tIV z#pmkw3p;>t&GNPUVfzglg%o*j(Qxfzps2Wc%x&hCGtK7}P_ED`-)3Wrr>Rd(s@0RY zp@sYm;W{#Uc{Dd5k`J@FIRj0Oi*rD}xka?r%sX@5{#yyXn>+6XJ{#Gge}(@O#w8&6 zIhN=A@uL_iuZHVNXW4+jY7=Vs2MdySe{;=*F4N=>J0(b0uxek4Iiw*Xo1d&`KM7Jo z25qX#m^c?SZubOJ9%)wb>xX*UI}ElS%UVYHB>o~an%c3VU)snu^96RT!S>nNZXC;6 z>b%|r9YQsw&D7n4o0A+it!MYU9-Rs38@)757k8dcn=GY$1d1KY`61vR;xG}3^;3iXTX7sS5H$T>eZ6JLVyWx5ocdladA)Z(_i zR8i`+Mj&!qOGl9#Q?W0kn5K^I)1#xH>hKWPQlybsPnms^ZY2m~PD_qwq&ZzrZ3=R6 zqAM9OZ?s!_^B|Bjbq#B_hXAX&KTc;-Mz7q?O$uO>&iC?Z*>+k6O6GVKz*C(h^u`7J zYcZ?)FPKKfWNK$Zf$7y=XiY6owl`7bf3f*=JrVJ*4pU!%p7;04&7&%YA(ZRvkF-2YUw|7Uo|_nwGE);nC1y!S8Rg#DL1z36|X)nl;Z zz^dMnI-=8rFMzw!ZF%dK5cdZpy&GL&>2Vd8xP5Hq#}rY#SduG*wU8oJH!J^$g2keN_w^K<%R?()Sr z>{EgP_@Ztf`;?cobG?Tdz@K-vfCZ1KTdDk1m(`u3Jxz z6kU^w?pk%eo%?9-Ny&MI9*EOHOIfzC-j&ujr!E()>vlX65SXY}At-ODj#^dYu51$~ zxzgADJ;NLih|;sh;5 z=^h+L4{8mp-@hfS8Ph=z>A|d&f2Y;)Xn4Qs?(quamwV}2lt!Jrr^jt0_!oc!l`vTg zq0nUYV$~vH$VJauh}$y+HEqqf z-%Soh5IPkL*?C*N`a$5VmURx~&MLYH57?n_j3g0}dT?@%yy+~g9EO|x#G=fNm?_HU zGs%X+)RtUJgH<^%<2oe7WZnDG^aMahs7`JF083&`>=V4lZHvb~CpYc;Bg-H(R}u-k>`n4n%`qlAFtId zXXR=E)!I&0w>m3v>TOx+*bmvI;hT;GEq?d)XO|kY{{^#uFU?q*5qqZsnf)zL^iN*; ziufOnEpI5F=nnB!jMhisBsNy1<$((F07ZemalQ_iycTpjvmSD88vjpKJ*w}D13<)+ z!>JcC3&i$?i^W9UX;9@a8$LYV9tta?iD*89!8mf}QiKuF3ACa4GzPYTbD(qdQ%0hDoB%qTf~k@Pjca9AKfNFiTi^6PoXLIy{^?zy)+`Y ztaj7>Jt@nf?~d6C%eFe!c4aLWJ{I@Joz(C{8Gpwc_fldcVT;Y^-Z03`dAdT6Q`QTL z^Zir*Ife}T-**!Sk|__|f6!RfbMG^TmwmdM)>Kve(#6S)B>G!(_AWong$qq3_Iulr z4_UDYRgL)9#+VmcLOKuJKIB}o>Twv{Nt|U&hK0$l@$DBYc?Gw9HVS*1B`KQ|mRq$6 zlhTH_D79LDDfN=0;`<)~@t5WK^?xOwfflhTe397c5%OWw7rO=PI0=@QJ%87kDcIBUHXp&Yqv=sA``G$Xf3wL& zp>T#JIS)5!epgG&GEbwH@T8C-lE})|6Fp3uX|}ZmI2SKcgi&G@GeFta3`V<{-FyZx zqS23()rUjL*`jkzjAoPE!bIIycapf)r4#=3vuxxZ4vfd1dHC$*4-gOJ5NeFyIu4A# z!e|kY)uoYhDGB3p7+UTX3!fdg~cmg_sS?*dq7p$#uGf$=bf^2tULFbO_rQ#ze z;2J5BXiDkg;MqGN-HQR1HPY5NMio|d)Ht3Y$}k+y^F1fi!Whj0?CpUE{$Dz zzr9JI&(3{>3xg|%m)2Wn=K9KJNhI6268KZ*@xT;Q6dP(kOkb~hG%YQ>7g2i5W#mY9 zAo|VQ&?EM~W;(ocZ9|N1Dz-4vQ%uG%)RLnw)o?V6Li`shkb3bpv6wg%SO1t&J%@gw zh?0N2$NF-wtpO)j=)XPl@{9Pi4YVTa9E;z!E9=n_1cmMBAw1q;&*zW1sdk`E_2Jhb zRnz^dH?XhP3sDu2GUmY7h$RmoV%dv=?rE*_(b9&Ur@@E*^rZ~)0FVE?nf?j#!ZFos zh=oo;6pn?{l+3b~jT9)+T;mh6iC9fHkR|;@B_6;~P7xivCLNh2vh0f_ZIOOau|1dH zAO6mP2T?F+OCyaa5;JMDW^|QCN{G zwu_0-68yV*%mWiYKz=RZfZR_5&LU)QCgkA#zdai`l)6`Y;Z zstH4GQEvwH9Wf!;dkV~@qJ+~KF2Tmh_*FFGQ1-Ly1hp%z(RRwPQYv+1lFGMN*Cmt8 z{%VmiuLqmhqP90Lc1dE+;cB|1XD+BZA49CP|AR{GG=ftPlP!Mc&Z|5CI?L7UkY7G9 z&~ehdsaahT+;IdB4e4rAFqY>}3unznaR?9YCrXu>Eo8h>2&nMuK{vOx`8MjdyDVPkTt;NgzX3 ebw&%@`{Cy@G5zj8`2PSO{{Wv4HO98)zyI0rLFXO- literal 0 HcmV?d00001 diff --git a/m5stack/fs/system/stampplc/develop/public.jpeg b/m5stack/fs/system/stampplc/develop/public.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..f9079cf54d801e75387a509de340f4a70d61891f GIT binary patch literal 15756 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 literal 0 HcmV?d00001 diff --git a/m5stack/fs/system/stampplc/ezdata.jpeg b/m5stack/fs/system/stampplc/ezdata.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..4e30db55031e4054a4857ac7b40fac7408a88032 GIT binary patch literal 3329 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 D;%_Fs literal 0 HcmV?d00001 diff --git a/m5stack/fs/system/stampplc/ico/c.jpeg b/m5stack/fs/system/stampplc/ico/c.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..919fbd2be270f3ca2d7679e5667fb338929cd5b9 GIT binary patch literal 1259 zcmex=}(87ESy|GNmT&`W}r`)K!6#ngprAvg_TW^L5N*ZScyYK z)X)*AOcJPs8R%C8U}a-r=Kz_YEC`ljWP$1j+Q!TXGJ#!4ScE}|LsaqqEe0N-W+p*q zK?ZwrxF@j&z-`KW&khybqU+$xs z*7E*8&dR<^*{16uwB&xsfvKHeSaSX|L`FL^_V0XU8}U`_ee~u? z*}8N+%RNW${Q4dC&FsaCoXFd^ZrohFVbvB!ld~C}=c~V(M0xxcb#=RR_f}Pv={Doa zy4uK)TW3~F1t)vzTV<{E{rXC8uGLiefLlM+9p&2_!W<6%$(_{LV(vZj>VuN(=H-*) zwU_r^xop=`^DE(B^VtLTVR8q<=ZgGNXbV%m_~YWqpN~KJ*~~0FzvIfhn2%@rEB(Hg zv|RK0w$(Z4YN|tKl(DVM=G|f{f1)e-gLmI}oq7Aotn#ea`#+xdwPL*N)wyWRv&*H< zsv%#(5(ORX6ZYSjB>u>k@A86&-^`USpPRk*+2*w|Md5Guotc|0q8*Wck#}mEO}6iq zHG6Et+?MW{%{7Db$@GA!WI``wrcGghUubC8-z}HPN|y|$I--o z_gV6%Z&7Zo5jLyUwGRCD^6R+Qq5S34i^CWHGb~zC|4?3b&-+iz=L{d7?yifM{iK!P z>SL?-pFwlmmrqmVrgg5W+WenE_4rb??_O6=X$RijcXY#ko0OCNpZtp|K0QBeWwP$& zEaN8I_lD7t*Sxx}Zk1daB@owl=AE}tow}W{T>Hi^?Q33rtoXM%++bDaog;hGI?uYi z*4BDGHS)Ug$!Xb=Yn%1ge){v=cbQ9D=b1UjXGz+|#ij1e*|jZk>*PZ1io<>@qdbHe z{Tri|IMNvB-A^%bQ#h7<#HIPg!J2usgdFW4)9k?q?2YMynkY|DvGcYTwa0v*UyBk^NH( z=YNs7|0TQP%HoTNNzfnVD zerN8)r;$lb@Kg~!?s}A zvMYvaN;R<=TG#v9yk)jND%s^&H2KGo?Aww*4{cpKbK`N7+w)g@{WIIj)_*(dR;<_4 zw4Q@&9{4dffBv;mm;cH!*DfPTp~<20#d7POUtI1O(-pNUH2X?)u7b|Zq}M7!JR1}5 z-cEd`$a3AJvo~cj&yub*PrFwPZyr<`Z{5*){n@QKp`TA2UZ8jJZ)joE&Mg}SCMfJo zcVgpUUpRRx!;0|hoX01N_ubJ{*{CeL;WEQ3$%xe=D%D}y8|q%ih_nBxTU2yUTYvt6 zM{m9F*Xgr%B<+urHhCX$JaSskW%oN*b58o4Wqu*vy6Byb+u6U`VTNC|VnunPPw$Vr zu;_#gPsHK`X|Adx(@scaEszq(w(mR4!uqo<>H2QF%)C1Gf z=51@!G7C4JW%5;7m+d~GkMsFDmEHxPj-8p<{@y|U*lq{gLak>u*C%~`yFCBXT(g%i zwj?^ZC@cwG$KtYUtG0%t>Ff1dzB0%i3G&G_T#}>X^s!88L)NA*63ZAi=5X~Z7#KD& zGqs)(m~cj5iq8=s_~SpdYwPx8U0;TznF|9XwsFjm5WddB#ww(uDs12sXlQ6GEG(C= ztgNiI%`dg;XXVw`*Uv4PqG+;OQ`2j;W|r5KtsJ`Ei~>;yTo~9`l9ZHecvmY;IwGW+ zn!6&PnyK|IvxBD8-dV7{i5wG-@_J6* Q9F(Ee!XH^v2bt#B@_gKni)WPVUiHdf7^` z^sD8x=URKKBKsoFi%nNNdmwiCn|oWjdG@bfKYiW)%Uc^)*&CQ`=}_tNNh$OfKYH$- z+rMddXB&m6+ioac?XM$qms>B`@9gU@>)EF#s0cn#IXqKRbCP6N!uvn9cPpwlZ9iTv z>Ho~?kH^BiqpB~S-!1ijvt}+Azh9f~?JXx}#-6N@%}&dj)M&qUfs>|9ecj)gM^BzU zzW&bKuAi1yHVY>Gb&1>a<-(d(t4eQgbDfqKouT1(b~R7(8;jnv#)%U>XP@}?VcmCG z?aJJHlb1d{9CWI3@4CHDlb7smn>^uc-p)zc2k!jK;K;hxvNBbOi-V*8jQWEspJem& zSFW+~PLGyaoV!!eWb4zO(h%E;QSxP7FT?jM$Q?ViTG8**G+iy`56!Qy?(Hr5lK3L- zyyM<($@NtpH>R5WXOPJ4S{NIa;(o_MXhV8Y!Umy_z3aSJTW)=$*;yrZWUnq0+sadx zY?=*$EIg5&jEyb=Q+#|}jvxQ>pJ7p*$L87Xvo}AIkN@TIeB+A5^q3bvdiQppUO2D8 zDcnr=>vF~44z+7ox~BLU8%F%Q-#=l0-N#RbwKLykKYsc=<@$zF{TptnT33XAU!P!> zceiNON{8F!Ivc0yMjEO|RP(L_hKa}Fr;9hAsM~+@iKK;em{!NVwAy1UH3j!rPKz8|g(vSU+eTmrYmCX+h{1EtPM89oYfGZeNn z3Vv7B`SaqR^-osci+a1Ix>l}y@>87`*UznqsPXeQv^>=~*VDvdWxCnjXR6a!?JwqZ zo&39P*0UolG7QIrPnCQNS}pqX?{vji(G#AgN1K}JN|``SaH&dR578p0ix>wAj*B)>*>QVFeCHXPoJ5?CIn=3D8MKv(UJaj`gQhi_xhE# zF5dU%b!ex#+{g2=n|AzuS7>q~>8wHc7ok{&6KAFeG7DZ=`0$R$&Z~RFqpw)?dFmaG f7IivkyKPp{A&vHMf$u``KnI*((b2cB{{Kw?*Letp literal 0 HcmV?d00001 diff --git a/m5stack/fs/system/stampplc/ico/f.jpeg b/m5stack/fs/system/stampplc/ico/f.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..235c23f87042a5e490b9a785a639d157f0b1c932 GIT binary patch literal 1279 zcmex=<&=<@sKoSg?Ss0mE+1MF41UZF-MMTBK zoq)>385v;mP*a%MIN5;`DuQ7BOiXYafX1-^^$RfwDgw0|Iy(Ko#lQp9%p}Mx$Y9T) z`up7H)t}4RgXzZ~-Vsbq{v5MDe5dbK#ZtXmwOh~M|9Z6P*&Z#? z^Io$yvZzE|(M_5be6n`lb?*A6=R2crPR-6c_BOfmiIv@!m*I!bFHKrhyrI(i=l7*4 zx~3K_QLA1GHIt$g(w{J*K;LH#x~pj(TSL&imFS?VnmV>+k(4Nca{t&+&kF8w2*%MM3eomk`J?z-`GRyMo(#*OWv+vd!Ws#dygwzYhdHtX!4 z&0+I8b?R4_UfT73=Cp+g+Z_IsJTN+DbcUlLv1kH2V`kzMHPI)jg6xayt{qJ^oRDh$e4ZcKkuvY*^BKtRkh05a=}Dj|D}JHojcm|c0aF@ z$CefR{qq}K&Yy{q50r_iu`l`i>&8sW8Ly38O(NoTS&I+UvLEEN`Pj0c$!N!GRb}1H zRX5JenQpkgDDK*X>Wd!_o3C7Y@|)Oa0jccM|C|j@oIHDa?k$e)SI&Dw*?Ij}Nu(^C z%+T4$IkUg%sxDvNec#ie7Ym% z>5yp;IpK8b&$FA?6PzvhbdGZ}%ubOJ8T*+H9CHV`hJ}#ZybaNWSRVj(pjSDnSR1 z2bx?telF5gB-%1z-KNv6SuETQCJUMbgPH|iMg?tMo1|?1Ief<4M^QiY{Ft_@XI>X^ zx~qJ?>tjGbprNAIlWl&nKYxGu`t?7<`u&H$cl-&NuWp^{cRS4_Wz}M(aEE;FXP2df ah2Q3_e|f;^-{aT!Z+ZXu_2)ms|C<1PB>KPr literal 0 HcmV?d00001 diff --git a/m5stack/fs/system/stampplc/ico/g.jpeg b/m5stack/fs/system/stampplc/ico/g.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..e271e388c415f09129f084e63e41ef09a7454da0 GIT binary patch literal 1266 zcmex=#P|76T7RfgrOWgFVB` z?^|!Z{K@a19>0Fm@ABNe`q^>)PQ0>nyc|yowKV=w7WtE|x$0cjss-HBrC;f9{Qc+4 zt51KzYd?f}XT)7S+Wu20AyRWz^w+d)QwnSsA1VoF^_u*Xzu+G8?3vro?g>^;{_vkc za`E0X(~hoW3Yq+w+}*pW{~PQ3{Wkq)Hr4j8Xa8xpUVmm=&9?{c zd}}Q}*UNrNPruGMZ&Fm`)_-4?+P^oO(V&>N-}2qlO26#ju2(kepM+c8UD|m4S<#m> z&t%WXXP%wAW&Vxi?ELgp-iBqZE=ybZE8oZmyIkVm8`bq>+q=t`FGeSu&p03!l(H|_ zDlFMWU8%`enbAvaQkJaul+Q^=*S~nY==3%H4?4HB8S3V1uYbH(tN7QBZADd;l9Hca z&VGJb=s-|Nd|m&tp!He{wN5!J3%>ZH{^a-LH*+eD9?vgaX|wb3Ou0{U-@W8qGxcp& z+tO918ZK=~tFlZl)A9Uq_?;Yo`SCq_Qa|Y{ZJ$!I|G= z$;9`!Hmx?^Eb^z@y6d08i|lXn6yH?c;fpR__n2eZV#%|seG68uR`7W;<=}=GcHSAM z6C{=2?#hvk(Ct*%f9T$Rv32X*!zWMqHY>8~tJdKJ*Qt4-Nl%y7-Ch~q?B=F@YyTRt zDJs{rKX=af{&TjjveO#5(8*I1YL}|J%oxlK)`4L(rcl?NKdU*Houd{Ye3--O^KEv(P zg3{J0FI#rbIload>bvagD7pCUu5B46 z8#RQFx_KR4ZRq4{ClGZ_>FbP+j?I<%)5~pcZ}fXUA#+~udC7GD%bd@9id}Y2QP$L8 z6xh8%FRmwMdYRo`7j^xmE?)j@+ucJ;O3#OdyD?6k+B6}wkwx&YfTO&)uFzZ^;YHeu zU!Pmow{YUsRSTz1ZRA-}?*f#&C~tQ4%(LRev(Jy&{4wk)crsvONoSESWFHaTpSIf+w9 za?8DICin6Me;rLyGrsxt(8phu+zb15UNaFaSZDm7!9%Mvt4q`ILiV)%^5I_v1tZq4 K|9=1Z|C<1?`24s4 literal 0 HcmV?d00001 diff --git a/m5stack/fs/system/stampplc/ico/h.jpeg b/m5stack/fs/system/stampplc/ico/h.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..ab1d1945e28d433143b49e0de0535eea2678db40 GIT binary patch literal 1166 zcmex=;GEUat{?1DnV93qOM ziDKf0MrLJOfodffL28i!BO5adCs0O3kbwyV7+Hbg0S1gr%q*;eY(fl*j_krBhDL$^ zZ!z#N0}T*l7G$tzc+npnKcRS@+g)DkGShPN5(SqxH$J~jdfOio>UkqYYimNXtLfEu zyRJ@UV*a?op0DVg^hpc7(-!|3w9d`hTdFALx8&B+;zPPTvn2T!rd_T4E-~|K+sU5e zO@i}}`xNSbT4((4VDEnhx9#DxUol&66sw+?x%a^R=R5Cof7vrJHHT~F*|WbrJZo33 zxj#E|zhckyvn)416xuCPetbG-@{H6jg`N`^EniEC6$C6=!63lY;Pgff2 zvA5THRo+={@zGQ#*l(+8>o2jJrV`&(%^ZJDd#mgE`C388WXHqW-@Z0Z&{VtlYsR~G zwNfRbCzh^S8n)MU+a;#6)ATkuDx4OReRrGRkV9?f5}$098L2A4#&*BXeEpaFalgOa z`~y4p?W5@MLI_qW=q zTcN-(skb3V^R-a4LgK21497Kps(A#&pQ+A0_D<}4ai#Iju(vt4X7Q~InzLNfEI1(4 zLq)_%?Cst169K_GGi!Rzf9p8@c#-^-qf(ZC?>_Nb=(kqYEHHER%2?BBr_G$7ZA@&K zz-YdAnQyU|YHy<8OhKPB4OLUEPAJb_UQ<71MxC9tQf-2f&4dGyJy{V)mV3XcaC?_tyuEt$R`>D=!6%pI+bc?( zjI+{H+8MS!>FR=Hx2Lb)Oh2t>#?iV!Zh|0pXQxt|zIftlw$=KMT?yP`2UdJyW|mMe z(1P6saivvXKd)5f@YOoy6}0M9)TE`lPTWBUT*NjAGB7qubaeEsy18LXkZPYo z$XlmxS|UvQ*Zb~&eT1*{Y<8{Iu?H)Y3QXTzn*T}r`l{`6b3z`i|8_?wA$-$0$z|#{ M<97YLzyAMC07RIz`Tzg` literal 0 HcmV?d00001 diff --git a/m5stack/fs/system/stampplc/ico/i.jpeg b/m5stack/fs/system/stampplc/ico/i.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..81c5e84075c269072aeb3a782241910ed77d445c GIT binary patch literal 1119 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 literal 0 HcmV?d00001 diff --git a/m5stack/fs/system/stampplc/ico/n.jpeg b/m5stack/fs/system/stampplc/ico/n.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..5f4388163e4229b7bb1de5eaf1a3370dab30aaa0 GIT binary patch literal 1369 zcmbu5eKZq#7{`Bm(dMm}n%xMCxz z?xfq@6j746$7*?PN9JX2befmK?Pa@KSa#>$`$zxxeg68M=llJf^E~Pa^(%n#IqZEH zKp+4>Gy~LAz)R!4O`DK!BWT*{9)LyyJix;smH>)|z|au2P~!m<3IksWfz;9gaBaMi8LMeJxFBlVE}Vm!P3+K4eq> z6VTf*7KT`YS_Y5lNI?Sbu^3l5B1$FO z{PE>20~ee!1`(y~geRUT-|_mrJdUjf(;a4OdTJ1|OBp_x@%&|E%w#5Q?4ij_-GeaK z>4n}ax)|(6dVT%AaB1)NeE+s;3yGs+swndqrZN5XN{9WQX8cRHMGmQgZEVsD140-2BTVJhdHY>UZ3&8b z*_xYP{#xmIUAmF)rvaMC(EpHMD)%8RL_VLI*6^%{YMQrB2ynDR6+$`;L#;SA4 z*;!grme77YhUDSikP`Qs6+Es!QD;D4NaIf?SD==;<95wIMT}ME&OJE0_3EPx%g{y7 z4F5V~YXxSdYyabs_U56Jbaz~F8Fx^q1{s{PrKPG3mK-LMzlS_adwJh!c*%JUmjlA2 z_7#P87cF!3&2}~FYkRPhaZ#7=34Nu})+mRj-OVNIr*72GIw@PR-qJn0cYF$OKV+dd zx-q~b!Kv)IZjl&)EndMEb2zo4k=$S2$XA};kp*$5$VU?1AjRM18h>;oUC~E&qMT{* z7|UxO@y*j!=FJ}yYqR+8&i2)ZD(oXNk~+eQvN^un2k0yVHj^{AK*v6hWh5pnw?<22 z=D3^|f$&&Slt(gcqIDJhMAWPFpWzo*a@eheAX0%`9O{Y?XQ5s^-|mClWzB90w4ZX~ zm-h9g;+0l^C6K#*oV6{N+{hbuWB>Z5Yxqc!l6fj*ct$5VeqPn=qqk(2VjJWMK^@o6 z9=q#6p^P55Nt_@5q5k$hR;HNxB~szF^>r1mT>N=t+}uy6MsAyTbE#rV(im-nPyg>**6W&T^{;FzT5p07~!`z*jQ1&s8AyNwP^ zN3mh-0_}Vhu!j-n##n*7kLY?Pa{3r(0T2BKygb>&x3LuV(hAAXN zs!NohWuPDtu6v%?Z~-0oGwSV$z&3ugWzd|v74KD2M5q(2GGOp2PdMtqG#ZL@*2yNY zwzM6a>^gli+1;2+a?8n7uKIlJE~{1e-S@tB{{>NSK@Fb0@Oz1mHt<8 literal 0 HcmV?d00001 diff --git a/m5stack/fs/system/stampplc/ico/o.jpeg b/m5stack/fs/system/stampplc/ico/o.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..ce955089801ff6c69c28a9c30bb3c611a961871c GIT binary patch literal 1253 zcmex==1^2p zF>-WH1geu{WMBd+L;^rV*f~H(C<%h4n1J$71tkUn%Q`1j(w(WlR#5``=+FbY5)uE?#maK_>-SfBQA?J@d%fyO9Lz!2e z%Wty_xZY3{@aCV#(-e`=wT8~o3mKc$T^mbZ^z_wzQ>zAcb&f6>X1--|fq@--RXc({e%pL_R{j9uU}u1|Ze#Rc;h#On(- zo2~T}IkoFl;mpk`UYyZ(d!Ffpzt-FmId9?F2)R1Td+w96+xAcSGwFWrZd>-6>vF4N zCPy}3l55y3a_i#F+m~*-$E}$=>qXzrog%SmB^_+1YX$f=X57_(^@=;oa$QBl%epp+ ztsS|JhN*A%&d_S`HN9fB$%-$0%Z4ixn&Y;MCRX37Q?aked0ZB5HZ$Y!ZrNLY-qz&L$H{8;lPXU>K0ROO#iiZLGR|>)-*YrK zJyJ_M>`L0w496;K0WlK!!;i(l@mFySN#;6 zWWcaPKRPx|JA2cTNf+3%KO3u@OEV9jKYiiaNsG<3k`h8~6Sl{#en0^Za0=@@(-TFCzzai8DG+O*y(4 zUL7i|nJTDhBXw6c^x}2f$XE%surMyiFcty#7fl?mc5Lw~icoh6cfUS2R@Pa0b*Qtp zHj|{sUjav!x{lYmG3k=W4);EOEN9P?EMuvv>8+x!=~XbxuXvV^lCJ=(OMqwyYXAfD zYWV~A`1k+f4qlfWzp+~J%*nuSbM;EznXkCE^44bdj5nET{<}Q11zPv^I15b9zNMyq z%l>v*=tHqnQZp4ZbpAF7WYu31IuzElR>O+7==vq?inZAhOEaU6xcJoD|GxViPinHX6ZnPDa{GBLBT3JNi>DLS$Xizq1@B~4uT{}uxe zGtdk{WV{6hq+j)MkqJ7pouue-d;+(qL$Yh#o48xs* zFX9uKYxeQYT66A$P2jO)SHqn$kIQd5H3qt*2yQ6dD`3BFi}T*y+ZAtB>ZnQc-LNUO z{4g`SLqExAr@*D8CC8nErM>paJ&jZl6S}-;|Ah_8hASe@{g}EjZ&{mJ^UFJ~d+yr2 zR#|?%!ffrSvI%uZ*jO2^>~yoAAtc!`E>l?Qq?)9s$eAnU{~4w~_}us6{riuLer}o}qN(-#sFp0-^UI;( zu@NGie|$}UEV-eO6To=l_>>Zf7Z1fM9%U{HIx5;_)%M^Fqw*&?4m9DSN86U6<>+%JGy}$1^`pTYJ6B+w}My_p?&wf6aMT{AXwo(%Ln__+`|_{*^&5 zRwUhv{q?O?%;7=OQE7%7!Piu_IUEjUxX>K=yOKvh{7&xNWAFIR7grkZ411e%Yx>=w zrAmH5Im;HTn&EX-N$l<2@)H5UIx}l}&c77wU)NE$OylDDckh)#xk@uNH+B2Qdc9n; zEpn5*X*Ul`1LxkiM%l9hb+S2nPIRTEFkhM6(`4Dz@XT=W-l=?|UGI+_JMi`O>KzNc zjrqiPE?F!RDOsre_K8l)q@^{p!ZxR^@?65xuVNsUEH%wg^U_aQzctspH(YHFoxb3x z0c&?cNF`%qkAQ@TUP4P-Ld&8wH8nN$TU9ewN0%90xxl75OO&xYp|i2kw!k4FP{}FK zm_=1sSlH0e@bZZhCr+-NJTq=zTxr$U&ns0qe6>z_1+6+2HEF4?6L-)77qJb342+Eu z9UXm*MK@O|D>8ppsm!ywF8}EE-}}!!=d1qd)<6C+X-T)RT)>kh8(KUIS8=#7DQ`Ts SwJ&ww`h8Z9-`CmyzX<^M%AE=T literal 0 HcmV?d00001 diff --git a/m5stack/fs/system/stampplc/ico/q.jpeg b/m5stack/fs/system/stampplc/ico/q.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..d38cbd96c5330db2416cdc85e8dcd5045934d6d5 GIT binary patch literal 1365 zcmex=yFT_riR>b1AmBkyzfY8FFDoOS+!%y(rNnHA5lE z%B9EW^;gX1`_E8z(Wp)5kbdx$uQ9%Jm)*$Vb=32Ju|{tVPfF4i-6dZ-IzJ@*lD0W_ zIC7WQi^O-2_PEXtoH&=)<}Ax;Q-v<;12aOWp7#Hyrr{O0(eR_a@XPZ4_wVnV)ANh7 z`g`_9yt<{%0fS!+m5*ClTUPYEUUQ{q$IT3_yVGVU@*P}TEcRXXZRYmYljgng9H9#-A6bF5J+vZ^Z$(ip7kx;~0)e1ZH;3ocd*XaL=8q zr%qolkZ*jYJ=a+<+CXEikNmyn{XccJJXX3J=bzc&<#K+#>gSSeJ7bT0ExKLJzu>?n z;Y^FICziFvEvptR6e;gt$#7~7m-?eR-RMi%8UGnJzFsqrK{?>&f(Si!xzcZE0oRxIWa@GC&_^Gh=%y;d_PoJl( zJ9?$IAx&iyqxE^FHM?H6tY~%G_E<%k*SPA&8JX#t>x<&9O>AF$>a|sUw^eM&&$rn( zG+xQ3O-U)+mbYY4QiEHjgvn8E(cd2Dn0vQAyLaHiTfuxz*|YQXV=tuIX7i?d2{UY& z>3cI`x+q8Ke$TG^*LN;t@scsNJCb##`lIK&^$R|h$KKJsQVf4J;vV>30_H)e`>XlS@&!MaVSTdO#@8$uQ| z2?i||cpVkAb#0Q;-Gc>%pWUAOE_v3m^HAHVZ2`|09e$uWd-jEG-5FUE_wRipY1nnx zSK_!PXHoUc@78CE1wV;RJDi?VDK69SU23)D!aF@tSN9y*_O<`pmP?-1Q=TbuR>xiY JZdd>RCIFhbIK2P> literal 0 HcmV?d00001 diff --git a/m5stack/fs/system/stampplc/ico/r.jpeg b/m5stack/fs/system/stampplc/ico/r.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..3e57362f7a7f5b3499f133c39e17283d534673d6 GIT binary patch literal 1373 zcmbu4dpy&77{|Z6xQ5JauXe~;$O#_as^x*_P z0)-hufAxN1q7;! z5YDvaLwTe+G6{P+Z^uap#CJhpa?7$;SdaAO65|auX`obsSFJ60ohMkis-|LmMB3w@ zlIC|++d3+=uH)8CBMGb(NAhFPlrA`;Tvu zD}xmlR6&NVGcttSzm?S3q2(g*u_SpY@)&cNdAGByymNn?BAaxI9IX=w2T+R|>dDuC zBPYqPaa&)LaW4x8@p{7ASls-&_cRt=H64bRabnAMDYvDU#jLkCU21-4DgLR9Wyiix zWM&WuCc?Q~0{Su8wlF?EaUY|+_l-j>sHET^jd@nxhFbj}0<3yT`ftxlO&w zpdU~8J#G`yeH^6>amCp|+1TtNJAe3?2Rp?3FzaomEH3`7*Yobnwq8X36$%0ZajYf- zQk9^qtDpC7+Q(r@72R>8Pdr%^s%zeB{q$jK{zQ2DdD~}!fAR&K8b-ghjnGX1fmUkK zz2bRxO8S$e?>tLnEm6@|*mU3ROczIz(U51|T=G^A7Z1Pj*qxjNvo_xaQ3vmI&JFyr z#M}0Iw=QNCeC%!DwPznU^=9gmwP)UY|6eTC(@O=iE(ThF&-mtG{o!o9t7KjC?& z*n5wWuYCaTPES?@dPN%HS_a1bjK0FhonIS>kCaI^)IIc0;k(|Ca%g;ira^fqwBnX7 zuH`(pag$CpH7MKU0pioeuADKV%fwXsKW8tndWH_0)viu&X{s)c9!Lz^Fz8o*ecq5A XWJ1fxas8!davgf_mMM}s2O9hrawRv3 literal 0 HcmV?d00001 diff --git a/m5stack/fs/system/stampplc/ico/s.jpeg b/m5stack/fs/system/stampplc/ico/s.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..76974e1192b8add4e236f76d16a5cfd592f06f14 GIT binary patch literal 1244 zcmex=`VOWt9aOm>HOun3x%ufqnr2MkZz!RzV>KHg;h} z4kg1xpdv}2M^Q{*U}a%r=K$$f5(JtAlxBfxXJBSwWMXAw7Zef}VGvbR`hSao2PnrR z$SlZU&+zhl>8_i<`OX{XZ$GmyuPpavb<~$Cfs8kgg7`e9sPKCnwK^9ZI_GMr1AlG$ z;rXiXOfTPhP&KdAV6Ez&%l_LM6>Y1`Fio!9R2*Y@_w9vQ2WA2qA* zrnQ~+JJz{)<_W^rzI?U1wdcoX_nxLN=Ne7F&z5_L8&dh8#vf05_9G2fx;bGEmV_IeXT$9^}M_LF25@dPu_g=eEI1c%GNx$QWvH5EXrVR-<6`O(RC+f-Sy&gMpJU;1p`js3s+&z!n; z;;kRqy{~=VQy#x|nrT_&*@74GuY%ovtlhFJU(h>tBI|u&Y1k;7xnv^cXZ?T}q({W~)#EJ-6i zHSNnk7pG~D^Eckl`CKktvnuV)Hu0REJ+r>@dTnR^7@*~s(U`938&)()<=Lu^RXV7qX}AJF)iB)-|r$Ue}bAl7GtozX<@dclr7N literal 0 HcmV?d00001 diff --git a/m5stack/fs/system/stampplc/ico/t.jpeg b/m5stack/fs/system/stampplc/ico/t.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..48be6f4684581233335e98dfbe961201cf4ef81f GIT binary patch literal 1108 zcmex=lP2pE}}*nzc2= z6)xQP{}uxeGtd-4WUBjidiIKNVmgI_io!V43H`OKK zq~}GBl#&EVS&!5N4uytVpLnr1UvIy(zaxA0>WghJf9~`2PtLt`Yq`=r{m&*Bu9~_0 zo&7bO`CI<+C%sz*5vM~ z+p1kzW8e2rT(Nt1`h%RgJ6;|%Eq2%K?X&utY@KyXJ=28ATi}=LezlMF${+l`{)lt! zn^X7RCT(x=vOT*r`JXAU-pt+^dVz+PK!%hArzh zTEMk%by&G#2*ZMn3=HfCzWTp7E99|C&ErX`?4Iljevm*{++hwDi`IYigwdt3I7LeP-X}%D|@~nb-0z zFS+Zr%gQj^J%}+Zj5X4OMZi&B`6Bn+clqTr-=5FEE%D@OaO=I--WylPE?)CBah2}* zgqiDYH=o#;|8fJ*wxeY~s%zJAE6ywaYS_+ifA_x9G@i;ruVqrJZDu~6RdRW$eeYsd zA&su@dt+B;Z7^9YIrrv_Ne*_4T4!0zy&~K3l;Qi)Es`F#Wy@dwXRwWR^gp@bM@;Lh zzztIiXYTr7J=4{DBgflo6IGau&PN4tS-P_HPPuq}>8s~<`bnF;PEWtEWJR~ z7q1vu8O-Eav&3kFihv{ggI_U`=l1r+tm^a(UN}+HN^6&u=`%@L3DML9VCr#XY2x_0 zr_Wt^^6bfH&d;BI?)-dvYmr2bM`tyT8J*QUWi;iiIde>%aQo z(sNthio8p^G-I+b+j>{?IoGW?Qyvvs?ch59R&b_r6SJnIz~Pf|TA%D&tWqlPD?E~( z^3r?8be9I!^^+IH|9oq3A*e?GL1xA(>zgf+YqNO+Cq~#?^G)6GwuoQ;{{5YEV&?2A z{XKibKEI;KhQzN7pO15Ka|TLYk6bBPxamUZZe0T>+2*Kcy6?PiU7l>O!NW4|k-?RP z+G)l%p(mGocx`C3+j!+AbEZAV-`NFi_MT-QY-pmZrvCTqI)Aq>`B#2i-~6helK1bP zS5r#-T6S8o%nl2WN^;4QRaiA`qmLTT8Px*gw*t(@hCkdk>lMlJT}$Iz*6R|sEwt|F zt*Ltt%{!^V#JhOdR6*~=;1@a^uHs&!T?n`g?F81>i&q3o<%>W>RW zT-F>p@+aT>TJoP6UrIjjpYWf-`cYESv&?4|_qSeuc4bk`_sp9`E?3_exfxz@)-Vmf z>f)NG>G+=^Tr%LSd6r|0HrqzdivJAz_teaZFSb4P<7eef?Tw}K36h;U4St^wtk&C= zsHGUNcE?GM!#?-68MCXN%Dyvw>k@;nLRNqIkN@7i^Uk-gzg)W4pWEoQodr@c1g{wd(+pP&5i8roH5^f z3PhX^fBi==Qv>SDqu=@PUwzP)<;)3-rH=tS3h#?YGYwx;n(Zs(q}%q`0VGG zz~>(>d)nAc4fc(hA|4tV?pUyH)9KbK4(^7K1x`T1#k&lBz)n^&BUTDR)e kr{!CZ^xw&yb}R93+u_t!@jX*K1$|~pikvw=|NH-&0N?x*K>z>% literal 0 HcmV?d00001 diff --git a/m5stack/fs/system/stampplc/ico/v.jpeg b/m5stack/fs/system/stampplc/ico/v.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..dee6e6e6465dfd32b400a6c9ffdfb7b3d3f58031 GIT binary patch literal 1333 zcmex=}L{tceu!yLlqtpLe3_KtOg3N*p_6$Yt{^7qZU&o&_-hTU6eEq7Q zliB7y;y9giBlqQPOZ6&Qm12Rw(3XT}Y5y6L9tgCs2=Lr;;ASq{6Yu}LHtjwC%_r0O zS3cXqJ$?J6WUZ6JC5Dk39!~MQP6wgic$z4|7fWc`lwUoD>7Z+B7n z_S0{l%{R@IUA9g5rXTCoXKD8JU#tZTo%$|jiG=Mq8GY&7n)mtfUq8?L?fCwFRNd9b zOZM*Bz3FktTidWG-K-|57^c-5`m&!c&Hioe>tT6TQ0mK)_PKSJ>#ur8MZUXyg85_F zI*XD?=fa!QXWU)I8e$&1waTwF{-$Q9ckPEB-HknBj+0hgS@_&hn~TRwB5BF&Cp(%d zrOg9&zTI~|W4~*Ey4%SWrIF9SNBm(ceH+QMQztcR3ftwRw-E;21)ich($j8CetFOI z)jr{s=T^RydHLh)+B*wMZfN^m)2^z2cH^?^eT5BQf0rLVe)354er4|DXNuEyOKCCwWp1_&)KIqh91s>;u$VXRbk2nKlxsVCt0#Ti z@+p7oyc6-|-~0YETv$;gF1j~5mJLox=0G9WSL__J7H+c1hL0N2=Rr?g%*>FuBTUCK3#b+V2#FtFI*pwr#^Dq_+BUay@5_d zSI`w%gQ)w-lU|rNKK|EUZ7==VYth<%H>Yb|ozo68``i^=Ar$hhdy|wy_E(OpGH2gL z$9e~ZHtt7T4N z;m^&^?PiiZP?%=nD51JUz*SI7a_up%sO?K%8%KvFT{IS!S65)ze6_fHlc2%QZ=YET zWJ)xAlq{qsxC$y=QFtFY>)!M=*RIa&b~$pU=3C*ZqB)ynwwpD{Tx0TB+Ss=!b7lXW zOiVnu6PUpJ9AS-{NERXuHHDRpor4o7qa?_{1k}jL0@V$)ikT6ppIuN$ScF4VQR)9J1|ESEfDBEK{}pUzJ6dUzwTku_q z_4+UKF5fCF`qR&RTN&CdRAHc{(lDLunxOfwUKL{S%zI@3_r!}$)5e6 zp>NL_#0-7Z^S`TK%T6)*Jql=~OXmgnX*`{$ePmKN5xTb^f)Q^DtB_XM8qnLqv1 z_qqJR7lMvltdE@i~ZfkNrG zc;}m$#nVa+HPQl0Pd%KTJbM{uh2A-r%iEKKBDUJp`ZYX_i+XA_^~kn4LfwBIFWc?C zS}mXK=O545xy8TnrQ!ndxHnzmYt+3VuW7fla_Gv)?`E@PSvm*5z-z`P@GL z-jy>qtVCX!9Xq$^=bNiqvtHf&=`6H0Ry(-#T1tOleVovaiN=-wXWlhCy?t}$EKk)e z`EcHCSmUA_B)m`7F3zrS2I4qCUY-9EQt;gVN}j-8sf zGr)u;u7FKzrn1VKmbY3ze=|N{apr{nz{wqr z%^K7Ecf`H?=3i7l#qwt1&gDn+CeE{oDRl~6wto5BeXW-_M^?2q>70^Wa^->+|7;hG#TEW2h z>gFSU(KmI0UuyfFC*FG*e*3(ebMC9ivvFtlY9@6xu`UV!d{jAXEz8mZr9uT|x#qX? zi^{_t=c_Zv>&>!nQLJIHO)FM?WYbIOagtZpO+IrfE&0N-^xv=l{J#kRw>J<; literal 0 HcmV?d00001 diff --git a/m5stack/fs/system/stampplc/ico/x.jpeg b/m5stack/fs/system/stampplc/ico/x.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..8f23f004afa1309b21bde410ab46eecc9c022a5b GIT binary patch literal 1185 zcmex=m16lu;K18qCDV%)-bDm0@IJW?>Z+VpCKC8ek|Qs-o&x`2Q9I56}uG zL1sY)dxjU)#pT%>?rz<NLviiqBpi>_84lN$nrMZ*@cvDszD z|G85txASeqS)DU|-E3#Ae|z}$Oj&r<oUx+ww%eymF`6H-nNA?ki#2bx-AI%rbf_n)uhJmQ(bhPHb6=&9kMZQ7wlx zt`%;(HRq~JX{cfELjPrQ>Wy)~9_{)h^=7y7>uVFfJ`eSO`%hnd;!6>T&9xK7RMb<~=z{~0E>u0C<`LZEBWk>T=)GN z7B(;Uv`65SKQcE@@6OBI(4lfgUu9d8VuO+WqL1_4KPBJZ`6cs0xT|ic*49_L+a`$R zY%HC`$riEU{o8GFNdmsb9y6|a8l3W2mKuBa-pei3d|lU*zxPe~n%lQ&jU&(XExZb| zPDc9td39YvG&sU%MK;&MVC{ctE_*b3Q~QjqT$Nc_=2<%&Ve%@vc|CFQ-nq=TUl`PN zIwc1G!qf>c0Q=^rZDL!IX)+$)LGRI{KXXC#vDcka0)^^*Ir{9>o@ceX%u9MF` z|2$&Xmlt_eQDH;Z`K@_%&$NU5JSXjRn${Sh$Kf&gQ&O+Ye}=p-UJL!!s+t96u3i~y zI_-#bEd;jt(8VcwHK%?SKu$$uhi#|zww5ZdhGGv zFQ%-1T-7&E*uGji&Rf67=$hw$hN7*?r$c*U+6-Q8+h zFMad&=$B|q3CmggpTdjupz^b%Uy5?U6esi~={f3@=oUAtQ{D}!0&ELX$P4K4=`%-f-0 zpfOQVV`h_3XJ_Y(88be8GBPsCPI(r-e)`bm!{AX$0=`}K4~9e+I2nc#;0HZwyyWsTYl|Ab$IWAOGh6p-0qiX j8})U+mDk40O)EN<6u#s3Il;0g&|B+Rm!P{W>;IboP4l{K literal 0 HcmV?d00001 diff --git a/m5stack/fs/system/stampplc/ico/y.jpeg b/m5stack/fs/system/stampplc/ico/y.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..89472d0723d6bb8b2657a2e65e3e200281f88b8b GIT binary patch literal 1246 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 literal 0 HcmV?d00001 diff --git a/m5stack/fs/system/stampplc/left.jpeg b/m5stack/fs/system/stampplc/left.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..8ba2494dbc0f68f173f7b71b742156339af2fbde GIT binary patch literal 733 zcmex=AfY;L=Pi%&d%T4D1|&Lc$`VhR%hJKxLvp zlaVzuFmP}Jg%t!Dn3#bEz%?^4voJETvI((sFbFCO|G&k+1C(MCWENzwXSmiL80#AP zscFqiP2Oy~gvhm%9GOm>+AH;`uR!eE!=C>PUiVDqj-OY8H?Y#Qe>BVJ#{_VP6&BAklVSi8X(O0V09hz^?Pp#Q`vS#J#)-%^G zKlJ!m#mTOW@oOM2I}e_4!9#!h4BS>AU7rvB^B{=2DK=;+eLF4slARP+`>ZvYo#Ct=j(N8v?#}`&0#YZ}pAX zS6VY~>9yL9g#MelfhXz&8T#k6rN!>)>DWBy$%BUv-}~tq@7!lsd1lVBd#i6}g&qqC zc^m4%#n!s8sq0zmgKZlV4C|k>Y+ZkS%bAn@S8Hum=RSS4e!;6(r*8Uq9n5*eGSBbC bl23w@7Ektg#I7>y`z)8>?GYV6|K9`v1*G!h literal 0 HcmV?d00001 diff --git a/m5stack/fs/system/stampplc/right.jpeg b/m5stack/fs/system/stampplc/right.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..712600f7ed52030ab988e57b52575401dac1af94 GIT binary patch literal 685 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 MHvKjD*Zlt`0E@GxXaE2J literal 0 HcmV?d00001 diff --git a/m5stack/fs/system/stampplc/setting/general.jpeg b/m5stack/fs/system/stampplc/setting/general.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..888f58289a6926e558ff8a8d5ed0925e0032284b GIT binary patch literal 1112 zcmex=*j)zs0}< zlwuNO7G$tz*qGKVDJVu#&p^9!?+PVqbqbXg)g)!?Mb8@@dnD!prE7#5XG z+9~le=0wW8(CVx2A}2jwdTWi#!SqVwRWkGJ%Syu7_~-vD3v)Ob=g?GV;QXF#^@oJQ zZ@Js}ZmtjQTc@yS>SS@1O|H!LUXyC)n_oG0Y32sc9VZL-F070cnlW?D%4wI*L~(b& zTK(cwifJpSXKt!^&!Q79XJ`60Rn)LoZ<`Zl&wlEzN#Kjhc6U?3pSApo7X$Otws0xG zdgaKhSsv+c%Gi9O?oE*HyLECJQ`^F&Lls?4YEC-fVsLn4V6#|9!nBBzSv|?d_8v?5 zZTdT&#m)Wl)%E=HjX&<)I?Z$Lv0+tZ?u30JXpT(Of@i0sCN0lxBY zg}#D|X7{8S7SeG2Q1#>iRbAe!!b)fp;2g zS8uj_y2T=F$8FW6YlF`eOZ6mo8M=Mq2s2!p92P0JHal?TO+n$S*>(2Le}xwR$%y~> z)$F&}{fYkb_kPtW)zvIcH+xnxbIo_*kE)zI<1|n0ZD%>uYq)(^w6)c(n|a(f93_Lo zA|m+91y^tu9=)Y-p@RKKTbD%gyu!I=MN7BYYi8;B-F^0_92TElV3sx;T`)0Ku(}|-Y|Gd`z zt$JCI=+;xA3`BhvNy!o?lCvd_ZCslzac!&AwXG{Ut}RjmgR13|{>eDZ-L$^)QqcbY FHv#o`nce^Z literal 0 HcmV?d00001 diff --git a/m5stack/fs/system/stampplc/setting/general/disable.jpeg b/m5stack/fs/system/stampplc/setting/general/disable.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..39d266e5d15fb546568f3915e16a8a5c51d4a246 GIT binary patch literal 546 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 a3YkfkS^#V4TKNMMx#Uc>TdT{^D*gsFIZ&wp literal 0 HcmV?d00001 diff --git a/m5stack/fs/system/stampplc/setting/wlan/input_default.jpeg b/m5stack/fs/system/stampplc/setting/wlan/input_default.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..d1e197a52d96e3d80585e232963e12f52d148c7e GIT binary patch literal 6673 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 literal 0 HcmV?d00001 diff --git a/m5stack/fs/system/stampplc/setting/wlan/input_psk.jpeg b/m5stack/fs/system/stampplc/setting/wlan/input_psk.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..00b91a712ea53b5c457771a59f3e3ea48f946184 GIT binary patch literal 6725 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 M4Idjirk&0F8#~dD1poj5 literal 0 HcmV?d00001 diff --git a/m5stack/fs/system/stampplc/setting/wlan/input_server.jpeg b/m5stack/fs/system/stampplc/setting/wlan/input_server.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..9908380d6c8270a96dc628fd4d68cf5b1394dd23 GIT binary patch literal 6546 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 literal 0 HcmV?d00001 diff --git a/m5stack/fs/system/stampplc/setting/wlan/input_ssid.jpeg b/m5stack/fs/system/stampplc/setting/wlan/input_ssid.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..b15802c5c8151104e5d45bfa7d0647591cae4d4d GIT binary patch literal 6751 zcmeH~XHZmKwuTQmD-F`*oS{KdlM%@@L1F_fC>ezYNrHUBr;=ks6GcEo8ju`XvS5HF z$3}8eaz+~@sxW*rHKp@oeoft)Tet2$Rp-3B)>_Znd;fUPIp>q-p8wZgsFm$aEzF&@NpTH%{t#^TRSgy(rds$&}S5cK#H5{vTcFhX@S#uARid(?) z>2;tHc)@x)Aq_=iBDIvLz_h+{H*2w}cu}c5I2koI!*aAymuT5A^13!Y&e()QzLDcg z^^Fz|DWX_5Je}d|m44o(LRyd*87z4Hhf#RY_cr1Q{Y2gucS3Qj#c_jCymurZ@21^x zhlQa_)s^>S$F7Vs3_PaDfP%ov?x!-S+L;Gb zv=A%2;-QQ_C9_hJj+I)mZi7EpOzA{})f*kWwGQriK%Hx&mCmgQ6@Y(Ufq^4bA4v(- z4w}#RuLs4gesMl*eUs`ZR8lpbR*cG|pWHVNQ!I$nloTd|P>a2nGsOpmA9c=aYZ#%@ zAVEarMY(eQo{cdV$LU5MK~eR9z$$$ZvYu#dE%_rpUZpIsthWmB<} z&-{|ANSPqI6iczE)_D(ytQaj(&;NSnTd#c3@;%-}XW_?^XTa7UM_|lxKjCD%WZvqG zYGQ0aFJfDpd*$W@XYuVPos(PR`@0fq%+dR(nu&_F*_*!4miDJCS}J}p zR+aVqf5m_2tb9cid@G8EGqhaH+wji_#AZ+pLxD)a$tX#gtAbfIVjQ!Pekt*ggW8}O zO%ydNkD5yw_Zer$v9p`^0eD|({4KAyNNK3OP+N1&i2FWm84Oe~W1M6QL`ADe;_L9- z%9cJHPtx3pG;2%f^WAf=*26yzc!rKm4?*5*JyXi7c|}T}2_+cHTTt*xbYQZ1Ypy%R zMgllE8|sFPqSv4B&qUrCX8|+QoA&I^!3*1MQ?~5$hi|Rw-nG1Y=lbOw@J8C618J3R z={_9~sM~&MX%v|VySP4Fv$%&G_@v!I!dh2bIY04ro6=3mLNX$1%2)J99(_t4Mmwf_ zAv*{2QNh~Hr3hl`SucODa=p;VY&JK}n~m~w+6rUX@!kt)O@cy;xBn$Y^M&T?IT(*? zO5OUn8!j}n<4S1Oq^n*1b)N=Pg^Yw=PVxA?q;A>=%&IZ&d?}q|;e5|4#M4tSl;*gW zNM4jL+c_X}=XC9H!NSyfZtaxjaij`qWS(unrtt?dvJ%1Xl&U<=v@ttg_E5BpM5k#$IfJk=~9<<aoH#1l6}ir|#ZCD(+Ln!MaL%UYKVgAaS3+4UY8FEEN)Uv}f8pHrkj zW(?A)q-~Cj+#0;+U3p1^VM1Rv&R~VWYsLUcXfl1X;S&4Wdbx%nHB=w#^!jvKT22}X zM#;A+r)iPnzjiw^XvtIaGk1UnQahF7!m$0f-AU`^S#q8I2cm4`>!wyxgI@*je=F^% z*zn@li2i^JXQ;hFDl**Tv_NW-EQVk&nA*RRDA0s0Q0%aNex(@9l&s4mvjocsH|hKHVf52r7K9xWTX%ARS8#EV|~GZZrYeY5&a)I`V}_L5%HW9Q0v zHTUv4T0_pT*vjVcwDiNp{VN-Rk1uCgo@oFPn&mm z6cnpysLw%!Vj@2Q1)79Ai4qg#^7c$WBuFLC2YnLB)%JYy4*9Xw9D5Y$W7`*W`&7;B zcw@m1yf@GMHp%jl^74{w*BbWKAGXKB_SR4JE~lR@o&)t;EoD_@eWoM-uIk^~`KVlZ z>u>ZRYqFMx$S;^N?!+9opdW{pU&J4iUc4uKPV9HDe!usoxB0ZCj!yjQZ+W>)AG{?N zg4Yl9mR_;us;i}|^nUuFeA<$8&H8)%*-YhL<=ZL=ErY)K-|~LjTIZdlA=evw4PS4( ztrGsfyqi%s7g#Ln=_}`U4u@|!k1T|;vtWg<12gkH>-tw2J$uC_n5w}GgpJJcB?iI3 zOi)L1`OQ*HXsJS9{%@yUK`(cZuf^1E<49%%-8a^$4qYT6dOF1JhKG!Gsv)6ogQoIP zseLNJUT#eo)Q*c^7Z(Ek8ouPDg^hf==}11EGCIODJ2}^Tl_1G8n<#?e3X9NKL=im+ zdD^4!UPcv_aEDKUeW@p}48h>o(@>pe1NI?hg;#F@>KhXV6@PR)+TKLJ09IPcNv^A> zZzJt|>v~J(-3{OqOgZA>L-l9#dJoqM4zz{sIPdqFQ%y%CY>T#Ji9~-*&(!A&qH;>} zqmvncTbPR7!1^qKkAo9mcTUc)FS!QjxSkwQHy?T2OP8Cz5Z%JaLx?(5@*(i_o6{GC;N;xt+2SoMa?N!+^PJONz+9sEGPS<3S1wIuvBSJ_u zeNysd%wUCiDVZmqAI>pXky9g*Hsw30}TY!ma!F-<3A$h)_$%|**7DZ0pR3Pio89$fB~jiBv9;rj=@2A- z*|j4J`TSXL*+x+#vqdJUSoE}f`=wEKb-qzhonz^$(c+8)W#hQP2IX0o871LhrGRQ^ z+26LU(_`I@i3O?W_=zJ2l^G8;4&?8#@b*(eK>Hu>mryp)$HZ;9h&Kr=tN58zV0VhvU)%7GN0VPm&{g1OlZ+YdYNgp^ zDDMxoR=ifGz6K^%H<8A`9%f*yk$FAJrt_0?C{qWK8|2YtXQPi^j?uo@YpV}T*?+um zwQOEjpTcA&=NY)> zM0nZSdU?#Y+G>6zms31s7cU!k?Jpj2DW4J6e`v+|f>7o95Y?8>ZHPSik=K_jJr_#F zPoK>cS0(SGh%}GRcOY|f26oR?ATe0<`3uPf;e!%59_e~IFp5hIH#^FzB!_3nncLfYCB{)We)S)l+u#)`n=C=ml z7( z>n8UmwT~+lgM8-tqa!;XP|1LauE464EK9X{F$HF-G3P)W3vt+Ni6uN-GxcYI?@zV4yA+(q`~1W|%?7Q-_h8lZY`$F7L@*Gb}_vp)Xm zCtL0>ndy{jko~ZNc_mmFWBd4<$8XXx&iH>(JE%mOjGM3n(!YIL{;s((k2pyA087QU z-k88+ej+QsJkfsn&poY}j%Taw%v`7Cm9OJvZ#7GDzp4Lun zUTp#@JW}*Rf6Yz}J`~01eP~yr5?JU}t8iV`>u2Ek(>-mJL;5&Xtg;iDvnFgz6A<^`=`S}6~;^hfeu5@_92J3Ot;qMT`gZSn&+ zFK&1J;eM>FX{$B8G_cS=9GIB?SWs?0K*3|lX7eZZ546o9Q5u(Gu}#5b#qauRcdyWj%(6^LkjgpVNg;oGWqv?Jq z#;r=9JfT2Zk2p)c`S_CE>vloSX!b=hd3&&kia_cs(fl{H!*9TY&-uSaDo#qWWgXDE z7&(u+Ttz3HXxEqz*mtDf8(fM>C-`cnbGWyc$2hG!d(*p3HG9bz#YZw(lQw4ReeXSD zYdQy-OSXaSYKyCf7UXv~Q62;*bwRWR^y^`U_<<4obyk&qW4lms_(wkH8*2i zRm{{~f7Um&EQIT*eZSdjd)(UZS5GaP=qU{=6g846w(3}TXR#xT>~_TzPNs#Z6{nnZ z1SaC2yhc;I#+S3xrNoCBIfpGLSirV$lA|*U8zNJ^1L<9IvdsaGv={%l{KNN)!0oDp zy4Upzhtnsvr*Ewer{{;fe;z)5G;}s0YJZuXGF!h>t$C|%M^EV8cTBQ{ov)3R_06sY zFCI;7d@EJENgUOa6ShR8Jwe}*qO~G;I&r3nZH-Sd(b8_$KT?e$sJNX*>vpscMBpLx zj6>O7C5%LxJ666U%|WhIi6)gOsh{GBV*`d!Bj!1oZ_-qL#Zk}x`J%s4oC7sv&|ko>f+k`ZTn72VCT`-G|gs}CbwGsmYU3~ z@l<6^81`G0J2yM?ckvd1#aUaJoUC(TKGE_VD75qM%*RCCfyElb!CFJO7Q)sdFqr(c zJ88c$oaT1X!ivG=?#fRD7+NhbGiLixA=2v-xAj6%#|O_gAOeL5Cc5~B9yN+iTm8;Q z+0DOCI=CdA#LQM{<;k?J)z3^WDyJFKgN`tFj#lDY*oW&{gfA-cMoU6R``@<3rKrp2 z7@*{zaYl1INmn*q&eFAOiR~*+LpLTZCaZ~zac~H2G*hYF9q0weScFBp_R{3{PmO92v(glb+l0^7iHfqziEPOgK1o#K`{EjDquy8JwOj1)~ zj!;^HHuUaNrl&|&S6$_eGH_(q%O;qAQp3YfqW6mnhQFK?8M9#b7)5LK!!t6U@tgx7 zNZN-~Bp-)yX4IV!*`91t83eNAJp+Xloq#ZJq$8Waw00`I01&8(BkwU^%Bln7&`p&1 z=sl;Duw)o9gyRWM$|jv_Hq=IBJ3LZUt|BI86b}Na6F?xP%Ca8ygFg;%)z?|t?>a|Z zr%~{v*TvJl$*Bq)mu4HCJyP1mt1qvKKZV2Xj9V5xAM-cQezjwB9P{HFimKUZeX0TSfBG6NN;$#41}R`RR= zzbxf{z8y=~O4Rc8Wq(0pS_A1tWK^Q={2RFe%}AS7X)V%z=1Z&r9EsrNm=Cd+8Pp-> zBE}DHJcaF&Oq;tKNfq8F@q3y;DKbWCam3ig9_-h9^cG=16TET`NNk@2d-FqIrY@ef JMJ(r2e*?vV1-AeI literal 0 HcmV?d00001 diff --git a/m5stack/fs/system/stampplc/setting/wlan/submit_select.jpeg b/m5stack/fs/system/stampplc/setting/wlan/submit_select.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..19bf1b0e4afc29de4b11cd5c7d65890093b539c0 GIT binary patch literal 2367 zcmbtVXI#@;68$HkqhcUZR0vW;jDRRa#Ra8E3q^{QARxrh!-^7+t|%%JKuRFOf(n9Y z=q(fpzGnzX2azuE(E@}bfzZpwEpPYxJM%jq?#!7p_uenFKe+z`IBaTYVhDgh0080! zus;Imb5DN_zfAs`1Gjts695$eB!Ni?NDcr)K@cctp9UP~UJ?ueAi)0s769=J^6>I; ztICG~5Dyp(2JwJ+|1AK4Aw0YQA3s#|gt`Ds*Gf!W&e}bIE0y6snXCPOBYqH=hmThf zA|S-ABcQ+LfPbYR+<)N$3;}VipnUwIVkgwC1Yk$xbd%(JelG710|&SgFcbm>t^h2i zBHN|o%ZO(D?k<5c6zMcqSZK3I(!7o$tLGRH?9g5sJ*GDUlvnVPD0Hms!C-=Co6Fc3 zCE3@@e{lxgH_%tfN?#fcpf+WSTtZ5fPJ0$JUkl8LAC?8n%BhcadU!-4Bg_gvJj;D` zO}NN95Boehwcr+D0G8898iMUjsJ7&rdSD5di&W2L0_J$YpRD_cC@>n@_)<`&LDKBE zM9-tjCL_`lXM-D%=ArJ*+YST4>_P|1gq!KSiLzPC^(-GRk|MS0&56p*?iz|iY_qzC z?K`6=66vjPN|LxulHx+b{27h-m@KfF(vY=+^_6DaOUoO@j@}C|89#!AMisp?yh0Tl zbBV+tZ^BG4@`)1H2}e=VkdBsgc}h>72Oi-vWp8pp)$x56I>I~nhX zG*;E7>nc1tBg%bKGs2YyKS_*tk7D#M~!rfu(+#L5V>ixuiVJ?)bzPE^Ztu# zZ-)1*Vy=8iYrzL>pWw|(62~%c<7eYLoLiO!A@2z-lr^O)>${<6 zwY(45hOQ1H1kccWi!{IE<2(0Y%DwlG*^bYZ@VTUA6u|0(8c&)v(8Wrfl2?Sw4CHpa zlICgnI##rHW_J9{kB!B#ZB9~n!1G6fQh3AP+pf5+3yWASMGPlg(=L3@37%-Mcm%V^*|)QqXjf6-*owYI8;sQVp%UJU2r{0PRx4eK8_2# zFlJzQJoJ0(biUS6oP~p`magq1Qav>x1gSV8walA~bCI`c6LX0{*uinQf%ayKUO7y~ zSNhtP^;(Ugy(YPoU_IjlWwvtQVix7N_W0BDY>AC+Y5!K(hiR#SK=MeKqim^jl}!wI z^7S8!0$`sh94?R^$QPMME1L_EQytQ`F>ylH&qn4ndKZxs`Z`X2^&c1Z4XHnNu*sVu z^Hgh3!Lj}}vc;K(ZGFMadK8dtRGUfv8_HfOw2)y_Y~9Ohk2p3G!o!XH6dNLHL~K#@CqyikF4lMc)Hjc(De zUd4AwP4LfJR9jftN$mos&f%x#kDrbuoftS$QZCZ}Is~0@iLk*C)8f=AZ9Xr`!+gIn zuZw!$xWjQeC#K~5T~l#*=rBh83rc5@k!$)h{0g?UYC+_;(0TGO2jVW_<~n}g0IoN+ zM>jHrS7Qhmspb_m0!DJ;UrZLS?oU64&I}L|((BG4zPY*vBPv7_A@hfI`6uKFg=RDw zt=)_ZOB*rxMG|R~M%z`2eMTaYVq+8Xm6&2;oY=%^rF01bwS;mIZs+Mt@tSXYs>moS ztq5UGYq~flq@7UV)Y!2xHBZ}2?+~AiqHe!<5Rl4}nm1G%ZyaF2zjV>sIU?pEn|K@3 z|nv$vvS%4W0e(>H^{sal&$jft)+ zlFj$lv$YZ_>XRk8GWYfi9*(x_DBrU1ef+at?nK z^&wq-beFln+0z-ti*cU4V$SaDZi?o~Y+Gq_7`L~VrIi{P`rW%C`ZA`n(YZY{(4m>f zTgLLcMsK!uFj5xGr7P=Ei&3>xp^24Ij7|6e4^gJiL>oV(z#X@~A@%3B`OoG_))OD^ z9r3bBP|-c5Cu&<&kR5HE8f}{s0e}>^exdY(ae_;^XQf!I9+&}#5x}rvZ(6Fj89z+L z4?ctiuP(}YT;2+=y|d9a%YSD~(<2BGY*AjD?7V$+8|mVXsrrL7=k7D;j KHVqHmANnVdgg7n$ literal 0 HcmV?d00001 diff --git a/m5stack/fs/system/stampplc/setting/wlan/submit_unselect.jpeg b/m5stack/fs/system/stampplc/setting/wlan/submit_unselect.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..5ec14b0165c61155c4c38b4a02174f3aa1ec3974 GIT binary patch literal 1263 zcmex=PKf)jbbR;ta6Egz>FtTwlv9qvp0%bJ?7#LZY7@63Zn1Ox)0Y)Zf7FGr}L3TwB zAz?$uK#@k#!inNQr6M3NqXLkqAg1Uo1|FbkOoGgU4E7Aa&+f~ge*5c}*WI0$WHWE_ zYq7A}JnW1W*s>6NW53aq#V431_aqo1qglt7{5mYn?ti;qI=G#! zcw2Z|-Ou`^A@8I%UzjZ0EZd#EQux;93$4Ku=W|XKQtmw^v|sh=N=**A5Wl&vKW>S* zRTaEq?^U)e=~rCJCu1tDmAB_?n|bhg}tP9)5F6=n6Cw`$s~Z)HbjO_sIhU+DE_w&Kb+ zI!;FBYB~QICc3O%XqGkWdP<;0_Ji$yPrBKxmhPK>_sw4U%etkuM(4gA-Fv(~>D#fk zCf}ZI4>rawwG2DEWQSpFso>1$ZF!D)!Hr8ai>Dl5^`FDneJodV)_vc|b@!FuOu4$% zK-KZX*J$>n#+buJTeW4UFB2;(S|90>cH!~Gv{<)({nV}|(Tqb+_HEjEBJs-a%3AxE zORImk);-f$m6a*^;n|Vys*0SI`lq&CS|^?}!|a0Mo;Ol|C*54}`5X7N;8)ghXS(_Q zQo9S4_eexbmc26Z_OPD&CcDaOZdG(qQ(&mGWXF{rXKxQ5ajl-hm)^}vQjrsm_RqYo z)7+N*-s<-|ol6B0#|$7y@}yRf$kc^iPM4e}ZJ9Jx)!~!KR3;9E?<@k2-_BVaxI9a| zvvzjy^;=6e<=kJT{jN^s=DS^Aracs!=^ZHcY0l>z)56-lvm&)Pq5?cQCC(i0;okOh z;r$Hr%PZe0oXA|Z#Ab%@mD5eCJr0i!3nkkHNEcY#V9j7Zck9o|Aa{mYeufRl5U5dM z0tZV&?Ew|$zy0&?*_?WQ_T=;Tlh6Nt-;-czyL;Z<weYxl^8w|_2bpiw?X%lw*u$vuds21bozFL_`TjFVN*rT`BTH4Ss7WoV-ccQ1 z0TV)&YRqE_n!u?t?*N0R-Hl^xvo7p0$+?}j@9N#jZH+E> literal 0 HcmV?d00001 diff --git a/m5stack/fs/system/stampplc/statusbar/battery/black.jpeg b/m5stack/fs/system/stampplc/statusbar/battery/black.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..1872066ef42bf3d5695ae8f5cc5883d4b4a3d31f GIT binary patch literal 1108 zcmex=ObeS2I7?=bZ znFSgDA7Ky&JB|Sfu(NV-0VUJ~7#P?Y8JL(DS%KaF0Y)Zf7FITP4nZLXPDNp5BS)t| zkO7Pg%uodoWem(LY^*>D9YK%|R(2L{zCl0J!FW`$A{uaK+BUQtnT)B1I@zGXXXj8igyzp+;FYU!#Xt}B{< zwN;i*^RhMPWSKSLZQMpjOZGL%M-M+q7PQzs$+#fp!m@&0bsQgiZyi2r%X)WWmAzTX z)qBbXFJ9bTZn-nLGV7OaSlo)=vS%~3wiW#|@)cd2w)ok~BG*c}kDHP>^e%>kgx?JC zEe`)Jwmvh2TU1V&@wfQdyAiXN%dOov|7?)w&E(`O1rts#=X?43>E-^aUp6v^ZoWdy zJ1$$WujL8nP=6BbDxIl)WKLI)ka)XEb4%UYtp_Ep{jd$)tG4j)D!;gGiPlBmYV}uo zO*Bj9D-iq7P^2T#FAXnV!CWO+;dwm1(P#X|hUUu|hz;`?iY4<0hZVzV3hexB5263rn_gJpJrmbYbG|xA6tf=d8|Oeri&dy7$t<=iK&bn?C-}5c_HE!7V4c#a)!%Humf; ze>=s~>4#QFBUkd2BZgbJ%Q<%Xh&ZN9Q~xNuq-f5*1B`FtRR#GKpMR38V|Zwx8_)fG z&1U}Phs`^>&b<^U-KNWW>Jro0ODVorE-kHl^UAV7FZGVc9;;>c?_`UsrY!lY7ObeS2I7?=bZ znFSgDA7PMSU|GD-prj4aFyKnRm&WnyMwV`S&x6cl0*7EuQ2W@KPu zMrZ}Hm|5A_Ie;?Sf%TyKH zoxBON8y}r*I-H_*P=2QHOo>yUbtWx-B;vBl`R%X3m%VQjAI)oecd%N1wpG?U<%*Jm zyO-DHMdgR>-rE|JJ+v!R*Qrk-<`kwbD-Lr#qK*5*G$f_S-E+BPR-53ACrM6Th{&{$L(s8rrr*1zg;(Igo?AxuaN%`52eoxgB z&5Ec#;dcMC=OJsi;%Qwkt*30X3Sa)GJ7(*Ls@1G-s;gziDqI~*H#D`|SVupy3=H%Z z+Gb|vbMu;*!m()6b|u#)&&WxR7Jcs9Dw^C)K9&8t|7-vH>cqLtD@PhIRqH>);(M#CIk?$hPHPBz7+-(lW7J2B)CJ3`bxl?VhN^L| zH<-!u^hoNpx((a@UH&qs`uG#=tcPAYc_s^sw+eMfwkIsSsAW>sIVr~|rq}zMxUAhx zJH|^A1=FO9lAn2bdJ9E`T(#OZx#i&zRkLMFu3n8bT(9-(+Dv{y#xS<6oJ-epo>?UH z`QPNz0;QV%?-l%#?#FVY5SASr|qg-@xFQ8!4>x#t69Px`BZPn zlvVO6Xc04c6tp};!gc$to~barVHY-Y@s|^Gm|1Ux nsn$-}`-8Q!NziMq)73ObeS2I7?=bZ znFSgDA7Ky&I+B?I8L%^OZ~-MW1Q;0D85o&an3#dy00BlOW)@a9b`AzXA&H3TSA`Xg z9GwDzDkT^hm{5&iWMyXO0Lo|z0yVL)vM{njOkiLT77_tkEXu&ds;JDuCMK?AXl&-x z`2Q9I56~7SL1sY)dxp#JLMM6^)ki-~X}z6wM_ce`*@ffA%fdcq-=XrBp_w>GSi!P6CzDDYg7R>#Af!F_zx{h7WlwbcEHmxlS?vc8$t^DrB zx`*lgSGKi9r5c@_eNkkmgU9u|XR9u3@|P*8YUlhmeOVW|)1iq|=g!Ny{>qK= zwe=x?_kKG3(9(ayOP3X2I?p__IXZ8XXnb8#eMg_m<&@)EWy_4eb-zBgH+_1YbN##a z@7j;I{M1`>GJZvbX@;Fx13#CcEyI<+hl&nAJL$jvh47S`5E=Xas%Lv$p17+A?MOE{ zX`Ne2lcl4k#q^8q*$O8UHI_Wxbo67O@(k-K)f~3J{;m97{j~j1(fq^< z>KZ@8tn1~ro@whmUgx*E;Nr_krCJ|+p1fPA#Hnh)xy>=sGJR7@w%k!cgH=y29sB61 zByqQ;nxXUK3r35;ry>UzoK{k5xm>Dc8SML7B1!N0*ZAJ=tW(8jtF6vRtgkIjykd~1 z7w&iDPQB4W>&s&DW;YZ{*5tLWxSqnPcIsnf-{gcB(yPvYwhk0<@;P+%pw}vfbB^K} zbB(8O^qV2TlQd(QnAtnQq8S=ZCw2TEse3%NtZ86AvG*Asx z%Q_hr*uOaZuP^c6nc{N8_J92ee;L?)RRw}hP3l(^4w+~WmAxx8D!i(;up%Se+iYSL f>!Yj7V`oj>dUnb3w`WhWy9rjhHPx){|9=wzt!PVS literal 0 HcmV?d00001 diff --git a/m5stack/fs/system/stampplc/statusbar/battery/green_charge.jpeg b/m5stack/fs/system/stampplc/statusbar/battery/green_charge.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..f0f50e9bdeb69c23e8c5d7347538b66147fe4c25 GIT binary patch literal 1044 zcmex=ObeS2I7?=bZ znFSgDA7PLHIhC0K39xf8uyO%qR0S9q*clj^SwVgQ14brh7FIT(yr7VXM8x!~!pa~6 z7#WyQ^fNHBGP834Wi$mD7+9H^*ce%1rh^O+Vigu)6J=l*6jM|dS28wpYW#nTfrlAr ziXgKfgFVB`?^-98TH3Guw8`o1);!UUKf5!MXI|E>-1^olho!W3UB2h$6`S5l8icxU z%2#=F-u3ix?K!S0(P}e=KL+^ye#5r>lkhb8GOxONhsdzqn#Xwar%wEy6JD_S_^W7V zU89*PXJ2&1EKtdQx4FtU)+Ma$hx^6``60^ zKVR-;yMNAVu5xF|*=fs7uK8VF7j{!|?xM73d5b=4n=1)_&N^dS1)G}XE;Psz){mPxBlh#pc}$Qn}9 z@bc&M6ASNH^?bFH2vM(C8+}wOWY?*(S$q992CVY;+?ubh9HO+@@O;ks8NPO!PlP=} z!@^t-3KUG7$9ZM~15>MJcFttIRgWc~#+xoPW}6V&ab>1Taii;%=8gXu+&^97nzHoY zC!u`4&BwF8F^Gl=sfAor%A9n){HF40Su@?EFH3)#vZicZ)Mq(sy}z}V=fo%nrd_(y za~fJLnR$=i)s84y*)c0aNq0;B8O1ZZR%C5hG~apt^eC0H=S~-B-g@<=KTNf7v(t{w d@X*^+Wvo{02(a3^Y~iw5f~GQB9}E891OQ~Vcn1Ig literal 0 HcmV?d00001 diff --git a/m5stack/fs/system/stampplc/statusbar/battery/red.jpeg b/m5stack/fs/system/stampplc/statusbar/battery/red.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..f07f9a2494f4a67dc8a495617f0c7c64bdd5a831 GIT binary patch literal 945 zcmex=ObeS2I7?=bZ znFSgDA7Ky&JB|SfuyZhS0VT8q7#P@?7@3)wS)r1QOw25-Z0sC@3__9-)2|9EI*BV8 zIR=6ZVPs$i`V|QF&O$|~Zt;ILpH%)6_R`l`Ib3s?JlgnIw`>8LeskyS(C3pKiq5ZN~{&s%)vu_4@0>$HHSHw%}tMlY>%+w)oK;;T~%yN~H#3Y@Z7(`)yoooP>*IFvT03R}%R z#i*gLu+`r0cK(}^?I*r$*WKW=|JJ&~zJ>d4p8lhgs!Zr1K-jb0~N|GR_(1@zv1uc`H4R1{j;X8=-u}HQ266r47Ymy|MFa~**ha6T6#-n zOyO2Nzq_%LYqq^AlKE8dVD=QD$sM&4Wt(`ygu48q*2u0}QY=0}jzxnX$sLSar4uYq z$8P#2xA66}iz^FHitM!NEm8X@y~Co&^9)n&gs`C4FEgc%Z*=j|+F7!3caX<2b0!;h zXDeGJ%av|i>Q*TM$8|gco*e60Aiv|Vbk6!Y$$PdTw7iGnnoS)#laBmTD(MTJzJtNK`=pEWgy-*MzF2l|_`{}q*hD8Z zy4%UJ*J!OU+gZU#_ok~wj51yt;e9@(SATudwk=BT{LfH1RqldjwHFW1%tZ%R=}J%E zYU07l_@~Ey;u-s!eR)2K{~6pHelxK97@eEb#ndw8snDDa2K)+%j8E(>W}2HyJ-uW0 jcy`(LRTtuxeCqknuwv=%{MS2^npObeS2I7?=bZ znFSgDA7PLHI+7XaP6Xg!WM$_9%BTr2FtRW)GP45pLS&hlSXkK@**O>lg+wGHre75n zR|c8D$iT#m&<ZKqG{NMFd5~7}&)Xm6TPCIUF1R z-(uhaI)X`%S&+e=;pOq*SDbM{Z~sM}xM!aCWwraOwYN@(Us@e?>6ce*oz0S~H_+xxUQY$`ejEk{$kpPhC*EFDqWjGWUDS`sXEeSAN%pcSqMxE4*H- zyjR!ocX-*RTOzylwk(+TGJfKEn`>LDHZqj=#CP;8mGla|xAe5Rf?I*GnZh-Hr9=+> zf~fh&^P>OC>i)6~Ti&~H{on0jt9DPVUB3LM$F#XND}`P@UG7_R`q8}-`-aRTKh{j# znsXRo{O(W5pO*GY&ja6n4*Ds0I)9I9z4aRAsWSfm8F(J=yLZP# z&+SuBxNNTXHgwtjHmh{(klyZ=V!i>m)Ys&-FIT?$t&4AW?#vB@bIF#VdFN&RMq>Q3)fT& zJdMQ(Ibya|m&B~v<F(n> zvzYh4n-iXM(>!eUy2sTw4<6V%z5Mp^b^HDHhUD!lzOr(T$I`r6Q!YnZyWLxB#J5Xm z_S&8NMrkit{@hHCRaF(KjlCwb@)}qF^Vdd4=TF)j@oBlN+}FSx0Sfb%MJcSk7*{vp z($<$|MQ4KkC<)Cq4W6cIAoRde$td7wV)AGINrm@idCcm$SAMn$TJw^tRd18FYuM88 z?ro}$CM?!D23YW?i+%b&W<`kE4p-VDcyZ!%70NIFz Af&c&j literal 0 HcmV?d00001 diff --git a/m5stack/fs/system/stampplc/statusbar/cloud/empty.jpeg b/m5stack/fs/system/stampplc/statusbar/cloud/empty.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..2647f691e2759ccc43b7f7943fab170f05f1a784 GIT binary patch literal 621 zcmex=O1eh6>7?=bZ znFSgDA7Ky!IgSAtaIkX$#T5h?n3$Lu8JL)1axAPsJ{vm+r?8NSC{Tqcx<&?8cA&7F zAW#poJ_aThMrKwvAz^k=rT@1WctDbZ%z_N|442~92!7LBz3a8N$^M|n=WJX<)3op2 zUE^!8Eq!)r*lZo$Yul_P`v1nSYTW2~B{-=uD6Knx>bj17PCOwW%=2G6R!v|3@v!y0 zlP~S9Up>6AH$LVDgUZ6|@7$-b2(DY7bawe;u}yR4=={n_bI?g+g@D68y+6Cy8Cd8!G)WL(srjty$U~&eL~~?wWL*x`Qp3RENz%>aALxm zigT;}GjJFAu6E4SE4^8oDwr(pzj2Z$!hRPi59rjV+fht^a=$ E0PjV!1^@s6 literal 0 HcmV?d00001 diff --git a/m5stack/fs/system/stampplc/statusbar/cloud/error.jpeg b/m5stack/fs/system/stampplc/statusbar/cloud/error.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..1850f45ead00958056653b9de7889f350dc0f1fc GIT binary patch literal 585 zcmex=O1eh6>7?=bZ znFSgDA7Kz@U|>R?PLO^*@WGS#3Rt})B zrXT|Y3o{EhTo(hQppXcIm^ibrqNAv?in^h*TVhIT`u|%DJV1+?1epaH>=`cRyM8sh z{`~JX-LB(0%yT+Z>x8E37Imyry{)!#58qA~Ij>Egc}W*pihGv}PikffO89d#alx&U zaJw1Lw820& zj$z53OGi%Md%Dae_2J_UYo6);44-br?VMxjTP4xFeDc~!mfLoUCeH3xdh5LLz!E0s z_Kp&pIXAafeimt)C8+ni?N0UU;06B*XF6}2uH3rW`+?%KIDztDQ#r42UYAhmbxZGZ YN2$bgg-!jS9~kq+@duCHw8H;40e|hHCIA2c literal 0 HcmV?d00001 diff --git a/m5stack/fs/system/stampplc/statusbar/cloud/green.jpeg b/m5stack/fs/system/stampplc/statusbar/cloud/green.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..223732358c768b2fc40363949954a2a88882710d GIT binary patch literal 640 zcmex=O1eh6>7?=bZ znFSgDA7Kz@U|aRXq z@LcYJcV6;6CDAXvmrGtIFAkYnCAG7a`$X3@mp{joS1sMXcwO}#jpSv@l02@q{k%#F zQitLWUQ)`M`?-3P(O=_-aTBsVmNpmftS+P1jNYyBxLPLB%M1}MN-~L(u(5&| zWtAmF+Zc=>qJ@Z2A|aRw6K3Upxc7eF_x*I9_dUP!9OfJ5J3zn^Wq|@95CDLj8(=N~ z#^=*N(|5=}Bb;wD`v8yw5CCkU5Cp&iLZBdoIS7aY0QB7dCpH)dJ15t9q$B`9*v=n; zutK10|0%FSSfFg|Fb)vl6ciE`5xszrQ`0a!uU>!v(C;t*PwX&GHjeWI0Rk-FBbNVO zfu2*oLxe#g5kL^Zd<*bE&vOt80tNv1Awm|txjWk>>GZInJ3j9hN&<#Yv<2>5sshtB z{TO>W#W5vJ%~h9G+KA#Rjr3?~jV}gsY%lkPw0apVC#1)w^8{$M`FF~>iq7<33>>Jg zk(i%z@aUjfY7A)SPfp(9#1zAiJMPWkgo%2&+k*%EfsI?R4cX-Y-iZ)ZMpEKOqP;Th za=4AP=q=UX;hdIismAhwteJ*LfvkI@U_-*la4D7+$KidA_rnVE*V8lEip3+tt73*z zN1oPBnZC(Y4fSpz2@?IA@BIB^d81a+C?ZNGl1cAj2OL2!mXo9Ax4XCwi=5RnD>KSJ zkhNe|ofaC?kw&LR;})O!4Ieng40iJ?sqG8gXhc!ZQ^f_MkmL3;gSw>X{hGgYwNgGY z*q!SV(Eb+LjJSM3@9_P4CIv;f53euVYjBDIucTyWssCV56sjM;!LN&Lobn~BEg_qi zre^kTl3q91OvgmWqra9*ihxdsq$cOZg8IC%oPM9kBNhzaNZ-H@I>AFY9noF+G)&B# z)vr1nLjKV8m)L^dmVwL*)x-ps&&|3kE2$TiT_2<9*+1SGEs!72GI;fDr1=bfmJKR+ zDbo2Dsa;3Wv|s z;TL3YK~gb~O>&pQ%Sv*jXeNKkUHf8VF8?^r>@2m-gaa$Qsy~$;{G5-Cc!Gal^6%lP z|I~M9!y2SbBj4(wnGKdf3}H>sn*bSnKM~ z1kk&*%Z<;|ODs z4Qg$9Xn{|jtZX*9Y#O$T!(w!7wLTxsY1c>S&Qim>x4vDCce1PE9y$?oscrr1@M=?L z>3WIk;%Nh(UA16r4JDxt-f2IuZo9f-n;Uu>=JDcYsh>`&GP{JZS)jcpCy%qoY`FhG zm@+{$qAmL2m70`oFk5C$%6U{T;dNgIz0LGepgkdmwdTf}?zV65W?1cB|8@7E{i#*| zI)x)BhHO)4o9%K*qj)BoOiUl&B{6sy!9krX&V{)l9u|*KSWi}$u$<=VXS@@x`N>fv zQ_E=M=C$nOtiq8Q71q1-gCX3I7oOC%er%&P9a3GpL6b=AosVzLK-4Qg4fVZ~60Y>p z@9;J|gHt|BiwPinN3k+|bwRm$lPIq>`jKLTl8&hYhSgaog^>2~gi?P46s40^rUGLA zxIej%U;lIAs9*Nb|z1@DnA zNsyAc6+yRncPtpIaP9t~w_*RmX;H;t=C8$e2mO2HdlQ;HPa4OTIHo6D#{n(9z*`4N z($lpSLGiN_aAHX=%$=g%z`yC^@%lk=q%~o`P~drt-x#cI3A-Zi5)%>#;iJ zZk4+nP_%>n9L&+tP0~8e4*7H#Z#O8dfY2;3WVIq>5x=e!qtWP-n@P86B`?RH3}QN# zuyI$e8yLmpZ|gVu-a@|M1)?qM&Wfx2k9ORWV}JX3WQ{=a$zOXmjOC_yQ{pDs4m#;p nDxSgL!~PT7s_OrKR-^{wB~g`8;o?vG3WmVnAT9% literal 0 HcmV?d00001 diff --git a/m5stack/fs/system/stampplc/statusbar/wifi/disconnected.jpeg b/m5stack/fs/system/stampplc/statusbar/wifi/disconnected.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..833b16b7f982b94fe9203adc42212b7183ab62bc GIT binary patch literal 582 zcmex=O1eh6>7?=bZ znFSgDA7Ky!IgSAtFtTw0#pMMU7+IJ>mcry%n3!4F7}+^QgvEq_3Ph2lAWB#{fWn%B zKn^ztGfWpS7zBlcMMTBKl?($J6jjunUEC7WTU!6$V&GwB1iFJ+kinke(tejO^R7SL zdyRE{LXYY4DY*rXtSVofM4#u~N-7Wv(SNmOhWkRJE5A9@j5OI+IzLhW^Ty~hH*dxC z%IizM9S_Re$(^@PqH4QF$U|NxlkO7bv$5MvJ}yzozjEzA!+xWrEYU|TC5dOfI!^XK z{T7$`bw-biUzy*Omww$l7fpH4z<%m#;_A!ASG&&qXE^tH_c9O7qtiJRr~Z)cY*_QO zt*c5kb+OQ?(|%@wc@u6a$(dc6t9@>}>805M%#~4BOxyCX8EP-TCfDOgJ@Tkw$fOsASCR9ojDvGuR@h S3D`M*38~`x&!Bqx|4jg14W3T` literal 0 HcmV?d00001 diff --git a/m5stack/fs/system/stampplc/statusbar/wifi/empty.jpeg b/m5stack/fs/system/stampplc/statusbar/wifi/empty.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..02a2729382f1437b4608355753462a85b019dec2 GIT binary patch literal 620 zcmex=O1eh6>7?=bZ znFSgDA7Kz@U|?@OWS&^*dDC1Man1Q#^G)wp{mNbc z8KmN`ebaaUP^(y$p7(wCqaB<4Zu5wD`YPBo`f3~8h^#hz&o`}YeTLD?4eZMGTF0{7 zS0B0C`!M>dmD-NmMVV8cC)_nq`b~18{x**kMW^yUoDDrA>_IoSlN3RSn3!5Yq zX-uB8deQ!tTidrydKx=DERgN!(Jv`22NJSGkEEprtm)X-W>@%=qwJqKA>xusi%iJAKd4|UxyBq)i FCIA7=wb%dv literal 0 HcmV?d00001 diff --git a/m5stack/fs/system/stampplc/statusbar/wifi/good.jpeg b/m5stack/fs/system/stampplc/statusbar/wifi/good.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..2c5b10c1b3cfa3f127e85d370047857065a4e5d4 GIT binary patch literal 583 zcmex=O1eh6>7?=bZ znFSgDA7Ky!IgSAtFtTz1#bpH;n3%zK!sM7)m{{2u*+n^ofYKspniv?^fud@H42(>y z4BRj+3=AT|qKvG9LW)4cSj5DYRaDiC{@-HYVP*unfLV~ip5a>G`_T3A-d5eYY+cj* zPF{LiQ0MEgZgzI2SGP=9)LhxOvwA}>Uf)uA#CxSec#H9jgW7I)9ySScuDr2vX>X`o zvuC*o*Zk5M@e$|sXWqOTViVV7RrB;~QC8@^>#OqW*@GB2a8?U3iDg?}WV!aA!6&#% z_4%A$d#iU1z0U4l$ENL8E&N~_cGFPce%QXI`+ebp?rEMHNd;mWMu!qJQ*TLj^LhR` z@B3m(U4g5u^!7)2Ro2UlOK;t--@j_*$tjC|n*QRh+nMAp*Qs>z_~9Eir(VuYjYwI_ zv-?Q*(>r3XADq%$yU6PH#I2i_Em3@_I#parI&MqaPbQUVXLq)oeACGC<#)=#?eBU| U&$+ehhDTz++ZhEfm;b*B010QKCjbBd literal 0 HcmV?d00001 diff --git a/m5stack/fs/system/stampplc/statusbar/wifi/mid.jpeg b/m5stack/fs/system/stampplc/statusbar/wifi/mid.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..1a5293e3821b43a08b4215b57c539083de647d9a GIT binary patch literal 572 zcmex=O1eh6>7?=bZ znFSgDA7Kz@U|}*A& zt9s7Np5M3hUe(B0=Y86p$1?oNb)U~2?B*ODR+%mW8E;&gCA;p($DOT%zB;elV#L@>R0g&wZ}Vm$C0aO6RYn*%$R~o@D;KUc!@zO1eh6>7?=bZ znFSgDA7Kz@U|KKmaofBNHndyO1b{AW&WeApwvcOArU5FK}7~-MrM}(w-|VUMluO93o_U<+-!HeIN9`L`kYxUrMo0{{;0WD zf9a}Kf(q9yy$w-bD^@2(EnYM0bpO4CQbX_eUUR;?`Kq7ldU91d^ML2#&dce3X)8oc zmrcFk+7+~F&sI;l=@TLwtB>^gDV^2KkiTT{&+*R3r@|lKZqK`GB~xalVi}v?ekH5u z@XmdUEUY%%VhOxG>C}t~;+GO$u}(d3;#AashWKZH>J=XScz3FjuiWkFFWENBH~*GL zl~>j5i&!Fl+PysL>^`@kI1^|Gx None: papers3 = PaperS3_Startup() papers3.startup(ssid, pswd, timeout) + elif board_id == M5.BOARD.M5StampPLC: + from .stampplc import StampPLC_Startup + + station = StampPLC_Startup() + station.startup(ssid, pswd, timeout) + # Only connect to network, not show any menu elif boot_opt is BOOT_OPT_NETWORK: startup = Startup() diff --git a/m5stack/modules/startup/manifest_stampplc.py b/m5stack/modules/startup/manifest_stampplc.py new file mode 100644 index 00000000..7dbe0b24 --- /dev/null +++ b/m5stack/modules/startup/manifest_stampplc.py @@ -0,0 +1,23 @@ +# SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +package( + "startup", + ( + "__init__.py", + "stampplc/__init__.py", + "stampplc/app_base.py", + "stampplc/framework.py", + "stampplc/res.py", + "stampplc/apps/app_list.py", + "stampplc/apps/app_run.py", + "stampplc/apps/dev.py", + "stampplc/apps/ezdata.py", + "stampplc/apps/launcher.py", + "stampplc/apps/settings.py", + "stampplc/apps/statusbar.py", + ), + base_path="..", + opt=3, +) diff --git a/m5stack/modules/startup/stampplc/__init__.py b/m5stack/modules/startup/stampplc/__init__.py new file mode 100644 index 00000000..eb083d80 --- /dev/null +++ b/m5stack/modules/startup/stampplc/__init__.py @@ -0,0 +1,46 @@ +# SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +from . import framework +from .apps.settings import SettingsApp +from .apps.dev import DevApp +from .apps.launcher import LauncherApp +from .apps.app_run import RunApp +from .apps.app_list import ListApp +from .apps.statusbar import StatusBarApp +from .apps.ezdata import EzDataApp +from startup import Startup +import M5 +import time + +# from .res import LOGO_IMG + + +class StampPLC_Startup: + def __init__(self) -> None: + self._wlan = Startup() + + def startup(self, ssid: str, pswd: str, timeout: int = 60) -> None: + self._wlan.connect_network(ssid, pswd) + # M5.Lcd.setRotation(0) + # M5.Lcd.drawImage(LOGO_IMG) + time.sleep_ms(200) + M5.Lcd.clear(0x333333) + + fw = framework.Framework() + setting_app = SettingsApp(None, data=self._wlan) + dev_app = DevApp(None, data=self._wlan) + launcher = LauncherApp() + run_app = RunApp(None, data=self._wlan) + list_app = ListApp(None, data=self._wlan) + ezdata_app = EzDataApp(None, data=self._wlan) + fw.install_bar(StatusBarApp(None, self._wlan)) + fw.install_launcher(launcher) + fw.install(launcher) + fw.install(setting_app) + fw.install(dev_app) + fw.install(run_app) + fw.install(list_app) + fw.install(ezdata_app) + fw.start() diff --git a/m5stack/modules/startup/stampplc/app_base.py b/m5stack/modules/startup/stampplc/app_base.py new file mode 100644 index 00000000..51b9b6c3 --- /dev/null +++ b/m5stack/modules/startup/stampplc/app_base.py @@ -0,0 +1,93 @@ +# SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import asyncio + + +def generator(d): + try: + len(d) + except TypeError: + cache = [] + for i in d: + yield i + cache.append(i) + d = cache + while d: + yield from d + + +class AppSelector: + def __init__(self, apps: list) -> None: + self._apps = apps + self._id = 0 + + def prev(self): + self._id = (self._id - 1) % len(self._apps) + return self._apps[self._id] + + def next(self): + self._id = (self._id + 1) % len(self._apps) + return self._apps[self._id] + + def current(self): + return self._apps[self._id] + + def select(self, app): + self._id = self._apps.index(app) + + def index(self, id): + self._id = id % len(self._apps) + return self._apps[self._id] + + +class AppBase: + def __init__(self) -> None: + self._task = None + + def on_install(self): + pass + + def on_launch(self): + pass + + def on_view(self): + pass + + def on_ready(self): + self._task = asyncio.create_task(self.on_run()) + + async def on_run(self): + while True: + await asyncio.sleep_ms(500) + + def on_hide(self): + self._task.cancel() + + def on_exit(self): + pass + + def on_uninstall(self): + pass + + def install(self): + self.on_install() + + def start(self): + self.on_launch() + self.on_view() + self.on_ready() + + def pause(self): + self.on_hide() + + def resume(self): + self.on_ready() + + def stop(self): + self.on_hide() + self.on_exit() + + def uninstall(self): + self.on_uninstall() diff --git a/m5stack/modules/startup/stampplc/apps/app_list.py b/m5stack/modules/startup/stampplc/apps/app_list.py new file mode 100644 index 00000000..afc3c6e3 --- /dev/null +++ b/m5stack/modules/startup/stampplc/apps/app_list.py @@ -0,0 +1,297 @@ +# SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +from .. import app_base +from .. import res +import widgets +import M5 +import esp32 +import machine +import os +import sys + + +class Rectangle: + def __init__(self, x, y, w, h, color, fill_c, parent=M5.Lcd) -> None: + self._x = x + self._y = y + self._w = w + self._h = h + self._color = color + self._fill_c = fill_c + self._parent = parent + self.set_pos(self._x, self._y) + + def set_pos(self, x, y): + self._x = x + self._y = y + self._parent.fillRect(self._x, self._y, self._w, self._h, self._fill_c) + self._parent.drawRect(self._x, self._y, self._w, self._h, self._color) + + def set_color(self, color, fill_c): + self._color = color + self._fill_c = fill_c + self._parent.fillRect(self._x, self._y, self._w, self._h, self._fill_c) + self._parent.drawRect(self._x, self._y, self._w, self._h, self._color) + + +class FileList: + def __init__(self, dir, suffix=".py") -> None: + self.files = [] + for file in os.listdir(dir): + if file.endswith(suffix): + self.files.append(file) + self.files_len = len(self.files) + self.file_pos = 0 + + def __contains__(self, item): + return item in self.files + + def __getitem__(self, item): + return self.files[item] + + # def __iter__(self): + # return iter(self.files) + + # def __next__(self): + # if self.file_pos < self.files_len: + # val = self.files[self.file_pos] + # self.file_pos += 1 + # return val + # else: + # raise StopIteration() + + def __len__(self): + return self.files_len + + +class ListApp(app_base.AppBase): + # log control + DEBUG = False + + def __init__(self, icos: dict, data=None) -> None: + self._wlan = data + super().__init__() + + def on_install(self): + pass + + def on_launch(self): + self._files = FileList("apps") + self._imgs = [] + self._icos = [] + self._labels = [] + self._max_file_num = 3 if len(self._files) > 3 else len(self._files) + self._cursor_pos = 0 + self._file_pos = 0 + + def on_view(self): + M5.Lcd.fillRect(0, 16, 240, 119, 0xEEEEEF) + + if self._max_file_num > 0: + self._img0 = widgets.Image(use_sprite=False) + self._img0.set_pos(6, 22) + self._img0.set_size(228, 32) + self._img0.set_src(res.CARD_228x32_SELECT_IMG) + self._imgs.append(self._img0) + + self._ico0 = widgets.Image(use_sprite=False) + self._ico0.set_pos(9, 25) + self._ico0.set_size(26, 26) + self._icos.append(self._ico0) + + self._label0 = widgets.Label( + "", + 40, + 27, + w=182, + h=22, + fg_color=0x000000, + bg_color=0xFFFFFF, + font=res.MontserratMedium18_VLW, + ) + self._labels.append(self._label0) + + if self._max_file_num > 1: + self._img1 = widgets.Image(use_sprite=False) + self._img1.set_pos(6, 60) + self._img1.set_size(228, 32) + self._img1.set_src(res.CARD_228x32_UNSELECT_IMG) + self._imgs.append(self._img1) + + self._ico1 = widgets.Image(use_sprite=False) + self._ico1.set_pos(9, 63) + self._ico1.set_size(26, 26) + self._icos.append(self._ico1) + + self._label1 = widgets.Label( + "", + 40, + 65, + w=182, + h=22, + fg_color=0x000000, + bg_color=0xFFFFFF, + font=res.MontserratMedium18_VLW, + ) + self._labels.append(self._label1) + + if self._max_file_num > 2: + self._img2 = widgets.Image(use_sprite=False) + self._img2.set_pos(6, 98) + self._img2.set_size(228, 32) + self._img2.set_src(res.CARD_228x32_UNSELECT_IMG) + self._imgs.append(self._img2) + + self._ico2 = widgets.Image(use_sprite=False) + self._ico2.set_pos(9, 101) + self._ico2.set_size(26, 26) + self._icos.append(self._ico2) + + self._label2 = widgets.Label( + "", + 40, + 103, + w=182, + h=22, + fg_color=0x000000, + bg_color=0xFFFFFF, + font=res.MontserratMedium18_VLW, + ) + self._labels.append(self._label2) + + for label, icos, file in zip(self._labels, self._icos, self._files): + ico_name = file[0].lower() + icos.set_src(f"/system/stampplc/ico/{ico_name}.jpeg") + label.set_text(file) + + def on_ready(self): + pass + + def on_hide(self): + M5.Lcd.fillRect(32, 26, 206, 103, 0x333333) + + def on_exit(self): + del ( + self._imgs, + self._icos, + self._labels, + self._files, + ) + + def _btn_up_event_handler(self, event): + self.DEBUG and print("_btn_up_event_handler") + if self._file_pos == 0 and self._cursor_pos == 0: + return + + # Clear selection cursor + self._imgs[self._cursor_pos].set_src(res.CARD_228x32_UNSELECT_IMG) + + # Calculate cursor and file positions + self._file_pos -= 1 + if self._file_pos < 0: + self._file_pos = 0 + + self._cursor_pos -= 1 + if self._cursor_pos < 0: + self._cursor_pos = 0 + + # cursor img + self._imgs[self._cursor_pos].set_src(res.CARD_228x32_SELECT_IMG) + + if self._file_pos < self._cursor_pos: + for label, icos, file in zip(self._labels, self._icos, self._files): + ico_name = file[0].lower() + icos.set_src(f"/system/stampplc/ico/{ico_name}.jpeg") + label.set_text(file) + else: + for label, icos, file in zip( + self._labels, + self._icos, + self._files[ + self._file_pos - self._cursor_pos : self._file_pos + (3 - self._cursor_pos) + ], + ): + ico_name = file[0].lower() + icos.set_src(f"/system/stampplc/ico/{ico_name}.jpeg") + label.set_text(file) + + def _btn_down_event_handler(self, fw): + # Clear selection cursor + self.DEBUG and print("_btn_down_event_handler") + self.DEBUG and print("_cursor_pos:", self._cursor_pos) + self._imgs[self._cursor_pos].set_src(res.CARD_228x32_UNSELECT_IMG) + + # Calculate cursor and file positions + self._file_pos += 1 + self._cursor_pos += 1 + + max_cursor_pos = len(self._files) - 1 if len(self._files) < 3 else 2 + if self._cursor_pos > max_cursor_pos: + self._cursor_pos = max_cursor_pos + + # cursor img + self.DEBUG and print("_cursor_pos:", self._cursor_pos) + self._imgs[self._cursor_pos].set_src(res.CARD_228x32_SELECT_IMG) + + if self._file_pos >= len(self._files): + self._file_pos = len(self._files) - 1 + + # Show File + if self._file_pos < 3: + for label, icos, file in zip(self._labels, self._icos, self._files): + ico_name = file[0].lower() + icos.set_src(f"/system/stampplc/ico/{ico_name}.jpeg") + label.set_text(file) + else: + for label, icos, file in zip( + self._labels, self._icos, self._files[self._file_pos - 2 : self._file_pos + 1] + ): + ico_name = file[0].lower() + icos.set_src(f"/system/stampplc/ico/{ico_name}.jpeg") + label.set_text(file) + + def _btn_once_event_handler(self, event): + execfile("/".join(["apps/", self._files[self._file_pos]]), {"__name__": "__main__"}) # noqa: F821 + sys.exit(0) + + def _btn_always_event_handler(self, event): + nvs = esp32.NVS("uiflow") + nvs.set_u8("boot_option", 2) + nvs.commit() + with open("apps/" + self._files[self._file_pos], "rb") as f_src, open( + "main.py", "wb" + ) as f_dst: + while True: + chunk = f_src.read(1024) + if not chunk: + break + f_dst.write(chunk) + machine.reset() + + async def _btnb_enter_event_handler(self, fw): + self._btn_once_event_handler(None) + + async def _btnb_ctrl_event_handler(self, fw): + self._btn_always_event_handler(None) + + async def _btnb_back_event_handler(self, fw): + pass + + async def _btna_next_event_handler(self, fw): + self._btn_up_event_handler(None) + + async def _btnc_next_event_handler(self, fw): + self._btn_down_event_handler(None) + + async def _kb_event_handler(self, event, fw): + if event.key == 182: # down key + self._btn_down_event_handler(None) + if event.key == 181: # up key + self._btn_up_event_handler(None) + + if event.key == 0x0D: # Enter key + self._btn_once_event_handler(event) + elif event.key in (ord("a"), ord("A")): + self._btn_always_event_handler(event) diff --git a/m5stack/modules/startup/stampplc/apps/app_run.py b/m5stack/modules/startup/stampplc/apps/app_run.py new file mode 100644 index 00000000..f9d429d9 --- /dev/null +++ b/m5stack/modules/startup/stampplc/apps/app_run.py @@ -0,0 +1,176 @@ +# SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +from .. import app_base +from .. import res +import widgets +import M5 +import esp32 +import machine +import sys +import os +import time + +try: + import M5Things + + _HAS_SERVER = True +except ImportError: + _HAS_SERVER = False + + +class RunApp(app_base.AppBase): + def __init__(self, icos: dict, data=None) -> None: + super().__init__() + self._enter_handler = self._handle_run_once + + def on_install(self): + pass + + def on_launch(self): + self._mtime_text, self._account_text, self._ver_text = self._get_file_info("main.py") + + def on_view(self): + M5.Lcd.fillRect(0, 16, 240, 119, 0xEEEEEF) + M5.Lcd.drawImage(res.RUN_INFO_IMG, 6, 22) + + self._name_label = widgets.Label( + "name", + 16, + 23, + w=208, + font_align=widgets.Label.LEFT_ALIGNED, + fg_color=0x000000, + bg_color=0xCDCDCD, + font=res.MontserratMedium16_VLW, + ) + self._name_label.set_text("main.py") + + self._mtime_label = widgets.Label( + "Time: 2023/5/14 12:23:43", + 16, + 46, + w=208, + font_align=widgets.Label.LEFT_ALIGNED, + fg_color=0x000000, + bg_color=0xFFFFFF, + font=res.MontserratMedium10_VLW, + ) + self._mtime_label.set_text(self._mtime_text) + + self._account_label = widgets.Label( + "Account: XXABC", + 16, + 60, + w=208, + font_align=widgets.Label.LEFT_ALIGNED, + fg_color=0x000000, + bg_color=0xFFFFFF, + font=res.MontserratMedium10_VLW, + ) + self._account_label.set_text(self._account_text) + + self._ver_label = widgets.Label( + "Ver: UIFLOW2.0 a18", + 16, + 74, + w=208, + font_align=widgets.Label.LEFT_ALIGNED, + fg_color=0x000000, + bg_color=0xFFFFFF, + font=res.MontserratMedium10_VLW, + ) + self._ver_label.set_text(self._ver_text) + + M5.Lcd.drawImage(res.RUN_ONCE_SELECT_IMG, 6, 100) + M5.Lcd.drawImage(res.RUN_ALWAYS_UNSELECT_IMG, 123, 100) + + def on_ready(self): + pass + + def on_hide(self): + self._enter_handler = self._handle_run_once + M5.Lcd.fillRect(32, 26, 206, 103, 0xEEEEEF) + + def on_exit(self): + del ( + self._name_label, + self._mtime_label, + self._account_label, + self._ver_label, + ) + + def _handle_run_once(self, fw): + execfile("main.py", {"__name__": "__main__"}) # noqa: F821 + sys.exit(0) + + def _handle_run_always(self, fw): + nvs = esp32.NVS("uiflow") + nvs.set_u8("boot_option", 2) + nvs.commit() + machine.reset() + + @staticmethod + def _get_file_info(path): + mtime = None + account = None + ver = f"Ver: UIFLOW2 {esp32.firmware_info()[3]}" + + try: + stat = os.stat(path) + mtime = time.localtime(stat[8]) + except OSError: + pass + + if mtime is None or mtime[0] < 2023 and mtime[1] < 9: + mtime = "Time: ----/--/-- --:--:--" + else: + mtime = "Time: {:04d}/{:d}/{:d} {:02d}:{:02d}:{:02d}".format( + mtime[0], mtime[1], mtime[2], mtime[3], mtime[4], mtime[5] + ) + + with open(path, "r") as f: + for line in f: + if line.find("Account") != -1: + account = line.split(":")[1].strip() + if line.find("Ver") != -1: + ver = line.split(":")[1].strip() + if account is not None and ver is not None: + break + + if account is None and _HAS_SERVER and M5Things.status() == 2: + infos = M5Things.info() + account = "Account: None" if len(infos[1]) == 0 else "Account: {:s}".format(infos[1]) + else: + account = "Account: None" + + return (mtime, account, ver) + + async def _btnb_enter_event_handler(self, fw): + self._enter_handler(fw) + + async def _btnb_back_event_handler(self, fw): + pass + + async def _btna_next_event_handler(self, fw): + M5.Lcd.drawImage(res.RUN_ONCE_SELECT_IMG, 6, 100) + M5.Lcd.drawImage(res.RUN_ALWAYS_UNSELECT_IMG, 123, 100) + self._enter_handler = self._handle_run_once + + async def _btnc_next_event_handler(self, fw): + M5.Lcd.drawImage(res.RUN_ONCE_UNSELECT_IMG, 6, 100) + M5.Lcd.drawImage(res.RUN_ALWAYS_SELECT_IMG, 123, 100) + self._enter_handler = self._handle_run_always + + async def _kb_event_handler(self, event, fw): + if event.key == 183: # Right key + M5.Lcd.drawImage(res.RUN_ONCE_UNSELECT_IMG, 6, 100) + M5.Lcd.drawImage(res.RUN_ALWAYS_SELECT_IMG, 123, 100) + self._enter_handler = self._handle_run_always + elif event.key == 180: # Left key + M5.Lcd.drawImage(res.RUN_ONCE_SELECT_IMG, 6, 100) + M5.Lcd.drawImage(res.RUN_ALWAYS_UNSELECT_IMG, 123, 100) + self._enter_handler = self._handle_run_once + elif event.key == 0x0D: # Enter key + self._enter_handler(fw) diff --git a/m5stack/modules/startup/stampplc/apps/dev.py b/m5stack/modules/startup/stampplc/apps/dev.py new file mode 100644 index 00000000..4b5015d9 --- /dev/null +++ b/m5stack/modules/startup/stampplc/apps/dev.py @@ -0,0 +1,189 @@ +# SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +from .. import app_base +from .. import res +import widgets +import M5 +import requests +import machine +import asyncio +import binascii +import os + +try: + import M5Things + + _HAS_SERVER = True +except ImportError: + _HAS_SERVER = False + + +class NetworkStatus: + INIT = 0 + RSSI_GOOD = 1 + RSSI_MID = 2 + RSSI_WORSE = 3 + DISCONNECTED = 4 + + +class CloudStatus: + INIT = 0 + CONNECTED = 1 + DISCONNECTED = 2 + + +class DevApp(app_base.AppBase): + def __init__(self, icos: dict, data=None) -> None: + super().__init__() + + def on_install(self): + pass + + def on_launch(self): + self._mac_text = self._get_mac() + self._account_text = self._get_account() + self._bg_src = self._get_bg_src() + + def on_view(self): + self._origin_x = 0 + self._origin_y = 0 + + M5.Lcd.fillRect(0, 16, 240, 119, 0xEEEEEF) + + self._bg_img = widgets.Image(use_sprite=False) + self._bg_img.set_pos(6, 22) + self._bg_img.set_size(228, 107) + self._bg_img.set_src(self._bg_src) + self._avatar_src = self._get_avatar() + + self._mac_label = widgets.Label( + "aabbcc112233", + 15, + 63, + w=116, + h=15, + fg_color=0x000000, + bg_color=0xFEFEFE, + font=res.MontserratMedium12_VLW, + ) + self._mac_label.set_text(self._mac_text) + + self._account_label = widgets.Label( + "XXABC", + 15, + 92, + w=90, + h=34, + fg_color=0x000000, + bg_color=0xFEFEFE, + font=res.MontserratMedium12_VLW, + ) + self._account_label.set_text(self._account_text) + + self._avatar_img = widgets.Image(use_sprite=False) + self._avatar_img.set_pos(110, 91) + self._avatar_img.set_size(38, 38) + self._avatar_img.set_scale(0.19, 0.19) + self._avatar_img.set_src(self._avatar_src) + + def on_ready(self): + super().on_ready() + + async def on_run(self): + refresh = False + while True: + t = self._get_bg_src() + if t != self._bg_src: + self._bg_src = t + self._bg_img.set_src(self._bg_src) + refresh = True + + refresh and self._mac_label.set_text(self._mac_text) + + t = self._get_account() + if t != self._account_text or refresh: + self._account_text = t + self._account_label.set_text(self._account_text) + + t = self._get_avatar() + if t != self._avatar_src: + self._avatar_src = t + try: + os.stat(self._avatar_src) + self._avatar_img.set_src(self._avatar_src) + except OSError: + self._dl_task = asyncio.create_task(self._dl_avatar(self._avatar_src)) + elif refresh: + self._avatar_img._draw(False) + + refresh = False + await asyncio.sleep_ms(1500) + + def on_hide(self): + self._task.cancel() + + def on_exit(self): + M5.Lcd.fillRect(30, 19, 210, 116, 0x333333) + del self._bg_img, self._mac_label, self._account_label + + async def _dl_avatar(self, dst): + if _HAS_SERVER is True and M5Things.status() == 2: + infos = M5Things.info() + if len(infos[4]) == 0: + self._avatar_img.set_src(res.AVATAR_IMG) + else: + try: + rsp = requests.get("https://community.m5stack.com" + str(infos[4])) + length = int(rsp.headers["Content-Length"]) + blocklen = 1024 + source = rsp.raw + read = 0 + with open(dst, "wb") as f: + # 逐块读取数据 + while read < length: + to_read = blocklen if (length - read) >= blocklen else length - read + buf = source.read(to_read) + read += len(buf) + f.write(buf) + self._avatar_img.set_src(dst) + except: + self._avatar_img.set_src(res.AVATAR_IMG) + else: + self._avatar_img.set_src(res.AVATAR_IMG) + + @staticmethod + def _get_mac(): + return binascii.hexlify(machine.unique_id()).upper() + + @staticmethod + def _get_account(): + if _HAS_SERVER is True and M5Things.status() == 2: + infos = M5Things.info() + return "None" if len(infos[1]) == 0 else infos[1] + else: + return "None" + + @staticmethod + def _get_avatar(): + if _HAS_SERVER is True and M5Things.status() == 2: + infos = M5Things.info() + print(infos) + if len(infos[4]) == 0: + return res.AVATAR_IMG + else: + return "/system/common/img/" + str(infos[4]).split("/")[-1] + else: + return res.AVATAR_IMG + + @staticmethod + def _get_bg_src(): + if _HAS_SERVER is True and M5Things.status() == 2: + infos = M5Things.info() + if infos[0] == 0: + return res.DEVELOP_PRIVATE_IMG + elif infos[0] in (1, 2): + return res.DEVELOP_PUBLIC_IMG + else: + return res.DEVELOP_PRIVATE_IMG diff --git a/m5stack/modules/startup/stampplc/apps/ezdata.py b/m5stack/modules/startup/stampplc/apps/ezdata.py new file mode 100644 index 00000000..75dad3dc --- /dev/null +++ b/m5stack/modules/startup/stampplc/apps/ezdata.py @@ -0,0 +1,40 @@ +# SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +from .. import app_base +from .. import res +import widgets +import M5 + + +class EzDataApp(app_base.AppBase): + def __init__(self, icos, data=None) -> None: + super().__init__() + + def on_install(self): + pass + + def on_view(self): + M5.Lcd.fillRect(0, 16, 240, 119, 0xEEEEEF) + self._text_label = widgets.Label( + "aabbcc112233", + 120, + 69, + w=240, + h=22, + font_align=widgets.Label.CENTER_ALIGNED, + fg_color=0x000000, + bg_color=0xEEEEEF, + font=res.MontserratMedium18_VLW, + ) + self._text_label.set_text("Please stay tuned") + + def on_ready(self): + pass + + def on_hide(self): + pass + + def on_exit(self): + pass diff --git a/m5stack/modules/startup/stampplc/apps/launcher.py b/m5stack/modules/startup/stampplc/apps/launcher.py new file mode 100644 index 00000000..b021c0c0 --- /dev/null +++ b/m5stack/modules/startup/stampplc/apps/launcher.py @@ -0,0 +1,164 @@ +# SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +from .. import app_base +from .. import res +import widgets +import M5 +from collections import namedtuple + +Icon = namedtuple("Icon", ["name", "src"]) + + +class LauncherApp(app_base.AppBase): + def __init__(self) -> None: + super().__init__() + self._icos = ( + Icon("SETTING", res.SETTING_ICO), + Icon("DEVELOP", res.DEVELOP_ICO), + Icon("APP.RUN", res.APPRUN_ICO), + Icon("APP.LIST", res.APPLIST_ICO), + Icon("EZDATA", res.EZDATA_ICO), + ) + self._id = 1 + + def on_view(self): + M5.Lcd.fillRect(0, 16, 240, 119, 0xEEEEEF) + + left = (len(self._icos) - 1) if self._id - 1 < 0 else self._id - 1 + self._left_img = widgets.Image(use_sprite=False) + self._left_img.set_pos(23, 42) + self._left_img.set_size(48, 48) + self._left_img.set_scale(0.75, 0.75) + self._left_img.set_src(self._icos[left].src) + + self._left_label = widgets.Label( + "SETTING", + 47, + 92, + w=48 + 4, + font_align=widgets.Label.CENTER_ALIGNED, + fg_color=0x000000, + bg_color=0xEEEEEF, + font=res.MontserratMedium10_VLW, + ) + self._left_label.set_text(self._icos[left].name) + + self._center_img = widgets.Image(use_sprite=False) + self._center_img.set_pos(88, 36) + self._center_img.set_size(64, 64) + self._center_img.set_src(self._icos[self._id].src) + + self._center_label = widgets.Label( + "DEVELOP", + 120, + 101, + w=64, + font_align=widgets.Label.CENTER_ALIGNED, + fg_color=0x000000, + bg_color=0xEEEEEF, + font=res.MontserratMedium12_VLW, + ) + self._center_label.set_text(self._icos[self._id].name) + + right = 0 if self._id + 1 > (len(self._icos) - 1) else self._id + 1 + self._right_img = widgets.Image(use_sprite=False) + self._right_img.set_pos(169, 42) + self._right_img.set_size(48, 48) + self._right_img.set_scale(0.75, 0.75) + self._right_img.set_src(self._icos[right].src) + + self._right_label = widgets.Label( + "APP.RUN", + 193, + 92, + w=48 + 4, + font_align=widgets.Label.CENTER_ALIGNED, + fg_color=0x000000, + bg_color=0xEEEEEF, + font=res.MontserratMedium10_VLW, + ) + self._right_label.set_text(self._icos[right].name) + + M5.Lcd.drawImage(res.LEFT_ICO, 3, 56) + M5.Lcd.drawImage(res.RIGHT_ICO, 227, 56) + + def on_ready(self): + pass + + def on_hide(self): + pass + + def on_exit(self): + pass + + def on_uninstall(self): + pass + + async def _btnb_enter_event_handler(self, fw): + app = fw._app_selector.index(self._id + 1) + app.start() + + async def _btnb_back_event_handler(self, fw): + pass + + async def _btna_next_event_handler(self, fw): + left = 0 + right = 0 + refresh = False + self._id = self._id - 1 if self._id - 1 >= 0 else (len(self._icos) - 1) + left = (len(self._icos) - 1) if self._id - 1 < 0 else self._id - 1 + right = 0 if self._id + 1 > (len(self._icos) - 1) else self._id + 1 + refresh = True + + if refresh: + self._left_img.set_src(self._icos[left].src) + self._left_label.set_text(self._icos[left].name) + self._center_img.set_src(self._icos[self._id].src) + self._center_label.set_text(self._icos[self._id].name) + self._right_img.set_src(self._icos[right].src) + self._right_label.set_text(self._icos[right].name) + + async def _btnc_next_event_handler(self, fw): + left = 0 + right = 0 + self._id = self._id + 1 if self._id + 1 < len(self._icos) else 0 + left = (len(self._icos) - 1) if self._id - 1 < 0 else self._id - 1 + right = 0 if self._id + 1 > (len(self._icos) - 1) else self._id + 1 + refresh = True + if refresh: + self._left_img.set_src(self._icos[left].src) + self._left_label.set_text(self._icos[left].name) + self._center_img.set_src(self._icos[self._id].src) + self._center_label.set_text(self._icos[self._id].name) + self._right_img.set_src(self._icos[right].src) + self._right_label.set_text(self._icos[right].name) + + async def _kb_event_handler(self, event, fw): + left = 0 + right = 0 + refresh = False + if event.key == 183: # right key + self._id = self._id + 1 if self._id + 1 < len(self._icos) else 0 + left = (len(self._icos) - 1) if self._id - 1 < 0 else self._id - 1 + right = 0 if self._id + 1 > (len(self._icos) - 1) else self._id + 1 + refresh = True + + if event.key == 180: # left key + self._id = self._id - 1 if self._id - 1 >= 0 else (len(self._icos) - 1) + left = (len(self._icos) - 1) if self._id - 1 < 0 else self._id - 1 + right = 0 if self._id + 1 > (len(self._icos) - 1) else self._id + 1 + refresh = True + + if refresh: + self._left_img.set_src(self._icos[left].src) + self._left_label.set_text(self._icos[left].name) + self._center_img.set_src(self._icos[self._id].src) + self._center_label.set_text(self._icos[self._id].name) + self._right_img.set_src(self._icos[right].src) + self._right_label.set_text(self._icos[right].name) + + if event.key == 0x0D: + app = fw._app_selector.index(self._id + 1) + app.start() diff --git a/m5stack/modules/startup/stampplc/apps/settings.py b/m5stack/modules/startup/stampplc/apps/settings.py new file mode 100644 index 00000000..0d2ae962 --- /dev/null +++ b/m5stack/modules/startup/stampplc/apps/settings.py @@ -0,0 +1,740 @@ +# SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +from .. import app_base +from .. import res +import widgets +import M5 +import esp32 + + +class WiFiSettingApp(app_base.AppBase): + def __init__(self, icos: dict, data=None) -> None: + self._wifi = data + super().__init__() + + def on_launch(self): + self.get_data() + self._option = 0 + + def on_view(self): + M5.Lcd.fillRect(0, 16, 240, 119, 0xEEEEEF) + + self._ssid_label = widgets.Label( + "ssid", + 70, + 32, + w=152, + h=16, + font_align=widgets.Label.LEFT_ALIGNED, + fg_color=0x000000, + bg_color=0xFFFFFF, + font=res.MontserratMedium10_VLW, + ) + self._ssid_label.set_long_mode(widgets.Label.LONG_DOT) + self._ssid_label.set_text(self.ssid) + + self._psk_label = widgets.Label( + "psk", + 70, + 54, + w=152, + h=16, + font_align=widgets.Label.LEFT_ALIGNED, + fg_color=0x000000, + bg_color=0xFFFFFF, + font=res.MontserratMedium10_VLW, + ) + self._psk_label.set_long_mode(widgets.Label.LONG_DOT) + if len(self.psk): + self._psk_label.set_text("*" * 20) + else: + self._psk_label.set_text("") + + self._server_label = widgets.Label( + "server", + 70, + 76, + w=152, + h=16, + font_align=widgets.Label.LEFT_ALIGNED, + fg_color=0x000000, + bg_color=0xFFFFFF, + font=res.MontserratMedium10_VLW, + ) + self._server_label.set_long_mode(widgets.Label.LONG_DOT) + self._server_label.set_text(self.server) + + self._submit_button = widgets.Image(use_sprite=False) + self._submit_button.set_pos(6, 105) + self._submit_button.set_size(228, 24) + self._submit_button.set_src(res.SUBMIT_UNSELECT_BUTTON_IMG) + + self._menu_selector = app_base.AppSelector( + ( + (0, self._select_default_option), + (1, self._select_ssid_option), + (2, self._select_psk_option), + (3, self._select_server_option), + (4, self._select_submit_button_option), + ) + ) + self._option, view_fn = self._menu_selector.index(0) + view_fn() + + def on_ready(self): + pass + + def on_hide(self): + pass + + def on_exit(self): + del ( + self._ssid_label, + self._psk_label, + self._server_label, + self.nvs, + self.ssid, + self.psk, + self.server, + self.ssid_tmp, + self.psk_tmp, + self.server_tmp, + self._option, + ) + + async def _btnb_enter_event_handler(self, fw): + self._option, view_fn = self._menu_selector.current() + view_fn() + + async def _btnb_back_event_handler(self, fw): + self.ssid_tmp = self.ssid + self.psk_tmp = self.psk + self.server_tmp = self.server + self._option, view_fn = self._menu_selector.index(0) + view_fn() + + async def _btna_next_event_handler(self, fw): + self._option, view_fn = self._menu_selector.prev() + view_fn() + + async def _btnc_next_event_handler(self, fw): + self._option, view_fn = self._menu_selector.next() + view_fn() + + async def _kb_event_handler(self, event, fw): + if event.key == 182: # down key + self._option, view_fn = self._menu_selector.next() + view_fn() + event.status = True + elif event.key == 181: # up key + self._option, view_fn = self._menu_selector.prev() + view_fn() + event.status = True + + if event.key == 0x0D and self._option == 4: # Enter key + self._option, view_fn = self._menu_selector.current() + view_fn() + event.status = True + + if event.key == 0x1B: # ESC key + self.ssid_tmp = self.ssid + self.psk_tmp = self.psk + self.server_tmp = self.server + self._option, view_fn = self._menu_selector.index(0) + view_fn() + event.status = True + + if event.key == 0x08 and self._option in (1, 2, 3): + print("backspace") + if self._option == 1: + self.ssid_tmp = self.ssid_tmp[:-1] + self._ssid_label.set_text(self.ssid_tmp) + elif self._option == 2: + self.psk_tmp = self.psk_tmp[:-1] + self._psk_label.set_text(self.psk_tmp) + elif self._option == 3: + self.server_tmp = self.server_tmp[:-1] + self._server_label.set_text(self.server_tmp) + event.status = True + elif event.key >= 0x20 and event.key <= 126: + if self._option == 1: + self.ssid_tmp += chr(event.key) + self._ssid_label.set_text(self.ssid_tmp) + elif self._option == 2: + self.psk_tmp += chr(event.key) + self._psk_label.set_text(self.psk_tmp) + elif self._option == 3: + self.server_tmp += chr(event.key) + self._server_label.set_text(self.server_tmp) + event.status = True + + def _select_default_option(self): + M5.Lcd.drawImage(res.WIFI_DEFAULT_IMG, 6, 22) + self._ssid_label.set_text(self.ssid_tmp) + if len(self.psk_tmp) == 0: + self._psk_label.set_text("") + else: + self._psk_label.set_text("*" * 20) + self._server_label.set_text(self.server_tmp) + self._submit_button.set_src(res.SUBMIT_UNSELECT_BUTTON_IMG) + + def _select_ssid_option(self): + M5.Lcd.drawImage(res.WIFI_SSID_IMG, 6, 22) + self._ssid_label.set_text(self.ssid_tmp) + if len(self.psk_tmp) == 0: + self._psk_label.set_text("") + else: + self._psk_label.set_text("*" * 20) + self._server_label.set_text(self.server_tmp) + self._submit_button.set_src(res.SUBMIT_UNSELECT_BUTTON_IMG) + + def _select_psk_option(self): + M5.Lcd.drawImage(res.WIFI_PSK_IMG, 6, 22) + self._ssid_label.set_text(self.ssid_tmp) + if len(self.psk_tmp) == 0: + self._psk_label.set_text("") + else: + self._psk_label.set_text("*" * 20) + self._server_label.set_text(self.server_tmp) + self._submit_button.set_src(res.SUBMIT_UNSELECT_BUTTON_IMG) + + def _select_server_option(self): + M5.Lcd.drawImage(res.WIFI_SERVER_IMG, 6, 22) + self._ssid_label.set_text(self.ssid_tmp) + if len(self.psk_tmp) == 0: + self._psk_label.set_text("") + else: + self._psk_label.set_text("*" * 20) + self._server_label.set_text(self.server_tmp) + self._submit_button.set_src(res.SUBMIT_UNSELECT_BUTTON_IMG) + + def _select_submit_button_option(self): + M5.Lcd.drawImage(res.WIFI_DEFAULT_IMG, 6, 22) + self._ssid_label.set_text(self.ssid_tmp) + if len(self.psk_tmp) == 0: + self._psk_label.set_text("") + else: + self._psk_label.set_text("*" * 20) + self._server_label.set_text(self.server_tmp) + self._submit_button.set_src(res.SUBMIT_SELECT_BUTTON_IMG) + + def get_data(self): + self.nvs = esp32.NVS("uiflow") + self.ssid = self.nvs.get_str("ssid0") + self.psk = self.nvs.get_str("pswd0") + self.server = self.nvs.get_str("server") + self.ssid_tmp = self.ssid + self.psk_tmp = self.psk + self.server_tmp = self.server + + def set_data(self): + is_save = False + if self.ssid != self.ssid_tmp: + self.ssid = self.ssid_tmp + self.nvs.set_str("ssid0", self.ssid) + print("set new ssid: ", self.ssid) + is_save = True + if self.psk != self.psk_tmp: + self.psk = self.psk_tmp + self.nvs.set_str("pswd0", self.psk) + print("set new psk: ", self.psk) + is_save = True + if self.server != self.server_tmp: + self.server = self.server_tmp + self.nvs.set_str("server", self.server) + print("set new server: ", self.server) + is_save = True + + if is_save is True: + self.nvs.commit() + self._wifi.wlan.disconnect() + self._wifi.wlan.active(False) + self._wifi.wlan.active(True) + self._wifi.connect_network(self.ssid, self.psk) + + +class BatteryChargeSetting(app_base.AppBase): + _current_options = {100: "100mA", 300: "300mA", 700: "700mA", 1000: "1000mA"} + + def __init__(self, icos: dict) -> None: + super().__init__() + + def install(self): + self.on_launch() + self.on_view() + self.on_hide() + + def on_launch(self): + self._current = self._get_charge_current() + self._options = app_base.generator(self._current_options) + while True: + t = next(self._options) + if t == self._current: + break + + def on_view(self): + self._menu_label = widgets.Label( + "Charge Current", + 14, + 65, + w=155, + h=22, + font_align=widgets.Label.LEFT_ALIGNED, + fg_color=0x000000, + bg_color=0xFFFFFF, + font=res.MontserratMedium18_VLW, + ) + + self._charge_current_label = widgets.Label( + "100mA", + 220, + 68, + w=45, + h=15, + font_align=widgets.Label.RIGHT_ALIGNED, + fg_color=0x000000, + bg_color=0xFFFFFF, + font=res.MontserratMedium12_VLW, + ) + + def on_ready(self): + M5.Lcd.drawImage(res.CARD_228x32_SELECT_IMG, 6, 60) + self._menu_label.set_text("Charge Current") + self._charge_current_label.set_text(self._current_options.get(self._current)) + + def on_hide(self): + M5.Lcd.drawImage(res.CARD_228x32_UNSELECT_IMG, 6, 60) + self._menu_label.set_text("Charge Current") + self._charge_current_label.set_text(self._current_options.get(self._current)) + + def on_exit(self): + del (self._menu_label, self._charge_current_label) + + def _handle_charge_current(self, fw): + self._current = next(self._options) + self._set_charge_current(self._current) + self._charge_current_label.set_text(self._current_options.get(self._current)) + + def _get_charge_current(self): + self.nvs = esp32.NVS("uiflow") + try: + return self.nvs.get_i32("charge_current") + except OSError: + return 300 + + def _set_charge_current(self, current): + M5.Power.setBatteryCharge(True) + M5.Power.setChargeCurrent(current) + self.nvs.set_i32("charge_current", current) + self.nvs.commit() + + async def _btnb_enter_event_handler(self, fw): + self._handle_charge_current(fw) + + async def _kb_event_handler(self, event, fw): + if event.key == 0x0D: # Enter key + self._handle_charge_current(fw) + event.status = True + + +class BootScreenSetting(app_base.AppBase): + _boot_options = { + 1: res.ENABLE_IMG, + 2: res.DISABLE_IMG, + } + + def __init__(self, icos: dict, data=None) -> None: + super().__init__() + + def on_install(self): + self.on_launch() + self.on_view() + self.on_hide() + + def on_launch(self): + self._option = self._get_boot_option() + self._option = 1 if self._option == 1 else 2 + self._options = app_base.generator(self._boot_options) + while True: + t = next(self._options) + if t == self._option: + break + + def on_view(self): + self._menu_label = widgets.Label( + "Boot Screen", + 14, + 103, + w=155, + h=22, + font_align=widgets.Label.LEFT_ALIGNED, + fg_color=0x000000, + bg_color=0xFFFFFF, + font=res.MontserratMedium18_VLW, + ) + + self._option_img = widgets.Image(use_sprite=False) + self._option_img.set_pos(193, 106) + self._option_img.set_size(30, 14) + self._option_img.set_src(self._boot_options.get(self._option)) + + def on_ready(self): + M5.Lcd.drawImage(res.CARD_228x32_SELECT_IMG, 6, 98) + self._menu_label.set_text("Boot Screen") + self._option_img.set_src(self._boot_options.get(self._option)) + + def on_hide(self): + M5.Lcd.drawImage(res.CARD_228x32_UNSELECT_IMG, 6, 98) + self._menu_label.set_text("Boot Screen") + self._option_img.set_src(self._boot_options.get(self._option)) + + def on_exit(self): + del (self._menu_label, self._option_img) + + @staticmethod + def _get_boot_option(): + nvs = esp32.NVS("uiflow") + return nvs.get_u8("boot_option") + + @staticmethod + def _set_boot_option(boot_option): + nvs = esp32.NVS("uiflow") + nvs.set_u8("boot_option", boot_option) + nvs.commit() + + def _handle_boot_option(self, fw): + self._option = next(self._options) + self._set_boot_option(self._option) + self._option_img.set_src(self._boot_options.get(self._option)) + + async def _btnb_enter_event_handler(self, fw): + self._handle_boot_option(fw) + + async def _kb_event_handler(self, event, fw): + if event.key == 0x0D: # Enter key + self._handle_boot_option(fw) + event.status = True + + +class BrightnessSettingApp(app_base.AppBase): + _brightness_options = {64: "25%", 128: "50%", 192: "75%", 255: "100%"} + + def __init__(self, icos: dict) -> None: + super().__init__() + + def on_install(self): + self.on_launch() + self.on_view() + self.on_hide() + + def on_launch(self): + self._brightness = M5.Lcd.getBrightness() + self._brightness = self.approximate(self._brightness) + self._options = app_base.generator(self._brightness_options) + while True: + t = next(self._options) + if t == self._brightness: + break + + def on_view(self): + self._menu_label = widgets.Label( + "Brightness", + 14, + 27, + w=155, + h=22, + font_align=widgets.Label.LEFT_ALIGNED, + fg_color=0x000000, + bg_color=0xFFFFFF, + font=res.MontserratMedium18_VLW, + ) + + self._brightness_label = widgets.Label( + "server", + 223, + 30, + w=40, + h=15, + font_align=widgets.Label.RIGHT_ALIGNED, + fg_color=0x000000, + bg_color=0xFFFFFF, + font=res.MontserratMedium12_VLW, + ) + + def on_ready(self): + M5.Lcd.drawImage(res.CARD_228x32_SELECT_IMG, 6, 22) + self._menu_label.set_text("Brightness") + self._brightness_label.set_text(self._brightness_options.get(self._brightness)) + + def on_hide(self): + M5.Lcd.drawImage(res.CARD_228x32_UNSELECT_IMG, 6, 22) + self._menu_label.set_text("Brightness") + self._brightness_label.set_text(self._brightness_options.get(self._brightness)) + + def on_exit(self): + del (self._menu_label, self._brightness_label) + + def _handle_brightness(self, fw): + self._brightness = next(self._options) + M5.Lcd.setBrightness(self._brightness) + self._brightness_label.set_text(self._brightness_options.get(self._brightness)) + + @staticmethod + def approximate(number): + tolerance = 32 + for v in (64, 128, 192, 255): + if number < 64: + return 64 + if abs(number - v) < tolerance: + return v + + async def _btnb_enter_event_handler(self, fw): + self._handle_brightness(fw) + + async def _kb_event_handler(self, event, fw): + if event.key == 0x0D: # Enter key + self._handle_brightness(fw) + event.status = True + + +class GeneralSettingApp(app_base.AppBase): + def __init__(self, icos: dict, data=None) -> None: + self._menus = ( + BrightnessSettingApp(None), + BatteryChargeSetting(None), + BootScreenSetting(None), + ) + self._menu_selector = app_base.AppSelector(self._menus) + super().__init__() + + def on_install(self): + pass + + def on_launch(self): + pass + + def on_view(self): + M5.Lcd.fillRect(0, 16, 240, 119, 0xEEEEEF) + + def on_ready(self): + pass + + def on_hide(self): + pass + + def start(self): + super().start() + for app in self._menus: + app.install() + self._menu_selector.current().resume() + + def stop(self): + for app in self._menus: + app.uninstall() + super().stop() + + async def _btnb_enter_event_handler(self, fw): + app = self._menu_selector.current() + await app._btnb_enter_event_handler(fw) + + async def _btnb_back_event_handler(self, fw): + pass + + async def _btna_next_event_handler(self, fw): + self._menu_selector.current().pause() + self._menu_selector.prev().resume() + + async def _btnc_next_event_handler(self, fw): + self._menu_selector.current().pause() + self._menu_selector.next().resume() + + async def _kb_event_handler(self, event, fw): + if event.key == 182: # down key + self._menu_selector.current().pause() + app = self._menu_selector.next().resume() + event.status = True + elif event.key == 181: # up key + self._menu_selector.current().pause() + self._menu_selector.prev().resume() + event.status = True + elif event.key == 0x0D: # Enter key + app = self._menu_selector.current() + await app._kb_event_handler(event, fw) + + +class SettingsApp(app_base.AppBase): + def __init__(self, icos: dict, data=None) -> None: + self._wlan = data + self._menus = ( + WiFiSettingApp(None, data=self._wlan), + GeneralSettingApp(None), + ) + self._menu_selector = app_base.AppSelector(self._menus) + super().__init__() + + def on_install(self): + pass + + def on_launch(self): + self._imgs = [] + self._icos = [] + self._labels = [] + self._app = None + + def on_view(self): + M5.Lcd.fillRect(0, 16, 240, 119, 0xEEEEEF) + + self._img0 = widgets.Image(use_sprite=False) + self._img0.set_pos(6, 22) + self._img0.set_size(228, 32) + self._img0.set_src(res.CARD_228x32_SELECT_IMG) + self._imgs.append(self._img0) + + self._ico0 = widgets.Image(use_sprite=False) + self._ico0.set_pos(9, 25) + self._ico0.set_size(26, 26) + self._ico0.set_src(res.WLAN_ICO_IMG) + self._icos.append(self._ico0) + + self._label0 = widgets.Label( + "", + 40, + 27, + w=182, + h=22, + fg_color=0x000000, + bg_color=0xFFFFFF, + font=res.MontserratMedium18_VLW, + ) + self._label0.set_text("WLAN") + self._labels.append(self._label0) + + M5.Lcd.drawImage(res.CARET_RIGHT, 213, 25) + + self._img1 = widgets.Image(use_sprite=False) + self._img1.set_pos(6, 60) + self._img1.set_size(228, 32) + self._img1.set_src(res.CARD_228x32_UNSELECT_IMG) + self._imgs.append(self._img1) + + self._ico1 = widgets.Image(use_sprite=False) + self._ico1.set_pos(9, 63) + self._ico1.set_size(26, 26) + self._ico1.set_src(res.GENERAL_ICO_IMG) + self._icos.append(self._ico1) + + self._label1 = widgets.Label( + "", + 40, + 65, + w=182, + h=22, + fg_color=0x000000, + bg_color=0xFFFFFF, + font=res.MontserratMedium18_VLW, + ) + self._label1.set_text("General") + self._labels.append(self._label1) + + M5.Lcd.drawImage(res.CARET_RIGHT, 213, 63) + + def on_ready(self): + pass + + def on_hide(self): + self._app = None + + async def _btnb_enter_event_handler(self, fw): + if self._app: + await self._app._btnb_enter_event_handler(fw) + return + + self._app = self._menu_selector.current() + print("current app:", self._app) + await fw.load(self._app) + + async def _btnb_back_event_handler(self, fw): + self._menu_selector.index(0) + self._app = None + + async def _btna_next_event_handler(self, fw): + if self._app: + await self._app._btna_next_event_handler(fw) + return + + self._menu_selector.index(0) + self._imgs[0].set_src(res.CARD_228x32_SELECT_IMG) + self._icos[0].refresh() + # self._labels[0].refresh() + self._labels[0].set_text("WLAN") + M5.Lcd.drawImage(res.CARET_RIGHT, 213, 25) + + self._imgs[1].set_src(res.CARD_228x32_UNSELECT_IMG) + self._icos[1].refresh() + # self._labels[1].refresh() + self._labels[1].set_text("General") + M5.Lcd.drawImage(res.CARET_RIGHT, 213, 63) + + async def _btnc_next_event_handler(self, fw): + if self._app: + await self._app._btnc_next_event_handler(fw) + return + + self._menu_selector.index(1) + self._imgs[0].set_src(res.CARD_228x32_UNSELECT_IMG) + self._icos[0].refresh() + # self._labels[0].refresh() + self._labels[0].set_text("WLAN") + M5.Lcd.drawImage(res.CARET_RIGHT, 213, 25) + + self._imgs[1].set_src(res.CARD_228x32_SELECT_IMG) + self._icos[1].refresh() + # self._labels[1].refresh() + self._labels[1].set_text("General") + M5.Lcd.drawImage(res.CARET_RIGHT, 213, 63) + + async def _kb_event_handler(self, event, fw): + if self._app: + await self._app._kb_event_handler(event, fw) + return + + if event.key == 0x0D: # Enter key + self._app = self._menu_selector.current() + print("current app:", self._app) + await fw.load(self._app) + event.status = True + elif event.key == 182: # down key + self._menu_selector.index(1) + self._imgs[0].set_src(res.CARD_228x32_UNSELECT_IMG) + self._icos[0].refresh() + # self._labels[0].refresh() + self._labels[0].set_text("WLAN") + M5.Lcd.drawImage(res.CARET_RIGHT, 213, 25) + + self._imgs[1].set_src(res.CARD_228x32_SELECT_IMG) + self._icos[1].refresh() + # self._labels[1].refresh() + self._labels[1].set_text("General") + M5.Lcd.drawImage(res.CARET_RIGHT, 213, 63) + + event.status = True + elif event.key == 181: # up key + self._menu_selector.index(0) + self._imgs[0].set_src(res.CARD_228x32_SELECT_IMG) + self._icos[0].refresh() + # self._labels[0].refresh() + self._labels[0].set_text("WLAN") + M5.Lcd.drawImage(res.CARET_RIGHT, 213, 25) + + self._imgs[1].set_src(res.CARD_228x32_UNSELECT_IMG) + self._icos[1].refresh() + # self._labels[1].refresh() + self._labels[1].set_text("General") + M5.Lcd.drawImage(res.CARET_RIGHT, 213, 63) + event.status = True + + async def _btna_event_handler(self, fw): + self._menu_selector.index(0) + self._app = None diff --git a/m5stack/modules/startup/stampplc/apps/statusbar.py b/m5stack/modules/startup/stampplc/apps/statusbar.py new file mode 100644 index 00000000..19d7d19e --- /dev/null +++ b/m5stack/modules/startup/stampplc/apps/statusbar.py @@ -0,0 +1,199 @@ +# SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +from .. import app_base +from .. import res +import widgets +import M5 +import network +import asyncio +import time + +try: + import M5Things + + _HAS_SERVER = True +except ImportError: + _HAS_SERVER = False + + +class NetworkStatus: + INIT = 0 + RSSI_GOOD = 1 + RSSI_MID = 2 + RSSI_WORSE = 3 + DISCONNECTED = 4 + + +class CloudStatus: + INIT = 0 + CONNECTED = 1 + DISCONNECTED = 2 + + +_WIFI_STATUS_ICO = { + NetworkStatus.INIT: res.WIFI_EMPTY_IMG, + NetworkStatus.RSSI_GOOD: res.WIFI_GOOD_IMG, + NetworkStatus.RSSI_MID: res.WIFI_MID_IMG, + NetworkStatus.RSSI_WORSE: res.WIFI_WORSE_IMG, + NetworkStatus.DISCONNECTED: res.WIFI_DISCONNECTED_IMG, +} + +_CLOUD_STATUS_ICOS = { + CloudStatus.INIT: res.SERVER_EMPTY_IMG, + CloudStatus.CONNECTED: res.SERVER_GREEN_IMG, + CloudStatus.DISCONNECTED: res.SERVER_ERROR_IMG, +} + + +class StatusBarApp(app_base.AppBase): + def __init__(self, icos: dict, wifi) -> None: + self._wifi = wifi + + def on_launch(self): + self._time_text = self._get_local_time_text() + self._network_status = self._get_network_status() + self._cloud_status = self._get_cloud_status() + self._battery_src = self._get_battery_src( + M5.Power.getBatteryLevel(), M5.Power.isCharging() + ) + self._battery_text = self._get_battery_text(M5.Power.getBatteryLevel()) + + def on_view(self): + M5.Lcd.fillRect(0, 0, 240, 16, 0xEEEEEF) + M5.Lcd.drawImage(res.BLUE_TITLE_IMG, 0, 0) + self._time_label = widgets.Label( + "12:23", + 120, + 1, + w=48, + font_align=widgets.Label.CENTER_ALIGNED, + fg_color=0x534D4C, + bg_color=0xEEEEEF, + font=res.MontserratMedium12_VLW, + ) + self._time_label.set_text(self._time_text) + + self._network_img = widgets.Image(use_sprite=False) + self._network_img.set_pos(163, 0) + self._network_img.set_size(16, 16) + self._network_img.set_src(_WIFI_STATUS_ICO[self._network_status]) + + self._cloud_img = widgets.Image(use_sprite=False) + self._cloud_img.set_pos(179, 0) + self._cloud_img.set_size(16, 16) + self._cloud_img.set_src(_CLOUD_STATUS_ICOS[self._cloud_status]) + + self._battery_img = widgets.Image(use_sprite=False) + self._battery_img.set_pos(195, 0) + self._battery_img.set_size(45, 16) + self._battery_img.set_src(self._battery_src) + + self._battery_label = widgets.Label( + "78%", + 212, + 2, + w=26 + 4, + font_align=widgets.Label.CENTER_ALIGNED, + fg_color=0x534D4C, + bg_color=0xFEFEFE, + font=res.MontserratMedium10_VLW, + ) + self._battery_label.set_text(self._battery_text) + + async def on_run(self): + refresh = False + while True: + t = self._get_local_time_text() + if t != self._time_text: + self._time_label.set_text(t) + self._time_text = t + + t = self._get_network_status() + if t != self._network_status: + self._network_img.set_src(_WIFI_STATUS_ICO[t]) + self._network_status = t + + t = self._get_cloud_status() + if t != self._cloud_status: + self._cloud_img.set_src(_CLOUD_STATUS_ICOS[t]) + + t = self._get_battery_src(M5.Power.getBatteryLevel(), M5.Power.isCharging()) + if t != self._battery_src: + self._battery_img.set_src(t) + self._battery_src = t + refresh = True + + t = self._get_battery_text(M5.Power.getBatteryLevel()) + if t != self._battery_text or refresh: + self._battery_label.set_text(t) + self._battery_text = t + refresh = False + + await asyncio.sleep_ms(5000) + + def _update_time(self, struct_time): + self._time_label.set_text("{:02d}:{:02d}".format(struct_time[3], struct_time[4])) + + def _update_wifi(self, status): + self._wifi_status = status + src = _WIFI_STATUS_ICO.get(self._wifi_status) + M5.Lcd.drawImage(src.src, src.x, src.y) + + def _update_server(self, status): + self._server_status = status + src = _CLOUD_STATUS_ICOS.get(self._server_status) + M5.Lcd.drawImage(src.src, src.x, src.y) + + @staticmethod + def _get_local_time_text(): + struct_time = time.localtime() + return "{:02d}:{:02d}".format(struct_time[3], struct_time[4]) + + def _get_network_status(self): + status = self._wifi.connect_status() + if status is network.STAT_GOT_IP: + rssi = self._wifi.get_rssi() + if rssi <= -80: + return NetworkStatus.RSSI_WORSE + elif rssi <= -60: + return NetworkStatus.RSSI_MID + else: + return NetworkStatus.RSSI_GOOD + else: + return NetworkStatus.DISCONNECTED + + @staticmethod + def _get_cloud_status(): + if _HAS_SERVER is True: + status = M5Things.status() + return { + -2: CloudStatus.DISCONNECTED, + -1: CloudStatus.DISCONNECTED, + 0: CloudStatus.INIT, + 1: CloudStatus.INIT, + 2: CloudStatus.CONNECTED, + 3: CloudStatus.DISCONNECTED, + }[status] + else: + return CloudStatus.DISCONNECTED + + @staticmethod + def _get_battery_src(battery, charging): + src = "" + if battery > 0 and battery <= 100: + if battery < 20: + src = res.BATTERY_RED_CHARGE_IMG if charging else res.BATTERY_RED_IMG + elif battery <= 100: + src = res.BATTERY_GREEN_CHARGE_IMG if charging else res.BATTERY_GREEN_IMG + else: + src = res.BATTERY_BLACK_CHARGE_IMG if charging else res.BATTERY_BLACK_IMG + return src + + @staticmethod + def _get_battery_text(battery): + if battery > 0 and battery <= 100: + return "{:d}%".format(battery) + else: + return "" diff --git a/m5stack/modules/startup/stampplc/framework.py b/m5stack/modules/startup/stampplc/framework.py new file mode 100644 index 00000000..cdf84b8f --- /dev/null +++ b/m5stack/modules/startup/stampplc/framework.py @@ -0,0 +1,134 @@ +# SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +from . import app_base +import machine +from unit import CardKBUnit, KeyCode +import M5 +import gc +import asyncio + + +class KeyEvent: + key = 0 + status = False + + +class Framework: + def __init__(self) -> None: + self._apps = [] + self._app_selector = app_base.AppSelector(self._apps) + self._launcher = None + self._bar = None + + def install_bar(self, bar: app_base.AppBase): + self._bar = bar + + def install_sidebar(self, bar: app_base.AppBase): + self._sidebar = bar + + def install_launcher(self, launcher: app_base.AppBase): + self._launcher = launcher + + def install(self, app: app_base.AppBase): + app.install() + self._apps.append(app) + + def start(self): + # asyncio.create_task(self.gc_task()) + asyncio.run(self.run()) + + async def unload(self, app: app_base.AppBase): + # app = self._apps.pop() + app.stop() + + async def load(self, app: app_base.AppBase): + app.start() + + async def reload(self, app: app_base.AppBase): + app.stop() + app.start() + + async def run(self): + self._bar and self._bar.start() + if self._launcher: + while True: + app = self._app_selector.current() + if app != self._launcher: + app = self._app_selector.next() + else: + break + self._launcher.start() + # asyncio.create_task(self.gc_task()) + + self.i2c0 = machine.I2C(0, scl=machine.Pin(1), sda=machine.Pin(2), freq=100000) + self._kb_status = False + if 0x5F in self.i2c0.scan(): + self._kb = CardKBUnit(self.i2c0) + self._event = KeyEvent() + self._kb_status = True + + while True: + M5.update() + if M5.BtnA.wasClicked(): + M5.Speaker.tone(3500, 50) + app = self._app_selector.current() + if hasattr(app, "_btna_next_event_handler"): + await app._btna_next_event_handler(self) + if M5.BtnB.wasClicked(): + M5.Speaker.tone(3500, 50) + app = self._app_selector.current() + if hasattr(app, "_btnb_enter_event_handler"): + await app._btnb_enter_event_handler(self) + elif M5.BtnB.wasHold(): + M5.Speaker.tone(3500, 50) + app = self._app_selector.current() + if hasattr(app, "_btnb_back_event_handler"): + await app._btnb_back_event_handler(self) + app.stop() + self._app_selector.select(self._launcher) + self._launcher.start() + elif M5.BtnB.wasDoubleClicked(): + M5.Speaker.tone(3500, 50) + app = self._app_selector.current() + if hasattr(app, "_btnb_ctrl_event_handler"): + await app._btnb_ctrl_event_handler(self) + if M5.BtnC.wasClicked(): + M5.Speaker.tone(3500, 50) + app = self._app_selector.current() + if hasattr(app, "_btnc_next_event_handler"): + await app._btnc_next_event_handler(self) + await asyncio.sleep_ms(10) + + if self._kb_status: + if self._kb.is_pressed(): + M5.Speaker.tone(3500, 50) + self._event.key = self._kb.get_key() + self._event.status = False + await self.handle_input(self._event) + + async def handle_input(self, event: KeyEvent): + if event.key is KeyCode.KEYCODE_RIGHT: + app = self._app_selector.current() + app.stop() + app = self._app_selector.next() + app.start() + event.status = True + if KeyCode.KEYCODE_LEFT == event.key: + app = self._app_selector.current() + app.stop() + app = self._app_selector.prev() + app.start() + event.status = True + if event.status is False: + app = self._app_selector.current() + if hasattr(app, "_kb_event_handler"): + await app._kb_event_handler(event, self) + + async def gc_task(self): + while True: + gc.collect() + print("heap RAM free:", gc.mem_free()) + print("heap RAM alloc:", gc.mem_alloc()) + await asyncio.sleep_ms(5000) diff --git a/m5stack/modules/startup/stampplc/res.py b/m5stack/modules/startup/stampplc/res.py new file mode 100644 index 00000000..d2d3712f --- /dev/null +++ b/m5stack/modules/startup/stampplc/res.py @@ -0,0 +1,74 @@ +# SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +_attrs = { + "LOGO_IMG": "/system/stampplc/boot/boot_logo_1.jpeg", + # apprun + "RUN_INFO_IMG": "/system/stampplc/apprun/run_info.jpeg", + "RUN_ONCE_SELECT_IMG": "/system/stampplc/apprun/run_once_select.jpeg", + "RUN_ONCE_UNSELECT_IMG": "/system/stampplc/apprun/run_once_unselect.jpeg", + "RUN_ALWAYS_SELECT_IMG": "/system/stampplc/apprun/run_always_select.jpeg", + "RUN_ALWAYS_UNSELECT_IMG": "/system/stampplc/apprun/run_always_unselect.jpeg", + # develop + "DEVELOP_PRIVATE_IMG": "/system/stampplc/develop/private.jpeg", + "DEVELOP_PUBLIC_IMG": "/system/stampplc/develop/public.jpeg", + "AVATAR_IMG": "/system/common/img/avatar.jpeg", + # ezdata + # launcher + "APPLIST_ICO": "/system/stampplc/applist.jpeg", + "DEVELOP_ICO": "/system/stampplc/develop.jpeg", + "APPRUN_ICO": "/system/stampplc/apprun.jpeg", + "EZDATA_ICO": "/system/stampplc/ezdata.jpeg", + "SETTING_ICO": "/system/stampplc/setting.jpeg", + "RIGHT_ICO": "/system/stampplc/right.jpeg", + "LEFT_ICO": "/system/stampplc/left.jpeg", + # setting + "WLAN_ICO_IMG": "/system/stampplc/setting/wlan.jpeg", + "GENERAL_ICO_IMG": "/system/stampplc/setting/general.jpeg", + "CARET_RIGHT": "/system/stampplc/setting/caret_right.jpeg", + # wlan + "WIFI_DEFAULT_IMG": "/system/stampplc/setting/wlan/input_default.jpeg", + "WIFI_SSID_IMG": "/system/stampplc/setting/wlan/input_ssid.jpeg", + "WIFI_PSK_IMG": "/system/stampplc/setting/wlan/input_psk.jpeg", + "WIFI_SERVER_IMG": "/system/stampplc/setting/wlan/input_server.jpeg", + "SUBMIT_SELECT_BUTTON_IMG": "/system/stampplc/setting/wlan/submit_select.jpeg", + "SUBMIT_UNSELECT_BUTTON_IMG": "/system/stampplc/setting/wlan/submit_unselect.jpeg", + # general + "DISABLE_IMG": "/system/stampplc/setting/general/disable.jpeg", + "ENABLE_IMG": "/system/stampplc/setting/general/enable.jpeg", + # statusbar + "BATTERY_BLACK_CHARGE_IMG": "/system/stampplc/statusbar/battery/black_charge.jpeg", + "BATTERY_BLACK_IMG": "/system/stampplc/statusbar/battery/black.jpeg", + "BATTERY_GREEN_CHARGE_IMG": "/system/stampplc/statusbar/battery/green_charge.jpeg", + "BATTERY_GREEN_IMG": "/system/stampplc/statusbar/battery/green.jpeg", + "BATTERY_RED_CHARGE_IMG": "/system/stampplc/statusbar/battery/red_charge.jpeg", + "BATTERY_RED_IMG": "/system/stampplc/statusbar/battery/red.jpeg", + "SERVER_EMPTY_IMG": "/system/stampplc/statusbar/cloud/empty.jpeg", + "SERVER_ERROR_IMG": "/system/stampplc/statusbar/cloud/error.jpeg", + "SERVER_GREEN_IMG": "/system/stampplc/statusbar/cloud/green.jpeg", + "WIFI_DISCONNECTED_IMG": "/system/stampplc/statusbar/wifi/disconnected.jpeg", + "WIFI_EMPTY_IMG": "/system/stampplc/statusbar/wifi/empty.jpeg", + "WIFI_GOOD_IMG": "/system/stampplc/statusbar/wifi/good.jpeg", + "WIFI_MID_IMG": "/system/stampplc/statusbar/wifi/mid.jpeg", + "WIFI_WORSE_IMG": "/system/stampplc/statusbar/wifi/worse.jpeg", + "BLUE_TITLE_IMG": "/system/stampplc/statusbar/title_blue.jpeg", + # common + "CARD_228x32_SELECT_IMG": "/system/stampplc/common/card_228x32_select.jpeg", + "CARD_228x32_UNSELECT_IMG": "/system/stampplc/common/card_228x32_unselect.jpeg", + # font + "MontserratMedium10_VLW": "/system/common/font/Montserrat-Medium-10.vlw", + "MontserratMedium12_VLW": "/system/common/font/Montserrat-Medium-12.vlw", + "MontserratMedium16_VLW": "/system/common/font/Montserrat-Medium-16.vlw", + "MontserratMedium18_VLW": "/system/common/font/Montserrat-Medium-18.vlw", + # path + "USER_AVATAR_PATH": "/system/common/img/", +} + + +def __getattr__(attr): + value = _attrs.get(attr, None) + if value is None: + raise AttributeError(attr) + globals()[attr] = value + return value From 9e9a7437298cc2ef12b2e882db062f5586268701 Mon Sep 17 00:00:00 2001 From: lbuque Date: Wed, 19 Feb 2025 11:34:28 +0800 Subject: [PATCH 010/322] libs/driver: Add aw9523 driver. Signed-off-by: lbuque --- m5stack/libs/driver/aw9523.py | 316 ++++++++++++++++++++++++++++++++ m5stack/libs/driver/manifest.py | 1 + 2 files changed, 317 insertions(+) create mode 100644 m5stack/libs/driver/aw9523.py diff --git a/m5stack/libs/driver/aw9523.py b/m5stack/libs/driver/aw9523.py new file mode 100644 index 00000000..2e1db3f7 --- /dev/null +++ b/m5stack/libs/driver/aw9523.py @@ -0,0 +1,316 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import machine +import micropython + + +def _aw9523_closure() -> tuple: # noqa: C901 + aw = None + + class _AW9523: + """Micropython driver for AW9523 GPIO expander. + + :param I2C i2c: The AW9523 is connected to the i2c bus. + :param int address: The i2c address of the AW9523. Default is 0x59. + :param int irq_pin: The pin number of the interrupt pin. Default is None. + """ + + AW9523_REG_CHIPID = 0x10 # ///< Register for hardcode chip ID + AW9523_REG_SOFTRESET = 0x7F # ///< Register for soft resetting + AW9523_REG_INPUT0 = 0x00 # ///< Register for reading input values + AW9523_REG_OUTPUT0 = 0x02 # ///< Register for writing output values + AW9523_REG_CONFIG0 = 0x04 # ///< Register for configuring direction + AW9523_REG_INTENABLE0 = 0x06 # ///< Register for enabling interrupt + AW9523_REG_GCR = 0x11 # ///< Register for general configuration + AW9523_REG_LEDMODE = 0x12 # ///< Register for configuring const current + + def __new__(cls, *args, **kwargs) -> "_AW9523": + nonlocal aw + if aw is None: + aw = super().__new__(cls) + aw._initialized = False + return aw + + def __init__(self, i2c, address: int = 0x59, irq_pin=None) -> None: + self._i2c = i2c + self._address = address + if self._initialized: + return + + uid = self._i2c.readfrom_mem(self._address, 0x10, 1)[0] + if uid != 0x23: + raise ValueError("AW9523 not found") + + self._i2c.writeto_mem(self._address, self.AW9523_REG_CONFIG0, b"\xff\xff") # all input + self._reg_bit_on(self.AW9523_REG_GCR, 4) # pull up mode + self._i2c.writeto_mem( + self._address, self.AW9523_REG_INTENABLE0, b"\xff\xff" + ) # no interrupt + + # clear PI4IOE5V6408 interrupt + try: + self._i2c.readfrom_mem(0x43, 0x13, 1) + except OSError: + pass + + if irq_pin is not None: + print("irq pin", irq_pin) + self._irq = machine.Pin(irq_pin, machine.Pin.IN, machine.Pin.PULL_UP) + self._irq.irq(self._irq_pin_handler, machine.Pin.IRQ_FALLING) + + self._pin_table = [None for _ in range(16)] + self._last_input_states = [0 for _ in range(16)] + self._initialized = True + + def apply(self, pin) -> None: + """Apply a pin to the pin table. + + :param Pin pin: The pin to be applied. The pin is an instance of Pin. + """ + self._pin_table[pin._id] = pin + + def unapply(self, pin) -> None: + """Remove a pin from the pin table. + + :param Pin pin: The pin to be removed. The pin is an instance of Pin. + """ + self._pin_table[pin._id] = None + + def pin_irq_enable(self, pid: int, en: bool) -> None: + """enable or disable interrupt for a pin. + + :param int pid: The pin number. The pin number is 0-15. + :param bool en: True for enable, False for disable. + """ + register = self.AW9523_REG_INTENABLE0 + (pid >> 3) # 8 bits per register + if en: + self._reg_bit_off(register, pid % 8) + else: + self._reg_bit_on(register, pid % 8) + + def _irq_pin_handler(self, pin: machine.Pin) -> None: + """The interrupt handler for the interrupt pin. + + :param Pin pin: The interrupt pin. The pin is an instance of Pin. + """ + # print("irq_handler") + # clear interrupt + # self._i2c.readfrom_mem(0x43, 0x13, 1) # clear PI4IOE5V6408 interrupt + self._i2c.readfrom_mem(self._address, self.AW9523_REG_INPUT0, 2) + + # higl level interrupt + for i in range(16): + if self._pin_table[i] is not None: + p = self._pin_table[i] + cur_value = p() + if p._mode != p.IN: + continue + if cur_value == self._last_input_states[i]: + continue + + # rising or falling + trigger = ( + p.IRQ_RISING if cur_value > self._last_input_states[i] else p.IRQ_FALLING + ) + # trigger + if p.handler[trigger] is not None: + micropython.schedule(p.handler[trigger], p) + + self._last_input_states[i] = cur_value + + def _reg_bit_on(self, reg: int, bit: int) -> None: + """Turn on a bit in a register. + + :param int reg: The register address. + :param int bit: The bit number to turn on. The bit number is 0-7. + """ + print("reg_bit_on", reg, bit) + value = self._i2c.readfrom_mem(self._address, reg, 1)[0] + self._i2c.writeto_mem(self._address, reg, bytes([value | 1 << bit])) + + def _reg_bit_off(self, reg: int, bit: int) -> None: + """Turn off a bit in a register. + + :param int reg: The register address. + :param int bit: The bit number to turn off. The bit number is 0-7. + """ + print("reg_bit_off", reg, bit) + value = self._i2c.readfrom_mem(self._address, reg, 1)[0] + self._i2c.writeto_mem(self._address, reg, bytes([value & ~(1 << bit)])) + + def set_pin_mode(self, pid: int, mode: int) -> None: + """Set the mode of a pin. + + :param int pid: The pin number. The pin number is 0-15. + :param int mode: The mode of the pin, machine.Pin.IN or machine.Pin.OUT. + """ + register = self.AW9523_REG_CONFIG0 + (pid >> 3) + if mode == machine.Pin.IN: + self._reg_bit_on(register, pid % 8) # input + else: + self._reg_bit_off(register, pid % 8) # output + + def get_output_state(self, pid: int) -> int: + """Get the output state of a pin. + + :param int pid: The pin number. The pin number is 0-15. + :return: The output state of the pin. 1 for high, 0 for low. + :rtype: int + """ + register = self.AW9523_REG_OUTPUT0 + (pid >> 3) + value = self._i2c.readfrom_mem(self._address, register, 1)[0] + return value >> (pid % 8) & 0x01 + + def set_output_state(self, pid: int, state: int) -> None: + """Set the output state of a pin. + + :param int pid: The pin number. The pin number is 0-15. + :param int state: The output state of the pin. 1 for high, 0 for low + """ + register = self.AW9523_REG_OUTPUT0 + (pid >> 3) + if state: + self._reg_bit_on(register, pid % 8) # high + else: + self._reg_bit_off(register, pid % 8) # low + + def get_input_state(self, pid: int) -> int: + """Get the input state of a pin. + + :param int pid: The pin number. The pin number is 0-15. + :return: The input state of the pin. 1 for high, 0 for low. + :rtype: int + """ + register = self.AW9523_REG_INPUT0 + (pid >> 3) + value = self._i2c.readfrom_mem(self._address, register, 1)[0] + return value >> (pid % 8) & 0x01 + + class _Pin: + """The Pin class for AW9523 GPIO expander. + + :param int id: The pin number. The pin number is 0-15. + :param int mode: The mode of the pin, machine.Pin.IN or machine.Pin.OUT. Default is machine.Pin.IN. + :param int value: The initial value of the pin. Default is None. + """ + + IN = machine.Pin.IN + OUT = machine.Pin.OUT + + IRQ_FALLING = machine.Pin.IRQ_FALLING + IRQ_RISING = machine.Pin.IRQ_RISING + + def __init__(self, id, mode: int = IN, value=None) -> None: + nonlocal aw + if aw is None: + raise ValueError("AW9523 not initialized") + self._aw = aw + self._id = id + self._mode = mode + self._aw.set_pin_mode(self._id, self._mode) + if value is not None: + self._aw.set_output_state(self._id, value) + self._aw.apply(self) + self.handler = {self.IRQ_FALLING: None, self.IRQ_RISING: None} + + def init(self, mode: int = -1, value=None) -> None: + """Re-initialise the pin using the given parameters. + + :param int mode: The mode of the pin, machine.Pin.IN or machine.Pin.OUT. Default is machine.Pin.IN. + :param int value: The initial value of the pin. Default is None. + """ + self._aw.set_pin_mode(self._id, mode) + if value is not None: + self._aw.set_output_state(self._id, value) + + def value(self, *args): + """ + This method allows to set and get the value of the pin, depending on whether + the argument ``args`` is supplied or not. + + If the argument is omitted then this method gets the digital logic level of + the pin, returning 0 or 1 corresponding to low and high voltage signals + respectively. The behaviour of this method depends on the mode of the pin: + + - ``Pin.IN`` - The method returns the actual input value currently present + on the pin. + - ``Pin.OUT`` - The behaviour and return value of the method is undefined. + + If the argument is supplied then this method sets the digital logic level of + the pin. The argument ``args`` can be anything that converts to a boolean. + If it converts to ``True``, the pin is set to state '1', otherwise it is set + to state '0'. The behaviour of this method depends on the mode of the pin: + + - ``Pin.IN`` - The value is stored in the output buffer for the pin. The + pin state does not change, it remains in the high-impedance state. The + stored value will become active on the pin as soon as it is changed to + ``Pin.OUT`` or ``Pin.OPEN_DRAIN`` mode. + - ``Pin.OUT`` - The output buffer is set to the given value immediately. + + When setting the value this method returns ``None``. + """ + return self.__call__(*args) + + def __call__(self, *args): + """ + This method allows to set and get the value of the pin, depending on whether + the argument ``args`` is supplied or not. + + If the argument is omitted then this method gets the digital logic level of + the pin, returning 0 or 1 corresponding to low and high voltage signals + respectively. The behaviour of this method depends on the mode of the pin: + + - ``Pin.IN`` - The method returns the actual input value currently present + on the pin. + - ``Pin.OUT`` - The behaviour and return value of the method is undefined. + + If the argument is supplied then this method sets the digital logic level of + the pin. The argument ``args`` can be anything that converts to a boolean. + If it converts to ``True``, the pin is set to state '1', otherwise it is set + to state '0'. The behaviour of this method depends on the mode of the pin: + + - ``Pin.IN`` - The value is stored in the output buffer for the pin. The + pin state does not change, it remains in the high-impedance state. The + stored value will become active on the pin as soon as it is changed to + ``Pin.OUT`` or ``Pin.OPEN_DRAIN`` mode. + - ``Pin.OUT`` - The output buffer is set to the given value immediately. + + When setting the value this method returns ``None``. + """ + if len(args) == 0: + if self._mode == self.IN: + return self._aw.get_input_state(self._id) + else: + return self._aw.get_output_state(self._id) + elif len(args) == 1: + self._aw.set_output_state(self._id, args[0]) + + def on(self) -> None: + """Set pin to "1" output level.""" + self._aw.set_output_state(self._id, 1) + + def off(self) -> None: + """Set pin to "0" output level.""" + self._aw.set_output_state(self._id, 0) + + def irq(self, handler=None, trigger=IRQ_FALLING | IRQ_RISING) -> None: + """Enable interrupt for the pin. + + :param function handler: The interrupt handler function. + :param int trigger: The interrupt trigger mode, machine.Pin.IRQ_FALLING or machine.Pin.IRQ_RISING. + """ + self._aw.pin_irq_enable(self._id, True) + self.handler[trigger] = handler + + def __del__(self) -> None: + """De-initialise the pin.""" + self._aw.unapply(self) + + def deinit(self) -> None: + """De-initialise the pin.""" + self._aw.unapply(self) + + return _AW9523, _Pin + + +AW9523, Pin = _aw9523_closure() diff --git a/m5stack/libs/driver/manifest.py b/m5stack/libs/driver/manifest.py index 52ea29ba..80d3836a 100644 --- a/m5stack/libs/driver/manifest.py +++ b/m5stack/libs/driver/manifest.py @@ -40,6 +40,7 @@ "ads1100.py", "adxl34x.py", "atgm336h.py", + "aw9523.py", "asr650x.py", "bh1750.py", "bh1750fvi.py", From b406de428accfb03f6cb86221741822444bb3238 Mon Sep 17 00:00:00 2001 From: lbuque Date: Thu, 27 Feb 2025 16:23:02 +0800 Subject: [PATCH 011/322] libs/hardware: Add PLC I/O support. Signed-off-by: lbuque --- m5stack/libs/hardware/__init__.py | 44 ++++++++++++++----- m5stack/libs/hardware/manifest.py | 3 +- m5stack/libs/hardware/plcio.py | 73 +++++++++++++++++++++++++++++++ 3 files changed, 109 insertions(+), 11 deletions(-) create mode 100644 m5stack/libs/hardware/plcio.py diff --git a/m5stack/libs/hardware/__init__.py b/m5stack/libs/hardware/__init__.py index c9917cd5..d84e2faa 100644 --- a/m5stack/libs/hardware/__init__.py +++ b/m5stack/libs/hardware/__init__.py @@ -2,13 +2,37 @@ # # SPDX-License-Identifier: MIT -from machine import * -from .rgb import RGB -from .button import Button -from .ir import IR -from .rfid import RFID -from .rotary import Rotary -from .keyboard import Keyboard -from .matrix_keyboard import MatrixKeyboard -from .scd40 import SCD40 -from .sen55 import SEN55 +_attrs = { + "Keyboard": "keyboard", + "Button": "button", + "IR": "ir", + "MatrixKeyboard": "matrix_keyboard", + "DigitalInput": "plcio", + "Relay": "plcio", + "RFID": "rfid", + "RGB": "rgb", + "Rotary": "rotary", + "SCD40": "scd40", + "SEN55": "sen55", +} + + +def __getattr__(attr): + if attr == "sdcard": + value = __import__("sdcard", None, None, True, 1) + globals()[attr] = value + return value + + mod = _attrs.get(attr, None) + if mod is None: + import machine + + value = getattr(machine, attr) + if value is None: + raise AttributeError(attr) + else: + globals()[attr] = value + return value + value = getattr(__import__(mod, None, None, True, 1), attr) + globals()[attr] = value + return value diff --git a/m5stack/libs/hardware/manifest.py b/m5stack/libs/hardware/manifest.py index 0ca2cbfd..47741983 100644 --- a/m5stack/libs/hardware/manifest.py +++ b/m5stack/libs/hardware/manifest.py @@ -11,6 +11,7 @@ "button.py", "ir.py", "matrix_keyboard.py", + "plcio.py", "rfid.py", "rgb.py", "rotary.py", @@ -19,5 +20,5 @@ "sen55.py", ), base_path="..", - opt=0, + # opt=0, ) diff --git a/m5stack/libs/hardware/plcio.py b/m5stack/libs/hardware/plcio.py new file mode 100644 index 00000000..289a2459 --- /dev/null +++ b/m5stack/libs/hardware/plcio.py @@ -0,0 +1,73 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +from driver import aw9523 +import M5 +import machine + + +def _plc_closure(): + aw = None + + def _aw9523_init(): + _board_map = { + # boardid: (i2c, scl, sda, irq_pin) + M5.BOARD.M5StampPLC: (1, 15, 13, 14), + } + i2c, scl, sda, irq_pin = _board_map.get(M5.getBoard(), (None, None, None, None)) + if i2c is None: + raise NotImplementedError("AW9523 is not supported on this board") + i2c = machine.I2C(i2c, scl=machine.Pin(scl), sda=machine.Pin(sda)) + return aw9523.AW9523(i2c, irq_pin=irq_pin) + + class DigitalInput(aw9523.Pin): + _pin_map = { + # input id: pin id + 1: 4, + 2: 5, + 3: 6, + 4: 7, + 5: 12, + 6: 13, + 7: 14, + 8: 15, + } + + def __init__(self, id) -> None: + nonlocal aw + if aw is None: + aw = _aw9523_init() + pid = self._pin_map.get(id, None) + if pid is None: + raise ValueError("Invalid DigitalInput ID") + super().__init__(pid) + + def get_status(self) -> bool: + return bool(self.value()) + + class Relay(aw9523.Pin): + _pin_map = { + # relay id: pin id + 1: 0, + 2: 1, + 3: 2, + 4: 3, + } + + def __init__(self, id) -> None: + nonlocal aw + if aw is None: + aw = _aw9523_init() + pid = self._pin_map.get(id, None) + if pid is None: + raise ValueError("Invalid Relay ID") + super().__init__(pid, mode=aw9523.Pin.OUT) + + def get_status(self) -> bool: + return bool(self.value()) + + return DigitalInput, Relay + + +DigitalInput, Relay = _plc_closure() From 147e9a2f276ab3980b477ea635fcd63280312f06 Mon Sep 17 00:00:00 2001 From: lbuque Date: Thu, 27 Feb 2025 16:25:27 +0800 Subject: [PATCH 012/322] module/startup: Execute the application and exit the startup ui. Signed-off-by: lbuque --- m5stack/modules/startup/airq.py | 2 +- m5stack/modules/startup/basic/apps/app_list.py | 2 +- m5stack/modules/startup/basic/apps/app_run.py | 2 +- m5stack/modules/startup/cardputer/apps/app_list.py | 2 +- m5stack/modules/startup/cardputer/apps/app_run.py | 2 +- m5stack/modules/startup/core2/apps/app_list.py | 2 +- m5stack/modules/startup/core2/apps/app_run.py | 2 +- m5stack/modules/startup/coreink.py | 2 +- m5stack/modules/startup/cores3/apps/app_list.py | 2 +- m5stack/modules/startup/cores3/apps/app_run.py | 2 +- m5stack/modules/startup/dial/apps/app_list.py | 2 +- m5stack/modules/startup/dial/apps/app_run.py | 2 +- m5stack/modules/startup/dinmeter/apps/app_list.py | 2 +- m5stack/modules/startup/dinmeter/apps/app_run.py | 2 +- m5stack/modules/startup/fire/apps/app_list.py | 2 +- m5stack/modules/startup/fire/apps/app_run.py | 2 +- m5stack/modules/startup/paper/apps/app_list.py | 2 +- m5stack/modules/startup/papers3/apps/app_list.py | 2 +- m5stack/modules/startup/stampplc/apps/app_list.py | 3 ++- m5stack/modules/startup/stampplc/apps/app_run.py | 3 ++- m5stack/modules/startup/stampplc/apps/settings.py | 12 ++++++------ m5stack/modules/startup/station/apps/app_list.py | 2 +- m5stack/modules/startup/station/apps/app_run.py | 2 +- m5stack/modules/startup/stickc.py | 4 ++-- m5stack/modules/startup/stickcplus.py | 4 ++-- m5stack/modules/startup/tough/apps/app_list.py | 2 +- m5stack/modules/startup/tough/apps/app_run.py | 2 +- 27 files changed, 36 insertions(+), 34 deletions(-) diff --git a/m5stack/modules/startup/airq.py b/m5stack/modules/startup/airq.py index 73c6a0f9..dd5fdc81 100644 --- a/m5stack/modules/startup/airq.py +++ b/m5stack/modules/startup/airq.py @@ -443,7 +443,7 @@ async def _keycode_enter_event_handler(self, fw): # print("_keycode_enter_event_handler") M5.Lcd.clear() execfile("/".join(["apps/", self._files[self._file_pos]]), {"__name__": "__main__"}) # noqa: F821 - sys.exit(0) + raise KeyboardInterrupt async def _keycode_back_event_handler(self, fw): # print("_keycode_back_event_handler") diff --git a/m5stack/modules/startup/basic/apps/app_list.py b/m5stack/modules/startup/basic/apps/app_list.py index 5626c121..099366f3 100644 --- a/m5stack/modules/startup/basic/apps/app_list.py +++ b/m5stack/modules/startup/basic/apps/app_list.py @@ -243,7 +243,7 @@ async def _btnb_event_handler(self, fw): async def _btnc_event_handler(self, fw): execfile("/".join(["apps/", self._files[self._file_pos]]), {"__name__": "__main__"}) # noqa: F821 - sys.exit(0) + raise KeyboardInterrupt async def _btnc_hold_event_handler(self, fw): boot_option.set_boot_option(2) diff --git a/m5stack/modules/startup/basic/apps/app_run.py b/m5stack/modules/startup/basic/apps/app_run.py index 1143d3ed..dfd0e67e 100644 --- a/m5stack/modules/startup/basic/apps/app_run.py +++ b/m5stack/modules/startup/basic/apps/app_run.py @@ -75,7 +75,7 @@ def on_exit(self): async def _btnb_event_handler(self, fw): # print("_btnb_event_handler") execfile("main.py", {"__name__": "__main__"}) # noqa: F821 - sys.exit(0) + raise KeyboardInterrupt async def _btnc_event_handler(self, fw): # print("_btnc_event_handler") diff --git a/m5stack/modules/startup/cardputer/apps/app_list.py b/m5stack/modules/startup/cardputer/apps/app_list.py index bdfe2eb6..7a93b235 100644 --- a/m5stack/modules/startup/cardputer/apps/app_list.py +++ b/m5stack/modules/startup/cardputer/apps/app_list.py @@ -256,7 +256,7 @@ def _btn_down_event_handler(self, fw): def _btn_once_event_handler(self, event): execfile("/".join(["apps/", self._files[self._file_pos]]), {"__name__": "__main__"}) # noqa: F821 - sys.exit(0) + raise KeyboardInterrupt def _btn_always_event_handler(self, event): nvs = esp32.NVS("uiflow") diff --git a/m5stack/modules/startup/cardputer/apps/app_run.py b/m5stack/modules/startup/cardputer/apps/app_run.py index bce7ff57..29505c03 100644 --- a/m5stack/modules/startup/cardputer/apps/app_run.py +++ b/m5stack/modules/startup/cardputer/apps/app_run.py @@ -103,7 +103,7 @@ def on_exit(self): def _handle_run_once(self, fw): execfile("main.py", {"__name__": "__main__"}) # noqa: F821 - sys.exit(0) + raise KeyboardInterrupt def _handle_run_always(self, fw): nvs = esp32.NVS("uiflow") diff --git a/m5stack/modules/startup/core2/apps/app_list.py b/m5stack/modules/startup/core2/apps/app_list.py index 5f7ea1f6..b65e84e0 100644 --- a/m5stack/modules/startup/core2/apps/app_list.py +++ b/m5stack/modules/startup/core2/apps/app_list.py @@ -280,7 +280,7 @@ def _btn_down_event_handler(self, fw): def _btn_once_event_handler(self, event): execfile("/".join(["apps/", self._files[self._file_pos]]), {"__name__": "__main__"}) # noqa: F821 - sys.exit(0) + raise KeyboardInterrupt def _btn_always_event_handler(self, event): nvs = esp32.NVS("uiflow") diff --git a/m5stack/modules/startup/core2/apps/app_run.py b/m5stack/modules/startup/core2/apps/app_run.py index 1947b766..21a10f47 100644 --- a/m5stack/modules/startup/core2/apps/app_run.py +++ b/m5stack/modules/startup/core2/apps/app_run.py @@ -106,7 +106,7 @@ async def _click_event_handler(self, x, y, fw): def _handle_run_once(self, fw): execfile("main.py", {"__name__": "__main__"}) # noqa: F821 - sys.exit(0) + raise KeyboardInterrupt def _handle_run_always(self, fw): nvs = esp32.NVS("uiflow") diff --git a/m5stack/modules/startup/coreink.py b/m5stack/modules/startup/coreink.py index 398cc095..9f6c1732 100644 --- a/m5stack/modules/startup/coreink.py +++ b/m5stack/modules/startup/coreink.py @@ -443,7 +443,7 @@ async def _keycode_enter_event_handler(self, fw): # print("_keycode_enter_event_handler") M5.Lcd.clear() execfile("/".join(["apps/", self._files[self._file_pos]]), {"__name__": "__main__"}) # noqa: F821 - sys.exit(0) + raise KeyboardInterrupt async def _keycode_back_event_handler(self, fw): # print("_keycode_back_event_handler") diff --git a/m5stack/modules/startup/cores3/apps/app_list.py b/m5stack/modules/startup/cores3/apps/app_list.py index 606926b5..9dd30c10 100644 --- a/m5stack/modules/startup/cores3/apps/app_list.py +++ b/m5stack/modules/startup/cores3/apps/app_list.py @@ -241,7 +241,7 @@ def _btn_down_event_handler(self, fw): def _btn_once_event_handler(self, event): execfile("/".join(["apps", self._files[self._file_pos]]), {"__name__": "__main__"}) # noqa: F821 - sys.exit(0) + raise KeyboardInterrupt def _btn_always_event_handler(self, event): nvs = esp32.NVS("uiflow") diff --git a/m5stack/modules/startup/cores3/apps/app_run.py b/m5stack/modules/startup/cores3/apps/app_run.py index 91f44fbb..14919d16 100644 --- a/m5stack/modules/startup/cores3/apps/app_run.py +++ b/m5stack/modules/startup/cores3/apps/app_run.py @@ -105,7 +105,7 @@ async def _click_event_handler(self, x, y, fw): def _handle_run_once(self, fw): execfile("main.py", {"__name__": "__main__"}) # noqa: F821 - sys.exit(0) + raise KeyboardInterrupt def _handle_run_always(self, fw): nvs = esp32.NVS("uiflow") diff --git a/m5stack/modules/startup/dial/apps/app_list.py b/m5stack/modules/startup/dial/apps/app_list.py index b8714d62..86912f9f 100644 --- a/m5stack/modules/startup/dial/apps/app_list.py +++ b/m5stack/modules/startup/dial/apps/app_list.py @@ -299,7 +299,7 @@ def _btn_down_event_handler(self, fw): def _btn_once_event_handler(self, event): execfile("/".join(["apps/", self._files[self._file_pos]]), {"__name__": "__main__"}) # noqa: F821 - sys.exit(0) + raise KeyboardInterrupt def _btn_always_event_handler(self, event): nvs = esp32.NVS("uiflow") diff --git a/m5stack/modules/startup/dial/apps/app_run.py b/m5stack/modules/startup/dial/apps/app_run.py index 47280c44..079bce09 100644 --- a/m5stack/modules/startup/dial/apps/app_run.py +++ b/m5stack/modules/startup/dial/apps/app_run.py @@ -115,7 +115,7 @@ async def _click_event_handler(self, x, y, fw): def _handle_run_once(self, fw): execfile("main.py", {"__name__": "__main__"}) # noqa: F821 - sys.exit(0) + raise KeyboardInterrupt def _handle_run_always(self, fw): nvs = esp32.NVS("uiflow") diff --git a/m5stack/modules/startup/dinmeter/apps/app_list.py b/m5stack/modules/startup/dinmeter/apps/app_list.py index 4b670173..ebfc25ef 100644 --- a/m5stack/modules/startup/dinmeter/apps/app_list.py +++ b/m5stack/modules/startup/dinmeter/apps/app_list.py @@ -256,7 +256,7 @@ def _btn_down_event_handler(self, fw): def _btn_once_event_handler(self, event): execfile("/".join(["apps/", self._files[self._file_pos]]), {"__name__": "__main__"}) # noqa: F821 - sys.exit(0) + raise KeyboardInterrupt def _btn_always_event_handler(self, event): nvs = esp32.NVS("uiflow") diff --git a/m5stack/modules/startup/dinmeter/apps/app_run.py b/m5stack/modules/startup/dinmeter/apps/app_run.py index c338bf58..fd7c1e74 100644 --- a/m5stack/modules/startup/dinmeter/apps/app_run.py +++ b/m5stack/modules/startup/dinmeter/apps/app_run.py @@ -103,7 +103,7 @@ def on_exit(self): def _handle_run_once(self, fw): execfile("main.py", {"__name__": "__main__"}) # noqa: F821 - sys.exit(0) + raise KeyboardInterrupt def _handle_run_always(self, fw): nvs = esp32.NVS("uiflow") diff --git a/m5stack/modules/startup/fire/apps/app_list.py b/m5stack/modules/startup/fire/apps/app_list.py index b8379067..4c672569 100644 --- a/m5stack/modules/startup/fire/apps/app_list.py +++ b/m5stack/modules/startup/fire/apps/app_list.py @@ -224,4 +224,4 @@ async def _btnb_event_handler(self, fw): async def _btnc_event_handler(self, fw): execfile("/".join(["apps/", self._files[self._file_pos]]), {"__name__": "__main__"}) # noqa: F821 - sys.exit(0) + raise KeyboardInterrupt diff --git a/m5stack/modules/startup/fire/apps/app_run.py b/m5stack/modules/startup/fire/apps/app_run.py index aff4bbad..a8426389 100644 --- a/m5stack/modules/startup/fire/apps/app_run.py +++ b/m5stack/modules/startup/fire/apps/app_run.py @@ -95,7 +95,7 @@ async def _btna_event_handler(self, fw): async def _btnb_event_handler(self, fw): execfile("main.py", {"__name__": "__main__"}) # noqa: F821 - sys.exit(0) + raise KeyboardInterrupt async def _btnc_event_handler(self, fw): nvs = esp32.NVS("uiflow") diff --git a/m5stack/modules/startup/paper/apps/app_list.py b/m5stack/modules/startup/paper/apps/app_list.py index 440dd214..e5b376aa 100644 --- a/m5stack/modules/startup/paper/apps/app_list.py +++ b/m5stack/modules/startup/paper/apps/app_list.py @@ -183,7 +183,7 @@ def _btn_run_event_handler(self, btn): return print("run %d, %s" % (self._file_pos, self._files[self._file_pos])) execfile("/".join(["apps/", self._files[self._file_pos]]), {"__name__": "__main__"}) # noqa: F821 - sys.exit(0) + raise KeyboardInterrupt async def _click_event_handler(self, x, y, fw): # print("_click_event_handler") diff --git a/m5stack/modules/startup/papers3/apps/app_list.py b/m5stack/modules/startup/papers3/apps/app_list.py index c10404a7..7edb97a4 100644 --- a/m5stack/modules/startup/papers3/apps/app_list.py +++ b/m5stack/modules/startup/papers3/apps/app_list.py @@ -183,7 +183,7 @@ def _btn_run_event_handler(self, btn): return print("run %d, %s" % (self._file_pos, self._files[self._file_pos])) execfile("/".join(["apps/", self._files[self._file_pos]]), {"__name__": "__main__"}) # noqa: F821 - sys.exit(0) + raise KeyboardInterrupt async def _click_event_handler(self, x, y, fw): # print("_click_event_handler") diff --git a/m5stack/modules/startup/stampplc/apps/app_list.py b/m5stack/modules/startup/stampplc/apps/app_list.py index afc3c6e3..a6cb6a00 100644 --- a/m5stack/modules/startup/stampplc/apps/app_list.py +++ b/m5stack/modules/startup/stampplc/apps/app_list.py @@ -254,7 +254,8 @@ def _btn_down_event_handler(self, fw): def _btn_once_event_handler(self, event): execfile("/".join(["apps/", self._files[self._file_pos]]), {"__name__": "__main__"}) # noqa: F821 - sys.exit(0) + # raise KeyboardInterrupt + raise KeyboardInterrupt def _btn_always_event_handler(self, event): nvs = esp32.NVS("uiflow") diff --git a/m5stack/modules/startup/stampplc/apps/app_run.py b/m5stack/modules/startup/stampplc/apps/app_run.py index f9d429d9..3b80d85e 100644 --- a/m5stack/modules/startup/stampplc/apps/app_run.py +++ b/m5stack/modules/startup/stampplc/apps/app_run.py @@ -103,7 +103,8 @@ def on_exit(self): def _handle_run_once(self, fw): execfile("main.py", {"__name__": "__main__"}) # noqa: F821 - sys.exit(0) + # raise KeyboardInterrupt + raise KeyboardInterrupt def _handle_run_always(self, fw): nvs = esp32.NVS("uiflow") diff --git a/m5stack/modules/startup/stampplc/apps/settings.py b/m5stack/modules/startup/stampplc/apps/settings.py index 0d2ae962..3983b62e 100644 --- a/m5stack/modules/startup/stampplc/apps/settings.py +++ b/m5stack/modules/startup/stampplc/apps/settings.py @@ -366,7 +366,7 @@ def on_view(self): self._menu_label = widgets.Label( "Boot Screen", 14, - 103, + 27, w=155, h=22, font_align=widgets.Label.LEFT_ALIGNED, @@ -376,17 +376,17 @@ def on_view(self): ) self._option_img = widgets.Image(use_sprite=False) - self._option_img.set_pos(193, 106) + self._option_img.set_pos(193, 30) self._option_img.set_size(30, 14) self._option_img.set_src(self._boot_options.get(self._option)) def on_ready(self): - M5.Lcd.drawImage(res.CARD_228x32_SELECT_IMG, 6, 98) + M5.Lcd.drawImage(res.CARD_228x32_SELECT_IMG, 6, 22) self._menu_label.set_text("Boot Screen") self._option_img.set_src(self._boot_options.get(self._option)) def on_hide(self): - M5.Lcd.drawImage(res.CARD_228x32_UNSELECT_IMG, 6, 98) + M5.Lcd.drawImage(res.CARD_228x32_UNSELECT_IMG, 6, 22) self._menu_label.set_text("Boot Screen") self._option_img.set_src(self._boot_options.get(self._option)) @@ -502,8 +502,8 @@ async def _kb_event_handler(self, event, fw): class GeneralSettingApp(app_base.AppBase): def __init__(self, icos: dict, data=None) -> None: self._menus = ( - BrightnessSettingApp(None), - BatteryChargeSetting(None), + # BrightnessSettingApp(None), + # BatteryChargeSetting(None), BootScreenSetting(None), ) self._menu_selector = app_base.AppSelector(self._menus) diff --git a/m5stack/modules/startup/station/apps/app_list.py b/m5stack/modules/startup/station/apps/app_list.py index 5b2ec68c..67743260 100644 --- a/m5stack/modules/startup/station/apps/app_list.py +++ b/m5stack/modules/startup/station/apps/app_list.py @@ -254,7 +254,7 @@ def _btn_down_event_handler(self, fw): def _btn_once_event_handler(self, event): execfile("/".join(["apps/", self._files[self._file_pos]]), {"__name__": "__main__"}) # noqa: F821 - sys.exit(0) + raise KeyboardInterrupt def _btn_always_event_handler(self, event): nvs = esp32.NVS("uiflow") diff --git a/m5stack/modules/startup/station/apps/app_run.py b/m5stack/modules/startup/station/apps/app_run.py index f9d429d9..e6a38433 100644 --- a/m5stack/modules/startup/station/apps/app_run.py +++ b/m5stack/modules/startup/station/apps/app_run.py @@ -103,7 +103,7 @@ def on_exit(self): def _handle_run_once(self, fw): execfile("main.py", {"__name__": "__main__"}) # noqa: F821 - sys.exit(0) + raise KeyboardInterrupt def _handle_run_always(self, fw): nvs = esp32.NVS("uiflow") diff --git a/m5stack/modules/startup/stickc.py b/m5stack/modules/startup/stickc.py index bea84190..bb2089fb 100644 --- a/m5stack/modules/startup/stickc.py +++ b/m5stack/modules/startup/stickc.py @@ -369,7 +369,7 @@ async def _keycode_back_event_handler(self, fw): def _handle_run_once(self, fw): M5.Lcd.clear(0xFFFFFF) execfile("main.py", {"__name__": "__main__"}) # noqa: F821 - sys.exit(0) + raise KeyboardInterrupt def _handle_run_always(self, fw): M5.Lcd.clear(0xFFFFFF) @@ -496,7 +496,7 @@ async def _keycode_enter_event_handler(self, fw): # print("_keycode_enter_event_handler") M5.Lcd.clear() execfile("/".join(["apps/", self._files[self._file_pos]]), {"__name__": "__main__"}) # noqa: F821 - sys.exit(0) + raise KeyboardInterrupt async def _keycode_back_event_handler(self, fw): # print("_keycode_back_event_handler") diff --git a/m5stack/modules/startup/stickcplus.py b/m5stack/modules/startup/stickcplus.py index 8b30c81d..9497b6bc 100644 --- a/m5stack/modules/startup/stickcplus.py +++ b/m5stack/modules/startup/stickcplus.py @@ -136,7 +136,7 @@ def __init__(self) -> None: def on_ready(self): M5.Lcd.clear() execfile("main.py", {"__name__": "__main__"}) # noqa: F821 - sys.exit(0) + raise KeyboardInterrupt class ListApp(AppBase): @@ -284,7 +284,7 @@ async def _keycode_enter_event_handler(self, fw): # print("_keycode_enter_event_handler") M5.Lcd.clear() execfile("/".join(["apps/", self._files[self._file_pos]]), {"__name__": "__main__"}) # noqa: F821 - sys.exit(0) + raise KeyboardInterrupt async def _keycode_back_event_handler(self, fw): # print("_keycode_back_event_handler") diff --git a/m5stack/modules/startup/tough/apps/app_list.py b/m5stack/modules/startup/tough/apps/app_list.py index 1980ac67..8718e48a 100644 --- a/m5stack/modules/startup/tough/apps/app_list.py +++ b/m5stack/modules/startup/tough/apps/app_list.py @@ -280,7 +280,7 @@ def _btn_down_event_handler(self, fw): def _btn_once_event_handler(self, event): execfile("/".join(["apps/", self._files[self._file_pos]]), {"__name__": "__main__"}) # noqa: F821 - sys.exit(0) + raise KeyboardInterrupt def _btn_always_event_handler(self, event): nvs = esp32.NVS("uiflow") diff --git a/m5stack/modules/startup/tough/apps/app_run.py b/m5stack/modules/startup/tough/apps/app_run.py index 4cca5398..94a4b3cd 100644 --- a/m5stack/modules/startup/tough/apps/app_run.py +++ b/m5stack/modules/startup/tough/apps/app_run.py @@ -106,7 +106,7 @@ async def _click_event_handler(self, x, y, fw): def _handle_run_once(self, fw): execfile("main.py", {"__name__": "__main__"}) # noqa: F821 - sys.exit(0) + raise KeyboardInterrupt def _handle_run_always(self, fw): nvs = esp32.NVS("uiflow") From 78fb2763e5ac71fccccadc2b6b6be0b4e25faeb4 Mon Sep 17 00:00:00 2001 From: lbuque Date: Thu, 27 Feb 2025 18:17:35 +0800 Subject: [PATCH 013/322] docs: Add PLC I/O docs. Signed-off-by: lbuque --- docs/en/controllers/stampplc.rst | 96 -------- docs/en/hardware/index.rst | 1 + docs/en/hardware/plcio.digitalinput.rst | 123 +++++++++++ docs/en/hardware/plcio.relay.rst | 152 +++++++++++++ docs/en/hardware/plcio.rst | 69 ++++++ docs/en/refs/hardware.plcio.digitalinput.ref | 15 ++ docs/en/refs/hardware.plcio.ref | 11 + docs/en/refs/hardware.plcio.relay.ref | 18 ++ .../hardware/plcio.digitalinput.po | 200 +++++++++++++++++ .../zh_CN/LC_MESSAGES/hardware/plcio.po | 109 ++++++++++ .../zh_CN/LC_MESSAGES/hardware/plcio.relay.po | 205 ++++++++++++++++++ .../stamplc_digital_input_example.m5f2 | 1 + .../stamplc_digital_input_example.py | 53 +++++ .../plcio/relay/stamplc_relay_example.m5f2 | 1 + .../plcio/relay/stamplc_relay_example.py | 60 +++++ .../hardware/plcio/stamplc_plcio_example.m5f2 | 1 + .../hardware/plcio/stamplc_plcio_example.py | 193 +++++++++++++++++ m5stack/libs/hardware/plcio.py | 20 +- 18 files changed, 1231 insertions(+), 97 deletions(-) delete mode 100644 docs/en/controllers/stampplc.rst create mode 100644 docs/en/hardware/plcio.digitalinput.rst create mode 100644 docs/en/hardware/plcio.relay.rst create mode 100644 docs/en/hardware/plcio.rst create mode 100644 docs/en/refs/hardware.plcio.digitalinput.ref create mode 100644 docs/en/refs/hardware.plcio.ref create mode 100644 docs/en/refs/hardware.plcio.relay.ref create mode 100644 docs/locales/zh_CN/LC_MESSAGES/hardware/plcio.digitalinput.po create mode 100644 docs/locales/zh_CN/LC_MESSAGES/hardware/plcio.po create mode 100644 docs/locales/zh_CN/LC_MESSAGES/hardware/plcio.relay.po create mode 100644 examples/hardware/plcio/digital_input/stamplc_digital_input_example.m5f2 create mode 100644 examples/hardware/plcio/digital_input/stamplc_digital_input_example.py create mode 100644 examples/hardware/plcio/relay/stamplc_relay_example.m5f2 create mode 100644 examples/hardware/plcio/relay/stamplc_relay_example.py create mode 100644 examples/hardware/plcio/stamplc_plcio_example.m5f2 create mode 100644 examples/hardware/plcio/stamplc_plcio_example.py diff --git a/docs/en/controllers/stampplc.rst b/docs/en/controllers/stampplc.rst deleted file mode 100644 index bc500a6a..00000000 --- a/docs/en/controllers/stampplc.rst +++ /dev/null @@ -1,96 +0,0 @@ -********** -StampPLC -********** - -.. TODO: Image resources are not ready - -.. include:: ../refs/controllers.cardputer.ref - - -Startup UI -========== - -.. NOTE:: - 1. ``BtnA`` and ``BtnC`` is used for menu selection. - 2. ``BtnA`` is used to execute menu items. - 3. Long press ``BtnA`` to exit the application. - 4. ``CardKB Unit`` is used for input. - -Launcher --------- - -|launcher.png| - -``BtnA`` and ``BtnC`` cycle through the available applications. - -``BtnB`` launches the selected application. - - -Setting -------- - -|setting.png| - -``BtnA`` and ``BtnC`` cycle through the available applications. - -``BtnB`` launches the selected application. - -Long press ``BtnB`` to exit the application. - - -WLAN -^^^^ - -|wlan.png| - -``BtnA`` and ``BtnC`` select the corresponding input box or button. - -``BtnB`` performs the corresponding action. - -Long press ``BtnB`` to exit the application. - - -Gerneral -^^^^^^^^ - -|general.png| - -``BtnA`` and ``BtnC`` keys select menus. - -``BtnB`` set. - -Long press ``BtnB`` to exit the application. - - -Develop -------- - -|develop.png| - -Long press ``BtnB`` to exit the application. - - -APP.RUN -------- - -|apprun.png| - -``BtnA`` and ``BtnC`` select the corresponding execution action. - -``BtnB`` click to run main.py according to the corresponding action. - -Long press ``BtnB`` to exit the application. - - -APP.LIST --------- - -|applist.png| - -``BtnA`` and ``BtnC`` cycle through the available applications. - -``BtnB`` click to run the application once. - -Double click ``BtnB`` to always run the application. - -Long press ``BtnB`` to exit the application. diff --git a/docs/en/hardware/index.rst b/docs/en/hardware/index.rst index 859916a7..c6f814da 100644 --- a/docs/en/hardware/index.rst +++ b/docs/en/hardware/index.rst @@ -12,6 +12,7 @@ Hardware ir.rst mic.rst pin.rst + plcio.rst rotary.rst scd40.rst sen55.rst diff --git a/docs/en/hardware/plcio.digitalinput.rst b/docs/en/hardware/plcio.digitalinput.rst new file mode 100644 index 00000000..dcb4ae59 --- /dev/null +++ b/docs/en/hardware/plcio.digitalinput.rst @@ -0,0 +1,123 @@ +Digital Input +============= + +.. include:: ../refs/hardware.plcio.digitalinput.ref + +Digital Input is used to read the digital input of host devices. + + +UiFlow2 Example +--------------- + +Get the digital input status +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Open the |stamplc_digital_input_example.m5f2| project in UiFlow2. + +This example demonstrates how to get the status of a digital input and display the status on the screen. + +UiFlow2 Code Block: + + |stamplc_digital_input_example.png| + +Example output: + + None + + +MicroPython Example +------------------- + +Get the digital input status +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +This example demonstrates how to get the status of a digital input and display the status on the screen. + +MicroPython Code Block: + + .. literalinclude:: ../../../examples/hardware/plcio/digital_input/stamplc_digital_input_example.py + +Example output: + + None + + +**API** +------- + +DigitalInput +^^^^^^^^^^^^ + +.. class:: DigitalInput(id: int) + + Initialize a digital input object. + + :param int id: The ID of the digital input. The range of ID is 1-8. + + UiFlow2 Code Block: + + |init.png| + + MicroPython Code Block: + + .. code-block:: python + + from hadrware import DigitalInput + + in1 = DigitalInput(1) + + + .. method:: DigitalInput.get_status() -> bool + + Get the status of the digital input. + + :return: The status of the digital input. + :rtype: bool + + UiFlow2 Code Block: + + |get_status.png| + + MicroPython Code Block: + + .. code-block:: python + + in1.get_status() + + + .. method:: DigitalInput.value() -> int + + Get the value of the digital input. + + :return: The value of the digital input. + :rtype: int + + UiFlow2 Code Block: + + |value.png| + + MicroPython Code Block: + + .. code-block:: python + + in1.value() + + .. method:: DigitalInput.irq(handler=None, trigger=IRQ_FALLING | IRQ_RISING) -> None + + Enable interrupt for the pin. + + :param function handler: The interrupt handler function. + :param int trigger: The interrupt trigger mode, DigitalInput.IRQ_FALLING or DigitalInput.IRQ_RISING. + + UiFlow2 Code Block: + + |irq.png| + + MicroPython Code Block: + + .. code-block:: python + + def handler(pin): + print('interrupt triggered') + + in1.irq(handler, DigitalInput.IRQ_FALLING) diff --git a/docs/en/hardware/plcio.relay.rst b/docs/en/hardware/plcio.relay.rst new file mode 100644 index 00000000..73fd77c7 --- /dev/null +++ b/docs/en/hardware/plcio.relay.rst @@ -0,0 +1,152 @@ +Relay +===== + +.. include:: ../refs/hardware.plcio.relay.ref + +Relay is used to control the relay of host devices. + + +UiFlow2 Example +--------------- + +Relay control +^^^^^^^^^^^^^ + +Open the |stamplc_relay_example.m5f2| project in UiFlow2. + +This example demonstrates how to use a button to control the state of a relay and display the relay's state value on the screen. + +UiFlow2 Code Block: + + |stamplc_relay_example.png| + +Example output: + + None + + +MicroPython Example +------------------- + +Relay control +^^^^^^^^^^^^^ + +This example demonstrates how to use a button to control the state of a relay and display the relay's state value on the screen. + +MicroPython Code Block: + + .. literalinclude:: ../../../examples/hardware/plcio/relay/stamplc_relay_example.py + +Example output: + + None + + +**API** +------- + +Relay +^^^^^ + +.. class:: Relay(id: int) + + Initialize a relay object. + + :param int id: The ID of the relay. The range of ID is 1-4. + + UiFlow2 Code Block: + + |init.png| + + MicroPython Code Block: + + .. code-block:: python + + from hadrware import Relay + + relay = Relay(1) + + + .. method:: Relay.on() -> None + + Turn on the relay. + + UiFlow2 Code Block: + + |on.png| + + MicroPython Code Block: + + .. code-block:: python + + relay.on() + + + .. method:: Relay.off() -> None + + Turn off the relay. + + UiFlow2 Code Block: + + |off.png| + + MicroPython Code Block: + + .. code-block:: python + + relay.off() + + + .. method:: Relay.value() -> int + + Get the value of the relay. + + :return: The value of the relay. + :rtype: int + + UiFlow2 Code Block: + + |set_value.png| + + |get_value.png| + + MicroPython Code Block: + + .. code-block:: python + + relay.value(1) + relay.value() + + + .. method:: Relay.get_status() -> bool + + Get the status of the relay. + + :return: The status of the relay. + :rtype: bool + + UiFlow2 Code Block: + + |get_status.png| + + MicroPython Code Block: + + .. code-block:: python + + relay.get_status() + + .. method:: Relay.set_status(status: bool) -> None + + Set the status of the relay. + + :param bool status: The status of the relay. + + UiFlow2 Code Block: + + |set_status.png| + + MicroPython Code Block: + + .. code-block:: python + + relay.set_status(True) diff --git a/docs/en/hardware/plcio.rst b/docs/en/hardware/plcio.rst new file mode 100644 index 00000000..3ad9a3a5 --- /dev/null +++ b/docs/en/hardware/plcio.rst @@ -0,0 +1,69 @@ +PLC I/O +======= + +.. include:: ../refs/hardware.plcio.ref + +PLC I/O is used to control the Input/Output (I/O) Capabilities of host devices. + +The following are the host's support for PLC I/O functions: + +.. table:: + :widths: auto + :align: center + + +-----------------+----------------+---------+ + |Controller | Digital Input | Relay | + +=================+================+=========+ + | StampPLC | |S| | |S| | + +-----------------+----------------+---------+ + + +.. |S| unicode:: U+2714 + + +UiFlow2 Example +--------------- + +Digital input control relay +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Open the |stamplc_plcio_example.m5f2| project in UiFlow2. + +This example shows how to control a relay using a digital input. + +UiFlow2 Code Block: + + |stamplc_plcio_example.png| + +Example output: + + None + + +MicroPython Example +------------------- + +Digital input control relay +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +This example shows how to control a relay using a digital input. + +MicroPython Code Block: + + .. literalinclude:: ../../../examples/hardware/plcio/stamplc_plcio_example.py + :language: python + :linenos: + +Example output: + + None + + +API +--- + +.. toctree:: + :maxdepth: 1 + + plcio.digitalinput.rst + plcio.relay.rst diff --git a/docs/en/refs/hardware.plcio.digitalinput.ref b/docs/en/refs/hardware.plcio.digitalinput.ref new file mode 100644 index 00000000..6b18e7e2 --- /dev/null +++ b/docs/en/refs/hardware.plcio.digitalinput.ref @@ -0,0 +1,15 @@ +.. |init.png| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/plcio/digital_input/init.png +.. |get_status.png| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/plcio/digital_input/get_status.png +.. |value.png| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/plcio/digital_input/value.png +.. |irq.png| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/plcio/digital_input/event.png + +.. |stamplc_digital_input_example.png| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/plcio/digital_input/stamplc_digital_input_example.png + +.. |stamplc_digital_input_example.m5f2| raw:: html + + + stamplc_digital_input_example.m5f2 + diff --git a/docs/en/refs/hardware.plcio.ref b/docs/en/refs/hardware.plcio.ref new file mode 100644 index 00000000..e6efc95d --- /dev/null +++ b/docs/en/refs/hardware.plcio.ref @@ -0,0 +1,11 @@ + +.. |stamplc_plcio_example.png| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/plcio/stamplc_plcio_example.png + +.. |stamplc_plcio_example.m5f2| raw:: html + + + stamplc_plcio_example.m5f2 + diff --git a/docs/en/refs/hardware.plcio.relay.ref b/docs/en/refs/hardware.plcio.relay.ref new file mode 100644 index 00000000..9419d465 --- /dev/null +++ b/docs/en/refs/hardware.plcio.relay.ref @@ -0,0 +1,18 @@ +.. |init.png| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/plcio/relay/init.png +.. |get_status.png| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/plcio/relay/get_status.png +.. |set_value.png| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/plcio/relay/set_value.png +.. |get_value.png| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/plcio/relay/get_value.png +.. |on.png| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/plcio/relay/on.png +.. |off.png| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/plcio/relay/off.png +.. |set_status.png| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/plcio/relay/set_status.png + +.. |stamplc_relay_example.png| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/plcio/relay/stamplc_relay_example.png + +.. |stamplc_relay_example.m5f2| raw:: html + + + stamplc_relay_example.m5f2 + diff --git a/docs/locales/zh_CN/LC_MESSAGES/hardware/plcio.digitalinput.po b/docs/locales/zh_CN/LC_MESSAGES/hardware/plcio.digitalinput.po new file mode 100644 index 00000000..f7158c44 --- /dev/null +++ b/docs/locales/zh_CN/LC_MESSAGES/hardware/plcio.digitalinput.po @@ -0,0 +1,200 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2016 - 2025 M5Stack Technology Co., Ltd +# This file is distributed under the same license as the UIFlow2 Programming +# Guide package. +# FIRST AUTHOR , 2025. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: UIFlow2 Programming Guide \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-03-06 11:08+0800\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: zh_CN\n" +"Language-Team: zh_CN \n" +"Plural-Forms: nplurals=1; plural=0;\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.16.0\n" + +#: ../../en/hardware/plcio.digitalinput.rst:2 88ac5ff0c15e41699d60ee811370157c +msgid "Digital Input" +msgstr "数字输入器" + +#: ../../en/hardware/plcio.digitalinput.rst:6 4ea7389bf78b48ef8f00a47080828719 +msgid "Digital Input is used to read the digital input of host devices." +msgstr "数字输入器用于读取主机设备的数字输入。" + +#: ../../en/hardware/plcio.digitalinput.rst:10 34167f2c6bef4558b4a24a90535effed +msgid "UiFlow2 Example" +msgstr "UiFlow2 应用示例" + +#: ../../en/hardware/plcio.digitalinput.rst:13 +#: ../../en/hardware/plcio.digitalinput.rst:32 164ba3c1f3cd43f8873b016e9bdc54c4 +#: 465ae606ad0c4d63a8f201b7e183c2b3 +msgid "Get the digital input status" +msgstr "获取数字输入状态" + +#: ../../en/hardware/plcio.digitalinput.rst:15 d75dd557a180404398c01b5600bb0197 +msgid "Open the |stamplc_digital_input_example.m5f2| project in UiFlow2." +msgstr "在 UiFlow2 中打开 |stamplc_digital_input_example.m5f2| 项目。" + +#: ../../en/hardware/plcio.digitalinput.rst:17 +#: ../../en/hardware/plcio.digitalinput.rst:34 253517e9117d4c71ba5d34a7e53a4e20 +#: 6ca384baa1ca43659c58f0b32ca5eeb6 +msgid "" +"This example demonstrates how to get the status of a digital input and " +"display the status on the screen." +msgstr "此示例演示如何获取数字输入的状态并在屏幕上显示该状态。" + +#: ../../en/hardware/plcio.digitalinput.rst:19 +#: ../../en/hardware/plcio.digitalinput.rst:57 +#: ../../en/hardware/plcio.digitalinput.rst:77 +#: ../../en/hardware/plcio.digitalinput.rst:95 +#: ../../en/hardware/plcio.digitalinput.rst:112 +#: 0e0b95072346460a8fd1f9b906553801 13aa79b50a5e4cedae326cedf29dc708 +#: 266a30cbd2ef498890c2fadd7c05e279 43f498c09e604e8d926d533620cf3699 +#: 5ae6196ead234863aafacd280b291387 +msgid "UiFlow2 Code Block:" +msgstr "UiFlow2 代码块:" + +#: ../../en/hardware/plcio.digitalinput.rst:21 89691cc5a39e443da40515a0ed85e1a6 +msgid "|stamplc_digital_input_example.png|" +msgstr "" + +#: ../../en/refs/hardware.plcio.digitalinput.ref:6 +#: 39d524ab927949368517614287268acc +msgid "stamplc_digital_input_example.png" +msgstr "" + +#: ../../en/hardware/plcio.digitalinput.rst:23 +#: ../../en/hardware/plcio.digitalinput.rst:40 bc7ff7e4e3444e279e4b27215946c80f +#: bf562185fcdf41fb859496708e0c3fa1 +msgid "Example output:" +msgstr "示例输出:" + +#: ../../en/hardware/plcio.digitalinput.rst:25 +#: ../../en/hardware/plcio.digitalinput.rst:42 7725e1d918924c80a98ca6160062a0b5 +#: cfd6ce6712b54260a0a18392f171bd59 +msgid "None" +msgstr "" + +#: ../../en/hardware/plcio.digitalinput.rst:29 c2da9ee57c864adc8e8201cd53eb176c +msgid "MicroPython Example" +msgstr "MicroPython 应用示例" + +#: ../../en/hardware/plcio.digitalinput.rst:36 +#: ../../en/hardware/plcio.digitalinput.rst:61 +#: ../../en/hardware/plcio.digitalinput.rst:81 +#: ../../en/hardware/plcio.digitalinput.rst:99 +#: ../../en/hardware/plcio.digitalinput.rst:116 +#: 62102e58b5604c40827862aa6aa2a502 64b53daffdac4555bef18cfafd77b78a +#: 6b1f14b7d2d04149b0e4c5e2907e20e7 9a2a7d471d8740b09039b4d5208c4461 +#: c7c5549ddd9547ad9d024e450232950c +msgid "MicroPython Code Block:" +msgstr "MicroPython 代码块:" + +#: ../../en/hardware/plcio.digitalinput.rst:46 4905f6afe28548348eaeeeabdf62d0e6 +msgid "**API**" +msgstr "API参考" + +#: ../../en/hardware/plcio.digitalinput.rst:49 4bef4f6efac04da5a8bc54356726ddb2 +msgid "DigitalInput" +msgstr "" + +#: ../../en/hardware/plcio.digitalinput.rst:53 7282b756d2ee4f8da186af006a963291 +msgid "Initialize a digital input object." +msgstr "初始化数字输入器对象。" + +#: ../../en/hardware/plcio.digitalinput.rst 10aaa514a678404caa8890991add6a68 +#: 3ca65b87d966419cabb3cf1311ec96f0 +msgid "Parameters" +msgstr "" + +#: ../../en/hardware/plcio.digitalinput.rst:55 0d9d837f553f4c2a99c9570f83783e6e +msgid "The ID of the digital input. The range of ID is 1-8." +msgstr "数字输入的ID。ID范围是1-8。" + +#: ../../en/hardware/plcio.digitalinput.rst:59 cdb4706fcc3f42958bf6a719cb1a8ae3 +msgid "|init.png|" +msgstr "" + +#: ../../en/refs/hardware.plcio.digitalinput.ref:1 +#: 39e5b00805134f44ac0b4b0521c6edc9 +msgid "init.png" +msgstr "" + +#: ../../en/hardware/plcio.digitalinput.rst:72 da60d199da514d26ad28a86f310f1ac6 +msgid "Get the status of the digital input." +msgstr "获取数字输入器的状态。" + +#: ../../en/hardware/plcio.digitalinput.rst 6ae136ac45784d07917ba98c5311cdb7 +#: f7f143f11e8f4fe78689e45907a1c389 +msgid "Returns" +msgstr "" + +#: ../../en/hardware/plcio.digitalinput.rst:74 d7d49c6b7a354ad1b33bbf9f00adf0b1 +msgid "The status of the digital input." +msgstr "数字输入器的状态。" + +#: ../../en/hardware/plcio.digitalinput.rst fbe9c6a280e64c01b4564feb84d2fc2f +#: fc91c6657bc949d58188bfbe923e0383 +msgid "Return type" +msgstr "" + +#: ../../en/hardware/plcio.digitalinput.rst:79 c00a8b8f9beb4147b4d323fe92f42c6b +msgid "|get_status.png|" +msgstr "" + +#: ../../en/refs/hardware.plcio.digitalinput.ref:2 +#: 052827b0f28745638858859a746a4a71 +msgid "get_status.png" +msgstr "" + +#: ../../en/hardware/plcio.digitalinput.rst:90 480a104fa9274b8b99a88c2a66d4246d +msgid "Get the value of the digital input." +msgstr "获取数字输入器的值。" + +#: ../../en/hardware/plcio.digitalinput.rst:92 2c62f5c5ef3b4bc5bd6f7d5a27b72bc2 +msgid "The value of the digital input." +msgstr "数字输入的值。" + +#: ../../en/hardware/plcio.digitalinput.rst:97 a31830a2e37c435a907285b4db983830 +msgid "|value.png|" +msgstr "" + +#: ../../en/refs/hardware.plcio.digitalinput.ref:3 +#: 4bcc2dd73a9944ffad16b9a282c23d09 +msgid "value.png" +msgstr "" + +#: ../../en/hardware/plcio.digitalinput.rst:107 +#: e18329c4719846d6b190440ff99d6226 +msgid "Enable interrupt for the pin." +msgstr "启用中断。" + +#: ../../en/hardware/plcio.digitalinput.rst:109 +#: 2f5a6d8e9fb648fc9f7d1ffe7434a963 +msgid "The interrupt handler function." +msgstr "中断处理函数。" + +#: ../../en/hardware/plcio.digitalinput.rst:110 +#: 8af8517c4cfb4414a3fa48bd3be16985 +msgid "" +"The interrupt trigger mode, DigitalInput.IRQ_FALLING or " +"DigitalInput.IRQ_RISING." +msgstr "中断触发模式,DigitalInput.IRQ_FALLING 或 DigitalInput.IRQ_RISING。" + +#: ../../en/hardware/plcio.digitalinput.rst:114 +#: 7ac0d594f07b483bbd98d10040e58032 +msgid "|irq.png|" +msgstr "" + +#: ../../en/refs/hardware.plcio.digitalinput.ref:4 +#: 0713ce43e82942219dd7636d67269b54 +msgid "irq.png" +msgstr "" + diff --git a/docs/locales/zh_CN/LC_MESSAGES/hardware/plcio.po b/docs/locales/zh_CN/LC_MESSAGES/hardware/plcio.po new file mode 100644 index 00000000..7cc9e17d --- /dev/null +++ b/docs/locales/zh_CN/LC_MESSAGES/hardware/plcio.po @@ -0,0 +1,109 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2016 - 2025 M5Stack Technology Co., Ltd +# This file is distributed under the same license as the UIFlow2 Programming +# Guide package. +# FIRST AUTHOR , 2025. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: UIFlow2 Programming Guide \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-03-06 10:54+0800\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: zh_CN\n" +"Language-Team: zh_CN \n" +"Plural-Forms: nplurals=1; plural=0;\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.16.0\n" + +#: ../../en/hardware/plcio.rst:2 e273f15232bb409a89d9f9709e1c1c97 +msgid "PLC I/O" +msgstr "" + +#: ../../en/hardware/plcio.rst:6 c80d4870d634452288da89b84120d09e +msgid "" +"PLC I/O is used to control the Input/Output (I/O) Capabilities of host " +"devices." +msgstr "PLC I/O 用于控制主机设备的输入/输出 (I/O) 功能。" + +#: ../../en/hardware/plcio.rst:8 d47973bb097b46f68f31a3244510de7b +msgid "The following are the host's support for PLC I/O functions:" +msgstr "以下是主机对PLC I/O功能的支持:" + +#: ../../en/hardware/plcio.rst:15 cb04c36816ac4edb99e74df2f0c72e8c +msgid "Controller" +msgstr "主机" + +#: ../../en/hardware/plcio.rst:15 d89e0bccd51a4591b7fd0e1ff1bbeb44 +msgid "Digital Input" +msgstr "数字输入器" + +#: ../../en/hardware/plcio.rst:15 50f01b8fe81d447a96cea2aacfc0d6ed +msgid "Relay" +msgstr "继电器" + +#: ../../en/hardware/plcio.rst:17 7fae604f835d4f7daddf9dea5660be20 +msgid "StampPLC" +msgstr "" + +#: ../../en/hardware/plcio.rst:17 1f45ebe7665c459d972a0ad457fa285d +#: 5a85b63a6554495ebd065bb15b938a21 +msgid "|S|" +msgstr "" + +#: ../../en/hardware/plcio.rst:25 c1dce40ce9164e52ae7ced5486065742 +msgid "UiFlow2 Example" +msgstr "UiFlow2 应用示例" + +#: ../../en/hardware/plcio.rst:28 ../../en/hardware/plcio.rst:47 +#: c2bf90ffc7b7481b90c008e48e4d4303 d0ab084898784c988a66c8c12c175dd6 +msgid "Digital input control relay" +msgstr "数字输入控制继电器" + +#: ../../en/hardware/plcio.rst:30 6931c0e0f391426ea6ee747ba8fa11fa +msgid "Open the |stamplc_plcio_example.m5f2| project in UiFlow2." +msgstr "在 UiFlow2 中打开 |stamplc_plcio_example.m5f2| 项目。" + +#: ../../en/hardware/plcio.rst:32 ../../en/hardware/plcio.rst:49 +#: 6073024144fd4237bd6a40a6aa013e45 f186190cccfc458aadf5bf3c92c26f5f +msgid "This example shows how to control a relay using a digital input." +msgstr "此示例演示如何使用数字输入控制继电器。" + +#: ../../en/hardware/plcio.rst:34 478e44eda5fe45aabdd6d5fd1e708e16 +msgid "UiFlow2 Code Block:" +msgstr "UiFlow2 代码块:" + +#: ../../en/hardware/plcio.rst:36 a99c625c600a461c84004039d257c912 +msgid "|stamplc_plcio_example.png|" +msgstr "" + +#: ../../en/refs/hardware.plcio.ref:2 867081067b5e4418ad2351fe55fad1fe +msgid "stamplc_plcio_example.png" +msgstr "" + +#: ../../en/hardware/plcio.rst:38 ../../en/hardware/plcio.rst:57 +#: dd15ac6b58174e178efcaff29abb582e f8561be5792b440086a5bf6d9f66ce79 +msgid "Example output:" +msgstr "示例输出:" + +#: ../../en/hardware/plcio.rst:40 ../../en/hardware/plcio.rst:59 +#: a812db24330844c9b22ba67c9bc97d79 d0fe846796e145669168530a0e5ac5a9 +msgid "None" +msgstr "" + +#: ../../en/hardware/plcio.rst:44 bb1bc9691f144fe7a6f328766cb00caa +msgid "MicroPython Example" +msgstr "MicroPython 应用示例" + +#: ../../en/hardware/plcio.rst:51 44b24587effc4ad4ac55180f5d7f9ea8 +msgid "MicroPython Code Block:" +msgstr "MicroPython 代码块:" + +#: ../../en/hardware/plcio.rst:63 07d432729e4e4aaab799038f53847e7b +msgid "API" +msgstr "API参考" + diff --git a/docs/locales/zh_CN/LC_MESSAGES/hardware/plcio.relay.po b/docs/locales/zh_CN/LC_MESSAGES/hardware/plcio.relay.po new file mode 100644 index 00000000..0a9958e9 --- /dev/null +++ b/docs/locales/zh_CN/LC_MESSAGES/hardware/plcio.relay.po @@ -0,0 +1,205 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2016 - 2025 M5Stack Technology Co., Ltd +# This file is distributed under the same license as the UIFlow2 Programming +# Guide package. +# FIRST AUTHOR , 2025. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: UIFlow2 Programming Guide \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-03-06 10:54+0800\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: zh_CN\n" +"Language-Team: zh_CN \n" +"Plural-Forms: nplurals=1; plural=0;\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.16.0\n" + +#: ../../en/hardware/plcio.relay.rst:2 ../../en/hardware/plcio.relay.rst:49 +#: 017c98f3232343d0b3fae8a95899ff4e 99959cc0d6d646b69180e852844c99fc +msgid "Relay" +msgstr "继电器" + +#: ../../en/hardware/plcio.relay.rst:6 dc504ec739f14cb19f47df30ffed2523 +msgid "Relay is used to control the relay of host devices." +msgstr "继电器用于控制主机设备的继电器。" + +#: ../../en/hardware/plcio.relay.rst:10 38714b866941483a9b12a86c364d2281 +msgid "UiFlow2 Example" +msgstr "UiFlow2 应用示例" + +#: ../../en/hardware/plcio.relay.rst:13 ../../en/hardware/plcio.relay.rst:32 +#: 49205aff822e4eea9a80b95265e8cda6 c86c5288a65f417aa6760eedc69bd3e8 +msgid "Relay control" +msgstr "继电器控制" + +#: ../../en/hardware/plcio.relay.rst:15 d4be0fe8a7bb4c7fa28218baaba9255a +msgid "Open the |stamplc_relay_example.m5f2| project in UiFlow2." +msgstr "在 UiFlow2 中打开 |stamplc_relay_example.m5f2| 项目。" + +#: ../../en/hardware/plcio.relay.rst:17 ../../en/hardware/plcio.relay.rst:34 +#: c09536753f614b938b80d31ef31a70b3 c7ee37f9cf5b48278c1a60ecc61db49a +msgid "" +"This example demonstrates how to use a button to control the state of a " +"relay and display the relay's state value on the screen." +msgstr "此示例演示如何使用按钮控制继电器的状态并在屏幕上显示继电器的状态值。" + +#: ../../en/hardware/plcio.relay.rst:19 ../../en/hardware/plcio.relay.rst:57 +#: ../../en/hardware/plcio.relay.rst:74 ../../en/hardware/plcio.relay.rst:89 +#: ../../en/hardware/plcio.relay.rst:107 ../../en/hardware/plcio.relay.rst:128 +#: ../../en/hardware/plcio.relay.rst:144 24f25354d1a746e7bb9dea8fe2630a7b +#: 313ecca88b514d7596655aeccb931148 4a7f9be5d3de48f28ab49ffe5c8037e2 +#: 96b9de97aae947f1ad9ae11ec8628a07 ac59ce327e364f198003146289392aa3 +#: d5bd8e192f594675b71a3041f3fa187e f0f939a787c44f6b840117c9e4ddd919 +msgid "UiFlow2 Code Block:" +msgstr "UiFlow2 代码块:" + +#: ../../en/hardware/plcio.relay.rst:21 4e95f0910f04415dbf13c2dca06395e8 +msgid "|stamplc_relay_example.png|" +msgstr "" + +#: ../../en/refs/hardware.plcio.relay.ref:9 9f914dc0d46a4aa0b4851f09b8d5b76f +msgid "stamplc_relay_example.png" +msgstr "" + +#: ../../en/hardware/plcio.relay.rst:23 ../../en/hardware/plcio.relay.rst:40 +#: 37276d8b1eea48ff9671f1394a6d5515 dd529c4b0eb340ac92bfe154d1466717 +msgid "Example output:" +msgstr "示例输出:" + +#: ../../en/hardware/plcio.relay.rst:25 ../../en/hardware/plcio.relay.rst:42 +#: 2c028c32691d424ca727dcbc35feebc8 e826ef2e029c4ca99090f2c3dedabd31 +msgid "None" +msgstr "" + +#: ../../en/hardware/plcio.relay.rst:29 dccbee68ec924d9eb4f87413bc71806d +msgid "MicroPython Example" +msgstr "MicroPython 应用示例" + +#: ../../en/hardware/plcio.relay.rst:36 ../../en/hardware/plcio.relay.rst:61 +#: ../../en/hardware/plcio.relay.rst:78 ../../en/hardware/plcio.relay.rst:93 +#: ../../en/hardware/plcio.relay.rst:113 ../../en/hardware/plcio.relay.rst:132 +#: ../../en/hardware/plcio.relay.rst:148 07c3be7d0ae34cef8b5d8f91bcf048fe +#: 22f1ebc5d2f7436486ff19ddec7e4607 6e9616c05e66432487474c92b059f6a1 +#: 7f13610992cb4f04bfbf1159fe9a1204 aa81383362944cfb8f2da67114bdd54e +#: ab64b2b128fa485d822d3ee5221a3dda eac2f362ba4f45f8a2f290588e69cd33 +msgid "MicroPython Code Block:" +msgstr "MicroPython 代码块:" + +#: ../../en/hardware/plcio.relay.rst:46 56751e3c471c4112b16fe8458314f5ea +msgid "**API**" +msgstr "API参考" + +#: ../../en/hardware/plcio.relay.rst:53 495846c580054ad28aae049105679336 +msgid "Initialize a relay object." +msgstr "初始化继电器对象。" + +#: ../../en/hardware/plcio.relay.rst 01446ad343d74e8aa5408abdbd647fb1 +#: e44b312621174e1d88343a387841ed1d +msgid "Parameters" +msgstr "" + +#: ../../en/hardware/plcio.relay.rst:55 149eb84bad914f36a952f70cbfb1a351 +msgid "The ID of the relay. The range of ID is 1-4." +msgstr "" + +#: ../../en/hardware/plcio.relay.rst:59 272fd6a488674f9c9617b3c8f35d3590 +msgid "|init.png|" +msgstr "继电器的ID,范围是1-4。" + +#: ../../en/refs/hardware.plcio.relay.ref:1 0e895f1f3122436d98539ebbaa5515a9 +msgid "init.png" +msgstr "" + +#: ../../en/hardware/plcio.relay.rst:72 141ba922734346b5ba4169a08f62536b +msgid "Turn on the relay." +msgstr "打开继电器" + +#: ../../en/hardware/plcio.relay.rst:76 c64c165987014bc79be991c0bb8eb475 +msgid "|on.png|" +msgstr "" + +#: ../../en/refs/hardware.plcio.relay.ref:5 fc8542786cd24edf866e2f02d465e7e4 +msgid "on.png" +msgstr "" + +#: ../../en/hardware/plcio.relay.rst:87 384a0c5c24874e0b93b32031a9aa9fd1 +msgid "Turn off the relay." +msgstr "关闭继电器" + +#: ../../en/hardware/plcio.relay.rst:91 5d1e8e6a1d414ee99e52f06d6bb08de3 +msgid "|off.png|" +msgstr "" + +#: ../../en/refs/hardware.plcio.relay.ref:6 f55d80d8d5e14e82ab0e2720733ce1f3 +msgid "off.png" +msgstr "" + +#: ../../en/hardware/plcio.relay.rst:102 f4865a4b20c74713a7d6fe7672fd5cfb +msgid "Get the value of the relay." +msgstr "获取继电器的值。" + +#: ../../en/hardware/plcio.relay.rst 3cb2b200839d4e13bd00002ff05818fc +#: bd238be89ac643839571f30716c76823 +msgid "Returns" +msgstr "" + +#: ../../en/hardware/plcio.relay.rst:104 8eab1940bb58451f87f021ac04a93d68 +msgid "The value of the relay." +msgstr "继电器的值。" + +#: ../../en/hardware/plcio.relay.rst f485a34f8a4845f3925b9ae708739b35 +#: fedd384e775949a5b9987962e2e4808c +msgid "Return type" +msgstr "" + +#: ../../en/hardware/plcio.relay.rst:109 1b3f9f9f1a9849a8b758bcef29c7482c +msgid "|set_value.png|" +msgstr "" + +#: ../../en/refs/hardware.plcio.relay.ref:3 3812f2c2a2584f2c90ea1e4d15f627ce +msgid "set_value.png" +msgstr "" + +#: ../../en/hardware/plcio.relay.rst:111 0889e1ae5d5a4fa79758a62356b394eb +msgid "|get_value.png|" +msgstr "" + +#: ../../en/refs/hardware.plcio.relay.ref:4 d973b1e12a834a9b9e182b32d4c992d7 +msgid "get_value.png" +msgstr "" + +#: ../../en/hardware/plcio.relay.rst:123 f0540bc59bad447586178bd2fc83112b +msgid "Get the status of the relay." +msgstr "获取继电器的状态。" + +#: ../../en/hardware/plcio.relay.rst:125 ../../en/hardware/plcio.relay.rst:142 +#: 30ef1f85da644442b61c5c641ebd334f f42aee7d36ca47c89d913c7ff5c08303 +msgid "The status of the relay." +msgstr "" + +#: ../../en/hardware/plcio.relay.rst:130 abbf0c16f9b44299b984821c1a5a8697 +msgid "|get_status.png|" +msgstr "" + +#: ../../en/refs/hardware.plcio.relay.ref:2 5a0a6538e8534b99af01d62d0e4292a1 +msgid "get_status.png" +msgstr "" + +#: ../../en/hardware/plcio.relay.rst:140 9cd99d5a9ece41a682e50b20d3cab431 +msgid "Set the status of the relay." +msgstr "设置继电器的状态。" + +#: ../../en/hardware/plcio.relay.rst:146 7963bc94d1554d9bbbcb7eef8d7ddcf8 +msgid "|set_status.png|" +msgstr "" + +#: ../../en/refs/hardware.plcio.relay.ref:7 0e5cde2d26e748deb58ae5fc50acba83 +msgid "set_status.png" +msgstr "" + diff --git a/examples/hardware/plcio/digital_input/stamplc_digital_input_example.m5f2 b/examples/hardware/plcio/digital_input/stamplc_digital_input_example.m5f2 new file mode 100644 index 00000000..b99ece9a --- /dev/null +++ b/examples/hardware/plcio/digital_input/stamplc_digital_input_example.m5f2 @@ -0,0 +1 @@ +{"version":"V2.0","versionNumber":"V2.2.3","type":"stamplc","components":[{"name":"screen","type":"screen","layer":0,"screenId":"builtin","screenName":"","id":"__stamplc_screen","createTime":1741226977293,"x":0,"y":0,"width":240,"height":135,"backgroundColor":"#000000","size":0,"isSelected":true},{"name":"label0","type":"label","layer":1,"screenId":"builtin","screenName":"","id":"heMhqLKy9B^Ltl5E","createTime":1741226990252,"x":112,"y":57,"color":"#ffffff","backgroundColor":"#222222","text":"label0","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false}],"resources":[{"hardware":["hardware_button","hardware_pin_button","speaker","ir","hardware_relay","digital_input"]}],"units":[],"hats":[],"bases":[],"i2cs":[],"blockly":"truedigitalinput_01label0Labeldigitalinput_0truedigitalinput_0FALLINGlabel0Labeldigitalinput_0digitalinput_0RISINGlabel0Labeldigitalinput_0","screen":[{"simulationName":"Built-in","type":"builtin","width":240,"height":135,"scale":0.49,"screenName":"","blockId":"","screenColorType":0,"id":"builtin","createTime":1741226977292}],"logicWhenNum":0,"customList":[]} \ No newline at end of file diff --git a/examples/hardware/plcio/digital_input/stamplc_digital_input_example.py b/examples/hardware/plcio/digital_input/stamplc_digital_input_example.py new file mode 100644 index 00000000..8277b9fc --- /dev/null +++ b/examples/hardware/plcio/digital_input/stamplc_digital_input_example.py @@ -0,0 +1,53 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import os, sys, io +import M5 +from M5 import * +from hardware import DigitalInput + + +label0 = None +digitalinput_0 = None + + +def digitalinput_0_falling_event(args): + global label0, digitalinput_0 + label0.setText(str(digitalinput_0.value())) + + +def digitalinput_0_rising_event(args): + global label0, digitalinput_0 + label0.setText(str(digitalinput_0.value())) + + +def setup(): + global label0, digitalinput_0 + + M5.begin() + label0 = Widgets.Label("label0", 112, 57, 1.0, 0xFFFFFF, 0x222222, Widgets.FONTS.DejaVu18) + + digitalinput_0 = DigitalInput(1) + digitalinput_0.irq(digitalinput_0_falling_event, digitalinput_0.IRQ_FALLING) + digitalinput_0.irq(digitalinput_0_rising_event, digitalinput_0.IRQ_RISING) + label0.setText(str(digitalinput_0.value())) + + +def loop(): + global label0, digitalinput_0 + M5.update() + + +if __name__ == "__main__": + try: + setup() + while True: + loop() + except (Exception, KeyboardInterrupt) as e: + try: + from utility import print_error_msg + + print_error_msg(e) + except ImportError: + print("please update to latest firmware") diff --git a/examples/hardware/plcio/relay/stamplc_relay_example.m5f2 b/examples/hardware/plcio/relay/stamplc_relay_example.m5f2 new file mode 100644 index 00000000..17d3cdd9 --- /dev/null +++ b/examples/hardware/plcio/relay/stamplc_relay_example.m5f2 @@ -0,0 +1 @@ +{"version":"V2.0","versionNumber":"V2.2.3","type":"stamplc","components":[{"name":"screen","type":"screen","layer":0,"screenId":"builtin","screenName":"","id":"__stamplc_screen","createTime":1741226586674,"x":0,"y":0,"width":240,"height":135,"backgroundColor":"#000000","size":0,"isSelected":true},{"name":"label0","type":"label","layer":1,"screenId":"builtin","screenName":"","id":"z#`fO3NNrxLL@5+8","createTime":1741226636841,"x":75,"y":28,"color":"#ffffff","backgroundColor":"#222222","text":"label0","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false}],"resources":[{"hardware":["hardware_button","hardware_pin_button","speaker","ir","hardware_relay","digital_input"]}],"units":[],"hats":[],"bases":[],"i2cs":[],"blockly":"truelabel0Labelrelay_0truerelay_01BtnAWAS_CLICKEDrelay_0BtnBWAS_CLICKEDrelay_0BtnCWAS_CLICKEDrelay_0Falserelay_0","screen":[{"simulationName":"Built-in","type":"builtin","width":240,"height":135,"scale":0.49,"screenName":"","blockId":"","screenColorType":0,"id":"builtin","createTime":1741226586674}],"logicWhenNum":0,"customList":[]} \ No newline at end of file diff --git a/examples/hardware/plcio/relay/stamplc_relay_example.py b/examples/hardware/plcio/relay/stamplc_relay_example.py new file mode 100644 index 00000000..3e4ccdec --- /dev/null +++ b/examples/hardware/plcio/relay/stamplc_relay_example.py @@ -0,0 +1,60 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import os, sys, io +import M5 +from M5 import * +from hardware import Relay + + +label0 = None +relay_0 = None + + +def btnA_wasClicked_event(state): # noqa: N802 + global label0, relay_0 + relay_0.on() + + +def btnB_wasClicked_event(state): # noqa: N802 + global label0, relay_0 + relay_0.off() + + +def btnC_wasClicked_event(state): # noqa: N802 + global label0, relay_0 + relay_0.set_status(not (relay_0.get_status())) + + +def setup(): + global label0, relay_0 + + M5.begin() + label0 = Widgets.Label("label0", 75, 28, 1.0, 0xFFFFFF, 0x222222, Widgets.FONTS.DejaVu18) + + BtnA.setCallback(type=BtnA.CB_TYPE.WAS_CLICKED, cb=btnA_wasClicked_event) + BtnB.setCallback(type=BtnB.CB_TYPE.WAS_CLICKED, cb=btnB_wasClicked_event) + BtnC.setCallback(type=BtnC.CB_TYPE.WAS_CLICKED, cb=btnC_wasClicked_event) + + relay_0 = Relay(1) + + +def loop(): + global label0, relay_0 + M5.update() + label0.setText(str(relay_0.value())) + + +if __name__ == "__main__": + try: + setup() + while True: + loop() + except (Exception, KeyboardInterrupt) as e: + try: + from utility import print_error_msg + + print_error_msg(e) + except ImportError: + print("please update to latest firmware") diff --git a/examples/hardware/plcio/stamplc_plcio_example.m5f2 b/examples/hardware/plcio/stamplc_plcio_example.m5f2 new file mode 100644 index 00000000..55d88b41 --- /dev/null +++ b/examples/hardware/plcio/stamplc_plcio_example.m5f2 @@ -0,0 +1 @@ +{"version":"V2.0","versionNumber":"V2.2.2","type":"stamplc","components":[{"name":"screen","type":"screen","layer":0,"screenId":"builtin","screenName":"","id":"__stamplc_screen","createTime":1741162399727,"x":0,"y":0,"width":240,"height":135,"backgroundColor":"#000000","size":0,"isSelected":true},{"name":"label0","type":"label","layer":1,"screenId":"builtin","screenName":"","id":"smkK1jlMhTeSjzmW","createTime":1741163411795,"x":20,"y":2,"color":"#ffffff","backgroundColor":"#222222","text":"label0","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false}],"resources":[{"hardware":["hardware_button","hardware_pin_button","speaker","ir","hardware_relay","digital_input"]}],"units":[],"hats":[],"bases":[],"i2cs":[],"blockly":"truedigitalinput_01digitalinput_12digitalinput_23digitalinput_34relay_01relay_12relay_23relay_34truedigitalinput_0FALLINGrelay_0digitalinput_0RISINGrelay_0digitalinput_1FALLINGrelay_1digitalinput_1RISINGrelay_1digitalinput_2FALLINGrelay_2digitalinput_2RISINGrelay_2digitalinput_3FALLINGrelay_3digitalinput_3RISINGrelay_3","screen":[{"simulationName":"Built-in","type":"builtin","width":240,"height":135,"scale":0.49,"screenName":"","blockId":"","screenColorType":0,"id":"builtin","createTime":1741162399727}],"logicWhenNum":0,"customList":[]} \ No newline at end of file diff --git a/examples/hardware/plcio/stamplc_plcio_example.py b/examples/hardware/plcio/stamplc_plcio_example.py new file mode 100644 index 00000000..2cb15209 --- /dev/null +++ b/examples/hardware/plcio/stamplc_plcio_example.py @@ -0,0 +1,193 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import os, sys, io +import M5 +from M5 import * +from hardware import DigitalInput +from hardware import Relay + + +label0 = None +digitalinput_0 = None +digitalinput_1 = None +digitalinput_2 = None +digitalinput_3 = None +relay_0 = None +relay_1 = None +relay_2 = None +relay_3 = None + + +def digitalinput_0_falling_event(args): + global \ + label0, \ + digitalinput_0, \ + digitalinput_1, \ + digitalinput_2, \ + digitalinput_3, \ + relay_0, \ + relay_1, \ + relay_2, \ + relay_3 + relay_0.on() + + +def digitalinput_0_rising_event(args): + global \ + label0, \ + digitalinput_0, \ + digitalinput_1, \ + digitalinput_2, \ + digitalinput_3, \ + relay_0, \ + relay_1, \ + relay_2, \ + relay_3 + relay_0.off() + + +def digitalinput_1_falling_event(args): + global \ + label0, \ + digitalinput_0, \ + digitalinput_1, \ + digitalinput_2, \ + digitalinput_3, \ + relay_0, \ + relay_1, \ + relay_2, \ + relay_3 + relay_1.on() + + +def digitalinput_1_rising_event(args): + global \ + label0, \ + digitalinput_0, \ + digitalinput_1, \ + digitalinput_2, \ + digitalinput_3, \ + relay_0, \ + relay_1, \ + relay_2, \ + relay_3 + relay_1.off() + + +def digitalinput_2_falling_event(args): + global \ + label0, \ + digitalinput_0, \ + digitalinput_1, \ + digitalinput_2, \ + digitalinput_3, \ + relay_0, \ + relay_1, \ + relay_2, \ + relay_3 + relay_2.on() + + +def digitalinput_2_rising_event(args): + global \ + label0, \ + digitalinput_0, \ + digitalinput_1, \ + digitalinput_2, \ + digitalinput_3, \ + relay_0, \ + relay_1, \ + relay_2, \ + relay_3 + relay_2.off() + + +def digitalinput_3_falling_event(args): + global \ + label0, \ + digitalinput_0, \ + digitalinput_1, \ + digitalinput_2, \ + digitalinput_3, \ + relay_0, \ + relay_1, \ + relay_2, \ + relay_3 + relay_3.on() + + +def digitalinput_3_rising_event(args): + global \ + label0, \ + digitalinput_0, \ + digitalinput_1, \ + digitalinput_2, \ + digitalinput_3, \ + relay_0, \ + relay_1, \ + relay_2, \ + relay_3 + relay_3.off() + + +def setup(): + global \ + label0, \ + digitalinput_0, \ + digitalinput_1, \ + digitalinput_2, \ + digitalinput_3, \ + relay_0, \ + relay_1, \ + relay_2, \ + relay_3 + + M5.begin() + label0 = Widgets.Label("label0", 20, 2, 1.0, 0xFFFFFF, 0x222222, Widgets.FONTS.DejaVu18) + + digitalinput_0 = DigitalInput(1) + digitalinput_0.irq(digitalinput_0_falling_event, digitalinput_0.IRQ_FALLING) + digitalinput_0.irq(digitalinput_0_rising_event, digitalinput_0.IRQ_RISING) + digitalinput_1 = DigitalInput(2) + digitalinput_1.irq(digitalinput_1_falling_event, digitalinput_1.IRQ_FALLING) + digitalinput_1.irq(digitalinput_1_rising_event, digitalinput_1.IRQ_RISING) + digitalinput_2 = DigitalInput(3) + digitalinput_2.irq(digitalinput_2_falling_event, digitalinput_2.IRQ_FALLING) + digitalinput_2.irq(digitalinput_2_rising_event, digitalinput_2.IRQ_RISING) + digitalinput_3 = DigitalInput(4) + digitalinput_3.irq(digitalinput_3_falling_event, digitalinput_3.IRQ_FALLING) + digitalinput_3.irq(digitalinput_3_rising_event, digitalinput_3.IRQ_RISING) + relay_0 = Relay(1) + relay_1 = Relay(2) + relay_2 = Relay(3) + relay_3 = Relay(4) + + +def loop(): + global \ + label0, \ + digitalinput_0, \ + digitalinput_1, \ + digitalinput_2, \ + digitalinput_3, \ + relay_0, \ + relay_1, \ + relay_2, \ + relay_3 + M5.update() + + +if __name__ == "__main__": + try: + setup() + while True: + loop() + except (Exception, KeyboardInterrupt) as e: + try: + from utility import print_error_msg + + print_error_msg(e) + except ImportError: + print("please update to latest firmware") diff --git a/m5stack/libs/hardware/plcio.py b/m5stack/libs/hardware/plcio.py index 289a2459..476bee67 100644 --- a/m5stack/libs/hardware/plcio.py +++ b/m5stack/libs/hardware/plcio.py @@ -7,7 +7,7 @@ import machine -def _plc_closure(): +def _plc_closure() -> tuple: aw = None def _aw9523_init(): @@ -44,6 +44,11 @@ def __init__(self, id) -> None: super().__init__(pid) def get_status(self) -> bool: + """Get the status of the digital input. + + :returns: The status of the digital input. + :rtype: bool + """ return bool(self.value()) class Relay(aw9523.Pin): @@ -65,8 +70,21 @@ def __init__(self, id) -> None: super().__init__(pid, mode=aw9523.Pin.OUT) def get_status(self) -> bool: + """Get the status of the relay. + + :returns: The status of the relay. + :rtype: bool + """ return bool(self.value()) + def set_status(self, status: bool) -> None: + """Set the status of the relay. + + :param status: The status to set. + :type status: bool + """ + self.value(int(status)) + return DigitalInput, Relay From 478cf3aeb47997e7e3fbbd0f0cf87bb3dcf897ef Mon Sep 17 00:00:00 2001 From: lbuque Date: Thu, 6 Mar 2025 11:49:16 +0800 Subject: [PATCH 014/322] boards: StampPLC renamed as StamPLC. Signed-off-by: lbuque --- docs/en/controllers/index.rst | 1 - m5stack/CMakeListsDefault.cmake | 2 +- m5stack/Makefile | 4 +- .../board.json | 0 .../manifest.py | 2 +- .../mpconfigboard.cmake | 4 +- .../mpconfigboard.h | 4 +- .../sdkconfig.board | 2 +- m5stack/cmodules/m5unified/m5unified.c | 2 +- .../system/{stampplc => stamplc}/applist.jpeg | Bin .../system/{stampplc => stamplc}/apprun.jpeg | Bin .../apprun/run_always_select.jpeg | Bin .../apprun/run_always_unselect.jpeg | Bin .../apprun/run_info.jpeg | Bin .../apprun/run_once_select.jpeg | Bin .../apprun/run_once_unselect.jpeg | Bin .../common/card_228x32_select.jpeg | Bin .../common/card_228x32_unselect.jpeg | Bin .../system/{stampplc => stamplc}/develop.jpeg | Bin .../develop/private.jpeg | Bin .../{stampplc => stamplc}/develop/public.jpeg | Bin .../system/{stampplc => stamplc}/ezdata.jpeg | Bin .../system/{stampplc => stamplc}/ico/a.jpeg | Bin .../system/{stampplc => stamplc}/ico/b.jpeg | Bin .../system/{stampplc => stamplc}/ico/c.jpeg | Bin .../system/{stampplc => stamplc}/ico/d.jpeg | Bin .../system/{stampplc => stamplc}/ico/e.jpeg | Bin .../system/{stampplc => stamplc}/ico/f.jpeg | Bin .../system/{stampplc => stamplc}/ico/g.jpeg | Bin .../system/{stampplc => stamplc}/ico/h.jpeg | Bin .../system/{stampplc => stamplc}/ico/i.jpeg | Bin .../system/{stampplc => stamplc}/ico/j.jpeg | Bin .../system/{stampplc => stamplc}/ico/k.jpeg | Bin .../system/{stampplc => stamplc}/ico/l.jpeg | Bin .../system/{stampplc => stamplc}/ico/m.jpeg | Bin .../system/{stampplc => stamplc}/ico/n.jpeg | Bin .../system/{stampplc => stamplc}/ico/o.jpeg | Bin .../system/{stampplc => stamplc}/ico/p.jpeg | Bin .../system/{stampplc => stamplc}/ico/q.jpeg | Bin .../system/{stampplc => stamplc}/ico/r.jpeg | Bin .../system/{stampplc => stamplc}/ico/s.jpeg | Bin .../system/{stampplc => stamplc}/ico/t.jpeg | Bin .../system/{stampplc => stamplc}/ico/u.jpeg | Bin .../system/{stampplc => stamplc}/ico/v.jpeg | Bin .../system/{stampplc => stamplc}/ico/w.jpeg | Bin .../system/{stampplc => stamplc}/ico/x.jpeg | Bin .../system/{stampplc => stamplc}/ico/y.jpeg | Bin .../system/{stampplc => stamplc}/ico/z.jpeg | Bin .../fs/system/{stampplc => stamplc}/left.jpeg | Bin .../system/{stampplc => stamplc}/right.jpeg | Bin .../system/{stampplc => stamplc}/setting.jpeg | Bin .../setting/caret_right.jpeg | Bin .../setting/general.jpeg | Bin .../setting/general/disable.jpeg | Bin .../setting/general/enable.jpeg | Bin .../{stampplc => stamplc}/setting/wlan.jpeg | Bin .../setting/wlan/input_default.jpeg | Bin .../setting/wlan/input_psk.jpeg | Bin .../setting/wlan/input_server.jpeg | Bin .../setting/wlan/input_ssid.jpeg | Bin .../setting/wlan/submit_select.jpeg | Bin .../setting/wlan/submit_unselect.jpeg | Bin .../statusbar/battery/black.jpeg | Bin .../statusbar/battery/black_charge.jpeg | Bin .../statusbar/battery/green.jpeg | Bin .../statusbar/battery/green_charge.jpeg | Bin .../statusbar/battery/red.jpeg | Bin .../statusbar/battery/red_charge.jpeg | Bin .../statusbar/cloud/empty.jpeg | Bin .../statusbar/cloud/error.jpeg | Bin .../statusbar/cloud/green.jpeg | Bin .../statusbar/title_blue.jpeg | Bin .../statusbar/wifi/disconnected.jpeg | Bin .../statusbar/wifi/empty.jpeg | Bin .../statusbar/wifi/good.jpeg | Bin .../statusbar/wifi/mid.jpeg | Bin .../statusbar/wifi/worse.jpeg | Bin m5stack/libs/hardware/plcio.py | 2 +- m5stack/modules/startup/__init__.py | 4 +- m5stack/modules/startup/manifest_stamplc.py | 23 ++++++ m5stack/modules/startup/manifest_stampplc.py | 23 ------ .../startup/{stampplc => stamplc}/__init__.py | 0 .../startup/{stampplc => stamplc}/app_base.py | 0 .../{stampplc => stamplc}/apps/app_list.py | 10 +-- .../{stampplc => stamplc}/apps/app_run.py | 0 .../startup/{stampplc => stamplc}/apps/dev.py | 0 .../{stampplc => stamplc}/apps/ezdata.py | 0 .../{stampplc => stamplc}/apps/launcher.py | 0 .../{stampplc => stamplc}/apps/settings.py | 0 .../{stampplc => stamplc}/apps/statusbar.py | 0 .../{stampplc => stamplc}/framework.py | 0 m5stack/modules/startup/stamplc/res.py | 74 ++++++++++++++++++ m5stack/modules/startup/stampplc/res.py | 74 ------------------ 93 files changed, 115 insertions(+), 116 deletions(-) rename m5stack/boards/{M5STACK_StampPLC => M5STACK_StamPLC}/board.json (100%) rename m5stack/boards/{M5STACK_StampPLC => M5STACK_StamPLC}/manifest.py (56%) rename m5stack/boards/{M5STACK_StampPLC => M5STACK_StamPLC}/mpconfigboard.cmake (89%) rename m5stack/boards/{M5STACK_StampPLC => M5STACK_StamPLC}/mpconfigboard.h (78%) rename m5stack/boards/{M5STACK_StampPLC => M5STACK_StamPLC}/sdkconfig.board (92%) rename m5stack/fs/system/{stampplc => stamplc}/applist.jpeg (100%) rename m5stack/fs/system/{stampplc => stamplc}/apprun.jpeg (100%) rename m5stack/fs/system/{stampplc => stamplc}/apprun/run_always_select.jpeg (100%) rename m5stack/fs/system/{stampplc => stamplc}/apprun/run_always_unselect.jpeg (100%) rename m5stack/fs/system/{stampplc => stamplc}/apprun/run_info.jpeg (100%) rename m5stack/fs/system/{stampplc => stamplc}/apprun/run_once_select.jpeg (100%) rename m5stack/fs/system/{stampplc => stamplc}/apprun/run_once_unselect.jpeg (100%) rename m5stack/fs/system/{stampplc => stamplc}/common/card_228x32_select.jpeg (100%) rename m5stack/fs/system/{stampplc => stamplc}/common/card_228x32_unselect.jpeg (100%) rename m5stack/fs/system/{stampplc => stamplc}/develop.jpeg (100%) rename m5stack/fs/system/{stampplc => stamplc}/develop/private.jpeg (100%) rename m5stack/fs/system/{stampplc => stamplc}/develop/public.jpeg (100%) rename m5stack/fs/system/{stampplc => stamplc}/ezdata.jpeg (100%) rename m5stack/fs/system/{stampplc => stamplc}/ico/a.jpeg (100%) rename m5stack/fs/system/{stampplc => stamplc}/ico/b.jpeg (100%) rename m5stack/fs/system/{stampplc => stamplc}/ico/c.jpeg (100%) rename m5stack/fs/system/{stampplc => stamplc}/ico/d.jpeg (100%) rename m5stack/fs/system/{stampplc => stamplc}/ico/e.jpeg (100%) rename m5stack/fs/system/{stampplc => stamplc}/ico/f.jpeg (100%) rename m5stack/fs/system/{stampplc => stamplc}/ico/g.jpeg (100%) rename m5stack/fs/system/{stampplc => stamplc}/ico/h.jpeg (100%) rename m5stack/fs/system/{stampplc => stamplc}/ico/i.jpeg (100%) rename m5stack/fs/system/{stampplc => stamplc}/ico/j.jpeg (100%) rename m5stack/fs/system/{stampplc => stamplc}/ico/k.jpeg (100%) rename m5stack/fs/system/{stampplc => stamplc}/ico/l.jpeg (100%) rename m5stack/fs/system/{stampplc => stamplc}/ico/m.jpeg (100%) rename m5stack/fs/system/{stampplc => stamplc}/ico/n.jpeg (100%) rename m5stack/fs/system/{stampplc => stamplc}/ico/o.jpeg (100%) rename m5stack/fs/system/{stampplc => stamplc}/ico/p.jpeg (100%) rename m5stack/fs/system/{stampplc => stamplc}/ico/q.jpeg (100%) rename m5stack/fs/system/{stampplc => stamplc}/ico/r.jpeg (100%) rename m5stack/fs/system/{stampplc => stamplc}/ico/s.jpeg (100%) rename m5stack/fs/system/{stampplc => stamplc}/ico/t.jpeg (100%) rename m5stack/fs/system/{stampplc => stamplc}/ico/u.jpeg (100%) rename m5stack/fs/system/{stampplc => stamplc}/ico/v.jpeg (100%) rename m5stack/fs/system/{stampplc => stamplc}/ico/w.jpeg (100%) rename m5stack/fs/system/{stampplc => stamplc}/ico/x.jpeg (100%) rename m5stack/fs/system/{stampplc => stamplc}/ico/y.jpeg (100%) rename m5stack/fs/system/{stampplc => stamplc}/ico/z.jpeg (100%) rename m5stack/fs/system/{stampplc => stamplc}/left.jpeg (100%) rename m5stack/fs/system/{stampplc => stamplc}/right.jpeg (100%) rename m5stack/fs/system/{stampplc => stamplc}/setting.jpeg (100%) rename m5stack/fs/system/{stampplc => stamplc}/setting/caret_right.jpeg (100%) rename m5stack/fs/system/{stampplc => stamplc}/setting/general.jpeg (100%) rename m5stack/fs/system/{stampplc => stamplc}/setting/general/disable.jpeg (100%) rename m5stack/fs/system/{stampplc => stamplc}/setting/general/enable.jpeg (100%) rename m5stack/fs/system/{stampplc => stamplc}/setting/wlan.jpeg (100%) rename m5stack/fs/system/{stampplc => stamplc}/setting/wlan/input_default.jpeg (100%) rename m5stack/fs/system/{stampplc => stamplc}/setting/wlan/input_psk.jpeg (100%) rename m5stack/fs/system/{stampplc => stamplc}/setting/wlan/input_server.jpeg (100%) rename m5stack/fs/system/{stampplc => stamplc}/setting/wlan/input_ssid.jpeg (100%) rename m5stack/fs/system/{stampplc => stamplc}/setting/wlan/submit_select.jpeg (100%) rename m5stack/fs/system/{stampplc => stamplc}/setting/wlan/submit_unselect.jpeg (100%) rename m5stack/fs/system/{stampplc => stamplc}/statusbar/battery/black.jpeg (100%) rename m5stack/fs/system/{stampplc => stamplc}/statusbar/battery/black_charge.jpeg (100%) rename m5stack/fs/system/{stampplc => stamplc}/statusbar/battery/green.jpeg (100%) rename m5stack/fs/system/{stampplc => stamplc}/statusbar/battery/green_charge.jpeg (100%) rename m5stack/fs/system/{stampplc => stamplc}/statusbar/battery/red.jpeg (100%) rename m5stack/fs/system/{stampplc => stamplc}/statusbar/battery/red_charge.jpeg (100%) rename m5stack/fs/system/{stampplc => stamplc}/statusbar/cloud/empty.jpeg (100%) rename m5stack/fs/system/{stampplc => stamplc}/statusbar/cloud/error.jpeg (100%) rename m5stack/fs/system/{stampplc => stamplc}/statusbar/cloud/green.jpeg (100%) rename m5stack/fs/system/{stampplc => stamplc}/statusbar/title_blue.jpeg (100%) rename m5stack/fs/system/{stampplc => stamplc}/statusbar/wifi/disconnected.jpeg (100%) rename m5stack/fs/system/{stampplc => stamplc}/statusbar/wifi/empty.jpeg (100%) rename m5stack/fs/system/{stampplc => stamplc}/statusbar/wifi/good.jpeg (100%) rename m5stack/fs/system/{stampplc => stamplc}/statusbar/wifi/mid.jpeg (100%) rename m5stack/fs/system/{stampplc => stamplc}/statusbar/wifi/worse.jpeg (100%) create mode 100644 m5stack/modules/startup/manifest_stamplc.py delete mode 100644 m5stack/modules/startup/manifest_stampplc.py rename m5stack/modules/startup/{stampplc => stamplc}/__init__.py (100%) rename m5stack/modules/startup/{stampplc => stamplc}/app_base.py (100%) rename m5stack/modules/startup/{stampplc => stamplc}/apps/app_list.py (96%) rename m5stack/modules/startup/{stampplc => stamplc}/apps/app_run.py (100%) rename m5stack/modules/startup/{stampplc => stamplc}/apps/dev.py (100%) rename m5stack/modules/startup/{stampplc => stamplc}/apps/ezdata.py (100%) rename m5stack/modules/startup/{stampplc => stamplc}/apps/launcher.py (100%) rename m5stack/modules/startup/{stampplc => stamplc}/apps/settings.py (100%) rename m5stack/modules/startup/{stampplc => stamplc}/apps/statusbar.py (100%) rename m5stack/modules/startup/{stampplc => stamplc}/framework.py (100%) create mode 100644 m5stack/modules/startup/stamplc/res.py delete mode 100644 m5stack/modules/startup/stampplc/res.py diff --git a/docs/en/controllers/index.rst b/docs/en/controllers/index.rst index cb009e2d..cf98ea67 100644 --- a/docs/en/controllers/index.rst +++ b/docs/en/controllers/index.rst @@ -14,4 +14,3 @@ Controllers airq.rst paper.rst dinmeter.rst - stampplc.rst diff --git a/m5stack/CMakeListsDefault.cmake b/m5stack/CMakeListsDefault.cmake index f43cc71c..63ed0ac0 100644 --- a/m5stack/CMakeListsDefault.cmake +++ b/m5stack/CMakeListsDefault.cmake @@ -137,7 +137,7 @@ if ( OR BOARD_TYPE STREQUAL "fire" OR BOARD_TYPE STREQUAL "capsule" OR BOARD_TYPE STREQUAL "tough" - OR BOARD_TYPE STREQUAL "stampplc" + OR BOARD_TYPE STREQUAL "stamplc" ) LIST(APPEND MICROPY_SOURCE_PORT ${PROJECT_DIR}/machine_hw_spi.c) LIST(APPEND MICROPY_SOURCE_PORT ${PROJECT_DIR}/machine_sdcard.c) diff --git a/m5stack/Makefile b/m5stack/Makefile index 9ac760c6..b8dd1d71 100644 --- a/m5stack/Makefile +++ b/m5stack/Makefile @@ -40,7 +40,7 @@ boards := \ M5STACK_AtomU:atomu \ M5STACK_Atom_Echo:atomecho \ M5STACK_AtomS3R:atoms3r \ - M5STACK_StampPLC:stampplc + M5STACK_StamPLC:stamplc define find_board $(if $(filter $(1):%,$(boards)),$(word 2,$(subst :, ,$(filter $(1):%,$(boards)))),none) @@ -77,7 +77,7 @@ BOARD_TYPE_DEF := \ atomu \ atomecho \ atoms3r \ - stampplc + stamplc # Select the board type to build, default is None # This value affects which folder in the "./fs/system/" directory is pack into "fs-system.bin" diff --git a/m5stack/boards/M5STACK_StampPLC/board.json b/m5stack/boards/M5STACK_StamPLC/board.json similarity index 100% rename from m5stack/boards/M5STACK_StampPLC/board.json rename to m5stack/boards/M5STACK_StamPLC/board.json diff --git a/m5stack/boards/M5STACK_StampPLC/manifest.py b/m5stack/boards/M5STACK_StamPLC/manifest.py similarity index 56% rename from m5stack/boards/M5STACK_StampPLC/manifest.py rename to m5stack/boards/M5STACK_StamPLC/manifest.py index 6e3f97cc..3f8a311b 100644 --- a/m5stack/boards/M5STACK_StampPLC/manifest.py +++ b/m5stack/boards/M5STACK_StamPLC/manifest.py @@ -2,4 +2,4 @@ # # SPDX-License-Identifier: MIT -include("$(MPY_DIR)/../m5stack/modules/startup/manifest_stampplc.py") +include("$(MPY_DIR)/../m5stack/modules/startup/manifest_stamplc.py") diff --git a/m5stack/boards/M5STACK_StampPLC/mpconfigboard.cmake b/m5stack/boards/M5STACK_StamPLC/mpconfigboard.cmake similarity index 89% rename from m5stack/boards/M5STACK_StampPLC/mpconfigboard.cmake rename to m5stack/boards/M5STACK_StamPLC/mpconfigboard.cmake index b62ad2cf..1aa5839a 100644 --- a/m5stack/boards/M5STACK_StampPLC/mpconfigboard.cmake +++ b/m5stack/boards/M5STACK_StamPLC/mpconfigboard.cmake @@ -4,7 +4,7 @@ set(IDF_TARGET esp32s3) -# stampplc https://github.com/m5stack/m5stack-board-id/blob/main/board.csv#L29 +# stamplc https://github.com/m5stack/m5stack-board-id/blob/main/board.csv#L29 set(BOARD_ID 21) set(SDKCONFIG_DEFAULTS @@ -17,7 +17,7 @@ set(SDKCONFIG_DEFAULTS ./boards/sdkconfig.usb_cdc ./boards/sdkconfig.flash_8mb ./boards/sdkconfig.freertos - ./boards/M5STACK_StampPLC/sdkconfig.board + ./boards/M5STACK_StamPLC/sdkconfig.board ) # If not enable LVGL, ignore this... diff --git a/m5stack/boards/M5STACK_StampPLC/mpconfigboard.h b/m5stack/boards/M5STACK_StamPLC/mpconfigboard.h similarity index 78% rename from m5stack/boards/M5STACK_StampPLC/mpconfigboard.h rename to m5stack/boards/M5STACK_StamPLC/mpconfigboard.h index cb159258..1cab2793 100644 --- a/m5stack/boards/M5STACK_StampPLC/mpconfigboard.h +++ b/m5stack/boards/M5STACK_StamPLC/mpconfigboard.h @@ -4,7 +4,7 @@ * SPDX-License-Identifier: MIT */ -#define MICROPY_HW_BOARD_NAME "M5STACK StampPLC" +#define MICROPY_HW_BOARD_NAME "M5STACK StamPLC" #define MICROPY_HW_MCU_NAME "ESP32-S3-FN8" #define MICROPY_PY_MACHINE_DAC (0) @@ -15,7 +15,7 @@ #define MICROPY_HW_I2C0_SCL (9) #define MICROPY_HW_I2C0_SDA (8) -#define MICROPY_HW_USB_CDC_INTERFACE_STRING "M5Stack StampPLC(UiFlow2)" +#define MICROPY_HW_USB_CDC_INTERFACE_STRING "M5Stack StamPLC(UiFlow2)" // If not enable LVGL, ignore this... #include "./../mpconfiglvgl.h" diff --git a/m5stack/boards/M5STACK_StampPLC/sdkconfig.board b/m5stack/boards/M5STACK_StamPLC/sdkconfig.board similarity index 92% rename from m5stack/boards/M5STACK_StampPLC/sdkconfig.board rename to m5stack/boards/M5STACK_StamPLC/sdkconfig.board index deacab52..0d9e6e2f 100644 --- a/m5stack/boards/M5STACK_StampPLC/sdkconfig.board +++ b/m5stack/boards/M5STACK_StamPLC/sdkconfig.board @@ -13,7 +13,7 @@ CONFIG_SPIRAM_MEMTEST= # CONFIG_FREERTOS_UNICORE=y # M5STACK UiFlow USB description -CONFIG_TINYUSB_DESC_CDC_STRING="M5Stack StampPLC(UiFlow2)" +CONFIG_TINYUSB_DESC_CDC_STRING="M5Stack StamPLC(UiFlow2)" # SSL CONFIG_MBEDTLS_AES_USE_INTERRUPT=n diff --git a/m5stack/cmodules/m5unified/m5unified.c b/m5stack/cmodules/m5unified/m5unified.c index 68f1dc06..cbd44725 100644 --- a/m5stack/cmodules/m5unified/m5unified.c +++ b/m5stack/cmodules/m5unified/m5unified.c @@ -28,7 +28,7 @@ static const mp_rom_map_elem_t m5_board_member_table[] = { { MP_ROM_QSTR(MP_QSTR_M5AirQ), MP_ROM_INT(15) }, { MP_ROM_QSTR(MP_QSTR_M5AtomS3R), MP_ROM_INT(18) }, { MP_ROM_QSTR(MP_QSTR_M5PaperS3), MP_ROM_INT(19) }, - { MP_ROM_QSTR(MP_QSTR_M5StampPLC), MP_ROM_INT(21) }, + { MP_ROM_QSTR(MP_QSTR_M5StamPLC), MP_ROM_INT(21) }, // non display boards { MP_ROM_QSTR(MP_QSTR_M5Atom), MP_ROM_INT(128) }, { MP_ROM_QSTR(MP_QSTR_M5AtomPsram), MP_ROM_INT(129) }, diff --git a/m5stack/fs/system/stampplc/applist.jpeg b/m5stack/fs/system/stamplc/applist.jpeg similarity index 100% rename from m5stack/fs/system/stampplc/applist.jpeg rename to m5stack/fs/system/stamplc/applist.jpeg diff --git a/m5stack/fs/system/stampplc/apprun.jpeg b/m5stack/fs/system/stamplc/apprun.jpeg similarity index 100% rename from m5stack/fs/system/stampplc/apprun.jpeg rename to m5stack/fs/system/stamplc/apprun.jpeg diff --git a/m5stack/fs/system/stampplc/apprun/run_always_select.jpeg b/m5stack/fs/system/stamplc/apprun/run_always_select.jpeg similarity index 100% rename from m5stack/fs/system/stampplc/apprun/run_always_select.jpeg rename to m5stack/fs/system/stamplc/apprun/run_always_select.jpeg diff --git a/m5stack/fs/system/stampplc/apprun/run_always_unselect.jpeg b/m5stack/fs/system/stamplc/apprun/run_always_unselect.jpeg similarity index 100% rename from m5stack/fs/system/stampplc/apprun/run_always_unselect.jpeg rename to m5stack/fs/system/stamplc/apprun/run_always_unselect.jpeg diff --git a/m5stack/fs/system/stampplc/apprun/run_info.jpeg b/m5stack/fs/system/stamplc/apprun/run_info.jpeg similarity index 100% rename from m5stack/fs/system/stampplc/apprun/run_info.jpeg rename to m5stack/fs/system/stamplc/apprun/run_info.jpeg diff --git a/m5stack/fs/system/stampplc/apprun/run_once_select.jpeg b/m5stack/fs/system/stamplc/apprun/run_once_select.jpeg similarity index 100% rename from m5stack/fs/system/stampplc/apprun/run_once_select.jpeg rename to m5stack/fs/system/stamplc/apprun/run_once_select.jpeg diff --git a/m5stack/fs/system/stampplc/apprun/run_once_unselect.jpeg b/m5stack/fs/system/stamplc/apprun/run_once_unselect.jpeg similarity index 100% rename from m5stack/fs/system/stampplc/apprun/run_once_unselect.jpeg rename to m5stack/fs/system/stamplc/apprun/run_once_unselect.jpeg diff --git a/m5stack/fs/system/stampplc/common/card_228x32_select.jpeg b/m5stack/fs/system/stamplc/common/card_228x32_select.jpeg similarity index 100% rename from m5stack/fs/system/stampplc/common/card_228x32_select.jpeg rename to m5stack/fs/system/stamplc/common/card_228x32_select.jpeg diff --git a/m5stack/fs/system/stampplc/common/card_228x32_unselect.jpeg b/m5stack/fs/system/stamplc/common/card_228x32_unselect.jpeg similarity index 100% rename from m5stack/fs/system/stampplc/common/card_228x32_unselect.jpeg rename to m5stack/fs/system/stamplc/common/card_228x32_unselect.jpeg diff --git a/m5stack/fs/system/stampplc/develop.jpeg b/m5stack/fs/system/stamplc/develop.jpeg similarity index 100% rename from m5stack/fs/system/stampplc/develop.jpeg rename to m5stack/fs/system/stamplc/develop.jpeg diff --git a/m5stack/fs/system/stampplc/develop/private.jpeg b/m5stack/fs/system/stamplc/develop/private.jpeg similarity index 100% rename from m5stack/fs/system/stampplc/develop/private.jpeg rename to m5stack/fs/system/stamplc/develop/private.jpeg diff --git a/m5stack/fs/system/stampplc/develop/public.jpeg b/m5stack/fs/system/stamplc/develop/public.jpeg similarity index 100% rename from m5stack/fs/system/stampplc/develop/public.jpeg rename to m5stack/fs/system/stamplc/develop/public.jpeg diff --git a/m5stack/fs/system/stampplc/ezdata.jpeg b/m5stack/fs/system/stamplc/ezdata.jpeg similarity index 100% rename from m5stack/fs/system/stampplc/ezdata.jpeg rename to m5stack/fs/system/stamplc/ezdata.jpeg diff --git a/m5stack/fs/system/stampplc/ico/a.jpeg b/m5stack/fs/system/stamplc/ico/a.jpeg similarity index 100% rename from m5stack/fs/system/stampplc/ico/a.jpeg rename to m5stack/fs/system/stamplc/ico/a.jpeg diff --git a/m5stack/fs/system/stampplc/ico/b.jpeg b/m5stack/fs/system/stamplc/ico/b.jpeg similarity index 100% rename from m5stack/fs/system/stampplc/ico/b.jpeg rename to m5stack/fs/system/stamplc/ico/b.jpeg diff --git a/m5stack/fs/system/stampplc/ico/c.jpeg b/m5stack/fs/system/stamplc/ico/c.jpeg similarity index 100% rename from m5stack/fs/system/stampplc/ico/c.jpeg rename to m5stack/fs/system/stamplc/ico/c.jpeg diff --git a/m5stack/fs/system/stampplc/ico/d.jpeg b/m5stack/fs/system/stamplc/ico/d.jpeg similarity index 100% rename from m5stack/fs/system/stampplc/ico/d.jpeg rename to m5stack/fs/system/stamplc/ico/d.jpeg diff --git a/m5stack/fs/system/stampplc/ico/e.jpeg b/m5stack/fs/system/stamplc/ico/e.jpeg similarity index 100% rename from m5stack/fs/system/stampplc/ico/e.jpeg rename to m5stack/fs/system/stamplc/ico/e.jpeg diff --git a/m5stack/fs/system/stampplc/ico/f.jpeg b/m5stack/fs/system/stamplc/ico/f.jpeg similarity index 100% rename from m5stack/fs/system/stampplc/ico/f.jpeg rename to m5stack/fs/system/stamplc/ico/f.jpeg diff --git a/m5stack/fs/system/stampplc/ico/g.jpeg b/m5stack/fs/system/stamplc/ico/g.jpeg similarity index 100% rename from m5stack/fs/system/stampplc/ico/g.jpeg rename to m5stack/fs/system/stamplc/ico/g.jpeg diff --git a/m5stack/fs/system/stampplc/ico/h.jpeg b/m5stack/fs/system/stamplc/ico/h.jpeg similarity index 100% rename from m5stack/fs/system/stampplc/ico/h.jpeg rename to m5stack/fs/system/stamplc/ico/h.jpeg diff --git a/m5stack/fs/system/stampplc/ico/i.jpeg b/m5stack/fs/system/stamplc/ico/i.jpeg similarity index 100% rename from m5stack/fs/system/stampplc/ico/i.jpeg rename to m5stack/fs/system/stamplc/ico/i.jpeg diff --git a/m5stack/fs/system/stampplc/ico/j.jpeg b/m5stack/fs/system/stamplc/ico/j.jpeg similarity index 100% rename from m5stack/fs/system/stampplc/ico/j.jpeg rename to m5stack/fs/system/stamplc/ico/j.jpeg diff --git a/m5stack/fs/system/stampplc/ico/k.jpeg b/m5stack/fs/system/stamplc/ico/k.jpeg similarity index 100% rename from m5stack/fs/system/stampplc/ico/k.jpeg rename to m5stack/fs/system/stamplc/ico/k.jpeg diff --git a/m5stack/fs/system/stampplc/ico/l.jpeg b/m5stack/fs/system/stamplc/ico/l.jpeg similarity index 100% rename from m5stack/fs/system/stampplc/ico/l.jpeg rename to m5stack/fs/system/stamplc/ico/l.jpeg diff --git a/m5stack/fs/system/stampplc/ico/m.jpeg b/m5stack/fs/system/stamplc/ico/m.jpeg similarity index 100% rename from m5stack/fs/system/stampplc/ico/m.jpeg rename to m5stack/fs/system/stamplc/ico/m.jpeg diff --git a/m5stack/fs/system/stampplc/ico/n.jpeg b/m5stack/fs/system/stamplc/ico/n.jpeg similarity index 100% rename from m5stack/fs/system/stampplc/ico/n.jpeg rename to m5stack/fs/system/stamplc/ico/n.jpeg diff --git a/m5stack/fs/system/stampplc/ico/o.jpeg b/m5stack/fs/system/stamplc/ico/o.jpeg similarity index 100% rename from m5stack/fs/system/stampplc/ico/o.jpeg rename to m5stack/fs/system/stamplc/ico/o.jpeg diff --git a/m5stack/fs/system/stampplc/ico/p.jpeg b/m5stack/fs/system/stamplc/ico/p.jpeg similarity index 100% rename from m5stack/fs/system/stampplc/ico/p.jpeg rename to m5stack/fs/system/stamplc/ico/p.jpeg diff --git a/m5stack/fs/system/stampplc/ico/q.jpeg b/m5stack/fs/system/stamplc/ico/q.jpeg similarity index 100% rename from m5stack/fs/system/stampplc/ico/q.jpeg rename to m5stack/fs/system/stamplc/ico/q.jpeg diff --git a/m5stack/fs/system/stampplc/ico/r.jpeg b/m5stack/fs/system/stamplc/ico/r.jpeg similarity index 100% rename from m5stack/fs/system/stampplc/ico/r.jpeg rename to m5stack/fs/system/stamplc/ico/r.jpeg diff --git a/m5stack/fs/system/stampplc/ico/s.jpeg b/m5stack/fs/system/stamplc/ico/s.jpeg similarity index 100% rename from m5stack/fs/system/stampplc/ico/s.jpeg rename to m5stack/fs/system/stamplc/ico/s.jpeg diff --git a/m5stack/fs/system/stampplc/ico/t.jpeg b/m5stack/fs/system/stamplc/ico/t.jpeg similarity index 100% rename from m5stack/fs/system/stampplc/ico/t.jpeg rename to m5stack/fs/system/stamplc/ico/t.jpeg diff --git a/m5stack/fs/system/stampplc/ico/u.jpeg b/m5stack/fs/system/stamplc/ico/u.jpeg similarity index 100% rename from m5stack/fs/system/stampplc/ico/u.jpeg rename to m5stack/fs/system/stamplc/ico/u.jpeg diff --git a/m5stack/fs/system/stampplc/ico/v.jpeg b/m5stack/fs/system/stamplc/ico/v.jpeg similarity index 100% rename from m5stack/fs/system/stampplc/ico/v.jpeg rename to m5stack/fs/system/stamplc/ico/v.jpeg diff --git a/m5stack/fs/system/stampplc/ico/w.jpeg b/m5stack/fs/system/stamplc/ico/w.jpeg similarity index 100% rename from m5stack/fs/system/stampplc/ico/w.jpeg rename to m5stack/fs/system/stamplc/ico/w.jpeg diff --git a/m5stack/fs/system/stampplc/ico/x.jpeg b/m5stack/fs/system/stamplc/ico/x.jpeg similarity index 100% rename from m5stack/fs/system/stampplc/ico/x.jpeg rename to m5stack/fs/system/stamplc/ico/x.jpeg diff --git a/m5stack/fs/system/stampplc/ico/y.jpeg b/m5stack/fs/system/stamplc/ico/y.jpeg similarity index 100% rename from m5stack/fs/system/stampplc/ico/y.jpeg rename to m5stack/fs/system/stamplc/ico/y.jpeg diff --git a/m5stack/fs/system/stampplc/ico/z.jpeg b/m5stack/fs/system/stamplc/ico/z.jpeg similarity index 100% rename from m5stack/fs/system/stampplc/ico/z.jpeg rename to m5stack/fs/system/stamplc/ico/z.jpeg diff --git a/m5stack/fs/system/stampplc/left.jpeg b/m5stack/fs/system/stamplc/left.jpeg similarity index 100% rename from m5stack/fs/system/stampplc/left.jpeg rename to m5stack/fs/system/stamplc/left.jpeg diff --git a/m5stack/fs/system/stampplc/right.jpeg b/m5stack/fs/system/stamplc/right.jpeg similarity index 100% rename from m5stack/fs/system/stampplc/right.jpeg rename to m5stack/fs/system/stamplc/right.jpeg diff --git a/m5stack/fs/system/stampplc/setting.jpeg b/m5stack/fs/system/stamplc/setting.jpeg similarity index 100% rename from m5stack/fs/system/stampplc/setting.jpeg rename to m5stack/fs/system/stamplc/setting.jpeg diff --git a/m5stack/fs/system/stampplc/setting/caret_right.jpeg b/m5stack/fs/system/stamplc/setting/caret_right.jpeg similarity index 100% rename from m5stack/fs/system/stampplc/setting/caret_right.jpeg rename to m5stack/fs/system/stamplc/setting/caret_right.jpeg diff --git a/m5stack/fs/system/stampplc/setting/general.jpeg b/m5stack/fs/system/stamplc/setting/general.jpeg similarity index 100% rename from m5stack/fs/system/stampplc/setting/general.jpeg rename to m5stack/fs/system/stamplc/setting/general.jpeg diff --git a/m5stack/fs/system/stampplc/setting/general/disable.jpeg b/m5stack/fs/system/stamplc/setting/general/disable.jpeg similarity index 100% rename from m5stack/fs/system/stampplc/setting/general/disable.jpeg rename to m5stack/fs/system/stamplc/setting/general/disable.jpeg diff --git a/m5stack/fs/system/stampplc/setting/general/enable.jpeg b/m5stack/fs/system/stamplc/setting/general/enable.jpeg similarity index 100% rename from m5stack/fs/system/stampplc/setting/general/enable.jpeg rename to m5stack/fs/system/stamplc/setting/general/enable.jpeg diff --git a/m5stack/fs/system/stampplc/setting/wlan.jpeg b/m5stack/fs/system/stamplc/setting/wlan.jpeg similarity index 100% rename from m5stack/fs/system/stampplc/setting/wlan.jpeg rename to m5stack/fs/system/stamplc/setting/wlan.jpeg diff --git a/m5stack/fs/system/stampplc/setting/wlan/input_default.jpeg b/m5stack/fs/system/stamplc/setting/wlan/input_default.jpeg similarity index 100% rename from m5stack/fs/system/stampplc/setting/wlan/input_default.jpeg rename to m5stack/fs/system/stamplc/setting/wlan/input_default.jpeg diff --git a/m5stack/fs/system/stampplc/setting/wlan/input_psk.jpeg b/m5stack/fs/system/stamplc/setting/wlan/input_psk.jpeg similarity index 100% rename from m5stack/fs/system/stampplc/setting/wlan/input_psk.jpeg rename to m5stack/fs/system/stamplc/setting/wlan/input_psk.jpeg diff --git a/m5stack/fs/system/stampplc/setting/wlan/input_server.jpeg b/m5stack/fs/system/stamplc/setting/wlan/input_server.jpeg similarity index 100% rename from m5stack/fs/system/stampplc/setting/wlan/input_server.jpeg rename to m5stack/fs/system/stamplc/setting/wlan/input_server.jpeg diff --git a/m5stack/fs/system/stampplc/setting/wlan/input_ssid.jpeg b/m5stack/fs/system/stamplc/setting/wlan/input_ssid.jpeg similarity index 100% rename from m5stack/fs/system/stampplc/setting/wlan/input_ssid.jpeg rename to m5stack/fs/system/stamplc/setting/wlan/input_ssid.jpeg diff --git a/m5stack/fs/system/stampplc/setting/wlan/submit_select.jpeg b/m5stack/fs/system/stamplc/setting/wlan/submit_select.jpeg similarity index 100% rename from m5stack/fs/system/stampplc/setting/wlan/submit_select.jpeg rename to m5stack/fs/system/stamplc/setting/wlan/submit_select.jpeg diff --git a/m5stack/fs/system/stampplc/setting/wlan/submit_unselect.jpeg b/m5stack/fs/system/stamplc/setting/wlan/submit_unselect.jpeg similarity index 100% rename from m5stack/fs/system/stampplc/setting/wlan/submit_unselect.jpeg rename to m5stack/fs/system/stamplc/setting/wlan/submit_unselect.jpeg diff --git a/m5stack/fs/system/stampplc/statusbar/battery/black.jpeg b/m5stack/fs/system/stamplc/statusbar/battery/black.jpeg similarity index 100% rename from m5stack/fs/system/stampplc/statusbar/battery/black.jpeg rename to m5stack/fs/system/stamplc/statusbar/battery/black.jpeg diff --git a/m5stack/fs/system/stampplc/statusbar/battery/black_charge.jpeg b/m5stack/fs/system/stamplc/statusbar/battery/black_charge.jpeg similarity index 100% rename from m5stack/fs/system/stampplc/statusbar/battery/black_charge.jpeg rename to m5stack/fs/system/stamplc/statusbar/battery/black_charge.jpeg diff --git a/m5stack/fs/system/stampplc/statusbar/battery/green.jpeg b/m5stack/fs/system/stamplc/statusbar/battery/green.jpeg similarity index 100% rename from m5stack/fs/system/stampplc/statusbar/battery/green.jpeg rename to m5stack/fs/system/stamplc/statusbar/battery/green.jpeg diff --git a/m5stack/fs/system/stampplc/statusbar/battery/green_charge.jpeg b/m5stack/fs/system/stamplc/statusbar/battery/green_charge.jpeg similarity index 100% rename from m5stack/fs/system/stampplc/statusbar/battery/green_charge.jpeg rename to m5stack/fs/system/stamplc/statusbar/battery/green_charge.jpeg diff --git a/m5stack/fs/system/stampplc/statusbar/battery/red.jpeg b/m5stack/fs/system/stamplc/statusbar/battery/red.jpeg similarity index 100% rename from m5stack/fs/system/stampplc/statusbar/battery/red.jpeg rename to m5stack/fs/system/stamplc/statusbar/battery/red.jpeg diff --git a/m5stack/fs/system/stampplc/statusbar/battery/red_charge.jpeg b/m5stack/fs/system/stamplc/statusbar/battery/red_charge.jpeg similarity index 100% rename from m5stack/fs/system/stampplc/statusbar/battery/red_charge.jpeg rename to m5stack/fs/system/stamplc/statusbar/battery/red_charge.jpeg diff --git a/m5stack/fs/system/stampplc/statusbar/cloud/empty.jpeg b/m5stack/fs/system/stamplc/statusbar/cloud/empty.jpeg similarity index 100% rename from m5stack/fs/system/stampplc/statusbar/cloud/empty.jpeg rename to m5stack/fs/system/stamplc/statusbar/cloud/empty.jpeg diff --git a/m5stack/fs/system/stampplc/statusbar/cloud/error.jpeg b/m5stack/fs/system/stamplc/statusbar/cloud/error.jpeg similarity index 100% rename from m5stack/fs/system/stampplc/statusbar/cloud/error.jpeg rename to m5stack/fs/system/stamplc/statusbar/cloud/error.jpeg diff --git a/m5stack/fs/system/stampplc/statusbar/cloud/green.jpeg b/m5stack/fs/system/stamplc/statusbar/cloud/green.jpeg similarity index 100% rename from m5stack/fs/system/stampplc/statusbar/cloud/green.jpeg rename to m5stack/fs/system/stamplc/statusbar/cloud/green.jpeg diff --git a/m5stack/fs/system/stampplc/statusbar/title_blue.jpeg b/m5stack/fs/system/stamplc/statusbar/title_blue.jpeg similarity index 100% rename from m5stack/fs/system/stampplc/statusbar/title_blue.jpeg rename to m5stack/fs/system/stamplc/statusbar/title_blue.jpeg diff --git a/m5stack/fs/system/stampplc/statusbar/wifi/disconnected.jpeg b/m5stack/fs/system/stamplc/statusbar/wifi/disconnected.jpeg similarity index 100% rename from m5stack/fs/system/stampplc/statusbar/wifi/disconnected.jpeg rename to m5stack/fs/system/stamplc/statusbar/wifi/disconnected.jpeg diff --git a/m5stack/fs/system/stampplc/statusbar/wifi/empty.jpeg b/m5stack/fs/system/stamplc/statusbar/wifi/empty.jpeg similarity index 100% rename from m5stack/fs/system/stampplc/statusbar/wifi/empty.jpeg rename to m5stack/fs/system/stamplc/statusbar/wifi/empty.jpeg diff --git a/m5stack/fs/system/stampplc/statusbar/wifi/good.jpeg b/m5stack/fs/system/stamplc/statusbar/wifi/good.jpeg similarity index 100% rename from m5stack/fs/system/stampplc/statusbar/wifi/good.jpeg rename to m5stack/fs/system/stamplc/statusbar/wifi/good.jpeg diff --git a/m5stack/fs/system/stampplc/statusbar/wifi/mid.jpeg b/m5stack/fs/system/stamplc/statusbar/wifi/mid.jpeg similarity index 100% rename from m5stack/fs/system/stampplc/statusbar/wifi/mid.jpeg rename to m5stack/fs/system/stamplc/statusbar/wifi/mid.jpeg diff --git a/m5stack/fs/system/stampplc/statusbar/wifi/worse.jpeg b/m5stack/fs/system/stamplc/statusbar/wifi/worse.jpeg similarity index 100% rename from m5stack/fs/system/stampplc/statusbar/wifi/worse.jpeg rename to m5stack/fs/system/stamplc/statusbar/wifi/worse.jpeg diff --git a/m5stack/libs/hardware/plcio.py b/m5stack/libs/hardware/plcio.py index 476bee67..2d01a0aa 100644 --- a/m5stack/libs/hardware/plcio.py +++ b/m5stack/libs/hardware/plcio.py @@ -13,7 +13,7 @@ def _plc_closure() -> tuple: def _aw9523_init(): _board_map = { # boardid: (i2c, scl, sda, irq_pin) - M5.BOARD.M5StampPLC: (1, 15, 13, 14), + M5.BOARD.M5StamPLC: (1, 15, 13, 14), } i2c, scl, sda, irq_pin = _board_map.get(M5.getBoard(), (None, None, None, None)) if i2c is None: diff --git a/m5stack/modules/startup/__init__.py b/m5stack/modules/startup/__init__.py index e86c3395..6c4b63f0 100644 --- a/m5stack/modules/startup/__init__.py +++ b/m5stack/modules/startup/__init__.py @@ -231,8 +231,8 @@ def startup(boot_opt, timeout: int = 60) -> None: papers3 = PaperS3_Startup() papers3.startup(ssid, pswd, timeout) - elif board_id == M5.BOARD.M5StampPLC: - from .stampplc import StampPLC_Startup + elif board_id == M5.BOARD.M5StamPLC: + from .stamplc import StampPLC_Startup station = StampPLC_Startup() station.startup(ssid, pswd, timeout) diff --git a/m5stack/modules/startup/manifest_stamplc.py b/m5stack/modules/startup/manifest_stamplc.py new file mode 100644 index 00000000..ea611056 --- /dev/null +++ b/m5stack/modules/startup/manifest_stamplc.py @@ -0,0 +1,23 @@ +# SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +package( + "startup", + ( + "__init__.py", + "stamplc/__init__.py", + "stamplc/app_base.py", + "stamplc/framework.py", + "stamplc/res.py", + "stamplc/apps/app_list.py", + "stamplc/apps/app_run.py", + "stamplc/apps/dev.py", + "stamplc/apps/ezdata.py", + "stamplc/apps/launcher.py", + "stamplc/apps/settings.py", + "stamplc/apps/statusbar.py", + ), + base_path="..", + opt=3, +) diff --git a/m5stack/modules/startup/manifest_stampplc.py b/m5stack/modules/startup/manifest_stampplc.py deleted file mode 100644 index 7dbe0b24..00000000 --- a/m5stack/modules/startup/manifest_stampplc.py +++ /dev/null @@ -1,23 +0,0 @@ -# SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD -# -# SPDX-License-Identifier: MIT - -package( - "startup", - ( - "__init__.py", - "stampplc/__init__.py", - "stampplc/app_base.py", - "stampplc/framework.py", - "stampplc/res.py", - "stampplc/apps/app_list.py", - "stampplc/apps/app_run.py", - "stampplc/apps/dev.py", - "stampplc/apps/ezdata.py", - "stampplc/apps/launcher.py", - "stampplc/apps/settings.py", - "stampplc/apps/statusbar.py", - ), - base_path="..", - opt=3, -) diff --git a/m5stack/modules/startup/stampplc/__init__.py b/m5stack/modules/startup/stamplc/__init__.py similarity index 100% rename from m5stack/modules/startup/stampplc/__init__.py rename to m5stack/modules/startup/stamplc/__init__.py diff --git a/m5stack/modules/startup/stampplc/app_base.py b/m5stack/modules/startup/stamplc/app_base.py similarity index 100% rename from m5stack/modules/startup/stampplc/app_base.py rename to m5stack/modules/startup/stamplc/app_base.py diff --git a/m5stack/modules/startup/stampplc/apps/app_list.py b/m5stack/modules/startup/stamplc/apps/app_list.py similarity index 96% rename from m5stack/modules/startup/stampplc/apps/app_list.py rename to m5stack/modules/startup/stamplc/apps/app_list.py index a6cb6a00..d694eeb6 100644 --- a/m5stack/modules/startup/stampplc/apps/app_list.py +++ b/m5stack/modules/startup/stamplc/apps/app_list.py @@ -163,7 +163,7 @@ def on_view(self): for label, icos, file in zip(self._labels, self._icos, self._files): ico_name = file[0].lower() - icos.set_src(f"/system/stampplc/ico/{ico_name}.jpeg") + icos.set_src(f"/system/stamplc/ico/{ico_name}.jpeg") label.set_text(file) def on_ready(self): @@ -203,7 +203,7 @@ def _btn_up_event_handler(self, event): if self._file_pos < self._cursor_pos: for label, icos, file in zip(self._labels, self._icos, self._files): ico_name = file[0].lower() - icos.set_src(f"/system/stampplc/ico/{ico_name}.jpeg") + icos.set_src(f"/system/stamplc/ico/{ico_name}.jpeg") label.set_text(file) else: for label, icos, file in zip( @@ -214,7 +214,7 @@ def _btn_up_event_handler(self, event): ], ): ico_name = file[0].lower() - icos.set_src(f"/system/stampplc/ico/{ico_name}.jpeg") + icos.set_src(f"/system/stamplc/ico/{ico_name}.jpeg") label.set_text(file) def _btn_down_event_handler(self, fw): @@ -242,14 +242,14 @@ def _btn_down_event_handler(self, fw): if self._file_pos < 3: for label, icos, file in zip(self._labels, self._icos, self._files): ico_name = file[0].lower() - icos.set_src(f"/system/stampplc/ico/{ico_name}.jpeg") + icos.set_src(f"/system/stamplc/ico/{ico_name}.jpeg") label.set_text(file) else: for label, icos, file in zip( self._labels, self._icos, self._files[self._file_pos - 2 : self._file_pos + 1] ): ico_name = file[0].lower() - icos.set_src(f"/system/stampplc/ico/{ico_name}.jpeg") + icos.set_src(f"/system/stamplc/ico/{ico_name}.jpeg") label.set_text(file) def _btn_once_event_handler(self, event): diff --git a/m5stack/modules/startup/stampplc/apps/app_run.py b/m5stack/modules/startup/stamplc/apps/app_run.py similarity index 100% rename from m5stack/modules/startup/stampplc/apps/app_run.py rename to m5stack/modules/startup/stamplc/apps/app_run.py diff --git a/m5stack/modules/startup/stampplc/apps/dev.py b/m5stack/modules/startup/stamplc/apps/dev.py similarity index 100% rename from m5stack/modules/startup/stampplc/apps/dev.py rename to m5stack/modules/startup/stamplc/apps/dev.py diff --git a/m5stack/modules/startup/stampplc/apps/ezdata.py b/m5stack/modules/startup/stamplc/apps/ezdata.py similarity index 100% rename from m5stack/modules/startup/stampplc/apps/ezdata.py rename to m5stack/modules/startup/stamplc/apps/ezdata.py diff --git a/m5stack/modules/startup/stampplc/apps/launcher.py b/m5stack/modules/startup/stamplc/apps/launcher.py similarity index 100% rename from m5stack/modules/startup/stampplc/apps/launcher.py rename to m5stack/modules/startup/stamplc/apps/launcher.py diff --git a/m5stack/modules/startup/stampplc/apps/settings.py b/m5stack/modules/startup/stamplc/apps/settings.py similarity index 100% rename from m5stack/modules/startup/stampplc/apps/settings.py rename to m5stack/modules/startup/stamplc/apps/settings.py diff --git a/m5stack/modules/startup/stampplc/apps/statusbar.py b/m5stack/modules/startup/stamplc/apps/statusbar.py similarity index 100% rename from m5stack/modules/startup/stampplc/apps/statusbar.py rename to m5stack/modules/startup/stamplc/apps/statusbar.py diff --git a/m5stack/modules/startup/stampplc/framework.py b/m5stack/modules/startup/stamplc/framework.py similarity index 100% rename from m5stack/modules/startup/stampplc/framework.py rename to m5stack/modules/startup/stamplc/framework.py diff --git a/m5stack/modules/startup/stamplc/res.py b/m5stack/modules/startup/stamplc/res.py new file mode 100644 index 00000000..e3d8402f --- /dev/null +++ b/m5stack/modules/startup/stamplc/res.py @@ -0,0 +1,74 @@ +# SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +_attrs = { + "LOGO_IMG": "/system/stamplc/boot/boot_logo_1.jpeg", + # apprun + "RUN_INFO_IMG": "/system/stamplc/apprun/run_info.jpeg", + "RUN_ONCE_SELECT_IMG": "/system/stamplc/apprun/run_once_select.jpeg", + "RUN_ONCE_UNSELECT_IMG": "/system/stamplc/apprun/run_once_unselect.jpeg", + "RUN_ALWAYS_SELECT_IMG": "/system/stamplc/apprun/run_always_select.jpeg", + "RUN_ALWAYS_UNSELECT_IMG": "/system/stamplc/apprun/run_always_unselect.jpeg", + # develop + "DEVELOP_PRIVATE_IMG": "/system/stamplc/develop/private.jpeg", + "DEVELOP_PUBLIC_IMG": "/system/stamplc/develop/public.jpeg", + "AVATAR_IMG": "/system/common/img/avatar.jpeg", + # ezdata + # launcher + "APPLIST_ICO": "/system/stamplc/applist.jpeg", + "DEVELOP_ICO": "/system/stamplc/develop.jpeg", + "APPRUN_ICO": "/system/stamplc/apprun.jpeg", + "EZDATA_ICO": "/system/stamplc/ezdata.jpeg", + "SETTING_ICO": "/system/stamplc/setting.jpeg", + "RIGHT_ICO": "/system/stamplc/right.jpeg", + "LEFT_ICO": "/system/stamplc/left.jpeg", + # setting + "WLAN_ICO_IMG": "/system/stamplc/setting/wlan.jpeg", + "GENERAL_ICO_IMG": "/system/stamplc/setting/general.jpeg", + "CARET_RIGHT": "/system/stamplc/setting/caret_right.jpeg", + # wlan + "WIFI_DEFAULT_IMG": "/system/stamplc/setting/wlan/input_default.jpeg", + "WIFI_SSID_IMG": "/system/stamplc/setting/wlan/input_ssid.jpeg", + "WIFI_PSK_IMG": "/system/stamplc/setting/wlan/input_psk.jpeg", + "WIFI_SERVER_IMG": "/system/stamplc/setting/wlan/input_server.jpeg", + "SUBMIT_SELECT_BUTTON_IMG": "/system/stamplc/setting/wlan/submit_select.jpeg", + "SUBMIT_UNSELECT_BUTTON_IMG": "/system/stamplc/setting/wlan/submit_unselect.jpeg", + # general + "DISABLE_IMG": "/system/stamplc/setting/general/disable.jpeg", + "ENABLE_IMG": "/system/stamplc/setting/general/enable.jpeg", + # statusbar + "BATTERY_BLACK_CHARGE_IMG": "/system/stamplc/statusbar/battery/black_charge.jpeg", + "BATTERY_BLACK_IMG": "/system/stamplc/statusbar/battery/black.jpeg", + "BATTERY_GREEN_CHARGE_IMG": "/system/stamplc/statusbar/battery/green_charge.jpeg", + "BATTERY_GREEN_IMG": "/system/stamplc/statusbar/battery/green.jpeg", + "BATTERY_RED_CHARGE_IMG": "/system/stamplc/statusbar/battery/red_charge.jpeg", + "BATTERY_RED_IMG": "/system/stamplc/statusbar/battery/red.jpeg", + "SERVER_EMPTY_IMG": "/system/stamplc/statusbar/cloud/empty.jpeg", + "SERVER_ERROR_IMG": "/system/stamplc/statusbar/cloud/error.jpeg", + "SERVER_GREEN_IMG": "/system/stamplc/statusbar/cloud/green.jpeg", + "WIFI_DISCONNECTED_IMG": "/system/stamplc/statusbar/wifi/disconnected.jpeg", + "WIFI_EMPTY_IMG": "/system/stamplc/statusbar/wifi/empty.jpeg", + "WIFI_GOOD_IMG": "/system/stamplc/statusbar/wifi/good.jpeg", + "WIFI_MID_IMG": "/system/stamplc/statusbar/wifi/mid.jpeg", + "WIFI_WORSE_IMG": "/system/stamplc/statusbar/wifi/worse.jpeg", + "BLUE_TITLE_IMG": "/system/stamplc/statusbar/title_blue.jpeg", + # common + "CARD_228x32_SELECT_IMG": "/system/stamplc/common/card_228x32_select.jpeg", + "CARD_228x32_UNSELECT_IMG": "/system/stamplc/common/card_228x32_unselect.jpeg", + # font + "MontserratMedium10_VLW": "/system/common/font/Montserrat-Medium-10.vlw", + "MontserratMedium12_VLW": "/system/common/font/Montserrat-Medium-12.vlw", + "MontserratMedium16_VLW": "/system/common/font/Montserrat-Medium-16.vlw", + "MontserratMedium18_VLW": "/system/common/font/Montserrat-Medium-18.vlw", + # path + "USER_AVATAR_PATH": "/system/common/img/", +} + + +def __getattr__(attr): + value = _attrs.get(attr, None) + if value is None: + raise AttributeError(attr) + globals()[attr] = value + return value diff --git a/m5stack/modules/startup/stampplc/res.py b/m5stack/modules/startup/stampplc/res.py deleted file mode 100644 index d2d3712f..00000000 --- a/m5stack/modules/startup/stampplc/res.py +++ /dev/null @@ -1,74 +0,0 @@ -# SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD -# -# SPDX-License-Identifier: MIT - -_attrs = { - "LOGO_IMG": "/system/stampplc/boot/boot_logo_1.jpeg", - # apprun - "RUN_INFO_IMG": "/system/stampplc/apprun/run_info.jpeg", - "RUN_ONCE_SELECT_IMG": "/system/stampplc/apprun/run_once_select.jpeg", - "RUN_ONCE_UNSELECT_IMG": "/system/stampplc/apprun/run_once_unselect.jpeg", - "RUN_ALWAYS_SELECT_IMG": "/system/stampplc/apprun/run_always_select.jpeg", - "RUN_ALWAYS_UNSELECT_IMG": "/system/stampplc/apprun/run_always_unselect.jpeg", - # develop - "DEVELOP_PRIVATE_IMG": "/system/stampplc/develop/private.jpeg", - "DEVELOP_PUBLIC_IMG": "/system/stampplc/develop/public.jpeg", - "AVATAR_IMG": "/system/common/img/avatar.jpeg", - # ezdata - # launcher - "APPLIST_ICO": "/system/stampplc/applist.jpeg", - "DEVELOP_ICO": "/system/stampplc/develop.jpeg", - "APPRUN_ICO": "/system/stampplc/apprun.jpeg", - "EZDATA_ICO": "/system/stampplc/ezdata.jpeg", - "SETTING_ICO": "/system/stampplc/setting.jpeg", - "RIGHT_ICO": "/system/stampplc/right.jpeg", - "LEFT_ICO": "/system/stampplc/left.jpeg", - # setting - "WLAN_ICO_IMG": "/system/stampplc/setting/wlan.jpeg", - "GENERAL_ICO_IMG": "/system/stampplc/setting/general.jpeg", - "CARET_RIGHT": "/system/stampplc/setting/caret_right.jpeg", - # wlan - "WIFI_DEFAULT_IMG": "/system/stampplc/setting/wlan/input_default.jpeg", - "WIFI_SSID_IMG": "/system/stampplc/setting/wlan/input_ssid.jpeg", - "WIFI_PSK_IMG": "/system/stampplc/setting/wlan/input_psk.jpeg", - "WIFI_SERVER_IMG": "/system/stampplc/setting/wlan/input_server.jpeg", - "SUBMIT_SELECT_BUTTON_IMG": "/system/stampplc/setting/wlan/submit_select.jpeg", - "SUBMIT_UNSELECT_BUTTON_IMG": "/system/stampplc/setting/wlan/submit_unselect.jpeg", - # general - "DISABLE_IMG": "/system/stampplc/setting/general/disable.jpeg", - "ENABLE_IMG": "/system/stampplc/setting/general/enable.jpeg", - # statusbar - "BATTERY_BLACK_CHARGE_IMG": "/system/stampplc/statusbar/battery/black_charge.jpeg", - "BATTERY_BLACK_IMG": "/system/stampplc/statusbar/battery/black.jpeg", - "BATTERY_GREEN_CHARGE_IMG": "/system/stampplc/statusbar/battery/green_charge.jpeg", - "BATTERY_GREEN_IMG": "/system/stampplc/statusbar/battery/green.jpeg", - "BATTERY_RED_CHARGE_IMG": "/system/stampplc/statusbar/battery/red_charge.jpeg", - "BATTERY_RED_IMG": "/system/stampplc/statusbar/battery/red.jpeg", - "SERVER_EMPTY_IMG": "/system/stampplc/statusbar/cloud/empty.jpeg", - "SERVER_ERROR_IMG": "/system/stampplc/statusbar/cloud/error.jpeg", - "SERVER_GREEN_IMG": "/system/stampplc/statusbar/cloud/green.jpeg", - "WIFI_DISCONNECTED_IMG": "/system/stampplc/statusbar/wifi/disconnected.jpeg", - "WIFI_EMPTY_IMG": "/system/stampplc/statusbar/wifi/empty.jpeg", - "WIFI_GOOD_IMG": "/system/stampplc/statusbar/wifi/good.jpeg", - "WIFI_MID_IMG": "/system/stampplc/statusbar/wifi/mid.jpeg", - "WIFI_WORSE_IMG": "/system/stampplc/statusbar/wifi/worse.jpeg", - "BLUE_TITLE_IMG": "/system/stampplc/statusbar/title_blue.jpeg", - # common - "CARD_228x32_SELECT_IMG": "/system/stampplc/common/card_228x32_select.jpeg", - "CARD_228x32_UNSELECT_IMG": "/system/stampplc/common/card_228x32_unselect.jpeg", - # font - "MontserratMedium10_VLW": "/system/common/font/Montserrat-Medium-10.vlw", - "MontserratMedium12_VLW": "/system/common/font/Montserrat-Medium-12.vlw", - "MontserratMedium16_VLW": "/system/common/font/Montserrat-Medium-16.vlw", - "MontserratMedium18_VLW": "/system/common/font/Montserrat-Medium-18.vlw", - # path - "USER_AVATAR_PATH": "/system/common/img/", -} - - -def __getattr__(attr): - value = _attrs.get(attr, None) - if value is None: - raise AttributeError(attr) - globals()[attr] = value - return value From b0b63fdf2197e65f48d7cb4f0a74ec0c799dcbec Mon Sep 17 00:00:00 2001 From: lbuque Date: Thu, 6 Mar 2025 17:04:15 +0800 Subject: [PATCH 015/322] tools/ci.sh: Add build of M5STACK_StamPLC. Signed-off-by: lbuque --- .github/workflows/build-release.yml | 1 + .github/workflows/nightly-build.yml | 5 +++++ tools/ci.sh | 1 + 3 files changed, 7 insertions(+) diff --git a/.github/workflows/build-release.yml b/.github/workflows/build-release.yml index b5e34c14..1207e1a7 100644 --- a/.github/workflows/build-release.yml +++ b/.github/workflows/build-release.yml @@ -48,6 +48,7 @@ jobs: ./m5stack/build-M5STACK_Paper/uiflow-*-*.bin ./m5stack/build-M5STACK_PaperS3/uiflow-*-*.bin ./m5stack/build-M5STACK_Stamp_PICO/uiflow-*-*.bin + ./m5stack/build-M5STACK_StamPLC/uiflow-*-*.bin ./m5stack/build-M5STACK_StampS3/uiflow-*-*.bin ./m5stack/build-M5STACK_Station/uiflow-*-*.bin ./m5stack/build-M5STACK_StickC/uiflow-*-*.bin diff --git a/.github/workflows/nightly-build.yml b/.github/workflows/nightly-build.yml index 32edaab6..680872f7 100644 --- a/.github/workflows/nightly-build.yml +++ b/.github/workflows/nightly-build.yml @@ -132,6 +132,11 @@ jobs: with: name: M5STACK_Stamp_PICO_firmware path: ./m5stack/build-M5STACK_Stamp_PICO/uiflow-*-*.bin + - name: Deliver StamPLC firmware + uses: actions/upload-artifact@v3 + with: + name: M5STACK_StamPLC_firmware + path: ./m5stack/build-M5STACK_StamPLC/uiflow-*-*.bin - name: Deliver StampS3 firmware uses: actions/upload-artifact@v3 with: diff --git a/tools/ci.sh b/tools/ci.sh index 3d67f9e9..9dbb512a 100644 --- a/tools/ci.sh +++ b/tools/ci.sh @@ -219,6 +219,7 @@ function ci_esp32_nightly_build { make ${MAKEOPTS} -C m5stack BOARD=M5STACK_Paper pack_all make ${MAKEOPTS} -C m5stack BOARD=M5STACK_PaperS3 pack_all make ${MAKEOPTS} -C m5stack BOARD=M5STACK_Stamp_PICO pack_all + make ${MAKEOPTS} -C m5stack BOARD=M5STACK_StamPLC pack_all make ${MAKEOPTS} -C m5stack BOARD=M5STACK_StampS3 pack_all make ${MAKEOPTS} -C m5stack BOARD=M5STACK_Station pack_all make ${MAKEOPTS} -C m5stack BOARD=M5STACK_StickC pack_all From a3b34d1f3099d4f8dd6028d5bbebbcbed84cbbc1 Mon Sep 17 00:00:00 2001 From: lbuque Date: Thu, 6 Mar 2025 17:13:11 +0800 Subject: [PATCH 016/322] style: Code format. Signed-off-by: lbuque --- .../camera/cores3_example_camera_display.py | 8 +- .../code_scanner/qrcode_detect_example.py | 9 +- .../dl/cores3_example_human_face_detect.py | 22 +- .../cores3_example_human_face_recognition.py | 153 +- .../dl/cores3_example_pedestrian_detect.py | 10 +- .../image/cores3_example_draw_test.py | 14 +- examples/advanced/jpg/take_photo_example.py | 12 +- .../device/m5cores3_usbd_keyboard_example.py | 12 +- .../usb/device/m5cores3_usbd_mouse_example.py | 35 +- .../camera/cores3_example_camera_display.py | 8 +- .../usb/cores3_module_usb_gpio_example.py | 5 +- .../usb/cores3_module_usb_kb_example.py | 21 +- .../usb/cores3_module_usb_mouse_example.py | 21 +- .../code_scanner/qrcode_detect_example.py | 9 +- .../dl/cores3_example_human_face_detect.py | 22 +- .../cores3_example_human_face_recognition.py | 153 +- .../dl/cores3_example_pedestrian_detect.py | 10 +- .../image/cores3_example_draw_test.py | 14 +- examples/softwave/jpg/take_photo_example.py | 12 +- m5stack/cmodules/omv/imlib/cmsis_iccarm.h | 93 +- m5stack/cmodules/omv/imlib/draw.c | 1211 ++++++------- m5stack/cmodules/omv/imlib/fmath.c | 326 ++-- m5stack/cmodules/omv/imlib/fmath.h | 112 +- m5stack/cmodules/omv/imlib/font.c | 1239 +++++++------ m5stack/cmodules/omv/imlib/font.h | 42 +- m5stack/cmodules/omv/imlib/imlib.c | 378 ++-- m5stack/cmodules/omv/imlib/imlib.h | 1560 ++++++++--------- m5stack/cmodules/omv/modules/py_assert.h | 26 +- m5stack/cmodules/omv/modules/py_camera.c | 14 +- .../cmodules/omv/modules/py_code_scanner.c | 53 +- m5stack/cmodules/omv/modules/py_esp_dl.c | 2 - m5stack/cmodules/omv/modules/py_esp_dl.h | 2 - m5stack/cmodules/omv/modules/py_esp_dl_.cpp | 55 +- m5stack/cmodules/omv/modules/py_helper.c | 308 ++-- m5stack/cmodules/omv/modules/py_helper.h | 81 +- m5stack/cmodules/omv/modules/py_image.c | 880 +++++----- m5stack/cmodules/omv/modules/py_image.h | 59 +- m5stack/cmodules/omv/modules/py_jpg.c | 28 +- m5stack/components/M5Unified/mpy_m5gfx.cpp | 10 +- 39 files changed, 3630 insertions(+), 3399 deletions(-) mode change 100755 => 100644 m5stack/cmodules/omv/imlib/cmsis_iccarm.h mode change 100755 => 100644 m5stack/cmodules/omv/imlib/draw.c mode change 100755 => 100644 m5stack/cmodules/omv/imlib/fmath.c mode change 100755 => 100644 m5stack/cmodules/omv/imlib/fmath.h mode change 100755 => 100644 m5stack/cmodules/omv/imlib/font.c mode change 100755 => 100644 m5stack/cmodules/omv/imlib/font.h mode change 100755 => 100644 m5stack/cmodules/omv/imlib/imlib.c mode change 100755 => 100644 m5stack/cmodules/omv/imlib/imlib.h mode change 100755 => 100644 m5stack/cmodules/omv/modules/py_assert.h mode change 100755 => 100644 m5stack/cmodules/omv/modules/py_camera.c mode change 100755 => 100644 m5stack/cmodules/omv/modules/py_code_scanner.c mode change 100755 => 100644 m5stack/cmodules/omv/modules/py_esp_dl.c mode change 100755 => 100644 m5stack/cmodules/omv/modules/py_esp_dl.h mode change 100755 => 100644 m5stack/cmodules/omv/modules/py_esp_dl_.cpp mode change 100755 => 100644 m5stack/cmodules/omv/modules/py_helper.c mode change 100755 => 100644 m5stack/cmodules/omv/modules/py_helper.h mode change 100755 => 100644 m5stack/cmodules/omv/modules/py_image.c mode change 100755 => 100644 m5stack/cmodules/omv/modules/py_image.h mode change 100755 => 100644 m5stack/cmodules/omv/modules/py_jpg.c diff --git a/examples/advanced/camera/cores3_example_camera_display.py b/examples/advanced/camera/cores3_example_camera_display.py index efba7b5b..5c931c8c 100644 --- a/examples/advanced/camera/cores3_example_camera_display.py +++ b/examples/advanced/camera/cores3_example_camera_display.py @@ -9,19 +9,22 @@ img = None + def setup(): global img M5.begin() Widgets.fillScreen(0x222222) camera.init(pixformat=camera.RGB565, framesize=camera.QVGA) + def loop(): global img M5.update() img = camera.snapshot() M5.Lcd.show(img, 0, 0, 320, 240) -if __name__ == '__main__': + +if __name__ == "__main__": try: setup() while True: @@ -29,8 +32,7 @@ def loop(): except (Exception, KeyboardInterrupt) as e: try: from utility import print_error_msg + print_error_msg(e) except ImportError: print("please update to latest firmware") - - diff --git a/examples/advanced/code_scanner/qrcode_detect_example.py b/examples/advanced/code_scanner/qrcode_detect_example.py index db4980fd..007e3f9e 100644 --- a/examples/advanced/code_scanner/qrcode_detect_example.py +++ b/examples/advanced/code_scanner/qrcode_detect_example.py @@ -12,6 +12,7 @@ img = None qrcode = None + def setup(): global img, qrcode M5.begin() @@ -19,6 +20,7 @@ def setup(): camera.init(pixformat=camera.RGB565, framesize=camera.QVGA) camera.set_hmirror(False) + def loop(): global img, qrcode M5.update() @@ -27,10 +29,11 @@ def loop(): if qrcode: print(qrcode.payload()) print(qrcode.type_name()) - img.draw_string(10, 10, str(qrcode.payload()), color=0x3333ff, scale=2) + img.draw_string(10, 10, str(qrcode.payload()), color=0x3333FF, scale=2) M5.Lcd.show(img, 0, 0, 320, 240) -if __name__ == '__main__': + +if __name__ == "__main__": try: setup() while True: @@ -38,7 +41,7 @@ def loop(): except (Exception, KeyboardInterrupt) as e: try: from utility import print_error_msg + print_error_msg(e) except ImportError: print("please update to latest firmware") - diff --git a/examples/advanced/dl/cores3_example_human_face_detect.py b/examples/advanced/dl/cores3_example_human_face_detect.py index dc8ed440..7ea322f8 100644 --- a/examples/advanced/dl/cores3_example_human_face_detect.py +++ b/examples/advanced/dl/cores3_example_human_face_detect.py @@ -9,13 +9,13 @@ import image - img = None detector = None detection_result = None res = None kp = None + def setup(): global img, detector, detection_result, kp M5.begin() @@ -23,6 +23,7 @@ def setup(): camera.init(pixformat=camera.RGB565, framesize=camera.QVGA) detector = dl.ObjectDetector(dl.model.HUMAN_FACE_DETECT) + def loop(): global img, detector, detection_result, kp M5.update() @@ -31,15 +32,18 @@ def loop(): if detection_result: for res in detection_result: kp = res.keypoint() - img.draw_circle(kp[0], kp[1], 3, color=0x0000ff, thickness=1, fill=True) - img.draw_circle(kp[2], kp[3], 3, color=0x00ff00, thickness=1, fill=True) - img.draw_circle(kp[4], kp[5], 3, color=0xff0000, thickness=1, fill=True) - img.draw_circle(kp[6], kp[7], 3, color=0x0000ff, thickness=1, fill=True) - img.draw_circle(kp[8], kp[9], 3, color=0x00ff00, thickness=1, fill=True) - img.draw_rectangle(res.x(), res.y(), res.w(), res.h(), color=0x3366ff, thickness=3, fill=False) + img.draw_circle(kp[0], kp[1], 3, color=0x0000FF, thickness=1, fill=True) + img.draw_circle(kp[2], kp[3], 3, color=0x00FF00, thickness=1, fill=True) + img.draw_circle(kp[4], kp[5], 3, color=0xFF0000, thickness=1, fill=True) + img.draw_circle(kp[6], kp[7], 3, color=0x0000FF, thickness=1, fill=True) + img.draw_circle(kp[8], kp[9], 3, color=0x00FF00, thickness=1, fill=True) + img.draw_rectangle( + res.x(), res.y(), res.w(), res.h(), color=0x3366FF, thickness=3, fill=False + ) M5.Lcd.show(img, 0, 0, 320, 240) -if __name__ == '__main__': + +if __name__ == "__main__": try: setup() while True: @@ -47,7 +51,7 @@ def loop(): except (Exception, KeyboardInterrupt) as e: try: from utility import print_error_msg + print_error_msg(e) except ImportError: print("please update to latest firmware") - diff --git a/examples/advanced/dl/cores3_example_human_face_recognition.py b/examples/advanced/dl/cores3_example_human_face_recognition.py index a3753597..6e4e70da 100644 --- a/examples/advanced/dl/cores3_example_human_face_recognition.py +++ b/examples/advanced/dl/cores3_example_human_face_recognition.py @@ -29,27 +29,98 @@ dl_recognize_result = None -def dual_button_0_blue_wasClicked_event(state): - global dual_button_0_blue, dual_button_0_red, sys_state, FACE_RECOGNIZE, FACE_ENROLL, FACE_DELETE, detector, img, dl_recognizer, detection_result, IDLE, kp, sys_state_prev, frame_count, res, dl_recognize_result +def dual_button_0_blue_wasClicked_event(state): # noqa: N802 + global \ + dual_button_0_blue, \ + dual_button_0_red, \ + sys_state, \ + FACE_RECOGNIZE, \ + FACE_ENROLL, \ + FACE_DELETE, \ + detector, \ + img, \ + dl_recognizer, \ + detection_result, \ + IDLE, \ + kp, \ + sys_state_prev, \ + frame_count, \ + res, \ + dl_recognize_result sys_state = FACE_RECOGNIZE -def dual_button_0_red_wasClicked_event(state): - global dual_button_0_blue, dual_button_0_red, sys_state, FACE_RECOGNIZE, FACE_ENROLL, FACE_DELETE, detector, img, dl_recognizer, detection_result, IDLE, kp, sys_state_prev, frame_count, res, dl_recognize_result + +def dual_button_0_red_wasClicked_event(state): # noqa: N802 + global \ + dual_button_0_blue, \ + dual_button_0_red, \ + sys_state, \ + FACE_RECOGNIZE, \ + FACE_ENROLL, \ + FACE_DELETE, \ + detector, \ + img, \ + dl_recognizer, \ + detection_result, \ + IDLE, \ + kp, \ + sys_state_prev, \ + frame_count, \ + res, \ + dl_recognize_result sys_state = FACE_ENROLL -def btnPWR_wasClicked_event(state): - global dual_button_0_blue, dual_button_0_red, sys_state, FACE_RECOGNIZE, FACE_ENROLL, FACE_DELETE, detector, img, dl_recognizer, detection_result, IDLE, kp, sys_state_prev, frame_count, res, dl_recognize_result + +def btnPWR_wasClicked_event(state): # noqa: N802 + global \ + dual_button_0_blue, \ + dual_button_0_red, \ + sys_state, \ + FACE_RECOGNIZE, \ + FACE_ENROLL, \ + FACE_DELETE, \ + detector, \ + img, \ + dl_recognizer, \ + detection_result, \ + IDLE, \ + kp, \ + sys_state_prev, \ + frame_count, \ + res, \ + dl_recognize_result sys_state = FACE_DELETE + def setup(): - global dual_button_0_blue, dual_button_0_red, sys_state, FACE_RECOGNIZE, FACE_ENROLL, FACE_DELETE, detector, img, dl_recognizer, detection_result, IDLE, kp, sys_state_prev, frame_count, res, dl_recognize_result + global \ + dual_button_0_blue, \ + dual_button_0_red, \ + sys_state, \ + FACE_RECOGNIZE, \ + FACE_ENROLL, \ + FACE_DELETE, \ + detector, \ + img, \ + dl_recognizer, \ + detection_result, \ + IDLE, \ + kp, \ + sys_state_prev, \ + frame_count, \ + res, \ + dl_recognize_result M5.begin() Widgets.fillScreen(0x222222) BtnPWR.setCallback(type=BtnPWR.CB_TYPE.WAS_CLICKED, cb=btnPWR_wasClicked_event) camera.init(pixformat=camera.RGB565, framesize=camera.QVGA) dual_button_0_blue, dual_button_0_red = DualButtonUnit((8, 9)) - dual_button_0_blue.setCallback(type=dual_button_0_blue.CB_TYPE.WAS_CLICKED, cb=dual_button_0_blue_wasClicked_event) - dual_button_0_red.setCallback(type=dual_button_0_red.CB_TYPE.WAS_CLICKED, cb=dual_button_0_red_wasClicked_event) + dual_button_0_blue.setCallback( + type=dual_button_0_blue.CB_TYPE.WAS_CLICKED, cb=dual_button_0_blue_wasClicked_event + ) + dual_button_0_red.setCallback( + type=dual_button_0_red.CB_TYPE.WAS_CLICKED, cb=dual_button_0_red_wasClicked_event + ) detector = dl.ObjectDetector(dl.model.HUMAN_FACE_DETECT) dl_recognizer = dl.HumanFaceRecognizer() IDLE = 0 @@ -60,8 +131,25 @@ def setup(): sys_state_prev = IDLE frame_count = 0 + def loop(): - global dual_button_0_blue, dual_button_0_red, sys_state, FACE_RECOGNIZE, FACE_ENROLL, FACE_DELETE, detector, img, dl_recognizer, detection_result, IDLE, kp, sys_state_prev, frame_count, res, dl_recognize_result + global \ + dual_button_0_blue, \ + dual_button_0_red, \ + sys_state, \ + FACE_RECOGNIZE, \ + FACE_ENROLL, \ + FACE_DELETE, \ + detector, \ + img, \ + dl_recognizer, \ + detection_result, \ + IDLE, \ + kp, \ + sys_state_prev, \ + frame_count, \ + res, \ + dl_recognize_result M5.update() dual_button_0_blue.tick(None) dual_button_0_red.tick(None) @@ -70,13 +158,15 @@ def loop(): if detection_result: for res in detection_result: kp = res.keypoint() - img.draw_string(10, 10, str('face'), color=0x3333ff, scale=1) - img.draw_circle(kp[0], kp[1], 3, color=0x3333ff, thickness=1, fill=True) - img.draw_circle(kp[2], kp[3], 3, color=0x33ff33, thickness=1, fill=True) - img.draw_circle(kp[4], kp[5], 3, color=0xff0000, thickness=1, fill=True) - img.draw_circle(kp[6], kp[7], 3, color=0x3333ff, thickness=1, fill=True) - img.draw_circle(kp[8], kp[9], 3, color=0x33ff33, thickness=1, fill=True) - img.draw_rectangle(res.x(), res.y(), res.w(), res.h(), color=0x3366ff, thickness=3, fill=False) + img.draw_string(10, 10, str("face"), color=0x3333FF, scale=1) + img.draw_circle(kp[0], kp[1], 3, color=0x3333FF, thickness=1, fill=True) + img.draw_circle(kp[2], kp[3], 3, color=0x33FF33, thickness=1, fill=True) + img.draw_circle(kp[4], kp[5], 3, color=0xFF0000, thickness=1, fill=True) + img.draw_circle(kp[6], kp[7], 3, color=0x3333FF, thickness=1, fill=True) + img.draw_circle(kp[8], kp[9], 3, color=0x33FF33, thickness=1, fill=True) + img.draw_rectangle( + res.x(), res.y(), res.w(), res.h(), color=0x3366FF, thickness=3, fill=False + ) if sys_state == FACE_DELETE: dl_recognizer.delete_id() sys_state_prev = sys_state @@ -91,26 +181,39 @@ def loop(): elif sys_state == FACE_RECOGNIZE: dl_recognize_result = dl_recognizer.recognize(img, res.keypoint()) if (dl_recognize_result.id()) > 0: - print((str('similarity: ') + str((dl_recognize_result.similarity())))) + print((str("similarity: ") + str((dl_recognize_result.similarity())))) sys_state_prev = sys_state sys_state = IDLE frame_count = 15 else: - img.draw_string(104, 10, str('face no detect'), color=0xff0000, scale=1) + img.draw_string(104, 10, str("face no detect"), color=0xFF0000, scale=1) if frame_count > 0: frame_count = frame_count - 1 if sys_state_prev == FACE_ENROLL: - img.draw_string(116, 10, str('face enroll'), color=0x33ff33, scale=1) + img.draw_string(116, 10, str("face enroll"), color=0x33FF33, scale=1) elif sys_state_prev == FACE_RECOGNIZE: if (dl_recognize_result.id()) > 0: - img.draw_string(100, 10, str((str('recognize id: ') + str((dl_recognize_result.id())))), color=0x33ff33, scale=1) + img.draw_string( + 100, + 10, + str((str("recognize id: ") + str((dl_recognize_result.id())))), + color=0x33FF33, + scale=1, + ) else: - img.draw_string(96, 10, str('no recognized'), color=0xff0000, scale=1) + img.draw_string(96, 10, str("no recognized"), color=0xFF0000, scale=1) elif sys_state_prev == FACE_DELETE: - img.draw_string(100, 10, str((str('remaining id: ') + str((dl_recognizer.enrolled_id_num())))), color=0xff0000, scale=1) + img.draw_string( + 100, + 10, + str((str("remaining id: ") + str((dl_recognizer.enrolled_id_num())))), + color=0xFF0000, + scale=1, + ) M5.Lcd.show(img, 0, 0, 320, 240) -if __name__ == '__main__': + +if __name__ == "__main__": try: setup() while True: @@ -118,7 +221,7 @@ def loop(): except (Exception, KeyboardInterrupt) as e: try: from utility import print_error_msg + print_error_msg(e) except ImportError: print("please update to latest firmware") - diff --git a/examples/advanced/dl/cores3_example_pedestrian_detect.py b/examples/advanced/dl/cores3_example_pedestrian_detect.py index 3c9e767d..64c0edf4 100644 --- a/examples/advanced/dl/cores3_example_pedestrian_detect.py +++ b/examples/advanced/dl/cores3_example_pedestrian_detect.py @@ -15,6 +15,7 @@ res = None kp = None + def setup(): global img, detector, detection_result, kp M5.begin() @@ -22,6 +23,7 @@ def setup(): camera.init(pixformat=camera.RGB565, framesize=camera.QVGA) detector = dl.ObjectDetector(dl.model.PEDESTRIAN_DETECT) + def loop(): global img, detector, detection_result, kp M5.update() @@ -30,10 +32,13 @@ def loop(): if detection_result: for res in detection_result: kp = res.keypoint() - img.draw_rectangle(res.x(), res.y(), res.w(), res.h(), color=0x3366ff, thickness=3, fill=False) + img.draw_rectangle( + res.x(), res.y(), res.w(), res.h(), color=0x3366FF, thickness=3, fill=False + ) M5.Lcd.show(img, 0, 0, 320, 240) -if __name__ == '__main__': + +if __name__ == "__main__": try: setup() while True: @@ -41,6 +46,7 @@ def loop(): except (Exception, KeyboardInterrupt) as e: try: from utility import print_error_msg + print_error_msg(e) except ImportError: print("please update to latest firmware") diff --git a/examples/advanced/image/cores3_example_draw_test.py b/examples/advanced/image/cores3_example_draw_test.py index fda53872..fd50dc13 100644 --- a/examples/advanced/image/cores3_example_draw_test.py +++ b/examples/advanced/image/cores3_example_draw_test.py @@ -9,23 +9,26 @@ img = None + def setup(): global img M5.begin() Widgets.fillScreen(0x222222) camera.init(pixformat=camera.RGB565, framesize=camera.QVGA) + def loop(): global img M5.update() img = camera.snapshot() - img.draw_string(10, 10, str('M5Stack'), color=0x3366ff, scale=2) - img.draw_rectangle(60, 80, 50, 40, color=0x33cc00, thickness=3, fill=False) - img.draw_line(200, 60, 260, 100, color=0xff0000, thickness=3) - img.draw_circle(160, 120, 30, color=0xffcc00, thickness=2, fill=False) + img.draw_string(10, 10, str("M5Stack"), color=0x3366FF, scale=2) + img.draw_rectangle(60, 80, 50, 40, color=0x33CC00, thickness=3, fill=False) + img.draw_line(200, 60, 260, 100, color=0xFF0000, thickness=3) + img.draw_circle(160, 120, 30, color=0xFFCC00, thickness=2, fill=False) M5.Lcd.show(img, 0, 0, 320, 240) -if __name__ == '__main__': + +if __name__ == "__main__": try: setup() while True: @@ -33,6 +36,7 @@ def loop(): except (Exception, KeyboardInterrupt) as e: try: from utility import print_error_msg + print_error_msg(e) except ImportError: print("please update to latest firmware") diff --git a/examples/advanced/jpg/take_photo_example.py b/examples/advanced/jpg/take_photo_example.py index 20bf08f5..3cc83be3 100644 --- a/examples/advanced/jpg/take_photo_example.py +++ b/examples/advanced/jpg/take_photo_example.py @@ -27,13 +27,14 @@ def setup(): last_time = 0 index = 0 + def loop(): global file_0, img, cnt, last_time, index, img_jpg M5.update() img = camera.snapshot() if (M5.Touch.getCount()) > 0: cnt = 5 - print('hello M5') + print("hello M5") if cnt > 0: if (time.ticks_diff((time.ticks_ms()), last_time)) >= 1000: last_time = time.ticks_ms() @@ -41,13 +42,16 @@ def loop(): if cnt == 0: img_jpg = jpg.encode(img, 80) index = index + 1 - file_0 = open('/flash/' + str((str('photo') + str(((str(index) + str('.jpg')))))), 'w') + file_0 = open( + "/flash/" + str((str("photo") + str((str(index) + str(".jpg"))))), "w" + ) file_0.write(img_jpg.bytearray()) file_0.close() img.draw_string(140, 80, str(cnt), color=0x000099, scale=5) M5.Lcd.show(img, 0, 0, 320, 240) -if __name__ == '__main__': + +if __name__ == "__main__": try: setup() while True: @@ -55,7 +59,7 @@ def loop(): except (Exception, KeyboardInterrupt) as e: try: from utility import print_error_msg + print_error_msg(e) except ImportError: print("please update to latest firmware") - diff --git a/examples/advanced/usb/device/m5cores3_usbd_keyboard_example.py b/examples/advanced/usb/device/m5cores3_usbd_keyboard_example.py index 1639ca1e..63704cbf 100644 --- a/examples/advanced/usb/device/m5cores3_usbd_keyboard_example.py +++ b/examples/advanced/usb/device/m5cores3_usbd_keyboard_example.py @@ -5,7 +5,7 @@ import M5 from M5 import * from unit import CardKBUnit -from usb.device.keyboard import Keyboard +from usb.device.keyboard import Keyboard from hardware import I2C from hardware import Pin @@ -17,22 +17,25 @@ key = None update = None + def cardkb_0_pressed_event(kb): global label, keyboard, i2c0, cardkb_0, key, update key = cardkb_0.get_key() update = True + def setup(): global label, keyboard, i2c0, cardkb_0, key, update M5.begin() Widgets.fillScreen(0x222222) - label = Widgets.Label("USB Keyboard", 73, 6, 1.0, 0x3cc7f1, 0x222222, Widgets.FONTS.DejaVu24) + label = Widgets.Label("USB Keyboard", 73, 6, 1.0, 0x3CC7F1, 0x222222, Widgets.FONTS.DejaVu24) keyboard = Keyboard() i2c0 = I2C(0, scl=Pin(1), sda=Pin(2), freq=100000) cardkb_0 = CardKBUnit(i2c0) cardkb_0.set_callback(cardkb_0_pressed_event) update = False + def loop(): global label, keyboard, i2c0, cardkb_0, key, update M5.update() @@ -42,7 +45,8 @@ def loop(): keyboard.input(str(chr(key))) update = False -if __name__ == '__main__': + +if __name__ == "__main__": try: setup() while True: @@ -50,7 +54,7 @@ def loop(): except (Exception, KeyboardInterrupt) as e: try: from utility import print_error_msg + print_error_msg(e) except ImportError: print("please update to latest firmware") - diff --git a/examples/advanced/usb/device/m5cores3_usbd_mouse_example.py b/examples/advanced/usb/device/m5cores3_usbd_mouse_example.py index 8662efd2..63770206 100644 --- a/examples/advanced/usb/device/m5cores3_usbd_mouse_example.py +++ b/examples/advanced/usb/device/m5cores3_usbd_mouse_example.py @@ -9,7 +9,6 @@ import m5utils - label0 = None mouse = None touch_active = None @@ -25,10 +24,22 @@ def setup(): - global label0, mouse, touch_active, sensitivity, x, last_touch_time, y, dx, click_active, dy, prev_x, prev_y + global \ + label0, \ + mouse, \ + touch_active, \ + sensitivity, \ + x, \ + last_touch_time, \ + y, \ + dx, \ + click_active, \ + dy, \ + prev_x, \ + prev_y M5.begin() Widgets.fillScreen(0x222222) - label0 = Widgets.Label("USB Mouse", 91, 6, 1.0, 0x158ee6, 0x222222, Widgets.FONTS.DejaVu24) + label0 = Widgets.Label("USB Mouse", 91, 6, 1.0, 0x158EE6, 0x222222, Widgets.FONTS.DejaVu24) mouse = Mouse() touch_active = False sensitivity = 2 @@ -37,7 +48,19 @@ def setup(): def loop(): - global label0, mouse, touch_active, sensitivity, x, last_touch_time, y, dx, click_active, dy, prev_x, prev_y + global \ + label0, \ + mouse, \ + touch_active, \ + sensitivity, \ + x, \ + last_touch_time, \ + y, \ + dx, \ + click_active, \ + dy, \ + prev_x, \ + prev_y M5.update() if mouse.is_open(): if M5.Touch.getCount(): @@ -70,7 +93,7 @@ def loop(): time.sleep_ms(100) -if __name__ == '__main__': +if __name__ == "__main__": try: setup() while True: @@ -78,7 +101,7 @@ def loop(): except (Exception, KeyboardInterrupt) as e: try: from utility import print_error_msg + print_error_msg(e) except ImportError: print("please update to latest firmware") - diff --git a/examples/hardware/camera/cores3_example_camera_display.py b/examples/hardware/camera/cores3_example_camera_display.py index efba7b5b..5c931c8c 100644 --- a/examples/hardware/camera/cores3_example_camera_display.py +++ b/examples/hardware/camera/cores3_example_camera_display.py @@ -9,19 +9,22 @@ img = None + def setup(): global img M5.begin() Widgets.fillScreen(0x222222) camera.init(pixformat=camera.RGB565, framesize=camera.QVGA) + def loop(): global img M5.update() img = camera.snapshot() M5.Lcd.show(img, 0, 0, 320, 240) -if __name__ == '__main__': + +if __name__ == "__main__": try: setup() while True: @@ -29,8 +32,7 @@ def loop(): except (Exception, KeyboardInterrupt) as e: try: from utility import print_error_msg + print_error_msg(e) except ImportError: print("please update to latest firmware") - - diff --git a/examples/module/usb/cores3_module_usb_gpio_example.py b/examples/module/usb/cores3_module_usb_gpio_example.py index d2c88c8c..fae95cce 100644 --- a/examples/module/usb/cores3_module_usb_gpio_example.py +++ b/examples/module/usb/cores3_module_usb_gpio_example.py @@ -8,7 +8,6 @@ import time - module_usb_0 = None last_time = None i = None @@ -53,7 +52,7 @@ def loop(): toggle = not toggle -if __name__ == '__main__': +if __name__ == "__main__": try: setup() while True: @@ -61,7 +60,7 @@ def loop(): except (Exception, KeyboardInterrupt) as e: try: from utility import print_error_msg + print_error_msg(e) except ImportError: print("please update to latest firmware") - diff --git a/examples/module/usb/cores3_module_usb_kb_example.py b/examples/module/usb/cores3_module_usb_kb_example.py index ac3e3a26..1bdbb57b 100644 --- a/examples/module/usb/cores3_module_usb_kb_example.py +++ b/examples/module/usb/cores3_module_usb_kb_example.py @@ -7,7 +7,6 @@ from module import USBModule - module_usb_0 = None modifier = None indata = None @@ -26,27 +25,27 @@ def loop(): module_usb_0.poll_data() modifier = module_usb_0.read_kb_modifier() if modifier & 0x01: - print('Left Control pressed') + print("Left Control pressed") if modifier & 0x02: - print('Left Shift pressed') + print("Left Shift pressed") if modifier & 0x04: - print('Left Alt pressed') + print("Left Alt pressed") if modifier & 0x08: - print('Left GUI pressed') + print("Left GUI pressed") if modifier & 0x10: - print('Right Control pressed') + print("Right Control pressed") if modifier & 0x20: - print('Right Shift pressed') + print("Right Shift pressed") if modifier & 0x40: - print('Right Alt pressed') + print("Right Alt pressed") if modifier & 0x80: - print('Right GUI pressed') + print("Right GUI pressed") indata = module_usb_0.read_kb_input(True) if indata: print(indata) -if __name__ == '__main__': +if __name__ == "__main__": try: setup() while True: @@ -54,7 +53,7 @@ def loop(): except (Exception, KeyboardInterrupt) as e: try: from utility import print_error_msg + print_error_msg(e) except ImportError: print("please update to latest firmware") - diff --git a/examples/module/usb/cores3_module_usb_mouse_example.py b/examples/module/usb/cores3_module_usb_mouse_example.py index fa7dc45b..0d559d70 100644 --- a/examples/module/usb/cores3_module_usb_mouse_example.py +++ b/examples/module/usb/cores3_module_usb_mouse_example.py @@ -7,7 +7,6 @@ from module import USBModule - title0 = None label_x = None label_y = None @@ -23,9 +22,11 @@ def setup(): global title0, label_x, label_y, module_usb_0, x, y, mouse_move, dx, dy M5.begin() Widgets.fillScreen(0x222222) - title0 = Widgets.Title("Module USB Example mouse", 3, 0xffffff, 0x0000FF, Widgets.FONTS.DejaVu18) - label_x = Widgets.Label("x", 130, 90, 1.0, 0xffffff, 0x222222, Widgets.FONTS.DejaVu18) - label_y = Widgets.Label("y", 180, 90, 1.0, 0xffffff, 0x222222, Widgets.FONTS.DejaVu18) + title0 = Widgets.Title( + "Module USB Example mouse", 3, 0xFFFFFF, 0x0000FF, Widgets.FONTS.DejaVu18 + ) + label_x = Widgets.Label("x", 130, 90, 1.0, 0xFFFFFF, 0x222222, Widgets.FONTS.DejaVu18) + label_y = Widgets.Label("y", 180, 90, 1.0, 0xFFFFFF, 0x222222, Widgets.FONTS.DejaVu18) module_usb_0 = USBModule(pin_cs=1, pin_int=10) x = 0 y = 0 @@ -35,23 +36,23 @@ def loop(): global title0, label_x, label_y, module_usb_0, x, y, mouse_move, dx, dy module_usb_0.poll_data() if module_usb_0.is_left_btn_pressed(): - print('click left') + print("click left") if module_usb_0.is_right_btn_pressed(): - print('click right') + print("click right") if module_usb_0.is_middle_btn_pressed(): - print('click middle') + print("click middle") mouse_move = module_usb_0.read_mouse_move() dx = mouse_move[0] dy = mouse_move[1] if dx != 0 or dy != 0: - print((str('move: ') + str(((str(dx) + str(((str(', ') + str(dy))))))))) + print((str("move: ") + str((str(dx) + str((str(", ") + str(dy))))))) x = min(max(x + dx, 0), 320) y = min(max(y + dy, 0), 240) label_x.setText(str(x)) label_y.setText(str(y)) -if __name__ == '__main__': +if __name__ == "__main__": try: setup() while True: @@ -59,7 +60,7 @@ def loop(): except (Exception, KeyboardInterrupt) as e: try: from utility import print_error_msg + print_error_msg(e) except ImportError: print("please update to latest firmware") - diff --git a/examples/softwave/code_scanner/qrcode_detect_example.py b/examples/softwave/code_scanner/qrcode_detect_example.py index db4980fd..007e3f9e 100644 --- a/examples/softwave/code_scanner/qrcode_detect_example.py +++ b/examples/softwave/code_scanner/qrcode_detect_example.py @@ -12,6 +12,7 @@ img = None qrcode = None + def setup(): global img, qrcode M5.begin() @@ -19,6 +20,7 @@ def setup(): camera.init(pixformat=camera.RGB565, framesize=camera.QVGA) camera.set_hmirror(False) + def loop(): global img, qrcode M5.update() @@ -27,10 +29,11 @@ def loop(): if qrcode: print(qrcode.payload()) print(qrcode.type_name()) - img.draw_string(10, 10, str(qrcode.payload()), color=0x3333ff, scale=2) + img.draw_string(10, 10, str(qrcode.payload()), color=0x3333FF, scale=2) M5.Lcd.show(img, 0, 0, 320, 240) -if __name__ == '__main__': + +if __name__ == "__main__": try: setup() while True: @@ -38,7 +41,7 @@ def loop(): except (Exception, KeyboardInterrupt) as e: try: from utility import print_error_msg + print_error_msg(e) except ImportError: print("please update to latest firmware") - diff --git a/examples/softwave/dl/cores3_example_human_face_detect.py b/examples/softwave/dl/cores3_example_human_face_detect.py index dc8ed440..7ea322f8 100644 --- a/examples/softwave/dl/cores3_example_human_face_detect.py +++ b/examples/softwave/dl/cores3_example_human_face_detect.py @@ -9,13 +9,13 @@ import image - img = None detector = None detection_result = None res = None kp = None + def setup(): global img, detector, detection_result, kp M5.begin() @@ -23,6 +23,7 @@ def setup(): camera.init(pixformat=camera.RGB565, framesize=camera.QVGA) detector = dl.ObjectDetector(dl.model.HUMAN_FACE_DETECT) + def loop(): global img, detector, detection_result, kp M5.update() @@ -31,15 +32,18 @@ def loop(): if detection_result: for res in detection_result: kp = res.keypoint() - img.draw_circle(kp[0], kp[1], 3, color=0x0000ff, thickness=1, fill=True) - img.draw_circle(kp[2], kp[3], 3, color=0x00ff00, thickness=1, fill=True) - img.draw_circle(kp[4], kp[5], 3, color=0xff0000, thickness=1, fill=True) - img.draw_circle(kp[6], kp[7], 3, color=0x0000ff, thickness=1, fill=True) - img.draw_circle(kp[8], kp[9], 3, color=0x00ff00, thickness=1, fill=True) - img.draw_rectangle(res.x(), res.y(), res.w(), res.h(), color=0x3366ff, thickness=3, fill=False) + img.draw_circle(kp[0], kp[1], 3, color=0x0000FF, thickness=1, fill=True) + img.draw_circle(kp[2], kp[3], 3, color=0x00FF00, thickness=1, fill=True) + img.draw_circle(kp[4], kp[5], 3, color=0xFF0000, thickness=1, fill=True) + img.draw_circle(kp[6], kp[7], 3, color=0x0000FF, thickness=1, fill=True) + img.draw_circle(kp[8], kp[9], 3, color=0x00FF00, thickness=1, fill=True) + img.draw_rectangle( + res.x(), res.y(), res.w(), res.h(), color=0x3366FF, thickness=3, fill=False + ) M5.Lcd.show(img, 0, 0, 320, 240) -if __name__ == '__main__': + +if __name__ == "__main__": try: setup() while True: @@ -47,7 +51,7 @@ def loop(): except (Exception, KeyboardInterrupt) as e: try: from utility import print_error_msg + print_error_msg(e) except ImportError: print("please update to latest firmware") - diff --git a/examples/softwave/dl/cores3_example_human_face_recognition.py b/examples/softwave/dl/cores3_example_human_face_recognition.py index a3753597..6e4e70da 100644 --- a/examples/softwave/dl/cores3_example_human_face_recognition.py +++ b/examples/softwave/dl/cores3_example_human_face_recognition.py @@ -29,27 +29,98 @@ dl_recognize_result = None -def dual_button_0_blue_wasClicked_event(state): - global dual_button_0_blue, dual_button_0_red, sys_state, FACE_RECOGNIZE, FACE_ENROLL, FACE_DELETE, detector, img, dl_recognizer, detection_result, IDLE, kp, sys_state_prev, frame_count, res, dl_recognize_result +def dual_button_0_blue_wasClicked_event(state): # noqa: N802 + global \ + dual_button_0_blue, \ + dual_button_0_red, \ + sys_state, \ + FACE_RECOGNIZE, \ + FACE_ENROLL, \ + FACE_DELETE, \ + detector, \ + img, \ + dl_recognizer, \ + detection_result, \ + IDLE, \ + kp, \ + sys_state_prev, \ + frame_count, \ + res, \ + dl_recognize_result sys_state = FACE_RECOGNIZE -def dual_button_0_red_wasClicked_event(state): - global dual_button_0_blue, dual_button_0_red, sys_state, FACE_RECOGNIZE, FACE_ENROLL, FACE_DELETE, detector, img, dl_recognizer, detection_result, IDLE, kp, sys_state_prev, frame_count, res, dl_recognize_result + +def dual_button_0_red_wasClicked_event(state): # noqa: N802 + global \ + dual_button_0_blue, \ + dual_button_0_red, \ + sys_state, \ + FACE_RECOGNIZE, \ + FACE_ENROLL, \ + FACE_DELETE, \ + detector, \ + img, \ + dl_recognizer, \ + detection_result, \ + IDLE, \ + kp, \ + sys_state_prev, \ + frame_count, \ + res, \ + dl_recognize_result sys_state = FACE_ENROLL -def btnPWR_wasClicked_event(state): - global dual_button_0_blue, dual_button_0_red, sys_state, FACE_RECOGNIZE, FACE_ENROLL, FACE_DELETE, detector, img, dl_recognizer, detection_result, IDLE, kp, sys_state_prev, frame_count, res, dl_recognize_result + +def btnPWR_wasClicked_event(state): # noqa: N802 + global \ + dual_button_0_blue, \ + dual_button_0_red, \ + sys_state, \ + FACE_RECOGNIZE, \ + FACE_ENROLL, \ + FACE_DELETE, \ + detector, \ + img, \ + dl_recognizer, \ + detection_result, \ + IDLE, \ + kp, \ + sys_state_prev, \ + frame_count, \ + res, \ + dl_recognize_result sys_state = FACE_DELETE + def setup(): - global dual_button_0_blue, dual_button_0_red, sys_state, FACE_RECOGNIZE, FACE_ENROLL, FACE_DELETE, detector, img, dl_recognizer, detection_result, IDLE, kp, sys_state_prev, frame_count, res, dl_recognize_result + global \ + dual_button_0_blue, \ + dual_button_0_red, \ + sys_state, \ + FACE_RECOGNIZE, \ + FACE_ENROLL, \ + FACE_DELETE, \ + detector, \ + img, \ + dl_recognizer, \ + detection_result, \ + IDLE, \ + kp, \ + sys_state_prev, \ + frame_count, \ + res, \ + dl_recognize_result M5.begin() Widgets.fillScreen(0x222222) BtnPWR.setCallback(type=BtnPWR.CB_TYPE.WAS_CLICKED, cb=btnPWR_wasClicked_event) camera.init(pixformat=camera.RGB565, framesize=camera.QVGA) dual_button_0_blue, dual_button_0_red = DualButtonUnit((8, 9)) - dual_button_0_blue.setCallback(type=dual_button_0_blue.CB_TYPE.WAS_CLICKED, cb=dual_button_0_blue_wasClicked_event) - dual_button_0_red.setCallback(type=dual_button_0_red.CB_TYPE.WAS_CLICKED, cb=dual_button_0_red_wasClicked_event) + dual_button_0_blue.setCallback( + type=dual_button_0_blue.CB_TYPE.WAS_CLICKED, cb=dual_button_0_blue_wasClicked_event + ) + dual_button_0_red.setCallback( + type=dual_button_0_red.CB_TYPE.WAS_CLICKED, cb=dual_button_0_red_wasClicked_event + ) detector = dl.ObjectDetector(dl.model.HUMAN_FACE_DETECT) dl_recognizer = dl.HumanFaceRecognizer() IDLE = 0 @@ -60,8 +131,25 @@ def setup(): sys_state_prev = IDLE frame_count = 0 + def loop(): - global dual_button_0_blue, dual_button_0_red, sys_state, FACE_RECOGNIZE, FACE_ENROLL, FACE_DELETE, detector, img, dl_recognizer, detection_result, IDLE, kp, sys_state_prev, frame_count, res, dl_recognize_result + global \ + dual_button_0_blue, \ + dual_button_0_red, \ + sys_state, \ + FACE_RECOGNIZE, \ + FACE_ENROLL, \ + FACE_DELETE, \ + detector, \ + img, \ + dl_recognizer, \ + detection_result, \ + IDLE, \ + kp, \ + sys_state_prev, \ + frame_count, \ + res, \ + dl_recognize_result M5.update() dual_button_0_blue.tick(None) dual_button_0_red.tick(None) @@ -70,13 +158,15 @@ def loop(): if detection_result: for res in detection_result: kp = res.keypoint() - img.draw_string(10, 10, str('face'), color=0x3333ff, scale=1) - img.draw_circle(kp[0], kp[1], 3, color=0x3333ff, thickness=1, fill=True) - img.draw_circle(kp[2], kp[3], 3, color=0x33ff33, thickness=1, fill=True) - img.draw_circle(kp[4], kp[5], 3, color=0xff0000, thickness=1, fill=True) - img.draw_circle(kp[6], kp[7], 3, color=0x3333ff, thickness=1, fill=True) - img.draw_circle(kp[8], kp[9], 3, color=0x33ff33, thickness=1, fill=True) - img.draw_rectangle(res.x(), res.y(), res.w(), res.h(), color=0x3366ff, thickness=3, fill=False) + img.draw_string(10, 10, str("face"), color=0x3333FF, scale=1) + img.draw_circle(kp[0], kp[1], 3, color=0x3333FF, thickness=1, fill=True) + img.draw_circle(kp[2], kp[3], 3, color=0x33FF33, thickness=1, fill=True) + img.draw_circle(kp[4], kp[5], 3, color=0xFF0000, thickness=1, fill=True) + img.draw_circle(kp[6], kp[7], 3, color=0x3333FF, thickness=1, fill=True) + img.draw_circle(kp[8], kp[9], 3, color=0x33FF33, thickness=1, fill=True) + img.draw_rectangle( + res.x(), res.y(), res.w(), res.h(), color=0x3366FF, thickness=3, fill=False + ) if sys_state == FACE_DELETE: dl_recognizer.delete_id() sys_state_prev = sys_state @@ -91,26 +181,39 @@ def loop(): elif sys_state == FACE_RECOGNIZE: dl_recognize_result = dl_recognizer.recognize(img, res.keypoint()) if (dl_recognize_result.id()) > 0: - print((str('similarity: ') + str((dl_recognize_result.similarity())))) + print((str("similarity: ") + str((dl_recognize_result.similarity())))) sys_state_prev = sys_state sys_state = IDLE frame_count = 15 else: - img.draw_string(104, 10, str('face no detect'), color=0xff0000, scale=1) + img.draw_string(104, 10, str("face no detect"), color=0xFF0000, scale=1) if frame_count > 0: frame_count = frame_count - 1 if sys_state_prev == FACE_ENROLL: - img.draw_string(116, 10, str('face enroll'), color=0x33ff33, scale=1) + img.draw_string(116, 10, str("face enroll"), color=0x33FF33, scale=1) elif sys_state_prev == FACE_RECOGNIZE: if (dl_recognize_result.id()) > 0: - img.draw_string(100, 10, str((str('recognize id: ') + str((dl_recognize_result.id())))), color=0x33ff33, scale=1) + img.draw_string( + 100, + 10, + str((str("recognize id: ") + str((dl_recognize_result.id())))), + color=0x33FF33, + scale=1, + ) else: - img.draw_string(96, 10, str('no recognized'), color=0xff0000, scale=1) + img.draw_string(96, 10, str("no recognized"), color=0xFF0000, scale=1) elif sys_state_prev == FACE_DELETE: - img.draw_string(100, 10, str((str('remaining id: ') + str((dl_recognizer.enrolled_id_num())))), color=0xff0000, scale=1) + img.draw_string( + 100, + 10, + str((str("remaining id: ") + str((dl_recognizer.enrolled_id_num())))), + color=0xFF0000, + scale=1, + ) M5.Lcd.show(img, 0, 0, 320, 240) -if __name__ == '__main__': + +if __name__ == "__main__": try: setup() while True: @@ -118,7 +221,7 @@ def loop(): except (Exception, KeyboardInterrupt) as e: try: from utility import print_error_msg + print_error_msg(e) except ImportError: print("please update to latest firmware") - diff --git a/examples/softwave/dl/cores3_example_pedestrian_detect.py b/examples/softwave/dl/cores3_example_pedestrian_detect.py index 3c9e767d..64c0edf4 100644 --- a/examples/softwave/dl/cores3_example_pedestrian_detect.py +++ b/examples/softwave/dl/cores3_example_pedestrian_detect.py @@ -15,6 +15,7 @@ res = None kp = None + def setup(): global img, detector, detection_result, kp M5.begin() @@ -22,6 +23,7 @@ def setup(): camera.init(pixformat=camera.RGB565, framesize=camera.QVGA) detector = dl.ObjectDetector(dl.model.PEDESTRIAN_DETECT) + def loop(): global img, detector, detection_result, kp M5.update() @@ -30,10 +32,13 @@ def loop(): if detection_result: for res in detection_result: kp = res.keypoint() - img.draw_rectangle(res.x(), res.y(), res.w(), res.h(), color=0x3366ff, thickness=3, fill=False) + img.draw_rectangle( + res.x(), res.y(), res.w(), res.h(), color=0x3366FF, thickness=3, fill=False + ) M5.Lcd.show(img, 0, 0, 320, 240) -if __name__ == '__main__': + +if __name__ == "__main__": try: setup() while True: @@ -41,6 +46,7 @@ def loop(): except (Exception, KeyboardInterrupt) as e: try: from utility import print_error_msg + print_error_msg(e) except ImportError: print("please update to latest firmware") diff --git a/examples/softwave/image/cores3_example_draw_test.py b/examples/softwave/image/cores3_example_draw_test.py index fda53872..fd50dc13 100644 --- a/examples/softwave/image/cores3_example_draw_test.py +++ b/examples/softwave/image/cores3_example_draw_test.py @@ -9,23 +9,26 @@ img = None + def setup(): global img M5.begin() Widgets.fillScreen(0x222222) camera.init(pixformat=camera.RGB565, framesize=camera.QVGA) + def loop(): global img M5.update() img = camera.snapshot() - img.draw_string(10, 10, str('M5Stack'), color=0x3366ff, scale=2) - img.draw_rectangle(60, 80, 50, 40, color=0x33cc00, thickness=3, fill=False) - img.draw_line(200, 60, 260, 100, color=0xff0000, thickness=3) - img.draw_circle(160, 120, 30, color=0xffcc00, thickness=2, fill=False) + img.draw_string(10, 10, str("M5Stack"), color=0x3366FF, scale=2) + img.draw_rectangle(60, 80, 50, 40, color=0x33CC00, thickness=3, fill=False) + img.draw_line(200, 60, 260, 100, color=0xFF0000, thickness=3) + img.draw_circle(160, 120, 30, color=0xFFCC00, thickness=2, fill=False) M5.Lcd.show(img, 0, 0, 320, 240) -if __name__ == '__main__': + +if __name__ == "__main__": try: setup() while True: @@ -33,6 +36,7 @@ def loop(): except (Exception, KeyboardInterrupt) as e: try: from utility import print_error_msg + print_error_msg(e) except ImportError: print("please update to latest firmware") diff --git a/examples/softwave/jpg/take_photo_example.py b/examples/softwave/jpg/take_photo_example.py index 20bf08f5..3cc83be3 100644 --- a/examples/softwave/jpg/take_photo_example.py +++ b/examples/softwave/jpg/take_photo_example.py @@ -27,13 +27,14 @@ def setup(): last_time = 0 index = 0 + def loop(): global file_0, img, cnt, last_time, index, img_jpg M5.update() img = camera.snapshot() if (M5.Touch.getCount()) > 0: cnt = 5 - print('hello M5') + print("hello M5") if cnt > 0: if (time.ticks_diff((time.ticks_ms()), last_time)) >= 1000: last_time = time.ticks_ms() @@ -41,13 +42,16 @@ def loop(): if cnt == 0: img_jpg = jpg.encode(img, 80) index = index + 1 - file_0 = open('/flash/' + str((str('photo') + str(((str(index) + str('.jpg')))))), 'w') + file_0 = open( + "/flash/" + str((str("photo") + str((str(index) + str(".jpg"))))), "w" + ) file_0.write(img_jpg.bytearray()) file_0.close() img.draw_string(140, 80, str(cnt), color=0x000099, scale=5) M5.Lcd.show(img, 0, 0, 320, 240) -if __name__ == '__main__': + +if __name__ == "__main__": try: setup() while True: @@ -55,7 +59,7 @@ def loop(): except (Exception, KeyboardInterrupt) as e: try: from utility import print_error_msg + print_error_msg(e) except ImportError: print("please update to latest firmware") - diff --git a/m5stack/cmodules/omv/imlib/cmsis_iccarm.h b/m5stack/cmodules/omv/imlib/cmsis_iccarm.h old mode 100755 new mode 100644 index 8228ecd4..8c21f2c7 --- a/m5stack/cmodules/omv/imlib/cmsis_iccarm.h +++ b/m5stack/cmodules/omv/imlib/cmsis_iccarm.h @@ -1,49 +1,44 @@ -/**************************************************************************//** - * @file cmsis_iccarm.h - * @brief CMSIS compiler ICCARM (IAR Compiler for Arm) header file - * @version V5.3.0 - * @date 14. April 2021 - ******************************************************************************/ - -//------------------------------------------------------------------------------ -// -// Copyright (c) 2017-2021 IAR Systems -// Copyright (c) 2017-2021 Arm Limited. All rights reserved. -// -// SPDX-License-Identifier: Apache-2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License") -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//------------------------------------------------------------------------------ -#include - -#ifndef __CMSIS_ICCARM_H__ -#define __CMSIS_ICCARM_H__ - -static inline uint32_t __USAT(int32_t val, uint32_t sat) -{ - if (sat <= 31U) - { - const uint32_t max = ((1U << sat) - 1U); - if (val > (int32_t)max) - { - return max; - } - else if (val < 0) - { - return 0U; - } - } - return (uint32_t)val; -} - -#endif /* __CMSIS_ICCARM_H__ */ +/**************************************************************************//** + * @file cmsis_iccarm.h + * @brief CMSIS compiler ICCARM (IAR Compiler for Arm) header file + * @version V5.3.0 + * @date 14. April 2021 + ******************************************************************************/ + +// ------------------------------------------------------------------------------ +// +// Copyright (c) 2017-2021 IAR Systems +// Copyright (c) 2017-2021 Arm Limited. All rights reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License") +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// ------------------------------------------------------------------------------ +#include + +#ifndef __CMSIS_ICCARM_H__ +#define __CMSIS_ICCARM_H__ + +static inline uint32_t __USAT(int32_t val, uint32_t sat) { + if (sat <= 31U) { + const uint32_t max = ((1U << sat) - 1U); + if (val > (int32_t)max) { + return max; + } else if (val < 0) { + return 0U; + } + } + return (uint32_t)val; +} + +#endif /* __CMSIS_ICCARM_H__ */ diff --git a/m5stack/cmodules/omv/imlib/draw.c b/m5stack/cmodules/omv/imlib/draw.c old mode 100755 new mode 100644 index 6410683b..2032c85c --- a/m5stack/cmodules/omv/imlib/draw.c +++ b/m5stack/cmodules/omv/imlib/draw.c @@ -1,603 +1,608 @@ -/* - * SPDX-License-Identifier: MIT - * - * Copyright (C) 2013-2024 OpenMV, LLC. - * Copyright (c) 2024 M5Stack Technology CO LTD - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * Basic drawing functions. - */ -#include "imlib.h" -#include -#include -#include -#include "utils.h" -#include "font.h" -#include "fmath.h" - - - -void *imlib_compute_row_ptr(const image_t *img, int y) { - switch (img->pixfmt) { - case OMV_PIXFORMAT_BINARY: { - return IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(img, y); - } - case OMV_PIXFORMAT_GRAYSCALE: { - return IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(img, y); - } - case OMV_PIXFORMAT_RGB565: { - return IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(img, y); - } - default: { - // This shouldn't happen, at least we return a valid memory block - return img->data; - } - } -} - -inline int imlib_get_pixel_fast(image_t *img, const void *row_ptr, int x) { - switch (img->pixfmt) { - case OMV_PIXFORMAT_BINARY: { - return IMAGE_GET_BINARY_PIXEL_FAST((uint32_t *) row_ptr, x); - } - case OMV_PIXFORMAT_GRAYSCALE: { - return IMAGE_GET_GRAYSCALE_PIXEL_FAST((uint8_t *) row_ptr, x); - } - case OMV_PIXFORMAT_RGB565: { - return IMAGE_GET_RGB565_PIXEL_FAST((uint16_t *) row_ptr, x); - } - default: { - return -1; - } - } -} - - -// Set pixel (handles boundary check and image type check). -void imlib_set_pixel(image_t *img, int x, int y, int p) { - if ((0 <= x) && (x < img->w) && (0 <= y) && (y < img->h)) { - switch (img->pixfmt) { - case OMV_PIXFORMAT_BINARY: { - IMAGE_PUT_BINARY_PIXEL(img, x, y, p); - break; - } - case OMV_PIXFORMAT_GRAYSCALE: { - IMAGE_PUT_GRAYSCALE_PIXEL(img, x, y, p); - break; - } - case OMV_PIXFORMAT_RGB565: { - IMAGE_PUT_RGB565_PIXEL(img, x, y, p); - break; - } - default: { - break; - } - } - } -} - -// https://stackoverflow.com/questions/1201200/fast-algorithm-for-drawing-filled-circles -static void point_fill(image_t *img, int cx, int cy, int r0, int r1, int c) { - for (int y = r0; y <= r1; y++) { - for (int x = r0; x <= r1; x++) { - if (((x * x) + (y * y)) <= (r0 * r0)) { - imlib_set_pixel(img, cx + x, cy + y, c); - } - } - } -} - -static void imlib_set_pixel_aa(image_t *img, int x, int y, int err, int c) { - if (!((0 <= x) && (x < img->w) && (0 <= y) && (y < img->h))) { - return; - } - - switch (img->pixfmt) { - case OMV_PIXFORMAT_BINARY: { - uint32_t *ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(img, y); - int old_c = IMAGE_GET_BINARY_PIXEL_FAST(ptr, x) * 255; - int new_c = (((old_c * err) + ((c ? 255 : 0) * (256 - err))) >> 8) > 127; - IMAGE_PUT_BINARY_PIXEL_FAST(ptr, x, new_c); - break; - } - case OMV_PIXFORMAT_GRAYSCALE: { - uint8_t *ptr = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(img, y); - int old_c = IMAGE_GET_GRAYSCALE_PIXEL_FAST(ptr, x); - int new_c = ((old_c * err) + ((c & 0xff) * (256 - err))) >> 8; - IMAGE_PUT_GRAYSCALE_PIXEL_FAST(ptr, x, new_c); - break; - } - case OMV_PIXFORMAT_RGB565: { - uint16_t *ptr = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(img, y); - int old_c = IMAGE_GET_RGB565_PIXEL_FAST(ptr, x); - int old_c_r5 = COLOR_RGB565_TO_R5(old_c); - int old_c_g6 = COLOR_RGB565_TO_G6(old_c); - int old_c_b5 = COLOR_RGB565_TO_B5(old_c); - int c_r5 = COLOR_RGB565_TO_R5(c); - int c_g6 = COLOR_RGB565_TO_G6(c); - int c_b5 = COLOR_RGB565_TO_B5(c); - int new_c_r5 = ((old_c_r5 * err) + (c_r5 * (256 - err))) >> 8; - int new_c_g6 = ((old_c_g6 * err) + (c_g6 * (256 - err))) >> 8; - int new_c_b5 = ((old_c_b5 * err) + (c_b5 * (256 - err))) >> 8; - int new_c = COLOR_R5_G6_B5_TO_RGB565(new_c_r5, new_c_g6, new_c_b5); - IMAGE_PUT_RGB565_PIXEL_FAST(ptr, x, new_c); - break; - } - default: { - break; - } - } -} - -// https://gist.github.com/randvoorhies/807ce6e20840ab5314eb7c547899de68#file-bresenham-js-L381 -static void imlib_draw_thin_line(image_t *img, int x0, int y0, int x1, int y1, int c) { - const int dx = abs(x1 - x0); - const int sx = x0 < x1 ? 1 : -1; - const int dy = abs(y1 - y0); - const int sy = y0 < y1 ? 1 : -1; - int err = dx - dy; - int e2, x2; // error value e_xy - int ed = dx + dy == 0 ? 1 : fast_floorf(fast_sqrtf(dx * dx + dy * dy)); - - for (;;) { - // pixel loop - imlib_set_pixel_aa(img, x0, y0, 256 * abs(err - dx + dy) / ed, c); - e2 = err; - x2 = x0; - if (2 * e2 >= -dx) { - // x step - if (x0 == x1) { - break; - } - if (e2 + dy < ed) { - imlib_set_pixel_aa(img, x0, y0 + sy, 256 * (e2 + dy) / ed, c); - } - err -= dy; - x0 += sx; - } - if (2 * e2 <= dy) { - // y step - if (y0 == y1) { - break; - } - if (dx - e2 < ed) { - imlib_set_pixel_aa(img, x2 + sx, y0, 256 * (dx - e2) / ed, c); - } - err += dx; - y0 += sy; - } - } -} - -// https://rosettacode.org/wiki/Bitmap/Bresenham%27s_line_algorithm#C -void imlib_draw_line(image_t* img, int x0, int y0, int x1, int y1, int color, int thickness) { - if (thickness > 0) { - int thickness0 = (thickness - 0) / 2; - int thickness1 = (thickness - 1) / 2; - int dx = abs(x1 - x0), sx = (x0 < x1) ? 1 : -1; - int dy = abs(y1 - y0), sy = (y0 < y1) ? 1 : -1; - int err = ((dx > dy) ? dx : -dy) / 2; - - for (;;) { - point_fill(img, x0, y0, -thickness0, thickness1, color); - if ((x0 == x1) && (y0 == y1)) break; - int e2 = err; - if (e2 > -dx) { err -= dy; x0 += sx; } - if (e2 < dy) { err += dx; y0 += sy; } - } - } -} - -static void xLine(image_t *img, int x1, int x2, int y, int c) { - while (x1 <= x2) { - imlib_set_pixel(img, x1++, y, c); - } -} - -static void yLine(image_t *img, int x, int y1, int y2, int c) { - while (y1 <= y2) { - imlib_set_pixel(img, x, y1++, c); - } -} - -void imlib_draw_rectangle(image_t *img, int rx, int ry, int rw, int rh, int c, int thickness, bool fill) { - if (fill) { - - for (int y = ry, yy = ry + rh; y < yy; y++) { - for (int x = rx, xx = rx + rw; x < xx; x++) { - imlib_set_pixel(img, x, y, c); - } - } - - } else if (thickness > 0) { - int thickness0 = (thickness - 0) / 2; - int thickness1 = (thickness - 1) / 2; - - for (int i = rx - thickness0, j = rx + rw + thickness1, k = ry + rh - 1; i < j; i++) { - yLine(img, i, ry - thickness0, ry + thickness1, c); - yLine(img, i, k - thickness0, k + thickness1, c); - } - - for (int i = ry - thickness0, j = ry + rh + thickness1, k = rx + rw - 1; i < j; i++) { - xLine(img, rx - thickness0, rx + thickness1, i, c); - xLine(img, k - thickness0, k + thickness1, i, c); - } - } -} - -// https://stackoverflow.com/questions/27755514/circle-with-thickness-drawing-algorithm -void imlib_draw_circle(image_t* img, int cx, int cy, int radius, int color, int thickness, bool fill) { - if (fill) { - point_fill(img, cx, cy, -radius, radius, color); - } else if (thickness > 0) { - int thickness0 = (thickness - 0) / 2; - int thickness1 = (thickness - 1) / 2; - - int xo = radius + thickness0; - int xi = IM_MAX(radius - thickness1, 0); - int xi_tmp = xi; - int y = 0; - int erro = 1 - xo; - int erri = 1 - xi; - - while (xo >= y) { - xLine(img, cx + xi, cx + xo, cy + y, color); - yLine(img, cx + y, cy + xi, cy + xo, color); - xLine(img, cx - xo, cx - xi, cy + y, color); - yLine(img, cx - y, cy + xi, cy + xo, color); - xLine(img, cx - xo, cx - xi, cy - y, color); - yLine(img, cx - y, cy - xo, cy - xi, color); - xLine(img, cx + xi, cx + xo, cy - y, color); - yLine(img, cx + y, cy - xo, cy - xi, color); - - y++; - - if (erro < 0) { - erro += 2 * y + 1; - } else { - xo--; - erro += 2 * (y - xo + 1); - } - - if (y > xi_tmp) { - xi = y; - } else { - if (erri < 0) { - erri += 2 * y + 1; - } else { - xi--; - erri += 2 * (y - xi + 1); - } - } - } - } -} - -// https://scratch.mit.edu/projects/50039326/ -static void scratch_draw_pixel(image_t *img, - int x0, - int y0, - int dx, - int dy, - float shear_dx, - float shear_dy, - int r0, - int r1, - int c) { - point_fill(img, x0 + dx, y0 + dy + fast_floorf((dx * shear_dy) / shear_dx), r0, r1, c); -} - -// https://scratch.mit.edu/projects/50039326/ -static void scratch_draw_line(image_t *img, int x0, int y0, int dx, int dy0, int dy1, float shear_dx, float shear_dy, int c) { - int y = y0 + fast_floorf((dx * shear_dy) / shear_dx); - yLine(img, x0 + dx, y + dy0, y + dy1, c); -} - -// https://scratch.mit.edu/projects/50039326/ -static void scratch_draw_sheared_ellipse(image_t *img, - int x0, - int y0, - int width, - int height, - bool filled, - float shear_dx, - float shear_dy, - int c, - int thickness) { - int thickness0 = (thickness - 0) / 2; - int thickness1 = (thickness - 1) / 2; - if (((thickness > 0) || filled) && (shear_dx != 0)) { - int a_squared = width * width; - int four_a_squared = a_squared * 4; - int b_squared = height * height; - int four_b_squared = b_squared * 4; - - int x = 0; - int y = height; - int sigma = (2 * b_squared) + (a_squared * (1 - (2 * height))); - - while ((b_squared * x) <= (a_squared * y)) { - if (filled) { - scratch_draw_line(img, x0, y0, x, -y, y, shear_dx, shear_dy, c); - scratch_draw_line(img, x0, y0, -x, -y, y, shear_dx, shear_dy, c); - } else { - scratch_draw_pixel(img, x0, y0, x, y, shear_dx, shear_dy, -thickness0, thickness1, c); - scratch_draw_pixel(img, x0, y0, -x, y, shear_dx, shear_dy, -thickness0, thickness1, c); - scratch_draw_pixel(img, x0, y0, x, -y, shear_dx, shear_dy, -thickness0, thickness1, c); - scratch_draw_pixel(img, x0, y0, -x, -y, shear_dx, shear_dy, -thickness0, thickness1, c); - } - - if (sigma >= 0) { - sigma += four_a_squared * (1 - y); - y -= 1; - } - - sigma += b_squared * ((4 * x) + 6); - x += 1; - } - - x = width; - y = 0; - sigma = (2 * a_squared) + (b_squared * (1 - (2 * width))); - - while ((a_squared * y) <= (b_squared * x)) { - if (filled) { - scratch_draw_line(img, x0, y0, x, -y, y, shear_dx, shear_dy, c); - scratch_draw_line(img, x0, y0, -x, -y, y, shear_dx, shear_dy, c); - } else { - scratch_draw_pixel(img, x0, y0, x, y, shear_dx, shear_dy, -thickness0, thickness1, c); - scratch_draw_pixel(img, x0, y0, -x, y, shear_dx, shear_dy, -thickness0, thickness1, c); - scratch_draw_pixel(img, x0, y0, x, -y, shear_dx, shear_dy, -thickness0, thickness1, c); - scratch_draw_pixel(img, x0, y0, -x, -y, shear_dx, shear_dy, -thickness0, thickness1, c); - } - - if (sigma >= 0) { - sigma += four_b_squared * (1 - x); - x -= 1; - } - - sigma += a_squared * ((4 * y) + 6); - y += 1; - } - } -} - -// https://scratch.mit.edu/projects/50039326/ -static void scratch_draw_rotated_ellipse(image_t *img, - int x, - int y, - int x_axis, - int y_axis, - int rotation, - bool filled, - int c, - int thickness) { - if ((x_axis > 0) && (y_axis > 0)) { - if ((x_axis == y_axis) || (rotation == 0)) { - scratch_draw_sheared_ellipse(img, x, y, x_axis / 2, y_axis / 2, filled, 1, 0, c, thickness); - } else if (rotation == 90) { - scratch_draw_sheared_ellipse(img, x, y, y_axis / 2, x_axis / 2, filled, 1, 0, c, thickness); - } else { - - // Avoid rotations above 90. - if (rotation > 90) { - rotation -= 90; - int temp = x_axis; - x_axis = y_axis; - y_axis = temp; - } - - // Avoid rotations above 45. - if (rotation > 45) { - rotation -= 90; - int temp = x_axis; - x_axis = y_axis; - y_axis = temp; - } - - float theta = fast_atanf(IM_DIV(y_axis, x_axis) * (-tanf(IM_DEG2RAD(rotation)))); - float shear_dx = (x_axis * cosf(theta) * cosf(IM_DEG2RAD(rotation))) - - (y_axis * sinf(theta) * sinf(IM_DEG2RAD(rotation))); - float shear_dy = (x_axis * cosf(theta) * sinf(IM_DEG2RAD(rotation))) + - (y_axis * sinf(theta) * cosf(IM_DEG2RAD(rotation))); - float shear_x_axis = fast_fabsf(shear_dx); - float shear_y_axis = IM_DIV((y_axis * x_axis), shear_x_axis); - scratch_draw_sheared_ellipse(img, - x, - y, - fast_floorf(shear_x_axis / 2), - fast_floorf(shear_y_axis / 2), - filled, - shear_dx, - shear_dy, - c, - thickness); - } - } -} - -void imlib_draw_ellipse(image_t *img, int cx, int cy, int rx, int ry, int rotation, int c, int thickness, bool fill) { - int r = rotation % 180; - if (r < 0) { - r += 180; - } - - scratch_draw_rotated_ellipse(img, cx, cy, rx * 2, ry * 2, r, fill, c, thickness); -} - -// char rotation == 0, 90, 180, 360, etc. -// string rotation == 0, 90, 180, 360, etc. -void imlib_draw_string(image_t *img, - int x_off, - int y_off, - const char *str, - int c, - float scale, - int x_spacing, - int y_spacing, - bool mono_space, - int char_rotation, - bool char_hmirror, - bool char_vflip, - int string_rotation, - bool string_hmirror, - bool string_vflip) { - char_rotation %= 360; - if (char_rotation < 0) { - char_rotation += 360; - } - char_rotation = (char_rotation / 90) * 90; - - string_rotation %= 360; - if (string_rotation < 0) { - string_rotation += 360; - } - string_rotation = (string_rotation / 90) * 90; - - bool char_swap_w_h = (char_rotation == 90) || (char_rotation == 270); - bool char_upsidedown = (char_rotation == 180) || (char_rotation == 270); - - if (string_hmirror) { - x_off -= fast_floorf(font[0].w * scale) - 1; - } - if (string_vflip) { - y_off -= fast_floorf(font[0].h * scale) - 1; - } - - int org_x_off = x_off; - int org_y_off = y_off; - const int anchor = x_off; - - for (char ch, last = '\0'; (ch = *str); str++, last = ch) { - - if ((last == '\r') && (ch == '\n')) { - // handle "\r\n" strings - continue; - } - - if ((ch == '\n') || (ch == '\r')) { - // handle '\n' or '\r' strings - x_off = anchor; - y_off += (string_vflip ? -1 : +1) * (fast_floorf((char_swap_w_h ? font[0].w : font[0].h) * scale) + y_spacing); // newline height == space height - continue; - } - - if ((ch < ' ') || (ch > '~')) { - // handle unknown characters - continue; - } - - const glyph_t *g = &font[ch - ' ']; - - if (!mono_space) { - // Find the first pixel set and offset to that. - bool exit = false; - - if (!char_swap_w_h) { - for (int x = 0, xx = g->w; x < xx; x++) { - for (int y = 0, yy = g->h; y < yy; y++) { - if (g->data[(char_upsidedown ^ char_vflip) ? (g->h - 1 - y) : y] & - (1 << ((char_upsidedown ^ char_hmirror ^ string_hmirror) ? x : (g->w - 1 - x)))) { - x_off += (string_hmirror ? +1 : -1) * fast_floorf(x * scale); - exit = true; - break; - } - } - - if (exit) { - break; - } - } - } else { - for (int y = g->h - 1; y >= 0; y--) { - for (int x = 0, xx = g->w; x < xx; x++) { - if (g->data[(char_upsidedown ^ char_vflip) ? (g->h - 1 - y) : y] & - (1 << ((char_upsidedown ^ char_hmirror ^ string_hmirror) ? x : (g->w - 1 - x)))) { - x_off += (string_hmirror ? +1 : -1) * fast_floorf((g->h - 1 - y) * scale); - exit = true; - break; - } - } - - if (exit) { - break; - } - } - } - } - - for (int y = 0, yy = fast_floorf(g->h * scale); y < yy; y++) { - for (int x = 0, xx = fast_floorf(g->w * scale); x < xx; x++) { - if (g->data[fast_floorf(y / scale)] & (1 << (g->w - 1 - fast_floorf(x / scale)))) { - int16_t x_tmp = x_off + (char_hmirror ? (xx - x - 1) : x), y_tmp = y_off + (char_vflip ? (yy - y - 1) : y); - point_rotate(x_tmp, y_tmp, IM_DEG2RAD(char_rotation), x_off + (xx / 2), y_off + (yy / 2), &x_tmp, &y_tmp); - point_rotate(x_tmp, y_tmp, IM_DEG2RAD(string_rotation), org_x_off, org_y_off, &x_tmp, &y_tmp); - imlib_set_pixel(img, x_tmp, y_tmp, c); - } - } - } - - if (mono_space) { - x_off += (string_hmirror ? -1 : +1) * (fast_floorf((char_swap_w_h ? g->h : g->w) * scale) + x_spacing); - } else { - // Find the last pixel set and offset to that. - bool exit = false; - - if (!char_swap_w_h) { - for (int x = g->w - 1; x >= 0; x--) { - for (int y = g->h - 1; y >= 0; y--) { - if (g->data[(char_upsidedown ^ char_vflip) ? (g->h - 1 - y) : y] & - (1 << ((char_upsidedown ^ char_hmirror ^ string_hmirror) ? x : (g->w - 1 - x)))) { - x_off += (string_hmirror ? -1 : +1) * (fast_floorf((x + 2) * scale) + x_spacing); - exit = true; - break; - } - } - - if (exit) { - break; - } - } - } else { - for (int y = 0, yy = g->h; y < yy; y++) { - for (int x = g->w - 1; x >= 0; x--) { - if (g->data[(char_upsidedown ^ char_vflip) ? (g->h - 1 - y) : y] & - (1 << ((char_upsidedown ^ char_hmirror ^ string_hmirror) ? x : (g->w - 1 - x)))) { - x_off += (string_hmirror ? -1 : +1) * (fast_floorf(((g->h - 1 - y) + 2) * scale) + x_spacing); - exit = true; - break; - } - } - - if (exit) { - break; - } - } - } - - if (!exit) { - x_off += (string_hmirror ? -1 : +1) * fast_floorf(scale * 3); // space char - } - } - } -} - - - +/* + * SPDX-License-Identifier: MIT + * + * Copyright (C) 2013-2024 OpenMV, LLC. + * Copyright (c) 2024 M5Stack Technology CO LTD + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * Basic drawing functions. + */ +#include "imlib.h" +#include +#include +#include +#include "utils.h" +#include "font.h" +#include "fmath.h" + + + +void *imlib_compute_row_ptr(const image_t *img, int y) { + switch (img->pixfmt) { + case OMV_PIXFORMAT_BINARY: { + return IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(img, y); + } + case OMV_PIXFORMAT_GRAYSCALE: { + return IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(img, y); + } + case OMV_PIXFORMAT_RGB565: { + return IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(img, y); + } + default: { + // This shouldn't happen, at least we return a valid memory block + return img->data; + } + } +} + +inline int imlib_get_pixel_fast(image_t *img, const void *row_ptr, int x) { + switch (img->pixfmt) { + case OMV_PIXFORMAT_BINARY: { + return IMAGE_GET_BINARY_PIXEL_FAST((uint32_t *)row_ptr, x); + } + case OMV_PIXFORMAT_GRAYSCALE: { + return IMAGE_GET_GRAYSCALE_PIXEL_FAST((uint8_t *)row_ptr, x); + } + case OMV_PIXFORMAT_RGB565: { + return IMAGE_GET_RGB565_PIXEL_FAST((uint16_t *)row_ptr, x); + } + default: { + return -1; + } + } +} + + +// Set pixel (handles boundary check and image type check). +void imlib_set_pixel(image_t *img, int x, int y, int p) { + if ((0 <= x) && (x < img->w) && (0 <= y) && (y < img->h)) { + switch (img->pixfmt) { + case OMV_PIXFORMAT_BINARY: { + IMAGE_PUT_BINARY_PIXEL(img, x, y, p); + break; + } + case OMV_PIXFORMAT_GRAYSCALE: { + IMAGE_PUT_GRAYSCALE_PIXEL(img, x, y, p); + break; + } + case OMV_PIXFORMAT_RGB565: { + IMAGE_PUT_RGB565_PIXEL(img, x, y, p); + break; + } + default: { + break; + } + } + } +} + +// https://stackoverflow.com/questions/1201200/fast-algorithm-for-drawing-filled-circles +static void point_fill(image_t *img, int cx, int cy, int r0, int r1, int c) { + for (int y = r0; y <= r1; y++) { + for (int x = r0; x <= r1; x++) { + if (((x * x) + (y * y)) <= (r0 * r0)) { + imlib_set_pixel(img, cx + x, cy + y, c); + } + } + } +} + +static void imlib_set_pixel_aa(image_t *img, int x, int y, int err, int c) { + if (!((0 <= x) && (x < img->w) && (0 <= y) && (y < img->h))) { + return; + } + + switch (img->pixfmt) { + case OMV_PIXFORMAT_BINARY: { + uint32_t *ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(img, y); + int old_c = IMAGE_GET_BINARY_PIXEL_FAST(ptr, x) * 255; + int new_c = (((old_c * err) + ((c ? 255 : 0) * (256 - err))) >> 8) > 127; + IMAGE_PUT_BINARY_PIXEL_FAST(ptr, x, new_c); + break; + } + case OMV_PIXFORMAT_GRAYSCALE: { + uint8_t *ptr = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(img, y); + int old_c = IMAGE_GET_GRAYSCALE_PIXEL_FAST(ptr, x); + int new_c = ((old_c * err) + ((c & 0xff) * (256 - err))) >> 8; + IMAGE_PUT_GRAYSCALE_PIXEL_FAST(ptr, x, new_c); + break; + } + case OMV_PIXFORMAT_RGB565: { + uint16_t *ptr = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(img, y); + int old_c = IMAGE_GET_RGB565_PIXEL_FAST(ptr, x); + int old_c_r5 = COLOR_RGB565_TO_R5(old_c); + int old_c_g6 = COLOR_RGB565_TO_G6(old_c); + int old_c_b5 = COLOR_RGB565_TO_B5(old_c); + int c_r5 = COLOR_RGB565_TO_R5(c); + int c_g6 = COLOR_RGB565_TO_G6(c); + int c_b5 = COLOR_RGB565_TO_B5(c); + int new_c_r5 = ((old_c_r5 * err) + (c_r5 * (256 - err))) >> 8; + int new_c_g6 = ((old_c_g6 * err) + (c_g6 * (256 - err))) >> 8; + int new_c_b5 = ((old_c_b5 * err) + (c_b5 * (256 - err))) >> 8; + int new_c = COLOR_R5_G6_B5_TO_RGB565(new_c_r5, new_c_g6, new_c_b5); + IMAGE_PUT_RGB565_PIXEL_FAST(ptr, x, new_c); + break; + } + default: { + break; + } + } +} + +// https://gist.github.com/randvoorhies/807ce6e20840ab5314eb7c547899de68#file-bresenham-js-L381 +static void imlib_draw_thin_line(image_t *img, int x0, int y0, int x1, int y1, int c) { + const int dx = abs(x1 - x0); + const int sx = x0 < x1 ? 1 : -1; + const int dy = abs(y1 - y0); + const int sy = y0 < y1 ? 1 : -1; + int err = dx - dy; + int e2, x2; // error value e_xy + int ed = dx + dy == 0 ? 1 : fast_floorf(fast_sqrtf(dx * dx + dy * dy)); + + for (;;) { + // pixel loop + imlib_set_pixel_aa(img, x0, y0, 256 * abs(err - dx + dy) / ed, c); + e2 = err; + x2 = x0; + if (2 * e2 >= -dx) { + // x step + if (x0 == x1) { + break; + } + if (e2 + dy < ed) { + imlib_set_pixel_aa(img, x0, y0 + sy, 256 * (e2 + dy) / ed, c); + } + err -= dy; + x0 += sx; + } + if (2 * e2 <= dy) { + // y step + if (y0 == y1) { + break; + } + if (dx - e2 < ed) { + imlib_set_pixel_aa(img, x2 + sx, y0, 256 * (dx - e2) / ed, c); + } + err += dx; + y0 += sy; + } + } +} + +// https://rosettacode.org/wiki/Bitmap/Bresenham%27s_line_algorithm#C +void imlib_draw_line(image_t *img, int x0, int y0, int x1, int y1, int color, int thickness) { + if (thickness > 0) { + int thickness0 = (thickness - 0) / 2; + int thickness1 = (thickness - 1) / 2; + int dx = abs(x1 - x0), sx = (x0 < x1) ? 1 : -1; + int dy = abs(y1 - y0), sy = (y0 < y1) ? 1 : -1; + int err = ((dx > dy) ? dx : -dy) / 2; + + for (;;) { + point_fill(img, x0, y0, -thickness0, thickness1, color); + if ((x0 == x1) && (y0 == y1)) { + break; + } + int e2 = err; + if (e2 > -dx) { + err -= dy; + x0 += sx; + } + if (e2 < dy) { + err += dx; + y0 += sy; + } + } + } +} + +static void xLine(image_t *img, int x1, int x2, int y, int c) { + while (x1 <= x2) { + imlib_set_pixel(img, x1++, y, c); + } +} + +static void yLine(image_t *img, int x, int y1, int y2, int c) { + while (y1 <= y2) { + imlib_set_pixel(img, x, y1++, c); + } +} + +void imlib_draw_rectangle(image_t *img, int rx, int ry, int rw, int rh, int c, int thickness, bool fill) { + if (fill) { + + for (int y = ry, yy = ry + rh; y < yy; y++) { + for (int x = rx, xx = rx + rw; x < xx; x++) { + imlib_set_pixel(img, x, y, c); + } + } + + } else if (thickness > 0) { + int thickness0 = (thickness - 0) / 2; + int thickness1 = (thickness - 1) / 2; + + for (int i = rx - thickness0, j = rx + rw + thickness1, k = ry + rh - 1; i < j; i++) { + yLine(img, i, ry - thickness0, ry + thickness1, c); + yLine(img, i, k - thickness0, k + thickness1, c); + } + + for (int i = ry - thickness0, j = ry + rh + thickness1, k = rx + rw - 1; i < j; i++) { + xLine(img, rx - thickness0, rx + thickness1, i, c); + xLine(img, k - thickness0, k + thickness1, i, c); + } + } +} + +// https://stackoverflow.com/questions/27755514/circle-with-thickness-drawing-algorithm +void imlib_draw_circle(image_t *img, int cx, int cy, int radius, int color, int thickness, bool fill) { + if (fill) { + point_fill(img, cx, cy, -radius, radius, color); + } else if (thickness > 0) { + int thickness0 = (thickness - 0) / 2; + int thickness1 = (thickness - 1) / 2; + + int xo = radius + thickness0; + int xi = IM_MAX(radius - thickness1, 0); + int xi_tmp = xi; + int y = 0; + int erro = 1 - xo; + int erri = 1 - xi; + + while (xo >= y) { + xLine(img, cx + xi, cx + xo, cy + y, color); + yLine(img, cx + y, cy + xi, cy + xo, color); + xLine(img, cx - xo, cx - xi, cy + y, color); + yLine(img, cx - y, cy + xi, cy + xo, color); + xLine(img, cx - xo, cx - xi, cy - y, color); + yLine(img, cx - y, cy - xo, cy - xi, color); + xLine(img, cx + xi, cx + xo, cy - y, color); + yLine(img, cx + y, cy - xo, cy - xi, color); + + y++; + + if (erro < 0) { + erro += 2 * y + 1; + } else { + xo--; + erro += 2 * (y - xo + 1); + } + + if (y > xi_tmp) { + xi = y; + } else { + if (erri < 0) { + erri += 2 * y + 1; + } else { + xi--; + erri += 2 * (y - xi + 1); + } + } + } + } +} + +// https://scratch.mit.edu/projects/50039326/ +static void scratch_draw_pixel(image_t *img, + int x0, + int y0, + int dx, + int dy, + float shear_dx, + float shear_dy, + int r0, + int r1, + int c) { + point_fill(img, x0 + dx, y0 + dy + fast_floorf((dx * shear_dy) / shear_dx), r0, r1, c); +} + +// https://scratch.mit.edu/projects/50039326/ +static void scratch_draw_line(image_t *img, int x0, int y0, int dx, int dy0, int dy1, float shear_dx, float shear_dy, int c) { + int y = y0 + fast_floorf((dx * shear_dy) / shear_dx); + yLine(img, x0 + dx, y + dy0, y + dy1, c); +} + +// https://scratch.mit.edu/projects/50039326/ +static void scratch_draw_sheared_ellipse(image_t *img, + int x0, + int y0, + int width, + int height, + bool filled, + float shear_dx, + float shear_dy, + int c, + int thickness) { + int thickness0 = (thickness - 0) / 2; + int thickness1 = (thickness - 1) / 2; + if (((thickness > 0) || filled) && (shear_dx != 0)) { + int a_squared = width * width; + int four_a_squared = a_squared * 4; + int b_squared = height * height; + int four_b_squared = b_squared * 4; + + int x = 0; + int y = height; + int sigma = (2 * b_squared) + (a_squared * (1 - (2 * height))); + + while ((b_squared * x) <= (a_squared * y)) { + if (filled) { + scratch_draw_line(img, x0, y0, x, -y, y, shear_dx, shear_dy, c); + scratch_draw_line(img, x0, y0, -x, -y, y, shear_dx, shear_dy, c); + } else { + scratch_draw_pixel(img, x0, y0, x, y, shear_dx, shear_dy, -thickness0, thickness1, c); + scratch_draw_pixel(img, x0, y0, -x, y, shear_dx, shear_dy, -thickness0, thickness1, c); + scratch_draw_pixel(img, x0, y0, x, -y, shear_dx, shear_dy, -thickness0, thickness1, c); + scratch_draw_pixel(img, x0, y0, -x, -y, shear_dx, shear_dy, -thickness0, thickness1, c); + } + + if (sigma >= 0) { + sigma += four_a_squared * (1 - y); + y -= 1; + } + + sigma += b_squared * ((4 * x) + 6); + x += 1; + } + + x = width; + y = 0; + sigma = (2 * a_squared) + (b_squared * (1 - (2 * width))); + + while ((a_squared * y) <= (b_squared * x)) { + if (filled) { + scratch_draw_line(img, x0, y0, x, -y, y, shear_dx, shear_dy, c); + scratch_draw_line(img, x0, y0, -x, -y, y, shear_dx, shear_dy, c); + } else { + scratch_draw_pixel(img, x0, y0, x, y, shear_dx, shear_dy, -thickness0, thickness1, c); + scratch_draw_pixel(img, x0, y0, -x, y, shear_dx, shear_dy, -thickness0, thickness1, c); + scratch_draw_pixel(img, x0, y0, x, -y, shear_dx, shear_dy, -thickness0, thickness1, c); + scratch_draw_pixel(img, x0, y0, -x, -y, shear_dx, shear_dy, -thickness0, thickness1, c); + } + + if (sigma >= 0) { + sigma += four_b_squared * (1 - x); + x -= 1; + } + + sigma += a_squared * ((4 * y) + 6); + y += 1; + } + } +} + +// https://scratch.mit.edu/projects/50039326/ +static void scratch_draw_rotated_ellipse(image_t *img, + int x, + int y, + int x_axis, + int y_axis, + int rotation, + bool filled, + int c, + int thickness) { + if ((x_axis > 0) && (y_axis > 0)) { + if ((x_axis == y_axis) || (rotation == 0)) { + scratch_draw_sheared_ellipse(img, x, y, x_axis / 2, y_axis / 2, filled, 1, 0, c, thickness); + } else if (rotation == 90) { + scratch_draw_sheared_ellipse(img, x, y, y_axis / 2, x_axis / 2, filled, 1, 0, c, thickness); + } else { + + // Avoid rotations above 90. + if (rotation > 90) { + rotation -= 90; + int temp = x_axis; + x_axis = y_axis; + y_axis = temp; + } + + // Avoid rotations above 45. + if (rotation > 45) { + rotation -= 90; + int temp = x_axis; + x_axis = y_axis; + y_axis = temp; + } + + float theta = fast_atanf(IM_DIV(y_axis, x_axis) * (-tanf(IM_DEG2RAD(rotation)))); + float shear_dx = (x_axis * cosf(theta) * cosf(IM_DEG2RAD(rotation))) - + (y_axis * sinf(theta) * sinf(IM_DEG2RAD(rotation))); + float shear_dy = (x_axis * cosf(theta) * sinf(IM_DEG2RAD(rotation))) + + (y_axis * sinf(theta) * cosf(IM_DEG2RAD(rotation))); + float shear_x_axis = fast_fabsf(shear_dx); + float shear_y_axis = IM_DIV((y_axis * x_axis), shear_x_axis); + scratch_draw_sheared_ellipse(img, + x, + y, + fast_floorf(shear_x_axis / 2), + fast_floorf(shear_y_axis / 2), + filled, + shear_dx, + shear_dy, + c, + thickness); + } + } +} + +void imlib_draw_ellipse(image_t *img, int cx, int cy, int rx, int ry, int rotation, int c, int thickness, bool fill) { + int r = rotation % 180; + if (r < 0) { + r += 180; + } + + scratch_draw_rotated_ellipse(img, cx, cy, rx * 2, ry * 2, r, fill, c, thickness); +} + +// char rotation == 0, 90, 180, 360, etc. +// string rotation == 0, 90, 180, 360, etc. +void imlib_draw_string(image_t *img, + int x_off, + int y_off, + const char *str, + int c, + float scale, + int x_spacing, + int y_spacing, + bool mono_space, + int char_rotation, + bool char_hmirror, + bool char_vflip, + int string_rotation, + bool string_hmirror, + bool string_vflip) { + char_rotation %= 360; + if (char_rotation < 0) { + char_rotation += 360; + } + char_rotation = (char_rotation / 90) * 90; + + string_rotation %= 360; + if (string_rotation < 0) { + string_rotation += 360; + } + string_rotation = (string_rotation / 90) * 90; + + bool char_swap_w_h = (char_rotation == 90) || (char_rotation == 270); + bool char_upsidedown = (char_rotation == 180) || (char_rotation == 270); + + if (string_hmirror) { + x_off -= fast_floorf(font[0].w * scale) - 1; + } + if (string_vflip) { + y_off -= fast_floorf(font[0].h * scale) - 1; + } + + int org_x_off = x_off; + int org_y_off = y_off; + const int anchor = x_off; + + for (char ch, last = '\0'; (ch = *str); str++, last = ch) { + + if ((last == '\r') && (ch == '\n')) { + // handle "\r\n" strings + continue; + } + + if ((ch == '\n') || (ch == '\r')) { + // handle '\n' or '\r' strings + x_off = anchor; + y_off += (string_vflip ? -1 : +1) * (fast_floorf((char_swap_w_h ? font[0].w : font[0].h) * scale) + y_spacing); // newline height == space height + continue; + } + + if ((ch < ' ') || (ch > '~')) { + // handle unknown characters + continue; + } + + const glyph_t *g = &font[ch - ' ']; + + if (!mono_space) { + // Find the first pixel set and offset to that. + bool exit = false; + + if (!char_swap_w_h) { + for (int x = 0, xx = g->w; x < xx; x++) { + for (int y = 0, yy = g->h; y < yy; y++) { + if (g->data[(char_upsidedown ^ char_vflip) ? (g->h - 1 - y) : y] & + (1 << ((char_upsidedown ^ char_hmirror ^ string_hmirror) ? x : (g->w - 1 - x)))) { + x_off += (string_hmirror ? +1 : -1) * fast_floorf(x * scale); + exit = true; + break; + } + } + + if (exit) { + break; + } + } + } else { + for (int y = g->h - 1; y >= 0; y--) { + for (int x = 0, xx = g->w; x < xx; x++) { + if (g->data[(char_upsidedown ^ char_vflip) ? (g->h - 1 - y) : y] & + (1 << ((char_upsidedown ^ char_hmirror ^ string_hmirror) ? x : (g->w - 1 - x)))) { + x_off += (string_hmirror ? +1 : -1) * fast_floorf((g->h - 1 - y) * scale); + exit = true; + break; + } + } + + if (exit) { + break; + } + } + } + } + + for (int y = 0, yy = fast_floorf(g->h * scale); y < yy; y++) { + for (int x = 0, xx = fast_floorf(g->w * scale); x < xx; x++) { + if (g->data[fast_floorf(y / scale)] & (1 << (g->w - 1 - fast_floorf(x / scale)))) { + int16_t x_tmp = x_off + (char_hmirror ? (xx - x - 1) : x), y_tmp = y_off + (char_vflip ? (yy - y - 1) : y); + point_rotate(x_tmp, y_tmp, IM_DEG2RAD(char_rotation), x_off + (xx / 2), y_off + (yy / 2), &x_tmp, &y_tmp); + point_rotate(x_tmp, y_tmp, IM_DEG2RAD(string_rotation), org_x_off, org_y_off, &x_tmp, &y_tmp); + imlib_set_pixel(img, x_tmp, y_tmp, c); + } + } + } + + if (mono_space) { + x_off += (string_hmirror ? -1 : +1) * (fast_floorf((char_swap_w_h ? g->h : g->w) * scale) + x_spacing); + } else { + // Find the last pixel set and offset to that. + bool exit = false; + + if (!char_swap_w_h) { + for (int x = g->w - 1; x >= 0; x--) { + for (int y = g->h - 1; y >= 0; y--) { + if (g->data[(char_upsidedown ^ char_vflip) ? (g->h - 1 - y) : y] & + (1 << ((char_upsidedown ^ char_hmirror ^ string_hmirror) ? x : (g->w - 1 - x)))) { + x_off += (string_hmirror ? -1 : +1) * (fast_floorf((x + 2) * scale) + x_spacing); + exit = true; + break; + } + } + + if (exit) { + break; + } + } + } else { + for (int y = 0, yy = g->h; y < yy; y++) { + for (int x = g->w - 1; x >= 0; x--) { + if (g->data[(char_upsidedown ^ char_vflip) ? (g->h - 1 - y) : y] & + (1 << ((char_upsidedown ^ char_hmirror ^ string_hmirror) ? x : (g->w - 1 - x)))) { + x_off += (string_hmirror ? -1 : +1) * (fast_floorf(((g->h - 1 - y) + 2) * scale) + x_spacing); + exit = true; + break; + } + } + + if (exit) { + break; + } + } + } + + if (!exit) { + x_off += (string_hmirror ? -1 : +1) * fast_floorf(scale * 3); // space char + } + } + } +} diff --git a/m5stack/cmodules/omv/imlib/fmath.c b/m5stack/cmodules/omv/imlib/fmath.c old mode 100755 new mode 100644 index e6612ea6..a7bbb118 --- a/m5stack/cmodules/omv/imlib/fmath.c +++ b/m5stack/cmodules/omv/imlib/fmath.c @@ -1,162 +1,164 @@ -#include "fmath.h" -#include - -const float __atanf_lut[4] = { - -0.0443265554792128f, //p7 - -0.3258083974640975f, //p3 - +0.1555786518463281f, //p5 - +0.9997878412794807f //p1 -}; - -typedef union { - uint32_t l; - struct { - uint32_t m : 20; - uint32_t e : 11; - uint32_t s : 1; - }; -}exp_t; - -float fast_expf(float x) { - exp_t e = { .l = 0 }; - e.l = (uint32_t)(1512775 * x + 1072632447); - e.e = (e.e - 1023 + 127) & 0xFF; - - uint32_t packed = (e.s << 31) | (e.e << 23) | (e.m << 3); - - float result; - memcpy(&result, &packed, sizeof(result)); - - return result; -} - -/* - * From Hackers Delight: - * This is a very approximate but very fast version of acbrt. It is just eight - * integer instructions (shift rights and adds), plus instructions to load the constant. - * 1/3 is approximated as 1/4 + 1/16 + 1/64 + 1/256 + ... + 1/65536. - * The constant 0x2a511cd0 balances the relative error at +-0.0321. - */ -float fast_cbrtf(float x) { - union { - int ix; float x; - } - v; - v.x = x; // x can be viewed as int. - v.ix = v.ix / 4 + v.ix / 16; // Approximate divide by 3. - v.ix = v.ix + v.ix / 16; - v.ix = v.ix + v.ix / 256; - v.ix = 0x2a511cd0 + v.ix; // Initial guess. - return v.x; -} - -inline float fast_atanf(float xx) { - float x, y, z; - int sign; - - x = xx; - - /* make argument positive and save the sign */ - if (xx < 0.0f) { - sign = -1; - x = -xx; - } else { - sign = 1; - x = xx; - } - /* range reduction */ - if (x > 2.414213562373095f) { - /* tan 3pi/8 */ - y = M_PI_2; - x = -(1.0f / x); - } else if (x > 0.4142135623730950f) { - /* tan pi/8 */ - y = M_PI_4; - x = (x - 1.0f) / (x + 1.0f); - } else { - y = 0.0f; - } - - z = x * x; - y += - (((8.05374449538e-2f * z - - 1.38776856032E-1f) * z - + 1.99777106478E-1f) * z - - 3.33329491539E-1f) * z * x + x; - - if (sign < 0) { - y = -y; - } - - return(y); -} - -float fast_atan2f(float y, float x) { - if (x > 0 && y >= 0) { - return fast_atanf(y / x); - } - - if (x < 0 && y >= 0) { - return M_PI - fast_atanf(-y / x); - } - - if (x < 0 && y < 0) { - return M_PI + fast_atanf(y / x); - } - - if (x > 0 && y < 0) { - return 2 * M_PI - fast_atanf(-y / x); - } - - return (y == 0) ? 0 : ((y > 0) ? M_PI : -M_PI); -} - -float fast_log2(float x) { - union { - float f; uint32_t i; - } - vx = { x }; - union { - uint32_t i; float f; - } - mx = { (vx.i & 0x007FFFFF) | 0x3f000000 }; - float y = vx.i; - y *= 1.1920928955078125e-7f; - - return y - 124.22551499f - 1.498030302f * mx.f - - 1.72587999f / (0.3520887068f + mx.f); -} - -float fast_log(float x) { - return 0.69314718f * fast_log2(x); -} - -float fast_powf(float a, float b) { - union { - float d; int x; - } - u = { a }; - u.x = (int) ((b * (u.x - 1064866805)) + 1064866805); - return u.d; -} - -void fast_get_min_max(float *data, size_t data_len, float *p_min, float *p_max) { - float min = FLT_MAX, max = -FLT_MAX; - - for (size_t i = 0; i < data_len; i++) { - float temp = data[i]; - - if (temp < min) { - min = temp; - } - - if (temp > max) { - max = temp; - } - } - - *p_min = min; - *p_max = max; -} - - +#include "fmath.h" +#include + +const float __atanf_lut[4] = { + -0.0443265554792128f, // p7 + -0.3258083974640975f, // p3 + +0.1555786518463281f, // p5 + +0.9997878412794807f // p1 +}; + +typedef union { + uint32_t l; + struct { + uint32_t m : 20; + uint32_t e : 11; + uint32_t s : 1; + }; +}exp_t; + +float fast_expf(float x) { + exp_t e = { .l = 0 }; + e.l = (uint32_t)(1512775 * x + 1072632447); + e.e = (e.e - 1023 + 127) & 0xFF; + + uint32_t packed = (e.s << 31) | (e.e << 23) | (e.m << 3); + + float result; + memcpy(&result, &packed, sizeof(result)); + + return result; +} + +/* + * From Hackers Delight: + * This is a very approximate but very fast version of acbrt. It is just eight + * integer instructions (shift rights and adds), plus instructions to load the constant. + * 1/3 is approximated as 1/4 + 1/16 + 1/64 + 1/256 + ... + 1/65536. + * The constant 0x2a511cd0 balances the relative error at +-0.0321. + */ +float fast_cbrtf(float x) { + union { + int ix; + float x; + } + v; + v.x = x; // x can be viewed as int. + v.ix = v.ix / 4 + v.ix / 16; // Approximate divide by 3. + v.ix = v.ix + v.ix / 16; + v.ix = v.ix + v.ix / 256; + v.ix = 0x2a511cd0 + v.ix; // Initial guess. + return v.x; +} + +inline float fast_atanf(float xx) { + float x, y, z; + int sign; + + x = xx; + + /* make argument positive and save the sign */ + if (xx < 0.0f) { + sign = -1; + x = -xx; + } else { + sign = 1; + x = xx; + } + /* range reduction */ + if (x > 2.414213562373095f) { + /* tan 3pi/8 */ + y = M_PI_2; + x = -(1.0f / x); + } else if (x > 0.4142135623730950f) { + /* tan pi/8 */ + y = M_PI_4; + x = (x - 1.0f) / (x + 1.0f); + } else { + y = 0.0f; + } + + z = x * x; + y += + (((8.05374449538e-2f * z + - 1.38776856032E-1f) * z + + 1.99777106478E-1f) * z + - 3.33329491539E-1f) * z * x + x; + + if (sign < 0) { + y = -y; + } + + return y; +} + +float fast_atan2f(float y, float x) { + if (x > 0 && y >= 0) { + return fast_atanf(y / x); + } + + if (x < 0 && y >= 0) { + return M_PI - fast_atanf(-y / x); + } + + if (x < 0 && y < 0) { + return M_PI + fast_atanf(y / x); + } + + if (x > 0 && y < 0) { + return 2 * M_PI - fast_atanf(-y / x); + } + + return (y == 0) ? 0 : ((y > 0) ? M_PI : -M_PI); +} + +float fast_log2(float x) { + union { + float f; + uint32_t i; + } + vx = { x }; + union { + uint32_t i; + float f; + } + mx = { (vx.i & 0x007FFFFF) | 0x3f000000 }; + float y = vx.i; + y *= 1.1920928955078125e-7f; + + return y - 124.22551499f - 1.498030302f * mx.f + - 1.72587999f / (0.3520887068f + mx.f); +} + +float fast_log(float x) { + return 0.69314718f * fast_log2(x); +} + +float fast_powf(float a, float b) { + union { + float d; + int x; + } + u = { a }; + u.x = (int)((b * (u.x - 1064866805)) + 1064866805); + return u.d; +} + +void fast_get_min_max(float *data, size_t data_len, float *p_min, float *p_max) { + float min = FLT_MAX, max = -FLT_MAX; + + for (size_t i = 0; i < data_len; i++) { + float temp = data[i]; + + if (temp < min) { + min = temp; + } + + if (temp > max) { + max = temp; + } + } + + *p_min = min; + *p_max = max; +} diff --git a/m5stack/cmodules/omv/imlib/fmath.h b/m5stack/cmodules/omv/imlib/fmath.h old mode 100755 new mode 100644 index 4aafb0ad..de356468 --- a/m5stack/cmodules/omv/imlib/fmath.h +++ b/m5stack/cmodules/omv/imlib/fmath.h @@ -1,56 +1,56 @@ -/** - * Fast approximate math functions. - */ -#ifndef __FMATH_H__ -#define __FMATH_H__ - - -#include -#include -#include - -#include - - -float fast_atanf(float x); -float fast_atan2f(float y, float x); -float fast_expf(float x); -float fast_cbrtf(float d); -float fast_log(float x); -float fast_log2(float x); -float fast_powf(float a, float b); -void fast_get_min_max(float *data, size_t data_len, float *p_min, float *p_max); - -// 快速平方根函数 -static inline float fast_sqrtf(float x) { - return sqrtf(x); -} - -// 快速 floor 函数 -static inline int fast_floorf(float x) { - // int i = (int)x; - // return (x < 0 && x != i) ? i - 1 : i; - return floorf(x); -} - -// 快速 ceil 函数 -static inline int fast_ceilf(float x) { - // int i = (int)x; - // return (x > 0 && x != i) ? i + 1 : i; - return ceilf(x); -} - -// 快速 round 函数 -static inline int fast_roundf(float x) { - //return (int)(x + (x >= 0 ? 0.5f : -0.5f)); - return roundf(x); -} - -// 快速 fabs 函数 -static inline float fast_fabsf(float x) { - //return (x >= 0) ? x : -x; - return fabsf(x); -} - - -#endif // __FMATH_H__ +/** + * Fast approximate math functions. + */ +#ifndef __FMATH_H__ +#define __FMATH_H__ + + +#include +#include +#include + +#include + + +float fast_atanf(float x); +float fast_atan2f(float y, float x); +float fast_expf(float x); +float fast_cbrtf(float d); +float fast_log(float x); +float fast_log2(float x); +float fast_powf(float a, float b); +void fast_get_min_max(float *data, size_t data_len, float *p_min, float *p_max); + +// 快速平方根函数 +static inline float fast_sqrtf(float x) { + return sqrtf(x); +} + +// 快速 floor 函数 +static inline int fast_floorf(float x) { + // int i = (int)x; + // return (x < 0 && x != i) ? i - 1 : i; + return floorf(x); +} + +// 快速 ceil 函数 +static inline int fast_ceilf(float x) { + // int i = (int)x; + // return (x > 0 && x != i) ? i + 1 : i; + return ceilf(x); +} + +// 快速 round 函数 +static inline int fast_roundf(float x) { + // return (int)(x + (x >= 0 ? 0.5f : -0.5f)); + return roundf(x); +} + +// 快速 fabs 函数 +static inline float fast_fabsf(float x) { + // return (x >= 0) ? x : -x; + return fabsf(x); +} + + +#endif // __FMATH_H__ diff --git a/m5stack/cmodules/omv/imlib/font.c b/m5stack/cmodules/omv/imlib/font.c old mode 100755 new mode 100644 index c665706a..5c6aede5 --- a/m5stack/cmodules/omv/imlib/font.c +++ b/m5stack/cmodules/omv/imlib/font.c @@ -1,622 +1,617 @@ -/* - * SPDX-License-Identifier: MIT - * - * Copyright (C) 2013-2024 OpenMV, LLC. - * Copyright (c) 2024 M5Stack Technology CO LTD - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * Font data. - * - */ -#include "font.h" - - - -/** - * ASCII 8x16 - * 0x20~0x7E - */ -const glyph_t font[] = -{ - {8, 16, {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // - - - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}}, - - {8, 16, {0x00,0x00,0x18,0x3C,0x3C,0x3C,0x18,0x18, // -!- - 0x18,0x00,0x18,0x18,0x00,0x00,0x00,0x00}}, - - {8, 16, {0x00,0x66,0x66,0x66,0x24,0x00,0x00,0x00, // -"- - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}}, - - {8, 16, {0x00,0x00,0x00,0x6C,0x6C,0xFE,0x6C,0x6C, // -#- - 0x6C,0xFE,0x6C,0x6C,0x00,0x00,0x00,0x00}}, - - {8, 16, {0x18,0x18,0x7C,0xC6,0xC2,0xC0,0x7C,0x06, // -$- - 0x86,0xC6,0x7C,0x18,0x18,0x00,0x00,0x00}}, - - {8, 16, {0x00,0x00,0x00,0x00,0xC2,0xC6,0x0C,0x18, // -%- - 0x30,0x60,0xC6,0x86,0x00,0x00,0x00,0x00}}, - - {8, 16, {0x00,0x00,0x38,0x6C,0x6C,0x38,0x76,0xDC, // -&- - 0xCC,0xCC,0xCC,0x76,0x00,0x00,0x00,0x00}}, - - {8, 16, {0x00,0x30,0x30,0x30,0x60,0x00,0x00,0x00, // -'- - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}}, - - {8, 16, {0x00,0x00,0x0C,0x18,0x30,0x30,0x30,0x30, // -(- - 0x30,0x30,0x18,0x0C,0x00,0x00,0x00,0x00}}, - - {8, 16, {0x00,0x00,0x30,0x18,0x0C,0x0C,0x0C,0x0C, // -)- - 0x0C,0x0C,0x18,0x30,0x00,0x00,0x00,0x00}}, - - {8, 16, {0x00,0x00,0x00,0x00,0x00,0x66,0x3C,0xFF, // -*- - 0x3C,0x66,0x00,0x00,0x00,0x00,0x00,0x00}}, - - {8, 16, {0x00,0x00,0x00,0x00,0x00,0x18,0x18,0x7E, // -+- - 0x18,0x18,0x00,0x00,0x00,0x00,0x00,0x00}}, - - {8, 16, {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // -,- - 0x00,0x18,0x18,0x18,0x30,0x00,0x00,0x00}}, - - {8, 16, {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFE, // --- - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}}, - - {8, 16, {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // -.- - 0x00,0x00,0x18,0x18,0x00,0x00,0x00,0x00}}, - - {8, 16, {0x00,0x00,0x00,0x00,0x02,0x06,0x0C,0x18, // -/- - 0x30,0x60,0xC0,0x80,0x00,0x00,0x00,0x00}}, - - {8, 16, {0x00,0x00,0x7C,0xC6,0xC6,0xCE,0xD6,0xD6, // -0- - 0xE6,0xC6,0xC6,0x7C,0x00,0x00,0x00,0x00}}, - - {8, 16, {0x00,0x00,0x18,0x38,0x78,0x18,0x18,0x18, // -1- - 0x18,0x18,0x18,0x7E,0x00,0x00,0x00,0x00}}, - - {8, 16, {0x00,0x00,0x7C,0xC6,0x06,0x0C,0x18,0x30, // -2- - 0x60,0xC0,0xC6,0xFE,0x00,0x00,0x00,0x00}}, - - {8, 16, {0x00,0x00,0x7C,0xC6,0x06,0x06,0x3C,0x06, // -3- - 0x06,0x06,0xC6,0x7C,0x00,0x00,0x00,0x00}}, - - {8, 16, {0x00,0x00,0x0C,0x1C,0x3C,0x6C,0xCC,0xFE, // -4- - 0x0C,0x0C,0x0C,0x1E,0x00,0x00,0x00,0x00}}, - - {8, 16, {0x00,0x00,0xFE,0xC0,0xC0,0xC0,0xFC,0x0E, // -5- - 0x06,0x06,0xC6,0x7C,0x00,0x00,0x00,0x00}}, - - {8, 16, {0x00,0x00,0x38,0x60,0xC0,0xC0,0xFC,0xC6, // -6- - 0xC6,0xC6,0xC6,0x7C,0x00,0x00,0x00,0x00}}, - - {8, 16, {0x00,0x00,0xFE,0xC6,0x06,0x06,0x0C,0x18, // -7- - 0x30,0x30,0x30,0x30,0x00,0x00,0x00,0x00}}, - - {8, 16, {0x00,0x00,0x7C,0xC6,0xC6,0xC6,0x7C,0xC6, // -8- - 0xC6,0xC6,0xC6,0x7C,0x00,0x00,0x00,0x00}}, - - {8, 16, {0x00,0x00,0x7C,0xC6,0xC6,0xC6,0x7E,0x06, // -9- - 0x06,0x06,0x0C,0x78,0x00,0x00,0x00,0x00}}, - - {8, 16, {0x00,0x00,0x00,0x00,0x18,0x18,0x00,0x00, // -:- - 0x00,0x18,0x18,0x00,0x00,0x00,0x00,0x00}}, - - {8, 16, {0x00,0x00,0x00,0x00,0x18,0x18,0x00,0x00, // -;- - 0x00,0x18,0x18,0x30,0x00,0x00,0x00,0x00}}, - - {8, 16, {0x00,0x00,0x00,0x06,0x0C,0x18,0x30,0x60, // -<- - 0x30,0x18,0x0C,0x06,0x00,0x00,0x00,0x00}}, - - {8, 16, {0x00,0x00,0x00,0x00,0x00,0x00,0xFE,0x00, // -=- - 0x00,0xFE,0x00,0x00,0x00,0x00,0x00,0x00}}, - - {8, 16, {0x00,0x00,0x00,0x60,0x30,0x18,0x0C,0x06, // ->- - 0x0C,0x18,0x30,0x60,0x00,0x00,0x00,0x00}}, - - {8, 16, {0x00,0x00,0x7C,0xC6,0xC6,0x0C,0x18,0x18, // -?- - 0x18,0x00,0x18,0x18,0x00,0x00,0x00,0x00}}, - - {8, 16, {0x00,0x00,0x00,0x7C,0xC6,0xC6,0xDE,0xDE, // -@- - 0xDE,0xDC,0xC0,0x7C,0x00,0x00,0x00,0x00}}, - - {8, 16, {0x00,0x00,0x10,0x38,0x6C,0xC6,0xC6,0xFE, // -A- - 0xC6,0xC6,0xC6,0xC6,0x00,0x00,0x00,0x00}}, - - {8, 16, {0x00,0x00,0xFC,0x66,0x66,0x66,0x7C,0x66, // -B- - 0x66,0x66,0x66,0xFC,0x00,0x00,0x00,0x00}}, - - {8, 16, {0x00,0x00,0x3C,0x66,0xC2,0xC0,0xC0,0xC0, // -C- - 0xC0,0xC2,0x66,0x3C,0x00,0x00,0x00,0x00}}, - - {8, 16, {0x00,0x00,0xF8,0x6C,0x66,0x66,0x66,0x66, // -D- - 0x66,0x66,0x6C,0xF8,0x00,0x00,0x00,0x00}}, - - {8, 16, {0x00,0x00,0xFE,0x66,0x62,0x68,0x78,0x68, // -E- - 0x60,0x62,0x66,0xFE,0x00,0x00,0x00,0x00}}, - - {8, 16, {0x00,0x00,0xFE,0x66,0x62,0x68,0x78,0x68, // -F- - 0x60,0x60,0x60,0xF0,0x00,0x00,0x00,0x00}}, - - {8, 16, {0x00,0x00,0x3C,0x66,0xC2,0xC0,0xC0,0xDE, // -G- - 0xC6,0xC6,0x66,0x3A,0x00,0x00,0x00,0x00}}, - - {8, 16, {0x00,0x00,0xC6,0xC6,0xC6,0xC6,0xFE,0xC6, // -H- - 0xC6,0xC6,0xC6,0xC6,0x00,0x00,0x00,0x00}}, - - {8, 16, {0x00,0x00,0x3C,0x18,0x18,0x18,0x18,0x18, // -I- - 0x18,0x18,0x18,0x3C,0x00,0x00,0x00,0x00}}, - - {8, 16, {0x00,0x00,0x1E,0x0C,0x0C,0x0C,0x0C,0x0C, // -J- - 0xCC,0xCC,0xCC,0x78,0x00,0x00,0x00,0x00}}, - - {8, 16, {0x00,0x00,0xE6,0x66,0x6C,0x6C,0x78,0x78, // -K- - 0x6C,0x66,0x66,0xE6,0x00,0x00,0x00,0x00}}, - - {8, 16, {0x00,0x00,0xF0,0x60,0x60,0x60,0x60,0x60, // -L- - 0x60,0x62,0x66,0xFE,0x00,0x00,0x00,0x00}}, - - {8, 16, {0x00,0x00,0xC6,0xEE,0xFE,0xFE,0xD6,0xC6, // -M- - 0xC6,0xC6,0xC6,0xC6,0x00,0x00,0x00,0x00}}, - - {8, 16, {0x00,0x00,0xC6,0xE6,0xF6,0xFE,0xDE,0xCE, // -N- - 0xC6,0xC6,0xC6,0xC6,0x00,0x00,0x00,0x00}}, - - {8, 16, {0x00,0x00,0x38,0x6C,0xC6,0xC6,0xC6,0xC6, // -O- - 0xC6,0xC6,0x6C,0x38,0x00,0x00,0x00,0x00}}, - - {8, 16, {0x00,0x00,0xFC,0x66,0x66,0x66,0x7C,0x60, // -P- - 0x60,0x60,0x60,0xF0,0x00,0x00,0x00,0x00}}, - - {8, 16, {0x00,0x00,0x7C,0xC6,0xC6,0xC6,0xC6,0xC6, // -Q- - 0xC6,0xD6,0xDE,0x7C,0x0C,0x0E,0x00,0x00}}, - - {8, 16, {0x00,0x00,0xFC,0x66,0x66,0x66,0x7C,0x6C, // -R- - 0x66,0x66,0x66,0xE6,0x00,0x00,0x00,0x00}}, - - {8, 16, {0x00,0x00,0x7C,0xC6,0xC6,0x60,0x38,0x0C, // -S- - 0x06,0xC6,0xC6,0x7C,0x00,0x00,0x00,0x00}}, - - {8, 16, {0x00,0x00,0x7E,0x7E,0x5A,0x18,0x18,0x18, // -T- - 0x18,0x18,0x18,0x3C,0x00,0x00,0x00,0x00}}, - - {8, 16, {0x00,0x00,0xC6,0xC6,0xC6,0xC6,0xC6,0xC6, // -U- - 0xC6,0xC6,0xC6,0x7C,0x00,0x00,0x00,0x00}}, - - {8, 16, {0x00,0x00,0xC6,0xC6,0xC6,0xC6,0xC6,0xC6, // -V- - 0xC6,0x6C,0x38,0x10,0x00,0x00,0x00,0x00}}, - - {8, 16, {0x00,0x00,0xC6,0xC6,0xC6,0xC6,0xC6,0xD6, // -W- - 0xD6,0xFE,0x6C,0x6C,0x00,0x00,0x00,0x00}}, - - {8, 16, {0x00,0x00,0xC6,0xC6,0x6C,0x6C,0x38,0x38, // -X- - 0x6C,0x6C,0xC6,0xC6,0x00,0x00,0x00,0x00}}, - - {8, 16, {0x00,0x00,0x66,0x66,0x66,0x66,0x3C,0x18, // -Y- - 0x18,0x18,0x18,0x3C,0x00,0x00,0x00,0x00}}, - - {8, 16, {0x00,0x00,0xFE,0xC6,0x86,0x0C,0x18,0x30, // -Z- - 0x60,0xC2,0xC6,0xFE,0x00,0x00,0x00,0x00}}, - - {8, 16, {0x00,0x00,0x3C,0x30,0x30,0x30,0x30,0x30, // -[- - 0x30,0x30,0x30,0x3C,0x00,0x00,0x00,0x00}}, - - {8, 16, {0x00,0x00,0x00,0x80,0xC0,0xE0,0x70,0x38, // -\- - 0x1C,0x0E,0x06,0x02,0x00,0x00,0x00,0x00}}, - - {8, 16, {0x00,0x00,0x3C,0x0C,0x0C,0x0C,0x0C,0x0C, // -]- - 0x0C,0x0C,0x0C,0x3C,0x00,0x00,0x00,0x00}}, - - {8, 16, {0x10,0x38,0x6C,0xC6,0x00,0x00,0x00,0x00, // -^- - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}}, - - {8, 16, {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // -_- - 0x00,0x00,0x00,0x00,0x00,0xFF,0x00,0x00}}, - - {8, 16, {0x30,0x30,0x18,0x00,0x00,0x00,0x00,0x00, // -`- - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}}, - - {8, 16, {0x00,0x00,0x00,0x00,0x00,0x78,0x0C,0x7C, // -a- - 0xCC,0xCC,0xCC,0x76,0x00,0x00,0x00,0x00}}, - - {8, 16, {0x00,0x00,0xE0,0x60,0x60,0x78,0x6C,0x66, // -b- - 0x66,0x66,0x66,0xDC,0x00,0x00,0x00,0x00}}, - - {8, 16, {0x00,0x00,0x00,0x00,0x00,0x7C,0xC6,0xC0, // -c- - 0xC0,0xC0,0xC6,0x7C,0x00,0x00,0x00,0x00}}, - - {8, 16, {0x00,0x00,0x1C,0x0C,0x0C,0x3C,0x6C,0xCC, // -d- - 0xCC,0xCC,0xCC,0x76,0x00,0x00,0x00,0x00}}, - - {8, 16, {0x00,0x00,0x00,0x00,0x00,0x7C,0xC6,0xFE, // -e- - 0xC0,0xC0,0xC6,0x7C,0x00,0x00,0x00,0x00}}, - - {8, 16, {0x00,0x00,0x38,0x6C,0x64,0x60,0xF0,0x60, // -f- - 0x60,0x60,0x60,0xF0,0x00,0x00,0x00,0x00}}, - - {8, 16, {0x00,0x00,0x00,0x00,0x00,0x76,0xCC,0xCC, // -g- - 0xCC,0xCC,0xCC,0x7C,0x0C,0xCC,0x78,0x00}}, - - {8, 16, {0x00,0x00,0xE0,0x60,0x60,0x6C,0x76,0x66, // -h- - 0x66,0x66,0x66,0xE6,0x00,0x00,0x00,0x00}}, - - {8, 16, {0x00,0x00,0x18,0x18,0x00,0x38,0x18,0x18, // -i- - 0x18,0x18,0x18,0x3C,0x00,0x00,0x00,0x00}}, - - {8, 16, {0x00,0x00,0x06,0x06,0x00,0x0E,0x06,0x06, // -j- - 0x06,0x06,0x06,0x06,0x66,0x66,0x3C,0x00}}, - - {8, 16, {0x00,0x00,0xE0,0x60,0x60,0x66,0x6C,0x78, // -k- - 0x78,0x6C,0x66,0xE6,0x00,0x00,0x00,0x00}}, - - {8, 16, {0x00,0x00,0x38,0x18,0x18,0x18,0x18,0x18, // -l- - 0x18,0x18,0x18,0x3C,0x00,0x00,0x00,0x00}}, - - {8, 16, {0x00,0x00,0x00,0x00,0x00,0xEC,0xFE,0xD6, // -m- - 0xD6,0xD6,0xD6,0xD6,0x00,0x00,0x00,0x00}}, - - {8, 16, {0x00,0x00,0x00,0x00,0x00,0xDC,0x66,0x66, // -n- - 0x66,0x66,0x66,0x66,0x00,0x00,0x00,0x00}}, - - {8, 16, {0x00,0x00,0x00,0x00,0x00,0x7C,0xC6,0xC6, // -o- - 0xC6,0xC6,0xC6,0x7C,0x00,0x00,0x00,0x00}}, - - {8, 16, {0x00,0x00,0x00,0x00,0x00,0xDC,0x66,0x66, // -p- - 0x66,0x66,0x66,0x7C,0x60,0x60,0xF0,0x00}}, - - {8, 16, {0x00,0x00,0x00,0x00,0x00,0x76,0xCC,0xCC, // -q- - 0xCC,0xCC,0xCC,0x7C,0x0C,0x0C,0x1E,0x00}}, - - {8, 16, {0x00,0x00,0x00,0x00,0x00,0xDC,0x76,0x62, // -r- - 0x60,0x60,0x60,0xF0,0x00,0x00,0x00,0x00}}, - - {8, 16, {0x00,0x00,0x00,0x00,0x00,0x7C,0xC6,0x60, // -s- - 0x38,0x0C,0xC6,0x7C,0x00,0x00,0x00,0x00}}, - - {8, 16, {0x00,0x00,0x10,0x30,0x30,0xFC,0x30,0x30, // -t- - 0x30,0x30,0x36,0x1C,0x00,0x00,0x00,0x00}}, - - {8, 16, {0x00,0x00,0x00,0x00,0x00,0xCC,0xCC,0xCC, // -u- - 0xCC,0xCC,0xCC,0x76,0x00,0x00,0x00,0x00}}, - - {8, 16, {0x00,0x00,0x00,0x00,0x00,0x66,0x66,0x66, // -v- - 0x66,0x66,0x3C,0x18,0x00,0x00,0x00,0x00}}, - - {8, 16, {0x00,0x00,0x00,0x00,0x00,0xC6,0xC6,0xC6, // -w- - 0xD6,0xD6,0xFE,0x6C,0x00,0x00,0x00,0x00}}, - - {8, 16, {0x00,0x00,0x00,0x00,0x00,0xC6,0x6C,0x38, // -x- - 0x38,0x38,0x6C,0xC6,0x00,0x00,0x00,0x00}}, - - {8, 16, {0x00,0x00,0x00,0x00,0x00,0xC6,0xC6,0xC6, // -y- - 0xC6,0xC6,0xC6,0x7E,0x06,0x0C,0xF8,0x00}}, - - {8, 16, {0x00,0x00,0x00,0x00,0x00,0xFE,0xCC,0x18, // -z- - 0x30,0x60,0xC6,0xFE,0x00,0x00,0x00,0x00}}, - - {8, 16, {0x00,0x00,0x0E,0x18,0x18,0x18,0x70,0x18, // -{- - 0x18,0x18,0x18,0x0E,0x00,0x00,0x00,0x00}}, - - {8, 16, {0x00,0x00,0x18,0x18,0x18,0x18,0x00,0x18, // -|- - 0x18,0x18,0x18,0x18,0x00,0x00,0x00,0x00}}, - - {8, 16, {0x00,0x00,0x70,0x18,0x18,0x18,0x0E,0x18, // -}- - 0x18,0x18,0x18,0x70,0x00,0x00,0x00,0x00}}, - - {8, 16, {0x00,0x00,0x76,0xDC,0x00,0x00,0x00,0x00, // -~- - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}}, - - {8, 16, {0x00,0x00,0x00,0x00,0x10,0x38,0x6C,0xC6, // -- - 0xC6,0xC6,0xFE,0x00,0x00,0x00,0x00,0x00}}, -}; - - -const unsigned char font_ascii_8x16[] = -{ - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // - - - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - - 0x00,0x00,0x18,0x3C,0x3C,0x3C,0x18,0x18, // -!- - 0x18,0x00,0x18,0x18,0x00,0x00,0x00,0x00, - - 0x00,0x66,0x66,0x66,0x24,0x00,0x00,0x00, // -"- - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - - 0x00,0x00,0x00,0x6C,0x6C,0xFE,0x6C,0x6C, // -#- - 0x6C,0xFE,0x6C,0x6C,0x00,0x00,0x00,0x00, - - 0x18,0x18,0x7C,0xC6,0xC2,0xC0,0x7C,0x06, // -$- - 0x86,0xC6,0x7C,0x18,0x18,0x00,0x00,0x00, - - 0x00,0x00,0x00,0x00,0xC2,0xC6,0x0C,0x18, // -%- - 0x30,0x60,0xC6,0x86,0x00,0x00,0x00,0x00, - - 0x00,0x00,0x38,0x6C,0x6C,0x38,0x76,0xDC, // -&- - 0xCC,0xCC,0xCC,0x76,0x00,0x00,0x00,0x00, - - 0x00,0x30,0x30,0x30,0x60,0x00,0x00,0x00, // -'- - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - - 0x00,0x00,0x0C,0x18,0x30,0x30,0x30,0x30, // -(- - 0x30,0x30,0x18,0x0C,0x00,0x00,0x00,0x00, - - 0x00,0x00,0x30,0x18,0x0C,0x0C,0x0C,0x0C, // -)- - 0x0C,0x0C,0x18,0x30,0x00,0x00,0x00,0x00, - - 0x00,0x00,0x00,0x00,0x00,0x66,0x3C,0xFF, // -*- - 0x3C,0x66,0x00,0x00,0x00,0x00,0x00,0x00, - - 0x00,0x00,0x00,0x00,0x00,0x18,0x18,0x7E, // -+- - 0x18,0x18,0x00,0x00,0x00,0x00,0x00,0x00, - - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // -,- - 0x00,0x18,0x18,0x18,0x30,0x00,0x00,0x00, - - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFE, // --- - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // -.- - 0x00,0x00,0x18,0x18,0x00,0x00,0x00,0x00, - - 0x00,0x00,0x00,0x00,0x02,0x06,0x0C,0x18, // -/- - 0x30,0x60,0xC0,0x80,0x00,0x00,0x00,0x00, - - 0x00,0x00,0x7C,0xC6,0xC6,0xCE,0xD6,0xD6, // -0- - 0xE6,0xC6,0xC6,0x7C,0x00,0x00,0x00,0x00, - - 0x00,0x00,0x18,0x38,0x78,0x18,0x18,0x18, // -1- - 0x18,0x18,0x18,0x7E,0x00,0x00,0x00,0x00, - - 0x00,0x00,0x7C,0xC6,0x06,0x0C,0x18,0x30, // -2- - 0x60,0xC0,0xC6,0xFE,0x00,0x00,0x00,0x00, - - 0x00,0x00,0x7C,0xC6,0x06,0x06,0x3C,0x06, // -3- - 0x06,0x06,0xC6,0x7C,0x00,0x00,0x00,0x00, - - 0x00,0x00,0x0C,0x1C,0x3C,0x6C,0xCC,0xFE, // -4- - 0x0C,0x0C,0x0C,0x1E,0x00,0x00,0x00,0x00, - - 0x00,0x00,0xFE,0xC0,0xC0,0xC0,0xFC,0x0E, // -5- - 0x06,0x06,0xC6,0x7C,0x00,0x00,0x00,0x00, - - 0x00,0x00,0x38,0x60,0xC0,0xC0,0xFC,0xC6, // -6- - 0xC6,0xC6,0xC6,0x7C,0x00,0x00,0x00,0x00, - - 0x00,0x00,0xFE,0xC6,0x06,0x06,0x0C,0x18, // -7- - 0x30,0x30,0x30,0x30,0x00,0x00,0x00,0x00, - - 0x00,0x00,0x7C,0xC6,0xC6,0xC6,0x7C,0xC6, // -8- - 0xC6,0xC6,0xC6,0x7C,0x00,0x00,0x00,0x00, - - 0x00,0x00,0x7C,0xC6,0xC6,0xC6,0x7E,0x06, // -9- - 0x06,0x06,0x0C,0x78,0x00,0x00,0x00,0x00, - - 0x00,0x00,0x00,0x00,0x18,0x18,0x00,0x00, // -:- - 0x00,0x18,0x18,0x00,0x00,0x00,0x00,0x00, - - 0x00,0x00,0x00,0x00,0x18,0x18,0x00,0x00, // -;- - 0x00,0x18,0x18,0x30,0x00,0x00,0x00,0x00, - - 0x00,0x00,0x00,0x06,0x0C,0x18,0x30,0x60, // -<- - 0x30,0x18,0x0C,0x06,0x00,0x00,0x00,0x00, - - 0x00,0x00,0x00,0x00,0x00,0x00,0xFE,0x00, // -=- - 0x00,0xFE,0x00,0x00,0x00,0x00,0x00,0x00, - - 0x00,0x00,0x00,0x60,0x30,0x18,0x0C,0x06, // ->- - 0x0C,0x18,0x30,0x60,0x00,0x00,0x00,0x00, - - 0x00,0x00,0x7C,0xC6,0xC6,0x0C,0x18,0x18, // -?- - 0x18,0x00,0x18,0x18,0x00,0x00,0x00,0x00, - - 0x00,0x00,0x00,0x7C,0xC6,0xC6,0xDE,0xDE, // -@- - 0xDE,0xDC,0xC0,0x7C,0x00,0x00,0x00,0x00, - - 0x00,0x00,0x10,0x38,0x6C,0xC6,0xC6,0xFE, // -A- - 0xC6,0xC6,0xC6,0xC6,0x00,0x00,0x00,0x00, - - 0x00,0x00,0xFC,0x66,0x66,0x66,0x7C,0x66, // -B- - 0x66,0x66,0x66,0xFC,0x00,0x00,0x00,0x00, - - 0x00,0x00,0x3C,0x66,0xC2,0xC0,0xC0,0xC0, // -C- - 0xC0,0xC2,0x66,0x3C,0x00,0x00,0x00,0x00, - - 0x00,0x00,0xF8,0x6C,0x66,0x66,0x66,0x66, // -D- - 0x66,0x66,0x6C,0xF8,0x00,0x00,0x00,0x00, - - 0x00,0x00,0xFE,0x66,0x62,0x68,0x78,0x68, // -E- - 0x60,0x62,0x66,0xFE,0x00,0x00,0x00,0x00, - - 0x00,0x00,0xFE,0x66,0x62,0x68,0x78,0x68, // -F- - 0x60,0x60,0x60,0xF0,0x00,0x00,0x00,0x00, - - 0x00,0x00,0x3C,0x66,0xC2,0xC0,0xC0,0xDE, // -G- - 0xC6,0xC6,0x66,0x3A,0x00,0x00,0x00,0x00, - - 0x00,0x00,0xC6,0xC6,0xC6,0xC6,0xFE,0xC6, // -H- - 0xC6,0xC6,0xC6,0xC6,0x00,0x00,0x00,0x00, - - 0x00,0x00,0x3C,0x18,0x18,0x18,0x18,0x18, // -I- - 0x18,0x18,0x18,0x3C,0x00,0x00,0x00,0x00, - - 0x00,0x00,0x1E,0x0C,0x0C,0x0C,0x0C,0x0C, // -J- - 0xCC,0xCC,0xCC,0x78,0x00,0x00,0x00,0x00, - - 0x00,0x00,0xE6,0x66,0x6C,0x6C,0x78,0x78, // -K- - 0x6C,0x66,0x66,0xE6,0x00,0x00,0x00,0x00, - - 0x00,0x00,0xF0,0x60,0x60,0x60,0x60,0x60, // -L- - 0x60,0x62,0x66,0xFE,0x00,0x00,0x00,0x00, - - 0x00,0x00,0xC6,0xEE,0xFE,0xFE,0xD6,0xC6, // -M- - 0xC6,0xC6,0xC6,0xC6,0x00,0x00,0x00,0x00, - - 0x00,0x00,0xC6,0xE6,0xF6,0xFE,0xDE,0xCE, // -N- - 0xC6,0xC6,0xC6,0xC6,0x00,0x00,0x00,0x00, - - 0x00,0x00,0x38,0x6C,0xC6,0xC6,0xC6,0xC6, // -O- - 0xC6,0xC6,0x6C,0x38,0x00,0x00,0x00,0x00, - - 0x00,0x00,0xFC,0x66,0x66,0x66,0x7C,0x60, // -P- - 0x60,0x60,0x60,0xF0,0x00,0x00,0x00,0x00, - - 0x00,0x00,0x7C,0xC6,0xC6,0xC6,0xC6,0xC6, // -Q- - 0xC6,0xD6,0xDE,0x7C,0x0C,0x0E,0x00,0x00, - - 0x00,0x00,0xFC,0x66,0x66,0x66,0x7C,0x6C, // -R- - 0x66,0x66,0x66,0xE6,0x00,0x00,0x00,0x00, - - 0x00,0x00,0x7C,0xC6,0xC6,0x60,0x38,0x0C, // -S- - 0x06,0xC6,0xC6,0x7C,0x00,0x00,0x00,0x00, - - 0x00,0x00,0x7E,0x7E,0x5A,0x18,0x18,0x18, // -T- - 0x18,0x18,0x18,0x3C,0x00,0x00,0x00,0x00, - - 0x00,0x00,0xC6,0xC6,0xC6,0xC6,0xC6,0xC6, // -U- - 0xC6,0xC6,0xC6,0x7C,0x00,0x00,0x00,0x00, - - 0x00,0x00,0xC6,0xC6,0xC6,0xC6,0xC6,0xC6, // -V- - 0xC6,0x6C,0x38,0x10,0x00,0x00,0x00,0x00, - - 0x00,0x00,0xC6,0xC6,0xC6,0xC6,0xC6,0xD6, // -W- - 0xD6,0xFE,0x6C,0x6C,0x00,0x00,0x00,0x00, - - 0x00,0x00,0xC6,0xC6,0x6C,0x6C,0x38,0x38, // -X- - 0x6C,0x6C,0xC6,0xC6,0x00,0x00,0x00,0x00, - - 0x00,0x00,0x66,0x66,0x66,0x66,0x3C,0x18, // -Y- - 0x18,0x18,0x18,0x3C,0x00,0x00,0x00,0x00, - - 0x00,0x00,0xFE,0xC6,0x86,0x0C,0x18,0x30, // -Z- - 0x60,0xC2,0xC6,0xFE,0x00,0x00,0x00,0x00, - - 0x00,0x00,0x3C,0x30,0x30,0x30,0x30,0x30, // -[- - 0x30,0x30,0x30,0x3C,0x00,0x00,0x00,0x00, - - 0x00,0x00,0x00,0x80,0xC0,0xE0,0x70,0x38, // -\- - 0x1C,0x0E,0x06,0x02,0x00,0x00,0x00,0x00, - - 0x00,0x00,0x3C,0x0C,0x0C,0x0C,0x0C,0x0C, // -]- - 0x0C,0x0C,0x0C,0x3C,0x00,0x00,0x00,0x00, - - 0x10,0x38,0x6C,0xC6,0x00,0x00,0x00,0x00, // -^- - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // -_- - 0x00,0x00,0x00,0x00,0x00,0xFF,0x00,0x00, - - 0x30,0x30,0x18,0x00,0x00,0x00,0x00,0x00, // -`- - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - - 0x00,0x00,0x00,0x00,0x00,0x78,0x0C,0x7C, // -a- - 0xCC,0xCC,0xCC,0x76,0x00,0x00,0x00,0x00, - - 0x00,0x00,0xE0,0x60,0x60,0x78,0x6C,0x66, // -b- - 0x66,0x66,0x66,0xDC,0x00,0x00,0x00,0x00, - - 0x00,0x00,0x00,0x00,0x00,0x7C,0xC6,0xC0, // -c- - 0xC0,0xC0,0xC6,0x7C,0x00,0x00,0x00,0x00, - - 0x00,0x00,0x1C,0x0C,0x0C,0x3C,0x6C,0xCC, // -d- - 0xCC,0xCC,0xCC,0x76,0x00,0x00,0x00,0x00, - - 0x00,0x00,0x00,0x00,0x00,0x7C,0xC6,0xFE, // -e- - 0xC0,0xC0,0xC6,0x7C,0x00,0x00,0x00,0x00, - - 0x00,0x00,0x38,0x6C,0x64,0x60,0xF0,0x60, // -f- - 0x60,0x60,0x60,0xF0,0x00,0x00,0x00,0x00, - - 0x00,0x00,0x00,0x00,0x00,0x76,0xCC,0xCC, // -g- - 0xCC,0xCC,0xCC,0x7C,0x0C,0xCC,0x78,0x00, - - 0x00,0x00,0xE0,0x60,0x60,0x6C,0x76,0x66, // -h- - 0x66,0x66,0x66,0xE6,0x00,0x00,0x00,0x00, - - 0x00,0x00,0x18,0x18,0x00,0x38,0x18,0x18, // -i- - 0x18,0x18,0x18,0x3C,0x00,0x00,0x00,0x00, - - 0x00,0x00,0x06,0x06,0x00,0x0E,0x06,0x06, // -j- - 0x06,0x06,0x06,0x06,0x66,0x66,0x3C,0x00, - - 0x00,0x00,0xE0,0x60,0x60,0x66,0x6C,0x78, // -k- - 0x78,0x6C,0x66,0xE6,0x00,0x00,0x00,0x00, - - 0x00,0x00,0x38,0x18,0x18,0x18,0x18,0x18, // -l- - 0x18,0x18,0x18,0x3C,0x00,0x00,0x00,0x00, - - 0x00,0x00,0x00,0x00,0x00,0xEC,0xFE,0xD6, // -m- - 0xD6,0xD6,0xD6,0xD6,0x00,0x00,0x00,0x00, - - 0x00,0x00,0x00,0x00,0x00,0xDC,0x66,0x66, // -n- - 0x66,0x66,0x66,0x66,0x00,0x00,0x00,0x00, - - 0x00,0x00,0x00,0x00,0x00,0x7C,0xC6,0xC6, // -o- - 0xC6,0xC6,0xC6,0x7C,0x00,0x00,0x00,0x00, - - 0x00,0x00,0x00,0x00,0x00,0xDC,0x66,0x66, // -p- - 0x66,0x66,0x66,0x7C,0x60,0x60,0xF0,0x00, - - 0x00,0x00,0x00,0x00,0x00,0x76,0xCC,0xCC, // -q- - 0xCC,0xCC,0xCC,0x7C,0x0C,0x0C,0x1E,0x00, - - 0x00,0x00,0x00,0x00,0x00,0xDC,0x76,0x62, // -r- - 0x60,0x60,0x60,0xF0,0x00,0x00,0x00,0x00, - - 0x00,0x00,0x00,0x00,0x00,0x7C,0xC6,0x60, // -s- - 0x38,0x0C,0xC6,0x7C,0x00,0x00,0x00,0x00, - - 0x00,0x00,0x10,0x30,0x30,0xFC,0x30,0x30, // -t- - 0x30,0x30,0x36,0x1C,0x00,0x00,0x00,0x00, - - 0x00,0x00,0x00,0x00,0x00,0xCC,0xCC,0xCC, // -u- - 0xCC,0xCC,0xCC,0x76,0x00,0x00,0x00,0x00, - - 0x00,0x00,0x00,0x00,0x00,0x66,0x66,0x66, // -v- - 0x66,0x66,0x3C,0x18,0x00,0x00,0x00,0x00, - - 0x00,0x00,0x00,0x00,0x00,0xC6,0xC6,0xC6, // -w- - 0xD6,0xD6,0xFE,0x6C,0x00,0x00,0x00,0x00, - - 0x00,0x00,0x00,0x00,0x00,0xC6,0x6C,0x38, // -x- - 0x38,0x38,0x6C,0xC6,0x00,0x00,0x00,0x00, - - 0x00,0x00,0x00,0x00,0x00,0xC6,0xC6,0xC6, // -y- - 0xC6,0xC6,0xC6,0x7E,0x06,0x0C,0xF8,0x00, - - 0x00,0x00,0x00,0x00,0x00,0xFE,0xCC,0x18, // -z- - 0x30,0x60,0xC6,0xFE,0x00,0x00,0x00,0x00, - - 0x00,0x00,0x0E,0x18,0x18,0x18,0x70,0x18, // -{- - 0x18,0x18,0x18,0x0E,0x00,0x00,0x00,0x00, - - 0x00,0x00,0x18,0x18,0x18,0x18,0x00,0x18, // -|- - 0x18,0x18,0x18,0x18,0x00,0x00,0x00,0x00, - - 0x00,0x00,0x70,0x18,0x18,0x18,0x0E,0x18, // -}- - 0x18,0x18,0x18,0x70,0x00,0x00,0x00,0x00, - - 0x00,0x00,0x76,0xDC,0x00,0x00,0x00,0x00, // -~- - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - - 0x00,0x00,0x00,0x00,0x10,0x38,0x6C,0xC6, // -␡- - 0xC6,0xC6,0xFE,0x00,0x00,0x00,0x00,0x00, -}; - - - - - +/* + * SPDX-License-Identifier: MIT + * + * Copyright (C) 2013-2024 OpenMV, LLC. + * Copyright (c) 2024 M5Stack Technology CO LTD + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * Font data. + * + */ +#include "font.h" + + + +/** + * ASCII 8x16 + * 0x20~0x7E + */ +const glyph_t font[] = +{ + {8, 16, {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // - - + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}}, + + {8, 16, {0x00,0x00,0x18,0x3C,0x3C,0x3C,0x18,0x18, // -!- + 0x18,0x00,0x18,0x18,0x00,0x00,0x00,0x00}}, + + {8, 16, {0x00,0x66,0x66,0x66,0x24,0x00,0x00,0x00, // -"- + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}}, + + {8, 16, {0x00,0x00,0x00,0x6C,0x6C,0xFE,0x6C,0x6C, // -#- + 0x6C,0xFE,0x6C,0x6C,0x00,0x00,0x00,0x00}}, + + {8, 16, {0x18,0x18,0x7C,0xC6,0xC2,0xC0,0x7C,0x06, // -$- + 0x86,0xC6,0x7C,0x18,0x18,0x00,0x00,0x00}}, + + {8, 16, {0x00,0x00,0x00,0x00,0xC2,0xC6,0x0C,0x18, // -%- + 0x30,0x60,0xC6,0x86,0x00,0x00,0x00,0x00}}, + + {8, 16, {0x00,0x00,0x38,0x6C,0x6C,0x38,0x76,0xDC, // -&- + 0xCC,0xCC,0xCC,0x76,0x00,0x00,0x00,0x00}}, + + {8, 16, {0x00,0x30,0x30,0x30,0x60,0x00,0x00,0x00, // -'- + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}}, + + {8, 16, {0x00,0x00,0x0C,0x18,0x30,0x30,0x30,0x30, // -(- + 0x30,0x30,0x18,0x0C,0x00,0x00,0x00,0x00}}, + + {8, 16, {0x00,0x00,0x30,0x18,0x0C,0x0C,0x0C,0x0C, // -)- + 0x0C,0x0C,0x18,0x30,0x00,0x00,0x00,0x00}}, + + {8, 16, {0x00,0x00,0x00,0x00,0x00,0x66,0x3C,0xFF, // -*- + 0x3C,0x66,0x00,0x00,0x00,0x00,0x00,0x00}}, + + {8, 16, {0x00,0x00,0x00,0x00,0x00,0x18,0x18,0x7E, // -+- + 0x18,0x18,0x00,0x00,0x00,0x00,0x00,0x00}}, + + {8, 16, {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // -,- + 0x00,0x18,0x18,0x18,0x30,0x00,0x00,0x00}}, + + {8, 16, {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFE, // --- + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}}, + + {8, 16, {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // -.- + 0x00,0x00,0x18,0x18,0x00,0x00,0x00,0x00}}, + + {8, 16, {0x00,0x00,0x00,0x00,0x02,0x06,0x0C,0x18, // -/- + 0x30,0x60,0xC0,0x80,0x00,0x00,0x00,0x00}}, + + {8, 16, {0x00,0x00,0x7C,0xC6,0xC6,0xCE,0xD6,0xD6, // -0- + 0xE6,0xC6,0xC6,0x7C,0x00,0x00,0x00,0x00}}, + + {8, 16, {0x00,0x00,0x18,0x38,0x78,0x18,0x18,0x18, // -1- + 0x18,0x18,0x18,0x7E,0x00,0x00,0x00,0x00}}, + + {8, 16, {0x00,0x00,0x7C,0xC6,0x06,0x0C,0x18,0x30, // -2- + 0x60,0xC0,0xC6,0xFE,0x00,0x00,0x00,0x00}}, + + {8, 16, {0x00,0x00,0x7C,0xC6,0x06,0x06,0x3C,0x06, // -3- + 0x06,0x06,0xC6,0x7C,0x00,0x00,0x00,0x00}}, + + {8, 16, {0x00,0x00,0x0C,0x1C,0x3C,0x6C,0xCC,0xFE, // -4- + 0x0C,0x0C,0x0C,0x1E,0x00,0x00,0x00,0x00}}, + + {8, 16, {0x00,0x00,0xFE,0xC0,0xC0,0xC0,0xFC,0x0E, // -5- + 0x06,0x06,0xC6,0x7C,0x00,0x00,0x00,0x00}}, + + {8, 16, {0x00,0x00,0x38,0x60,0xC0,0xC0,0xFC,0xC6, // -6- + 0xC6,0xC6,0xC6,0x7C,0x00,0x00,0x00,0x00}}, + + {8, 16, {0x00,0x00,0xFE,0xC6,0x06,0x06,0x0C,0x18, // -7- + 0x30,0x30,0x30,0x30,0x00,0x00,0x00,0x00}}, + + {8, 16, {0x00,0x00,0x7C,0xC6,0xC6,0xC6,0x7C,0xC6, // -8- + 0xC6,0xC6,0xC6,0x7C,0x00,0x00,0x00,0x00}}, + + {8, 16, {0x00,0x00,0x7C,0xC6,0xC6,0xC6,0x7E,0x06, // -9- + 0x06,0x06,0x0C,0x78,0x00,0x00,0x00,0x00}}, + + {8, 16, {0x00,0x00,0x00,0x00,0x18,0x18,0x00,0x00, // -:- + 0x00,0x18,0x18,0x00,0x00,0x00,0x00,0x00}}, + + {8, 16, {0x00,0x00,0x00,0x00,0x18,0x18,0x00,0x00, // -;- + 0x00,0x18,0x18,0x30,0x00,0x00,0x00,0x00}}, + + {8, 16, {0x00,0x00,0x00,0x06,0x0C,0x18,0x30,0x60, // -<- + 0x30,0x18,0x0C,0x06,0x00,0x00,0x00,0x00}}, + + {8, 16, {0x00,0x00,0x00,0x00,0x00,0x00,0xFE,0x00, // -=- + 0x00,0xFE,0x00,0x00,0x00,0x00,0x00,0x00}}, + + {8, 16, {0x00,0x00,0x00,0x60,0x30,0x18,0x0C,0x06, // ->- + 0x0C,0x18,0x30,0x60,0x00,0x00,0x00,0x00}}, + + {8, 16, {0x00,0x00,0x7C,0xC6,0xC6,0x0C,0x18,0x18, // -?- + 0x18,0x00,0x18,0x18,0x00,0x00,0x00,0x00}}, + + {8, 16, {0x00,0x00,0x00,0x7C,0xC6,0xC6,0xDE,0xDE, // -@- + 0xDE,0xDC,0xC0,0x7C,0x00,0x00,0x00,0x00}}, + + {8, 16, {0x00,0x00,0x10,0x38,0x6C,0xC6,0xC6,0xFE, // -A- + 0xC6,0xC6,0xC6,0xC6,0x00,0x00,0x00,0x00}}, + + {8, 16, {0x00,0x00,0xFC,0x66,0x66,0x66,0x7C,0x66, // -B- + 0x66,0x66,0x66,0xFC,0x00,0x00,0x00,0x00}}, + + {8, 16, {0x00,0x00,0x3C,0x66,0xC2,0xC0,0xC0,0xC0, // -C- + 0xC0,0xC2,0x66,0x3C,0x00,0x00,0x00,0x00}}, + + {8, 16, {0x00,0x00,0xF8,0x6C,0x66,0x66,0x66,0x66, // -D- + 0x66,0x66,0x6C,0xF8,0x00,0x00,0x00,0x00}}, + + {8, 16, {0x00,0x00,0xFE,0x66,0x62,0x68,0x78,0x68, // -E- + 0x60,0x62,0x66,0xFE,0x00,0x00,0x00,0x00}}, + + {8, 16, {0x00,0x00,0xFE,0x66,0x62,0x68,0x78,0x68, // -F- + 0x60,0x60,0x60,0xF0,0x00,0x00,0x00,0x00}}, + + {8, 16, {0x00,0x00,0x3C,0x66,0xC2,0xC0,0xC0,0xDE, // -G- + 0xC6,0xC6,0x66,0x3A,0x00,0x00,0x00,0x00}}, + + {8, 16, {0x00,0x00,0xC6,0xC6,0xC6,0xC6,0xFE,0xC6, // -H- + 0xC6,0xC6,0xC6,0xC6,0x00,0x00,0x00,0x00}}, + + {8, 16, {0x00,0x00,0x3C,0x18,0x18,0x18,0x18,0x18, // -I- + 0x18,0x18,0x18,0x3C,0x00,0x00,0x00,0x00}}, + + {8, 16, {0x00,0x00,0x1E,0x0C,0x0C,0x0C,0x0C,0x0C, // -J- + 0xCC,0xCC,0xCC,0x78,0x00,0x00,0x00,0x00}}, + + {8, 16, {0x00,0x00,0xE6,0x66,0x6C,0x6C,0x78,0x78, // -K- + 0x6C,0x66,0x66,0xE6,0x00,0x00,0x00,0x00}}, + + {8, 16, {0x00,0x00,0xF0,0x60,0x60,0x60,0x60,0x60, // -L- + 0x60,0x62,0x66,0xFE,0x00,0x00,0x00,0x00}}, + + {8, 16, {0x00,0x00,0xC6,0xEE,0xFE,0xFE,0xD6,0xC6, // -M- + 0xC6,0xC6,0xC6,0xC6,0x00,0x00,0x00,0x00}}, + + {8, 16, {0x00,0x00,0xC6,0xE6,0xF6,0xFE,0xDE,0xCE, // -N- + 0xC6,0xC6,0xC6,0xC6,0x00,0x00,0x00,0x00}}, + + {8, 16, {0x00,0x00,0x38,0x6C,0xC6,0xC6,0xC6,0xC6, // -O- + 0xC6,0xC6,0x6C,0x38,0x00,0x00,0x00,0x00}}, + + {8, 16, {0x00,0x00,0xFC,0x66,0x66,0x66,0x7C,0x60, // -P- + 0x60,0x60,0x60,0xF0,0x00,0x00,0x00,0x00}}, + + {8, 16, {0x00,0x00,0x7C,0xC6,0xC6,0xC6,0xC6,0xC6, // -Q- + 0xC6,0xD6,0xDE,0x7C,0x0C,0x0E,0x00,0x00}}, + + {8, 16, {0x00,0x00,0xFC,0x66,0x66,0x66,0x7C,0x6C, // -R- + 0x66,0x66,0x66,0xE6,0x00,0x00,0x00,0x00}}, + + {8, 16, {0x00,0x00,0x7C,0xC6,0xC6,0x60,0x38,0x0C, // -S- + 0x06,0xC6,0xC6,0x7C,0x00,0x00,0x00,0x00}}, + + {8, 16, {0x00,0x00,0x7E,0x7E,0x5A,0x18,0x18,0x18, // -T- + 0x18,0x18,0x18,0x3C,0x00,0x00,0x00,0x00}}, + + {8, 16, {0x00,0x00,0xC6,0xC6,0xC6,0xC6,0xC6,0xC6, // -U- + 0xC6,0xC6,0xC6,0x7C,0x00,0x00,0x00,0x00}}, + + {8, 16, {0x00,0x00,0xC6,0xC6,0xC6,0xC6,0xC6,0xC6, // -V- + 0xC6,0x6C,0x38,0x10,0x00,0x00,0x00,0x00}}, + + {8, 16, {0x00,0x00,0xC6,0xC6,0xC6,0xC6,0xC6,0xD6, // -W- + 0xD6,0xFE,0x6C,0x6C,0x00,0x00,0x00,0x00}}, + + {8, 16, {0x00,0x00,0xC6,0xC6,0x6C,0x6C,0x38,0x38, // -X- + 0x6C,0x6C,0xC6,0xC6,0x00,0x00,0x00,0x00}}, + + {8, 16, {0x00,0x00,0x66,0x66,0x66,0x66,0x3C,0x18, // -Y- + 0x18,0x18,0x18,0x3C,0x00,0x00,0x00,0x00}}, + + {8, 16, {0x00,0x00,0xFE,0xC6,0x86,0x0C,0x18,0x30, // -Z- + 0x60,0xC2,0xC6,0xFE,0x00,0x00,0x00,0x00}}, + + {8, 16, {0x00,0x00,0x3C,0x30,0x30,0x30,0x30,0x30, // -[- + 0x30,0x30,0x30,0x3C,0x00,0x00,0x00,0x00}}, + + {8, 16, {0x00,0x00,0x00,0x80,0xC0,0xE0,0x70,0x38, // -\- + 0x1C,0x0E,0x06,0x02,0x00,0x00,0x00,0x00}}, + + {8, 16, {0x00,0x00,0x3C,0x0C,0x0C,0x0C,0x0C,0x0C, // -]- + 0x0C,0x0C,0x0C,0x3C,0x00,0x00,0x00,0x00}}, + + {8, 16, {0x10,0x38,0x6C,0xC6,0x00,0x00,0x00,0x00, // -^- + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}}, + + {8, 16, {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // -_- + 0x00,0x00,0x00,0x00,0x00,0xFF,0x00,0x00}}, + + {8, 16, {0x30,0x30,0x18,0x00,0x00,0x00,0x00,0x00, // -`- + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}}, + + {8, 16, {0x00,0x00,0x00,0x00,0x00,0x78,0x0C,0x7C, // -a- + 0xCC,0xCC,0xCC,0x76,0x00,0x00,0x00,0x00}}, + + {8, 16, {0x00,0x00,0xE0,0x60,0x60,0x78,0x6C,0x66, // -b- + 0x66,0x66,0x66,0xDC,0x00,0x00,0x00,0x00}}, + + {8, 16, {0x00,0x00,0x00,0x00,0x00,0x7C,0xC6,0xC0, // -c- + 0xC0,0xC0,0xC6,0x7C,0x00,0x00,0x00,0x00}}, + + {8, 16, {0x00,0x00,0x1C,0x0C,0x0C,0x3C,0x6C,0xCC, // -d- + 0xCC,0xCC,0xCC,0x76,0x00,0x00,0x00,0x00}}, + + {8, 16, {0x00,0x00,0x00,0x00,0x00,0x7C,0xC6,0xFE, // -e- + 0xC0,0xC0,0xC6,0x7C,0x00,0x00,0x00,0x00}}, + + {8, 16, {0x00,0x00,0x38,0x6C,0x64,0x60,0xF0,0x60, // -f- + 0x60,0x60,0x60,0xF0,0x00,0x00,0x00,0x00}}, + + {8, 16, {0x00,0x00,0x00,0x00,0x00,0x76,0xCC,0xCC, // -g- + 0xCC,0xCC,0xCC,0x7C,0x0C,0xCC,0x78,0x00}}, + + {8, 16, {0x00,0x00,0xE0,0x60,0x60,0x6C,0x76,0x66, // -h- + 0x66,0x66,0x66,0xE6,0x00,0x00,0x00,0x00}}, + + {8, 16, {0x00,0x00,0x18,0x18,0x00,0x38,0x18,0x18, // -i- + 0x18,0x18,0x18,0x3C,0x00,0x00,0x00,0x00}}, + + {8, 16, {0x00,0x00,0x06,0x06,0x00,0x0E,0x06,0x06, // -j- + 0x06,0x06,0x06,0x06,0x66,0x66,0x3C,0x00}}, + + {8, 16, {0x00,0x00,0xE0,0x60,0x60,0x66,0x6C,0x78, // -k- + 0x78,0x6C,0x66,0xE6,0x00,0x00,0x00,0x00}}, + + {8, 16, {0x00,0x00,0x38,0x18,0x18,0x18,0x18,0x18, // -l- + 0x18,0x18,0x18,0x3C,0x00,0x00,0x00,0x00}}, + + {8, 16, {0x00,0x00,0x00,0x00,0x00,0xEC,0xFE,0xD6, // -m- + 0xD6,0xD6,0xD6,0xD6,0x00,0x00,0x00,0x00}}, + + {8, 16, {0x00,0x00,0x00,0x00,0x00,0xDC,0x66,0x66, // -n- + 0x66,0x66,0x66,0x66,0x00,0x00,0x00,0x00}}, + + {8, 16, {0x00,0x00,0x00,0x00,0x00,0x7C,0xC6,0xC6, // -o- + 0xC6,0xC6,0xC6,0x7C,0x00,0x00,0x00,0x00}}, + + {8, 16, {0x00,0x00,0x00,0x00,0x00,0xDC,0x66,0x66, // -p- + 0x66,0x66,0x66,0x7C,0x60,0x60,0xF0,0x00}}, + + {8, 16, {0x00,0x00,0x00,0x00,0x00,0x76,0xCC,0xCC, // -q- + 0xCC,0xCC,0xCC,0x7C,0x0C,0x0C,0x1E,0x00}}, + + {8, 16, {0x00,0x00,0x00,0x00,0x00,0xDC,0x76,0x62, // -r- + 0x60,0x60,0x60,0xF0,0x00,0x00,0x00,0x00}}, + + {8, 16, {0x00,0x00,0x00,0x00,0x00,0x7C,0xC6,0x60, // -s- + 0x38,0x0C,0xC6,0x7C,0x00,0x00,0x00,0x00}}, + + {8, 16, {0x00,0x00,0x10,0x30,0x30,0xFC,0x30,0x30, // -t- + 0x30,0x30,0x36,0x1C,0x00,0x00,0x00,0x00}}, + + {8, 16, {0x00,0x00,0x00,0x00,0x00,0xCC,0xCC,0xCC, // -u- + 0xCC,0xCC,0xCC,0x76,0x00,0x00,0x00,0x00}}, + + {8, 16, {0x00,0x00,0x00,0x00,0x00,0x66,0x66,0x66, // -v- + 0x66,0x66,0x3C,0x18,0x00,0x00,0x00,0x00}}, + + {8, 16, {0x00,0x00,0x00,0x00,0x00,0xC6,0xC6,0xC6, // -w- + 0xD6,0xD6,0xFE,0x6C,0x00,0x00,0x00,0x00}}, + + {8, 16, {0x00,0x00,0x00,0x00,0x00,0xC6,0x6C,0x38, // -x- + 0x38,0x38,0x6C,0xC6,0x00,0x00,0x00,0x00}}, + + {8, 16, {0x00,0x00,0x00,0x00,0x00,0xC6,0xC6,0xC6, // -y- + 0xC6,0xC6,0xC6,0x7E,0x06,0x0C,0xF8,0x00}}, + + {8, 16, {0x00,0x00,0x00,0x00,0x00,0xFE,0xCC,0x18, // -z- + 0x30,0x60,0xC6,0xFE,0x00,0x00,0x00,0x00}}, + + {8, 16, {0x00,0x00,0x0E,0x18,0x18,0x18,0x70,0x18, // -{- + 0x18,0x18,0x18,0x0E,0x00,0x00,0x00,0x00}}, + + {8, 16, {0x00,0x00,0x18,0x18,0x18,0x18,0x00,0x18, // -|- + 0x18,0x18,0x18,0x18,0x00,0x00,0x00,0x00}}, + + {8, 16, {0x00,0x00,0x70,0x18,0x18,0x18,0x0E,0x18, // -}- + 0x18,0x18,0x18,0x70,0x00,0x00,0x00,0x00}}, + + {8, 16, {0x00,0x00,0x76,0xDC,0x00,0x00,0x00,0x00, // -~- + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}}, + + {8, 16, {0x00,0x00,0x00,0x00,0x10,0x38,0x6C,0xC6, // -- + 0xC6,0xC6,0xFE,0x00,0x00,0x00,0x00,0x00}}, +}; + + +const unsigned char font_ascii_8x16[] = +{ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // - - + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + + 0x00,0x00,0x18,0x3C,0x3C,0x3C,0x18,0x18, // -!- + 0x18,0x00,0x18,0x18,0x00,0x00,0x00,0x00, + + 0x00,0x66,0x66,0x66,0x24,0x00,0x00,0x00, // -"- + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + + 0x00,0x00,0x00,0x6C,0x6C,0xFE,0x6C,0x6C, // -#- + 0x6C,0xFE,0x6C,0x6C,0x00,0x00,0x00,0x00, + + 0x18,0x18,0x7C,0xC6,0xC2,0xC0,0x7C,0x06, // -$- + 0x86,0xC6,0x7C,0x18,0x18,0x00,0x00,0x00, + + 0x00,0x00,0x00,0x00,0xC2,0xC6,0x0C,0x18, // -%- + 0x30,0x60,0xC6,0x86,0x00,0x00,0x00,0x00, + + 0x00,0x00,0x38,0x6C,0x6C,0x38,0x76,0xDC, // -&- + 0xCC,0xCC,0xCC,0x76,0x00,0x00,0x00,0x00, + + 0x00,0x30,0x30,0x30,0x60,0x00,0x00,0x00, // -'- + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + + 0x00,0x00,0x0C,0x18,0x30,0x30,0x30,0x30, // -(- + 0x30,0x30,0x18,0x0C,0x00,0x00,0x00,0x00, + + 0x00,0x00,0x30,0x18,0x0C,0x0C,0x0C,0x0C, // -)- + 0x0C,0x0C,0x18,0x30,0x00,0x00,0x00,0x00, + + 0x00,0x00,0x00,0x00,0x00,0x66,0x3C,0xFF, // -*- + 0x3C,0x66,0x00,0x00,0x00,0x00,0x00,0x00, + + 0x00,0x00,0x00,0x00,0x00,0x18,0x18,0x7E, // -+- + 0x18,0x18,0x00,0x00,0x00,0x00,0x00,0x00, + + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // -,- + 0x00,0x18,0x18,0x18,0x30,0x00,0x00,0x00, + + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFE, // --- + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // -.- + 0x00,0x00,0x18,0x18,0x00,0x00,0x00,0x00, + + 0x00,0x00,0x00,0x00,0x02,0x06,0x0C,0x18, // -/- + 0x30,0x60,0xC0,0x80,0x00,0x00,0x00,0x00, + + 0x00,0x00,0x7C,0xC6,0xC6,0xCE,0xD6,0xD6, // -0- + 0xE6,0xC6,0xC6,0x7C,0x00,0x00,0x00,0x00, + + 0x00,0x00,0x18,0x38,0x78,0x18,0x18,0x18, // -1- + 0x18,0x18,0x18,0x7E,0x00,0x00,0x00,0x00, + + 0x00,0x00,0x7C,0xC6,0x06,0x0C,0x18,0x30, // -2- + 0x60,0xC0,0xC6,0xFE,0x00,0x00,0x00,0x00, + + 0x00,0x00,0x7C,0xC6,0x06,0x06,0x3C,0x06, // -3- + 0x06,0x06,0xC6,0x7C,0x00,0x00,0x00,0x00, + + 0x00,0x00,0x0C,0x1C,0x3C,0x6C,0xCC,0xFE, // -4- + 0x0C,0x0C,0x0C,0x1E,0x00,0x00,0x00,0x00, + + 0x00,0x00,0xFE,0xC0,0xC0,0xC0,0xFC,0x0E, // -5- + 0x06,0x06,0xC6,0x7C,0x00,0x00,0x00,0x00, + + 0x00,0x00,0x38,0x60,0xC0,0xC0,0xFC,0xC6, // -6- + 0xC6,0xC6,0xC6,0x7C,0x00,0x00,0x00,0x00, + + 0x00,0x00,0xFE,0xC6,0x06,0x06,0x0C,0x18, // -7- + 0x30,0x30,0x30,0x30,0x00,0x00,0x00,0x00, + + 0x00,0x00,0x7C,0xC6,0xC6,0xC6,0x7C,0xC6, // -8- + 0xC6,0xC6,0xC6,0x7C,0x00,0x00,0x00,0x00, + + 0x00,0x00,0x7C,0xC6,0xC6,0xC6,0x7E,0x06, // -9- + 0x06,0x06,0x0C,0x78,0x00,0x00,0x00,0x00, + + 0x00,0x00,0x00,0x00,0x18,0x18,0x00,0x00, // -:- + 0x00,0x18,0x18,0x00,0x00,0x00,0x00,0x00, + + 0x00,0x00,0x00,0x00,0x18,0x18,0x00,0x00, // -;- + 0x00,0x18,0x18,0x30,0x00,0x00,0x00,0x00, + + 0x00,0x00,0x00,0x06,0x0C,0x18,0x30,0x60, // -<- + 0x30,0x18,0x0C,0x06,0x00,0x00,0x00,0x00, + + 0x00,0x00,0x00,0x00,0x00,0x00,0xFE,0x00, // -=- + 0x00,0xFE,0x00,0x00,0x00,0x00,0x00,0x00, + + 0x00,0x00,0x00,0x60,0x30,0x18,0x0C,0x06, // ->- + 0x0C,0x18,0x30,0x60,0x00,0x00,0x00,0x00, + + 0x00,0x00,0x7C,0xC6,0xC6,0x0C,0x18,0x18, // -?- + 0x18,0x00,0x18,0x18,0x00,0x00,0x00,0x00, + + 0x00,0x00,0x00,0x7C,0xC6,0xC6,0xDE,0xDE, // -@- + 0xDE,0xDC,0xC0,0x7C,0x00,0x00,0x00,0x00, + + 0x00,0x00,0x10,0x38,0x6C,0xC6,0xC6,0xFE, // -A- + 0xC6,0xC6,0xC6,0xC6,0x00,0x00,0x00,0x00, + + 0x00,0x00,0xFC,0x66,0x66,0x66,0x7C,0x66, // -B- + 0x66,0x66,0x66,0xFC,0x00,0x00,0x00,0x00, + + 0x00,0x00,0x3C,0x66,0xC2,0xC0,0xC0,0xC0, // -C- + 0xC0,0xC2,0x66,0x3C,0x00,0x00,0x00,0x00, + + 0x00,0x00,0xF8,0x6C,0x66,0x66,0x66,0x66, // -D- + 0x66,0x66,0x6C,0xF8,0x00,0x00,0x00,0x00, + + 0x00,0x00,0xFE,0x66,0x62,0x68,0x78,0x68, // -E- + 0x60,0x62,0x66,0xFE,0x00,0x00,0x00,0x00, + + 0x00,0x00,0xFE,0x66,0x62,0x68,0x78,0x68, // -F- + 0x60,0x60,0x60,0xF0,0x00,0x00,0x00,0x00, + + 0x00,0x00,0x3C,0x66,0xC2,0xC0,0xC0,0xDE, // -G- + 0xC6,0xC6,0x66,0x3A,0x00,0x00,0x00,0x00, + + 0x00,0x00,0xC6,0xC6,0xC6,0xC6,0xFE,0xC6, // -H- + 0xC6,0xC6,0xC6,0xC6,0x00,0x00,0x00,0x00, + + 0x00,0x00,0x3C,0x18,0x18,0x18,0x18,0x18, // -I- + 0x18,0x18,0x18,0x3C,0x00,0x00,0x00,0x00, + + 0x00,0x00,0x1E,0x0C,0x0C,0x0C,0x0C,0x0C, // -J- + 0xCC,0xCC,0xCC,0x78,0x00,0x00,0x00,0x00, + + 0x00,0x00,0xE6,0x66,0x6C,0x6C,0x78,0x78, // -K- + 0x6C,0x66,0x66,0xE6,0x00,0x00,0x00,0x00, + + 0x00,0x00,0xF0,0x60,0x60,0x60,0x60,0x60, // -L- + 0x60,0x62,0x66,0xFE,0x00,0x00,0x00,0x00, + + 0x00,0x00,0xC6,0xEE,0xFE,0xFE,0xD6,0xC6, // -M- + 0xC6,0xC6,0xC6,0xC6,0x00,0x00,0x00,0x00, + + 0x00,0x00,0xC6,0xE6,0xF6,0xFE,0xDE,0xCE, // -N- + 0xC6,0xC6,0xC6,0xC6,0x00,0x00,0x00,0x00, + + 0x00,0x00,0x38,0x6C,0xC6,0xC6,0xC6,0xC6, // -O- + 0xC6,0xC6,0x6C,0x38,0x00,0x00,0x00,0x00, + + 0x00,0x00,0xFC,0x66,0x66,0x66,0x7C,0x60, // -P- + 0x60,0x60,0x60,0xF0,0x00,0x00,0x00,0x00, + + 0x00,0x00,0x7C,0xC6,0xC6,0xC6,0xC6,0xC6, // -Q- + 0xC6,0xD6,0xDE,0x7C,0x0C,0x0E,0x00,0x00, + + 0x00,0x00,0xFC,0x66,0x66,0x66,0x7C,0x6C, // -R- + 0x66,0x66,0x66,0xE6,0x00,0x00,0x00,0x00, + + 0x00,0x00,0x7C,0xC6,0xC6,0x60,0x38,0x0C, // -S- + 0x06,0xC6,0xC6,0x7C,0x00,0x00,0x00,0x00, + + 0x00,0x00,0x7E,0x7E,0x5A,0x18,0x18,0x18, // -T- + 0x18,0x18,0x18,0x3C,0x00,0x00,0x00,0x00, + + 0x00,0x00,0xC6,0xC6,0xC6,0xC6,0xC6,0xC6, // -U- + 0xC6,0xC6,0xC6,0x7C,0x00,0x00,0x00,0x00, + + 0x00,0x00,0xC6,0xC6,0xC6,0xC6,0xC6,0xC6, // -V- + 0xC6,0x6C,0x38,0x10,0x00,0x00,0x00,0x00, + + 0x00,0x00,0xC6,0xC6,0xC6,0xC6,0xC6,0xD6, // -W- + 0xD6,0xFE,0x6C,0x6C,0x00,0x00,0x00,0x00, + + 0x00,0x00,0xC6,0xC6,0x6C,0x6C,0x38,0x38, // -X- + 0x6C,0x6C,0xC6,0xC6,0x00,0x00,0x00,0x00, + + 0x00,0x00,0x66,0x66,0x66,0x66,0x3C,0x18, // -Y- + 0x18,0x18,0x18,0x3C,0x00,0x00,0x00,0x00, + + 0x00,0x00,0xFE,0xC6,0x86,0x0C,0x18,0x30, // -Z- + 0x60,0xC2,0xC6,0xFE,0x00,0x00,0x00,0x00, + + 0x00,0x00,0x3C,0x30,0x30,0x30,0x30,0x30, // -[- + 0x30,0x30,0x30,0x3C,0x00,0x00,0x00,0x00, + + 0x00,0x00,0x00,0x80,0xC0,0xE0,0x70,0x38, // -\- + 0x1C,0x0E,0x06,0x02,0x00,0x00,0x00,0x00, + + 0x00,0x00,0x3C,0x0C,0x0C,0x0C,0x0C,0x0C, // -]- + 0x0C,0x0C,0x0C,0x3C,0x00,0x00,0x00,0x00, + + 0x10,0x38,0x6C,0xC6,0x00,0x00,0x00,0x00, // -^- + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // -_- + 0x00,0x00,0x00,0x00,0x00,0xFF,0x00,0x00, + + 0x30,0x30,0x18,0x00,0x00,0x00,0x00,0x00, // -`- + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + + 0x00,0x00,0x00,0x00,0x00,0x78,0x0C,0x7C, // -a- + 0xCC,0xCC,0xCC,0x76,0x00,0x00,0x00,0x00, + + 0x00,0x00,0xE0,0x60,0x60,0x78,0x6C,0x66, // -b- + 0x66,0x66,0x66,0xDC,0x00,0x00,0x00,0x00, + + 0x00,0x00,0x00,0x00,0x00,0x7C,0xC6,0xC0, // -c- + 0xC0,0xC0,0xC6,0x7C,0x00,0x00,0x00,0x00, + + 0x00,0x00,0x1C,0x0C,0x0C,0x3C,0x6C,0xCC, // -d- + 0xCC,0xCC,0xCC,0x76,0x00,0x00,0x00,0x00, + + 0x00,0x00,0x00,0x00,0x00,0x7C,0xC6,0xFE, // -e- + 0xC0,0xC0,0xC6,0x7C,0x00,0x00,0x00,0x00, + + 0x00,0x00,0x38,0x6C,0x64,0x60,0xF0,0x60, // -f- + 0x60,0x60,0x60,0xF0,0x00,0x00,0x00,0x00, + + 0x00,0x00,0x00,0x00,0x00,0x76,0xCC,0xCC, // -g- + 0xCC,0xCC,0xCC,0x7C,0x0C,0xCC,0x78,0x00, + + 0x00,0x00,0xE0,0x60,0x60,0x6C,0x76,0x66, // -h- + 0x66,0x66,0x66,0xE6,0x00,0x00,0x00,0x00, + + 0x00,0x00,0x18,0x18,0x00,0x38,0x18,0x18, // -i- + 0x18,0x18,0x18,0x3C,0x00,0x00,0x00,0x00, + + 0x00,0x00,0x06,0x06,0x00,0x0E,0x06,0x06, // -j- + 0x06,0x06,0x06,0x06,0x66,0x66,0x3C,0x00, + + 0x00,0x00,0xE0,0x60,0x60,0x66,0x6C,0x78, // -k- + 0x78,0x6C,0x66,0xE6,0x00,0x00,0x00,0x00, + + 0x00,0x00,0x38,0x18,0x18,0x18,0x18,0x18, // -l- + 0x18,0x18,0x18,0x3C,0x00,0x00,0x00,0x00, + + 0x00,0x00,0x00,0x00,0x00,0xEC,0xFE,0xD6, // -m- + 0xD6,0xD6,0xD6,0xD6,0x00,0x00,0x00,0x00, + + 0x00,0x00,0x00,0x00,0x00,0xDC,0x66,0x66, // -n- + 0x66,0x66,0x66,0x66,0x00,0x00,0x00,0x00, + + 0x00,0x00,0x00,0x00,0x00,0x7C,0xC6,0xC6, // -o- + 0xC6,0xC6,0xC6,0x7C,0x00,0x00,0x00,0x00, + + 0x00,0x00,0x00,0x00,0x00,0xDC,0x66,0x66, // -p- + 0x66,0x66,0x66,0x7C,0x60,0x60,0xF0,0x00, + + 0x00,0x00,0x00,0x00,0x00,0x76,0xCC,0xCC, // -q- + 0xCC,0xCC,0xCC,0x7C,0x0C,0x0C,0x1E,0x00, + + 0x00,0x00,0x00,0x00,0x00,0xDC,0x76,0x62, // -r- + 0x60,0x60,0x60,0xF0,0x00,0x00,0x00,0x00, + + 0x00,0x00,0x00,0x00,0x00,0x7C,0xC6,0x60, // -s- + 0x38,0x0C,0xC6,0x7C,0x00,0x00,0x00,0x00, + + 0x00,0x00,0x10,0x30,0x30,0xFC,0x30,0x30, // -t- + 0x30,0x30,0x36,0x1C,0x00,0x00,0x00,0x00, + + 0x00,0x00,0x00,0x00,0x00,0xCC,0xCC,0xCC, // -u- + 0xCC,0xCC,0xCC,0x76,0x00,0x00,0x00,0x00, + + 0x00,0x00,0x00,0x00,0x00,0x66,0x66,0x66, // -v- + 0x66,0x66,0x3C,0x18,0x00,0x00,0x00,0x00, + + 0x00,0x00,0x00,0x00,0x00,0xC6,0xC6,0xC6, // -w- + 0xD6,0xD6,0xFE,0x6C,0x00,0x00,0x00,0x00, + + 0x00,0x00,0x00,0x00,0x00,0xC6,0x6C,0x38, // -x- + 0x38,0x38,0x6C,0xC6,0x00,0x00,0x00,0x00, + + 0x00,0x00,0x00,0x00,0x00,0xC6,0xC6,0xC6, // -y- + 0xC6,0xC6,0xC6,0x7E,0x06,0x0C,0xF8,0x00, + + 0x00,0x00,0x00,0x00,0x00,0xFE,0xCC,0x18, // -z- + 0x30,0x60,0xC6,0xFE,0x00,0x00,0x00,0x00, + + 0x00,0x00,0x0E,0x18,0x18,0x18,0x70,0x18, // -{- + 0x18,0x18,0x18,0x0E,0x00,0x00,0x00,0x00, + + 0x00,0x00,0x18,0x18,0x18,0x18,0x00,0x18, // -|- + 0x18,0x18,0x18,0x18,0x00,0x00,0x00,0x00, + + 0x00,0x00,0x70,0x18,0x18,0x18,0x0E,0x18, // -}- + 0x18,0x18,0x18,0x70,0x00,0x00,0x00,0x00, + + 0x00,0x00,0x76,0xDC,0x00,0x00,0x00,0x00, // -~- + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + + 0x00,0x00,0x00,0x00,0x10,0x38,0x6C,0xC6, // -␡- + 0xC6,0xC6,0xFE,0x00,0x00,0x00,0x00,0x00, +}; diff --git a/m5stack/cmodules/omv/imlib/font.h b/m5stack/cmodules/omv/imlib/font.h old mode 100755 new mode 100644 index 7f2da1e7..3a22b74d --- a/m5stack/cmodules/omv/imlib/font.h +++ b/m5stack/cmodules/omv/imlib/font.h @@ -1,22 +1,20 @@ -#ifndef __FONT_H -#define __FONT_H - - -#include - -typedef struct { - int w; - int h; - uint8_t data[16]; -} glyph_t; - -extern const glyph_t font[95]; - - -extern const unsigned char font_ascii_8x16[95]; -extern const uint8_t font_unicode_16x16_start[] asm("_binary_font_unicode_16x16_bin_start"); -extern const uint8_t font_unicode_16x16_end[] asm("_binary_font_unicode_16x16_bin_end"); - -#endif // __FONT_H - - +#ifndef __FONT_H +#define __FONT_H + + +#include + +typedef struct { + int w; + int h; + uint8_t data[16]; +} glyph_t; + +extern const glyph_t font[95]; + + +extern const unsigned char font_ascii_8x16[95]; +extern const uint8_t font_unicode_16x16_start[] asm ("_binary_font_unicode_16x16_bin_start"); +extern const uint8_t font_unicode_16x16_end[] asm ("_binary_font_unicode_16x16_bin_end"); + +#endif // __FONT_H diff --git a/m5stack/cmodules/omv/imlib/imlib.c b/m5stack/cmodules/omv/imlib/imlib.c old mode 100755 new mode 100644 index d933e795..0f08ebf4 --- a/m5stack/cmodules/omv/imlib/imlib.c +++ b/m5stack/cmodules/omv/imlib/imlib.c @@ -1,190 +1,188 @@ -/* - * SPDX-License-Identifier: MIT - * - * Copyright (C) 2013-2024 OpenMV, LLC. - * Copyright (c) 2024 M5Stack Technology CO LTD - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * Image library. - */ -#include "imlib.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include "fmath.h" - - - -//======================================================================================= -// Point Stuff -//======================================================================================= -void point_init(point_t *ptr, int x, int y) { - ptr->x = x; - ptr->y = y; -} - -void point_copy(point_t *dst, point_t *src) { - memcpy(dst, src, sizeof(point_t)); -} - -bool point_equal_fast(point_t *ptr0, point_t *ptr1) { - return !memcmp(ptr0, ptr1, sizeof(point_t)); -} - -int point_quadrance(point_t *ptr0, point_t *ptr1) { - int delta_x = ptr0->x - ptr1->x; - int delta_y = ptr0->y - ptr1->y; - return (delta_x * delta_x) + (delta_y * delta_y); -} - -void point_rotate(int x, int y, float r, int center_x, int center_y, int16_t *new_x, int16_t *new_y) { - x -= center_x; - y -= center_y; - *new_x = (x * cosf(r)) - (y * sinf(r)) + center_x; - *new_y = (x * sinf(r)) + (y * cosf(r)) + center_y; -} - -void point_min_area_rectangle(point_t *corners, point_t *new_corners, int corners_len) { - // Corners need to be sorted! - int i_min = 0; - int i_min_area = INT_MAX; - int i_x0 = 0, i_y0 = 0; - int i_x1 = 0, i_y1 = 0; - int i_x2 = 0, i_y2 = 0; - int i_x3 = 0, i_y3 = 0; - float i_r = 0; - - // This algorithm aligns the 4 edges produced by the 4 corners to the x axis and then computes the - // min area rect for each alignment. The smallest rect is chosen and then re-rotated and returned. - for (int i = 0; i < corners_len; i++) { - int16_t x0 = corners[i].x, y0 = corners[i].y; - int x_diff = corners[(i + 1) % corners_len].x - corners[i].x; - int y_diff = corners[(i + 1) % corners_len].y - corners[i].y; - float r = -fast_atan2f(y_diff, x_diff); - - int16_t x1[corners_len - 1]; - int16_t y1[corners_len - 1]; - for (int j = 0, jj = corners_len - 1; j < jj; j++) { - point_rotate(corners[(i + j + 1) % corners_len].x, corners[(i + j + 1) % corners_len].y, r, x0, y0, x1 + j, y1 + j); - } - - int minx = x0; - int maxx = x0; - int miny = y0; - int maxy = y0; - for (int j = 0, jj = corners_len - 1; j < jj; j++) { - minx = IM_MIN(minx, x1[j]); - maxx = IM_MAX(maxx, x1[j]); - miny = IM_MIN(miny, y1[j]); - maxy = IM_MAX(maxy, y1[j]); - } - - int area = (maxx - minx + 1) * (maxy - miny + 1); - if (area < i_min_area) { - i_min = i; - i_min_area = area; - i_x0 = minx, i_y0 = miny; - i_x1 = maxx, i_y1 = miny; - i_x2 = maxx, i_y2 = maxy; - i_x3 = minx, i_y3 = maxy; - i_r = r; - } - } - - point_rotate(i_x0, i_y0, -i_r, corners[i_min].x, corners[i_min].y, &new_corners[0].x, &new_corners[0].y); - point_rotate(i_x1, i_y1, -i_r, corners[i_min].x, corners[i_min].y, &new_corners[1].x, &new_corners[1].y); - point_rotate(i_x2, i_y2, -i_r, corners[i_min].x, corners[i_min].y, &new_corners[2].x, &new_corners[2].y); - point_rotate(i_x3, i_y3, -i_r, corners[i_min].x, corners[i_min].y, &new_corners[3].x, &new_corners[3].y); -} - - - -//======================================================================================= -// Line Stuff -//======================================================================================= - - -// http://www.skytopia.com/project/articles/compsci/clipping.html -bool lb_clip_line(line_t *l, int x, int y, int w, int h) { - // line is drawn if this returns true - int xdelta = l->x2 - l->x1, ydelta = l->y2 - l->y1, p[4], q[4]; - float umin = 0, umax = 1; - - p[0] = -(xdelta); - p[1] = +(xdelta); - p[2] = -(ydelta); - p[3] = +(ydelta); - - q[0] = l->x1 - (x); - q[1] = (x + w - 1) - l->x1; - q[2] = l->y1 - (y); - q[3] = (y + h - 1) - l->y1; - - for (int i = 0; i < 4; i++) { - if (p[i]) { - float u = ((float) q[i]) / ((float) p[i]); - - if (p[i] < 0) { - // outside to inside - if (u > umax) { - return false; - } - if (u > umin) { - umin = u; - } - } - - if (p[i] > 0) { - // inside to outside - if (u < umin) { - return false; - } - if (u < umax) { - umax = u; - } - } - - } else if (q[i] < 0) { - return false; - } - } - - if (umax < umin) { - return false; - } - - int x1_c = l->x1 + (xdelta * umin); - int y1_c = l->y1 + (ydelta * umin); - int x2_c = l->x1 + (xdelta * umax); - int y2_c = l->y1 + (ydelta * umax); - l->x1 = x1_c; - l->y1 = y1_c; - l->x2 = x2_c; - l->y2 = y2_c; - - return true; -} - - \ No newline at end of file +/* + * SPDX-License-Identifier: MIT + * + * Copyright (C) 2013-2024 OpenMV, LLC. + * Copyright (c) 2024 M5Stack Technology CO LTD + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * Image library. + */ +#include "imlib.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include "fmath.h" + + + +// ======================================================================================= +// Point Stuff +// ======================================================================================= +void point_init(point_t *ptr, int x, int y) { + ptr->x = x; + ptr->y = y; +} + +void point_copy(point_t *dst, point_t *src) { + memcpy(dst, src, sizeof(point_t)); +} + +bool point_equal_fast(point_t *ptr0, point_t *ptr1) { + return !memcmp(ptr0, ptr1, sizeof(point_t)); +} + +int point_quadrance(point_t *ptr0, point_t *ptr1) { + int delta_x = ptr0->x - ptr1->x; + int delta_y = ptr0->y - ptr1->y; + return (delta_x * delta_x) + (delta_y * delta_y); +} + +void point_rotate(int x, int y, float r, int center_x, int center_y, int16_t *new_x, int16_t *new_y) { + x -= center_x; + y -= center_y; + *new_x = (x * cosf(r)) - (y * sinf(r)) + center_x; + *new_y = (x * sinf(r)) + (y * cosf(r)) + center_y; +} + +void point_min_area_rectangle(point_t *corners, point_t *new_corners, int corners_len) { + // Corners need to be sorted! + int i_min = 0; + int i_min_area = INT_MAX; + int i_x0 = 0, i_y0 = 0; + int i_x1 = 0, i_y1 = 0; + int i_x2 = 0, i_y2 = 0; + int i_x3 = 0, i_y3 = 0; + float i_r = 0; + + // This algorithm aligns the 4 edges produced by the 4 corners to the x axis and then computes the + // min area rect for each alignment. The smallest rect is chosen and then re-rotated and returned. + for (int i = 0; i < corners_len; i++) { + int16_t x0 = corners[i].x, y0 = corners[i].y; + int x_diff = corners[(i + 1) % corners_len].x - corners[i].x; + int y_diff = corners[(i + 1) % corners_len].y - corners[i].y; + float r = -fast_atan2f(y_diff, x_diff); + + int16_t x1[corners_len - 1]; + int16_t y1[corners_len - 1]; + for (int j = 0, jj = corners_len - 1; j < jj; j++) { + point_rotate(corners[(i + j + 1) % corners_len].x, corners[(i + j + 1) % corners_len].y, r, x0, y0, x1 + j, y1 + j); + } + + int minx = x0; + int maxx = x0; + int miny = y0; + int maxy = y0; + for (int j = 0, jj = corners_len - 1; j < jj; j++) { + minx = IM_MIN(minx, x1[j]); + maxx = IM_MAX(maxx, x1[j]); + miny = IM_MIN(miny, y1[j]); + maxy = IM_MAX(maxy, y1[j]); + } + + int area = (maxx - minx + 1) * (maxy - miny + 1); + if (area < i_min_area) { + i_min = i; + i_min_area = area; + i_x0 = minx, i_y0 = miny; + i_x1 = maxx, i_y1 = miny; + i_x2 = maxx, i_y2 = maxy; + i_x3 = minx, i_y3 = maxy; + i_r = r; + } + } + + point_rotate(i_x0, i_y0, -i_r, corners[i_min].x, corners[i_min].y, &new_corners[0].x, &new_corners[0].y); + point_rotate(i_x1, i_y1, -i_r, corners[i_min].x, corners[i_min].y, &new_corners[1].x, &new_corners[1].y); + point_rotate(i_x2, i_y2, -i_r, corners[i_min].x, corners[i_min].y, &new_corners[2].x, &new_corners[2].y); + point_rotate(i_x3, i_y3, -i_r, corners[i_min].x, corners[i_min].y, &new_corners[3].x, &new_corners[3].y); +} + + + +// ======================================================================================= +// Line Stuff +// ======================================================================================= + + +// http://www.skytopia.com/project/articles/compsci/clipping.html +bool lb_clip_line(line_t *l, int x, int y, int w, int h) { + // line is drawn if this returns true + int xdelta = l->x2 - l->x1, ydelta = l->y2 - l->y1, p[4], q[4]; + float umin = 0, umax = 1; + + p[0] = -(xdelta); + p[1] = +(xdelta); + p[2] = -(ydelta); + p[3] = +(ydelta); + + q[0] = l->x1 - (x); + q[1] = (x + w - 1) - l->x1; + q[2] = l->y1 - (y); + q[3] = (y + h - 1) - l->y1; + + for (int i = 0; i < 4; i++) { + if (p[i]) { + float u = ((float)q[i]) / ((float)p[i]); + + if (p[i] < 0) { + // outside to inside + if (u > umax) { + return false; + } + if (u > umin) { + umin = u; + } + } + + if (p[i] > 0) { + // inside to outside + if (u < umin) { + return false; + } + if (u < umax) { + umax = u; + } + } + + } else if (q[i] < 0) { + return false; + } + } + + if (umax < umin) { + return false; + } + + int x1_c = l->x1 + (xdelta * umin); + int y1_c = l->y1 + (ydelta * umin); + int x2_c = l->x1 + (xdelta * umax); + int y2_c = l->y1 + (ydelta * umax); + l->x1 = x1_c; + l->y1 = y1_c; + l->x2 = x2_c; + l->y2 = y2_c; + + return true; +} diff --git a/m5stack/cmodules/omv/imlib/imlib.h b/m5stack/cmodules/omv/imlib/imlib.h old mode 100755 new mode 100644 index 5d0c2981..612e074c --- a/m5stack/cmodules/omv/imlib/imlib.h +++ b/m5stack/cmodules/omv/imlib/imlib.h @@ -1,783 +1,777 @@ -#pragma once - -#include "esp_err.h" -#include "esp_log.h" -#include -#include "fmath.h" - - -#ifdef __cplusplus -extern "C" { -#endif - - - -#define IM_LOG2_2(x) (((x) & 0x2ULL) ? (2) : 1) // NO ({ ... }) ! -#define IM_LOG2_4(x) (((x) & 0xCULL) ? (2 + IM_LOG2_2((x) >> 2)) : IM_LOG2_2(x)) // NO ({ ... }) ! -#define IM_LOG2_8(x) (((x) & 0xF0ULL) ? (4 + IM_LOG2_4((x) >> 4)) : IM_LOG2_4(x)) // NO ({ ... }) ! -#define IM_LOG2_16(x) (((x) & 0xFF00ULL) ? (8 + IM_LOG2_8((x) >> 8)) : IM_LOG2_8(x)) // NO ({ ... }) ! -#define IM_LOG2_32(x) (((x) & 0xFFFF0000ULL) ? (16 + IM_LOG2_16((x) >> 16)) : IM_LOG2_16(x)) // NO ({ ... }) ! -#define IM_LOG2(x) (((x) & 0xFFFFFFFF00000000ULL) ? (32 + IM_LOG2_32((x) >> 32)) : IM_LOG2_32(x)) // NO ({ ... }) ! - -#define IM_IS_SIGNED(a) (__builtin_types_compatible_p(__typeof__(a), signed) || \ - __builtin_types_compatible_p(__typeof__(a), signed long)) -#define IM_IS_UNSIGNED(a) (__builtin_types_compatible_p(__typeof__(a), unsigned) || \ - __builtin_types_compatible_p(__typeof__(a), unsigned long)) -#define IM_SIGN_COMPARE(a, b) ((IM_IS_SIGNED(a) && IM_IS_UNSIGNED(b)) || \ - (IM_IS_SIGNED(b) && IM_IS_UNSIGNED(a))) - -#define IM_MAX(a, b) \ - ({__typeof__ (a) _a = (a); __typeof__ (b) _b = (b); \ - __builtin_choose_expr(IM_SIGN_COMPARE(_a, _b), (void) 0, (_a > _b ? _a : _b)); }) - -#define IM_MIN(a, b) \ - ({__typeof__ (a) _a = (a); __typeof__ (b) _b = (b); \ - __builtin_choose_expr(IM_SIGN_COMPARE(_a, _b), (void) 0, (_a < _b ? _a : _b)); }) - -#define IM_CLAMP(x, min, max) IM_MAX(IM_MIN((x), (max)), (min)) - -#define IM_DIV(a, b) ({ __typeof__ (a) _a = (a); __typeof__ (b) _b = (b); _b ? (_a / _b) : 0; }) -#define IM_MOD(a, b) ({ __typeof__ (a) _a = (a); __typeof__ (b) _b = (b); _b ? (_a % _b) : 0; }) - -#define INT8_T_BITS (sizeof(int8_t) * 8) -#define INT8_T_MASK (INT8_T_BITS - 1) -#define INT8_T_SHIFT IM_LOG2(INT8_T_MASK) - -#define INT16_T_BITS (sizeof(int16_t) * 8) -#define INT16_T_MASK (INT16_T_BITS - 1) -#define INT16_T_SHIFT IM_LOG2(INT16_T_MASK) - -#define INT32_T_BITS (sizeof(int32_t) * 8) -#define INT32_T_MASK (INT32_T_BITS - 1) -#define INT32_T_SHIFT IM_LOG2(INT32_T_MASK) - -#define INT64_T_BITS (sizeof(int64_t) * 8) -#define INT64_T_MASK (INT64_T_BITS - 1) -#define INT64_T_SHIFT IM_LOG2(INT64_T_MASK) - -#define UINT8_T_BITS (sizeof(uint8_t) * 8) -#define UINT8_T_MASK (UINT8_T_BITS - 1) -#define UINT8_T_SHIFT IM_LOG2(UINT8_T_MASK) - -#define UINT16_T_BITS (sizeof(uint16_t) * 8) -#define UINT16_T_MASK (UINT16_T_BITS - 1) -#define UINT16_T_SHIFT IM_LOG2(UINT16_T_MASK) - -#define UINT32_T_BITS (sizeof(uint32_t) * 8) -#define UINT32_T_MASK (UINT32_T_BITS - 1) -#define UINT32_T_SHIFT IM_LOG2(UINT32_T_MASK) - -#define UINT64_T_BITS (sizeof(uint64_t) * 8) -#define UINT64_T_MASK (UINT64_T_BITS - 1) -#define UINT64_T_SHIFT IM_LOG2(UINT64_T_MASK) - -#define IM_DEG2RAD(x) (((x) * M_PI) / 180) -#define IM_RAD2DEG(x) (((x) * 180) / M_PI) - - -//======================================================================================= -// Point Stuff -//======================================================================================= -typedef struct point { - int16_t x; - int16_t y; -} point_t; - -void point_init(point_t *ptr, int x, int y); -void point_copy(point_t *dst, point_t *src); -bool point_equal_fast(point_t *ptr0, point_t *ptr1); -int point_quadrance(point_t *ptr0, point_t *ptr1); -void point_rotate(int x, int y, float r, int center_x, int center_y, int16_t *new_x, int16_t *new_y); -void point_min_area_rectangle(point_t *corners, point_t *new_corners, int corners_len); - - -//======================================================================================= -// Line Stuff -//======================================================================================= -typedef struct line { - int16_t x1; - int16_t y1; - int16_t x2; - int16_t y2; -} line_t; - -bool lb_clip_line(line_t *l, int x, int y, int w, int h); - - -//======================================================================================= -// Rectangle Stuff -//======================================================================================= -typedef struct rectangle { - int16_t x; - int16_t y; - int16_t w; - int16_t h; -} rectangle_t; - -typedef struct bounding_box_lnk_data { - rectangle_t rect; - float score; - int label_index; -} bounding_box_lnk_data_t; - - -//======================================================================================= -// Color Stuff -//======================================================================================= -typedef struct color_thresholds_list_lnk_data { - uint8_t LMin, LMax; // or grayscale - int8_t AMin, AMax; - int8_t BMin, BMax; -} -color_thresholds_list_lnk_data_t; - -#define COLOR_THRESHOLD_BINARY(pixel, threshold, invert) \ - ({ \ - __typeof__ (pixel) _pixel = (pixel); \ - __typeof__ (threshold) _threshold = (threshold); \ - __typeof__ (invert) _invert = (invert); \ - ((_threshold->LMin <= _pixel) && (_pixel <= _threshold->LMax)) ^ _invert; \ - }) - -#define COLOR_THRESHOLD_GRAYSCALE(pixel, threshold, invert) \ - ({ \ - __typeof__ (pixel) _pixel = (pixel); \ - __typeof__ (threshold) _threshold = (threshold); \ - __typeof__ (invert) _invert = (invert); \ - ((_threshold->LMin <= _pixel) && (_pixel <= _threshold->LMax)) ^ _invert; \ - }) - -#define COLOR_THRESHOLD_RGB565(pixel, threshold, invert) \ - ({ \ - __typeof__ (pixel) _pixel = (pixel); \ - __typeof__ (threshold) _threshold = (threshold); \ - __typeof__ (invert) _invert = (invert); \ - uint8_t _l = COLOR_RGB565_TO_L(_pixel); \ - int8_t _a = COLOR_RGB565_TO_A(_pixel); \ - int8_t _b = COLOR_RGB565_TO_B(_pixel); \ - ((_threshold->LMin <= _l) && (_l <= _threshold->LMax) && \ - (_threshold->AMin <= _a) && (_a <= _threshold->AMax) && \ - (_threshold->BMin <= _b) && (_b <= _threshold->BMax)) ^ _invert; \ - }) - -#define COLOR_BOUND_BINARY(pixel0, pixel1, threshold) \ - ({ \ - __typeof__ (pixel0) _pixel0 = (pixel0); \ - __typeof__ (pixel1) _pixel1 = (pixel1); \ - __typeof__ (threshold) _threshold = (threshold); \ - (abs(_pixel0 - _pixel1) <= _threshold); \ - }) - -#define COLOR_BOUND_GRAYSCALE(pixel0, pixel1, threshold) \ - ({ \ - __typeof__ (pixel0) _pixel0 = (pixel0); \ - __typeof__ (pixel1) _pixel1 = (pixel1); \ - __typeof__ (threshold) _threshold = (threshold); \ - (abs(_pixel0 - _pixel1) <= _threshold); \ - }) - -#define COLOR_BOUND_RGB565(pixel0, pixel1, threshold) \ - ({ \ - __typeof__ (pixel0) _pixel0 = (pixel0); \ - __typeof__ (pixel1) _pixel1 = (pixel1); \ - __typeof__ (threshold) _threshold = (threshold); \ - (abs(COLOR_RGB565_TO_R5(_pixel0) - COLOR_RGB565_TO_R5(_pixel1)) <= COLOR_RGB565_TO_R5(_threshold)) && \ - (abs(COLOR_RGB565_TO_G6(_pixel0) - COLOR_RGB565_TO_G6(_pixel1)) <= COLOR_RGB565_TO_G6(_threshold)) && \ - (abs(COLOR_RGB565_TO_B5(_pixel0) - COLOR_RGB565_TO_B5(_pixel1)) <= COLOR_RGB565_TO_B5(_threshold)); \ - }) - -#define COLOR_BINARY_MIN 0 -#define COLOR_BINARY_MAX 1 -#define COLOR_GRAYSCALE_BINARY_MIN 0x00 -#define COLOR_GRAYSCALE_BINARY_MAX 0xFF -#define COLOR_RGB565_BINARY_MIN 0x0000 -#define COLOR_RGB565_BINARY_MAX 0xFFFF - -#define COLOR_GRAYSCALE_MIN 0 -#define COLOR_GRAYSCALE_MAX 255 - -#define COLOR_R5_MIN 0 -#define COLOR_R5_MAX 31 -#define COLOR_G6_MIN 0 -#define COLOR_G6_MAX 63 -#define COLOR_B5_MIN 0 -#define COLOR_B5_MAX 31 - -#define COLOR_R8_MIN 0 -#define COLOR_R8_MAX 255 -#define COLOR_G8_MIN 0 -#define COLOR_G8_MAX 255 -#define COLOR_B8_MIN 0 -#define COLOR_B8_MAX 255 - -#define COLOR_L_MIN 0 -#define COLOR_L_MAX 100 -#define COLOR_A_MIN -128 -#define COLOR_A_MAX 127 -#define COLOR_B_MIN -128 -#define COLOR_B_MAX 127 - -#define COLOR_Y_MIN 0 -#define COLOR_Y_MAX 255 -#define COLOR_U_MIN -128 -#define COLOR_U_MAX 127 -#define COLOR_V_MIN -128 -#define COLOR_V_MAX 127 - - -//======================================================================================= -// RGB565 Stuff -//======================================================================================= -#define COLOR_RGB565_TO_R5(pixel) (((pixel) >> 11) & 0x1F) -#define COLOR_RGB565_TO_R8(pixel) \ - ({ \ - __typeof__ (pixel) __pixel = (pixel); \ - __pixel = (__pixel >> 8) & 0xF8; \ - __pixel | (__pixel >> 5); \ - }) - -#define COLOR_RGB565_TO_G6(pixel) (((pixel) >> 5) & 0x3F) -#define COLOR_RGB565_TO_G8(pixel) \ - ({ \ - __typeof__ (pixel) __pixel = (pixel); \ - __pixel = (__pixel >> 3) & 0xFC; \ - __pixel | (__pixel >> 6); \ - }) - -#define COLOR_RGB565_TO_B5(pixel) ((pixel) & 0x1F) -#define COLOR_RGB565_TO_B8(pixel) \ - ({ \ - __typeof__ (pixel) __pixel = (pixel); \ - __pixel = (__pixel << 3) & 0xF8; \ - __pixel | (__pixel >> 5); \ - }) - -#define COLOR_R5_G6_B5_TO_RGB565(r5, g6, b5) (((r5) << 11) | ((g6) << 5) | (b5)) -#define COLOR_R8_G8_B8_TO_RGB565(r8, g8, b8) ((((r8) & 0xF8) << 8) | (((g8) & 0xFC) << 3) | ((b8) >> 3)) - -#define COLOR_RGB888_TO_Y(r8, g8, b8) ((((r8) * 38) + ((g8) * 75) + ((b8) * 15)) >> 7) // 0.299R + 0.587G + 0.114B -#define COLOR_RGB565_TO_Y(rgb565) \ - ({ \ - __typeof__ (rgb565) __rgb565 = (rgb565); \ - int r = COLOR_RGB565_TO_R8(__rgb565); \ - int g = COLOR_RGB565_TO_G8(__rgb565); \ - int b = COLOR_RGB565_TO_B8(__rgb565); \ - COLOR_RGB888_TO_Y(r, g, b); \ - }) - -#define COLOR_Y_TO_RGB888(pixel) ((pixel) * 0x010101) -#define COLOR_Y_TO_RGB565(pixel) \ - ({ \ - __typeof__ (pixel) __pixel = (pixel); \ - int __rb_pixel = (__pixel >> 3) & 0x1F; \ - (__rb_pixel * 0x0801) + ((__pixel << 3) & 0x7E0); \ - }) - -#define COLOR_RGB888_TO_U(r8, g8, b8) ((((r8) * -21) - ((g8) * 43) + ((b8) * 64)) >> 7) // -0.168736R - 0.331264G + 0.5B -#define COLOR_RGB565_TO_U(rgb565) \ - ({ \ - __typeof__ (rgb565) __rgb565 = (rgb565); \ - int r = COLOR_RGB565_TO_R8(__rgb565); \ - int g = COLOR_RGB565_TO_G8(__rgb565); \ - int b = COLOR_RGB565_TO_B8(__rgb565); \ - COLOR_RGB888_TO_U(r, g, b); \ - }) - -#define COLOR_RGB888_TO_V(r8, g8, b8) ((((r8) * 64) - ((g8) * 54) - ((b8) * 10)) >> 7) // 0.5R - 0.418688G - 0.081312B -#define COLOR_RGB565_TO_V(rgb565) \ - ({ \ - __typeof__ (rgb565) __rgb565 = (rgb565); \ - int r = COLOR_RGB565_TO_R8(__rgb565); \ - int g = COLOR_RGB565_TO_G8(__rgb565); \ - int b = COLOR_RGB565_TO_B8(__rgb565); \ - COLOR_RGB888_TO_V(r, g, b); \ - }) - -extern const int8_t lab_table[196608 / 2]; - -#ifdef IMLIB_ENABLE_LAB_LUT -#define COLOR_RGB565_TO_L(pixel) lab_table[((pixel >> 1) * 3) + 0] -#define COLOR_RGB565_TO_A(pixel) lab_table[((pixel >> 1) * 3) + 1] -#define COLOR_RGB565_TO_B(pixel) lab_table[((pixel >> 1) * 3) + 2] -#else -#define COLOR_RGB565_TO_L(pixel) imlib_rgb565_to_l(pixel) -#define COLOR_RGB565_TO_A(pixel) imlib_rgb565_to_a(pixel) -#define COLOR_RGB565_TO_B(pixel) imlib_rgb565_to_b(pixel) -#endif - -#define COLOR_LAB_TO_RGB565(l, a, b) imlib_lab_to_rgb(l, a, b) -#define COLOR_YUV_TO_RGB565(y, u, v) imlib_yuv_to_rgb((y) + 128, u, v) - -#define COLOR_BINARY_TO_GRAYSCALE(pixel) ((pixel) * COLOR_GRAYSCALE_MAX) -#define COLOR_BINARY_TO_RGB565(pixel) COLOR_YUV_TO_RGB565(((pixel) ? 127 : -128), 0, 0) -#define COLOR_RGB565_TO_BINARY(pixel) (COLOR_RGB565_TO_Y(pixel) > (((COLOR_Y_MAX - COLOR_Y_MIN) / 2) + COLOR_Y_MIN)) -#define COLOR_RGB565_TO_GRAYSCALE(pixel) COLOR_RGB565_TO_Y(pixel) -#define COLOR_GRAYSCALE_TO_BINARY(pixel) ((pixel) > \ - (((COLOR_GRAYSCALE_MAX - COLOR_GRAYSCALE_MIN) / 2) + COLOR_GRAYSCALE_MIN)) -#define COLOR_GRAYSCALE_TO_RGB565(pixel) COLOR_YUV_TO_RGB565(((pixel) - 128), 0, 0) - -typedef enum { - COLOR_PALETTE_RAINBOW, - COLOR_PALETTE_IRONBOW, - COLOR_PALETTE_DEPTH, - COLOR_PALETTE_EVT_DARK, - COLOR_PALETTE_EVT_LIGHT -} color_palette_t; - -// Color palette LUTs -extern const uint16_t rainbow_table[256]; -extern const uint16_t ironbow_table[256]; -extern const uint16_t depth_table[256]; -extern const uint16_t evt_dark_table[256]; -extern const uint16_t evt_light_table[256]; - - - -//======================================================================================= -// Image Stuff -//======================================================================================= - -// Pixel format IDs. -typedef enum { - OMV_PIXFORMAT_ID_BINARY = 1, - OMV_PIXFORMAT_ID_GRAY = 2, - OMV_PIXFORMAT_ID_RGB565 = 3, - OMV_PIXFORMAT_ID_BAYER = 4, - OMV_PIXFORMAT_ID_YUV422 = 5, - OMV_PIXFORMAT_ID_JPEG = 6, - OMV_PIXFORMAT_ID_PNG = 7, - OMV_PIXFORMAT_ID_ARGB8 = 8, - /* Note: Update OMV_PIXFORMAT_IS_VALID when adding new formats */ -} omv_pixformat_id_t; - -// Pixel sub-format IDs. -typedef enum { - SUBFORMAT_ID_GRAY8 = 0, - SUBFORMAT_ID_GRAY16 = 1, - SUBFORMAT_ID_BGGR = 0, // !!! Note: Make sure bayer sub-formats don't !!! - SUBFORMAT_ID_GBRG = 1, // !!! overflow the sensor.hw_flags.bayer field !!! - SUBFORMAT_ID_GRBG = 2, - SUBFORMAT_ID_RGGB = 3, - SUBFORMAT_ID_YUV422 = 0, - SUBFORMAT_ID_YVU422 = 1, - /* Note: Update OMV_PIXFORMAT_IS_VALID when adding new formats */ -} subformat_id_t; - -// Pixel format Byte Per Pixel. -typedef enum { - OMV_PIXFORMAT_BPP_BINARY = 0, - OMV_PIXFORMAT_BPP_GRAY8 = 1, - OMV_PIXFORMAT_BPP_GRAY16 = 2, - OMV_PIXFORMAT_BPP_RGB565 = 2, - OMV_PIXFORMAT_BPP_BAYER = 1, - OMV_PIXFORMAT_BPP_YUV422 = 2, - OMV_PIXFORMAT_BPP_ARGB8 = 4, - /* Note: Update OMV_PIXFORMAT_IS_VALID when adding new formats */ -} omv_pixformat_bpp_t; - -// Pixel format flags. -#define OMV_PIXFORMAT_FLAGS_Y (1 << 28) // YUV format. -#define OMV_PIXFORMAT_FLAGS_M (1 << 27) // Mutable format. -#define OMV_PIXFORMAT_FLAGS_C (1 << 26) // Colored format. -#define OMV_PIXFORMAT_FLAGS_J (1 << 25) // Compressed format (JPEG/PNG). -#define OMV_PIXFORMAT_FLAGS_R (1 << 24) // RAW/Bayer format. -#define OMV_PIXFORMAT_FLAGS_CY (OMV_PIXFORMAT_FLAGS_C | OMV_PIXFORMAT_FLAGS_Y) -#define OMV_PIXFORMAT_FLAGS_CM (OMV_PIXFORMAT_FLAGS_C | OMV_PIXFORMAT_FLAGS_M) -#define OMV_PIXFORMAT_FLAGS_CR (OMV_PIXFORMAT_FLAGS_C | OMV_PIXFORMAT_FLAGS_R) -#define OMV_PIXFORMAT_FLAGS_CJ (OMV_PIXFORMAT_FLAGS_C | OMV_PIXFORMAT_FLAGS_J) -#define IMLIB_IMAGE_MAX_SIZE(x) ((x) & 0xFFFFFFFF) - -// *INDENT-OFF* -// Each pixel format encodes flags, pixel format id and bpp as follows: -// 31......29 28 27 26 25 24 23..........16 15...........8 7.............0 -// YF MF CF JF RF -// NOTE: Bit 31-30 must Not be used for omv_pixformat_t to be used as mp_int_t. -typedef enum { - OMV_PIXFORMAT_INVALID = (0x00000000U), - OMV_PIXFORMAT_BINARY = (OMV_PIXFORMAT_FLAGS_M | (OMV_PIXFORMAT_ID_BINARY << 16) | (0 << 8) | OMV_PIXFORMAT_BPP_BINARY ), - OMV_PIXFORMAT_GRAYSCALE = (OMV_PIXFORMAT_FLAGS_M | (OMV_PIXFORMAT_ID_GRAY << 16) | (SUBFORMAT_ID_GRAY8 << 8) | OMV_PIXFORMAT_BPP_GRAY8 ), - OMV_PIXFORMAT_RGB565 = (OMV_PIXFORMAT_FLAGS_CM | (OMV_PIXFORMAT_ID_RGB565 << 16) | (0 << 8) | OMV_PIXFORMAT_BPP_RGB565 ), - OMV_PIXFORMAT_ARGB8 = (OMV_PIXFORMAT_FLAGS_CM | (OMV_PIXFORMAT_ID_ARGB8 << 16) | (0 << 8) | OMV_PIXFORMAT_BPP_ARGB8 ), - OMV_PIXFORMAT_BAYER = (OMV_PIXFORMAT_FLAGS_CR | (OMV_PIXFORMAT_ID_BAYER << 16) | (SUBFORMAT_ID_BGGR << 8) | OMV_PIXFORMAT_BPP_BAYER ), - OMV_PIXFORMAT_BAYER_BGGR = (OMV_PIXFORMAT_FLAGS_CR | (OMV_PIXFORMAT_ID_BAYER << 16) | (SUBFORMAT_ID_BGGR << 8) | OMV_PIXFORMAT_BPP_BAYER ), - OMV_PIXFORMAT_BAYER_GBRG = (OMV_PIXFORMAT_FLAGS_CR | (OMV_PIXFORMAT_ID_BAYER << 16) | (SUBFORMAT_ID_GBRG << 8) | OMV_PIXFORMAT_BPP_BAYER ), - OMV_PIXFORMAT_BAYER_GRBG = (OMV_PIXFORMAT_FLAGS_CR | (OMV_PIXFORMAT_ID_BAYER << 16) | (SUBFORMAT_ID_GRBG << 8) | OMV_PIXFORMAT_BPP_BAYER ), - OMV_PIXFORMAT_BAYER_RGGB = (OMV_PIXFORMAT_FLAGS_CR | (OMV_PIXFORMAT_ID_BAYER << 16) | (SUBFORMAT_ID_RGGB << 8) | OMV_PIXFORMAT_BPP_BAYER ), - OMV_PIXFORMAT_YUV = (OMV_PIXFORMAT_FLAGS_CY | (OMV_PIXFORMAT_ID_YUV422 << 16) | (SUBFORMAT_ID_YUV422 << 8) | OMV_PIXFORMAT_BPP_YUV422 ), - OMV_PIXFORMAT_YUV422 = (OMV_PIXFORMAT_FLAGS_CY | (OMV_PIXFORMAT_ID_YUV422 << 16) | (SUBFORMAT_ID_YUV422 << 8) | OMV_PIXFORMAT_BPP_YUV422 ), - OMV_PIXFORMAT_YVU422 = (OMV_PIXFORMAT_FLAGS_CY | (OMV_PIXFORMAT_ID_YUV422 << 16) | (SUBFORMAT_ID_YVU422 << 8) | OMV_PIXFORMAT_BPP_YUV422 ), - OMV_PIXFORMAT_JPEG = (OMV_PIXFORMAT_FLAGS_CJ | (OMV_PIXFORMAT_ID_JPEG << 16) | (0 << 8) | 0 ), - OMV_PIXFORMAT_PNG = (OMV_PIXFORMAT_FLAGS_CJ | (OMV_PIXFORMAT_ID_PNG << 16) | (0 << 8) | 0 ), - OMV_PIXFORMAT_LAST = (0xFFFFFFFFU), -} omv_pixformat_t; -// *INDENT-ON* - - -#define OMV_PIXFORMAT_MUTABLE_ANY \ - OMV_PIXFORMAT_BINARY: \ - case OMV_PIXFORMAT_GRAYSCALE: \ - case OMV_PIXFORMAT_RGB565: \ - case OMV_PIXFORMAT_ARGB8 \ - -#define OMV_PIXFORMAT_BAYER_ANY \ - OMV_PIXFORMAT_BAYER_BGGR: \ - case OMV_PIXFORMAT_BAYER_GBRG: \ - case OMV_PIXFORMAT_BAYER_GRBG: \ - case OMV_PIXFORMAT_BAYER_RGGB \ - -#define OMV_PIXFORMAT_YUV_ANY \ - OMV_PIXFORMAT_YUV422: \ - case OMV_PIXFORMAT_YVU422 \ - -#define OMV_PIXFORMAT_COMPRESSED_ANY \ - OMV_PIXFORMAT_JPEG: \ - case OMV_PIXFORMAT_PNG \ - -#define IMLIB_OMV_PIXFORMAT_IS_VALID(x) \ - ((x == OMV_PIXFORMAT_BINARY) \ - || (x == OMV_PIXFORMAT_GRAYSCALE) \ - || (x == OMV_PIXFORMAT_RGB565) \ - || (x == OMV_PIXFORMAT_ARGB8) \ - || (x == OMV_PIXFORMAT_BAYER_BGGR) \ - || (x == OMV_PIXFORMAT_BAYER_GBRG) \ - || (x == OMV_PIXFORMAT_BAYER_GRBG) \ - || (x == OMV_PIXFORMAT_BAYER_RGGB) \ - || (x == OMV_PIXFORMAT_YUV422) \ - || (x == OMV_PIXFORMAT_YVU422) \ - || (x == OMV_PIXFORMAT_JPEG) \ - || (x == OMV_PIXFORMAT_PNG)) \ - - -#define OMV_PIXFORMAT_STRUCT \ -struct { \ - union { \ - struct { \ - uint32_t bpp :8; \ - uint32_t subfmt_id :8; \ - uint32_t pixfmt_id :8; \ - uint32_t is_bayer :1; \ - uint32_t is_compressed :1; \ - uint32_t is_color :1; \ - uint32_t is_mutable :1; \ - uint32_t is_yuv :1; \ - uint32_t /*reserved*/ :3; \ - }; \ - uint32_t pixfmt; \ - }; \ - uint32_t size; /* for compressed images */ \ -} - -typedef struct image { - int32_t w; - int32_t h; - OMV_PIXFORMAT_STRUCT; - union { - uint8_t *pixels; - uint8_t *data; - }; -} image_t; - - - -#define IMAGE_BINARY_LINE_LEN(image) (((image)->w + UINT32_T_MASK) >> UINT32_T_SHIFT) -#define IMAGE_BINARY_LINE_LEN_BYTES(image) (IMAGE_BINARY_LINE_LEN(image) * sizeof(uint32_t)) - -#define IMAGE_GRAYSCALE_LINE_LEN(image) ((image)->w) -#define IMAGE_GRAYSCALE_LINE_LEN_BYTES(image) (IMAGE_GRAYSCALE_LINE_LEN(image) * sizeof(uint8_t)) - -#define IMAGE_RGB565_LINE_LEN(image) ((image)->w) -#define IMAGE_RGB565_LINE_LEN_BYTES(image) (IMAGE_RGB565_LINE_LEN(image) * sizeof(uint16_t)) - -#define IMAGE_GET_BINARY_PIXEL(image, x, y) \ - ({ \ - __typeof__ (image) _image = (image); \ - __typeof__ (x) _x = (x); \ - __typeof__ (y) _y = (y); \ - (((uint32_t *) _image->data)[(((_image->w + UINT32_T_MASK) >> UINT32_T_SHIFT) * _y) + (_x >> UINT32_T_SHIFT)] >> \ - (_x & UINT32_T_MASK)) & 1; \ - }) - -#define IMAGE_PUT_BINARY_PIXEL(image, x, y, v) \ - ({ \ - __typeof__ (image) _image = (image); \ - __typeof__ (x) _x = (x); \ - __typeof__ (y) _y = (y); \ - __typeof__ (v) _v = (v); \ - size_t _i = (((_image->w + UINT32_T_MASK) >> UINT32_T_SHIFT) * _y) + (_x >> UINT32_T_SHIFT); \ - size_t _j = _x & UINT32_T_MASK; \ - ((uint32_t *) _image->data)[_i] = (((uint32_t *) _image->data)[_i] & (~(1 << _j))) | ((_v & 1) << _j); \ - }) - -#define IMAGE_CLEAR_BINARY_PIXEL(image, x, y) \ - ({ \ - __typeof__ (image) _image = (image); \ - __typeof__ (x) _x = (x); \ - __typeof__ (y) _y = (y); \ - ((uint32_t *) _image->data)[(((_image->w + UINT32_T_MASK) >> \ - UINT32_T_SHIFT) * _y) + (_x >> UINT32_T_SHIFT)] &= ~(1 << (_x & UINT32_T_MASK)); \ - }) - -#define IMAGE_SET_BINARY_PIXEL(image, x, y) \ - ({ \ - __typeof__ (image) _image = (image); \ - __typeof__ (x) _x = (x); \ - __typeof__ (y) _y = (y); \ - ((uint32_t *) _image->data)[(((_image->w + UINT32_T_MASK) >> UINT32_T_SHIFT) * _y) + (_x >> UINT32_T_SHIFT)] |= 1 << \ - (_x & \ - UINT32_T_MASK); \ - }) - -#define IMAGE_GET_GRAYSCALE_PIXEL(image, x, y) \ - ({ \ - __typeof__ (image) _image = (image); \ - __typeof__ (x) _x = (x); \ - __typeof__ (y) _y = (y); \ - ((uint8_t *) _image->data)[(_image->w * _y) + _x]; \ - }) - -#define IMAGE_PUT_GRAYSCALE_PIXEL(image, x, y, v) \ - ({ \ - __typeof__ (image) _image = (image); \ - __typeof__ (x) _x = (x); \ - __typeof__ (y) _y = (y); \ - __typeof__ (v) _v = (v); \ - ((uint8_t *) _image->data)[(_image->w * _y) + _x] = _v; \ - }) - -#define IMAGE_GET_RGB565_PIXEL(image, x, y) \ - ({ \ - __typeof__ (image) _image = (image); \ - __typeof__ (x) _x = (x); \ - __typeof__ (y) _y = (y); \ - ((uint16_t *) _image->data)[(_image->w * _y) + _x]; \ - }) - -#define IMAGE_PUT_RGB565_PIXEL(image, x, y, v) \ - ({ \ - __typeof__ (image) _image = (image); \ - __typeof__ (x) _x = (x); \ - __typeof__ (y) _y = (y); \ - __typeof__ (v) _v = (v); \ - ((uint16_t *) _image->data)[(_image->w * _y) + _x] = _v; \ - }) - -#define IMAGE_GET_YUV_PIXEL(image, x, y) \ - ({ \ - __typeof__ (image) _image = (image); \ - __typeof__ (x) _x = (x); \ - __typeof__ (y) _y = (y); \ - ((uint16_t *) _image->data)[(_image->w * _y) + _x]; \ - }) - -#define IMAGE_PUT_YUV_PIXEL(image, x, y, v) \ - ({ \ - __typeof__ (image) _image = (image); \ - __typeof__ (x) _x = (x); \ - __typeof__ (y) _y = (y); \ - __typeof__ (v) _v = (v); \ - ((uint16_t *) _image->data)[(_image->w * _y) + _x] = _v; \ - }) - -#define IMAGE_GET_BAYER_PIXEL(image, x, y) \ - ({ \ - __typeof__ (image) _image = (image); \ - __typeof__ (x) _x = (x); \ - __typeof__ (y) _y = (y); \ - ((uint8_t *) _image->data)[(_image->w * _y) + _x]; \ - }) - -#define IMAGE_PUT_BAYER_PIXEL(image, x, y, v) \ - ({ \ - __typeof__ (image) _image = (image); \ - __typeof__ (x) _x = (x); \ - __typeof__ (y) _y = (y); \ - __typeof__ (v) _v = (v); \ - ((uint8_t *) _image->data)[(_image->w * _y) + _x] = _v; \ - }) - -// Fast Stuff // - -#define IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(image, y) \ - ({ \ - __typeof__ (image) _image = (image); \ - __typeof__ (y) _y = (y); \ - ((uint32_t *) _image->data) + (((_image->w + UINT32_T_MASK) >> UINT32_T_SHIFT) * _y); \ - }) - -#define IMAGE_GET_BINARY_PIXEL_FAST(row_ptr, x) \ - ({ \ - __typeof__ (row_ptr) _row_ptr = (row_ptr); \ - __typeof__ (x) _x = (x); \ - (_row_ptr[_x >> UINT32_T_SHIFT] >> (_x & UINT32_T_MASK)) & 1; \ - }) - -#define IMAGE_PUT_BINARY_PIXEL_FAST(row_ptr, x, v) \ - ({ \ - __typeof__ (row_ptr) _row_ptr = (row_ptr); \ - __typeof__ (x) _x = (x); \ - __typeof__ (v) _v = (v); \ - size_t _i = _x >> UINT32_T_SHIFT; \ - size_t _j = _x & UINT32_T_MASK; \ - _row_ptr[_i] = (_row_ptr[_i] & (~(1 << _j))) | ((_v & 1) << _j); \ - }) - -#define IMAGE_CLEAR_BINARY_PIXEL_FAST(row_ptr, x) \ - ({ \ - __typeof__ (row_ptr) _row_ptr = (row_ptr); \ - __typeof__ (x) _x = (x); \ - _row_ptr[_x >> UINT32_T_SHIFT] &= ~(1 << (_x & UINT32_T_MASK)); \ - }) - -#define IMAGE_SET_BINARY_PIXEL_FAST(row_ptr, x) \ - ({ \ - __typeof__ (row_ptr) _row_ptr = (row_ptr); \ - __typeof__ (x) _x = (x); \ - _row_ptr[_x >> UINT32_T_SHIFT] |= 1 << (_x & UINT32_T_MASK); \ - }) - -#define IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(image, y) \ - ({ \ - __typeof__ (image) _image = (image); \ - __typeof__ (y) _y = (y); \ - ((uint8_t *) _image->data) + (_image->w * _y); \ - }) - -#define IMAGE_GET_GRAYSCALE_PIXEL_FAST(row_ptr, x) \ - ({ \ - __typeof__ (row_ptr) _row_ptr = (row_ptr); \ - __typeof__ (x) _x = (x); \ - _row_ptr[_x]; \ - }) - -#define IMAGE_PUT_GRAYSCALE_PIXEL_FAST(row_ptr, x, v) \ - ({ \ - __typeof__ (row_ptr) _row_ptr = (row_ptr); \ - __typeof__ (x) _x = (x); \ - __typeof__ (v) _v = (v); \ - _row_ptr[_x] = _v; \ - }) - -#define IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(image, y) \ - ({ \ - __typeof__ (image) _image = (image); \ - __typeof__ (y) _y = (y); \ - ((uint16_t *) _image->data) + (_image->w * _y); \ - }) - -#define IMAGE_GET_RGB565_PIXEL_FAST(row_ptr, x) \ - ({ \ - __typeof__ (row_ptr) _row_ptr = (row_ptr); \ - __typeof__ (x) _x = (x); \ - _row_ptr[_x]; \ - }) - -#define IMAGE_PUT_RGB565_PIXEL_FAST(row_ptr, x, v) \ - ({ \ - __typeof__ (row_ptr) _row_ptr = (row_ptr); \ - __typeof__ (x) _x = (x); \ - __typeof__ (v) _v = (v); \ - _row_ptr[_x] = _v; \ - }) - -#define IMAGE_COMPUTE_BAYER_PIXEL_ROW_PTR(image, y) \ - ({ \ - __typeof__ (image) _image = (image); \ - __typeof__ (y) _y = (y); \ - ((uint8_t *) _image->data) + (_image->w * _y); \ - }) - -#define IMAGE_COMPUTE_YUV_PIXEL_ROW_PTR(image, y) \ - ({ \ - __typeof__ (image) _image = (image); \ - __typeof__ (y) _y = (y); \ - ((uint16_t *) _image->data) + (_image->w * _y); \ - }) - - - -typedef enum { - OMV_FRAMESIZE_INVALID = 0, - // C/SIF Resolutions - OMV_FRAMESIZE_QQCIF, // 88x72 - OMV_FRAMESIZE_QCIF, // 176x144 - OMV_FRAMESIZE_CIF, // 352x288 - OMV_FRAMESIZE_QQSIF, // 88x60 - OMV_FRAMESIZE_QSIF, // 176x120 - OMV_FRAMESIZE_SIF, // 352x240 - // VGA Resolutions - OMV_FRAMESIZE_QQQQVGA, // 40x30 - OMV_FRAMESIZE_QQQVGA, // 80x60 - OMV_FRAMESIZE_QQVGA, // 160x120 - OMV_FRAMESIZE_QVGA, // 320x240 - OMV_FRAMESIZE_VGA, // 640x480 - OMV_FRAMESIZE_HQQQQVGA, // 30x20 - OMV_FRAMESIZE_HQQQVGA, // 60x40 - OMV_FRAMESIZE_HQQVGA, // 120x80 - OMV_FRAMESIZE_HQVGA, // 240x160 - OMV_FRAMESIZE_HVGA, // 480x320 - // FFT Resolutions - OMV_FRAMESIZE_64X32, // 64x32 - OMV_FRAMESIZE_64X64, // 64x64 - OMV_FRAMESIZE_128X64, // 128x64 - OMV_FRAMESIZE_128X128, // 128x128 - // Himax Resolutions - OMV_FRAMESIZE_160X160, // 160x160 - OMV_FRAMESIZE_320X320, // 320x320 - // Other - OMV_FRAMESIZE_LCD, // 128x160 - OMV_FRAMESIZE_QQVGA2, // 128x160 - OMV_FRAMESIZE_WVGA, // 720x480 - OMV_FRAMESIZE_WVGA2, // 752x480 - OMV_FRAMESIZE_SVGA, // 800x600 - OMV_FRAMESIZE_XGA, // 1024x768 - OMV_FRAMESIZE_WXGA, // 1280x768 - OMV_FRAMESIZE_SXGA, // 1280x1024 - OMV_FRAMESIZE_SXGAM, // 1280x960 - OMV_FRAMESIZE_UXGA, // 1600x1200 - OMV_FRAMESIZE_HD, // 1280x720 - OMV_FRAMESIZE_FHD, // 1920x1080 - OMV_FRAMESIZE_QHD, // 2560x1440 - OMV_FRAMESIZE_QXGA, // 2048x1536 - OMV_FRAMESIZE_WQXGA, // 2560x1600 - OMV_FRAMESIZE_WQXGA2, // 2592x1944 -} omv_framesize_t; - - - -//======================================================================================= -// draw functions -//======================================================================================= -int imlib_get_pixel(image_t *img, int x, int y); -int imlib_get_pixel_fast(image_t *img, const void *row_ptr, int x); -void imlib_set_pixel(image_t *img, int x, int y, int p); -void imlib_draw_line(image_t *img, int x0, int y0, int x1, int y1, int c, int thickness); -void imlib_draw_rectangle(image_t *img, int rx, int ry, int rw, int rh, int c, int thickness, bool fill); -void imlib_draw_circle(image_t *img, int cx, int cy, int r, int c, int thickness, bool fill); -void imlib_draw_ellipse(image_t *img, int cx, int cy, int rx, int ry, int rotation, int c, int thickness, bool fill); -void imlib_draw_string(image_t *img, - int x_off, - int y_off, - const char *str, - int c, - float scale, - int x_spacing, - int y_spacing, - bool mono_space, - int char_rotation, - bool char_hmirror, - bool char_vflip, - int string_rotation, - bool string_hmirror, - bool string_hflip); - - -#ifdef __cplusplus -} -#endif - - - - - - - +#pragma once + +#include "esp_err.h" +#include "esp_log.h" +#include +#include "fmath.h" + + +#ifdef __cplusplus +extern "C" { +#endif + + + +#define IM_LOG2_2(x) (((x) & 0x2ULL) ? (2) : 1) // NO ({ ... }) ! +#define IM_LOG2_4(x) (((x) & 0xCULL) ? (2 + IM_LOG2_2((x) >> 2)) : IM_LOG2_2(x)) // NO ({ ... }) ! +#define IM_LOG2_8(x) (((x) & 0xF0ULL) ? (4 + IM_LOG2_4((x) >> 4)) : IM_LOG2_4(x)) // NO ({ ... }) ! +#define IM_LOG2_16(x) (((x) & 0xFF00ULL) ? (8 + IM_LOG2_8((x) >> 8)) : IM_LOG2_8(x)) // NO ({ ... }) ! +#define IM_LOG2_32(x) (((x) & 0xFFFF0000ULL) ? (16 + IM_LOG2_16((x) >> 16)) : IM_LOG2_16(x)) // NO ({ ... }) ! +#define IM_LOG2(x) (((x) & 0xFFFFFFFF00000000ULL) ? (32 + IM_LOG2_32((x) >> 32)) : IM_LOG2_32(x)) // NO ({ ... }) ! + +#define IM_IS_SIGNED(a) (__builtin_types_compatible_p(__typeof__(a), signed) || \ + __builtin_types_compatible_p(__typeof__(a), signed long)) +#define IM_IS_UNSIGNED(a) (__builtin_types_compatible_p(__typeof__(a), unsigned) || \ + __builtin_types_compatible_p(__typeof__(a), unsigned long)) +#define IM_SIGN_COMPARE(a, b) ((IM_IS_SIGNED(a) && IM_IS_UNSIGNED(b)) || \ + (IM_IS_SIGNED(b) && IM_IS_UNSIGNED(a))) + +#define IM_MAX(a, b) \ + ({__typeof__ (a) _a = (a); __typeof__ (b) _b = (b); \ + __builtin_choose_expr(IM_SIGN_COMPARE(_a, _b), (void)0, (_a > _b ? _a : _b)); }) + +#define IM_MIN(a, b) \ + ({__typeof__ (a) _a = (a); __typeof__ (b) _b = (b); \ + __builtin_choose_expr(IM_SIGN_COMPARE(_a, _b), (void)0, (_a < _b ? _a : _b)); }) + +#define IM_CLAMP(x, min, max) IM_MAX(IM_MIN((x), (max)), (min)) + +#define IM_DIV(a, b) ({ __typeof__ (a) _a = (a); __typeof__ (b) _b = (b); _b ? (_a / _b) : 0; }) +#define IM_MOD(a, b) ({ __typeof__ (a) _a = (a); __typeof__ (b) _b = (b); _b ? (_a % _b) : 0; }) + +#define INT8_T_BITS (sizeof(int8_t) * 8) +#define INT8_T_MASK (INT8_T_BITS - 1) +#define INT8_T_SHIFT IM_LOG2(INT8_T_MASK) + +#define INT16_T_BITS (sizeof(int16_t) * 8) +#define INT16_T_MASK (INT16_T_BITS - 1) +#define INT16_T_SHIFT IM_LOG2(INT16_T_MASK) + +#define INT32_T_BITS (sizeof(int32_t) * 8) +#define INT32_T_MASK (INT32_T_BITS - 1) +#define INT32_T_SHIFT IM_LOG2(INT32_T_MASK) + +#define INT64_T_BITS (sizeof(int64_t) * 8) +#define INT64_T_MASK (INT64_T_BITS - 1) +#define INT64_T_SHIFT IM_LOG2(INT64_T_MASK) + +#define UINT8_T_BITS (sizeof(uint8_t) * 8) +#define UINT8_T_MASK (UINT8_T_BITS - 1) +#define UINT8_T_SHIFT IM_LOG2(UINT8_T_MASK) + +#define UINT16_T_BITS (sizeof(uint16_t) * 8) +#define UINT16_T_MASK (UINT16_T_BITS - 1) +#define UINT16_T_SHIFT IM_LOG2(UINT16_T_MASK) + +#define UINT32_T_BITS (sizeof(uint32_t) * 8) +#define UINT32_T_MASK (UINT32_T_BITS - 1) +#define UINT32_T_SHIFT IM_LOG2(UINT32_T_MASK) + +#define UINT64_T_BITS (sizeof(uint64_t) * 8) +#define UINT64_T_MASK (UINT64_T_BITS - 1) +#define UINT64_T_SHIFT IM_LOG2(UINT64_T_MASK) + +#define IM_DEG2RAD(x) (((x) * M_PI) / 180) +#define IM_RAD2DEG(x) (((x) * 180) / M_PI) + + +// ======================================================================================= +// Point Stuff +// ======================================================================================= +typedef struct point { + int16_t x; + int16_t y; +} point_t; + +void point_init(point_t *ptr, int x, int y); +void point_copy(point_t *dst, point_t *src); +bool point_equal_fast(point_t *ptr0, point_t *ptr1); +int point_quadrance(point_t *ptr0, point_t *ptr1); +void point_rotate(int x, int y, float r, int center_x, int center_y, int16_t *new_x, int16_t *new_y); +void point_min_area_rectangle(point_t *corners, point_t *new_corners, int corners_len); + + +// ======================================================================================= +// Line Stuff +// ======================================================================================= +typedef struct line { + int16_t x1; + int16_t y1; + int16_t x2; + int16_t y2; +} line_t; + +bool lb_clip_line(line_t *l, int x, int y, int w, int h); + + +// ======================================================================================= +// Rectangle Stuff +// ======================================================================================= +typedef struct rectangle { + int16_t x; + int16_t y; + int16_t w; + int16_t h; +} rectangle_t; + +typedef struct bounding_box_lnk_data { + rectangle_t rect; + float score; + int label_index; +} bounding_box_lnk_data_t; + + +// ======================================================================================= +// Color Stuff +// ======================================================================================= +typedef struct color_thresholds_list_lnk_data { + uint8_t LMin, LMax; // or grayscale + int8_t AMin, AMax; + int8_t BMin, BMax; +} +color_thresholds_list_lnk_data_t; + +#define COLOR_THRESHOLD_BINARY(pixel, threshold, invert) \ + ({ \ + __typeof__ (pixel) _pixel = (pixel); \ + __typeof__ (threshold) _threshold = (threshold); \ + __typeof__ (invert) _invert = (invert); \ + ((_threshold->LMin <= _pixel) && (_pixel <= _threshold->LMax)) ^ _invert; \ + }) + +#define COLOR_THRESHOLD_GRAYSCALE(pixel, threshold, invert) \ + ({ \ + __typeof__ (pixel) _pixel = (pixel); \ + __typeof__ (threshold) _threshold = (threshold); \ + __typeof__ (invert) _invert = (invert); \ + ((_threshold->LMin <= _pixel) && (_pixel <= _threshold->LMax)) ^ _invert; \ + }) + +#define COLOR_THRESHOLD_RGB565(pixel, threshold, invert) \ + ({ \ + __typeof__ (pixel) _pixel = (pixel); \ + __typeof__ (threshold) _threshold = (threshold); \ + __typeof__ (invert) _invert = (invert); \ + uint8_t _l = COLOR_RGB565_TO_L(_pixel); \ + int8_t _a = COLOR_RGB565_TO_A(_pixel); \ + int8_t _b = COLOR_RGB565_TO_B(_pixel); \ + ((_threshold->LMin <= _l) && (_l <= _threshold->LMax) && \ + (_threshold->AMin <= _a) && (_a <= _threshold->AMax) && \ + (_threshold->BMin <= _b) && (_b <= _threshold->BMax)) ^ _invert; \ + }) + +#define COLOR_BOUND_BINARY(pixel0, pixel1, threshold) \ + ({ \ + __typeof__ (pixel0) _pixel0 = (pixel0); \ + __typeof__ (pixel1) _pixel1 = (pixel1); \ + __typeof__ (threshold) _threshold = (threshold); \ + (abs(_pixel0 - _pixel1) <= _threshold); \ + }) + +#define COLOR_BOUND_GRAYSCALE(pixel0, pixel1, threshold) \ + ({ \ + __typeof__ (pixel0) _pixel0 = (pixel0); \ + __typeof__ (pixel1) _pixel1 = (pixel1); \ + __typeof__ (threshold) _threshold = (threshold); \ + (abs(_pixel0 - _pixel1) <= _threshold); \ + }) + +#define COLOR_BOUND_RGB565(pixel0, pixel1, threshold) \ + ({ \ + __typeof__ (pixel0) _pixel0 = (pixel0); \ + __typeof__ (pixel1) _pixel1 = (pixel1); \ + __typeof__ (threshold) _threshold = (threshold); \ + (abs(COLOR_RGB565_TO_R5(_pixel0) - COLOR_RGB565_TO_R5(_pixel1)) <= COLOR_RGB565_TO_R5(_threshold)) && \ + (abs(COLOR_RGB565_TO_G6(_pixel0) - COLOR_RGB565_TO_G6(_pixel1)) <= COLOR_RGB565_TO_G6(_threshold)) && \ + (abs(COLOR_RGB565_TO_B5(_pixel0) - COLOR_RGB565_TO_B5(_pixel1)) <= COLOR_RGB565_TO_B5(_threshold)); \ + }) + +#define COLOR_BINARY_MIN 0 +#define COLOR_BINARY_MAX 1 +#define COLOR_GRAYSCALE_BINARY_MIN 0x00 +#define COLOR_GRAYSCALE_BINARY_MAX 0xFF +#define COLOR_RGB565_BINARY_MIN 0x0000 +#define COLOR_RGB565_BINARY_MAX 0xFFFF + +#define COLOR_GRAYSCALE_MIN 0 +#define COLOR_GRAYSCALE_MAX 255 + +#define COLOR_R5_MIN 0 +#define COLOR_R5_MAX 31 +#define COLOR_G6_MIN 0 +#define COLOR_G6_MAX 63 +#define COLOR_B5_MIN 0 +#define COLOR_B5_MAX 31 + +#define COLOR_R8_MIN 0 +#define COLOR_R8_MAX 255 +#define COLOR_G8_MIN 0 +#define COLOR_G8_MAX 255 +#define COLOR_B8_MIN 0 +#define COLOR_B8_MAX 255 + +#define COLOR_L_MIN 0 +#define COLOR_L_MAX 100 +#define COLOR_A_MIN -128 +#define COLOR_A_MAX 127 +#define COLOR_B_MIN -128 +#define COLOR_B_MAX 127 + +#define COLOR_Y_MIN 0 +#define COLOR_Y_MAX 255 +#define COLOR_U_MIN -128 +#define COLOR_U_MAX 127 +#define COLOR_V_MIN -128 +#define COLOR_V_MAX 127 + + +// ======================================================================================= +// RGB565 Stuff +// ======================================================================================= +#define COLOR_RGB565_TO_R5(pixel) (((pixel) >> 11) & 0x1F) +#define COLOR_RGB565_TO_R8(pixel) \ + ({ \ + __typeof__ (pixel) __pixel = (pixel); \ + __pixel = (__pixel >> 8) & 0xF8; \ + __pixel | (__pixel >> 5); \ + }) + +#define COLOR_RGB565_TO_G6(pixel) (((pixel) >> 5) & 0x3F) +#define COLOR_RGB565_TO_G8(pixel) \ + ({ \ + __typeof__ (pixel) __pixel = (pixel); \ + __pixel = (__pixel >> 3) & 0xFC; \ + __pixel | (__pixel >> 6); \ + }) + +#define COLOR_RGB565_TO_B5(pixel) ((pixel) & 0x1F) +#define COLOR_RGB565_TO_B8(pixel) \ + ({ \ + __typeof__ (pixel) __pixel = (pixel); \ + __pixel = (__pixel << 3) & 0xF8; \ + __pixel | (__pixel >> 5); \ + }) + +#define COLOR_R5_G6_B5_TO_RGB565(r5, g6, b5) (((r5) << 11) | ((g6) << 5) | (b5)) +#define COLOR_R8_G8_B8_TO_RGB565(r8, g8, b8) ((((r8) & 0xF8) << 8) | (((g8) & 0xFC) << 3) | ((b8) >> 3)) + +#define COLOR_RGB888_TO_Y(r8, g8, b8) ((((r8) * 38) + ((g8) * 75) + ((b8) * 15)) >> 7) // 0.299R + 0.587G + 0.114B +#define COLOR_RGB565_TO_Y(rgb565) \ + ({ \ + __typeof__ (rgb565) __rgb565 = (rgb565); \ + int r = COLOR_RGB565_TO_R8(__rgb565); \ + int g = COLOR_RGB565_TO_G8(__rgb565); \ + int b = COLOR_RGB565_TO_B8(__rgb565); \ + COLOR_RGB888_TO_Y(r, g, b); \ + }) + +#define COLOR_Y_TO_RGB888(pixel) ((pixel) * 0x010101) +#define COLOR_Y_TO_RGB565(pixel) \ + ({ \ + __typeof__ (pixel) __pixel = (pixel); \ + int __rb_pixel = (__pixel >> 3) & 0x1F; \ + (__rb_pixel * 0x0801) + ((__pixel << 3) & 0x7E0); \ + }) + +#define COLOR_RGB888_TO_U(r8, g8, b8) ((((r8) * -21) - ((g8) * 43) + ((b8) * 64)) >> 7) // -0.168736R - 0.331264G + 0.5B +#define COLOR_RGB565_TO_U(rgb565) \ + ({ \ + __typeof__ (rgb565) __rgb565 = (rgb565); \ + int r = COLOR_RGB565_TO_R8(__rgb565); \ + int g = COLOR_RGB565_TO_G8(__rgb565); \ + int b = COLOR_RGB565_TO_B8(__rgb565); \ + COLOR_RGB888_TO_U(r, g, b); \ + }) + +#define COLOR_RGB888_TO_V(r8, g8, b8) ((((r8) * 64) - ((g8) * 54) - ((b8) * 10)) >> 7) // 0.5R - 0.418688G - 0.081312B +#define COLOR_RGB565_TO_V(rgb565) \ + ({ \ + __typeof__ (rgb565) __rgb565 = (rgb565); \ + int r = COLOR_RGB565_TO_R8(__rgb565); \ + int g = COLOR_RGB565_TO_G8(__rgb565); \ + int b = COLOR_RGB565_TO_B8(__rgb565); \ + COLOR_RGB888_TO_V(r, g, b); \ + }) + +extern const int8_t lab_table[196608 / 2]; + +#ifdef IMLIB_ENABLE_LAB_LUT +#define COLOR_RGB565_TO_L(pixel) lab_table[((pixel >> 1) * 3) + 0] +#define COLOR_RGB565_TO_A(pixel) lab_table[((pixel >> 1) * 3) + 1] +#define COLOR_RGB565_TO_B(pixel) lab_table[((pixel >> 1) * 3) + 2] +#else +#define COLOR_RGB565_TO_L(pixel) imlib_rgb565_to_l(pixel) +#define COLOR_RGB565_TO_A(pixel) imlib_rgb565_to_a(pixel) +#define COLOR_RGB565_TO_B(pixel) imlib_rgb565_to_b(pixel) +#endif + +#define COLOR_LAB_TO_RGB565(l, a, b) imlib_lab_to_rgb(l, a, b) +#define COLOR_YUV_TO_RGB565(y, u, v) imlib_yuv_to_rgb((y) + 128, u, v) + +#define COLOR_BINARY_TO_GRAYSCALE(pixel) ((pixel) * COLOR_GRAYSCALE_MAX) +#define COLOR_BINARY_TO_RGB565(pixel) COLOR_YUV_TO_RGB565(((pixel) ? 127 : -128), 0, 0) +#define COLOR_RGB565_TO_BINARY(pixel) (COLOR_RGB565_TO_Y(pixel) > (((COLOR_Y_MAX - COLOR_Y_MIN) / 2) + COLOR_Y_MIN)) +#define COLOR_RGB565_TO_GRAYSCALE(pixel) COLOR_RGB565_TO_Y(pixel) +#define COLOR_GRAYSCALE_TO_BINARY(pixel) ((pixel) > \ + (((COLOR_GRAYSCALE_MAX - COLOR_GRAYSCALE_MIN) / 2) + COLOR_GRAYSCALE_MIN)) +#define COLOR_GRAYSCALE_TO_RGB565(pixel) COLOR_YUV_TO_RGB565(((pixel) - 128), 0, 0) + +typedef enum { + COLOR_PALETTE_RAINBOW, + COLOR_PALETTE_IRONBOW, + COLOR_PALETTE_DEPTH, + COLOR_PALETTE_EVT_DARK, + COLOR_PALETTE_EVT_LIGHT +} color_palette_t; + +// Color palette LUTs +extern const uint16_t rainbow_table[256]; +extern const uint16_t ironbow_table[256]; +extern const uint16_t depth_table[256]; +extern const uint16_t evt_dark_table[256]; +extern const uint16_t evt_light_table[256]; + + + +// ======================================================================================= +// Image Stuff +// ======================================================================================= + +// Pixel format IDs. +typedef enum { + OMV_PIXFORMAT_ID_BINARY = 1, + OMV_PIXFORMAT_ID_GRAY = 2, + OMV_PIXFORMAT_ID_RGB565 = 3, + OMV_PIXFORMAT_ID_BAYER = 4, + OMV_PIXFORMAT_ID_YUV422 = 5, + OMV_PIXFORMAT_ID_JPEG = 6, + OMV_PIXFORMAT_ID_PNG = 7, + OMV_PIXFORMAT_ID_ARGB8 = 8, + /* Note: Update OMV_PIXFORMAT_IS_VALID when adding new formats */ +} omv_pixformat_id_t; + +// Pixel sub-format IDs. +typedef enum { + SUBFORMAT_ID_GRAY8 = 0, + SUBFORMAT_ID_GRAY16 = 1, + SUBFORMAT_ID_BGGR = 0, // !!! Note: Make sure bayer sub-formats don't !!! + SUBFORMAT_ID_GBRG = 1, // !!! overflow the sensor.hw_flags.bayer field !!! + SUBFORMAT_ID_GRBG = 2, + SUBFORMAT_ID_RGGB = 3, + SUBFORMAT_ID_YUV422 = 0, + SUBFORMAT_ID_YVU422 = 1, + /* Note: Update OMV_PIXFORMAT_IS_VALID when adding new formats */ +} subformat_id_t; + +// Pixel format Byte Per Pixel. +typedef enum { + OMV_PIXFORMAT_BPP_BINARY = 0, + OMV_PIXFORMAT_BPP_GRAY8 = 1, + OMV_PIXFORMAT_BPP_GRAY16 = 2, + OMV_PIXFORMAT_BPP_RGB565 = 2, + OMV_PIXFORMAT_BPP_BAYER = 1, + OMV_PIXFORMAT_BPP_YUV422 = 2, + OMV_PIXFORMAT_BPP_ARGB8 = 4, + /* Note: Update OMV_PIXFORMAT_IS_VALID when adding new formats */ +} omv_pixformat_bpp_t; + +// Pixel format flags. +#define OMV_PIXFORMAT_FLAGS_Y (1 << 28) // YUV format. +#define OMV_PIXFORMAT_FLAGS_M (1 << 27) // Mutable format. +#define OMV_PIXFORMAT_FLAGS_C (1 << 26) // Colored format. +#define OMV_PIXFORMAT_FLAGS_J (1 << 25) // Compressed format (JPEG/PNG). +#define OMV_PIXFORMAT_FLAGS_R (1 << 24) // RAW/Bayer format. +#define OMV_PIXFORMAT_FLAGS_CY (OMV_PIXFORMAT_FLAGS_C | OMV_PIXFORMAT_FLAGS_Y) +#define OMV_PIXFORMAT_FLAGS_CM (OMV_PIXFORMAT_FLAGS_C | OMV_PIXFORMAT_FLAGS_M) +#define OMV_PIXFORMAT_FLAGS_CR (OMV_PIXFORMAT_FLAGS_C | OMV_PIXFORMAT_FLAGS_R) +#define OMV_PIXFORMAT_FLAGS_CJ (OMV_PIXFORMAT_FLAGS_C | OMV_PIXFORMAT_FLAGS_J) +#define IMLIB_IMAGE_MAX_SIZE(x) ((x) & 0xFFFFFFFF) + +// *INDENT-OFF* +// Each pixel format encodes flags, pixel format id and bpp as follows: +// 31......29 28 27 26 25 24 23..........16 15...........8 7.............0 +// YF MF CF JF RF +// NOTE: Bit 31-30 must Not be used for omv_pixformat_t to be used as mp_int_t. +typedef enum { + OMV_PIXFORMAT_INVALID = (0x00000000U), + OMV_PIXFORMAT_BINARY = (OMV_PIXFORMAT_FLAGS_M | (OMV_PIXFORMAT_ID_BINARY << 16) | (0 << 8) | OMV_PIXFORMAT_BPP_BINARY), + OMV_PIXFORMAT_GRAYSCALE = (OMV_PIXFORMAT_FLAGS_M | (OMV_PIXFORMAT_ID_GRAY << 16) | (SUBFORMAT_ID_GRAY8 << 8) | OMV_PIXFORMAT_BPP_GRAY8), + OMV_PIXFORMAT_RGB565 = (OMV_PIXFORMAT_FLAGS_CM | (OMV_PIXFORMAT_ID_RGB565 << 16) | (0 << 8) | OMV_PIXFORMAT_BPP_RGB565), + OMV_PIXFORMAT_ARGB8 = (OMV_PIXFORMAT_FLAGS_CM | (OMV_PIXFORMAT_ID_ARGB8 << 16) | (0 << 8) | OMV_PIXFORMAT_BPP_ARGB8), + OMV_PIXFORMAT_BAYER = (OMV_PIXFORMAT_FLAGS_CR | (OMV_PIXFORMAT_ID_BAYER << 16) | (SUBFORMAT_ID_BGGR << 8) | OMV_PIXFORMAT_BPP_BAYER), + OMV_PIXFORMAT_BAYER_BGGR = (OMV_PIXFORMAT_FLAGS_CR | (OMV_PIXFORMAT_ID_BAYER << 16) | (SUBFORMAT_ID_BGGR << 8) | OMV_PIXFORMAT_BPP_BAYER), + OMV_PIXFORMAT_BAYER_GBRG = (OMV_PIXFORMAT_FLAGS_CR | (OMV_PIXFORMAT_ID_BAYER << 16) | (SUBFORMAT_ID_GBRG << 8) | OMV_PIXFORMAT_BPP_BAYER), + OMV_PIXFORMAT_BAYER_GRBG = (OMV_PIXFORMAT_FLAGS_CR | (OMV_PIXFORMAT_ID_BAYER << 16) | (SUBFORMAT_ID_GRBG << 8) | OMV_PIXFORMAT_BPP_BAYER), + OMV_PIXFORMAT_BAYER_RGGB = (OMV_PIXFORMAT_FLAGS_CR | (OMV_PIXFORMAT_ID_BAYER << 16) | (SUBFORMAT_ID_RGGB << 8) | OMV_PIXFORMAT_BPP_BAYER), + OMV_PIXFORMAT_YUV = (OMV_PIXFORMAT_FLAGS_CY | (OMV_PIXFORMAT_ID_YUV422 << 16) | (SUBFORMAT_ID_YUV422 << 8) | OMV_PIXFORMAT_BPP_YUV422), + OMV_PIXFORMAT_YUV422 = (OMV_PIXFORMAT_FLAGS_CY | (OMV_PIXFORMAT_ID_YUV422 << 16) | (SUBFORMAT_ID_YUV422 << 8) | OMV_PIXFORMAT_BPP_YUV422), + OMV_PIXFORMAT_YVU422 = (OMV_PIXFORMAT_FLAGS_CY | (OMV_PIXFORMAT_ID_YUV422 << 16) | (SUBFORMAT_ID_YVU422 << 8) | OMV_PIXFORMAT_BPP_YUV422), + OMV_PIXFORMAT_JPEG = (OMV_PIXFORMAT_FLAGS_CJ | (OMV_PIXFORMAT_ID_JPEG << 16) | (0 << 8) | 0), + OMV_PIXFORMAT_PNG = (OMV_PIXFORMAT_FLAGS_CJ | (OMV_PIXFORMAT_ID_PNG << 16) | (0 << 8) | 0), + OMV_PIXFORMAT_LAST = (0xFFFFFFFFU), +} omv_pixformat_t; +// *INDENT-ON* + + +#define OMV_PIXFORMAT_MUTABLE_ANY \ +OMV_PIXFORMAT_BINARY: \ + case OMV_PIXFORMAT_GRAYSCALE: \ + case OMV_PIXFORMAT_RGB565: \ + case OMV_PIXFORMAT_ARGB8 \ + +#define OMV_PIXFORMAT_BAYER_ANY \ + OMV_PIXFORMAT_BAYER_BGGR: \ + case OMV_PIXFORMAT_BAYER_GBRG: \ + case OMV_PIXFORMAT_BAYER_GRBG: \ + case OMV_PIXFORMAT_BAYER_RGGB \ + +#define OMV_PIXFORMAT_YUV_ANY \ + OMV_PIXFORMAT_YUV422: \ + case OMV_PIXFORMAT_YVU422 \ + +#define OMV_PIXFORMAT_COMPRESSED_ANY \ + OMV_PIXFORMAT_JPEG: \ + case OMV_PIXFORMAT_PNG \ + +#define IMLIB_OMV_PIXFORMAT_IS_VALID(x) \ + ((x == OMV_PIXFORMAT_BINARY) \ + || (x == OMV_PIXFORMAT_GRAYSCALE) \ + || (x == OMV_PIXFORMAT_RGB565) \ + || (x == OMV_PIXFORMAT_ARGB8) \ + || (x == OMV_PIXFORMAT_BAYER_BGGR) \ + || (x == OMV_PIXFORMAT_BAYER_GBRG) \ + || (x == OMV_PIXFORMAT_BAYER_GRBG) \ + || (x == OMV_PIXFORMAT_BAYER_RGGB) \ + || (x == OMV_PIXFORMAT_YUV422) \ + || (x == OMV_PIXFORMAT_YVU422) \ + || (x == OMV_PIXFORMAT_JPEG) \ + || (x == OMV_PIXFORMAT_PNG)) \ + + +#define OMV_PIXFORMAT_STRUCT \ + struct { \ + union { \ + struct { \ + uint32_t bpp: \ + 8; \ + uint32_t subfmt_id : 8; \ + uint32_t pixfmt_id : 8; \ + uint32_t is_bayer : 1; \ + uint32_t is_compressed : 1; \ + uint32_t is_color : 1; \ + uint32_t is_mutable : 1; \ + uint32_t is_yuv : 1; \ + uint32_t /*reserved*/ : 3; \ + }; \ + uint32_t pixfmt; \ + }; \ + uint32_t size; /* for compressed images */ \ + } + +typedef struct image { + int32_t w; + int32_t h; + OMV_PIXFORMAT_STRUCT; + union { + uint8_t *pixels; + uint8_t *data; + }; +} image_t; + + + +#define IMAGE_BINARY_LINE_LEN(image) (((image)->w + UINT32_T_MASK) >> UINT32_T_SHIFT) +#define IMAGE_BINARY_LINE_LEN_BYTES(image) (IMAGE_BINARY_LINE_LEN(image) * sizeof(uint32_t)) + +#define IMAGE_GRAYSCALE_LINE_LEN(image) ((image)->w) +#define IMAGE_GRAYSCALE_LINE_LEN_BYTES(image) (IMAGE_GRAYSCALE_LINE_LEN(image) * sizeof(uint8_t)) + +#define IMAGE_RGB565_LINE_LEN(image) ((image)->w) +#define IMAGE_RGB565_LINE_LEN_BYTES(image) (IMAGE_RGB565_LINE_LEN(image) * sizeof(uint16_t)) + +#define IMAGE_GET_BINARY_PIXEL(image, x, y) \ + ({ \ + __typeof__ (image) _image = (image); \ + __typeof__ (x) _x = (x); \ + __typeof__ (y) _y = (y); \ + (((uint32_t *)_image->data)[(((_image->w + UINT32_T_MASK) >> UINT32_T_SHIFT) * _y) + (_x >> UINT32_T_SHIFT)] >> \ + (_x & UINT32_T_MASK)) & 1; \ + }) + +#define IMAGE_PUT_BINARY_PIXEL(image, x, y, v) \ + ({ \ + __typeof__ (image) _image = (image); \ + __typeof__ (x) _x = (x); \ + __typeof__ (y) _y = (y); \ + __typeof__ (v) _v = (v); \ + size_t _i = (((_image->w + UINT32_T_MASK) >> UINT32_T_SHIFT) * _y) + (_x >> UINT32_T_SHIFT); \ + size_t _j = _x & UINT32_T_MASK; \ + ((uint32_t *)_image->data)[_i] = (((uint32_t *)_image->data)[_i] & (~(1 << _j))) | ((_v & 1) << _j); \ + }) + +#define IMAGE_CLEAR_BINARY_PIXEL(image, x, y) \ + ({ \ + __typeof__ (image) _image = (image); \ + __typeof__ (x) _x = (x); \ + __typeof__ (y) _y = (y); \ + ((uint32_t *)_image->data)[(((_image->w + UINT32_T_MASK) >> \ + UINT32_T_SHIFT) * _y) + (_x >> UINT32_T_SHIFT)] &= ~(1 << (_x & UINT32_T_MASK)); \ + }) + +#define IMAGE_SET_BINARY_PIXEL(image, x, y) \ + ({ \ + __typeof__ (image) _image = (image); \ + __typeof__ (x) _x = (x); \ + __typeof__ (y) _y = (y); \ + ((uint32_t *)_image->data)[(((_image->w + UINT32_T_MASK) >> UINT32_T_SHIFT) * _y) + (_x >> UINT32_T_SHIFT)] |= 1 << \ + (_x & \ + UINT32_T_MASK); \ + }) + +#define IMAGE_GET_GRAYSCALE_PIXEL(image, x, y) \ + ({ \ + __typeof__ (image) _image = (image); \ + __typeof__ (x) _x = (x); \ + __typeof__ (y) _y = (y); \ + ((uint8_t *)_image->data)[(_image->w * _y) + _x]; \ + }) + +#define IMAGE_PUT_GRAYSCALE_PIXEL(image, x, y, v) \ + ({ \ + __typeof__ (image) _image = (image); \ + __typeof__ (x) _x = (x); \ + __typeof__ (y) _y = (y); \ + __typeof__ (v) _v = (v); \ + ((uint8_t *)_image->data)[(_image->w * _y) + _x] = _v; \ + }) + +#define IMAGE_GET_RGB565_PIXEL(image, x, y) \ + ({ \ + __typeof__ (image) _image = (image); \ + __typeof__ (x) _x = (x); \ + __typeof__ (y) _y = (y); \ + ((uint16_t *)_image->data)[(_image->w * _y) + _x]; \ + }) + +#define IMAGE_PUT_RGB565_PIXEL(image, x, y, v) \ + ({ \ + __typeof__ (image) _image = (image); \ + __typeof__ (x) _x = (x); \ + __typeof__ (y) _y = (y); \ + __typeof__ (v) _v = (v); \ + ((uint16_t *)_image->data)[(_image->w * _y) + _x] = _v; \ + }) + +#define IMAGE_GET_YUV_PIXEL(image, x, y) \ + ({ \ + __typeof__ (image) _image = (image); \ + __typeof__ (x) _x = (x); \ + __typeof__ (y) _y = (y); \ + ((uint16_t *)_image->data)[(_image->w * _y) + _x]; \ + }) + +#define IMAGE_PUT_YUV_PIXEL(image, x, y, v) \ + ({ \ + __typeof__ (image) _image = (image); \ + __typeof__ (x) _x = (x); \ + __typeof__ (y) _y = (y); \ + __typeof__ (v) _v = (v); \ + ((uint16_t *)_image->data)[(_image->w * _y) + _x] = _v; \ + }) + +#define IMAGE_GET_BAYER_PIXEL(image, x, y) \ + ({ \ + __typeof__ (image) _image = (image); \ + __typeof__ (x) _x = (x); \ + __typeof__ (y) _y = (y); \ + ((uint8_t *)_image->data)[(_image->w * _y) + _x]; \ + }) + +#define IMAGE_PUT_BAYER_PIXEL(image, x, y, v) \ + ({ \ + __typeof__ (image) _image = (image); \ + __typeof__ (x) _x = (x); \ + __typeof__ (y) _y = (y); \ + __typeof__ (v) _v = (v); \ + ((uint8_t *)_image->data)[(_image->w * _y) + _x] = _v; \ + }) + +// Fast Stuff // + +#define IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(image, y) \ + ({ \ + __typeof__ (image) _image = (image); \ + __typeof__ (y) _y = (y); \ + ((uint32_t *)_image->data) + (((_image->w + UINT32_T_MASK) >> UINT32_T_SHIFT) * _y); \ + }) + +#define IMAGE_GET_BINARY_PIXEL_FAST(row_ptr, x) \ + ({ \ + __typeof__ (row_ptr) _row_ptr = (row_ptr); \ + __typeof__ (x) _x = (x); \ + (_row_ptr[_x >> UINT32_T_SHIFT] >> (_x & UINT32_T_MASK)) & 1; \ + }) + +#define IMAGE_PUT_BINARY_PIXEL_FAST(row_ptr, x, v) \ + ({ \ + __typeof__ (row_ptr) _row_ptr = (row_ptr); \ + __typeof__ (x) _x = (x); \ + __typeof__ (v) _v = (v); \ + size_t _i = _x >> UINT32_T_SHIFT; \ + size_t _j = _x & UINT32_T_MASK; \ + _row_ptr[_i] = (_row_ptr[_i] & (~(1 << _j))) | ((_v & 1) << _j); \ + }) + +#define IMAGE_CLEAR_BINARY_PIXEL_FAST(row_ptr, x) \ + ({ \ + __typeof__ (row_ptr) _row_ptr = (row_ptr); \ + __typeof__ (x) _x = (x); \ + _row_ptr[_x >> UINT32_T_SHIFT] &= ~(1 << (_x & UINT32_T_MASK)); \ + }) + +#define IMAGE_SET_BINARY_PIXEL_FAST(row_ptr, x) \ + ({ \ + __typeof__ (row_ptr) _row_ptr = (row_ptr); \ + __typeof__ (x) _x = (x); \ + _row_ptr[_x >> UINT32_T_SHIFT] |= 1 << (_x & UINT32_T_MASK); \ + }) + +#define IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(image, y) \ + ({ \ + __typeof__ (image) _image = (image); \ + __typeof__ (y) _y = (y); \ + ((uint8_t *)_image->data) + (_image->w * _y); \ + }) + +#define IMAGE_GET_GRAYSCALE_PIXEL_FAST(row_ptr, x) \ + ({ \ + __typeof__ (row_ptr) _row_ptr = (row_ptr); \ + __typeof__ (x) _x = (x); \ + _row_ptr[_x]; \ + }) + +#define IMAGE_PUT_GRAYSCALE_PIXEL_FAST(row_ptr, x, v) \ + ({ \ + __typeof__ (row_ptr) _row_ptr = (row_ptr); \ + __typeof__ (x) _x = (x); \ + __typeof__ (v) _v = (v); \ + _row_ptr[_x] = _v; \ + }) + +#define IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(image, y) \ + ({ \ + __typeof__ (image) _image = (image); \ + __typeof__ (y) _y = (y); \ + ((uint16_t *)_image->data) + (_image->w * _y); \ + }) + +#define IMAGE_GET_RGB565_PIXEL_FAST(row_ptr, x) \ + ({ \ + __typeof__ (row_ptr) _row_ptr = (row_ptr); \ + __typeof__ (x) _x = (x); \ + _row_ptr[_x]; \ + }) + +#define IMAGE_PUT_RGB565_PIXEL_FAST(row_ptr, x, v) \ + ({ \ + __typeof__ (row_ptr) _row_ptr = (row_ptr); \ + __typeof__ (x) _x = (x); \ + __typeof__ (v) _v = (v); \ + _row_ptr[_x] = _v; \ + }) + +#define IMAGE_COMPUTE_BAYER_PIXEL_ROW_PTR(image, y) \ + ({ \ + __typeof__ (image) _image = (image); \ + __typeof__ (y) _y = (y); \ + ((uint8_t *)_image->data) + (_image->w * _y); \ + }) + +#define IMAGE_COMPUTE_YUV_PIXEL_ROW_PTR(image, y) \ + ({ \ + __typeof__ (image) _image = (image); \ + __typeof__ (y) _y = (y); \ + ((uint16_t *)_image->data) + (_image->w * _y); \ + }) + + + +typedef enum { + OMV_FRAMESIZE_INVALID = 0, + // C/SIF Resolutions + OMV_FRAMESIZE_QQCIF, // 88x72 + OMV_FRAMESIZE_QCIF, // 176x144 + OMV_FRAMESIZE_CIF, // 352x288 + OMV_FRAMESIZE_QQSIF, // 88x60 + OMV_FRAMESIZE_QSIF, // 176x120 + OMV_FRAMESIZE_SIF, // 352x240 + // VGA Resolutions + OMV_FRAMESIZE_QQQQVGA, // 40x30 + OMV_FRAMESIZE_QQQVGA, // 80x60 + OMV_FRAMESIZE_QQVGA, // 160x120 + OMV_FRAMESIZE_QVGA, // 320x240 + OMV_FRAMESIZE_VGA, // 640x480 + OMV_FRAMESIZE_HQQQQVGA, // 30x20 + OMV_FRAMESIZE_HQQQVGA, // 60x40 + OMV_FRAMESIZE_HQQVGA, // 120x80 + OMV_FRAMESIZE_HQVGA, // 240x160 + OMV_FRAMESIZE_HVGA, // 480x320 + // FFT Resolutions + OMV_FRAMESIZE_64X32, // 64x32 + OMV_FRAMESIZE_64X64, // 64x64 + OMV_FRAMESIZE_128X64, // 128x64 + OMV_FRAMESIZE_128X128, // 128x128 + // Himax Resolutions + OMV_FRAMESIZE_160X160, // 160x160 + OMV_FRAMESIZE_320X320, // 320x320 + // Other + OMV_FRAMESIZE_LCD, // 128x160 + OMV_FRAMESIZE_QQVGA2, // 128x160 + OMV_FRAMESIZE_WVGA, // 720x480 + OMV_FRAMESIZE_WVGA2, // 752x480 + OMV_FRAMESIZE_SVGA, // 800x600 + OMV_FRAMESIZE_XGA, // 1024x768 + OMV_FRAMESIZE_WXGA, // 1280x768 + OMV_FRAMESIZE_SXGA, // 1280x1024 + OMV_FRAMESIZE_SXGAM, // 1280x960 + OMV_FRAMESIZE_UXGA, // 1600x1200 + OMV_FRAMESIZE_HD, // 1280x720 + OMV_FRAMESIZE_FHD, // 1920x1080 + OMV_FRAMESIZE_QHD, // 2560x1440 + OMV_FRAMESIZE_QXGA, // 2048x1536 + OMV_FRAMESIZE_WQXGA, // 2560x1600 + OMV_FRAMESIZE_WQXGA2, // 2592x1944 +} omv_framesize_t; + + + +// ======================================================================================= +// draw functions +// ======================================================================================= +int imlib_get_pixel(image_t *img, int x, int y); +int imlib_get_pixel_fast(image_t *img, const void *row_ptr, int x); +void imlib_set_pixel(image_t *img, int x, int y, int p); +void imlib_draw_line(image_t *img, int x0, int y0, int x1, int y1, int c, int thickness); +void imlib_draw_rectangle(image_t *img, int rx, int ry, int rw, int rh, int c, int thickness, bool fill); +void imlib_draw_circle(image_t *img, int cx, int cy, int r, int c, int thickness, bool fill); +void imlib_draw_ellipse(image_t *img, int cx, int cy, int rx, int ry, int rotation, int c, int thickness, bool fill); +void imlib_draw_string(image_t *img, + int x_off, + int y_off, + const char *str, + int c, + float scale, + int x_spacing, + int y_spacing, + bool mono_space, + int char_rotation, + bool char_hmirror, + bool char_vflip, + int string_rotation, + bool string_hmirror, + bool string_hflip); + + +#ifdef __cplusplus +} +#endif diff --git a/m5stack/cmodules/omv/modules/py_assert.h b/m5stack/cmodules/omv/modules/py_assert.h old mode 100755 new mode 100644 index 7e3aaa95..33095917 --- a/m5stack/cmodules/omv/modules/py_assert.h +++ b/m5stack/cmodules/omv/modules/py_assert.h @@ -29,8 +29,8 @@ do { \ if ((cond) == 0) { \ mp_raise_msg(&mp_type_OSError, \ - MP_ERROR_TEXT( \ - "Operation not supported")); \ + MP_ERROR_TEXT( \ + "Operation not supported")); \ } \ } while (0) @@ -38,7 +38,7 @@ do { \ if ((cond) == 0) { \ mp_raise_msg(&mp_type_OSError, \ - MP_ERROR_TEXT(msg)); \ + MP_ERROR_TEXT(msg)); \ } \ } while (0) @@ -46,7 +46,7 @@ do { \ if ((cond) == 1) { \ mp_raise_msg(&mp_type_OSError, \ - MP_ERROR_TEXT(msg)); \ + MP_ERROR_TEXT(msg)); \ } \ } while (0) @@ -56,10 +56,10 @@ __typeof__ (type) _b = (type); \ if (!MP_OBJ_IS_TYPE(_a, _b)) { \ mp_raise_msg_varg(&mp_type_TypeError, \ - MP_ERROR_TEXT( \ - "Can't convert %s to %s"), \ - mp_obj_get_type_str(_a), \ - mp_obj_get_type_str(_b)); \ + MP_ERROR_TEXT( \ + "Can't convert %s to %s"), \ + mp_obj_get_type_str(_a), \ + mp_obj_get_type_str(_b)); \ } \ } while (0) /* IS_TYPE doesn't work for str objs */ @@ -68,11 +68,11 @@ __typeof__ (obj) _a = (obj); \ if (!MP_OBJ_IS_STR(_a)) { \ mp_raise_msg_varg( \ - &mp_type_TypeError, \ - MP_ERROR_TEXT( \ - "Can't convert %s to %s"), \ - mp_obj_get_type_str(_a), \ - str_type.name); \ + &mp_type_TypeError, \ + MP_ERROR_TEXT( \ + "Can't convert %s to %s"), \ + mp_obj_get_type_str(_a), \ + str_type.name); \ } \ } while (0) diff --git a/m5stack/cmodules/omv/modules/py_camera.c b/m5stack/cmodules/omv/modules/py_camera.c old mode 100755 new mode 100644 index 6169ded8..0cdbe327 --- a/m5stack/cmodules/omv/modules/py_camera.c +++ b/m5stack/cmodules/omv/modules/py_camera.c @@ -389,16 +389,14 @@ static MP_DEFINE_CONST_FUN_OBJ_1(camera_colorbar_obj, camera_colorbar); static camera_fb_t *g_frame = NULL; -void swap_rgb565(uint16_t* pixel) -{ - *pixel = (*pixel >> 8) | (*pixel << 8); +void swap_rgb565(uint16_t *pixel) { + *pixel = (*pixel >> 8) | (*pixel << 8); } -void image_endian_swap(image_t* img) -{ - uint16_t* pixel = (uint16_t*)img->data; +void image_endian_swap(image_t *img) { + uint16_t *pixel = (uint16_t *)img->data; for (int i = 0; i < img->w * img->h; i++) { - swap_rgb565(&pixel[i]); + swap_rgb565(&pixel[i]); } } @@ -419,7 +417,7 @@ static mp_obj_t py_camera_snapshot() { img.pixfmt = OMV_PIXFORMAT_GRAYSCALE; } img.data = g_frame->buf; - //image_endian_swap(&img); + // image_endian_swap(&img); return py_image_from_struct(&img); } diff --git a/m5stack/cmodules/omv/modules/py_code_scanner.c b/m5stack/cmodules/omv/modules/py_code_scanner.c old mode 100755 new mode 100644 index d33dc044..744240cf --- a/m5stack/cmodules/omv/modules/py_code_scanner.c +++ b/m5stack/cmodules/omv/modules/py_code_scanner.c @@ -23,7 +23,7 @@ // ================================================================================================= -// object: qrcode +// object: qrcode #define py_qrcode_obj_size 2 typedef struct py_qrcode_obj { @@ -32,17 +32,15 @@ typedef struct py_qrcode_obj { } py_qrcode_obj_t; -static void py_qrcode_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) -{ +static void py_qrcode_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { py_qrcode_obj_t *self = self_in; mp_printf(print, - "qrcode type name: %s, payload: %s\n", - mp_obj_str_get_str(self->type_name), mp_obj_str_get_str(self->data)); + "qrcode type name: %s, payload: %s\n", + mp_obj_str_get_str(self->type_name), mp_obj_str_get_str(self->data)); } -static mp_obj_t py_qrcode_subscr(mp_obj_t self_in, mp_obj_t index, mp_obj_t value) -{ - if (value == MP_OBJ_SENTINEL) { +static mp_obj_t py_qrcode_subscr(mp_obj_t self_in, mp_obj_t index, mp_obj_t value) { + if (value == MP_OBJ_SENTINEL) { py_qrcode_obj_t *self = self_in; if (MP_OBJ_IS_TYPE(index, &mp_type_slice)) { mp_bound_slice_t slice; @@ -54,16 +52,22 @@ static mp_obj_t py_qrcode_subscr(mp_obj_t self_in, mp_obj_t index, mp_obj_t valu return result; } switch (mp_get_index(self->base.type, py_qrcode_obj_size, index, false)) { - case 0: return self->type_name; - case 1: return self->data; + case 0: + return self->type_name; + case 1: + return self->data; } } - return MP_OBJ_NULL; + return MP_OBJ_NULL; } -mp_obj_t py_qrcode_type_name(mp_obj_t self_in) { return ((py_qrcode_obj_t *) self_in)->type_name; } -mp_obj_t py_qrcode_data(mp_obj_t self_in) { return ((py_qrcode_obj_t *) self_in)->data; } +mp_obj_t py_qrcode_type_name(mp_obj_t self_in) { + return ((py_qrcode_obj_t *)self_in)->type_name; +} +mp_obj_t py_qrcode_data(mp_obj_t self_in) { + return ((py_qrcode_obj_t *)self_in)->data; +} static MP_DEFINE_CONST_FUN_OBJ_1(py_qrcode_type_name_obj, py_qrcode_type_name); static MP_DEFINE_CONST_FUN_OBJ_1(py_qrcode_data_obj, py_qrcode_data); @@ -86,19 +90,18 @@ MP_DEFINE_CONST_OBJ_TYPE( // ================================================================================================= -// method: find_qrcodes -static mp_obj_t py_find_qrcodes(mp_obj_t arg_img) -{ - image_t* img = (image_t *)py_image_cobj(arg_img); +// method: find_qrcodes +static mp_obj_t py_find_qrcodes(mp_obj_t arg_img) { + image_t *img = (image_t *)py_image_cobj(arg_img); // Decode Progress esp_image_scanner_t *esp_scan = esp_code_scanner_create(); - esp_code_scanner_config_t config = {ESP_CODE_SCANNER_MODE_FAST, - ESP_CODE_SCANNER_IMAGE_RGB565, - img->w, - img->h}; + esp_code_scanner_config_t config = {ESP_CODE_SCANNER_MODE_FAST, + ESP_CODE_SCANNER_IMAGE_RGB565, + img->w, + img->h}; esp_code_scanner_set_config(esp_scan, config); - + int decoded_num = esp_code_scanner_scan_image(esp_scan, img->data); if (decoded_num) { esp_code_scanner_symbol_t result = esp_code_scanner_result(esp_scan); @@ -117,7 +120,7 @@ static MP_DEFINE_CONST_FUN_OBJ_1(py_find_qrcodes_obj, py_find_qrcodes); // ================================================================================================= -// module: code_scanner +// module: code_scanner static const mp_rom_map_elem_t code_scanner_globals_table[] = { { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_code_scanner) }, { MP_ROM_QSTR(MP_QSTR_qrcode), MP_ROM_PTR(&py_qrcode_type) }, @@ -126,10 +129,8 @@ static const mp_rom_map_elem_t code_scanner_globals_table[] = { static MP_DEFINE_CONST_DICT(code_scanner_globals, code_scanner_globals_table); const mp_obj_module_t module_code_scanner = { - .base = { &mp_type_module }, + .base = { &mp_type_module }, .globals = (mp_obj_dict_t *)&code_scanner_globals, }; MP_REGISTER_MODULE(MP_QSTR_code_scanner, module_code_scanner); - - diff --git a/m5stack/cmodules/omv/modules/py_esp_dl.c b/m5stack/cmodules/omv/modules/py_esp_dl.c old mode 100755 new mode 100644 index accb20f7..8bccd75d --- a/m5stack/cmodules/omv/modules/py_esp_dl.c +++ b/m5stack/cmodules/omv/modules/py_esp_dl.c @@ -187,5 +187,3 @@ const mp_obj_module_t py_module_dl = { }; MP_REGISTER_MODULE(MP_QSTR_dl, py_module_dl); - - diff --git a/m5stack/cmodules/omv/modules/py_esp_dl.h b/m5stack/cmodules/omv/modules/py_esp_dl.h old mode 100755 new mode 100644 index 310861c2..1f435b7d --- a/m5stack/cmodules/omv/modules/py_esp_dl.h +++ b/m5stack/cmodules/omv/modules/py_esp_dl.h @@ -54,5 +54,3 @@ mp_obj_t py_human_face_recognizer_recognize(mp_obj_t self_in, mp_obj_t img, mp_o #ifdef __cplusplus } #endif - - diff --git a/m5stack/cmodules/omv/modules/py_esp_dl_.cpp b/m5stack/cmodules/omv/modules/py_esp_dl_.cpp old mode 100755 new mode 100644 index 85f907dc..9aba867b --- a/m5stack/cmodules/omv/modules/py_esp_dl_.cpp +++ b/m5stack/cmodules/omv/modules/py_esp_dl_.cpp @@ -24,8 +24,8 @@ extern const mp_obj_type_t py_object_detector_type; extern const mp_obj_type_t py_detect_result_type; -static HumanFaceDetect* human_face_detector = nullptr; -static PedestrianDetect* pedestrian_detector = nullptr; +static HumanFaceDetect *human_face_detector = nullptr; +static PedestrianDetect *pedestrian_detector = nullptr; mp_obj_t py_object_detector_make_new(const mp_obj_type_t *type, mp_uint_t n_args, mp_uint_t n_kw, const mp_obj_t *args) { @@ -44,7 +44,7 @@ mp_obj_t py_object_detector_make_new(const mp_obj_type_t *type, mp_uint_t n_args pedestrian_detector = nullptr; } human_face_detector = new HumanFaceDetect(); - } + } } else if (obj->model_id == PEDESTRIAN_DETECT) { if (pedestrian_detector == nullptr) { if (human_face_detector) { @@ -83,11 +83,11 @@ mp_obj_t py_object_detector_infer(mp_obj_t self_in, mp_obj_t img_obj) { py_object_detector_obj_t *self = (py_object_detector_obj_t *)(self_in); image_t *img = (image_t *)py_image_cobj(img_obj); - std::list detect_results; + std::list < dl::detect::result_t > detect_results; if (self->model_id == HUMAN_FACE_DETECT) { - detect_results = human_face_detector->run((uint16_t *)img->data, {img->h, img->w, 3}); + detect_results = human_face_detector->run((uint16_t *)img->data, {img->h, img->w, 3}); } else if (self->model_id == PEDESTRIAN_DETECT) { - detect_results = pedestrian_detector->run((uint16_t *)img->data, {img->h, img->w, 3}); + detect_results = pedestrian_detector->run((uint16_t *)img->data, {img->h, img->w, 3}); } else { // TODO: 使用自定义模型,待实现 } @@ -115,7 +115,7 @@ mp_obj_t py_object_detector_infer(mp_obj_t self_in, mp_obj_t img_obj) { keypoint_tuple[j] = mp_obj_new_int(0); } o->keypoint = mp_obj_new_tuple(10, keypoint_tuple); - } + } mp_obj_list_append(detection_list, o); } return detection_list; @@ -140,8 +140,8 @@ typedef struct { mp_obj_t py_human_face_recognizer_make_new(const mp_obj_type_t *type, mp_uint_t n_args, mp_uint_t n_kw, const mp_obj_t *args) { py_human_face_recognizer_obj_t *obj = m_new_obj(py_human_face_recognizer_obj_t); obj->base.type = &py_human_face_recognizer_type; - obj->recognizer = new FaceRecognizer(static_cast(CONFIG_DB_FILE_SYSTEM), - HumanFaceFeat::model_type_t::MODEL_MFN); + obj->recognizer = new FaceRecognizer(static_cast < dl::recognition::db_type_t > (CONFIG_DB_FILE_SYSTEM), + HumanFaceFeat::model_type_t::MODEL_MFN); return MP_OBJ_FROM_PTR(obj); } @@ -159,7 +159,7 @@ mp_obj_t py_human_face_recognizer_enroll_id(size_t n_args, const mp_obj_t *args) size_t len; mp_obj_t *elem; mp_obj_get_array(args[2], &len, &elem); - std::vector landmarks; + std::vector < int > landmarks; for (uint8_t i = 0; i < 10; i++) { landmarks.push_back(mp_obj_get_int(elem[i])); } @@ -198,39 +198,36 @@ mp_obj_t py_human_face_recognizer_clear_id(mp_obj_t self_in) { } mp_obj_t py_human_face_recognizer_recognize(mp_obj_t self_in, mp_obj_t img_obj, mp_obj_t keypoint) { - py_human_face_recognizer_obj_t* self = (py_human_face_recognizer_obj_t*)(self_in); - image_t* img = (image_t*)py_image_cobj(img_obj); + py_human_face_recognizer_obj_t *self = (py_human_face_recognizer_obj_t *)(self_in); + image_t *img = (image_t *)py_image_cobj(img_obj); size_t len; - mp_obj_t* elem; + mp_obj_t *elem; mp_obj_get_array(keypoint, &len, &elem); - std::vector> landmarks; - std::vector temp; + std::vector < std::vector < int >> landmarks; + std::vector < int > temp; for (uint8_t i = 0; i < 10; i++) { - temp.push_back(mp_obj_get_int(elem[i])); + temp.push_back(mp_obj_get_int(elem[i])); } - landmarks.push_back(temp); + landmarks.push_back(temp); - std::vector> res; - res = self->recognizer->recognize((uint16_t*)img->data, {(int)img->h, (int)img->w, 3}, landmarks); + std::vector < std::list < dl::recognition::query_info >> res; + res = self->recognizer->recognize((uint16_t *)img->data, {(int)img->h, (int)img->w, 3}, landmarks); py_recognize_result_obj_t *o = m_new_obj(py_recognize_result_obj_t); o->base.type = &py_recognize_result_type; o->similarity = mp_obj_new_float(0); o->id = mp_obj_new_int(0); if (res.size() == 1) { - for (const auto &top_k : res) { + for (const auto &top_k : res) { for (const auto &k : top_k) { - o->similarity = mp_obj_new_float(k.similarity); - o->id = mp_obj_new_int(k.id); + o->similarity = mp_obj_new_float(k.similarity); + o->id = mp_obj_new_int(k.id); } - } + } } else { - //return mp_const_none; // TODO: 待优化 + // return mp_const_none; // TODO: 待优化 } - - return MP_OBJ_FROM_PTR(o); -} - - + return MP_OBJ_FROM_PTR(o); +} diff --git a/m5stack/cmodules/omv/modules/py_helper.c b/m5stack/cmodules/omv/modules/py_helper.c old mode 100755 new mode 100644 index 4ee92dd3..4ee512ad --- a/m5stack/cmodules/omv/modules/py_helper.c +++ b/m5stack/cmodules/omv/modules/py_helper.c @@ -1,156 +1,152 @@ -/* - * SPDX-License-Identifier: MIT - * - * Copyright (C) 2013-2024 OpenMV, LLC. - * Copyright (c) 2024 M5Stack Technology CO LTD - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * Python helper functions. - */ - -#include "py/obj.h" -#include "py/runtime.h" -#include "py_helper.h" -#include "py_image.h" - -#include "cmsis_iccarm.h" - - - -image_t *py_helper_arg_to_image(const mp_obj_t arg, uint32_t flags) { - image_t *image = NULL; - image = py_image_cobj(arg); - if (flags) { - if ((flags & ARG_IMAGE_MUTABLE) && !image->is_mutable) { - mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("Expected a mutable image")); - } else if ((flags & ARG_IMAGE_UNCOMPRESSED) && image->is_compressed) { - mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("Expected an uncompressed image")); - } else if ((flags & ARG_IMAGE_GRAYSCALE) && image->pixfmt != OMV_PIXFORMAT_GRAYSCALE) { - mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("Expected an uncompressed image")); - } - } - return image; -} - - -uint py_helper_consume_array(size_t n_args, const mp_obj_t *args, uint arg_index, size_t len, const mp_obj_t **items) -{ - if (MP_OBJ_IS_TYPE(args[arg_index], &mp_type_tuple) || MP_OBJ_IS_TYPE(args[arg_index], &mp_type_list)) { - mp_obj_get_array_fixed_n(args[arg_index], len, (mp_obj_t **) items); - return arg_index + 1; - } else { - //PY_ASSERT_TRUE_MSG((n_args - arg_index) >= len, "Not enough positional arguments!"); - *items = args + arg_index; - return arg_index + len; - } -} - -float py_helper_keyword_float(uint n_args, const mp_obj_t *args, uint arg_index, - mp_map_t *kw_args, mp_obj_t kw, float default_val) { - mp_map_elem_t *kw_arg = mp_map_lookup(kw_args, kw, MP_MAP_LOOKUP); - - if (kw_arg) { - default_val = mp_obj_get_float(kw_arg->value); - } else if (n_args > arg_index) { - default_val = mp_obj_get_float(args[arg_index]); - } - - return default_val; -} - -int py_helper_keyword_color(image_t *img, uint n_args, const mp_obj_t *args, uint arg_index, - mp_map_t *kw_args, int default_val) { - mp_map_elem_t *kw_arg = kw_args ? mp_map_lookup(kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_color), MP_MAP_LOOKUP) : NULL; - - if (kw_arg) { - if (mp_obj_is_integer(kw_arg->value)) { - //default_val = mp_obj_get_int(kw_arg->value); - int tmp = mp_obj_get_int(kw_arg->value); - int r, g, b; - r = tmp >> 8 & 0x00F800; - g = tmp >> 5 & 0x0007E0; - b = tmp >> 3 & 0x00001F; - default_val = (r | g | b); - } else { - mp_obj_t *arg_color; - mp_obj_get_array_fixed_n(kw_arg->value, 3, &arg_color); - default_val = COLOR_R8_G8_B8_TO_RGB565(__USAT(mp_obj_get_int(arg_color[0]), 8), - __USAT(mp_obj_get_int(arg_color[1]), 8), - __USAT(mp_obj_get_int(arg_color[2]), 8)); - - switch (img->pixfmt) { - case OMV_PIXFORMAT_BINARY: { - default_val = COLOR_RGB565_TO_BINARY(default_val); - break; - } - case OMV_PIXFORMAT_GRAYSCALE: { - default_val = COLOR_RGB565_TO_GRAYSCALE(default_val); - break; - } - default: { - break; - } - } - } - } else if (n_args > arg_index) { - if (mp_obj_is_integer(args[arg_index])) { - default_val = mp_obj_get_int(args[arg_index]); - } else { - mp_obj_t *arg_color; - mp_obj_get_array_fixed_n(args[arg_index], 3, &arg_color); - default_val = COLOR_R8_G8_B8_TO_RGB565(__USAT(mp_obj_get_int(arg_color[0]), 8), - __USAT(mp_obj_get_int(arg_color[1]), 8), - __USAT(mp_obj_get_int(arg_color[2]), 8)); - switch (img->pixfmt) { - case OMV_PIXFORMAT_BINARY: { - default_val = COLOR_RGB565_TO_BINARY(default_val); - break; - } - case OMV_PIXFORMAT_GRAYSCALE: { - default_val = COLOR_RGB565_TO_GRAYSCALE(default_val); - break; - } - default: { - break; - } - } - } - } - - default_val = (default_val << 8) | (default_val >> 8); // swap endian for esp32 - return default_val; -} - - -int py_helper_keyword_int(size_t n_args, const mp_obj_t *args, uint arg_index, - mp_map_t *kw_args, mp_obj_t kw, int default_val) -{ - mp_map_elem_t *kw_arg = mp_map_lookup(kw_args, kw, MP_MAP_LOOKUP); - - if (kw_arg) { - default_val = mp_obj_get_int(kw_arg->value); - } else if (n_args > arg_index) { - default_val = mp_obj_get_int(args[arg_index]); - } - - return default_val; -} - - +/* + * SPDX-License-Identifier: MIT + * + * Copyright (C) 2013-2024 OpenMV, LLC. + * Copyright (c) 2024 M5Stack Technology CO LTD + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * Python helper functions. + */ + +#include "py/obj.h" +#include "py/runtime.h" +#include "py_helper.h" +#include "py_image.h" + +#include "cmsis_iccarm.h" + + + +image_t *py_helper_arg_to_image(const mp_obj_t arg, uint32_t flags) { + image_t *image = NULL; + image = py_image_cobj(arg); + if (flags) { + if ((flags & ARG_IMAGE_MUTABLE) && !image->is_mutable) { + mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("Expected a mutable image")); + } else if ((flags & ARG_IMAGE_UNCOMPRESSED) && image->is_compressed) { + mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("Expected an uncompressed image")); + } else if ((flags & ARG_IMAGE_GRAYSCALE) && image->pixfmt != OMV_PIXFORMAT_GRAYSCALE) { + mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("Expected an uncompressed image")); + } + } + return image; +} + + +uint py_helper_consume_array(size_t n_args, const mp_obj_t *args, uint arg_index, size_t len, const mp_obj_t **items) { + if (MP_OBJ_IS_TYPE(args[arg_index], &mp_type_tuple) || MP_OBJ_IS_TYPE(args[arg_index], &mp_type_list)) { + mp_obj_get_array_fixed_n(args[arg_index], len, (mp_obj_t **)items); + return arg_index + 1; + } else { + // PY_ASSERT_TRUE_MSG((n_args - arg_index) >= len, "Not enough positional arguments!"); + *items = args + arg_index; + return arg_index + len; + } +} + +float py_helper_keyword_float(uint n_args, const mp_obj_t *args, uint arg_index, + mp_map_t *kw_args, mp_obj_t kw, float default_val) { + mp_map_elem_t *kw_arg = mp_map_lookup(kw_args, kw, MP_MAP_LOOKUP); + + if (kw_arg) { + default_val = mp_obj_get_float(kw_arg->value); + } else if (n_args > arg_index) { + default_val = mp_obj_get_float(args[arg_index]); + } + + return default_val; +} + +int py_helper_keyword_color(image_t *img, uint n_args, const mp_obj_t *args, uint arg_index, + mp_map_t *kw_args, int default_val) { + mp_map_elem_t *kw_arg = kw_args ? mp_map_lookup(kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_color), MP_MAP_LOOKUP) : NULL; + + if (kw_arg) { + if (mp_obj_is_integer(kw_arg->value)) { + // default_val = mp_obj_get_int(kw_arg->value); + int tmp = mp_obj_get_int(kw_arg->value); + int r, g, b; + r = tmp >> 8 & 0x00F800; + g = tmp >> 5 & 0x0007E0; + b = tmp >> 3 & 0x00001F; + default_val = (r | g | b); + } else { + mp_obj_t *arg_color; + mp_obj_get_array_fixed_n(kw_arg->value, 3, &arg_color); + default_val = COLOR_R8_G8_B8_TO_RGB565(__USAT(mp_obj_get_int(arg_color[0]), 8), + __USAT(mp_obj_get_int(arg_color[1]), 8), + __USAT(mp_obj_get_int(arg_color[2]), 8)); + + switch (img->pixfmt) { + case OMV_PIXFORMAT_BINARY: { + default_val = COLOR_RGB565_TO_BINARY(default_val); + break; + } + case OMV_PIXFORMAT_GRAYSCALE: { + default_val = COLOR_RGB565_TO_GRAYSCALE(default_val); + break; + } + default: { + break; + } + } + } + } else if (n_args > arg_index) { + if (mp_obj_is_integer(args[arg_index])) { + default_val = mp_obj_get_int(args[arg_index]); + } else { + mp_obj_t *arg_color; + mp_obj_get_array_fixed_n(args[arg_index], 3, &arg_color); + default_val = COLOR_R8_G8_B8_TO_RGB565(__USAT(mp_obj_get_int(arg_color[0]), 8), + __USAT(mp_obj_get_int(arg_color[1]), 8), + __USAT(mp_obj_get_int(arg_color[2]), 8)); + switch (img->pixfmt) { + case OMV_PIXFORMAT_BINARY: { + default_val = COLOR_RGB565_TO_BINARY(default_val); + break; + } + case OMV_PIXFORMAT_GRAYSCALE: { + default_val = COLOR_RGB565_TO_GRAYSCALE(default_val); + break; + } + default: { + break; + } + } + } + } + + default_val = (default_val << 8) | (default_val >> 8); // swap endian for esp32 + return default_val; +} + + +int py_helper_keyword_int(size_t n_args, const mp_obj_t *args, uint arg_index, + mp_map_t *kw_args, mp_obj_t kw, int default_val) { + mp_map_elem_t *kw_arg = mp_map_lookup(kw_args, kw, MP_MAP_LOOKUP); + + if (kw_arg) { + default_val = mp_obj_get_int(kw_arg->value); + } else if (n_args > arg_index) { + default_val = mp_obj_get_int(args[arg_index]); + } + + return default_val; +} diff --git a/m5stack/cmodules/omv/modules/py_helper.h b/m5stack/cmodules/omv/modules/py_helper.h old mode 100755 new mode 100644 index 3a22ce9f..3d5a00ee --- a/m5stack/cmodules/omv/modules/py_helper.h +++ b/m5stack/cmodules/omv/modules/py_helper.h @@ -1,41 +1,40 @@ -/* - * This file is part of the OpenMV project. - * - * Copyright (c) 2013-2021 Ibrahim Abdelkader - * Copyright (c) 2013-2021 Kwabena W. Agyeman - * - * This work is licensed under the MIT license, see the file LICENSE for details. - * - * Python helper functions. - */ -#ifndef __PY_HELPER_H__ -#define __PY_HELPER_H__ - -#include "py/obj.h" -#include "imlib.h" - - -typedef enum py_helper_arg_image_flags { - ARG_IMAGE_ANY = (0 << 0), - ARG_IMAGE_MUTABLE = (1 << 0), - ARG_IMAGE_UNCOMPRESSED = (1 << 1), - ARG_IMAGE_GRAYSCALE = (1 << 2), - ARG_IMAGE_ALLOC = (1 << 3) -} py_helper_arg_image_flags_t; - -image_t *py_helper_arg_to_image(const mp_obj_t arg, uint32_t flags); - -uint py_helper_consume_array(size_t n_args, const mp_obj_t *args, uint arg_index, size_t len, const mp_obj_t **items); - -int py_helper_keyword_int(size_t n_args, const mp_obj_t *args, uint arg_index, - mp_map_t *kw_args, mp_obj_t kw, int default_val); - -float py_helper_keyword_float(uint n_args, const mp_obj_t *args, uint arg_index, - mp_map_t *kw_args, mp_obj_t kw, float default_val); - -int py_helper_keyword_color(image_t *img, uint n_args, const mp_obj_t *args, uint arg_index, - mp_map_t *kw_args, int default_val); - - -#endif // __PY_HELPER__ - +/* + * This file is part of the OpenMV project. + * + * Copyright (c) 2013-2021 Ibrahim Abdelkader + * Copyright (c) 2013-2021 Kwabena W. Agyeman + * + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * Python helper functions. + */ +#ifndef __PY_HELPER_H__ +#define __PY_HELPER_H__ + +#include "py/obj.h" +#include "imlib.h" + + +typedef enum py_helper_arg_image_flags { + ARG_IMAGE_ANY = (0 << 0), + ARG_IMAGE_MUTABLE = (1 << 0), + ARG_IMAGE_UNCOMPRESSED = (1 << 1), + ARG_IMAGE_GRAYSCALE = (1 << 2), + ARG_IMAGE_ALLOC = (1 << 3) +} py_helper_arg_image_flags_t; + +image_t *py_helper_arg_to_image(const mp_obj_t arg, uint32_t flags); + +uint py_helper_consume_array(size_t n_args, const mp_obj_t *args, uint arg_index, size_t len, const mp_obj_t **items); + +int py_helper_keyword_int(size_t n_args, const mp_obj_t *args, uint arg_index, + mp_map_t *kw_args, mp_obj_t kw, int default_val); + +float py_helper_keyword_float(uint n_args, const mp_obj_t *args, uint arg_index, + mp_map_t *kw_args, mp_obj_t kw, float default_val); + +int py_helper_keyword_color(image_t *img, uint n_args, const mp_obj_t *args, uint arg_index, + mp_map_t *kw_args, int default_val); + + +#endif // __PY_HELPER__ diff --git a/m5stack/cmodules/omv/modules/py_image.c b/m5stack/cmodules/omv/modules/py_image.c old mode 100755 new mode 100644 index dc59e654..eed2d353 --- a/m5stack/cmodules/omv/modules/py_image.c +++ b/m5stack/cmodules/omv/modules/py_image.c @@ -1,445 +1,435 @@ -/* - * SPDX-License-Identifier: MIT - * - * Copyright (C) 2013-2024 OpenMV, LLC. - * Copyright (c) 2024 M5Stack Technology CO LTD - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * Image Python module. - */ -#include "py_image.h" -#include -#include -#include "py/nlr.h" -#include "py/obj.h" -#include "py/objlist.h" -#include "py/objstr.h" -#include "py/objtuple.h" -#include "py/objtype.h" -#include "py/runtime.h" -#include "py/mphal.h" - -#include "imlib.h" -#include "py_helper.h" -#include "py_assert.h" - - - -#define PY_ASSERT_TYPE(obj, type) \ - do { \ - __typeof__ (obj) _a = (obj); \ - __typeof__ (type) _b = (type); \ - if (!MP_OBJ_IS_TYPE(_a, _b)) { \ - mp_raise_msg_varg(&mp_type_TypeError, \ - MP_ERROR_TEXT( \ - "Can't convert %s to %s"), \ - mp_obj_get_type_str(_a), \ - mp_obj_get_type_str(_b)); \ - } \ - } while (0) - -size_t image_size(image_t *img) -{ - return img->size; -} - - -//================================================================================== -// class: image.Image -//================================================================================== - - -//================================================================================== -// Basic Methods -//================================================================================== -const mp_obj_type_t py_image_type; - - -void *py_image_cobj(mp_obj_t img_obj) -{ - PY_ASSERT_TYPE(img_obj, &py_image_type); - return &((py_image_obj_t *) img_obj)->_cobj; -} - - -static mp_obj_t py_image_width(mp_obj_t img_obj) { - return mp_obj_new_int(((image_t *) py_image_cobj(img_obj))->w); -} -static MP_DEFINE_CONST_FUN_OBJ_1(py_image_width_obj, py_image_width); - -static mp_obj_t py_image_height(mp_obj_t img_obj) { - return mp_obj_new_int(((image_t *) py_image_cobj(img_obj))->h); -} -static MP_DEFINE_CONST_FUN_OBJ_1(py_image_height_obj, py_image_height); - -static mp_obj_t py_image_format(mp_obj_t img_obj) { - image_t *image = py_image_cobj(img_obj); - switch (image->pixfmt) { - case OMV_PIXFORMAT_BINARY: - return mp_obj_new_int(OMV_PIXFORMAT_BINARY); - case OMV_PIXFORMAT_GRAYSCALE: - return mp_obj_new_int(OMV_PIXFORMAT_GRAYSCALE); - case OMV_PIXFORMAT_RGB565: - return mp_obj_new_int(OMV_PIXFORMAT_RGB565); - case OMV_PIXFORMAT_BAYER_ANY: - return mp_obj_new_int(OMV_PIXFORMAT_BAYER); - case OMV_PIXFORMAT_YUV_ANY: - return mp_obj_new_int(OMV_PIXFORMAT_YUV422); - case OMV_PIXFORMAT_JPEG: - return mp_obj_new_int(OMV_PIXFORMAT_JPEG); - case OMV_PIXFORMAT_PNG: - return mp_obj_new_int(OMV_PIXFORMAT_PNG); - default: - return mp_obj_new_int(OMV_PIXFORMAT_INVALID); - } -} -static MP_DEFINE_CONST_FUN_OBJ_1(py_image_format_obj, py_image_format); - -static mp_obj_t py_image_size(mp_obj_t img_obj) { - return mp_obj_new_int(image_size((image_t *) py_image_cobj(img_obj))); -} -static MP_DEFINE_CONST_FUN_OBJ_1(py_image_size_obj, py_image_size); - -static mp_obj_t py_image_bytearray(mp_obj_t img_obj) { - image_t *arg_img = (image_t *) py_image_cobj(img_obj); - return mp_obj_new_bytearray_by_ref(image_size(arg_img), arg_img->data); -} -static MP_DEFINE_CONST_FUN_OBJ_1(py_image_bytearray_obj, py_image_bytearray); - - -//==================================================================================== -// Drawings Methods -//==================================================================================== -static mp_obj_t py_image_clear(mp_obj_t img_obj) -{ - image_t *arg_img = (image_t *) py_image_cobj(img_obj); - memset(arg_img->pixels, 0, image_size(arg_img)); - return img_obj; -} -static MP_DEFINE_CONST_FUN_OBJ_1(py_image_clear_obj, py_image_clear); - -static mp_obj_t py_image_draw_line(size_t n_args, const mp_obj_t *args, mp_map_t *kw_args) -{ - const mp_obj_t *arg_vec; - uint offset = py_helper_consume_array(n_args, args, 0, 4, &arg_vec); - - image_t *arg_img = (image_t *) py_image_cobj(arg_vec[0]); - int arg_x0 = mp_obj_get_int(arg_vec[1]); - int arg_y0 = mp_obj_get_int(arg_vec[2]); - int arg_x1 = mp_obj_get_int(arg_vec[3]); - int arg_y1 = mp_obj_get_int(arg_vec[4]); - //int arg_c = py_helper_keyword_color(n_args, args, offset + 0, kw_args, -1); - int arg_c = py_helper_keyword_color(arg_img, n_args, args, offset + 0, kw_args, -1); // White. - int arg_thickness = py_helper_keyword_int(n_args, args, offset + 1, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_thickness), 1); - - imlib_draw_line(arg_img, arg_x0, arg_y0, arg_x1, arg_y1, arg_c, arg_thickness); - - return arg_vec[0]; -} -static MP_DEFINE_CONST_FUN_OBJ_KW(py_image_draw_line_obj, 3, py_image_draw_line); - -static mp_obj_t py_image_draw_rectangle(size_t n_args, const mp_obj_t *args, mp_map_t *kw_args) -{ - const mp_obj_t *arg_vec; - uint offset = py_helper_consume_array(n_args, args, 0, 4, &arg_vec); - - image_t *arg_img = (image_t *) py_image_cobj(arg_vec[0]); - int arg_rx = mp_obj_get_int(arg_vec[1]); - int arg_ry = mp_obj_get_int(arg_vec[2]); - int arg_rw = mp_obj_get_int(arg_vec[3]); - int arg_rh = mp_obj_get_int(arg_vec[4]); - //int arg_c = py_helper_keyword_color(n_args, args, offset + 0, kw_args, -1); - int arg_c = py_helper_keyword_color(arg_img, n_args, args, offset + 0, kw_args, -1); // White. - int arg_thickness = py_helper_keyword_int(n_args, args, offset + 1, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_thickness), 1); - int arg_fill = py_helper_keyword_int(n_args, args, offset + 2, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_fill), 1); - - imlib_draw_rectangle(arg_img, arg_rx, arg_ry, arg_rw, arg_rh, arg_c, arg_thickness, arg_fill); - - return arg_vec[0]; -} -static MP_DEFINE_CONST_FUN_OBJ_KW(py_image_draw_rectangle_obj, 4, py_image_draw_rectangle); - -static mp_obj_t py_image_draw_circle(size_t n_args, const mp_obj_t *args, mp_map_t *kw_args) -{ - const mp_obj_t *arg_vec; - uint offset = py_helper_consume_array(n_args, args, 0, 3, &arg_vec); - - image_t *arg_img = (image_t *) py_image_cobj(arg_vec[0]); - int arg_cx = mp_obj_get_int(arg_vec[1]); - int arg_cy = mp_obj_get_int(arg_vec[2]); - int arg_radius = mp_obj_get_int(arg_vec[3]); - //int arg_c = py_helper_keyword_color(n_args, args, offset + 0, kw_args, -1); - int arg_c = py_helper_keyword_color(arg_img, n_args, args, offset + 0, kw_args, -1); // White. - int arg_thickness = py_helper_keyword_int(n_args, args, offset + 1, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_thickness), 1); - int arg_fill = py_helper_keyword_int(n_args, args, offset + 2, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_fill), 1); - - imlib_draw_circle(arg_img, arg_cx, arg_cy, arg_radius, arg_c, arg_thickness, arg_fill); - - return arg_vec[0]; -} -static MP_DEFINE_CONST_FUN_OBJ_KW(py_image_draw_circle_obj, 4, py_image_draw_circle); - -static mp_obj_t py_image_draw_ellipse(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) { - image_t *arg_img = py_helper_arg_to_image(args[0], ARG_IMAGE_MUTABLE); - - const mp_obj_t *arg_vec; - uint offset = py_helper_consume_array(n_args, args, 1, 5, &arg_vec); - int arg_cx = mp_obj_get_int(arg_vec[0]); - int arg_cy = mp_obj_get_int(arg_vec[1]); - int arg_rx = mp_obj_get_int(arg_vec[2]); - int arg_ry = mp_obj_get_int(arg_vec[3]); - int arg_r = mp_obj_get_int(arg_vec[4]); - - int arg_c = - py_helper_keyword_color(arg_img, n_args, args, offset + 1, kw_args, -1); // White. - int arg_thickness = - py_helper_keyword_int(n_args, args, offset + 2, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_thickness), 1); - bool arg_fill = - py_helper_keyword_int(n_args, args, offset + 3, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_fill), false); - - imlib_draw_ellipse(arg_img, arg_cx, arg_cy, arg_rx, arg_ry, arg_r, arg_c, arg_thickness, arg_fill); - return args[0]; -} -static MP_DEFINE_CONST_FUN_OBJ_KW(py_image_draw_ellipse_obj, 2, py_image_draw_ellipse); - -static mp_obj_t py_image_draw_string(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) { - image_t *arg_img = py_helper_arg_to_image(args[0], ARG_IMAGE_MUTABLE); - - const mp_obj_t *arg_vec; - uint offset = py_helper_consume_array(n_args, args, 1, 3, &arg_vec); - int arg_x_off = mp_obj_get_int(arg_vec[0]); - int arg_y_off = mp_obj_get_int(arg_vec[1]); - const char *arg_str = mp_obj_str_get_str(arg_vec[2]); - - int arg_c = - py_helper_keyword_color(arg_img, n_args, args, offset + 0, kw_args, -1); // White. - float arg_scale = - py_helper_keyword_float(n_args, args, offset + 1, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_scale), 1.0); - PY_ASSERT_TRUE_MSG(0 < arg_scale, "Error: 0 < scale!"); - int arg_x_spacing = - py_helper_keyword_int(n_args, args, offset + 2, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_x_spacing), 0); - int arg_y_spacing = - py_helper_keyword_int(n_args, args, offset + 3, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_y_spacing), 0); - bool arg_mono_space = - py_helper_keyword_int(n_args, args, offset + 4, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_mono_space), true); - int arg_char_rotation = - py_helper_keyword_int(n_args, args, offset + 5, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_char_rotation), 0); - int arg_char_hmirror = - py_helper_keyword_int(n_args, args, offset + 6, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_char_hmirror), false); - int arg_char_vflip = - py_helper_keyword_int(n_args, args, offset + 7, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_char_vflip), false); - int arg_string_rotation = - py_helper_keyword_int(n_args, args, offset + 8, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_string_rotation), 0); - int arg_string_hmirror = - py_helper_keyword_int(n_args, args, offset + 9, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_string_hmirror), false); - int arg_string_vflip = - py_helper_keyword_int(n_args, args, offset + 10, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_string_vflip), false); - - imlib_draw_string(arg_img, arg_x_off, arg_y_off, arg_str, - arg_c, arg_scale, arg_x_spacing, arg_y_spacing, arg_mono_space, - arg_char_rotation, arg_char_hmirror, arg_char_vflip, - arg_string_rotation, arg_string_hmirror, arg_string_vflip); - return args[0]; -} -static MP_DEFINE_CONST_FUN_OBJ_KW(py_image_draw_string_obj, 2, py_image_draw_string); - -static mp_obj_t py_image_draw_cross(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) { - image_t *arg_img = py_helper_arg_to_image(args[0], ARG_IMAGE_MUTABLE); - - const mp_obj_t *arg_vec; - uint offset = py_helper_consume_array(n_args, args, 1, 2, &arg_vec); - int arg_x = mp_obj_get_int(arg_vec[0]); - int arg_y = mp_obj_get_int(arg_vec[1]); - - int arg_c = - py_helper_keyword_color(arg_img, n_args, args, offset + 0, kw_args, -1); // White. - int arg_s = - py_helper_keyword_int(n_args, args, offset + 1, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_size), 5); - int arg_thickness = - py_helper_keyword_int(n_args, args, offset + 2, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_thickness), 1); - - imlib_draw_line(arg_img, arg_x - arg_s, arg_y, arg_x + arg_s, arg_y, arg_c, arg_thickness); - imlib_draw_line(arg_img, arg_x, arg_y - arg_s, arg_x, arg_y + arg_s, arg_c, arg_thickness); - return args[0]; -} -static MP_DEFINE_CONST_FUN_OBJ_KW(py_image_draw_cross_obj, 2, py_image_draw_cross); - -static mp_obj_t py_image_draw_arrow(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) { - image_t *arg_img = py_helper_arg_to_image(args[0], ARG_IMAGE_MUTABLE); - - const mp_obj_t *arg_vec; - uint offset = py_helper_consume_array(n_args, args, 1, 4, &arg_vec); - int arg_x0 = mp_obj_get_int(arg_vec[0]); - int arg_y0 = mp_obj_get_int(arg_vec[1]); - int arg_x1 = mp_obj_get_int(arg_vec[2]); - int arg_y1 = mp_obj_get_int(arg_vec[3]); - - int arg_c = - py_helper_keyword_color(arg_img, n_args, args, offset + 0, kw_args, -1); // White. - int arg_s = - py_helper_keyword_int(n_args, args, offset + 1, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_size), 10); - int arg_thickness = - py_helper_keyword_int(n_args, args, offset + 2, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_thickness), 1); - - int dx = (arg_x1 - arg_x0); - int dy = (arg_y1 - arg_y0); - float length = fast_sqrtf((dx * dx) + (dy * dy)); - - float ux = IM_DIV(dx, length); - float uy = IM_DIV(dy, length); - float vx = -uy; - float vy = ux; - - int a0x = fast_roundf(arg_x1 - (arg_s * ux) + (arg_s * vx * 0.5)); - int a0y = fast_roundf(arg_y1 - (arg_s * uy) + (arg_s * vy * 0.5)); - int a1x = fast_roundf(arg_x1 - (arg_s * ux) - (arg_s * vx * 0.5)); - int a1y = fast_roundf(arg_y1 - (arg_s * uy) - (arg_s * vy * 0.5)); - - imlib_draw_line(arg_img, arg_x0, arg_y0, arg_x1, arg_y1, arg_c, arg_thickness); - imlib_draw_line(arg_img, arg_x1, arg_y1, a0x, a0y, arg_c, arg_thickness); - imlib_draw_line(arg_img, arg_x1, arg_y1, a1x, a1y, arg_c, arg_thickness); - return args[0]; -} -static MP_DEFINE_CONST_FUN_OBJ_KW(py_image_draw_arrow_obj, 2, py_image_draw_arrow); - -static mp_obj_t py_image_draw_edges(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) { - image_t *arg_img = py_helper_arg_to_image(args[0], ARG_IMAGE_MUTABLE); - - mp_obj_t *corners, *p0, *p1, *p2, *p3; - mp_obj_get_array_fixed_n(args[1], 4, &corners); - mp_obj_get_array_fixed_n(corners[0], 2, &p0); - mp_obj_get_array_fixed_n(corners[1], 2, &p1); - mp_obj_get_array_fixed_n(corners[2], 2, &p2); - mp_obj_get_array_fixed_n(corners[3], 2, &p3); - - int x0, y0, x1, y1, x2, y2, x3, y3; - x0 = mp_obj_get_int(p0[0]); - y0 = mp_obj_get_int(p0[1]); - x1 = mp_obj_get_int(p1[0]); - y1 = mp_obj_get_int(p1[1]); - x2 = mp_obj_get_int(p2[0]); - y2 = mp_obj_get_int(p2[1]); - x3 = mp_obj_get_int(p3[0]); - y3 = mp_obj_get_int(p3[1]); - - int arg_c = - py_helper_keyword_color(arg_img, n_args, args, 2, kw_args, -1); // White. - int arg_s = - py_helper_keyword_int(n_args, args, 3, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_size), 0); - int arg_thickness = - py_helper_keyword_int(n_args, args, 4, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_thickness), 1); - bool arg_fill = - py_helper_keyword_int(n_args, args, 5, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_fill), false); - - imlib_draw_line(arg_img, x0, y0, x1, y1, arg_c, arg_thickness); - imlib_draw_line(arg_img, x1, y1, x2, y2, arg_c, arg_thickness); - imlib_draw_line(arg_img, x2, y2, x3, y3, arg_c, arg_thickness); - imlib_draw_line(arg_img, x3, y3, x0, y0, arg_c, arg_thickness); - - if (arg_s >= 1) { - imlib_draw_circle(arg_img, x0, y0, arg_s, arg_c, arg_thickness, arg_fill); - imlib_draw_circle(arg_img, x1, y1, arg_s, arg_c, arg_thickness, arg_fill); - imlib_draw_circle(arg_img, x2, y2, arg_s, arg_c, arg_thickness, arg_fill); - imlib_draw_circle(arg_img, x3, y3, arg_s, arg_c, arg_thickness, arg_fill); - } - - return args[0]; -} -static MP_DEFINE_CONST_FUN_OBJ_KW(py_image_draw_edges_obj, 2, py_image_draw_edges); - - - - -mp_obj_t py_image(int w, int h, omv_pixformat_t pixfmt, uint32_t size, void *pixels) -{ - py_image_obj_t *o = m_new_obj(py_image_obj_t); - o->base.type = &py_image_type; - o->_cobj.w = w; - o->_cobj.h = h; - o->_cobj.size = size; - o->_cobj.pixfmt = pixfmt; - o->_cobj.pixels = pixels; - return o; -} - -mp_obj_t py_image_from_struct(image_t *img) -{ - py_image_obj_t *o = m_new_obj(py_image_obj_t); - o->base.type = &py_image_type; - o->_cobj = *img; - return o; -} - - -static const mp_rom_map_elem_t locals_dict_table[] = { - /* Basic Methods */ - {MP_ROM_QSTR(MP_QSTR_width), MP_ROM_PTR(&py_image_width_obj)}, - {MP_ROM_QSTR(MP_QSTR_height), MP_ROM_PTR(&py_image_height_obj)}, - {MP_ROM_QSTR(MP_QSTR_format), MP_ROM_PTR(&py_image_format_obj)}, - {MP_ROM_QSTR(MP_QSTR_size), MP_ROM_PTR(&py_image_size_obj)}, - {MP_ROM_QSTR(MP_QSTR_bytearray), MP_ROM_PTR(&py_image_bytearray_obj)}, - /* Drawing Methods */ - {MP_ROM_QSTR(MP_QSTR_clear), MP_ROM_PTR(&py_image_clear_obj)}, - {MP_ROM_QSTR(MP_QSTR_draw_line), MP_ROM_PTR(&py_image_draw_line_obj)}, - {MP_ROM_QSTR(MP_QSTR_draw_rectangle), MP_ROM_PTR(&py_image_draw_rectangle_obj)}, - {MP_ROM_QSTR(MP_QSTR_draw_circle), MP_ROM_PTR(&py_image_draw_circle_obj)}, - {MP_ROM_QSTR(MP_QSTR_draw_ellipse), MP_ROM_PTR(&py_image_draw_ellipse_obj)}, - {MP_ROM_QSTR(MP_QSTR_draw_string), MP_ROM_PTR(&py_image_draw_string_obj)}, - {MP_ROM_QSTR(MP_QSTR_draw_cross), MP_ROM_PTR(&py_image_draw_cross_obj)}, - {MP_ROM_QSTR(MP_QSTR_draw_arrow), MP_ROM_PTR(&py_image_draw_arrow_obj)}, - {MP_ROM_QSTR(MP_QSTR_draw_edges), MP_ROM_PTR(&py_image_draw_edges_obj)}, -}; - -static MP_DEFINE_CONST_DICT(py_image_locals_dict, locals_dict_table); - -MP_DEFINE_CONST_OBJ_TYPE( - py_image_type, - MP_QSTR_Image, - MP_TYPE_FLAG_NONE, - locals_dict, &py_image_locals_dict -); - - - -//================================================================================== -// module: image -//================================================================================== - -static const mp_rom_map_elem_t globals_dict_table[] = { - {MP_ROM_QSTR(MP_QSTR___name__), MP_OBJ_NEW_QSTR(MP_QSTR_image)}, - // Pixel formats - {MP_ROM_QSTR(MP_QSTR_GRAYSCALE), MP_ROM_INT(OMV_PIXFORMAT_GRAYSCALE)},/* 1BPP/GRAYSCALE*/ - {MP_ROM_QSTR(MP_QSTR_RGB565), MP_ROM_INT(OMV_PIXFORMAT_RGB565)}, /* 2BPP/RGB565*/ - {MP_ROM_QSTR(MP_QSTR_YUV422), MP_ROM_INT(OMV_PIXFORMAT_YUV422)}, /* 2BPP/YUV422*/ - {MP_ROM_QSTR(MP_QSTR_JPEG), MP_ROM_INT(OMV_PIXFORMAT_JPEG)}, /* JPEG/COMPRESSED*/ - // - {MP_ROM_QSTR(MP_QSTR_Image), MP_ROM_PTR(&py_image_type)}, -}; - -static MP_DEFINE_CONST_DICT(globals_dict, globals_dict_table); - -const mp_obj_module_t image_module = { - .base = { &mp_type_module }, - .globals = (mp_obj_t) &globals_dict -}; - -MP_REGISTER_MODULE(MP_QSTR_image, image_module); - - +/* + * SPDX-License-Identifier: MIT + * + * Copyright (C) 2013-2024 OpenMV, LLC. + * Copyright (c) 2024 M5Stack Technology CO LTD + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * Image Python module. + */ +#include "py_image.h" +#include +#include +#include "py/nlr.h" +#include "py/obj.h" +#include "py/objlist.h" +#include "py/objstr.h" +#include "py/objtuple.h" +#include "py/objtype.h" +#include "py/runtime.h" +#include "py/mphal.h" + +#include "imlib.h" +#include "py_helper.h" +#include "py_assert.h" + + + +#define PY_ASSERT_TYPE(obj, type) \ + do { \ + __typeof__ (obj) _a = (obj); \ + __typeof__ (type) _b = (type); \ + if (!MP_OBJ_IS_TYPE(_a, _b)) { \ + mp_raise_msg_varg(&mp_type_TypeError, \ + MP_ERROR_TEXT( \ + "Can't convert %s to %s"), \ + mp_obj_get_type_str(_a), \ + mp_obj_get_type_str(_b)); \ + } \ + } while (0) + +size_t image_size(image_t *img) { + return img->size; +} + + +// ================================================================================== +// class: image.Image +// ================================================================================== + + +// ================================================================================== +// Basic Methods +// ================================================================================== +const mp_obj_type_t py_image_type; + + +void *py_image_cobj(mp_obj_t img_obj) { + PY_ASSERT_TYPE(img_obj, &py_image_type); + return &((py_image_obj_t *)img_obj)->_cobj; +} + + +static mp_obj_t py_image_width(mp_obj_t img_obj) { + return mp_obj_new_int(((image_t *)py_image_cobj(img_obj))->w); +} +static MP_DEFINE_CONST_FUN_OBJ_1(py_image_width_obj, py_image_width); + +static mp_obj_t py_image_height(mp_obj_t img_obj) { + return mp_obj_new_int(((image_t *)py_image_cobj(img_obj))->h); +} +static MP_DEFINE_CONST_FUN_OBJ_1(py_image_height_obj, py_image_height); + +static mp_obj_t py_image_format(mp_obj_t img_obj) { + image_t *image = py_image_cobj(img_obj); + switch (image->pixfmt) { + case OMV_PIXFORMAT_BINARY: + return mp_obj_new_int(OMV_PIXFORMAT_BINARY); + case OMV_PIXFORMAT_GRAYSCALE: + return mp_obj_new_int(OMV_PIXFORMAT_GRAYSCALE); + case OMV_PIXFORMAT_RGB565: + return mp_obj_new_int(OMV_PIXFORMAT_RGB565); + case OMV_PIXFORMAT_BAYER_ANY: + return mp_obj_new_int(OMV_PIXFORMAT_BAYER); + case OMV_PIXFORMAT_YUV_ANY: + return mp_obj_new_int(OMV_PIXFORMAT_YUV422); + case OMV_PIXFORMAT_JPEG: + return mp_obj_new_int(OMV_PIXFORMAT_JPEG); + case OMV_PIXFORMAT_PNG: + return mp_obj_new_int(OMV_PIXFORMAT_PNG); + default: + return mp_obj_new_int(OMV_PIXFORMAT_INVALID); + } +} +static MP_DEFINE_CONST_FUN_OBJ_1(py_image_format_obj, py_image_format); + +static mp_obj_t py_image_size(mp_obj_t img_obj) { + return mp_obj_new_int(image_size((image_t *)py_image_cobj(img_obj))); +} +static MP_DEFINE_CONST_FUN_OBJ_1(py_image_size_obj, py_image_size); + +static mp_obj_t py_image_bytearray(mp_obj_t img_obj) { + image_t *arg_img = (image_t *)py_image_cobj(img_obj); + return mp_obj_new_bytearray_by_ref(image_size(arg_img), arg_img->data); +} +static MP_DEFINE_CONST_FUN_OBJ_1(py_image_bytearray_obj, py_image_bytearray); + + +// ==================================================================================== +// Drawings Methods +// ==================================================================================== +static mp_obj_t py_image_clear(mp_obj_t img_obj) { + image_t *arg_img = (image_t *)py_image_cobj(img_obj); + memset(arg_img->pixels, 0, image_size(arg_img)); + return img_obj; +} +static MP_DEFINE_CONST_FUN_OBJ_1(py_image_clear_obj, py_image_clear); + +static mp_obj_t py_image_draw_line(size_t n_args, const mp_obj_t *args, mp_map_t *kw_args) { + const mp_obj_t *arg_vec; + uint offset = py_helper_consume_array(n_args, args, 0, 4, &arg_vec); + + image_t *arg_img = (image_t *)py_image_cobj(arg_vec[0]); + int arg_x0 = mp_obj_get_int(arg_vec[1]); + int arg_y0 = mp_obj_get_int(arg_vec[2]); + int arg_x1 = mp_obj_get_int(arg_vec[3]); + int arg_y1 = mp_obj_get_int(arg_vec[4]); + // int arg_c = py_helper_keyword_color(n_args, args, offset + 0, kw_args, -1); + int arg_c = py_helper_keyword_color(arg_img, n_args, args, offset + 0, kw_args, -1); // White. + int arg_thickness = py_helper_keyword_int(n_args, args, offset + 1, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_thickness), 1); + + imlib_draw_line(arg_img, arg_x0, arg_y0, arg_x1, arg_y1, arg_c, arg_thickness); + + return arg_vec[0]; +} +static MP_DEFINE_CONST_FUN_OBJ_KW(py_image_draw_line_obj, 3, py_image_draw_line); + +static mp_obj_t py_image_draw_rectangle(size_t n_args, const mp_obj_t *args, mp_map_t *kw_args) { + const mp_obj_t *arg_vec; + uint offset = py_helper_consume_array(n_args, args, 0, 4, &arg_vec); + + image_t *arg_img = (image_t *)py_image_cobj(arg_vec[0]); + int arg_rx = mp_obj_get_int(arg_vec[1]); + int arg_ry = mp_obj_get_int(arg_vec[2]); + int arg_rw = mp_obj_get_int(arg_vec[3]); + int arg_rh = mp_obj_get_int(arg_vec[4]); + // int arg_c = py_helper_keyword_color(n_args, args, offset + 0, kw_args, -1); + int arg_c = py_helper_keyword_color(arg_img, n_args, args, offset + 0, kw_args, -1); // White. + int arg_thickness = py_helper_keyword_int(n_args, args, offset + 1, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_thickness), 1); + int arg_fill = py_helper_keyword_int(n_args, args, offset + 2, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_fill), 1); + + imlib_draw_rectangle(arg_img, arg_rx, arg_ry, arg_rw, arg_rh, arg_c, arg_thickness, arg_fill); + + return arg_vec[0]; +} +static MP_DEFINE_CONST_FUN_OBJ_KW(py_image_draw_rectangle_obj, 4, py_image_draw_rectangle); + +static mp_obj_t py_image_draw_circle(size_t n_args, const mp_obj_t *args, mp_map_t *kw_args) { + const mp_obj_t *arg_vec; + uint offset = py_helper_consume_array(n_args, args, 0, 3, &arg_vec); + + image_t *arg_img = (image_t *)py_image_cobj(arg_vec[0]); + int arg_cx = mp_obj_get_int(arg_vec[1]); + int arg_cy = mp_obj_get_int(arg_vec[2]); + int arg_radius = mp_obj_get_int(arg_vec[3]); + // int arg_c = py_helper_keyword_color(n_args, args, offset + 0, kw_args, -1); + int arg_c = py_helper_keyword_color(arg_img, n_args, args, offset + 0, kw_args, -1); // White. + int arg_thickness = py_helper_keyword_int(n_args, args, offset + 1, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_thickness), 1); + int arg_fill = py_helper_keyword_int(n_args, args, offset + 2, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_fill), 1); + + imlib_draw_circle(arg_img, arg_cx, arg_cy, arg_radius, arg_c, arg_thickness, arg_fill); + + return arg_vec[0]; +} +static MP_DEFINE_CONST_FUN_OBJ_KW(py_image_draw_circle_obj, 4, py_image_draw_circle); + +static mp_obj_t py_image_draw_ellipse(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) { + image_t *arg_img = py_helper_arg_to_image(args[0], ARG_IMAGE_MUTABLE); + + const mp_obj_t *arg_vec; + uint offset = py_helper_consume_array(n_args, args, 1, 5, &arg_vec); + int arg_cx = mp_obj_get_int(arg_vec[0]); + int arg_cy = mp_obj_get_int(arg_vec[1]); + int arg_rx = mp_obj_get_int(arg_vec[2]); + int arg_ry = mp_obj_get_int(arg_vec[3]); + int arg_r = mp_obj_get_int(arg_vec[4]); + + int arg_c = + py_helper_keyword_color(arg_img, n_args, args, offset + 1, kw_args, -1); // White. + int arg_thickness = + py_helper_keyword_int(n_args, args, offset + 2, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_thickness), 1); + bool arg_fill = + py_helper_keyword_int(n_args, args, offset + 3, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_fill), false); + + imlib_draw_ellipse(arg_img, arg_cx, arg_cy, arg_rx, arg_ry, arg_r, arg_c, arg_thickness, arg_fill); + return args[0]; +} +static MP_DEFINE_CONST_FUN_OBJ_KW(py_image_draw_ellipse_obj, 2, py_image_draw_ellipse); + +static mp_obj_t py_image_draw_string(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) { + image_t *arg_img = py_helper_arg_to_image(args[0], ARG_IMAGE_MUTABLE); + + const mp_obj_t *arg_vec; + uint offset = py_helper_consume_array(n_args, args, 1, 3, &arg_vec); + int arg_x_off = mp_obj_get_int(arg_vec[0]); + int arg_y_off = mp_obj_get_int(arg_vec[1]); + const char *arg_str = mp_obj_str_get_str(arg_vec[2]); + + int arg_c = + py_helper_keyword_color(arg_img, n_args, args, offset + 0, kw_args, -1); // White. + float arg_scale = + py_helper_keyword_float(n_args, args, offset + 1, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_scale), 1.0); + PY_ASSERT_TRUE_MSG(0 < arg_scale, "Error: 0 < scale!"); + int arg_x_spacing = + py_helper_keyword_int(n_args, args, offset + 2, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_x_spacing), 0); + int arg_y_spacing = + py_helper_keyword_int(n_args, args, offset + 3, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_y_spacing), 0); + bool arg_mono_space = + py_helper_keyword_int(n_args, args, offset + 4, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_mono_space), true); + int arg_char_rotation = + py_helper_keyword_int(n_args, args, offset + 5, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_char_rotation), 0); + int arg_char_hmirror = + py_helper_keyword_int(n_args, args, offset + 6, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_char_hmirror), false); + int arg_char_vflip = + py_helper_keyword_int(n_args, args, offset + 7, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_char_vflip), false); + int arg_string_rotation = + py_helper_keyword_int(n_args, args, offset + 8, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_string_rotation), 0); + int arg_string_hmirror = + py_helper_keyword_int(n_args, args, offset + 9, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_string_hmirror), false); + int arg_string_vflip = + py_helper_keyword_int(n_args, args, offset + 10, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_string_vflip), false); + + imlib_draw_string(arg_img, arg_x_off, arg_y_off, arg_str, + arg_c, arg_scale, arg_x_spacing, arg_y_spacing, arg_mono_space, + arg_char_rotation, arg_char_hmirror, arg_char_vflip, + arg_string_rotation, arg_string_hmirror, arg_string_vflip); + return args[0]; +} +static MP_DEFINE_CONST_FUN_OBJ_KW(py_image_draw_string_obj, 2, py_image_draw_string); + +static mp_obj_t py_image_draw_cross(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) { + image_t *arg_img = py_helper_arg_to_image(args[0], ARG_IMAGE_MUTABLE); + + const mp_obj_t *arg_vec; + uint offset = py_helper_consume_array(n_args, args, 1, 2, &arg_vec); + int arg_x = mp_obj_get_int(arg_vec[0]); + int arg_y = mp_obj_get_int(arg_vec[1]); + + int arg_c = + py_helper_keyword_color(arg_img, n_args, args, offset + 0, kw_args, -1); // White. + int arg_s = + py_helper_keyword_int(n_args, args, offset + 1, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_size), 5); + int arg_thickness = + py_helper_keyword_int(n_args, args, offset + 2, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_thickness), 1); + + imlib_draw_line(arg_img, arg_x - arg_s, arg_y, arg_x + arg_s, arg_y, arg_c, arg_thickness); + imlib_draw_line(arg_img, arg_x, arg_y - arg_s, arg_x, arg_y + arg_s, arg_c, arg_thickness); + return args[0]; +} +static MP_DEFINE_CONST_FUN_OBJ_KW(py_image_draw_cross_obj, 2, py_image_draw_cross); + +static mp_obj_t py_image_draw_arrow(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) { + image_t *arg_img = py_helper_arg_to_image(args[0], ARG_IMAGE_MUTABLE); + + const mp_obj_t *arg_vec; + uint offset = py_helper_consume_array(n_args, args, 1, 4, &arg_vec); + int arg_x0 = mp_obj_get_int(arg_vec[0]); + int arg_y0 = mp_obj_get_int(arg_vec[1]); + int arg_x1 = mp_obj_get_int(arg_vec[2]); + int arg_y1 = mp_obj_get_int(arg_vec[3]); + + int arg_c = + py_helper_keyword_color(arg_img, n_args, args, offset + 0, kw_args, -1); // White. + int arg_s = + py_helper_keyword_int(n_args, args, offset + 1, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_size), 10); + int arg_thickness = + py_helper_keyword_int(n_args, args, offset + 2, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_thickness), 1); + + int dx = (arg_x1 - arg_x0); + int dy = (arg_y1 - arg_y0); + float length = fast_sqrtf((dx * dx) + (dy * dy)); + + float ux = IM_DIV(dx, length); + float uy = IM_DIV(dy, length); + float vx = -uy; + float vy = ux; + + int a0x = fast_roundf(arg_x1 - (arg_s * ux) + (arg_s * vx * 0.5)); + int a0y = fast_roundf(arg_y1 - (arg_s * uy) + (arg_s * vy * 0.5)); + int a1x = fast_roundf(arg_x1 - (arg_s * ux) - (arg_s * vx * 0.5)); + int a1y = fast_roundf(arg_y1 - (arg_s * uy) - (arg_s * vy * 0.5)); + + imlib_draw_line(arg_img, arg_x0, arg_y0, arg_x1, arg_y1, arg_c, arg_thickness); + imlib_draw_line(arg_img, arg_x1, arg_y1, a0x, a0y, arg_c, arg_thickness); + imlib_draw_line(arg_img, arg_x1, arg_y1, a1x, a1y, arg_c, arg_thickness); + return args[0]; +} +static MP_DEFINE_CONST_FUN_OBJ_KW(py_image_draw_arrow_obj, 2, py_image_draw_arrow); + +static mp_obj_t py_image_draw_edges(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) { + image_t *arg_img = py_helper_arg_to_image(args[0], ARG_IMAGE_MUTABLE); + + mp_obj_t *corners, *p0, *p1, *p2, *p3; + mp_obj_get_array_fixed_n(args[1], 4, &corners); + mp_obj_get_array_fixed_n(corners[0], 2, &p0); + mp_obj_get_array_fixed_n(corners[1], 2, &p1); + mp_obj_get_array_fixed_n(corners[2], 2, &p2); + mp_obj_get_array_fixed_n(corners[3], 2, &p3); + + int x0, y0, x1, y1, x2, y2, x3, y3; + x0 = mp_obj_get_int(p0[0]); + y0 = mp_obj_get_int(p0[1]); + x1 = mp_obj_get_int(p1[0]); + y1 = mp_obj_get_int(p1[1]); + x2 = mp_obj_get_int(p2[0]); + y2 = mp_obj_get_int(p2[1]); + x3 = mp_obj_get_int(p3[0]); + y3 = mp_obj_get_int(p3[1]); + + int arg_c = + py_helper_keyword_color(arg_img, n_args, args, 2, kw_args, -1); // White. + int arg_s = + py_helper_keyword_int(n_args, args, 3, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_size), 0); + int arg_thickness = + py_helper_keyword_int(n_args, args, 4, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_thickness), 1); + bool arg_fill = + py_helper_keyword_int(n_args, args, 5, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_fill), false); + + imlib_draw_line(arg_img, x0, y0, x1, y1, arg_c, arg_thickness); + imlib_draw_line(arg_img, x1, y1, x2, y2, arg_c, arg_thickness); + imlib_draw_line(arg_img, x2, y2, x3, y3, arg_c, arg_thickness); + imlib_draw_line(arg_img, x3, y3, x0, y0, arg_c, arg_thickness); + + if (arg_s >= 1) { + imlib_draw_circle(arg_img, x0, y0, arg_s, arg_c, arg_thickness, arg_fill); + imlib_draw_circle(arg_img, x1, y1, arg_s, arg_c, arg_thickness, arg_fill); + imlib_draw_circle(arg_img, x2, y2, arg_s, arg_c, arg_thickness, arg_fill); + imlib_draw_circle(arg_img, x3, y3, arg_s, arg_c, arg_thickness, arg_fill); + } + + return args[0]; +} +static MP_DEFINE_CONST_FUN_OBJ_KW(py_image_draw_edges_obj, 2, py_image_draw_edges); + + + + +mp_obj_t py_image(int w, int h, omv_pixformat_t pixfmt, uint32_t size, void *pixels) { + py_image_obj_t *o = m_new_obj(py_image_obj_t); + o->base.type = &py_image_type; + o->_cobj.w = w; + o->_cobj.h = h; + o->_cobj.size = size; + o->_cobj.pixfmt = pixfmt; + o->_cobj.pixels = pixels; + return o; +} + +mp_obj_t py_image_from_struct(image_t *img) { + py_image_obj_t *o = m_new_obj(py_image_obj_t); + o->base.type = &py_image_type; + o->_cobj = *img; + return o; +} + + +static const mp_rom_map_elem_t locals_dict_table[] = { + /* Basic Methods */ + {MP_ROM_QSTR(MP_QSTR_width), MP_ROM_PTR(&py_image_width_obj)}, + {MP_ROM_QSTR(MP_QSTR_height), MP_ROM_PTR(&py_image_height_obj)}, + {MP_ROM_QSTR(MP_QSTR_format), MP_ROM_PTR(&py_image_format_obj)}, + {MP_ROM_QSTR(MP_QSTR_size), MP_ROM_PTR(&py_image_size_obj)}, + {MP_ROM_QSTR(MP_QSTR_bytearray), MP_ROM_PTR(&py_image_bytearray_obj)}, + /* Drawing Methods */ + {MP_ROM_QSTR(MP_QSTR_clear), MP_ROM_PTR(&py_image_clear_obj)}, + {MP_ROM_QSTR(MP_QSTR_draw_line), MP_ROM_PTR(&py_image_draw_line_obj)}, + {MP_ROM_QSTR(MP_QSTR_draw_rectangle), MP_ROM_PTR(&py_image_draw_rectangle_obj)}, + {MP_ROM_QSTR(MP_QSTR_draw_circle), MP_ROM_PTR(&py_image_draw_circle_obj)}, + {MP_ROM_QSTR(MP_QSTR_draw_ellipse), MP_ROM_PTR(&py_image_draw_ellipse_obj)}, + {MP_ROM_QSTR(MP_QSTR_draw_string), MP_ROM_PTR(&py_image_draw_string_obj)}, + {MP_ROM_QSTR(MP_QSTR_draw_cross), MP_ROM_PTR(&py_image_draw_cross_obj)}, + {MP_ROM_QSTR(MP_QSTR_draw_arrow), MP_ROM_PTR(&py_image_draw_arrow_obj)}, + {MP_ROM_QSTR(MP_QSTR_draw_edges), MP_ROM_PTR(&py_image_draw_edges_obj)}, +}; + +static MP_DEFINE_CONST_DICT(py_image_locals_dict, locals_dict_table); + +MP_DEFINE_CONST_OBJ_TYPE( + py_image_type, + MP_QSTR_Image, + MP_TYPE_FLAG_NONE, + locals_dict, &py_image_locals_dict + ); + + + +// ================================================================================== +// module: image +// ================================================================================== + +static const mp_rom_map_elem_t globals_dict_table[] = { + {MP_ROM_QSTR(MP_QSTR___name__), MP_OBJ_NEW_QSTR(MP_QSTR_image)}, + // Pixel formats + {MP_ROM_QSTR(MP_QSTR_GRAYSCALE), MP_ROM_INT(OMV_PIXFORMAT_GRAYSCALE)},/* 1BPP/GRAYSCALE*/ + {MP_ROM_QSTR(MP_QSTR_RGB565), MP_ROM_INT(OMV_PIXFORMAT_RGB565)}, /* 2BPP/RGB565*/ + {MP_ROM_QSTR(MP_QSTR_YUV422), MP_ROM_INT(OMV_PIXFORMAT_YUV422)}, /* 2BPP/YUV422*/ + {MP_ROM_QSTR(MP_QSTR_JPEG), MP_ROM_INT(OMV_PIXFORMAT_JPEG)}, /* JPEG/COMPRESSED*/ + // + {MP_ROM_QSTR(MP_QSTR_Image), MP_ROM_PTR(&py_image_type)}, +}; + +static MP_DEFINE_CONST_DICT(globals_dict, globals_dict_table); + +const mp_obj_module_t image_module = { + .base = { &mp_type_module }, + .globals = (mp_obj_t)&globals_dict +}; + +MP_REGISTER_MODULE(MP_QSTR_image, image_module); diff --git a/m5stack/cmodules/omv/modules/py_image.h b/m5stack/cmodules/omv/modules/py_image.h old mode 100755 new mode 100644 index 26f73918..601b893d --- a/m5stack/cmodules/omv/modules/py_image.h +++ b/m5stack/cmodules/omv/modules/py_image.h @@ -1,32 +1,27 @@ -#ifdef __cplusplus -extern "C" { -#endif - -#pragma once - -#include "py/obj.h" -#include "imlib.h" - - -typedef struct _py_image_obj_t { - mp_obj_base_t base; - image_t _cobj; -} py_image_obj_t; - -extern const mp_obj_type_t py_image_type; - -mp_obj_t py_image(int width, int height, omv_pixformat_t pixfmt, uint32_t size, void* pixels); -mp_obj_t py_image_from_struct(image_t* img); -void* py_image_cobj(mp_obj_t img_obj); - - - - -#ifdef __cplusplus -} -#endif - - - - - +#ifdef __cplusplus +extern "C" { +#endif + +#pragma once + +#include "py/obj.h" +#include "imlib.h" + + +typedef struct _py_image_obj_t { + mp_obj_base_t base; + image_t _cobj; +} py_image_obj_t; + +extern const mp_obj_type_t py_image_type; + +mp_obj_t py_image(int width, int height, omv_pixformat_t pixfmt, uint32_t size, void *pixels); +mp_obj_t py_image_from_struct(image_t *img); +void *py_image_cobj(mp_obj_t img_obj); + + + + +#ifdef __cplusplus +} +#endif diff --git a/m5stack/cmodules/omv/modules/py_jpg.c b/m5stack/cmodules/omv/modules/py_jpg.c old mode 100755 new mode 100644 index 53964c56..8fc013ed --- a/m5stack/cmodules/omv/modules/py_jpg.c +++ b/m5stack/cmodules/omv/modules/py_jpg.c @@ -13,15 +13,14 @@ #include "py/stream.h" #include "extmod/vfs.h" #include "extmod/vfs_fat.h" - + #include "esp_camera.h" #include "imlib.h" #include "py_image.h" - -static mp_obj_t py_jpg_encode(size_t n_args, const mp_obj_t *args) -{ + +static mp_obj_t py_jpg_encode(size_t n_args, const mp_obj_t *args) { image_t *img = (image_t *)py_image_cobj(args[0]); uint8_t quality = 30; @@ -34,7 +33,7 @@ static mp_obj_t py_jpg_encode(size_t n_args, const mp_obj_t *args) if (quality > 100) { quality = 100; } - } + } static image_t img_jpg; img_jpg.w = img->w; @@ -49,8 +48,7 @@ static mp_obj_t py_jpg_encode(size_t n_args, const mp_obj_t *args) } static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(py_jpg_encode_obj, 1, 2, py_jpg_encode); -static mp_obj_t py_jpg_decode(mp_obj_t img_obj) -{ +static mp_obj_t py_jpg_decode(mp_obj_t img_obj) { image_t *img_jpg = (image_t *)py_image_cobj(img_obj); static image_t img_rgb565; @@ -59,11 +57,11 @@ static mp_obj_t py_jpg_decode(mp_obj_t img_obj) img_rgb565.w = img_jpg->w; img_rgb565.h = img_jpg->h; img_rgb565.size = img_rgb565.w * img_rgb565.h * sizeof(uint16_t); // RGB565 - if (img_rgb565.data == NULL) { + if (img_rgb565.data == NULL) { img_rgb565.data = (uint8_t *)heap_caps_malloc(img_rgb565.size, MALLOC_CAP_8BIT | MALLOC_CAP_SPIRAM); } else { - // TODO: - //img_rgb565.data = (uint8_t *)heap_caps_realloc(img_rgb565.data, img_rgb565.size, MALLOC_CAP_8BIT | MALLOC_CAP_SPIRAM); + // TODO: + // img_rgb565.data = (uint8_t *)heap_caps_realloc(img_rgb565.data, img_rgb565.size, MALLOC_CAP_8BIT | MALLOC_CAP_SPIRAM); } if (img_rgb565.data == NULL) { mp_raise_msg(&mp_type_MemoryError, MP_ERROR_TEXT("Failed to allocate memory for image buffer")); @@ -74,7 +72,7 @@ static mp_obj_t py_jpg_decode(mp_obj_t img_obj) return py_image_from_struct(&img_rgb565); } static MP_DEFINE_CONST_FUN_OBJ_1(py_jpg_decode_obj, py_jpg_decode); - + static const mp_rom_map_elem_t globals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_jpg) }, @@ -84,12 +82,8 @@ static const mp_rom_map_elem_t globals_dict_table[] = { static MP_DEFINE_CONST_DICT(globals_dict, globals_dict_table); const mp_obj_module_t mp_module_jpg = { - .base = { &mp_type_module }, - .globals = (mp_obj_dict_t *)&globals_dict, + .base = { &mp_type_module }, + .globals = (mp_obj_dict_t *)&globals_dict, }; MP_REGISTER_MODULE(MP_QSTR_jpg, mp_module_jpg); - - - - diff --git a/m5stack/components/M5Unified/mpy_m5gfx.cpp b/m5stack/components/M5Unified/mpy_m5gfx.cpp index 14770a7a..7b0c5202 100644 --- a/m5stack/components/M5Unified/mpy_m5gfx.cpp +++ b/m5stack/components/M5Unified/mpy_m5gfx.cpp @@ -994,7 +994,7 @@ mp_obj_t gfx_drawRawBuf(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_ar return mp_const_none; } -#if USE_OMV +#if USE_OMV #include "py_image.h" #include "py_helper.h" #include "imlib.h" @@ -1012,7 +1012,7 @@ mp_obj_t gfx_show(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); - image_t* img = py_helper_arg_to_image(args[ARG_img].u_obj, ARG_IMAGE_ANY | ARG_IMAGE_ALLOC); + image_t *img = py_helper_arg_to_image(args[ARG_img].u_obj, ARG_IMAGE_ANY | ARG_IMAGE_ALLOC); int x = args[ARG_x].u_int; int y = args[ARG_y].u_int; @@ -1023,11 +1023,11 @@ mp_obj_t gfx_show(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { gfx->startWrite(); if (w >= img->w) { gfx->setAddrWindow(x, y, w, h); - gfx->writePixels((const uint16_t *)img->data, w*h, args[ARG_swap].u_bool); + gfx->writePixels((const uint16_t *)img->data, w * h, args[ARG_swap].u_bool); } else { for (int row = 0; row < h; row++) { gfx->setAddrWindow(x, y + row, w, 1); - gfx->writePixels((const uint16_t *)img->data + img->w*row, w, args[ARG_swap].u_bool); + gfx->writePixels((const uint16_t *)img->data + img->w * row, w, args[ARG_swap].u_bool); } } gfx->endWrite(); @@ -1035,7 +1035,7 @@ mp_obj_t gfx_show(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { } #else mp_obj_t gfx_show(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { - // do nothing + // do nothing return mp_const_none; } #endif From 58c723e76a18211b4b4d6c3c00137b72409589db Mon Sep 17 00:00:00 2001 From: lbuque Date: Thu, 6 Mar 2025 17:19:08 +0800 Subject: [PATCH 017/322] version.text: Update to V2.2.3. Signed-off-by: lbuque --- m5stack/version.txt | 2 +- third-party/version.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/m5stack/version.txt b/m5stack/version.txt index cce35088..9cde184f 100644 --- a/m5stack/version.txt +++ b/m5stack/version.txt @@ -1 +1 @@ -V2.2.2 \ No newline at end of file +V2.2.3 \ No newline at end of file diff --git a/third-party/version.txt b/third-party/version.txt index cce35088..9cde184f 100644 --- a/third-party/version.txt +++ b/third-party/version.txt @@ -1 +1 @@ -V2.2.2 \ No newline at end of file +V2.2.3 \ No newline at end of file From 5fa578213db97b30c9971eca3418a2be23eedba1 Mon Sep 17 00:00:00 2001 From: Rob2 <7896896+Rob2@users.noreply.github.com> Date: Sat, 8 Feb 2025 12:28:56 +0000 Subject: [PATCH 018/322] address transposition in pps.py Functions read_mcu_temperature and read_input_voltage were using transposed addresses --- m5stack/libs/module/pps.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/m5stack/libs/module/pps.py b/m5stack/libs/module/pps.py index 7f4f8e6d..80aa795c 100644 --- a/m5stack/libs/module/pps.py +++ b/m5stack/libs/module/pps.py @@ -169,7 +169,7 @@ def read_input_voltage(self) -> float: Returns: float: The current input voltage in Volts. """ - voltage = self.i2c.readfrom_mem(self.addr, 0x10, 4) + voltage = self.i2c.readfrom_mem(self.addr, 0x14, 4) return struct.unpack(" int: @@ -193,7 +193,7 @@ def read_mcu_temperature(self) -> float: Returns: float: The current MCU temperature in degrees Celsius. """ - temperature = self.i2c.readfrom_mem(self.addr, 0x14, 4) + temperature = self.i2c.readfrom_mem(self.addr, 0x10, 4) return struct.unpack(" int: From 3785b68d472b2e0ca150c7143244de0f9743282e Mon Sep 17 00:00:00 2001 From: Tinyu-Zhao Date: Tue, 4 Mar 2025 17:54:10 +0800 Subject: [PATCH 019/322] docs: Unify Atom document naming, sort. Signed-off-by: Tinyu-Zhao --- docs/en/base/atom_can.rst | 4 ++-- docs/en/base/atom_gps.rst | 4 ++-- docs/en/base/atom_socket.rst | 4 ++-- docs/en/base/index.rst | 3 ++- docs/en/base/motion.rst | 2 +- m5stack/libs/base/__init__.py | 1 + 6 files changed, 10 insertions(+), 8 deletions(-) diff --git a/docs/en/base/atom_can.rst b/docs/en/base/atom_can.rst index 28227864..7145fc16 100644 --- a/docs/en/base/atom_can.rst +++ b/docs/en/base/atom_can.rst @@ -1,5 +1,5 @@ -ATOM CAN Base -============== +Atomic CAN Base +================ .. sku: A103/KO57 diff --git a/docs/en/base/atom_gps.rst b/docs/en/base/atom_gps.rst index 7001b1b4..03768825 100644 --- a/docs/en/base/atom_gps.rst +++ b/docs/en/base/atom_gps.rst @@ -1,5 +1,5 @@ -ATOM GPS Base -============== +Atomic GPS Base +================ .. sku: A134/K043 diff --git a/docs/en/base/atom_socket.rst b/docs/en/base/atom_socket.rst index 492b55f6..4badcc69 100644 --- a/docs/en/base/atom_socket.rst +++ b/docs/en/base/atom_socket.rst @@ -1,5 +1,5 @@ -ATOM Socket Base -================ +Atom Socket Base +================= .. include:: ../refs/base.atom_socket.ref diff --git a/docs/en/base/index.rst b/docs/en/base/index.rst index 85631125..81d044a2 100644 --- a/docs/en/base/index.rst +++ b/docs/en/base/index.rst @@ -4,9 +4,10 @@ Base .. toctree:: :maxdepth: 1 + atom_socket.rst + dtu_lorawan.rst atom_can.rst atom_gps.rst - atom_socket.rst display.rst echo.rst hdriver.rst diff --git a/docs/en/base/motion.rst b/docs/en/base/motion.rst index d65b67eb..45e125ac 100644 --- a/docs/en/base/motion.rst +++ b/docs/en/base/motion.rst @@ -1,4 +1,4 @@ -Motion Base +Atomic Motion Base ============================ .. sku: A090 diff --git a/m5stack/libs/base/__init__.py b/m5stack/libs/base/__init__.py index 1b698c78..fd2b9421 100644 --- a/m5stack/libs/base/__init__.py +++ b/m5stack/libs/base/__init__.py @@ -4,6 +4,7 @@ _attrs = { "ATOMCANBase": "atom_can", + "AtomDTULoRaWANBase": "dtu_lorawan", "ATOMGPSBase": "atom_gps", "ATOMSocketBase": "atom_socket", "ATOMEchoBase": "echo", From 0b4f2868d33ba9bd6f5e3207d5b2e2d0167d318e Mon Sep 17 00:00:00 2001 From: Tinyu-Zhao Date: Mon, 17 Mar 2025 16:00:53 +0800 Subject: [PATCH 020/322] lib/base: Add DTU LoRaWAN-Series base support. Signed-off-by: Tinyu-Zhao --- docs/en/base/dtu_lorawan.rst | 68 +++ docs/en/refs/base.dtu_lorawan.ref | 46 ++ .../zh_CN/LC_MESSAGES/base/dtu_lorawan.po | 564 ++++++++++++++++++ .../atoms3r_dtu_lorawan_example.m5f2 | 1 + .../atoms3r_dtu_lorawan_example.py | 52 ++ m5stack/libs/base/atom_gps.py | 2 +- m5stack/libs/base/dtu_lorawan.py | 40 ++ m5stack/libs/base/manifest.py | 1 + m5stack/libs/driver/asr650x.py | 375 ++++++++---- m5stack/libs/unit/lorawan.py | 16 +- 10 files changed, 1025 insertions(+), 140 deletions(-) create mode 100644 docs/en/base/dtu_lorawan.rst create mode 100644 docs/en/refs/base.dtu_lorawan.ref create mode 100644 docs/locales/zh_CN/LC_MESSAGES/base/dtu_lorawan.po create mode 100644 examples/base/dtu_lorawan/atoms3r_dtu_lorawan_example.m5f2 create mode 100644 examples/base/dtu_lorawan/atoms3r_dtu_lorawan_example.py create mode 100644 m5stack/libs/base/dtu_lorawan.py diff --git a/docs/en/base/dtu_lorawan.rst b/docs/en/base/dtu_lorawan.rst new file mode 100644 index 00000000..06252d65 --- /dev/null +++ b/docs/en/base/dtu_lorawan.rst @@ -0,0 +1,68 @@ +Atom DTU LoRaWAN-Series Base +============================ + +.. sku: K061/K062/K063 + +.. include:: ../refs/base.dtu_lorawan.ref + +This is the driver library for the Atom DTU LoRaWAN-Series Base to accept and send data from the LoRaWAN module. + +Support the following products: + + ===================== ===================== ===================== + |Atom DTU LoRaWAN470| |Atom DTU LoRaWAN868| |Atom DTU LoRaWAN915| + ===================== ===================== ===================== + +UiFlow2 Example +--------------- + +LoRaWAN communication +^^^^^^^^^^^^^^^^^^^^^ + +Open the |atoms3r_dtu_lorawan_example.m5f2| project in UiFlow2. + +This example shows how to receive and send data using the Atom DTU LoRaWAN Base. + +UiFlow2 Code Block: + + |example.png| + +Example output: + + None + +MicroPython Example +------------------- + +LoRaWAN communication +^^^^^^^^^^^^^^^^^^^^^^ + +This example shows how to receive and send data using the Atom DTU LoRaWAN Base. + +MicroPython Code Block: + + .. literalinclude:: ../../../examples/base/dtu_lorawan/atoms3r_dtu_lorawan_example.py + :language: python + :linenos: + +Example output: + + None + +**API** +------- + +AtomDTULoRaWANBase +^^^^^^^^^^^^^^^^^^ + +.. autoclass:: base.dtu_lorawan.AtomDTULoRaWANBase + :members: + +.. autoclass:: driver.asr650x.LoRaWAN_470 + :members: + :exclude-members: get_ABP_config, get_OTAA_config, config_OTAA, config_ABP + +.. autoclass:: driver.asr650x.LoRaWAN_Asr650x + :members: + :member-order: bysource + :exclude-members: enable_adaptive_datarate, get_APPSKEY, get_AppKey, get_AppEui, reset_module_to_default, get_device_address, get_DevAddr, set_device_address, set_DevAddr, get_device_eui, get_DevEui, set_device_eui, set_DevEui, get_product_serial_number, set_AppEui, set_AppKey, set_APPSKEY, get_NWKSKEY, set_NWKSKEY, get_join_mode, get_frequency_band_mask, get_uplink_downlink_mode, get_work_mode, get_class_mode, set_work_mode, get_status, receive_data, set_uplink_confirm_mode, set_datarate, set_report_mode, set_tx_power, enable_adaptive_datarate, set_rx1_delay_time \ No newline at end of file diff --git a/docs/en/refs/base.dtu_lorawan.ref b/docs/en/refs/base.dtu_lorawan.ref new file mode 100644 index 00000000..587ca899 --- /dev/null +++ b/docs/en/refs/base.dtu_lorawan.ref @@ -0,0 +1,46 @@ +.. |Atom DTU LoRaWAN470| image:: https://static-cdn.m5stack.com/resource/docs/products/atom/atom_dtu_lorawan470/atom_dtu_lorawan470_01.webp + :target: https://docs.m5stack.com/en/atom/atom_dtu_lorawan470 + :height: 200px + :width: 200px + +.. |Atom DTU LoRaWAN868| image:: https://static-cdn.m5stack.com/resource/docs/products/atom/atom_dtu_lorawan868/atom_dtu_lorawan868_01.webp + :target: https://docs.m5stack.com/en/atom/atom_dtu_lorawan868 + :height: 200px + :width: 200px + +.. |Atom DTU LoRaWAN915| image:: https://static-cdn.m5stack.com/resource/docs/products/atom/atom_dtu_lorawan915/atom_dtu_lorawan915_01.webp + :target: https://docs.m5stack.com/en/atom/atom_dtu_lorawan915 + :height: 200px + :width: 200px + + +.. |init.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/lorawan470/init.png + +.. |get_abp_config.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/lorawan470/get_abp_config.png +.. |get_otaa_config.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/lorawan470/get_otaa_config.png +.. |check_join_status.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/lorawan470/check_join_status.png +.. |check_uplink_status.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/lorawan470/check_uplink_status.png +.. |check_downlink_data.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/lorawan470/check_downlink_data.png +.. |uart_port_id.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/lorawan470/uart_port_id.png +.. |config_otaa.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/lorawan470/config_otaa.png +.. |config_abp.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/lorawan470/config_abp.png +.. |config.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/lorawan470/config.png +.. |set_rx_window_param.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/lorawan470/set_rx_window_param.png +.. |set_class_mode.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/lorawan470/set_class_mode.png +.. |set_uplink_downlink_mode.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/lorawan470/set_uplink_downlink_mode.png +.. |set_join_mode.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/lorawan470/set_join_mode.png +.. |join.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/lorawan470/start_join.png +.. |join_stop.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/lorawan470/stop_join.png +.. |set_uplink_app_port.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/lorawan470/set_uplink_app_port.png +.. |send_data.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/lorawan470/send_data.png + +.. |example.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/lorawan470/example.png + +.. |atoms3r_dtu_lorawan_example.m5f2| raw:: html + + + atoms3r_dtu_lorawan_example.m5f2 + \ No newline at end of file diff --git a/docs/locales/zh_CN/LC_MESSAGES/base/dtu_lorawan.po b/docs/locales/zh_CN/LC_MESSAGES/base/dtu_lorawan.po new file mode 100644 index 00000000..5c3c57f1 --- /dev/null +++ b/docs/locales/zh_CN/LC_MESSAGES/base/dtu_lorawan.po @@ -0,0 +1,564 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2016 - 2025 M5Stack Technology Co., Ltd +# This file is distributed under the same license as the UIFlow2 Programming +# Guide package. +# FIRST AUTHOR , 2025. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: UIFlow2 Programming Guide \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-03-17 15:57+0800\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: zh_CN\n" +"Language-Team: zh_CN \n" +"Plural-Forms: nplurals=1; plural=0;\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.16.0\n" + +#: ../../en/base/dtu_lorawan.rst:2 a9d6721cebaf48b18af1a13cfc3ee68f +msgid "Atom DTU LoRaWAN-Series Base" +msgstr "" + +#: ../../en/base/dtu_lorawan.rst:8 d2fc2c808a424bf38f4757b9198a837c +msgid "" +"This is the driver library for the Atom DTU LoRaWAN-Series Base to accept" +" and send data from the LoRaWAN module." +msgstr "这是 Atom DTU LoRaWAN 系列底座的驱动程序库,用于接收和发送来自 LoRaWAN 模块的数据。" + +#: ../../en/base/dtu_lorawan.rst:10 68036b2bd9c94ec1aade8ee0a159af9c +msgid "Support the following products:" +msgstr "支持以下产品:" + +#: ../../en/base/dtu_lorawan.rst:13 71f970403ea64e5cbc399b81223ef702 +msgid "|Atom DTU LoRaWAN470|" +msgstr "" + +#: ../../en/refs/base.dtu_lorawan.ref c456771d00b94a359f75f4083c49a2db +msgid "Atom DTU LoRaWAN470" +msgstr "" + +#: ../../en/base/dtu_lorawan.rst:13 b1f10446da74426fb610641074d2a07f +msgid "|Atom DTU LoRaWAN868|" +msgstr "" + +#: ../../en/refs/base.dtu_lorawan.ref e41298704f6f44339eb8b9a7686f3a1b +msgid "Atom DTU LoRaWAN868" +msgstr "" + +#: ../../en/base/dtu_lorawan.rst:13 beac1e93f8e34f979995384b241148f0 +msgid "|Atom DTU LoRaWAN915|" +msgstr "" + +#: ../../en/refs/base.dtu_lorawan.ref e1ecf4fa4cde4c5a8a144e696738f745 +msgid "Atom DTU LoRaWAN915" +msgstr "" + +#: ../../en/base/dtu_lorawan.rst:17 bfdf89bbb9b64102b590718ff191e24d +msgid "UiFlow2 Example" +msgstr "UIFLOW2 应用示例:" + +#: ../../en/base/dtu_lorawan.rst:20 ../../en/base/dtu_lorawan.rst:38 +#: 344fa992253e4e2ab1605eb211d97141 +msgid "LoRaWAN communication" +msgstr "LoRaWAN 通讯" + +#: ../../en/base/dtu_lorawan.rst:22 b2ce57ccc3574f6789ca412a44fedde9 +msgid "Open the |atoms3r_dtu_lorawan_example.m5f2| project in UiFlow2." +msgstr "在UiFlow2 中打开 |atoms3r_dtu_lorawan_example.m5f2| 项目。" + +#: ../../en/base/dtu_lorawan.rst:24 ../../en/base/dtu_lorawan.rst:40 +#: 2e208a3e87eb4755bf11552abeebb3f9 +msgid "" +"This example shows how to receive and send data using the Atom DTU " +"LoRaWAN Base." +msgstr "本示例演示如何使用 Atom DTU LoRaWAN Base 接收和发送数据。" + +#: ../../en/base/dtu_lorawan.rst:26 1baa6583abee427bae4b15c28602ebf8 +#: 77acbebafa4c429285b180f3f20472e0 b2f24dd6d8334f958878af430c2eff3f +#: base.dtu_lorawan.AtomDTULoRaWANBase:7 +#: driver.asr650x.LoRaWAN_470.check_downlink_data:7 +#: driver.asr650x.LoRaWAN_470.check_join_status:6 +#: driver.asr650x.LoRaWAN_470.check_uplink_status:6 +#: driver.asr650x.LoRaWAN_470.config_abp:7 +#: driver.asr650x.LoRaWAN_470.config_otaa:7 +#: driver.asr650x.LoRaWAN_470.get_abp_config:6 +#: driver.asr650x.LoRaWAN_470.get_otaa_config:6 +#: driver.asr650x.LoRaWAN_Asr650x.join:8 +#: driver.asr650x.LoRaWAN_Asr650x.send_data:7 +#: driver.asr650x.LoRaWAN_Asr650x.set_join_mode:5 +#: driver.asr650x.LoRaWAN_Asr650x.set_uplink_app_port:5 of +msgid "UiFlow2 Code Block:" +msgstr "UiFlow2 代码块:" + +#: ../../en/base/dtu_lorawan.rst:28 b5570951cab6459bb4a675479b828c9f +msgid "|example.png|" +msgstr "" + +#: ../../en/refs/base.dtu_lorawan.ref:37 6c7c4861892b4ad0a04782366b582756 +msgid "example.png" +msgstr "" + +#: ../../en/base/dtu_lorawan.rst:30 ../../en/base/dtu_lorawan.rst:48 +#: 3e45c52e560547d491214bc9f0aabf84 +msgid "Example output:" +msgstr "示例输出:" + +#: ../../en/base/dtu_lorawan.rst:32 ../../en/base/dtu_lorawan.rst:50 +#: d68de0b2fee6437292726d4ae9619027 +msgid "None" +msgstr "" + +#: ../../en/base/dtu_lorawan.rst:35 e823616d435f4c94ab6a714c3f5ea434 +msgid "MicroPython Example" +msgstr "MicroPython 应用示例:" + +#: ../../en/base/dtu_lorawan.rst:42 0e55c9b086464811902132e5db88f8fb +#: a505d5dba3ec4809b00e0d54ba13b60b base.dtu_lorawan.AtomDTULoRaWANBase:11 +#: driver.asr650x.LoRaWAN_470:7 +#: driver.asr650x.LoRaWAN_470.check_downlink_data:11 +#: driver.asr650x.LoRaWAN_470.check_join_status:10 +#: driver.asr650x.LoRaWAN_470.check_uplink_status:10 +#: driver.asr650x.LoRaWAN_470.config_abp:11 +#: driver.asr650x.LoRaWAN_470.config_otaa:11 +#: driver.asr650x.LoRaWAN_470.get_abp_config:10 +#: driver.asr650x.LoRaWAN_470.get_otaa_config:10 +#: driver.asr650x.LoRaWAN_Asr650x:6 driver.asr650x.LoRaWAN_Asr650x.join:14 +#: driver.asr650x.LoRaWAN_Asr650x.send_data:11 +#: driver.asr650x.LoRaWAN_Asr650x.set_class_mode:10 +#: driver.asr650x.LoRaWAN_Asr650x.set_frequency_band_mask:5 +#: driver.asr650x.LoRaWAN_Asr650x.set_join_mode:9 +#: driver.asr650x.LoRaWAN_Asr650x.set_rx_window_param:7 +#: driver.asr650x.LoRaWAN_Asr650x.set_uplink_app_port:9 +#: driver.asr650x.LoRaWAN_Asr650x.set_uplink_downlink_mode:5 +#: e73e5abcd0d341c1a4d300dc434d592e of +msgid "MicroPython Code Block:" +msgstr "MicroPython 代码块:" + +#: ../../en/base/dtu_lorawan.rst:53 3bbbc1bb771b434baf662d6ce997e279 +msgid "**API**" +msgstr "" + +#: ../../en/base/dtu_lorawan.rst:56 5fa8395d2d924490a1d790e1e1ba49ee +msgid "AtomDTULoRaWANBase" +msgstr "" + +#: base.dtu_lorawan.AtomDTULoRaWANBase:1 e6f29a1127274972bf070ca7d845a300 of +msgid "Bases: :py:class:`~driver.asr650x.LoRaWAN_470`" +msgstr "" + +#: base.dtu_lorawan.AtomDTULoRaWANBase:1 dcead66aab1147f890a9e9c165fbc286 of +msgid "Create an AtomDTULoRaWANBase object" +msgstr "创建 AtomDTULoRaWANBase 对象。" + +#: ../../en/base/dtu_lorawan.rst 3637970253a0457ba8b8530f3d2434c7 +#: ba50db3199134a7ba499968ed348490b base.dtu_lorawan.AtomDTULoRaWANBase +#: ccb7115e40fb4743a24748b96638888b +#: driver.asr650x.LoRaWAN_470.check_downlink_data +#: driver.asr650x.LoRaWAN_470.config_abp driver.asr650x.LoRaWAN_470.config_otaa +#: driver.asr650x.LoRaWAN_Asr650x.join driver.asr650x.LoRaWAN_Asr650x.send_data +#: driver.asr650x.LoRaWAN_Asr650x.set_class_mode +#: driver.asr650x.LoRaWAN_Asr650x.set_frequency_band_mask +#: driver.asr650x.LoRaWAN_Asr650x.set_join_mode +#: driver.asr650x.LoRaWAN_Asr650x.set_rx_window_param +#: driver.asr650x.LoRaWAN_Asr650x.set_uplink_app_port +#: driver.asr650x.LoRaWAN_Asr650x.set_uplink_downlink_mode of +msgid "Parameters" +msgstr "参数" + +#: 1dd3c0a943514d1a9fb687a61d57f7eb base.dtu_lorawan.AtomDTULoRaWANBase:3 of +msgid "The UART ID to use (0, 1, or 2). Default is 2." +msgstr "UART ID 号可使用 0, 1, 2, 默认使用2。" + +#: b82f559fc4f44c978f2b0b17eead81dd base.dtu_lorawan.AtomDTULoRaWANBase:4 of +msgid "A list or tuple containing the TX and RX pin numbers." +msgstr "包含 TX 和 RX 引脚编号的列表或元组。" + +#: base.dtu_lorawan.AtomDTULoRaWANBase:9 f69bea33d8624feeb2a8b38f7ad18325 of +msgid "|init.png|" +msgstr "" + +#: ../../en/refs/base.dtu_lorawan.ref:17 6c7c4861892b4ad0a04782366b582756 +msgid "init.png" +msgstr "" + +#: 2da51ecd7e544d9abe14cfe095181f10 driver.asr650x.LoRaWAN_470:1 of +msgid "Bases: :py:class:`~driver.asr650x.LoRaWAN_Asr650x`" +msgstr "" + +#: a5417dbdc0d84bcea32ad6030c2214ca driver.asr650x.LoRaWAN_470:1 +#: driver.asr650x.LoRaWAN_Asr650x:1 of +msgid "Create an LoRaWAN object." +msgstr "创建 LoRaWAN 对象。" + +#: 8b919b170d0745ca954d6f5b822d8bca driver.asr650x.LoRaWAN_470:3 of +msgid "The UART TX pin number." +msgstr "UART TX 引脚编号。" + +#: b1c960be89b34aeb8836eef6468d47d3 driver.asr650x.LoRaWAN_470:4 of +msgid "The UART RX pin number." +msgstr "UART RX 引脚编号。" + +#: 7ce02d55f97a45758a5c465c2b7d8c1e driver.asr650x.LoRaWAN_470:5 +#: driver.asr650x.LoRaWAN_Asr650x:4 of +msgid "Whether to enable debug mode." +msgstr "是否开启调试模式" + +#: 76cc2d7fe6a745d895e31dbd00afcb85 +#: driver.asr650x.LoRaWAN_470.check_downlink_data:1 of +msgid "Check downlink data, if have downlink data, return the message." +msgstr "检查下行链路数据,如果有下行链路数据,则返回信息。" + +#: driver.asr650x.LoRaWAN_470.check_downlink_data:3 +#: f752cc40daca4df2942bd56df0cb83c5 of +msgid "The timeout time." +msgstr "超时时间。" + +#: 9cef9bbbcc604ecda1d6e26b4af0d4fc +#: driver.asr650x.LoRaWAN_470.check_downlink_data +#: driver.asr650x.LoRaWAN_470.check_join_status +#: driver.asr650x.LoRaWAN_470.check_uplink_status +#: driver.asr650x.LoRaWAN_470.get_abp_config +#: driver.asr650x.LoRaWAN_470.get_otaa_config of +msgid "Returns" +msgstr "返回值" + +#: 76cc2d7fe6a745d895e31dbd00afcb85 +#: driver.asr650x.LoRaWAN_470.check_downlink_data:4 of +msgid "False if no downlink data, otherwise return the downlink data." +msgstr "如果没有下行链路数据则为 False,否则返回下行链路数据。" + +#: 52bab6fb4f464b4b95df625165a1b95b +#: driver.asr650x.LoRaWAN_470.check_downlink_data +#: driver.asr650x.LoRaWAN_470.check_join_status +#: driver.asr650x.LoRaWAN_470.check_uplink_status +#: driver.asr650x.LoRaWAN_470.get_abp_config +#: driver.asr650x.LoRaWAN_470.get_otaa_config of +msgid "Return type" +msgstr "返回类型" + +#: a6d28373675649c99e94aa8dab0f87e6 +#: driver.asr650x.LoRaWAN_470.check_downlink_data:9 of +msgid "|check_downlink_data.png|" +msgstr "" + +#: ../../en/refs/base.dtu_lorawan.ref:23 3c2a20a530bd4de0836654bd4893b185 +msgid "check_downlink_data.png" +msgstr "" + +#: 7055eee76a234616816b5feb88702e0d +#: driver.asr650x.LoRaWAN_470.check_join_status:1 of +msgid "Check the LoRaWAN network join status." +msgstr "检查 LoRaWAN 网络加入状态。" + +#: dda4b8dfcf7946f4bda3742352675c91 +#: driver.asr650x.LoRaWAN_470.check_join_status:3 of +msgid "The LoRaWAN network join status." +msgstr "LoRaWAN 网络加入状态。" + +#: 245c37aaa005497294dbaa1bac4174fb +#: driver.asr650x.LoRaWAN_470.check_join_status:8 of +msgid "|check_join_status.png|" +msgstr "" + +#: ../../en/refs/base.dtu_lorawan.ref:21 6c7c4861892b4ad0a04782366b582756 +msgid "check_join_status.png" +msgstr "" + +#: 77958755c83046a7b73da09fd1135f2a +#: driver.asr650x.LoRaWAN_470.check_uplink_status:1 of +msgid "Check the data uplink status." +msgstr "检查数据上行链路状态。" + +#: 7e79c2d89d814d99a2425eec0ebb8dce +#: driver.asr650x.LoRaWAN_470.check_uplink_status:3 of +msgid "The data uplink status." +msgstr "数据上行链路状态。" + +#: ddafdc6fa62b440f8a3ed05d595a582d +#: driver.asr650x.LoRaWAN_470.check_uplink_status:8 of +msgid "|check_uplink_status.png|" +msgstr "" + +#: ../../en/refs/base.dtu_lorawan.ref:22 6c7c4861892b4ad0a04782366b582756 +msgid "check_uplink_status.png" +msgstr "检查数据上行链路状态。" + +#: 9343acadf0104616867adc75faff56bd driver.asr650x.LoRaWAN_470.config_abp:1 of +msgid "Config the ABP join mode information." +msgstr "配置 ABP 连接模式信息。" + +#: b71c70ae5562466fa7a33e0ea448929f driver.asr650x.LoRaWAN_470.config_abp:3 of +msgid "The device address." +msgstr "设备地址。" + +#: driver.asr650x.LoRaWAN_470.config_abp:4 ebbc98abae7f4c1e92c27f529f7d374f of +msgid "The application session key." +msgstr "应用程序会话密钥。" + +#: driver.asr650x.LoRaWAN_470.config_abp:5 e214e0e19f2048cc9abc47384cc038c3 of +msgid "The network session key." +msgstr "网络会话密钥。" + +#: 4ac84004c3c84bef9d5f330f8c307f74 driver.asr650x.LoRaWAN_470.config_abp:9 of +msgid "|config_abp.png|" +msgstr "" + +#: ../../en/refs/base.dtu_lorawan.ref:26 6c7c4861892b4ad0a04782366b582756 +msgid "config_abp.png" +msgstr "" + +#: 50a63e1ead9c46dcac7cabb80c83f98d driver.asr650x.LoRaWAN_470.config_otaa:1 of +msgid "Config the OTAA join mode information." +msgstr "配置 OTAA 连接模式信息。" + +#: 29ba0db402b148738c4a89f34a280095 driver.asr650x.LoRaWAN_470.config_otaa:3 of +msgid "The device EUI." +msgstr "设备 EUI。" + +#: b4ff68069aff44d489afe8e5988c30fc driver.asr650x.LoRaWAN_470.config_otaa:4 of +msgid "The application EUI." +msgstr "应用程序 EUI。" + +#: 2868b6dff1614685b41b263def3a4669 driver.asr650x.LoRaWAN_470.config_otaa:5 of +msgid "The application key." +msgstr "应用程序密钥。" + +#: a7fd52d2e9ee4ab4b89c4b29b3715517 driver.asr650x.LoRaWAN_470.config_otaa:9 of +msgid "|config_otaa.png|" +msgstr "" + +#: ../../en/refs/base.dtu_lorawan.ref:25 6c7c4861892b4ad0a04782366b582756 +msgid "config_otaa.png" +msgstr "" + +#: be72627f5720464a90cc077af41518e5 driver.asr650x.LoRaWAN_470.get_abp_config:1 +#: of +msgid "Get the ABP join mode information." +msgstr "获取 ABP 连接模式信息。" + +#: 893ce916ea464c8f822b1e1b92e6caed driver.asr650x.LoRaWAN_470.get_abp_config:3 +#: of +msgid "The ABP join mode information(devaddr, appskey, newskey)." +msgstr "ABP 连接模式信息(devaddr、appskey、newskey)。" + +#: 319705f8c69c49d38150361139b44c96 driver.asr650x.LoRaWAN_470.get_abp_config:8 +#: of +msgid "|get_abp_config.png|" +msgstr "" + +#: ../../en/refs/base.dtu_lorawan.ref:19 6c7c4861892b4ad0a04782366b582756 +msgid "get_abp_config.png" +msgstr "" + +#: a73bef66585f47dbae4da55992d1a97c +#: driver.asr650x.LoRaWAN_470.get_otaa_config:1 of +msgid "Get the OTAA join mode information." +msgstr "获取 OTAA 连接模式信息。" + +#: 885180d79d664c628294e4590b648d38 +#: driver.asr650x.LoRaWAN_470.get_otaa_config:3 of +msgid "The OTAA join mode information(deveui, appeui, appkey)." +msgstr "OTAA 连接模式信息(deveui、appskey、newskey)。" + +#: driver.asr650x.LoRaWAN_470.get_otaa_config:8 +#: f7f0bb2bdd5744d886964242aa91c0c0 of +msgid "|get_otaa_config.png|" +msgstr "" + +#: ../../en/refs/base.dtu_lorawan.ref:20 6c7c4861892b4ad0a04782366b582756 +msgid "get_otaa_config.png" +msgstr "" + +#: c88482d320d64266b899e1e7e0786059 driver.asr650x.LoRaWAN_Asr650x:1 of +msgid "Bases: :py:class:`object`" +msgstr "" + +#: 947e007eb7364ce5b7c3a407bd1fb41d driver.asr650x.LoRaWAN_Asr650x:3 of +msgid "The UART object." +msgstr "UART 对象。" + +#: 6e666fd90fe441d1b5e021ef51e15a32 +#: driver.asr650x.LoRaWAN_Asr650x.set_join_mode:1 of +msgid "Set the LoRaWAN join mode." +msgstr "设置 LoRaWAN 连接模式。" + +#: 4acf57d75f264e9ab1be3a40494bfdbc +#: driver.asr650x.LoRaWAN_Asr650x.set_join_mode:3 of +msgid "The LoRaWAN join mode." +msgstr "LoRaWAN 连接模式。" + +#: d7095628441a4a0e99d59434c047c01f +#: driver.asr650x.LoRaWAN_Asr650x.set_join_mode:7 of +msgid "|set_join_mode.png|" +msgstr "" + +#: ../../en/refs/base.dtu_lorawan.ref:31 6c7c4861892b4ad0a04782366b582756 +msgid "set_join_mode.png" +msgstr "" + +#: 1a17cf160769422283cb0a759dd969f0 +#: driver.asr650x.LoRaWAN_Asr650x.set_frequency_band_mask:1 of +msgid "Set the frequency band mask." +msgstr "设置频段掩码。" + +#: 6bf0e1ed79fe41c18f2136af4db1561c +#: driver.asr650x.LoRaWAN_Asr650x.set_frequency_band_mask:3 of +msgid "The frequency band mask." +msgstr "频段掩码。" + +#: 24cd213f431547acb55f6a4b1a93e4ef +#: driver.asr650x.LoRaWAN_Asr650x.set_uplink_downlink_mode:1 of +msgid "Set the uplink and downlink frequency." +msgstr "设置上行和下行链路的频率。" + +#: 5316e6818ff541668966ebf63cbc4ad4 +#: driver.asr650x.LoRaWAN_Asr650x.set_uplink_downlink_mode:3 of +msgid "The uplink and downlink frequency." +msgstr "上行和下行链路的频率。" + +#: b542e1a1e08e44e4b4bbe9b08bd39fca +#: driver.asr650x.LoRaWAN_Asr650x.set_class_mode:1 of +msgid "" +"Set the class mode, if the class mode is 0, the branch, para1, para2, " +"para3, para4 will be ignored." +msgstr "设置class 模式,如果class模式为 0,则忽略branch、para1、para2、para3 和para4。" + +#: driver.asr650x.LoRaWAN_Asr650x.set_class_mode:3 +#: fd24239fed0c45acba5a89aca798fae1 of +msgid "The class mode." +msgstr "class模式。" + +#: 22fe123bbf6a4293a9610324c5fbe993 +#: driver.asr650x.LoRaWAN_Asr650x.set_class_mode:4 of +msgid "The branch selection." +msgstr "branch选择。" + +#: a05244c86dd74a0b9f47f8a78ea83ede +#: driver.asr650x.LoRaWAN_Asr650x.set_class_mode:5 of +msgid "Set the beacon frequency, unit is Hz." +msgstr "设置beacon 频率,单位为 Hz。" + +#: driver.asr650x.LoRaWAN_Asr650x.set_class_mode:6 +#: e99e43d809844d07baa9105fa25bb105 of +msgid "Set the beacon data rate." +msgstr "设置beacon 数据速率。" + +#: 136c34e4e78d439ea1fcedd7fe24ad84 +#: driver.asr650x.LoRaWAN_Asr650x.set_class_mode:7 of +msgid "Set ping frequency, unit is Hz." +msgstr "设置 ping 频率,单位为 Hz。" + +#: c99d9ee1c8ad475484038197be699f32 +#: driver.asr650x.LoRaWAN_Asr650x.set_class_mode:8 of +msgid "Set ping data rate." +msgstr "设置 ping 数据速率。" + +#: 5abd0baf05e346ffb2f8a700e81d08d0 driver.asr650x.LoRaWAN_Asr650x.join:1 of +msgid "Join the LoRaWAN network." +msgstr "加入 LoRaWAN 网络。" + +#: 087322b56e2a4261b07ee00093c1fb53 driver.asr650x.LoRaWAN_Asr650x.join:3 of +msgid "0 stop join, 1 start join." +msgstr "0 停止加入,1 开始加入。" + +#: driver.asr650x.LoRaWAN_Asr650x.join:4 e53575ba92ec4a469b2a3244e91645fc of +msgid "0 close auto join, 1 open auto join." +msgstr "0 关闭自动加入,1 打开自动加入。" + +#: 0efd28816fd842ebaf5b6df0aa180ae4 driver.asr650x.LoRaWAN_Asr650x.join:5 of +msgid "join interval, unit is second(7~255)." +msgstr "加入间隔,单位为秒(7~255)。" + +#: 7e7d425d4a6c4a158a3c7828f891964f driver.asr650x.LoRaWAN_Asr650x.join:6 of +msgid "join retry times(1~256)." +msgstr "加入重试次数(1~256)。" + +#: 5f065385572848d596c48d2a78ddaf7b driver.asr650x.LoRaWAN_Asr650x.join:10 of +msgid "|join.png|" +msgstr "" + +#: ../../en/refs/base.dtu_lorawan.ref:32 6c7c4861892b4ad0a04782366b582756 +msgid "join.png" +msgstr "" + +#: 82afce4d3d244535af386fce51bdf25d driver.asr650x.LoRaWAN_Asr650x.join:12 of +msgid "|join_stop.png|" +msgstr "" + +#: ../../en/refs/base.dtu_lorawan.ref:33 6c7c4861892b4ad0a04782366b582756 +msgid "join_stop.png" +msgstr "" + +#: 82316a10914f400ab69ce8198f62255f driver.asr650x.LoRaWAN_Asr650x.send_data:1 +#: of +msgid "Send data payload to LoRaWAN gateway." +msgstr "向 LoRaWAN 网关发送数据有效载荷。" + +#: dcf767f2993b499385e30e35327e92d2 driver.asr650x.LoRaWAN_Asr650x.send_data:3 +#: of +msgid "The data to send." +msgstr "要发送的数据。" + +#: 67c1933a892d43b5a61b554256e74443 driver.asr650x.LoRaWAN_Asr650x.send_data:4 +#: of +msgid "The confirm mode." +msgstr "确认模式。" + +#: driver.asr650x.LoRaWAN_Asr650x.send_data:5 e39f4554920f4830a6262af32afdbd81 +#: of +msgid "The number of trials." +msgstr "尝试重连的次数。" + +#: 347f5d37aa804ab19c3c36c6892352b6 driver.asr650x.LoRaWAN_Asr650x.send_data:9 +#: of +msgid "|send_data.png|" +msgstr "" + +#: ../../en/refs/base.dtu_lorawan.ref:35 6c7c4861892b4ad0a04782366b582756 +msgid "send_data.png" +msgstr "" + +#: 0fe5169238484f7cb87f2428461bd5f0 +#: driver.asr650x.LoRaWAN_Asr650x.set_uplink_app_port:1 of +msgid "Set the uplink app port." +msgstr "设置上行链路app端口。" + +#: 2b55b7e90a87482da486ef72b387c09e +#: driver.asr650x.LoRaWAN_Asr650x.set_uplink_app_port:3 of +msgid "The uplink app port." +msgstr "上行链路app端口。" + +#: a5862d7087114b25bee5f63255ca7ab3 +#: driver.asr650x.LoRaWAN_Asr650x.set_uplink_app_port:7 of +msgid "|set_uplink_app_port.png|" +msgstr "" + +#: ../../en/refs/base.dtu_lorawan.ref:34 6c7c4861892b4ad0a04782366b582756 +msgid "set_uplink_app_port.png" +msgstr "" + +#: driver.asr650x.LoRaWAN_Asr650x.set_rx_window_param:1 +#: e88ff972cf904359b26d0c8a89bf7219 of +msgid "Set the receive window parameter." +msgstr "设置接收窗口参数。" + +#: 1e827171d64e4656a118176771377ecd +#: driver.asr650x.LoRaWAN_Asr650x.set_rx_window_param:3 of +msgid "The RX1 offset." +msgstr "RX1 偏移量。" + +#: ddc8912755d34b5ba3eef99f36f4f9de +#: driver.asr650x.LoRaWAN_Asr650x.set_rx_window_param:4 of +msgid "The RX2 data rate." +msgstr "RX2 数据速率" + +#: deb328a1edc444d7834c9c1283528d15 +#: driver.asr650x.LoRaWAN_Asr650x.set_rx_window_param:5 of +msgid "The RX2 frequency." +msgstr "RX2 频率。" + diff --git a/examples/base/dtu_lorawan/atoms3r_dtu_lorawan_example.m5f2 b/examples/base/dtu_lorawan/atoms3r_dtu_lorawan_example.m5f2 new file mode 100644 index 00000000..d8264359 --- /dev/null +++ b/examples/base/dtu_lorawan/atoms3r_dtu_lorawan_example.m5f2 @@ -0,0 +1 @@ +{"version":"V2.0","versionNumber":"V2.2.3","type":"atoms3r","components":[{"name":"screen","type":"screen","layer":0,"screenId":"builtin","screenName":"","id":"__atoms3r_screen","createTime":1741251596476,"x":0,"y":0,"width":128,"height":128,"backgroundColor":"#000000","size":0,"isSelected":true},{"name":"title0","type":"title","layer":1,"screenId":"builtin","screenName":"","id":"susozwa0!j`P*B-V","createTime":1741254702788,"x":0,"y":0,"color":"#ffffff","backgroundColor":"#0000FF","text":"LoRaWAN","textOffset":3,"font":"Widgets.FONTS.DejaVu18","isSelected":false}],"resources":[{"hardware":["hardware_button","hardware_pin_button","imu","ir"]},{"base":["base_lorawan470"]}],"units":[],"hats":[],"bases":[{"type":"base_lorawan470","name":"base_lorawan470","id":"x@Q&5$A4-hxfxLA*","createTime":1741251620983,"initBlockType":"base_lorawan470_init"}],"i2cs":[],"blockly":"true2560OTAAxxxxxxxxxxxx04000050530000021112020LoRaWAN configuration completetrueBtnASend Messagetrue1115","screen":[{"simulationName":"Built-in","type":"builtin","width":128,"height":128,"scale":1.3,"screenName":"","blockId":"","screenColorType":0,"id":"builtin","createTime":1741251596473}],"logicWhenNum":0,"customList":[]} \ No newline at end of file diff --git a/examples/base/dtu_lorawan/atoms3r_dtu_lorawan_example.py b/examples/base/dtu_lorawan/atoms3r_dtu_lorawan_example.py new file mode 100644 index 00000000..5242c7e0 --- /dev/null +++ b/examples/base/dtu_lorawan/atoms3r_dtu_lorawan_example.py @@ -0,0 +1,52 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import os, sys, io +import M5 +from M5 import * +from base import AtomDTULoRaWANBase + + +title0 = None +base_lorawan470 = None + + +def setup(): + global title0, base_lorawan470 + + M5.begin() + title0 = Widgets.Title("LoRaWAN", 3, 0xFFFFFF, 0x0000FF, Widgets.FONTS.DejaVu18) + + base_lorawan470 = AtomDTULoRaWANBase(2, port=(5, 6)) + base_lorawan470.set_join_mode(0) + base_lorawan470.config_otaa("xxxx", "xxxx", "xxxx") + base_lorawan470.set_frequency_band_mask("0400") + base_lorawan470.set_rx_window_param(0, 0, 505300000) + base_lorawan470.set_class_mode(2) + base_lorawan470.set_uplink_downlink_mode(1) + base_lorawan470.set_uplink_app_port(1) + base_lorawan470.join(1, 1, 20, 20) + print("LoRaWAN configuration complete") + + +def loop(): + global title0, base_lorawan470 + M5.update() + if BtnA.isPressed(): + print("Send Message") + base_lorawan470.send_data("11", 1, 15) + + +if __name__ == "__main__": + try: + setup() + while True: + loop() + except (Exception, KeyboardInterrupt) as e: + try: + from utility import print_error_msg + + print_error_msg(e) + except ImportError: + print("please update to latest firmware") diff --git a/m5stack/libs/base/atom_gps.py b/m5stack/libs/base/atom_gps.py index 87af6bde..9babf72b 100644 --- a/m5stack/libs/base/atom_gps.py +++ b/m5stack/libs/base/atom_gps.py @@ -31,7 +31,7 @@ class ATOMGPSBase: from machine import UART from base import ATOMGPSBase - gps = ATOMGPSBase(id=1, port=(16, 17), debug=True) + gps = ATOMGPSBase(id=1, port=(16, 17)) """ def __init__(self, id: Literal[0, 1, 2] = 1, port: list | tuple = None, debug=False): diff --git a/m5stack/libs/base/dtu_lorawan.py b/m5stack/libs/base/dtu_lorawan.py new file mode 100644 index 00000000..357fd0f8 --- /dev/null +++ b/m5stack/libs/base/dtu_lorawan.py @@ -0,0 +1,40 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +from driver.asr650x import LoRaWAN_470 +import machine +import sys + +if sys.platform != "esp32": + from typing import Literal + + +class AtomDTULoRaWANBase(LoRaWAN_470): + """Create an AtomDTULoRaWANBase object + + :param int id: The UART ID to use (0, 1, or 2). Default is 2. + :param port: A list or tuple containing the TX and RX pin numbers. + :type port: list | tuple + + UiFlow2 Code Block: + + |init.png| + + MicroPython Code Block: + + .. code-block:: python + + from base import AtomDTULoRaWANBase + + dtu_lorawan = AtomDTULoRaWANBase(0, (6, 5)) + """ + + def __init__(self, id: Literal[0, 1, 2] = 1, port: list | tuple = None): + self._uart = machine.UART( + id, tx=port[0], rx=port[1], baudrate=115200, bits=8, parity=None, stop=1 + ) + super(LoRaWAN_470, self).__init__(self._uart) + + def deinit(self): + pass diff --git a/m5stack/libs/base/manifest.py b/m5stack/libs/base/manifest.py index 90e861f0..35335eb7 100644 --- a/m5stack/libs/base/manifest.py +++ b/m5stack/libs/base/manifest.py @@ -9,6 +9,7 @@ "atom_gps.py", "atom_socket.py", "display.py", + "dtu_lorawan.py", "echo.py", "hdriver.py", "motion.py", diff --git a/m5stack/libs/driver/asr650x.py b/m5stack/libs/driver/asr650x.py index 5b52fff6..72552980 100644 --- a/m5stack/libs/driver/asr650x.py +++ b/m5stack/libs/driver/asr650x.py @@ -12,9 +12,22 @@ class LoRaWAN_Asr650x(object): - def __init__(self, tx, rx, debug=False): - self.__uart = machine.UART(1, tx=tx, rx=rx) - self.__uart.init(115200, bits=0, parity=None, stop=1) + """Create an LoRaWAN object. + + :param machine.UART uart: The UART object. + :param bool debug: Whether to enable debug mode. + + MicroPython Code Block: + + .. code-block:: python + + from driver.asr650x import LoRaWAN_Asr650x + + lora = LoRaWAN_Asr650x(uart) + """ + + def __init__(self, uart, debug=False): + self.__uart = uart self.debug = debug self.set_work_mode(2) self._downlink_buffer = [] @@ -293,14 +306,19 @@ def get_join_mode(self): return "OTAA" def set_join_mode(self, mode): - """ - Set LoRaWAN Mode. - Parameter: - OTAA 0 - ABP 1 - Return: - True - False + """Set the LoRaWAN join mode. + + :param int mode: The LoRaWAN join mode. + + UiFlow2 Code Block: + + |set_join_mode.png| + + MicroPython Code Block: + + .. code-block:: python + + lora.set_join_mode(0) """ cmd = "AT+CJOINMODE=" + str(mode) result, error = self.__at_cmd(cmd) @@ -322,13 +340,15 @@ def get_frequency_band_mask(self): return result[1][15:] def set_frequency_band_mask(self, mask): - """ - Get frequency band mask. - Parameter: - mask - Return: - True - False + """Set the frequency band mask. + + :param str mask: The frequency band mask. + + MicroPython Code Block: + + .. code-block:: python + + lora.set_frequency_band_mask("0001") """ cmd = "AT+CFREQBANDMASK=" + str(mask) result, error = self.__at_cmd(cmd) @@ -352,15 +372,15 @@ def get_uplink_downlink_mode(self): return result[1][11:] def set_uplink_downlink_mode(self, mode): - """ - Get uplink and downlink mode. - Parameter: - mode: - 1 Same frequency mode - 2 Inter-frequency mode - Return: - True - False + """Set the uplink and downlink frequency. + + :param int mode: The uplink and downlink frequency. + + MicroPython Code Block: + + .. code-block:: python + + lora.set_uplink_downlink_mode(1) """ cmd = "AT+CULDLMODE=" + str(mode) result, error = self.__at_cmd(cmd) @@ -418,21 +438,20 @@ class mode: def set_class_mode( self, class_mode, branch=None, para1=None, para2=None, para3=None, para4=None ): - """ - Set class mode with params. - Parameter: - class_mode: - 0 classA - 1 classB - 2 classC - branch: - para1: - para2: - para3: - para4: - Return: - True - False + """Set the class mode, if the class mode is 0, the branch, para1, para2, para3, para4 will be ignored. + + :param int class_mode: The class mode. + :param int branch: The branch selection. + :param int para1: Set the beacon frequency, unit is Hz. + :param int para2: Set the beacon data rate. + :param int para3: Set ping frequency, unit is Hz. + :param int para4: Set ping data rate. + + MicroPython Code Block: + + .. code-block:: python + + lora.set_class_mode(0, 0, 0, 0, 0, 0) """ cmd = "AT+CCLASS=" + str(class_mode) if ( @@ -467,22 +486,27 @@ def get_status(self): pass def join(self, para1, para2=None, para3=None, para4=None): - """ - Join LoRaWAN network. - Parameter: - para1: - 0 stop join - 1 start join - para2: - 0 close auto join - 1 open auto join - para3: - 7 ~ 255 - para4: - 1 ~ 256 - Return: - True - False + """Join the LoRaWAN network. + + :param int para1: 0 stop join, 1 start join. + :param int para2: 0 close auto join, 1 open auto join. + :param int para3: join interval, unit is second(7~255). + :param int para4: join retry times(1~256). + + UiFlow2 Code Block: + + |join.png| + + |join_stop.png| + + MicroPython Code Block: + + .. code-block:: python + + lora.join(1, 1, 8, 1) + + lora.join(0) + """ cmd = "AT+CJOIN=" + str(para1) if para2 is not None and para3 is not None and para4 is not None: @@ -491,19 +515,21 @@ def join(self, para1, para2=None, para3=None, para4=None): return not error def send_data(self, payload, confirm=None, nbtrials=None): - """ - Send data. - Parameter: - confirm: - 0 - 1 - nbtrials: - 1 ~ 15 - payload: - xxxxxx HEX format string - Return: - True - False + """Send data payload to LoRaWAN gateway. + + :param str payload: The data to send. + :param int confirm: The confirm mode. + :param int nbtrials: The number of trials. + + UiFlow2 Code Block: + + |send_data.png| + + MicroPython Code Block: + + .. code-block:: python + + lora.send_data("Hello, World!", 1, 1) """ data = self._bytes_to_hex_str(payload.encode()) if confirm is not None and nbtrials is not None: @@ -553,13 +579,19 @@ def set_uplink_confirm_mode(self, mode): return not error def set_uplink_app_port(self, port): - """ - Set uplink app port, setting before send data. - Parameter: - port 1 ~ 233 - Return: - True - False + """Set the uplink app port. + + :param int port: The uplink app port. + + UiFlow2 Code Block: + + |set_uplink_app_port.png| + + MicroPython Code Block: + + .. code-block:: python + + lora.set_uplink_app_port(1) """ cmd = "AT+CAPPPORT=" + str(port) result, error = self.__at_cmd(cmd) @@ -636,22 +668,19 @@ def enable_adaptive_datarate(self, status): return not error def set_rx_window_param(self, rx1_offset, rx2_dr, rx2_freq): + """Set the receive window parameter. + + :param int rx1_offset: The RX1 offset. + :param int rx2_dr: The RX2 data rate. + :param int rx2_freq: The RX2 frequency. + + MicroPython Code Block: + + .. code-block:: python + + lora.set_rx_window_param(0, 0, 868100000) """ - Set receive window param. - Parameter: - rx1_offset: - rx2_dr: - 0 SF12 BW125 - 1 SF11 BW125 - 2 SF10 BW125 - 3 SF9 BW125 - 4 SF8 BW125 - 5 SF7 BW125 - rx2_freq: int hz - Return: - True - False - """ + cmd = "AT+CRXP={},{},{}".format(rx1_offset, rx2_dr, rx2_freq) result, error = self.__at_cmd(cmd) return not error @@ -742,18 +771,41 @@ def _hex_str_to_bytes(self, hexStr): class LoRaWAN_470(LoRaWAN_Asr650x): - def __init__(self, tx, rx, debug=False): - """ - Parameter: + """Create an LoRaWAN object. - Return: - True - False - """ - super(LoRaWAN_470, self).__init__(tx, rx, debug) + :param int tx: The UART TX pin number. + :param int rx: The UART RX pin number. + :param bool debug: Whether to enable debug mode. + + MicroPython Code Block: + + .. code-block:: python + + from driver.asr650x import LoRaWAN_470 + + lora = LoRaWAN_470(tx=17, rx=16) + """ + + def __init__(self, uart, debug=False): + super(LoRaWAN_470, self).__init__(uart, debug) def config_abp(self, devaddr, appskey, nwkskey): - # 2.0.3添加 + """Config the ABP join mode information. + + :param str devaddr: The device address. + :param str appskey: The application session key. + :param str nwkskey: The network session key. + + UiFlow2 Code Block: + + |config_abp.png| + + MicroPython Code Block: + + .. code-block:: python + + lora.config_abp("0037CAE1FC3542B9", "70B3D57ED003B699", "67FA4ED1075A20573BCDD7594C458698") + """ self.config_ABP(devaddr, appskey, nwkskey) def config_ABP(self, devaddr, appskey, nwkskey): # noqa: N802 @@ -770,7 +822,21 @@ def config_ABP(self, devaddr, appskey, nwkskey): # noqa: N802 self.set_join_mode(1) def get_abp_config(self): - # 2.0.3添加 + """Get the ABP join mode information. + + :returns: The ABP join mode information(devaddr, appskey, newskey). + :rtype: tuple + + UiFlow2 Code Block: + + |get_abp_config.png| + + MicroPython Code Block: + + .. code-block:: python + + lora.get_abp_config() + """ return self.get_ABP_config() def get_ABP_config(self): # noqa: N802 @@ -785,7 +851,23 @@ def get_ABP_config(self): # noqa: N802 return (self.get_DevAddr(), self.get_APPSKEY(), self.get_NWKSKEY()) def config_otaa(self, deveui, appeui, appkey): - # 2.0.3添加 + """Config the OTAA join mode information. + + :param str deveui: The device EUI. + :param str appeui: The application EUI. + :param str appkey: The application key. + + UiFlow2 Code Block: + + |config_otaa.png| + + MicroPython Code Block: + + .. code-block:: python + + lora.config_otaa("0037CAE1FC3542B9", "70B3D57ED003B699", "67FA4ED1075A20573BCDD7594C458698") + """ + self.config_OTAA(deveui, appeui, appkey) def config_OTAA(self, deveui, appeui, appkey): # noqa: N802 @@ -804,7 +886,21 @@ def config_OTAA(self, deveui, appeui, appkey): # noqa: N802 self.set_join_mode(0) def get_otaa_config(self): - # 2.0.3添加 + """Get the OTAA join mode information. + + :returns: The OTAA join mode information(deveui, appeui, appkey). + :rtype: tuple + + UiFlow2 Code Block: + + |get_otaa_config.png| + + MicroPython Code Block: + + .. code-block:: python + + lora.get_otaa_config() + """ return self.get_OTAA_config() def get_OTAA_config(self): # noqa: N802 @@ -818,12 +914,20 @@ def get_OTAA_config(self): # noqa: N802 return (self.get_DevEui(), self.get_AppEui(), self.get_AppKey()) def check_join_status(self): - """ - Check the LoRaWAN network join status. - Parameter: - Return: - True join OK - False join fail + """Check the LoRaWAN network join status. + + :returns: The LoRaWAN network join status. + :rtype: bool + + UiFlow2 Code Block: + + |check_join_status.png| + + MicroPython Code Block: + + .. code-block:: python + + lora.check_join_status() """ if self._join_status: return self._join_status @@ -850,12 +954,20 @@ def check_join_status(self): pass def check_uplink_status(self): - """ - Check data uplink status. - Parameter: - Return: - True success - False failed + """Check the data uplink status. + + :returns: The data uplink status. + :rtype: bool + + UiFlow2 Code Block: + + |check_uplink_status.png| + + MicroPython Code Block: + + .. code-block:: python + + lora.check_uplink_status() """ cmd = "AT+CSTATUS?" result, error = self.__at_cmd(cmd) @@ -879,12 +991,21 @@ def check_uplink_status(self): pass def check_downlink_data(self, timeout=10): - """ - Check downlink data, dta will receive after uplink data. - Parameter: - Return: - false - data + """Check downlink data, if have downlink data, return the message. + + :param int timeout: The timeout time. + :returns: False if no downlink data, otherwise return the downlink data. + :rtype: bool | str + + UiFlow2 Code Block: + + |check_downlink_data.png| + + MicroPython Code Block: + + .. code-block:: python + + lora.check_downlink_data() """ msgs = [] now_time = time.time() @@ -904,7 +1025,7 @@ def check_downlink_data(self, timeout=10): class LoRaWAN_915(LoRaWAN_470): - def __init__(self, tx, rx, debug=False): + def __init__(self, uart, debug=False): """ Parameter: @@ -912,11 +1033,11 @@ def __init__(self, tx, rx, debug=False): True False """ - super(LoRaWAN_915, self).__init__(tx, rx, debug) + super(LoRaWAN_915, self).__init__(uart, debug) class LoRaWAN_868(LoRaWAN_470): - def __init__(self, tx, rx, debug=False): + def __init__(self, uart, debug=False): """ Parameter: @@ -924,7 +1045,7 @@ def __init__(self, tx, rx, debug=False): True False """ - super(LoRaWAN_868, self).__init__(tx, rx, debug) + super(LoRaWAN_868, self).__init__(uart, debug) """ diff --git a/m5stack/libs/unit/lorawan.py b/m5stack/libs/unit/lorawan.py index 7cb9530d..334bea91 100644 --- a/m5stack/libs/unit/lorawan.py +++ b/m5stack/libs/unit/lorawan.py @@ -12,18 +12,10 @@ class LoRaWANUnit(LoRaWAN_470): def __init__(self, id: Literal[0, 1, 2] = 1, port: list | tuple = None): - self._id = id - super(LoRaWAN_470, self).__init__(tx=port[1], rx=port[0]) - self.tx = port[1] - self.rx = port[0] - - def uart_port_id(self, id_num): - """ - set core device uart id - id_num: 1-2 - """ - self.__uart = machine.UART(id_num, tx=self.tx, rx=self.rx) - self.__uart.init(115200, bits=0, parity=None, stop=1) + self._uart = machine.UART( + id, tx=port[0], rx=port[1], baudrate=115200, bits=8, parity=None, stop=1 + ) + super(LoRaWAN_470, self).__init__(self._uart) def deinit(self): pass From 7757cb08f4ea4a05785c4f5fe8e9edff521ccc48 Mon Sep 17 00:00:00 2001 From: Tinyu-Zhao Date: Thu, 13 Mar 2025 14:38:08 +0800 Subject: [PATCH 021/322] lib/base: Add Atomic TFCard base support. Signed-off-by: Tinyu-Zhao --- docs/en/base/index.rst | 1 + docs/en/base/tfcard.rst | 91 +++++++++ docs/en/refs/base.tfcard.ref | 22 +++ docs/locales/zh_CN/LC_MESSAGES/base/tfcard.po | 179 ++++++++++++++++++ .../base/tfcard/atoms3r_tfcard_example.m5f2 | 1 + .../base/tfcard/atoms3r_tfcard_example.py | 54 ++++++ m5stack/libs/base/__init__.py | 1 + m5stack/libs/base/manifest.py | 1 + m5stack/libs/base/tfcard.py | 5 + m5stack/libs/hardware/__init__.py | 1 + m5stack/libs/hardware/sdcard.py | 3 +- 11 files changed, 358 insertions(+), 1 deletion(-) create mode 100644 docs/en/base/tfcard.rst create mode 100644 docs/en/refs/base.tfcard.ref create mode 100644 docs/locales/zh_CN/LC_MESSAGES/base/tfcard.po create mode 100644 examples/base/tfcard/atoms3r_tfcard_example.m5f2 create mode 100644 examples/base/tfcard/atoms3r_tfcard_example.py create mode 100644 m5stack/libs/base/tfcard.py diff --git a/docs/en/base/index.rst b/docs/en/base/index.rst index 81d044a2..6a184a2f 100644 --- a/docs/en/base/index.rst +++ b/docs/en/base/index.rst @@ -17,3 +17,4 @@ Base rs485.rst speaker.rst stepmotor.rst + tfcard.rst diff --git a/docs/en/base/tfcard.rst b/docs/en/base/tfcard.rst new file mode 100644 index 00000000..c366f5e8 --- /dev/null +++ b/docs/en/base/tfcard.rst @@ -0,0 +1,91 @@ +Atomic TFCard Base +===================== + +.. sku: A135/K044 + +.. include:: ../refs/base.tfcard.ref + +This is the driver library for the Atomic TFCard Base, which is used to mount TFCard. + +Support the following products: + + ================== ===================== + |Atom TFCard| |Atomic TFCard Base| + ================== ===================== + + +UiFlow2 Example +--------------- + +TFCard mount +^^^^^^^^^^^^^^^^^ + +Open the |atoms3r_tfcard_example.m5f2| project in UiFlow2. + +This example demonstrates how to read/create a directory using Atomic TFCard Base. + +UiFlow2 Code Block: + + |example.png| + +Example output: + + Files in the /sd directory. + + +MicroPython Example +------------------- + +TFCard mount +^^^^^^^^^^^^^^^^^^ + +This example demonstrates how to read/create a directory using Atomic TFCard Base. + +MicroPython Code Block: + + .. literalinclude:: ../../../examples/base/tfcard/atoms3r_tfcard_example.py + :language: python + :linenos: + +Example output: + + Files in the /sd directory. + + +**API** +------- + +function AtomicTFCardBase +^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. function:: AtomicTFCardBase(slot=1, width=1, cd=None, wp=None, sck=None, miso=None, mosi=None, cs=None, freq=20000000) + + This function is only used to initialize and mount the SD card to + the /sd directory, and to try to unmount the existing SD card before + mounting it. Other file operations (such as reading/writing files, + creating directories, etc.) need to be performed by the os module. + + :param int slot: Which of the available interfaces to use. The default value is 1. + :param int width: The bus width for the SD/MMC interface. The default value is 1. + :param int cd: The card-detect pin to use. The default value is None. + :param int wp: The write-protect pin to use. The default value is None. + :param int sck: The SPI clock pin to use. The default value is None. + :param int miso: The SPI miso pin to use. The default value is None. + :param int mosi: The SPI mosi pin to use. The default value is None. + :param int cs: The SPI chip select pin to use. The default value is None. + :param int freq: The SD/MMC interface frequency in Hz. The default value is 20000000. + + :return: None + + UiFlow2 Code Block: + + |init.png| + + MicroPython Code Block: + + .. code-block:: python + + from base import AtomicTFCardBase + base_tfcard = AtomicTFCardBase(slot=3, width=1, sck=7, miso=8, mosi=6, freq=20000000) + + See :mod:`micropython:os` -- basic "operating system" for more details. diff --git a/docs/en/refs/base.tfcard.ref b/docs/en/refs/base.tfcard.ref new file mode 100644 index 00000000..bff3433a --- /dev/null +++ b/docs/en/refs/base.tfcard.ref @@ -0,0 +1,22 @@ +.. |Atom TFCard| image:: https://static-cdn.m5stack.com/resource/docs/products/atom/atomictf/atomictf_01.webp + :target: https://docs.m5stack.com/en/atom/atomictf + :height: 200px + :width: 200px + +.. |Atomic TFCard Base| image:: https://static-cdn.m5stack.com/resource/docs/products/atom/Atomic%20TF-Card%20Reader/img-f481a189-c8a3-4b21-8bde-24a6a8b4131d.webp + :target: https://docs.m5stack.com/en/atom/Atomic%20TF-Card%20Reader + :height: 200px + :width: 200px + +.. |init.png| image:: https://m5stack.oss-cn-shenzhen.aliyuncs.com/mpy_docs/base/tfcard/init.png + +.. |example.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/tfcard/example.png + +.. |atoms3r_tfcard_example.m5f2| raw:: html + + + atoms3r_tfcard_example.m5f2 + \ No newline at end of file diff --git a/docs/locales/zh_CN/LC_MESSAGES/base/tfcard.po b/docs/locales/zh_CN/LC_MESSAGES/base/tfcard.po new file mode 100644 index 00000000..3a63458a --- /dev/null +++ b/docs/locales/zh_CN/LC_MESSAGES/base/tfcard.po @@ -0,0 +1,179 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2016 - 2025 M5Stack Technology Co., Ltd +# This file is distributed under the same license as the UIFlow2 Programming +# Guide package. +# FIRST AUTHOR , 2025. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: UIFlow2 Programming Guide \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-03-20 10:02+0800\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: zh_CN\n" +"Language-Team: zh_CN \n" +"Plural-Forms: nplurals=1; plural=0;\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.16.0\n" + +#: ../../en/base/tfcard.rst:2 ../../en/refs/base.tfcard.ref +#: 53e706bccbfb4d3e8bf4f932d7826b2b eae765a617a845cc85f7120d60949b75 +msgid "Atomic TFCard Base" +msgstr "" + +#: ../../en/base/tfcard.rst:8 315ca7c8bc4840be8a14d40b6523756f +msgid "" +"This is the driver library for the Atomic TFCard Base, which is used to " +"mount TFCard." +msgstr "这是 Atomic TFCard Base 的驱动程序库,用于挂载TF卡。" + +#: ../../en/base/tfcard.rst:10 3df6cc370d0c4564a4a21c7b051d104e +msgid "Support the following products:" +msgstr "支持以下产品:" + +#: ../../en/base/tfcard.rst:13 bf49c52891e44f75bd5beb91e2a1cae8 +msgid "|Atom TFCard|" +msgstr "" + +#: ../../en/refs/base.tfcard.ref 12e9de2f9c204f16bdc3d28b168e9c04 +msgid "Atom TFCard" +msgstr "" + +#: ../../en/base/tfcard.rst:13 6fcf0d24183c4d3992e538f8ac2b172a +msgid "|Atomic TFCard Base|" +msgstr "" + +#: ../../en/base/tfcard.rst:18 26ac04b8ab8a4d8dbcd1b892ef921e0f +msgid "UiFlow2 Example" +msgstr "UIFLOW2 应用示例:" + +#: ../../en/base/tfcard.rst:21 ../../en/base/tfcard.rst:40 +#: 86974b2090f94483b498583be026e8e6 eaa59298d6e742a9a2ce85642c740694 +msgid "TFCard mount" +msgstr "TFCard 挂载" + +#: ../../en/base/tfcard.rst:23 d173dc7fcd6242caab91a8bf42fa3f8b +msgid "Open the |atoms3r_tfcard_example.m5f2| project in UiFlow2." +msgstr "在UiFlow2 中打开 |atoms3r_tfcard_example.m5f2| 项目。" + +#: ../../en/base/tfcard.rst:25 ../../en/base/tfcard.rst:42 +#: 528bddcb47ec4b349968a512418f5a0c 587d76e1f74d4fb7a4c5ee2f06c061cc +msgid "" +"This example demonstrates how to read/create a directory using Atomic " +"TFCard Base." +msgstr "本示例演示如何使用 Atomic TFCard Base 读取/创建目录。" + +#: ../../en/base/tfcard.rst:27 ../../en/base/tfcard.rst:80 +#: 8cf893b763a44469990a875ab73c43a4 ea6b80daa04d4ddeb5e2a2084627262c +msgid "UiFlow2 Code Block:" +msgstr "UiFlow2 代码块:" + +#: ../../en/base/tfcard.rst:29 e8d262155bbf4cfb9e9474117d2581ad +msgid "|example.png|" +msgstr "" + +#: ../../en/refs/base.tfcard.ref:13 ccb3e3eaffc1457ba421d3a8ea46cded +msgid "example.png" +msgstr "" + +#: ../../en/base/tfcard.rst:31 ../../en/base/tfcard.rst:50 +#: 17d34608e4a1415093ac666622731de4 e2efdd7dc8ee48f7a7bd53ef44c77b09 +msgid "Example output:" +msgstr "示例输出:" + +#: ../../en/base/tfcard.rst:33 ../../en/base/tfcard.rst:52 +#: 1fe50c13ec1d49a5bae98e060894cd84 a8fd600917a74531bd099fcb115d2b30 +msgid "Files in the /sd directory." +msgstr "/sd 目录下的文件。" + +#: ../../en/base/tfcard.rst:37 a89b210eee09404587e5704576c69488 +msgid "MicroPython Example" +msgstr "MicroPython 应用示例:" + +#: ../../en/base/tfcard.rst:44 ../../en/base/tfcard.rst:84 +#: 53214d60887f4e51859466470d9c7b5d 9320435d91b54526a7d527c80b94fb2f +msgid "MicroPython Code Block:" +msgstr "MicroPython 代码块:" + +#: ../../en/base/tfcard.rst:56 748bddc7df6c444ea0f4370328481104 +msgid "**API**" +msgstr "API参考" + +#: ../../en/base/tfcard.rst:59 c6b1a4d3e78a4f4c9f9b182a8976423e +msgid "function AtomicTFCardBase" +msgstr "" + +#: ../../en/base/tfcard.rst:63 1c23c31b6e52411493f66f44d0a0ce93 +msgid "" +"This function is only used to initialize and mount the SD card to the /sd" +" directory, and to try to unmount the existing SD card before mounting " +"it. Other file operations (such as reading/writing files, creating " +"directories, etc.) need to be performed by the os module." +msgstr "" +"该函数仅用于初始化并挂载 SD 卡到 /sd 目录下,同时在挂载前尝试卸载已有的 SD 卡。其他文件操作(如读写文件、创建目录等)需要通过 os " +"模块来实现。" + +#: ../../en/base/tfcard.rst 07af9d0299c94d5e800e2d173dde5651 +msgid "Parameters" +msgstr "" + +#: ../../en/base/tfcard.rst:68 e57365d0e7c648dd9ebf5e4114c09329 +msgid "Which of the available interfaces to use. The default value is 1." +msgstr "使用哪个可用接口。默认值为 1。" + +#: ../../en/base/tfcard.rst:69 7f8671f0edb14e3d86ac162ddd21e978 +msgid "The bus width for the SD/MMC interface. The default value is 1." +msgstr "SD/MMC 接口的总线宽度。默认值为 1。" + +#: ../../en/base/tfcard.rst:70 ad7df43ca75b431aa8c8c950d2f2f44c +msgid "The card-detect pin to use. The default value is None." +msgstr "要使用的卡检测引脚。默认值为 None。" + +#: ../../en/base/tfcard.rst:71 7cf9949d365849e794393c4c6af121e3 +msgid "The write-protect pin to use. The default value is None." +msgstr "要使用的写保护引脚。默认值为 None。" + +#: ../../en/base/tfcard.rst:72 783b69aa004c432aa2ebf85878e00a26 +msgid "The SPI clock pin to use. The default value is None." +msgstr "要使用的 SPI 时钟引脚。默认值为 None。" + +#: ../../en/base/tfcard.rst:73 ca16fa63befd41fb9f0fbe313c0e4314 +msgid "The SPI miso pin to use. The default value is None." +msgstr "要使用的 SPI miso 引脚。默认值为 None。" + +#: ../../en/base/tfcard.rst:74 99ab603b78024aceba13430a0a17b817 +msgid "The SPI mosi pin to use. The default value is None." +msgstr "要使用的 SPI mosi 引脚。默认值为 None。" + +#: ../../en/base/tfcard.rst:75 8f3d00b4996e4edcba93d3b706c0009d +msgid "The SPI chip select pin to use. The default value is None." +msgstr "要使用的 SPI 芯片选择引脚。默认值为 None。" + +#: ../../en/base/tfcard.rst:76 36ed3823e19f421486ef2d4d750b360a +msgid "The SD/MMC interface frequency in Hz. The default value is 20000000." +msgstr "SD/MMC 接口频率(单位:Hz)。默认值为 20000000。" + +#: ../../en/base/tfcard.rst 13476fb53d0245c7bdf5f22b6a05c4ac +msgid "Returns" +msgstr "" + +#: ../../en/base/tfcard.rst:78 aa69052037b74e4cbda196f027dc101b +msgid "None" +msgstr "" + +#: ../../en/base/tfcard.rst:82 7620886067e841e1bdbf20db45f8ae6c +msgid "|init.png|" +msgstr "" + +#: ../../en/refs/base.tfcard.ref:11 82ff98f445ae4215836eae9704e458f6 +msgid "init.png" +msgstr "" + +#: ../../en/base/tfcard.rst:91 d8bcb83b58cb489c9b98d3e082ecbca6 +msgid "See :mod:`micropython:os` -- basic \"operating system\" for more details." +msgstr "详情请参见 :mod:`micropython:os` -- 基本操作系统服务。" + diff --git a/examples/base/tfcard/atoms3r_tfcard_example.m5f2 b/examples/base/tfcard/atoms3r_tfcard_example.m5f2 new file mode 100644 index 00000000..f89b82ca --- /dev/null +++ b/examples/base/tfcard/atoms3r_tfcard_example.m5f2 @@ -0,0 +1 @@ +{"version":"V2.0","versionNumber":"V2.2.3","type":"atoms3r","components":[{"name":"screen","type":"screen","layer":0,"screenId":"builtin","screenName":"","id":"__atoms3r_screen","createTime":1741598861906,"x":0,"y":0,"width":128,"height":128,"backgroundColor":"#000000","size":0,"isSelected":true},{"name":"title0","type":"title","layer":1,"screenId":"builtin","screenName":"","id":"ktgCT97W-O6__mQn","createTime":1741600458186,"x":0,"y":0,"color":"#ffffff","backgroundColor":"#0000FF","text":"TFCard e.g.","textOffset":3,"font":"Widgets.FONTS.DejaVu18","isSelected":false}],"resources":[{"hardware":["hardware_button","hardware_pin_button","imu","ir"]},{"base":["base_tfcard"]}],"units":[],"hats":[],"bases":[{"type":"base_tfcard","name":"base_tfcard","id":"tSsn=5dwBE!Jvi&q","createTime":1742434313847,"initBlockType":"base_tfcard_init"}],"i2cs":[],"blockly":"true37861000000/sdhello M5Current dir:hello M5list /sd/dir: sdcard_testTry create 'sdcard_test' directory in /sd/sdcard_testhello M5'sdcard_test' is directory?:sdcard_testhello M5'sdcard_test' is file?:sdcard_testDelay 1s to delete 'sdcard_test' directory1sdcard_testsdcard_testDirectory 'sdcard_test' deleted successfullytrue","screen":[{"simulationName":"Built-in","type":"builtin","width":128,"height":128,"scale":1.3,"screenName":"","blockId":"","screenColorType":0,"id":"builtin","createTime":1741598861906}],"logicWhenNum":0,"customList":[]} \ No newline at end of file diff --git a/examples/base/tfcard/atoms3r_tfcard_example.py b/examples/base/tfcard/atoms3r_tfcard_example.py new file mode 100644 index 00000000..db9a33fb --- /dev/null +++ b/examples/base/tfcard/atoms3r_tfcard_example.py @@ -0,0 +1,54 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import os, sys, io +import M5 +from M5 import * +from base import AtomicTFCardBase +import time + + +title0 = None +base_tfcard = None + + +def setup(): + global title0, base_tfcard + + M5.begin() + title0 = Widgets.Title("TFCard e.g.", 3, 0xFFFFFF, 0x0000FF, Widgets.FONTS.DejaVu18) + + base_tfcard = AtomicTFCardBase(slot=3, width=1, sck=7, miso=8, mosi=6, freq=1000000) + os.chdir("/sd") + print((str("Current dir:") + str((os.getcwd())))) + print((str("list /sd/dir: ") + str((os.listdir("/sd/"))))) + if not ("sdcard_test" in os.listdir("/sd/")): # noqa: E713 + print("Try create 'sdcard_test' directory in /sd/") + os.mkdir("/sd/sdcard_test") + print((str("'sdcard_test' is directory?:") + str((os.stat("/sd/sdcard_test")[0] == 0x4000)))) + print((str("'sdcard_test' is file?:") + str((os.stat("/sd/sdcard_test")[0] == 0x8000)))) + print("Delay 1s to delete 'sdcard_test' directory") + time.sleep(1) + os.rmdir("/sd/sdcard_test") + if not ("sdcard_test" in os.listdir("/sd/")): # noqa: E713 + print("Directory 'sdcard_test' deleted successfully") + + +def loop(): + global title0, base_tfcard + M5.update() + + +if __name__ == "__main__": + try: + setup() + while True: + loop() + except (Exception, KeyboardInterrupt) as e: + try: + from utility import print_error_msg + + print_error_msg(e) + except ImportError: + print("please update to latest firmware") diff --git a/m5stack/libs/base/__init__.py b/m5stack/libs/base/__init__.py index fd2b9421..08d13e93 100644 --- a/m5stack/libs/base/__init__.py +++ b/m5stack/libs/base/__init__.py @@ -17,6 +17,7 @@ "AtomRS485": "rs232", "SpeakerBase": "speaker", "AtomicStepmotorBase": "stepmotor", + "AtomicTFCardBase": "tfcard", } diff --git a/m5stack/libs/base/manifest.py b/m5stack/libs/base/manifest.py index 35335eb7..a5813a7e 100644 --- a/m5stack/libs/base/manifest.py +++ b/m5stack/libs/base/manifest.py @@ -17,6 +17,7 @@ "rs232.py", "speaker.py", "stepmotor.py", + "tfcard.py", ), base_path="..", opt=3, diff --git a/m5stack/libs/base/tfcard.py b/m5stack/libs/base/tfcard.py new file mode 100644 index 00000000..245e2b21 --- /dev/null +++ b/m5stack/libs/base/tfcard.py @@ -0,0 +1,5 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +from hardware import SDCard as AtomicTFCardBase diff --git a/m5stack/libs/hardware/__init__.py b/m5stack/libs/hardware/__init__.py index d84e2faa..a8d56978 100644 --- a/m5stack/libs/hardware/__init__.py +++ b/m5stack/libs/hardware/__init__.py @@ -13,6 +13,7 @@ "RGB": "rgb", "Rotary": "rotary", "SCD40": "scd40", + "SDCard": "sdcard", "SEN55": "sen55", } diff --git a/m5stack/libs/hardware/sdcard.py b/m5stack/libs/hardware/sdcard.py index af89ddfc..91e965d8 100644 --- a/m5stack/libs/hardware/sdcard.py +++ b/m5stack/libs/hardware/sdcard.py @@ -2,7 +2,8 @@ # # SPDX-License-Identifier: MIT -import machine, os +import machine +import os def create_sdcard_closure(): From b7d371a4e5146b52509ed2c1ffa68b5447ff397e Mon Sep 17 00:00:00 2001 From: Tinyu-Zhao Date: Mon, 17 Mar 2025 15:21:08 +0800 Subject: [PATCH 022/322] lib/module: Encoder4Moulde add softboot, fixe motor ch4 not work. Signed-off-by: Tinyu-Zhao --- docs/en/module/encoder4_motor.rst | 63 ++ docs/en/module/index.rst | 1 + docs/en/refs/module.encoder4_motor.ref | 50 + .../LC_MESSAGES/module/encoder4_motor.po | 888 ++++++++++++++++++ .../encoder4motor_core2_example.m5f2 | 1 + .../encoder4motor_core2_example.py | 68 ++ m5stack/libs/module/encoder4_motor.py | 580 +++++++++--- 7 files changed, 1518 insertions(+), 133 deletions(-) create mode 100644 docs/en/module/encoder4_motor.rst create mode 100644 docs/en/refs/module.encoder4_motor.ref create mode 100644 docs/locales/zh_CN/LC_MESSAGES/module/encoder4_motor.po create mode 100644 examples/module/encoder4_motor/encoder4motor_core2_example.m5f2 create mode 100644 examples/module/encoder4_motor/encoder4motor_core2_example.py diff --git a/docs/en/module/encoder4_motor.rst b/docs/en/module/encoder4_motor.rst new file mode 100644 index 00000000..b74bb13a --- /dev/null +++ b/docs/en/module/encoder4_motor.rst @@ -0,0 +1,63 @@ +4EncoderMotor Module +====================== + +.. sku: M138/M138-V11 + +.. include:: ../refs/module.encoder4_motor.ref + +4EncoderMotor Module is a 4-channel encoder motor driver module that utilizes the STM32+BL5617 solution. It is suitable for various applications such as robot motion control, automation equipment, smart vehicles, laboratory equipment, and industrial automation systems. + +This is the driver library for the 4EncoderMotor Module, use to control motor and read encoder value. + +Support the following products: + + ================== =================== + |4EncoderMotor| |4EncoderMotor-V11| + ================== =================== + + +UiFlow2 Example +--------------- + +Motor control +^^^^^^^^^^^^^ + +Open the |encoder4motor_core2_example.m5f2| project in UiFlow2. + +This example shows how to control the motor and read the encoder value. + +UiFlow2 Code Block: + + |example.png| + +Example output: + + None + +MicroPython Example +------------------- + +Motor control +^^^^^^^^^^^^^^ + +This example shows how to control the motor and read the encoder value. + +MicroPython Code Block: + + .. literalinclude:: ../../../examples/module/encoder4_motor/encoder4motor_core2_example.py + :language: python + :linenos: + +Example output: + + None + +**API** +------- + +Encoder4MotorModule +^^^^^^^^^^^^^^^^^^^^ + +.. autoclass:: module.encoder4_motor.Encoder4MotorModule + :members: + :member-order: bysource \ No newline at end of file diff --git a/docs/en/module/index.rst b/docs/en/module/index.rst index 1a5f48c1..84572de7 100644 --- a/docs/en/module/index.rst +++ b/docs/en/module/index.rst @@ -10,6 +10,7 @@ Module display.rst dmx.rst dualkmeter.rst + encoder4_motor.rst fan.rst gnss.rst gps.rst diff --git a/docs/en/refs/module.encoder4_motor.ref b/docs/en/refs/module.encoder4_motor.ref new file mode 100644 index 00000000..cd23ec00 --- /dev/null +++ b/docs/en/refs/module.encoder4_motor.ref @@ -0,0 +1,50 @@ +.. |4EncoderMotor| image:: https://static-cdn.m5stack.com/resource/docs/products/module/Module-4EncoderMotor/img-b0ee8659-161f-4ffd-ac50-3702aa06a60b.webp + :target: https://docs.m5stack.com/en/module/Module-4EncoderMotor + :height: 200px + :width: 200px + +.. |4EncoderMotor-V11| image:: https://m5stack.oss-cn-shenzhen.aliyuncs.com/resource/docs/products/module/Module_4EncoderMotor_V1.1/4.webp + :target: https://docs.m5stack.com/en/module/Module_4EncoderMotor_V1.1 + :height: 200px + :width: 200px + + +.. |init.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/encoder4motor/init.png +.. |set_motor_mode.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/encoder4motor/set_motor_mode.png +.. |set_all_motors_mode.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/encoder4motor/set_all_motors_mode.png +.. |set_motor_pwm_dutycycle.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/encoder4motor/set_motor_pwm_dutycycle.png +.. |get_motor_encoder_value.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/encoder4motor/get_motor_encoder_value.png +.. |set_motor_encoder_value.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/encoder4motor/set_motor_encoder_value.png +.. |get_encoder_mode.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/encoder4motor/get_encoder_mode.png +.. |set_encoder_mode.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/encoder4motor/set_encoder_mode.png +.. |get_motor_speed_value.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/encoder4motor/get_motor_speed_value.png +.. |set_position_encoder_value.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/encoder4motor/set_position_encoder_value.png +.. |set_position_max_speed_value.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/encoder4motor/set_position_max_speed_value.png +.. |get_position_pid_value.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/encoder4motor/get_position_pid_value.png +.. |get_position_PID_value.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/encoder4motor/get_position_PID_value.png +.. |set_position_pid_value.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/encoder4motor/set_position_pid_value.png +.. |set_position_PID_value.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/encoder4motor/set_position_PID_value.png +.. |get_speed_PID_value.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/encoder4motor/get_speed_PID_value.png +.. |set_speed_PID_value.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/encoder4motor/set_speed_PID_value.png +.. |set_speed_point_value.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/encoder4motor/set_speed_point_value.png +.. |get_vin_current_float_value.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/encoder4motor/get_vin_current_float_value.png +.. |get_vin_current_int_value.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/encoder4motor/get_vin_current_int_value.png +.. |get_vin_adc_raw8_value.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/encoder4motor/get_vin_adc_raw8_value.png +.. |get_vin_adc_raw12_value.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/encoder4motor/get_vin_adc_raw12_value.png +.. |get_vin_voltage.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/encoder4motor/get_vin_voltage.png +.. |get_device_spec.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/encoder4motor/get_device_spec.png +.. |get_soft_start_state.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/encoder4motor/get_soft_start_state.png +.. |set_soft_start_state.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/encoder4motor/set_soft_start_state.png +.. |set_i2c_address.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/encoder4motor/set_i2c_address.png + +.. |example.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/encoder4motor/example.png + + +.. |encoder4motor_core2_example.m5f2| raw:: html + + + encoder4motor_core2_example.m5f2 + \ No newline at end of file diff --git a/docs/locales/zh_CN/LC_MESSAGES/module/encoder4_motor.po b/docs/locales/zh_CN/LC_MESSAGES/module/encoder4_motor.po new file mode 100644 index 00000000..38b75859 --- /dev/null +++ b/docs/locales/zh_CN/LC_MESSAGES/module/encoder4_motor.po @@ -0,0 +1,888 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2016 - 2025 M5Stack Technology Co., Ltd +# This file is distributed under the same license as the UIFlow2 Programming +# Guide package. +# FIRST AUTHOR , 2025. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: UIFlow2 Programming Guide \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-03-20 10:43+0800\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: zh_CN\n" +"Language-Team: zh_CN \n" +"Plural-Forms: nplurals=1; plural=0;\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.16.0\n" + +#: ../../en/module/encoder4_motor.rst:2 bd4315956432455f8c68419ed0fe67e1 +msgid "4EncoderMotor Module" +msgstr "" + +#: ../../en/module/encoder4_motor.rst:8 a6adabd726c54814b1a1476fb534581c +msgid "" +"4EncoderMotor Module is a 4-channel encoder motor driver module that " +"utilizes the STM32+BL5617 solution. It is suitable for various " +"applications such as robot motion control, automation equipment, smart " +"vehicles, laboratory equipment, and industrial automation systems." +msgstr "" +"4EncoderMotor 模块是一款采用 STM32+BL5617 解决方案的 4 " +"通道编码器电机驱动器模块。它适用于机器人运动控制、自动化设备、智能车辆、实验室设备和工业自动化系统等各种应用。" + +#: ../../en/module/encoder4_motor.rst:10 27e581165f1d4de2aa8329ab05e86cc6 +msgid "" +"This is the driver library for the 4EncoderMotor Module, use to control " +"motor and read encoder value." +msgstr "这是 4EncoderMotor 模块的驱动库,用于控制电机并读取编码器值。" + +#: ../../en/module/encoder4_motor.rst:12 8ec606acc663487ba117698c4c05eedc +msgid "Support the following products:" +msgstr "支持以下产品:" + +#: ../../en/module/encoder4_motor.rst:15 2f65c234b72745668d31426f365f4525 +msgid "|4EncoderMotor|" +msgstr "" + +#: ../../en/refs/module.encoder4_motor.ref 4cd976e5fdc54648b4ca1af2c36ad926 +msgid "4EncoderMotor" +msgstr "" + +#: ../../en/module/encoder4_motor.rst:15 e689aebb775b4a9cbfe1da42c1d6f349 +msgid "|4EncoderMotor-V11|" +msgstr "" + +#: ../../en/refs/module.encoder4_motor.ref 41c5e6116bcb41d99ff2a789ba993022 +msgid "4EncoderMotor-V11" +msgstr "" + +#: ../../en/module/encoder4_motor.rst:20 ef6668b8a2244c5c8d312fec50b0df39 +msgid "UiFlow2 Example" +msgstr "UiFlow2 应用示例" + +#: ../../en/module/encoder4_motor.rst:23 ../../en/module/encoder4_motor.rst:41 +#: 1e353129db1948568e25f051b083740a 9856afbadb494bc996e6a2011f281919 +msgid "Motor control" +msgstr "电机控制" + +#: ../../en/module/encoder4_motor.rst:25 d9aedf7b94c749a4800bd70c44b5a60e +#, fuzzy +msgid "Open the |encoder4motor_core2_example.m5f2| project in UiFlow2." +msgstr "在 UiFlow2 中打开 |motor_control_example.m5f2| 项目。" + +#: ../../en/module/encoder4_motor.rst:27 ../../en/module/encoder4_motor.rst:43 +#: 60f2be2716644d958735c592e2a617ed c7c724f078cf44e9a9b915f75996b428 +msgid "This example shows how to control the motor and read the encoder value." +msgstr "这个示例展示了如何控制电机并读取编码器值。" + +#: ../../en/module/encoder4_motor.rst:29 05da97441c2441bcb372a8e23d430c0e +#: 068ed62d80e3493980ba100539e3ad45 08ed466ff7004f9490b0645b115fe1df +#: 0cfb3ca5fcae4cb8b45cff2c8fb3c49f 2f27c223f3f546e19a1756e3e74f1467 +#: 306d4ddf56f84d038d073d9b703a8354 323ffe5c74ab45df8ae8f9f977a57aa0 +#: 5691227ba1194153ae092c66685996a3 628dd2b174a9474d9d5b2876ea958484 +#: 648c6a14e3de48389e347e4a01b4f1b3 6db81d14a4ab466e8374feaa0bd7275f +#: 6f3f60a2a4e44815b128e8d9314ae0fa 73d6713be77345b4a58b176df9bf3c21 +#: ab7ba6250b01464cb4fbceff5f2be7de ace2efab7cfd4cf693ae8f5109d3c63f +#: ae70bef066074185b6e9ecae5c60ff2e b11dd426f7024e15a8d9e20a763c9768 +#: becd3696276e4c799e384f8ea3944a42 c02004426dd846bcbe821faa029ea68a +#: d3ca0d492b554227aa7fef6752915e5a d54679c54e514f10b6e6a742c43b6715 +#: daa4c4cda92149f4b1a3f7f746314367 f19529e0dd81466aa26fbbeaace4011f +#: f32b152af7e34cd0bbf5a8c080413091 f4f0e2a130b240c5b1e41399cdf930c5 +#: fdb72600268541d58e791f85e2bdb298 module.encoder4_motor.Encoder4MotorModule:5 +#: module.encoder4_motor.Encoder4MotorModule.get_device_spec:8 +#: module.encoder4_motor.Encoder4MotorModule.get_encoder_mode:6 +#: module.encoder4_motor.Encoder4MotorModule.get_motor_encoder_value:8 +#: module.encoder4_motor.Encoder4MotorModule.get_motor_speed_value:8 +#: module.encoder4_motor.Encoder4MotorModule.get_position_pid_value:8 +#: module.encoder4_motor.Encoder4MotorModule.get_soft_start_state:8 +#: module.encoder4_motor.Encoder4MotorModule.get_speed_pid_value:8 +#: module.encoder4_motor.Encoder4MotorModule.get_vin_adc_raw12_value:6 +#: module.encoder4_motor.Encoder4MotorModule.get_vin_adc_raw8_value:6 +#: module.encoder4_motor.Encoder4MotorModule.get_vin_current_float_value:6 +#: module.encoder4_motor.Encoder4MotorModule.get_vin_current_int_value:6 +#: module.encoder4_motor.Encoder4MotorModule.get_vin_voltage:6 +#: module.encoder4_motor.Encoder4MotorModule.set_all_motors_mode:10 +#: module.encoder4_motor.Encoder4MotorModule.set_encoder_mode:9 +#: module.encoder4_motor.Encoder4MotorModule.set_i2c_address:5 +#: module.encoder4_motor.Encoder4MotorModule.set_motor_encoder_value:6 +#: module.encoder4_motor.Encoder4MotorModule.set_motor_mode:11 +#: module.encoder4_motor.Encoder4MotorModule.set_motor_pwm_dutycycle:6 +#: module.encoder4_motor.Encoder4MotorModule.set_position_encoder_value:6 +#: module.encoder4_motor.Encoder4MotorModule.set_position_max_speed_value:6 +#: module.encoder4_motor.Encoder4MotorModule.set_position_pid_value:8 +#: module.encoder4_motor.Encoder4MotorModule.set_soft_start_state:10 +#: module.encoder4_motor.Encoder4MotorModule.set_speed_pid_value:8 +#: module.encoder4_motor.Encoder4MotorModule.set_speed_point_value:6 of +msgid "UiFlow2 Code Block:" +msgstr "UiFlow2 代码块:" + +#: ../../en/module/encoder4_motor.rst:31 dd03c36401fe4c87b074631f947a3f49 +msgid "|example.png|" +msgstr "" + +#: ../../en/refs/module.encoder4_motor.ref:40 07659a113de54fb6a42ee0296c6de221 +msgid "example.png" +msgstr "" + +#: ../../en/module/encoder4_motor.rst:33 ../../en/module/encoder4_motor.rst:51 +#: 380ad5c94d0e4205a16017be9fb0c0ea 5e417b028e0f41c68fe0fdc37c09846e +msgid "Example output:" +msgstr "示例输出:" + +#: ../../en/module/encoder4_motor.rst:35 ../../en/module/encoder4_motor.rst:53 +#: 024000bb08a14a00a9fb0d9534cdd043 667a0c23aa084d7ba342bb6baf0517f1 +msgid "None" +msgstr "" + +#: ../../en/module/encoder4_motor.rst:38 7e2a3217e8894514928fb25b76bc1d4d +msgid "MicroPython Example" +msgstr "MicroPython 应用示例" + +#: ../../en/module/encoder4_motor.rst:45 01ad54e54d4d4e9f9f72ad10ada742fb +#: 265f745232ab4cdebe9afd391f7bedf8 4ba149d229844cdc9e4a105c8df557fe +#: 5f5acb0cd91c4653b210dc89db1bd015 66431ec9e7b34f28a4e744866f867f89 +#: 683cbba2a4d140ad88981303ce3cd340 6be4cebda9d14286b92207beef3e76a4 +#: 71618a48a121445eb98ff27cb83dec92 782c4a4035644af5bdb60443bf26584d +#: 7eab511cbc5b486f824701d0266ca2bc 8af7853b0f394281935352d399637819 +#: 8e2580ace3bc4ef6a01c964ee0c9ab3e 91e99f3212864c8a96a3be86d59a2f6c +#: a1e2c1144162469e91522abe95fe3a27 a70189088a5b436dbec3383ea7ff362a +#: b7274ed9ffec4b3bb892d6bdbfd5fbf9 b8b5e10391394790a8e0af3c4d3f7ba8 +#: b9159810254244e687c5123b2c3bfc2c bb2b5badfdff4343bd98930b27e080bf +#: be1e4305652b4e91b14e5e943615d29f c5406b4c4cbd4527bea615e4b4778d49 +#: d1e9b229c9674ad5a8510b3923663564 d5229a7af33249da9ada2930dd432792 +#: e180ac2aa779497d9666b509a860cd54 ecf85a066537435bbe03a9196af05d23 +#: ffce00ccfa8e43ad964fb6dd24e373c5 module.encoder4_motor.Encoder4MotorModule:9 +#: module.encoder4_motor.Encoder4MotorModule.get_device_spec:12 +#: module.encoder4_motor.Encoder4MotorModule.get_encoder_mode:10 +#: module.encoder4_motor.Encoder4MotorModule.get_motor_encoder_value:12 +#: module.encoder4_motor.Encoder4MotorModule.get_motor_speed_value:12 +#: module.encoder4_motor.Encoder4MotorModule.get_position_pid_value:12 +#: module.encoder4_motor.Encoder4MotorModule.get_soft_start_state:12 +#: module.encoder4_motor.Encoder4MotorModule.get_speed_pid_value:12 +#: module.encoder4_motor.Encoder4MotorModule.get_vin_adc_raw12_value:10 +#: module.encoder4_motor.Encoder4MotorModule.get_vin_adc_raw8_value:10 +#: module.encoder4_motor.Encoder4MotorModule.get_vin_current_float_value:10 +#: module.encoder4_motor.Encoder4MotorModule.get_vin_current_int_value:10 +#: module.encoder4_motor.Encoder4MotorModule.get_vin_voltage:10 +#: module.encoder4_motor.Encoder4MotorModule.set_all_motors_mode:14 +#: module.encoder4_motor.Encoder4MotorModule.set_encoder_mode:13 +#: module.encoder4_motor.Encoder4MotorModule.set_i2c_address:9 +#: module.encoder4_motor.Encoder4MotorModule.set_motor_encoder_value:10 +#: module.encoder4_motor.Encoder4MotorModule.set_motor_mode:15 +#: module.encoder4_motor.Encoder4MotorModule.set_motor_pwm_dutycycle:10 +#: module.encoder4_motor.Encoder4MotorModule.set_position_encoder_value:10 +#: module.encoder4_motor.Encoder4MotorModule.set_position_max_speed_value:10 +#: module.encoder4_motor.Encoder4MotorModule.set_position_pid_value:12 +#: module.encoder4_motor.Encoder4MotorModule.set_soft_start_state:14 +#: module.encoder4_motor.Encoder4MotorModule.set_speed_pid_value:12 +#: module.encoder4_motor.Encoder4MotorModule.set_speed_point_value:10 of +msgid "MicroPython Code Block:" +msgstr "MicroPython 代码块:" + +#: ../../en/module/encoder4_motor.rst:56 f2a46e21ccb5464c93e3e58cfd7c7315 +msgid "**API**" +msgstr "" + +#: ../../en/module/encoder4_motor.rst:59 8499e98abdf1490d9fb986a4ef997691 +msgid "Encoder4MotorModule" +msgstr "" + +#: f02efad4c4ec4c27a06d802f6efd2597 module.encoder4_motor.Encoder4MotorModule:1 +#: of +msgid "Bases: :py:class:`object`" +msgstr "" + +#: b9f79515aa50481a84a3877541e6024d module.encoder4_motor.Encoder4MotorModule:1 +#: of +msgid "Create an Encoder4MotorModule object" +msgstr "创建一个 Encoder4MotorModule 对象" + +#: ../../en/module/encoder4_motor.rst 1c6d7b5feba0483b95fff2297d5b70a2 +#: 1eb05a7c3ae94c1cbb9ee2e1bce03936 2af11c1c3de34f509796dc8a43a5c870 +#: 2e76eb5bf06f44ceaad27170bef19263 2fe6f60c1dd0434dad20792c20f76741 +#: 3fdf7f0ce1974df1a0a60eecbb9cca3d 50811a995ff2420d81e58365af010147 +#: 517b0d704f7c4dd586f525ef5813a1ba 544d457860c242d284df33ed91048de4 +#: 6c6b4f0b15b94f09a00fedb1e36fb08c 719ebe8621f248df9b555f80e683557c +#: 8b534811c1f041419d66e086071978dd 955b58313f0d42ae8a8bd87c6633042b +#: a04308841a1c494baaa5b68b2d43368c a9ea621beb634e939f96079731bb5a6c +#: bcfade9de1234b8fb008e6be9586a5df e09668a25a854c8ea3f6019f553f6bdd +#: e90c36c38fcb47b7a47ba9593db8cbfb ed53ea59fbd94942999cdee45e561a55 +#: module.encoder4_motor.Encoder4MotorModule.get_device_spec +#: module.encoder4_motor.Encoder4MotorModule.get_motor_encoder_value +#: module.encoder4_motor.Encoder4MotorModule.get_motor_speed_value +#: module.encoder4_motor.Encoder4MotorModule.get_position_pid_value +#: module.encoder4_motor.Encoder4MotorModule.get_soft_start_state +#: module.encoder4_motor.Encoder4MotorModule.get_speed_pid_value +#: module.encoder4_motor.Encoder4MotorModule.set_all_motors_mode +#: module.encoder4_motor.Encoder4MotorModule.set_encoder_mode +#: module.encoder4_motor.Encoder4MotorModule.set_i2c_address +#: module.encoder4_motor.Encoder4MotorModule.set_motor_encoder_value +#: module.encoder4_motor.Encoder4MotorModule.set_motor_mode +#: module.encoder4_motor.Encoder4MotorModule.set_motor_pwm_dutycycle +#: module.encoder4_motor.Encoder4MotorModule.set_position_encoder_value +#: module.encoder4_motor.Encoder4MotorModule.set_position_max_speed_value +#: module.encoder4_motor.Encoder4MotorModule.set_position_pid_value +#: module.encoder4_motor.Encoder4MotorModule.set_soft_start_state +#: module.encoder4_motor.Encoder4MotorModule.set_speed_pid_value +#: module.encoder4_motor.Encoder4MotorModule.set_speed_point_value of +msgid "Parameters" +msgstr "" + +#: 56f9dce568a941d78c2c457e37e34d72 module.encoder4_motor.Encoder4MotorModule:3 +#: of +msgid "The I2C address of the device. Default is 0x24." +msgstr "设备的 I2C 地址。默认是 0x24。" + +#: 25fa8b1197884c789b6d88b4c985d560 module.encoder4_motor.Encoder4MotorModule:7 +#: of +msgid "|init.png|" +msgstr "" + +#: ../../en/refs/module.encoder4_motor.ref:12 ed606987ec884eafbf5ce2e85effd678 +msgid "init.png" +msgstr "" + +#: 0401a6847c984f9280c8b3f780858d17 +#: module.encoder4_motor.Encoder4MotorModule.set_motor_mode:1 of +msgid "Set the motor mode." +msgstr "设置电机的工作模式。" + +#: f869c52737ff44f9ab8b6ad87b510f87 +#: module.encoder4_motor.Encoder4MotorModule.set_motor_mode:3 of +msgid "The motor to set the mode." +msgstr "要设置工作模式的电机。" + +#: c42a0350222747179f02b2498b4b0d4f +#: module.encoder4_motor.Encoder4MotorModule.set_motor_mode:4 of +msgid "" +"The mode of the motor. Options: - ``NORMAL_MODE``: 0 - " +"``POSITION_MODE``: 1 - ``SPEED_MODE``: 2" +msgstr "" +"电机的工作模式。选项: - ``NORMAL_MODE``: 0 - ``POSITION_MODE``: 1 - " +"``SPEED_MODE``: 2" + +#: 72d0729a1ff44ba184f476cef80fa659 +#: module.encoder4_motor.Encoder4MotorModule.set_motor_mode:4 of +msgid "The mode of the motor." +msgstr "电机模式。" + +#: 5a48b07eb29542b8aacc2ecbf1581b33 9258033020e04b268600cbfb15df4f8c +#: ac4fd85a237a4d0d9e881c36965d8703 b346bd1507f6426398744ab70af72b28 +#: module.encoder4_motor.Encoder4MotorModule.set_all_motors_mode:8 +#: module.encoder4_motor.Encoder4MotorModule.set_encoder_mode:7 +#: module.encoder4_motor.Encoder4MotorModule.set_motor_mode:9 +#: module.encoder4_motor.Encoder4MotorModule.set_soft_start_state:8 of +msgid "Options:" +msgstr "" + +#: 38907abed7c94c41b4cd949d88241beb 746b96ff2238481b84a760d85bc2ff6d +#: module.encoder4_motor.Encoder4MotorModule.set_all_motors_mode:6 +#: module.encoder4_motor.Encoder4MotorModule.set_motor_mode:7 of +msgid "``NORMAL_MODE``: 0" +msgstr "" + +#: 25765c43b0ea48fa8afdf65a19631258 f82f8b0509ad48109608bc0e5d5d9c68 +#: module.encoder4_motor.Encoder4MotorModule.set_all_motors_mode:7 +#: module.encoder4_motor.Encoder4MotorModule.set_motor_mode:8 of +msgid "``POSITION_MODE``: 1" +msgstr "" + +#: 67e1fcadafa9456fb55fbe513e36d789 a6c3b75d813f4d4b8534a6e06bb7c4b7 +#: module.encoder4_motor.Encoder4MotorModule.set_all_motors_mode:8 +#: module.encoder4_motor.Encoder4MotorModule.set_motor_mode:9 of +msgid "``SPEED_MODE``: 2" +msgstr "" + +#: c29209888afd4829890ab5266ce458ca +#: module.encoder4_motor.Encoder4MotorModule.set_motor_mode:13 of +msgid "|set_motor_mode.png|" +msgstr "" + +#: ../../en/refs/module.encoder4_motor.ref:13 0471a1b495f644eeae621e26680ffe7d +msgid "set_motor_mode.png" +msgstr "" + +#: ff2049b3bd394b3f9e290ceb7b9999db +#: module.encoder4_motor.Encoder4MotorModule.set_all_motors_mode:1 of +msgid "Set the mode of all motors." +msgstr "设置所有电机的模式。" + +#: 38aeaa4b52df4ad88d5403f17635d70e +#: module.encoder4_motor.Encoder4MotorModule.set_all_motors_mode:3 of +msgid "" +"The mode of the motors. Options: - ``NORMAL_MODE``: 0 - " +"``POSITION_MODE``: 1 - ``SPEED_MODE``: 2" +msgstr "" +"电机的工作模式。选项: - ``NORMAL_MODE``: 0 - ``POSITION_MODE``: 1 - " +"``SPEED_MODE``: 2" + +#: c83c179b92984fe0ad39590c068f4028 +#: module.encoder4_motor.Encoder4MotorModule.set_all_motors_mode:3 of +msgid "The mode of the motors." +msgstr "所有电机的模式。" + +#: 5e0627465bf24eaa87b1b0f8b03b87f1 +#: module.encoder4_motor.Encoder4MotorModule.set_all_motors_mode:12 of +msgid "|set_all_motors_mode.png|" +msgstr "" + +#: ../../en/refs/module.encoder4_motor.ref:14 57fb2f624c12446787006548147b6456 +msgid "set_all_motors_mode.png" +msgstr "" + +#: 358076530bb6412caceb433243266e3d +#: module.encoder4_motor.Encoder4MotorModule.set_motor_pwm_dutycycle:1 of +msgid "Set the PWM duty cycle of a motor." +msgstr "设置电机的 PWM 占空比。" + +#: 006b725d42de49578b2236d29c17c464 +#: module.encoder4_motor.Encoder4MotorModule.set_motor_pwm_dutycycle:3 of +msgid "The motor to set the PWM duty cycle." +msgstr "要设置 PWM 占空比的电机。" + +#: 4eacfe9dc5c94c6592f7aef7d3413bf1 +#: module.encoder4_motor.Encoder4MotorModule.set_motor_pwm_dutycycle:4 of +msgid "The PWM duty cycle." +msgstr "PWM 占空比。" + +#: cd46b0de0815464599a0c4d41a457d72 +#: module.encoder4_motor.Encoder4MotorModule.set_motor_pwm_dutycycle:8 of +msgid "|set_motor_pwm_dutycycle.png|" +msgstr "" + +#: ../../en/refs/module.encoder4_motor.ref:15 83fac57467574bd9b650f4f5e7e4b0c3 +msgid "set_motor_pwm_dutycycle.png" +msgstr "" + +#: 520fec6b90944d55af22c85344f09223 +#: module.encoder4_motor.Encoder4MotorModule.get_motor_encoder_value:1 of +msgid "Get the encoder value of a motor." +msgstr "获取电机的编码器值。" + +#: e9dd4c1aeccf4ee1ad88105798936db9 +#: module.encoder4_motor.Encoder4MotorModule.get_motor_encoder_value:3 of +msgid "The motor to get the encoder value." +msgstr "要获取编码器值的电机。" + +#: 1fc93d9fb6934c7a9ad563328986b172 22b1e704726d49f9b5e56c341db7707b +#: 3aa67be1444040f8a8779ea4a79f2fe8 7731b7ec46b64fecaa66b260d6039a1f +#: 9a7bd228f57447fa8ae514df0b5a932f a120dee27d7745b888f2e90b664156eb +#: b0d51978df72431993d6c1c1952163e5 b2a3b967d59c43f18a5a7637a8836cf3 +#: b83fff6092af44ce8be0eb903e496d0f e1112111218e4f91a011b2fe15447162 +#: ec0f1efb4efb40f8a1692121e5d57749 f8f02690fc9549da9117e6248ed303fc +#: module.encoder4_motor.Encoder4MotorModule.get_device_spec +#: module.encoder4_motor.Encoder4MotorModule.get_encoder_mode +#: module.encoder4_motor.Encoder4MotorModule.get_motor_encoder_value +#: module.encoder4_motor.Encoder4MotorModule.get_motor_speed_value +#: module.encoder4_motor.Encoder4MotorModule.get_position_pid_value +#: module.encoder4_motor.Encoder4MotorModule.get_soft_start_state +#: module.encoder4_motor.Encoder4MotorModule.get_speed_pid_value +#: module.encoder4_motor.Encoder4MotorModule.get_vin_adc_raw12_value +#: module.encoder4_motor.Encoder4MotorModule.get_vin_adc_raw8_value +#: module.encoder4_motor.Encoder4MotorModule.get_vin_current_float_value +#: module.encoder4_motor.Encoder4MotorModule.get_vin_current_int_value +#: module.encoder4_motor.Encoder4MotorModule.get_vin_voltage of +msgid "Returns" +msgstr "返回值" + +#: 1d2f19acc7594cf7830026b1b03fa5f2 6f19a7618b2b46dbbf38cab2d2f5965b +#: module.encoder4_motor.Encoder4MotorModule.get_motor_encoder_value:5 +#: module.encoder4_motor.Encoder4MotorModule.set_motor_encoder_value:4 of +msgid "The encoder value." +msgstr "编码器值。" + +#: 0ca4324049c34adaa94daaa61cc4584d 1c74c177615b49709ba2a5f179ab9002 +#: 29c6c565d7a9408ca718ed919ce04f59 3d0c0755e5864b91ad942037b9e77f2c +#: 3fb15c8890c840f890c023111ebf66b0 5d262d16c21c482a81cfff00f1cdeefc +#: 7c593023c964452f816fe3249d3e53af 885a2c4bb4d64e04b71b88d1597e1303 +#: 8ef4a0a7d1a64524b59c6e68c2a71782 90ef6261c11640ec83d6d2caeaf978d9 +#: cea853002e1d463f90be1d503f7af7bc f2b9a8bf803746c1a2ff70221becfec3 +#: module.encoder4_motor.Encoder4MotorModule.get_device_spec +#: module.encoder4_motor.Encoder4MotorModule.get_encoder_mode +#: module.encoder4_motor.Encoder4MotorModule.get_motor_encoder_value +#: module.encoder4_motor.Encoder4MotorModule.get_motor_speed_value +#: module.encoder4_motor.Encoder4MotorModule.get_position_pid_value +#: module.encoder4_motor.Encoder4MotorModule.get_soft_start_state +#: module.encoder4_motor.Encoder4MotorModule.get_speed_pid_value +#: module.encoder4_motor.Encoder4MotorModule.get_vin_adc_raw12_value +#: module.encoder4_motor.Encoder4MotorModule.get_vin_adc_raw8_value +#: module.encoder4_motor.Encoder4MotorModule.get_vin_current_float_value +#: module.encoder4_motor.Encoder4MotorModule.get_vin_current_int_value +#: module.encoder4_motor.Encoder4MotorModule.get_vin_voltage of +msgid "Return type" +msgstr "返回类型" + +#: 994f731637d9493187387945f39d454c +#: module.encoder4_motor.Encoder4MotorModule.get_motor_encoder_value:10 of +msgid "|get_motor_encoder_value.png|" +msgstr "" + +#: ../../en/refs/module.encoder4_motor.ref:16 c3cda8b3c720428ba15fa51822b20028 +msgid "get_motor_encoder_value.png" +msgstr "" + +#: 2e3593e8ac7e45d08f1e1a253a447085 +#: module.encoder4_motor.Encoder4MotorModule.set_motor_encoder_value:1 of +msgid "Set the encoder value of a motor." +msgstr "设置电机的编码器值。" + +#: 043d122c76e84b9ab3d499f6a68f57ac +#: module.encoder4_motor.Encoder4MotorModule.set_motor_encoder_value:3 of +msgid "The motor to set the encoder value." +msgstr "要设置编码器值的电机。" + +#: e128e832f7b14317901d678a67be176c +#: module.encoder4_motor.Encoder4MotorModule.set_motor_encoder_value:8 of +msgid "|set_motor_encoder_value.png|" +msgstr "" + +#: ../../en/refs/module.encoder4_motor.ref:17 9f50e22755334be0836cf192faab9f90 +msgid "set_motor_encoder_value.png" +msgstr "" + +#: 7988d3682fb74d3ebef07728b0955fdd +#: module.encoder4_motor.Encoder4MotorModule.get_encoder_mode:1 of +msgid "Get the encoder mode." +msgstr "获取编码器模式。" + +#: cefcf3abd68b49b78cb93c19b432e046 +#: module.encoder4_motor.Encoder4MotorModule.get_encoder_mode:3 of +msgid "The encoder mode." +msgstr "编码器模式。" + +#: 2bc9d491e6064b88b02e81861b194828 +#: module.encoder4_motor.Encoder4MotorModule.get_encoder_mode:8 of +msgid "|get_encoder_mode.png|" +msgstr "" + +#: ../../en/refs/module.encoder4_motor.ref:18 b0a8575978744fa0b161fcd9800a7cd4 +msgid "get_encoder_mode.png" +msgstr "" + +#: 294918c9e6844f7abb286a3ec14ca0d7 +#: module.encoder4_motor.Encoder4MotorModule.set_encoder_mode:1 of +msgid "Set the encoder mode." +msgstr "设置编码器模式。" + +#: 7cc220519a494867b44247fc8614c85f +#: module.encoder4_motor.Encoder4MotorModule.set_encoder_mode:3 of +msgid "The mode of the encoder. Options: - ``AB``: 0 - ``BA``: 1" +msgstr "编码器模式。选项: - ``AB``: 0 - ``BA``: 1" + +#: cb79f78dfbc54988b7aa911c1b6cae9f +#: module.encoder4_motor.Encoder4MotorModule.set_encoder_mode:3 of +msgid "The mode of the encoder." +msgstr "编码器模式。" + +#: 073c34bfa19b414b8b21c28cd3391074 +#: module.encoder4_motor.Encoder4MotorModule.set_encoder_mode:6 of +msgid "``AB``: 0" +msgstr "" + +#: b6c9f72f5aae49d5ae2cd9714f2071e5 +#: module.encoder4_motor.Encoder4MotorModule.set_encoder_mode:7 of +msgid "``BA``: 1" +msgstr "" + +#: a42e6c571ad643faa0c8727a0d488d38 +#: module.encoder4_motor.Encoder4MotorModule.set_encoder_mode:11 of +msgid "|set_encoder_mode.png|" +msgstr "" + +#: ../../en/refs/module.encoder4_motor.ref:19 fe75922023e14c5381c42792871a3003 +msgid "set_encoder_mode.png" +msgstr "" + +#: 0d9b1151c2c14e1d94a8a743f12abc3a +#: module.encoder4_motor.Encoder4MotorModule.get_motor_speed_value:1 of +msgid "Get the speed value of a motor." +msgstr "获取电机的速度值。" + +#: ae04bb0399ad42319304db253c0a2d9a +#: module.encoder4_motor.Encoder4MotorModule.get_motor_speed_value:3 of +msgid "The motor to get the speed value." +msgstr "要获取速度值的电机。" + +#: 1592459f574b4a58a7647c0b4726f20f +#: module.encoder4_motor.Encoder4MotorModule.get_motor_speed_value:5 of +msgid "The speed value." +msgstr "速度值。" + +#: 04c9be85d5454a77aaa2012e0c027c7b +#: module.encoder4_motor.Encoder4MotorModule.get_motor_speed_value:10 of +msgid "|get_motor_speed_value.png|" +msgstr "" + +#: ../../en/refs/module.encoder4_motor.ref:20 c940e18d92bf435cb254806470e93cd1 +msgid "get_motor_speed_value.png" +msgstr "" + +#: d99d8e057b2a4017913e64bdd143f039 +#: module.encoder4_motor.Encoder4MotorModule.set_position_encoder_value:1 of +msgid "Set the position encoder value of a motor." +msgstr "设置电机的位置编码器值。" + +#: 44f0d996b0a14b6aa2a51ed56c7d3573 +#: module.encoder4_motor.Encoder4MotorModule.set_position_encoder_value:3 of +msgid "The motor to set the position encoder value." +msgstr "要设置位置编码器值的电机。" + +#: 6055b0f78a91428c9a9904c8b470e6d7 +#: module.encoder4_motor.Encoder4MotorModule.set_position_encoder_value:4 of +msgid "The position encoder value." +msgstr "位置编码器值。" + +#: 9b0130be3b87413fac5f827dbaefa44a +#: module.encoder4_motor.Encoder4MotorModule.set_position_encoder_value:8 of +msgid "|set_position_encoder_value.png|" +msgstr "" + +#: ../../en/refs/module.encoder4_motor.ref:21 9ab7b73cf7384425bf28220b4892f5cd +msgid "set_position_encoder_value.png" +msgstr "" + +#: 0b936d1e9fc74888bc2f8f7ca5249f69 +#: module.encoder4_motor.Encoder4MotorModule.set_position_max_speed_value:1 of +msgid "Set the maximum speed value of a motor." +msgstr "设置电机的最大速度值。" + +#: a241c9a07d274d0f92fc09d55f4ded4d +#: module.encoder4_motor.Encoder4MotorModule.set_position_max_speed_value:3 of +msgid "The motor to set the maximum speed value." +msgstr "电机设置的最高转速值。" + +#: a962b8a662234614830040e18f7b7056 +#: module.encoder4_motor.Encoder4MotorModule.set_position_max_speed_value:4 of +msgid "The maximum speed value." +msgstr "最大速度值。" + +#: 2b07208aefe24afc9ed1fa5b3195b99c +#: module.encoder4_motor.Encoder4MotorModule.set_position_max_speed_value:8 of +msgid "|set_position_max_speed_value.png|" +msgstr "" + +#: ../../en/refs/module.encoder4_motor.ref:22 a47b2c4764704f52aa6d891a0adb4c57 +msgid "set_position_max_speed_value.png" +msgstr "" + +#: f269f7daffae46f892cfd2172f4c6055 +#: module.encoder4_motor.Encoder4MotorModule.get_position_pid_value:1 of +msgid "Get the position PID value of a motor." +msgstr "获取电机的位置 PID 值。" + +#: 5546789bc1fc4711842f5a4007b1c747 +#: module.encoder4_motor.Encoder4MotorModule.get_position_pid_value:3 of +msgid "The motor to get the position P,I,D value." +msgstr "获取位置 P,I,D 值的电机。" + +#: 1ad2f239cc014b2c83db3c8797d5aa8b +#: module.encoder4_motor.Encoder4MotorModule.get_position_pid_value:5 of +msgid "The position PID value." +msgstr "位置 PID 值。" + +#: b50050b024754fa9851f7cf1025e3bf2 +#: module.encoder4_motor.Encoder4MotorModule.get_position_pid_value:10 of +msgid "|get_position_PID_value.png|" +msgstr "" + +#: ../../en/refs/module.encoder4_motor.ref:24 b06bc46735fc49bc868475485c33dc14 +msgid "get_position_PID_value.png" +msgstr "" + +#: e08727063a0f42be86b45368705a10c4 +#: module.encoder4_motor.Encoder4MotorModule.set_position_pid_value:1 of +msgid "Set the position P,I,D value of a motor." +msgstr "设置电机的位置 P,I,D 值。" + +#: b7268095e82844b79f695593dbfad2bc +#: module.encoder4_motor.Encoder4MotorModule.set_position_pid_value:3 of +msgid "The motor to set the position P,I,D value." +msgstr "设置位置 P,I,D 值的电机。" + +#: 56bdb2d964f140149310c6fa2a9283b6 e440d4181bce4797af57136c67da7f40 +#: module.encoder4_motor.Encoder4MotorModule.set_position_pid_value:4 +#: module.encoder4_motor.Encoder4MotorModule.set_speed_pid_value:4 of +msgid "The P value." +msgstr "比例值。" + +#: 86eb8bbc4796438193b436d2b847bd51 912f632876454f20a9fce5aa29b17f16 +#: module.encoder4_motor.Encoder4MotorModule.set_position_pid_value:5 +#: module.encoder4_motor.Encoder4MotorModule.set_speed_pid_value:5 of +msgid "The I value." +msgstr "积分值。" + +#: 8a4aee72f744400a8798c461c237d710 f560755032cb4d9dbdc3947891feed63 +#: module.encoder4_motor.Encoder4MotorModule.set_position_pid_value:6 +#: module.encoder4_motor.Encoder4MotorModule.set_speed_pid_value:6 of +msgid "The D value." +msgstr "微分值。" + +#: d591f36dbc6242e9a87cf8a09bd4565f +#: module.encoder4_motor.Encoder4MotorModule.set_position_pid_value:10 of +msgid "|set_position_PID_value.png|" +msgstr "" + +#: ../../en/refs/module.encoder4_motor.ref:26 0baafeffe1fa4b8e9e6da45a870c92be +msgid "set_position_PID_value.png" +msgstr "" + +#: 19d1bc0108fd475d829ec439d97d82bc +#: module.encoder4_motor.Encoder4MotorModule.get_speed_pid_value:1 of +msgid "Get the speed PID value of a motor." +msgstr "获取电机的速度 PID 值。" + +#: 977be871f45346d4900c00b1ee67ab7b +#: module.encoder4_motor.Encoder4MotorModule.get_speed_pid_value:3 of +msgid "The motor to get the speed P,I,D value." +msgstr "获取速度 P,I,D 值的电机。" + +#: ca1857413fb64c4687041c4b126d63dd +#: module.encoder4_motor.Encoder4MotorModule.get_speed_pid_value:5 of +msgid "The speed P,I,D value." +msgstr "速度 P,I,D 值。" + +#: 774b12d881a2470893ad76675a360112 +#: module.encoder4_motor.Encoder4MotorModule.get_speed_pid_value:10 of +msgid "|get_speed_PID_value.png|" +msgstr "" + +#: ../../en/refs/module.encoder4_motor.ref:27 2c070049d7444f5db860ebd0b49e9456 +msgid "get_speed_PID_value.png" +msgstr "" + +#: 90259da053f241589ba91573b47e1abf +#: module.encoder4_motor.Encoder4MotorModule.set_speed_pid_value:1 of +msgid "Set the speed PID value of a motor." +msgstr "设置电机的速度 PID 值。" + +#: 9cc63a4fd736481889642c781be9d189 +#: module.encoder4_motor.Encoder4MotorModule.set_speed_pid_value:3 of +msgid "The motor to set the speed PID value." +msgstr "设置速度 PID 值的电机。" + +#: 362fb1a41ed94820a21c022b9078f4ec +#: module.encoder4_motor.Encoder4MotorModule.set_speed_pid_value:10 of +msgid "|set_speed_PID_value.png|" +msgstr "" + +#: ../../en/refs/module.encoder4_motor.ref:28 d59779fcbad8419185255bcceec4617b +msgid "set_speed_PID_value.png" +msgstr "" + +#: 6a3cc6a46f5b42389d279fe8fbe3e899 +#: module.encoder4_motor.Encoder4MotorModule.set_speed_point_value:1 of +msgid "Set the speed point value of a motor." +msgstr "设置电机的速度目标值。" + +#: 81a56abe96e7439b9de6363baeecffd4 +#: module.encoder4_motor.Encoder4MotorModule.set_speed_point_value:3 of +msgid "The motor to set the speed point value." +msgstr "设置速度目标值的电机。" + +#: a516527a10e442e79d2060043b746388 +#: module.encoder4_motor.Encoder4MotorModule.set_speed_point_value:4 of +msgid "The speed point value." +msgstr "速度目标值。" + +#: 99362aad64974f16a7bb5aca27a3dd60 +#: module.encoder4_motor.Encoder4MotorModule.set_speed_point_value:8 of +msgid "|set_speed_point_value.png|" +msgstr "" + +#: ../../en/refs/module.encoder4_motor.ref:29 3e7463c4cd644119b7e6d15e307512b9 +msgid "set_speed_point_value.png" +msgstr "" + +#: af2be4d11da14ff0958b5a64f2a41155 +#: module.encoder4_motor.Encoder4MotorModule.get_vin_current_float_value:1 of +msgid "Get the input current value in float." +msgstr "获取输入电流值(浮点数)。" + +#: 6162563e38924d5a82d590f166858e00 d56e0874726845f38a4a3b768deadcf1 +#: module.encoder4_motor.Encoder4MotorModule.get_vin_current_float_value:3 +#: module.encoder4_motor.Encoder4MotorModule.get_vin_current_int_value:3 of +msgid "The input current value." +msgstr "输入电流值。" + +#: cbf5bc3ebf89481f9583268daceb3187 +#: module.encoder4_motor.Encoder4MotorModule.get_vin_current_float_value:8 of +msgid "|get_vin_current_float_value.png|" +msgstr "" + +#: ../../en/refs/module.encoder4_motor.ref:30 1c7a9a951d0d4a1d998bff3d7f33c500 +msgid "get_vin_current_float_value.png" +msgstr "" + +#: 1de5f8c52c074e579bd16580d86b45e5 +#: module.encoder4_motor.Encoder4MotorModule.get_vin_current_int_value:1 of +msgid "Get the input current value in int." +msgstr "获取输入电流值(整数)。" + +#: c35513fef9d24b42a08b03a4f6f7a6fc +#: module.encoder4_motor.Encoder4MotorModule.get_vin_current_int_value:8 of +msgid "|get_vin_current_int_value.png|" +msgstr "" + +#: ../../en/refs/module.encoder4_motor.ref:31 546395a3ca314db485e62617d59c6f99 +msgid "get_vin_current_int_value.png" +msgstr "" + +#: a9ff65f6700d49129fc40b9b153e7b12 +#: module.encoder4_motor.Encoder4MotorModule.get_vin_adc_raw8_value:1 of +msgid "Get the input voltage ADC raw value in 8-bit." +msgstr "获取输入电压的 ADC 原始值(8 位)。" + +#: 329950b02f6f497dbea57636b45a26e4 88646aad13fc41239dfe09bdff5fbf53 +#: module.encoder4_motor.Encoder4MotorModule.get_vin_adc_raw12_value:3 +#: module.encoder4_motor.Encoder4MotorModule.get_vin_adc_raw8_value:3 of +msgid "The input voltage ADC raw value." +msgstr "输入电压的 ADC 原始值。" + +#: 7e64ba83fe584af48cc3a2d251fb6746 +#: module.encoder4_motor.Encoder4MotorModule.get_vin_adc_raw8_value:8 of +msgid "|get_vin_adc_raw8_value.png|" +msgstr "" + +#: ../../en/refs/module.encoder4_motor.ref:32 d080fe308cb64d5dbd64807e9d3b22ef +msgid "get_vin_adc_raw8_value.png" +msgstr "" + +#: f60cf76a4f474ecaa8b170813767bb7b +#: module.encoder4_motor.Encoder4MotorModule.get_vin_adc_raw12_value:1 of +msgid "Get the input voltage ADC raw value in 12-bit." +msgstr "获取输入电压的 ADC 原始值(12 位)。" + +#: e039ff9b10804f0fbd84782a1b2118b5 +#: module.encoder4_motor.Encoder4MotorModule.get_vin_adc_raw12_value:8 of +msgid "|get_vin_adc_raw12_value.png|" +msgstr "" + +#: ../../en/refs/module.encoder4_motor.ref:33 9e73af266f094c6eb6a400478dc0c06a +msgid "get_vin_adc_raw12_value.png" +msgstr "" + +#: 9d518c6d9e6647b4b4cb379eda6a85a6 +#: module.encoder4_motor.Encoder4MotorModule.get_vin_voltage:1 of +msgid "Get the input voltage value." +msgstr "获取输入电压值。" + +#: 6283a93cf74e49ac8e6d1b0679eb2bf1 +#: module.encoder4_motor.Encoder4MotorModule.get_vin_voltage:3 of +msgid "The input voltage value." +msgstr "输入电压值。" + +#: 6021e6c3f7eb441fb53095637427321f +#: module.encoder4_motor.Encoder4MotorModule.get_vin_voltage:8 of +msgid "|get_vin_voltage.png|" +msgstr "" + +#: ../../en/refs/module.encoder4_motor.ref:34 ae8847747ed24364a4b90a8c151dc1bf +msgid "get_vin_voltage.png" +msgstr "" + +#: 6322d647b9ac4f0b926d5036d6b70bbf +#: module.encoder4_motor.Encoder4MotorModule.get_device_spec:1 of +msgid "Get the device specification." +msgstr "获取设备规格。" + +#: d9e7092a3160410997db7cabef21c643 +#: module.encoder4_motor.Encoder4MotorModule.get_device_spec:3 of +msgid "The information to get." +msgstr "要获取的信息。" + +#: 149efccfbecc4cfc9be87ac112a455dc +#: module.encoder4_motor.Encoder4MotorModule.get_device_spec:5 of +msgid "The device specification(firmware version/I2C address)." +msgstr "设备规格(固件版本/I2C 地址)。" + +#: d5fb2c8181dc4528a5168f44248eb75b +#: module.encoder4_motor.Encoder4MotorModule.get_device_spec:10 of +msgid "|get_device_spec.png|" +msgstr "" + +#: ../../en/refs/module.encoder4_motor.ref:35 e67534b7d678437797c6b132f0caa444 +msgid "get_device_spec.png" +msgstr "" + +#: 7fb66dbdad8b47e483a7c14c8f78f03a +#: module.encoder4_motor.Encoder4MotorModule.get_soft_start_state:1 of +msgid "Get the soft start state of a motor." +msgstr "获取电机的软启动状态。" + +#: b7c29102c0eb494997fdb02a366f8e35 +#: module.encoder4_motor.Encoder4MotorModule.get_soft_start_state:3 of +msgid "The motor to get the soft start state." +msgstr "电机的软启动状态。" + +#: 68bd802de72b4ff4ac6ed44173e632fc c554c6866de74c17b2cb2d61ed20ceeb +#: module.encoder4_motor.Encoder4MotorModule.get_soft_start_state:5 +#: module.encoder4_motor.Encoder4MotorModule.set_soft_start_state:4 of +msgid "The soft start state." +msgstr "软启动状态。" + +#: 5566ae2251184c01aeeec3973928cbde +#: module.encoder4_motor.Encoder4MotorModule.get_soft_start_state:10 of +msgid "|get_soft_start_state.png|" +msgstr "" + +#: ../../en/refs/module.encoder4_motor.ref:36 53810e8c4090417590d4315643ff4a6d +msgid "get_soft_start_state.png" +msgstr "" + +#: d1f9b3ddfe9e4da8998dd082acd4dd0d +#: module.encoder4_motor.Encoder4MotorModule.set_soft_start_state:1 of +msgid "Set the soft start state of a motor." +msgstr "设置电机的软启动状态。" + +#: 875c39ac68df4cdcace92f042506a889 +#: module.encoder4_motor.Encoder4MotorModule.set_soft_start_state:3 of +msgid "The motor to set the soft start state." +msgstr "要设置软启动状态的电机。" + +#: 4a464f84439442ae89ce1c12f58c9337 +#: module.encoder4_motor.Encoder4MotorModule.set_soft_start_state:4 of +msgid "The soft start state. Options: - ``True``: 1 - ``False``: 0" +msgstr "软启动状态。选项: - ``True``: 1 - ``False``: 0" + +#: 6a3787ac647c46089853db6cfb71164c +#: module.encoder4_motor.Encoder4MotorModule.set_soft_start_state:7 of +msgid "``True``: 1" +msgstr "" + +#: 44d84d643d7e4918ae3a246de44d530f +#: module.encoder4_motor.Encoder4MotorModule.set_soft_start_state:8 of +msgid "``False``: 0" +msgstr "" + +#: a9b78cca968948688164e3a195ce500c +#: module.encoder4_motor.Encoder4MotorModule.set_soft_start_state:12 of +msgid "|set_soft_start_state.png|" +msgstr "" + +#: ../../en/refs/module.encoder4_motor.ref:37 a3d49afef6ef42e082c5b6ea72785206 +msgid "set_soft_start_state.png" +msgstr "" + +#: 90ae61f698914093a88f36a1bbbcaf65 +#: module.encoder4_motor.Encoder4MotorModule.set_i2c_address:1 of +msgid "Set the I2C address of the device." +msgstr "设置设备的 I2C 地址。" + +#: f341c5f9f4c9479bb5c165ae1bc3f25d +#: module.encoder4_motor.Encoder4MotorModule.set_i2c_address:3 of +msgid "The I2C address to set." +msgstr "设置的 I2C 地址。" + +#: cc00dddd73a344fb8146a76633fb748b +#: module.encoder4_motor.Encoder4MotorModule.set_i2c_address:7 of +msgid "|set_i2c_address.png|" +msgstr "" + +#: ../../en/refs/module.encoder4_motor.ref:38 c63faa3c42a942d396d7149eb5a5b9a3 +msgid "set_i2c_address.png" +msgstr "" + diff --git a/examples/module/encoder4_motor/encoder4motor_core2_example.m5f2 b/examples/module/encoder4_motor/encoder4motor_core2_example.m5f2 new file mode 100644 index 00000000..5637d181 --- /dev/null +++ b/examples/module/encoder4_motor/encoder4motor_core2_example.m5f2 @@ -0,0 +1 @@ +{"version":"V2.0","versionNumber":"V2.2.3","type":"core2","components":[{"name":"screen","type":"screen","layer":0,"screenId":"builtin","screenName":"","id":"__core2_screen","createTime":1741773089191,"x":0,"y":0,"width":320,"height":240,"backgroundColor":"#222222","size":0,"isSelected":true},{"name":"title0","type":"title","layer":1,"screenId":"builtin","screenName":"","id":"iRY8VFJzSh89T&I-","createTime":1741831912664,"x":0,"y":0,"color":"#ffffff","backgroundColor":"#0000FF","text":"4EncoderMotor Core2 Example","textOffset":3,"font":"Widgets.FONTS.DejaVu18","isSelected":false},{"name":"label0","type":"label","layer":2,"screenId":"builtin","screenName":"","id":"tmKZKWq44Hw`r&@s","createTime":1741832182164,"x":1,"y":56,"color":"#ffffff","backgroundColor":"#222222","text":"label0","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false,"width":58,"height":20},{"name":"label1","type":"label","layer":3,"screenId":"builtin","screenName":"","id":"nyfcCoxsloNX@H3V","createTime":1741832183963,"x":1,"y":100,"color":"#ffffff","backgroundColor":"#222222","text":"label1","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false,"width":58,"height":20},{"name":"label2","type":"label","layer":4,"screenId":"builtin","screenName":"","id":"p0Xq8rSwv@nvYR7Y","createTime":1741832185341,"x":1,"y":144,"color":"#ffffff","backgroundColor":"#222222","text":"label2","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false,"width":58,"height":20},{"name":"label3","type":"label","layer":5,"screenId":"builtin","screenName":"","id":"oZrD3Z@gwjzL579^","createTime":1741832186765,"x":1,"y":185,"color":"#ffffff","backgroundColor":"#222222","text":"label3","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false,"width":58,"height":20}],"resources":[{"hardware":["hardware_button","hardware_pin_button","imu","speaker","touch","mic"]},{"module":["encoder4motor"]}],"units":[],"hats":[],"bases":[],"i2cs":[],"blockly":"trueencoder4_motor_00x24encoder4_motor_00x02encoder4_motor_00x0050encoder4_motor_00x0150encoder4_motor_00x0250encoder4_motor_00x0350truelabel0LabelMotor1 Speed:encoder4_motor_00x00label1LabelMotor2 Speed:encoder4_motor_00x01label2LabelMotor3 Speed:encoder4_motor_00x02label3LabelMotor4 Speed:encoder4_motor_00x03","screen":[{"simulationName":"Built-in","type":"builtin","width":320,"height":240,"scale":0.78,"screenName":"","blockId":"","screenColorType":0,"id":"builtin","createTime":1741773089189}],"logicWhenNum":0,"customList":[]} \ No newline at end of file diff --git a/examples/module/encoder4_motor/encoder4motor_core2_example.py b/examples/module/encoder4_motor/encoder4motor_core2_example.py new file mode 100644 index 00000000..f9d2fb6c --- /dev/null +++ b/examples/module/encoder4_motor/encoder4motor_core2_example.py @@ -0,0 +1,68 @@ +# SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import os, sys, io +import M5 +from M5 import * +from module import Encoder4MotorModule + + +title0 = None +label0 = None +label1 = None +label2 = None +label3 = None +encoder4_motor_0 = None + + +def setup(): + global title0, label0, label1, label2, label3, encoder4_motor_0 + + M5.begin() + Widgets.fillScreen(0x222222) + title0 = Widgets.Title( + "4EncoderMotor Core2 Example", 3, 0xFFFFFF, 0x0000FF, Widgets.FONTS.DejaVu18 + ) + label0 = Widgets.Label("label0", 1, 56, 1.0, 0xFFFFFF, 0x222222, Widgets.FONTS.DejaVu18) + label1 = Widgets.Label("label1", 1, 100, 1.0, 0xFFFFFF, 0x222222, Widgets.FONTS.DejaVu18) + label2 = Widgets.Label("label2", 1, 144, 1.0, 0xFFFFFF, 0x222222, Widgets.FONTS.DejaVu18) + label3 = Widgets.Label("label3", 1, 185, 1.0, 0xFFFFFF, 0x222222, Widgets.FONTS.DejaVu18) + + encoder4_motor_0 = Encoder4MotorModule(address=0x24) + encoder4_motor_0.set_all_motors_mode(0x02) + encoder4_motor_0.set_speed_point_value(0x00, 50) + encoder4_motor_0.set_speed_point_value(0x01, 50) + encoder4_motor_0.set_speed_point_value(0x02, 50) + encoder4_motor_0.set_speed_point_value(0x03, 50) + + +def loop(): + global title0, label0, label1, label2, label3, encoder4_motor_0 + M5.update() + label0.setText( + str((str("Motor1 Speed:") + str((encoder4_motor_0.get_motor_speed_value(0x00))))) + ) + label1.setText( + str((str("Motor2 Speed:") + str((encoder4_motor_0.get_motor_speed_value(0x01))))) + ) + label2.setText( + str((str("Motor3 Speed:") + str((encoder4_motor_0.get_motor_speed_value(0x02))))) + ) + label3.setText( + str((str("Motor4 Speed:") + str((encoder4_motor_0.get_motor_speed_value(0x03))))) + ) + + +if __name__ == "__main__": + try: + setup() + while True: + loop() + except (Exception, KeyboardInterrupt) as e: + try: + from utility import print_error_msg + + print_error_msg(e) + except ImportError: + print("please update to latest firmware") diff --git a/m5stack/libs/module/encoder4_motor.py b/m5stack/libs/module/encoder4_motor.py index b20f088a..57326581 100644 --- a/m5stack/libs/module/encoder4_motor.py +++ b/m5stack/libs/module/encoder4_motor.py @@ -1,244 +1,558 @@ -# SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD # # SPDX-License-Identifier: MIT -from .mbus import i2c1 -from .module_helper import ModuleError +from module.mbus import i2c1 +from module.module_helper import ModuleError import struct import time -DEVADDR = 0x24 -MTR_PWM_DUTY = 0x20 -MTR_ENCODER = 0x30 -MTR_SPEED = 0x40 -MTR1_MODE = 0x50 -MTR2_MODE = 0x60 -MTR3_MODE = 0x70 -MTR4_MODE = 0x80 -VIN_AMPS_FLT = 0x90 -VIN_ADC8 = 0xA0 -VIN_ADC12 = 0xB0 -VIN_AMPS_INT = 0xC0 -ENCODER_AB = 0xD0 -FIRM_VER = 0xFE -I2C_ADDR = 0xFF +class Encoder4MotorModule: + """Create an Encoder4MotorModule object -NORMAL_MODE = 0x00 -POSITION_MODE = 0x01 -SPEED_MODE = 0x02 + :param int address: The I2C address of the device. Default is 0x24. + UiFlow2 Code Block: + + |init.png| + + MicroPython Code Block: + + .. code-block:: python + + from module import Encoder4MotorModule + + encoder4_motor = Encoder4MotorModule(0x24) + """ + + DEVADDR = 0x24 + + MTR_PWM_DUTY = 0x20 + MTR_ENCODER = 0x30 + MTR_SPEED = 0x40 + MTR1_MODE = 0x50 + MTR2_MODE = 0x60 + MTR3_MODE = 0x70 + MTR4_MODE = 0x80 + VIN_AMPS_FLT = 0x90 + VIN_ADC8 = 0xA0 + VIN_ADC12 = 0xB0 + VIN_AMPS_INT = 0xC0 + ENCODER_AB = 0xD0 + SOFT_START = 0xD1 + FIRM_VER = 0xFE + I2C_ADDR = 0xFF + + NORMAL_MODE = 0x00 + POSITION_MODE = 0x01 + SPEED_MODE = 0x02 -class Encoder4MotorModule: def __init__(self, address: int = DEVADDR): - """ - init the i2c address - address : 1 to 127 - """ - self.mtr_i2c = i2c1 - self.i2c_addr = address + self._i2c = i2c1 + self._address = address if address >= 1 and address <= 127: - self.i2c_addr = address + self._address = address self.available() - self.mode = NORMAL_MODE + self.mode = self.NORMAL_MODE def available(self): check = False - for i in range(3): - if self.i2c_addr in self.mtr_i2c.scan(): + for _ in range(3): + if self._address in self._i2c.scan(): check = True break time.sleep(0.2) if not check: raise ModuleError("4Encoder motor module maybe not connect") - def set_motor_mode(self, pos, mode): - """ - pos: [ONE or TWO or THREE or FOUR] - mode: [NORMAL_MODE or POSITION or SPEED] + def set_motor_mode(self, motor, mode): + """Set the motor mode. + + :param int motor: The motor to set the mode. + :param int mode: The mode of the motor. + + Options: + - ``NORMAL_MODE``: 0 + - ``POSITION_MODE``: 1 + - ``SPEED_MODE``: 2 + + UiFlow2 Code Block: + + |set_motor_mode.png| + + MicroPython Code Block: + + .. code-block:: python + + encoder4_motor.set_motor_mode(0, encoder4_motor.NORMAL_MODE) """ self.mode = mode - self.mtr_i2c.writeto_mem(self.i2c_addr, MTR1_MODE + (0x10 * pos), bytearray([mode])) + self._i2c.writeto_mem(self._address, self.MTR1_MODE + (0x10 * motor), bytearray([mode])) def set_all_motors_mode(self, mode): + """Set the mode of all motors. + + :param int mode: The mode of the motors. + + Options: + - ``NORMAL_MODE``: 0 + - ``POSITION_MODE``: 1 + - ``SPEED_MODE``: 2 + + UiFlow2 Code Block: + + |set_all_motors_mode.png| + + MicroPython Code Block: + + .. code-block:: python + + encoder4_motor.set_all_motors_mode(encoder4_motor.NORMAL_MODE) """ - mode: [NORMAL_MODE or POSITION or SPEED] - """ - for i in range(0, 3): + for i in range(0, 4): self.set_motor_mode(i, mode) - time.sleep_ms(150) - def set_motor_pwm_dutycycle(self, pos, duty): - """ - pos: [ONE or TWO or THREE or FOUR] - duty: -127 ~ 127 + def set_motor_pwm_dutycycle(self, motor, duty): + """Set the PWM duty cycle of a motor. + + :param int motor: The motor to set the PWM duty cycle. + :param int duty: The PWM duty cycle. + + UiFlow2 Code Block: + + |set_motor_pwm_dutycycle.png| + + MicroPython Code Block: + + .. code-block:: python + + encoder4_motor.set_motor_pwm_dutycycle(0, 127) """ duty = min(max(duty, -128), 127) duty = struct.pack(">b", duty) - self.mtr_i2c.writeto_mem(self.i2c_addr, MTR_PWM_DUTY + pos, duty) + self._i2c.writeto_mem(self._address, self.MTR_PWM_DUTY + motor, duty) def get_motor_encoder_value(self, pos): + """Get the encoder value of a motor. + + :param int pos: The motor to get the encoder value. + + :returns: The encoder value. + :rtype: int + + UiFlow2 Code Block: + + |get_motor_encoder_value.png| + + MicroPython Code Block: + + .. code-block:: python + + encoder4_motor.get_motor_encoder_value(0) """ - pos: [ONE or TWO or THREE or FOUR] - """ - buf = self.mtr_i2c.readfrom_mem(self.i2c_addr, MTR_ENCODER + (0x04 * pos), 4) + buf = self._i2c.readfrom_mem(self._address, self.MTR_ENCODER + (0x04 * pos), 4) return struct.unpack(">i", buf)[0] def set_motor_encoder_value(self, pos, value): - """ - pos: [ONE or TWO or THREE or FOUR] - value: signed int 4byte + """Set the encoder value of a motor. + + :param int pos: The motor to set the encoder value. + :param int value: The encoder value. + + UiFlow2 Code Block: + + |set_motor_encoder_value.png| + + MicroPython Code Block: + + .. code-block:: python + + encoder4_motor.set_motor_encoder_value(0, 100) """ value = struct.pack(">i", value) - self.mtr_i2c.writeto_mem(self.i2c_addr, MTR_ENCODER + (0x04 * pos), value) + self._i2c.writeto_mem(self._address, self.MTR_ENCODER + (0x04 * pos), value) def get_encoder_mode(self): + """Get the encoder mode. + + :return: The encoder mode. + :rtype: int + + UiFlow2 Code Block: + + |get_encoder_mode.png| + + MicroPython Code Block: + + .. code-block:: python + + encoder4_motor.get_encoder_mode() """ - return: 0[AB] or 1[BA] mode - """ - return self.mtr_i2c.readfrom_mem(self.i2c_addr, ENCODER_AB, 1)[0] + return self._i2c.readfrom_mem(self._address, self.ENCODER_AB, 1)[0] def set_encoder_mode(self, mode): + """Set the encoder mode. + + :param int mode: The mode of the encoder. + + Options: + - ``AB``: 0 + - ``BA``: 1 + + UiFlow2 Code Block: + + |set_encoder_mode.png| + + MicroPython Code Block: + + .. code-block:: python + + encoder4_motor.set_encoder_mode(0x00) """ - mode: [AB or BA] - """ - self.mtr_i2c.writeto_mem(self.i2c_addr, ENCODER_AB, bytearray([mode])) + self._i2c.writeto_mem(self._address, self.ENCODER_AB, bytearray([mode])) time.sleep_ms(150) def get_motor_speed_value(self, pos): + """Get the speed value of a motor. + + :param int pos: The motor to get the speed value. + + :returns: The speed value. + :rtype: int + + UiFlow2 Code Block: + + |get_motor_speed_value.png| + + MicroPython Code Block: + + .. code-block:: python + + encoder4_motor.get_motor_speed_value(0) """ - pos: [ONE or TWO or THREE or FOUR] - """ - return self.mtr_i2c.readfrom_mem(self.i2c_addr, MTR_SPEED + pos, 1)[0] + return self._i2c.readfrom_mem(self._address, self.MTR_SPEED + pos, 1)[0] def set_position_encoder_value(self, pos, value): - """ - pos: [ONE or TWO or THREE or FOUR] - value: signed int 4byte + """Set the position encoder value of a motor. + + :param int pos: The motor to set the position encoder value. + :param int value: The position encoder value. + + UiFlow2 Code Block: + + |set_position_encoder_value.png| + + MicroPython Code Block: + + .. code-block:: python + + encoder4_motor.set_position_encoder_value(0, 100) """ value = struct.pack(" Date: Mon, 17 Mar 2025 16:13:05 +0800 Subject: [PATCH 023/322] board.cpp: Fix I2C initialization error on NanoC6. Signed-off-by: lbuque --- m5stack/board.cpp | 2 +- m5stack/components/M5Unified/mpy_m5unified.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/m5stack/board.cpp b/m5stack/board.cpp index fd88f718..6a10dcd9 100644 --- a/m5stack/board.cpp +++ b/m5stack/board.cpp @@ -27,7 +27,7 @@ extern "C" { } #endif - if (in_scl != 255 || in_sda != 255) { + if (in_scl != GPIO_NUM_NC || in_sda != GPIO_NUM_NC) { ESP_LOGI("BOARD", "Internal I2C(%d) init", in_port); i2c_config_t conf; memset(&conf, 0, sizeof(i2c_config_t)); diff --git a/m5stack/components/M5Unified/mpy_m5unified.cpp b/m5stack/components/M5Unified/mpy_m5unified.cpp index 994bfe09..0ab386e7 100644 --- a/m5stack/components/M5Unified/mpy_m5unified.cpp +++ b/m5stack/components/M5Unified/mpy_m5unified.cpp @@ -430,7 +430,7 @@ static void in_i2c_init(void) { } #endif - if (in_scl != 255 || in_sda != 255) { + if (in_scl != GPIO_NUM_NC || in_sda != GPIO_NUM_NC) { ESP_LOGI("BOARD", "Internal I2C(%d) init", in_port); // if (in_port == I2C_NUM_0) { // periph_module_enable(PERIPH_I2C0_MODULE); From a3062b58ac31788f28d7c286292a825b833e5ff6 Mon Sep 17 00:00:00 2001 From: lbuque Date: Mon, 17 Mar 2025 14:09:45 +0800 Subject: [PATCH 024/322] libs/hardware: Add PWR485 support. Signed-off-by: lbuque --- docs/en/hardware/index.rst | 4 +- docs/en/hardware/plcio.digitalinput.rst | 2 + docs/en/hardware/plcio.relay.rst | 2 + docs/en/hardware/pwr485.rst | 79 ++ docs/en/hardware/uart.rst | 427 ++++++++++ docs/en/refs/hardware.pwr485.ref | 12 + docs/en/refs/hardware.uart.ref | 29 + .../zh_CN/LC_MESSAGES/hardware/pwr485.po | 140 ++++ .../zh_CN/LC_MESSAGES/hardware/uart.po | 763 ++++++++++++++++++ .../hardware/pwr485/stamplc_ehco_example.m5f2 | 1 + .../hardware/pwr485/stamplc_ehco_example.py | 87 ++ examples/hardware/uart/cores3_echo_example.py | 51 ++ .../hardware/uart/cores3_echo_exmaple.m5f2 | 1 + m5stack/Makefile | 2 + m5stack/libs/hardware/__init__.py | 1 + m5stack/libs/hardware/manifest.py | 1 + m5stack/libs/hardware/pwr485.py | 40 + .../0008-machine_uart-add-uart-mode.patch | 43 + 18 files changed, 1684 insertions(+), 1 deletion(-) create mode 100644 docs/en/hardware/pwr485.rst create mode 100644 docs/en/hardware/uart.rst create mode 100644 docs/en/refs/hardware.pwr485.ref create mode 100644 docs/en/refs/hardware.uart.ref create mode 100644 docs/locales/zh_CN/LC_MESSAGES/hardware/pwr485.po create mode 100644 docs/locales/zh_CN/LC_MESSAGES/hardware/uart.po create mode 100644 examples/hardware/pwr485/stamplc_ehco_example.m5f2 create mode 100644 examples/hardware/pwr485/stamplc_ehco_example.py create mode 100644 examples/hardware/uart/cores3_echo_example.py create mode 100644 examples/hardware/uart/cores3_echo_exmaple.m5f2 create mode 100644 m5stack/libs/hardware/pwr485.py create mode 100644 m5stack/patches/0008-machine_uart-add-uart-mode.patch diff --git a/docs/en/hardware/index.rst b/docs/en/hardware/index.rst index c6f814da..ff9eb240 100644 --- a/docs/en/hardware/index.rst +++ b/docs/en/hardware/index.rst @@ -8,14 +8,16 @@ Hardware als.rst button.rst can.rst + display.rst imu.rst ir.rst mic.rst pin.rst plcio.rst + pwr485.rst rotary.rst scd40.rst sen55.rst speaker.rst + uart.rst wdt.rst - display.rst diff --git a/docs/en/hardware/plcio.digitalinput.rst b/docs/en/hardware/plcio.digitalinput.rst index dcb4ae59..ee43c809 100644 --- a/docs/en/hardware/plcio.digitalinput.rst +++ b/docs/en/hardware/plcio.digitalinput.rst @@ -36,6 +36,8 @@ This example demonstrates how to get the status of a digital input and display t MicroPython Code Block: .. literalinclude:: ../../../examples/hardware/plcio/digital_input/stamplc_digital_input_example.py + :language: python + :linenos: Example output: diff --git a/docs/en/hardware/plcio.relay.rst b/docs/en/hardware/plcio.relay.rst index 73fd77c7..d0316a73 100644 --- a/docs/en/hardware/plcio.relay.rst +++ b/docs/en/hardware/plcio.relay.rst @@ -36,6 +36,8 @@ This example demonstrates how to use a button to control the state of a relay an MicroPython Code Block: .. literalinclude:: ../../../examples/hardware/plcio/relay/stamplc_relay_example.py + :language: python + :linenos: Example output: diff --git a/docs/en/hardware/pwr485.rst b/docs/en/hardware/pwr485.rst new file mode 100644 index 00000000..04de4253 --- /dev/null +++ b/docs/en/hardware/pwr485.rst @@ -0,0 +1,79 @@ +PWR485 +====== + +.. include:: ../refs/hardware.pwr485.ref + +The PWR485 is a RS485 interface that can be used to communicate with other devices. + +The following are the host's support for PWR485: + +.. table:: + :widths: auto + :align: center + + +-----------------+----------------+ + |Controller | Status | + +=================+================+ + | StampPLC | |S| | + +-----------------+----------------+ + +.. |S| unicode:: U+2714 + + +UiFlow2 Example +--------------- + +Echo +^^^^ + +Open the |stamplc_ehco_example.m5f2| project in UiFlow2. + +This example demonstrates how to utilize PWR485 interfaces by echoing back to the +sender any data received on configured PWR485. + +By pressing different keys, different characters are sent. + +UiFlow2 Code Block: + + |stamplc_ehco_example.png| + +Example output: + + None + + + +MicroPython Example +------------------- + +Echo +^^^^ + +This example demonstrates how to utilize PWR485 interfaces by echoing back to the +sender any data received on configured PWR485. + +By pressing different keys, different characters are sent. + +MicroPython Code Block: + + .. literalinclude:: ../../../examples/hardware/pwr485/stamplc_ehco_example.py + :language: python + :linenos: + +Example output: + + None + + +MicroPython Example +------------------- + + +**API** +------- + +class PWR485 +^^^^^^^^^^^^ + +.. autoclass:: hardware.pwr485.PWR485 + :members: diff --git a/docs/en/hardware/uart.rst b/docs/en/hardware/uart.rst new file mode 100644 index 00000000..0c1cbf25 --- /dev/null +++ b/docs/en/hardware/uart.rst @@ -0,0 +1,427 @@ +UART +==== + +.. include:: ../refs/hardware.uart.ref + +UART implements the standard UART/USART duplex serial communications protocol. At the physical level it consists of 2 lines: RX and TX. The unit of communication is a character (not to be confused with a string character) which can be 8 or 9 bits wide. + +UiFlow2 Example +--------------- + +Echo +^^^^ + +Open the |cores3_echo_exmaple.m5f2| project in UiFlow2. + +This example demonstrates how to utilize UART interfaces by echoing back to the +sender any data received on configured UART. + +UiFlow2 Code Block: + + |cores3_echo_exmaple.png| + +Example output: + + None + + + +MicroPython Example +------------------- + +Echo +^^^^ + +This example demonstrates how to utilize UART interfaces by echoing back to the +sender any data received on configured UART. + +MicroPython Code Block: + + .. literalinclude:: ../../../examples/hardware/uart/cores3_echo_example.py + :language: python + :linenos: + +Example output: + + None + + +**API** +------- + +class UART +^^^^^^^^^^ + +.. _hardware.UART: + +.. class:: UART(id, baudrate=9600, bits=8, parity=None, stop=1, *, ...) + + Construct a UART object of the given id. + + For more parameters, please refer to init. + + UiFlow2 Code Block: + + |init.png| + + MicroPython Code Block: + + .. code-block:: python + + from hadrware import UART + + uart1 = UART(1, baudrate=115200, bits=8, parity=None, stop=1, tx=9, rx=10) + + + .. method:: UART.init(baudrate=9600, bits=8, parity=None, stop=1, *, ...) + + Initialise the UART bus with the given parameters. + + :param int baudrate: the clock rate. + :param int bits: the number of bits per character, 7, 8 or 9. + :param parity: the parity, ``None``, 0 (even) or 1 (odd). + :type parity: None or int + :param int stop: the number of stop bits, 1 or 2. + :keyword tx: the TX pin to use. + :type tx: Pin or int + :keyword rx: the RX pin to use. + :type rx: Pin or int + :keyword rts: the RTS (output) pin to use for hardware receive flow control. + :type rts: Pin or int + :keyword cts: the CTS (input) pin to use for hardware transmit flow control. + :type cts: Pin or int + :keyword int txbuf: the length in characters of the TX buffer. + :keyword int rxbuf: the length in characters of the RX buffer. + :keyword int timeout: the time to wait for the first character (in ms). + :keyword int timeout_char: the time to wait between characters (in ms). + :keyword int invert: which lines to invert. + + - ``0`` will not invert lines (idle state of both lines is logic high). + + - ``UART.INV_TX`` will invert TX line (idle state of TX line now logic low). + + - ``UART.INV_RX`` will invert RX line (idle state of RX line now logic low). + + - ``UART.INV_TX | UART.INV_RX`` will invert both lines (idle state at logic low). + + :keyword int flow: which hardware flow control signals to use. The value is a bitmask. + + - ``0`` will ignore hardware flow control signals. + + - ``UART.RTS`` will enable receive flow control by using the RTS output pin to signal if the receive FIFO has sufficient space to accept more data. + + - ``UART.CTS`` will enable transmit flow control by pausing transmission when the CTS input pin signals that the receiver is running low on buffer space. + + - ``UART.RTS | UART.CTS`` will enable both, for full hardware flow control. + + :keyword int mode: the mode of the UART. The value is a bitmask. + + - ``UART.MODE_UART`` specifies regular UART mode. + + - ``UART.MODE_RS485_HALF_DUPLEX`` specifies half duplex RS485 UART mode control by RTS pin. + + - ``UART.MODE_IRDA`` specifies IRDA UART mode. + + - ``UART.MODE_RS485_COLLISION_DETECT`` specifies RS485 collision detection UART mode (used for test purposes). + + - ``UART.MODE_RS485_APP_CTRL`` specifies application control RS485 UART mode (used for test purposes). + + .. note:: + It is possible to call ``init()`` multiple times on the same object in + order to reconfigure UART on the fly. That allows using single UART + peripheral to serve different devices attached to different GPIO pins. + Only one device can be served at a time in that case. + Also do not call ``deinit()`` as it will prevent calling ``init()`` + again. + + UiFlow2 Code Block: + + |setup.png| + + MicroPython Code Block: + + .. code-block:: python + + uart1.init(baudrate=115200, bits=8, parity=None, stop=1, tx=9, rx=10) + + + .. method:: UART.deinit() + + Turn off the UART bus. + + .. note:: + You will not be able to call ``init()`` on the object after ``deinit()``. + A new instance needs to be created in that case. + + UiFlow2 Code Block: + + |deinit.png| + + MicroPython Code Block: + + .. code-block:: python + + uart1.deinit() + + + .. method:: UART.any() + + Returns an integer counting the number of characters that can be read without + blocking. It will return 0 if there are no characters available and a positive + number if there are characters. The method may return 1 even if there is more + than one character available for reading. + + :return: the number of characters available for reading. + :rtype: int + + For more sophisticated querying of available characters use select.poll:: + + poll = select.poll() + poll.register(uart, select.POLLIN) + poll.poll(timeout) + + UiFlow2 Code Block: + + |any.png| + + MicroPython Code Block: + + .. code-block:: python + + print(uart1.any()) + + + .. method:: UART.read([nbytes]) + + Read characters. If ``nbytes`` is specified then read at most that many bytes, + otherwise read as much data as possible. It may return sooner if a timeout + is reached. The timeout is configurable in the constructor. + + :return: a bytes object containing the bytes read in. Returns ``None`` on timeout. + :rtype: bytes or None + + UiFlow2 Code Block: + + |read_all.png| + + |read_bytes.png| + + |read_raw_data.png| + + MicroPython Code Block: + + .. code-block:: python + + print(uart1.read()) + + + .. method:: UART.readinto(buf[, nbytes]) + + Read bytes into the ``buf``. If ``nbytes`` is specified then read at most + that many bytes. Otherwise, read at most ``len(buf)`` bytes. It may return sooner if a timeout + is reached. The timeout is configurable in the constructor. + + :return: number of bytes read and stored into ``buf`` or ``None`` on timeout. + :rtype: int or None + + UiFlow2 Code Block: + + |readinto.png| + + MicroPython Code Block: + + .. code-block:: python + + buf = bytearray(10) + uart1.readinto(buf) + + + .. method:: UART.readline() + + Read a line, ending in a newline character. It may return sooner if a timeout + is reached. The timeout is configurable in the constructor. + + :return: the line read or ``None`` on timeout. + :rtype: str or None + + UiFlow2 Code Block: + + |readline.png| + + MicroPython Code Block: + + .. code-block:: python + + print(uart1.readline()) + + + .. method:: UART.write(buf) + + Write the buffer of bytes to the bus. + + :param buf: the buffer of bytes to write. + :type buf: bytes or bytearray or str + + :return: number of bytes written or ``None`` on timeout. + :rtype: int or None + + UiFlow2 Code Block: + + |write.png| + + |write1.png| + + |write_line.png| + + |write_list.png| + + |write_raw_data.png| + + |write_raw_data_list.png| + + MicroPython Code Block: + + .. code-block:: python + + uart1.write('1234!') + + + .. method:: UART.sendbreak() + + Send a break condition on the bus. This drives the bus low for a duration + longer than required for a normal transmission of a character. + + UiFlow2 Code Block: + + |sendbreak.png| + + MicroPython Code Block: + + .. code-block:: python + + uart1.sendbreak() + + + .. method:: UART.flush() + + Waits until all data has been sent. In case of a timeout, an exception is raised. The timeout + duration depends on the tx buffer size and the baud rate. Unless flow control is enabled, a timeout + should not occur. + + .. note:: + + For the rp2, esp8266 and nrf ports the call returns while the last byte is sent. + If required, a one character wait time has to be added in the calling script. + + UiFlow2 Code Block: + + |flush.png| + + MicroPython Code Block: + + .. code-block:: python + + uart1.flush() + + + .. method:: UART.txdone() + + Tells whether all data has been sent or no data transfer is happening. In this case, + it returns ``True``. If a data transmission is ongoing it returns ``False``. + + .. note:: + + For the rp2, esp8266 and nrf ports the call may return ``True`` even if the last byte + of a transfer is still being sent. If required, a one character wait time has to be + added in the calling script. + + UiFlow2 Code Block: + + |txdone.png| + + MicroPython Code Block: + + .. code-block:: python + + print(uart1.txdone()) + + + .. method:: UART.irq(handler=None, trigger=0, hard=False) + + Configure an interrupt handler to be called when a UART event occurs. + + :param func handler: an optional function to be called when the interrupt event triggers. The handler must take exactly one argument which is the ``UART`` instance. + + :param int trigger: configures the event(s) which can generate an interrupt. Possible values are a mask of one or more of the following: + + - ``UART.IRQ_RXIDLE`` interrupt after receiving at least one character and then the RX line goes idle. + + - ``UART.IRQ_RX`` interrupt after each received character. + + - ``UART.IRQ_TXIDLE`` interrupt after or while the last character(s) of a message are or have been sent. + + - ``UART.IRQ_BREAK`` interrupt when a break state is detected at RX + + :param bool hard: if true a hardware interrupt is used. This reduces the delay between the pin change and the handler being called. Hard interrupt handlers may not allocate memory; see :ref:`isr_rules`. + + :return: Returns an irq object. + + Due to limitations of the hardware not all trigger events are available on all ports. + + .. table:: Availability of triggers + :align: center + + ============== ========== ====== ========== ========= + Port / Trigger IRQ_RXIDLE IRQ_RX IRQ_TXIDLE IRQ_BREAK + ============== ========== ====== ========== ========= + CC3200 yes + ESP32 yes yes yes + MIMXRT yes yes + NRF yes yes + RENESAS-RA yes yes + RP2 yes yes yes + SAMD yes yes yes + STM32 yes yes + ============== ========== ====== ========== ========= + + .. note:: + - The ESP32 port does not support the option hard=True. + + - The rp2 port's UART.IRQ_TXIDLE is only triggered when the message + is longer than 5 characters and the trigger happens when still 5 characters + are to be sent. + + - The rp2 port's UART.IRQ_BREAK needs receiving valid characters for triggering + again. + + - The SAMD port's UART.IRQ_TXIDLE is triggered while the last character is sent. + + - On STM32F4xx MCU's, using the trigger UART.IRQ_RXIDLE the handler will be called once + after the first character and then after the end of the message, when the line is + idle. + + + Availability: cc3200, esp32, mimxrt, nrf, renesas-ra, rp2, samd, stm32. + + + .. data:: UART.RTS + .. data:: UART.CTS + + Flow control options. + + + .. data:: UART.MODE_UART + .. data:: UART.MODE_RS485_HALF_DUPLEX + .. data:: UART.MODE_IRDA + .. data:: UART.MODE_RS485_COLLISION_DETECT + .. data:: UART.MODE_RS485_APP_CTRL + + UART mode options. + + + .. data:: UART.IRQ_RXIDLE + .. data:: UART.IRQ_RX + .. data:: UART.IRQ_TXIDLE + .. data:: UART.IRQ_BREAK + + IRQ trigger sources. diff --git a/docs/en/refs/hardware.pwr485.ref b/docs/en/refs/hardware.pwr485.ref new file mode 100644 index 00000000..ef637f0a --- /dev/null +++ b/docs/en/refs/hardware.pwr485.ref @@ -0,0 +1,12 @@ +.. |init.png| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/pwr485/init.png + +.. |stamplc_ehco_example.png| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/pwr485/example_echo.png + +.. |stamplc_ehco_example.m5f2| raw:: html + + + stamplc_ehco_example.m5f2 + diff --git a/docs/en/refs/hardware.uart.ref b/docs/en/refs/hardware.uart.ref new file mode 100644 index 00000000..923d926c --- /dev/null +++ b/docs/en/refs/hardware.uart.ref @@ -0,0 +1,29 @@ +.. |init.png| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/uart/init.png +.. |any.png| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/uart/any.png +.. |deinit.png| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/uart/deinit.png +.. |setup.png| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/uart/init_setup.png +.. |read_all.png| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/uart/read_all.png +.. |read_bytes.png| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/uart/read_bytes.png +.. |read_raw_data.png| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/uart/read_raw_data.png +.. |readline.png| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/uart/readline.png +.. |readinto.png| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/uart/readinto.png +.. |write.png| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/uart/write.png +.. |write1.png| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/uart/write1.png +.. |write_line.png| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/uart/write_line.png +.. |write_list.png| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/uart/write_list.png +.. |write_raw_data.png| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/uart/write_raw_data.png +.. |write_raw_data_list.png| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/uart/write_raw_data1.png +.. |txdone.png| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/uart/txdone.png +.. |flush.png| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/uart/flush.png +.. |sendbreak.png| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/uart/sendbreak.png + +.. |cores3_echo_exmaple.png| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/uart/example_echo.png + +.. |cores3_echo_exmaple.m5f2| raw:: html + + + cores3_echo_exmaple.m5f2 + diff --git a/docs/locales/zh_CN/LC_MESSAGES/hardware/pwr485.po b/docs/locales/zh_CN/LC_MESSAGES/hardware/pwr485.po new file mode 100644 index 00000000..068ff222 --- /dev/null +++ b/docs/locales/zh_CN/LC_MESSAGES/hardware/pwr485.po @@ -0,0 +1,140 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2016 - 2025 M5Stack Technology Co., Ltd +# This file is distributed under the same license as the UIFlow2 Programming +# Guide package. +# FIRST AUTHOR , 2025. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: UIFlow2 Programming Guide \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-03-20 09:24+0800\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: zh_CN\n" +"Language-Team: zh_CN \n" +"Plural-Forms: nplurals=1; plural=0;\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.16.0\n" + +#: ../../en/hardware/pwr485.rst:2 e723eb5673fc4269b7e4a8fe6e1896cd +msgid "PWR485" +msgstr "" + +#: ../../en/hardware/pwr485.rst:6 9e9bfb04ab414fb6a6d8d6fcb6da2fdb +msgid "" +"The PWR485 is a RS485 interface that can be used to communicate with " +"other devices." +msgstr "PWR485是RS485接口,可用于与其他设备通信。" + +#: ../../en/hardware/pwr485.rst:8 3563629b23f04c86a739668b0c6bcbcf +msgid "The following are the host's support for PWR485:" +msgstr "以下是主机对PWR485的支持:" + +#: ../../en/hardware/pwr485.rst:15 f54bdf5e7deb43e99a8795aebfee23f1 +msgid "Controller" +msgstr "主机" + +#: ../../en/hardware/pwr485.rst:15 43a54a8dc3f74990a502af3b8103edae +msgid "Status" +msgstr "状态" + +#: ../../en/hardware/pwr485.rst:17 f1d4ddc6b2964fce96d4b22774cea493 +msgid "StampPLC" +msgstr "" + +#: ../../en/hardware/pwr485.rst:17 9def5763653f41c88a6ed1f537f527ca +msgid "|S|" +msgstr "" + +#: ../../en/hardware/pwr485.rst:24 99c72985bf5346eca906856366cef7aa +msgid "UiFlow2 Example" +msgstr "UiFlow2 应用示例" + +#: ../../en/hardware/pwr485.rst:27 ../../en/hardware/pwr485.rst:50 +#: f4e12bcf47e741d0a344895041fa5788 +msgid "Echo" +msgstr "回声" + +#: ../../en/hardware/pwr485.rst:29 20265e715eb048baaa1c394d0abc4608 +msgid "Open the |stamplc_ehco_example.m5f2| project in UiFlow2." +msgstr "在 UiFlow2 中打开 |stamplc_ehco_example.m5f2| 项目。" + +#: ../../en/hardware/pwr485.rst:31 ../../en/hardware/pwr485.rst:52 +#: a23f5ca3242d4cdda8a71f1791abf4f9 +msgid "" +"This example demonstrates how to utilize PWR485 interfaces by echoing " +"back to the sender any data received on configured PWR485." +msgstr "此示例演示了如何利用 PWR485 接口,向发送方回显在配置的 PWR485 上收到的任何数据。" + +#: ../../en/hardware/pwr485.rst:34 ../../en/hardware/pwr485.rst:55 +#: 859c5e575a9c43208f8f62874ae3ad0d +msgid "By pressing different keys, different characters are sent." +msgstr "通过按下不同的键,可以发送不同的字符。" + +#: ../../en/hardware/pwr485.rst:36 2089d0a61f934a33b80f8892b5f5f1c5 +#: hardware.pwr485.PWR485:5 of +msgid "UiFlow2 Code Block:" +msgstr "UiFlow2 代码块:" + +#: ../../en/hardware/pwr485.rst:38 944bdda494d6450c86480d37f9408668 +msgid "|stamplc_ehco_example.png|" +msgstr "" + +#: ../../en/refs/hardware.pwr485.ref:3 266c850017794b1bb910605d73f0496d +msgid "stamplc_ehco_example.png" +msgstr "" + +#: ../../en/hardware/pwr485.rst:40 ../../en/hardware/pwr485.rst:63 +#: dcd7421891af4594b714a3218e4b514f +msgid "Example output:" +msgstr "示例输出:" + +#: ../../en/hardware/pwr485.rst:42 ../../en/hardware/pwr485.rst:65 +#: a984955b2f8e4daca12f1d5759793240 +msgid "None" +msgstr "" + +#: ../../en/hardware/pwr485.rst:47 ../../en/hardware/pwr485.rst:69 +#: 3599003a9c5e4094833c450325b5e47b +msgid "MicroPython Example" +msgstr "MicroPython 应用示例" + +#: ../../en/hardware/pwr485.rst:57 3599003a9c5e4094833c450325b5e47b +#: hardware.pwr485.PWR485:9 of +msgid "MicroPython Code Block:" +msgstr "MicroPython 代码块:" + +#: ../../en/hardware/pwr485.rst:73 9daa553c3dd54fca9bdd4a0e9e103e80 +msgid "**API**" +msgstr "API参考" + +#: ../../en/hardware/pwr485.rst:76 854f22f110fd4378ab2a73d05ee690cf +msgid "class PWR485" +msgstr "" + +#: 0d03b5990aa2447bb4daefb5092e9fcf hardware.pwr485.PWR485:1 of +msgid "Bases: :py:class:`object`" +msgstr "" + +#: abe30a8b7d1b4848b89d504f38001cb2 hardware.pwr485.PWR485:1 of +msgid "Construct a PWR485 object of the given id." +msgstr "构造给定 id 的 PWR485 对象。" + +#: 93b720b4a63e41259d45e4a226e55b8b hardware.pwr485.PWR485:3 of +msgid "" +"PWR485 class inherits UART class, See :ref:`hardware.UART " +"` for more details." +msgstr "PWR485 类继承了 UART 类,更多详细信息请参阅 :ref:`hardware.UART ` 。" + +#: 20ae3f89e8df46ab962ee45c26c6779b hardware.pwr485.PWR485:7 of +msgid "|init.png|" +msgstr "" + +#: ../../en/refs/hardware.pwr485.ref:1 efaefb662e524bf68f3bf96984df3e6d +msgid "init.png" +msgstr "" + diff --git a/docs/locales/zh_CN/LC_MESSAGES/hardware/uart.po b/docs/locales/zh_CN/LC_MESSAGES/hardware/uart.po new file mode 100644 index 00000000..057d173a --- /dev/null +++ b/docs/locales/zh_CN/LC_MESSAGES/hardware/uart.po @@ -0,0 +1,763 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2016 - 2025 M5Stack Technology Co., Ltd +# This file is distributed under the same license as the UIFlow2 Programming +# Guide package. +# FIRST AUTHOR , 2025. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: UIFlow2 Programming Guide\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-03-19 16:56+0800\n" +"PO-Revision-Date: 2025-03-19 17:35+0800\n" +"Last-Translator: \n" +"Language-Team: zh_CN \n" +"Language: zh_CN\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=1; plural=0;\n" +"Generated-By: Babel 2.16.0\n" +"X-Generator: Poedit 3.5\n" + +#: ../../en/hardware/uart.rst:2 c9e6fa0b479b40d5a17d03cd54bf4bd6 +msgid "UART" +msgstr "" + +#: ../../en/hardware/uart.rst:6 058f9f563c0d40fba9a8741bf8894152 +msgid "" +"UART implements the standard UART/USART duplex serial communications protocol. " +"At the physical level it consists of 2 lines: RX and TX. The unit of " +"communication is a character (not to be confused with a string character) which " +"can be 8 or 9 bits wide." +msgstr "" +"UART 实现标准 UART/USART 双工串行通信协议。在物理层面上,它由 2 条线路组成:RX " +"和 TX。通信单位是一个字符(不要与字符串字符混淆),可以是 8 位或 9 位宽。" + +#: ../../en/hardware/uart.rst:9 1e0a61c84b8a417a973abbc26c04d8eb +msgid "UiFlow2 Example" +msgstr "UiFlow2 应用示例" + +#: ../../en/hardware/uart.rst:12 ../../en/hardware/uart.rst:33 +#: 2c74db7904234f789ebeba89040d766b 69328d153f534ac49f130af1f5476876 +msgid "Echo" +msgstr "回声" + +#: ../../en/hardware/uart.rst:14 aabcb4827d12466e9994414244ccb8dc +msgid "Open the |cores3_echo_exmaple.m5f2| project in UiFlow2." +msgstr "在 UiFlow2 中打开 |cores3_echo_exmaple.m5f2| 项目。" + +#: ../../en/hardware/uart.rst:16 ../../en/hardware/uart.rst:35 +#: 09558c3828a1440684d391611c200ef7 3c788fd9713641c981222ee9ea73ec63 +msgid "" +"This example demonstrates how to utilize UART interfaces by echoing back to the " +"sender any data received on configured UART." +msgstr "" +"此示例演示了如何利用 UART 接口,通过将配置的 UART 上接收到的任何数据回传给发送" +"方。" + +#: ../../en/hardware/uart.rst:19 ../../en/hardware/uart.rst:61 +#: ../../en/hardware/uart.rst:135 ../../en/hardware/uart.rst:154 +#: ../../en/hardware/uart.rst:181 ../../en/hardware/uart.rst:201 +#: ../../en/hardware/uart.rst:225 ../../en/hardware/uart.rst:245 +#: ../../en/hardware/uart.rst:266 ../../en/hardware/uart.rst:292 +#: ../../en/hardware/uart.rst:314 ../../en/hardware/uart.rst:336 +#: 1a8478ee7e0e45cc96c621b8ee964c4a 1b49efb3754e43b5b51e6c7d48ddf812 +#: 22fbb19a3b1b413f97da27b653e906b5 +msgid "UiFlow2 Code Block:" +msgstr "UiFlow2 代码块:" + +#: ../../en/hardware/uart.rst:21 9359e70602da4d51acb3e96240c9e484 +msgid "|cores3_echo_exmaple.png|" +msgstr "" + +#: ../../en/refs/hardware.uart.ref:20 dfc9652fea594f8b9164f84b0bf04d58 +msgid "cores3_echo_exmaple.png" +msgstr "" + +#: ../../en/hardware/uart.rst:23 ../../en/hardware/uart.rst:44 +#: 80938af2d29a478c9445e32a02e98199 f8aa583af1834a76bfb01250d9acb73f +msgid "Example output:" +msgstr "示例输出:" + +#: ../../en/hardware/uart.rst:25 ../../en/hardware/uart.rst:46 +#: 70d9f18b13b548b4bc9380c02f21b8d8 afd166f3f16d4604a705d212dde7bf7e +msgid "None" +msgstr "" + +#: ../../en/hardware/uart.rst:30 e76a3954646b47f2ab1e1ff7ce7f643c +msgid "MicroPython Example" +msgstr "MicroPython 应用示例" + +#: ../../en/hardware/uart.rst:38 ../../en/hardware/uart.rst:65 +#: ../../en/hardware/uart.rst:139 ../../en/hardware/uart.rst:158 +#: ../../en/hardware/uart.rst:185 ../../en/hardware/uart.rst:209 +#: ../../en/hardware/uart.rst:229 ../../en/hardware/uart.rst:249 +#: ../../en/hardware/uart.rst:280 ../../en/hardware/uart.rst:296 +#: ../../en/hardware/uart.rst:318 ../../en/hardware/uart.rst:340 +#: 0b501a67b2d14e43b43a193fb7a66beb 5b7a589813cd486a93264fbd68353d17 +#: 94e4a13a17c74a7f94e9c839c23128d4 9e506085ae84466f96d314ea4f0d5294 +msgid "MicroPython Code Block:" +msgstr "MicroPython 代码块:" + +#: ../../en/hardware/uart.rst:50 dcd3898f23234236b65a1f8f4ff12bba +msgid "**API**" +msgstr "API参考" + +#: ../../en/hardware/uart.rst:53 58008f35ce9046ecb45291f78a246044 +msgid "class UART" +msgstr "" + +#: ../../en/hardware/uart.rst:57 9cbb2a823b1d4336add2a222de95639a +msgid "Construct a UART object of the given id." +msgstr "构造给定 id 的 UART 对象。" + +#: ../../en/hardware/uart.rst:59 fc3b3f0b2f8643fd94f873c4abcb1e33 +msgid "For more parameters, please refer to init." +msgstr "更多参数请参考 init 。" + +#: ../../en/hardware/uart.rst:63 865c7d86ce17452ba71b148b7dd0e4f2 +msgid "|init.png|" +msgstr "" + +#: ../../en/refs/hardware.uart.ref:1 322f48f8569b4a1bb7733f955876e62e +msgid "init.png" +msgstr "" + +#: ../../en/hardware/uart.rst:76 ca5ad9f8539f438e85b2d896bdcaea89 +msgid "Initialise the UART bus with the given parameters." +msgstr "使用给定的参数初始化 UART 总线。" + +#: ../../en/hardware/uart.rst 4ba37cbdf91e47ab899e555e8218e9ec +#: 7dd8e08af7654fe1b0ef7bf11b76bc30 +msgid "Parameters" +msgstr "" + +#: ../../en/hardware/uart.rst:78 bc5b8fd12b89427e8d0e39c056a5e299 +msgid "the clock rate." +msgstr "时钟频率。" + +#: ../../en/hardware/uart.rst:79 93eb316ccafd41e2b11bf06249f60634 +msgid "the number of bits per character, 7, 8 or 9." +msgstr "每个字符的位数,7、8 或 9。" + +#: ../../en/hardware/uart.rst:80 e19f54a5f0e14ed5a1cd03e2e9d32280 +msgid "the parity, ``None``, 0 (even) or 1 (odd)." +msgstr "奇偶校验,“无”,0(偶数)或 1(奇数)。" + +#: ../../en/hardware/uart.rst:82 ef2b43e3d7c34afb842798a10352a2d1 +msgid "the number of stop bits, 1 or 2." +msgstr "停止位数,1 或 2。" + +#: ../../en/hardware/uart.rst 6f28e9e7ddd0427ca459085aaddd7d94 +msgid "Keyword Arguments" +msgstr "" + +#: ../../en/hardware/uart.rst:83 8e7afb4e5bd24d9fb272077352e39a69 +msgid "the TX pin to use." +msgstr "要使用的 TX 引脚。" + +#: ../../en/hardware/uart.rst:85 2ee3f38368fe48ad8be0abbe1936b29c +msgid "the RX pin to use." +msgstr "要使用的 RX 引脚。" + +#: ../../en/hardware/uart.rst:87 19888ce59649405f8ef5361f13ce4952 +msgid "the RTS (output) pin to use for hardware receive flow control." +msgstr "用于硬件接收流控制的 RTS(输出)引脚。" + +#: ../../en/hardware/uart.rst:89 ed34481d2eeb4c94a0eaeada7355c141 +msgid "the CTS (input) pin to use for hardware transmit flow control." +msgstr "用于硬件传输流控制的 CTS(输入)引脚。" + +#: ../../en/hardware/uart.rst:91 2eafecf1f9fd4fffa5866c702876d543 +msgid "the length in characters of the TX buffer." +msgstr "TX 缓冲区的长度(以字符为单位)。" + +#: ../../en/hardware/uart.rst:92 8dee487145f84312bf259b3c9fa90bb0 +msgid "the length in characters of the RX buffer." +msgstr "RX 缓冲区的长度(以字符为单位)。" + +#: ../../en/hardware/uart.rst:93 2198a824bc1d41238c3d3fcf10f26a01 +msgid "the time to wait for the first character (in ms)." +msgstr "等待第一个字符的时间(以毫秒为单位)。" + +#: ../../en/hardware/uart.rst:94 9c7752c6fb21436d9b35b3afeba17f91 +msgid "the time to wait between characters (in ms)." +msgstr "字符之间等待的时间(以毫秒为单位)。" + +#: ../../en/hardware/uart.rst:95 3a8573dc954b41d3802ebb99b49be0d3 +msgid "" +"which lines to invert. - ``0`` will not invert lines (idle state of both lines " +"is logic high). - ``UART.INV_TX`` will invert TX line (idle state of TX line " +"now logic low). - ``UART.INV_RX`` will invert RX line (idle state of RX line " +"now logic low). - ``UART.INV_TX | UART.INV_RX`` will invert both lines (idle " +"state at logic low)." +msgstr "" + +#: ../../en/hardware/uart.rst:95 d36ae0d1bfd441ec870f2510e88c488f +msgid "which lines to invert." +msgstr "要反转哪些线。" + +#: ../../en/hardware/uart.rst:97 79ac7a44eb734c9bbf678661cd7adb07 +msgid "``0`` will not invert lines (idle state of both lines is logic high)." +msgstr "``0`` 不会反转线路(两条线路的空闲状态都为逻辑高电平)。" + +#: ../../en/hardware/uart.rst:99 243a2363667b488582f1dc2161bf907f +msgid "``UART.INV_TX`` will invert TX line (idle state of TX line now logic low)." +msgstr "``UART.INV_TX`` 将反转 TX 线(TX 线的空闲状态现在为逻辑低)。" + +#: ../../en/hardware/uart.rst:101 cb7699f638764209aff6afb3105b170c +msgid "``UART.INV_RX`` will invert RX line (idle state of RX line now logic low)." +msgstr "``UART.INV_RX`` 将反转 RX 线(RX 线的空闲状态现在为逻辑低)。" + +#: ../../en/hardware/uart.rst:103 a528fea1d7094c3e95e050fa448b1e7a +msgid "" +"``UART.INV_TX | UART.INV_RX`` will invert both lines (idle state at logic low)." +msgstr "``UART.INV_TX | UART.INV_RX`` 将反转两条线(逻辑低时的空闲状态)。" + +#: ../../en/hardware/uart.rst:105 42cd902cdf8a470bba73c0e27e04f8f4 +msgid "" +"which hardware flow control signals to use. The value is a bitmask. - ``0`` " +"will ignore hardware flow control signals. - ``UART.RTS`` will enable receive " +"flow control by using the RTS output pin to signal if the receive FIFO has " +"sufficient space to accept more data. - ``UART.CTS`` will enable transmit flow " +"control by pausing transmission when the CTS input pin signals that the " +"receiver is running low on buffer space. - ``UART.RTS | UART.CTS`` will enable " +"both, for full hardware flow control." +msgstr "" + +#: ../../en/hardware/uart.rst:105 7e5f162f54734f40bdf4d605b63e3a63 +msgid "which hardware flow control signals to use. The value is a bitmask." +msgstr "使用哪些硬件流控制信号。该值是一个位掩码。" + +#: ../../en/hardware/uart.rst:107 b583ef965e944140a37e3a545966f0cf +msgid "``0`` will ignore hardware flow control signals." +msgstr "``0`` 将忽略硬件流控制信号。" + +#: ../../en/hardware/uart.rst:109 a1713dfe88c14407876baf255142b964 +msgid "" +"``UART.RTS`` will enable receive flow control by using the RTS output pin to " +"signal if the receive FIFO has sufficient space to accept more data." +msgstr "" +"``UART.RTS`` 将通过使用 RTS 输出引脚来发出信号以表明接收 FIFO 是否有足够的空间来" +"接受更多数据,从而实现接收流控制。" + +#: ../../en/hardware/uart.rst:111 e046b3b71cbd4306b531728360c56bfa +msgid "" +"``UART.CTS`` will enable transmit flow control by pausing transmission when the " +"CTS input pin signals that the receiver is running low on buffer space." +msgstr "" +"``UART.CTS`` 会通过在 CTS 输入引脚信号表明接收器缓冲区空间不足时暂停传输,来启用" +"传输流量控制。" + +#: ../../en/hardware/uart.rst:113 a528fea1d7094c3e95e050fa448b1e7a +msgid "``UART.RTS | UART.CTS`` will enable both, for full hardware flow control." +msgstr "``UART.RTS | UART.CTS`` 将启用两者,实现完整的硬件流控制。" + +#: ../../en/hardware/uart.rst:115 adc065f942394300bcc5295461637710 +msgid "" +"the mode of the UART. The value is a bitmask. - ``UART.MODE_UART`` specifies " +"regular UART mode. - ``UART.MODE_RS485_HALF_DUPLEX`` specifies half duplex " +"RS485 UART mode control by RTS pin. - ``UART.MODE_IRDA`` specifies IRDA UART " +"mode. - ``UART.MODE_RS485_COLLISION_DETECT`` specifies RS485 collision " +"detection UART mode (used for test purposes). - ``UART.MODE_RS485_APP_CTRL`` " +"specifies application control RS485 UART mode (used for test purposes)." +msgstr "" + +#: ../../en/hardware/uart.rst:115 6c3cadbdec5548e0a4b462fde3ca07ba +msgid "the mode of the UART. The value is a bitmask." +msgstr "UART 的模式。该值是一个位掩码。" + +#: ../../en/hardware/uart.rst:117 1c2abeac4d9c434fba0bb0a3446a26a4 +msgid "``UART.MODE_UART`` specifies regular UART mode." +msgstr "``UART.MODE_UART`` 指定常规 UART 模式。" + +#: ../../en/hardware/uart.rst:119 983f1fcf8d934ba980ab1763106b89a6 +msgid "" +"``UART.MODE_RS485_HALF_DUPLEX`` specifies half duplex RS485 UART mode control " +"by RTS pin." +msgstr "" +"``UART.MODE_RS485_HALF_DUPLEX`` 指定通过 RTS 引脚控制半双工 RS485 UART 模式。" + +#: ../../en/hardware/uart.rst:121 8cfeb5cab21e4efbbb02a62c9b8dcd9d +msgid "``UART.MODE_IRDA`` specifies IRDA UART mode." +msgstr "``UART.MODE_IRDA`` 指定IRDA UART模式。" + +#: ../../en/hardware/uart.rst:123 8e6046a526bb4be5b928f6d99bde4cf0 +msgid "" +"``UART.MODE_RS485_COLLISION_DETECT`` specifies RS485 collision detection UART " +"mode (used for test purposes)." +msgstr "" +"``UART.MODE_RS485_COLLISION_DETECT`` 指定 RS485 碰撞检测 UART 模式(用于测试目" +"的)。" + +#: ../../en/hardware/uart.rst:125 34177ae18b404d6a8954823f1e7f2322 +msgid "" +"``UART.MODE_RS485_APP_CTRL`` specifies application control RS485 UART mode " +"(used for test purposes)." +msgstr "" +"``UART.MODE_RS485_APP_CTRL`` 指定应用程序控制RS485 UART模式(用于测试目的)。" + +#: ../../en/hardware/uart.rst:128 94c32826c9d94347b1f95b44a916926f +msgid "" +"It is possible to call ``init()`` multiple times on the same object in order to " +"reconfigure UART on the fly. That allows using single UART peripheral to serve " +"different devices attached to different GPIO pins. Only one device can be " +"served at a time in that case. Also do not call ``deinit()`` as it will prevent " +"calling ``init()`` again." +msgstr "" +"可以对同一对象多次调用 ``init()`` ,以便动态重新配置 UART。这样可以使用单个 " +"UART 外设来为连接到不同 GPIO 引脚的不同设备提供服务。在这种情况下,一次只能为一" +"个设备提供服务。另外,不要调用 ``deinit()`` ,因为它会阻止再次调用 ``init()`` 。" + +#: ../../en/hardware/uart.rst:137 29d6f5faf9e7465f88f31dc9a1aba8f9 +msgid "|setup.png|" +msgstr "" + +#: ../../en/refs/hardware.uart.ref:4 a032f8b224794fc1bd957eddb6ac7e44 +msgid "setup.png" +msgstr "" + +#: ../../en/hardware/uart.rst:148 a6b3966e947c4feea6e0bd818db7bbc0 +msgid "Turn off the UART bus." +msgstr "关闭 UART 总线。" + +#: ../../en/hardware/uart.rst:151 f2e9c64097c340f78e56ab75bfedb7c2 +msgid "" +"You will not be able to call ``init()`` on the object after ``deinit()``. A new " +"instance needs to be created in that case." +msgstr "" +"在 ``deinit()`` 之后,您将无法在对象上调用 ``init()`` 。在这种情况下,需要创建一" +"个新实例。" + +#: ../../en/hardware/uart.rst:156 0e60d48de3484204aea2a1e3138cc423 +msgid "|deinit.png|" +msgstr "" + +#: ../../en/refs/hardware.uart.ref:3 4431de9efabc4bfa89bc685782ac301d +msgid "deinit.png" +msgstr "" + +#: ../../en/hardware/uart.rst:167 3b4b17b4d99d4d0d9af9e60ea14652c8 +msgid "" +"Returns an integer counting the number of characters that can be read without " +"blocking. It will return 0 if there are no characters available and a positive " +"number if there are characters. The method may return 1 even if there is more " +"than one character available for reading." +msgstr "" +"返回一个整数,表示可以无阻塞读取的字符数。如果没有可用字符,则返回 0;如果有字" +"符,则返回一个正数。即使有多个字符可供读取,该方法也可能返回 1。" + +#: ../../en/hardware/uart.rst 308149309cb54c5fa3fdb3463dfea327 +msgid "Returns" +msgstr "" + +#: ../../en/hardware/uart.rst:172 1dfc48cb46c947358c886f6268ddce54 +msgid "the number of characters available for reading." +msgstr "可读取的字符数。" + +#: ../../en/hardware/uart.rst 7c5a85310eb74248ad2efe723356532d +msgid "Return type" +msgstr "" + +#: ../../en/hardware/uart.rst:175 c6fd00ab266d4b5da9c463925ca3e6a6 +msgid "For more sophisticated querying of available characters use select.poll::" +msgstr "对于更复杂的可用字符查询,请使用 select.poll::" + +#: ../../en/hardware/uart.rst:183 7d81178c5c7544f8b497ef1e05ca2be1 +msgid "|any.png|" +msgstr "" + +#: ../../en/refs/hardware.uart.ref:2 4c539490ca97429aa642c46edd2190a3 +msgid "any.png" +msgstr "" + +#: ../../en/hardware/uart.rst:194 79c17ede995d4b759cc21c34dd9b306a +msgid "" +"Read characters. If ``nbytes`` is specified then read at most that many bytes, " +"otherwise read as much data as possible. It may return sooner if a timeout is " +"reached. The timeout is configurable in the constructor." +msgstr "" +"读取字符。如果指定了 ``nbytes``,则最多读取那么多字节,否则读取尽可能多的数据。" +"如果达到超时,它可能会更早返回。超时可以在构造函数中配置。" + +#: ../../en/hardware/uart.rst:198 563d788c834d4763aea3c772b2342a12 +msgid "a bytes object containing the bytes read in. Returns ``None`` on timeout." +msgstr "包含读入字节的字节对象。超时时返回 ``None`` 。" + +#: ../../en/hardware/uart.rst:203 f61aa243eecf49f9a5c64cb3b72dd7af +msgid "|read_all.png|" +msgstr "" + +#: ../../en/refs/hardware.uart.ref:5 7e03382437a64148a8a60a1fed2b5757 +msgid "read_all.png" +msgstr "" + +#: ../../en/hardware/uart.rst:205 e305d72c10a94f2cb06c10b4cc43f25e +msgid "|read_bytes.png|" +msgstr "" + +#: ../../en/refs/hardware.uart.ref:6 4236071b6dfd42df817da4f98912cb78 +msgid "read_bytes.png" +msgstr "" + +#: ../../en/hardware/uart.rst:207 89dca9bd6e904d908ceb7042e4059143 +msgid "|read_raw_data.png|" +msgstr "" + +#: ../../en/refs/hardware.uart.ref:7 7577647b24754a5ca33efff203233c2e +msgid "read_raw_data.png" +msgstr "" + +#: ../../en/hardware/uart.rst:218 c19d3bf2048842c9ae97ee427a3ef064 +msgid "" +"Read bytes into the ``buf``. If ``nbytes`` is specified then read at most that " +"many bytes. Otherwise, read at most ``len(buf)`` bytes. It may return sooner " +"if a timeout is reached. The timeout is configurable in the constructor." +msgstr "" +"将字节读入 ``buf`` 。如果指定了 ``nbytes`` ,则最多读取那么多字节。否则,最多读" +"取 ``len(buf)`` 个字节。如果达到超时,它可能会更早返回。超时可以在构造函数中配" +"置。" + +#: ../../en/hardware/uart.rst:222 53e38326e59f4535bce6824028c927ce +msgid "number of bytes read and stored into ``buf`` or ``None`` on timeout." +msgstr "超时时读取并存储到 ``buf`` 或 ``None`` 中的字节数。" + +#: ../../en/hardware/uart.rst:227 f5ddab64cd144a33b97bed3defc42be2 +msgid "|readinto.png|" +msgstr "" + +#: ../../en/refs/hardware.uart.ref:9 b3bbfd0012c1431888e5c0fb931d5d48 +msgid "readinto.png" +msgstr "" + +#: ../../en/hardware/uart.rst:239 cba434e48b084033a65c0d9b4822ca49 +msgid "" +"Read a line, ending in a newline character. It may return sooner if a timeout " +"is reached. The timeout is configurable in the constructor." +msgstr "" +"读取一行,以换行符结尾。如果达到超时,它可能会更快返回。超时可在构造函数中配置。" + +#: ../../en/hardware/uart.rst:242 0031cd6260874421b205fb82332f31f5 +msgid "the line read or ``None`` on timeout." +msgstr "超时时读取的行或 ``None`` 。" + +#: ../../en/hardware/uart.rst:247 f4202e63a05c4103bbf79e191a523778 +msgid "|readline.png|" +msgstr "" + +#: ../../en/refs/hardware.uart.ref:8 0ab0114f781a45b4ba5cf26b6564ef0e +msgid "readline.png" +msgstr "" + +#: ../../en/hardware/uart.rst:258 db13224d23dd452ba51b150337fcb0be +msgid "Write the buffer of bytes to the bus." +msgstr "将字节缓冲区写入总线。" + +#: ../../en/hardware/uart.rst:260 7c696fb90ff4407893f7a43c7520c35f +msgid "the buffer of bytes to write." +msgstr "要写入的字节缓冲区。" + +#: ../../en/hardware/uart.rst:263 64f9075fa8af4ba9904d0d24560940c2 +msgid "number of bytes written or ``None`` on timeout." +msgstr "超时时写入的字节数或 ``None`` 。" + +#: ../../en/hardware/uart.rst:268 4e35524f762c4bc99c27476af5687f3c +msgid "|write.png|" +msgstr "" + +#: ../../en/refs/hardware.uart.ref:10 4dcba7f057ad4198b83cdcb79cd82e06 +msgid "write.png" +msgstr "" + +#: ../../en/hardware/uart.rst:270 366a9461fcf9425c9cd1cf332f5626f4 +msgid "|write1.png|" +msgstr "" + +#: ../../en/refs/hardware.uart.ref:11 2e94a2f28aa4457e9fde09e6866b8bf9 +msgid "write1.png" +msgstr "" + +#: ../../en/hardware/uart.rst:272 26464862aaaa449ca42c7eb74dc11d5a +msgid "|write_line.png|" +msgstr "" + +#: ../../en/refs/hardware.uart.ref:12 f074ec8c18814715999ef129a5736391 +msgid "write_line.png" +msgstr "" + +#: ../../en/hardware/uart.rst:274 e857dfe372214d8fbcba0c6d85942eb4 +msgid "|write_list.png|" +msgstr "" + +#: ../../en/refs/hardware.uart.ref:13 169ab24191c54278813a4f662be1e042 +msgid "write_list.png" +msgstr "" + +#: ../../en/hardware/uart.rst:276 2b8d053f53414a76b8bf160e0b12b24c +msgid "|write_raw_data.png|" +msgstr "" + +#: ../../en/refs/hardware.uart.ref:14 62ff8d2b589c45129ce7c9c553ea807c +msgid "write_raw_data.png" +msgstr "" + +#: ../../en/hardware/uart.rst:278 cfed3851eeff4935bbb9b76a9a9cac95 +msgid "|write_raw_data_list.png|" +msgstr "" + +#: ../../en/refs/hardware.uart.ref:15 8f44425b440c4919a5a6fca5930d6a42 +msgid "write_raw_data_list.png" +msgstr "" + +#: ../../en/hardware/uart.rst:289 d81eb47671224363976342d823d274c2 +msgid "" +"Send a break condition on the bus. This drives the bus low for a duration " +"longer than required for a normal transmission of a character." +msgstr "" +"在总线上发送中断条件。这会将总线驱动为低电平,持续时间比正常传输字符所需的时间更" +"长。" + +#: ../../en/hardware/uart.rst:294 e46573604e494ad6839adec65f026edb +msgid "|sendbreak.png|" +msgstr "" + +#: ../../en/refs/hardware.uart.ref:18 d0e4ca4e64a84002991d4696f1f45616 +msgid "sendbreak.png" +msgstr "" + +#: ../../en/hardware/uart.rst:305 98b226ec878d49d283ee710254032e61 +msgid "" +"Waits until all data has been sent. In case of a timeout, an exception is " +"raised. The timeout duration depends on the tx buffer size and the baud rate. " +"Unless flow control is enabled, a timeout should not occur." +msgstr "" +"等待所有数据发送完毕。如果发生超时,则会引发异常。超时持续时间取决于 tx 缓冲区大" +"小和波特率。除非启用了流量控制,否则不应发生超时。" + +#: ../../en/hardware/uart.rst:311 b7df0e9f59234f01abb7dae7a8f4d857 +msgid "" +"For the rp2, esp8266 and nrf ports the call returns while the last byte is " +"sent. If required, a one character wait time has to be added in the calling " +"script." +msgstr "" +"对于 rp2、esp8266 和 nrf 端口,调用将在发送最后一个字节时返回。如果需要,必须在" +"调用脚本中添加一个字符的等待时间。" + +#: ../../en/hardware/uart.rst:316 1f6eee01fbd04139b223016b07bc1cd0 +msgid "|flush.png|" +msgstr "" + +#: ../../en/refs/hardware.uart.ref:17 ac647656e6a94f448d12c85196959367 +msgid "flush.png" +msgstr "" + +#: ../../en/hardware/uart.rst:327 435cb9b3c6b641f4b36a947fc1a45e3d +msgid "" +"Tells whether all data has been sent or no data transfer is happening. In this " +"case, it returns ``True``. If a data transmission is ongoing it returns " +"``False``." +msgstr "" +"告知是否已发送所有数据或未发生任何数据传输。在这种情况下,它返回 ``True`` 。如果" +"正在进行数据传输,则返回 ``False`` 。" + +#: ../../en/hardware/uart.rst:332 c55285188c11419b9b86dbcc8c5b5094 +msgid "" +"For the rp2, esp8266 and nrf ports the call may return ``True`` even if the " +"last byte of a transfer is still being sent. If required, a one character wait " +"time has to be added in the calling script." +msgstr "" +"对于 rp2、esp8266 和 nrf 端口,即使传输的最后一个字节仍在发送,调用也可能返回 " +"``True`` 。如果需要,必须在调用脚本中添加一个字符的等待时间。" + +#: ../../en/hardware/uart.rst:338 8cb3368a190c41d1bc0272843b0b2ed0 +msgid "|txdone.png|" +msgstr "" + +#: ../../en/refs/hardware.uart.ref:16 d3c281a545c641d1b785b0eb29427680 +msgid "txdone.png" +msgstr "" + +#: ../../en/hardware/uart.rst:349 220381fbc94548e7ae7c796d8c02efd3 +msgid "Configure an interrupt handler to be called when a UART event occurs." +msgstr "配置一个中断处理程序,在发生 UART 事件时调用。" + +#: ../../en/hardware/uart.rst:351 d588d5a34a804b21ac81a92d9dcdec44 +msgid "" +"an optional function to be called when the interrupt event triggers. The " +"handler must take exactly one argument which is the ``UART`` instance." +msgstr "" +"中断事件触发时调用的可选函数。处理程序必须只接受一个参数,即 ``UART`` 实例。" + +#: ../../en/hardware/uart.rst:353 bd38829bec744ae582018e4e1733138d +msgid "" +"configures the event(s) which can generate an interrupt. Possible values are a " +"mask of one or more of the following: - ``UART.IRQ_RXIDLE`` interrupt after " +"receiving at least one character and then the RX line goes idle. - ``UART." +"IRQ_RX`` interrupt after each received character. - ``UART.IRQ_TXIDLE`` " +"interrupt after or while the last character(s) of a message are or have been " +"sent. - ``UART.IRQ_BREAK`` interrupt when a break state is detected at RX" +msgstr "" + +#: ../../en/hardware/uart.rst:353 222f77931a994e7d8b2c19940a9a5a75 +msgid "" +"configures the event(s) which can generate an interrupt. Possible values are a " +"mask of one or more of the following:" +msgstr "配置可生成中断的事件。可能的值是以下一个或多个掩码:" + +#: ../../en/hardware/uart.rst:355 0bd1d1c31f6d4298b30a76eb00c79e10 +msgid "" +"``UART.IRQ_RXIDLE`` interrupt after receiving at least one character and then " +"the RX line goes idle." +msgstr "" +"``UART.IRQ_RXIDLE`` 中断在接收到至少一个字符后,并且 RX 线路进入空闲状态时触发。" + +#: ../../en/hardware/uart.rst:357 b29b087b7d554243919011fb537dc7b1 +msgid "``UART.IRQ_RX`` interrupt after each received character." +msgstr "``UART.IRQ_RX`` 中断在每接收一个字符后触发。" + +#: ../../en/hardware/uart.rst:359 017f546d481f4e18a7d3c217b8bf6138 +msgid "" +"``UART.IRQ_TXIDLE`` interrupt after or while the last character(s) of a message " +"are or have been sent." +msgstr "" +"``UART.IRQ_TXIDLE`` 中断在消息的最后一个或多个字符发送完成后或发送过程中触发。" + +#: ../../en/hardware/uart.rst:361 92caf52f7207435f8b947b446de0b750 +msgid "``UART.IRQ_BREAK`` interrupt when a break state is detected at RX" +msgstr "``UART.IRQ_BREAK`` 中断在 RX 端检测到断开状态时触发。" + +#: ../../en/hardware/uart.rst:363 585576bcf9ba435796ab7967fe66d17e +msgid "" +"if true a hardware interrupt is used. This reduces the delay between the pin " +"change and the handler being called. Hard interrupt handlers may not allocate " +"memory; see :ref:`isr_rules`." +msgstr "" +"如果为真,则使用硬件中断。这减少了引脚变化和调用处理程序之间的延迟。硬中断处理程" +"序可能不会分配内存;请参阅 :ref:`isr_rules` 。" + +#: ../../en/hardware/uart.rst:365 0b9be4a1206a44d0a0446b400207e7ae +msgid "Returns an irq object." +msgstr "返回一个 irq 对象。" + +#: ../../en/hardware/uart.rst:367 768599bb0fa8434d92c4d9ab9924278b +msgid "" +"Due to limitations of the hardware not all trigger events are available on all " +"ports." +msgstr "由于硬件的限制,并非所有触发事件都可在所有端口上使用。" + +#: ../../en/hardware/uart.rst:369 af059429b9184a26a417d179f882796d +msgid "Availability of triggers" +msgstr "触发器的可用性" + +#: ../../en/hardware/uart.rst:373 5f507b6c72144f8f9ed751ebabd12d45 +msgid "Port / Trigger" +msgstr "端口 / 触发器" + +#: ../../en/hardware/uart.rst:373 e4297960108f4cfbbb131091323fe00b +msgid "IRQ_RXIDLE" +msgstr "" + +#: ../../en/hardware/uart.rst:373 40cb6a9a94d3430b8623eab4a43081f5 +msgid "IRQ_RX" +msgstr "" + +#: ../../en/hardware/uart.rst:373 0640ff146db4443cb74eccc06a9191cc +msgid "IRQ_TXIDLE" +msgstr "" + +#: ../../en/hardware/uart.rst:373 aa9ba8bc63334ab1a73144073836974f +msgid "IRQ_BREAK" +msgstr "" + +#: ../../en/hardware/uart.rst:375 7359fa18eaa94bcb80d047fe8cd00d29 +msgid "CC3200" +msgstr "" + +#: ../../en/hardware/uart.rst:375 ../../en/hardware/uart.rst:376 +#: ../../en/hardware/uart.rst:377 ../../en/hardware/uart.rst:378 +#: ../../en/hardware/uart.rst:379 ../../en/hardware/uart.rst:380 +#: ../../en/hardware/uart.rst:381 ../../en/hardware/uart.rst:382 +#: 5263ea413f8348f894e378c1cf3ec3a8 5a0650625271427f9b1d6fb568273b6b +#: 6a4ff38e6c484d93b725408a77c7c472 7656726a12a64ca9b8f094bbbd72b2ab +#: a36a8c3a79a84e64839e4e9a02ba1eae a83cccb77925437da295a4d47b3b55ec +msgid "yes" +msgstr "" + +#: ../../en/hardware/uart.rst:376 52897fde48d44c628ef13c0de2288ee1 +msgid "ESP32" +msgstr "" + +#: ../../en/hardware/uart.rst:377 b26614c24900456eb040ef634b38bbc2 +msgid "MIMXRT" +msgstr "" + +#: ../../en/hardware/uart.rst:378 627829eac3c7465ca8378e7f3a1db46e +msgid "NRF" +msgstr "" + +#: ../../en/hardware/uart.rst:379 4163dfc2777d49d8ac86797d97a866ce +msgid "RENESAS-RA" +msgstr "" + +#: ../../en/hardware/uart.rst:380 828c822769c14216a9627ed77d41b98b +msgid "RP2" +msgstr "" + +#: ../../en/hardware/uart.rst:381 e59e6ae678da4f6d99bd8552116c3bf7 +msgid "SAMD" +msgstr "" + +#: ../../en/hardware/uart.rst:382 65b36f41fd854acfb90e4068465e21f2 +msgid "STM32" +msgstr "" + +#: ../../en/hardware/uart.rst:386 f6a018ad1f62442dba51cffc32095630 +msgid "The ESP32 port does not support the option hard=True." +msgstr "ESP32 端口不支持选项 hard=True。" + +#: ../../en/hardware/uart.rst:388 fb9d6f11c1d14b949b9572491b65d48c +msgid "" +"The rp2 port's UART.IRQ_TXIDLE is only triggered when the message is longer " +"than 5 characters and the trigger happens when still 5 characters are to be " +"sent." +msgstr "" +"仅当消息长度超过5个字符时,rp2端口的UART.IRQ_TXIDLE才会触发,并且当仍有5个字符需" +"要发送时才会触发。" + +#: ../../en/hardware/uart.rst:392 dc9edc6c005a40f0a4b2572906cbc3cc +msgid "" +"The rp2 port's UART.IRQ_BREAK needs receiving valid characters for triggering " +"again." +msgstr "rp2端口的UART.IRQ_BREAK需要接收有效字符才能再次触发。" + +#: ../../en/hardware/uart.rst:395 a7bfc3e606e94a459fb41cbd99acc107 +msgid "" +"The SAMD port's UART.IRQ_TXIDLE is triggered while the last character is sent." +msgstr "发送最后一个字符时,触发SAND端口UART.IRQ IDLE。" + +#: ../../en/hardware/uart.rst:397 5fd2115e4be04b58bd65286d304a33cd +msgid "" +"On STM32F4xx MCU's, using the trigger UART.IRQ_RXIDLE the handler will be " +"called once after the first character and then after the end of the message, " +"when the line is idle." +msgstr "" +"在 STM32F4xx MCU 上,使用触发器 UART.IRQ_RXIDLE,当线路空闲时,处理程序将在第一" +"个字符之后调用一次,然后在消息结束后调用一次。" + +#: ../../en/hardware/uart.rst:402 6e8b260d703346639d29b12efc952246 +msgid "Availability: cc3200, esp32, mimxrt, nrf, renesas-ra, rp2, samd, stm32." +msgstr "有效性:cc3200、esp32、mimxrt、nrf、renesas-ra、rp2、samd、stm32。" + +#: ../../en/hardware/uart.rst:408 2a871de78ac640748cb8611fa2ac4736 +msgid "Flow control options." +msgstr "流量控制选项。" + +#: ../../en/hardware/uart.rst:417 2364adf071584878a7221beb11ef1e84 +msgid "UART mode options." +msgstr "UART 模式选项。" + +#: ../../en/hardware/uart.rst:425 92ca5ee3fe414a8ab41b508e998a925e +msgid "IRQ trigger sources." +msgstr "IRQ 触发源。" diff --git a/examples/hardware/pwr485/stamplc_ehco_example.m5f2 b/examples/hardware/pwr485/stamplc_ehco_example.m5f2 new file mode 100644 index 00000000..5baca202 --- /dev/null +++ b/examples/hardware/pwr485/stamplc_ehco_example.m5f2 @@ -0,0 +1 @@ +{"version":"V2.0","versionNumber":"V2.2.3","type":"stamplc","components":[{"name":"screen","type":"screen","layer":0,"screenId":"builtin","screenName":"","id":"__stamplc_screen","createTime":1742377217742,"x":0,"y":0,"width":240,"height":135,"backgroundColor":"#000000","size":0,"isSelected":true},{"name":"label0","type":"label","layer":1,"screenId":"builtin","screenName":"","id":"yyrS%2t@IqZG8#L=","createTime":1742377311674,"x":69,"y":61,"color":"#ffffff","backgroundColor":"#222222","text":"label0","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false}],"resources":[{"hardware":["hardware_button","hardware_pin_button","speaker","ir","hardware_plcio","hardware_pwr485"]},{"unit":["unit_iso485"]}],"units":[{"type":"unit_iso485","name":"iso485_0","portList":["A","C","Custom"],"portType":"C","userPort":[22,21],"id":"uokpFzYG9h-B6uQ&","createTime":1742377265461,"initBlockId":",l7-t4~v+u@#=,WW:VSv"}],"hats":[],"bases":[],"i2cs":[],"blockly":"truepwr485_0false21152008None1039iso485_0false11152008None1trueiso485_0label0Labeliso485_0BtnBWAS_CLICKEDpwr485_0BBtnAWAS_CLICKEDpwr485_0ABtnCWAS_CLICKEDpwr485_0C","screen":[{"simulationName":"Built-in","type":"builtin","width":240,"height":135,"scale":0.49,"screenName":"","blockId":"","screenColorType":0,"id":"builtin","createTime":1742377217741}],"logicWhenNum":0,"customList":[]} \ No newline at end of file diff --git a/examples/hardware/pwr485/stamplc_ehco_example.py b/examples/hardware/pwr485/stamplc_ehco_example.py new file mode 100644 index 00000000..f145885b --- /dev/null +++ b/examples/hardware/pwr485/stamplc_ehco_example.py @@ -0,0 +1,87 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import os, sys, io +import M5 +from M5 import * +from hardware import PWR485 +from unit import ISO485Unit + + +label0 = None +pwr485_0 = None +iso485_0 = None + + +def btnA_wasClicked_event(state): # noqa: N802 + global label0, pwr485_0, iso485_0 + pwr485_0.write("A") + + +def btnB_wasClicked_event(state): # noqa: N802 + global label0, pwr485_0, iso485_0 + pwr485_0.write("B") + + +def btnC_wasClicked_event(state): # noqa: N802 + global label0, pwr485_0, iso485_0 + pwr485_0.write("C") + + +def setup(): + global label0, pwr485_0, iso485_0 + + M5.begin() + label0 = Widgets.Label("label0", 69, 61, 1.0, 0xFFFFFF, 0x222222, Widgets.FONTS.DejaVu18) + + BtnA.setCallback(type=BtnA.CB_TYPE.WAS_CLICKED, cb=btnA_wasClicked_event) + BtnB.setCallback(type=BtnB.CB_TYPE.WAS_CLICKED, cb=btnB_wasClicked_event) + BtnC.setCallback(type=BtnC.CB_TYPE.WAS_CLICKED, cb=btnC_wasClicked_event) + + pwr485_0 = PWR485( + 2, + baudrate=115200, + bits=8, + parity=None, + stop=1, + tx=0, + rx=39, + rts=46, + mode=PWR485.MODE_RS485_HALF_DUPLEX, + ) + iso485_0 = ISO485Unit( + 1, + port=(4, 5), + baudrate=115200, + bits=8, + parity=None, + stop=1, + txbuf=256, + rxbuf=256, + timeout=0, + timeout_char=0, + invert=0, + flow=0, + ) + + +def loop(): + global label0, pwr485_0, iso485_0 + M5.update() + if iso485_0.any(): + label0.setText(str(iso485_0.read())) + + +if __name__ == "__main__": + try: + setup() + while True: + loop() + except (Exception, KeyboardInterrupt) as e: + try: + from utility import print_error_msg + + print_error_msg(e) + except ImportError: + print("please update to latest firmware") diff --git a/examples/hardware/uart/cores3_echo_example.py b/examples/hardware/uart/cores3_echo_example.py new file mode 100644 index 00000000..e48c5e3c --- /dev/null +++ b/examples/hardware/uart/cores3_echo_example.py @@ -0,0 +1,51 @@ +# SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import os, sys, io +import M5 +from M5 import * +from hardware import UART +import time + + +label0 = None +uart1 = None + + +i = None + + +def setup(): + global label0, uart1, i + + M5.begin() + Widgets.fillScreen(0x222222) + label0 = Widgets.Label("label0", 102, 85, 1.0, 0xFFFFFF, 0x222222, Widgets.FONTS.DejaVu18) + + uart1 = UART(1, baudrate=115200, bits=8, parity=None, stop=1, tx=9, rx=10) + i = 0 + + +def loop(): + global label0, uart1, i + M5.update() + uart1.write(i) + if uart1.any(): + label0.setText(str(uart1.read())) + i = (i if isinstance(i, (int, float)) else 0) + 1 + time.sleep(1) + + +if __name__ == "__main__": + try: + setup() + while True: + loop() + except (Exception, KeyboardInterrupt) as e: + try: + from utility import print_error_msg + + print_error_msg(e) + except ImportError: + print("please update to latest firmware") diff --git a/examples/hardware/uart/cores3_echo_exmaple.m5f2 b/examples/hardware/uart/cores3_echo_exmaple.m5f2 new file mode 100644 index 00000000..506271b0 --- /dev/null +++ b/examples/hardware/uart/cores3_echo_exmaple.m5f2 @@ -0,0 +1 @@ +{"version":"V2.0","versionNumber":"V2.2.3","type":"cores3","components":[{"name":"screen","type":"screen","layer":0,"screenId":"builtin","screenName":"","id":"__cores3_screen","createTime":1742351956878,"x":0,"y":0,"width":320,"height":240,"backgroundColor":"#222222","size":0,"isSelected":true},{"name":"label0","type":"label","layer":1,"screenId":"builtin","screenName":"","id":"uS!+4@nFqFgAF`mJ","createTime":1742353484773,"x":102,"y":85,"color":"#ffffff","backgroundColor":"#222222","text":"label0","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false}],"resources":[{"hardware":["hardware_button","hardware_pin_button","uart","imu","speaker","touch","als","mic"]}],"units":[],"hats":[],"bases":[],"i2cs":[],"blockly":"itrue1false1152008None1910i0true1i1label0Label1i11","screen":[{"simulationName":"Built-in","type":"builtin","width":320,"height":240,"scale":0.78,"screenName":"","blockId":"","screenColorType":0,"id":"builtin","createTime":1742351956876}],"logicWhenNum":0,"customList":[]} \ No newline at end of file diff --git a/m5stack/Makefile b/m5stack/Makefile index b8dd1d71..ed848adb 100644 --- a/m5stack/Makefile +++ b/m5stack/Makefile @@ -300,6 +300,7 @@ patch: $(call Package/patche,$(abspath ./../micropython),$(abspath ./patches/0005-micropython-fix-SDCard-16223.patch)) $(call Package/patche,$(abspath ./../micropython),$(abspath ./patches/0006-modtime-add-timezone-method.patch)) $(call Package/patche,$(abspath ./../micropython),$(abspath ./patches/0007-Add-set-default-netif-method.patch)) + $(call Package/patche,$(abspath ./../micropython),$(abspath ./patches/0008-machine_uart-add-uart-mode.patch)) $(call Package/patche,$(abspath $(IDF_PATH)),$(abspath ./patches/1003-WIP-Compatible-with-esp-adf-v2.7.diff)) $(call Package/patche,$(abspath ./components/M5Unified/M5Unified),$(abspath ./patches/2005-Support-LTR553.patch)) $(call Package/patche,$(abspath $(ADF_PATH)),$(abspath ./patches/3002-Modify-i2s_stream_idf5.patch)) @@ -308,6 +309,7 @@ patch: # Unapply patches unpatch: $(call Package/unpatche,$(abspath ./components/lv_bindings),$(abspath ./patches/0002_avoid_lv_bindings_compile_error.patch)) + $(call Package/unpatche,$(abspath ./../micropython),$(abspath ./patches/0008-machine_uart-add-uart-mode.patch)) $(call Package/unpatche,$(abspath ./../micropython),$(abspath ./patches/0007-Add-set-default-netif-method.patch)) $(call Package/unpatche,$(abspath ./../micropython),$(abspath ./patches/0006-modtime-add-timezone-method.patch)) $(call Package/unpatche,$(abspath ./../micropython),$(abspath ./patches/0005-micropython-fix-SDCard-16223.patch)) diff --git a/m5stack/libs/hardware/__init__.py b/m5stack/libs/hardware/__init__.py index a8d56978..c8b47e84 100644 --- a/m5stack/libs/hardware/__init__.py +++ b/m5stack/libs/hardware/__init__.py @@ -9,6 +9,7 @@ "MatrixKeyboard": "matrix_keyboard", "DigitalInput": "plcio", "Relay": "plcio", + "PWR485": "pwr485", "RFID": "rfid", "RGB": "rgb", "Rotary": "rotary", diff --git a/m5stack/libs/hardware/manifest.py b/m5stack/libs/hardware/manifest.py index 47741983..850f91f5 100644 --- a/m5stack/libs/hardware/manifest.py +++ b/m5stack/libs/hardware/manifest.py @@ -12,6 +12,7 @@ "ir.py", "matrix_keyboard.py", "plcio.py", + "pwr485.py", "rfid.py", "rgb.py", "rotary.py", diff --git a/m5stack/libs/hardware/pwr485.py b/m5stack/libs/hardware/pwr485.py new file mode 100644 index 00000000..eda2c85c --- /dev/null +++ b/m5stack/libs/hardware/pwr485.py @@ -0,0 +1,40 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import machine + + +class PWR485: + """Construct a PWR485 object of the given id. + + PWR485 class inherits UART class, See :ref:`hardware.UART ` for more details. + + UiFlow2 Code Block: + + |init.png| + + MicroPython Code Block: + + .. code-block:: python + + from hadrware import PWR485 + + pwr485_0 = PWR485(2, baudrate=115200, bits=8, parity=None, stop=1, tx=0, rx=39, rts=46, mode=PWR485.MODE_RS485_HALF_DUPLEX) + """ + + MODE_UART = machine.UART.MODE_UART + MODE_RS485_HALF_DUPLEX = machine.UART.MODE_RS485_HALF_DUPLEX + MODE_IRDA = machine.UART.MODE_IRDA + MODE_RS485_COLLISION_DETECT = machine.UART.MODE_RS485_COLLISION_DETECT + MODE_RS485_APP_CTRL = machine.UART.MODE_RS485_APP_CTRL + + def __new__( + cls, + id, + **kwargs, + ): + return machine.UART( + id, + **kwargs, + ) diff --git a/m5stack/patches/0008-machine_uart-add-uart-mode.patch b/m5stack/patches/0008-machine_uart-add-uart-mode.patch new file mode 100644 index 00000000..80b8daa8 --- /dev/null +++ b/m5stack/patches/0008-machine_uart-add-uart-mode.patch @@ -0,0 +1,43 @@ +Index: micropython/ports/esp32/machine_uart.c +=================================================================== +--- micropython.orig/ports/esp32/machine_uart.c ++++ micropython/ports/esp32/machine_uart.c +@@ -108,6 +108,11 @@ static const char *_parity_name[] = {"No + { MP_ROM_QSTR(MP_QSTR_IRQ_RX), MP_ROM_INT(UART_IRQ_RX) }, \ + { MP_ROM_QSTR(MP_QSTR_IRQ_RXIDLE), MP_ROM_INT(UART_IRQ_RXIDLE) }, \ + { MP_ROM_QSTR(MP_QSTR_IRQ_BREAK), MP_ROM_INT(UART_IRQ_BREAK) }, \ ++ { MP_ROM_QSTR(MP_QSTR_MODE_UART), MP_ROM_INT(UART_MODE_UART) }, \ ++ { MP_ROM_QSTR(MP_QSTR_MODE_RS485_HALF_DUPLEX), MP_ROM_INT(UART_MODE_RS485_HALF_DUPLEX) }, \ ++ { MP_ROM_QSTR(MP_QSTR_MODE_IRDA), MP_ROM_INT(UART_MODE_IRDA) }, \ ++ { MP_ROM_QSTR(MP_QSTR_MODE_RS485_COLLISION_DETECT), MP_ROM_INT(UART_MODE_RS485_COLLISION_DETECT) }, \ ++ { MP_ROM_QSTR(MP_QSTR_MODE_RS485_APP_CTRL), MP_ROM_INT(UART_MODE_RS485_APP_CTRL) }, \ + + static void uart_timer_callback(void *self_in) { + machine_timer_obj_t *self = self_in; +@@ -227,7 +232,7 @@ static void mp_machine_uart_print(const + } + + static void mp_machine_uart_init_helper(machine_uart_obj_t *self, size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { +- enum { ARG_baudrate, ARG_bits, ARG_parity, ARG_stop, ARG_tx, ARG_rx, ARG_rts, ARG_cts, ARG_txbuf, ARG_rxbuf, ARG_timeout, ARG_timeout_char, ARG_invert, ARG_flow }; ++ enum { ARG_baudrate, ARG_bits, ARG_parity, ARG_stop, ARG_tx, ARG_rx, ARG_rts, ARG_cts, ARG_txbuf, ARG_rxbuf, ARG_timeout, ARG_timeout_char, ARG_invert, ARG_flow, ARG_mode }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_baudrate, MP_ARG_INT, {.u_int = 0} }, + { MP_QSTR_bits, MP_ARG_INT, {.u_int = 0} }, +@@ -243,6 +248,7 @@ static void mp_machine_uart_init_helper( + { MP_QSTR_timeout_char, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1} }, + { MP_QSTR_invert, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1} }, + { MP_QSTR_flow, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1} }, ++ { MP_QSTR_mode, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = UART_MODE_UART} }, + }; + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); +@@ -405,6 +411,9 @@ static void mp_machine_uart_init_helper( + uint8_t uart_fifo_len = UART_FIFO_LEN; + #endif + check_esp_err(uart_set_hw_flow_ctrl(self->uart_num, self->flowcontrol, uart_fifo_len - uart_fifo_len / 4)); ++ ++ // Set mode ++ check_esp_err(uart_set_mode(self->uart_num, args[ARG_mode].u_int)); + } + + static mp_obj_t mp_machine_uart_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { From d4f0d58c695b35c760ada8801a7e396f07f00810 Mon Sep 17 00:00:00 2001 From: lbuque Date: Tue, 18 Mar 2025 15:09:43 +0800 Subject: [PATCH 025/322] docs: Update the documentation of diplay devices. Signed-off-by: lbuque --- docs/en/hardware/display.rst | 2 + docs/en/module/display.rst | 57 +-- docs/en/module/rca.rst | 59 ++-- docs/en/units/glass.rst | 53 +-- docs/en/units/glass2.rst | 55 +-- docs/en/units/index.rst | 2 +- docs/en/units/lcd.rst | 54 +-- docs/en/units/minioled.rst | 52 +-- docs/en/units/oled.rst | 57 ++- docs/en/units/rca.rst | 58 ++-- .../locales/zh_CN/LC_MESSAGES/base/display.po | 4 +- .../zh_CN/LC_MESSAGES/module/display.po | 150 ++++---- docs/locales/zh_CN/LC_MESSAGES/module/rca.po | 170 ++++++--- docs/locales/zh_CN/LC_MESSAGES/units/accel.po | 326 +++++++++--------- docs/locales/zh_CN/LC_MESSAGES/units/glass.po | 116 ++++--- .../locales/zh_CN/LC_MESSAGES/units/glass2.po | 116 ++++--- docs/locales/zh_CN/LC_MESSAGES/units/lcd.po | 118 ++++--- .../zh_CN/LC_MESSAGES/units/minioled.po | 116 ++++--- docs/locales/zh_CN/LC_MESSAGES/units/oled.po | 116 ++++--- docs/locales/zh_CN/LC_MESSAGES/units/rca.po | 161 ++++++--- .../display/cores3_display_example.m5f2 | 2 +- .../module/display/cores3_display_example.py | 26 +- examples/module/rca/core2_rca_example.m5f2 | 2 +- examples/unit/glass/cores3_glass_example.m5f2 | 2 +- .../unit/glass2/cores3_glass2_example.m5f2 | 2 +- examples/unit/glass2/cores3_glass2_example.py | 3 +- examples/unit/lcd/cores3_lcd_example.m5f2 | 2 +- .../minioled/cores3_minioled_example.m5f2 | 2 +- .../unit/minioled/cores3_minioled_example.py | 3 +- examples/unit/oled/cores3_oled_example.m5f2 | 2 +- examples/unit/oled/cores3_oled_example.py | 7 +- examples/unit/rca/core2_rca_example.m5f2 | 2 +- m5stack/libs/module/display.py | 42 +-- m5stack/libs/module/rca.py | 53 +-- m5stack/libs/unit/accel.py | 2 +- m5stack/libs/unit/glass.py | 37 +- m5stack/libs/unit/glass2.py | 39 +-- m5stack/libs/unit/lcd.py | 39 +-- m5stack/libs/unit/minioled.py | 37 +- m5stack/libs/unit/oled.py | 37 +- m5stack/libs/unit/pahub.py | 26 +- m5stack/libs/unit/rca.py | 48 +-- 42 files changed, 1321 insertions(+), 936 deletions(-) diff --git a/docs/en/hardware/display.rst b/docs/en/hardware/display.rst index 70b1d9c0..01d713ed 100644 --- a/docs/en/hardware/display.rst +++ b/docs/en/hardware/display.rst @@ -1,3 +1,5 @@ +.. _hardware.Display: + Display ======= diff --git a/docs/en/module/display.rst b/docs/en/module/display.rst index 7ba574e1..ff5a7669 100644 --- a/docs/en/module/display.rst +++ b/docs/en/module/display.rst @@ -2,50 +2,59 @@ Display Module ============== +.. sku: M126 + .. include:: ../refs/module.display.ref Display Module 13.2 is an expansion module for HD audio and video, using GAOYUN GW1NR series FPGA chip to output display signals, and employing the LT8618S chip for signal output conditioning. Support the following products: -|DisplayModule| + |DisplayModule| -Micropython Example: - .. literalinclude:: ../../../examples/module/display/cores3_display_example.py - :language: python - :linenos: +UiFlow2 Example +--------------- +Draw Text +^^^^^^^^^ -UIFLOW2 Example: +Open the |cores3_display_example.m5f2| project in UiFlow2. + +This example displays the text "Display" on the screen. + +UiFlow2 Code Block: |example.png| -.. only:: builder_html +Example output: - |cores3_display_example.m5f2| + None -class DisplayModule +MicroPython Example ------------------- -Constructors ------------- +This example displays the text "Display" on the screen. + +MicroPython Code Block: + + .. literalinclude:: ../../../examples/module/display/cores3_display_example.py + :language: python + :linenos: + +Example output: + + None -.. class:: DisplayModule(port: None, width: int = 1280, height: int = 720, refresh_rate: float = 60.0, output_width: int = 1280, output_height: int = 720, scale_w: int = 1, scale_h: int = 1, pixel_clock = 74250000) - Initialize the Module Display +**API** +------- - :param tuple port: The port to which the Module Display is connected. port[0]: not used, port[1]: dac pin. - :param int width: The width of the Module Display. - :param int height: The height of the Module Display. - :param int refresh_rate: The refresh rate of the Module Display. - :param int output_width: The output width of the Module Display. - :param int output_height: The output height of the Module Display. - :param int scale_w: The scale width of the Module Display. - :param int scale_h: The scale height of the Module Display. - :param int pixel_clock: The pixel clock of the Module Display. +Class DisplayModule +^^^^^^^^^^^^^^^^^^^ - UIFLOW2: +.. autoclass:: module.display.DisplayModule + :members: - |init.png| + DisplayModule class inherits Display class, See :ref:`hardware.Display ` for more details. diff --git a/docs/en/module/rca.rst b/docs/en/module/rca.rst index 8d0a2d0f..04d81779 100644 --- a/docs/en/module/rca.rst +++ b/docs/en/module/rca.rst @@ -1,59 +1,62 @@ - RCA Module ========== +.. sku: M125 + .. include:: ../refs/module.rca.ref Module RCA is a female jack terminal block for transmitting composite video (audio or video), one of the most common A/V connectors, which transmits video or audio signals from a component device to an output device (i.e., a display or speaker). Support the following products: -|RCAModule| + |RCAModule| -Micropython Example: - .. literalinclude:: ../../../examples/module/rca/core2_rca_example.py - :language: python - :linenos: +UiFlow2 Example +--------------- +Draw Text +^^^^^^^^^ -UIFLOW2 Example: +Open the |core2_rca_example.m5f2| project in UiFlow2. - |example.png| +This example displays the text "RCA" on the screen. -.. only:: builder_html +UiFlow2 Code Block: - |core2_rca_example.m5f2| + |example.png| +Example output: -class RCAModule ---------------- + None -Constructors ------------- -.. class:: RCAModule(port: int =26, width: int = 216, height: int = 144, output_width: int = 216, output_height: int = 144, signal_type: int = 0, use_psram: int=0, output_level: int = 0) +MicroPython Example +------------------- - Initialize the Module RCA +Draw Text +^^^^^^^^^ - :param tuple port: The port to which the Module RCA is connected. port[0]: not used, port[1]: dac pin. - :param int width: The width of the RCA display. - :param int height: The height of the RCA display. - :param int output_width: The width of the output of the RCA display. - :param int output_height: The height of the output of the RCA display. - :param int signal_type: The signal type of the RCA display. NTSC=0, NTSC_J=1, PAL=2, PAL_M=3, PAL_N=4. - :param int use_psram: The use of psram of the RCA display. - :param int output_level: The output level of the RCA display. +This example displays the text "RCA" on the screen. - UIFLOW2: +MicroPython Code Block: - |init.png| + .. literalinclude:: ../../../examples/module/rca/core2_rca_example.py + :language: python + :linenos: +Example output: -Methods -------- + None +**API** +------- +Class RCAModule +^^^^^^^^^^^^^^^ +.. autoclass:: module.rca.RCAModule + :members: + RCAModule class inherits Display class, See :ref:`hardware.Display ` for more details. diff --git a/docs/en/units/glass.rst b/docs/en/units/glass.rst index 40bcc06a..4955ca18 100644 --- a/docs/en/units/glass.rst +++ b/docs/en/units/glass.rst @@ -1,4 +1,3 @@ - Glass Unit =========== @@ -8,40 +7,54 @@ Unit Glass is a 1.51-inch transparent OLED expansion screen unit. It adopts STM3 Support the following products: -|GlassUnit| + |GlassUnit| -Micropython Example: - .. literalinclude:: ../../../examples/unit/glass/cores3_glass_example.py - :language: python - :linenos: +UiFlow2 Example +--------------- + +Draw Text +^^^^^^^^^ +Open the |cores3_glass_example.m5f2| project in UiFlow2. -UIFLOW2 Example: +This example displays the text "GLASS" on the screen. + +UiFlow2 Code Block: |example.png| +Example output: -.. only:: builder_html + None - |cores3_glass_example.m5f2| +MicroPython Example +------------------- -class GlassUnit ---------------- +Draw Text +^^^^^^^^^ + +This example displays the text "GLASS" on the screen. + +MicroPython Code Block: + + .. literalinclude:: ../../../examples/unit/glass/cores3_glass_example.py + :language: python + :linenos: -Constructors ------------- +Example output: -.. class:: GlassUnit(i2c, address: int = 0x3d, freq: int = 400000) + None - Initialize the Unit Glass - :param I2C i2c: the I2C object. - :param int address: I2C address of the Unit Glass, default is 0x3D. - :param int freq: I2C frequency of the Unit Glass. +**API** +------- - UIFLOW2: +class GlassUnit +^^^^^^^^^^^^^^^ - |init.png| +.. autoclass:: unit.glass.GlassUnit + :members: + GlassUnit class inherits Display class, See :ref:`hardware.Display ` for more details. diff --git a/docs/en/units/glass2.rst b/docs/en/units/glass2.rst index 39d00591..8c3a0b1b 100644 --- a/docs/en/units/glass2.rst +++ b/docs/en/units/glass2.rst @@ -1,4 +1,3 @@ - Glass2 Unit =========== @@ -8,38 +7,54 @@ Glass2 Unit is a 1.51-inch transparent OLED display unit that adopts the SSD1309 Support the following products: -|Glass2Unit| + |Glass2Unit| -Micropython Example: - .. literalinclude:: ../../../examples/unit/glass2/cores3_glass2_example.py - :language: python - :linenos: +UiFlow2 Example +--------------- + +Draw Text +^^^^^^^^^ +Open the |cores3_glass2_example.m5f2| project in UiFlow2. -UIFLOW2 Example: +This example displays the text "GLASS2" on the screen. + +UiFlow2 Code Block: |example.png| +Example output: -.. only:: builder_html + None - |cores3_glass2_example.m5f2| -class Glass2Unit ----------------- +MicroPython Example +------------------- + +Draw Text +^^^^^^^^^ -Constructors ------------- +This example displays the text "GLASS2" on the screen. -.. class:: Glass2Unit(i2c, address: int = 0x3c, freq: int = 400000) +MicroPython Code Block: - Initialize the Unit Glass2 + .. literalinclude:: ../../../examples/unit/glass2/cores3_glass2_example.py + :language: python + :linenos: + +Example output: + + None - :param I2C i2c: the I2C object. - :param int address: I2C address of the Unit Glass2, default is 0x3c. - :param int freq: I2C frequency of the Unit Glass2. - UIFLOW2: +**API** +------- + +class Glass2Unit +---------------- + +.. autoclass:: unit.glass2.Glass2Unit + :members: - |init.png| + Glass2Unit class inherits Display class, See :ref:`hardware.Display ` for more details. diff --git a/docs/en/units/index.rst b/docs/en/units/index.rst index a5f23793..acf5c1e9 100644 --- a/docs/en/units/index.rst +++ b/docs/en/units/index.rst @@ -2,7 +2,7 @@ Unit ==== .. toctree:: - :maxdepth: 4 + :maxdepth: 1 ac_measure.rst accel.rst diff --git a/docs/en/units/lcd.rst b/docs/en/units/lcd.rst index 6d8e7487..f97485ce 100644 --- a/docs/en/units/lcd.rst +++ b/docs/en/units/lcd.rst @@ -1,8 +1,9 @@ LCD Unit ======== -.. include:: ../refs/unit.lcd.ref +.. sku: U120 +.. include:: ../refs/unit.lcd.ref Unit LCD is a 1.14 inch color LCD expansion screen unit. It adopts ST7789V2 drive scheme, the resolution is 135*240, and it supports @@ -14,43 +15,56 @@ which can easily adsorb the metal surface for fixing. The LCD screen extension is suitable for embedding in various instruments or control devices that need to display simple content as a display panel. - Support the following products: |LCDUnit| -Micropython Example: +UiFlow2 Example +--------------- - .. literalinclude:: ../../../examples/unit/lcd/cores3_lcd_example.py - :language: python - :linenos: +Draw Text +^^^^^^^^^ +Open the |cores3_lcd_example.m5f2| project in UiFlow2. -UIFLOW2 Example: +This example displays the text "LCD" on the screen. + +UiFlow2 Code Block: |example.png| +Example output: -.. only:: builder_html + None - |cores3_lcd_example.m5f2| +MicroPython Example +------------------- -class LCDUnit -------------- +Draw Text +^^^^^^^^^ -Constructors ------------- +This example displays the text "LCD" on the screen. -.. class:: LCDUnit(i2c, address: int = 0x3e, freq: int = 400000) +MicroPython Code Block: - Initialize the Unit LCD + .. literalinclude:: ../../../examples/unit/lcd/cores3_lcd_example.py + :language: python + :linenos: + +Example output: - :param I2C i2c: the I2C object. - :param int address: I2C address of the Unit LCDUnit, default is 0x3e. - :param int freq: I2C frequency of the Unit LCDUnit. + None + + +**API** +------- + +class LCDUnit +^^^^^^^^^^^^^ - UIFLOW2: +.. autoclass:: unit.lcd.LCDUnit + :members: - |init.png| + LCDUnit class inherits Display class, See :ref:`hardware.Display ` for more details. diff --git a/docs/en/units/minioled.rst b/docs/en/units/minioled.rst index de4d2952..3bb23cc7 100644 --- a/docs/en/units/minioled.rst +++ b/docs/en/units/minioled.rst @@ -8,48 +8,54 @@ MiniOLED UNIT is a 0.42-inch I2C interface OLED screen unit, it's a 72*40, monoc Support the following products: -|MiniOLEDUnit| + |MiniOLEDUnit| -Micropython Example: - .. literalinclude:: ../../../examples/unit/minioled/cores3_minioled_example.py - :language: python - :linenos: +UiFlow2 Example +--------------- +Draw Text +^^^^^^^^^ -UIFLOW2 Example: +Open the |cores3_minioled_example.m5f2| project in UiFlow2. - |example.png| +This example displays the text "Mini" on the screen. +UiFlow2 Code Block: -.. only:: builder_html - - |cores3_minioled_example.m5f2| + |example.png| +Example output: -class MiniOLEDUnit ------------------- + None -Constructors ------------- -.. class:: MiniOLEDUnit(i2c, address: int = 0x3c, freq: int = 400000) +MicroPython Example +------------------- - Initialize the Unit MiniOLED +Draw Text +^^^^^^^^^ - :param I2C i2c: the I2C object. - :param int|list|tuple address: I2C address of the Unit MiniOLED, default is 0x3c. - :param int freq: I2C frequency of the Unit MiniOLED. +This example displays the text "Mini" on the screen. - UIFLOW2: +MicroPython Code Block: - |init.png| + .. literalinclude:: ../../../examples/unit/minioled/cores3_minioled_example.py + :language: python + :linenos: +Example output: -Methods -------- + None +**API** +------- +class MiniOLEDUnit +^^^^^^^^^^^^^^^^^^ +.. autoclass:: unit.minioled.MiniOLEDUnit + :members: + MiniOLEDUnit class inherits Display class, See :ref:`hardware.Display ` for more details. diff --git a/docs/en/units/oled.rst b/docs/en/units/oled.rst index e586d0fe..cba2c4bb 100644 --- a/docs/en/units/oled.rst +++ b/docs/en/units/oled.rst @@ -2,43 +2,62 @@ OLED Unit ========== +.. sku: u119 + .. include:: ../refs/unit.oled.ref Unit OLED is a 1.3-inch OLED expansion screen unit. Driveing by SH1107, and the resolution is 128*64, monochrome display. Support the following products: -|OLEDUnit| + |OLEDUnit| -Micropython Example: - .. literalinclude:: ../../../examples/unit/oled/cores3_oled_example.py - :language: python - :linenos: +UiFlow2 Example +--------------- + +Draw Text +^^^^^^^^^ +Open the |cores3_oled_example.m5f2| project in UiFlow2. -UIFLOW2 Example: +This example displays the text "OLED" on the screen. + +UiFlow2 Code Block: |example.png| -.. only:: builder_html +Example output: - |cores3_oled_example.m5f2| + None -class OLEDUnit --------------- -Constructors ------------- +MicroPython Example +------------------- -.. class:: OLEDUnit(i2c, address: int = 0x3c, freq: int = 400000) +Draw Text +^^^^^^^^^ - Initialize the Unit OLED +This example displays the text "OLED" on the screen. - :param I2C i2c: the I2C object. - :param int address: I2C address of the Unit OLED, default is 0x3c. - :param int freq: I2C frequency of the Unit OLED. +MicroPython Code Block: + + .. literalinclude:: ../../../examples/unit/oled/cores3_oled_example.py + :language: python + :linenos: + +Example output: + + None + + +**API** +------- + +class OLEDUnit +^^^^^^^^^^^^^^ - UIFLOW2: +.. autoclass:: unit.oled.OLEDUnit + :members: - |init.png| + OLEDUnit class inherits Display class, See :ref:`hardware.Display ` for more details. diff --git a/docs/en/units/rca.rst b/docs/en/units/rca.rst index 8b9e60f0..f83b8765 100644 --- a/docs/en/units/rca.rst +++ b/docs/en/units/rca.rst @@ -2,52 +2,62 @@ RCA Unit ========= +.. sku: U155 + .. include:: ../refs/unit.rca.ref Unit RCA is a female jack terminal block for transmitting composite video (audio or video), one of the most common A/V connectors, which transmits video or audio signals from a component device to an output device (i.e., a display or speaker). Support the following products: -|RCAUnit| + |RCAUnit| -Micropython Example: +UiFlow2 Example +--------------- - .. literalinclude:: ../../../examples/unit/rca/core2_rca_example.py - :language: python - :linenos: +Draw Text +^^^^^^^^^ + +Open the |core2_rca_example.m5f2| project in UiFlow2. +This example displays the text "RCA" on the screen. -UIFLOW2 Example: +UiFlow2 Code Block: |example.png| +Example output: + + None -.. only:: builder_html - |core2_rca_example.m5f2| +MicroPython Example +------------------- +Draw Text +^^^^^^^^^ -class RCAUnit -------------- +This example displays the text "RCA" on the screen. + +MicroPython Code Block: + + .. literalinclude:: ../../../examples/unit/rca/core2_rca_example.py + :language: python + :linenos: -Constructors ------------- +Example output: -.. class:: RCAUnit(port: tuple =(36, 26), width: int = 216, height: = int 144, output_width: int = 216, output_height: = int 144, signal_type: int = 0, use_psram: int = 0, output_level: int = 0) + None - Initialize the Unit RCA - :param tuple port: The port to which the Unit RCA is connected. port[0]: not used, port[1]: dac pin. - :param int width: The width of the RCA display. - :param int height: The height of the RCA display. - :param int output_width: The width of the output of the RCA display. - :param int output_height: The height of the output of the RCA display. - :param int signal_type: The signal type of the RCA display. NTSC=0, NTSC_J=1, PAL=2, PAL_M=3, PAL_N=4. - :param int use_psram: The use of psram of the RCA display. - :param int output_level: The output level of the RCA display. +**API** +------- - UIFLOW2: +Class RCAUnit +^^^^^^^^^^^^^ - |init.png| +.. autoclass:: unit.rca.RCAUnit + :members: + RCAUnit class inherits Display class, See :ref:`hardware.Display ` for more details. diff --git a/docs/locales/zh_CN/LC_MESSAGES/base/display.po b/docs/locales/zh_CN/LC_MESSAGES/base/display.po index fdcea37b..8802c9a6 100644 --- a/docs/locales/zh_CN/LC_MESSAGES/base/display.po +++ b/docs/locales/zh_CN/LC_MESSAGES/base/display.po @@ -182,12 +182,12 @@ msgstr "" #: ../../en/base/display.rst:86 f47a46f53a55496e883829adab13074d msgid "MicroPython Example" -msgstr "" +msgstr "MicroPython 应用示例" #: ../../en/base/display.rst:93 ../../en/base/display.rst:109 #: af3327c84e5b46f9a7032c33ab7f0563 base.display.AtomicDisplayBase:16 of msgid "MicroPython Code Block:" -msgstr "MicroPython 应用示例" +msgstr "MicroPython 代码块:" #: ../../en/base/display.rst:119 3150ae65a3d04e1cadbcb5d431636095 msgid "**API**" diff --git a/docs/locales/zh_CN/LC_MESSAGES/module/display.po b/docs/locales/zh_CN/LC_MESSAGES/module/display.po index 12eb2c55..092db8ef 100644 --- a/docs/locales/zh_CN/LC_MESSAGES/module/display.po +++ b/docs/locales/zh_CN/LC_MESSAGES/module/display.po @@ -9,7 +9,7 @@ msgid "" msgstr "" "Project-Id-Version: UIFlow2 Programming Guide \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-01-16 15:35+0800\n" +"POT-Creation-Date: 2025-03-18 10:45+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language: zh_CN\n" @@ -20,112 +20,140 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.16.0\n" -#: ../../en/module/display.rst:3 56f60f350eee482b98a47be2f63fa73a +#: ../../en/module/display.rst:3 0e7ad055e4c64b77b61c51951b615786 msgid "Display Module" msgstr "" -#: ../../en/module/display.rst:7 818238a833f44119a10c4f52d8c4da83 +#: ../../en/module/display.rst:9 97b47c837deb46d1a384cf5a0631fdcc msgid "" "Display Module 13.2 is an expansion module for HD audio and video, using " "GAOYUN GW1NR series FPGA chip to output display signals, and employing " "the LT8618S chip for signal output conditioning." -msgstr "" +msgstr "Display Module 13.2 为高清音视频的扩展模块,采用GAOYUN GW1NR系列FPGA芯片输出显示信号,采用LT8618S芯片进行信号输出调理。" -#: ../../en/module/display.rst:9 d4b65e71bfad493a845bd5adaff20464 +#: ../../en/module/display.rst:11 655d2ad845904108abf63dc626dad8ed msgid "Support the following products:" -msgstr "" +msgstr "支持以下产品:" -#: ../../en/module/display.rst:11 711f56f7479d49bf93afc60870e0d90c +#: ../../en/module/display.rst:13 9ad55ee56f854fd8ad726678e366f554 msgid "|DisplayModule|" msgstr "" -#: ../../en/refs/module.display.ref 0b06939893504724b333e502e425759d +#: ../../en/refs/module.display.ref 59f7de96d8de4c0ba386952cc538d984 msgid "DisplayModule" msgstr "" -#: ../../en/module/display.rst:13 b6f912def9f6473ba0bdabcda699fed6 -msgid "Micropython Example:" -msgstr "" +#: ../../en/module/display.rst:17 283deafa52394941a166297e02685cba +msgid "UiFlow2 Example" +msgstr "UiFlow2 应用示例" -#: ../../en/module/display.rst:20 07477a8c40c94de2a7ab20f05c1158aa -msgid "UIFLOW2 Example:" -msgstr "" +#: ../../en/module/display.rst:20 5a7e3ed3085a4c9080d43483cc242ebf +msgid "Draw Text" +msgstr "绘制文本" + +#: ../../en/module/display.rst:22 a773a6882ea9477fafb66b18adffd625 +msgid "Open the |cores3_display_example.m5f2| project in UiFlow2." +msgstr "在 UiFlow2 中打开 |cores3_display_example.m5f2| 项目。" + +#: ../../en/module/display.rst:24 ../../en/module/display.rst:38 +#: 4bbe9744ac6d472cae5886be35f41639 7ce959a72b344bab9c5874cc5042a392 +msgid "This example displays the text \"Display\" on the screen." +msgstr "此示例在屏幕上显示文本“Display”。" -#: ../../en/module/display.rst:22 437c19abd0614f3987bf063fcb15ad88 +#: ../../en/module/display.rst:26 ac25fad5f6754d6cb52bf26c6e54430c +#: c4280f62b81845f5a1bcd885ce213fa6 module.display.DisplayModule:12 of +msgid "UiFlow2 Code Block:" +msgstr "UiFlow2 代码块:" + +#: ../../en/module/display.rst:28 a21838eae5554170883aba1905af4e6e msgid "|example.png|" msgstr "" -#: ../../en/refs/module.display.ref:9 32d8cc86e90f424bad60901037c946a6 +#: ../../en/refs/module.display.ref:9 c20454d879f8455fbbbcd778a8867a60 msgid "example.png" msgstr "" -#: ../../en/module/display.rst:26 d73f2650d16d440fa0c0636d77d33e98 -msgid "|cores3_display_example.m5f2|" -msgstr "" +#: ../../en/module/display.rst:30 ../../en/module/display.rst:46 +#: 4a29d09f1c364de69e25674d22789b59 db489b84f7684d9fad3d722033ff36a4 +msgid "Example output:" +msgstr "示例输出:" -#: ../../en/module/display.rst:30 0a478a5f9b184115a31951e385b97b6b -msgid "class DisplayModule" +#: ../../en/module/display.rst:32 ../../en/module/display.rst:48 +#: 0686c947a12a45fcb88d6d4da209d015 be88846665c54df096f4c0528df1680e +msgid "None" msgstr "" -#: ../../en/module/display.rst:33 957e53469b8b47fd87af7604b961c9f5 -msgid "Constructors" -msgstr "" +#: ../../en/module/display.rst:36 8032908527d748829ec230ffe4c89fe6 +msgid "MicroPython Example" +msgstr "MicroPython 应用示例" -#: ../../en/module/display.rst:37 fdaba48c5706413b97fc1b1f8d72b7bd -msgid "Initialize the Module Display" -msgstr "" +#: ../../en/module/display.rst:40 9c55a73cd0aa41068dd4d8ee2088b1dc +#: 9ff4a13bfa3a4beaa95b89c66fd4ed7f module.display.DisplayModule:16 of +msgid "MicroPython Code Block:" +msgstr "MicroPython 代码块:" -#: ../../en/module/display.rst b1daecd9236a42e5aeee4cd794ee17d4 -msgid "Parameters" -msgstr "" +#: ../../en/module/display.rst:52 c968632041154e82bca4b4ac87a56bcd +msgid "**API**" +msgstr "API参考" -#: ../../en/module/display.rst:39 f9ea8e6b1ec24a53bb03e6a7ed01cc00 -msgid "" -"The port to which the Module Display is connected. port[0]: not used, " -"port[1]: dac pin." +#: ../../en/module/display.rst:55 48493f6894ed4d1d90c9fe74f6b02d4b +msgid "Class DisplayModule" msgstr "" -#: ../../en/module/display.rst:40 1b472c178e3649aaa9c7a5d9ef181e5c -msgid "The width of the Module Display." +#: c3efbbb859a14be281e0877cf11f5941 module.display.DisplayModule:1 of +msgid "Bases: :py:class:`object`" msgstr "" -#: ../../en/module/display.rst:41 70e1539e6da747c88ba59912ae958871 -msgid "The height of the Module Display." -msgstr "" +#: 84a73d5dc605435a87fdd1604052450a module.display.DisplayModule:1 of +msgid "Initialize the Display Module." +msgstr "初始化 Display Module。" -#: ../../en/module/display.rst:42 176939f9dac24e429375fdf94d060d40 -msgid "The refresh rate of the Module Display." +#: ../../en/module/display.rst 368444dc176e4e16b8f01fb631f92b28 +msgid "Parameters" msgstr "" -#: ../../en/module/display.rst:43 2ed3ea6a16d04af59a698af3279a763a -msgid "The output width of the Module Display." -msgstr "" +#: 9b076e56caed4f4eb8011833e8df490b module.display.DisplayModule:3 of +msgid "The logical width of the Display Module. Default is 1280px." +msgstr "Display Module 的逻辑宽度。默认值为 1280px。" -#: ../../en/module/display.rst:44 bf663d0b788a47ffb8b86a8a59e2cf14 -msgid "The output height of the Module Display." -msgstr "" +#: 855fb1e3a29a4dcca481a7f2afc8604d module.display.DisplayModule:4 of +msgid "The logical height of the Display Module. Default is 720px." +msgstr "Display Module 的逻辑高度。默认值为 720px。" -#: ../../en/module/display.rst:45 f3e43e6723b94227b64a6c874d5356cc -msgid "The scale width of the Module Display." -msgstr "" +#: af99f68dd0ba4e1386f935133cec70eb module.display.DisplayModule:5 of +msgid "The refresh rate of the Display Module. Default is 60Hz." +msgstr "Display Module 的刷新率。默认为 60Hz。" -#: ../../en/module/display.rst:46 3217cb8c48eb4a339ee7ad356bfb1783 -msgid "The scale height of the Module Display." -msgstr "" +#: ce847b487a944b6687c84dc2174dbaba module.display.DisplayModule:6 of +msgid "The width of the output of the Display Module. Default is 1280px." +msgstr "Display Module 输出的宽度。默认为 1280px。" -#: ../../en/module/display.rst:47 50081f2e784746fe83b5b69e753718f1 -msgid "The pixel clock of the Module Display." -msgstr "" +#: ae5f54d2f1504642997e965c49b60158 module.display.DisplayModule:7 of +msgid "The height of the output of the Display Module. Default is 720px." +msgstr "Display Module 输出的高度。默认为 720px。" -#: ../../en/module/display.rst:49 395ce63a976f4cab98ac6a271123a4b0 -msgid "UIFLOW2:" -msgstr "" +#: 7e7ae0fab3274eb19759eabf323d70f9 module.display.DisplayModule:8 of +msgid "The scale width of the Display Module. Default is 1." +msgstr "Display Module 的宽度缩放比例因子。默认值为 1。" -#: ../../en/module/display.rst:51 8eb140721c674538a904450fb3a11d00 +#: 6900945d01fd447baba6d8068edf4e50 module.display.DisplayModule:9 of +msgid "The scale height of the Display Module. Default is 1." +msgstr "Display Module 的高度缩放比例因子。默认值为 1。" + +#: ff4cf3dc593e40d7a7c14124eae9ce10 module.display.DisplayModule:10 of +msgid "The pixel clock of the Display Module. Default is 74250000." +msgstr "Display Module 的像素时钟。默认值为 74250000。" + +#: 125845e87dd649388ccaeb853de509ba module.display.DisplayModule:14 of msgid "|init.png|" msgstr "" -#: ../../en/refs/module.display.ref:7 63e695cc646145f489fa7d9ffac3e0ce +#: ../../en/refs/module.display.ref:7 21774f46c0ec45238acc716d34af591f msgid "init.png" msgstr "" +#: ../../en/module/display.rst:60 567ca5a589c34a398d49e5fd574bfd7c +msgid "" +"DisplayModule class inherits Display class, See :ref:`hardware.Display " +"` for more details." +msgstr "DisplayModule 类继承了 Display 类,更多详细信息请参阅 :ref:`hardware.Display ` 。" diff --git a/docs/locales/zh_CN/LC_MESSAGES/module/rca.po b/docs/locales/zh_CN/LC_MESSAGES/module/rca.po index fd320772..d18c83e9 100644 --- a/docs/locales/zh_CN/LC_MESSAGES/module/rca.po +++ b/docs/locales/zh_CN/LC_MESSAGES/module/rca.po @@ -9,7 +9,7 @@ msgid "" msgstr "" "Project-Id-Version: UIFlow2 Programming Guide \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-01-16 15:35+0800\n" +"POT-Creation-Date: 2025-03-18 17:25+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language: zh_CN\n" @@ -20,115 +20,175 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.16.0\n" -#: ../../en/module/rca.rst:3 e540d1702a0b42aabd6a6639aaf872f7 +#: ../../en/module/rca.rst:2 16fe8d445f574fd2a97d8ac3256478d2 msgid "RCA Module" msgstr "" -#: ../../en/module/rca.rst:7 68752295fd3541628cc2a9b14ec9745e +#: ../../en/module/rca.rst:8 4c15c36e3181459288ff10c2de9d199a msgid "" "Module RCA is a female jack terminal block for transmitting composite " "video (audio or video), one of the most common A/V connectors, which " "transmits video or audio signals from a component device to an output " "device (i.e., a display or speaker)." msgstr "" +"RCA 模块是用于传输复合视频(音频或视频)的母插孔接线端子,是最常见的 A/V " +"连接器之一,它将视频或音频信号从组件设备传输到输出设备(即显示器或扬声器)。" -#: ../../en/module/rca.rst:9 8963b4e53c3f46eab43855dc7296b69c +#: ../../en/module/rca.rst:10 d370429015d542e9a654b603476e9778 msgid "Support the following products:" -msgstr "" +msgstr "支持以下产品:" -#: ../../en/module/rca.rst:11 be50b24c46ab4df08468517d22c5026d +#: ../../en/module/rca.rst:12 c28f2ddb3cac40cab7b4ede59a06292a msgid "|RCAModule|" msgstr "" -#: ../../en/refs/module.rca.ref 07b75df3754f47ebbe61ea6669cf1067 +#: ../../en/refs/module.rca.ref 1935b3cd8fc04dc0b0f2a4ad3cf25057 msgid "RCAModule" msgstr "" -#: ../../en/module/rca.rst:13 132b6b50214b4e2696da3bedeb939142 -msgid "Micropython Example:" -msgstr "" +#: ../../en/module/rca.rst:16 801f45d9c2c64fe6bae0706a23eb1280 +msgid "UiFlow2 Example" +msgstr "UiFlow2 应用示例" -#: ../../en/module/rca.rst:20 67272d63a79941769308e19a7606f38f -msgid "UIFLOW2 Example:" -msgstr "" +#: ../../en/module/rca.rst:19 ../../en/module/rca.rst:38 +#: 23a8359609734d21a8412e5451dc35dd d8270a6008ca4835ac23c08532ba0f18 +msgid "Draw Text" +msgstr "绘制文本" + +#: ../../en/module/rca.rst:21 ecd318f4ecf441f78115d1a913a2419f +msgid "Open the |core2_rca_example.m5f2| project in UiFlow2." +msgstr "在 UiFlow2 中打开 |core2_rca_example.m5f2| 项目。" -#: ../../en/module/rca.rst:22 c1000430f8c0439a967199aab65ecda7 +#: ../../en/module/rca.rst:23 ../../en/module/rca.rst:40 +#: 2c95d4d0a98a4e42b014750ded085387 732e649f57d64993ba7129afa7ae22bb +msgid "This example displays the text \"RCA\" on the screen." +msgstr "此示例在屏幕上显示文本“RCA”。" + +#: ../../en/module/rca.rst:25 4d991f53a5ac48ea818135e89988bb4b +#: 4f4ca1de58f64568b1d0b91ed21da7e8 module.rca.RCAModule:12 of +msgid "UiFlow2 Code Block:" +msgstr "UiFlow2 代码块:" + +#: ../../en/module/rca.rst:27 f77b802fb826440d9c0834376108dd57 msgid "|example.png|" msgstr "" -#: ../../en/refs/module.rca.ref:9 067b07dd8e8c4a58b01e2147d69008fb +#: ../../en/refs/module.rca.ref:9 3887e0c753474fd78bd03a8d7ec401a5 msgid "example.png" msgstr "" -#: ../../en/module/rca.rst:26 8fc87d2ea12c471fb6f03f1c71f19693 -msgid "|core2_rca_example.m5f2|" -msgstr "" +#: ../../en/module/rca.rst:29 ../../en/module/rca.rst:48 +#: 20fe8aa954ae464883a2f6881733c7d0 b1f1915dc0cf4eee9f695886eb59c07f +msgid "Example output:" +msgstr "示例输出:" -#: ../../en/module/rca.rst:30 578953f881954f5b81c6271ec3608be3 -msgid "class RCAModule" +#: ../../en/module/rca.rst:31 ../../en/module/rca.rst:50 +#: 88ecdc7628a44633a57e4a20ebe7195e af5c687e69b94affa7c4d2d8d3ecbcbf +msgid "None" msgstr "" -#: ../../en/module/rca.rst:33 6ece5206ca5546c7adfedf65fefe9e4b -msgid "Constructors" +#: ../../en/module/rca.rst:35 499c594c242949349ca020009ecf5ee5 +msgid "MicroPython Example" +msgstr "MicroPython 应用示例" + +#: ../../en/module/rca.rst:42 499c594c242949349ca020009ecf5ee5 +#: module.rca.RCAModule:16 of +msgid "MicroPython Code Block:" +msgstr "MicroPython 代码块:" + +#: ../../en/module/rca.rst:54 82ac218f32e64f229207fe639bda3e3e +msgid "**API**" +msgstr "API参考" + +#: ../../en/module/rca.rst:57 0015aa5d40484430a2dd9ec21cc34079 +msgid "Class RCAModule" msgstr "" -#: ../../en/module/rca.rst:37 d4e5336e54fa4e26b41e307671744fd5 -msgid "Initialize the Module RCA" +#: d6bbc6c747bb48869a2f50d88f0ebfcb module.rca.RCAModule:1 of +msgid "Bases: :py:class:`object`" msgstr "" -#: ../../en/module/rca.rst d78a92f85d724ad490e8f31fb2154a1c +#: 56a13f7e15e14a94a578a11551f46cfb module.rca.RCAModule:1 of +#, fuzzy +msgid "Initialize the RCA Module." +msgstr "初始化 RCA Module。" + +#: ../../en/module/rca.rst 788378cc6e2b49579c59c37e3e3b4d98 msgid "Parameters" msgstr "" -#: ../../en/module/rca.rst:39 92b0cb92a1db401cabca769fa7574edd -msgid "" -"The port to which the Module RCA is connected. port[0]: not used, " -"port[1]: dac pin." -msgstr "" +#: 3c9c20f80d384406a3fcceba92b3d97e module.rca.RCAModule:3 of +msgid "The dac pin to which the RCA Module is connected." +msgstr "RCA 模块连接到的 DAC 引脚。" -#: ../../en/module/rca.rst:40 4b2bdaae716c43f3b7bb4429fb9d3c94 +#: b92da428b78d457785cac259c876504b module.rca.RCAModule:4 of msgid "The width of the RCA display." -msgstr "" +msgstr "RCA 显示器的宽度。" -#: ../../en/module/rca.rst:41 1b7b051ac1344d0ea5d4d0fff66509a8 +#: fe5c347e693642929d0ea898e7ac859f module.rca.RCAModule:5 of msgid "The height of the RCA display." -msgstr "" +msgstr "RCA 显示器的高度。" -#: ../../en/module/rca.rst:42 2988c6902fee475d95b1ca87f1de6aab +#: 9896bc0f7be94be09e2794cdeab36824 module.rca.RCAModule:6 of msgid "The width of the output of the RCA display." -msgstr "" +msgstr "RCA 显示器输出的宽度。" -#: ../../en/module/rca.rst:43 c4ea65eebf4146fab52650f725db0280 +#: 8cd9a771eb6f41ccac9e103a20c21e50 module.rca.RCAModule:7 of msgid "The height of the output of the RCA display." -msgstr "" +msgstr "RCA 显示器输出的高度。" -#: ../../en/module/rca.rst:44 7e7825ae834c4b12b5e0daefb27da0eb +#: a1dc2424344341cb8b76c2d62d3ccbb5 module.rca.RCAModule:8 of msgid "" -"The signal type of the RCA display. NTSC=0, NTSC_J=1, " -"PAL=2, PAL_M=3, PAL_N=4." -msgstr "" +"The signal type of the RCA display. NTSC=0, NTSC_J=1, PAL=2, PAL_M=3, " +"PAL_N=4." +msgstr "RCA显示的信号类型。" -#: ../../en/module/rca.rst:45 e3f4ddb213f34519a312f44924e8c4ee +#: c97195dee719432c89f1d6941bd9e143 module.rca.RCAModule:9 of msgid "The use of psram of the RCA display." -msgstr "" +msgstr "RCA显示器的psram的使用。" -#: ../../en/module/rca.rst:46 df3c90aafa1341a7ba1dce62d9609697 +#: 3686b63331984dd9ada1e7980d7f2c1e module.rca.RCAModule:10 of msgid "The output level of the RCA display." -msgstr "" - -#: ../../en/module/rca.rst:48 95aae9fd0b6844a38ef88794aa7ba9e0 -msgid "UIFLOW2:" -msgstr "" +msgstr "RCA 显示的输出电平。" -#: ../../en/module/rca.rst:50 513188061ecc454282ee406f14c53f4a +#: fceef27cac49455b8669d79340697a0a module.rca.RCAModule:14 of msgid "|init.png|" msgstr "" -#: ../../en/refs/module.rca.ref:7 2dcd7de70bcf430ab27b5c6ea3c729d0 +#: ../../en/refs/module.rca.ref:7 1cfee8c5551e4672b7afe34873a31f53 msgid "init.png" msgstr "" -#: ../../en/module/rca.rst:54 15c1fddfdb144587b1d817dfa77b844e -msgid "Methods" -msgstr "" +#: ../../en/module/rca.rst:62 ebfe26e5800a4bafa1bfbc432ad63120 +msgid "" +"RCAModule class inherits Display class, See :ref:`hardware.Display " +"` for more details." +msgstr "" +"RCAModule 类继承了 Display 类,更多详细信息请参阅 :ref:`hardware.Display " +"` 。" + +#: ../../docstring d5e06dace4614b5c9610ef631ebfca6e module.rca.RCAModule.NTSC:1 +#: of +msgid "signal type. National Television System Committee." +msgstr "信号类型。国家电视系统委员会制定的制式。" + +#: ../../docstring fbf53603aaab473b8014bde21622b52c +#: module.rca.RCAModule.NTSC_J:1 of +msgid "signal type. National Television System Committee Japan." +msgstr "信号类型。国家电视系统委员会制式的日本标准。" + +#: ../../docstring 11639b06ecf741ac9ac05595df170e29 module.rca.RCAModule.PAL:1 +#: of +msgid "signal type. Phase Alternating Line." +msgstr "信号类型。逐行扫描制式。" + +#: ../../docstring 11639b06ecf741ac9ac05595df170e29 +#: module.rca.RCAModule.PAL_M:1 of +msgid "signal type. Phase Alternating Line M." +msgstr "信号类型。逐行扫描制式 M格式。" + +#: ../../docstring d3882a48c4ff442b945c3c0d736bb747 +#: module.rca.RCAModule.PAL_N:1 of +msgid "signal type. Phase Alternating Line N." +msgstr "信号类型。逐行扫描制式 N格式。" diff --git a/docs/locales/zh_CN/LC_MESSAGES/units/accel.po b/docs/locales/zh_CN/LC_MESSAGES/units/accel.po index 57258ab2..b8d9051c 100644 --- a/docs/locales/zh_CN/LC_MESSAGES/units/accel.po +++ b/docs/locales/zh_CN/LC_MESSAGES/units/accel.po @@ -9,7 +9,7 @@ msgid "" msgstr "" "Project-Id-Version: UIFlow2 Programming Guide \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-02-20 09:57+0800\n" +"POT-Creation-Date: 2025-03-18 14:44+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language: zh_CN\n" @@ -20,57 +20,57 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.16.0\n" -#: ../../en/units/accel.rst:2 17e806a1853e40cfa76349808a6682c0 +#: ../../en/units/accel.rst:2 902d1a8835d24d54a53ca621efe0d7da msgid "Accel Unit" msgstr "" -#: ../../en/units/accel.rst:8 d0705e55306b444fab31ecbf4d0ccb90 +#: ../../en/units/accel.rst:8 b28ec5d4dc8246ff85f6efd1064a2194 msgid "" "This is the driver library of Accel Unit, which is used to obtain data " "from the acceleration sensor and support motion detection." msgstr "这是Accel Unit的驱动库,用于从加速度传感器获取数据,并支持运动检测。" -#: ../../en/units/accel.rst:11 e424333627d54044af580b5c88956c56 +#: ../../en/units/accel.rst:11 97aa8b576d224186a393c5a6b0e5fad4 msgid "Support the following products:" msgstr "支持以下产品:" -#: ../../en/units/accel.rst:13 7e61e6152edb409da57c02ba6117fb6a +#: ../../en/units/accel.rst:13 b39ec85d50964bdc9e5f1558e07f6349 msgid "|ACCEL|" msgstr "" -#: ../../en/refs/unit.accel.ref cbb8810147b9445183ead6eea566d9aa +#: ../../en/refs/unit.accel.ref 3a99c67e620447cf9bf1b0b05d88deb6 msgid "ACCEL" msgstr "" -#: ../../en/units/accel.rst:17 9bb6d60867b84249a66191a628e436d3 +#: ../../en/units/accel.rst:17 a6450b3e96c549a1b0381f18b058cb91 msgid "UiFlow2 Example" msgstr "UiFlow2 应用示例" #: ../../en/units/accel.rst:20 ../../en/units/accel.rst:38 -#: a1376949ff104031b9af4175f00267fd d9a3ea60b5b647d08cc1513a423d22de +#: 7a1425fae4b9420f80e9f947c213869e dbc7f3ac645b476085d1cefbe32cf18a msgid "get accel value" msgstr "获取加速度值" -#: ../../en/units/accel.rst:22 47e7dfa44b804c658bcbd2c063a5a0cc +#: ../../en/units/accel.rst:22 333162315e20402ab970b498949fb027 msgid "Open the |stickcplus2_unit_accel_example.m5f2| project in UiFlow2." msgstr "在 UiFlow2 上打开 |stickcplus2_unit_accel_example.m5f2| 项目。" #: ../../en/units/accel.rst:24 ../../en/units/accel.rst:40 -#: 53215bfc539943ab832470981f0cbc3e f4b7ad4802a94274a29db5b35528e7d1 +#: 5f6573bd34a2425f97b137d187be4ae7 6e31cfcc8df04efd812f4f828375e64d msgid "" "This example gets the acceleration value of the Accel Unit and displays " "it on the screen." msgstr "此示例获取加速度单元的加速度值并将其显示在屏幕上。" -#: ../../en/units/accel.rst:26 0e6f8debc6b346f2bfb9a422654e11cb -#: 152cedabd8b64aeebe560e0b8a4141a6 2ae6c452524e485496f2d992a362da6f -#: 2be9252a48974fa7b74126b6f0f9eefc 3443f6942b394efc9e02adfd058927a0 -#: 3d338f9fbc5c43e4830bf0037e49b610 4142f78424c847c0a75d14b8a0fef631 -#: 626a2c68276548429f8e9520bc44202a 65deb000c32a4a94942a2ecabe5a202a -#: 7dade6eb1a2f40528938d722d99d9c27 81ba4d98d0d34f05b64a68af449d849f -#: 84421403ff264fe1bba9fb2a40ca6811 85d285848ffb44438709e60dce769264 -#: 90fc1248d6514cbbb3750ecdea877c9e e2bf88b1894d46119942c61d7d7b5df4 -#: e8a644247aa74ffeab444a12d9abd2f7 of unit.accel.AccelUnit:6 +#: ../../en/units/accel.rst:26 0300743665984fec8bf554ba14bdee82 +#: 04db7119fef4456cb0078cab18d69af7 075e3390a85241fea385cacc078c858e +#: 11709dca9143494fb5ac5d66fe431d59 31beafa999424b019aa18cbfa27395b7 +#: 7ceb364f5cb24ca299e96a76742be891 8c9d9810af954c28bcc4cee645c718de +#: a27d0e3c91044314a10f6d51f748194d a2bb428776c648ddbc6969e3e58f4c1c +#: b1a3188821f84da3aa575fb0b188e159 c23919799ab2475a9a1afa1af116fdb5 +#: e0e4478d49df4c85bb54e14596697cf2 e3e364d5a0264e36bcc92bb6695a140f +#: eaf994ac36984bda99c1b566eb5ca696 f468fd3a9d784ba6a5fd93a7a241fbea +#: f6fed1fd70c246f8970e03bb0d8a80d1 of unit.accel.AccelUnit:6 #: unit.accel.AccelUnit.disable_freefall_detection:3 #: unit.accel.AccelUnit.disable_motion_detection:3 #: unit.accel.AccelUnit.disable_tap_detection:3 @@ -84,37 +84,37 @@ msgstr "此示例获取加速度单元的加速度值并将其显示在屏幕上 msgid "UiFlow2 Code Block:" msgstr "UiFlow2 代码块:" -#: ../../en/units/accel.rst:28 485c9fdfd12643eea9bd41ba1ba40f40 +#: ../../en/units/accel.rst:28 b9d2f2d9dc464118a87c8a3e899a6277 msgid "|example.png|" msgstr "" -#: ../../en/refs/unit.accel.ref:7 385d370e9d244ef98d4e3ba22cd3dda1 +#: ../../en/refs/unit.accel.ref:7 7adec39714794532a576866916f134a6 msgid "example.png" msgstr "" #: ../../en/units/accel.rst:30 ../../en/units/accel.rst:48 -#: 3d291ec9a6c64dfda1dce867dc9d6793 d7e029f06dd74aae8b38198774ac91c4 +#: d45cdab5f52844a79dfb051007dc918c f9b58fef3a9240eea4d53ebeb0718e44 msgid "Example output:" msgstr "示例输出:" #: ../../en/units/accel.rst:32 ../../en/units/accel.rst:50 -#: 0cac84f0a9c94d51b46baa9779f2ac5b 94179f5d91ac4c7aa920a3585bbe5988 +#: 0d60397bcc9a443f83312751bc5e3c7e f978e6eccd9e43a081221168c4360913 msgid "None" msgstr "无" -#: ../../en/units/accel.rst:35 0016310d395448dcbd2b70294c5bd267 +#: ../../en/units/accel.rst:35 0b25f7ddaa2140a99d238974bb22f118 msgid "MicroPython Example" msgstr "MicroPython 应用示例" -#: ../../en/units/accel.rst:42 0e7c57b9b2014855935d66a6397865b6 -#: 1edd36ef2142406381568e8449337d1e 36cc1634a9fc4c1a8ca138eea14cf035 -#: 529d963b87b1401391387a4fed15c1a0 6c62b7abcfd3428ebb1bcb4f1a1b6f79 -#: 70663fba7dcb474e967a2391f8c0b5de 7be801995be5448c9116a61703bf5176 -#: 7f99352a09a84e55b78923af66fc9c37 8362829ebed1425cb93d87f841c7fb37 -#: 8ab0415205b74b80a4c10381a026a636 a58ed3509e724739ab79a653899266b8 -#: be99c6f1fe80463e9d72f5b532ee9b8a d98257d041b143b08e076c7027351d13 -#: e7bc6772509b4ff3a7f892a067da39b3 e9492c2976e843648db81212fc366180 -#: fbef680c6396410dafc3cbb8588d3b6d of unit.accel.AccelUnit:10 +#: ../../en/units/accel.rst:42 0383941d1b014c63991aa0684bab057b +#: 06d1c393ee7e4705b95c3a57fb6aa723 13f2167369214da1b831ffe59f132522 +#: 1e97cf2e0d0a4b449d45bfbc95517272 47aa8816d404498fa4c6d60e3012e335 +#: 48a396b22a3b4a09ab6ed86f41440b79 6227a3d566fa43629208ad03f65f4586 +#: 7a2c864b105343b491dc0270f960c03b 81a7b6f5bcc242d3b1e6cf10086c883f +#: 8475e08d8d12485ba7c1ff959afd7259 a0c67291561b45dabcec3c672b6e2f65 +#: a7fbfdfff99c49179ad6745158072f54 b46fcbf10cd04beeba832c5576640de8 +#: b71e9fc9de9c4dff962ed620c347fef1 c40da3af2bd94dda9de311f2ef03446d +#: da4090fc0d9345b48cdfbfe29b5f2a3a of unit.accel.AccelUnit:10 #: unit.accel.AccelUnit.disable_freefall_detection:7 #: unit.accel.AccelUnit.disable_motion_detection:7 #: unit.accel.AccelUnit.disable_tap_detection:7 @@ -128,28 +128,28 @@ msgstr "MicroPython 应用示例" msgid "MicroPython Code Block:" msgstr "MicroPython 代码块:" -#: ../../en/units/accel.rst:54 f62fe8c7ffd4452898e07a5e227b2846 +#: ../../en/units/accel.rst:54 3a14b5f0a3774beb80cdc9ee81d0ca60 msgid "**API**" msgstr "API参考" -#: ../../en/units/accel.rst:57 9a68c03b570b4f2aa7ace20204a19b66 +#: ../../en/units/accel.rst:57 2e50e258dddf4416b38ea49371d4db9a msgid "AccelUnit" msgstr "" -#: 7b1bd3b4a9884434bdf94050c8608593 of unit.accel.AccelUnit:1 +#: d5134f4cc1424a70998d0162ca6a2ed3 of unit.accel.AccelUnit:1 msgid "Bases: :py:class:`~driver.adxl34x.ADXL345`" msgstr "" -#: b10a3f829d824c5ebbe1edd995ff1daf of unit.accel.AccelUnit:1 +#: 30b45339e792456eb67b9ad35d22f14d of unit.accel.AccelUnit:1 msgid "Create an AccelUnit object." msgstr "创建一个 AccelUnit 对象。" -#: ../../en/units/accel.rst 0894bdf473f94b81a87427508ecd4cf4 -#: 1180fd3da64048e6b11a0b69d70d7e08 2a8862f8ad2d4e73a9370208d14963b2 -#: 346136e38f334c95954ae8b12c13683d 37454a39a8f640c3a5a858d10d6e857a -#: 5893a91d32a54916baaa26bc8510788e 9d44110890a04687b12be1a2d1052603 -#: 9ee883901a494fa5b45bdd96d3340daf c625a53fcad74693bc036fe6d736f47e -#: cc6a309882ee41ed8e92ff54de35f51e of +#: ../../en/units/accel.rst 15cbd56808674203aecbf3ebecdaa6d6 +#: 268930730f6d4664b41d5db2bccaea5b 5acffaf2cae9467691caff5cd6e4e995 +#: 5c7ea7bfca0c4614975453b326764770 646cccd43ae443dd8b008e37fc91000a +#: 7d279dec01d44cc3b421cbe8d85dde3a 94660055b04240fb849077cf43838c66 +#: c0557c6dbf5047a894aa4ffa18491de0 d7bd73b689a249dc8c5275ba8c97fb3e +#: db5a448cfa4f4567b3cfca9b231d246a of #: unit.accel.AccelUnit.enable_freefall_detection #: unit.accel.AccelUnit.enable_motion_detection #: unit.accel.AccelUnit.enable_tap_detection unit.accel.AccelUnit.set_data_rate @@ -157,50 +157,50 @@ msgstr "创建一个 AccelUnit 对象。" msgid "Parameters" msgstr "" -#: 02f7f04fe28245a28ac8d35aed4ac91e of unit.accel.AccelUnit:3 -msgid "The I2C bus the AccelUnit is connected to." -msgstr "AccelUnit 所连接的 I2C 总线。" +#: 42358ea81a8a41c7be94a7a9ae3b45fb of unit.accel.AccelUnit:3 +msgid "The I2C bus the Accel Unit is connected to." +msgstr "Accel Unit 所连接的 I2C 总线。" -#: 00f48c46a24c41a1bcd28f6eb9ace7a8 of unit.accel.AccelUnit:4 +#: 9875d27eaaa24581b1d8c9ca78c804a2 of unit.accel.AccelUnit:4 msgid "The I2C address of the device. Default is 0x53." msgstr "设备的 I2C 地址。默认值为 0x53。" -#: 23a1cc3992e34392bf84f797f1f5b2d1 of unit.accel.AccelUnit:8 +#: 9fee83fbe03f405faf05a79e4e0d8d61 of unit.accel.AccelUnit:8 msgid "|init.svg|" msgstr "" -#: ../../en/refs/unit.accel.ref:9 24cb7c69e01240aba7a6db2c802efd90 +#: ../../en/refs/unit.accel.ref:9 f7f5b893502843309e4d11164e9373d3 msgid "init.svg" msgstr "" -#: 61aea975f35d416eb4c0bbaf84b09fc1 8b4a4d627a7e46588efff58da737fe8b +#: 24f4cfcafba4474e9ddc8d21e82736e6 a9b77c59e23e421595caea29b6f0a6dd #: driver.adxl34x.ADXL345.disable_freefall_detection:1 of #: unit.accel.AccelUnit.disable_freefall_detection:1 msgid "Disable freefall detection." msgstr "禁用自由落体检测。" -#: 4a1bef02c2d2447597aaca495b422c9e of +#: fc7a4727ba564ed7bae668f1bfe335e9 of #: unit.accel.AccelUnit.disable_freefall_detection:5 msgid "|disable_freefall_detection.png|" msgstr "" -#: ../../en/refs/unit.accel.ref:41 b266f7bd39f041e9a8c15ab89a033bf2 +#: ../../en/refs/unit.accel.ref:41 fc01d3473639439cb000544153a9180d msgid "disable_freefall_detection.png" msgstr "" -#: ../../en/units/accel.rst 0b4fcf4809bc4faeb05e8a487ac5dc0d -#: 135bff7c7a674cb3a114989e69392548 19136e0dae414dfa9386df6242998cc8 -#: 1efd1e7924e544cd9585211cbe8c77b0 2bce66e0cd084299a88901212fac10cd -#: 4c721a493f744635aa7da02f0594a9e6 4ebbc031ba924ced8e9e1059e145a8df -#: 7f012dc8bd174435a23e6440a0206824 80a249b372ab4f349f9a9898f9d32d98 -#: 8776851069c14fa9b50dd742bec476cb 8dd394d1032748c8b3fc98791c59f4d7 -#: 908a9cb286f047ca80d2523df023bb93 93c75077a933483ea4bb7ddc22633543 -#: a0e964a63b0c42dd809b5e16740288ca a62e3d9917be4703bfb2384cf160a35f -#: c275aaaa828041baa95da3a5d4850c99 +#: ../../en/units/accel.rst 164c1b89be9f4e8d9d8e072310cf7212 +#: 18f8b2a23338431bba0ffd8193bd2474 1aa6d8cdbab54b8faa100c77bcd2c165 +#: 3bfe4e1ba448478abad2dbeefc3a36f4 523807771ec3424ea4f80eb430451d5c +#: 5c3fe8692bab48eb8eb5b5362c82269b 717624ead153430891230fa1b88f5c97 +#: 784bec2611c94f4eabef5fbc3f202c03 9a9f8e8c05b84bea99e95005f8f2f471 +#: a8defe2ca4d54640b3f926bb9adabe0a b44bf5a7a9164b549196d687099511a6 +#: bc6208040bc648e9a67949454cbc1c39 c4735f977daa4d979efaa2cb4964e93f +#: cccd6aec81a24d1483ba22cde7c84dfb dd5d95ff471749589d81a7a213b04c26 +#: ddb95762c3c94e249a7496aabdf42d08 #: driver.adxl34x.ADXL345.disable_freefall_detection #: driver.adxl34x.ADXL345.disable_motion_detection #: driver.adxl34x.ADXL345.disable_tap_detection -#: e5bf3cc4ed944ce1845ee385905adf13 edb83a9c0157435e831b603300bf2f29 of +#: f7a0d9e316d145d28dd28883b803f15d fe9f27d28bc24cc0ae991db6ade03452 of #: unit.accel.AccelUnit.disable_freefall_detection #: unit.accel.AccelUnit.disable_motion_detection #: unit.accel.AccelUnit.disable_tap_detection @@ -214,54 +214,53 @@ msgstr "" msgid "Return type" msgstr "" -#: 2fa32249046545c48cf8483dd4a04afd cae8abd3de1d4312883e37ce7f6b36f7 +#: 791fabd5df33493abef396c202de3b07 d54bee36fdc847bd89c2ed6b9cc11b0e #: driver.adxl34x.ADXL345.disable_motion_detection:1 of #: unit.accel.AccelUnit.disable_motion_detection:1 msgid "Disable motion detection." msgstr "禁用运动检测。" -#: 9a45117adb6f4c7a952a41eddf0bed2a of +#: 0dba8846b6cd4cf8b1af00e01e1dd9ea of #: unit.accel.AccelUnit.disable_motion_detection:5 msgid "|disable_motion_detection.png|" msgstr "" -#: ../../en/refs/unit.accel.ref:23 46a4a9a38a7146c3a506a3198f222dd6 +#: ../../en/refs/unit.accel.ref:23 336dc2d046af4c80bd7a9da4d9ccbf70 msgid "disable_motion_detection.png" msgstr "" -#: 122cdfa5b39d4bc69c48f172253fd00b -#: driver.adxl34x.ADXL345.disable_tap_detection:1 -#: eb7240e232dc4b8799504c5c58d68de0 of +#: 1cc1661ee1b54730844b24e2ff19405c 2dfe7b5bdfe04253b2be677253995849 +#: driver.adxl34x.ADXL345.disable_tap_detection:1 of #: unit.accel.AccelUnit.disable_tap_detection:1 msgid "Disable tap detection." msgstr "禁用点击检测。" -#: 5fa485fb99d44c4c90e2981b28642b9a of +#: a6487f5fa6ad4bd884b1b6ba5ba27a1a of #: unit.accel.AccelUnit.disable_tap_detection:5 msgid "|disable_tap_detection.png|" msgstr "" -#: ../../en/refs/unit.accel.ref:50 beca0d9801d94a1ab9e2225bc130d8ed +#: ../../en/refs/unit.accel.ref:50 7d839aff4410475abd7f62f9dd333df8 msgid "disable_tap_detection.png" msgstr "" -#: 00988fec5ce249d09a8aacfe70338a52 a05f4cbbf86e46e5b9177ca3bd611253 +#: 25c6e88d4df74835be4c692f6904195e 49a096f47e5b475e9448b25ec3c8bf20 #: driver.adxl34x.ADXL345.enable_freefall_detection:1 of #: unit.accel.AccelUnit.enable_freefall_detection:1 msgid "Freefall detection parameters:" msgstr "自由落体检测参数:" -#: 1171b86ab0b24314b51b5a5a22fce471 -#: driver.adxl34x.ADXL345.enable_freefall_detection:3 -#: eccabef56ed245a4bc96074965751ba3 of +#: 49aa8b31536249e1b4580f7f352d333c 6f241515428c47efa397a63df8c6e811 +#: driver.adxl34x.ADXL345.enable_freefall_detection:3 of #: unit.accel.AccelUnit.enable_freefall_detection:3 msgid "" "The value that acceleration on all axes must be under to register as " "dropped. The scale factor is 62.5 mg/LSB." msgstr "所有轴上的加速度必须低于该值才能记录为下降。比例因子为 62.5 mg/LSB。" -#: 45d3272c18ac4ce5b75db67b1430f04e c2e7c39ebb2b43729d9e1b8b3712710b -#: driver.adxl34x.ADXL345.enable_freefall_detection:7 of +#: aec0a5b566f0485e86ba49967ebfbb58 +#: driver.adxl34x.ADXL345.enable_freefall_detection:7 +#: f4a26b6e2da84ef9953b14715beac15c of #: unit.accel.AccelUnit.enable_freefall_detection:7 msgid "" "The amount of time that acceleration on all axes must be less than " @@ -271,12 +270,12 @@ msgstr "" "所有轴上的加速度必须小于“阈值”的时间量才能记录为掉落。比例因子为 5 毫秒/LSB。建议值介于 100 毫秒和 350 毫秒(20 到 " "70)之间。" -#: 3a2ab5bdb374479fbc407cb11fb0bad4 521523370eea4201a3f10a7cbe1e8a37 -#: 56f19c27bb9547a1b267bb82f95d6171 8241a259137846d59d6942b91163a93b -#: 9f03e3a910d040d3b9e8843cc4727698 +#: 102c14b3b9594b5082b8cf9a84bf70b0 25e6a031170e494b8ba2700c49cf4ba6 +#: 3bbf2287d1534a248082e817ab6de3dd b2a38a6c7f264563b247692db353d578 #: driver.adxl34x.ADXL345.enable_freefall_detection:12 #: driver.adxl34x.ADXL345.enable_motion_detection:7 -#: driver.adxl34x.ADXL345.enable_tap_detection:23 of +#: driver.adxl34x.ADXL345.enable_tap_detection:23 +#: fc0f57cc76cf49ff9d496296ea497ccc of #: unit.accel.AccelUnit.enable_freefall_detection:12 #: unit.accel.AccelUnit.enable_tap_detection:22 msgid "" @@ -284,32 +283,31 @@ msgid "" " use keyword arguments:" msgstr "如果您希望自己设置它们而不是使用默认值,则必须使用关键字参数:" -#: d774f9064a2946fe85020881fdf8af02 of +#: 6f0be3ece7ed497bac9af7f9609c30a7 of #: unit.accel.AccelUnit.enable_freefall_detection:21 msgid "|enable_freefall_detection1.png|" msgstr "" -#: ../../en/refs/unit.accel.ref:35 a851b1e779d042d98c2a591be346b48e +#: ../../en/refs/unit.accel.ref:35 5ab50b2f8b974ea89044082ef45be9b8 msgid "enable_freefall_detection1.png" msgstr "" -#: c5cc53f21d5b49008dc8744aedf79b76 of +#: 6ba90c3b4f504e15bbb4882af08e1804 of #: unit.accel.AccelUnit.enable_freefall_detection:23 msgid "|enable_freefall_detection2.png|" msgstr "" -#: ../../en/refs/unit.accel.ref:38 52645c0037aa48e28d554ee8a674c0a2 +#: ../../en/refs/unit.accel.ref:38 6d44df84f7f540d6886187414a7056e9 msgid "enable_freefall_detection2.png" msgstr "" -#: 3cc776df0dbe46d09337b3092f2aebe4 -#: driver.adxl34x.ADXL345.enable_motion_detection:1 -#: faf16f66938f4e81921535b2d977b89d of +#: 6e1a73d6647a420f9b7a5315be43265b 7a690f8202c6471da82be9977c4bb023 +#: driver.adxl34x.ADXL345.enable_motion_detection:1 of #: unit.accel.AccelUnit.enable_motion_detection:1 msgid "The activity detection parameters." msgstr "活动检测参数。" -#: 2af48e05c9d34163841412c826899a13 2cd31617ec5840f6ad00c0cd42d706ba +#: 547b32cc15c745bea2e6c4a41191a747 c13671b9d54b4d989fca2dc1b77be0dc #: driver.adxl34x.ADXL345.enable_motion_detection:3 of #: unit.accel.AccelUnit.enable_motion_detection:3 msgid "" @@ -317,44 +315,44 @@ msgid "" "active. The scale factor is 62.5 mg/LSB." msgstr "任何轴上的加速度必须超过该值才能注册为活动值。比例因子为 62.5 mg/LSB。" -#: ea9b06babe094d8889a1af0a990a52c2 of +#: 2f465f589fbb4024b62fe585a094c2a4 of #: unit.accel.AccelUnit.enable_motion_detection:7 msgid "" "If you wish to set them yourself rather than using the defaults, you must" " use keyword arguments::" msgstr "如果您希望自己设置它们而不是使用默认值,则必须使用关键字参数:" -#: dc586d1be7e14e29b8f29da45794982e of +#: 9f2f01b15a1b4392b8fcab7054203e2c of #: unit.accel.AccelUnit.enable_motion_detection:14 msgid "|enable_motion_detection1.png|" msgstr "" -#: ../../en/refs/unit.accel.ref:17 9ec4905731a747639dd9495b64b4a4b0 +#: ../../en/refs/unit.accel.ref:17 e326fb13062f4950aaaf09e610341ae9 msgid "enable_motion_detection1.png" msgstr "" -#: 2030bc78b26d44afa7c5262d0a351fc5 of +#: 3e371f5fe7874fd6ac17733e7e5d055e of #: unit.accel.AccelUnit.enable_motion_detection:16 msgid "|enable_motion_detection2.png|" msgstr "" -#: ../../en/refs/unit.accel.ref:20 b41afad15e074df7a38537df8057387b +#: ../../en/refs/unit.accel.ref:20 7bb56bd62373496fae19df723f47be65 msgid "enable_motion_detection2.png" msgstr "" -#: 0f4e17b09769409d81c386ac3c85e160 a5d75c1baf43425f93bbee56ba246674 +#: 1b276bfad2854526a6b576fc2959b3d7 a243202c7f30497bb5677cdb07641092 #: driver.adxl34x.ADXL345.enable_tap_detection:1 of #: unit.accel.AccelUnit.enable_tap_detection:1 msgid "The tap detection parameters." msgstr "点击检测参数。" -#: 0581eb6464094195af9713574075034e 36a78ce50390455ebb5a086219ff8b80 +#: 7e7232fefc0c4f308c1eca79cac7dba4 8f5ddfa8fda14350b6cb8c304103e6e0 #: driver.adxl34x.ADXL345.enable_tap_detection:3 of #: unit.accel.AccelUnit.enable_tap_detection:3 msgid "1 to detect only single taps, and 2 to detect only double taps." msgstr "1 仅检测单击,2 仅检测双击。" -#: 16f6f3ef59eb4054ad29c69035ab10c2 2e481cf5a5254da79515492ddf95e1b6 +#: 4bae85d8583043ef94f525f9149a89b5 d19488c1e1c44be2b49418b47e41e231 #: driver.adxl34x.ADXL345.enable_tap_detection:6 of #: unit.accel.AccelUnit.enable_tap_detection:6 msgid "" @@ -362,7 +360,7 @@ msgid "" "higher the value the less sensitive the detection." msgstr "点击检测的阈值。比例因子为 62.5 mg/LSB。值越高,检测越不灵敏。" -#: 1c4a077013384bf1b1ef21bed5de8e9e 4d75b154e5d045ceae48f69140e30932 +#: 246363ba0f48479eb814262b8415df24 b82fb242597f4e35a129037bf28e6d32 #: driver.adxl34x.ADXL345.enable_tap_detection:10 of #: unit.accel.AccelUnit.enable_tap_detection:10 msgid "" @@ -370,9 +368,8 @@ msgid "" " ``duration`` won't register as a tap. The scale factor is 625 µs/LSB." msgstr "这将限制脉冲持续时间高于“阈值”。任何超过“持续时间”的脉冲都不会被记录为抽头。比例因子为 625 µs/LSB。" -#: dc21bc277a8e4257bef75db1d0555eb9 -#: driver.adxl34x.ADXL345.enable_tap_detection:14 -#: e43ca940727748e581b840bb5e942b39 of +#: 064fc7d79a0c475ea840cf87285b8f15 2784f3fad3f24a3f9b37d86ec5e560b1 +#: driver.adxl34x.ADXL345.enable_tap_detection:14 of #: unit.accel.AccelUnit.enable_tap_detection:14 msgid "" "(double tap only) The length of time after the initial impulse falls " @@ -380,270 +377,271 @@ msgid "" " scale factor is 1.25 ms/LSB." msgstr "(仅限双击)初始脉冲低于“阈值”后,启动窗口寻找第二个脉冲的时间长度。比例因子为 1.25 ms/LSB。" -#: 29b0e7c1547e47e9acd190f327ae41bc cebbb06083634524b12f844671ff2013 -#: driver.adxl34x.ADXL345.enable_tap_detection:19 of +#: 7566e7c1415344c182771d2cf7969e40 +#: driver.adxl34x.ADXL345.enable_tap_detection:19 +#: e3964e547a8a4a839521fd2ea5ff9e2b of #: unit.accel.AccelUnit.enable_tap_detection:19 msgid "" "(double tap only) The length of the window in which to look for a second " "tap. The scale factor is 1.25 ms/LSB." msgstr "(仅限双击)查找第二次点击的窗口长度。比例因子为 1.25 ms/LSB。" -#: ad671e434a0d47aba0f7219c81e923b2 of +#: ddacd24b1f284a3a960f624d8bed6ad2 of #: unit.accel.AccelUnit.enable_tap_detection:31 msgid "|enable_tap_detection1.png|" msgstr "" -#: ../../en/refs/unit.accel.ref:44 34a7f6cac7f94c3fa31e2eb9944571c8 +#: ../../en/refs/unit.accel.ref:44 98c224ccdaa94c0586982b362814f48a msgid "enable_tap_detection1.png" msgstr "" -#: ffe66494c0514adeaf8deeda55411750 of +#: 5b1d277cad2648beb1959e9f232ecef3 of #: unit.accel.AccelUnit.enable_tap_detection:33 msgid "|enable_tap_detection2.png|" msgstr "" -#: ../../en/refs/unit.accel.ref:47 e2169a9247754595b8f8c2dfa993489d +#: ../../en/refs/unit.accel.ref:47 bf247d82d7ca436f8acda273962fc0f7 msgid "enable_tap_detection2.png" msgstr "" -#: 65ea3876b9744fcb9d3a053962cad146 9b3b76055a3b4afc85a7db7af45f1388 of +#: 2a6baf7d029f48c5b96031cfd69f363c adabe98e0e0a4a8385a1abb764e7c92f of #: unit.accel.ADXL345.acceleration:1 unit.accel.AccelUnit.get_accel:1 msgid "" "The x, y, z acceleration values returned in a 3-tuple in :math:`m / s ^ " "2`." msgstr "以 3 元组形式返回 x、y、z 加速度值,单位为 :math:`m / s ^ 2`。" -#: 940318346dd94dde9c89e07be2a4fa65 a0d91aaafccd42379da585feea2d46d3 -#: b2406c2590e143ac85bd729d98ee118a e130d1ec0031420680512c45b20f30a1 -#: f30099a9108643d99dcb9d7a346bfa2f f9782be99bec44e3832d6819627cb8b0 of +#: 127a2a03c76b4e3a9cf2c825306e2a85 b4ec3829fed946e59bad9c7d8dd5f637 +#: bdb065d30e3c457291664cbf66ad82f5 e2bb6b7e2181422abd1516523683b5f3 +#: e3b57755522244b3bdb259c87aa292ef fa2fc50bb30c40228ef7a67d2bd37e6f of #: unit.accel.AccelUnit.get_accel unit.accel.AccelUnit.get_data_rate #: unit.accel.AccelUnit.get_range unit.accel.AccelUnit.is_freefall #: unit.accel.AccelUnit.is_motion unit.accel.AccelUnit.is_tap msgid "Returns" msgstr "" -#: b1ee5a8c469f4feb83bd0fb3a4d3a149 of unit.accel.AccelUnit.get_accel:3 +#: 9ed6bf3b22374ec798eba2c42449bd9f of unit.accel.AccelUnit.get_accel:3 msgid "x, y, z acceleration values in :math:`m / s ^ 2`." msgstr "x、y、z 加速度值,单位为 :math:`m / s ^ 2` 。" -#: 8df137cbe3474fefb8b36b10623343ac of unit.accel.AccelUnit.get_accel:8 +#: c61a9b33fc304bdea7a71098b28ace9b of unit.accel.AccelUnit.get_accel:8 msgid "|get_accel.svg|" msgstr "" -#: ../../en/refs/unit.accel.ref:12 e80ce14765bb4275b33f1bb0e2a14d27 +#: ../../en/refs/unit.accel.ref:12 001414be2c7346cda721401819a0c6b3 msgid "get_accel.svg" msgstr "" -#: d82c83ec81bf440ba3a5e433a8bc244c of unit.accel.AccelUnit.get_data_rate:1 +#: 2c6d8c3ced3a4ca5b687fb3352164f1f of unit.accel.AccelUnit.get_data_rate:1 msgid "Get the data rate of the sensor." msgstr "获取传感器的数据速率。" -#: 1cd9377b9dc54644b6b99c1fc5813917 502fcc3185f047d196f59b63f628d6f7 -#: d50e7c1b311747e7af6da9185146e24e of unit.accel.ADXL345.data_rate:1 +#: 79324073024a4af5922d00836501057c a8249252fef849fe96d0e2a15cdda2a8 +#: c97c1eaa8bfc4d53b2517d4c81e855e7 of unit.accel.ADXL345.data_rate:1 #: unit.accel.AccelUnit.get_data_rate:3 unit.accel.AccelUnit.set_data_rate:3 msgid "The data rate of the sensor." msgstr "传感器的数据速率。" -#: 505645b6b7f141509251c7f2106a2ca9 of unit.accel.AccelUnit.get_data_rate:8 +#: e49441732afc45638764cb838fc31e21 of unit.accel.AccelUnit.get_data_rate:8 msgid "|get_data_rate.svg|" msgstr "" -#: ../../en/refs/unit.accel.ref:52 1b0b17c29f1d4176af277308fccec106 +#: ../../en/refs/unit.accel.ref:52 126a6cf2c79d48cd8136b85fc0b91cc8 msgid "get_data_rate.svg" msgstr "" -#: fe0584a6a8044527a6b00c22c4905726 of unit.accel.AccelUnit.get_range:1 +#: a743f49e869842ad85df8473255735a0 of unit.accel.AccelUnit.get_range:1 msgid "Get the measurement range of the sensor." msgstr "获取传感器的测量范围。" -#: 5fe669addc804839957e214d4d666249 6a51f7f9b8084d989a13412b27f2f4df -#: b02331963d25429c80cc31c27d9c345a e3c71dd44fd84cb98e8a7b0d8c8f0f2c of +#: 28372fe3638b481c983525769a9a18a9 3f1a124e64ac4590aa050e651a62929c +#: a9dd393069134061a4105d9b5ead9441 b0f5f5dc9e8f453baeb3434d20979781 of #: unit.accel.ADXL345.range:1 unit.accel.AccelUnit.get_range:3 #: unit.accel.AccelUnit.set_range:1 unit.accel.AccelUnit.set_range:3 msgid "The measurement range of the sensor." msgstr "传感器的测量范围。" -#: 39b7c645aa7f44d09fe067ec3e22b97f of unit.accel.AccelUnit.get_range:8 +#: 3f17b660ef624a20becb3a1df0c6cf55 of unit.accel.AccelUnit.get_range:8 msgid "|get_range.svg|" msgstr "" -#: ../../en/refs/unit.accel.ref:58 19f1065b01924debbd3b6ff9ea82713b +#: ../../en/refs/unit.accel.ref:58 a40c3d79249d4825a1799c1967a4da63 msgid "get_range.svg" msgstr "" -#: 91a3fa6b00f94e9693b0a0d4969fb60f of unit.accel.AccelUnit.is_freefall:1 +#: 5cee4757d76a465fa5779f97ba376a5d of unit.accel.AccelUnit.is_freefall:1 msgid "Returns True if freefall has been detected." msgstr "如果检测到自由落体则返回 True。" -#: 75bf28c5b792432baaf4b6c8b4a22f02 of unit.accel.AccelUnit.is_freefall:3 +#: df8c56049fef44ab973160cd8b9a671d of unit.accel.AccelUnit.is_freefall:3 msgid "True if freefall has been detected." msgstr "如果检测到自由落体则返回 True。" -#: 20df10cb371343ce8f6dbfc4dbbb0128 of unit.accel.AccelUnit.is_freefall:8 +#: 94e5848358c74f178195c9a61bf7c691 of unit.accel.AccelUnit.is_freefall:8 msgid "|is_freefall.svg|" msgstr "" -#: ../../en/refs/unit.accel.ref:31 41769eaaec6f41d3a195605d4a887d28 +#: ../../en/refs/unit.accel.ref:31 809186a7b1444b7f95d056d2f4c5c84a msgid "is_freefall.svg" msgstr "" -#: ab3dfd31d2ab48ea90e78a12cef2345a of unit.accel.AccelUnit.is_motion:1 +#: 07c042dab8504ae0a42136b0acd68b63 of unit.accel.AccelUnit.is_motion:1 msgid "Returns True if motion has been detected." msgstr "如果检测到运动则返回 True。" -#: 86a511c869b74d5c99a71ec9e8c02990 of unit.accel.AccelUnit.is_motion:3 +#: 88e351cc3a4e4e5386b8214080a389ec of unit.accel.AccelUnit.is_motion:3 msgid "True if motion has been detected." msgstr "如果检测到运动则返回 True。" -#: 948ebed119be45df8117d004885e9a69 of unit.accel.AccelUnit.is_motion:7 +#: 70a1ca685f504700a2ded6fadfb4db44 of unit.accel.AccelUnit.is_motion:7 msgid "|is_motion.svg|" msgstr "" -#: ../../en/refs/unit.accel.ref:28 c18289bc7cf44dd4843bd8d8e894737e +#: ../../en/refs/unit.accel.ref:28 5453e4cbc1de458cabb4cf01244d35ff msgid "is_motion.svg" msgstr "" -#: 24cf61a3e7ce4f31a34de3e626cab2bc of unit.accel.AccelUnit.is_tap:1 +#: 29d133d198ae4df1bd1a73dd7cb32c95 of unit.accel.AccelUnit.is_tap:1 msgid "Returns True if a tap has been detected." msgstr "如果检测到点击则返回 True。" -#: 07bc222b724b44ab95d66ccb391834b2 of unit.accel.AccelUnit.is_tap:3 +#: 660d5fdea93b4d95b032cd19811cad1d of unit.accel.AccelUnit.is_tap:3 msgid "True if a tap has been detected." msgstr "如果检测到点击则为 True。" -#: ac676267d1744eb4bfd05892fd98eef9 of unit.accel.AccelUnit.is_tap:8 +#: 2a61834f78e04c859f94a9dffa126d48 of unit.accel.AccelUnit.is_tap:8 msgid "|is_tap.svg|" msgstr "" -#: ../../en/refs/unit.accel.ref:25 dafdee8918d64ae8b0c7344a7ab7c5c9 +#: ../../en/refs/unit.accel.ref:25 956fc58f973c496ba9eac25e24915839 msgid "is_tap.svg" msgstr "" -#: e978f771861c4b18b1252277f3d4489c of unit.accel.AccelUnit.set_data_rate:1 +#: a09ea98bcb364a149011d683687d5fa7 of unit.accel.AccelUnit.set_data_rate:1 msgid "Set the data rate of the sensor." msgstr "设置传感器的数据速率。" -#: 1a6bc588efbe4c76aa8aad83fdafa710 of unit.accel.AccelUnit.set_data_rate:7 +#: ffa5cdb8b48244bd91e5648712f30ad4 of unit.accel.AccelUnit.set_data_rate:7 msgid "|set_data_rate.svg|" msgstr "" -#: ../../en/refs/unit.accel.ref:55 9b6b44fc8ae745a6bffe67b27afadf65 +#: ../../en/refs/unit.accel.ref:55 6fc54122a0ab4ab8bbb55088baaa289d msgid "set_data_rate.svg" msgstr "" -#: 02b6ffc6b18c4721a9d53db422a79eb9 of unit.accel.AccelUnit.set_range:7 +#: a481e6f2905f43beba9f079e16f81df2 of unit.accel.AccelUnit.set_range:7 msgid "|set_range.svg|" msgstr "" -#: ../../en/refs/unit.accel.ref:61 27b29aaae465484ca8e2be3ed28c1ac9 +#: ../../en/refs/unit.accel.ref:61 f0ff674c2acf4f0fa7707d769adb3f69 msgid "set_range.svg" msgstr "" -#: ../../en/units/accel.rst:63 d56b0d23506d44d6895d0f52e06384e6 +#: ../../en/units/accel.rst:63 5e126904cd5a45f3964a668fa644668a msgid "ADXL345" msgstr "" -#: 64b74c2096a14f28b8e1c91d1c3cdc08 driver.adxl34x.ADXL345:1 of +#: 96449244ae134184ba24833fd997a36d driver.adxl34x.ADXL345:1 of msgid "Bases: :py:class:`object`" msgstr "" -#: c24bb412875747b49f57e1da503d1a2c driver.adxl34x.ADXL345:1 of +#: 22ea60ecd8aa44d9855cb2e388e90720 driver.adxl34x.ADXL345:1 of msgid "Driver for the ADXL345 3 axis accelerometer." msgstr "ADXL345 3 轴加速度计的驱动程序。" -#: 07b5bc4e781a411e9f78c6cfdffc7e20 driver.adxl34x.ADXL345:3 of +#: 1d9cf86055b64ab9b7372aec1f200dec driver.adxl34x.ADXL345:3 of msgid "The I2C bus the ADXL345 is connected to." msgstr "ADXL345 所连接的 I2C 总线。" -#: 5d0acb125354422e96e9a08903119836 driver.adxl34x.ADXL345:4 of +#: 9c65adf3a75a4f2c880691014453382a driver.adxl34x.ADXL345:4 of msgid "The I2C device address for the sensor. Default is :const:`0x53`." msgstr "传感器的 I2C 设备地址。默认值为 :const:`0x53`。" -#: 169e9cd71a0b4c458060ce6aeea5f288 driver.adxl34x.ADXL345:7 of +#: 8c0d48fae86f428d9ead4f6cb74f84d1 driver.adxl34x.ADXL345:7 of msgid "**Quickstart: Importing and using the device**" msgstr "**快速入门:导入和使用设备**" -#: d64e094480b048809f11cb1bc37e45f1 driver.adxl34x.ADXL345:9 of +#: driver.adxl34x.ADXL345:9 ff841d2f966e4231b2c542ea8d4d9f5e of msgid "" "Here is an example of using the :class:`ADXL345` class. First you will " "need to import the libraries to use the sensor:" msgstr "以下是使用 :class:`ADXL345` 类的示例。首先,您需要导入库才能使用传感器:" -#: de8072ff643c4bf8a91deb1c129222f6 driver.adxl34x.ADXL345:17 of +#: 7975488392f046f7b80a9c998b593820 driver.adxl34x.ADXL345:17 of msgid "" "Once this is done you can define your `I2C` object and define your sensor" " object:" msgstr "完成后,您可以定义 `I2C` 对象并定义传感器对象:" -#: a468dc01f3cf4c2dba3627e7840a6a27 driver.adxl34x.ADXL345:25 of +#: 8811065014e24104b9f57ed7dc83f069 driver.adxl34x.ADXL345:25 of msgid "Now you have access to the :attr:`acceleration` attribute:" msgstr "现在你可以访问 :attr:`acceleration` 属性:" -#: a95711f54f794202a89cd31b53875d3c of unit.accel.ADXL345.events:1 +#: 8fc0baf7b8af4fab8befcdb2b14ff263 of unit.accel.ADXL345.events:1 msgid "" ":attr:`events` will return a dictionary with a key for each event type " "that has been enabled." msgstr ":attr:`events` 将返回一个字典,其中包含已启用的每个事件类型的键。" -#: db3cf406e9c4492cb90cf03052b70e56 of unit.accel.ADXL345.events:4 +#: ad70e97cf843429896fc949540911fe8 of unit.accel.ADXL345.events:4 msgid "The possible keys are:" msgstr "可能的关键字有:" -#: 008ea1d3fa5a4d11ad9109ad09389c52 of unit.accel.ADXL345.events:7 +#: 13d40541a66e475d9f1cae8fdcbdd3fb of unit.accel.ADXL345.events:7 msgid "Key" msgstr "关键字" -#: 1385398077b548488be7017b036dfac9 of unit.accel.ADXL345.events:7 +#: fb3725cc0f27447596e1cefe44b0cbb0 of unit.accel.ADXL345.events:7 msgid "Description" msgstr "描述" -#: 2425e677a90d48c8b15520aa4f49628c of unit.accel.ADXL345.events:9 +#: 9d86454478ae44ba99f7ff349c4dbf1d of unit.accel.ADXL345.events:9 msgid "``tap``" msgstr "" -#: c78c95f8c45145b296ceac02dd4524ff of unit.accel.ADXL345.events:9 +#: 593ae3f326c540bab6fe67296362eae4 of unit.accel.ADXL345.events:9 msgid "" "True if a tap was detected recently. Whether it's looking for a single or" " double tap is determined by the tap param of `enable_tap_detection`." msgstr "如果最近检测到点击,则为 True。是否寻找单击或双击取决于 `enable_tap_detection` 的点击参数。" -#: ff16b9697594455b8c571ffbb4246b3d of unit.accel.ADXL345.events:12 +#: 3bb87819a3254042bd92e47a046a2f59 of unit.accel.ADXL345.events:12 msgid "``motion``" msgstr "" -#: 65929031848e437a89108d0bfc4084bd of unit.accel.ADXL345.events:12 +#: 2e53cc23d7934389b94de9af8867335e of unit.accel.ADXL345.events:12 msgid "" "True if the sensor has seen acceleration above the threshold set with " "`enable_motion_detection`." msgstr "如果传感器检测到的加速度高于使用 `enable_motion_detection` 设置的阈值,则为真。" -#: db33facb67054b5cb6a6404827ab19bb of unit.accel.ADXL345.events:15 +#: 7ff171f6349e4962ade65e516e524cfe of unit.accel.ADXL345.events:15 msgid "``freefall``" msgstr "" -#: f6589a86806848b28e440357a1eae914 of unit.accel.ADXL345.events:15 +#: 209ab0f6372b4514aff77bb6009224dd of unit.accel.ADXL345.events:15 msgid "" "True if the sensor was in freefall. Parameters are set when enabled with " "`enable_freefall_detection`." msgstr "如果传感器处于自由落体状态,则为 True。使用 `enable_freefall_detection` 启用时设置参数。" -#: 27ed22790071433991b1803edf4f12dd of unit.accel.ADXL345.offset:1 +#: 406f7311d02f4f9bbd61b6b27fb6d158 of unit.accel.ADXL345.offset:1 msgid "The x, y, z offsets as a tuple of raw count values." msgstr "x、y、z 偏移量作为原始计数值的元组。" -#: ad10a565f063462192c5b15940823f2c of unit.accel.ADXL345.offset:3 +#: 90c08af4f2ed4314b8c710f05377fa1b of unit.accel.ADXL345.offset:3 msgid "See offset_calibration example for usage." msgstr "请参阅 offset_calibration 示例以了解用法。" -#: 7ce2540ce44c43a8a899609cadc66752 of unit.accel.ADXL345.raw_x:1 +#: efdb10564d314681b18b6ba7fef159e3 of unit.accel.ADXL345.raw_x:1 msgid "The raw x value." msgstr "原始 x 轴的值。" -#: b339531e92c0495fbe4abc28d0f3fb17 of unit.accel.ADXL345.raw_y:1 +#: 1180de999d334c99a182eeb9c18510e5 of unit.accel.ADXL345.raw_y:1 msgid "The raw y value." msgstr "原始 y 轴的值。" -#: 798ad3715a3840f4bece0109f8ea5a51 of unit.accel.ADXL345.raw_z:1 +#: ca4c7f8dec86476bafd9a91a18ac83b3 of unit.accel.ADXL345.raw_z:1 msgid "The raw z value." msgstr "原始 z 轴的值。" diff --git a/docs/locales/zh_CN/LC_MESSAGES/units/glass.po b/docs/locales/zh_CN/LC_MESSAGES/units/glass.po index b3cac4d8..e2fd803b 100644 --- a/docs/locales/zh_CN/LC_MESSAGES/units/glass.po +++ b/docs/locales/zh_CN/LC_MESSAGES/units/glass.po @@ -9,7 +9,7 @@ msgid "" msgstr "" "Project-Id-Version: UIFlow2 Programming Guide \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-01-16 15:35+0800\n" +"POT-Creation-Date: 2025-03-18 17:35+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language: zh_CN\n" @@ -20,86 +20,122 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.16.0\n" -#: ../../en/units/glass.rst:3 460a75d7557a4d30b6f82b4f4771316a +#: ../../en/units/glass.rst:2 eed38334f888418fa84de7287982ff8c msgid "Glass Unit" msgstr "" -#: ../../en/units/glass.rst:7 f57b72cda2fc41c6a296c8a1c34a578c +#: ../../en/units/glass.rst:6 3788f83a654a42ff9650b8f23d1850f2 msgid "" "Unit Glass is a 1.51-inch transparent OLED expansion screen unit. It " "adopts STM32+SSD1309 driver scheme,resolution is 128*64, monochrome " "display, transparent area is 128*56." msgstr "" +"Unit " +"Glass是一款1.51英寸透明OLED扩展屏单元,采用STM32+SSD1309驱动方案,分辨率为128*64,单色显示,透明区域为128*56。" -#: ../../en/units/glass.rst:9 5932c479c8f24e82aad18b062c1a770e +#: ../../en/units/glass.rst:8 db983c70c8e3458ca3d9fc81cacd1b32 msgid "Support the following products:" -msgstr "" +msgstr "支持以下产品:" -#: ../../en/units/glass.rst:11 d99191de275448cdafa8d5b66fcb7658 +#: ../../en/units/glass.rst:10 59a1d1211bfb4518bd227fec04ee5d2c msgid "|GlassUnit|" msgstr "" -#: ../../en/refs/unit.glass.ref 89f1c76271e449f486ab189089154e9b +#: ../../en/refs/unit.glass.ref b3e29c391c5947449efe1ec43cade9ec msgid "GlassUnit" msgstr "" -#: ../../en/units/glass.rst:13 c249f527d2ee4b37a5e03ab387f74dce -msgid "Micropython Example:" -msgstr "" +#: ../../en/units/glass.rst:14 f15f21f999cb47d48b633db08d85df9b +msgid "UiFlow2 Example" +msgstr "UiFlow2 应用示例" -#: ../../en/units/glass.rst:20 193542c05cce4d859614c09dc627bf9c -msgid "UIFLOW2 Example:" -msgstr "" +#: ../../en/units/glass.rst:17 ../../en/units/glass.rst:36 +#: 0dd2d5e04f4442ff8744f3b2968fe656 a2a7e44275244319aaf306be611c8fe2 +msgid "Draw Text" +msgstr "绘制文本" + +#: ../../en/units/glass.rst:19 cd6fe49e6fee469380d0cde596187894 +msgid "Open the |cores3_glass_example.m5f2| project in UiFlow2." +msgstr "在 UiFlow2 中打开 |cores3_glass_example.m5f2| 项目。" + +#: ../../en/units/glass.rst:21 ../../en/units/glass.rst:38 +#: 1337d02c2df240ebb2c707710414f065 32f72c456a1a4d488def2af58538403c +msgid "This example displays the text \"GLASS\" on the screen." +msgstr "此示例在屏幕上显示文本“GLASS”。" -#: ../../en/units/glass.rst:22 5f300e30370d4f87bfac901844719ca7 +#: ../../en/units/glass.rst:23 4c14a9cf101646b184563cab403c85e4 +#: 8ec756921e25470983b1d2ab0a2f27f2 of unit.glass.GlassUnit:7 +msgid "UiFlow2 Code Block:" +msgstr "UiFlow2 代码块:" + +#: ../../en/units/glass.rst:25 a59581e18b9f449d9e7fb991a115f49c msgid "|example.png|" msgstr "" -#: ../../en/refs/unit.glass.ref:9 f60fef80be004979af17e3658ab4b791 +#: ../../en/refs/unit.glass.ref:9 b17d03c9be8f49248ccb657fcb79271b msgid "example.png" msgstr "" -#: ../../en/units/glass.rst:27 9cc2d313649849c3aa2f3cb4ddf82f30 -msgid "|cores3_glass_example.m5f2|" -msgstr "" +#: ../../en/units/glass.rst:27 ../../en/units/glass.rst:46 +#: 05d3ae8c49074b7aae639430a9783581 62babb03d927420eb133e03d24735db4 +msgid "Example output:" +msgstr "示例输出:" -#: ../../en/units/glass.rst:31 dc70cf872b784e46ac9937e2b74c4f7c -msgid "class GlassUnit" +#: ../../en/units/glass.rst:29 ../../en/units/glass.rst:48 +#: 70fe4a2ea47749399c2a2e16fb683240 d9a41ef7d1974391b30718c21ab76a2c +msgid "None" msgstr "" -#: ../../en/units/glass.rst:34 f331ca890305447ba4f3e3f3b53ab4ef -msgid "Constructors" -msgstr "" +#: ../../en/units/glass.rst:33 e6910ec8da174b73949c69f0b839f6f9 +msgid "MicroPython Example" +msgstr "MicroPython 应用示例" -#: ../../en/units/glass.rst:38 f9d62955ef774483a7d21f6bd8b667b5 -msgid "Initialize the Unit Glass" -msgstr "" +#: ../../en/units/glass.rst:40 e6910ec8da174b73949c69f0b839f6f9 of +#: unit.glass.GlassUnit:11 +msgid "MicroPython Code Block:" +msgstr "MicroPython 代码块:" -#: ../../en/units/glass.rst f5a82523c2364e588de06137aa621251 -msgid "Parameters" -msgstr "" +#: ../../en/units/glass.rst:52 acf9207146994bc6944d19c2e5e4ca58 +msgid "**API**" +msgstr "API参考" -#: ../../en/units/glass.rst:40 2f7b2235a2084698952c50e295f00e29 -msgid "the I2C object." +#: ../../en/units/glass.rst:55 f7ab95afeffa4d2aa89ecfbc0ec6fda8 +msgid "class GlassUnit" msgstr "" -#: ../../en/units/glass.rst:41 dc721f20fbd445fa84aed841990cd7b3 -msgid "I2C address of the Unit Glass, default is 0x3D." +#: f5c357ad1c3c46139d6de9d258be634c of unit.glass.GlassUnit:1 +msgid "Bases: :py:class:`object`" msgstr "" -#: ../../en/units/glass.rst:42 f4e539e5ece34781b3ac041d07015934 -msgid "I2C frequency of the Unit Glass." -msgstr "" +#: 39442969c95b4a94be962f10196f19a7 of unit.glass.GlassUnit:1 +msgid "Initialize the Glass Unit." +msgstr "初始化 Glass Unit。" -#: ../../en/units/glass.rst:44 ab5cd65499cb441cbdcc36e09537f6ed -msgid "UIFLOW2:" +#: ../../en/units/glass.rst 6e2eb4a45c3f4053ba5fdc76077c4c9b +msgid "Parameters" msgstr "" -#: ../../en/units/glass.rst:46 8a1c893b309847a98dee1265e4d5971f +#: 853b7746e1594a54840573327f35d301 of unit.glass.GlassUnit:3 +msgid "The I2C bus the Glass Unit is connected to." +msgstr "Glass Unit 所连接的 I2C 总线。" + +#: 40d1524cd85444628380f58c9764d1de of unit.glass.GlassUnit:5 +msgid "The I2C address of the Glass Unit, default is 0x3D." +msgstr "Glass Unit 的 I2C 地址,默认为 0x3D。" + +#: 553f736aea7a4a9bb078b8d75e969c7f of unit.glass.GlassUnit:9 msgid "|init.png|" msgstr "" -#: ../../en/refs/unit.glass.ref:7 6fcd4398f1194ad2b66ce66fe36451d9 +#: ../../en/refs/unit.glass.ref:7 d804f983d0e34a0fa5aff050d51aa368 msgid "init.png" msgstr "" +#: ../../en/units/glass.rst:60 c79e957cb9a745ed8c4f07242e3475ae +msgid "" +"GlassUnit class inherits Display class, See :ref:`hardware.Display " +"` for more details." +msgstr "" +"GlassUnit 类继承了 Display 类,有关更多详细信息,请参阅 :ref:`hardware.Display " +"` 。" + diff --git a/docs/locales/zh_CN/LC_MESSAGES/units/glass2.po b/docs/locales/zh_CN/LC_MESSAGES/units/glass2.po index 1da91df4..c873c12b 100644 --- a/docs/locales/zh_CN/LC_MESSAGES/units/glass2.po +++ b/docs/locales/zh_CN/LC_MESSAGES/units/glass2.po @@ -9,7 +9,7 @@ msgid "" msgstr "" "Project-Id-Version: UIFlow2 Programming Guide \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-01-16 15:35+0800\n" +"POT-Creation-Date: 2025-03-18 17:35+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language: zh_CN\n" @@ -20,85 +20,119 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.16.0\n" -#: ../../en/units/glass2.rst:3 06b3a634a73d49c7a73168fdae4cb709 +#: ../../en/units/glass2.rst:2 7806212935214173968f248d4f0f5cbf msgid "Glass2 Unit" msgstr "" -#: ../../en/units/glass2.rst:7 d8fa2fec303844e4b1a376dca23e6850 +#: ../../en/units/glass2.rst:6 56f4d8ae53e2463fbbce2cfe057d34af msgid "" "Glass2 Unit is a 1.51-inch transparent OLED display unit that adopts the " "SSD1309 driver solution." -msgstr "" +msgstr "Glass2 Unit是一款1.51英寸透明OLED显示单元,采用SSD1309驱动方案。" -#: ../../en/units/glass2.rst:9 fc827147fa3e474fba886920a189163f +#: ../../en/units/glass2.rst:8 1f0890e509044e67bda2e5426ccca03d msgid "Support the following products:" -msgstr "" +msgstr "支持以下产品:" -#: ../../en/units/glass2.rst:11 bfef52e136fb410e859675dfdef9d9fe +#: ../../en/units/glass2.rst:10 a618d7e04348429eb929cb1e468a6c5e msgid "|Glass2Unit|" msgstr "" -#: ../../en/refs/unit.glass2.ref 90cdb70010ac4211adae2e8a1f7f683e +#: ../../en/refs/unit.glass2.ref 2c3a84eed7bd43b5b7ca1bd38d9831ee msgid "Glass2Unit" msgstr "" -#: ../../en/units/glass2.rst:13 e642f1ac28224292925f5295cc560562 -msgid "Micropython Example:" -msgstr "" +#: ../../en/units/glass2.rst:14 775fda7cc35b44bcae30809a2f6f748d +msgid "UiFlow2 Example" +msgstr "UiFlow2 应用示例" -#: ../../en/units/glass2.rst:20 c2b45d9b51594e0fa53859006a3a1b6a -msgid "UIFLOW2 Example:" -msgstr "" +#: ../../en/units/glass2.rst:17 ../../en/units/glass2.rst:36 +#: f5921c9af37c40ed8de63baf87495cd3 +msgid "Draw Text" +msgstr "绘制文本" + +#: ../../en/units/glass2.rst:19 78317ee3ab9b470aa83526d28de1275a +msgid "Open the |cores3_glass2_example.m5f2| project in UiFlow2." +msgstr "在 UiFlow2 中打开 |cores3_glass2_example.m5f2| 项目。" + +#: ../../en/units/glass2.rst:21 ../../en/units/glass2.rst:38 +#: 26073a2c71bc42ab965a1ad7ba5dcdcf +msgid "This example displays the text \"GLASS2\" on the screen." +msgstr "此示例在屏幕上显示文本“GLASS2”。" -#: ../../en/units/glass2.rst:22 20aebbc0a5054446be80195e5f02b49b +#: ../../en/units/glass2.rst:23 484ae652e66f46459674744bdce99887 of +#: unit.glass2.Glass2Unit:7 +msgid "UiFlow2 Code Block:" +msgstr "UiFlow2 代码块:" + +#: ../../en/units/glass2.rst:25 68f0ac80f83349219fc238804ca106e6 msgid "|example.png|" msgstr "" -#: ../../en/refs/unit.glass2.ref:9 349f2227ce424135afe921295061a10b +#: ../../en/refs/unit.glass2.ref:9 f74547368aa147de82106f8992ec87b0 msgid "example.png" msgstr "" -#: ../../en/units/glass2.rst:27 ccb946f53d154a6986bc56658b839bd8 -msgid "|cores3_glass2_example.m5f2|" -msgstr "" +#: ../../en/units/glass2.rst:27 ../../en/units/glass2.rst:46 +#: cae3210226044a5387e1c7888e866220 +msgid "Example output:" +msgstr "示例输出:" -#: ../../en/units/glass2.rst:30 37c9c7562fc2448f8a807bcb84ecf596 -msgid "class Glass2Unit" +#: ../../en/units/glass2.rst:29 ../../en/units/glass2.rst:48 +#: e8a906c96479488ea032e5ad79d9a192 +msgid "None" msgstr "" -#: ../../en/units/glass2.rst:33 e640d0bfae9d4e87ad09fa6603c54476 -msgid "Constructors" -msgstr "" +#: ../../en/units/glass2.rst:33 775fda7cc35b44bcae30809a2f6f748d +msgid "MicroPython Example" +msgstr "MicroPython 应用示例" -#: ../../en/units/glass2.rst:37 6f72ed509e684723b68ac19ce67a5aeb -msgid "Initialize the Unit Glass2" -msgstr "" +#: ../../en/units/glass2.rst:40 775fda7cc35b44bcae30809a2f6f748d of +#: unit.glass2.Glass2Unit:11 +msgid "MicroPython Code Block:" +msgstr "MicroPython 代码块:" -#: ../../en/units/glass2.rst 1b8739be2c5746ad905ff9234d7c7c7f -msgid "Parameters" -msgstr "" +#: ../../en/units/glass2.rst:52 39ebd240b74840b9898e7cdb8b7eea2f +msgid "**API**" +msgstr "API参考" -#: ../../en/units/glass2.rst:39 359ad93073b04718b03687a963e5b4d8 -msgid "the I2C object." +#: ../../en/units/glass2.rst:55 d36691fb564f48fa9874d1eb450450b0 +msgid "class Glass2Unit" msgstr "" -#: ../../en/units/glass2.rst:40 0a432b226dec439b968c2292f3324ec4 -msgid "I2C address of the Unit Glass2, default is 0x3c." +#: d80b527cbbdb412cbc26896c6d6e0563 of unit.glass2.Glass2Unit:1 +msgid "Bases: :py:class:`object`" msgstr "" -#: ../../en/units/glass2.rst:41 ee98e127660b44b8a65fd8f4b48e045f -msgid "I2C frequency of the Unit Glass2." -msgstr "" +#: 714581982b6c4a4a9caed8c2f56cbad6 of unit.glass2.Glass2Unit:1 +msgid "Initialize the Glass2 Unit." +msgstr "初始化 Glass2 Unit。" -#: ../../en/units/glass2.rst:43 395e48f6fd2443cc914f85f93c2e2092 -msgid "UIFLOW2:" +#: ../../en/units/glass2.rst 788c17ff03cd4e4b9f150ccb77ccd12c +msgid "Parameters" msgstr "" -#: ../../en/units/glass2.rst:45 16b5f8c5daa14ef18acb506e3be1a21a +#: ef8d374dfedf43489fb0f475068ee013 of unit.glass2.Glass2Unit:3 +msgid "The I2C bus the Glass2 Unit is connected to." +msgstr "Glass2 Unit 所连接的 I2C 总线。" + +#: 691c85eb2f35494aa5986d914c222767 of unit.glass2.Glass2Unit:5 +msgid "The I2C address of the Glass2 Unit, default is 0x3C." +msgstr "Glass2 Unit 的 I2C 地址,默认为 0x3C。" + +#: c0648f2334474e9e8dfd2c5f90fd5a76 of unit.glass2.Glass2Unit:9 msgid "|init.png|" msgstr "" -#: ../../en/refs/unit.glass2.ref:7 69b5a4a39f5e4e57960c1bd336f94384 +#: ../../en/refs/unit.glass2.ref:7 a9bf3fb23f8844398aa693d426ac379c msgid "init.png" msgstr "" +#: ../../en/units/glass2.rst:60 dc3617e3ef6847168492263b0c581877 +msgid "" +"Glass2Unit class inherits Display class, See :ref:`hardware.Display " +"` for more details." +msgstr "" +"Glass2Unit 类继承了 Display 类,有关更多详细信息,请参阅 :ref:`hardware.Display " +"` 。" + diff --git a/docs/locales/zh_CN/LC_MESSAGES/units/lcd.po b/docs/locales/zh_CN/LC_MESSAGES/units/lcd.po index a7a3be33..d773a3db 100644 --- a/docs/locales/zh_CN/LC_MESSAGES/units/lcd.po +++ b/docs/locales/zh_CN/LC_MESSAGES/units/lcd.po @@ -9,7 +9,7 @@ msgid "" msgstr "" "Project-Id-Version: UIFlow2 Programming Guide \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-01-16 15:35+0800\n" +"POT-Creation-Date: 2025-03-18 17:35+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language: zh_CN\n" @@ -20,11 +20,11 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.16.0\n" -#: ../../en/units/lcd.rst:2 3e17635765cb469283d3a59f4d4e6f6a +#: ../../en/units/lcd.rst:2 5594214ce6eb40f58ecfcf1244630525 msgid "LCD Unit" msgstr "" -#: ../../en/units/lcd.rst:7 5ac44751536a4cf38ec09a127b634ebe +#: ../../en/units/lcd.rst:8 fc6a8958835d4ad0a548d432cee88c5a msgid "" "Unit LCD is a 1.14 inch color LCD expansion screen unit. It adopts " "ST7789V2 drive scheme, the resolution is 135*240, and it supports RGB666 " @@ -36,76 +36,114 @@ msgid "" "screen extension is suitable for embedding in various instruments or " "control devices that need to display simple content as a display panel." msgstr "" +"Unit LCD 为 1.14 英寸彩色 LCD 扩展屏单元。采用 ST7789V2 驱动方案,分辨率为 135*240,支持 RGB666 " +"显示(262144 种颜色)。内部集成 ESP32-PICO 控制核心(内置固件,显示开发更便捷),支持通过 " +"I2C(addr:0x3E)通信接口进行控制和固件升级。屏幕背面采用一体化磁吸设计,可轻松吸附在金属表面进行固定。该 LCD " +"屏扩展适合嵌入到各类需要作为显示面板显示简单内容的仪器或控制设备中。" -#: ../../en/units/lcd.rst:18 56d65490ae8a4c98a18fce6afb6e6aad +#: ../../en/units/lcd.rst:18 bc93ef2bda13497cb9f0f24ddcfb443d msgid "Support the following products:" -msgstr "" +msgstr "支持以下产品:" -#: ../../en/units/lcd.rst:20 68c26ed3e1214b539a82ee146cf4fa98 +#: ../../en/units/lcd.rst:20 c4b88defc7fb4ad28e6548332aaf2a59 msgid "|LCDUnit|" msgstr "" -#: ../../en/refs/unit.lcd.ref c5b81a08496d43bd84e94231ca57d1a6 +#: ../../en/refs/unit.lcd.ref b8b0516e7239405bb4d56f4bc339ceb8 msgid "LCDUnit" msgstr "" -#: ../../en/units/lcd.rst:23 a760cef984cf44e1bb36880fb3188993 -msgid "Micropython Example:" -msgstr "" +#: ../../en/units/lcd.rst:24 5e832522088c4684b490b266c34bb1ac +msgid "UiFlow2 Example" +msgstr "UiFlow2 应用示例" -#: ../../en/units/lcd.rst:30 a58c4c19239943ca90e065f76fd2ee1c -msgid "UIFLOW2 Example:" -msgstr "" +#: ../../en/units/lcd.rst:27 ../../en/units/lcd.rst:46 +#: 0440caa7996e4d9f96718602b6719cb7 858d8e05b5b64770ab12957eb65a9c6a +msgid "Draw Text" +msgstr "绘制文本" + +#: ../../en/units/lcd.rst:29 5b1cffcc8f0d4867b1281615e5a73810 +msgid "Open the |cores3_lcd_example.m5f2| project in UiFlow2." +msgstr "在 UiFlow2 中打开 |cores3_lcd_example.m5f2| 项目。" + +#: ../../en/units/lcd.rst:31 ../../en/units/lcd.rst:48 +#: c4751f7d70234f5c99b3048e011fa702 ef24fd949d15467d957abe6e499b2d50 +msgid "This example displays the text \"LCD\" on the screen." +msgstr "此示例在屏幕上显示文本“LCD”。" -#: ../../en/units/lcd.rst:32 18f725a7ddf144b69d9e1d9ee2cf55bf +#: ../../en/units/lcd.rst:33 a70978c4c9a94962a3adfe4f245137f6 +#: b54605391f4e4a08977b31b23de0cf7d of unit.lcd.LCDUnit:7 +msgid "UiFlow2 Code Block:" +msgstr "UiFlow2 代码块:" + +#: ../../en/units/lcd.rst:35 da4d4b3dc5a6483ba7b515a9e7f630d0 msgid "|example.png|" msgstr "" -#: ../../en/refs/unit.lcd.ref:9 92a23150006e4489a32da0d01fabb9ec +#: ../../en/refs/unit.lcd.ref:9 7857b745903a4f62b3a1c6cc5940fee7 msgid "example.png" msgstr "" -#: ../../en/units/lcd.rst:37 a2ad2740360d46faad101ca05a265b40 -msgid "|cores3_lcd_example.m5f2|" -msgstr "" +#: ../../en/units/lcd.rst:37 ../../en/units/lcd.rst:56 +#: 4a16fe1148dc4c3691d159d73c655b19 b7e378ca11a64cffa82c4fef2a650f96 +msgid "Example output:" +msgstr "示例输出:" -#: ../../en/units/lcd.rst:41 252c2d4aab1e4e8d9d3d30ec6ac0d477 -msgid "class LCDUnit" +#: ../../en/units/lcd.rst:39 ../../en/units/lcd.rst:58 +#: 8640bdf82c804821b1270c0e570801ef cb7bd3d911aa468580665a4ce867d4ef +msgid "None" msgstr "" -#: ../../en/units/lcd.rst:44 ad03476a4ca148e386272d29c82b9e66 -msgid "Constructors" -msgstr "" +#: ../../en/units/lcd.rst:43 f75fcd38a6c54369910d6cd992d6dd30 +msgid "MicroPython Example" +msgstr "MicroPython 应用示例" -#: ../../en/units/lcd.rst:48 abf9fa88d4a9422a9970aee135b3d451 -msgid "Initialize the Unit LCD" -msgstr "" +#: ../../en/units/lcd.rst:50 f75fcd38a6c54369910d6cd992d6dd30 of +#: unit.lcd.LCDUnit:11 +msgid "MicroPython Code Block:" +msgstr "MicroPython 代码块:" -#: ../../en/units/lcd.rst 85036a7cd47b482c84cb640b93ec9ed1 -msgid "Parameters" -msgstr "" +#: ../../en/units/lcd.rst:62 1ba300444b544353a67759b8e4e8feeb +msgid "**API**" +msgstr "API参考" -#: ../../en/units/lcd.rst:50 5ebf59e9a37c475c994a129fec37a85e -msgid "the I2C object." +#: ../../en/units/lcd.rst:65 08c546e535884f73bc859d30d9a1b974 +msgid "class LCDUnit" msgstr "" -#: ../../en/units/lcd.rst:51 37f895bc63e04fec944140e7581560d6 -msgid "I2C address of the Unit LCDUnit, default is 0x3e." +#: ff3d764b7ee242bfbff406d05297c591 of unit.lcd.LCDUnit:1 +msgid "Bases: :py:class:`object`" msgstr "" -#: ../../en/units/lcd.rst:52 2b662e0ae15e456ea67be0df6e52a239 -msgid "I2C frequency of the Unit LCDUnit." -msgstr "" +#: 0bf66ad365cb413e876d5684fe2247d9 of unit.lcd.LCDUnit:1 +msgid "Initialize the LCD Unit." +msgstr "初始化 LCD Unit。" -#: ../../en/units/lcd.rst:54 0600cd1f6ce0442780fce18d6a44b164 -msgid "UIFLOW2:" +#: ../../en/units/lcd.rst 188924d834474a40accc18e784e59db3 +msgid "Parameters" msgstr "" -#: ../../en/units/lcd.rst:56 7ace95e9bfc84efc8d74ba7bd264da4c +#: dd26e83c28724aa3afb317256ba48a1f of unit.lcd.LCDUnit:3 +msgid "The I2C bus the LCD Unit is connected to." +msgstr "LCD Unit 所连接的 I2C 总线。" + +#: 56cc4dda622345c59f37f5679dd51f32 of unit.lcd.LCDUnit:5 +msgid "The I2C address of the LCD Unit, default is 0x3E." +msgstr "LCD Unit 的 I2C 地址,默认为 0x3E。" + +#: 20fd7bad9cd8433d8e1bb57124a1ad7a of unit.lcd.LCDUnit:9 msgid "|init.png|" msgstr "" -#: ../../en/refs/unit.lcd.ref:7 eb37208beafa4a278826f506efebefe2 +#: ../../en/refs/unit.lcd.ref:7 2d437ff109b84d97930dc67e774cd980 msgid "init.png" msgstr "" +"LCDUnit 类继承了 Display 类,有关更多详细信息,请参阅 :ref:`hardware.Display " +"` 。" + +#: ../../en/units/lcd.rst:70 69267cb704474b1ea43e9d5b2166e9ad +msgid "" +"LCDUnit class inherits Display class, See :ref:`hardware.Display " +"` for more details." +msgstr "" diff --git a/docs/locales/zh_CN/LC_MESSAGES/units/minioled.po b/docs/locales/zh_CN/LC_MESSAGES/units/minioled.po index 52dc88cd..74bcf952 100644 --- a/docs/locales/zh_CN/LC_MESSAGES/units/minioled.po +++ b/docs/locales/zh_CN/LC_MESSAGES/units/minioled.po @@ -9,7 +9,7 @@ msgid "" msgstr "" "Project-Id-Version: UIFlow2 Programming Guide \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-01-16 15:35+0800\n" +"POT-Creation-Date: 2025-03-18 17:35+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language: zh_CN\n" @@ -20,89 +20,119 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.16.0\n" -#: ../../en/units/minioled.rst:3 da14fb9311164139b0ac103b18b62876 +#: ../../en/units/minioled.rst:3 c978f6eac7b84e5697c5c7757807d79e msgid "MiniOLED Unit" msgstr "" -#: ../../en/units/minioled.rst:7 dae2172db0e744ee8e7755335ef75113 +#: ../../en/units/minioled.rst:7 3c761a809ab64c54b8c488b9a37a49c4 msgid "" "MiniOLED UNIT is a 0.42-inch I2C interface OLED screen unit, it's a " "72*40, monochrome white display." -msgstr "" +msgstr "MiniOLED UNIT 是一颗0.42英寸I2C接口OLED屏幕单元,尺寸为72*40,单色白色显示屏。" -#: ../../en/units/minioled.rst:9 aaaf5eda1c0c423ba9feea66765d5074 +#: ../../en/units/minioled.rst:9 8c8100a8b1db46e29d2d06dd6c8c9132 msgid "Support the following products:" -msgstr "" +msgstr "支持以下产品:" -#: ../../en/units/minioled.rst:11 9b869a78924a4f8589b93016c21251a7 +#: ../../en/units/minioled.rst:11 45887e6ecd844e05ba502b35e247112e msgid "|MiniOLEDUnit|" msgstr "" -#: ../../en/refs/unit.minioled.ref 349b75361aa9488ca3a3ae37a9666f20 +#: ../../en/refs/unit.minioled.ref e279a9a1c79a4cf58d63d2cec4836c5b msgid "MiniOLEDUnit" msgstr "" -#: ../../en/units/minioled.rst:13 f1c9c1996453472f836190f7af32b593 -msgid "Micropython Example:" -msgstr "" +#: ../../en/units/minioled.rst:15 15e091cf4e734f0fbd07232488a39333 +msgid "UiFlow2 Example" +msgstr "UiFlow2 应用示例" -#: ../../en/units/minioled.rst:20 9d43bc141d1342a19789d73be3ff13a4 -msgid "UIFLOW2 Example:" -msgstr "" +#: ../../en/units/minioled.rst:18 ../../en/units/minioled.rst:37 +#: 7c816e36b8f7406098c9f8c039841da1 c4177e45cca0462b91c4703eadea21c0 +msgid "Draw Text" +msgstr "绘制文本" + +#: ../../en/units/minioled.rst:20 d5aacb5486e94be3bc2f5141a51cfac3 +msgid "Open the |cores3_minioled_example.m5f2| project in UiFlow2." +msgstr "在 UiFlow2 中打开 |cores3_minioled_example.m5f2| 项目。" -#: ../../en/units/minioled.rst:22 ce0d8062d17f4e7a98cde2108283592e +#: ../../en/units/minioled.rst:22 ../../en/units/minioled.rst:39 +#: 3422c46b75da4c0581c156723054b391 8949432b1ac945c1ae5c55d7b12012e2 +msgid "This example displays the text \"Mini\" on the screen." +msgstr "此示例在屏幕上显示文本“Mini”。" + +#: ../../en/units/minioled.rst:24 1f61350a281b4792a1d2587e3e514046 +#: c1f71ed5be674a20b07e387c32f281b9 of unit.minioled.MiniOLEDUnit:7 +msgid "UiFlow2 Code Block:" +msgstr "UiFlow2 代码块:" + +#: ../../en/units/minioled.rst:26 c56c5eea5c334fa588bf2e45ca4c6436 msgid "|example.png|" msgstr "" -#: ../../en/refs/unit.minioled.ref:9 9d96e239c65e48aab811797838d34af6 +#: ../../en/refs/unit.minioled.ref:9 9687518782cb4de1acdb6fa810d01b3e msgid "example.png" msgstr "" -#: ../../en/units/minioled.rst:27 e95666c3074e436e9f1f7dc314ddb142 -msgid "|cores3_minioled_example.m5f2|" -msgstr "" +#: ../../en/units/minioled.rst:28 ../../en/units/minioled.rst:47 +#: 4e3459be9a844da3b8c2df7ca78cae96 e92d30822be940cda16be819bd4aa172 +msgid "Example output:" +msgstr "示例输出:" -#: ../../en/units/minioled.rst:31 1bac2416267f4f66864e406d6ee05ea2 -msgid "class MiniOLEDUnit" +#: ../../en/units/minioled.rst:30 ../../en/units/minioled.rst:49 +#: 0dffa0b591b9427abf6566e3b50bfc8f 362a9f04d686443a9979f558eafea6b2 +msgid "None" msgstr "" -#: ../../en/units/minioled.rst:34 3ead3c72e3b24d1c8b595256308ff411 -msgid "Constructors" -msgstr "" +#: ../../en/units/minioled.rst:34 c910431d1b6c4aa18e25713c209a58e6 +msgid "MicroPython Example" +msgstr "MicroPython 应用示例" -#: ../../en/units/minioled.rst:38 cd9515952a694a6e8a9bcc73a5f45469 -msgid "Initialize the Unit MiniOLED" -msgstr "" +#: ../../en/units/minioled.rst:41 c910431d1b6c4aa18e25713c209a58e6 of +#: unit.minioled.MiniOLEDUnit:11 +msgid "MicroPython Code Block:" +msgstr "MicroPython 代码块:" -#: ../../en/units/minioled.rst 2c45f156553f4f9381b47fa6d4c727bb -msgid "Parameters" -msgstr "" +#: ../../en/units/minioled.rst:53 05149c009c2241de9a8316790deb9dcc +msgid "**API**" +msgstr "API参考" -#: ../../en/units/minioled.rst:40 89532c2686224a7cb257fdb699c5ff91 -msgid "the I2C object." +#: ../../en/units/minioled.rst:56 f918935de0974e52a8b53dd198dc07e0 +msgid "class MiniOLEDUnit" msgstr "" -#: ../../en/units/minioled.rst:41 eb6cb87fc6a84bd9ab5795938a655f87 -msgid "I2C address of the Unit MiniOLED, default is 0x3c." +#: bd86875cf9c04daa9f3a96256f01ffc4 of unit.minioled.MiniOLEDUnit:1 +msgid "Bases: :py:class:`object`" msgstr "" -#: ../../en/units/minioled.rst:42 4d0d445f025d4b1ba8e057f3f6a39327 -msgid "I2C frequency of the Unit MiniOLED." -msgstr "" +#: 97bf0f5d1be64a75b48340147f782fa8 of unit.minioled.MiniOLEDUnit:1 +msgid "Initialize the Mini OLED Unit." +msgstr "初始化 Mini OLED Unit。" -#: ../../en/units/minioled.rst:44 fec90b0f8f904363a9df7134db9b119b -msgid "UIFLOW2:" +#: ../../en/units/minioled.rst 2ad5ba51984445e38858225e7b292942 +msgid "Parameters" msgstr "" -#: ../../en/units/minioled.rst:46 2ad61eac25e247f4a0b9d372f520d435 +#: 0da06983ddfd4bb1bff210fa44435beb of unit.minioled.MiniOLEDUnit:3 +msgid "The I2C bus the Mini OLED Unit is connected to." +msgstr "Mini OLED Unit 所连接的 I2C 总线。" + +#: 045ce4ba32ca4ea6b9d84d69ae8e218f of unit.minioled.MiniOLEDUnit:5 +msgid "The I2C address of the Mini OLED Unit, default is 0x3C." +msgstr "Mini OLED Unit 的 I2C 地址,默认为 0x3C。" + +#: a548688e2bf4475f8b3b4441358f18be of unit.minioled.MiniOLEDUnit:9 msgid "|init.png|" msgstr "" -#: ../../en/refs/unit.minioled.ref:7 5fec3e30178145adbf806cad1a66b476 +#: ../../en/refs/unit.minioled.ref:7 52c134110d6246349555038d8162d298 msgid "init.png" msgstr "" -#: ../../en/units/minioled.rst:50 996626773cac47aea7bd0c743aba901c -msgid "Methods" +#: ../../en/units/minioled.rst:61 da522888519f44de84b7f73930e20968 +msgid "" +"MiniOLEDUnit class inherits Display class, See :ref:`hardware.Display " +"` for more details." msgstr "" +"MiniOLEDUnit 类继承了 Display 类,有关更多详细信息,请参阅 :ref:`hardware.Display " +"` 。" diff --git a/docs/locales/zh_CN/LC_MESSAGES/units/oled.po b/docs/locales/zh_CN/LC_MESSAGES/units/oled.po index ee466deb..272545b4 100644 --- a/docs/locales/zh_CN/LC_MESSAGES/units/oled.po +++ b/docs/locales/zh_CN/LC_MESSAGES/units/oled.po @@ -9,7 +9,7 @@ msgid "" msgstr "" "Project-Id-Version: UIFlow2 Programming Guide \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-01-16 15:35+0800\n" +"POT-Creation-Date: 2025-03-18 17:35+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language: zh_CN\n" @@ -20,85 +20,119 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.16.0\n" -#: ../../en/units/oled.rst:3 31926aea8a254638902f7f3d696a0149 +#: ../../en/units/oled.rst:3 55fe5aaf29094d169f3838c8637c2b10 msgid "OLED Unit" msgstr "" -#: ../../en/units/oled.rst:7 92e636e4ffbb43b8be558c95e220c5fe +#: ../../en/units/oled.rst:9 563fd28410094c8084da8cefa3f150b1 msgid "" "Unit OLED is a 1.3-inch OLED expansion screen unit. Driveing by SH1107, " "and the resolution is 128*64, monochrome display." -msgstr "" +msgstr "OLED单元为1.3寸OLED扩展屏单元,采用SH1107驱动,分辨率为128*64,单色显示。" -#: ../../en/units/oled.rst:9 fcc4d69d965b424eaa8a3d7f639bf098 +#: ../../en/units/oled.rst:11 288435d2a8424d3998ad992d76909550 msgid "Support the following products:" -msgstr "" +msgstr "支持以下产品:" -#: ../../en/units/oled.rst:11 32e808b56c4e4db89fa2d975a9fd3060 +#: ../../en/units/oled.rst:13 24f0906e87374631843eb498be3bb43d msgid "|OLEDUnit|" msgstr "" -#: ../../en/refs/unit.oled.ref 8cbdbc212c51412192252b99a6d7089a +#: ../../en/refs/unit.oled.ref f16eea86c3f64328b34523a717c5800b msgid "OLEDUnit" msgstr "" -#: ../../en/units/oled.rst:13 646f0693fb65442f85c6ec944bf13a18 -msgid "Micropython Example:" -msgstr "" +#: ../../en/units/oled.rst:17 903b2984777746ff9bda1bcbe397f515 +msgid "UiFlow2 Example" +msgstr "UiFlow2 应用示例" -#: ../../en/units/oled.rst:20 04044090564f4dfda44fbcb58381cbf9 -msgid "UIFLOW2 Example:" -msgstr "" +#: ../../en/units/oled.rst:20 ../../en/units/oled.rst:39 +#: 89bb40731f704961b3e177cdc7ca6fa1 af59a76943f14f1ca3ec55248fad837d +msgid "Draw Text" +msgstr "绘制文本" + +#: ../../en/units/oled.rst:22 8d4913bf11b34a089e60777134261ed2 +msgid "Open the |cores3_oled_example.m5f2| project in UiFlow2." +msgstr "在 UiFlow2 中打开 |cores3_oled_example.m5f2| 项目。" + +#: ../../en/units/oled.rst:24 ../../en/units/oled.rst:41 +#: 95f62a1ea89941229d950978720b7874 aaca82f5e2d64101a2f3e3877965173e +msgid "This example displays the text \"OLED\" on the screen." +msgstr "此示例在屏幕上显示文本“OLED”。" -#: ../../en/units/oled.rst:22 30a069c2719047c0b3ebf82acdb3da85 +#: ../../en/units/oled.rst:26 47832ce9fc9741a1b30a24d0ec26915d +#: fd368cc332e947e8947c46e6394418d8 of unit.oled.OLEDUnit:7 +msgid "UiFlow2 Code Block:" +msgstr "UiFlow2 代码块:" + +#: ../../en/units/oled.rst:28 e2c15de26d9a42c18840562ad786acea msgid "|example.png|" msgstr "" -#: ../../en/refs/unit.oled.ref:9 5525a88878d14c3dbba1e5869a3f2248 +#: ../../en/refs/unit.oled.ref:9 26a426adae124c5e934d97f10fcaad63 msgid "example.png" msgstr "" -#: ../../en/units/oled.rst:26 e9f40ca74c9d46198458854933c8c41f -msgid "|cores3_oled_example.m5f2|" -msgstr "" +#: ../../en/units/oled.rst:30 ../../en/units/oled.rst:49 +#: 02abf01904a641bea2cb3b3bcfd6da70 20e2b35ea09e45929e7f05f5f062d4bc +msgid "Example output:" +msgstr "示例输出:" -#: ../../en/units/oled.rst:29 3f82a6b6a9ee4be099e43b088c6b5145 -msgid "class OLEDUnit" +#: ../../en/units/oled.rst:32 ../../en/units/oled.rst:51 +#: 4d4a663bb85046bfaed9b1837c517e77 b3be7c0f59cc4b80a66234805f144d0f +msgid "None" msgstr "" -#: ../../en/units/oled.rst:32 3e200feb4ef64aa4b59230854c3ed3ad -msgid "Constructors" -msgstr "" +#: ../../en/units/oled.rst:36 4acc7cb34e864ca6b6a5d6016fe54551 +msgid "MicroPython Example" +msgstr "MicroPython 应用示例" -#: ../../en/units/oled.rst:36 c367231d0cb74d9daf00a111d1e52751 -msgid "Initialize the Unit OLED" -msgstr "" +#: ../../en/units/oled.rst:43 4acc7cb34e864ca6b6a5d6016fe54551 of +#: unit.oled.OLEDUnit:11 +msgid "MicroPython Code Block:" +msgstr "MicroPython 代码块:" -#: ../../en/units/oled.rst 0f7838c6a7bd4ab984d2a75c216d26f8 -msgid "Parameters" -msgstr "" +#: ../../en/units/oled.rst:55 f518797bc3704d8aa2ba0ddee8447d01 +msgid "**API**" +msgstr "API参考" -#: ../../en/units/oled.rst:38 75f14b3560db408b943f6754c9e69122 -msgid "the I2C object." +#: ../../en/units/oled.rst:58 3a111f94d57248f0b2739f5f90244563 +msgid "class OLEDUnit" msgstr "" -#: ../../en/units/oled.rst:39 a4ff8babb71e4c848bf050942d4911ae -msgid "I2C address of the Unit OLED, default is 0x3c." +#: cd6a974fcf03435ba09f64750ee071da of unit.oled.OLEDUnit:1 +msgid "Bases: :py:class:`object`" msgstr "" -#: ../../en/units/oled.rst:40 2bcc7ae3035b4824b9f0ada455db8ce1 -msgid "I2C frequency of the Unit OLED." -msgstr "" +#: 15711339fb38406d8d9943d2ec9bab96 of unit.oled.OLEDUnit:1 +msgid "Initialize the OLED Unit." +msgstr "初始化 OLED Unit。" -#: ../../en/units/oled.rst:42 5537abfc8efe42a195f760e22d3c675e -msgid "UIFLOW2:" +#: ../../en/units/oled.rst 63db4e1858814553a62c9572d747af4f +msgid "Parameters" msgstr "" -#: ../../en/units/oled.rst:44 206fdb5f469f4951bf7eebb35675e501 +#: be3e459948fd4d328eafce3ae88322be of unit.oled.OLEDUnit:3 +msgid "The I2C bus the OLED Unit is connected to." +msgstr "OLED Unit 所连接的 I2C 总线。" + +#: 9550bd9cb7d849ceba884b72fea6d47e of unit.oled.OLEDUnit:5 +msgid "The I2C address of the OLED Unit, default is 0x3C." +msgstr "OLED Unit 的 I2C 地址,默认为 0x3C。" + +#: 725f54f09303403eb353cc2114f64d8b of unit.oled.OLEDUnit:9 msgid "|init.png|" msgstr "" -#: ../../en/refs/unit.oled.ref:7 8245882c3d36483fa21d8341a5ba807d +#: ../../en/refs/unit.oled.ref:7 70d1d238867f4bbca303167c5d85427a msgid "init.png" msgstr "" +#: ../../en/units/oled.rst:63 9664e9714756471e8ceefb92a102f8b1 +msgid "" +"OLEDUnit class inherits Display class, See :ref:`hardware.Display " +"` for more details." +msgstr "" +"OLEDUnit 类继承了 Display 类,有关更多详细信息,请参阅 :ref:`hardware.Display " +"` 。" + diff --git a/docs/locales/zh_CN/LC_MESSAGES/units/rca.po b/docs/locales/zh_CN/LC_MESSAGES/units/rca.po index fc48c98e..b6628511 100644 --- a/docs/locales/zh_CN/LC_MESSAGES/units/rca.po +++ b/docs/locales/zh_CN/LC_MESSAGES/units/rca.po @@ -9,7 +9,7 @@ msgid "" msgstr "" "Project-Id-Version: UIFlow2 Programming Guide \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-01-16 15:35+0800\n" +"POT-Creation-Date: 2025-03-18 17:30+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language: zh_CN\n" @@ -20,111 +20,170 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.16.0\n" -#: ../../en/units/rca.rst:3 f44d7cf068a54087b9b5949949271d45 +#: ../../en/units/rca.rst:3 eae5729b6f084736805d6062b67c47c4 msgid "RCA Unit" msgstr "" -#: ../../en/units/rca.rst:7 d07b2adfd5c44f79a7681a63a5eecc16 +#: ../../en/units/rca.rst:9 ec68743841fd481d9d5a4ce126d37d1c msgid "" "Unit RCA is a female jack terminal block for transmitting composite video" " (audio or video), one of the most common A/V connectors, which transmits" " video or audio signals from a component device to an output device " "(i.e., a display or speaker)." -msgstr "" +msgstr "RCA 单元是用于传输复合视频(音频或视频)的母插孔接线端子,是最常见的 A/V 连接器之一,它将视频或音频信号从组件设备传输到输出设备(即显示器或扬声器)。" -#: ../../en/units/rca.rst:9 a0d4952112b84997ad18b9b24dd65a37 +#: ../../en/units/rca.rst:11 c3ab48cf38e3409ab501221a20169de1 msgid "Support the following products:" -msgstr "" +msgstr "支持以下产品:" -#: ../../en/units/rca.rst:11 9d481c4915eb48f68cfe57e5a6d3dc0f +#: ../../en/units/rca.rst:13 adbc9bc8ff8846d68f908b9f8036f40e msgid "|RCAUnit|" msgstr "" -#: ../../en/refs/unit.rca.ref 6359a92c67d541c7abd2e98e04f56a7c +#: ../../en/refs/unit.rca.ref 559571be61204331b7555d976e1c0299 msgid "RCAUnit" msgstr "" -#: ../../en/units/rca.rst:14 c6218f3c81444735bbd1694e43b177e9 -msgid "Micropython Example:" -msgstr "" +#: ../../en/units/rca.rst:17 ba3da3f5fa574d0082330f6f0daef900 +msgid "UiFlow2 Example" +msgstr "UiFlow2 应用示例" -#: ../../en/units/rca.rst:21 0182df815e9a407e90f9fde0f6692bd4 -msgid "UIFLOW2 Example:" -msgstr "" +#: ../../en/units/rca.rst:20 ../../en/units/rca.rst:39 +#: 2332fc522f63487b8c91f4924a023b14 72693357a14b49d59c5fd22e6857c02c +msgid "Draw Text" +msgstr "绘制文本" -#: ../../en/units/rca.rst:23 bffaab75359f442a9cb742c009f90324 +#: ../../en/units/rca.rst:22 27f235d4439047fca03b4a034daa418d +msgid "Open the |core2_rca_example.m5f2| project in UiFlow2." +msgstr "在 UiFlow2 中打开 |core2_rca_example.m5f2| 项目。" + +#: ../../en/units/rca.rst:24 ../../en/units/rca.rst:41 +#: 3bde4f5b43244406a6a50c2101a91c2c f8027375a1c84d3b8fe867cee14b32f7 +msgid "This example displays the text \"RCA\" on the screen." +msgstr "此示例在屏幕上显示文本“RCA”。" + +#: ../../en/units/rca.rst:26 916b4498d9f64ee196645ba3b3bd551f +#: 974f4047a00f42f4ba51c200502925ac of unit.rca.RCAUnit:12 +msgid "UiFlow2 Code Block:" +msgstr "UiFlow2 代码块:" + +#: ../../en/units/rca.rst:28 227832dd895a46178d5a32e94e95a3b5 msgid "|example.png|" msgstr "" -#: ../../en/refs/unit.rca.ref:9 14fbcae1c1c848cf883f4202d0cafd0b +#: ../../en/refs/unit.rca.ref:9 1af233d64ccc4c56b9b117c867754ca9 msgid "example.png" msgstr "" -#: ../../en/units/rca.rst:28 327eff05b9074ccd83b1bfb14558b8f7 -msgid "|core2_rca_example.m5f2|" -msgstr "" +#: ../../en/units/rca.rst:30 ../../en/units/rca.rst:49 +#: 76182495f5464b76859594d363f1dd25 a5b7b95504be4224a571690c45813c31 +msgid "Example output:" +msgstr "示例输出:" -#: ../../en/units/rca.rst:32 b442d5298c5147b985d236d5ce9eabc7 -msgid "class RCAUnit" +#: ../../en/units/rca.rst:32 ../../en/units/rca.rst:51 +#: 3e0f4f458b93404d9b7ec4933425d5cb 6ba9aefaf5d941839c158f1ea67e4a5f +msgid "None" msgstr "" -#: ../../en/units/rca.rst:35 4eab15085eb047ad9761b61ccd66928e -msgid "Constructors" +#: ../../en/units/rca.rst:36 816956ed6ecc4510a2d0321b0f30b071 +msgid "MicroPython Example" +msgstr "MicroPython 应用示例" + +#: ../../en/units/rca.rst:43 816956ed6ecc4510a2d0321b0f30b071 of +#: unit.rca.RCAUnit:16 +msgid "MicroPython Code Block:" +msgstr "MicroPython 代码块:" + +#: ../../en/units/rca.rst:55 459a92810d754c2bae089af9a41f336a +msgid "**API**" +msgstr "API参考" + +#: ../../en/units/rca.rst:58 e01b54a165754342afc89fabe90110cf +msgid "Class RCAUnit" msgstr "" -#: ../../en/units/rca.rst:39 f73d5f563ae6460d94b4d80e8ef0c2e5 -msgid "Initialize the Unit RCA" +#: c35f90407ae441b283bb714a5cfeba0c of unit.rca.RCAUnit:1 +msgid "Bases: :py:class:`object`" msgstr "" -#: ../../en/units/rca.rst 434acda94f9d4a618069ec2cdcb30eef +#: cdfb4dc740ab4e7692b9310d6b661a74 of unit.rca.RCAUnit:1 +msgid "Initialize the RCA Unit." +msgstr "初始化 RCA Unit。" + +#: ../../en/units/rca.rst 97d797685dc64eba94bfad4430a7091a msgid "Parameters" msgstr "" -#: ../../en/units/rca.rst:41 c182f45f9f2247b3971b464ba5f3ea28 +#: b308e123f1fb46599dcf6582387cbc19 of unit.rca.RCAUnit:3 msgid "" -"The port to which the Unit RCA is connected. port[0]: not used, port[1]: " +"The port to which the RCA Unit is connected. port[0]: not used, port[1]: " "dac pin." -msgstr "" +msgstr "RCA Unit 连接到的端口。 port[0]:未使用,port[1]:dac 引脚。" -#: ../../en/units/rca.rst:42 2bf63c3924104c5aae22839b57f4c2fa +#: 7abcfcc7ba5940d190d6c52db2923168 of unit.rca.RCAUnit:4 msgid "The width of the RCA display." -msgstr "" +msgstr "RCA 显示器的宽度。" -#: ../../en/units/rca.rst:43 5ac6ca1492fe4341be476bfd7e40ed92 +#: 9136bda1f35f4502afc649e36a79f26f of unit.rca.RCAUnit:5 msgid "The height of the RCA display." -msgstr "" +msgstr "RCA 显示器的高度。" -#: ../../en/units/rca.rst:44 068997e564ef42c68ff5e912e36c8945 +#: 4d79dce178ac4dd99d60ff6ac4a9957d of unit.rca.RCAUnit:6 msgid "The width of the output of the RCA display." -msgstr "" +msgstr "RCA 显示器输出的宽度。" -#: ../../en/units/rca.rst:45 c479b4fae7b64eb393e8a505c35442f6 +#: 8df4edd9c8df4468b83548e084ed941b of unit.rca.RCAUnit:7 msgid "The height of the output of the RCA display." -msgstr "" +msgstr "RCA 显示器输出的高度。" -#: ../../en/units/rca.rst:46 a2b43d6dd2954fce9268cf51f6da198f +#: 3dd632e7ad814feeb646fc452b314bb4 of unit.rca.RCAUnit:8 msgid "" -"The signal type of the RCA display. NTSC=0, NTSC_J=1, " -"PAL=2, PAL_M=3, PAL_N=4." -msgstr "" +"The signal type of the RCA display. NTSC=0, NTSC_J=1, PAL=2, PAL_M=3, " +"PAL_N=4." +msgstr "RCA显示的信号类型。" -#: ../../en/units/rca.rst:47 eca2eb790c0a42ca9e86eb8ffdd6bc8a +#: dfe9301a633d42a9bedb72fda2c1dfcc of unit.rca.RCAUnit:9 msgid "The use of psram of the RCA display." -msgstr "" +msgstr "RCA显示器的psram的使用。" -#: ../../en/units/rca.rst:48 e77b7f95a1c64edd9e4f0440211fea79 +#: 472f2b11976e47ba87673824c9b2f7df of unit.rca.RCAUnit:10 msgid "The output level of the RCA display." -msgstr "" - -#: ../../en/units/rca.rst:50 602c428eb66641e998d9dfdf6fc1e2a8 -msgid "UIFLOW2:" -msgstr "" +msgstr "RCA 显示的输出电平。" -#: ../../en/units/rca.rst:52 3512b5084589477382c4b193880dde20 +#: 011ed5f7f09347d7b5430c8c688c2a55 of unit.rca.RCAUnit:14 msgid "|init.png|" msgstr "" -#: ../../en/refs/unit.rca.ref:7 d0cc428660314f3c8dbdcf8e4e5d972a +#: ../../en/refs/unit.rca.ref:7 eaaa20712ff84f52b1ed4608b8ce478f msgid "init.png" msgstr "" +#: ../../en/units/rca.rst:63 0bd580ea31ca4dbda833ba8412020fe8 +msgid "" +"RCAUnit class inherits Display class, See :ref:`hardware.Display " +"` for more details." +msgstr "" +"RCAUnit 类继承了 Display 类,更多详细信息请参阅 :ref:`hardware.Display " +"` 。" + +#: ../../docstring f05fddc1715747b89fffce69a14a76e5 of unit.rca.RCAUnit.NTSC:1 +msgid "signal type. National Television System Committee." +msgstr "信号类型。国家电视系统委员会制定的制式。" + +#: ../../docstring 0ae638ad00c84e349ff7ba9c84552b18 of +#: unit.rca.RCAUnit.NTSC_J:1 +msgid "signal type. National Television System Committee Japan." +msgstr "信号类型。国家电视系统委员会制式的日本标准。" + +#: ../../docstring d8a4090627d049ef8277091d7306dc05 of unit.rca.RCAUnit.PAL:1 +msgid "signal type. Phase Alternating Line." +msgstr "信号类型。逐行扫描制式。" + +#: ../../docstring 9a5d8433617b4d628a18ecee2a6f1594 of unit.rca.RCAUnit.PAL_M:1 +msgid "signal type. Phase Alternating Line M." +msgstr "信号类型。逐行扫描制式 M格式。" + +#: ../../docstring a462157038f84aeab64f981efe964261 of unit.rca.RCAUnit.PAL_N:1 +msgid "signal type. Phase Alternating Line N." +msgstr "信号类型。逐行扫描制式 N格式。" + diff --git a/examples/module/display/cores3_display_example.m5f2 b/examples/module/display/cores3_display_example.m5f2 index 7a4dac4e..0597c30f 100644 --- a/examples/module/display/cores3_display_example.m5f2 +++ b/examples/module/display/cores3_display_example.m5f2 @@ -1 +1 @@ -{"version":"V2.0","versionNumber":"V2.2.0","type":"cores3","components":[{"name":"screen","type":"screen","layer":0,"screenId":"builtin","screenName":"","id":"__cores3_screen","createTime":1735805864270,"x":0,"y":0,"width":320,"height":240,"backgroundColor":"#222222","size":0,"isSelected":true},{"name":"label0","type":"label","layer":1,"screenId":"builtin","screenName":"","id":"mWV8!K2BC+1w+&2E","createTime":1735805878729,"x":127,"y":109,"color":"#ffffff","backgroundColor":"#222222","text":"CoreS3","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false},{"name":"screen","type":"screen","layer":0,"screenId":"m%PV$mbx&f69%$Wz","screenName":"module_display","id":"__display_screen","createTime":1735805890327,"x":0,"y":0,"width":1280,"height":720,"backgroundColor":"#000000","size":0,"isSelected":true},{"name":"label1","type":"label","layer":2,"screenId":"m%PV$mbx&f69%$Wz","screenName":"module_display","id":"xz4Ud%5s%GyYpNtE","createTime":1735805892105,"x":506,"y":318,"color":"#ffffff","backgroundColor":"#222222","text":"Display","engine":"gfx","font":"Widgets.FONTS.DejaVu72","rotation":0,"isSelected":false,"width":221,"height":82}],"resources":[{"hardware":["hardware_button","hardware_pin_button","imu","speaker","touch","als","mic"]},{"module":["module_display"]}],"units":[],"hats":[],"bases":[],"i2cs":[],"blockly":"truetrue12807201280720601174250000true","screen":[{"simulationName":"Built-in","type":"builtin","width":320,"height":240,"scale":0.78,"screenName":"","blockId":"","screenColorType":0,"id":"builtin","createTime":1735805864269},{"simulationName":"display_0","type":"display","width":1280,"height":720,"scale":0.234,"screenName":"module_display","blockId":"?H*Q8q3wgC21aF%?3Xq,","screenColorType":0,"id":"m%PV$mbx&f69%$Wz","createTime":1735805890313,"fullZoom":1.976}],"logicWhenNum":0,"customList":[]} \ No newline at end of file +{"version":"V2.0","versionNumber":"V2.2.3","type":"cores3","components":[{"name":"screen","type":"screen","layer":0,"screenId":"builtin","screenName":"","id":"__cores3_screen","createTime":1735805864270,"x":0,"y":0,"width":320,"height":240,"backgroundColor":"#222222","size":0},{"name":"label0","type":"label","layer":1,"screenId":"builtin","screenName":"","id":"mWV8!K2BC+1w+&2E","createTime":1735805878729,"x":127,"y":109,"color":"#ffffff","backgroundColor":"#222222","text":"CoreS3","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0},{"name":"screen","type":"screen","layer":0,"screenId":"m%PV$mbx&f69%$Wz","screenName":"module_display","id":"__display_screen","createTime":1735805890327,"x":0,"y":0,"width":1280,"height":720,"backgroundColor":"#000000","size":0,"isSelected":true},{"name":"label1","type":"label","layer":2,"screenId":"m%PV$mbx&f69%$Wz","screenName":"module_display","id":"xz4Ud%5s%GyYpNtE","createTime":1735805892105,"x":506,"y":318,"color":"#ffffff","backgroundColor":"#222222","text":"Display","engine":"gfx","font":"Widgets.FONTS.DejaVu72","rotation":0,"isSelected":false}],"resources":[{"hardware":["hardware_button","hardware_pin_button","imu","speaker","touch","als","mic"]},{"module":["module_display"]}],"units":[],"hats":[],"bases":[],"i2cs":[],"blockly":"truetrue12807201280720601174250000true","screen":[{"simulationName":"Built-in","type":"builtin","width":320,"height":240,"scale":0.78,"screenName":"","blockId":"","screenColorType":0,"id":"builtin","createTime":1735805864269},{"simulationName":"display_0","type":"display","width":1280,"height":720,"scale":0.234,"screenName":"module_display","blockId":"?H*Q8q3wgC21aF%?3Xq,","screenColorType":0,"id":"m%PV$mbx&f69%$Wz","createTime":1735805890313,"fullZoom":1.976}],"logicWhenNum":0,"customList":[]} \ No newline at end of file diff --git a/examples/module/display/cores3_display_example.py b/examples/module/display/cores3_display_example.py index d92501f9..f0e12bbb 100644 --- a/examples/module/display/cores3_display_example.py +++ b/examples/module/display/cores3_display_example.py @@ -5,6 +5,7 @@ import os, sys, io import M5 from M5 import * +from module import DisplayModule label0 = None @@ -19,22 +20,15 @@ def setup(): Widgets.fillScreen(0x222222) label0 = Widgets.Label("CoreS3", 127, 109, 1.0, 0xFFFFFF, 0x222222, Widgets.FONTS.DejaVu18) - module_display = M5.addDisplay( - None, - 0, - { - "module_display": { - "enabled": True, - "width": 1280, - "height": 720, - "output_width": 1280, - "output_height": 720, - "refresh_rate": 60, - "scale_w": 1, - "scale_h": 1, - "pixel_clock": 74250000, - } - }, + module_display = DisplayModule( + width=1280, + height=720, + output_width=1280, + output_height=720, + refresh_rate=60, + pixel_clock=74250000, + scale_w=1, + scale_h=1, ) label1 = Widgets.Label( "Display", 506, 318, 1.0, 0xFFFFFF, 0x222222, Widgets.FONTS.DejaVu72, module_display diff --git a/examples/module/rca/core2_rca_example.m5f2 b/examples/module/rca/core2_rca_example.m5f2 index f2435adb..9fb2fdaf 100644 --- a/examples/module/rca/core2_rca_example.m5f2 +++ b/examples/module/rca/core2_rca_example.m5f2 @@ -1 +1 @@ -{"version":"V2.0","versionNumber":"V2.2.0","type":"core2","components":[{"name":"screen","type":"screen","layer":0,"screenId":"builtin","screenName":"","id":"__core2_screen","createTime":1735634306050,"x":0,"y":0,"width":320,"height":240,"backgroundColor":"#222222","size":0,"isSelected":true},{"name":"label0","type":"label","layer":1,"screenId":"builtin","screenName":"","id":"tzBNQ5O@Mx!qD8D%","createTime":1735634324665,"x":133,"y":109,"color":"#ffffff","backgroundColor":"#222222","text":"Core2","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false},{"name":"screen","type":"screen","layer":0,"screenId":"i+0z4xyo%bzrv$Qg","screenName":"module_rca","id":"__display_screen","createTime":1735634326562,"x":0,"y":0,"width":216,"height":144,"backgroundColor":"#000000","size":0,"isSelected":true},{"name":"label1","type":"label","layer":2,"screenId":"i+0z4xyo%bzrv$Qg","screenName":"module_rca","id":"nMMeGDkOfV&omJKL","createTime":1735634328048,"x":88,"y":61,"color":"#ffffff","backgroundColor":"#222222","text":"RCA","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false,"width":58,"height":21}],"resources":[{"hardware":["hardware_button","hardware_pin_button","imu","speaker","touch","mic"]},{"module":["rca"]}],"units":[],"hats":[],"bases":[],"i2cs":[],"blockly":"truefalse2161440000260true","screen":[{"simulationName":"Built-in","type":"builtin","width":320,"height":240,"scale":0.78,"screenName":"","blockId":"","screenColorType":0,"id":"builtin","createTime":1735634306049},{"simulationName":"display_0","type":"display","width":216,"height":144,"scale":1,"screenName":"module_rca","blockId":"otL%FqM=HBYW{VOIIlcf","screenColorType":0,"id":"i+0z4xyo%bzrv$Qg","createTime":1735634326543,"fullZoom":2.313}],"logicWhenNum":0,"customList":[]} \ No newline at end of file +{"version":"V2.0","versionNumber":"V2.2.3","type":"core2","components":[{"name":"screen","type":"screen","layer":0,"screenId":"builtin","screenName":"","id":"__core2_screen","createTime":1735634306050,"x":0,"y":0,"width":320,"height":240,"backgroundColor":"#222222","size":0,"isSelected":true},{"name":"label0","type":"label","layer":1,"screenId":"builtin","screenName":"","id":"tzBNQ5O@Mx!qD8D%","createTime":1735634324665,"x":133,"y":109,"color":"#ffffff","backgroundColor":"#222222","text":"Core2","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false},{"name":"screen","type":"screen","layer":0,"screenId":"i+0z4xyo%bzrv$Qg","screenName":"module_rca","id":"__display_screen","createTime":1735634326562,"x":0,"y":0,"width":216,"height":144,"backgroundColor":"#000000","size":0},{"name":"label1","type":"label","layer":2,"screenId":"i+0z4xyo%bzrv$Qg","screenName":"module_rca","id":"nMMeGDkOfV&omJKL","createTime":1735634328048,"x":88,"y":61,"color":"#ffffff","backgroundColor":"#222222","text":"RCA","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0}],"resources":[{"hardware":["hardware_button","hardware_pin_button","imu","speaker","touch","mic"]},{"module":["rca"]}],"units":[],"hats":[],"bases":[],"i2cs":[],"blockly":"truefalse2161440000260true","screen":[{"simulationName":"Built-in","type":"builtin","width":320,"height":240,"scale":0.78,"screenName":"","blockId":"","screenColorType":0,"id":"builtin","createTime":1735634306049},{"simulationName":"display_0","type":"display","width":216,"height":144,"scale":1,"screenName":"module_rca","blockId":"otL%FqM=HBYW{VOIIlcf","screenColorType":0,"id":"i+0z4xyo%bzrv$Qg","createTime":1735634326543,"fullZoom":2.313}],"logicWhenNum":0,"customList":[]} \ No newline at end of file diff --git a/examples/unit/glass/cores3_glass_example.m5f2 b/examples/unit/glass/cores3_glass_example.m5f2 index 3fd89f6a..a168ec83 100644 --- a/examples/unit/glass/cores3_glass_example.m5f2 +++ b/examples/unit/glass/cores3_glass_example.m5f2 @@ -1 +1 @@ -{"version":"V2.0","versionNumber":"V2.2.0","type":"cores3","components":[{"name":"screen","type":"screen","layer":0,"screenId":"builtin","screenName":"","id":"__cores3_screen","createTime":1735638308189,"x":0,"y":0,"width":320,"height":240,"backgroundColor":"#222222","size":0,"isSelected":true},{"name":"label0","type":"label","layer":1,"screenId":"builtin","screenName":"","id":"mUFtfj0p7lfWOf+#","createTime":1735638319187,"x":138,"y":111,"color":"#ffffff","backgroundColor":"#222222","text":"CoreS3","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false},{"name":"screen","type":"screen","layer":0,"screenId":"e5u`+!qzTFO_zCB#","screenName":"glass_0","id":"__display_screen","createTime":1735638321786,"x":0,"y":0,"width":128,"height":64,"backgroundColor":"#000000","size":0,"isSelected":true},{"name":"label1","type":"label","layer":2,"screenId":"e5u`+!qzTFO_zCB#","screenName":"glass_0","id":"m1V0gWWJL`=IZ$Nn","createTime":1735638335828,"x":32,"y":21,"color":"#ffffff","backgroundColor":"#222222","text":"GLASS","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false}],"resources":[{"hardware":["hardware_button","hardware_pin_button","imu","speaker","touch","als","mic","i2c"]},{"unit":["unit_glass"]}],"units":[{"type":"unit_glass","name":"glass_0","portList":["A","PAHUB","Custom"],"portType":"A","userPort":[22,21],"id":"gNz4rQNcPVVYj=b%","createTime":1735638314542,"bus":"i2c0","pahubPortList":[0,1,2,3,4,5],"pahubPort":0,"initBlockId":"R.XJ3~SD{]EAkExK!c)R"}],"hats":[],"bases":[],"i2cs":[{"id":"i2c0","portType":"A","userPort":[22,21],"freq":"100000","blockId":"p[o5N]6g6WMk~;@gazlY"}],"blockly":"true010000012glass_0true0x3dtrue","screen":[{"simulationName":"Built-in","type":"builtin","width":320,"height":240,"scale":0.78,"screenName":"","blockId":"","screenColorType":0,"id":"builtin","createTime":1735638308188},{"simulationName":"display_0","type":"display","width":128,"height":64,"scale":1,"screenName":"glass_0","blockId":"R.XJ3~SD{]EAkExK!c)R","screenColorType":1,"id":"e5u`+!qzTFO_zCB#","createTime":1735638321773,"fullZoom":5.203}],"logicWhenNum":0,"customList":[]} \ No newline at end of file +{"version":"V2.0","versionNumber":"V2.2.3","type":"cores3","components":[{"name":"screen","type":"screen","layer":0,"screenId":"builtin","screenName":"","id":"__cores3_screen","createTime":1735638308189,"x":0,"y":0,"width":320,"height":240,"backgroundColor":"#222222","size":0},{"name":"label0","type":"label","layer":1,"screenId":"builtin","screenName":"","id":"mUFtfj0p7lfWOf+#","createTime":1735638319187,"x":138,"y":111,"color":"#ffffff","backgroundColor":"#222222","text":"CoreS3","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0},{"name":"screen","type":"screen","layer":0,"screenId":"e5u`+!qzTFO_zCB#","screenName":"glass_0","id":"__display_screen","createTime":1735638321786,"x":0,"y":0,"width":128,"height":64,"backgroundColor":"#000000","size":0,"isSelected":true},{"name":"label1","type":"label","layer":2,"screenId":"e5u`+!qzTFO_zCB#","screenName":"glass_0","id":"m1V0gWWJL`=IZ$Nn","createTime":1735638335828,"x":32,"y":21,"color":"#ffffff","backgroundColor":"#222222","text":"GLASS","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false}],"resources":[{"hardware":["hardware_button","hardware_pin_button","imu","speaker","touch","als","mic","i2c"]},{"unit":["unit_glass"]}],"units":[{"type":"unit_glass","name":"glass_0","portList":["A","PAHUB","Custom"],"portType":"A","userPort":[22,21],"id":"gNz4rQNcPVVYj=b%","createTime":1742264512296,"bus":"i2c0","pahubPortList":[0,1,2,3,4,5],"pahubPort":0,"initBlockId":"R.XJ3~SD{]EAkExK!c)R"}],"hats":[],"bases":[],"i2cs":[{"id":"i2c0","portType":"A","userPort":[22,21],"freq":"100000","blockId":"p[o5N]6g6WMk~;@gazlY"}],"blockly":"true010000012glass_0true0x3dtrue","screen":[{"simulationName":"Built-in","type":"builtin","width":320,"height":240,"scale":0.78,"screenName":"","blockId":"","screenColorType":0,"id":"builtin","createTime":1735638308188},{"simulationName":"display_0","type":"display","width":128,"height":64,"scale":1,"screenName":"glass_0","blockId":"R.XJ3~SD{]EAkExK!c)R","screenColorType":1,"id":"e5u`+!qzTFO_zCB#","createTime":1735638321773,"fullZoom":5.203}],"logicWhenNum":0,"customList":[]} \ No newline at end of file diff --git a/examples/unit/glass2/cores3_glass2_example.m5f2 b/examples/unit/glass2/cores3_glass2_example.m5f2 index f2a166ba..11ad135d 100644 --- a/examples/unit/glass2/cores3_glass2_example.m5f2 +++ b/examples/unit/glass2/cores3_glass2_example.m5f2 @@ -1 +1 @@ -{"version":"V2.0","versionNumber":"V2.2.1","type":"cores3","components":[{"name":"screen","type":"screen","layer":0,"screenId":"builtin","screenName":"","id":"__cores3_screen","createTime":1737170160925,"x":0,"y":0,"width":320,"height":240,"backgroundColor":"#222222","size":0,"isSelected":true},{"name":"label0","type":"label","layer":1,"screenId":"builtin","screenName":"","id":"kCjo#tXbLT^BJ=7S","createTime":1737170201503,"x":127,"y":109,"color":"#ffffff","backgroundColor":"#222222","text":"CoreS3","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false},{"name":"screen","type":"screen","layer":0,"screenId":"pEhB-&n$61OJ&@AI","screenName":"glass2_0","id":"__display_screen","createTime":1737170214123,"x":0,"y":0,"width":128,"height":64,"backgroundColor":"#000000","size":0,"isSelected":true},{"name":"label1","type":"label","layer":2,"screenId":"pEhB-&n$61OJ&@AI","screenName":"glass2_0","id":"a-NdpP`^lv@kuASH","createTime":1737170220920,"x":26,"y":21,"color":"#ffffff","backgroundColor":"#222222","text":"GLASS2","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false}],"resources":[{"hardware":["hardware_button","hardware_pin_button","imu","speaker","touch","als","mic","i2c"]},{"unit":["unit_glass2"]}],"units":[{"type":"unit_glass2","name":"glass2_0","portList":["A","PAHUB","Custom"],"portType":"A","userPort":[22,21],"id":"l0!vlnraZOAgY_ki","createTime":1737170170377,"bus":"i2c0","pahubPortList":[0,1,2,3,4,5],"pahubPort":0,"initBlockId":"ev_ZrE@eatXV0bZ5sGc+"}],"hats":[],"bases":[],"i2cs":[{"id":"i2c0","portType":"A","userPort":[22,21],"freq":"100000","blockId":"%~KJ(T1m(it7a(mb)[Jy"}],"blockly":"true010000012glass2_0false0x3ctrue","screen":[{"simulationName":"Built-in","type":"builtin","width":320,"height":240,"scale":0.78,"screenName":"","blockId":"","screenColorType":0,"id":"builtin","createTime":1737170160893},{"simulationName":"display_0","type":"display","width":128,"height":64,"scale":1,"screenName":"glass2_0","blockId":"ev_ZrE@eatXV0bZ5sGc+","screenColorType":1,"id":"pEhB-&n$61OJ&@AI","createTime":1737170214105,"fullZoom":5.203}],"logicWhenNum":0,"customList":[]} \ No newline at end of file +{"version":"V2.0","versionNumber":"V2.2.3","type":"cores3","components":[{"name":"screen","type":"screen","layer":0,"screenId":"builtin","screenName":"","id":"__cores3_screen","createTime":1737170160925,"x":0,"y":0,"width":320,"height":240,"backgroundColor":"#222222","size":0,"isSelected":true},{"name":"label0","type":"label","layer":1,"screenId":"builtin","screenName":"","id":"kCjo#tXbLT^BJ=7S","createTime":1737170201503,"x":127,"y":109,"color":"#ffffff","backgroundColor":"#222222","text":"CoreS3","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false},{"name":"screen","type":"screen","layer":0,"screenId":"pEhB-&n$61OJ&@AI","screenName":"glass2_0","id":"__display_screen","createTime":1737170214123,"x":0,"y":0,"width":128,"height":64,"backgroundColor":"#000000","size":0},{"name":"label1","type":"label","layer":2,"screenId":"pEhB-&n$61OJ&@AI","screenName":"glass2_0","id":"a-NdpP`^lv@kuASH","createTime":1737170220920,"x":26,"y":21,"color":"#ffffff","backgroundColor":"#222222","text":"GLASS2","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0}],"resources":[{"hardware":["hardware_button","hardware_pin_button","imu","speaker","touch","als","mic","i2c"]},{"unit":["unit_glass2"]}],"units":[{"type":"unit_glass2","name":"glass2_0","portList":["A","PAHUB","Custom"],"portType":"A","userPort":[22,21],"id":"l0!vlnraZOAgY_ki","createTime":1742264607003,"bus":"i2c0","pahubPortList":[0,1,2,3,4,5],"pahubPort":0,"initBlockId":"ev_ZrE@eatXV0bZ5sGc+"}],"hats":[],"bases":[],"i2cs":[{"id":"i2c0","portType":"A","userPort":[22,21],"freq":"100000","blockId":"%~KJ(T1m(it7a(mb)[Jy"}],"blockly":"true010000012glass2_0false0x3ctrue","screen":[{"simulationName":"Built-in","type":"builtin","width":320,"height":240,"scale":0.78,"screenName":"","blockId":"","screenColorType":0,"id":"builtin","createTime":1737170160893},{"simulationName":"display_0","type":"display","width":128,"height":64,"scale":1,"screenName":"glass2_0","blockId":"ev_ZrE@eatXV0bZ5sGc+","screenColorType":1,"id":"pEhB-&n$61OJ&@AI","createTime":1737170214105,"fullZoom":5.203}],"logicWhenNum":0,"customList":[]} \ No newline at end of file diff --git a/examples/unit/glass2/cores3_glass2_example.py b/examples/unit/glass2/cores3_glass2_example.py index 49582cb9..715fe0a1 100644 --- a/examples/unit/glass2/cores3_glass2_example.py +++ b/examples/unit/glass2/cores3_glass2_example.py @@ -7,6 +7,7 @@ from M5 import * from hardware import I2C from hardware import Pin +from unit import Glass2Unit label0 = None @@ -23,7 +24,7 @@ def setup(): label0 = Widgets.Label("CoreS3", 127, 109, 1.0, 0xFFFFFF, 0x222222, Widgets.FONTS.DejaVu18) i2c0 = I2C(0, scl=Pin(1), sda=Pin(2), freq=100000) - glass2_0 = M5.addDisplay(i2c0, 0x3C, {"unit_glass2": True}) + glass2_0 = Glass2Unit(i2c0, 0x3C) label1 = Widgets.Label( "GLASS2", 26, 21, 1.0, 0xFFFFFF, 0x222222, Widgets.FONTS.DejaVu18, glass2_0 ) diff --git a/examples/unit/lcd/cores3_lcd_example.m5f2 b/examples/unit/lcd/cores3_lcd_example.m5f2 index 75b2daec..12b008f2 100644 --- a/examples/unit/lcd/cores3_lcd_example.m5f2 +++ b/examples/unit/lcd/cores3_lcd_example.m5f2 @@ -1 +1 @@ -{"version":"V2.0","versionNumber":"V2.2.0","type":"cores3","components":[{"name":"screen","type":"screen","layer":0,"screenId":"builtin","screenName":"","id":"__cores3_screen","createTime":1735638025318,"x":0,"y":0,"width":320,"height":240,"backgroundColor":"#222222","size":0,"isSelected":true},{"name":"label0","type":"label","layer":1,"screenId":"builtin","screenName":"","id":"cg`Jr9X=50vjwj%u","createTime":1735638034711,"x":127,"y":109,"color":"#ffffff","backgroundColor":"#222222","text":"CoreS3","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false},{"name":"screen","type":"screen","layer":0,"screenId":"aUYnwbk_rg1PljeS","screenName":"lcd_0","id":"__display_screen","createTime":1735638049338,"x":0,"y":0,"width":135,"height":240,"backgroundColor":"#000000","size":0,"isSelected":true},{"name":"label1","type":"label","layer":2,"screenId":"aUYnwbk_rg1PljeS","screenName":"lcd_0","id":"i4XP^NsqfaNqiLoq","createTime":1735638054199,"x":48,"y":109,"color":"#ffffff","backgroundColor":"#222222","text":"LCD","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false}],"resources":[{"hardware":["hardware_button","hardware_pin_button","imu","speaker","touch","als","mic","i2c"]},{"unit":["unit_lcd"]}],"units":[{"type":"unit_lcd","name":"lcd_0","portList":["A","PAHUB","Custom"],"portType":"A","userPort":[22,21],"id":"q=6D9KnOdhNEEyR@","createTime":1735638030019,"bus":"i2c0","pahubPortList":[0,1,2,3,4,5],"pahubPort":0,"initBlockId":"IXS0*]U,3A1~Jz:5EDXm"}],"hats":[],"bases":[],"i2cs":[{"id":"i2c0","portType":"A","userPort":[22,21],"freq":"100000","blockId":"I+TwSeTtibW0sc)PtP0%"}],"blockly":"true010000012lcd_0true0x3etrue","screen":[{"simulationName":"Built-in","type":"builtin","width":320,"height":240,"scale":0.78,"screenName":"","blockId":"","screenColorType":0,"id":"builtin","createTime":1735638025316},{"simulationName":"display_0","type":"display","width":135,"height":240,"scale":1,"screenName":"lcd_0","blockId":"IXS0*]U,3A1~Jz:5EDXm","screenColorType":0,"id":"aUYnwbk_rg1PljeS","createTime":1735638049322,"fullZoom":1.387}],"logicWhenNum":0,"customList":[]} \ No newline at end of file +{"version":"V2.0","versionNumber":"V2.2.3","type":"cores3","components":[{"name":"screen","type":"screen","layer":0,"screenId":"builtin","screenName":"","id":"__cores3_screen","createTime":1735638025318,"x":0,"y":0,"width":320,"height":240,"backgroundColor":"#222222","size":0},{"name":"label0","type":"label","layer":1,"screenId":"builtin","screenName":"","id":"cg`Jr9X=50vjwj%u","createTime":1735638034711,"x":127,"y":109,"color":"#ffffff","backgroundColor":"#222222","text":"CoreS3","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0},{"name":"screen","type":"screen","layer":0,"screenId":"aUYnwbk_rg1PljeS","screenName":"lcd_0","id":"__display_screen","createTime":1735638049338,"x":0,"y":0,"width":135,"height":240,"backgroundColor":"#000000","size":0,"isSelected":true},{"name":"label1","type":"label","layer":2,"screenId":"aUYnwbk_rg1PljeS","screenName":"lcd_0","id":"i4XP^NsqfaNqiLoq","createTime":1735638054199,"x":48,"y":109,"color":"#ffffff","backgroundColor":"#222222","text":"LCD","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false}],"resources":[{"hardware":["hardware_button","hardware_pin_button","imu","speaker","touch","als","mic","i2c"]},{"unit":["unit_lcd"]}],"units":[{"type":"unit_lcd","name":"lcd_0","portList":["A","PAHUB","Custom"],"portType":"A","userPort":[22,21],"id":"q=6D9KnOdhNEEyR@","createTime":1742264360188,"bus":"i2c0","pahubPortList":[0,1,2,3,4,5],"pahubPort":0,"initBlockId":"IXS0*]U,3A1~Jz:5EDXm"}],"hats":[],"bases":[],"i2cs":[{"id":"i2c0","portType":"A","userPort":[22,21],"freq":"100000","blockId":"I+TwSeTtibW0sc)PtP0%"}],"blockly":"true010000012lcd_0true0x3etrue","screen":[{"simulationName":"Built-in","type":"builtin","width":320,"height":240,"scale":0.78,"screenName":"","blockId":"","screenColorType":0,"id":"builtin","createTime":1735638025316},{"simulationName":"display_0","type":"display","width":135,"height":240,"scale":1,"screenName":"lcd_0","blockId":"IXS0*]U,3A1~Jz:5EDXm","screenColorType":0,"id":"aUYnwbk_rg1PljeS","createTime":1735638049322,"fullZoom":1.387}],"logicWhenNum":0,"customList":[]} \ No newline at end of file diff --git a/examples/unit/minioled/cores3_minioled_example.m5f2 b/examples/unit/minioled/cores3_minioled_example.m5f2 index a30730c1..0b584386 100644 --- a/examples/unit/minioled/cores3_minioled_example.m5f2 +++ b/examples/unit/minioled/cores3_minioled_example.m5f2 @@ -1 +1 @@ -{"version":"V2.0","versionNumber":"V2.2.1","type":"cores3","components":[{"name":"screen","type":"screen","layer":0,"screenId":"builtin","screenName":"","id":"__cores3_screen","createTime":1737170529938,"x":0,"y":0,"width":320,"height":240,"backgroundColor":"#222222","size":0,"isSelected":true},{"name":"label0","type":"label","layer":1,"screenId":"builtin","screenName":"","id":"w-GHU1jMa4CVoo^H","createTime":1737170543145,"x":127,"y":109,"color":"#ffffff","backgroundColor":"#222222","text":"CoreS3","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false},{"name":"screen","type":"screen","layer":0,"screenId":"baT&w=Pcd`S$LTDH","screenName":"minioled_0","id":"__display_screen","createTime":1737170553082,"x":0,"y":0,"width":70,"height":40,"backgroundColor":"#000000","size":0,"isSelected":true},{"name":"label1","type":"label","layer":2,"screenId":"baT&w=Pcd`S$LTDH","screenName":"minioled_0","id":"s`8Wwl8iYx!IMsH1","createTime":1737170559525,"x":15,"y":9,"color":"#ffffff","backgroundColor":"#222222","text":"Mini","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false,"width":58,"height":21}],"resources":[{"hardware":["hardware_button","hardware_pin_button","imu","speaker","touch","als","mic","i2c"]},{"unit":["unit_minioled"]}],"units":[{"type":"unit_minioled","name":"minioled_0","portList":["A","PAHUB","Custom"],"portType":"A","userPort":[22,21],"id":"s@HlJnwfONL1E9&O","createTime":1737170538019,"bus":"i2c0","pahubPortList":[0,1,2,3,4,5],"pahubPort":0,"initBlockId":"v(Bbe`F.A}(Ts3~AHve@"}],"hats":[],"bases":[],"i2cs":[{"id":"i2c0","portType":"A","userPort":[22,21],"freq":"100000","blockId":"s0;WY{QusWUC)(4#/Y7Z"}],"blockly":"true010000012minioled_0true0x3ctrue","screen":[{"simulationName":"Built-in","type":"builtin","width":320,"height":240,"scale":0.78,"screenName":"","blockId":"","screenColorType":0,"id":"builtin","createTime":1737170529934},{"simulationName":"display_0","type":"display","width":70,"height":40,"scale":1,"screenName":"minioled_0","blockId":"v(Bbe`F.A}(Ts3~AHve@","screenColorType":1,"id":"baT&w=Pcd`S$LTDH","createTime":1737170553064,"fullZoom":8.325}],"logicWhenNum":0,"customList":[]} \ No newline at end of file +{"version":"V2.0","versionNumber":"V2.2.3","type":"cores3","components":[{"name":"screen","type":"screen","layer":0,"screenId":"builtin","screenName":"","id":"__cores3_screen","createTime":1737170529938,"x":0,"y":0,"width":320,"height":240,"backgroundColor":"#222222","size":0},{"name":"label0","type":"label","layer":1,"screenId":"builtin","screenName":"","id":"w-GHU1jMa4CVoo^H","createTime":1737170543145,"x":127,"y":109,"color":"#ffffff","backgroundColor":"#222222","text":"CoreS3","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0},{"name":"screen","type":"screen","layer":0,"screenId":"baT&w=Pcd`S$LTDH","screenName":"minioled_0","id":"__display_screen","createTime":1737170553082,"x":0,"y":0,"width":70,"height":40,"backgroundColor":"#000000","size":0,"isSelected":true},{"name":"label1","type":"label","layer":2,"screenId":"baT&w=Pcd`S$LTDH","screenName":"minioled_0","id":"s`8Wwl8iYx!IMsH1","createTime":1737170559525,"x":15,"y":9,"color":"#ffffff","backgroundColor":"#222222","text":"Mini","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false}],"resources":[{"hardware":["hardware_button","hardware_pin_button","imu","speaker","touch","als","mic","i2c"]},{"unit":["unit_minioled"]}],"units":[{"type":"unit_minioled","name":"minioled_0","portList":["A","PAHUB","Custom"],"portType":"A","userPort":[22,21],"id":"s@HlJnwfONL1E9&O","createTime":1742264090215,"bus":"i2c0","pahubPortList":[0,1,2,3,4,5],"pahubPort":0,"initBlockId":"v(Bbe`F.A}(Ts3~AHve@"}],"hats":[],"bases":[],"i2cs":[{"id":"i2c0","portType":"A","userPort":[22,21],"freq":"100000","blockId":"s0;WY{QusWUC)(4#/Y7Z"}],"blockly":"true010000012minioled_0true0x3ctrue","screen":[{"simulationName":"Built-in","type":"builtin","width":320,"height":240,"scale":0.78,"screenName":"","blockId":"","screenColorType":0,"id":"builtin","createTime":1737170529934},{"simulationName":"display_0","type":"display","width":70,"height":40,"scale":1,"screenName":"minioled_0","blockId":"v(Bbe`F.A}(Ts3~AHve@","screenColorType":1,"id":"baT&w=Pcd`S$LTDH","createTime":1737170553064,"fullZoom":8.325}],"logicWhenNum":0,"customList":[]} \ No newline at end of file diff --git a/examples/unit/minioled/cores3_minioled_example.py b/examples/unit/minioled/cores3_minioled_example.py index 8bdddb7d..7cd80d08 100644 --- a/examples/unit/minioled/cores3_minioled_example.py +++ b/examples/unit/minioled/cores3_minioled_example.py @@ -7,6 +7,7 @@ from M5 import * from hardware import I2C from hardware import Pin +from unit import MiniOLEDUnit label0 = None @@ -23,7 +24,7 @@ def setup(): label0 = Widgets.Label("CoreS3", 127, 109, 1.0, 0xFFFFFF, 0x222222, Widgets.FONTS.DejaVu18) i2c0 = I2C(0, scl=Pin(1), sda=Pin(2), freq=100000) - minioled_0 = M5.addDisplay(i2c0, 0x3C, {"unit_mini_oled": True}) + minioled_0 = MiniOLEDUnit(i2c0, 0x3C) label1 = Widgets.Label( "Mini", 15, 9, 1.0, 0xFFFFFF, 0x222222, Widgets.FONTS.DejaVu18, minioled_0 ) diff --git a/examples/unit/oled/cores3_oled_example.m5f2 b/examples/unit/oled/cores3_oled_example.m5f2 index 87b65ada..076846a9 100644 --- a/examples/unit/oled/cores3_oled_example.m5f2 +++ b/examples/unit/oled/cores3_oled_example.m5f2 @@ -1 +1 @@ -{"version":"V2.0","versionNumber":"V2.2.0","type":"cores3","components":[{"name":"screen","type":"screen","layer":0,"screenId":"builtin","screenName":"","id":"__cores3_screen","createTime":1735807653121,"x":0,"y":0,"width":320,"height":240,"backgroundColor":"#222222","size":0,"isSelected":true},{"name":"label0","type":"label","layer":1,"screenId":"builtin","screenName":"","id":"z&fdY5PXlBFvBCm-","createTime":1735807695753,"x":127,"y":109,"color":"#ffffff","backgroundColor":"#222222","text":"CoreS3","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false},{"name":"screen","type":"screen","layer":0,"screenId":"r_htkEtl@kJ2d!U1","screenName":"oled_0","id":"__display_screen","createTime":1735807708582,"x":0,"y":0,"width":64,"height":128,"backgroundColor":"#000000","size":0,"isSelected":true},{"name":"label1","type":"label","layer":2,"screenId":"r_htkEtl@kJ2d!U1","screenName":"oled_0","id":"b2VII+iMduXn^X&t","createTime":1735807711160,"x":5,"y":53,"color":"#ffffff","backgroundColor":"#222222","text":"OLED","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false}],"resources":[{"hardware":["hardware_button","hardware_pin_button","imu","speaker","touch","als","mic","i2c"]},{"unit":["unit_oled"]}],"units":[{"type":"unit_oled","name":"oled_0","portList":["A","PAHUB","Custom"],"portType":"A","userPort":[22,21],"id":"aUlL$9ih*`KtQ1&M","createTime":1735807691755,"bus":"i2c0","pahubPortList":[0,1,2,3,4,5],"pahubPort":0,"initBlockId":"Ee8lx{kc40Ptv~co^gAB"}],"hats":[],"bases":[],"i2cs":[{"id":"i2c0","portType":"A","userPort":[22,21],"freq":"100000","blockId":":1Oz0;ISm{A;F;3pScYB"}],"blockly":"true010000012oled_0true0x3ctrue","screen":[{"simulationName":"Built-in","type":"builtin","width":320,"height":240,"scale":0.78,"screenName":"","blockId":"","screenColorType":0,"id":"builtin","createTime":1735807653121},{"simulationName":"display_0","type":"display","width":64,"height":128,"scale":1,"screenName":"oled_0","blockId":"Ee8lx{kc40Ptv~co^gAB","screenColorType":1,"id":"r_htkEtl@kJ2d!U1","createTime":1735807708565,"fullZoom":2.602}],"logicWhenNum":0,"customList":[]} \ No newline at end of file +{"version":"V2.0","versionNumber":"V2.2.3","type":"cores3","components":[{"name":"screen","type":"screen","layer":0,"screenId":"builtin","screenName":"","id":"__cores3_screen","createTime":1735807653121,"x":0,"y":0,"width":320,"height":240,"backgroundColor":"#222222","size":0},{"name":"label0","type":"label","layer":1,"screenId":"builtin","screenName":"","id":"z&fdY5PXlBFvBCm-","createTime":1735807695753,"x":127,"y":109,"color":"#ffffff","backgroundColor":"#222222","text":"CoreS3","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0},{"name":"screen","type":"screen","layer":0,"screenId":"r_htkEtl@kJ2d!U1","screenName":"oled_0","id":"__display_screen","createTime":1735807708582,"x":0,"y":0,"width":64,"height":128,"backgroundColor":"#000000","size":0,"isSelected":true},{"name":"label1","type":"label","layer":2,"screenId":"r_htkEtl@kJ2d!U1","screenName":"oled_0","id":"b2VII+iMduXn^X&t","createTime":1735807711160,"x":5,"y":53,"color":"#ffffff","backgroundColor":"#222222","text":"OLED","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false}],"resources":[{"hardware":["hardware_button","hardware_pin_button","imu","speaker","touch","als","mic","i2c"]},{"unit":["unit_oled"]}],"units":[{"type":"unit_oled","name":"oled_0","portList":["A","PAHUB","Custom"],"portType":"A","userPort":[22,21],"id":"aUlL$9ih*`KtQ1&M","createTime":1742264419472,"bus":"i2c0","pahubPortList":[0,1,2,3,4,5],"pahubPort":0,"initBlockId":"Ee8lx{kc40Ptv~co^gAB"}],"hats":[],"bases":[],"i2cs":[{"id":"i2c0","portType":"A","userPort":[22,21],"freq":"100000","blockId":":1Oz0;ISm{A;F;3pScYB"}],"blockly":"true010000012oled_0true0x3ctrue","screen":[{"simulationName":"Built-in","type":"builtin","width":320,"height":240,"scale":0.78,"screenName":"","blockId":"","screenColorType":0,"id":"builtin","createTime":1735807653121},{"simulationName":"display_0","type":"display","width":64,"height":128,"scale":1,"screenName":"oled_0","blockId":"Ee8lx{kc40Ptv~co^gAB","screenColorType":1,"id":"r_htkEtl@kJ2d!U1","createTime":1735807708565,"fullZoom":2.602}],"logicWhenNum":0,"customList":[]} \ No newline at end of file diff --git a/examples/unit/oled/cores3_oled_example.py b/examples/unit/oled/cores3_oled_example.py index db75902e..a79acc96 100644 --- a/examples/unit/oled/cores3_oled_example.py +++ b/examples/unit/oled/cores3_oled_example.py @@ -1,8 +1,13 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + import os, sys, io import M5 from M5 import * from hardware import I2C from hardware import Pin +from unit import OLEDUnit label0 = None @@ -19,7 +24,7 @@ def setup(): label0 = Widgets.Label("CoreS3", 127, 109, 1.0, 0xFFFFFF, 0x222222, Widgets.FONTS.DejaVu18) i2c0 = I2C(0, scl=Pin(1), sda=Pin(2), freq=100000) - oled_0 = M5.addDisplay(i2c0, 0x3C, {"unit_oled": True}) + oled_0 = OLEDUnit(i2c0, 0x3C) label1 = Widgets.Label("OLED", 5, 53, 1.0, 0xFFFFFF, 0x222222, Widgets.FONTS.DejaVu18, oled_0) diff --git a/examples/unit/rca/core2_rca_example.m5f2 b/examples/unit/rca/core2_rca_example.m5f2 index 4f21f7cd..30602927 100644 --- a/examples/unit/rca/core2_rca_example.m5f2 +++ b/examples/unit/rca/core2_rca_example.m5f2 @@ -1 +1 @@ -{"version":"V2.0","versionNumber":"V2.2.0","type":"core2","components":[{"name":"screen","type":"screen","layer":0,"screenId":"builtin","screenName":"","id":"__core2_screen","createTime":1735635827687,"x":0,"y":0,"width":320,"height":240,"backgroundColor":"#222222","size":0,"isSelected":true},{"name":"label0","type":"label","layer":1,"screenId":"builtin","screenName":"","id":"j4o97TmAk26=R&@w","createTime":1735635842834,"x":133,"y":109,"color":"#ffffff","backgroundColor":"#222222","text":"Core2","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false},{"name":"screen","type":"screen","layer":0,"screenId":"dI05n#fI!nWIYJ@*","screenName":"rca_0","id":"__display_screen","createTime":1735635857100,"x":0,"y":0,"width":216,"height":144,"backgroundColor":"#000000","size":0,"isSelected":true},{"name":"label1","type":"label","layer":2,"screenId":"dI05n#fI!nWIYJ@*","screenName":"rca_0","id":"pXqPOeyGrZs6mC&s","createTime":1735635859464,"x":88,"y":61,"color":"#ffffff","backgroundColor":"#222222","text":"RCA","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false}],"resources":[{"hardware":["hardware_button","hardware_pin_button","imu","speaker","touch","mic"]},{"unit":["unit_rca"]}],"units":[{"type":"unit_rca","name":"rca_0","portList":["B","Custom"],"portType":"B","userPort":[22,21],"id":"wYPsZfJYf9sw9*gO","createTime":1735635836445,"initBlockId":"C,R}rJ,l?sjA(hC+;u`C"}],"hats":[],"bases":[],"i2cs":[],"blockly":"truerca_0true21614400000true","screen":[{"simulationName":"Built-in","type":"builtin","width":320,"height":240,"scale":0.78,"screenName":"","blockId":"","screenColorType":0,"id":"builtin","createTime":1735635827686},{"simulationName":"display_0","type":"display","width":216,"height":144,"scale":1,"screenName":"rca_0","blockId":"C,R}rJ,l?sjA(hC+;u`C","screenColorType":0,"id":"dI05n#fI!nWIYJ@*","createTime":1735635857080,"fullZoom":2.313}],"logicWhenNum":0,"customList":[]} \ No newline at end of file +{"version":"V2.0","versionNumber":"V2.2.3","type":"core2","components":[{"name":"screen","type":"screen","layer":0,"screenId":"builtin","screenName":"","id":"__core2_screen","createTime":1735635827687,"x":0,"y":0,"width":320,"height":240,"backgroundColor":"#222222","size":0},{"name":"label0","type":"label","layer":1,"screenId":"builtin","screenName":"","id":"j4o97TmAk26=R&@w","createTime":1735635842834,"x":133,"y":109,"color":"#ffffff","backgroundColor":"#222222","text":"Core2","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0},{"name":"screen","type":"screen","layer":0,"screenId":"dI05n#fI!nWIYJ@*","screenName":"rca_0","id":"__display_screen","createTime":1735635857100,"x":0,"y":0,"width":216,"height":144,"backgroundColor":"#000000","size":0,"isSelected":true},{"name":"label1","type":"label","layer":2,"screenId":"dI05n#fI!nWIYJ@*","screenName":"rca_0","id":"pXqPOeyGrZs6mC&s","createTime":1735635859464,"x":88,"y":61,"color":"#ffffff","backgroundColor":"#222222","text":"RCA","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false}],"resources":[{"hardware":["hardware_button","hardware_pin_button","imu","speaker","touch","mic"]},{"unit":["unit_rca"]}],"units":[{"type":"unit_rca","name":"rca_0","portList":["B","Custom"],"portType":"B","userPort":[22,21],"id":"wYPsZfJYf9sw9*gO","createTime":1742264828977,"initBlockId":"C,R}rJ,l?sjA(hC+;u`C"}],"hats":[],"bases":[],"i2cs":[],"blockly":"truerca_0true21614400000true","screen":[{"simulationName":"Built-in","type":"builtin","width":320,"height":240,"scale":0.78,"screenName":"","blockId":"","screenColorType":0,"id":"builtin","createTime":1735635827686},{"simulationName":"display_0","type":"display","width":216,"height":144,"scale":1,"screenName":"rca_0","blockId":"C,R}rJ,l?sjA(hC+;u`C","screenColorType":0,"id":"dI05n#fI!nWIYJ@*","createTime":1735635857080,"fullZoom":2.313}],"logicWhenNum":0,"customList":[]} \ No newline at end of file diff --git a/m5stack/libs/module/display.py b/m5stack/libs/module/display.py index 69a5c52a..9ee20317 100644 --- a/m5stack/libs/module/display.py +++ b/m5stack/libs/module/display.py @@ -7,26 +7,31 @@ class DisplayModule: - """! Display Module 13.2 is an expansion module for HD audio and video. + """Initialize the Display Module. - @en Display Module 13.2 is an expansion module for HD audio and video, using GAOYUN GW1NR series FPGA chip to output display signals, and employing the LT8618S chip for signal output conditioning. - @cn Display Module 13.2是一个高清音视频扩展模块,采用高云GW1NR系列FPGA芯片输出显示信号,采用LT8618S芯片进行信号输出调理。 + :param int width: The logical width of the Display Module. Default is 1280px. + :param int height: The logical height of the Display Module. Default is 720px. + :param int refresh_rate: The refresh rate of the Display Module. Default is 60Hz. + :param int output_width: The width of the output of the Display Module. Default is 1280px. + :param int output_height: The height of the output of the Display Module. Default is 720px. + :param int scale_w: The scale width of the Display Module. Default is 1. + :param int scale_h: The scale height of the Display Module. Default is 1. + :param int pixel_clock: The pixel clock of the Display Module. Default is 74250000. - @color #0FE6D7 - @link https://docs.m5stack.com/en/module/Display%20Module%2013.2 - @image https://static-cdn.m5stack.com/resource/docs/products/module/Display%20Module%2013.2/img-cec9dc43-a087-44da-a219-831f70b19314.webp - @category module + UiFlow2 Code Block: - @example - from module import DisplayModule - disp = DisplayModule() - disp.display.fill(0) + |init.png| + MicroPython Code Block: + + .. code-block:: python + + from module import DisplayModule + module_display = DisplayModule(1280, 720, 60, 1280, 720, 1, 1, 74250000) """ def __new__( cls, - port: tuple = (36, 26), width: int = 1280, height: int = 720, refresh_rate: int = 60, @@ -36,19 +41,6 @@ def __new__( scale_h: int = 1, pixel_clock: int = 74250000, ) -> None: - """! Initialize the Module Display - - @param port The port to which the Module Display is connected. port[0]: not used, port[1]: dac pin. - @param width The width of the Module Display. - @param height The height of the Module Display. - @param refresh_rate The refresh rate of the Module Display. - @param output_width The width of the output of the Module Display. - @param output_height The height of the output of the Module Display. - @param scale_w The scale width of the Module Display. - @param scale_h The scale height of the Module Display. - @param pixel_clock The pixel clock of the Module Display. - - """ return M5.addDisplay( None, 0, diff --git a/m5stack/libs/module/rca.py b/m5stack/libs/module/rca.py index 70708eaf..9c71ce7f 100644 --- a/m5stack/libs/module/rca.py +++ b/m5stack/libs/module/rca.py @@ -7,32 +7,47 @@ class RCAModule: - """! Module RCA is a female jack terminal block for transmitting composite video. + """Initialize the RCA Module. - @en Module RCA is a female jack terminal block for transmitting composite video (audio or video), one of the most common A/V connectors, which transmits video or audio signals from a component device to an output device (i.e., a display or speaker). - @cn Module RCA是一个1.3英寸RCA扩展屏单元。采用SH1107驱动,分辨率为128*64,单色显示。 + :param int pin: The dac pin to which the RCA Module is connected. + :param int width: The width of the RCA display. + :param int height: The height of the RCA display. + :param int output_width: The width of the output of the RCA display. + :param int output_height: The height of the output of the RCA display. + :param int signal_type: The signal type of the RCA display. NTSC=0, NTSC_J=1, PAL=2, PAL_M=3, PAL_N=4. + :param int use_psram: The use of psram of the RCA display. + :param int output_level: The output level of the RCA display. - @color #0FE6D7 - @link https://docs.m5stack.com/en/unit/RCA - @image https://static-cdn.m5stack.com/resource/docs/products/unit/RCA/img-9420bb3d-22b8-4f80-b7fe-e708088f1e51.webp - @category module + UiFlow2 Code Block: - @example - from module import RCAModule - rca = RCAModule() - rca.display.fill(0) + |init.png| + MicroPython Code Block: + + .. code-block:: python + + from module import RCAModule + module_rca = RCAModule(26, width=216, height=144, output_width=0, output_height=0, signal_type=RCAModule.NTSC, use_psram=0, output_level=0) """ NTSC = 0 + """signal type. National Television System Committee.""" + NTSC_J = 1 + """signal type. National Television System Committee Japan.""" + PAL = 2 + """signal type. Phase Alternating Line.""" + PAL_M = 3 + """signal type. Phase Alternating Line M.""" + PAL_N = 4 + """signal type. Phase Alternating Line N.""" def __new__( cls, - port: int = 26, + pin: int = 26, width: int = 216, height: int = 144, output_width: int = 216, @@ -41,18 +56,6 @@ def __new__( use_psram: int = 0, output_level: int = 0, ) -> None: - """! Initialize the Module RCA - - @param port The port to which the Module RCA is connected. port[0]: not used, port[1]: dac pin. - @param width The width of the RCA display. - @param height The height of the RCA display. - @param output_width The width of the output of the RCA display. - @param output_height The height of the output of the RCA display. - @param signal_type The signal type of the RCA display. NTSC=0, NTSC_J=1, PAL=2, PAL_M=3, PAL_N=4. - @param use_psram The use of psram of the RCA display. - @param output_level The output level of the RCA display. - - """ return M5.addDisplay( None, 0, @@ -66,7 +69,7 @@ def __new__( "output_height": output_height, "signal_type": signal_type, "use_psram": use_psram, - "pin_dac": port, + "pin_dac": pin, "output_level": output_level, } }, diff --git a/m5stack/libs/unit/accel.py b/m5stack/libs/unit/accel.py index e1b8fb18..bc39dc8e 100644 --- a/m5stack/libs/unit/accel.py +++ b/m5stack/libs/unit/accel.py @@ -9,7 +9,7 @@ class AccelUnit(ADXL345): """Create an AccelUnit object. - :param I2C i2c: The I2C bus the AccelUnit is connected to. + :param I2C i2c: The I2C bus the Accel Unit is connected to. :param int address: The I2C address of the device. Default is 0x53. UiFlow2 Code Block: diff --git a/m5stack/libs/unit/glass.py b/m5stack/libs/unit/glass.py index 6808bf3c..a4eb636b 100644 --- a/m5stack/libs/unit/glass.py +++ b/m5stack/libs/unit/glass.py @@ -6,33 +6,30 @@ import M5 from .pahub import PAHUBUnit from machine import I2C +import sys + +if sys.platform != "esp32": + from typing import Union class GlassUnit: - """! Unit Glass is a 1.51-inch transparent OLED expansion screen unit. + """Initialize the Glass Unit. - @en Unit Glass is a 1.51-inch transparent OLED expansion screen unit. It adopts STM32+SSD1309 driver scheme,resolution is 128*64, monochrome display, transparent area is 128*56. - @cn Unit Glass是一个1.51英寸透明OLED扩展屏单元。采用STM32+SSD1309驱动方案,分辨率为128*64,单色显示,透明区域为128*56。 + :param i2c: The I2C bus the Glass Unit is connected to. + :type i2c: I2C | PAHUBUnit + :param int address: The I2C address of the Glass Unit, default is 0x3D. - @color #0FE6D7 - @link https://docs.m5stack.com/en/unit/Glass%20Unit - @image https://static-cdn.m5stack.com/resource/docs/products/unit/Glass%20Unit/img-4384183e-b663-4dfc-bc3f-5070166c6e2b.webp - @category unit + UiFlow2 Code Block: - @example - from unit import GlassUnit - glass = GlassUnit() - glass.display.fill(0) + |init.png| - """ + MicroPython Code Block: - def __new__( - cls, i2c: I2C | PAHUBUnit, address: int | list | tuple = 0x3D, freq: int = 400000 - ) -> None: - """! Initialize the Unit Glass + .. code-block:: python + + from unit import GlassUnit + glass_0 = GlassUnit(i2c0, 0x3d) + """ - @param port The port to which the Unit Glass is connected. port[0]: scl pin, port[1]: sda pin. - @param address I2C address of the Unit Glass, default is 0x3D. - @param freq I2C frequency of the Unit Glass. - """ + def __new__(cls, i2c: Union[I2C, PAHUBUnit], address: int | list | tuple = 0x3D): return M5.addDisplay(i2c, address, {"unit_glass": True}) # Add Glass unit diff --git a/m5stack/libs/unit/glass2.py b/m5stack/libs/unit/glass2.py index a1dcfbd2..eb98bcee 100644 --- a/m5stack/libs/unit/glass2.py +++ b/m5stack/libs/unit/glass2.py @@ -6,35 +6,30 @@ import M5 from .pahub import PAHUBUnit from machine import I2C +import sys + +if sys.platform != "esp32": + from typing import Union class Glass2Unit: - """! Glass2 Unit is a 1.51-inch transparent OLED display unit that adopts the SSD1309 driver solution. + """Initialize the Glass2 Unit. - @en Glass2 Unit is a 1.51-inch transparent OLED display unit that adopts the SSD1309 driver solution. - @cn Glass2 Unit是一个1.51英寸透明OLED显示单元,采用SSD1309驱动方案。 + :param i2c: The I2C bus the Glass2 Unit is connected to. + :type i2c: I2C | PAHUBUnit + :param int address: The I2C address of the Glass2 Unit, default is 0x3C. - @color #0FE6D7 - @link https://docs.m5stack.com/en/unit/Glass2%20Unit - @image https://static-cdn.m5stack.com/resource/docs/products/unit/Glass2%20Unit/img-d882d0b7-dce0-4202-9b76-b9f25e7ad829.webp - @category unit + UiFlow2 Code Block: - @example - from unit import Glass2Unit - from hardware import * - i2c = I2C(1, scl=22, sda=21) - display = Glass2Unit(i2c, 0x3c).display - display.fill(0) + |init.png| - """ + MicroPython Code Block: - def __new__( - cls, i2c: I2C | PAHUBUnit, address: int | list | tuple = 0x3C, freq: int = 400000 - ) -> None: - """! Initialize the Unit Glass2 + .. code-block:: python + + from unit import Glass2Unit + glass2_0 = Glass2Unit(i2c0, 0x3c) + """ - @param port The port to which the Unit Glass2 is connected. port[0]: scl pin, port[1]: sda pin. - @param address I2C address of the Unit Glass2, default is 0x3C. - @param freq I2C frequency of the Unit Glass2. - """ + def __new__(cls, i2c: Union[I2C, PAHUBUnit], address: int | list | tuple = 0x3C) -> None: return M5.addDisplay(i2c, address, {"unit_glass2": True}) # Add Glass2 unit diff --git a/m5stack/libs/unit/lcd.py b/m5stack/libs/unit/lcd.py index f8debc8d..8be47d2f 100644 --- a/m5stack/libs/unit/lcd.py +++ b/m5stack/libs/unit/lcd.py @@ -6,35 +6,30 @@ import M5 from .pahub import PAHUBUnit from machine import I2C +import sys + +if sys.platform != "esp32": + from typing import Union class LCDUnit: - """! Unit LCD is a 1.14 inch color LCD expansion screen unit. + """Initialize the LCD Unit. - @en Unit LCD is a 1.14 inch color LCD expansion screen unit. It adopts ST7789V2 drive scheme, the resolution is 135*240, and it supports RGB666 display (262,144 colors). - @cn Unit LCD是一个1.14英寸彩色LCD扩展屏单元。采用ST7789V2驱动方案,分辨率为135*240,支持RGB666显示(262,144种颜色)。 + :param i2c: The I2C bus the LCD Unit is connected to. + :type i2c: I2C | PAHUBUnit + :param int address: The I2C address of the LCD Unit, default is 0x3E. - @color #0FE6D7 - @link https://docs.m5stack.com/en/unit/lcd - @image https://static-cdn.m5stack.com/resource/docs/products/unit/lcd/lcd_01.webp - @category unit + UiFlow2 Code Block: - @example - from unit import LCDUnit - from hardware import * - i2c = I2C(1, scl=22, sda=21) - display = LCDUnit(i2c, 0x3c).display - display.fill(0) + |init.png| - """ + MicroPython Code Block: - def __new__( - cls, i2c: I2C | PAHUBUnit, address: int | list | tuple = 0x3E, freq: int = 400000 - ) -> None: - """! Initialize the Unit LCD + .. code-block:: python + + from unit import LCDUnit + lcd_0 = LCDUnit(i2c0, 0x3e) + """ - @param port The port to which the Unit LCD is connected. port[0]: scl pin, port[1]: sda pin. - @param address I2C address of the Unit LCD, default is 0x3D. - @param freq I2C frequency of the Unit LCD. - """ + def __new__(cls, i2c: Union[I2C, PAHUBUnit], address: int | list | tuple = 0x3E) -> None: return M5.addDisplay(i2c, address, {"unit_lcd": True}) # Add LCD unit diff --git a/m5stack/libs/unit/minioled.py b/m5stack/libs/unit/minioled.py index e68c520b..ff922d5c 100644 --- a/m5stack/libs/unit/minioled.py +++ b/m5stack/libs/unit/minioled.py @@ -6,33 +6,30 @@ import M5 from .pahub import PAHUBUnit from machine import I2C +import sys + +if sys.platform != "esp32": + from typing import Union class MiniOLEDUnit: - """! MiniOLED UNIT is a 0.42-inch I2C interface OLED screen unit, it's a 72*40, monochrome white display. + """Initialize the Mini OLED Unit. - @en MiniOLED UNIT is a 0.42-inch I2C interface OLED screen unit, it's a 72*40, monochrome white display. - @cn MiniOLED UNIT是一个0.42英寸I2C接口OLED屏单元,分辨率为72*40,单色白色显示。 + :param i2c: The I2C bus the Mini OLED Unit is connected to. + :type i2c: I2C | PAHUBUnit + :param int address: The I2C address of the Mini OLED Unit, default is 0x3C. - @color #0FE6D7 - @link https://docs.m5stack.com/en/unit/MiniOLED%20Unit - @image https://static-cdn.m5stack.com/resource/docs/products/unit/MiniOLED%20Unit/img-8d9a2ae0-331b-4c02-8e2f-0f9142a4395d.webp - @category unit + UiFlow2 Code Block: - @example - from unit import MiniOLEDUnit - oled = MiniOLEDUnit() - oled.display.fill(0) + |init.png| - """ + MicroPython Code Block: - def __new__( - cls, i2c: I2C | PAHUBUnit, address: int | list | tuple = 0x3C, freq: int = 400000 - ) -> None: - """! Initialize the Unit MiniOLED + .. code-block:: python + + from unit import MiniOLEDUnit + minioled_0 = MiniOLEDUnit(i2c0, 0x3c) + """ - @param port The port to which the Unit MiniOLED is connected. port[0]: scl pin, port[1]: sda pin. - @param address I2C address of the Unit MiniOLED, default is 0x3c. - @param freq I2C frequency of the Unit MiniOLED. - """ + def __new__(cls, i2c: Union[I2C, PAHUBUnit], address: int | list | tuple = 0x3C) -> None: return M5.addDisplay(i2c, address, {"unit_mini_oled": True}) # Add MiniOLED unit diff --git a/m5stack/libs/unit/oled.py b/m5stack/libs/unit/oled.py index 219f33fe..e05d7d80 100644 --- a/m5stack/libs/unit/oled.py +++ b/m5stack/libs/unit/oled.py @@ -6,33 +6,30 @@ import M5 from .pahub import PAHUBUnit from machine import I2C +import sys + +if sys.platform != "esp32": + from typing import Union class OLEDUnit: - """! Unit OLED is a 1.3-inch OLED expansion screen unit. + """Initialize the OLED Unit. - @en Unit OLED is a 1.3-inch OLED expansion screen unit. Driveing by SH1107, and the resolution is 128*64, monochrome display. - @cn Unit OLED是一个1.3英寸OLED扩展屏单元。采用SH1107驱动,分辨率为128*64,单色显示。 + :param i2c: The I2C bus the OLED Unit is connected to. + :type i2c: I2C | PAHUBUnit + :param int address: The I2C address of the OLED Unit, default is 0x3C. - @color #0FE6D7 - @link https://docs.m5stack.com/en/unit/oled - @image https://static-cdn.m5stack.com/resource/docs/products/unit/oled/oled_01.webp - @category unit + UiFlow2 Code Block: - @example - from unit import OLEDUnit - oled = OLEDUnit() - oled.display.fill(0) + |init.png| - """ + MicroPython Code Block: - def __new__( - cls, i2c: I2C | PAHUBUnit, address: int | list | tuple = 0x3C, freq: int = 400000 - ) -> None: - """! Initialize the Unit OLED + .. code-block:: python + + from unit import OLEDUnit + oled_0 = OLEDUnit(i2c0, 0x3c) + """ - @param port The port to which the Unit OLED is connected. port[0]: scl pin, port[1]: sda pin. - @param address I2C address of the Unit OLED, default is 0x3c. - @param freq I2C frequency of the Unit OLED. - """ + def __new__(cls, i2c: Union[I2C, PAHUBUnit], address: int | list | tuple = 0x3C) -> None: return M5.addDisplay(i2c, address, {"unit_oled": True}) # Add OLED unit diff --git a/m5stack/libs/unit/pahub.py b/m5stack/libs/unit/pahub.py index 466ec5b2..7c2e6c96 100644 --- a/m5stack/libs/unit/pahub.py +++ b/m5stack/libs/unit/pahub.py @@ -4,13 +4,11 @@ from machine import I2C from micropython import const import time +import sys + +if sys.platform != "esp32": + from typing import Literal -try: - from typing import overload, Sequence - from typing_extensions import Literal - from uio import AnyReadableBuf, AnyWritableBuf -except ImportError: - pass PAHUB_CHN0 = const(0) PAHUB_CHN1 = const(1) @@ -63,12 +61,12 @@ def stop(self) -> None: self._i2c.stop() self.release_channel(self._chn) - def readinto(self, buf: AnyWritableBuf, nack: bool = True) -> None: + def readinto(self, buf, nack: bool = True) -> None: self.select_channel(self._chn) self._i2c.readinto(buf, nack) self.release_channel(self._chn) - def write(self, buf: AnyReadableBuf) -> int: + def write(self, buf) -> int: self.select_channel(self._chn) result = self._i2c.write(buf) self.release_channel(self._chn) @@ -80,19 +78,19 @@ def readfrom(self, addr: int, nbytes: int, stop: bool = True) -> bytes: self.release_channel(self._chn) return result - def readfrom_into(self, addr: int, buf: AnyWritableBuf, stop: bool = True) -> None: + def readfrom_into(self, addr: int, buf, stop: bool = True) -> None: self.select_channel(self._chn) self._i2c.readfrom_into(addr, buf, stop) self.release_channel(self._chn) - def writeto(self, addr: int, buf: AnyReadableBuf, stop: bool = True) -> int: + def writeto(self, addr: int, buf, stop: bool = True) -> int: self.select_channel(self._chn) result = self._i2c.writeto(addr, buf, stop) time.sleep_ms(100) self.release_channel(self._chn) return result - def writevto(self, addr: int, vector: Sequence[AnyReadableBuf], stop: bool = True) -> int: + def writevto(self, addr: int, vector, stop: bool = True) -> int: self.select_channel(self._chn) result = self._i2c.writevto(addr, vector, stop) self.release_channel(self._chn) @@ -104,14 +102,12 @@ def readfrom_mem(self, addr: int, memaddr: int, nbytes: int, addrsize: int = 8) self.release_channel(self._chn) return result - def readfrom_mem_into( - self, addr: int, memaddr: int, buf: AnyWritableBuf, addrsize: int = 8 - ) -> None: + def readfrom_mem_into(self, addr: int, memaddr: int, buf, addrsize: int = 8) -> None: self.select_channel(self._chn) self._i2c.readfrom_mem_into(addr, memaddr, buf, addrsize=addrsize) self.release_channel(self._chn) - def writeto_mem(self, addr: int, memaddr: int, buf: AnyReadableBuf, addrsize: int = 8) -> None: + def writeto_mem(self, addr: int, memaddr: int, buf, addrsize: int = 8) -> None: self.select_channel(self._chn) self._i2c.writeto_mem(addr, memaddr, buf, addrsize=addrsize) self.release_channel(self._chn) diff --git a/m5stack/libs/unit/rca.py b/m5stack/libs/unit/rca.py index 9efeb817..03307af7 100644 --- a/m5stack/libs/unit/rca.py +++ b/m5stack/libs/unit/rca.py @@ -7,28 +7,43 @@ class RCAUnit: - """! Unit RCA is a female jack terminal block for transmitting composite video. + """Initialize the RCA Unit. - @en Unit RCA is a female jack terminal block for transmitting composite video (audio or video), one of the most common A/V connectors, which transmits video or audio signals from a component device to an output device (i.e., a display or speaker). - @cn Unit RCA是一个1.3英寸RCA扩展屏单元。采用SH1107驱动,分辨率为128*64,单色显示。 + :param tuple port: The port to which the RCA Unit is connected. port[0]: not used, port[1]: dac pin. + :param int width: The width of the RCA display. + :param int height: The height of the RCA display. + :param int output_width: The width of the output of the RCA display. + :param int output_height: The height of the output of the RCA display. + :param int signal_type: The signal type of the RCA display. NTSC=0, NTSC_J=1, PAL=2, PAL_M=3, PAL_N=4. + :param int use_psram: The use of psram of the RCA display. + :param int output_level: The output level of the RCA display. - @color #0FE6D7 - @link https://docs.m5stack.com/en/unit/RCA - @image https://static-cdn.m5stack.com/resource/docs/products/unit/RCA/img-9420bb3d-22b8-4f80-b7fe-e708088f1e51.webp - @category unit + UiFlow2 Code Block: - @example - from unit import RCAUnit - rca = RCAUnit() - rca.display.fill(0) + |init.png| + MicroPython Code Block: + + .. code-block:: python + + from module import RCAModule + module_rca = RCAModule(26, width=216, height=144, output_width=0, output_height=0, signal_type=RCAModule.NTSC, use_psram=0, output_level=0) """ NTSC = 0 + """signal type. National Television System Committee.""" + NTSC_J = 1 + """signal type. National Television System Committee Japan.""" + PAL = 2 + """signal type. Phase Alternating Line.""" + PAL_M = 3 + """signal type. Phase Alternating Line M.""" + PAL_N = 4 + """signal type. Phase Alternating Line N.""" def __new__( cls, @@ -41,17 +56,6 @@ def __new__( use_psram: int = 0, output_level: int = 0, ) -> None: - """! Initialize the Unit RCA - - @param port The port to which the Unit RCA is connected. port[0]: not used, port[1]: dac pin. - @param width The width of the RCA display. - @param height The height of the RCA display. - @param output_width The width of the output of the RCA display. - @param output_height The height of the output of the RCA display. - @param signal_type The signal type of the RCA display. NTSC=0, NTSC_J=1, PAL=2, PAL_M=3, PAL_N=4. - @param use_psram The use of psram of the RCA display. - @param output_level The output level of the RCA display. - """ return M5.addDisplay( None, 0, From 8f5dcf1a60bf6d77cac6a832472f8f14280a0ebf Mon Sep 17 00:00:00 2001 From: Tinyu-Zhao Date: Thu, 20 Mar 2025 16:08:48 +0800 Subject: [PATCH 026/322] lib/module: Add Commu module support. Signed-off-by: Tinyu-Zhao --- docs/en/conf.py | 2 +- docs/en/module/commu.rst | 72 ++ docs/en/module/index.rst | 1 + docs/en/refs/module.commu.ref | 23 + .../locales/zh_CN/LC_MESSAGES/module/commu.po | 442 ++++++++++++ .../module/commu/commu_core2_example.m5f2 | 1 + examples/module/commu/commu_core2_example.py | 64 ++ m5stack/libs/driver/manifest.py | 14 +- m5stack/libs/driver/mcp2515/__init__.py | 3 + m5stack/libs/driver/mcp2515/can_frame.py | 94 +++ m5stack/libs/driver/mcp2515/mcp2515_param.py | 642 ++++++++++++++++++ m5stack/libs/driver/mcp2515/mcp2515_spi.py | 436 ++++++++++++ m5stack/libs/module/__init__.py | 3 + m5stack/libs/module/commu.py | 291 ++++++++ m5stack/libs/module/manifest.py | 1 + 15 files changed, 2083 insertions(+), 6 deletions(-) create mode 100644 docs/en/module/commu.rst create mode 100644 docs/en/refs/module.commu.ref create mode 100644 docs/locales/zh_CN/LC_MESSAGES/module/commu.po create mode 100644 examples/module/commu/commu_core2_example.m5f2 create mode 100644 examples/module/commu/commu_core2_example.py create mode 100644 m5stack/libs/driver/mcp2515/__init__.py create mode 100644 m5stack/libs/driver/mcp2515/can_frame.py create mode 100644 m5stack/libs/driver/mcp2515/mcp2515_param.py create mode 100644 m5stack/libs/driver/mcp2515/mcp2515_spi.py create mode 100644 m5stack/libs/module/commu.py diff --git a/docs/en/conf.py b/docs/en/conf.py index 6b287a6f..7c6a57f6 100644 --- a/docs/en/conf.py +++ b/docs/en/conf.py @@ -51,7 +51,7 @@ "M5", "module.mbus", "network", - "m5can" + "m5can", ] autodoc_default_options = { diff --git a/docs/en/module/commu.rst b/docs/en/module/commu.rst new file mode 100644 index 00000000..308018f7 --- /dev/null +++ b/docs/en/module/commu.rst @@ -0,0 +1,72 @@ +Commu Module +============== + +.. sku: M011 + +.. include:: ../refs/module.commu.ref + +This is the driver library for the module Commu for receiving and sending CAN / RS485 / I2C data. + +Support the following products: + + |commu| + + +UiFlow2 Example +--------------- + +CAN, RS485, I2C communication +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Open the |commu_core2_example.m5f2| project in UiFlow2. + +This example shows how to receive and send data using the Commu Module. + +UiFlow2 Code Block: + + |example.png| + +Example output: + + None + +MicroPython Example +------------------- + +CAN, RS485, I2C communication +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +This example shows how to receive and send data using the Commu Module. + +MicroPython Code Block: + + .. literalinclude:: ../../../examples/module/commu/commu_core2_example.py + :language: python + :linenos: + +Example output: + + None + +**API** +------- + +CommuModule +^^^^^^^^^^^ + +.. autoclass:: module.commu.CommuModuleCAN + :members: + +.. autoclass:: module.commu.CommuModuleRS485 + :members: + + The `CommuModuleRS485` class wraps an instance of the `UART` class. + + For more details, see :ref:`hardware.UART `. + +.. autoclass:: module.commu.CommuModuleI2C + :members: + + The `CommuModuleI2C` class wraps an instance of the `I2C` class. + + For more details, see :ref:`machine.I2C `. -- a two-wire serial protocol. diff --git a/docs/en/module/index.rst b/docs/en/module/index.rst index 84572de7..074d9cd1 100644 --- a/docs/en/module/index.rst +++ b/docs/en/module/index.rst @@ -7,6 +7,7 @@ Module 4in8out.rst ain4.rst bala2.rst + commu.rst display.rst dmx.rst dualkmeter.rst diff --git a/docs/en/refs/module.commu.ref b/docs/en/refs/module.commu.ref new file mode 100644 index 00000000..1d2f82a7 --- /dev/null +++ b/docs/en/refs/module.commu.ref @@ -0,0 +1,23 @@ +.. |commu| image:: https://static-cdn.m5stack.com/resource/docs/products/module/commu/commu_01.webp + :target: https://docs.m5stack.com/en/module/commu + :height: 200px + :width: 200px + +.. |init.png| image:: https://m5stack.oss-cn-shenzhen.aliyuncs.com/mpy_docs/module/commu/init.png +.. |info.png| image:: https://m5stack.oss-cn-shenzhen.aliyuncs.com/mpy_docs/module/commu/info.png +.. |get_irq_state.png| image:: https://m5stack.oss-cn-shenzhen.aliyuncs.com/mpy_docs/module/commu/get_irq_state.png +.. |any.png| image:: https://m5stack.oss-cn-shenzhen.aliyuncs.com/mpy_docs/module/commu/any.png +.. |recv_message.png| image:: https://m5stack.oss-cn-shenzhen.aliyuncs.com/mpy_docs/module/commu/recv_message.png +.. |recv_message_into.png| image:: https://m5stack.oss-cn-shenzhen.aliyuncs.com/mpy_docs/module/commu/recv_message_into.png +.. |send.png| image:: https://m5stack.oss-cn-shenzhen.aliyuncs.com/mpy_docs/module/commu/send.png + +.. |example.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/commu/example.png + +.. |commu_core2_example.m5f2| raw:: html + + + commu_core2_example.m5f2 + \ No newline at end of file diff --git a/docs/locales/zh_CN/LC_MESSAGES/module/commu.po b/docs/locales/zh_CN/LC_MESSAGES/module/commu.po new file mode 100644 index 00000000..37a84de9 --- /dev/null +++ b/docs/locales/zh_CN/LC_MESSAGES/module/commu.po @@ -0,0 +1,442 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2016 - 2025 M5Stack Technology Co., Ltd +# This file is distributed under the same license as the UIFlow2 Programming +# Guide package. +# FIRST AUTHOR , 2025. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: UIFlow2 Programming Guide\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-03-20 17:06+0800\n" +"PO-Revision-Date: 2025-03-20 16:05+0800\n" +"Last-Translator: \n" +"Language: zh_CN\n" +"Language-Team: zh_CN \n" +"Plural-Forms: nplurals=1; plural=0;\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.16.0\n" + +#: ../../en/module/commu.rst:2 937adf6dd137423e8ee1f461e4c01229 +msgid "Commu Module" +msgstr "" + +#: ../../en/module/commu.rst:8 2bad3100a227459fb6403c283c49241e +msgid "" +"This is the driver library for the module Commu for receiving and sending" +" CAN / RS485 / I2C data." +msgstr "这是模块 Commu 的驱动程序库,用于接收和发送 CAN / RS485 / I2C 数据。" + +#: ../../en/module/commu.rst:10 2b8185a516c54e09a157fd1e67e7169b +msgid "Support the following products:" +msgstr "支持以下产品: " + +#: ../../en/module/commu.rst:12 8df991a1ee3848f28e5f134678a83e28 +msgid "|commu|" +msgstr "" + +#: ../../en/refs/module.commu.ref 0e8e1a1eef6d49ad99b4fa365710f39b +msgid "commu" +msgstr "" + +#: ../../en/module/commu.rst:16 97cad8f65d18400f9e7182d5831ea6d4 +msgid "UiFlow2 Example" +msgstr "UiFlow2 示例" + +#: ../../en/module/commu.rst:19 ../../en/module/commu.rst:37 +#: 97e500187721423094b5612878280f0c a3979ec8fa624d74b9241837ef96f61d +msgid "CAN, RS485, I2C communication" +msgstr "CAN, RS485, I2C 通信" + +#: ../../en/module/commu.rst:21 0bc5e8227b8443609992facae5d0f2f9 +msgid "Open the |commu_core2_example.m5f2| project in UiFlow2." +msgstr "打开 |commu_core2_example.m5f2| 项目在 UiFlow2 中。" + +#: ../../en/module/commu.rst:23 ../../en/module/commu.rst:39 +#: 54b570dd61c145fba7f1e3b451b588d4 849a577721a94bab8796c932f667769b +msgid "This example shows how to receive and send data using the Commu Module." +msgstr "这个示例展示了如何使用 Commu 模块接收和发送数据。" + +#: ../../en/module/commu.rst:25 012c46dd96214b0eb2095d10b32c73a1 +#: a313879e8ed241349cb7d42202e2a13e b3b31b90c82d4efabdd28c83cdd16ab0 +#: bc0784f5a1614f9f949136e9d5334633 e33f5357aeaf4e5589f4787bf91be0f1 +#: f8731eb1bd4c4f7b995ab3a0338f85d2 module.commu.CommuModuleCAN:39 +#: module.commu.CommuModuleCAN.any:6 module.commu.CommuModuleCAN.info:6 +#: module.commu.CommuModuleCAN.recv:15 module.commu.CommuModuleCAN.send:9 of +msgid "UiFlow2 Code Block:" +msgstr "UiFlow2 代码块:" + +#: ../../en/module/commu.rst:27 988b11b51d154a578bff159719e9f150 +msgid "|example.png|" +msgstr "" + +#: ../../en/refs/module.commu.ref:14 88463f4ba5624f77b21ec4e66eed6ed1 +msgid "example.png" +msgstr "" + +#: ../../en/module/commu.rst:29 ../../en/module/commu.rst:47 +#: 5916f5eb30664677b89c1c0f6335856f 8d911ea6b1394e968e4e424441d504f5 +msgid "Example output:" +msgstr "示例输出: " + +#: ../../en/module/commu.rst:31 ../../en/module/commu.rst:49 +#: 0cc4040faae84934856cdd2dddec8553 d0a87c64c9aa4f549e079a6fe2302813 +msgid "None" +msgstr "" + +#: ../../en/module/commu.rst:34 f0be773bf16e4501a4e3b586ebb5f131 +msgid "MicroPython Example" +msgstr "MicroPython 示例" + +#: ../../en/module/commu.rst:41 044dc9912c4b4eb6a6ef20066559c63e +#: 4bf2c847cc3e4f78ac77df0c2d61bdf1 868289c0479848e79aa1cd5d9d6d5292 +#: 9181701ebe6d40cf995c598dfbf5681a 96467597b0f9465caf51e4b5b4edc53a +#: 9be19e9df10948d79f232e043c7ca608 module.commu.CommuModuleCAN:43 +#: module.commu.CommuModuleCAN.any:10 module.commu.CommuModuleCAN.info:10 +#: module.commu.CommuModuleCAN.recv:21 module.commu.CommuModuleCAN.send:13 of +msgid "MicroPython Code Block:" +msgstr "MicroPython 代码块:" + +#: ../../en/module/commu.rst:52 a7b59cb00c5c4a6d9893e6abec82ab80 +msgid "**API**" +msgstr "API参考" + +#: ../../en/module/commu.rst:55 97d77eee397a4e61b5feeafa343763f8 +msgid "CommuModule" +msgstr "" + +#: 3ea8751fdc804e169a1e5c1c2f5c9c03 module.commu.CommuModuleCAN:1 of +msgid "Bases: :py:class:`~driver.mcp2515.mcp2515_spi.MCP2515_CAN`" +msgstr "" + +#: a56d2f22829d42048b5b352549c56f3d module.commu.CommuModuleCAN:1 of +msgid "Create an CommuModuleCAN object" +msgstr "创建一个 CommuModuleCAN 对象" + +#: ../../en/module/commu.rst 48487a939d434fa98e827293e39d3e0a +#: bcce32c472834835a3044994e9bf3592 e0c2e15fdc9e46f9b640c720c403a566 +#: module.commu.CommuModuleCAN.recv module.commu.CommuModuleCAN.send of +msgid "Parameters" +msgstr "" + +#: de4b0024b4e4485ab163b871d5b6bdf0 module.commu.CommuModuleCAN:3 of +msgid "" +"The CAN mode to use(NORMAL, LISTEN_ONLY), Default is NORMAL. Options:" +" - ``NORMAL``: Normal mode - ``LISTEN_ONLY``: Listen only mode" +msgstr "" +"使用的 CAN 模式 (NORMAL, LISTEN_ONLY),默认是 NORMAL。选项:\n" +" - ``NORMAL``: 正常模式\n" +" - ``LISTEN_ONLY``: 监听模式" + +#: fb083d2bc6164b6dae40900eb143ed76 module.commu.CommuModuleCAN:3 of +msgid "The CAN mode to use(NORMAL, LISTEN_ONLY), Default is NORMAL." +msgstr "使用的 CAN 模式 (NORMAL, LISTEN_ONLY),默认是 NORMAL。" + +#: 8daa918f89eb4b3394877848322483cc 9ba0278541274a4fa63c199c8760d10b +#: f45d02a214b84423bf3610b6a98230ae module.commu.CommuModuleCAN:7 +#: module.commu.CommuModuleCAN:28 module.commu.CommuModuleCAN:35 of +msgid "Options:" +msgstr "选项: " + +#: 49dfabeac6a847c69693346e6488f878 module.commu.CommuModuleCAN:6 of +msgid "``NORMAL``: Normal mode" +msgstr "``NORMAL``: 正常模式" + +#: ed97262e1e064fa8ab3a5bded3f3e038 module.commu.CommuModuleCAN:7 of +msgid "``LISTEN_ONLY``: Listen only mode" +msgstr "``LISTEN_ONLY``: 监听模式" + +#: f254bac6c5cf4c39a4d39ec7e4280826 module.commu.CommuModuleCAN:9 of +#, fuzzy +msgid "" +"The baudrate to use, Default is CAN_1000KBPS. Options: - " +"``CAN_5KBPS``: 5Kbps - ``CAN_10KBPS``: 10Kbps - ``CAN_20KBPS``: " +"20Kbps - ``CAN_31K25BPS``: 31.25Kbps - ``CAN_33KBPS``: 33Kbps" +" - ``CAN_40KBPS``: 40Kbps - ``CAN_50KBPS``: 50Kbps - " +"``CAN_80KBPS``: 80Kbps - ``CAN_83K3BPS``: 83.33Kbps - " +"``CAN_95KBPS``: 95Kbps - ``CAN_100KBPS``: 100Kbps - " +"``CAN_125KBPS``: 125Kbps - ``CAN_200KBPS``: 200Kbps - " +"``CAN_250KBPS``: 250Kbps - ``CAN_500KBPS``: 500Kbps - " +"``CAN_1000KBPS``: 1Mbps" +msgstr "" +"选项: - ``CAN_5KBPS``: 5Kbps - ``CAN_10KBPS``: 10Kbps - " +"``CAN_20KBPS``: 20Kbps - ``CAN_31K25BPS``: 31.25Kbps - " +"``CAN_33KBPS``: 33Kbps - ``CAN_40KBPS``: 40Kbps - ``CAN_50KBPS``:" +" 50Kbps - ``CAN_80KBPS``: 80Kbps - ``CAN_83K3BPS``: 83.33Kbps" +" - ``CAN_95KBPS``: 95Kbps - ``CAN_100KBPS``: 100Kbps - " +"``CAN_125KBPS``: 125Kbps - ``CAN_200KBPS``: 200Kbps - " +"``CAN_250KBPS``: 250Kbps - ``CAN_500KBPS``: 500Kbps - " +"``CAN_1000KBPS``: 1Mbps" + +#: b41784ee435547e992201a768fa0296d module.commu.CommuModuleCAN:9 of +msgid "The baudrate to use, Default is CAN_1000KBPS." +msgstr "使用的波特率,默认是 CAN_1000KBPS。" + +#: b4c1c73e726a4750bc9096eb060998ac module.commu.CommuModuleCAN:12 of +msgid "``CAN_5KBPS``: 5Kbps" +msgstr "" + +#: 76204e91c60b461fa141c26f8bb226da module.commu.CommuModuleCAN:13 of +msgid "``CAN_10KBPS``: 10Kbps" +msgstr "" + +#: cc5aa639e36448c7b81f0c0d65265f51 module.commu.CommuModuleCAN:14 of +msgid "``CAN_20KBPS``: 20Kbps" +msgstr "" + +#: 0d8b107921074508a9685047402ada0b module.commu.CommuModuleCAN:15 of +msgid "``CAN_31K25BPS``: 31.25Kbps" +msgstr "" + +#: 340d803cfea84277b091f89dd502f5e1 module.commu.CommuModuleCAN:16 of +msgid "``CAN_33KBPS``: 33Kbps" +msgstr "" + +#: dd6aac86c35b40efb9930ce2fbabc3c5 module.commu.CommuModuleCAN:17 of +msgid "``CAN_40KBPS``: 40Kbps" +msgstr "" + +#: ff1123fe7bbc437db04c5053081e85e0 module.commu.CommuModuleCAN:18 of +msgid "``CAN_50KBPS``: 50Kbps" +msgstr "" + +#: 16805acf8cb54ac3bfa28a20cab29465 module.commu.CommuModuleCAN:19 of +msgid "``CAN_80KBPS``: 80Kbps" +msgstr "" + +#: f797059633964eb08c9f1a9a1804aaaa module.commu.CommuModuleCAN:20 of +msgid "``CAN_83K3BPS``: 83.33Kbps" +msgstr "" + +#: 71e96db883464bc9b24cc937a78393ee module.commu.CommuModuleCAN:21 of +msgid "``CAN_95KBPS``: 95Kbps" +msgstr "" + +#: 7b7ee203fd5a499ab19c2c01a18cab80 module.commu.CommuModuleCAN:22 of +msgid "``CAN_100KBPS``: 100Kbps" +msgstr "" + +#: ffc6db2f6d294a918576f2a5f0d2e2d0 module.commu.CommuModuleCAN:23 of +msgid "``CAN_125KBPS``: 125Kbps" +msgstr "" + +#: 15dd3d6f30014f7e9b21d5eb1e3917c1 module.commu.CommuModuleCAN:24 of +msgid "``CAN_200KBPS``: 200Kbps" +msgstr "" + +#: a472169b23a34cbea7bd83e673d92a27 module.commu.CommuModuleCAN:25 of +msgid "``CAN_250KBPS``: 250Kbps" +msgstr "" + +#: 3af227a7493c4d8a965288828fc36df1 module.commu.CommuModuleCAN:26 of +msgid "``CAN_500KBPS``: 500Kbps" +msgstr "" + +#: 1a60f80791534197a787c0d5f40ef07e module.commu.CommuModuleCAN:27 of +msgid "``CAN_1000KBPS``: 1Mbps" +msgstr "" + +#: e4f6b62a97584aa2abbb2606a1282439 module.commu.CommuModuleCAN:30 of +msgid "The SPI baudrate to use, Default is 8000000." +msgstr "使用的 SPI 波特率,默认是 8000000。" + +#: bef71905f66f404e822a64815b621728 module.commu.CommuModuleCAN:31 of +msgid "" +"The CAN ID mode to use(MCP_STDEXT, MCP_EXTDONLY), Default is MCP_STDEXT." +" Options: - ``MCP_STDEXT``: Standard and Extended - " +"``MCP_EXTDONLY``: Extended only" +msgstr "" +"使用的 CAN ID 模式 (MCP_STDEXT, MCP_EXTDONLY),默认是 MCP_STDEXT。选项: \n" +" - ``MCP_STDEXT``: 标准+扩展模式 - ``MCP_EXTDONLY``: 拓展模式" + +#: bf8b72a899524a23a5f92ba49184c732 module.commu.CommuModuleCAN:31 of +msgid "The CAN ID mode to use(MCP_STDEXT, MCP_EXTDONLY), Default is MCP_STDEXT." +msgstr "使用的 CAN ID 模式 (MCP_STDEXT, MCP_EXTDONLY),默认是 MCP_STDEXT。" + +#: 61342e83241741268a3f52a391604a3b module.commu.CommuModuleCAN:34 of +msgid "``MCP_STDEXT``: Standard and Extended" +msgstr "``MCP_STDEXT``: 标准+扩展模式" + +#: 45772d660b664e7d9167c4ee180eafe4 module.commu.CommuModuleCAN:35 of +msgid "``MCP_EXTDONLY``: Extended only" +msgstr "``MCP_EXTDONLY``: 拓展模式" + +#: aa00d80a7235483e9d3f4d22617635c1 module.commu.CommuModuleCAN:37 of +msgid "Whether to enable debug mode, Default is False." +msgstr "是否启用调试模式,默认为 “False”。" + +#: f6fc2396ae784ddab0fa59e565345af3 module.commu.CommuModuleCAN:41 of +msgid "|init.png|" +msgstr "" + +#: ../../en/refs/module.commu.ref:6 4acbcfbd33d64c6c8f3010cd97a77dbd +msgid "init.png" +msgstr "" + +#: 280f822312264511acd49886ab12b058 module.commu.CommuModuleCAN.any:1 of +msgid "Check if any message is available." +msgstr "" + +#: 0289ed9b04a14c00ac3b4cd8b5513f64 97f5fa29d9df417caebc41d7c33e66c5 +#: c68e694b411349d88c15d2131def6b75 d8d8225eeea348799ab031f87856b087 +#: module.commu.CommuModuleCAN.any module.commu.CommuModuleCAN.info +#: module.commu.CommuModuleCAN.recv module.commu.CommuModuleCAN.send of +msgid "Returns" +msgstr "" + +#: 718065d6c3ce45f0bb3b65ee6b367007 module.commu.CommuModuleCAN.any:3 of +msgid "The current message availability." +msgstr "检查是否有消息可用。" + +#: c6bf7505612749b89b80f1a9adc616f4 d359b1172aa24592a8eebfaf716f5097 +#: dd719c06e84b4ac3b59769257a6659a3 fa08b6dad89e4cdd84bd23db678b5df8 +#: module.commu.CommuModuleCAN.any module.commu.CommuModuleCAN.info +#: module.commu.CommuModuleCAN.recv module.commu.CommuModuleCAN.send of +msgid "Return type" +msgstr "返回类型" + +#: 496b28eaba644df99f220cb02daf5303 module.commu.CommuModuleCAN.any:8 of +msgid "|any.png|" +msgstr "" + +#: ../../en/refs/module.commu.ref:9 f35fcc65fd1f41a18cfc595ac62530f3 +msgid "any.png" +msgstr "" + +#: 00e6e64719b74a5cb77ac3503368ad2f module.commu.CommuModuleCAN.info:1 of +msgid "Get the state of error information." +msgstr "获取错误信息。" + +#: 96f5eace42d642b5862c7e817fe4a1ab module.commu.CommuModuleCAN.info:3 of +msgid "The current error information." +msgstr "当前的错误信息。" + +#: f11f679a0c154b25aab1a1f79b3ef1c8 module.commu.CommuModuleCAN.info:8 of +msgid "|info.png|" +msgstr "" + +#: ../../en/refs/module.commu.ref:7 71a2a66283c743f79dd2d7bbcd14f8cf +msgid "info.png" +msgstr "" + +#: 746033cfe8df4e75bcaf4d59834058c9 module.commu.CommuModuleCAN.recv:1 of +msgid "Read a message from the CAN bus." +msgstr "从 CAN 总线读取消息。" + +#: fb9a38255a8741f1b56339198d8ec601 module.commu.CommuModuleCAN.recv:3 of +msgid "The fifo is an integer, it can be any number and compatible with Pyb.CAN" +msgstr "fifo 是一个整数,可以是任何数字,并与 Pyb.CAN 兼容。" + +#: 0aa266944aa340e6895c0d64e309d17d module.commu.CommuModuleCAN.recv:4 of +msgid "list is an optional list object to be used as the return value." +msgstr "list 是一个可选的列表对象,将用作返回值。" + +#: c22e903061e44881916c2fbf8dce4382 module.commu.CommuModuleCAN.recv:5 of +msgid "timeout is the timeout in milliseconds to wait for the receive." +msgstr "timeout 是等待接收的超时时间(毫秒)。" + +#: b8847250c2b840baa6d3c9ac837dc97e module.commu.CommuModuleCAN.recv:6 of +msgid "Tuple containing (can_id, is_extended, is_rtr, fmi, data)" +msgstr "包含(can_id、is_extended、is_rtr、fmi、data)的元组" + +#: 261b7f1b6d9e4da3940cb3ba95957557 module.commu.CommuModuleCAN.recv:7 of +msgid "" +"tuple - The id of the message. - A boolean that indicates if the message" +" ID is standard or extended. - A boolean that indicates if the message is" +" an RTR message. - The FMI (Filter Match Index) value. - An array " +"containing the data." +msgstr "" +"tuple \n" +" - 信息的 ID。 - 表示报文 ID 是标准还是扩展的布尔值。 - 表示报文是否为 RTR 报文的布尔值。 - 过滤匹配索引(FMI)值。 -" +" 包含数据的数组。" + +#: ae9082a8892c49a4919ffa77e2195759 module.commu.CommuModuleCAN.recv:7 of +msgid "tuple" +msgstr "" + +#: 74a72abea34f47e392d58004a25e1041 module.commu.CommuModuleCAN.recv:9 of +msgid "The id of the message." +msgstr "消息数据。" + +#: 06b3f2d47a684fc78fdeb0d779f5c8b4 module.commu.CommuModuleCAN.recv:10 of +msgid "A boolean that indicates if the message ID is standard or extended." +msgstr "布尔值,表示信息 ID 是标准的还是扩展的。" + +#: 9ec3900c3cea4621bb7b783de08a0837 module.commu.CommuModuleCAN.recv:11 of +msgid "A boolean that indicates if the message is an RTR message." +msgstr "布尔值,用于指示报文是否为 RTR 报文。" + +#: 5e82c9e12b7549758d01a189df91cf90 module.commu.CommuModuleCAN.recv:12 of +msgid "The FMI (Filter Match Index) value." +msgstr "FMI(筛选匹配指数)值。" + +#: 30a2821a5ea44194a42df608ed724d58 module.commu.CommuModuleCAN.recv:13 of +msgid "An array containing the data." +msgstr "包含数据的数组。" + +#: 670d0b83692c4cf9815baf5480b54326 module.commu.CommuModuleCAN.recv:17 of +msgid "|recv_message.png|" +msgstr "" + +#: ../../en/refs/module.commu.ref:10 13e6fecc989e4b95afa502172abee049 +msgid "recv_message.png" +msgstr "" + +#: 86f2d655b42e4dd9ba5a53ffc6618df1 module.commu.CommuModuleCAN.recv:19 of +msgid "|recv_message_into.png|" +msgstr "" + +#: ../../en/refs/module.commu.ref:11 5ddabf7fe6dd4d26b0193c4d1be28138 +msgid "recv_message_into.png" +msgstr "" + +#: c44784472ac64754855d2d20d3af6975 module.commu.CommuModuleCAN.send:1 of +msgid "Send a message to the CAN bus." +msgstr "发送消息到 CAN 总线。" + +#: 97f7c421f7b2450aa16349ff2d3e8e86 c1d5f630c5ee4bbeacc7ffa7046f1989 +#: module.commu.CommuModuleCAN.send:3 module.commu.CommuModuleCAN.send:6 of +msgid "The message data." +msgstr "消息数据。" + +#: b1fb69484dc6427f8e3a5dd979b75a0f module.commu.CommuModuleCAN.send:4 of +msgid "The CAN ID." +msgstr "" + +#: 9a1649c6027546d1b4486df4521e2d38 module.commu.CommuModuleCAN.send:5 of +msgid "Whether to use extended frame format." +msgstr "是否使用扩展帧格式。" + +#: ca9d5edeb2834b89920eb67639a6c81f module.commu.CommuModuleCAN.send:11 of +msgid "|send.png|" +msgstr "" + +#: ../../en/refs/module.commu.ref:12 950143b2e6e3486d944891456951e38c +msgid "send.png" +msgstr "" + +#: 35972e31cc824791b6c764bcc1ce6273 b22774a70d9c4b0ba554a0f12279ff38 +#: module.commu.CommuModuleI2C:1 module.commu.CommuModuleRS485:1 of +msgid "Bases: :py:class:`object`" +msgstr "" + +#: ../../en/module/commu.rst:63 46dca2360a6a4acca1e25e4c9b0e1c1e +msgid "The `CommuModuleRS485` class wraps an instance of the `UART` class." +msgstr "`CommuModuleRS485` 类包装了一个 `UART` 类的实例。" + +#: ../../en/module/commu.rst:65 a32afb7d3fca49d7aa3cdf2b894012ba +msgid "For more details, see :ref:`hardware.UART `." +msgstr "更多详情,请参阅 :ref:`hardware.UART `。" + +#: ../../en/module/commu.rst:70 17074b48be164416b2859b27ade1ae31 +msgid "The `CommuModuleI2C` class wraps an instance of the `I2C` class." +msgstr "`CommuModuleI2C` 类包装了一个 `I2C` 类的实例。" + +#: ../../en/module/commu.rst:72 563a7c1c944a4cadb75626ba07361f9b +msgid "" +"For more details, see :ref:`machine.I2C `. -- a two-wire " +"serial protocol." +msgstr "更多详情,请参阅 :ref:`machine.I2C `. -- 两线串行协议。" diff --git a/examples/module/commu/commu_core2_example.m5f2 b/examples/module/commu/commu_core2_example.m5f2 new file mode 100644 index 00000000..747bfb2a --- /dev/null +++ b/examples/module/commu/commu_core2_example.m5f2 @@ -0,0 +1 @@ +{"version":"V2.0","versionNumber":"V2.2.4","type":"core2","components":[{"name":"screen","type":"screen","layer":0,"screenId":"builtin","screenName":"","id":"__core2_screen","createTime":1742177184505,"x":0,"y":0,"width":320,"height":240,"backgroundColor":"#222222","size":0,"isSelected":true},{"name":"title0","type":"title","layer":1,"screenId":"builtin","screenName":"","id":"vy%MYibn*ksknQ3D","createTime":1742182523187,"x":0,"y":0,"color":"#ffffff","backgroundColor":"#0000FF","text":"COMMUModule Core2 Example","textOffset":3,"font":"Widgets.FONTS.DejaVu18","isSelected":false},{"name":"label0","type":"label","layer":2,"screenId":"builtin","screenName":"","id":"avGcZucKeHz%uIkO","createTime":1742182526023,"x":1,"y":77,"color":"#ffffff","backgroundColor":"#222222","text":"CAN Rec:","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false},{"name":"label1","type":"label","layer":3,"screenId":"builtin","screenName":"","id":"cExRl4nbr*Rz82h9","createTime":1742182534468,"x":1,"y":121,"color":"#ffffff","backgroundColor":"#222222","text":"RS485 Rec:","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false},{"name":"label2","type":"label","layer":4,"screenId":"builtin","screenName":"","id":"e8N6c-8ljfowrRIU","createTime":1742182536373,"x":1,"y":166,"color":"#ffffff","backgroundColor":"#222222","text":"I2C List:","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false}],"resources":[{"hardware":["hardware_button","hardware_pin_button","imu","i2c","speaker","touch","mic"]},{"module":["module_commu"]}],"units":[],"hats":[],"bases":[],"i2cs":[{"id":"i2c0","portType":"A","userPort":[22,21],"freq":"100000"}],"blockly":"truecommu_0can0x0016commu_1rs485False21152008None11413commu_2i2c02221100000truecommu_0label0LabelCAN Rec:commu_0100BtnAcommu_0uiflow20FalseBtnBlabel2LabelI2C List:commu_2commu_1label1LabelRS485 Rec:commu_1","screen":[{"simulationName":"Built-in","type":"builtin","width":320,"height":240,"scale":0.78,"screenName":"","blockId":"","screenColorType":0,"id":"builtin","createTime":1742177184503}],"logicWhenNum":0,"customList":[]} \ No newline at end of file diff --git a/examples/module/commu/commu_core2_example.py b/examples/module/commu/commu_core2_example.py new file mode 100644 index 00000000..3007d3b4 --- /dev/null +++ b/examples/module/commu/commu_core2_example.py @@ -0,0 +1,64 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import os, sys, io +import M5 +from M5 import * +from module import CommuModuleCAN +from module import CommuModuleRS485 +from module import CommuModuleI2C +from hardware import Pin + + +title0 = None +label0 = None +label1 = None +label2 = None +commu_0 = None +commu_1 = None +commu_2 = None + + +def setup(): + global title0, label0, label1, label2, commu_0, commu_1, commu_2 + + M5.begin() + Widgets.fillScreen(0x222222) + title0 = Widgets.Title( + "COMMUModule Core2 Example", 3, 0xFFFFFF, 0x0000FF, Widgets.FONTS.DejaVu18 + ) + label0 = Widgets.Label("CAN Rec:", 1, 77, 1.0, 0xFFFFFF, 0x222222, Widgets.FONTS.DejaVu18) + label1 = Widgets.Label("RS485 Rec:", 1, 121, 1.0, 0xFFFFFF, 0x222222, Widgets.FONTS.DejaVu18) + label2 = Widgets.Label("I2C List:", 1, 166, 1.0, 0xFFFFFF, 0x222222, Widgets.FONTS.DejaVu18) + + commu_0 = CommuModuleCAN(0x00, baudrate=16) + commu_1 = CommuModuleRS485(2, baudrate=115200, bits=8, parity=None, stop=1, tx=14, rx=13) + commu_2 = CommuModuleI2C(0, scl=Pin(22), sda=Pin(21), freq=100000) + + +def loop(): + global title0, label0, label1, label2, commu_0, commu_1, commu_2 + M5.update() + if commu_0.any(): + label0.setText(str((str("CAN Rec:") + str((commu_0.recv()))))) + if BtnA.isPressed(): + commu_0.send("uiflow2", 0, extframe=False) + elif BtnB.isPressed(): + label2.setText(str((str("I2C List:") + str((commu_2.scan()))))) + if commu_1.any(): + label1.setText(str((str("RS485 Rec:") + str((commu_1.read()))))) + + +if __name__ == "__main__": + try: + setup() + while True: + loop() + except (Exception, KeyboardInterrupt) as e: + try: + from utility import print_error_msg + + print_error_msg(e) + except ImportError: + print("please update to latest firmware") diff --git a/m5stack/libs/driver/manifest.py b/m5stack/libs/driver/manifest.py index 80d3836a..b5d9da4e 100644 --- a/m5stack/libs/driver/manifest.py +++ b/m5stack/libs/driver/manifest.py @@ -20,11 +20,18 @@ "ir/receiver.py", "ir/transmitter.py", "jrd4035/__init__.py", + "mcp2515/__init__.py", + "mcp2515/mcp2515_spi.py", + "mcp2515/mcp2515_param.py", + "mcp2515/can_frame.py", "mfrc522/__init__.py", "mfrc522/cmd.py", "mfrc522/firmware.py", "mfrc522/reg.py", - "max3421e.py", + "modbus/master/__init__.py", + "modbus/master/uConst.py", + "modbus/master/uFunctions.py", + "modbus/master/uSerial.py", "neopixel/__init__.py", "neopixel/sk6812.py", "neopixel/ws2812.py", @@ -54,6 +61,7 @@ "drf1609h.py", "haptic.py", "ina226.py", + "max3421e.py", "mcp4725.py", "mlx90614.py", "pca9554.py", @@ -74,10 +82,6 @@ "tcs3472.py", "timer_thread.py", "vl53l0x.py", - "modbus/master/__init__.py", - "modbus/master/uConst.py", - "modbus/master/uFunctions.py", - "modbus/master/uSerial.py", "paj7620.py", "mlx90640.py", "mpu6886.py", diff --git a/m5stack/libs/driver/mcp2515/__init__.py b/m5stack/libs/driver/mcp2515/__init__.py new file mode 100644 index 00000000..e46cb4b2 --- /dev/null +++ b/m5stack/libs/driver/mcp2515/__init__.py @@ -0,0 +1,3 @@ +# SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT diff --git a/m5stack/libs/driver/mcp2515/can_frame.py b/m5stack/libs/driver/mcp2515/can_frame.py new file mode 100644 index 00000000..cc573ff5 --- /dev/null +++ b/m5stack/libs/driver/mcp2515/can_frame.py @@ -0,0 +1,94 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +# Special address description flags for the CAN_ID +CAN_EFF_FLAG = 0x80000000 # EFF/SFF is set in the MSB +CAN_RTR_FLAG = 0x40000000 # remote transmission request +CAN_ERR_FLAG = 0x20000000 # error message frame + +# Valid bits in CAN ID for frame formats +CAN_SFF_MASK = 0x000007FF # standard frame format (SFF) +CAN_EFF_MASK = 0x1FFFFFFF # extended frame format (EFF) +CAN_ERR_MASK = 0x1FFFFFFF # omit EFF, RTR, ERR flags + +CAN_SFF_ID_BITS = 11 +CAN_EFF_ID_BITS = 29 + +# CAN payload length and DLC definitions according to ISO 11898-1 +CAN_MAX_DLC = 8 +CAN_MAX_DLEN = 8 + +# CAN ID length +CAN_IDLEN = 4 + + +class CANFrame: + def __init__(self, can_id: int, data: bytes = b"") -> None: + # + # Controller Area Network Identifier structure + # + # bit 0-28 : CAN identifier (11/29 bit) + # bit 29 : error message frame flag (0 = data frame, 1 = error message) + # bit 30 : remote transmission request flag (1 = rtr frame) + # bit 31 : frame format flag (0 = standard 11 bit, 1 = extended 29 bit) + # + # 32 bit CAN ID + EFF/RTR/ERR flags + # + self.can_id = can_id # type: int + self.data = data # type: bytes + + @property + def can_id(self) -> int: + return self._can_id + + @can_id.setter + def can_id(self, can_id: int) -> None: + self._can_id = can_id + self._arbitration_id = can_id & CAN_EFF_MASK # type: int + + @property + def data(self) -> bytes: + return self._data + + @data.setter + def data(self, data: bytes) -> None: + self._data = b"" # type: bytes + self._dlc = 0 # frame payload length in byte (0 .. CAN_MAX_DLEN) + + if not data: + return + + if len(data) > CAN_MAX_DLEN: + raise Exception("The CAN frame data length exceeds the maximum") + + self._data = data + self._dlc = len(data) + + @property + def arbitration_id(self) -> int: + return self._arbitration_id + + @property + def dlc(self) -> int: + return self._dlc + + @property + def is_extended_id(self) -> bool: + return bool(self._can_id & CAN_EFF_FLAG) + + @property + def is_remote_frame(self) -> bool: + return bool(self._can_id & CAN_RTR_FLAG) + + @property + def is_error_frame(self) -> bool: + return bool(self._can_id & CAN_ERR_FLAG) + + def __str__(self) -> str: + data = ( + "remote request" + if self.is_remote_frame + else " ".join("{:02X}".format(b) for b in self.data) + ) + return "{: >8X} [{}] {}".format(self.arbitration_id, self.dlc, data) diff --git a/m5stack/libs/driver/mcp2515/mcp2515_param.py b/m5stack/libs/driver/mcp2515/mcp2515_param.py new file mode 100644 index 00000000..3e5568d0 --- /dev/null +++ b/m5stack/libs/driver/mcp2515/mcp2515_param.py @@ -0,0 +1,642 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +SPI_DUMMY_INT = 0x00 +SPI_TRANSFER_LEN = 1 +SPI_HOLD_US = 50 + +# +# speed 8M +# +MCP_8MHz_1000kBPS_CFG1 = 0x00 +MCP_8MHz_1000kBPS_CFG2 = 0x80 +MCP_8MHz_1000kBPS_CFG3 = 0x80 + +MCP_8MHz_500kBPS_CFG1 = 0x00 +MCP_8MHz_500kBPS_CFG2 = 0x90 +MCP_8MHz_500kBPS_CFG3 = 0x82 + +MCP_8MHz_250kBPS_CFG1 = 0x00 +MCP_8MHz_250kBPS_CFG2 = 0xB1 +MCP_8MHz_250kBPS_CFG3 = 0x85 + +MCP_8MHz_200kBPS_CFG1 = 0x00 +MCP_8MHz_200kBPS_CFG2 = 0xB4 +MCP_8MHz_200kBPS_CFG3 = 0x86 + +MCP_8MHz_125kBPS_CFG1 = 0x01 +MCP_8MHz_125kBPS_CFG2 = 0xB1 +MCP_8MHz_125kBPS_CFG3 = 0x85 + +MCP_8MHz_100kBPS_CFG1 = 0x01 +MCP_8MHz_100kBPS_CFG2 = 0xB4 +MCP_8MHz_100kBPS_CFG3 = 0x86 + +MCP_8MHz_80kBPS_CFG1 = 0x01 +MCP_8MHz_80kBPS_CFG2 = 0xBF +MCP_8MHz_80kBPS_CFG3 = 0x87 + +MCP_8MHz_50kBPS_CFG1 = 0x03 +MCP_8MHz_50kBPS_CFG2 = 0xB4 +MCP_8MHz_50kBPS_CFG3 = 0x86 + +MCP_8MHz_40kBPS_CFG1 = 0x03 +MCP_8MHz_40kBPS_CFG2 = 0xBF +MCP_8MHz_40kBPS_CFG3 = 0x87 + +MCP_8MHz_33k3BPS_CFG1 = 0x47 +MCP_8MHz_33k3BPS_CFG2 = 0xE2 +MCP_8MHz_33k3BPS_CFG3 = 0x85 + +MCP_8MHz_31k25BPS_CFG1 = 0x07 +MCP_8MHz_31k25BPS_CFG2 = 0xA4 +MCP_8MHz_31k25BPS_CFG3 = 0x84 + +MCP_8MHz_20kBPS_CFG1 = 0x07 +MCP_8MHz_20kBPS_CFG2 = 0xBF +MCP_8MHz_20kBPS_CFG3 = 0x87 + +MCP_8MHz_10kBPS_CFG1 = 0x0F +MCP_8MHz_10kBPS_CFG2 = 0xBF +MCP_8MHz_10kBPS_CFG3 = 0x87 + +MCP_8MHz_5kBPS_CFG1 = 0x1F +MCP_8MHz_5kBPS_CFG2 = 0xBF +MCP_8MHz_5kBPS_CFG3 = 0x87 + +# +# speed 16M +# +MCP_16MHz_1000kBPS_CFG1 = 0x00 +MCP_16MHz_1000kBPS_CFG2 = 0xD0 +MCP_16MHz_1000kBPS_CFG3 = 0x82 + +MCP_16MHz_500kBPS_CFG1 = 0x00 +MCP_16MHz_500kBPS_CFG2 = 0xF0 +MCP_16MHz_500kBPS_CFG3 = 0x86 + +MCP_16MHz_250kBPS_CFG1 = 0x41 +MCP_16MHz_250kBPS_CFG2 = 0xF1 +MCP_16MHz_250kBPS_CFG3 = 0x85 + +MCP_16MHz_200kBPS_CFG1 = 0x01 +MCP_16MHz_200kBPS_CFG2 = 0xFA +MCP_16MHz_200kBPS_CFG3 = 0x87 + +MCP_16MHz_125kBPS_CFG1 = 0x03 +MCP_16MHz_125kBPS_CFG2 = 0xF0 +MCP_16MHz_125kBPS_CFG3 = 0x86 + +MCP_16MHz_100kBPS_CFG1 = 0x03 +MCP_16MHz_100kBPS_CFG2 = 0xFA +MCP_16MHz_100kBPS_CFG3 = 0x87 + +MCP_16MHz_80kBPS_CFG1 = 0x03 +MCP_16MHz_80kBPS_CFG2 = 0xFF +MCP_16MHz_80kBPS_CFG3 = 0x87 + +MCP_16MHz_83k3BPS_CFG1 = 0x03 +MCP_16MHz_83k3BPS_CFG2 = 0xBE +MCP_16MHz_83k3BPS_CFG3 = 0x07 + +MCP_16MHz_50kBPS_CFG1 = 0x07 +MCP_16MHz_50kBPS_CFG2 = 0xFA +MCP_16MHz_50kBPS_CFG3 = 0x87 + +MCP_16MHz_40kBPS_CFG1 = 0x07 +MCP_16MHz_40kBPS_CFG2 = 0xFF +MCP_16MHz_40kBPS_CFG3 = 0x87 + +MCP_16MHz_33k3BPS_CFG1 = 0x4E +MCP_16MHz_33k3BPS_CFG2 = 0xF1 +MCP_16MHz_33k3BPS_CFG3 = 0x85 + +MCP_16MHz_20kBPS_CFG1 = 0x0F +MCP_16MHz_20kBPS_CFG2 = 0xFF +MCP_16MHz_20kBPS_CFG3 = 0x87 + +MCP_16MHz_10kBPS_CFG1 = 0x1F +MCP_16MHz_10kBPS_CFG2 = 0xFF +MCP_16MHz_10kBPS_CFG3 = 0x87 + +MCP_16MHz_5kBPS_CFG1 = 0x3F +MCP_16MHz_5kBPS_CFG2 = 0xFF +MCP_16MHz_5kBPS_CFG3 = 0x87 + +# +# speed 20M +# +MCP_20MHz_1000kBPS_CFG1 = 0x00 +MCP_20MHz_1000kBPS_CFG2 = 0xD9 +MCP_20MHz_1000kBPS_CFG3 = 0x82 + +MCP_20MHz_500kBPS_CFG1 = 0x00 +MCP_20MHz_500kBPS_CFG2 = 0xFA +MCP_20MHz_500kBPS_CFG3 = 0x87 + +MCP_20MHz_250kBPS_CFG1 = 0x41 +MCP_20MHz_250kBPS_CFG2 = 0xFB +MCP_20MHz_250kBPS_CFG3 = 0x86 + +MCP_20MHz_200kBPS_CFG1 = 0x01 +MCP_20MHz_200kBPS_CFG2 = 0xFF +MCP_20MHz_200kBPS_CFG3 = 0x87 + +MCP_20MHz_125kBPS_CFG1 = 0x03 +MCP_20MHz_125kBPS_CFG2 = 0xFA +MCP_20MHz_125kBPS_CFG3 = 0x87 + +MCP_20MHz_100kBPS_CFG1 = 0x04 +MCP_20MHz_100kBPS_CFG2 = 0xFA +MCP_20MHz_100kBPS_CFG3 = 0x87 + +MCP_20MHz_83k3BPS_CFG1 = 0x04 +MCP_20MHz_83k3BPS_CFG2 = 0xFE +MCP_20MHz_83k3BPS_CFG3 = 0x87 + +MCP_20MHz_80kBPS_CFG1 = 0x04 +MCP_20MHz_80kBPS_CFG2 = 0xFF +MCP_20MHz_80kBPS_CFG3 = 0x87 + +MCP_20MHz_50kBPS_CFG1 = 0x09 +MCP_20MHz_50kBPS_CFG2 = 0xFA +MCP_20MHz_50kBPS_CFG3 = 0x87 + +MCP_20MHz_40kBPS_CFG1 = 0x09 +MCP_20MHz_40kBPS_CFG2 = 0xFF +MCP_20MHz_40kBPS_CFG3 = 0x87 + +MCP_20MHz_33k3BPS_CFG1 = 0x0B +MCP_20MHz_33k3BPS_CFG2 = 0xFF +MCP_20MHz_33k3BPS_CFG3 = 0x87 + + +class CAN_CLOCK: + MCP_20MHZ = 1 + MCP_16MHZ = 2 + MCP_8MHZ = 3 + + +class CAN_SPEED: + CAN_5KBPS = 1 + CAN_10KBPS = 2 + CAN_20KBPS = 3 + CAN_31K25BPS = 4 + CAN_33KBPS = 5 + CAN_40KBPS = 6 + CAN_50KBPS = 7 + CAN_80KBPS = 8 + CAN_83K3BPS = 9 + CAN_95KBPS = 10 + CAN_100KBPS = 11 + CAN_125KBPS = 12 + CAN_200KBPS = 13 + CAN_250KBPS = 14 + CAN_500KBPS = 15 + CAN_1000KBPS = 16 + + +class CAN_CLKOUT: + CLKOUT_DISABLE = -1 + CLKOUT_DIV1 = 0x0 + CLKOUT_DIV2 = 0x1 + CLKOUT_DIV4 = 0x2 + CLKOUT_DIV8 = 0x3 + + +class ERROR: + ERROR_OK = 0 + ERROR_FAIL = 1 + ERROR_ALLTXBUSY = 2 + ERROR_FAILINIT = 3 + ERROR_FAILTX = 4 + ERROR_NOMSG = 5 + + +class MASK: + MASK0 = 1 + MASK1 = 2 + + +class RXF: + RXF0 = 0 + RXF1 = 1 + RXF2 = 2 + RXF3 = 3 + RXF4 = 4 + RXF5 = 5 + + +class RXBn: + RXB0 = 0 + RXB1 = 1 + + +class TXBn: + TXB0 = 0 + TXB1 = 1 + TXB2 = 2 + + +class CANINTF: + CANINTF_RX0IF = 0x01 + CANINTF_RX1IF = 0x02 + CANINTF_TX0IF = 0x04 + CANINTF_TX1IF = 0x08 + CANINTF_TX2IF = 0x10 + CANINTF_ERRIF = 0x20 + CANINTF_WAKIF = 0x40 + CANINTF_MERRF = 0x80 + + +class EFLG: + EFLG_RX1OVR = 1 << 7 + EFLG_RX0OVR = 1 << 6 + EFLG_TXBO = 1 << 5 + EFLG_TXEP = 1 << 4 + EFLG_RXEP = 1 << 3 + EFLG_TXWAR = 1 << 2 + EFLG_RXWAR = 1 << 1 + EFLG_EWARN = 1 << 0 + + +CANCTRL_REQOP = 0xE0 +CANCTRL_ABAT = 0x10 +CANCTRL_OSM = 0x08 +CANCTRL_CLKEN = 0x04 +CANCTRL_CLKPRE = 0x03 + + +class CANCTRL_REQOP_MODE: + CANCTRL_REQOP_NORMAL = 0x00 + CANCTRL_REQOP_SLEEP = 0x20 + CANCTRL_REQOP_LOOPBACK = 0x40 + CANCTRL_REQOP_LISTENONLY = 0x60 + CANCTRL_REQOP_CONFIG = 0x80 + CANCTRL_REQOP_POWERUP = 0xE0 + + +CANSTAT_OPMOD = 0xE0 +CANSTAT_ICOD = 0x0E + +CNF3_SOF = 0x80 + +TXB_EXIDE_MASK = 0x08 +DLC_MASK = 0x0F +RTR_MASK = 0x40 + +RXBnCTRL_RXM_ANY = 0x60 +RXBnCTRL_RXM_EXT = 0x40 +RXBnCTRL_RXM_STD = 0x20 +RXBnCTRL_RXM_STDEXT = 0x00 +RXBnCTRL_RXM_MASK = 0x60 +RXBnCTRL_RTR = 0x08 +RXB0CTRL_BUKT = 0x04 +RXB0CTRL_FILHIT_MASK = 0x03 +RXB1CTRL_FILHIT_MASK = 0x07 +RXB0CTRL_FILHIT = 0x00 +RXB1CTRL_FILHIT = 0x01 + +MCP_SIDH = 0 +MCP_SIDL = 1 +MCP_EID8 = 2 +MCP_EID0 = 3 +MCP_DLC = 4 +MCP_DATA = 5 + + +class STAT: + STAT_RX0IF = 1 << 0 + STAT_RX1IF = 1 << 1 + + +STAT_RXIF_MASK = STAT.STAT_RX0IF | STAT.STAT_RX1IF + + +class TXBnCTRL: + TXB_ABTF = 0x40 + TXB_MLOA = 0x20 + TXB_TXERR = 0x10 + TXB_TXREQ = 0x08 + TXB_TXIE = 0x04 + TXB_TXP = 0x03 + + +BxBFS_MASK = 0x30 +BxBFE_MASK = 0x0C +BxBFM_MASK = 0x03 + + +EFLG_ERRORMASK = ( + EFLG.EFLG_RX1OVR | EFLG.EFLG_RX0OVR | EFLG.EFLG_TXBO | EFLG.EFLG_TXEP | EFLG.EFLG_RXEP +) + + +class INSTRUCTION: + INSTRUCTION_WRITE = 0x02 + INSTRUCTION_READ = 0x03 + INSTRUCTION_BITMOD = 0x05 + INSTRUCTION_LOAD_TX0 = 0x40 + INSTRUCTION_LOAD_TX1 = 0x42 + INSTRUCTION_LOAD_TX2 = 0x44 + INSTRUCTION_RTS_TX0 = 0x81 + INSTRUCTION_RTS_TX1 = 0x82 + INSTRUCTION_RTS_TX2 = 0x84 + INSTRUCTION_RTS_ALL = 0x87 + INSTRUCTION_READ_RX0 = 0x90 + INSTRUCTION_READ_RX1 = 0x94 + INSTRUCTION_READ_STATUS = 0xA0 + INSTRUCTION_RX_STATUS = 0xB0 + INSTRUCTION_RESET = 0xC0 + + +class REGISTER: + MCP_RXF0SIDH = 0x00 + MCP_RXF0SIDL = 0x01 + MCP_RXF0EID8 = 0x02 + MCP_RXF0EID0 = 0x03 + MCP_RXF1SIDH = 0x04 + MCP_RXF1SIDL = 0x05 + MCP_RXF1EID8 = 0x06 + MCP_RXF1EID0 = 0x07 + MCP_RXF2SIDH = 0x08 + MCP_RXF2SIDL = 0x09 + MCP_RXF2EID8 = 0x0A + MCP_RXF2EID0 = 0x0B + MCP_BFPCTRL = 0x0C + MCP_TXRTSCTRL = 0x0D + MCP_CANSTAT = 0x0E + MCP_CANCTRL = 0x0F + MCP_RXF3SIDH = 0x10 + MCP_RXF3SIDL = 0x11 + MCP_RXF3EID8 = 0x12 + MCP_RXF3EID0 = 0x13 + MCP_RXF4SIDH = 0x14 + MCP_RXF4SIDL = 0x15 + MCP_RXF4EID8 = 0x16 + MCP_RXF4EID0 = 0x17 + MCP_RXF5SIDH = 0x18 + MCP_RXF5SIDL = 0x19 + MCP_RXF5EID8 = 0x1A + MCP_RXF5EID0 = 0x1B + MCP_TEC = 0x1C + MCP_REC = 0x1D + MCP_RXM0SIDH = 0x20 + MCP_RXM0SIDL = 0x21 + MCP_RXM0EID8 = 0x22 + MCP_RXM0EID0 = 0x23 + MCP_RXM1SIDH = 0x24 + MCP_RXM1SIDL = 0x25 + MCP_RXM1EID8 = 0x26 + MCP_RXM1EID0 = 0x27 + MCP_CNF3 = 0x28 + MCP_CNF2 = 0x29 + MCP_CNF1 = 0x2A + MCP_CANINTE = 0x2B + MCP_CANINTF = 0x2C + MCP_EFLG = 0x2D + MCP_TXB0CTRL = 0x30 + MCP_TXB0SIDH = 0x31 + MCP_TXB0SIDL = 0x32 + MCP_TXB0EID8 = 0x33 + MCP_TXB0EID0 = 0x34 + MCP_TXB0DLC = 0x35 + MCP_TXB0DATA = 0x36 + MCP_TXB1CTRL = 0x40 + MCP_TXB1SIDH = 0x41 + MCP_TXB1SIDL = 0x42 + MCP_TXB1EID8 = 0x43 + MCP_TXB1EID0 = 0x44 + MCP_TXB1DLC = 0x45 + MCP_TXB1DATA = 0x46 + MCP_TXB2CTRL = 0x50 + MCP_TXB2SIDH = 0x51 + MCP_TXB2SIDL = 0x52 + MCP_TXB2EID8 = 0x53 + MCP_TXB2EID0 = 0x54 + MCP_TXB2DLC = 0x55 + MCP_TXB2DATA = 0x56 + MCP_RXB0CTRL = 0x60 + MCP_RXB0SIDH = 0x61 + MCP_RXB0SIDL = 0x62 + MCP_RXB0EID8 = 0x63 + MCP_RXB0EID0 = 0x64 + MCP_RXB0DLC = 0x65 + MCP_RXB0DATA = 0x66 + MCP_RXB1CTRL = 0x70 + MCP_RXB1SIDH = 0x71 + MCP_RXB1SIDL = 0x72 + MCP_RXB1EID8 = 0x73 + MCP_RXB1EID0 = 0x74 + MCP_RXB1DLC = 0x75 + MCP_RXB1DATA = 0x76 + + +N_TXBUFFERS = 3 +N_RXBUFFERS = 2 + + +CAN_CFGS = { + CAN_CLOCK.MCP_8MHZ: { + CAN_SPEED.CAN_5KBPS: [ + MCP_8MHz_5kBPS_CFG1, + MCP_8MHz_5kBPS_CFG2, + MCP_8MHz_5kBPS_CFG3, + ], + CAN_SPEED.CAN_10KBPS: [ + MCP_8MHz_10kBPS_CFG1, + MCP_8MHz_10kBPS_CFG2, + MCP_8MHz_10kBPS_CFG3, + ], + CAN_SPEED.CAN_20KBPS: [ + MCP_8MHz_20kBPS_CFG1, + MCP_8MHz_20kBPS_CFG2, + MCP_8MHz_20kBPS_CFG3, + ], + CAN_SPEED.CAN_31K25BPS: [ + MCP_8MHz_31k25BPS_CFG1, + MCP_8MHz_31k25BPS_CFG2, + MCP_8MHz_31k25BPS_CFG3, + ], + CAN_SPEED.CAN_33KBPS: [ + MCP_8MHz_33k3BPS_CFG1, + MCP_8MHz_33k3BPS_CFG2, + MCP_8MHz_33k3BPS_CFG3, + ], + CAN_SPEED.CAN_40KBPS: [ + MCP_8MHz_40kBPS_CFG1, + MCP_8MHz_40kBPS_CFG2, + MCP_8MHz_40kBPS_CFG3, + ], + CAN_SPEED.CAN_50KBPS: [ + MCP_8MHz_50kBPS_CFG1, + MCP_8MHz_50kBPS_CFG2, + MCP_8MHz_50kBPS_CFG3, + ], + CAN_SPEED.CAN_80KBPS: [ + MCP_8MHz_80kBPS_CFG1, + MCP_8MHz_80kBPS_CFG2, + MCP_8MHz_80kBPS_CFG3, + ], + CAN_SPEED.CAN_100KBPS: [ + MCP_8MHz_100kBPS_CFG1, + MCP_8MHz_100kBPS_CFG2, + MCP_8MHz_100kBPS_CFG3, + ], + CAN_SPEED.CAN_125KBPS: [ + MCP_8MHz_125kBPS_CFG1, + MCP_8MHz_125kBPS_CFG2, + MCP_8MHz_125kBPS_CFG3, + ], + CAN_SPEED.CAN_200KBPS: [ + MCP_8MHz_200kBPS_CFG1, + MCP_8MHz_200kBPS_CFG2, + MCP_8MHz_200kBPS_CFG3, + ], + CAN_SPEED.CAN_250KBPS: [ + MCP_8MHz_250kBPS_CFG1, + MCP_8MHz_250kBPS_CFG2, + MCP_8MHz_250kBPS_CFG3, + ], + CAN_SPEED.CAN_500KBPS: [ + MCP_8MHz_500kBPS_CFG1, + MCP_8MHz_500kBPS_CFG2, + MCP_8MHz_500kBPS_CFG3, + ], + CAN_SPEED.CAN_1000KBPS: [ + MCP_8MHz_1000kBPS_CFG1, + MCP_8MHz_1000kBPS_CFG2, + MCP_8MHz_1000kBPS_CFG3, + ], + }, + CAN_CLOCK.MCP_16MHZ: { + CAN_SPEED.CAN_5KBPS: [ + MCP_16MHz_5kBPS_CFG1, + MCP_16MHz_5kBPS_CFG2, + MCP_16MHz_5kBPS_CFG3, + ], + CAN_SPEED.CAN_10KBPS: [ + MCP_16MHz_10kBPS_CFG1, + MCP_16MHz_10kBPS_CFG2, + MCP_16MHz_10kBPS_CFG3, + ], + CAN_SPEED.CAN_20KBPS: [ + MCP_16MHz_20kBPS_CFG1, + MCP_16MHz_20kBPS_CFG2, + MCP_16MHz_20kBPS_CFG3, + ], + CAN_SPEED.CAN_33KBPS: [ + MCP_16MHz_33k3BPS_CFG1, + MCP_16MHz_33k3BPS_CFG2, + MCP_16MHz_33k3BPS_CFG3, + ], + CAN_SPEED.CAN_40KBPS: [ + MCP_16MHz_40kBPS_CFG1, + MCP_16MHz_40kBPS_CFG2, + MCP_16MHz_40kBPS_CFG3, + ], + CAN_SPEED.CAN_50KBPS: [ + MCP_16MHz_50kBPS_CFG1, + MCP_16MHz_50kBPS_CFG2, + MCP_16MHz_50kBPS_CFG3, + ], + CAN_SPEED.CAN_80KBPS: [ + MCP_16MHz_80kBPS_CFG1, + MCP_16MHz_80kBPS_CFG2, + MCP_16MHz_80kBPS_CFG3, + ], + CAN_SPEED.CAN_83K3BPS: [ + MCP_16MHz_83k3BPS_CFG1, + MCP_16MHz_83k3BPS_CFG2, + MCP_16MHz_83k3BPS_CFG3, + ], + CAN_SPEED.CAN_100KBPS: [ + MCP_16MHz_100kBPS_CFG1, + MCP_16MHz_100kBPS_CFG2, + MCP_16MHz_100kBPS_CFG3, + ], + CAN_SPEED.CAN_125KBPS: [ + MCP_16MHz_125kBPS_CFG1, + MCP_16MHz_125kBPS_CFG2, + MCP_16MHz_125kBPS_CFG3, + ], + CAN_SPEED.CAN_200KBPS: [ + MCP_16MHz_200kBPS_CFG1, + MCP_16MHz_200kBPS_CFG2, + MCP_16MHz_200kBPS_CFG3, + ], + CAN_SPEED.CAN_250KBPS: [ + MCP_16MHz_250kBPS_CFG1, + MCP_16MHz_250kBPS_CFG2, + MCP_16MHz_250kBPS_CFG3, + ], + CAN_SPEED.CAN_500KBPS: [ + MCP_16MHz_500kBPS_CFG1, + MCP_16MHz_500kBPS_CFG2, + MCP_16MHz_500kBPS_CFG3, + ], + CAN_SPEED.CAN_1000KBPS: [ + MCP_16MHz_1000kBPS_CFG1, + MCP_16MHz_1000kBPS_CFG2, + MCP_16MHz_1000kBPS_CFG3, + ], + }, + CAN_CLOCK.MCP_20MHZ: { + CAN_SPEED.CAN_33KBPS: [ + MCP_20MHz_33k3BPS_CFG1, + MCP_20MHz_33k3BPS_CFG2, + MCP_20MHz_33k3BPS_CFG3, + ], + CAN_SPEED.CAN_40KBPS: [ + MCP_20MHz_40kBPS_CFG1, + MCP_20MHz_40kBPS_CFG2, + MCP_20MHz_40kBPS_CFG3, + ], + CAN_SPEED.CAN_50KBPS: [ + MCP_20MHz_50kBPS_CFG1, + MCP_20MHz_50kBPS_CFG2, + MCP_20MHz_50kBPS_CFG3, + ], + CAN_SPEED.CAN_80KBPS: [ + MCP_20MHz_80kBPS_CFG1, + MCP_20MHz_80kBPS_CFG2, + MCP_20MHz_80kBPS_CFG3, + ], + CAN_SPEED.CAN_83K3BPS: [ + MCP_20MHz_83k3BPS_CFG1, + MCP_20MHz_83k3BPS_CFG2, + MCP_20MHz_83k3BPS_CFG3, + ], + CAN_SPEED.CAN_100KBPS: [ + MCP_20MHz_100kBPS_CFG1, + MCP_20MHz_100kBPS_CFG2, + MCP_20MHz_100kBPS_CFG3, + ], + CAN_SPEED.CAN_125KBPS: [ + MCP_20MHz_125kBPS_CFG1, + MCP_20MHz_125kBPS_CFG2, + MCP_20MHz_125kBPS_CFG3, + ], + CAN_SPEED.CAN_200KBPS: [ + MCP_20MHz_200kBPS_CFG1, + MCP_20MHz_200kBPS_CFG2, + MCP_20MHz_200kBPS_CFG3, + ], + CAN_SPEED.CAN_250KBPS: [ + MCP_20MHz_250kBPS_CFG1, + MCP_20MHz_250kBPS_CFG2, + MCP_20MHz_250kBPS_CFG3, + ], + CAN_SPEED.CAN_500KBPS: [ + MCP_20MHz_500kBPS_CFG1, + MCP_20MHz_500kBPS_CFG2, + MCP_20MHz_500kBPS_CFG3, + ], + CAN_SPEED.CAN_1000KBPS: [ + MCP_20MHz_1000kBPS_CFG1, + MCP_20MHz_1000kBPS_CFG2, + MCP_20MHz_1000kBPS_CFG3, + ], + }, +} diff --git a/m5stack/libs/driver/mcp2515/mcp2515_spi.py b/m5stack/libs/driver/mcp2515/mcp2515_spi.py new file mode 100644 index 00000000..20d19fd3 --- /dev/null +++ b/m5stack/libs/driver/mcp2515/mcp2515_spi.py @@ -0,0 +1,436 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import sys +import time +import collections + +from driver.mcp2515.mcp2515_param import * +from driver.mcp2515.can_frame import * + +from machine import Pin, SPI + +MCP_STDEXT = 0 # Standard and Extended */ +MCP_STD = 1 # Standard IDs ONLY */ +MCP_EXT = 2 # Extended IDs ONLY */ +MCP_ANY = 3 + + +TXBnREGS = collections.namedtuple("TXBnREGS", "CTRL SIDH DATA") +RXBnREGS = collections.namedtuple("RXBnREGS", "CTRL SIDH DATA CANINTFRXnIF") + + +TXB = [ + TXBnREGS(REGISTER.MCP_TXB0CTRL, REGISTER.MCP_TXB0SIDH, REGISTER.MCP_TXB0DATA), + TXBnREGS(REGISTER.MCP_TXB1CTRL, REGISTER.MCP_TXB1SIDH, REGISTER.MCP_TXB1DATA), + TXBnREGS(REGISTER.MCP_TXB2CTRL, REGISTER.MCP_TXB2SIDH, REGISTER.MCP_TXB2DATA), +] + +RXB = [ + RXBnREGS( + REGISTER.MCP_RXB0CTRL, + REGISTER.MCP_RXB0SIDH, + REGISTER.MCP_RXB0DATA, + CANINTF.CANINTF_RX0IF, + ), + RXBnREGS( + REGISTER.MCP_RXB1CTRL, + REGISTER.MCP_RXB1SIDH, + REGISTER.MCP_RXB1DATA, + CANINTF.CANINTF_RX1IF, + ), +] + +HSPI = 2 + + +class MCP2515_CAN: + def __init__(self): + pass + + def mcp2515_spi_init( + self, spi=HSPI, spi_baud=8000000, sclk=18, mosi=23, miso=19, cs=12, irq=15 + ) -> None: + print(spi) + self.hspi = SPI( + spi, + baudrate=spi_baud, + polarity=0, + phase=0, + bits=8, + firstbit=SPI.MSB, + sck=Pin(sclk), + mosi=Pin(mosi), + miso=Pin(miso), + ) + self.cs = Pin(cs, Pin.OUT) + self.cs.on() + self.irq = Pin(irq, Pin.IN) + self.mcp2515_rx_index = 0 + self.extframe = False + self.spi_start() + if self.mcp2515_set_config_mode() != ERROR.ERROR_OK: + raise Exception("Commu Module SPI init failed, maybe commu module is not connected") + + # add the some code + + def mcp2515_reset(self) -> int: + self.spi_start() + self.spi_transfer_reg(INSTRUCTION.INSTRUCTION_RESET) + self.spi_end() + time.sleep_ms(10) # type: ignore + + def mcp2515_set_config_mode(self) -> int: + return self.mcp2515_set_can_ctrl_mode(CANCTRL_REQOP_MODE.CANCTRL_REQOP_CONFIG) + + # def setListenOnlyMode(self) -> int: + # return self.mcp2515_set_can_ctrl_mode(CANCTRL_REQOP_MODE.CANCTRL_REQOP_LISTENONLY) + + def mcp2515_set_sleep_mode(self) -> int: + return self.mcp2515_set_can_ctrl_mode(CANCTRL_REQOP_MODE.CANCTRL_REQOP_SLEEP) + + # def setLoopbackMode(self) -> int: + # return self.mcp2515_set_can_ctrl_mode(CANCTRL_REQOP_MODE.CANCTRL_REQOP_LOOPBACK) + + def mcp2515_set_normal_mode(self) -> int: + return self.mcp2515_set_can_ctrl_mode(CANCTRL_REQOP_MODE.CANCTRL_REQOP_NORMAL) + + def mcp2515_set_mode(self, mode: int) -> int: + return self.mcp2515_set_can_ctrl_mode(mode) + + def mcp2515_set_can_ctrl_mode(self, mode: int) -> int: + self.mcp2515_modify_register(REGISTER.MCP_CANCTRL, CANCTRL_REQOP, mode) + end_time = time.ticks_add(time.ticks_ms(), 200) # type: ignore + mode_match = False + while time.ticks_diff(time.ticks_ms(), end_time) < 0: # type: ignore + newmode = self.mcp2515_read_register(REGISTER.MCP_CANSTAT) + newmode &= CANSTAT_OPMOD + mode_match = newmode == mode + if mode_match: + break + return ERROR.ERROR_OK if mode_match else ERROR.ERROR_FAIL + + def mcp2515_config_rate(self, canSpeed: int, canClock: int = CAN_CLOCK.MCP_16MHZ) -> int: + error = self.mcp2515_set_config_mode() + if error != ERROR.ERROR_OK: + return error + set_ = 1 + try: + cfg1, cfg2, cfg3 = CAN_CFGS[canClock][canSpeed] + except KeyError: + set_ = 0 + if set_: + self.mcp2515_set_register(REGISTER.MCP_CNF1, cfg1) + self.mcp2515_set_register(REGISTER.MCP_CNF2, cfg2) + self.mcp2515_set_register(REGISTER.MCP_CNF3, cfg3) + return ERROR.ERROR_OK + return ERROR.ERROR_FAIL + + def mcp2515_init_can_buffers(self) -> None: + self.mcp2515_write_id(REGISTER.MCP_RXM0SIDH, 1, 0x00) + self.mcp2515_write_id(REGISTER.MCP_RXM1SIDH, 1, 0x00) + self.mcp2515_write_id(REGISTER.MCP_RXF0SIDH, 1, 0x00) + self.mcp2515_write_id(REGISTER.MCP_RXF1SIDH, 0, 0x00) + self.mcp2515_write_id(REGISTER.MCP_RXF2SIDH, 1, 0x00) + self.mcp2515_write_id(REGISTER.MCP_RXF3SIDH, 0, 0x00) + self.mcp2515_write_id(REGISTER.MCP_RXF4SIDH, 1, 0x00) + self.mcp2515_write_id(REGISTER.MCP_RXF5SIDH, 0, 0x00) + a1 = REGISTER.MCP_TXB0CTRL + a2 = REGISTER.MCP_TXB1CTRL + a3 = REGISTER.MCP_TXB2CTRL + for i in range(0, 14): + self.mcp2515_set_register(a1, 0) + self.mcp2515_set_register(a2, 0) + self.mcp2515_set_register(a3, 0) + a1 += 1 + a2 += 1 + a3 += 1 + self.mcp2515_set_register(REGISTER.MCP_RXB0CTRL, 0) + self.mcp2515_set_register(REGISTER.MCP_RXB1CTRL, 0) + + def mcp2515_init(self, canIDMode: int, canSpeed: int, canClock: int) -> bool: + self.mcp2515_reset() + if self.mcp2515_set_can_ctrl_mode(CANCTRL_REQOP_MODE.CANCTRL_REQOP_CONFIG) > 0: + return False + if self.mcp2515_config_rate(canSpeed, canClock) > 0: + return False + self.mcp2515_init_can_buffers() + self.mcp2515_set_register( + REGISTER.MCP_CANINTE, CANINTF.CANINTF_RX0IF | CANINTF.CANINTF_RX1IF + ) + self.mcp2515_set_register(REGISTER.MCP_BFPCTRL, BxBFS_MASK | BxBFE_MASK) + self.mcp2515_set_register(REGISTER.MCP_TXRTSCTRL, 0x00) + if canIDMode == MCP_ANY: + self.mcp2515_modify_register( + REGISTER.MCP_RXB0CTRL, + RXBnCTRL_RXM_MASK | RXB0CTRL_BUKT, + RXBnCTRL_RXM_ANY | RXB0CTRL_BUKT, + ) + self.mcp2515_modify_register( + REGISTER.MCP_RXB1CTRL, RXBnCTRL_RXM_MASK, RXBnCTRL_RXM_ANY + ) + if canIDMode == MCP_STDEXT: + self.mcp2515_modify_register( + REGISTER.MCP_RXB0CTRL, + RXBnCTRL_RXM_MASK | RXB0CTRL_BUKT, + RXBnCTRL_RXM_STDEXT | RXB0CTRL_BUKT, + ) + self.mcp2515_modify_register( + REGISTER.MCP_RXB1CTRL, RXBnCTRL_RXM_MASK, RXBnCTRL_RXM_STDEXT + ) + if self.mcp2515_set_can_ctrl_mode(CANCTRL_REQOP_MODE.CANCTRL_REQOP_NORMAL) > 0: + return False + return True + + def mcp2515_write_id(self, mcp_addr: int, ext: int, id_: int) -> None: + canid = id_ & 0xFFFF + buffer = bytearray(CAN_IDLEN) + if ext: + buffer[MCP_EID0] = canid & 0xFF + buffer[MCP_EID8] = canid >> 8 + canid = id_ >> 16 + buffer[MCP_SIDL] = canid & 0x03 + buffer[MCP_SIDL] += (canid & 0x1C) << 3 + buffer[MCP_SIDL] |= TXB_EXIDE_MASK + buffer[MCP_SIDH] = canid >> 5 + else: + buffer[MCP_SIDH] = canid >> 3 + buffer[MCP_SIDL] = (canid & 0x07) << 5 + buffer[MCP_EID0] = 0 + buffer[MCP_EID8] = 0 + self.mcp2515_set_registers(mcp_addr, buffer) + + def mcp2515_prepare_id(self, ext: int, id_: int) -> bytearray: + canid = id_ & 0xFFFF + buffer = bytearray(CAN_IDLEN) + + if ext: + buffer[MCP_EID0] = canid & 0xFF + buffer[MCP_EID8] = canid >> 8 + canid = id_ >> 16 + buffer[MCP_SIDL] = canid & 0x03 + buffer[MCP_SIDL] += (canid & 0x1C) << 3 + buffer[MCP_SIDL] |= TXB_EXIDE_MASK + buffer[MCP_SIDH] = canid >> 5 + else: + buffer[MCP_SIDH] = canid >> 3 + buffer[MCP_SIDL] = (canid & 0x07) << 5 + buffer[MCP_EID0] = 0 + buffer[MCP_EID8] = 0 + + return buffer + + def mcp2515_read_can_message(self, rxbn: int = None): + if rxbn is None: + return self.mcp2515_read_message() + + rxb = RXB[rxbn] + + tbufdata = self.mcp2515_read_registers(rxb.SIDH, 1 + CAN_IDLEN) + + id_ = (tbufdata[MCP_SIDH] << 3) + (tbufdata[MCP_SIDL] >> 5) + + is_extended = False + if (tbufdata[MCP_SIDL] & TXB_EXIDE_MASK) == TXB_EXIDE_MASK: + id_ = (id_ << 2) + (tbufdata[MCP_SIDL] & 0x03) + id_ = (id_ << 8) + tbufdata[MCP_EID8] + id_ = (id_ << 8) + tbufdata[MCP_EID0] + id_ |= CAN_EFF_FLAG + is_extended = True + + dlc = tbufdata[MCP_DLC] & DLC_MASK + if dlc > CAN_MAX_DLEN: + return ERROR.ERROR_FAIL, None, None, None, None, None + + ctrl = self.mcp2515_read_register(rxb.CTRL) + is_rtr = bool(ctrl & RXBnCTRL_RTR) + if is_rtr: + id_ |= CAN_RTR_FLAG + + fmi = 0xFF # Assuming FMI is not used, set to a default value + + data = bytearray(self.mcp2515_read_registers(rxb.DATA, dlc)) + + return ERROR.ERROR_OK, id_, is_extended, is_rtr, fmi, data + + def mcp2515_read_message(self) -> tuple[int, any]: + rc = ERROR.ERROR_NOMSG, None + + stat = self.mcp2515_get_status() + if stat & STAT.STAT_RX0IF and self.mcp2515_rx_index == 0: + rc = self.mcp2515_read_can_message(RXBn.RXB0) + if self.mcp2515_get_status() & STAT.STAT_RX1IF: + self.mcp2515_rx_index = 1 + self.mcp2515_modify_register(REGISTER.MCP_CANINTF, RXB[RXBn.RXB0].CANINTFRXnIF, 0) + elif stat & STAT.STAT_RX1IF: + rc = self.mcp2515_read_can_message(RXBn.RXB1) + self.mcp2515_rx_index = 0 + self.mcp2515_modify_register(REGISTER.MCP_CANINTF, RXB[RXBn.RXB1].CANINTFRXnIF, 0) + + return rc + + def mcp2515_send_can_message(self, extframe: bool, frame: any, txbn: int = None) -> int: + self.extframe = extframe + if txbn is None: + return self.mcp2515_send_message(extframe, frame) + + if frame.dlc > CAN_MAX_DLEN: + return ERROR.ERROR_FAILTX + + txbuf = TXB[txbn] + # ext = frame.can_id & CAN_EFF_FLAG + rtr = frame.can_id & CAN_RTR_FLAG + id_ = frame.can_id & (CAN_EFF_MASK if self.extframe else CAN_SFF_MASK) + data = self.mcp2515_prepare_id(self.extframe, id_) + mcp_dlc = (frame.dlc | RTR_MASK) if rtr else frame.dlc + + data.extend(bytearray(1 + frame.dlc)) + data[MCP_DLC] = mcp_dlc + data[MCP_DATA : MCP_DATA + frame.dlc] = frame.data + self.mcp2515_set_registers(txbuf.SIDH, data) + + self.mcp2515_modify_register( + txbuf.CTRL, TXBnCTRL.TXB_TXREQ, TXBnCTRL.TXB_TXREQ, spifastend=True + ) + + ctrl = self.mcp2515_read_register(txbuf.CTRL) + if ctrl & (TXBnCTRL.TXB_ABTF | TXBnCTRL.TXB_MLOA | TXBnCTRL.TXB_TXERR): + return ERROR.ERROR_FAILTX + return ERROR.ERROR_OK + + def mcp2515_send_message(self, extframe: bool, frame: any) -> int: + if frame.dlc > CAN_MAX_DLEN: + return ERROR.ERROR_FAILTX + + tx_buffer = [TXBn.TXB0, TXBn.TXB1, TXBn.TXB2] + + for i in range(N_TXBUFFERS): + txbuf = TXB[tx_buffer[i]] + ctrlval = self.mcp2515_read_register(txbuf.CTRL) + if (ctrlval & TXBnCTRL.TXB_TXREQ) == 0: + return self.mcp2515_send_can_message(extframe, frame, tx_buffer[i]) + + return ERROR.ERROR_ALLTXBUSY + + def mcp2515_check_receive(self) -> bool: + res = self.mcp2515_get_status() + if res & STAT_RXIF_MASK: + return True + return False + + def mcp2515_check_error(self) -> bool: + eflg = self.mcp2515_get_error_flags() + + if eflg & EFLG_ERRORMASK: + return True + return False + + def mcp2515_get_error_flags(self) -> int: + return self.mcp2515_read_register(REGISTER.MCP_EFLG) + + def mcp2515_clear_rxn_ovr_flags(self) -> None: + self.mcp2515_modify_register(REGISTER.MCP_EFLG, EFLG.EFLG_RX0OVR | EFLG.EFLG_RX1OVR, 0) + + def mcp2515_get_interrupts(self) -> int: + return self.mcp2515_read_register(REGISTER.MCP_CANINTF) + + def mcp2515_clear_interrupts(self) -> None: + self.mcp2515_set_register(REGISTER.MCP_CANINTF, 0) + + def mcp2515_get_interrupt_mask(self) -> int: + return self.mcp2515_read_register(REGISTER.MCP_CANINTE) + + def mcp2515_clear_tx_interrupts(self) -> None: + self.mcp2515_modify_register( + REGISTER.MCP_CANINTF, + CANINTF.CANINTF_TX0IF | CANINTF.CANINTF_TX1IF | CANINTF.CANINTF_TX2IF, + 0, + ) + + def mcp2515_clear_rxn_ovr(self) -> None: + eflg = self.mcp2515_get_error_flags() + if eflg != 0: + self.mcp2515_clear_rxn_ovr_flags() + self.mcp2515_clear_interrupts() + # mcp2515_modify_register(REGISTER.MCP_CANINTF, CANINTF.CANINTF_ERRIF, 0) + + def mcp2515_clear_merr(self) -> None: + # self.mcp2515_modify_register(REGISTER.MCP_EFLG, EFLG.EFLG_RX0OVR | EFLG.EFLG_RX1OVR, 0) + # self.mcp2515_clear_interrupts() + self.mcp2515_modify_register(REGISTER.MCP_CANINTF, CANINTF.CANINTF_MERRF, 0) + + def mcp2515_clear_errif(self) -> None: + # self.mcp2515_modify_register(REGISTER.MCP_EFLG, EFLG.EFLG_RX0OVR | EFLG.EFLG_RX1OVR, 0) + # self.mcp2515_clear_interrupts() + self.mcp2515_modify_register(REGISTER.MCP_CANINTF, CANINTF.CANINTF_ERRIF, 0) + + def mcp2515_read_register(self, reg: int) -> int: + self.spi_start() + self.spi_transfer_reg(INSTRUCTION.INSTRUCTION_READ) + self.spi_transfer_reg(reg) + ret = self.spi_transfer_reg() + self.spi_end() + return ret + + def mcp2515_read_registers(self, reg: int, n: int) -> list[int]: + self.spi_start() + self.spi_transfer_reg(INSTRUCTION.INSTRUCTION_READ) + self.spi_transfer_reg(reg) + # MCP2515 has auto-increment of address-pointer + values = [] + for i in range(n): + values.append(self.spi_transfer_reg()) + self.spi_end() + return values + + def mcp2515_set_register(self, reg: int, value: int) -> None: + self.spi_start() + self.spi_transfer_reg(INSTRUCTION.INSTRUCTION_WRITE) + self.spi_transfer_reg(reg) + self.spi_transfer_reg(value) + self.spi_end() + + def mcp2515_set_registers(self, reg: int, values: bytearray) -> None: + self.spi_start() + self.spi_transfer_reg(INSTRUCTION.INSTRUCTION_WRITE) + self.spi_transfer_reg(reg) + for v in values: + self.spi_transfer_reg(v) + self.spi_end() + + def mcp2515_modify_register( + self, reg: int, mask: int, data: int, spifastend: bool = False + ) -> None: + self.spi_start() + self.spi_transfer_reg(INSTRUCTION.INSTRUCTION_BITMOD) + self.spi_transfer_reg(reg) + self.spi_transfer_reg(mask) + self.spi_transfer_reg(data) + if not spifastend: + self.spi_end() + else: + self.cs.on() + time.sleep_us(SPI_HOLD_US) # type: ignore + + def mcp2515_get_status(self) -> int: + self.spi_start() + self.spi_transfer_reg(INSTRUCTION.INSTRUCTION_READ_STATUS) + i = self.spi_transfer_reg() + self.spi_end() + return i + + def spi_transfer_reg(self, value=None): + if value is None: + response = bytearray(1) + self.hspi.write_readinto(bytes([0x00]), response) + return int.from_bytes(response, sys.byteorder) + else: + self.hspi.write(bytes([value])) + return None + + def spi_start(self): + self.cs.off() + + def spi_end(self): + self.cs.on() diff --git a/m5stack/libs/module/__init__.py b/m5stack/libs/module/__init__.py index d7dec206..acee58ec 100644 --- a/m5stack/libs/module/__init__.py +++ b/m5stack/libs/module/__init__.py @@ -7,6 +7,9 @@ _attrs = { "AIN4Module": "ain4", "Bala2Module": "bala2", + "CommuModuleCAN": "commu", + "CommuModuleI2C": "commu", + "CommuModuleRS485": "commu", "DisplayModule": "display", "DMX512Module": "dmx", "DualKmeterModule": "dual_kmeter", diff --git a/m5stack/libs/module/commu.py b/m5stack/libs/module/commu.py new file mode 100644 index 00000000..79151efb --- /dev/null +++ b/m5stack/libs/module/commu.py @@ -0,0 +1,291 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +from driver.mcp2515.mcp2515_spi import MCP2515_CAN +from driver.mcp2515.can_frame import CANFrame +from machine import UART, I2C +import time + +ERROR_OK = 0 +ERROR_FAIL = 1 +ERROR_ALLTXBUSY = 2 +ERROR_FAILINIT = 3 +ERROR_FAILTX = 4 +ERROR_NOMSG = 5 + +CAN_SFF_MASK = 0x000007FF # standard frame format (SFF) +CAN_EFF_MASK = 0x1FFFFFFF # extended frame format (EFF) + +import M5 +from collections import namedtuple + +MBusIO = namedtuple("MBusIO", ["cs", "int", "sck", "mosi", "miso"]) +iomap = { + M5.BOARD.M5Stack: MBusIO(12, 15, 18, 23, 19), + M5.BOARD.M5StackCore2: MBusIO(27, 2, 18, 23, 38), + M5.BOARD.M5StackCoreS3: MBusIO(6, 13, 36, 37, 35), +}.get(M5.getBoard()) + + +class CommuModuleCAN(MCP2515_CAN): + """Create an CommuModuleCAN object + + :param int mode: The CAN mode to use(NORMAL, LISTEN_ONLY), Default is NORMAL. + + Options: + - ``NORMAL``: Normal mode + - ``LISTEN_ONLY``: Listen only mode + + :param int baudrate: The baudrate to use, Default is CAN_1000KBPS. + + Options: + - ``CAN_5KBPS``: 5Kbps + - ``CAN_10KBPS``: 10Kbps + - ``CAN_20KBPS``: 20Kbps + - ``CAN_31K25BPS``: 31.25Kbps + - ``CAN_33KBPS``: 33Kbps + - ``CAN_40KBPS``: 40Kbps + - ``CAN_50KBPS``: 50Kbps + - ``CAN_80KBPS``: 80Kbps + - ``CAN_83K3BPS``: 83.33Kbps + - ``CAN_95KBPS``: 95Kbps + - ``CAN_100KBPS``: 100Kbps + - ``CAN_125KBPS``: 125Kbps + - ``CAN_200KBPS``: 200Kbps + - ``CAN_250KBPS``: 250Kbps + - ``CAN_500KBPS``: 500Kbps + - ``CAN_1000KBPS``: 1Mbps + + + :param int spi_baud: The SPI baudrate to use, Default is 8000000. + :param int canIDMode: The CAN ID mode to use(MCP_STDEXT, MCP_EXTDONLY), Default is MCP_STDEXT. + + Options: + - ``MCP_STDEXT``: Standard and Extended + - ``MCP_EXTDONLY``: Extended only + + :param bool debug: Whether to enable debug mode, Default is False. + + UiFlow2 Code Block: + + |init.png| + + MicroPython Code Block: + + .. code-block:: python + + from module import CommuModuleCAN + + commu = CommuModuleCAN(CommuModule.NORMAL, baudrate=16) + + """ + + NORMAL = 0x00 + LISTEN_ONLY = 0x60 + + MCP_STDEXT = 0 # Standard and Extended + MCP_8MHZ = 3 + + CAN_5KBPS = 1 + CAN_10KBPS = 2 + CAN_20KBPS = 3 + CAN_31K25BPS = 4 + CAN_33KBPS = 5 + CAN_40KBPS = 6 + CAN_50KBPS = 7 + CAN_80KBPS = 8 + CAN_83K3BPS = 9 + CAN_95KBPS = 10 + CAN_100KBPS = 11 + CAN_125KBPS = 12 + CAN_200KBPS = 13 + CAN_250KBPS = 14 + CAN_500KBPS = 15 + CAN_1000KBPS = 16 + + def __init__( + self, + mode: int = NORMAL, + baudrate: int = CAN_1000KBPS, + spi_baud=8000000, + canIDMode: int = MCP_STDEXT, + debug=False, + ): + super().__init__() + self.debug = debug + self.mcp2515_spi_init( + spi_baud=spi_baud, + sclk=iomap.sck, + mosi=iomap.mosi, + miso=iomap.miso, + cs=iomap.cs, + irq=iomap.int, + ) + + mode = self.mcp2515_set_mode(mode) + if self.debug: + print("CAN Mode Function Error Message Code: {} \r\n".format(mode)) + if self.mcp2515_init(canIDMode, baudrate, canClock=self.MCP_8MHZ) is not True: + raise Exception("Commu Module init failed") + self.commu_can_id = None + self.error = ERROR_OK + + def info(self): + """Get the state of error information. + + :returns: The current error information. + :rtype: str + + UiFlow2 Code Block: + + |info.png| + + MicroPython Code Block: + + .. code-block:: python + + commu.info() + """ + return self.error + + def reset(self) -> None: + self.mcp2515_reset() + + def get_irq_state(self) -> bool: + return bool(not self.irq.value()) + + def any(self) -> bool: + """Check if any message is available. + + :returns: The current message availability. + :rtype: bool + + UiFlow2 Code Block: + + |any.png| + + MicroPython Code Block: + + .. code-block:: python + + commu.any() + """ + return self.mcp2515_check_receive() + + def clear_interrupts(self): + self.mcp2515_clear_interrupts() + + def recv(self, fifo=0, list=None, timeout=5000): + """Read a message from the CAN bus. + + :param int fifo: The fifo is an integer, it can be any number and compatible with Pyb.CAN + :param list list: list is an optional list object to be used as the return value. + :param int timeout: timeout is the timeout in milliseconds to wait for the receive. + :returns: Tuple containing (can_id, is_extended, is_rtr, fmi, data) + :rtype: tuple + + - The id of the message. + - A boolean that indicates if the message ID is standard or extended. + - A boolean that indicates if the message is an RTR message. + - The FMI (Filter Match Index) value. + - An array containing the data. + + UiFlow2 Code Block: + + |recv_message.png| + + |recv_message_into.png| + + MicroPython Code Block: + + .. code-block:: python + + commu.recv(0) + + buf = bytearray(8) + lst = [0, 0, 0, 0, memoryview(buf)] + # No heap memory is allocated in the following call + commu.recv(0, lst) + """ + + start_time = time.ticks_ms() + + while time.ticks_ms() - start_time < timeout: + self.error, can_id, is_extended, is_rtr, fmi, data = self.mcp2515_read_can_message() + if self.debug: + print( + "CAN Read Data Packet Function Error Message Code: {} \r\n".format(self.error) + ) + if self.error == ERROR_OK: + self.commu_can_id = can_id & (CAN_EFF_MASK if self.extframe else CAN_SFF_MASK) + if list is None: + return [can_id, is_extended, is_rtr, fmi, bytes(data)] + else: + list[:] = [can_id, is_extended, is_rtr, fmi, bytes(data)] + return True + time.sleep(0.01) + + return None + + def send(self, data: str, can_id: int, extframe: bool = False) -> int: + """Send a message to the CAN bus. + + :param str data: The message data. + :param int can_id: The CAN ID. + :param bool extframe: Whether to use extended frame format. + :returns: The message data. + :rtype: str + + UiFlow2 Code Block: + + |send.png| + + MicroPython Code Block: + + .. code-block:: python + + commu.send('uiflow2', 0, extframe=False) + """ + can_frame = CANFrame(can_id, bytes(data.encode())) + send = self.mcp2515_send_can_message(extframe, can_frame) + if self.debug: + print("CAN Send Data Packet Function Error Message Code: {} \r\n".format(send)) + return send + + def commu_can_debug(self, enable=True) -> None: + self.debug = enable + + def deinit(self): + pass + # self.hspi.deinit() + + def commu_can_config_rate(self, canSpeed: int, canClock: int = 3) -> int: + config = self.mcp2515_config_rate(canSpeed, canClock) + if self.debug: + print("CAN Configure Function Error Message Code: {} \r\n".format(config)) + return config + + +class CommuModuleRS485: + def __new__( + cls, + id, + **kwargs, + ): + return UART( + id, + **kwargs, + ) + + +class CommuModuleI2C: + def __new__( + cls, + id, + **kwargs, + ): + return I2C( + id, + **kwargs, + ) diff --git a/m5stack/libs/module/manifest.py b/m5stack/libs/module/manifest.py index ace22b28..bd8ac017 100644 --- a/m5stack/libs/module/manifest.py +++ b/m5stack/libs/module/manifest.py @@ -8,6 +8,7 @@ "__init__.py", "ain4.py", "bala2.py", + "commu.py", "display.py", "dmx.py", "dual_kmeter.py", From d984f9391d6581ac78d033fbbff6fd63f3ba8712 Mon Sep 17 00:00:00 2001 From: Tinyu-Zhao Date: Wed, 19 Mar 2025 10:42:55 +0800 Subject: [PATCH 027/322] lib/unit: Add Unit PDM Support. Signed-off-by: Tinyu-Zhao --- docs/en/refs/unit.pdm.ref | 28 ++ docs/en/units/index.rst | 1 + docs/en/units/pdm.rst | 206 ++++++++++++ docs/locales/zh_CN/LC_MESSAGES/units/pdm.po | 352 ++++++++++++++++++++ examples/unit/pdm/pdm_cores3_example.m5f2 | 1 + examples/unit/pdm/pdm_cores3_example.py | 65 ++++ m5stack/libs/unit/__init__.py | 1 + m5stack/libs/unit/manifest.py | 1 + m5stack/libs/unit/pdm.py | 69 ++++ 9 files changed, 724 insertions(+) create mode 100644 docs/en/refs/unit.pdm.ref create mode 100644 docs/en/units/pdm.rst create mode 100644 docs/locales/zh_CN/LC_MESSAGES/units/pdm.po create mode 100644 examples/unit/pdm/pdm_cores3_example.m5f2 create mode 100644 examples/unit/pdm/pdm_cores3_example.py create mode 100644 m5stack/libs/unit/pdm.py diff --git a/docs/en/refs/unit.pdm.ref b/docs/en/refs/unit.pdm.ref new file mode 100644 index 00000000..5782c8a7 --- /dev/null +++ b/docs/en/refs/unit.pdm.ref @@ -0,0 +1,28 @@ + +.. |pdm| image:: https://static-cdn.m5stack.com/resource/docs/products/unit/pdm/pdm_01.webp + :target: https://docs.m5stack.com/en/unit/pdm + :height: 200px + :width: 200px + +.. |init.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/pdm/init.png +.. |begin.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/pdm/begin.png +.. |end.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/pdm/end.png +.. |record.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/pdm/record.png +.. |isRecording.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/pdm/isRecording.png +.. |recordWavFile.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/pdrecordWavFilem/.png +.. |get_config_boolean.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/pdm/get_config_boolean.png +.. |get_config_int.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/pdm/get_config_int.png +.. |set_config_boolean.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/pdm/set_config_boolean.png +.. |set_config_int.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/pdm/set_config_int.png + +.. |example.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/pdm/example.png + +.. |pdm_cores3_example.m5f2| raw:: html + + + pdm_cores3_example.m5f2 + + diff --git a/docs/en/units/index.rst b/docs/en/units/index.rst index acf5c1e9..685af89d 100644 --- a/docs/en/units/index.rst +++ b/docs/en/units/index.rst @@ -78,6 +78,7 @@ Unit oled.rst op90.rst op180.rst + pdm.rst pir.rst puzzle.rst qrcode.rst diff --git a/docs/en/units/pdm.rst b/docs/en/units/pdm.rst new file mode 100644 index 00000000..5334475c --- /dev/null +++ b/docs/en/units/pdm.rst @@ -0,0 +1,206 @@ +PDM Unit +========== + +.. sku: U089 + +.. include:: ../refs/unit.pdm.ref + +This is the driver library of PDM Unit, which is provides a set of methods to control the PDM microphone. Through the +I2S interface, the module can record audio data and save it as WAV files. + +Support the following products: + + |PDM| + + +UiFlow2 Example +--------------- + +record voice and play voice +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Open the |pdm_cores3_example.m5f2| project in UiFlow2. + +This example records voice and plays voice. + +UiFlow2 Code Block: + + |example.png| + +Example output: + + None + +MicroPython Example +------------------- + +record voice and play voice +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +This example records voice and plays voice. + +MicroPython Code Block: + + .. literalinclude:: ../../../examples/unit/pdm/pdm_cores3_example.py + :language: python + :linenos: + +Example output: + + None + + +**API** +------- + +PDMUnit +^^^^^^^^^ + +.. autoclass:: unit.pdm.PDMUnit + :members: + + .. py:method:: begin() + + Initialize the PDM microphone. + + :return: Returns True if initialization was successful, False otherwise. + :rtype: bool + + UiFlow2 Code Block: + + |begin.png| + + MicroPython Code Block: + + .. code-block:: python + + pdm.begin() + + .. py:method:: end() + + Stop the PDM microphone. + + UiFlow2 Code Block: + + |end.png| + + MicroPython Code Block: + + .. code-block:: python + + pdm.end() + + .. py:method:: record(buffer, sample_rate=16000, stereo=False) + + Record audio data into the provided buffer. + + :param bytearray buffer: Buffer to store the recorded audio data + :param int sample_rate: Sample rate in Hz (default: 16000) + :param bool stereo: True for stereo recording, False for mono (default: False) + :return: True if recording started successfully + :rtype: bool + + UiFlow2 Code Block: + + |record.png| + + MicroPython Code Block: + + .. code-block:: python + + rec_data = bytearray(16000 * 5) # 5 seconds buffer + pdm.record(rec_data, 16000, False) + + .. py:method:: isRecording() + + Check if recording is in progress. + + :return: Returns the number of bytes recorded. + + * ``0`` - Not recording + * ``1`` - Recording (Queue has available space) + * ``2`` - Recording (Queue is full) + + :rtype: int + + UiFlow2 Code Block: + + |isRecording.png| + + MicroPython Code Block: + + .. code-block:: python + + pdm.isRecording() + + .. py:method:: recordWavFile(path, rate=16000, time=5, stereo=False) + + Record audio directly to a WAV file. + + :param str path: Path to save the WAV file + :param int rate: Sample rate in Hz (default: 16000) + :param int time: Recording duration in seconds (default: 5) + :param bool stereo: True for stereo recording, False for mono (default: False) + :return: True if recording was successful + :rtype: bool + + UiFlow2 Code Block: + + |recordWavFile.png| + + MicroPython Code Block: + + .. code-block:: python + + pdm.recordWavFile("/sd/test.wav", 16000, 5, False) + + .. py:method:: config(**kwargs) + + Configure the PDM microphone parameters. + + :param kwargs: Configuration parameters + + - pin_data_in: Data input pin + - pin_ws: Word select pin + - sample_rate: Sample rate in Hz + - stereo: Stereo mode + - over_sampling: Over sampling rate + - noise_filter_level: Noise filter level + - magnification: Audio magnification + - dma_buf_len: DMA buffer length + - dma_buf_count: DMA buffer count + - task_priority: Task priority + - task_pinned_core: Task core pinning + - i2s_port: I2S port number + + UiFlow2 Code Block: + + |get_config_boolean.png| + + |get_config_int.png| + + |set_config_boolean.png| + + |set_config_int.png| + + MicroPython Code Block: + + .. code-block:: python + + pdm.config( + dma_buf_count=3, + dma_buf_len=256, + over_sampling=2, + noise_filter_level=0, + sample_rate=16000, + pin_data_in=1, + pin_ws=2, + pin_bck=-1, + pin_mck=-1, + use_adc=False, + stereo=False, + magnification=1, + task_priority=2, + task_pinned_core=255, + i2s_port=i2s_port, + ) diff --git a/docs/locales/zh_CN/LC_MESSAGES/units/pdm.po b/docs/locales/zh_CN/LC_MESSAGES/units/pdm.po new file mode 100644 index 00000000..78e964ae --- /dev/null +++ b/docs/locales/zh_CN/LC_MESSAGES/units/pdm.po @@ -0,0 +1,352 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2016 - 2025 M5Stack Technology Co., Ltd +# This file is distributed under the same license as the UIFlow2 Programming +# Guide package. +# FIRST AUTHOR , 2025. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: UIFlow2 Programming Guide \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-03-19 10:31+0800\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: zh_CN\n" +"Language-Team: zh_CN \n" +"Plural-Forms: nplurals=1; plural=0;\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.16.0\n" + +#: ../../en/units/pdm.rst:2 ec7809f167c244ff8542254af5ab7e4b +msgid "PDM Unit" +msgstr "" + +#: ../../en/units/pdm.rst:8 cd5ca23d0994446889d66256671e6b9f +msgid "" +"This is the driver library of PDM Unit, which is provides a set of " +"methods to control the PDM microphone. Through the I2S interface, the " +"module can record audio data and save it as WAV files." +msgstr "这是 PDM 单元的驱动程序库,提供了一套控制 PDM 麦克风的方法。通过 I2S 接口,该模块可记录音频数据并保存为 WAV 文件。" + +#: ../../en/units/pdm.rst:11 c97375295f634bbfb0e5b0def74c900b +msgid "Support the following products:" +msgstr "支持以下产品:" + +#: ../../en/units/pdm.rst:13 275ea1761cb84f3096c46b7287787693 +msgid "|PDM|" +msgstr "" + +#: ../../en/refs/unit.pdm.ref 7edd6c4d1aa34778801f5ee7036fd1f1 +msgid "pdm" +msgstr "" + +#: ../../en/units/pdm.rst:17 860ace05097c43b380857001caaf52f2 +msgid "UiFlow2 Example" +msgstr "UiFlow2 应用示例" + +#: ../../en/units/pdm.rst:20 ../../en/units/pdm.rst:38 +#: a1a1324cb29f41179c5f4d86cfee0b72 +msgid "record voice and play voice" +msgstr "录音音频并播放" + +#: ../../en/units/pdm.rst:22 ee23d1fb844a4a269c2ecef3aac1bc66 +msgid "Open the |pdm_cores3_example.m5f2| project in UiFlow2." +msgstr "在 UiFlow2 中打开 |pdm_cores3_example.m5f2| 项目。" + +#: ../../en/units/pdm.rst:24 ../../en/units/pdm.rst:40 +#: e42d9fe55a2e4ed89aa1a5c93dc98327 +msgid "This example records voice and plays voice." +msgstr "这个示例展示了录音音频并播放。" + +#: ../../en/units/pdm.rst:26 ../../en/units/pdm.rst:69 +#: ../../en/units/pdm.rst:83 ../../en/units/pdm.rst:103 +#: ../../en/units/pdm.rst:126 ../../en/units/pdm.rst:147 +#: ../../en/units/pdm.rst:176 a0dbd900f3254a5c9b8fd5f900aea01b of +#: unit.pdm.PDMUnit:7 +msgid "UiFlow2 Code Block:" +msgstr "UiFlow2 代码块:" + +#: ../../en/units/pdm.rst:28 ca8b74fa9d8e40878f0f2f9b7a2d8a7e +msgid "|example.png|" +msgstr "" + +#: ../../en/refs/unit.pdm.ref:15 3a064799f41a457891fdfe9e0783dc63 +msgid "example.png" +msgstr "" + +#: ../../en/units/pdm.rst:30 ../../en/units/pdm.rst:48 +#: 3a639cad0cf04d8792361e905c49431b +msgid "Example output:" +msgstr "示例输出:" + +#: ../../en/units/pdm.rst:32 ../../en/units/pdm.rst:50 +#: 8e6bc9ab1d564917bd241a947cfa5ec1 +msgid "None" +msgstr "" + +#: ../../en/units/pdm.rst:35 5242d09884e3424f8bb0caeffc80b4b8 +msgid "MicroPython Example" +msgstr "MicroPython 应用示例" + +#: ../../en/units/pdm.rst:42 ../../en/units/pdm.rst:73 +#: ../../en/units/pdm.rst:87 ../../en/units/pdm.rst:107 +#: ../../en/units/pdm.rst:130 ../../en/units/pdm.rst:151 +#: ../../en/units/pdm.rst:180 9d1a7656efc94701b801ccbf5d904724 of +#: unit.pdm.PDMUnit:11 +msgid "MicroPython Code Block:" +msgstr "MicroPython 代码块:" + +#: ../../en/units/pdm.rst:54 3255b806cb1346d78648583f2265e357 +msgid "**API**" +msgstr "" + +#: ../../en/units/pdm.rst:57 8ea8240b03e34db58662a25a28582f58 +msgid "PDMUnit" +msgstr "" + +#: 55d0965f11c2414b903b94130a219e38 of unit.pdm.PDMUnit:1 +msgid "Bases: :py:class:`object`" +msgstr "" + +#: 2823d7b5ba934b1cba408cd6b2074d64 of unit.pdm.PDMUnit:1 +msgid "PDM Unit class." +msgstr "" + +#: ../../en/units/pdm.rst 16d93ddb27344765a047a3bc468aeb53 +msgid "Parameters" +msgstr "" + +#: c2baf299bf5c41f0b2cc81673bafc85a of unit.pdm.PDMUnit:3 +msgid "Connect to the PDM Unit." +msgstr "连接到 PDM Unit" + +#: 07c9e6caa24547e1a72eff9bf6b16209 of unit.pdm.PDMUnit:4 +msgid "I2S port number." +msgstr "I2S 端口号。" + +#: e4ed361fbde446e588145f61367dc4dc of unit.pdm.PDMUnit:5 +msgid "Sample rate." +msgstr "采样率。" + +#: 66066b99ce3a413c8ead457e4d503c69 of unit.pdm.PDMUnit:9 +msgid "|init.png|" +msgstr "" + +#: ../../en/refs/unit.pdm.ref:7 6e9c8156cb58435db2dfcdf61db59bd8 +msgid "init.png" +msgstr "" + +#: ../../en/units/pdm.rst:64 b603501567d54481a465ae3172e27fb2 +msgid "Initialize the PDM microphone." +msgstr "初始化 PDM 麦克风。" + +#: ../../en/units/pdm.rst 2da41e375a5a457fac1cfde853dd1347 +msgid "Returns" +msgstr "返回" + +#: ../../en/units/pdm.rst:66 7df738cc5995493fae7db7fa697a8226 +msgid "Returns True if initialization was successful, False otherwise." +msgstr "如果初始化成功,返回 True,否则返回 False。" + +#: ../../en/units/pdm.rst fcbcb0de3a4a4cd5b18283a6086eaa56 +msgid "Return type" +msgstr "返回类型" + +#: ../../en/units/pdm.rst:71 5991615e30734cd09c7397f1e29c62f2 +msgid "|begin.png|" +msgstr "" + +#: ../../en/refs/unit.pdm.ref:8 6e9c8156cb58435db2dfcdf61db59bd8 +msgid "begin.png" +msgstr "" + +#: ../../en/units/pdm.rst:81 0d6cd0feb1814e029767ddb07f1aee50 +msgid "Stop the PDM microphone." +msgstr "停止 PDM 麦克风。" + +#: ../../en/units/pdm.rst:85 6dc4d555a33a4b569a642eb1166e2876 +msgid "|end.png|" +msgstr "" + +#: ../../en/refs/unit.pdm.ref:9 6e9c8156cb58435db2dfcdf61db59bd8 +msgid "end.png" +msgstr "" + +#: ../../en/units/pdm.rst:95 b6b8b652ccb040f59fee024f457e1536 +msgid "Record audio data into the provided buffer." +msgstr "将音频数据记录到提供的缓冲区中。" + +#: ../../en/units/pdm.rst:97 9d3e5864698c45dd95d8e65b67613392 +msgid "Buffer to store the recorded audio data" +msgstr "存储记录的音频数据的缓冲区。" + +#: ../../en/units/pdm.rst:98 ../../en/units/pdm.rst:141 +#: 15de1842fe804aa5bdb64448f86722ae +msgid "Sample rate in Hz (default: 16000)" +msgstr "采样率(默认:16000)" + +#: ../../en/units/pdm.rst:99 ../../en/units/pdm.rst:143 +#: 604ead0f70d84ae78dd08a7eb45bb80c +msgid "True for stereo recording, False for mono (default: False)" +msgstr "True 表示立体声录音,False 表示单声道录音(默认:False)" + +#: ../../en/units/pdm.rst:100 87d998cf34fd49ba92092c89b2cf5d85 +msgid "True if recording started successfully" +msgstr "如果录音成功开始,返回 True,否则返回 False。" + +#: ../../en/units/pdm.rst:105 409168f0d4914994a482388837b69ee4 +msgid "|record.png|" +msgstr "" + +#: ../../en/refs/unit.pdm.ref:10 6e9c8156cb58435db2dfcdf61db59bd8 +msgid "record.png" +msgstr "" + +#: ../../en/units/pdm.rst:116 b4b5af3faa374e43ae132ee4e40a49cd +msgid "Check if recording is in progress." +msgstr "检查录音是否正在进行。" + +#: ../../en/units/pdm.rst:118 4a022066205048c0aecaf1c0f1896201 +msgid "" +"Returns the number of bytes recorded. * ``0`` - Not recording * ``1`` - " +"Recording (Queue has available space) * ``2`` - Recording (Queue is full)" +msgstr "返回记录的字节数。 * ``0`` - 未录音\n" +" * ``1`` - 录音(队列有可用空间)\n" +" * ``2`` - 录音(队列已满)" + +#: ../../en/units/pdm.rst:118 f880b76185f34539865fac8e7c8d9ed6 +msgid "Returns the number of bytes recorded." +msgstr "返回记录的字节数。" + +#: ../../en/units/pdm.rst:120 ffab5efcf44e401d9f1bd60ddff4616e +msgid "``0`` - Not recording" +msgstr "``0`` - 未录音" + +#: ../../en/units/pdm.rst:121 07bb85b362f840b895c0fc551d71972e +msgid "``1`` - Recording (Queue has available space)" +msgstr "``1`` - 录音(队列有可用空间)" + +#: ../../en/units/pdm.rst:122 7ca2061ec4434abdb680a993b715c2b8 +msgid "``2`` - Recording (Queue is full)" +msgstr "``2`` - 录音(队列已满)" + +#: ../../en/units/pdm.rst:128 883a02585cf9408cbf39e7bbc48af21d +msgid "|isRecording.png|" +msgstr "" + +#: ../../en/refs/unit.pdm.ref:11 6e9c8156cb58435db2dfcdf61db59bd8 +msgid "isRecording.png" +msgstr "" + +#: ../../en/units/pdm.rst:138 ade6877086474114917dcb02ce274d1a +msgid "Record audio directly to a WAV file." +msgstr "直接将音频记录到 WAV 文件中。" + +#: ../../en/units/pdm.rst:140 57ad4dd1a9744309bd148f55cf77c4b5 +msgid "Path to save the WAV file" +msgstr "保存 WAV 文件的路径" + +#: ../../en/units/pdm.rst:142 fe6d3a66ec4f417087601e5dc67f9b1a +msgid "Recording duration in seconds (default: 5)" +msgstr "录音时长(默认:5秒)" + +#: ../../en/units/pdm.rst:144 6a01de3bcfec430d8dd5301fd7c534e5 +msgid "True if recording was successful" +msgstr "如果录音成功,返回 True,否则返回 False。" + +#: ../../en/units/pdm.rst:149 4430bceb0717437295a46f5862f11c17 +msgid "|recordWavFile.png|" +msgstr "" + +#: ../../en/refs/unit.pdm.ref:12 3a064799f41a457891fdfe9e0783dc63 +msgid "recordWavFile.png" +msgstr "" + +#: ../../en/units/pdm.rst:159 b8b2df30b16946a5af129a3c1ac85857 +msgid "Configure the PDM microphone parameters." +msgstr "配置 PDM 麦克风参数。" + +msgid "" +"Configuration parameters - pin_data_in: Data input pin - pin_ws: Word " +"select pin - sample_rate: Sample rate in Hz - stereo: Stereo mode - " +"over_sampling: Over sampling rate - noise_filter_level: Noise filter " +"level - magnification: Audio magnification - dma_buf_len: DMA buffer " +"length - dma_buf_count: DMA buffer count - task_priority: Task priority -" +" task_pinned_core: Task core pinning - i2s_port: I2S port number" +msgstr "" +"配置参数 - pin_data_in: 数据输入引脚\n" +" - pin_ws: 字选引脚\n" +" - sample_rate: 采样率\n" +" - stereo: 立体声模式\n" +" - over_sampling: 过采样率\n" +" - noise_filter_level: 噪声过滤级别\n" +" - magnification: 音频放大倍数\n" +" - dma_buf_len: DMA 缓冲区长度\n" +" - dma_buf_count: DMA 缓冲区计数\n" +" - task_priority: 任务优先级\n" +" - task_pinned_core: 指定任务运行的核心\n" +" - i2s_port: I2S 端口号" + +#: ../../en/units/pdm.rst:161 ea459b72faba4867b1c41d17491722ab +msgid "Configuration parameters" +msgstr "配置参数" + +#: ../../en/units/pdm.rst:163 af0a2149114041d9b3fb98cd2eee72c7 +msgid "pin_data_in: Data input pin" +msgstr "pin_data_in: 数据输入引脚" + +#: ../../en/units/pdm.rst:164 798f6c2640df4f38bcd306e21d207546 +msgid "pin_ws: Word select pin" +msgstr "pin_ws: 字选引脚" + +#: ../../en/units/pdm.rst:165 0b8619fe6f0d4e3c81dc36722471bfa8 +msgid "sample_rate: Sample rate in Hz" +msgstr "sample_rate: 采样率" + +#: ../../en/units/pdm.rst:166 7894b475c48a4514b4a2f19fbd59f5f2 +msgid "stereo: Stereo mode" +msgstr "stereo: 立体声模式" + +#: ../../en/units/pdm.rst:167 bf7e18bc48a84514bf903cc6d50090ec +msgid "over_sampling: Over sampling rate" +msgstr "over_sampling: 过采样率" + +#: ../../en/units/pdm.rst:168 baf64af13f6c4a6f8673d358c2919a23 +msgid "noise_filter_level: Noise filter level" +msgstr "noise_filter_level: 噪声过滤级别" + +#: ../../en/units/pdm.rst:169 10e25adbf1d546b6a7a1ca05259df615 +msgid "magnification: Audio magnification" +msgstr "magnification: 音频放大倍数" + +#: ../../en/units/pdm.rst:170 e970c26a35854ce78f8e8d49bc4b9756 +msgid "dma_buf_len: DMA buffer length" +msgstr "dma_buf_len: DMA 缓冲区长度" + +#: ../../en/units/pdm.rst:171 479516133b424d99b847f7493adcea1a +msgid "dma_buf_count: DMA buffer count" +msgstr "dma_buf_count: DMA 缓冲区计数" + +#: ../../en/units/pdm.rst:172 25a5f4d381014d27b950da1ab859c194 +msgid "task_priority: Task priority" +msgstr "task_priority: 任务优先级" + +#: ../../en/units/pdm.rst:173 a180450c66ec4c48ae881d3dad2be322 +msgid "task_pinned_core: Task core pinning" +msgstr "task_pinned_core: 指定任务运行的核心" + +#: ../../en/units/pdm.rst:174 3c8898aaac654c92b9ba216f7f529272 +msgid "i2s_port: I2S port number" +msgstr "i2s_port: I2S 端口号" + +#: ../../en/units/pdm.rst:178 43395ae638a04638a8a96c3ca9dfdce4 +msgid "|config.png|" +msgstr "" + +#: ../../en/refs/unit.pdm.ref:13 6e9c8156cb58435db2dfcdf61db59bd8 +msgid "config.png" +msgstr "" + diff --git a/examples/unit/pdm/pdm_cores3_example.m5f2 b/examples/unit/pdm/pdm_cores3_example.m5f2 new file mode 100644 index 00000000..7937c763 --- /dev/null +++ b/examples/unit/pdm/pdm_cores3_example.m5f2 @@ -0,0 +1 @@ +{"version":"V2.0","versionNumber":"V2.2.3","type":"cores3","components":[{"name":"screen","type":"screen","layer":0,"screenId":"builtin","screenName":"","id":"__cores3_screen","createTime":1717660236218,"x":0,"y":0,"width":320,"height":240,"backgroundColor":"#222222","size":0,"isSelected":true},{"name":"label0","type":"label","layer":1,"screenId":"builtin","screenName":"","id":"rW^fX1*1vprMa8z@","createTime":1717660555376,"x":128,"y":114,"color":"#ffffff","backgroundColor":"#222222","text":"label0","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false,"width":58,"height":21},{"name":"title0","type":"title","layer":1,"screenId":"builtin","screenName":"","id":"fUyX^usfI`lUa7#%","createTime":1742349913202,"x":0,"y":0,"color":"#ffffff","backgroundColor":"#0000FF","text":"PDMUnit CoreS3 Example","textOffset":3,"font":"Widgets.FONTS.DejaVu18","isSelected":false}],"resources":[{"hardware":["hardware_button","hardware_pin_button","imu","speaker","touch","als","mic"]},{"unit":["unit_pdm"]}],"units":[{"type":"unit_pdm","name":"pdm_0","portList":["A","B","C","Custom"],"portType":"A","userPort":[22,21],"id":"gss%Q0aN!0e$&#RW","createTime":1742349854435,"initBlockId":"bMOGU7vj4{9Vi2SNi)9z"}],"hats":[],"bases":[],"i2cs":[],"blockly":"rec_datatruepdm_0244100100pdm_0rec_data1MULTIPLY4410010label0rec...pdm_0Falserec_data100WHILEpdm_0label0rec...100pdm_0label0play...rec_data16000MULTIPLY441002WHILE100label0donetrue","screen":[{"simulationName":"Built-in","type":"builtin","width":320,"height":240,"scale":0.78,"screenName":"","blockId":"","screenColorType":0,"id":"builtin","createTime":1717660236214}],"logicWhenNum":0,"customList":[]} \ No newline at end of file diff --git a/examples/unit/pdm/pdm_cores3_example.py b/examples/unit/pdm/pdm_cores3_example.py new file mode 100644 index 00000000..055168b6 --- /dev/null +++ b/examples/unit/pdm/pdm_cores3_example.py @@ -0,0 +1,65 @@ +# SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import os, sys, io +import M5 +from M5 import * +from unit import PDMUnit +import time + + +label0 = None +title0 = None +pdm_0 = None + + +rec_data = None + + +def setup(): + global label0, title0, pdm_0, rec_data + + M5.begin() + Widgets.fillScreen(0x222222) + label0 = Widgets.Label("label0", 128, 114, 1.0, 0xFFFFFF, 0x222222, Widgets.FONTS.DejaVu18) + title0 = Widgets.Title("PDMUnit CoreS3 Example", 3, 0xFFFFFF, 0x0000FF, Widgets.FONTS.DejaVu18) + + pdm_0 = PDMUnit((1, 2), i2s_port=2, sample_rate=44100) + Speaker.begin() + Speaker.setVolumePercentage(1) + Speaker.end() + pdm_0.begin() + rec_data = bytearray(44100 * 10) + label0.setText(str("rec...")) + pdm_0.record(rec_data, _, False) + time.sleep_ms(100) + while pdm_0.isRecording(): + label0.setText(str("rec...")) + time.sleep_ms(100) + pdm_0.end() + Speaker.begin() + label0.setText(str("play...")) + Speaker.playRaw(rec_data, 44100 * 2) + while Speaker.isPlaying(): + time.sleep_ms(100) + label0.setText(str("done")) + + +def loop(): + global label0, title0, pdm_0, rec_data + M5.update() + + +if __name__ == "__main__": + try: + setup() + while True: + loop() + except (Exception, KeyboardInterrupt) as e: + try: + from utility import print_error_msg + + print_error_msg(e) + except ImportError: + print("please update to latest firmware") diff --git a/m5stack/libs/unit/__init__.py b/m5stack/libs/unit/__init__.py index 33212afd..83dc5e52 100644 --- a/m5stack/libs/unit/__init__.py +++ b/m5stack/libs/unit/__init__.py @@ -89,6 +89,7 @@ "OPUnit": "op", "PAHUBUnit": "pahub", "PBHUBUnit": "pbhub", + "PDMUnit": "pdm", "PIRUnit": "pir", "PuzzleUnit": "puzzle", "QRCodeUnit": "qrcode", diff --git a/m5stack/libs/unit/manifest.py b/m5stack/libs/unit/manifest.py index d9f93488..b61fff11 100644 --- a/m5stack/libs/unit/manifest.py +++ b/m5stack/libs/unit/manifest.py @@ -88,6 +88,7 @@ "op.py", "pahub.py", "pbhub.py", + "pdm.py", "pir.py", "puzzle.py", "qrcode.py", diff --git a/m5stack/libs/unit/pdm.py b/m5stack/libs/unit/pdm.py new file mode 100644 index 00000000..609acec5 --- /dev/null +++ b/m5stack/libs/unit/pdm.py @@ -0,0 +1,69 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import M5 + + +class PDMUnit: + """PDM Unit class. + + :param list | tuple port: Connect to the PDM Unit. + :param int i2s_port: I2S port number. + :param int sample_rate: Sample rate. + + UiFlow2 Code Block: + + |init.png| + + MicroPython Code Block: + + .. code-block:: python + + from unit import PDMUnit + + pdm_0 = PDMUnit((1, 2), i2s_port=0, sample_rate=44100) + """ + + _instance = None + + def __new__(cls, *args, **kwargs): + if cls._instance is None: + cls._instance = super().__new__(cls) + cls._instance._initialized = False + return cls._instance + + def __init__( + self, + port: list | tuple = (1, 2), + i2s_port: int = 0, + sample_rate: int = 44100, + ) -> None: + if self._initialized: + return + + self._mic = M5.createMic() + self._mic.config( + dma_buf_count=3, + dma_buf_len=256, + over_sampling=2, + noise_filter_level=0, + sample_rate=sample_rate, + pin_data_in=port[1], + pin_ws=port[0], + pin_bck=-1, + pin_mck=-1, + use_adc=False, + stereo=False, + magnification=2, + task_priority=2, + task_pinned_core=255, + i2s_port=i2s_port, + ) + self._initialized = True + + def __getattr__(self, name): + return getattr(self._mic, name) + + def deinit(self): + self._mic.deinit() From 0c89e8dab87618b13779fbb7e88a218b1b86a62a Mon Sep 17 00:00:00 2001 From: lbuque Date: Thu, 20 Mar 2025 17:27:57 +0800 Subject: [PATCH 028/322] .github: Update action runner. Signed-off-by: lbuque --- .github/workflows/build-release.yml | 2 +- .github/workflows/code_formatting.yml | 2 +- .github/workflows/nightly-build.yml | 2 +- .github/workflows/ports_m5stack.yml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build-release.yml b/.github/workflows/build-release.yml index 1207e1a7..3d344cda 100644 --- a/.github/workflows/build-release.yml +++ b/.github/workflows/build-release.yml @@ -8,7 +8,7 @@ on: jobs: build: - runs-on: self-hosted + runs-on: [self-hosted, Linux, X64] steps: - uses: actions/checkout@v3.3.0 diff --git a/.github/workflows/code_formatting.yml b/.github/workflows/code_formatting.yml index 0aefd961..f9bd0c5c 100644 --- a/.github/workflows/code_formatting.yml +++ b/.github/workflows/code_formatting.yml @@ -4,7 +4,7 @@ on: [push, pull_request] jobs: build: - runs-on: ubuntu-latest + runs-on: [self-hosted, Linux, X64] steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 diff --git a/.github/workflows/nightly-build.yml b/.github/workflows/nightly-build.yml index 680872f7..15375bcb 100644 --- a/.github/workflows/nightly-build.yml +++ b/.github/workflows/nightly-build.yml @@ -10,7 +10,7 @@ permissions: jobs: build: - runs-on: self-hosted + runs-on: [self-hosted, Linux, X64] steps: - uses: actions/checkout@v3.3.0 - name: Install packages diff --git a/.github/workflows/ports_m5stack.yml b/.github/workflows/ports_m5stack.yml index 9874f59b..1900b530 100644 --- a/.github/workflows/ports_m5stack.yml +++ b/.github/workflows/ports_m5stack.yml @@ -14,7 +14,7 @@ on: jobs: build_idf44: - runs-on: ubuntu-20.04 + runs-on: [self-hosted, Linux, X64] steps: - uses: actions/checkout@v3.3.0 - name: Install packages From 04a6d8b968d2df007b3312a41125c1d65dccb29f Mon Sep 17 00:00:00 2001 From: lbuque Date: Thu, 20 Mar 2025 17:46:04 +0800 Subject: [PATCH 029/322] version.text: Update to V2.2.4. Signed-off-by: lbuque --- m5stack/version.txt | 2 +- third-party/version.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/m5stack/version.txt b/m5stack/version.txt index 9cde184f..efd71067 100644 --- a/m5stack/version.txt +++ b/m5stack/version.txt @@ -1 +1 @@ -V2.2.3 \ No newline at end of file +V2.2.4 \ No newline at end of file diff --git a/third-party/version.txt b/third-party/version.txt index 9cde184f..efd71067 100644 --- a/third-party/version.txt +++ b/third-party/version.txt @@ -1 +1 @@ -V2.2.3 \ No newline at end of file +V2.2.4 \ No newline at end of file From eac6d6617462fa79070bc2d0112ad50bc48897da Mon Sep 17 00:00:00 2001 From: lbuque Date: Fri, 21 Mar 2025 12:04:25 +0800 Subject: [PATCH 030/322] boards: Update the partition table of CoreS3. The app partition does not have enough space to accommodate the program. Signed-off-by: lbuque --- m5stack/boards/M5STACK_CoreS3/sdkconfig.board | 6 +++--- m5stack/partitions_16mb_omv.csv | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/m5stack/boards/M5STACK_CoreS3/sdkconfig.board b/m5stack/boards/M5STACK_CoreS3/sdkconfig.board index 382d1576..f9e45780 100644 --- a/m5stack/boards/M5STACK_CoreS3/sdkconfig.board +++ b/m5stack/boards/M5STACK_CoreS3/sdkconfig.board @@ -2,9 +2,9 @@ # # SPDX-License-Identifier: MIT -CONFIG_FLASHMODE_DIO=y # QIO mode has some problem when mount fs -CONFIG_ESPTOOLPY_FLASHMODE_DIO=y -CONFIG_ESPTOOLPY_FLASHMODE="dio" +CONFIG_FLASHMODE_QIO=y # QIO mode has some problem when mount fs +CONFIG_ESPTOOLPY_FLASHMODE_QIO=y +CONFIG_ESPTOOLPY_FLASHMODE="qio" CONFIG_ESPTOOLPY_FLASHFREQ_80M=y CONFIG_ESPTOOLPY_FLASHSIZE_DETECT=y CONFIG_ESPTOOLPY_AFTER_NORESET=y diff --git a/m5stack/partitions_16mb_omv.csv b/m5stack/partitions_16mb_omv.csv index 155e7935..6c12806d 100644 --- a/m5stack/partitions_16mb_omv.csv +++ b/m5stack/partitions_16mb_omv.csv @@ -2,7 +2,7 @@ # Note: if you change the phy_init or app partition offset, make sure to change the offset in Kconfig.projbuild,,,, nvs,data,nvs,0x9000,0x6000, phy_init,data,phy,0xf000,0x1000, -factory,app,factory,0x10000,0x810000, -sys,data,fat,0x820000,0x100000, -vfs,data,fat,0x920000,0x400000, -storage,data,spiffs,0xF30000,0xA0000, +factory,app,factory,0x10000,0x950000, +sys,data,fat,0x960000,0x100000, +vfs,data,fat,0xA60000,0x500000, +storage,data,spiffs,0xF60000,0xA0000, From 0d5b4568939cc15b81800c3c50f356838ee7aa93 Mon Sep 17 00:00:00 2001 From: Tinyu-Zhao Date: Tue, 18 Mar 2025 17:01:13 +0800 Subject: [PATCH 031/322] lib/unit: Add some api for new FW. Signed-off-by: Tinyu-Zhao --- m5stack/libs/unit/extio2.py | 76 ++++++++++++++++++++++++++++++++- m5stack/libs/unit/pbhub.py | 29 ++++++++++++- m5stack/libs/unit/weight_i2c.py | 4 +- 3 files changed, 104 insertions(+), 5 deletions(-) diff --git a/m5stack/libs/unit/extio2.py b/m5stack/libs/unit/extio2.py index 32708c68..047a9e95 100644 --- a/m5stack/libs/unit/extio2.py +++ b/m5stack/libs/unit/extio2.py @@ -18,6 +18,8 @@ _REG_SERVO_ANGLE_8B_REG_CH_1 = const(0x50) _REG_SERVO_PULSE_16B_REG_CH_1 = const(0x60) _REG_RGB_24B_REG_CH_1 = const(0x70) +_REG_PWM_DUTY_CYCLE_REG = const(0x90) +_REG_PWM_FREQ_REG = const(0xA0) _REG_FW_VERSION = const(0xFE) _REG_ADDR_CONFIG = const(0xFF) @@ -145,7 +147,7 @@ def __init__(self, i2c: I2C | PAHUBUnit, address: int = _DEFAULT_ADDRESS) -> Non self._addr = address self._BUFFER = memoryview(bytearray(3)) - def set_config_mode(self, id: int, mode: Literal[0, 1, 2, 3, 4]) -> None: + def set_config_mode(self, id: int, mode: Literal[0, 1, 2, 3, 4, 5]) -> None: """ note: en: Set the configuration mode for a specific channel. @@ -309,6 +311,78 @@ def read_servo_pulse(self, id: int) -> int: """ return self._read_u16(_REG_SERVO_PULSE_16B_REG_CH_1 + (id * 2)) + def set_pwm_frequency(self, freq: int = 1) -> None: + """Set the PWM frequency of the pin. + + :param int freq: The PWM frequency of the pin. + + UiFlow2 Code Block: + + |set_pwm_frequency.png| + + MicroPython Code Block: + + .. code-block:: python + + extio2.set_pwm_frequency(1) + """ + self._write_u8(_REG_PWM_FREQ_REG, freq) + + def get_pwm_frequency(self) -> int: + """Get current PWM frequency of the pin. + + :returns: The current PWM frequency of the pin. + :rtype: int + + UiFlow2 Code Block: + + |get_pwm_frequency.png| + + MicroPython Code Block: + + .. code-block:: python + + extio2.get_pwm_frequency() + """ + return self._read_u8(_REG_PWM_FREQ_REG) + + def set_pwm_duty_cycle(self, id: int, duty_cycle: int) -> None: + """Set the PWM duty cycle of the pin. + + :param int duty_cycle: The PWM duty cycle of the pin. + + UiFlow2 Code Block: + + |set_pwm_duty_cycle.png| + + MicroPython Code Block: + + .. code-block:: python + + extio2.set_pwm_duty_cycle(0, 50) + """ + if not (0 <= duty_cycle <= 100): + raise ValueError("Duty cycle error, range is 0~100") + self._write_u8(_REG_PWM_DUTY_CYCLE_REG + id, duty_cycle) + + def get_pwm_duty_cycle(self, id: int) -> int: + """Get current PWM duty cycle of the pin. + + :returns: The current PWM duty cycle of the pin. + :rtype: int + + UiFlow2 Code Block: + + |get_pwm_duty_cycle.png| + + MicroPython Code Block: + + .. code-block:: python + + extio2.get_pwm_duty_cycle(0) + """ + return self._read_u8(_REG_PWM_DUTY_CYCLE_REG + id) + def read_rgb_led(self, id: int) -> int: """ note: diff --git a/m5stack/libs/unit/pbhub.py b/m5stack/libs/unit/pbhub.py index 6f54c546..30eb6263 100644 --- a/m5stack/libs/unit/pbhub.py +++ b/m5stack/libs/unit/pbhub.py @@ -12,6 +12,7 @@ hub_addr = [0x40, 0x50, 0x60, 0x70, 0x80, 0xA0] PBHUB_ADDR = 0x61 +LED_CTRL_REG = 0xFA FW_VER_REG = 0xFE I2C_ADDR_REG = 0xFF @@ -105,7 +106,10 @@ def set_rgb_color_pos(self, num, led, color_in): color_in : 0 to 0xfffff """ num = max(min(num, 5), 0) - out_buf = led.to_bytes(2, "little") + color_in.to_bytes(3, "big") + color_grb = ( + ((color_in & 0x00FF00) << 8) | ((color_in & 0xFF0000) >> 8) | (color_in & 0x0000FF) + ) + out_buf = led.to_bytes(2, "little") + color_grb.to_bytes(3, "big") self.pbhub_i2c.writeto_mem(self.i2c_addr, hub_addr[num] | 0x09, out_buf) def set_rgb_color(self, num, begin, count, color_in): @@ -117,8 +121,15 @@ def set_rgb_color(self, num, begin, count, color_in): color_in : 0 to 0xfffff """ num = max(min(num, 5), 0) + + color_grb = ( + ((color_in & 0x00FF00) << 8) | ((color_in & 0xFF0000) >> 8) | (color_in & 0x0000FF) + ) + out_buf = ( - begin.to_bytes(2, "little") + count.to_bytes(2, "little") + color_in.to_bytes(3, "big") + begin.to_bytes(2, "little") + + count.to_bytes(2, "little") + + color_grb.to_bytes(3, "big") ) self.pbhub_i2c.writeto_mem(self.i2c_addr, hub_addr[num] | 0x0A, out_buf) @@ -131,6 +142,20 @@ def set_rgb_brightness(self, num, value): num = max(min(num, 5), 0) self.write_mem_list(hub_addr[num] | 0x0B, [value]) + def set_rgb_mode(self, mode): + """ + set RGB led mode. + mode : 0 to 1 + """ + self.pbhub_i2c.writeto_mem(self.i2c_addr, LED_CTRL_REG, bytes([mode])) + + def get_rgb_mode(self): + """ + get RGB led mode. + return : 0 or 1 + """ + return self.pbhub_i2c.readfrom_mem(self.i2c_addr, LED_CTRL_REG, 1)[0] + def set_servo_angle(self, num, pos, value): """ set servo angle. diff --git a/m5stack/libs/unit/weight_i2c.py b/m5stack/libs/unit/weight_i2c.py index 843b3186..cf57786f 100644 --- a/m5stack/libs/unit/weight_i2c.py +++ b/m5stack/libs/unit/weight_i2c.py @@ -69,7 +69,7 @@ def set_calibration( weight2_adc: Weight2 in ADC. """ - gap = abs(weight2_adc - weight1_adc) / abs(weight2_g - weight1_g) + gap = (weight2_adc - weight1_adc) / (weight2_g - weight1_g) self.i2c.writeto_mem(self.unit_addr, GAP_REG, struct.pack(" None: def get_weight_int(self) -> int: """! Get the weight in grams(int).""" data = self.i2c.readfrom_mem(self.unit_addr, WEIGHT_X100_REG, 4) - return struct.unpack(" str: From 04f6c291fe951f36b29ad545a544aa06959a8339 Mon Sep 17 00:00:00 2001 From: Tinyu-Zhao Date: Wed, 2 Apr 2025 10:26:49 +0800 Subject: [PATCH 032/322] lib/unit: Add Unit PDM Support. Signed-off-by: Tinyu-Zhao --- docs/en/hardware/mic.rst | 2 + docs/en/refs/unit.pdm.ref | 5 +- docs/en/units/pdm.rst | 148 +------- docs/locales/zh_CN/LC_MESSAGES/units/pdm.po | 385 +++++++++----------- m5stack/libs/unit/pdm.py | 4 +- 5 files changed, 183 insertions(+), 361 deletions(-) diff --git a/docs/en/hardware/mic.rst b/docs/en/hardware/mic.rst index 527235b1..0aa7fbe1 100644 --- a/docs/en/hardware/mic.rst +++ b/docs/en/hardware/mic.rst @@ -1,3 +1,5 @@ +.. _hardware.Mic: + Mic === diff --git a/docs/en/refs/unit.pdm.ref b/docs/en/refs/unit.pdm.ref index 5782c8a7..6fd6a024 100644 --- a/docs/en/refs/unit.pdm.ref +++ b/docs/en/refs/unit.pdm.ref @@ -10,10 +10,7 @@ .. |record.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/pdm/record.png .. |isRecording.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/pdm/isRecording.png .. |recordWavFile.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/pdrecordWavFilem/.png -.. |get_config_boolean.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/pdm/get_config_boolean.png -.. |get_config_int.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/pdm/get_config_int.png -.. |set_config_boolean.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/pdm/set_config_boolean.png -.. |set_config_int.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/pdm/set_config_int.png +.. |config.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/pdm/config.png .. |example.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/pdm/example.png diff --git a/docs/en/units/pdm.rst b/docs/en/units/pdm.rst index 5334475c..db88308d 100644 --- a/docs/en/units/pdm.rst +++ b/docs/en/units/pdm.rst @@ -1,3 +1,5 @@ +.. _unit.PDMUnit: + PDM Unit ========== @@ -59,148 +61,4 @@ PDMUnit .. autoclass:: unit.pdm.PDMUnit :members: - .. py:method:: begin() - - Initialize the PDM microphone. - - :return: Returns True if initialization was successful, False otherwise. - :rtype: bool - - UiFlow2 Code Block: - - |begin.png| - - MicroPython Code Block: - - .. code-block:: python - - pdm.begin() - - .. py:method:: end() - - Stop the PDM microphone. - - UiFlow2 Code Block: - - |end.png| - - MicroPython Code Block: - - .. code-block:: python - - pdm.end() - - .. py:method:: record(buffer, sample_rate=16000, stereo=False) - - Record audio data into the provided buffer. - - :param bytearray buffer: Buffer to store the recorded audio data - :param int sample_rate: Sample rate in Hz (default: 16000) - :param bool stereo: True for stereo recording, False for mono (default: False) - :return: True if recording started successfully - :rtype: bool - - UiFlow2 Code Block: - - |record.png| - - MicroPython Code Block: - - .. code-block:: python - - rec_data = bytearray(16000 * 5) # 5 seconds buffer - pdm.record(rec_data, 16000, False) - - .. py:method:: isRecording() - - Check if recording is in progress. - - :return: Returns the number of bytes recorded. - - * ``0`` - Not recording - * ``1`` - Recording (Queue has available space) - * ``2`` - Recording (Queue is full) - - :rtype: int - - UiFlow2 Code Block: - - |isRecording.png| - - MicroPython Code Block: - - .. code-block:: python - - pdm.isRecording() - - .. py:method:: recordWavFile(path, rate=16000, time=5, stereo=False) - - Record audio directly to a WAV file. - - :param str path: Path to save the WAV file - :param int rate: Sample rate in Hz (default: 16000) - :param int time: Recording duration in seconds (default: 5) - :param bool stereo: True for stereo recording, False for mono (default: False) - :return: True if recording was successful - :rtype: bool - - UiFlow2 Code Block: - - |recordWavFile.png| - - MicroPython Code Block: - - .. code-block:: python - - pdm.recordWavFile("/sd/test.wav", 16000, 5, False) - - .. py:method:: config(**kwargs) - - Configure the PDM microphone parameters. - - :param kwargs: Configuration parameters - - - pin_data_in: Data input pin - - pin_ws: Word select pin - - sample_rate: Sample rate in Hz - - stereo: Stereo mode - - over_sampling: Over sampling rate - - noise_filter_level: Noise filter level - - magnification: Audio magnification - - dma_buf_len: DMA buffer length - - dma_buf_count: DMA buffer count - - task_priority: Task priority - - task_pinned_core: Task core pinning - - i2s_port: I2S port number - - UiFlow2 Code Block: - - |get_config_boolean.png| - - |get_config_int.png| - - |set_config_boolean.png| - - |set_config_int.png| - - MicroPython Code Block: - - .. code-block:: python - - pdm.config( - dma_buf_count=3, - dma_buf_len=256, - over_sampling=2, - noise_filter_level=0, - sample_rate=16000, - pin_data_in=1, - pin_ws=2, - pin_bck=-1, - pin_mck=-1, - use_adc=False, - stereo=False, - magnification=1, - task_priority=2, - task_pinned_core=255, - i2s_port=i2s_port, - ) + PDMUnit class inherits Mic class, See :ref:`hardware.Mic ` for more details. diff --git a/docs/locales/zh_CN/LC_MESSAGES/units/pdm.po b/docs/locales/zh_CN/LC_MESSAGES/units/pdm.po index 78e964ae..0e782e66 100644 --- a/docs/locales/zh_CN/LC_MESSAGES/units/pdm.po +++ b/docs/locales/zh_CN/LC_MESSAGES/units/pdm.po @@ -9,7 +9,7 @@ msgid "" msgstr "" "Project-Id-Version: UIFlow2 Programming Guide \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-03-19 10:31+0800\n" +"POT-Creation-Date: 2025-04-02 10:25+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language: zh_CN\n" @@ -20,22 +20,22 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.16.0\n" -#: ../../en/units/pdm.rst:2 ec7809f167c244ff8542254af5ab7e4b +#: ../../en/units/pdm.rst:4 ec7809f167c244ff8542254af5ab7e4b msgid "PDM Unit" msgstr "" -#: ../../en/units/pdm.rst:8 cd5ca23d0994446889d66256671e6b9f +#: ../../en/units/pdm.rst:10 cd5ca23d0994446889d66256671e6b9f msgid "" "This is the driver library of PDM Unit, which is provides a set of " "methods to control the PDM microphone. Through the I2S interface, the " "module can record audio data and save it as WAV files." msgstr "这是 PDM 单元的驱动程序库,提供了一套控制 PDM 麦克风的方法。通过 I2S 接口,该模块可记录音频数据并保存为 WAV 文件。" -#: ../../en/units/pdm.rst:11 c97375295f634bbfb0e5b0def74c900b +#: ../../en/units/pdm.rst:13 c97375295f634bbfb0e5b0def74c900b msgid "Support the following products:" msgstr "支持以下产品:" -#: ../../en/units/pdm.rst:13 275ea1761cb84f3096c46b7287787693 +#: ../../en/units/pdm.rst:15 275ea1761cb84f3096c46b7287787693 msgid "|PDM|" msgstr "" @@ -43,33 +43,30 @@ msgstr "" msgid "pdm" msgstr "" -#: ../../en/units/pdm.rst:17 860ace05097c43b380857001caaf52f2 +#: ../../en/units/pdm.rst:19 860ace05097c43b380857001caaf52f2 msgid "UiFlow2 Example" msgstr "UiFlow2 应用示例" -#: ../../en/units/pdm.rst:20 ../../en/units/pdm.rst:38 +#: ../../en/units/pdm.rst:22 ../../en/units/pdm.rst:40 #: a1a1324cb29f41179c5f4d86cfee0b72 msgid "record voice and play voice" msgstr "录音音频并播放" -#: ../../en/units/pdm.rst:22 ee23d1fb844a4a269c2ecef3aac1bc66 +#: ../../en/units/pdm.rst:24 ee23d1fb844a4a269c2ecef3aac1bc66 msgid "Open the |pdm_cores3_example.m5f2| project in UiFlow2." msgstr "在 UiFlow2 中打开 |pdm_cores3_example.m5f2| 项目。" -#: ../../en/units/pdm.rst:24 ../../en/units/pdm.rst:40 +#: ../../en/units/pdm.rst:26 ../../en/units/pdm.rst:42 #: e42d9fe55a2e4ed89aa1a5c93dc98327 msgid "This example records voice and plays voice." msgstr "这个示例展示了录音音频并播放。" -#: ../../en/units/pdm.rst:26 ../../en/units/pdm.rst:69 -#: ../../en/units/pdm.rst:83 ../../en/units/pdm.rst:103 -#: ../../en/units/pdm.rst:126 ../../en/units/pdm.rst:147 -#: ../../en/units/pdm.rst:176 a0dbd900f3254a5c9b8fd5f900aea01b of +#: ../../en/units/pdm.rst:28 a0dbd900f3254a5c9b8fd5f900aea01b of #: unit.pdm.PDMUnit:7 msgid "UiFlow2 Code Block:" msgstr "UiFlow2 代码块:" -#: ../../en/units/pdm.rst:28 ca8b74fa9d8e40878f0f2f9b7a2d8a7e +#: ../../en/units/pdm.rst:30 ca8b74fa9d8e40878f0f2f9b7a2d8a7e msgid "|example.png|" msgstr "" @@ -77,33 +74,30 @@ msgstr "" msgid "example.png" msgstr "" -#: ../../en/units/pdm.rst:30 ../../en/units/pdm.rst:48 +#: ../../en/units/pdm.rst:32 ../../en/units/pdm.rst:50 #: 3a639cad0cf04d8792361e905c49431b msgid "Example output:" msgstr "示例输出:" -#: ../../en/units/pdm.rst:32 ../../en/units/pdm.rst:50 +#: ../../en/units/pdm.rst:34 ../../en/units/pdm.rst:52 #: 8e6bc9ab1d564917bd241a947cfa5ec1 msgid "None" msgstr "" -#: ../../en/units/pdm.rst:35 5242d09884e3424f8bb0caeffc80b4b8 +#: ../../en/units/pdm.rst:37 5242d09884e3424f8bb0caeffc80b4b8 msgid "MicroPython Example" msgstr "MicroPython 应用示例" -#: ../../en/units/pdm.rst:42 ../../en/units/pdm.rst:73 -#: ../../en/units/pdm.rst:87 ../../en/units/pdm.rst:107 -#: ../../en/units/pdm.rst:130 ../../en/units/pdm.rst:151 -#: ../../en/units/pdm.rst:180 9d1a7656efc94701b801ccbf5d904724 of +#: ../../en/units/pdm.rst:44 9d1a7656efc94701b801ccbf5d904724 of #: unit.pdm.PDMUnit:11 msgid "MicroPython Code Block:" msgstr "MicroPython 代码块:" -#: ../../en/units/pdm.rst:54 3255b806cb1346d78648583f2265e357 +#: ../../en/units/pdm.rst:56 3255b806cb1346d78648583f2265e357 msgid "**API**" msgstr "" -#: ../../en/units/pdm.rst:57 8ea8240b03e34db58662a25a28582f58 +#: ../../en/units/pdm.rst:59 8ea8240b03e34db58662a25a28582f58 msgid "PDMUnit" msgstr "" @@ -115,7 +109,7 @@ msgstr "" msgid "PDM Unit class." msgstr "" -#: ../../en/units/pdm.rst 16d93ddb27344765a047a3bc468aeb53 +#: 16d93ddb27344765a047a3bc468aeb53 of unit.pdm.PDMUnit msgid "Parameters" msgstr "" @@ -123,9 +117,9 @@ msgstr "" msgid "Connect to the PDM Unit." msgstr "连接到 PDM Unit" -#: 07c9e6caa24547e1a72eff9bf6b16209 of unit.pdm.PDMUnit:4 -msgid "I2S port number." -msgstr "I2S 端口号。" +#: 626eef5ac07f4a759bf1dd4c723f76c2 of unit.pdm.PDMUnit:4 +msgid "I2S port number(0 or 1, 2 is automatic select of available ports)." +msgstr "I2S 端口号(0 或 1,2 是自动选择可用端口)" #: e4ed361fbde446e588145f61367dc4dc of unit.pdm.PDMUnit:5 msgid "Sample rate." @@ -139,214 +133,185 @@ msgstr "" msgid "init.png" msgstr "" -#: ../../en/units/pdm.rst:64 b603501567d54481a465ae3172e27fb2 -msgid "Initialize the PDM microphone." -msgstr "初始化 PDM 麦克风。" +#: ../../en/units/pdm.rst:64 e7d06e5e621748f1a7ea5b885c62aab5 +msgid "" +"PDMUnit class inherits Mic class, See :ref:`hardware.Mic ` " +"for more details." +msgstr "PDMUnit 类继承了 Mic 类,详情请参见 :ref:`hardware.Mic `" -#: ../../en/units/pdm.rst 2da41e375a5a457fac1cfde853dd1347 -msgid "Returns" -msgstr "返回" +#~ msgid "Initialize the PDM microphone." +#~ msgstr "初始化 PDM 麦克风。" -#: ../../en/units/pdm.rst:66 7df738cc5995493fae7db7fa697a8226 -msgid "Returns True if initialization was successful, False otherwise." -msgstr "如果初始化成功,返回 True,否则返回 False。" +#~ msgid "Returns" +#~ msgstr "返回" -#: ../../en/units/pdm.rst fcbcb0de3a4a4cd5b18283a6086eaa56 -msgid "Return type" -msgstr "返回类型" +#~ msgid "Returns True if initialization was successful, False otherwise." +#~ msgstr "如果初始化成功,返回 True,否则返回 False。" -#: ../../en/units/pdm.rst:71 5991615e30734cd09c7397f1e29c62f2 -msgid "|begin.png|" -msgstr "" +#~ msgid "Return type" +#~ msgstr "返回类型" -#: ../../en/refs/unit.pdm.ref:8 6e9c8156cb58435db2dfcdf61db59bd8 -msgid "begin.png" -msgstr "" +#~ msgid "|begin.png|" +#~ msgstr "" -#: ../../en/units/pdm.rst:81 0d6cd0feb1814e029767ddb07f1aee50 -msgid "Stop the PDM microphone." -msgstr "停止 PDM 麦克风。" +#~ msgid "begin.png" +#~ msgstr "" -#: ../../en/units/pdm.rst:85 6dc4d555a33a4b569a642eb1166e2876 -msgid "|end.png|" -msgstr "" +#~ msgid "Stop the PDM microphone." +#~ msgstr "停止 PDM 麦克风。" -#: ../../en/refs/unit.pdm.ref:9 6e9c8156cb58435db2dfcdf61db59bd8 -msgid "end.png" -msgstr "" +#~ msgid "|end.png|" +#~ msgstr "" -#: ../../en/units/pdm.rst:95 b6b8b652ccb040f59fee024f457e1536 -msgid "Record audio data into the provided buffer." -msgstr "将音频数据记录到提供的缓冲区中。" +#~ msgid "end.png" +#~ msgstr "" -#: ../../en/units/pdm.rst:97 9d3e5864698c45dd95d8e65b67613392 -msgid "Buffer to store the recorded audio data" -msgstr "存储记录的音频数据的缓冲区。" +#~ msgid "Record audio data into the provided buffer." +#~ msgstr "将音频数据记录到提供的缓冲区中。" -#: ../../en/units/pdm.rst:98 ../../en/units/pdm.rst:141 -#: 15de1842fe804aa5bdb64448f86722ae -msgid "Sample rate in Hz (default: 16000)" -msgstr "采样率(默认:16000)" +#~ msgid "Buffer to store the recorded audio data" +#~ msgstr "存储记录的音频数据的缓冲区。" -#: ../../en/units/pdm.rst:99 ../../en/units/pdm.rst:143 -#: 604ead0f70d84ae78dd08a7eb45bb80c -msgid "True for stereo recording, False for mono (default: False)" -msgstr "True 表示立体声录音,False 表示单声道录音(默认:False)" +#~ msgid "Sample rate in Hz (default: 16000)" +#~ msgstr "采样率(默认:16000)" -#: ../../en/units/pdm.rst:100 87d998cf34fd49ba92092c89b2cf5d85 -msgid "True if recording started successfully" -msgstr "如果录音成功开始,返回 True,否则返回 False。" +#~ msgid "True for stereo recording, False for mono (default: False)" +#~ msgstr "True 表示立体声录音,False 表示单声道录音(默认:False)" -#: ../../en/units/pdm.rst:105 409168f0d4914994a482388837b69ee4 -msgid "|record.png|" -msgstr "" +#~ msgid "True if recording started successfully" +#~ msgstr "如果录音成功开始,返回 True,否则返回 False。" -#: ../../en/refs/unit.pdm.ref:10 6e9c8156cb58435db2dfcdf61db59bd8 -msgid "record.png" -msgstr "" +#~ msgid "|record.png|" +#~ msgstr "" -#: ../../en/units/pdm.rst:116 b4b5af3faa374e43ae132ee4e40a49cd -msgid "Check if recording is in progress." -msgstr "检查录音是否正在进行。" +#~ msgid "record.png" +#~ msgstr "" -#: ../../en/units/pdm.rst:118 4a022066205048c0aecaf1c0f1896201 -msgid "" -"Returns the number of bytes recorded. * ``0`` - Not recording * ``1`` - " -"Recording (Queue has available space) * ``2`` - Recording (Queue is full)" -msgstr "返回记录的字节数。 * ``0`` - 未录音\n" -" * ``1`` - 录音(队列有可用空间)\n" -" * ``2`` - 录音(队列已满)" - -#: ../../en/units/pdm.rst:118 f880b76185f34539865fac8e7c8d9ed6 -msgid "Returns the number of bytes recorded." -msgstr "返回记录的字节数。" - -#: ../../en/units/pdm.rst:120 ffab5efcf44e401d9f1bd60ddff4616e -msgid "``0`` - Not recording" -msgstr "``0`` - 未录音" - -#: ../../en/units/pdm.rst:121 07bb85b362f840b895c0fc551d71972e -msgid "``1`` - Recording (Queue has available space)" -msgstr "``1`` - 录音(队列有可用空间)" - -#: ../../en/units/pdm.rst:122 7ca2061ec4434abdb680a993b715c2b8 -msgid "``2`` - Recording (Queue is full)" -msgstr "``2`` - 录音(队列已满)" - -#: ../../en/units/pdm.rst:128 883a02585cf9408cbf39e7bbc48af21d -msgid "|isRecording.png|" -msgstr "" +#~ msgid "Check if recording is in progress." +#~ msgstr "检查录音是否正在进行。" -#: ../../en/refs/unit.pdm.ref:11 6e9c8156cb58435db2dfcdf61db59bd8 -msgid "isRecording.png" -msgstr "" +#~ msgid "" +#~ "Returns the number of bytes recorded." +#~ " * ``0`` - Not recording * " +#~ "``1`` - Recording (Queue has available" +#~ " space) * ``2`` - Recording (Queue" +#~ " is full)" +#~ msgstr "" +#~ "返回记录的字节数。 * ``0`` - 未录音\n" +#~ " * ``1`` - 录音(队列有可用空间)\n" +#~ " * ``2`` - 录音(队列已满)" -#: ../../en/units/pdm.rst:138 ade6877086474114917dcb02ce274d1a -msgid "Record audio directly to a WAV file." -msgstr "直接将音频记录到 WAV 文件中。" +#~ msgid "Returns the number of bytes recorded." +#~ msgstr "返回记录的字节数。" -#: ../../en/units/pdm.rst:140 57ad4dd1a9744309bd148f55cf77c4b5 -msgid "Path to save the WAV file" -msgstr "保存 WAV 文件的路径" +#~ msgid "``0`` - Not recording" +#~ msgstr "``0`` - 未录音" -#: ../../en/units/pdm.rst:142 fe6d3a66ec4f417087601e5dc67f9b1a -msgid "Recording duration in seconds (default: 5)" -msgstr "录音时长(默认:5秒)" +#~ msgid "``1`` - Recording (Queue has available space)" +#~ msgstr "``1`` - 录音(队列有可用空间)" -#: ../../en/units/pdm.rst:144 6a01de3bcfec430d8dd5301fd7c534e5 -msgid "True if recording was successful" -msgstr "如果录音成功,返回 True,否则返回 False。" +#~ msgid "``2`` - Recording (Queue is full)" +#~ msgstr "``2`` - 录音(队列已满)" -#: ../../en/units/pdm.rst:149 4430bceb0717437295a46f5862f11c17 -msgid "|recordWavFile.png|" -msgstr "" +#~ msgid "|isRecording.png|" +#~ msgstr "" -#: ../../en/refs/unit.pdm.ref:12 3a064799f41a457891fdfe9e0783dc63 -msgid "recordWavFile.png" -msgstr "" +#~ msgid "isRecording.png" +#~ msgstr "" -#: ../../en/units/pdm.rst:159 b8b2df30b16946a5af129a3c1ac85857 -msgid "Configure the PDM microphone parameters." -msgstr "配置 PDM 麦克风参数。" +#~ msgid "Record audio directly to a WAV file." +#~ msgstr "直接将音频记录到 WAV 文件中。" -msgid "" -"Configuration parameters - pin_data_in: Data input pin - pin_ws: Word " -"select pin - sample_rate: Sample rate in Hz - stereo: Stereo mode - " -"over_sampling: Over sampling rate - noise_filter_level: Noise filter " -"level - magnification: Audio magnification - dma_buf_len: DMA buffer " -"length - dma_buf_count: DMA buffer count - task_priority: Task priority -" -" task_pinned_core: Task core pinning - i2s_port: I2S port number" -msgstr "" -"配置参数 - pin_data_in: 数据输入引脚\n" -" - pin_ws: 字选引脚\n" -" - sample_rate: 采样率\n" -" - stereo: 立体声模式\n" -" - over_sampling: 过采样率\n" -" - noise_filter_level: 噪声过滤级别\n" -" - magnification: 音频放大倍数\n" -" - dma_buf_len: DMA 缓冲区长度\n" -" - dma_buf_count: DMA 缓冲区计数\n" -" - task_priority: 任务优先级\n" -" - task_pinned_core: 指定任务运行的核心\n" -" - i2s_port: I2S 端口号" - -#: ../../en/units/pdm.rst:161 ea459b72faba4867b1c41d17491722ab -msgid "Configuration parameters" -msgstr "配置参数" - -#: ../../en/units/pdm.rst:163 af0a2149114041d9b3fb98cd2eee72c7 -msgid "pin_data_in: Data input pin" -msgstr "pin_data_in: 数据输入引脚" - -#: ../../en/units/pdm.rst:164 798f6c2640df4f38bcd306e21d207546 -msgid "pin_ws: Word select pin" -msgstr "pin_ws: 字选引脚" - -#: ../../en/units/pdm.rst:165 0b8619fe6f0d4e3c81dc36722471bfa8 -msgid "sample_rate: Sample rate in Hz" -msgstr "sample_rate: 采样率" - -#: ../../en/units/pdm.rst:166 7894b475c48a4514b4a2f19fbd59f5f2 -msgid "stereo: Stereo mode" -msgstr "stereo: 立体声模式" - -#: ../../en/units/pdm.rst:167 bf7e18bc48a84514bf903cc6d50090ec -msgid "over_sampling: Over sampling rate" -msgstr "over_sampling: 过采样率" - -#: ../../en/units/pdm.rst:168 baf64af13f6c4a6f8673d358c2919a23 -msgid "noise_filter_level: Noise filter level" -msgstr "noise_filter_level: 噪声过滤级别" - -#: ../../en/units/pdm.rst:169 10e25adbf1d546b6a7a1ca05259df615 -msgid "magnification: Audio magnification" -msgstr "magnification: 音频放大倍数" - -#: ../../en/units/pdm.rst:170 e970c26a35854ce78f8e8d49bc4b9756 -msgid "dma_buf_len: DMA buffer length" -msgstr "dma_buf_len: DMA 缓冲区长度" - -#: ../../en/units/pdm.rst:171 479516133b424d99b847f7493adcea1a -msgid "dma_buf_count: DMA buffer count" -msgstr "dma_buf_count: DMA 缓冲区计数" - -#: ../../en/units/pdm.rst:172 25a5f4d381014d27b950da1ab859c194 -msgid "task_priority: Task priority" -msgstr "task_priority: 任务优先级" - -#: ../../en/units/pdm.rst:173 a180450c66ec4c48ae881d3dad2be322 -msgid "task_pinned_core: Task core pinning" -msgstr "task_pinned_core: 指定任务运行的核心" - -#: ../../en/units/pdm.rst:174 3c8898aaac654c92b9ba216f7f529272 -msgid "i2s_port: I2S port number" -msgstr "i2s_port: I2S 端口号" - -#: ../../en/units/pdm.rst:178 43395ae638a04638a8a96c3ca9dfdce4 -msgid "|config.png|" -msgstr "" +#~ msgid "Path to save the WAV file" +#~ msgstr "保存 WAV 文件的路径" -#: ../../en/refs/unit.pdm.ref:13 6e9c8156cb58435db2dfcdf61db59bd8 -msgid "config.png" -msgstr "" +#~ msgid "Recording duration in seconds (default: 5)" +#~ msgstr "录音时长(默认:5秒)" + +#~ msgid "True if recording was successful" +#~ msgstr "如果录音成功,返回 True,否则返回 False。" + +#~ msgid "|recordWavFile.png|" +#~ msgstr "" + +#~ msgid "recordWavFile.png" +#~ msgstr "" + +#~ msgid "Configure the PDM microphone parameters." +#~ msgstr "配置 PDM 麦克风参数。" + +#~ msgid "" +#~ "Configuration parameters - pin_data_in: Data" +#~ " input pin - pin_ws: Word select " +#~ "pin - sample_rate: Sample rate in " +#~ "Hz - stereo: Stereo mode - " +#~ "over_sampling: Over sampling rate - " +#~ "noise_filter_level: Noise filter level - " +#~ "magnification: Audio magnification - " +#~ "dma_buf_len: DMA buffer length - " +#~ "dma_buf_count: DMA buffer count - " +#~ "task_priority: Task priority - " +#~ "task_pinned_core: Task core pinning - " +#~ "i2s_port: I2S port number" +#~ msgstr "" +#~ "配置参数 - pin_data_in: 数据输入引脚\n" +#~ " - pin_ws: 字选引脚\n" +#~ " - sample_rate: 采样率\n" +#~ " - stereo: 立体声模式\n" +#~ " - over_sampling: 过采样率\n" +#~ " - noise_filter_level: 噪声过滤级别\n" +#~ " - magnification: 音频放大倍数\n" +#~ " - dma_buf_len: DMA 缓冲区长度\n" +#~ " - dma_buf_count: DMA 缓冲区计数\n" +#~ " - task_priority: 任务优先级\n" +#~ " - task_pinned_core: 指定任务运行的核心\n" +#~ " - i2s_port: I2S 端口号" + +#~ msgid "Configuration parameters" +#~ msgstr "配置参数" + +#~ msgid "pin_data_in: Data input pin" +#~ msgstr "pin_data_in: 数据输入引脚" + +#~ msgid "pin_ws: Word select pin" +#~ msgstr "pin_ws: 字选引脚" + +#~ msgid "sample_rate: Sample rate in Hz" +#~ msgstr "sample_rate: 采样率" + +#~ msgid "stereo: Stereo mode" +#~ msgstr "stereo: 立体声模式" + +#~ msgid "over_sampling: Over sampling rate" +#~ msgstr "over_sampling: 过采样率" + +#~ msgid "noise_filter_level: Noise filter level" +#~ msgstr "noise_filter_level: 噪声过滤级别" + +#~ msgid "magnification: Audio magnification" +#~ msgstr "magnification: 音频放大倍数" + +#~ msgid "dma_buf_len: DMA buffer length" +#~ msgstr "dma_buf_len: DMA 缓冲区长度" + +#~ msgid "dma_buf_count: DMA buffer count" +#~ msgstr "dma_buf_count: DMA 缓冲区计数" + +#~ msgid "task_priority: Task priority" +#~ msgstr "task_priority: 任务优先级" + +#~ msgid "task_pinned_core: Task core pinning" +#~ msgstr "task_pinned_core: 指定任务运行的核心" + +#~ msgid "i2s_port: I2S port number" +#~ msgstr "i2s_port: I2S 端口号" + +#~ msgid "|config.png|" +#~ msgstr "" + +#~ msgid "config.png" +#~ msgstr "" + +#~ msgid "I2S port number." +#~ msgstr "I2S 端口号。" diff --git a/m5stack/libs/unit/pdm.py b/m5stack/libs/unit/pdm.py index 609acec5..bc6a48a3 100644 --- a/m5stack/libs/unit/pdm.py +++ b/m5stack/libs/unit/pdm.py @@ -9,7 +9,7 @@ class PDMUnit: """PDM Unit class. :param list | tuple port: Connect to the PDM Unit. - :param int i2s_port: I2S port number. + :param int i2s_port: I2S port number(0 or 1, 2 is automatic select of available ports). :param int sample_rate: Sample rate. UiFlow2 Code Block: @@ -66,4 +66,4 @@ def __getattr__(self, name): return getattr(self._mic, name) def deinit(self): - self._mic.deinit() + self._mic.end() From 7997db4bc6b21e32383f4ccb919c89ef159dcd3d Mon Sep 17 00:00:00 2001 From: Tinyu-Zhao Date: Wed, 2 Apr 2025 09:19:10 +0800 Subject: [PATCH 033/322] lib/hat: Add Hat SPK2 support. Signed-off-by: Tinyu-Zhao --- docs/en/hardware/speaker.rst | 2 + docs/en/hats/index.rst | 1 + docs/en/hats/speaker.rst | 2 +- docs/en/hats/speaker2.rst | 62 +++++++++ docs/en/refs/hat.speaker.ref | 2 +- docs/en/refs/hat.speaker2.ref | 18 +++ .../locales/zh_CN/LC_MESSAGES/base/speaker.po | 120 +++++++++--------- .../locales/zh_CN/LC_MESSAGES/hats/speaker.po | 84 ++++-------- .../zh_CN/LC_MESSAGES/hats/speaker2.po | 98 ++++++++++++++ .../speaker2_stickcplus2_example.m5f2 | 1 + .../speaker2/speaker2_stickcplus2_example.py | 54 ++++++++ m5stack/libs/hat/speaker2.py | 9 +- 12 files changed, 327 insertions(+), 126 deletions(-) create mode 100644 docs/en/hats/speaker2.rst create mode 100644 docs/en/refs/hat.speaker2.ref create mode 100644 docs/locales/zh_CN/LC_MESSAGES/hats/speaker2.po create mode 100644 examples/hat/speaker2/speaker2_stickcplus2_example.m5f2 create mode 100644 examples/hat/speaker2/speaker2_stickcplus2_example.py diff --git a/docs/en/hardware/speaker.rst b/docs/en/hardware/speaker.rst index f1e5afcb..bf9d5e77 100644 --- a/docs/en/hardware/speaker.rst +++ b/docs/en/hardware/speaker.rst @@ -1,3 +1,5 @@ +.. _hardware.Speaker: + Speaker ======= diff --git a/docs/en/hats/index.rst b/docs/en/hats/index.rst index a54da6bf..c014fcdd 100644 --- a/docs/en/hats/index.rst +++ b/docs/en/hats/index.rst @@ -22,6 +22,7 @@ HAT servo.rst servo8.rst speaker.rst + speaker2.rst thermal.rst tof.rst vibrator.rst diff --git a/docs/en/hats/speaker.rst b/docs/en/hats/speaker.rst index e7d181fc..a222607d 100644 --- a/docs/en/hats/speaker.rst +++ b/docs/en/hats/speaker.rst @@ -16,7 +16,7 @@ Below is the detailed support for Speaker on the host: :align: center +-----------------+-------------------+ - |Controller | Atomic Echo Base | + |Controller | Speaker Hat | +=================+===================+ | CoreInk | |S| | +-----------------+-------------------+ diff --git a/docs/en/hats/speaker2.rst b/docs/en/hats/speaker2.rst new file mode 100644 index 00000000..99a936e4 --- /dev/null +++ b/docs/en/hats/speaker2.rst @@ -0,0 +1,62 @@ +.. _hat.Speaker2: + +Speaker2 Hat +============= + +.. sku: U055-B + +.. include:: ../refs/hat.speaker2.ref + +This is the driver library of Speaker2 Hat, which is provides a set of methods to control the speaker. + +Support the following products: + + |Speaker2| + + +UiFlow2 Example +--------------- +play audio +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Open the |speaker2_stickcplus2_example.m5f2| project in UiFlow2. + +This example demonstrates how to play audio. + +UiFlow2 Code Block: + + |example.png| + +Example output: + + None + +MicroPython Example +------------------- + +play audio +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +This example demonstrates how to play audio. + +MicroPython Code Block: + + .. literalinclude:: ../../../examples/hat/speaker2/speaker2_stickcplus2_example.py + :language: python + :linenos: + +Example output: + + None + + +**API** +------- + +Speaker2 +^^^^^^^^^ + +.. autoclass:: unit.speaker2.Speaker2 + :members: + + Speaker2 class inherits Speaker class, See :ref:`hardware.Speaker.Methods ` for more details. diff --git a/docs/en/refs/hat.speaker.ref b/docs/en/refs/hat.speaker.ref index 54f43e05..95580d71 100644 --- a/docs/en/refs/hat.speaker.ref +++ b/docs/en/refs/hat.speaker.ref @@ -11,7 +11,7 @@ .. |stickc_plus2_speaker_example.m5f2| raw:: html stickc_plus2_speaker_example.m5f2 diff --git a/docs/en/refs/hat.speaker2.ref b/docs/en/refs/hat.speaker2.ref new file mode 100644 index 00000000..92bab251 --- /dev/null +++ b/docs/en/refs/hat.speaker2.ref @@ -0,0 +1,18 @@ + +.. |Speaker2| image:: https://static-cdn.m5stack.com/resource/docs/products/hat/Hat-SPK2/img-20b44754-2357-4fcd-be78-77f1107f89ec.webp + :target: https://docs.m5stack.com/en/hat/Hat-SPK2 + :height: 200px + :width: 200px + +.. |init.png| image:: https://static-cdn.m5stack.com/mpy_docs/hat/speaker2/init.png + +.. |example.png| image:: https://static-cdn.m5stack.com/mpy_docs/hat/speaker2/example.png + +.. |stickc_plus2_speaker_example.m5f2| raw:: html + + + stickc_plus2_speaker_example.m5f2 + diff --git a/docs/locales/zh_CN/LC_MESSAGES/base/speaker.po b/docs/locales/zh_CN/LC_MESSAGES/base/speaker.po index 57f694b3..53eda153 100644 --- a/docs/locales/zh_CN/LC_MESSAGES/base/speaker.po +++ b/docs/locales/zh_CN/LC_MESSAGES/base/speaker.po @@ -9,7 +9,7 @@ msgid "" msgstr "" "Project-Id-Version: UIFlow2 Programming Guide \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-01-17 14:57+0800\n" +"POT-Creation-Date: 2025-03-20 14:46+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language: zh_CN\n" @@ -21,214 +21,216 @@ msgstr "" "Generated-By: Babel 2.16.0\n" #: ../../en/base/speaker.rst:2 ../../en/refs/base.speaker.ref -#: 0774dd7c9be64cdfbd2db6327a0ccb4d 2d9feb76a4dc44699b914ffe25d102b0 +#: 58db4bf7a4ff4ce39d433a7dde154e93 a810f4af25ea47a99b8d0fa42f2a2a63 msgid "Atomic Speaker Base" msgstr "" -#: ../../en/base/speaker.rst:8 72589ffbc9ca4329bfb1d31f085d20c2 +#: ../../en/base/speaker.rst:8 0dc3d0a2c81d4cd6940d07d8a878e880 msgid "The following products are supported:" msgstr "" -#: ../../en/base/speaker.rst:10 ed9124e29aa5407b848a6770f840c205 +#: ../../en/base/speaker.rst:10 b6dc03d72efe444dbc045320c79b0ae4 msgid "|Atomic Speaker Base|" msgstr "" -#: ../../en/base/speaker.rst:12 41ad9440bed24cf8a5361a219c0786dc +#: ../../en/base/speaker.rst:12 cd23240789954103ab4b9df847fb8cbe msgid "Below is the detailed support for Speaker on the host:" msgstr "" -#: ../../en/base/speaker.rst:19 4498850b6b3448c1a71a39eca315d7e1 +#: ../../en/base/speaker.rst:19 10395eee8ef041bba80e1a0f9ce90fe9 msgid "Controller" msgstr "" -#: ../../en/base/speaker.rst:19 6c89d1a94fbd49c58b9ad04b37e42e17 +#: ../../en/base/speaker.rst:19 ab919c00b2214c26bae70f6f0bb1ea0c msgid "NS4168" msgstr "" -#: ../../en/base/speaker.rst:19 90d894932e9c4b528e9393ba057c7ed6 +#: ../../en/base/speaker.rst:19 a62766ffb88c4fc08fc97c4ed0acce82 msgid "SDCard" msgstr "" -#: ../../en/base/speaker.rst:21 c070158016454a2e862bb6ca0dbfd955 +#: ../../en/base/speaker.rst:21 32bc6807dd044f959ef0ba4250385cfd msgid "Atom Echo" msgstr "" #: ../../en/base/speaker.rst:21 ../../en/base/speaker.rst:25 -#: ../../en/base/speaker.rst:27 14aa1a2efb034b6f81f85a696ed0b525 -#: 18242f475eee4b0f9c31c812827c3638 295e19ae234148b6bcc8167774957fb2 -#: c1431edfc01c4e099114dc61bf4421ff +#: ../../en/base/speaker.rst:27 64459bafd58c4be68972fcb5a6ebe050 +#: 8021bfed7a844d66bea23eff4b892b77 95c170b071f74ee0a796167d1da5cee4 +#: bb374cd7c0e84a9caab06695f13d1016 msgid "|O|" msgstr "" -#: ../../en/base/speaker.rst:23 a1b0068497b24512b3e8b0bcd9cf0ca0 +#: ../../en/base/speaker.rst:23 db9b3ac625a7490a8969186d5b3e6a37 msgid "Atom Lite" msgstr "" #: ../../en/base/speaker.rst:23 ../../en/base/speaker.rst:25 #: ../../en/base/speaker.rst:27 ../../en/base/speaker.rst:29 #: ../../en/base/speaker.rst:31 ../../en/base/speaker.rst:33 -#: ../../en/base/speaker.rst:35 25731979fc0145ee8d5e0c8b7fba9900 -#: 2ef4496c4b814b47b1e0f1167e59f979 34c938ebc986427383823ba840263825 -#: 4d26790a2ae44bb38abf5543a6b7abde 50a7966c9fd7499097f6e97836168065 -#: 55e8a7f49751497c96c8864e85fc0bdc 70131b6ceddc47deb068212626a7cbc2 -#: 79b596c618c94763bff14861a4a33fff b06b075782dd439da7a5672f71a4e921 -#: b6d34a3a288e4463864a86d8bbd91f7f c29aaaf2b53740ffae07c007b9b04338 -#: d818310e9ea5433998c8afbbc5c94b53 +#: ../../en/base/speaker.rst:35 19805fc60a554e3ba15d01888907a8c8 +#: 1beac97af5574f809b9723a4c4f7635e 2b4650c3d9a64a6eb5798f1c8c251ed0 +#: 5f4e1d518fa84b46bf378b93a16f0cf8 63139e8ecc404d14a4859879f15d2069 +#: 8541bca646ee415b9ea37cf9cbd92369 b5a8a4f5063144ffa54b991457ce49af +#: be195a688302485096dbb090d7688296 c67013026f724b51b6ada6224b40f4e6 +#: e92bfbcf826c480386ec249e16e4bcd2 eb3b005feb3a4ded8cf2434dc95a62f2 +#: f103d83faea04c7c840feff99217b102 msgid "|S|" msgstr "" -#: ../../en/base/speaker.rst:25 aa3c5442e2d141b8bc19efaba7e78455 +#: ../../en/base/speaker.rst:25 39e5051360574cf19b4b501861c40605 msgid "Atom Matrix" msgstr "" -#: ../../en/base/speaker.rst:27 314301f4e37f4dec86ecac6e11fc7a1c +#: ../../en/base/speaker.rst:27 f3da5858860c4c0a967068c2c1de51bc msgid "AtomS3" msgstr "" -#: ../../en/base/speaker.rst:29 febbbe14e6e6471b8bc4fdaaea78ec1f +#: ../../en/base/speaker.rst:29 7e545e9963d14a498d62e1eb6266828d msgid "AtomS3 Lite" msgstr "" -#: ../../en/base/speaker.rst:31 5ed5334d0f3141df995adb610b6e9351 +#: ../../en/base/speaker.rst:31 50e125b001394d84abe546769506846f msgid "AtomS3R" msgstr "" -#: ../../en/base/speaker.rst:33 32a6e58c351f48b48864520de714e0e2 +#: ../../en/base/speaker.rst:33 c85a62f4e72343b18ad38c79e01da709 msgid "AtomS3R-CAM" msgstr "" -#: ../../en/base/speaker.rst:35 0c323c36517f48b2b452cb429b36cd4a +#: ../../en/base/speaker.rst:35 a7ad1751d82247f4b76763f87a3f6d16 msgid "AtomS3R-Ext" msgstr "" -#: ../../en/base/speaker.rst:41 5056c0cbf182482b9380132daf2efed4 +#: ../../en/base/speaker.rst:41 78f7c7498f624562af0c242baa57f4bf msgid "|S|: Supported." msgstr "" -#: ../../en/base/speaker.rst:43 f9cf4f82e6c04be5ac5d06782c335655 +#: ../../en/base/speaker.rst:43 804a7a7783c34a26ad06b10063404428 msgid "|O|: Optional, It conflicts with some internal resource of the host." msgstr "" -#: ../../en/base/speaker.rst:46 61783e6bc8dd4720aaebf80ac094c77f +#: ../../en/base/speaker.rst:46 ea021465b5414340a204cabbe6a1607f msgid "Micropython Example:" msgstr "" -#: ../../en/base/speaker.rst:53 406c2b7174c84f82bb0fb006ec8c1518 +#: ../../en/base/speaker.rst:53 03516d9d548a4564b0ff4863553d8665 msgid "UIFLOW2 Example:" msgstr "" -#: ../../en/base/speaker.rst:55 fef5a6f653244fffa048ce8c89c93c56 +#: ../../en/base/speaker.rst:55 f47deb19e45e4311b37de94bdcc503d4 msgid "|example.png|" msgstr "" -#: ../../en/refs/base.speaker.ref:9 02a941918d7f451cb6434213ff6e0b54 +#: ../../en/refs/base.speaker.ref:9 b33149ad45d742549e28716cadcf9361 msgid "example.png" msgstr "" -#: ../../en/base/speaker.rst:60 e30e4dca493e470d8d241137ea44e728 +#: ../../en/base/speaker.rst:60 178f13b84a9f4dbf933de24ffebea193 msgid "|atoms3_speaker_example.m5f2|" msgstr "" -#: ../../en/base/speaker.rst:64 8cefb4b3806c4aa18b556eb053356c8c -msgid "class ATOMEchoBase" +#: ../../en/base/speaker.rst:64 b97b28535b264394904c8393e9e2e79d +msgid "class SpeakerBase" msgstr "" #: ../../en/base/speaker.rst:67 ../../en/base/speaker.rst:99 -#: 1f7af86f06d644f895b5ebb35f5676ea c1bb5ac768794a0fa52beccd89e551d7 +#: f568267df4fc4738a5b5c65f8fa31c60 msgid "Constructors" msgstr "" -#: ../../en/base/speaker.rst:71 1f44b4d1f3f345d8b83bd9ee2d6a875d +#: ../../en/base/speaker.rst:71 11507e65e6fa447a8a68af67f512cdc4 msgid "Create an SpeakerBase object." msgstr "" -#: ../../en/base/speaker.rst 41266395555d4130acb0882e7b54b90b -#: b38c67b3797e43d09fe126a568e4be64 +#: ../../en/base/speaker.rst af24adeac55042aeacb92b1a19191d76 msgid "Parameters" msgstr "" -#: ../../en/base/speaker.rst:73 3ca8f6b919284807bed25afaccf4a5bf +#: ../../en/base/speaker.rst:73 ca20e21fb349412f9b3281ebdf3ee00b msgid "The I2S port number." msgstr "" -#: ../../en/base/speaker.rst:74 2610f6db64914df28cfc0e4e4d2e8022 +#: ../../en/base/speaker.rst:74 439271fb8844489db0891d4dc7eb5f3f msgid "The I2S SCK pin." msgstr "" -#: ../../en/base/speaker.rst:75 56b7bc64738b4285811ddc595f15b45a +#: ../../en/base/speaker.rst:75 8b6dfeb79ac24f5d846a010dd2216333 msgid "The I2S WS pin." msgstr "" -#: ../../en/base/speaker.rst:76 89928edf67034853ab8a1e28bc7ea6a0 +#: ../../en/base/speaker.rst:76 fb46e9f06313475ca100dff46b488f6f msgid "The I2S DI pin." msgstr "" #: ../../en/base/speaker.rst:78 ../../en/base/speaker.rst:113 -#: 0a6297c470354a8e87de3876eb4d9283 3afd26273e1f47f99f08e4f1d8def8c8 +#: 4df7ff4016654af4873b89db824337d4 msgid "UIFLOW2:" msgstr "" -#: ../../en/base/speaker.rst:80 44ad00ceeace4ef7abab1b39f0e1bcc1 +#: ../../en/base/speaker.rst:80 e68cc5d99a4d4b5a8d849eafc4dfec21 msgid "|SpeakerBase.png|" msgstr "" -#: ../../en/refs/base.speaker.ref:7 9f8e057ec4564d63a0ed64cc4e718e4b +#: ../../en/refs/base.speaker.ref:7 a6b21da3cc4a4c7fa837c66ed7e7cac2 msgid "SpeakerBase.png" msgstr "" #: ../../en/base/speaker.rst:82 ../../en/base/speaker.rst:117 -#: 1f7a237754a0466c9eeece966eb0780f 8a847053a97648eba2a87a6a66511c70 +#: 63492f4725604a92982a1041b9b629c1 msgid "Micropython::" msgstr "" -#: ../../en/base/speaker.rst:92 5d3fc10a212e4f9a873647ecc099e663 +#: ../../en/base/speaker.rst:92 a02fe5dbb3364a28987015a28ba73180 msgid "" "SpeakerBase class inherits M5.Speaker class, See " ":ref:`hardware.Speaker.Methods ` for more " "details." msgstr "" -#: ../../en/base/speaker.rst:96 f812234778164fbe88b3bef8ef50cdc3 +#: ../../en/base/speaker.rst:96 4c24373b68584ba5ab6805c5ed30e76d msgid "class SDCard" msgstr "" -#: ../../en/base/speaker.rst:103 b8f96547ef584d78a635d763b015ae6c +#: ../../en/base/speaker.rst:103 03273ad02e7a4ff8835258ef29d9913e msgid "Create an SDCard object." msgstr "" -#: ../../en/base/speaker.rst:105 688294bfe4ae4dbd989a962be878f03b +#: ../../en/base/speaker.rst:105 636ef61cf180427083da399850bd997b msgid "The slot number of the SD card. Default is 2." msgstr "" -#: ../../en/base/speaker.rst:106 776b55ea2a8641c8b11cfc112b823c53 +#: ../../en/base/speaker.rst:106 bc636cdd74b644f58b1f1059facb39cd msgid "width selects the bus width for the SD/MMC interface." msgstr "" -#: ../../en/base/speaker.rst:107 283695f26709447fa981d9b6fb7cd0b3 +#: ../../en/base/speaker.rst:107 2293f3181335499488dbe237ee28b333 msgid "sck can be used to specify an SPI clock pin." msgstr "" -#: ../../en/base/speaker.rst:108 fa2534d83ad94ca48d7719f0f5629155 +#: ../../en/base/speaker.rst:108 0a795039ccdb46e7aaa39b78c80976ad msgid "miso can be used to specify an SPI miso pin." msgstr "" -#: ../../en/base/speaker.rst:109 f673ec8ba00247769ed1bfb160bc5ae0 +#: ../../en/base/speaker.rst:109 82f3378a0ea44976ad6af69a094108e2 msgid "mosi can be used to specify an SPI mosi pin." msgstr "" -#: ../../en/base/speaker.rst:110 b1143a544dc44a1f948432b35a466dc7 +#: ../../en/base/speaker.rst:110 c8c84009462547f780f4189a216a32ee msgid "cs can be used to specify an SPI chip select pin." msgstr "" -#: ../../en/base/speaker.rst:111 2ff18b6040de4fb28da3d402f1493799 +#: ../../en/base/speaker.rst:111 7956b11f0f094829ad1acdbd3ae9297f msgid "freq selects the SD/MMC interface frequency in Hz." msgstr "" -#: ../../en/base/speaker.rst:115 6eaea4f1d3294d12839c06afec366fdf +#: ../../en/base/speaker.rst:115 fc47d9e83e8f490aaa1fee39a7c2abf4 msgid "|SDCard.png|" msgstr "" -#: ../../en/refs/base.speaker.ref:11 0c4c838c95af4377add3458dcbc3ffb7 +#: ../../en/refs/base.speaker.ref:11 8b4d723768744e57a5cbbefd6226b46f msgid "SDCard.png" msgstr "" +#~ msgid "class ATOMEchoBase" +#~ msgstr "" + diff --git a/docs/locales/zh_CN/LC_MESSAGES/hats/speaker.po b/docs/locales/zh_CN/LC_MESSAGES/hats/speaker.po index 26892734..6ea2dc82 100644 --- a/docs/locales/zh_CN/LC_MESSAGES/hats/speaker.po +++ b/docs/locales/zh_CN/LC_MESSAGES/hats/speaker.po @@ -9,7 +9,7 @@ msgid "" msgstr "" "Project-Id-Version: UIFlow2 Programming Guide \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-01-17 14:57+0800\n" +"POT-Creation-Date: 2025-03-28 10:32+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language: zh_CN\n" @@ -21,107 +21,73 @@ msgstr "" "Generated-By: Babel 2.16.0\n" #: ../../en/hats/speaker.rst:2 ../../en/refs/hat.speaker.ref -#: 33f06417a1984a04be96fe8cf1ffb0e6 b4776d87ed594f1c8c7d7ec484779a01 +#: 653a1bec8f9e43319fdacc407c32605c 9e385a87d36645bfab19869ecff4161c msgid "Speaker Hat" msgstr "" -#: ../../en/hats/speaker.rst:8 cc0b80fc60ee487fa604da15ad34a01f +#: ../../en/hats/speaker.rst:8 f28e0f98ee62463c933c8a22a990f10e msgid "The following products are supported:" -msgstr "" +msgstr "支持以下产品:" -#: ../../en/hats/speaker.rst:10 9915737c9225466c82cc094a1b29c0ad +#: ../../en/hats/speaker.rst:10 fa72906a5bdb4004b0eb255f39cbe05b msgid "|Speaker Hat|" msgstr "" -#: ../../en/hats/speaker.rst:12 0af9c838dab1434785384e3af1003097 -msgid "Below is the detailed support for Speaker on the host:" -msgstr "" - -#: ../../en/hats/speaker.rst:19 fc8c4277d3954207a23a9c5da42d51d5 -msgid "Controller" -msgstr "" - -#: ../../en/hats/speaker.rst:19 591dd911349c4a91a289af7fe9d7a982 -msgid "Atomic Echo Base" -msgstr "" - -#: ../../en/hats/speaker.rst:21 c693cb3526394af0b20039311b32db31 -msgid "CoreInk" -msgstr "" - -#: ../../en/hats/speaker.rst:21 ../../en/hats/speaker.rst:23 -#: ../../en/hats/speaker.rst:25 ../../en/hats/speaker.rst:27 -#: 1b2fcd27a3a344fa9e13cd4f7817e3cc 2a953feb6a5b4b4cb13f6bfe617c2536 -#: 4a8c7c9485f240de93c528a098816c0f 7b697dac0ce848359c5c165700f7d3cf -msgid "|S|" -msgstr "" - -#: ../../en/hats/speaker.rst:23 fb40b38843f9438fb07731b0a875452d -msgid "StickC" -msgstr "" - -#: ../../en/hats/speaker.rst:25 a5a48c4eafa544e1be14baf81e67a086 -msgid "StickC PLUS" -msgstr "" - -#: ../../en/hats/speaker.rst:27 44b102593c6848969d6212673ba198c8 -msgid "StickC PLUS2" -msgstr "" - -#: ../../en/hats/speaker.rst:33 acab3527cc1e4f8a92f3f4d19087efa9 +#: ../../en/hats/speaker.rst:12 3d9573ea16fd4e30927df330c304ca07 msgid "Micropython Example:" -msgstr "" +msgstr "Micropython 示例:" -#: ../../en/hats/speaker.rst:40 c125467acc504b94b0cada7f32a58276 +#: ../../en/hats/speaker.rst:19 61bf4e6994ae43c99571f817d8e9d303 msgid "UIFLOW2 Example:" -msgstr "" +msgstr "UiFlow2 示例:" -#: ../../en/hats/speaker.rst:42 725651a21a5549a4ac0ecbc460f52de7 +#: ../../en/hats/speaker.rst:21 efb84fe8bbf84115a467be882609a77a msgid "|example.png|" msgstr "" -#: ../../en/refs/hat.speaker.ref:9 04faf518014f49bea9b27df1964145c8 +#: ../../en/refs/hat.speaker.ref:9 e88583419e814475ad6504846d31b546 msgid "example.png" msgstr "" -#: ../../en/hats/speaker.rst:47 11a938451dd84a27bfa22f62384141a4 +#: ../../en/hats/speaker.rst:26 e9b02cf6407e491987fe2472ac3dddf0 msgid "|stickc_plus2_speaker_example.m5f2|" msgstr "" -#: ../../en/hats/speaker.rst:49 db00eb6465814a20a24122311a38c149 +#: ../../en/hats/speaker.rst:28 8010119784c64af28987c6c808795471 msgid "" ":download:`poweron_2_5s.wav " "<../../../examples/hardware/speaker/poweron_2_5s.wav>`" msgstr "" -#: ../../en/hats/speaker.rst:52 d3758ddceb354df984b46dc39157af0b +#: ../../en/hats/speaker.rst:31 120c375a5b5b4114a7dee67ed66b3627 msgid "class SpeakerHat" msgstr "" -#: ../../en/hats/speaker.rst:55 10c18120f724484f9bdc66ce368a447e +#: ../../en/hats/speaker.rst:34 9bc980b978e64ef08fb1976b4e3785d9 msgid "Constructors" -msgstr "" +msgstr "构造函数" -#: ../../en/hats/speaker.rst:59 2122e9e4bff54d69969794067db7678b +#: ../../en/hats/speaker.rst:38 80e5e252fd3945448eb21b9c888a58d4 msgid "Create an SpeakerHat object." -msgstr "" +msgstr "创建一个 SpeakerHat 对象。" -#: ../../en/hats/speaker.rst:61 733e37c7b60b4b9e81f1fb41a69ccde4 +#: ../../en/hats/speaker.rst:40 e6a1c84d1a974bbc9d20b0a17d93a0e8 msgid "UIFLOW2:" msgstr "" -#: ../../en/hats/speaker.rst:63 e6296c40c7e34610ad290c6df7174897 +#: ../../en/hats/speaker.rst:42 274363c3c8f2453cabeae325687ed841 msgid "|init.png|" msgstr "" -#: ../../en/refs/hat.speaker.ref:7 ae558db01e9a4da5bcd14021b5157750 +#: ../../en/refs/hat.speaker.ref:7 36b2712286fe49e09cfb300e2cb8682f msgid "init.png" msgstr "" -#: ../../en/hats/speaker.rst:65 1e1970a362e3424c96a0f97588b2f834 +#: ../../en/hats/speaker.rst:44 b801228738794be5bf660ee513f06999 msgid "" "SpeakerHat class inherits M5.Speaker class, See " ":ref:`hardware.Speaker.Methods ` for more " "details." -msgstr "" - +msgstr "SpeakerHat 类继承 M5.Speaker 类,请参阅 " +":ref:`hardware.Speaker.Methods ` 了解更多" +"详细信息。" diff --git a/docs/locales/zh_CN/LC_MESSAGES/hats/speaker2.po b/docs/locales/zh_CN/LC_MESSAGES/hats/speaker2.po new file mode 100644 index 00000000..e8d465ce --- /dev/null +++ b/docs/locales/zh_CN/LC_MESSAGES/hats/speaker2.po @@ -0,0 +1,98 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2016 - 2025 M5Stack Technology Co., Ltd +# This file is distributed under the same license as the UIFlow2 Programming +# Guide package. +# FIRST AUTHOR , 2025. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: UIFlow2 Programming Guide \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-03-28 10:45+0800\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: zh_CN\n" +"Language-Team: zh_CN \n" +"Plural-Forms: nplurals=1; plural=0;\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.16.0\n" + +#: ../../en/hats/speaker2.rst:4 45b622ba3ee34ab285f09be2bd010114 +msgid "Speaker2 Hat" +msgstr "" + +#: ../../en/hats/speaker2.rst:10 6abec3ed2e7143b5a8aec7dd35b0e191 +msgid "" +"This is the driver library of Speaker2 Hat, which is provides a set of " +"methods to control the speaker." +msgstr "这是 Speaker2 Hat 的驱动库,提供了控制扬声器的功能。" + +#: ../../en/hats/speaker2.rst:12 b05b8530731042bfa41df41fd6478d64 +msgid "Support the following products:" +msgstr "支持以下产品:" + +#: ../../en/hats/speaker2.rst:14 6b83ba641fe8435b9781655d89dda0c0 +msgid "|Speaker2|" +msgstr "" + +#: ../../en/hats/speaker2.rst:18 96f600cea6fb4d5287288e214fd92d7a +msgid "UiFlow2 Example" +msgstr "UiFlow2 示例" + +#: ../../en/hats/speaker2.rst:20 ../../en/hats/speaker2.rst:38 +#: 1cd5924d4f424f78b819dd9feb880ed1 30dfca6bf7314b5e9184df71a8c39aba +msgid "play audio" +msgstr "播放音频" + +#: ../../en/hats/speaker2.rst:22 8d4619e62df54eb0a14ecc61a6714a4f +msgid "Open the |speaker2_stickcplus2_example.m5f2| project in UiFlow2." +msgstr "在 UiFlow2 中打开 |speaker2_stickcplus2_example.m5f2| 项目。" + +#: ../../en/hats/speaker2.rst:24 ../../en/hats/speaker2.rst:40 +#: 52c1d4bfdacf438cbd0447787deaa902 5a601875a81649e78b1980163c360a51 +msgid "This example demonstrates how to play audio." +msgstr "这个示例演示了如何播放音频。" + +#: ../../en/hats/speaker2.rst:26 cc2bf49c6d7247af8123a1928a8697b2 +msgid "UiFlow2 Code Block:" +msgstr "UiFlow2 代码块:" + +#: ../../en/hats/speaker2.rst:28 7ae223dccfae46e18b062f59767929b1 +msgid "|example.png|" +msgstr "" + +#: ../../en/hats/speaker2.rst:30 ../../en/hats/speaker2.rst:48 +#: 08ff9b60ad434e5f8686485ec956d547 57a31e0bd37f4a6292c59428ebd38f89 +msgid "Example output:" +msgstr "示例输出:" + +#: ../../en/hats/speaker2.rst:32 ../../en/hats/speaker2.rst:50 +#: 0ef4f74f90e54aa09980dcb757822bd1 d3ef95454f0749569d137996d556d919 +msgid "None" +msgstr "" + +#: ../../en/hats/speaker2.rst:35 7924eb04b76243a6a1b9d543a02f857e +msgid "MicroPython Example" +msgstr "MicroPython 示例" + +#: ../../en/hats/speaker2.rst:42 75c333870b5f414984bb902a3c561310 +msgid "MicroPython Code Block:" +msgstr "MicroPython 代码块:" + +#: ../../en/hats/speaker2.rst:54 8771d5c5fc6e4a218b931c3022ceb81d +msgid "**API**" +msgstr "" + +#: ../../en/hats/speaker2.rst:57 5591946668d945e19a55281fd61bc1fc +msgid "Speaker2" +msgstr "" + +#: ../../en/hats/speaker2.rst:62 de42c8fe346245c696806a6139542f66 +msgid "" +"Speaker2 class inherits Speaker class, See :ref:`hardware.Speaker.Methods" +" ` for more details." +msgstr "Speaker2 类继承 Speaker 类,请参阅 :ref:`hardware.Speaker.Methods" +" ` 了解更多详细信息。" diff --git a/examples/hat/speaker2/speaker2_stickcplus2_example.m5f2 b/examples/hat/speaker2/speaker2_stickcplus2_example.m5f2 new file mode 100644 index 00000000..892cb9ff --- /dev/null +++ b/examples/hat/speaker2/speaker2_stickcplus2_example.m5f2 @@ -0,0 +1 @@ +{"version":"V2.0","versionNumber":"V2.2.4","type":"stickc-plus2","components":[{"name":"screen","type":"screen","layer":0,"screenId":"builtin","screenName":"","id":"__stickc-plus2_screen","createTime":1743126601996,"x":0,"y":0,"width":135,"height":240,"backgroundColor":"#000000","size":0,"isSelected":true},{"name":"title1","type":"title","layer":1,"screenId":"builtin","screenName":"","id":"b8HaJp=OAaK*ZOpS","createTime":1743128602908,"x":0,"y":0,"color":"#ffffff","backgroundColor":"#0000FF","text":"SPK2 StickcPlus2 e.g.","textOffset":3,"font":"Widgets.FONTS.DejaVu18","isSelected":false},{"name":"label0","type":"label","layer":1,"screenId":"builtin","screenName":"","id":"rD4p7itUVBfbTpD8","createTime":1743130585914,"x":1,"y":39,"color":"#ffffff","backgroundColor":"#222222","text":"Press BtnA to Beep","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false,"width":181,"height":21},{"name":"label1","type":"label","layer":2,"screenId":"builtin","screenName":"","id":"t^7&dZl-XUiaUUYC","createTime":1743130717212,"x":1,"y":74,"color":"#ffffff","backgroundColor":"#222222","text":"Press BtnB to play wav","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false,"width":58,"height":21}],"resources":[{"hardware":["hardware_button","hardware_pin_button","imu","speaker","ir"]},{"hat":["hat_spk2"]}],"units":[],"hats":[{"type":"hat_spk2","name":"hat_spk2_0","id":"r=ku+keopJXPw=wb","createTime":1743130499412,"portList":["HAT"],"portType":"HAT","initBlockType":"hat_spk2_init","initBlockId":"e0*(6^M5c{XUrphj6]DC"}],"bases":[],"i2cs":[],"blockly":"true3builtinhat_spk2_0hat_spk2_0100trueBtnAhat_spk2_02000100BtnBhat_spk2_0flashpoweron_2_5s.wav","screen":[{"simulationName":"Built-in","type":"builtin","width":135,"height":240,"scale":0.92,"screenName":"","blockId":"","screenColorType":0,"id":"builtin","createTime":1743126601991}],"logicWhenNum":0,"customList":[]} \ No newline at end of file diff --git a/examples/hat/speaker2/speaker2_stickcplus2_example.py b/examples/hat/speaker2/speaker2_stickcplus2_example.py new file mode 100644 index 00000000..677adeba --- /dev/null +++ b/examples/hat/speaker2/speaker2_stickcplus2_example.py @@ -0,0 +1,54 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import os, sys, io +import M5 +from M5 import * +from hat import Speaker2Hat + + +title1 = None +label0 = None +label1 = None +hat_spk2_0 = None + + +def setup(): + global title1, label0, label1, hat_spk2_0 + + Widgets.setRotation(3) + M5.begin() + title1 = Widgets.Title("SPK2 StickcPlus2 e.g.", 3, 0xFFFFFF, 0x0000FF, Widgets.FONTS.DejaVu18) + label0 = Widgets.Label( + "Press BtnA to Beep", 1, 39, 1.0, 0xFFFFFF, 0x222222, Widgets.FONTS.DejaVu18 + ) + label1 = Widgets.Label( + "Press BtnB to play wav", 1, 74, 1.0, 0xFFFFFF, 0x222222, Widgets.FONTS.DejaVu18 + ) + + hat_spk2_0 = Speaker2Hat((26, 0)) + hat_spk2_0.setVolumePercentage(1) + + +def loop(): + global title1, label0, label1, hat_spk2_0 + M5.update() + if BtnA.wasPressed(): + hat_spk2_0.tone(2000, 100) + if BtnB.wasPressed(): + hat_spk2_0.playWavFile("/flash/res/audio/poweron_2_5s.wav") + + +if __name__ == "__main__": + try: + setup() + while True: + loop() + except (Exception, KeyboardInterrupt) as e: + try: + from utility import print_error_msg + + print_error_msg(e) + except ImportError: + print("please update to latest firmware") diff --git a/m5stack/libs/hat/speaker2.py b/m5stack/libs/hat/speaker2.py index 87d994e0..8353d16c 100644 --- a/m5stack/libs/hat/speaker2.py +++ b/m5stack/libs/hat/speaker2.py @@ -2,23 +2,20 @@ class Speaker2Hat: - def __new__(cls): + def __new__(cls, *args, **kwargs): spk = M5.createSpeaker() spk.config( pin_data_out=25, pin_bck=26, pin_ws=0, sample_rate=48000, - stereo=False, - buzzer=False, - use_dac=False, - dac_zero_level=0, + stereo=True, magnification=16, dma_buf_len=256, dma_buf_count=8, task_priority=2, task_pinned_core=255, - i2s_port=1, + i2s_port=2, ) M5.Speaker.end() M5.Mic.end() From 73c9bf0530e5b911cc9e32c73c79aaaa6cac7673 Mon Sep 17 00:00:00 2001 From: luoweiyuan Date: Fri, 28 Mar 2025 15:08:50 +0800 Subject: [PATCH 034/322] libs/module: Add support for Module13.2 ECG. Signed-off-by: luoweiyuan --- docs/en/module/ecg.rst | 87 ++++++ docs/en/module/index.rst | 1 + docs/en/refs/module.ecg.ref | 21 ++ docs/locales/zh_CN/LC_MESSAGES/module/ecg.po | 272 ++++++++++++++++++ .../ecg/cores3_ecg_module_base_example.m5f2 | 1 + .../ecg/cores3_ecg_module_base_example.py | 125 ++++++++ m5stack/libs/module/__init__.py | 1 + m5stack/libs/module/ecg.py | 103 +++++++ m5stack/libs/module/manifest.py | 1 + 9 files changed, 612 insertions(+) create mode 100644 docs/en/module/ecg.rst create mode 100644 docs/en/refs/module.ecg.ref create mode 100644 docs/locales/zh_CN/LC_MESSAGES/module/ecg.po create mode 100644 examples/module/ecg/cores3_ecg_module_base_example.m5f2 create mode 100644 examples/module/ecg/cores3_ecg_module_base_example.py create mode 100644 m5stack/libs/module/ecg.py diff --git a/docs/en/module/ecg.rst b/docs/en/module/ecg.rst new file mode 100644 index 00000000..9012e219 --- /dev/null +++ b/docs/en/module/ecg.rst @@ -0,0 +1,87 @@ +ECG Module +============================ + +.. sku: M034 + +.. include:: ../refs/module.ecg.ref + +This library is the driver for Module13.2 ECG, and the module communicates via UART. + +Support the following products: + + |Module13.2 ECG| + +UiFlow2 Example: +-------------------------- + +Heart Rate Monitoring Display +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Open the |cores3_ecg_module_base_example.m5f2| project in UiFlow2. + +This example program is used for real-time heart rate monitoring and ECG waveform display. During measurement, the device continuously plots the ECG (Electrocardiogram) waveform and automatically calculates and displays the heart rate data once the signal stabilizes. + +**Electrode Placement Instructions**: +Please follow the guidelines below to correctly connect the ECG electrodes: + +- ``Right Arm (RA)``: Place the right arm electrode on the right edge of the sternum, at the 2nd intercostal space along the midclavicular line, near the right shoulder. +- ``Left Arm (LA)``: Place the left arm electrode on the left edge of the sternum, at the 2nd intercostal space along the midclavicular line, near the left shoulder. +- ``Left Leg (LL)``: Place the left leg electrode above the iliac crest (hip bone) on the left lower abdomen, or on the lower left side of the abdomen. + + +**Measurement Precautions**: +To ensure stable and accurate ECG signals, please follow these precautions: + +- ``Stay relaxed``: Avoid muscle tension to reduce signal interference. +- ``Remain still``: Minimize movement during measurement to obtain a stable ECG signal. + + +UiFlow2 Code Block: + + |cores3_ecg_module_base_example.png| + +Example output: + + None + +MicroPython Example: +-------------------------- + +Heart Rate Monitoring Display +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +This example program is used for real-time heart rate monitoring and ECG waveform display. During measurement, the device continuously plots the ECG (Electrocardiogram) waveform and automatically calculates and displays the heart rate data once the signal stabilizes. + +**Electrode Placement Instructions**: +Please follow the guidelines below to correctly connect the ECG electrodes: + +- ``Right Arm (RA)``: Place the right arm electrode on the right edge of the sternum, at the 2nd intercostal space along the midclavicular line, near the right shoulder. +- ``Left Arm (LA)``: Place the left arm electrode on the left edge of the sternum, at the 2nd intercostal space along the midclavicular line, near the left shoulder. +- ``Left Leg (LL)``: Place the left leg electrode above the iliac crest (hip bone) on the left lower abdomen, or on the lower left side of the abdomen. + + +**Measurement Precautions**: +To ensure stable and accurate ECG signals, please follow these precautions: + +- ``Stay relaxed``: Avoid muscle tension to reduce signal interference. +- ``Remain still``: Minimize movement during measurement to obtain a stable ECG signal. + + +MicroPython Code Block: + + .. literalinclude:: ../../../examples/module/ecg/cores3_ecg_module_base_example.py + :language: python + :linenos: + +Example output: + + None + +**API** +-------------------------- + +ECGModule +^^^^^^^^^^^^^^^^^^^^^^^^ + +.. autoclass:: module.ecg.ECGModule + :members: diff --git a/docs/en/module/index.rst b/docs/en/module/index.rst index 074d9cd1..73f7bc0b 100644 --- a/docs/en/module/index.rst +++ b/docs/en/module/index.rst @@ -11,6 +11,7 @@ Module display.rst dmx.rst dualkmeter.rst + ecg.rst encoder4_motor.rst fan.rst gnss.rst diff --git a/docs/en/refs/module.ecg.ref b/docs/en/refs/module.ecg.ref new file mode 100644 index 00000000..6b01354b --- /dev/null +++ b/docs/en/refs/module.ecg.ref @@ -0,0 +1,21 @@ + +.. |Module13.2 ECG| image:: https://static-cdn.m5stack.com/resource/docs/products/module/ecg/ecg_01.webp + :target: https://docs.m5stack.com/zh_CN/module/ecg + :height: 200px + :width: 200px + +.. |init.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/ecg/init.png +.. |poll_data.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/ecg/poll_data.png +.. |read_heartrate.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/ecg/read_heartrate.png +.. |read_raw_ecg_data.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/ecg/read_raw_ecg_data.png + +.. |cores3_ecg_module_base_example.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/ecg/example.png + +.. |cores3_ecg_module_base_example.m5f2| raw:: html + + + cores3_ecg_module_base_example.m5f2 + diff --git a/docs/locales/zh_CN/LC_MESSAGES/module/ecg.po b/docs/locales/zh_CN/LC_MESSAGES/module/ecg.po new file mode 100644 index 00000000..9ccb8933 --- /dev/null +++ b/docs/locales/zh_CN/LC_MESSAGES/module/ecg.po @@ -0,0 +1,272 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2016 - 2025 M5Stack Technology Co., Ltd +# This file is distributed under the same license as the UIFlow2 Programming +# Guide package. +# FIRST AUTHOR , 2025. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: UIFlow2 Programming Guide \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-03-28 14:50+0800\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: zh_CN\n" +"Language-Team: zh_CN \n" +"Plural-Forms: nplurals=1; plural=0;\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.16.0\n" + +#: ../../en/module/ecg.rst:2 296c8a1a944045afaf634ba5bf2b4f18 +msgid "ECG Module" +msgstr "" + +#: ../../en/module/ecg.rst:8 b3728f298ac74a4298376126f34bf86f +msgid "" +"This library is the driver for Module13.2 ECG, and the module " +"communicates via UART." +msgstr "这个库是 Module13.2 ECG 的驱动程序,模块通过 UART 进行通信。" + +#: ../../en/module/ecg.rst:10 a0d9579012894eb28c1542a34bc78667 +msgid "Support the following products:" +msgstr "支持以下产品:" + +#: ../../en/module/ecg.rst:12 192529d5256c4a1faa755a023fb77955 +msgid "|Module13.2 ECG|" +msgstr "" + +#: ../../en/refs/module.ecg.ref c86edb01e34e462e9b7d086ebfbce5b3 +msgid "Module13.2 ECG" +msgstr "" + +#: ../../en/module/ecg.rst:15 f869fd485807484e92956a8e478633e0 +msgid "UiFlow2 Example:" +msgstr "UiFlow2 应用示例:" + +#: ../../en/module/ecg.rst:18 ../../en/module/ecg.rst:51 +#: b746d29ec1a8480088c4076970726641 +msgid "Heart Rate Monitoring Display" +msgstr "心率检测显示" + +#: ../../en/module/ecg.rst:20 e89d5f85a85d45da97760971f8459ce0 +msgid "Open the |cores3_ecg_module_base_example.m5f2| project in UiFlow2." +msgstr "在 UiFlow2 上打开 |cores3_ecg_module_base_example.m5f2| 项目。" + +#: ../../en/module/ecg.rst:22 ../../en/module/ecg.rst:53 +#: 46871a845db34c698d3508b2ff8a7d9c bc7fb61af0a5471d9f4098d4a031e0c6 +msgid "" +"This example program is used for real-time heart rate monitoring and ECG " +"waveform display. During measurement, the device continuously plots the " +"ECG (Electrocardiogram) waveform and automatically calculates and " +"displays the heart rate data once the signal stabilizes." +msgstr "本示例程序用于实时读取心率并显示心电波形。测量过程中,设备将连续绘制 ECG(心电图)波形,并在信号稳定后自动计算并显示心率数据。" + +#: ../../en/module/ecg.rst:24 ../../en/module/ecg.rst:55 +#: 2762f640d0c143d88fefe6f903c6bd7c c192e4a7ed504f3088bfd7b559cdd1e2 +msgid "" +"**Electrode Placement Instructions**: Please follow the guidelines below " +"to correctly connect the ECG electrodes:" +msgstr "**心电电极连接**:请按照以下方法正确连接心电电极:" + +#: ../../en/module/ecg.rst:27 ../../en/module/ecg.rst:58 +#: b6803ffddf4f4994a91c86055164ce28 ccfbaeeee3804219ac97e8bce0d2b35a +msgid "" +"``Right Arm (RA)``: Place the right arm electrode on the right edge of " +"the sternum, at the 2nd intercostal space along the midclavicular line, " +"near the right shoulder." +msgstr "右上(RA):右上导联线放在胸骨右缘,锁骨中线第2肋间靠近右肩的位置。" + +#: ../../en/module/ecg.rst:28 ../../en/module/ecg.rst:59 +#: b3b29c73649f4d61b2cb95677bf3a36d c3ab0c09b847420b91758d808e38591c +msgid "" +"``Left Arm (LA)``: Place the left arm electrode on the left edge of the " +"sternum, at the 2nd intercostal space along the midclavicular line, near " +"the left shoulder." +msgstr "左上(LA):左上导联线位于胸骨左缘,锁骨中线第2肋间靠近左肩的位置。" + +#: ../../en/module/ecg.rst:29 ../../en/module/ecg.rst:60 +#: 2f9082f8c9a740cd9357bce5a07e4a86 b64f8b71b4dc4449a490e45e0b04d6cd +msgid "" +"``Left Leg (LL)``: Place the left leg electrode above the iliac crest " +"(hip bone) on the left lower abdomen, or on the lower left side of the " +"abdomen." +msgstr "左下(LL):左下导联线放在左侧下腹部,髂嵴(hip bone)上方,或腹部左下侧。" + +#: ../../en/module/ecg.rst:32 ../../en/module/ecg.rst:63 +#: 9522377584a9455c98d50fcac77c78fa baee1ad2617f404284d2861270a02eb5 +msgid "" +"**Measurement Precautions**: To ensure stable and accurate ECG signals, " +"please follow these precautions:" +msgstr "**测量注意事项**:为了获得稳定、准确的心电信号,请遵循以下注意事项:" + +#: ../../en/module/ecg.rst:35 ../../en/module/ecg.rst:66 +#: 82bad8137e3e49509ffeb9f7b263630b db0c923e7cab49cdaa93c4fc2251fdcf +msgid "``Stay relaxed``: Avoid muscle tension to reduce signal interference." +msgstr "身体放松: 避免肌肉紧张,以减少干扰信号。" + +#: ../../en/module/ecg.rst:36 ../../en/module/ecg.rst:67 +#: 892e029f9b664be3bd8f05984eadfd34 bc50e457d7f84f1abcf9f4aa0d02fb93 +msgid "" +"``Remain still``: Minimize movement during measurement to obtain a stable" +" ECG signal." +msgstr "静止不动: 测量过程中尽量避免移动,以获得稳定的心电信号。" + +#: ../../en/module/ecg.rst:39 58debac2237d49219615190a01f908ff +#: fe8588cdefd94427807e83a2460b1494 module.ecg.ECGModule:7 +#: module.ecg.ECGModule.poll_data:7 module.ecg.ECGModule.read_heartrate:8 +#: module.ecg.ECGModule.read_raw_ecg_data:6 of +msgid "UiFlow2 Code Block:" +msgstr "UiFlow2 代码块:" + +#: ../../en/module/ecg.rst:41 36dfe936b007462181f6af13d722efe3 +msgid "|cores3_ecg_module_base_example.png|" +msgstr "" + +#: ../../en/refs/module.ecg.ref:12 7725cde7caac41bcbcf14ce1dccaf86e +msgid "cores3_ecg_module_base_example.png" +msgstr "" + +#: ../../en/module/ecg.rst:43 ../../en/module/ecg.rst:76 +#: 17a8d108e44746fe82660f0e21ac41ac +msgid "Example output:" +msgstr "示例输出:" + +#: ../../en/module/ecg.rst:45 ../../en/module/ecg.rst:78 +#: b83ee817b8214b3ca84e7c8a48be1c0d +msgid "None" +msgstr "" + +#: ../../en/module/ecg.rst:48 b17d6400b694478fbf7d5731d642b5b0 +msgid "MicroPython Example:" +msgstr "" + +#: ../../en/module/ecg.rst:70 36bbd733f3904335bec2de16b7a4d4d5 +#: module.ecg.ECGModule:11 module.ecg.ECGModule.poll_data:11 +#: module.ecg.ECGModule.read_heartrate:12 +#: module.ecg.ECGModule.read_raw_ecg_data:10 of +msgid "MicroPython Code Block:" +msgstr "MicroPython 应用示例:" + +#: ../../en/module/ecg.rst:81 2d3d700af1174c2f98325e8c1720b278 +msgid "**API**" +msgstr "API应用" + +#: ../../en/module/ecg.rst:84 296c8a1a944045afaf634ba5bf2b4f18 +msgid "ECGModule" +msgstr "" + +#: c638bab91bc84dc1a0437cdcdf972975 module.ecg.ECGModule:1 of +msgid "Bases: :py:class:`object`" +msgstr "" + +#: 1dc3f95ab0c14772982099d369ffc5c5 module.ecg.ECGModule:1 of +msgid "Create an ECGModule object." +msgstr "创建一个 ECGModule 对象。" + +#: ../../en/module/ecg.rst f4469e1b2d0743adb40bd4d3827093ef +msgid "Parameters" +msgstr "" + +#: 895602a91de94bb1b1bc479d3da0419c module.ecg.ECGModule:3 of +msgid "UART id." +msgstr "UART 端口号。" + +#: a55628f0a1f24e6ea8caf27106820bdf module.ecg.ECGModule:4 of +msgid "the UART TX pin." +msgstr "UART 发送引脚。" + +#: 90cc16af767c4c4896dbbbf1876ccafa module.ecg.ECGModule:5 of +msgid "the UART RX pin." +msgstr "UART 接收引脚。" + +#: 272b6ea6551c4903ad54769efb2ce962 module.ecg.ECGModule:9 of +msgid "|init.png|" +msgstr "" + +#: ../../en/refs/module.ecg.ref:7 7725cde7caac41bcbcf14ce1dccaf86e +msgid "init.png" +msgstr "" + +#: 59ba7c77a0434b29ad225856d8e83fc9 module.ecg.ECGModule.poll_data:1 of +msgid "Poll data." +msgstr "轮询数据。" + +#: ec5609dc2ee24cb291f72bcf0f8b6772 module.ecg.ECGModule.poll_data:3 of +msgid "" +"This function checks for new data from the module and should be called " +"repeatedly in a loop to ensure continuous data retrieval." +msgstr "此函数用于检查模块是否有新数据,应在循环中重复调用,以确保持续获取数据。" + +#: dd4e137791324db6ba150882bed26df8 module.ecg.ECGModule.poll_data:9 of +msgid "|poll_data.png|" +msgstr "" + +#: ../../en/refs/module.ecg.ref:8 f3d9757acb83460d98bc74f157590952 +msgid "poll_data.png" +msgstr "" + +#: e00cac6fc5b543d89ac5fb7b31c7a273 module.ecg.ECGModule.poll_data +#: module.ecg.ECGModule.read_heartrate module.ecg.ECGModule.read_raw_ecg_data +#: of +msgid "Return type" +msgstr "" + +#: 1c95e89f478c498f9d58996aa092c321 module.ecg.ECGModule.read_heartrate:1 of +msgid "Read heartrate." +msgstr "" + +#: c42748944c1941b3a2103758ded9d824 module.ecg.ECGModule.read_heartrate +#: module.ecg.ECGModule.read_raw_ecg_data of +msgid "Returns" +msgstr "" + +#: d6aef574a83a4f8ab66e4612ec9231df module.ecg.ECGModule.read_heartrate:3 of +msgid "heart rate" +msgstr "" + +#: 5d8d4d19c1e345f6b9b1813c3ae03eb3 module.ecg.ECGModule.read_heartrate:6 of +msgid "If heart rate is no valid, return -1." +msgstr "如果心率数据无效,则返回 -1。" + +#: ab6e00b7e8bf45c79443744e51b89e85 module.ecg.ECGModule.read_heartrate:10 of +msgid "|read_heartrate.png|" +msgstr "" + +#: ../../en/refs/module.ecg.ref:9 6ee193a310824baf9e63c851ed8a45f5 +msgid "read_heartrate.png" +msgstr "" + +#: 5f1d9087a84545eb835f210eaebfdc90 module.ecg.ECGModule.read_raw_ecg_data:1 of +msgid "Read raw ECG data." +msgstr "读取原始 ECG 数据。" + +#: 3cba6c0aaa6e4bebb3e6618af1f3c9fb module.ecg.ECGModule.read_raw_ecg_data:3 of +msgid "ECG data" +msgstr "" + +#: 4b95e2fee3a940d18d9b3abd0b0c9f34 module.ecg.ECGModule.read_raw_ecg_data:8 of +msgid "|read_raw_ecg_data.png|" +msgstr "" + +#: ../../en/refs/module.ecg.ref:10 b8ebee6b59f84e4eb19a704f0e1bee5a +msgid "read_raw_ecg_data.png" +msgstr "" + +#~ msgid "Open the |cores3_ecg_module_example.m5f2| project in UiFlow2." +#~ msgstr "" + +#~ msgid "The example demonstrates" +#~ msgstr "" + +#~ msgid "|cores3_ecg_module_example.png|" +#~ msgstr "" + +#~ msgid "@note: 必须放在循环中" +#~ msgstr "" + +#~ msgid "|read_ecg_data.png|" +#~ msgstr "" + diff --git a/examples/module/ecg/cores3_ecg_module_base_example.m5f2 b/examples/module/ecg/cores3_ecg_module_base_example.m5f2 new file mode 100644 index 00000000..91d87493 --- /dev/null +++ b/examples/module/ecg/cores3_ecg_module_base_example.m5f2 @@ -0,0 +1 @@ +{"version":"V2.0","versionNumber":"V2.2.4","type":"cores3","components":[{"name":"screen","type":"screen","layer":0,"screenId":"builtin","screenName":"","id":"__cores3_screen","createTime":1742949464449,"x":0,"y":0,"width":320,"height":240,"backgroundColor":"#222222","size":0,"isSelected":true},{"name":"label0","type":"label","layer":1,"screenId":"builtin","screenName":"","id":"mr+CBEWqPX*$ix!2","createTime":1742949698732,"x":5,"y":35,"color":"#ff0000","backgroundColor":"#222222","text":"HearRate:","engine":"gfx","font":"Widgets.FONTS.DejaVu24","rotation":0,"isSelected":false,"width":126,"height":28},{"name":"title0","type":"title","layer":2,"screenId":"builtin","screenName":"","id":"zCm_iA^S8tkoTKwI","createTime":1742949724627,"x":0,"y":0,"color":"#ffffff","backgroundColor":"#0000FF","text":"ECGModule Example","textOffset":3,"font":"Widgets.FONTS.DejaVu24","isSelected":false,"width":321,"height":25},{"name":"label_hr","type":"label","layer":3,"screenId":"builtin","screenName":"","id":"xRHEWDm#Sf26XH^e","createTime":1742949749348,"x":139,"y":35,"color":"#ff0000","backgroundColor":"#222222","text":" BPM","engine":"gfx","font":"Widgets.FONTS.DejaVu24","rotation":0,"isSelected":false,"width":69,"height":28}],"resources":[{"hardware":["hardware_button","hardware_pin_button","imu","speaker","touch","als","mic"]},{"module":["module_ecg"]}],"units":[],"hats":[],"bases":[],"i2cs":[],"blockly":"last_timeheartratetrueecg_0171trueecg_0GT11last_time200last_timeheartrateecg_0hello M5ecg_0GTheartrate0label_hrLabelhello M5heartrateBPM","screen":[{"simulationName":"Built-in","type":"builtin","width":320,"height":240,"scale":0.78,"screenName":"","blockId":"","screenColorType":0,"id":"builtin","createTime":1742949464446}],"logicWhenNum":0,"customList":[]} \ No newline at end of file diff --git a/examples/module/ecg/cores3_ecg_module_base_example.py b/examples/module/ecg/cores3_ecg_module_base_example.py new file mode 100644 index 00000000..493f9315 --- /dev/null +++ b/examples/module/ecg/cores3_ecg_module_base_example.py @@ -0,0 +1,125 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import os, sys, io +import M5 +from M5 import * +from module import ECGModule +import time +import m5utils + + +label0 = None +title0 = None +label_hr = None +ecg_0 = None +display_width = None +ecg_data = None +last_time = None +points = None +heartrate = None +new_ecg_data = None +new_ecg_data_len = None +data_max = None +data_min = None +i = None +y0 = None +y1 = None + + +def setup(): + global \ + label0, \ + title0, \ + label_hr, \ + ecg_0, \ + display_width, \ + ecg_data, \ + last_time, \ + points, \ + heartrate, \ + new_ecg_data, \ + new_ecg_data_len, \ + data_max, \ + data_min, \ + y0, \ + i, \ + y1 + + M5.begin() + label0 = Widgets.Label("HearRate:", 5, 35, 1.0, 0xFF0000, 0x000000, Widgets.FONTS.DejaVu24) + title0 = Widgets.Title("ECGModule Example", 3, 0xFFFFFF, 0x0000FF, Widgets.FONTS.DejaVu24) + label_hr = Widgets.Label(" BPM", 142, 35, 1.0, 0xFF0000, 0x000000, Widgets.FONTS.DejaVu24) + + ecg_0 = ECGModule(1, tx=7, rx=1) + display_width = M5.Lcd.width() + ecg_data = [0] * display_width + points = [0] * display_width + + +def loop(): + global \ + label0, \ + title0, \ + label_hr, \ + ecg_0, \ + display_width, \ + ecg_data, \ + last_time, \ + points, \ + heartrate, \ + new_ecg_data, \ + new_ecg_data_len, \ + data_max, \ + data_min, \ + y0, \ + i, \ + y1 + M5.update() + ecg_0.poll_data() + if (time.ticks_diff((time.ticks_ms()), last_time)) > 200: + last_time = time.ticks_ms() + heartrate = ecg_0.read_heartrate() + if heartrate > 0: + label0.setColor(0x00FF00, 0x000000) + label_hr.setColor(0x00FF00, 0x000000) + label_hr.setText(str((str(heartrate) + str("BPM")))) + else: + label0.setColor(0xFF0000, 0x000000) + label_hr.setText(str(" ")) + new_ecg_data = ecg_0.read_raw_ecg_data() + if new_ecg_data: + new_ecg_data_len = len(new_ecg_data) + ecg_data = ecg_data[int((new_ecg_data_len + 1) - 1) :] + ecg_data = ecg_data + new_ecg_data + data_max = max(ecg_data) + data_min = min(ecg_data) + data_max = data_max + data_max / 10 + data_min = data_min - data_min / 10 + if data_min < 0: + data_min = 0 + for i in range(display_width): + points[int((i + 1) - 1)] = m5utils.remap( + ecg_data[int((i + 1) - 1)], data_min, data_max, 150, 0 + ) + + M5.Lcd.fillRect(0, 70, display_width, 155, 0x000000) + for i in range(display_width - 1): + y0 = int(70 + points[int((i + 1) - 1)]) + y1 = int(70 + points[int((i + 2) - 1)]) + M5.Lcd.drawLine(i + 1, y0, i + 2, y1, 0xFF0000) + + +if __name__ == "__main__": + try: + setup() + while True: + loop() + except (Exception, KeyboardInterrupt) as e: + try: + from utility import print_error_msg + + print_error_msg(e) + except ImportError: + print("please update to latest firmware") diff --git a/m5stack/libs/module/__init__.py b/m5stack/libs/module/__init__.py index acee58ec..0a464aef 100644 --- a/m5stack/libs/module/__init__.py +++ b/m5stack/libs/module/__init__.py @@ -13,6 +13,7 @@ "DisplayModule": "display", "DMX512Module": "dmx", "DualKmeterModule": "dual_kmeter", + "ECGModule": "ecg", "Encoder4MotorModule": "encoder4_motor", "FanModule": "fan", "GNSSModule": "gnss", diff --git a/m5stack/libs/module/ecg.py b/m5stack/libs/module/ecg.py new file mode 100644 index 00000000..46532797 --- /dev/null +++ b/m5stack/libs/module/ecg.py @@ -0,0 +1,103 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import machine +import time +from collections import deque +from micropython import const + + +class ECGModule: + """Create an ECGModule object. + + :param int id: UART id. + :param int tx: the UART TX pin. + :param int rx: the UART RX pin. + + UiFlow2 Code Block: + + |init.png| + + MicroPython Code Block: + + .. code-block:: python + + from module import ECGModule + + module_ecg = ECGModule(id = 1, tx = 7, rx = 1) + """ + + def __init__(self, id: int = 1, tx: int = 7, rx: int = 1): + self.serial = machine.UART(id, baudrate=115200, tx=rx, rx=tx) # 交叉 + self.serial.read() + self.raw_ecg_buf_len = 320 + self.raw_ecg_buf = deque([], self.raw_ecg_buf_len) + self._last_heartrate = -1 + self.FRAME_LEN = const(11) # 帧固定为11字节 + + def poll_data(self) -> None: + """Poll data. + + This function checks for new data from the module and + should be called repeatedly in a loop to ensure continuous + data retrieval. + + UiFlow2 Code Block: + + |poll_data.png| + + MicroPython Code Block: + + .. code-block:: python + + module_ecg.poll_data() + """ + while self.serial.any() >= self.FRAME_LEN: + frame = self.serial.read(self.FRAME_LEN) + if frame[0] != 0xAA or frame[-1] != 0xEF: + continue + data_length = frame[1] * 2 + if data_length != 8: + continue + raw_ecg = (frame[2] << 8) | frame[3] + self.raw_ecg_buf.append(raw_ecg) + self._last_heartrate = frame[9] if (frame[8] == 0x01) else -1 + + def read_heartrate(self) -> int: + """Read heartrate. + + :returns: heart rate + :rtype: int + + If heart rate is no valid, return -1. + + UiFlow2 Code Block: + + |read_heartrate.png| + + MicroPython Code Block: + + .. code-block:: python + + module_ecg.read_heartrate() + """ + return self._last_heartrate + + def read_raw_ecg_data(self) -> list: + """Read raw ECG data. + + :returns: ECG data + :rtype: list + + UiFlow2 Code Block: + + |read_raw_ecg_data.png| + + MicroPython Code Block: + + .. code-block:: python + + module_ecg.read_raw_ecg_data() + """ + return list(self.raw_ecg_buf) diff --git a/m5stack/libs/module/manifest.py b/m5stack/libs/module/manifest.py index bd8ac017..9bec2535 100644 --- a/m5stack/libs/module/manifest.py +++ b/m5stack/libs/module/manifest.py @@ -12,6 +12,7 @@ "display.py", "dmx.py", "dual_kmeter.py", + "ecg.py", "encoder4_motor.py", "fan.py", "gnss.py", From 746c8067cc2f0d097ebab39960c1219983b96153 Mon Sep 17 00:00:00 2001 From: Tinyu-Zhao Date: Wed, 2 Apr 2025 09:17:08 +0800 Subject: [PATCH 035/322] lib/unit: Modifying the ASR to enable debug mode. Signed-off-by: Tinyu-Zhao --- m5stack/libs/unit/asr.py | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/m5stack/libs/unit/asr.py b/m5stack/libs/unit/asr.py index a7a816e3..027b90f3 100644 --- a/m5stack/libs/unit/asr.py +++ b/m5stack/libs/unit/asr.py @@ -29,13 +29,6 @@ class ASRUnit: asr = ASRUnit(id=1, port=(1, 2)) """ - DEBUG = True - - myprint = print if DEBUG else lambda *_, **__: None - """ - :meta private: - """ - _COMMAND_LIST = { 0x01: ["up", None], 0x02: ["down", None], @@ -82,28 +75,34 @@ class ASRUnit: 0xFF: ["Hi,M Five", None], } - def __init__(self, id: Literal[0, 1, 2] = 1, port: list | tuple = None): + def __init__(self, id: Literal[0, 1, 2] = 1, port: list | tuple = None, verbose: bool = False): self.uart = UART(id, tx=port[1], rx=port[0]) self.uart.init(115200, bits=8, parity=None, stop=1) self.uart.irq(handler=self._handler, trigger=UART.IRQ_RXIDLE) self.raw_message = "" self.command_num = 0 self.is_recieved = False + self.verbose = verbose + + def _debug_print(self, *args, **kwargs): + """Print debug information if verbose mode is enabled.""" + if self.verbose: + print(*args, **kwargs) def _handler(self, uart) -> None: data = uart.readline() if data is not None and len(data) >= 5: - self.myprint(("Received data: ", data)) + self._debug_print(("Received data: ", data)) if data[0] == 0xAA and data[1] == 0x55 and data[-2] == 0x55 and data[-1] == 0xAA: self.is_recieved = True self.raw_message = " ".join(f"0x{byte:02X}" for byte in data) - self.myprint(("Parsed message:", self.raw_message.split())) + self._debug_print(("Parsed message:", self.raw_message.split())) self.command_num = data[2] self.check_tick_callback() else: - self.myprint("Invalid frame received: header/footer mismatch") + self._debug_print("Invalid frame received: header/footer mismatch") uart.read() def get_received_status(self) -> bool: @@ -146,7 +145,7 @@ def send_message( """ message: list[int] = [0xAA, 0x55, command_num, 0x55, 0xAA] buf = bytes(message) - self.myprint(buf) + self._debug_print(buf) self.uart.write(buf) def get_current_raw_message(self) -> str: @@ -340,6 +339,6 @@ def check_tick_callback(self): asr.check_tick_callback() """ handler = self._COMMAND_LIST.get(self.command_num, ["", None])[1] - self.myprint(("handler: ", handler)) + self._debug_print(("handler: ", handler)) if handler is not None: schedule(handler, self) From 19291616fb907269e69cb293fe13f7ab4e8c35d5 Mon Sep 17 00:00:00 2001 From: luoweiyuan Date: Wed, 2 Apr 2025 18:07:18 +0800 Subject: [PATCH 036/322] libs/base: Add support for AtomicQRCodeBase, AtomicQRCode2Base, QRCodeModule. --- docs/en/base/index.rst | 2 + docs/en/base/qrcode.rst | 178 ++++ docs/en/base/qrcode2.rst | 567 ++++++++++++ docs/en/module/index.rst | 1 + docs/en/module/qrcode.rst | 555 ++++++++++++ docs/en/refs/base.qrcode.ref | 71 ++ docs/en/refs/base.qrcode2.ref | 80 ++ docs/en/refs/module.qrcode.ref | 71 ++ docs/locales/zh_CN/LC_MESSAGES/base/qrcode.po | 616 +++++++++++++ .../locales/zh_CN/LC_MESSAGES/base/qrcode2.po | 841 ++++++++++++++++++ .../zh_CN/LC_MESSAGES/module/qrcode.po | 785 ++++++++++++++++ .../atoms3_qrcode_auto_mode_example.m5f2 | 1 + .../qrcode/atoms3_qrcode_auto_mode_example.py | 50 ++ .../atoms3_qrcode_host_mode_example.m5f2 | 1 + .../qrcode/atoms3_qrcode_host_mode_example.py | 69 ++ .../atoms3_qrcode_key_mode_example.m5f2 | 1 + .../qrcode/atoms3_qrcode_key_mode_example.py | 68 ++ ...s3_qrcode_motion_sensing_mode_example.m5f2 | 1 + ...oms3_qrcode_motion_sensing_mode_example.py | 49 + .../atoms3_qrcode_pulse_mode_example.m5f2 | 1 + .../atoms3_qrcode_pulse_mode_example.py | 62 ++ .../atoms3_qrcode2_auto_mode_example.m5f2 | 1 + .../atoms3_qrcode2_auto_mode_example.py | 49 + ...toms3_qrcode2_continuous_mode_example.m5f2 | 1 + .../atoms3_qrcode2_continuous_mode_example.py | 68 ++ .../atoms3_qrcode2_key_mode_example.m5f2 | 1 + .../atoms3_qrcode2_key_mode_example.py | 67 ++ ...3_qrcode2_motion_sensing_mode_example.m5f2 | 1 + ...ms3_qrcode2_motion_sensing_mode_example.py | 50 ++ .../atoms3_qrcode2_pulse_mode_example.m5f2 | 1 + .../atoms3_qrcode2_pulse_mode_example.py | 62 ++ .../cores3_qrcode_auto_mode_example.m5f2 | 1 + .../qrcode/cores3_qrcode_auto_mode_example.py | 50 ++ ...cores3_qrcode_continuous_mode_example.m5f2 | 1 + .../cores3_qrcode_continuous_mode_example.py | 68 ++ ...s3_qrcode_motion_sensing_mode_example.m5f2 | 1 + ...res3_qrcode_motion_sensing_mode_example.py | 51 ++ .../cores3_qrcode_pulse_mode_example.m5f2 | 1 + .../cores3_qrcode_pulse_mode_example.py | 63 ++ m5stack/libs/base/__init__.py | 2 + m5stack/libs/base/manifest.py | 2 + m5stack/libs/base/qrcode.py | 352 ++++++++ m5stack/libs/base/qrcode2.py | 31 + m5stack/libs/driver/manifest.py | 3 + m5stack/libs/driver/qrcode/__init__.py | 3 + m5stack/libs/driver/qrcode/qrcode_m14.py | 262 ++++++ .../libs/driver/qrcode/serial_cmd_helper.py | 74 ++ m5stack/libs/module/__init__.py | 1 + m5stack/libs/module/manifest.py | 1 + m5stack/libs/module/qrcode.py | 113 +++ 50 files changed, 5451 insertions(+) create mode 100644 docs/en/base/qrcode.rst create mode 100644 docs/en/base/qrcode2.rst create mode 100644 docs/en/module/qrcode.rst create mode 100644 docs/en/refs/base.qrcode.ref create mode 100644 docs/en/refs/base.qrcode2.ref create mode 100644 docs/en/refs/module.qrcode.ref create mode 100644 docs/locales/zh_CN/LC_MESSAGES/base/qrcode.po create mode 100644 docs/locales/zh_CN/LC_MESSAGES/base/qrcode2.po create mode 100644 docs/locales/zh_CN/LC_MESSAGES/module/qrcode.po create mode 100644 examples/base/qrcode/atoms3_qrcode_auto_mode_example.m5f2 create mode 100644 examples/base/qrcode/atoms3_qrcode_auto_mode_example.py create mode 100644 examples/base/qrcode/atoms3_qrcode_host_mode_example.m5f2 create mode 100644 examples/base/qrcode/atoms3_qrcode_host_mode_example.py create mode 100644 examples/base/qrcode/atoms3_qrcode_key_mode_example.m5f2 create mode 100644 examples/base/qrcode/atoms3_qrcode_key_mode_example.py create mode 100644 examples/base/qrcode/atoms3_qrcode_motion_sensing_mode_example.m5f2 create mode 100644 examples/base/qrcode/atoms3_qrcode_motion_sensing_mode_example.py create mode 100644 examples/base/qrcode/atoms3_qrcode_pulse_mode_example.m5f2 create mode 100644 examples/base/qrcode/atoms3_qrcode_pulse_mode_example.py create mode 100644 examples/base/qrcode2/atoms3_qrcode2_auto_mode_example.m5f2 create mode 100644 examples/base/qrcode2/atoms3_qrcode2_auto_mode_example.py create mode 100644 examples/base/qrcode2/atoms3_qrcode2_continuous_mode_example.m5f2 create mode 100644 examples/base/qrcode2/atoms3_qrcode2_continuous_mode_example.py create mode 100644 examples/base/qrcode2/atoms3_qrcode2_key_mode_example.m5f2 create mode 100644 examples/base/qrcode2/atoms3_qrcode2_key_mode_example.py create mode 100644 examples/base/qrcode2/atoms3_qrcode2_motion_sensing_mode_example.m5f2 create mode 100644 examples/base/qrcode2/atoms3_qrcode2_motion_sensing_mode_example.py create mode 100644 examples/base/qrcode2/atoms3_qrcode2_pulse_mode_example.m5f2 create mode 100644 examples/base/qrcode2/atoms3_qrcode2_pulse_mode_example.py create mode 100644 examples/module/qrcode/cores3_qrcode_auto_mode_example.m5f2 create mode 100644 examples/module/qrcode/cores3_qrcode_auto_mode_example.py create mode 100644 examples/module/qrcode/cores3_qrcode_continuous_mode_example.m5f2 create mode 100644 examples/module/qrcode/cores3_qrcode_continuous_mode_example.py create mode 100644 examples/module/qrcode/cores3_qrcode_motion_sensing_mode_example.m5f2 create mode 100644 examples/module/qrcode/cores3_qrcode_motion_sensing_mode_example.py create mode 100644 examples/module/qrcode/cores3_qrcode_pulse_mode_example.m5f2 create mode 100644 examples/module/qrcode/cores3_qrcode_pulse_mode_example.py create mode 100644 m5stack/libs/base/qrcode.py create mode 100644 m5stack/libs/base/qrcode2.py create mode 100644 m5stack/libs/driver/qrcode/__init__.py create mode 100644 m5stack/libs/driver/qrcode/qrcode_m14.py create mode 100644 m5stack/libs/driver/qrcode/serial_cmd_helper.py create mode 100644 m5stack/libs/module/qrcode.py diff --git a/docs/en/base/index.rst b/docs/en/base/index.rst index 6a184a2f..5ae230eb 100644 --- a/docs/en/base/index.rst +++ b/docs/en/base/index.rst @@ -13,6 +13,8 @@ Base hdriver.rst motion.rst pwm.rst + qrcode.rst + qrcode2.rst rs232.rst rs485.rst speaker.rst diff --git a/docs/en/base/qrcode.rst b/docs/en/base/qrcode.rst new file mode 100644 index 00000000..298df02b --- /dev/null +++ b/docs/en/base/qrcode.rst @@ -0,0 +1,178 @@ +Atomic QRCode Base +============================ + +.. sku: A133 + +.. include:: ../refs/base.qrcode.ref + +This library is the driver for Atomic QRCode Base, and the module communicates via UART. + +Support the following products: + + |Atomic QRCode Base| + +UiFlow2 Example: +-------------------------- + +QRCode Scan in Key Mode +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Open the |atoms3_qrcode_key_mode_example.m5f2| project in UiFlow2. + +In **Key Mode**, the module starts decoding when the button is pressed and stops decoding when the button is released. After a successful decoding, it stops decoding. To continue decoding, the button must be released and pressed again. + +UiFlow2 Code Block: + + |atoms3_qrcode_key_mode_example.png| + +Example output: + + None + +QRCode Scan in Host Mode +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Open the |atoms3_qrcode_host_mode_example.m5f2| project in UiFlow2. + +In **Host Mode**, pressing the button once starts decoding, and pressing the button again stops decoding. + +UiFlow2 Code Block: + + |atoms3_qrcode_host_mode_example.png| + +Example output: + + None + +QRCode Scan in Auto Mode +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Open the |atoms3_qrcode_auto_mode_example.m5f2| project in UiFlow2. + +In **Auto Mode**, the module starts decoding when powered on and cannot be stopped. + +UiFlow2 Code Block: + + |atoms3_qrcode_auto_mode_example.png| + +Example output: + + None + +QRCode Scan in Pulse Mode +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Open the |atoms3_qrcode_pulse_mode_example.m5f2| project in UiFlow2. + +In **Pulse Mode**, set the TRIG pin to hold a low level for more than 20ms to trigger decoding once. + +UiFlow2 Code Block: + + |atoms3_qrcode_pulse_mode_example.png| + +Example output: + + None + +QRCode Scan in Motion Sensing Mode +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Open the |atoms3_qrcode_motion_sensing_mode_example.m5f2| project in UiFlow2. + +In **Motion Sensing Mode**, the module automatically triggers decoding when it detects a change in the scene based on visual recognition information. + +UiFlow2 Code Block: + + |atoms3_qrcode_motion_sensing_mode_example.png| + +Example output: + + None + + +MicroPython Example: +-------------------------- + +QRCode Scan in Key Mode +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +In **Key Mode**, the module starts decoding when the button is pressed and stops decoding when the button is released. After a successful decoding, it stops decoding. To continue decoding, the button must be released and pressed again. + +MicroPython Code Block: + + .. literalinclude:: ../../../examples/base/qrcode/atoms3_qrcode_key_mode_example.py + :language: python + :linenos: + +Example output: + + None + +QRCode Scan in Host Mode +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +In **Host Mode**, pressing the button once starts decoding, and pressing the button again stops decoding. + +MicroPython Code Block: + + .. literalinclude:: ../../../examples/base/qrcode/atoms3_qrcode_host_mode_example.py + :language: python + :linenos: + +Example output: + + None + +QRCode Scan in Auto Mode +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +In **Auto Mode**, the module starts decoding when powered on and cannot be stopped. + +MicroPython Code Block: + + .. literalinclude:: ../../../examples/base/qrcode/atoms3_qrcode_auto_mode_example.py + :language: python + :linenos: + +Example output: + + None + +QRCode Scan in Pulse Mode +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +In **Pulse Mode**, set the TRIG pin to hold a low level for more than 20ms to trigger decoding once. + +MicroPython Code Block: + + .. literalinclude:: ../../../examples/base/qrcode/atoms3_qrcode_pulse_mode_example.py + :language: python + :linenos: + +Example output: + + None + +QRCode Scan in Motion Sensing Mode +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +In **Motion Sensing Mode**, the module automatically triggers decoding when it detects a change in the scene based on visual recognition information. + +MicroPython Code Block: + + .. literalinclude:: ../../../examples/base/qrcode/atoms3_qrcode_motion_sensing_mode_example.py + :language: python + :linenos: + +Example output: + + None + +**API** +-------------------------- + +AtomicQRCodeBase +^^^^^^^^^^^^^^^^^^^^^^^^ + +.. autoclass:: base.qrcode.AtomicQRCodeBase + :members: diff --git a/docs/en/base/qrcode2.rst b/docs/en/base/qrcode2.rst new file mode 100644 index 00000000..83a639ad --- /dev/null +++ b/docs/en/base/qrcode2.rst @@ -0,0 +1,567 @@ +Atomic QRCode2 Base +============================ + +.. sku: A133-B + +.. include:: ../refs/base.qrcode2.ref + +This library is the driver for Atomic QRCode2 Base, and the module communicates via UART. + +Support the following products: + + |Atomic QRCode2 Base| + +UiFlow2 Example: +-------------------------- + +QRCode Scan in Key Mode +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Open the |atoms3_qrcode2_key_mode_example.m5f2| project in UiFlow2. + +In **Key Mode**, the module starts decoding when the button is pressed and stops decoding when the button is released. After a successful decoding, it stops decoding. To continue decoding, the button must be released and pressed again. + +UiFlow2 Code Block: + + |atoms3_qrcode2_key_mode_example.png| + +Example output: + + None + +QRCode Scan in Continuous Mode +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Open the |atoms3_qrcode2_continuous_mode_example.m5f2| project in UiFlow2. + +In **Continuous Mode**, pressing the button once starts decoding, and pressing the button again stops decoding. + +UiFlow2 Code Block: + + |atoms3_qrcode2_continuous_mode_example.png| + +Example output: + + None + +QRCode Scan in Auto Mode +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Open the |atoms3_qrcode2_auto_mode_example.m5f2| project in UiFlow2. + +In **Auto Mode**, the module starts decoding when powered on and cannot be stopped. + +UiFlow2 Code Block: + + |atoms3_qrcode2_auto_mode_example.png| + +Example output: + + None + +QRCode Scan in Pulse Mode +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Open the |atoms3_qrcode2_pulse_mode_example.m5f2| project in UiFlow2. + +In **Pulse Mode**, set the TRIG pin to hold a low level for more than 20ms to trigger decoding once. + +UiFlow2 Code Block: + + |atoms3_qrcode2_pulse_mode_example.png| + +Example output: + + None + +QRCode Scan in Motion Sensing Mode +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Open the |atoms3_qrcode2_motion_sensing_mode_example.m5f2| project in UiFlow2. + +In **Motion Sensing Mode**, the module automatically triggers decoding when it detects a change in the scene based on visual recognition information. + +UiFlow2 Code Block: + + |atoms3_qrcode2_motion_sensing_mode_example.png| + +Example output: + + None + + +MicroPython Example: +-------------------------- + +QRCode Scan in Key Mode +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +In **Key Mode**, the module starts decoding when the button is pressed and stops decoding when the button is released. After a successful decoding, it stops decoding. To continue decoding, the button must be released and pressed again. + +MicroPython Code Block: + + .. literalinclude:: ../../../examples/base/qrcode2/atoms3_qrcode2_key_mode_example.py + :language: python + :linenos: + +Example output: + + None + +QRCode Scan in Continuous Mode +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +In **Continuous Mode**, pressing the button once starts decoding, and pressing the button again stops decoding. + +MicroPython Code Block: + + .. literalinclude:: ../../../examples/base/qrcode2/atoms3_qrcode2_continuous_mode_example.py + :language: python + :linenos: + +Example output: + + None + +QRCode Scan in Auto Mode +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +In **Auto Mode**, the module starts decoding when powered on and cannot be stopped. + +MicroPython Code Block: + + .. literalinclude:: ../../../examples/base/qrcode2/atoms3_qrcode2_auto_mode_example.py + :language: python + :linenos: + +Example output: + + None + +QRCode Scan in Pulse Mode +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +In **Pulse Mode**, set the TRIG pin to hold a low level for more than 20ms to trigger decoding once. + +MicroPython Code Block: + + .. literalinclude:: ../../../examples/base/qrcode2/atoms3_qrcode2_pulse_mode_example.py + :language: python + :linenos: + +Example output: + + None + +QRCode Scan in Motion Sensing Mode +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +In **Motion Sensing Mode**, the module automatically triggers decoding when it detects a change in the scene based on visual recognition information. + +MicroPython Code Block: + + .. literalinclude:: ../../../examples/base/qrcode2/atoms3_qrcode2_motion_sensing_mode_example.py + :language: python + :linenos: + +Example output: + + None + +**API** +-------------------------- + +AtomicQRCode2Base +^^^^^^^^^^^^^^^^^^^^^^^^ + + +.. class:: base.qrcode2.AtomicQRCode2Base + + Create an AtomicQRCode2Base object. + + :param int id: UART id. + :param int tx: the UART TX pin. + :param int rx: the UART RX pin. + :param int trig: the trigger pin. + + UiFlow2 Code Block: + + |init.png| + + MicroPython Code Block: + + .. code-block:: python + + from base import AtomicQRCode2Base + + base_qrcode2 = AtomicQRCode2Base(id = 1, tx = 6, rx = 5, trig = 7) + + .. method:: set_trig(value) + + Set trigger pin value. + + :param int value: + + - ``0`` : low level. + - ``1`` : high level. + + UiFlow2 Code Block: + + |set_trig.png| + + MicroPython Code Block: + + .. code-block:: python + + base_qrcode2.set_trig(value) + + .. method:: start_decode() + + Start decode. + + UiFlow2 Code Block: + + |start_decode.png| + + MicroPython Code Block: + + .. code-block:: python + + base_qrcode2.start_decode() + + .. method:: stop_decode() + + Stop decode. + + UiFlow2 Code Block: + + |stop_decode.png| + + MicroPython Code Block: + + .. code-block:: python + + base_qrcode2.stop_decode() + + .. method:: read() + + Read qrcode data. + + :returns: qrcode data. + :rtype: None | bytes + + If no data is received, return None. + + UiFlow2 Code Block: + + |read.png| + + MicroPython Code Block: + + .. code-block:: python + + base_qrcode2.read() + + .. method:: set_trigger_mode(mode) + + Set trigger mode. + + :param int mode: The trigger mode. Available options: + + - ``TRIGGER_MODE_KEY``: Key Mode, Decoding starts when the trigger pin is low and stops when the trigger pin is high. + - ``TRIGGER_MODE_CONTINUOUS``: Call start_decode() to start decoding and stop_decode() to stop decoding. + - ``TRIGGER_MODE_AUTO``: Auto Mode, Performs continuous decoding upon power-up and cannot be stopped. + - ``TRIGGER_MODE_PULSE``: Pulse Mode, A 20ms low-level pulse on the trigger pin initiates a single decoding operation. + - ``TRIGGER_MODE_MOTION_SENSING``: Motion Sensing Mode, Uses image recognition; decoding starts when a scene change is detected. + + UiFlow2 Code Block: + + |set_trigger_mode.png| + + MicroPython Code Block: + + .. code-block:: python + + base_qrcode2.set_trigger_mode(mode) + + .. method:: set_decode_delay(delay_ms) + + Set decode delay. + + :param int delay_ms: decode delay time(ms), 0 means continuous decoding until success. + + UiFlow2 Code Block: + + |set_decode_delay.png| + + MicroPython Code Block: + + .. code-block:: python + + base_qrcode2.set_decode_delay(delay_ms) + + .. method:: set_trigger_timeout(timeout_ms) + + Set trigger timeout. + + :param timeout_ms: trigger timeout time(ms), Decoding will automatically stop when the duration exceeds this value. + + UiFlow2 Code Block: + + |set_trigger_timeout.png| + + MicroPython Code Block: + + .. code-block:: python + + base_qrcode2.set_trigger_timeout(timeout_ms) + + .. method:: set_motion_sensitivity(level) + + Set motion detection sensitivity. (in Motion Sensing Mode) + + :param int level: sensitivity level. Range: 1~5. The higher the level, the more sensitive it is to scene changes. + + UiFlow2 Code Block: + + |set_motion_sensitivity.png| + + MicroPython Code Block: + + .. code-block:: python + + base_qrcode2.set_motion_sensitivity(level) + + .. method:: set_continuous_decode_delay(delay_ms) + + Set continuous decode delay. (in Motion Sensing Mode) + + :param int delay_ms: delay time(unit: 100ms), 0 means continuous decoding until timeout. + + UiFlow2 Code Block: + + |set_continuous_decode_delay.png| + + MicroPython Code Block: + + .. code-block:: python + + base_qrcode2.set_continuous_decode_delay(delay_ms) + + .. method:: set_trigger_decode_delay(delay_ms): + + Set trigger decode delay. (in Motion Sensing Mode) + + Sets the trigger decoding delay time. This is the delay between re-entering the scene change detection phase and starting recognition again after detecting a change. + + :param int delay_ms: Trigger decode delay time(unit: ms). + + UiFlow2 Code Block: + + |set_trigger_decode_delay.png| + + MicroPython Code Block: + + .. code-block:: python + + base_qrcode2.set_trigger_decode_delay(delay_ms) + + .. method:: set_same_code_interval(interval_ms) + + Set same code interval. + + :param int interval_ms: The interval time for repeated recognition of the same code (unit: ms). + + UiFlow2 Code Block: + + |set_same_code_interval.png| + + MicroPython Code Block: + + .. code-block:: python + + base_qrcode2.set_same_code_interval(interval_ms) + + .. method:: set_diff_code_interval(interval_ms) + + Set difference code interval. + + :param int interval_ms: The interval time for repeated recognition of the difference code (unit: ms). + + UiFlow2 Code Block: + + |set_diff_code_interval.png| + + MicroPython Code Block: + + .. code-block:: python + + base_qrcode2.set_diff_code_interval(interval_ms) + + .. method:: set_same_code_no_delay(enable) + + Set same code no delay. + + :param bool enable: Whether to enable non-delay output for the same code. True means enabled, False means disabled. + + UiFlow2 Code Block: + + |set_same_code_no_delay.png| + + MicroPython Code Block: + + .. code-block:: python + + base_qrcode2.set_same_code_no_delay(enable) + + .. method:: set_fill_light_mode(mode) + + Set fill light mode. + + :param int mode: The fill light mode. Available options: + + - ``FILL_LIGHT_OFF``: Light off. + - ``FILL_LIGHT_ON``: Light on. + - ``FILL_LIGHT_ON_DECODE``: Light on during decoding. + + UiFlow2 Code Block: + + |set_fill_light_mode.png| + + MicroPython Code Block: + + .. code-block:: python + + base_qrcode2.set_fill_light_mode(mode) + + .. method:: set_fill_light_brightness(brightness) + + Set fill light brightness. + + :param int brightness: The fill light brightness. Range: 0~100. + + UiFlow2 Code Block: + + |set_fill_light_brightness.png| + + MicroPython Code Block: + + .. code-block:: python + + base_qrcode2.set_fill_light_brightness(brightness) + + .. method:: set_pos_light_mode(mode) + + Set positioning light mode. + + :param int mode: The positioning light mode. Available options: + + - ``POS_LIGHT_OFF``: Light off. + - ``POS_LIGHT_ON_DECODE``: Light on during decoding. + - ``POS_LIGHT_FLASH_ON_DECODE``: Light flash during decoding. + + UiFlow2 Code Block: + + |set_pos_light_mode.png| + + MicroPython Code Block: + + .. code-block:: python + + base_qrcode2.set_pos_light_mode(mode) + + .. method:: set_startup_tone(mode) + + Set startup tone. + + :param int mode: + + - ``0``: Disable startup tone. + - ``1``: Play 4 beeps. + - ``2``: Play 2 beeps. + + UiFlow2 Code Block: + + |set_startup_tone.png| + + MicroPython Code Block: + + .. code-block:: python + + base_qrcode2.set_startup_tone(mode) + + .. method:: set_decode_success_beep(count) + + Set decode success beep. + + :param int count: + + - ``0``: No prompt sound. + - ``1``: Play prompt sound once. + - ``2``: Play prompt sound twice. + + UiFlow2 Code Block: + + |set_decode_success_beep.png| + + MicroPython Code Block: + + .. code-block:: python + + base_qrcode2.set_decode_success_beep(count) + + .. method:: set_case_conversion(mode) + + Set case conversion. + + :param int mode: + + - ``0``: Off (Original data). + - ``1``: Convert to uppercase. + - ``2``: Convert to lowercase. + + UiFlow2 Code Block: + + |set_case_conversion.png| + + MicroPython Code Block: + + .. code-block:: python + + base_qrcode2.set_case_conversion(mode) + + .. method:: set_protocol_format(mode) + + :param int mode: + + - ``0``: No protocol + - ``1``: Format 1: [0x03] + Data Length (2 bytes) + Data + - ``2``: Format 2: [0x03] + Data Length + Number of Barcodes + Code 1 Data Length + Code 1 Data + ... + CRC + - ``3``: Format 3: [0x03] + Data Length + Number of Barcodes + Code 1 ID + Code 1 Data Length + Code 1 Data + ... + CRC + + CRC generate reference program. + + .. code-block:: python + + def crc16_calc(data: bytes) -> int: + ca_crc = 0 + for byte in data: + for i in range(7, -1, -1): + if ca_crc & 0x8000: + ca_crc = (ca_crc << 1) ^ 0x18005 + else: + ca_crc <<= 1 + if (byte & (1 << i)) != 0: + ca_crc ^= 0x18005 + return ca_crc & 0xFFFF + + UiFlow2 Code Block: + + |set_protocol_format.png| + + MicroPython Code Block: + + .. code-block:: python + + base_qrcode2.set_protocol_format(mode) + diff --git a/docs/en/module/index.rst b/docs/en/module/index.rst index 73f7bc0b..51196c5a 100644 --- a/docs/en/module/index.rst +++ b/docs/en/module/index.rst @@ -31,6 +31,7 @@ Module pm25.rst pps.rst pwrcan.rst + qrcode.rst rca.rst relay_2.rst rs232.rst diff --git a/docs/en/module/qrcode.rst b/docs/en/module/qrcode.rst new file mode 100644 index 00000000..3f278580 --- /dev/null +++ b/docs/en/module/qrcode.rst @@ -0,0 +1,555 @@ +QRCode Module +============================ + +.. sku: + +.. include:: ../refs/module.qrcode.ref + +This library is the driver for Module13.2 QRCode, and the module communicates via UART. + +Support the following products: + + |Module13.2 QRCode| + +UiFlow2 Example: +-------------------------- + +QRCode Scan in Continuous Mode +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Open the |cores3_qrcode_continuous_mode_example.m5f2| project in UiFlow2. + +In **Continuous Mode**, pressing the button once starts decoding, and pressing the button again stops decoding. + +UiFlow2 Code Block: + + |cores3_qrcode_continuous_mode_example.png| + +Example output: + + None + +QRCode Scan in Auto Mode +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Open the |cores3_qrcode_auto_mode_example.m5f2| project in UiFlow2. + +In **Auto Mode**, the module starts decoding when powered on and cannot be stopped. + +UiFlow2 Code Block: + + |cores3_qrcode_auto_mode_example.png| + +Example output: + + None + +QRCode Scan in Pulse Mode +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Open the |cores3_qrcode_pulse_mode_example.m5f2| project in UiFlow2. + +In **Pulse Mode**, set the TRIG pin to hold a low level for more than 20ms to trigger decoding once. + +UiFlow2 Code Block: + + |cores3_qrcode_pulse_mode_example.png| + +Example output: + + None + +QRCode Scan in Motion Sensing Mode +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Open the |cores3_qrcode_motion_sensing_mode_example.m5f2| project in UiFlow2. + +In **Motion Sensing Mode**, the module automatically triggers decoding when it detects a change in the scene based on visual recognition information. + +UiFlow2 Code Block: + + |cores3_qrcode_motion_sensing_mode_example.png| + +Example output: + + None + +MicroPython Example: +-------------------------- + +QRCode Scan in Continuous Mode +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +In **Continuous Mode**, pressing the button once starts decoding, and pressing the button again stops decoding. + +MicroPython Code Block: + + .. literalinclude:: ../../../examples/module/qrcode/cores3_qrcode_continuous_mode_example.py + :language: python + :linenos: + +Example output: + + None + +QRCode Scan in Auto Mode +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +In **Auto Mode**, the module starts decoding when powered on and cannot be stopped. + +MicroPython Code Block: + + .. literalinclude:: ../../../examples/module/qrcode/cores3_qrcode_auto_mode_example.py + :language: python + :linenos: + +Example output: + + None + +QRCode Scan in Pulse Mode +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +In **Pulse Mode**, set the TRIG pin to hold a low level for more than 20ms to trigger decoding once. + +MicroPython Code Block: + + .. literalinclude:: ../../../examples/module/qrcode/cores3_qrcode_pulse_mode_example.py + :language: python + :linenos: + +Example output: + + None + +QRCode Scan in Motion Sensing Mode +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +In **Motion Sensing Mode**, the module automatically triggers decoding when it detects a change in the scene based on visual recognition information. + +MicroPython Code Block: + + .. literalinclude:: ../../../examples/module/qrcode/cores3_qrcode_motion_sensing_mode_example.py + :language: python + :linenos: + +Example output: + + None + +**API** +-------------------------- + +QRCodeModule +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. class:: module.qrcode.QRCodeModule + + Create an QRCodeModule object. + + :param int id: UART id. + :param int tx: the UART TX pin. + :param int rx: the UART RX pin. + + UiFlow2 Code Block: + + |init.png| + + MicroPython Code Block: + + .. code-block:: python + + from module import ModuleQRCode + + module_qrcode = ModuleQRCode(id = 1, tx = 17, rx = 18) + + .. method:: set_power(enable) + + Set power. + + :param bool enable: + + - ``True`` : power on. + - ``False`` : power off. + + UiFlow2 Code Block: + + |set_power.png| + + MicroPython Code Block: + + .. code-block:: python + + module_qrcode.set_power(enable) + + .. method:: set_trig(value) + + Set trigger pin value. + + :param int value: + + - ``0`` : low level. + - ``1`` : high level. + + UiFlow2 Code Block: + + |set_trig.png| + + MicroPython Code Block: + + .. code-block:: python + + module_qrcode.set_trig(value) + + .. method:: start_decode() + + Start decode. + + UiFlow2 Code Block: + + |start_decode.png| + + MicroPython Code Block: + + .. code-block:: python + + module_qrcode.start_decode() + + .. method:: stop_decode() + + Stop decode. + + UiFlow2 Code Block: + + |stop_decode.png| + + MicroPython Code Block: + + .. code-block:: python + + module_qrcode.stop_decode() + + .. method:: read() + + Read decode data. + + :returns: qrcode data. + :rtype: None | bytes + + If no data is received, return None. + + UiFlow2 Code Block: + + |read.png| + + MicroPython Code Block: + + .. code-block:: python + + module_qrcode.read() + + .. method:: set_trigger_mode(mode) + + Set trigger mode. + + :param int mode: The trigger mode. Available options: + + - ``TRIGGER_MODE_KEY``: Key Mode, Triggers a single decode; decoding stops after a successful read. + - ``TRIGGER_MODE_CONTINUOUS``: Continuous Mode, Triggers continuous decoding; decoding continues even after a successful read and stops only when triggered again. + - ``TRIGGER_MODE_AUTO``: Auto Mode, Performs continuous decoding upon power-up and cannot be stopped. + - ``TRIGGER_MODE_PULSE``: Pulse Mode, The Trig pin's low-level signal triggers decoding, which stops after a successful read or when the single read time limit is reached. + - ``TRIGGER_MODE_MOTION_SENSING``: Motion Sensing Mode, Uses image recognition; decoding starts when a scene change is detected. + + UiFlow2 Code Block: + + |set_trigger_mode.png| + + MicroPython Code Block: + + .. code-block:: python + + module_qrcode.set_trigger_mode(mode) + + .. method:: set_decode_delay(delay_ms) + + Set decode delay. + + :param int delay_ms: decode delay time(ms), 0 means continuous decoding until success. + + UiFlow2 Code Block: + + |set_decode_delay.png| + + MicroPython Code Block: + + .. code-block:: python + + module_qrcode.set_decode_delay(delay_ms) + + .. method:: set_trigger_timeout(timeout_ms) + + Set trigger timeout. + + :param timeout_ms: trigger timeout time(ms), Decoding will automatically stop when the duration exceeds this value. + + UiFlow2 Code Block: + + |set_trigger_timeout.png| + + MicroPython Code Block: + + .. code-block:: python + + module_qrcode.set_trigger_timeout(timeout_ms) + + .. method:: set_motion_sensitivity(level) + + Set motion detection sensitivity. (in Motion Sensing Mode) + + :param int level: sensitivity level. Range: 1~5. The higher the level, the more sensitive it is to scene changes. + + UiFlow2 Code Block: + + |set_motion_sensitivity.png| + + MicroPython Code Block: + + .. code-block:: python + + module_qrcode.set_motion_sensitivity(level) + + .. method:: set_continuous_decode_delay(delay_100ms) + + Set continuous decode delay. (in Motion Sensing Mode) + + :param int delay_ms: delay time(unit: 100ms), 0 means continuous decoding until timeout. + + UiFlow2 Code Block: + + |set_continuous_decode_delay.png| + + MicroPython Code Block: + + .. code-block:: python + + module_qrcode.set_continuous_decode_delay(delay_ms) + + .. method:: set_trigger_decode_delay(delay_ms): + + Set trigger decode delay. (in Motion Sensing Mode) + + Sets the trigger decoding delay time. This is the delay between re-entering the scene change detection phase and starting recognition again after detecting a change. + + :param int delay_ms: Trigger decode delay time(unit: ms). + + UiFlow2 Code Block: + + |set_trigger_decode_delay.png| + + MicroPython Code Block: + + .. code-block:: python + + module_qrcode.set_trigger_decode_delay(delay_ms) + + .. method:: set_same_code_interval(interval_ms) + + Set same code interval. + + :param int interval_ms: The interval time for repeated recognition of the same code (unit: ms). + + UiFlow2 Code Block: + + |set_same_code_interval.png| + + MicroPython Code Block: + + .. code-block:: python + + module_qrcode.set_same_code_interval(interval_ms) + + .. method:: set_diff_code_interval(interval_ms) + + Set difference code interval. + + :param int interval_ms: The interval time for repeated recognition of the difference code (unit: ms). + + UiFlow2 Code Block: + + |set_diff_code_interval.png| + + MicroPython Code Block: + + .. code-block:: python + + module_qrcode.set_diff_code_interval(interval_ms) + + .. method:: set_same_code_no_delay(enable) + + Set same code no delay. + + :param bool enable: Whether to enable non-delay output for the same code. True means enabled, False means disabled. + + UiFlow2 Code Block: + + |set_same_code_no_delay.png| + + MicroPython Code Block: + + .. code-block:: python + + module_qrcode.set_same_code_no_delay(enable) + + .. method:: set_fill_light_mode(mode) + + Set fill light mode. + + :param int mode: The fill light mode. Available options: + + - ``FILL_LIGHT_OFF``: Light off. + - ``FILL_LIGHT_ON``: Light on. + - ``FILL_LIGHT_ON_DECODE``: Light on during decoding. + + UiFlow2 Code Block: + + |set_fill_light_mode.png| + + MicroPython Code Block: + + .. code-block:: python + + module_qrcode.set_fill_light_mode(mode) + + .. method:: set_fill_light_brightness(brightness) + + Set fill light brightness. + + :param int brightness: The fill light brightness. Range: 0~100. + + UiFlow2 Code Block: + + |set_fill_light_brightness.png| + + MicroPython Code Block: + + .. code-block:: python + + module_qrcode.set_fill_light_brightness(brightness) + + .. method:: set_pos_light_mode(mode) + + Set positioning light mode. + + :param int mode: The positioning light mode. Available options: + + - ``POS_LIGHT_OFF``: Light off. + - ``POS_LIGHT_ON_DECODE``: Light on during decoding. + - ``POS_LIGHT_FLASH_ON_DECODE``: Light flash during decoding. + + UiFlow2 Code Block: + + |set_pos_light_mode.png| + + MicroPython Code Block: + + .. code-block:: python + + module_qrcode.set_pos_light_mode(mode) + + .. method:: set_startup_tone(mode) + + Set startup tone. + + :param int mode: + + - ``0``: Disable startup tone. + - ``1``: Play 4 beeps. + - ``2``: Play 2 beeps. + + UiFlow2 Code Block: + + |set_startup_tone.png| + + MicroPython Code Block: + + .. code-block:: python + + module_qrcode.set_startup_tone(mode) + + .. method:: set_decode_success_beep(count) + + Set decode success beep. + + :param int count: + + - ``0``: No prompt sound. + - ``1``: Play prompt sound once. + - ``2``: Play prompt sound twice. + + UiFlow2 Code Block: + + |set_decode_success_beep.png| + + MicroPython Code Block: + + .. code-block:: python + + module_qrcode.set_decode_success_beep(count) + + .. method:: set_case_conversion(mode) + + Set case conversion. + + :param int mode: + + - ``0``: Off (Original data). + - ``1``: Convert to uppercase. + - ``2``: Convert to lowercase. + + UiFlow2 Code Block: + + |set_case_conversion.png| + + MicroPython Code Block: + + .. code-block:: python + + module_qrcode.set_case_conversion(mode) + + .. method:: set_protocol_format(mode): + + .. method:: set_protocol_format(mode) + + :param int mode: + + - ``0``: No protocol + - ``1``: Format 1: [0x03] + Data Length (2 bytes) + Data + - ``2``: Format 2: [0x03] + Data Length + Number of Barcodes + Code 1 Data Length + Code 1 Data + ... + CRC + - ``3``: Format 3: [0x03] + Data Length + Number of Barcodes + Code 1 ID + Code 1 Data Length + Code 1 Data + ... + CRC + + CRC generate reference program. + + .. code-block:: python + + def crc16_calc(data: bytes) -> int: + ca_crc = 0 + for byte in data: + for i in range(7, -1, -1): + if ca_crc & 0x8000: + ca_crc = (ca_crc << 1) ^ 0x18005 + else: + ca_crc <<= 1 + if (byte & (1 << i)) != 0: + ca_crc ^= 0x18005 + return ca_crc & 0xFFFF + + UiFlow2 Code Block: + + |set_protocol_format.png| + + MicroPython Code Block: + + .. code-block:: python + + module_qrcode.set_protocol_format(mode) + diff --git a/docs/en/refs/base.qrcode.ref b/docs/en/refs/base.qrcode.ref new file mode 100644 index 00000000..b3655c2a --- /dev/null +++ b/docs/en/refs/base.qrcode.ref @@ -0,0 +1,71 @@ +.. |Atomic QRCode Base| image:: https://static-cdn.m5stack.com/resource/docs/products/atom/ATOM%20QR-CODE_v1.1(Excluding%20Atom)/img-3fe4e63e-ae76-4a9b-b3fc-948adb3c274d.jpg + :target: https://docs.m5stack.com/zh_CN/atom/ATOM%20QR-CODE_v1.1(Excluding%20Atom) + :height: 200px + :width: 200px + +.. |init.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/qrcode/init.png +.. |read.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/qrcode/read.png +.. |start_decode.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/qrcode/start_decode.png +.. |stop_decode.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/qrcode/stop_decode.png +.. |set_trig.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/qrcode/set_trig.png +.. |set_trigger_mode.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/qrcode/set_trigger_mode.png +.. |set_decode_continuous.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/qrcode/set_decode_continuous.png +.. |set_decode_interval.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/qrcode/set_decode_interval.png +.. |set_same_code_interval.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/qrcode/set_same_code_interval.png +.. |set_pos_light_mode.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/qrcode/set_pos_light_mode.png +.. |set_fill_light_mode.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/qrcode/set_fill_light_mode.png +.. |set_startup_tone.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/qrcode/set_startup_tone.png +.. |set_decode_success_tone.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/qrcode/set_decode_success_tone.png +.. |set_config_tone.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/qrcode/set_config_tone.png + +.. |atoms3_qrcode_key_mode_example.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/qrcode/key_mode_example.png +.. |atoms3_qrcode_auto_mode_example.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/qrcode/auto_mode_example.png +.. |atoms3_qrcode_host_mode_example.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/qrcode/host_mode_example.png +.. |atoms3_qrcode_motion_sensing_mode_example.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/qrcode/motion_sensing_mode_example.png +.. |atoms3_qrcode_pulse_mode_example.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/qrcode/pulse_mode_example.png + +.. |atoms3_qrcode_auto_mode_example.m5f2| raw:: html + + + atoms3_qrcode_auto_mode_example.m5f2 + + +.. |atoms3_qrcode_host_mode_example.m5f2| raw:: html + + + atoms3_qrcode_host_mode_example.m5f2 + + +.. |atoms3_qrcode_key_mode_example.m5f2| raw:: html + + + atoms3_qrcode_key_mode_example.m5f2 + + +.. |atoms3_qrcode_motion_sensing_mode_example.m5f2| raw:: html + + + atoms3_qrcode_motion_sensing_mode_example.m5f2 + + +.. |atoms3_qrcode_pulse_mode_example.m5f2| raw:: html + + + atoms3_qrcode_pulse_mode_example.m5f2 + + diff --git a/docs/en/refs/base.qrcode2.ref b/docs/en/refs/base.qrcode2.ref new file mode 100644 index 00000000..d7a5ceea --- /dev/null +++ b/docs/en/refs/base.qrcode2.ref @@ -0,0 +1,80 @@ +.. |Atomic QRCode2 Base| image:: https://m5stack.oss-cn-shenzhen.aliyuncs.com/resource/docs/products/atom/Atomic%20QRCode2%20Base/4.webp + :target: https://docs.m5stack.com/zh_CN/atom/Atomic%20QRCode2%20Base + :height: 200px + :width: 200px + +.. |init.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/qrcode2/init.png +.. |set_trig.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/qrcode2/set_trig.png +.. |start_decode.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/qrcode2/start_decode.png +.. |stop_decode.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/qrcode2/stop_decode.png +.. |read.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/qrcode2/read.png +.. |set_trigger_mode.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/qrcode2/set_trigger_mode.png +.. |set_decode_delay.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/qrcode2/set_decode_delay.png +.. |set_trigger_timeout.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/qrcode2/set_trigger_timeout.png +.. |set_motion_sensitivity.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/qrcode2/set_motion_sensitivity.png +.. |set_continuous_decode_delay.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/qrcode2/set_continuous_decode_delay.png +.. |set_trigger_decode_delay.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/qrcode2/set_trigger_decode_delay.png +.. |set_same_code_interval.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/qrcode2/set_same_code_interval.png +.. |set_diff_code_interval.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/qrcode2/set_diff_code_interval.png +.. |set_same_code_no_delay.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/qrcode2/set_same_code_no_delay.png +.. |set_fill_light_mode.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/qrcode2/set_fill_light_mode.png +.. |set_fill_light_brightness.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/qrcode2/set_fill_light_brightness.png +.. |set_pos_light_mode.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/qrcode2/set_pos_light_mode.png +.. |set_startup_tone.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/qrcode2/set_startup_tone.png +.. |set_decode_success_beep.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/qrcode2/set_decode_success_beep.png +.. |set_case_conversion.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/qrcode2/set_case_conversion.png +.. |set_protocol_format.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/qrcode2/set_protocol_format.png + +.. |atoms3_qrcode2_key_mode_example.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/qrcode2/key_mode_example.png +.. |atoms3_qrcode2_continuous_mode_example.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/qrcode2/continuous_mode_example.png +.. |atoms3_qrcode2_auto_mode_example.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/qrcode2/auto_mode_example.png +.. |atoms3_qrcode2_pulse_mode_example.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/qrcode2/pulse_mode_example.png +.. |atoms3_qrcode2_motion_sensing_mode_example.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/qrcode2/motion_sensing_mode_example.png + +.. |atoms3_qrcode2_key_mode_example.m5f2| raw:: html + + + atoms3_qrcode2_key_mode_example.m5f2 + + +.. |atoms3_qrcode2_continuous_mode_example.m5f2| raw:: html + + + atoms3_qrcode2_continuous_mode_example.m5f2 + + +.. |atoms3_qrcode2_auto_mode_example.m5f2| raw:: html + + + atoms3_qrcode2_auto_mode_example.m5f2 + + +.. |atoms3_qrcode2_pulse_mode_example.m5f2| raw:: html + + + atoms3_qrcode2_pulse_mode_example.m5f2 + + + +.. |atoms3_qrcode2_motion_sensing_mode_example.m5f2| raw:: html + + + atoms3_qrcode2_motion_sensing_mode_example.m5f2 + + + diff --git a/docs/en/refs/module.qrcode.ref b/docs/en/refs/module.qrcode.ref new file mode 100644 index 00000000..6490b68b --- /dev/null +++ b/docs/en/refs/module.qrcode.ref @@ -0,0 +1,71 @@ +.. |Module13.2 QRCode| image:: https://m5stack.oss-cn-shenzhen.aliyuncs.com/resource/docs/products/module/QRCodeModule/4.webp + :target: https://docs.m5stack.com/zh_CN/module/QRCodeModule + :height: 200px + :width: 200px + +.. |init.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/qrcode/init.png +.. |set_power.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/qrcode/set_power.png +.. |set_trig.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/qrcode/set_trig.png +.. |start_decode.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/qrcode/start_decode.png +.. |stop_decode.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/qrcode/stop_decode.png +.. |read.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/qrcode/read.png +.. |set_trigger_mode.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/qrcode/set_trigger_mode.png +.. |set_decode_delay.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/qrcode/set_decode_delay.png +.. |set_trigger_timeout.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/qrcode/set_trigger_timeout.png +.. |set_motion_sensitivity.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/qrcode/set_motion_sensitivity.png +.. |set_continuous_decode_delay.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/qrcode/set_continuous_decode_delay.png +.. |set_trigger_decode_delay.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/qrcode/set_trigger_decode_delay.png +.. |set_same_code_interval.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/qrcode/set_same_code_interval.png +.. |set_diff_code_interval.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/qrcode/set_diff_code_interval.png +.. |set_same_code_no_delay.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/qrcode/set_same_code_no_delay.png +.. |set_fill_light_mode.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/qrcode/set_fill_light_mode.png +.. |set_fill_light_brightness.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/qrcode/set_fill_light_brightness.png +.. |set_pos_light_mode.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/qrcode/set_pos_light_mode.png +.. |set_startup_tone.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/qrcode/set_startup_tone.png +.. |set_decode_success_beep.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/qrcode/set_decode_success_beep.png +.. |set_case_conversion.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/qrcode/set_case_conversion.png +.. |set_protocol_format.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/qrcode/set_protocol_format.png + +.. |cores3_qrcode_continuous_mode_example.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/qrcode/continuous_mode_example.png +.. |cores3_qrcode_auto_mode_example.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/qrcode/auto_mode_example.png +.. |cores3_qrcode_pulse_mode_example.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/qrcode/pulse_mode_example.png +.. |cores3_qrcode_motion_sensing_mode_example.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/qrcode/motion_sensing_mode_example.png + +.. |cores3_qrcode_continuous_mode_example.m5f2| raw:: html + + + cores3_qrcode_continuous_mode_example.m5f2 + + +.. |cores3_qrcode_auto_mode_example.m5f2| raw:: html + + + cores3_qrcode_auto_mode_example.m5f2 + + +.. |cores3_qrcode_pulse_mode_example.m5f2| raw:: html + + + cores3_qrcode_pulse_mode_example.m5f2 + + + +.. |cores3_qrcode_motion_sensing_mode_example.m5f2| raw:: html + + + cores3_qrcode_motion_sensing_mode_example.m5f2 + + + diff --git a/docs/locales/zh_CN/LC_MESSAGES/base/qrcode.po b/docs/locales/zh_CN/LC_MESSAGES/base/qrcode.po new file mode 100644 index 00000000..16be07ae --- /dev/null +++ b/docs/locales/zh_CN/LC_MESSAGES/base/qrcode.po @@ -0,0 +1,616 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2016 - 2025 M5Stack Technology Co., Ltd +# This file is distributed under the same license as the UIFlow2 Programming +# Guide package. +# FIRST AUTHOR , 2025. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: UIFlow2 Programming Guide \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-04-02 14:09+0800\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: zh_CN\n" +"Language-Team: zh_CN \n" +"Plural-Forms: nplurals=1; plural=0;\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.16.0\n" + +#: ../../en/base/qrcode.rst:2 ../../en/refs/base.qrcode.ref +#: 7b40537339fc4494b4ca5f3ed1b13d01 c52f827db1c94c628cb0bc315f66a057 +msgid "Atomic QRCode Base" +msgstr "" + +#: ../../en/base/qrcode.rst:8 7db66204ddc64b35a585d37838f1d95a +msgid "" +"This library is the driver for Atomic QRCode Base, and the module " +"communicates via UART." +msgstr "这个库是 Atomic QRCode Base 的驱动,该模块使用 UART 通信。" + +#: ../../en/base/qrcode.rst:10 bdf57674d78c4967b71483a315b6db8a +msgid "Support the following products:" +msgstr "支持以下产品:" + +#: ../../en/base/qrcode.rst:12 eaa49b0bad684aef86dc7f9d53ec4303 +msgid "|Atomic QRCode Base|" +msgstr "" + +#: ../../en/base/qrcode.rst:15 5b33f52fe9ee4f81b0be2ac3f8078223 +msgid "UiFlow2 Example:" +msgstr "UiFlow2 应用示例:" + +#: ../../en/base/qrcode.rst:18 ../../en/base/qrcode.rst:97 +#: 906100b3bac44610ac32eb4783a1e982 +msgid "QRCode Scan in Key Mode" +msgstr "按键模式二维码识别" + +#: ../../en/base/qrcode.rst:20 1131d3397dbb457d9267babec00e2683 +msgid "Open the |atoms3_qrcode_key_mode_example.m5f2| project in UiFlow2." +msgstr "在 UiFlow2 上打开 |atoms3_qrcode_key_mode_example.m5f2| 项目。" + +#: ../../en/base/qrcode.rst:22 ../../en/base/qrcode.rst:99 +#: 73da58b25bcf4efba69ec551342b4067 +msgid "" +"In **Key Mode**, the module starts decoding when the button is pressed " +"and stops decoding when the button is released. After a successful " +"decoding, it stops decoding. To continue decoding, the button must be " +"released and pressed again." +msgstr "在按键模式下,模块在按下按键时开始解码,并在释放按键时停止解码" + +#: ../../en/base/qrcode.rst:24 ../../en/base/qrcode.rst:39 +#: ../../en/base/qrcode.rst:54 ../../en/base/qrcode.rst:69 +#: ../../en/base/qrcode.rst:84 604c7a4dad4f4032aa71c8a2376d3bad +#: base.qrcode.AtomicQRCodeBase:9 base.qrcode.AtomicQRCodeBase.read:6 +#: base.qrcode.AtomicQRCodeBase.set_config_tone:5 +#: base.qrcode.AtomicQRCodeBase.set_decode_continuous:5 +#: base.qrcode.AtomicQRCodeBase.set_decode_interval:5 +#: base.qrcode.AtomicQRCodeBase.set_decode_success_tone:5 +#: base.qrcode.AtomicQRCodeBase.set_fill_light_mode:8 +#: base.qrcode.AtomicQRCodeBase.set_pos_light_mode:8 +#: base.qrcode.AtomicQRCodeBase.set_same_code_interval:5 +#: base.qrcode.AtomicQRCodeBase.set_startup_tone:5 +#: base.qrcode.AtomicQRCodeBase.set_trig:5 +#: base.qrcode.AtomicQRCodeBase.set_trigger_mode:11 +#: base.qrcode.AtomicQRCodeBase.start_decode:3 +#: base.qrcode.AtomicQRCodeBase.stop_decode:3 of +msgid "UiFlow2 Code Block:" +msgstr "UiFlow2 代码块:" + +#: ../../en/base/qrcode.rst:26 5bfed0b94f5342ad9dd66e1fc6ac0504 +msgid "|atoms3_qrcode_key_mode_example.png|" +msgstr "" + +#: ../../en/refs/base.qrcode.ref:21 bed01f7b08ba47e685a2f853a22fdd87 +msgid "atoms3_qrcode_key_mode_example.png" +msgstr "" + +#: ../../en/base/qrcode.rst:28 ../../en/base/qrcode.rst:43 +#: ../../en/base/qrcode.rst:58 ../../en/base/qrcode.rst:73 +#: ../../en/base/qrcode.rst:88 ../../en/base/qrcode.rst:107 +#: ../../en/base/qrcode.rst:122 ../../en/base/qrcode.rst:137 +#: ../../en/base/qrcode.rst:152 ../../en/base/qrcode.rst:167 +#: 2afdcac45a684e2a90bb16379edbf8cb +msgid "Example output:" +msgstr "示例输出:" + +#: ../../en/base/qrcode.rst:30 ../../en/base/qrcode.rst:45 +#: ../../en/base/qrcode.rst:60 ../../en/base/qrcode.rst:75 +#: ../../en/base/qrcode.rst:90 ../../en/base/qrcode.rst:109 +#: ../../en/base/qrcode.rst:124 ../../en/base/qrcode.rst:139 +#: ../../en/base/qrcode.rst:154 ../../en/base/qrcode.rst:169 +#: 4e693db17eb346239283aeb539da887e +msgid "None" +msgstr "无" + +#: ../../en/base/qrcode.rst:33 ../../en/base/qrcode.rst:112 +#: 88aa5d9d1fb24e9bb4c926e05d388589 +msgid "QRCode Scan in Host Mode" +msgstr "主机模式二维码识别" + +#: ../../en/base/qrcode.rst:35 c779342f79b74b1f935e81b339d2964f +msgid "Open the |atoms3_qrcode_host_mode_example.m5f2| project in UiFlow2." +msgstr "在 UiFlow2 上打开 |atoms3_qrcode_host_mode_example.m5f2| 项目。" + +#: ../../en/base/qrcode.rst:37 ../../en/base/qrcode.rst:114 +#: c7d55e435c3c4d2697ad0ef7e0777ef5 +msgid "" +"In **Host Mode**, pressing the button once starts decoding, and pressing " +"the button again stops decoding." +msgstr "在主机模式下,按下一次按键开始解码,再次按下停止解码。" + +#: ../../en/base/qrcode.rst:41 0f0c32df7be846b6a114955efc407958 +msgid "|atoms3_qrcode_host_mode_example.png|" +msgstr "" + +#: ../../en/refs/base.qrcode.ref:23 1875db2daf104a0fa23fe5e77db6814a +msgid "atoms3_qrcode_host_mode_example.png" +msgstr "" + +#: ../../en/base/qrcode.rst:48 ../../en/base/qrcode.rst:127 +#: 46a08382da834a258b73961598a762e8 +msgid "QRCode Scan in Auto Mode" +msgstr "自动模式二维码识别" + +#: ../../en/base/qrcode.rst:50 d64c3fc29d7d4ff0965cbc29e07a453a +msgid "Open the |atoms3_qrcode_auto_mode_example.m5f2| project in UiFlow2." +msgstr "在 UiFlow2 上打开 |atoms3_qrcode_auto_mode_example.m5f2| 项目。" + +#: ../../en/base/qrcode.rst:52 ../../en/base/qrcode.rst:129 +#: 9c969a916e68495e9e9ee8b4077c932c +msgid "" +"In **Auto Mode**, the module starts decoding when powered on and cannot " +"be stopped." +msgstr "在自动模式下,模块上电开始扫码解码,不可停止。" + +#: ../../en/base/qrcode.rst:56 a7c65d0ae49a4d8e9b5c92ec3e6a0dc9 +msgid "|atoms3_qrcode_auto_mode_example.png|" +msgstr "" + +#: ../../en/refs/base.qrcode.ref:22 1875db2daf104a0fa23fe5e77db6814a +msgid "atoms3_qrcode_auto_mode_example.png" +msgstr "" + +#: ../../en/base/qrcode.rst:63 ../../en/base/qrcode.rst:142 +#: 56fe99e0922f4bc9874dcd4dbda1369e +msgid "QRCode Scan in Pulse Mode" +msgstr "脉冲模式二维码识别" + +#: ../../en/base/qrcode.rst:65 5ec76b4fa52b46c3b15ede4ba2597d0d +msgid "Open the |atoms3_qrcode_pulse_mode_example.m5f2| project in UiFlow2." +msgstr "在 UiFlow2 上打开 |atoms3_qrcode_pulse_mode_example.m5f2| 项目。" + +#: ../../en/base/qrcode.rst:67 ../../en/base/qrcode.rst:144 +#: b9a8c91888284f48bfa73b51cb9717c7 +msgid "" +"In **Pulse Mode**, set the TRIG pin to hold a low level for more than " +"20ms to trigger decoding once." +msgstr "在脉冲模式下,设置触发引脚超过 20ms 的低电平触发解码一次。" + +#: ../../en/base/qrcode.rst:71 0f0c32df7be846b6a114955efc407958 +msgid "|atoms3_qrcode_pulse_mode_example.png|" +msgstr "" + +#: ../../en/refs/base.qrcode.ref:25 8de7e3a64b3f497aa2d5600421cd4c03 +msgid "atoms3_qrcode_pulse_mode_example.png" +msgstr "" + +#: ../../en/base/qrcode.rst:78 ../../en/base/qrcode.rst:157 +#: 2a06595f469c4a41b5d2776ce1bc391e +msgid "QRCode Scan in Motion Sensing Mode" +msgstr "移动感应模式二维码识别" + +#: ../../en/base/qrcode.rst:80 8dab69b931c247f1b9b8aa2326e4a4d1 +msgid "" +"Open the |atoms3_qrcode_motion_sensing_mode_example.m5f2| project in " +"UiFlow2." +msgstr "在 UiFlow2 上打开 |atoms3_qrcode_motion_sensing_mode_example.m5f2| 项目。" + +#: ../../en/base/qrcode.rst:82 ../../en/base/qrcode.rst:159 +#: ce5efe2cddaa42ebab8e222c2fb90632 +msgid "" +"In **Motion Sensing Mode**, the module automatically triggers decoding " +"when it detects a change in the scene based on visual recognition " +"information." +msgstr "在感应模式下,模块基于视觉识别信息检测到场景变化时,会自动触发解码。" + +#: ../../en/base/qrcode.rst:86 87967366948847668ce131d9ba7ae6de +msgid "|atoms3_qrcode_motion_sensing_mode_example.png|" +msgstr "" + +#: ../../en/refs/base.qrcode.ref:24 2d87ce54a5ab41e79889ceca804ef923 +msgid "atoms3_qrcode_motion_sensing_mode_example.png" +msgstr "" + +#: ../../en/base/qrcode.rst:94 3874c893c1974c058ee6fa50806c9104 +msgid "MicroPython Example:" +msgstr "MicroPython 应用示例:" + +#: ../../en/base/qrcode.rst:101 ../../en/base/qrcode.rst:116 +#: ../../en/base/qrcode.rst:131 ../../en/base/qrcode.rst:146 +#: ../../en/base/qrcode.rst:161 base.qrcode.AtomicQRCodeBase:13 +#: base.qrcode.AtomicQRCodeBase.read:10 +#: base.qrcode.AtomicQRCodeBase.set_config_tone:9 +#: base.qrcode.AtomicQRCodeBase.set_decode_continuous:9 +#: base.qrcode.AtomicQRCodeBase.set_decode_interval:9 +#: base.qrcode.AtomicQRCodeBase.set_decode_success_tone:9 +#: base.qrcode.AtomicQRCodeBase.set_fill_light_mode:12 +#: base.qrcode.AtomicQRCodeBase.set_pos_light_mode:12 +#: base.qrcode.AtomicQRCodeBase.set_same_code_interval:9 +#: base.qrcode.AtomicQRCodeBase.set_startup_tone:9 +#: base.qrcode.AtomicQRCodeBase.set_trig:9 +#: base.qrcode.AtomicQRCodeBase.set_trigger_mode:15 +#: base.qrcode.AtomicQRCodeBase.start_decode:7 +#: base.qrcode.AtomicQRCodeBase.stop_decode:7 e87394b516494acd971be7f5a9d23da8 +#: of +msgid "MicroPython Code Block:" +msgstr "MicroPython 代码块:" + +#: ../../en/base/qrcode.rst:172 fe3cb49c8b75429492f41479fb95b78f +msgid "**API**" +msgstr "API应用" + +#: ../../en/base/qrcode.rst:175 7e8e886fbf584015bc357e9b6af6d3cc +msgid "AtomicQRCodeBase" +msgstr "" + +#: 7303ed7c70b444efab08d2b83915dce8 base.qrcode.AtomicQRCodeBase:1 of +msgid "Bases: :py:class:`object`" +msgstr "" + +#: 6cea6bc11787420c9d1efe7b22ae7a9c base.qrcode.AtomicQRCodeBase:1 of +msgid "Create an AtomicQRCodeBase object." +msgstr "创建一个 AtomicQRCodeBase 对象。" + +#: ../../en/base/qrcode.rst b4a183a49e454c1cb6701b6246e267bc +#: base.qrcode.AtomicQRCodeBase.set_config_tone +#: base.qrcode.AtomicQRCodeBase.set_decode_continuous +#: base.qrcode.AtomicQRCodeBase.set_decode_interval +#: base.qrcode.AtomicQRCodeBase.set_decode_success_tone +#: base.qrcode.AtomicQRCodeBase.set_fill_light_mode +#: base.qrcode.AtomicQRCodeBase.set_pos_light_mode +#: base.qrcode.AtomicQRCodeBase.set_same_code_interval +#: base.qrcode.AtomicQRCodeBase.set_startup_tone +#: base.qrcode.AtomicQRCodeBase.set_trig +#: base.qrcode.AtomicQRCodeBase.set_trigger_mode of +msgid "Parameters" +msgstr "" + +#: 0145bf5a02234ef6b02c7341bc98b1e9 base.qrcode.AtomicQRCodeBase:3 of +msgid "UART id." +msgstr "UART 端口号。" + +#: 52ae2020c2ec4cfabefafb3604ae32df base.qrcode.AtomicQRCodeBase:4 of +msgid "the UART TX pin." +msgstr "UART 发送引脚。" + +#: 3ba38cad19a04d0da508b91f3556a9bc base.qrcode.AtomicQRCodeBase:5 of +msgid "the UART RX pin." +msgstr "UART 接收引脚。" + +#: base.qrcode.AtomicQRCodeBase:6 e36af146bc684c4092d2d09115c709d7 of +msgid "the trigger pin." +msgstr "触发引脚。" + +#: base.qrcode.AtomicQRCodeBase:7 bc8ce60ca46e40ae92ae16d769c48bc5 of +msgid "the receive done pin." +msgstr "接收完成引脚。" + +#: base.qrcode.AtomicQRCodeBase:11 daa82a8690ff4ada98094591652d2b30 of +msgid "|init.png|" +msgstr "" + +#: ../../en/refs/base.qrcode.ref:6 029d663ac83c499bbea8eb453093f776 +msgid "init.png" +msgstr "" + +#: base.qrcode.AtomicQRCodeBase.read:1 fc0ef57268a64d8b90d4066446b7f740 of +msgid "Read decode data." +msgstr "读取解码数据。" + +#: base.qrcode.AtomicQRCodeBase.read eb24ef4de2704da3993b504b260771cb of +msgid "Returns" +msgstr "" + +#: base.qrcode.AtomicQRCodeBase.read:3 fda839b0c2f6407a8fc2513579592b4f of +msgid "qrcode data." +msgstr "二维码数据。" + +#: b1ac1669b30341c5a6b3f772812968f3 base.qrcode.AtomicQRCodeBase.read +#: base.qrcode.AtomicQRCodeBase.set_config_tone +#: base.qrcode.AtomicQRCodeBase.set_decode_continuous +#: base.qrcode.AtomicQRCodeBase.set_decode_interval +#: base.qrcode.AtomicQRCodeBase.set_decode_success_tone +#: base.qrcode.AtomicQRCodeBase.set_fill_light_mode +#: base.qrcode.AtomicQRCodeBase.set_pos_light_mode +#: base.qrcode.AtomicQRCodeBase.set_same_code_interval +#: base.qrcode.AtomicQRCodeBase.set_startup_tone +#: base.qrcode.AtomicQRCodeBase.set_trig +#: base.qrcode.AtomicQRCodeBase.set_trigger_mode +#: base.qrcode.AtomicQRCodeBase.start_decode +#: base.qrcode.AtomicQRCodeBase.stop_decode of +msgid "Return type" +msgstr "" + +#: a4a2c67bbd2c4c3b846ee956a56feabf base.qrcode.AtomicQRCodeBase.read:8 of +msgid "|read.png|" +msgstr "" + +#: ../../en/refs/base.qrcode.ref:7 a5c357b7caf24fe58c9505be600aa96b +msgid "read.png" +msgstr "" + +#: 83d79a9e7f4c4c3f87b5d67055128e7e +#: base.qrcode.AtomicQRCodeBase.set_config_tone:1 of +msgid "Set config tone." +msgstr "设置配置参数提示音。" + +#: 1fec5a0a1b02487aab6b378859261f6a +#: base.qrcode.AtomicQRCodeBase.set_config_tone:3 of +msgid "True - Enable set config tone, False - Disable set config tone." +msgstr "True - 打开配置参数提示音,False - 关闭配置参数提示音。" + +#: base.qrcode.AtomicQRCodeBase.set_config_tone:7 +#: bc1ecdfb4a08446892c461a2034beaa8 of +msgid "|set_config_tone.png|" +msgstr "" + +#: ../../en/refs/base.qrcode.ref:19 d64271e66b2e41cc90d930c901696962 +msgid "set_config_tone.png" +msgstr "" + +#: 426404f989214850b5580923c709ba23 +#: base.qrcode.AtomicQRCodeBase.set_decode_continuous:1 of +msgid "Set continuous decode time." +msgstr "设置解码持续时间。" + +#: 79e4d7b4584440599d3cf9249aa54756 +#: base.qrcode.AtomicQRCodeBase.set_decode_continuous:3 of +msgid "" +"Continuous scanning time(unit: 100ms). Range: 099, i.e., 025,500 ms (0 " +"means unlimited)." +msgstr "解码持续时间(单位: 100ms),范围:0~99,对应 0~9900 ms(0表示无限时)。" + +#: 1b3634bf9aed449893f64ba7ee67d61f +#: base.qrcode.AtomicQRCodeBase.set_decode_continuous:7 of +msgid "|set_decode_continuous.png|" +msgstr "" + +#: ../../en/refs/base.qrcode.ref:12 14800e4518114afbbdc561a3137522c1 +msgid "set_decode_continuous.png" +msgstr "" + +#: 1c8fb4570f8a46ea963c27dbfbcc659a +#: base.qrcode.AtomicQRCodeBase.set_decode_interval:1 of +msgid "Set decode interval." +msgstr "设置解码间隔时间" + +#: 03cc56dbaf7e4961a36da6929779d03b +#: base.qrcode.AtomicQRCodeBase.set_decode_interval:3 of +msgid "Decode interval time(unit: 100ms). Range: 0~99, i.e., 0~9,900 ms." +msgstr "解码间隔时间(单位:100ms),范围:0~99,对应 0~9900 ms。" + +#: 1299eae93f7e4179aad642ca5d501333 +#: base.qrcode.AtomicQRCodeBase.set_decode_interval:7 of +msgid "|set_decode_interval.png|" +msgstr "" + +#: ../../en/refs/base.qrcode.ref:13 c58269e8c9374e5fb62e14647b75a6d7 +msgid "set_decode_interval.png" +msgstr "" + +#: 5107c807678e44e39dd5242e1a4d9443 +#: base.qrcode.AtomicQRCodeBase.set_decode_success_tone:1 of +msgid "Set decode success tone." +msgstr "设置解码成功提示音。" + +#: 5cb06ae15a234aaf8b39b1eb2fc5182a +#: base.qrcode.AtomicQRCodeBase.set_decode_success_tone:3 of +msgid "True - Enable decode success tone, False - Disable decode success tone." +msgstr "True - 打开解码成功提示音,False - 关闭解码成功提示音。" + +#: 47e0f948d8114130ab48d373b193f5f2 +#: base.qrcode.AtomicQRCodeBase.set_decode_success_tone:7 of +msgid "|set_decode_success_tone.png|" +msgstr "" + +#: ../../en/refs/base.qrcode.ref:18 b1f9819c23104d3193b0c16c7deaeecf +msgid "set_decode_success_tone.png" +msgstr "" + +#: base.qrcode.AtomicQRCodeBase.set_fill_light_mode:1 +#: e14595fce3cc4e7183a84e239e1a3ae4 of +msgid "Set fill light mode." +msgstr "设置补光灯模式。" + +#: 788b03ae48b54cf3beea30ed77f4f6f1 +#: base.qrcode.AtomicQRCodeBase.set_fill_light_mode:3 of +msgid "" +"The fill light mode. Available options: - ``FILL_LIGHT_OFF``: Light off. " +"- ``FILL_LIGHT_ON``: Light on. - ``FILL_LIGHT_ON_DECODE``: Light on " +"during decoding." +msgstr "" +"补光灯模式,可选项: - ``FILL_LIGHT_OFF``: 常灭。- ``FILL_LIGHT_ON``: 常亮。 - " +"``FILL_LIGHT_ON_DECODE``: 解码时亮。" + +#: 4f7f19980aa648709955b11cb0447e00 +#: base.qrcode.AtomicQRCodeBase.set_fill_light_mode:10 of +msgid "|set_fill_light_mode.png|" +msgstr "" + +#: ../../en/refs/base.qrcode.ref:16 06ffe2206f14448c9e2c7204012a8aeb +msgid "set_fill_light_mode.png" +msgstr "" + +#: 1c80b13792b3461bbac2b02c239d5f44 +#: base.qrcode.AtomicQRCodeBase.set_pos_light_mode:1 of +msgid "Set positioning light mode." +msgstr "设置定位灯模式。" + +#: base.qrcode.AtomicQRCodeBase.set_pos_light_mode:3 +#: fa17ef2d916f435bb183aa7c15aa5dfe of +msgid "" +"The positioning light mode. Available options: - ``POS_LIGHT_OFF``: Light" +" off. - ``POS_LIGHT_ON``: Light on. - ``POS_LIGHT_ON_DECODE``: Light " +"flash during decoding." +msgstr "" +"定位灯模式,可选项: - ``POS_LIGHT_OFF``: 常灭。 - ``POS_LIGHT_ON``: 常亮。 - " +"``POS_LIGHT_ON_DECODE``: 解码时亮。" + +#: b47787ad7bb649cea8b03651f678a9a7 +#: base.qrcode.AtomicQRCodeBase.set_pos_light_mode:10 of +msgid "|set_pos_light_mode.png|" +msgstr "" + +#: ../../en/refs/base.qrcode.ref:15 9e07ed1254fc4da1a0e85004774d9a9c +msgid "set_pos_light_mode.png" +msgstr "" + +#: 34eacedcb326497489cce9452e7c7bcd +#: base.qrcode.AtomicQRCodeBase.set_same_code_interval:1 of +msgid "Set same code interval." +msgstr "设置同码间隔。" + +#: base.qrcode.AtomicQRCodeBase.set_same_code_interval:3 +#: ebdde3f7becf496c882460a3174b755d of +msgid "" +"Decode interval for the same code(unit: 100ms). Range: 0~99, i.e., " +"0~9,900 ms." +msgstr "同码间隔(单位:100ms), 范围: 0~99, 对应 0~9900ms 。" + +#: 91f2f840b6444d49b5403323e67c0a1e +#: base.qrcode.AtomicQRCodeBase.set_same_code_interval:7 of +msgid "|set_same_code_interval.png|" +msgstr "" + +#: ../../en/refs/base.qrcode.ref:14 e6e1d70e60234a2ba0996a22b035e29a +msgid "set_same_code_interval.png" +msgstr "" + +#: base.qrcode.AtomicQRCodeBase.set_startup_tone:1 +#: e83b9b5c5b274123816c814ddfb19c52 of +msgid "Set startup tone." +msgstr "设置启动提示音。" + +#: base.qrcode.AtomicQRCodeBase.set_startup_tone:3 +#: e4b9a94e6f0c42378a5344c332f9fd50 of +msgid "True - Enable startup tone, False - Disable startup tone." +msgstr "True - 开启启动提示音, False - 关闭启动提示音。" + +#: 0d91bc8778494ecfbb48954459531dbd +#: base.qrcode.AtomicQRCodeBase.set_startup_tone:7 of +msgid "|set_startup_tone.png|" +msgstr "" + +#: ../../en/refs/base.qrcode.ref:17 d8c3921e4bef45579bef6aa272bdf8ed +msgid "set_startup_tone.png" +msgstr "" + +#: base.qrcode.AtomicQRCodeBase.set_trig:1 d5320318612144c2b989eb91a9908007 of +msgid "Set trigger value." +msgstr "设置触发引脚电平。" + +#: 4647fcccbb814468b57fa21233cf1afb base.qrcode.AtomicQRCodeBase.set_trig:3 of +msgid "``0`` - Low level, ``1`` - High level." +msgstr "``0`` - 低电平, ``1`` - 高电平。" + +#: base.qrcode.AtomicQRCodeBase.set_trig:7 f5fc1ef0e5d5435d90b5c869329144a7 of +msgid "|set_trig.png|" +msgstr "" + +#: ../../en/refs/base.qrcode.ref:10 1fbce50db19b4dc6bdbfed820fda01e0 +msgid "set_trig.png" +msgstr "" + +#: base.qrcode.AtomicQRCodeBase.set_trigger_mode:1 +#: ead26d2c52fa470aaff1993e466166ef of +#, fuzzy +msgid "Set trigger mode." +msgstr "设置触发引脚电平。" + +#: 967dcdd6bde0455781b7a46010251f55 +#: base.qrcode.AtomicQRCodeBase.set_trigger_mode:3 of +msgid "" +"The trigger mode. Available options: - ``TRIGGER_MODE_KEY``: Key Mode, " +"Triggers a single decode; decoding stops after a successful read. - " +"``TRIGGER_MODE_HOST``: Host Mode, The command controls the start/stop of " +"decoding. Once triggered, decoding will stop either upon successful " +"decoding or after a timeout of 5 seconds. - ``TRIGGER_MODE_AUTO``: Auto " +"Mode, Performs continuous decoding upon power-up and cannot be stopped. -" +" ``TRIGGER_MODE_PULSE``: Pulse Mode, The Trig pin's low-level signal " +"triggers decoding, which stops after a successful read or when the single" +" read time limit is reached. - ``TRIGGER_MODE_MOTION_SENSING``: Motion " +"Sensing Mode, Uses image recognition; decoding starts when a scene change" +" is detected." +msgstr "" +"触发解码模式,可选项: - ``TRIGGER_MODE_KEY``: 按键模式, " +"触发引脚低电平触发解码,高电平停止解码,解码成功后停止解码。- ``TRIGGER_MODE_HOST``: 主机模式, " +"命令控制解码,解码成功或者超时停止解码- ``TRIGGER_MODE_AUTO``: 自动模式, 上电自动开始解码。- " +"``TRIGGER_MODE_PULSE``: 脉冲模式, 触发引脚 20ms 以上低电平触发一次解码。- " +"``TRIGGER_MODE_MOTION_SENSING``: 移动感应模式,使用图像识别感应,当检测到场景发生变化时,触发解码。" + +#: 75471af763f44c3f8a61359b2a5b01d4 +#: base.qrcode.AtomicQRCodeBase.set_trigger_mode:3 of +msgid "The trigger mode. Available options:" +msgstr "触发解码模式,可选项:" + +#: 146c1bddb138493d802c1340284f6b5c +#: base.qrcode.AtomicQRCodeBase.set_trigger_mode:5 of +msgid "" +"``TRIGGER_MODE_KEY``: Key Mode, Triggers a single decode; decoding stops " +"after a successful read." +msgstr "``TRIGGER_MODE_KEY``: 按键模式, 触发引脚低电平触发解码,高电平停止解码,解码成功后停止解码。" + +#: b6a60fafd98549a4b387491832517656 +#: base.qrcode.AtomicQRCodeBase.set_trigger_mode:6 of +msgid "" +"``TRIGGER_MODE_HOST``: Host Mode, The command controls the start/stop of " +"decoding. Once triggered, decoding will stop either upon successful " +"decoding or after a timeout of 5 seconds." +msgstr "``TRIGGER_MODE_HOST``: 主机模式, 命令控制解码,解码成功或者超时停止解码" + +#: 87dabc703f8348a1b65c0a7392ef9666 +#: base.qrcode.AtomicQRCodeBase.set_trigger_mode:7 of +msgid "" +"``TRIGGER_MODE_AUTO``: Auto Mode, Performs continuous decoding upon " +"power-up and cannot be stopped." +msgstr "`TRIGGER_MODE_AUTO``: 自动模式, 上电自动开始解码。" + +#: 660c021fd8004e519b4aeeb5f9f3ed9d +#: base.qrcode.AtomicQRCodeBase.set_trigger_mode:8 of +msgid "" +"``TRIGGER_MODE_PULSE``: Pulse Mode, The Trig pin's low-level signal " +"triggers decoding, which stops after a successful read or when the single" +" read time limit is reached." +msgstr "`TRIGGER_MODE_PULSE``: 脉冲模式, 触发引脚 20ms 以上低电平触发一次解码。" + +#: 51400f11929e4b7bac36e36611d0a2be +#: base.qrcode.AtomicQRCodeBase.set_trigger_mode:9 of +msgid "" +"``TRIGGER_MODE_MOTION_SENSING``: Motion Sensing Mode, Uses image " +"recognition; decoding starts when a scene change is detected." +msgstr "``TRIGGER_MODE_MOTION_SENSING``: 移动感应模式,使用图像识别感应,当检测到场景发生变化时,触发解码。" + +#: 6a9bd15ea9c3461ea740d01bc432051c +#: base.qrcode.AtomicQRCodeBase.set_trigger_mode:13 of +msgid "|set_trigger_mode.png|" +msgstr "" + +#: ../../en/refs/base.qrcode.ref:11 6127f4e92a2a4a3184108eaa72ef774a +msgid "set_trigger_mode.png" +msgstr "" + +#: base.qrcode.AtomicQRCodeBase.start_decode:1 f476d125d2e041b0bdd49f34ca328301 +#: of +msgid "Start decode." +msgstr "开始解码。" + +#: 23dee6b6c4064c1ba452745c99da3118 base.qrcode.AtomicQRCodeBase.start_decode:5 +#: of +msgid "|start_decode.png|" +msgstr "" + +#: ../../en/refs/base.qrcode.ref:8 f44bd38ccd0c4b69ae49e5210f819ac8 +msgid "start_decode.png" +msgstr "" + +#: 23b22346674845e7abd3255f9301c46b base.qrcode.AtomicQRCodeBase.stop_decode:1 +#: of +msgid "Stop decode." +msgstr "停止解码。" + +#: 89e6f5edc2e34d9ca9ad97a7461a50f0 base.qrcode.AtomicQRCodeBase.stop_decode:5 +#: of +msgid "|stop_decode.png|" +msgstr "" + +#: ../../en/refs/base.qrcode.ref:9 9cad9e9a9bd04cd5ab82bd9b49a0e7b7 +msgid "stop_decode.png" +msgstr "" + +#~ msgid "Set trigger mode. 设置触发解码模式。" +#~ msgstr "设置触发解码模式。" + diff --git a/docs/locales/zh_CN/LC_MESSAGES/base/qrcode2.po b/docs/locales/zh_CN/LC_MESSAGES/base/qrcode2.po new file mode 100644 index 00000000..29f82458 --- /dev/null +++ b/docs/locales/zh_CN/LC_MESSAGES/base/qrcode2.po @@ -0,0 +1,841 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2016 - 2025 M5Stack Technology Co., Ltd +# This file is distributed under the same license as the UIFlow2 Programming +# Guide package. +# FIRST AUTHOR , 2025. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: UIFlow2 Programming Guide \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-04-02 10:32+0800\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: zh_CN\n" +"Language-Team: zh_CN \n" +"Plural-Forms: nplurals=1; plural=0;\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.16.0\n" + +#: ../../en/base/qrcode2.rst:2 ../../en/refs/base.qrcode2.ref +#: 78005daddd914e65b86a697815748f36 e1016a6d0709462786d7c4fa8710ad43 +msgid "Atomic QRCode2 Base" +msgstr "" + +#: ../../en/base/qrcode2.rst:8 8db0a16ba9ae4610877eedf4625516f1 +msgid "" +"This library is the driver for Atomic QRCode2 Base, and the module " +"communicates via UART." +msgstr "这个库是 Atomic QRCode2 Base 的驱动,该模块使用 UART 通信。" + +#: ../../en/base/qrcode2.rst:10 779b91868b2b4af0845269708c7513c0 +msgid "Support the following products:" +msgstr "支持以下产品:" + +#: ../../en/base/qrcode2.rst:12 f6e31e48bcca4fdfabe0d29e39854fcb +msgid "|Atomic QRCode2 Base|" +msgstr "" + +#: ../../en/base/qrcode2.rst:15 c29b66b2cc874db7b663d97116d13bca +msgid "UiFlow2 Example:" +msgstr "UiFlow2 应用示例:" + +#: ../../en/base/qrcode2.rst:18 ../../en/base/qrcode2.rst:97 +#: 9d612a7690d24574a7b37681710dc600 ab7b0e55a49b4f898750883fe6cba34b +msgid "QRCode Scan in Key Mode" +msgstr "按键模式二维码识别" + +#: ../../en/base/qrcode2.rst:20 24c6e66f34024ea6b47331abb0950827 +msgid "Open the |atoms3_qrcode2_key_mode_example.m5f2| project in UiFlow2." +msgstr "在 UiFlow2 上打开 |atoms3_qrcode2_key_mode_example.m5f2| 项目。" + +#: ../../en/base/qrcode2.rst:22 ../../en/base/qrcode2.rst:99 +#: 17a74b524eba41d48eaad2695b3a7701 8f8041dcd32e4757abcad40be9bf4191 +msgid "" +"In **Key Mode**, the module starts decoding when the button is pressed " +"and stops decoding when the button is released. After a successful " +"decoding, it stops decoding. To continue decoding, the button must be " +"released and pressed again." +msgstr "在按键模式下,模块在按下按键时开始解码,并在释放按键时停止解码" + +#: ../../en/base/qrcode2.rst:24 ../../en/base/qrcode2.rst:39 +#: ../../en/base/qrcode2.rst:54 ../../en/base/qrcode2.rst:69 +#: ../../en/base/qrcode2.rst:84 ../../en/base/qrcode2.rst:187 +#: ../../en/base/qrcode2.rst:208 ../../en/base/qrcode2.rst:222 +#: ../../en/base/qrcode2.rst:236 ../../en/base/qrcode2.rst:255 +#: ../../en/base/qrcode2.rst:277 ../../en/base/qrcode2.rst:293 +#: ../../en/base/qrcode2.rst:309 ../../en/base/qrcode2.rst:325 +#: ../../en/base/qrcode2.rst:341 ../../en/base/qrcode2.rst:359 +#: ../../en/base/qrcode2.rst:375 ../../en/base/qrcode2.rst:391 +#: ../../en/base/qrcode2.rst:407 ../../en/base/qrcode2.rst:427 +#: ../../en/base/qrcode2.rst:443 ../../en/base/qrcode2.rst:463 +#: ../../en/base/qrcode2.rst:483 ../../en/base/qrcode2.rst:503 +#: ../../en/base/qrcode2.rst:523 ../../en/base/qrcode2.rst:558 +#: 0133752e60b44b59b8f01c2a1b6dc2f8 08847127439947f284101c0c4954fc21 +#: 0b8e68b7159a42e0809d61b7e0fce784 0dd1481f65114bc3ba41abc22f572acf +#: 1b643ce0d78d4deb8c42c6efe5415313 1b703fab3dd04572a0cd009e214aa41a +#: 1cfe3dbd47484379b72e895a95a592b2 271064dc0b264e4d8aed77baceb9e213 +#: 2acd96650319423f8b3901c8ecd75a58 2b12be6b25d1435693d78b106c87d76b +#: 2eb8e58bcf994129a4b670947fae92c4 369911c2210f4d4897b43d5bc1e5ba04 +#: 387ffd9a631d45d0a24d0ac81dca7cb4 400b09356f4a4ef1ace6c564f8b43032 +#: 4ad033b0c1b8421db2065306cba2fbe3 53ef9f66821d41ee9954de2c5a30bb8a +#: 5a1d1ce211a047e7bee66e08cd1d0be8 6632f2ac1afe4b13a6590f7c9612ee9d +#: 66b5eff85e1c4bda8ece98e3cd72e8bb 66fc0370723d434b827b9c11ca973ae1 +#: 7735c54f69cc4b76b56ed9bad89e4e5a 86ba3c3142a5401c819034f98af92d3f +#: bce3306de5a14e2d96d265d40df8fece d7f938c69d54453fb1fda9c3b0bf0b45 +#: ed7da4646fc548fa9ec2c7d7f163421f fb9c34d21939424c9c461acff358fa5f +msgid "UiFlow2 Code Block:" +msgstr "UiFlow2 代码块:" + +#: ../../en/base/qrcode2.rst:26 337c91fe951f4cba9283f3fb6ca0813d +msgid "|atoms3_qrcode2_key_mode_example.png|" +msgstr "" + +#: ../../en/refs/base.qrcode2.ref:28 d362b117f1c9471b8252e7159f774a10 +msgid "atoms3_qrcode2_key_mode_example.png" +msgstr "" + +#: ../../en/base/qrcode2.rst:28 ../../en/base/qrcode2.rst:43 +#: ../../en/base/qrcode2.rst:58 ../../en/base/qrcode2.rst:73 +#: ../../en/base/qrcode2.rst:88 ../../en/base/qrcode2.rst:107 +#: ../../en/base/qrcode2.rst:122 ../../en/base/qrcode2.rst:137 +#: ../../en/base/qrcode2.rst:152 ../../en/base/qrcode2.rst:167 +#: 011c7fa5939c48c29e66fcb56e99a22d 3afe3424be0c41afa4344e5bed3414cf +#: 5541ec5e7cf54cea98164bbda80be849 5ddc54b8dc1142239e0a0bb11fd0d0c5 +#: 6bca7ba8fa894c98a8c5bf75af56d846 826b62be12e343c8943fe0cbc2c2df6e +#: 8cd4dd43dd304c458281a9040dd17c36 8e8354ad9c1d4b9aa1e6b5bfccb75d8d +#: f51d47dedc33493992ea991e9ee4b486 f8280e5010ae4294a0640bc32c040511 +msgid "Example output:" +msgstr "示例输出:" + +#: ../../en/base/qrcode2.rst:30 ../../en/base/qrcode2.rst:45 +#: ../../en/base/qrcode2.rst:60 ../../en/base/qrcode2.rst:75 +#: ../../en/base/qrcode2.rst:90 ../../en/base/qrcode2.rst:109 +#: ../../en/base/qrcode2.rst:124 ../../en/base/qrcode2.rst:139 +#: ../../en/base/qrcode2.rst:154 ../../en/base/qrcode2.rst:169 +#: 0221fb522c45402bacd9d89102694288 0d745760d7ab4314a6f3e067a42c98ad +#: 1c3f12fb6aea485cb93b0434903b60ea 29260b2baed04c34b89b80c36216ac31 +#: 2c68a6d6a78248ba96d4ce3e2d154fe1 4a86647640154eb8aca0a53bb1670fac +#: 6463897667a748c48cfb693404f25292 baceae4a729946edacc32aee8c381232 +#: d046d7e6d72848ab84e60a603411b073 d61bd90ec2694d738d45732d16324b05 +msgid "None" +msgstr "无" + +#: ../../en/base/qrcode2.rst:33 ../../en/base/qrcode2.rst:112 +#: 95ceb055366540e2ab75040cf8294652 ccdc18adea2d46f4afcdb3f7020536a9 +msgid "QRCode Scan in Continuous Mode" +msgstr "连续模式二维码识别" + +#: ../../en/base/qrcode2.rst:35 5b8fca59ce2d42ab81e5d965a6a06259 +msgid "Open the |atoms3_qrcode2_continuous_mode_example.m5f2| project in UiFlow2." +msgstr "在 UiFlow2 上打开 |atoms3_qrcode2_continuous_mode_example.m5f2| 项目。" + +#: ../../en/base/qrcode2.rst:37 ../../en/base/qrcode2.rst:114 +#: 615c40e8b60640d19e29ea55ff796825 e34b3911a04e4f728a978cc59f222ef1 +msgid "" +"In **Continuous Mode**, pressing the button once starts decoding, and " +"pressing the button again stops decoding." +msgstr "在连续模式下,按下一次按键模块开始解码,再次按下按键模块停止解码。" + +#: ../../en/base/qrcode2.rst:41 84147938cd5741eabf103a0c2d34ebe9 +msgid "|atoms3_qrcode2_continuous_mode_example.png|" +msgstr "" + +#: ../../en/refs/base.qrcode2.ref:29 6d1f9df2d4ae4d9f9deaafbd4a874d31 +msgid "atoms3_qrcode2_continuous_mode_example.png" +msgstr "" + +#: ../../en/base/qrcode2.rst:48 ../../en/base/qrcode2.rst:127 +#: 62c928bb2d2e41c3bf4009840f85174c e96f1e5241844a178a5127cc59cd0e04 +msgid "QRCode Scan in Auto Mode" +msgstr "自动模式二维码识别" + +#: ../../en/base/qrcode2.rst:50 ed8e6b13729345eaba5e2ef441d10f3d +msgid "Open the |atoms3_qrcode2_auto_mode_example.m5f2| project in UiFlow2." +msgstr "在 UiFlow2 上打开 |atoms3_qrcode2_auto_mode_example.m5f2| 项目。" + +#: ../../en/base/qrcode2.rst:52 ../../en/base/qrcode2.rst:129 +#: 057580ba6b744b4bbfe409cd38f5226a d314c140396a4dffac613ecd3f319dbf +msgid "" +"In **Auto Mode**, the module starts decoding when powered on and cannot " +"be stopped." +msgstr "在自动模式下,模块上电开始扫码解码,不可停止。" + +#: ../../en/base/qrcode2.rst:56 6dd2ed820e284b60b98370e67e0a484c +msgid "|atoms3_qrcode2_auto_mode_example.png|" +msgstr "" + +#: ../../en/refs/base.qrcode2.ref:30 e013f1d76a2449fd924887519df39317 +msgid "atoms3_qrcode2_auto_mode_example.png" +msgstr "" + +#: ../../en/base/qrcode2.rst:63 ../../en/base/qrcode2.rst:142 +#: 0208efc8eec946978cef6596bcbcbcb8 411f01452cbc4676b56b20a2abebc8ff +msgid "QRCode Scan in Pulse Mode" +msgstr "脉冲模式二维码识别" + +#: ../../en/base/qrcode2.rst:65 27fe9bda1ad84c1685013b48d8382780 +msgid "Open the |atoms3_qrcode2_pulse_mode_example.m5f2| project in UiFlow2." +msgstr "在 UiFlow2 上打开 |atoms3_qrcode2_pulse_mode_example.m5f2| 项目。" + +#: ../../en/base/qrcode2.rst:67 ../../en/base/qrcode2.rst:144 +#: c023cc1f21224848982f968f50bf38d9 f2f080da87a5425d8469c217553bbe94 +msgid "" +"In **Pulse Mode**, set the TRIG pin to hold a low level for more than " +"20ms to trigger decoding once." +msgstr "在脉冲模式下,设置触发引脚超过 20ms 的低电平触发解码一次。" + +#: ../../en/base/qrcode2.rst:71 c405fdcb6a9f4d84820402497e769f65 +msgid "|atoms3_qrcode2_pulse_mode_example.png|" +msgstr "" + +#: ../../en/refs/base.qrcode2.ref:31 03884397623944e483f3ddbe1ce60f34 +msgid "atoms3_qrcode2_pulse_mode_example.png" +msgstr "" + +#: ../../en/base/qrcode2.rst:78 ../../en/base/qrcode2.rst:157 +#: 525ece72c66b4d7fbb1aba9e8c3a4e15 be78e3c7cb6145a099bf6894b1f12b66 +msgid "QRCode Scan in Motion Sensing Mode" +msgstr "感应模式二维码识别" + +#: ../../en/base/qrcode2.rst:80 3d1c7fc3979745f5b4e78d907a6f61dc +msgid "" +"Open the |atoms3_qrcode2_motion_sensing_mode_example.m5f2| project in " +"UiFlow2." +msgstr "在 UiFlow2 上打开 |atoms3_qrcode2_motion_sensing_mode_example.m5f2| 项目。" + +#: ../../en/base/qrcode2.rst:82 ../../en/base/qrcode2.rst:159 +#: 71b54f292df14bcabc69179bfdd03314 f3873f65257e4b4ca51b8bbd6b3d4297 +msgid "" +"In **Motion Sensing Mode**, the module automatically triggers decoding " +"when it detects a change in the scene based on visual recognition " +"information." +msgstr "在感应模式下,模块基于视觉识别信息检测到场景变化时,会自动触发解码。" + +#: ../../en/base/qrcode2.rst:86 5755ee92d40b439c951307c39b57ea98 +msgid "|atoms3_qrcode2_motion_sensing_mode_example.png|" +msgstr "" + +#: ../../en/refs/base.qrcode2.ref:32 2289140a75e344f9a0fd26c90ca65216 +msgid "atoms3_qrcode2_motion_sensing_mode_example.png" +msgstr "" + +#: ../../en/base/qrcode2.rst:94 33db3b5b48314d68b63461d2a4d5aaac +msgid "MicroPython Example:" +msgstr "MicroPython 应用示例:" + +#: ../../en/base/qrcode2.rst:101 ../../en/base/qrcode2.rst:116 +#: ../../en/base/qrcode2.rst:131 ../../en/base/qrcode2.rst:146 +#: ../../en/base/qrcode2.rst:161 ../../en/base/qrcode2.rst:191 +#: ../../en/base/qrcode2.rst:212 ../../en/base/qrcode2.rst:226 +#: ../../en/base/qrcode2.rst:240 ../../en/base/qrcode2.rst:259 +#: ../../en/base/qrcode2.rst:281 ../../en/base/qrcode2.rst:297 +#: ../../en/base/qrcode2.rst:313 ../../en/base/qrcode2.rst:329 +#: ../../en/base/qrcode2.rst:345 ../../en/base/qrcode2.rst:363 +#: ../../en/base/qrcode2.rst:379 ../../en/base/qrcode2.rst:395 +#: ../../en/base/qrcode2.rst:411 ../../en/base/qrcode2.rst:431 +#: ../../en/base/qrcode2.rst:447 ../../en/base/qrcode2.rst:467 +#: ../../en/base/qrcode2.rst:487 ../../en/base/qrcode2.rst:507 +#: ../../en/base/qrcode2.rst:527 ../../en/base/qrcode2.rst:562 +#: 0e766eb23e9f42d08436b22030962881 1346b9161e974f5683530f2bcf77bc90 +#: 1c76bd83d06946f59351e27d28bc0881 1f86119f319744d58606aef297691be1 +#: 267e54ae29db4cebb007e8b1b22ddd8b 2c7857414f3a4e479b0602d742a7c9ca +#: 3399e075daa34d63ac51a6396cb0e9d4 3d92f9810c85423ca6e209449c276a24 +#: 46cb79fce262455babf512826c3dc68f 628cd519c1354b56822d990997ef987e +#: 63b4b80af0784f1fbcccef70f6a723fd 77a61a53a1c74576b26d2c27bba4c004 +#: 7a67f6a9c6c74e62bfa0e8aafaca18cc 81b04bcef2f94c3e8cd34d0e05b97ae8 +#: 866606a286f24d6497661025839e5e91 98458ddfbce84442a4e31b33f0abc7ce +#: 993df627eccd421a9c90de44a2bf4fd7 a7f9ec6946a5463ba0efe0031b35542a +#: baee8a3a47f64b2484d81ced47a17f8b bc4be862105b43139b6f0f7e8ee692f0 +#: c02a37fd448b4594bb19bbfa8ecd7bb6 c789ce00125046c38fcf1890dee4b22c +#: f0d1f1c1d8814140adb9b312222a2f4b f81b7810e98a404a9bca1c21aa8cfdaa +#: f93ca1790b15407a9d8b4d135e843179 ff471fbf351b44d18c06c160cf7f0e4b +msgid "MicroPython Code Block:" +msgstr "MicroPython 代码块:" + +#: ../../en/base/qrcode2.rst:172 9c7baedbbf3549f5bb4cc7ed8eb153ab +msgid "**API**" +msgstr "API应用" + +#: ../../en/base/qrcode2.rst:175 0299ebf4a44c4dc4beb73c89432c6e5d +msgid "AtomicQRCode2Base" +msgstr "" + +#: ../../en/base/qrcode2.rst:180 fd249ecc892a423997abd56ca6242541 +msgid "Create an AtomicQRCode2Base object." +msgstr "创建一个 AtomicQRCode2Base 对象。" + +#: ../../en/base/qrcode2.rst 06db3cc9e1e24a0493d9aec50c8e0ce6 +#: 189ffdff96eb4405b0bf33dd74b22b28 18ce1c0793ce41dfbe30333d68a1bd11 +#: 19350e420119441aac939a8db5910add 3c07a757f8aa4de382f0c0a174b058b5 +#: 4c18a06e02aa4fbf85a108fcd004bdd0 54eb0e200dbf4791809b72e7aa715f3e +#: 5e517845684f4608ae9d2125eb2afaa1 5e74947077e34269a4f62b08c3684260 +#: 6afa440b0d1b4aec8f5581056c6f8def 847b2171a61247cab6c96a2076ae119c +#: 86fb8be7fdf24ac1b06d2b499b16bf66 8f6b8ad0878144d0b0abc0f9e76398f7 +#: a62681c3452f4a7ca4af2fa6edd0e5f0 a94fe9a0ef534760836e3dd29d29f763 +#: d5afbf079b2a4342a06f63e0e34f269b da5f3aaa0fda41699d512eae88e5f510 +#: f41d3e18c4db40bbb164b5aeac5d6428 +msgid "Parameters" +msgstr "" + +#: ../../en/base/qrcode2.rst:182 fa8773ab589547c9912a6378d7f9986e +msgid "UART id." +msgstr "UART 端口号。" + +#: ../../en/base/qrcode2.rst:183 c93be63f6617426b8ae2dae63f89cc1b +msgid "the UART TX pin." +msgstr "UART 发送引脚。" + +#: ../../en/base/qrcode2.rst:184 795a02efa3454dfea170332f8244f318 +msgid "the UART RX pin." +msgstr "UART 接收引脚。" + +#: ../../en/base/qrcode2.rst:185 cb44b1764d024f9ba39c265d0e84682f +msgid "the trigger pin." +msgstr "触发引脚。" + +#: ../../en/base/qrcode2.rst:189 c11cfe8e4d094a5092fa41e476f544ed +msgid "|init.png|" +msgstr "" + +#: ../../en/refs/base.qrcode2.ref:6 35f86915cb634924a02e1ee7ce04e426 +msgid "init.png" +msgstr "" + +#: ../../en/base/qrcode2.rst:201 2b98eaff98ac4a109abfd9c956c79085 +msgid "Set trigger pin value." +msgstr "设置触发引脚电平。" + +#: ../../en/base/qrcode2.rst:203 8f2c297cb711422ab8c2f719970e43cd +msgid "- ``0`` : low level. - ``1`` : high level." +msgstr "- ``0``:低电平。 - ``1``:高电平。" + +#: ../../en/base/qrcode2.rst:205 b7dc42b090d24dffbcb2bbbcf8697d61 +msgid "``0`` : low level." +msgstr "``0``:低电平。" + +#: ../../en/base/qrcode2.rst:206 5de7b02539274ca9b73ad51b34538b57 +msgid "``1`` : high level." +msgstr "``1``:高电平。" + +#: ../../en/base/qrcode2.rst:210 f5f3627d12564bd3acc08685811a5f74 +msgid "|set_trig.png|" +msgstr "" + +#: ../../en/refs/base.qrcode2.ref:7 43bdbfa9ce2641e6aa17aabca19bcd67 +msgid "set_trig.png" +msgstr "" + +#: ../../en/base/qrcode2.rst:220 5fa96fcc92124ad2b98dd5e86e9d3737 +msgid "Start decode." +msgstr "开始解码。" + +#: ../../en/base/qrcode2.rst:224 1bb6e96e7233488e9326ee94849a56ab +msgid "|start_decode.png|" +msgstr "" + +#: ../../en/refs/base.qrcode2.ref:8 c3bec8ce860d4b75bf0e1674e9a65e2a +msgid "start_decode.png" +msgstr "" + +#: ../../en/base/qrcode2.rst:234 5d105f36c919436dabb80aa822416af4 +msgid "Stop decode." +msgstr "停止解码。" + +#: ../../en/base/qrcode2.rst:238 185bad56a3484861be2cc4c2efba926f +msgid "|stop_decode.png|" +msgstr "" + +#: ../../en/refs/base.qrcode2.ref:9 859712ce531f4c8f8f3525e76cacd6a2 +msgid "stop_decode.png" +msgstr "" + +#: ../../en/base/qrcode2.rst:248 84c723ebacbd48cbb9bf6a1ca409829e +msgid "Read qrcode data." +msgstr "读取二维码数据。" + +#: ../../en/base/qrcode2.rst bd4c1944623a494f98a0710349bc8414 +msgid "Returns" +msgstr "" + +#: ../../en/base/qrcode2.rst:250 9cade8b91805460fb71230ebcd65e30c +msgid "qrcode data." +msgstr "二维码数据。" + +#: ../../en/base/qrcode2.rst d65d4afd2be44f35b5ca77ba4d243252 +msgid "Return type" +msgstr "" + +#: ../../en/base/qrcode2.rst:253 9d4fcada71304ed2b5529dd2b8b7e8ff +msgid "If no data is received, return None." +msgstr "如果未识别到,则返回 None。" + +#: ../../en/base/qrcode2.rst:257 71b2dfd43704475ca9008a9fcc70fba7 +msgid "|read.png|" +msgstr "" + +#: ../../en/refs/base.qrcode2.ref:10 6e7fc0bbfbef40dcafe506aa1f0dfe19 +msgid "read.png" +msgstr "" + +#: ../../en/base/qrcode2.rst:267 fd70df73c1ed40219ac1ab5ef3f075ac +msgid "Set trigger mode." +msgstr "设置触发模式。" + +#: ../../en/base/qrcode2.rst:269 54bf7298789e42f99754c3bf20b8960e +msgid "" +"The trigger mode. Available options: - ``TRIGGER_MODE_KEY``: Key Mode, " +"Decoding starts when the trigger pin is low and stops when the trigger " +"pin is high. - ``TRIGGER_MODE_CONTINUOUS``: Call start_decode() to start " +"decoding and stop_decode() to stop decoding. - ``TRIGGER_MODE_AUTO``: " +"Auto Mode, Performs continuous decoding upon power-up and cannot be " +"stopped. - ``TRIGGER_MODE_PULSE``: Pulse Mode, A 20ms low-level pulse on " +"the trigger pin initiates a single decoding operation. - " +"``TRIGGER_MODE_MOTION_SENSING``: Motion Sensing Mode, Uses image " +"recognition; decoding starts when a scene change is detected." +msgstr "" +"触发模式,可选项: -``TRIGGER_MODE_KEY``:按键模式,触发引脚低电平开始解码,触发引脚高电平停止解码。- " +"``TRIGGER_MODE_CONTINUOUS``: 连续模式,调用 start_decode() 开始解码,调用 " +"stop_decode()。 停止解码。- ``TRIGGER_MODE_AUTO``: 自动模式,模块上电开始解码,不可停止。- " +"``TRIGGER_MODE_PULSE``: 脉冲模式,触发引脚 20ms 的低电平触发一次解码。- " +"``TRIGGER_MODE_MOTION_SENSING``: 移动感应模式,模块根据检测到的图像信息,当场景发生变化时触发解码。" + +#: ../../en/base/qrcode2.rst:269 85184c5225f6400589650528d9f4f6f7 +msgid "The trigger mode. Available options:" +msgstr "触发模式,可选项:" + +#: ../../en/base/qrcode2.rst:271 27546feaff604bb4b95790cd249635da +msgid "" +"``TRIGGER_MODE_KEY``: Key Mode, Decoding starts when the trigger pin is " +"low and stops when the trigger pin is high." +msgstr "" + +#: ../../en/base/qrcode2.rst:272 ef2fd17be41d4ce9828ed68dd1051e2c +msgid "" +"``TRIGGER_MODE_CONTINUOUS``: Call start_decode() to start decoding and " +"stop_decode() to stop decoding." +msgstr "" + +#: ../../en/base/qrcode2.rst:273 1f9a7aadf41944db968ccad562026dff +msgid "" +"``TRIGGER_MODE_AUTO``: Auto Mode, Performs continuous decoding upon " +"power-up and cannot be stopped." +msgstr "- ``TRIGGER_MODE_AUTO``: 自动模式,模块上电开始解码,不可停止。" + +#: ../../en/base/qrcode2.rst:274 b312803d26d24c84bf3ceaae777f8035 +msgid "" +"``TRIGGER_MODE_PULSE``: Pulse Mode, A 20ms low-level pulse on the trigger" +" pin initiates a single decoding operation." +msgstr "" + +#: ../../en/base/qrcode2.rst:275 217b540257114dfba4d424c39a2b05e4 +msgid "" +"``TRIGGER_MODE_MOTION_SENSING``: Motion Sensing Mode, Uses image " +"recognition; decoding starts when a scene change is detected." +msgstr "- ``TRIGGER_MODE_MOTION_SENSING``: 移动感应模式,模块根据检测到的图像信息,当场景发生变化时触发解码。" + +#: ../../en/base/qrcode2.rst:279 f50c10d3ca7f448bb40e473f1222a444 +msgid "|set_trigger_mode.png|" +msgstr "" + +#: ../../en/refs/base.qrcode2.ref:11 d0563bf83cc24cb9808906147a997e65 +msgid "set_trigger_mode.png" +msgstr "" + +#: ../../en/base/qrcode2.rst:289 6e0aa373d1ac4118bf5665db56913c00 +msgid "Set decode delay." +msgstr "设置命令解码延迟 (按键模式下)。" + +#: ../../en/base/qrcode2.rst:291 e84a5ff4c55b400ba51992322395d12b +msgid "decode delay time(ms), 0 means continuous decoding until success." +msgstr "解码延迟时间(单位:毫秒),0 表示持续解码直到成功。" + +#: ../../en/base/qrcode2.rst:295 3472f1f0c0a44fe2914d8a787c853195 +msgid "|set_decode_delay.png|" +msgstr "" + +#: ../../en/refs/base.qrcode2.ref:12 f4f0f27dddd64931a85ce2a1b34f5b18 +msgid "set_decode_delay.png" +msgstr "" + +#: ../../en/base/qrcode2.rst:305 79b57e078e594e50ae9ec8eff70aa95e +msgid "Set trigger timeout." +msgstr "设置触发超时。(脉冲模式下)。 " + +#: ../../en/base/qrcode2.rst:307 3d7850b3eed9444c8a6e2f92dceffd6c +msgid "" +"trigger timeout time(ms), Decoding will automatically stop when the " +"duration exceeds this value." +msgstr "触发超时时间(单位:毫秒),解码持续时间超过该值时自动停止。" + +#: ../../en/base/qrcode2.rst:311 3628605466a84e54a007001dc35e7850 +msgid "|set_trigger_timeout.png|" +msgstr "" + +#: ../../en/refs/base.qrcode2.ref:13 3418f2e324704249b81588a6344d6153 +msgid "set_trigger_timeout.png" +msgstr "" + +#: ../../en/base/qrcode2.rst:321 503e7ba4384742d09fa446de9e973d76 +msgid "Set motion detection sensitivity. (in Motion Sensing Mode)" +msgstr "设置移动感应灵敏度。(感应模式下)" + +#: ../../en/base/qrcode2.rst:323 8295732ffe444d45b4a30958e28fa683 +msgid "" +"sensitivity level. Range: 1~5. The higher the level, the more sensitive " +"it is to scene changes." +msgstr "灵敏度等级,范围 1-5,等级越高对场景变化越敏感。默认等级为 3。" + +#: ../../en/base/qrcode2.rst:327 cf582b403ced41d591e9833db030a602 +msgid "|set_motion_sensitivity.png|" +msgstr "" + +#: ../../en/refs/base.qrcode2.ref:14 11f84dbba8ae4e36b2d963b6f9626dab +msgid "set_motion_sensitivity.png" +msgstr "" + +#: ../../en/base/qrcode2.rst:337 652761cb151e444ba58d1ba86ae054e5 +msgid "Set continuous decode delay. (in Motion Sensing Mode)" +msgstr "设置持续解码延迟时间。(感应模式下)" + +#: ../../en/base/qrcode2.rst:339 2fafad9f5a3e407183de4297469e50eb +msgid "delay time(unit: 100ms), 0 means continuous decoding until timeout." +msgstr "延迟时间,单位为 100 毫秒。值为 0 表示持续解码直到超时。" + +#: ../../en/base/qrcode2.rst:343 89c12649a39b4282bc3ecadc02aecfc4 +msgid "|set_continuous_decode_delay.png|" +msgstr "" + +#: ../../en/refs/base.qrcode2.ref:15 a25876f9c9324ae6a49b7ee59036febe +msgid "set_continuous_decode_delay.png" +msgstr "" + +#: ../../en/base/qrcode2.rst:353 350c99f65b1b401895a4be029863f1dc +msgid "Set trigger decode delay. (in Motion Sensing Mode)" +msgstr "设置触发解码延迟时间(感应模式下)" + +#: ../../en/base/qrcode2.rst:355 dcecb7fdf2364d2cbdfbdf82c6cf8bb6 +msgid "" +"Sets the trigger decoding delay time. This is the delay between re-" +"entering the scene change detection phase and starting recognition again " +"after detecting a change." +msgstr "设置触发解码延迟时间。当重新进入检测场景变化阶段,到检测到变化再次开始识读之间的延迟时长。" + +#: ../../en/base/qrcode2.rst:357 738d263b27e9451fa869d27c7a69ec8c +msgid "Trigger decode delay time(unit: ms)." +msgstr "触发解码延迟时间,单位为毫秒。" + +#: ../../en/base/qrcode2.rst:361 fe0f0292846b4ec5810e97b556a8548b +msgid "|set_trigger_decode_delay.png|" +msgstr "" + +#: ../../en/refs/base.qrcode2.ref:16 f69ad1f7f004448e8dbf41f57b17bf83 +msgid "set_trigger_decode_delay.png" +msgstr "" + +#: ../../en/base/qrcode2.rst:371 5460d847ddd847869d6f9269446dffce +msgid "Set same code interval." +msgstr "设置同码间隔时间。" + +#: ../../en/base/qrcode2.rst:373 ea04c20fe0a0456c8a14c4e171a5206e +msgid "The interval time for repeated recognition of the same code (unit: ms)." +msgstr "相同条码的重复识读间隔时间,单位为毫秒。" + +#: ../../en/base/qrcode2.rst:377 c23e271457754ecf9c42a23b196c96a3 +msgid "|set_same_code_interval.png|" +msgstr "" + +#: ../../en/refs/base.qrcode2.ref:17 ecaaab2d8f8e44b3938e1d41204c0970 +msgid "set_same_code_interval.png" +msgstr "" + +#: ../../en/base/qrcode2.rst:387 376d337f47194edc965b0ba82d8700b8 +msgid "Set difference code interval." +msgstr "设置异码间隔时间。" + +#: ../../en/base/qrcode2.rst:389 50f2ddfdd2c447d19f3cd0ac925bf60b +msgid "" +"The interval time for repeated recognition of the difference code (unit: " +"ms)." +msgstr "不同条码的识读间隔时间,单位为毫秒。" + +#: ../../en/base/qrcode2.rst:393 61de1ecb21884a08935f8c53ac9d48d8 +msgid "|set_diff_code_interval.png|" +msgstr "" + +#: ../../en/refs/base.qrcode2.ref:18 d3bad2c2ef1a49baa29418f0123555ce +msgid "set_diff_code_interval.png" +msgstr "" + +#: ../../en/base/qrcode2.rst:403 a5b38542d8824a75b9fd5dcd9c97e324 +msgid "Set same code no delay." +msgstr "设置同码非延迟输出。" + +#: ../../en/base/qrcode2.rst:405 c476dfe2a4654f95927cac0d46e4d97d +msgid "" +"Whether to enable non-delay output for the same code. True means enabled," +" False means disabled." +msgstr "是否开启同码非延迟输出,True 表示开启,False 表示关闭。" + +#: ../../en/base/qrcode2.rst:409 bda2fd36cbd14ca9a4ec3811a3e3ee2d +msgid "|set_same_code_no_delay.png|" +msgstr "" + +#: ../../en/refs/base.qrcode2.ref:19 66addfedb7e349519515267140e1bbec +msgid "set_same_code_no_delay.png" +msgstr "" + +#: ../../en/base/qrcode2.rst:419 331d445a4def4242a3b5b82b7ab1ddc2 +msgid "Set fill light mode." +msgstr "设置补光灯模式。" + +#: ../../en/base/qrcode2.rst:421 e2cd0104d6c24ca9a3fbb64f8434c0a6 +msgid "" +"The fill light mode. Available options: - ``FILL_LIGHT_OFF``: Light off." +" - ``FILL_LIGHT_ON``: Light on. - ``FILL_LIGHT_ON_DECODE``: Light on " +"during decoding." +msgstr "" +"补光灯模式,可选项:- ``FILL_LIGHT_OFF``: 常灭。- ``FILL_LIGHT_ON``: 常亮。- " +"``FILL_LIGHT_ON_DECODE``: 解码时亮。" + +#: ../../en/base/qrcode2.rst:421 c1c1fc76f1b24c318c8e6f7b6f5bf965 +msgid "The fill light mode. Available options:" +msgstr "补光灯模式,可选项:" + +#: ../../en/base/qrcode2.rst:423 b3666140852248b69479e9eb3d98aba8 +msgid "``FILL_LIGHT_OFF``: Light off." +msgstr "``FILL_LIGHT_OFF``: 常灭。" + +#: ../../en/base/qrcode2.rst:424 d9f31233634845fca2e6c86f7b160fbb +msgid "``FILL_LIGHT_ON``: Light on." +msgstr "``FILL_LIGHT_ON``: 常亮。" + +#: ../../en/base/qrcode2.rst:425 62535f6518f54cd48cf4ecdaa5cf7956 +msgid "``FILL_LIGHT_ON_DECODE``: Light on during decoding." +msgstr "``FILL_LIGHT_ON_DECODE``: 解码时亮。" + +#: ../../en/base/qrcode2.rst:429 35186f00ec4447248c516246904d8b78 +msgid "|set_fill_light_mode.png|" +msgstr "" + +#: ../../en/refs/base.qrcode2.ref:20 241aebbb30874547aed75120a25395a1 +msgid "set_fill_light_mode.png" +msgstr "" + +#: ../../en/base/qrcode2.rst:439 0ab86eec67d046b78782b2ab34f76487 +msgid "Set fill light brightness." +msgstr "设置补光灯亮度。" + +#: ../../en/base/qrcode2.rst:441 a8bfa8d9085244f7bb13f37e311e0807 +msgid "The fill light brightness. Range: 0~100." +msgstr "补光灯亮度,范围:0~100。" + +#: ../../en/base/qrcode2.rst:445 f1372c7c99bc497683c563a8e0f4e6eb +msgid "|set_fill_light_brightness.png|" +msgstr "" + +#: ../../en/refs/base.qrcode2.ref:21 88115d8159754a9ea531bcd283b6683a +msgid "set_fill_light_brightness.png" +msgstr "" + +#: ../../en/base/qrcode2.rst:455 d14c7c57abed4cf38c77f7a818bd1f4f +msgid "Set positioning light mode." +msgstr "设置定位灯模式。" + +#: ../../en/base/qrcode2.rst:457 2afe393766414b479350f2fc134b1b31 +msgid "The positioning light mode. Available options:" +msgstr "定位灯模式,可选项:" + +#: ../../en/base/qrcode2.rst:459 6312bc18da6e4fa9bb9326f68caa9689 +msgid "``POS_LIGHT_OFF``: Light off." +msgstr "``POS_LIGHT_OFF·``: 常灭。" + +#: ../../en/base/qrcode2.rst:460 3a8021cb717d475ab8ee7a9693575c3c +msgid "``POS_LIGHT_ON_DECODE``: Light on during decoding." +msgstr "``POS_LIGHT_ON_DECODE``: 解码时亮。" + +#: ../../en/base/qrcode2.rst:461 464dc9a0503b431b91606c9949570ee7 +msgid "``POS_LIGHT_FLASH_ON_DECODE``: Light flash during decoding." +msgstr "``POS_LIGHT_FLASH_ON_DECODE``: 解码时闪烁。" + +#: ../../en/base/qrcode2.rst:465 77396a7a4a15412da7438bf1b17d83b2 +msgid "|set_pos_light_mode.png|" +msgstr "" + +#: ../../en/refs/base.qrcode2.ref:22 cc96891e6e724dc7bb4a4d1a6fc2ac90 +msgid "set_pos_light_mode.png" +msgstr "" + +#: ../../en/base/qrcode2.rst:475 94220856639c462baba0f859cc392b8d +msgid "Set startup tone." +msgstr "设置启动音" + +#: ../../en/base/qrcode2.rst:477 a6c3874220464ace96ea0bc6fa69bf72 +msgid "" +"- ``0``: Disable startup tone. - ``1``: Play 4 beeps. - ``2``: Play 2 " +"beeps." +msgstr "- ``0``: 关闭启动音。 - ``1``: 4 声。 - ``2``: 2 声。" + +#: ../../en/base/qrcode2.rst:479 fbf86e02fd0344d0be59ffc95c8f6c78 +msgid "``0``: Disable startup tone." +msgstr "``0``: 关闭启动音。" + +#: ../../en/base/qrcode2.rst:480 22993cc69ea743619e72944ea92a3fcf +msgid "``1``: Play 4 beeps." +msgstr "``1``: 4 声。" + +#: ../../en/base/qrcode2.rst:481 eb325bb40b424fc9bf1665e3f7b94cab +msgid "``2``: Play 2 beeps." +msgstr "``2``: 2 声。" + +#: ../../en/base/qrcode2.rst:485 ed8be29bcc104ed99f879f0ae395b169 +msgid "|set_startup_tone.png|" +msgstr "" + +#: ../../en/refs/base.qrcode2.ref:23 4e01e3c11dc64c2eb738932916fdcc5c +msgid "set_startup_tone.png" +msgstr "" + +#: ../../en/base/qrcode2.rst:495 e8eac73de2264102bf81e15ea40eb786 +msgid "Set decode success beep." +msgstr "设置解码成功提示音。" + +#: ../../en/base/qrcode2.rst:497 5c671d5377104842894b51cacd7843e2 +msgid "" +"- ``0``: No prompt sound. - ``1``: Play prompt sound once. - ``2``: Play " +"prompt sound twice." +msgstr "- ``0``: 无提示音。 - ``1``: 播放1次。 - ``2``: 播放2次。" + +#: ../../en/base/qrcode2.rst:499 ea36a56bc4aa447dba285af6cd459103 +msgid "``0``: No prompt sound." +msgstr "``0``: 无提示音。" + +#: ../../en/base/qrcode2.rst:500 20bd206f72d84650af010b446743b57e +msgid "``1``: Play prompt sound once." +msgstr "``1``: 播放1次。" + +#: ../../en/base/qrcode2.rst:501 b9a01af3d86d4e3393a2aedee21e34f0 +msgid "``2``: Play prompt sound twice." +msgstr "``2``: 播放2次。" + +#: ../../en/base/qrcode2.rst:505 9f52b1f011764b0b90142b431690f4eb +msgid "|set_decode_success_beep.png|" +msgstr "" + +#: ../../en/refs/base.qrcode2.ref:24 7cb6fe4ad6a6490a91bab07b59d297c2 +msgid "set_decode_success_beep.png" +msgstr "" + +#: ../../en/base/qrcode2.rst:515 4ffcdb9df96549ae89001ea6624d58c0 +msgid "Set case conversion." +msgstr "设置大小写转换。" + +#: ../../en/base/qrcode2.rst:517 9fe32c8022874ab59a7b387f62667df4 +msgid "" +"- ``0``: Off (Original data). - ``1``: Convert to uppercase. - ``2``: " +"Convert to lowercase." +msgstr "- ``0``: 不转换。 - ``1``: 转为大写。 - ``2``: 转为小写。" + +#: ../../en/base/qrcode2.rst:519 4882ef767d964147a6a5531c7ab1763b +msgid "``0``: Off (Original data)." +msgstr "``0``: 不转换。" + +#: ../../en/base/qrcode2.rst:520 cdbd5ada81014a46ab22d237ca0b98b7 +msgid "``1``: Convert to uppercase." +msgstr "``1``: 转为大写。" + +#: ../../en/base/qrcode2.rst:521 4132a85501e84ae69031e6b21c811677 +msgid "``2``: Convert to lowercase." +msgstr "``2``: 转为小写。" + +#: ../../en/base/qrcode2.rst:525 ac1a30ef5ba442da9965d0de06eccba4 +msgid "|set_case_conversion.png|" +msgstr "" + +#: ../../en/refs/base.qrcode2.ref:25 65b49d8751374227b553b24cb65e989b +msgid "set_case_conversion.png" +msgstr "" + +#: ../../en/base/qrcode2.rst:535 b82b90638078445ebdc62314ea96272c +msgid "" +"- ``0``: No protocol - ``1``: Format 1: [0x03] + Data Length (2 bytes) + " +"Data - ``2``: Format 2: [0x03] + Data Length + Number of Barcodes + Code " +"1 Data Length + Code 1 Data + ... + CRC - ``3``: Format 3: [0x03] + Data " +"Length + Number of Barcodes + Code 1 ID + Code 1 Data Length + Code 1 " +"Data + ... + CRC" +msgstr "" +"- ``0``: 无协议- ``1``: 格式 1:[0x03] + 数据长度(2bytes) + 数据- ``2``: 格式 2:[0x03] " +"+ 数据长度 + 条码个数 + 条码 1 数据长度 + 条码 1 数据 +... + CRC- ``3``: 格式 3:[0x03] + 数据长度" +" + 条码个数 + 条码 1ID 号 + 条码 1 数据长度 + 条码 1 数据 + ... + CR" + +#: ../../en/base/qrcode2.rst:537 90fbde5cacd34a52aff43542051190bc +msgid "``0``: No protocol" +msgstr "`0``: 无协议 \"" + +#: ../../en/base/qrcode2.rst:538 dd5fcd01816a4aff9a1ae3cc63435ee7 +msgid "``1``: Format 1: [0x03] + Data Length (2 bytes) + Data" +msgstr "``1``: 格式 1:[0x03] + 数据长度(2bytes) + 数据" + +#: ../../en/base/qrcode2.rst:539 1c73765956564c2d97eac2f0e5fbd604 +msgid "" +"``2``: Format 2: [0x03] + Data Length + Number of Barcodes + Code 1 Data " +"Length + Code 1 Data + ... + CRC" +msgstr "``2``: 格式 2:[0x03] + 数据长度 + 条码个数 + 条码 1 数据长度 + 条码 1 数据 +... + CRC" + +#: ../../en/base/qrcode2.rst:540 cf3184968951427b97c7f72ce62e446e +msgid "" +"``3``: Format 3: [0x03] + Data Length + Number of Barcodes + Code 1 ID + " +"Code 1 Data Length + Code 1 Data + ... + CRC" +msgstr "" +"``3``: 格式 3:[0x03] + 数据长度 + 条码个数 + 条码 1ID 号 + 条码 1 数据长度 + 条码 1 数据 + ... +" +" CRC" + +#: ../../en/base/qrcode2.rst:542 ec07970359fa425997a82281b18cae86 +msgid "CRC generate reference program." +msgstr "CRC 生成参考程序。" + +#: ../../en/base/qrcode2.rst:560 dfe5051fa5094b1d92787b94c85587d1 +msgid "|set_protocol_format.png|" +msgstr "" + +#: ../../en/refs/base.qrcode2.ref:26 c64d20a6dcaf44f3ba20f27022108d09 +msgid "set_protocol_format.png" +msgstr "" + +#~ msgid "" +#~ "``TRIGGER_MODE_KEY``: Key Mode, Triggers a " +#~ "single decode; decoding stops after a" +#~ " successful read." +#~ msgstr "-``TRIGGER_MODE_KEY``:按键模式,触发引脚低电平开始解码,触发引脚高电平停止解码。" + +#~ msgid "" +#~ "``TRIGGER_MODE_CONTINUOUS``: Continuous Mode, " +#~ "Triggers continuous decoding; decoding " +#~ "continues even after a successful read" +#~ " and stops only when triggered again." +#~ msgstr "" +#~ "- ``TRIGGER_MODE_CONTINUOUS``: 连续模式,调用 " +#~ "start_decode() 开始解码,调用 stop_decode() 停止解码。" + +#~ msgid "" +#~ "``TRIGGER_MODE_PULSE``: Pulse Mode, The Trig" +#~ " pin's low-level signal triggers " +#~ "decoding, which stops after a successful" +#~ " read or when the single read " +#~ "time limit is reached." +#~ msgstr "- ``TRIGGER_MODE_PULSE``: 脉冲模式,触发引脚 20ms 的低电平触发一次解码。" + +#~ msgid "" +#~ "The positioning light mode. Available " +#~ "options: - ``POS_LIGHT_OFF``: Light off. " +#~ "- ``POS_LIGHT_ON_DECODE``: Light on during " +#~ "decoding. - ``POS_LIGHT_FLASH_ON_DECODE``: Light " +#~ "flash during decoding." +#~ msgstr "" +#~ "定位灯模式,可选项:- ``POS_LIGHT_OFF·`: 常灭。- " +#~ "``POS_LIGHT_ON_DECODE`: 解码时亮。- " +#~ "``POS_LIGHT_FLASH_ON_DECODE`: 解码时闪烁。" + diff --git a/docs/locales/zh_CN/LC_MESSAGES/module/qrcode.po b/docs/locales/zh_CN/LC_MESSAGES/module/qrcode.po new file mode 100644 index 00000000..3c6607cc --- /dev/null +++ b/docs/locales/zh_CN/LC_MESSAGES/module/qrcode.po @@ -0,0 +1,785 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2016 - 2025 M5Stack Technology Co., Ltd +# This file is distributed under the same license as the UIFlow2 Programming +# Guide package. +# FIRST AUTHOR , 2025. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: UIFlow2 Programming Guide \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-04-02 17:02+0800\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: zh_CN\n" +"Language-Team: zh_CN \n" +"Plural-Forms: nplurals=1; plural=0;\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.16.0\n" + +#: ../../en/module/qrcode.rst:2 0937a2e31ff44d3b8d8ada7421c1c45a +msgid "QRCode Module" +msgstr "" + +#: ../../en/module/qrcode.rst:8 c5bea96a5efd41c8a885af3d79051838 +msgid "" +"This library is the driver for Module13.2 QRCode, and the module " +"communicates via UART." +msgstr "这个库是 Module13.2 QRCode 的驱动,该模块使用 UART 通信。" + +#: ../../en/module/qrcode.rst:10 5ab92b7d84a646908dfb3df45b547086 +msgid "Support the following products:" +msgstr "支持以下产品:" + +#: ../../en/module/qrcode.rst:12 9af8ebdfac85465286d23d7be7d8e16b +msgid "|Module13.2 QRCode|" +msgstr "" + +#: ../../en/refs/module.qrcode.ref d48c4ba037a44d138b0792d092640c2f +msgid "Module13.2 QRCode" +msgstr "" + +#: ../../en/module/qrcode.rst:15 f4855820de604a3d9e71e4331f81b21e +msgid "UiFlow2 Example:" +msgstr "UiFlow2 应用示例:" + +#: ../../en/module/qrcode.rst:18 ../../en/module/qrcode.rst:81 +#: 3446a012fca347c881f11b136ed124e3 a1775f4475564dd4a5494e775b215754 +msgid "QRCode Scan in Continuous Mode" +msgstr "连续模式二维码识别" + +#: ../../en/module/qrcode.rst:20 6e48d8df20e94b258a218e336d612dab +msgid "Open the |cores3_qrcode_continuous_mode_example.m5f2| project in UiFlow2." +msgstr "在 UiFlow2 上打开 |cores3_qrcode_continuous_mode_example.m5f2| 项目。" + +#: ../../en/module/qrcode.rst:22 ../../en/module/qrcode.rst:83 +#: 231502ae7fb2405faffd507848de4d02 65a98e7610954a6e9f5eac62a92fd352 +msgid "" +"In **Continuous Mode**, pressing the button once starts decoding, and " +"pressing the button again stops decoding." +msgstr "在连续模式下,按下一次按键模块开始解码,再次按下按键模块停止解码。" + +#: ../../en/module/qrcode.rst:24 ../../en/module/qrcode.rst:39 +#: ../../en/module/qrcode.rst:54 ../../en/module/qrcode.rst:69 +#: ../../en/module/qrcode.rst:154 ../../en/module/qrcode.rst:175 +#: ../../en/module/qrcode.rst:194 ../../en/module/qrcode.rst:208 +#: ../../en/module/qrcode.rst:222 ../../en/module/qrcode.rst:241 +#: ../../en/module/qrcode.rst:263 ../../en/module/qrcode.rst:279 +#: ../../en/module/qrcode.rst:295 ../../en/module/qrcode.rst:311 +#: ../../en/module/qrcode.rst:327 ../../en/module/qrcode.rst:345 +#: ../../en/module/qrcode.rst:361 ../../en/module/qrcode.rst:377 +#: ../../en/module/qrcode.rst:393 ../../en/module/qrcode.rst:413 +#: ../../en/module/qrcode.rst:429 ../../en/module/qrcode.rst:449 +#: ../../en/module/qrcode.rst:469 ../../en/module/qrcode.rst:489 +#: ../../en/module/qrcode.rst:509 ../../en/module/qrcode.rst:546 +#: 053e40d9b9664047b79f3e04e58bff27 0a61b214632c4904bceccf6964eeebd8 +#: 1fb6222742474a999c7de421f3edddfe 4730bda370b6462ebb738799d12109a7 +#: a143dc1248894b6594c9162c0d7c818c b262ec6b33994c30bd06821696ade411 +#: dd0034e4dc8e4be1ae165c6f3db78647 e01bd6bc7dc1448d85725757e9d0ec49 +msgid "UiFlow2 Code Block:" +msgstr "UiFlow2 代码块:" + +#: ../../en/module/qrcode.rst:26 ea557fa8b86a4601af5e2629d357799d +msgid "|cores3_qrcode_continuous_mode_example.png|" +msgstr "" + +#: ../../en/refs/module.qrcode.ref:29 a822ef06d6d143d9aaccba46dff23e15 +msgid "cores3_qrcode_continuous_mode_example.png" +msgstr "" + +#: ../../en/module/qrcode.rst:28 ../../en/module/qrcode.rst:43 +#: ../../en/module/qrcode.rst:58 ../../en/module/qrcode.rst:73 +#: ../../en/module/qrcode.rst:91 ../../en/module/qrcode.rst:106 +#: ../../en/module/qrcode.rst:121 ../../en/module/qrcode.rst:136 +#: 0528be4602a14c4c92226c358d50759e 5e3bcbb5d2744f47b1a5ee18399b7b00 +#: 6ef3ce8536774272bfd0b9a00a83ea19 89d3039b068f4e0cb9b3bab1e4b9be05 +#: 8ca1e6c83096431cadf4b073f85029b9 9853435774c04cc191ab2d91ad1224dd +#: aecf2e6aa48f4adfb9ec7b7f0168af7e bbcf2ef8a01245548d58b8c7ca645561 +msgid "Example output:" +msgstr "示例输出:" + +#: ../../en/module/qrcode.rst:30 ../../en/module/qrcode.rst:45 +#: ../../en/module/qrcode.rst:60 ../../en/module/qrcode.rst:75 +#: ../../en/module/qrcode.rst:93 ../../en/module/qrcode.rst:108 +#: ../../en/module/qrcode.rst:123 ../../en/module/qrcode.rst:138 +#: 085a5fd5780c4b8fb3cbf1f7fe080702 088b0d18b100486ab740a0cc6b10fb18 +#: 284253a5bbac49a29164f047d2cabd77 5dfb7fb55f814acfb20238a98d1f7f95 +#: 62adb8137a9642118d118cb1b17bd3f0 637aec3200e7420abc4def63b3ad5f5a +#: aea1d971b8704aab9651607abb18c457 c72c062293ee46b5af17ef9dbaa1f6c3 +msgid "None" +msgstr "" + +#: ../../en/module/qrcode.rst:33 ../../en/module/qrcode.rst:96 +#: 4b0255b4dcec43a78a86ef2a99927420 8e810f9281194784b4b13a201329e296 +msgid "QRCode Scan in Auto Mode" +msgstr "自动模式二维码识别" + +#: ../../en/module/qrcode.rst:35 981d54317f824e4e95dd89ea66e2cb3b +msgid "Open the |cores3_qrcode_auto_mode_example.m5f2| project in UiFlow2." +msgstr "在 UiFlow2 上打开 |cores3_qrcode_auto_mode_example.m5f2| 项目。" + +#: ../../en/module/qrcode.rst:37 ../../en/module/qrcode.rst:98 +#: 0538b5de5b764b8ea787e4e94aae0284 3359d8d394604064b70f0acb96539d0b +msgid "" +"In **Auto Mode**, the module starts decoding when powered on and cannot " +"be stopped." +msgstr "在自动模式下,模块上电开始识别解码,不可停止。" + +#: ../../en/module/qrcode.rst:41 0abe1be6bffe45ca99c25dba83849e11 +msgid "|cores3_qrcode_auto_mode_example.png|" +msgstr "" + +#: ../../en/refs/module.qrcode.ref:30 511ba54f56b7409d80133067128c83a7 +msgid "cores3_qrcode_auto_mode_example.png" +msgstr "" + +#: ../../en/module/qrcode.rst:48 ../../en/module/qrcode.rst:111 +#: 5093adf617fe4a6a93a7d73841c87df7 7cd8c0cfe1ca43ababb6a50eb226eaee +msgid "QRCode Scan in Pulse Mode" +msgstr "脉冲模式二维码识别" + +#: ../../en/module/qrcode.rst:50 6b297be299404dfd8a569be25b063630 +msgid "Open the |cores3_qrcode_pulse_mode_example.m5f2| project in UiFlow2." +msgstr "在 UiFlow2 上打开 |cores3_qrcode_pulse_mode_example.m5f2| 项目。" + +#: ../../en/module/qrcode.rst:52 ../../en/module/qrcode.rst:113 +#: 0e7b17041f8749018f8c34feb3c0633e 27719011bb6e45dcb6820a3c312ad500 +msgid "" +"In **Pulse Mode**, set the TRIG pin to hold a low level for more than " +"20ms to trigger decoding once." +msgstr "在脉冲模式下,设置触发引脚超过 20ms 的低电平触发解码一次。" + +#: ../../en/module/qrcode.rst:56 2812d6692f14461eaccea136a4b3ea55 +msgid "|cores3_qrcode_pulse_mode_example.png|" +msgstr "" + +#: ../../en/refs/module.qrcode.ref:31 632c5b8e09324f8aacfb824f98f442dd +msgid "cores3_qrcode_pulse_mode_example.png" +msgstr "" + +#: ../../en/module/qrcode.rst:63 ../../en/module/qrcode.rst:126 +#: 40320e2a3cdc4f0aa440f9e70be03856 5074c652db4c4765a671a2a88e4b8b75 +msgid "QRCode Scan in Motion Sensing Mode" +msgstr "移动感应模式二维码识别" + +#: ../../en/module/qrcode.rst:65 6a9eb4ae60aa42f982db43a6a3118a3b +msgid "" +"Open the |cores3_qrcode_motion_sensing_mode_example.m5f2| project in " +"UiFlow2." +msgstr "在 UiFlow2 上打开 |cores3_qrcode_motion_sensing_mode_example.m5f2| 项目。" + +#: ../../en/module/qrcode.rst:67 ../../en/module/qrcode.rst:128 +#: 46681f68d5a348ad81f07d76e60592a0 50e21e6e58944b498efcd5e2685b00d9 +msgid "" +"In **Motion Sensing Mode**, the module automatically triggers decoding " +"when it detects a change in the scene based on visual recognition " +"information." +msgstr "在感应模式下,模块基于视觉识别信息检测到场景变化时,会自动触发解码。" + +#: ../../en/module/qrcode.rst:71 5eee8043fbd541fa9e928069d3a9844a +msgid "|cores3_qrcode_motion_sensing_mode_example.png|" +msgstr "" + +#: ../../en/refs/module.qrcode.ref:32 bdaa1499c2d74edc88876f64e4cae77d +msgid "cores3_qrcode_motion_sensing_mode_example.png" +msgstr "" + +#: ../../en/module/qrcode.rst:78 c98b4fa42d6f4522a43ddd7d073f3e40 +msgid "MicroPython Example:" +msgstr "MicroPython 应用示例:" + +#: ../../en/module/qrcode.rst:85 ../../en/module/qrcode.rst:100 +#: ../../en/module/qrcode.rst:115 ../../en/module/qrcode.rst:130 +#: ../../en/module/qrcode.rst:158 ../../en/module/qrcode.rst:179 +#: ../../en/module/qrcode.rst:198 ../../en/module/qrcode.rst:212 +#: ../../en/module/qrcode.rst:226 ../../en/module/qrcode.rst:245 +#: ../../en/module/qrcode.rst:267 ../../en/module/qrcode.rst:283 +#: ../../en/module/qrcode.rst:299 ../../en/module/qrcode.rst:315 +#: ../../en/module/qrcode.rst:331 ../../en/module/qrcode.rst:349 +#: ../../en/module/qrcode.rst:365 ../../en/module/qrcode.rst:381 +#: ../../en/module/qrcode.rst:397 ../../en/module/qrcode.rst:417 +#: ../../en/module/qrcode.rst:433 ../../en/module/qrcode.rst:453 +#: ../../en/module/qrcode.rst:473 ../../en/module/qrcode.rst:493 +#: ../../en/module/qrcode.rst:513 ../../en/module/qrcode.rst:550 +#: 248f2123565646ddac1f20c66fb4d49f 4c7ea1b5002e451581cb3020e7340cbc +#: 6afa7f2bbed345bdb8d132b7c11cc959 a6bb514d7a544abc9c42029836b850a9 +#: bb77fb17700743e38d5858a1457b7cb8 dbd768af743e4c82902062da6865b0ff +#: e7af55bd323e4df287a63626c7b48760 e82be7d4c04c4e60af2cba7ee1097367 +msgid "MicroPython Code Block:" +msgstr "MicroPython 代码块:" + +#: ../../en/module/qrcode.rst:141 b02c0821b6984e36916efeb7488964cc +msgid "**API**" +msgstr "API应用" + +#: ../../en/module/qrcode.rst:144 1c779da748bc4c4fa8c9046944211e72 +msgid "QRCodeModule" +msgstr "" + +#: ../../en/module/qrcode.rst:148 475e9a2769ca451998b31c5b8cdddb35 +msgid "Create an QRCodeModule object." +msgstr "创建一个 QRCodeModule 对象。" + +#: ../../en/module/qrcode.rst 1c61101e57154670815038d9cfa8bf4b +#: 2ddc6350ab034e9a894e4632ba7d9d54 62ac26e3b61944c6b172d107934ee4d8 +#: 9e122025a8284de0ba85d448c94b23b4 +msgid "Parameters" +msgstr "" + +#: ../../en/module/qrcode.rst:150 de800311af384a1c8dd2affc56e847fc +msgid "UART id." +msgstr "UART 端口号。" + +#: ../../en/module/qrcode.rst:151 e4941959e2ee40b18b3577cefd56911a +msgid "the UART TX pin." +msgstr "UART 发送引脚。" + +#: ../../en/module/qrcode.rst:152 b76ac621fd42435e830347b746500ff6 +msgid "the UART RX pin." +msgstr "UART 接收引脚。" + +#: ../../en/module/qrcode.rst:156 dd1220e70e9e42319a11d23d2b2e436c +msgid "|init.png|" +msgstr "" + +#: ../../en/refs/module.qrcode.ref:6 9567497098104269b8f4c6f6911b2bdf +msgid "init.png" +msgstr "" + +#: ../../en/module/qrcode.rst:168 832cf7f083e54a6686f3ce07c0ffb1ba +msgid "Set power." +msgstr "设置二维码识别模块电源。" + +#: ../../en/module/qrcode.rst:170 7551de9ba2bb466cbbaac1b42ac04b00 +msgid "- ``True`` : power on. - ``False`` : power off." +msgstr "- ``True`` : 打开。 - ``False`` : 关闭。" + +#: ../../en/module/qrcode.rst:172 31407ea00534457f8cb8fbc4cecf1c25 +msgid "``True`` : power on." +msgstr "``True`` : 打开。" + +#: ../../en/module/qrcode.rst:173 57471e2904f244838dd7c048297aa775 +msgid "``False`` : power off." +msgstr "``False`` : 关闭。" + +#: ../../en/module/qrcode.rst:177 2ee889e62a15484ba7adb47bf644d796 +msgid "|set_power.png|" +msgstr "" + +#: ../../en/refs/module.qrcode.ref:7 2efbe269b2184740a07bb92130a5b2f7 +msgid "set_power.png" +msgstr "" + +#: ../../en/module/qrcode.rst:187 06f437ef654841418080450a809e37ce +msgid "Set trigger pin value." +msgstr "设置触发引脚电平。" + +#: ../../en/module/qrcode.rst:189 14e66996e002473483e9f5cc64aeb8c9 +msgid "- ``0`` : low level. - ``1`` : high level." +msgstr "- ``0``:低电平。 - ``1``:高电平。" + +#: ../../en/module/qrcode.rst:191 588387f286b54b80ad3e02f7d2d52576 +msgid "``0`` : low level." +msgstr "``0``:低电平。" + +#: ../../en/module/qrcode.rst:192 588387f286b54b80ad3e02f7d2d52576 +msgid "``1`` : high level." +msgstr "``1``:高电平。" + +#: ../../en/module/qrcode.rst:196 82d12abc81f74a25b6ec9d70b07f16f3 +msgid "|set_trig.png|" +msgstr "" + +#: ../../en/refs/module.qrcode.ref:8 6375a025bed14ec89aa4044286870744 +msgid "set_trig.png" +msgstr "" + +#: ../../en/module/qrcode.rst:206 2ad6c00cb9304852b179d24b22ff4067 +msgid "Start decode." +msgstr "开始解码。" + +#: ../../en/module/qrcode.rst:210 1545c0dd414e481b8b110978cbd71f6e +msgid "|start_decode.png|" +msgstr "" + +#: ../../en/refs/module.qrcode.ref:9 816c6309e3b14e6987ed8921e4803bdd +msgid "start_decode.png" +msgstr "" + +#: ../../en/module/qrcode.rst:220 ef1b7d0f0b5d441792677cd22c47472b +msgid "Stop decode." +msgstr "停止解码。" + +#: ../../en/module/qrcode.rst:224 7b3c072f1fee46f883d34078da5401df +msgid "|stop_decode.png|" +msgstr "" + +#: ../../en/refs/module.qrcode.ref:10 44b98ae897504a8f8831e001b204ac02 +msgid "stop_decode.png" +msgstr "" + +#: ../../en/module/qrcode.rst:234 5254f39db77e48e6a36ebb4d0a1d7d82 +msgid "Read decode data." +msgstr "读取二维码数据。" + +#: ../../en/module/qrcode.rst 6c93d3bf963b4527b48ebc0dce7c7f96 +msgid "Returns" +msgstr "" + +#: ../../en/module/qrcode.rst:236 323d2b4432094c2b9c5a0400e13fec89 +msgid "qrcode data." +msgstr "二维码数据。" + +#: ../../en/module/qrcode.rst a5d48cb2285f4dec905ff500c27d5877 +msgid "Return type" +msgstr "" + +#: ../../en/module/qrcode.rst:239 958ec43dda1344078e7bcb04de2799b1 +msgid "If no data is received, return None." +msgstr "如果未识别到,则返回 None。" + +#: ../../en/module/qrcode.rst:243 a96fa88d9e2e49e0bd59008304ad1171 +msgid "|read.png|" +msgstr "" + +#: ../../en/refs/module.qrcode.ref:11 24f53f5ddc514695ba121c25f1fc898a +msgid "read.png" +msgstr "" + +#: ../../en/module/qrcode.rst:253 f4a78c7476784fdabfa313d6115d8046 +msgid "Set trigger mode." +msgstr "设置触发模式。" + +#: ../../en/module/qrcode.rst:255 53a2a9e97c994a14bdf5a0d4256ea165 +msgid "" +"The trigger mode. Available options: - ``TRIGGER_MODE_KEY``: Key Mode, " +"Triggers a single decode; decoding stops after a successful read. - " +"``TRIGGER_MODE_CONTINUOUS``: Continuous Mode, Triggers continuous " +"decoding; decoding continues even after a successful read and stops only " +"when triggered again. - ``TRIGGER_MODE_AUTO``: Auto Mode, Performs " +"continuous decoding upon power-up and cannot be stopped. - " +"``TRIGGER_MODE_PULSE``: Pulse Mode, The Trig pin's low-level signal " +"triggers decoding, which stops after a successful read or when the single" +" read time limit is reached. - ``TRIGGER_MODE_MOTION_SENSING``: Motion " +"Sensing Mode, Uses image recognition; decoding starts when a scene change" +" is detected." +msgstr "触发模式,可选项: - ``TRIGGER_MODE_KEY``: 按键模式,触发引脚低电平开始解码,高电平停止解码。" +" - ``TRIGGER_MODE_CONTINUOUS``: 连续模式: 调用 start_decode() 开始解码,调用 stop_decode() 停止解码。" +" - ``TRIGGER_MODE_AUTO``: 自动模式: 上电开始解码,不可停止。" +" - ``TRIGGER_MODE_PULSE``: 脉冲模式,触发引脚 20ms 以上低电平触发一次解码。" +" - ``TRIGGER_MODE_MOTION_SENSING``: 移动感应模式,使用图像检测,当检测到场景发生变化时触发解码。" + +#: ../../en/module/qrcode.rst:255 6a58b9fd7069460e83cebe10c6cc8a10 +msgid "The trigger mode. Available options:" +msgstr "" + +#: ../../en/module/qrcode.rst:257 7240a63db2e845d28053c4c02415265d +msgid "" +"``TRIGGER_MODE_KEY``: Key Mode, Triggers a single decode; decoding stops " +"after a successful read." +msgstr "``TRIGGER_MODE_KEY``: 按键模式,触发引脚低电平开始解码,高电平停止解码。"" + +#: ../../en/module/qrcode.rst:258 f9aa5a8ce6ff4c9292d4a4e36029720f +msgid "" +"``TRIGGER_MODE_CONTINUOUS``: Continuous Mode, Triggers continuous " +"decoding; decoding continues even after a successful read and stops only " +"when triggered again." +msgstr "``TRIGGER_MODE_CONTINUOUS``: 连续模式: 调用 start_decode() 开始解码,调用 stop_decode() 停止解码。" + +#: ../../en/module/qrcode.rst:259 3b4aee48c2d94e4aa3b8e2dcc581e541 +msgid "" +"``TRIGGER_MODE_AUTO``: Auto Mode, Performs continuous decoding upon " +"power-up and cannot be stopped." +msgstr "``TRIGGER_MODE_AUTO``: 自动模式: 上电开始解码,不可停止。" + +#: ../../en/module/qrcode.rst:260 5bb2013b36db47888e946c51571ac276 +msgid "" +"``TRIGGER_MODE_PULSE``: Pulse Mode, The Trig pin's low-level signal " +"triggers decoding, which stops after a successful read or when the single" +" read time limit is reached." +msgstr "``TRIGGER_MODE_PULSE``: 脉冲模式,触发引脚 20ms 以上低电平触发一次解码。" + +#: ../../en/module/qrcode.rst:261 481d20d870a748ee821d404c867d64ec +msgid "" +"``TRIGGER_MODE_MOTION_SENSING``: Motion Sensing Mode, Uses image " +"recognition; decoding starts when a scene change is detected." +msgstr "``TRIGGER_MODE_MOTION_SENSING``: 移动感应模式,使用图像检测,当检测到场景发生变化时触发解码。" + +#: ../../en/module/qrcode.rst:265 82d12abc81f74a25b6ec9d70b07f16f3 +msgid "|set_trigger_mode.png|" +msgstr "" + +#: ../../en/refs/module.qrcode.ref:12 6375a025bed14ec89aa4044286870744 +msgid "set_trigger_mode.png" +msgstr "" + +#: ../../en/module/qrcode.rst:275 f808269cfba243059913f1cfdd204d1a +msgid "Set decode delay." +msgstr "设置命令解码延迟 (按键模式下)。" + +#: ../../en/module/qrcode.rst:277 15f85f004aed4f42b73a8b629741fd04 +msgid "decode delay time(ms), 0 means continuous decoding until success." +msgstr "解码延迟时间(单位:毫秒),0 表示持续解码直到成功。" + +#: ../../en/module/qrcode.rst:281 88e3f963945f43868115ec4777975657 +msgid "|set_decode_delay.png|" +msgstr "" + +#: ../../en/refs/module.qrcode.ref:13 3a6c74bdeb0d4e39a0a7716e91e6b8b0 +msgid "set_decode_delay.png" +msgstr "" + +#: ../../en/module/qrcode.rst:291 06f437ef654841418080450a809e37ce +msgid "Set trigger timeout." +msgstr "设置触发超时。(脉冲模式下)。" + +#: ../../en/module/qrcode.rst:293 262f5669a5744213a383d88b66794983 +msgid "" +"trigger timeout time(ms), Decoding will automatically stop when the " +"duration exceeds this value." +msgstr "触发超时时间(单位:毫秒),解码持续时间超过该值时自动停止解码。" + +#: ../../en/module/qrcode.rst:297 ac23d836c11a4dd39ecbce214d2b4d60 +msgid "|set_trigger_timeout.png|" +msgstr "" + +#: ../../en/refs/module.qrcode.ref:14 bf2124c13fac493b985c587097b16743 +msgid "set_trigger_timeout.png" +msgstr "" + +#: ../../en/module/qrcode.rst:307 240937adec014053922bd0ed504880cd +msgid "Set motion detection sensitivity. (in Motion Sensing Mode)" +msgstr "设置移动感应灵敏度。(感应模式下)" + +#: ../../en/module/qrcode.rst:309 12873cbf75a247d096dfd62ee0f4087f +msgid "" +"sensitivity level. Range: 1~5. The higher the level, the more sensitive " +"it is to scene changes." +msgstr "灵敏度等级,范围 1-5,等级越高对场景变化越敏感。默认等级为 3。" + +#: ../../en/module/qrcode.rst:313 59cc032fb3404d6d919a6421f3a04020 +msgid "|set_motion_sensitivity.png|" +msgstr "" + +#: ../../en/refs/module.qrcode.ref:15 1d6b8ba25a8b407ca0eb6167b903137b +msgid "set_motion_sensitivity.png" +msgstr "" + +#: ../../en/module/qrcode.rst:323 38ec2ffd65fd4912b2c62341f67fe376 +msgid "Set continuous decode delay. (in Motion Sensing Mode)" +msgstr "设置持续解码延迟时间。(感应模式下)" + +#: ../../en/module/qrcode.rst:325 e68f792ec91a4b0aa03a26f6a659be64 +msgid "delay time(unit: 100ms), 0 means continuous decoding until timeout." +msgstr "延迟时间,单位为 100 毫秒。值为 0 表示持续解码直到超时。" + +#: ../../en/module/qrcode.rst:329 4dcee9d9e3a04c53ac763a4beca0932c +msgid "|set_continuous_decode_delay.png|" +msgstr "" + +#: ../../en/refs/module.qrcode.ref:16 b38bfec7173c48fd954482350bdeb0f0 +msgid "set_continuous_decode_delay.png" +msgstr "" + +#: ../../en/module/qrcode.rst:339 b4768ba0bd204a029b6f355b7cb42598 +msgid "Set trigger decode delay. (in Motion Sensing Mode)" +msgstr "设置触发解码延迟时间(感应模式下)" + +#: ../../en/module/qrcode.rst:341 96731c0de15b4bd58940cb1c1d540c07 +msgid "" +"Sets the trigger decoding delay time. This is the delay between re-" +"entering the scene change detection phase and starting recognition again " +"after detecting a change." +msgstr "触发解码延迟时间。当重新进入检测场景变化阶段,到检测到变化再次开始识读之间的延迟时长。" + +#: ../../en/module/qrcode.rst:343 6d2b5fb2328743e5a91d1b9a216ee4a5 +msgid "Trigger decode delay time(unit: ms)." +msgstr "触发解码延迟时间,单位为毫秒。" + +#: ../../en/module/qrcode.rst:347 0feb040fa16b4deaac3d0b828aae3ea9 +msgid "|set_trigger_decode_delay.png|" +msgstr "" + +#: ../../en/refs/module.qrcode.ref:17 0a63bcb7fad646749df16d8ed927a71c +msgid "set_trigger_decode_delay.png" +msgstr "" + +#: ../../en/module/qrcode.rst:357 7a4af6f5a6dd4b6a8ad31266e2eb8ed7 +msgid "Set same code interval." +msgstr "设置同码间隔时间。" + +#: ../../en/module/qrcode.rst:359 230045275d534f219e91586ca991b91c +msgid "The interval time for repeated recognition of the same code (unit: ms)." +msgstr "相同条码的重复识读间隔时间,单位为毫秒。" + +#: ../../en/module/qrcode.rst:363 8f5612f2987446d583728402d1ea87ec +msgid "|set_same_code_interval.png|" +msgstr "" + +#: ../../en/refs/module.qrcode.ref:18 891a4399954349429b680bfbd0f79af8 +msgid "set_same_code_interval.png" +msgstr "" + +#: ../../en/module/qrcode.rst:373 13d69b79095540aca5e1447f84fe9e41 +msgid "Set difference code interval." +msgstr "设置异码间隔时间。" + +#: ../../en/module/qrcode.rst:375 a3f5314d284d4bf3905a7d5922f60e8b +msgid "" +"The interval time for repeated recognition of the difference code (unit: " +"ms)." +msgstr "不同码的识读间隔时间,单位为毫秒。" + +#: ../../en/module/qrcode.rst:379 59fbced5d3424e4cb489e688ac683cab +msgid "|set_diff_code_interval.png|" +msgstr "" + +#: ../../en/refs/module.qrcode.ref:19 841dbbc64d584c4eb4b4a4f902f2c7eb +msgid "set_diff_code_interval.png" +msgstr "" + +#: ../../en/module/qrcode.rst:389 70f0c00d25ff4604b7c8928e0e0b71b8 +msgid "Set same code no delay." +msgstr "设置同码非延迟输出。" + +#: ../../en/module/qrcode.rst:391 feb603cccec6429cacada6e7d03311f4 +msgid "" +"Whether to enable non-delay output for the same code. True means enabled," +" False means disabled." +msgstr "是否开启同码非延迟输出,True 表示开启,False 表示关闭。" + +#: ../../en/module/qrcode.rst:395 92b808563ee443c08f1fee8446bde22f +msgid "|set_same_code_no_delay.png|" +msgstr "" + +#: ../../en/refs/module.qrcode.ref:20 584f9f0522a14676858ac27b23037a6e +msgid "set_same_code_no_delay.png" +msgstr "" + +#: ../../en/module/qrcode.rst:405 5039e18385cc4d308e7770a99173256b +msgid "Set fill light mode." +msgstr "设置补光灯模式。" + +#: ../../en/module/qrcode.rst:407 51e05556426744459a5b1d79e7480b47 +msgid "" +"The fill light mode. Available options: - ``FILL_LIGHT_OFF``: Light off." +" - ``FILL_LIGHT_ON``: Light on. - ``FILL_LIGHT_ON_DECODE``: Light on " +"during decoding." +msgstr "补光灯模式,可选项:- ``FILL_LIGHT_OFF``: 常灭。" +" - ``FILL_LIGHT_ON``: 常亮。 - ``FILL_LIGHT_ON_DECODE``: 解码时亮。" + +#: ../../en/module/qrcode.rst:407 3403058ab58a48fabef5521109be4ce9 +msgid "The fill light mode. Available options:" +msgstr "补光灯模式,可选项:" + +#: ../../en/module/qrcode.rst:409 7ae1c7ebd34845c58db1f7a64e684a35 +msgid "``FILL_LIGHT_OFF``: Light off." +msgstr "``FILL_LIGHT_OFF``: 常灭。" + +#: ../../en/module/qrcode.rst:410 5360c9c83057423c8fb80b2881d3d76f +msgid "``FILL_LIGHT_ON``: Light on." +msgstr "``FILL_LIGHT_ON``: 常亮。" + +#: ../../en/module/qrcode.rst:411 4918186c32aa410690ab528fc1e7fcb8 +msgid "``FILL_LIGHT_ON_DECODE``: Light on during decoding." +msgstr "``FILL_LIGHT_ON_DECODE``: 解码时亮。" + +#: ../../en/module/qrcode.rst:415 a0b2896f7a1b493b9dd9c923757b181a +msgid "|set_fill_light_mode.png|" +msgstr "" + +#: ../../en/refs/module.qrcode.ref:21 e6f15be9a2dc44e3bfaa6574af4979eb +msgid "set_fill_light_mode.png" +msgstr "" + +#: ../../en/module/qrcode.rst:425 87d6d09a37e7482a94d00ce0bc186fe4 +msgid "Set fill light brightness." +msgstr "设置补光灯亮度。" + +#: ../../en/module/qrcode.rst:427 57f773a0199e4c2ab76bfc5c138afe7b +msgid "The fill light brightness. Range: 0~100." +msgstr "补光灯亮度,范围:0~100。" + +#: ../../en/module/qrcode.rst:431 da056e3941744dd7ab00553606bc0a2d +msgid "|set_fill_light_brightness.png|" +msgstr "" + +#: ../../en/refs/module.qrcode.ref:22 f787a0f262164452af63c0f85afb45b7 +msgid "set_fill_light_brightness.png" +msgstr "" + +#: ../../en/module/qrcode.rst:441 cb458c2dace84f22869425f26b9dd249 +msgid "Set positioning light mode." +msgstr "设置定位灯模式" + +#: ../../en/module/qrcode.rst:443 a30565f21c9f49559def270d49e1fccb +msgid "" +"The positioning light mode. Available options: - ``POS_LIGHT_OFF``: " +"Light off. - ``POS_LIGHT_ON_DECODE``: Light on during decoding. - " +"``POS_LIGHT_FLASH_ON_DECODE``: Light flash during decoding." +msgstr "定位灯模式,可选项: - ``POS_LIGHT_OFF``: 常灭。" +" - ``POS_LIGHT_ON_DECODE``: 解码时亮。 - ``POS_LIGHT_FLASH_ON_DECODE``: 解码时闪烁。" + +#: ../../en/module/qrcode.rst:443 e6f871a5b5294cf2b12fd4b6360f4d0e +msgid "The positioning light mode. Available options:" +msgstr "定位灯模式,可选项目:" + +#: ../../en/module/qrcode.rst:445 9229f937f77f448abf6b31dd34f9dc60 +msgid "``POS_LIGHT_OFF``: Light off." +msgstr "``POS_LIGHT_OFF``: 常灭。" + +#: ../../en/module/qrcode.rst:446 96195466961a4928a292592d892f4512 +msgid "``POS_LIGHT_ON_DECODE``: Light on during decoding." +msgstr "``POS_LIGHT_ON_DECODE``: 解码时亮。" + +#: ../../en/module/qrcode.rst:447 e7b2495d105f437c9a53971c6263e810 +msgid "``POS_LIGHT_FLASH_ON_DECODE``: Light flash during decoding." +msgstr "``POS_LIGHT_FLASH_ON_DECODE``: 解码时闪烁。" + +#: ../../en/module/qrcode.rst:451 e9ba2ef0e5b4463ca97d61944cd5a5ad +msgid "|set_pos_light_mode.png|" +msgstr "" + +#: ../../en/refs/module.qrcode.ref:23 3fbc55962a7d4c549edc1c4ca57d13d2 +msgid "set_pos_light_mode.png" +msgstr "" + +#: ../../en/module/qrcode.rst:461 634e464ca4cf41c6ab33d00d6cba1ea5 +msgid "Set startup tone." +msgstr "设置启动提示音。" + +#: ../../en/module/qrcode.rst:463 58f8d4ffe19f4a79aba9496d833fecca +msgid "" +"- ``0``: Disable startup tone. - ``1``: Play 4 beeps. - ``2``: Play 2 " +"beeps." +msgstr "- ``0``: 关闭启动提示音。 - ``1``: 4 声。 - ``2``: 2 声。" + +#: ../../en/module/qrcode.rst:465 6506c092b43b41f1b65e0d859ee00898 +msgid "``0``: Disable startup tone." +msgstr "``0``: 关闭启动提示音。" + +#: ../../en/module/qrcode.rst:466 588387f286b54b80ad3e02f7d2d52576 +msgid "``1``: Play 4 beeps." +msgstr "``1``: 4 声。" + +#: ../../en/module/qrcode.rst:467 0bd3d54391a94ea281702a9f909bbc6d +msgid "``2``: Play 2 beeps." +msgstr "``2``: 2 声。" + +#: ../../en/module/qrcode.rst:471 fe94387ad0be473999835309b14882d3 +msgid "|set_startup_tone.png|" +msgstr "" + +#: ../../en/refs/module.qrcode.ref:24 f345b80f309c4822b0b4747886d799f0 +msgid "set_startup_tone.png" +msgstr "" + +#: ../../en/module/qrcode.rst:481 09069075814f4930a4f3eb7bd20108b5 +msgid "Set decode success beep." +msgstr "设置解码成功提示音。" + +#: ../../en/module/qrcode.rst:483 1b48242163734a3eb96c9f4ac02d07f2 +msgid "" +"- ``0``: No prompt sound. - ``1``: Play prompt sound once. - ``2``: Play " +"prompt sound twice." +msgstr "- ``0``: 无提示音。 - ``1``: 播放1次。 - ``2``: 播放2次。" + +#: ../../en/module/qrcode.rst:485 3c7bc8459776451caaf76187f6ee7903 +msgid "``0``: No prompt sound." +msgstr "``0``: 无提示音。" + +#: ../../en/module/qrcode.rst:486 6a68fa820cef44b0b66d6d2a25612c29 +msgid "``1``: Play prompt sound once." +msgstr "``1``: 播放1次。" + +#: ../../en/module/qrcode.rst:487 b31b6f7a7c094c7b9af986efa04296ea +msgid "``2``: Play prompt sound twice." +msgstr "``2``: 播放2次。" + +#: ../../en/module/qrcode.rst:491 ac65c0dfd5fb4d919118046747710bda +msgid "|set_decode_success_beep.png|" +msgstr "" + +#: ../../en/refs/module.qrcode.ref:25 b870c3b2a2694b7a8784f7cce85f2671 +msgid "set_decode_success_beep.png" +msgstr "" + +#: ../../en/module/qrcode.rst:501 b7c9dc8f64184cbe8d07a34b379caa6b +msgid "Set case conversion." +msgstr "设置大小写转换。" + +#: ../../en/module/qrcode.rst:503 50d7b7abd2ca4fdfadb0134f932bf889 +msgid "" +"- ``0``: Off (Original data). - ``1``: Convert to uppercase. - ``2``: " +"Convert to lowercase." +msgstr "- ``0``: 不转换。 - ``1``: 转为大写。 - ``2``: 转为小写。" + +#: ../../en/module/qrcode.rst:505 6a952872d904475b94b6e72aab2e6f62 +msgid "``0``: Off (Original data)." +msgstr "``0``: 不转换。" + +#: ../../en/module/qrcode.rst:506 b1c9da7007634449b7332fdec567f397 +msgid "``1``: Convert to uppercase." +msgstr "``1``: 转为大写。" + +#: ../../en/module/qrcode.rst:507 6d6223e4e839426d9dcb9feed120e772 +msgid "``2``: Convert to lowercase." +msgstr "``2``: 转为小写。" + +#: ../../en/module/qrcode.rst:511 664eac5cc92b452fb5948d538ba9beef +msgid "|set_case_conversion.png|" +msgstr "" + +#: ../../en/refs/module.qrcode.ref:26 74ecd23578f54b28be0bcdbb5516f814 +msgid "set_case_conversion.png" +msgstr "" + +#: ../../en/module/qrcode.rst:523 17937105812e47e58f999eedb0d711f7 +msgid "" +"- ``0``: No protocol - ``1``: Format 1: [0x03] + Data Length (2 bytes) + " +"Data - ``2``: Format 2: [0x03] + Data Length + Number of Barcodes + Code " +"1 Data Length + Code 1 Data + ... + CRC - ``3``: Format 3: [0x03] + Data " +"Length + Number of Barcodes + Code 1 ID + Code 1 Data Length + Code 1 " +"Data + ... + CRC" +msgstr "" +"- ``0``: 无协议- ``1``: 格式 1:[0x03] + 数据长度(2bytes) + 数据- ``2``: 格式 2:[0x03] " +"+ 数据长度 + 条码个数 + 条码 1 数据长度 + 条码 1 数据 +... + CRC- ``3``: 格式 3:[0x03] + 数据长度" +" + 条码个数 + 条码 1ID 号 + 条码 1 数据长度 + 条码 1 数据 + ... + CR" + +#: ../../en/module/qrcode.rst:525 bc3a96b96dac45f6b08d22e5c54d6865 +msgid "``0``: No protocol" +msgstr "`0``: 无协议 "" + +#: ../../en/module/qrcode.rst:526 e3bac21632a048b49697aa294f1c1d78 +msgid "``1``: Format 1: [0x03] + Data Length (2 bytes) + Data" +msgstr "``1``: 格式 1:[0x03] + 数据长度(2bytes) + 数据" + +#: ../../en/module/qrcode.rst:527 b7052ffd7e1844d194230df57ee448ad +msgid "" +"``2``: Format 2: [0x03] + Data Length + Number of Barcodes + Code 1 Data " +"Length + Code 1 Data + ... + CRC" +msgstr "``2``: 格式 2:[0x03] + 数据长度 + 条码个数 + 条码 1 数据长度 + 条码 1 数据 +... + CRC" + +#: ../../en/module/qrcode.rst:528 63a6d0442d37475f85da5f445dbddd97 +msgid "" +"``3``: Format 3: [0x03] + Data Length + Number of Barcodes + Code 1 ID + " +"Code 1 Data Length + Code 1 Data + ... + CRC" +msgstr "" +"``3``: 格式 3:[0x03] + 数据长度 + 条码个数 + 条码 1ID 号 + 条码 1 数据长度 + 条码 1 数据 + ... +" +" CRC" + +#: ../../en/module/qrcode.rst:530 eb03557febad49ab9e149460632febd1 +msgid "CRC generate reference program." +msgstr "CRC 生成参考程序。" + +#: ../../en/module/qrcode.rst:548 4b7bcb829d9a49868d4dada4d84c521c +msgid "|set_protocol_format.png|" +msgstr "" + +#: ../../en/refs/module.qrcode.ref:27 0c6a69771ffb47e2b51710c95ffffef3 +msgid "set_protocol_format.png" +msgstr "" + \ No newline at end of file diff --git a/examples/base/qrcode/atoms3_qrcode_auto_mode_example.m5f2 b/examples/base/qrcode/atoms3_qrcode_auto_mode_example.m5f2 new file mode 100644 index 00000000..d0884d6f --- /dev/null +++ b/examples/base/qrcode/atoms3_qrcode_auto_mode_example.m5f2 @@ -0,0 +1 @@ +{"version":"V2.0","versionNumber":"V2.2.4","type":"atoms3","components":[{"name":"screen","type":"screen","layer":0,"screenId":"builtin","screenName":"","id":"__atoms3_screen","createTime":1743036051902,"x":0,"y":0,"width":128,"height":128,"backgroundColor":"#000000","size":0,"isSelected":true},{"name":"title0","type":"title","layer":1,"screenId":"builtin","screenName":"","id":"b3HouTo^jcpjeXh_","createTime":1743036136724,"x":0,"y":0,"color":"#ffffff","backgroundColor":"#0000FF","text":"QRCode","textOffset":3,"font":"Widgets.FONTS.DejaVu18","isSelected":false},{"name":"label_data","type":"label","layer":2,"screenId":"builtin","screenName":"","id":"vXA+r&QTPnZ&W1UO","createTime":1743036144638,"x":5,"y":60,"color":"#ffffff","backgroundColor":"#000000","text":"data","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false},{"name":"label_status","type":"label","layer":3,"screenId":"builtin","screenName":"","id":"oXXAn+E!GWYPtFF@","createTime":1743038251516,"x":5,"y":25,"color":"#00ff00","backgroundColor":"#000000","text":"scanning","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false}],"resources":[{"hardware":["hardware_button","hardware_pin_button","imu","ir"]},{"base":["base_qrcode"]}],"units":[],"hats":[],"bases":[{"type":"base_qrcode","name":"base_qrcode","id":"x7xDmBxl$NxQ!p$Z","createTime":1743475616876,"initBlockType":"base_qrcode_init","initBlockId":"F/e[5AfAd|nyI/%R]@?["}],"i2cs":[],"blockly":"datatrue25678TRIGGER_MODE_AUTOtruedatadatahello M5datalabel_dataLabeldata","screen":[{"simulationName":"Built-in","type":"builtin","width":128,"height":128,"scale":1.3,"screenName":"","blockId":"","screenColorType":0,"id":"builtin","createTime":1743036051901}],"logicWhenNum":0,"customList":[]} \ No newline at end of file diff --git a/examples/base/qrcode/atoms3_qrcode_auto_mode_example.py b/examples/base/qrcode/atoms3_qrcode_auto_mode_example.py new file mode 100644 index 00000000..cbf12b28 --- /dev/null +++ b/examples/base/qrcode/atoms3_qrcode_auto_mode_example.py @@ -0,0 +1,50 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import os, sys, io +import M5 +from M5 import * +from base import AtomicQRCodeBase + + +title0 = None +label_data = None +label_status = None +base_qrcode = None +data = None + + +def setup(): + global title0, label_data, label_status, base_qrcode, data + M5.begin() + title0 = Widgets.Title("QRCode", 3, 0xFFFFFF, 0x0000FF, Widgets.FONTS.DejaVu18) + label_data = Widgets.Label("data", 5, 60, 1.0, 0xFFFFFF, 0x000000, Widgets.FONTS.DejaVu18) + label_status = Widgets.Label( + "scanning", 5, 25, 1.0, 0x00FF00, 0x000000, Widgets.FONTS.DejaVu18 + ) + base_qrcode = AtomicQRCodeBase(2, 5, 6, 7, 8) + base_qrcode.set_trigger_mode(base_qrcode.TRIGGER_MODE_AUTO) + + +def loop(): + global title0, label_data, label_status, base_qrcode, data + M5.update() + data = base_qrcode.read() + if data: + print(data.decode()) + label_data.setText(str(data.decode())) + + +if __name__ == "__main__": + try: + setup() + while True: + loop() + except (Exception, KeyboardInterrupt) as e: + try: + from utility import print_error_msg + + print_error_msg(e) + except ImportError: + print("please update to latest firmware") diff --git a/examples/base/qrcode/atoms3_qrcode_host_mode_example.m5f2 b/examples/base/qrcode/atoms3_qrcode_host_mode_example.m5f2 new file mode 100644 index 00000000..3830dd5f --- /dev/null +++ b/examples/base/qrcode/atoms3_qrcode_host_mode_example.m5f2 @@ -0,0 +1 @@ +{"version":"V2.0","versionNumber":"V2.2.4","type":"atoms3","components":[{"name":"screen","type":"screen","layer":0,"screenId":"builtin","screenName":"","id":"__atoms3_screen","createTime":1743037389484,"x":0,"y":0,"width":128,"height":128,"backgroundColor":"#000000","size":0,"isSelected":true},{"name":"title0","type":"title","layer":1,"screenId":"builtin","screenName":"","id":"iX$fTx$n0Ld26KJ9","createTime":1743037477368,"x":0,"y":0,"color":"#ffffff","backgroundColor":"#0000FF","text":"QRCode","textOffset":3,"font":"Widgets.FONTS.DejaVu18","isSelected":false},{"name":"label_data","type":"label","layer":2,"screenId":"builtin","screenName":"","id":"xM$q$+lDMdNTnGXh","createTime":1743037487726,"x":5,"y":60,"color":"#ffffff","backgroundColor":"#000000","text":"data","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false},{"name":"label_status","type":"label","layer":3,"screenId":"builtin","screenName":"","id":"kBh1eanBWxL^KZqc","createTime":1743037919981,"x":5,"y":25,"color":"#ffffff","backgroundColor":"#000000","text":"stop scan","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false}],"resources":[{"hardware":["hardware_button","hardware_pin_button","imu","ir"]},{"base":["base_qrcode"]}],"units":[],"hats":[],"bases":[{"type":"base_qrcode","name":"base_qrcode","id":"xJwSRp`7g%!u0iUw","createTime":1743475945373,"initBlockType":"base_qrcode_init","initBlockId":"%D$Hu-`|)G[anjh8TKt3"}],"i2cs":[],"blockly":"is_scanningdatatrue25678is_scanningFALSETRIGGER_MODE_HOSTPOS_LIGHT_ON_DECODEFILL_LIGHT_ON_DECODEtruedatadatalabel_dataLabeldatahello M5dataBtnAWAS_CLICKEDis_scanninglabel_statusstop scanlabel_statusrgb255255255rgb000label_statusscanninglabel_statusrgb02550rgb000is_scanningis_scanning","screen":[{"simulationName":"Built-in","type":"builtin","width":128,"height":128,"scale":1.3,"screenName":"","blockId":"","screenColorType":0,"id":"builtin","createTime":1743037389483}],"logicWhenNum":0,"customList":[]} \ No newline at end of file diff --git a/examples/base/qrcode/atoms3_qrcode_host_mode_example.py b/examples/base/qrcode/atoms3_qrcode_host_mode_example.py new file mode 100644 index 00000000..32fc8fb9 --- /dev/null +++ b/examples/base/qrcode/atoms3_qrcode_host_mode_example.py @@ -0,0 +1,69 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import os, sys, io +import M5 +from M5 import * +from base import AtomicQRCodeBase + + +title0 = None +label_data = None +label_status = None +base_qrcode = None +is_scanning = None +data = None + + +def btna_was_clicked_event(state): + global title0, label_data, label_status, base_qrcode, is_scanning, data + if is_scanning: + base_qrcode.stop_decode() + label_status.setText(str("stop scan")) + label_status.setColor(0xFFFFFF, 0x000000) + else: + base_qrcode.start_decode() + label_status.setText(str("scanning")) + label_status.setColor(0x00FF00, 0x000000) + is_scanning = not is_scanning + + +def setup(): + global title0, label_data, label_status, base_qrcode, is_scanning, data + M5.begin() + title0 = Widgets.Title("QRCode", 3, 0xFFFFFF, 0x0000FF, Widgets.FONTS.DejaVu18) + label_data = Widgets.Label("data", 5, 60, 1.0, 0xFFFFFF, 0x000000, Widgets.FONTS.DejaVu18) + label_status = Widgets.Label( + "stop scan", 5, 25, 1.0, 0xFFFFFF, 0x000000, Widgets.FONTS.DejaVu18 + ) + BtnA.setCallback(type=BtnA.CB_TYPE.WAS_CLICKED, cb=btna_was_clicked_event) + base_qrcode = AtomicQRCodeBase(2, 5, 6, 7, 8) + is_scanning = False + base_qrcode.set_trigger_mode(base_qrcode.TRIGGER_MODE_HOST) + base_qrcode.set_pos_light_mode(base_qrcode.POS_LIGHT_ON_DECODE) + base_qrcode.set_fill_light_mode(base_qrcode.FILL_LIGHT_ON_DECODE) + + +def loop(): + global title0, label_data, label_status, base_qrcode, is_scanning, data + M5.update() + data = base_qrcode.read() + if data: + label_data.setText(str(data.decode())) + print(data.decode()) + base_qrcode.start_decode() + + +if __name__ == "__main__": + try: + setup() + while True: + loop() + except (Exception, KeyboardInterrupt) as e: + try: + from utility import print_error_msg + + print_error_msg(e) + except ImportError: + print("please update to latest firmware") diff --git a/examples/base/qrcode/atoms3_qrcode_key_mode_example.m5f2 b/examples/base/qrcode/atoms3_qrcode_key_mode_example.m5f2 new file mode 100644 index 00000000..8a928c7e --- /dev/null +++ b/examples/base/qrcode/atoms3_qrcode_key_mode_example.m5f2 @@ -0,0 +1 @@ +{"version":"V2.0","versionNumber":"V2.2.4","type":"atoms3","components":[{"name":"screen","type":"screen","layer":0,"screenId":"builtin","screenName":"","id":"__atoms3_screen","createTime":1743036051902,"x":0,"y":0,"width":128,"height":128,"backgroundColor":"#000000","size":0,"isSelected":true},{"name":"title0","type":"title","layer":1,"screenId":"builtin","screenName":"","id":"b3HouTo^jcpjeXh_","createTime":1743036136724,"x":0,"y":0,"color":"#ffffff","backgroundColor":"#0000FF","text":"QRCode","textOffset":3,"font":"Widgets.FONTS.DejaVu18","isSelected":false},{"name":"label_data","type":"label","layer":2,"screenId":"builtin","screenName":"","id":"vXA+r&QTPnZ&W1UO","createTime":1743036144638,"x":5,"y":60,"color":"#ffffff","backgroundColor":"#000000","text":"data","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false},{"name":"label_status","type":"label","layer":3,"screenId":"builtin","screenName":"","id":"oXXAn+E!GWYPtFF@","createTime":1743038251516,"x":5,"y":25,"color":"#ffffff","backgroundColor":"#000000","text":"stop scan","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false}],"resources":[{"hardware":["hardware_button","hardware_pin_button","imu","ir"]},{"base":["base_qrcode"]}],"units":[],"hats":[],"bases":[{"type":"base_qrcode","name":"base_qrcode","id":"y!*j6^-#bRN3H@wc","createTime":1743472842366,"initBlockType":"base_qrcode_init","initBlockId":"??0hDB)l2=n./?B%4OT|"}],"i2cs":[],"blockly":"is_scanningdatastatustrue25678TRIGGER_MODE_KEYis_scanningFALSEstatusis_scanningtrueBtnA0is_scanningTRUE1is_scanningFALSENEQstatusis_scanningstatusis_scanningstatuslabel_statusrgb02550rgb000label_statusscanninglabel_statusrgb255255255rgb000label_statusstop scandatadatalabel_dataLabeldatahello M5data","screen":[{"simulationName":"Built-in","type":"builtin","width":128,"height":128,"scale":1.3,"screenName":"","blockId":"","screenColorType":0,"id":"builtin","createTime":1743036051901}],"logicWhenNum":0,"customList":[]} \ No newline at end of file diff --git a/examples/base/qrcode/atoms3_qrcode_key_mode_example.py b/examples/base/qrcode/atoms3_qrcode_key_mode_example.py new file mode 100644 index 00000000..7e97d75c --- /dev/null +++ b/examples/base/qrcode/atoms3_qrcode_key_mode_example.py @@ -0,0 +1,68 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import os, sys, io +import M5 +from M5 import * +from base import AtomicQRCodeBase + + +title0 = None +label_data = None +label_status = None +base_qrcode = None +is_scanning = None +status = None +data = None + + +def setup(): + global title0, label_data, label_status, base_qrcode, is_scanning, status, data + M5.begin() + title0 = Widgets.Title("QRCode", 3, 0xFFFFFF, 0x0000FF, Widgets.FONTS.DejaVu18) + label_data = Widgets.Label("data", 5, 60, 1.0, 0xFFFFFF, 0x000000, Widgets.FONTS.DejaVu18) + label_status = Widgets.Label( + "stop scan", 5, 25, 1.0, 0xFFFFFF, 0x000000, Widgets.FONTS.DejaVu18 + ) + base_qrcode = AtomicQRCodeBase(2, 5, 6, 7, 8) + base_qrcode.set_trigger_mode(base_qrcode.TRIGGER_MODE_KEY) + is_scanning = False + status = is_scanning + + +def loop(): + global title0, label_data, label_status, base_qrcode, is_scanning, status, data + M5.update() + if BtnA.isPressed(): + base_qrcode.set_trig(0) + is_scanning = True + else: + base_qrcode.set_trig(1) + is_scanning = False + if status != is_scanning: + status = is_scanning + if status: + label_status.setColor(0x00FF00, 0x000000) + label_status.setText(str("scanning")) + else: + label_status.setColor(0xFFFFFF, 0x000000) + label_status.setText(str("stop scan")) + data = base_qrcode.read() + if data: + label_data.setText(str(data.decode())) + print(data.decode()) + + +if __name__ == "__main__": + try: + setup() + while True: + loop() + except (Exception, KeyboardInterrupt) as e: + try: + from utility import print_error_msg + + print_error_msg(e) + except ImportError: + print("please update to latest firmware") diff --git a/examples/base/qrcode/atoms3_qrcode_motion_sensing_mode_example.m5f2 b/examples/base/qrcode/atoms3_qrcode_motion_sensing_mode_example.m5f2 new file mode 100644 index 00000000..cb578014 --- /dev/null +++ b/examples/base/qrcode/atoms3_qrcode_motion_sensing_mode_example.m5f2 @@ -0,0 +1 @@ +{"version":"V2.0","versionNumber":"V2.2.4","type":"atoms3","components":[{"name":"screen","type":"screen","layer":0,"screenId":"builtin","screenName":"","id":"__atoms3_screen","createTime":1743036051902,"x":0,"y":0,"width":128,"height":128,"backgroundColor":"#000000","size":0,"isSelected":true},{"name":"title0","type":"title","layer":1,"screenId":"builtin","screenName":"","id":"b3HouTo^jcpjeXh_","createTime":1743036136724,"x":0,"y":0,"color":"#ffffff","backgroundColor":"#0000FF","text":"QRCode","textOffset":3,"font":"Widgets.FONTS.DejaVu18","isSelected":false},{"name":"label_data","type":"label","layer":2,"screenId":"builtin","screenName":"","id":"vXA+r&QTPnZ&W1UO","createTime":1743036144638,"x":5,"y":60,"color":"#ffffff","backgroundColor":"#000000","text":"data","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false},{"name":"label_status","type":"label","layer":3,"screenId":"builtin","screenName":"","id":"oXXAn+E!GWYPtFF@","createTime":1743038251516,"x":5,"y":25,"color":"#00ff00","backgroundColor":"#000000","text":"detecting","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false}],"resources":[{"hardware":["hardware_button","hardware_pin_button","imu","ir"]},{"base":["base_qrcode"]}],"units":[],"hats":[],"bases":[{"type":"base_qrcode","name":"base_qrcode","id":"cFP2!gtI3zzI*e_o","createTime":1743475818858,"initBlockType":"base_qrcode_init","initBlockId":"eI?mkID(68^OKt,daMz}"}],"i2cs":[],"blockly":"datatrue25678TRIGGER_MODE_MOTION_SENSINGtruedatadatalabel_dataLabeldata","screen":[{"simulationName":"Built-in","type":"builtin","width":128,"height":128,"scale":1.3,"screenName":"","blockId":"","screenColorType":0,"id":"builtin","createTime":1743036051901}],"logicWhenNum":0,"customList":[]} \ No newline at end of file diff --git a/examples/base/qrcode/atoms3_qrcode_motion_sensing_mode_example.py b/examples/base/qrcode/atoms3_qrcode_motion_sensing_mode_example.py new file mode 100644 index 00000000..3deebb87 --- /dev/null +++ b/examples/base/qrcode/atoms3_qrcode_motion_sensing_mode_example.py @@ -0,0 +1,49 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import os, sys, io +import M5 +from M5 import * +from base import AtomicQRCodeBase + + +title0 = None +label_data = None +label_status = None +base_qrcode = None +data = None + + +def setup(): + global title0, label_data, label_status, base_qrcode, data + M5.begin() + title0 = Widgets.Title("QRCode", 3, 0xFFFFFF, 0x0000FF, Widgets.FONTS.DejaVu18) + label_data = Widgets.Label("data", 5, 60, 1.0, 0xFFFFFF, 0x000000, Widgets.FONTS.DejaVu18) + label_status = Widgets.Label( + "detecting", 5, 25, 1.0, 0x00FF00, 0x000000, Widgets.FONTS.DejaVu18 + ) + base_qrcode = AtomicQRCodeBase(2, 5, 6, 7, 8) + base_qrcode.set_trigger_mode(base_qrcode.TRIGGER_MODE_MOTION_SENSING) + + +def loop(): + global title0, label_data, label_status, base_qrcode, data + M5.update() + data = base_qrcode.read() + if data: + label_data.setText(str(data.decode())) + + +if __name__ == "__main__": + try: + setup() + while True: + loop() + except (Exception, KeyboardInterrupt) as e: + try: + from utility import print_error_msg + + print_error_msg(e) + except ImportError: + print("please update to latest firmware") diff --git a/examples/base/qrcode/atoms3_qrcode_pulse_mode_example.m5f2 b/examples/base/qrcode/atoms3_qrcode_pulse_mode_example.m5f2 new file mode 100644 index 00000000..c4e207dd --- /dev/null +++ b/examples/base/qrcode/atoms3_qrcode_pulse_mode_example.m5f2 @@ -0,0 +1 @@ +{"version":"V2.0","versionNumber":"V2.2.4","type":"atoms3","components":[{"name":"screen","type":"screen","layer":0,"screenId":"builtin","screenName":"","id":"__atoms3_screen","createTime":1743037389484,"x":0,"y":0,"width":128,"height":128,"backgroundColor":"#000000","size":0,"isSelected":true},{"name":"title0","type":"title","layer":1,"screenId":"builtin","screenName":"","id":"iX$fTx$n0Ld26KJ9","createTime":1743037477368,"x":0,"y":0,"color":"#ffffff","backgroundColor":"#0000FF","text":"QRCode","textOffset":3,"font":"Widgets.FONTS.DejaVu18","isSelected":false},{"name":"label_data","type":"label","layer":2,"screenId":"builtin","screenName":"","id":"xM$q$+lDMdNTnGXh","createTime":1743037487726,"x":5,"y":60,"color":"#ffffff","backgroundColor":"#000000","text":"data","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false},{"name":"label_status","type":"label","layer":3,"screenId":"builtin","screenName":"","id":"kBh1eanBWxL^KZqc","createTime":1743037919981,"x":5,"y":25,"color":"#ffffff","backgroundColor":"#000000","text":"stop scan","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false}],"resources":[{"hardware":["hardware_button","hardware_pin_button","imu","ir"]},{"base":["base_qrcode"]}],"units":[],"hats":[],"bases":[{"type":"base_qrcode","name":"base_qrcode","id":"xoG&-sd9$Fl=UO*J","createTime":1743475726855,"initBlockType":"base_qrcode_init","initBlockId":"rT^cf_h}VxkgcNs{h,xP"}],"i2cs":[],"blockly":"datatrue25678TRIGGER_MODE_PULSEtruedatadatalabel_dataLabeldatalabel_statusstop scanlabel_statusrgb255255255rgb000BtnAWAS_CLICKED0201label_statusscanninglabel_statusrgb02550rgb000","screen":[{"simulationName":"Built-in","type":"builtin","width":128,"height":128,"scale":1.3,"screenName":"","blockId":"","screenColorType":0,"id":"builtin","createTime":1743037389483}],"logicWhenNum":0,"customList":[]} \ No newline at end of file diff --git a/examples/base/qrcode/atoms3_qrcode_pulse_mode_example.py b/examples/base/qrcode/atoms3_qrcode_pulse_mode_example.py new file mode 100644 index 00000000..02ec5187 --- /dev/null +++ b/examples/base/qrcode/atoms3_qrcode_pulse_mode_example.py @@ -0,0 +1,62 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import os, sys, io +import M5 +from M5 import * +from base import AtomicQRCodeBase +import time + + +title0 = None +label_data = None +label_status = None +base_qrcode = None +data = None + + +def btna_was_clicked_event(state): + global title0, label_data, label_status, base_qrcode, data + base_qrcode.set_trig(0) + time.sleep_ms(20) + base_qrcode.set_trig(1) + label_status.setText(str("scanning")) + label_status.setColor(0x00FF00, 0x000000) + + +def setup(): + global title0, label_data, label_status, base_qrcode, data + M5.begin() + title0 = Widgets.Title("QRCode", 3, 0xFFFFFF, 0x0000FF, Widgets.FONTS.DejaVu18) + label_data = Widgets.Label("data", 5, 60, 1.0, 0xFFFFFF, 0x000000, Widgets.FONTS.DejaVu18) + label_status = Widgets.Label( + "stop scan", 5, 25, 1.0, 0xFFFFFF, 0x000000, Widgets.FONTS.DejaVu18 + ) + BtnA.setCallback(type=BtnA.CB_TYPE.WAS_CLICKED, cb=btna_was_clicked_event) + base_qrcode = AtomicQRCodeBase(2, 5, 6, 7, 8) + base_qrcode.set_trigger_mode(base_qrcode.TRIGGER_MODE_PULSE) + + +def loop(): + global title0, label_data, label_status, base_qrcode, data + M5.update() + data = base_qrcode.read() + if data: + label_data.setText(str(data.decode())) + label_status.setText(str("stop scan")) + label_status.setColor(0xFFFFFF, 0x000000) + + +if __name__ == "__main__": + try: + setup() + while True: + loop() + except (Exception, KeyboardInterrupt) as e: + try: + from utility import print_error_msg + + print_error_msg(e) + except ImportError: + print("please update to latest firmware") diff --git a/examples/base/qrcode2/atoms3_qrcode2_auto_mode_example.m5f2 b/examples/base/qrcode2/atoms3_qrcode2_auto_mode_example.m5f2 new file mode 100644 index 00000000..41bcbb08 --- /dev/null +++ b/examples/base/qrcode2/atoms3_qrcode2_auto_mode_example.m5f2 @@ -0,0 +1 @@ +{"version":"V2.0","versionNumber":"V2.2.4","type":"atoms3","components":[{"name":"screen","type":"screen","layer":0,"screenId":"builtin","screenName":"","id":"__atoms3_screen","createTime":1743036051902,"x":0,"y":0,"width":128,"height":128,"backgroundColor":"#000000","size":0,"isSelected":true},{"name":"title0","type":"title","layer":1,"screenId":"builtin","screenName":"","id":"b3HouTo^jcpjeXh_","createTime":1743036136724,"x":0,"y":0,"color":"#ffffff","backgroundColor":"#0000FF","text":"QRCode","textOffset":3,"font":"Widgets.FONTS.DejaVu18","isSelected":false},{"name":"label_data","type":"label","layer":2,"screenId":"builtin","screenName":"","id":"vXA+r&QTPnZ&W1UO","createTime":1743036144638,"x":5,"y":60,"color":"#ffffff","backgroundColor":"#000000","text":"data","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false,"width":42,"height":21},{"name":"label_status","type":"label","layer":3,"screenId":"builtin","screenName":"","id":"oXXAn+E!GWYPtFF@","createTime":1743038251516,"x":5,"y":25,"color":"#00ff00","backgroundColor":"#000000","text":"scanning","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false,"width":58,"height":21}],"resources":[{"hardware":["hardware_button","hardware_pin_button","imu","ir"]},{"base":["base_qrcode2"]}],"units":[],"hats":[],"bases":[{"type":"base_qrcode2","name":"base_qrcode2","id":"j!EcAXLZqb1uzK_8","createTime":1743036061282,"initBlockType":"base_qrcode2_init","initBlockId":"5w+1!rbW[8_kiA7)?Bz$"}],"i2cs":[],"blockly":"datatrue2567TRIGGER_MODE_AUTOtruedatadatalabel_dataLabeldata","screen":[{"simulationName":"Built-in","type":"builtin","width":128,"height":128,"scale":1.3,"screenName":"","blockId":"","screenColorType":0,"id":"builtin","createTime":1743036051901}],"logicWhenNum":0,"customList":[]} \ No newline at end of file diff --git a/examples/base/qrcode2/atoms3_qrcode2_auto_mode_example.py b/examples/base/qrcode2/atoms3_qrcode2_auto_mode_example.py new file mode 100644 index 00000000..b46c2d41 --- /dev/null +++ b/examples/base/qrcode2/atoms3_qrcode2_auto_mode_example.py @@ -0,0 +1,49 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import os, sys, io +import M5 +from M5 import * +from base import AtomicQRCode2Base + + +title0 = None +label_data = None +label_status = None +base_qrcode2 = None +data = None + + +def setup(): + global title0, label_data, label_status, base_qrcode2, data + M5.begin() + title0 = Widgets.Title("QRCode", 3, 0xFFFFFF, 0x0000FF, Widgets.FONTS.DejaVu18) + label_data = Widgets.Label("data", 5, 60, 1.0, 0xFFFFFF, 0x000000, Widgets.FONTS.DejaVu18) + label_status = Widgets.Label( + "scanning", 5, 25, 1.0, 0x00FF00, 0x000000, Widgets.FONTS.DejaVu18 + ) + base_qrcode2 = AtomicQRCode2Base(2, 5, 6, 7) + base_qrcode2.set_trigger_mode(base_qrcode2.TRIGGER_MODE_AUTO) + + +def loop(): + global title0, label_data, label_status, base_qrcode2, data + M5.update() + data = base_qrcode2.read() + if data: + label_data.setText(str(data.decode())) + + +if __name__ == "__main__": + try: + setup() + while True: + loop() + except (Exception, KeyboardInterrupt) as e: + try: + from utility import print_error_msg + + print_error_msg(e) + except ImportError: + print("please update to latest firmware") diff --git a/examples/base/qrcode2/atoms3_qrcode2_continuous_mode_example.m5f2 b/examples/base/qrcode2/atoms3_qrcode2_continuous_mode_example.m5f2 new file mode 100644 index 00000000..2d84c026 --- /dev/null +++ b/examples/base/qrcode2/atoms3_qrcode2_continuous_mode_example.m5f2 @@ -0,0 +1 @@ +{"version":"V2.0","versionNumber":"V2.2.4","type":"atoms3","components":[{"name":"screen","type":"screen","layer":0,"screenId":"builtin","screenName":"","id":"__atoms3_screen","createTime":1743037389484,"x":0,"y":0,"width":128,"height":128,"backgroundColor":"#000000","size":0,"isSelected":true},{"name":"title0","type":"title","layer":1,"screenId":"builtin","screenName":"","id":"iX$fTx$n0Ld26KJ9","createTime":1743037477368,"x":0,"y":0,"color":"#ffffff","backgroundColor":"#0000FF","text":"QRCode","textOffset":3,"font":"Widgets.FONTS.DejaVu18","isSelected":false},{"name":"label_data","type":"label","layer":2,"screenId":"builtin","screenName":"","id":"xM$q$+lDMdNTnGXh","createTime":1743037487726,"x":5,"y":60,"color":"#ffffff","backgroundColor":"#000000","text":"data","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false,"width":42,"height":21},{"name":"label_status","type":"label","layer":3,"screenId":"builtin","screenName":"","id":"kBh1eanBWxL^KZqc","createTime":1743037919981,"x":5,"y":25,"color":"#ffffff","backgroundColor":"#000000","text":"stop scan","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false,"width":91,"height":21}],"resources":[{"hardware":["hardware_button","hardware_pin_button","imu","ir"]},{"base":["base_qrcode2"]}],"units":[],"hats":[],"bases":[{"type":"base_qrcode2","name":"base_qrcode2","id":"qC&OzPTRZEd*w15m","createTime":1743037394287,"initBlockType":"base_qrcode2_init","initBlockId":",U3m_iA5s)#TJUCt;^dd"}],"i2cs":[],"blockly":"is_scanningdatatrue2567TRIGGER_MODE_CONTINUOUSis_scanningFALSEtruedatadatalabel_dataLabeldataBtnAWAS_CLICKEDis_scanninglabel_statusstop scanlabel_statusrgb255255255rgb000label_statusscanninglabel_statusrgb02550rgb000is_scanningis_scanning","screen":[{"simulationName":"Built-in","type":"builtin","width":128,"height":128,"scale":1.3,"screenName":"","blockId":"","screenColorType":0,"id":"builtin","createTime":1743037389483}],"logicWhenNum":0,"customList":[]} \ No newline at end of file diff --git a/examples/base/qrcode2/atoms3_qrcode2_continuous_mode_example.py b/examples/base/qrcode2/atoms3_qrcode2_continuous_mode_example.py new file mode 100644 index 00000000..581af051 --- /dev/null +++ b/examples/base/qrcode2/atoms3_qrcode2_continuous_mode_example.py @@ -0,0 +1,68 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import os, sys, io +import M5 +from M5 import * +from base import AtomicQRCode2Base + + +title0 = None +label_data = None +label_status = None +base_qrcode2 = None +is_scanning = None +data = None + + +def btna_was_clicked_event(state): + global title0, label_data, label_status, base_qrcode2, is_scanning, data + if is_scanning: + base_qrcode2.stop_decode() + label_status.setText(str("stop scan")) + label_status.setColor(0xFFFFFF, 0x000000) + else: + base_qrcode2.start_decode() + label_status.setText(str("scanning")) + label_status.setColor(0x00FF00, 0x000000) + is_scanning = not is_scanning + + +def setup(): + global title0, label_data, label_status, base_qrcode2, is_scanning, data + M5.begin() + title0 = Widgets.Title("QRCode", 3, 0xFFFFFF, 0x0000FF, Widgets.FONTS.DejaVu18) + label_data = Widgets.Label("data", 5, 60, 1.0, 0xFFFFFF, 0x000000, Widgets.FONTS.DejaVu18) + label_status = Widgets.Label( + "stop scan", 5, 25, 1.0, 0xFFFFFF, 0x000000, Widgets.FONTS.DejaVu18 + ) + BtnA.setCallback(type=BtnA.CB_TYPE.WAS_CLICKED, cb=btna_was_clicked_event) + base_qrcode2 = AtomicQRCode2Base(2, 5, 6, 7) + base_qrcode2.set_trigger_mode(base_qrcode2.TRIGGER_MODE_CONTINUOUS) + base_qrcode2.set_startup_tone(1) + base_qrcode2.set_decode_success_beep(2) + base_qrcode2.stop_decode() + is_scanning = False + + +def loop(): + global title0, label_data, label_status, base_qrcode2, is_scanning, data + M5.update() + data = base_qrcode2.read() + if data: + label_data.setText(str(data.decode())) + + +if __name__ == "__main__": + try: + setup() + while True: + loop() + except (Exception, KeyboardInterrupt) as e: + try: + from utility import print_error_msg + + print_error_msg(e) + except ImportError: + print("please update to latest firmware") diff --git a/examples/base/qrcode2/atoms3_qrcode2_key_mode_example.m5f2 b/examples/base/qrcode2/atoms3_qrcode2_key_mode_example.m5f2 new file mode 100644 index 00000000..c1b64b91 --- /dev/null +++ b/examples/base/qrcode2/atoms3_qrcode2_key_mode_example.m5f2 @@ -0,0 +1 @@ +{"version":"V2.0","versionNumber":"V2.2.4","type":"atoms3","components":[{"name":"screen","type":"screen","layer":0,"screenId":"builtin","screenName":"","id":"__atoms3_screen","createTime":1743036051902,"x":0,"y":0,"width":128,"height":128,"backgroundColor":"#000000","size":0,"isSelected":true},{"name":"title0","type":"title","layer":1,"screenId":"builtin","screenName":"","id":"b3HouTo^jcpjeXh_","createTime":1743036136724,"x":0,"y":0,"color":"#ffffff","backgroundColor":"#0000FF","text":"QRCode","textOffset":3,"font":"Widgets.FONTS.DejaVu18","isSelected":false},{"name":"label_data","type":"label","layer":2,"screenId":"builtin","screenName":"","id":"vXA+r&QTPnZ&W1UO","createTime":1743036144638,"x":5,"y":60,"color":"#ffffff","backgroundColor":"#000000","text":"data","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false,"width":42,"height":21},{"name":"label_status","type":"label","layer":3,"screenId":"builtin","screenName":"","id":"oXXAn+E!GWYPtFF@","createTime":1743038251516,"x":5,"y":25,"color":"#ffffff","backgroundColor":"#000000","text":"stop scan","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false,"width":58,"height":21}],"resources":[{"hardware":["hardware_button","hardware_pin_button","imu","ir"]},{"base":["base_qrcode2"]}],"units":[],"hats":[],"bases":[{"type":"base_qrcode2","name":"base_qrcode2","id":"j!EcAXLZqb1uzK_8","createTime":1743036061282,"initBlockType":"base_qrcode2_init","initBlockId":"5w+1!rbW[8_kiA7)?Bz$"}],"i2cs":[],"blockly":"is_scanningdatastatustrue2567TRIGGER_MODE_KEYis_scanningFALSEstatusis_scanningtrueBtnA0is_scanningTRUE1is_scanningFALSENEQstatusis_scanningstatusis_scanningstatuslabel_statusrgb02550rgb000label_statusscanninglabel_statusrgb255255255rgb000label_statusstop scandatadatalabel_dataLabeldata","screen":[{"simulationName":"Built-in","type":"builtin","width":128,"height":128,"scale":1.3,"screenName":"","blockId":"","screenColorType":0,"id":"builtin","createTime":1743036051901}],"logicWhenNum":0,"customList":[]} \ No newline at end of file diff --git a/examples/base/qrcode2/atoms3_qrcode2_key_mode_example.py b/examples/base/qrcode2/atoms3_qrcode2_key_mode_example.py new file mode 100644 index 00000000..099f7db8 --- /dev/null +++ b/examples/base/qrcode2/atoms3_qrcode2_key_mode_example.py @@ -0,0 +1,67 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import os, sys, io +import M5 +from M5 import * +from base import AtomicQRCode2Base + + +title0 = None +label_data = None +label_status = None +base_qrcode2 = None +is_scanning = None +status = None +data = None + + +def setup(): + global title0, label_data, label_status, base_qrcode2, is_scanning, status, data + M5.begin() + title0 = Widgets.Title("QRCode", 3, 0xFFFFFF, 0x0000FF, Widgets.FONTS.DejaVu18) + label_data = Widgets.Label("data", 5, 60, 1.0, 0xFFFFFF, 0x000000, Widgets.FONTS.DejaVu18) + label_status = Widgets.Label( + "stop scan", 5, 25, 1.0, 0xFFFFFF, 0x000000, Widgets.FONTS.DejaVu18 + ) + base_qrcode2 = AtomicQRCode2Base(2, 5, 6, 7) + base_qrcode2.set_trigger_mode(base_qrcode2.TRIGGER_MODE_KEY) + is_scanning = False + status = is_scanning + + +def loop(): + global title0, label_data, label_status, base_qrcode2, is_scanning, status, data + M5.update() + if BtnA.isPressed(): + base_qrcode2.set_trig(0) + is_scanning = True + else: + base_qrcode2.set_trig(1) + is_scanning = False + if status != is_scanning: + status = is_scanning + if status: + label_status.setColor(0x00FF00, 0x000000) + label_status.setText(str("scanning")) + else: + label_status.setColor(0xFFFFFF, 0x000000) + label_status.setText(str("stop scan")) + data = base_qrcode2.read() + if data: + label_data.setText(str(data.decode())) + + +if __name__ == "__main__": + try: + setup() + while True: + loop() + except (Exception, KeyboardInterrupt) as e: + try: + from utility import print_error_msg + + print_error_msg(e) + except ImportError: + print("please update to latest firmware") diff --git a/examples/base/qrcode2/atoms3_qrcode2_motion_sensing_mode_example.m5f2 b/examples/base/qrcode2/atoms3_qrcode2_motion_sensing_mode_example.m5f2 new file mode 100644 index 00000000..9b312450 --- /dev/null +++ b/examples/base/qrcode2/atoms3_qrcode2_motion_sensing_mode_example.m5f2 @@ -0,0 +1 @@ +{"version":"V2.0","versionNumber":"V2.2.4","type":"atoms3","components":[{"name":"screen","type":"screen","layer":0,"screenId":"builtin","screenName":"","id":"__atoms3_screen","createTime":1743036051902,"x":0,"y":0,"width":128,"height":128,"backgroundColor":"#000000","size":0,"isSelected":true},{"name":"title0","type":"title","layer":1,"screenId":"builtin","screenName":"","id":"b3HouTo^jcpjeXh_","createTime":1743036136724,"x":0,"y":0,"color":"#ffffff","backgroundColor":"#0000FF","text":"QRCode","textOffset":3,"font":"Widgets.FONTS.DejaVu18","isSelected":false},{"name":"label_data","type":"label","layer":2,"screenId":"builtin","screenName":"","id":"vXA+r&QTPnZ&W1UO","createTime":1743036144638,"x":5,"y":60,"color":"#ffffff","backgroundColor":"#000000","text":"data","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false},{"name":"label_status","type":"label","layer":3,"screenId":"builtin","screenName":"","id":"oXXAn+E!GWYPtFF@","createTime":1743038251516,"x":5,"y":25,"color":"#00ff00","backgroundColor":"#000000","text":"detecting","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false}],"resources":[{"hardware":["hardware_button","hardware_pin_button","imu","ir"]},{"base":["base_qrcode2"]}],"units":[],"hats":[],"bases":[{"type":"base_qrcode2","name":"base_qrcode2","id":"wJ-2XiGHJVP%4HL2","createTime":1743038854939,"initBlockType":"base_qrcode2_init","initBlockId":"5w+1!rbW[8_kiA7)?Bz$"}],"i2cs":[],"blockly":"datatrue2567TRIGGER_MODE_MOTION_SENSING1truedatadatalabel_dataLabeldata","screen":[{"simulationName":"Built-in","type":"builtin","width":128,"height":128,"scale":1.3,"screenName":"","blockId":"","screenColorType":0,"id":"builtin","createTime":1743036051901}],"logicWhenNum":0,"customList":[]} \ No newline at end of file diff --git a/examples/base/qrcode2/atoms3_qrcode2_motion_sensing_mode_example.py b/examples/base/qrcode2/atoms3_qrcode2_motion_sensing_mode_example.py new file mode 100644 index 00000000..edd8b25a --- /dev/null +++ b/examples/base/qrcode2/atoms3_qrcode2_motion_sensing_mode_example.py @@ -0,0 +1,50 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import os, sys, io +import M5 +from M5 import * +from base import AtomicQRCode2Base + + +title0 = None +label_data = None +label_status = None +base_qrcode2 = None +data = None + + +def setup(): + global title0, label_data, label_status, base_qrcode2, data + M5.begin() + title0 = Widgets.Title("QRCode", 3, 0xFFFFFF, 0x0000FF, Widgets.FONTS.DejaVu18) + label_data = Widgets.Label("data", 5, 60, 1.0, 0xFFFFFF, 0x000000, Widgets.FONTS.DejaVu18) + label_status = Widgets.Label( + "detecting", 5, 25, 1.0, 0x00FF00, 0x000000, Widgets.FONTS.DejaVu18 + ) + base_qrcode2 = AtomicQRCode2Base(2, 5, 6, 7) + base_qrcode2.set_trigger_mode(base_qrcode2.TRIGGER_MODE_MOTION_SENSING) + base_qrcode2.set_motion_sensitivity(1) + + +def loop(): + global title0, label_data, label_status, base_qrcode2, data + M5.update() + data = base_qrcode2.read() + if data: + label_data.setText(str(data.decode())) + + +if __name__ == "__main__": + try: + setup() + while True: + loop() + except (Exception, KeyboardInterrupt) as e: + try: + from utility import print_error_msg + + print_error_msg(e) + except ImportError: + print("please update to latest firmware") diff --git a/examples/base/qrcode2/atoms3_qrcode2_pulse_mode_example.m5f2 b/examples/base/qrcode2/atoms3_qrcode2_pulse_mode_example.m5f2 new file mode 100644 index 00000000..2a1bcbcd --- /dev/null +++ b/examples/base/qrcode2/atoms3_qrcode2_pulse_mode_example.m5f2 @@ -0,0 +1 @@ +{"version":"V2.0","versionNumber":"V2.2.4","type":"atoms3","components":[{"name":"screen","type":"screen","layer":0,"screenId":"builtin","screenName":"","id":"__atoms3_screen","createTime":1743037389484,"x":0,"y":0,"width":128,"height":128,"backgroundColor":"#000000","size":0,"isSelected":true},{"name":"title0","type":"title","layer":1,"screenId":"builtin","screenName":"","id":"iX$fTx$n0Ld26KJ9","createTime":1743037477368,"x":0,"y":0,"color":"#ffffff","backgroundColor":"#0000FF","text":"QRCode","textOffset":3,"font":"Widgets.FONTS.DejaVu18","isSelected":false},{"name":"label_data","type":"label","layer":2,"screenId":"builtin","screenName":"","id":"xM$q$+lDMdNTnGXh","createTime":1743037487726,"x":5,"y":60,"color":"#ffffff","backgroundColor":"#000000","text":"data","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false,"width":42,"height":21},{"name":"label_status","type":"label","layer":3,"screenId":"builtin","screenName":"","id":"kBh1eanBWxL^KZqc","createTime":1743037919981,"x":5,"y":25,"color":"#ffffff","backgroundColor":"#000000","text":"stop scan","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false,"width":91,"height":21}],"resources":[{"hardware":["hardware_button","hardware_pin_button","imu","ir"]},{"base":["base_qrcode2"]}],"units":[],"hats":[],"bases":[{"type":"base_qrcode2","name":"base_qrcode2","id":"qC&OzPTRZEd*w15m","createTime":1743037394287,"initBlockType":"base_qrcode2_init","initBlockId":",U3m_iA5s)#TJUCt;^dd"}],"i2cs":[],"blockly":"datatrue2567TRIGGER_MODE_PULSEtruedatadatalabel_dataLabeldatalabel_statusstop scanlabel_statusrgb255255255rgb000BtnAWAS_CLICKED0201label_statusscanninglabel_statusrgb02550rgb000","screen":[{"simulationName":"Built-in","type":"builtin","width":128,"height":128,"scale":1.3,"screenName":"","blockId":"","screenColorType":0,"id":"builtin","createTime":1743037389483}],"logicWhenNum":0,"customList":[]} \ No newline at end of file diff --git a/examples/base/qrcode2/atoms3_qrcode2_pulse_mode_example.py b/examples/base/qrcode2/atoms3_qrcode2_pulse_mode_example.py new file mode 100644 index 00000000..ad88a51f --- /dev/null +++ b/examples/base/qrcode2/atoms3_qrcode2_pulse_mode_example.py @@ -0,0 +1,62 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import os, sys, io +import M5 +from M5 import * +from base import AtomicQRCode2Base +import time + + +title0 = None +label_data = None +label_status = None +base_qrcode2 = None +data = None + + +def btna_was_clicked_event(state): + global title0, label_data, label_status, base_qrcode2, data + base_qrcode2.set_trig(0) + time.sleep_ms(20) + base_qrcode2.set_trig(1) + label_status.setText(str("scanning")) + label_status.setColor(0x00FF00, 0x000000) + + +def setup(): + global title0, label_data, label_status, base_qrcode2, data + M5.begin() + title0 = Widgets.Title("QRCode", 3, 0xFFFFFF, 0x0000FF, Widgets.FONTS.DejaVu18) + label_data = Widgets.Label("data", 5, 60, 1.0, 0xFFFFFF, 0x000000, Widgets.FONTS.DejaVu18) + label_status = Widgets.Label( + "stop scan", 5, 25, 1.0, 0xFFFFFF, 0x000000, Widgets.FONTS.DejaVu18 + ) + BtnA.setCallback(type=BtnA.CB_TYPE.WAS_CLICKED, cb=btna_was_clicked_event) + base_qrcode2 = AtomicQRCode2Base(2, 5, 6, 7) + base_qrcode2.set_trigger_mode(base_qrcode2.TRIGGER_MODE_PULSE) + + +def loop(): + global title0, label_data, label_status, base_qrcode2, data + M5.update() + data = base_qrcode2.read() + if data: + label_data.setText(str(data.decode())) + label_status.setText(str("stop scan")) + label_status.setColor(0xFFFFFF, 0x000000) + + +if __name__ == "__main__": + try: + setup() + while True: + loop() + except (Exception, KeyboardInterrupt) as e: + try: + from utility import print_error_msg + + print_error_msg(e) + except ImportError: + print("please update to latest firmware") diff --git a/examples/module/qrcode/cores3_qrcode_auto_mode_example.m5f2 b/examples/module/qrcode/cores3_qrcode_auto_mode_example.m5f2 new file mode 100644 index 00000000..9d23be41 --- /dev/null +++ b/examples/module/qrcode/cores3_qrcode_auto_mode_example.m5f2 @@ -0,0 +1 @@ +{"version":"V2.0","versionNumber":"V2.2.4","type":"cores3","components":[{"name":"screen","type":"screen","layer":0,"screenId":"builtin","screenName":"","id":"__cores3_screen","createTime":1743060029522,"x":0,"y":0,"width":320,"height":240,"backgroundColor":"#222222","size":0,"isSelected":true},{"name":"title0","type":"title","layer":1,"screenId":"builtin","screenName":"","id":"o9rr9ZV^@YSU0lAK","createTime":1743060439332,"x":0,"y":0,"color":"#ffffff","backgroundColor":"#0000FF","text":"QRCode","textOffset":3,"font":"Widgets.FONTS.DejaVu24","isSelected":false},{"name":"label_status","type":"label","layer":2,"screenId":"builtin","screenName":"","id":"koDo+ur_G9%#+g&!","createTime":1743060449596,"x":5,"y":50,"color":"#ffffff","backgroundColor":"#222222","text":"scanning","engine":"gfx","font":"Widgets.FONTS.DejaVu24","rotation":0,"isSelected":false},{"name":"label_data","type":"label","layer":3,"screenId":"builtin","screenName":"","id":"q-APsp*vlV`vm6*Q","createTime":1743060497297,"x":5,"y":100,"color":"#ffffff","backgroundColor":"#222222","text":"data","engine":"gfx","font":"Widgets.FONTS.DejaVu24","rotation":0,"isSelected":false}],"resources":[{"hardware":["hardware_button","hardware_pin_button","imu","speaker","touch","als","mic"]},{"module":["module_qrcode"]}],"units":[],"hats":[],"bases":[],"i2cs":[],"blockly":"datatruemodule_qrcode_021718module_qrcode_0TRIGGER_MODE_AUTOtruedatamodule_qrcode_0datalabel_dataLabeldata","screen":[{"simulationName":"Built-in","type":"builtin","width":320,"height":240,"scale":0.78,"screenName":"","blockId":"","screenColorType":0,"id":"builtin","createTime":1743060029522}],"logicWhenNum":0,"customList":[]} \ No newline at end of file diff --git a/examples/module/qrcode/cores3_qrcode_auto_mode_example.py b/examples/module/qrcode/cores3_qrcode_auto_mode_example.py new file mode 100644 index 00000000..f27251f4 --- /dev/null +++ b/examples/module/qrcode/cores3_qrcode_auto_mode_example.py @@ -0,0 +1,50 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import os, sys, io +import M5 +from M5 import * +from module import QRCodeModule + + +title0 = None +label_status = None +label_data = None +module_qrcode_0 = None +data = None + + +def setup(): + global title0, label_status, label_data, module_qrcode_0, data + M5.begin() + Widgets.fillScreen(0x222222) + title0 = Widgets.Title("QRCode", 3, 0xFFFFFF, 0x0000FF, Widgets.FONTS.DejaVu24) + label_status = Widgets.Label( + "scanning", 5, 50, 1.0, 0xFFFFFF, 0x222222, Widgets.FONTS.DejaVu24 + ) + label_data = Widgets.Label("data", 5, 100, 1.0, 0xFFFFFF, 0x222222, Widgets.FONTS.DejaVu24) + module_qrcode_0 = QRCodeModule(2, tx=17, rx=18) + module_qrcode_0.set_trigger_mode(QRCodeModule.TRIGGER_MODE_AUTO) + + +def loop(): + global title0, label_status, label_data, module_qrcode_0, data + M5.update() + data = module_qrcode_0.read() + if data: + label_data.setText(str(data.decode())) + + +if __name__ == "__main__": + try: + setup() + while True: + loop() + except (Exception, KeyboardInterrupt) as e: + try: + from utility import print_error_msg + + print_error_msg(e) + except ImportError: + print("please update to latest firmware") diff --git a/examples/module/qrcode/cores3_qrcode_continuous_mode_example.m5f2 b/examples/module/qrcode/cores3_qrcode_continuous_mode_example.m5f2 new file mode 100644 index 00000000..7134a16a --- /dev/null +++ b/examples/module/qrcode/cores3_qrcode_continuous_mode_example.m5f2 @@ -0,0 +1 @@ +{"version":"V2.0","versionNumber":"V2.2.4","type":"cores3","components":[{"name":"screen","type":"screen","layer":0,"screenId":"builtin","screenName":"","id":"__cores3_screen","createTime":1743060029522,"x":0,"y":0,"width":320,"height":240,"backgroundColor":"#222222","size":0,"isSelected":true},{"name":"title0","type":"title","layer":1,"screenId":"builtin","screenName":"","id":"o9rr9ZV^@YSU0lAK","createTime":1743060439332,"x":0,"y":0,"color":"#ffffff","backgroundColor":"#0000FF","text":"QRCode","textOffset":3,"font":"Widgets.FONTS.DejaVu24","isSelected":false},{"name":"label_status","type":"label","layer":2,"screenId":"builtin","screenName":"","id":"koDo+ur_G9%#+g&!","createTime":1743060449596,"x":5,"y":50,"color":"#ffffff","backgroundColor":"#222222","text":"stop scan","engine":"gfx","font":"Widgets.FONTS.DejaVu24","rotation":0,"isSelected":false},{"name":"label_data","type":"label","layer":3,"screenId":"builtin","screenName":"","id":"q-APsp*vlV`vm6*Q","createTime":1743060497297,"x":4,"y":100,"color":"#ffffff","backgroundColor":"#222222","text":"data","engine":"gfx","font":"Widgets.FONTS.DejaVu24","rotation":0,"isSelected":false,"width":56,"height":28}],"resources":[{"hardware":["hardware_button","hardware_pin_button","imu","speaker","touch","als","mic"]},{"module":["module_qrcode"]}],"units":[],"hats":[],"bases":[],"i2cs":[],"blockly":"is_scanningdatatruemodule_qrcode_011718module_qrcode_0TRIGGER_MODE_CONTINUOUSmodule_qrcode_0is_scanningFALSEtruedatamodule_qrcode_0datalabel_dataLabeldatahello M5dataBtnPWRWAS_CLICKEDis_scanningmodule_qrcode_0label_statusrgb255255255hex0x222222label_statusstop scanmodule_qrcode_0label_statusrgb02550hex0x222222label_statusscanningis_scanningis_scanning","screen":[{"simulationName":"Built-in","type":"builtin","width":320,"height":240,"scale":0.78,"screenName":"","blockId":"","screenColorType":0,"id":"builtin","createTime":1743060029522}],"logicWhenNum":0,"customList":[]} \ No newline at end of file diff --git a/examples/module/qrcode/cores3_qrcode_continuous_mode_example.py b/examples/module/qrcode/cores3_qrcode_continuous_mode_example.py new file mode 100644 index 00000000..c94cff0b --- /dev/null +++ b/examples/module/qrcode/cores3_qrcode_continuous_mode_example.py @@ -0,0 +1,68 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import os, sys, io +import M5 +from M5 import * +from module import QRCodeModule + + +title0 = None +label_status = None +label_data = None +module_qrcode_0 = None +is_scanning = None +data = None + + +def btn_pwr_was_clicked_event(state): + global title0, label_status, label_data, module_qrcode_0, is_scanning, data + if is_scanning: + module_qrcode_0.stop_decode() + label_status.setColor(0xFFFFFF, 0x222222) + label_status.setText(str("stop scan")) + else: + module_qrcode_0.start_decode() + label_status.setColor(0x00FF00, 0x222222) + label_status.setText(str("scanning")) + is_scanning = not is_scanning + + +def setup(): + global title0, label_status, label_data, module_qrcode_0, is_scanning, data + M5.begin() + Widgets.fillScreen(0x222222) + title0 = Widgets.Title("QRCode", 3, 0xFFFFFF, 0x0000FF, Widgets.FONTS.DejaVu24) + label_status = Widgets.Label( + "stop scan", 5, 50, 1.0, 0xFFFFFF, 0x222222, Widgets.FONTS.DejaVu24 + ) + label_data = Widgets.Label("data", 4, 100, 1.0, 0xFFFFFF, 0x222222, Widgets.FONTS.DejaVu24) + BtnPWR.setCallback(type=BtnPWR.CB_TYPE.WAS_CLICKED, cb=btn_pwr_was_clicked_event) + module_qrcode_0 = QRCodeModule(1, tx=17, rx=18) + module_qrcode_0.set_trigger_mode(QRCodeModule.TRIGGER_MODE_CONTINUOUS) + module_qrcode_0.stop_decode() + is_scanning = False + + +def loop(): + global title0, label_status, label_data, module_qrcode_0, is_scanning, data + M5.update() + data = module_qrcode_0.read() + if data: + label_data.setText(str(data.decode())) + print(data.decode()) + + +if __name__ == "__main__": + try: + setup() + while True: + loop() + except (Exception, KeyboardInterrupt) as e: + try: + from utility import print_error_msg + + print_error_msg(e) + except ImportError: + print("please update to latest firmware") diff --git a/examples/module/qrcode/cores3_qrcode_motion_sensing_mode_example.m5f2 b/examples/module/qrcode/cores3_qrcode_motion_sensing_mode_example.m5f2 new file mode 100644 index 00000000..df7a437b --- /dev/null +++ b/examples/module/qrcode/cores3_qrcode_motion_sensing_mode_example.m5f2 @@ -0,0 +1 @@ +{"version":"V2.0","versionNumber":"V2.2.4","type":"cores3","components":[{"name":"screen","type":"screen","layer":0,"screenId":"builtin","screenName":"","id":"__cores3_screen","createTime":1743060029522,"x":0,"y":0,"width":320,"height":240,"backgroundColor":"#222222","size":0,"isSelected":true},{"name":"title0","type":"title","layer":1,"screenId":"builtin","screenName":"","id":"o9rr9ZV^@YSU0lAK","createTime":1743060439332,"x":0,"y":0,"color":"#ffffff","backgroundColor":"#0000FF","text":"QRCode","textOffset":3,"font":"Widgets.FONTS.DejaVu24","isSelected":false},{"name":"label_status","type":"label","layer":2,"screenId":"builtin","screenName":"","id":"koDo+ur_G9%#+g&!","createTime":1743060449596,"x":5,"y":50,"color":"#00ff00","backgroundColor":"#222222","text":"detecting","engine":"gfx","font":"Widgets.FONTS.DejaVu24","rotation":0,"isSelected":false},{"name":"label_data","type":"label","layer":3,"screenId":"builtin","screenName":"","id":"q-APsp*vlV`vm6*Q","createTime":1743060497297,"x":5,"y":100,"color":"#ffffff","backgroundColor":"#222222","text":"data","engine":"gfx","font":"Widgets.FONTS.DejaVu24","rotation":0,"isSelected":false}],"resources":[{"hardware":["hardware_button","hardware_pin_button","imu","speaker","touch","als","mic"]},{"module":["module_qrcode"]}],"units":[],"hats":[],"bases":[],"i2cs":[],"blockly":"datatruemodule_qrcode_021718module_qrcode_0TRIGGER_MODE_MOTION_SENSINGmodule_qrcode_01truedatamodule_qrcode_0datalabel_dataLabeldata","screen":[{"simulationName":"Built-in","type":"builtin","width":320,"height":240,"scale":0.78,"screenName":"","blockId":"","screenColorType":0,"id":"builtin","createTime":1743060029522}],"logicWhenNum":0,"customList":[]} \ No newline at end of file diff --git a/examples/module/qrcode/cores3_qrcode_motion_sensing_mode_example.py b/examples/module/qrcode/cores3_qrcode_motion_sensing_mode_example.py new file mode 100644 index 00000000..a8b3824c --- /dev/null +++ b/examples/module/qrcode/cores3_qrcode_motion_sensing_mode_example.py @@ -0,0 +1,51 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import os, sys, io +import M5 +from M5 import * +from module import QRCodeModule + + +title0 = None +label_status = None +label_data = None +module_qrcode_0 = None +data = None + + +def setup(): + global title0, label_status, label_data, module_qrcode_0, data + M5.begin() + Widgets.fillScreen(0x222222) + title0 = Widgets.Title("QRCode", 3, 0xFFFFFF, 0x0000FF, Widgets.FONTS.DejaVu24) + label_status = Widgets.Label( + "detecting", 5, 50, 1.0, 0x00FF00, 0x222222, Widgets.FONTS.DejaVu24 + ) + label_data = Widgets.Label("data", 5, 100, 1.0, 0xFFFFFF, 0x222222, Widgets.FONTS.DejaVu24) + module_qrcode_0 = QRCodeModule(2, tx=17, rx=18) + module_qrcode_0.set_trigger_mode(QRCodeModule.TRIGGER_MODE_MOTION_SENSING) + module_qrcode_0.set_motion_sensitivity(1) + + +def loop(): + global title0, label_status, label_data, module_qrcode_0, data + M5.update() + data = module_qrcode_0.read() + if data: + label_data.setText(str(data.decode())) + + +if __name__ == "__main__": + try: + setup() + while True: + loop() + except (Exception, KeyboardInterrupt) as e: + try: + from utility import print_error_msg + + print_error_msg(e) + except ImportError: + print("please update to latest firmware") diff --git a/examples/module/qrcode/cores3_qrcode_pulse_mode_example.m5f2 b/examples/module/qrcode/cores3_qrcode_pulse_mode_example.m5f2 new file mode 100644 index 00000000..1b85b1aa --- /dev/null +++ b/examples/module/qrcode/cores3_qrcode_pulse_mode_example.m5f2 @@ -0,0 +1 @@ +{"version":"V2.0","versionNumber":"V2.2.4","type":"cores3","components":[{"name":"screen","type":"screen","layer":0,"screenId":"builtin","screenName":"","id":"__cores3_screen","createTime":1743060029522,"x":0,"y":0,"width":320,"height":240,"backgroundColor":"#222222","size":0,"isSelected":true},{"name":"title0","type":"title","layer":1,"screenId":"builtin","screenName":"","id":"o9rr9ZV^@YSU0lAK","createTime":1743060439332,"x":0,"y":0,"color":"#ffffff","backgroundColor":"#0000FF","text":"QRCode","textOffset":3,"font":"Widgets.FONTS.DejaVu24","isSelected":false},{"name":"label_status","type":"label","layer":2,"screenId":"builtin","screenName":"","id":"koDo+ur_G9%#+g&!","createTime":1743060449596,"x":5,"y":50,"color":"#ffffff","backgroundColor":"#222222","text":"scanning","engine":"gfx","font":"Widgets.FONTS.DejaVu24","rotation":0,"isSelected":false,"width":112,"height":28},{"name":"label_data","type":"label","layer":3,"screenId":"builtin","screenName":"","id":"q-APsp*vlV`vm6*Q","createTime":1743060497297,"x":5,"y":100,"color":"#ffffff","backgroundColor":"#222222","text":"data","engine":"gfx","font":"Widgets.FONTS.DejaVu24","rotation":0,"isSelected":false,"width":56,"height":28}],"resources":[{"hardware":["hardware_button","hardware_pin_button","imu","speaker","touch","als","mic"]},{"module":["module_qrcode"]}],"units":[],"hats":[],"bases":[],"i2cs":[],"blockly":"datatruemodule_qrcode_021718module_qrcode_0TRIGGER_MODE_PULSEtruedatamodule_qrcode_0datalabel_dataLabeldatalabel_statusrgb255255255hex0x222222label_statusstop scanBtnPWRWAS_CLICKEDmodule_qrcode_0020module_qrcode_01label_statusrgb02550hex0x222222label_statusscanning","screen":[{"simulationName":"Built-in","type":"builtin","width":320,"height":240,"scale":0.78,"screenName":"","blockId":"","screenColorType":0,"id":"builtin","createTime":1743060029522}],"logicWhenNum":0,"customList":[]} \ No newline at end of file diff --git a/examples/module/qrcode/cores3_qrcode_pulse_mode_example.py b/examples/module/qrcode/cores3_qrcode_pulse_mode_example.py new file mode 100644 index 00000000..f6622db5 --- /dev/null +++ b/examples/module/qrcode/cores3_qrcode_pulse_mode_example.py @@ -0,0 +1,63 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import os, sys, io +import M5 +from M5 import * +from module import QRCodeModule +import time + + +title0 = None +label_status = None +label_data = None +module_qrcode_0 = None +data = None + + +def btn_pwr_was_clicked_event(state): + global title0, label_status, label_data, module_qrcode_0, data + module_qrcode_0.set_trig(0) + time.sleep_ms(20) + module_qrcode_0.set_trig(1) + label_status.setColor(0x00FF00, 0x222222) + label_status.setText(str("scanning")) + + +def setup(): + global title0, label_status, label_data, module_qrcode_0, data + M5.begin() + Widgets.fillScreen(0x222222) + title0 = Widgets.Title("QRCode", 3, 0xFFFFFF, 0x0000FF, Widgets.FONTS.DejaVu24) + label_status = Widgets.Label( + "scanning", 5, 50, 1.0, 0xFFFFFF, 0x222222, Widgets.FONTS.DejaVu24 + ) + label_data = Widgets.Label("data", 5, 100, 1.0, 0xFFFFFF, 0x222222, Widgets.FONTS.DejaVu24) + BtnPWR.setCallback(type=BtnPWR.CB_TYPE.WAS_CLICKED, cb=btn_pwr_was_clicked_event) + module_qrcode_0 = QRCodeModule(2, tx=17, rx=18) + module_qrcode_0.set_trigger_mode(QRCodeModule.TRIGGER_MODE_PULSE) + + +def loop(): + global title0, label_status, label_data, module_qrcode_0, data + M5.update() + data = module_qrcode_0.read() + if data: + label_data.setText(str(data.decode())) + label_status.setColor(0xFFFFFF, 0x222222) + label_status.setText(str("stop scan")) + + +if __name__ == "__main__": + try: + setup() + while True: + loop() + except (Exception, KeyboardInterrupt) as e: + try: + from utility import print_error_msg + + print_error_msg(e) + except ImportError: + print("please update to latest firmware") diff --git a/m5stack/libs/base/__init__.py b/m5stack/libs/base/__init__.py index 08d13e93..f48d67ba 100644 --- a/m5stack/libs/base/__init__.py +++ b/m5stack/libs/base/__init__.py @@ -13,6 +13,8 @@ "Motion": "motion", "MotionBase": "motion", "AtomicPWMBase": "pwm", + "AtomicQRCodeBase": "qrcode", + "AtomicQRCode2Base": "qrcode2", "AtomRS232": "rs232", "AtomRS485": "rs232", "SpeakerBase": "speaker", diff --git a/m5stack/libs/base/manifest.py b/m5stack/libs/base/manifest.py index a5813a7e..375c7ab8 100644 --- a/m5stack/libs/base/manifest.py +++ b/m5stack/libs/base/manifest.py @@ -14,6 +14,8 @@ "hdriver.py", "motion.py", "pwm.py", + "qrcode.py", + "qrcode2.py", "rs232.py", "speaker.py", "stepmotor.py", diff --git a/m5stack/libs/base/qrcode.py b/m5stack/libs/base/qrcode.py new file mode 100644 index 00000000..66507980 --- /dev/null +++ b/m5stack/libs/base/qrcode.py @@ -0,0 +1,352 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import time +import machine +from micropython import const +from driver.qrcode import serial_cmd_helper + + +class AtomicQRCodeBase: + """Create an AtomicQRCodeBase object. + + :param int id: UART id. + :param int tx: the UART TX pin. + :param int rx: the UART RX pin. + :param int trig: the trigger pin. + :param int done: the receive done pin. + + UiFlow2 Code Block: + + |init.png| + + MicroPython Code Block: + + .. code-block:: python + + from base import AtomicQRCodeBase + + base_qrcode = AtomicQRCodeBase(id = 1, tx = 6, rx = 5, trig = 7, done = 8) + """ + + # 触发解码模式 + TRIGGER_MODE_KEY = const(0) # 按键模式:触发单次解码,解码成功后停止解码 + TRIGGER_MODE_HOST = const( + 8 + ) # 主机模式:指令控制开始/停止解码,触发解码后超时为识别或解码成功将停止解码。 + TRIGGER_MODE_AUTO = const(4) # 自动模式:上电后连续解码 + TRIGGER_MODE_PULSE = const( + 2 + ) # 脉冲模式:Trig 引脚低电平触发解码,在识读成功或达到单次读码时长限制时结束读取。 + TRIGGER_MODE_MOTION_SENSING = const( + 9 + ) # 移动感应模式:图像识别,当检测到场景发送变化时,开始解码。 + # 补光灯模式 + FILL_LIGHT_ON_DECODE = const(0) # 解码时亮 + FILL_LIGHT_ON = const(1) # 常亮 + FILL_LIGHT_OFF = const(2) # 常灭 + # 定位灯模式 + POS_LIGHT_ON_DECODE = const(0) # 解码时亮 + POS_LIGHT_ON = const(1) # 常亮 + POS_LIGHT_OFF = const(2) # 常灭 + cmd_ack = bytes([0x04, 0xD0, 0x00, 0x00, 0xFF, 0x2C]) + + def __init__( + self, id: int = 1, tx: int = 5, rx: int = 6, trig: int = 7, done: int = 8 + ) -> None: + self.serial = machine.UART(id, baudrate=9600, tx=rx, rx=tx, timeout=100) + self.serial_handler = serial_cmd_helper.SerialCmdHelper(self.serial, False) + self.trig = machine.Pin(trig, machine.Pin.OUT) + self.done = machine.Pin(done, machine.Pin.IN) + self.serial.write(b"\x00") # 唤醒设备 + time.sleep_ms(100) + self.serial.read() + + def set_trig(self, value: int) -> None: + """Set trigger value. + + :param int value: ``0`` - Low level, ``1`` - High level. + + UiFlow2 Code Block: + + |set_trig.png| + + MicroPython Code Block: + + .. code-block:: python + + base_qrcode.set_trig(value) + """ + self.trig.value(value) + + def start_decode(self) -> None: + """Start decode. + + UiFlow2 Code Block: + + |start_decode.png| + + MicroPython Code Block: + + .. code-block:: python + + base_qrcode.start_decode() + """ + cmd_start_decode = bytes([0x04, 0xE4, 0x04, 0x00, 0xFF, 0x14]) + self.serial_handler.cmd(cmd_start_decode, self.cmd_ack, timeout_ms=100) + + def stop_decode(self) -> None: + """Stop decode. + + UiFlow2 Code Block: + + |stop_decode.png| + + MicroPython Code Block: + + .. code-block:: python + + base_qrcode.stop_decode() + """ + cmd_stop_decode = bytes([0x04, 0xE5, 0x04, 0x00, 0xFF, 0x13]) + self.serial_handler.cmd(cmd_stop_decode, self.cmd_ack, timeout_ms=100) + + def read(self) -> None | bytes: + """Read decode data. + + :returns: qrcode data. + :rtype: None | bytes + + UiFlow2 Code Block: + + |read.png| + + MicroPython Code Block: + + .. code-block:: python + + base_qrcode.read() + """ + data = bytearray() + timeout_ms = 500 # 等待数据接收完成 (115200 波特率接收 200 字符传输耗时 17.3 ms) + gap_timeout = 3 + start_time = time.ticks_ms() + last_recv_time = 0 + if self.done.value(): # 解码成功 + # 接收完整的一帧数据 + while time.ticks_diff(time.ticks_ms(), start_time) < timeout_ms: + if self.serial.any() > 0: + chunk = self.serial.read() + if chunk: + data.extend(chunk) + last_recv_time = time.ticks_ms() + elif time.ticks_diff(time.ticks_ms(), last_recv_time) > gap_timeout: + break + time.sleep_ms(1) + if data: + return bytes(data) + return None + + # ============================================================================ + # 识读模式 + # ============================================================================ + def set_trigger_mode(self, mode: int) -> None: + """Set trigger mode. + + :param int mode: The trigger mode. Available options: + + - ``TRIGGER_MODE_KEY``: Key Mode, Triggers a single decode; decoding stops after a successful read. + - ``TRIGGER_MODE_HOST``: Host Mode, The command controls the start/stop of decoding. Once triggered, decoding will stop either upon successful decoding or after a timeout of 5 seconds. + - ``TRIGGER_MODE_AUTO``: Auto Mode, Performs continuous decoding upon power-up and cannot be stopped. + - ``TRIGGER_MODE_PULSE``: Pulse Mode, The Trig pin's low-level signal triggers decoding, which stops after a successful read or when the single read time limit is reached. + - ``TRIGGER_MODE_MOTION_SENSING``: Motion Sensing Mode, Uses image recognition; decoding starts when a scene change is detected. + + UiFlow2 Code Block: + + |set_trigger_mode.png| + + MicroPython Code Block: + + .. code-block:: python + + base_qrcode.set_trigger_mode(mode) + """ + check = { + TRIGGER_MODE_KEY: 0x9D, + TRIGGER_MODE_HOST: 0x95, + TRIGGER_MODE_AUTO: 0x99, + TRIGGER_MODE_PULSE: 0x9B, + TRIGGER_MODE_MOTION_SENSING: 0x94, + } + cmd_trigger_mode = bytes([0x07, 0xC6, 0x04, 0x08, 0x00, 0x8A, mode, 0xFE, check[mode]]) + self.serial_handler.cmd_retry(cmd_trigger_mode, self.cmd_ack, timeout_ms=300) + + def set_decode_continuous(self, delay_100ms: int) -> None: + """Set continuous decode time. + + :param int delay_100ms: Continuous scanning time(unit: 100ms). Range: 099, i.e., 025,500 ms (0 means unlimited). + + UiFlow2 Code Block: + + |set_decode_continuous.png| + + MicroPython Code Block: + + .. code-block:: python + + base_qrcode.set_decode_continuous(delay_100ms) + """ + if delay_100ms > 255: + delay_100ms = 255 + cmd = bytes([0x07, 0xC6, 0x04, 0x08, 0x00, 0x88, delay_100ms, 0xFE, 0x9F - delay_100ms]) + self.serial_handler.cmd(cmd, self.cmd_ack, timeout_ms=150) + + def set_decode_interval(self, interval_100ms: int) -> None: + """Set decode interval. + + :param int interval_100ms: Decode interval time(unit: 100ms). Range: 0~99, i.e., 0~9,900 ms. + + UiFlow2 Code Block: + + |set_decode_interval.png| + + MicroPython Code Block: + + .. code-block:: python + + base_qrcode.set_decode_interval(interval_100ms) + """ + if interval_100ms > 99: + interval_100ms = 99 + cmd = bytes( + [0x07, 0xC6, 0x04, 0x08, 0x00, 0x89, interval_100ms, 0xFE, 0x9E - interval_100ms] + ) + self.serial_handler.cmd(cmd, self.cmd_ack, timeout_ms=250) + + def set_same_code_interval(self, interval_100ms: int) -> None: + """Set same code interval. + + :param int interval_100ms: Decode interval for the same code(unit: 100ms). Range: 0~99, i.e., 0~9,900 ms. + + UiFlow2 Code Block: + + |set_same_code_interval.png| + + MicroPython Code Block: + + .. code-block:: python + + base_qrcode.set_same_code_interval(interval_100ms) + """ + if interval_100ms > 99: + interval_100ms = 99 + cmd = bytearray([0x08, 0xC6, 0x04, 0x08, 0x00, 0xF3, 0x03, interval_100ms, 0xFE, 0]) + check = ((sum(cmd[:-2]) % 256) ^ 0xFF) + 1 # 求和取低字节,计算补码 + cmd[-1] = check + self.serial_handler.cmd(cmd, self.cmd_ack, timeout_ms=250) + + # ============================================================================ + # System config + # ============================================================================ + def set_fill_light_mode(self, mode: int) -> None: + """Set fill light mode. + + :param int mode: The fill light mode. Available options: + - ``FILL_LIGHT_OFF``: Light off. + - ``FILL_LIGHT_ON``: Light on. + - ``FILL_LIGHT_ON_DECODE``: Light on during decoding. + + UiFlow2 Code Block: + + |set_fill_light_mode.png| + + MicroPython Code Block: + + .. code-block:: python + + base_qrcode.set_fill_light_mode(mode) + """ + cmd_fill_light = bytes([0x08, 0xC6, 0x04, 0x08, 0x00, 0xF2, 0x02, mode, 0xFE, 0x32 - mode]) + self.serial_handler.cmd(cmd_fill_light, self.cmd_ack, timeout_ms=150) + + def set_pos_light_mode(self, mode: int) -> None: + """Set positioning light mode. + + :param int mode: The positioning light mode. Available options: + - ``POS_LIGHT_OFF``: Light off. + - ``POS_LIGHT_ON``: Light on. + - ``POS_LIGHT_ON_DECODE``: Light flash during decoding. + + UiFlow2 Code Block: + + |set_pos_light_mode.png| + + MicroPython Code Block: + + .. code-block:: python + + base_qrcode.set_pos_light_mode(mode) + """ + cmd_pos_light = bytes([0x08, 0xC6, 0x04, 0x08, 0x00, 0xF2, 0x03, mode, 0xFE, 0x31 - mode]) + self.serial_handler.cmd(cmd_pos_light, self.cmd_ack, timeout_ms=150) + + def set_startup_tone(self, enable: bool) -> None: + """Set startup tone. + + :param bool enable: True - Enable startup tone, False - Disable startup tone. + + UiFlow2 Code Block: + + |set_startup_tone.png| + + MicroPython Code Block: + + .. code-block:: python + + base_qrcode.set_startup_tone(enable) + """ + s1 = 0x01 if enable else 0x00 + s2 = 0x26 if enable else 0x27 + cmd = bytes([0x08, 0xC6, 0x04, 0x08, 0x00, 0xF2, 0x0D, s1, 0xFE, s2]) + self.serial_handler.cmd(cmd, self.cmd_ack, timeout_ms=150) + + def set_decode_success_tone(self, enable: bool) -> None: + """Set decode success tone. + + :param bool enable: True - Enable decode success tone, False - Disable decode success tone. + + UiFlow2 Code Block: + + |set_decode_success_tone.png| + + MicroPython Code Block: + + .. code-block:: python + + base_qrcode.set_decode_success_tone(enable) + """ + s1 = 0x01 if enable else 0x00 + s2 = 0xEE if enable else 0xEF + cmd = bytes([0x07, 0xC6, 0x04, 0x08, 0x00, 0x38, s1, 0xFE, s2]) + self.serial_handler.cmd(cmd, self.cmd_ack, timeout_ms=150) + + def set_config_tone(self, enable: bool) -> None: + """Set config tone. + + :param bool enable: True - Enable set config tone, False - Disable set config tone. + + UiFlow2 Code Block: + + |set_config_tone.png| + + MicroPython Code Block: + + .. code-block:: python + + base_qrcode.set_config_tone(enable) + """ + s1 = 0x01 if enable else 0x00 + s2 = 0x25 if enable else 0x26 + cmd = bytes([0x08, 0xC6, 0x04, 0x08, 0x00, 0xF2, 0x0E, s1, 0xFE, s2]) + self.serial_handler.cmd(cmd, self.cmd_ack, timeout_ms=150) diff --git a/m5stack/libs/base/qrcode2.py b/m5stack/libs/base/qrcode2.py new file mode 100644 index 00000000..4aa770e4 --- /dev/null +++ b/m5stack/libs/base/qrcode2.py @@ -0,0 +1,31 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import time +from driver.qrcode.qrcode_m14 import QRCodeM14 + + +class AtomicQRCode2Base(QRCodeM14): + """Create an AtomicQRCode2Base object. + + :param int id: UART id. + :param int tx: the UART TX pin. + :param int rx: the UART RX pin. + :param int trig: the trigger pin. + + UiFlow2 Code Block: + + |init.png| + + MicroPython Code Block: + + .. code-block:: python + + from base import AtomicQRCode2Base + + base_qrcode2 = AtomicQRCode2Base(id = 1, tx = 6, rx = 5, trig = 7) + """ + + def __init__(self, id: int = 1, tx: int = 5, rx: int = 6, trig: int = 7): + super().__init__(id, tx, rx, trig) diff --git a/m5stack/libs/driver/manifest.py b/m5stack/libs/driver/manifest.py index b5d9da4e..7c3bc309 100644 --- a/m5stack/libs/driver/manifest.py +++ b/m5stack/libs/driver/manifest.py @@ -35,6 +35,9 @@ "neopixel/__init__.py", "neopixel/sk6812.py", "neopixel/ws2812.py", + "qrcode/__init__.py", + "qrcode/qrcode_m14.py", + "qrcode/serial_cmd_helper.py", "rotary/__init__.py", "rotary/rotary_irq_esp.py", "rotary/rotary.py", diff --git a/m5stack/libs/driver/qrcode/__init__.py b/m5stack/libs/driver/qrcode/__init__.py new file mode 100644 index 00000000..ae748eb3 --- /dev/null +++ b/m5stack/libs/driver/qrcode/__init__.py @@ -0,0 +1,3 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT diff --git a/m5stack/libs/driver/qrcode/qrcode_m14.py b/m5stack/libs/driver/qrcode/qrcode_m14.py new file mode 100644 index 00000000..b725afb7 --- /dev/null +++ b/m5stack/libs/driver/qrcode/qrcode_m14.py @@ -0,0 +1,262 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import time +import machine +from micropython import const +from driver.qrcode import serial_cmd_helper + + +class QRCodeM14: + """ + 此驱动适用于 M14 二维码识别模块 + """ + + # 触发解码模式 + TRIGGER_MODE_KEY = const(0) # 按键模式:触发单次解码,解码成功后停止解码 + TRIGGER_MODE_CONTINUOUS = const( + 1 + ) # 连续模式:触发连续解码,解码成功后继续解码,停止解码需要再次触发 + TRIGGER_MODE_AUTO = const(2) # 自动模式:上电后连续解码,不可停止 + TRIGGER_MODE_PULSE = const( + 4 + ) # 脉冲模式:Trig 引脚低电平触发解码,在识读成功或达到单次读码时长限制时结束读取。 + TRIGGER_MODE_MOTION_SENSING = const( + 5 + ) # 移动感应模式:图像识别,当检测到场景发送变化时,开始解码。 + # 补光灯模式 + FILL_LIGHT_OFF = const(0) # 常灭 + FILL_LIGHT_ON = const(3) # 常亮 + FILL_LIGHT_ON_DECODE = const(2) # 解码时亮 + # 定位灯模式 + POS_LIGHT_OFF = const(0) # 常灭 + POS_LIGHT_ON_DECODE = const(2) # 解码时亮 + POS_LIGHT_FLASH_ON_DECODE = const(1) # 解码时闪烁 + + def __init__(self, id: int = 1, tx: int = 5, rx: int = 6, trig: int = 7, led=None) -> None: + self.serial = machine.UART(id, baudrate=115200, tx=rx, rx=tx) # 交叉 + self.serial_handler = serial_cmd_helper.SerialCmdHelper(self.serial, False) + self.trig = machine.Pin(trig, machine.Pin.OUT) + self.led = led + + def set_trig(self, value: int) -> None: + """Set trigger pin. 设置触发引脚。 + :param int value: ``0`` - 低电平,``1`` - 高电平。 + """ + self.trig.value(value) + + def start_decode(self) -> None: + """Start decode. 开始解码。""" + cmd_start_decode = bytes([0x32, 0x75, 0x01]) # 开始解码 + self.serial_handler.cmd(cmd_start_decode) # 此指令无返回 + + def stop_decode(self) -> None: + """Stop decode. 停止解码。""" + cmd_stop_decode = bytes([0x32, 0x75, 0x02]) + cmd_stop_decode_ack = bytes([0x33, 0x75, 0x02, 0x00, 0x00]) + self.serial_handler.cmd(cmd_stop_decode, cmd_stop_decode_ack, timeout_ms=150) + + def read(self) -> None | bytes: + """Read decode data. 读取解码数据。""" + data = bytearray() + timeout_ms = 50 # 等待数据接收完成 (115200 波特率接收 200 字符传输耗时 17.3 ms) + gap_timeout = 3 + start_time = time.ticks_ms() + last_recv_time = 0 + if self.serial.any() > 0: + # 接收完整的一帧数据 + while time.ticks_diff(time.ticks_ms(), start_time) < timeout_ms: + if self.serial.any() > 0: + chunk = self.serial.read() + if chunk: + data.extend(chunk) + last_recv_time = time.ticks_ms() + elif time.ticks_diff(time.ticks_ms(), last_recv_time) > gap_timeout: + break + time.sleep_ms(1) + if data: + return bytes(data) + return None + + # ============================================================================ + # 识读模式 + # ============================================================================ + def set_trigger_mode(self, mode: int) -> None: + """Set trigger mode. 设置触发解码模式。 + + :param int mode: The decode trigger mode. + """ + cmd_trigger_mode = bytes([0x21, 0x61, 0x41, mode]) + cmd_trigger_mode_ack = bytes([0x22, 0x61, 0x41, mode, 0x00]) + self.serial_handler.cmd(cmd_trigger_mode, cmd_trigger_mode_ack, timeout_ms=200) + + def set_decode_delay(self, delay_ms: int) -> None: + """Set decode delay. 设置命令解码延迟 (按键模式)。 + + :param int delay_ms: 解码延迟时间(单位:毫秒), 0 表示持续解码直到成功。 + """ + high_byte = (delay_ms >> 8) & 0xFF + low_byte = delay_ms & 0xFF + cmd = bytes([0x21, 0x61, 0x8A, high_byte, low_byte]) + cmd_ack = bytes([0x22, 0x61, 0x8A, 0x00, 0x00]) + self.serial_handler.cmd(cmd, cmd_ack, timeout_ms=200) + + def set_trigger_timeout(self, timeout_ms: int) -> None: + """Set trigger timeout. 设置触发超时。(脉冲模式)。 + + :param int timeout_ms: 触发超时时间(单位:毫秒), 解码持续时间超过该值时自动停止。 + """ + high_byte = (timeout_ms >> 8) & 0xFF + low_byte = timeout_ms & 0xFF + cmd = bytes([0x21, 0x61, 0x82, high_byte, low_byte]) + cmd_ack = bytes([0x22, 0x61, 0x82, 0x00, 0x00]) + self.serial_handler.cmd(cmd, cmd_ack, timeout_ms=200) + + def set_motion_sensitivity(self, level: int = 3) -> None: + """Set motion detection sensitivity. 设置移动感应灵敏度。(感应模式中) + + :param int level: 灵敏度等级, 范围 1-5, 等级越高对场景变化越敏感, 默认等级为 3。 + """ + cmd = bytes([0x21, 0x61, 0x44, level]) + cmd_ack = bytes([0x22, 0x61, 0x44, 0x00]) + self.serial_handler.cmd(cmd, cmd_ack, timeout_ms=200) + + def set_continuous_decode_delay(self, delay_ms: int) -> None: + """Set continuous decode delay. 设置持续解码延迟时间。(感应模式中) + + :param int delay_ms: 延迟时间,单位为 100 毫秒。值为 0 表示持续解码直到超时。 + """ + high_byte = (delay_ms >> 8) & 0xFF + low_byte = delay_ms & 0xFF + cmd = bytes([0x21, 0x61, 0x8C, high_byte, low_byte]) + cmd_ack = bytes([0x22, 0x61, 0x8C, 0x00, 0x00]) + self.serial_handler.cmd(cmd, cmd_ack, timeout_ms=200) + + def set_trigger_decode_delay(self, delay_ms: int) -> None: + """设置触发解码延迟时间(感应模式中) + + 置触发解码延迟时间。当重新进入检测场景变化阶段,到检测到变化再次开始识读之间的延迟时长。 + + :param int delay_ms: 触发解码延迟时间,单位为毫秒。 + """ + high_byte = (delay_ms >> 8) & 0xFF + low_byte = delay_ms & 0xFF + cmd = bytes([0x21, 0x61, 0x85, high_byte, low_byte]) + cmd_ack = bytes([0x22, 0x61, 0x85, 0x00, 0x00]) + self.serial_handler.cmd(cmd, cmd_ack, timeout_ms=200) + + def set_same_code_interval(self, interval_ms: int) -> None: + """Set same code interval. 设置同码间隔时间。 + + :param interval_ms: 同一条码的重复识读间隔时间,单位为毫秒。 + """ + high_byte = (interval_ms >> 8) & 0xFF + low_byte = interval_ms & 0xFF + cmd = bytes([0x21, 0x64, 0x82, high_byte, low_byte]) + cmd_ack = bytes([0x22, 0x64, 0x82, 0x00, 0x00]) + self.serial_handler.cmd(cmd, cmd_ack, timeout_ms=200) + + def set_diff_code_interval(self, interval_ms: int) -> None: + """Set difference code interval. 设置异码间隔时间。 + + :param int interval_ms: 不同条码的识读间隔时间,单位为毫秒。 + """ + high_byte = (interval_ms >> 8) & 0xFF + low_byte = interval_ms & 0xFF + cmd = bytes([0x21, 0x64, 0x81, high_byte, low_byte]) + cmd_ack = bytes([0x22, 0x64, 0x81, 0x00, 0x00]) + self.serial_handler.cmd(cmd, cmd_ack, timeout_ms=200) + + def set_same_code_no_delay(self, enable: bool) -> None: + """Set same code no delay. 设置同码非延迟输出。 + + :param bool enable: 是否开启同码非延迟输出, True 表示开启, False 表示关闭。 + """ + mode = 0x01 if enable else 0x00 + cmd = bytes([0x21, 0x64, 0x43, mode]) + cmd_ack = bytes([0x22, 0x64, 0x43, mode, 0x00]) + self.serial_handler.cmd(cmd, cmd_ack, timeout_ms=200) + + # ============================================================================ + # 系统设置 + # ============================================================================ + def set_fill_light_mode(self, mode: int = FILL_LIGHT_ON_DECODE) -> None: + """Set fill light mode. 设置补光灯模式。 + + :param int mode: The fill light mode. Available options: + - ``FILL_LIGHT_OFF``: Light off. 常灭 + - ``FILL_LIGHT_ON``: Light on. 常亮 + - ``FILL_LIGHT_ON_DECODE``: Light on during decoding. 解码时亮 + """ + cmd_fill_light = bytes([0x21, 0x62, 0x41, mode]) + cmd_fill_light_ack = bytes([0x22, 0x62, 0x41, mode, 0x00]) + self.serial_handler.cmd(cmd_fill_light, cmd_fill_light_ack, timeout_ms=100) + + def set_fill_light_brightness(self, brightness: int = 60) -> None: + """Set fill light brightness. 设置补光灯亮度。 + + :param int brightness: The fill light brightness. Range: 0~100. + """ + brightness = max(0, min(100, brightness)) + cmd_light_brightness = bytes([0x21, 0x62, 0x48, brightness]) + cmd_light_brightness_ack = bytes([0x22, 0x62, 0x48, brightness, 0x00]) + self.serial_handler.cmd(cmd_light_brightness, cmd_light_brightness_ack, timeout_ms=100) + + def set_pos_light_mode(self, mode: int = POS_LIGHT_ON_DECODE) -> None: + """Set positioning light mode. 设置定位灯模式。 + + :param int mode: The positioning light mode. Available options: + - ``POS_LIGHT_OFF``: Light off. 常灭 + - ``POS_LIGHT_ON_DECODE``: Light on during decoding. 解码时亮 + - ``POS_LIGHT_FLASH_ON_DECODE``: Light flash during decoding. 解码时闪烁 + """ + cmd_pos_light = bytes([0x21, 0x62, 0x42, mode]) + cmd_pos_light_ack = bytes([0x22, 0x62, 0x42, mode, 0x00]) + self.serial_handler.cmd(cmd_pos_light, cmd_pos_light_ack, timeout_ms=100) + + def set_startup_tone(self, mode: int) -> None: + """Set startup tone. 设置启动提示音。 + + :param mode: 0 - 关闭启动提示音 + 1 - 播放 4 声 + 2 - 播放 2 声 + """ + cmd_startup_tone = bytes([0x21, 0x63, 0x45, mode]) + cmd_startup_tone_ack = bytes([0x22, 0x63, 0x45, mode]) + self.serial_handler.cmd(cmd_startup_tone, cmd_startup_tone_ack, timeout_ms=150) + + def set_decode_success_beep(self, count: int) -> None: + """Set decode success beep. 设置解码成功提示音次数。 + + :param count: 0 - 无提示音 + 1 - 播放 1 次提示音 + 2 - 播放 2 次提示音 + """ + cmd_cfg_decode_beep = bytes([0x21, 0x63, 0x42, count]) + cmd_cfg_decode_beep_ack = bytes([0x22, 0x63, 0x42, count]) + self.serial_handler.cmd(cmd_cfg_decode_beep, cmd_cfg_decode_beep_ack, timeout_ms=150) + + # ============================================================================ + # 数据编辑 + # ============================================================================ + def set_case_conversion(self, mode: int = 0) -> None: + """Set case conversion. 设置数据大小写转换模式。 + :param mode: 0x00 - 关闭(原始数据) + 0x01 - 转大写(小写字母转换为大写) + 0x02 - 转小写(大写字母转换为小写) + """ + cmd = bytes([0x21, 0x51, 0x48, mode]) + cmd_ack = bytes([0x22, 0x51, 0x48, 0x00]) + self.serial_handler.cmd(cmd, cmd_ack, timeout_ms=200) + + def set_protocol_format(self, mode: int) -> None: + """Set protocol format. 设置协议格式。 + :param mode: 0 - 无协议 + 1 - 格式 1: [0x03] + 数据长度(2bytes) + 数据 + 2 - 格式 2: [0x03] + 数据长度 + 条码个数 + 条码 1 数据长度 + 条码 1 数据 +... + CRC + 3 - 格式 3: [0x03] + 数据长度 + 条码个数 + 条码 1ID 号 + 条码 1 数据长度 + 条码 1 数据 + ... + CRC + """ + cmd = bytes([0x21, 0x51, 0x43, mode]) + cmd_ack = bytes([0x22, 0x51, 0x43, 0x00]) + self.serial_handler.cmd(cmd, cmd_ack, timeout_ms=200) diff --git a/m5stack/libs/driver/qrcode/serial_cmd_helper.py b/m5stack/libs/driver/qrcode/serial_cmd_helper.py new file mode 100644 index 00000000..5ff07e2d --- /dev/null +++ b/m5stack/libs/driver/qrcode/serial_cmd_helper.py @@ -0,0 +1,74 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import time + + +class CmdStatus: + SUCCESS = 0 + INVALID_PARAM = 1 + TIMEOUT = 2 + ACK_MISMATCH = 3 + + +class SerialCmdHelper: + def __init__(self, serial, debug: bool = False): + self.serial = serial + self.debug = debug + + def _log_debug(self, message: str): + if self.debug: + print(f"\n[DEBUG] {message}") + + def _format_bytes(self, data: bytes) -> str: + return " ".join(f"{b:02X}" for b in data) + + def cmd(self, cmd, cmd_ack=None, timeout_ms: int = 1000) -> CmdStatus: + """ + 发送串口命令并处理响应。 + :param cmd: 要发送的命令。 + :param cmd_ack: 期望的响应 (None 表示不期望响应)。 + :param timeout_ms: 等待响应的超时时间 (仅在 cmd_ack 不为 None 时生效)。 + :return: 执行状态。 + """ + tmp = self.serial.read() # flush rx buffer + self._log_debug(f"TX: {self._format_bytes(cmd)}") + self.serial.write(cmd) + if cmd_ack is None: + return CmdStatus.SUCCESS + ack_length = len(cmd_ack) + start_time = time.ticks_ms() + while time.ticks_diff(time.ticks_ms(), start_time) < timeout_ms: + if self.serial.any() >= ack_length: + rx = self.serial.read(ack_length) + self._log_debug(f"RX: {self._format_bytes(rx)}") + if rx == cmd_ack: + return CmdStatus.SUCCESS + else: + return CmdStatus.ACK_MISMATCH + time.sleep_ms(1) + return CmdStatus.TIMEOUT + + def cmd_retry( + self, + cmd: bytes, + cmd_ack: bytes, + timeout_ms: int = 1000, + retries: int = 3, + retry_delay_ms: int = 100, + ) -> CmdStatus: + """ + 发送命令并进行重试。 + :param retries: 重试次数。 + :param retry_delay_ms: 每次重试之间的延迟时间 (毫秒)。 + :return: 执行状态。 + """ + for attempt in range(retries): + self._log_debug(f"Attempt {attempt + 1}/{retries}") + status = self.cmd(cmd, cmd_ack, timeout_ms) + if status == CmdStatus.SUCCESS: + return CmdStatus.SUCCESS + self._log_debug(f"Retry after {retry_delay_ms} ms") + time.sleep_ms(retry_delay_ms) + return status diff --git a/m5stack/libs/module/__init__.py b/m5stack/libs/module/__init__.py index 0a464aef..a4933348 100644 --- a/m5stack/libs/module/__init__.py +++ b/m5stack/libs/module/__init__.py @@ -38,6 +38,7 @@ "RCAModule": "rca", "PwrCANModule": "pwrcan", "PwrCANModuleRS485": "pwrcan", + "QRCodeModule": "qrcode", "Relay2Module": "relay_2", "Relay4Module": "relay_4", "RS232Module": "rs232", diff --git a/m5stack/libs/module/manifest.py b/m5stack/libs/module/manifest.py index 9bec2535..c4ad592c 100644 --- a/m5stack/libs/module/manifest.py +++ b/m5stack/libs/module/manifest.py @@ -37,6 +37,7 @@ "pm25.py", "pps.py", "pwrcan.py", + "qrcode.py", "rca.py", "relay_2.py", "relay_4.py", diff --git a/m5stack/libs/module/qrcode.py b/m5stack/libs/module/qrcode.py new file mode 100644 index 00000000..63ab795d --- /dev/null +++ b/m5stack/libs/module/qrcode.py @@ -0,0 +1,113 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import time +from driver.qrcode.qrcode_m14 import QRCodeM14 +from module.mbus import i2c1 + + +class QRCodeModule(QRCodeM14): + # PI4IOE register + PI4IO_REG_CHIP_RESET = const(0x01) + PI4IO_REG_IO_DIR = const(0x03) + PI4IO_REG_OUT_SET = const(0x05) + PI4IO_REG_OUT_H_IM = const(0x07) + PI4IO_REG_IN_DEF_STA = const(0x09) + PI4IO_REG_PULL_EN = const(0x0B) + PI4IO_REG_PULL_SEL = const(0x0D) + PI4IO_REG_IN_STA = const(0x0F) + PI4IO_REG_INT_MASK = const(0x11) + PI4IO_REG_IRQ_STA = const(0x13) + + """Create an AtomicQRCode2Base object. + + :param int id: UART id. + :param int tx: the UART TX pin. + :param int rx: the UART RX pin. + :param int trig: the trigger pin. + + UiFlow2 Code Block: + + |init.png| + + MicroPython Code Block: + + .. code-block:: python + + from module import QRCodeModule + + module_qrcode = QRCodeModule(id = 1, tx = 17, rx = 18) + """ + + def __init__(self, id: int = 1, tx: int = 17, rx: int = 18): + super().__init__(id, tx=rx, rx=tx) # ??? + self.i2c = i2c1 + self.address = 0x43 + if self.address not in self.i2c.scan(): + raise RuntimeError("PI4IOE not found!") + self.init_device() + self.set_power(True) # 默认开启电源 + time.sleep_ms(1000) # wait for module startup. + + def _write_reg(self, reg, value): + self.i2c.writeto(self.address, bytes([reg, value])) + + def _read_reg(self, reg): + self.i2c.writeto(self.address, bytes([reg])) + return self.i2c.readfrom(self.address, 1)[0] + + def init_device(self): + self._write_reg(self.PI4IO_REG_CHIP_RESET, 0xFF) # 复位 + chip_id = self._read_reg(PI4IO_REG_CHIP_RESET) + # print(f"Chip ID: {chip_id:#04x}") + self._write_reg(self.PI4IO_REG_IO_DIR, 0b00010001) # P4--QR_TRIG P0--PWR_EN 输出 + self._write_reg(self.PI4IO_REG_OUT_H_IM, 0b00000000) # 关闭高阻态 + self._write_reg(self.PI4IO_REG_PULL_SEL, 0b11111111) # 上拉配置 + self._write_reg(self.PI4IO_REG_PULL_EN, 0b11111111) # 使能上拉 + self._write_reg(self.PI4IO_REG_OUT_SET, 0b00010001) # 设定默认输出值 + + def set_power(self, enable: bool = True) -> None: + """Set power. 设置电源。 + + :param enable: True - power on. 开启 + False - power off. 关闭 + + UiFlow2 Code Block: + + |set_power.png| + + MicroPython Code Block: + + .. code-block:: python + + module_qrcode.set_power(enable) + """ + tmp = self._read_reg(self.PI4IO_REG_OUT_SET) + if enable: + tmp |= 0x01 + else: + tmp &= 0xFE + self._write_reg(self.PI4IO_REG_OUT_SET, tmp) + + def set_trig(self, value: int) -> None: + """Set trigger value. + + :param int value: ``0`` - Low level, ``1`` - High level. + + UiFlow2 Code Block: + + |set_trig.png| + + MicroPython Code Block: + + .. code-block:: python + + module_qrcode.set_trig(value) + """ + tmp = self._read_reg(self.PI4IO_REG_OUT_SET) + if value: + tmp |= 0x10 + else: + tmp &= 0xEF + self._write_reg(self.PI4IO_REG_OUT_SET, tmp) From 9418cf65df24a472cc38501ac67ad49496c59196 Mon Sep 17 00:00:00 2001 From: luoweiyuan Date: Thu, 3 Apr 2025 08:59:36 +0800 Subject: [PATCH 037/322] libs/unit: Add support for RF433TUnit and RF433RUnit. Signed-off-by: luoweiyuan --- docs/en/conf.py | 1 + docs/en/refs/unit.rf433r.ref | 22 ++ docs/en/refs/unit.rf433t.ref | 19 + docs/en/units/index.rst | 2 + docs/en/units/rf433r.rst | 62 +++ docs/en/units/rf433t.rst | 61 +++ .../locales/zh_CN/LC_MESSAGES/units/rf433r.po | 226 +++++++++++ .../locales/zh_CN/LC_MESSAGES/units/rf433t.po | 169 +++++++++ .../rf433r/basic_rf433r_recv_example.m5f2 | 1 + .../unit/rf433r/basic_rf433r_recv_example.py | 52 +++ .../rf433t/cores3_rf433t_send_example.m5f2 | 1 + .../unit/rf433t/cores3_rf433t_send_example.py | 60 +++ m5stack/cmodules/cmodules.cmake | 4 + m5stack/cmodules/rf433/micropython.cmake | 21 ++ m5stack/cmodules/rf433/py_rf433.c | 355 ++++++++++++++++++ m5stack/libs/unit/__init__.py | 2 + m5stack/libs/unit/manifest.py | 2 + m5stack/libs/unit/rf433r.py | 107 ++++++ m5stack/libs/unit/rf433t.py | 45 +++ 19 files changed, 1212 insertions(+) create mode 100644 docs/en/refs/unit.rf433r.ref create mode 100644 docs/en/refs/unit.rf433t.ref create mode 100644 docs/en/units/rf433r.rst create mode 100644 docs/en/units/rf433t.rst create mode 100644 docs/locales/zh_CN/LC_MESSAGES/units/rf433r.po create mode 100644 docs/locales/zh_CN/LC_MESSAGES/units/rf433t.po create mode 100644 examples/unit/rf433r/basic_rf433r_recv_example.m5f2 create mode 100644 examples/unit/rf433r/basic_rf433r_recv_example.py create mode 100644 examples/unit/rf433t/cores3_rf433t_send_example.m5f2 create mode 100644 examples/unit/rf433t/cores3_rf433t_send_example.py create mode 100644 m5stack/cmodules/rf433/micropython.cmake create mode 100644 m5stack/cmodules/rf433/py_rf433.c create mode 100644 m5stack/libs/unit/rf433r.py create mode 100644 m5stack/libs/unit/rf433t.py diff --git a/docs/en/conf.py b/docs/en/conf.py index 7c6a57f6..5aaca157 100644 --- a/docs/en/conf.py +++ b/docs/en/conf.py @@ -52,6 +52,7 @@ "module.mbus", "network", "m5can", + "rf433", ] autodoc_default_options = { diff --git a/docs/en/refs/unit.rf433r.ref b/docs/en/refs/unit.rf433r.ref new file mode 100644 index 00000000..f4c7051c --- /dev/null +++ b/docs/en/refs/unit.rf433r.ref @@ -0,0 +1,22 @@ +.. |Unit RF433R| image:: https://static-cdn.m5stack.com/resource/docs/products/unit/rf433_r/rf433_r_01.webp + :target: https://docs.m5stack.com/zh_CN/unit/rf433_r + :height: 200px + :width: 200px + +.. |init.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/rf433r/init.png +.. |start_recv.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/rf433r/start_recv.png +.. |stop_recv.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/rf433r/stop_recv.png +.. |read.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/rf433r/read.png +.. |set_recv_callback.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/rf433r/receive_data_event.png + +.. |basic_rf433r_recv_example.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/rf433r/recv_example.png + +.. |basic_rf433r_recv_example.m5f2| raw:: html + + + basic_rf433r_recv_example.m5f2 + + diff --git a/docs/en/refs/unit.rf433t.ref b/docs/en/refs/unit.rf433t.ref new file mode 100644 index 00000000..2e910057 --- /dev/null +++ b/docs/en/refs/unit.rf433t.ref @@ -0,0 +1,19 @@ +.. |Unit RF433T| image:: https://static-cdn.m5stack.com/resource/docs/products/unit/rf433_t/rf433_t_01.webp + :target: https://docs.m5stack.com/zh_CN/unit/rf433_t + :height: 200px + :width: 200px + +.. |init.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/rf433t/init.png +.. |send.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/rf433t/send.png + +.. |cores3_rf433t_send_example.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/rf433t/send_example.png + +.. |cores3_rf433t_send_example.m5f2| raw:: html + + + cores3_rf433t_send_example.m5f2 + + diff --git a/docs/en/units/index.rst b/docs/en/units/index.rst index 685af89d..6e02b730 100644 --- a/docs/en/units/index.rst +++ b/docs/en/units/index.rst @@ -86,6 +86,8 @@ Unit reflective_ir.rst relay.rst relay4.rst + rf433r.rst + rf433t.rst rfid.rst rgb.rst roller485.rst diff --git a/docs/en/units/rf433r.rst b/docs/en/units/rf433r.rst new file mode 100644 index 00000000..36ba13b8 --- /dev/null +++ b/docs/en/units/rf433r.rst @@ -0,0 +1,62 @@ +RF433R Unit +============================ + +.. sku: U113 + +.. include:: ../refs/unit.rf433r.ref + +This library is the driver for Unit RF433R. + +Support the following products: + + |Unit RF433R| + +.. note:: ESP32-S3 related devices, such as CoreS2 and AtomS3, are not supported yet. + +UiFlow2 Example: +-------------------------- + +Receive data +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Open the |basic_rf433r_recv_example.m5f2| project in UiFlow2. + +The example program demonstrates communication using RF433TUnit and RF433RUnit. +Please refer to the sending end. `RF433TUnit `_ + +UiFlow2 Code Block: + + |basic_rf433r_recv_example.png| + +Example output: + + None + +MicroPython Example: +-------------------------- + +Receive data +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The example program demonstrates communication using RF433TUnit and RF433RUnit. +Please refer to the sending end. `RF433TUnit `_ + +MicroPython Code Block: + + .. literalinclude:: ../../../examples/unit/rf433r/basic_rf433r_recv_example.py + :language: python + :linenos: + +Example output: + + None + + +**API** +-------------------------- + +RF433RUnit +^^^^^^^^^^^^^^^^^^^^^^^^ + +.. autoclass:: unit.rf433r.RF433RUnit + :members: diff --git a/docs/en/units/rf433t.rst b/docs/en/units/rf433t.rst new file mode 100644 index 00000000..064136c8 --- /dev/null +++ b/docs/en/units/rf433t.rst @@ -0,0 +1,61 @@ +RF433T Unit +============================ + +.. sku: U114 + +.. include:: ../refs/unit.rf433t.ref + +This library is the driver for Unit RF433T. + +Support the following products: + + |Unit RF433T| + +UiFlow2 Example: +-------------------------- + +Send data +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The example program demonstrates communication using RF433TUnit and RF433RUnit. +Please refer to the receiving end. `RF433RUnit `_ + +Open the |cores3_rf433t_send_example.m5f2| project in UiFlow2. + + +UiFlow2 Code Block: + + |cores3_rf433t_send_example.png| + +Example output: + + None + +MicroPython Example: +-------------------------- + +Send data +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The example program demonstrates communication using RF433TUnit and RF433RUnit. +Please refer to the receiving end. `RF433RUnit `_ + +MicroPython Code Block: + + .. literalinclude:: ../../../examples/unit/rf433t/cores3_rf433t_send_example.py + :language: python + :linenos: + +Example output: + + None + + +**API** +-------------------------- + +RF433T +^^^^^^^^^^^^^^^^^^^^^^^^ + +.. autoclass:: unit.rf433t.RF433TUnit + :members: diff --git a/docs/locales/zh_CN/LC_MESSAGES/units/rf433r.po b/docs/locales/zh_CN/LC_MESSAGES/units/rf433r.po new file mode 100644 index 00000000..16d2e869 --- /dev/null +++ b/docs/locales/zh_CN/LC_MESSAGES/units/rf433r.po @@ -0,0 +1,226 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2016 - 2025 M5Stack Technology Co., Ltd +# This file is distributed under the same license as the UIFlow2 Programming +# Guide package. +# FIRST AUTHOR , 2025. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: UIFlow2 Programming Guide \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-04-03 08:48+0800\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: zh_CN\n" +"Language-Team: zh_CN \n" +"Plural-Forms: nplurals=1; plural=0;\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.16.0\n" + +#: ../../en/units/rf433r.rst:2 4445de6c4b274d2d83837aaf0f32381d +msgid "RF433R Unit" +msgstr "" + +#: ../../en/units/rf433r.rst:8 a12b1d0b17ae4232aba7f3123ee4d21a +msgid "This library is the driver for Unit RF433R." +msgstr "这个库是 Unit RF433R 的驱动" + +#: ../../en/units/rf433r.rst:10 7cc7a18caf69454e979a39207155d5ea +msgid "Support the following products:" +msgstr "支持以下产品:" + +#: ../../en/units/rf433r.rst:12 95c7af3f64a841ad8dbc8fccb0bab6af +msgid "|Unit RF433R|" +msgstr "" + +#: ../../en/refs/unit.rf433r.ref 850e07ce8996468a8079ebeafb9448b3 +msgid "Unit RF433R" +msgstr "" + +#: ../../en/units/rf433r.rst:14 d675944739bf42c59de272a9858594bb +msgid "" +"ESP32-S3 related devices, such as CoreS2 and AtomS3, are not supported " +"yet." +msgstr "目前尚不支持与 ESP32-S3 相关的设备,如 CoreS2 和 AtomS3。" + +#: ../../en/units/rf433r.rst:17 abd499cf49f94038b89f825728190c02 +msgid "UiFlow2 Example:" +msgstr "UiFlow2 应用示例:" + +#: ../../en/units/rf433r.rst:20 ../../en/units/rf433r.rst:39 +#: 1b85108962834888913b32318f4ccc80 6816b3f0aedd42fc80c4f265bae57f53 +msgid "Receive data" +msgstr "接收数据。" + +#: ../../en/units/rf433r.rst:22 f8d926aa56fc42ec891822259f75f7a2 +msgid "Open the |basic_rf433r_recv_example.m5f2| project in UiFlow2." +msgstr "在 UiFlow2 上打开 |basic_rf433r_recv_example.m5f2| 项目。" + +#: ../../en/units/rf433r.rst:24 ../../en/units/rf433r.rst:41 +#: 03bda188405742afab52e9c4cc6c305e cee6361f9b264a9c9796bf4db603ffa0 +msgid "" +"The example program demonstrates communication using RF433TUnit and " +"RF433RUnit. Please refer to the sending end. `RF433TUnit `_" +msgstr "" +"示例程序演示使用 RF433TUnit 和 RF433RUnit 进行通信,发送数据端请查看 `RF433TUnit `_" + +#: ../../en/units/rf433r.rst:27 2226b428ec894bc4ad36037e1997cfef +#: 5dc829993c00490488f7747fb778e024 5ebd5093f5044364b44f37b20c48d754 +#: 6d43835f062c44fd8e00a25c5bbba88e b530030e2398408594c33c4978e49536 +#: d5269070522947a69b1627ee7d6834ce of unit.rf433r.RF433RUnit:5 +#: unit.rf433r.RF433RUnit.read:8 unit.rf433r.RF433RUnit.set_recv_callback:5 +#: unit.rf433r.RF433RUnit.start_recv:3 unit.rf433r.RF433RUnit.stop_recv:3 +msgid "UiFlow2 Code Block:" +msgstr "UiFlow2 代码块:" + +#: ../../en/units/rf433r.rst:29 76cd70b5dc594e589bee1e69ed4cca49 +msgid "|basic_rf433r_recv_example.png|" +msgstr "" + +#: ../../en/refs/unit.rf433r.ref:12 402ef48f2dff4aa6ae8db39f61d80905 +msgid "basic_rf433r_recv_example.png" +msgstr "" + +#: ../../en/units/rf433r.rst:31 ../../en/units/rf433r.rst:50 +#: 405a856b65894399992fb328a01a854b a7d05ff06f254d04be95f441365cf599 +msgid "Example output:" +msgstr "示例输出:" + +#: ../../en/units/rf433r.rst:33 ../../en/units/rf433r.rst:52 +#: 31ddbb8fdb384962a0f5101f7b2f5d41 5e250aaf35574a0f8ef5701eb0919cb0 +msgid "None" +msgstr "无" + +#: ../../en/units/rf433r.rst:36 4178a0627b1349d9b1c393a21142405f +msgid "MicroPython Example:" +msgstr "MicroPython 应用示例:" + +#: ../../en/units/rf433r.rst:44 01b87f46d105471eb9dd262b59592cb7 +#: 29764dc24d1b425fab147f2279d3af8a 2a7de16d88144b82b40e9932c851136e +#: 60b9732a90394725bb63ed76c4ea9e19 b28b5c4139f24104b3606c73eb5fd3e7 +#: d09d4eaa7b4542d4b7cf6b022fb23077 of unit.rf433r.RF433RUnit:9 +#: unit.rf433r.RF433RUnit.read:12 unit.rf433r.RF433RUnit.set_recv_callback:9 +#: unit.rf433r.RF433RUnit.start_recv:7 unit.rf433r.RF433RUnit.stop_recv:7 +msgid "MicroPython Code Block:" +msgstr "MicroPython 代码块:" + +#: ../../en/units/rf433r.rst:56 5978c7adc9864248a753fbdab974b742 +msgid "**API**" +msgstr "API应用" + +#: ../../en/units/rf433r.rst:59 1a48b13ad41b430e8aa5ea9c3ce55dab +msgid "RF433RUnit" +msgstr "" + +#: 9ad5e6d6a94b4df2a9d6a1591cad197b of unit.rf433r.RF433RUnit:1 +msgid "Bases: :py:class:`object`" +msgstr "" + +#: 824a5fe5a6414001bc1823bcfabd6e02 of unit.rf433r.RF433RUnit:1 +msgid "Create an RF433RUnit object." +msgstr "创建一个 RF433RUnit 对象。" + +#: ../../en/units/rf433r.rst a91ddf8a349f47dba4b7ef2022f409e9 +#: f9c26756d1ce498198d879ce6296a7ef of unit.rf433r.RF433RUnit.set_recv_callback +msgid "Parameters" +msgstr "" + +#: 5f418152b8d84b0d8c13a3d20e659184 of unit.rf433r.RF433RUnit:3 +msgid "Tuple containing grove port pin numbers." +msgstr "包含 grove 端口引脚的 Tuple。" + +#: 5c7d5c0361af462988edddfddb0f64ef of unit.rf433r.RF433RUnit:7 +msgid "|init.png|" +msgstr "" + +#: ../../en/refs/unit.rf433r.ref:6 3d1dbd547a1b4e488b96d7a89266858c +msgid "init.png" +msgstr "" + +#: 8200274e666c4c9488d4bda9cdd30c85 of unit.rf433r.RF433RUnit.read:1 +msgid "Read data." +msgstr "读取数据。" + +#: 18b216df1b5d451c9ad83b022449cc0c of unit.rf433r.RF433RUnit.read +msgid "Returns" +msgstr "" + +#: c20484427e504e8eb1cac88b4281dcc6 of unit.rf433r.RF433RUnit.read:3 +msgid "receive data." +msgstr "接收数据。" + +#: 43dfdb5062844f848b7788bab845dea0 5473fa2eeb8542149b45785768aaadad +#: b09ffec03fbb41b5a80e6724a641cac8 of unit.rf433r.RF433RUnit.read +#: unit.rf433r.RF433RUnit.start_recv unit.rf433r.RF433RUnit.stop_recv +msgid "Return type" +msgstr "" + +#: 928b84236cf748acba3285f59e5d7998 of unit.rf433r.RF433RUnit.read:6 +msgid "If no data is received, return None." +msgstr "未接收到数据,则返回 None。" + +#: 6802717ac9114d97b88a879023888749 of unit.rf433r.RF433RUnit.read:10 +msgid "|read.png|" +msgstr "" + +#: ../../en/refs/unit.rf433r.ref:9 3ab838b7b37c4c25a5282d730752b10e +msgid "read.png" +msgstr "" + +#: a5003ca26b7c40528e6b81a50a95e8d3 of +#: unit.rf433r.RF433RUnit.set_recv_callback:1 +msgid "Set receive data callback function." +msgstr "设置接收数据回调函数。" + +#: 215049adb99545e9a74c2f121d740802 of +#: unit.rf433r.RF433RUnit.set_recv_callback:3 +msgid "A function that will be called when data is received." +msgstr "当接收到数据时将调用的函数。" + +#: bcf90006ecff4b028af98681ffce61aa of +#: unit.rf433r.RF433RUnit.set_recv_callback:7 +msgid "|set_recv_callback.png|" +msgstr "" + +#: ../../en/refs/unit.rf433r.ref:10 0b639c50787a490395f18da41339245b +msgid "set_recv_callback.png" +msgstr "" + +#: d73496d696d349409b977fe4e4099709 of unit.rf433r.RF433RUnit.start_recv:1 +msgid "Start receive data." +msgstr "开始接收数据。" + +#: 1446c0163a954fe38cc08f5cda7ac45e of unit.rf433r.RF433RUnit.start_recv:5 +msgid "|start_recv.png|" +msgstr "" + +#: ../../en/refs/unit.rf433r.ref:7 3a23c170ba2744cd837746b72efa277c +msgid "start_recv.png" +msgstr "" + +#: e31cf211ca2c46c1910837a763675ece of unit.rf433r.RF433RUnit.stop_recv:1 +msgid "Stop receive data." +msgstr "停止接收数据。" + +#: 2831d48effe94327bc88af5395058b61 of unit.rf433r.RF433RUnit.stop_recv:5 +msgid "|stop_recv.png|" +msgstr "" + +#: ../../en/refs/unit.rf433r.ref:8 7dea46b2cec94f2c989c01204bbadfba +msgid "stop_recv.png" +msgstr "" + +#~ msgid "" +#~ "示例程序演示使用 RF433TUnit 和 RF433RUnit 进行通信,发送数据端请查看" +#~ " `RF433TUnit `_" +#~ msgstr "" + +#~ msgid "the input signal pin." +#~ msgstr "" + diff --git a/docs/locales/zh_CN/LC_MESSAGES/units/rf433t.po b/docs/locales/zh_CN/LC_MESSAGES/units/rf433t.po new file mode 100644 index 00000000..28ade481 --- /dev/null +++ b/docs/locales/zh_CN/LC_MESSAGES/units/rf433t.po @@ -0,0 +1,169 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2016 - 2025 M5Stack Technology Co., Ltd +# This file is distributed under the same license as the UIFlow2 Programming +# Guide package. +# FIRST AUTHOR , 2025. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: UIFlow2 Programming Guide \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-04-03 08:48+0800\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: zh_CN\n" +"Language-Team: zh_CN \n" +"Plural-Forms: nplurals=1; plural=0;\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.16.0\n" + +#: ../../en/units/rf433t.rst:2 160fac0d959649b9a9bbe93f8a522d62 +msgid "RF433T Unit" +msgstr "" + +#: ../../en/units/rf433t.rst:8 6dea9174a6ec4959903af5696975a96c +msgid "This library is the driver for Unit RF433T." +msgstr "这个库是 Unit RF433T 的驱动" + +#: ../../en/units/rf433t.rst:10 a577fbd7a4f842b5bf4aaffbb240c307 +msgid "Support the following products:" +msgstr "支持以下产品:" + +#: ../../en/units/rf433t.rst:12 68b6d889c8e3461ca2e5d001762d92be +msgid "|Unit RF433T|" +msgstr "" + +#: ../../en/refs/unit.rf433t.ref 98107d67b79f4f43a36b6c1995b61994 +msgid "Unit RF433T" +msgstr "" + +#: ../../en/units/rf433t.rst:15 4513db7564154b5cbb0047ed8feebff1 +msgid "UiFlow2 Example:" +msgstr "UiFlow2 应用示例:" + +#: ../../en/units/rf433t.rst:18 ../../en/units/rf433t.rst:38 +#: 62558d89a1b14a5ba14b3a18f4201cbd ceb15fc754674e75901efda2259844d4 +msgid "Send data" +msgstr "发送数据。" + +#: ../../en/units/rf433t.rst:20 ../../en/units/rf433t.rst:40 +#: 098997616d8a4fec9102eaa6ae33721d 18eedc77ba32434d9942e71da8e44105 +msgid "" +"The example program demonstrates communication using RF433TUnit and " +"RF433RUnit. Please refer to the receiving end. `RF433RUnit `_" +msgstr "" +"示例程序演示使用 RF433TUnit 和 RF433RUnit 进行通信,接收数据端请查看 `RF433RUnit `_" + +#: ../../en/units/rf433t.rst:23 3a462343266b4fd6b77dd910a4d6bbd1 +msgid "Open the |cores3_rf433t_send_example.m5f2| project in UiFlow2." +msgstr "在 UiFlow2 上打开 |cores3_rf433t_send_example.m5f2| 项目。" + +#: ../../en/units/rf433t.rst:26 cebc3247b77c43928689000624e7a28e +#: d75823132820479ca416b20aab15d5b2 of unit.rf433t.RF433TUnit:5 +#: unit.rf433t.RF433TUnit.send:6 +msgid "UiFlow2 Code Block:" +msgstr "UiFlow2 代码块:" + +#: ../../en/units/rf433t.rst:28 382789cf0d6d48f0bdbb0e51922ab2a2 +msgid "|cores3_rf433t_send_example.png|" +msgstr "" + +#: ../../en/refs/unit.rf433t.ref:9 d856298c541e422895e9a549a304338a +msgid "cores3_rf433t_send_example.png" +msgstr "" + +#: ../../en/units/rf433t.rst:30 ../../en/units/rf433t.rst:49 +#: 1e61d549640f470eb4a1debe8fca4925 75902abea76b4600b05b5a4f27fa2538 +msgid "Example output:" +msgstr "示例输出:" + +#: ../../en/units/rf433t.rst:32 ../../en/units/rf433t.rst:51 +#: 56accd0283544e8ca7ec64120ec08ce6 ce7f75313f16496082900bd3367bf58e +msgid "None" +msgstr "无" + +#: ../../en/units/rf433t.rst:35 32e3d8a309064741aa60e56a3be38cf5 +msgid "MicroPython Example:" +msgstr "MicroPython 应用示例:" + +#: ../../en/units/rf433t.rst:43 14983f6c51d049149e28fd69a67630d3 +#: b9c725a17136421aaecf9756bfa04f15 of unit.rf433t.RF433TUnit:9 +#: unit.rf433t.RF433TUnit.send:10 +msgid "MicroPython Code Block:" +msgstr "MicroPython 代码块:" + +#: ../../en/units/rf433t.rst:55 e166f16930d94854afb065b036512640 +msgid "**API**" +msgstr "API应用" + +#: ../../en/units/rf433t.rst:58 b13f4ed2a2aa490c971e8ac63b4a4aac +msgid "RF433T" +msgstr "" + +#: 776f6927b38a4d02b6e0112fbed22fad of unit.rf433t.RF433TUnit:1 +msgid "Bases: :py:class:`object`" +msgstr "" + +#: f2dae67c99814907b765b7a3caa88015 of unit.rf433t.RF433TUnit:1 +msgid "Create an RF433TUnit object." +msgstr "创建一个 RF433TUnit 对象。" + +#: ../../en/units/rf433t.rst a4ec64ba72a44bee9ec614f6294c34ec of +#: unit.rf433t.RF433TUnit.send +msgid "Parameters" +msgstr "" + +#: 9eadce8130c845eba03b007e4c14f219 of unit.rf433t.RF433TUnit:3 +msgid "" +"Tuple containing grove port pin numbers. UiFlow2 Code Block: " +"|init.png| MicroPython Code Block: .. code-block:: python " +"from unit import RF433TUnit unit_rf433t = RF433TUnit(port=(8, " +"9))" +msgstr "" + +#: c4539c6d95264e34a39a63e967240a6a of unit.rf433t.RF433TUnit:3 +msgid "Tuple containing grove port pin numbers." +msgstr "包含 grove 端口引脚的 Tuple。" + +#: 8c31167c1ffa4fa796ee66ddeab7f54c of unit.rf433t.RF433TUnit:7 +msgid "|init.png|" +msgstr "" + +#: ../../en/refs/unit.rf433t.ref:6 ed6ac2362501426db579052dcf604297 +msgid "init.png" +msgstr "" + +#: 54c4b67a29f34127b6b2e930509cbecc of unit.rf433t.RF433TUnit.send:1 +msgid "Send data." +msgstr "发送数据。" + +#: 603668216af44e509b67f308e08aed8e of unit.rf433t.RF433TUnit.send:3 +msgid "The data to send." +msgstr "要发送的数据。" + +#: 6ed36a45335a4212917a355be85eb0bc of unit.rf433t.RF433TUnit.send +msgid "Return type" +msgstr "" + +#: 8c31167c1ffa4fa796ee66ddeab7f54c of unit.rf433t.RF433TUnit.send:8 +msgid "|send.png|" +msgstr "" + +#: ../../en/refs/unit.rf433t.ref:7 ed6ac2362501426db579052dcf604297 +msgid "send.png" +msgstr "" + +#~ msgid "the data to send." +#~ msgstr "" + +#~ msgid "" +#~ "示例程序演示使用 RF433TUnit 和 RF433RUnit 进行通信,接收数据端请查看" +#~ " `RF433RUnit `_" +#~ msgstr "" + diff --git a/examples/unit/rf433r/basic_rf433r_recv_example.m5f2 b/examples/unit/rf433r/basic_rf433r_recv_example.m5f2 new file mode 100644 index 00000000..c70b29eb --- /dev/null +++ b/examples/unit/rf433r/basic_rf433r_recv_example.m5f2 @@ -0,0 +1 @@ +{"version":"V2.0","versionNumber":"V2.2.4","type":"fire","components":[{"name":"screen","type":"screen","layer":0,"screenId":"builtin","screenName":"","id":"__fire_screen","createTime":1743145835977,"x":0,"y":0,"width":320,"height":240,"backgroundColor":"#000000","size":0,"isSelected":true},{"name":"title0","type":"title","layer":1,"screenId":"builtin","screenName":"","id":"ieXaR=t7zL5G1!fC","createTime":1743147368986,"x":0,"y":0,"color":"#ffffff","backgroundColor":"#0000FF","text":"RF433R Recv Data","textOffset":3,"font":"Widgets.FONTS.DejaVu24","isSelected":false},{"name":"label_cnt","type":"label","layer":2,"screenId":"builtin","screenName":"","id":"dKW%jondbCv!1+t5","createTime":1743147409647,"x":115,"y":118,"color":"#00ff00","backgroundColor":"#000000","text":"Count","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false}],"resources":[{"hardware":["hardware_button","hardware_pin_button","imu","rgb","speaker"]},{"unit":["unit_rf433r"]}],"units":[{"type":"unit_rf433r","name":"rf433r_0","portList":["A","B","C","Custom"],"portType":"B","userPort":[22,21],"id":"eAN^+cOskz*vZOmA","createTime":1743148086141}],"hats":[],"bases":[],"i2cs":[],"blockly":"rf433r_datacnttruerf433r_0rf433r_0truerf433r_0rf433r_datacntGETLASTrf433r_datalabel_cntLabelCount: cnthello M5rf433r_data","screen":[{"simulationName":"Built-in","type":"builtin","width":320,"height":240,"scale":0.78,"screenName":"","blockId":"","screenColorType":0,"id":"builtin","createTime":1743145835976}],"logicWhenNum":0,"customList":[]} \ No newline at end of file diff --git a/examples/unit/rf433r/basic_rf433r_recv_example.py b/examples/unit/rf433r/basic_rf433r_recv_example.py new file mode 100644 index 00000000..74a0dea0 --- /dev/null +++ b/examples/unit/rf433r/basic_rf433r_recv_example.py @@ -0,0 +1,52 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import os, sys, io +import M5 +from M5 import * +from unit import RF433RUnit + + +title0 = None +label_cnt = None +rf433r_0 = None +rf433r_data = None +cnt = None + + +def rf433r_0_receive_event(received_data): + global title0, label_cnt, rf433r_0, rf433r_data, cnt + rf433r_data = received_data + cnt = rf433r_data[-1] + label_cnt.setText(str((str("Count: ") + str(cnt)))) + print(rf433r_data) + + +def setup(): + global title0, label_cnt, rf433r_0, rf433r_data, cnt + M5.begin() + title0 = Widgets.Title("RF433R Recv Data", 3, 0xFFFFFF, 0x0000FF, Widgets.FONTS.DejaVu24) + label_cnt = Widgets.Label("Count", 115, 118, 1.0, 0x00FF00, 0x000000, Widgets.FONTS.DejaVu18) + rf433r_0 = RF433RUnit((36, 26)) + rf433r_0.set_recv_callback(rf433r_0_receive_event) + rf433r_0.start_recv() + + +def loop(): + global title0, label_cnt, rf433r_0, rf433r_data, cnt + M5.update() + + +if __name__ == "__main__": + try: + setup() + while True: + loop() + except (Exception, KeyboardInterrupt) as e: + try: + from utility import print_error_msg + + print_error_msg(e) + except ImportError: + print("please update to latest firmware") diff --git a/examples/unit/rf433t/cores3_rf433t_send_example.m5f2 b/examples/unit/rf433t/cores3_rf433t_send_example.m5f2 new file mode 100644 index 00000000..da7de2b7 --- /dev/null +++ b/examples/unit/rf433t/cores3_rf433t_send_example.m5f2 @@ -0,0 +1 @@ +{"version":"V2.0","versionNumber":"V2.2.4","type":"cores3","components":[{"name":"screen","type":"screen","layer":0,"screenId":"builtin","screenName":"","id":"__cores3_screen","createTime":1743146016540,"x":0,"y":0,"width":320,"height":240,"backgroundColor":"#000000","size":0,"isSelected":true},{"name":"title0","type":"title","layer":1,"screenId":"builtin","screenName":"","id":"zK^E%Dp%Zeb!L-$a","createTime":1743146047807,"x":0,"y":0,"color":"#ffffff","backgroundColor":"#0000FF","text":"RF433T Send Data","textOffset":3,"font":"Widgets.FONTS.DejaVu24","isSelected":false},{"name":"label0","type":"label","layer":2,"screenId":"builtin","screenName":"","id":"mTZmy@@1yurx`BqW","createTime":1743146078279,"x":41,"y":54,"color":"#ffffff","backgroundColor":"#000000","text":"Press Power Button Send","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false,"width":237,"height":21},{"name":"label_cnt","type":"label","layer":3,"screenId":"builtin","screenName":"","id":"toUfM8#*88_ETLxT","createTime":1743146114314,"x":115,"y":118,"color":"#00ff00","backgroundColor":"#000000","text":"Count","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false,"width":56,"height":21}],"resources":[{"hardware":["hardware_button","hardware_pin_button","imu","speaker","touch","als","mic"]},{"unit":["unit_rf433t"]}],"units":[{"type":"unit_rf433t","name":"rf433t_0","portList":["A","B","C","Custom"],"portType":"B","userPort":[22,21],"id":"ysICuEbWXBc^$eha","createTime":1743146025082,"initBlockId":"lE6uw%e_$Q#+8Uo0brB*"}],"hats":[],"bases":[],"i2cs":[],"blockly":"cnttx_buftruerf433t_0tx_buf3SETFROM_STARTtx_buf00SETFROM_STARTtx_buf10cnt0SETLASTtx_bufcnttrueBtnPWRWAS_CLICKEDcntADD1cnt1SETLASTtx_bufcntrf433t_0tx_buflabel_cntLabelCount: cnt","screen":[{"simulationName":"Built-in","type":"builtin","width":320,"height":240,"scale":0.78,"screenName":"","blockId":"","screenColorType":0,"id":"builtin","createTime":1743146016540}],"logicWhenNum":0,"customList":[]} \ No newline at end of file diff --git a/examples/unit/rf433t/cores3_rf433t_send_example.py b/examples/unit/rf433t/cores3_rf433t_send_example.py new file mode 100644 index 00000000..f4364f8c --- /dev/null +++ b/examples/unit/rf433t/cores3_rf433t_send_example.py @@ -0,0 +1,60 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import os, sys, io +import M5 +from M5 import * +from unit import RF433TUnit + + +title0 = None +label0 = None +label_cnt = None +rf433t_0 = None +cnt = None +tx_buf = None + + +def btnPWR_wasClicked_event(state): + global title0, label0, label_cnt, rf433t_0, cnt, tx_buf + cnt = cnt + 1 + tx_buf[-1] = cnt + rf433t_0.send(tx_buf) + label_cnt.setText(str((str("Count: ") + str(cnt)))) + + +def setup(): + global title0, label0, label_cnt, rf433t_0, cnt, tx_buf + M5.begin() + title0 = Widgets.Title("RF433T Send Data", 3, 0xFFFFFF, 0x0000FF, Widgets.FONTS.DejaVu24) + label0 = Widgets.Label( + "Press Power Button Send", 41, 54, 1.0, 0xFFFFFF, 0x000000, Widgets.FONTS.DejaVu18 + ) + label_cnt = Widgets.Label("Count", 115, 118, 1.0, 0x00FF00, 0x000000, Widgets.FONTS.DejaVu18) + BtnPWR.setCallback(type=BtnPWR.CB_TYPE.WAS_CLICKED, cb=btnPWR_wasClicked_event) + rf433t_0 = RF433TUnit((8, 9)) + tx_buf = bytearray(3) + tx_buf[-1] = 0 + tx_buf[0] = 0 + cnt = 0 + tx_buf[-1] = cnt + + +def loop(): + global title0, label0, label_cnt, rf433t_0, cnt, tx_buf + M5.update() + + +if __name__ == "__main__": + try: + setup() + while True: + loop() + except (Exception, KeyboardInterrupt) as e: + try: + from utility import print_error_msg + + print_error_msg(e) + except ImportError: + print("please update to latest firmware") diff --git a/m5stack/cmodules/cmodules.cmake b/m5stack/cmodules/cmodules.cmake index 87c53541..3a4380c6 100644 --- a/m5stack/cmodules/cmodules.cmake +++ b/m5stack/cmodules/cmodules.cmake @@ -22,6 +22,10 @@ include(${CMAKE_CURRENT_LIST_DIR}/m5can/m5can.cmake) # add m5unified module include(${CMAKE_CURRENT_LIST_DIR}/m5unified/m5unified.cmake) +# add rf433 module +include(${CMAKE_CURRENT_LIST_DIR}/rf433/micropython.cmake) + + if(ADF_MODULE_ENABLE) include(${CMAKE_CURRENT_LIST_DIR}/adf_module/micropython.cmake) endif() diff --git a/m5stack/cmodules/rf433/micropython.cmake b/m5stack/cmodules/rf433/micropython.cmake new file mode 100644 index 00000000..903fca45 --- /dev/null +++ b/m5stack/cmodules/rf433/micropython.cmake @@ -0,0 +1,21 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +# Create an INTERFACE library for our C module. +add_library(moudle_rf433 INTERFACE) + +# Add our source files to the lib +target_sources(moudle_rf433 INTERFACE + ${CMAKE_CURRENT_LIST_DIR}/py_rf433.c +) + +# Add the current directory as an include directory. +target_include_directories(moudle_rf433 INTERFACE + ${CMAKE_CURRENT_LIST_DIR} +) + +# Link our INTERFACE library to the usermod target. +target_link_libraries(usermod INTERFACE moudle_rf433) + + diff --git a/m5stack/cmodules/rf433/py_rf433.c b/m5stack/cmodules/rf433/py_rf433.c new file mode 100644 index 00000000..6665c935 --- /dev/null +++ b/m5stack/cmodules/rf433/py_rf433.c @@ -0,0 +1,355 @@ +/* + * SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD + * + * SPDX-License-Identifier: MIT + */ + +#include +#include +#include +#include "py/nlr.h" +#include "py/obj.h" +#include "py/objlist.h" +#include "py/objstr.h" +#include "py/objtuple.h" +#include "py/objtype.h" +#include "py/runtime.h" +#include "py/mphal.h" +#include "driver/rmt.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/queue.h" +#include "esp_check.h" + +static const char *TAG = "py_rf433"; + +#define RMT_TX_CHANNEL RMT_CHANNEL_0 +#define RMT_RX_CHANNEL RMT_CHANNEL_1 + +#define RMT_CLK_DIV 80 /*!< RMT counter clock divider */ +#define RMT_1US_TICKS (80000000 / RMT_CLK_DIV / 1000000) +#define RMT_1MS_TICKS (RMT_1US_TICKS * 1000) + +RingbufHandle_t rb = NULL; + +// 自定义协议参数 +#define SYNC_HIGH_US 9000 +#define SYNC_LOW_US 4500 + +#define BIT_HIGH_US 500 +#define BIT0_LOW_US 500 +#define BIT1_LOW_US 1500 + +#define FRAME_HEADER 0xAA +#define FRAME_TAIL 0xA0 + +// ================================================================================================= +// class: rf433.Tx +typedef struct _tx_obj_t { + mp_obj_base_t base; + int out; +} tx_obj_t; + +extern const mp_obj_type_t mp_tx_type; + +// 编码单个比特 +static rmt_item32_t encode_bit(bool bit) +{ + rmt_item32_t item; + if (bit) { + item.level0 = 1; + item.duration0 = BIT_HIGH_US; // 1 高电平持续时间 + item.level1 = 0; + item.duration1 = BIT1_LOW_US; // 1 低电平持续时间 + } else { + item.level0 = 1; + item.duration0 = BIT_HIGH_US; // 0 高电平持续时间 + item.level1 = 0; + item.duration1 = BIT0_LOW_US; // 0 低电平持续时间 + } + + return item; +} + +// 编码一个字节 +static void encode_byte(rmt_item32_t *items, int *index, uint8_t byte) +{ + for (int bit_idx = 7; bit_idx >= 0; bit_idx--) { + items[(*index)++] = encode_bit((byte >> bit_idx) & 1); + } +} + +// 发送 RMT 数据 +void send_data(const uint8_t *payload, size_t len) +{ + // 计算 RMT 数据的最大数量(同步位 + 帧头 + 数据长度 + 数据 + 帧尾,每个字节占 8 个项) + size_t item_max_count = 1 + (2 + len + 1) * 8; + rmt_item32_t items[item_max_count]; + int item_idx = 0; + + // 添加同步位 + items[item_idx++] = (rmt_item32_t){.duration0 = SYNC_HIGH_US, .level0 = 1, .duration1 = SYNC_LOW_US, .level1 = 0}; + + // 添加帧头 0xAA + encode_byte(items, &item_idx, FRAME_HEADER); + + // 数据 payload 长度 + encode_byte(items, &item_idx, len); + + // 添加 payload 数据 + for (size_t i = 0; i < len; i++) { + encode_byte(items, &item_idx, payload[i]); + } + + // 添加帧尾 0xA0 + encode_byte(items, &item_idx, FRAME_TAIL); + + // 发送 RMT 数据 + rmt_write_items(RMT_TX_CHANNEL, items, item_idx, true); + rmt_wait_tx_done(RMT_TX_CHANNEL, portMAX_DELAY); +} + +static bool tx_is_initialized; +static mp_obj_t tx_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) +{ + tx_obj_t *self = mp_obj_malloc_with_finaliser(tx_obj_t, &mp_tx_type); + + static const mp_arg_t allowed_args[] = { + {MP_QSTR_out_pin, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL}}, + }; + mp_arg_val_t args_vals[MP_ARRAY_SIZE(allowed_args)]; + mp_map_t kw_args; + mp_map_init_fixed_table(&kw_args, n_kw, args + n_args); + mp_arg_parse_all(n_args, args + 1, &kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args_vals); + + self->out = mp_obj_get_int(args_vals[0].u_obj); + if (!tx_is_initialized) { // TODO: deinit() 实现 + tx_is_initialized = true; + rmt_config_t tx_cfg; + tx_cfg.rmt_mode = RMT_MODE_TX; + tx_cfg.channel = RMT_TX_CHANNEL; + tx_cfg.gpio_num = self->out; + tx_cfg.mem_block_num = 1; + tx_cfg.tx_config.loop_en = false; + tx_cfg.tx_config.carrier_en = false; + tx_cfg.tx_config.idle_output_en = true; + tx_cfg.tx_config.idle_level = (rmt_idle_level_t)0; + tx_cfg.clk_div = 80; // RMT_CLK_DIV; // 时钟分频 + ESP_ERROR_CHECK(rmt_config(&tx_cfg)); + ESP_ERROR_CHECK(rmt_driver_install(tx_cfg.channel, 0, 0)); + } + + return MP_OBJ_FROM_PTR(self); +} + +static mp_obj_t tx_send(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) +{ + const mp_arg_t allowed_args[] = { + {MP_QSTR_data, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_obj = mp_const_none}}, + }; + + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + mp_buffer_info_t data_buff; + mp_get_buffer_raise(args[0].u_obj, &data_buff, MP_BUFFER_READ); + + send_data((const uint8_t *)data_buff.buf, data_buff.len); + + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_KW(tx_send_obj, 1, tx_send); + +static const mp_rom_map_elem_t tx_locals_dict_table[] = { + // Methods + {MP_ROM_QSTR(MP_QSTR_send), MP_ROM_PTR(&tx_send_obj)}, +}; +MP_DEFINE_CONST_DICT(tx_locals_dict, tx_locals_dict_table); + +MP_DEFINE_CONST_OBJ_TYPE(mp_tx_type, MP_QSTR_Tx, MP_TYPE_FLAG_NONE, make_new, tx_make_new, locals_dict, + &tx_locals_dict); + +// ================================================================================================= +// class: rf433.Rx +typedef struct _rx_obj_t { + mp_obj_base_t base; + int in; +} rx_obj_t; + +extern const mp_obj_type_t mp_rx_type; + +static mp_obj_t rx_callback = mp_const_none; +static uint8_t rx_buffer[256]; +static size_t rx_len = 0; +static const size_t RX_MAX_LEN = 64; + +static bool decode_bit(rmt_item32_t item) +{ + int high_us = item.duration0; + int low_us = item.duration1; + // 根据 endode 参数修改 + return (high_us > 350 && high_us < 650 && low_us > 1350 && low_us < 1650); +} + +static void decode_data(rmt_item32_t *items, size_t num_items) +{ + uint8_t byte = 0; + int bit_count = 0; + int rx_state = 0; + size_t data_len = 0; + size_t buf_index = 0; + + for (int i = 0; i < num_items; i++) { + bool bit = decode_bit(items[i]); + byte = (byte << 1) | bit; + bit_count++; + if (bit_count == 8) { + if (rx_state == 0 && byte == FRAME_HEADER) { + rx_state++; + } else if (rx_state == 1) { + rx_state++; + data_len = byte; + } else if (rx_state == 2) { + if (buf_index < RX_MAX_LEN) { + rx_buffer[buf_index++] = byte; + if (buf_index == data_len + 1) { + break; + } + } + } else { + rx_state = 0; + buf_index = 0; + } + bit_count = 0; + byte = 0; + } + } + + if (rx_buffer[data_len] == FRAME_TAIL) { + rx_len = data_len; + if (rx_callback != mp_const_none && rx_len > 0) { + if (!mp_sched_schedule(rx_callback, mp_const_none)) { + mp_printf(&mp_plat_print, "Task queue full, cannot schedule callback!\n"); + } + } + } +} + +void app_rf433r_rx_decode(void *param) +{ + RingbufHandle_t rb = NULL; + rmt_get_ringbuf_handle(RMT_RX_CHANNEL, &rb); + rmt_rx_start(RMT_RX_CHANNEL, true); + + while (rb) { + size_t rx_size = 0; + rmt_item32_t *items = (rmt_item32_t *)xRingbufferReceive(rb, &rx_size, portMAX_DELAY); + if (items) { + size_t num_items = rx_size / sizeof(rmt_item32_t); + decode_data(items, num_items); + vRingbufferReturnItem(rb, (void *)items); + } + } +} + +static bool rx_is_initialized; +static mp_obj_t rx_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) +{ + rx_obj_t *self = mp_obj_malloc_with_finaliser(rx_obj_t, &mp_rx_type); + + static const mp_arg_t allowed_args[] = { + {MP_QSTR_in_pin, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL}}, + }; + mp_arg_val_t args_vals[MP_ARRAY_SIZE(allowed_args)]; + mp_map_t kw_args; + mp_map_init_fixed_table(&kw_args, n_kw, args + n_args); + mp_arg_parse_all(n_args, args + 1, &kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args_vals); + + self->in = mp_obj_get_int(args_vals[0].u_obj); + if (!rx_is_initialized) { // TODO: deinit() 实现 + rx_is_initialized = true; + rmt_config_t rxconfig; + rxconfig.rmt_mode = RMT_MODE_RX; + rxconfig.channel = RMT_RX_CHANNEL; + rxconfig.gpio_num = self->in; + rxconfig.mem_block_num = 6; + rxconfig.clk_div = RMT_CLK_DIV; + rxconfig.rx_config.filter_en = true; + rxconfig.rx_config.filter_ticks_thresh = 100 * RMT_1US_TICKS; + rxconfig.rx_config.idle_threshold = 3 * RMT_1MS_TICKS; + ESP_ERROR_CHECK(rmt_config(&rxconfig)); + ESP_ERROR_CHECK(rmt_driver_install(rxconfig.channel, 4096, 0)); + + xTaskCreate(app_rf433r_rx_decode, "app_rf433r_rx_decode", 4096, NULL, 3, NULL); + } + + return MP_OBJ_FROM_PTR(self); +} + +static mp_obj_t rx_start_recv(mp_obj_t self_in) +{ + if (rb == NULL) { + rmt_get_ringbuf_handle(RMT_RX_CHANNEL, &rb); + rmt_rx_start(RMT_RX_CHANNEL, true); + } + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_1(rx_start_recv_obj, rx_start_recv); + +static mp_obj_t rx_stop_recv(mp_obj_t self_in) +{ + rb = NULL; + rmt_rx_stop(RMT_RX_CHANNEL); + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_1(rx_stop_recv_obj, rx_stop_recv); + +static mp_obj_t rx_set_recv_callback(mp_obj_t self_in, mp_obj_t callback) +{ + if (mp_obj_is_callable(callback)) { + rx_callback = callback; + } else { + mp_raise_ValueError(MP_ERROR_TEXT("callback must be callable or None")); + } + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_2(rx_set_recv_callback_obj, rx_set_recv_callback); + +static mp_obj_t rx_read(mp_obj_t self_in) +{ + if (rx_len == 0) { + return mp_const_none; + } + mp_obj_t data = mp_obj_new_bytes(rx_buffer, rx_len); + rx_len = 0; + return data; +} +static MP_DEFINE_CONST_FUN_OBJ_1(rx_read_obj, rx_read); + +static const mp_rom_map_elem_t rx_locals_dict_table[] = { + // Methods + {MP_ROM_QSTR(MP_QSTR_start_recv), MP_ROM_PTR(&rx_start_recv_obj)}, + {MP_ROM_QSTR(MP_QSTR_stop_recv), MP_ROM_PTR(&rx_stop_recv_obj)}, + {MP_ROM_QSTR(MP_QSTR_read), MP_ROM_PTR(&rx_read_obj)}, + {MP_ROM_QSTR(MP_QSTR_set_recv_callback), MP_ROM_PTR(&rx_set_recv_callback_obj)}, +}; +MP_DEFINE_CONST_DICT(rx_locals_dict, rx_locals_dict_table); + +MP_DEFINE_CONST_OBJ_TYPE(mp_rx_type, MP_QSTR_Rx, MP_TYPE_FLAG_NONE, make_new, rx_make_new, locals_dict, + &rx_locals_dict); + +// ================================================================================================= +// module: rf433 +static const mp_rom_map_elem_t rf433_globals_table[] = { + {MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_rf433)}, + {MP_ROM_QSTR(MP_QSTR_Tx), MP_ROM_PTR(&mp_tx_type)}, + {MP_ROM_QSTR(MP_QSTR_Rx), MP_ROM_PTR(&mp_rx_type)}, +}; +static MP_DEFINE_CONST_DICT(rf433_globals, rf433_globals_table); + +const mp_obj_module_t module_rf433 = { + .base = {&mp_type_module}, + .globals = (mp_obj_dict_t *)&rf433_globals, +}; + +MP_REGISTER_MODULE(MP_QSTR_rf433, module_rf433); diff --git a/m5stack/libs/unit/__init__.py b/m5stack/libs/unit/__init__.py index 83dc5e52..975d487a 100644 --- a/m5stack/libs/unit/__init__.py +++ b/m5stack/libs/unit/__init__.py @@ -98,6 +98,8 @@ "RelayUnit": "relay", "Relay4Unit": "relay4", "Relay2Unit": "relay2", + "RF433RUnit": "rf433r", + "RF433TUnit": "rf433t", "RFIDUnit": "rfid", "RGBUnit": "rgb", "Roller485Unit": "roller485", diff --git a/m5stack/libs/unit/manifest.py b/m5stack/libs/unit/manifest.py index b61fff11..f1dc5116 100644 --- a/m5stack/libs/unit/manifest.py +++ b/m5stack/libs/unit/manifest.py @@ -97,6 +97,8 @@ "relay.py", "relay2.py", "relay4.py", + "rf433r.py", + "rf433t.py", "rfid.py", "rgb.py", "roller485.py", diff --git a/m5stack/libs/unit/rf433r.py b/m5stack/libs/unit/rf433r.py new file mode 100644 index 00000000..96b33dae --- /dev/null +++ b/m5stack/libs/unit/rf433r.py @@ -0,0 +1,107 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import rf433 +from micropython import schedule + + +class RF433RUnit: + """Create an RF433RUnit object. + + :param list|tuple port: Tuple containing grove port pin numbers. + + UiFlow2 Code Block: + + |init.png| + + MicroPython Code Block: + + .. code-block:: python + + from unit import RF433RUnit + + unit_rf433r = RF433RUnit(port=(36, 26)) + """ + + def __init__(self, port: list | tuple = None): + self.rx = rf433.Rx(in_pin=port[0]) + self.callback = None + + def set_recv_callback(self, callback): + """Set receive data callback function. + + :param callback: A function that will be called when data is received. + + UiFlow2 Code Block: + + |set_recv_callback.png| + + MicroPython Code Block: + + .. code-block:: python + + def unit_rf433r_receive_event(received_data): + rf433r_data = received_data + + unit_rf433r.set_recv_callback(unit_rf433r_receive_event) + """ + self.callback = callback + + def _irq_callback(t): + if self.callback: + data = self.read() + if data: + schedule(self.callback, data) + + self.rx.set_recv_callback(_irq_callback) + + def start_recv(self) -> None: + """Start receive data. + + UiFlow2 Code Block: + + |start_recv.png| + + MicroPython Code Block: + + .. code-block:: python + + unit_rf433r.start_recv() + """ + self.rx.start_recv() + + def stop_recv(self) -> None: + """Stop receive data. + + UiFlow2 Code Block: + + |stop_recv.png| + + MicroPython Code Block: + + .. code-block:: python + + unit_rf433r.stop_recv() + """ + self.rx.stop_recv() + + def read(self) -> bytes | None: + """Read data. + + :returns: receive data. + :rtype: bytes | None + + If no data is received, return None. + + UiFlow2 Code Block: + + |read.png| + + MicroPython Code Block: + + .. code-block:: python + + unit_rf433r.read() + """ + return self.rx.read() diff --git a/m5stack/libs/unit/rf433t.py b/m5stack/libs/unit/rf433t.py new file mode 100644 index 00000000..9364af81 --- /dev/null +++ b/m5stack/libs/unit/rf433t.py @@ -0,0 +1,45 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import rf433 + + +class RF433TUnit: + """Create an RF433TUnit object. + + :param list|tuple port: Tuple containing grove port pin numbers. + + UiFlow2 Code Block: + + |init.png| + + MicroPython Code Block: + + .. code-block:: python + + from unit import RF433TUnit + + unit_rf433t = RF433TUnit(port=(8, 9)) + """ + + def __init__(self, port: list | tuple = None): + self.tx = rf433.Tx(out_pin=port[1]) + + def send(self, data: bytes | bytearray) -> None: + """Send data. + + :param data: The data to send. + :type data: bytes | bytearray + + UiFlow2 Code Block: + + |send.png| + + MicroPython Code Block: + + .. code-block:: python + + unit_rf433t.send(data) + """ + self.tx.send(data) From 02d33c9d7ccc92502e369245a037832c2ebdd886 Mon Sep 17 00:00:00 2001 From: luoweiyuan Date: Thu, 3 Apr 2025 11:19:16 +0800 Subject: [PATCH 038/322] libs/module: Add support for Module GatewayH2. Signed-off-by: luoweiyuan --- .gitmodules | 4 + docs/en/module/gateway_h2.rst | 163 ++++++++ docs/en/module/index.rst | 1 + docs/en/refs/module.gateway_h2.ref | 23 ++ .../zh_CN/LC_MESSAGES/module/gateway_h2.po | 261 ++++++++++++ .../cores3_switch_endpoint_example.m5f2 | 1 + .../cores3_switch_endpoint_example.py | 129 ++++++ m5stack/Makefile | 1 + m5stack/cmodules/cmodules.cmake | 2 + .../esp_zigbee_host/micropython.cmake | 21 + .../esp_zigbee_host/py_esp_zigbee_host.c | 375 ++++++++++++++++++ m5stack/components/esp_zigbee_host | 1 + m5stack/libs/module/__init__.py | 1 + m5stack/libs/module/gateway_h2.py | 26 ++ m5stack/libs/module/manifest.py | 1 + 15 files changed, 1010 insertions(+) create mode 100644 docs/en/module/gateway_h2.rst create mode 100644 docs/en/refs/module.gateway_h2.ref create mode 100644 docs/locales/zh_CN/LC_MESSAGES/module/gateway_h2.po create mode 100644 examples/module/gateway_h2/cores3_switch_endpoint_example.m5f2 create mode 100644 examples/module/gateway_h2/cores3_switch_endpoint_example.py create mode 100644 m5stack/cmodules/esp_zigbee_host/micropython.cmake create mode 100644 m5stack/cmodules/esp_zigbee_host/py_esp_zigbee_host.c create mode 160000 m5stack/components/esp_zigbee_host create mode 100644 m5stack/libs/module/gateway_h2.py diff --git a/.gitmodules b/.gitmodules index 5ce935b5..eb2a10d1 100644 --- a/.gitmodules +++ b/.gitmodules @@ -28,3 +28,7 @@ [submodule "m5stack/components/epdiy"] path = m5stack/components/epdiy url = https://github.com/vroland/epdiy.git + +[submodule "m5stack/components/esp_zigbee_host"] + path = m5stack/components/esp_zigbee_host + url = https://github.com/hlym123/esp_zigbee_host.git diff --git a/docs/en/module/gateway_h2.rst b/docs/en/module/gateway_h2.rst new file mode 100644 index 00000000..d6705825 --- /dev/null +++ b/docs/en/module/gateway_h2.rst @@ -0,0 +1,163 @@ +GatewayH2 Module +============================ + +.. sku: M141 + +.. include:: ../refs/module.gateway_h2.ref + +This library is the driver for Module Gateway H2, and the module communicates via UART. + +Support the following products: + + |Module Gateway H2| + +.. note:: When using this module, you need to flash the NCP firmware to the module. For details, refer to the `ESP Zigbee NCP `_ documentation. + +UiFlow2 Example: +-------------------------- + +Switch Control +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. note:: To use this example, you need to flash this program onto an ESP32C6 or similar module as a light node device. For details, refer to `HA_on_off_light `_ + +Open the |cores3_switch_endpoint_example.m5f2| project in UiFlow2. + +The example demonstrates group control and targeted device operation for light nodes through SwitchEndpoint of Gateway H2 module. + +UiFlow2 Code Block: + + |cores3_switch_endpoint_example.png| + +Example output: + + None + +MicroPython Example: +-------------------------- + +Switch Control +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The example demonstrates group control and targeted device operation for light nodes through SwitchEndpoint of Gateway H2 module. + +MicroPython Code Block: + + .. literalinclude:: ../../../examples/module/gateway_h2/cores3_switch_endpoint_example.py + :language: python + :linenos: + +Example output: + + None + +**API** +-------------------------- + +GatewayH2Module +^^^^^^^^^^^^^^^^^^^^^^^^ + +.. class:: module.gateway_h2.GatewayH2Module + + Create an GatewayH2Module object. + + :param int id: UART id. + :param int tx: the UART TX pin. + :param int rx: the UART RX pin. + + UiFlow2 Code Block: + + |init.png| + + MicroPython Code Block: + + .. code-block:: python + + from module import GatewayH2Module + + module_gateway_h2 = GatewayH2Module(id = 1, tx = 10, rx = 17) + + .. method:: create_switch_endpoint() + + Create Switch Endpoint. + + :returns SwitchEndpoint: zigbee switch endpoint object. + :return type: SwitchEndpoint + + UiFlow2 Code Block: + + |init.png| + + MicroPython Code Block: + + .. code-block:: python + + h2_switch_endpoint = module_gateway_h2.create_switch_endpoint() + +.. class:: SwitchEndpoint + + Return by GatewayH2Module.create_switch_endpoint() + + .. method:: on([addr]) + + Turn on the light. + + :param addr: The device address (optional). + + - If called as ``on()``, turn on all devices. + - If called as ``on(addr)``, turn on special address devices. + + UiFlow2 Code Block: + + |on.png| + |all_on.png| + + MicroPython Code Block: + + .. code-block:: python + + h2_switch_endpoint.on(addr) + h2_switch_endpoint.on() + + .. method:: off([addr]) + + Turn off the light. + + :param addr: The device address (optional). + + - If called as ``off()``, turn off all devices. + - If called as ``off(addr)``, turn off special address devices. + + UiFlow2 Code Block: + + |off.png| + |all_off.png| + + MicroPython Code Block: + + .. code-block:: python + + h2_switch_endpoint.off(addr) + h2_switch_endpoint.off() + + .. method:: toggle([addr]) + + Toggle the light state. + + :param addr: The device address (optional). + + - If called as ``toggle()``, toggle all devices. + - If called as ``toggle(addr)``, toggle special address devices. + + UiFlow2 Code Block: + + |toggle.png| + |all_toggle.png| + + MicroPython Code Block: + + .. code-block:: python + + h2_switch_endpoint.toggle(addr) + h2_switch_endpoint.toggle() + diff --git a/docs/en/module/index.rst b/docs/en/module/index.rst index 51196c5a..6a10276a 100644 --- a/docs/en/module/index.rst +++ b/docs/en/module/index.rst @@ -14,6 +14,7 @@ Module ecg.rst encoder4_motor.rst fan.rst + gateway_h2.rst gnss.rst gps.rst gpsv2.rst diff --git a/docs/en/refs/module.gateway_h2.ref b/docs/en/refs/module.gateway_h2.ref new file mode 100644 index 00000000..2fcf6eea --- /dev/null +++ b/docs/en/refs/module.gateway_h2.ref @@ -0,0 +1,23 @@ +.. |Module Gateway H2| image:: https://m5stack-doc.oss-cn-shenzhen.aliyuncs.com/464/M141_01.webp + :target: https://docs.m5stack.com/zh_CN/module/Module%20Gateway%20H2 + :height: 200px + :width: 200px + +.. |init.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/gateway_h2/init.png +.. |on.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/gateway_h2/on.png +.. |all_on.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/gateway_h2/all_on.png +.. |off.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/gateway_h2/off.png +.. |all_off.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/gateway_h2/all_off.png +.. |toggle.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/gateway_h2/toggle.png +.. |all_toggle.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/gateway_h2/all_toggle.png +.. |cores3_switch_endpoint_example.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/gateway_h2/example.png + +.. |cores3_switch_endpoint_example.m5f2| raw:: html + + + cores3_switch_endpoint_example.m5f2 + + diff --git a/docs/locales/zh_CN/LC_MESSAGES/module/gateway_h2.po b/docs/locales/zh_CN/LC_MESSAGES/module/gateway_h2.po new file mode 100644 index 00000000..d9572b75 --- /dev/null +++ b/docs/locales/zh_CN/LC_MESSAGES/module/gateway_h2.po @@ -0,0 +1,261 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2016 - 2025 M5Stack Technology Co., Ltd +# This file is distributed under the same license as the UIFlow2 Programming +# Guide package. +# FIRST AUTHOR , 2025. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: UIFlow2 Programming Guide \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-04-03 10:55+0800\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: zh_CN\n" +"Language-Team: zh_CN \n" +"Plural-Forms: nplurals=1; plural=0;\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.16.0\n" + +#: ../../en/module/gateway_h2.rst:2 ../../en/module/gateway_h2.rst:58 +#: 6795b0c03a6349549cc0a19e2e231d82 6d2f389a0f11415ca559892bc73432b2 +msgid "GatewayH2Module" +msgstr "" + +#: ../../en/module/gateway_h2.rst:8 041bb1bed81b4d36a344534817a7399e +msgid "" +"This library is the driver for Module Gateway H2, and the module " +"communicates via UART." +msgstr "这个库是 Module Gateway H2 的驱动,该模块使用 UART 通信。" + +#: ../../en/module/gateway_h2.rst:10 0179fe0fad564981ad424b331e6d40ff +msgid "Support the following products:" +msgstr "支持以下产品:" + +#: ../../en/module/gateway_h2.rst:12 8303c52707f04ddbb85270d64609112a +msgid "|Module Gateway H2|" +msgstr "" + +#: ../../en/refs/module.gateway_h2.ref bca19ca6e4484df68ffca00b0e6f6ab3 +msgid "Module Gateway H2" +msgstr "" + +#: ../../en/module/gateway_h2.rst:14 db6fc6b6b861442eb78e8ec4c42304eb +msgid "" +"When using this module, you need to flash the NCP firmware to the module." +" For details, refer to the `ESP Zigbee NCP " +"`_" +" documentation." +msgstr "当使用此模块时,需要先给模块烧录 NCP 固件,详情请查看 " +"`ESP Zigbee NCP `_" + +#: ../../en/module/gateway_h2.rst:17 498bac24265a4bbb8162789e858cb8a8 +msgid "UiFlow2 Example:" +msgstr "UiFlow2 应用示例:" + +#: ../../en/module/gateway_h2.rst:20 ../../en/module/gateway_h2.rst:40 +#: 0073ce626526478abb81a8b1afab8513 9a1f54b291c5497c9db57e47a93b98ee +msgid "Switch Control" +msgstr "开关控制" + +#: ../../en/module/gateway_h2.rst:22 a268f346c8f34b178d9ce374d069b58f +msgid "" +"To use this example, you need to flash this program onto an ESP32C6 or " +"similar module as a light node device. For details, refer to " +"`HA_on_off_light `_" +msgstr "要使用此示例,您还需要 ESP32C6 或类似模块,作为灯节点设备。详情请查看 " +"`HA_on_off_light `_" + +#: ../../en/module/gateway_h2.rst:24 b66191d527ae4cf5ac715b166e5925ae +msgid "Open the |cores3_switch_endpoint_example.m5f2| project in UiFlow2." +msgstr "在 UiFlow2 上打开 |cores3_switch_endpoint_example.m5f2| 项目。" + +#: ../../en/module/gateway_h2.rst:26 ../../en/module/gateway_h2.rst:42 +#: 0c4a14dad07542b29cfc89b8cdd23906 7daff67e589a4c31b44b2633169779d3 +msgid "" +"The example demonstrates group control and targeted device operation for " +"light nodes through SwitchEndpoint of Gateway H2 module." +msgstr "示例程序演示通过 Gateway H2 模块的 SwitchEndpoint 对灯节点进开关控制。" + +#: ../../en/module/gateway_h2.rst:28 ../../en/module/gateway_h2.rst:68 +#: ../../en/module/gateway_h2.rst:87 ../../en/module/gateway_h2.rst:110 +#: ../../en/module/gateway_h2.rst:131 ../../en/module/gateway_h2.rst:152 +#: 776959c331ea44eb90bccd6419f613ec ae3e88dab1ca42b0a8341dbb2137256c +#: bc93b5556e3c409fb3b07030f29102ac ce25b5a427c44741956abd309e2369d8 +msgid "UiFlow2 Code Block:" +msgstr "UiFlow2 代码块:" + +#: ../../en/module/gateway_h2.rst:30 f67afed0a2444bb2b9be1de0c48ec1d7 +msgid "|cores3_switch_endpoint_example.png|" +msgstr "" + +#: ../../en/refs/module.gateway_h2.ref:13 59e23416323b4b2281d3cba1af4ef9dc +msgid "cores3_switch_endpoint_example.png" +msgstr "" + +#: ../../en/module/gateway_h2.rst:32 ../../en/module/gateway_h2.rst:50 +#: 838d61133cda48c98871899eb59671f9 9ede75f1d5324ed5a5697de10996472d +msgid "Example output:" +msgstr "示例输出:" + +#: ../../en/module/gateway_h2.rst:34 ../../en/module/gateway_h2.rst:52 +#: 2670111baae24bb780efb1a0215ae66e ace5f81630d4466087540fd9d39c88ee +msgid "None" +msgstr "无" + +#: ../../en/module/gateway_h2.rst:37 8792ab6af90b4d169c189e36210d9c55 +msgid "MicroPython Example:" +msgstr "MicroPython 应用示例:" + +#: ../../en/module/gateway_h2.rst:44 ../../en/module/gateway_h2.rst:72 +#: ../../en/module/gateway_h2.rst:91 ../../en/module/gateway_h2.rst:115 +#: ../../en/module/gateway_h2.rst:136 ../../en/module/gateway_h2.rst:157 +#: d0132ccd22034544b1b5b4801e818b12 e882ca30fdde4d03bbab151e35a58636 +#: f4fff747dcdb41739b03189f6bd7c40c +msgid "MicroPython Code Block:" +msgstr "MicroPython 代码块:" + +#: ../../en/module/gateway_h2.rst:55 4eb772729e244915a9dabedb60fef637 +msgid "**API**" +msgstr "API应用" + +#: ../../en/module/gateway_h2.rst:62 b1116c2d676544ad9b40ef96af9cf344 +msgid "Create an GatewayH2Module object." +msgstr "创建一个 GatewayH2Module 对象。" + +#: ../../en/module/gateway_h2.rst 6c767589cbab411daadd8a480fdf7340 +#: 748336fbd6d24c1bb024644f855b627d +msgid "Parameters" +msgstr "" + +#: ../../en/module/gateway_h2.rst:64 f83792df85cc4a839d53fc1bf8a549e8 +msgid "UART id." +msgstr "UART 端口号。" + +#: ../../en/module/gateway_h2.rst:65 fdcf149d52e84dd591e55d5e1671af93 +msgid "the UART TX pin." +msgstr "UART 发送引脚。" + +#: ../../en/module/gateway_h2.rst:66 022e1da314aa4e649a4267ba622aa5b3 +msgid "the UART RX pin." +msgstr "UART 接收引脚。" + +#: ../../en/module/gateway_h2.rst:70 ../../en/module/gateway_h2.rst:89 +#: b807c412ab84489888985f0b59061eb0 d131f0ceaa2f419ab9e6d1814e234049 +msgid "|init.png|" +msgstr "" + +#: ../../en/refs/module.gateway_h2.ref:6 3eb18a4d3a4c49648aa61b9efc66faa1 +#: fa246b563473471680fa53b5daf8ca90 +msgid "init.png" +msgstr "" + +#: ../../en/module/gateway_h2.rst:82 9f9d616ef9ac48b68e6c93c3ea9f2208 +msgid "Create Switch Endpoint." +msgstr "创建 Switch Endpoint。" + +#: ../../en/module/gateway_h2.rst 302656c90385422abd5be78e3c486386 +msgid "returns SwitchEndpoint" +msgstr "" + +#: ../../en/module/gateway_h2.rst:84 89d0f116cb344f838933212a9a727d49 +msgid "zigbee switch endpoint object." +msgstr "" + +#: ../../en/module/gateway_h2.rst 81c36522dc754c1888081f9fa3a751ad +msgid "return type" +msgstr "" + +#: ../../en/module/gateway_h2.rst:85 7528a12373be4cb0a2d1df2aaebe07ee +msgid "SwitchEndpoint" +msgstr "" + +#: ../../en/module/gateway_h2.rst:99 61668f3ff2a04c039cfbb71d16a0c1ea +msgid "Return by GatewayH2Module.create_switch_endpoint()" +msgstr "由 GatewayH2Module.create_switch_endpoint() 返回" + +#: ../../en/module/gateway_h2.rst:103 a07638d724264303b28870ad9da86189 +msgid "Turn on the light." +msgstr "开灯" + +#: ../../en/module/gateway_h2.rst:105 ../../en/module/gateway_h2.rst:126 +#: ../../en/module/gateway_h2.rst:147 b753ac510c2b4a0497801a450d87692f +msgid "The device address (optional)." +msgstr "设备地址(可选)" + +#: ../../en/module/gateway_h2.rst:107 3abe4963d9f140519f6af06d781b9b71 +msgid "If called as ``on()``, turn on all devices." +msgstr "调用 ``on()`` 打开所有设备。" + +#: ../../en/module/gateway_h2.rst:108 e65586ae0dfe41cc9667583b87a6bdfb +msgid "If called as ``on(addr)``, turn on special address devices." +msgstr "调用 ``on(addr)`` 打开地址为 addr 的设备。" + +#: ../../en/module/gateway_h2.rst:112 60e593971dd54420bc0724fea016e73f +msgid "|on.png| |all_on.png|" +msgstr "" + +#: ../../en/refs/module.gateway_h2.ref:7 fa246b563473471680fa53b5daf8ca90 +msgid "on.png" +msgstr "" + +#: ../../en/refs/module.gateway_h2.ref:8 1c528b2c6fa74b61bbcba4d68aef2968 +msgid "all_on.png" +msgstr "" + +#: ../../en/module/gateway_h2.rst:124 b40f46a37cd54040a14c355c20e09ccf +msgid "Turn off the light." +msgstr "关灯" + +#: ../../en/module/gateway_h2.rst:128 723eac96f2044545a1b5f237642e8d1f +msgid "If called as ``off()``, turn off all devices." +msgstr "调用 ``off()`` 关闭所有设备。" + +#: ../../en/module/gateway_h2.rst:129 49fbbc6856774e88b6e560ca05cebfde +msgid "If called as ``off(addr)``, turn off special address devices." +msgstr "调用 ``off(addr)`` 关闭地址为 addr 的设备。" + +#: ../../en/module/gateway_h2.rst:133 da56af9bdcbc4364ab079a0968a66343 +msgid "|off.png| |all_off.png|" +msgstr "" + +#: ../../en/refs/module.gateway_h2.ref:9 f03c0d5c0c644e83970bb598f8a9613e +msgid "off.png" +msgstr "" + +#: ../../en/refs/module.gateway_h2.ref:10 68fa471c6d6348978131e0bce193fc5e +msgid "all_off.png" +msgstr "" + +#: ../../en/module/gateway_h2.rst:145 fe9674650c6d4fb5b8b3e5c76dc1fb57 +msgid "Toggle the light state." +msgstr "翻转灯状态" + +#: ../../en/module/gateway_h2.rst:149 1a10e2918246434b96abb8f7fadb0951 +msgid "If called as ``toggle()``, toggle all devices." +msgstr "调用 ``toggle()`` 翻转所有设备的状态。" + +#: ../../en/module/gateway_h2.rst:150 9d323f8ee3ec40eb97a958bcfc8a9779 +msgid "If called as ``toggle(addr)``, toggle special address devices." +msgstr "调用 ``toggle(addr)`` 翻转地址为 addr 的设备的状态。" + +#: ../../en/module/gateway_h2.rst:154 e0e017a948d74af1b2790a0a0e452ba8 +msgid "|toggle.png| |all_toggle.png|" +msgstr "" + +#: ../../en/refs/module.gateway_h2.ref:11 4d1a3f5113894862a205ab05e2949fb3 +msgid "toggle.png" +msgstr "" + +#: ../../en/refs/module.gateway_h2.ref:12 d47c6caf80b34dee97e256d3f56bd022 +msgid "all_toggle.png" +msgstr "" + +#~ msgid "|create_switch_endpoint.png|" +#~ msgstr "" + diff --git a/examples/module/gateway_h2/cores3_switch_endpoint_example.m5f2 b/examples/module/gateway_h2/cores3_switch_endpoint_example.m5f2 new file mode 100644 index 00000000..f43693f9 --- /dev/null +++ b/examples/module/gateway_h2/cores3_switch_endpoint_example.m5f2 @@ -0,0 +1 @@ +{"version":"V2.0","versionNumber":"V2.2.4","type":"cores3","components":[{"name":"screen","type":"screen","layer":0,"screenId":"builtin","screenName":"","id":"__cores3_screen","createTime":1743470222209,"x":0,"y":0,"width":320,"height":240,"backgroundColor":"#222222","size":0,"isSelected":true},{"name":"title0","type":"title","layer":1,"screenId":"builtin","screenName":"","id":"cqamGm$P1*KR&0hK","createTime":1743471513154,"x":0,"y":0,"color":"#ffffff","backgroundColor":"#0000FF","text":"Switch Endpoint Example","textOffset":3,"font":"Widgets.FONTS.DejaVu18","isSelected":false},{"name":"label0","type":"label","layer":2,"screenId":"builtin","screenName":"","id":"z#%mj5FdxT0Hg+4i","createTime":1743471531542,"x":2,"y":50,"color":"#ffffff","backgroundColor":"#222222","text":"press the power button toggle","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false},{"name":"label1","type":"label","layer":3,"screenId":"builtin","screenName":"","id":"x9UWiMh8iDRwr@Y7","createTime":1743471564823,"x":2,"y":26,"color":"#ffffff","backgroundColor":"#222222","text":"if has device connect","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false},{"name":"label2","type":"label","layer":4,"screenId":"builtin","screenName":"","id":"yrY-ASm00I7MWq0`","createTime":1743471635234,"x":2,"y":90,"color":"#ffffff","backgroundColor":"#222222","text":"connect device: ","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false},{"name":"label_addr","type":"label","layer":5,"screenId":"builtin","screenName":"","id":"qeaS_L2ai7%Mus^f","createTime":1743473256801,"x":5,"y":115,"color":"#00ff00","backgroundColor":"#222222","text":"None","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false}],"resources":[{"hardware":["hardware_button","hardware_pin_button","imu","speaker","touch","als","mic"]},{"module":["module_gps","module_ecg","module_gateway_h2"]}],"units":[],"hats":[],"bases":[],"i2cs":[],"blockly":"device_addrdevice_countdevice_listtruemodule_h2_021017device_count0device_listtruemodule_h2_0device_addrhello M5device_addrEQFIRSTdevice_listdevice_addr0INSERTLASTdevice_listdevice_addrdevice_countADD1device_count1label_addrLabelnew device addr: device_addrBtnPWRWAS_CLICKEDdevice_listmodule_h2_0","screen":[{"simulationName":"Built-in","type":"builtin","width":320,"height":240,"scale":0.78,"screenName":"","blockId":"","screenColorType":0,"id":"builtin","createTime":1743470222209}],"logicWhenNum":0,"customList":[]} \ No newline at end of file diff --git a/examples/module/gateway_h2/cores3_switch_endpoint_example.py b/examples/module/gateway_h2/cores3_switch_endpoint_example.py new file mode 100644 index 00000000..5f9d1fe7 --- /dev/null +++ b/examples/module/gateway_h2/cores3_switch_endpoint_example.py @@ -0,0 +1,129 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import os, sys, io +import M5 +from M5 import * +from module import GatewayH2Module + + +title0 = None +label0 = None +label1 = None +label2 = None +label_addr = None +module_h2_0 = None +module_h2_0_ep = None +device_addr = None +device_count = None +device_list = None + + +def first_index(my_list, elem): + try: + index = my_list.index(elem) + 1 + except: + index = 0 + return index + + +def module_h2_0_ep_bind_event(addr): + global \ + title0, \ + label0, \ + label1, \ + label2, \ + label_addr, \ + module_h2_0, \ + module_h2_0_ep, \ + device_addr, \ + device_count, \ + device_list + device_addr = addr + print(device_addr) + if first_index(device_list, device_addr) == 0: + device_list.append(device_addr) + device_count = device_count + 1 + label_addr.setText(str((str("new device addr: ") + str(device_addr)))) + + +def btn_pwr_was_clicked_event(state): + global \ + title0, \ + label0, \ + label1, \ + label2, \ + label_addr, \ + module_h2_0, \ + module_h2_0_ep, \ + device_addr, \ + device_count, \ + device_list + if not not len(device_list): + module_h2_0_ep.toggle() + + +def setup(): + global \ + title0, \ + label0, \ + label1, \ + label2, \ + label_addr, \ + module_h2_0, \ + module_h2_0_ep, \ + device_addr, \ + device_count, \ + device_list + + M5.begin() + Widgets.fillScreen(0x222222) + title0 = Widgets.Title( + "Switch Endpoint Example", 3, 0xFFFFFF, 0x0000FF, Widgets.FONTS.DejaVu18 + ) + label0 = Widgets.Label( + "press the power button toggle", 2, 50, 1.0, 0xFFFFFF, 0x222222, Widgets.FONTS.DejaVu18 + ) + label1 = Widgets.Label( + "if has device connect", 2, 26, 1.0, 0xFFFFFF, 0x222222, Widgets.FONTS.DejaVu18 + ) + label2 = Widgets.Label( + "connect device: ", 2, 90, 1.0, 0xFFFFFF, 0x222222, Widgets.FONTS.DejaVu18 + ) + label_addr = Widgets.Label("None", 5, 115, 1.0, 0x00FF00, 0x222222, Widgets.FONTS.DejaVu18) + BtnPWR.setCallback(type=BtnPWR.CB_TYPE.WAS_CLICKED, cb=btn_pwr_was_clicked_event) + module_h2_0 = GatewayH2Module(2, 17, 10) + module_h2_0_ep = module_h2_0.create_switch_ep() + module_h2_0_ep.set_bind_callback(module_h2_0_ep_bind_event) + device_count = 0 + device_list = [] + + +def loop(): + global \ + title0, \ + label0, \ + label1, \ + label2, \ + label_addr, \ + module_h2_0, \ + module_h2_0_ep, \ + device_addr, \ + device_count, \ + device_list + M5.update() + + +if __name__ == "__main__": + try: + setup() + while True: + loop() + except (Exception, KeyboardInterrupt) as e: + try: + from utility import print_error_msg + + print_error_msg(e) + except ImportError: + print("please update to latest firmware") diff --git a/m5stack/Makefile b/m5stack/Makefile index ed848adb..1c772f74 100644 --- a/m5stack/Makefile +++ b/m5stack/Makefile @@ -277,6 +277,7 @@ submodules: git submodule update --init ./components/esp32-camera git submodule update --init ./components/esp_dl git submodule update --init ./components/esp-code-scanner + git submodule update --init ./components/esp_zigbee_host git submodule update --init ./components/epdiy git submodule update --init ./components/M5Unified/M5GFX git submodule update --init ./components/M5Unified/M5Unified diff --git a/m5stack/cmodules/cmodules.cmake b/m5stack/cmodules/cmodules.cmake index 3a4380c6..ad4ea4fb 100644 --- a/m5stack/cmodules/cmodules.cmake +++ b/m5stack/cmodules/cmodules.cmake @@ -25,6 +25,8 @@ include(${CMAKE_CURRENT_LIST_DIR}/m5unified/m5unified.cmake) # add rf433 module include(${CMAKE_CURRENT_LIST_DIR}/rf433/micropython.cmake) +# add esp_zigbee_host module +include(${CMAKE_CURRENT_LIST_DIR}/esp_zigbee_host/micropython.cmake) if(ADF_MODULE_ENABLE) include(${CMAKE_CURRENT_LIST_DIR}/adf_module/micropython.cmake) diff --git a/m5stack/cmodules/esp_zigbee_host/micropython.cmake b/m5stack/cmodules/esp_zigbee_host/micropython.cmake new file mode 100644 index 00000000..9d291d6d --- /dev/null +++ b/m5stack/cmodules/esp_zigbee_host/micropython.cmake @@ -0,0 +1,21 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +# Create an INTERFACE library for our C module. +add_library(moudle_esp_zigbee_host INTERFACE) + +# Add our source files to the lib +target_sources(moudle_esp_zigbee_host INTERFACE + ${CMAKE_CURRENT_LIST_DIR}/py_esp_zigbee_host.c +) + +# Add the current directory as an include directory. +target_include_directories(moudle_esp_zigbee_host INTERFACE + ${CMAKE_CURRENT_LIST_DIR} + # esp_zigbee_host + ${CMAKE_CURRENT_LIST_DIR}/../../components/esp_zigbee_host/include +) + +# Link our INTERFACE library to the usermod target. +target_link_libraries(usermod INTERFACE moudle_esp_zigbee_host) diff --git a/m5stack/cmodules/esp_zigbee_host/py_esp_zigbee_host.c b/m5stack/cmodules/esp_zigbee_host/py_esp_zigbee_host.c new file mode 100644 index 00000000..e6193ad1 --- /dev/null +++ b/m5stack/cmodules/esp_zigbee_host/py_esp_zigbee_host.c @@ -0,0 +1,375 @@ +/* + * SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD + * + * SPDX-License-Identifier: MIT + */ + +#include +#include +#include +#include "py/nlr.h" +#include "py/obj.h" +#include "py/objlist.h" +#include "py/objstr.h" +#include "py/objtuple.h" +#include "py/objtype.h" +#include "py/runtime.h" +#include "py/mphal.h" + +#include "esp_check.h" +#include "string.h" +#include "nvs_flash.h" +#include "esp_log.h" + +#include "ha/esp_zigbee_ha_standard.h" +#include "esp_zigbee_core.h" + +static const char *TAG = "ESP_ZB_HOST"; + +/* Zigbee configuration */ +#define MAX_CHILDREN 10 /* the max amount of connected devices */ +#define INSTALLCODE_POLICY_ENABLE false /* enable the install code policy for security */ +#define HA_ONOFF_SWITCH_ENDPOINT 1 /* esp light switch device endpoint */ +#define HA_ESP_LIGHT_ENDPOINT 10 /* esp light bulb device endpoint, used to process light controlling commands */ +#define ESP_ZB_PRIMARY_CHANNEL_MASK (1l << 13) /* Zigbee primary channel mask use in the example */ + +typedef struct light_bulb_device_params_s { + esp_zb_ieee_addr_t ieee_addr; + uint8_t endpoint; + uint16_t short_addr; +} light_bulb_device_params_t; + +mp_obj_t g_bind_cb; + +static void bdb_start_top_level_commissioning_cb(uint8_t mode_mask) +{ + ESP_RETURN_ON_FALSE(esp_zb_bdb_start_top_level_commissioning(mode_mask) == ESP_OK, , TAG, + "Failed to start Zigbee bdb commissioning"); +} + +// object: devinfo +typedef struct _py_devinfo_obj { + mp_obj_base_t base; + mp_obj_t addr; + mp_obj_t endpoint; +} py_devinfo_obj_t; + +mp_obj_t mp_devinfo_addr(mp_obj_t self_in) +{ + return ((py_devinfo_obj_t *)self_in)->addr; +} +static MP_DEFINE_CONST_FUN_OBJ_1(mp_devinfo_addr_obj, mp_devinfo_addr); + +mp_obj_t mp_devinfo_endpoint(mp_obj_t self_in) +{ + return ((py_devinfo_obj_t *)self_in)->endpoint; +} +static MP_DEFINE_CONST_FUN_OBJ_1(mp_devinfo_endpoint_obj, mp_devinfo_endpoint); + +static const mp_rom_map_elem_t mp_devinfo_locals_dict_table[] = { + {MP_ROM_QSTR(MP_QSTR_addr), MP_ROM_PTR(&mp_devinfo_addr_obj)}, + {MP_ROM_QSTR(MP_QSTR_endpoint), MP_ROM_PTR(&mp_devinfo_endpoint_obj)}, +}; +static MP_DEFINE_CONST_DICT(mp_devinfo_locals_dict, mp_devinfo_locals_dict_table); + +static mp_obj_t py_devinfo_make_new(mp_obj_t addr, mp_obj_t endpoint); + +MP_DEFINE_CONST_OBJ_TYPE(py_devinfo_type, MP_QSTR_devinfo, MP_TYPE_FLAG_NONE, make_new, py_devinfo_make_new, + locals_dict, &mp_devinfo_locals_dict); + +static mp_obj_t py_devinfo_make_new(mp_obj_t addr, mp_obj_t endpoint) +{ + py_devinfo_obj_t *self = m_new_obj(py_devinfo_obj_t); + self->base.type = &py_devinfo_type; + self->addr = addr; + self->endpoint = endpoint; + return MP_OBJ_FROM_PTR(self); +} + +static void bind_cb(esp_zb_zdp_status_t zdo_status, void *user_ctx) +{ + if (zdo_status == ESP_ZB_ZDP_STATUS_SUCCESS) { + if (user_ctx) { + light_bulb_device_params_t *light = (light_bulb_device_params_t *)user_ctx; + uint16_t addr = light->short_addr; + free(light); + mp_sched_schedule(g_bind_cb, mp_obj_new_int(addr)); + } + } +} + +static void user_find_cb(esp_zb_zdp_status_t zdo_status, uint16_t addr, uint8_t endpoint, void *user_ctx) +{ + if (zdo_status == ESP_ZB_ZDP_STATUS_SUCCESS) { + esp_zb_zdo_bind_req_param_t bind_req; + light_bulb_device_params_t *light = (light_bulb_device_params_t *)malloc(sizeof(light_bulb_device_params_t)); + light->endpoint = endpoint; + light->short_addr = addr; + esp_zb_ieee_address_by_short(light->short_addr, light->ieee_addr); + esp_zb_get_long_address(bind_req.src_address); + bind_req.src_endp = HA_ONOFF_SWITCH_ENDPOINT; + bind_req.cluster_id = ESP_ZB_ZCL_CLUSTER_ID_ON_OFF; + bind_req.dst_addr_mode = ESP_ZB_ZDO_BIND_DST_ADDR_MODE_64_BIT_EXTENDED; + memcpy(bind_req.dst_address_u.addr_long, light->ieee_addr, sizeof(esp_zb_ieee_addr_t)); + bind_req.dst_endp = endpoint; + bind_req.req_dst_addr = esp_zb_get_short_address(); /* TODO: Send bind request to self */ + esp_zb_zdo_device_bind_req(&bind_req, bind_cb, (void *)light); + } +} + +void esp_zb_app_signal_handler(esp_zb_app_signal_t *signal_struct) +{ + uint32_t *p_sg_p = signal_struct->p_app_signal; + esp_err_t err_status = signal_struct->esp_err_status; + esp_zb_app_signal_type_t sig_type = *p_sg_p; + esp_zb_zdo_signal_device_annce_params_t *dev_annce_params = NULL; + switch (sig_type) { + case ESP_ZB_ZDO_SIGNAL_SKIP_STARTUP: + ESP_LOGI(TAG, "Initialize Zigbee stack"); + esp_zb_bdb_start_top_level_commissioning( + ESP_ZB_BDB_MODE_INITIALIZATION); // 启动 Zigbee BDB 顶层的网络配置或设备行为模式。 + break; + case ESP_ZB_BDB_SIGNAL_DEVICE_FIRST_START: + case ESP_ZB_BDB_SIGNAL_DEVICE_REBOOT: + if (err_status == ESP_OK) { + ESP_LOGI(TAG, "Start network formation"); + esp_zb_bdb_start_top_level_commissioning(ESP_ZB_BDB_MODE_NETWORK_FORMATION); + } else { + ESP_LOGW(TAG, "%s failed with status: %s, retrying", esp_zb_zdo_signal_to_string(sig_type), + esp_err_to_name(err_status)); + esp_zb_scheduler_alarm((esp_zb_callback_t)bdb_start_top_level_commissioning_cb, + ESP_ZB_BDB_MODE_INITIALIZATION, 1000); + } + break; + case ESP_ZB_BDB_SIGNAL_FORMATION: // 设备尝试创建 Zigbee 网络,并完成网络形成的过程后,协议栈发出的信号。 + if (err_status == ESP_OK) { + esp_zb_ieee_addr_t extended_pan_id; + esp_zb_get_extended_pan_id(extended_pan_id); + ESP_LOGI(TAG, + "Formed network successfully (Extended PAN ID: %02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x, PAN " + "ID: 0x%04hx, Channel:%d, Short Address: 0x%04hx)", + extended_pan_id[7], extended_pan_id[6], extended_pan_id[5], extended_pan_id[4], + extended_pan_id[3], extended_pan_id[2], extended_pan_id[1], extended_pan_id[0], + esp_zb_get_pan_id(), esp_zb_get_current_channel(), esp_zb_get_short_address()); + esp_zb_bdb_start_top_level_commissioning(ESP_ZB_BDB_MODE_NETWORK_STEERING); + } else { + ESP_LOGI(TAG, "Restart network formation (status: %s)", esp_err_to_name(err_status)); + esp_zb_scheduler_alarm((esp_zb_callback_t)bdb_start_top_level_commissioning_cb, + ESP_ZB_BDB_MODE_NETWORK_FORMATION, 1000); + } + break; + case ESP_ZB_BDB_SIGNAL_STEERING: + if (err_status == ESP_OK) { + ESP_LOGI(TAG, "Network steering started"); + } + break; + case ESP_ZB_ZDO_SIGNAL_DEVICE_ANNCE: // 处理新设备加入网络的情况,并查找设备。 + dev_annce_params = (esp_zb_zdo_signal_device_annce_params_t *)esp_zb_app_signal_get_params(p_sg_p); + ESP_LOGI(TAG, "New device commissioned or rejoined (short: 0x%04hx)", dev_annce_params->device_short_addr); + esp_zb_zdo_match_desc_req_param_t cmd_req; + cmd_req.dst_nwk_addr = dev_annce_params->device_short_addr; + cmd_req.addr_of_interest = dev_annce_params->device_short_addr; + esp_zb_zdo_find_on_off_light(&cmd_req, user_find_cb, NULL); + break; + case ESP_ZB_NWK_SIGNAL_PERMIT_JOIN_STATUS: + if (err_status == ESP_OK) { + if (*(uint8_t *)esp_zb_app_signal_get_params(p_sg_p)) { + ESP_LOGI(TAG, "Network(0x%04hx) is open for %d seconds", esp_zb_get_pan_id(), + *(uint8_t *)esp_zb_app_signal_get_params(p_sg_p)); + } else { + ESP_LOGW(TAG, "Network(0x%04hx) closed, devices joining not allowed.", esp_zb_get_pan_id()); + } + } + break; + default: + ESP_LOGI(TAG, "ZDO signal: %s (0x%x), status: %s", esp_zb_zdo_signal_to_string(sig_type), sig_type, + esp_err_to_name(err_status)); + break; + } +} + +static void esp_zb_task(void *pvParameters) +{ + esp_zb_cfg_t zb_nwk_cfg = { + .esp_zb_role = ESP_ZB_DEVICE_TYPE_COORDINATOR, + .install_code_policy = INSTALLCODE_POLICY_ENABLE, + .nwk_cfg.zczr_cfg = + { + .max_children = MAX_CHILDREN, + }, + }; + esp_zb_init(&zb_nwk_cfg); + + esp_zb_on_off_switch_cfg_t switch_cfg = ESP_ZB_DEFAULT_ON_OFF_SWITCH_CONFIG(); + esp_zb_ep_list_t *esp_zb_on_off_switch_ep = esp_zb_on_off_switch_ep_create(HA_ONOFF_SWITCH_ENDPOINT, &switch_cfg); + + esp_zb_device_register(esp_zb_on_off_switch_ep); + esp_zb_set_primary_network_channel_set(ESP_ZB_PRIMARY_CHANNEL_MASK); + ESP_ERROR_CHECK(esp_zb_start(false)); + esp_zb_stack_main_loop(); +} + +// ================================================================================================= +// class: SwitchEndpoint +typedef struct _switch_endpoint_obj_t { + mp_obj_base_t base; + int uart_id; + int uart_tx; + int uart_rx; +} switch_endpoint_obj_t; + +extern const mp_obj_type_t mp_switch_endpoint_type; +static bool is_initialized; + +static mp_obj_t switch_endpoint_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) +{ + switch_endpoint_obj_t *self = mp_obj_malloc_with_finaliser(switch_endpoint_obj_t, &mp_switch_endpoint_type); + + static const mp_arg_t allowed_args[] = { + {MP_QSTR_id, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL}}, + {MP_QSTR_tx, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL}}, + {MP_QSTR_rx, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL}}, + }; + mp_arg_val_t args_vals[MP_ARRAY_SIZE(allowed_args)]; + mp_map_t kw_args; + mp_map_init_fixed_table(&kw_args, n_kw, args + n_args); + mp_arg_parse_all(n_args, args + 1, &kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args_vals); + + if (args_vals[0].u_obj != MP_OBJ_NULL) { + self->uart_id = mp_obj_get_int(args_vals[0].u_obj); + } + if (args_vals[1].u_obj != MP_OBJ_NULL) { + self->uart_tx = mp_obj_get_int(args_vals[1].u_obj); + } + if (args_vals[2].u_obj != MP_OBJ_NULL) { + self->uart_rx = mp_obj_get_int(args_vals[2].u_obj); + } + + host_bus_init(self->uart_id, self->uart_tx, self->uart_rx); + if (!is_initialized) { + esp_zb_platform_config_t config = { + .radio_config = + { + .radio_mode = RADIO_MODE_UART_NCP, + }, + .host_config = + { + .host_mode = HOST_CONNECTION_MODE_UART, + }, + }; + ESP_ERROR_CHECK(esp_zb_platform_config(&config)); + xTaskCreate(esp_zb_task, "zigbee_main", 4096, NULL, 5, NULL); + if (self->uart_id == 2) { // FIXME: (That's testing for you) + vTaskDelay(pdMS_TO_TICKS(200)); + host_bus_deinit(); + vTaskDelay(pdMS_TO_TICKS(20)); + host_bus_init(self->uart_id, self->uart_tx, self->uart_rx); + ESP_ERROR_CHECK(esp_zb_platform_config(&config)); + xTaskCreate(esp_zb_task, "zigbee_main", 4096, NULL, 5, NULL); + } + is_initialized = true; + } + + return MP_OBJ_FROM_PTR(self); +} + +static mp_obj_t switch_endpoint_set_bind_cb(mp_obj_t self_in, mp_obj_t cb) +{ + if (cb == mp_const_none || mp_obj_is_callable(cb)) { + g_bind_cb = cb; + } else { + mp_raise_ValueError(MP_ERROR_TEXT("callback must be callable or None")); + } + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_2(switch_endpoint_set_bind_cb_obj, switch_endpoint_set_bind_cb); + +static mp_obj_t switch_endpoint_on(size_t n_args, const mp_obj_t *args) +{ + switch_endpoint_obj_t *self = MP_OBJ_TO_PTR(args[0]); + esp_zb_zcl_on_off_cmd_t cmd_req; + + cmd_req.zcl_basic_cmd.src_endpoint = HA_ONOFF_SWITCH_ENDPOINT; + cmd_req.on_off_cmd_id = ESP_ZB_ZCL_CMD_ON_OFF_ON_ID; + if (n_args > 1) { + cmd_req.address_mode = ESP_ZB_APS_ADDR_MODE_16_ENDP_PRESENT; + cmd_req.zcl_basic_cmd.dst_addr_u.addr_short = mp_obj_get_int(args[1]); + cmd_req.zcl_basic_cmd.dst_endpoint = 10; + } else { + cmd_req.address_mode = ESP_ZB_APS_ADDR_MODE_DST_ADDR_ENDP_NOT_PRESENT; + } + + esp_zb_zcl_on_off_cmd_req(&cmd_req); + + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(switch_endpoint_on_obj, 1, 2, switch_endpoint_on); + +static mp_obj_t switch_endpoint_off(size_t n_args, const mp_obj_t *args) +{ + switch_endpoint_obj_t *self = MP_OBJ_TO_PTR(args[0]); + esp_zb_zcl_on_off_cmd_t cmd_req; + + cmd_req.zcl_basic_cmd.src_endpoint = HA_ONOFF_SWITCH_ENDPOINT; + cmd_req.on_off_cmd_id = ESP_ZB_ZCL_CMD_ON_OFF_OFF_ID; + if (n_args > 1) { + cmd_req.address_mode = ESP_ZB_APS_ADDR_MODE_16_ENDP_PRESENT; + cmd_req.zcl_basic_cmd.dst_addr_u.addr_short = mp_obj_get_int(args[1]); + cmd_req.zcl_basic_cmd.dst_endpoint = 10; + } else { + cmd_req.address_mode = ESP_ZB_APS_ADDR_MODE_DST_ADDR_ENDP_NOT_PRESENT; + } + + esp_zb_zcl_on_off_cmd_req(&cmd_req); + + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(switch_endpoint_off_obj, 1, 2, switch_endpoint_off); + +static mp_obj_t switch_endpoint_toggle(size_t n_args, const mp_obj_t *args) +{ + switch_endpoint_obj_t *self = MP_OBJ_TO_PTR(args[0]); + esp_zb_zcl_on_off_cmd_t cmd_req; + + cmd_req.zcl_basic_cmd.src_endpoint = HA_ONOFF_SWITCH_ENDPOINT; + cmd_req.on_off_cmd_id = ESP_ZB_ZCL_CMD_ON_OFF_TOGGLE_ID; + if (n_args > 1) { + cmd_req.address_mode = ESP_ZB_APS_ADDR_MODE_16_ENDP_PRESENT; + cmd_req.zcl_basic_cmd.dst_addr_u.addr_short = mp_obj_get_int(args[1]); + cmd_req.zcl_basic_cmd.dst_endpoint = 10; + } else { + cmd_req.address_mode = ESP_ZB_APS_ADDR_MODE_DST_ADDR_ENDP_NOT_PRESENT; + } + + esp_zb_zcl_on_off_cmd_req(&cmd_req); + + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(switch_endpoint_toggle_obj, 1, 2, switch_endpoint_toggle); + +static const mp_rom_map_elem_t switch_endpoint_locals_dict_table[] = { + // Methods + {MP_ROM_QSTR(MP_QSTR_set_bind_callback), MP_ROM_PTR(&switch_endpoint_set_bind_cb_obj)}, + {MP_ROM_QSTR(MP_QSTR_on), MP_ROM_PTR(&switch_endpoint_on_obj)}, + {MP_ROM_QSTR(MP_QSTR_off), MP_ROM_PTR(&switch_endpoint_off_obj)}, + {MP_ROM_QSTR(MP_QSTR_toggle), MP_ROM_PTR(&switch_endpoint_toggle_obj)}, +}; +MP_DEFINE_CONST_DICT(switch_endpoint_locals_dict, switch_endpoint_locals_dict_table); + +MP_DEFINE_CONST_OBJ_TYPE(mp_switch_endpoint_type, MP_QSTR_SwitchEndpoint, MP_TYPE_FLAG_NONE, make_new, + switch_endpoint_make_new, locals_dict, &switch_endpoint_locals_dict); + +// ================================================================================================= +// module: esp_zigbee_host +static const mp_rom_map_elem_t esp_zigbee_host_globals_table[] = { + {MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_esp_zigbee_host)}, + {MP_ROM_QSTR(MP_QSTR_SwitchEndpoint), MP_ROM_PTR(&mp_switch_endpoint_type)}, +}; +static MP_DEFINE_CONST_DICT(esp_zigbee_host_globals, esp_zigbee_host_globals_table); + +const mp_obj_module_t module_esp_zigbee_host = { + .base = {&mp_type_module}, + .globals = (mp_obj_dict_t *)&esp_zigbee_host_globals, +}; + +MP_REGISTER_MODULE(MP_QSTR_esp_zigbee_host, module_esp_zigbee_host); diff --git a/m5stack/components/esp_zigbee_host b/m5stack/components/esp_zigbee_host new file mode 160000 index 00000000..52dda1ff --- /dev/null +++ b/m5stack/components/esp_zigbee_host @@ -0,0 +1 @@ +Subproject commit 52dda1ffd9a543af94f32cbd5de17f42620259ee diff --git a/m5stack/libs/module/__init__.py b/m5stack/libs/module/__init__.py index a4933348..d241ad22 100644 --- a/m5stack/libs/module/__init__.py +++ b/m5stack/libs/module/__init__.py @@ -16,6 +16,7 @@ "ECGModule": "ecg", "Encoder4MotorModule": "encoder4_motor", "FanModule": "fan", + "GatewayH2Module": "gateway_h2", "GNSSModule": "gnss", "GPSModule": "gps", "GPSV2Module": "gpsv2", diff --git a/m5stack/libs/module/gateway_h2.py b/m5stack/libs/module/gateway_h2.py new file mode 100644 index 00000000..3b808e87 --- /dev/null +++ b/m5stack/libs/module/gateway_h2.py @@ -0,0 +1,26 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import esp_zigbee_host +from esp_zigbee_host import SwitchEndpoint + + +class GatewayH2Module: + """Create GatewayH2Module object. + + :param int id: UART id. range:1~2 + :param int rx: the UART RX pin. + :param int tx: the UART TX pin. + + """ + + def __init__(self, id: int = 1, rx: int = 17, tx: int = 10, boot: int = 18, en: int = 7): + self.id = id + self.tx = rx # 交叉 + self.rx = tx + self.boot = boot + self.en = en + + def create_switch_ep(self): + return SwitchEndpoint(id=self.id, tx=self.tx, rx=self.rx) diff --git a/m5stack/libs/module/manifest.py b/m5stack/libs/module/manifest.py index c4ad592c..55e52414 100644 --- a/m5stack/libs/module/manifest.py +++ b/m5stack/libs/module/manifest.py @@ -15,6 +15,7 @@ "ecg.py", "encoder4_motor.py", "fan.py", + "gateway_h2.py", "gnss.py", "gps.py", "gpsv2.py", From ddf668abf5ba5710fc9eb5a1642ebfb1e0701223 Mon Sep 17 00:00:00 2001 From: lbuque Date: Thu, 3 Apr 2025 11:53:12 +0800 Subject: [PATCH 039/322] docs: Add tcp/udp docs. Signed-off-by: lbuque --- docs/en/refs/software.tcp.client.ref | 22 ++ docs/en/refs/software.tcp.server.ref | 27 ++ docs/en/refs/software.udp.client.ref | 19 + docs/en/refs/software.udp.server.ref | 21 + docs/en/software/index.rst | 2 + docs/en/software/tcp.client.rst | 242 ++++++++++++ docs/en/software/tcp.rst | 19 + docs/en/software/tcp.server.rst | 276 +++++++++++++ docs/en/software/udp.client.rst | 194 ++++++++++ docs/en/software/udp.rst | 17 + docs/en/software/udp.server.rst | 240 ++++++++++++ .../zh_CN/LC_MESSAGES/software/tcp.client.po | 309 +++++++++++++++ .../locales/zh_CN/LC_MESSAGES/software/tcp.po | 43 +++ .../zh_CN/LC_MESSAGES/software/tcp.server.po | 362 ++++++++++++++++++ .../zh_CN/LC_MESSAGES/software/udp.client.po | 245 ++++++++++++ .../locales/zh_CN/LC_MESSAGES/software/udp.po | 40 ++ .../zh_CN/LC_MESSAGES/software/udp.server.po | 290 ++++++++++++++ .../tcp/cores3_tcp_client_example.m5f2 | 1 + .../softwave/tcp/cores3_tcp_client_example.py | 114 ++++++ .../tcp/cores3_tcp_server_example.m5f2 | 1 + .../softwave/tcp/cores3_tcp_server_example.py | 120 ++++++ .../udp/cores3_udp_client_example.m5f2 | 1 + .../softwave/udp/cores3_udp_client_example.py | 112 ++++++ .../udp/cores3_udp_server_example.m5f2 | 1 + .../softwave/udp/cores3_udp_server_example.py | 105 +++++ 25 files changed, 2823 insertions(+) create mode 100644 docs/en/refs/software.tcp.client.ref create mode 100644 docs/en/refs/software.tcp.server.ref create mode 100644 docs/en/refs/software.udp.client.ref create mode 100644 docs/en/refs/software.udp.server.ref create mode 100644 docs/en/software/tcp.client.rst create mode 100644 docs/en/software/tcp.rst create mode 100644 docs/en/software/tcp.server.rst create mode 100644 docs/en/software/udp.client.rst create mode 100644 docs/en/software/udp.rst create mode 100644 docs/en/software/udp.server.rst create mode 100644 docs/locales/zh_CN/LC_MESSAGES/software/tcp.client.po create mode 100644 docs/locales/zh_CN/LC_MESSAGES/software/tcp.po create mode 100644 docs/locales/zh_CN/LC_MESSAGES/software/tcp.server.po create mode 100644 docs/locales/zh_CN/LC_MESSAGES/software/udp.client.po create mode 100644 docs/locales/zh_CN/LC_MESSAGES/software/udp.po create mode 100644 docs/locales/zh_CN/LC_MESSAGES/software/udp.server.po create mode 100644 examples/softwave/tcp/cores3_tcp_client_example.m5f2 create mode 100644 examples/softwave/tcp/cores3_tcp_client_example.py create mode 100644 examples/softwave/tcp/cores3_tcp_server_example.m5f2 create mode 100644 examples/softwave/tcp/cores3_tcp_server_example.py create mode 100644 examples/softwave/udp/cores3_udp_client_example.m5f2 create mode 100644 examples/softwave/udp/cores3_udp_client_example.py create mode 100644 examples/softwave/udp/cores3_udp_server_example.m5f2 create mode 100644 examples/softwave/udp/cores3_udp_server_example.py diff --git a/docs/en/refs/software.tcp.client.ref b/docs/en/refs/software.tcp.client.ref new file mode 100644 index 00000000..33fa32b4 --- /dev/null +++ b/docs/en/refs/software.tcp.client.ref @@ -0,0 +1,22 @@ + +.. |close.png| image:: https://static-cdn.m5stack.com/mpy_docs/software/tcp/client/close.png +.. |init.png| image:: https://static-cdn.m5stack.com/mpy_docs/software/tcp/client/init.png +.. |read.png| image:: https://static-cdn.m5stack.com/mpy_docs/software/tcp/client/read.png +.. |recv.png| image:: https://static-cdn.m5stack.com/mpy_docs/software/tcp/client/recv.png +.. |send.png| image:: https://static-cdn.m5stack.com/mpy_docs/software/tcp/client/send.png +.. |setsockopt.png| image:: https://static-cdn.m5stack.com/mpy_docs/software/tcp/client/setsockopt.png +.. |write.png| image:: https://static-cdn.m5stack.com/mpy_docs/software/tcp/client/write.png +.. |readline.png| image:: https://static-cdn.m5stack.com/mpy_docs/software/tcp/client/readline.png +.. |setblocking.png| image:: https://static-cdn.m5stack.com/mpy_docs/software/tcp/client/setblocking.png +.. |settimeout.png| image:: https://static-cdn.m5stack.com/mpy_docs/software/tcp/client/settimeout.png + +.. |cores3_tcp_client_example.png| image:: https://static-cdn.m5stack.com/mpy_docs/software/tcp/client/cores3_tcp_client_example.png + +.. |cores3_tcp_client_example.m5f2| raw:: html + + + cores3_tcp_client_example.m5f2 + diff --git a/docs/en/refs/software.tcp.server.ref b/docs/en/refs/software.tcp.server.ref new file mode 100644 index 00000000..ab893f96 --- /dev/null +++ b/docs/en/refs/software.tcp.server.ref @@ -0,0 +1,27 @@ + +.. |close.png| image:: https://static-cdn.m5stack.com/mpy_docs/software/tcp/server/close.png +.. |close_client.png| image:: https://static-cdn.m5stack.com/mpy_docs/software/tcp/server/close_client.png +.. |accept.png| image:: https://static-cdn.m5stack.com/mpy_docs/software/tcp/server/accept.png +.. |init.png| image:: https://static-cdn.m5stack.com/mpy_docs/software/tcp/server/init.png +.. |read.png| image:: https://static-cdn.m5stack.com/mpy_docs/software/tcp/server/read.png +.. |recv.png| image:: https://static-cdn.m5stack.com/mpy_docs/software/tcp/server/recv.png +.. |recvfrom.png| image:: https://static-cdn.m5stack.com/mpy_docs/software/tcp/server/recvfrom.png +.. |send.png| image:: https://static-cdn.m5stack.com/mpy_docs/software/tcp/server/send.png +.. |sendto.png| image:: https://static-cdn.m5stack.com/mpy_docs/software/tcp/server/sendto.png +.. |setsockopt.png| image:: https://static-cdn.m5stack.com/mpy_docs/software/tcp/server/setsockopt.png +.. |write.png| image:: https://static-cdn.m5stack.com/mpy_docs/software/tcp/server/write.png +.. |readline.png| image:: https://static-cdn.m5stack.com/mpy_docs/software/tcp/server/readline.png +.. |setblocking.png| image:: https://static-cdn.m5stack.com/mpy_docs/software/tcp/server/setblocking.png +.. |settimeout.png| image:: https://static-cdn.m5stack.com/mpy_docs/software/tcp/server/settimeout.png + + +.. |cores3_tcp_server_example.png| image:: https://static-cdn.m5stack.com/mpy_docs/software/tcp/server/cores3_tcp_server_example.png + +.. |cores3_tcp_server_example.m5f2| raw:: html + + + cores3_tcp_server_example.m5f2 + diff --git a/docs/en/refs/software.udp.client.ref b/docs/en/refs/software.udp.client.ref new file mode 100644 index 00000000..b88663fb --- /dev/null +++ b/docs/en/refs/software.udp.client.ref @@ -0,0 +1,19 @@ + +.. |close.png| image:: https://static-cdn.m5stack.com/mpy_docs/software/udp/client/close.png +.. |init.png| image:: https://static-cdn.m5stack.com/mpy_docs/software/udp/client/init.png +.. |read.png| image:: https://static-cdn.m5stack.com/mpy_docs/software/udp/client/read.png +.. |recv.png| image:: https://static-cdn.m5stack.com/mpy_docs/software/udp/client/recv.png +.. |send.png| image:: https://static-cdn.m5stack.com/mpy_docs/software/udp/client/send.png +.. |setsockopt.png| image:: https://static-cdn.m5stack.com/mpy_docs/software/udp/client/setsockopt.png +.. |write.png| image:: https://static-cdn.m5stack.com/mpy_docs/software/udp/client/write.png + +.. |cores3_udp_client_example.png| image:: https://static-cdn.m5stack.com/mpy_docs/software/udp/client/cores3_udp_client_example.png + +.. |cores3_udp_client_example.m5f2| raw:: html + + + cores3_udp_client_example.m5f2 + diff --git a/docs/en/refs/software.udp.server.ref b/docs/en/refs/software.udp.server.ref new file mode 100644 index 00000000..c3af365f --- /dev/null +++ b/docs/en/refs/software.udp.server.ref @@ -0,0 +1,21 @@ + +.. |close.png| image:: https://static-cdn.m5stack.com/mpy_docs/software/udp/server/close.png +.. |init.png| image:: https://static-cdn.m5stack.com/mpy_docs/software/udp/server/init.png +.. |read.png| image:: https://static-cdn.m5stack.com/mpy_docs/software/udp/server/read.png +.. |recv.png| image:: https://static-cdn.m5stack.com/mpy_docs/software/udp/server/recv.png +.. |recvfrom.png| image:: https://static-cdn.m5stack.com/mpy_docs/software/udp/server/recvfrom.png +.. |send.png| image:: https://static-cdn.m5stack.com/mpy_docs/software/udp/server/send.png +.. |sendto.png| image:: https://static-cdn.m5stack.com/mpy_docs/software/udp/server/sendto.png +.. |setsockopt.png| image:: https://static-cdn.m5stack.com/mpy_docs/software/udp/server/setsockopt.png +.. |write.png| image:: https://static-cdn.m5stack.com/mpy_docs/software/udp/server/write.png + +.. |cores3_udp_server_example.png| image:: https://static-cdn.m5stack.com/mpy_docs/software/udp/server/cores3_udp_server_example.png + +.. |cores3_udp_server_example.m5f2| raw:: html + + + cores3_udp_server_example.m5f2 + diff --git a/docs/en/software/index.rst b/docs/en/software/index.rst index 421491f3..daf281c1 100644 --- a/docs/en/software/index.rst +++ b/docs/en/software/index.rst @@ -9,3 +9,5 @@ Software modbus.rst requests2.rst umqtt.rst + tcp.rst + udp.rst diff --git a/docs/en/software/tcp.client.rst b/docs/en/software/tcp.client.rst new file mode 100644 index 00000000..6f84e4ea --- /dev/null +++ b/docs/en/software/tcp.client.rst @@ -0,0 +1,242 @@ +TCP Client +========== + +.. include:: ../refs/software.tcp.client.ref + +UiFlow2 Example +--------------- + +simple client +^^^^^^^^^^^^^ + +Open the |cores3_tcp_client_example.m5f2| project in UiFlow2. + +This example creates a TCP client that sends data to a server. + +UiFlow2 Code Block: + + |cores3_tcp_client_example.png| + +Example output: + + None + +MicroPython Example +------------------- + +simple client +^^^^^^^^^^^^^ + +This example creates a TCP client that sends data to a server. + +MicroPython Code Block: + + .. literalinclude:: ../../../examples/softwave/tcp/cores3_tcp_client_example.py + :language: python + :linenos: + +Example output: + + None + + +**API** +------- + +.. method:: socket.socket() + socket.connect(address) + :no-index: + + Create a socket object and connect to the server. + + :param tuple address: A tuple containing the server IP address and port number. + :return: A socket object. + :rtype: socket.socket + + UiFlow2 Code Block: + + |init.png| + + MicroPython Code Block: + + .. code-block:: python + + import socket + + tcpc = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + tcpc.connect(('192.168.8.236', 8001)) + + +.. method:: socket.close() + :no-index: + + Close the socket connection. + + UiFlow2 Code Block: + + |close.png| + + MicroPython Code Block: + + .. code-block:: python + + tcpc.close() + +.. method:: socket.recv(bufsize) + :no-index: + + Receive data from the socket. + + :param int bufsize: The maximum amount of data to be received at once. + :return: The received data. + :rtype: bytes + + UiFlow2 Code Block: + + |recv.png| + + MicroPython Code Block: + + .. code-block:: python + + data = tcpc.recv(1024) + print(data) + + +.. method:: socket.read() + :no-index: + + Read data from the socket. + + :return: The received data. + :rtype: bytes + + UiFlow2 Code Block: + + |read.png| + + MicroPython Code Block: + + .. code-block:: python + + data = tcpc.read() + print(data) + +.. method:: socket.readline() + :no-index: + + Read a line, ending in a newline character. + + :return: the line read. + :rtype: str + + UiFlow2 Code Block: + + |readline.png| + + MicroPython Code Block: + + .. code-block:: python + + data = tcpc.readline() + print(data) + + +.. method:: socket.send(data) + :no-index: + + Send data to the socket. The socket must be connected to a remote socket. + + :param data: The data to be sent. + :type data: bytes | str + :return: The number of bytes sent. + :rtype: int + + UiFlow2 Code Block: + + |send.png| + + MicroPython Code Block: + + .. code-block:: python + + tcpc.send(b'Hello, World!') + tcpc.send('Hello, World!') + + +.. method:: socket.write(data) + :no-index: + + Write the buffer of bytes to the socket. This function will try to write all + data to a socket (no “short writes”). This may be not possible with a + non-blocking socket though, and returned value will be less than the length + of buf. + + :param data: The data to be sent. + :type data: bytes | str + :return: The number of bytes sent. + :rtype: int + + UiFlow2 Code Block: + + |write.png| + + MicroPython Code Block: + + .. code-block:: python + + tcpc.write(b'Hello, World!') + tcpc.write('Hello, World!') + + +.. method:: socket.setblocking(flag) + :no-index: + + Set blocking or non-blocking mode of the socket: if flag is false, the + socket is set to non-blocking, else to blocking mode. + + This method is a shorthand for certain `settimeout()` calls: + + * ``sock.setblocking(True)`` is equivalent to ``sock.settimeout(None)`` + * ``sock.setblocking(False)`` is equivalent to ``sock.settimeout(0)`` + + :param bool flag: If True, the socket will be in blocking mode. If False, the socket will be in non-blocking mode. + :return: None + :rtype: None + + UiFlow2 Code Block: + + |setblocking.png| + + MicroPython Code Block: + + .. code-block:: python + + tcpc.setblocking(True) + tcpc.setblocking(False) + + +.. method:: socket.settimeout(timeout) + :no-index: + + Set a timeout on blocking socket operations. The value argument can be a + nonnegative floating point number expressing seconds, or None. If a + non-zero value is given, subsequent socket operations will raise an OSError + exception if the timeout period value has elapsed before the operation has + completed. If zero is given, the socket is put in non-blocking mode. If None + is given, the socket is put in blocking mode. + + :param float timeout: The timeout value in seconds. If None, the socket is put in blocking mode. + :return: None + :rtype: None + + UiFlow2 Code Block: + + |settimeout.png| + + MicroPython Code Block: + + .. code-block:: python + + tcpc.settimeout(5) + tcpc.settimeout(None) diff --git a/docs/en/software/tcp.rst b/docs/en/software/tcp.rst new file mode 100644 index 00000000..415328a1 --- /dev/null +++ b/docs/en/software/tcp.rst @@ -0,0 +1,19 @@ +TCP +=== + +TCP is a connection-oriented protocol, meaning that a connection must be +established before data can be sent. It uses a three-way handshake to establish +a connection, which involves the exchange of SYN (synchronize) and ACK +(acknowledge) packets between the client and server. Once the connection is +established, data can be sent in both directions until either party decides to +close the connection. + + +API +--- + +.. toctree:: + :maxdepth: 1 + + tcp.client.rst + tcp.server.rst diff --git a/docs/en/software/tcp.server.rst b/docs/en/software/tcp.server.rst new file mode 100644 index 00000000..c1f6bda8 --- /dev/null +++ b/docs/en/software/tcp.server.rst @@ -0,0 +1,276 @@ +TCP Server +========== + +.. include:: ../refs/software.tcp.server.ref + +UiFlow2 Example +--------------- + +echo server +^^^^^^^^^^^ + +Open the |cores3_tcp_server_example.m5f2| project in UiFlow2. + +This example creates a TCP server that echoes the received data. + +UiFlow2 Code Block: + + |cores3_tcp_server_example.png| + +Example output: + + None + +MicroPython Example +------------------- + +echo server +^^^^^^^^^^^ + +This example creates a TCP server that echoes the received data. + +MicroPython Code Block: + + .. literalinclude:: ../../../examples/softwave/tcp/cores3_tcp_server_example.py + :language: python + :linenos: + +Example output: + + None + + +**API** +------- + +.. method:: socket.socket() + socket.bind(address) + socket.listen([backlog]) + :no-index: + + Create a socket object and bind it to an address. + + :param tuple address: A tuple containing the server IP address and port number. + :param int backlog: The maximum number of queued connections. Default is 5. + :return: A socket object. + :rtype: socket.socket + + UiFlow2 Code Block: + + |init.png| + + MicroPython Code Block: + + .. code-block:: python + + import socket + + tcps = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + tcps.bind(('0.0.0.0', 8001)) + tcps.listen(5) + + +.. method:: socket.close() + :no-index: + + Close the socket connection. + + UiFlow2 Code Block: + + |close.png| + + |close_client.png| + + MicroPython Code Block: + + .. code-block:: python + + tcps.close() + + +.. method:: socket.accept() + :no-index: + + Accept a connection. The socket must be bound to an address and listening + for connections. The return value is a pair (conn, address) where conn is a + new socket object usable to send and receive data on the connection, + and address is the address bound to the socket on the other end of the + connection. + + :return: A tuple containing the client socket and the address of the client. + :rtype: tuple + + UiFlow2 Code Block: + + |accept.png| + + MicroPython Code Block: + + .. code-block:: python + + client_socket, addr = tcps.accept() + print('Connected by', addr) + + +.. method:: socket.recv(bufsize) + :no-index: + + Receive data from the socket. + + :param int bufsize: The maximum amount of data to be received at once. + :return: The received data. + :rtype: bytes + + UiFlow2 Code Block: + + |recv.png| + + MicroPython Code Block: + + .. code-block:: python + + data = tcps.recv(1024) + print(data) + + +.. method:: socket.read() + :no-index: + + Read data from the socket. + + :return: The received data. + :rtype: bytes + + UiFlow2 Code Block: + + |read.png| + + MicroPython Code Block: + + .. code-block:: python + + data = tcps.read() + print(data) + + +.. method:: socket.send(data) + :no-index: + + Send data to the socket. The socket must be connected to a remote socket. + + :param data: The data to be sent. + :type data: bytes | str + :return: The number of bytes sent. + :rtype: int + + UiFlow2 Code Block: + + |send.png| + + MicroPython Code Block: + + .. code-block:: python + + tcps.send(b'Hello, World!') + tcps.send('Hello, World!') + + +.. method:: socket.write(data) + :no-index: + + Write the buffer of bytes to the socket. This function will try to write all + data to a socket (no “short writes”). This may be not possible with a + non-blocking socket though, and returned value will be less than the length + of buf. + + :param data: The data to be sent. + :type data: bytes | str + :return: The number of bytes sent. + :rtype: int + + UiFlow2 Code Block: + + |write.png| + + MicroPython Code Block: + + .. code-block:: python + + tcps.write(b'Hello, World!') + tcps.write('Hello, World!') + + +.. method:: socket.setblocking(flag) + :no-index: + + Set blocking or non-blocking mode of the socket: if flag is false, the + socket is set to non-blocking, else to blocking mode. + + This method is a shorthand for certain `settimeout()` calls: + + * ``sock.setblocking(True)`` is equivalent to ``sock.settimeout(None)`` + * ``sock.setblocking(False)`` is equivalent to ``sock.settimeout(0)`` + + :param bool flag: If True, the socket will be in blocking mode. If False, the socket will be in non-blocking mode. + :return: None + :rtype: None + + UiFlow2 Code Block: + + |setblocking.png| + + MicroPython Code Block: + + .. code-block:: python + + tcps.setblocking(True) + tcps.setblocking(False) + + +.. method:: socket.settimeout(timeout) + :no-index: + + Set a timeout on blocking socket operations. The value argument can be a + nonnegative floating point number expressing seconds, or None. If a + non-zero value is given, subsequent socket operations will raise an OSError + exception if the timeout period value has elapsed before the operation has + completed. If zero is given, the socket is put in non-blocking mode. If None + is given, the socket is put in blocking mode. + + :param float timeout: The timeout value in seconds. If None, the socket is put in blocking mode. + :return: None + :rtype: None + + UiFlow2 Code Block: + + |settimeout.png| + + MicroPython Code Block: + + .. code-block:: python + + tcps.settimeout(5) + tcps.settimeout(None) + + +.. method:: socket.setsockopt(level, optname, value) + :no-index: + + Set the value of the given socket option. The needed symbolic constants are + defined in the socket module (SO_* etc.). The value can be an integer or a + bytes-like object representing a buffer. + + :param int level: The level at which the option is defined. + :param int optname: The name of the option to set. + :param value: The value to set for the option. + + UiFlow2 Code Block: + + |setsockopt.png| + + MicroPython Code Block: + + .. code-block:: python + + tcps.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 0) + tcps.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) diff --git a/docs/en/software/udp.client.rst b/docs/en/software/udp.client.rst new file mode 100644 index 00000000..818c61f4 --- /dev/null +++ b/docs/en/software/udp.client.rst @@ -0,0 +1,194 @@ +UDP Client +========== + +.. include:: ../refs/software.udp.client.ref + +UiFlow2 Example +--------------- + +simple client +^^^^^^^^^^^^^ + +Open the |cores3_udp_client_example.m5f2| project in UiFlow2. + +This example creates a UDP client that sends data to a server. + +UiFlow2 Code Block: + + |cores3_udp_client_example.png| + +Example output: + + None + +MicroPython Example +------------------- + +sample client +^^^^^^^^^^^^^ + +This example creates a UDP client that sends data to a server. + +MicroPython Code Block: + + .. literalinclude:: ../../../examples/softwave/udp/cores3_udp_client_example.py + :language: python + :linenos: + +Example output: + + None + + +**API** +------- + +.. method:: socket.socket() + socket.connect(address) + :no-index: + + Create a socket object and connect to the server. + + :param tuple address: A tuple containing the server IP address and port number. + :return: A socket object. + :rtype: socket.socket + + UiFlow2 Code Block: + + |init.png| + + MicroPython Code Block: + + .. code-block:: python + + import socket + + udpc = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + udpc.connect(('192.168.8.236', 8000)) + + +.. method:: socket.close() + :no-index: + + Close the socket connection. + + UiFlow2 Code Block: + + |close.png| + + MicroPython Code Block: + + .. code-block:: python + + udpc.close() + + +.. method:: socket.recv(bufsize) + :no-index: + + Receive data from the socket. + + :param int bufsize: The maximum amount of data to be received at once. + :return: The received data. + :rtype: bytes + + UiFlow2 Code Block: + + |recv.png| + + MicroPython Code Block: + + .. code-block:: python + + data = udpc.recv(1024) + print(data) + + +.. method:: socket.read() + :no-index: + + Read data from the socket. + + :return: The received data. + :rtype: bytes + + UiFlow2 Code Block: + + |read.png| + + MicroPython Code Block: + + .. code-block:: python + + data = udpc.read() + print(data) + + +.. method:: socket.send(data) + :no-index: + + Send data to the socket. The socket must be connected to a remote socket. + + :param data: The data to be sent. + :type data: bytes | str + :return: The number of bytes sent. + :rtype: int + + UiFlow2 Code Block: + + |send.png| + + MicroPython Code Block: + + .. code-block:: python + + udpc.send(b'Hello, World!') + udpc.send('Hello, World!') + + +.. method:: socket.write(data) + :no-index: + + Write the buffer of bytes to the socket. This function will try to write all + data to a socket (no “short writes”). This may be not possible with a + non-blocking socket though, and returned value will be less than the length + of buf. + + :param data: The data to be sent. + :type data: bytes | str + :return: The number of bytes sent. + :rtype: int + + UiFlow2 Code Block: + + |write.png| + + MicroPython Code Block: + + .. code-block:: python + + udpc.write(b'Hello, World!') + udpc.write('Hello, World!') + + +.. method:: socket.setsockopt(level, optname, value) + :no-index: + + Set the value of the given socket option. The needed symbolic constants are + defined in the socket module (SO_* etc.). The value can be an integer or a + bytes-like object representing a buffer. + + :param int level: The level at which the option is defined. + :param int optname: The name of the option to set. + :param value: The value to set for the option. + + UiFlow2 Code Block: + + |setsockopt.png| + + MicroPython Code Block: + + .. code-block:: python + + udpc.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 0) + udpc.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) diff --git a/docs/en/software/udp.rst b/docs/en/software/udp.rst new file mode 100644 index 00000000..6419fba9 --- /dev/null +++ b/docs/en/software/udp.rst @@ -0,0 +1,17 @@ +UDP +=== + +The UDP module provides a simple way to send and receive data over the network +using the User Datagram Protocol (UDP). It is designed for low-latency +communication and is suitable for applications where speed is more critical +than reliability. + + +API +--- + +.. toctree:: + :maxdepth: 1 + + udp.client.rst + udp.server.rst diff --git a/docs/en/software/udp.server.rst b/docs/en/software/udp.server.rst new file mode 100644 index 00000000..28df98fb --- /dev/null +++ b/docs/en/software/udp.server.rst @@ -0,0 +1,240 @@ +UDP Server +========== + +.. include:: ../refs/software.udp.server.ref + +UiFlow2 Example +--------------- + +echo server +^^^^^^^^^^^ + +Open the |cores3_udp_server_example.m5f2| project in UiFlow2. + +This example creates a UDP server that echoes the received data. + +UiFlow2 Code Block: + + |cores3_udp_server_example.png| + +Example output: + + None + +MicroPython Example +------------------- + +echo server +^^^^^^^^^^^ + +This example creates a UDP server that echoes the received data. + +MicroPython Code Block: + + .. literalinclude:: ../../../examples/softwave/udp/cores3_udp_server_example.py + :language: python + :linenos: + +Example output: + + None + + +**API** +------- + +.. method:: socket.socket() + socket.bind(address) + :no-index: + + Create a socket object and bind it to an address. + + :param tuple address: A tuple containing the server IP address and port number. + :return: A socket object. + :rtype: socket.socket + + UiFlow2 Code Block: + + |init.png| + + MicroPython Code Block: + + .. code-block:: python + + import socket + + udps = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + udps.bind(('0.0.0.0', 8000)) + + +.. method:: socket.close() + :no-index: + + Close the socket connection. + + UiFlow2 Code Block: + + |close.png| + + MicroPython Code Block: + + .. code-block:: python + + udps.close() + + +.. method:: socket.recv(bufsize) + :no-index: + + Receive data from the socket. + + :param int bufsize: The maximum amount of data to be received at once. + :return: The received data. + :rtype: bytes + + UiFlow2 Code Block: + + |recv.png| + + MicroPython Code Block: + + .. code-block:: python + + data = udps.recv(1024) + print(data) + + +.. method:: socket.recvfrom(bufsize) + :no-index: + + Receive data from the socket. The return value is a pair (bytes, address) + where bytes is a bytes object representing the data received and address is + the address of the socket sending the data. + + :param int bufsize: The maximum amount of data to be received at once. + :return: The received data. + :rtype: tuple + + UiFlow2 Code Block: + + |recvfrom.png| + + MicroPython Code Block: + + .. code-block:: python + + data, address = udps.recvfrom(1024) + print(data) + + +.. method:: socket.read() + :no-index: + + Read data from the socket. + + :return: The received data. + :rtype: bytes + + UiFlow2 Code Block: + + |read.png| + + MicroPython Code Block: + + .. code-block:: python + + data = udps.read() + print(data) + + +.. method:: socket.send(data) + :no-index: + + Send data to the socket. The socket must be connected to a remote socket. + + :param data: The data to be sent. + :type data: bytes | str + :return: The number of bytes sent. + :rtype: int + + UiFlow2 Code Block: + + |send.png| + + MicroPython Code Block: + + .. code-block:: python + + udps.send(b'Hello, World!') + udps.send('Hello, World!') + + +.. method:: socket.sendto(data, address) + :no-index: + + Send data to the socket. The socket should not be connected to a remote socket, since the destination socket is specified by address. + + :param data: The data to be sent. + :type data: bytes | str + :param tuple address: A tuple containing the destination IP address and port number. + :return: The number of bytes sent. + :rtype: int + + UiFlow2 Code Block: + + |sendto.png| + + MicroPython Code Block: + + .. code-block:: python + + udps.sendto("hello", ("192.168.8.8", 8000)) + + +.. method:: socket.write(data) + :no-index: + + Write the buffer of bytes to the socket. This function will try to write all + data to a socket (no “short writes”). This may be not possible with a + non-blocking socket though, and returned value will be less than the length + of buf. + + :param data: The data to be sent. + :type data: bytes | str + :return: The number of bytes sent. + :rtype: int + + UiFlow2 Code Block: + + |write.png| + + MicroPython Code Block: + + .. code-block:: python + + udps.write(b'Hello, World!') + udps.write('Hello, World!') + + +.. method:: socket.setsockopt(level, optname, value) + :no-index: + + Set the value of the given socket option. The needed symbolic constants are + defined in the socket module (SO_* etc.). The value can be an integer or a + bytes-like object representing a buffer. + + :param int level: The level at which the option is defined. + :param int optname: The name of the option to set. + :param value: The value to set for the option. + + UiFlow2 Code Block: + + |setsockopt.png| + + MicroPython Code Block: + + .. code-block:: python + + udps.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 0) + udps.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + diff --git a/docs/locales/zh_CN/LC_MESSAGES/software/tcp.client.po b/docs/locales/zh_CN/LC_MESSAGES/software/tcp.client.po new file mode 100644 index 00000000..28c8591f --- /dev/null +++ b/docs/locales/zh_CN/LC_MESSAGES/software/tcp.client.po @@ -0,0 +1,309 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2016 - 2025 M5Stack Technology Co., Ltd +# This file is distributed under the same license as the UIFlow2 Programming +# Guide package. +# FIRST AUTHOR , 2025. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: UIFlow2 Programming Guide\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-04-03 11:49+0800\n" +"PO-Revision-Date: 2025-04-03 11:35+0800\n" +"Last-Translator: \n" +"Language: zh_CN\n" +"Language-Team: zh_CN \n" +"Plural-Forms: nplurals=1; plural=0;\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.16.0\n" + +#: ../../en/software/tcp.client.rst:2 91960f5852b64604997145618f459d30 +msgid "TCP Client" +msgstr "TCP 客户端" + +#: ../../en/software/tcp.client.rst:7 1a31715710cf477a8ab93d1ee1c6f8ac +msgid "UiFlow2 Example" +msgstr "UIFLOW2 应用示例" + +#: ../../en/software/tcp.client.rst:10 ../../en/software/tcp.client.rst:28 +#: 5dd4f769e0fb4dca91fd6a3cb5c0ca96 f30e4282f3f34acf856ac32a1bde1fb1 +msgid "simple client" +msgstr "简单客户端" + +#: ../../en/software/tcp.client.rst:12 cc08eaf441c44e6e947ad82f30c7b4ac +msgid "Open the |cores3_tcp_client_example.m5f2| project in UiFlow2." +msgstr "在 UiFlow2 中打开 |cores3_tcp_client_example.m5f2| 项目。" + +#: ../../en/software/tcp.client.rst:14 ../../en/software/tcp.client.rst:30 +#: 2ffca9fc95de4234af5a74dfef3a169d ca9b3f9cbe83419789dc8c7f7248ceba +msgid "This example creates a TCP client that sends data to a server." +msgstr "此示例创建了一个向服务器发送数据的 TCP 客户端。" + +#: ../../en/software/tcp.client.rst:16 ../../en/software/tcp.client.rst:56 +#: ../../en/software/tcp.client.rst:75 ../../en/software/tcp.client.rst:94 +#: ../../en/software/tcp.client.rst:114 ../../en/software/tcp.client.rst:133 +#: ../../en/software/tcp.client.rst:155 ../../en/software/tcp.client.rst:180 +#: ../../en/software/tcp.client.rst:207 ../../en/software/tcp.client.rst:233 +#: 1883ae7dbaee41f3b037284b12d97233 3efd54f4d13c491cbd86bea277676af5 +#: 4d011acdb9984e5f8204e8f5357b3c2a 5eba9b0d2b4e44eda42066bd184698c0 +#: 6668472d481546fba3f1b49e33a6b8df 715447dd52134d2b8710340dca482894 +#: 8d0f043f76904c84bb5d6b4d46a3aa72 a76bea9273fa4a8f890aa2a18251fc90 +#: dc00d87bfef34e52b297d9d903e1e25c f13452e9bdbf4a48887f8793e137fe28 +msgid "UiFlow2 Code Block:" +msgstr "UiFlow2 代码块:" + +#: ../../en/software/tcp.client.rst:18 63bb144f45014e54af51dbbf99ca9f58 +msgid "|cores3_tcp_client_example.png|" +msgstr "" + +#: ../../en/refs/software.tcp.client.ref:13 b7eec67237484da39c7373a802eb44e3 +msgid "cores3_tcp_client_example.png" +msgstr "" + +#: ../../en/software/tcp.client.rst:20 ../../en/software/tcp.client.rst:38 +#: 742fe3272e6a4c6a9af4a19195e1fd4b d98d90bb0fcb42f88a821787cb4b1040 +msgid "Example output:" +msgstr "示例输出:" + +#: ../../en/software/tcp.client.rst:22 ../../en/software/tcp.client.rst:40 +#: ../../en/software/tcp.client.rst:204 ../../en/software/tcp.client.rst:205 +#: ../../en/software/tcp.client.rst:230 462ac21659b640a68c5fa23455ae840d +#: 4a211d30280d42459908f0bd294f8b11 7b3b2317ab1e4189a5ce429810f05c6b +#: 7cb1507cf07548a0aebce0bc58d93704 b4abf4ed53814e02a7d70da11fb4d159 +msgid "None" +msgstr "" + +#: ../../en/software/tcp.client.rst:25 e8a78c79eccb40e9a2966e997e7635f0 +msgid "MicroPython Example" +msgstr "MicroPython 应用示例" + +#: ../../en/software/tcp.client.rst:32 ../../en/software/tcp.client.rst:60 +#: ../../en/software/tcp.client.rst:79 ../../en/software/tcp.client.rst:98 +#: ../../en/software/tcp.client.rst:118 ../../en/software/tcp.client.rst:137 +#: ../../en/software/tcp.client.rst:159 ../../en/software/tcp.client.rst:184 +#: ../../en/software/tcp.client.rst:211 ../../en/software/tcp.client.rst:237 +#: 17cc698edb5b4ec89266ffeb5801f605 373de59d52564db1bb7d129681257458 +#: 4bfc9f99198249fd9a8c4d8aa81aee3a 622ef85164a849dca0be5de6169f23c2 +#: 6369d2facc6a4d458e2b8d466835c3f9 9b31186ce16846edb9dd8aa55a8ca266 +#: 9d0bfb7559814129bf34a2ceca165a79 a606e3f3d2854119b84a402bd6530556 +#: ddd37af80f2b4bb68471213205f733a0 f0760184a3f44edbb93ff52fd22435a9 +msgid "MicroPython Code Block:" +msgstr "MicroPython 代码块:" + +#: ../../en/software/tcp.client.rst:44 3d581bb7a7a645f69eedca4ce0a0bc3f +msgid "**API**" +msgstr "API参考" + +#: ../../en/software/tcp.client.rst:50 02e0760ff90f4dea8e8490f472306547 +msgid "Create a socket object and connect to the server." +msgstr "创建一个套接字对象并连接到服务器。" + +#: ../../en/software/tcp.client.rst 1103ca35943642169844fbf303d75dd0 +#: 1cca7e11ba584acba4bf8b04a73fbb6b ce3084c1a86249c1a8a86ef302d89b29 +#: da828f12fe6d4b18b9bd0e9e6e53df8f e95f0d80a56e48bdba51cf86295b7d6f +msgid "Parameters" +msgstr "" + +#: ../../en/software/tcp.client.rst:52 899967d6bd434331a51eef40580f3604 +msgid "A tuple containing the server IP address and port number." +msgstr "包含服务器 IP 地址和端口号的元组。" + +#: ../../en/software/tcp.client.rst 06c1f42461c44273b079a9404fa9f3d7 +#: 13f67a7757de46bfa508dee5f33c7671 2baa7e1ede704438bb63eaf2b6f63572 +#: 3a29bdcd2e004a5cb8345be62becea68 d1f464755e42405681f5a75743b14d8c +#: dc82a88b3c9845bfbe51ecbcfe3fae55 e305eac65a904b5f85b76fe067826617 +msgid "Returns" +msgstr "" + +#: ../../en/software/tcp.client.rst:53 f417c7745ef742c3b52cbc4199a5e61c +msgid "A socket object." +msgstr "一个套接字对象。" + +#: ../../en/software/tcp.client.rst 2fc27d8439c2486abb02c1131a355373 +#: 3152a81330444840abdc20da546ccaa0 98afacc9b3fa47e0a3433cdc5496f921 +#: ddbdcb800882490698cbf23d967dd4a9 e13595dab7fd4c58870f92e518585ead +#: ec6bd0209c4541d48d147f22f7eb9109 eff21a1e76b6472f8b5d93c5f14851f3 +msgid "Return type" +msgstr "" + +#: ../../en/software/tcp.client.rst:58 40bb83698bc249a19db17e6e31d8bea1 +msgid "|init.png|" +msgstr "" + +#: ../../en/refs/software.tcp.client.ref:3 9f6b80b661af4a3d8a757e3c180fa1fe +msgid "init.png" +msgstr "" + +#: ../../en/software/tcp.client.rst:73 62cef5fa0288499ba7f030c5d0a76b5f +msgid "Close the socket connection." +msgstr "关闭套接字连接。" + +#: ../../en/software/tcp.client.rst:77 97fb4e68acdd4a7b886c7cf8a75e7a8c +msgid "|close.png|" +msgstr "" + +#: ../../en/refs/software.tcp.client.ref:2 f2e886245b414486bbdf2de3cf6b90b5 +msgid "close.png" +msgstr "" + +#: ../../en/software/tcp.client.rst:88 848def99d97d4856b06b03081dff643d +msgid "Receive data from the socket." +msgstr "从套接字接收数据。" + +#: ../../en/software/tcp.client.rst:90 ede1933f3b8e498b85afc7cf7700029f +msgid "The maximum amount of data to be received at once." +msgstr "一次可接收的最大数据量。" + +#: ../../en/software/tcp.client.rst:91 ../../en/software/tcp.client.rst:111 +#: 354943f2da9b43f989932d4c18767122 55253e5a75e849e7b6b223d6771f4b66 +msgid "The received data." +msgstr "接收到的数据。" + +#: ../../en/software/tcp.client.rst:96 d031d8f16ded4ec38c70fa767966cc56 +msgid "|recv.png|" +msgstr "" + +#: ../../en/refs/software.tcp.client.ref:5 8ba055e27f1f47a1b93bdf082d4486e5 +msgid "recv.png" +msgstr "" + +#: ../../en/software/tcp.client.rst:109 bd659fe6e89b41ab90907eddefb95ac4 +msgid "Read data from the socket." +msgstr "从套接字读取数据。" + +#: ../../en/software/tcp.client.rst:116 fb5088c273184bda9e23894c8262a717 +msgid "|read.png|" +msgstr "" + +#: ../../en/refs/software.tcp.client.ref:4 6661cf5b5903491680bdc5e38f3dbccd +msgid "read.png" +msgstr "" + +#: ../../en/software/tcp.client.rst:128 599c9e618ea54f959e7a3d6876612d5a +msgid "Read a line, ending in a newline character." +msgstr "读取一行,以换行符结尾。" + +#: ../../en/software/tcp.client.rst:130 56232c02e5794e689774fda0979f96f8 +msgid "the line read." +msgstr "读到这一行。" + +#: ../../en/software/tcp.client.rst:135 26e70e6e4d2f481fa2eb10a079869afa +msgid "|readline.png|" +msgstr "" + +#: ../../en/refs/software.tcp.client.ref:9 bba2359bbbfa4ee6ae82639e2f72224a +msgid "readline.png" +msgstr "" + +#: ../../en/software/tcp.client.rst:148 e0767c8b8d30425e916c508f698d400e +msgid "Send data to the socket. The socket must be connected to a remote socket." +msgstr "向套接字发送数据。该套接字必须连接到远程套接字。" + +#: ../../en/software/tcp.client.rst:150 ../../en/software/tcp.client.rst:175 +#: 28dcc0e59ac242fc89f5c73183c512a1 dc0b2cda650a48f4bff54720e640d971 +msgid "The data to be sent." +msgstr "要发送的数据。" + +#: ../../en/software/tcp.client.rst:152 ../../en/software/tcp.client.rst:177 +#: 7d01b50470a94170b3e8f2aea5b4c6d6 ab16b84bbbc54ac2954ebecc044fd72d +msgid "The number of bytes sent." +msgstr "已发送的字节数。" + +#: ../../en/software/tcp.client.rst:157 d09bda16eb274ca4bb93057602273015 +msgid "|send.png|" +msgstr "" + +#: ../../en/refs/software.tcp.client.ref:6 295f523a428b4e0b84908c18c5cf6eda +msgid "send.png" +msgstr "" + +#: ../../en/software/tcp.client.rst:170 9beb30fa05894d91ba641a8a2b168fbf +msgid "" +"Write the buffer of bytes to the socket. This function will try to write " +"all data to a socket (no “short writes”). This may be not possible with a" +" non-blocking socket though, and returned value will be less than the " +"length of buf." +msgstr "" +"将字节缓冲区写入套接字。此函数将尝试将所有数据写入套接字(无“短写入”)。不过,对于非阻塞套接字,这可能无法实现,并且返回的值将小于 buf " +"的长度。" + +#: ../../en/software/tcp.client.rst:182 bf76f225bae24b04b1dfeb1b0e94160a +msgid "|write.png|" +msgstr "" + +#: ../../en/refs/software.tcp.client.ref:8 83763805c2da48718d6f51cab766d2eb +msgid "write.png" +msgstr "" + +#: ../../en/software/tcp.client.rst:195 74c871c444cd4fe9924fcf4946072f4a +msgid "" +"Set blocking or non-blocking mode of the socket: if flag is false, the " +"socket is set to non-blocking, else to blocking mode." +msgstr "设置套接字的阻塞或非阻塞模式:如果标志为假,则套接字设置为非阻塞,否则设置为阻塞模式。" + +#: ../../en/software/tcp.client.rst:198 2aa0b4dd5d804a0aa0d28cf4bdd833ff +msgid "This method is a shorthand for certain `settimeout()` calls:" +msgstr "此方法是某些 `settimeout()` 调用的简写:" + +#: ../../en/software/tcp.client.rst:200 984712a6acba4921ad56d948ebcddc86 +msgid "``sock.setblocking(True)`` is equivalent to ``sock.settimeout(None)``" +msgstr "``sock.setblocking(True)`` 等同于 ``sock.settimeout(None)``" + +#: ../../en/software/tcp.client.rst:201 abbab6e306ad4abbb3cc61d4e0773d3c +msgid "``sock.setblocking(False)`` is equivalent to ``sock.settimeout(0)``" +msgstr "``sock.setblocking(False)`` 等同于 ``sock.settimeout(0)``" + +#: ../../en/software/tcp.client.rst f3766a567e624601b29579d641cecd0d +msgid "param bool flag" +msgstr "" + +#: ../../en/software/tcp.client.rst:203 a21d060ed73c441b80c4302298a221a6 +msgid "" +"If True, the socket will be in blocking mode. If False, the socket will " +"be in non-blocking mode." +msgstr "如果为 True,套接字将处于阻塞模式。如果为 False,套接字将处于非阻塞模式。" + +#: ../../en/software/tcp.client.rst 850f609a40dc4ebb8ae8304ec82295b2 +msgid "return" +msgstr "" + +#: ../../en/software/tcp.client.rst e35fb7b8753f4e53b074227b63ce7725 +msgid "rtype" +msgstr "" + +#: ../../en/software/tcp.client.rst:209 a057238d469d4973b116fa8a6db7c8e5 +msgid "|setblocking.png|" +msgstr "" + +#: ../../en/refs/software.tcp.client.ref:10 30ca51a97835406ba139e04810090fe2 +msgid "setblocking.png" +msgstr "" + +#: ../../en/software/tcp.client.rst:222 05c8689637894932a0841636b618bdf2 +msgid "" +"Set a timeout on blocking socket operations. The value argument can be a " +"nonnegative floating point number expressing seconds, or None. If a non-" +"zero value is given, subsequent socket operations will raise an OSError " +"exception if the timeout period value has elapsed before the operation " +"has completed. If zero is given, the socket is put in non-blocking mode. " +"If None is given, the socket is put in blocking mode." +msgstr "" +"设置阻塞套接字操作的超时时间。值参数可以是表示秒数的非负浮点数,也可以是 " +"None。如果给出了非零值,则在操作完成之前如果超时时间值已过,则后续套接字操作将引发 OSError " +"异常。如果给出了零,则套接字将处于非阻塞模式。如果给出了 None,则套接字将处于阻塞模式。" + +#: ../../en/software/tcp.client.rst:229 77b978dc07dd48b498b198de791102bf +msgid "The timeout value in seconds. If None, the socket is put in blocking mode." +msgstr "超时值(以秒为单位)。如果为 None,则套接字将处于阻塞模式。" + +#: ../../en/software/tcp.client.rst:235 0373d0b4ede3478eb2dbd539e5ecb038 +msgid "|settimeout.png|" +msgstr "" + +#: ../../en/refs/software.tcp.client.ref:11 795b06ac96a34022b7c130c283a44244 +msgid "settimeout.png" +msgstr "" + diff --git a/docs/locales/zh_CN/LC_MESSAGES/software/tcp.po b/docs/locales/zh_CN/LC_MESSAGES/software/tcp.po new file mode 100644 index 00000000..ceca0b1a --- /dev/null +++ b/docs/locales/zh_CN/LC_MESSAGES/software/tcp.po @@ -0,0 +1,43 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2016 - 2025 M5Stack Technology Co., Ltd +# This file is distributed under the same license as the UIFlow2 Programming +# Guide package. +# FIRST AUTHOR , 2025. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: UIFlow2 Programming Guide\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-04-03 10:58+0800\n" +"PO-Revision-Date: 2025-04-03 11:04+0800\n" +"Last-Translator: \n" +"Language-Team: zh_CN \n" +"Language: zh_CN\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=1; plural=0;\n" +"Generated-By: Babel 2.16.0\n" +"X-Generator: Poedit 3.5\n" + +#: ../../en/software/tcp.rst:2 9a65f6b67ede4e34bbd4050df309c519 +msgid "TCP" +msgstr "" + +#: ../../en/software/tcp.rst:4 f8ff03bdb2aa473fa2013249fae3e551 +msgid "" +"TCP is a connection-oriented protocol, meaning that a connection must be " +"established before data can be sent. It uses a three-way handshake to " +"establish a connection, which involves the exchange of SYN (synchronize) " +"and ACK (acknowledge) packets between the client and server. Once the " +"connection is established, data can be sent in both directions until " +"either party decides to close the connection." +msgstr "" +"TCP 是一种面向连接的协议,这意味着必须先建立连接,然后才能发送数据。它使" +"用三方握手建立连接,包括客户端和服务器之间交换 SYN(同步)和 ACK(确认)" +"数据包。连接一旦建立,数据就可以双向发送,直到任何一方决定关闭连接。" + +#: ../../en/software/tcp.rst:13 33ba51246eda46e8b6a649c490917be7 +msgid "API" +msgstr "" diff --git a/docs/locales/zh_CN/LC_MESSAGES/software/tcp.server.po b/docs/locales/zh_CN/LC_MESSAGES/software/tcp.server.po new file mode 100644 index 00000000..4d7b5e37 --- /dev/null +++ b/docs/locales/zh_CN/LC_MESSAGES/software/tcp.server.po @@ -0,0 +1,362 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2016 - 2025 M5Stack Technology Co., Ltd +# This file is distributed under the same license as the UIFlow2 Programming +# Guide package. +# FIRST AUTHOR , 2025. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: UIFlow2 Programming Guide\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-04-03 10:58+0800\n" +"PO-Revision-Date: 2025-04-03 11:47+0800\n" +"Last-Translator: \n" +"Language-Team: zh_CN \n" +"Language: zh_CN\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=1; plural=0;\n" +"Generated-By: Babel 2.16.0\n" +"X-Generator: Poedit 3.5\n" + +#: ../../en/software/tcp.server.rst:2 1820d1291c3946aeba886b677559d53a +msgid "TCP Server" +msgstr "TCP 服务端" + +#: ../../en/software/tcp.server.rst:7 b998e3a5e2bc48aba1243e0aadff4abd +msgid "UiFlow2 Example" +msgstr "UIFLOW2 应用示例" + +#: ../../en/software/tcp.server.rst:10 ../../en/software/tcp.server.rst:28 +#: 73c473783aa2481aa63a8b5af7cd7943 84a1ceabd4434f7094e492a8cd8c267b +msgid "echo server" +msgstr "回显服务器" + +#: ../../en/software/tcp.server.rst:12 1002ffeb6b114852b19a96aaa70d8aeb +msgid "Open the |cores3_tcp_server_example.m5f2| project in UiFlow2." +msgstr "在 UiFlow2 中打开 |cores3_tcp_server_example.m5f2| 项目。" + +#: ../../en/software/tcp.server.rst:14 ../../en/software/tcp.server.rst:30 +#: 02330608bc7a49b4af56f213b3c99246 b1b82fddb33846b7844c0a84b77102ad +msgid "This example creates a TCP server that echoes the received data." +msgstr "此示例创建一个回显接收数据的 TCP 服务器。" + +#: ../../en/software/tcp.server.rst:16 ../../en/software/tcp.server.rst:58 +#: ../../en/software/tcp.server.rst:78 ../../en/software/tcp.server.rst:103 +#: ../../en/software/tcp.server.rst:124 ../../en/software/tcp.server.rst:144 +#: ../../en/software/tcp.server.rst:166 ../../en/software/tcp.server.rst:191 +#: ../../en/software/tcp.server.rst:218 ../../en/software/tcp.server.rst:244 +#: ../../en/software/tcp.server.rst:267 133c4200323f46bf91ec8b5730bcc8f7 +#: 42cad91d915e470d887693a43e4e66b9 4a8e0802016a40dfb120168964a31cbf +#: 4d58f3e99a8a49738ce4b89e658d446e 6606f468d20e464d8a6039becd5bb64f +#: 7f9118e6c4b2499dbed222d01923a6aa 82a0452f69204155a597ba2c9017daf6 +#: aff861bd10db46f79f20637b07c307c1 c7a2fad5422a435abb0222ccb0051f06 +#: fc23315b9d1249aba30e9a997d9e0887 feeb123815bd41a8a0bff2dee199f14a +msgid "UiFlow2 Code Block:" +msgstr "UiFlow2 代码块:" + +#: ../../en/software/tcp.server.rst:18 e929d916ee26423a9f7b30c3d601b298 +msgid "|cores3_tcp_server_example.png|" +msgstr "" + +#: ../../en/refs/software.tcp.server.ref:18 769e98d140c04a8bacea34cf01e052bf +msgid "cores3_tcp_server_example.png" +msgstr "" + +#: ../../en/software/tcp.server.rst:20 ../../en/software/tcp.server.rst:38 +#: 68881b12c2cf4ee0adcb773173fc98a6 f7031b50fae24e0ca6906784a6488ffd +msgid "Example output:" +msgstr "示例输出:" + +#: ../../en/software/tcp.server.rst:22 ../../en/software/tcp.server.rst:40 +#: ../../en/software/tcp.server.rst:215 ../../en/software/tcp.server.rst:216 +#: ../../en/software/tcp.server.rst:241 2850cfdb83344b19a31c081d11cc8fb9 +#: 4e89e1375af7457b9aabf039b05ed70c 65c99a73cbe4460a99ce8fc525f17373 +#: bd1e7591deb240e49bcecee10db8afed d7602ea1e45446239ce095880fdc29d2 +msgid "None" +msgstr "" + +#: ../../en/software/tcp.server.rst:25 d27bcfa0f8b84957bbd1cbc37e0a619b +msgid "MicroPython Example" +msgstr "MicroPython 应用示例" + +#: ../../en/software/tcp.server.rst:32 ../../en/software/tcp.server.rst:62 +#: ../../en/software/tcp.server.rst:84 ../../en/software/tcp.server.rst:107 +#: ../../en/software/tcp.server.rst:128 ../../en/software/tcp.server.rst:148 +#: ../../en/software/tcp.server.rst:170 ../../en/software/tcp.server.rst:195 +#: ../../en/software/tcp.server.rst:222 ../../en/software/tcp.server.rst:248 +#: ../../en/software/tcp.server.rst:271 058673a329244dbebafd0e96f35eeb84 +#: 160dc1f30a054f7eb10056d021801869 232711df877243c1a742ccc982b16cbe +#: 3e1b41468a8f4ab7b243eb90a40f8675 580e894cc7574b71a029251f86e47ab4 +#: 61fa1535432547eebfd150a501e9e102 6d219a908be446d382f370ae1d509efd +#: 7447d8d5a3b546b786c12406d6f219c4 cc52d336609d4f86ae394eb814188614 +#: de88b0f6808a41c4b593f84e8c4007ba e7c5464cf1b746328cae44fead1ca930 +msgid "MicroPython Code Block:" +msgstr "MicroPython 代码块:" + +#: ../../en/software/tcp.server.rst:44 766a41c5280c415288d42a00540789d1 +msgid "**API**" +msgstr "API参考" + +#: ../../en/software/tcp.server.rst:51 bf855f7a03ba453ead9b547af7beca26 +msgid "Create a socket object and bind it to an address." +msgstr "创建一个套接字对象并将其绑定到一个地址。" + +#: ../../en/software/tcp.server.rst 155cbf56871c49d798372b95ff4739b2 +#: 2f15c46e9a3e403ca1dbed3c9bae48e5 76b3b5c8497c406a8c2ab1ab901ce5dd +#: 985c1c7ebbff4de6a99fc0378f858412 b9086b0e05f34a8fb2d79cac12b609ae +#: cc4790e6404d41a692bc28af71b1880b +msgid "Parameters" +msgstr "" + +#: ../../en/software/tcp.server.rst:53 1e038a24153a4741a06d87084a6619ac +msgid "A tuple containing the server IP address and port number." +msgstr "包含服务器 IP 地址和端口号的元组。" + +#: ../../en/software/tcp.server.rst:54 97c62995bc694283a8c754066ec4a002 +msgid "The maximum number of queued connections. Default is 5." +msgstr "排队连接的最大数量。默认值为 5。" + +#: ../../en/software/tcp.server.rst 1c5a0e8fd5754dd7a95c50b8809b0ce0 +#: 46c35b39b0bb41e7ab8d7e9bdb3910ca 74d158ab129244428971628aad13216d +#: 8261eb42f9db49279d0feb3168501f0a bc2c5c66199748b781014c06bec59ab6 +#: c653dc06337f4d128cd9c95918d6365b f2d76f43b71a4560a0e758fbbd184403 +msgid "Returns" +msgstr "" + +#: ../../en/software/tcp.server.rst:55 4b8383b2806e4ababb679af0543e91b3 +msgid "A socket object." +msgstr "一个套接字对象。" + +#: ../../en/software/tcp.server.rst 19dc63c8bb3b4772abd0d58834c099f9 +#: 47e6a2971e604a06a2f055c2885d2e9d 4c3c662b328b4bbbaa5406101d340ae9 +#: 5e77a7ed2577416fae4e211c369938ec a2d8d6274fbe4a22bf151155a767b370 +#: af0b591937e34daa8630428d8897b09b f4b678a773df459493415a28b0754922 +msgid "Return type" +msgstr "" + +#: ../../en/software/tcp.server.rst:60 0ada19ad18674374a6ec50c95922acf2 +msgid "|init.png|" +msgstr "" + +#: ../../en/refs/software.tcp.server.ref:5 525979b4984b44a1a9d4cbb8f7966528 +msgid "init.png" +msgstr "" + +#: ../../en/software/tcp.server.rst:76 b130a72a7be545e9b05181c985a4cd7a +msgid "Close the socket connection." +msgstr "关闭套接字连接。" + +#: ../../en/software/tcp.server.rst:80 a68ebf411f6245058acf8344e144bfa3 +msgid "|close.png|" +msgstr "" + +#: ../../en/refs/software.tcp.server.ref:2 cc822d7df7064f2d9c4494ca51a26a61 +msgid "close.png" +msgstr "" + +#: ../../en/software/tcp.server.rst:82 4fbbbe3fc5da472781e826da0ad0d3aa +msgid "|close_client.png|" +msgstr "" + +#: ../../en/refs/software.tcp.server.ref:3 0e2dbcd4a26d446599b4b8664689e5c2 +msgid "close_client.png" +msgstr "" + +#: ../../en/software/tcp.server.rst:94 701798a5f33549c39694bcfa2e2785c7 +msgid "" +"Accept a connection. The socket must be bound to an address and listening for " +"connections. The return value is a pair (conn, address) where conn is a new " +"socket object usable to send and receive data on the connection, and address is " +"the address bound to the socket on the other end of the connection." +msgstr "" +"接受连接。套接字必须绑定到地址并监听连接。返回值是一对 (conn, address),其中 " +"conn 是可用于在连接上发送和接收数据的新套接字对象,address 是绑定到连接另一端套" +"接字的地址。" + +#: ../../en/software/tcp.server.rst:100 5d18f6dcd92842c6b9b7bb7a0dbae014 +msgid "A tuple containing the client socket and the address of the client." +msgstr "包含客户端套接字和客户端地址的元组。" + +#: ../../en/software/tcp.server.rst:105 3abcf19293254f7aba6930d36105016d +msgid "|accept.png|" +msgstr "" + +#: ../../en/refs/software.tcp.server.ref:4 133a1b858f8c4d9d9fb756be7e6f5837 +msgid "accept.png" +msgstr "" + +#: ../../en/software/tcp.server.rst:118 dc7b2d0de4ae4ca380196ef184061821 +msgid "Receive data from the socket." +msgstr "从套接字接收数据。" + +#: ../../en/software/tcp.server.rst:120 cffec4f47b53428d9a9f464f9c303b66 +msgid "The maximum amount of data to be received at once." +msgstr "一次可接收的最大数据量。" + +#: ../../en/software/tcp.server.rst:121 ../../en/software/tcp.server.rst:141 +#: 0dbf1436e61f49388a28471ababdb372 36fbd28a58b54579bef060213cce3027 +msgid "The received data." +msgstr "接收到的数据。" + +#: ../../en/software/tcp.server.rst:126 a2a678fece474075be1a8900a9285c5b +msgid "|recv.png|" +msgstr "" + +#: ../../en/refs/software.tcp.server.ref:7 27d2d8b6609e41b5916742efc48dbdd0 +msgid "recv.png" +msgstr "" + +#: ../../en/software/tcp.server.rst:139 f945e7bfd0224bfd90a30ed652b6d59d +msgid "Read data from the socket." +msgstr "从套接字读取数据。" + +#: ../../en/software/tcp.server.rst:146 30b616a5692341338cb33087e7aa070e +msgid "|read.png|" +msgstr "" + +#: ../../en/refs/software.tcp.server.ref:6 1b522d3a45a8481494689e007285e06a +msgid "read.png" +msgstr "" + +#: ../../en/software/tcp.server.rst:159 0b98bef3f43d4aa19555d132d37e9d49 +msgid "Send data to the socket. The socket must be connected to a remote socket." +msgstr "向套接字发送数据。该套接字必须连接到远程套接字。" + +#: ../../en/software/tcp.server.rst:161 ../../en/software/tcp.server.rst:186 +#: 0bb765886b7f463890510ff92e55ea39 f878fa9c6842450d9f7db5c634f1640f +msgid "The data to be sent." +msgstr "要发送的数据。" + +#: ../../en/software/tcp.server.rst:163 ../../en/software/tcp.server.rst:188 +#: 8739cdc28ea0400d9baf807193ed9df5 a1f1cb37bf4c4f2981a231601ae3d176 +msgid "The number of bytes sent." +msgstr "已发送的字节数。" + +#: ../../en/software/tcp.server.rst:168 216bf30f73334510a57c2ba5cb328fd8 +msgid "|send.png|" +msgstr "" + +#: ../../en/refs/software.tcp.server.ref:9 a1acf3010c67447482f6b272cdd81bb8 +msgid "send.png" +msgstr "" + +#: ../../en/software/tcp.server.rst:181 9f48d1cd185f484eaf5937abea608f5b +msgid "" +"Write the buffer of bytes to the socket. This function will try to write all " +"data to a socket (no “short writes”). This may be not possible with a non-" +"blocking socket though, and returned value will be less than the length of buf." +msgstr "" +"将字节缓冲区写入套接字。此函数将尝试将所有数据写入套接字(无“短写入”)。不过,对" +"于非阻塞套接字,这可能无法实现,并且返回的值将小于 buf 的长度。" + +#: ../../en/software/tcp.server.rst:193 1af0f91faa314017b6dc3b8f84008791 +msgid "|write.png|" +msgstr "" + +#: ../../en/refs/software.tcp.server.ref:12 418a5524e8c94314889862701e7cd715 +msgid "write.png" +msgstr "" + +#: ../../en/software/tcp.server.rst:206 51060170cd0b459582d057e783b4d05e +msgid "" +"Set blocking or non-blocking mode of the socket: if flag is false, the socket " +"is set to non-blocking, else to blocking mode." +msgstr "" +"设置套接字的阻塞或非阻塞模式:如果标志为假,则套接字设置为非阻塞,否则设置为阻塞" +"模式。" + +#: ../../en/software/tcp.server.rst:209 8b39b75008d14a5880f3c18cfb63fa4b +msgid "This method is a shorthand for certain `settimeout()` calls:" +msgstr "此方法是某些 `settimeout()` 调用的简写:" + +#: ../../en/software/tcp.server.rst:211 3a1d0ae4bae94d79aec4a8114ffbd970 +msgid "``sock.setblocking(True)`` is equivalent to ``sock.settimeout(None)``" +msgstr "``sock.setblocking(True)`` 等同于 ``sock.settimeout(None)``" + +#: ../../en/software/tcp.server.rst:212 32a9d52f0c9f4debbcaffc648bccd86a +msgid "``sock.setblocking(False)`` is equivalent to ``sock.settimeout(0)``" +msgstr "``sock.setblocking(False)`` 等同于 ``sock.settimeout(0)``" + +#: ../../en/software/tcp.server.rst 169a50902b7e455082bdc2287ec66389 +msgid "param bool flag" +msgstr "" + +#: ../../en/software/tcp.server.rst:214 b2650ddab9574503b1624ddde4006e5b +msgid "" +"If True, the socket will be in blocking mode. If False, the socket will be in " +"non-blocking mode." +msgstr "如果为 True,套接字将处于阻塞模式。如果为 False,套接字将处于非阻塞模式。" + +#: ../../en/software/tcp.server.rst 83c3572cc9294a8ea78ee78f75801e47 +msgid "return" +msgstr "" + +#: ../../en/software/tcp.server.rst 6e2f578685ca4924ac26a434e4900a07 +msgid "rtype" +msgstr "" + +#: ../../en/software/tcp.server.rst:220 1feb68a35c2e46879e77927779e0ae88 +msgid "|setblocking.png|" +msgstr "" + +#: ../../en/refs/software.tcp.server.ref:14 cde1d7c6ca66474f88207f9efd68cf0b +msgid "setblocking.png" +msgstr "" + +#: ../../en/software/tcp.server.rst:233 ec32ba92fdc44fd7ad59916c45e50c53 +msgid "" +"Set a timeout on blocking socket operations. The value argument can be a " +"nonnegative floating point number expressing seconds, or None. If a non-zero " +"value is given, subsequent socket operations will raise an OSError exception if " +"the timeout period value has elapsed before the operation has completed. If " +"zero is given, the socket is put in non-blocking mode. If None is given, the " +"socket is put in blocking mode." +msgstr "" +"设置阻塞套接字操作的超时时间。值参数可以是表示秒数的非负浮点数,也可以是 None。" +"如果给出了非零值,则在操作完成之前如果超时时间值已过,则后续套接字操作将引发 " +"OSError 异常。如果给出了零,则套接字将处于非阻塞模式。如果给出了 None,则套接字" +"将处于阻塞模式。" + +#: ../../en/software/tcp.server.rst:240 3f0ab43aa4f647fbaf66eacf6e13c947 +msgid "The timeout value in seconds. If None, the socket is put in blocking mode." +msgstr "超时值(以秒为单位)。如果为 None,则套接字将处于阻塞模式。" + +#: ../../en/software/tcp.server.rst:246 d0ac2f87c4f64baea24e5cfb6a337752 +msgid "|settimeout.png|" +msgstr "" + +#: ../../en/refs/software.tcp.server.ref:15 eedd2e151c444a14a91a406bb2b79d15 +msgid "settimeout.png" +msgstr "" + +#: ../../en/software/tcp.server.rst:259 3f2d56c522634741bd9c897a2cbe412d +msgid "" +"Set the value of the given socket option. The needed symbolic constants are " +"defined in the socket module (SO_* etc.). The value can be an integer or a " +"bytes-like object representing a buffer." +msgstr "" +"设置给定套接字选项的值。所需的符号常量在套接字模块中定义(SO_* 等)。该值可以是" +"整数或表示缓冲区的字节类对象。" + +#: ../../en/software/tcp.server.rst:263 65fcc30c23094cd48270654b0c816b66 +msgid "The level at which the option is defined." +msgstr "定义选项的级别。" + +#: ../../en/software/tcp.server.rst:264 fe10ba7df6e24a1981edee0755ed034c +msgid "The name of the option to set." +msgstr "要设置的选项的名称。" + +#: ../../en/software/tcp.server.rst:265 df492888271d457697ef45363b30bb3c +msgid "The value to set for the option." +msgstr "为选项设置的值。" + +#: ../../en/software/tcp.server.rst:269 4c30411f165a49f48522f0ca3a7ec4aa +msgid "|setsockopt.png|" +msgstr "" + +#: ../../en/refs/software.tcp.server.ref:11 2c79d99058ad4f67a814861012bbad68 +msgid "setsockopt.png" +msgstr "" diff --git a/docs/locales/zh_CN/LC_MESSAGES/software/udp.client.po b/docs/locales/zh_CN/LC_MESSAGES/software/udp.client.po new file mode 100644 index 00000000..26154caf --- /dev/null +++ b/docs/locales/zh_CN/LC_MESSAGES/software/udp.client.po @@ -0,0 +1,245 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2016 - 2025 M5Stack Technology Co., Ltd +# This file is distributed under the same license as the UIFlow2 Programming +# Guide package. +# FIRST AUTHOR , 2025. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: UIFlow2 Programming Guide\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-04-03 11:49+0800\n" +"PO-Revision-Date: 2025-04-03 11:35+0800\n" +"Last-Translator: \n" +"Language: zh_CN\n" +"Language-Team: zh_CN \n" +"Plural-Forms: nplurals=1; plural=0;\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.16.0\n" + +#: ../../en/software/udp.client.rst:2 0a528c0383e1478bba5d54fd27e0e8f2 +msgid "UDP Client" +msgstr "UDP 客户端" + +#: ../../en/software/udp.client.rst:7 62e18ac8fe834b51b1ae288230962fd9 +msgid "UiFlow2 Example" +msgstr "UIFLOW2 应用示例" + +#: ../../en/software/udp.client.rst:10 d34e605a20bd4e9489b70f24b5074ead +msgid "simple client" +msgstr "简单客户端" + +#: ../../en/software/udp.client.rst:12 c566cc6b82964928bd485dbdb9e73ec3 +msgid "Open the |cores3_udp_client_example.m5f2| project in UiFlow2." +msgstr "在 UiFlow2 中打开 |cores3_udp_client_example.m5f2| 项目。" + +#: ../../en/software/udp.client.rst:14 ../../en/software/udp.client.rst:30 +#: c8e2e14e35714fefba447b9dc3950cd5 e09475f1c50e47c582892a9ef0ccf472 +msgid "This example creates a UDP client that sends data to a server." +msgstr "此示例创建一个向服务器发送数据的 UDP 客户端。" + +#: ../../en/software/udp.client.rst:16 ../../en/software/udp.client.rst:56 +#: ../../en/software/udp.client.rst:75 ../../en/software/udp.client.rst:95 +#: ../../en/software/udp.client.rst:115 ../../en/software/udp.client.rst:137 +#: ../../en/software/udp.client.rst:162 ../../en/software/udp.client.rst:185 +#: 07578219b6ed42d8bbaed47c8ee2287c 491aea1073d94d57bcc8cc34a2ab26d5 +#: 4f1839566f4b4058849b92cb06f124c8 5c0359f4ede847cbb35b3fc3ba48f091 +#: c5353a8ef9f04603be101b40d83d1716 c5580b38497d46e98f07cb81b113b72b +#: d6787e23d7c64a01801a55524971f393 f19bd2f6f73147fbbf5e1ad66a9c9ea8 +msgid "UiFlow2 Code Block:" +msgstr "UiFlow2 代码块:" + +#: ../../en/software/udp.client.rst:18 78fa70a7fa8c469a94cc1f41d1f1a64d +msgid "|cores3_udp_client_example.png|" +msgstr "" + +#: ../../en/refs/software.udp.client.ref:10 7682e67c17d24069a29b5be2270a7436 +msgid "cores3_udp_client_example.png" +msgstr "" + +#: ../../en/software/udp.client.rst:20 ../../en/software/udp.client.rst:38 +#: 763f50bd156741b89f0afaea94101af9 b632d2a9b73944c683023e1203f4355a +msgid "Example output:" +msgstr "示例输出:" + +#: ../../en/software/udp.client.rst:22 ../../en/software/udp.client.rst:40 +#: a3102655616245cf965855bf195591bd d1ee4d320734478d9a05c2efb4027549 +msgid "None" +msgstr "" + +#: ../../en/software/udp.client.rst:25 e531617aebd342d1b17c1352451678b9 +msgid "MicroPython Example" +msgstr "MicroPython 应用示例" + +#: ../../en/software/udp.client.rst:28 bc4367d1dfa343f8b335723ae5d60fd8 +msgid "sample client" +msgstr "简单客户端" + +#: ../../en/software/udp.client.rst:32 ../../en/software/udp.client.rst:60 +#: ../../en/software/udp.client.rst:79 ../../en/software/udp.client.rst:99 +#: ../../en/software/udp.client.rst:119 ../../en/software/udp.client.rst:141 +#: ../../en/software/udp.client.rst:166 ../../en/software/udp.client.rst:189 +#: 02a87cc640c048afb19e7cc81be65eb7 30ff11cca97042c18b29c70bf8cba834 +#: 3d65858c366649b3af836852463b3526 576e33629be343a5be9f1fd98c301128 +#: 964f34c7e1fb49dab5a34bd324897cdf c18dcb8753f340988e80bed7376bd700 +#: e17504b08cd74dd0aec3b7d652660b95 e3352431b4214c0f804933e5f4fa9522 +msgid "MicroPython Code Block:" +msgstr "MicroPython 代码块:" + +#: ../../en/software/udp.client.rst:44 38085219afa747c2a9545202a657754d +msgid "**API**" +msgstr "API参考" + +#: ../../en/software/udp.client.rst:50 d8eb45ac5513450e901c0efb2bc60d05 +msgid "Create a socket object and connect to the server." +msgstr "创建一个套接字对象并连接到服务器。" + +#: ../../en/software/udp.client.rst 1a7e34dfe87c41e19d63660e89684d25 +#: 9b0307647b6843c3b0f5735d7333006b aa4b11b381594f1fbb965fc2853ddbec +#: ba01636c4cd84ed5ba789c2c642df4cb c452d362231d4424ab4bf32b7b5842a0 +msgid "Parameters" +msgstr "" + +#: ../../en/software/udp.client.rst:52 2840a881b4144dc29a696aeb4ee30eac +msgid "A tuple containing the server IP address and port number." +msgstr "包含服务器 IP 地址和端口号的元组。" + +#: ../../en/software/udp.client.rst 002c3ef2170a4b668b2d1ce8bed6c568 +#: 3da3e39f2631465e96dd819c50821217 b45d582b8f4c4afdad90be9faba96457 +#: d0893fad547143bfa2522d35195eca35 dd3bb143f5504859934bc31339d2d766 +msgid "Returns" +msgstr "" + +#: ../../en/software/udp.client.rst:53 bc9b80df2a984d969eecced5833d356e +msgid "A socket object." +msgstr "一个套接字对象。" + +#: ../../en/software/udp.client.rst 1450f82b10284002b646ae57fdce7328 +#: 8dc42167a89447a7a1dd05e35c5a4e79 9091013762394793b696ab2d44318b51 +#: b5f1a74d37e74aa6a6c078796181d6f4 ff4e8652f01b4db1a65358e3d3d7cbd9 +msgid "Return type" +msgstr "" + +#: ../../en/software/udp.client.rst:58 3b9ec4363c864ef4af0102820f8477a5 +msgid "|init.png|" +msgstr "" + +#: ../../en/refs/software.udp.client.ref:3 dcc408c11d78491aa63c8f8cfeb1e0b6 +msgid "init.png" +msgstr "" + +#: ../../en/software/udp.client.rst:73 0760c6f39f9f4932afbc5c5b2dbff1d9 +msgid "Close the socket connection." +msgstr "关闭套接字连接。" + +#: ../../en/software/udp.client.rst:77 81f8e5ed73904c798bea0f2be9857efb +msgid "|close.png|" +msgstr "" + +#: ../../en/refs/software.udp.client.ref:2 8f03a3227abc4c178bd1951e7e023953 +msgid "close.png" +msgstr "" + +#: ../../en/software/udp.client.rst:89 05a649a48fed4cbda5d44f2d4c0c6587 +msgid "Receive data from the socket." +msgstr "从套接字接收数据。" + +#: ../../en/software/udp.client.rst:91 643ad60f52f541a0a655cf124f97213e +msgid "The maximum amount of data to be received at once." +msgstr "一次可接收的最大数据量。" + +#: ../../en/software/udp.client.rst:92 ../../en/software/udp.client.rst:112 +#: 30864224da054da2b9bb3a71a63ad9d6 6a569b67b0074506b10eb66d9346d54b +msgid "The received data." +msgstr "接收到的数据。" + +#: ../../en/software/udp.client.rst:97 1448c467b8d04affa8dc62f703e64f39 +msgid "|recv.png|" +msgstr "" + +#: ../../en/refs/software.udp.client.ref:5 1291d8e82e9a4101b94415cd67b92490 +msgid "recv.png" +msgstr "" + +#: ../../en/software/udp.client.rst:110 a75cb8dbdedb47b38dcfa6675f068ff8 +msgid "Read data from the socket." +msgstr "" + +#: ../../en/software/udp.client.rst:117 ad5a77bf21e342578cd86248a29111d5 +msgid "|read.png|" +msgstr "" + +#: ../../en/refs/software.udp.client.ref:4 835b40b79cae4430bab9f57837704bda +msgid "read.png" +msgstr "" + +#: ../../en/software/udp.client.rst:130 b00b59a24f184d9ebfb1d2817d6bc450 +msgid "Send data to the socket. The socket must be connected to a remote socket." +msgstr "向套接字发送数据。该套接字必须连接到远程套接字。" + +#: ../../en/software/udp.client.rst:132 ../../en/software/udp.client.rst:157 +#: 63cff285ee4147f3af158738759f6234 ba19d636a9754ea7b478eb5971315e6b +msgid "The data to be sent." +msgstr "The data to be sent." + +#: ../../en/software/udp.client.rst:134 ../../en/software/udp.client.rst:159 +#: 216f70d04201456cb7528c858c451e4e db87f57a19ab46e2ba306f14bca392ed +msgid "The number of bytes sent." +msgstr "已发送的字节数。" + +#: ../../en/software/udp.client.rst:139 7e85942aa4f14924ba3318a6d2ff0c10 +msgid "|send.png|" +msgstr "" + +#: ../../en/refs/software.udp.client.ref:6 77394ffd34d346e9a5ebabe285e34bfe +msgid "send.png" +msgstr "" + +#: ../../en/software/udp.client.rst:152 c8c5c547b33b487f81ec8385ab8cb674 +msgid "" +"Write the buffer of bytes to the socket. This function will try to write " +"all data to a socket (no “short writes”). This may be not possible with a" +" non-blocking socket though, and returned value will be less than the " +"length of buf." +msgstr "" +"将字节缓冲区写入套接字。此函数将尝试将所有数据写入套接字(无“短写入”)。不过,对于非阻塞套接字,这可能无法实现,并且返回的值将小于 buf " +"的长度。" + +#: ../../en/software/udp.client.rst:164 e8c96ebf6d934f4b84497a93ed1b12a9 +msgid "|write.png|" +msgstr "" + +#: ../../en/refs/software.udp.client.ref:8 21fbdf8c954a497d9f6da054f7c4d1d7 +msgid "write.png" +msgstr "" + +#: ../../en/software/udp.client.rst:177 f7bfb5103b3d43b9bb6dff4f544e6f22 +msgid "" +"Set the value of the given socket option. The needed symbolic constants " +"are defined in the socket module (SO_* etc.). The value can be an integer" +" or a bytes-like object representing a buffer." +msgstr "设置给定套接字选项的值。所需的符号常量在套接字模块中定义(SO_* 等)。该值可以是整数或表示缓冲区的字节类对象。" + +#: ../../en/software/udp.client.rst:181 261cd4f469954396af46c2d5f6a1a741 +msgid "The level at which the option is defined." +msgstr "定义选项的级别。" + +#: ../../en/software/udp.client.rst:182 d1231b780a0e499d851741e2f7736ba1 +msgid "The name of the option to set." +msgstr "要设置的选项的名称。" + +#: ../../en/software/udp.client.rst:183 6e1a6e5c8ed547e3acfbbeb59c2263bf +msgid "The value to set for the option." +msgstr "为选项设置的值。" + +#: ../../en/software/udp.client.rst:187 c41c9ca02e9f47028cf7f8686cd4d956 +msgid "|setsockopt.png|" +msgstr "" + +#: ../../en/refs/software.udp.client.ref:7 afe6934a7a9a4d8abfbfca013f7706e3 +msgid "setsockopt.png" +msgstr "" + diff --git a/docs/locales/zh_CN/LC_MESSAGES/software/udp.po b/docs/locales/zh_CN/LC_MESSAGES/software/udp.po new file mode 100644 index 00000000..37c9986e --- /dev/null +++ b/docs/locales/zh_CN/LC_MESSAGES/software/udp.po @@ -0,0 +1,40 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2016 - 2025 M5Stack Technology Co., Ltd +# This file is distributed under the same license as the UIFlow2 Programming +# Guide package. +# FIRST AUTHOR , 2025. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: UIFlow2 Programming Guide\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-04-03 10:58+0800\n" +"PO-Revision-Date: 2025-04-03 11:35+0800\n" +"Last-Translator: \n" +"Language-Team: zh_CN \n" +"Language: zh_CN\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=1; plural=0;\n" +"Generated-By: Babel 2.16.0\n" +"X-Generator: Poedit 3.5\n" + +#: ../../en/software/udp.rst:2 1fc1e669f73a43ad8f33a2088b868405 +msgid "UDP" +msgstr "" + +#: ../../en/software/udp.rst:4 631a8df268e044029c36bc7466bca9f3 +msgid "" +"The UDP module provides a simple way to send and receive data over the " +"network using the User Datagram Protocol (UDP). It is designed for low-" +"latency communication and is suitable for applications where speed is " +"more critical than reliability." +msgstr "" +"UDP 模块提供了一种使用用户数据报协议 (UDP) 通过网络发送和接收数据的简单" +"方法。它专为低延迟通信而设计,适合速度比可靠性更重要的应用程序。" + +#: ../../en/software/udp.rst:11 9fc3e18c53d246b4847e9e6eab86eab9 +msgid "API" +msgstr "API参考" diff --git a/docs/locales/zh_CN/LC_MESSAGES/software/udp.server.po b/docs/locales/zh_CN/LC_MESSAGES/software/udp.server.po new file mode 100644 index 00000000..42d59950 --- /dev/null +++ b/docs/locales/zh_CN/LC_MESSAGES/software/udp.server.po @@ -0,0 +1,290 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2016 - 2025 M5Stack Technology Co., Ltd +# This file is distributed under the same license as the UIFlow2 Programming +# Guide package. +# FIRST AUTHOR , 2025. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: UIFlow2 Programming Guide\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-04-03 10:58+0800\n" +"PO-Revision-Date: 2025-04-03 11:46+0800\n" +"Last-Translator: \n" +"Language-Team: zh_CN \n" +"Language: zh_CN\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=1; plural=0;\n" +"Generated-By: Babel 2.16.0\n" +"X-Generator: Poedit 3.5\n" + +#: ../../en/software/udp.server.rst:2 d8278a3649524449b1afe77c149b3821 +msgid "UDP Server" +msgstr "UDP 服务端" + +#: ../../en/software/udp.server.rst:7 678b4f40b18c4beb9acdb97a577c062b +msgid "UiFlow2 Example" +msgstr "UIFLOW2 应用示例" + +#: ../../en/software/udp.server.rst:10 ../../en/software/udp.server.rst:28 +#: 77e87e5090a440eba357ca0784f12f01 a360624077bb48ee8e4d3093a79a98f3 +msgid "echo server" +msgstr "回显服务器" + +#: ../../en/software/udp.server.rst:12 0a52892ebf844e51b03be158b5cf113d +msgid "Open the |cores3_udp_server_example.m5f2| project in UiFlow2." +msgstr "在 UiFlow2 中打开 |cores3_udp_server_example.m5f2| 项目。" + +#: ../../en/software/udp.server.rst:14 ../../en/software/udp.server.rst:30 +#: 35b7c5e4c8b54779836c53c32fb812c9 c8404cad65a847fdaad85e54e32698d8 +msgid "This example creates a UDP server that echoes the received data." +msgstr "此示例创建一个回显接收数据的 UDP 服务器。" + +#: ../../en/software/udp.server.rst:16 ../../en/software/udp.server.rst:56 +#: ../../en/software/udp.server.rst:75 ../../en/software/udp.server.rst:95 +#: ../../en/software/udp.server.rst:118 ../../en/software/udp.server.rst:138 +#: ../../en/software/udp.server.rst:160 ../../en/software/udp.server.rst:183 +#: ../../en/software/udp.server.rst:207 ../../en/software/udp.server.rst:230 +#: 0f45d1f4a15d46dd8183672881a3080d 23f9ecc4f19e4314abee3f2e931f547f +#: 243506d6337e4f24a631b0ff41d53474 2f873e916ed94d7b943479106704823e +#: 568a690482c14047b34ff60b161ca878 8d9f66bd768040b88c891b9dededab71 +#: ce4c1d5055fd4f7dab53f03f42a90d16 efcf1f3224d84d259f17a4e6111c6938 +#: f4a2182df6054ee3b35ca6373a43d912 f843faa5c551484dbc4611d3c085bd28 +msgid "UiFlow2 Code Block:" +msgstr "UiFlow2 代码块:" + +#: ../../en/software/udp.server.rst:18 0d7516e45945441ab3bf5577c7e156f3 +msgid "|cores3_udp_server_example.png|" +msgstr "" + +#: ../../en/refs/software.udp.server.ref:12 6a1830ce6b7147e38c33c22b9bfa2d33 +msgid "cores3_udp_server_example.png" +msgstr "" + +#: ../../en/software/udp.server.rst:20 ../../en/software/udp.server.rst:38 +#: 74430ad2282d421d937c121737481f61 d596d36ee86945a6a83cfde6f354cc32 +msgid "Example output:" +msgstr "示例输出:" + +#: ../../en/software/udp.server.rst:22 ../../en/software/udp.server.rst:40 +#: 22df65fb83b94b429ac942a483c0c9fa 36fa50ae0ce24ccea5dcdeb075b7e4e0 +msgid "None" +msgstr "" + +#: ../../en/software/udp.server.rst:25 bff1fd419fa247b987dd32d02e2253d7 +msgid "MicroPython Example" +msgstr "MicroPython 应用示例" + +#: ../../en/software/udp.server.rst:32 ../../en/software/udp.server.rst:60 +#: ../../en/software/udp.server.rst:79 ../../en/software/udp.server.rst:99 +#: ../../en/software/udp.server.rst:122 ../../en/software/udp.server.rst:142 +#: ../../en/software/udp.server.rst:164 ../../en/software/udp.server.rst:187 +#: ../../en/software/udp.server.rst:211 ../../en/software/udp.server.rst:234 +#: 3d019d0fbaf542f39c20b5823e536474 3e8fbab2b07a444682d897d75978a692 +#: 5f35f5532afa4b8aa192658154523e45 7d4bec7751fa4a98814aeb347c459cfe +#: 8fabbec91c9341488b13da6ff29399ff 914eff3d3e3c4b108dec36ff8cae4d54 +#: 985fed4eae0f41869ddd0d34cd54f51b b54be1111c3f4e39918cb1d78640c6f7 +#: b97c474cb9b4430480e520983ac425d8 ef2d11f5480540a4b33e8b6799ecadf4 +msgid "MicroPython Code Block:" +msgstr "MicroPython 代码块:" + +#: ../../en/software/udp.server.rst:44 deaf999efa4b41e0af49b4d35950135e +msgid "**API**" +msgstr "API参考" + +#: ../../en/software/udp.server.rst:50 2d7c5f7a2af64a98946567e9b68475fa +msgid "Create a socket object and bind it to an address." +msgstr "创建一个套接字对象并将其绑定到一个地址。" + +#: ../../en/software/udp.server.rst 039a3243e0ba4094b31e4840a973e7df +#: 0e22121ea2aa4366a043b17473dc2a6e 541cf0e79e444be3b82de6b1dc0375df +#: 7755013a9a474788b402356cdcedf9a7 818fb16a3d9d48948028ab6bab3a8871 +#: 90731081ef0f47faa6dad2b5a5a21d3f dce167b36160484f869f6e602d77b562 +msgid "Parameters" +msgstr "" + +#: ../../en/software/udp.server.rst:52 03a20acd85f146dcaa190107ac24e7b5 +msgid "A tuple containing the server IP address and port number." +msgstr "包含服务器 IP 地址和端口号的元组。" + +#: ../../en/software/udp.server.rst 0a6a6317f55a4709b47f5b355928701e +#: 2b91d1ff67424c09b3abb9e5520336a9 53aeffaa8c3f4fe8ad959fb6c3ecaafb +#: 571e6a0295eb483b9da68be10aeb92a2 619a26358c3b462f9b6c11caa1c04c19 +#: 6d5b970aff874441bb833b23e38b62cd fd1db73d3d5e43f78a83951fff66cdeb +msgid "Returns" +msgstr "" + +#: ../../en/software/udp.server.rst:53 cd665cafc1e54da3b776ce6bcaf534b0 +msgid "A socket object." +msgstr "一个套接字对象。" + +#: ../../en/software/udp.server.rst 2623960717aa4cc69d4d5e0e6f43bc61 +#: 5181a390d1f941c38e1fc6b207947a8b 5d57309a2b994a2083d9395b7cd036a5 +#: 8ce80c3de85b480aa8340c55b911d8da b9eb29b03a6942018b7104342350f1e8 +#: f495bef1896c426e917ad4805177ebb4 fc490a31e82148ba830abe40ed8e2549 +msgid "Return type" +msgstr "" + +#: ../../en/software/udp.server.rst:58 5b8910d543ca4678910836c5031f79ff +msgid "|init.png|" +msgstr "" + +#: ../../en/refs/software.udp.server.ref:3 1a7d3ff0489a45d892f9aba9c04ceb7c +msgid "init.png" +msgstr "" + +#: ../../en/software/udp.server.rst:73 17863b9f41b3435b9bfa631ee7bbe49b +msgid "Close the socket connection." +msgstr "关闭套接字连接。" + +#: ../../en/software/udp.server.rst:77 11be5e7b8da3409480c6e9470b1a1fa3 +msgid "|close.png|" +msgstr "" + +#: ../../en/refs/software.udp.server.ref:2 0d8e1061cb8841ffa9e8a2763de94db9 +msgid "close.png" +msgstr "" + +#: ../../en/software/udp.server.rst:89 954efece7233495eb138163ed5d527f7 +msgid "Receive data from the socket." +msgstr "从套接字接收数据。" + +#: ../../en/software/udp.server.rst:91 ../../en/software/udp.server.rst:114 +#: bf706e7f26404df1b1d8eda21c6cd047 dc17f7b773374bcf9ab2d76a1a9292ab +msgid "The maximum amount of data to be received at once." +msgstr "一次可接收的最大数据量。" + +#: ../../en/software/udp.server.rst:92 ../../en/software/udp.server.rst:115 +#: ../../en/software/udp.server.rst:135 4405ab323aee4b568be741ff8504ffcf +#: 9be6abe5f6494cccb01dd50b342db849 b55eafc357214bfd9c67a19a821d89f1 +msgid "The received data." +msgstr "接收到的数据。" + +#: ../../en/software/udp.server.rst:97 a73b614ca3e241a1af762f970b606298 +msgid "|recv.png|" +msgstr "" + +#: ../../en/refs/software.udp.server.ref:5 a6e14e094a6c4212bc3c78b1c4157258 +msgid "recv.png" +msgstr "" + +#: ../../en/software/udp.server.rst:110 da8600409723470bbe4852184f0702c9 +msgid "" +"Receive data from the socket. The return value is a pair (bytes, address) " +"where bytes is a bytes object representing the data received and address is " +"the address of the socket sending the data." +msgstr "" +"从套接字接收数据。返回值是一对 (bytes, address),其中 bytes 是表示接收数据的字" +"节对象,address 是发送数据的套接字的地址。" + +#: ../../en/software/udp.server.rst:120 e6ec55e9d6ed4ee19cd0f92da84cf09e +msgid "|recvfrom.png|" +msgstr "" + +#: ../../en/refs/software.udp.server.ref:6 d3ed6071981642ac8932686f2477098b +msgid "recvfrom.png" +msgstr "" + +#: ../../en/software/udp.server.rst:133 df963c32cd014709821ba86bfb6beb43 +msgid "Read data from the socket." +msgstr "从套接字读取数据。" + +#: ../../en/software/udp.server.rst:140 66d24f6b126947f1b5e6870da7f13f9b +msgid "|read.png|" +msgstr "" + +#: ../../en/refs/software.udp.server.ref:4 02c01566d85d4c85843a19e00769637a +msgid "read.png" +msgstr "" + +#: ../../en/software/udp.server.rst:153 69ce596f9628428096fc2400f7653fb0 +msgid "Send data to the socket. The socket must be connected to a remote socket." +msgstr "向套接字发送数据。该套接字必须连接到远程套接字。" + +#: ../../en/software/udp.server.rst:155 ../../en/software/udp.server.rst:177 +#: ../../en/software/udp.server.rst:202 0f2ee727d45146029b90e19ead4e8c10 +#: 2a40f52341474e549d71ba913224763d 7736742137dc4109ac7ae300c11eff78 +msgid "The data to be sent." +msgstr "要发送的数据。" + +#: ../../en/software/udp.server.rst:157 ../../en/software/udp.server.rst:180 +#: ../../en/software/udp.server.rst:204 00c8d6d1cad8417ab478c4a965985334 +#: 35946b7d3f204f3baed4921da9792908 ef708cb33ee240c29c72e291054d8e93 +msgid "The number of bytes sent." +msgstr "已发送的字节数。" + +#: ../../en/software/udp.server.rst:162 f04d829ffc254afbb5dc6b5bae7b2e52 +msgid "|send.png|" +msgstr "" + +#: ../../en/refs/software.udp.server.ref:7 56ac5a0f3b7a4d2da79b724a9cb150a3 +msgid "send.png" +msgstr "" + +#: ../../en/software/udp.server.rst:175 ddeb18e6288644b5ae134c3bce22f786 +msgid "" +"Send data to the socket. The socket should not be connected to a remote " +"socket, since the destination socket is specified by address." +msgstr "" +"将数据发送到套接字。套接字不应连接到远程套接字,因为目标套接字由地址指定。" + +#: ../../en/software/udp.server.rst:179 d3f262bae5fc4243880f0312f1e4c295 +msgid "A tuple containing the destination IP address and port number." +msgstr "包含目标 IP 地址和端口号的元组。" + +#: ../../en/software/udp.server.rst:185 05a267a5a9324348bebfac38457321d8 +msgid "|sendto.png|" +msgstr "" + +#: ../../en/refs/software.udp.server.ref:8 99bdc9c0e03a477fbd0fb857e0d49076 +msgid "sendto.png" +msgstr "" + +#: ../../en/software/udp.server.rst:197 fa465c1ddbe948c4b3a2f498ff9d58d5 +msgid "" +"Write the buffer of bytes to the socket. This function will try to write all " +"data to a socket (no “short writes”). This may be not possible with a non-" +"blocking socket though, and returned value will be less than the length of buf." +msgstr "" +"将字节缓冲区写入套接字。此函数将尝试将所有数据写入套接字(无“短写入”)。不过," +"对于非阻塞套接字,这可能无法实现,并且返回的值将小于 buf 的长度。" + +#: ../../en/software/udp.server.rst:209 d96dbcb4249049c29699bcd4b9bf2a77 +msgid "|write.png|" +msgstr "" + +#: ../../en/refs/software.udp.server.ref:10 49d03c97021a4ede923a60cff229ec5d +msgid "write.png" +msgstr "" + +#: ../../en/software/udp.server.rst:222 ee461e7031964994b04464aeaa9b0289 +msgid "" +"Set the value of the given socket option. The needed symbolic constants are " +"defined in the socket module (SO_* etc.). The value can be an integer or a " +"bytes-like object representing a buffer." +msgstr "" +"设置给定套接字选项的值。所需的符号常量在套接字模块中定义(SO_* 等)。该值可以是" +"整数或表示缓冲区的字节类对象。" + +#: ../../en/software/udp.server.rst:226 b755a9915142458da18627238f06da89 +msgid "The level at which the option is defined." +msgstr "定义选项的级别。" + +#: ../../en/software/udp.server.rst:227 c1537d0619ac478891e153eec1390a65 +msgid "The name of the option to set." +msgstr "要设置的选项的名称。" + +#: ../../en/software/udp.server.rst:228 980be093b553474b8bf3e6d885743f2c +msgid "The value to set for the option." +msgstr "为选项设置的值。" + +#: ../../en/software/udp.server.rst:232 1b7a6c1cc72f4ad48b99aa8f0528d8b3 +msgid "|setsockopt.png|" +msgstr "" + +#: ../../en/refs/software.udp.server.ref:9 ba854416ac344093b0c0b897d642f9fe +msgid "setsockopt.png" +msgstr "" diff --git a/examples/softwave/tcp/cores3_tcp_client_example.m5f2 b/examples/softwave/tcp/cores3_tcp_client_example.m5f2 new file mode 100644 index 00000000..ce07edb4 --- /dev/null +++ b/examples/softwave/tcp/cores3_tcp_client_example.m5f2 @@ -0,0 +1 @@ +{"version":"V2.0","versionNumber":"V2.2.4","type":"cores3","components":[{"name":"screen","type":"screen","layer":0,"screenId":"builtin","screenName":"","id":"__cores3_screen","createTime":1743586147974,"x":0,"y":0,"width":320,"height":240,"backgroundColor":"#222222","size":0,"isSelected":true},{"name":"title0","type":"title","layer":1,"screenId":"builtin","screenName":"","id":"s3Ml$$K#`_jZ!sb&","createTime":1743587486944,"x":0,"y":0,"color":"#ffffff","backgroundColor":"#0000FF","text":"TCP Client","textOffset":3,"font":"Widgets.FONTS.DejaVu24","isSelected":false},{"name":"label0","type":"label","layer":2,"screenId":"builtin","screenName":"","id":"w@UkG*AyNQ=Em-%t","createTime":1743587503703,"x":4,"y":32,"color":"#ffffff","backgroundColor":"#222222","text":"Local IP:","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false,"width":78,"height":20},{"name":"label1","type":"label","layer":3,"screenId":"builtin","screenName":"","id":"zFFHa`y4O7#CV5tc","createTime":1743587505613,"x":4,"y":60,"color":"#ffffff","backgroundColor":"#222222","text":"Server IP:","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false,"width":91,"height":20},{"name":"label2","type":"label","layer":4,"screenId":"builtin","screenName":"","id":"g4wQE#f2yya7L0sm","createTime":1743587506651,"x":4,"y":88,"color":"#ffffff","backgroundColor":"#222222","text":"Server Port","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false,"width":106,"height":20},{"name":"label3","type":"label","layer":5,"screenId":"builtin","screenName":"","id":"oV6H=3Ysu*wEwFhY","createTime":1743587507777,"x":4,"y":118,"color":"#ffffff","backgroundColor":"#222222","text":"Send:","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false,"width":54,"height":20},{"name":"label4","type":"label","layer":6,"screenId":"builtin","screenName":"","id":"vNmLqrn8iySSbpIi","createTime":1743587513026,"x":4,"y":144,"color":"#ffffff","backgroundColor":"#222222","text":"Recv:","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false,"width":51,"height":20},{"name":"label5","type":"label","layer":7,"screenId":"builtin","screenName":"","id":"i`Gc7VY_b%cDTxg4","createTime":1743587516223,"x":136,"y":32,"color":"#ffffff","backgroundColor":"#222222","text":"label5","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false,"width":58,"height":20},{"name":"label6","type":"label","layer":8,"screenId":"builtin","screenName":"","id":"mEXso*6lp@A-=lKG","createTime":1743587517499,"x":136,"y":60,"color":"#ffffff","backgroundColor":"#222222","text":"label6","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false,"width":58,"height":20},{"name":"label7","type":"label","layer":9,"screenId":"builtin","screenName":"","id":"mxyvH1yp&5wsfCf4","createTime":1743587518757,"x":136,"y":88,"color":"#ffffff","backgroundColor":"#222222","text":"label7","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false,"width":58,"height":20},{"name":"label8","type":"label","layer":10,"screenId":"builtin","screenName":"","id":"yA&s8f#rPz$@@8*c","createTime":1743587520314,"x":136,"y":118,"color":"#ffffff","backgroundColor":"#222222","text":"label8","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false,"width":58,"height":20},{"name":"label9","type":"label","layer":11,"screenId":"builtin","screenName":"","id":"q@f30PRsKEBQruJm","createTime":1743587526890,"x":136,"y":144,"color":"#ffffff","backgroundColor":"#222222","text":"label9","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false,"width":58,"height":20}],"resources":[{"software":["tcp"]},{"hardware":["hardware_button","hardware_pin_button","imu","speaker","touch","als","mic"]}],"units":[],"hats":[],"bases":[],"i2cs":[],"blockly":"countdatatrueTrue192.168.8.2368001count0label5Labellabel6192.168.8.137label78001truecountlabel88001count100data1024count1datalabel98001data1count","screen":[{"simulationName":"Built-in","type":"builtin","width":320,"height":240,"scale":0.78,"screenName":"","blockId":"","screenColorType":0,"id":"builtin","createTime":1743586147965}],"logicWhenNum":0,"customList":[]} \ No newline at end of file diff --git a/examples/softwave/tcp/cores3_tcp_client_example.py b/examples/softwave/tcp/cores3_tcp_client_example.py new file mode 100644 index 00000000..91bda721 --- /dev/null +++ b/examples/softwave/tcp/cores3_tcp_client_example.py @@ -0,0 +1,114 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import os, sys, io +import M5 +from M5 import * +import socket +import network +import time + + +title0 = None +label0 = None +label1 = None +label2 = None +label3 = None +label4 = None +label5 = None +label6 = None +label7 = None +label8 = None +label9 = None +tcpc = None +wlan = None + + +count = None +data = None + + +def setup(): + global \ + title0, \ + label0, \ + label1, \ + label2, \ + label3, \ + label4, \ + label5, \ + label6, \ + label7, \ + label8, \ + label9, \ + tcpc, \ + wlan, \ + count, \ + data + + M5.begin() + Widgets.fillScreen(0x222222) + title0 = Widgets.Title("TCP Client", 3, 0xFFFFFF, 0x0000FF, Widgets.FONTS.DejaVu24) + label0 = Widgets.Label("Local IP:", 4, 32, 1.0, 0xFFFFFF, 0x222222, Widgets.FONTS.DejaVu18) + label1 = Widgets.Label("Server IP:", 4, 60, 1.0, 0xFFFFFF, 0x222222, Widgets.FONTS.DejaVu18) + label2 = Widgets.Label("Server Port", 4, 88, 1.0, 0xFFFFFF, 0x222222, Widgets.FONTS.DejaVu18) + label3 = Widgets.Label("Send:", 4, 118, 1.0, 0xFFFFFF, 0x222222, Widgets.FONTS.DejaVu18) + label4 = Widgets.Label("Recv:", 4, 144, 1.0, 0xFFFFFF, 0x222222, Widgets.FONTS.DejaVu18) + label5 = Widgets.Label("label5", 136, 32, 1.0, 0xFFFFFF, 0x222222, Widgets.FONTS.DejaVu18) + label6 = Widgets.Label("label6", 136, 60, 1.0, 0xFFFFFF, 0x222222, Widgets.FONTS.DejaVu18) + label7 = Widgets.Label("label7", 136, 88, 1.0, 0xFFFFFF, 0x222222, Widgets.FONTS.DejaVu18) + label8 = Widgets.Label("label8", 136, 118, 1.0, 0xFFFFFF, 0x222222, Widgets.FONTS.DejaVu18) + label9 = Widgets.Label("label9", 136, 144, 1.0, 0xFFFFFF, 0x222222, Widgets.FONTS.DejaVu18) + + wlan = network.WLAN(network.STA_IF) + wlan.active(True) + tcpc = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + tcpc.connect(("192.168.8.236", 8001)) + count = 0 + label5.setText(str(wlan.ifconfig()[0])) + label6.setText(str("192.168.8.137")) + label7.setText(str("8001")) + + +def loop(): + global \ + title0, \ + label0, \ + label1, \ + label2, \ + label3, \ + label4, \ + label5, \ + label6, \ + label7, \ + label8, \ + label9, \ + tcpc, \ + wlan, \ + count, \ + data + tcpc.send(str(count)) + label8.setText(str(count)) + time.sleep_ms(100) + data = tcpc.recv(1024) + count = (count if isinstance(count, (int, float)) else 0) + 1 + if not len(data): + pass + label9.setText(str(data)) + time.sleep(1) + M5.update() + + +if __name__ == "__main__": + try: + setup() + while True: + loop() + except (Exception, KeyboardInterrupt) as e: + try: + from utility import print_error_msg + + print_error_msg(e) + except ImportError: + print("please update to latest firmware") diff --git a/examples/softwave/tcp/cores3_tcp_server_example.m5f2 b/examples/softwave/tcp/cores3_tcp_server_example.m5f2 new file mode 100644 index 00000000..f2400750 --- /dev/null +++ b/examples/softwave/tcp/cores3_tcp_server_example.m5f2 @@ -0,0 +1 @@ +{"version":"V2.0","versionNumber":"V2.2.4","type":"cores3","components":[{"name":"screen","type":"screen","layer":0,"screenId":"builtin","screenName":"","id":"__cores3_screen","createTime":1743580828731,"x":0,"y":0,"width":320,"height":240,"backgroundColor":"#222222","size":0,"isSelected":true},{"name":"label0","type":"label","layer":1,"screenId":"builtin","screenName":"","id":"vp0EAKKY+Iz!+XB%","createTime":1743584584091,"x":4,"y":32,"color":"#ffffff","backgroundColor":"#222222","text":"Local IP:","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false,"width":78,"height":21},{"name":"label1","type":"label","layer":2,"screenId":"builtin","screenName":"","id":"fF3B^pT1@D4g_bsJ","createTime":1743584585459,"x":4,"y":60,"color":"#ffffff","backgroundColor":"#222222","text":"Local Port:","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false,"width":99,"height":21},{"name":"label2","type":"label","layer":3,"screenId":"builtin","screenName":"","id":"vr5W=g_I0#!fa=Vp","createTime":1743584585459,"x":4,"y":88,"color":"#ffffff","backgroundColor":"#222222","text":"Remote IP:","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false,"width":102,"height":21},{"name":"label3","type":"label","layer":4,"screenId":"builtin","screenName":"","id":"brL5plwysvD3`wNC","createTime":1743584599622,"x":4,"y":118,"color":"#ffffff","backgroundColor":"#222222","text":"Remote Port:","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false,"width":121,"height":20},{"name":"label4","type":"label","layer":5,"screenId":"builtin","screenName":"","id":"za#PfbzS-IsQ5d%%","createTime":1743584600702,"x":4,"y":144,"color":"#ffffff","backgroundColor":"#222222","text":"Data:","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false,"width":51,"height":21},{"name":"label5","type":"label","layer":6,"screenId":"builtin","screenName":"","id":"aMn$28G^NBerazTb","createTime":1743584607488,"x":136,"y":32,"color":"#ffffff","backgroundColor":"#222222","text":"label5","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false,"width":58,"height":20},{"name":"label6","type":"label","layer":7,"screenId":"builtin","screenName":"","id":"ajZdtHZyhp6K&ekX","createTime":1743584608760,"x":136,"y":60,"color":"#ffffff","backgroundColor":"#222222","text":"label6","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false,"width":58,"height":21},{"name":"label7","type":"label","layer":8,"screenId":"builtin","screenName":"","id":"z$R84PpVj68&D`+O","createTime":1743584610129,"x":136,"y":88,"color":"#ffffff","backgroundColor":"#222222","text":"label7","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false,"width":58,"height":20},{"name":"label8","type":"label","layer":9,"screenId":"builtin","screenName":"","id":"ov3nhKoEIHlZhoAK","createTime":1743584611283,"x":136,"y":118,"color":"#ffffff","backgroundColor":"#222222","text":"label8","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false,"width":58,"height":20},{"name":"label9","type":"label","layer":10,"screenId":"builtin","screenName":"","id":"h8Z^651o8UOj-IZl","createTime":1743584614921,"x":136,"y":144,"color":"#ffffff","backgroundColor":"#222222","text":"label9","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false,"width":58,"height":20},{"name":"title0","type":"title","layer":11,"screenId":"builtin","screenName":"","id":"q%IzErdb0$*aOzY8","createTime":1743585671170,"x":0,"y":0,"color":"#ffffff","backgroundColor":"#0000FF","text":"TCP Server","textOffset":3,"font":"Widgets.FONTS.DejaVu24","isSelected":false,"width":321,"height":25}],"resources":[{"software":["tcp"]},{"hardware":["hardware_button","hardware_pin_button","imu","speaker","touch","als","mic"]}],"units":[],"hats":[],"bases":[],"i2cs":[],"blockly":"con_infocon_sockaddressdatatrueTrue0.0.0.0800151label5Labellabel68001truecon_infocon_sockGETFROM_STARTcon_info1addressGETFROM_STARTcon_info2label7LabelGETFROM_STARTaddress1label8LabelGETFROM_STARTaddress2WHILETRUEdatacon_sock1024label9Labeldatacon_sockdata","screen":[{"simulationName":"Built-in","type":"builtin","width":320,"height":240,"scale":0.78,"screenName":"","blockId":"","screenColorType":0,"id":"builtin","createTime":1743580828727}],"logicWhenNum":0,"customList":[]} \ No newline at end of file diff --git a/examples/softwave/tcp/cores3_tcp_server_example.py b/examples/softwave/tcp/cores3_tcp_server_example.py new file mode 100644 index 00000000..626f9f8d --- /dev/null +++ b/examples/softwave/tcp/cores3_tcp_server_example.py @@ -0,0 +1,120 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import os, sys, io +import M5 +from M5 import * +import network +import socket + + +label0 = None +label1 = None +label2 = None +label3 = None +label4 = None +label5 = None +label6 = None +label7 = None +label8 = None +label9 = None +title0 = None +wlan = None +tcps = None + + +con_info = None +con_sock = None +address = None +data = None + + +def setup(): + global \ + label0, \ + label1, \ + label2, \ + label3, \ + label4, \ + label5, \ + label6, \ + label7, \ + label8, \ + label9, \ + title0, \ + wlan, \ + tcps, \ + con_info, \ + con_sock, \ + address, \ + data + + M5.begin() + Widgets.fillScreen(0x222222) + label0 = Widgets.Label("Local IP:", 4, 32, 1.0, 0xFFFFFF, 0x222222, Widgets.FONTS.DejaVu18) + label1 = Widgets.Label("Local Port:", 4, 60, 1.0, 0xFFFFFF, 0x222222, Widgets.FONTS.DejaVu18) + label2 = Widgets.Label("Remote IP:", 4, 88, 1.0, 0xFFFFFF, 0x222222, Widgets.FONTS.DejaVu18) + label3 = Widgets.Label("Remote Port:", 4, 118, 1.0, 0xFFFFFF, 0x222222, Widgets.FONTS.DejaVu18) + label4 = Widgets.Label("Data:", 4, 144, 1.0, 0xFFFFFF, 0x222222, Widgets.FONTS.DejaVu18) + label5 = Widgets.Label("label5", 136, 32, 1.0, 0xFFFFFF, 0x222222, Widgets.FONTS.DejaVu18) + label6 = Widgets.Label("label6", 136, 60, 1.0, 0xFFFFFF, 0x222222, Widgets.FONTS.DejaVu18) + label7 = Widgets.Label("label7", 136, 88, 1.0, 0xFFFFFF, 0x222222, Widgets.FONTS.DejaVu18) + label8 = Widgets.Label("label8", 136, 118, 1.0, 0xFFFFFF, 0x222222, Widgets.FONTS.DejaVu18) + label9 = Widgets.Label("label9", 136, 144, 1.0, 0xFFFFFF, 0x222222, Widgets.FONTS.DejaVu18) + title0 = Widgets.Title("TCP Server", 3, 0xFFFFFF, 0x0000FF, Widgets.FONTS.DejaVu24) + + wlan = network.WLAN(network.STA_IF) + wlan.active(True) + tcps = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + tcps.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + tcps.bind(("0.0.0.0", 8001)) + tcps.listen(5) + tcps.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + label5.setText(str(wlan.ifconfig()[0])) + label6.setText(str("8001")) + + +def loop(): + global \ + label0, \ + label1, \ + label2, \ + label3, \ + label4, \ + label5, \ + label6, \ + label7, \ + label8, \ + label9, \ + title0, \ + wlan, \ + tcps, \ + con_info, \ + con_sock, \ + address, \ + data + M5.update() + con_info = tcps.accept() + con_sock = con_info[0] + address = con_info[1] + label7.setText(str(address[0])) + label8.setText(str(address[1])) + while True: + data = con_sock.recv(1024) + label9.setText(str(data)) + con_sock.send(data) + + +if __name__ == "__main__": + try: + setup() + while True: + loop() + except (Exception, KeyboardInterrupt) as e: + try: + from utility import print_error_msg + + print_error_msg(e) + except ImportError: + print("please update to latest firmware") diff --git a/examples/softwave/udp/cores3_udp_client_example.m5f2 b/examples/softwave/udp/cores3_udp_client_example.m5f2 new file mode 100644 index 00000000..e0eb46fe --- /dev/null +++ b/examples/softwave/udp/cores3_udp_client_example.m5f2 @@ -0,0 +1 @@ +{"version":"V2.0","versionNumber":"V2.2.4","type":"cores3","components":[{"name":"screen","type":"screen","layer":0,"screenId":"builtin","screenName":"","id":"__cores3_screen","createTime":1743577518297,"x":0,"y":0,"width":320,"height":240,"backgroundColor":"#222222","size":0,"isSelected":true},{"name":"title0","type":"title","layer":1,"screenId":"builtin","screenName":"","id":"oMmPETb^TiU^`dus","createTime":1743577522581,"x":0,"y":0,"color":"#ffffff","backgroundColor":"#0000FF","text":"UDP Clinet","textOffset":3,"font":"Widgets.FONTS.DejaVu24","isSelected":false,"width":321,"height":25},{"name":"label0","type":"label","layer":2,"screenId":"builtin","screenName":"","id":"wqftbjvZ$EGB%uCK","createTime":1743577532815,"x":4,"y":32,"color":"#ffffff","backgroundColor":"#222222","text":"Local IP:","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false,"width":78,"height":21},{"name":"label1","type":"label","layer":3,"screenId":"builtin","screenName":"","id":"t^r*tu5^&K%&Q2q5","createTime":1743577534018,"x":4,"y":60,"color":"#ffffff","backgroundColor":"#222222","text":"Server IP:","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false,"width":91,"height":21},{"name":"label2","type":"label","layer":4,"screenId":"builtin","screenName":"","id":"kX_-O553qy+T@@**","createTime":1743577535126,"x":4,"y":88,"color":"#ffffff","backgroundColor":"#222222","text":"Server Port:","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false,"width":112,"height":21},{"name":"label3","type":"label","layer":5,"screenId":"builtin","screenName":"","id":"njFkF67y4LL+#KFG","createTime":1743577536185,"x":4,"y":116,"color":"#ffffff","backgroundColor":"#222222","text":"Send:","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false,"width":54,"height":21},{"name":"label4","type":"label","layer":6,"screenId":"builtin","screenName":"","id":"lmhc`y%gnE7*=wEF","createTime":1743577537390,"x":4,"y":144,"color":"#ffffff","backgroundColor":"#222222","text":"Recv:","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false,"width":58,"height":20},{"name":"label5","type":"label","layer":7,"screenId":"builtin","screenName":"","id":"xGT!Cw8eIe5psApl","createTime":1743577694276,"x":136,"y":32,"color":"#ffffff","backgroundColor":"#222222","text":"label5","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false,"width":58,"height":20},{"name":"label6","type":"label","layer":8,"screenId":"builtin","screenName":"","id":"e9kr*l`GG`3HktTh","createTime":1743577695705,"x":136,"y":60,"color":"#ffffff","backgroundColor":"#222222","text":"label6","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false,"width":58,"height":20},{"name":"label7","type":"label","layer":9,"screenId":"builtin","screenName":"","id":"x$qCr5`_h#3JVf+-","createTime":1743577697233,"x":136,"y":88,"color":"#ffffff","backgroundColor":"#222222","text":"label7","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false,"width":58,"height":20},{"name":"label8","type":"label","layer":10,"screenId":"builtin","screenName":"","id":"sH-2+ZAMBbFO-B7h","createTime":1743577698387,"x":136,"y":118,"color":"#ffffff","backgroundColor":"#222222","text":"label8","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false,"width":58,"height":20},{"name":"label9","type":"label","layer":11,"screenId":"builtin","screenName":"","id":"pmf%$8#Q3X5v=%B7","createTime":1743577705361,"x":136,"y":144,"color":"#ffffff","backgroundColor":"#222222","text":"label9","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false,"width":58,"height":20}],"resources":[{"software":["udp"]},{"hardware":["hardware_button","hardware_pin_button","imu","speaker","touch","als","mic"]}],"units":[],"hats":[],"bases":[],"i2cs":[],"blockly":"countresptrueTrue192.168.8.2368000count0label5Labellabel6192.168.8.236label78000truecount100resp1024label88000countlabel98000respcount11","screen":[{"simulationName":"Built-in","type":"builtin","width":320,"height":240,"scale":0.78,"screenName":"","blockId":"","screenColorType":0,"id":"builtin","createTime":1743577518257}],"logicWhenNum":0,"customList":[]} \ No newline at end of file diff --git a/examples/softwave/udp/cores3_udp_client_example.py b/examples/softwave/udp/cores3_udp_client_example.py new file mode 100644 index 00000000..b5ce4f8b --- /dev/null +++ b/examples/softwave/udp/cores3_udp_client_example.py @@ -0,0 +1,112 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import os, sys, io +import M5 +from M5 import * +import network +import socket +import time + + +title0 = None +label0 = None +label1 = None +label2 = None +label3 = None +label4 = None +label5 = None +label6 = None +label7 = None +label8 = None +label9 = None +wlan = None +udpc = None + + +count = None +resp = None + + +def setup(): + global \ + title0, \ + label0, \ + label1, \ + label2, \ + label3, \ + label4, \ + label5, \ + label6, \ + label7, \ + label8, \ + label9, \ + wlan, \ + udpc, \ + count, \ + resp + + M5.begin() + Widgets.fillScreen(0x222222) + title0 = Widgets.Title("UDP Clinet", 3, 0xFFFFFF, 0x0000FF, Widgets.FONTS.DejaVu24) + label0 = Widgets.Label("Local IP:", 4, 32, 1.0, 0xFFFFFF, 0x222222, Widgets.FONTS.DejaVu18) + label1 = Widgets.Label("Server IP:", 4, 60, 1.0, 0xFFFFFF, 0x222222, Widgets.FONTS.DejaVu18) + label2 = Widgets.Label("Server Port:", 4, 88, 1.0, 0xFFFFFF, 0x222222, Widgets.FONTS.DejaVu18) + label3 = Widgets.Label("Send:", 4, 116, 1.0, 0xFFFFFF, 0x222222, Widgets.FONTS.DejaVu18) + label4 = Widgets.Label("Recv:", 4, 144, 1.0, 0xFFFFFF, 0x222222, Widgets.FONTS.DejaVu18) + label5 = Widgets.Label("label5", 136, 32, 1.0, 0xFFFFFF, 0x222222, Widgets.FONTS.DejaVu18) + label6 = Widgets.Label("label6", 136, 60, 1.0, 0xFFFFFF, 0x222222, Widgets.FONTS.DejaVu18) + label7 = Widgets.Label("label7", 136, 88, 1.0, 0xFFFFFF, 0x222222, Widgets.FONTS.DejaVu18) + label8 = Widgets.Label("label8", 136, 118, 1.0, 0xFFFFFF, 0x222222, Widgets.FONTS.DejaVu18) + label9 = Widgets.Label("label9", 136, 144, 1.0, 0xFFFFFF, 0x222222, Widgets.FONTS.DejaVu18) + + wlan = network.WLAN(network.STA_IF) + wlan.active(True) + udpc = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + udpc.connect(("192.168.8.236", 8000)) + count = 0 + label5.setText(str(wlan.ifconfig()[0])) + label6.setText(str("192.168.8.236")) + label7.setText(str("8000")) + + +def loop(): + global \ + title0, \ + label0, \ + label1, \ + label2, \ + label3, \ + label4, \ + label5, \ + label6, \ + label7, \ + label8, \ + label9, \ + wlan, \ + udpc, \ + count, \ + resp + M5.update() + udpc.send(str(count)) + time.sleep_ms(100) + resp = udpc.recv(1024) + label8.setText(str(count)) + label9.setText(str(resp)) + count = (count if isinstance(count, (int, float)) else 0) + 1 + time.sleep(1) + + +if __name__ == "__main__": + try: + setup() + while True: + loop() + except (Exception, KeyboardInterrupt) as e: + try: + from utility import print_error_msg + + print_error_msg(e) + except ImportError: + print("please update to latest firmware") diff --git a/examples/softwave/udp/cores3_udp_server_example.m5f2 b/examples/softwave/udp/cores3_udp_server_example.m5f2 new file mode 100644 index 00000000..6df8d958 --- /dev/null +++ b/examples/softwave/udp/cores3_udp_server_example.m5f2 @@ -0,0 +1 @@ +{"version":"V2.0","versionNumber":"V2.2.4","type":"cores3","components":[{"name":"screen","type":"screen","layer":0,"screenId":"builtin","screenName":"","id":"__cores3_screen","createTime":1743576490304,"x":0,"y":0,"width":320,"height":240,"backgroundColor":"#222222","size":0,"isSelected":true},{"name":"title0","type":"title","layer":1,"screenId":"builtin","screenName":"","id":"qYMY^0_RXgMQQWv@","createTime":1743576500296,"x":0,"y":0,"color":"#ffffff","backgroundColor":"#0000FF","text":"UDP Server","textOffset":3,"font":"Widgets.FONTS.DejaVu24","isSelected":false},{"name":"label0","type":"label","layer":2,"screenId":"builtin","screenName":"","id":"gRdu-4EIcuf9zrYE","createTime":1743576521829,"x":4,"y":32,"color":"#ffffff","backgroundColor":"#222222","text":"Local IP:","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false,"width":78,"height":20},{"name":"label1","type":"label","layer":3,"screenId":"builtin","screenName":"","id":"kpKENh+iXg9$N5&e","createTime":1743576521829,"x":4,"y":60,"color":"#ffffff","backgroundColor":"#222222","text":"Local Port:","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false,"width":99,"height":20},{"name":"label2","type":"label","layer":4,"screenId":"builtin","screenName":"","id":"v+FNjwz5x1Cvq`6a","createTime":1743576557455,"x":4,"y":88,"color":"#ffffff","backgroundColor":"#222222","text":"Remote IP:","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false,"width":102,"height":21},{"name":"label3","type":"label","layer":5,"screenId":"builtin","screenName":"","id":"yf`VjkEFFU-mW!6$","createTime":1743576557455,"x":4,"y":116,"color":"#ffffff","backgroundColor":"#222222","text":"Remote Port:","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false,"width":123,"height":21},{"name":"label4","type":"label","layer":6,"screenId":"builtin","screenName":"","id":"sAjep5*ob^vqtxv@","createTime":1743576557455,"x":4,"y":144,"color":"#ffffff","backgroundColor":"#222222","text":"Recv Data:","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false,"width":103,"height":20},{"name":"label5","type":"label","layer":7,"screenId":"builtin","screenName":"","id":"n-m!6R7nI$&-Gm5c","createTime":1743576888312,"x":136,"y":32,"color":"#ffffff","backgroundColor":"#222222","text":"label5","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false,"width":58,"height":20},{"name":"label6","type":"label","layer":8,"screenId":"builtin","screenName":"","id":"p443BfFjqt9=V89r","createTime":1743576888312,"x":136,"y":60,"color":"#ffffff","backgroundColor":"#222222","text":"label6","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false,"width":58,"height":20},{"name":"label7","type":"label","layer":9,"screenId":"builtin","screenName":"","id":"npsKk^Gia7rqHs@+","createTime":1743576888312,"x":136,"y":88,"color":"#ffffff","backgroundColor":"#222222","text":"label7","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false,"width":58,"height":20},{"name":"label8","type":"label","layer":10,"screenId":"builtin","screenName":"","id":"jR%bvt7hRKiXC=&q","createTime":1743576888312,"x":136,"y":116,"color":"#ffffff","backgroundColor":"#222222","text":"label8","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false,"width":58,"height":20},{"name":"label9","type":"label","layer":11,"screenId":"builtin","screenName":"","id":"x86PKGhA+l7Dk5pK","createTime":1743576888312,"x":136,"y":144,"color":"#ffffff","backgroundColor":"#222222","text":"label9","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false,"width":58,"height":21}],"resources":[{"software":["udp"]},{"hardware":["hardware_button","hardware_pin_button","imu","speaker","touch","als","mic"]}],"units":[],"hats":[],"bases":[],"i2cs":[],"blockly":"recv_datatrueTrue0.0.0.080001label5Labellabel68000truerecv_data1024label7LabelGETFROM_STARTGETFROM_STARTrecv_data21label8LabelGETFROM_STARTGETFROM_STARTrecv_data22label9LabelGETFROM_STARTrecv_data1GETFROM_STARTrecv_data1GETFROM_STARTrecv_data2","screen":[{"simulationName":"Built-in","type":"builtin","width":320,"height":240,"scale":0.78,"screenName":"","blockId":"","screenColorType":0,"id":"builtin","createTime":1743576490301}],"logicWhenNum":0,"customList":[]} \ No newline at end of file diff --git a/examples/softwave/udp/cores3_udp_server_example.py b/examples/softwave/udp/cores3_udp_server_example.py new file mode 100644 index 00000000..297416ef --- /dev/null +++ b/examples/softwave/udp/cores3_udp_server_example.py @@ -0,0 +1,105 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import os, sys, io +import M5 +from M5 import * +import network +import socket + + +title0 = None +label0 = None +label1 = None +label2 = None +label3 = None +label4 = None +label5 = None +label6 = None +label7 = None +label8 = None +label9 = None +wlan = None +udps = None + + +recv_data = None + + +def setup(): + global \ + title0, \ + label0, \ + label1, \ + label2, \ + label3, \ + label4, \ + label5, \ + label6, \ + label7, \ + label8, \ + label9, \ + wlan, \ + udps, \ + recv_data + + M5.begin() + Widgets.fillScreen(0x222222) + title0 = Widgets.Title("UDP Server", 3, 0xFFFFFF, 0x0000FF, Widgets.FONTS.DejaVu24) + label0 = Widgets.Label("Local IP:", 4, 32, 1.0, 0xFFFFFF, 0x222222, Widgets.FONTS.DejaVu18) + label1 = Widgets.Label("Local Port:", 4, 60, 1.0, 0xFFFFFF, 0x222222, Widgets.FONTS.DejaVu18) + label2 = Widgets.Label("Remote IP:", 4, 88, 1.0, 0xFFFFFF, 0x222222, Widgets.FONTS.DejaVu18) + label3 = Widgets.Label("Remote Port:", 4, 116, 1.0, 0xFFFFFF, 0x222222, Widgets.FONTS.DejaVu18) + label4 = Widgets.Label("Recv Data:", 4, 144, 1.0, 0xFFFFFF, 0x222222, Widgets.FONTS.DejaVu18) + label5 = Widgets.Label("label5", 136, 32, 1.0, 0xFFFFFF, 0x222222, Widgets.FONTS.DejaVu18) + label6 = Widgets.Label("label6", 136, 60, 1.0, 0xFFFFFF, 0x222222, Widgets.FONTS.DejaVu18) + label7 = Widgets.Label("label7", 136, 88, 1.0, 0xFFFFFF, 0x222222, Widgets.FONTS.DejaVu18) + label8 = Widgets.Label("label8", 136, 116, 1.0, 0xFFFFFF, 0x222222, Widgets.FONTS.DejaVu18) + label9 = Widgets.Label("label9", 136, 144, 1.0, 0xFFFFFF, 0x222222, Widgets.FONTS.DejaVu18) + + wlan = network.WLAN(network.STA_IF) + wlan.active(True) + udps = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + udps.bind(("0.0.0.0", 8000)) + udps.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + label5.setText(str(wlan.ifconfig()[0])) + label6.setText(str("8000")) + + +def loop(): + global \ + title0, \ + label0, \ + label1, \ + label2, \ + label3, \ + label4, \ + label5, \ + label6, \ + label7, \ + label8, \ + label9, \ + wlan, \ + udps, \ + recv_data + M5.update() + recv_data = udps.recvfrom(1024) + label7.setText(str(recv_data[1][0])) + label8.setText(str(recv_data[1][1])) + label9.setText(str(recv_data[0])) + udps.sendto(recv_data[0], recv_data[1]) + + +if __name__ == "__main__": + try: + setup() + while True: + loop() + except (Exception, KeyboardInterrupt) as e: + try: + from utility import print_error_msg + + print_error_msg(e) + except ImportError: + print("please update to latest firmware") From 7a8939921a8a30b5432be5e7462158d0c6e6f197 Mon Sep 17 00:00:00 2001 From: lbuque Date: Thu, 3 Apr 2025 17:40:04 +0800 Subject: [PATCH 040/322] docs: Fix speaker2 Hat docs. Signed-off-by: lbuque --- docs/en/hats/speaker2.rst | 2 +- docs/en/refs/hat.speaker2.ref | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/en/hats/speaker2.rst b/docs/en/hats/speaker2.rst index 99a936e4..1a2a802a 100644 --- a/docs/en/hats/speaker2.rst +++ b/docs/en/hats/speaker2.rst @@ -56,7 +56,7 @@ Example output: Speaker2 ^^^^^^^^^ -.. autoclass:: unit.speaker2.Speaker2 +.. autoclass:: hat.speaker2.Speaker2Hat :members: Speaker2 class inherits Speaker class, See :ref:`hardware.Speaker.Methods ` for more details. diff --git a/docs/en/refs/hat.speaker2.ref b/docs/en/refs/hat.speaker2.ref index 92bab251..0fff2397 100644 --- a/docs/en/refs/hat.speaker2.ref +++ b/docs/en/refs/hat.speaker2.ref @@ -8,11 +8,11 @@ .. |example.png| image:: https://static-cdn.m5stack.com/mpy_docs/hat/speaker2/example.png -.. |stickc_plus2_speaker_example.m5f2| raw:: html +.. |speaker2_stickcplus2_example.m5f2| raw:: html - stickc_plus2_speaker_example.m5f2 + speaker2_stickcplus2_example.m5f2 From cf61f7399c4248f8588a496ab12378f3167ff8c3 Mon Sep 17 00:00:00 2001 From: lbuque Date: Thu, 3 Apr 2025 17:29:03 +0800 Subject: [PATCH 041/322] version.text: Update to V2.2.5. Signed-off-by: lbuque --- m5stack/version.txt | 2 +- third-party/version.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/m5stack/version.txt b/m5stack/version.txt index efd71067..a014b0b6 100644 --- a/m5stack/version.txt +++ b/m5stack/version.txt @@ -1 +1 @@ -V2.2.4 \ No newline at end of file +V2.2.5 \ No newline at end of file diff --git a/third-party/version.txt b/third-party/version.txt index efd71067..a014b0b6 100644 --- a/third-party/version.txt +++ b/third-party/version.txt @@ -1 +1 @@ -V2.2.4 \ No newline at end of file +V2.2.5 \ No newline at end of file From 1f68486fff903edb11eda2edee625d860e34b7ce Mon Sep 17 00:00:00 2001 From: lbuque Date: Tue, 1 Apr 2025 17:24:21 +0800 Subject: [PATCH 042/322] components/M5Unified: Add VLW font support. Signed-off-by: lbuque --- m5stack/components/M5Unified/mpy_m5gfx.cpp | 27 ++- m5stack/components/M5Unified/mpy_m5gfx.h | 1 + .../components/M5Unified/mpy_m5unified.cpp | 2 +- .../components/M5Unified/mpy_m5widgets.cpp | 78 ++++++-- third-party/cmodules/m5unified/mpy_m5gfx.cpp | 72 ++++++- third-party/cmodules/m5unified/mpy_m5gfx.h | 1 + .../cmodules/m5unified/mpy_m5unified.cpp | 2 +- .../cmodules/m5unified/mpy_m5widgets.cpp | 177 ++++++++++++++---- 8 files changed, 306 insertions(+), 54 deletions(-) diff --git a/m5stack/components/M5Unified/mpy_m5gfx.cpp b/m5stack/components/M5Unified/mpy_m5gfx.cpp index 7b0c5202..b7a86ee3 100644 --- a/m5stack/components/M5Unified/mpy_m5gfx.cpp +++ b/m5stack/components/M5Unified/mpy_m5gfx.cpp @@ -35,6 +35,10 @@ static inline M5GFX *getGfx(const mp_obj_t *args) { return (M5GFX *)((((gfx_obj_t *)MP_OBJ_TO_PTR(args[0]))->gfx)); } +static inline LFS2Wrapper *getFontWrapper(const mp_obj_t *args) { + return (LFS2Wrapper *)((((gfx_obj_t *)MP_OBJ_TO_PTR(args[0]))->font_wrapper)); +} + // -------- GFX common wrapper mp_obj_t gfx_width(mp_obj_t self) { auto gfx = getGfx(&self); @@ -96,7 +100,6 @@ mp_obj_t gfx_setColorDepth(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw } -LFS2Wrapper fontWrapper; mp_obj_t gfx_loadFont(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { enum {ARG_font}; /* *FORMAT-OFF* */ @@ -112,8 +115,13 @@ mp_obj_t gfx_loadFont(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args auto gfx = getGfx(&pos_args[0]); if (mp_obj_is_str(args[ARG_font].u_obj) && ((size_t)mp_obj_len(args[ARG_font].u_obj) < 128)) { // file gfx->unloadFont(); - fontWrapper.open(mp_obj_str_get_str(args[ARG_font].u_obj), LFS2_O_RDONLY); - ret = gfx->loadFont((lgfx::DataWrapper *)&fontWrapper); + LFS2Wrapper *fontWrapper = getFontWrapper(&pos_args[0]); + if (fontWrapper == NULL) { + fontWrapper = new LFS2Wrapper(); + ((gfx_obj_t *)MP_OBJ_TO_PTR(pos_args[0]))->font_wrapper = fontWrapper; + } + fontWrapper->open(mp_obj_str_get_str(args[ARG_font].u_obj), LFS2_O_RDONLY); + ret = gfx->loadFont((lgfx::DataWrapper *)fontWrapper); } else { // buffer mp_buffer_info_t bufinfo; mp_get_buffer_raise(args[ARG_font].u_obj, &bufinfo, MP_BUFFER_READ); @@ -143,7 +151,18 @@ mp_obj_t gfx_setFont(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); auto gfx = getGfx(&pos_args[0]); - gfx->setFont((const m5gfx::IFont *)((font_obj_t *)args[ARG_font].u_obj)->font); + if (mp_obj_is_str(args[ARG_font].u_obj) && ((size_t)mp_obj_len(args[ARG_font].u_obj) < 128)) { // file + gfx->unloadFont(); + LFS2Wrapper *fontWrapper = getFontWrapper(&pos_args[0]); + if (fontWrapper == NULL) { + fontWrapper = new LFS2Wrapper(); + ((gfx_obj_t *)MP_OBJ_TO_PTR(pos_args[0]))->font_wrapper = fontWrapper; + } + fontWrapper->open(mp_obj_str_get_str(args[ARG_font].u_obj), LFS2_O_RDONLY); + gfx->loadFont((lgfx::DataWrapper *)fontWrapper); + } else { + gfx->setFont((const m5gfx::IFont *)((font_obj_t *)args[ARG_font].u_obj)->font); + } return mp_const_none; } diff --git a/m5stack/components/M5Unified/mpy_m5gfx.h b/m5stack/components/M5Unified/mpy_m5gfx.h index 7351351e..d17c14ea 100644 --- a/m5stack/components/M5Unified/mpy_m5gfx.h +++ b/m5stack/components/M5Unified/mpy_m5gfx.h @@ -40,6 +40,7 @@ enum user_tp_t { typedef struct _gfx_obj_t { mp_obj_base_t base; void *gfx; + void *font_wrapper; } gfx_obj_t; typedef struct _font_obj_t { diff --git a/m5stack/components/M5Unified/mpy_m5unified.cpp b/m5stack/components/M5Unified/mpy_m5unified.cpp index 0ab386e7..1eb6d205 100644 --- a/m5stack/components/M5Unified/mpy_m5unified.cpp +++ b/m5stack/components/M5Unified/mpy_m5unified.cpp @@ -63,7 +63,7 @@ const pwr_obj_t m5_imu = {&mp_imu_type, &(M5.Imu) }; btn_obj_t m5_btnC = {&mp_btn_type, &(M5.BtnC), {0}}; btn_obj_t m5_btnPWR = {&mp_btn_type, &(M5.BtnPWR), {0}}; btn_obj_t m5_btnEXT = {&mp_btn_type, &(M5.BtnEXT), {0}}; - gfx_obj_t m5_display = {&mp_gfxdevice_type, &(M5.Display)}; + gfx_obj_t m5_display = {&mp_gfxdevice_type, &(M5.Display), NULL}; touch_obj_t m5_touch = {&mp_touch_type, &(M5.Touch) }; const mic_obj_t m5_mic = {&mp_mic_type, &(M5.Mic) }; diff --git a/m5stack/components/M5Unified/mpy_m5widgets.cpp b/m5stack/components/M5Unified/mpy_m5widgets.cpp index 37a1af2a..9ecb5a1a 100644 --- a/m5stack/components/M5Unified/mpy_m5widgets.cpp +++ b/m5stack/components/M5Unified/mpy_m5widgets.cpp @@ -6,6 +6,7 @@ #include #include "lgfx/utility/lgfx_tjpgd.h" +#include "lgfx/v1/LGFXBase.hpp" extern "C" { @@ -122,6 +123,8 @@ typedef struct _widgets_label_obj_t { LGFX_Device *gfx; const char *text; const m5gfx::IFont *font; + LFS2Wrapper *fontWrapper; + m5gfx::RunTimeFont *rtfont; widgets_color_t color; widgets_pos_t text_pos; widgets_size_t size; @@ -137,9 +140,32 @@ static void m5widgets_label_erase_helper(const widgets_label_obj_t *self) { static void m5widgets_label_draw_helper(const widgets_label_obj_t *self) { self->gfx->setTextColor((uint32_t)self->color.fg_color, (uint32_t)self->color.bg_color); self->gfx->setTextSize(self->size.text_size); + lgfx::v1::FontMetrics metrics; + self->font->getDefaultMetric(&metrics); + if (self->font->getType() == lgfx::v1::IFont::font_type_t::ft_vlw) { + self->gfx->fillRect(self->text_pos.x0, self->text_pos.y0, self->gfx->textWidth(self->text, self->font), metrics.height * 2, (uint32_t)self->color.bg_color); + } self->gfx->drawString(self->text, self->text_pos.x0, self->text_pos.y0, self->font); } +static void m5widgets_label_font_init_helper(widgets_label_obj_t *self, mp_obj_t font) { + if (font != mp_const_none) { + if (mp_obj_is_str(font)) { + // FIXME: check if the font is already + self->fontWrapper->open(mp_obj_str_get_str(font), LFS2_O_RDONLY); + if (self->rtfont->loadFont((lgfx::DataWrapper *)self->fontWrapper)) { + self->font = self->rtfont; + } else { + self->font = self->gfx->getFont(); + } + } else { + self->font = (const m5gfx::IFont *)((font_obj_t *)font)->font; + } + } else { + self->font = &m5gfx::fonts::DejaVu9; + } +} + mp_obj_t m5widgets_label_setText(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { enum {ARG_text}; /* *FORMAT-OFF* */ @@ -244,7 +270,7 @@ mp_obj_t m5widgets_label_setFont(size_t n_args, const mp_obj_t *pos_args, mp_map widgets_label_obj_t *self = (widgets_label_obj_t *)pos_args[0]; auto stash_style = self->gfx->getTextStyle(); m5widgets_label_erase_helper(self); - self->font = (const m5gfx::IFont *)((font_obj_t *)args[ARG_font].u_obj)->font; + m5widgets_label_font_init_helper(self, args[ARG_font].u_obj); m5widgets_label_draw_helper(self); self->gfx->setTextStyle(stash_style); return mp_const_none; @@ -271,6 +297,7 @@ mp_obj_t m5widgets_label_setVisible(size_t n_args, const mp_obj_t *pos_args, mp_ return mp_const_none; } +extern gfx_obj_t m5_display; mp_obj_t m5widgets_label_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { enum {ARG_text, ARG_x, ARG_y, ARG_text_sz, ARG_text_c, ARG_bg_c, ARG_font, ARG_parent}; /* *FORMAT-OFF* */ @@ -304,11 +331,9 @@ mp_obj_t m5widgets_label_make_new(const mp_obj_type_t *type, size_t n_args, size self->color.fg_color = (uint32_t)args[ARG_text_c].u_int; self->color.bg_color = (uint32_t)args[ARG_bg_c].u_int; - if (args[ARG_font].u_obj != mp_const_none) { - self->font = (const m5gfx::IFont *)((font_obj_t *)args[ARG_font].u_obj)->font; - } else { - self->font = &m5gfx::fonts::DejaVu9; - } + self->rtfont = new m5gfx::VLWfont(); + self->fontWrapper = new LFS2Wrapper(); + m5widgets_label_font_init_helper(self, args[ARG_font].u_obj); if (args[ARG_text].u_obj == mp_const_none) { self->text = "Label"; @@ -328,6 +353,8 @@ typedef struct _widgets_title_obj_t { LGFX_Device *gfx; const char *text; const m5gfx::IFont *font; + LFS2Wrapper *fontWrapper; + m5gfx::RunTimeFont *rtfont; widgets_pos_t text_pos; widgets_color_t color; widgets_size_t size; @@ -344,9 +371,32 @@ static void m5widgets_title_draw_helper(widgets_title_obj_t *self) { // text self->gfx->setTextColor((uint32_t)self->color.fg_color, (uint32_t)self->color.bg_color); self->gfx->setTextSize(self->size.text_size); + lgfx::v1::FontMetrics metrics; + self->font->getDefaultMetric(&metrics); + if (self->font->getType() == lgfx::v1::IFont::font_type_t::ft_vlw) { + self->gfx->fillRect(self->text_pos.x0, self->text_pos.y0, self->gfx->textWidth(self->text, self->font), metrics.height * 2, (uint32_t)self->color.bg_color); + } self->gfx->drawString(self->text, self->text_pos.x0, self->text_pos.y0, self->font); } +static void m5widgets_title_font_init_helper(widgets_title_obj_t *self, mp_obj_t font) { + if (font != mp_const_none) { + if (mp_obj_is_str(font)) { + // FIXME: check if the font is already + self->fontWrapper->open(mp_obj_str_get_str(font), LFS2_O_RDONLY); + if (self->rtfont->loadFont((lgfx::DataWrapper *)self->fontWrapper)) { + self->font = self->rtfont; + } else { + self->font = self->gfx->getFont(); + } + } else { + self->font = (const m5gfx::IFont *)((font_obj_t *)font)->font; + } + } else { + self->font = &m5gfx::fonts::DejaVu9; + } +} + mp_obj_t m5widgets_title_setText(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { enum {ARG_text}; /* *FORMAT-OFF* */ @@ -488,14 +538,18 @@ mp_obj_t m5widgets_title_make_new(const mp_obj_type_t *type, size_t n_args, size self->text = mp_obj_str_get_str(args[ARG_text].u_obj); } - if (args[ARG_font].u_obj != mp_const_none) { - self->font = (const m5gfx::IFont *)((font_obj_t *)args[ARG_font].u_obj)->font; - } else { - self->font = &m5gfx::fonts::DejaVu9; - } + self->rtfont = new m5gfx::VLWfont(); + self->fontWrapper = new LFS2Wrapper(); + m5widgets_title_font_init_helper(self, args[ARG_font].u_obj); self->size.w = self->gfx->width(); - self->size.h = self->gfx->fontHeight(self->font); + if (self->font->getType() == lgfx::v1::IFont::font_type_t::ft_vlw) { + lgfx::v1::FontMetrics metrics; + self->font->getDefaultMetric(&metrics); + self->size.h = metrics.height * 2; + } else { + self->size.h = self->gfx->fontHeight(self->font); + } self->text_pos.x0 = args[ARG_text_x].u_int; self->text_pos.y0 = 0; self->color.fg_color = args[ARG_text_c].u_int; diff --git a/third-party/cmodules/m5unified/mpy_m5gfx.cpp b/third-party/cmodules/m5unified/mpy_m5gfx.cpp index 532b0eb4..65442c14 100644 --- a/third-party/cmodules/m5unified/mpy_m5gfx.cpp +++ b/third-party/cmodules/m5unified/mpy_m5gfx.cpp @@ -35,6 +35,10 @@ static inline M5GFX *getGfx(const mp_obj_t *args) { return (M5GFX *)((((gfx_obj_t *)MP_OBJ_TO_PTR(args[0]))->gfx)); } +static inline LFS2Wrapper *getFontWrapper(const mp_obj_t *args) { + return (LFS2Wrapper *)((((gfx_obj_t *)MP_OBJ_TO_PTR(args[0]))->font_wrapper)); +} + // -------- GFX common wrapper mp_obj_t gfx_width(mp_obj_t self) { auto gfx = getGfx(&self); @@ -96,7 +100,6 @@ mp_obj_t gfx_setColorDepth(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw } -LFS2Wrapper fontWrapper; mp_obj_t gfx_loadFont(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { enum {ARG_font}; /* *FORMAT-OFF* */ @@ -112,8 +115,13 @@ mp_obj_t gfx_loadFont(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args auto gfx = getGfx(&pos_args[0]); if (mp_obj_is_str(args[ARG_font].u_obj) && ((size_t)mp_obj_len(args[ARG_font].u_obj) < 128)) { // file gfx->unloadFont(); - fontWrapper.open(mp_obj_str_get_str(args[ARG_font].u_obj), LFS2_O_RDONLY); - ret = gfx->loadFont((lgfx::DataWrapper *)&fontWrapper); + LFS2Wrapper *fontWrapper = getFontWrapper(&pos_args[0]); + if (fontWrapper == NULL) { + fontWrapper = new LFS2Wrapper(); + ((gfx_obj_t *)MP_OBJ_TO_PTR(pos_args[0]))->font_wrapper = fontWrapper; + } + fontWrapper->open(mp_obj_str_get_str(args[ARG_font].u_obj), LFS2_O_RDONLY); + ret = gfx->loadFont((lgfx::DataWrapper *)fontWrapper); } else { // buffer mp_buffer_info_t bufinfo; mp_get_buffer_raise(args[ARG_font].u_obj, &bufinfo, MP_BUFFER_READ); @@ -143,7 +151,18 @@ mp_obj_t gfx_setFont(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); auto gfx = getGfx(&pos_args[0]); - gfx->setFont((const m5gfx::IFont *)((font_obj_t *)args[ARG_font].u_obj)->font); + if (mp_obj_is_str(args[ARG_font].u_obj) && ((size_t)mp_obj_len(args[ARG_font].u_obj) < 128)) { // file + gfx->unloadFont(); + LFS2Wrapper *fontWrapper = getFontWrapper(&pos_args[0]); + if (fontWrapper == NULL) { + fontWrapper = new LFS2Wrapper(); + ((gfx_obj_t *)MP_OBJ_TO_PTR(pos_args[0]))->font_wrapper = fontWrapper; + } + fontWrapper->open(mp_obj_str_get_str(args[ARG_font].u_obj), LFS2_O_RDONLY); + gfx->loadFont((lgfx::DataWrapper *)fontWrapper); + } else { + gfx->setFont((const m5gfx::IFont *)((font_obj_t *)args[ARG_font].u_obj)->font); + } return mp_const_none; } @@ -1145,6 +1164,51 @@ mp_obj_t gfx_newCanvas(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_arg return MP_OBJ_FROM_PTR(res); } +mp_obj_t gfx_writeCommand(mp_obj_t self, mp_obj_t cmd) { + auto gfx = getGfx(&self); + gfx->writeCommand((uint8_t)mp_obj_get_int(cmd)); + return mp_const_none; +} + +mp_obj_t gfx_writeCommand16(mp_obj_t self, mp_obj_t cmd) { + auto gfx = getGfx(&self); + gfx->writeCommand16((uint16_t)mp_obj_get_int(cmd)); + return mp_const_none; +} + +mp_obj_t gfx_writeData(mp_obj_t self, mp_obj_t data) { + auto gfx = getGfx(&self); + gfx->writeData((uint8_t)mp_obj_get_int(data)); + return mp_const_none; +} + +mp_obj_t gfx_writeData16(mp_obj_t self, mp_obj_t data) { + auto gfx = getGfx(&self); + gfx->writeData16((uint16_t)mp_obj_get_int(data)); + return mp_const_none; +} + +mp_obj_t gfx_writeData32(mp_obj_t self, mp_obj_t data) { + auto gfx = getGfx(&self); + gfx->writeData32((uint32_t)mp_obj_get_int(data)); + return mp_const_none; +} + +mp_obj_t gfx_readData(mp_obj_t self, mp_obj_t index) { + auto gfx = getGfx(&self); + return mp_obj_new_int(gfx->readData8((uint8_t)mp_obj_get_int(index))); +} + +mp_obj_t gfx_readData16(mp_obj_t self, mp_obj_t index) { + auto gfx = getGfx(&self); + return mp_obj_new_int(gfx->readData16((uint8_t)mp_obj_get_int(index))); +} + +mp_obj_t gfx_readData32(mp_obj_t self, mp_obj_t index) { + auto gfx = getGfx(&self); + return mp_obj_new_int(gfx->readData32((uint8_t)mp_obj_get_int(index))); +} + // -------- GFX device wrapper mp_obj_t gfx_startWrite(mp_obj_t self) { auto gfx = getGfx(&self); diff --git a/third-party/cmodules/m5unified/mpy_m5gfx.h b/third-party/cmodules/m5unified/mpy_m5gfx.h index 7351351e..d17c14ea 100644 --- a/third-party/cmodules/m5unified/mpy_m5gfx.h +++ b/third-party/cmodules/m5unified/mpy_m5gfx.h @@ -40,6 +40,7 @@ enum user_tp_t { typedef struct _gfx_obj_t { mp_obj_base_t base; void *gfx; + void *font_wrapper; } gfx_obj_t; typedef struct _font_obj_t { diff --git a/third-party/cmodules/m5unified/mpy_m5unified.cpp b/third-party/cmodules/m5unified/mpy_m5unified.cpp index ff3aab66..c4d39430 100644 --- a/third-party/cmodules/m5unified/mpy_m5unified.cpp +++ b/third-party/cmodules/m5unified/mpy_m5unified.cpp @@ -19,7 +19,7 @@ extern "C" /* *FORMAT-OFF* */ -gfx_obj_t m5_display = {&mp_gfxdevice_type, &(box3GFX)}; +gfx_obj_t m5_display = {&mp_gfxdevice_type, &(box3GFX), NULL}; touch_obj_t m5_touch = {&mp_touch_type, &(box3GFX.Touch)}; /* *FORMAT-ON* */ diff --git a/third-party/cmodules/m5unified/mpy_m5widgets.cpp b/third-party/cmodules/m5unified/mpy_m5widgets.cpp index 85ff87b9..a632ed60 100644 --- a/third-party/cmodules/m5unified/mpy_m5widgets.cpp +++ b/third-party/cmodules/m5unified/mpy_m5widgets.cpp @@ -123,6 +123,8 @@ typedef struct _widgets_label_obj_t { LGFX_Device *gfx; const char *text; const m5gfx::IFont *font; + LFS2Wrapper *fontWrapper; + m5gfx::RunTimeFont *rtfont; widgets_color_t color; widgets_pos_t text_pos; widgets_size_t size; @@ -138,9 +140,32 @@ static void m5widgets_label_erase_helper(const widgets_label_obj_t *self) { static void m5widgets_label_draw_helper(const widgets_label_obj_t *self) { self->gfx->setTextColor((uint32_t)self->color.fg_color, (uint32_t)self->color.bg_color); self->gfx->setTextSize(self->size.text_size); + lgfx::v1::FontMetrics metrics; + self->font->getDefaultMetric(&metrics); + if (self->font->getType() == lgfx::v1::IFont::font_type_t::ft_vlw) { + self->gfx->fillRect(self->text_pos.x0, self->text_pos.y0, self->gfx->textWidth(self->text, self->font), metrics.height * 2, (uint32_t)self->color.bg_color); + } self->gfx->drawString(self->text, self->text_pos.x0, self->text_pos.y0, self->font); } +static void m5widgets_label_font_init_helper(widgets_label_obj_t *self, mp_obj_t font) { + if (font != mp_const_none) { + if (mp_obj_is_str(font)) { + // FIXME: check if the font is already + self->fontWrapper->open(mp_obj_str_get_str(font), LFS2_O_RDONLY); + if (self->rtfont->loadFont((lgfx::DataWrapper *)self->fontWrapper)) { + self->font = self->rtfont; + } else { + self->font = self->gfx->getFont(); + } + } else { + self->font = (const m5gfx::IFont *)((font_obj_t *)font)->font; + } + } else { + self->font = &m5gfx::fonts::DejaVu9; + } +} + mp_obj_t m5widgets_label_setText(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { enum {ARG_text}; /* *FORMAT-OFF* */ @@ -245,7 +270,7 @@ mp_obj_t m5widgets_label_setFont(size_t n_args, const mp_obj_t *pos_args, mp_map widgets_label_obj_t *self = (widgets_label_obj_t *)pos_args[0]; auto stash_style = self->gfx->getTextStyle(); m5widgets_label_erase_helper(self); - self->font = (const m5gfx::IFont *)((font_obj_t *)args[ARG_font].u_obj)->font; + m5widgets_label_font_init_helper(self, args[ARG_font].u_obj); m5widgets_label_draw_helper(self); self->gfx->setTextStyle(stash_style); return mp_const_none; @@ -272,6 +297,7 @@ mp_obj_t m5widgets_label_setVisible(size_t n_args, const mp_obj_t *pos_args, mp_ return mp_const_none; } +extern gfx_obj_t m5_display; mp_obj_t m5widgets_label_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { enum {ARG_text, ARG_x, ARG_y, ARG_text_sz, ARG_text_c, ARG_bg_c, ARG_font, ARG_parent}; /* *FORMAT-OFF* */ @@ -305,11 +331,9 @@ mp_obj_t m5widgets_label_make_new(const mp_obj_type_t *type, size_t n_args, size self->color.fg_color = (uint32_t)args[ARG_text_c].u_int; self->color.bg_color = (uint32_t)args[ARG_bg_c].u_int; - if (args[ARG_font].u_obj != mp_const_none) { - self->font = (const m5gfx::IFont *)((font_obj_t *)args[ARG_font].u_obj)->font; - } else { - self->font = &m5gfx::fonts::DejaVu9; - } + self->rtfont = new m5gfx::VLWfont(); + self->fontWrapper = new LFS2Wrapper(); + m5widgets_label_font_init_helper(self, args[ARG_font].u_obj); if (args[ARG_text].u_obj == mp_const_none) { self->text = "Label"; @@ -329,6 +353,8 @@ typedef struct _widgets_title_obj_t { LGFX_Device *gfx; const char *text; const m5gfx::IFont *font; + LFS2Wrapper *fontWrapper; + m5gfx::RunTimeFont *rtfont; widgets_pos_t text_pos; widgets_color_t color; widgets_size_t size; @@ -345,9 +371,32 @@ static void m5widgets_title_draw_helper(widgets_title_obj_t *self) { // text self->gfx->setTextColor((uint32_t)self->color.fg_color, (uint32_t)self->color.bg_color); self->gfx->setTextSize(self->size.text_size); + lgfx::v1::FontMetrics metrics; + self->font->getDefaultMetric(&metrics); + if (self->font->getType() == lgfx::v1::IFont::font_type_t::ft_vlw) { + self->gfx->fillRect(self->text_pos.x0, self->text_pos.y0, self->gfx->textWidth(self->text, self->font), metrics.height * 2, (uint32_t)self->color.bg_color); + } self->gfx->drawString(self->text, self->text_pos.x0, self->text_pos.y0, self->font); } +static void m5widgets_title_font_init_helper(widgets_title_obj_t *self, mp_obj_t font) { + if (font != mp_const_none) { + if (mp_obj_is_str(font)) { + // FIXME: check if the font is already + self->fontWrapper->open(mp_obj_str_get_str(font), LFS2_O_RDONLY); + if (self->rtfont->loadFont((lgfx::DataWrapper *)self->fontWrapper)) { + self->font = self->rtfont; + } else { + self->font = self->gfx->getFont(); + } + } else { + self->font = (const m5gfx::IFont *)((font_obj_t *)font)->font; + } + } else { + self->font = &m5gfx::fonts::DejaVu9; + } +} + mp_obj_t m5widgets_title_setText(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { enum {ARG_text}; /* *FORMAT-OFF* */ @@ -489,14 +538,18 @@ mp_obj_t m5widgets_title_make_new(const mp_obj_type_t *type, size_t n_args, size self->text = mp_obj_str_get_str(args[ARG_text].u_obj); } - if (args[ARG_font].u_obj != mp_const_none) { - self->font = (const m5gfx::IFont *)((font_obj_t *)args[ARG_font].u_obj)->font; - } else { - self->font = &m5gfx::fonts::DejaVu9; - } + self->rtfont = new m5gfx::VLWfont(); + self->fontWrapper = new LFS2Wrapper(); + m5widgets_title_font_init_helper(self, args[ARG_font].u_obj); self->size.w = self->gfx->width(); - self->size.h = self->gfx->fontHeight(self->font); + if (self->font->getType() == lgfx::v1::IFont::font_type_t::ft_vlw) { + lgfx::v1::FontMetrics metrics; + self->font->getDefaultMetric(&metrics); + self->size.h = metrics.height * 2; + } else { + self->size.h = self->gfx->fontHeight(self->font); + } self->text_pos.x0 = args[ARG_text_x].u_int; self->text_pos.y0 = 0; self->color.fg_color = args[ARG_text_c].u_int; @@ -516,9 +569,13 @@ typedef struct _widgets_image_obj_t { const char *img; widgets_pos_t pos; widgets_size_t size; + struct scale_t { + float x; + float y; + } scale; }widgets_image_obj_t; -static void m5widgets_image_erase_helper(widgets_image_obj_t *self) { +static inline void m5widgets_image_erase_helper(widgets_image_obj_t *self) { self->gfx->fillRect(self->pos.x0, self->pos.y0, self->size.w, self->size.h, _bg_color_g); } @@ -534,11 +591,17 @@ static bool m5widgets_image_bmp_helper(LFS2Wrapper *file, widgets_image_obj_t *s if (buf[0] != 'B' && buf[1] != 'M') { return false; } - m5widgets_image_erase_helper(self); file->seek(16, LFS2_SEEK_CUR); - self->size.w = file->read32(); - self->size.h = file->read32(); + uint32_t w = file->read32(); + uint32_t h = file->read32(); file->close(); + + if (w != self->size.w || h != self->size.h) { + m5widgets_image_erase_helper(self); + } + + self->size.w = w * self->scale.x; + self->size.h = h * self->scale.y; // printf("%s W:%d H:%d\r\n", self->img, self->size.w, self->size.h); return true; } @@ -547,10 +610,10 @@ static bool m5widgets_image_jpg_helper(LFS2Wrapper *file, widgets_image_obj_t *s if (!file->open(self->img, LFS2_O_RDONLY)) { return false; } - m5widgets_image_erase_helper(self); - uint8_t idx, result = 0; uint16_t value; + uint32_t w = self->size.w; + uint32_t h = self->size.h; while (!result) { if (!file->read(&idx, 1) || idx != 0xff || !file->read(&idx, 1)) { result = 3; @@ -577,8 +640,8 @@ static bool m5widgets_image_jpg_helper(LFS2Wrapper *file, widgets_image_obj_t *s break; case 0xC0: file->seek(0x03, LFS2_SEEK_CUR); - self->size.h = (uint32_t)file->read16swap(); - self->size.w = (uint32_t)file->read16swap(); + h = (uint32_t)file->read16swap(); + w = (uint32_t)file->read16swap(); result = 1; break; case 0xDA: @@ -595,6 +658,12 @@ static bool m5widgets_image_jpg_helper(LFS2Wrapper *file, widgets_image_obj_t *s } } file->close(); + + if (w != self->size.w || h != self->size.h) { + m5widgets_image_erase_helper(self); + } + self->size.w = w * self->scale.x; + self->size.h = h * self->scale.y; // printf("%s W:%d H:%d result:%d\r\n", self->img, self->size.w, self->size.h, result); return result == 1? true: false; } @@ -611,17 +680,23 @@ static bool m5widgets_image_png_helper(LFS2Wrapper *file, widgets_image_obj_t *s if ((buf[0] << 16 | buf[1]) != 0x89504E47) { return false; } - m5widgets_image_erase_helper(self); + file->seek(16); for (size_t i = 0; i < 2; i++) { buf[i] = file->read16swap(); } - self->size.w = buf[0] << 16 | buf[1]; + uint32_t w = buf[0] << 16 | buf[1]; for (size_t i = 0; i < 2; i++) { buf[i] = file->read16swap(); } - self->size.h = buf[0] << 16 | buf[1]; + uint32_t h = buf[0] << 16 | buf[1]; file->close(); + + if (w != self->size.w || h != self->size.h) { + m5widgets_image_erase_helper(self); + } + self->size.w = w * self->scale.x; + self->size.h = h * self->scale.y; // printf("%s W:%d H:%d\r\n", self->img, self->size.w, self->size.h); return true; } @@ -638,13 +713,13 @@ static bool m5widgets_image_draw_helper(widgets_image_obj_t *self) { bool ret = true; if (strstr(ftype, "bmp") != NULL) { ret = m5widgets_image_bmp_helper(&wrapper, self); - self->gfx->drawBmpFile(&wrapper, self->img, self->pos.x0, self->pos.y0, ~0u, ~0u, 0, 0, 1.0, 1.0); + self->gfx->drawBmpFile(&wrapper, self->img, self->pos.x0, self->pos.y0, ~0u, ~0u, 0, 0, self->scale.x, self->scale.y); } else if ((strstr(ftype, "jpg") != NULL) || (strstr(ftype, "jpeg") != NULL)) { ret = m5widgets_image_jpg_helper(&wrapper, self); - self->gfx->drawJpgFile(&wrapper, self->img, self->pos.x0, self->pos.y0, ~0u, ~0u, 0, 0, 1.0, 1.0); + self->gfx->drawJpgFile(&wrapper, self->img, self->pos.x0, self->pos.y0, ~0u, ~0u, 0, 0, self->scale.x, self->scale.y); } else if (strstr(ftype, "png") != NULL) { ret = m5widgets_image_png_helper(&wrapper, self); - self->gfx->drawPngFile(&wrapper, self->img, self->pos.x0, self->pos.y0, ~0u, ~0u, 0, 0, 1.0, 1.0); + self->gfx->drawPngFile(&wrapper, self->img, self->pos.x0, self->pos.y0, ~0u, ~0u, 0, 0, self->scale.x, self->scale.y); } else { printf("Image format was not bmp, jpg, png\r\n"); } @@ -688,7 +763,9 @@ mp_obj_t m5widgets_image_setCursor(size_t n_args, const mp_obj_t *pos_args, mp_m mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); widgets_image_obj_t *self = (widgets_image_obj_t *)pos_args[0]; - m5widgets_image_erase_helper(self); + if (args[ARG_x].u_int != self->pos.x0 || self->pos.y0 != args[ARG_y].u_int) { + m5widgets_image_erase_helper(self); + } self->pos.x0 = args[ARG_x].u_int; self->pos.y0 = args[ARG_y].u_int; @@ -715,14 +792,42 @@ mp_obj_t m5widgets_image_setVisible(size_t n_args, const mp_obj_t *pos_args, mp_ return mp_const_none; } + +mp_obj_t m5widgets_image_setScale(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum {ARG_scale_x, ARG_scale_y}; + /* *FORMAT-OFF* */ + const mp_arg_t allowed_args[] = { + { MP_QSTR_scale_x, MP_ARG_OBJ | MP_ARG_REQUIRED, {.u_obj = 0 } }, + { MP_QSTR_scale_y, MP_ARG_OBJ | MP_ARG_REQUIRED, {.u_obj = 0 } }, + }; + /* *FORMAT-ON* */ + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + widgets_image_obj_t *self = (widgets_image_obj_t *)pos_args[0]; + float scale_x = mp_obj_get_float(args[ARG_scale_x].u_obj); + float scale_y = mp_obj_get_float(args[ARG_scale_y].u_obj); + if (scale_x != self->scale.x || scale_y != self->scale.y) { + m5widgets_image_erase_helper(self); + } + + self->scale.x = scale_x; + self->scale.y = scale_y; + m5widgets_image_draw_helper(self); + return mp_const_none; +} + + mp_obj_t m5widgets_image_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { - enum {ARG_img, ARG_x, ARG_y, ARG_parent}; + enum {ARG_img, ARG_x, ARG_y, ARG_scale_x, ARG_scale_y, ARG_parent}; /* *FORMAT-OFF* */ const mp_arg_t allowed_args[] = { - { MP_QSTR_img, MP_ARG_OBJ | MP_ARG_REQUIRED, {.u_obj = mp_const_none } }, - { MP_QSTR_x, MP_ARG_INT | MP_ARG_REQUIRED, {.u_int = 0 } }, - { MP_QSTR_y, MP_ARG_INT | MP_ARG_REQUIRED, {.u_int = 0 } }, - { MP_QSTR_parent, MP_ARG_OBJ , {.u_obj = mp_const_none} }, + { MP_QSTR_img, MP_ARG_OBJ | MP_ARG_REQUIRED, {.u_obj = mp_const_none } }, + { MP_QSTR_x, MP_ARG_INT | MP_ARG_REQUIRED, {.u_int = 0 } }, + { MP_QSTR_y, MP_ARG_INT | MP_ARG_REQUIRED, {.u_int = 0 } }, + { MP_QSTR_scale_x, MP_ARG_OBJ , {.u_obj = mp_const_none} }, + { MP_QSTR_scale_y, MP_ARG_OBJ , {.u_obj = mp_const_none} }, + { MP_QSTR_parent, MP_ARG_OBJ , {.u_obj = mp_const_none} }, }; /* *FORMAT-ON* */ mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; @@ -741,6 +846,14 @@ mp_obj_t m5widgets_image_make_new(const mp_obj_type_t *type, size_t n_args, size self->pos.y0 = args[ARG_y].u_int; self->size.w = 0; self->size.h = 0; + self->scale.x = 1.0; + self->scale.y = 1.0; + if (args[ARG_scale_x].u_obj != mp_const_none) { + self->scale.x = mp_obj_get_float(args[ARG_scale_x].u_obj); + } + if (args[ARG_scale_y].u_obj != mp_const_none) { + self->scale.y = mp_obj_get_float(args[ARG_scale_y].u_obj); + } if (args[ARG_img].u_obj == mp_const_none) { self->img = "res/img/default.png"; From 45cce1d65368ba42357b50311d75f3b617ac6ba8 Mon Sep 17 00:00:00 2001 From: lbuque Date: Wed, 9 Apr 2025 11:12:51 +0800 Subject: [PATCH 043/322] libs/umqtt: Delete the code that reads the certificate content. ssl.py implements the load_cert_chain method to read the certificate content. FIXME: When there is only certfile, ssl cannot establish a link. Signed-off-by: lbuque --- m5stack/libs/umqtt/__init__.py | 26 +------------------------- 1 file changed, 1 insertion(+), 25 deletions(-) diff --git a/m5stack/libs/umqtt/__init__.py b/m5stack/libs/umqtt/__init__.py index cfa62d55..0b8b0b51 100644 --- a/m5stack/libs/umqtt/__init__.py +++ b/m5stack/libs/umqtt/__init__.py @@ -18,20 +18,7 @@ def __init__( ssl=False, ssl_params={}, ): - ssl_params1 = ssl_params - if ssl: - pass - key_path = ssl_params1.get("key", None) - key_value = self._load_file(key_path) - if key_value: - ssl_params1["key"] = key_value - - cert_path = ssl_params1.get("cert", None) - cert_value = self._load_file(cert_path) - if cert_value: - ssl_params1["cert"] = cert_value - - super().__init__(client_id, server, port, user, password, keepalive, ssl, ssl_params1) + super().__init__(client_id, server, port, user, password, keepalive, ssl, ssl_params) self.set_callback(self._callback) self._topics = {} @@ -51,14 +38,3 @@ def _callback(self, topic, msg): def subscribe(self, topic, handler, qos=0): self._topics[topic] = handler return super().subscribe(topic, qos) - - @staticmethod - def _load_file(path): - if isinstance(path, str) and path.startswith("/flash"): - try: - with open(path, "r") as f: - return f.read() - except: - return None - else: - return None From 55bb9824a2de3b3df7beff434c1121f74be88adb Mon Sep 17 00:00:00 2001 From: lbuque Date: Mon, 7 Apr 2025 16:25:15 +0800 Subject: [PATCH 044/322] libs/modbus: Fixed verbose parameter not working. Signed-off-by: lbuque --- m5stack/libs/modbus/modbus/frame.py | 50 +++++++-------------- m5stack/libs/modbus/modbus/master.py | 20 +++++---- m5stack/libs/modbus/modbus/slave.py | 24 +++++----- m5stack/libs/modbus/test/test_modbus_tcp.py | 29 ++++++++---- 4 files changed, 59 insertions(+), 64 deletions(-) diff --git a/m5stack/libs/modbus/modbus/frame.py b/m5stack/libs/modbus/modbus/frame.py index 6dfdbcfc..162777db 100644 --- a/m5stack/libs/modbus/modbus/frame.py +++ b/m5stack/libs/modbus/modbus/frame.py @@ -3,13 +3,6 @@ # # SPDX-License-Identifier: MIT -# ToDo: -# - remove debug output -# - error messages + test cases -# - documentation - -debug = True - class ModbusFrame: def __init__( @@ -198,7 +191,7 @@ def _crc16(cls, data): return crc @classmethod - def parse_frame(cls, frame, fr_type=None): + def parse_frame(cls, frame, fr_type=None, verbose=False): """Factory Method: Create a ModbusRTUFrame from bytearray. Args: @@ -209,8 +202,7 @@ def parse_frame(cls, frame, fr_type=None): ModbusRTUFrame: parsed frame """ - if debug: - print("Parsing RTU frame: " + " ".join(["{:02x}".format(x) for x in frame])) + verbose and print("Parsing RTU frame: " + " ".join(["{:02x}".format(x) for x in frame])) if len(frame) < 2: return @@ -219,8 +211,7 @@ def parse_frame(cls, frame, fr_type=None): func_code = frame[1] if cls._check_both(frame): - if debug: - print("frame is request or response") + verbose and print("frame is request or response") register = (frame[2] << 8) + frame[3] data = frame[4:6] f = ModbusRTUFrame( @@ -230,13 +221,11 @@ def parse_frame(cls, frame, fr_type=None): fr_type="request", data=data, ) - if debug: - print(f) + verbose and print(f) return f if cls._check_request(frame) and ((fr_type is None) or (fr_type == "request")): - if debug: - print("frame is request") + verbose and print("frame is request") register = (frame[2] << 8) + frame[3] length = (frame[4] << 8) + frame[5] data = None @@ -251,13 +240,11 @@ def parse_frame(cls, frame, fr_type=None): length=length, data=data, ) - if debug: - print(f) + verbose and print(f) return f if cls._check_response(frame) and ((fr_type is None) or (fr_type == "response")): - if debug: - print("frame is response") + verbose and print("frame is response") f = None if func_code in [0x01, 0x02, 0x03, 0x04]: bc = frame[2] @@ -410,7 +397,7 @@ def __str__(self): ) @classmethod - def parse_frame(cls, frame): + def parse_frame(cls, frame, verbose=False): """Factory Method: Create a ModbusTCPFrame from bytearray. Args: @@ -420,8 +407,7 @@ def parse_frame(cls, frame): ModbusTCPFrame: parsed frame """ - if debug: - print("Parsing TCP frame: " + " ".join(["{:02x}".format(x) for x in frame])) + verbose and print("Parsing TCP frame: " + " ".join(["{:02x}".format(x) for x in frame])) if len(frame) < 8: return @@ -432,8 +418,7 @@ def parse_frame(cls, frame): func_code = frame[7] if cls._check_both(frame, length): - if debug: - print("frame is request or response") + verbose and print("frame is request or response") register = (frame[8] << 8) + frame[9] data = frame[10:12] f = ModbusTCPFrame( @@ -444,13 +429,11 @@ def parse_frame(cls, frame): fr_type="request", data=data, ) - if debug: - print(f) + verbose and print(f) return f if cls._check_request(frame, length): - if debug: - print("frame is request") + verbose and print("frame is request") register = (frame[8] << 8) + frame[9] d_length = (frame[10] << 8) + frame[11] data = None @@ -466,14 +449,12 @@ def parse_frame(cls, frame): data=data, length=d_length, ) - if debug: - print(f) + verbose and print(f) return f f = None if cls._check_response(frame, length): - if debug: - print("frame is request") + verbose and print("frame is request") if func_code in [0x01, 0x02, 0x03, 0x04]: bc = frame[8] data = frame[9 : 9 + bc] @@ -505,8 +486,7 @@ def parse_frame(cls, frame): error_code=error_code, ) if f is not None: - if debug: - print(f) + verbose and print(f) return f # raise ValueError("Could not parse Frame " + " ".join(["{:02x}".format(x) for x in frame])) diff --git a/m5stack/libs/modbus/modbus/master.py b/m5stack/libs/modbus/modbus/master.py index 580ffb15..4420c53a 100644 --- a/m5stack/libs/modbus/modbus/master.py +++ b/m5stack/libs/modbus/modbus/master.py @@ -250,7 +250,7 @@ def _read_registers( state, resp = self._send(f.get_frame(), timeout=timeout) if state is False: return None - resp_frame = ModbusTCPFrame.parse_frame(resp) + resp_frame = ModbusTCPFrame.parse_frame(resp, verbose=self._verbose) self.ti += 1 elif self.ms_type == "rtu": f = ModbusRTUFrame( @@ -263,7 +263,9 @@ def _read_registers( state, resp = self._send(f.get_frame(), timeout=timeout) if state is False: return None - resp_frame = ModbusRTUFrame.parse_frame(resp, fr_type="response") + resp_frame = ModbusRTUFrame.parse_frame( + resp, fr_type="response", verbose=self._verbose + ) return resp_frame.data @@ -292,7 +294,7 @@ async def _read_registers_async( state, resp = await self._send_async(f.get_frame(), timeout=timeout) if state is False: return None - resp_frame = ModbusTCPFrame.parse_frame(resp) + resp_frame = ModbusTCPFrame.parse_frame(resp, verbose=self._verbose) self.ti += 1 elif self.ms_type == "rtu": f = ModbusRTUFrame( @@ -305,7 +307,9 @@ async def _read_registers_async( state, resp = await self._send_async(f.get_frame(), timeout=timeout) if state is False: return None - resp_frame = ModbusRTUFrame.parse_frame(resp, fr_type="response") + resp_frame = ModbusRTUFrame.parse_frame( + resp, fr_type="response", verbose=self._verbose + ) return resp_frame.data @@ -574,9 +578,9 @@ def _write_registers( return None if self.ms_type == "tcp": - resp_frame = ModbusTCPFrame.parse_frame(resp) + resp_frame = ModbusTCPFrame.parse_frame(resp, verbose=self._verbose) elif self.ms_type == "rtu": - resp_frame = ModbusRTUFrame.parse_frame(resp) + resp_frame = ModbusRTUFrame.parse_frame(resp, verbose=self._verbose) if resp_frame is None: return None @@ -677,9 +681,9 @@ async def _write_registers_async( return None if self.ms_type == "tcp": - resp_frame = ModbusTCPFrame.parse_frame(resp) + resp_frame = ModbusTCPFrame.parse_frame(resp, verbose=self._verbose) elif self.ms_type == "rtu": - resp_frame = ModbusRTUFrame.parse_frame(resp) + resp_frame = ModbusRTUFrame.parse_frame(resp, verbose=self._verbose) if resp_frame.func_code in [0x05, 0x06]: return resp_frame.data diff --git a/m5stack/libs/modbus/modbus/slave.py b/m5stack/libs/modbus/modbus/slave.py index 8e4458da..e3dde660 100644 --- a/m5stack/libs/modbus/modbus/slave.py +++ b/m5stack/libs/modbus/modbus/slave.py @@ -701,7 +701,7 @@ def _set_data(self, register, length, data_Block, data): class _CModbusRTUSlave(ModbusSlave): - def __init__(self, uart, verbose=True, *args, **kwargs): + def __init__(self, uart, verbose=False, *args, **kwargs): self._verbose = verbose self.uart = uart super(_CModbusRTUSlave, self).__init__(sl_type="rtu", *args, **kwargs) @@ -720,7 +720,7 @@ async def run_async(self): def tick(self): if not self.stopped and self.uart.inWaiting(): rsp = self.uart.read_all() - frame = ModbusRTUFrame.parse_frame(rsp) + frame = ModbusRTUFrame.parse_frame(rsp, verbose=self._verbose) if frame is None or ( self.ignore_unit_id is not True and frame.device_addr != self._device_address ): @@ -738,7 +738,7 @@ def tick(self): class _MModbusRTUSlave(ModbusSlave): - def __init__(self, uart, verbose=True, *args, **kwargs): + def __init__(self, uart, verbose=False, *args, **kwargs): self._verbose = verbose self.uart = uart super(_MModbusRTUSlave, self).__init__( @@ -768,7 +768,7 @@ def run(self): def tick(self): if not self.stopped and self.uart.any(): rsp = self.uart.read() - frame = ModbusRTUFrame.parse_frame(rsp) + frame = ModbusRTUFrame.parse_frame(rsp, verbose=self._verbose) if frame is None or ( self.ignore_unit_id is not True and frame.device_addr != self._device_address ): @@ -795,7 +795,7 @@ def __new__(cls, *args, **kwargs): class _CModbusTCPServer(ModbusSlave): - def __init__(self, host, port, verbose=True, *args, **kwargs): + def __init__(self, host, port, verbose=False, *args, **kwargs): self.host = host self.port = port self._verbose = verbose @@ -822,7 +822,7 @@ def run(self): frame = conn.recv(256) if len(frame) > 0: try: - frame = ModbusTCPFrame.parse_frame(frame) + frame = ModbusTCPFrame.parse_frame(frame, verbose=self._verbose) if frame is None or ( self.ignore_unit_id is not True and frame.device_addr != self._device_address @@ -855,7 +855,7 @@ def tick(self): client = self.clients[fd] frame = client.recv(256) if len(frame) > 0: - frame = ModbusTCPFrame.parse_frame(frame) + frame = ModbusTCPFrame.parse_frame(frame, verbose=self._verbose) if frame is None or ( self.ignore_unit_id is not True and frame.unit_id != self._device_address @@ -890,7 +890,7 @@ async def run_async(self): while True: frame = await loop.sock_recv(conn, 256) if len(frame) > 0: - req = ModbusTCPFrame.parse_frame(frame) + req = ModbusTCPFrame.parse_frame(frame, verbose=self._verbose) if req is None or (req.device_addr != self._device_address): continue self._verbose and print("received Frame {}".format(req)) @@ -914,7 +914,7 @@ async def run_async(self): class _MModbusTCPServer(ModbusSlave): - def __init__(self, host, port, verbose=True, *args, **kwargs): + def __init__(self, host, port, verbose=False, *args, **kwargs): self.host = host self.port = port self._verbose = verbose @@ -942,7 +942,7 @@ def run(self): frame = conn.recv(256) if len(frame) > 0: try: - frame = ModbusTCPFrame.parse_frame(frame) + frame = ModbusTCPFrame.parse_frame(frame, verbose=self._verbose) if frame is None or ( self.ignore_unit_id is not True and frame.unit_id != self._device_address @@ -972,7 +972,7 @@ def tick(self): else: frame = fd.recv(256) if len(frame) > 0: - frame = ModbusTCPFrame.parse_frame(frame) + frame = ModbusTCPFrame.parse_frame(frame, verbose=self._verbose) if frame is None or ( self.ignore_unit_id is not True and frame.unit_id != self._device_address @@ -1006,7 +1006,7 @@ async def run_async(self): while True: frame = await loop.sock_recv(conn, 256) if len(frame) > 0: - req = ModbusTCPFrame.parse_frame(frame) + req = ModbusTCPFrame.parse_frame(frame, verbose=self._verbose) if req is None or req.device_addr != self._device_address: continue self._verbose and print("received Frame {}".format(req)) diff --git a/m5stack/libs/modbus/test/test_modbus_tcp.py b/m5stack/libs/modbus/test/test_modbus_tcp.py index b261cc40..10e7fa32 100644 --- a/m5stack/libs/modbus/test/test_modbus_tcp.py +++ b/m5stack/libs/modbus/test/test_modbus_tcp.py @@ -14,6 +14,7 @@ import _thread running = False +port = 10000 def slave_loop(args): @@ -26,9 +27,10 @@ def slave_loop(args): class Test(unittest.TestCase): def setUp(self): self.slave_address = 0x01 + global port self.srv = ModbusTCPServer( "127.0.0.1", - 505, + port, context={ "coils": [ { @@ -64,26 +66,30 @@ def setUp(self): # return super().setUp() def tearDown(self): - global running + global running, port running = False self.srv.stop() time.sleep(1) + port += 1 # return super().tearDown() def test_read_coils(self): - cl = ModbusTCPClient("127.0.0.1", 505) + global port + cl = ModbusTCPClient("127.0.0.1", port, verbose=True) cl.connect() self.assertEqual(cl.read_coils(self.slave_address, 1000, 1), [True]) self.assertEqual(cl.read_coils(self.slave_address, 1001, 1), [False]) self.assertEqual(cl.read_coils(self.slave_address, 1000, 2), [True, False]) self.assertEqual(cl.read_coils(self.slave_address, 1001, 2), [False, True]) self.assertEqual(cl.read_coils(self.slave_address, 1000, 3), [True, False, True]) + time.sleep(1) cl.disconnect() self.srv.stop() time.sleep(1) def test_read_digital_inputs(self): - cl = ModbusTCPClient("127.0.0.1", 505) + global port + cl = ModbusTCPClient("127.0.0.1", port) cl.connect() self.assertEqual(cl.read_discrete_inputs(self.slave_address, 1000, 1), [True]) self.assertEqual(cl.read_discrete_inputs(self.slave_address, 1000, 2), [True, False]) @@ -102,7 +108,8 @@ def test_read_digital_inputs(self): time.sleep(1) def test_read_holding_registers(self): - cl = ModbusTCPClient("127.0.0.1", 505) + global port + cl = ModbusTCPClient("127.0.0.1", port) cl.connect() self.assertEqual(cl.read_holding_registers(self.slave_address, 1000, 1), [0x0001]) self.assertEqual( @@ -122,7 +129,8 @@ def test_read_holding_registers(self): time.sleep(1) def test_read_input_registers(self): - cl = ModbusTCPClient("127.0.0.1", 505) + global port + cl = ModbusTCPClient("127.0.0.1", port) cl.connect() self.assertEqual( cl.read_input_registers(self.slave_address, 1000, 1), @@ -145,7 +153,8 @@ def test_read_input_registers(self): time.sleep(1) def test_read_write_coils(self): - cl = ModbusTCPClient("127.0.0.1", 505) + global port + cl = ModbusTCPClient("127.0.0.1", port) cl.connect() self.assertEqual(cl.write_single_coil(self.slave_address, 1001, 1), True) self.assertEqual(cl.read_coils(self.slave_address, 1000, 3), [True, True, True]) @@ -166,7 +175,8 @@ def test_read_write_coils(self): time.sleep(1) def test_read_write_holding_register(self): - cl = ModbusTCPClient("127.0.0.1", 505) + global port + cl = ModbusTCPClient("127.0.0.1", port) cl.connect() self.assertEqual(cl.write_single_register(self.slave_address, 1000, 0xFFEE), 0xFFEE) self.assertEqual(cl.read_holding_registers(self.slave_address, 1000, 1), [0xFFEE]) @@ -180,7 +190,8 @@ def test_read_write_holding_register(self): time.sleep(1) def test_write_multiple_registers(self): - cl = ModbusTCPClient("127.0.0.1", 505) + global port + cl = ModbusTCPClient("127.0.0.1", port) cl.connect() self.assertEqual(cl.write_multiple_registers(self.slave_address, 1000, [0xFFEE]), 1) self.assertEqual(cl.read_holding_registers(self.slave_address, 1000, 1), [0xFFEE]) From 8097d8337cd4c709d335c62682f48efb41179a9b Mon Sep 17 00:00:00 2001 From: lbuque Date: Tue, 8 Apr 2025 18:29:23 +0800 Subject: [PATCH 045/322] docs: Change the svg image format to png. Signed-off-by: lbuque --- .gitlab-ci.yml | 4 +- docs/README.md | 4 + docs/en/conf.py | 15 ++ docs/en/hardware/als.rst | 4 +- docs/en/hardware/button.rst | 26 ++-- docs/en/hardware/can.rst | 18 +-- docs/en/hardware/imu.rst | 4 +- docs/en/hardware/mic.rst | 4 +- docs/en/hats/cardkb.rst | 2 +- docs/en/hats/neoflash.rst | 8 +- docs/en/hats/servo.rst | 10 +- docs/en/hats/servo8.rst | 18 +-- docs/en/module/dualkmeter.rst | 20 +-- docs/en/module/lora.rst | 4 +- docs/en/quick-reference/usb-mode.rst | 2 +- docs/en/refs/base.can.ref | 2 +- docs/en/refs/base.display.ref | 2 +- docs/en/refs/base.echo.ref | 2 +- docs/en/refs/base.gps.ref | 32 ++--- docs/en/refs/base.qrcode2.ref | 2 +- docs/en/refs/base.tfcard.ref | 2 +- docs/en/refs/hardware.button.ref | 22 +-- docs/en/refs/hardware.can.ref | 18 +-- docs/en/refs/hardware.sen55.ref | 50 +++---- docs/en/refs/hat.cardkb.ref | 2 - docs/en/refs/hat.env.ref | 6 - docs/en/refs/hat.neoflash.ref | 8 +- docs/en/refs/hat.pir.ref | 7 - docs/en/refs/hat.servo.ref | 10 +- docs/en/refs/hat.servo8.ref | 18 +-- docs/en/refs/module.bala2.ref | 2 +- docs/en/refs/module.commu.ref | 14 +- docs/en/refs/module.dmx.ref | 4 +- docs/en/refs/module.dualkmeter.ref | 20 +-- docs/en/refs/module.encoder4_motor.ref | 6 +- docs/en/refs/module.gnss.ref | 12 +- docs/en/refs/module.gpsv2.ref | 2 +- docs/en/refs/module.lora.ref | 12 +- docs/en/refs/module.nbiot.ref | 2 +- docs/en/refs/module.pwrcan.ref | 2 +- docs/en/refs/module.qrcode.ref | 2 +- docs/en/refs/module.servo2.ref | 2 +- docs/en/refs/module.zigbee.ref | 4 +- docs/en/refs/qr.usb-mode.ref | 2 +- docs/en/refs/software.modbus.rtu.master.ref | 2 - docs/en/refs/software.tcp.client.ref | 2 +- docs/en/refs/software.tcp.server.ref | 6 +- docs/en/refs/software.umqtt.default.ref | 2 +- docs/en/refs/system.power.ref | 42 +++--- docs/en/refs/system.ref | 2 - docs/en/refs/unit.accel.ref | 20 --- docs/en/refs/unit.angle.ref | 8 +- docs/en/refs/unit.buzzer.ref | 12 +- docs/en/refs/unit.bytebutton.ref | 2 +- docs/en/refs/unit.byteswitch.ref | 2 +- docs/en/refs/unit.can.ref | 4 +- docs/en/refs/unit.dac.ref | 11 -- docs/en/refs/unit.dlight.ref | 6 +- docs/en/refs/unit.dmx.ref | 2 +- docs/en/refs/unit.dual_button.ref | 5 - docs/en/refs/unit.earth.ref | 7 - docs/en/refs/unit.encoder.ref | 20 +-- docs/en/refs/unit.env.ref | 10 +- docs/en/refs/unit.finger.ref | 18 --- docs/en/refs/unit.gps_v11.ref | 2 +- docs/en/refs/unit.ir.ref | 4 - docs/en/refs/unit.joystick.ref | 9 -- docs/en/refs/unit.joystick2.ref | 2 +- docs/en/refs/unit.light.ref | 5 - docs/en/refs/unit.lora_e220.ref | 26 ++-- docs/en/refs/unit.lora_e220_433.ref | 4 +- docs/en/refs/unit.lorawan_rui3.ref | 8 +- docs/en/refs/unit.midi.ref | 2 +- docs/en/refs/unit.miniscale.ref | 30 ++-- docs/en/refs/unit.nbiot2.ref | 2 +- docs/en/refs/unit.ncir.ref | 4 - docs/en/refs/unit.op180.ref | 8 +- docs/en/refs/unit.op90.ref | 8 +- docs/en/refs/unit.pir.ref | 6 - docs/en/refs/unit.puzzle.ref | 2 +- docs/en/refs/unit.relay.ref | 6 - docs/en/refs/unit.rgb.ref | 10 +- docs/en/refs/unit.roller485.ref | 2 +- docs/en/refs/unit.rollercan.ref | 2 +- docs/en/refs/unit.scroll.ref | 2 +- docs/en/refs/unit.thermal.ref | 18 +-- docs/en/refs/unit.timerpwr.ref | 2 +- docs/en/refs/unit.tmos.ref | 2 +- docs/en/refs/unit.tof.ref | 13 -- docs/en/refs/unit.ultrasonic.ref | 6 +- docs/en/refs/unit.vibrator.ref | 7 - docs/en/refs/unit.weight_i2c.ref | 32 ++--- docs/en/software/umqtt.default.rst | 2 +- docs/en/system/power.rst | 46 +++--- docs/en/units/angle.rst | 8 +- docs/en/units/buzzer.rst | 12 +- docs/en/units/dac.rst | 20 +-- docs/en/units/dlight.rst | 6 +- docs/en/units/earth.rst | 12 +- docs/en/units/encoder.rst | 20 +-- docs/en/units/env.rst | 10 +- docs/en/units/light.rst | 8 +- docs/en/units/lora_e220.rst | 28 ++-- docs/en/units/lora_e220_433.rst | 12 +- docs/en/units/miniscale.rst | 140 +++++++++--------- docs/en/units/op180.rst | 8 +- docs/en/units/op90.rst | 8 +- docs/en/units/qrcode.rst | 4 +- docs/en/units/rgb.rst | 10 +- docs/en/units/thermal.rst | 18 +-- docs/en/units/ultrasonic.rst | 6 +- docs/en/units/vibrator.rst | 2 +- docs/en/units/weight_i2c.rst | 152 ++++++++++---------- m5stack/libs/unit/accel.py | 18 +-- 114 files changed, 627 insertions(+), 742 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index afdedbe5..a0572bc0 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -42,8 +42,8 @@ build-docs: - python3 -m venv . - source bin/activate - pip3 install -r requirements.txt - - sphinx-build -b html -D language=zh_CN ./en/ build/html/zh_CN - - sphinx-build -b html -D language=en ./en/ build/html/en + - sphinx-build -W -b html -D language=zh_CN ./en/ build/html/zh_CN + - sphinx-build -W -b html -D language=en ./en/ build/html/en - cd - artifacts: paths: diff --git a/docs/README.md b/docs/README.md index d316044b..09c90dc5 100644 --- a/docs/README.md +++ b/docs/README.md @@ -15,6 +15,10 @@ sphinx-build -b gettext ./en build/gettext sphinx-intl update -p ./build/gettext -l zh_CN sphinx-build -b html -D language=zh_CN ./en/ build/html/zh_CN # 简体中文 sphinx-build -b html -D language=en ./en/ build/html/en # English +# pdf +sphinx-build -b latex ./en/ ./build/latex +cd ./build/latex +make ``` ## 预览 diff --git a/docs/en/conf.py b/docs/en/conf.py index 5aaca157..77d353d7 100644 --- a/docs/en/conf.py +++ b/docs/en/conf.py @@ -70,6 +70,21 @@ "micropython": ("https://docs.micropython.org/en/v1.22.0/", None), } +latex_engine = 'pdflatex' + +latex_elements = { + # 这里保留了简单的代码块字体设置,移除了中文排版相关的配置 + 'preamble': r''' +\usepackage{listings} +\lstset{ + basicstyle=\ttfamily\small, + breaklines=true, +} +''', + 'fncychap': '\\usepackage[Bjornstrup]{fncychap}', # 可选章节样式 + 'printindex': r'\footnotesize\raggedright\printindex', +} + # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] diff --git a/docs/en/hardware/als.rst b/docs/en/hardware/als.rst index b81a8630..6937184f 100644 --- a/docs/en/hardware/als.rst +++ b/docs/en/hardware/als.rst @@ -44,9 +44,9 @@ class ALS .. important:: - Methods of the ALS Class heavily rely on ``M5.begin()`` |M5.begin.svg| and ``M5.update()`` |M5.update.svg|. + Methods of the ALS Class heavily rely on ``M5.begin()`` |M5.begin.png| and ``M5.update()`` |M5.update.png|. - All calls to methods of ALS objects should be placed after ``M5.begin()`` |M5.begin.svg|, and ``M5.update()`` |M5.update.svg| should be called in the main loop. + All calls to methods of ALS objects should be placed after ``M5.begin()`` |M5.begin.png|, and ``M5.update()`` |M5.update.png| should be called in the main loop. Methods diff --git a/docs/en/hardware/button.rst b/docs/en/hardware/button.rst index a6d4de60..deead235 100644 --- a/docs/en/hardware/button.rst +++ b/docs/en/hardware/button.rst @@ -60,7 +60,7 @@ Micropython Example:: UIFLOW2 Example: - |example.svg| + |example.png| .. only:: builder_html @@ -73,9 +73,9 @@ class Button .. important:: - Methods of Button Class heavily rely on ``M5.begin()`` |M5.begin.svg| and ``M5.update()`` |M5.update.svg|. + Methods of Button Class heavily rely on ``M5.begin()`` |M5.begin.png| and ``M5.update()`` |M5.update.png|. - All calls to methods of Button objects should be placed after ``M5.begin()`` |M5.begin.svg| and ``M5.update()`` |M5.update.svg| should be called in the main loop. + All calls to methods of Button objects should be placed after ``M5.begin()`` |M5.begin.png| and ``M5.update()`` |M5.update.png| should be called in the main loop. Methods @@ -87,7 +87,7 @@ Methods UIFLOW2: - |isHolding.svg| + |isHolding.png| .. method:: Button.isPressed() @@ -96,7 +96,7 @@ Methods UIFLOW2: - |isPressed.svg| + |isPressed.png| .. method:: Button.isReleased() @@ -105,7 +105,7 @@ Methods UIFLOW2: - |isReleased.svg| + |isReleased.png| .. method:: Button.wasClicked() @@ -114,7 +114,7 @@ Methods UIFLOW2: - |wasClicked.svg| + |wasClicked.png| .. method:: Button.wasDoubleClicked() @@ -123,7 +123,7 @@ Methods UIFLOW2: - |wasDoubleClicked.svg| + |wasDoubleClicked.png| .. method:: Button.wasHold() @@ -132,7 +132,7 @@ Methods UIFLOW2: - |wasHold.svg| + |wasHold.png| .. method:: Button.wasPressed() @@ -141,7 +141,7 @@ Methods UIFLOW2: - |wasPressed.svg| + |wasPressed.png| .. method:: Button.wasReleased() @@ -150,7 +150,7 @@ Methods UIFLOW2: - |wasReleased.svg| + |wasReleased.png| .. method:: Button.wasSingleClicked() @@ -159,7 +159,7 @@ Methods UIFLOW2: - |wasSingleClicked.svg| + |wasSingleClicked.png| Event Handling @@ -171,7 +171,7 @@ Event Handling UIFLOW2: - |setCallback.svg| + |setCallback.png| Constants diff --git a/docs/en/hardware/can.rst b/docs/en/hardware/can.rst index 9cf1b7e6..2b108b47 100644 --- a/docs/en/hardware/can.rst +++ b/docs/en/hardware/can.rst @@ -34,7 +34,7 @@ Constructors UIFLOW2: - |init.svg| + |init.png| Methods @@ -80,7 +80,7 @@ Methods UIFLOW2: - |deinit.svg| + |deinit.png| .. method:: CAN.restart() @@ -96,7 +96,7 @@ Methods UIFLOW2: - |restart.svg| + |restart.png| .. method:: CAN.state() @@ -110,7 +110,7 @@ Methods UIFLOW2: - |state.svg| + |state.png| .. method:: CAN.info([list]) @@ -134,7 +134,7 @@ Methods UIFLOW2: - |info.svg| + |info.png| .. method:: CAN.any(fifo) @@ -143,7 +143,7 @@ Methods UIFLOW2: - |any.svg| + |any.png| .. method:: CAN.recv(fifo, list=None, *, timeout=5000) @@ -182,9 +182,9 @@ Methods UIFLOW2: - |recv1.svg| + |recv1.png| - |recv2.svg| + |recv2.png| .. method:: CAN.send(data, id, *, timeout=0, rtr=False, extframe=False) @@ -211,7 +211,7 @@ Methods UIFLOW2: - |send.svg| + |send.png| Constants diff --git a/docs/en/hardware/imu.rst b/docs/en/hardware/imu.rst index 76a53192..09091a7d 100644 --- a/docs/en/hardware/imu.rst +++ b/docs/en/hardware/imu.rst @@ -56,9 +56,9 @@ class IMU .. important:: - Methods of the IMU Class heavily rely on ``M5.begin()`` |M5.begin.svg| and ``M5.update()`` |M5.update.svg|. + Methods of the IMU Class heavily rely on ``M5.begin()`` |M5.begin.png| and ``M5.update()`` |M5.update.png|. - All calls to methods of IMU objects should be placed after ``M5.begin()`` |M5.begin.svg|, and ``M5.update()`` |M5.update.svg| should be called in the main loop. + All calls to methods of IMU objects should be placed after ``M5.begin()`` |M5.begin.png|, and ``M5.update()`` |M5.update.png| should be called in the main loop. Methods diff --git a/docs/en/hardware/mic.rst b/docs/en/hardware/mic.rst index 0aa7fbe1..402868cf 100644 --- a/docs/en/hardware/mic.rst +++ b/docs/en/hardware/mic.rst @@ -57,9 +57,9 @@ class Mic .. important:: - Methods of the Mic Class heavily rely on ``M5.begin()`` |M5.begin.svg| and ``M5.update()`` |M5.update.svg|. + Methods of the Mic Class heavily rely on ``M5.begin()`` |M5.begin.png| and ``M5.update()`` |M5.update.png|. - All calls to methods of Mic objects should be placed after ``M5.begin()`` |M5.begin.svg|, and ``M5.update()`` |M5.update.svg| should be called in the main loop. + All calls to methods of Mic objects should be placed after ``M5.begin()`` |M5.begin.png|, and ``M5.update()`` |M5.update.png| should be called in the main loop. .. _hardware.Mic.Methods: diff --git a/docs/en/hats/cardkb.rst b/docs/en/hats/cardkb.rst index 5412c56f..11c6477d 100644 --- a/docs/en/hats/cardkb.rst +++ b/docs/en/hats/cardkb.rst @@ -17,7 +17,7 @@ Micropython Example: UIFLOW2 Example: - |example.svg| + |example.png| .. only:: builder_html diff --git a/docs/en/hats/neoflash.rst b/docs/en/hats/neoflash.rst index f3078d9a..1a0ba46c 100644 --- a/docs/en/hats/neoflash.rst +++ b/docs/en/hats/neoflash.rst @@ -30,7 +30,7 @@ Micropython Example:: UIFLOW2 Example: - |example.svg| + |example.png| .. only:: builder_html @@ -50,7 +50,7 @@ Constructors UIFLOW2: - |init.svg| + |init.png| Methods @@ -66,7 +66,7 @@ Methods UIFLOW2: - |set_pixel.svg| + |set_pixel.png| .. method:: NeoFlashHat.set_pixels(data: list) -> None @@ -77,7 +77,7 @@ Methods UIFLOW2: - |set_pixels.svg| + |set_pixels.png| Constants diff --git a/docs/en/hats/servo.rst b/docs/en/hats/servo.rst index bf5b7d05..1300d56f 100644 --- a/docs/en/hats/servo.rst +++ b/docs/en/hats/servo.rst @@ -29,7 +29,7 @@ Micropython Example:: UIFLOW2 Example: - |example.svg| + |example.png| .. only:: builder_html @@ -49,7 +49,7 @@ Constructors UIFLOW2: - |init.svg| + |init.png| Methods @@ -63,7 +63,7 @@ Methods UIFLOW2: - |set_duty.svg| + |set_duty.png| .. method:: ServoHat.set_percent(percent: int) -> None @@ -74,7 +74,7 @@ Methods UIFLOW2: - |set_percent.svg| + |set_percent.png| .. method:: ServoHat.deinit() @@ -83,4 +83,4 @@ Methods UIFLOW2: - |deinit.svg| + |deinit.png| diff --git a/docs/en/hats/servo8.rst b/docs/en/hats/servo8.rst index 3415aaf9..642cfacf 100644 --- a/docs/en/hats/servo8.rst +++ b/docs/en/hats/servo8.rst @@ -50,7 +50,7 @@ Constructors UIFLOW2: - |init.svg| + |init.png| Methods @@ -65,7 +65,7 @@ Methods UIFLOW2: - |write_servo_angle.svg| + |write_servo_angle.png| .. method:: Servos8Hat.read_servo_angle(ch) @@ -76,7 +76,7 @@ Methods UIFLOW2: - |read_servo_angle.svg| + |read_servo_angle.png| .. method:: Servos8Hat.write_servo_pulse(ch, pulse) @@ -88,7 +88,7 @@ Methods UIFLOW2: - |write_servo_pulse.svg| + |write_servo_pulse.png| .. method:: Servos8Hat.read_servo_pulse(ch) @@ -99,7 +99,7 @@ Methods UIFLOW2: - |read_servo_pulse.svg| + |read_servo_pulse.png| .. method:: Servos8Hat.power_ctrl(state) @@ -110,7 +110,7 @@ Methods UIFLOW2: - |power_ctrl.svg| + |power_ctrl.png| .. method:: Servos8Hat.power_on() @@ -119,7 +119,7 @@ Methods UIFLOW2: - |power_on.svg| + |power_on.png| .. method:: Servos8Hat.power_off() @@ -128,7 +128,7 @@ Methods UIFLOW2: - |power_off.svg| + |power_off.png| .. method:: Servos8Hat.get_power_state() @@ -137,4 +137,4 @@ Methods UIFLOW2: - |get_power_state.svg| + |get_power_state.png| diff --git a/docs/en/module/dualkmeter.rst b/docs/en/module/dualkmeter.rst index dc401568..73260a2a 100644 --- a/docs/en/module/dualkmeter.rst +++ b/docs/en/module/dualkmeter.rst @@ -27,7 +27,7 @@ Micropython Example:: UIFLOW2 Example: - |example.svg| + |example.png| .. only:: builder_html @@ -47,7 +47,7 @@ Constructors UIFLOW2: - |init.svg| + |init.png| Methods @@ -61,7 +61,7 @@ Methods UIFLOW2: - |get_thermocouple_temperature.svg| + |get_thermocouple_temperature.png| .. method:: DualKmeterModule.get_kmeter_temperature(scale=0) -> float @@ -72,7 +72,7 @@ Methods UIFLOW2: - |get_kmeter_temperature.svg| + |get_kmeter_temperature.png| .. method:: DualKmeterModule.get_kmeter_channel() -> int @@ -81,7 +81,7 @@ Methods UIFLOW2: - |get_kmeter_channel.svg| + |get_kmeter_channel.png| .. method:: DualKmeterModule.set_kmeter_channel(channel) -> None @@ -90,7 +90,7 @@ Methods UIFLOW2: - |set_kmeter_channel.svg| + |set_kmeter_channel.png| .. method:: DualKmeterModule.is_ready() -> bool @@ -99,7 +99,7 @@ Methods UIFLOW2: - |is_ready.svg| + |is_ready.png| .. method:: DualKmeterModule.get_thermocouple_temperature_string(scale=0) -> str @@ -110,7 +110,7 @@ Methods UIFLOW2: - |get_thermocouple_temperature_string.svg| + |get_thermocouple_temperature_string.png| .. method:: DualKmeterModule.get_kmeter_temperature_string(scale=0) -> str @@ -121,7 +121,7 @@ Methods UIFLOW2: - |get_kmeter_temperature_string.svg| + |get_kmeter_temperature_string.png| .. method:: DualKmeterModule.get_fw_ver() -> int @@ -130,7 +130,7 @@ Methods UIFLOW2: - |get_fw_ver.svg| + |get_fw_ver.png| Constants diff --git a/docs/en/module/lora.rst b/docs/en/module/lora.rst index 7045fb9e..e9b014b9 100644 --- a/docs/en/module/lora.rst +++ b/docs/en/module/lora.rst @@ -33,9 +33,9 @@ Micropython Example:: UIFLOW2 Example: - |example_tx.svg| + |example_tx.png| - |example_rx.svg| + |example_rx.png| .. only:: builder_html diff --git a/docs/en/quick-reference/usb-mode.rst b/docs/en/quick-reference/usb-mode.rst index 54772149..eda065cc 100644 --- a/docs/en/quick-reference/usb-mode.rst +++ b/docs/en/quick-reference/usb-mode.rst @@ -66,6 +66,6 @@ Image resources need to be saved under the ``res/img`` directory. Use Images from the Device -------------------------- -Use the |setImage.svg| block to set the filename of the image to be displayed, and show the image on the screen. +Use the |setImage.png| block to set the filename of the image to be displayed, and show the image on the screen. |usb-mode-8.gif| diff --git a/docs/en/refs/base.can.ref b/docs/en/refs/base.can.ref index 89ef13a3..0ef56f79 100644 --- a/docs/en/refs/base.can.ref +++ b/docs/en/refs/base.can.ref @@ -8,7 +8,7 @@ :height: 200px :width: 200px -.. |init.png| image:: https://m5stack.oss-cn-shenzhen.aliyuncs.com/mpy_docs/base/can/init.png +.. |init.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/can/init.png .. |example.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/can/example.png diff --git a/docs/en/refs/base.display.ref b/docs/en/refs/base.display.ref index 38f2f928..b5de1a3e 100644 --- a/docs/en/refs/base.display.ref +++ b/docs/en/refs/base.display.ref @@ -1,4 +1,4 @@ -.. |Atomic Display Base| image:: https://m5stack.oss-cn-shenzhen.aliyuncs.com/resource/docs/products/atom/Atomic%20Display%20Base/4.webp +.. |Atomic Display Base| image:: https://static-cdn.m5stack.com/resource/docs/products/atom/Atomic%20Display%20Base/4.webp :target: https://docs.m5stack.com/en/atom/Atomic%20Display%20Base :height: 200px :width: 200px diff --git a/docs/en/refs/base.echo.ref b/docs/en/refs/base.echo.ref index 887b1e0d..b5a53e8c 100644 --- a/docs/en/refs/base.echo.ref +++ b/docs/en/refs/base.echo.ref @@ -1,5 +1,5 @@ -.. |Atomic Echo Base| image:: https://m5stack.oss-cn-shenzhen.aliyuncs.com/resource/docs/products/atom/Atomic%20Echo%20Base/4.webp +.. |Atomic Echo Base| image:: https://static-cdn.m5stack.com/resource/docs/products/atom/Atomic%20Echo%20Base/4.webp :target: https://docs.m5stack.com/en/atom/Atomic%20Echo%20Base :height: 200px :width: 200px diff --git a/docs/en/refs/base.gps.ref b/docs/en/refs/base.gps.ref index d338aab7..eb4ab900 100644 --- a/docs/en/refs/base.gps.ref +++ b/docs/en/refs/base.gps.ref @@ -8,22 +8,22 @@ :height: 200px :width: 200px -.. |init.png| image:: https://m5stack.oss-cn-shenzhen.aliyuncs.com/mpy_docs/base/gps/init.png -.. |get_antenna_state.png| image:: https://m5stack.oss-cn-shenzhen.aliyuncs.com/mpy_docs/base/gps/get_antenna_state.png -.. |get_gps_time.png| image:: https://m5stack.oss-cn-shenzhen.aliyuncs.com/mpy_docs/base/gps/get_gps_time.png -.. |get_gps_date.png| image:: https://m5stack.oss-cn-shenzhen.aliyuncs.com/mpy_docs/base/gps/get_gps_date.png -.. |get_gps_date_time.png| image:: https://m5stack.oss-cn-shenzhen.aliyuncs.com/mpy_docs/base/gps/get_gps_date_time.png -.. |get_timestamp.png| image:: https://m5stack.oss-cn-shenzhen.aliyuncs.com/mpy_docs/base/gps/get_timestamp.png -.. |get_latitude.png| image:: https://m5stack.oss-cn-shenzhen.aliyuncs.com/mpy_docs/base/gps/get_latitude.png -.. |get_longitude.png| image:: https://m5stack.oss-cn-shenzhen.aliyuncs.com/mpy_docs/base/gps/get_longitude.png -.. |get_altitude.png| image:: https://m5stack.oss-cn-shenzhen.aliyuncs.com/mpy_docs/base/gps/get_altitude.png -.. |get_satellite_num.png| image:: https://m5stack.oss-cn-shenzhen.aliyuncs.com/mpy_docs/base/gps/get_satellite_num.png -.. |get_pos_quality.png| image:: https://m5stack.oss-cn-shenzhen.aliyuncs.com/mpy_docs/base/gps/get_pos_quality.png -.. |get_corse_over_ground.png| image:: https://m5stack.oss-cn-shenzhen.aliyuncs.com/mpy_docs/base/gps/get_corse_over_ground.png -.. |get_speed_over_ground.png| image:: https://m5stack.oss-cn-shenzhen.aliyuncs.com/mpy_docs/base/gps/get_speed_over_ground.png -.. |set_time_zone.png| image:: https://m5stack.oss-cn-shenzhen.aliyuncs.com/mpy_docs/base/gps/set_time_zone.png -.. |get_time_zone.png| image:: https://m5stack.oss-cn-shenzhen.aliyuncs.com/mpy_docs/base/gps/get_time_zone.png -.. |deinit.png| image:: https://m5stack.oss-cn-shenzhen.aliyuncs.com/mpy_docs/base/gps/deinit.png +.. |init.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/gps/init.png +.. |get_antenna_state.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/gps/get_antenna_state.png +.. |get_gps_time.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/gps/get_gps_time.png +.. |get_gps_date.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/gps/get_gps_date.png +.. |get_gps_date_time.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/gps/get_gps_date_time.png +.. |get_timestamp.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/gps/get_timestamp.png +.. |get_latitude.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/gps/get_latitude.png +.. |get_longitude.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/gps/get_longitude.png +.. |get_altitude.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/gps/get_altitude.png +.. |get_satellite_num.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/gps/get_satellite_num.png +.. |get_pos_quality.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/gps/get_pos_quality.png +.. |get_corse_over_ground.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/gps/get_corse_over_ground.png +.. |get_speed_over_ground.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/gps/get_speed_over_ground.png +.. |set_time_zone.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/gps/set_time_zone.png +.. |get_time_zone.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/gps/get_time_zone.png +.. |deinit.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/gps/deinit.png .. |example.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/gps/example.png diff --git a/docs/en/refs/base.qrcode2.ref b/docs/en/refs/base.qrcode2.ref index d7a5ceea..9d2ea6dd 100644 --- a/docs/en/refs/base.qrcode2.ref +++ b/docs/en/refs/base.qrcode2.ref @@ -1,4 +1,4 @@ -.. |Atomic QRCode2 Base| image:: https://m5stack.oss-cn-shenzhen.aliyuncs.com/resource/docs/products/atom/Atomic%20QRCode2%20Base/4.webp +.. |Atomic QRCode2 Base| image:: https://static-cdn.m5stack.com/resource/docs/products/atom/Atomic%20QRCode2%20Base/4.webp :target: https://docs.m5stack.com/zh_CN/atom/Atomic%20QRCode2%20Base :height: 200px :width: 200px diff --git a/docs/en/refs/base.tfcard.ref b/docs/en/refs/base.tfcard.ref index bff3433a..bf740c69 100644 --- a/docs/en/refs/base.tfcard.ref +++ b/docs/en/refs/base.tfcard.ref @@ -8,7 +8,7 @@ :height: 200px :width: 200px -.. |init.png| image:: https://m5stack.oss-cn-shenzhen.aliyuncs.com/mpy_docs/base/tfcard/init.png +.. |init.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/tfcard/init.png .. |example.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/tfcard/example.png diff --git a/docs/en/refs/hardware.button.ref b/docs/en/refs/hardware.button.ref index d301c18f..5d57fcd6 100644 --- a/docs/en/refs/hardware.button.ref +++ b/docs/en/refs/hardware.button.ref @@ -1,24 +1,24 @@ -.. |example.svg| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/button/example.svg +.. |example.png| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/button/example.png -.. |isHolding.svg| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/button/isHolding.svg +.. |isHolding.png| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/button/isHolding.png -.. |isPressed.svg| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/button/isPressed.svg +.. |isPressed.png| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/button/isPressed.png -.. |isReleased.svg| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/button/isReleased.svg +.. |isReleased.png| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/button/isReleased.png -.. |wasClicked.svg| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/button/wasClicked.svg +.. |wasClicked.png| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/button/wasClicked.png -.. |wasDoubleClicked.svg| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/button/wasDoubleClicked.svg +.. |wasDoubleClicked.png| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/button/wasDoubleClicked.png -.. |wasHold.svg| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/button/wasHold.svg +.. |wasHold.png| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/button/wasHold.png -.. |wasPressed.svg| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/button/wasPressed.svg +.. |wasPressed.png| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/button/wasPressed.png -.. |wasReleased.svg| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/button/wasReleased.svg +.. |wasReleased.png| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/button/wasReleased.png -.. |wasSingleClicked.svg| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/button/wasSingleClicked.svg +.. |wasSingleClicked.png| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/button/wasSingleClicked.png -.. |setCallback.svg| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/button/setCallback.svg +.. |setCallback.png| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/button/setCallback.png .. |button_cores3_example.m5f2| raw:: html diff --git a/docs/en/refs/hardware.can.ref b/docs/en/refs/hardware.can.ref index 05d85f60..686591da 100644 --- a/docs/en/refs/hardware.can.ref +++ b/docs/en/refs/hardware.can.ref @@ -1,17 +1,17 @@ -.. |init.svg| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/can/init.svg +.. |init.png| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/can/init.png -.. |deinit.svg| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/can/deinit.svg +.. |deinit.png| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/can/deinit.png -.. |restart.svg| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/can/restart.svg +.. |restart.png| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/can/restart.png -.. |state.svg| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/can/get_state.svg +.. |state.png| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/can/get_state.png -.. |info.svg| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/can/get_info.svg +.. |info.png| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/can/get_info.png -.. |any.svg| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/can/any.svg +.. |any.png| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/can/any.png -.. |recv1.svg| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/can/recv_message.svg +.. |recv1.png| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/can/recv_message.png -.. |recv2.svg| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/can/recv_message_into.svg +.. |recv2.png| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/can/recv_message_into.png -.. |send.svg| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/can/send.svg +.. |send.png| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/can/send.png diff --git a/docs/en/refs/hardware.sen55.ref b/docs/en/refs/hardware.sen55.ref index 35db4c04..25f42f89 100644 --- a/docs/en/refs/hardware.sen55.ref +++ b/docs/en/refs/hardware.sen55.ref @@ -1,33 +1,33 @@ .. |init.png| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/sen55/init.png .. |set_power_state.png| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/sen55/set_power_state.png .. |get_power_state.png| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/sen55/get_power_state.png -.. |available.png| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/sen55/available.png +.. .. |available.png| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/sen55/available.png .. |set_work_mode.png| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/sen55/set_work_mode.png -.. |get_sensor_data.png| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/sen55/get_sensor_data.png +.. .. |get_sensor_data.png| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/sen55/get_sensor_data.png .. |get_data_ready_flag.png| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/sen55/get_data_ready_flag.png -.. |set_temp_cmp_params.png| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/sen55/set_temp_cmp_params.png -.. |get_temp_cmp_params.png| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/sen55/get_temp_cmp_params.png -.. |set_warm_start_param.png| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/sen55/set_warm_start_param.png -.. |get_warm_start_param.png| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/sen55/get_warm_start_param.png -.. |set_voc_algo_tuning_params.png| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/sen55/set_voc_algo_tuning_params.png -.. |get_voc_algo_tuning_params.png| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/sen55/get_voc_algo_tuning_params.png -.. |set_nox_algo_tuning_params.png| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/sen55/set_nox_algo_tuning_params.png -.. |get_nox_algo_tuning_params.png| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/sen55/get_nox_algo_tuning_params.png -.. |set_rht_acceleration_mode.png| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/sen55/set_rht_acceleration_mode.png -.. |get_rht_acceleration_mode.png| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/sen55/get_rht_acceleration_mode.png -.. |get_voc_algo_state.png| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/sen55/get_voc_algo_state.png -.. |set_voc_algo_state.png| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/sen55/set_voc_algo_state.png -.. |set_start_fan_cleaning.png| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/sen55/set_start_fan_cleaning.png -.. |get_auto_cleaning_interval.png| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/sen55/get_auto_cleaning_interval.png -.. |set_auto_cleaning_interval.png| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/sen55/set_auto_cleaning_interval.png -.. |get_device_status.png| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/sen55/get_device_status.png -.. |clear_device_status.png| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/sen55/clear_device_status.png -.. |get_serial_number.png| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/sen55/get_serial_number.png -.. |get_product_name.png| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/sen55/get_product_name.png -.. |send_cmd.png| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/sen55/send_cmd.png -.. |read_response.png| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/sen55/read_response.png -.. |check_crc.png| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/sen55/check_crc.png -.. |crc8.png| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/sen55/crc8.png +.. .. |set_temp_cmp_params.png| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/sen55/set_temp_cmp_params.png +.. .. |get_temp_cmp_params.png| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/sen55/get_temp_cmp_params.png +.. .. |set_warm_start_param.png| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/sen55/set_warm_start_param.png +.. .. |get_warm_start_param.png| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/sen55/get_warm_start_param.png +.. .. |set_voc_algo_tuning_params.png| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/sen55/set_voc_algo_tuning_params.png +.. .. |get_voc_algo_tuning_params.png| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/sen55/get_voc_algo_tuning_params.png +.. .. |set_nox_algo_tuning_params.png| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/sen55/set_nox_algo_tuning_params.png +.. .. |get_nox_algo_tuning_params.png| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/sen55/get_nox_algo_tuning_params.png +.. .. |set_rht_acceleration_mode.png| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/sen55/set_rht_acceleration_mode.png +.. .. |get_rht_acceleration_mode.png| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/sen55/get_rht_acceleration_mode.png +.. .. |get_voc_algo_state.png| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/sen55/get_voc_algo_state.png +.. .. |set_voc_algo_state.png| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/sen55/set_voc_algo_state.png +.. .. |set_start_fan_cleaning.png| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/sen55/set_start_fan_cleaning.png +.. .. |get_auto_cleaning_interval.png| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/sen55/get_auto_cleaning_interval.png +.. .. |set_auto_cleaning_interval.png| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/sen55/set_auto_cleaning_interval.png +.. .. |get_device_status.png| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/sen55/get_device_status.png +.. .. |clear_device_status.png| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/sen55/clear_device_status.png +.. .. |get_serial_number.png| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/sen55/get_serial_number.png +.. .. |get_product_name.png| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/sen55/get_product_name.png +.. .. |send_cmd.png| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/sen55/send_cmd.png +.. .. |read_response.png| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/sen55/read_response.png +.. .. |check_crc.png| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/sen55/check_crc.png +.. .. |crc8.png| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/sen55/crc8.png .. |get_pm1_0.png| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/sen55/get_pm1_0.png .. |get_pm2_5.png| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/sen55/get_pm2_5.png .. |get_pm4_0.png| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/sen55/get_pm4_0.png diff --git a/docs/en/refs/hat.cardkb.ref b/docs/en/refs/hat.cardkb.ref index c2b72f41..bba23e97 100644 --- a/docs/en/refs/hat.cardkb.ref +++ b/docs/en/refs/hat.cardkb.ref @@ -3,10 +3,8 @@ :height: 200px :width: 200 px -.. |example.svg| image:: https://static-cdn.m5stack.com/mpy_docs/hat/cardkb/example.svg .. |example.png| image:: https://static-cdn.m5stack.com/mpy_docs/hat/cardkb/example.png -.. |init.svg| image:: https://static-cdn.m5stack.com/mpy_docs/hat/cardkb/init.svg .. |init.png| image:: https://static-cdn.m5stack.com/mpy_docs/hat/cardkb/init.png .. |stickc_plus2_cardkb_example.m5f2| raw:: html diff --git a/docs/en/refs/hat.env.ref b/docs/en/refs/hat.env.ref index 6b58a72c..ef751ec5 100644 --- a/docs/en/refs/hat.env.ref +++ b/docs/en/refs/hat.env.ref @@ -7,19 +7,13 @@ :height: 200px :width: 200px -.. |example.svg| image:: https://static-cdn.m5stack.com/mpy_docs/hat/env/example.svg .. |example.png| image:: https://static-cdn.m5stack.com/mpy_docs/hat/env/example.png -.. |init.svg| image:: https://static-cdn.m5stack.com/mpy_docs/hat/env/init.svg .. |init.png| image:: https://static-cdn.m5stack.com/mpy_docs/hat/env/init.png -.. |read_temperature.svg| image:: https://static-cdn.m5stack.com/mpy_docs/hat/env/read_temperature.svg .. |read_temperature.png| image:: https://static-cdn.m5stack.com/mpy_docs/hat/env/read_temperature.png -.. |read_humidity.svg| image:: https://static-cdn.m5stack.com/mpy_docs/hat/env/read_humidity.svg .. |read_humidity.png| image:: https://static-cdn.m5stack.com/mpy_docs/hat/env/read_humidity.png - -.. |read_pressure.svg| image:: https://static-cdn.m5stack.com/mpy_docs/hat/env/read_pressure.svg .. |read_pressure.png| image:: https://static-cdn.m5stack.com/mpy_docs/hat/env/read_pressure.png .. |stickc_plus2_env_hat_example.m5f2| raw:: html diff --git a/docs/en/refs/hat.neoflash.ref b/docs/en/refs/hat.neoflash.ref index b2f2dcc7..e6dc0dd2 100644 --- a/docs/en/refs/hat.neoflash.ref +++ b/docs/en/refs/hat.neoflash.ref @@ -4,9 +4,9 @@ :height: 200px :width: 200px -.. |init.svg| image:: https://static-cdn.m5stack.com/mpy_docs/hat/neoflash/init.svg -.. |set_pixel.svg| image:: https://static-cdn.m5stack.com/mpy_docs/hat/neoflash/set_pixel.svg -.. |set_pixels.svg| image:: https://static-cdn.m5stack.com/mpy_docs/hat/neoflash/set_pixels.svg +.. |init.png| image:: https://static-cdn.m5stack.com/mpy_docs/hat/neoflash/init.png +.. |set_pixel.png| image:: https://static-cdn.m5stack.com/mpy_docs/hat/neoflash/set_pixel.png +.. |set_pixels.png| image:: https://static-cdn.m5stack.com/mpy_docs/hat/neoflash/set_pixels.png -.. |example.svg| image:: https://static-cdn.m5stack.com/mpy_docs/hat/neoflash/example.svg +.. |example.png| image:: https://static-cdn.m5stack.com/mpy_docs/hat/neoflash/example.png diff --git a/docs/en/refs/hat.pir.ref b/docs/en/refs/hat.pir.ref index 71e7c177..ab01ca27 100644 --- a/docs/en/refs/hat.pir.ref +++ b/docs/en/refs/hat.pir.ref @@ -4,25 +4,18 @@ :width: 200px -.. |example.svg| image:: https://static-cdn.m5stack.com/mpy_docs/hat/pir/example.svg .. |example.png| image:: https://static-cdn.m5stack.com/mpy_docs/hat/pir/example.png -.. |init.svg| image:: https://static-cdn.m5stack.com/mpy_docs/hat/pir/init.svg .. |init.png| image:: https://static-cdn.m5stack.com/mpy_docs/hat/pir/init.png -.. |get_status.svg| image:: https://static-cdn.m5stack.com/mpy_docs/hat/pir/get_status.svg .. |get_status.png| image:: https://static-cdn.m5stack.com/mpy_docs/hat/pir/get_status.png -.. |enable_irq.svg| image:: https://static-cdn.m5stack.com/mpy_docs/hat/pir/enable_irq.svg .. |enable_irq.png| image:: https://static-cdn.m5stack.com/mpy_docs/hat/pir/enable_irq.png -.. |disable_irq.svg| image:: https://static-cdn.m5stack.com/mpy_docs/hat/pir/disable_irq.svg .. |disable_irq.png| image:: https://static-cdn.m5stack.com/mpy_docs/hat/pir/disable_irq.png -.. |set_callback.svg| image:: https://static-cdn.m5stack.com/mpy_docs/hat/pir/set_callback.svg .. |set_callback.png| image:: https://static-cdn.m5stack.com/mpy_docs/hat/pir/set_callback.png - .. |stickc_plus2_pir_hat_example.m5f2| raw:: html cores3_lora433_rx_example.m5f2 @@ -28,7 +28,7 @@ .. |cores3_lora433_tx_example.m5f2| raw:: html cores3_lora433_tx_example.m5f2 @@ -37,7 +37,7 @@ .. |cores3_lora868_rx_example.m5f2| raw:: html cores3_lora868_rx_example.m5f2 @@ -46,7 +46,7 @@ .. |cores3_lora868_tx_example.m5f2| raw:: html cores3_lora868_tx_example.m5f2 diff --git a/docs/en/refs/module.nbiot.ref b/docs/en/refs/module.nbiot.ref index c359f653..748c5f4e 100644 --- a/docs/en/refs/module.nbiot.ref +++ b/docs/en/refs/module.nbiot.ref @@ -4,4 +4,4 @@ :width: 200px -.. |init.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/nbiot/init.png \ No newline at end of file +.. |init.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/com_nbiot/init.png \ No newline at end of file diff --git a/docs/en/refs/module.pwrcan.ref b/docs/en/refs/module.pwrcan.ref index dbd18473..a9b7de9b 100644 --- a/docs/en/refs/module.pwrcan.ref +++ b/docs/en/refs/module.pwrcan.ref @@ -1,4 +1,4 @@ -.. |PwrCANModule| image:: https://m5stack.oss-cn-shenzhen.aliyuncs.com/resource/docs/products/module/Module13.2-PwrCAN/4.webp +.. |PwrCANModule| image:: https://static-cdn.m5stack.com/resource/docs/products/module/Module13.2-PwrCAN/4.webp :target: https://docs.m5stack.com/en/module/Module13.2-PwrCAN :height: 200px :width: 200px diff --git a/docs/en/refs/module.qrcode.ref b/docs/en/refs/module.qrcode.ref index 6490b68b..b1748b14 100644 --- a/docs/en/refs/module.qrcode.ref +++ b/docs/en/refs/module.qrcode.ref @@ -1,4 +1,4 @@ -.. |Module13.2 QRCode| image:: https://m5stack.oss-cn-shenzhen.aliyuncs.com/resource/docs/products/module/QRCodeModule/4.webp +.. |Module13.2 QRCode| image:: https://static-cdn.m5stack.com/resource/docs/products/module/QRCodeModule/4.webp :target: https://docs.m5stack.com/zh_CN/module/QRCodeModule :height: 200px :width: 200px diff --git a/docs/en/refs/module.servo2.ref b/docs/en/refs/module.servo2.ref index 5c5cb0af..cf3304ce 100644 --- a/docs/en/refs/module.servo2.ref +++ b/docs/en/refs/module.servo2.ref @@ -5,7 +5,7 @@ :width: 200px .. |init.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/servo2/init.png -.. |_us2duty.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/servo2/_us2duty.png +.. .. |_us2duty.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/servo2/_us2duty.png .. |set_degrees.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/servo2/set_degrees.png .. |set_duty.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/servo2/set_duty.png .. |set_pulse_width.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/servo2/set_pulse_width.png diff --git a/docs/en/refs/module.zigbee.ref b/docs/en/refs/module.zigbee.ref index 2fef5e35..1496b919 100644 --- a/docs/en/refs/module.zigbee.ref +++ b/docs/en/refs/module.zigbee.ref @@ -8,12 +8,12 @@ .. |get_custom_address.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/com_zigbee/get_custom_address.png .. |init.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/com_zigbee/init.png .. |isconnected.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/com_zigbee/isconnected.png -.. |join_network.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/com_zigbee/join_network.png +.. .. |join_network.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/com_zigbee/join_network.png .. |p2p_transmission.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/com_zigbee/p2p_transmission.png .. |receive_none_block.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/com_zigbee/receive_none_block.png .. |receive_data_str_event.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/com_zigbee/receive_data_str_event.png .. |receive_data_event.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/com_zigbee/receive_data_event.png -.. |restart_module.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/com_zigbee/restart_module.png +.. .. |restart_module.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/com_zigbee/restart_module.png .. |set_ant_type.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/com_zigbee/set_ant_type.png .. |set_channel.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/com_zigbee/set_channel.png .. |set_device_type.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/com_zigbee/set_device_type.png diff --git a/docs/en/refs/qr.usb-mode.ref b/docs/en/refs/qr.usb-mode.ref index eb084cd3..605f263b 100644 --- a/docs/en/refs/qr.usb-mode.ref +++ b/docs/en/refs/qr.usb-mode.ref @@ -7,4 +7,4 @@ .. |usb-mode-7.gif| image:: ../../_static/quick-reference/usb-mode/usb-mode-7.gif .. |usb-mode-8.gif| image:: ../../_static/quick-reference/usb-mode/usb-mode-8.gif -.. |setImage.svg| image:: https://static-cdn.m5stack.com/mpy_docs/widgets/image/setImage.svg +.. |setImage.png| image:: https://static-cdn.m5stack.com/mpy_docs/widgets/image/setImage.png diff --git a/docs/en/refs/software.modbus.rtu.master.ref b/docs/en/refs/software.modbus.rtu.master.ref index 094b76d3..9feaa972 100644 --- a/docs/en/refs/software.modbus.rtu.master.ref +++ b/docs/en/refs/software.modbus.rtu.master.ref @@ -1,5 +1,3 @@ -.. |connect.png| image:: https://static-cdn.m5stack.com/mpy_docs/software/modbus/modbus_rtu_master/connect.png -.. |disconnect.png| image:: https://static-cdn.m5stack.com/mpy_docs/software/modbus/modbus_rtu_master/disconnect.png .. |init.png| image:: https://static-cdn.m5stack.com/mpy_docs/software/modbus/modbus_rtu_master/init.png .. |read_coils.png| image:: https://static-cdn.m5stack.com/mpy_docs/software/modbus/modbus_rtu_master/read_coils.png .. |read_discrete_inputs.png| image:: https://static-cdn.m5stack.com/mpy_docs/software/modbus/modbus_rtu_master/read_discrete_inputs.png diff --git a/docs/en/refs/software.tcp.client.ref b/docs/en/refs/software.tcp.client.ref index 33fa32b4..44d50e6e 100644 --- a/docs/en/refs/software.tcp.client.ref +++ b/docs/en/refs/software.tcp.client.ref @@ -4,7 +4,7 @@ .. |read.png| image:: https://static-cdn.m5stack.com/mpy_docs/software/tcp/client/read.png .. |recv.png| image:: https://static-cdn.m5stack.com/mpy_docs/software/tcp/client/recv.png .. |send.png| image:: https://static-cdn.m5stack.com/mpy_docs/software/tcp/client/send.png -.. |setsockopt.png| image:: https://static-cdn.m5stack.com/mpy_docs/software/tcp/client/setsockopt.png +.. .. |setsockopt.png| image:: https://static-cdn.m5stack.com/mpy_docs/software/tcp/client/setsockopt.png .. |write.png| image:: https://static-cdn.m5stack.com/mpy_docs/software/tcp/client/write.png .. |readline.png| image:: https://static-cdn.m5stack.com/mpy_docs/software/tcp/client/readline.png .. |setblocking.png| image:: https://static-cdn.m5stack.com/mpy_docs/software/tcp/client/setblocking.png diff --git a/docs/en/refs/software.tcp.server.ref b/docs/en/refs/software.tcp.server.ref index ab893f96..cbc16756 100644 --- a/docs/en/refs/software.tcp.server.ref +++ b/docs/en/refs/software.tcp.server.ref @@ -5,12 +5,12 @@ .. |init.png| image:: https://static-cdn.m5stack.com/mpy_docs/software/tcp/server/init.png .. |read.png| image:: https://static-cdn.m5stack.com/mpy_docs/software/tcp/server/read.png .. |recv.png| image:: https://static-cdn.m5stack.com/mpy_docs/software/tcp/server/recv.png -.. |recvfrom.png| image:: https://static-cdn.m5stack.com/mpy_docs/software/tcp/server/recvfrom.png +.. .. |recvfrom.png| image:: https://static-cdn.m5stack.com/mpy_docs/software/tcp/server/recvfrom.png .. |send.png| image:: https://static-cdn.m5stack.com/mpy_docs/software/tcp/server/send.png -.. |sendto.png| image:: https://static-cdn.m5stack.com/mpy_docs/software/tcp/server/sendto.png +.. .. |sendto.png| image:: https://static-cdn.m5stack.com/mpy_docs/software/tcp/server/sendto.png .. |setsockopt.png| image:: https://static-cdn.m5stack.com/mpy_docs/software/tcp/server/setsockopt.png .. |write.png| image:: https://static-cdn.m5stack.com/mpy_docs/software/tcp/server/write.png -.. |readline.png| image:: https://static-cdn.m5stack.com/mpy_docs/software/tcp/server/readline.png +.. .. |readline.png| image:: https://static-cdn.m5stack.com/mpy_docs/software/tcp/server/readline.png .. |setblocking.png| image:: https://static-cdn.m5stack.com/mpy_docs/software/tcp/server/setblocking.png .. |settimeout.png| image:: https://static-cdn.m5stack.com/mpy_docs/software/tcp/server/settimeout.png diff --git a/docs/en/refs/software.umqtt.default.ref b/docs/en/refs/software.umqtt.default.ref index dfb59295..524da9cd 100644 --- a/docs/en/refs/software.umqtt.default.ref +++ b/docs/en/refs/software.umqtt.default.ref @@ -1,4 +1,4 @@ -.. |example.svg| image:: https://static-cdn.m5stack.com/mpy_docs/software/mqtt/example.svg +.. |example.png| image:: https://static-cdn.m5stack.com/mpy_docs/software/mqtt/example.png .. |init.png| image:: https://static-cdn.m5stack.com/mpy_docs/software/mqtt/init.png diff --git a/docs/en/refs/system.power.ref b/docs/en/refs/system.power.ref index be424178..a97ff238 100644 --- a/docs/en/refs/system.power.ref +++ b/docs/en/refs/system.power.ref @@ -1,39 +1,39 @@ -.. |setExtOutput1.svg| image:: https://static-cdn.m5stack.com/mpy_docs/system/power/setExtOutput1.svg -.. |setExtOutput2.svg| image:: https://static-cdn.m5stack.com/mpy_docs/system/power/setExtOutput2.svg +.. |setExtOutput1.png| image:: https://static-cdn.m5stack.com/mpy_docs/system/power/setExtOutput1.png +.. |setExtOutput2.png| image:: https://static-cdn.m5stack.com/mpy_docs/system/power/setExtOutput2.png -.. |getExtOutput.svg| image:: https://static-cdn.m5stack.com/mpy_docs/system/power/getExtOutput.svg +.. |getExtOutput.png| image:: https://static-cdn.m5stack.com/mpy_docs/system/power/getExtOutput.png -.. |setUsbOutput.svg| image:: https://static-cdn.m5stack.com/mpy_docs/system/power/setUsbOutput.svg +.. |setUsbOutput.png| image:: https://static-cdn.m5stack.com/mpy_docs/system/power/setUsbOutput.png -.. |getUsbOutput.svg| image:: https://static-cdn.m5stack.com/mpy_docs/system/power/getUsbOutput.svg +.. |getUsbOutput.png| image:: https://static-cdn.m5stack.com/mpy_docs/system/power/getUsbOutput.png -.. |setLed.svg| image:: https://static-cdn.m5stack.com/mpy_docs/system/power/setLed.svg +.. |setLed.png| image:: https://static-cdn.m5stack.com/mpy_docs/system/power/setLed.png -.. |powerOff.svg| image:: https://static-cdn.m5stack.com/mpy_docs/system/power/powerOff.svg +.. |powerOff.png| image:: https://static-cdn.m5stack.com/mpy_docs/system/power/powerOff.png -.. |timerSleep1.svg| image:: https://static-cdn.m5stack.com/mpy_docs/system/power/timerSleep1.svg -.. |timerSleep2.svg| image:: https://static-cdn.m5stack.com/mpy_docs/system/power/timerSleep2.svg -.. |timerSleep3.svg| image:: https://static-cdn.m5stack.com/mpy_docs/system/power/timerSleep3.svg +.. |timerSleep1.png| image:: https://static-cdn.m5stack.com/mpy_docs/system/power/timerSleep1.png +.. |timerSleep2.png| image:: https://static-cdn.m5stack.com/mpy_docs/system/power/timerSleep2.png +.. |timerSleep3.png| image:: https://static-cdn.m5stack.com/mpy_docs/system/power/timerSleep3.png -.. |deepSleep.svg| image:: https://static-cdn.m5stack.com/mpy_docs/system/power/deepSleep.svg +.. |deepSleep.png| image:: https://static-cdn.m5stack.com/mpy_docs/system/power/deepSleep.png -.. |lightSleep.svg| image:: https://static-cdn.m5stack.com/mpy_docs/system/power/lightSleep.svg +.. |lightSleep.png| image:: https://static-cdn.m5stack.com/mpy_docs/system/power/lightSleep.png -.. |getBatteryLevel.svg| image:: https://static-cdn.m5stack.com/mpy_docs/system/power/getBatteryLevel.svg +.. |getBatteryLevel.png| image:: https://static-cdn.m5stack.com/mpy_docs/system/power/getBatteryLevel.png -.. |setBatteryCharge.svg| image:: https://static-cdn.m5stack.com/mpy_docs/system/power/setBatteryCharge.svg +.. |setBatteryCharge.png| image:: https://static-cdn.m5stack.com/mpy_docs/system/power/setBatteryCharge.png -.. |setChargeCurrent.svg| image:: https://static-cdn.m5stack.com/mpy_docs/system/power/setChargeCurrent.svg +.. |setChargeCurrent.png| image:: https://static-cdn.m5stack.com/mpy_docs/system/power/setChargeCurrent.png -.. |setChargeVoltage.svg| image:: https://static-cdn.m5stack.com/mpy_docs/system/power/setChargeVoltage.svg +.. |setChargeVoltage.png| image:: https://static-cdn.m5stack.com/mpy_docs/system/power/setChargeVoltage.png -.. |isCharging.svg| image:: https://static-cdn.m5stack.com/mpy_docs/system/power/isCharging.svg +.. |isCharging.png| image:: https://static-cdn.m5stack.com/mpy_docs/system/power/isCharging.png -.. |getBatteryVoltage.svg| image:: https://static-cdn.m5stack.com/mpy_docs/system/power/getBatteryVoltage.svg +.. |getBatteryVoltage.png| image:: https://static-cdn.m5stack.com/mpy_docs/system/power/getBatteryVoltage.png -.. |getBatteryCurrent.svg| image:: https://static-cdn.m5stack.com/mpy_docs/system/power/getBatteryCurrent.svg +.. |getBatteryCurrent.png| image:: https://static-cdn.m5stack.com/mpy_docs/system/power/getBatteryCurrent.png -.. |getKeyState.svg| image:: https://static-cdn.m5stack.com/mpy_docs/system/power/getKeyState.svg +.. |getKeyState.png| image:: https://static-cdn.m5stack.com/mpy_docs/system/power/getKeyState.png -.. |setVibration.svg| image:: https://static-cdn.m5stack.com/mpy_docs/system/power/setVibration.svg +.. |setVibration.png| image:: https://static-cdn.m5stack.com/mpy_docs/system/power/setVibration.png diff --git a/docs/en/refs/system.ref b/docs/en/refs/system.ref index 6129d3d4..f9b63b84 100644 --- a/docs/en/refs/system.ref +++ b/docs/en/refs/system.ref @@ -1,5 +1,3 @@ -.. |M5.begin.svg| image:: https://static-cdn.m5stack.com/mpy_docs/system/system/begin.svg .. |M5.begin.png| image:: https://static-cdn.m5stack.com/mpy_docs/system/system/begin.png -.. |M5.update.svg| image:: https://static-cdn.m5stack.com/mpy_docs/system/system/update.svg .. |M5.update.png| image:: https://static-cdn.m5stack.com/mpy_docs/system/system/update.png diff --git a/docs/en/refs/unit.accel.ref b/docs/en/refs/unit.accel.ref index e7332ff5..c6a61a98 100644 --- a/docs/en/refs/unit.accel.ref +++ b/docs/en/refs/unit.accel.ref @@ -3,62 +3,42 @@ :height: 200px :width: 200px -.. |example.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/accel/example.svg .. |example.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/accel/example.png -.. |init.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/accel/init.svg .. |init.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/accel/init.png -.. |get_accel.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/accel/get_accel.svg .. |get_accel.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/accel/get_accel.png - -.. |enable_motion_detection1.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/accel/enable_motion_detection.svg .. |enable_motion_detection1.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/accel/enable_motion_detection.png -.. |enable_motion_detection2.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/accel/enable_motion_detection1.svg .. |enable_motion_detection2.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/accel/enable_motion_detection1.png -.. |disable_motion_detection.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/accel/disable_motion_detection.svg .. |disable_motion_detection.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/accel/disable_motion_detection.png -.. |is_tap.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/accel/is_tap.svg .. |is_tap.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/accel/is_tap.png -.. |is_motion.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/accel/is_motion.svg .. |is_motion.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/accel/is_motion.png -.. |is_freefall.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/accel/is_freefall.svg .. |is_freefall.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/accel/is_freefall.png -.. |enable_freefall_detection1.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/accel/enable_freefall_detection.svg .. |enable_freefall_detection1.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/accel/enable_freefall_detection.png -.. |enable_freefall_detection2.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/accel/enable_freefall_detection2.svg .. |enable_freefall_detection2.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/accel/enable_freefall_detection2.png -.. |disable_freefall_detection.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/accel/disable_freefall_detection.svg .. |disable_freefall_detection.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/accel/disable_freefall_detection.png -.. |enable_tap_detection1.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/accel/enable_tap_detection.svg .. |enable_tap_detection1.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/accel/enable_tap_detection.png -.. |enable_tap_detection2.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/accel/enable_tap_detection2.svg .. |enable_tap_detection2.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/accel/enable_tap_detection2.png -.. |disable_tap_detection.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/accel/disable_tap_detection.svg .. |disable_tap_detection.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/accel/disable_tap_detection.png -.. |get_data_rate.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/accel/get_data_rate.svg .. |get_data_rate.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/accel/get_data_rate.png -.. |set_data_rate.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/accel/set_data_rate.svg .. |set_data_rate.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/accel/set_data_rate.png -.. |get_range.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/accel/get_range.svg .. |get_range.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/accel/get_range.png -.. |set_range.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/accel/set_range.svg .. |set_range.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/accel/set_range.png diff --git a/docs/en/refs/unit.angle.ref b/docs/en/refs/unit.angle.ref index 3927bb1b..965fe31a 100644 --- a/docs/en/refs/unit.angle.ref +++ b/docs/en/refs/unit.angle.ref @@ -3,13 +3,13 @@ :height: 200px :width: 200px -.. |example.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/angle/example.svg +.. |example.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/angle/example.png -.. |init.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/angle/init.svg +.. |init.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/angle/init.png -.. |get_value.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/angle/get_value.svg +.. |get_value.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/angle/get_value.png -.. |get_voltage.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/angle/get_voltage.svg +.. |get_voltage.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/angle/get_voltage.png .. |angle_core_example.m5f2| raw:: html diff --git a/docs/en/refs/unit.buzzer.ref b/docs/en/refs/unit.buzzer.ref index 19e70281..3e6b7a06 100644 --- a/docs/en/refs/unit.buzzer.ref +++ b/docs/en/refs/unit.buzzer.ref @@ -3,17 +3,17 @@ :height: 200px :width: 200px -.. |init.svg| image:: https://m5stack.oss-cn-shenzhen.aliyuncs.com/mpy_docs/unit/buzzer/init.svg +.. |init.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/buzzer/init.png -.. |once.svg| image:: https://m5stack.oss-cn-shenzhen.aliyuncs.com/mpy_docs/unit/buzzer/play_once.svg +.. |once.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/buzzer/play_once.png -.. |set_freq.svg| image:: https://m5stack.oss-cn-shenzhen.aliyuncs.com/mpy_docs/unit/buzzer/set_freq.svg +.. |set_freq.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/buzzer/set_freq.png -.. |set_duty.svg| image:: https://m5stack.oss-cn-shenzhen.aliyuncs.com/mpy_docs/unit/buzzer/set_duty.svg +.. |set_duty.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/buzzer/set_duty.png -.. |turn_off.svg| image:: https://m5stack.oss-cn-shenzhen.aliyuncs.com/mpy_docs/unit/buzzer/turn_off.svg +.. |turn_off.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/buzzer/turn_off.png -.. |deinit.svg| image:: https://m5stack.oss-cn-shenzhen.aliyuncs.com/mpy_docs/unit/buzzer/deinit.svg +.. |deinit.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/buzzer/deinit.png .. |example.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/buzzer/example.png diff --git a/docs/en/refs/unit.bytebutton.ref b/docs/en/refs/unit.bytebutton.ref index 8964e743..bd65b29b 100644 --- a/docs/en/refs/unit.bytebutton.ref +++ b/docs/en/refs/unit.bytebutton.ref @@ -1,4 +1,4 @@ -.. |ByteButtonUnit| image:: https://m5stack.oss-cn-shenzhen.aliyuncs.com/resource/docs/products/unit/Unit%20ByteButton/4.webp +.. |ByteButtonUnit| image:: https://static-cdn.m5stack.com/resource/docs/products/unit/Unit%20ByteButton/4.webp :target: https://docs.m5stack.com/en/unit/Unit%20ByteButton :height: 200px :width: 200px diff --git a/docs/en/refs/unit.byteswitch.ref b/docs/en/refs/unit.byteswitch.ref index c8b2257e..e04641e0 100644 --- a/docs/en/refs/unit.byteswitch.ref +++ b/docs/en/refs/unit.byteswitch.ref @@ -1,5 +1,5 @@ -.. |ByteSwitchUnit| image:: https://m5stack.oss-cn-shenzhen.aliyuncs.com/resource/docs/products/unit/Unit%20ByteSwitch/4.webp +.. |ByteSwitchUnit| image:: https://static-cdn.m5stack.com/resource/docs/products/unit/Unit%20ByteSwitch/4.webp :target: https://docs.m5stack.com/en/unit/Unit%20ByteSwitch :height: 200px :width: 200px diff --git a/docs/en/refs/unit.can.ref b/docs/en/refs/unit.can.ref index 1bc0f31f..b9c89400 100644 --- a/docs/en/refs/unit.can.ref +++ b/docs/en/refs/unit.can.ref @@ -8,9 +8,9 @@ :height: 200px :width: 200px -.. |init.png| image:: https://m5stack.oss-cn-shenzhen.aliyuncs.com/mpy_docs/unit/can/init.png +.. |init.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/can/init.png -.. |init1.png| image:: https://m5stack.oss-cn-shenzhen.aliyuncs.com/mpy_docs/unit/can/init1.png +.. |init1.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/can/init1.png .. |tx_example.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/can/tx_example.png diff --git a/docs/en/refs/unit.dac.ref b/docs/en/refs/unit.dac.ref index 09a88be1..1cf51075 100644 --- a/docs/en/refs/unit.dac.ref +++ b/docs/en/refs/unit.dac.ref @@ -3,37 +3,26 @@ :height: 200px :width: 200px -.. |example.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/dac/example.svg .. |example.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/dac/example.png -.. |init.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/dac/init.svg .. |init.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/dac/init.png -.. |get_value.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/dac/get_value.svg .. |get_value.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/dac/get_value.png -.. |get_voltage.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/dac/get_voltage.svg .. |get_voltage.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/dac/get_voltage.png -.. |set_value.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/dac/set_value.svg .. |set_value.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/dac/set_value.png -.. |set_voltage.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/dac/set_voltage.svg .. |set_voltage.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/dac/set_voltage.png -.. |get_raw_value.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/dac/get_raw_value.svg .. |get_raw_value.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/dac/get_raw_value.png -.. |set_raw_value.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/dac/set_raw_value.svg .. |set_raw_value.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/dac/set_raw_value.png -.. |get_normalized_value.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/dac/get_normalized_value.svg .. |get_normalized_value.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/dac/get_normalized_value.png -.. |set_normalized_value.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/dac/set_normalized_value.svg .. |set_normalized_value.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/dac/set_normalized_value.png -.. |save_to_eeprom.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/dac/save_to_eeprom.svg .. |save_to_eeprom.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/dac/save_to_eeprom.png .. |dac_core_example.m5f2| raw:: html diff --git a/docs/en/refs/unit.dlight.ref b/docs/en/refs/unit.dlight.ref index bade97ff..d3611513 100644 --- a/docs/en/refs/unit.dlight.ref +++ b/docs/en/refs/unit.dlight.ref @@ -6,11 +6,11 @@ .. |example.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/dlight/example.png -.. |init.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/dlight/init.svg +.. |init.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/dlight/init.png -.. |get_lux.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/dlight/get_lux.svg +.. |get_lux.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/dlight/get_lux.png -.. |configure.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/dlight/configure.svg +.. |configure.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/dlight/configure.png .. |dlight_core_example.m5f2| raw:: html diff --git a/docs/en/refs/unit.dmx.ref b/docs/en/refs/unit.dmx.ref index eff30687..4706c002 100644 --- a/docs/en/refs/unit.dmx.ref +++ b/docs/en/refs/unit.dmx.ref @@ -1,5 +1,5 @@ -.. |dmx| image:: https://m5stack.oss-cn-shenzhen.aliyuncs.com/resource/docs/products/unit/Unit-DMX/4.webp +.. |dmx| image:: https://static-cdn.m5stack.com/resource/docs/products/unit/Unit-DMX/4.webp :target: https://docs.m5stack.com/en/unit/dmx :height: 200px :width: 200px diff --git a/docs/en/refs/unit.dual_button.ref b/docs/en/refs/unit.dual_button.ref index 9c685474..3b706ff2 100644 --- a/docs/en/refs/unit.dual_button.ref +++ b/docs/en/refs/unit.dual_button.ref @@ -3,19 +3,14 @@ :height: 200px :width: 200px -.. |example.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/dual_button/example.svg .. |example.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/dual_button/example.png -.. |init.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/dual_button/init.svg .. |init.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/dual_button/init.png -.. |get_status.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/dual_button/get_status.svg .. |get_status.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/dual_button/get_status.png -.. |setCallback.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/dual_button/setCallback.svg .. |setCallback.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/dual_button/setCallback.png -.. |tick.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/dual_button/tick.svg .. |tick.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/dual_button/tick.png .. |dual_button_core_example.m5f2| raw:: html diff --git a/docs/en/refs/unit.earth.ref b/docs/en/refs/unit.earth.ref index 23f9b4a9..54ce53d7 100644 --- a/docs/en/refs/unit.earth.ref +++ b/docs/en/refs/unit.earth.ref @@ -3,25 +3,18 @@ :height: 200px :width: 200px -.. |example.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/earth/example.svg .. |example.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/earth/example.png -.. |init.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/earth/init.svg .. |init.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/earth/init.png -.. |get_analog_value.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/earth/get_analog_value.svg .. |get_analog_value.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/earth/get_analog_value.png -.. |get_digital_value.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/earth/get_digital_value.svg .. |get_digital_value.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/earth/get_digital_value.png -.. |get_voltage_mv.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/earth/get_voltage_mv.svg .. |get_voltage_mv.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/earth/get_voltage_mv.png -.. |humidity.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/earth/humidity.svg .. |humidity.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/earth/humidity.png -.. |set_calibrate.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/earth/set_calibrate.svg .. |set_calibrate.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/earth/set_calibrate.png .. |earth_core_example.m5f2| raw:: html diff --git a/docs/en/refs/unit.encoder.ref b/docs/en/refs/unit.encoder.ref index 4e2f8a55..dfcf048e 100644 --- a/docs/en/refs/unit.encoder.ref +++ b/docs/en/refs/unit.encoder.ref @@ -4,25 +4,25 @@ :width: 200px -.. |example.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/encoder/example.svg +.. |example.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/encoder/example.png -.. |init.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/encoder/init.svg +.. |init.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/encoder/init.png -.. |get_rotary_status.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/encoder/get_rotary_status.svg +.. |get_rotary_status.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/encoder/get_rotary_status.png -.. |get_rotary_value.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/encoder/get_rotary_value.svg +.. |get_rotary_value.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/encoder/get_rotary_value.png -.. |get_button_status.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/encoder/get_button_status.svg +.. |get_button_status.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/encoder/get_button_status.png -.. |get_rotary_increments.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/encoder/get_rotary_increments.svg +.. |get_rotary_increments.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/encoder/get_rotary_increments.png -.. |reset_rotary_value.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/encoder/reset_rotary_value.svg +.. |reset_rotary_value.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/encoder/reset_rotary_value.png -.. |set_rotary_value.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/encoder/set_rotary_value.svg +.. |set_rotary_value.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/encoder/set_rotary_value.png -.. |set_color.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/encoder/set_color.svg +.. |set_color.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/encoder/set_color.png -.. |fill_color.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/encoder/fill_color.svg +.. |fill_color.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/encoder/fill_color.png .. |stickc_plus2_encoder_example.m5f2| raw:: html diff --git a/docs/en/refs/unit.env.ref b/docs/en/refs/unit.env.ref index fd51d2d3..1a14d87e 100644 --- a/docs/en/refs/unit.env.ref +++ b/docs/en/refs/unit.env.ref @@ -11,15 +11,15 @@ :height: 200px :width: 200px -.. |example.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/env/example.svg +.. |example.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/env/example.png -.. |init.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/env/init.svg +.. |init.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/env/init.png -.. |read_temperature.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/env/read_temperature.svg +.. |read_temperature.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/env/read_temperature.png -.. |read_humidity.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/env/read_humidity.svg +.. |read_humidity.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/env/read_humidity.png -.. |read_pressure.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/env/read_pressure.svg +.. |read_pressure.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/env/read_pressure.png .. |env_cores3_example.m5f2| raw:: html diff --git a/docs/en/refs/unit.finger.ref b/docs/en/refs/unit.finger.ref index edc8ce28..3ba0a8e7 100644 --- a/docs/en/refs/unit.finger.ref +++ b/docs/en/refs/unit.finger.ref @@ -3,58 +3,40 @@ :height: 200px :width: 200px -.. |init.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/finger/init.svg .. |init.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/finger/init.png -.. |sleep.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/finger/sleep.svg .. |sleep.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/finger/sleep.png -.. |get_add_mode.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/finger/get_add_mode.svg .. |get_add_mode.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/finger/get_add_mode.png -.. |set_add_mode.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/finger/set_add_mode.svg .. |set_add_mode.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/finger/set_add_mode.png -.. |add_user.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/finger/add_user.svg .. |add_user.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/finger/add_user.png -.. |delete_user.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/finger/delete_user.svg .. |delete_user.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/finger/delete_user.png -.. |delete_all_user.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/finger/delete_all_user.svg .. |delete_all_user.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/finger/delete_all_user.png -.. |get_user_count.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/finger/get_user_count.svg .. |get_user_count.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/finger/get_user_count.png -.. |get_user_capacity.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/finger/get_user_capacity.svg .. |get_user_capacity.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/finger/get_user_capacity.png -.. |compare_id.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/finger/compare_id.svg .. |compare_id.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/finger/compare_id.png -.. |compare_finger.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/finger/compare_finger.svg .. |compare_finger.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/finger/compare_finger.png -.. |get_user_list.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/finger/get_user_list.svg .. |get_user_list.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/finger/get_user_list.png -.. |get_user_info.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/finger/get_user_info.svg .. |get_user_info.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/finger/get_user_info.png -.. |get_user_permission.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/finger/get_user_permission.svg .. |get_user_permission.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/finger/get_user_permission.png -.. |get_user_characteristic.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/finger/get_user_characteristic.svg .. |get_user_characteristic.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/finger/get_user_characteristic.png -.. |add_user_info.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/finger/add_user_info.svg .. |add_user_info.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/finger/add_user_info.png -.. |get_match_level.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/finger/get_match_level.svg .. |get_match_level.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/finger/get_match_level.png -.. |set_match_level.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/finger/set_match_level.svg .. |set_match_level.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/finger/set_match_level.png .. |example.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/finger/example.png diff --git a/docs/en/refs/unit.gps_v11.ref b/docs/en/refs/unit.gps_v11.ref index 4e6e8dd7..55e8180a 100644 --- a/docs/en/refs/unit.gps_v11.ref +++ b/docs/en/refs/unit.gps_v11.ref @@ -1,5 +1,5 @@ -.. |GPSV11Unit| image:: https://m5stack.oss-cn-shenzhen.aliyuncs.com/resource/docs/products/unit/Unit-GPS%20v1.1/4.webp +.. |GPSV11Unit| image:: https://static-cdn.m5stack.com/resource/docs/products/unit/Unit-GPS%20v1.1/4.webp :target: https://docs.m5stack.com/en/unit/Unit-GPS%20v1.1 :height: 200px :width: 200px diff --git a/docs/en/refs/unit.ir.ref b/docs/en/refs/unit.ir.ref index ce3d5163..accc5502 100644 --- a/docs/en/refs/unit.ir.ref +++ b/docs/en/refs/unit.ir.ref @@ -3,16 +3,12 @@ :height: 200px :width: 200px -.. |example.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/ir/example.svg .. |example.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/ir/example.png -.. |init.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/ir/init.svg .. |init.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/ir/init.png -.. |rx_event.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/ir/rx_event.svg .. |rx_event.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/ir/rx_event.png -.. |tx.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/ir/tx.svg .. |tx.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/ir/tx.png .. |ir_core_example.m5f2| raw:: html diff --git a/docs/en/refs/unit.joystick.ref b/docs/en/refs/unit.joystick.ref index bedb9a37..ef33cb35 100644 --- a/docs/en/refs/unit.joystick.ref +++ b/docs/en/refs/unit.joystick.ref @@ -3,31 +3,22 @@ :height: 200px :width: 200px -.. |example.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/joystick/example.svg .. |example.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/joystick/example.png -.. |init.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/joystick/init.svg .. |init.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/joystick/init.png -.. |get_x_raw.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/joystick/get_x_raw.svg .. |get_x_raw.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/joystick/get_x_raw.png -.. |get_y_raw.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/joystick/get_y_raw.svg .. |get_y_raw.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/joystick/get_y_raw.png -.. |get_x.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/joystick/get_x.svg .. |get_x.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/joystick/get_x.png -.. |get_y.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/joystick/get_y.svg .. |get_y.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/joystick/get_y.png -.. |swap_x.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/joystick/swap_x.svg .. |swap_x.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/joystick/swap_x.png -.. |swap_y.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/joystick/swap_y.svg .. |swap_y.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/joystick/swap_y.png -.. |get_button_status.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/joystick/get_button_status.svg .. |get_button_status.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/joystick/get_button_status.png .. |joystick_stickcplus2_example.m5f2| raw:: html diff --git a/docs/en/refs/unit.joystick2.ref b/docs/en/refs/unit.joystick2.ref index d0909b91..cbc859d6 100644 --- a/docs/en/refs/unit.joystick2.ref +++ b/docs/en/refs/unit.joystick2.ref @@ -1,5 +1,5 @@ -.. |Joystick2Unit| image:: https://m5stack.oss-cn-shenzhen.aliyuncs.com/resource/docs/products/unit/Unit-JoyStick2/4.webp +.. |Joystick2Unit| image:: https://static-cdn.m5stack.com/resource/docs/products/unit/Unit-JoyStick2/4.webp :target: https://docs.m5stack.com/en/unit/Unit-JoyStick2 :height: 200px :width: 200px diff --git a/docs/en/refs/unit.light.ref b/docs/en/refs/unit.light.ref index c977214d..07e56aaa 100644 --- a/docs/en/refs/unit.light.ref +++ b/docs/en/refs/unit.light.ref @@ -3,19 +3,14 @@ :height: 200px :width: 200px -.. |example.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/light/example.svg .. |example.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/light/example.png -.. |init.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/light/init.svg .. |init.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/light/init.png -.. |get_digital_value.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/light/get_digital_value.svg .. |get_digital_value.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/light/get_digital_value.png -.. |get_analog_value.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/light/get_analog_value.svg .. |get_analog_value.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/light/get_analog_value.png -.. |get_ohm.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/light/get_ohm.svg .. |get_ohm.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/light/get_ohm.png .. |light_core_example.m5f2| raw:: html diff --git a/docs/en/refs/unit.lora_e220.ref b/docs/en/refs/unit.lora_e220.ref index 418982d9..a7df8511 100644 --- a/docs/en/refs/unit.lora_e220.ref +++ b/docs/en/refs/unit.lora_e220.ref @@ -3,33 +3,33 @@ :height: 200px :width: 200px -.. |tx_example.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/lorae220/tx_example.svg +.. |tx_example.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/lorae220/tx_example.png -.. |rx_example.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/lorae220/rx_example.svg +.. |rx_example.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/lorae220/rx_example.png -.. |lora_e220_core_example.m5f2| image:: https://static-cdn.m5stack.com/mpy_docs/unit/env/init.svg +.. |lora_e220_core_example.m5f2| image:: https://static-cdn.m5stack.com/mpy_docs/unit/env/init.png .. |working mode.jpg| image:: https://static-cdn.m5stack.com/resource/docs/products/unit/LoRaE220-JP%20Unit/3.jpg -.. |init.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/lorae220/init.svg +.. |init.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/lorae220/init.png -.. |setup.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/lorae220/setup.svg +.. |setup.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/lorae220/setup.png -.. |receiveNoneBlock.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/lorae220/receiveNoneBlock.svg +.. |receiveNoneBlock.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/lorae220/receiveNoneBlock.png -.. |receive_callback1.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/lorae220/receive_data_event.svg +.. |receive_callback1.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/lorae220/receive_data_event.png -.. |receive_callback2.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/lorae220/receive_data_str_event.svg +.. |receive_callback2.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/lorae220/receive_data_str_event.png -.. |stopReceive.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/lorae220/stopReceive.svg +.. |stopReceive.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/lorae220/stopReceive.png -.. |receive.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/lorae220/receive.svg +.. |receive.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/lorae220/receive.png -.. |send1.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/lorae220/send.svg +.. |send1.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/lorae220/send.png -.. |send2.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/lorae220/send_line.svg +.. |send2.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/lorae220/send_line.png -.. |send3.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/lorae220/send_raw_data.svg +.. |send3.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/lorae220/send_raw_data.png .. |lora_e220_tx_core2.m5f2| raw:: html diff --git a/docs/en/refs/unit.lora_e220_433.ref b/docs/en/refs/unit.lora_e220_433.ref index 224da357..578cf596 100644 --- a/docs/en/refs/unit.lora_e220_433.ref +++ b/docs/en/refs/unit.lora_e220_433.ref @@ -1,4 +1,4 @@ -.. |LoRaE220-433| image:: https://m5stack.oss-cn-shenzhen.aliyuncs.com/resource/docs/products/unit/UNIT_LoraE220(433MHz)/4.webp +.. |LoRaE220-433| image:: https://static-cdn.m5stack.com/resource/docs/products/unit/UNIT_LoraE220(433MHz)/4.webp :target: https://docs.m5stack.com/en/unit/Unit%20LoRaE220-433 :height: 200px :width: 200px @@ -9,7 +9,7 @@ .. |lora_e220_core_example.m5f2| image:: https://static-cdn.m5stack.com/mpy_docs/unit/env/init.png -.. |working mode.jpg| image:: https://m5stack.oss-cn-shenzhen.aliyuncs.com/resource/docs/products/unit/UNIT_LoraE220(433MHz)/5.jpg +.. |working mode.jpg| image:: https://static-cdn.m5stack.com/resource/docs/products/unit/UNIT_LoraE220(433MHz)/5.jpg .. |init.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/lorae220433/init.png diff --git a/docs/en/refs/unit.lorawan_rui3.ref b/docs/en/refs/unit.lorawan_rui3.ref index 5f1f3ee6..ae09d36e 100644 --- a/docs/en/refs/unit.lorawan_rui3.ref +++ b/docs/en/refs/unit.lorawan_rui3.ref @@ -1,19 +1,19 @@ -.. |LoRaWAN-CN470| image:: https://m5stack.oss-cn-shenzhen.aliyuncs.com/resource/docs/products/unit/Unit%20LoRaWAN-CN470/4.webp +.. |LoRaWAN-CN470| image:: https://static-cdn.m5stack.com/resource/docs/products/unit/Unit%20LoRaWAN-CN470/4.webp :target: https://docs.m5stack.com/en/unit/Unit%20LoRaWAN-CN470 :height: 200px :width: 200px -.. |LoRaWAN-AS923| image:: https://m5stack.oss-cn-shenzhen.aliyuncs.com/resource/docs/products/unit/Unit%20LoRaWAN-AS923/4.webp +.. |LoRaWAN-AS923| image:: https://static-cdn.m5stack.com/resource/docs/products/unit/Unit%20LoRaWAN-AS923/4.webp :target: https://docs.m5stack.com/en/unit/Unit%20LoRaWAN-AS923 :height: 200px :width: 200px -.. |LoRaWAN-EU868| image:: https://m5stack.oss-cn-shenzhen.aliyuncs.com/resource/docs/products/unit/Unit%20LoRaWAN-EU868/4.webp +.. |LoRaWAN-EU868| image:: https://static-cdn.m5stack.com/resource/docs/products/unit/Unit%20LoRaWAN-EU868/4.webp :target: https://docs.m5stack.com/en/unit/Unit%20LoRaWAN-EU868 :height: 200px :width: 200px -.. |LoRaWAN-US915| image:: https://m5stack.oss-cn-shenzhen.aliyuncs.com/resource/docs/products/unit/Unit%20LoRaWAN-US915/4.webp +.. |LoRaWAN-US915| image:: https://static-cdn.m5stack.com/resource/docs/products/unit/Unit%20LoRaWAN-US915/4.webp :target: https://docs.m5stack.com/en/unit/Unit%20LoRaWAN-US915 :height: 200px :width: 200px diff --git a/docs/en/refs/unit.midi.ref b/docs/en/refs/unit.midi.ref index d6866d03..23a729c0 100644 --- a/docs/en/refs/unit.midi.ref +++ b/docs/en/refs/unit.midi.ref @@ -1,4 +1,4 @@ -.. |MIDIUnit| image:: https://m5stack.oss-cn-shenzhen.aliyuncs.com/resource/docs/products/unit/Unit-MIDI/4.webp +.. |MIDIUnit| image:: https://static-cdn.m5stack.com/resource/docs/products/unit/Unit-MIDI/4.webp :target: https://docs.m5stack.com/en/unit/Unit-midi :height: 200px :width: 200 px diff --git a/docs/en/refs/unit.miniscale.ref b/docs/en/refs/unit.miniscale.ref index 1cc47386..af8bb741 100644 --- a/docs/en/refs/unit.miniscale.ref +++ b/docs/en/refs/unit.miniscale.ref @@ -3,32 +3,32 @@ :height: 200px :width: 200px -.. |init.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/miniscales/init.svg +.. |init.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/miniscales/init.png -.. |calibration.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/miniscales/calibration.svg +.. |calibration.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/miniscales/calibration.png -.. |tare.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/miniscales/tare.svg +.. |tare.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/miniscales/tare.png -.. |getAverageFilterLevel.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/miniscales/getAverageFilterLevel.svg +.. |getAverageFilterLevel.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/miniscales/getAverageFilterLevel.png -.. |getEMAFilterAlpha.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/miniscales/getEMAFilterAlpha.svg +.. |getEMAFilterAlpha.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/miniscales/getEMAFilterAlpha.png -.. |getLowPassFilter.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/miniscales/getLowPassFilter.svg +.. |getLowPassFilter.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/miniscales/getLowPassFilter.png -.. |get_adc.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/miniscales/get_adc.svg +.. |get_adc.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/miniscales/get_adc.png -.. |get_button.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/miniscales/get_button.svg +.. |get_button.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/miniscales/get_button.png -.. |get_weight.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/miniscales/get_weight.svg +.. |get_weight.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/miniscales/get_weight.png -.. |reset.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/miniscales/reset.svg +.. |reset.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/miniscales/reset.png -.. |setAverageFilterLevel.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/miniscales/setAverageFilterLevel.svg +.. |setAverageFilterLevel.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/miniscales/setAverageFilterLevel.png -.. |setEMAFilterAlpha.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/miniscales/setEMAFilterAlpha.svg +.. |setEMAFilterAlpha.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/miniscales/setEMAFilterAlpha.png -.. |setLed.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/miniscales/setLed.svg +.. |setLed.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/miniscales/setLed.png -.. |setLowPassFilter.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/miniscales/setLowPassFilter.svg +.. |setLowPassFilter.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/miniscales/setLowPassFilter.png -.. |example.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/miniscales/example.svg +.. |example.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/miniscales/example.png diff --git a/docs/en/refs/unit.nbiot2.ref b/docs/en/refs/unit.nbiot2.ref index 3cfb0bfc..6bda1477 100644 --- a/docs/en/refs/unit.nbiot2.ref +++ b/docs/en/refs/unit.nbiot2.ref @@ -1,4 +1,4 @@ -.. |NB-IOT2Unit| image:: https://m5stack.oss-cn-shenzhen.aliyuncs.com/resource/docs/products/unit/Unit%20NB-IoT2%20(SIM7028)/4.webp +.. |NB-IOT2Unit| image:: https://static-cdn.m5stack.com/resource/docs/products/unit/Unit%20NB-IoT2%20(SIM7028)/4.webp :target: https://docs.m5stack.com/en/unit/Unit%20NB-IoT2(SIM7028) :height: 200px :width: 200 px diff --git a/docs/en/refs/unit.ncir.ref b/docs/en/refs/unit.ncir.ref index af41f133..8dc10fb4 100644 --- a/docs/en/refs/unit.ncir.ref +++ b/docs/en/refs/unit.ncir.ref @@ -3,16 +3,12 @@ :height: 200px :width: 200px -.. |example.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/ncir/example.svg .. |example.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/ncir/example.png -.. |init.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/ncir/init.svg .. |init.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/ncir/init.png -.. |get_ambient_temperature.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/ncir/get_ambient_temperature.svg .. |get_ambient_temperature.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/ncir/get_ambient_temperature.png -.. |get_object_temperature.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/ncir/get_object_temperature.svg .. |get_object_temperature.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/ncir/get_object_temperature.png .. |ncir_core_example.m5f2| raw:: html diff --git a/docs/en/refs/unit.op180.ref b/docs/en/refs/unit.op180.ref index 603246df..a2978e43 100644 --- a/docs/en/refs/unit.op180.ref +++ b/docs/en/refs/unit.op180.ref @@ -3,13 +3,13 @@ :height: 200px :width: 200px -.. |example.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/op180/example.svg +.. |example.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/op180/example.png -.. |init.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/op180/init.svg +.. |init.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/op180/init.png -.. |get_count_value.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/op180/get_counter_value.svg +.. |get_count_value.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/op180/get_counter_value.png -.. |reset_count_value.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/op180/reset_counter_value.svg +.. |reset_count_value.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/op180/reset_counter_value.png .. |op180_core_example.m5f2| raw:: html diff --git a/docs/en/refs/unit.op90.ref b/docs/en/refs/unit.op90.ref index 2fdc523c..7736ed97 100644 --- a/docs/en/refs/unit.op90.ref +++ b/docs/en/refs/unit.op90.ref @@ -3,13 +3,13 @@ :height: 200px :width: 200px -.. |example.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/op90/example.svg +.. |example.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/op90/example.png -.. |init.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/op90/init.svg +.. |init.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/op90/init.png -.. |get_count_value.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/op90/get_counter_value.svg +.. |get_count_value.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/op90/get_counter_value.png -.. |reset_count_value.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/op90/reset_counter_value.svg +.. |reset_count_value.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/op90/reset_counter_value.png .. |op90_core_example.m5f2| raw:: html diff --git a/docs/en/refs/unit.pir.ref b/docs/en/refs/unit.pir.ref index a844b355..6f545140 100644 --- a/docs/en/refs/unit.pir.ref +++ b/docs/en/refs/unit.pir.ref @@ -4,22 +4,16 @@ :width: 200px -.. |example.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/pir/example.svg .. |example.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/pir/example.png -.. |init.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/pir/init.svg .. |init.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/pir/init.png -.. |get_status.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/pir/get_status.svg .. |get_status.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/pir/get_status.png -.. |enable_irq.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/pir/enable_irq.svg .. |enable_irq.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/pir/enable_irq.png -.. |disable_irq.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/pir/disable_irq.svg .. |disable_irq.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/pir/disable_irq.png -.. |set_callback.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/pir/set_callback.svg .. |set_callback.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/pir/set_callback.png .. |pir_core_example.m5f2| raw:: html diff --git a/docs/en/refs/unit.puzzle.ref b/docs/en/refs/unit.puzzle.ref index 19a72e0b..d1221e41 100644 --- a/docs/en/refs/unit.puzzle.ref +++ b/docs/en/refs/unit.puzzle.ref @@ -1,5 +1,5 @@ -.. |PuzzleUnit| image:: https://m5stack.oss-cn-shenzhen.aliyuncs.com/resource/docs/products/unit/Unit-Puzzle/4.webp +.. |PuzzleUnit| image:: https://static-cdn.m5stack.com/resource/docs/products/unit/Unit-Puzzle/4.webp :target: https://docs.m5stack.com/en/unit/Unit-Puzzle :height: 200px :width: 200px diff --git a/docs/en/refs/unit.relay.ref b/docs/en/refs/unit.relay.ref index 41f84ad2..ae5606d8 100644 --- a/docs/en/refs/unit.relay.ref +++ b/docs/en/refs/unit.relay.ref @@ -3,22 +3,16 @@ :height: 200px :width: 200px -.. |example.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/relay/example.svg .. |example.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/relay/example.png -.. |init.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/relay/init.svg .. |init.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/relay/init.png -.. |get_status.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/relay/get_status.svg .. |get_status.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/relay/get_status.png -.. |on.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/relay/on.svg .. |on.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/relay/on.png -.. |off.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/relay/off.svg .. |off.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/relay/off.png -.. |set_status.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/relay/set_status.svg .. |set_status.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/relay/set_status.png .. |relay_core_example.m5f2| raw:: html diff --git a/docs/en/refs/unit.rgb.ref b/docs/en/refs/unit.rgb.ref index 51c8faa5..bc813db9 100644 --- a/docs/en/refs/unit.rgb.ref +++ b/docs/en/refs/unit.rgb.ref @@ -3,15 +3,15 @@ :height: 200px :width: 200px -.. |example.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/rgb/example.svg +.. |example.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/rgb/example.png -.. |init.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/rgb/init.svg +.. |init.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/rgb/init.png -.. |set_brightness.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/rgb/set_brightness.svg +.. |set_brightness.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/rgb/set_brightness.png -.. |fill_color.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/rgb/fill_color.svg +.. |fill_color.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/rgb/fill_color.png -.. |set_color.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/rgb/set_color.svg +.. |set_color.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/rgb/set_color.png .. |rgb_core_example.m5f2| raw:: html diff --git a/docs/en/refs/unit.roller485.ref b/docs/en/refs/unit.roller485.ref index 26b977a2..b25ba168 100644 --- a/docs/en/refs/unit.roller485.ref +++ b/docs/en/refs/unit.roller485.ref @@ -1,5 +1,5 @@ -.. |Roller485| image:: https://m5stack.oss-cn-shenzhen.aliyuncs.com/resource/docs/products/unit/Unit-Roller485/4.webp +.. |Roller485| image:: https://static-cdn.m5stack.com/resource/docs/products/unit/Unit-Roller485/4.webp :target: https://docs.m5stack.com/en/unit/Unit-Roller485 :height: 200px :width: 200px diff --git a/docs/en/refs/unit.rollercan.ref b/docs/en/refs/unit.rollercan.ref index e1106b21..8f5cbf04 100644 --- a/docs/en/refs/unit.rollercan.ref +++ b/docs/en/refs/unit.rollercan.ref @@ -1,5 +1,5 @@ -.. |RollerCAN| image:: https://m5stack.oss-cn-shenzhen.aliyuncs.com/resource/docs/products/unit/Unit-RollerCAN/4.webp +.. |RollerCAN| image:: https://static-cdn.m5stack.com/resource/docs/products/unit/Unit-RollerCAN/4.webp :target: https://docs.m5stack.com/en/unit/Unit-RollerCAN :height: 200px :width: 200px diff --git a/docs/en/refs/unit.scroll.ref b/docs/en/refs/unit.scroll.ref index 7f77d7f4..bfd73237 100644 --- a/docs/en/refs/unit.scroll.ref +++ b/docs/en/refs/unit.scroll.ref @@ -1,4 +1,4 @@ -.. |Scroll| image:: https://m5stack.oss-cn-shenzhen.aliyuncs.com/resource/docs/products/unit/UNIT-Scroll/4.webp +.. |Scroll| image:: https://static-cdn.m5stack.com/resource/docs/products/unit/UNIT-Scroll/4.webp :target: https://docs.m5stack.com/zh_CN/unit/UNIT-Scroll :height: 200px :width: 200px diff --git a/docs/en/refs/unit.thermal.ref b/docs/en/refs/unit.thermal.ref index b6a92390..59efe605 100644 --- a/docs/en/refs/unit.thermal.ref +++ b/docs/en/refs/unit.thermal.ref @@ -4,20 +4,20 @@ :width: 200px -.. |init.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/thermal/init.svg +.. |init.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/thermal/init.png -.. |get_max_temperature.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/thermal/get_max_temperature.svg +.. |get_max_temperature.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/thermal/get_max_temperature.png -.. |get_midpoint_temperature.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/thermal/get_midpoint_temperature.svg +.. |get_midpoint_temperature.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/thermal/get_midpoint_temperature.png -.. |get_min_temperature.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/thermal/get_min_temperature.svg +.. |get_min_temperature.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/thermal/get_min_temperature.png -.. |get_pixel_temperature.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/thermal/get_pixel_temperature.svg +.. |get_pixel_temperature.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/thermal/get_pixel_temperature.png -.. |get_refresh_rate.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/thermal/get_refresh_rate.svg +.. |get_refresh_rate.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/thermal/get_refresh_rate.png -.. |get_temperature_buffer.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/thermal/get_temperature_buffer.svg +.. |get_temperature_buffer.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/thermal/get_temperature_buffer.png -.. |set_refresh_rate.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/thermal/set_refresh_rate.svg +.. |set_refresh_rate.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/thermal/set_refresh_rate.png -.. |update_temperature_buffer.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/thermal/update_temperature_buffer.svg +.. |update_temperature_buffer.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/thermal/update_temperature_buffer.png diff --git a/docs/en/refs/unit.timerpwr.ref b/docs/en/refs/unit.timerpwr.ref index d697e737..a3a36b76 100644 --- a/docs/en/refs/unit.timerpwr.ref +++ b/docs/en/refs/unit.timerpwr.ref @@ -1,5 +1,5 @@ -.. |TimerPWRUnit| image:: https://m5stack.oss-cn-shenzhen.aliyuncs.com/resource/docs/products/unit/Unit-TimerPWR/4.webp +.. |TimerPWRUnit| image:: https://static-cdn.m5stack.com/resource/docs/products/unit/Unit-TimerPWR/4.webp :target: https://docs.m5stack.com/en/unit/Unit-TimerPWR :height: 200px :width: 200px diff --git a/docs/en/refs/unit.tmos.ref b/docs/en/refs/unit.tmos.ref index 58e1cb35..93e53476 100644 --- a/docs/en/refs/unit.tmos.ref +++ b/docs/en/refs/unit.tmos.ref @@ -1,4 +1,4 @@ -.. |TMOS| image:: https://m5stack.oss-cn-shenzhen.aliyuncs.com/resource/docs/products/unit/UNIT-TMOS%20PIR/4.webp +.. |TMOS| image:: https://static-cdn.m5stack.com/resource/docs/products/unit/UNIT-TMOS%20PIR/4.webp :target: https://docs.m5stack.com/en/unit/UNIT-TMOS%20PIR :height: 200px :width: 200px diff --git a/docs/en/refs/unit.tof.ref b/docs/en/refs/unit.tof.ref index 6845a399..48fa2adb 100644 --- a/docs/en/refs/unit.tof.ref +++ b/docs/en/refs/unit.tof.ref @@ -3,43 +3,30 @@ :height: 200px :width: 200px -.. |example.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/tof/example.svg .. |example.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/tof/example.png -.. |init.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/tof/init.svg .. |init.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/tof/init.png -.. |get_distance.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/tof/get_distance.svg .. |get_distance.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/tof/get_distance.png -.. |get_data_ready.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/tof/get_data_ready.svg .. |get_data_ready.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/tof/get_data_ready.png -.. |get_range.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/tof/get_range.svg .. |get_range.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/tof/get_range.png -.. |is_continuous_mode.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/tof/is_continuous_mode.svg .. |is_continuous_mode.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/tof/is_continuous_mode.png -.. |get_measurement_timing_budget.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/tof/get_measurement_timing_budget.svg .. |get_measurement_timing_budget.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/tof/get_measurement_timing_budget.png -.. |get_signal_rate_limit.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/tof/get_signal_rate_limit.svg .. |get_signal_rate_limit.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/tof/get_signal_rate_limit.png -.. |set_measurement_timing_budget.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/tof/set_measurement_timing_budget.svg .. |set_measurement_timing_budget.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/tof/set_measurement_timing_budget.png -.. |set_address.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/tof/set_address.svg .. |set_address.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/tof/set_address.png -.. |set_signal_rate_limit.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/tof/set_signal_rate_limit.svg .. |set_signal_rate_limit.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/tof/set_signal_rate_limit.png -.. |start_continuous.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/tof/start_continuous.svg .. |start_continuous.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/tof/start_continuous.png -.. |stop_continuous.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/tof/stop_continuous.svg .. |stop_continuous.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/tof/stop_continuous.png .. |tof_core_example.m5f2| raw:: html diff --git a/docs/en/refs/unit.ultrasonic.ref b/docs/en/refs/unit.ultrasonic.ref index c3717ec4..ada34e8f 100644 --- a/docs/en/refs/unit.ultrasonic.ref +++ b/docs/en/refs/unit.ultrasonic.ref @@ -4,11 +4,11 @@ :width: 200px -.. |example.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/ultrasonic/example.svg +.. |example.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/ultrasonic/example.png -.. |init.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/ultrasonic/init.svg +.. |init.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/ultrasonic/init.png -.. |get_target_distance.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/ultrasonic/get_target_distance.svg +.. |get_target_distance.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/ultrasonic/get_target_distance.png .. |ultrasonic_core_example.m5f2| raw:: html diff --git a/docs/en/refs/unit.vibrator.ref b/docs/en/refs/unit.vibrator.ref index 6dbdc0a0..ebc97f32 100644 --- a/docs/en/refs/unit.vibrator.ref +++ b/docs/en/refs/unit.vibrator.ref @@ -3,25 +3,18 @@ :height: 200px :width: 200 px -.. |example.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/vibrator/example.svg .. |example.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/vibrator/example.png -.. |init.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/vibrator/init.svg .. |init.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/vibrator/init.png -.. |once.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/vibrator/once.svg .. |once.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/vibrator/once.png -.. |set_freq.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/vibrator/set_freq.svg .. |set_freq.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/vibrator/set_freq.png -.. |set_duty.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/vibrator/set_duty.svg .. |set_duty.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/vibrator/set_duty.png -.. |turn_off.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/vibrator/turn_off.svg .. |turn_off.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/vibrator/turn_off.png -.. |deinit.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/vibrator/deinit.svg .. |deinit.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/vibrator/deinit.png .. |core_vibrator_example.m5f2| raw:: html diff --git a/docs/en/refs/unit.weight_i2c.ref b/docs/en/refs/unit.weight_i2c.ref index 4c5cc6cc..31910b40 100644 --- a/docs/en/refs/unit.weight_i2c.ref +++ b/docs/en/refs/unit.weight_i2c.ref @@ -3,37 +3,37 @@ :height: 200px :width: 200px -.. |init_i2c_address.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/weight_i2c/init.svg +.. |init_i2c_address.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/weight_i2c/init.png -.. |get_adc_raw.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/weight_i2c/get_adc_raw.svg +.. |get_adc_raw.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/weight_i2c/get_adc_raw.png -.. |get_weight_float.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/weight_i2c/get_weight_float.svg +.. |get_weight_float.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/weight_i2c/get_weight_float.png -.. |get_weight_int.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/weight_i2c/get_weight_int.svg +.. |get_weight_int.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/weight_i2c/get_weight_int.png -.. |get_weight_str.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/weight_i2c/get_weight_str.svg +.. |get_weight_str.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/weight_i2c/get_weight_str.png -.. |set_reset_offset.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/weight_i2c/set_reset_offset.svg +.. |set_reset_offset.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/weight_i2c/set_reset_offset.png -.. |set_calibration.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/weight_i2c/set_calibration.svg +.. |set_calibration.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/weight_i2c/set_calibration.png -.. |set_lowpass_filter.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/weight_i2c/set_lowpass_filter.svg +.. |set_lowpass_filter.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/weight_i2c/set_lowpass_filter.png -.. |get_lowpass_filter.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/weight_i2c/get_lowpass_filter.svg +.. |get_lowpass_filter.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/weight_i2c/get_lowpass_filter.png -.. |set_average_filter_level.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/weight_i2c/set_average_filter_level.svg +.. |set_average_filter_level.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/weight_i2c/set_average_filter_level.png -.. |get_average_filter_level.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/weight_i2c/get_average_filter_level.svg +.. |get_average_filter_level.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/weight_i2c/get_average_filter_level.png -.. |set_ema_filter_alpha.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/weight_i2c/set_ema_filter_alpha.svg +.. |set_ema_filter_alpha.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/weight_i2c/set_ema_filter_alpha.png -.. |get_ema_filter_alpha.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/weight_i2c/get_ema_filter_alpha.svg +.. |get_ema_filter_alpha.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/weight_i2c/get_ema_filter_alpha.png -.. |set_i2c_address.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/weight_i2c/set_i2c_address.svg +.. |set_i2c_address.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/weight_i2c/set_i2c_address.png -.. |get_device_spec.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/weight_i2c/get_device_spec.svg +.. |get_device_spec.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/weight_i2c/get_device_spec.png -.. |example.svg| image:: https://static-cdn.m5stack.com/mpy_docs/unit/weight_i2c/example.svg +.. |example.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/weight_i2c/example.png .. |weight-i2c-demo.m5f2| raw:: html diff --git a/docs/en/software/umqtt.default.rst b/docs/en/software/umqtt.default.rst index b26c4ce6..63dda1d8 100644 --- a/docs/en/software/umqtt.default.rst +++ b/docs/en/software/umqtt.default.rst @@ -17,7 +17,7 @@ Micropython Example: UIFLOW2 Example: - |example.svg| + |example.png| .. only:: builder_html diff --git a/docs/en/system/power.rst b/docs/en/system/power.rst index d274f4b1..2894e8cd 100644 --- a/docs/en/system/power.rst +++ b/docs/en/system/power.rst @@ -9,9 +9,9 @@ class Power .. important:: - Methods of the Power Class depend on ``M5.begin()`` |M5.begin.svg|. + Methods of the Power Class depend on ``M5.begin()`` |M5.begin.png|. - All methods calling the Power object need to be placed after ``M5.begin()`` |M5.begin.svg|. + All methods calling the Power object need to be placed after ``M5.begin()`` |M5.begin.png|. Methods @@ -30,8 +30,8 @@ Methods UIFLOW2: - |setExtOutput1.svg| - |setExtOutput2.svg| + |setExtOutput1.png| + |setExtOutput2.png| .. method:: Power.getExtOutput() -> bool @@ -44,7 +44,7 @@ Methods UIFLOW2: - |getExtOutput.svg| + |getExtOutput.png| .. method:: Power.setUsbOutput(enable: bool) -> None @@ -57,7 +57,7 @@ Methods UIFLOW2: - |setUsbOutput.svg| + |setUsbOutput.png| .. method:: Power.getUsbOutput() -> bool @@ -69,7 +69,7 @@ Methods UIFLOW2: - |getUsbOutput.svg| + |getUsbOutput.png| .. method:: Power.setLed(brightness=255) -> None @@ -81,7 +81,7 @@ Methods UIFLOW2: - |setLed.svg| + |setLed.png| .. method:: Power.powerOff() @@ -90,7 +90,7 @@ Methods UIFLOW2: - |powerOff.svg| + |powerOff.png| .. method:: Power.timerSleep(seconds) -> None @@ -111,9 +111,9 @@ Methods UIFLOW2: - |timerSleep1.svg| - |timerSleep2.svg| - |timerSleep3.svg| + |timerSleep1.png| + |timerSleep2.png| + |timerSleep3.png| .. method:: Power.deepSleep(micro_seconds: int=0, wakeup: bool=True) @@ -126,7 +126,7 @@ Methods UIFLOW2: - |deepSleep.svg| + |deepSleep.png| .. method:: Power.lightSleep(micro_seconds: int=0, wakeup: bool=True) @@ -139,7 +139,7 @@ Methods UIFLOW2: - |lightSleep.svg| + |lightSleep.png| .. method:: Power.getBatteryLevel() -> int @@ -148,7 +148,7 @@ Methods UIFLOW2: - |getBatteryLevel.svg| + |getBatteryLevel.png| .. method:: Power.setBatteryCharge(enable: bool) -> None @@ -157,7 +157,7 @@ Methods UIFLOW2: - |setBatteryCharge.svg| + |setBatteryCharge.png| .. method:: Power.setChargeCurrent(max_mA: int) -> None @@ -168,7 +168,7 @@ Methods UIFLOW2: - |setChargeCurrent.svg| + |setChargeCurrent.png| .. method:: Power.setChargeVoltage(max_mV: int) -> None @@ -179,7 +179,7 @@ Methods UIFLOW2: - |setChargeVoltage.svg| + |setChargeVoltage.png| .. method:: Power.isCharging() -> bool @@ -188,7 +188,7 @@ Methods UIFLOW2: - |isCharging.svg| + |isCharging.png| .. method:: Power.getBatteryVoltage() -> int @@ -197,7 +197,7 @@ Methods UIFLOW2: - |getBatteryVoltage.svg| + |getBatteryVoltage.png| .. method:: Power.getBatteryCurrent() -> int @@ -206,7 +206,7 @@ Methods UIFLOW2: - |getBatteryCurrent.svg| + |getBatteryCurrent.png| .. method:: Power.getKeyState() -> int @@ -215,7 +215,7 @@ Methods UIFLOW2: - |getKeyState.svg| + |getKeyState.png| .. method:: Power.setVibration(level: int) -> None @@ -225,7 +225,7 @@ Methods UIFLOW2: - |setVibration.svg| + |setVibration.png| .. _class PORT: diff --git a/docs/en/units/angle.rst b/docs/en/units/angle.rst index bd98e5bc..ec3f0ca7 100644 --- a/docs/en/units/angle.rst +++ b/docs/en/units/angle.rst @@ -24,7 +24,7 @@ Micropython Example:: UIFLOW2 Example: - |example.svg| + |example.png| .. only:: builder_html @@ -47,7 +47,7 @@ Constructors UIFLOW2: - |init.svg| + |init.png| Methods @@ -59,7 +59,7 @@ Methods UIFLOW2: - |get_value.svg| + |get_value.png| .. method:: Angle.get_voltage() @@ -68,4 +68,4 @@ Methods UIFLOW2: - |get_voltage.svg| + |get_voltage.png| diff --git a/docs/en/units/buzzer.rst b/docs/en/units/buzzer.rst index fa5a4ed4..41380b0d 100644 --- a/docs/en/units/buzzer.rst +++ b/docs/en/units/buzzer.rst @@ -40,7 +40,7 @@ Constructors UIFLOW2: - |init.svg| + |init.png| Methods @@ -56,7 +56,7 @@ Methods UIFLOW2: - |once.svg| + |once.png| .. method:: BuzzerUnit.set_freq(freq: int) @@ -67,7 +67,7 @@ Methods UIFLOW2: - |set_freq.svg| + |set_freq.png| .. method:: BuzzerUnit.set_duty(duty: int) @@ -78,7 +78,7 @@ Methods UIFLOW2: - |set_duty.svg| + |set_duty.png| .. method:: BuzzerUnit.turn_off() @@ -87,7 +87,7 @@ Methods UIFLOW2: - |turn_off.svg| + |turn_off.png| .. method:: BuzzerUnit.deint() @@ -96,4 +96,4 @@ Methods UIFLOW2: - |deinit.svg| + |deinit.png| diff --git a/docs/en/units/dac.rst b/docs/en/units/dac.rst index b788d5cb..1f4c08b8 100644 --- a/docs/en/units/dac.rst +++ b/docs/en/units/dac.rst @@ -44,7 +44,7 @@ Constructors UIFLOW2: - |init.svg| + |init.png| .. _unit.DACUnit.Methods: @@ -60,7 +60,7 @@ Methods UIFLOW2: - |get_value.svg| + |get_value.png| .. method:: DACUnit.get_voltage() -> float @@ -71,7 +71,7 @@ Methods UIFLOW2: - |get_voltage.svg| + |get_voltage.png| .. method:: DACUnit.set_value(value: int) -> None @@ -82,7 +82,7 @@ Methods UIFLOW2: - |set_value.svg| + |set_value.png| .. method:: DACUnit.set_voltage(voltage: float) -> None @@ -93,7 +93,7 @@ Methods UIFLOW2: - |set_voltage.svg| + |set_voltage.png| .. method:: DACUnit.get_raw_value() -> int @@ -104,7 +104,7 @@ Methods UIFLOW2: - |get_raw_value.svg| + |get_raw_value.png| .. method:: DACUnit.set_raw_value(value: int) -> None @@ -115,7 +115,7 @@ Methods UIFLOW2: - |set_raw_value.svg| + |set_raw_value.png| .. method:: DACUnit.get_normalized_value() -> float @@ -126,7 +126,7 @@ Methods UIFLOW2: - |get_normalized_value.svg| + |get_normalized_value.png| .. method:: DACUnit.set_normalized_value(value: float) -> None @@ -137,7 +137,7 @@ Methods UIFLOW2: - |set_normalized_value.svg| + |set_normalized_value.png| .. method:: DACUnit.save_to_eeprom() -> None @@ -145,4 +145,4 @@ Methods UIFLOW2: - |save_to_eeprom.svg| + |save_to_eeprom.png| diff --git a/docs/en/units/dlight.rst b/docs/en/units/dlight.rst index e1fe609a..014c5165 100644 --- a/docs/en/units/dlight.rst +++ b/docs/en/units/dlight.rst @@ -40,7 +40,7 @@ Constructors UIFLOW2: - |init.svg| + |init.png| .. _unit.DLightUnit.Methods: @@ -54,7 +54,7 @@ Methods UIFLOW2: - |get_lux.svg| + |get_lux.png| .. method:: DLightUnit.configure() @@ -63,4 +63,4 @@ Methods UIFLOW2: - |configure.svg| + |configure.png| diff --git a/docs/en/units/earth.rst b/docs/en/units/earth.rst index 3196e77d..fbe75ac8 100644 --- a/docs/en/units/earth.rst +++ b/docs/en/units/earth.rst @@ -40,7 +40,7 @@ Constructors UIFLOW2: - |init.svg| + |init.png| Methods @@ -52,7 +52,7 @@ Methods UIFLOW2: - |get_analog_value.svg| + |get_analog_value.png| .. method:: EARTH.get_digital_value() @@ -61,7 +61,7 @@ Methods UIFLOW2: - |get_digital_value.svg| + |get_digital_value.png| .. method:: EARTH.get_voltage_mv() @@ -70,7 +70,7 @@ Methods UIFLOW2: - |get_voltage_mv.svg| + |get_voltage_mv.png| .. method:: EARTH.humidity() @@ -79,7 +79,7 @@ Methods UIFLOW2: - |humidity.svg| + |humidity.png| .. method:: EARTH.set_calibrate() @@ -88,4 +88,4 @@ Methods UIFLOW2: - |set_calibrate.svg| + |set_calibrate.png| diff --git a/docs/en/units/encoder.rst b/docs/en/units/encoder.rst index 8595e1cc..e582154a 100644 --- a/docs/en/units/encoder.rst +++ b/docs/en/units/encoder.rst @@ -17,7 +17,7 @@ Micropython Example: UIFLOW2 Example: - |example.svg| + |example.png| .. only:: builder_html @@ -40,7 +40,7 @@ Constructors UIFLOW2: - |init.svg| + |init.png| Methods @@ -52,7 +52,7 @@ Methods UIFLOW2: - |get_rotary_status.svg| + |get_rotary_status.png| .. method:: EncoderUnit.get_rotary_value() -> int @@ -61,7 +61,7 @@ Methods UIFLOW2: - |get_rotary_value.svg| + |get_rotary_value.png| .. method:: EncoderUnit.get_rotary_increments() -> int @@ -71,7 +71,7 @@ Methods UIFLOW2: - |get_rotary_increments.svg| + |get_rotary_increments.png| .. method:: EncoderUnit.reset_rotary_value() -> None @@ -80,7 +80,7 @@ Methods UIFLOW2: - |reset_rotary_value.svg| + |reset_rotary_value.png| .. method:: EncoderUnit.set_rotary_value(new_value: int) -> None @@ -91,7 +91,7 @@ Methods UIFLOW2: - |set_rotary_value.svg| + |set_rotary_value.png| .. method:: EncoderUnit.get_button_status() -> bool @@ -100,7 +100,7 @@ Methods UIFLOW2: - |get_button_status.svg| + |get_button_status.png| .. method:: EncoderUnit.set_color(index, rgb: int) -> None @@ -112,7 +112,7 @@ Methods UIFLOW2: - |set_color.svg| + |set_color.png| .. method:: EncoderUnit.fill_color(rgb: int) -> None @@ -123,5 +123,5 @@ Methods UIFLOW2: - |fill_color.svg| + |fill_color.png| diff --git a/docs/en/units/env.rst b/docs/en/units/env.rst index aeae80ce..39258cab 100644 --- a/docs/en/units/env.rst +++ b/docs/en/units/env.rst @@ -31,7 +31,7 @@ Micropython Example:: UIFLOW2 Example: - |example.svg| + |example.png| .. only:: builder_html @@ -59,7 +59,7 @@ Constructors UIFLOW2: - |init.svg| + |init.png| Methods @@ -71,7 +71,7 @@ Methods UIFLOW2: - |read_temperature.svg| + |read_temperature.png| .. method:: ENVUnit.read_humidity() @@ -80,7 +80,7 @@ Methods UIFLOW2: - |read_humidity.svg| + |read_humidity.png| .. method:: ENVUnit.read_pressure() @@ -89,4 +89,4 @@ Methods UIFLOW2: - |read_pressure.svg| + |read_pressure.png| diff --git a/docs/en/units/light.rst b/docs/en/units/light.rst index d6602d05..dde28f18 100644 --- a/docs/en/units/light.rst +++ b/docs/en/units/light.rst @@ -40,7 +40,7 @@ Constructors UIFLOW2: - |init.svg| + |init.png| Methods @@ -52,7 +52,7 @@ Methods UIFLOW2: - |get_digital_value.svg| + |get_digital_value.png| .. method:: Light.get_analog_value() @@ -61,7 +61,7 @@ Methods UIFLOW2: - |get_analog_value.svg| + |get_analog_value.png| .. method:: Light.get_ohm() @@ -70,4 +70,4 @@ Methods UIFLOW2: - |get_ohm.svg| + |get_ohm.png| diff --git a/docs/en/units/lora_e220.rst b/docs/en/units/lora_e220.rst index 7813ac21..66f44bb0 100644 --- a/docs/en/units/lora_e220.rst +++ b/docs/en/units/lora_e220.rst @@ -37,12 +37,12 @@ Micropython RX Example:: UIFLOW2 TX Example: - |tx_example.svg| + |tx_example.png| UIFLOW2 RX Example: - |rx_example.svg| + |rx_example.png| .. only:: builder_html @@ -104,7 +104,7 @@ Constructors UIFLOW2: - |init.svg| + |init.png| Methods @@ -131,7 +131,7 @@ Methods UIFLOW2: - |setup.svg| + |setup.png| .. method:: LoRaE220JPUnit.receiveNoneBlock(receive_callback: function) -> None @@ -150,7 +150,7 @@ Methods UIFLOW2: - |receiveNoneBlock.svg| + |receiveNoneBlock.png| .. method:: LoRaE220JPUnit.receive_none_block(receive_callback: function) -> None @@ -169,9 +169,9 @@ Methods UIFLOW2: - |receiveNoneBlock.svg| - |receive_callback1.svg| - |receive_callback2.svg| + |receiveNoneBlock.png| + |receive_callback1.png| + |receive_callback2.png| .. method:: LoRaE220JPUnit.stopReceive() -> None @@ -185,7 +185,7 @@ Methods UIFLOW2: - |stopReceive.svg| + |stopReceive.png| .. method:: LoRaE220JPUnit.stop_receive() -> None @@ -200,7 +200,7 @@ Methods UIFLOW2: - |stopReceive.svg| + |stopReceive.png| .. method:: LoRaE220JPUnit.receive(timeout=1000) -> tuple[bytes, int] @@ -211,7 +211,7 @@ Methods UIFLOW2: - |receive.svg| + |receive.png| .. method:: LoRaE220JPUnit.send(target_address: int, target_channel: int, send_data: bytes | str) -> bool @@ -228,9 +228,9 @@ Methods UIFLOW2: - |send1.svg| - |send2.svg| - |send3.svg| + |send1.png| + |send2.png| + |send3.png| Constants diff --git a/docs/en/units/lora_e220_433.rst b/docs/en/units/lora_e220_433.rst index 7c09dda3..af2c165a 100644 --- a/docs/en/units/lora_e220_433.rst +++ b/docs/en/units/lora_e220_433.rst @@ -211,11 +211,11 @@ Constants --------- .. data:: LoRaE220433Unit.AIRRATE_2_4K - LoRaE220433Unit.AIRRATE_4_8K - LoRaE220433Unit.AIRRATE_9_6K - LoRaE220433Unit.AIRRATE_19_2K - LoRaE220433Unit.AIRRATE_38_4K - LoRaE220433Unit.AIRRATE_62_5K + LoRaE220433Unit.AIRRATE_4_8K + LoRaE220433Unit.AIRRATE_9_6K + LoRaE220433Unit.AIRRATE_19_2K + LoRaE220433Unit.AIRRATE_38_4K + LoRaE220433Unit.AIRRATE_62_5K :type: int rate. @@ -261,7 +261,7 @@ Constants LoRaE220433Unit.WOR_2500MS LoRaE220433Unit.WOR_3000MS LoRaE220433Unit.WOR_3500MS - LoRaE220433Unit.WOR_4000MS + LoRaE220433Unit.WOR_4000MS :type: int Wireless wake-up time. diff --git a/docs/en/units/miniscale.rst b/docs/en/units/miniscale.rst index d3e8c9b0..e833fd8f 100644 --- a/docs/en/units/miniscale.rst +++ b/docs/en/units/miniscale.rst @@ -8,27 +8,27 @@ The ``Miniscale`` class is designed for interfacing with a mini scale weight sen Support the following products: -|MINISCALE| +|MINISCALE| Micropython Example:: - import os, sys, io - import M5 - from M5 import * - import time - from unit import MiniScaleUnit + import os, sys, io + import M5 + from M5 import * + import time + from unit import MiniScaleUnit - i2c = I2C(0, scl=Pin(1), sda=Pin(2), freq=400000) - scale = MiniScaleUnit(i2c) - scale.setLed(255, 0, 0) - print(miniscale.weight) + i2c = I2C(0, scl=Pin(1), sda=Pin(2), freq=400000) + scale = MiniScaleUnit(i2c) + scale.setLed(255, 0, 0) + print(miniscale.weight) UIFLOW2 Example: - |example.svg| + |example.png| .. only:: builder_html @@ -42,14 +42,14 @@ Constructors .. class:: MiniScaleUnit(i2c0) - Create an MiniScaleUnit object. + Create an MiniScaleUnit object. - - ``I2C0`` is I2C Port. + - ``I2C0`` is I2C Port. - - UIFLOW2: - |init.svg| + UIFLOW2: + + |init.png| Methods @@ -58,136 +58,136 @@ Methods .. method:: MiniScaleUnit.adc - Gets the raw adc readout. + Gets the raw adc readout. - UIFLOW2: + UIFLOW2: - |get_adc.svg| + |get_adc.png| .. method:: MiniScaleUnit.weight - Gets the weight readout in grams. + Gets the weight readout in grams. - UIFLOW2: + UIFLOW2: - |get_weight.svg| + |get_weight.png| .. method:: MiniScaleUnit.button - Gets the button state. + Gets the button state. - UIFLOW2: + UIFLOW2: - |get_button.svg| + |get_button.png| .. method:: MiniScaleUnit.tare() - Tare the scale. + Tare the scale. - UIFLOW2: + UIFLOW2: - |tare.svg| + |tare.png| .. method:: MiniScaleUnit.set_led(r, g, b) - Sets the RGB LED color. + Sets the RGB LED color. - - ``r``: Red value (0 - 255). - - ``g``: Green value (0 - 255). - - ``b``: Blue value (0 - 255). + - ``r``: Red value (0 - 255). + - ``g``: Green value (0 - 255). + - ``b``: Blue value (0 - 255). - UIFLOW2: + UIFLOW2: - |setLed.svg| + |setLed.png| .. method:: MiniScaleUnit.reset - Resets sensor. + Resets sensor. - UIFLOW2: + UIFLOW2: - |reset.svg| + |reset.png| .. method:: MiniScaleUnit.calibration(weight1_g, weight1_adc, weight2_g, weight2_adc) - - Calibrates the MiniScale sensor. - - ``weight1_g``: Weight1 in grams. - - ``weight1_adc``: Weight1 in ADC value. - - ``weight2_g``: Weight2 in grams. - - ``weight2_adc``: Weight2 in ADC value. + Calibrates the MiniScale sensor. + + - ``weight1_g``: Weight1 in grams. + - ``weight1_adc``: Weight1 in ADC value. + - ``weight2_g``: Weight2 in grams. + - ``weight2_adc``: Weight2 in ADC value. - calibration steps: + calibration steps: - 1. Reset sensor; - 2. Get adc, this is weight1_adc (should be zero). And weight1_g is also 0. - 3. Put some weight on it, then get adc, this is weight2_adc. And weight2_g is weight in gram you put on it. + 1. Reset sensor; + 2. Get adc, this is weight1_adc (should be zero). And weight1_g is also 0. + 3. Put some weight on it, then get adc, this is weight2_adc. And weight2_g is weight in gram you put on it. - UIFLOW2: + UIFLOW2: - |calibration.svg| + |calibration.png| .. method:: MiniScaleUnit.set_low_pass_filter(enable) - Enables or disables the low pass filter. + Enables or disables the low pass filter. - UIFLOW2: + UIFLOW2: - |setLowPassFilter.svg| + |setLowPassFilter.png| .. method:: MiniScaleUnit.get_low_pass_filter - Returns the status of the low pass filter (enabled or not). + Returns the status of the low pass filter (enabled or not). - UIFLOW2: + UIFLOW2: - |getLowPassFilter.svg| + |getLowPassFilter.png| .. method:: MiniScaleUnit.set_average_filter_level(level) - Sets the level of the average filter. + Sets the level of the average filter. - - ``level``: Level of the average filter (0 - 50). Larger value for smoother result but more latency + - ``level``: Level of the average filter (0 - 50). Larger value for smoother result but more latency - UIFLOW2: + UIFLOW2: - |setAverageFilterLevel.svg| + |setAverageFilterLevel.png| .. method:: MiniScaleUnit.get_average_filter_level - Returns the level of the average filter. + Returns the level of the average filter. - UIFLOW2: + UIFLOW2: - |getAverageFilterLevel.svg| + |getAverageFilterLevel.png| .. method:: MiniScaleUnit.set_ema_filter_alpha(alpha) - Sets the alpha value for the EMA filter. + Sets the alpha value for the EMA filter. - The EMA (Exponential Moving Average) filter is more sensitive to changes in data compared to the average filter. + The EMA (Exponential Moving Average) filter is more sensitive to changes in data compared to the average filter. - - ``alpha``: Alpha value for the EMA filter (0 - 99). Smaller value for smoother result but more latency + - ``alpha``: Alpha value for the EMA filter (0 - 99). Smaller value for smoother result but more latency - UIFLOW2: + UIFLOW2: - |setEMAFilterAlpha.svg| + |setEMAFilterAlpha.png| .. method:: MiniScaleUnit.get_ema_filter_alpha - Returns the alpha value for the EMA filter. + Returns the alpha value for the EMA filter. - UIFLOW2: + UIFLOW2: - |getEMAFilterAlpha.svg| + |getEMAFilterAlpha.png| diff --git a/docs/en/units/op180.rst b/docs/en/units/op180.rst index d99698c3..542d1c2e 100644 --- a/docs/en/units/op180.rst +++ b/docs/en/units/op180.rst @@ -35,7 +35,7 @@ Micropython Example:: UIFLOW2 Example: - |example.svg| + |example.png| .. only:: builder_html @@ -58,7 +58,7 @@ Constructors UIFLOW2: - |init.svg| + |init.png| Methods @@ -70,7 +70,7 @@ Methods UIFLOW2: - |get_count_value.svg| + |get_count_value.png| .. method:: OP108.count_reset() @@ -78,4 +78,4 @@ Methods UIFLOW2: - |reset_count_value.svg| + |reset_count_value.png| diff --git a/docs/en/units/op90.rst b/docs/en/units/op90.rst index 1cc1bc8a..e4bd8b58 100644 --- a/docs/en/units/op90.rst +++ b/docs/en/units/op90.rst @@ -38,7 +38,7 @@ Micropython Example:: UIFLOW2 Example: - |example.svg| + |example.png| .. only:: builder_html @@ -61,7 +61,7 @@ Constructors UIFLOW2: - |init.svg| + |init.png| Methods @@ -73,7 +73,7 @@ Methods UIFLOW2: - |get_count_value.svg| + |get_count_value.png| .. method:: OPUnit.count_reset() @@ -82,4 +82,4 @@ Methods UIFLOW2: - |reset_count_value.svg| + |reset_count_value.png| diff --git a/docs/en/units/qrcode.rst b/docs/en/units/qrcode.rst index 0f583ff1..30372fdb 100644 --- a/docs/en/units/qrcode.rst +++ b/docs/en/units/qrcode.rst @@ -175,7 +175,7 @@ Methods Get the firmware version details and I2C address of this device. The parameters is: - - ``info``: 0xFE: firmware version, 0xFF: I2C address + - ``info``: 0xFE: firmware version, 0xFF: I2C address UIFLOW2: @@ -195,7 +195,7 @@ Methods The i2c address can be changed by the user and this address should be between 0x01 and 0x7F. - - ``addr``: range of address(0x01 - 0x7F). + - ``addr``: range of address(0x01 - 0x7F). UIFLOW2: diff --git a/docs/en/units/rgb.rst b/docs/en/units/rgb.rst index 03d57bfe..1e76f3cc 100644 --- a/docs/en/units/rgb.rst +++ b/docs/en/units/rgb.rst @@ -25,7 +25,7 @@ Micropython Example:: UIFLOW2 Example: - |example.svg| + |example.png| .. only:: builder_html @@ -49,7 +49,7 @@ Constructors UIFLOW2: - |init.svg| + |init.png| Methods @@ -61,7 +61,7 @@ Methods UIFLOW2: - |set_brightness.svg| + |set_brightness.png| .. method:: RGB.fill_color(c: int) @@ -70,7 +70,7 @@ Methods UIFLOW2: - |fill_color.svg| + |fill_color.png| .. method:: RGB.set_color(i, c: int) @@ -79,4 +79,4 @@ Methods UIFLOW2: - |set_color.svg| + |set_color.png| diff --git a/docs/en/units/thermal.rst b/docs/en/units/thermal.rst index 76408360..839c5e07 100644 --- a/docs/en/units/thermal.rst +++ b/docs/en/units/thermal.rst @@ -23,7 +23,7 @@ Constructors UIFLOW2: - |init.svg| + |init.png| .. _unit.ThermaltUnit.Methods: @@ -39,7 +39,7 @@ Methods UIFLOW2: - |get_max_temperature.svg| + |get_max_temperature.png| .. property:: ThermalUnit.get_min_temperature @@ -50,7 +50,7 @@ Methods UIFLOW2: - |get_min_temperature.svg| + |get_min_temperature.png| .. property:: ThermalUnit.get_midpoint_temperature @@ -61,7 +61,7 @@ Methods UIFLOW2: - |get_midpoint_temperature.svg| + |get_midpoint_temperature.png| .. method:: ThermalUnit.get_pixel_temperature(x: int, y: int) -> float @@ -75,7 +75,7 @@ Methods UIFLOW2: - |get_pixel_temperature.svg| + |get_pixel_temperature.png| .. property:: ThermalUnit.get_refresh_rate @@ -86,7 +86,7 @@ Methods UIFLOW2: - |get_refresh_rate.svg| + |get_refresh_rate.png| .. method:: ThermalUnit.get_temperature_buffer() -> list @@ -97,7 +97,7 @@ Methods UIFLOW2: - |get_temperature_buffer.svg| + |get_temperature_buffer.png| .. method:: ThermalUnit.set_refresh_rate(rate: int) -> None @@ -108,7 +108,7 @@ Methods UIFLOW2: - |set_refresh_rate.svg| + |set_refresh_rate.png| .. method:: ThermalUnit.update_temperature_buffer() -> bytes @@ -119,4 +119,4 @@ Methods UIFLOW2: - |update_temperature_buffer.svg| + |update_temperature_buffer.png| diff --git a/docs/en/units/ultrasonic.rst b/docs/en/units/ultrasonic.rst index 1383d96d..0b6c2691 100644 --- a/docs/en/units/ultrasonic.rst +++ b/docs/en/units/ultrasonic.rst @@ -30,7 +30,7 @@ Micropython Example:: UIFLOW2 Example: - |example.svg| + |example.png| .. only:: builder_html @@ -53,7 +53,7 @@ Constructors UIFLOW2: - |init.svg| + |init.png| Methods @@ -65,4 +65,4 @@ Methods UIFLOW2: - |get_target_distance.svg| + |get_target_distance.png| diff --git a/docs/en/units/vibrator.rst b/docs/en/units/vibrator.rst index fad0e80c..ba519da3 100644 --- a/docs/en/units/vibrator.rst +++ b/docs/en/units/vibrator.rst @@ -18,7 +18,7 @@ Micropython Example: UIFLOW2 Example: - |example.svg| + |example.png| .. only:: builder_html diff --git a/docs/en/units/weight_i2c.rst b/docs/en/units/weight_i2c.rst index b9ffe140..2a3c98f3 100644 --- a/docs/en/units/weight_i2c.rst +++ b/docs/en/units/weight_i2c.rst @@ -8,29 +8,29 @@ The ``Weight I2C`` Unit is a weighing acquisition transmitter unit that adopts t Support the following products: -|WEIGHT I2C| +|WEIGHT I2C| Micropython Example:: - import os, sys, io - import M5 - from M5 import * - from hardware import * - from unit import WEIGHT_I2CUnit - import time + import os, sys, io + import M5 + from M5 import * + from hardware import * + from unit import WEIGHT_I2CUnit + import time - i2c0 = I2C(0, scl=Pin(1), sda=Pin(2), freq=100000) - weight_i2c0 = WEIGHT_I2CUnit(i2c0) - print(weight_i2c_0.get_adc_raw) - print(weight_i2c_0.get_weight_float) - time.sleep_ms(100) + i2c0 = I2C(0, scl=Pin(1), sda=Pin(2), freq=100000) + weight_i2c0 = WEIGHT_I2CUnit(i2c0) + print(weight_i2c_0.get_adc_raw) + print(weight_i2c_0.get_weight_float) + time.sleep_ms(100) UIFLOW2 Example: - |example.svg| + |example.png| .. only:: builder_html @@ -45,159 +45,159 @@ Constructors .. class:: WEIGHT_I2CUnit(i2c0, 0x26) - Create an WEIGHT_I2CUnit object. + Create an WEIGHT_I2CUnit object. - - ``I2C0`` is I2C Port. - - ``0x26`` is default I2C address + - ``I2C0`` is I2C Port. + - ``0x26`` is default I2C address - - UIFLOW2: - |init_i2c_address.svg| + UIFLOW2: + + |init_i2c_address.png| Methods ---------------------- -.. method:: WEIGHT_I2CUnit.get_adc_raw +.. method:: WEIGHT_I2CUnit.get_adc_raw - Gets the raw adc value. + Gets the raw adc value. - UIFLOW2: + UIFLOW2: - |get_adc_raw.svg| + |get_adc_raw.png| .. method:: WEIGHT_I2CUnit.get_weight_float - Gets the weight in grams float value. + Gets the weight in grams float value. - UIFLOW2: + UIFLOW2: - |get_weight_float.svg| + |get_weight_float.png| .. method:: WEIGHT_I2CUnit.get_weight_int - Gets the weight in grams int value. + Gets the weight in grams int value. - UIFLOW2: + UIFLOW2: - |get_weight_int.svg| + |get_weight_int.png| .. method:: WEIGHT_I2CUnit.get_weight_str - Gets the weight in grams string value. + Gets the weight in grams string value. - UIFLOW2: + UIFLOW2: - |get_weight_str.svg| + |get_weight_str.png| .. method:: WEIGHT_I2CUnit.set_reset_offset() - Reset the offset value(Tare). + Reset the offset value(Tare). - UIFLOW2: + UIFLOW2: - |set_reset_offset.svg| + |set_reset_offset.png| .. method:: WEIGHT_I2CUnit.set_calibration(weight1_g, weight1_adc, weight2_g, weight2_adc) - - Calibrates the Load sensor. - - ``weight1_g``: Weight1 in grams. - - ``weight1_adc``: Weight1 in ADC value. - - ``weight2_g``: Weight2 in grams. - - ``weight2_adc``: Weight2 in ADC value. + Calibrates the Load sensor. + + - ``weight1_g``: Weight1 in grams. + - ``weight1_adc``: Weight1 in ADC value. + - ``weight2_g``: Weight2 in grams. + - ``weight2_adc``: Weight2 in ADC value. - calibration steps: + calibration steps: - 1.Reset offset(Tare). - 2.Get the raw ADC value at no-load weight, this is the Raw ADC of zero weight in 0g. - 3.Put some weight on it, then get adc, this is the load weight adc value and the gram weight you put on it. + 1.Reset offset(Tare). + 2.Get the raw ADC value at no-load weight, this is the Raw ADC of zero weight in 0g. + 3.Put some weight on it, then get adc, this is the load weight adc value and the gram weight you put on it. - UIFLOW2: + UIFLOW2: - |set_calibration.svg| + |set_calibration.png| .. method:: WEIGHT_I2CUnit.set_lowpass_filter(Enable) - Enable or disable the low pass filter. + Enable or disable the low pass filter. - UIFLOW2: + UIFLOW2: - |set_lowpass_filter.svg| + |set_lowpass_filter.png| .. method:: WEIGHT_I2CUnit.get_lowpass_filter - Returns the status of the low pass filter (enable or disable). + Returns the status of the low pass filter (enable or disable). - UIFLOW2: + UIFLOW2: - |get_lowpass_filter.svg| + |get_lowpass_filter.png| .. method:: WEIGHT_I2CUnit.set_average_filter_level(level) - Sets the level of the average filter. + Sets the level of the average filter. - - ``level``: Level of the average filter (0 - 50). Larger value for smoother result but more latency + - ``level``: Level of the average filter (0 - 50). Larger value for smoother result but more latency - UIFLOW2: + UIFLOW2: - |set_average_filter_level.svg| + |set_average_filter_level.png| .. method:: WEIGHT_I2CUnit.get_average_filter_level - Returns the level of the average filter. + Returns the level of the average filter. - UIFLOW2: + UIFLOW2: - |get_average_filter_level.svg| + |get_average_filter_level.png| .. method:: WEIGHT_I2CUnit.set_ema_filter_alpha(alpha) - Sets the alpha value for the EMA filter. + Sets the alpha value for the EMA filter. - The EMA (Exponential Moving Average) filter is more sensitive to changes in data compared to the average filter. + The EMA (Exponential Moving Average) filter is more sensitive to changes in data compared to the average filter. - - ``alpha``: Alpha value for the EMA filter (0 - 99). Smaller value for smoother result but more latency + - ``alpha``: Alpha value for the EMA filter (0 - 99). Smaller value for smoother result but more latency - UIFLOW2: + UIFLOW2: - |set_ema_filter_alpha.svg| + |set_ema_filter_alpha.png| .. method:: WEIGHT_I2CUnit.get_ema_filter_alpha - Returns the alpha value for the EMA filter. + Returns the alpha value for the EMA filter. - UIFLOW2: + UIFLOW2: - |get_ema_filter_alpha.svg| + |get_ema_filter_alpha.png| .. method:: WEIGHT_I2CUnit.set_i2c_address(address) - The i2c address can be changed by the user and this address should be between 0x01 and 0x7F. + The i2c address can be changed by the user and this address should be between 0x01 and 0x7F. - - ``address``: range of address(0x01 - 0x7F). + - ``address``: range of address(0x01 - 0x7F). - UIFLOW2: + UIFLOW2: - |set_i2c_address.svg| + |set_i2c_address.png| .. method:: WEIGHT_I2CUnit.get_device_spec(info) - Get the firmware version details and I2c address of this device. + Get the firmware version details and I2c address of this device. - - ``info``: (0xFE, 0xFF) + - ``info``: (0xFE, 0xFF) - UIFLOW2: + UIFLOW2: - |get_device_spec.svg| \ No newline at end of file + |get_device_spec.png| diff --git a/m5stack/libs/unit/accel.py b/m5stack/libs/unit/accel.py index bc39dc8e..856e9b60 100644 --- a/m5stack/libs/unit/accel.py +++ b/m5stack/libs/unit/accel.py @@ -14,7 +14,7 @@ class AccelUnit(ADXL345): UiFlow2 Code Block: - |init.svg| + |init.png| MicroPython Code Block: @@ -38,7 +38,7 @@ def get_accel(self) -> tuple[float, float, float]: UiFlow2 Code Block: - |get_accel.svg| + |get_accel.png| MicroPython Code Block: @@ -96,7 +96,7 @@ def get_data_rate(self) -> int: UiFlow2 Code Block: - |get_data_rate.svg| + |get_data_rate.png| MicroPython Code Block: @@ -113,7 +113,7 @@ def set_data_rate(self, rate: int) -> None: UiFlow2 Code Block: - |set_data_rate.svg| + |set_data_rate.png| MicroPython Code Block: @@ -131,7 +131,7 @@ def get_range(self) -> int: UiFlow2 Code Block: - |get_range.svg| + |get_range.png| MicroPython Code Block: @@ -148,7 +148,7 @@ def set_range(self, range: int) -> None: UiFlow2 Code Block: - |set_range.svg| + |set_range.png| MicroPython Code Block: @@ -166,7 +166,7 @@ def is_tap(self) -> bool: UiFlow2 Code Block: - |is_tap.svg| + |is_tap.png| MicroPython Code Block: @@ -184,7 +184,7 @@ def is_motion(self) -> bool: :rtype: bool UiFlow2 Code Block: - |is_motion.svg| + |is_motion.png| MicroPython Code Block: @@ -203,7 +203,7 @@ def is_freefall(self) -> bool: UiFlow2 Code Block: - |is_freefall.svg| + |is_freefall.png| MicroPython Code Block: From 295975f05f512de5bd974ba2d64a64e78c734bc0 Mon Sep 17 00:00:00 2001 From: lbuque Date: Wed, 9 Apr 2025 14:49:36 +0800 Subject: [PATCH 046/322] docs: Add Unit 8Servos api document. Signed-off-by: lbuque --- docs/en/conf.py | 1 + docs/en/refs/unit.servos8.ref | 44 ++ docs/en/units/index.rst | 1 + docs/en/units/servos8.rst | 95 ++++ examples/unit/8servos/cores3_rgb_example.m5f2 | 1 + examples/unit/8servos/cores3_rgb_example.py | 55 +++ .../unit/8servos/cores3_servo_example.m5f2 | 1 + examples/unit/8servos/cores3_servo_example.py | 56 +++ m5stack/libs/unit/servos8.py | 429 +++++++++++++----- 9 files changed, 558 insertions(+), 125 deletions(-) create mode 100644 docs/en/refs/unit.servos8.ref create mode 100644 docs/en/units/servos8.rst create mode 100644 examples/unit/8servos/cores3_rgb_example.m5f2 create mode 100644 examples/unit/8servos/cores3_rgb_example.py create mode 100644 examples/unit/8servos/cores3_servo_example.m5f2 create mode 100644 examples/unit/8servos/cores3_servo_example.py diff --git a/docs/en/conf.py b/docs/en/conf.py index 77d353d7..9cb2d838 100644 --- a/docs/en/conf.py +++ b/docs/en/conf.py @@ -68,6 +68,7 @@ intersphinx_mapping = { "python": ("https://docs.python.org/3", None), "micropython": ("https://docs.micropython.org/en/v1.22.0/", None), + "uiflow2": ("https://uiflow-micropython.readthedocs.io/en/latest/", None), } latex_engine = 'pdflatex' diff --git a/docs/en/refs/unit.servos8.ref b/docs/en/refs/unit.servos8.ref new file mode 100644 index 00000000..c7b76071 --- /dev/null +++ b/docs/en/refs/unit.servos8.ref @@ -0,0 +1,44 @@ +.. |8Servos Unit| image:: https://static-cdn.m5stack.com/resource/docs/products/unit/8Servos%20Unit/img-353a21be-5d6e-4323-912c-5b9df887b513.webp + :target: https://docs.m5stack.com/en/unit/8Servos%20Unit + :height: 200px + :width: 200px + +.. |example_servo.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/8servos/example_servo.png +.. |example_rgb.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/8servos/example_rgb.png + + +.. |get_12bit_adc_raw.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/8servos/get_12bit_adc_raw.png +.. |get_8bit_adc_raw.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/8servos/get_8bit_adc_raw.png +.. |get_device_spec.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/8servos/get_device_spec.png +.. |get_digital_input.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/8servos/get_digital_input.png +.. |get_input_current.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/8servos/get_input_current.png +.. |get_mode.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/8servos/get_mode.png +.. |get_rgb_led.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/8servos/get_rgb_led.png +.. |get_servo_angle.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/8servos/get_servo_angle.png +.. |get_servo_pulse.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/8servos/get_servo_pulse.png +.. |init.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/8servos/init.png +.. |set_i2c_address.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/8servos/set_i2c_address.png +.. |set_mode.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/8servos/set_mode.png +.. |set_output_value.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/8servos/set_output_value.png +.. |set_pwm_dutycycle.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/8servos/set_pwm_dutycycle.png +.. |set_rgb_led.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/8servos/set_rgb_led.png +.. |set_servo_angle.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/8servos/set_servo_angle.png +.. |set_servo_pulse.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/8servos/set_servo_pulse.png + +.. |cores3_servo_example.m5f2| raw:: html + + + cores3_servo_example.m5f2 + + +.. |cores3_rgb_example.m5f2| raw:: html + + + cores3_rgb_example.m5f2 + diff --git a/docs/en/units/index.rst b/docs/en/units/index.rst index 6e02b730..728f9646 100644 --- a/docs/en/units/index.rst +++ b/docs/en/units/index.rst @@ -94,6 +94,7 @@ Unit rollercan.rst scales.rst scroll.rst + servos8.rst ssr.rst synth.rst thermal.rst diff --git a/docs/en/units/servos8.rst b/docs/en/units/servos8.rst new file mode 100644 index 00000000..1e5f1227 --- /dev/null +++ b/docs/en/units/servos8.rst @@ -0,0 +1,95 @@ +8Servos Unit +============ + +.. sku: U165 + +.. include:: ../refs/unit.servos8.ref + +This is the driver library for the 8Servos Unit. It is a 8-channel servo +controller that can control up to 8 servos. It can be used to control the +angles of the servos, set the pulse width, and set the mode of the servos. + +Support the following products: + + |8Servos Unit| + + +UiFlow2 Example +--------------- + +servo control +^^^^^^^^^^^^^ + +Open the |cores3_servo_example.m5f2| project in UiFlow2. + +This example controls the servo angle of the 8Servos Unit. + +UiFlow2 Code Block: + + |example_servo.png| + +Example output: + + None + + +rgb control +^^^^^^^^^^^ + +Open the |cores3_rgb_example.m5f2| project in UiFlow2. + +This example controls the RGB LED of the 8Servos Unit. + +UiFlow2 Code Block: + + |example_rgb.png| + +Example output: + + None + + +MicroPython Example +------------------- + +servo control +^^^^^^^^^^^^^ + +This example controls the servo angle of the 8Servos Unit. + +MicroPython Code Block: + + .. literalinclude:: ../../../examples/unit/8servos/cores3_servo_example.py + :language: python + :linenos: + +Example output: + + None + + +rgb control +^^^^^^^^^^^ + +This example controls the RGB LED of the 8Servos Unit. + +MicroPython Code Block: + + .. literalinclude:: ../../../examples/unit/8servos/cores3_rgb_example.py + :language: python + :linenos: + +Example output: + + None + + + +**API** +------- + +Servos8Unit +^^^^^^^^^^^ + +.. autoclass:: unit.servos8.Servos8Unit + :members: diff --git a/examples/unit/8servos/cores3_rgb_example.m5f2 b/examples/unit/8servos/cores3_rgb_example.m5f2 new file mode 100644 index 00000000..baad3bc3 --- /dev/null +++ b/examples/unit/8servos/cores3_rgb_example.m5f2 @@ -0,0 +1 @@ +{"version":"V2.0","versionNumber":"V2.2.5","type":"cores3","components":[{"name":"screen","type":"screen","layer":0,"screenId":"builtin","screenName":"","id":"__cores3_screen","createTime":1729583797465,"x":0,"y":0,"width":320,"height":240,"backgroundColor":"#222222","size":0,"isSelected":true},{"name":"label0","type":"label","layer":1,"screenId":"builtin","screenName":"","id":"m=6Kw6=nwVC+78w1","createTime":1729583916249,"x":92,"y":109,"color":"#ffffff","backgroundColor":"#222222","text":"8Servos Unit","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false}],"resources":[{"hardware":["hardware_button","hardware_pin_button","imu","speaker","touch","als","mic","i2c"]},{"unit":["unit_servos8"]}],"units":[{"type":"unit_servos8","name":"servos8_0","portList":["A","PAHUB","Custom"],"portType":"A","userPort":[22,21],"id":"uC#J5aOp+TDR6Ixu","createTime":1744182563002,"bus":"i2c0","pahubPortList":[0,1,2,3,4,5],"pahubPort":0,"initBlockId":"+S]aC6O65Hyk6?LkQ*jk"}],"hats":[],"bases":[],"i2cs":[{"id":"i2c0","portType":"A","userPort":[22,21],"freq":"100000","blockId":"=}z/nG.Dx,?e}if.}_`9"}],"blockly":"true010000012servos8_00x25servos8_040trueservos8_00palette#ff00001servos8_00palette#33ff331servos8_00palette#3366ff1","screen":[{"simulationName":"Built-in","type":"builtin","width":320,"height":240,"scale":0.78,"screenName":"","blockId":"","screenColorType":0,"id":"builtin","createTime":1729583797461}],"logicWhenNum":0,"customList":[]} \ No newline at end of file diff --git a/examples/unit/8servos/cores3_rgb_example.py b/examples/unit/8servos/cores3_rgb_example.py new file mode 100644 index 00000000..bdb39310 --- /dev/null +++ b/examples/unit/8servos/cores3_rgb_example.py @@ -0,0 +1,55 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import os, sys, io +import M5 +from M5 import * +from hardware import I2C +from hardware import Pin +from unit import Servos8Unit +import time + + +label0 = None +i2c0 = None +servos8_0 = None + + +def setup(): + global label0, i2c0, servos8_0 + + M5.begin() + Widgets.fillScreen(0x222222) + label0 = Widgets.Label( + "8Servos Unit", 92, 109, 1.0, 0xFFFFFF, 0x222222, Widgets.FONTS.DejaVu18 + ) + + i2c0 = I2C(0, scl=Pin(1), sda=Pin(2), freq=100000) + servos8_0 = Servos8Unit(i2c0, 0x25) + servos8_0.set_mode(4, 0) + + +def loop(): + global label0, i2c0, servos8_0 + M5.update() + servos8_0.set_rgb_led(0xFF0000, 0) + time.sleep(1) + servos8_0.set_rgb_led(0x33FF33, 0) + time.sleep(1) + servos8_0.set_rgb_led(0x3366FF, 0) + time.sleep(1) + + +if __name__ == "__main__": + try: + setup() + while True: + loop() + except (Exception, KeyboardInterrupt) as e: + try: + from utility import print_error_msg + + print_error_msg(e) + except ImportError: + print("please update to latest firmware") diff --git a/examples/unit/8servos/cores3_servo_example.m5f2 b/examples/unit/8servos/cores3_servo_example.m5f2 new file mode 100644 index 00000000..e82db45e --- /dev/null +++ b/examples/unit/8servos/cores3_servo_example.m5f2 @@ -0,0 +1 @@ +{"version":"V2.0","versionNumber":"V2.2.5","type":"cores3","components":[{"name":"screen","type":"screen","layer":0,"screenId":"builtin","screenName":"","id":"__cores3_screen","createTime":1729583797465,"x":0,"y":0,"width":320,"height":240,"backgroundColor":"#222222","size":0,"isSelected":true},{"name":"label0","type":"label","layer":1,"screenId":"builtin","screenName":"","id":"m=6Kw6=nwVC+78w1","createTime":1729583916249,"x":92,"y":109,"color":"#ffffff","backgroundColor":"#222222","text":"8Servos Unit","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false}],"resources":[{"hardware":["hardware_button","hardware_pin_button","imu","speaker","touch","als","mic","i2c"]},{"unit":["unit_servos8"]}],"units":[{"type":"unit_servos8","name":"servos8_0","portList":["A","PAHUB","Custom"],"portType":"A","userPort":[22,21],"id":"uC#J5aOp+TDR6Ixu","createTime":1744181687619,"bus":"i2c0","pahubPortList":[0,1,2,3,4,5],"pahubPort":0,"initBlockId":"+S]aC6O65Hyk6?LkQ*jk"}],"hats":[],"bases":[],"i2cs":[{"id":"i2c0","portType":"A","userPort":[22,21],"freq":"100000","blockId":"=}z/nG.Dx,?e}if.}_`9"}],"blockly":"true010000012servos8_00x25servos8_033servos8_037trueservos8_0345servos8_07451servos8_03150servos8_071501","screen":[{"simulationName":"Built-in","type":"builtin","width":320,"height":240,"scale":0.78,"screenName":"","blockId":"","screenColorType":0,"id":"builtin","createTime":1729583797461}],"logicWhenNum":0,"customList":[]} \ No newline at end of file diff --git a/examples/unit/8servos/cores3_servo_example.py b/examples/unit/8servos/cores3_servo_example.py new file mode 100644 index 00000000..4db4a5d6 --- /dev/null +++ b/examples/unit/8servos/cores3_servo_example.py @@ -0,0 +1,56 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import os, sys, io +import M5 +from M5 import * +from hardware import I2C +from hardware import Pin +from unit import Servos8Unit +import time + + +label0 = None +i2c0 = None +servos8_0 = None + + +def setup(): + global label0, i2c0, servos8_0 + + M5.begin() + Widgets.fillScreen(0x222222) + label0 = Widgets.Label( + "8Servos Unit", 92, 109, 1.0, 0xFFFFFF, 0x222222, Widgets.FONTS.DejaVu18 + ) + + i2c0 = I2C(0, scl=Pin(1), sda=Pin(2), freq=100000) + servos8_0 = Servos8Unit(i2c0, 0x25) + servos8_0.set_mode(3, 3) + servos8_0.set_mode(3, 7) + + +def loop(): + global label0, i2c0, servos8_0 + M5.update() + servos8_0.set_servo_angle(45, 3) + servos8_0.set_servo_angle(45, 7) + time.sleep(1) + servos8_0.set_servo_angle(150, 3) + servos8_0.set_servo_angle(150, 7) + time.sleep(1) + + +if __name__ == "__main__": + try: + setup() + while True: + loop() + except (Exception, KeyboardInterrupt) as e: + try: + from utility import print_error_msg + + print_error_msg(e) + except ImportError: + print("please update to latest firmware") diff --git a/m5stack/libs/unit/servos8.py b/m5stack/libs/unit/servos8.py index 820b9bb3..af24acf7 100644 --- a/m5stack/libs/unit/servos8.py +++ b/m5stack/libs/unit/servos8.py @@ -2,12 +2,15 @@ # # SPDX-License-Identifier: MIT -from machine import I2C +import machine from .pahub import PAHUBUnit from .unit_helper import UnitError import struct import time +import sys +if sys.platform != "esp32": + from typing import Union SERVOS_8_ADDR = 0x25 @@ -33,200 +36,376 @@ class Servos8Unit: - def __init__(self, i2c: I2C | PAHUBUnit, address: int | list | tuple = SERVOS_8_ADDR): - """ - slave_addr : 1 to 127 - """ - self.servos8_i2c = i2c - self.i2c_addr = address - if address >= 1 and address <= 127: - self.i2c_addr = address - self.available() + """Create a servos 8 unit object. + + :param i2c: The I2C bus the servos 8 unit is connected to. + :type i2c: machine.I2C | PAHUBUnit + :param int address: The I2C address of the device. Default is 0x25. + + :raises UnitError: If the servos 8 unit is not connected. + + UiFlow2 Code Block: + |init.png| + + MicroPython Code Block: + + .. code-block:: python + + from hardware import I2C + from unit import Servos8Unit - def available(self): - if self.i2c_addr not in self.servos8_i2c.scan(): + i2c0 = I2C(0, scl=Pin(1), sda=Pin(2), freq=100000) + servos8_0 = Servos8Unit(i2c0, 0x25) + """ + + DIGITAL_INPUT_MODE = 0 + DIGITAL_OUTPUT_MODE = 1 + ADC_INPUT_MODE = 2 + SERVO_CTL_MODE = 3 + RGB_LED_MODE = 4 + PWM_DUTY_MODE = 5 + + def __init__( + self, i2c: Union[machine.I2C, PAHUBUnit], address: int | list | tuple = SERVOS_8_ADDR + ): + self._i2c = i2c + self._address = address + if self._address not in self._i2c.scan(): raise UnitError("8 Servos unit maybe not connect") - def get_mode(self, channel): - """ - get mode. - channel: 0 to 7 + def get_mode(self, channel: int) -> int: + """Get the current mode of a specific channel. + + :param int channel: The channel number (0 to 7) to get the mode for. + :return: The mode of the specified channel. + :rtype: int + + UiFlow2 Code Block: + + |get_mode.png| + + MicroPython Code Block: + + .. code-block:: python + + servos8_0.get_mode(0) """ if channel >= 0 and channel <= 7: - return self.servos8_i2c.readfrom_mem(self.i2c_addr, MODE_REG + channel, 1)[0] + return self._i2c.readfrom_mem(self._address, MODE_REG + channel, 1)[0] - def set_mode(self, mode, channel): - """ - set mode. - channel: 0 to 7 - mode: DIGITAL_INPUT_MODE=0 - DIGITAL_OUTPUT_MODE=1 - ADC_INPUT_MODE=2 - SERVO_CTL_MODE=3 - RGB_LED_MODE=4 - PWM_DUTY_MODE=5 + def set_mode(self, mode: int, channel: int) -> None: + """Set the mode of a specific channel. + + :param int mode: The mode to set for the channel. + :param int channel: The channel number (0 to 7) to set the mode for. + + UiFlow2 Code Block: + + |set_mode.png| + + MicroPython Code Block: + + .. code-block:: python + + servos8_0.set_mode(0, 0) """ if channel >= 0 and channel <= 7: - self.servos8_i2c.writeto_mem(self.i2c_addr, MODE_REG + channel, bytearray([mode])) + self._i2c.writeto_mem(self._address, MODE_REG + channel, bytearray([mode])) - def get_digital_input(self, channel): - """ - get digital input. - channel: 0 to 7 + def get_digital_input(self, channel: int) -> bool: + """Get the digital input value of a specific channel. + + :param int channel: The channel number (0 to 7) to get the digital input for. + :return: The digital input value (True or False) of the specified channel. + :rtype: bool + + UiFlow2 Code Block: + + |get_digital_input.png| + + MicroPython Code Block: + + .. code-block:: python + + servos8_0.get_digital_input(0) """ if channel >= 0 and channel <= 7: if DIGITAL_IN_MODE == self.get_mode(channel): return bool( - self.servos8_i2c.readfrom_mem(self.i2c_addr, (DIGITAL_IN_REG + channel), 1)[0] + self._i2c.readfrom_mem(self._address, (DIGITAL_IN_REG + channel), 1)[0] ) - def set_output_value(self, value, channel): - """ - set digital output. - channel: 0 to 7 - value : 0 or 1 + def set_output_value(self, value: int, channel: int) -> None: + """Set the digital output value of a specific channel. + + :param int value: The digital output value (0 or 1) to set for the channel. + :param int channel: The channel number (0 to 7) to set the digital output for. + + UiFlow2 Code Block: + + |set_output_value.png| + + MicroPython Code Block: + + .. code-block:: python + + servos8_0.set_output_value(1, 0) """ if channel >= 0 and channel <= 7: if DIGITAL_OUT_MODE == self.get_mode(channel): - self.servos8_i2c.writeto_mem( - self.i2c_addr, (DIGITAL_OUT_REG + channel), bytearray([value]) + self._i2c.writeto_mem( + self._address, (DIGITAL_OUT_REG + channel), bytearray([value]) ) - def get_8bit_adc_raw(self, channel): - """ - get adc 8 bit. - channel: 0 to 7 - return : 0 to 255 + def get_8bit_adc_raw(self, channel: int) -> int: + """Get the 8-bit ADC value of a specific channel. + + :param int channel: The channel number (0 to 7) to get the 8-bit ADC value for. + :return: The 8-bit ADC value (0 to 255) of the specified channel. + :rtype: int + + UiFlow2 Code Block: + + |get_8bit_adc_raw.png| + + MicroPython Code Block: + + .. code-block:: python + + servos8_0.get_8bit_adc_raw(0) """ if channel >= 0 and channel <= 7: if ADC_IN_MODE == self.get_mode(channel): - return self.servos8_i2c.readfrom_mem(self.i2c_addr, (ADC_8IN_REG + channel), 1)[0] + return self._i2c.readfrom_mem(self._address, (ADC_8IN_REG + channel), 1)[0] - def get_12bit_adc_raw(self, channel): - """ - get adc 12 bit. - channel: 0 to 7 - return : 0 to 4095 + def get_12bit_adc_raw(self, channel: int) -> int: + """Get the 12-bit ADC value of a specific channel. + + :param int channel: The channel number (0 to 7) to get the 12-bit ADC value for. + :return: The 12-bit ADC value (0 to 4095) of the specified channel. + :rtype: int + + UiFlow2 Code Block: + + |get_12bit_adc_raw.png| + + MicroPython Code Block: + + .. code-block:: python + + servos8_0.get_12bit_adc_raw(0) """ if channel >= 0 and channel <= 7: if ADC_IN_MODE == self.get_mode(channel): - buf = self.servos8_i2c.readfrom_mem( - self.i2c_addr, (ADC_12IN_REG + (channel * 2)), 2 - ) + buf = self._i2c.readfrom_mem(self._address, (ADC_12IN_REG + (channel * 2)), 2) return struct.unpack(" None: + """Set the servo angle of a specific channel. + + :param int angle: The servo angle (0 to 180) to set for the channel. + :param int channel: The channel number (0 to 7) to set the servo angle for. + + UiFlow2 Code Block: + + |set_servo_angle.png| + + MicroPython Code Block: + + .. code-block:: python + + servos8_0.set_servo_angle(90, 0) """ if channel >= 0 and channel <= 7: if SERVO_CTRL_MODE == self.get_mode(channel): - self.servos8_i2c.writeto_mem( - self.i2c_addr, (SERVO_ANGLE_REG + channel), bytearray([angle]) + self._i2c.writeto_mem( + self._address, (SERVO_ANGLE_REG + channel), bytearray([angle]) ) - def get_servo_angle(self, channel): - """ - get servo angle. - channel: 0 to 7 - return : 0 to 180 + def get_servo_angle(self, channel: int) -> int: + """Get the servo angle of a specific channel. + + :param int channel: The channel number (0 to 7) to get the servo angle for. + :return: The servo angle (0 to 180) of the specified channel. + :rtype: int + + UiFlow2 Code Block: + + |get_servo_angle.png| + + MicroPython Code Block: + + .. code-block:: python + + servos8_0.get_servo_angle(0) """ if channel >= 0 and channel <= 7: if SERVO_CTRL_MODE == self.get_mode(channel): - return self.servos8_i2c.readfrom_mem( - self.i2c_addr, (SERVO_ANGLE_REG + channel), 1 - )[0] + return self._i2c.readfrom_mem(self._address, (SERVO_ANGLE_REG + channel), 1)[0] - def set_servo_pulse(self, pulse, channel): - """ - set servo pulse. - pulse : 500 to 2500 - channel: 0 to 7 + def set_servo_pulse(self, pulse: int, channel: int) -> None: + """Set the servo pulse of a specific channel. + + :param int pulse: The servo pulse (500 to 2500) to set for the channel. + :param int channel: The channel number (0 to 7) to set the servo pulse for. + + UiFlow2 Code Block: + + |set_servo_pulse.png| + + MicroPython Code Block: + + .. code-block:: python + + servos8_0.set_servo_pulse(1500, 0) """ if channel >= 0 and channel <= 7: if SERVO_CTRL_MODE == self.get_mode(channel): pulse = struct.pack(" int: + """Get the servo pulse of a specific channel. + + :param int channel: The channel number (0 to 7) to get the servo pulse for. + :return: The servo pulse (500 to 2500) of the specified channel. + :rtype: int + + UiFlow2 Code Block: + + |get_servo_pulse.png| + + MicroPython Code Block: + + .. code-block:: python + + servos8_0.get_servo_pulse(0) """ if channel >= 0 and channel <= 7: if SERVO_CTRL_MODE == self.get_mode(channel): - buf = self.servos8_i2c.readfrom_mem( - self.i2c_addr, (SERVO_PULSE_REG + (channel * 2)), 2 - ) + buf = self._i2c.readfrom_mem(self._address, (SERVO_PULSE_REG + (channel * 2)), 2) return struct.unpack(" None: + """Set the RGB LED color of a specific channel. + + :param int rgb: The RGB color value (0x000000 to 0xffffff) to set for the channel. + :param int channel: The channel number (0 to 7) to set the RGB LED color for. + + UiFlow2 Code Block: + + |set_rgb_led.png| + + MicroPython Code Block: + + .. code-block:: python + + servos8_0.set_rgb_led(0xFF0000, 0) """ - r = (rgb >> 16) & 0xFF - g = (rgb >> 8) & 0xFF - b = rgb & 0xFF if channel >= 0 and channel <= 7: if RGB_LED_MODE == self.get_mode(channel): - self.servos8_i2c.writeto_mem( - self.i2c_addr, (RGB_LED_REG + (channel * 3)), bytearray([r, g, b]) + self._i2c.writeto_mem( + self._address, (RGB_LED_REG + (channel * 3)), rgb.to_bytes(3, "big") ) - def get_rgb_led(self, channel): - """ - get rgb led. - channel: 0 to 7 - return : (0 to 255, 0 to 255, 0 to 255) + def get_rgb_led(self, channel: int) -> int: + """Get the RGB LED color of a specific channel. + + :param int channel: The channel number (0 to 7) to get the RGB LED color for. + :return: The RGB color value (0x000000 to 0xffffff) of the specified channel. + :rtype: int + + UiFlow2 Code Block: + + |get_rgb_led.png| + + MicroPython Code Block: + + .. code-block:: python + + servos8_0.get_rgb_led(0) """ if channel >= 0 and channel <= 7: if RGB_LED_MODE == self.get_mode(channel): - return list( - self.servos8_i2c.readfrom_mem(self.i2c_addr, (RGB_LED_REG + (channel * 3)), 3) - ) + buf = self._i2c.readfrom_mem(self._address, (RGB_LED_REG + (channel * 3)), 3) + return int.from_bytes(buf, "big") - def set_pwm_dutycycle(self, duty, channel): - """ - set pwm dutycycle. - duty : 0 to 100 - channel: 0 to 7 + def set_pwm_dutycycle(self, duty: int, channel: int) -> None: + """Set the PWM duty cycle of a specific channel. + + :param int duty: The PWM duty cycle (0 to 100) to set for the channel. + :param int channel: The channel number (0 to 7) to set the PWM duty cycle for. + + UiFlow2 Code Block: + + |set_pwm_dutycycle.png| + + MicroPython Code Block: + + .. code-block:: python + + servos8_0.set_pwm_dutycycle(50, 0) """ if channel >= 0 and channel <= 7: if PWM_DUTY_MODE == self.get_mode(channel): duty = max(min(duty, 100), 0) - self.servos8_i2c.writeto_mem( - self.i2c_addr, (PWM_DUTY_REG + channel), bytearray([duty]) - ) + self._i2c.writeto_mem(self._address, (PWM_DUTY_REG + channel), bytearray([duty])) - def get_input_current(self): - """ - get input current. + def get_input_current(self) -> float: + """Get the input current of the servos 8 unit. + + :return: The input current in Amperes. + :rtype: float + + UiFlow2 Code Block: + + |get_input_current.png| + + MicroPython Code Block: + + .. code-block:: python + + servos8_0.get_input_current() """ - buf = self.servos8_i2c.readfrom_mem(self.i2c_addr, SERVO_CURRENT_REG, 4) + buf = self._i2c.readfrom_mem(self._address, SERVO_CURRENT_REG, 4) return round(struct.unpack("= FW_VER_REG and mode <= I2C_ADDR_REG: - return self.servos8_i2c.readfrom_mem(self.i2c_addr, mode, 1)[0] + return self._i2c.readfrom_mem(self._address, mode, 1)[0] def set_i2c_address(self, addr): - """ - set i2c address. - addr: 1 to 127 + """Set the I2C address of the servos 8 unit. + + :param int addr: The new I2C address (1 to 127) to set for the servos 8 unit. + + UiFlow2 Code Block: + + |set_i2c_address.png| + + MicroPython Code Block: + + .. code-block:: python + + servos8_0.set_i2c_address(0x25) """ if addr >= 1 and addr <= 127: - if addr != self.i2c_addr: - self.servos8_i2c.writeto_mem(self.i2c_addr, I2C_ADDR_REG, bytearray([addr])) - self.i2c_addr = addr + if addr != self._address: + self._i2c.writeto_mem(self._address, I2C_ADDR_REG, bytearray([addr])) + self._address = addr time.sleep_ms(150) From c0b243b4c190894d265f87440031dc8134b29c27 Mon Sep 17 00:00:00 2001 From: Tinyu Date: Wed, 16 Apr 2025 10:48:09 +0800 Subject: [PATCH 047/322] docs: Add ToF4M Unit docs. Signed-off-by: Tinyu --- docs/en/refs/unit.tof4m.ref | 28 + docs/en/units/index.rst | 1 + docs/en/units/tof4m.rst | 66 + .../zh_CN/LC_MESSAGES/base/atom_can.po | 77 +- .../zh_CN/LC_MESSAGES/base/atom_gps.po | 266 ++-- .../zh_CN/LC_MESSAGES/base/atom_socket.po | 131 +- docs/locales/zh_CN/LC_MESSAGES/base/motion.po | 221 +-- .../locales/zh_CN/LC_MESSAGES/hardware/als.po | 76 +- .../zh_CN/LC_MESSAGES/hardware/button.po | 274 ++-- .../locales/zh_CN/LC_MESSAGES/hardware/can.po | 260 ++-- .../zh_CN/LC_MESSAGES/hardware/display.po | 445 +++--- .../locales/zh_CN/LC_MESSAGES/hardware/imu.po | 129 +- .../locales/zh_CN/LC_MESSAGES/hardware/mic.po | 291 ++-- docs/locales/zh_CN/LC_MESSAGES/hats/cardkb.po | 48 +- .../zh_CN/LC_MESSAGES/hats/neoflash.po | 108 +- docs/locales/zh_CN/LC_MESSAGES/hats/servo.po | 114 +- docs/locales/zh_CN/LC_MESSAGES/hats/servo8.po | 188 ++- .../locales/zh_CN/LC_MESSAGES/hats/speaker.po | 76 +- .../zh_CN/LC_MESSAGES/hats/speaker2.po | 54 +- docs/locales/zh_CN/LC_MESSAGES/module/ain4.po | 113 +- .../locales/zh_CN/LC_MESSAGES/module/bala2.po | 351 ++--- .../zh_CN/LC_MESSAGES/module/dualkmeter.po | 200 ++- docs/locales/zh_CN/LC_MESSAGES/module/fan.po | 193 +-- .../zh_CN/LC_MESSAGES/module/gateway_h2.po | 136 +- docs/locales/zh_CN/LC_MESSAGES/module/lora.po | 164 ++- .../zh_CN/LC_MESSAGES/module/lora_sx1262.po | 243 ++-- .../LC_MESSAGES/quick-reference/usb-mode.po | 86 +- .../LC_MESSAGES/software/umqtt.default.po | 166 +-- .../locales/zh_CN/LC_MESSAGES/system/power.po | 404 ++++-- docs/locales/zh_CN/LC_MESSAGES/units/accel.po | 381 +++--- docs/locales/zh_CN/LC_MESSAGES/units/angle.po | 121 +- .../locales/zh_CN/LC_MESSAGES/units/buzzer.po | 142 +- .../zh_CN/LC_MESSAGES/units/bytebutton.po | 225 +-- .../zh_CN/LC_MESSAGES/units/byteswitch.po | 225 +-- docs/locales/zh_CN/LC_MESSAGES/units/dac.po | 220 +-- .../locales/zh_CN/LC_MESSAGES/units/dlight.po | 84 +- docs/locales/zh_CN/LC_MESSAGES/units/earth.po | 130 +- .../zh_CN/LC_MESSAGES/units/encoder.po | 204 ++- docs/locales/zh_CN/LC_MESSAGES/units/env.po | 170 ++- .../zh_CN/LC_MESSAGES/units/flash_light.po | 85 +- docs/locales/zh_CN/LC_MESSAGES/units/light.po | 97 +- .../zh_CN/LC_MESSAGES/units/lora_e220.po | 363 +++-- .../zh_CN/LC_MESSAGES/units/lorawan_rui3.po | 1218 +++++++++-------- .../zh_CN/LC_MESSAGES/units/miniscale.po | 310 +++-- docs/locales/zh_CN/LC_MESSAGES/units/op180.po | 104 +- docs/locales/zh_CN/LC_MESSAGES/units/op90.po | 104 +- docs/locales/zh_CN/LC_MESSAGES/units/rgb.po | 144 +- .../zh_CN/LC_MESSAGES/units/servos8.po | 616 +++++++++ .../zh_CN/LC_MESSAGES/units/thermal.po | 202 ++- docs/locales/zh_CN/LC_MESSAGES/units/tof4m.po | 334 +++++ .../zh_CN/LC_MESSAGES/units/ultrasonic.po | 98 +- .../zh_CN/LC_MESSAGES/units/vibrator.po | 89 +- .../zh_CN/LC_MESSAGES/units/weight_i2c.po | 320 +++-- examples/unit/tof4m/tof4m_core_example.m5f2 | 1 + examples/unit/tof4m/tof4m_core_example.py | 55 + m5stack/libs/driver/vl53l1x.py | 162 ++- 56 files changed, 6847 insertions(+), 4266 deletions(-) create mode 100644 docs/en/refs/unit.tof4m.ref create mode 100644 docs/en/units/tof4m.rst create mode 100644 docs/locales/zh_CN/LC_MESSAGES/units/servos8.po create mode 100644 docs/locales/zh_CN/LC_MESSAGES/units/tof4m.po create mode 100644 examples/unit/tof4m/tof4m_core_example.m5f2 create mode 100644 examples/unit/tof4m/tof4m_core_example.py diff --git a/docs/en/refs/unit.tof4m.ref b/docs/en/refs/unit.tof4m.ref new file mode 100644 index 00000000..bb9e931c --- /dev/null +++ b/docs/en/refs/unit.tof4m.ref @@ -0,0 +1,28 @@ +.. |ToF4M| image:: https://static-cdn.m5stack.com/resource/docs/products/unit/Unit-ToF4M/img-b393dca4-9143-4409-8753-0a776b8fe2a2.webp + :target: https://docs.m5stack.com/en/unit/Unit-ToF4M + :height: 200px + :width: 200px + + +.. |init.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/tof4m/init.png +.. |get_model_info.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/tof4m/get_model_info.png +.. |get_distance.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/tof4m/get_distance.png +.. |set_continuous_start_measurement.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/tof4m/set_continuous_start_measurement.png +.. |set_continuous_stop_measurement.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/tof4m/set_continuous_stop_measurement.png +.. |get_data_ready.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/tof4m/get_data_ready.png +.. |get_measurement_timing_budget.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/tof4m/get_measurement_timing_budget.png +.. |set_measurement_timing_budget.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/tof4m/set_measurement_timing_budget.png +.. |get_distance_mode.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/tof4m/get_distance_mode.png +.. |set_distance_mode.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/tof4m/set_distance_mode.png +.. |set_i2c_address.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/tof4m/set_i2c_address.png + +.. |example.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/tof4m/example.png + +.. |tof4m_core_example.m5f2| raw:: html + + + tof4m_core_example.m5f2 + \ No newline at end of file diff --git a/docs/en/units/index.rst b/docs/en/units/index.rst index 728f9646..c889fb86 100644 --- a/docs/en/units/index.rst +++ b/docs/en/units/index.rst @@ -101,6 +101,7 @@ Unit timerpwr.rst tmos.rst tof.rst + tof4m.rst tvoc.rst uhf_rfid.rst ultrasonic.rst diff --git a/docs/en/units/tof4m.rst b/docs/en/units/tof4m.rst new file mode 100644 index 00000000..91c81022 --- /dev/null +++ b/docs/en/units/tof4m.rst @@ -0,0 +1,66 @@ +ToF4M Unit +========== + +.. sku: U056 + +.. include:: ../refs/unit.tof4m.ref + +This is the driver library of ToF4M Unit, which is used to obtain distance data from the +VL53L1CXV0FY sensor. + +Support the following products: + + |ToF4M| + + +UiFlow2 Example +--------------- + +get distance value +^^^^^^^^^^^^^^^^^^^ + +Open the |tof4m_core_example.m5f2| project in UiFlow2. + +This example gets the distance value of the ToF4M Unit and displays it on the screen. + +UiFlow2 Code Block: + + |example.png| + +Example output: + + None + +MicroPython Example +------------------- + +get distance value +^^^^^^^^^^^^^^^^^^^ + +This example gets the distance value of the ToF4M Unit and displays it on the screen. + +MicroPython Code Block: + + .. literalinclude:: ../../../examples/unit/tof4m/tof4m_core_example.py + :language: python + :linenos: + +Example output: + + None + + +**API** +------- + +TOF4MUnit +^^^^^^^^^ + +.. autoclass:: unit.tof4m.TOF4MUnit + :members: + +VL53L1CXV0FY +^^^^^^^^^^^^^ + +.. autoclass:: driver.vl53l1x.VL53L1X + :members: diff --git a/docs/locales/zh_CN/LC_MESSAGES/base/atom_can.po b/docs/locales/zh_CN/LC_MESSAGES/base/atom_can.po index 007af123..22a266b6 100644 --- a/docs/locales/zh_CN/LC_MESSAGES/base/atom_can.po +++ b/docs/locales/zh_CN/LC_MESSAGES/base/atom_can.po @@ -9,7 +9,7 @@ msgid "" msgstr "" "Project-Id-Version: UIFlow2 Programming Guide \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-02-27 10:42+0800\n" +"POT-Creation-Date: 2025-04-03 10:58+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language: zh_CN\n" @@ -20,134 +20,135 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.16.0\n" -#: ../../en/base/atom_can.rst:2 3497f684611a4999913631a952d2884a -msgid "ATOM CAN Base" +#: ../../en/base/atom_can.rst:2 ../../en/refs/base.can.ref +#: 93fcb5e57ad246b98dcbc5c2d59dd794 d53f875effe54b10bb158978537be511 +msgid "Atomic CAN Base" msgstr "" -#: ../../en/base/atom_can.rst:8 c02f250c268e4453b639a8218138a8da +#: ../../en/base/atom_can.rst:8 5e10243753214e0eb93622a5b795b9de msgid "" "This is the driver library for the ATOM CAN Base to accept and send data " "from the CAN module." msgstr "这是 ATOM CAN Base 的驱动程序库,用于从 CAN 模块接受和发送数据。" -#: ../../en/base/atom_can.rst:10 ea89d5c61f5246cf97315f53b588b80d +#: ../../en/base/atom_can.rst:10 234569d792254d8eb94090923e531d6b msgid "Support the following products:" msgstr "支持以下产品:" -#: ../../en/base/atom_can.rst:13 086e5e12ea6f4a5bac81f44a203124cb +#: ../../en/base/atom_can.rst:13 24c7c0eb485a4c9ca5918eace30cc0e8 msgid "|Atom CAN|" msgstr "" -#: ../../en/refs/base.can.ref b65458c8b6fa4cc88ff761875f435731 +#: ../../en/refs/base.can.ref 14268761d2e24a6792a44eda5b300bde msgid "Atom CAN" msgstr "" -#: ../../en/base/atom_can.rst:13 3cd238e49d914e55981968c19ae028b8 +#: ../../en/base/atom_can.rst:13 78b234cda51f44699fb227ea90d9ba0a msgid "|Atomic CAN Base|" msgstr "" -#: ../../en/refs/base.can.ref 74dc394dfcac47b1a3eae00394413c69 -msgid "Atomic CAN Base" -msgstr "" - -#: ../../en/base/atom_can.rst:18 502ae5c47de645ceb1e42d24ccdf0eaa +#: ../../en/base/atom_can.rst:18 b1bebd8f40c44b308b2a9a2815c1f2a2 msgid "UiFlow2 Example" msgstr "UIFLOW2 应用示例:" #: ../../en/base/atom_can.rst:21 ../../en/base/atom_can.rst:39 -#: 2e83a695c8bc4505a81ed870dcc9d469 +#: 776c522275d744038c9f1340157a7c5b da14563d675f49f6b6e6ad9dc74c07de msgid "CAN communication" msgstr "CAN 通讯" -#: ../../en/base/atom_can.rst:23 1c26c8c40e4c4b1c9bd9213d0d795018 +#: ../../en/base/atom_can.rst:23 d1bb8c5d507d4429987e81a6d0e6e148 msgid "Open the |atoms3_can_example.m5f2| project in UiFlow2." msgstr "在UiFlow2 中打开 |atoms3_can_example.m5f2| 项目。" #: ../../en/base/atom_can.rst:25 ../../en/base/atom_can.rst:41 -#: 77ee4b221bde42809a6fb2d59d6b9661 +#: e3ecc32e387344bb9d9677c03e5a9080 ef26141b675042d682e99bfccf3fb5cc msgid "This example shows how to receive and send data using the Atom CAN Base." msgstr "本示例演示如何使用 Atom CAN Base 接收和发送数据。" -#: ../../en/base/atom_can.rst:27 0468680d4f4640399d5292156a6f0fba -#: base.atom_can.ATOMCANBase:9 of +#: ../../en/base/atom_can.rst:27 b4e5234094d5412c8cf8b2ea0dde3dd8 +#: base.atom_can.ATOMCANBase:9 cc1d3bab7e5340b7a1a8b4348ca95e41 of msgid "UiFlow2 Code Block:" msgstr "UiFlow2 代码块:" -#: ../../en/base/atom_can.rst:29 af7198e3c7b846ad9f45a6a1a0804786 +#: ../../en/base/atom_can.rst:29 89aeb7324e2444609015457c7576a8c1 msgid "|example.png|" msgstr "" -#: ../../en/refs/base.can.ref:13 60ce6c7198e44e37acce71bd652c659d +#: ../../en/refs/base.can.ref:13 ddcac6363695450aa24544e5c4b8eabc msgid "example.png" msgstr "" #: ../../en/base/atom_can.rst:31 ../../en/base/atom_can.rst:49 -#: 1ba9c05d25674a73812f3c8490e4addd +#: 0b13ea5c888b4226898185295d3d18dc 7623729ddc2e470c91550b79731c2f02 msgid "Example output:" msgstr "示例输出:" #: ../../en/base/atom_can.rst:33 ../../en/base/atom_can.rst:51 -#: 1fc543f451b44786bbf3677d6ab05fae e858f1882d0c41ebb04341d3cff6fc40 +#: 245d99db30ae4e80863191653635127a 2a025b31861b4dad8fbcbd2aa7d2eb05 msgid "Output of received CAN message data via serial port." msgstr "通过串行端口输出接收到的 CAN 通讯数据。" -#: ../../en/base/atom_can.rst:36 5dedb1e55d3b4c0f98cb4df2dbfa4903 +#: ../../en/base/atom_can.rst:36 bf1546f071a5493cbee31905e4a83653 msgid "MicroPython Example" msgstr "MicroPython 应用示例:" -#: ../../en/base/atom_can.rst:43 5dedb1e55d3b4c0f98cb4df2dbfa4903 -#: base.atom_can.ATOMCANBase:13 of +#: ../../en/base/atom_can.rst:43 373f89b7191e4aff89c64a33eb63a69d +#: 4a6fe67dea96421fbf09d4202b73dfa5 base.atom_can.ATOMCANBase:13 of msgid "MicroPython Code Block:" msgstr "MicroPython 代码块:" -#: ../../en/base/atom_can.rst:54 d255e6250d644684a26fc7bafe559ad2 +#: ../../en/base/atom_can.rst:54 08cdad95f1fb4f5482c88b232b6a1b7b msgid "**API**" msgstr "" -#: ../../en/base/atom_can.rst:57 045ffa87322a48288254b73ef0e12464 +#: ../../en/base/atom_can.rst:57 0788595b09e44dce80eacd9aee12a3c2 msgid "ATOMCANBase" msgstr "" -#: base.atom_can.ATOMCANBase:1 bc2a7cf8bea3452e923dc2754f730592 of +#: base.atom_can.ATOMCANBase:1 c7b44c329eb1470faf6d88495f5fff14 of msgid "Bases: :py:class:`~m5can.CAN`" msgstr "" -#: ac6ee4c7a7ad415a8182770911bba837 base.atom_can.ATOMCANBase:1 of +#: 8d4ec482e8834d2eaf0a9fc9901852ef base.atom_can.ATOMCANBase:1 of msgid "Create an ATOMCANBase object" msgstr "创建 ATOMCANBase 对象。" -#: base.atom_can.ATOMCANBase bbf4ea362fa442baaf051e7d0279875d of +#: base.atom_can.ATOMCANBase d1542cc40d1d4893b40639aa68156459 of msgid "Parameters" msgstr "参数" -#: 56daed2b74a042aeb5fed675194b8768 base.atom_can.ATOMCANBase:3 of +#: 5ca80b6c1a5f49ae9e89f5b391255dd3 base.atom_can.ATOMCANBase:3 of msgid "The CAN ID to use, Default is 0." msgstr "CAN 通讯使用的 ID 号, 默认为0" -#: 4dc96c0227964a008126b7b398781711 base.atom_can.ATOMCANBase:4 of +#: base.atom_can.ATOMCANBase:4 bc1be12ec5f742c9b6d5437ee3239e52 of msgid "A list or tuple containing the TX and RX pin numbers." msgstr "包含 TX 和 RX 引脚编号的列表或元组。" -#: 095a0ab0ef754842999c491628844929 base.atom_can.ATOMCANBase:6 of +#: 05bdc1f4afc34bdab3d2f0256eeb1a19 base.atom_can.ATOMCANBase:6 of msgid "" "The CAN mode to use(NORMAL, NO_ACKNOWLEDGE, LISTEN_ONLY), Default is " "NORMAL." msgstr "使用的 CAN 模式(NORMAL、NO_ACKNOWLEDGE、LISTEN_ONLY),默认为 NORMAL。" -#: 705fa9fd527045b296731bb06ff2f312 base.atom_can.ATOMCANBase:7 of +#: 82dd11c2dfea4d429b3b24d68f71024f base.atom_can.ATOMCANBase:7 of msgid "The baudrate to use, Default is 1000000." msgstr "使用的波特率,默认为 1000000。" -#: af7198e3c7b846ad9f45a6a1a0804786 base.atom_can.ATOMCANBase:11 of +#: 2ba1be4c47894eb8ae693cd9ac52d066 base.atom_can.ATOMCANBase:11 of msgid "|init.png|" msgstr "" -#: ../../en/refs/base.can.ref:11 1e7e9392cfb24494b38ebdd14473654c +#: ../../en/refs/base.can.ref:11 ba3fc17c04024fa5a8afa763f56efac2 msgid "init.png" msgstr "" -#: ../../en/base/atom_can.rst:62 50f3d420d34241b596cb539ca7b905f0 +#: ../../en/base/atom_can.rst:62 78840d7c2d164b1ca73c224041a66496 msgid "" "ATOMCANBase class inherits CAN class, See :ref:`hardware.CAN " "` for more details." -msgstr "ATOMCANBase 类继承了 CAN 类,详情请参见 :ref:`hardware.CAN `。" \ No newline at end of file +msgstr "ATOMCANBase 类继承了 CAN 类,详情请参见 :ref:`hardware.CAN `。" + +#~ msgid "ATOM CAN Base" +#~ msgstr "" + diff --git a/docs/locales/zh_CN/LC_MESSAGES/base/atom_gps.po b/docs/locales/zh_CN/LC_MESSAGES/base/atom_gps.po index 3608e515..d193563f 100644 --- a/docs/locales/zh_CN/LC_MESSAGES/base/atom_gps.po +++ b/docs/locales/zh_CN/LC_MESSAGES/base/atom_gps.po @@ -9,7 +9,7 @@ msgid "" msgstr "" "Project-Id-Version: UIFlow2 Programming Guide \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-02-26 12:27+0800\n" +"POT-Creation-Date: 2025-04-03 10:58+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language: zh_CN\n" @@ -20,63 +20,63 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.16.0\n" -#: ../../en/base/atom_gps.rst:2 ../../en/refs/base.gps.ref -#: ae26b16a24d94c84a7293460de125856 d2896f4066de4fa1b6486476595e8006 -msgid "ATOM GPS Base" +#: ../../en/base/atom_gps.rst:2 87147a81887c4699a875ef0ee5855b10 +msgid "Atomic GPS Base" msgstr "" -#: ../../en/base/atom_gps.rst:8 087fbc2ff5934fc890dbd4d2dd076a4f +#: ../../en/base/atom_gps.rst:8 098d58489b7a46329ca5e95d16ad6a88 msgid "" "This is the driver library of ATOM GPS Base, which is used to obtain data" " from the GPS module." msgstr "这是 ATOM GPS Base 的驱动程序库,用于从 GPS 模块获取数据。" -#: ../../en/base/atom_gps.rst:11 66133002dbfd41e98ff614ebd7636d15 +#: ../../en/base/atom_gps.rst:11 29ee9523336a4a20aff027a7280e79be msgid "Support the following products:" msgstr "支持以下产品:" -#: ../../en/base/atom_gps.rst:14 97bb503b70da4ae49645896711697de3 +#: ../../en/base/atom_gps.rst:14 15a21b456ab843e6984fc1a7ddc8033a msgid "|ATOM GPS|" msgstr "" -#: ../../en/refs/base.gps.ref 2959460dd10e4ece826cff7106249c7f +#: ../../en/refs/base.gps.ref d57c5d2698cd4aaab4337f54fe6b01d0 msgid "ATOM GPS" msgstr "" -#: ../../en/base/atom_gps.rst:14 d255f92c81104fc6b30ee0c14a80f210 +#: ../../en/base/atom_gps.rst:14 e85a315fb90e4ffe9fed071288ab5365 msgid "|ATOM GPS Base|" msgstr "" -#: ../../en/base/atom_gps.rst:19 cb1416a182c946ae9891d6067b0a75ea +#: ../../en/refs/base.gps.ref beeba9115094455f865933f589e6313f +msgid "ATOM GPS Base" +msgstr "" + +#: ../../en/base/atom_gps.rst:19 f096c038c0ee460c89dfba30b46d8279 msgid "UiFlow2 Example" msgstr "UIFLOW2 应用示例:" #: ../../en/base/atom_gps.rst:22 ../../en/base/atom_gps.rst:40 -#: 3a88477a43b94993a3ecbc2cd1f305f2 96ea060cd8524c749384a711ce3625af +#: 2ff8f09447f54c16a0f5ec4cd8f8b3fe bf95534494b34a6486817a9e57243f04 msgid "get gps data" msgstr "获取 GPS 数据" -#: ../../en/base/atom_gps.rst:24 38a0f91a46b24113b9596df9a1032522 +#: ../../en/base/atom_gps.rst:24 46743341f77c49e3b4ea207e3b03ffed msgid "Open the |atoms3_gps_example.m5f2| project in UiFlow2." msgstr "在UiFlow2 中打开 |atoms3_gps_example.m5f2| 项目。" #: ../../en/base/atom_gps.rst:26 ../../en/base/atom_gps.rst:42 -#: 82ff92fb68ef4d04b74764e6863bedc6 9711921da3d947d5b859fd23d9a0d78f +#: bf07a6d1843e4c9a89d3348b2ab6100a e23c84ed1f9744faa96b6c247cb0025a msgid "" "This example gets the GPS data of the ATOM GPS Base and displays it on " "the serial monitor." msgstr "本示例获取 ATOM GPS Base 的 GPS 数据并将其显示在串口监视器中。" -#: ../../en/base/atom_gps.rst:28 13e81fc2700544e0948427687a159410 -#: 3104be6b4999403f87a51b1a757e3042 3a60008e98204b40956d53fa7e20c3a7 -#: 51c8c12b95444fc4b16d147a1654e23d 53e259eae67f4814a9c176e2330e9c21 -#: 7aea5244f8974d7782e078c9b75d9f34 84c0c780c0bd4d7b926b298249cdcce4 -#: 9358d310d064464da1b343fe63b99591 9ef19b5dfa164e6987fc48d25c748b56 -#: 9f668bb8b540409ca5397fe5692ab2c3 a033d09a54134a458025cf03f1ace335 -#: a2d1f668f8ea4e7f998b31d7f7a321fc a6460e00e2d34f80b68a1901006bd6eb -#: abb84ff5971847128fd96bbc6bf187dc b5e88f318900446ab7a0da5e845741c6 -#: base.atom_gps.ATOMGPSBase:8 base.atom_gps.ATOMGPSBase.deinit:3 -#: base.atom_gps.ATOMGPSBase.get_altitude:6 +#: ../../en/base/atom_gps.rst:28 114b7fe821ac461eac8a5ca0d21d6905 +#: 17d30f8aa45d4fb5b0b6f3007e0118a7 2326844163014db899d24b006df97443 +#: 27d6c568f7e848d1902b2ab381c190f8 31a39484dbce46f2a24fa4e620d0af32 +#: 35588ad6c61a4314aae2e06ec6cfc156 631cf3c365214461baf0fe3795302af8 +#: 9429733e77484ae9810e182a9300dfde abf1c6709412421aa94846f77407a7d3 +#: b6e8402871924bab96e34e260b28fe9d base.atom_gps.ATOMGPSBase:8 +#: base.atom_gps.ATOMGPSBase.deinit:3 base.atom_gps.ATOMGPSBase.get_altitude:6 #: base.atom_gps.ATOMGPSBase.get_antenna_state:6 #: base.atom_gps.ATOMGPSBase.get_corse_over_ground:6 #: base.atom_gps.ATOMGPSBase.get_gps_date:6 @@ -89,41 +89,41 @@ msgstr "本示例获取 ATOM GPS Base 的 GPS 数据并将其显示在串口监 #: base.atom_gps.ATOMGPSBase.get_speed_over_ground:6 #: base.atom_gps.ATOMGPSBase.get_time_zone:6 #: base.atom_gps.ATOMGPSBase.get_timestamp:6 -#: base.atom_gps.ATOMGPSBase.set_time_zone:5 d39b92fd99854b47a3400f4255cae1d2 -#: d7dc4fb641cf4bfb929c42b06b42d3e6 of +#: base.atom_gps.ATOMGPSBase.set_time_zone:5 c2813af37a2248a0be404bb2e68929b5 +#: c75db696c3dc47c78a6f0ea2b69ecb9c c904db163bf24db18c0a238899880781 +#: e0b72576875c46d89de8a95e90e72519 e158927ffbcd48f2be782c51d4baf2c5 +#: e3ee841109da458c9cba1ee92a3de2b7 fd1ad6e840094cf59ee048e10913be59 of msgid "UiFlow2 Code Block:" msgstr "UiFlow2 代码块:" -#: ../../en/base/atom_gps.rst:30 367cd78e8da64f5782cb8a2ae3058ea5 +#: ../../en/base/atom_gps.rst:30 b808bd4aa0944631830dc9ad1672c0ea msgid "|example.png|" msgstr "" -#: ../../en/refs/base.gps.ref:28 40d27002d4d74bd3924fb94dcfe02646 +#: ../../en/refs/base.gps.ref:28 1e40bf1040e24f2ea0c1630bd3e12094 msgid "example.png" msgstr "" #: ../../en/base/atom_gps.rst:32 ../../en/base/atom_gps.rst:50 -#: 77823a9eeac14d88980d28f6aabbe4c5 7e72e0c966804a95b784529eef402546 +#: 7ee1893e8c124c93ac26a051f6d20356 b03805d3c4da43a38e0f2ac6c08a0836 msgid "Example output:" msgstr "示例输出:" #: ../../en/base/atom_gps.rst:34 ../../en/base/atom_gps.rst:52 -#: 6b4dbb4b640c496690f55ac20b660cd5 765afd85fd0f4411a9abde657402221b +#: 187530b228cb4c6ba06f93d00e381a95 8d4982a10c594752aa0e98ef53b73952 msgid "None" msgstr "" -#: ../../en/base/atom_gps.rst:37 41cd0a3aa72e4957a7b039cd13bc6584 +#: ../../en/base/atom_gps.rst:37 da52a295136b47ecb4a70d6f77a04ef2 msgid "MicroPython Example" msgstr "MicroPython 应用示例:" -#: ../../en/base/atom_gps.rst:44 048885f83d8f4f65ae3d5abe4485fe6e -#: 050b6c9716034e43b1bb190f9d28c3a2 1a30f417d3274c82b78819c2aa834200 -#: 1ef770333d3d47e1acee616b194db5a7 2a999dfb54144b55a23b1df6bff84b25 -#: 357012164ff041e4b64438720aea6c68 4f6eafd292894d4b9a61ad904c906a65 -#: 5a3dda96331e417c96a353a23714ac1a 8a4c2c65864d4b61a4b24d841051160a -#: 8b6620a63bde41289d1ea4ef2c8954d1 98bb71b1a82647949ef8febee74099b7 -#: 9f5101c120c54c1b87fbea33fe1c981a b27211e92a324e0b976b528fedb4da02 -#: b98dd29c84d148d7b888b53f1daab7e3 base.atom_gps.ATOMGPSBase:12 +#: ../../en/base/atom_gps.rst:44 1f580c3a690e42368bb0d7bec4967770 +#: 41937545a9d742d2bc56b4fde4f543ae 4cd7a9d4955946dab9b17a5cec3f846b +#: 50a3466817724338b3b4ad14b7ea8503 6464d1bec39c45618e91e9f3587b6655 +#: 6d3b97ed29cf4f9299c690077074fdca 75a838e623d7497d9370226907dd6381 +#: 7f444b1e7fd84dd1a9c8ba9aaaa8cc50 89cfc9baefce4ec980a67220586a2899 +#: 8f5c92c600764e289166ecf9f5d66c63 base.atom_gps.ATOMGPSBase:12 #: base.atom_gps.ATOMGPSBase.deinit:7 base.atom_gps.ATOMGPSBase.get_altitude:10 #: base.atom_gps.ATOMGPSBase.get_antenna_state:10 #: base.atom_gps.ATOMGPSBase.get_corse_over_ground:10 @@ -137,72 +137,75 @@ msgstr "MicroPython 应用示例:" #: base.atom_gps.ATOMGPSBase.get_speed_over_ground:10 #: base.atom_gps.ATOMGPSBase.get_time_zone:10 #: base.atom_gps.ATOMGPSBase.get_timestamp:10 -#: base.atom_gps.ATOMGPSBase.set_time_zone:9 c1388af8a05f40cfa540817b963f8711 -#: c5c36bb92ba949d29275cc65e0bd7624 ff33ea1d25924de7a05be5304638b3e7 of +#: base.atom_gps.ATOMGPSBase.set_time_zone:9 bf28fdeacba040fcaebfcafc1705f373 +#: c15e351925984e9689114cb3864a7bb3 e7aa92dff6d54d4da4fbc74867be2ede +#: ef27c1c01a9f4427b31f65d137d451cc ef43067407fe45c8a469700f5062235c +#: fb326fce20d8448890a9ee4259273f77 fbd858eeb5ae4c80b6ab5aa934d440a2 of msgid "MicroPython Code Block:" msgstr "MicroPython 代码块:" -#: ../../en/base/atom_gps.rst:56 6bd51aefdbbf497b9b1ab99fd22af814 +#: ../../en/base/atom_gps.rst:56 66d5f13b637c445f86978f61c1f3ab99 msgid "**API**" msgstr "" -#: ../../en/base/atom_gps.rst:59 6d35355e9dbd4788b2acf836145b447a +#: ../../en/base/atom_gps.rst:59 88796625bd7248a7a36b939ae80044dc msgid "ATOMGPSBase" msgstr "" -#: base.atom_gps.ATOMGPSBase:1 d10eb819084046b18d43b3d64c5669f6 of +#: 1b1877f02f5949139ad2b12ffe76c109 base.atom_gps.ATOMGPSBase:1 of msgid "Bases: :py:class:`object`" msgstr "" -#: 331e93929781462ead9f1277fdb44868 base.atom_gps.ATOMGPSBase:1 of +#: base.atom_gps.ATOMGPSBase:1 eee9c7bb6e314191a55891d50ae236b3 of msgid "Create an ATOMGPSBase object." msgstr "创建 ATOMGPSBase 对象。" -#: ../../en/base/atom_gps.rst 03585621341949d596c274f310acdbd3 -#: 26a3ec31c255447a98c2b48d383906f5 base.atom_gps.ATOMGPSBase.set_time_zone of +#: ../../en/base/atom_gps.rst 7cd5f614596643cfaad69f2be8554d44 +#: adcc30720f5d4239a39e526e2ebdb377 base.atom_gps.ATOMGPSBase.set_time_zone of msgid "Parameters" msgstr "参数" -#: 15f8f669a30b4080a187b6d2d3fafa95 base.atom_gps.ATOMGPSBase:3 of +#: 41b7151408234e6eb9a133a583271d4e base.atom_gps.ATOMGPSBase:3 of msgid "The UART ID to use (0, 1, or 2). Default is 2." msgstr "UART ID 号可使用 0, 1, 2, 默认使用2。" -#: 4269dbb2279b48c08cc15a916311f700 base.atom_gps.ATOMGPSBase:4 of +#: 9bbdd6a739504025b06dd4010bcb9427 base.atom_gps.ATOMGPSBase:4 of msgid "A list or tuple containing the TX and RX pin numbers." msgstr "包含 TX 和 RX 引脚编号的列表或元组。" -#: base.atom_gps.ATOMGPSBase:6 d747e7f33e0440928288dc9398ec8fef of +#: base.atom_gps.ATOMGPSBase:6 dd1937e3696641fb8933e73d343041f8 of msgid "Whether to enable debug mode. Default is False." msgstr "是否启用调试模式, 默认为不开启。" -#: 5a1a3ec0029a43879fb7bbf8e8ea7a8c base.atom_gps.ATOMGPSBase:10 of +#: 8a7ee7d95510424db512de4cc2b3512f base.atom_gps.ATOMGPSBase:10 of msgid "|init.png|" msgstr "" -#: ../../en/refs/base.gps.ref:11 e5fd76a4b9f3458cafff9d97fe618ae6 +#: ../../en/refs/base.gps.ref:11 804d85194d254ca4967d55a718408475 msgid "init.png" msgstr "" -#: base.atom_gps.ATOMGPSBase.deinit:1 ee9a5b1e3e44470bab287ba831670c7a of +#: a73edf41bd93428f9980182c7740777e base.atom_gps.ATOMGPSBase.deinit:1 of msgid "" "Deinitialize the GPS unit, stopping any running tasks and releasing " "resources." msgstr "取消 GPS 设备的初始化,停止正在运行的任务并释放资源。" -#: 6ea970e318614e4ca4418133e3ff15c8 base.atom_gps.ATOMGPSBase.deinit:5 of +#: 954685b30a4c471d841ad3d840c9c49f base.atom_gps.ATOMGPSBase.deinit:5 of msgid "|deinit.png|" msgstr "" -#: ../../en/refs/base.gps.ref:26 076fa6176488403996331203dcb589d9 +#: ../../en/refs/base.gps.ref:26 ed91dee8002b43148b6dc166d647d3b0 msgid "deinit.png" msgstr "" -#: 09898585002b44218a54ba582e8e0493 108c2887936f4f0f9428f0d6332aeba5 -#: 115a2941b6c3463b88a40541c010ffba 29b6a3672d614d328addab9cbf893a2c -#: 31cfc2a7f67d4199b1c028e9f29e51ef 4607ee80996f450bbcc98d90b7b45b13 -#: 4d6520c6f9a44b55a4fbc9ce2918e4a7 8ffe8626046b4fb19cc698195605c1c7 -#: 9afc97a1ac2145af863101339a494fb1 base.atom_gps.ATOMGPSBase.deinit -#: base.atom_gps.ATOMGPSBase.get_altitude +#: 0c1e17732b7645388f7ab724dd789888 0c81a84577c04a518023d8b919e21a9e +#: 0d87e273125245bd952cff04211665ba 0dd3356a614143edab8436279526531e +#: 1a0e8e23702f4c429c72c17786f52587 6986ef91ca7e4fc89a016a7d5722ad1d +#: 6a039f3d91c344b4a686637cbcde0220 7b6bc88c5507484c8033191c867ed61f +#: 8af175e7d84a4454a0aa38325118a226 9a0f9d24c9444d6280e7b3d65b949bfb +#: 9ed2ff25a8604d98a63a6d22d382ada7 a350fa9fe10c416380b651928de04a26 +#: base.atom_gps.ATOMGPSBase.deinit base.atom_gps.ATOMGPSBase.get_altitude #: base.atom_gps.ATOMGPSBase.get_antenna_state #: base.atom_gps.ATOMGPSBase.get_corse_over_ground #: base.atom_gps.ATOMGPSBase.get_gps_date @@ -215,23 +218,19 @@ msgstr "" #: base.atom_gps.ATOMGPSBase.get_speed_over_ground #: base.atom_gps.ATOMGPSBase.get_time_zone #: base.atom_gps.ATOMGPSBase.get_timestamp -#: base.atom_gps.ATOMGPSBase.set_time_zone c056553bce8a41138d6826c3f23d3d33 -#: c896b37b76644b3eab005c6db1307342 d2b550c0b2d24c0090426719c6c6778b -#: e547ee63abc542bc951eafe605eb39af fb75237af2e14016ba60cf1e6eefeccf -#: fcdc848aac204c148179e1803d008993 of +#: base.atom_gps.ATOMGPSBase.set_time_zone bec5d48a107d4d9cb9262c11ed6d2ead +#: c6b258ad2eca4d2b90e2325fdb300188 f19efcb45b27409ab37a06881eea407b of msgid "Return type" msgstr "返回类型" -#: 720e251c6eea431baca3ec03eae17694 base.atom_gps.ATOMGPSBase.get_altitude:1 of +#: 17a92fb5bf6c448a97b89e7b375d77fe base.atom_gps.ATOMGPSBase.get_altitude:1 of msgid "Get the current altitude." msgstr "获取当前的海拔高度。" -#: 17b0b660aba9469295ee82f0bd75b6aa 330f865411a046368498c037e62f6969 -#: 4730e0a4ffb44931a37a298eacffff0b 51dd39b34a69404d9dee89105d4a5b0a -#: 5a049269aacc41a184503e9d88354171 5d938d428a41460a965d3861ca3c24ee -#: 5db58bf9517b43f08e45c35a0af1b4f2 71723bc0f42e4ec0af3c7ee400cdc593 -#: 7f464998851d4cf4ad34cfe1c5d1c449 98e963e37126405ca9a996478a8fc8d2 -#: 9fc92b42bbb0428ca1230fa20e129314 a42a4d1869444688a7c9ab6849985bfe +#: 029a1691fbe34f1d96509489a5e9a6f3 03caeae8412b46aba354d3651409f146 +#: 45b4f8e972cb4b68a30e9c87b6169769 502160b9a5a14eec8dc35f9eb0a7d487 +#: 7200135e57954148869cf25d3144545b 73d7a69d62c84325b42b947ce2964aec +#: 7c612f3de7cf487397079e746d8a4337 871259028f24488d8bbc3184325ae1a8 #: base.atom_gps.ATOMGPSBase.get_altitude #: base.atom_gps.ATOMGPSBase.get_antenna_state #: base.atom_gps.ATOMGPSBase.get_corse_over_ground @@ -244,258 +243,261 @@ msgstr "获取当前的海拔高度。" #: base.atom_gps.ATOMGPSBase.get_satellite_num #: base.atom_gps.ATOMGPSBase.get_speed_over_ground #: base.atom_gps.ATOMGPSBase.get_time_zone -#: base.atom_gps.ATOMGPSBase.get_timestamp da6fa238bd274caea42a8f29506ad347 of +#: base.atom_gps.ATOMGPSBase.get_timestamp c2b3ee8a91234768a488c6f0ac71b90b +#: cdbba7f177f143009eaf5f0cc8ec8b97 d4787276e9c34774865bce4c76d5cc9d +#: e587e40e935e413c9155267bd467d2d6 ee01293ec8fb4211b5e0f34cff918aff of msgid "Returns" msgstr "返回值" -#: 726b6100c1c04a539e50a254cc4a0cd5 base.atom_gps.ATOMGPSBase.get_altitude:3 of +#: 17ddc83b4ba542c9b7ff3f5bd8cee7be base.atom_gps.ATOMGPSBase.get_altitude:3 of msgid "The current altitude." msgstr "获取当前的海拔高度。" -#: 959889e786c6491eae2c3a7df40e4d76 base.atom_gps.ATOMGPSBase.get_altitude:8 of +#: 2cf562d680f148c6ab5febb740c79927 base.atom_gps.ATOMGPSBase.get_altitude:8 of msgid "|get_altitude.png|" msgstr "" -#: ../../en/refs/base.gps.ref:19 914abf1f34bc4ad9a5baa21523a36cf8 +#: ../../en/refs/base.gps.ref:19 03b3a5b8a55246b9a3a88bc5479daa93 msgid "get_altitude.png" msgstr "" -#: 2ce191cfedc649dfac2df0d3e668d757 -#: base.atom_gps.ATOMGPSBase.get_antenna_state:1 of +#: base.atom_gps.ATOMGPSBase.get_antenna_state:1 +#: ef2151cbfc5c4c008a8d80ec8ff1360b of msgid "Get the state of the antenna." msgstr "获取当前的天线状态。" #: base.atom_gps.ATOMGPSBase.get_antenna_state:3 -#: e732a9dfd8444086b018028471396a48 of +#: de9ef76869df450c8ed8e1ca38b6ca71 of msgid "The current antenna state." msgstr "当前的天线状态。" #: base.atom_gps.ATOMGPSBase.get_antenna_state:8 -#: d897114b5d704acea37463569b2c5fe9 of +#: ddaa6c12329b468a8d8c8662b7be8f63 of msgid "|get_antenna_state.png|" msgstr "" -#: ../../en/refs/base.gps.ref:12 25aa6fc782b34154afb86feec28d1793 +#: ../../en/refs/base.gps.ref:12 2ad2a8711d6346a7a4ef3e18ce12b297 msgid "get_antenna_state.png" msgstr "" -#: 3ec328e0fbb847c782747c249cbd630f +#: 479a722257384e41935ebc5f063ab370 #: base.atom_gps.ATOMGPSBase.get_corse_over_ground:1 of msgid "Get the course over ground (COG)." msgstr "获取当前相对地面真航向。" -#: 4b914f47474240e8ab7742b84a12711f +#: 1e0794ddd91244999275cd8146b84b89 #: base.atom_gps.ATOMGPSBase.get_corse_over_ground:3 of msgid "The course over ground in degrees." msgstr "相对地面真航向。" #: base.atom_gps.ATOMGPSBase.get_corse_over_ground:8 -#: ce964f8e68e14cd0acdde5237f9a9104 of +#: d70b8f6732df4f4db84d2369808be94a of msgid "|get_corse_over_ground.png|" msgstr "" -#: ../../en/refs/base.gps.ref:22 b0c9740d7c1340039e364560cf3e7773 +#: ../../en/refs/base.gps.ref:22 e92764dba90c4d029d06ff8c796aa4ba msgid "get_corse_over_ground.png" msgstr "" -#: 557cc3391f5d4d1f85f59d7dfd15ed88 base.atom_gps.ATOMGPSBase.get_gps_date:1 of +#: 6cddfe2dc6824b8ea21cb8fdda166b57 base.atom_gps.ATOMGPSBase.get_gps_date:1 of msgid "Get the current GPS date." msgstr "获取当前的 GPS 日期。" -#: base.atom_gps.ATOMGPSBase.get_gps_date:3 dead6936ee464f4480cab19f79afb459 of +#: 66c619671fbf4044bcfe4e5f93b05a28 base.atom_gps.ATOMGPSBase.get_gps_date:3 of msgid "The GPS date as a list of strings [day, month, year]." msgstr "以字符串 [日、月、年] 列表形式显示的 GPS 日期。" -#: 228fa69c32ce41a8a1b481aa09d8d92a base.atom_gps.ATOMGPSBase.get_gps_date:8 of +#: b5bbf0e94b1f4f908cf64d14a03508e3 base.atom_gps.ATOMGPSBase.get_gps_date:8 of msgid "|get_gps_date.png|" msgstr "" -#: ../../en/refs/base.gps.ref:14 eb257b84232d41e8a3869341464b1972 +#: ../../en/refs/base.gps.ref:14 b5ac964fe5514e98a31971358e1b3203 msgid "get_gps_date.png" msgstr "" -#: 29a6456fe7bb4a9e9ffc7945275b25a1 +#: 3ca695fb7c7244ec87c75bd28b3cdf6c #: base.atom_gps.ATOMGPSBase.get_gps_date_time:1 of msgid "Get the current GPS date and time combined." msgstr "获取当前 GPS 日期和时间。" -#: 47ecc9363f784d5496e5d68ac70601ac +#: 844298eb14454dc3bcc548390b9244b4 #: base.atom_gps.ATOMGPSBase.get_gps_date_time:3 of msgid "" "The GPS date and time as a list of strings [year, month, day, hour, " "minute, second]." msgstr "GPS 日期和时间的字符串列表 [年、月、日、时、分、秒]。" -#: a34ef74ff0ca45abb309e03c7ed01da2 -#: base.atom_gps.ATOMGPSBase.get_gps_date_time:8 of +#: base.atom_gps.ATOMGPSBase.get_gps_date_time:8 +#: c6ca972addc54e39afea559cbc387977 of msgid "|get_gps_date_time.png|" msgstr "" -#: ../../en/refs/base.gps.ref:15 19a270d194dc4af395d3488f94dbc432 +#: ../../en/refs/base.gps.ref:15 7bed6383a3ee4fc786509c965f3078be msgid "get_gps_date_time.png" msgstr "" -#: base.atom_gps.ATOMGPSBase.get_gps_time:1 f531990d1bfc48e3b2081177abc192c9 of +#: 87cf4f225e3442148e8724fe19b0c881 base.atom_gps.ATOMGPSBase.get_gps_time:1 of msgid "Get the current GPS time." msgstr "获取当前 GPS 时间。" -#: 84493e6ecc9743cda453658a2e8e6722 base.atom_gps.ATOMGPSBase.get_gps_time:3 of +#: 2df9ad658e624648b78d146dfe6fb91c base.atom_gps.ATOMGPSBase.get_gps_time:3 of msgid "The GPS time as a list of strings [hour, minute, second]." msgstr "以 [时、分、秒] 字符串列表形式显示的 GPS 时间。" -#: 57d068ea736d43dd9d3f5c560db160dd base.atom_gps.ATOMGPSBase.get_gps_time:8 of +#: 8206a7bc72a44bd4887aaba981c55031 base.atom_gps.ATOMGPSBase.get_gps_time:8 of msgid "|get_gps_time.png|" msgstr "" -#: ../../en/refs/base.gps.ref:13 a9878dcea77e4690b1336305e1f60e19 +#: ../../en/refs/base.gps.ref:13 f55eb81ff98f4e14984f98ff40d32020 msgid "get_gps_time.png" msgstr "" -#: 446012fd8993421299eb4c71b2b593d1 base.atom_gps.ATOMGPSBase.get_latitude:1 of +#: 6d3d9c6ff29a470aac763090b44f6a13 base.atom_gps.ATOMGPSBase.get_latitude:1 of msgid "Get the current latitude." -msgstr 获取当前的海拔高度"" +msgstr "取当前的海拔高度\"" -#: a29a1497806443e884fd7129b2820434 base.atom_gps.ATOMGPSBase.get_latitude:3 of +#: 6890b09d25ff4767a54ec10082882e99 base.atom_gps.ATOMGPSBase.get_latitude:3 of msgid "The current latitude." msgstr "当前的海拔高度。" -#: 486e0d9de051479fbd2a2cbb9cb8d512 base.atom_gps.ATOMGPSBase.get_latitude:8 of +#: a868327f815e4d8e85a553d62f07ba68 base.atom_gps.ATOMGPSBase.get_latitude:8 of msgid "|get_latitude.png|" msgstr "" -#: ../../en/refs/base.gps.ref:17 8e201015417248f2b2f466bba2930286 +#: ../../en/refs/base.gps.ref:17 f0d740c898854518b7edc4c73b363e4f msgid "get_latitude.png" msgstr "" -#: 434ee14c762e48febe6f0aaa98032876 base.atom_gps.ATOMGPSBase.get_longitude:1 +#: base.atom_gps.ATOMGPSBase.get_longitude:1 ef12b011817242ac9d2810e6624b0d27 #: of msgid "Get the current longitude." msgstr "获取当前纬度。" -#: 468b23c4bf694152a2675e208b97ef15 base.atom_gps.ATOMGPSBase.get_longitude:3 +#: 53e96feee6de4090ab5b5e30085b9ef4 base.atom_gps.ATOMGPSBase.get_longitude:3 #: of msgid "The current longitude." msgstr "获取当前纬度。" -#: 783dfce7b87f438c8cd35542e76eb493 base.atom_gps.ATOMGPSBase.get_longitude:8 +#: 7f69a30af4284c4f8491aec1da1bbc55 base.atom_gps.ATOMGPSBase.get_longitude:8 #: of msgid "|get_longitude.png|" msgstr "" -#: ../../en/refs/base.gps.ref:18 3ba59dcb7bc841439cb28dca77c05efa +#: ../../en/refs/base.gps.ref:18 bb4532169fd9449db097bf9ff67ff654 msgid "get_longitude.png" msgstr "" -#: base.atom_gps.ATOMGPSBase.get_pos_quality:1 eedaf69284d64bcf93dec4712d85473a +#: 4c74be81dbf54e298add3c525053f688 base.atom_gps.ATOMGPSBase.get_pos_quality:1 #: of msgid "Get the quality of the GPS position." msgstr "获取 GPS 定位质量。" -#: 61f402d5d85e4f9eae22c78898b0ba80 base.atom_gps.ATOMGPSBase.get_pos_quality:3 +#: 2d3bef7cdc974c569d74e6a5f3680f55 base.atom_gps.ATOMGPSBase.get_pos_quality:3 #: of msgid "The position quality indicator." msgstr "位置质量指标。" -#: 57afbd466c444eff8a34707470bb07c1 base.atom_gps.ATOMGPSBase.get_pos_quality:8 +#: 1e11850a75d848aa9252e72ed4fa45c6 base.atom_gps.ATOMGPSBase.get_pos_quality:8 #: of msgid "|get_pos_quality.png|" msgstr "" -#: ../../en/refs/base.gps.ref:21 9dc11224817842f5bb23ec03a3d48bb4 +#: ../../en/refs/base.gps.ref:21 4fc2a7ad987946b78f708ebd0f6945b7 msgid "get_pos_quality.png" msgstr "" -#: 42c972240dcb418dbb8ba65cb8f0ff40 +#: 31dca0891c7e4b0d91db6e1dd4807578 #: base.atom_gps.ATOMGPSBase.get_satellite_num:1 of msgid "Get the number of satellites used for positioning." msgstr "获取用于定位的卫星数量。" -#: base.atom_gps.ATOMGPSBase.get_satellite_num:3 -#: c7b31f9793964c748ed4d65bd0e6e859 of +#: 92b18b0c700d4f78ac845309f4bcaefb +#: base.atom_gps.ATOMGPSBase.get_satellite_num:3 of msgid "The number of satellites." msgstr "卫星数量。" -#: 4ba321178c5b45378fb57d23fc4af27c +#: b7069433eb3348a285f31b6e1f36edaa #: base.atom_gps.ATOMGPSBase.get_satellite_num:8 of msgid "|get_satellite_num.png|" msgstr "" -#: ../../en/refs/base.gps.ref:20 6b1e43bdb5ca4743945821119a83fc5b +#: ../../en/refs/base.gps.ref:20 becfb2be19224484890d9043746cbc87 msgid "get_satellite_num.png" msgstr "" -#: 44ede812e16748fc89491efcdfe0c54b +#: 4a7295a99dda4b099e348eb885e1b4f7 #: base.atom_gps.ATOMGPSBase.get_speed_over_ground:1 of msgid "Get the speed over ground (SOG)." msgstr "获取获取地面速度。" -#: 15a64a344b6a4b96a0156218538847a7 +#: 644a4b6a8b9e4131b46c20109b554cd8 #: base.atom_gps.ATOMGPSBase.get_speed_over_ground:3 of msgid "The speed over ground in knots." msgstr "对地面速度,单位为节。" -#: 2659e1f8497b4fc0abe086e8d41eb5e1 +#: 477aa7d7d6f440a7bad6a13269f986a3 #: base.atom_gps.ATOMGPSBase.get_speed_over_ground:8 of msgid "|get_speed_over_ground.png|" msgstr "" -#: ../../en/refs/base.gps.ref:23 86a1795ff90e435da82e0f7aae5cb6bb +#: ../../en/refs/base.gps.ref:23 203c30e981a74652aeaefaf0401ec5d5 msgid "get_speed_over_ground.png" msgstr "" -#: b4ceebc8cb6241a39fb1079a45796d4a base.atom_gps.ATOMGPSBase.get_time_zone:1 +#: 5655545561a247d99b9f9d96ef948024 base.atom_gps.ATOMGPSBase.get_time_zone:1 #: of msgid "Get the current time zone offset." msgstr "获取当前时区偏移。" -#: 0e8d89e27feb42689ebea5f7879cd039 base.atom_gps.ATOMGPSBase.get_time_zone:3 +#: 70194a5ad0d44fc0868023b05d5a0705 base.atom_gps.ATOMGPSBase.get_time_zone:3 #: of msgid "The current time zone offset." msgstr "当前时区偏移。" -#: 5dcc2dd2904c4f5d951050eeb8ea9907 base.atom_gps.ATOMGPSBase.get_time_zone:8 +#: 81857072787f4134a8b43b23c58bc2ed base.atom_gps.ATOMGPSBase.get_time_zone:8 #: of msgid "|get_time_zone.png|" msgstr "" -#: ../../en/refs/base.gps.ref:25 fc4913211ce04b3c93fe459ac7ab3eee +#: ../../en/refs/base.gps.ref:25 8530dbed25484ea88e7c34390850bcad msgid "get_time_zone.png" msgstr "" -#: 5f11758ac891478681795f0182948484 base.atom_gps.ATOMGPSBase.get_timestamp:1 +#: base.atom_gps.ATOMGPSBase.get_timestamp:1 d6aaefe7af2f4526b00b877184c2f4b3 #: of msgid "Get the timestamp of the current GPS time." msgstr "获取当前 GPS 时间的时间戳。" -#: 68c6632aa41944ce883ec9b691e736ee base.atom_gps.ATOMGPSBase.get_timestamp:3 +#: base.atom_gps.ATOMGPSBase.get_timestamp:3 d2e094025ec44548bd3fe651185dcab8 #: of msgid "The timestamp representing the current GPS time." msgstr "代表当前 GPS 时间的时间戳。" -#: base.atom_gps.ATOMGPSBase.get_timestamp:8 d62421f2eef146a297a8d25c1c41240d +#: 93b323da85ef44c2a9356da5f0bef1c7 base.atom_gps.ATOMGPSBase.get_timestamp:8 #: of msgid "|get_timestamp.png|" msgstr "" -#: ../../en/refs/base.gps.ref:16 274ccc6eb5334b75aeec2a3127200eb1 +#: ../../en/refs/base.gps.ref:16 c3ad188011c44ed89e83222cebf6155b msgid "get_timestamp.png" msgstr "" -#: base.atom_gps.ATOMGPSBase.set_time_zone:1 fd41f168af0f453883694b1014e66c53 +#: base.atom_gps.ATOMGPSBase.set_time_zone:1 f938b9b384694f9581a6816480d917a9 #: of msgid "Set the time zone offset for the GPS time." msgstr "设置 GPS 时间的时区偏移。" -#: 18a4984a456d45439d8d1e40ae086eb2 base.atom_gps.ATOMGPSBase.set_time_zone:3 +#: 4c984b660eff401c97d11ee8d329fca1 base.atom_gps.ATOMGPSBase.set_time_zone:3 #: of msgid "The time zone offset value to set." msgstr "要设置的时区偏移值。" -#: 7f6b8b74fbb0466d8479f20ed822d91a base.atom_gps.ATOMGPSBase.set_time_zone:7 +#: 4b9e0baffc0e4b7cbfae90c4378e5e64 base.atom_gps.ATOMGPSBase.set_time_zone:7 #: of msgid "|set_time_zone.png|" msgstr "" -#: ../../en/refs/base.gps.ref:24 f88edec2b13d47efb69c360740baadd9 +#: ../../en/refs/base.gps.ref:24 1e957ffc30d14893af6c8d8e4564227b msgid "set_time_zone.png" -msgstr "" \ No newline at end of file +msgstr "" + diff --git a/docs/locales/zh_CN/LC_MESSAGES/base/atom_socket.po b/docs/locales/zh_CN/LC_MESSAGES/base/atom_socket.po index 50554c7b..49abfb4a 100644 --- a/docs/locales/zh_CN/LC_MESSAGES/base/atom_socket.po +++ b/docs/locales/zh_CN/LC_MESSAGES/base/atom_socket.po @@ -9,7 +9,7 @@ msgid "" msgstr "" "Project-Id-Version: UIFlow2 Programming Guide \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-01-16 15:35+0800\n" +"POT-Creation-Date: 2025-04-03 10:58+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language: zh_CN\n" @@ -20,11 +20,11 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.16.0\n" -#: ../../en/base/atom_socket.rst:2 c7d167983edd43509eaf44220dd73567 -msgid "ATOM Socket Base" +#: ../../en/base/atom_socket.rst:2 5ab7293f377c48ac84b97a55d1f37a88 +msgid "Atom Socket Base" msgstr "" -#: ../../en/base/atom_socket.rst:6 25964e0f33084c0a8a07ec6ecc6651d6 +#: ../../en/base/atom_socket.rst:6 34d817fa6406442680a58af2a55e8eae msgid "" "The `ATOMSocketBase` class is a smart power socket integrated with the M5" " ATOM controller. It features a built-in HLW8032 high-precision power " @@ -34,59 +34,59 @@ msgid "" "in smart homes, industrial control, and energy management." msgstr "" -#: ../../en/base/atom_socket.rst:8 18d35ce0d9554025a2f67eeaecf8fe16 +#: ../../en/base/atom_socket.rst:8 5186ead62819472e8feb240f95df2131 msgid "Supports the following products:" msgstr "" -#: ../../en/base/atom_socket.rst:10 94c2d25d98244e099a049650c6ff9a9d +#: ../../en/base/atom_socket.rst:10 62acfe71b1c44436a003f74915487339 msgid "|ATOMSocketBase|" msgstr "" -#: ../../en/refs/base.atom_socket.ref dfe86b9a614e41e183e24c610bbe4c23 +#: ../../en/refs/base.atom_socket.ref a22ea9289d1c4f0493e8db918e4b36f7 msgid "ATOMSocketBase" msgstr "" -#: ../../en/base/atom_socket.rst:13 3afc5578b2c343e69a29fa9e12bf1a31 +#: ../../en/base/atom_socket.rst:13 4d4094c3b7634e9aafc08fc4ca4860ff msgid "Micropython Example:" msgstr "" -#: ../../en/base/atom_socket.rst:20 f04e67a3dcf0474ab3aa491b237cb630 +#: ../../en/base/atom_socket.rst:20 8c36750ea4684fe4a2b0c40d50e0f1ff msgid "UIFLOW2 Example:" msgstr "" -#: ../../en/base/atom_socket.rst:22 983307c73edb45d78cc99d1103dc2aaf +#: ../../en/base/atom_socket.rst:22 dc4947d1c3324e0ba357adc27732df38 msgid "|example.png|" msgstr "" -#: ../../en/refs/base.atom_socket.ref:30 091c62639cb141229d7b388f5ebdb484 +#: ../../en/refs/base.atom_socket.ref:30 255c4da6253f410c8213b5e9b0cad159 msgid "example.png" msgstr "" -#: ../../en/base/atom_socket.rst:27 9cb367aa018b4fe5bc8e0c63aca9984c +#: ../../en/base/atom_socket.rst:27 2a3c68a97e9c4c57ba9b90dcff8bca20 msgid "|atomlite_socket_example.m5f2|" msgstr "" -#: ../../en/base/atom_socket.rst:31 fb762b5c0a8e42618bfaa8f9476db167 +#: ../../en/base/atom_socket.rst:31 d91b478e2c524c19a25a03e92a0ed2fc msgid "class ATOMSocketBase" msgstr "" -#: ../../en/base/atom_socket.rst:34 062aca913618422a89cde23784f6f211 +#: ../../en/base/atom_socket.rst:34 352b38ad3738407b913c8936c1d2ffbc msgid "Constructors" msgstr "" -#: ../../en/base/atom_socket.rst:38 f78e207aba8e42788cb951e01c3a1d32 +#: ../../en/base/atom_socket.rst:38 cfcc6d750c434e31a4ec82b8acce4330 msgid "Initializes the ATOM Socket." msgstr "" -#: ../../en/base/atom_socket.rst:40 af7de4311cd4446383868c274eb15c60 +#: ../../en/base/atom_socket.rst:40 6b96706785354c11aa4c7012eb5e0452 msgid "``_id``: Serial ID, not actually used by this base." msgstr "" -#: ../../en/base/atom_socket.rst:41 f991d85be5d64e8fa28dcb6aae5a144c +#: ../../en/base/atom_socket.rst:41 0ac2dcc4351249e2b71cdb3099652fd8 msgid "``port``: UART pin numbers." msgstr "" -#: ../../en/base/atom_socket.rst:42 f922c8867ce843d9b6d08a15646aed20 +#: ../../en/base/atom_socket.rst:42 79a39661bf6841018c0c6fb206b2745a msgid "``relay``: The relay pin number." msgstr "" @@ -96,174 +96,177 @@ msgstr "" #: ../../en/base/atom_socket.rst:107 ../../en/base/atom_socket.rst:116 #: ../../en/base/atom_socket.rst:125 ../../en/base/atom_socket.rst:134 #: ../../en/base/atom_socket.rst:143 ../../en/base/atom_socket.rst:154 -#: 27318a0130e74077976c5d45b38f5dc5 3201a6333fd746eba16768d7fd82112e -#: 5e7217e875f144108f940b0c5979d309 7cf2d4cc8f0b4f149c7fd9aadc9426b9 -#: 8cd2326d113847399dac41ac9c35dc71 9acd1cd04cb240289c145b0ba1a84b32 -#: 9b4902423a8a4c03ad4d844ba8a45264 b6a8bc9248b147eb9904267ee489f1f7 -#: b8c505aaaeba491da2de7651fa816048 c452b42e09c0408a88eb132bc0e26fb4 -#: d2ba24b0e11e483abb2b11b994a76f46 fba83f6d308c466fb6a12daed3aa5460 +#: 017801fba76448e5a5f249d9a54fc382 20c3509f08e64f17bce4e269ab811736 +#: 3b541e1a30ab4ef986f79a3e3f00ada7 41033b6e1b124859959ff496f2008bb2 +#: 4d8b587890584bd8aa5a563e7ef05d3d 8df5118a6b644b8fb9c4a1e04a61a81a +#: a170a7433a1748b6a554da7c10204230 dab5823c609a4347bc616e53a7534b05 +#: e8c773150dca4edea3630fbf92e80ad8 eaad96545f9f4acb8c01fa72a5062666 +#: f015e78ed2e347258042d5c33aa0d9fb f58d81b223594c81964cd87aac7b9ff1 msgid "UIFLOW2:" msgstr "" -#: ../../en/base/atom_socket.rst:46 d2eb0698be8e46f7aa6632a59aedc41d +#: ../../en/base/atom_socket.rst:46 847380e3d3fa4a1fb4ee76ab1543d9bf msgid "|init.png|" msgstr "" -#: ../../en/refs/base.atom_socket.ref:6 78509771496548ff853ea9365cb053c0 +#: ../../en/refs/base.atom_socket.ref:6 118f78f8747c45c894ce70653038a0c8 msgid "init.png" msgstr "" -#: ../../en/base/atom_socket.rst:50 448a726f08a14c1e989b25f463ae0fae +#: ../../en/base/atom_socket.rst:50 74da69d4c6a8413f8424047f69c5b516 msgid "Methods" msgstr "" -#: ../../en/base/atom_socket.rst:54 073ac8c331a54885a63977356a4b1231 +#: ../../en/base/atom_socket.rst:54 07e505c4360348478108d38397f1b2c0 msgid "Sets the state of the ATOM Socket's relay." msgstr "" -#: ../../en/base/atom_socket.rst:56 78847fbadec748efb71e8e7c05241dbf +#: ../../en/base/atom_socket.rst:56 742ac5118cb740619dbe36f3d4474367 msgid "``state``: The desired state of the relay, True = ON, False = OFF." msgstr "" -#: ../../en/base/atom_socket.rst:60 b8b3cc7b54924d65b94c4a6e10381562 +#: ../../en/base/atom_socket.rst:60 2595b79622a349dcb247da1d617299dd msgid "|set_relay.png|" msgstr "" -#: ../../en/refs/base.atom_socket.ref:8 18cb6d2ebe0141a58f9e7cc22a3f9f84 +#: ../../en/refs/base.atom_socket.ref:8 310b9e535ac641bc96b434e018a38b4a msgid "set_relay.png" msgstr "" -#: ../../en/base/atom_socket.rst:65 38e6a4b7956f494eae199475b434cecb +#: ../../en/base/atom_socket.rst:65 97193fcc25f34bd0bc2e367c9c486357 msgid "Retrieves data from the ATOM Socket." msgstr "" -#: ../../en/base/atom_socket.rst:67 da84a233ca0045aaa4b62557eeb77c43 +#: ../../en/base/atom_socket.rst:67 954d90f95bec4827b5f7e1ca2cc98a52 msgid "``timeout``: Function timeout period." msgstr "" -#: ../../en/base/atom_socket.rst:69 ff1371dbdea14b4fb92503082752ab8d +#: ../../en/base/atom_socket.rst:69 4cf8c867efb04bf881d8d7fbc973aaca msgid "" "Returns the ATOM Socket data: Tuple (Voltage (V), Current (A), Power (W)," " Total Energy (KWh)), or None if timeout occurs." msgstr "" -#: ../../en/base/atom_socket.rst:73 31c2ccc8fbeb4d5eb4392b7f78679258 +#: ../../en/base/atom_socket.rst:73 c792f91578f3488a92936f2d49ff0622 msgid "|get_data.png|" msgstr "" -#: ../../en/refs/base.atom_socket.ref:10 02050596ed8c4c519af378d4ee1e7b93 +#: ../../en/refs/base.atom_socket.ref:10 0d2dc6b6b0f0496b8523e621963b3d66 msgid "get_data.png" msgstr "" -#: ../../en/base/atom_socket.rst:78 070e8fcf0b134b4398220a5d834134d7 +#: ../../en/base/atom_socket.rst:78 facc45d4fddc4909a2830571ebb5acbc msgid "Retrieves the voltage measurement from the ATOM Socket." msgstr "" -#: ../../en/base/atom_socket.rst:82 7f0823e405e94ce6a1a43fbd3aeaafba +#: ../../en/base/atom_socket.rst:82 328c4d3e19724836b8e19b038a799933 msgid "|get_voltage.png|" msgstr "" -#: ../../en/refs/base.atom_socket.ref:12 18131f5c027e4d9ab3560b5f7d80ff36 +#: ../../en/refs/base.atom_socket.ref:12 6becd482736d4fddb54213318748b019 msgid "get_voltage.png" msgstr "" -#: ../../en/base/atom_socket.rst:87 df9d4a50444548ad8a2dbc5de6a70b6b +#: ../../en/base/atom_socket.rst:87 3f80948ef395432299b3c517a62e8244 msgid "Retrieves the current measurement from the ATOM Socket." msgstr "" -#: ../../en/base/atom_socket.rst:91 10ee6799135b448b94e2acb517fa3346 +#: ../../en/base/atom_socket.rst:91 1dfc4d30d5194955a577a6977b4bb775 msgid "|get_current.png|" msgstr "" -#: ../../en/refs/base.atom_socket.ref:14 586d4cf4e6bb43af8c28ff419a9ab707 +#: ../../en/refs/base.atom_socket.ref:14 309a99a8f07b4e229793cb218d89814d msgid "get_current.png" msgstr "" -#: ../../en/base/atom_socket.rst:96 7a5aab990af14a8982a1cc2678d96a33 +#: ../../en/base/atom_socket.rst:96 13e08cfc138343b4bd78d7d16ecfdf41 msgid "Retrieves the power measurement from the ATOM Socket." msgstr "" -#: ../../en/base/atom_socket.rst:100 e6ad80f21db743838a1a72170c2d66b6 +#: ../../en/base/atom_socket.rst:100 0f1b54f025f746b08a931ab3db460833 msgid "|get_power.png|" msgstr "" -#: ../../en/refs/base.atom_socket.ref:16 85f315927b5345efb8ec6434212b9040 +#: ../../en/refs/base.atom_socket.ref:16 5f9618ff976c4e54876c65b855fdd806 msgid "get_power.png" msgstr "" -#: ../../en/base/atom_socket.rst:105 cb5b1fced2b04d41bd091168f876e76f +#: ../../en/base/atom_socket.rst:105 135ebf3c9e72447f867a6753a5892c49 msgid "Retrieves the power factor from the ATOM Socket." msgstr "" -#: ../../en/base/atom_socket.rst:109 f72e55a119e14c67ae9007ba82f1f5f1 +#: ../../en/base/atom_socket.rst:109 4d1e89653bbd4361873ded26545286b7 msgid "|get_pf.png|" msgstr "" -#: ../../en/refs/base.atom_socket.ref:18 70190abd4463440a8a4d311497f5d723 +#: ../../en/refs/base.atom_socket.ref:18 713ce64262354549bc258ba206cf09e3 msgid "get_pf.png" msgstr "" -#: ../../en/base/atom_socket.rst:114 3b559c244f9447b194d2a622c0cb7aae +#: ../../en/base/atom_socket.rst:114 96396a9a09f941dd85a7b87cb063ecfb msgid "Calculates the inspecting power of the ATOM Socket." msgstr "" -#: ../../en/base/atom_socket.rst:118 70b618206a1248549c92c39328985541 +#: ../../en/base/atom_socket.rst:118 d3b2b71464f043f2a6d62747eaeeb449 msgid "|get_inspecting_power.png|" msgstr "" -#: ../../en/refs/base.atom_socket.ref:20 8ca7d0d87fca4917ab0512b313957208 +#: ../../en/refs/base.atom_socket.ref:20 27c5e001b118430b984820d548bdf740 msgid "get_inspecting_power.png" msgstr "" -#: ../../en/base/atom_socket.rst:123 d0b33994191d4c61af9e6a28fe4b9d4a +#: ../../en/base/atom_socket.rst:123 27cc8663236e4dc29fd0b921027d2f0b msgid "Calculates the power factor of the ATOM Socket." msgstr "" -#: ../../en/base/atom_socket.rst:127 2f99b7e6acd64198b7fbba5d98e8decd +#: ../../en/base/atom_socket.rst:127 4523eb6320d649e2897e392bc4d63b3d msgid "|get_power_factor.png|" msgstr "" -#: ../../en/refs/base.atom_socket.ref:22 74dd514a50cb4a9e834c86973370db04 +#: ../../en/refs/base.atom_socket.ref:22 fbe7dcba8daa44fe8aa2d03cf1fd8730 msgid "get_power_factor.png" msgstr "" -#: ../../en/base/atom_socket.rst:132 858285cef7294e948e8a0b9540769acc +#: ../../en/base/atom_socket.rst:132 59286f16bb8743b6a27383aeccd1b177 msgid "Retrieves the accumulated energy measurement in KWh from the ATOM Socket." msgstr "" -#: ../../en/base/atom_socket.rst:136 17a474969bf04caab172d0a5979668cd +#: ../../en/base/atom_socket.rst:136 5cbd0f7fce0b4e42b47f1c3fa2523731 msgid "|get_kwh.png|" msgstr "" -#: ../../en/refs/base.atom_socket.ref:24 9ddb9048cf8f4823bac0e1cca341bf11 +#: ../../en/refs/base.atom_socket.ref:24 aa1d493a22ac4b9eaaff105d8a4f88aa msgid "get_kwh.png" msgstr "" -#: ../../en/base/atom_socket.rst:141 ec7a8bc660aa457b9248040d62447903 +#: ../../en/base/atom_socket.rst:141 64bcaa2ee9744902b624ffdd77456f74 msgid "Stops receiving data from the ATOM Socket." msgstr "" -#: ../../en/base/atom_socket.rst:145 57ec55311283472fb51ddb579c392f2c +#: ../../en/base/atom_socket.rst:145 9d7a37d013694467816460dc99532903 msgid "|stop_receive_data.png|" msgstr "" -#: ../../en/refs/base.atom_socket.ref:28 8def185d72664c89bea0fac167080a0d +#: ../../en/refs/base.atom_socket.ref:28 66bdb933671848e18dd3f11cbe911aee msgid "stop_receive_data.png" msgstr "" -#: ../../en/base/atom_socket.rst:150 2b57110688cd454290f0c9ab1ab1f6fe +#: ../../en/base/atom_socket.rst:150 501f28f01053416b8451683184bc0877 msgid "Receives data from the ATOM Socket in non-blocking mode." msgstr "" -#: ../../en/base/atom_socket.rst:152 f83a6645b0f443b1913dd3f2f47f6ff2 +#: ../../en/base/atom_socket.rst:152 f67cb074e50c4ea2a8aafb8e5ac2de87 msgid "``receive_callback``: Callback function to handle the received data." msgstr "" -#: ../../en/base/atom_socket.rst:156 53ed2a212adb436a8549a42836fa487b +#: ../../en/base/atom_socket.rst:156 55604ff0b1054da08759fd75d44da73e msgid "|receive_none_block.png|" msgstr "" -#: ../../en/refs/base.atom_socket.ref:26 53f596e4c04b43fe9abf2d8b8130497b +#: ../../en/refs/base.atom_socket.ref:26 4192c5f61e4c4475b7625be9e16b84df msgid "receive_none_block.png" msgstr "" +#~ msgid "ATOM Socket Base" +#~ msgstr "" + diff --git a/docs/locales/zh_CN/LC_MESSAGES/base/motion.po b/docs/locales/zh_CN/LC_MESSAGES/base/motion.po index 6600f94c..17ce3021 100644 --- a/docs/locales/zh_CN/LC_MESSAGES/base/motion.po +++ b/docs/locales/zh_CN/LC_MESSAGES/base/motion.po @@ -9,7 +9,7 @@ msgid "" msgstr "" "Project-Id-Version: UIFlow2 Programming Guide \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-02-21 09:35+0800\n" +"POT-Creation-Date: 2025-04-03 10:58+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language: zh_CN\n" @@ -20,13 +20,11 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.16.0\n" -#: ../../en/base/motion.rst:2 ../../en/base/motion.rst:23 -#: ../../en/base/motion.rst:56 8cb57a42f73947b7a36e1b9e4d93cf92 -#: e9c1f8c58b224634b19d4436d4ee86b1 -msgid "Motion Base" +#: ../../en/base/motion.rst:2 fd8dedb467204d8aad20c0b3bd456f89 +msgid "Atomic Motion Base" msgstr "" -#: ../../en/base/motion.rst:8 b7e1bc4ef6b3491c89517f0a5693584c +#: ../../en/base/motion.rst:8 eeecc50c868f40d78926304c8570b684 msgid "" "Atomic Motion Base is a servo and DC motor driver designed specifically " "for the ATOM series controllers. It integrates an STM32 control chip " @@ -34,307 +32,340 @@ msgid "" "provides 4 servo channels and 2 DC motor interfaces, offering convenience" " for scenarios that require control of multiple servos or motor drivers, " "such as multi-axis servo robotic arms or small car motor control." -msgstr "Atomic Motion Base 是一款专为 ATOM 系列控制器设计的舵机和直流电机驱动器。它内部集成了 STM32 控制芯片,并使用 I2C 通信进行控制。Atomic Motion Base 提供 4 路舵机通道和 2 路直流电机接口,方便用于需要控制多个舵机或电机的场景,如多舵机械臂或小车电机控制。" +msgstr "" +"Atomic Motion Base 是一款专为 ATOM 系列控制器设计的舵机和直流电机驱动器。它内部集成了 STM32 控制芯片,并使用 " +"I2C 通信进行控制。Atomic Motion Base 提供 4 路舵机通道和 2 " +"路直流电机接口,方便用于需要控制多个舵机或电机的场景,如多舵机械臂或小车电机控制。" -#: ../../en/base/motion.rst:10 a620d816918a4699b4aa4787e6269a82 +#: ../../en/base/motion.rst:10 8cda377fab69497784593fd17fead334 msgid "" "Atomic Motion Base v1.1 adds INA226 to implement current and voltage " "detection." msgstr "Atomic Motion Base v1.1 增加 INA266 实现电流,电压和功率检测。" -#: ../../en/base/motion.rst:12 fd9e44a03f3c405990e2684ca8ac543d +#: ../../en/base/motion.rst:12 65798094902f4f13b3c0d4d0f3ec4131 msgid "Support the following products:" msgstr "支持以下产品:" -#: ../../en/base/motion.rst:14 c4005630731248a1b549339be4d39106 +#: ../../en/base/motion.rst:15 b51339bff83b4cc3976265167681d96d msgid "|Motion|" msgstr "" -#: ../../en/base/motion.rst:86 ../../en/refs/base.motion.ref -#: a1b296d155ef4243821f10b19064ca16 c4005630731248a1b549339be4d39106 +#: ../../en/base/motion.rst:90 ../../en/refs/base.motion.ref +#: 1eec9cb863df4dc3a3889dcc98ab4e9d b9e14124cdc246e0997167470b93ff16 msgid "Motion" msgstr "" -#: ../../en/base/motion.rst:16 a678135f54a94123b4a7e607a3d004b1 +#: ../../en/base/motion.rst:15 a2ff1e45f0ec46aea14ca22f07539960 msgid "|Motion Base v1.1|" msgstr "" -#: ../../en/base/motion.rst:38 ../../en/base/motion.rst:69 -#: ../../en/refs/base.motion.ref 0bd6cda2abfa496ca2ed75ebf9ac3c7d -#: 4f255df1b72e4c848c325593002eaa2b +#: ../../en/base/motion.rst:38 ../../en/base/motion.rst:71 +#: ../../en/refs/base.motion.ref 2054097f13b242f092b0f7bf5f81f7b9 +#: 377d48bb9a7e4f558c37ec720138ceb3 416e7b09aae44a2d98fcccf1c1595623 msgid "Motion Base v1.1" msgstr "" -#: ../../en/base/motion.rst:20 3ce7d7c5a2024f878f93bccfa8cb3e40 +#: ../../en/base/motion.rst:20 c805b4cd38d84ef8aa6b06a663c7ec94 msgid "UiFlow2 Example:" msgstr "UiFlow2 应用示例:" -#: ../../en/base/motion.rst:25 425c48018c84461d9e98ce52b66de48b +#: ../../en/base/motion.rst:23 ../../en/base/motion.rst:56 +#: 05b2997d19d246669cb52a9798e35fe0 6540d2c198d844d2b76d6930a9eafd3f +msgid "Motion Base" +msgstr "" + +#: ../../en/base/motion.rst:25 223f49310c50438cbfccaa8115c973ac msgid "Open the |atoms3_lite_motion_base_example.m5f2| project in UiFlow2." msgstr "在 UiFlow2 上打开 |atoms3_lite_motion_base_example.m5f2| 项目。" -#: ../../en/base/motion.rst:27 7072f5404bd54727b72b8da35ab7bfaf +#: ../../en/base/motion.rst:27 ../../en/base/motion.rst:58 +#: 2bd86d3b09814640bff045cb510f9af8 b1f58426a58e413493ad2cde27310191 msgid "" "This example controls the servo to rotate to a specified angle and sets " "the motor to rotate." msgstr "此示例控制舵机旋转到指定角度,设置电机旋转。" #: ../../en/base/motion.rst:29 ../../en/base/motion.rst:44 -#: ../../en/base/motion.rst:95 ../../en/base/motion.rst:117 -#: ../../en/base/motion.rst:134 ../../en/base/motion.rst:152 -#: ../../en/base/motion.rst:169 ../../en/base/motion.rst:187 -#: ../../en/base/motion.rst:204 ../../en/base/motion.rst:224 -#: ../../en/base/motion.rst:244 ../../en/base/motion.rst:264 -#: 64dae5e39b2745da8830752accea607b +#: ../../en/base/motion.rst:99 ../../en/base/motion.rst:121 +#: ../../en/base/motion.rst:138 ../../en/base/motion.rst:156 +#: ../../en/base/motion.rst:173 ../../en/base/motion.rst:191 +#: ../../en/base/motion.rst:208 ../../en/base/motion.rst:228 +#: ../../en/base/motion.rst:248 ../../en/base/motion.rst:268 +#: 296c7753e1044cdaacf0f0d668e17dd0 35ca39b308694a9c9bc88c29ff314f59 +#: 37cf5bc1cb224fc9bd336629b7d50930 5246209a2d0c4120b8031c3d627a8c4a +#: 74fccd8aa92043dbbb94ee0968c2756b 76f722ca6fbc4df1ad3e7dba9c8e9bcf +#: a50125799c1e4e7c9cda34143f01a035 a7e688d67c0742fa848858ffca01846c +#: a8ee1721f43947498b1e8ec75353843b ae16a5ef51cf47fc86266cf44552e0e1 +#: af2f7ea5eeee4c4f9a859743251f450e efc66258eab948dfbf5dca5bcc7285e9 msgid "UiFlow2 Code Block:" msgstr "UiFlow2 代码块:" -#: ../../en/base/motion.rst:31 c7f7b368e651428e8c66e00490b63c96 +#: ../../en/base/motion.rst:31 22fe30a33db44c89918fdd327cee95a7 msgid "|example.png|" msgstr "" -#: ../../en/refs/base.motion.ref:23 2a91616f621f492c977ec8ec9195c510 +#: ../../en/refs/base.motion.ref:23 3315298d45904940bb64a264c78e9c2e msgid "example.png" msgstr "" #: ../../en/base/motion.rst:33 ../../en/base/motion.rst:48 -#: ../../en/base/motion.rst:64 ../../en/base/motion.rst:77 -#: 2cd46fe3f7f9421f8ecaa6043b47e720 +#: ../../en/base/motion.rst:66 ../../en/base/motion.rst:81 +#: 27ad1a508d4847a3ae2c60a68fcb6d84 3daa9faae3fe4daa9cf733821acacd36 +#: 6a434288eac849b09d1714b0149020af 8b5ac48cc5a34dd28bea2061c3cf84b7 msgid "Example output:" msgstr "示例输出:" #: ../../en/base/motion.rst:35 ../../en/base/motion.rst:50 -#: ../../en/base/motion.rst:66 ../../en/base/motion.rst:79 -#: 14671b5447ca48a6abbc2902c234bac0 +#: ../../en/base/motion.rst:68 ../../en/base/motion.rst:83 +#: 0e5d45e7d4a04a449e6527abe02efd45 27deaff71d72471880edda9c5a076de3 +#: 99522afe7ff546768748f39741a5a194 d8922ccc5bf742fabf9716071d950844 msgid "None" msgstr "无" -#: ../../en/base/motion.rst:40 d47264573dbf456080fbb0d4b4dd973c +#: ../../en/base/motion.rst:40 a5efb829fc13496884b57110339a367c msgid "Open the |atoms3_motion_base_v1.1_example.m5f2| project in UiFlow2." msgstr "在 UiFlow2 上打开 |atoms3_motion_base_v1.1_example.m5f2| 项目。" -#: ../../en/base/motion.rst:42 4c055eed634f445499b8b7721f1f6038 +#: ../../en/base/motion.rst:42 ../../en/base/motion.rst:73 +#: 2fa64c93cad542e199db55bfcd4ab0f4 e33ea222b99b4442b7fdbb4544e0a731 msgid "" "The example program switches the motor's running speed when the screen " "button is pressed, and the screen displays the current, voltage, and " "power." msgstr "示例程序在按下屏幕按钮时切换电机的运行速度,屏幕显示当前的电流、电压和功率。" -#: ../../en/base/motion.rst:46 631386a3f8394b449ca0b0f256d950ec +#: ../../en/base/motion.rst:46 7954008fc2784a7a95ea25e51ddb52f3 msgid "|motion_base_v1.1_example.png|" msgstr "" -#: ../../en/refs/base.motion.ref:24 c3a9b7b750e84d43958422e3e2fc7f0f +#: ../../en/refs/base.motion.ref:24 ecf28ad94bb64936ad4c5e73ffaadcfd msgid "motion_base_v1.1_example.png" msgstr "" -#: ../../en/base/motion.rst:53 e9c1f8c58b224634b19d4436d4ee86b1 +#: ../../en/base/motion.rst:53 a556fb60f0ee40ba977473c034c914bb msgid "MicroPython Example:" msgstr "MicroPython 应用示例:" -#: ../../en/base/motion.rst:58 ../../en/base/motion.rst:71 -#: ../../en/base/motion.rst:99 ../../en/base/motion.rst:121 -#: ../../en/base/motion.rst:138 ../../en/base/motion.rst:156 -#: ../../en/base/motion.rst:173 ../../en/base/motion.rst:191 -#: ../../en/base/motion.rst:208 ../../en/base/motion.rst:228 -#: ../../en/base/motion.rst:248 ../../en/base/motion.rst:268 -#: e9c1f8c58b224634b19d4436d4ee86b1 +#: ../../en/base/motion.rst:60 ../../en/base/motion.rst:75 +#: ../../en/base/motion.rst:103 ../../en/base/motion.rst:125 +#: ../../en/base/motion.rst:142 ../../en/base/motion.rst:160 +#: ../../en/base/motion.rst:177 ../../en/base/motion.rst:195 +#: ../../en/base/motion.rst:212 ../../en/base/motion.rst:232 +#: ../../en/base/motion.rst:252 ../../en/base/motion.rst:272 +#: 24c34cc3f68a4b56bdd8eed92f2edf12 339523b1dfed47fc9ca6416fc8d946b1 +#: 364f135a0e9e4cf487231647bfacd31a 7edc2ee52b784dcd9ebff198200d4638 +#: 8077e999840748cf8f9031137ef866ee 8c7c25594abc4e7b9ba85dfb0fbad758 +#: 9fdab1fd399b4bf3914b300cd84480a7 c4a18d7a9853447b9d41c91b2258c1ff +#: d4af98053ac944618e8ade4716b162fd d80ad3178cec48f4a60c312ea46caeb0 +#: e8fb2db00912456687355b323b9ef1fc ec4afba379a94e4a9d8f372fb771d25b msgid "MicroPython Code Block:" msgstr "MicroPython 代码块:" -#: ../../en/base/motion.rst:83 2192b4879a334f4ca059e9b8cade2b77 +#: ../../en/base/motion.rst:87 1ba4f32cd42e47918b73882050fa7999 msgid "**API**" msgstr "API应用" -#: ../../en/base/motion.rst:90 e87abaa0973e48f5b13ec3a970b24b66 +#: ../../en/base/motion.rst:94 b290c15ab97248c7b04eadcb03905baf msgid "Create an Motion object." msgstr "创建一个 Motion 对象。" -#: ../../en/base/motion.rst df7d8c074c6347919e449475db16d885 +#: ../../en/base/motion.rst 0fb2928193bc4397b1c15799b7994c3b +#: 5963cc7f3fb145b1a496a02fd93bd11a 85ad327594684ce894a45767615cd947 +#: 89f4e10cf68146fca05858955fa55597 921bf5d06931461b9bd17e42cadd9089 +#: b713c5c7980b43ff9bee879c51b0ade4 b8ddb736052249ab90ff4648d5a85254 msgid "Parameters" msgstr "" -#: ../../en/base/motion.rst:92 a4ccebf129e444e595464420f5ef1acb +#: ../../en/base/motion.rst:96 0985a4677a544a81a8da3e6826ad1bfc msgid "The I2C port to use." msgstr "要使用的 I2C 端口。" -#: ../../en/base/motion.rst:93 6f2db8b6d2f84d46a2d089e2883ef8e8 +#: ../../en/base/motion.rst:97 8407f649ff0c474bb5479ea841f1770e msgid "The device address. Default is 0x38." msgstr "设备地址,默认为 0x38。" -#: ../../en/base/motion.rst:97 2ad023eac8444a84a245da89b39219d6 +#: ../../en/base/motion.rst:101 3c3818488a0a41fea35101fec2a47b75 msgid "|__init__.png|" msgstr "" -#: ../../en/refs/base.motion.ref:12 437578a402844251bfe7d3bf89ca1f63 +#: ../../en/refs/base.motion.ref:12 f534954476b849449c7da1f65a1cadf7 msgid "__init__.png" msgstr "" -#: ../../en/base/motion.rst:111 653412b3f2f54c12a9a3a5de14490094 +#: ../../en/base/motion.rst:115 cda65b46ce2740408838e279e3cab3d0 msgid "Get the angle of the servo." msgstr "获取舵机角度。" -#: ../../en/base/motion.rst:113 ../../en/base/motion.rst:131 -#: ../../en/base/motion.rst:148 ../../en/base/motion.rst:166 -#: 6643efad13084915a1aee5e2fc341196 +#: ../../en/base/motion.rst:117 ../../en/base/motion.rst:135 +#: ../../en/base/motion.rst:152 ../../en/base/motion.rst:170 +#: 30377ebdcd334bb486f4b29b1a92c169 7ade860815254212942c5bebefd8019e +#: e4ab1ca0364549ac8886b514bdcd9f91 f021e0f185cb46f98e35ff6d573c15a4 msgid "The servo channel. Range: 1~4." msgstr "舵机通道,范围:1~4。" -#: ../../en/base/motion.rst b91f3b29ea904fbc927c0298304c622e +#: ../../en/base/motion.rst 4188de5e107a44d7b0bd0fc84d08ac9b +#: 95082bd09ca84ad69734eecb1902a18c 9e0db1857aa7480f87cc83557029d9e1 +#: a8962a678fbd4072a35437a73f31d98e afbdccfbc47947d783f57de4cb0f3c91 +#: c1a36a49e9134630a13478efecee1e78 msgid "Returns" msgstr "" -#: ../../en/base/motion.rst:114 efdca5ed2aab4d8b8574f189f41803a8 +#: ../../en/base/motion.rst:118 2d415f3a5cb6477e8041fe1c484f8de4 msgid "Specify the servo angle for the specified channel. Range: 0~180." msgstr "指定通道舵机的角度,范围:0~180。" -#: ../../en/base/motion.rst c1f1306d0e36407dad3ec400243f6678 +#: ../../en/base/motion.rst 22a6b5f7c6ec411482b26a31d9dbcf1c +#: 2d6da2a5ac954297842c3ddc8024e3ab 523ba534a7964da2bfe60c3aeb2c2ea7 +#: 956972f113f744a3a2b789286e7d22e1 cbbe6e7132bc4dbaa82513051ab6cfa4 +#: f6092826179a4b2dbc6c3da7b64c949a msgid "Return type" msgstr "" -#: ../../en/base/motion.rst:119 42606b3d54f842a3936b7b8b8598c27d +#: ../../en/base/motion.rst:123 cfe89051a45b4fe1b9499180050ef1e8 msgid "|get_servo_angle.png|" msgstr "" -#: ../../en/refs/base.motion.ref:13 c3a9b7b750e84d43958422e3e2fc7f0f +#: ../../en/refs/base.motion.ref:13 a42815d853c14da686eb99abe6904f35 msgid "get_servo_angle.png" msgstr "" -#: ../../en/base/motion.rst:129 eef6b80699764e89807c21c09833bb7e +#: ../../en/base/motion.rst:133 915d5b9f448f4162a8d0530bdcdcd949 msgid "Set the angle of the servo." msgstr "设置舵机角度。" -#: ../../en/base/motion.rst:132 41a488f691074ec6a96d8562befa419b +#: ../../en/base/motion.rst:136 8aa64c176c3e4ac0bc0f6e9520081914 msgid "The servo angle. Range: 0~180." msgstr "舵机角度,范围:0~180。" -#: ../../en/base/motion.rst:136 91d895a6605549658df70a5604d772d3 +#: ../../en/base/motion.rst:140 c39b850a5e4042b1bf4db3cf9b502a28 msgid "|set_servo_angle.png|" msgstr "" -#: ../../en/refs/base.motion.ref:14 c90fcc9f4b6049eaba9bf70377ea7adb +#: ../../en/refs/base.motion.ref:14 bbd76cdddf094f1d807d378b99d562b3 msgid "set_servo_angle.png" msgstr "" -#: ../../en/base/motion.rst:146 653412b3f2f54c12a9a3a5de14490094 +#: ../../en/base/motion.rst:150 a0c0c00f1aeb4560951f54e2e433d7f2 msgid "Get the pulse of the servo." msgstr "获取舵机控制信号的脉冲宽度。" -#: ../../en/base/motion.rst:149 33c48653bf7d4a75aca9dabcbd36aeee +#: ../../en/base/motion.rst:153 27094dc9745f455b8dcb4fc6f4c65ea5 msgid "Specify the servo pulse for the specified channel. Range: 500~2500." msgstr "指定通道舵机控制信号的脉冲宽度,范围:500~2500 (us)。" -#: ../../en/base/motion.rst:154 0931097552384d1e9d8eebcd0b5ce2fa +#: ../../en/base/motion.rst:158 c7536c9815984b4c9bf027d9189599c5 msgid "|get_servo_pulse.png|" msgstr "" -#: ../../en/refs/base.motion.ref:15 7ca7698177c446379cae9f23afa05af4 +#: ../../en/refs/base.motion.ref:15 dfbfc96f6d4f4c3ba457d84860cf0afb msgid "get_servo_pulse.png" msgstr "" -#: ../../en/base/motion.rst:164 653412b3f2f54c12a9a3a5de14490094 +#: ../../en/base/motion.rst:168 349ba397a7c9496bb9bc451fd7100d42 msgid "Write the pulse of the servo." msgstr "写舵机控制信号脉冲宽度。" -#: ../../en/base/motion.rst:167 4a411f060bae4b2386c728d9d72fce25 +#: ../../en/base/motion.rst:171 0a2843ef344e4f86b12d3d428883d03b msgid "The servo pulse. Range: 500~2500." msgstr "舵机控制信号脉冲宽度,范围:500~2500 (us)。" -#: ../../en/base/motion.rst:171 f17a8dd802bf463a80f076e1c2d06ce1 +#: ../../en/base/motion.rst:175 e05c46b46747421192cd91933b6cae12 msgid "|write_servo_pulse.png|" msgstr "" -#: ../../en/refs/base.motion.ref:16 cdcaf0d8754b4d68b1c524e032e34c48 +#: ../../en/refs/base.motion.ref:16 506a11c22bbf45a182cb6934086a0d25 msgid "write_servo_pulse.png" msgstr "" -#: ../../en/base/motion.rst:181 f775642288fe429c9b9df7bc09447c67 +#: ../../en/base/motion.rst:185 1c4afb3c31ad451183e0ba36a274c667 msgid "Get the speed of the motor." msgstr "获取电机转速。" -#: ../../en/base/motion.rst:183 ../../en/base/motion.rst:201 -#: bf0b17d1c760429dbd2367ac4bd0b7b2 +#: ../../en/base/motion.rst:187 ../../en/base/motion.rst:205 +#: 25737862b7764dd8bedb04fb64ed56e0 c3362c73858e47c388fdb5d59a24fe4a msgid "The motor channel. Range: 1~2." msgstr "电机通道,范围:1~2。" -#: ../../en/base/motion.rst:184 f4ffb0922aed499caa93dbd268dc1bca +#: ../../en/base/motion.rst:188 4f9fe509ece34462b1821adec846e81e msgid "Specify the speed for the specified channel. Range: -127~127." msgstr "指定通道电机的转速,范围:-127~127。" -#: ../../en/base/motion.rst:189 2a664000eda346a89aba7a760b0b3d14 +#: ../../en/base/motion.rst:193 4c3884c178aa4bb98cd25c73dac985d4 msgid "|get_motor_speed.png|" msgstr "" -#: ../../en/refs/base.motion.ref:17 6c09491d5dd6409c81909a6d0e78eec2 +#: ../../en/refs/base.motion.ref:17 1c55ddafb3ac43a88f2ef0910b556ef2 msgid "get_motor_speed.png" msgstr "" -#: ../../en/base/motion.rst:199 2a664000eda346a89aba7a760b0b3d14 +#: ../../en/base/motion.rst:203 00c0627589d944bc98c6bb5c16d450e6 msgid "Set motor speed." msgstr "设置电机转速。" -#: ../../en/base/motion.rst:202 d7c3ebb5f84d41ed956566d9d840dcdf +#: ../../en/base/motion.rst:206 e60d8837d0ab46a18946e42f25ad4060 msgid "The motor speed. Range: -127~127." msgstr "电机转速,范围:-127~127。" -#: ../../en/base/motion.rst:206 2b53dbab3157413e85c1d9bc6217e2ce +#: ../../en/base/motion.rst:210 298daaa44a2243c48b87171c481327a9 msgid "|set_motor_speed.png|" msgstr "" -#: ../../en/refs/base.motion.ref:18 bb7e088a998a441e8603c88c36276f71 +#: ../../en/refs/base.motion.ref:18 346d056737894be6990a8cb50f0d808f msgid "set_motor_speed.png" msgstr "" -#: ../../en/base/motion.rst:216 b5fc23d0895546a3af6121f532d21804 +#: ../../en/base/motion.rst:220 f9af5b02304a4ac0aef0e22798476bff msgid "Read voltage (unit: V)." msgstr "读取电压(单位:V)。" -#: ../../en/base/motion.rst:218 4ea4b485d09640af802a7f956d8ba84f +#: ../../en/base/motion.rst:222 0d92a7683b854fa6a46076a6cb8fbccb msgid "The voltage value in volts." msgstr "电压值(以伏特为单位)。" -#: ../../en/base/motion.rst:222 ../../en/base/motion.rst:242 -#: ../../en/base/motion.rst:262 63f1a0aa47064b7480b1880025f95303 +#: ../../en/base/motion.rst:226 ../../en/base/motion.rst:246 +#: ../../en/base/motion.rst:266 444476c30654423b858d45ceafdbb069 +#: 8197559809b64eef8df4102d530842e5 b059b3e358124532becb7d8cf5bc9c52 msgid "This method is supported only on Motion Base v1.1 and later versions." msgstr "此方法仅在 Motion Base v1.1 及更高版本中支持。" -#: ../../en/base/motion.rst:226 42606b3d54f842a3936b7b8b8598c27d +#: ../../en/base/motion.rst:230 c6ca7476a43b40de9eeed7ca72886019 msgid "|read_voltage.png|" msgstr "" -#: ../../en/refs/base.motion.ref:19 c3a9b7b750e84d43958422e3e2fc7f0f +#: ../../en/refs/base.motion.ref:19 737051362cb64a11919ca06ba9a6ea72 msgid "read_voltage.png" msgstr "" -#: ../../en/base/motion.rst:236 bc81b18966b244d3ba9c9044071b9dff +#: ../../en/base/motion.rst:240 c04933238fbb40f1808167f851408a3e msgid "Read current (unit: A)." msgstr "读取电流(单位:A)。" -#: ../../en/base/motion.rst:238 10a1c716fd714fc38efed7d1b383ff99 +#: ../../en/base/motion.rst:242 a5e53a62f93c4188ae72a216cb92b6b7 msgid "The current value in amperes." msgstr "电流值(以安培为单位)。" -#: ../../en/base/motion.rst:246 42606b3d54f842a3936b7b8b8598c27d +#: ../../en/base/motion.rst:250 fd585d350a194b54bb726bdb0105e6f8 msgid "|read_current.png|" msgstr "" -#: ../../en/refs/base.motion.ref:20 2a91616f621f492c977ec8ec9195c510 +#: ../../en/refs/base.motion.ref:20 840de5bb3b27490ca793d9bf244f4381 msgid "read_current.png" msgstr "" -#: ../../en/base/motion.rst:256 03a010f191c84356909e1c544f48cc72 +#: ../../en/base/motion.rst:260 26c02c1709644d9e81f1b7e5efa47114 msgid "Read power (unit: W)." msgstr "读取功率(单位:W)。" -#: ../../en/base/motion.rst:258 c572bd63ae8e4c8ebd002917bbd0312d +#: ../../en/base/motion.rst:262 d98514e8700349688967c86c62885020 msgid "The power value in watts." msgstr "功率值(以瓦特为单位)。" -#: ../../en/base/motion.rst:266 c7f7b368e651428e8c66e00490b63c96 +#: ../../en/base/motion.rst:270 d8b3e22805064cd4ab3f966792c852c9 msgid "|read_power.png|" msgstr "" -#: ../../en/refs/base.motion.ref:21 2a91616f621f492c977ec8ec9195c510 +#: ../../en/refs/base.motion.ref:21 9f2a9ba65bdc4b12bede3826c2d5cdfd msgid "read_power.png" msgstr "" diff --git a/docs/locales/zh_CN/LC_MESSAGES/hardware/als.po b/docs/locales/zh_CN/LC_MESSAGES/hardware/als.po index 2c4eea81..6c6fa82d 100644 --- a/docs/locales/zh_CN/LC_MESSAGES/hardware/als.po +++ b/docs/locales/zh_CN/LC_MESSAGES/hardware/als.po @@ -9,7 +9,7 @@ msgid "" msgstr "" "Project-Id-Version: UIFlow2 Programming Guide \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-01-16 15:35+0800\n" +"POT-Creation-Date: 2025-04-18 15:58+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language: zh_CN\n" @@ -21,96 +21,114 @@ msgstr "" "Generated-By: Babel 2.16.0\n" #: ../../en/hardware/als.rst:2 ../../en/hardware/als.rst:16 -#: a3c3ca81dfd543aeba9e0bfdd52f2890 b820e696eeb245c8ae4c41ba2e62c54a +#: 715dfd5bfb85440fa6fed1b8be63d826 cc80b6b60fc745d09c3a34770ab8811c msgid "ALS" msgstr "" -#: ../../en/hardware/als.rst:7 47607a447f984e978424e9beeb2c5d5f +#: ../../en/hardware/als.rst:7 23497f94ae0248f3b345b373f5e695bc msgid "" "ALS is used to read the built-in ambient light sensor inside the host " "device." msgstr "" -#: ../../en/hardware/als.rst:9 c157c6e74b4845f79bae2dae1f30cdd0 +#: ../../en/hardware/als.rst:9 2392b9c4619942cdadbc233c8ca82d77 msgid "The following are the details of the host's support for ALS:" msgstr "" -#: ../../en/hardware/als.rst:18 fb7b33490df747899d84f432f6a5fd9f +#: ../../en/hardware/als.rst:18 8de46dd675c740bfbff655484a9c82c9 msgid "CoreS3" msgstr "" -#: ../../en/hardware/als.rst:18 0b94145e7a13402bafd795acdcc6cec3 +#: ../../en/hardware/als.rst:18 9822bf0e225f4fda833b407bfc8dfc7d msgid "|S|" msgstr "" -#: ../../en/hardware/als.rst:20 873fea3751ea4e2c8fa1209b33900280 +#: ../../en/hardware/als.rst:20 671ee2fa6f6748cb94f3b8b4bdf4a968 msgid "CoreS3 SE" msgstr "" -#: ../../en/hardware/als.rst:27 0983dc3e9f4c4ff79fc8f355c3dca131 +#: ../../en/hardware/als.rst:27 1a5efaf54aa943c1ada3ed4b46f0872d msgid "Micropython Example:" msgstr "" -#: ../../en/hardware/als.rst:33 5ff419585c71435d92fe6ba9209f9e05 +#: ../../en/hardware/als.rst:33 9d71783848ec4681af5e19cda6f397d4 msgid "UIFLOW2 Example:" msgstr "" -#: ../../en/hardware/als.rst:35 f43eba528dcc4a9b8753396db897278f +#: ../../en/hardware/als.rst:35 87aa146913f24181bfeb05fa46b683ba msgid "|example.png|" msgstr "" -#: ../../en/refs/hardware.als.ref:4 0c5a05418a1f4897b7e0d37a6d315a29 +#: ../../en/refs/hardware.als.ref:4 ef2b53a7f95448bfa6f2fd9736f2cf0e msgid "example.png" msgstr "" -#: ../../en/hardware/als.rst:39 3dafb516d45c40a6b3a49d04facf6676 +#: ../../en/hardware/als.rst:39 aab6a4db4c354a4b80575221ac71f703 msgid "|als_cores3_example.m5f2|" msgstr "" -#: ../../en/hardware/als.rst:43 d83bd480f5494b08883b0e6331ddce32 +#: ../../en/hardware/als.rst:43 f2869aef778b44bfbbc36d50fd3f5b01 msgid "class ALS" msgstr "" -#: ../../en/hardware/als.rst:47 f0533522039a47769281903f5c5dd233 +#: ../../en/hardware/als.rst:47 d560c8cb5c5c4faba6ed5f05bebac32f msgid "" -"Methods of the ALS Class heavily rely on ``M5.begin()`` |M5.begin.svg| " -"and ``M5.update()`` |M5.update.svg|." +"Methods of the ALS Class heavily rely on ``M5.begin()`` |M5.begin.png| " +"and ``M5.update()`` |M5.update.png|." msgstr "" -#: ../../en/refs/system.ref:1 6798c00a9e7d4dc5977bfd392c0e38a0 -#: eeb05e5a8d9d4da681a6eac225bd82cb -msgid "M5.begin.svg" +#: ../../en/refs/system.ref:1 48ec0fcaaf3940f8a4fae0f31838eeff +msgid "M5.begin.png" msgstr "" -#: ../../en/refs/system.ref:4 1130dac2ae8e46098ead0da9c9be4b17 -#: 39cfb909ef914640b6768735d3fda177 -msgid "M5.update.svg" +#: ../../en/refs/system.ref:3 80695b8ed2eb44509c376d5b5aec4046 +msgid "M5.update.png" msgstr "" -#: ../../en/hardware/als.rst:49 f3d5e32598c2402b9f668d1fc9c6dce6 +#: ../../en/hardware/als.rst:49 145e47bd2ea54cf9a7a1dd8e00aa5afd msgid "" "All calls to methods of ALS objects should be placed after ``M5.begin()``" -" |M5.begin.svg|, and ``M5.update()`` |M5.update.svg| should be called in " +" |M5.begin.png|, and ``M5.update()`` |M5.update.png| should be called in " "the main loop." msgstr "" -#: ../../en/hardware/als.rst:53 2404957574a44b6e8b21feb9c851ea33 +#: ../../en/hardware/als.rst:53 86c2fdde246b4455961bb3e8594eea54 msgid "Methods" msgstr "" -#: ../../en/hardware/als.rst:57 3efae75b03f745eea12cf320e4b3d4b4 +#: ../../en/hardware/als.rst:57 3ea536206b7648cca2c694091c0bfa3f msgid "Read the ambient light sensor value built into the host device." msgstr "" -#: ../../en/hardware/als.rst:59 b95baf0f44f843299ab2961b4b7dda75 +#: ../../en/hardware/als.rst:59 a8d4730336bd48ebbbe60659d82db36c msgid "UIFLOW2:" msgstr "" -#: ../../en/hardware/als.rst:61 52ea4478481d43e79cb5cf2a3dee2230 +#: ../../en/hardware/als.rst:61 837480772ee840adab4c6e60ae1fa2be msgid "|getLightSensorData.png|" msgstr "" -#: ../../en/refs/hardware.als.ref:2 fc23360e517444d19e3ce9f1bdb8b73b +#: ../../en/refs/hardware.als.ref:2 ac06447943064a8db612bde5cae195c9 msgid "getLightSensorData.png" msgstr "" +#~ msgid "" +#~ "Methods of the ALS Class heavily " +#~ "rely on ``M5.begin()`` |M5.begin.svg| and " +#~ "``M5.update()`` |M5.update.svg|." +#~ msgstr "" + +#~ msgid "M5.begin.svg" +#~ msgstr "" + +#~ msgid "M5.update.svg" +#~ msgstr "" + +#~ msgid "" +#~ "All calls to methods of ALS " +#~ "objects should be placed after " +#~ "``M5.begin()`` |M5.begin.svg|, and ``M5.update()``" +#~ " |M5.update.svg| should be called in " +#~ "the main loop." +#~ msgstr "" + diff --git a/docs/locales/zh_CN/LC_MESSAGES/hardware/button.po b/docs/locales/zh_CN/LC_MESSAGES/hardware/button.po index d3a7424f..608ceeef 100644 --- a/docs/locales/zh_CN/LC_MESSAGES/hardware/button.po +++ b/docs/locales/zh_CN/LC_MESSAGES/hardware/button.po @@ -9,7 +9,7 @@ msgid "" msgstr "" "Project-Id-Version: UIFlow2 Programming Guide \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-01-16 15:35+0800\n" +"POT-Creation-Date: 2025-04-18 15:58+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language: zh_CN\n" @@ -20,130 +20,130 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.16.0\n" -#: ../../en/hardware/button.rst:2 f5afb750dea249c0a566510b3f1bf6ad +#: ../../en/hardware/button.rst:2 a58bbba53b8d48d980ca31d3142489ff msgid "Button" msgstr "" -#: ../../en/hardware/button.rst:8 db5858a8c18b45329772ce82ef6eb8cc +#: ../../en/hardware/button.rst:8 13c4bf9f47ca4945808461577f054fb9 msgid "" "Button is used to control the built-in buttons inside the host device. " "Below is the detailed Button support for the host:" msgstr "Button用于控制主机内部集成的按键。以下是主机的Button支持详细:" -#: ../../en/hardware/button.rst:16 56e2161a480446e5af2f59cff1de987a +#: ../../en/hardware/button.rst:16 083bf20a84534e2db23984b362e07d66 msgid "BtnA" msgstr "" -#: ../../en/hardware/button.rst:16 020a784b1aca42c09e946e58cd3ad9d0 +#: ../../en/hardware/button.rst:16 6f393a8a677b4c2d81f966bc0e4d668f msgid "BtnB" msgstr "" -#: ../../en/hardware/button.rst:16 6871714047aa4212b465e51f2e4e15e4 +#: ../../en/hardware/button.rst:16 09cbc5ee0baf4883b8580ab71a73be65 msgid "BtnC" msgstr "" -#: ../../en/hardware/button.rst:16 0365dd8e857e4ce5bce879d6205ef91a +#: ../../en/hardware/button.rst:16 2b9e56f32802432aaa7b41bcbec91c44 msgid "BtnPWR" msgstr "" -#: ../../en/hardware/button.rst:16 61ffa60c5ef1493b904ceb85b7e58b34 +#: ../../en/hardware/button.rst:16 f7145819fc6348e88c75400751c9fd40 msgid "BtnEXT" msgstr "" -#: ../../en/hardware/button.rst:18 b98247aeb9e04f3fabf558ec26ce3f86 +#: ../../en/hardware/button.rst:18 1c71bf19c9ef4d4da4ededd3dee51df4 msgid "AtomS3" msgstr "" #: ../../en/hardware/button.rst:18 ../../en/hardware/button.rst:20 #: ../../en/hardware/button.rst:22 ../../en/hardware/button.rst:24 #: ../../en/hardware/button.rst:26 ../../en/hardware/button.rst:28 -#: 200acfb0638a4d9387e27b5bdc2c8f9b 4e6c312f076d4575ae20764002340d65 -#: 6ff80c8db09b4fb3a7be88878b06caa3 829aaf0783904ecc9503f4070ba851d5 -#: a528be29fbac4021b6c0a49f11d8d097 acf53d36d44c4cb2859088094b91914b -#: c48b2b9059284451a6cefe4914943d52 de7f0a5d12884ac792a2ff2504b4434a +#: 091ca64e5b4345db8df50a137ee94ef9 2c2b80de6c2d45dd9601e3211f074dba +#: 3772fc2c4dc743179fe50c1f9e59d09c 834aab65451b4f66b039d83c1cb29597 +#: 9468a3e64db94daa9324b736a684ea93 ab15942d55704677969a39e1fbeef723 +#: ab3c869b601b4f95b196f52b6af3619e msgid "|S|" msgstr "" -#: ../../en/hardware/button.rst:20 97feb66f7bbe47e790c7b739bd183e39 +#: ../../en/hardware/button.rst:20 1859cf2c59734f86b5a7fef19243689a msgid "AtomS3 Lite" msgstr "" -#: ../../en/hardware/button.rst:22 3134f4cd6f9e43379c8d3f6e33c3a319 +#: ../../en/hardware/button.rst:22 6c31b1179e324d189ab6a7461ab52038 msgid "AtomS3U" msgstr "" -#: ../../en/hardware/button.rst:24 c55769bc2ee84d1783479d0e6706c652 +#: ../../en/hardware/button.rst:24 a93da48715bc45c4815b5875a683d8d4 msgid "StampS3" msgstr "" -#: ../../en/hardware/button.rst:26 e3f5491291f84508a55430ccadbcb815 +#: ../../en/hardware/button.rst:26 ee1afdf2c170422db392f668c002588d msgid "CoreS3" msgstr "" -#: ../../en/hardware/button.rst:28 e6a04b8e1ba14ae4a4a56347722d0c21 +#: ../../en/hardware/button.rst:28 e15d44c48d1b4d6cb547b902533052bc msgid "Core2" msgstr "" -#: ../../en/hardware/button.rst:30 7f0cbf9776f9427c8458fb728c1c2a2f +#: ../../en/hardware/button.rst:30 260b6749bbe644cc9ff909a1f30d09cd msgid "TOUGH" msgstr "" -#: ../../en/hardware/button.rst:36 17848dac8462441e9466afa91d17803a +#: ../../en/hardware/button.rst:36 c0f85be758b643748dd494884b0577e2 msgid "Micropython Example::" msgstr "" -#: ../../en/hardware/button.rst:61 19a3c6a1a8f045039f9f33942e4d5613 +#: ../../en/hardware/button.rst:61 f7f6bbfbb83b4289be59055287ae7d6d msgid "UIFLOW2 Example:" msgstr "" -#: ../../en/hardware/button.rst:63 e8b7a37c56dc4db5a3a9637caf9c8acf -msgid "|example.svg|" +#: ../../en/hardware/button.rst:63 2b94edd6b17e450bb94aecb84c7359b8 +msgid "|example.png|" msgstr "" -#: ../../en/refs/hardware.button.ref:1 77fbe04581bf498e92f9c833cc4857ea -msgid "example.svg" +#: ../../en/refs/hardware.button.ref:1 41f797bf788c4ed6af392ee0ee15f163 +msgid "example.png" msgstr "" -#: ../../en/hardware/button.rst:68 7abdec9d5de8463b8c1e2d6742df277b +#: ../../en/hardware/button.rst:68 db29a313f284421c9e4e6eeea4631fd1 msgid "|button_cores3_example.m5f2|" msgstr "" -#: ../../en/hardware/button.rst:72 bd1d6360768b4f42a9bbe440304e9233 +#: ../../en/hardware/button.rst:72 3b1960b3c1464e0b90793efe677e738a msgid "class Button" msgstr "" -#: ../../en/hardware/button.rst:76 8465973b1c2349b6a1e80e890d0e6882 +#: ../../en/hardware/button.rst:76 fdb545a1170a48b6bc0b41e5a5d178ec +#, fuzzy msgid "" -"Methods of Button Class heavily rely on ``M5.begin()`` |M5.begin.svg| and" -" ``M5.update()`` |M5.update.svg|." +"Methods of Button Class heavily rely on ``M5.begin()`` |M5.begin.png| and" +" ``M5.update()`` |M5.update.png|." msgstr "" "Button Class的方法重度依赖 ``M5.begin()`` |M5.begin.svg| 和 ``M5.update()`` " "|M5.update.svg|。" -#: ../../en/refs/system.ref:1 00d507d43de34719b240f779f59ef75a -#: 904ab1a9f7e140dda697dcefc6596361 -msgid "M5.begin.svg" +#: ../../en/refs/system.ref:1 1c2a4e96c9d54ba2ad4d5e3733012b1d +msgid "M5.begin.png" msgstr "" -#: ../../en/refs/system.ref:4 73a9fffc8df8498daf538b6daac11283 -#: 90dde20ec0f24ba98633980c3370ce48 -msgid "M5.update.svg" +#: ../../en/refs/system.ref:3 84c54c18473449ebaf2ae0d110cdcd19 +msgid "M5.update.png" msgstr "" -#: ../../en/hardware/button.rst:78 f4c947af2f2c42ac8325bae9dc5dd116 +#: ../../en/hardware/button.rst:78 ef2b2f21216741a99a679ecffbc8f285 +#, fuzzy msgid "" "All calls to methods of Button objects should be placed after " -"``M5.begin()`` |M5.begin.svg| and ``M5.update()`` |M5.update.svg| should " +"``M5.begin()`` |M5.begin.png| and ``M5.update()`` |M5.update.png| should " "be called in the main loop." msgstr "" "调用 Button 对象的所有方法,需要放在 ``M5.begin()`` |M5.begin.svg| 的后面,并在 主循环中调用 " "``M5.update()`` |M5.update.svg|。" -#: ../../en/hardware/button.rst:82 5675b4e33316459cbae254523afa1abb +#: ../../en/hardware/button.rst:82 b0496842fb054501849d92562dd6dc41 msgid "Methods" msgstr "" -#: ../../en/hardware/button.rst:86 bfe944418a564a1faa2a7aa04daed25c +#: ../../en/hardware/button.rst:86 903f4446c8254ef9ac697f8f8006c52c msgid "Returns whether the Button object is in a long press state." msgstr "返回 Button 对象是否处于长按状态。" @@ -152,170 +152,166 @@ msgstr "返回 Button 对象是否处于长按状态。" #: ../../en/hardware/button.rst:124 ../../en/hardware/button.rst:133 #: ../../en/hardware/button.rst:142 ../../en/hardware/button.rst:151 #: ../../en/hardware/button.rst:160 ../../en/hardware/button.rst:172 -#: 087426c20bfe4ccfb00bdc668d849d06 0bf299e4c167402c9bb9d01191709171 -#: 3d8c784a3d934248a22bf366580770c1 3fb60f1ab12c4fa8bd39619966bed984 -#: 4488131fc80a421c85fa52eb4b01bd2e 6794c88d5e634794bb303a23cc0c29f8 -#: 7cf1f52d342c423687efc8db33a6c108 7e1f93c0afd740dea4e22220767898f8 -#: a00717ea62af4b3bac059df4b73a2459 fcdc384dc0794f18bf9231ecd6b632e7 +#: fba9539e34d1421b8de4aa318a2b68e6 msgid "UIFLOW2:" msgstr "" -#: ../../en/hardware/button.rst:90 0d22c15d00f7461b80ccb24047999403 -msgid "|isHolding.svg|" +#: ../../en/hardware/button.rst:90 21e6f5d14126483bb35e6ad664d424fa +msgid "|isHolding.png|" msgstr "" -#: ../../en/refs/hardware.button.ref:3 1b990d9cbb824e20add97f3ea8e42ce6 -msgid "isHolding.svg" +#: ../../en/refs/hardware.button.ref:3 c00ecaeeb3c54df885659f1db7e245e7 +msgid "isHolding.png" msgstr "" -#: ../../en/hardware/button.rst:95 4b45ffdd2ee540028b73845c2dc9d175 +#: ../../en/hardware/button.rst:95 1f7c45eb8a674173930e49424a323ad1 msgid "Returns whether the Button object is in a pressed state." msgstr "返回 Button 对象是否处于按下状态。" -#: ../../en/hardware/button.rst:99 963db56f36f74a558b071629e59f136f -msgid "|isPressed.svg|" +#: ../../en/hardware/button.rst:99 7e5b4fc481db4181aa6dc6271510af83 +msgid "|isPressed.png|" msgstr "" -#: ../../en/refs/hardware.button.ref:5 5739113fb1614a2da7da099e0f35fe59 -msgid "isPressed.svg" +#: ../../en/refs/hardware.button.ref:5 6fa023643b1a4069bb2b204159db5ce9 +msgid "isPressed.png" msgstr "" -#: ../../en/hardware/button.rst:104 fd1bee5e24194fb6b04f6c5ce93a6831 +#: ../../en/hardware/button.rst:104 8266568ed24945f6aee151b3e0dbb394 msgid "Returns whether the Button object is in a released state." msgstr "返回 Button 对象是否处于松开状态。" -#: ../../en/hardware/button.rst:108 9ce8d843e09f48658e3db4e9d6d6ed44 -msgid "|isReleased.svg|" +#: ../../en/hardware/button.rst:108 394df28813254ab7bf3e3f94ac608cc0 +msgid "|isReleased.png|" msgstr "" -#: ../../en/refs/hardware.button.ref:7 13ed4f3eeaa74d9bb553cb99946cf6c2 -msgid "isReleased.svg" +#: ../../en/refs/hardware.button.ref:7 3a17cde67cd24a5a9bedf2fa620af659 +msgid "isReleased.png" msgstr "" -#: ../../en/hardware/button.rst:113 f45cec0a2a2c43999056d4bcfd2e330f +#: ../../en/hardware/button.rst:113 cef979bacc214f5a9b3ab5d96945d189 msgid "Returns True when the Button object is briefly pressed and released." msgstr "当 Button 对象被短暂按下并释放时返回 True。" -#: ../../en/hardware/button.rst:117 9de3c14345dd49eb80e0dde34335bed1 -msgid "|wasClicked.svg|" +#: ../../en/hardware/button.rst:117 efa00524d2ab4666897ec88e72572dab +msgid "|wasClicked.png|" msgstr "" -#: ../../en/refs/hardware.button.ref:9 a056c0c646da46ceac3c6bd7b1bf04eb -msgid "wasClicked.svg" +#: ../../en/refs/hardware.button.ref:9 d13ea5ee87d3497fa4178b9e335f0a29 +msgid "wasClicked.png" msgstr "" -#: ../../en/hardware/button.rst:122 4b3e072bbd054c0fb1838eaecc88493e +#: ../../en/hardware/button.rst:122 806287c2d16946a3b0e8ff09207a38f2 msgid "" "Returns True when the Button object is double-clicked after a certain " "amount of time." msgstr "当 Button 对象被双击后经过一段时间后返回 True。" -#: ../../en/hardware/button.rst:126 e31e76d258d64b928afe515d4ab1ad69 -msgid "|wasDoubleClicked.svg|" +#: ../../en/hardware/button.rst:126 cb7d53f41ac84953852270b0e3cbebf7 +msgid "|wasDoubleClicked.png|" msgstr "" -#: ../../en/refs/hardware.button.ref:11 017df4fd933f4cd58ebd43ac8775d4bf -msgid "wasDoubleClicked.svg" +#: ../../en/refs/hardware.button.ref:11 4a5feb297ea5421a8d02e100eba16ef3 +msgid "wasDoubleClicked.png" msgstr "" -#: ../../en/hardware/button.rst:131 960317e94f6344d2bafef9453b67cde8 +#: ../../en/hardware/button.rst:131 3ef32c26cf33466bae841711a97f3593 msgid "" "Returns True when the Button object is held down for a certain amount of " "time." msgstr "当 Button 对象被按住一段时间时返回 True。" -#: ../../en/hardware/button.rst:135 7f57820da4614bbb87770581e8873b92 -msgid "|wasHold.svg|" +#: ../../en/hardware/button.rst:135 874ae55c03b84e1384afd156e0f498f2 +msgid "|wasHold.png|" msgstr "" -#: ../../en/refs/hardware.button.ref:13 4542ac4aa4ab49f2b1eddf7e3d76bc17 -msgid "wasHold.svg" +#: ../../en/refs/hardware.button.ref:13 0de1d00b1ba147b0ade2c8409367a346 +msgid "wasHold.png" msgstr "" -#: ../../en/hardware/button.rst:140 12146383a712480eb864f93195e3437c +#: ../../en/hardware/button.rst:140 78f6a73868694e22acdfece71a78eb6e msgid "Returns True when the Button object is pressed." msgstr "当 Button 对象被按下时返回 True。" -#: ../../en/hardware/button.rst:144 7716c111b2294e6490636c65fe18a6f0 -msgid "|wasPressed.svg|" +#: ../../en/hardware/button.rst:144 20872fdc0b8947cc82390d55e404c7ac +msgid "|wasPressed.png|" msgstr "" -#: ../../en/refs/hardware.button.ref:15 f01ab5fde4a14d13b5b4069cdc9b86ae -msgid "wasPressed.svg" +#: ../../en/refs/hardware.button.ref:15 1c02dfb111864ca39f559993f67388ee +msgid "wasPressed.png" msgstr "" -#: ../../en/hardware/button.rst:149 ffc87d38fb3347af96e4dea07be1e5db +#: ../../en/hardware/button.rst:149 efcae6311a164b88b51c55a6f113d40a msgid "Returns True when the Button object is released." msgstr "当 Button 对象被松开时返回 True。" -#: ../../en/hardware/button.rst:153 b135736565f248038085d66241e47c96 -msgid "|wasReleased.svg|" +#: ../../en/hardware/button.rst:153 fbe99d901ea44be3a7a1e841416a981b +msgid "|wasReleased.png|" msgstr "" -#: ../../en/refs/hardware.button.ref:17 c08e3d63a64842db9c9e4bfa3f2bc4cd -msgid "wasReleased.svg" +#: ../../en/refs/hardware.button.ref:17 36110d5ef20f4fa9b338d2263dcb7356 +msgid "wasReleased.png" msgstr "" -#: ../../en/hardware/button.rst:158 d1440ffd2190447f9d8203cdb17bdbc4 +#: ../../en/hardware/button.rst:158 700259f387e0455f9fdc44955004d6c6 msgid "" "Returns True when the Button object is single-clicked after a certain " "amount of time." msgstr "当 Button 对象被单击后经过一段时间后返回 True。" -#: ../../en/hardware/button.rst:162 431e9a79d50449669a1542e64ad83a3f -msgid "|wasSingleClicked.svg|" +#: ../../en/hardware/button.rst:162 1e181380de5043dfb3f2e434c1115dc3 +msgid "|wasSingleClicked.png|" msgstr "" -#: ../../en/refs/hardware.button.ref:19 0544e4975830423284b4d225d65c5aed -msgid "wasSingleClicked.svg" +#: ../../en/refs/hardware.button.ref:19 44ab82320ce648a19a6c47a8d5078a8f +msgid "wasSingleClicked.png" msgstr "" -#: ../../en/hardware/button.rst:166 74757f9568dd4cfab625b6c6deef94d4 +#: ../../en/hardware/button.rst:166 489467daeb1f4e8d8007973844fe2580 msgid "Event Handling" msgstr "" -#: ../../en/hardware/button.rst:170 b9e58ee203a44e2abf233d6b3e141e57 +#: ../../en/hardware/button.rst:170 453a2928934a414eb0a67243868f111b msgid "Sets the event callback function." msgstr "设置事件回调函数" -#: ../../en/hardware/button.rst:174 172b777b13b54671be6273e4af01d684 -msgid "|setCallback.svg|" +#: ../../en/hardware/button.rst:174 e7094d63826a4608949deeb33f886652 +msgid "|setCallback.png|" msgstr "" -#: ../../en/refs/hardware.button.ref:21 84c214e7280c4dafba284680edf12843 -msgid "setCallback.svg" +#: ../../en/refs/hardware.button.ref:21 82f8fbe48a75456ba16f561801222ed6 +msgid "setCallback.png" msgstr "" #: ../../en/hardware/button.rst:178 ../../en/hardware/button.rst:189 -#: 3ab9ad228deb40608080f966028592f7 7609ff41bdaf41fbba459ce30d425e7c +#: 27d685ac523b4ae6ba37dea1f350eea3 msgid "Constants" msgstr "" -#: ../../en/hardware/button.rst:182 4cb1155bc13144cc89b8b7de735dd029 +#: ../../en/hardware/button.rst:182 c0c4a7e39d5d4a15ae79093216d4d576 msgid "A Callback_Type object." msgstr "" -#: ../../en/hardware/button.rst:186 94ba1b9f227f44a692daa0ac64bdf7b1 +#: ../../en/hardware/button.rst:186 e19bcf66b0d44125b3a596251bccc8c6 msgid "class Callback_Type" msgstr "一个 Callback_Type 对象。" -#: ../../en/hardware/button.rst:193 76a5c981c3ea4de2a2edb8294e072ee5 +#: ../../en/hardware/button.rst:193 94ea629a89e64e0ab441a48e9d3612d0 msgid "Single click event type." msgstr "单击事件类型。" -#: ../../en/hardware/button.rst:198 f918414f09f74833b2f7641814acf2ab +#: ../../en/hardware/button.rst:198 af52fdc2d8114c93ab737a0bcfaf9dad msgid "Double click event type." msgstr "双击事件类型。" -#: ../../en/hardware/button.rst:203 cce89872b09247f9a41f679658485e1a +#: ../../en/hardware/button.rst:203 28b0338cbfdb46a880e46175a522cefc msgid "Long press event type." msgstr "长按事件类型。" -#: ../../en/hardware/button.rst:209 a7caf4cc6136467dac2295f594079369 +#: ../../en/hardware/button.rst:209 94568f6c1312471c92ab361dcbc0891e msgid "Press event type" msgstr "按下事件类型。" -#: ../../en/hardware/button.rst:213 3f890f7b621e47e49ec7c739f0bb4aa2 +#: ../../en/hardware/button.rst:213 f6176c04341f4561955a0545feb09021 msgid "Release event type" msgstr "" @@ -384,3 +380,75 @@ msgstr "" #~ msgid ":download:`example.m5f2 <../../_static/hardware/button/example.m5f2>`" #~ msgstr "" +#~ msgid "|example.svg|" +#~ msgstr "" + +#~ msgid "example.svg" +#~ msgstr "" + +#~ msgid "M5.begin.svg" +#~ msgstr "" + +#~ msgid "M5.update.svg" +#~ msgstr "" + +#~ msgid "|isHolding.svg|" +#~ msgstr "" + +#~ msgid "isHolding.svg" +#~ msgstr "" + +#~ msgid "|isPressed.svg|" +#~ msgstr "" + +#~ msgid "isPressed.svg" +#~ msgstr "" + +#~ msgid "|isReleased.svg|" +#~ msgstr "" + +#~ msgid "isReleased.svg" +#~ msgstr "" + +#~ msgid "|wasClicked.svg|" +#~ msgstr "" + +#~ msgid "wasClicked.svg" +#~ msgstr "" + +#~ msgid "|wasDoubleClicked.svg|" +#~ msgstr "" + +#~ msgid "wasDoubleClicked.svg" +#~ msgstr "" + +#~ msgid "|wasHold.svg|" +#~ msgstr "" + +#~ msgid "wasHold.svg" +#~ msgstr "" + +#~ msgid "|wasPressed.svg|" +#~ msgstr "" + +#~ msgid "wasPressed.svg" +#~ msgstr "" + +#~ msgid "|wasReleased.svg|" +#~ msgstr "" + +#~ msgid "wasReleased.svg" +#~ msgstr "" + +#~ msgid "|wasSingleClicked.svg|" +#~ msgstr "" + +#~ msgid "wasSingleClicked.svg" +#~ msgstr "" + +#~ msgid "|setCallback.svg|" +#~ msgstr "" + +#~ msgid "setCallback.svg" +#~ msgstr "" + diff --git a/docs/locales/zh_CN/LC_MESSAGES/hardware/can.po b/docs/locales/zh_CN/LC_MESSAGES/hardware/can.po index 91660f8c..4702e701 100644 --- a/docs/locales/zh_CN/LC_MESSAGES/hardware/can.po +++ b/docs/locales/zh_CN/LC_MESSAGES/hardware/can.po @@ -9,7 +9,7 @@ msgid "" msgstr "" "Project-Id-Version: UIFlow2 Programming Guide \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-01-16 15:35+0800\n" +"POT-Creation-Date: 2025-04-18 15:58+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language: zh_CN\n" @@ -20,11 +20,11 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.16.0\n" -#: ../../en/hardware/can.rst:4 e59f53c2f741489cbecf23d943cff652 +#: ../../en/hardware/can.rst:4 bf007143a9d84c6c81b6ef0bb336ef50 msgid "CAN" msgstr "" -#: ../../en/hardware/can.rst:8 6687cbe59a6e43dd9a26c9b54c819f40 +#: ../../en/hardware/can.rst:8 27681edb31d347bca18400c0fc3ff475 msgid "" "CAN implements support for classic CAN (available on F4, F7 MCUs) and CAN" " FD (H7 series) controllers. At the physical level CAN bus consists of 2 " @@ -33,21 +33,21 @@ msgid "" "to the correct voltage levels on the bus." msgstr "" -#: ../../en/hardware/can.rst:13 65c441f4c4414e3090f494864d1fa87f +#: ../../en/hardware/can.rst:13 8adfba911cfd49de88053090c60d79bb msgid "" "Example usage for classic CAN controller in Loopback (transceiver-less) " "mode::" msgstr "" -#: ../../en/hardware/can.rst:21 d7fe8b4fdf1c4681a04e2b5e55800c2b +#: ../../en/hardware/can.rst:21 6c1ae436a166428a8472f14620789080 msgid "For detailed examples, please refer to: :ref:`unit.CANUnit `" msgstr "" -#: ../../en/hardware/can.rst:25 5d395d14069d46a8aef22a3d1d8aaa32 +#: ../../en/hardware/can.rst:25 e6d38ffd96164146bf46086de182efff msgid "Constructors" msgstr "" -#: ../../en/hardware/can.rst:29 0ae7c738db2d4bdb903174febf18f168 +#: ../../en/hardware/can.rst:29 94d75f2a91564806bc85d05665864b85 msgid "" "Construct a CAN object on the given bus. *bus* must be 0. With no " "additional parameters, the CAN object is created but not initialised (it " @@ -60,82 +60,82 @@ msgstr "" #: ../../en/hardware/can.rst:97 ../../en/hardware/can.rst:111 #: ../../en/hardware/can.rst:135 ../../en/hardware/can.rst:144 #: ../../en/hardware/can.rst:183 ../../en/hardware/can.rst:212 -#: 0ee13c11a8f648cf99f4457eda9b5f22 6982ecdcdd97432e88353665590766fb -#: 6f3e9426f6cc4bc984b14a6024c01498 773ba0eca22242268c20351890788d17 -#: 80146832eca14131801fe3f8b307cff1 969082de81f14a98a39da9f2a92c2e48 -#: d2d6c412f69c43558e7616047843ad42 d2f9ec58175f46e1916a8835cedbbaa0 +#: 297ef924db8f4b0eaaf61b3e5e5b836a 5ab95dd922db42e4adaa01e8b9aa0fcb +#: 5c0196ada7fc42fb9cbcd440d245441f 60742619dc6f4398bf1f1b37fb87d2e4 +#: 71b734d30590454a8e665c7eead0d8fe 82b7ea485a8c43e09e56592fbf6eb6a8 +#: bae22cb2084342be9037cd5e1c8c8e06 c770affa2b8f43ca847fac3428f84b2f msgid "UIFLOW2:" msgstr "" -#: ../../en/hardware/can.rst:37 83d7649e40ac4510a618b864eb1db6d2 -msgid "|init.svg|" +#: ../../en/hardware/can.rst:37 a556321b20ad40b0b614801152dbf502 +msgid "|init.png|" msgstr "" -#: ../../en/refs/hardware.can.ref:1 08f88f5d4c654d4fbc11316a380a3a59 -msgid "init.svg" +#: ../../en/refs/hardware.can.ref:1 c597ed8374894d3c952c692977896b5d +msgid "init.png" msgstr "" -#: ../../en/hardware/can.rst:41 4cc4958d749348f5a953083b40d26214 +#: ../../en/hardware/can.rst:41 66fa2bbedc7a4347a8835f282a59fa4d msgid "Methods" msgstr "" -#: ../../en/hardware/can.rst:45 997ed42770ab49989ce451340bce7cae +#: ../../en/hardware/can.rst:45 8a30991eaf1f438984ec375eef0c1139 msgid "Initialise the CAN bus with the given parameters:" msgstr "" -#: ../../en/hardware/can.rst:47 8ed1e741de4f4a2ea5603c9e46a5d71a +#: ../../en/hardware/can.rst:47 286a3cce887f44c7a761bc82ba310efc msgid "*mode* is one of: NORMAL, NO_ACKNOWLEDGE, LISTEN_ONLY" msgstr "" -#: ../../en/hardware/can.rst:48 2bd1f9a4212944b8ad456ad606bec24a +#: ../../en/hardware/can.rst:48 cf394c6c75ce457b88d2f5771ec2f0bd msgid "*tx* is the pin to use for transmitting data" msgstr "" -#: ../../en/hardware/can.rst:49 a55c3e13d5f94c86b8c8206fb8c07cb2 +#: ../../en/hardware/can.rst:49 87e723c624d2494bb2d6c94a90ebcace msgid "*rx* is the pin to use for receiving data" msgstr "" -#: ../../en/hardware/can.rst:50 abe37c842342420498b1f3a262d14c02 +#: ../../en/hardware/can.rst:50 c48eb3c28c654dce8e6b2cc7cec315b7 msgid "" "*prescaler* is the value by which the CAN input clock is divided to " "generate the nominal bit time quanta. The prescaler can be a value " "between 1 and 1024 inclusive for classic CAN." msgstr "" -#: ../../en/hardware/can.rst:53 39835b60e7684298aa6392fe987d6f95 +#: ../../en/hardware/can.rst:53 c4a9bbaf79754c50af279a801e0087a4 msgid "" "*sjw* is the resynchronisation jump width in units of time quanta for " "nominal bits; it can be a value between 1 and 4 inclusive for classic " "CAN." msgstr "" -#: ../../en/hardware/can.rst:55 7939c0149db9464e9cf7d021cce0435f +#: ../../en/hardware/can.rst:55 d266253dbf8d432197c5631a6723c115 msgid "" "*bs1* defines the location of the sample point in units of the time " "quanta for nominal bits; it can be a value between 1 and 16 inclusive for" " classic CAN." msgstr "" -#: ../../en/hardware/can.rst:57 9bb203ab1c7a4edc8effc09c32c4a3ac +#: ../../en/hardware/can.rst:57 72fa587c0ce94da3bee406a1ad78f0de msgid "" "*bs2* defines the location of the transmit point in units of the time " "quanta for nominal bits; it can be a value between 1 and 8 inclusive for " "classic CAN." msgstr "" -#: ../../en/hardware/can.rst:59 9c102a8011034efe9b2ce6ac7c3be600 +#: ../../en/hardware/can.rst:59 79b21ae9a3784c859dcac228b656b7ca msgid "" "*triple_sampling* is Enables triple sampling when the TWAI controller " "samples a bit" msgstr "" -#: ../../en/hardware/can.rst:62 035fabb97ac54e60a0470e5f8653023c +#: ../../en/hardware/can.rst:62 2a27169f6c394c7d8a535e2dd89e76b1 msgid "" "The time quanta tq is the basic unit of time for the CAN bus. tq is the " "CAN prescaler value divided by APB_CLK clock source (typically 80 MHz);" msgstr "" -#: ../../en/hardware/can.rst:65 d27f29077e8c40f596581518c7c5befc +#: ../../en/hardware/can.rst:65 2d57cc9ca3084a0baa702364af74b9f3 msgid "" "A single bit is made up of the synchronisation segment, which is always 1" " tq. Then follows bit segment 1, then bit segment 2. The sample point is" @@ -144,36 +144,36 @@ msgid "" "+ BS2 multiplied by the time quanta tq." msgstr "" -#: ../../en/hardware/can.rst:71 b4982058e3634ee6a5b2ff96ff3135b5 +#: ../../en/hardware/can.rst:71 ef9593b76320404eb25f181e7493a92f msgid "" "For example, with APB_CLK=80MHz, prescaler=32, sjw=3, bs1=15, bs2=4, the " "value of tq is 0.4 microseconds. The bittime is 8 microseconds, and the " "baudrate is 125kHz." msgstr "" -#: ../../en/hardware/can.rst:75 926933526d53474f94be5d6347bcf5ba +#: ../../en/hardware/can.rst:75 bd284e5722aa4b0f94c108d17d9d8bdf msgid "See esp32 technical reference manual for more details." msgstr "" -#: ../../en/hardware/can.rst:79 61788bd560f34e6787176e769c18c5f9 +#: ../../en/hardware/can.rst:79 632db63a4e1a4e8fbb9595837d4297c0 msgid "Turn off the CAN bus." msgstr "" -#: ../../en/hardware/can.rst:83 c6e17c7d3c694765810a0b36f487aa16 -msgid "|deinit.svg|" +#: ../../en/hardware/can.rst:83 379d78b2fb744e5db526c7a45c6f1517 +msgid "|deinit.png|" msgstr "" -#: ../../en/refs/hardware.can.ref:3 892509bab86a4eedbeaee7c3386c2af6 -msgid "deinit.svg" +#: ../../en/refs/hardware.can.ref:3 0d4093a6dfd145cdb8f8d15db29cdcbe +msgid "deinit.png" msgstr "" -#: ../../en/hardware/can.rst:88 ad40c54cb0e04863bc056fdc9e9d97ed +#: ../../en/hardware/can.rst:88 6dd16ef5cc5c4d1f9df4860b8280260c msgid "" "Force a software restart of the CAN controller without resetting its " "configuration." msgstr "" -#: ../../en/hardware/can.rst:91 9199e47eb4db4f068ae40a6eb2c8b576 +#: ../../en/hardware/can.rst:91 3385795e4b8f407b87fe112312b34713 msgid "" "If the controller enters the bus-off state then it will no longer " "participate in bus activity. If the controller is not configured to " @@ -182,45 +182,45 @@ msgid "" "protocol to leave the bus-off state and go into the error active state." msgstr "" -#: ../../en/hardware/can.rst:99 d89b8855bab9474ab7380f9bf423a46c -msgid "|restart.svg|" +#: ../../en/hardware/can.rst:99 54dec484c78e4486b17397dbb8db8140 +msgid "|restart.png|" msgstr "" -#: ../../en/refs/hardware.can.ref:5 9b9503a656184a7b9edfc10f6db5ec4c -msgid "restart.svg" +#: ../../en/refs/hardware.can.ref:5 082b319f4cb74f449d7a9b47623ef804 +msgid "restart.png" msgstr "" -#: ../../en/hardware/can.rst:103 fd119898d27d4a1e9e9e11560d46f9cc +#: ../../en/hardware/can.rst:103 0a192e64013b455a8e42da84a1e4e7fb msgid "Return the state of the controller. The return value can be one of:" msgstr "" -#: ../../en/hardware/can.rst:105 6b08f0e07e8a4777a0479101ea70b379 +#: ../../en/hardware/can.rst:105 beb0f08a943b403198d0a85c75eeda19 msgid "``CAN.STOPPED`` -- the controller is completely off and reset;" msgstr "" -#: ../../en/hardware/can.rst:106 745d875a5a0b4f44913a916068163490 +#: ../../en/hardware/can.rst:106 751c7afbf76f4792a42f616b44b5016f msgid "``CAN.RUNNING`` -- The controller can transmit and receive messages;" msgstr "" -#: ../../en/hardware/can.rst:107 47d254e43a4c4555876f4e506e6e3909 +#: ../../en/hardware/can.rst:107 39a10d334cb149a581e0d0aa5b7fb4c7 msgid "" "``CAN.BUS_OFF`` -- the controller is on but not participating in bus " "activity (TEC overflowed beyond 255);" msgstr "" -#: ../../en/hardware/can.rst:109 8c347f2379024dbb896ffefd1795622f +#: ../../en/hardware/can.rst:109 501a958252ac463ca795710cacc191f3 msgid "``RECOVERING`` -- The controller is undergoing bus recovery." msgstr "" -#: ../../en/hardware/can.rst:113 9526977166c143299abfdbf7ecfd36f0 -msgid "|state.svg|" +#: ../../en/hardware/can.rst:113 ffce2bb0357544e899ea21e2e2d85c50 +msgid "|state.png|" msgstr "" -#: ../../en/refs/hardware.can.ref:7 53e85617bc8b443b907287620328b1ed -msgid "state.svg" +#: ../../en/refs/hardware.can.ref:7 44d3bbe075a44cd18dd12f944eb76f5e +msgid "state.png" msgstr "" -#: ../../en/hardware/can.rst:118 7993c15595354eedb39d208e51b47171 +#: ../../en/hardware/can.rst:118 0c785b4eaf92480fb21f03a9a246672a msgid "" "Get information about the controller's error states and TX and RX " "buffers. If *list* is provided then it should be a list object with at " @@ -229,117 +229,117 @@ msgid "" "value of the method is the populated list." msgstr "" -#: ../../en/hardware/can.rst:124 ee2c0f399a9743b895959b527f167c63 +#: ../../en/hardware/can.rst:124 0191ddb873f741e2857475b8cf2ad1cd msgid "The values in the list are:" msgstr "" -#: ../../en/hardware/can.rst:126 b52263b9d6ef489f9dc1d1c6ef1489ac +#: ../../en/hardware/can.rst:126 37a4e9d210494b01ac328f9ff2b7ca5b msgid "TEC value" msgstr "" -#: ../../en/hardware/can.rst:127 f0489c4273544600a1a3bc462b0764c3 +#: ../../en/hardware/can.rst:127 d3fad10949434ec19642e23ace2895db msgid "REC value" msgstr "" -#: ../../en/hardware/can.rst:128 06f0166fd1b24b368be389b5f770ed3a +#: ../../en/hardware/can.rst:128 7325599351a74905805b71b0cf4fbbdb msgid "" "number of times the controller enterted the Error Warning state(ignored " "for now, compatible with pyb.CAN)" msgstr "" -#: ../../en/hardware/can.rst:129 c81ec217a2414283b6438f55582f1f32 +#: ../../en/hardware/can.rst:129 227ffe888c334cc7a55d8e1aeb2cd3ca msgid "" "number of times the controller enterted the Error Passive state(ignored " "for now, compatible with pyb.CAN)" msgstr "" -#: ../../en/hardware/can.rst:130 4c8bb292dde847059acd96d2b063da15 +#: ../../en/hardware/can.rst:130 469268cbfefa46ea84656ce7bf735867 msgid "" "number of times the controller enterted the Bus Off state(ignored for " "now, compatible with pyb.CAN)" msgstr "" -#: ../../en/hardware/can.rst:131 b1f6fb4a8b0647f89d71fd4b674a3f5c +#: ../../en/hardware/can.rst:131 d55354bb43ac40f3af585330a8b7bfb8 msgid "number of pending TX messages" msgstr "" -#: ../../en/hardware/can.rst:132 93ff88ce2b264c4a99e887d7b4842c77 +#: ../../en/hardware/can.rst:132 46e91ee147a84ae8999b7c9aeb66575b msgid "number of pending RX messages" msgstr "" -#: ../../en/hardware/can.rst:133 ce6319bf457f4b878cd86fae491bdf4e +#: ../../en/hardware/can.rst:133 59bce7a654f347ee94e4370daa0c2a83 msgid "" "number of pending RX messages on fifo 1(ignored for now, compatible with " "pyb.CAN)" msgstr "" -#: ../../en/hardware/can.rst:137 bb3cdc5c451541408f6cfa9ed63993e1 -msgid "|info.svg|" +#: ../../en/hardware/can.rst:137 d45c6a8daebc4343b4869580d1d38804 +msgid "|info.png|" msgstr "" -#: ../../en/refs/hardware.can.ref:9 6938a4471199457a8f2fcd91fe4ed6d4 -msgid "info.svg" +#: ../../en/refs/hardware.can.ref:9 66776b69a597431e930042ca8ad27a6a +msgid "info.png" msgstr "" -#: ../../en/hardware/can.rst:142 640e5e03754f433d828b317b0d981745 +#: ../../en/hardware/can.rst:142 2284c2d77f5b41cd9d5a6ab790a2a3cb msgid "Return ``True`` if any message waiting on the FIFO, else ``False``." msgstr "" -#: ../../en/hardware/can.rst:146 64e0b6882d714cd594efb2cbcb09e2f8 -msgid "|any.svg|" +#: ../../en/hardware/can.rst:146 a1fe20d87ce64fa19d15c001ac2daa32 +msgid "|any.png|" msgstr "" -#: ../../en/refs/hardware.can.ref:11 0b0b65a6447d46c683880a33003a99a9 -msgid "any.svg" +#: ../../en/refs/hardware.can.ref:11 86ae9e1746194b609616a19821019d3d +msgid "any.png" msgstr "" -#: ../../en/hardware/can.rst:151 63d65ef178054887b25eefe5be07e4fa +#: ../../en/hardware/can.rst:151 fe37a67d810e474fad4c00db935e0785 msgid "Receive data on the bus:" msgstr "" -#: ../../en/hardware/can.rst:153 37eb98fd4c93424d864ac493938f6c65 +#: ../../en/hardware/can.rst:153 246e3f39a9ea4457a497521962e6e9ec msgid "*fifo* is an integer, it can be any number and compatible with pyb.CAN" msgstr "" -#: ../../en/hardware/can.rst:154 1a02cedab5624cf4afc26106d6fba7ae +#: ../../en/hardware/can.rst:154 62eaf06634a3435c9ef78d834a300cdc msgid "*list* is an optional list object to be used as the return value" msgstr "" -#: ../../en/hardware/can.rst:155 22acc374629e4ba6a331e4338101ea7e +#: ../../en/hardware/can.rst:155 85cd36ea98e9445295afabd65fb75a22 msgid "*timeout* is the timeout in milliseconds to wait for the receive." msgstr "" -#: ../../en/hardware/can.rst:157 b02f4a2e8f72412c861563f955b219e2 +#: ../../en/hardware/can.rst:157 b9451f2fd00f42808a3e2ed274435a69 msgid "Return value: A tuple containing five values." msgstr "" -#: ../../en/hardware/can.rst:159 21e8220046b04b2691de6296131dc9e3 +#: ../../en/hardware/can.rst:159 acff02e795094555b12edc5528c261a3 msgid "The id of the message." msgstr "" -#: ../../en/hardware/can.rst:160 dfd5ac9fb7fa42068304ec96c738f54b +#: ../../en/hardware/can.rst:160 f46485c0e29444828401bba90d9f35a5 msgid "A boolean that indicates if the message ID is standard or extended." msgstr "" -#: ../../en/hardware/can.rst:161 d88fef32b1744c928d661fa408188144 +#: ../../en/hardware/can.rst:161 9067d8e269414edd859b6febbee7cee4 msgid "A boolean that indicates if the message is an RTR message." msgstr "" -#: ../../en/hardware/can.rst:162 e00c927829c144cfb299461fbb138e73 +#: ../../en/hardware/can.rst:162 781765c949424ef590bb63209805d4e4 msgid "The FMI (Filter Match Index) value." msgstr "" -#: ../../en/hardware/can.rst:163 98461f9ac7714aeab5ae8867acd15257 +#: ../../en/hardware/can.rst:163 77423320519e446c81d45b6a746fc01e msgid "An array containing the data." msgstr "" -#: ../../en/hardware/can.rst:165 2fd6bca9a26d44d28361eb61685f945c +#: ../../en/hardware/can.rst:165 136baf89c1634f4ca03931aada23b67a msgid "" "If *list* is ``None`` then a new tuple will be allocated, as well as a " "new bytes object to contain the data (as the fifth element in the tuple)." msgstr "" -#: ../../en/hardware/can.rst:168 bcb97a81dd264b88af312ac6f1be257b +#: ../../en/hardware/can.rst:168 162e413e51824189acdc6edcbad14cec msgid "" "If *list* is not ``None`` then it should be a list object with a least " "five elements. The fifth element should be a memoryview object which is " @@ -352,39 +352,39 @@ msgid "" "data without using the heap. For example::" msgstr "" -#: ../../en/hardware/can.rst:185 fe817d36cb314c7ea5c5de7fdb5cafa9 -msgid "|recv1.svg|" +#: ../../en/hardware/can.rst:185 7549b0a9bfaf444b9736ca8064fd5c04 +msgid "|recv1.png|" msgstr "" -#: ../../en/refs/hardware.can.ref:13 1aff592bc62e41d4ac787d136569319f -msgid "recv1.svg" +#: ../../en/refs/hardware.can.ref:13 4467ff291cc448e295672a56f40b9507 +msgid "recv1.png" msgstr "" -#: ../../en/hardware/can.rst:187 197bee05e9db4187b33921d72c25c214 -msgid "|recv2.svg|" +#: ../../en/hardware/can.rst:187 c4177bbe678e45a28b675935f89910a5 +msgid "|recv2.png|" msgstr "" -#: ../../en/refs/hardware.can.ref:15 daf479a7a40c4e1cbf0f2d10aed9d8a0 -msgid "recv2.svg" +#: ../../en/refs/hardware.can.ref:15 11e0e62db17d4869a824cdb7e9107a54 +msgid "recv2.png" msgstr "" -#: ../../en/hardware/can.rst:192 03a6a1752a7145c08d99870351d63331 +#: ../../en/hardware/can.rst:192 8a138825e5444eba831b9d72699e3747 msgid "Send a message on the bus:" msgstr "" -#: ../../en/hardware/can.rst:194 c7b460e07b884249b37933b563a4f265 +#: ../../en/hardware/can.rst:194 b335c81e37314a3190ed064247ca39dc msgid "*data* is the data to send (an integer to send, or a buffer object)." msgstr "" -#: ../../en/hardware/can.rst:195 abd50fe542d64df290c5a3fc96a22ce9 +#: ../../en/hardware/can.rst:195 341c4a2fb1af49058d957d05077aac87 msgid "*id* is the id of the message to be sent." msgstr "" -#: ../../en/hardware/can.rst:196 5773623f39a4457382227b698722cbe0 +#: ../../en/hardware/can.rst:196 cc4eda959aec42ab8729530ed4ffdfe9 msgid "*timeout* is the timeout in milliseconds to wait for the send." msgstr "" -#: ../../en/hardware/can.rst:197 283999c572aa4258a8c2bf2fa76b8b26 +#: ../../en/hardware/can.rst:197 370e58e51a0e4ef18dc67dd0811606f9 msgid "" "*rtr* is a boolean that specifies if the message shall be sent as a " "remote transmission request. If *rtr* is True then only the length of " @@ -392,13 +392,13 @@ msgid "" "*data* are unused." msgstr "" -#: ../../en/hardware/can.rst:201 6261427d28fb4cecba1be78c002678c7 +#: ../../en/hardware/can.rst:201 8bb4db20810840deb31257d25b90eb6f msgid "" "*extframe* if True the frame will have an extended identifier (29 bits), " "otherwise a standard identifier (11 bits) is used." msgstr "" -#: ../../en/hardware/can.rst:204 7be585f2b42843a0842c38645e0ff3b0 +#: ../../en/hardware/can.rst:204 2a9a8b7bd66e4ba4bc46e4e61786e53a msgid "" "If timeout is 0 the message is placed in a buffer in one of three " "hardware buffers and the method returns immediately. If all three buffers" @@ -407,27 +407,81 @@ msgid "" "within the specified time an exception is thrown." msgstr "" -#: ../../en/hardware/can.rst:210 114036b9f9b04d2ea1b0b7a2dda5245c +#: ../../en/hardware/can.rst:210 3f38362a61ab47548404a59924459f9f msgid "Return value: ``None``." msgstr "" -#: ../../en/hardware/can.rst:214 28684fc15ceb4eb5bf9f83db93d95b10 -msgid "|send.svg|" +#: ../../en/hardware/can.rst:214 a1e97dad94824f15b11855723d89a2ca +msgid "|send.png|" msgstr "" -#: ../../en/refs/hardware.can.ref:17 a78b3a4c44f840ec8722ba8572696ac0 -msgid "send.svg" +#: ../../en/refs/hardware.can.ref:17 069ee0a50ef74078af99d4bc68e12bdb +msgid "send.png" msgstr "" -#: ../../en/hardware/can.rst:218 1a3fb34611944eddb097f65035f0acdf +#: ../../en/hardware/can.rst:218 026225c94025468c868bc2c68bfb04e0 msgid "Constants" msgstr "" -#: ../../en/hardware/can.rst:224 3a59c432963a46f087a31ee6eec042fd +#: ../../en/hardware/can.rst:224 bca16eaecc9c433483563dd87f0328f3 msgid "The mode of the CAN bus used in :meth:`~CAN.init()`." msgstr "" -#: ../../en/hardware/can.rst:232 eeeeb93c1070415fa30acc849d49b1ce +#: ../../en/hardware/can.rst:232 509155617aae47d0ac366faf15628c58 msgid "Possible states of the CAN controller returned from :meth:`~CAN.state()`." msgstr "" +#~ msgid "|init.svg|" +#~ msgstr "" + +#~ msgid "init.svg" +#~ msgstr "" + +#~ msgid "|deinit.svg|" +#~ msgstr "" + +#~ msgid "deinit.svg" +#~ msgstr "" + +#~ msgid "|restart.svg|" +#~ msgstr "" + +#~ msgid "restart.svg" +#~ msgstr "" + +#~ msgid "|state.svg|" +#~ msgstr "" + +#~ msgid "state.svg" +#~ msgstr "" + +#~ msgid "|info.svg|" +#~ msgstr "" + +#~ msgid "info.svg" +#~ msgstr "" + +#~ msgid "|any.svg|" +#~ msgstr "" + +#~ msgid "any.svg" +#~ msgstr "" + +#~ msgid "|recv1.svg|" +#~ msgstr "" + +#~ msgid "recv1.svg" +#~ msgstr "" + +#~ msgid "|recv2.svg|" +#~ msgstr "" + +#~ msgid "recv2.svg" +#~ msgstr "" + +#~ msgid "|send.svg|" +#~ msgstr "" + +#~ msgid "send.svg" +#~ msgstr "" + diff --git a/docs/locales/zh_CN/LC_MESSAGES/hardware/display.po b/docs/locales/zh_CN/LC_MESSAGES/hardware/display.po index 7d1fcd12..af891c0b 100644 --- a/docs/locales/zh_CN/LC_MESSAGES/hardware/display.po +++ b/docs/locales/zh_CN/LC_MESSAGES/hardware/display.po @@ -9,7 +9,7 @@ msgid "" msgstr "" "Project-Id-Version: UIFlow2 Programming Guide \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-01-16 15:35+0800\n" +"POT-Creation-Date: 2025-04-03 10:58+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language: zh_CN\n" @@ -20,602 +20,635 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.16.0\n" -#: ../../en/hardware/display.rst:2 f9f901c90ea5462999aa9971aa7109ee +#: ../../en/hardware/display.rst:4 1144f0b75e6d44dd9182da08d5c66242 msgid "Display" msgstr "" -#: ../../en/hardware/display.rst:4 c930a24fb88a40e18e16b5f714df0cc1 +#: ../../en/hardware/display.rst:6 292206cb76dd4b86b47b585788b38497 msgid "A lcd display library" msgstr "" -#: ../../en/hardware/display.rst:11 58688d274c224f7c9579da6bddaa8d38 +#: ../../en/hardware/display.rst:13 4f7e88f934584be1b92aa6bca6bf86a8 msgid "Micropython Example" msgstr "" -#: ../../en/hardware/display.rst:14 a171c1a975b1431ebd6e9a0d88981dda +#: ../../en/hardware/display.rst:16 3e1179ec25b74f0bb50c6aa687c1a261 msgid "draw test" msgstr "" -#: ../../en/hardware/display.rst:66 620faa4bd9a945e9a3d21b0329686bbc +#: ../../en/hardware/display.rst:68 ed7db7582ed24d7f8e4b8c9109f4fc45 msgid "Functions" msgstr "" -#: ../../en/hardware/display.rst:70 012abc1d943f4c3aae431fc5a70707d1 +#: ../../en/hardware/display.rst:72 5d4da6f86dc34c5f99e324fb82bfbc60 msgid "Get the horizontal resolution of the display." msgstr "" -#: ../../en/hardware/display.rst:72 3d4f1b563d6c4fa48150ccb13b767ac2 +#: ../../en/hardware/display.rst:74 964bc36b5e8843ec8e6a9f1469b684da msgid "" "Returns An integer representing the horizontal resolution (width) in " "pixels." msgstr "" -#: ../../en/hardware/display.rst:76 fdbf318ae2ef4d9a96a4dfcae7916991 +#: ../../en/hardware/display.rst:78 adccf65031e44f1292db39cb10e98f04 msgid "Get the vertical resolution of the display." msgstr "" -#: ../../en/hardware/display.rst:78 32959bcdaae44a858565002a03b17e2f +#: ../../en/hardware/display.rst:80 4a8b307f94b9409c8dbd3f5997adc881 msgid "" "Returns An integer representing the vertical resolution (height) in " "pixels." msgstr "" -#: ../../en/hardware/display.rst:82 c979bc054db545668724d6172da044a7 +#: ../../en/hardware/display.rst:84 0268c3e6c54547ac91b0b9fc57ac8b58 msgid "Get the current rotation of the display." msgstr "" -#: ../../en/hardware/display.rst:84 3ad5be8e84494274a0f3c47ab18cf715 +#: ../../en/hardware/display.rst:86 f9866b7669114b0abb7ff77d4f0155fe msgid "Returns An integer representing the display's rotation:" msgstr "" -#: ../../en/hardware/display.rst:86 ../../en/hardware/display.rst:113 -#: 3be1ec2b2f634b679e857b3b53958af1 8cb0fb2116b04e3a81c48004acf01a78 +#: ../../en/hardware/display.rst:88 ../../en/hardware/display.rst:115 +#: 33efa20d8dac4b28b612bec7fb31efed 3dd7de9babf541a38a01b18b33830b98 msgid "``1``: 0° rotation" msgstr "" -#: ../../en/hardware/display.rst:87 ../../en/hardware/display.rst:114 -#: 7cb6b315a7fc4450bef41d758c3f34c9 cff78c2f228c4fdd8348fd639245a2c6 +#: ../../en/hardware/display.rst:89 ../../en/hardware/display.rst:116 +#: 3f16b99bfc85490986676b259f65714f 66d53eee62a34a9db1f037123a4e0f05 msgid "``2``: 90° rotation" msgstr "" -#: ../../en/hardware/display.rst:88 ../../en/hardware/display.rst:115 -#: 1868463439a84cc8b4548178457c5949 9624c0a81cfc41478483b4eb2015e55d +#: ../../en/hardware/display.rst:90 ../../en/hardware/display.rst:117 +#: 0e8f3509025f474e897895a034724b2d fb3f6b1b88d8480e8a7b2a5fe196e4b9 msgid "``3``: 180° rotation" msgstr "" -#: ../../en/hardware/display.rst:89 ../../en/hardware/display.rst:116 -#: 37a32424d485433c9259cf539bfa393c 808bca6f44c0451e89e89490873b70bb +#: ../../en/hardware/display.rst:91 ../../en/hardware/display.rst:118 +#: 70b952cf1148452bbc4d2303bff4dff9 afe0828ae5014383be6b479330e42c8e msgid "``4``: 270° rotation" msgstr "" -#: ../../en/hardware/display.rst:93 8516dee8fb044f7f9fe7b275a5c40594 +#: ../../en/hardware/display.rst:95 faa759de24dd4eb7ba0f1a8add37d354 msgid "Get the color depth of the display." msgstr "" -#: ../../en/hardware/display.rst:95 c166f8f151cf481aa5e02a8560fb4a53 +#: ../../en/hardware/display.rst:97 3e12f142582c4b9eadebdd86955535f4 msgid "Returns An integer representing the display's color depth in bits." msgstr "" -#: ../../en/hardware/display.rst:109 a33b0210f5d1457da1c2e697392b6212 +#: ../../en/hardware/display.rst:111 2029e6fa89cf4097ab82a722ee6309c9 msgid "Set the rotation of the display." msgstr "" -#: ../../en/hardware/display.rst:111 c4e8c4b6d4e04e1e98c57726dfab55f2 +#: ../../en/hardware/display.rst:113 4ffd7df483ff48e7899cfc2756b1a52c msgid "The ``r`` parameter only accepts the following values:" msgstr "" -#: ../../en/hardware/display.rst:120 a19a66e146204f4fb2967120fbaed653 +#: ../../en/hardware/display.rst:122 84fa0e39d9e742158d5dd2c2889a44ea msgid "Set the color depth of the display." msgstr "" -#: ../../en/hardware/display.rst:122 56fd041c2414440ba61adc8bf0b7c0f4 +#: ../../en/hardware/display.rst:124 d8544534a76a4bd78c9f243ba430c6d6 msgid "``bpp`` The desired color depth in bits per pixel." msgstr "" -#: ../../en/hardware/display.rst:124 aadbc70e9bd04f4e9a762c127221f6fa +#: ../../en/hardware/display.rst:126 187162d692de4e54a8dfcbdb1f2eec9c msgid "" "Notes: For CoreS3 devices, the color depth is fixed at 16 bits, and this " "method has no effect." msgstr "" -#: ../../en/hardware/display.rst:128 a32f71c2b7fc4df9968211532f87ccf0 +#: ../../en/hardware/display.rst:130 0f81544fd03048eaa0472b7ae184a76b msgid "Set the font for the display." msgstr "" -#: ../../en/hardware/display.rst:130 0e86de1616bf49da8503bbbea4e5032a +#: ../../en/hardware/display.rst:132 0ac8b90657b34f5a81178b6b28ec7006 msgid "The ``font`` parameter only accepts the following values:" msgstr "" -#: ../../en/hardware/display.rst:132 ffea5a6028d5434d96d8defc973a241a -msgid "K5.Lcd.FONTS.ASCII7" +#: ../../en/hardware/display.rst:134 70ecf03e47e9413e970c5fd8d9f464da +msgid "M5.Lcd.FONTS.ASCII7" msgstr "" -#: ../../en/hardware/display.rst:133 a356841f300246afb5bd2747cf137395 -msgid "K5.Lcd.FONTS.DejaVu9" +#: ../../en/hardware/display.rst:135 55c76b14f10c4f38a676ecbf9eef7684 +msgid "M5.Lcd.FONTS.DejaVu9" msgstr "" -#: ../../en/hardware/display.rst:134 d6350c6a25694c4ba6e960a81c42f518 -msgid "K5.Lcd.FONTS.DejaVu12" +#: ../../en/hardware/display.rst:136 6003f869a00d4a72b842fe8f67b93ad5 +msgid "M5.Lcd.FONTS.DejaVu12" msgstr "" -#: ../../en/hardware/display.rst:135 2130a84a3ce44c4a83dbfef8bc4055ad -msgid "K5.Lcd.FONTS.DejaVu18" +#: ../../en/hardware/display.rst:137 5f4d68fcee714f4ea6e43caeaa67d8c5 +msgid "M5.Lcd.FONTS.DejaVu18" msgstr "" -#: ../../en/hardware/display.rst:136 ba4cff34c9fc4b5f8d3c1f3681c2496e -msgid "K5.Lcd.FONTS.DejaVu24" +#: ../../en/hardware/display.rst:138 25ba25b358454e95afeab920787f75d1 +msgid "M5.Lcd.FONTS.DejaVu24" msgstr "" -#: ../../en/hardware/display.rst:137 583a05640e664f1694ed379a9d5ba38c -msgid "K5.Lcd.FONTS.DejaVu40" +#: ../../en/hardware/display.rst:139 42875ff7686d42e09e27c4392984a15d +msgid "M5.Lcd.FONTS.DejaVu40" msgstr "" -#: ../../en/hardware/display.rst:138 20f6358328674a1db727a653112f0372 -msgid "K5.Lcd.FONTS.DejaVu56" +#: ../../en/hardware/display.rst:140 730c79be54874468babdb22af5d2120a +msgid "M5.Lcd.FONTS.DejaVu56" msgstr "" -#: ../../en/hardware/display.rst:139 9c98acbb1ac6457b86ed4c1d0c74288c -msgid "K5.Lcd.FONTS.DejaVu72" +#: ../../en/hardware/display.rst:141 0c2287e86f80436f8ccf800f0a5c9c21 +msgid "M5.Lcd.FONTS.DejaVu72" msgstr "" -#: ../../en/hardware/display.rst:140 74ca2d5a0b9c4630a2ffea467181a06c -msgid "K5.Lcd.FONTS.EFontCN24" +#: ../../en/hardware/display.rst:142 81c4b6f55192451ca643b15d81bd0667 +msgid "M5.Lcd.FONTS.EFontCN24" msgstr "" -#: ../../en/hardware/display.rst:141 f7f0746f708e4c8dbb804d6c0876511b -msgid "K5.Lcd.FONTS.EFontJA24" +#: ../../en/hardware/display.rst:143 14f75c4e5a334be6bd48f8c462fc02ee +msgid "M5.Lcd.FONTS.EFontJA24" msgstr "" -#: ../../en/hardware/display.rst:142 00131ea29fa84425a3e38fd76461abca -msgid "K5.Lcd.FONTS.EFontKR24" +#: ../../en/hardware/display.rst:144 5c6ff79e48be41899aa2ff7f90fb0988 +msgid "M5.Lcd.FONTS.EFontKR24" msgstr "" -#: ../../en/hardware/display.rst:146 b58374e706d4428bb49f139c5d120549 +#: ../../en/hardware/display.rst:148 cb5cb858ab004c379656a012bb4196e7 msgid "Set the text color and background color." msgstr "" -#: ../../en/hardware/display.rst:148 64713ba66e4f4c08a116539148a7e852 +#: ../../en/hardware/display.rst:150 073f682993e64b4d94935306ea75dba7 msgid "``fgcolor`` The text color in RGB888 format. Default is 0 (black)." msgstr "" -#: ../../en/hardware/display.rst:149 ca6d51b5bede4018845c0dbe874c6af1 +#: ../../en/hardware/display.rst:151 9e331907256c4f72873cb730285745a6 msgid "``bgcolor`` The background color in RGB888 format. Default is 0 (black)." msgstr "" -#: ../../en/hardware/display.rst:153 bae20a2cec9148848c6daf2a81da33dc +#: ../../en/hardware/display.rst:155 2eb30dfd0ba44c9ba485d63dbe08f319 msgid "Enable or disable text scrolling." msgstr "" -#: ../../en/hardware/display.rst:155 6530527c17fe4a0685224e647860e428 +#: ../../en/hardware/display.rst:157 ebe4596068a1488e83049188301a408b msgid "" "``scroll`` Set to True to enable text scrolling, or False to disable it. " "Default is False.\\" msgstr "" -#: ../../en/hardware/display.rst:159 48b60a4136e74610ac24f593f07fdbaf +#: ../../en/hardware/display.rst:161 f5b4f369bfa34bb799785cd6b43faa12 msgid "Set the size of the text." msgstr "" -#: ../../en/hardware/display.rst:161 b9009761678641dfaf098378ccc3fc7d +#: ../../en/hardware/display.rst:163 a8b963a4b4664b5397a7747f5a050ea0 msgid "``size`` The desired text size." msgstr "" -#: ../../en/hardware/display.rst:165 6226403d3e484c9a99a18b4e2300d412 +#: ../../en/hardware/display.rst:167 98e181ccd619448297da49ed1700b3d0 msgid "Set the cursor position." msgstr "" -#: ../../en/hardware/display.rst:167 b4658cb1834d4fe9b6860397fe632ab5 +#: ../../en/hardware/display.rst:169 771bde5a833740c29ecfd8d7aee8622c msgid "``x`` The horizontal position of the cursor. Default is 0." msgstr "" -#: ../../en/hardware/display.rst:168 e09444650e8e458899a9865a6b09b2cc +#: ../../en/hardware/display.rst:170 e157a26ce0ac410d924ac5eeddf40640 msgid "``y`` The vertical position of the cursor. Default is 0." msgstr "" -#: ../../en/hardware/display.rst:172 ae27a3822d954bc7bc0b062dbe291b54 +#: ../../en/hardware/display.rst:174 83c1a89ee02f45e8915a963ba30c3550 msgid "Clear the display with a specific color." msgstr "" -#: ../../en/hardware/display.rst:174 ../../en/hardware/display.rst:180 -#: ../../en/hardware/display.rst:207 ../../en/hardware/display.rst:227 -#: 3b87eafe85ac4476b723c32ebf8151bb 5b0ec0e87339409da3eea2ea9f88ab00 -#: 864e80fde96f4f698d8fd3d9c3c03974 c8fdce7202da481ca1b7c3f4007543aa +#: ../../en/hardware/display.rst:176 ../../en/hardware/display.rst:182 +#: ../../en/hardware/display.rst:209 ../../en/hardware/display.rst:229 +#: 5c6c191600e7470580955dee132263de 6158bc48b710428d85d9c0b22f47beca +#: 660360a923c04931867e1eeac0178bf5 6e69b2a76f7045649ede8d046b131ddf msgid "``color`` The fill color in RGB888 format. Default is 0." msgstr "" -#: ../../en/hardware/display.rst:178 2b97fa9d010f41c1aac89c33fa084376 +#: ../../en/hardware/display.rst:180 ad36a6ff28054197a23b24047899fea3 msgid "Fill the entire screen with a specified color." msgstr "" -#: ../../en/hardware/display.rst:185 ba7f58b73c6a43688d461cb763ddd252 +#: ../../en/hardware/display.rst:187 2418963bed0d48e9a5746a9cbdd66e81 msgid "Draw a single pixel on the screen." msgstr "" -#: ../../en/hardware/display.rst:187 02f82db01cf944e7a30e73668e2d4e5f +#: ../../en/hardware/display.rst:189 337ede9ed7534f1bbd55ae51ee52f79f msgid "``x`` The horizontal coordinate of the pixel. Default is -1." msgstr "" -#: ../../en/hardware/display.rst:188 485baf23a72a4cac9c8b9ff2af0a44f6 +#: ../../en/hardware/display.rst:190 e74c3aec349c4a228afeed6dacb11a76 msgid "``y`` The vertical coordinate of the pixel. Default is -1." msgstr "" -#: ../../en/hardware/display.rst:189 f2863d6beb5343d3873a85650ceeb86f +#: ../../en/hardware/display.rst:191 d6c1bd4334564b6d9aa1d220e51020cd msgid "``color`` The color of the pixel in RGB888 format. Default is 0." msgstr "" -#: ../../en/hardware/display.rst:193 961c83f04c7147c2b122d137a45c3785 +#: ../../en/hardware/display.rst:195 015370504d89468bb1929b4536fb4fa2 msgid "Draw an outline of a circle." msgstr "" -#: ../../en/hardware/display.rst:195 ../../en/hardware/display.rst:204 -#: 5f59d333e824497d85fc544fbdc2ae17 fbd66493b0a442ec8894d98b044120d2 +#: ../../en/hardware/display.rst:197 ../../en/hardware/display.rst:206 +#: 77d5adb379934dfa8ff74bda63295f9c 863926a6763441dbaae2892725d2f62a msgid "``x`` The x-coordinate of the circle center. Default is -1." msgstr "" -#: ../../en/hardware/display.rst:196 8e0b83dff1274ad9bbdaa591fd7520dc +#: ../../en/hardware/display.rst:198 ../../en/hardware/display.rst:207 +#: 3b87435835a14267863f424d4794080f 3e5c3ba1bc4642859a2004361cb622b1 msgid "``y`` The y-coordinate of the circle center. Default is -1." msgstr "" -#: ../../en/hardware/display.rst:197 ../../en/hardware/display.rst:206 -#: 0f75fb187c8944b9838a04c1d27967a3 f7df79140e1646d6b942d064ee76f130 +#: ../../en/hardware/display.rst:199 ../../en/hardware/display.rst:208 +#: 1009c0a41f5b48abb15e8a4f11dc215b a28630daff04430ca801619b9e052925 msgid "``r`` The radius of the circle. Default is -1." msgstr "" -#: ../../en/hardware/display.rst:198 46a581b7b2444e6aaffc35f18b5a6b5c +#: ../../en/hardware/display.rst:200 1c23e3a4cb474ca4a7bcd0449faa8192 msgid "``color`` The color of the circle in RGB888 format. Default is 0." msgstr "" -#: ../../en/hardware/display.rst:202 445e16c41846444b89a091aeae7edd7e +#: ../../en/hardware/display.rst:204 07bc7ab86d564853906e0740823c4e98 msgid "Draw a filled circle." msgstr "" -#: ../../en/hardware/display.rst:205 2e985145c3e441d69d0a2662c168c465 -msgid "`` y`` The y-coordinate of the circle center. Default is -1." -msgstr "" - -#: ../../en/hardware/display.rst:211 b2c251e568e34ef995fb97f1b10c3f25 +#: ../../en/hardware/display.rst:213 1d42c9f07f2b44ecab79ca8bf55d644a msgid "Draw an outline of an ellipse." msgstr "" -#: ../../en/hardware/display.rst:213 ../../en/hardware/display.rst:223 -#: 099d0f7f848f4c1fb3242137f7013f8f b0f42db31527476ea9913bba895c61cc +#: ../../en/hardware/display.rst:215 ../../en/hardware/display.rst:225 +#: 24d77e38257545f2965cf9a073ed023c 6e050933b5614f6a98a6ea2a610477c6 msgid "``x`` The x-coordinate of the ellipse center. Default is -1." msgstr "" -#: ../../en/hardware/display.rst:214 ../../en/hardware/display.rst:224 -#: 71e5ff852e75410da09790a4d2368483 8ae5f9939de646158e1357aa3d82d83b +#: ../../en/hardware/display.rst:216 ../../en/hardware/display.rst:226 +#: 81ae0a1839594f7483626452d9572e62 ffe2e71aee0b46b09b5de368b3a6a8fb msgid "``y`` The y-coordinate of the ellipse center. Default is -1." msgstr "" -#: ../../en/hardware/display.rst:215 ../../en/hardware/display.rst:225 -#: 0ee551b677344fc88dafc68afad3e81c 8a4ccb9c47ed4069aca26640b90510e4 +#: ../../en/hardware/display.rst:217 ../../en/hardware/display.rst:227 +#: 07f7db561b86411b97ac4781ee35c2f2 7a32fe81ff0c4dcd972b621b65c24d1d msgid "``rx`` The horizontal radius of the ellipse. Default is -1." msgstr "" -#: ../../en/hardware/display.rst:216 ../../en/hardware/display.rst:226 -#: 3813e994b9c54a859d142b4af04e6ec6 b5064858d80b41bd95b5f0814cf35e29 +#: ../../en/hardware/display.rst:218 ../../en/hardware/display.rst:228 +#: 330704d9440847e18716fa06f2bb9081 ff96fdcea2544f139029752d08c31d97 msgid "``ry`` The vertical radius of the ellipse. Default is -1." msgstr "" -#: ../../en/hardware/display.rst:217 ac7f5367ec444098bf0be6185ccf62c2 +#: ../../en/hardware/display.rst:219 af5d142b59c747f9b7fcc9d1e980a2b3 msgid "``color`` The color of the ellipse in RGB888 format. Default is 0." msgstr "" -#: ../../en/hardware/display.rst:221 0fd01014536c47938e16622ae2e99a78 +#: ../../en/hardware/display.rst:223 f9fdc102118b4bb98b24f6a83795ec93 msgid "Draw a filled ellipse." msgstr "" -#: ../../en/hardware/display.rst:231 978d8b823cbd48b1a41216014fa82093 +#: ../../en/hardware/display.rst:233 48fd5a39d6a34732aa85dc90bbd64fd7 msgid "Draw a line." msgstr "" -#: ../../en/hardware/display.rst:233 2fb4586249124c42b93f72feeff4ba8f +#: ../../en/hardware/display.rst:235 c0fe69d7f5724e1aae9b2e306402fba5 msgid "``x0, y0`` Starting point coordinates of the line. Default is -1." msgstr "" -#: ../../en/hardware/display.rst:234 972cff91c6174322a11f186d4e95d54a +#: ../../en/hardware/display.rst:236 79e8969ca13c432d8e0b540bb3014841 msgid "``x1, y1`` Ending point coordinates of the line. Default is -1." msgstr "" -#: ../../en/hardware/display.rst:235 ../../en/hardware/display.rst:243 -#: ../../en/hardware/display.rst:251 ../../en/hardware/display.rst:260 -#: ../../en/hardware/display.rst:269 ../../en/hardware/display.rst:279 -#: ../../en/hardware/display.rst:299 ../../en/hardware/display.rst:310 -#: ../../en/hardware/display.rst:321 ../../en/hardware/display.rst:430 -#: 24e6b2640b6642ba85aa48fbf86e6791 2ac74ed7a5584911aecc59e2422f06d2 -#: 2d1cd7024e464544a3e548133099e9f0 30335df6f1b64f019b33e11797e2b0a5 -#: 8ed8e21669784e86a82c080ab0080d18 9dca7eda189c46cfa62c9bd2ca4dd293 -#: b1db08c2bfa141aba3e16435abeeed3b e51e16f2c50542df8cb4c91ae12f011e -#: ebc636aa03d84caea0b30e0d2ccbfd48 ecd61ac9aba0483d87d4585398112f2e +#: ../../en/hardware/display.rst:237 ../../en/hardware/display.rst:245 +#: ../../en/hardware/display.rst:253 ../../en/hardware/display.rst:262 +#: ../../en/hardware/display.rst:271 ../../en/hardware/display.rst:281 +#: ../../en/hardware/display.rst:301 ../../en/hardware/display.rst:312 +#: ../../en/hardware/display.rst:323 ../../en/hardware/display.rst:432 +#: 24788bfe1cbc41e29ecc10abbbc4637b 539e8765c9434fd58fa18bc8de903e07 +#: 57f6f9a941a1441d83ab0ff66aac7cb8 6f337ea89150442e9167af30eea8de58 +#: 74d2ff1f47364f49b540c723fc9df79b 942ef45baaab438bb5825266fef2e054 +#: c2e89a55cf6c49598c7d4c12d5e1949e c4cac61a47ca4c5b911500c98a5cc756 +#: d54dd490e6554e55b6bd35123a6a2550 e2afc30dc1ef42e9ae00ea9bdb5b3b2c msgid "``color`` Color in RGB888 format. Default is 0." msgstr "" -#: ../../en/hardware/display.rst:239 f2ed1a7ca2c44308b6924515aaf01395 +#: ../../en/hardware/display.rst:241 0a29cdb2f0f64aa7b6d9366107d5e8bc msgid "Draw a rectangle." msgstr "" -#: ../../en/hardware/display.rst:241 ../../en/hardware/display.rst:249 -#: ../../en/hardware/display.rst:257 ../../en/hardware/display.rst:266 -#: 346c8ff6180042ae8072ce95d953a042 b095b585e87b4d37bc5095b0a26a0fa0 -#: d65d698f5d50489e8a8e81fc547b2ff7 fcfaf06fd24a4a33a46a046320e7aabf +#: ../../en/hardware/display.rst:243 ../../en/hardware/display.rst:251 +#: ../../en/hardware/display.rst:259 ../../en/hardware/display.rst:268 +#: 42eb9ae3cb1149f6a226479725d40ec1 6ac97cb9143f4f11aa512938ba83d13a +#: a88789b2c3534cc988cdeaffe6cc9427 bcef1c53b3a74f71ae30c1df84bab91b msgid "``x, y`` Top-left corner coordinates of the rectangle. Default is -1." msgstr "" -#: ../../en/hardware/display.rst:242 ../../en/hardware/display.rst:250 -#: ../../en/hardware/display.rst:258 ../../en/hardware/display.rst:267 -#: 173b589195384b60b84cbc153a245174 484cc115ea3340d093afe9ca63c61ecb -#: 93e8cef7563f457ca216826f0d368ac2 bc7edbb1d1de411185645235428b57bd +#: ../../en/hardware/display.rst:244 ../../en/hardware/display.rst:252 +#: ../../en/hardware/display.rst:260 ../../en/hardware/display.rst:269 +#: 04a95103cf414d22b04b244d9e91f188 5b5d1796383940d2a689464495d31bcd +#: b333928906f0460bbda516b8ae35531f e8baf4ca71744fd1b712269867852fc6 msgid "``w, h`` Width and height of the rectangle. Default is -1." msgstr "" -#: ../../en/hardware/display.rst:247 2409f041b97c4f9ca0f704f1a973a5ff +#: ../../en/hardware/display.rst:249 8f29ed2e67f148abb837e252f69d4e4c msgid "Draw a filled rectangle." msgstr "" -#: ../../en/hardware/display.rst:255 3df0e35afc6d4d06945917cc3d4f2561 +#: ../../en/hardware/display.rst:257 85acb75a70574c3a8d76199c7cc964dc msgid "Draw a rounded rectangle." msgstr "" -#: ../../en/hardware/display.rst:259 ../../en/hardware/display.rst:268 -#: 647a1eb795c94ae6885856a4ff01f9cf fb958e123c21402b9736bb08174c5a2f +#: ../../en/hardware/display.rst:261 ../../en/hardware/display.rst:270 +#: 5472da802071497ba0e7f752a5ab4735 c1dcd70992ab41199f8324e91583bda5 msgid "``r`` Radius of the corners. Default is -1." msgstr "" -#: ../../en/hardware/display.rst:264 3b7a3b07f813446dacddb08a8772ad2f +#: ../../en/hardware/display.rst:266 8ed5f96f0d664a51857a29d010eb4ea4 msgid "Draw a filled rounded rectangle." msgstr "" -#: ../../en/hardware/display.rst:274 17800087dd3c4f8380535abd117a05e0 +#: ../../en/hardware/display.rst:276 4816740775324eff9f5e4d8ef6b6937a msgid "Draw a triangle." msgstr "" -#: ../../en/hardware/display.rst:276 ../../en/hardware/display.rst:285 -#: 57cc2616780c4d87ad723e54cd196a96 d800577307004bf7b5b3e52346d4d68b +#: ../../en/hardware/display.rst:278 ../../en/hardware/display.rst:287 +#: 484283251a0b4e598e8545244bd3264d f4f90969963749fd97d902bc63bfb54a msgid "``x0, y0`` Coordinates of the first vertex. Default is -1." msgstr "" -#: ../../en/hardware/display.rst:277 ../../en/hardware/display.rst:286 -#: 5288312935bf4d5c9591d9e891bd7dcb 9cb9d1e0cc774444890f19dc399b4a1f +#: ../../en/hardware/display.rst:279 ../../en/hardware/display.rst:288 +#: 55b060b3691f48b2b374797cdac2048f cc94adf56a2e499b9641097a6df2d8f0 msgid "``x1, y1`` Coordinates of the second vertex. Default is -1." msgstr "" -#: ../../en/hardware/display.rst:278 ../../en/hardware/display.rst:287 -#: 39a53e654675402895a3364b8ae5d0f1 af0f804370f044768f5fc03f8487ac3b +#: ../../en/hardware/display.rst:280 ../../en/hardware/display.rst:289 +#: 3af3ce7444b445abb9da82f32777248c a9663440d96b40eea392c36e43893dcf msgid "``x2, y2`` Coordinates of the third vertex. Default is -1." msgstr "" -#: ../../en/hardware/display.rst:283 aec20e7b18564950b3e475ba3e831561 +#: ../../en/hardware/display.rst:285 4508fb8c0c854ac3b7dfffc3eb25efea msgid "Draw a filled triangle." msgstr "" -#: ../../en/hardware/display.rst:288 ../../en/hardware/display.rst:332 -#: 834b5804da3c415cb74aa235ba645240 cc272b105423456ba6076088213cada3 +#: ../../en/hardware/display.rst:290 ../../en/hardware/display.rst:334 +#: d763684a13ad430793765840c8763d9a ffc99121de0d42e2b351a5d2677aaf91 msgid "``color:`` Color in RGB888 format. Default is 0." msgstr "" -#: ../../en/hardware/display.rst:292 cf2cf5012e5e4f9d9ebcd7165f28f615 +#: ../../en/hardware/display.rst:294 74c911debb5b4a588ad51a12579f2ea7 msgid "Draw an arc." msgstr "" -#: ../../en/hardware/display.rst:294 ../../en/hardware/display.rst:305 -#: 564b09ab6b084aa081ec7da61cb061f1 a3073bf6e373402688b08b0f1af16be4 +#: ../../en/hardware/display.rst:296 ../../en/hardware/display.rst:307 +#: 72f177a7ab0f4b43ac3f2cfdad4c6feb e99fd046015041e3a8cf216fcd72c389 msgid "``x, y`` Center coordinates of the arc. Default is -1." msgstr "" -#: ../../en/hardware/display.rst:295 ../../en/hardware/display.rst:306 -#: e354d314aaa14192bbc3a0cb8307cec6 e68a0ac3502049afbe2bcb90f3613739 +#: ../../en/hardware/display.rst:297 ../../en/hardware/display.rst:308 +#: 0db0325821c8428ab37ed79e14bb5b5f 2c850d418e57494090dcfe01371bfbfd msgid "``r0`` Inner radius of the arc. Default is -1." msgstr "" -#: ../../en/hardware/display.rst:296 ../../en/hardware/display.rst:307 -#: e9ed15e7649a4fd29c9c73862f6423eb f162a73a6d2d494aa892ad8218150b70 +#: ../../en/hardware/display.rst:298 ../../en/hardware/display.rst:309 +#: 4bb6720c490d4aa58a843f4fc1808579 e4fb7219caec4466a0ebcc623273c8fb msgid "``r1`` Outer radius of the arc. Default is -1." msgstr "" -#: ../../en/hardware/display.rst:297 ../../en/hardware/display.rst:308 -#: ../../en/hardware/display.rst:319 ../../en/hardware/display.rst:330 -#: 752bc2a57f8d43d5b6420e0fadf0a2d7 86be313730f543cf9267adbe79c5176e -#: b8c20f5bbc3e41139d8e873a9f626dc0 fad3b206e2f240db9d4872e80a8d9c78 +#: ../../en/hardware/display.rst:299 ../../en/hardware/display.rst:310 +#: ../../en/hardware/display.rst:321 ../../en/hardware/display.rst:332 +#: 287d6b2235704e999d0846b54ad44bf6 714a761ff87948a699ef2f210d77815a +#: a8f5218579934eb8a5389cbcaecdf202 c567be8ad7ec4c2fb2b8962d11b9ac38 msgid "``angle0`` Starting angle of the arc (in degrees). Default is -1." msgstr "" -#: ../../en/hardware/display.rst:298 ../../en/hardware/display.rst:309 -#: ../../en/hardware/display.rst:331 2c11dbc6f6324adc90ef579bf849746e -#: 674eddeece54408195f9eec27c9ad8cb ddedbade206a4656a816531176209e74 +#: ../../en/hardware/display.rst:300 ../../en/hardware/display.rst:311 +#: ../../en/hardware/display.rst:333 40bd66bf56df47db83cac75a454a229b +#: 49467d333f4742f5bdbb188f01da4b81 b16c8fca83e944918433ee5c36c7eb1a msgid "``angle1`` Ending angle of the arc (in degrees). Default is -1." msgstr "" -#: ../../en/hardware/display.rst:303 245a92ba2c3c4c8d8047a6b32505f84d +#: ../../en/hardware/display.rst:305 e41b306671c7438ca1f58318f470d733 msgid "Draw a filled arc." msgstr "" -#: ../../en/hardware/display.rst:314 ffccbc0d0bd646b8a7afecc7fa3850a4 +#: ../../en/hardware/display.rst:316 1f2b686832704358aa5ed7f0c6330d50 msgid "Draw an elliptical arc." msgstr "" -#: ../../en/hardware/display.rst:316 ../../en/hardware/display.rst:327 -#: 3ad4609b524643099e40a7d88485a5fc 419059ad902246bebeb0d701229423e0 +#: ../../en/hardware/display.rst:318 ../../en/hardware/display.rst:329 +#: 2c800cef0f274ff481799f2ae3e8a3e4 f2560e1d1dee4a10ae037ce2a9a6ed5e msgid "``x, y`` Center coordinates of the elliptical arc. Default is -1." msgstr "" -#: ../../en/hardware/display.rst:317 ../../en/hardware/display.rst:328 -#: 7e63953c140e4a20a38d1ea22af4705f f64f3f4cd6f54c4c9da423078c2f3039 +#: ../../en/hardware/display.rst:319 ../../en/hardware/display.rst:330 +#: a7c02230a9074a97981e167fc1cdf87e fba42ecaffbe4b9686a5483957ecf91f msgid "" "``r0x, r0y`` Radii of the inner ellipse (horizontal and vertical). " "Default is -1." msgstr "" -#: ../../en/hardware/display.rst:318 ../../en/hardware/display.rst:329 -#: 472aee98a1fb4253ba73af5a30db85c2 5a8f131c488b4cada8e0b7ec05c8e4c9 +#: ../../en/hardware/display.rst:320 ../../en/hardware/display.rst:331 +#: 5fcd2eb12ecf4aa09840ab6ddd362e70 a1c4d1d2427f4ae980c42c2cd2d03257 msgid "" "``r1x, r1y`` Radii of the outer ellipse (horizontal and vertical). " "Default is -1." msgstr "" -#: ../../en/hardware/display.rst:320 b4b136e741a5434595e563216c3b0250 +#: ../../en/hardware/display.rst:322 58c1ccb2b1354a08a54c9cf871275df0 msgid "``angle1`` Ending angle of the arc (in degrees). Default is 0." msgstr "" -#: ../../en/hardware/display.rst:325 8dcbe0c5bd344192bb0448e46c6eaa57 +#: ../../en/hardware/display.rst:327 2faaa85a742d42178cd966a211e133d1 msgid "Draw a filled elliptical arc." msgstr "" -#: ../../en/hardware/display.rst:336 ae89aece24394acabc875a735a177d6e +#: ../../en/hardware/display.rst:338 ba2e249576d04aadb7cbadbc59a09e5a msgid "Draw a QR code." msgstr "" -#: ../../en/hardware/display.rst:338 428ee98ba8464b79b31e91f6df403282 +#: ../../en/hardware/display.rst:340 79210a88c6ee4810b7f041725ea5b38f msgid "``text`` QR code content." msgstr "" -#: ../../en/hardware/display.rst:339 9d34918a2a7d4680913f84856c797686 +#: ../../en/hardware/display.rst:341 a3cef6d055244ec286e010e5e05779d8 msgid "``x, y`` Starting coordinates for displaying the QR code." msgstr "" -#: ../../en/hardware/display.rst:340 789741eca98349dd8169e1e0ea3b876b +#: ../../en/hardware/display.rst:342 c1d3be9e7dc545f3a76e5d982c9c50e5 msgid "``w:`` Width of the QR code. Default is 0." msgstr "" -#: ../../en/hardware/display.rst:341 90e230612dfe4a829af7c1908efc0b1e +#: ../../en/hardware/display.rst:343 a5f85a36391c45dd8733951eb36c89c2 msgid "``version`` QR code version. Default is 1." msgstr "" -#: ../../en/hardware/display.rst:343 ../../en/hardware/display.rst:405 -#: ../../en/hardware/display.rst:448 876e1933dd364e328d7fe75969ff5221 -#: a5824627bf1a4b2aae20e83487e0293c ea5dccd69ea74d76be5bb04c33406819 +#: ../../en/hardware/display.rst:345 ../../en/hardware/display.rst:407 +#: ../../en/hardware/display.rst:450 201b779c6f2440debdb0af3078ec4356 +#: 32c707e618b442be97f4e897d177347d 8d9af6d08eca41939bd66fcdbbf1ff30 msgid "**Example**:" msgstr "" -#: ../../en/hardware/display.rst:345 8b805fc8f6ce461790d495c734bb1bb5 +#: ../../en/hardware/display.rst:347 b47c7735d57a492194c428c4976273ad msgid "Generate and display a QR code with the content \"hello\":" msgstr "" -#: ../../en/hardware/display.rst:353 90d26f3944f9423ea58c6d3e35d33855 +#: ../../en/hardware/display.rst:355 b975d4c7cd3c49ed9fa4dcf8b05eda2f msgid "Draw a PNG image." msgstr "" -#: ../../en/hardware/display.rst:355 ../../en/hardware/display.rst:382 -#: ../../en/hardware/display.rst:391 ../../en/hardware/display.rst:400 -#: 74f4cb62ef3844f1b9fb036bf6a18bed 982b6ee09a634e60aee6cf7da7209014 -#: c21ab57d58344acba4eec3472fcc8bcc d6bc03d105b14d85b4d17a384c445cd3 +#: ../../en/hardware/display.rst:357 ../../en/hardware/display.rst:384 +#: ../../en/hardware/display.rst:393 ../../en/hardware/display.rst:402 +#: 79be809b16c842e5a12e3dfa65d07fba b566f3bd7bdb489391bd2f8234113fee +#: dece691fe2d24348926d5ae06cc5585e f090d98913de40ef953695d1c25d66d4 msgid "``img`` Image file path or opened image data." msgstr "" -#: ../../en/hardware/display.rst:356 ../../en/hardware/display.rst:383 -#: ../../en/hardware/display.rst:392 ../../en/hardware/display.rst:401 -#: ../../en/hardware/display.rst:420 2abf8a225aa1418b8d007f9d2acb05e5 -#: 377aa17d2da74e3b9dbb44c44c9fa731 6c3c79c3ece64ba4853e226dc862c309 -#: 90c460cc64314c2fb0e2a1cd08463b4c fbd790f19c884c77a3cdc04149b363a4 +#: ../../en/hardware/display.rst:358 ../../en/hardware/display.rst:385 +#: ../../en/hardware/display.rst:394 ../../en/hardware/display.rst:403 +#: ../../en/hardware/display.rst:422 34fb9005f6c94f4a9a6986bf02d6e0fe +#: 4af54c7322d3459c9b7515c00999e1dd 6bb64bba96ee4295aba5c02f1ca322ea +#: 7e07f8e2a6a7424198294038cd83d4ba ef7fc8e0a4a04b8792d9b5876438597a msgid "``x, y`` Starting coordinates on the display screen." msgstr "" -#: ../../en/hardware/display.rst:357 ../../en/hardware/display.rst:384 -#: ../../en/hardware/display.rst:393 ../../en/hardware/display.rst:402 -#: 111eedfba39e474e8002db32fd573747 207eb8d5dcbf4b4ba126e8142031de9d -#: 77735ad1f42a4dc6ac570d3f3f3e726e 8f4012bfa8ed47968d1438bc3fbf2871 +#: ../../en/hardware/display.rst:359 ../../en/hardware/display.rst:386 +#: ../../en/hardware/display.rst:395 ../../en/hardware/display.rst:404 +#: 12180cc8126a43b3bb7e118687d46ffc 200ede19b3764eaa813e40adec41a5e3 +#: 822b9e0d360447228fda3427df0fab4d 97c1255d66704a1e9affc7ec29b37412 msgid "``maxW, maxH`` Width and height to be drawn. Draws the full image if ≤0." msgstr "" -#: ../../en/hardware/display.rst:358 ../../en/hardware/display.rst:385 -#: ../../en/hardware/display.rst:394 ../../en/hardware/display.rst:403 -#: 51ba5b77e2d7445e81effbf0cacc086e 770a6b2c48124c6ea7af3d2ff91c6b3b -#: 9f72e9e229754ad18271ccfead12afa0 d52671ad7adf4e9ab75d8930478b8372 +#: ../../en/hardware/display.rst:360 ../../en/hardware/display.rst:387 +#: ../../en/hardware/display.rst:396 ../../en/hardware/display.rst:405 +#: 03fd294c6cc44f33ae0da0205d7848c8 39058bccc2c844fc93df956b4ea8dbd6 +#: 43fbb5b38e764d8790d7a7f1b203d560 9addf43e2bbf42b0924faa075a2b351a msgid "``offX, offY`` Offset in the image to start from." msgstr "" -#: ../../en/hardware/display.rst:359 693f90196ccc41b7bc94b46674f092d2 +#: ../../en/hardware/display.rst:361 d64fa358355e46edaeda95a3a787fa6e msgid "``scaleX, scaleY`` Whether to scale the image horizontally or vertically." msgstr "" -#: ../../en/hardware/display.rst:361 d2104f0e3f2b4b7c879ece398efe8a3e +#: ../../en/hardware/display.rst:363 28809e09f07441e8a9c910aee3e6768b msgid "**Examples**:" msgstr "" -#: ../../en/hardware/display.rst:363 d768b8239a564e6a88e4e1f99db376d9 +#: ../../en/hardware/display.rst:365 24e73e088a0f442fbe33bc81c0735e50 msgid "Display a PNG image from a specified path:" msgstr "" -#: ../../en/hardware/display.rst:369 58a4c033f52b47f096d80f688752b0b3 +#: ../../en/hardware/display.rst:371 eb3d314b190c4bc7accc561e07b0f258 msgid "Display a PNG image from read data:" msgstr "" -#: ../../en/hardware/display.rst:380 9d9400a9d627422dabd0e6dcc891fea7 +#: ../../en/hardware/display.rst:382 1e0735b2a7cf461cab5478faf3230470 msgid "Draw a JPG image." msgstr "" -#: ../../en/hardware/display.rst:389 9a6660e82b754a258e4e0eb47010ddd5 +#: ../../en/hardware/display.rst:391 db765317835c4e5dbbab843ff7ad012d msgid "Draw a BMP image." msgstr "" -#: ../../en/hardware/display.rst:398 df3a9ef2337d4322baed6b38735efa75 +#: ../../en/hardware/display.rst:400 53f9191c8b814e5bb1701c8cba9e2601 msgid "Draw an image." msgstr "" -#: ../../en/hardware/display.rst:407 d692001e7e154f77b5ce508151e22f3a +#: ../../en/hardware/display.rst:409 a0194c06cdbd486db9198e2f053f2741 msgid "Draw an image from the buffer:" msgstr "" -#: ../../en/hardware/display.rst:417 721cf69f2ab245cb8cec3dc85fc07590 +#: ../../en/hardware/display.rst:419 b39f7270fcba47b3a22e9c464ff5f4da msgid "Draw an image from raw buffer data." msgstr "" -#: ../../en/hardware/display.rst:419 cba5e6ec5a9a4c30aa7662bcc2006781 +#: ../../en/hardware/display.rst:421 c19d5aaa9bd4435894441c67dbc787ed msgid "``buf`` Image buffer." msgstr "" -#: ../../en/hardware/display.rst:421 dd45331760f14137983b6d5c1e949c1e +#: ../../en/hardware/display.rst:423 143de434b9be404c9e7180f316f7bfbc msgid "``w, h`` Width and height of the image." msgstr "" -#: ../../en/hardware/display.rst:422 7321e5460fb84ab68057912fac52478b +#: ../../en/hardware/display.rst:424 a362c0fbd93549a2a798b68040814f7b msgid "``len`` Length of the image data." msgstr "" -#: ../../en/hardware/display.rst:423 ff44abd0886d4028adddbae67fcfbd0e +#: ../../en/hardware/display.rst:425 c8a93adb99de4c0ab03be719b94bdc01 msgid "``swap`` Whether to enable inverted display." msgstr "" -#: ../../en/hardware/display.rst:427 e9240d1572e148ad934dc64ccf0027dc +#: ../../en/hardware/display.rst:429 4aa65d2a875c483b80361de5066ca48c msgid "Display a string (no formatting support)." msgstr "" -#: ../../en/hardware/display.rst:429 6a4e33f86fe24c829208cd2666fc6e7e +#: ../../en/hardware/display.rst:431 b151e79c0092448cb791bc9fd677b7f9 msgid "``text`` Text to display." msgstr "" -#: ../../en/hardware/display.rst:434 92d8e16fd5b64b8690acdaad0876b942 +#: ../../en/hardware/display.rst:436 6d89be9b5d624e4db14ed4c3dafbe561 msgid "Display a formatted string." msgstr "" -#: ../../en/hardware/display.rst:436 bc36fb72181a412eb788261ed0711631 +#: ../../en/hardware/display.rst:438 a734cf86fbd5442ebb437d18d8bf1093 msgid "``text`` Text to display with formatting." msgstr "" -#: ../../en/hardware/display.rst:440 b64832c210ff481086b2d3bd78ca1482 +#: ../../en/hardware/display.rst:442 07f9beaeb59240bc9c71894c8925be64 msgid "Create a canvas." msgstr "" -#: ../../en/hardware/display.rst:442 74a44c3bb05e4d849a28fe6d41c10a60 +#: ../../en/hardware/display.rst:444 ef728efc93ab4200b3115d0602a53053 msgid "``w, h`` Width and height of the canvas." msgstr "" -#: ../../en/hardware/display.rst:443 c235bc66cd314bdaa886b84b4c1429d8 +#: ../../en/hardware/display.rst:445 495468b16ce54d23839cddadec74b5eb msgid "``bpp`` Color depth. Default is -1." msgstr "" -#: ../../en/hardware/display.rst:444 8e30d24dec2c4791b7088f6e4eed3971 +#: ../../en/hardware/display.rst:446 68c065ae6f58410e87d8154ac17685da msgid "``psram`` Whether to use PSRAM. Default is False." msgstr "" -#: ../../en/hardware/display.rst:446 ac18469821274f0f8f25f4229f23260a +#: ../../en/hardware/display.rst:448 0b5d6b29cce9420da7e1922708fc4d71 msgid "Returns Created canvas object." msgstr "" -#: ../../en/hardware/display.rst:458 ce8f0f094e1c41b98640d3381db0015f +#: ../../en/hardware/display.rst:460 b0f6baf4c8e74b5f81460c1d46c5a660 msgid "Start writing to the display." msgstr "" -#: ../../en/hardware/display.rst:462 dd91fdef9f234665b2f2d117ff8cccf5 +#: ../../en/hardware/display.rst:464 4ddd560d23a94a1fb468c36211b42145 msgid "End writing to the display." msgstr "" +#~ msgid "K5.Lcd.FONTS.ASCII7" +#~ msgstr "" + +#~ msgid "K5.Lcd.FONTS.DejaVu9" +#~ msgstr "" + +#~ msgid "K5.Lcd.FONTS.DejaVu12" +#~ msgstr "" + +#~ msgid "K5.Lcd.FONTS.DejaVu18" +#~ msgstr "" + +#~ msgid "K5.Lcd.FONTS.DejaVu24" +#~ msgstr "" + +#~ msgid "K5.Lcd.FONTS.DejaVu40" +#~ msgstr "" + +#~ msgid "K5.Lcd.FONTS.DejaVu56" +#~ msgstr "" + +#~ msgid "K5.Lcd.FONTS.DejaVu72" +#~ msgstr "" + +#~ msgid "K5.Lcd.FONTS.EFontCN24" +#~ msgstr "" + +#~ msgid "K5.Lcd.FONTS.EFontJA24" +#~ msgstr "" + +#~ msgid "K5.Lcd.FONTS.EFontKR24" +#~ msgstr "" + +#~ msgid "`` y`` The y-coordinate of the circle center. Default is -1." +#~ msgstr "" + diff --git a/docs/locales/zh_CN/LC_MESSAGES/hardware/imu.po b/docs/locales/zh_CN/LC_MESSAGES/hardware/imu.po index 31b1c92a..3033a5ce 100644 --- a/docs/locales/zh_CN/LC_MESSAGES/hardware/imu.po +++ b/docs/locales/zh_CN/LC_MESSAGES/hardware/imu.po @@ -9,7 +9,7 @@ msgid "" msgstr "" "Project-Id-Version: UIFlow2 Programming Guide \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-01-16 15:35+0800\n" +"POT-Creation-Date: 2025-04-18 15:58+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language: zh_CN\n" @@ -20,228 +20,225 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.16.0\n" -#: ../../en/hardware/imu.rst:2 7a1bc5bb00754af5a02b59c852dc6c37 +#: ../../en/hardware/imu.rst:2 05a6e6002c344762bedf186687c3f7a6 msgid "IMU" msgstr "" -#: ../../en/hardware/imu.rst:7 f5faf41cae6243a8a15ac8025a10f2dd +#: ../../en/hardware/imu.rst:7 7434f456710249ab9b3d6d5903d8168e msgid "" "IMU is used to control the built-in accelerometer and gyroscope inside " "the host device. Below is the detailed IMU support for the host:" msgstr "IMU 用于控制主机内部集成加速计与陀螺仪的按键。以下是主机的 IMU 支持详细:" -#: ../../en/hardware/imu.rst:15 6957c83da34549c7b1c7264e834a6e1f +#: ../../en/hardware/imu.rst:15 a10c0cc1775b46f0a6492ef1f4fb3d43 msgid "MPU6886" msgstr "" -#: ../../en/hardware/imu.rst:15 961d6b12e8684e509f3db5997f080c12 +#: ../../en/hardware/imu.rst:15 6fb92093594d4023b2b473dec5bc1b57 msgid "BMI270" msgstr "" -#: ../../en/hardware/imu.rst:15 9ed405b877b04626b6ebcd7d40e12d32 +#: ../../en/hardware/imu.rst:15 f6c3785004364c588cf022762c0d1bb4 msgid "BMM150" msgstr "" -#: ../../en/hardware/imu.rst:17 821af82b10ec4cb5a1d2e9d53f30131d +#: ../../en/hardware/imu.rst:17 726cb84c3b594eb1ab44cad38849dc3d msgid "AtomS3" msgstr "" #: ../../en/hardware/imu.rst:17 ../../en/hardware/imu.rst:25 #: ../../en/hardware/imu.rst:27 ../../en/hardware/imu.rst:31 -#: ../../en/hardware/imu.rst:33 4b41f15e6a7e477f9804b024db2567f0 -#: 66908bdb0ccb42ed88f2c298135f12e7 7bdf8c6904fb4f7fa64d2acd1d959493 -#: 95a30b9df98549689761c84401cd73a7 c878daad5ed943148f81f28a4343552f -#: ce0c399f01e5408ea2aa3b5dac32a83f +#: ../../en/hardware/imu.rst:33 77518937635c499e9846f2f79e452b92 +#: e6bb00d5d8ec460797a0470ae665cb18 eb09c61ff3774ba79592378bd57c2398 msgid "|S|" msgstr "" -#: ../../en/hardware/imu.rst:19 0c3fc0cab97145d3b10d46b4f1fb5e08 +#: ../../en/hardware/imu.rst:19 af7d25a0e49e49869e2d737743b1af6a msgid "AtomS3 Lite" msgstr "" -#: ../../en/hardware/imu.rst:21 7e6cad99e2ec4e77ad528404dcf9b7aa +#: ../../en/hardware/imu.rst:21 b06f1bec2d784023998d364044b70fe1 msgid "AtomS3U" msgstr "" -#: ../../en/hardware/imu.rst:23 80938ab25965484cb886e03a5424b9fd +#: ../../en/hardware/imu.rst:23 1f01250f751f415ea2a3ac6ff6d9be21 msgid "StampS3" msgstr "" -#: ../../en/hardware/imu.rst:25 82e113c3ace146c5aa6c309fd7df4fcc +#: ../../en/hardware/imu.rst:25 a3c91f6d575e4f048a7107aa5c4fef0a msgid "CoreS3" msgstr "" -#: ../../en/hardware/imu.rst:27 c6f37a50b8fb43c7bb4bf72e3661526a +#: ../../en/hardware/imu.rst:27 e67964caa6ae451a95030cd6a430fe58 msgid "Core2" msgstr "" -#: ../../en/hardware/imu.rst:29 94993dfac7054017bc6e40870068f8af +#: ../../en/hardware/imu.rst:29 e722da7a08cd4694b57760791628b040 msgid "TOUGH" msgstr "" -#: ../../en/hardware/imu.rst:31 8b3fec182fae483a824f905899c5ffaf +#: ../../en/hardware/imu.rst:31 3467dfd913fd431db5cfc1b5917eebb5 msgid "StickC Plus" msgstr "" -#: ../../en/hardware/imu.rst:33 0d4279d3e1764da09460bde8eb3a851c +#: ../../en/hardware/imu.rst:33 7c4f6b9cda8d4312ba8e43e621a596c7 msgid "StickC Plus2" msgstr "" -#: ../../en/hardware/imu.rst:39 c3dae3ca891d4ffa9c59faae97ff28da +#: ../../en/hardware/imu.rst:39 764245c4153e4259bd70857c3f349f26 msgid "Micropython Example:" msgstr "" -#: ../../en/hardware/imu.rst:45 98f7d5e9231f488ea99586c0f2f86d98 +#: ../../en/hardware/imu.rst:45 314aa265cde6453d89598e9f40660427 msgid "UIFLOW2 Example:" msgstr "" -#: ../../en/hardware/imu.rst:47 9e714b68a3704947aee1ab76d2e64d3b +#: ../../en/hardware/imu.rst:47 246326f3f530442aaa20913fde3602ab msgid "|example.png|" msgstr "" -#: ../../en/refs/hardware.imu.ref:20 bbb7ba464d7d4f58ac90398d510c7896 +#: ../../en/refs/hardware.imu.ref:20 f78e3aa3903b48e1b9ec7cd1a214d58c msgid "example.png" msgstr "" -#: ../../en/hardware/imu.rst:51 b9cbe66141524bfbb83bebf075ab5323 +#: ../../en/hardware/imu.rst:51 a240f49506bd4b1e838cc2e4f9c57cbd msgid "|imu_cores3_example.m5f2|" msgstr "" -#: ../../en/hardware/imu.rst:55 1efd6ab32d03416491d4727045832778 +#: ../../en/hardware/imu.rst:55 3b1662bc1ca54eacb95cc78a6b31bca4 msgid "class IMU" msgstr "" -#: ../../en/hardware/imu.rst:59 37a8b6d4056d475b89430f5176aee84e +#: ../../en/hardware/imu.rst:59 6dff35f9cb8143228c60ff2aeef5c319 +#, fuzzy msgid "" -"Methods of the IMU Class heavily rely on ``M5.begin()`` |M5.begin.svg| " -"and ``M5.update()`` |M5.update.svg|." +"Methods of the IMU Class heavily rely on ``M5.begin()`` |M5.begin.png| " +"and ``M5.update()`` |M5.update.png|." msgstr "" "IMU Class的方法重度依赖 ``M5.begin()`` |M5.begin.svg| 和 ``M5.update()`` " "|M5.update.svg|。" -#: ../../en/refs/system.ref:1 6028fc9643b6450897b66ece1112898a -#: 6d642d672e1e46e4b51ed6051ec37b6a -msgid "M5.begin.svg" +#: ../../en/refs/system.ref:1 d4092b1286b84ae1a159ac8b0dd11f20 +msgid "M5.begin.png" msgstr "" -#: ../../en/refs/system.ref:4 19b3433cb49e43b380dbeeda3c9e24ba -#: 71ae5329477e408f8179790246303582 -msgid "M5.update.svg" +#: ../../en/refs/system.ref:3 0eafcaf2952a48c4b4c397c628e87c33 +msgid "M5.update.png" msgstr "" -#: ../../en/hardware/imu.rst:61 2416c878e153492aa82372cd38f4488c +#: ../../en/hardware/imu.rst:61 8065a09e7d3a489bb546beffdf70d37e +#, fuzzy msgid "" "All calls to methods of IMU objects should be placed after ``M5.begin()``" -" |M5.begin.svg|, and ``M5.update()`` |M5.update.svg| should be called in " +" |M5.begin.png|, and ``M5.update()`` |M5.update.png| should be called in " "the main loop." msgstr "" "调用 IMU 对象的所有方法,需要放在 ``M5.begin()`` |M5.begin.svg| 的后面,并在主循环中调用 " "``M5.update()`` |M5.update.svg|。" -#: ../../en/hardware/imu.rst:65 e68285ec0a0146848855fc406917326f +#: ../../en/hardware/imu.rst:65 79f1f4581d79469c92e045282e117053 msgid "Methods" msgstr "" -#: ../../en/hardware/imu.rst:69 01b6bba047b54112a93de0e258007184 +#: ../../en/hardware/imu.rst:69 36f8dda6a514427cbd2914778b371ea7 msgid "Get the tuple of x, y, and z values of the accelerometer." msgstr "获取加速度计的 x、y 和 z 值的三元组。" #: ../../en/hardware/imu.rst:71 ../../en/hardware/imu.rst:85 -#: ../../en/hardware/imu.rst:97 26f56f47f927444ba529e8e5602091c1 -#: 7bcf60f0fad14ce2b21fb721afed4e99 b84da3768b47481d98a2e6e64704e8d3 +#: ../../en/hardware/imu.rst:97 39b5a589dc1d410383f758ca6be12cba msgid "UIFLOW2:" msgstr "" -#: ../../en/hardware/imu.rst:73 2cd4cd4c519b4cd98e6f693f8a7b0dc8 +#: ../../en/hardware/imu.rst:73 814f7072521448aa9c86d702b6f2341d msgid "|getAccel.png|" msgstr "" -#: ../../en/refs/hardware.imu.ref:2 cd7672036e254f3297ad24e7d8c67b6d +#: ../../en/refs/hardware.imu.ref:2 971feb9ee7ef4daa9e5139f358d8aad3 msgid "getAccel.png" msgstr "" -#: ../../en/hardware/imu.rst:75 6a007babc5cf4ca0bad00b2d641491c3 +#: ../../en/hardware/imu.rst:75 a69db91530904241a2c266712546b158 msgid "|getAccel2.png|" msgstr "" -#: ../../en/refs/hardware.imu.ref:4 3032b85e9e224594a3e5c0cb3deeb446 +#: ../../en/refs/hardware.imu.ref:4 e214c5d055d54aeb852a68f9c0776dd7 msgid "getAccel2.png" msgstr "" -#: ../../en/hardware/imu.rst:77 d6c6a9b3ea294e8facb893b84044a2c2 +#: ../../en/hardware/imu.rst:77 a4b6d42a01664068822e1b22574effd5 msgid "|getAccel3.png|" msgstr "" -#: ../../en/refs/hardware.imu.ref:6 2c86141d6e6a4bce8023d84ef6d047e4 +#: ../../en/refs/hardware.imu.ref:6 6a0596f00d4a41548b21f64805af428e msgid "getAccel3.png" msgstr "" -#: ../../en/hardware/imu.rst:83 64b6d79543f849a8bee638be2fd4be9f +#: ../../en/hardware/imu.rst:83 db17b9a98ca9470f8cd1244f9c9b5834 msgid "Get the tuple of x, y, and z values of the gyroscope." msgstr "获取角速度传感器(陀螺仪)的 x、y 和 z 值的三元组。" -#: ../../en/hardware/imu.rst:87 2166bf10f321497f8516a26ca830c091 +#: ../../en/hardware/imu.rst:87 c447fe25b21a4692853e6bed92c67594 msgid "|getGyro.png|" msgstr "" -#: ../../en/refs/hardware.imu.ref:8 d1d5deacea1b4568aac56591fa9f94dd +#: ../../en/refs/hardware.imu.ref:8 649dab3554a740079e589ad57717f2b4 msgid "getGyro.png" msgstr "" -#: ../../en/hardware/imu.rst:89 81090fcb905e40179810e085973862f9 +#: ../../en/hardware/imu.rst:89 b69cdb13de5749c098c5c1b6e0b3dcc4 msgid "|getGyro2.png|" msgstr "" -#: ../../en/refs/hardware.imu.ref:10 14dde3562b9c45e3b1c38e7975714608 +#: ../../en/refs/hardware.imu.ref:10 bd5981f1a01b40a9b1b26ff5762230d1 msgid "getGyro2.png" msgstr "" -#: ../../en/hardware/imu.rst:91 457d7365082741d3b6369fb093d38da3 +#: ../../en/hardware/imu.rst:91 87810d18154d4ab7945c4862f75943c5 msgid "|getGyro3.png|" msgstr "" -#: ../../en/refs/hardware.imu.ref:12 ae72d5181c0a4fd88807fa91ee1c11c2 +#: ../../en/refs/hardware.imu.ref:12 50af6ff990104b07b56fb85d187a7e24 msgid "getGyro3.png" msgstr "" -#: ../../en/hardware/imu.rst:95 16063b52e5064ea180c9528030b6aa22 +#: ../../en/hardware/imu.rst:95 a774e35b969946cf94f08b5f9cc1a9a7 #, fuzzy msgid "Get the tuple of x, y, and z values of the magnetometer." msgstr "获取加速度计的 x、y 和 z 值的三元组。" -#: ../../en/hardware/imu.rst:99 d7654f1fbb8c468685426e9154c1286d +#: ../../en/hardware/imu.rst:99 829e513edb4943bc82d6d1c36bb69d18 msgid "|getMag.png|" msgstr "" -#: ../../en/refs/hardware.imu.ref:14 fb51e3853d2a452ab7491f726b31e6fa +#: ../../en/refs/hardware.imu.ref:14 4ecbf452128143e787de412a749b0470 msgid "getMag.png" msgstr "" -#: ../../en/hardware/imu.rst:101 25806ef352344bcab864e54a233ea423 +#: ../../en/hardware/imu.rst:101 865a9983985a44e9907b45abb85d8de1 msgid "|getMag2.png|" msgstr "" -#: ../../en/refs/hardware.imu.ref:16 5eaecbe1a92e4b148891c8fd120a14a3 +#: ../../en/refs/hardware.imu.ref:16 31b029a67bc44431af83740d277413fd msgid "getMag2.png" msgstr "" -#: ../../en/hardware/imu.rst:103 496d6e826752472d9912bb89ec3f02fe +#: ../../en/hardware/imu.rst:103 4407d4541d274bcfa1d1dda2235de0df msgid "|getMag3.png|" msgstr "" -#: ../../en/refs/hardware.imu.ref:18 a5762cc95c7a4a0a93b1213c2fa36032 +#: ../../en/refs/hardware.imu.ref:18 8e7222f6f8824aa69da949f80afd1476 msgid "getMag3.png" msgstr "" -#: ../../en/hardware/imu.rst:107 8c0da126b0e94a9aa875e66089ebfd91 +#: ../../en/hardware/imu.rst:107 a136477668274c1e8e8dce7b0bac1782 msgid "class IMU_TYPE" msgstr "" -#: ../../en/hardware/imu.rst:110 f7dd716dc0ea4d70a1d388b7d7b61ac0 +#: ../../en/hardware/imu.rst:110 7cf41bc485ee417eba78b3cef09810ac msgid "Constants" msgstr "" -#: ../../en/hardware/imu.rst:121 4d9798e52549468ea6837d30fae79cfd +#: ../../en/hardware/imu.rst:121 78abeca8dc8c41baade7cdf1dc98ea2a msgid "The model of the IMU." msgstr "IMU 的型号。" @@ -301,3 +298,9 @@ msgstr "IMU 的型号。" #~ msgid "Get the chip model of the IMU." #~ msgstr "获取 IMU 的芯片型号。" +#~ msgid "M5.begin.svg" +#~ msgstr "" + +#~ msgid "M5.update.svg" +#~ msgstr "" + diff --git a/docs/locales/zh_CN/LC_MESSAGES/hardware/mic.po b/docs/locales/zh_CN/LC_MESSAGES/hardware/mic.po index 4a6a740f..80909fff 100644 --- a/docs/locales/zh_CN/LC_MESSAGES/hardware/mic.po +++ b/docs/locales/zh_CN/LC_MESSAGES/hardware/mic.po @@ -9,7 +9,7 @@ msgid "" msgstr "" "Project-Id-Version: UIFlow2 Programming Guide \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-01-16 15:35+0800\n" +"POT-Creation-Date: 2025-04-18 15:58+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language: zh_CN\n" @@ -20,122 +20,122 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.16.0\n" -#: ../../en/hardware/mic.rst:2 b3b1ce02311549599f19496e986aaeb3 +#: ../../en/hardware/mic.rst:4 e57f58d27baa4b92b75442a1243a4987 msgid "Mic" msgstr "" -#: ../../en/hardware/mic.rst:8 25c23850012444a585f12f2c80ea52ce +#: ../../en/hardware/mic.rst:10 8804ce541cd744b8893639092379e1cb msgid "" "Mic is used to control the built-in microphone inside the host device. " "Below is the detailed Mic support for the host:" msgstr "Mic 用于控制主机内部集成的按键。以下是主机的 Mic 支持详细:" -#: ../../en/hardware/mic.rst:16 fd6d00855f5b44a58e7ea1e52818d066 +#: ../../en/hardware/mic.rst:18 435bb4ece0574f0a9df1c12790176efe msgid "Controller" msgstr "" -#: ../../en/hardware/mic.rst:16 016124fed9e34ed8849fafa9956cc5b5 +#: ../../en/hardware/mic.rst:18 51189895b5d74a2dae11b218addb6fe6 msgid "SPM1423" msgstr "" -#: ../../en/hardware/mic.rst:16 95aa075c9ae94b7bb78660985e0b009e +#: ../../en/hardware/mic.rst:18 2d292262ff8e4837a6122e5f1c374e60 msgid "ES7210" msgstr "" -#: ../../en/hardware/mic.rst:18 a350c75fa5dd458eb7f6b9c871d27d32 +#: ../../en/hardware/mic.rst:20 92a685f46b204513b4a8af3b59f338db msgid "AtomS3" msgstr "" -#: ../../en/hardware/mic.rst:20 08b23215f438464a9cc82bc333230472 +#: ../../en/hardware/mic.rst:22 49a5bd5dfb8345df930dd06d14cf49df msgid "AtomS3 Lite" msgstr "" -#: ../../en/hardware/mic.rst:22 563464ad39a944d087b5ca0782ce6850 +#: ../../en/hardware/mic.rst:24 4630ac0ff7d3495d89c16aa20e80fd53 msgid "AtomS3U" msgstr "" -#: ../../en/hardware/mic.rst:22 ../../en/hardware/mic.rst:26 -#: ../../en/hardware/mic.rst:28 43e31ecabe7445e2a73b02727e10b7af -#: d48c29fc4d4a40e5b0924b0df4b51861 f7d59a3c3ed34bfa8407938cf325a5f5 +#: ../../en/hardware/mic.rst:24 ../../en/hardware/mic.rst:28 +#: ../../en/hardware/mic.rst:30 0811e2a97ba74b279d82edb5a79c36a1 +#: c4758f6c79e44cb7bdf7e317ccbb01a8 msgid "|S|" msgstr "" -#: ../../en/hardware/mic.rst:24 c069a57aecb14302afa33a74a41ea3dc +#: ../../en/hardware/mic.rst:26 0f748011c3b44ca08777250b4dab0620 msgid "StampS3" msgstr "" -#: ../../en/hardware/mic.rst:26 fb9fd53adef8440093386f827616df6e +#: ../../en/hardware/mic.rst:28 2e7d678b3e5049128ec76407d9236b95 msgid "CoreS3" msgstr "" -#: ../../en/hardware/mic.rst:28 9e1f1fe92f0d4c4db6706c793dcf64b8 +#: ../../en/hardware/mic.rst:30 7c8012408daa4010ac6911801f0786f7 msgid "Core2" msgstr "" -#: ../../en/hardware/mic.rst:30 5eb34da6752f43fd95209cbe9b6013fc +#: ../../en/hardware/mic.rst:32 84ecc90dcf39499abef5f3e24621c762 msgid "TOUGH" msgstr "" -#: ../../en/hardware/mic.rst:36 fa721205d5af4d409113af024f40ca78 +#: ../../en/hardware/mic.rst:38 3eeb5c5b50914a33a0fe37ad2a2f4261 msgid "Micropython Example:" msgstr "" -#: ../../en/hardware/mic.rst:43 5171585d314e417eb6b23bdc60eda549 +#: ../../en/hardware/mic.rst:45 b1e15304ee04407eb4ba9be59df2cb60 msgid "UIFLOW2 Example:" msgstr "" -#: ../../en/hardware/mic.rst:45 482d863f8dcf48528dc2d3e390b3c960 +#: ../../en/hardware/mic.rst:47 cfe2c5874245433d976e0a06151208a3 msgid "|example.png|" msgstr "" -#: ../../en/refs/hardware.mic.ref:24 bab5c52ebf774f8b8fb0f9835aae02a4 +#: ../../en/refs/hardware.mic.ref:24 56fc216b868e4f129b53256e0d69f51d msgid "example.png" msgstr "" -#: ../../en/hardware/mic.rst:50 d6319808a20a458980deb1fb8ec1df15 +#: ../../en/hardware/mic.rst:52 4ab03052363f4de0ae81755f3ebf3f70 msgid "|cores3_mic_example.m5f2|" msgstr "" -#: ../../en/hardware/mic.rst:54 1d76b136b5b34e72b17d7072a612f843 +#: ../../en/hardware/mic.rst:56 3947c879dc4743e2a88b0b8ca79124d0 msgid "class Mic" msgstr "" -#: ../../en/hardware/mic.rst:58 4176538d80f44ed4a7bd62f35a2978fe +#: ../../en/hardware/mic.rst:60 a62de768b71f4142b5e893acd6502135 +#, fuzzy msgid "" -"Methods of the Mic Class heavily rely on ``M5.begin()`` |M5.begin.svg| " -"and ``M5.update()`` |M5.update.svg|." +"Methods of the Mic Class heavily rely on ``M5.begin()`` |M5.begin.png| " +"and ``M5.update()`` |M5.update.png|." msgstr "" "Mic Class的方法重度依赖 ``M5.begin()`` |M5.begin.svg| 和 ``M5.update()`` " "|M5.update.svg|。" -#: ../../en/refs/system.ref:1 6807e8d794ee48138098be7de37d7826 -#: e871eb2992cc455199ca1f92741286e3 -msgid "M5.begin.svg" +#: ../../en/refs/system.ref:1 1107b7e3d56f4ac592d832f4bb771ce8 +msgid "M5.begin.png" msgstr "" -#: ../../en/refs/system.ref:4 cb499824f3884480947c1e702b3963d0 -#: e6fe4ba6a4ff45a58bd36750532a5806 -msgid "M5.update.svg" +#: ../../en/refs/system.ref:3 3d7d7c178ef244839da2a1c5f0bf37d2 +msgid "M5.update.png" msgstr "" -#: ../../en/hardware/mic.rst:60 926ed390e60c4dd4a1b9cbdfff87ea7a +#: ../../en/hardware/mic.rst:62 7e69a120c28c4df29f2dfd1d5bb03fb5 +#, fuzzy msgid "" "All calls to methods of Mic objects should be placed after ``M5.begin()``" -" |M5.begin.svg|, and ``M5.update()`` |M5.update.svg| should be called in " +" |M5.begin.png|, and ``M5.update()`` |M5.update.png| should be called in " "the main loop." msgstr "" "调用 Mic 对象的所有方法,需要放在 ``M5.begin()`` |M5.begin.svg| 的后面,并在 主循环中调用 " "``M5.update()`` |M5.update.svg|。" -#: ../../en/hardware/mic.rst:64 99c9877983974f659f577b2ba0dbe0f3 +#: ../../en/hardware/mic.rst:67 8cdf78bd381242a8a2ff89cb9458f101 msgid "Methods" msgstr "" -#: ../../en/hardware/mic.rst:70 94a65fa25ca04f33ad05767a329804dd +#: ../../en/hardware/mic.rst:73 c4f3d866fb1240d19ff53436049c9c08 msgid "Get or set the parameters of the Mic object." msgstr "获取或者设置 Mic 对象的参数。" -#: ../../en/hardware/mic.rst:72 97352e005abb42649482dc18ac23a2ef +#: ../../en/hardware/mic.rst:75 f27c2c547b654930b0555a32bd720a9b msgid "" "When no parameters are passed, it returns an object of " ":py:class:`mic_config_t`. When a :py:class:`mic_config_t` object is " @@ -144,339 +144,330 @@ msgstr "" "当不传入任何参数时,会返回 :py:class:`mic_config_t` 的对象。当传入一个 :py:class:`mic_config_t`" " 的对象, Mic 会设置 Mic 的所有支持的参数。" -#: ../../en/hardware/mic.rst:75 9032d485ff1b43e2ad665b7f943a88e0 +#: ../../en/hardware/mic.rst:78 84b73dcb0ace41e88aedda9d1ff8d3ea msgid "" "When passing parameters from the table below, Mic will get or set the " "passed parameters." msgstr "当传入下表中的参数, Mic 会对传入的参数进行获取或者设置。" -#: ../../en/hardware/mic.rst:77 42b339e772cf44c582b30621d537d383 +#: ../../en/hardware/mic.rst:80 613af91298b544579605008c0f4ba212 msgid "The following parameters are supported:" msgstr "以下是支持的参数:" -#: ../../en/hardware/mic.rst:80 26fbca4664b547d19969c661614acabe +#: ../../en/hardware/mic.rst:83 40ef4d888ed54ab880b4691a0c54ec44 msgid "Parameter" msgstr "" -#: ../../en/hardware/mic.rst:80 60871017de84485cba70fce5f7ac0c79 +#: ../../en/hardware/mic.rst:83 14ddf9a722744601b18b56d5c80cb10c msgid "Type" msgstr "" -#: ../../en/hardware/mic.rst:80 52b5ccf5742b4b78a61a942497a8a57c +#: ../../en/hardware/mic.rst:83 7bba60eb30b74f1a96a5fcae425751dc msgid "Description" msgstr "" -#: ../../en/hardware/mic.rst:82 baa40de597344b2daeac89b08bdc20f8 +#: ../../en/hardware/mic.rst:85 596e9e7bd34f4e389b48d3c7bf5ea8b2 msgid "pin_data_in" msgstr "" -#: ../../en/hardware/mic.rst:82 ../../en/hardware/mic.rst:83 -#: ../../en/hardware/mic.rst:84 ../../en/hardware/mic.rst:85 -#: ../../en/hardware/mic.rst:86 ../../en/hardware/mic.rst:88 -#: ../../en/hardware/mic.rst:89 ../../en/hardware/mic.rst:90 +#: ../../en/hardware/mic.rst:85 ../../en/hardware/mic.rst:86 +#: ../../en/hardware/mic.rst:87 ../../en/hardware/mic.rst:88 +#: ../../en/hardware/mic.rst:89 ../../en/hardware/mic.rst:91 #: ../../en/hardware/mic.rst:92 ../../en/hardware/mic.rst:93 -#: ../../en/hardware/mic.rst:94 ../../en/hardware/mic.rst:95 -#: ../../en/hardware/mic.rst:96 0af4eca08cfe40d99ff3a1eaa8c73052 -#: 0eb81b430dab442986f6875b3e47d4d6 187e1b19674d40eea9e8cc1ce93f12f2 -#: 2a7e0b79c07845b19e90375ddb3a5c21 4772ec57b777447fb05d02cccfd40d4a -#: 4f405de60a3a4d728c1d7f3bdb2d8866 5e790ec4f7044cc38307944f29a577a5 -#: 73ee13e0cf464143ae1f13e6a6dd3162 d063b31670744444a201ac27bf4bf672 -#: d09001ebb4b7433e820f182a1f1bb856 d211d375d8bb44a3bb8f5cd170273744 -#: d293f74b29374b78950d004b64db059d ede771d8b56a427c81de25b34c334341 +#: ../../en/hardware/mic.rst:95 ../../en/hardware/mic.rst:96 +#: ../../en/hardware/mic.rst:97 ../../en/hardware/mic.rst:98 +#: ../../en/hardware/mic.rst:99 0119aa441a124857825f6739adcc589e msgid "(integer)" msgstr "" -#: ../../en/hardware/mic.rst:82 ../../en/hardware/mic.rst:201 -#: c922856d7a324af6bc6aa5553be108f4 d3db71fba3614973be5185e82edbab04 +#: ../../en/hardware/mic.rst:85 ../../en/hardware/mic.rst:204 +#: 3bd291db6aa54bad8977481a58ada686 msgid "Serial data line of I2S, representing audio data in binary complement." msgstr "I2S 的串行数据线,用二进制补码表示的音频数据。" -#: ../../en/hardware/mic.rst:83 715b59bcffcd438b813edb12c34d5ff1 +#: ../../en/hardware/mic.rst:86 3970ad8279b444fdba2a31651194d95a msgid "pin_bck" msgstr "" -#: ../../en/hardware/mic.rst:83 ../../en/hardware/mic.rst:207 -#: 346b56af5966468c8d9cabb7664f7e37 91a5602b1eea4382b6d968b34759f2a8 +#: ../../en/hardware/mic.rst:86 ../../en/hardware/mic.rst:210 +#: fb4ede39ba024aeeb050bfebc85bbc0c msgid "Serial clock line of I2S, corresponding to each bit of digital audio data." msgstr "I2S 的串行时钟线,对应数字音频的每一位数据。" -#: ../../en/hardware/mic.rst:84 85354d27d5ae429b8f1fe6d2218f887e +#: ../../en/hardware/mic.rst:87 31d7620256b744fda5d9929791469c08 msgid "pin_mck" msgstr "" -#: ../../en/hardware/mic.rst:84 ../../en/hardware/mic.rst:213 -#: 0b806c65a19a4c3e858bd19495dd0bc5 c3de36aea79b44dc9e8317e4f056dfcb +#: ../../en/hardware/mic.rst:87 ../../en/hardware/mic.rst:216 +#: 341ba1eac5ca4ec6902f9c3fdee606ec e29ec625497048ee8bf7e13c7b37d740 msgid "" "Master clock line of I2S. Generally, to better synchronize between " "systems, increase the MCLK signal, MCLK frequency = 256 * sampling " "frequency." msgstr "I2S 的主时钟线。一般为了使系统间能够更好地同步时增加MCLK信号,MCLK的频率 = 256 * 采样频率。" -#: ../../en/hardware/mic.rst:85 a62a2dadb7684925a6dc5ad9b02aa182 +#: ../../en/hardware/mic.rst:88 927d23a899be470c9a0e9ea0fb8f3133 msgid "pin_ws" msgstr "" -#: ../../en/hardware/mic.rst:85 ../../en/hardware/mic.rst:220 -#: 72c22f12ae6c4734a268455fed4d01a2 d263a79dded2448fba655c4f1554a9a3 +#: ../../en/hardware/mic.rst:88 ../../en/hardware/mic.rst:223 +#: 38b299c561844dd58c9d8c20b8259f22 msgid "Frame clock of I2S, used to switch left and right channel data." msgstr "I2S 的帧时钟,用于切换左右声道的数据。" -#: ../../en/hardware/mic.rst:86 8346960280a24dba94ace48d9f59f9af +#: ../../en/hardware/mic.rst:89 375f998c6cb44d5f94741a8f0e6f7892 msgid "sample_rate" msgstr "" -#: ../../en/hardware/mic.rst:86 ../../en/hardware/mic.rst:226 -#: 1337d624454a488689ad5b340fc5aaf9 9ddebae07fc24ac3a30555dc27f73c12 +#: ../../en/hardware/mic.rst:89 ../../en/hardware/mic.rst:229 +#: b11a480ea12b44879ae25421e1bdf9c9 msgid "Target sampling rate of input audio." msgstr "输入音频的目标采样率。" -#: ../../en/hardware/mic.rst:87 115483e6675647dcbe4267a907865ca7 +#: ../../en/hardware/mic.rst:90 b8aaeb00a9534b1a88d5b2ad1b8c40a6 msgid "stereo" msgstr "" -#: ../../en/hardware/mic.rst:87 ../../en/hardware/mic.rst:91 -#: 26141673832a4791940aed4e0857a010 5bd6e337328c4a9b8624cc5e0100a514 +#: ../../en/hardware/mic.rst:90 ../../en/hardware/mic.rst:94 +#: c411d48d9d184598a12107123845771d msgid "(boolean)" msgstr "" -#: ../../en/hardware/mic.rst:87 ../../en/hardware/mic.rst:232 -#: 10657aac934345ce8739922fe7aee893 7a81d8370c304a61b767701eef6cda25 +#: ../../en/hardware/mic.rst:90 ../../en/hardware/mic.rst:235 +#: f9407dbdca4340f8b6e55465707f05da msgid "Use stereo output." msgstr "使用双声道输出。" -#: ../../en/hardware/mic.rst:88 500d9dab39f941df91b7e671d26f4ecc +#: ../../en/hardware/mic.rst:91 29cf81e2f4234c1e88b79edacd6978f0 msgid "over_sampling" msgstr "" -#: ../../en/hardware/mic.rst:88 ../../en/hardware/mic.rst:244 -#: 101c5b5b000944b1bab66bfb6dccc0bd 22588f20f8ff4518b8e380bbdec1270f +#: ../../en/hardware/mic.rst:91 ../../en/hardware/mic.rst:247 +#: 23a6b2f078234fc288256173b615f3c7 msgid "Number of times to average the sampling." msgstr "求平均值的采样次数。" -#: ../../en/hardware/mic.rst:89 08d1734f5e414773927c835278f94975 +#: ../../en/hardware/mic.rst:92 df2bb695e12e4e9b837760810af9a319 msgid "magnification" msgstr "" -#: ../../en/hardware/mic.rst:89 ../../en/hardware/mic.rst:250 -#: 036d4cd1a814443c9ab2957fd614e379 59e512a6ffd04fbca5730a2449695975 +#: ../../en/hardware/mic.rst:92 ../../en/hardware/mic.rst:253 +#: 2a882f822d044a27af89646db4eee352 msgid "Multiplier of the input value." msgstr "输入值的乘数。" -#: ../../en/hardware/mic.rst:90 093069a0e6cc4e0891daf55cbdf8a7d7 +#: ../../en/hardware/mic.rst:93 9fb79c4e05b74ef7815dd1ca43432b7b msgid "noise_filter_level" msgstr "" -#: ../../en/hardware/mic.rst:90 ../../en/hardware/mic.rst:256 -#: 117412d5ea6f4712b87adfcd21d16aa8 be18d9553db84939bc4a52ca282beaee +#: ../../en/hardware/mic.rst:93 ../../en/hardware/mic.rst:259 +#: 91a9553d27444b2aa75d21c75eec7379 msgid "Coefficient of the previous value used for noise filtering." msgstr "先前值的系数,用于噪声过滤。" -#: ../../en/hardware/mic.rst:91 98a1bf88cf90498ca772bcf08c51ee7a +#: ../../en/hardware/mic.rst:94 201016f7fdb548b48e6a8902535b6482 msgid "use_adc" msgstr "" -#: ../../en/hardware/mic.rst:91 ../../en/hardware/mic.rst:262 -#: 407ee6a8b1564fb0b57d751757b89440 ff0374ecf4b946afb7fda9edac26b217 +#: ../../en/hardware/mic.rst:94 ../../en/hardware/mic.rst:265 +#: 3f1a3dbe634a4320a077f69e51fb8cad msgid "Use analog input microphone (only pin_data_in is needed)." msgstr "使用模拟输入麦克风(仅需要 pin_data_in )。" -#: ../../en/hardware/mic.rst:92 67b98880a06a49b18a5201f0273b421f +#: ../../en/hardware/mic.rst:95 f400bd6141f746a181f49fe03d34ac7b msgid "dma_buf_len" msgstr "" -#: ../../en/hardware/mic.rst:92 ../../en/hardware/mic.rst:268 -#: 0aaa410f700e4aa6bd424d435b9e0193 746ea7885ba34e9cb36fcb3567c46fde +#: ../../en/hardware/mic.rst:95 ../../en/hardware/mic.rst:271 +#: c8986fce42364e14aee17917750d5e54 msgid "DMA buffer length of I2S." msgstr "I2S 的DMA缓冲区长度。" -#: ../../en/hardware/mic.rst:93 eb2d39e792c3466592ab9a58a5b042f5 +#: ../../en/hardware/mic.rst:96 a1bb48b4c5d24f3ea231e79f55c3b5fd msgid "dma_buf_count" msgstr "" -#: ../../en/hardware/mic.rst:93 ../../en/hardware/mic.rst:274 -#: 3979f1e15fc542abbf5f0821a01bd5e2 537ee9ed6f6245d8a901990f6a9be9ca +#: ../../en/hardware/mic.rst:96 ../../en/hardware/mic.rst:277 +#: 2e988b8f38914cbc8b50abd10ef06f9d msgid "Number of DMA buffers of I2S." msgstr "I2S 的DMA缓冲区数量。" -#: ../../en/hardware/mic.rst:94 633abd895a66422e8bbd3d6ac5f2aed3 +#: ../../en/hardware/mic.rst:97 5afeeddc37eb4315b6d028c7be04a8a9 msgid "task_priority" msgstr "" -#: ../../en/hardware/mic.rst:94 ../../en/hardware/mic.rst:280 -#: 85a99df483cd4d609c2e782cf49a6c03 9f7f97b6fff445c49009fad439a3e276 +#: ../../en/hardware/mic.rst:97 ../../en/hardware/mic.rst:283 +#: 294bdbc348cf4d00a7b98d0b43d088b3 msgid "Priority of background tasks." msgstr "后台任务优先级。" -#: ../../en/hardware/mic.rst:95 65f372d83f624892b0de68eb85e0be30 +#: ../../en/hardware/mic.rst:98 03b4e68a267c4ac39e90c5753943177c msgid "task_pinned_core" msgstr "" -#: ../../en/hardware/mic.rst:95 ../../en/hardware/mic.rst:286 -#: 2ada91cce4a147ec8aedad2eef9da634 66aa58513ae54d57a9d6c906b0225a35 +#: ../../en/hardware/mic.rst:98 ../../en/hardware/mic.rst:289 +#: 8dd1d7000a6b440a877c6c09cc61b583 msgid "CPU used by background tasks." msgstr "后台任务使用的CPU。" -#: ../../en/hardware/mic.rst:96 6a8ed0ef24d54e1e9ab4179ee883442c +#: ../../en/hardware/mic.rst:99 4477970f856a404d8a2147d3a8f21861 msgid "i2s_port" msgstr "" -#: ../../en/hardware/mic.rst:96 ../../en/hardware/mic.rst:292 -#: b117a5fb17f24bd3baeb9a4ca1beb3a5 f0432459eb1a4721806adf02ad202982 +#: ../../en/hardware/mic.rst:99 ../../en/hardware/mic.rst:295 +#: c6ffca80d0de47c7b6b465dd77179609 msgid "I2S port." msgstr "I2S端口。" -#: ../../en/hardware/mic.rst:99 ../../en/hardware/mic.rst:126 -#: ../../en/hardware/mic.rst:135 ../../en/hardware/mic.rst:144 -#: ../../en/hardware/mic.rst:153 ../../en/hardware/mic.rst:168 -#: ../../en/hardware/mic.rst:177 ../../en/hardware/mic.rst:190 -#: 06a535ebb9fe4de6a70bc76e077d2141 3c08b05a3c37485e8c87c32355a777e9 -#: 5b7e8fc9ae3c493ea33d59e6f1bb90d2 5df61cd01a3b4a43a3fcf4e304947f56 -#: 74c7d437997d4f6d8675627f18b066e7 a070a72bfa414c1ea185df48285ab1f7 -#: deae6b4a69394c36aa4ec30427a63150 e9b71e4defa04953aeee3feb8e0777b8 +#: ../../en/hardware/mic.rst:102 ../../en/hardware/mic.rst:129 +#: ../../en/hardware/mic.rst:138 ../../en/hardware/mic.rst:147 +#: ../../en/hardware/mic.rst:156 ../../en/hardware/mic.rst:171 +#: ../../en/hardware/mic.rst:180 ../../en/hardware/mic.rst:193 +#: 4f5c13c91acf4616aedadb46ed3b913d msgid "UIFLOW2:" msgstr "" -#: ../../en/hardware/mic.rst:101 a22d92a5e94e4c33b41401f0471ac46c +#: ../../en/hardware/mic.rst:104 ba34712f893b44c187deab1043b9987b msgid "Read property:" msgstr "读取属性:" -#: ../../en/hardware/mic.rst:103 ../../en/hardware/mic.rst:113 -#: 518d0d1181df41d59cf4b8ea3f76a992 68733467b31f41f69d5e4ab00a85adb4 +#: ../../en/hardware/mic.rst:106 ../../en/hardware/mic.rst:116 +#: c4a131af25974f8bb50e863e711fc615 msgid "Python::" msgstr "" -#: ../../en/hardware/mic.rst:107 2129e48778c84b6bb662f883fa0e2889 +#: ../../en/hardware/mic.rst:110 8ce7b4e6ceb144778a2a07649aa5a939 msgid "|get_config_boolean.png|" msgstr "" -#: ../../en/refs/hardware.mic.ref:2 3a2677cb0ab141e5a3c49c6c9a6f4847 +#: ../../en/refs/hardware.mic.ref:2 772ff2d4d1564e999f80bf9125cd4581 msgid "get_config_boolean.png" msgstr "" -#: ../../en/hardware/mic.rst:109 683a2e8bbf884521a6ac68c70a2743c0 +#: ../../en/hardware/mic.rst:112 32387f0aec014aa9961d3283e78341d6 msgid "|get_config_int.png|" msgstr "" -#: ../../en/refs/hardware.mic.ref:4 a32967c25c594889890164819d4d311d +#: ../../en/refs/hardware.mic.ref:4 1e1efc81bc3f4bc6a0e64285b53a9bbd msgid "get_config_int.png" msgstr "" -#: ../../en/hardware/mic.rst:111 c431e69fb771473ab00f8702ec5a26e9 +#: ../../en/hardware/mic.rst:114 51e9ee059de841eca8d253b999d47dfc msgid "Set property:" msgstr "设置属性:" -#: ../../en/hardware/mic.rst:117 63cf9712093b4268b1e63e5ce395fa64 +#: ../../en/hardware/mic.rst:120 0b6de19a71de4c4c88d20a1a230bdba0 msgid "|set_config_int.png|" msgstr "" -#: ../../en/refs/hardware.mic.ref:6 e171a5e26cde4b6382e4bd8c85592a41 +#: ../../en/refs/hardware.mic.ref:6 7b720173effb4bb18b754cdf42180059 msgid "set_config_int.png" msgstr "" -#: ../../en/hardware/mic.rst:119 66df4fd37463410e8ce8149bd470aecb +#: ../../en/hardware/mic.rst:122 8bafb1dbdf344c2b928569523f637ac8 msgid "|set_config_boolean.png|" msgstr "" -#: ../../en/refs/hardware.mic.ref:8 b56c556ef5754d0e80f8bf091cd27d88 +#: ../../en/refs/hardware.mic.ref:8 2eb5b67e2faa4e30907cb5bbafdab884 msgid "set_config_boolean.png" msgstr "" -#: ../../en/hardware/mic.rst:124 73f6ab1d7a2e4fb186be8f0fe9227600 +#: ../../en/hardware/mic.rst:127 d8fdadaac4db4d54ab81ce8c54c6f6fa msgid "Start the Mic function. Returns True if successful." msgstr "启动 Mic 功能。执行成功返回 True 。" -#: ../../en/hardware/mic.rst:128 4e7887f02e62426f8b161f10fbd5c317 +#: ../../en/hardware/mic.rst:131 c39cb4fd9fd64d0399f25563ecc47416 msgid "|begin.png|" msgstr "" -#: ../../en/refs/hardware.mic.ref:10 5b4f8e89eebc41f292dc4abb0092b897 +#: ../../en/refs/hardware.mic.ref:10 dbf31bca4ddc4f96847e59a58e2884d8 msgid "begin.png" msgstr "" -#: ../../en/hardware/mic.rst:133 4278c6fd930a4fda90d3df88adf6115b +#: ../../en/hardware/mic.rst:136 28878b52c2894c10b9790fff33d382c6 #, fuzzy msgid "Stop the Mic function. Returns True if successful." msgstr "启动 Mic 功能。执行成功返回 True 。" -#: ../../en/hardware/mic.rst:137 7b78e68363fd49c0be639dc6d967c71b +#: ../../en/hardware/mic.rst:140 9ccab4c9d7204e319f0389c45736927d msgid "|end.png|" msgstr "" -#: ../../en/refs/hardware.mic.ref:12 84d6fb65e21641b0b7f1efc69b0db2dc +#: ../../en/refs/hardware.mic.ref:12 c92d98ac20674eee9d13b9e5eb417da3 msgid "end.png" msgstr "" -#: ../../en/hardware/mic.rst:142 f13750790c254e2a9eec496756c6a397 +#: ../../en/hardware/mic.rst:145 484635abd634464fa048bd3e5a428930 msgid "Check if Mic is running. Returns a boolean value." msgstr "获取 Mic 是否处于运行状态, 返回bool类型。" -#: ../../en/hardware/mic.rst:146 f7794b9b59ff466d87a32e6f2042a366 +#: ../../en/hardware/mic.rst:149 d1b37267382148de9b07fd8aed13ea0c msgid "|isRunning.png|" msgstr "" -#: ../../en/refs/hardware.mic.ref:14 1966d9ed28b7418ea6d52db57d076aa6 +#: ../../en/refs/hardware.mic.ref:14 f8d476a4d5a847e1b1f105dde3db598b msgid "isRunning.png" msgstr "" -#: ../../en/hardware/mic.rst:151 e0bb950ea0b146e183d97a69279d746d +#: ../../en/hardware/mic.rst:154 310f58a5826a41f0b29d01c4c11a9477 msgid "Check if Mic is enabled. Returns a boolean value." msgstr "获取 Mic 是否处于使能状态, 返回bool类型。" -#: ../../en/hardware/mic.rst:155 3258f57924e2404498d8a96da4367f55 +#: ../../en/hardware/mic.rst:158 08f0b2d56a3d4df3b9d63376a2e891d0 msgid "|isEnabled.png|" msgstr "" -#: ../../en/refs/hardware.mic.ref:16 de1552eb30b340a18f5446c256a610cb +#: ../../en/refs/hardware.mic.ref:16 a0eaf4f8b3ef472fbcdf8a775dba0728 msgid "isEnabled.png" msgstr "" -#: ../../en/hardware/mic.rst:160 a990604a5ca240a8b6ba8ab2ca25fee2 +#: ../../en/hardware/mic.rst:163 2adf9376137643ba9e4521bf9c9f7985 msgid "Check if Mic is recording. Returns an integer value." msgstr "获取 Mic 是否处于录音状态, 返回int类型。" -#: ../../en/hardware/mic.rst:162 96082b5df03f41e9b49a9a2e8b0a889a +#: ../../en/hardware/mic.rst:165 61a651177b544453bee893a6ae516355 msgid "Return values:" msgstr "返回值:" -#: ../../en/hardware/mic.rst:164 dea9703ffc434db1ba0eeb5d27181ddd +#: ../../en/hardware/mic.rst:167 17f7e0e6a604437c8cd1ada8ff4f6675 msgid "0=not recording" msgstr "" -#: ../../en/hardware/mic.rst:165 77a9e61b577b41f0854141786d8eee33 +#: ../../en/hardware/mic.rst:168 43fd3639c7f749a482c065b943fd23fb msgid "1=recording (There's room in the queue)" msgstr "" -#: ../../en/hardware/mic.rst:166 43c77458d97e455dbd4776b77897a6fe +#: ../../en/hardware/mic.rst:169 fc4a701361344106b19604146a30b5c4 msgid "2=recording (There's no room in the queue.)" msgstr "" -#: ../../en/hardware/mic.rst:170 afffe8cdd41448959fe9362435e11777 +#: ../../en/hardware/mic.rst:173 7ee7bb2f210e48c793a9e43ae6ac44ce msgid "|isRecording.png|" msgstr "" -#: ../../en/refs/hardware.mic.ref:18 70867a62e6654e559acffcb024f622ea +#: ../../en/refs/hardware.mic.ref:18 5303cab834b5429ea80b6edd22d90843 msgid "isRecording.png" msgstr "" -#: ../../en/hardware/mic.rst:175 dc6efc84d783411aa8cd4a870b829cca +#: ../../en/hardware/mic.rst:178 488b1ea424344d169f6585296d580d7c msgid "" "Set the sampling rate. The parameter sample_rate generally includes 8000," " 11025, 22050, 32000, 44100." msgstr "设置采样率。参数 sample_rate 一般有 8000 , 11025 ,22050 ,32000 ,44100" -#: ../../en/hardware/mic.rst:179 e341db4e946247059f441d4ef001ab06 +#: ../../en/hardware/mic.rst:182 27358e8b217147728618088f6acabab9 msgid "|setSampleRate.png|" msgstr "" -#: ../../en/refs/hardware.mic.ref:20 fe713a4a60774ea4b69b75211e179448 +#: ../../en/refs/hardware.mic.ref:20 a463f6b39d3a4e90a78b26c3e2413213 msgid "setSampleRate.png" msgstr "" -#: ../../en/hardware/mic.rst:184 108551c468fb4be78d560e09e7121f05 +#: ../../en/hardware/mic.rst:187 51e6f3869af8479185d88938896ad1cd msgid "Record audio data." msgstr "" -#: ../../en/hardware/mic.rst:186 d51ec8108d774afa8c50f64bee2c5517 +#: ../../en/hardware/mic.rst:189 8642bb8b84ab4a3db1edcab9a214273d msgid "" "The parameter rec_data requires passing a buffer. The parameter rate " "generally includes 8000, 11025, 22050, 32000, 44100, with a default of " @@ -485,15 +476,15 @@ msgstr "" "参数 rec_data 要求传入一个buffer。参数 rate 一般有 8000, 11025,22050,32000,44100, " "默认填8000。参数 stereo 传入True或者False。" -#: ../../en/hardware/mic.rst:192 c0054fea893f4bd5abe968121c6c47d9 +#: ../../en/hardware/mic.rst:195 c71644866cbe4d3f802404056d62b049 msgid "|record.png|" msgstr "" -#: ../../en/refs/hardware.mic.ref:22 3939e699b46e43b19d80e32e6ae3ba13 +#: ../../en/refs/hardware.mic.ref:22 7ce3e8471c5e474a8543781369f725be msgid "record.png" msgstr "" -#: ../../en/hardware/mic.rst:196 9790429a34aa4a5993a0dcb32a109a5d +#: ../../en/hardware/mic.rst:199 25e4e698db37451798841b7f2a2d9744 msgid "class mic_config_t" msgstr "" @@ -684,3 +675,9 @@ msgstr "" #~ msgid "record.svg" #~ msgstr "" +#~ msgid "M5.begin.svg" +#~ msgstr "" + +#~ msgid "M5.update.svg" +#~ msgstr "" + diff --git a/docs/locales/zh_CN/LC_MESSAGES/hats/cardkb.po b/docs/locales/zh_CN/LC_MESSAGES/hats/cardkb.po index 5499d75a..990d4f95 100644 --- a/docs/locales/zh_CN/LC_MESSAGES/hats/cardkb.po +++ b/docs/locales/zh_CN/LC_MESSAGES/hats/cardkb.po @@ -9,7 +9,7 @@ msgid "" msgstr "" "Project-Id-Version: UIFlow2 Programming Guide \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-01-16 15:35+0800\n" +"POT-Creation-Date: 2025-04-18 15:58+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language: zh_CN\n" @@ -21,78 +21,84 @@ msgstr "" "Generated-By: Babel 2.16.0\n" #: ../../en/hats/cardkb.rst:2 ../../en/refs/hat.cardkb.ref -#: 504fe05e453241bd8ff17b62e15704e3 b82d8160b035427aaf9221bdd7191dc5 +#: 5b1041d3d75b42bb9644a27041c67d0e 9563193ee99f4d80bfbefdadc1339a6d msgid "CardKB Hat" msgstr "" -#: ../../en/hats/cardkb.rst:6 5218eef6c758401088c0b596302d901f +#: ../../en/hats/cardkb.rst:6 5e9a75a26283441085521f3125b334c9 msgid "The following products are supported:" msgstr "" -#: ../../en/hats/cardkb.rst:8 de77716253c44986948c39655feea5ca +#: ../../en/hats/cardkb.rst:8 273909a4480d4a9eb895dc4b553f45f3 msgid "|CardKB Hat|" msgstr "" -#: ../../en/hats/cardkb.rst:11 4805a0e85b794329a30c3d2efcec4f6d +#: ../../en/hats/cardkb.rst:11 02407ca35cca429dad7602efa74381f3 msgid "Micropython Example:" msgstr "" -#: ../../en/hats/cardkb.rst:18 5f4fdda170f24f45a7ae6bff2daba363 +#: ../../en/hats/cardkb.rst:18 c5e7a6dc81db4ed5b1043277ad977fbf msgid "UIFLOW2 Example:" msgstr "" -#: ../../en/hats/cardkb.rst:20 bb9d242b42934cbdae9973834824fa58 -msgid "|example.svg|" +#: ../../en/hats/cardkb.rst:20 86a277f753b442699008009a2b0150aa +msgid "|example.png|" msgstr "" -#: ../../en/refs/hat.cardkb.ref:6 bccad13a273b492192fff90ddab9fb7e -msgid "example.svg" +#: ../../en/refs/hat.cardkb.ref:6 ae360e222e3a49708c092e26d68148b5 +msgid "example.png" msgstr "" -#: ../../en/hats/cardkb.rst:25 228d99754d37442cb5bb02afde17e76a +#: ../../en/hats/cardkb.rst:25 87af136db0f440c0851f6232cbc86335 msgid "|stickc_plus2_cardkb_example.m5f2|" msgstr "" -#: ../../en/hats/cardkb.rst:29 546f74b735f64d7fa20d673b19dff996 +#: ../../en/hats/cardkb.rst:29 f118a1eaa6d14b26ac3db9609afb9b85 msgid "class CardKBHat" msgstr "" -#: ../../en/hats/cardkb.rst:32 b9fcce3b3c52402a81d603fc3d99f7e5 +#: ../../en/hats/cardkb.rst:32 97e3f4def31547b39ec746cd6f3a79d2 msgid "Constructors" msgstr "" -#: ../../en/hats/cardkb.rst:36 63b8eb5c0ad0433dbf4ae6d051993e38 +#: ../../en/hats/cardkb.rst:36 8505e572938b41caa274174f979d8999 msgid "Create a CardKBHat object." msgstr "" -#: ../../en/hats/cardkb.rst 64ceac3bedcc406791ea1a5fdb886755 +#: ../../en/hats/cardkb.rst c74366c88ac541d6a164627198d3c84c msgid "Parameters" msgstr "" -#: ../../en/hats/cardkb.rst:38 7010a655dacc409fa0c65df28904b4fd +#: ../../en/hats/cardkb.rst:38 90e13ee048404fd983e9e18dcd7402f4 msgid "I2C object" msgstr "" -#: ../../en/hats/cardkb.rst:39 5dd0cae813bb419aa2ca58ae46fd1ead +#: ../../en/hats/cardkb.rst:39 1b2357a324544e20b1b27fda0b8a1b18 msgid "the I2C address of the device. Default is 0x5F." msgstr "" -#: ../../en/hats/cardkb.rst:41 739f7c70aea746bd93a9e35b4864b8fb +#: ../../en/hats/cardkb.rst:41 39ce792d71014b16bd7888f496e309e7 msgid "UIFLOW2:" msgstr "" -#: ../../en/hats/cardkb.rst:43 0a7bc1693b264957bf51e0a16d9f71a1 +#: ../../en/hats/cardkb.rst:43 a819e615647a460daeaa5979adae851a msgid "|init.png|" msgstr "" -#: ../../en/refs/hat.cardkb.ref:10 7ecf95ed42094297a46b05c81a4b2ea6 +#: ../../en/refs/hat.cardkb.ref:8 aaa4c946d3c442ea9bb2e12af41bef0c msgid "init.png" msgstr "" -#: ../../en/hats/cardkb.rst:46 d751fd626c3644069ce2932eef69c2c6 +#: ../../en/hats/cardkb.rst:46 aa16edadee03417abc6741afbcb4381a msgid "" "CardKBHat class inherits CardKBUnit class, See " ":ref:`unit.CardKBUnit.Methods ` for more " "details." msgstr "" +#~ msgid "|example.svg|" +#~ msgstr "" + +#~ msgid "example.svg" +#~ msgstr "" + diff --git a/docs/locales/zh_CN/LC_MESSAGES/hats/neoflash.po b/docs/locales/zh_CN/LC_MESSAGES/hats/neoflash.po index 88817334..e14fc208 100644 --- a/docs/locales/zh_CN/LC_MESSAGES/hats/neoflash.po +++ b/docs/locales/zh_CN/LC_MESSAGES/hats/neoflash.po @@ -9,7 +9,7 @@ msgid "" msgstr "" "Project-Id-Version: UIFlow2 Programming Guide \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-01-16 15:35+0800\n" +"POT-Creation-Date: 2025-04-18 15:58+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language: zh_CN\n" @@ -20,11 +20,11 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.16.0\n" -#: ../../en/hats/neoflash.rst:2 6c8daef23e64437da86dae4ed11d115c +#: ../../en/hats/neoflash.rst:2 97778fc3e2ea4a3f8917f21427cba083 msgid "NeoFlash Hat" msgstr "" -#: ../../en/hats/neoflash.rst:7 7e2d125385cf4b39a2649b3b868133f1 +#: ../../en/hats/neoflash.rst:7 209e095423b74c4aa0034fbe9d992d6c msgid "" "NeoFlash HAT is specifically designed for M5StickC, it is an RGB LED " "matrix. Space on PCB board is 58x23.5mm and total include 126 RGB LEDs. " @@ -33,122 +33,146 @@ msgid "" "experience on either display digital numbers or colorful light effect." msgstr "" -#: ../../en/hats/neoflash.rst:14 e37dae8df5fa4c2890c2cb1d7cec6644 +#: ../../en/hats/neoflash.rst:14 a47f7464f7fd441998fbc27c4e4411de msgid "Support the following products:" msgstr "" -#: ../../en/hats/neoflash.rst:16 bbea99fef8c746968a78284ec6f67a13 +#: ../../en/hats/neoflash.rst:16 36bf2f8be11a462eaa2a31a50f7eae12 msgid "|NeoFlashHat|" msgstr "" -#: ../../en/refs/hat.neoflash.ref 8664d35bc7a64ec294641d0925b23b87 +#: ../../en/refs/hat.neoflash.ref f28bba6145e4401d954dfdec3017053f msgid "NeoFlashHat" msgstr "" -#: ../../en/hats/neoflash.rst:19 99393031ab75455f825bdb35ed814286 +#: ../../en/hats/neoflash.rst:19 433ef3f5b8f84e97b654bc2e2f8b3003 msgid "Micropython Example::" msgstr "" -#: ../../en/hats/neoflash.rst:31 53ca38deece34f6881e75018080ba487 +#: ../../en/hats/neoflash.rst:31 dfc97e2b74b14e4e99904482eb07ec80 msgid "UIFLOW2 Example:" msgstr "" -#: ../../en/hats/neoflash.rst:33 e6be0a8773304782aa7b796ee025ddb2 -msgid "|example.svg|" +#: ../../en/hats/neoflash.rst:33 0690a4d22b1b4496b56e8c02924e1350 +msgid "|example.png|" msgstr "" -#: ../../en/refs/hat.neoflash.ref:11 e9fdacb7b1b5465bbf53585d6f8bf3f9 -msgid "example.svg" +#: ../../en/refs/hat.neoflash.ref:11 b6fe708f95e8472d944338eaa1a32427 +msgid "example.png" msgstr "" -#: ../../en/hats/neoflash.rst:40 6992ec742c934c1f987858954c410484 +#: ../../en/hats/neoflash.rst:40 649d8b2f08d742ce95bc83f2495196b1 msgid "class NeoFlashHat" msgstr "" -#: ../../en/hats/neoflash.rst:43 340effaadbc4446c9457d12b6e775f05 +#: ../../en/hats/neoflash.rst:43 c527a2bfd2d240278505cb1c417eedb5 msgid "Constructors" msgstr "" -#: ../../en/hats/neoflash.rst:47 22150cbb9c3948c0b6e00ea97bff7a92 +#: ../../en/hats/neoflash.rst:47 158aa4d3cd8f4207876f8bda64b5c282 msgid "Initialize the NeoFlashHat." msgstr "" -#: ../../en/hats/neoflash.rst 3cd24ac711c74334aa2d48688e51a10a -#: 41ac64cbb9d14c10a77b9090f5db6568 8a2dad61d3b54844a5e4ee2863c1530d +#: ../../en/hats/neoflash.rst 443ca822a2cd4e8a9be76c3e20e7520d +#: 8e7a9133ad2942cd851206acbbb5fc24 fc7964bbc7a9451d8a25c18258badad1 msgid "Parameters" msgstr "" -#: ../../en/hats/neoflash.rst:49 ef48bcfd077c41c7956d5f71581e3fb9 +#: ../../en/hats/neoflash.rst:49 874aebf175c843b088035520ee453cd5 msgid "The port to which the NeoFlashHat is connected. port[0]: LEDs pin." msgstr "" #: ../../en/hats/neoflash.rst:51 ../../en/hats/neoflash.rst:67 -#: ../../en/hats/neoflash.rst:78 31d47110604c4f978b52cbaba28744f5 -#: 70721f75828f4615a16a50210f20127e aebad07385f045aa82dfc638fa64a3a5 +#: ../../en/hats/neoflash.rst:78 03c759407f034fdb94f9a43cc16b874b +#: 75c373ec8994403985de221c35a4379e 813d35fabd4d45cf859303f365e836ae msgid "UIFLOW2:" msgstr "" -#: ../../en/hats/neoflash.rst:53 f41318eccf6f48d0990d505ffd154ac9 -msgid "|init.svg|" +#: ../../en/hats/neoflash.rst:53 9a095201b32744829f6bbaf57766f0f6 +msgid "|init.png|" msgstr "" -#: ../../en/refs/hat.neoflash.ref:7 a52aed2fd5844a6fb6f88877d578d7e1 -msgid "init.svg" +#: ../../en/refs/hat.neoflash.ref:7 b7683d610a34418c8c9a7baa9d3e1170 +msgid "init.png" msgstr "" -#: ../../en/hats/neoflash.rst:57 4daf5a56789148a0a311de1261d3e6ef +#: ../../en/hats/neoflash.rst:57 901becd9e505479599889a5ab7035818 msgid "Methods" msgstr "" -#: ../../en/hats/neoflash.rst:61 a9aeb6be965e43eda6f5d46e6a3b58e4 +#: ../../en/hats/neoflash.rst:61 639d1d6bf4ca4968b66b5e504b094c68 msgid "Set the color of the pixel." msgstr "" -#: ../../en/hats/neoflash.rst:63 aba3ae7df05d4a40b47383f352229407 +#: ../../en/hats/neoflash.rst:63 d41c61db67a843d7a9170b736c7fe2ef msgid "The x coordinate of the pixel." msgstr "" -#: ../../en/hats/neoflash.rst:64 b768081006f94177a1637d9534f10cb7 +#: ../../en/hats/neoflash.rst:64 3d50644fddb94274b0f044f3f7045be3 msgid "The y coordinate of the pixel." msgstr "" -#: ../../en/hats/neoflash.rst:65 9b13cf9179f547b1b5ae3796ff764f95 +#: ../../en/hats/neoflash.rst:65 ab710430cb154ba097cb8088b09adaa5 msgid "The color of the pixel." msgstr "" -#: ../../en/hats/neoflash.rst:69 b81d2647f2e54084adde8312a69d5b33 -msgid "|set_pixel.svg|" +#: ../../en/hats/neoflash.rst:69 d388a4f1bbd84716ae35065a9674f9bb +msgid "|set_pixel.png|" msgstr "" -#: ../../en/refs/hat.neoflash.ref:8 ba3d8e7464e74b75b9c57fce0b07196d -msgid "set_pixel.svg" +#: ../../en/refs/hat.neoflash.ref:8 a92cab397b0349d9a1d049da181c3871 +msgid "set_pixel.png" msgstr "" -#: ../../en/hats/neoflash.rst:74 e30460a0336c44278dcb61ba73915dc6 +#: ../../en/hats/neoflash.rst:74 636a3edeb2894000beb8b62b5c451052 msgid "Set the color of the pixels." msgstr "" -#: ../../en/hats/neoflash.rst:76 982c752b92d24d40b1ba08a87d1f4847 +#: ../../en/hats/neoflash.rst:76 5b2f3400d54b48d3a92cb0f015a6b2a3 msgid "The list of the pixel position and color, [x, y, color]." msgstr "" -#: ../../en/hats/neoflash.rst:80 715307f1567b4c81849a474e145ba155 -msgid "|set_pixels.svg|" +#: ../../en/hats/neoflash.rst:80 8b77bb0a9c724564a47c1c30414973e2 +msgid "|set_pixels.png|" msgstr "" -#: ../../en/refs/hat.neoflash.ref:9 8890bb8f968146fba19e95605e33f6b6 -msgid "set_pixels.svg" +#: ../../en/refs/hat.neoflash.ref:9 097eb8e3340f49f59d7da2203f63e224 +msgid "set_pixels.png" msgstr "" -#: ../../en/hats/neoflash.rst:84 c0057653c68746ffac2a9e6a5694712b +#: ../../en/hats/neoflash.rst:84 ddad1314e40c49a2afa29d1179c72fe4 msgid "Constants" msgstr "" -#: ../../en/hats/neoflash.rst:88 f05ecd133b2c4d909954f1b1d3fe3657 +#: ../../en/hats/neoflash.rst:88 f89cab8d541e44dc918406d57213ec62 msgid "The width of the NeoFlashHat." msgstr "" -#: ../../en/hats/neoflash.rst:92 0b0e752053cf4d1e95830053a78370df +#: ../../en/hats/neoflash.rst:92 3c6ee6ea80af4949a3da00c1a01eb4ab msgid "The height of the NeoFlashHat." msgstr "" +#~ msgid "|example.svg|" +#~ msgstr "" + +#~ msgid "example.svg" +#~ msgstr "" + +#~ msgid "|init.svg|" +#~ msgstr "" + +#~ msgid "init.svg" +#~ msgstr "" + +#~ msgid "|set_pixel.svg|" +#~ msgstr "" + +#~ msgid "set_pixel.svg" +#~ msgstr "" + +#~ msgid "|set_pixels.svg|" +#~ msgstr "" + +#~ msgid "set_pixels.svg" +#~ msgstr "" + diff --git a/docs/locales/zh_CN/LC_MESSAGES/hats/servo.po b/docs/locales/zh_CN/LC_MESSAGES/hats/servo.po index 40d15a79..02b002c5 100644 --- a/docs/locales/zh_CN/LC_MESSAGES/hats/servo.po +++ b/docs/locales/zh_CN/LC_MESSAGES/hats/servo.po @@ -9,7 +9,7 @@ msgid "" msgstr "" "Project-Id-Version: UIFlow2 Programming Guide \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-01-16 15:35+0800\n" +"POT-Creation-Date: 2025-04-18 15:58+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language: zh_CN\n" @@ -20,11 +20,11 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.16.0\n" -#: ../../en/hats/servo.rst:2 871e77abea02431c94f317a92409c026 +#: ../../en/hats/servo.rst:2 30c89778a07b4e4f969b8e5bd46b9fd2 msgid "Servo Hat" msgstr "" -#: ../../en/hats/servo.rst:7 0ffae736448e4d51abc3b1e41b7e1178 +#: ../../en/hats/servo.rst:7 00e8544643e44d1e848a90e3c06af276 msgid "" "SERVO HAT as the name suggests, is a servo motor module with the new and " "upgraded \"ES9251II\" digital servo,This comes with 145°±10° range of " @@ -32,115 +32,145 @@ msgid "" " connected to G26 on M5StickC." msgstr "" -#: ../../en/hats/servo.rst:13 ae6fd79ed5cb413eb45f451d3a4f2677 +#: ../../en/hats/servo.rst:13 60bf20360bf146b69be7650e3e1a2175 msgid "Support the following products:" msgstr "" -#: ../../en/hats/servo.rst:15 22c1d601ce964284934fc1e57c3c8375 +#: ../../en/hats/servo.rst:15 fc8ed5bbf8824d6aaf151d197e041028 msgid "|ServoHat|" msgstr "" -#: ../../en/refs/hat.servo.ref 46f4f699877b4ff3aa600fa0b394ff99 +#: ../../en/refs/hat.servo.ref b539733cd47742dd875654df57199847 msgid "ServoHat" msgstr "" -#: ../../en/hats/servo.rst:18 a86c7f9e9db44d7d96cd34102083c9aa +#: ../../en/hats/servo.rst:18 bf956d4307a445bf8e029af4283dcaae msgid "Micropython Example::" msgstr "" -#: ../../en/hats/servo.rst:30 ede0eb17cc214f2c87a13f1ff0380be4 +#: ../../en/hats/servo.rst:30 7417a5b9b9944cbdbf7a669499042b41 msgid "UIFLOW2 Example:" msgstr "" -#: ../../en/hats/servo.rst:32 4309c684633d46c88f218585158ff5a2 -msgid "|example.svg|" +#: ../../en/hats/servo.rst:32 c5ee9744b87f4d0c9da52500d8a40063 +msgid "|example.png|" msgstr "" -#: ../../en/refs/hat.servo.ref:12 e010087227e245f1b548a87f40806b87 -msgid "example.svg" +#: ../../en/refs/hat.servo.ref:12 823fae6ddefb4d1285ec6e9eece3c5ae +msgid "example.png" msgstr "" -#: ../../en/hats/servo.rst:39 5c037531f8d14e268195ae0d98fe1309 +#: ../../en/hats/servo.rst:39 395d73e5d8a84bdfbd879d368fd04fc6 msgid "class ServoHat" msgstr "" -#: ../../en/hats/servo.rst:42 e89b2ae6df9f4c06a08ac6ca58148c7d +#: ../../en/hats/servo.rst:42 561e96edcd764db080b5b3bb4e3ef46d msgid "Constructors" msgstr "" -#: ../../en/hats/servo.rst:46 8e74dba99b7d4a079575f6d3999d75fd +#: ../../en/hats/servo.rst:46 675abcc2726140ab9e3d1b569d0c0622 msgid "Initialize the Servo." msgstr "" -#: ../../en/hats/servo.rst 370ffdb967614676af8b710431964b69 -#: 54d3dc6db656472ab43d504bde95d5aa bd864d0a0fbc40b393bbb0b695374286 +#: ../../en/hats/servo.rst 0d8ee2c2203e4fcc8e8e4e643ad4e617 +#: 7a74d59801124cfcb5eded1fb275c876 a61491f4ac3f4184bf497f0e90c0f462 msgid "Parameters" msgstr "" -#: ../../en/hats/servo.rst:48 eb85d76ca69048db90fcc60ad570aeef +#: ../../en/hats/servo.rst:48 94dad5a9b5764779962f373c86dc3fa1 msgid "The port to which the Servo is connected. port[0]: servo pin" msgstr "" #: ../../en/hats/servo.rst:50 ../../en/hats/servo.rst:64 #: ../../en/hats/servo.rst:75 ../../en/hats/servo.rst:84 -#: 7aa0c0c1644944e599abed02251dff18 7df0419e2a2d4c3e8f85f46a71cc4f36 -#: b94cafad77c143ac96fabe965a1b24c5 f2cf2bc7804b4f26acae710ec57adc9f +#: 2f209ef6da91485d824b3bbe757f6573 83f2372857c540af9313053780327831 +#: 89801be4d889403abdf714cf56ec31a6 dc34bb4948db4be3897af7ee463187e7 msgid "UIFLOW2:" msgstr "" -#: ../../en/hats/servo.rst:52 84f9df724c1a4c598ceb5486549cbf13 -msgid "|init.svg|" +#: ../../en/hats/servo.rst:52 f786fe26216948d8a74c20d9ec3a822e +msgid "|init.png|" msgstr "" -#: ../../en/refs/hat.servo.ref:7 a42497cf89364705bdd4801594399142 -msgid "init.svg" +#: ../../en/refs/hat.servo.ref:7 7f611eb6f10c44318d5e7c1853142234 +msgid "init.png" msgstr "" -#: ../../en/hats/servo.rst:56 a125542da4004796806c94406b3fc942 +#: ../../en/hats/servo.rst:56 d5cc33e621204c77bd62dd09b19b96b4 msgid "Methods" msgstr "" -#: ../../en/hats/servo.rst:60 338065e56e3440d1a66dfcde2d43410a +#: ../../en/hats/servo.rst:60 477991341132418fb4469a65a7dc7172 msgid "Set the duty cycle." msgstr "" -#: ../../en/hats/servo.rst:62 37d5e991142b49d08954ac8551804c39 +#: ../../en/hats/servo.rst:62 f85e5d6a89d848afbaadfb076dd64093 msgid "The duty cycle. from 26 to 127." msgstr "" -#: ../../en/hats/servo.rst:66 1e77d59793314c1f863f72a8d6f02613 -msgid "|set_duty.svg|" +#: ../../en/hats/servo.rst:66 92cdda7ad3234197a35f2b1c90d8bdaa +msgid "|set_duty.png|" msgstr "" -#: ../../en/refs/hat.servo.ref:8 0462e1cfc80b49aa987174dd8d7b8a93 -msgid "set_duty.svg" +#: ../../en/refs/hat.servo.ref:8 59cdee36a5e5460ea802651fec6fe33b +msgid "set_duty.png" msgstr "" -#: ../../en/hats/servo.rst:71 68981e2851c74c9ab4ba05fa2325695b +#: ../../en/hats/servo.rst:71 b8677d301fc54e3489f6fb732ca24b5f msgid "Set the clamping percentage." msgstr "" -#: ../../en/hats/servo.rst:73 ebd2d766d27644fa9a13a05b1efc44fb +#: ../../en/hats/servo.rst:73 1133e75c85c54b4f84cd9d35b4ab0494 msgid "The clamping percentage. from 0 to 100." msgstr "" -#: ../../en/hats/servo.rst:77 e4e93e12e56d47d0a28d16accbcceda2 -msgid "|set_percent.svg|" +#: ../../en/hats/servo.rst:77 4a9ef013db6041568fdcff00f8571a90 +msgid "|set_percent.png|" msgstr "" -#: ../../en/refs/hat.servo.ref:9 95ae522e475647a4aef5cb0e1a8626ca -msgid "set_percent.svg" +#: ../../en/refs/hat.servo.ref:9 2a7b458673334e0b95308c3970ac3822 +msgid "set_percent.png" msgstr "" -#: ../../en/hats/servo.rst:82 9a5b2509aeea4ff391b91e5e6d97daea +#: ../../en/hats/servo.rst:82 054ab6ff946742bd8f92a7af95df6797 msgid "Deinitialize the Servo." msgstr "" -#: ../../en/hats/servo.rst:86 a7a3bc4cbe194cbfbebbbcf36faa3aca -msgid "|deinit.svg|" +#: ../../en/hats/servo.rst:86 3c47303deff54112bc2121a2f047128d +msgid "|deinit.png|" msgstr "" -#: ../../en/refs/hat.servo.ref:10 e2252bc70b474cec81d537c8497bba50 -msgid "deinit.svg" +#: ../../en/refs/hat.servo.ref:10 58b9da3a9c1242a58cb53e913cc1b74e +msgid "deinit.png" msgstr "" +#~ msgid "|example.svg|" +#~ msgstr "" + +#~ msgid "example.svg" +#~ msgstr "" + +#~ msgid "|init.svg|" +#~ msgstr "" + +#~ msgid "init.svg" +#~ msgstr "" + +#~ msgid "|set_duty.svg|" +#~ msgstr "" + +#~ msgid "set_duty.svg" +#~ msgstr "" + +#~ msgid "|set_percent.svg|" +#~ msgstr "" + +#~ msgid "set_percent.svg" +#~ msgstr "" + +#~ msgid "|deinit.svg|" +#~ msgstr "" + +#~ msgid "deinit.svg" +#~ msgstr "" + diff --git a/docs/locales/zh_CN/LC_MESSAGES/hats/servo8.po b/docs/locales/zh_CN/LC_MESSAGES/hats/servo8.po index 6c7d9995..4e9cfa45 100644 --- a/docs/locales/zh_CN/LC_MESSAGES/hats/servo8.po +++ b/docs/locales/zh_CN/LC_MESSAGES/hats/servo8.po @@ -9,7 +9,7 @@ msgid "" msgstr "" "Project-Id-Version: UIFlow2 Programming Guide \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-01-16 15:35+0800\n" +"POT-Creation-Date: 2025-04-18 15:58+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language: zh_CN\n" @@ -20,11 +20,11 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.16.0\n" -#: ../../en/hats/servo8.rst:2 08a5a66bbd8a43928fada9c9c2e0a8c3 +#: ../../en/hats/servo8.rst:2 568fcc975dd34ca7bd6509991aa8d5c5 msgid "Servos8 Hat" msgstr "" -#: ../../en/hats/servo8.rst:6 babc39d6685e4784bf704c35ef16d438 +#: ../../en/hats/servo8.rst:6 e934473ff1e54c08b71c3755d5d1fc62 msgid "" "8Servos HAT v1.1 is an 8-channel servo driver module that works with the " "M5StickC/C Plus series. Adopt STM32F030F4 as main controller to drive " @@ -35,39 +35,39 @@ msgid "" "support Maximum 1.3A load. Applied for robotic and DIY projects." msgstr "" -#: ../../en/hats/servo8.rst:15 c1da8af318304ffabafc38ac376a4ae6 +#: ../../en/hats/servo8.rst:15 49240e3193c94043a6f075ae3b6993b5 msgid "Support the following products:" msgstr "" -#: ../../en/hats/servo8.rst:17 c652622f70f0417e937018ca1a04dbe9 +#: ../../en/hats/servo8.rst:17 4cd9f62500eb452eac8f2f5bcfb5eacd msgid "|Servo8|" msgstr "" -#: ../../en/refs/hat.servo8.ref 0539feb747fc41a18652047a733ded41 +#: ../../en/refs/hat.servo8.ref f48469e4c8604c89a8f6f7abac410cbd msgid "Servo8" msgstr "" -#: ../../en/hats/servo8.rst:20 925de9703f634f55aaa112406d79613b +#: ../../en/hats/servo8.rst:20 16617c17aac24fda9e11f95cca39e95a msgid "Micropython Example::" msgstr "" -#: ../../en/hats/servo8.rst:39 7f819485a7de424c8c0fc5db526fcd5d +#: ../../en/hats/servo8.rst:39 1e12ac42bb0142e39b62b51345b56ff7 msgid "class Servos8Hat" msgstr "" -#: ../../en/hats/servo8.rst:42 e5e5d1a11bd7406c90f31e467d5021a0 +#: ../../en/hats/servo8.rst:42 75806cf86531454b93f8ec5decc601eb msgid "Constructors" msgstr "" -#: ../../en/hats/servo8.rst:46 7cc62c5029ea460db0fef7b38f3a2b20 +#: ../../en/hats/servo8.rst:46 83e55f3097a2423eb93b5bc86b40e3df msgid "Initialize the Servos8Hat." msgstr "" -#: ../../en/hats/servo8.rst:48 2b6b84958b7847cf8e808ec0f3d24f37 +#: ../../en/hats/servo8.rst:48 560c31dde04645008f10fd5729706520 msgid "``i2c``: I2C port to use." msgstr "" -#: ../../en/hats/servo8.rst:49 9c941976673a4504873b125ff056c01a +#: ../../en/hats/servo8.rst:49 8924e44af79a4b5eb56a4f0fc3e6c19f msgid "``address``: I2C address of the servo8." msgstr "" @@ -75,138 +75,192 @@ msgstr "" #: ../../en/hats/servo8.rst:77 ../../en/hats/servo8.rst:89 #: ../../en/hats/servo8.rst:100 ../../en/hats/servo8.rst:111 #: ../../en/hats/servo8.rst:120 ../../en/hats/servo8.rst:129 -#: ../../en/hats/servo8.rst:138 497a85f4f0c34944bb68334dae60acc7 -#: 4e03bb7df39e437da2c03d2d6570ce43 72d6d06a92c0437495758b726fc048dc -#: 73ac07c7ccae48dcaf77b39615a42155 ccddccac71324457ba0595988275d08c -#: d0d3ae40d4cd479da5ec77b0e82c6bf6 d79db8909df2430185c620b4db3a2168 -#: efa542fd71e7432db0f6f5da9dc3f742 fb6c9737636b4ce39814cc46344bb3e3 +#: ../../en/hats/servo8.rst:138 00832246296041c7812a9e67ed750cf6 +#: 2cde7cc3df1c4bda82d246615f3845b5 5de22b3b8d9c43c4b708e13252a7b038 +#: 9bf76debdb5f4fb5b4b7b64f8410c457 9f4e9420dd65424ab3d57bab93b85f2a +#: a0a9689f6bc74af196872dd8d1b0dd7f b45b902cd7ef44d883ef4a241aacdb4a +#: f0b9a443271745c59b6fa904b77a6af3 f813233a621b4c39ac0f5734a5aff2e0 msgid "UIFLOW2:" msgstr "" -#: ../../en/hats/servo8.rst:53 27a345cc784844efb749e88bb4f68da6 -msgid "|init.svg|" +#: ../../en/hats/servo8.rst:53 3e8ecce769214f04b9a1af607ebead1b +msgid "|init.png|" msgstr "" -#: ../../en/refs/hat.servo8.ref:6 5e991c3a252441dc9d40d24e5f42aeb7 -msgid "init.svg" +#: ../../en/refs/hat.servo8.ref:6 fcf1766ce8e04ffab86d0cc8ecc7e660 +msgid "init.png" msgstr "" -#: ../../en/hats/servo8.rst:57 6d3c3e13e6f84b4eb3cd5104a6778871 +#: ../../en/hats/servo8.rst:57 a4c194e01f414611afe4177f91396937 msgid "Methods" msgstr "" -#: ../../en/hats/servo8.rst:61 d7ea961dc7774dffafb688e44aa1d656 +#: ../../en/hats/servo8.rst:61 6f176f22b8aa40328f09b9edcd7a88fa msgid "Set the angle of the servo." msgstr "" #: ../../en/hats/servo8.rst:63 ../../en/hats/servo8.rst:75 #: ../../en/hats/servo8.rst:86 ../../en/hats/servo8.rst:98 -#: 4df86a7064144a39bf64bc9cad0762b3 7add0f89b78c4f1998d660535bd03ad0 -#: 9f6f2567960b4e4da7df0b552fd203f5 d7e18a0bd988466ba97aefcc44fc571a +#: 43c1baaf34904e8b8c18bbce8af8871c 654af9a8283445e1bd37a14f340f035f +#: a13ecd85039b41c8aeb2781827cc3172 d955b915cd2741cc8793b705d3704cf8 msgid "``ch``: The channel (1 to 8) of the servo." msgstr "" -#: ../../en/hats/servo8.rst:64 b4ece07102604021a10d8847b5aeb802 +#: ../../en/hats/servo8.rst:64 036db378b06f4c0ea6b90264e4184eda msgid "``angle``: The angle (0 to 180) of the servo." msgstr "" -#: ../../en/hats/servo8.rst:68 32693d39cecf4485b9f23d2f88cdd52e -msgid "|write_servo_angle.svg|" +#: ../../en/hats/servo8.rst:68 f05066a9c8ce488d9d3eef1cc1c502cb +msgid "|write_servo_angle.png|" msgstr "" -#: ../../en/refs/hat.servo8.ref:7 74091d1cc1244d67a34f923a0bf3a1fa -msgid "write_servo_angle.svg" +#: ../../en/refs/hat.servo8.ref:7 c2cd062afc144f5caec0ad67a443d065 +msgid "write_servo_angle.png" msgstr "" -#: ../../en/hats/servo8.rst:73 19c03f435ac7479db76df4b98b9e619b +#: ../../en/hats/servo8.rst:73 705e34fcd74143c0a7037140d060a00b msgid "Read the angle of the servo." msgstr "" -#: ../../en/hats/servo8.rst:79 2ba83cbe8e394830a7d4dbaaf5895e6f -msgid "|read_servo_angle.svg|" +#: ../../en/hats/servo8.rst:79 eb702c8404934809ab0b9b009ff3dd47 +msgid "|read_servo_angle.png|" msgstr "" -#: ../../en/refs/hat.servo8.ref:8 9a0e6859c0dc413a90f5e12dd0715a8b -msgid "read_servo_angle.svg" +#: ../../en/refs/hat.servo8.ref:8 f97cac7c66c143bda00b262e59370550 +msgid "read_servo_angle.png" msgstr "" -#: ../../en/hats/servo8.rst:84 a7876ef5b7ab4a8f985b3734753c57fe +#: ../../en/hats/servo8.rst:84 f8074170613a4b6eb562061e1ff1b822 msgid "Set the pulse of the servo." msgstr "" -#: ../../en/hats/servo8.rst:87 c4f99be2da7b4afb8c8536c00aa72a32 +#: ../../en/hats/servo8.rst:87 5c8b5019be3d4cc8a9e39edc74872118 msgid "``pulse``: The pulse (500 to 2500) of the servo." msgstr "" -#: ../../en/hats/servo8.rst:91 96c7244f539043b1986224f1b2007999 -msgid "|write_servo_pulse.svg|" +#: ../../en/hats/servo8.rst:91 71046aafa92343da81870c295fa70770 +msgid "|write_servo_pulse.png|" msgstr "" -#: ../../en/refs/hat.servo8.ref:9 400ba95575204721ad904a2c27b7a687 -msgid "write_servo_pulse.svg" +#: ../../en/refs/hat.servo8.ref:9 d58026b261cb4ee7b2524431bc4d5ca8 +msgid "write_servo_pulse.png" msgstr "" -#: ../../en/hats/servo8.rst:96 0e352feb2fa64bf9b4ce2e3464a250c1 +#: ../../en/hats/servo8.rst:96 62d8e2a3eeba48bdad6c846adc709fbc msgid "Read the pulse of the servo." msgstr "" -#: ../../en/hats/servo8.rst:102 0f32294b8b8c4afc9f2120f4d4e0b27b -msgid "|read_servo_pulse.svg|" +#: ../../en/hats/servo8.rst:102 da5f2a5486564c54b8f9855edbb18c4d +msgid "|read_servo_pulse.png|" msgstr "" -#: ../../en/refs/hat.servo8.ref:10 0793bcba496a4971b08359348d39dac5 -msgid "read_servo_pulse.svg" +#: ../../en/refs/hat.servo8.ref:10 eea3cb66568945a28830ccd775dab5b5 +msgid "read_servo_pulse.png" msgstr "" -#: ../../en/hats/servo8.rst:107 91c0ee058d074107856c87e74c841c24 +#: ../../en/hats/servo8.rst:107 6771cdfa141546b5a08bbb2c1ab09a67 msgid "Control the power of the servo." msgstr "" -#: ../../en/hats/servo8.rst:109 d430c77f3d78439bbe01174273ad1e13 +#: ../../en/hats/servo8.rst:109 5a7f47894f7a4c9fa501dbdb2a567d8c msgid "``state``: The state of the power, 0 for OFF and 1 for ON." msgstr "" -#: ../../en/hats/servo8.rst:113 71f35bd398c64292842de5dd0b219edd -msgid "|power_ctrl.svg|" +#: ../../en/hats/servo8.rst:113 a31cee007d294bc8bf351cfb38604009 +msgid "|power_ctrl.png|" msgstr "" -#: ../../en/refs/hat.servo8.ref:11 8cb8736d8d3348598f3217bc5c86e3f0 -msgid "power_ctrl.svg" +#: ../../en/refs/hat.servo8.ref:11 bd2c0f8e497e4fc1ba2875da88db2ae2 +msgid "power_ctrl.png" msgstr "" -#: ../../en/hats/servo8.rst:118 5f2c223122ca4f85b6a978f6ca97a9d6 +#: ../../en/hats/servo8.rst:118 d608395e1f4e4bd09e2ca1e894609e70 msgid "Turn on the power of the servo." msgstr "" -#: ../../en/hats/servo8.rst:122 b3405a78baea4edab3c812d5649f9fdb -msgid "|power_on.svg|" +#: ../../en/hats/servo8.rst:122 fbdf591d53aa45eaaa2a5f3162779e6f +msgid "|power_on.png|" msgstr "" -#: ../../en/refs/hat.servo8.ref:12 cbcfb5dc6bef490aa6b76addb6a06e3c -msgid "power_on.svg" +#: ../../en/refs/hat.servo8.ref:12 31c7ff86f8904e71b418750950adf96b +msgid "power_on.png" msgstr "" -#: ../../en/hats/servo8.rst:127 e60aa472e3bc4a70b4c3533cd7b1d16d +#: ../../en/hats/servo8.rst:127 9519bdcd7c0a4fb4b7c179721b8265a6 msgid "Turn off the power of the servo." msgstr "" -#: ../../en/hats/servo8.rst:131 55d03f093f6b4c458a79ccc4cab7818a -msgid "|power_off.svg|" +#: ../../en/hats/servo8.rst:131 b384e3c42b944a62aac23501a3735cda +msgid "|power_off.png|" msgstr "" -#: ../../en/refs/hat.servo8.ref:13 9ede8a48b2fe4e4bac7723599ef351f8 -msgid "power_off.svg" +#: ../../en/refs/hat.servo8.ref:13 d9fde55f72a944a19febb28764b71d81 +msgid "power_off.png" msgstr "" -#: ../../en/hats/servo8.rst:136 11cbe653e5fa48e59bb66f4fd895d41c +#: ../../en/hats/servo8.rst:136 71760f345c23476ea8798f1627abbb97 msgid "Get the state of the power of the servo." msgstr "" -#: ../../en/hats/servo8.rst:140 dd5ea3c3ee0e4573a2279f8e7cb250ce -msgid "|get_power_state.svg|" +#: ../../en/hats/servo8.rst:140 7c53d7ae3140401293fc4efb44a08d34 +msgid "|get_power_state.png|" msgstr "" -#: ../../en/refs/hat.servo8.ref:14 702bfaf0e48d401c9b73569375e25961 -msgid "get_power_state.svg" +#: ../../en/refs/hat.servo8.ref:14 2ac637489c8d4c8ab4eb15b1611ad9a2 +msgid "get_power_state.png" msgstr "" +#~ msgid "|init.svg|" +#~ msgstr "" + +#~ msgid "init.svg" +#~ msgstr "" + +#~ msgid "|write_servo_angle.svg|" +#~ msgstr "" + +#~ msgid "write_servo_angle.svg" +#~ msgstr "" + +#~ msgid "|read_servo_angle.svg|" +#~ msgstr "" + +#~ msgid "read_servo_angle.svg" +#~ msgstr "" + +#~ msgid "|write_servo_pulse.svg|" +#~ msgstr "" + +#~ msgid "write_servo_pulse.svg" +#~ msgstr "" + +#~ msgid "|read_servo_pulse.svg|" +#~ msgstr "" + +#~ msgid "read_servo_pulse.svg" +#~ msgstr "" + +#~ msgid "|power_ctrl.svg|" +#~ msgstr "" + +#~ msgid "power_ctrl.svg" +#~ msgstr "" + +#~ msgid "|power_on.svg|" +#~ msgstr "" + +#~ msgid "power_on.svg" +#~ msgstr "" + +#~ msgid "|power_off.svg|" +#~ msgstr "" + +#~ msgid "power_off.svg" +#~ msgstr "" + +#~ msgid "|get_power_state.svg|" +#~ msgstr "" + +#~ msgid "get_power_state.svg" +#~ msgstr "" + diff --git a/docs/locales/zh_CN/LC_MESSAGES/hats/speaker.po b/docs/locales/zh_CN/LC_MESSAGES/hats/speaker.po index 6ea2dc82..7e6b020f 100644 --- a/docs/locales/zh_CN/LC_MESSAGES/hats/speaker.po +++ b/docs/locales/zh_CN/LC_MESSAGES/hats/speaker.po @@ -9,7 +9,7 @@ msgid "" msgstr "" "Project-Id-Version: UIFlow2 Programming Guide \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-03-28 10:32+0800\n" +"POT-Creation-Date: 2025-04-18 15:58+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language: zh_CN\n" @@ -20,74 +20,108 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.16.0\n" -#: ../../en/hats/speaker.rst:2 ../../en/refs/hat.speaker.ref -#: 653a1bec8f9e43319fdacc407c32605c 9e385a87d36645bfab19869ecff4161c +#: ../../en/hats/speaker.rst:2 ../../en/hats/speaker.rst:19 +#: ../../en/refs/hat.speaker.ref 0a14cb07f6fc46e29b85399aeebb89a8 +#: a83261413d6a4e818e0d2f68166d47aa ac3ba597ca444174ac900dd78346390e msgid "Speaker Hat" msgstr "" -#: ../../en/hats/speaker.rst:8 f28e0f98ee62463c933c8a22a990f10e +#: ../../en/hats/speaker.rst:8 5cc0f9a0d9384453bc584aebb92d7d1a msgid "The following products are supported:" msgstr "支持以下产品:" -#: ../../en/hats/speaker.rst:10 fa72906a5bdb4004b0eb255f39cbe05b +#: ../../en/hats/speaker.rst:10 2806ccd851ee48fe927097cf7ecf8e3b msgid "|Speaker Hat|" msgstr "" -#: ../../en/hats/speaker.rst:12 3d9573ea16fd4e30927df330c304ca07 +#: ../../en/hats/speaker.rst:12 56880f9f6717459fb9ac2b388bfa905f +msgid "Below is the detailed support for Speaker on the host:" +msgstr "" + +#: ../../en/hats/speaker.rst:19 940f3f4585d8499aad5106ce9a08b604 +#, fuzzy +msgid "Controller" +msgstr "构造函数" + +#: ../../en/hats/speaker.rst:21 d5f04fea72e2491fb1a43aa684f2c70d +msgid "CoreInk" +msgstr "" + +#: ../../en/hats/speaker.rst:21 ../../en/hats/speaker.rst:23 +#: ../../en/hats/speaker.rst:25 ../../en/hats/speaker.rst:27 +#: 04ec3f8ec3d74d1e9a6b49feb3818e07 29bd1cbf749c4134be0fb9c469308ce3 +#: 7655acb712d540b6a6c32dbc8d8eed4e a50dffc41d694694811793ca5e0b3e76 +msgid "|S|" +msgstr "" + +#: ../../en/hats/speaker.rst:23 ed1623ccb9ca499abac6cac1265ebef0 +msgid "StickC" +msgstr "" + +#: ../../en/hats/speaker.rst:25 db19356a2e6547f0914e166007e97bbc +msgid "StickC PLUS" +msgstr "" + +#: ../../en/hats/speaker.rst:27 9f3d3f47d70447f5a8f7a567fbc7577b +msgid "StickC PLUS2" +msgstr "" + +#: ../../en/hats/speaker.rst:33 e59b01519653400487705b5d0c5c72ea msgid "Micropython Example:" msgstr "Micropython 示例:" -#: ../../en/hats/speaker.rst:19 61bf4e6994ae43c99571f817d8e9d303 +#: ../../en/hats/speaker.rst:40 c69c1d2516c149fe82df0335c046d417 msgid "UIFLOW2 Example:" msgstr "UiFlow2 示例:" -#: ../../en/hats/speaker.rst:21 efb84fe8bbf84115a467be882609a77a +#: ../../en/hats/speaker.rst:42 1111d696bb354c20a4b14b1240eabbb0 msgid "|example.png|" msgstr "" -#: ../../en/refs/hat.speaker.ref:9 e88583419e814475ad6504846d31b546 +#: ../../en/refs/hat.speaker.ref:9 1c2dee4f8d014403a2a0003b2ce1bf3a msgid "example.png" msgstr "" -#: ../../en/hats/speaker.rst:26 e9b02cf6407e491987fe2472ac3dddf0 +#: ../../en/hats/speaker.rst:47 8b4a7c7ec9564a7e978985a73f1239c2 msgid "|stickc_plus2_speaker_example.m5f2|" msgstr "" -#: ../../en/hats/speaker.rst:28 8010119784c64af28987c6c808795471 +#: ../../en/hats/speaker.rst:49 da9ab3c97cbb4998acd32b5429eabada msgid "" ":download:`poweron_2_5s.wav " "<../../../examples/hardware/speaker/poweron_2_5s.wav>`" msgstr "" -#: ../../en/hats/speaker.rst:31 120c375a5b5b4114a7dee67ed66b3627 +#: ../../en/hats/speaker.rst:52 15e35e8b039b4fa9845ff6b6a2f0d67c msgid "class SpeakerHat" msgstr "" -#: ../../en/hats/speaker.rst:34 9bc980b978e64ef08fb1976b4e3785d9 +#: ../../en/hats/speaker.rst:55 9d07ddc8050b4ed69a5ae2f5b2ab07d2 msgid "Constructors" msgstr "构造函数" -#: ../../en/hats/speaker.rst:38 80e5e252fd3945448eb21b9c888a58d4 +#: ../../en/hats/speaker.rst:59 83f8f47e75c146a08a0cc173ec8161e6 msgid "Create an SpeakerHat object." msgstr "创建一个 SpeakerHat 对象。" -#: ../../en/hats/speaker.rst:40 e6a1c84d1a974bbc9d20b0a17d93a0e8 +#: ../../en/hats/speaker.rst:61 69715042e6a6436dab99db7a81f361e9 msgid "UIFLOW2:" msgstr "" -#: ../../en/hats/speaker.rst:42 274363c3c8f2453cabeae325687ed841 +#: ../../en/hats/speaker.rst:63 f55ef37251b346abb06ea5bfb07fe541 msgid "|init.png|" msgstr "" -#: ../../en/refs/hat.speaker.ref:7 36b2712286fe49e09cfb300e2cb8682f +#: ../../en/refs/hat.speaker.ref:7 2e14558648504cbf82920eaee8828d24 msgid "init.png" msgstr "" -#: ../../en/hats/speaker.rst:44 b801228738794be5bf660ee513f06999 +#: ../../en/hats/speaker.rst:65 26cebf9bf0604ef5a680f33766485c43 msgid "" "SpeakerHat class inherits M5.Speaker class, See " ":ref:`hardware.Speaker.Methods ` for more " "details." -msgstr "SpeakerHat 类继承 M5.Speaker 类,请参阅 " -":ref:`hardware.Speaker.Methods ` 了解更多" -"详细信息。" +msgstr "" +"SpeakerHat 类继承 M5.Speaker 类,请参阅 :ref:`hardware.Speaker.Methods " +"` 了解更多详细信息。" + diff --git a/docs/locales/zh_CN/LC_MESSAGES/hats/speaker2.po b/docs/locales/zh_CN/LC_MESSAGES/hats/speaker2.po index e8d465ce..2c5e81ac 100644 --- a/docs/locales/zh_CN/LC_MESSAGES/hats/speaker2.po +++ b/docs/locales/zh_CN/LC_MESSAGES/hats/speaker2.po @@ -9,7 +9,7 @@ msgid "" msgstr "" "Project-Id-Version: UIFlow2 Programming Guide \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-03-28 10:45+0800\n" +"POT-Creation-Date: 2025-04-18 15:58+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language: zh_CN\n" @@ -20,79 +20,91 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.16.0\n" -#: ../../en/hats/speaker2.rst:4 45b622ba3ee34ab285f09be2bd010114 +#: ../../en/hats/speaker2.rst:4 c717761d3bb44e0482b534f376ed4f5a msgid "Speaker2 Hat" msgstr "" -#: ../../en/hats/speaker2.rst:10 6abec3ed2e7143b5a8aec7dd35b0e191 +#: ../../en/hats/speaker2.rst:10 6a03e0e30f944ab5af09b309b0fa92d4 msgid "" "This is the driver library of Speaker2 Hat, which is provides a set of " "methods to control the speaker." msgstr "这是 Speaker2 Hat 的驱动库,提供了控制扬声器的功能。" -#: ../../en/hats/speaker2.rst:12 b05b8530731042bfa41df41fd6478d64 +#: ../../en/hats/speaker2.rst:12 06dced4614354cdc9a7b9019dda277f8 msgid "Support the following products:" msgstr "支持以下产品:" -#: ../../en/hats/speaker2.rst:14 6b83ba641fe8435b9781655d89dda0c0 +#: ../../en/hats/speaker2.rst:14 bee8639e0b4942e887c05823de966c06 msgid "|Speaker2|" msgstr "" -#: ../../en/hats/speaker2.rst:18 96f600cea6fb4d5287288e214fd92d7a +#: ../../en/hats/speaker2.rst:57 ../../en/refs/hat.speaker2.ref +#: 1b6edc02f85e458392cfbbeddbdff6ca 6b79eb6bce4a4857acda3d9dc684a8c1 +msgid "Speaker2" +msgstr "" + +#: ../../en/hats/speaker2.rst:18 7946350b53a34cb0892d82f4a99cf14a msgid "UiFlow2 Example" msgstr "UiFlow2 示例" #: ../../en/hats/speaker2.rst:20 ../../en/hats/speaker2.rst:38 -#: 1cd5924d4f424f78b819dd9feb880ed1 30dfca6bf7314b5e9184df71a8c39aba +#: 1edfbcadc68e425887c44b37c33d336e fa1c07eb883a40c186b6cd307f872b89 msgid "play audio" msgstr "播放音频" -#: ../../en/hats/speaker2.rst:22 8d4619e62df54eb0a14ecc61a6714a4f +#: ../../en/hats/speaker2.rst:22 e7242a2d7ede4a4e9119444f1ffe0c0d msgid "Open the |speaker2_stickcplus2_example.m5f2| project in UiFlow2." msgstr "在 UiFlow2 中打开 |speaker2_stickcplus2_example.m5f2| 项目。" #: ../../en/hats/speaker2.rst:24 ../../en/hats/speaker2.rst:40 -#: 52c1d4bfdacf438cbd0447787deaa902 5a601875a81649e78b1980163c360a51 +#: 72d04bfba755427ab46ddce27b537bf2 e7e668c5df85425397d4549acd4fafc7 msgid "This example demonstrates how to play audio." msgstr "这个示例演示了如何播放音频。" -#: ../../en/hats/speaker2.rst:26 cc2bf49c6d7247af8123a1928a8697b2 +#: ../../en/hats/speaker2.rst:26 d40725cb02f949bb838ddaa347ad2782 msgid "UiFlow2 Code Block:" msgstr "UiFlow2 代码块:" -#: ../../en/hats/speaker2.rst:28 7ae223dccfae46e18b062f59767929b1 +#: ../../en/hats/speaker2.rst:28 daffbc2b7a754e2690716c2dba49069d msgid "|example.png|" msgstr "" +#: ../../en/refs/hat.speaker2.ref:9 a14be26cf8ef4049802d352ea61af842 +#, fuzzy +msgid "example.png" +msgstr "示例输出:" + #: ../../en/hats/speaker2.rst:30 ../../en/hats/speaker2.rst:48 -#: 08ff9b60ad434e5f8686485ec956d547 57a31e0bd37f4a6292c59428ebd38f89 +#: 6a74a8f9b5d14b97a23e5984b90a1b43 79afc37339c2436f82967136f918611d msgid "Example output:" msgstr "示例输出:" #: ../../en/hats/speaker2.rst:32 ../../en/hats/speaker2.rst:50 -#: 0ef4f74f90e54aa09980dcb757822bd1 d3ef95454f0749569d137996d556d919 +#: 1eeed9e12562450b865ea762c6d3c832 7387d4e30d594fa08490e0c4af1fd0ab msgid "None" msgstr "" -#: ../../en/hats/speaker2.rst:35 7924eb04b76243a6a1b9d543a02f857e +#: ../../en/hats/speaker2.rst:35 796bb4d3dd0c42c1bf768d5bc6ef434c msgid "MicroPython Example" msgstr "MicroPython 示例" -#: ../../en/hats/speaker2.rst:42 75c333870b5f414984bb902a3c561310 +#: ../../en/hats/speaker2.rst:42 8ddc08c421ea43099b69f0f6e2f0b4bb msgid "MicroPython Code Block:" msgstr "MicroPython 代码块:" -#: ../../en/hats/speaker2.rst:54 8771d5c5fc6e4a218b931c3022ceb81d +#: ../../en/hats/speaker2.rst:54 4bd99667f444491d9560e7d54c873fde msgid "**API**" msgstr "" -#: ../../en/hats/speaker2.rst:57 5591946668d945e19a55281fd61bc1fc -msgid "Speaker2" +#: 1bb3e274083841818e82a39846dc0eae hat.speaker2.Speaker2Hat:1 of +msgid "Bases: :py:class:`object`" msgstr "" -#: ../../en/hats/speaker2.rst:62 de42c8fe346245c696806a6139542f66 +#: ../../en/hats/speaker2.rst:62 0e9b3c2292af4f659fdc0f38aaf09994 msgid "" "Speaker2 class inherits Speaker class, See :ref:`hardware.Speaker.Methods" " ` for more details." -msgstr "Speaker2 类继承 Speaker 类,请参阅 :ref:`hardware.Speaker.Methods" -" ` 了解更多详细信息。" +msgstr "" +"Speaker2 类继承 Speaker 类,请参阅 :ref:`hardware.Speaker.Methods " +"` 了解更多详细信息。" + diff --git a/docs/locales/zh_CN/LC_MESSAGES/module/ain4.po b/docs/locales/zh_CN/LC_MESSAGES/module/ain4.po index 86f44c90..6a93afa0 100644 --- a/docs/locales/zh_CN/LC_MESSAGES/module/ain4.po +++ b/docs/locales/zh_CN/LC_MESSAGES/module/ain4.po @@ -9,7 +9,7 @@ msgid "" msgstr "" "Project-Id-Version: UIFlow2 Programming Guide \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-01-16 15:35+0800\n" +"POT-Creation-Date: 2025-04-03 10:58+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language: zh_CN\n" @@ -20,197 +20,200 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.16.0\n" -#: ../../en/module/ain4.rst:3 147f6f506b994ea98f859b31aea3e047 +#: ../../en/module/ain4.rst:3 006356093f7548828ebed27e10f9eadb msgid "AIN4 Module" msgstr "" -#: ../../en/module/ain4.rst:7 17977f78ee524db188de0520d7f25050 +#: ../../en/module/ain4.rst:7 dbd916ee474c4cc983cb7ffd8ec9ec2a msgid "The following products are supported:" msgstr "" -#: ../../en/module/ain4.rst:9 f1caac50279a4a3482397f4ae15fee72 +#: ../../en/module/ain4.rst:9 a91a8a101eef464a94fd88adcb4873a8 msgid "|AIN4Module|" msgstr "" -#: ../../en/refs/module.ain4.ref c580eda19b6f4caea8fa7f41035f562e +#: ../../en/refs/module.ain4.ref b2981613dc234e808489d3d00889fd39 msgid "AIN4Module" msgstr "" -#: ../../en/module/ain4.rst:11 1e6619536f15482584e5224f11c6bfac +#: ../../en/module/ain4.rst:11 7d83b76748914102b7c14286828c52d0 msgid "Micropython Example::" msgstr "" -#: ../../en/module/ain4.rst:63 c59e764a09614ecab8985bddfe38ee68 +#: ../../en/module/ain4.rst:63 1afe7e11045e499c8717b8e5c8ea12a7 msgid "UIFLOW2 Example:" msgstr "" -#: ../../en/module/ain4.rst:65 e56d0b408888405ba52439bf7b908521 +#: ../../en/module/ain4.rst:65 2d8b4a4d961f41328ca1a4299728ec6f msgid "|example.png|" msgstr "" -#: ../../en/refs/module.ain4.ref:14 02b6fc3c863a4eed9748493a762dbd89 +#: ../../en/refs/module.ain4.ref:14 c6602bcd3300479890f797057f3f079b msgid "example.png" msgstr "" -#: ../../en/module/ain4.rst:69 b6f665ed9ee049c6804cc227a90e13da +#: ../../en/module/ain4.rst:69 9cc91d30b92f43c2888967e3c7554b09 msgid "|ain4_core2_example.m5f2|" msgstr "" -#: ../../en/module/ain4.rst:72 e2e74ca1aa8344f59444c4d362de5508 +#: ../../en/module/ain4.rst:72 52a8857ca66b41cfb042883689a79bf8 msgid "class AIN4Module" msgstr "" -#: ../../en/module/ain4.rst:75 e312892350f8406ba97fb96fd00a76fc +#: ../../en/module/ain4.rst:75 9d4fbcd9027a4ee79ed37d7c50d27b9a msgid "Constructors" msgstr "" -#: ../../en/module/ain4.rst:79 2db777e40f3d4a74b0f88a61fdd42b63 +#: ../../en/module/ain4.rst:79 59e874b144c94ec7bc212aca9b7a6f66 msgid "Init I2C Module AIN 4-20mA I2C Address." msgstr "" -#: ../../en/module/ain4.rst 4b839af2fad44aaeb93a177c4caad4f8 -#: ad5afe8d7e6742c69a8c90e4f545f13d cff27295cdf34fd2953280b00efda1cf -#: f66372d14ee94df0b6849b62dc7aab60 f9c4493f72ea484ab47322a186f4e317 +#: ../../en/module/ain4.rst 38f4135e081a4ba3bb2ea8228646f467 +#: 434b9c5f377740c59bb54bfd4b9f029f 7df6c20d74bd47fd9f9f70ae17603cff +#: e6a118d531a942d2b0cdb56553cfce39 ea2b8818634a4853b9648f19121c9f15 msgid "Parameters" msgstr "" -#: ../../en/module/ain4.rst:81 f52d6101ed6c4944bbad23c61eb0e84b +#: ../../en/module/ain4.rst:81 990ccd67e2b84685998f403e164cceb8 msgid "I2C address of the AIN4Module." msgstr "" #: ../../en/module/ain4.rst:83 ../../en/module/ain4.rst:99 #: ../../en/module/ain4.rst:112 ../../en/module/ain4.rst:123 #: ../../en/module/ain4.rst:133 ../../en/module/ain4.rst:143 -#: ../../en/module/ain4.rst:153 12e4b7636a0d45ea93976cf2211985a0 -#: 1c109c6812ba4aeba7e44e0586e2ef04 254481f5ec4d4ac8a39053efc07fec3a -#: 3bf97b24f839452585861a573e4f7b6e 97ea3eb5103c4f109cff721c3ef5754a -#: de7ebc01026e4310bd05837024084117 f11c12ce54da49acb750c1e08d456af7 +#: ../../en/module/ain4.rst:153 07366973b38048979dcb3065f315c6ed +#: 16c1008b04e54cfd9c4d350215e4101f 24755769d5584344a08b8bd3843f38bb +#: 56414ba3f70a41538e97de17247d8091 b269240b7d084d87b64fa8cfa78f6a1d +#: bae0a0e63f264c53b058188c06e64b95 d119ea768cbc4b2fb25c87c3bab02902 msgid "UIFLOW2:" msgstr "" -#: ../../en/module/ain4.rst:85 de1e78ea76eb4bb6a4c1f569a4ef4077 +#: ../../en/module/ain4.rst:85 b61179eaf16243879814adcacd597b29 msgid "|init.png|" msgstr "" -#: ../../en/refs/module.ain4.ref:6 e78b5c12faf64679be03aea4f67b153e +#: ../../en/refs/module.ain4.ref:6 cd94745030694eff9e6f4a45b63db12b msgid "init.png" msgstr "" -#: ../../en/module/ain4.rst:89 b6ccfe31a3fd4a3d8c55c1b95d3e3a64 +#: ../../en/module/ain4.rst:89 841739c86b064d739b7886128da0dd6b msgid "Methods" msgstr "" -#: ../../en/module/ain4.rst:93 6e61243b677e4750b40f1f61f4537cfa +#: ../../en/module/ain4.rst:93 42d4023f0a4e4aa096172f7e4f48504d msgid "Retrieves the raw ADC value from the specified channel." msgstr "" -#: ../../en/module/ain4.rst:95 3e577268b02b451fb48ece6b7eb00c92 +#: ../../en/module/ain4.rst:95 2d44d51a4e4a40e09a60b2ef817ab2bd msgid "The channel number (1 to 4) to read the ADC value from." msgstr "" -#: ../../en/module/ain4.rst 145d6da29e7f4a9f8a488ef923dfac2a -#: b505d4cefe374a0490d4cd72e0489d9e c90350e6889948e78a262d78b2491694 -#: d5bf63d3dcbc4dfc8af4c1a327051085 +#: ../../en/module/ain4.rst 0e3e3a85bea240a88961448da42a8130 +#: 2c3a912e0468471cbf15f94082855fd9 49208f5cc8934b2dbee1ddb663213950 +#: 4bf9644ee42e438c853f1317b91f70f6 msgid "Returns" msgstr "" -#: ../../en/module/ain4.rst:97 cd7a0c4867674c6196f32d7c2579f06c +#: ../../en/module/ain4.rst:97 947fe66171cf409eabf10882c678f85e msgid "Raw ADC value as a 12-bit integer." msgstr "" -#: ../../en/module/ain4.rst:101 3e895e5fce6241c181978ec1e3359162 +#: ../../en/module/ain4.rst:101 347e39bc778e4819b37dbe734389ed2e msgid "|get_adc_raw_value.png|" msgstr "" -#: ../../en/refs/module.ain4.ref:7 ce28716ad2c0493ab2ab7c1717eb2755 +#: ../../en/refs/module.ain4.ref:7 5228b63449214cdda055a7bf232ea035 msgid "get_adc_raw_value.png" msgstr "" -#: ../../en/module/ain4.rst:105 50845bc359cf4b2aa05cef21be859044 +#: ../../en/module/ain4.rst:105 a117cbb5fdea4c1aa87696f36489c55b msgid "Retrieves the current value (in mA) from the specified channel." msgstr "" -#: ../../en/module/ain4.rst:107 c7bb9a060c994d28ac2c5c46f2fe4ff9 +#: ../../en/module/ain4.rst:107 6fbea012dce2480a9087970d0a4307a2 msgid "The channel number (1 to 4) to read the current value from." msgstr "" -#: ../../en/module/ain4.rst:109 43696784cb4c4ef7ad456c4322d4969a +#: ../../en/module/ain4.rst:109 6d471872d688445587c214994fcbd838 msgid "Current value in milliamperes (mA)." msgstr "" -#: ../../en/module/ain4.rst:114 f3bb3366b7d54b7cbb8832ef434f8111 +#: ../../en/module/ain4.rst:114 dfc18d8a4a3b4904b94b01f478d7c77d msgid "|get_current_value.png|" msgstr "" -#: ../../en/refs/module.ain4.ref:8 64bc104d662f427389dc17e51bc10dd8 +#: ../../en/refs/module.ain4.ref:8 5e6dbef9bcda4595932d1a730af21a2e msgid "get_current_value.png" msgstr "" -#: ../../en/module/ain4.rst:118 af57825cbe1d46bfadd56d0e2f0fe3cf +#: ../../en/module/ain4.rst:118 c02a5cc3cb3a4a05a67e1d5acbbc25d2 msgid "Sets the calibration current for the specified channel." msgstr "" -#: ../../en/module/ain4.rst:120 73dd442e068c43fcadb7d7d71624344b +#: ../../en/module/ain4.rst:120 45f3acaf56d2435ab3fd124e44cf9914 msgid "The channel number (1 to 4) to set the calibration for." msgstr "" -#: ../../en/module/ain4.rst:121 a0298e9ab11643318427b65c93025117 +#: ../../en/module/ain4.rst:121 63c95286d62e4aad8f4b671edfbc2506 msgid "The calibration current value, ranging from 4 to 20 mA." msgstr "" -#: ../../en/module/ain4.rst:125 fcd2d97e7cb74b14b599e8478c593086 +#: ../../en/module/ain4.rst:125 582ebfba340b43d98980b5a527b470bd msgid "|set_cal_current.png|" msgstr "" -#: ../../en/refs/module.ain4.ref:9 b72333c5a6fc4af094b4adf58e9c94ec +#: ../../en/refs/module.ain4.ref:9 3d6a7f2360da40239d5e586d867df906 msgid "set_cal_current.png" msgstr "" -#: ../../en/module/ain4.rst:129 701b1ea1556d4cd8a46181659951627b +#: ../../en/module/ain4.rst:129 35ccf07aef5b4b32bf7213f687dcb6d5 msgid "Retrieves the firmware version of the AIN 4-20mA module." msgstr "" -#: ../../en/module/ain4.rst:131 7f5533b4c3c847ffbfb2f1b371d898cb +#: ../../en/module/ain4.rst:131 e9fc9c6f88fa41129f0e689d289cf646 msgid "Firmware version." msgstr "" -#: ../../en/module/ain4.rst:135 34d91d55ea864c5cb906576787d6d6be +#: ../../en/module/ain4.rst:135 8c2126e46b294985934ca0146b07ceea msgid "|get_firmware_version.png|" msgstr "" -#: ../../en/refs/module.ain4.ref:10 c7619ae7d7814ed89b699d4d046cbb91 +#: ../../en/refs/module.ain4.ref:10 1eaff0c5d00b46c7844b2c111b6ad517 msgid "get_firmware_version.png" msgstr "" -#: ../../en/module/ain4.rst:139 69e242f36670417c94eafd7ef05b4fab +#: ../../en/module/ain4.rst:139 904be0ae6d044fb9a852a3abaeb07aca msgid "Retrieves the current I2C address of the AIN 4-20mA module." msgstr "" -#: ../../en/module/ain4.rst:141 78bda88c54c44bb985adf02f040c575a +#: ../../en/module/ain4.rst:141 a714969fa0754b23a22f0b5fdb1943aa msgid "I2C address as a string in hexadecimal format." msgstr "" -#: ../../en/module/ain4.rst:145 60e312dfe3f34ec29f4c4b3b1b0c09d3 +#: ../../en/module/ain4.rst:145 a7779d927ac04b2996ec84549ae91378 msgid "|get_i2c_address.png|" msgstr "" -#: ../../en/refs/module.ain4.ref:11 5d382057fd894cbca8a6555ce9c71552 +#: ../../en/refs/module.ain4.ref:11 4f45d8e8e36b489ebfc64b91c96177a4 msgid "get_i2c_address.png" msgstr "" -#: ../../en/module/ain4.rst:149 64b81542930344f89c62462dd1fb7389 +#: ../../en/module/ain4.rst:149 f881c1d94ef9440aaec4035387c747a1 msgid "Sets a new I2C address for the AIN 4-20mA module." msgstr "" -#: ../../en/module/ain4.rst:151 232bb0c9f7d84aea8f5806d98bbc2686 -msgid "The new I2C address, must be between 0x08 and 0x78." +#: ../../en/module/ain4.rst:151 57ab2e0c61554b1fa6412b9353839649 +msgid "The new I2C address, must be between 0x08 and 0x77." msgstr "" -#: ../../en/module/ain4.rst:155 108acb36cd7e433892aeec1d3423b64b +#: ../../en/module/ain4.rst:155 d8772b4d01fc4490824a379325824910 msgid "|set_i2c_address.png|" msgstr "" -#: ../../en/refs/module.ain4.ref:12 c968a26f5fdf493ca0f7137cba5883e4 +#: ../../en/refs/module.ain4.ref:12 03967a4356e54ce9a00c52e291bb0623 msgid "set_i2c_address.png" msgstr "" +#~ msgid "The new I2C address, must be between 0x08 and 0x78." +#~ msgstr "" + diff --git a/docs/locales/zh_CN/LC_MESSAGES/module/bala2.po b/docs/locales/zh_CN/LC_MESSAGES/module/bala2.po index fb449774..1e7781eb 100644 --- a/docs/locales/zh_CN/LC_MESSAGES/module/bala2.po +++ b/docs/locales/zh_CN/LC_MESSAGES/module/bala2.po @@ -9,7 +9,7 @@ msgid "" msgstr "" "Project-Id-Version: UIFlow2 Programming Guide \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-02-21 15:07+0800\n" +"POT-Creation-Date: 2025-04-03 10:58+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language: zh_CN\n" @@ -20,11 +20,11 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.16.0\n" -#: ../../en/module/bala2.rst:2 8b579796274240f482a1ea4b20da67ac +#: ../../en/module/bala2.rst:2 1f100ba2d6a845f08751d3b0acafb3d7 msgid "Bala2 Module" msgstr "" -#: ../../en/module/bala2.rst:8 3dd5a7c7010645108a4e3602384b17ac +#: ../../en/module/bala2.rst:8 70bc6d6d350549d2842601a75f0df888 msgid "" "The Bala2 Module is part of the M5Stack stackable module series. The " "module communicates with the host via the I2C interface, and its built-in" @@ -34,90 +34,110 @@ msgstr "" "Bala2 模块是 M5Stack 堆叠模块系列的一部分。该模块通过 I2C 接口与主机通信,内置的微控制器负责电机的 PWM " "控制、读取编码器计数,和输出舵机控制信号。" -#: ../../en/module/bala2.rst:10 17d693df5dc348a6928e6d9c82a9eb22 +#: ../../en/module/bala2.rst:10 66e63302d6ff4e2a934a712916e171d7 msgid "Support the following products:" msgstr "支持以下产品:" -#: ../../en/module/bala2.rst:12 c0e82552374743a380a1f3e3cb50ffd3 -msgid "|Bala2| |Bala2-Fire|" +#: ../../en/module/bala2.rst:12 f9f280349646466bac0964b53303aa6b +msgid "|Bala2|" msgstr "" -#: ../../en/refs/module.bala2.ref 51606c4d0a944e98a73a1d515236ddf0 +#: ../../en/refs/module.bala2.ref 248a8c1303214b0886915be678bed03c msgid "Bala2" msgstr "" -#: ../../en/refs/module.bala2.ref 51606c4d0a944e98a73a1d515236ddf0 +#: ../../en/module/bala2.rst:14 4e2b3007cb3a4038bc282102859db468 +msgid "|Bala2-Fire|" +msgstr "" + +#: ../../en/refs/module.bala2.ref a0939d190b764e59ad000ffb6dbc45bb msgid "Bala2-Fire" msgstr "" -#: ../../en/module/bala2.rst:17 a66e860276084bc5a41cc67a42caf4f1 +#: ../../en/module/bala2.rst:18 5a43c93011b64b9eb65f3954d07987ff msgid "UiFlow2 Example" msgstr "UiFlow2 应用示例:" -#: ../../en/module/bala2.rst:20 ../../en/module/bala2.rst:83 -#: 2103cf90421542ef96c0a5d1a958c4cb +#: ../../en/module/bala2.rst:21 ../../en/module/bala2.rst:84 +#: 44fba959d55043b69a6bc6d5c5430be3 9ec90b252d894f3c88ee0be681495d93 msgid "Servo control" msgstr "舵机控制" -#: ../../en/module/bala2.rst:22 c19ceb0f930c4f45964a01e7929a4ec7 +#: ../../en/module/bala2.rst:23 0ac4151a56804c2eb334ba7bc684faae msgid "Open the |servo_control_example.m5f2| project in UiFlow2." msgstr "在 UiFlow2 上打开 |servo_control_example.m5f2| 项目。" -#: ../../en/module/bala2.rst:24 ../../en/module/bala2.rst:85 -#: 67d82e61feb64d7db3eb69ca3519aecd +#: ../../en/module/bala2.rst:25 ../../en/module/bala2.rst:86 +#: 1ede219a6d0f42ac95e018e507539a67 b3573751659d42c08d6d3e22e53aa765 msgid "Control the servo to swing back and forth between 0° and 180°." msgstr "控制舵机 0°~180° 来回转动。" -#: ../../en/module/bala2.rst:26 ../../en/module/bala2.rst:41 -#: ../../en/module/bala2.rst:56 ../../en/module/bala2.rst:71 -#: ../../en/module/bala2.rst:154 ../../en/module/bala2.rst:170 -#: ../../en/module/bala2.rst:187 ../../en/module/bala2.rst:204 -#: ../../en/module/bala2.rst:221 ../../en/module/bala2.rst:238 -#: ../../en/module/bala2.rst:252 ../../en/module/bala2.rst:266 -#: ../../en/module/bala2.rst:285 ../../en/module/bala2.rst:303 -#: ../../en/module/bala2.rst:320 ../../en/module/bala2.rst:336 -#: ../../en/module/bala2.rst:353 ../../en/module/bala2.rst:371 -#: ../../en/module/bala2.rst:388 ../../en/module/bala2.rst:404 -#: ../../en/module/bala2.rst:421 ../../en/module/bala2.rst:437 -#: bc5104713d3c47049acf52a68d6aba6f +#: ../../en/module/bala2.rst:27 ../../en/module/bala2.rst:42 +#: ../../en/module/bala2.rst:57 ../../en/module/bala2.rst:72 +#: ../../en/module/bala2.rst:155 ../../en/module/bala2.rst:171 +#: ../../en/module/bala2.rst:188 ../../en/module/bala2.rst:205 +#: ../../en/module/bala2.rst:222 ../../en/module/bala2.rst:239 +#: ../../en/module/bala2.rst:253 ../../en/module/bala2.rst:267 +#: ../../en/module/bala2.rst:286 ../../en/module/bala2.rst:304 +#: ../../en/module/bala2.rst:321 ../../en/module/bala2.rst:337 +#: ../../en/module/bala2.rst:354 ../../en/module/bala2.rst:372 +#: ../../en/module/bala2.rst:389 ../../en/module/bala2.rst:405 +#: ../../en/module/bala2.rst:422 ../../en/module/bala2.rst:438 +#: 06480614a5224b27b7c4862bfe7632e8 0a022f3ff20d4d448fed9f73d54f3bdd +#: 12b8f5308ab5433c9fde825d1866b41a 13644faa67474a268c57408e864b90d4 +#: 28deb3d7c9e44e8784b7fd5c9bf51175 2d3d4acab0d648a78f7c40722db20562 +#: 4cebf0eb373a403cace5f5375576496b 592cb53951944adeb7e69e6df39868e0 +#: 8bcc11d33656405daa7df3f60dc5cda6 8f53cef9efd64987959cf3cac6478eea +#: 904a929be26141c88e8cecbd7c19a2b4 9c99d63f20ca4c6eb0d8d8a5796b1fdf +#: 9e228b62bc474b51be8572427321bcb2 9e564db243e0485883380e6b231e6a98 +#: a584fd32de274dc3b53753c1c6828bf4 b44bc0f62b5047adb79e7774c2b8539d +#: bcb40b7e766e4c97be18a624bcb4c8d5 c23c383fb09d4be4bc5498b21e5140d9 +#: cdf4921b0c54478a8ccfa26f40a10807 dfbe64529ad14af0b16326c92688b3bf +#: e4998e76db5e446bbf693c623ee7ecbf f3ca08ccca9844c7bcd4087fd46f3836 msgid "UiFlow2 Code Block:" msgstr "UiFlow2 代码块:" -#: ../../en/module/bala2.rst:28 7adbeebff54e4bd1b2ecca78161508b1 +#: ../../en/module/bala2.rst:29 4ea6e48f0b034670a862d775e9c86636 msgid "|servo_control_example.png|" msgstr "" -#: ../../en/refs/module.bala2.ref:32 5b48957f7bc24e019321100fa0e505da +#: ../../en/refs/module.bala2.ref:32 88a0d58711334032830d2f7a714e9389 msgid "servo_control_example.png" msgstr "" -#: ../../en/module/bala2.rst:30 ../../en/module/bala2.rst:45 -#: ../../en/module/bala2.rst:60 ../../en/module/bala2.rst:75 -#: ../../en/module/bala2.rst:93 ../../en/module/bala2.rst:108 -#: ../../en/module/bala2.rst:123 ../../en/module/bala2.rst:138 -#: eb787eb3c1d645e3a29d3bde1c5c7623 +#: ../../en/module/bala2.rst:31 ../../en/module/bala2.rst:46 +#: ../../en/module/bala2.rst:61 ../../en/module/bala2.rst:76 +#: ../../en/module/bala2.rst:94 ../../en/module/bala2.rst:109 +#: ../../en/module/bala2.rst:124 ../../en/module/bala2.rst:139 +#: 00c0440f1ad64c0aa6f253dd4bcadbaf 8608c692a78c495a883cc7a8fa37bed7 +#: 863cf951677f4c51ae204bfc3fc55353 c04669283e9d4ef9af17d711afe906ce +#: c72e05732f374b4b8f185ff5dea298d7 d27f28c046324a6fa901b6f5d5658d92 +#: e3f6279d63fa44d39a035b450722c5c6 f9b0b11074cf4c36a4034fcc5326ceb4 msgid "Example output:" msgstr "示例输出:" -#: ../../en/module/bala2.rst:32 ../../en/module/bala2.rst:47 -#: ../../en/module/bala2.rst:62 ../../en/module/bala2.rst:77 -#: ../../en/module/bala2.rst:95 ../../en/module/bala2.rst:110 -#: ../../en/module/bala2.rst:125 ../../en/module/bala2.rst:140 -#: 94f91e4e097747d7ad6018e15675ad42 +#: ../../en/module/bala2.rst:33 ../../en/module/bala2.rst:48 +#: ../../en/module/bala2.rst:63 ../../en/module/bala2.rst:78 +#: ../../en/module/bala2.rst:96 ../../en/module/bala2.rst:111 +#: ../../en/module/bala2.rst:126 ../../en/module/bala2.rst:141 +#: 2d073e0b70a2480fb5668a46d5dbc638 3c45d9a2f8b742d0a8c1d323208f4a32 +#: 6535ae0cf5cf4aa98b21df5d41d9cd32 7e7b7a4e938546c094b6dae1298606c4 +#: 9adf1b3b60ef4266b0fe8ba6258d5dc0 aed1f096c7e34b28969144dd24161ae4 +#: b5288afeb0b6455b96a6e4163b3e98a8 faf09ec0554b439da97ec3ca2b2479dd msgid "None" msgstr "无" -#: ../../en/module/bala2.rst:35 ../../en/module/bala2.rst:98 -#: 724ef0befedd4edfbb93c13cdcdedeeb +#: ../../en/module/bala2.rst:36 ../../en/module/bala2.rst:99 +#: 1c439cf50a1b403b9d8c741899c2b6b9 a671da7986ad40d6b3e180e6105b1538 msgid "Motor control" msgstr "电机控制" -#: ../../en/module/bala2.rst:37 6ff5f833ac1e435da6a38977c211e4de +#: ../../en/module/bala2.rst:38 6b3382d44be840edb2ace80501f2a855 msgid "Open the |motor_control_example.m5f2| project in UiFlow2." msgstr "在 UiFlow2 上打开 |motor_control_example.m5f2| 项目。" -#: ../../en/module/bala2.rst:39 ../../en/module/bala2.rst:100 -#: b304814eb07e482fa285318812b24fa0 +#: ../../en/module/bala2.rst:40 ../../en/module/bala2.rst:101 +#: 68b8c371209046538fb7f6e4c8b44a2e a4d39ad57ac744c2a5d174901d84ca68 msgid "" "Run the program, and the car's motors will first rotate forward, " "gradually accelerating to the maximum speed, then gradually decelerating " @@ -126,49 +146,49 @@ msgid "" "will come to a complete stop, with the motor speed returning to zero." msgstr "运行程序,小车电机先正转,逐步加速至最大速度,然后逐步减速至停止。随后,电机反转,同样逐步加速到最大速度,再逐步减速至停止。最后,小车停止运动,电机速度归零。" -#: ../../en/module/bala2.rst:43 bd6989cbc48b41b7a978652aab92cfb1 +#: ../../en/module/bala2.rst:44 231be6bb360e40abbb8cfa230d9512b7 msgid "|motor_control_example.png|" msgstr "" -#: ../../en/refs/module.bala2.ref:30 2dec4754f837493c98d7e2085eb23bb9 +#: ../../en/refs/module.bala2.ref:30 a8803780737a473b951c8d961dca36a3 msgid "motor_control_example.png" msgstr "" -#: ../../en/module/bala2.rst:50 ../../en/module/bala2.rst:113 -#: f582d402bd65440b8973556dffd326fa +#: ../../en/module/bala2.rst:51 ../../en/module/bala2.rst:114 +#: 6880602142ed42149de8b1f57ca87a9e a2e965b2f85a44c88e220784b50939b4 msgid "Read encoder" msgstr "读取编码器" -#: ../../en/module/bala2.rst:52 fbb11056e88a42b3a1f5b119e4d3e865 +#: ../../en/module/bala2.rst:53 97a10324e7834dec9b42fc377d42e71e msgid "Open the |read_encoder_example.m5f2| project in UiFlow2." msgstr "在 UiFlow2 上打开 |read_encoder_example.m5f2| 项目。" -#: ../../en/module/bala2.rst:54 ../../en/module/bala2.rst:115 -#: a57fd91998e94250a3903c6707addffc +#: ../../en/module/bala2.rst:55 ../../en/module/bala2.rst:116 +#: be544f914ced418fb494cbf58b6a3b6e f46aa966f9bf4914943ab4a1174e714f msgid "" "Run the program and manually rotate the wheels to observe the screen " "display." msgstr "运行程序,用手转动车轮观察屏幕显示。" -#: ../../en/module/bala2.rst:58 fca97708dfc34fb2a95646514dbb3fa4 +#: ../../en/module/bala2.rst:59 0b590d467d5049ae80f39105909193b1 msgid "|read_encoder_example.png|" msgstr "" -#: ../../en/refs/module.bala2.ref:31 6960a45d75b847b5b3c2ed8d6f609490 +#: ../../en/refs/module.bala2.ref:31 e92c36f81414487c9ad3d36acadc40af msgid "read_encoder_example.png" msgstr "" -#: ../../en/module/bala2.rst:65 ../../en/module/bala2.rst:128 -#: b4c294c011064dd2bb3978a11791ac1c +#: ../../en/module/bala2.rst:66 ../../en/module/bala2.rst:129 +#: b7e303db89484501995b350e4ddb2f1d dd1f8dceb4854090afb82adc06338cd5 msgid "Car control" msgstr "小车控制" -#: ../../en/module/bala2.rst:67 93fc211986e94974a116e5baa9a6ac05 +#: ../../en/module/bala2.rst:68 e8872b9077f94e0db75609b7a89a3080 msgid "Open the |car_control_example.m5f2| project in UiFlow2." msgstr "在 UiFlow2 上打开 |car_control_example.m5f2| 项目。" -#: ../../en/module/bala2.rst:69 ../../en/module/bala2.rst:130 -#: 50dc1938a5cb453c97c9b26e41c306a2 +#: ../../en/module/bala2.rst:70 ../../en/module/bala2.rst:131 +#: 03255c91ae7f47ab84c8e33fae2ec5a0 8b06e340103d424abc5dec9196acc33a msgid "" "Save the program to the controller, place the car on its side, and turn " "it on. After the gyroscope calibration is complete, the car will " @@ -178,351 +198,374 @@ msgid "" "state." msgstr "保存程序到控制器中,侧放小车并开机,等待陀螺仪校准完成后,小车会自动直立保持平衡。随后,它会依次执行左转、右转、前进、后退等动作,最后停止,回到平衡状态。" -#: ../../en/module/bala2.rst:73 d12f8cd9694a4ebabac050676223e097 +#: ../../en/module/bala2.rst:74 ca77d2b41930497095175e4cdb619c64 msgid "|car_control_example.png|" msgstr "" -#: ../../en/refs/module.bala2.ref:33 311a2013bcab4f2aaaf13a3da52d6ee5 +#: ../../en/refs/module.bala2.ref:33 c810df9820a045938e00c01a5fc9719d msgid "car_control_example.png" msgstr "" -#: ../../en/module/bala2.rst:80 61952d2e51084986859879473c97b376 +#: ../../en/module/bala2.rst:81 793f678026fc46fcbb9a308037ffe018 msgid "MicroPython Example" msgstr "MicroPython 应用示例:" -#: ../../en/module/bala2.rst:87 ../../en/module/bala2.rst:102 -#: ../../en/module/bala2.rst:117 ../../en/module/bala2.rst:132 -#: ../../en/module/bala2.rst:158 ../../en/module/bala2.rst:174 -#: ../../en/module/bala2.rst:191 ../../en/module/bala2.rst:208 -#: ../../en/module/bala2.rst:225 ../../en/module/bala2.rst:242 -#: ../../en/module/bala2.rst:256 ../../en/module/bala2.rst:270 -#: ../../en/module/bala2.rst:289 ../../en/module/bala2.rst:307 -#: ../../en/module/bala2.rst:324 ../../en/module/bala2.rst:340 -#: ../../en/module/bala2.rst:357 ../../en/module/bala2.rst:375 -#: ../../en/module/bala2.rst:392 ../../en/module/bala2.rst:408 -#: ../../en/module/bala2.rst:425 ../../en/module/bala2.rst:441 -#: 2058336b3bd147c6b522a036c6c56d7b +#: ../../en/module/bala2.rst:88 ../../en/module/bala2.rst:103 +#: ../../en/module/bala2.rst:118 ../../en/module/bala2.rst:133 +#: ../../en/module/bala2.rst:159 ../../en/module/bala2.rst:175 +#: ../../en/module/bala2.rst:192 ../../en/module/bala2.rst:209 +#: ../../en/module/bala2.rst:226 ../../en/module/bala2.rst:243 +#: ../../en/module/bala2.rst:257 ../../en/module/bala2.rst:271 +#: ../../en/module/bala2.rst:290 ../../en/module/bala2.rst:308 +#: ../../en/module/bala2.rst:325 ../../en/module/bala2.rst:341 +#: ../../en/module/bala2.rst:358 ../../en/module/bala2.rst:376 +#: ../../en/module/bala2.rst:393 ../../en/module/bala2.rst:409 +#: ../../en/module/bala2.rst:426 ../../en/module/bala2.rst:442 +#: 057c8c336cd3405d9683fc0e00ef1c9a 0df8057bd5f04b0e90e780b05287be92 +#: 20359f0e5d4d479bba05c3c18f79c468 4636be1ae7914e41b36f2bdf2cc16a0a +#: 5656c4e17053441f9dfc3160e53bd2b0 5f7ef2cdcfe04458a8990806d1e59cc9 +#: 5ff858be9ded4dcea73c2f9d55a15b4c 6f39f3338d7244de9152429016b8ee4d +#: 77c554a6afcb4f23b9575b4481310a44 9beed22b1d7145c5ac960beb49006ca2 +#: b118ff58ca8c45c9a4bf5eee6d0df49e b4fef442f6aa43f2998723b70cdaf9a1 +#: c07d9f14be3641138aec0f6a4285f3d2 cfeb734cdbf042d0ba519caea49f43f0 +#: dec93ccd638441bfa0b40bde554ebf20 df7ece7250904bf7ae96d1eb949168d8 +#: e8224ce1ccf44135a79a8c948b615fec eaa9ed12b78d4f2eb040d2438c2e913c +#: edbd2a77403b4a5d918697adc3403e96 ef3c16e5c21d4b618ed18ad234285470 +#: effdaa87f1a243f78bf554074a71271c ff69d12492f94c6b8d2ef05d33d781a9 msgid "MicroPython Code Block:" msgstr "MicroPython 代码块:" -#: ../../en/module/bala2.rst:143 b8f0976072fb4e289935312fbff8d7fd +#: ../../en/module/bala2.rst:144 41800db3713b4a36a9446a593ededdc7 msgid "**API**" msgstr "API应用" -#: ../../en/module/bala2.rst:146 7fb890e04d58455881922cdc2c1e2905 +#: ../../en/module/bala2.rst:147 e1f99ab2f5f14523997084105b3ad886 msgid "Bala2Module" msgstr "" -#: ../../en/module/bala2.rst:150 0b6f18d63f004e538ad40fded38e1ba5 +#: ../../en/module/bala2.rst:151 e842553ad5e449fdbbe8da860d7f079b msgid "Create an Bala2Module object." msgstr "创建一个 Bala2Module 对象" -#: ../../en/module/bala2.rst 4eec50ef61af4c61a434816f5890c4a7 +#: ../../en/module/bala2.rst 0172e4504c214c4c840935c1ed280273 +#: 5e9d0d525c4c4cc0b6c6ce5d5df9233d 6be99cd05e314c03b818e16c716e781a +#: 78c771b993714f7e8fa516bf80ff1326 87198377b6944e919b39b9d8aa6e39e8 +#: 8b41e8ba2f4f481eb5ddb444c9c1f7ae a397376160184970831770bd4dc26a34 +#: d2c2ecddf0604cb2b88f191b0f0a6998 dfca47d80a4948aab083682377bca4be msgid "Parameters" msgstr "" -#: ../../en/module/bala2.rst:152 08927f936ce2444097ec291153d4eb1b +#: ../../en/module/bala2.rst:153 32d79c5d211a450ebf3d2192cacfcd13 msgid "" "Timer ID from 0 to 3 (Use a timer to periodically call the balance " "control program.)" msgstr "定时器 ID 从 0 到 3(使用定时器周期性地调用平衡控制程序)。" -#: ../../en/module/bala2.rst:156 728f0418f04c47c493caf115e05b7e4b +#: ../../en/module/bala2.rst:157 2c7250ce80304da68c848781c3eedb47 msgid "|init.png|" msgstr "" -#: ../../en/refs/module.bala2.ref:11 555a813e314e4e80a2ec6db1ec401fb0 +#: ../../en/refs/module.bala2.ref:11 f1c6f7aa9ba147b0b8f0ce4754f3ee4c msgid "init.png" msgstr "" -#: ../../en/module/bala2.rst:168 7ad499cfa9e74f03ac28e8c9388ff762 +#: ../../en/module/bala2.rst:169 f5f33e82b39e4c99ac58bee32dc29803 msgid "Calibrate sensor" msgstr "校准传感器" -#: ../../en/module/bala2.rst:172 e99c5f4be33847a983f8a967e470cfb5 +#: ../../en/module/bala2.rst:173 c579bfbd29644868874d6363b0a511f5 msgid "|calibrate.png|" msgstr "" -#: ../../en/refs/module.bala2.ref:20 ce510b294ec24a70ac094aefc6ce9e9b +#: ../../en/refs/module.bala2.ref:20 0e9852708da84a84b4a5e6169c437eb9 msgid "calibrate.png" msgstr "" -#: ../../en/module/bala2.rst:182 38f73e9c6cfb4128975c96338a363d15 +#: ../../en/module/bala2.rst:183 f3c5a4a37b544337b8cb6cbf3b6e42f7 msgid "Set motor speed" msgstr "设置电机转速" -#: ../../en/module/bala2.rst:184 2bf9f10ac80d4008bdf027c6c8fbd051 +#: ../../en/module/bala2.rst:185 3e9d71de370546a799de76249271b56d msgid "The speed of the left motor. Range: -1023 ~ 1023." msgstr "左边电机转速,范围:-1023 ~ 1023。" -#: ../../en/module/bala2.rst:185 1c53b735878a45779dd5ebc1aa3ffeb6 +#: ../../en/module/bala2.rst:186 c8fc0b9f2d224d99a4cf29b2e22f52c9 msgid "The speed of the right motor. Range: -1023 ~ 1023." msgstr "右边电机转速,范围:-1023 ~ 1023。" -#: ../../en/module/bala2.rst:189 023d09e7eb3942419d4534baf7a0cab7 +#: ../../en/module/bala2.rst:190 78f4e680220442248b7e7400fdac4d94 msgid "|set_motor_speed.png|" msgstr "" -#: ../../en/refs/module.bala2.ref:21 e9f59f73cb894e4492c5a929bdbe388a +#: ../../en/refs/module.bala2.ref:21 64facb506e7b4a7395e9ec93c0a64c48 msgid "set_motor_speed.png" msgstr "" -#: ../../en/module/bala2.rst:199 dbe5fa44da6f45c8a05b0ad7f0a967b4 +#: ../../en/module/bala2.rst:200 30bc3532e8e74666b15591475d69a448 msgid "Set encoder value" msgstr "设置编码器数值" -#: ../../en/module/bala2.rst:201 43264dcadadc4f21bd90c841168b1e3c +#: ../../en/module/bala2.rst:202 c5da8af75c604035bebd051ede6bc3f4 msgid "The value of the left encoder. Range: -2^31 ~ 2^31." msgstr "左边电机编码器数值,范围:-2^31 ~ 2^31。" -#: ../../en/module/bala2.rst:202 4066011d962e417a8898af5291a429eb +#: ../../en/module/bala2.rst:203 e5ab48d38778453cbe4818e8d25ab37f msgid "The value of the right encoder. Range: -2^31 ~ 2^31." msgstr "右边电机编码器数值,范围:-2^31 ~ 2^31。" -#: ../../en/module/bala2.rst:206 9287d9067e8540b2ae2dfb9e9c57a625 +#: ../../en/module/bala2.rst:207 48da48524d714bcf899f3c6bd17825f3 msgid "|set_encoder_value.png|" msgstr "" -#: ../../en/refs/module.bala2.ref:22 216a625a5d9d4a71a62189c49abe430a +#: ../../en/refs/module.bala2.ref:22 57066376a2924fefa0f2802987beb59d msgid "set_encoder_value.png" msgstr "" -#: ../../en/module/bala2.rst:216 6e62c52642c1494b9e756fdbaca18216 +#: ../../en/module/bala2.rst:217 10937ddb8446495f9bb8dc2679e40c3d msgid "The left, right encoder value returned in a 2-tuple" msgstr "左、右编码器值以二元组的形式返回。" -#: ../../en/module/bala2.rst c9abe40f15784509a90837f0d7381ee4 +#: ../../en/module/bala2.rst 0e95cbe11d3143f18fad2787450322de +#: 20ca332b2608421685e4abda48d8aa2c 2131aefbc0d54cfd8dcb95f33f46e5dd +#: 85de64d2c9e0448da9150d41eadd2c04 b7bd5450cdb04e6db06cebabc2b4c37e +#: f5d675de69cf4c4c9338589579a0fc16 msgid "Returns" msgstr "" -#: ../../en/module/bala2.rst:218 c3a58359fd644709b2961334a2a79d62 +#: ../../en/module/bala2.rst:219 dd703fee4714409585a42d75b8e14394 msgid "left, right encoder value" msgstr "左,右编码器数值" -#: ../../en/module/bala2.rst 4bf7852763f143e6ab0fc8d71be42014 +#: ../../en/module/bala2.rst 34997226ac8d46c2a940f93e4f7df60a +#: 41115c4cb4d7429daf2d91ce8ec040ea 49a328ca2376430494a679993d093352 +#: 519fefb0136d43edbd4c1b92a2a69793 aa483eb52ac449b7a8f1b47fdf9eaf52 +#: dd08504ffaa1459b872edb32befdeb83 msgid "Return type" msgstr "" -#: ../../en/module/bala2.rst:223 fd4cb9a0dade4664a6e7ef87e9480058 +#: ../../en/module/bala2.rst:224 6c5abfa35b5c4d589f9fc0a0d653b058 msgid "|get_encoder_value.png|" msgstr "" -#: ../../en/refs/module.bala2.ref:12 807c5a0c8d3b4e36bab1c0de9b77556e +#: ../../en/refs/module.bala2.ref:12 fb30693808bb42dc8a03766b1291e24f msgid "get_encoder_value.png" msgstr "" -#: ../../en/module/bala2.rst:233 859d51b7db0148bfba90fa8f0fc1e84c +#: ../../en/module/bala2.rst:234 6345b46265fa48dea0c9c5097b1dd9f8 msgid "Set servo angle" msgstr "设置舵机角度" -#: ../../en/module/bala2.rst:235 bc30962afa384625883b1f229925d80e +#: ../../en/module/bala2.rst:236 ca429e1fcc0d469cb8b5f95feb3b030f msgid "The position of the output cahnnel. Range: 1 ~ 4." msgstr "输出通道,范围:1~4。" -#: ../../en/module/bala2.rst:236 9cd489257f104a2387576ec77334d0dd +#: ../../en/module/bala2.rst:237 0249d700d51d4523b7b931888e0904d1 msgid "The value of the right encoder. Range: 0 ~ 180." msgstr "舵机角度,范围:0~180。" -#: ../../en/module/bala2.rst:240 d36cede719e947aa883fbb4e6995fba2 +#: ../../en/module/bala2.rst:241 781ea8c77e904060b43785a848f99223 msgid "|set_servo_angle.png|" msgstr "" -#: ../../en/refs/module.bala2.ref:23 4a9550728cff4fb6b776f84a5fa7e684 +#: ../../en/refs/module.bala2.ref:23 0906ca3612ce408dbcbcef9736d95571 msgid "set_servo_angle.png" msgstr "" -#: ../../en/module/bala2.rst:250 83a3bdfe1b27456e8684bdd94b23da3f +#: ../../en/module/bala2.rst:251 ae718bb89c1b43218c92ad3a7e9f5d3f msgid "Start the balance car (car upright balance)" msgstr "开始直立平衡控制" -#: ../../en/module/bala2.rst:254 b966d6093d7440909bb3b35c3bf71820 +#: ../../en/module/bala2.rst:255 7de990d9547f4f24bdf29118cd4ddf45 msgid "|start.png|" msgstr "" -#: ../../en/refs/module.bala2.ref:18 45e175d263e94a51a836c9b58667f128 +#: ../../en/refs/module.bala2.ref:18 f607e0f44971452e9a92911f9da9e8e3 msgid "start.png" msgstr "" -#: ../../en/module/bala2.rst:264 e000880243074afc9db3b3f43ab2b327 +#: ../../en/module/bala2.rst:265 ea580f6dceca49b491b83b6c9df37f08 msgid "Stop the balance car (stop the balance control of the car)" msgstr "停止平衡控制" -#: ../../en/module/bala2.rst:268 57c55a0ec4e64780b28818cc20229631 +#: ../../en/module/bala2.rst:269 45ee08c2f9724077a00975c57e454de7 msgid "|stop.png|" msgstr "" -#: ../../en/refs/module.bala2.ref:19 30c0032e4c6146c085e0c4f26709865d +#: ../../en/refs/module.bala2.ref:19 b6c8b7e922994478a642e9878e9ebb04 msgid "stop.png" msgstr "" -#: ../../en/module/bala2.rst:278 d593255c08834d88866f3be948815f19 +#: ../../en/module/bala2.rst:279 b9cfca0767ae4c5d890e56e7a6cdfbdd msgid "Get the tilt angle of the balance car" msgstr "获取小车倾角" -#: ../../en/module/bala2.rst:280 00382933443240b9acb4883d1e8b401f +#: ../../en/module/bala2.rst:281 9987c792b299496082aecfdc09bfc431 msgid "The angle of the car" msgstr "小车倾角" -#: ../../en/module/bala2.rst:283 c6e5f4456e26458b961286579f37760f +#: ../../en/module/bala2.rst:284 2867b0d71d304802b501e1bd9572eca7 msgid "Data is valid only when the car is running (start() is called)." msgstr "小车运行 start() 时数据才有效" -#: ../../en/module/bala2.rst:287 56ab09afa5d243b49edba781f0d2faed +#: ../../en/module/bala2.rst:288 f495449287804f95aee8fe9183b85c39 msgid "|get_angle.png|" msgstr "" -#: ../../en/refs/module.bala2.ref:14 3fbad204b31b4a7d871d4ff5c18da656 +#: ../../en/refs/module.bala2.ref:14 8545ebae1d6c44de885dea61f6d716ef msgid "get_angle.png" msgstr "" -#: ../../en/module/bala2.rst:297 23767ebe8b9b4edb8e8fc66bb30564fd +#: ../../en/module/bala2.rst:298 34dfaec8a5f748d495faf2a1612d4b1e msgid "Set angle PID parameters" msgstr "设置角度环 PID 参数" -#: ../../en/module/bala2.rst:299 ../../en/module/bala2.rst:367 -#: 37aed2bb6b66496ab683f17188eb6bcc +#: ../../en/module/bala2.rst:300 ../../en/module/bala2.rst:368 +#: b4b3526a244f498492bee5667e4c921d b8e70902b2f947f6ace4c5f92db8a8ed msgid "Proportional gain" msgstr "比例增" -#: ../../en/module/bala2.rst:300 ../../en/module/bala2.rst:368 -#: e38f48b6361a4d8488e2df41f6d587a1 +#: ../../en/module/bala2.rst:301 ../../en/module/bala2.rst:369 +#: 98e1b57436a84febac49fcb64ab905e7 de40adc0241c427db34a72d070327090 msgid "Integral gain" msgstr "积分增益" -#: ../../en/module/bala2.rst:301 ../../en/module/bala2.rst:369 -#: a9e2ac06f932418ba1399db0c6f4745d +#: ../../en/module/bala2.rst:302 ../../en/module/bala2.rst:370 +#: 1aaa2ad5730548c08bf20fcd61706a29 eb3481b28d574e9ca025cbc261a5044c msgid "Derivative gain" msgstr "微分增益" -#: ../../en/module/bala2.rst:305 572904a053a04014a3fb0fb7be25067c +#: ../../en/module/bala2.rst:306 21b1dabe5ac14eef9ce6bcee6493179d msgid "|set_angle_pid.png|" msgstr "" -#: ../../en/refs/module.bala2.ref:24 120e7709967648d3961700308a684630 +#: ../../en/refs/module.bala2.ref:24 326eb849041d47b9b5627fad923a4614 msgid "set_angle_pid.png" msgstr "" -#: ../../en/module/bala2.rst:315 ea8137da1c4b465db6a1f5a86d494640 +#: ../../en/module/bala2.rst:316 cefec2bbd7884c4086fa775d2e34fa5e msgid "The angle loop PID parameters returned in a 3-tuple" msgstr "角度环 PID 参数以三元组的形式返回。" -#: ../../en/module/bala2.rst:317 ../../en/module/bala2.rst:385 -#: 81f8b33b876d4872bb08af4cc9a9f9ca +#: ../../en/module/bala2.rst:318 ../../en/module/bala2.rst:386 +#: 07ef38aa74904648927634bb1596a470 d8f49735dc844a92ba6201df82561a4b msgid "kp, ki, kd parameters" msgstr "" -#: ../../en/module/bala2.rst:322 4b81aeb043a74ed39a179ee0bfef06c3 +#: ../../en/module/bala2.rst:323 da3d2a4e5ebb4b668b51687363fbf9a7 msgid "|get_angle_pid.png|" msgstr "" -#: ../../en/refs/module.bala2.ref:13 7fa34598f286438f92c96ff0e902cb73 +#: ../../en/refs/module.bala2.ref:13 b8848f4867de430396fee3a1a16aad97 msgid "get_angle_pid.png" msgstr "" -#: ../../en/module/bala2.rst:332 7d54b82b25c64cec999db23213d58f03 +#: ../../en/module/bala2.rst:333 cfb2ef41dffd499fbe6c8591b38a6755 msgid "Set angle loop PID control target." msgstr "设置角度环 PID 控制目标" -#: ../../en/module/bala2.rst:334 0a95bef6893c4c84a0b8ba94e1ee298c +#: ../../en/module/bala2.rst:335 e0406ffd3d774ae7b4d15065e690cce9 msgid "The angle of the angle loop PID control target. Default is 0." msgstr "角度环控制目标角度,默认为0。" -#: ../../en/module/bala2.rst:338 8758357f2e3747cbaa25980ec64ef0bb +#: ../../en/module/bala2.rst:339 f1861300fdab4437805b03773f5d3c9a msgid "|set_angle_pid_target.png|" msgstr "" -#: ../../en/refs/module.bala2.ref:25 f16492a63a8948a383257a360a68b0a9 +#: ../../en/refs/module.bala2.ref:25 11414399f9fe4e37a04839d200acb6b3 msgid "set_angle_pid_target.png" msgstr "" -#: ../../en/module/bala2.rst:348 993ddc4ba5d64a0eb500bdee9391e305 +#: ../../en/module/bala2.rst:349 2f3fff444227441e9f8fa1becafe897d msgid "Get angle loop PID control target" msgstr "获取角度环 PID 控制目标。" -#: ../../en/module/bala2.rst:350 b8248bdef4ba4754899549624b339d97 +#: ../../en/module/bala2.rst:351 7dc23c6e9c9a42bf8c00995769face87 msgid "The angle loop PID control target" msgstr "角度环 PID 控制目标。" -#: ../../en/module/bala2.rst:355 6fa8adc5c75f46d28c2336c58450f12b +#: ../../en/module/bala2.rst:356 f03bc3052b834ab79d9d8debda053ecb msgid "|get_angle_pid_target.png|" msgstr "" -#: ../../en/refs/module.bala2.ref:15 4eade647672d4d7e9beffd306e9fe806 +#: ../../en/refs/module.bala2.ref:15 ac00c7d0819047fca78e55960f7e8e2d msgid "get_angle_pid_target.png" msgstr "" -#: ../../en/module/bala2.rst:365 41eddfc4b0af411581cc253d7f315bfe +#: ../../en/module/bala2.rst:366 3d28f62497194bbb8a690d0fc679733e msgid "Set speed loop PID parameters." msgstr "设置速度环 PID 控制参数。" -#: ../../en/module/bala2.rst:373 26f92314b8e34bd7adc68eb95c98b821 +#: ../../en/module/bala2.rst:374 eef7be1426064a4db3daa20c45dc02f8 msgid "|set_speed_pid.png|" msgstr "" -#: ../../en/refs/module.bala2.ref:26 4159ed1431a54f8f8d06f4ddeb2be90f +#: ../../en/refs/module.bala2.ref:26 418dae6830aa41a88d73ed57c67c286c msgid "set_speed_pid.png" msgstr "" -#: ../../en/module/bala2.rst:383 c13a3dfe3aca47c39716bb1f9098457d +#: ../../en/module/bala2.rst:384 9d62b060d22e4f019c0d22f132ac331d msgid "The speed loop PID parameters returned in a 3-tuple" msgstr "速度环 PID 参数以三元组的形式返回。" -#: ../../en/module/bala2.rst:390 f24797f470ab41c08c9fbbce89d54d25 +#: ../../en/module/bala2.rst:391 2bb05c1301434306a55aeb6bc4be7778 msgid "|get_speed_pid.png|" msgstr "" -#: ../../en/refs/module.bala2.ref:16 8dd3df04bbc5424f82196a17d147308f +#: ../../en/refs/module.bala2.ref:16 9407d4dfeb5d4cd39fa98bbc95b13915 msgid "get_speed_pid.png" msgstr "" -#: ../../en/module/bala2.rst:400 d5f06e3b0014482da2b091b868cade01 +#: ../../en/module/bala2.rst:401 68bb0365510743cc9cb00b2f0a6ac022 msgid "Set speed loop PID control target." msgstr "设置速度环 PID 控制目标。" -#: ../../en/module/bala2.rst:402 d0bed22c9ecc490d8deba1bba9e77c4d +#: ../../en/module/bala2.rst:403 f0f269c3e5114e778bad0fcd168b56e3 msgid "The speed of the speed loop PID control target. Default is 0." msgstr "速度环 PID 控制目标,默认为0。" -#: ../../en/module/bala2.rst:406 f3add6ca9e3b4a7791f9f4b9babd223c +#: ../../en/module/bala2.rst:407 cf890ee1592a4a6095694ebf4d4e903c msgid "|set_speed_pid_target.png|" msgstr "" -#: ../../en/refs/module.bala2.ref:27 96785310562d4ce6a453ba8655d3a8cc +#: ../../en/refs/module.bala2.ref:27 2f712e819dee44cfb347d17a07fc6851 msgid "set_speed_pid_target.png" msgstr "" -#: ../../en/module/bala2.rst:416 4feeb8f8e74c42ceae0f6ad325ba4e5a +#: ../../en/module/bala2.rst:417 5cb6f6c89eec4514a8700d4d985810d0 msgid "Get speed loop PID control target" msgstr "获取速度环 PID 控制目标。" -#: ../../en/module/bala2.rst:418 d2d50c9d518946f0bf9c6db82ab66e15 +#: ../../en/module/bala2.rst:419 297a4283df1a47379acbd83e9301c69b msgid "The speed loop PID control target" msgstr "速度环 PID 控制目标。" -#: ../../en/module/bala2.rst:423 74bfa0fc58ba488d9697ea41700fc6c9 +#: ../../en/module/bala2.rst:424 05c93d7e68d8426c8d84b4244ce42cb2 msgid "|get_speed_pid_target.png|" msgstr "" -#: ../../en/refs/module.bala2.ref:17 b3f283b23a8547e38fed96e0a015dd54 +#: ../../en/refs/module.bala2.ref:17 a2f435501c8f4ac8a81664c4b41e5548 msgid "get_speed_pid_target.png" msgstr "" -#: ../../en/module/bala2.rst:433 397fe509ca614fd2a521bf9a3cf0f91e +#: ../../en/module/bala2.rst:434 082ae64dd77f4e309219a49161847013 msgid "Set turning speed" msgstr "设置转向速度。" -#: ../../en/module/bala2.rst:435 2c92ba0d4d4f45cba53e6855c02d6e63 +#: ../../en/module/bala2.rst:436 cebca2bafb7743a099aabd5bd1f974e8 msgid "The speed of the left and right motor offset" msgstr "左右电机速度偏差。" -#: ../../en/module/bala2.rst:439 3d13eafdb5a44b4da2bada09ba31a457 +#: ../../en/module/bala2.rst:440 bdd1169b766b4152a274ed404750a608 msgid "|set_turn_speed.png|" msgstr "" -#: ../../en/refs/module.bala2.ref:28 ba01a95b4e374b89904f7df8014ac58e +#: ../../en/refs/module.bala2.ref:28 1773a5a5a2e04345bd1762b7f6eb0a1b msgid "set_turn_speed.png" msgstr "" #~ msgid "|Bala2-Fire|" #~ msgstr "" +#~ msgid "|Bala2| |Bala2-Fire|" +#~ msgstr "" + diff --git a/docs/locales/zh_CN/LC_MESSAGES/module/dualkmeter.po b/docs/locales/zh_CN/LC_MESSAGES/module/dualkmeter.po index c34fcf07..e4f35d50 100644 --- a/docs/locales/zh_CN/LC_MESSAGES/module/dualkmeter.po +++ b/docs/locales/zh_CN/LC_MESSAGES/module/dualkmeter.po @@ -9,7 +9,7 @@ msgid "" msgstr "" "Project-Id-Version: UIFlow2 Programming Guide \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-01-16 15:35+0800\n" +"POT-Creation-Date: 2025-04-18 15:58+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language: zh_CN\n" @@ -20,51 +20,51 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.16.0\n" -#: ../../en/module/dualkmeter.rst:2 94683b9c0e814389820f2e14f57bc8ac +#: ../../en/module/dualkmeter.rst:2 0cc431d30ef541dfb5167d475f52c44a msgid "DualKmeter Module" msgstr "" -#: ../../en/module/dualkmeter.rst:7 d372702f9c4d4dd1849ee012b24668a7 +#: ../../en/module/dualkmeter.rst:7 902dacf56aef4725b2cb6044dd05d80b msgid "Supported Products:" msgstr "支持以下产品:" -#: ../../en/module/dualkmeter.rst:10 f534a691bf704e42beb56d31eb8746fd +#: ../../en/module/dualkmeter.rst:10 f5d96fbdae7d444188252a60de110a41 msgid "|DualKmeter Module13.2|" msgstr "" -#: ../../en/refs/module.dualkmeter.ref ad26a2eb8a274c9e8dccbfef5a5ccb2b +#: ../../en/refs/module.dualkmeter.ref 055585766b0c4e939ce7c8dd558dc88e msgid "DualKmeter Module13.2" msgstr "" -#: ../../en/module/dualkmeter.rst:14 bf6ef171c18848519e81408ea39d45e8 +#: ../../en/module/dualkmeter.rst:14 fd5904cc9a0446a886aeaa66fec2965d msgid "Micropython Example::" msgstr "" -#: ../../en/module/dualkmeter.rst:28 af9e5e78ff6f497f8af1749426d41a3a +#: ../../en/module/dualkmeter.rst:28 eae27c7a9d974c7da5d94e54a36db4c4 msgid "UIFLOW2 Example:" msgstr "" -#: ../../en/module/dualkmeter.rst:30 0a19f8d72eec48819ac7f4cb23666593 -msgid "|example.svg|" +#: ../../en/module/dualkmeter.rst:30 c79950dd2e4a46ceb63cb0394111c3a3 +msgid "|example.png|" msgstr "" -#: ../../en/refs/module.dualkmeter.ref:6 badaabf12ffb4e27a6d9acdaab472890 -msgid "example.svg" +#: ../../en/refs/module.dualkmeter.ref:6 a26facd745464ac781f3b0b7d0c78e35 +msgid "example.png" msgstr "" -#: ../../en/module/dualkmeter.rst:35 b888bb2c1af7472ebdb30e9374e4a5e0 +#: ../../en/module/dualkmeter.rst:35 6b43218d548d4c02982faceb6a4de836 msgid "|dualkmeter_cores3_example.m5f2|" msgstr "" -#: ../../en/module/dualkmeter.rst:39 6fee06062011496ebdb81ab14fc2d239 +#: ../../en/module/dualkmeter.rst:39 372d5ff813c041269f68c320b20e8af7 msgid "class DualKmeterModule" msgstr "" -#: ../../en/module/dualkmeter.rst:42 840c965d01ef45f5b45c4bcd4e2317dc +#: ../../en/module/dualkmeter.rst:42 fcb8336ad09d40f9b88588c89d22eb9e msgid "Constructors" msgstr "" -#: ../../en/module/dualkmeter.rst:46 ab3c17f5732f4b7689afac14a32e2f3f +#: ../../en/module/dualkmeter.rst:46 3ad9fd5cd0fe4280876c38da85e57c8a msgid "" "Create a DualKmeterModule object. ``address`` accepts values from 0x11 to" " 0x20." @@ -74,27 +74,27 @@ msgstr "创建 DualKmeterModule 对象,``address`` 接受 0x11 ~ 0x20 的值 #: ../../en/module/dualkmeter.rst:73 ../../en/module/dualkmeter.rst:82 #: ../../en/module/dualkmeter.rst:91 ../../en/module/dualkmeter.rst:100 #: ../../en/module/dualkmeter.rst:111 ../../en/module/dualkmeter.rst:122 -#: ../../en/module/dualkmeter.rst:131 4519306efc1a4184942bc4ecd342a638 -#: 81a016e5ffa64da9acc41302d337a67d 91058b7224054e9684ae37943c139ddf -#: 943030444f7d4068b5df028328e5bb7f a41b3e914db74355b7bdd1b9b62aa53f -#: c7349a61c3064efe9317ae81f0b6c9fc e115e6655d2d41f19d7bbf32b2a50e53 -#: ffa8401fd3ef44909c67270c7fe2bab3 ffd98f2b3da448b5ac24108b65dcfa73 +#: ../../en/module/dualkmeter.rst:131 63a5dc1de6594960b8065f1611413a8f +#: 76979b59182b40609cb44cb0fc0763b7 893163279bf64c14b86921bcb8f37845 +#: 97c6698a89cf48a0b6e0b72de2c58594 b88a448437674578951551378304a85a +#: f482e46e8bb344e594e97bc5f94ce67a f7d42d9c33da4059996666249e79993b +#: f8b15662c7454ab982a3fe9dec9d8dca ff83868205564a26917869e1a6cf2c2c msgid "UIFLOW2:" msgstr "" -#: ../../en/module/dualkmeter.rst:50 d1cebfbae71947c5aa3f22bea3b11d8e -msgid "|init.svg|" +#: ../../en/module/dualkmeter.rst:50 bf6fb1b6ad284669b3e355445181a253 +msgid "|init.png|" msgstr "" -#: ../../en/refs/module.dualkmeter.ref:8 8a1d72a1b80845509c42371ef4de1b1a -msgid "init.svg" +#: ../../en/refs/module.dualkmeter.ref:8 ca6e2dcabdac4934b02e5979345bdd55 +msgid "init.png" msgstr "" -#: ../../en/module/dualkmeter.rst:54 8c6638679fd64361bd7576500d127af6 +#: ../../en/module/dualkmeter.rst:54 6d47521e19704c98a4494fdcee3aba6a msgid "Methods" msgstr "" -#: ../../en/module/dualkmeter.rst:58 5e3938ab8d714051b515fd092e67db3c +#: ../../en/module/dualkmeter.rst:58 cfe58ef2103649a8aa6cf62853adf1af msgid "" "Get the temperature of the thermocouple in the DualKmeter Module. Returns" " a float value." @@ -102,8 +102,8 @@ msgstr "获取 DualKmeter Module 热电偶的温度,返回 float 类型的值 #: ../../en/module/dualkmeter.rst:60 ../../en/module/dualkmeter.rst:71 #: ../../en/module/dualkmeter.rst:109 ../../en/module/dualkmeter.rst:120 -#: 201ef6e07b4f46bab7f2c36cc0cf3332 468c836dd8d444f2a8f1ad7861c3569f -#: 66b7a4367ee4484b89b5c7972847449f b331eb134d6945debb541c37fac66629 +#: 192faf56624d4df18c2fe4fe4e9c8c37 6575ce39823248a0a520052f7d8c702f +#: 9dde47a0957547bd99fc73b1dc0b9de5 f2f801d2fe0b485da26baed1a2f74121 msgid "" "``scale`` accepts values of :py:data:`DualKmeter.CELSIUS` or " ":py:data:`DualKmeter.FAHRENHEIT`." @@ -111,122 +111,182 @@ msgstr "" "``scale`` 允许接受的值是 :py:data:`DualKmeter.CELSIUS` 或者 " ":py:data:`DualKmeter.FAHRENHEIT` 。" -#: ../../en/module/dualkmeter.rst:64 9d7eb601351c4e03becfefa21c7ae75c -msgid "|get_thermocouple_temperature.svg|" +#: ../../en/module/dualkmeter.rst:64 3b754ad61b5740b5974ce71d58122aec +msgid "|get_thermocouple_temperature.png|" msgstr "" -#: ../../en/refs/module.dualkmeter.ref:10 8f9d5bef5355490badca31d048fe80de -msgid "get_thermocouple_temperature.svg" +#: ../../en/refs/module.dualkmeter.ref:10 26387003409e4e048621169c03535b88 +msgid "get_thermocouple_temperature.png" msgstr "" -#: ../../en/module/dualkmeter.rst:69 089866c5d7864b5e87164849e7400bfb +#: ../../en/module/dualkmeter.rst:69 3edaffcb9ff141e2811b5834c9248c3d msgid "" "Get the internal temperature of the DualKmeter Module. Returns a float " "value." msgstr "获取 DualKmeter Module 内部的温度,返回 float 类型的值。" -#: ../../en/module/dualkmeter.rst:75 0e5dcead964945a29ad64492d72a9815 -msgid "|get_kmeter_temperature.svg|" +#: ../../en/module/dualkmeter.rst:75 ed029a2bab924c918f98e91c3cf244ed +msgid "|get_kmeter_temperature.png|" msgstr "" -#: ../../en/refs/module.dualkmeter.ref:12 ec647c2ee9464461be3e7cadbb320e38 -msgid "get_kmeter_temperature.svg" +#: ../../en/refs/module.dualkmeter.ref:12 e1289662c4964243896a63691cf68aa9 +msgid "get_kmeter_temperature.png" msgstr "" -#: ../../en/module/dualkmeter.rst:80 e78db24f3e7c4fc9a4ecdbb59e1b1dfe +#: ../../en/module/dualkmeter.rst:80 94e4eb29cf2c454ca2bc14fbc622e4e2 msgid "" "Get the current thermocouple channel being used in the DualKmeter Module." " ``0`` represents channel 1, and ``1`` represents channel 2." msgstr "获取 DualKmeter Module 当前使用的热电偶通道。``0`` 是通道1, ``1`` 是通道2。" -#: ../../en/module/dualkmeter.rst:84 fb72b9d25f504f1ea6a4ed2ba6e60c5a -msgid "|get_kmeter_channel.svg|" +#: ../../en/module/dualkmeter.rst:84 e64830653f334bafa5f2132f682f68d8 +msgid "|get_kmeter_channel.png|" msgstr "" -#: ../../en/refs/module.dualkmeter.ref:14 9a8c22d39c2e45878e1c0827fcd53fe0 -msgid "get_kmeter_channel.svg" +#: ../../en/refs/module.dualkmeter.ref:14 c5a259c6ab7d404684c8cf2896fb27ba +msgid "get_kmeter_channel.png" msgstr "" -#: ../../en/module/dualkmeter.rst:89 f86f9b547c5a4f52b5dd7ddf0e809cbe +#: ../../en/module/dualkmeter.rst:89 4ce9c27312c146d5970f44a8a133b1bb msgid "" "Set the thermocouple channel to be used in the DualKmeter Module. ``0`` " "represents channel 1, and ``1`` represents channel 2." msgstr "设置 DualKmeter Module 使用的热电偶通道。``0`` 是通道1, ``1`` 是通道2。" -#: ../../en/module/dualkmeter.rst:93 caa6bc814d314c04855967d06bb50d8e -msgid "|set_kmeter_channel.svg|" +#: ../../en/module/dualkmeter.rst:93 23a03121c91f4bea890b52153f41c06d +msgid "|set_kmeter_channel.png|" msgstr "" -#: ../../en/refs/module.dualkmeter.ref:16 bd99277a12e740a89b6ab97ac8ddd40f -msgid "set_kmeter_channel.svg" +#: ../../en/refs/module.dualkmeter.ref:16 23ac7742619347dba2ee9ba846f53869 +msgid "set_kmeter_channel.png" msgstr "" -#: ../../en/module/dualkmeter.rst:98 b384ab6ac81c4f59a0110a67e4ddd0c8 +#: ../../en/module/dualkmeter.rst:98 4a7df23cd1234b7a9c2e4e5c090e1381 msgid "Check if the measurement result is ready." msgstr "获取测量结果是否存于就绪状态。" -#: ../../en/module/dualkmeter.rst:102 7ba3b75c96b94a8c9cd701f2c5859c0a -msgid "|is_ready.svg|" +#: ../../en/module/dualkmeter.rst:102 6425cc26f7c34245bbb0321f0cac650c +msgid "|is_ready.png|" msgstr "" -#: ../../en/refs/module.dualkmeter.ref:18 93d722c1dd164a14bba87798d9db25ad -msgid "is_ready.svg" +#: ../../en/refs/module.dualkmeter.ref:18 9955ff32f5d14e738eb81d2f246c52cd +msgid "is_ready.png" msgstr "" -#: ../../en/module/dualkmeter.rst:107 1b743fe5947b48a981eb371d53c0fba2 +#: ../../en/module/dualkmeter.rst:107 8e3d2b29f79c4acd8664183cab9b551b msgid "" "Get the temperature of the thermocouple in the DualKmeter Module as a " "string with a sign." msgstr "获取 DualKmeter Module 热电偶的温度,返回带正负符号的温度字符串。" -#: ../../en/module/dualkmeter.rst:113 4198b99ce00b4abd98b14ba22b96c29e -msgid "|get_thermocouple_temperature_string.svg|" +#: ../../en/module/dualkmeter.rst:113 4434446b19f14d1db0a60d990d764825 +msgid "|get_thermocouple_temperature_string.png|" msgstr "" -#: ../../en/refs/module.dualkmeter.ref:20 0d53855a8e7b4e7d961bb7186f443601 -msgid "get_thermocouple_temperature_string.svg" +#: ../../en/refs/module.dualkmeter.ref:20 b4244dbab7c7485eb14dbc00bd1ce992 +msgid "get_thermocouple_temperature_string.png" msgstr "" -#: ../../en/module/dualkmeter.rst:118 78d384666adb489186a647277b2adfe8 +#: ../../en/module/dualkmeter.rst:118 a00109e8d00f4d39845dcaa28617080c msgid "" "Get the internal temperature of the DualKmeter Module as a string with a " "sign." msgstr "获取 DualKmeter Module 内部的温度,返回带正负符号的温度字符串。" -#: ../../en/module/dualkmeter.rst:124 b2d169c7dde6424488f1cf7ea1a5809a -msgid "|get_kmeter_temperature_string.svg|" +#: ../../en/module/dualkmeter.rst:124 08742d9e63084526883af96fdefa7a55 +msgid "|get_kmeter_temperature_string.png|" msgstr "" -#: ../../en/refs/module.dualkmeter.ref:22 4f3995ac01d540788dd63057f2841bd8 -msgid "get_kmeter_temperature_string.svg" +#: ../../en/refs/module.dualkmeter.ref:22 62682d1ffedb48eeb8a1a861a51d118a +msgid "get_kmeter_temperature_string.png" msgstr "" -#: ../../en/module/dualkmeter.rst:129 8a01933f114a4649b94412d1a7fb38a6 +#: ../../en/module/dualkmeter.rst:129 fa54f388a20e4e3495825bd9ceef4b23 msgid "" "Get the firmware version of the DualKmeter Module. Returns an integer " "version number." msgstr "获取 DualKmeter Module 的固件版本。返回 int 类型的版本号。" -#: ../../en/module/dualkmeter.rst:133 7a3cd81eabdc4a5d8a097e11ed5bdccb -msgid "|get_fw_ver.svg|" +#: ../../en/module/dualkmeter.rst:133 a56892f0870e4fbeba3942fd6906e616 +msgid "|get_fw_ver.png|" msgstr "" -#: ../../en/refs/module.dualkmeter.ref:24 a7f56651f1ec4a79b3c4ee17d13cf98e -msgid "get_fw_ver.svg" +#: ../../en/refs/module.dualkmeter.ref:24 29198c70ef5a4e2ba8eb7263227d979d +msgid "get_fw_ver.png" msgstr "" -#: ../../en/module/dualkmeter.rst:137 a4248243cd32426bad0e1ac4f3a2ad62 +#: ../../en/module/dualkmeter.rst:137 410559ba0940415c99e6436885096b5f msgid "Constants" msgstr "" -#: ../../en/module/dualkmeter.rst:142 9d7bb3e837dc46559e1b958c684c3a6b +#: ../../en/module/dualkmeter.rst:142 9d424a46adcf4f2fa4d0b8964752b58c msgid "Celsius scale." msgstr "摄氏温标。" -#: ../../en/module/dualkmeter.rst:148 6ed69c5a601b43ca892b44c36e8d45b9 +#: ../../en/module/dualkmeter.rst:148 a30883cc5b99415889f420acd5f86d93 msgid "Fahrenheit scale." msgstr "华氏温标。" #~ msgid ":download:`example.m5f2 <../../_static/module/dualkmeter/example.m5f2>`" #~ msgstr "" +#~ msgid "|example.svg|" +#~ msgstr "" + +#~ msgid "example.svg" +#~ msgstr "" + +#~ msgid "|init.svg|" +#~ msgstr "" + +#~ msgid "init.svg" +#~ msgstr "" + +#~ msgid "|get_thermocouple_temperature.svg|" +#~ msgstr "" + +#~ msgid "get_thermocouple_temperature.svg" +#~ msgstr "" + +#~ msgid "|get_kmeter_temperature.svg|" +#~ msgstr "" + +#~ msgid "get_kmeter_temperature.svg" +#~ msgstr "" + +#~ msgid "|get_kmeter_channel.svg|" +#~ msgstr "" + +#~ msgid "get_kmeter_channel.svg" +#~ msgstr "" + +#~ msgid "|set_kmeter_channel.svg|" +#~ msgstr "" + +#~ msgid "set_kmeter_channel.svg" +#~ msgstr "" + +#~ msgid "|is_ready.svg|" +#~ msgstr "" + +#~ msgid "is_ready.svg" +#~ msgstr "" + +#~ msgid "|get_thermocouple_temperature_string.svg|" +#~ msgstr "" + +#~ msgid "get_thermocouple_temperature_string.svg" +#~ msgstr "" + +#~ msgid "|get_kmeter_temperature_string.svg|" +#~ msgstr "" + +#~ msgid "get_kmeter_temperature_string.svg" +#~ msgstr "" + +#~ msgid "|get_fw_ver.svg|" +#~ msgstr "" + +#~ msgid "get_fw_ver.svg" +#~ msgstr "" + diff --git a/docs/locales/zh_CN/LC_MESSAGES/module/fan.po b/docs/locales/zh_CN/LC_MESSAGES/module/fan.po index 3de719c7..abf5765d 100644 --- a/docs/locales/zh_CN/LC_MESSAGES/module/fan.po +++ b/docs/locales/zh_CN/LC_MESSAGES/module/fan.po @@ -9,7 +9,7 @@ msgid "" msgstr "" "Project-Id-Version: UIFlow2 Programming Guide \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-02-20 15:12+0800\n" +"POT-Creation-Date: 2025-04-03 10:58+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language: zh_CN\n" @@ -20,57 +20,59 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.16.0\n" -#: ../../en/module/fan.rst:2 8e3ee19c86404443a06da4fd5fe9dc5c +#: ../../en/module/fan.rst:2 cf6ca6cfecfa46e3a94f1fdd19d848b1 msgid "Fan v1.1 Module" msgstr "" -#: ../../en/module/fan.rst:8 ea1c5eba810849a68e0fd924db65fe1e +#: ../../en/module/fan.rst:8 ec9e48fd15834949ba0b263db8735eaa msgid "" "This is the driver library of Fan Module, which is used to control the " "fan." msgstr "这是Module FAN的驱动库, 用于控制Module FAN。" -#: ../../en/module/fan.rst:10 02a5ca02e4c044d481c891a2ea96a4d6 +#: ../../en/module/fan.rst:10 317818a1e07848bab812fd7d057d0707 msgid "Support the following products:" msgstr "支持以下产品:" -#: ../../en/module/fan.rst:12 f859a2d896814ff4b32ad7dc35d92879 +#: ../../en/module/fan.rst:12 9dd8805dfb6e43fbb474a5af39fdf937 msgid "|FAN|" msgstr "" -#: ../../en/refs/module.fan.ref 729a07278ba44f349bf4fbeb376b3fc2 +#: ../../en/refs/module.fan.ref 86d56092d99942178ea55e44a555770c msgid "FAN" msgstr "" -#: ../../en/module/fan.rst:16 9343ec4b19d4405597b87e1272464d3e +#: ../../en/module/fan.rst:16 0fcad8dc693c4887a59d23699db0d3fe msgid "UiFlow2 Example" msgstr "UiFlow2 应用示例" #: ../../en/module/fan.rst:19 ../../en/module/fan.rst:37 -#: 75d70ec5638a4a69bee0c1f0ab6cb0ea a303fdbaea884d49b0e51040c1e14593 +#: 171747dfdd2d47a7b1069eb24091f1dc ddd54382082c4d6a88b6117b5c8ef040 msgid "control module fan v1.1" msgstr "控制 module fan v1.1" -#: ../../en/module/fan.rst:21 53b433f63b6f48c19b6791a7e4b0faab +#: ../../en/module/fan.rst:21 ffd9e40e5f86406e889e9f7aac57575b msgid "Open the |fan_cores3_example.m5f2| project in UiFlow2." msgstr "在 UiFlow2 上打开 |fan_cores3_example.m5f2| 项目。" #: ../../en/module/fan.rst:23 ../../en/module/fan.rst:39 -#: 1297e4d487484d62b24b804a7c59ef24 9885be0d88444dbf89afc2ddfa07915f +#: 68c97b1f394d4994b01e38a5738617ac ef7641717c364a27840a1dc7056242a7 msgid "" "Initializes the fan module, sets the fan status, PWM frequency and duty " "cycle, and displays the fan status, speed, PWM frequency and duty cycle " "on the screen in real time. When the user touches the screen, the fan " "status toggles on/off." -msgstr "初始化风扇模块,设置风扇状态、PWM 频率和占空比,并在屏幕上实时显示风扇状态、转速、PWM 频率和占空比。当用户触摸屏幕时,风扇状态会切换为开/关。" - -#: ../../en/module/fan.rst:25 019f02f2b00b4ba8b89b850e5db0e748 -#: 04ab7c3d88f0483fabf05b37ffb34428 1def3cb718cd48f3817e90fb738c8bbf -#: 213d0ab2ca96486f92a29c766f4ce695 4a69014f330b450598baaf1b332d89e4 -#: 57a05457a7364b97bbee4b8ac4d6ab2f 5dd59248dab248ba95c6453122039bde -#: 76ab23850c774f688848b93359929dfe 7768e7c495fb4e0bb902dafe0221fd2e -#: 94dad7a6ac5849afa5114983247c774a c6a0b8e26a9a4445b4ce2a7c8aeea9c7 -#: f314b104602d4d01863be5d92f8ad29b f8f366b09a5041229ca9ad14ca3f5c1a +msgstr "" +"初始化风扇模块,设置风扇状态、PWM 频率和占空比,并在屏幕上实时显示风扇状态、转速、PWM " +"频率和占空比。当用户触摸屏幕时,风扇状态会切换为开/关。" + +#: ../../en/module/fan.rst:25 0ee041ceafab4431975c9e101732602c +#: 38bed7f2863e485fa6fd7cf7292de2b3 3dc55631f2764793939fb4d0697aa1e5 +#: 3de58450decd42879fe3083fbfe20cd8 4aa2d9e894914b2c874d343c1bb26b2e +#: 5225a44a9e634b6394aa96c0f8db24ec 95150dbeca034b2b874421145c4a295a +#: a9585316c17c4095bedb1ba914c4ca2c b7a9e07d565c4b6783f817a0be01d232 +#: d7a6efbeec8e49cb988f82399c5f05c4 e746614520b6497f9a02e79a3dd1cafa +#: ef588b8558f0432388cc0e8ff76b0f6e f9af730dba5b465aadb86aedeb778f33 #: module.fan.FanModule.get_fan_rpm:6 module.fan.FanModule.get_fan_state:6 #: module.fan.FanModule.get_firmware_version:6 #: module.fan.FanModule.get_i2c_address:6 @@ -84,35 +86,35 @@ msgstr "初始化风扇模块,设置风扇状态、PWM 频率和占空比, msgid "UiFlow2 Code Block:" msgstr "UiFlow2 代码块:" -#: ../../en/module/fan.rst:27 451d2524c58944f58e84b6b49c5e2a95 +#: ../../en/module/fan.rst:27 11fbd331603d49feac753e682954c99a msgid "|example.png|" msgstr "" -#: ../../en/refs/module.fan.ref:5 f9825155fe2b4cb781a7a5faca7981d2 +#: ../../en/refs/module.fan.ref:5 8944d2f8ceb2460a807ef9ed006972c7 msgid "example.png" msgstr "" #: ../../en/module/fan.rst:29 ../../en/module/fan.rst:47 -#: 50fff7ca636c490ebc2393668313e9ce ec4dab0ff5204431a9aaf77842d103e5 +#: 66bfcaa18bbf430ab79c4e80d28d0829 750c70c5c6a94217b774471ae4b2b807 msgid "Example output:" msgstr "示例输出:" #: ../../en/module/fan.rst:31 ../../en/module/fan.rst:49 -#: 08ab28b6f43e4317ad576e9a980d3a11 9ba75b029746436eb903c80e0be978d3 +#: 53d59702a4f44974bc403c0ab95b294b 7c45d688313b485d8350cbbe54ea2dfb msgid "None" msgstr "无" -#: ../../en/module/fan.rst:34 3351491d4b93423586b88b7ef1a2bafc +#: ../../en/module/fan.rst:34 78ea864d8b2c4708b1246cb7ee074494 msgid "MicroPython Example" msgstr "MicroPython 应用示例" -#: ../../en/module/fan.rst:41 0a0ae7171e954b7686bbf920ce80f6fb -#: 139f67ff7f1745c4a15170c29010c4d9 1ea80bbe29164adaa8014a264ddc919b -#: 548cb0634bfd49dab3bfd27765940d9f 7b5dd6bb2c0f4ab08c29b3a9b7a19237 -#: a318227aa0ab40538e6e4911df532c3f ad09ef0b35ee412c9190f921a0729cd0 -#: ba1a5284134b42c78765a645b8f07216 d9e12d3d4125422d95c2ccb14b57d722 -#: da1b7fc30fab4c4fb8f7e56148888b4a df816738705944aaaaf93b352c64702c -#: ebb46f7c7def43139aa9a5b3b9f2275d f71759758f594f278dc993966f3e9578 +#: ../../en/module/fan.rst:41 0574c3bc06fb487da7d1be62fa439e5c +#: 0dc2a5b231a34ff19cccd9410136d80e 236acbeb6efb46cc8191cd30651121a5 +#: 2db4c050ef7b437cb53d07d198e4fc88 39bf3cf2a68f414285d10d2f168207be +#: 47b588559f854376b43d55079ba2b2d3 7839ac2bdbd7439ea77d090aabed225e +#: 7ade6478bac24100b8e7029851fca3e9 8a39fed659f4471f8a7f5ca9a7f52363 +#: cb2b0794b4df46cbbcd14e88ab0036ef d3f782c14120418f863af74420ea0c00 +#: e9d5620136834aba8520668835e8081a f397e996ee7c4f8abf984a4e3ea51d56 #: module.fan.FanModule.get_fan_rpm:10 module.fan.FanModule.get_fan_state:10 #: module.fan.FanModule.get_firmware_version:10 #: module.fan.FanModule.get_i2c_address:10 @@ -126,51 +128,51 @@ msgstr "MicroPython 应用示例" msgid "MicroPython Code Block:" msgstr "MicroPython 代码块:" -#: ../../en/module/fan.rst:53 9b200b91d07b4888b32ce6ab2115bb10 +#: ../../en/module/fan.rst:53 2a29f411271742498484bf5ce5d87d51 msgid "**API**" msgstr "" -#: ../../en/module/fan.rst:56 47c64461c9cb484a8e40c61e659e523c +#: ../../en/module/fan.rst:56 029e555e8ace48b6979b45b911d0a778 msgid "FanModule" msgstr "" -#: d523c826d9ee43e6b220769c460e968a module.fan.FanModule:1 of +#: ae1b5b726d4f42f09b7da648683c7efd module.fan.FanModule:1 of msgid "Bases: :py:class:`object`" msgstr "" -#: ../../en/module/fan.rst 0fcad1cb939f4607b29863c5069c53e6 -#: 1362a73f188b4d089fee4f9f14b8b79c 4af71338031a4bb5a467fa91ab6e32b5 -#: 5cfdd37a6d6c4c3f849683c31b95539a 767b1601a50d470695ea7b9f036d6bf7 +#: ../../en/module/fan.rst 7462cabe54724cc79d56c1f926287197 +#: 896a5ccbeb7b4f3c8c55f0fce9bf90c9 b9099923b17d41bf85f052060ceb2228 +#: d8390eaf0149468d9438022f3761dbf5 fe275413f3dc41b9a28f5f4ca683f4db #: module.fan.FanModule.set_fan_state module.fan.FanModule.set_i2c_address #: module.fan.FanModule.set_pwm_duty_cycle #: module.fan.FanModule.set_pwm_frequency of msgid "Parameters" msgstr "" -#: 1fedf2ff9fcb45a3b7cb47894a64c4ef module.fan.FanModule.set_fan_state:1 of +#: 39ef5935a70b4332a64e3d8a92b3f86a module.fan.FanModule.set_fan_state:1 of msgid "Set the fan state to on or off." msgstr "设置风扇的状态为开/关。" -#: e1e3e57761c54372ba9e413f26f88ccb module.fan.FanModule.set_fan_state:3 of +#: 172a9e2a4b0b4ab6befb4b6c9d1443f6 module.fan.FanModule.set_fan_state:3 of msgid "The state of the fan." msgstr "风扇的状态。" -#: 41f3e0937da04a91a63582f1ba54aea0 module.fan.FanModule.set_fan_state:7 of +#: 9587d95a4add4eb8b1a0e4d3aef9bcd1 module.fan.FanModule.set_fan_state:7 of msgid "|set_fan_state.png|" msgstr "" -#: ../../en/refs/module.fan.ref:8 ac190eb13f6c4e7baa64008499175b8f +#: ../../en/refs/module.fan.ref:8 f0bca698a81e4c53bb96803094ab6860 msgid "set_fan_state.png" msgstr "" -#: 79e69e4b952444e8b3a2302c4a154138 module.fan.FanModule.get_fan_state:1 of +#: 7d3fd23d9d0d45c29497ef5bb9543bbe module.fan.FanModule.get_fan_state:1 of msgid "Get current fan state." msgstr "获取当前风扇的状态。" -#: 0832ef1e1eb744c58df1afccdb35bcf7 20c9b2cb692a4ac39b104e62ca930658 -#: 3e8024b74857494683894f18aaf5a23d 800c21223d11441f96f57edaa69996e3 -#: 90238fd59ec84bd2b1b2496c3356464d c7e035e014a2439a8ee0cf005b058e42 -#: fe7704dad2f74b1c9284f97b6d01337f module.fan.FanModule.get_fan_rpm +#: 12ed9576d1a3402e85145f14cd4c3af3 1cfa6c421b4a49d599244c2d078be075 +#: 81f49867258140b09813e0df5fbf8c06 8a998de0c3c841ca8f79aa54fad38559 +#: 8e3cde7adbc3406295d5c97ae5a723f0 a34354ac8ab64494ac764eec9d0677cb +#: a6a72c021bfa4b439a0eb8b5450be0a1 module.fan.FanModule.get_fan_rpm #: module.fan.FanModule.get_fan_state module.fan.FanModule.get_firmware_version #: module.fan.FanModule.get_i2c_address module.fan.FanModule.get_pwm_duty_cycle #: module.fan.FanModule.get_pwm_frequency @@ -178,16 +180,16 @@ msgstr "获取当前风扇的状态。" msgid "Returns" msgstr "" -#: a43bd590bc1e4abf92e7fbd4d4bddc7f module.fan.FanModule.get_fan_state:3 of +#: c19bb4874e1548b3b81e6009ee818d3d module.fan.FanModule.get_fan_state:3 of msgid "The current fan state." msgstr "当前风扇的状态。" -#: 2c8f9c3f9f1e43cc9bb558add5b4943d 58c7820fae7c46a88b70a2fdcf9a8c7b -#: 5e2272f81f09415f87952659336521fb 61052b3d1760418aac5cdb13cc9607d1 -#: 71913b759d33461da00354b766936973 759591725b2347ffacae7dba82d80b3d -#: 7653214fd5854b289afd0a2822243d66 7dacae67fe7a4a9b9ab7a29925d7d53a -#: 838bbd9908eb45acbc0d36dad1216696 b3234ccbe6704f6fa239d1054ef646c0 -#: d9e4384e37474654a15cc7b812c450ef module.fan.FanModule.get_fan_rpm +#: 0ba9f4e142e24715ad660d2cdc3fd0e3 230c2de259394f519b3d87833bb71134 +#: 34c34d729a1047dcbfa8ab649519f6e1 351071db19f3440f821ed6e3a3f2750d +#: 7a5df47f86034d069138a4f7956d6775 9827de8eba5b412bb25178c9d3c57d4d +#: b6ef5345d9b44052a2c39187125afe21 b93d2fe07a0d40bfba4a467cf7dfbcad +#: c9e50d86dc2243148b8daee8bbe88bfc eec8ef338772409e836e5de4cc37d86c +#: fdd6679af69940cfa450071d3402398e module.fan.FanModule.get_fan_rpm #: module.fan.FanModule.get_fan_state module.fan.FanModule.get_firmware_version #: module.fan.FanModule.get_i2c_address module.fan.FanModule.get_pwm_duty_cycle #: module.fan.FanModule.get_pwm_frequency @@ -197,177 +199,182 @@ msgstr "当前风扇的状态。" msgid "Return type" msgstr "返回值类型" -#: c23b8e1e6538462bb672b77af2f7a083 module.fan.FanModule.get_fan_state:8 of +#: 109cd13f0b404163a2432be3500b15a3 module.fan.FanModule.get_fan_state:8 of msgid "|get_fan_state.png|" msgstr "" -#: ../../en/refs/module.fan.ref:9 e538e95bb9fc4235a4a3ac00abaf3246 +#: ../../en/refs/module.fan.ref:9 072ba281622c4d88be11f43bd957d426 msgid "get_fan_state.png" msgstr "" -#: 99adcc2faec042d489b9ef8b68484dc4 module.fan.FanModule.set_pwm_frequency:1 of +#: bf1b0dcecea047bd8009d43c4cbc76ad module.fan.FanModule.set_pwm_frequency:1 of msgid "Set the PWM frequency of the fan." msgstr "设置风扇的PWM频率。" -#: 50db985498a5410eb77c69b6b5f01d52 module.fan.FanModule.set_pwm_frequency:3 of +#: cabb21ef79c74affb2043c4b1ad35962 module.fan.FanModule.set_pwm_frequency:3 of msgid "The PWM frequency of the fan." msgstr "风扇的PWM频率。" -#: b433c9a5ad44441bb74a937ce759b181 module.fan.FanModule.set_pwm_frequency:7 of +#: bd6caf7989f14f38834ab7e8101d2579 module.fan.FanModule.set_pwm_frequency:7 of msgid "|set_pwm_frequency.png|" msgstr "" -#: ../../en/refs/module.fan.ref:10 fbae333f50c24ba6afbe2ff233aebff7 +#: ../../en/refs/module.fan.ref:10 163ef2a01a4b4504865d0d9e325331c3 msgid "set_pwm_frequency.png" msgstr "" -#: a256af6dacd34c4994047690310244fa module.fan.FanModule.get_pwm_frequency:1 of +#: a74f29746cf44b3296344d39e3a3e801 module.fan.FanModule.get_pwm_frequency:1 of msgid "Get current PWM frequency." msgstr "获取当前PWM频率。" -#: 30adb78f27bd495e9c111368128842e1 module.fan.FanModule.get_pwm_frequency:3 of +#: a64a5042624645c6b8bbdeac949d5e3b module.fan.FanModule.get_pwm_frequency:3 of msgid "The current PWM frequency." msgstr "当前PWM频率。" -#: ad8272ede4af4f138e17aa3d922aff06 module.fan.FanModule.get_pwm_frequency:8 of +#: 02bb1b98f38e42e0a669bfa8dab1c692 module.fan.FanModule.get_pwm_frequency:8 of msgid "|get_pwm_frequency.png|" msgstr "" -#: ../../en/refs/module.fan.ref:11 17bc73f4fd7047a38fef14ca4096f17e +#: ../../en/refs/module.fan.ref:11 650a62fa7e384a0b8dfe4c4ec8be5141 msgid "get_pwm_frequency.png" msgstr "" -#: a9124eea11394c9095a8f0a118882160 module.fan.FanModule.set_pwm_duty_cycle:1 +#: 677a92a93a9e48caa630706c8b549bdf module.fan.FanModule.set_pwm_duty_cycle:1 #: of msgid "Set the PWM duty cycle of the fan." msgstr "设置风扇的PWM占空比。" -#: b81215d6c72648de9beb857119a5969b module.fan.FanModule.set_pwm_duty_cycle:3 +#: a003dece29c443e49679d47b94fa5bc5 module.fan.FanModule.set_pwm_duty_cycle:3 #: of msgid "The PWM duty cycle of the fan." msgstr "风扇的PWM占空比。" -#: f009cfef212a4d3faaf0af8a1fd17534 module.fan.FanModule.set_pwm_duty_cycle:7 +#: 15452ec8592e40e9923eba1502c9e3c5 module.fan.FanModule.set_pwm_duty_cycle:7 #: of msgid "|set_pwm_duty_cycle.png|" msgstr "" -#: ../../en/refs/module.fan.ref:12 a36964749d0e499f9be711caa338d83e +#: ../../en/refs/module.fan.ref:12 e681b0b38c7b4b1080fa1fb226fe0687 msgid "set_pwm_duty_cycle.png" msgstr "" -#: cdf66531ab2744069ab21c0949d7fc4b module.fan.FanModule.get_pwm_duty_cycle:1 +#: d4fe3a5e32424820b3a68fae7541cd0b module.fan.FanModule.get_pwm_duty_cycle:1 #: of msgid "Get current PWM duty cycle." msgstr "获取当前PWM占空比。" -#: 6c483a26685c41b99a67dac3bfcb880b module.fan.FanModule.get_pwm_duty_cycle:3 +#: 9a671fb81b5a46c4a7181772b5841a33 module.fan.FanModule.get_pwm_duty_cycle:3 #: of msgid "The current PWM duty cycle." msgstr "当前PWM占空比。" -#: 9a244c5d6acd4304b468a6fa3a03d851 module.fan.FanModule.get_pwm_duty_cycle:8 +#: f055a3ec798e4486b57be3ec2cdb118c module.fan.FanModule.get_pwm_duty_cycle:8 #: of msgid "|get_pwm_duty_cycle.png|" msgstr "" -#: ../../en/refs/module.fan.ref:13 cf38d1e93d534f1e9cff90dd446e04c3 +#: ../../en/refs/module.fan.ref:13 d710d88671b646239bfd27064e4c05df msgid "get_pwm_duty_cycle.png" msgstr "" -#: 55b7dfaae7074061a49b9fc43767af65 module.fan.FanModule.get_fan_rpm:1 of +#: d934225a5d4e42c3a3ea4d4fb1db2811 module.fan.FanModule.get_fan_rpm:1 of msgid "Get current fan RPM." msgstr "获取当前风扇的转速(每分钟转速)。" -#: d590ac3e8b744432a3c657bfbb3a5869 module.fan.FanModule.get_fan_rpm:3 of +#: 51068b562d34475ca3aacc5deb26aa29 module.fan.FanModule.get_fan_rpm:3 of msgid "The current fan RPM." msgstr "当前风扇的转速(每分钟转速)。" -#: 578f0cff874a4c00983234d7dcaa7c23 module.fan.FanModule.get_fan_rpm:8 of +#: 10960b061b564b3eb478cd7cfb8cc918 module.fan.FanModule.get_fan_rpm:8 of msgid "|get_fan_rpm.png|" msgstr "" -#: ../../en/refs/module.fan.ref:14 42c16f3dd17948549114571e998f914d +#: ../../en/refs/module.fan.ref:14 5d15f7a519014a22aab57b760508d691 msgid "get_fan_rpm.png" msgstr "" -#: 769947e19ff249e5a285410baabf9cf6 module.fan.FanModule.get_single_frequency:1 +#: 427d830e102343409d65563514b2ea5a module.fan.FanModule.get_single_frequency:1 #: of msgid "Get current single frequency." msgstr "获取当前信号频率。" -#: 47633facec364a218cf1f99faee0f6fb module.fan.FanModule.get_single_frequency:3 +#: a9ae6bff6ac447e38f6d16fd8580e274 module.fan.FanModule.get_single_frequency:3 #: of msgid "The current single frequency." msgstr "当前信号频率。" -#: b8aea18cced3487687eacc3e2bd12902 module.fan.FanModule.get_single_frequency:8 +#: 0c3fc4fa8d2047eda4f3546f96bdc0d9 module.fan.FanModule.get_single_frequency:8 #: of msgid "|get_single_frequency.png|" msgstr "" -#: ../../en/refs/module.fan.ref:15 340e80feb9e546af96da824d392e9159 +#: ../../en/refs/module.fan.ref:15 77b28ee0e36f46efa23c5fd8c340570f msgid "get_single_frequency.png" msgstr "" -#: 5e35109428ee43ba93c5309d2012d9af module.fan.FanModule.write_flash:1 of +#: 27b52acf99494c5090c9c82286b64c35 module.fan.FanModule.write_flash:1 of msgid "" "Save the current configuration(fan status, PWM frequency, and PWM duty " "cycle) to the flash." msgstr "将当前配置(风扇状态、PWM 频率和 PWM 占空比)保存到闪存中。" -#: d37abe1024264de5888da285a43a921c module.fan.FanModule.write_flash:5 of +#: 9d80ea49e2424de89d938497d3d9c6f2 module.fan.FanModule.write_flash:5 of msgid "|write_flash.png|" msgstr "" -#: ../../en/refs/module.fan.ref:16 eeea0282c5094626be51533572f26011 +#: ../../en/refs/module.fan.ref:16 123050944ae14d62bfa4fd7dd5be543a msgid "write_flash.png" msgstr "" -#: 38dbd9f887f041b89593917f64bd17eb module.fan.FanModule.get_firmware_version:1 +#: 4318d3ebe198454ba8c7f977d5159e78 module.fan.FanModule.get_firmware_version:1 #: of msgid "Get current firmware version." msgstr "获取当前固件版本。" -#: 516638a5bea045b7bd27d9d7bca6ad49 module.fan.FanModule.get_firmware_version:3 +#: 5a458cab14674bf496c6d5d28749fdfa module.fan.FanModule.get_firmware_version:3 #: of msgid "The current firmware version." msgstr "当前固件版本。" -#: 5a0da8fdbf4940f79f2d0731870121a6 module.fan.FanModule.get_firmware_version:8 +#: bce354c504064925b49071e830c5643f module.fan.FanModule.get_firmware_version:8 #: of msgid "|get_firmware_version.png|" msgstr "" -#: ../../en/refs/module.fan.ref:17 acb3fde569c64d9e839382c3f15dc04c +#: ../../en/refs/module.fan.ref:17 01e72674f3084da0ac5d3f4a9be2a4cf msgid "get_firmware_version.png" msgstr "" -#: 7577cb8ff3974ea7aaaa79a3a0d837f1 module.fan.FanModule.get_i2c_address:1 of +#: 025af500d46449eab167d137fc8bd301 module.fan.FanModule.get_i2c_address:1 of msgid "Get current I2C address." msgstr "获取当前I2C地址。" -#: 4ea32f7330734748a2f013621d921fff module.fan.FanModule.get_i2c_address:3 of +#: d899ada3969146159402249e79c2c547 module.fan.FanModule.get_i2c_address:3 of msgid "The current I2C address." msgstr "当前I2C地址。" -#: 0dc3d2d17add47dd956cf915ea110ed2 module.fan.FanModule.get_i2c_address:8 of +#: ebcff0c5b96c415e90f1c1bf62ae8f57 module.fan.FanModule.get_i2c_address:8 of msgid "|get_i2c_address.png|" msgstr "" -#: ../../en/refs/module.fan.ref:18 5a8930515f1144c28ee728cb9151490f +#: ../../en/refs/module.fan.ref:18 c5a1ef0531884dcd8bd6ac9baa7a1c7b msgid "get_i2c_address.png" msgstr "" -#: 6be3aa0b67d74ab08a30204762744d63 module.fan.FanModule.set_i2c_address:1 of +#: 0e34e03be95042228d1f49f63cc3bd9b module.fan.FanModule.set_i2c_address:1 of msgid "Set the I2C address of the fan." msgstr "设置Module Fan的I2C地址。" -#: 48a9ded1077745a185259f6dcfd9a2c9 module.fan.FanModule.set_i2c_address:3 of +#: 958068ea33f8485ea05791cf3e1b4aff module.fan.FanModule.set_i2c_address:3 of msgid "The I2C address of the fan." msgstr "Module Fan的I2C地址。" -#: d64a893082824b5b854801fa97bcc7e4 module.fan.FanModule.set_i2c_address:7 of +#: d76f9b3ec0fd41d0905ba7d24041b7be module.fan.FanModule.set_i2c_address:7 of msgid "|set_i2c_address.png|" msgstr "" +#: ../../en/refs/module.fan.ref:19 37f8e3ba7b5f481c92d94c5f55541fea +#, fuzzy +msgid "set_i2c_address.png" +msgstr "获取当前I2C地址。" + diff --git a/docs/locales/zh_CN/LC_MESSAGES/module/gateway_h2.po b/docs/locales/zh_CN/LC_MESSAGES/module/gateway_h2.po index d9572b75..7fc683a6 100644 --- a/docs/locales/zh_CN/LC_MESSAGES/module/gateway_h2.po +++ b/docs/locales/zh_CN/LC_MESSAGES/module/gateway_h2.po @@ -9,7 +9,7 @@ msgid "" msgstr "" "Project-Id-Version: UIFlow2 Programming Guide \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-04-03 10:55+0800\n" +"POT-Creation-Date: 2025-04-18 15:58+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language: zh_CN\n" @@ -20,63 +20,65 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.16.0\n" -#: ../../en/module/gateway_h2.rst:2 ../../en/module/gateway_h2.rst:58 -#: 6795b0c03a6349549cc0a19e2e231d82 6d2f389a0f11415ca559892bc73432b2 -msgid "GatewayH2Module" -msgstr "" +#: ../../en/module/gateway_h2.rst:2 e19e55afc022403693a4976a9db3aea6 +#, fuzzy +msgid "GatewayH2 Module" +msgstr "创建一个 GatewayH2Module 对象。" -#: ../../en/module/gateway_h2.rst:8 041bb1bed81b4d36a344534817a7399e +#: ../../en/module/gateway_h2.rst:8 aa9c851722064a0199671b9293d09e49 msgid "" "This library is the driver for Module Gateway H2, and the module " "communicates via UART." msgstr "这个库是 Module Gateway H2 的驱动,该模块使用 UART 通信。" -#: ../../en/module/gateway_h2.rst:10 0179fe0fad564981ad424b331e6d40ff +#: ../../en/module/gateway_h2.rst:10 d86d4e9f66204a8babc5e2d194d635ca msgid "Support the following products:" msgstr "支持以下产品:" -#: ../../en/module/gateway_h2.rst:12 8303c52707f04ddbb85270d64609112a +#: ../../en/module/gateway_h2.rst:12 a4ec1e708cda4aff96cf787f76fdc5c5 msgid "|Module Gateway H2|" msgstr "" -#: ../../en/refs/module.gateway_h2.ref bca19ca6e4484df68ffca00b0e6f6ab3 +#: ../../en/refs/module.gateway_h2.ref 773736b1f3254bbb8e1a6628973aab6c msgid "Module Gateway H2" msgstr "" -#: ../../en/module/gateway_h2.rst:14 db6fc6b6b861442eb78e8ec4c42304eb +#: ../../en/module/gateway_h2.rst:14 c8ec17fe2c86438c8cfc213301a43a20 msgid "" "When using this module, you need to flash the NCP firmware to the module." " For details, refer to the `ESP Zigbee NCP " "`_" " documentation." -msgstr "当使用此模块时,需要先给模块烧录 NCP 固件,详情请查看 " -"`ESP Zigbee NCP `_" +msgstr "" +"当使用此模块时,需要先给模块烧录 NCP 固件,详情请查看 `ESP Zigbee NCP " +"`_" -#: ../../en/module/gateway_h2.rst:17 498bac24265a4bbb8162789e858cb8a8 +#: ../../en/module/gateway_h2.rst:17 1181fbeb5bea4791bb3de6793704b859 msgid "UiFlow2 Example:" msgstr "UiFlow2 应用示例:" #: ../../en/module/gateway_h2.rst:20 ../../en/module/gateway_h2.rst:40 -#: 0073ce626526478abb81a8b1afab8513 9a1f54b291c5497c9db57e47a93b98ee +#: 29e7430bb1914308b4e0286371d50c9e a515a86dc99e452c8f277c86ec46e009 msgid "Switch Control" msgstr "开关控制" -#: ../../en/module/gateway_h2.rst:22 a268f346c8f34b178d9ce374d069b58f +#: ../../en/module/gateway_h2.rst:22 db4dfa574e0e4fad808edd3e25a06552 msgid "" "To use this example, you need to flash this program onto an ESP32C6 or " "similar module as a light node device. For details, refer to " "`HA_on_off_light `_" -msgstr "要使用此示例,您还需要 ESP32C6 或类似模块,作为灯节点设备。详情请查看 " -"`HA_on_off_light `_" -#: ../../en/module/gateway_h2.rst:24 b66191d527ae4cf5ac715b166e5925ae +#: ../../en/module/gateway_h2.rst:24 39110b3a782e49f096b1257054f1f6ca msgid "Open the |cores3_switch_endpoint_example.m5f2| project in UiFlow2." msgstr "在 UiFlow2 上打开 |cores3_switch_endpoint_example.m5f2| 项目。" #: ../../en/module/gateway_h2.rst:26 ../../en/module/gateway_h2.rst:42 -#: 0c4a14dad07542b29cfc89b8cdd23906 7daff67e589a4c31b44b2633169779d3 +#: cc7ff6b8a3ff4fc3985877fe1f607cf4 f4053911fc3e40b7a35b6540b14b75a8 msgid "" "The example demonstrates group control and targeted device operation for " "light nodes through SwitchEndpoint of Gateway H2 module." @@ -85,174 +87,182 @@ msgstr "示例程序演示通过 Gateway H2 模块的 SwitchEndpoint 对灯节 #: ../../en/module/gateway_h2.rst:28 ../../en/module/gateway_h2.rst:68 #: ../../en/module/gateway_h2.rst:87 ../../en/module/gateway_h2.rst:110 #: ../../en/module/gateway_h2.rst:131 ../../en/module/gateway_h2.rst:152 -#: 776959c331ea44eb90bccd6419f613ec ae3e88dab1ca42b0a8341dbb2137256c -#: bc93b5556e3c409fb3b07030f29102ac ce25b5a427c44741956abd309e2369d8 +#: 2f6658ae25494530890cd84bb968b848 47b82bb79100491c92be76ab1f9e63f2 +#: 8f5c1a7ee3424fa5a3c91aa743c42e96 cc180cdd81b24c89b0a45418b594750c +#: cf912c8a810349caa5d2672525e3c67c d91d269cc6c943db8223006762f0dc97 msgid "UiFlow2 Code Block:" msgstr "UiFlow2 代码块:" -#: ../../en/module/gateway_h2.rst:30 f67afed0a2444bb2b9be1de0c48ec1d7 +#: ../../en/module/gateway_h2.rst:30 d5c35632775e4a4b9410cc55457e3ccf msgid "|cores3_switch_endpoint_example.png|" msgstr "" -#: ../../en/refs/module.gateway_h2.ref:13 59e23416323b4b2281d3cba1af4ef9dc +#: ../../en/refs/module.gateway_h2.ref:13 8720d54fbade418281f2769387097397 msgid "cores3_switch_endpoint_example.png" msgstr "" #: ../../en/module/gateway_h2.rst:32 ../../en/module/gateway_h2.rst:50 -#: 838d61133cda48c98871899eb59671f9 9ede75f1d5324ed5a5697de10996472d +#: 7c65e468a40a41f59ad85576fddbc585 e41ffe3b90354f15af140a75c498a2ce msgid "Example output:" msgstr "示例输出:" #: ../../en/module/gateway_h2.rst:34 ../../en/module/gateway_h2.rst:52 -#: 2670111baae24bb780efb1a0215ae66e ace5f81630d4466087540fd9d39c88ee +#: 01c8437c8ba044b1a82e6cb912c6440b 10868a5fed744bab8dc4e928ed8c0f35 msgid "None" msgstr "无" -#: ../../en/module/gateway_h2.rst:37 8792ab6af90b4d169c189e36210d9c55 +#: ../../en/module/gateway_h2.rst:37 824f70f7b2ff40fa8d0447e23c185511 msgid "MicroPython Example:" msgstr "MicroPython 应用示例:" #: ../../en/module/gateway_h2.rst:44 ../../en/module/gateway_h2.rst:72 #: ../../en/module/gateway_h2.rst:91 ../../en/module/gateway_h2.rst:115 #: ../../en/module/gateway_h2.rst:136 ../../en/module/gateway_h2.rst:157 -#: d0132ccd22034544b1b5b4801e818b12 e882ca30fdde4d03bbab151e35a58636 -#: f4fff747dcdb41739b03189f6bd7c40c +#: 2d2af19b49734f5ca224127927653653 3dc8c4347fff4eacacfee8305afba39e +#: 6a010522357e475db129e50d97f9653c a483406eb0d6449eacb7b55c07c28e9c +#: b12d7fb4a8dc4480b63556fafb117f08 f49b82712c4e4d2f93139c250d4eca77 msgid "MicroPython Code Block:" msgstr "MicroPython 代码块:" -#: ../../en/module/gateway_h2.rst:55 4eb772729e244915a9dabedb60fef637 +#: ../../en/module/gateway_h2.rst:55 e4eb486c928f4e51bf676f19c1dd4ffb msgid "**API**" msgstr "API应用" -#: ../../en/module/gateway_h2.rst:62 b1116c2d676544ad9b40ef96af9cf344 +#: ../../en/module/gateway_h2.rst:58 a568092b9dc04fe492b4155ed9115958 +msgid "GatewayH2Module" +msgstr "" + +#: ../../en/module/gateway_h2.rst:62 8537114f70814414b3f58c2955b0d65a msgid "Create an GatewayH2Module object." msgstr "创建一个 GatewayH2Module 对象。" -#: ../../en/module/gateway_h2.rst 6c767589cbab411daadd8a480fdf7340 -#: 748336fbd6d24c1bb024644f855b627d +#: ../../en/module/gateway_h2.rst 3070fc6f968a43c1ba0c4ae56a69a0a0 +#: 44b7126bac6f46c08bdc85044234a25b 62583fcd99b444caacf9fe153482ce32 +#: 9309b42129d2473f9272d571e9d00dd6 msgid "Parameters" msgstr "" -#: ../../en/module/gateway_h2.rst:64 f83792df85cc4a839d53fc1bf8a549e8 +#: ../../en/module/gateway_h2.rst:64 e02ea7abe71a4590b0d46ad0df2d1f4a msgid "UART id." msgstr "UART 端口号。" -#: ../../en/module/gateway_h2.rst:65 fdcf149d52e84dd591e55d5e1671af93 +#: ../../en/module/gateway_h2.rst:65 2127f6d4cade47f59a3f510577295eff msgid "the UART TX pin." msgstr "UART 发送引脚。" -#: ../../en/module/gateway_h2.rst:66 022e1da314aa4e649a4267ba622aa5b3 +#: ../../en/module/gateway_h2.rst:66 1ddf0cad1349499baeaf41483ab6273b msgid "the UART RX pin." msgstr "UART 接收引脚。" #: ../../en/module/gateway_h2.rst:70 ../../en/module/gateway_h2.rst:89 -#: b807c412ab84489888985f0b59061eb0 d131f0ceaa2f419ab9e6d1814e234049 +#: 9111b896e2014345b8c5d1b25f8bd7e5 ca24a8deff564343a5d4449648c11981 msgid "|init.png|" msgstr "" -#: ../../en/refs/module.gateway_h2.ref:6 3eb18a4d3a4c49648aa61b9efc66faa1 -#: fa246b563473471680fa53b5daf8ca90 +#: ../../en/refs/module.gateway_h2.ref:6 3b908e3c324747e68de2b3a70d549c7a +#: bbffccefaf724868b68e4312323f9f0a msgid "init.png" msgstr "" -#: ../../en/module/gateway_h2.rst:82 9f9d616ef9ac48b68e6c93c3ea9f2208 +#: ../../en/module/gateway_h2.rst:82 d5628482070d4d16aa9d18b95227efb4 msgid "Create Switch Endpoint." msgstr "创建 Switch Endpoint。" -#: ../../en/module/gateway_h2.rst 302656c90385422abd5be78e3c486386 +#: ../../en/module/gateway_h2.rst 2231cc84a222440aa5447f871a297331 msgid "returns SwitchEndpoint" msgstr "" -#: ../../en/module/gateway_h2.rst:84 89d0f116cb344f838933212a9a727d49 +#: ../../en/module/gateway_h2.rst:84 98d661a71d9b4242981cb7748aaa585e msgid "zigbee switch endpoint object." msgstr "" -#: ../../en/module/gateway_h2.rst 81c36522dc754c1888081f9fa3a751ad +#: ../../en/module/gateway_h2.rst 05a8105697c74bad8f7b10874d473842 msgid "return type" msgstr "" -#: ../../en/module/gateway_h2.rst:85 7528a12373be4cb0a2d1df2aaebe07ee +#: ../../en/module/gateway_h2.rst:85 e1b84a056feb4b05a6c73841e7881726 msgid "SwitchEndpoint" msgstr "" -#: ../../en/module/gateway_h2.rst:99 61668f3ff2a04c039cfbb71d16a0c1ea +#: ../../en/module/gateway_h2.rst:99 30b6ea2f2b3a46b68a65b55f74fd7a4b msgid "Return by GatewayH2Module.create_switch_endpoint()" msgstr "由 GatewayH2Module.create_switch_endpoint() 返回" -#: ../../en/module/gateway_h2.rst:103 a07638d724264303b28870ad9da86189 +#: ../../en/module/gateway_h2.rst:103 66b8ead44d6a49c9ac840db2cfcc9f77 msgid "Turn on the light." msgstr "开灯" #: ../../en/module/gateway_h2.rst:105 ../../en/module/gateway_h2.rst:126 -#: ../../en/module/gateway_h2.rst:147 b753ac510c2b4a0497801a450d87692f +#: ../../en/module/gateway_h2.rst:147 286135c737584b74b09f445aeab7d6a3 +#: 2ea0861bb4ab402fa2e3d86634d90e04 f5266d4fa2de4bb4a542508621eab5c6 msgid "The device address (optional)." msgstr "设备地址(可选)" -#: ../../en/module/gateway_h2.rst:107 3abe4963d9f140519f6af06d781b9b71 +#: ../../en/module/gateway_h2.rst:107 914a3e1934344784aa8329a40222cf5a msgid "If called as ``on()``, turn on all devices." msgstr "调用 ``on()`` 打开所有设备。" -#: ../../en/module/gateway_h2.rst:108 e65586ae0dfe41cc9667583b87a6bdfb +#: ../../en/module/gateway_h2.rst:108 f134e000848d4c4a9bd213ef83aa2340 msgid "If called as ``on(addr)``, turn on special address devices." msgstr "调用 ``on(addr)`` 打开地址为 addr 的设备。" -#: ../../en/module/gateway_h2.rst:112 60e593971dd54420bc0724fea016e73f +#: ../../en/module/gateway_h2.rst:112 367452a58d1044e5a6981ba6e68cf4f1 msgid "|on.png| |all_on.png|" msgstr "" -#: ../../en/refs/module.gateway_h2.ref:7 fa246b563473471680fa53b5daf8ca90 +#: ../../en/refs/module.gateway_h2.ref:7 475ea9a989aa4799ad950140b9abba0f msgid "on.png" msgstr "" -#: ../../en/refs/module.gateway_h2.ref:8 1c528b2c6fa74b61bbcba4d68aef2968 +#: ../../en/refs/module.gateway_h2.ref:8 16e38960f83a496e832af54799473f09 msgid "all_on.png" msgstr "" -#: ../../en/module/gateway_h2.rst:124 b40f46a37cd54040a14c355c20e09ccf +#: ../../en/module/gateway_h2.rst:124 47691d2689cd41f5b7a5edd75dac69f7 msgid "Turn off the light." msgstr "关灯" -#: ../../en/module/gateway_h2.rst:128 723eac96f2044545a1b5f237642e8d1f +#: ../../en/module/gateway_h2.rst:128 cbda5ec47d154658908c3e32b290e909 msgid "If called as ``off()``, turn off all devices." msgstr "调用 ``off()`` 关闭所有设备。" -#: ../../en/module/gateway_h2.rst:129 49fbbc6856774e88b6e560ca05cebfde +#: ../../en/module/gateway_h2.rst:129 4d7c81222dd843cbb6a8dbe303122419 msgid "If called as ``off(addr)``, turn off special address devices." msgstr "调用 ``off(addr)`` 关闭地址为 addr 的设备。" -#: ../../en/module/gateway_h2.rst:133 da56af9bdcbc4364ab079a0968a66343 +#: ../../en/module/gateway_h2.rst:133 1ad21bd02ced4aa8aa8fa7494ee296b2 msgid "|off.png| |all_off.png|" msgstr "" -#: ../../en/refs/module.gateway_h2.ref:9 f03c0d5c0c644e83970bb598f8a9613e +#: ../../en/refs/module.gateway_h2.ref:9 ad474273949c49359db12d470fdd7fb7 msgid "off.png" msgstr "" -#: ../../en/refs/module.gateway_h2.ref:10 68fa471c6d6348978131e0bce193fc5e +#: ../../en/refs/module.gateway_h2.ref:10 ffcad8fcbb674e62a9a940f4853ab268 msgid "all_off.png" msgstr "" -#: ../../en/module/gateway_h2.rst:145 fe9674650c6d4fb5b8b3e5c76dc1fb57 +#: ../../en/module/gateway_h2.rst:145 5db16a9263374b239875e25088de04fd msgid "Toggle the light state." msgstr "翻转灯状态" -#: ../../en/module/gateway_h2.rst:149 1a10e2918246434b96abb8f7fadb0951 +#: ../../en/module/gateway_h2.rst:149 2123233fe6964ee3880b918811db9645 msgid "If called as ``toggle()``, toggle all devices." msgstr "调用 ``toggle()`` 翻转所有设备的状态。" -#: ../../en/module/gateway_h2.rst:150 9d323f8ee3ec40eb97a958bcfc8a9779 +#: ../../en/module/gateway_h2.rst:150 9e0e58cdbae74ea980c76d76d88c42c0 msgid "If called as ``toggle(addr)``, toggle special address devices." msgstr "调用 ``toggle(addr)`` 翻转地址为 addr 的设备的状态。" -#: ../../en/module/gateway_h2.rst:154 e0e017a948d74af1b2790a0a0e452ba8 +#: ../../en/module/gateway_h2.rst:154 eb61c645907a424f9d3eceabf08ef0f2 msgid "|toggle.png| |all_toggle.png|" msgstr "" -#: ../../en/refs/module.gateway_h2.ref:11 4d1a3f5113894862a205ab05e2949fb3 +#: ../../en/refs/module.gateway_h2.ref:11 a2eacaac1b1d4c8cb38e1ee5195768a8 msgid "toggle.png" msgstr "" -#: ../../en/refs/module.gateway_h2.ref:12 d47c6caf80b34dee97e256d3f56bd022 +#: ../../en/refs/module.gateway_h2.ref:12 f5b4821391ed49a79db61d3c968ac3bb msgid "all_toggle.png" msgstr "" diff --git a/docs/locales/zh_CN/LC_MESSAGES/module/lora.po b/docs/locales/zh_CN/LC_MESSAGES/module/lora.po index db366c53..d25b7465 100644 --- a/docs/locales/zh_CN/LC_MESSAGES/module/lora.po +++ b/docs/locales/zh_CN/LC_MESSAGES/module/lora.po @@ -9,7 +9,7 @@ msgid "" msgstr "" "Project-Id-Version: UIFlow2 Programming Guide \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-01-16 15:35+0800\n" +"POT-Creation-Date: 2025-04-18 15:58+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language: zh_CN\n" @@ -20,124 +20,124 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.16.0\n" -#: ../../en/module/lora.rst:3 7f2a708363e84efdbd39d58fd75628e1 +#: ../../en/module/lora.rst:3 762f8edd969e4bd6ba0849a2798f6fc8 msgid "Lora Module" msgstr "" -#: ../../en/module/lora.rst:7 d343b9be506a4e85b08f649a3f84d78f +#: ../../en/module/lora.rst:7 b816cd15925d430c90e20cc919fd0abc msgid "" "The LoRa433_V1.1 Module is part of the M5Stack stackable module series. " "It is a LoRa communication module that operates at a 433MHz frequency and" " utilizes the Ra-02 module (SX1278 chip) solution." msgstr "" -#: ../../en/module/lora.rst:9 c3fe750c95a44c41a53014119b419d56 +#: ../../en/module/lora.rst:9 3511b8bcbcef4bb39fb052cc782db340 msgid "Support the following products:" msgstr "" -#: ../../en/module/lora.rst:11 33453eabd7ae4c6e9d681263a0f2d9bf +#: ../../en/module/lora.rst:11 6668bf79fba641939750b3959fa6dd73 msgid "|LoraModule|" msgstr "" -#: ../../en/refs/module.lora.ref 0ebc7adcb67c4284a93bae26d98236f6 +#: ../../en/refs/module.lora.ref 4b400c242e004e38884f92eaecaef57b msgid "LoraModule" msgstr "" -#: ../../en/module/lora.rst:13 92a57955649d4ad39fd98b04c07ae252 +#: ../../en/module/lora.rst:13 dd729f197f934caa8d4c064735785855 msgid "Micropython Example::" msgstr "" -#: ../../en/module/lora.rst:34 42473482eefe476ca58a82db374ab2db +#: ../../en/module/lora.rst:34 82b4b23ccf0641afbb1c6141be013351 msgid "UIFLOW2 Example:" msgstr "" -#: ../../en/module/lora.rst:36 7ba4c677348e4c1a80e529e5cd46be9a -msgid "|example_tx.svg|" +#: ../../en/module/lora.rst:36 d9361200a69b4d4e99b5414342cb4422 +msgid "|example_tx.png|" msgstr "" -#: ../../en/refs/module.lora.ref:16 c4d277a0188e4ad5b2355aec019d0f18 -msgid "example_tx.svg" +#: ../../en/refs/module.lora.ref:16 bfd288d2be864f809a01a0e280604aa6 +msgid "example_tx.png" msgstr "" -#: ../../en/module/lora.rst:38 cf1647403f9d4026b6774a64d6d8641a -msgid "|example_rx.svg|" +#: ../../en/module/lora.rst:38 be012c97fe6e4b1380b3089387caa813 +msgid "|example_rx.png|" msgstr "" -#: ../../en/refs/module.lora.ref:17 fd6ee3e2279145469c0925c1b42bd1c2 -msgid "example_rx.svg" +#: ../../en/refs/module.lora.ref:17 0edb00521a994a1ab62524d3ca2644ab +msgid "example_rx.png" msgstr "" -#: ../../en/module/lora.rst:43 04a414fc995340bab41a9b30faf23b9b +#: ../../en/module/lora.rst:43 fc2615f54b5145bba6b1f11aac2a0263 msgid "|cores3_lora433_rx_example.m5f2|" msgstr "" -#: ../../en/module/lora.rst:45 465ee4fce705451aa69739f54deb0a88 +#: ../../en/module/lora.rst:45 5181ffb126484532b67a813e4c36de98 msgid "|cores3_lora433_tx_example.m5f2|" msgstr "" -#: ../../en/module/lora.rst:47 b60e5d3f0fd14941ad2d626cb1b385c3 +#: ../../en/module/lora.rst:47 3034c606f8824f92845ba525db18c439 msgid "|cores3_lora868_rx_example.m5f2|" msgstr "" -#: ../../en/module/lora.rst:49 4b2b57a8ef624c8fa2883d0aeeddd550 +#: ../../en/module/lora.rst:49 c5440c719acc49cdb9b49033001f7e48 msgid "|cores3_lora868_tx_example.m5f2|" msgstr "" -#: ../../en/module/lora.rst:53 12b2dbc317dc43e0a37237d274423768 +#: ../../en/module/lora.rst:53 0d41347cfadc416f8362aca720a75f90 msgid "class LoraModule" msgstr "" -#: ../../en/module/lora.rst:56 9f784778b952456dbdd6759b1303704a +#: ../../en/module/lora.rst:56 f0ca0182e9284487ba03814464e2bd04 msgid "Constructors" msgstr "" -#: ../../en/module/lora.rst:60 b0a0a974c25c4e468aa421ecdb414576 +#: ../../en/module/lora.rst:60 953b12b1c7d6475dbe16be318be828a3 msgid "Initialize the LoRa module." msgstr "" -#: ../../en/module/lora.rst 0dce39e8e6f64694895dd68420179d68 -#: 87bbcda49ab240c8a77689946c64fde2 be542e0537154322aecebf3910ed08cd -#: c0b4dacb6afa4fb3bf749e656e5c4186 +#: ../../en/module/lora.rst 2ea8d727b15b4563ad36191c2bfe5704 +#: 30d9fdeafe574837a4f40a98daef9117 36a36c7a4f8845abb8ee5abb375e824f +#: 7d0949a3b2734e81a7fb8bba9728ae45 msgid "Parameters" msgstr "" -#: ../../en/module/lora.rst:62 89049e69ada846ff96bc95b699d138e8 +#: ../../en/module/lora.rst:62 eb53ebf03d73490ca69cb95cdd680445 msgid "Chip select pin" msgstr "" -#: ../../en/module/lora.rst:63 84d363cc4a794eeeaf642fade25baffd +#: ../../en/module/lora.rst:63 29e6f817f14b49a9af5849cab2e91fa7 msgid "Interrupt pin" msgstr "" -#: ../../en/module/lora.rst:64 ca8d74d76bca40e9b9401de45233b930 +#: ../../en/module/lora.rst:64 0f6a27ff9efe46fdbf9f7deba6b196d9 msgid "Reset pin" msgstr "" -#: ../../en/module/lora.rst:65 39ad7b1b92554f0b9698438f6a5472c7 +#: ../../en/module/lora.rst:65 ab32f20eb1ae44ea94b4aade3198c172 msgid "LoRa RF frequency in kHz." msgstr "" -#: ../../en/module/lora.rst:66 b1f769cb288947918f9a8b71fb6f3dbd +#: ../../en/module/lora.rst:66 9cf38496f98045d9bc09ae75edaf63a2 msgid "" "Spreading factor, Higher spreading factors allow reception of weaker " "signals but have slower data rate." msgstr "" -#: ../../en/module/lora.rst:67 1b9010f98c554c759c3bd00d5f571ff3 +#: ../../en/module/lora.rst:67 2994719651d845d39a76b75e921e78c9 msgid "Bandwidth value in kHz. Must be exactly one of BANDWIDTHS" msgstr "" -#: ../../en/module/lora.rst:68 7d279f6e30a647efa55b5e3f7103bc9b +#: ../../en/module/lora.rst:68 8746e0c31d4a4fb19e56d6e2cb7c0f83 msgid "" "Forward Error Correction (FEC) coding rate is expressed as a ratio, " "`4/N`." msgstr "" -#: ../../en/module/lora.rst:69 bcbd0770ec4240d28d6bd9822f51a704 +#: ../../en/module/lora.rst:69 6855729b795748ce802d55f46f44b994 msgid "Length of the preamble sequence, in units of symbols." msgstr "" -#: ../../en/module/lora.rst:70 ae192a841b0c47068e02fdf5d7f4081f +#: ../../en/module/lora.rst:70 48807b87bebe4d54abdb6525798c15df msgid "Output power in dBm." msgstr "" @@ -145,45 +145,45 @@ msgstr "" #: ../../en/module/lora.rst:103 ../../en/module/lora.rst:113 #: ../../en/module/lora.rst:124 ../../en/module/lora.rst:134 #: ../../en/module/lora.rst:144 ../../en/module/lora.rst:154 -#: 0356ce1f82b247d3b2126a3431264561 15faab7e7c974ef09c73f43f71ecbf3e -#: 29478d77942d4d8592530cd9037fc97b a894824cb5f647ffa1a35be9e934d4fb -#: a9d2c798b13f477dbbf2e39dba200356 b2d355c128d3429d9bee643b8614d50e -#: c785a35c9986459b998226931b83044c dae6ce30e7744a79b2e0551a8030c84b +#: 004d02a1fd7248d5b2cd6cbcbb15af29 0195e014a264492ca04048f07399e490 +#: 3244fbb2f43846dfa25218eb390b584b 3593c1f5d06e480eade847d35217bcf7 +#: 577451347c3f41fd9fa0e1a87c82ed4b 6a2f365d3bad4c53b6ce0e6f37c3de68 +#: 801b0780df8f4f35b7f5816acd8e5b70 dad3939d0cdc4d0c9f95a763b835e80d msgid "UIFLOW2:" msgstr "" -#: ../../en/module/lora.rst:74 4d102572656a44a881e47ebb89acd3b3 +#: ../../en/module/lora.rst:74 402718b054b548a5b767e6659b57e807 msgid "|init.png|" msgstr "" -#: ../../en/refs/module.lora.ref:7 db90251121b940f59001c237aa4335d5 +#: ../../en/refs/module.lora.ref:7 02352bec824746daa4d2545fdc0aafba msgid "init.png" msgstr "" -#: ../../en/module/lora.rst:78 cdfb7b0fe0164e61a13d4bdf3cb2cfac +#: ../../en/module/lora.rst:78 b9767a5616cd4704a1d2646e59fe027a msgid "Methods" msgstr "" -#: ../../en/module/lora.rst:82 6409d353e7ac45a087e596917abec298 +#: ../../en/module/lora.rst:82 93552eafee444ba2a93ec9bb25757006 msgid "Send a data packet." msgstr "" -#: ../../en/module/lora.rst 8f8dc377dc19458cb17517b4aae708f9 +#: ../../en/module/lora.rst 9361c9ecfd0847fb9305f0a892e686aa msgid "return (int)" msgstr "" -#: ../../en/module/lora.rst:84 5474ae1c830c4f839af05918e815dd04 +#: ../../en/module/lora.rst:84 c411e216ec9d4041a0c0ce433c1f1e2f msgid "" "The return value is the timestamp when transmission completed, as " "a`time.ticks_ms()` result. It will be more accurate if the " "modem was initialized to use interrupts." msgstr "" -#: ../../en/module/lora.rst:86 c279956d971442b7b333d5854696efd2 +#: ../../en/module/lora.rst:86 3d1b0561fe0a4b24952baa6a9f05d6f1 msgid "The data packet to send." msgstr "" -#: ../../en/module/lora.rst:87 c33e5a956f8447e884f1695ad7752c38 +#: ../../en/module/lora.rst:87 f982fbf562204f448a42571244c584ee msgid "" "Time to transmit the packet in milliseconds. For precise timing of sent " "packets, there is an optional `tx_at_ms` argument which is a " @@ -192,36 +192,36 @@ msgid "" " block until that time arrives" msgstr "" -#: ../../en/module/lora.rst:91 351af52403684e938e013bd48a1f8f09 +#: ../../en/module/lora.rst:91 6f83fd599f394afb98403afd2a965951 msgid "|send.png|" msgstr "" -#: ../../en/refs/module.lora.ref:8 cc8e0d981d8e43429cd8301b555cf926 +#: ../../en/refs/module.lora.ref:8 5a5c159f2d2f44daa7dfdc0801235e64 msgid "send.png" msgstr "" -#: ../../en/module/lora.rst:95 a6658c0b6d4245a3b57a9a97f13a77d7 +#: ../../en/module/lora.rst:95 78f43fe793de44db9caed467ebbd8cab msgid "Receive a data packet." msgstr "" -#: ../../en/module/lora.rst c92fbc5388b64eb69fe8117c7e84140c +#: ../../en/module/lora.rst e3c48c1581874d7483c95e36b23c0ba2 msgid "return (RxPacket)" msgstr "" -#: ../../en/module/lora.rst:97 14a9470f6fcc4f65ae47c32ac205f8b7 +#: ../../en/module/lora.rst:97 214231772f924676bff0b8cdcb06a3f9 msgid "" "Returns None on timeout, or an `RxPacket` instance with the " "packet on success." msgstr "" -#: ../../en/module/lora.rst:99 fdce09207b2f4137b0003e0b3cdfb26f +#: ../../en/module/lora.rst:99 4341b57fd2fd49e3b8a72c06f22a401d msgid "" "Optional, sets a receive timeout in milliseconds. If None (default " "value), then the function will block indefinitely until a packet is " "received." msgstr "" -#: ../../en/module/lora.rst:100 61adb0b2ade94b4f9af2381695ada9d9 +#: ../../en/module/lora.rst:100 2e4f7004b6ec4a8f9bcccc156412657a msgid "" "Necessary to set if `implicit_header` is set to " "`True` (see above). This is the length of the packet to " @@ -229,7 +229,7 @@ msgid "" "received radio header includes the length." msgstr "" -#: ../../en/module/lora.rst:101 0965c11054f140f5b0a94c9841d393dc +#: ../../en/module/lora.rst:101 716ba87ebb6a4937b7c56802684e9b59 msgid "" "Optional, this can be an `RxPacket` object previously received " "from the modem. If the newly received packet has the same length, this " @@ -238,89 +238,101 @@ msgid "" " is allocated and returned instead." msgstr "" -#: ../../en/module/lora.rst:105 5c96c6ce6a514ea4910491c72c30811f +#: ../../en/module/lora.rst:105 611775034af6408784638a232d423097 msgid "|recv.png|" msgstr "" -#: ../../en/refs/module.lora.ref:9 4a1337c4408743a9977fa73e77396260 +#: ../../en/refs/module.lora.ref:9 241aefd9d87b4388b94c8a250cb54241 msgid "recv.png" msgstr "" -#: ../../en/module/lora.rst:109 50b09d5dfc76453f8f496b96d57310ed +#: ../../en/module/lora.rst:109 80e27224512f4ceead0f3459ba39b850 msgid "Start receiving data once, trigger an interrupt when data is received." msgstr "" -#: ../../en/module/lora.rst:115 5b95d0629ab14006b6c2c0b62e2765db +#: ../../en/module/lora.rst:115 7fed6574e5fd45fea90232d0de50910d msgid "|start_recv.png|" msgstr "" -#: ../../en/refs/module.lora.ref:10 2d9b5cc5d80f478288ad340423f8cdaa +#: ../../en/refs/module.lora.ref:10 63cc6253a4034727a48098fce0c65b67 msgid "start_recv.png" msgstr "" -#: ../../en/module/lora.rst:119 e064e90a12aa47cea4addb20ba2de472 +#: ../../en/module/lora.rst:119 d05c5adf40a34d6d8af123014c2cae8d msgid "Set the IRQ callback function." msgstr "" -#: ../../en/module/lora.rst:122 0dec294711fb40fcae36a8a94d16579d +#: ../../en/module/lora.rst:122 7fb839f5668e4a24a8ded2a71208d67c msgid "" "The callback function. The function should accept one argument, which is " "the received data." msgstr "" -#: ../../en/module/lora.rst:126 c95104548d694d17a65193cd76f28548 +#: ../../en/module/lora.rst:126 ba38118c7ce7425f9ed2ed04b03fbe7e msgid "|set_irq_callback.png|" msgstr "" -#: ../../en/refs/module.lora.ref:11 8bd55b05fd584eab98dfc89a815c675a +#: ../../en/refs/module.lora.ref:11 766bf99f95a646a199e3ed59f95e1ae9 msgid "set_irq_callback.png" msgstr "" -#: ../../en/module/lora.rst:130 a49bd8d83e574cebb77415fabb852a9b +#: ../../en/module/lora.rst:130 9f0330fead3f41dd8c27a863b88a2b8b msgid "Set the modem to standby mode." msgstr "" -#: ../../en/module/lora.rst:136 b13e110bc23942c2a890874efc871ed5 +#: ../../en/module/lora.rst:136 577608766cc6476baddde09842aceb76 msgid "|standby.png|" msgstr "" -#: ../../en/refs/module.lora.ref:12 078fe3096c5d4c3988ad9db9e3cbf6aa +#: ../../en/refs/module.lora.ref:12 5255f0be3b634ddf8aa55e4bf2f513e5 msgid "standby.png" msgstr "" -#: ../../en/module/lora.rst:140 220e218f9fde47cbaf07c2edbca1dcac +#: ../../en/module/lora.rst:140 53fb9a1cbfb24ac7a441d1e7ceac9224 msgid "Set the modem to sleep mode." msgstr "" -#: ../../en/module/lora.rst:146 1cd81525c7694b72be0213b7c07b08b5 +#: ../../en/module/lora.rst:146 3cb52349270944a6b425132255ddcb82 msgid "|sleep.png|" msgstr "" -#: ../../en/refs/module.lora.ref:13 8fe737cc4fbe4da0b342f4228a47de40 +#: ../../en/refs/module.lora.ref:13 2b125805c7bb4194852e9969facdf12c msgid "sleep.png" msgstr "" -#: ../../en/module/lora.rst:150 73943497e423472690fddc7596137725 +#: ../../en/module/lora.rst:150 8c2e362799cf403db1617ac69853ff0a msgid "Check if the IRQ has been triggered." msgstr "" -#: ../../en/module/lora.rst:156 6772b6d19dad4a00a532145a16fecfe1 +#: ../../en/module/lora.rst:156 65ef958558de4f68a1032a9189731eb3 msgid "|irq_triggered.png|" msgstr "" -#: ../../en/refs/module.lora.ref:14 7522d5793a31412d86de4ff665742ec0 +#: ../../en/refs/module.lora.ref:14 7eda6abcab4842ada3cb9d44678540e7 msgid "irq_triggered.png" msgstr "" -#: ../../en/module/lora.rst:161 3066c2f713ad45be9c20fa43dfbbbc29 +#: ../../en/module/lora.rst:161 fcda3f2fa4c1493bb2091941c2cd06c3 msgid "Constants" msgstr "" -#: ../../en/module/lora.rst:166 f143b63c676f4727baa97f0ed350e7bd +#: ../../en/module/lora.rst:166 431a4a2c31604a60b36b28159c680e7a msgid "Select the LoRa frequency band." msgstr "" -#: ../../en/module/lora.rst:171 21bd3eb3ee35406088ae12d42bee390f +#: ../../en/module/lora.rst:171 a96be03e1aa54cc2ac793a504a9ab085 msgid "Valid bandwidth" msgstr "" +#~ msgid "|example_tx.svg|" +#~ msgstr "" + +#~ msgid "example_tx.svg" +#~ msgstr "" + +#~ msgid "|example_rx.svg|" +#~ msgstr "" + +#~ msgid "example_rx.svg" +#~ msgstr "" + diff --git a/docs/locales/zh_CN/LC_MESSAGES/module/lora_sx1262.po b/docs/locales/zh_CN/LC_MESSAGES/module/lora_sx1262.po index f9862e10..e661e5aa 100644 --- a/docs/locales/zh_CN/LC_MESSAGES/module/lora_sx1262.po +++ b/docs/locales/zh_CN/LC_MESSAGES/module/lora_sx1262.po @@ -9,7 +9,7 @@ msgid "" msgstr "" "Project-Id-Version: UIFlow2 Programming Guide \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-02-20 18:12+0800\n" +"POT-Creation-Date: 2025-04-03 10:58+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language: zh_CN\n" @@ -20,50 +20,58 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.16.0\n" -#: ../../en/module/lora_sx1262.rst:2 8dad6a34e33c47d3993d3e85cf9ab10a +#: ../../en/module/lora_sx1262.rst:2 909ab2a439064ba180605c1e7223de89 msgid "LoRa868 v1.2 Module" msgstr "" -#: ../../en/module/lora_sx1262.rst:8 f6d14ea49abb4de8ad79401d2378f895 +#: ../../en/module/lora_sx1262.rst:8 65ebb5aae0ac424e887e01feac13320d msgid "" "The LoRa868 v1.2 Module is part of the M5Stack stackable module series. " "It is a LoRa communication module that operates at a 900MHz frequency and" " utilizes the SX1262 chip solution." -msgstr "LoRa868 v1.2 模块是 M5Stack 堆叠模块系列的一部分,属于 LoRa 通信模块,工作在 900MHz 频率,采用 SX1262 芯片解决方案。" +msgstr "" +"LoRa868 v1.2 模块是 M5Stack 堆叠模块系列的一部分,属于 LoRa 通信模块,工作在 900MHz 频率,采用 SX1262 " +"芯片解决方案。" -#: ../../en/module/lora_sx1262.rst:10 042f6386c11a4dd7a4b4d9447774515f +#: ../../en/module/lora_sx1262.rst:10 8f2ff97b70384575b91b08b22a7a8e7e msgid "Support the following products:" msgstr "支持以下产品:" -#: ../../en/module/lora_sx1262.rst:12 02086159dc8d4ef5ace24bcdc333975a -msgid "|Module LoRa868 v1.2|" +#: ../../en/module/lora_sx1262.rst:12 102f08a9024e4e46a8d181b54984516f +msgid "|LoRa868Module v1.2|" +msgstr "" + +#: ../../en/refs/module.lora_sx1262.ref e4d85310e30b4c85b75e1e73187fdb7f +msgid "LoRa868Module v1.2" msgstr "" -#: ../../en/module/lora_sx1262.rst:16 2bcf566bcac54633b14f2c732dfb85bc +#: ../../en/module/lora_sx1262.rst:16 20c3089da354498e99b522894ed02349 msgid "UiFlow2 Example" msgstr "UiFlow2 应用示例:" #: ../../en/module/lora_sx1262.rst:18 ../../en/module/lora_sx1262.rst:53 -#: b3550b6a30894b02b2afc37a08c926d1 cc1ca52064274f1bafcfa6102f11ef3a +#: 49a15d310a9649d58bd842d195bc51e8 860982a5fe6f42348e9c18ae22ffb114 msgid "" "Before using the following examples, please check the DIP switches on the" " module to ensure that the pins used in the example match the DIP switch " "positions. For specific configurations, please refer to the product " "manual page. The SPI configuration has been implemented internally, so " "users do not need to worry about it." -msgstr "在使用以下示例之前,请检查模块上的拨码开关,以确保示例中使用的引脚与拨码开关的位置相匹配。有关具体配置,请参考产品手册页面。SPI 配置已在内部实现。" +msgstr "" +"在使用以下示例之前,请检查模块上的拨码开关,以确保示例中使用的引脚与拨码开关的位置相匹配。有关具体配置,请参考产品手册页面。SPI " +"配置已在内部实现。" #: ../../en/module/lora_sx1262.rst:21 ../../en/module/lora_sx1262.rst:56 -#: 59ead01191964f168e3d13f843cfb447 aed5d291553747239fa0322bf85c2dc4 +#: 25f4529af5f646db8109f7bf58c5e380 318eb348bc2f47fdab1e0a2e21b0c3d7 msgid "Sender" msgstr "发送端" -#: ../../en/module/lora_sx1262.rst:23 d15161c1d7a54fc3955fe8b0fa32db35 +#: ../../en/module/lora_sx1262.rst:23 0d4786701a5b4af59ba71caf6b7b343c msgid "Open the |cores3_lora_sx1262_tx_example.m5f2| project in UiFlow2." msgstr "在 UiFlow2 上打开 |cores3_lora_sx1262_tx_example.m5f2| 项目。" #: ../../en/module/lora_sx1262.rst:25 ../../en/module/lora_sx1262.rst:58 -#: 730bf05c5ca941bbbe1edc1fc796bd04 d85ecb76da6448f796e141813abde91d +#: 287d2c1f6b0c41e6a6f9d65a7dbeeecd d9a6f758da7e4b0b95de744c83b666be msgid "This example sends data every second." msgstr "此示例每秒发送一次数据。" @@ -72,59 +80,59 @@ msgstr "此示例每秒发送一次数据。" #: ../../en/module/lora_sx1262.rst:167 ../../en/module/lora_sx1262.rst:189 #: ../../en/module/lora_sx1262.rst:210 ../../en/module/lora_sx1262.rst:226 #: ../../en/module/lora_sx1262.rst:242 ../../en/module/lora_sx1262.rst:259 -#: 10bf3fe611c3480b809d082f19c65f7b 125463ab7d40480a98ffca42f10bb989 -#: 4e10e38e4fd44548a730ec01281bfead 63b817805eef4bd78e9d7dd3b41bd92b -#: 72574a5df7614800a0ab263935038a81 8b02290e0b614f4bbbf8484eacd7d737 -#: ad830cbd2928467ea913d5ba0b9a41a1 c1ac09ee8f964359af15932691f45260 -#: d3691435ea214f46be48d8637bfa9559 fbe34aa75da94d149e36f7e0eb81194b +#: 3d8f430e2718482d9766194b3d7eb5b7 3e3c5dea533a4b27b749a1c9ed5a72f0 +#: 449565b6e23347e08f75fe5fc2f7a536 544e42871cf14b6b810aacc0cd30aaee +#: 6888fc0c73944feb89cc882c2974b814 8ac9d52c20b44c0693886e6d41a0f5e3 +#: c3b1eff99ae04d3f8b3af94b98ab95ed c786196438154e8a9a9428b167a36e15 +#: df0e05658bd04c6598ba316a0edf7acd eff05456e1cc421facb3b74a875595fc msgid "UiFlow2 Code Block:" msgstr "UiFlow2 代码块:" -#: ../../en/module/lora_sx1262.rst:29 a9dbb4c3be5d40108d15a9ca0d581091 +#: ../../en/module/lora_sx1262.rst:29 f8076474c14a46a9bc135cc5cd180722 msgid "|cores3_lora_sx1262_tx_example.png|" msgstr "" -#: ../../en/refs/module.lora_sx1262.ref:21 d08abf32fc6f4e429e5be4b03fb524b6 +#: ../../en/refs/module.lora_sx1262.ref:21 4a0dcbd56d9c429790c8ab236c3fe362 msgid "cores3_lora_sx1262_tx_example.png" msgstr "" #: ../../en/module/lora_sx1262.rst:31 ../../en/module/lora_sx1262.rst:46 #: ../../en/module/lora_sx1262.rst:66 ../../en/module/lora_sx1262.rst:81 -#: 770a823bd2a4489abea532a3e1e017a5 96ca514298664de0942401e667278932 -#: a49153590f584dca9cf57549a386410a fc0cf2b24fc44815ad0558f09c546bb4 +#: 0948dcfcabbf436298ff39bc6087c31d 1a296bc3de08431f8c0fb5113fc671c6 +#: 5e34dfad13fd4174afb49dd6b18b83bc e94bd28de62e457bad9c76c076de72f4 msgid "Example output:" msgstr "示例输出:" #: ../../en/module/lora_sx1262.rst:33 ../../en/module/lora_sx1262.rst:48 #: ../../en/module/lora_sx1262.rst:68 ../../en/module/lora_sx1262.rst:83 -#: 3543a000af664dbd991b23f8e9bcba76 4fbb7dc2d01f443aa06aa846de5e2a03 -#: 6b79929a799e4d8a9bf91de0f9ce83c4 6ced150579854a66a0a100aa5c50b419 +#: 5a66fd453e654282b0d4f12ea9f1a649 9cd45426bec944708adf04da58fedabd +#: abe69fedd95d4cd9bc74a9cc8ab3989f cbb4f56ea60c45e49c437c58aaf9c6ab msgid "None" msgstr "无" #: ../../en/module/lora_sx1262.rst:36 ../../en/module/lora_sx1262.rst:71 -#: 2cad1fc7de2f4934a59c080ff36c12d1 3241284329e14005a7ad546dadd7d78a +#: 80f73e368e3f45fb9c71d6a8815f5e80 88a688570291428cbf28157844ab3a3f msgid "Receiver" msgstr "接收端" -#: ../../en/module/lora_sx1262.rst:38 c26cf3cd700648e0ac2fa2d4ac0f6f36 +#: ../../en/module/lora_sx1262.rst:38 1e84d3cbe71a44069236ba1903bce9e1 msgid "Open the |cores3_lora_sx1262_rx_example.m5f2| project in UiFlow2." msgstr "在 UiFlow2 上打开 |cores3_lora_sx1262_rx_example.m5f2| 项目。" #: ../../en/module/lora_sx1262.rst:40 ../../en/module/lora_sx1262.rst:73 -#: 5c35c7589b1745d3bb8ace3457b88414 dafcb35990ee4b57a96b8be1c3bbf5cd +#: 4ec17c9b8b774af7a90c9b53e811d78d 790d2371894040d7b3bdc73025419f4b msgid "This example receives and displays data." msgstr "此示例接收并显示数据。" -#: ../../en/module/lora_sx1262.rst:44 1f4fc6bc43e74dfa8bf94d29e41a318e +#: ../../en/module/lora_sx1262.rst:44 4cc9de12bf00404a980a7a113a681969 msgid "|cores3_lora_sx1262_rx_example.png|" msgstr "" -#: ../../en/refs/module.lora_sx1262.ref:20 134673b4e75d49518ef8297457c4f91e +#: ../../en/refs/module.lora_sx1262.ref:20 16aefbc45bab4da2a0e168c240806a3f msgid "cores3_lora_sx1262_rx_example.png" msgstr "" -#: ../../en/module/lora_sx1262.rst:51 ddc377abb9434bf8bf831e70dba0a863 +#: ../../en/module/lora_sx1262.rst:51 3499850b3efc4a08b7035823681f4817 msgid "MicroPython Example" msgstr "MicroPython 应用示例:" @@ -133,333 +141,332 @@ msgstr "MicroPython 应用示例:" #: ../../en/module/lora_sx1262.rst:171 ../../en/module/lora_sx1262.rst:193 #: ../../en/module/lora_sx1262.rst:214 ../../en/module/lora_sx1262.rst:230 #: ../../en/module/lora_sx1262.rst:246 ../../en/module/lora_sx1262.rst:263 -#: 016bc72d6c7d47f18ad77dedcdff15ce 099e73086aef41ceacc77e19b4db9230 -#: 165e5f0959214276b9cc63f0cfde84e0 17330ab8a84446b4b6f7a5d65da5bcbb -#: 17b1bb3d07e84877963a02e61fd3260f 95bc2c4eef164e9d8c9c669dbe120aa9 -#: bdecb366bc574c8ba7ce59da3b1e9d01 c829a4b982e14749a6a3686be4da532b -#: df96edebced54f39a91308c116526e04 e5783a87cc6d46108e77c391a8d23d25 +#: 17883b171c8b4cb39202ae99a60f5c72 35186c67c2734f5d9bff41ecb5f3fbed +#: 402822ec52264abd8c9d570c1acac4be 678ebde175b4436098db80798eb955e0 +#: a584c318918d4205a361a6464c3f1b5c b7c0567c96734d74a8dca609eede5016 +#: c3c221baaa0c4926a1d2268b8de182b6 c5770a2540bb4a7388f37c8731d318f0 +#: db27a69de1774fdcb4f667c964079e47 f7880fc4ea2d4107af4b1a1309ff62d9 msgid "MicroPython Code Block:" msgstr "MicroPython 代码块:" -#: ../../en/module/lora_sx1262.rst:86 8750c93f980449fa875ffba6ca7d3060 +#: ../../en/module/lora_sx1262.rst:86 9356f25aba9b41e49aca0078c5a691af msgid "**API**" msgstr "API参考" -#: ../../en/module/lora_sx1262.rst:90 6a0b782506c44fc9a501a2f75725dc81 +#: ../../en/module/lora_sx1262.rst:90 46b51d3b82b94945ae6010e626f8fbab msgid "class LoRaSx1262Module" msgstr "" -#: ../../en/module/lora_sx1262.rst:104 95e830c8d4b44344b6b385d02d9812e4 +#: ../../en/module/lora_sx1262.rst:104 66bf6def07fe452a990eead3fd99db71 msgid "Create an LoRaSx1262Module object." msgstr "创建一个 LoRaSx1262Module 对象。" -#: ../../en/module/lora_sx1262.rst 2da45f20631f4a6bb61dfa7bdcf272da -#: a67a0dc2895f49c681a875a2bfa66a09 ba01b6eb22744513837f394200d3163c -#: bab9a5c9b1954675bcf5b708aad79f6f +#: ../../en/module/lora_sx1262.rst 2ef70a40ba6a4682949f4c8d4c2f9d63 +#: 7fee3bd18a2f48949b01b40f5d7389a9 895fe81d42924c57a584e3d38470d1f9 +#: fcfc2388373a48169dcead2b4f820fa2 msgid "Parameters" msgstr "" -#: ../../en/module/lora_sx1262.rst:106 65be72cb18e04609a4cd74912d6de9e7 +#: ../../en/module/lora_sx1262.rst:106 ef9c70ede4444bffbefb59f69d173b7d msgid "The Timer ID. Range: 0~3. Default is 0." msgstr "定时器 ID,范围:0~3,默认为 0。" -#: ../../en/module/lora_sx1262.rst:107 31f201ebc3044305814bda91748b7e83 +#: ../../en/module/lora_sx1262.rst:107 ddc74b06162d4cd093a50a8bcdb199ed msgid "(RST) Reset pin number." msgstr "(RST) 复位引脚号。" -#: ../../en/module/lora_sx1262.rst:108 22e161fe62f6419592a9d89af09f766c +#: ../../en/module/lora_sx1262.rst:108 4d7dd9cbc6124caf888518656f5d8578 msgid "(NSS) Chip select pin number." msgstr "(NSS) 片选引脚号。" -#: ../../en/module/lora_sx1262.rst:109 6cb06d4472c54c9c93b167b521eedbf3 +#: ../../en/module/lora_sx1262.rst:109 3f45bc3bc6d944bb91dccbc64132e044 msgid "(IRQ) Interrupt pin number." msgstr "(IRQ) 中断引脚号。" -#: ../../en/module/lora_sx1262.rst:110 7127eaf0b7a14fc69223956bd1a48dc5 +#: ../../en/module/lora_sx1262.rst:110 8258663f9b75401a8fd702dc8c33249d msgid "(BUSY) Busy pin number." msgstr "(BUSY) 忙检查引脚号。" -#: ../../en/module/lora_sx1262.rst:111 055aa306ca6b42719d252542d03c22b9 +#: ../../en/module/lora_sx1262.rst:111 55528275a37b485da286e32f84a6b905 msgid "LoRa RF frequency in KHz, with a range of 850000 KHz to 930000 KHz." msgstr "LoRa 射频通信频率,单位 kHz,范围:850000 KHz ~ 930000 KHz。" -#: ../../en/module/lora_sx1262.rst:112 86c6aefd2b84464fb756b9d4b0a0eb8f +#: ../../en/module/lora_sx1262.rst:112 ec4539092b5a446cb0815303b99bfde7 msgid "" "Bandwidth, options include: - ``\"7.8\"``: 7.8 KHz - ``\"10.4\"``: 10.4 " "KHz - ``\"15.6\"``: 15.6 KHz - ``\"20.8\"``: 20.8 KHz - ``\"31.25\"``: " "31.25 KHz - ``\"41.7\"``: 41.7 KHz - ``\"62.5\"``: 62.5 KHz - " "``\"125\"``: 125 KHz - ``\"250\"``: 250 KHz - ``\"500\"``: 500 KHz" -msgstr "带宽,包括如下: - ``\"7.8\"``: 7.8 KHz - ``\"10.4\"``: 10.4 -KHz - ``\"15.6\"``: 15.6 KHz - ``\"20.8\"``: 20.8 KHz - ``\"31.25\"``: -31.25 KHz - ``\"41.7\"``: 41.7 KHz - ``\"62.5\"``: 62.5 KHz - -``\"125\"``: 125 KHz - ``\"250\"``: 250 KHz - ``\"500\"``: 500 KHz" msgstr "" - -#: ../../en/module/lora_sx1262.rst:112 2d2ef9d218a44547849b526224c620ea + +#: ../../en/module/lora_sx1262.rst:112 771ca968cc0a4194931b91aca3091317 msgid "Bandwidth, options include:" msgstr "带宽,包括如下:" -#: ../../en/module/lora_sx1262.rst:114 fb1c65d6536c462f948edcd85f345c4f +#: ../../en/module/lora_sx1262.rst:114 951a1befa3d143d493396ccadc6bb298 msgid "``\"7.8\"``: 7.8 KHz" msgstr "" -#: ../../en/module/lora_sx1262.rst:115 8900c77d30f84e82af60d71a912db424 +#: ../../en/module/lora_sx1262.rst:115 f039dcbc86f4482fa721b9ee43215168 msgid "``\"10.4\"``: 10.4 KHz" msgstr "" -#: ../../en/module/lora_sx1262.rst:116 60d87abbb87a43a7808577238e5acb58 +#: ../../en/module/lora_sx1262.rst:116 c665eb7fa415443fbd98a045aac6cf69 msgid "``\"15.6\"``: 15.6 KHz" msgstr "" -#: ../../en/module/lora_sx1262.rst:117 21b39406745f4fbfb75d6b042cd6696d +#: ../../en/module/lora_sx1262.rst:117 183ebeaeb203414c9aa4a511980d9a06 msgid "``\"20.8\"``: 20.8 KHz" msgstr "" -#: ../../en/module/lora_sx1262.rst:118 67b4b69b5627410cbf6c340e2dd1103b +#: ../../en/module/lora_sx1262.rst:118 1bb30ade802845d6b6e8e146b6012034 msgid "``\"31.25\"``: 31.25 KHz" msgstr "" -#: ../../en/module/lora_sx1262.rst:119 f6679b51266d4d75b54f0f7b10f62e59 +#: ../../en/module/lora_sx1262.rst:119 e29969ee2ff44f27a7640fce4be7e184 msgid "``\"41.7\"``: 41.7 KHz" msgstr "" -#: ../../en/module/lora_sx1262.rst:120 5079b965409e4d7d851ed467259d2b34 +#: ../../en/module/lora_sx1262.rst:120 0c25db03f62841ad978b20cc6cd67028 msgid "``\"62.5\"``: 62.5 KHz" msgstr "" -#: ../../en/module/lora_sx1262.rst:121 57c03f54c1424c5785714b7cc565edab +#: ../../en/module/lora_sx1262.rst:121 4ca6f0f82829404195a5ba115648f3ef msgid "``\"125\"``: 125 KHz" msgstr "" -#: ../../en/module/lora_sx1262.rst:122 ad34e683875e4f678f21e6498c9aaaaa +#: ../../en/module/lora_sx1262.rst:122 55f9a62d93c84f808876637565a48a9f msgid "``\"250\"``: 250 KHz" msgstr "" -#: ../../en/module/lora_sx1262.rst:123 861b19a8756a45ddbc6e848187a4fc17 +#: ../../en/module/lora_sx1262.rst:123 17299775c067496a99b66c8c6bc7367f msgid "``\"500\"``: 500 KHz" msgstr "" -#: ../../en/module/lora_sx1262.rst:124 467e08b08d5a4f12b3905ad51fa391dc +#: ../../en/module/lora_sx1262.rst:124 04e14cd97d104f1484750337dbc5fa81 msgid "" "Spreading factor, range from 7 to 12. Higher spreading factors allow " "reception of weaker signals but with slower data rates." msgstr "扩频因子,范围从 7 到 12。较高的扩展因子可以接收较弱的信号,但数据传输速率较慢。" -#: ../../en/module/lora_sx1262.rst:125 771e2c09256d4478b83a49904bcc99c0 +#: ../../en/module/lora_sx1262.rst:125 f02f612f3b47436f997f5c10f62ad629 msgid "" "Forward Error Correction (FEC) coding rate expressed as 4/N, with a range" " from 5 to 8." msgstr "前向纠错(FEC)编码率以 4/N 的形式表示,范围从 5 到 8。" -#: ../../en/module/lora_sx1262.rst:126 22fc81d59207413dbeed56e041e67dae +#: ../../en/module/lora_sx1262.rst:126 5f9ee3f81f514890b067941c9c6c313c msgid "Length of the preamble sequence in symbols, range from 0 to 255." msgstr "前导符长度,范围 0~255。" -#: ../../en/module/lora_sx1262.rst:127 b008d1922d3f4bb4a6f437567938844d +#: ../../en/module/lora_sx1262.rst:127 bda069dab7b445bc96372487e8718fbd msgid "Sync word to mark the start of the data frame, default is 0x12." msgstr "同步字,用于标记数据帧的开始,默认值是 0x12。" -#: ../../en/module/lora_sx1262.rst:128 8f9f44985c3142b696e57c6c7615aa97 +#: ../../en/module/lora_sx1262.rst:128 d6dda6a7dc4b42368b6f4e307903fed5 msgid "Output power in dBm, range from -9 to 22." msgstr "输出功率以 dBm 为单位,范围从 -9 到 22。" -#: ../../en/module/lora_sx1262.rst:132 7608a351f59a404c85b43a0e6c2e3520 +#: ../../en/module/lora_sx1262.rst:132 4d95734b4ec6495f9576a8bc5e6ab86f msgid "|init.png|" msgstr "" -#: ../../en/refs/module.lora_sx1262.ref:8 475321d9220e4fe3acb9dbecb17e94b9 +#: ../../en/refs/module.lora_sx1262.ref:8 3f76cef93d7a430397693b1430a5f8ec msgid "init.png" msgstr "" -#: ../../en/module/lora_sx1262.rst:144 57007da3f0d04a7f9a8331c6f67827f1 +#: ../../en/module/lora_sx1262.rst:144 4a9d84909c7240b99e18815870e9cb63 msgid "Set the interrupt callback function to be executed on IRQ." msgstr "设置中断回调函数。" -#: ../../en/module/lora_sx1262.rst:146 11240bc99c9c4df1803bc49aac093510 +#: ../../en/module/lora_sx1262.rst:146 8cbb4953b1794a10982e770e72d8fb32 msgid "" "The callback function to be invoked when the interrupt is triggered. The " "callback should not take any arguments and should return nothing." msgstr "当中断触发时调用的回调函数。回调函数不应接受任何参数,并且不应返回任何值。" -#: ../../en/module/lora_sx1262.rst:149 dab20089f55c40cb9d23340f8bcd9b61 +#: ../../en/module/lora_sx1262.rst:149 14c55fcc8b104975a6b1537573226fcd msgid "Call `start_recv()` to begin receiving data." msgstr "调用 `start_recv()` 开始接收数据。" -#: ../../en/module/lora_sx1262.rst:153 73e4ffda5a1e4765a5729fde30e9587a +#: ../../en/module/lora_sx1262.rst:153 2510464b9ce844a1acf0ce914f3db6ca msgid "|set_irq_callback.png|" msgstr "" -#: ../../en/refs/module.lora_sx1262.ref:12 868eed11304c4bbb85509e25a2f63e91 +#: ../../en/refs/module.lora_sx1262.ref:12 bfd193b2c7424c3c93229f600068c574 msgid "set_irq_callback.png" msgstr "" -#: ../../en/module/lora_sx1262.rst:163 985038d847364df8b2747c87b46b5542 +#: ../../en/module/lora_sx1262.rst:163 f8d70fce93084bd5982bcaf3f948e76f msgid "Start receive data." msgstr "开始接收数据。" -#: ../../en/module/lora_sx1262.rst:165 6f7aa9b73b304787a76c263faace561a +#: ../../en/module/lora_sx1262.rst:165 2a8eb07d42e04b5ab07942c298374146 msgid "This method initiates the process to begin receiving data." msgstr "该方法启动接收数据。" -#: ../../en/module/lora_sx1262.rst:169 e2e6ecef5a124b9a8882f3fdfe403d93 +#: ../../en/module/lora_sx1262.rst:169 746dcce8bd2f4ad3a07f555e95899214 msgid "|start_recv.png|" msgstr "" -#: ../../en/refs/module.lora_sx1262.ref:9 e98a28dc84cf458a971eda17d35db41b +#: ../../en/refs/module.lora_sx1262.ref:9 7236859b1d644950968fa84820744fe7 msgid "start_recv.png" msgstr "" -#: ../../en/module/lora_sx1262.rst:179 6354206e47234c1eb7c3102c69308115 +#: ../../en/module/lora_sx1262.rst:179 087cceefd5b54e92847d6d8f5227cb7e msgid "Receive data." msgstr "接收数据。" -#: ../../en/module/lora_sx1262.rst:181 c8672b22d2784b42affab092bba87983 +#: ../../en/module/lora_sx1262.rst:181 2e173c85673243cd9ca78943d59ee87a msgid "Timeout in milliseconds (optional). Default is None." msgstr "超时时间(单位:ms,可选),默认为 None。" -#: ../../en/module/lora_sx1262.rst:182 d71c5103a49249178c4b8542c38f8c8f +#: ../../en/module/lora_sx1262.rst:182 108618e127ce4877abdc5eeaaee6e2ba msgid "Length of the data to be read. Default is 0xFF." msgstr "要读取的数据长度,默认为 0xFF。" -#: ../../en/module/lora_sx1262.rst:183 876c3ed415004b06a1edc3eae81095b1 +#: ../../en/module/lora_sx1262.rst:183 5835db152cf940b8bdafef8ebd095866 msgid "An instance of `RxPacket` (optional) to reuse." msgstr "一个 RxPacket 实例(可选参数),用于重用。" -#: ../../en/module/lora_sx1262.rst 5f2953ad4f024c84960997701e812987 -#: 78569a8a77aa452ba7a2f34337f60eef e50361a945a64a8dbe497d175b3b133b +#: ../../en/module/lora_sx1262.rst 2f082ea9d73342bf9d01bc649cced612 +#: 3671e37a4fb840abb930b3a0f575e680 fab78bfa39304c9089a1c96b35b1f26a msgid "Returns" msgstr "" -#: ../../en/module/lora_sx1262.rst:184 23eb8378046a432b92e04193d1456f98 +#: ../../en/module/lora_sx1262.rst:184 ffd83e5ad2314ba985f711d00c95466f msgid "Received packet instance" msgstr "接收数据实例。" -#: ../../en/module/lora_sx1262.rst 2349de07f80c4665ab86d4ab99460b9f -#: 796d64ec3b134b8f9321007abee60b74 c14e8e5f2c9d4d6ab946f916aac55875 +#: ../../en/module/lora_sx1262.rst 996640002eb249c7849340c66a0897c0 +#: a60a39f6f62c4a2fb683de6b88932585 ccd2c0660f0a4f1e973c9f67c6682ac6 msgid "Return type" msgstr "" -#: ../../en/module/lora_sx1262.rst:187 ce4892b86e0e4f419e87f7a2962dfb36 +#: ../../en/module/lora_sx1262.rst:187 75445498032645bbbc2ae5acfe34617a msgid "" "Attempt to receive a LoRa packet. Returns `None` if timeout occurs, or " "returns the received packet instance." msgstr "尝试接收一个 LoRa 数据包。如果发生超时,则返回 None,否则返回接收到的数据包实例。" -#: ../../en/module/lora_sx1262.rst:191 42a9b9a3a0c54fdaa5a67d3892820cad +#: ../../en/module/lora_sx1262.rst:191 66e9953d9c4b49e7aba505495b2bd74b msgid "|recv.png|" msgstr "" -#: ../../en/refs/module.lora_sx1262.ref:10 96dea2290e264304a3c74304e77881d5 +#: ../../en/refs/module.lora_sx1262.ref:10 cdc5a492038c49f5aa65096d457a30bb msgid "recv.png" msgstr "" -#: ../../en/module/lora_sx1262.rst:201 106970f6abd543d58912b789a3015409 +#: ../../en/module/lora_sx1262.rst:201 a87eb389cb8b415a850cb36d36d97124 msgid "Send data." msgstr "发送数据。" -#: ../../en/module/lora_sx1262.rst:203 30520a58463f42de95b7cdf6109aea73 +#: ../../en/module/lora_sx1262.rst:203 611f0120876d48dfbf8663ce391f83ed msgid "The data to be sent." msgstr "要发送的数据。" -#: ../../en/module/lora_sx1262.rst:204 2fb8e9abff6c4f63a43b2ede4f9a09a4 +#: ../../en/module/lora_sx1262.rst:204 7625691270a6477589142282c1746209 msgid "" "The timestamp in milliseconds when to send the data (optional). Default " "is None." msgstr "发送数据的时间戳(以 ms 为单位,可选参数)。默认为 None。" -#: ../../en/module/lora_sx1262.rst:205 b327db9a285b45db96a3f2ea1948975e +#: ../../en/module/lora_sx1262.rst:205 6b1a15b2822c495ebf3f8663514213b8 msgid "" "Returns a timestamp (result of `time.ticks_ms()`) indicating when the " "data packet was sent." msgstr "返回一个时间戳(time.ticks_ms() 的结果),表示数据包发送的时间。" -#: ../../en/module/lora_sx1262.rst:208 e31fe749e19249d0bae3da89c3e577d7 +#: ../../en/module/lora_sx1262.rst:208 880f2a8cc6a046a5ab758cd530f3d1c8 msgid "Send a data packet and return the timestamp after the packet is sent." msgstr "发送一个数据包并返回数据包发送后的时间戳。" -#: ../../en/module/lora_sx1262.rst:212 1ed503f26cf14d988e511f33951f3b9a +#: ../../en/module/lora_sx1262.rst:212 164bd9529d2d4376bfaaf4dbccfa5ea8 msgid "|send.png|" msgstr "" -#: ../../en/refs/module.lora_sx1262.ref:15 dd89929d50c14c778f838de588090192 +#: ../../en/refs/module.lora_sx1262.ref:15 f5fd52c7cdbf43c48d993591419027ee msgid "send.png" msgstr "" -#: ../../en/module/lora_sx1262.rst:222 b1f7311d10a44484bcf3ac683137f88f +#: ../../en/module/lora_sx1262.rst:222 320259e97c8e4af1b0e7be18366e0d79 msgid "Set module to standby mode." msgstr "设置模块为待机模式。" -#: ../../en/module/lora_sx1262.rst:224 f4e3ef0d967b435cba45da49e7de8f1a +#: ../../en/module/lora_sx1262.rst:224 ee61bdde37cb487d9760750402645704 msgid "Puts the LoRa module into standby mode, consuming less power." msgstr "设置 LoRa 模块为待机模式,降低功耗。" -#: ../../en/module/lora_sx1262.rst:228 e4e9ae541215494ebfbfc3cca0f3e5bc +#: ../../en/module/lora_sx1262.rst:228 658ba4b4c3864f008296504d952c023a msgid "|standby.png|" msgstr "" -#: ../../en/refs/module.lora_sx1262.ref:17 4c2ea99b10634bbbb38f21822ec1fe99 +#: ../../en/refs/module.lora_sx1262.ref:17 24857b73396b45a493aa5625014eba33 msgid "standby.png" msgstr "" -#: ../../en/module/lora_sx1262.rst:238 4621b0b078a245468275bfdd87774bfd +#: ../../en/module/lora_sx1262.rst:238 9fccc302890e478c9382c94c920bead2 msgid "Put the module to sleep mode." msgstr "设置模块为休眠模式。" -#: ../../en/module/lora_sx1262.rst:240 e3786a770b2d47489a677c4cdd4ac6d8 +#: ../../en/module/lora_sx1262.rst:240 4cdeba7c430b45d08605ddf20cea8009 msgid "Reduces the power consumption by putting the module into deep sleep mode." msgstr "通过将模块置于休眠模式来降低功耗。" -#: ../../en/module/lora_sx1262.rst:244 c30b01db79624a05bea0323064eca862 +#: ../../en/module/lora_sx1262.rst:244 4c51d457314f41c09d16ff3a4050f23a msgid "|sleep.png|" msgstr "" -#: ../../en/refs/module.lora_sx1262.ref:18 288322c8fb834297bbf6a859488ce218 +#: ../../en/refs/module.lora_sx1262.ref:18 5b189b5a0aab4db487d193d7b83cba24 msgid "sleep.png" msgstr "" -#: ../../en/module/lora_sx1262.rst:254 24985a3df4974655a973c95340d3fe12 +#: ../../en/module/lora_sx1262.rst:254 b71c7198ef074f2da3b81c7446e756ed msgid "Check IRQ trigger." msgstr "检查 IRQ 是否触发。" -#: ../../en/module/lora_sx1262.rst:256 5ea75e34642b46efa263d91cadf0189d +#: ../../en/module/lora_sx1262.rst:256 67e86b8b2c76433589535f189b48635c msgid "" "Returns `True` if an interrupt service routine (ISR) has been triggered " "since the last send or receive started." msgstr "如果自上次发送或接收开始以来中断服务例程(ISR)已被触发,则返回 True。" -#: ../../en/module/lora_sx1262.rst:261 eb1dcc9d2f3b4290b27e031ebd845679 +#: ../../en/module/lora_sx1262.rst:261 0a2d1720820049db963a1bcfbf751462 msgid "|irq_triggered.png|" msgstr "" -#: ../../en/refs/module.lora_sx1262.ref:16 43a2072622e543168b9dacd1bc2f5c30 +#: ../../en/refs/module.lora_sx1262.ref:16 5447b337e0e04df8bb03639b7dd9c1f6 msgid "irq_triggered.png" msgstr "" -#: ../../en/module/lora_sx1262.rst:270 45a4a6dcfcf2432ea550e14d6be19711 +#: ../../en/module/lora_sx1262.rst:270 e862112210f240629c3da7254ffc12de msgid "class RxPacket" msgstr "" -#: ../../en/module/lora_sx1262.rst:274 fc79985c43f34633af94f503e22fbb49 +#: ../../en/module/lora_sx1262.rst:274 3da775ff990241f881dff2f4b136f85c msgid "Create an RxPacket object." msgstr "创建一个 RxPacket 对象。" -#: ../../en/module/lora_sx1262.rst:278 02b4f7f407bc4b57a230c65201996fc3 +#: ../../en/module/lora_sx1262.rst:278 da50f30d65f7412f84c832ecee629e4e msgid "Decode the received data." msgstr "解码接收到的数据。" -#: ../../en/module/lora_sx1262.rst:282 b3eda4b7bffa40fd8c3c9661573ac7e3 +#: ../../en/module/lora_sx1262.rst:282 fa7755b2ee9e46bd9f5b13bf9d74f9bc msgid "Timestamp of when the data was received." msgstr "数据接收时的时间戳。" -#: ../../en/module/lora_sx1262.rst:286 b0b06457467d442082f4a254c1f0d56d +#: ../../en/module/lora_sx1262.rst:286 6c084d10a6914703abf0072d0a11e57f msgid "Received signal strength (units: dBm)." msgstr "接收信号强度(单位:dBm)。" -#: ../../en/module/lora_sx1262.rst:290 558d1a6b13844156a9de5809f77d8a7e +#: ../../en/module/lora_sx1262.rst:290 70986fea0a744403a5cf1dea17948f49 msgid "Signal-to-noise ratio (units: dB * 4)." msgstr "信噪比(单位:dB * 4)。" -#: ../../en/module/lora_sx1262.rst:294 ee8ada3af1c84649a11ac8c56d171249 +#: ../../en/module/lora_sx1262.rst:294 51abcfa2a78c4d7f9f310d08f6af4c7f msgid "CRC validity check." msgstr "CRC 校验。" +#~ msgid "|Module LoRa868 v1.2|" +#~ msgstr "" + diff --git a/docs/locales/zh_CN/LC_MESSAGES/quick-reference/usb-mode.po b/docs/locales/zh_CN/LC_MESSAGES/quick-reference/usb-mode.po index e9a184b9..b019072c 100644 --- a/docs/locales/zh_CN/LC_MESSAGES/quick-reference/usb-mode.po +++ b/docs/locales/zh_CN/LC_MESSAGES/quick-reference/usb-mode.po @@ -9,7 +9,7 @@ msgid "" msgstr "" "Project-Id-Version: UIFlow2 Programming Guide \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-01-16 15:35+0800\n" +"POT-Creation-Date: 2025-04-18 15:58+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language: zh_CN\n" @@ -20,153 +20,163 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.16.0\n" -#: ../../en/quick-reference/usb-mode.rst:2 e801dce0a866425085c2ee4ef6568e58 +#: ../../en/quick-reference/usb-mode.rst:2 764b782faa264875bd46cd837acdf21c msgid "USB Mode" msgstr "" -#: ../../en/quick-reference/usb-mode.rst:8 571a98e317e24780bfa79830a47e4623 +#: ../../en/quick-reference/usb-mode.rst:8 3715a9b416ea4f6b9cb525d3e0e59f7c msgid "Switch to USB Mode" msgstr "" -#: ../../en/quick-reference/usb-mode.rst:10 3863125e43744cbe8b9ab282d0e753ba +#: ../../en/quick-reference/usb-mode.rst:10 868d3b3e74154c328d0cefee29f35dc0 msgid "Select the device under ``USB Device`` to enter USB mode." msgstr "" -#: ../../en/quick-reference/usb-mode.rst:12 a06d20baf1fd4d71b55d67c9874b2138 +#: ../../en/quick-reference/usb-mode.rst:12 48a75d47bc4e4ece82c577728c16053a msgid "|usb-mode-1.gif|" msgstr "" -#: ../../en/refs/qr.usb-mode.ref:1 a903cc65c76943caa88224c89d101b4a +#: ../../en/refs/qr.usb-mode.ref:1 ba5c90866b3a4674a2765b8def44851b msgid "usb-mode-1.gif" msgstr "" -#: ../../en/quick-reference/usb-mode.rst:16 2b401561e73f4f99980ae1e140cdb7fe +#: ../../en/quick-reference/usb-mode.rst:16 3204a7f456244815afa5cc90219288a7 msgid "Run the Program" msgstr "" -#: ../../en/quick-reference/usb-mode.rst:18 32efea5c8bbb4d5eb8a649136523a031 +#: ../../en/quick-reference/usb-mode.rst:18 e4fb88ed4b3245a28f59196d57a8e46f msgid "Click the run button to execute the current program." msgstr "" -#: ../../en/quick-reference/usb-mode.rst:20 9df35469168e427f9097e40e91dcbc44 +#: ../../en/quick-reference/usb-mode.rst:20 e9dc5cdd6d0845f5bad73634abec29ea msgid "|usb-mode-2.gif|" msgstr "" -#: ../../en/refs/qr.usb-mode.ref:2 d8499257184548f0a6728fc85a21c307 +#: ../../en/refs/qr.usb-mode.ref:2 11826c6dd61b47799844fc70fbf0262a msgid "usb-mode-2.gif" msgstr "" -#: ../../en/quick-reference/usb-mode.rst:24 6ad58c1f0dd04bf39f97c5585d2e2d0b +#: ../../en/quick-reference/usb-mode.rst:24 ecacab009ec344309f94e1a9398faac7 msgid "Terminate the Program Execution" msgstr "" -#: ../../en/quick-reference/usb-mode.rst:26 cae1f4a43ada4b4590f9870163b0ef5a +#: ../../en/quick-reference/usb-mode.rst:26 ed866400d9024af9a2df65baa0d674ce msgid "Use ``Ctrl + C`` to terminate the execution of the program." msgstr "" -#: ../../en/quick-reference/usb-mode.rst:28 cabde50d5c7a47bb84d44aeb2046f083 +#: ../../en/quick-reference/usb-mode.rst:28 d2944f6588dc4108967d11ae12066b6c msgid "|usb-mode-3.gif|" msgstr "" -#: ../../en/refs/qr.usb-mode.ref:3 f603c7bb01e543dfab0863bc341de390 +#: ../../en/refs/qr.usb-mode.ref:3 90cdbce91dbf47d08991fab9c6c4e0b1 msgid "usb-mode-3.gif" msgstr "" -#: ../../en/quick-reference/usb-mode.rst:32 efca2bae170840eea59a46c09f89642d +#: ../../en/quick-reference/usb-mode.rst:32 c1bd79ab21794906b9d8003a06b1ef95 msgid "Save the Program to the Device and Run" msgstr "" -#: ../../en/quick-reference/usb-mode.rst:34 ae61060d0a4e4034a4788f275a22f828 +#: ../../en/quick-reference/usb-mode.rst:34 85ef444f0144486facbd76031c0e4125 msgid "Click the save button to save the program to the device and run it." msgstr "" -#: ../../en/quick-reference/usb-mode.rst:36 6dc712d87b944b4280a12ecaa9d4bfc7 +#: ../../en/quick-reference/usb-mode.rst:36 7103df4eec6f434997ca4c4923ca6433 msgid "|usb-mode-4.gif|" msgstr "" -#: ../../en/refs/qr.usb-mode.ref:4 2949bbe4e9f84ab093756434ba0a41aa +#: ../../en/refs/qr.usb-mode.ref:4 7ab1156a63934706b973d3c738a916d4 msgid "usb-mode-4.gif" msgstr "" -#: ../../en/quick-reference/usb-mode.rst:40 e655fd0f0f304a20891500a4a4cdd484 +#: ../../en/quick-reference/usb-mode.rst:40 79e13950a9bd49faa3ff6dc981cedcd0 msgid "File Operations" msgstr "" -#: ../../en/quick-reference/usb-mode.rst:42 702b5e54b95949b593d16f8c56c3b6b7 +#: ../../en/quick-reference/usb-mode.rst:42 e4bb638525ec494db4c41e86ca509f9f msgid "" "Click the ``Get File`` button to export the program from the device to " "the computer. Click the ``Delete File`` button to delete the program from" " the device." msgstr "" -#: ../../en/quick-reference/usb-mode.rst:45 31706f417af04337bc350f0329a8dcad +#: ../../en/quick-reference/usb-mode.rst:45 e2aaefd798df4815976f46bd252d46e5 msgid "|usb-mode-5.gif|" msgstr "" -#: ../../en/refs/qr.usb-mode.ref:5 f2b19f4f7e424c24b1d9cfbfe4740bfe +#: ../../en/refs/qr.usb-mode.ref:5 b23c406618754626a0a18917d5fddc42 msgid "usb-mode-5.gif" msgstr "" -#: ../../en/quick-reference/usb-mode.rst:49 7f6b8d717bf34b0d9d8b70a13a87370f +#: ../../en/quick-reference/usb-mode.rst:49 f748740ab9794ccd8a294c9304007a30 msgid "Browse Directories" msgstr "" -#: ../../en/quick-reference/usb-mode.rst:51 4b5278090db74b1cadbd60e3cf831aa8 +#: ../../en/quick-reference/usb-mode.rst:51 2405c1034fc54f4ca834ce8fe61e0565 msgid "" "Double-click with the mouse to enter a directory; single-click on the " "path bar to return to the previous directory level." msgstr "" -#: ../../en/quick-reference/usb-mode.rst:53 46f6897f4abc4ba094beff97c3b5683a +#: ../../en/quick-reference/usb-mode.rst:53 4d7bbeceb7a84bfbba62dabf29ff2fa4 msgid "|usb-mode-6.gif|" msgstr "" -#: ../../en/refs/qr.usb-mode.ref:6 459cc79bcf2a491a83cc34ade8843cc0 +#: ../../en/refs/qr.usb-mode.ref:6 7fe5f98b1a9f4f26820474a099344444 msgid "usb-mode-6.gif" msgstr "" -#: ../../en/quick-reference/usb-mode.rst:57 0c5b318e15414b4a93c436dd88bdb54e +#: ../../en/quick-reference/usb-mode.rst:57 6115fbbcff6041c1b0b695ad4b364dca msgid "Upload an Image to the Device" msgstr "" -#: ../../en/quick-reference/usb-mode.rst:59 9d14b1d95faa4fab906b3862cf83940c +#: ../../en/quick-reference/usb-mode.rst:59 c2153af82d7047388c364907dab10a3f msgid "" "Navigate to a specific directory within the device, click the ``Send File" " to Here`` button, select an image from your computer, and then upload it" " to that directory." msgstr "" -#: ../../en/quick-reference/usb-mode.rst:61 d2f1db524be147c9b6b0760a81cc0888 +#: ../../en/quick-reference/usb-mode.rst:61 016c52151d5d467eb3125b62abb3e459 msgid "Image resources need to be saved under the ``res/img`` directory." msgstr "" -#: ../../en/quick-reference/usb-mode.rst:63 242877b5e9714e8aa5058ba3ed99f624 +#: ../../en/quick-reference/usb-mode.rst:63 0976d18029424afe96faa5b5205243e5 msgid "|usb-mode-7.gif|" msgstr "" -#: ../../en/refs/qr.usb-mode.ref:7 63cd822ac20a4208981e1bc0d7ebc2cd +#: ../../en/refs/qr.usb-mode.ref:7 e95fe07368e145bfa0a0077aea17e644 msgid "usb-mode-7.gif" msgstr "" -#: ../../en/quick-reference/usb-mode.rst:67 81b5a2ad2e4c46e1b04d46b0f5d62618 +#: ../../en/quick-reference/usb-mode.rst:67 9c75c1283f354a31b6821ae616601e93 msgid "Use Images from the Device" msgstr "" -#: ../../en/quick-reference/usb-mode.rst:69 81bea873214e48eea602e1297e313186 +#: ../../en/quick-reference/usb-mode.rst:69 6abf15e6e29749ecbea609f7c55268f1 msgid "" -"Use the |setImage.svg| block to set the filename of the image to be " +"Use the |setImage.png| block to set the filename of the image to be " "displayed, and show the image on the screen." msgstr "" -#: ../../en/refs/qr.usb-mode.ref:10 9ff04352e78a46de8d01280fbc9a5694 -msgid "setImage.svg" +#: ../../en/refs/qr.usb-mode.ref:10 402ed7420fde4df6b2ff1d35b9496351 +msgid "setImage.png" msgstr "" -#: ../../en/quick-reference/usb-mode.rst:71 48c228e757f94512841c5d732a7244e8 +#: ../../en/quick-reference/usb-mode.rst:71 0d20b189792542febb3c601b91b27100 msgid "|usb-mode-8.gif|" msgstr "" -#: ../../en/refs/qr.usb-mode.ref:8 53d64b5797184942b942ed0f97127ada +#: ../../en/refs/qr.usb-mode.ref:8 9f1d6701b54b41a58a0948fe881708bf msgid "usb-mode-8.gif" msgstr "" +#~ msgid "" +#~ "Use the |setImage.svg| block to set " +#~ "the filename of the image to be" +#~ " displayed, and show the image on " +#~ "the screen." +#~ msgstr "" + +#~ msgid "setImage.svg" +#~ msgstr "" + diff --git a/docs/locales/zh_CN/LC_MESSAGES/software/umqtt.default.po b/docs/locales/zh_CN/LC_MESSAGES/software/umqtt.default.po index 3bfac550..eac01f0f 100644 --- a/docs/locales/zh_CN/LC_MESSAGES/software/umqtt.default.po +++ b/docs/locales/zh_CN/LC_MESSAGES/software/umqtt.default.po @@ -9,7 +9,7 @@ msgid "" msgstr "" "Project-Id-Version: UIFlow2 Programming Guide \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-01-16 15:35+0800\n" +"POT-Creation-Date: 2025-04-18 15:58+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language: zh_CN\n" @@ -20,95 +20,95 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.16.0\n" -#: ../../en/software/umqtt.default.rst:4 9c5e660e7f394e9980d03e31378ceb8f +#: ../../en/software/umqtt.default.rst:4 a66e31c6c8aa4393903275df94ac8860 msgid "umqtt.default" msgstr "" -#: ../../en/software/umqtt.default.rst:8 9cf24b0b433d4a509877a4a7c470f13e +#: ../../en/software/umqtt.default.rst:8 ab67ec74972f4f5096536e5de93765f0 msgid "" "`umqtt.default` rewrites the :py:meth:`subscribe` method and supports ca " "file." msgstr "`umqtt.default` 重写了 :py:meth:`subscribe` 方法并支持 ca 文件。" -#: ../../en/software/umqtt.default.rst:11 7dd4d12996a34d3fa2775dca25c5e652 +#: ../../en/software/umqtt.default.rst:11 e120fc43b2f541e2b251f43878f7a91f msgid "Micropython Example:" msgstr "" -#: ../../en/software/umqtt.default.rst:18 3a85d239870b4ca5a596bc0586c5cf93 +#: ../../en/software/umqtt.default.rst:18 4ed270b76162421ca3b264eba6bae1ee msgid "UIFLOW2 Example:" msgstr "" -#: ../../en/software/umqtt.default.rst:20 f614e076c3d842998dfe31cb5d588eae -msgid "|example.svg|" +#: ../../en/software/umqtt.default.rst:20 bbae5d5317f648509ce50801bebeafdf +msgid "|example.png|" msgstr "" -#: ../../en/refs/software.umqtt.default.ref:1 0e9d27ee32b64426aa67cfb53d60fb63 -msgid "example.svg" +#: ../../en/refs/software.umqtt.default.ref:1 b3130552ab29452f8e83d51cc9dd0014 +msgid "example.png" msgstr "" -#: ../../en/software/umqtt.default.rst:25 aec6254d0f814c44a29337c1a7ec6de2 +#: ../../en/software/umqtt.default.rst:25 93f5021ceee84e8d92a394d2f12e192a msgid "|mqtts_cores3_example.m5f2|" msgstr "" -#: ../../en/software/umqtt.default.rst:29 e5a30daf3fea4aaebb5ef81426f2949c +#: ../../en/software/umqtt.default.rst:29 821dbc35d81a4dc4849b2492f415455f msgid "Constructors" msgstr "" -#: ../../en/software/umqtt.default.rst:33 c8c2a0018b8b4908933dad5063cc63ff +#: ../../en/software/umqtt.default.rst:33 e1472179ce094330bc025d34f4aa02e6 #, fuzzy msgid "Create an MQTTClient object." msgstr "MQTTClient 对象" -#: ../../en/software/umqtt.default.rst 01e5b989a36e479ca88b817ed1a59e6a -#: 409e4aae63c04063aa33ed6594335e85 52df5a2fb33c4054906fb1a19101cf4e -#: b07de7a88bc8460d9c4eb7a9be8de29b +#: ../../en/software/umqtt.default.rst 68ad5af425b64cdc909a89da46b6e4cc +#: 822232e9a65a4f6e958d98b9d8259784 adb7998b1c1c496c889b8ad352307d33 +#: ed65c3dc312640e6a5d825ce947cc9e8 msgid "Parameters" msgstr "" -#: ../../en/software/umqtt.default.rst:35 aa13b50722e84d51af2315a62b22fc1a +#: ../../en/software/umqtt.default.rst:35 5142d64093bf4024b3b093ae98002c7d msgid "the unique client id string used when connecting to the broker." msgstr " 连接到MQTT broker时使用的唯一客户端 ID 字符串" -#: ../../en/software/umqtt.default.rst:37 e10797bdcfe64a28931c6d2ddaa84388 +#: ../../en/software/umqtt.default.rst:37 c38cedf3dbd74ab6b07ae0f77013a04e msgid "the hostname or IP address of the remote broker." msgstr "远程MQTT broker的主机名或 IP 地址。" -#: ../../en/software/umqtt.default.rst:38 d74189c2321d4d8f8ae18201e60768f1 +#: ../../en/software/umqtt.default.rst:38 2d5e792b20134dc08de6f7b19653807d msgid "the network port of the server host to connect to." msgstr "要连接的服务器主机的网络端口。" -#: ../../en/software/umqtt.default.rst:39 16b73cbbb0a84ff38ddb028648f32fad +#: ../../en/software/umqtt.default.rst:39 5c0ca891d3754751b07a0a5e8f569607 msgid "a username for broker authentication." msgstr "用于MQTT broker身份验证的用户名。" -#: ../../en/software/umqtt.default.rst:41 50a22e1c288e4168a70b52308d848a50 +#: ../../en/software/umqtt.default.rst:41 c13e2129e39744b8b9153eb420198ed9 msgid "a password for broker authentication." msgstr "用于MQTT broker身份验证的密码。" -#: ../../en/software/umqtt.default.rst:43 992336b4f0c542ddb83c11ba8a1a52b7 +#: ../../en/software/umqtt.default.rst:43 6cb7e3d410894ce6a4c131be22cf7936 msgid "" "maximum period in seconds allowed between communications with the broker." " If no other messages are being exchanged, this controls the rate at " "which the client will send ping messages to the broker." msgstr "与代理通信之间允许的最长时间(以秒为单位)。如果没有交换其他消息,这将控制客户端向代理发送 ping 消息的速率。" -#: ../../en/software/umqtt.default.rst:47 dd0d8cb4f841476aa0ce5dbb9304f59a +#: ../../en/software/umqtt.default.rst:47 e6dc3740877f45f1bf60b902b62ef8b0 msgid "Whether to use ssl." msgstr "是否使用ssl。" -#: ../../en/software/umqtt.default.rst:48 5e602f18ece649f8bc39476d7e07b7f8 +#: ../../en/software/umqtt.default.rst:48 21c8bfdf73c84f1ca05e0cd6a7d3672f msgid "Some parameters required to initiate an ssl connection." msgstr "启动 ssl 连接所需的一些参数。" -#: ../../en/software/umqtt.default.rst 954fdd7efb694fb69f5135aee9e64ea5 +#: ../../en/software/umqtt.default.rst c581f0f7eaf7427cb177829d62d92cc2 msgid "Returns" msgstr "" -#: ../../en/software/umqtt.default.rst:49 bf04128c713b4ec1b2fc1c658bfe40a0 +#: ../../en/software/umqtt.default.rst:49 763f1c5a50f64393b4cfb0cce530e1df msgid "MQTTClient object" msgstr "MQTTClient 对象" -#: ../../en/software/umqtt.default.rst d80e55f70d834bdea1f6e97fd285f449 +#: ../../en/software/umqtt.default.rst d197b65775ad4d72a91eeea4719ecbe1 msgid "Return type" msgstr "" @@ -121,36 +121,36 @@ msgstr "" #: ../../en/software/umqtt.default.rst:129 #: ../../en/software/umqtt.default.rst:160 #: ../../en/software/umqtt.default.rst:182 -#: ../../en/software/umqtt.default.rst:192 16a54de0b82f4a888d8d09aaef28bc54 -#: 274bb44eb7b84ebea80b03b45c11fa98 3814581f4d3847a2893b82e2802083f9 -#: 535adc6b54244d20a2a676031ad55a40 69b90d19179d4aff8f13709a13707d8a -#: 7e8ff5b27d3147f58799f76ebcce11b9 8c17139e0eb34f4f81eb2f2f74fb996d -#: a7320a1cc1f644b8ba21644076d8bf9b d1154118984548ac81bcc597ac4260f0 -#: fd7043b220124dfabf61d4acc2c7fbe7 +#: ../../en/software/umqtt.default.rst:192 2b0d42a3d9974ddaafb6214b89305f36 +#: 3818c37a1d4c4600bceef189130954fc 477c69bdba2a44b7b90f63485561d2de +#: 7c8c4ee4d89d4e12831ef71fe1da64a5 978e192c814441eaa0f5779227e61910 +#: 99a258e4c7b04c4eacda246a162751ac b18dcdcb27a64f5cbb9c3e8a9cbe42ad +#: c99320177259425cb9b95c388dd31aab cb065cdf233f480ebdf710e1ef8cd8e9 +#: cba4170e0faf4307996d4d6d661263de msgid "UIFLOW2:" msgstr "" -#: ../../en/software/umqtt.default.rst:54 22df473ca110439ca25263b6c115d365 +#: ../../en/software/umqtt.default.rst:54 fb8c022908ed472684d6eeaf46de6e43 msgid "|init.png|" msgstr "" -#: ../../en/refs/software.umqtt.default.ref:3 372c90f9e11d471f8cde51c7a59cfa72 +#: ../../en/refs/software.umqtt.default.ref:3 c44cceab7dac4cd590989d9a88463039 msgid "init.png" msgstr "" -#: ../../en/software/umqtt.default.rst:56 f7f0f13e2f514ed8bad43949ddfc1ef4 +#: ../../en/software/umqtt.default.rst:56 940c24d2b4c04a77af5bce981e8202d8 msgid "|init_ssl.png|" msgstr "" -#: ../../en/refs/software.umqtt.default.ref:5 e2adac1b2f4c42d2afa37194dcfc29fc +#: ../../en/refs/software.umqtt.default.ref:5 2ce1d383214e415689ee1d19ecd1c2b2 msgid "init_ssl.png" msgstr "" -#: ../../en/software/umqtt.default.rst:60 5eb080f522a349ddb3753a9d894d9e4f +#: ../../en/software/umqtt.default.rst:60 0e10fc037e324e07a495c10c8ed7c272 msgid "Methods" msgstr "" -#: ../../en/software/umqtt.default.rst:64 a55896c0e192412ca9962bccb22ce349 +#: ../../en/software/umqtt.default.rst:64 40e64231bdd04100b1aa304dd8edc43d msgid "" "Connect to a server. Returns True if this connection uses persisten " "session stored on a server (this will be always False if " @@ -159,152 +159,152 @@ msgstr "" "连接到服务器。如果此连接使用存储在服务器上的持久会话,则返回 True (如果使用clean_session=True 参数(默认),则始终为 " "False )。" -#: ../../en/software/umqtt.default.rst:70 5cc634e420394bdfaf4928ae8031d98c +#: ../../en/software/umqtt.default.rst:70 0694c38039b54b34871ae0c864e69e92 msgid "|connect.png|" msgstr "" -#: ../../en/refs/software.umqtt.default.ref:7 36350ac8f7ad4a2c9879eb454c3f719b +#: ../../en/refs/software.umqtt.default.ref:7 f25732a504d645e990a49db5a0fdbb63 msgid "connect.png" msgstr "" #: ../../en/software/umqtt.default.rst:75 -#: ../../en/software/umqtt.default.rst:84 7ce95e859b7447c7b2deecbd4aef4ea0 -#: a28781ce65844e4d806a14ac9c66d67d +#: ../../en/software/umqtt.default.rst:84 b156af1453b5405ca6fe50c228a89ea8 +#: c659c312c4924d9fafc2e5094355311d msgid "Disconnect from a server, release resources." msgstr "断开与服务器的连接,释放资源。" -#: ../../en/software/umqtt.default.rst:79 631c8083a6b94f569fa9e2a0a6edd004 +#: ../../en/software/umqtt.default.rst:79 0d9f8c5ef40b4e5cbac028cbab2497c1 msgid "|disconnect.png|" msgstr "" -#: ../../en/refs/software.umqtt.default.ref:9 bd68fb3aa61548b8b5ee5b7c825382f8 +#: ../../en/refs/software.umqtt.default.ref:9 892276de09634c298ff73620c2a7b04e msgid "disconnect.png" msgstr "" -#: ../../en/software/umqtt.default.rst:88 055f1a80568a49f789298845aec2b02e +#: ../../en/software/umqtt.default.rst:88 3e5f81f45fd14d00beef6f3a6ee8b05e msgid "|reconnect.png|" msgstr "" -#: ../../en/refs/software.umqtt.default.ref:11 2efd3232faf948acba3d2cab63e4f7e4 +#: ../../en/refs/software.umqtt.default.ref:11 8b473925850648b0adedddd5e6194e19 msgid "reconnect.png" msgstr "" -#: ../../en/software/umqtt.default.rst:93 1b9a01d12b61424c8e752d837b78d501 +#: ../../en/software/umqtt.default.rst:93 b86ba6bda9e24adab54383cbbf3bc00c msgid "" "Ping server (response is processed automatically by " ":py:meth:`wait_msg()`)." msgstr "Ping 服务器(响应由 :py:meth:`wait_msg()` 自动处理)。" #: ../../en/software/umqtt.default.rst:97 -#: ../../en/software/umqtt.default.rst:184 353a487512824b63abf4bbf5280ece9a -#: fad20e1e2fda4759aeefda2cb914adc5 +#: ../../en/software/umqtt.default.rst:184 8f3897d79ce8440c941c79fe4fa9bd00 +#: a1aea52a0cf14b0d9e1bb740fdda4185 msgid "None" msgstr "" -#: ../../en/software/umqtt.default.rst:102 24c2a54b91cc46b8a0357d8196e648f7 +#: ../../en/software/umqtt.default.rst:102 26c46aa9e8ae4a789eb5582d1e407c04 msgid "Publish a message." msgstr "发布消息。" -#: ../../en/software/umqtt.default.rst:104 1bd8c061393c43a5980bad83d4503e4c +#: ../../en/software/umqtt.default.rst:104 83a9a69fac98492e81fd0a24ad131e7c msgid "the topic that the message should be published on." msgstr "应发布消息的主题。" -#: ../../en/software/umqtt.default.rst:106 ad892c4956464c62973b91a24f7c7ed4 +#: ../../en/software/umqtt.default.rst:106 5687a9fce96e41eaa8b5186849cedc52 msgid "the message to send as a will." msgstr "实际要发送的消息。" #: ../../en/software/umqtt.default.rst:108 -#: ../../en/software/umqtt.default.rst:156 12a165eed7534136b8262f2ab23ff3d4 -#: daf7bbd5ef8f4f3492bda690eebb39ea +#: ../../en/software/umqtt.default.rst:156 e6ffa35768fa41edbc5de67e89f94019 +#: f54a54b79a0643d3a8b39c131bb1780f msgid "" "if set to True, the will message will be set as the \"last " "will\"/retained message for the topic." msgstr "如果设置为 True ,则遗嘱消息将被设置为该主题的“最后遗嘱”/保留消息。" -#: ../../en/software/umqtt.default.rst:110 90d22e78e2ef4980a19ba3a5a7d4fd31 +#: ../../en/software/umqtt.default.rst:110 8ed0015b5634453591810bc7ae39c56a msgid "the quality of service level to use" msgstr "使用的服务质量级别" -#: ../../en/software/umqtt.default.rst:114 f2162737b52543bc8955bc72b8ab6be1 +#: ../../en/software/umqtt.default.rst:114 ce2a76acdd904861a337df3567e17bfa msgid "|publish.png|" msgstr "" -#: ../../en/refs/software.umqtt.default.ref:13 b372be2c90a14701a281a73b50621c77 +#: ../../en/refs/software.umqtt.default.ref:13 056afb33542046c7b964244dddec6bb9 msgid "publish.png" msgstr "" -#: ../../en/software/umqtt.default.rst:119 e0f72f060ace42a087bc4c6e2c70234d +#: ../../en/software/umqtt.default.rst:119 5c2ba4d26a3245c9a08ac0281afa9e92 msgid "Subscribe to a topic." msgstr "订阅主题。" -#: ../../en/software/umqtt.default.rst:121 3e6e842537e04106843b15bd98918a72 +#: ../../en/software/umqtt.default.rst:121 6759c4fd8e684d94a80a1bb9ae46430b msgid "a string specifying the subscription topic to subscribe to." msgstr "指定要订阅的订阅主题的字符串。" -#: ../../en/software/umqtt.default.rst:123 79cc3452940744b9899cdbe66a67643a +#: ../../en/software/umqtt.default.rst:123 31e279e1e55c41658edfcf0c47bdff82 msgid "" "called when a message has been received on a topic that the client " "subscribes to and the message does match an existing topic filter " "callback." msgstr "当收到有关客户端订阅的主题的消息并且该消息与现有主题过滤器回调匹配时调用。" -#: ../../en/software/umqtt.default.rst:126 cbb0d57ecc5c4a1f9ccacf52b338a4d6 +#: ../../en/software/umqtt.default.rst:126 5d92784c83ae4f3a9971b76f07e216fa msgid "the desired quality of service level for the subscription. Defaults to 0." msgstr "订阅所需的服务质量级别。 默认为 0。" #: ../../en/software/umqtt.default.rst:131 -#: ../../en/software/umqtt.default.rst:162 3fd2cd0e805f4976a1f126bcfdd90513 -#: d9a222fa2cf64adeb34f2f9a31f6b6f5 +#: ../../en/software/umqtt.default.rst:162 3fd9062e3bcd4acbb50b164eb7d46e4a +#: f18d113418634474a5f4d5549164a9ad msgid "|subscribe.png|" msgstr "" -#: ../../en/refs/software.umqtt.default.ref:15 6b62f755d1cc4edf945b1da92cf44b7c -#: 7c6ba30fe46e49e5894c9da4759a4bca +#: ../../en/refs/software.umqtt.default.ref:15 6d2f6ac967cc45b38f0e899a7a53df6b +#: 8c7b1d0cd1214d51b11fd5359c00c8d5 msgid "subscribe.png" msgstr "" -#: ../../en/software/umqtt.default.rst:133 b359ba2ccd944438be2c0767ca847d23 +#: ../../en/software/umqtt.default.rst:133 92dbcc4b54a246e1884276da81950833 msgid "An handler showing a message has been received::" msgstr "显示已收到消息的处理程序::" -#: ../../en/software/umqtt.default.rst:139 b116bf87e0584c39be82926dac8cef17 +#: ../../en/software/umqtt.default.rst:139 93680928ed704e39afd561da2c5d07e2 #, fuzzy msgid "" "On uiflow2, you can get the **topic** and **message** of the current " "handler through |get_topic.png| and |get_msg.png|." msgstr "在 uiflow2 上,可以通过 |get_topic.svg| 和 |get_msg.svg| 获取当前 handler " -#: ../../en/refs/software.umqtt.default.ref:17 2979e8114c9549c7895a778964ca1684 +#: ../../en/refs/software.umqtt.default.ref:17 66a7b5c57c0f4d3e878c432cffd3b6fc msgid "get_topic.png" msgstr "" -#: ../../en/refs/software.umqtt.default.ref:19 8725eb90a0e94feb8d2e54d69fcf840b +#: ../../en/refs/software.umqtt.default.ref:19 25051e05cd04491bae994604e3345470 msgid "get_msg.png" msgstr "" -#: ../../en/software/umqtt.default.rst:147 29babd64ec8d418a8d265314cd0fd555 +#: ../../en/software/umqtt.default.rst:147 825b7295f49d4ba9be52721146473680 msgid "Should be called before :py:meth:`connect()`." msgstr "应该在 :py:meth:`connect()` 之前调用。" -#: ../../en/software/umqtt.default.rst:149 5d94fe3e080941ecb0bdd8bb53574bcd +#: ../../en/software/umqtt.default.rst:149 8d59d6d422304ce7a577fc2e5bcd6c0f msgid "Set MQTT \"last will\" message." msgstr "设置 MQTT 遗嘱消息。" -#: ../../en/software/umqtt.default.rst:151 92ff310693774439914ded5761539efa +#: ../../en/software/umqtt.default.rst:151 3dc86ef0061e4c5bb0f5d3591ae59dc0 msgid "the topic that the will message should be published on." msgstr "将发布意愿消息的主题。" -#: ../../en/software/umqtt.default.rst:153 225ad99e09174f998af9de5a772f2f7f +#: ../../en/software/umqtt.default.rst:153 2718bafa53634e718cc1dfaa45432da7 msgid "" "the message to send as a will. If not given, or set to None a zero length" " message will be used as the will." msgstr " 如果设置为 True ,则遗嘱消息将被设置为该主题的“最后遗嘱”/保留消息。" -#: ../../en/software/umqtt.default.rst:158 cb93ee28ed114c41a166c8fa6da550a9 +#: ../../en/software/umqtt.default.rst:158 ae72509c7dec4d80876cc9ebf1fd8911 msgid "the quality of service level to use for the will." msgstr "用于意愿的服务质量水平。" -#: ../../en/software/umqtt.default.rst:169 2fbc95b37b8d4192b0e7132e0e445fae +#: ../../en/software/umqtt.default.rst:169 8f2acff033d544c79cd015d25e1c0f84 msgid "" ":py:meth:`wait_msg()` and :py:meth:`check_msg()` are \"main loop " "iteration\" methods, blocking and non-blocking version. They should be " @@ -317,7 +317,7 @@ msgstr "" "如果您没有任何其他前台任务要执行(即您的应用程序仅对订阅的MQTT 消息做出反应),则应在循环中定期调用 " ":py:meth:`wait_msg()` ,如果您还处理其他前台任务,则使用 :py:meth:`check_msg ()` 。" -#: ../../en/software/umqtt.default.rst:176 5a66b6fd52af4d3f9891827ec625cfb2 +#: ../../en/software/umqtt.default.rst:176 473607d9aac14457b84458a2246cf6f8 msgid "" "Note that you don't need to call :py:meth:`wait_msg()` / " ":py:meth:`check_msg()` if you only publish messages, never subscribe to " @@ -326,21 +326,21 @@ msgstr "" "请注意,如果您只发布消息,从不订阅消息,则不需要调用 :py:meth:`wait_msg()` / :py:meth:`check_msg()`" " 。" -#: ../../en/software/umqtt.default.rst:180 ff010c1c7e2c4303900f9fbb49071231 +#: ../../en/software/umqtt.default.rst:180 9e26210a1cba42d98102e4329ca2e73c msgid "Wait for a server message." msgstr "等待服务器消息。" -#: ../../en/software/umqtt.default.rst:189 d165231f89a940b4ada787708f8a2f21 +#: ../../en/software/umqtt.default.rst:189 3dd7d026706d4d8c81ad301126587b45 msgid "" "Check if there's pending message from server. If yes, process the same " "way as :py:meth:`wait_msg()`, if not, return immediately." msgstr "检查是否有来自服务器的待处理消息。如果是, 则与 :py:meth:`wait_msg()` 处理方式" -#: ../../en/software/umqtt.default.rst:194 a345b694db8e4f4d8c1a9ef677e6d0f8 +#: ../../en/software/umqtt.default.rst:194 6be388d2d87f424991e122eee9a20868 msgid "|wait_msg.png|" msgstr "" -#: ../../en/refs/software.umqtt.default.ref:23 3f20643b6fa046b1a2ac4ea0bfd626c7 +#: ../../en/refs/software.umqtt.default.ref:23 e8796e7aa78c408ba113b7867a7edcbd msgid "wait_msg.png" msgstr "" @@ -407,3 +407,9 @@ msgstr "" #~ msgid "wait_msg.svg" #~ msgstr "" +#~ msgid "|example.svg|" +#~ msgstr "" + +#~ msgid "example.svg" +#~ msgstr "" + diff --git a/docs/locales/zh_CN/LC_MESSAGES/system/power.po b/docs/locales/zh_CN/LC_MESSAGES/system/power.po index 9896f23f..58e4e068 100644 --- a/docs/locales/zh_CN/LC_MESSAGES/system/power.po +++ b/docs/locales/zh_CN/LC_MESSAGES/system/power.po @@ -9,7 +9,7 @@ msgid "" msgstr "" "Project-Id-Version: UIFlow2 Programming Guide \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-01-16 15:35+0800\n" +"POT-Creation-Date: 2025-04-18 15:58+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language: zh_CN\n" @@ -20,45 +20,44 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.16.0\n" -#: ../../en/system/power.rst:2 cbdfbd4bc8024940af50987f9e84c8a4 +#: ../../en/system/power.rst:2 86ca4e10441243408cd23f327c9e735c msgid "Power" msgstr "" -#: ../../en/system/power.rst:8 47cd4253073840288f586fa548ebf9f8 +#: ../../en/system/power.rst:8 59f39dc644324750a1369a632045fbee msgid "class Power" msgstr "" -#: ../../en/system/power.rst:12 4ba23ea170744310b7a74ae251f62eeb -msgid "Methods of the Power Class depend on ``M5.begin()`` |M5.begin.svg|." +#: ../../en/system/power.rst:12 86251ec7013f460ca2876d7e6e959a3d +msgid "Methods of the Power Class depend on ``M5.begin()`` |M5.begin.png|." msgstr "" -#: ../../en/refs/system.ref:1 c8770a17b9f640c2861407c2d6c2916d -#: cf68bdc5a0c041d89a160150ff8fda4b -msgid "M5.begin.svg" +#: ../../en/refs/system.ref:1 752299d28f3441da93d3ee3d47967a00 +msgid "M5.begin.png" msgstr "" -#: ../../en/system/power.rst:14 9e5deb8096c64dc6ba018b1e5f13daf8 +#: ../../en/system/power.rst:14 f896310c37174aa4b46c778e720f8123 msgid "" "All methods calling the Power object need to be placed after " -"``M5.begin()`` |M5.begin.svg|." +"``M5.begin()`` |M5.begin.png|." msgstr "" -#: ../../en/system/power.rst:18 1996fb16fb874fb994f8cc9534417e72 +#: ../../en/system/power.rst:18 68d18c1ceedf4771b468729212f2d861 msgid "Methods" msgstr "" -#: ../../en/system/power.rst:22 d5baf98c96124769b8ff1e18e1524129 +#: ../../en/system/power.rst:22 fdbcdcece25b423e8c0e2e6cb062045e msgid "Set power output of the external ports." msgstr "" -#: ../../en/system/power.rst:24 e94cc77573b94712bd560c0ab3ad13d2 +#: ../../en/system/power.rst:24 8e14f30016ea4fc39bbca36d620ac4b9 msgid "" "When ``enable`` is True, the power output of the external ports is in " "output mode. When ``enable`` is False, the power output of the external " "ports is in input mode." msgstr "" -#: ../../en/system/power.rst:28 22d3a86c61c5471aa0430d66f2941d6a +#: ../../en/system/power.rst:28 bacc63b1aee845129296e3b9a8c0c9f0 msgid "" "``port`` is the port number, optional values are available in :ref:`class" " PORT`, only valid for M5Stack Station." @@ -73,348 +72,469 @@ msgstr "" #: ../../en/system/power.rst:180 ../../en/system/power.rst:189 #: ../../en/system/power.rst:198 ../../en/system/power.rst:207 #: ../../en/system/power.rst:216 ../../en/system/power.rst:226 -#: 0d25154ffc464b03b3acd16d8f7a843f 23e230de7fb04e3e9bf4715cb035d2d4 -#: 2932c310159246c7872297be76b0f0ac 4145cff1f8f24a50b1c275206fff2a71 -#: 5090864c7805419098ed01a516680db9 50b9fb9b2234400a954081fc84c48365 -#: 5fe01144a96f418e957a08ad7bc304c5 7dfd0d8293024feba33b008b9bd32577 -#: 84a120ed77874addbdbd50251dff8f98 8cef0287f5cf4c66a5491ed7ecbcd5d6 -#: b7c099d302364534932d363b90eaea93 c0981750114449498a64e6ee0b849eb1 -#: c61e4c7ca2ba46099bd559c72c1ecfd9 e53a4ad2d48b45b1ab9b963932f4aa4b -#: f2028f0d862f4b9a83c17c32150d9bba f211e2fdd2614ef98d56f294914f2bcc -#: f826ae217dfd49a5ad18c46476190b08 fcf706d9b1c340169041c8375f1c6862 +#: bbbedf7e920a46f6baa8f5d9e22659c4 msgid "UIFLOW2:" msgstr "" -#: ../../en/system/power.rst:33 cdbbfced4ca84b1881eabed176449f9b -msgid "|setExtOutput1.svg| |setExtOutput2.svg|" +#: ../../en/system/power.rst:33 076007f8c9d44559ba21f1d0c477df44 +msgid "|setExtOutput1.png| |setExtOutput2.png|" msgstr "" -#: ../../en/refs/system.power.ref:1 8b80ec2931f349248d6b022bac356c0b -msgid "setExtOutput1.svg" +#: ../../en/refs/system.power.ref:1 118bead411a144c0b32bedbfdabe9688 +msgid "setExtOutput1.png" msgstr "" -#: ../../en/refs/system.power.ref:2 a6f584a85e7e4d0a859dad63acdffd73 -msgid "setExtOutput2.svg" +#: ../../en/refs/system.power.ref:2 8fbeeff820cc473d9eba8c8eeffe7580 +msgid "setExtOutput2.png" msgstr "" -#: ../../en/system/power.rst:39 dd60bc2117aa455c83500b454e4bb92f +#: ../../en/system/power.rst:39 83c753dbb09d4c348eed7c53fe94ea8c msgid "Get power output of the external ports." msgstr "" -#: ../../en/system/power.rst:41 f434abe75bd040d991ed9dddd9ee108e +#: ../../en/system/power.rst:41 d931855ca7a44a8287c4f32a9732cb3a msgid "" "Returns ``True`` if the power output of the external ports is in output " "mode. Returns ``False`` if the power output of the external ports is in " "input mode." msgstr "" -#: ../../en/system/power.rst:47 4e948f0735fb451498d4647627ebf45d -msgid "|getExtOutput.svg|" +#: ../../en/system/power.rst:47 a1fecfaa732d4a858b67ff6812341372 +msgid "|getExtOutput.png|" msgstr "" -#: ../../en/refs/system.power.ref:4 11b9be0c3bde494099dd62bef8bdd10a -msgid "getExtOutput.svg" +#: ../../en/refs/system.power.ref:4 4f1e3da551f54e47a60464a4c5e846f0 +msgid "getExtOutput.png" msgstr "" -#: ../../en/system/power.rst:52 3735334bfdba4531846417bc4e5bcd2c +#: ../../en/system/power.rst:52 af5920e35eb2495591e4004e8baaf0db msgid "Set power output of the main USB port." msgstr "" -#: ../../en/system/power.rst:54 b481644a4aa94fdc8b70a2872bed9f60 +#: ../../en/system/power.rst:54 9984551995b3480cb1b011802f5cd0c2 msgid "" "When ``enable`` is True, the power output of the main USB port is in " "output mode. When ``enable`` is False, the power output of the main USB " "port is in input mode." msgstr "" -#: ../../en/system/power.rst:60 ebd9d5353797402c90a5522868059ec2 -msgid "|setUsbOutput.svg|" +#: ../../en/system/power.rst:60 71dc0e4e4bba426f9138a2a1a4054c42 +msgid "|setUsbOutput.png|" msgstr "" -#: ../../en/refs/system.power.ref:6 8eef1d6ae0d84b9e85b1dc3fb56a6f43 -msgid "setUsbOutput.svg" +#: ../../en/refs/system.power.ref:6 8dafd9f25b5b4b45a92d614aff43c632 +msgid "setUsbOutput.png" msgstr "" -#: ../../en/system/power.rst:65 4801861cdfd04ac3a3901b213d382e01 +#: ../../en/system/power.rst:65 ca492f3d596544a8865ae7e40fc1cbcc msgid "Get power output of the main USB port." msgstr "" -#: ../../en/system/power.rst:67 52e63099d9c44b9cb6ec222f1d4f2629 +#: ../../en/system/power.rst:67 c5f96f20af18484a84fd6885baa2b805 msgid "" "Returns ``True`` if the power output of the main USB port is in output " "mode. Returns ``False`` if the power output of the main USB port is in " "input mode." msgstr "" -#: ../../en/system/power.rst:72 465b8fdb129f4b8f9de67c9ab190532b -msgid "|getUsbOutput.svg|" +#: ../../en/system/power.rst:72 4e5011434a2648ebaa9c857ac9537c4e +msgid "|getUsbOutput.png|" msgstr "" -#: ../../en/refs/system.power.ref:8 b4d2cbefbd39421fb295ff616f9c09a2 -msgid "getUsbOutput.svg" +#: ../../en/refs/system.power.ref:8 6c258a31bd724cb78cad9e5e02c4819d +msgid "getUsbOutput.png" msgstr "" -#: ../../en/system/power.rst:77 ca7f2a78f4bd49039a894da76dae1b76 +#: ../../en/system/power.rst:77 db65a363c2db4c64b0c504baba922391 msgid "Turn on/off the power LED." msgstr "" -#: ../../en/system/power.rst:79 e44c88eb849142ce88344bd5045b8b0d +#: ../../en/system/power.rst:79 7a33f44121ac43c8a877d2f86730a0f7 msgid "" "``brightness`` is the brightness value, ranging from 0 to 255. 0 is off, " "255 is the maximum brightness." msgstr "" -#: ../../en/system/power.rst:84 991f2024aa774826946ec75d2907b376 -msgid "|setLed.svg|" +#: ../../en/system/power.rst:84 0408130bdfdc4fc9bd58da42f70789bc +msgid "|setLed.png|" msgstr "" -#: ../../en/refs/system.power.ref:10 4c68e1017cab44b4a1193e3c58d28392 -msgid "setLed.svg" +#: ../../en/refs/system.power.ref:10 bb3ae8453ab94d738e289b6eaf0db32a +msgid "setLed.png" msgstr "" -#: ../../en/system/power.rst:89 95743bb243ee4e9193aff66377c5059a +#: ../../en/system/power.rst:89 c81ee3e941274a2793c125a369745970 msgid "Turn off all power." msgstr "" -#: ../../en/system/power.rst:93 efbeb5791c5c4aadbbfd5cd8280d3380 -msgid "|powerOff.svg|" +#: ../../en/system/power.rst:93 d71ac019fd84483cb3cbb4014d41c72f +msgid "|powerOff.png|" msgstr "" -#: ../../en/refs/system.power.ref:12 ed3240d5671540c58c08d15b97fb3948 -msgid "powerOff.svg" +#: ../../en/refs/system.power.ref:12 e51da17ef3884167a8dc9227e5e1341f +msgid "powerOff.png" msgstr "" -#: ../../en/system/power.rst:100 423c3d6367214a44a8259be5f59bf2c7 +#: ../../en/system/power.rst:100 25d34622558747d38b78d6c4be654bcb msgid "sleep and timer boot. The boot condition can be specified by the argument." msgstr "" -#: ../../en/system/power.rst:102 53135e5dd09e429ab163272df36af1c0 +#: ../../en/system/power.rst:102 e6063bd363f346b58ec5b0e83574d16c msgid "``seconds``: Range is 1 - 15300, in seconds." msgstr "" -#: ../../en/system/power.rst:104 3dcdf616168c49768965e20c8ed36149 +#: ../../en/system/power.rst:104 68a213e2235445fd868282eee6d7903b msgid "``minutes``: Range is 0 - 59, in minutes." msgstr "" -#: ../../en/system/power.rst:106 0d2ee343f2684d2285bfcc2039e35ad3 +#: ../../en/system/power.rst:106 135f42cd925146039e5d5a93f95f96a3 msgid "``hours``: Range is 0 - 23, in hours." msgstr "" -#: ../../en/system/power.rst:108 b8e077d9aa74482ca0fea06e40ea5a54 +#: ../../en/system/power.rst:108 2bff7a190faf4b7dae35bfe38fc4cf33 msgid "``date``: Range is 1 - 31, in days." msgstr "" -#: ../../en/system/power.rst:110 b766a36c4c5d475083e95baa465d4a36 +#: ../../en/system/power.rst:110 0300f3c421184461b6b37d5ba99b21aa msgid "``weekDay``: Range is 0 - 6." msgstr "" -#: ../../en/system/power.rst:114 f3150432b2a140e6a2ab776d128c7242 -msgid "|timerSleep1.svg| |timerSleep2.svg| |timerSleep3.svg|" +#: ../../en/system/power.rst:114 47f4cd08c499476e8e6df0436156dad1 +msgid "|timerSleep1.png| |timerSleep2.png| |timerSleep3.png|" msgstr "" -#: ../../en/refs/system.power.ref:14 17e5f1f0db754e55aa7d2e9fa8662cc6 -msgid "timerSleep1.svg" +#: ../../en/refs/system.power.ref:14 1e813fef8d784122ba87c6070b132a16 +msgid "timerSleep1.png" msgstr "" -#: ../../en/refs/system.power.ref:15 3125155c0e5347de8231955e971c8a23 -msgid "timerSleep2.svg" +#: ../../en/refs/system.power.ref:15 9c98047b5c144c19b886521a89f24ace +msgid "timerSleep2.png" msgstr "" -#: ../../en/refs/system.power.ref:16 4b4d9db79e2a43fdbd33dfb00b8759f2 -msgid "timerSleep3.svg" +#: ../../en/refs/system.power.ref:16 289b682def3e44b190e0de5d049810b5 +msgid "timerSleep3.png" msgstr "" -#: ../../en/system/power.rst:121 5b48bd749d5849ce95aa66d4fa3a9ce4 +#: ../../en/system/power.rst:121 7193fb84cdf641e4bcb39d76f934670b msgid "ESP32 deepsleep." msgstr "" #: ../../en/system/power.rst:123 ../../en/system/power.rst:136 -#: 1a384a1734654c26af16e631c4f191af bf242a62fccd43568cf2d3d9d1862e5d +#: a00f6a2ff5da47aaa705d1c6b9416f47 msgid "``micro_seconds``: Number of micro seconds to wakeup." msgstr "" #: ../../en/system/power.rst:125 ../../en/system/power.rst:138 -#: ca74f05a4ea04196af9c2271989fd7e0 daed40208e694b0899f22f62544789b4 +#: d5b8ae85f1ed4fd19c90e78c86972cc2 msgid "``wakeup``: Whether to wake up." msgstr "" -#: ../../en/system/power.rst:129 b7ca28a2bcc5469f8e17ec632c6638c2 -msgid "|deepSleep.svg|" +#: ../../en/system/power.rst:129 b5f14c0fc6384d24a1656e4f84a399f9 +msgid "|deepSleep.png|" msgstr "" -#: ../../en/refs/system.power.ref:19 c3ba75c8a8a2463ebceff263822da166 -msgid "deepSleep.svg" +#: ../../en/refs/system.power.ref:19 31a5714081a54f54a2993e5baa9ace5f +msgid "deepSleep.png" msgstr "" -#: ../../en/system/power.rst:134 6083cad0649e482480b26e9dc8e311ba +#: ../../en/system/power.rst:134 d7c15f3d799b43b1b7aec1ceb3720268 msgid "ESP32 lightsleep." msgstr "" -#: ../../en/system/power.rst:142 8f41e7817be243c2a228ed641a76db8a -msgid "|lightSleep.svg|" +#: ../../en/system/power.rst:142 d10051e646ee4cd7bd9b89f53817183f +msgid "|lightSleep.png|" msgstr "" -#: ../../en/refs/system.power.ref:21 00cad023a8864285a133eb0787620f4d -msgid "lightSleep.svg" +#: ../../en/refs/system.power.ref:21 36d36b42bcea4e62bbbd3f794671a427 +msgid "lightSleep.png" msgstr "" -#: ../../en/system/power.rst:147 b0c7e31af6cb499db31d6293f94209b9 +#: ../../en/system/power.rst:147 2842f8402ac34ee1987eaf2f1d786176 msgid "Get the remaining battery power percentage. Returns a value between 0-100." msgstr "" -#: ../../en/system/power.rst:151 7e20530f4e0c400d98294b52978ee4a2 -msgid "|getBatteryLevel.svg|" +#: ../../en/system/power.rst:151 31c4d188bf3a473797a0bdaf83e634d2 +msgid "|getBatteryLevel.png|" msgstr "" -#: ../../en/refs/system.power.ref:23 c4c6c6d0f5c2404195dc12adf8203d5d -msgid "getBatteryLevel.svg" +#: ../../en/refs/system.power.ref:23 3e2d9a188c2a48998acf6d727b84a8cb +msgid "getBatteryLevel.png" msgstr "" -#: ../../en/system/power.rst:156 3a8e45d4135141bbb22afe45c3d6d719 +#: ../../en/system/power.rst:156 c220897ab4f04cad8478882287c61b2e msgid "Set battery charging enable." msgstr "" -#: ../../en/system/power.rst:160 6d2115a8322843488aa13ad627fa8507 -msgid "|setBatteryCharge.svg|" +#: ../../en/system/power.rst:160 76c44bd8ddb84536bba0ef85b3625a89 +msgid "|setBatteryCharge.png|" msgstr "" -#: ../../en/refs/system.power.ref:25 5b66a2caae464f6aad6e6d75918f60b0 -msgid "setBatteryCharge.svg" +#: ../../en/refs/system.power.ref:25 9efe9e3c0e9e48c58461543967090d72 +msgid "setBatteryCharge.png" msgstr "" -#: ../../en/system/power.rst:165 2e7a71218b5b472b8dac24144f3f7881 +#: ../../en/system/power.rst:165 f163b4c4d3df4b73b5637d696188e7e3 msgid "Set battery charge current." msgstr "" -#: ../../en/system/power.rst:167 3584f4e065c24f4ba36521f325e7bb3c +#: ../../en/system/power.rst:167 d12967e9ec714840a49a5f50053c224e msgid "``max_mA``: Range is 0-2000, in milliamps." msgstr "" -#: ../../en/system/power.rst:171 f9e97a302f124cca902f702635590081 -msgid "|setChargeCurrent.svg|" +#: ../../en/system/power.rst:171 f94b8cff9d1e41798bc03641aceff4d1 +msgid "|setChargeCurrent.png|" msgstr "" -#: ../../en/refs/system.power.ref:27 43736de91e434a2eacdf7911e8254c96 -msgid "setChargeCurrent.svg" +#: ../../en/refs/system.power.ref:27 92a0f5bc14eb43fb9d18aa7d1b42fea0 +msgid "setChargeCurrent.png" msgstr "" -#: ../../en/system/power.rst:176 af76d1f3976047faae9680a70ad30fc2 +#: ../../en/system/power.rst:176 8aa803863b8142cc802ab43e23eab7ea msgid "Set battery charge voltage." msgstr "" -#: ../../en/system/power.rst:178 13f8b21733e447a2afeb9b63bf3b6df2 +#: ../../en/system/power.rst:178 7dfbabdc7e9b4329b1d41eeb3b355896 msgid "``max_mV``: Range is 4100-4600, in millivolts." msgstr "" -#: ../../en/system/power.rst:182 a21428ad420e47aaa3e06d4434b58c4e -msgid "|setChargeVoltage.svg|" +#: ../../en/system/power.rst:182 89ad4b34dd484ed3acb6a2934f28578b +msgid "|setChargeVoltage.png|" msgstr "" -#: ../../en/refs/system.power.ref:29 b2a3cfb409f545a6a03ab163e307bbac -msgid "setChargeVoltage.svg" +#: ../../en/refs/system.power.ref:29 670b668b9dd249cdab60bcc8b2e1fc0e +msgid "setChargeVoltage.png" msgstr "" -#: ../../en/system/power.rst:187 fdc91cec8c704607985759bd78562142 +#: ../../en/system/power.rst:187 52994ca550d84c60938527fa56789bfb msgid "Get whether the battery is currently charging or not." msgstr "" -#: ../../en/system/power.rst:191 2e7d573c886a43a59cbec2d79aada4ce -msgid "|isCharging.svg|" +#: ../../en/system/power.rst:191 a270f0949dbb4ab98eae8aacb93fb798 +msgid "|isCharging.png|" msgstr "" -#: ../../en/refs/system.power.ref:31 4576eccda62643f9805bc9e4bd94cb77 -msgid "isCharging.svg" +#: ../../en/refs/system.power.ref:31 df57f9a708cd4eae8354b2adb63a68b9 +msgid "isCharging.png" msgstr "" -#: ../../en/system/power.rst:196 0e542fa83c604b94bc35f15d743dd512 +#: ../../en/system/power.rst:196 67689e10260d47dfa5f02e9c19615720 msgid "Get battery voltage. Unit is millivolts." msgstr "" -#: ../../en/system/power.rst:200 a3a28980f388427386fd5bb14162758c -msgid "|getBatteryVoltage.svg|" +#: ../../en/system/power.rst:200 0cabd5fcdb51448da3f8bc2c472cb8c4 +msgid "|getBatteryVoltage.png|" msgstr "" -#: ../../en/refs/system.power.ref:33 d3ab1e03cc23482ab7b311cf74dc77f3 -msgid "getBatteryVoltage.svg" +#: ../../en/refs/system.power.ref:33 ebca79ba6158455cb9debbaec7ae20eb +msgid "getBatteryVoltage.png" msgstr "" -#: ../../en/system/power.rst:205 201e2113499d4bfc8690a7c2bc380bcc +#: ../../en/system/power.rst:205 a9e8f009f61f4f30b19d37ea2105c658 msgid "Get battery current. Unit is milliamps." msgstr "" -#: ../../en/system/power.rst:209 8659e8ffe3a5411482093ba8fe92abc6 -msgid "|getBatteryCurrent.svg|" +#: ../../en/system/power.rst:209 d96ea7c9558243b9bf5e6eebcfeeddfc +msgid "|getBatteryCurrent.png|" msgstr "" -#: ../../en/refs/system.power.ref:35 9c6bd7c7ef5642df8120a26fba7408c1 -msgid "getBatteryCurrent.svg" +#: ../../en/refs/system.power.ref:35 74e8afb9d5e64bbdb2339a0434a474f7 +msgid "getBatteryCurrent.png" msgstr "" -#: ../../en/system/power.rst:214 3bfaedc84daa4b6c90ae480c72d3a96f +#: ../../en/system/power.rst:214 9c35e1179ca445c0938ec9809ce21e89 msgid "Get Power Key Press condition." msgstr "" -#: ../../en/system/power.rst:218 875f5e85a9ba46729b09af97a97f4e3b -msgid "|getKeyState.svg|" +#: ../../en/system/power.rst:218 fc35d424640a467dbf31fc2347feb60c +msgid "|getKeyState.png|" msgstr "" -#: ../../en/refs/system.power.ref:37 1b88519aefb147fb877fbf4a23200300 -msgid "getKeyState.svg" +#: ../../en/refs/system.power.ref:37 87426412ec8c43d8a5f48a28cbd4efe1 +msgid "getKeyState.png" msgstr "" -#: ../../en/system/power.rst:222 d98d1b1f710e4fc28eae67a88de18493 +#: ../../en/system/power.rst:222 426f5072ff8d42fa9052e3d31cb2be52 msgid "Operate the vibration motor." msgstr "" -#: ../../en/system/power.rst:224 2de57d69aff44e51b5f7bbf909013b95 +#: ../../en/system/power.rst:224 21f6117173f24ca5b6b7fef7476edeb3 msgid "``level``: Vibration intensity, ranging from 0-255." msgstr "" -#: ../../en/system/power.rst:228 ea53f926b12249ad9d7700e965ce6958 -msgid "|setVibration.svg|" +#: ../../en/system/power.rst:228 7132cf052c3a496db9e9ffd50408ffc9 +msgid "|setVibration.png|" msgstr "" -#: ../../en/refs/system.power.ref:39 124d305b99ea42a2b8511b335ffca2aa -msgid "setVibration.svg" +#: ../../en/refs/system.power.ref:39 5bde08f32be74e1699c5ab2646e472dd +msgid "setVibration.png" msgstr "" -#: ../../en/system/power.rst:234 357d199629e44ceea640fa981afa4803 +#: ../../en/system/power.rst:234 c3bc53e5dfa4494dba280a421fe2f131 msgid "class PORT" msgstr "" -#: ../../en/system/power.rst:237 98d997d3726542a6ad28b5ce37466c9d +#: ../../en/system/power.rst:237 4430c03c2a8047f4b8a8b137d309a48e msgid "Constants" msgstr "" -#: ../../en/system/power.rst:241 abf29bf13fbd48dc8ec766c6ac2121f9 +#: ../../en/system/power.rst:241 212501dd591942d381812a1bbf001a20 msgid "Port A." msgstr "" -#: ../../en/system/power.rst:246 62a6fa8098c64982b03f30b0c89dc1f5 +#: ../../en/system/power.rst:246 08e7c2e5c95c474086576e7c721f3768 msgid "Port B1." msgstr "" -#: ../../en/system/power.rst:251 962a335756304cba889fc0e936b179f5 +#: ../../en/system/power.rst:251 649d1bdcb9a4409b87f553490fdf96ab msgid "Port B2." msgstr "" -#: ../../en/system/power.rst:256 8588c355c54946bd94019e37d6d4083f +#: ../../en/system/power.rst:256 1e2bc080e5cb4fb3b33b4ede0fb23a6e msgid "Port C1." msgstr "" -#: ../../en/system/power.rst:261 5c727ae4e92e45b18b2f2ec9ddd2540e +#: ../../en/system/power.rst:261 67cfd84f77104a00a41759eebe75e635 msgid "Port C2." msgstr "" -#: ../../en/system/power.rst:266 f2b684fd30a64417b80c17a4ca445032 +#: ../../en/system/power.rst:266 e351eeaba787454aaaaacfbb0323e5b3 msgid "USB Port." msgstr "" -#: ../../en/system/power.rst:271 f5a0025f6f664a988885936111d9c362 +#: ../../en/system/power.rst:271 5b105d734fd5439c98396c941eebb18e msgid "HAT Port." msgstr "" -#: ../../en/system/power.rst:276 6eaf7447232640aeafb269fb2d759640 +#: ../../en/system/power.rst:276 0f989c81fb684ccda05ceabc59f75d16 msgid "All Ports." msgstr "" +#~ msgid "Methods of the Power Class depend on ``M5.begin()`` |M5.begin.svg|." +#~ msgstr "" + +#~ msgid "M5.begin.svg" +#~ msgstr "" + +#~ msgid "" +#~ "All methods calling the Power object " +#~ "need to be placed after ``M5.begin()``" +#~ " |M5.begin.svg|." +#~ msgstr "" + +#~ msgid "|setExtOutput1.svg| |setExtOutput2.svg|" +#~ msgstr "" + +#~ msgid "setExtOutput1.svg" +#~ msgstr "" + +#~ msgid "setExtOutput2.svg" +#~ msgstr "" + +#~ msgid "|getExtOutput.svg|" +#~ msgstr "" + +#~ msgid "getExtOutput.svg" +#~ msgstr "" + +#~ msgid "|setUsbOutput.svg|" +#~ msgstr "" + +#~ msgid "setUsbOutput.svg" +#~ msgstr "" + +#~ msgid "|getUsbOutput.svg|" +#~ msgstr "" + +#~ msgid "getUsbOutput.svg" +#~ msgstr "" + +#~ msgid "|setLed.svg|" +#~ msgstr "" + +#~ msgid "setLed.svg" +#~ msgstr "" + +#~ msgid "|powerOff.svg|" +#~ msgstr "" + +#~ msgid "powerOff.svg" +#~ msgstr "" + +#~ msgid "|timerSleep1.svg| |timerSleep2.svg| |timerSleep3.svg|" +#~ msgstr "" + +#~ msgid "timerSleep1.svg" +#~ msgstr "" + +#~ msgid "timerSleep2.svg" +#~ msgstr "" + +#~ msgid "timerSleep3.svg" +#~ msgstr "" + +#~ msgid "|deepSleep.svg|" +#~ msgstr "" + +#~ msgid "deepSleep.svg" +#~ msgstr "" + +#~ msgid "|lightSleep.svg|" +#~ msgstr "" + +#~ msgid "lightSleep.svg" +#~ msgstr "" + +#~ msgid "|getBatteryLevel.svg|" +#~ msgstr "" + +#~ msgid "getBatteryLevel.svg" +#~ msgstr "" + +#~ msgid "|setBatteryCharge.svg|" +#~ msgstr "" + +#~ msgid "setBatteryCharge.svg" +#~ msgstr "" + +#~ msgid "|setChargeCurrent.svg|" +#~ msgstr "" + +#~ msgid "setChargeCurrent.svg" +#~ msgstr "" + +#~ msgid "|setChargeVoltage.svg|" +#~ msgstr "" + +#~ msgid "setChargeVoltage.svg" +#~ msgstr "" + +#~ msgid "|isCharging.svg|" +#~ msgstr "" + +#~ msgid "isCharging.svg" +#~ msgstr "" + +#~ msgid "|getBatteryVoltage.svg|" +#~ msgstr "" + +#~ msgid "getBatteryVoltage.svg" +#~ msgstr "" + +#~ msgid "|getBatteryCurrent.svg|" +#~ msgstr "" + +#~ msgid "getBatteryCurrent.svg" +#~ msgstr "" + +#~ msgid "|getKeyState.svg|" +#~ msgstr "" + +#~ msgid "getKeyState.svg" +#~ msgstr "" + +#~ msgid "|setVibration.svg|" +#~ msgstr "" + +#~ msgid "setVibration.svg" +#~ msgstr "" + diff --git a/docs/locales/zh_CN/LC_MESSAGES/units/accel.po b/docs/locales/zh_CN/LC_MESSAGES/units/accel.po index b8d9051c..447017bf 100644 --- a/docs/locales/zh_CN/LC_MESSAGES/units/accel.po +++ b/docs/locales/zh_CN/LC_MESSAGES/units/accel.po @@ -9,7 +9,7 @@ msgid "" msgstr "" "Project-Id-Version: UIFlow2 Programming Guide \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-03-18 14:44+0800\n" +"POT-Creation-Date: 2025-04-18 15:58+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language: zh_CN\n" @@ -20,58 +20,50 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.16.0\n" -#: ../../en/units/accel.rst:2 902d1a8835d24d54a53ca621efe0d7da +#: ../../en/units/accel.rst:2 a42364e5de6442ee99d860a5f5fbf996 msgid "Accel Unit" msgstr "" -#: ../../en/units/accel.rst:8 b28ec5d4dc8246ff85f6efd1064a2194 +#: ../../en/units/accel.rst:8 be7bd41375074ee0a6bdb4511965615d msgid "" "This is the driver library of Accel Unit, which is used to obtain data " "from the acceleration sensor and support motion detection." msgstr "这是Accel Unit的驱动库,用于从加速度传感器获取数据,并支持运动检测。" -#: ../../en/units/accel.rst:11 97aa8b576d224186a393c5a6b0e5fad4 +#: ../../en/units/accel.rst:11 ffcc244c06494dab86ec8efe41c6a989 msgid "Support the following products:" msgstr "支持以下产品:" -#: ../../en/units/accel.rst:13 b39ec85d50964bdc9e5f1558e07f6349 +#: ../../en/units/accel.rst:13 1341b9fb55884f089cb745d428c3ee8f msgid "|ACCEL|" msgstr "" -#: ../../en/refs/unit.accel.ref 3a99c67e620447cf9bf1b0b05d88deb6 +#: ../../en/refs/unit.accel.ref 59fb828193b5409e942511b25b1d391a msgid "ACCEL" msgstr "" -#: ../../en/units/accel.rst:17 a6450b3e96c549a1b0381f18b058cb91 +#: ../../en/units/accel.rst:17 616145209a3249db91dbf6fc2f015464 msgid "UiFlow2 Example" msgstr "UiFlow2 应用示例" #: ../../en/units/accel.rst:20 ../../en/units/accel.rst:38 -#: 7a1425fae4b9420f80e9f947c213869e dbc7f3ac645b476085d1cefbe32cf18a +#: 7455903155c2496488c60f0e41a169a4 msgid "get accel value" msgstr "获取加速度值" -#: ../../en/units/accel.rst:22 333162315e20402ab970b498949fb027 +#: ../../en/units/accel.rst:22 5ce66ee2d2a94288b95cf42501a9eea8 msgid "Open the |stickcplus2_unit_accel_example.m5f2| project in UiFlow2." msgstr "在 UiFlow2 上打开 |stickcplus2_unit_accel_example.m5f2| 项目。" #: ../../en/units/accel.rst:24 ../../en/units/accel.rst:40 -#: 5f6573bd34a2425f97b137d187be4ae7 6e31cfcc8df04efd812f4f828375e64d +#: 25ca27d36e814ca3964f599a0896ca40 msgid "" "This example gets the acceleration value of the Accel Unit and displays " "it on the screen." msgstr "此示例获取加速度单元的加速度值并将其显示在屏幕上。" -#: ../../en/units/accel.rst:26 0300743665984fec8bf554ba14bdee82 -#: 04db7119fef4456cb0078cab18d69af7 075e3390a85241fea385cacc078c858e -#: 11709dca9143494fb5ac5d66fe431d59 31beafa999424b019aa18cbfa27395b7 -#: 7ceb364f5cb24ca299e96a76742be891 8c9d9810af954c28bcc4cee645c718de -#: a27d0e3c91044314a10f6d51f748194d a2bb428776c648ddbc6969e3e58f4c1c -#: b1a3188821f84da3aa575fb0b188e159 c23919799ab2475a9a1afa1af116fdb5 -#: e0e4478d49df4c85bb54e14596697cf2 e3e364d5a0264e36bcc92bb6695a140f -#: eaf994ac36984bda99c1b566eb5ca696 f468fd3a9d784ba6a5fd93a7a241fbea -#: f6fed1fd70c246f8970e03bb0d8a80d1 of unit.accel.AccelUnit:6 -#: unit.accel.AccelUnit.disable_freefall_detection:3 +#: ../../en/units/accel.rst:26 a249159b8dec461c9529e39f1d0eb3b1 of +#: unit.accel.AccelUnit:6 unit.accel.AccelUnit.disable_freefall_detection:3 #: unit.accel.AccelUnit.disable_motion_detection:3 #: unit.accel.AccelUnit.disable_tap_detection:3 #: unit.accel.AccelUnit.enable_freefall_detection:19 @@ -84,37 +76,30 @@ msgstr "此示例获取加速度单元的加速度值并将其显示在屏幕上 msgid "UiFlow2 Code Block:" msgstr "UiFlow2 代码块:" -#: ../../en/units/accel.rst:28 b9d2f2d9dc464118a87c8a3e899a6277 +#: ../../en/units/accel.rst:28 c949df9c35fa47359b876696b493abe8 msgid "|example.png|" msgstr "" -#: ../../en/refs/unit.accel.ref:7 7adec39714794532a576866916f134a6 +#: ../../en/refs/unit.accel.ref:6 e6d021da4ba14b19b1dd397fb72286ba msgid "example.png" msgstr "" #: ../../en/units/accel.rst:30 ../../en/units/accel.rst:48 -#: d45cdab5f52844a79dfb051007dc918c f9b58fef3a9240eea4d53ebeb0718e44 +#: 99790a236b374684b3e5acd0b0b66aa7 msgid "Example output:" msgstr "示例输出:" #: ../../en/units/accel.rst:32 ../../en/units/accel.rst:50 -#: 0d60397bcc9a443f83312751bc5e3c7e f978e6eccd9e43a081221168c4360913 +#: ee15cd039eed4cefbd12940a8a427624 msgid "None" msgstr "无" -#: ../../en/units/accel.rst:35 0b25f7ddaa2140a99d238974bb22f118 +#: ../../en/units/accel.rst:35 d7bd35e7e2204b4f85e86651f8457d7a msgid "MicroPython Example" msgstr "MicroPython 应用示例" -#: ../../en/units/accel.rst:42 0383941d1b014c63991aa0684bab057b -#: 06d1c393ee7e4705b95c3a57fb6aa723 13f2167369214da1b831ffe59f132522 -#: 1e97cf2e0d0a4b449d45bfbc95517272 47aa8816d404498fa4c6d60e3012e335 -#: 48a396b22a3b4a09ab6ed86f41440b79 6227a3d566fa43629208ad03f65f4586 -#: 7a2c864b105343b491dc0270f960c03b 81a7b6f5bcc242d3b1e6cf10086c883f -#: 8475e08d8d12485ba7c1ff959afd7259 a0c67291561b45dabcec3c672b6e2f65 -#: a7fbfdfff99c49179ad6745158072f54 b46fcbf10cd04beeba832c5576640de8 -#: b71e9fc9de9c4dff962ed620c347fef1 c40da3af2bd94dda9de311f2ef03446d -#: da4090fc0d9345b48cdfbfe29b5f2a3a of unit.accel.AccelUnit:10 +#: ../../en/units/accel.rst:42 b39048b4283c4f03afb76c3ae656a4a2 +#: ed85db5961f8435c82f1877562a52db8 of unit.accel.AccelUnit:10 #: unit.accel.AccelUnit.disable_freefall_detection:7 #: unit.accel.AccelUnit.disable_motion_detection:7 #: unit.accel.AccelUnit.disable_tap_detection:7 @@ -128,28 +113,23 @@ msgstr "MicroPython 应用示例" msgid "MicroPython Code Block:" msgstr "MicroPython 代码块:" -#: ../../en/units/accel.rst:54 3a14b5f0a3774beb80cdc9ee81d0ca60 +#: ../../en/units/accel.rst:54 eb74d241d4514552acb9d1375a11c87e msgid "**API**" msgstr "API参考" -#: ../../en/units/accel.rst:57 2e50e258dddf4416b38ea49371d4db9a +#: ../../en/units/accel.rst:57 8659fd462d264358a2dca0fe636cc399 msgid "AccelUnit" msgstr "" -#: d5134f4cc1424a70998d0162ca6a2ed3 of unit.accel.AccelUnit:1 +#: abac852125094fda97383b3663768adc of unit.accel.AccelUnit:1 msgid "Bases: :py:class:`~driver.adxl34x.ADXL345`" msgstr "" -#: 30b45339e792456eb67b9ad35d22f14d of unit.accel.AccelUnit:1 +#: 6e9ae389b89a4138b1cea587a8b535a9 of unit.accel.AccelUnit:1 msgid "Create an AccelUnit object." msgstr "创建一个 AccelUnit 对象。" -#: ../../en/units/accel.rst 15cbd56808674203aecbf3ebecdaa6d6 -#: 268930730f6d4664b41d5db2bccaea5b 5acffaf2cae9467691caff5cd6e4e995 -#: 5c7ea7bfca0c4614975453b326764770 646cccd43ae443dd8b008e37fc91000a -#: 7d279dec01d44cc3b421cbe8d85dde3a 94660055b04240fb849077cf43838c66 -#: c0557c6dbf5047a894aa4ffa18491de0 d7bd73b689a249dc8c5275ba8c97fb3e -#: db5a448cfa4f4567b3cfca9b231d246a of +#: ../../en/units/accel.rst c57f5d19f68642a1bf645fcfa67de397 of #: unit.accel.AccelUnit.enable_freefall_detection #: unit.accel.AccelUnit.enable_motion_detection #: unit.accel.AccelUnit.enable_tap_detection unit.accel.AccelUnit.set_data_rate @@ -157,50 +137,42 @@ msgstr "创建一个 AccelUnit 对象。" msgid "Parameters" msgstr "" -#: 42358ea81a8a41c7be94a7a9ae3b45fb of unit.accel.AccelUnit:3 +#: 398d3d98d6fc48b8826cea4edcc10d29 of unit.accel.AccelUnit:3 msgid "The I2C bus the Accel Unit is connected to." msgstr "Accel Unit 所连接的 I2C 总线。" -#: 9875d27eaaa24581b1d8c9ca78c804a2 of unit.accel.AccelUnit:4 +#: c144cf754efc4a4c8a94287b0de23313 of unit.accel.AccelUnit:4 msgid "The I2C address of the device. Default is 0x53." msgstr "设备的 I2C 地址。默认值为 0x53。" -#: 9fee83fbe03f405faf05a79e4e0d8d61 of unit.accel.AccelUnit:8 -msgid "|init.svg|" +#: ee35fc066ba24c6a83c25eb8d0071100 of unit.accel.AccelUnit:8 +msgid "|init.png|" msgstr "" -#: ../../en/refs/unit.accel.ref:9 f7f5b893502843309e4d11164e9373d3 -msgid "init.svg" +#: ../../en/refs/unit.accel.ref:8 ff96fc000d924aa4995230876f2a13b9 +msgid "init.png" msgstr "" -#: 24f4cfcafba4474e9ddc8d21e82736e6 a9b77c59e23e421595caea29b6f0a6dd +#: 3745b220ad5b49748cf5c4ce3b6ebbf2 #: driver.adxl34x.ADXL345.disable_freefall_detection:1 of #: unit.accel.AccelUnit.disable_freefall_detection:1 msgid "Disable freefall detection." msgstr "禁用自由落体检测。" -#: fc7a4727ba564ed7bae668f1bfe335e9 of +#: 649a5733518c47e5936d30573724b242 of #: unit.accel.AccelUnit.disable_freefall_detection:5 msgid "|disable_freefall_detection.png|" msgstr "" -#: ../../en/refs/unit.accel.ref:41 fc01d3473639439cb000544153a9180d +#: ../../en/refs/unit.accel.ref:28 89ad411a75e34feaa9cc177da5a5250d msgid "disable_freefall_detection.png" msgstr "" -#: ../../en/units/accel.rst 164c1b89be9f4e8d9d8e072310cf7212 -#: 18f8b2a23338431bba0ffd8193bd2474 1aa6d8cdbab54b8faa100c77bcd2c165 -#: 3bfe4e1ba448478abad2dbeefc3a36f4 523807771ec3424ea4f80eb430451d5c -#: 5c3fe8692bab48eb8eb5b5362c82269b 717624ead153430891230fa1b88f5c97 -#: 784bec2611c94f4eabef5fbc3f202c03 9a9f8e8c05b84bea99e95005f8f2f471 -#: a8defe2ca4d54640b3f926bb9adabe0a b44bf5a7a9164b549196d687099511a6 -#: bc6208040bc648e9a67949454cbc1c39 c4735f977daa4d979efaa2cb4964e93f -#: cccd6aec81a24d1483ba22cde7c84dfb dd5d95ff471749589d81a7a213b04c26 -#: ddb95762c3c94e249a7496aabdf42d08 +#: ../../en/units/accel.rst 0d654cbd53dc4f759bb38a92b225fb3f #: driver.adxl34x.ADXL345.disable_freefall_detection #: driver.adxl34x.ADXL345.disable_motion_detection #: driver.adxl34x.ADXL345.disable_tap_detection -#: f7a0d9e316d145d28dd28883b803f15d fe9f27d28bc24cc0ae991db6ade03452 of +#: ed821feb9cdf47e0801699c422391fdb of #: unit.accel.AccelUnit.disable_freefall_detection #: unit.accel.AccelUnit.disable_motion_detection #: unit.accel.AccelUnit.disable_tap_detection @@ -214,43 +186,43 @@ msgstr "" msgid "Return type" msgstr "" -#: 791fabd5df33493abef396c202de3b07 d54bee36fdc847bd89c2ed6b9cc11b0e +#: bc27d6f9bfa6475faa00daa504c71bed #: driver.adxl34x.ADXL345.disable_motion_detection:1 of #: unit.accel.AccelUnit.disable_motion_detection:1 msgid "Disable motion detection." msgstr "禁用运动检测。" -#: 0dba8846b6cd4cf8b1af00e01e1dd9ea of +#: 0483dce6eb2345afac6293e619a3588b of #: unit.accel.AccelUnit.disable_motion_detection:5 msgid "|disable_motion_detection.png|" msgstr "" -#: ../../en/refs/unit.accel.ref:23 336dc2d046af4c80bd7a9da4d9ccbf70 +#: ../../en/refs/unit.accel.ref:16 b5505a9a8f944e71bf717eefe0d9c206 msgid "disable_motion_detection.png" msgstr "" -#: 1cc1661ee1b54730844b24e2ff19405c 2dfe7b5bdfe04253b2be677253995849 +#: c463930bcbb14463b489704784b09516 #: driver.adxl34x.ADXL345.disable_tap_detection:1 of #: unit.accel.AccelUnit.disable_tap_detection:1 msgid "Disable tap detection." msgstr "禁用点击检测。" -#: a6487f5fa6ad4bd884b1b6ba5ba27a1a of +#: f1043f37adb24efc8559457d753a7720 of #: unit.accel.AccelUnit.disable_tap_detection:5 msgid "|disable_tap_detection.png|" msgstr "" -#: ../../en/refs/unit.accel.ref:50 7d839aff4410475abd7f62f9dd333df8 +#: ../../en/refs/unit.accel.ref:34 91e45bd3033749b2a20b92bc8e7f4c25 msgid "disable_tap_detection.png" msgstr "" -#: 25c6e88d4df74835be4c692f6904195e 49a096f47e5b475e9448b25ec3c8bf20 +#: 1b2b2816067d4ae1b9bff24e91432f5e #: driver.adxl34x.ADXL345.enable_freefall_detection:1 of #: unit.accel.AccelUnit.enable_freefall_detection:1 msgid "Freefall detection parameters:" msgstr "自由落体检测参数:" -#: 49aa8b31536249e1b4580f7f352d333c 6f241515428c47efa397a63df8c6e811 +#: 69a21864aecb436b977e445f3b5d9675 c323c67f2e1f4cf38fffd0c0a5be0956 #: driver.adxl34x.ADXL345.enable_freefall_detection:3 of #: unit.accel.AccelUnit.enable_freefall_detection:3 msgid "" @@ -258,9 +230,8 @@ msgid "" "dropped. The scale factor is 62.5 mg/LSB." msgstr "所有轴上的加速度必须低于该值才能记录为下降。比例因子为 62.5 mg/LSB。" -#: aec0a5b566f0485e86ba49967ebfbb58 -#: driver.adxl34x.ADXL345.enable_freefall_detection:7 -#: f4a26b6e2da84ef9953b14715beac15c of +#: c5ddd22e80da4a2aa47106c9edd165e2 df1568d4a1154b099d978f93bb1a4803 +#: driver.adxl34x.ADXL345.enable_freefall_detection:7 of #: unit.accel.AccelUnit.enable_freefall_detection:7 msgid "" "The amount of time that acceleration on all axes must be less than " @@ -270,12 +241,10 @@ msgstr "" "所有轴上的加速度必须小于“阈值”的时间量才能记录为掉落。比例因子为 5 毫秒/LSB。建议值介于 100 毫秒和 350 毫秒(20 到 " "70)之间。" -#: 102c14b3b9594b5082b8cf9a84bf70b0 25e6a031170e494b8ba2700c49cf4ba6 -#: 3bbf2287d1534a248082e817ab6de3dd b2a38a6c7f264563b247692db353d578 +#: b9beb141e00246c68c111b6aa493876e #: driver.adxl34x.ADXL345.enable_freefall_detection:12 #: driver.adxl34x.ADXL345.enable_motion_detection:7 -#: driver.adxl34x.ADXL345.enable_tap_detection:23 -#: fc0f57cc76cf49ff9d496296ea497ccc of +#: driver.adxl34x.ADXL345.enable_tap_detection:23 of #: unit.accel.AccelUnit.enable_freefall_detection:12 #: unit.accel.AccelUnit.enable_tap_detection:22 msgid "" @@ -283,31 +252,31 @@ msgid "" " use keyword arguments:" msgstr "如果您希望自己设置它们而不是使用默认值,则必须使用关键字参数:" -#: 6f0be3ece7ed497bac9af7f9609c30a7 of +#: fa8d0124482b4d35ad0f49b95fdc870c of #: unit.accel.AccelUnit.enable_freefall_detection:21 msgid "|enable_freefall_detection1.png|" msgstr "" -#: ../../en/refs/unit.accel.ref:35 5ab50b2f8b974ea89044082ef45be9b8 +#: ../../en/refs/unit.accel.ref:24 9f3f312b28fb42d881aa771cec9150fd msgid "enable_freefall_detection1.png" msgstr "" -#: 6ba90c3b4f504e15bbb4882af08e1804 of +#: dabcd8ffeda849eb9cbdcdd64d6879ed of #: unit.accel.AccelUnit.enable_freefall_detection:23 msgid "|enable_freefall_detection2.png|" msgstr "" -#: ../../en/refs/unit.accel.ref:38 6d44df84f7f540d6886187414a7056e9 +#: ../../en/refs/unit.accel.ref:26 dbb512261b684f658dd78c06f27d9d1c msgid "enable_freefall_detection2.png" msgstr "" -#: 6e1a73d6647a420f9b7a5315be43265b 7a690f8202c6471da82be9977c4bb023 -#: driver.adxl34x.ADXL345.enable_motion_detection:1 of +#: driver.adxl34x.ADXL345.enable_motion_detection:1 +#: e238f49747b04267b97d448cce26f9d1 of #: unit.accel.AccelUnit.enable_motion_detection:1 msgid "The activity detection parameters." msgstr "活动检测参数。" -#: 547b32cc15c745bea2e6c4a41191a747 c13671b9d54b4d989fca2dc1b77be0dc +#: 98994f51b06e4cbb859d51915cbf6c40 a9b8d55cfd7f42b680ebd215f3844408 #: driver.adxl34x.ADXL345.enable_motion_detection:3 of #: unit.accel.AccelUnit.enable_motion_detection:3 msgid "" @@ -315,44 +284,44 @@ msgid "" "active. The scale factor is 62.5 mg/LSB." msgstr "任何轴上的加速度必须超过该值才能注册为活动值。比例因子为 62.5 mg/LSB。" -#: 2f465f589fbb4024b62fe585a094c2a4 of +#: d65b19af8f3e4d0190a4977cec0c20b7 of #: unit.accel.AccelUnit.enable_motion_detection:7 msgid "" "If you wish to set them yourself rather than using the defaults, you must" " use keyword arguments::" msgstr "如果您希望自己设置它们而不是使用默认值,则必须使用关键字参数:" -#: 9f2f01b15a1b4392b8fcab7054203e2c of +#: 4c6f58d0d8634c5ab6bc66b2038b4e06 of #: unit.accel.AccelUnit.enable_motion_detection:14 msgid "|enable_motion_detection1.png|" msgstr "" -#: ../../en/refs/unit.accel.ref:17 e326fb13062f4950aaaf09e610341ae9 +#: ../../en/refs/unit.accel.ref:12 2ed2c1556ba14d0b8e656b5fcf2aa99a msgid "enable_motion_detection1.png" msgstr "" -#: 3e371f5fe7874fd6ac17733e7e5d055e of +#: b39c9774004143bbb743647e51be712f of #: unit.accel.AccelUnit.enable_motion_detection:16 msgid "|enable_motion_detection2.png|" msgstr "" -#: ../../en/refs/unit.accel.ref:20 7bb56bd62373496fae19df723f47be65 +#: ../../en/refs/unit.accel.ref:14 3875a176f6814205a1ab52c1a1aed444 msgid "enable_motion_detection2.png" msgstr "" -#: 1b276bfad2854526a6b576fc2959b3d7 a243202c7f30497bb5677cdb07641092 +#: 499a03f1808c4e699bfd12bdd6c655b3 #: driver.adxl34x.ADXL345.enable_tap_detection:1 of #: unit.accel.AccelUnit.enable_tap_detection:1 msgid "The tap detection parameters." msgstr "点击检测参数。" -#: 7e7232fefc0c4f308c1eca79cac7dba4 8f5ddfa8fda14350b6cb8c304103e6e0 +#: 431d6061c25c499cbc9488a29d359049 #: driver.adxl34x.ADXL345.enable_tap_detection:3 of #: unit.accel.AccelUnit.enable_tap_detection:3 msgid "1 to detect only single taps, and 2 to detect only double taps." msgstr "1 仅检测单击,2 仅检测双击。" -#: 4bae85d8583043ef94f525f9149a89b5 d19488c1e1c44be2b49418b47e41e231 +#: 184c73bbd9534c6199406a57e73f762b a379e297d6964080900d13d9e2303313 #: driver.adxl34x.ADXL345.enable_tap_detection:6 of #: unit.accel.AccelUnit.enable_tap_detection:6 msgid "" @@ -360,7 +329,7 @@ msgid "" "higher the value the less sensitive the detection." msgstr "点击检测的阈值。比例因子为 62.5 mg/LSB。值越高,检测越不灵敏。" -#: 246363ba0f48479eb814262b8415df24 b82fb242597f4e35a129037bf28e6d32 +#: 89bbaea06ca3494991c6a8f078b57799 #: driver.adxl34x.ADXL345.enable_tap_detection:10 of #: unit.accel.AccelUnit.enable_tap_detection:10 msgid "" @@ -368,7 +337,7 @@ msgid "" " ``duration`` won't register as a tap. The scale factor is 625 µs/LSB." msgstr "这将限制脉冲持续时间高于“阈值”。任何超过“持续时间”的脉冲都不会被记录为抽头。比例因子为 625 µs/LSB。" -#: 064fc7d79a0c475ea840cf87285b8f15 2784f3fad3f24a3f9b37d86ec5e560b1 +#: 035422a92628463ba5c1bcc3df95fbc4 83169e08cae444e2b828dc1809632512 #: driver.adxl34x.ADXL345.enable_tap_detection:14 of #: unit.accel.AccelUnit.enable_tap_detection:14 msgid "" @@ -377,271 +346,267 @@ msgid "" " scale factor is 1.25 ms/LSB." msgstr "(仅限双击)初始脉冲低于“阈值”后,启动窗口寻找第二个脉冲的时间长度。比例因子为 1.25 ms/LSB。" -#: 7566e7c1415344c182771d2cf7969e40 -#: driver.adxl34x.ADXL345.enable_tap_detection:19 -#: e3964e547a8a4a839521fd2ea5ff9e2b of +#: 57cce65774a94dae95d981b2ab2c300c 874ef7f3d0484746abd6cf0da71fa97d +#: driver.adxl34x.ADXL345.enable_tap_detection:19 of #: unit.accel.AccelUnit.enable_tap_detection:19 msgid "" "(double tap only) The length of the window in which to look for a second " "tap. The scale factor is 1.25 ms/LSB." msgstr "(仅限双击)查找第二次点击的窗口长度。比例因子为 1.25 ms/LSB。" -#: ddacd24b1f284a3a960f624d8bed6ad2 of +#: b9e43d2bd916423b93bdd7d97b576484 of #: unit.accel.AccelUnit.enable_tap_detection:31 msgid "|enable_tap_detection1.png|" msgstr "" -#: ../../en/refs/unit.accel.ref:44 98c224ccdaa94c0586982b362814f48a +#: ../../en/refs/unit.accel.ref:30 24f34d6681ea473d9f0f6a2695a0afcc msgid "enable_tap_detection1.png" msgstr "" -#: 5b1d277cad2648beb1959e9f232ecef3 of +#: 73b2155c40464a44a00363af079ff735 of #: unit.accel.AccelUnit.enable_tap_detection:33 msgid "|enable_tap_detection2.png|" msgstr "" -#: ../../en/refs/unit.accel.ref:47 bf247d82d7ca436f8acda273962fc0f7 +#: ../../en/refs/unit.accel.ref:32 f76ea27d9d3f41fd9beab83b755469dd msgid "enable_tap_detection2.png" msgstr "" -#: 2a6baf7d029f48c5b96031cfd69f363c adabe98e0e0a4a8385a1abb764e7c92f of -#: unit.accel.ADXL345.acceleration:1 unit.accel.AccelUnit.get_accel:1 +#: e3b40c0792ad40acbbb0c877d6f664ee of unit.accel.ADXL345.acceleration:1 +#: unit.accel.AccelUnit.get_accel:1 msgid "" "The x, y, z acceleration values returned in a 3-tuple in :math:`m / s ^ " "2`." msgstr "以 3 元组形式返回 x、y、z 加速度值,单位为 :math:`m / s ^ 2`。" -#: 127a2a03c76b4e3a9cf2c825306e2a85 b4ec3829fed946e59bad9c7d8dd5f637 -#: bdb065d30e3c457291664cbf66ad82f5 e2bb6b7e2181422abd1516523683b5f3 -#: e3b57755522244b3bdb259c87aa292ef fa2fc50bb30c40228ef7a67d2bd37e6f of -#: unit.accel.AccelUnit.get_accel unit.accel.AccelUnit.get_data_rate -#: unit.accel.AccelUnit.get_range unit.accel.AccelUnit.is_freefall -#: unit.accel.AccelUnit.is_motion unit.accel.AccelUnit.is_tap +#: 76d119ad9521421bb6228b473ecde02c of unit.accel.AccelUnit.get_accel +#: unit.accel.AccelUnit.get_data_rate unit.accel.AccelUnit.get_range +#: unit.accel.AccelUnit.is_freefall unit.accel.AccelUnit.is_motion +#: unit.accel.AccelUnit.is_tap msgid "Returns" msgstr "" -#: 9ed6bf3b22374ec798eba2c42449bd9f of unit.accel.AccelUnit.get_accel:3 +#: e32be9b55df04c38943e31590c8d0cab of unit.accel.AccelUnit.get_accel:3 msgid "x, y, z acceleration values in :math:`m / s ^ 2`." msgstr "x、y、z 加速度值,单位为 :math:`m / s ^ 2` 。" -#: c61a9b33fc304bdea7a71098b28ace9b of unit.accel.AccelUnit.get_accel:8 -msgid "|get_accel.svg|" +#: 829e1cfb6bc349fea242d4703fb144f3 of unit.accel.AccelUnit.get_accel:8 +msgid "|get_accel.png|" msgstr "" -#: ../../en/refs/unit.accel.ref:12 001414be2c7346cda721401819a0c6b3 -msgid "get_accel.svg" +#: ../../en/refs/unit.accel.ref:10 ec5c9346ff8a467ba6dbd0b7877a89ed +msgid "get_accel.png" msgstr "" -#: 2c6d8c3ced3a4ca5b687fb3352164f1f of unit.accel.AccelUnit.get_data_rate:1 +#: d542339afeed42878350ed99a831b5ec of unit.accel.AccelUnit.get_data_rate:1 msgid "Get the data rate of the sensor." msgstr "获取传感器的数据速率。" -#: 79324073024a4af5922d00836501057c a8249252fef849fe96d0e2a15cdda2a8 -#: c97c1eaa8bfc4d53b2517d4c81e855e7 of unit.accel.ADXL345.data_rate:1 -#: unit.accel.AccelUnit.get_data_rate:3 unit.accel.AccelUnit.set_data_rate:3 +#: 0b16bfce611d49caa4346f9460bde1cf 8643750e77e14c8bb941450e1bed69b8 of +#: unit.accel.ADXL345.data_rate:1 unit.accel.AccelUnit.get_data_rate:3 +#: unit.accel.AccelUnit.set_data_rate:3 msgid "The data rate of the sensor." msgstr "传感器的数据速率。" -#: e49441732afc45638764cb838fc31e21 of unit.accel.AccelUnit.get_data_rate:8 -msgid "|get_data_rate.svg|" +#: 923b04b25c274bbd99b6d94ec7cab946 of unit.accel.AccelUnit.get_data_rate:8 +msgid "|get_data_rate.png|" msgstr "" -#: ../../en/refs/unit.accel.ref:52 126a6cf2c79d48cd8136b85fc0b91cc8 -msgid "get_data_rate.svg" +#: ../../en/refs/unit.accel.ref:36 028d3c08350a42a4a7b11642393566f3 +msgid "get_data_rate.png" msgstr "" -#: a743f49e869842ad85df8473255735a0 of unit.accel.AccelUnit.get_range:1 +#: 59068dabd8004f5e9747b620bd17af4a of unit.accel.AccelUnit.get_range:1 msgid "Get the measurement range of the sensor." msgstr "获取传感器的测量范围。" -#: 28372fe3638b481c983525769a9a18a9 3f1a124e64ac4590aa050e651a62929c -#: a9dd393069134061a4105d9b5ead9441 b0f5f5dc9e8f453baeb3434d20979781 of +#: 9c84e815159f4583ac43487f41bb761d a7a9731b5a2a40b9950808252b084dc9 of #: unit.accel.ADXL345.range:1 unit.accel.AccelUnit.get_range:3 #: unit.accel.AccelUnit.set_range:1 unit.accel.AccelUnit.set_range:3 msgid "The measurement range of the sensor." msgstr "传感器的测量范围。" -#: 3f17b660ef624a20becb3a1df0c6cf55 of unit.accel.AccelUnit.get_range:8 -msgid "|get_range.svg|" +#: 87e70daf7fbf40d4881b34cddcbd8157 of unit.accel.AccelUnit.get_range:8 +msgid "|get_range.png|" msgstr "" -#: ../../en/refs/unit.accel.ref:58 a40c3d79249d4825a1799c1967a4da63 -msgid "get_range.svg" +#: ../../en/refs/unit.accel.ref:40 53513738514e4f1794dd0894b2d9ba15 +msgid "get_range.png" msgstr "" -#: 5cee4757d76a465fa5779f97ba376a5d of unit.accel.AccelUnit.is_freefall:1 +#: 062eacb8428e40bdb6311ddfffa012b6 of unit.accel.AccelUnit.is_freefall:1 msgid "Returns True if freefall has been detected." msgstr "如果检测到自由落体则返回 True。" -#: df8c56049fef44ab973160cd8b9a671d of unit.accel.AccelUnit.is_freefall:3 +#: 9c2633bb41dc489c928c164039a363ba of unit.accel.AccelUnit.is_freefall:3 msgid "True if freefall has been detected." msgstr "如果检测到自由落体则返回 True。" -#: 94e5848358c74f178195c9a61bf7c691 of unit.accel.AccelUnit.is_freefall:8 -msgid "|is_freefall.svg|" +#: 2d44296a7db34f8697a66361d60bc287 of unit.accel.AccelUnit.is_freefall:8 +msgid "|is_freefall.png|" msgstr "" -#: ../../en/refs/unit.accel.ref:31 809186a7b1444b7f95d056d2f4c5c84a -msgid "is_freefall.svg" +#: ../../en/refs/unit.accel.ref:22 e0240099696344fe964c7a11a72ffeaf +msgid "is_freefall.png" msgstr "" -#: 07c042dab8504ae0a42136b0acd68b63 of unit.accel.AccelUnit.is_motion:1 +#: 04ed824e2b1b4cfe998f281ec2808ae9 of unit.accel.AccelUnit.is_motion:1 msgid "Returns True if motion has been detected." msgstr "如果检测到运动则返回 True。" -#: 88e351cc3a4e4e5386b8214080a389ec of unit.accel.AccelUnit.is_motion:3 +#: 20e8a78ae585449cba838227a2afc329 of unit.accel.AccelUnit.is_motion:3 msgid "True if motion has been detected." msgstr "如果检测到运动则返回 True。" -#: 70a1ca685f504700a2ded6fadfb4db44 of unit.accel.AccelUnit.is_motion:7 -msgid "|is_motion.svg|" +#: 715f9fbf744b4c40b62cb0734bdef2a4 of unit.accel.AccelUnit.is_motion:7 +msgid "|is_motion.png|" msgstr "" -#: ../../en/refs/unit.accel.ref:28 5453e4cbc1de458cabb4cf01244d35ff -msgid "is_motion.svg" +#: ../../en/refs/unit.accel.ref:20 a9bfaa8f5efb4491b47b066821a3ae9a +msgid "is_motion.png" msgstr "" -#: 29d133d198ae4df1bd1a73dd7cb32c95 of unit.accel.AccelUnit.is_tap:1 +#: b3db09631cd344ea8c3149873b7a35d6 of unit.accel.AccelUnit.is_tap:1 msgid "Returns True if a tap has been detected." msgstr "如果检测到点击则返回 True。" -#: 660d5fdea93b4d95b032cd19811cad1d of unit.accel.AccelUnit.is_tap:3 +#: 962cc9221d95444ba8b5309fafb099c1 of unit.accel.AccelUnit.is_tap:3 msgid "True if a tap has been detected." msgstr "如果检测到点击则为 True。" -#: 2a61834f78e04c859f94a9dffa126d48 of unit.accel.AccelUnit.is_tap:8 -msgid "|is_tap.svg|" +#: b1e6dcdff63e447abf3ee6cf946662c0 of unit.accel.AccelUnit.is_tap:8 +msgid "|is_tap.png|" msgstr "" -#: ../../en/refs/unit.accel.ref:25 956fc58f973c496ba9eac25e24915839 -msgid "is_tap.svg" +#: ../../en/refs/unit.accel.ref:18 47f085cf416841d29d31e53049457c75 +msgid "is_tap.png" msgstr "" -#: a09ea98bcb364a149011d683687d5fa7 of unit.accel.AccelUnit.set_data_rate:1 +#: fc41e1a437a34220a5c7ce27a6f99ded of unit.accel.AccelUnit.set_data_rate:1 msgid "Set the data rate of the sensor." msgstr "设置传感器的数据速率。" -#: ffa5cdb8b48244bd91e5648712f30ad4 of unit.accel.AccelUnit.set_data_rate:7 -msgid "|set_data_rate.svg|" +#: 6ca1c540080f43678903799a2149ed06 of unit.accel.AccelUnit.set_data_rate:7 +msgid "|set_data_rate.png|" msgstr "" -#: ../../en/refs/unit.accel.ref:55 6fc54122a0ab4ab8bbb55088baaa289d -msgid "set_data_rate.svg" +#: ../../en/refs/unit.accel.ref:38 56146f437d1b4cad85994a13bce298e4 +msgid "set_data_rate.png" msgstr "" -#: a481e6f2905f43beba9f079e16f81df2 of unit.accel.AccelUnit.set_range:7 -msgid "|set_range.svg|" +#: 7a3fb91876e2454ea5535c6f04e5594c of unit.accel.AccelUnit.set_range:7 +msgid "|set_range.png|" msgstr "" -#: ../../en/refs/unit.accel.ref:61 f0ff674c2acf4f0fa7707d769adb3f69 -msgid "set_range.svg" +#: ../../en/refs/unit.accel.ref:42 bb85b7a1c8834ffa964723d972172ddd +msgid "set_range.png" msgstr "" -#: ../../en/units/accel.rst:63 5e126904cd5a45f3964a668fa644668a +#: ../../en/units/accel.rst:63 c66b3ac340aa491ebec8b14fa457ae5c msgid "ADXL345" msgstr "" -#: 96449244ae134184ba24833fd997a36d driver.adxl34x.ADXL345:1 of +#: driver.adxl34x.ADXL345:1 ff5020779b6c4f058aef6704faaa7cba of msgid "Bases: :py:class:`object`" msgstr "" -#: 22ea60ecd8aa44d9855cb2e388e90720 driver.adxl34x.ADXL345:1 of +#: 5461f5fca9e7475aaad22cf8d821a32c driver.adxl34x.ADXL345:1 of msgid "Driver for the ADXL345 3 axis accelerometer." msgstr "ADXL345 3 轴加速度计的驱动程序。" -#: 1d9cf86055b64ab9b7372aec1f200dec driver.adxl34x.ADXL345:3 of +#: b234f6f056234ae1b71d39dc69158cde driver.adxl34x.ADXL345:3 of msgid "The I2C bus the ADXL345 is connected to." msgstr "ADXL345 所连接的 I2C 总线。" -#: 9c65adf3a75a4f2c880691014453382a driver.adxl34x.ADXL345:4 of +#: 973fa1dbd05843ceade42299220f7445 driver.adxl34x.ADXL345:4 of msgid "The I2C device address for the sensor. Default is :const:`0x53`." msgstr "传感器的 I2C 设备地址。默认值为 :const:`0x53`。" -#: 8c0d48fae86f428d9ead4f6cb74f84d1 driver.adxl34x.ADXL345:7 of +#: 48d210ae04eb4427ba2491b793ed05b4 driver.adxl34x.ADXL345:7 of msgid "**Quickstart: Importing and using the device**" msgstr "**快速入门:导入和使用设备**" -#: driver.adxl34x.ADXL345:9 ff841d2f966e4231b2c542ea8d4d9f5e of +#: cf61299ba3b840f5ad65f09c7d2964fb driver.adxl34x.ADXL345:9 of msgid "" "Here is an example of using the :class:`ADXL345` class. First you will " "need to import the libraries to use the sensor:" msgstr "以下是使用 :class:`ADXL345` 类的示例。首先,您需要导入库才能使用传感器:" -#: 7975488392f046f7b80a9c998b593820 driver.adxl34x.ADXL345:17 of +#: 4358ca655b7a48158a36c8bad1916e48 driver.adxl34x.ADXL345:17 of msgid "" "Once this is done you can define your `I2C` object and define your sensor" " object:" msgstr "完成后,您可以定义 `I2C` 对象并定义传感器对象:" -#: 8811065014e24104b9f57ed7dc83f069 driver.adxl34x.ADXL345:25 of +#: 76e7623efced4b0894d09f8cc6e5d63b driver.adxl34x.ADXL345:25 of msgid "Now you have access to the :attr:`acceleration` attribute:" msgstr "现在你可以访问 :attr:`acceleration` 属性:" -#: 8fc0baf7b8af4fab8befcdb2b14ff263 of unit.accel.ADXL345.events:1 +#: 04c95efc13424e198246a14f8183145a of unit.accel.ADXL345.events:1 msgid "" ":attr:`events` will return a dictionary with a key for each event type " "that has been enabled." msgstr ":attr:`events` 将返回一个字典,其中包含已启用的每个事件类型的键。" -#: ad70e97cf843429896fc949540911fe8 of unit.accel.ADXL345.events:4 +#: d78bac19581e451ca87628ef2c66c8d0 of unit.accel.ADXL345.events:4 msgid "The possible keys are:" msgstr "可能的关键字有:" -#: 13d40541a66e475d9f1cae8fdcbdd3fb of unit.accel.ADXL345.events:7 +#: 7c3c6362ccd34724bd7456a60b99c34a of unit.accel.ADXL345.events:7 msgid "Key" msgstr "关键字" -#: fb3725cc0f27447596e1cefe44b0cbb0 of unit.accel.ADXL345.events:7 +#: 5d46acc26b934940a70384c041800089 of unit.accel.ADXL345.events:7 msgid "Description" msgstr "描述" -#: 9d86454478ae44ba99f7ff349c4dbf1d of unit.accel.ADXL345.events:9 +#: 704fc59e476f4b56b6a7729e7ec071ea of unit.accel.ADXL345.events:9 msgid "``tap``" msgstr "" -#: 593ae3f326c540bab6fe67296362eae4 of unit.accel.ADXL345.events:9 +#: 34f79a952e5d499dad717fffb2249e9c of unit.accel.ADXL345.events:9 msgid "" "True if a tap was detected recently. Whether it's looking for a single or" " double tap is determined by the tap param of `enable_tap_detection`." msgstr "如果最近检测到点击,则为 True。是否寻找单击或双击取决于 `enable_tap_detection` 的点击参数。" -#: 3bb87819a3254042bd92e47a046a2f59 of unit.accel.ADXL345.events:12 +#: 9cef7901207d4112a26b2cfff4f885c6 of unit.accel.ADXL345.events:12 msgid "``motion``" msgstr "" -#: 2e53cc23d7934389b94de9af8867335e of unit.accel.ADXL345.events:12 +#: 78a9591bfe804edda28f283cdfdb0aae of unit.accel.ADXL345.events:12 msgid "" "True if the sensor has seen acceleration above the threshold set with " "`enable_motion_detection`." msgstr "如果传感器检测到的加速度高于使用 `enable_motion_detection` 设置的阈值,则为真。" -#: 7ff171f6349e4962ade65e516e524cfe of unit.accel.ADXL345.events:15 +#: a8098c20bbb14496a9787f300459d2fb of unit.accel.ADXL345.events:15 msgid "``freefall``" msgstr "" -#: 209ab0f6372b4514aff77bb6009224dd of unit.accel.ADXL345.events:15 +#: fb84eaecb6194edf84b7edd5e8511c54 of unit.accel.ADXL345.events:15 msgid "" "True if the sensor was in freefall. Parameters are set when enabled with " "`enable_freefall_detection`." msgstr "如果传感器处于自由落体状态,则为 True。使用 `enable_freefall_detection` 启用时设置参数。" -#: 406f7311d02f4f9bbd61b6b27fb6d158 of unit.accel.ADXL345.offset:1 +#: 9015558362f5434ebd3430842d0d9e5c of unit.accel.ADXL345.offset:1 msgid "The x, y, z offsets as a tuple of raw count values." msgstr "x、y、z 偏移量作为原始计数值的元组。" -#: 90c08af4f2ed4314b8c710f05377fa1b of unit.accel.ADXL345.offset:3 +#: 782238f20055429c9d1b27c875603c88 of unit.accel.ADXL345.offset:3 msgid "See offset_calibration example for usage." msgstr "请参阅 offset_calibration 示例以了解用法。" -#: efdb10564d314681b18b6ba7fef159e3 of unit.accel.ADXL345.raw_x:1 +#: 000ed8f733ac465896a17ba2de1066a7 of unit.accel.ADXL345.raw_x:1 msgid "The raw x value." msgstr "原始 x 轴的值。" -#: 1180de999d334c99a182eeb9c18510e5 of unit.accel.ADXL345.raw_y:1 +#: 931d0577b91e461d85fa29221dff875d of unit.accel.ADXL345.raw_y:1 msgid "The raw y value." msgstr "原始 y 轴的值。" -#: ca4c7f8dec86476bafd9a91a18ac83b3 of unit.accel.ADXL345.raw_z:1 +#: 972ecb35860b413aba1fddcf6c5264b8 of unit.accel.ADXL345.raw_z:1 msgid "The raw z value." msgstr "原始 z 轴的值。" @@ -822,3 +787,57 @@ msgstr "原始 z 轴的值。" #~ msgid "Disable motion detection" #~ msgstr "禁用运动检测”" +#~ msgid "|init.svg|" +#~ msgstr "" + +#~ msgid "init.svg" +#~ msgstr "" + +#~ msgid "|get_accel.svg|" +#~ msgstr "" + +#~ msgid "get_accel.svg" +#~ msgstr "" + +#~ msgid "|get_data_rate.svg|" +#~ msgstr "" + +#~ msgid "get_data_rate.svg" +#~ msgstr "" + +#~ msgid "|get_range.svg|" +#~ msgstr "" + +#~ msgid "get_range.svg" +#~ msgstr "" + +#~ msgid "|is_freefall.svg|" +#~ msgstr "" + +#~ msgid "is_freefall.svg" +#~ msgstr "" + +#~ msgid "|is_motion.svg|" +#~ msgstr "" + +#~ msgid "is_motion.svg" +#~ msgstr "" + +#~ msgid "|is_tap.svg|" +#~ msgstr "" + +#~ msgid "is_tap.svg" +#~ msgstr "" + +#~ msgid "|set_data_rate.svg|" +#~ msgstr "" + +#~ msgid "set_data_rate.svg" +#~ msgstr "" + +#~ msgid "|set_range.svg|" +#~ msgstr "" + +#~ msgid "set_range.svg" +#~ msgstr "" + diff --git a/docs/locales/zh_CN/LC_MESSAGES/units/angle.po b/docs/locales/zh_CN/LC_MESSAGES/units/angle.po index dbcc2f92..97e66f49 100644 --- a/docs/locales/zh_CN/LC_MESSAGES/units/angle.po +++ b/docs/locales/zh_CN/LC_MESSAGES/units/angle.po @@ -9,7 +9,7 @@ msgid "" msgstr "" "Project-Id-Version: UIFlow2 Programming Guide \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-03-11 15:31+0800\n" +"POT-Creation-Date: 2025-04-18 15:58+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language: zh_CN\n" @@ -18,112 +18,131 @@ msgstr "" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" -"Generated-By: Babel 2.12.1\n" +"Generated-By: Babel 2.16.0\n" -#: ../../en/units/angle.rst:2 416671239b7a4296bd7df29f6ae2b46e +#: ../../en/units/angle.rst:2 3d976d8914bf42a8862704c7d6470ec3 msgid "Angle Unit" msgstr "" -#: ../../en/refs/unit.angle.ref 169830dc48744b1591e0a29aa2121f18 -#: 95039787b9f94c39b84bb51c8839ca77 -msgid "Angle" -msgstr "" - -#: ../../en/refs/unit.angle.ref:6 441f06fccaa24b55aa9154a9fb980f70 -#: 6f9295067bd74097a9e4d3cbd861f354 -msgid "example.svg" -msgstr "" - -#: ../../en/refs/unit.angle.ref:8 34f6334b119c49809733ca9761a9c21a -#: fcb72a8cac624375bb02aca45fb83f6f -msgid "init.svg" -msgstr "" - -#: ../../en/refs/unit.angle.ref:10 73e66f956e5d4b658985312c93a6c6b9 -#: 9b3b108b819b4c4bb7519f1498797a12 -msgid "get_value.svg" -msgstr "" - -#: ../../en/refs/unit.angle.ref:12 9a475e7279d1404fba64d40baa8b8a9e -#: eb3607650ed34473977839e401a195de -msgid "get_voltage.svg" -msgstr "" - -#: ../../en/units/angle.rst:6 596cd95831cc43a3aff1df74332a0a47 +#: ../../en/units/angle.rst:6 442b5b4790564be39652d351eaf5f443 msgid "The following products are supported:" msgstr "支持以下产品:" -#: ../../en/units/angle.rst:8 99d4126a0fc04ea38ec1613684a7dab8 +#: ../../en/units/angle.rst:8 c2531222c4784ba7b280e8f8ed318bad msgid "|Angle|" msgstr "" -#: ../../en/units/angle.rst:10 538f88505731452eb1ccbad309f3f37a +#: ../../en/refs/unit.angle.ref f0af0cddb90241918e593d780cba52ab +msgid "Angle" +msgstr "" + +#: ../../en/units/angle.rst:10 7d50e58ef8604d2a8240596ad04e2760 msgid "Micropython Example::" msgstr "" -#: ../../en/units/angle.rst:25 1843233c9dd54b62bd3fdfe922c27328 +#: ../../en/units/angle.rst:25 9201614af764480d9285b940e6db329e msgid "UIFLOW2 Example:" msgstr "" -#: ../../en/units/angle.rst:27 8df7b678b2774e1ab57eaf62b3a3eb24 -msgid "|example.svg|" +#: ../../en/units/angle.rst:27 e14180559dba46a2b010a88ec900bdc5 +msgid "|example.png|" +msgstr "" + +#: ../../en/refs/unit.angle.ref:6 2956b556ba0f41408c4d4103e1c9042b +msgid "example.png" msgstr "" -#: ../../en/units/angle.rst:32 4bb8ca5d079d4faca0f7ab1aed94e926 +#: ../../en/units/angle.rst:32 7f4a407396b44e8d9b9a45ff0d61286d msgid "|angle_core_example.m5f2|" msgstr "" -#: ../../en/units/angle.rst:36 c7c3991d4a4b4590bcc522120c36a1d4 +#: ../../en/units/angle.rst:36 f5758f80f79f4c4a8e0d79a24257bf55 msgid "class Angle" msgstr "" -#: ../../en/units/angle.rst:39 c1d88060769949719ada9cfe142918a8 +#: ../../en/units/angle.rst:39 5eafdc8b2db14a46a0a28b7ba0f56fdc msgid "Constructors" msgstr "" -#: ../../en/units/angle.rst:43 65d8f81ca0e44cc9965fe9eb27698209 +#: ../../en/units/angle.rst:43 f7a8baf56b9a4c7fa6d76b27055bb83e msgid "Create an Angle object." msgstr "创建一个Angle对象。" -#: ../../en/units/angle.rst:46 d5d2e9191a384ed182d8c72bd92c1254 +#: ../../en/units/angle.rst:46 a187a0ed68b8400c93ae8ca940d07c26 msgid "parameter is:" msgstr "参数是:" -#: ../../en/units/angle.rst:46 26d1f2a4525a494e8218ae5f5fcb2a7e +#: ../../en/units/angle.rst:46 dc11a4bbacda4004aef707474e01060b msgid "``port`` is the pins number of the port" msgstr "``port`` 是端口的引脚号" #: ../../en/units/angle.rst:48 ../../en/units/angle.rst:60 -#: ../../en/units/angle.rst:69 30286798f2504f70b9c330d00dbe1628 -#: bb995e98ea874c0ca759ebf272c193c6 e0ab47bdbabe41acb30e1d74ee9ca98d +#: ../../en/units/angle.rst:69 62ca081daff042e49cb2fd0ed2e45f30 +#: 98cd2fe60a944e39a266ce46628616e3 ac8ac15e1c314a62a184867e2a0c9697 msgid "UIFLOW2:" msgstr "" -#: ../../en/units/angle.rst:50 ef394f389e6d44e7b4e24923301ce124 -msgid "|init.svg|" +#: ../../en/units/angle.rst:50 4ebc27b07b814f358f665a45d20f90b4 +msgid "|init.png|" +msgstr "" + +#: ../../en/refs/unit.angle.ref:8 75d7d88d04184e8985b88405515524cc +msgid "init.png" msgstr "" -#: ../../en/units/angle.rst:54 ae5ae21222fc48a6a579de920cff1ea3 +#: ../../en/units/angle.rst:54 a93c4c4397e64186a1931a1a693afb8f msgid "Methods" msgstr "" -#: ../../en/units/angle.rst:58 f455e3e6599a45db985536ee3512c865 +#: ../../en/units/angle.rst:58 fe91b46713c9489697b6ea2a697dbe0d msgid "" "This method allows reading the Angle's rotation value and returning an " "integer value. The range is 0-65535." msgstr "此方法允许读取Angle的旋转值并返回一个整型数值。范围为0-65535。" -#: ../../en/units/angle.rst:62 6749e6cb641449848dd792b01ef29b45 -msgid "|get_value.svg|" +#: ../../en/units/angle.rst:62 76ada92a60784878a951960a941ca672 +msgid "|get_value.png|" +msgstr "" + +#: ../../en/refs/unit.angle.ref:10 8c75eb9d103a4faf9ee71aaa3d838ca7 +msgid "get_value.png" msgstr "" -#: ../../en/units/angle.rst:67 dfa8ed43b0034ddb90b154fc3da7434c +#: ../../en/units/angle.rst:67 7b67e883c96149b7a1dac619a0a078f8 msgid "" "This method allows reading the voltage value of Angle, and the return " "value is a floating point value." msgstr "此方法允许读取Angle的电压值, 返回值为一个浮点型数值。" -#: ../../en/units/angle.rst:71 85732b9876454cf28303581eea39ff10 -msgid "|get_voltage.svg|" +#: ../../en/units/angle.rst:71 7883d2ad2c3e4aefb4e1e4cbf2e12ac2 +msgid "|get_voltage.png|" +msgstr "" + +#: ../../en/refs/unit.angle.ref:12 ad3c955e67214a61842dc8d6dff0eebf +msgid "get_voltage.png" msgstr "" +#~ msgid "example.svg" +#~ msgstr "" + +#~ msgid "init.svg" +#~ msgstr "" + +#~ msgid "get_value.svg" +#~ msgstr "" + +#~ msgid "get_voltage.svg" +#~ msgstr "" + +#~ msgid "|example.svg|" +#~ msgstr "" + +#~ msgid "|init.svg|" +#~ msgstr "" + +#~ msgid "|get_value.svg|" +#~ msgstr "" + +#~ msgid "|get_voltage.svg|" +#~ msgstr "" + diff --git a/docs/locales/zh_CN/LC_MESSAGES/units/buzzer.po b/docs/locales/zh_CN/LC_MESSAGES/units/buzzer.po index 065201cd..e9fdbd00 100644 --- a/docs/locales/zh_CN/LC_MESSAGES/units/buzzer.po +++ b/docs/locales/zh_CN/LC_MESSAGES/units/buzzer.po @@ -9,7 +9,7 @@ msgid "" msgstr "" "Project-Id-Version: UIFlow2 Programming Guide \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-01-16 15:35+0800\n" +"POT-Creation-Date: 2025-04-18 15:58+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language: zh_CN\n" @@ -20,159 +20,195 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.16.0\n" -#: ../../en/units/buzzer.rst:2 90131fafec824063bab591b9027fbfd2 +#: ../../en/units/buzzer.rst:2 2c1f8b3771c2471f8e4303e720c29175 msgid "Buzzer Unit" msgstr "" -#: ../../en/units/buzzer.rst:6 874417f3ccf643f58dee6629869f45c1 +#: ../../en/units/buzzer.rst:6 de1225c93a5a4c208ea768be14f0d28c msgid "Support the following products:" msgstr "" -#: ../../en/units/buzzer.rst:8 8b528a6dae8143459f59c1871f76a35a +#: ../../en/units/buzzer.rst:8 0aef0d83ec424a61bba0d2ce0c5106e7 msgid "|Buzzer|" msgstr "" -#: ../../en/refs/unit.buzzer.ref 4650642919354948a82db63da32a1888 +#: ../../en/refs/unit.buzzer.ref 17abf892c84f4380b44072429241375f msgid "Buzzer" msgstr "" -#: ../../en/units/buzzer.rst:11 0c3e439dc4244cb182772a6c950701f6 +#: ../../en/units/buzzer.rst:11 143aa3bae63047ce907303902020990d msgid "Micropython Example:" msgstr "" -#: ../../en/units/buzzer.rst:18 b427d4ae8efe4ebcbe28f6a2358db390 +#: ../../en/units/buzzer.rst:18 962ff4854bc94946b91a163bf4fa9d0f msgid "UIFLOW2 Example:" msgstr "" -#: ../../en/units/buzzer.rst:20 02fd3b657a674d308a51c9205b9a97b8 +#: ../../en/units/buzzer.rst:20 8a2c18fdef1e44f5a56bf36fcb145d76 msgid "|example.png|" msgstr "" -#: ../../en/refs/unit.buzzer.ref:18 ae846cabe153420886c795e688b49401 +#: ../../en/refs/unit.buzzer.ref:18 8433b06fc1ef4425b9ab073c23828ed8 msgid "example.png" msgstr "" -#: ../../en/units/buzzer.rst:25 95f29917380f4f328fa519bfc6a769da +#: ../../en/units/buzzer.rst:25 0e616883fa4740b5be81b805785bffd3 msgid "|cores3_buzzer_example.m5f2|" msgstr "" -#: ../../en/units/buzzer.rst:29 69c3219a2bfa4673837cb442663ad6bc +#: ../../en/units/buzzer.rst:29 3bb26db8b166494395f52ed14e746ba2 msgid "class BuzzerUnit" msgstr "" -#: ../../en/units/buzzer.rst:32 a73fdc3883304cc0b5e5026dabc2e685 +#: ../../en/units/buzzer.rst:32 6851e233f65a4cb484018d01392050f2 msgid "Constructors" msgstr "" -#: ../../en/units/buzzer.rst:36 6853fc8596774cc986c81b075b1803fb +#: ../../en/units/buzzer.rst:36 6a58118ad9824ed4a29966a6f7def358 msgid "Create an BuzzerUnit object." msgstr "" -#: ../../en/units/buzzer.rst:39 2771e0a90fb34324a4315cec18e8027e +#: ../../en/units/buzzer.rst:39 6ca342211ea74867a13093bd80110a72 msgid "The parameters are:" msgstr "" -#: ../../en/units/buzzer.rst:39 d559da589262492e81590cb7c45b974c +#: ../../en/units/buzzer.rst:39 291e80d4374c45e09114cfade17e7f0a msgid "``port`` Is the pin number of the port" msgstr "" #: ../../en/units/buzzer.rst:41 ../../en/units/buzzer.rst:57 #: ../../en/units/buzzer.rst:68 ../../en/units/buzzer.rst:79 #: ../../en/units/buzzer.rst:88 ../../en/units/buzzer.rst:97 -#: 26bded08c4594707b84fdcf9ae2f189a 6eea00bb208149628c64a4ef4550e867 -#: 7d0938895dcc4f85a091062f4cae692c b309202ef8174035964f9086f35f10b1 -#: c194fc217d1341118621c5f1a74f4ffa e9cd49fb04f74499a186636673b48451 +#: 249e6bb81f3448bb82fbc0d6e58559f1 781ef5276e974285ab0529e0034f8d5e +#: 7bb16caf57784080b1707b01c68c045b b95a59bb54674e86a19c44dd22490087 +#: cd7005e59e204dfeaea32a92b6deacc4 d7df8a06282e4f8ea39a9c6afa515307 msgid "UIFLOW2:" msgstr "" -#: ../../en/units/buzzer.rst:43 cefd99991dae45d8a28baf7b14eb2ece -msgid "|init.svg|" +#: ../../en/units/buzzer.rst:43 3440758ee5f74d4c9fbbef7350e72178 +msgid "|init.png|" msgstr "" -#: ../../en/refs/unit.buzzer.ref:6 fa85e4f91cd149358eb812c3fec2415b -msgid "init.svg" +#: ../../en/refs/unit.buzzer.ref:6 acf4a25db227447eae8dca5e26aa4df6 +msgid "init.png" msgstr "" -#: ../../en/units/buzzer.rst:47 dafeb5b836294cb19156f7274df7d542 +#: ../../en/units/buzzer.rst:47 a6340b41c7574a479292231e911f9809 msgid "Methods" msgstr "" -#: ../../en/units/buzzer.rst:51 7e26ece0d907435384637c7da8704e83 +#: ../../en/units/buzzer.rst:51 b016cad124ba44218857cbeb8f26cb67 msgid "Play buzzer once." msgstr "" -#: ../../en/units/buzzer.rst 105b0909d50148b488377288b7be7c1b -#: c67f609cf3cb4afd80c85cbe900a7ae9 d6bbbcd4ecf04490b1ef9ac831dc0bcf +#: ../../en/units/buzzer.rst 220d1212e8ed48c8b41dccd74525290b +#: 83a9d42e3f7d4bf8bfa3a92befdd7cae c4ede305eb764f7c8460b2ca697bf51f msgid "Parameters" msgstr "" #: ../../en/units/buzzer.rst:53 ../../en/units/buzzer.rst:66 -#: 0a518d4cc9b446ecaf68fec5f6fd5648 47372ef62206407d87951919a577ecf8 +#: cf9d229287564d18b253f0b0e79ef9e0 fabdb004dc8748cfa69672b417d6fca2 msgid "The frequency of the vibration, range is 100 - 10000Hz." msgstr "" #: ../../en/units/buzzer.rst:54 ../../en/units/buzzer.rst:77 -#: 87fec023516d4e748d91ed45c78d3db3 d448198e02ab4c73afa69df493fffa8f +#: 86cc5777329c4b8ba7d4d2d07dc16368 959c0c1a0e8d41bd9b29b78fdc6ed771 msgid "The duty cycle of the vibration, range is 0 - 100." msgstr "" -#: ../../en/units/buzzer.rst:55 67e5911db96446529af96a861850d456 +#: ../../en/units/buzzer.rst:55 79ce179b16d74846bfa0222623fa306d msgid "The duration of the vibration, range is 0 - 10000ms." msgstr "" -#: ../../en/units/buzzer.rst:59 b1277ec314744f16946b4fef3e714083 -msgid "|once.svg|" +#: ../../en/units/buzzer.rst:59 16a66fe421524288a42c393ab9e30057 +msgid "|once.png|" msgstr "" -#: ../../en/refs/unit.buzzer.ref:8 5438b26ad4e3423a9513c9b3dadf281a -msgid "once.svg" +#: ../../en/refs/unit.buzzer.ref:8 0bdbcb5e821f4cca9ace571c265660ae +msgid "once.png" msgstr "" -#: ../../en/units/buzzer.rst:64 9b6a713083c84cfa9e917ad655b7d992 +#: ../../en/units/buzzer.rst:64 5c82b07b8e944378be89ddf2e2abae9c msgid "Set the frequency of the buzzer." msgstr "" -#: ../../en/units/buzzer.rst:70 bb6da0ff1fcc43ccac44f261593b11da -msgid "|set_freq.svg|" +#: ../../en/units/buzzer.rst:70 092a8749e6b74be083f4eb3b232aa3f9 +msgid "|set_freq.png|" msgstr "" -#: ../../en/refs/unit.buzzer.ref:10 21867ad14db447babf5cd0292c1ab68a -msgid "set_freq.svg" +#: ../../en/refs/unit.buzzer.ref:10 abf34f268cad434ab5466ab99abb5180 +msgid "set_freq.png" msgstr "" -#: ../../en/units/buzzer.rst:75 9e0a557f6a574665a5b2104fb0aed303 +#: ../../en/units/buzzer.rst:75 54bebc85eea04ea6a5e9260beca55d03 msgid "Set the duty cycle of the buzzer." msgstr "" -#: ../../en/units/buzzer.rst:81 6c76809672f84121ae4b3bad5d6d9657 -msgid "|set_duty.svg|" +#: ../../en/units/buzzer.rst:81 9f2179d17aa64fdcab5101736ec323f5 +msgid "|set_duty.png|" msgstr "" -#: ../../en/refs/unit.buzzer.ref:12 d369e8cbfd9a42e9821d25e100dd4770 -msgid "set_duty.svg" +#: ../../en/refs/unit.buzzer.ref:12 0ae00a9ff5b44cd7af7d486aa00b73fd +msgid "set_duty.png" msgstr "" -#: ../../en/units/buzzer.rst:86 07b6687cd99c450397baf248b0268134 +#: ../../en/units/buzzer.rst:86 1c021657055a451db3626fbb0258590a msgid "Turn off the buzzer." msgstr "" -#: ../../en/units/buzzer.rst:90 f3d3441e1bed4454a306c510d9b6a3c6 -msgid "|turn_off.svg|" +#: ../../en/units/buzzer.rst:90 ff52e34aed6f49b4995a8f92a27d4712 +msgid "|turn_off.png|" msgstr "" -#: ../../en/refs/unit.buzzer.ref:14 1fa857f3ee7f4397a4a23073d4624d68 -msgid "turn_off.svg" +#: ../../en/refs/unit.buzzer.ref:14 87e95a0a965e4559a11a045f2c3671b9 +msgid "turn_off.png" msgstr "" -#: ../../en/units/buzzer.rst:95 673cfde8b8274ccb9d398503da76d274 +#: ../../en/units/buzzer.rst:95 73eac0f345064bff8a14104a81b7e69d msgid "Deinitialize the buzzer." msgstr "" -#: ../../en/units/buzzer.rst:99 c75cc3b0fbbd4c9087cc635637ef70f2 -msgid "|deinit.svg|" +#: ../../en/units/buzzer.rst:99 d960d0b3fb5448dda223d72ccedf3541 +msgid "|deinit.png|" msgstr "" -#: ../../en/refs/unit.buzzer.ref:16 762378146e4743d6b5d9acf258573964 -msgid "deinit.svg" +#: ../../en/refs/unit.buzzer.ref:16 ca1ea37ed3ea4e5f85aeb7958f9d9ea8 +msgid "deinit.png" msgstr "" +#~ msgid "|init.svg|" +#~ msgstr "" + +#~ msgid "init.svg" +#~ msgstr "" + +#~ msgid "|once.svg|" +#~ msgstr "" + +#~ msgid "once.svg" +#~ msgstr "" + +#~ msgid "|set_freq.svg|" +#~ msgstr "" + +#~ msgid "set_freq.svg" +#~ msgstr "" + +#~ msgid "|set_duty.svg|" +#~ msgstr "" + +#~ msgid "set_duty.svg" +#~ msgstr "" + +#~ msgid "|turn_off.svg|" +#~ msgstr "" + +#~ msgid "turn_off.svg" +#~ msgstr "" + +#~ msgid "|deinit.svg|" +#~ msgstr "" + +#~ msgid "deinit.svg" +#~ msgstr "" + diff --git a/docs/locales/zh_CN/LC_MESSAGES/units/bytebutton.po b/docs/locales/zh_CN/LC_MESSAGES/units/bytebutton.po index fb139792..7d273d28 100644 --- a/docs/locales/zh_CN/LC_MESSAGES/units/bytebutton.po +++ b/docs/locales/zh_CN/LC_MESSAGES/units/bytebutton.po @@ -9,7 +9,7 @@ msgid "" msgstr "" "Project-Id-Version: UIFlow2 Programming Guide \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-01-16 15:35+0800\n" +"POT-Creation-Date: 2025-04-03 10:58+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language: zh_CN\n" @@ -20,11 +20,11 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.16.0\n" -#: ../../en/units/bytebutton.rst:3 56e1488e2bd94d4691bb95f63e8227c8 +#: ../../en/units/bytebutton.rst:3 a1e6d173526f4f42b6fcaa6800192ed0 msgid "ByteButton Unit" msgstr "" -#: ../../en/units/bytebutton.rst:7 d9424c7b125645beb310c0e8138c41c4 +#: ../../en/units/bytebutton.rst:7 76106ed0e12f4eeba41ad2bc8167834a msgid "" "Unit ByteButton is an 8-button touch switch input unit equipped with 8 " "button inputs and 9 WS2812C RGB LEDs. It uses the STM32 microcontroller " @@ -36,66 +36,66 @@ msgid "" "exhibitions." msgstr "" -#: ../../en/units/bytebutton.rst:9 4677f62031134e5b8cff806d32cb9092 +#: ../../en/units/bytebutton.rst:9 0a35cf27507f483e8f575594b9e3d079 msgid "Support the following products:" msgstr "" -#: ../../en/units/bytebutton.rst:11 6320c3881e2c4bcc8a8e385e3bd5f7d3 +#: ../../en/units/bytebutton.rst:11 66ea794e7c0f476cb42df733116fc507 msgid "|ByteButtonUnit|" msgstr "" -#: ../../en/refs/unit.bytebutton.ref c21875fb9217481fb7fe3aa48810bfe4 +#: ../../en/refs/unit.bytebutton.ref 2ed6748047b64d31b61444504f8df882 msgid "ByteButtonUnit" msgstr "" -#: ../../en/units/bytebutton.rst:13 10842054e5394e718a11f5cd9bbdcddb +#: ../../en/units/bytebutton.rst:13 bf2f5e07f1c749e58eb4d9fb9777aef7 msgid "Micropython Example:" msgstr "" -#: ../../en/units/bytebutton.rst:20 5c5c79244ed341ef9631968fd44fe0f7 +#: ../../en/units/bytebutton.rst:20 d66bc0625fe6447ab5cc23d7f8af0b41 msgid "UIFLOW2 Example:" msgstr "" -#: ../../en/units/bytebutton.rst:22 532c09a36c484ae4b1ff50d0e3fcc7e0 +#: ../../en/units/bytebutton.rst:22 ae0305a648da4affbdb1d4dd1a883db7 msgid "|example.png|" msgstr "" -#: ../../en/refs/unit.bytebutton.ref:31 c3974300bfc6428bbd5378efc63ad1a5 +#: ../../en/refs/unit.bytebutton.ref:31 42f7e4ee5eca46e58cbd1bac3b4d0aa8 msgid "example.png" msgstr "" -#: ../../en/units/bytebutton.rst:26 e594144e0aea4150ab3aa60af91617c6 +#: ../../en/units/bytebutton.rst:26 fcd27012abd44ba097e91402c6aeeaa5 msgid "|bytebutton_cores3_example.m5f2|" msgstr "" -#: ../../en/units/bytebutton.rst:29 2cc41e804f56429193680c93700faa8c +#: ../../en/units/bytebutton.rst:29 91d82e4cfa66490da7b0329fe598770b msgid "class ByteButtonUnit" msgstr "" -#: ../../en/units/bytebutton.rst:32 b82c48313b6c45dc83d929c4f5f7f559 +#: ../../en/units/bytebutton.rst:32 c06c0d89097f471898d449926f0378b5 msgid "Constructors" msgstr "" -#: ../../en/units/bytebutton.rst:36 61584fb662ce4fd19e47764a8ff98b16 +#: ../../en/units/bytebutton.rst:36 9076c935f4d34d4e9a5ecf8aea0e18fb msgid "Initialize the ByteButtonUnit with a specified I2C address." msgstr "" -#: ../../en/units/bytebutton.rst 141241205deb4cfab7770dafa69e4f06 -#: 1741b4c340394d5a9ac6b6b38271e51a 243b51cad34b44b98daa71a74d3b3872 -#: 3e6426f7a2504a82acda2b5648fe1d67 45abc051e774486d844606f2362aebe5 -#: 6ea9b58d41a040a6a750462129aba035 77a483ea1ec941ed8fb5352cbe563a6d -#: 7ad030161114461d94a78b878a2cebdd 85dae81a3e9d40499b7bd34dc4a1b5da -#: 954efef80d754a1bab8ad85e275d08e9 ad028011c89c4842a3046df7e69eac14 -#: d4b79121ecd54128b9f26f5b6a939a0e eef13452b08b48f79ec1d5534e8a786d -#: fc43959e451e45af85e702cf2645413f +#: ../../en/units/bytebutton.rst 08850501948d4aefb2091874b0635c13 +#: 097ba2c73eaa4a478a99c2dada59f5a0 0cb8c71dab2646599c4545fe2e6476b4 +#: 2831a81042d243edb8cd643caf02639e 30f5914544134ed988e700d64aa1620a +#: 5afb45805db5438aab0c07b81d9a394e 6280ad5e94f843979f660d251a9486fb +#: 6d783dff8f0e40679f355a95b19ee568 7a90e5f08b8e45ed8b7512472f74d638 +#: 8e94d10848684d558c1166080e9bb247 d897c121c0bd49849e06d5c8bc472b71 +#: dcc4ea49d54747779856e9f4553043ff eb8fd08040884fcb9b7d1a16e932652a +#: f99db42b030048b7bc1ac3bb77a9dd21 msgid "Parameters" msgstr "" -#: ../../en/units/bytebutton.rst:38 c7bebe5429294753aeede29f152dffd5 +#: ../../en/units/bytebutton.rst:38 12dd5a765692466485d69f60f33b4caf msgid "The I2C interface instance for communication." msgstr "" -#: ../../en/units/bytebutton.rst:39 63e3f12800a64146bcec2ce6faf25c4b +#: ../../en/units/bytebutton.rst:39 2fba8c62eecc4beb9538601df1500f00 msgid "The I2C address of the ByteButtonUnit, default is 0x47." msgstr "" @@ -107,328 +107,331 @@ msgstr "" #: ../../en/units/bytebutton.rst:155 ../../en/units/bytebutton.rst:165 #: ../../en/units/bytebutton.rst:174 ../../en/units/bytebutton.rst:212 #: ../../en/units/bytebutton.rst:221 ../../en/units/bytebutton.rst:231 -#: ../../en/units/bytebutton.rst:240 03595c1a3ee546219bea34a73e483fb2 -#: 0cdb0d3216f64d089bc74e83c09dc2a5 1207081764e94ffd9f6dcb2fe49e66af -#: 2569d30253174a6da9bb7eab8eb8d396 35ee7f757a03402a829ac484ac5db38c -#: 38780f02f1344b48bf9e01019342cc92 3cab6a8d2daf4ee698cd7c23c8cbb112 -#: 3f396c809c074186b677455458182ffa 45ce0a8aeb6e4a32b51662dfe27a5663 -#: 707c789f9bce44f893a235a8ee81bcd8 724104f485a441b49e5934bc4e72abed -#: 824b1a861f5245c4ad39378276f0cef6 a92af08fd4df46efa00f9d71be3de9f1 -#: b44c42376c6e48e6856bafd598ac8cba b7ca269ccb754aa78ecb8bd7c2b91f1a -#: dc6d2363d3ec40e18c54d19a58f65067 eb772f1dba154d2183ade0aadb452aac +#: ../../en/units/bytebutton.rst:240 19cc74b76b7f4c139cf5321653472192 +#: 1c6cd1ef0f384af8beadda2b1add1066 4953a4d6f9884a278942936531e69d22 +#: 4b0a5c0dca1446a4bf620066c99f788a 608fea76f7ee40f1a561fb6822bc94d6 +#: 74499db438ee45b0b9d494d83b5b8121 75021df79fa540358850821894585244 +#: 7c2714c308f44ee6a227c811576c92f5 84553ce7658343e7ba4c468c6c7f5267 +#: 9571c2c9031e45c090988fcdb4e92736 a738627bd2d74d5cb7140e1bc337c999 +#: b13b2aaca9cc4f879839b9637b8b82c9 b4df0d59453a439683d6d3e385797172 +#: d3edf7e42cf54217b80cd60a2725b83c d6f2e40b5e9d4df78fb1560a15e0803e +#: e051a46fa79b49a2bc9c1c93d7c721cd e5033fd7c3cc41e5b5243749ad84ef99 msgid "UIFLOW2:" msgstr "" -#: ../../en/units/bytebutton.rst:43 02043c973cba4a3d994b1c835460ea7d +#: ../../en/units/bytebutton.rst:43 56fc9f4006994b06b4abc83199fb1044 msgid "|init.png|" msgstr "" -#: ../../en/refs/unit.bytebutton.ref:6 335256c3b8b84f26a1b67099d408a5ca +#: ../../en/refs/unit.bytebutton.ref:6 14e6d00970514a59b94f86345ebc8571 msgid "init.png" msgstr "" -#: ../../en/units/bytebutton.rst:47 7de6dc3764734904977e018b866e4ee0 +#: ../../en/units/bytebutton.rst:47 ec1b3ba715a24e729bd7fa266ac29412 msgid "Methods" msgstr "" -#: ../../en/units/bytebutton.rst:51 4a4af46df8b64d6d90cf6e8b3026ef2c +#: ../../en/units/bytebutton.rst:51 401e0622fbed42b7a466a8c36a6f758b msgid "" "Get the status of all buttons as an integer, where each bit represents " "the state of each button." msgstr "" -#: ../../en/units/bytebutton.rst:56 9def02decb894ed2bb6b799488c379e6 +#: ../../en/units/bytebutton.rst:56 ee4db0336a9844c9bbf992a0ccd3ce43 msgid "|get_byte_button_status.png|" msgstr "" -#: ../../en/refs/unit.bytebutton.ref:7 f7127e2612aa4aef8a3e2ce5a2eca413 +#: ../../en/refs/unit.bytebutton.ref:7 4a5fb1730f954f95bf40b759ab9e1e14 msgid "get_byte_button_status.png" msgstr "" -#: ../../en/units/bytebutton.rst:60 0113ecb816b44950ae4c980b75dece1d +#: ../../en/units/bytebutton.rst:60 d9016f670cd74cfb96537869a0f262fb msgid "Get the state of a specific button." msgstr "" -#: ../../en/units/bytebutton.rst:62 fa4774061c2c43cdb502315703532e82 +#: ../../en/units/bytebutton.rst:62 3848f58484fd43b4a41f8f2cd9c110a8 msgid "The index of the button (0-7)." msgstr "" -#: ../../en/units/bytebutton.rst:66 9e02810a869b4737993261767163550b +#: ../../en/units/bytebutton.rst:66 3cdd6c840c444d94bb492fb2057e43b4 msgid "|get_button_state.png|" msgstr "" -#: ../../en/refs/unit.bytebutton.ref:8 0585e96d1e554cc7bf2d67b69c46e510 +#: ../../en/refs/unit.bytebutton.ref:8 da03017982534b8a98258d6b2c521e9d msgid "get_button_state.png" msgstr "" -#: ../../en/units/bytebutton.rst:70 72a9abc60e0d4128b0e9a375afed9b72 +#: ../../en/units/bytebutton.rst:70 d302ae0b5c164c93b404e9deca282324 msgid "Get the current LED show mode." msgstr "" -#: ../../en/units/bytebutton.rst:74 f81b0fc6c8b84c57a181eaac9a547a94 +#: ../../en/units/bytebutton.rst:74 2dc353f222294c3d8d71b6632ad544ab msgid "|get_led_show_mode.png|" msgstr "" -#: ../../en/refs/unit.bytebutton.ref:9 ab1d825829014ee59295317fd650849a +#: ../../en/refs/unit.bytebutton.ref:9 a98cdae56d8e40b4a980f7946999a23f msgid "get_led_show_mode.png" msgstr "" -#: ../../en/units/bytebutton.rst:78 9c8a54ddc58e48ba9d35798c5c112248 +#: ../../en/units/bytebutton.rst:78 944e459c72104bafbde474961de55ed3 msgid "Set the LED show mode." msgstr "" -#: ../../en/units/bytebutton.rst:80 b66a9edfedf14c1e9e17dca622111982 +#: ../../en/units/bytebutton.rst:80 0e4436215a2141d49371c5f61c5c1017 msgid "" "The LED show mode to set. Options: - ``BYTEBUTTON_LED_USER_MODE``: 0" " - ``BYTEBUTTON_LED_SYS_MODE``: 1" msgstr "" -#: ../../en/units/bytebutton.rst:80 730c54cdcf6d49559b526fb7d18539c7 +#: ../../en/units/bytebutton.rst:80 080d260ff05742789062d08fe3baa7b7 msgid "The LED show mode to set." msgstr "" -#: ../../en/units/bytebutton.rst:84 7faac104d19e4909af68d3f434bca996 +#: ../../en/units/bytebutton.rst:84 c6e8839271214034a6f1d0c4329974be msgid "Options:" msgstr "" -#: ../../en/units/bytebutton.rst:83 f9d95199db59459cabc4e4294a2f7566 +#: ../../en/units/bytebutton.rst:83 892fc6ea55ce4429818a676545dfaf25 msgid "``BYTEBUTTON_LED_USER_MODE``: 0" msgstr "" -#: ../../en/units/bytebutton.rst:84 2b70c428e05f47f0b92719418c7a1651 +#: ../../en/units/bytebutton.rst:84 86a846b4da0e46d380810e1518fc9dc4 msgid "``BYTEBUTTON_LED_SYS_MODE``: 1" msgstr "" -#: ../../en/units/bytebutton.rst:88 9cc402819b0d4663b49372fbf2609856 +#: ../../en/units/bytebutton.rst:88 5a9734901880432b8e43aa8193d2bab4 msgid "|set_led_show_mode.png|" msgstr "" -#: ../../en/refs/unit.bytebutton.ref:10 4a62184540854b3582a5e65c4941bcc9 +#: ../../en/refs/unit.bytebutton.ref:10 b5829b3005714a4a9d593c73256ad4e5 msgid "set_led_show_mode.png" msgstr "" -#: ../../en/units/bytebutton.rst:92 ab9d1b08638240528b3f6047dce15c49 +#: ../../en/units/bytebutton.rst:92 8a7266f857a149008c44231f42be1fdf msgid "Set the brightness of a specific LED." msgstr "" #: ../../en/units/bytebutton.rst:94 ../../en/units/bytebutton.rst:105 #: ../../en/units/bytebutton.rst:115 ../../en/units/bytebutton.rst:130 #: ../../en/units/bytebutton.rst:188 ../../en/units/bytebutton.rst:195 -#: 1983dc97099748bfb7dcf14ee6c61841 26c5c384dfb44510b281d50dcda2ba2a -#: 2a7d13fa52f140c68aba2785521d9498 556411fe6bd441c1a312c92eef4f68e7 -#: 7d37e90ea7204dde83ee0db35638abb5 dee2f1e866894358a702baf030a1ccc0 +#: 3c80efcc17f04270a0b3d8a19918c2cc 495a121ff8ef4bfaa32d1e7f5442f410 +#: 929f3a46943344f78a7a94151de47277 af096e2561274a3889bb407d6d8798d7 +#: bd5a3b87709f494890c90eed7fa2796d ddd1dc4cecfb47eea860acca9e37c573 msgid "The index of the LED (0-7)." msgstr "" #: ../../en/units/bytebutton.rst:95 ../../en/units/bytebutton.rst:144 -#: a9780dbfdd824cb1a8118366abf4d650 c57c0416305f4429a2806b6cfa98c628 +#: 0941c8c6d137479fb1be70e16c4d1a28 b0c8ab7abf6242dd9f3fa002e9e679be msgid "The brightness level (0-255)." msgstr "" -#: ../../en/units/bytebutton.rst:99 d639a1ce4e4f4b5a9caf374778f2a0a5 +#: ../../en/units/bytebutton.rst:99 ee00e8bd685644e29a3c4b928b5bb59c msgid "|set_led_brightness.png|" msgstr "" -#: ../../en/refs/unit.bytebutton.ref:11 af5a1dcdcabb40ccaf18c83434b323c9 +#: ../../en/refs/unit.bytebutton.ref:11 d0fb238227214cf6b6b4897d48c4de58 msgid "set_led_brightness.png" msgstr "" -#: ../../en/units/bytebutton.rst:103 d2506c50468c4724b63521e4eead700a +#: ../../en/units/bytebutton.rst:103 fec52c235b3a4f869f1015a63cf82f93 msgid "Get the brightness of a specific LED." msgstr "" -#: ../../en/units/bytebutton.rst:109 63a5d0da570f49f2a452f906987b3411 +#: ../../en/units/bytebutton.rst:109 abb03a88192948ab9c43a730dc66af59 msgid "|get_led_brightness.png|" msgstr "" -#: ../../en/refs/unit.bytebutton.ref:12 7452033eb6314eea92dd4b49a1ae9361 +#: ../../en/refs/unit.bytebutton.ref:12 d6353222969d43928a9daf2972238dad msgid "get_led_brightness.png" msgstr "" -#: ../../en/units/bytebutton.rst:113 90adbad0ac4b4853ad16b3efd7488052 +#: ../../en/units/bytebutton.rst:113 8d42e0c5636b41f29258b4d37ac1385e msgid "Set the color of a specific LED." msgstr "" #: ../../en/units/bytebutton.rst:116 ../../en/units/bytebutton.rst:163 -#: 51447468b89f4596876020f3da133405 6378d84b03f24893ad52637ce2fea961 +#: 27cf59c5e2854b5982116217c65b8e6e ed4021e5fb224e7186c09be091cca707 msgid "The RGB888 color value to set." msgstr "" #: ../../en/units/bytebutton.rst:117 ../../en/units/bytebutton.rst:131 -#: 0dca72cf35e548c2bdcf3ba378bbb811 186d1a4f8e634130b04fd8241d68db8b +#: 893dbebb39094477bd8cdd6afe428d84 a04fa0159b1441c98e6469c445e2bb1b msgid "The LED show mode, default is BYTEBUTTON_LED_SYS_MODE." msgstr "" #: ../../en/units/bytebutton.rst:118 ../../en/units/bytebutton.rst:132 -#: 2470910eaf5745248e65fdc909abe83b abac32bcd5e94755905d7c445103ee94 +#: 108788845e4247d187297001671d8c4c ba20661de8734751b009b13e1f4945a4 msgid "Whether the button is pressed (affects color in SYS mode)." msgstr "" -#: ../../en/units/bytebutton.rst:122 4b5863d2bdb24fc9a36e5293badb77e3 +#: ../../en/units/bytebutton.rst:122 552bafe185f647aa8aa0699c83b97a00 msgid "|set_sys_mode_led_color.png|" msgstr "" -#: ../../en/refs/unit.bytebutton.ref:13 59fa090e5a784aa0aaf19599af05bfb6 +#: ../../en/refs/unit.bytebutton.ref:13 2abd9f309a1c408988b662eb1dd50380 msgid "set_sys_mode_led_color.png" msgstr "" -#: ../../en/units/bytebutton.rst:124 b6b42dd277e14b87aafa4122e0aea36a +#: ../../en/units/bytebutton.rst:124 160313f703c043bba0977f74fa6167d9 msgid "|set_user_mode_led_color.png|" msgstr "" -#: ../../en/refs/unit.bytebutton.ref:14 744a2196bee94110bd179ef825c0a1f0 +#: ../../en/refs/unit.bytebutton.ref:14 2b1d35584b76469ea5f1f525ddf98de4 msgid "set_user_mode_led_color.png" msgstr "" -#: ../../en/units/bytebutton.rst:128 8fa2079c153c4fd18eb9d0c09dd121c0 +#: ../../en/units/bytebutton.rst:128 e4319059790a475684389804969ccace msgid "Get the color of a specific LED." msgstr "" -#: ../../en/units/bytebutton.rst:136 8ad8998703844b088c10797899614402 +#: ../../en/units/bytebutton.rst:136 580c1b2ed6c54589b6bc52d278d4963b msgid "|get_sys_mode_led_color.png|" msgstr "" -#: ../../en/refs/unit.bytebutton.ref:15 d8213fb0f2e448eeaafe96f69ef46c3b +#: ../../en/refs/unit.bytebutton.ref:15 d06bd735f3614d008d1f51a523f72192 msgid "get_sys_mode_led_color.png" msgstr "" -#: ../../en/units/bytebutton.rst:138 0127c26b58e3418a9b6f78fe9a81c2a1 +#: ../../en/units/bytebutton.rst:138 190ae451b8344b39a4b6aebc208d44ec msgid "|get_user_mode_led_color.png|" msgstr "" -#: ../../en/refs/unit.bytebutton.ref:16 f324d3d99ada4fdcb6ccbfbb0b850bd4 +#: ../../en/refs/unit.bytebutton.ref:16 0bd6906b564a409a9ed3dce373f27791 msgid "get_user_mode_led_color.png" msgstr "" -#: ../../en/units/bytebutton.rst:142 67023ea6603e49acae9d18964ba94ae5 +#: ../../en/units/bytebutton.rst:142 e13651f0191040749fe3913b7c0497f2 msgid "Set the brightness of the indicator LED." msgstr "" -#: ../../en/units/bytebutton.rst:148 1ced4ed80ff14277bdcfce3a5dc2d53d +#: ../../en/units/bytebutton.rst:148 ed20494a2e7d41f4ae51f9a99b6ed5be msgid "|set_indicator_brightness.png|" msgstr "" -#: ../../en/refs/unit.bytebutton.ref:17 7dfd8861fb4646578db886d3238ae654 +#: ../../en/refs/unit.bytebutton.ref:17 9f106fde762e48ab973bde4831b30765 msgid "set_indicator_brightness.png" msgstr "" -#: ../../en/units/bytebutton.rst:152 df4ab52f25274783859ce8677000ce9f +#: ../../en/units/bytebutton.rst:152 ef22cb7d915047519fddb4e6aa9fa01c msgid "Get the brightness of the indicator LED." msgstr "" -#: ../../en/units/bytebutton.rst:157 a2c1679c56b042ad84111ecde85b1cd2 +#: ../../en/units/bytebutton.rst:157 79ee29a9fa8e4565ad1ff104ffc29687 msgid "|get_indicator_brightness.png|" msgstr "" -#: ../../en/refs/unit.bytebutton.ref:18 6defb9c84fd54cb392749c8bc9493ec2 +#: ../../en/refs/unit.bytebutton.ref:18 0cfb75560b03408fa1aaebb91bfec970 msgid "get_indicator_brightness.png" msgstr "" -#: ../../en/units/bytebutton.rst:161 0addea696cf64286b4b892358e9a0eaf +#: ../../en/units/bytebutton.rst:161 b50f721b90864b47a5643ca5b225e287 msgid "Set the color of the indicator LED in RGB888 format." msgstr "" -#: ../../en/units/bytebutton.rst:167 a0daf33d8b0445c7875b5b064cec2e18 +#: ../../en/units/bytebutton.rst:167 b6be273142554fa4b413b33c16393823 msgid "|set_indicator_color.png|" msgstr "" -#: ../../en/refs/unit.bytebutton.ref:19 052ed3acdd754d85b45fd252e9288b98 +#: ../../en/refs/unit.bytebutton.ref:19 eff0f82139214b7980a29446f6fee250 msgid "set_indicator_color.png" msgstr "" -#: ../../en/units/bytebutton.rst:171 0ba61903101c4f8cbe947261c837e248 +#: ../../en/units/bytebutton.rst:171 c8f93b665bb84fb4ad32affe65dd17a1 msgid "Get the color of the indicator LED in RGB888 format." msgstr "" -#: ../../en/units/bytebutton.rst:176 be90e7b0f97d4379840a3d8fdeaca515 +#: ../../en/units/bytebutton.rst:176 1c54f61461684f00886793fa6a1b9a8b msgid "|get_indicator_color.png|" msgstr "" -#: ../../en/refs/unit.bytebutton.ref:20 9bddaa9d50c54e56a070290dc9da773d +#: ../../en/refs/unit.bytebutton.ref:20 642f7e7c46494bcab9bcd8a94ce907c2 msgid "get_indicator_color.png" msgstr "" -#: ../../en/units/bytebutton.rst:180 d84671b50f4b442ea04f21b117805aa1 +#: ../../en/units/bytebutton.rst:180 6ca0a01d99ca4dbfbbc0e40fb2f32a5f msgid "Convert an RGB888 color value to RGB233 format." msgstr "" -#: ../../en/units/bytebutton.rst:182 c58dfff3d7544e41ad740d4e38373d1d +#: ../../en/units/bytebutton.rst:182 cbf26fdbb702441593f3a9e537c98f9a msgid "The RGB888 color value as a 32-bit integer." msgstr "" -#: ../../en/units/bytebutton.rst:186 68bb89056dc34b94968d48a31b86ad75 +#: ../../en/units/bytebutton.rst:186 afbef44d7cfa430299d46e288d7feef9 msgid "Set the color of a specific LED in RGB233 format." msgstr "" -#: ../../en/units/bytebutton.rst:189 6cd7dc031e8c43b2be7f4d373cf2cabf +#: ../../en/units/bytebutton.rst:189 a11b795e275544069ea7396c5d88464a msgid "The RGB233 color value to set." msgstr "" -#: ../../en/units/bytebutton.rst:193 e295027f826e4953b8190519a14bd16d +#: ../../en/units/bytebutton.rst:193 2fc7e1b211154eb5b2d7ab26ecf59bb5 msgid "Get the color of a specific LED in RGB233 format." msgstr "" -#: ../../en/units/bytebutton.rst:199 1aca96f8fca04941bfdca668b0d09a23 +#: ../../en/units/bytebutton.rst:199 77119f2c7d7943239f434d1ef13492bb msgid "Enable or disable IRQ functionality." msgstr "" -#: ../../en/units/bytebutton.rst:201 cc28ca22c54d4ccd8d5a7e805e1d0ed6 +#: ../../en/units/bytebutton.rst:201 4859faa9af474fdd998f7e2cc2b9edbf msgid "Whether to enable (True) or disable (False) IRQ." msgstr "" -#: ../../en/units/bytebutton.rst:205 f0d503d785dd4d549ccfa444d1d750b0 +#: ../../en/units/bytebutton.rst:205 29d6845bfa9e4a638517568162e91da0 msgid "Get the current IRQ enable status." msgstr "" -#: ../../en/units/bytebutton.rst:209 51701206d3b8455f8f70855557599f14 +#: ../../en/units/bytebutton.rst:209 ea4a2e839759472fb6bbb652b437ff7b msgid "Save the current user settings to flash." msgstr "" -#: ../../en/units/bytebutton.rst:214 6c5c76b609674a5eaf86323704b95701 +#: ../../en/units/bytebutton.rst:214 6c468e7f1fca4dd1a72f45ad3c03eadc msgid "|save_to_flash.png|" msgstr "" -#: ../../en/refs/unit.bytebutton.ref:26 dba81b9ca37b4394893381481769c9a0 +#: ../../en/refs/unit.bytebutton.ref:26 7e5e83ec17674522bd25a775260e9c70 msgid "save_to_flash.png" msgstr "" -#: ../../en/units/bytebutton.rst:218 ca7fe3bb109d494b8578a11000a46b79 +#: ../../en/units/bytebutton.rst:218 224c783b73864a3890689ab7650c92c1 msgid "Get the firmware version of the ByteButtonUnit." msgstr "" -#: ../../en/units/bytebutton.rst:223 0599b53cb7454d308ca5ca6d28d2d829 +#: ../../en/units/bytebutton.rst:223 a36dd4f7ed0a4e43bfa03f750128e3d4 msgid "|get_firmware_version.png|" msgstr "" -#: ../../en/refs/unit.bytebutton.ref:27 6dde66ec0e2c47df9bcaa2e8934f470a +#: ../../en/refs/unit.bytebutton.ref:27 be446241c78d459f809b78c79f744234 msgid "get_firmware_version.png" msgstr "" -#: ../../en/units/bytebutton.rst:227 80f827622b4749f69529197c29520236 +#: ../../en/units/bytebutton.rst:227 3d593abd41ec45558a778db375570857 msgid "Set a new I2C address for the ByteButtonUnit." msgstr "" -#: ../../en/units/bytebutton.rst:229 4988315ce7fc4d03b33873008a25ea8b -msgid "The new I2C address to set. Must be in the range 0x08 to 0x78." +#: ../../en/units/bytebutton.rst:229 ec694709cb254ef5b6ba3063c117d59a +msgid "The new I2C address to set. Must be in the range 0x08 to 0x77." msgstr "" -#: ../../en/units/bytebutton.rst:233 070f9c0e15d94f5988307542c69f15f9 +#: ../../en/units/bytebutton.rst:233 4cc9e26f557a480e8911e00d8d418f97 msgid "|set_i2c_address.png|" msgstr "" -#: ../../en/refs/unit.bytebutton.ref:28 e0956931b5204cd1954a190501fd9cce +#: ../../en/refs/unit.bytebutton.ref:28 cc05384cbaef445cac97d03d0efd3f68 msgid "set_i2c_address.png" msgstr "" -#: ../../en/units/bytebutton.rst:237 617f53f0989b4a488d4ecf322b1c7518 +#: ../../en/units/bytebutton.rst:237 3e57a9892d634e4f8c51db2b7c126ec9 msgid "Get the current I2C address of the ByteButtonUnit." msgstr "" -#: ../../en/units/bytebutton.rst:242 88f7c006c72c400eab9bc384f2ed1561 +#: ../../en/units/bytebutton.rst:242 9dcc3c2aa9b14b3b88ec8cfda44191b9 msgid "|get_i2c_address.png|" msgstr "" -#: ../../en/refs/unit.bytebutton.ref:29 83adef5023614e67a6995960bd7762ab +#: ../../en/refs/unit.bytebutton.ref:29 236c5790e647495ca37d55c1b3328674 msgid "get_i2c_address.png" msgstr "" +#~ msgid "The new I2C address to set. Must be in the range 0x08 to 0x78." +#~ msgstr "" + diff --git a/docs/locales/zh_CN/LC_MESSAGES/units/byteswitch.po b/docs/locales/zh_CN/LC_MESSAGES/units/byteswitch.po index d17acebf..0c2088f3 100644 --- a/docs/locales/zh_CN/LC_MESSAGES/units/byteswitch.po +++ b/docs/locales/zh_CN/LC_MESSAGES/units/byteswitch.po @@ -9,7 +9,7 @@ msgid "" msgstr "" "Project-Id-Version: UIFlow2 Programming Guide \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-01-16 15:35+0800\n" +"POT-Creation-Date: 2025-04-03 10:58+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language: zh_CN\n" @@ -20,11 +20,11 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.16.0\n" -#: ../../en/units/byteswitch.rst:3 297d7bb9df294f1f85a9075542c6442e +#: ../../en/units/byteswitch.rst:3 a9790d6b53e24caa905eece726812424 msgid "ByteSwitch Unit" msgstr "" -#: ../../en/units/byteswitch.rst:7 d6b228b415614603b28026136b527908 +#: ../../en/units/byteswitch.rst:7 7f7849e2877c4a8da355ade18c810e4b msgid "" "Unit ByteSwitch is an 8-switch touch switch input unit equipped with 8 " "switch inputs and 9 WS2812C RGB LEDs. It uses the STM32 microcontroller " @@ -36,66 +36,66 @@ msgid "" "exhibitions." msgstr "" -#: ../../en/units/byteswitch.rst:9 0e53dd38423e4b3aac16151f7a859d80 +#: ../../en/units/byteswitch.rst:9 29276aeb34b8450eb57e17c2d51ca611 msgid "Support the following products:" msgstr "" -#: ../../en/units/byteswitch.rst:11 27509343303147f9a4c864b16c280d3b +#: ../../en/units/byteswitch.rst:11 c465b6d283db4e7ca84fda163d4fea68 msgid "|ByteSwitchUnit|" msgstr "" -#: ../../en/refs/unit.byteswitch.ref c5ac055b20c94d27a0ccd0b1634c90bd +#: ../../en/refs/unit.byteswitch.ref 16b893028cfe4d519382045ea9ba8b6f msgid "ByteSwitchUnit" msgstr "" -#: ../../en/units/byteswitch.rst:13 63e42331df6d4c1ab326b26a4644e188 +#: ../../en/units/byteswitch.rst:13 b6c467f242554539bab3cc1e90efe3bf msgid "Micropython Example:" msgstr "" -#: ../../en/units/byteswitch.rst:19 57618e6a6e3247589c3430ed3f40480e +#: ../../en/units/byteswitch.rst:19 921fc99eb5eb4f2f95b070ec71be9139 msgid "UIFLOW2 Example:" msgstr "" -#: ../../en/units/byteswitch.rst:21 bed155e79eab4a35b57440ef89834919 +#: ../../en/units/byteswitch.rst:21 9cded9ee116942b392651aa832be2839 msgid "|example.png|" msgstr "" -#: ../../en/refs/unit.byteswitch.ref:27 0c22da805e674897887636dadfc138d5 +#: ../../en/refs/unit.byteswitch.ref:27 64e7d1d83f9c47afb76c4755fdda451d msgid "example.png" msgstr "" -#: ../../en/units/byteswitch.rst:25 43e569068f5245d88d771be892ab9555 +#: ../../en/units/byteswitch.rst:25 f7dcd1bfb75c4bd7930468c623650a21 msgid "|byteswitch_cores3_example.m5f2|" msgstr "" -#: ../../en/units/byteswitch.rst:29 c2933437a37646818964f87e53c071ed +#: ../../en/units/byteswitch.rst:29 84f43abbbe5d49158a4aaff2f130d270 msgid "class ByteSwitchUnit" msgstr "" -#: ../../en/units/byteswitch.rst:32 5b9d4b2acfb94844924d6d5228f3ebce +#: ../../en/units/byteswitch.rst:32 85265077a192454f9ca742f7a52243e8 msgid "Constructors" msgstr "" -#: ../../en/units/byteswitch.rst:36 3a71607bd6f142028a6e17387edda98e +#: ../../en/units/byteswitch.rst:36 fd6255b9e8e547ef99cd3c87ed3f9e6f msgid "Initialize the ByteSwitchUnit with a specified I2C address." msgstr "" -#: ../../en/units/byteswitch.rst 22d26431d4ee466b9041cf4bed32a924 -#: 241b80b6dcbf41d198dcb09083e0d323 38e3b0617d664f0fa53ccc8259c8cbe0 -#: 413a2d08549f42d5bb5a014301617186 4971c60bf04d4ce997335202a1ed35be -#: 694f47ead05f4df2a312f5be7475e380 71740126f6194d5a8809a240497122c7 -#: 78e598739c734bd8bb3dbee115420440 82343d048a434ec8943f5be471893505 -#: ac8ed5df90f6483a9e413515544a4e3f d9ace0e1f6144a719f3b4fda1a2854fa -#: e3090968ab8e4eec9114d74b38099b2a ee550979b8374d9abf202614fd26ae8a -#: fecadbe5f64d41c8bb078043f02f3154 +#: ../../en/units/byteswitch.rst 118227532f6243d3bf23b2ab7a445684 +#: 17336f354437425e9d66c880e3a7c854 21e6bbca58144f59a626a929fa095c57 +#: 82e832d2f37742998a14e682baa2dbcc 8a0741371d38458983e9175153460227 +#: 92ef865a1ba641fba49d95080e50bb88 97098b3126c84a35a35fb083cb3e725c +#: ab5845d47eb94610aae8689ace92ff4d c2820141e5324396a318605bf848e3f2 +#: eab0439138a741b1966defec4582cb27 ec92eda3f02241edbc3b52b83ea96639 +#: f24fe6df8ed9460eb3879826c12a5e9f f4b60628500241bf9819b3b5c3de641b +#: fee5032ef5864f0bb453394166e61d6a msgid "Parameters" msgstr "" -#: ../../en/units/byteswitch.rst:38 bec8fb365e3042359a624345e20890a4 +#: ../../en/units/byteswitch.rst:38 0450e00a14f745bf9c95d5986d78fb46 msgid "The I2C interface instance for communication." msgstr "" -#: ../../en/units/byteswitch.rst:39 ca5f2800bf3f4c629b4302bbe2ac2b6d +#: ../../en/units/byteswitch.rst:39 0c206ddc8b6d49368b9cf926ee0a3282 msgid "The I2C address of the ByteSwitchUnit, default is 0x46." msgstr "" @@ -107,328 +107,331 @@ msgstr "" #: ../../en/units/byteswitch.rst:155 ../../en/units/byteswitch.rst:165 #: ../../en/units/byteswitch.rst:174 ../../en/units/byteswitch.rst:211 #: ../../en/units/byteswitch.rst:220 ../../en/units/byteswitch.rst:230 -#: ../../en/units/byteswitch.rst:239 130797f60082454083ac739342473cf7 -#: 2c6f63400e4046578f344a23603752c9 36ef22b2ddb3456ba9dfb54e40cb24e8 -#: 3e6511ce64ba486eb0776a9a2240f93d 4191ea119f1344cfaa54e9dbe6e33cf5 -#: 4325480cea1746b4add8fe939814bbb2 45910d960647432b8f405cca9bb37c00 -#: 47ea7de5a26244dd965aca41d2fb93cf 5b80476401764f1b8300cbe69618b1f6 -#: 658ead26c63544698253c3c4a0dc22b6 7ec708e6b5a84af0aa21cb1249d2623e -#: 81e2897583294417b1edd5c00df1b1aa 913f24d8ae70424d8116fb26f5b0f154 -#: b440a62310a14e2d8ece57e47d1a3fa5 c6baa2b6a1b74951bff5b610afb1a433 -#: d211012aea0e44e7b647551a6774f221 dd5f55132c064566976d5e938c1279f1 +#: ../../en/units/byteswitch.rst:239 0badb793b1b04512820f9a165da2a494 +#: 0c74b2b3bd1c41c1840142b459eae518 19e722290a9d432b92da553b5797e0f6 +#: 1c225d3184a842d7a0546a9a7b69e2aa 2deaac0067d0481ca904cbeb9543eb48 +#: 497df380733c43f5b38ab75003dd554e 5353e1bcb2d44e3a930e3e9e7fffd6e0 +#: 5f7ea1a2c7ae406db179020c22870b93 7a0d29197f5b45aeb86247bd950ea9cd +#: 916807d57ece42c181e88e6858ffe438 a163f39f70b54972931ab43775190718 +#: a2dea64bc39a409187fa67f45938907a c0834ebc6b0e43bd892bf7233ac1a75b +#: d353cdcd30424e98814206b8f5194c6c d3f16cc8fde441968b5b3dfa5cfe5174 +#: d602a928bcc34f9cb1f3a507e8958106 f466f6dea9eb4aaa9ea059e9fcd9708c msgid "UIFLOW2:" msgstr "" -#: ../../en/units/byteswitch.rst:43 4e00a2313cd3458ab0ad83258cdea184 +#: ../../en/units/byteswitch.rst:43 31b43b9406cd4466bbb6d993e578b575 msgid "|init.png|" msgstr "" -#: ../../en/refs/unit.byteswitch.ref:7 8dac041df1424f5ea29fc6142dd230d4 +#: ../../en/refs/unit.byteswitch.ref:7 a7016e507338492b8d6dcf05ca1b746a msgid "init.png" msgstr "" -#: ../../en/units/byteswitch.rst:46 bc6f26c341d94f44bf5c599211422404 +#: ../../en/units/byteswitch.rst:46 21aa46e601aa47d1922ea46fb42cec7b msgid "Methods" msgstr "" -#: ../../en/units/byteswitch.rst:50 60210c4aae584b6590b6947925216ada +#: ../../en/units/byteswitch.rst:50 dd0859fe5ea341c6bc302825a2ffda03 msgid "" "Get the status of all switchs as an integer, where each bit represents " "the state of each switch." msgstr "" -#: ../../en/units/byteswitch.rst:55 a644ad9352a349c6ba500110b098f338 +#: ../../en/units/byteswitch.rst:55 658d46df9a064aa3884dd0e9cef5b37a msgid "|get_byte_switch_status.png|" msgstr "" -#: ../../en/refs/unit.byteswitch.ref:8 5777d0d1dd3b43babd1018f81577f039 +#: ../../en/refs/unit.byteswitch.ref:8 6da25c2534214865b5f64db43edc8059 msgid "get_byte_switch_status.png" msgstr "" -#: ../../en/units/byteswitch.rst:59 e4a38528ba8d47ce8ea172e25ac3f01c +#: ../../en/units/byteswitch.rst:59 bf51330afdc94f7b94107761958fae4b msgid "Get the state of a specific switch." msgstr "" -#: ../../en/units/byteswitch.rst:61 7b1986b1fad34c8f83127611c927787a +#: ../../en/units/byteswitch.rst:61 3e4f740075464c68ba6458c57d5b7de8 msgid "The index of the switch (0-7)." msgstr "" -#: ../../en/units/byteswitch.rst:65 db54957c10524de79673420f0fe0b9e9 +#: ../../en/units/byteswitch.rst:65 a0580cd98d114de1a93a5e4f8f97ef26 msgid "|get_switch_state.png|" msgstr "" -#: ../../en/refs/unit.byteswitch.ref:9 61b758254c354c51b4b2215b412bd4cb +#: ../../en/refs/unit.byteswitch.ref:9 bedbef00e4e949ad951ed0ea2d6995e9 msgid "get_switch_state.png" msgstr "" -#: ../../en/units/byteswitch.rst:69 1689cacb8f844d519e797d16d73afd2c +#: ../../en/units/byteswitch.rst:69 d59e41562d9e4cd3a998b2d1e38811ab msgid "Get the current LED show mode." msgstr "" -#: ../../en/units/byteswitch.rst:73 e9f0b8886e9949f3aa4fd8abd70e15a9 +#: ../../en/units/byteswitch.rst:73 ebc0212e47c04cfc8fc5f5e86c231516 msgid "|get_led_show_mode.png|" msgstr "" -#: ../../en/refs/unit.byteswitch.ref:10 0528ca9a7f6a4638a0c6a1e0d468013f +#: ../../en/refs/unit.byteswitch.ref:10 c9a823af073444a0a1fa29e17b0e6965 msgid "get_led_show_mode.png" msgstr "" -#: ../../en/units/byteswitch.rst:77 e75c4784a006441fbc957b2adadccd01 +#: ../../en/units/byteswitch.rst:77 71db8c72e65c4fbf90869378832da58a msgid "Set the LED show mode." msgstr "" -#: ../../en/units/byteswitch.rst:79 04f77e787ea640dcacf5c9180ad679a0 +#: ../../en/units/byteswitch.rst:79 a0ff4049d1c2497092137b71ad8b7177 msgid "" "The LED show mode to set. Options: - ``BYTESWITCH_LED_USER_MODE``: 0" " - ``BYTESWITCH_LED_SYS_MODE``: 1" msgstr "" -#: ../../en/units/byteswitch.rst:79 0a738a650ece418b9d671d80e707ce1a +#: ../../en/units/byteswitch.rst:79 b49f1a1004ec4f38b0389b1600564ac6 msgid "The LED show mode to set." msgstr "" -#: ../../en/units/byteswitch.rst:83 06fd1646b3774a1c93bf4720f85246f8 +#: ../../en/units/byteswitch.rst:83 4bc019ec0c634ad1a5ff9905ef873312 msgid "Options:" msgstr "" -#: ../../en/units/byteswitch.rst:82 8d4dc0fe4ec049a58b155ef54e08128f +#: ../../en/units/byteswitch.rst:82 7964f97c044a41c099659189b34d88f7 msgid "``BYTESWITCH_LED_USER_MODE``: 0" msgstr "" -#: ../../en/units/byteswitch.rst:83 ab5633d9787e46e983904070de0d0fa0 +#: ../../en/units/byteswitch.rst:83 7f887211a34949618c6174c9b03a6e62 msgid "``BYTESWITCH_LED_SYS_MODE``: 1" msgstr "" -#: ../../en/units/byteswitch.rst:87 cb8fd3fb212d4d2cb65b0b96758739c5 +#: ../../en/units/byteswitch.rst:87 fedd555df56c4ce58941722f7ff03c98 msgid "|set_led_show_mode.png|" msgstr "" -#: ../../en/refs/unit.byteswitch.ref:11 cd8a0b33180c4c9ba85b6d7402411baf +#: ../../en/refs/unit.byteswitch.ref:11 5b8b4accb1cb4cdc9e4b432ae6d2febb msgid "set_led_show_mode.png" msgstr "" -#: ../../en/units/byteswitch.rst:91 3cdce184a030418291c8a38bb0a8a536 +#: ../../en/units/byteswitch.rst:91 e4e9d52685374baaa71914077b6a2054 msgid "Set the brightness of a specific LED." msgstr "" #: ../../en/units/byteswitch.rst:93 ../../en/units/byteswitch.rst:104 #: ../../en/units/byteswitch.rst:114 ../../en/units/byteswitch.rst:129 #: ../../en/units/byteswitch.rst:188 ../../en/units/byteswitch.rst:195 -#: 59c1e517f9fa4bbdab58ddc0d21076b6 696dc61a7ecc48a690a44205359cac2d -#: a1125bfbfd51464383b10e1534d91330 d13cfa2f8e4049ecb95d6ca0e18827aa -#: e477962ed9444fdcb5e0fdfe597b01e2 eb0984dfbdb544dbb87a80ca34a4450d +#: 081b6b8e7c74468da84a5869b025b7e5 29302867008848b98cee2f8192770a5d +#: 74ee4a174c5a4cb0acb8941fb7512e56 83342b6bb0664cd0b6adb48e95f9e57c +#: b6dcd3f01d9b4e8b8585351c006344c1 bdf2add596d84659bdf50a01ad4043f6 msgid "The index of the LED (0-7)." msgstr "" #: ../../en/units/byteswitch.rst:94 ../../en/units/byteswitch.rst:144 -#: 4f577992b6b1443c9a64fd62537a0788 ce52c79674b341dc934ff02ddfc88be9 +#: 3d8674a5d1194037a617b254e1b4031c 9fd20c05168943d89c8b77c6c6911c45 msgid "The brightness level (0-255)." msgstr "" -#: ../../en/units/byteswitch.rst:98 5fea9abe8d6b45a7b2a20a7153ee6bf2 +#: ../../en/units/byteswitch.rst:98 fc41ff48d3544c6b8c81187e5c0c920d msgid "|set_led_brightness.png|" msgstr "" -#: ../../en/refs/unit.byteswitch.ref:12 ab30f0cd63cd4362a3f672bb3faad360 +#: ../../en/refs/unit.byteswitch.ref:12 bf7bb05589144137bb5ce6358294cc68 msgid "set_led_brightness.png" msgstr "" -#: ../../en/units/byteswitch.rst:102 b551404bbabc46aabc9f93bf2dd403f7 +#: ../../en/units/byteswitch.rst:102 cd6687fbba7e4157997d857f601867af msgid "Get the brightness of a specific LED." msgstr "" -#: ../../en/units/byteswitch.rst:108 720f9664662a4ffa8270c765a934145d +#: ../../en/units/byteswitch.rst:108 c08a7c08106b4f62baaf6e4e189890d5 msgid "|get_led_brightness.png|" msgstr "" -#: ../../en/refs/unit.byteswitch.ref:13 4e7c1b1de4e24182b25717f1b3cee2a1 +#: ../../en/refs/unit.byteswitch.ref:13 49c8faf2c6e64ab39699aa5dcdae28e7 msgid "get_led_brightness.png" msgstr "" -#: ../../en/units/byteswitch.rst:112 6eda3831c8034557927ab2c619b5d9a5 +#: ../../en/units/byteswitch.rst:112 f05f4214099f40f9802460758043621f msgid "Set the color of a specific LED." msgstr "" #: ../../en/units/byteswitch.rst:115 ../../en/units/byteswitch.rst:163 -#: 690f64cfd3c94b22b0042ff569f67660 e20384ae1ebf485a83bd5fdca68ab0ed +#: 1597d2908940464ba17854d0e0618c71 d438bd766d14412dba644836a8ae4ab5 msgid "The RGB888 color value to set." msgstr "" #: ../../en/units/byteswitch.rst:116 ../../en/units/byteswitch.rst:130 -#: 5e705a5de06a478fbdcc5e1918e02c22 b05879428730412f8068e2302f830e84 +#: ed389d0e04a54586b475c1fa74a3b8e8 feb1fe9a9b4c4dc48c723549a85eb781 msgid "The LED show mode, default is BYTESWITCH_LED_SYS_MODE." msgstr "" #: ../../en/units/byteswitch.rst:117 ../../en/units/byteswitch.rst:131 -#: 85a7eeec9b41475fa59681198407be03 d12dce5a798a417dbfe47056c6ffded5 +#: 55b584756f284915a3606bc3e46ba1cb bd115747b26e4a42bf75d8a9d5276944 msgid "Whether the switch is pressed (affects color in SYS mode)." msgstr "" -#: ../../en/units/byteswitch.rst:121 cb957760ef2842799a20411a76b06c73 +#: ../../en/units/byteswitch.rst:121 c67ce1288b414c6babdd1818f32a3e21 msgid "|set_sys_mode_led_color.png|" msgstr "" -#: ../../en/refs/unit.byteswitch.ref:14 55a466726ee94b5abfd9b4c08eb8b3a2 +#: ../../en/refs/unit.byteswitch.ref:14 c553e63d946a483f99eed330dacd229d msgid "set_sys_mode_led_color.png" msgstr "" -#: ../../en/units/byteswitch.rst:123 2fa4b50da3094d528bb6dc698da840c7 +#: ../../en/units/byteswitch.rst:123 238397b9cc06490e965b88aa0de85f3c msgid "|set_user_mode_led_color.png|" msgstr "" -#: ../../en/refs/unit.byteswitch.ref:15 369fd1e31faa4b4e9e51c062d52f2097 +#: ../../en/refs/unit.byteswitch.ref:15 5b336b0b80c045cf8dfcdaec56c112d6 msgid "set_user_mode_led_color.png" msgstr "" -#: ../../en/units/byteswitch.rst:127 5f639432129540ab80dc19c7924e1345 +#: ../../en/units/byteswitch.rst:127 6448b3ab85624772ad9effd6f202049d msgid "Get the color of a specific LED." msgstr "" -#: ../../en/units/byteswitch.rst:135 e3054ecf4c614c5196b1426b538335d6 +#: ../../en/units/byteswitch.rst:135 19577333df36487a943e992eb9c167e4 msgid "|get_sys_mode_led_color.png|" msgstr "" -#: ../../en/refs/unit.byteswitch.ref:16 37a4636ce9514fcdbf009795d8f39f2b +#: ../../en/refs/unit.byteswitch.ref:16 32b2a62d5d01441194036513b824c97d msgid "get_sys_mode_led_color.png" msgstr "" -#: ../../en/units/byteswitch.rst:137 1610b8f14ae8477e800df7e436d2b383 +#: ../../en/units/byteswitch.rst:137 63f0860df9334a63ae55141ebc4ea88e msgid "|get_user_mode_led_color.png|" msgstr "" -#: ../../en/refs/unit.byteswitch.ref:17 3c33365e56124a16a6c3c307cbb85b61 +#: ../../en/refs/unit.byteswitch.ref:17 2e9b1a6b6cd346c9803b5176798de492 msgid "get_user_mode_led_color.png" msgstr "" -#: ../../en/units/byteswitch.rst:142 8245d453b7ef49bc921f876ee3c3bc58 +#: ../../en/units/byteswitch.rst:142 c2fd27555e104e069846f45bd820b2b3 msgid "Set the brightness of the indicator LED." msgstr "" -#: ../../en/units/byteswitch.rst:148 af89d58ce3434b5f9c676325b392bcfa +#: ../../en/units/byteswitch.rst:148 2e879137d7f04bb1a6d9d6fb5534727f msgid "|set_indicator_brightness.png|" msgstr "" -#: ../../en/refs/unit.byteswitch.ref:18 2178978b20f54358897f89ea45a3b839 +#: ../../en/refs/unit.byteswitch.ref:18 1a6a3989b7af440c9e45381a1c626d36 msgid "set_indicator_brightness.png" msgstr "" -#: ../../en/units/byteswitch.rst:152 16ab785e7b9b49018410a8ca48ff65e9 +#: ../../en/units/byteswitch.rst:152 30bdf09701c749bd93fa56fcf129231a msgid "Get the brightness of the indicator LED." msgstr "" -#: ../../en/units/byteswitch.rst:157 13879baf3427486295333eb2a66d5180 +#: ../../en/units/byteswitch.rst:157 50938bba1223421a89d5282b5fca39b5 msgid "|get_indicator_brightness.png|" msgstr "" -#: ../../en/refs/unit.byteswitch.ref:19 0454c1bb85a945dc80ca7a3c2ba05e06 +#: ../../en/refs/unit.byteswitch.ref:19 525d3efb3f464e35b02437a1bf084f1b msgid "get_indicator_brightness.png" msgstr "" -#: ../../en/units/byteswitch.rst:161 6eafe3c4e75d4173b45ec0d654b91dfc +#: ../../en/units/byteswitch.rst:161 ec8fccab98cf4c938d97c8a0fc19e588 msgid "Set the color of the indicator LED in RGB888 format." msgstr "" -#: ../../en/units/byteswitch.rst:167 b8c695e211894456a805cbe6846c9034 +#: ../../en/units/byteswitch.rst:167 9f7daa7d74d446aab9549c8cf83ada4a msgid "|set_indicator_color.png|" msgstr "" -#: ../../en/refs/unit.byteswitch.ref:20 f75bbcd5852747f1a21ef24a3b438b73 +#: ../../en/refs/unit.byteswitch.ref:20 a016168dbe6b49df9b8687339ed8e52b msgid "set_indicator_color.png" msgstr "" -#: ../../en/units/byteswitch.rst:171 afa04c9908d642269bf850de09b9af0e +#: ../../en/units/byteswitch.rst:171 4e198e8e472f49dfaf24bce076775699 msgid "Get the color of the indicator LED in RGB888 format." msgstr "" -#: ../../en/units/byteswitch.rst:176 9e00d303cd234a29befd99650bf545e0 +#: ../../en/units/byteswitch.rst:176 b00747340de648ad97e4feae29708177 msgid "|get_indicator_color.png|" msgstr "" -#: ../../en/refs/unit.byteswitch.ref:21 c74064edaef94e17acfc84de816b8832 +#: ../../en/refs/unit.byteswitch.ref:21 c80809e23428432e893566a8cce5740c msgid "get_indicator_color.png" msgstr "" -#: ../../en/units/byteswitch.rst:180 3f3151a4549c4ac6a8e032a45a9f454c +#: ../../en/units/byteswitch.rst:180 cf244172758347beb6b6a0077e332777 msgid "Convert an RGB888 color value to RGB233 format." msgstr "" -#: ../../en/units/byteswitch.rst:182 40374997470841c092521e9ac30dfe91 +#: ../../en/units/byteswitch.rst:182 a1b988a29f054b1cbd6f556f5600e1c0 msgid "The RGB888 color value as a 32-bit integer." msgstr "" -#: ../../en/units/byteswitch.rst:186 47aafe65d3654f748476368b9df76111 +#: ../../en/units/byteswitch.rst:186 579b5887502547b3a735d923916f8e64 msgid "Set the color of a specific LED in RGB233 format." msgstr "" -#: ../../en/units/byteswitch.rst:189 a5123593e56345a5896479a25db7661a +#: ../../en/units/byteswitch.rst:189 27afca9d7b134ac089a47aa78ef613c4 msgid "The RGB233 color value to set." msgstr "" -#: ../../en/units/byteswitch.rst:193 b8eefc05db68421b9c45e677bf69691c +#: ../../en/units/byteswitch.rst:193 c9e52bdb206440da958b1f50e7be61e2 msgid "Get the color of a specific LED in RGB233 format." msgstr "" -#: ../../en/units/byteswitch.rst:199 ae4105afae094b7aaa5dc8c4fa9976e0 +#: ../../en/units/byteswitch.rst:199 fd6674e6fb9f4ce1a5d3f8309f7f125a msgid "Enable or disable IRQ functionality." msgstr "" -#: ../../en/units/byteswitch.rst:201 b03ad2609f53443086933009ddcef399 +#: ../../en/units/byteswitch.rst:201 889a287145634b9780e1e69119a664b0 msgid "Whether to enable (True) or disable (False) IRQ." msgstr "" -#: ../../en/units/byteswitch.rst:205 3eaef0e9541941899a32566605f0a2d3 +#: ../../en/units/byteswitch.rst:205 89e5a23f6fb9411783bf662b365bb9a8 msgid "Get the current IRQ enable status." msgstr "" -#: ../../en/units/byteswitch.rst:209 4dfbc406a1334c9eb7f726cc42574c8f +#: ../../en/units/byteswitch.rst:209 03faa4f3802949c597ecadf664b17d93 msgid "Save the current user settings to flash." msgstr "" -#: ../../en/units/byteswitch.rst:213 bc4782efb0004b329bff14c43a83368c +#: ../../en/units/byteswitch.rst:213 f9365248500f4fe597302650c5723e9d msgid "|save_to_flash.png|" msgstr "" -#: ../../en/refs/unit.byteswitch.ref:22 9f0c8b7e69434d7f9f2a72d4d41967f7 +#: ../../en/refs/unit.byteswitch.ref:22 5a26011fe30342d5bf3f4f6bf84a7440 msgid "save_to_flash.png" msgstr "" -#: ../../en/units/byteswitch.rst:217 d39d734986d3448b8e9a59e8f723143e +#: ../../en/units/byteswitch.rst:217 d66f636f813d4c0691362e4ccae41f6e msgid "Get the firmware version of the ByteSwitchUnit." msgstr "" -#: ../../en/units/byteswitch.rst:222 40ff630bbc09436a88196536e70bb50d +#: ../../en/units/byteswitch.rst:222 5397b8b804914c0099fc715b858d49fe msgid "|get_firmware_version.png|" msgstr "" -#: ../../en/refs/unit.byteswitch.ref:23 58119f4146a14021afd8194492ca54d9 +#: ../../en/refs/unit.byteswitch.ref:23 c7cd7f7843054fe88b7fde2191544691 msgid "get_firmware_version.png" msgstr "" -#: ../../en/units/byteswitch.rst:226 c9a248b39dc34722a5c4d605b22e1d03 +#: ../../en/units/byteswitch.rst:226 170c29cf3c664c56a094fbb7380dc627 msgid "Set a new I2C address for the ByteSwitchUnit." msgstr "" -#: ../../en/units/byteswitch.rst:228 2c6114013c824e2b81262a9e12f74ee8 -msgid "The new I2C address to set. Must be in the range 0x08 to 0x78." +#: ../../en/units/byteswitch.rst:228 d760f3e58e2b4cf388e498228dc5f01b +msgid "The new I2C address to set. Must be in the range 0x08 to 0x77." msgstr "" -#: ../../en/units/byteswitch.rst:232 39c9d819b7f54a6d8d44425752f6eea5 +#: ../../en/units/byteswitch.rst:232 acc9357adfa84f50a9edaa0bde7edeab msgid "|set_i2c_address.png|" msgstr "" -#: ../../en/refs/unit.byteswitch.ref:24 8625a473081e435fb0766e9725667e71 +#: ../../en/refs/unit.byteswitch.ref:24 563cd39879d941839c679b2b66a4b1e3 msgid "set_i2c_address.png" msgstr "" -#: ../../en/units/byteswitch.rst:236 086ecf3f3402413f8c7fc02c39b638f5 +#: ../../en/units/byteswitch.rst:236 92862d222607499a9967d1c92bb7bc8f msgid "Get the current I2C address of the ByteSwitchUnit." msgstr "" -#: ../../en/units/byteswitch.rst:241 a83c698d84954522809ebcafaf5d1e3f +#: ../../en/units/byteswitch.rst:241 201037b36c924047b9145a8a95a98b2c msgid "|get_i2c_address.png|" msgstr "" -#: ../../en/refs/unit.byteswitch.ref:25 24093a496ba1431b80f439c2390c925b +#: ../../en/refs/unit.byteswitch.ref:25 bf6d055e76fd45d4b5f388266174ac5e msgid "get_i2c_address.png" msgstr "" +#~ msgid "The new I2C address to set. Must be in the range 0x08 to 0x78." +#~ msgstr "" + diff --git a/docs/locales/zh_CN/LC_MESSAGES/units/dac.po b/docs/locales/zh_CN/LC_MESSAGES/units/dac.po index 990e8e1a..18fe6852 100644 --- a/docs/locales/zh_CN/LC_MESSAGES/units/dac.po +++ b/docs/locales/zh_CN/LC_MESSAGES/units/dac.po @@ -9,7 +9,7 @@ msgid "" msgstr "" "Project-Id-Version: UIFlow2 Programming Guide \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-01-16 15:35+0800\n" +"POT-Creation-Date: 2025-04-18 15:58+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language: zh_CN\n" @@ -20,80 +20,78 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.16.0\n" -#: ../../en/units/dac.rst:2 810115cd34b440fdb37915f37646bd57 +#: ../../en/units/dac.rst:2 37f45cffa9a84673851bd21b618124a4 msgid "DAC Unit" msgstr "" -#: ../../en/units/dac.rst:6 b53de5991d104d2fa9e6448f59bd1ca0 +#: ../../en/units/dac.rst:6 465db846763447d8b685b7bcdbc7baa6 msgid "" "The `Dac2` class interfaces with a GP8413 15-bit Digital to Analog " "Converter (DAC), capable of converting digital signals into two channels " "of analog voltage output, ranging from 0-5V and 0-10V." msgstr "" -#: ../../en/units/dac.rst:8 1789ad65b0444b1cad8e4e5865cf7968 +#: ../../en/units/dac.rst:8 75d9628431be4c63bc226d1b7ccbee66 msgid "Support the following products:" msgstr "" -#: ../../en/units/dac.rst:10 d965890e84f641f4a66f879b90effb1d +#: ../../en/units/dac.rst:10 a0ef8c64a99e4a76b57d422b8b616135 msgid "|DACUnit|" msgstr "" -#: ../../en/refs/unit.dac.ref 7b2b917fb07143858ca4c00f33b2bd93 +#: ../../en/refs/unit.dac.ref 3004fd8cee24437482ab8cc46570fc83 msgid "DACUnit" msgstr "" -#: ../../en/units/dac.rst:13 c7af60493dbb48bca4958d8d0586a7f8 +#: ../../en/units/dac.rst:13 b116c0dc2ad74d18a36681e50bf98390 msgid "Micropython Example:" msgstr "" -#: ../../en/units/dac.rst:20 dcc920186a2146c4a14af0254bd7ad9f +#: ../../en/units/dac.rst:20 309aa4c9215b484d83b562755578a335 msgid "UIFLOW2 Example:" msgstr "" -#: ../../en/units/dac.rst:22 871afd74832a45a3b4de5c6e94f99e02 +#: ../../en/units/dac.rst:22 0065d1b7825347339bff40d5047b14e9 msgid "|example.png|" msgstr "" -#: ../../en/refs/unit.dac.ref:7 c071cb02c5174c5390f8d24ec36d96b0 +#: ../../en/refs/unit.dac.ref:6 cc64c240025c46f486dec249cebda11b msgid "example.png" msgstr "" -#: ../../en/units/dac.rst:27 69418a70c89348e6899bcb755956c167 +#: ../../en/units/dac.rst:27 c3d94d153f9e43e4b6aac5fea29e7e5c msgid "|dac_core_example.m5f2|" msgstr "" -#: ../../en/units/dac.rst:31 a4c10d1da7764d42b28a61363f96cdf2 +#: ../../en/units/dac.rst:31 9aacf7c64c9842a6acc786274413f61a msgid "class DACUnit" msgstr "" -#: ../../en/units/dac.rst:34 cd684ae7a5d6493684b65026fb8a547a +#: ../../en/units/dac.rst:34 cf450dcf3fe747e49419f01e4e46164a msgid "Constructors" msgstr "" -#: ../../en/units/dac.rst:38 9fcebbf153574bae9301bfb4706f0afb +#: ../../en/units/dac.rst:38 2f3caa42997c4e7887d52efcc719af04 msgid "Create an DACUnit object." msgstr "" -#: ../../en/units/dac.rst 1c1b8c51430148cd813766babcb1e924 -#: 9b97b2ea1c4f4dadbcb269cfdfe24e41 a10577322831429c83f384126130dc90 -#: f5ca3fb78cb94a11bbe4fb085f1977cd f9bb64c4e39a46aca27c4c041d106811 +#: ../../en/units/dac.rst 2f6467bf11b6469eb30273ae7b3a2025 msgid "Parameters" msgstr "" -#: ../../en/units/dac.rst:40 9d2f8664b9f848a580aff720403057e4 +#: ../../en/units/dac.rst:40 fc3734669bc445db81f208a38cf29149 msgid "I2C object" msgstr "" -#: ../../en/units/dac.rst:41 ada77d631ed94daeae74827577a5f1b5 +#: ../../en/units/dac.rst:41 35ee7c7ba3bc4b51a46d4ef610b204ac msgid "I2C address" msgstr "" -#: ../../en/units/dac.rst:42 a839d3c0dfba4e199b2796d1e30e3ca6 +#: ../../en/units/dac.rst:42 5277b4e9b49445668ae45729dda1346a msgid "Supply voltage" msgstr "" -#: ../../en/units/dac.rst:43 14032c3cc4e7473b8fba33b82296d9dd +#: ../../en/units/dac.rst:43 091d5d79c1aa4f04859f94232361018d msgid "Output voltage" msgstr "" @@ -102,160 +100,214 @@ msgstr "" #: ../../en/units/dac.rst:94 ../../en/units/dac.rst:105 #: ../../en/units/dac.rst:116 ../../en/units/dac.rst:127 #: ../../en/units/dac.rst:138 ../../en/units/dac.rst:146 -#: 021d66e906364026a56168f46f4b9cbe 54dc4e8197714b599556d8f283711707 -#: 6b7d0ae476384488a045899547e5553a 6f3934109947412485b3c29224d1d581 -#: 8c0915937d7244108591fd6193186663 b8decd17dd834fe1b07f9813fb54ff5e -#: d001cedd9e364cc596276683247d9e5b d8f9fe4eda064a94afcd29b47262ce08 -#: e302293d514c43a28f5472c12933da53 f2d595f4b42645c280ed5d5e01168a1b +#: 84df8d96f7c54844a67d99aba5c8342d msgid "UIFLOW2:" msgstr "" -#: ../../en/units/dac.rst:47 0de30ec221df466691498096e62dfad0 -msgid "|init.svg|" +#: ../../en/units/dac.rst:47 782210e15dde4a65ad0ffe9cd1daef2e +msgid "|init.png|" msgstr "" -#: ../../en/refs/unit.dac.ref:9 f2806bee5017489e991843a7750b11bf -msgid "init.svg" +#: ../../en/refs/unit.dac.ref:8 ef2a4d1f39e6416aa512d08cb5b425d8 +msgid "init.png" msgstr "" -#: ../../en/units/dac.rst:53 ef219cc5b740477284cf8243ba61496f +#: ../../en/units/dac.rst:53 535a88a9a56d4d1a877abefba9f2c9f9 msgid "Methods" msgstr "" -#: ../../en/units/dac.rst:57 705beb83ca4c46fcb3d92c1ab5ca310d +#: ../../en/units/dac.rst:57 c000f62fe5ab4274ba89f663bb9d4633 msgid "Get the current value of the DAC." msgstr "" -#: ../../en/units/dac.rst 5ecafa3faade40cab6766c0dc54366b7 -#: 848ff2e1ad1b4b0a88276c222766596b bdce3dee71d84edf8ab5a566c9cd393a -#: be095f2beb944561999d364b17fb6a9a +#: ../../en/units/dac.rst 971907dd2f674a48b7863bd6b3acfc7b msgid "Returns" msgstr "" #: ../../en/units/dac.rst:59 ../../en/units/dac.rst:81 -#: 87dd5f508adf4b7da83f7578c72402e4 998b03ad6b624c92a0bb5f1614476a79 +#: f800d24690674639b29a4f0f23652668 msgid "The DAC value as a 16-bit unsigned value." msgstr "" -#: ../../en/units/dac.rst:63 a4bcd88e86a64f1286fbe77108b79c39 -msgid "|get_value.svg|" +#: ../../en/units/dac.rst:63 4160306ca71146388696d59715b11bec +msgid "|get_value.png|" msgstr "" -#: ../../en/refs/unit.dac.ref:12 3b8b40381a6347eb94e71f8841b9dced -msgid "get_value.svg" +#: ../../en/refs/unit.dac.ref:10 d81fef809ea0414f8faf7195b3f993dc +msgid "get_value.png" msgstr "" -#: ../../en/units/dac.rst:68 996e1b91f9d84453a2ff9ab8b4fa979d +#: ../../en/units/dac.rst:68 7520079cf83a4970ab8eb243260fcb3a msgid "Get the current voltage of the DAC." msgstr "" -#: ../../en/units/dac.rst:70 8733d110fb8242368e7af41c3ecedca7 +#: ../../en/units/dac.rst:70 cf3d04509b2e462881ed549cf45b73f9 msgid "The DAC voltage as a float." msgstr "" -#: ../../en/units/dac.rst:74 4c6a98c383c448299a8e439853f57816 -msgid "|get_voltage.svg|" +#: ../../en/units/dac.rst:74 b46ed1f576e5433da7784fe390b33081 +msgid "|get_voltage.png|" msgstr "" -#: ../../en/refs/unit.dac.ref:15 329e25e8a6104783aaef2f704e43d3dd -msgid "get_voltage.svg" +#: ../../en/refs/unit.dac.ref:12 318beb18d1d34578a8bba0b26bd00f84 +msgid "get_voltage.png" msgstr "" -#: ../../en/units/dac.rst:79 df23b07d0e43494986e3800bf6ea070d +#: ../../en/units/dac.rst:79 8717df58997d40cd88395ad1a57ff06f msgid "Set the value of the DAC." msgstr "" -#: ../../en/units/dac.rst:85 e5d01380988d4fa08e9d7454482c63d6 -msgid "|set_value.svg|" +#: ../../en/units/dac.rst:85 141bcfb528ae4e988cbdb0f3751584eb +msgid "|set_value.png|" msgstr "" -#: ../../en/refs/unit.dac.ref:18 95fc8294f33942f5b4c9eb4c90b04024 -msgid "set_value.svg" +#: ../../en/refs/unit.dac.ref:14 724e9fbebbb9456cbd3fc43280e3e90d +msgid "set_value.png" msgstr "" -#: ../../en/units/dac.rst:90 456282f613f1450d8adfc0849d0c6d0d +#: ../../en/units/dac.rst:90 9cef8d5e43a44a7a9830c5a8fb63c8ef msgid "Set the voltage of the DAC." msgstr "" -#: ../../en/units/dac.rst:92 5af8fc487b2e4a4eb0680a91c8f39662 +#: ../../en/units/dac.rst:92 329270c0c59c4b12ab9bdf232780bf59 msgid "The DAC voltage as a float. The voltage must be between 0 and 3.3V." msgstr "" -#: ../../en/units/dac.rst:96 7b23268446834b109c703f4dc178714b -msgid "|set_voltage.svg|" +#: ../../en/units/dac.rst:96 83359482d60d4cf486f989f2f8c7b4a7 +msgid "|set_voltage.png|" msgstr "" -#: ../../en/refs/unit.dac.ref:21 ee476a656b1242a2a73eafcce14b24a2 -msgid "set_voltage.svg" +#: ../../en/refs/unit.dac.ref:16 2e1d66965e724797b21ae86e97236fa3 +msgid "set_voltage.png" msgstr "" -#: ../../en/units/dac.rst:101 7f287561372e4558949b74e48c5e25d7 +#: ../../en/units/dac.rst:101 4ef92874304a4cee80163679323783e5 msgid "Get the raw value of the DAC." msgstr "" #: ../../en/units/dac.rst:103 ../../en/units/dac.rst:114 -#: 84ef1ef064674e168b90ce0ba0e81d67 b64f5eda031645a1a7c09c5c22846af3 +#: 1387a31cc49741dfb68a97943d802a78 msgid "The raw DAC value as a 12-bit unsigned value." msgstr "" -#: ../../en/units/dac.rst:107 34c70d29f3c24c6a96e9bf0eacd073bc -msgid "|get_raw_value.svg|" +#: ../../en/units/dac.rst:107 21bf4abf6a42493bb8f1ca63e3b3ebaa +msgid "|get_raw_value.png|" msgstr "" -#: ../../en/refs/unit.dac.ref:24 0c396c3bb2d242e285f157e9c0f4e71a -msgid "get_raw_value.svg" +#: ../../en/refs/unit.dac.ref:18 0febedf90ff04daa9798782ee550c33c +msgid "get_raw_value.png" msgstr "" -#: ../../en/units/dac.rst:112 a0a00b37101d441e98982682e593a9da +#: ../../en/units/dac.rst:112 ae02962e422c43038103cbe6ecd116b0 msgid "Set the raw value of the DAC." msgstr "" -#: ../../en/units/dac.rst:118 59152bc7fb4146e4afd66ca4c0344ffd -msgid "|set_raw_value.svg|" +#: ../../en/units/dac.rst:118 4b31f0ab45be4ae7a4da5f919c61527c +msgid "|set_raw_value.png|" msgstr "" -#: ../../en/refs/unit.dac.ref:27 5721083714b7467b85d19ab192bd8181 -msgid "set_raw_value.svg" +#: ../../en/refs/unit.dac.ref:20 bae66024c926440aabc2bc2ef3c5d2cc +msgid "set_raw_value.png" msgstr "" -#: ../../en/units/dac.rst:123 5d5e4d90b0da4aa09006aedd754fe6d3 +#: ../../en/units/dac.rst:123 048132c7fb0e47408441f099534d3b9e msgid "Get the normalized value of the DAC." msgstr "" #: ../../en/units/dac.rst:125 ../../en/units/dac.rst:136 -#: 5db2520a66744bc5a0e84913d530790e ba00e13aa5fc4daf8a3bad225736eab2 +#: c729b66d68ad4428aaebe9ea275c7f19 msgid "The normalized DAC value as a float." msgstr "" -#: ../../en/units/dac.rst:129 ed279639bf6544a7bb84884201093ca0 -msgid "|get_normalized_value.svg|" +#: ../../en/units/dac.rst:129 ce527c7cb1624e99820993c095e2a95e +msgid "|get_normalized_value.png|" msgstr "" -#: ../../en/refs/unit.dac.ref:30 d69a02c8060f4ddda1e5757d046102a2 -msgid "get_normalized_value.svg" +#: ../../en/refs/unit.dac.ref:22 cf3ace7276314180bb96893c755fffe5 +msgid "get_normalized_value.png" msgstr "" -#: ../../en/units/dac.rst:134 3b27846a5d324dbbabb1b21bdde8ca29 +#: ../../en/units/dac.rst:134 bf49db2c96f5448697c7c0ddb0b92cc6 msgid "Set the normalized value of the DAC." msgstr "" -#: ../../en/units/dac.rst:140 acb2643288304263a3a6e4e10ec2063b -msgid "|set_normalized_value.svg|" +#: ../../en/units/dac.rst:140 0e59fee831714aab8f2ffeb7788d23f5 +msgid "|set_normalized_value.png|" msgstr "" -#: ../../en/refs/unit.dac.ref:33 5c7d1cd44f8c41a3bc11f0ee7e1f0a91 -msgid "set_normalized_value.svg" +#: ../../en/refs/unit.dac.ref:24 3712bd22940d40e7a3c34006dc024dd9 +msgid "set_normalized_value.png" msgstr "" -#: ../../en/units/dac.rst:144 1d7bd49b2c0f4de8aa0c5ec8875afa8c +#: ../../en/units/dac.rst:144 2d158085421246d49c7518409f9b405e msgid "Save the current DAC value to EEPROM." msgstr "" -#: ../../en/units/dac.rst:148 9162483c9e4a452eae61ae290e5663c4 -msgid "|save_to_eeprom.svg|" +#: ../../en/units/dac.rst:148 5251ddc3d0764ceab2b8128b9cec5f53 +msgid "|save_to_eeprom.png|" msgstr "" -#: ../../en/refs/unit.dac.ref:36 f6eb3cc994e34b009fa5656bda30a3ab -msgid "save_to_eeprom.svg" +#: ../../en/refs/unit.dac.ref:26 3dc686d452b643fc99937c320ed6b058 +msgid "save_to_eeprom.png" msgstr "" +#~ msgid "|init.svg|" +#~ msgstr "" + +#~ msgid "init.svg" +#~ msgstr "" + +#~ msgid "|get_value.svg|" +#~ msgstr "" + +#~ msgid "get_value.svg" +#~ msgstr "" + +#~ msgid "|get_voltage.svg|" +#~ msgstr "" + +#~ msgid "get_voltage.svg" +#~ msgstr "" + +#~ msgid "|set_value.svg|" +#~ msgstr "" + +#~ msgid "set_value.svg" +#~ msgstr "" + +#~ msgid "|set_voltage.svg|" +#~ msgstr "" + +#~ msgid "set_voltage.svg" +#~ msgstr "" + +#~ msgid "|get_raw_value.svg|" +#~ msgstr "" + +#~ msgid "get_raw_value.svg" +#~ msgstr "" + +#~ msgid "|set_raw_value.svg|" +#~ msgstr "" + +#~ msgid "set_raw_value.svg" +#~ msgstr "" + +#~ msgid "|get_normalized_value.svg|" +#~ msgstr "" + +#~ msgid "get_normalized_value.svg" +#~ msgstr "" + +#~ msgid "|set_normalized_value.svg|" +#~ msgstr "" + +#~ msgid "set_normalized_value.svg" +#~ msgstr "" + +#~ msgid "|save_to_eeprom.svg|" +#~ msgstr "" + +#~ msgid "save_to_eeprom.svg" +#~ msgstr "" + diff --git a/docs/locales/zh_CN/LC_MESSAGES/units/dlight.po b/docs/locales/zh_CN/LC_MESSAGES/units/dlight.po index 2ed61b47..06677bd0 100644 --- a/docs/locales/zh_CN/LC_MESSAGES/units/dlight.po +++ b/docs/locales/zh_CN/LC_MESSAGES/units/dlight.po @@ -9,7 +9,7 @@ msgid "" msgstr "" "Project-Id-Version: UIFlow2 Programming Guide \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-01-16 15:35+0800\n" +"POT-Creation-Date: 2025-04-18 15:58+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language: zh_CN\n" @@ -20,110 +20,110 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.16.0\n" -#: ../../en/units/dlight.rst:2 33148228d23c482fa12d414cc10c8fe8 +#: ../../en/units/dlight.rst:2 c66002a62067494886eff0073a394502 msgid "DLight Unit" msgstr "" -#: ../../en/units/dlight.rst:6 5d824bcd39ac464c9788de54b9ef0a53 +#: ../../en/units/dlight.rst:6 b7fade91875f423886736f5d8e66a2db msgid "Support the following products:" msgstr "支持以下产品:" -#: ../../en/units/dlight.rst:8 b18ce837b65a4dc1a167d51db5ea7c3a +#: ../../en/units/dlight.rst:8 1fccafb71c49458da7d13cb71a2e41e9 msgid "|Dlight|" msgstr "" -#: ../../en/refs/unit.dlight.ref 5fea689f65c941f7b6fb6456d60ce9ef +#: ../../en/refs/unit.dlight.ref 0024e20d73e0412aa8c1d17a9e6dbac4 msgid "Dlight" msgstr "" -#: ../../en/units/dlight.rst:11 d3e3eac27e914939b364a1f28e946f28 +#: ../../en/units/dlight.rst:11 d732b2d777544658ab61117b306dcc0c msgid "Micropython Example:" msgstr "" -#: ../../en/units/dlight.rst:18 6633b47b604b4a7b9d9b765c4e0ce1b9 +#: ../../en/units/dlight.rst:18 12e6f836de704dbbba7fcc014fb6991a msgid "UIFLOW2 Example:" msgstr "" -#: ../../en/units/dlight.rst:20 7bab8f2ff2b146f5ac2fbedde6359be4 +#: ../../en/units/dlight.rst:20 d3cc913c184547a9ad248d0b0e091b8f msgid "|example.png|" msgstr "" -#: ../../en/refs/unit.dlight.ref:7 e7b8632fe137409ba4cf96b571607a54 +#: ../../en/refs/unit.dlight.ref:7 30770058f06b4c1c89cca6c46da13257 msgid "example.png" msgstr "" -#: ../../en/units/dlight.rst:25 996542f9ecff4d8d9c3f3ebefc19d957 +#: ../../en/units/dlight.rst:25 e9ea9316ef8445ce8e4d8fd8fd9d6643 msgid "|dlight_core_example.m5f2|" msgstr "" -#: ../../en/units/dlight.rst:29 bcd62dcd938b4e99ab6718ff87dec0c5 +#: ../../en/units/dlight.rst:29 3d980333b65446bd9e401c6b0438ac93 msgid "class DLight" msgstr "" -#: ../../en/units/dlight.rst:32 aeb69efe7ed743d6b376b4a5757dcf44 +#: ../../en/units/dlight.rst:32 4514f3e23eea445d999cfa08ddbfdcc8 msgid "Constructors" msgstr "" -#: ../../en/units/dlight.rst:36 29204b771f5b441abdc5fb016143238c +#: ../../en/units/dlight.rst:36 79b2b5c0effe4fac9175a671250261e3 msgid "Create a DLight object." msgstr "创建一个DLight对象." -#: ../../en/units/dlight.rst c204add87d014bf491f361eee353a17f +#: ../../en/units/dlight.rst 8569557a93084fe695f8021b36bc630c #, fuzzy msgid "Parameters" msgstr "参数是:" -#: ../../en/units/dlight.rst:38 3443de9a753f406fbd93a32621c259d2 +#: ../../en/units/dlight.rst:38 45e68a7dcfdf440b922fd594b1c9efc6 #, fuzzy msgid "the I2C object." msgstr "创建一个DLight对象." -#: ../../en/units/dlight.rst:39 798a5e9664eb4144abf0cdc92a11e947 +#: ../../en/units/dlight.rst:39 e3218ce82912479a8196b2bf45e77661 msgid "the I2C address of the device. Default is 0x23." msgstr "" #: ../../en/units/dlight.rst:41 ../../en/units/dlight.rst:55 -#: ../../en/units/dlight.rst:64 6f35b29ec9174343a352e73c13c4734d -#: 903d03f1a1124d5e8a973905b7213ab1 e24ce3a1ed574119bfa1867cd95a0682 +#: ../../en/units/dlight.rst:64 226a0b2eb06347b7b637a65b91b23fc6 +#: 28d8820809234ade81c6ca507bb6ea45 e29920960aa34541a180b688f9748d18 msgid "UIFLOW2:" msgstr "" -#: ../../en/units/dlight.rst:43 35fe5d8a6f1b45dfb17d73dc078adf8d -msgid "|init.svg|" +#: ../../en/units/dlight.rst:43 cdd25f4874c644e7b263d49b6d3bdf6e +msgid "|init.png|" msgstr "" -#: ../../en/refs/unit.dlight.ref:9 a8c31611d38b4ddd8da6e56a61dd3040 -msgid "init.svg" +#: ../../en/refs/unit.dlight.ref:9 1109e0af78a54b67a7e61af9bcc20255 +msgid "init.png" msgstr "" -#: ../../en/units/dlight.rst:49 7e97fc67bf6b42f892e5afa85c5f6138 +#: ../../en/units/dlight.rst:49 394d26a854144466a186320b1b231350 msgid "Methods" msgstr "" -#: ../../en/units/dlight.rst:53 39bf627b913b4bc8a6c18e2a68b93fe6 +#: ../../en/units/dlight.rst:53 7bb9f0284b844ee5ac157eb3d3e1a0f3 msgid "Get light lux." msgstr "获取光照强度。" -#: ../../en/units/dlight.rst:57 f2328cc2226c41c2a694ef3224598748 -msgid "|get_lux.svg|" +#: ../../en/units/dlight.rst:57 0904e82043bc4962af92d979b366b2b8 +msgid "|get_lux.png|" msgstr "" -#: ../../en/refs/unit.dlight.ref:11 f828cd3caa6e4c279c3e4ad08a5ef3da -msgid "get_lux.svg" +#: ../../en/refs/unit.dlight.ref:11 2a4fef7e210440abb0c52e25767e9a20 +msgid "get_lux.png" msgstr "" -#: ../../en/units/dlight.rst:62 1f76728a49cd45d8952537377d0c2946 +#: ../../en/units/dlight.rst:62 93d522e45dc24ca9b122946f9a933ff3 msgid "" "Configure the measurement mode (continuous measurement/single " "measurement) and resolution." msgstr "配置测量模式(持续测量/单次测量)和分辨率。" -#: ../../en/units/dlight.rst:66 137f74bc1e0946e6a81add12b1b84cde -msgid "|configure.svg|" +#: ../../en/units/dlight.rst:66 c0f9c7f483d84a94b369f4e2e1b14436 +msgid "|configure.png|" msgstr "" -#: ../../en/refs/unit.dlight.ref:13 4a07ac495528453ab0937003367d7c24 -msgid "configure.svg" +#: ../../en/refs/unit.dlight.ref:13 38615256448e43eeac7eeb3b6c0205e9 +msgid "configure.png" msgstr "" #~ msgid "example.svg" @@ -138,3 +138,21 @@ msgstr "" #~ msgid "``PORT`` Define an i2c port." #~ msgstr "``PORT`` 定义i2c端口。" +#~ msgid "|init.svg|" +#~ msgstr "" + +#~ msgid "init.svg" +#~ msgstr "" + +#~ msgid "|get_lux.svg|" +#~ msgstr "" + +#~ msgid "get_lux.svg" +#~ msgstr "" + +#~ msgid "|configure.svg|" +#~ msgstr "" + +#~ msgid "configure.svg" +#~ msgstr "" + diff --git a/docs/locales/zh_CN/LC_MESSAGES/units/earth.po b/docs/locales/zh_CN/LC_MESSAGES/units/earth.po index 7bd7a9a5..addadf62 100644 --- a/docs/locales/zh_CN/LC_MESSAGES/units/earth.po +++ b/docs/locales/zh_CN/LC_MESSAGES/units/earth.po @@ -9,7 +9,7 @@ msgid "" msgstr "" "Project-Id-Version: UIFlow2 Programming Guide \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-01-16 15:35+0800\n" +"POT-Creation-Date: 2025-04-18 15:58+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language: zh_CN\n" @@ -20,151 +20,149 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.16.0\n" -#: ../../en/units/earth.rst:2 f5557e39ef1c43ef8b6bad54209990d4 +#: ../../en/units/earth.rst:2 c3ea704a177949c7bf8d0856d42a11eb msgid "EARTH Unit" msgstr "" -#: ../../en/units/earth.rst:6 60fd17ce780a4fb1abc8bb7ab4eb0bae +#: ../../en/units/earth.rst:6 31beaecd4c88452eb4163666ca0a61e3 msgid "Support the following products:" msgstr "支持以下产品:" -#: ../../en/units/earth.rst:8 171faaf6117f4f97a371dcf2ddb20878 +#: ../../en/units/earth.rst:8 80f194fd8e5e4bbeb577ceb71685e29f msgid "|EARTH|" msgstr "" -#: ../../en/refs/unit.earth.ref 9e37e8b65e084cc38a1649a0c52823cb +#: ../../en/refs/unit.earth.ref b14d5b926b784783b46fa012a3ffd417 msgid "EARTH" msgstr "" -#: ../../en/units/earth.rst:11 078ab49290c542709f866f49f0e47e60 +#: ../../en/units/earth.rst:11 1d13b51b023a457484b2e63b6558d373 msgid "Micropython Example:" msgstr "" -#: ../../en/units/earth.rst:18 209dd649d03a4a4ebe6ffc217171f384 +#: ../../en/units/earth.rst:18 081e91d12f064b2f90da975159c35f9b msgid "UIFLOW2 Example:" msgstr "" -#: ../../en/units/earth.rst:20 2977689a6281444fb807bba5c3476c2d +#: ../../en/units/earth.rst:20 88d599e4a4c445e2bb1589dcdff9610b msgid "|example.png|" msgstr "" -#: ../../en/refs/unit.earth.ref:7 d0d8b266bc504e8dbcf1e22bbba79cd4 +#: ../../en/refs/unit.earth.ref:6 a0a67d277a1b44bc897b81d04244c0ba msgid "example.png" msgstr "" -#: ../../en/units/earth.rst:25 f4bf0d99f1084541b72ef79463576b16 +#: ../../en/units/earth.rst:25 f2559705db444ebc8d3c1398c507bfcb msgid "|earth_core_example.m5f2|" msgstr "" -#: ../../en/units/earth.rst:29 9be1aec7c29448b8ae4aacea0fb34edc +#: ../../en/units/earth.rst:29 064cabc4569c49a89a979dd365528e10 msgid "class Earth" msgstr "" -#: ../../en/units/earth.rst:32 d5b2ad0ce3074be1ae8f7aa78f9d72f2 +#: ../../en/units/earth.rst:32 36c5af3959a74e5792325a056c320b79 msgid "Constructors" msgstr "" -#: ../../en/units/earth.rst:36 ba741d8de7314c6f81f380d82ee3b14c +#: ../../en/units/earth.rst:36 bac5e0ced9404d1a8d85a1d7ce1d17cd msgid "Create an Earth object." msgstr "创建一个Earth对象。" -#: ../../en/units/earth.rst:39 ec7e83955fa5405e82456e7c3d44ee4f +#: ../../en/units/earth.rst:39 6ecbe26322124576933483944348f1b6 msgid "The parameters is:" msgstr "参数是:" -#: ../../en/units/earth.rst:39 41cc5fb1e30c4c6eaf43a4e901531c48 +#: ../../en/units/earth.rst:39 a08be0f7407348e39059904e6f6743f6 msgid "``port`` Is the pin number of the port" msgstr "``port`` 是端口的引脚号" #: ../../en/units/earth.rst:41 ../../en/units/earth.rst:53 #: ../../en/units/earth.rst:62 ../../en/units/earth.rst:71 #: ../../en/units/earth.rst:80 ../../en/units/earth.rst:89 -#: 39f4cd45f33c4dd2a6d362d2a35c71c9 3b33685eebc34e9cb9e9a2cd9219ad26 -#: 4688d2cbf98b4fe190f0fdde6d46c360 993d2c3c69384366b673df1104fd6e25 -#: b5baad9f1ddd48a4aa0d2db10c310e13 bc03e7886cc546459924ebadbc7bc59b +#: de591c64342e4bde90346ae01087fede msgid "UIFLOW2:" msgstr "" -#: ../../en/units/earth.rst:43 7ff9f0867db34c6190eaee7b5c33ee43 -msgid "|init.svg|" +#: ../../en/units/earth.rst:43 ec7fd7cf4d4a40f78de249b514e5886d +msgid "|init.png|" msgstr "" -#: ../../en/refs/unit.earth.ref:9 c20d7996ad404bd4885ada869ce67aea -msgid "init.svg" +#: ../../en/refs/unit.earth.ref:8 99ca66dedb7440079f035c9db42718cd +msgid "init.png" msgstr "" -#: ../../en/units/earth.rst:47 109a41848223493987901a78add7daf3 +#: ../../en/units/earth.rst:47 479fc8349fb04b41b4e99bc027eb3e47 msgid "Methods" msgstr "" -#: ../../en/units/earth.rst:51 4ab8fb11397846d98210129a196581bd +#: ../../en/units/earth.rst:51 c0c78cc562b74ed78b6621126d554845 msgid "" "This method allows you to read the analog captured by EARTH and return an" " integer value. The value ranges from 0 to 65535." msgstr "此方法允许读取EARTH采集的模拟量并返回一个整数型数值。范围为0-65535。" -#: ../../en/units/earth.rst:55 bb11c37e26704ea892baaee719ffba89 -msgid "|get_analog_value.svg|" +#: ../../en/units/earth.rst:55 23a918e34445455e809d1fbc6fbeadf9 +msgid "|get_analog_value.png|" msgstr "" -#: ../../en/refs/unit.earth.ref:12 99bc69cf34254ae4a6f0cbd6c91af265 -msgid "get_analog_value.svg" +#: ../../en/refs/unit.earth.ref:10 6de87c58c78649eca91e5fb92579b746 +msgid "get_analog_value.png" msgstr "" -#: ../../en/units/earth.rst:60 c119450bab8b4f6c97a1cddb52b6e819 +#: ../../en/units/earth.rst:60 43e30e827f354e8eb076e16d305967d1 msgid "" "This method allows you to read the amount of numbers collected by EARTH " "and return an integer value. The value ranges from 0 to 1." msgstr "此方法允许读取EARTH采集的数字量并返回一个整数型数值。范围为0或者1。" -#: ../../en/units/earth.rst:64 db5ad08da6b44cccbb87f5b36cfda5a8 -msgid "|get_digital_value.svg|" +#: ../../en/units/earth.rst:64 db744d8a26bd4b21a10115f63b9d445d +msgid "|get_digital_value.png|" msgstr "" -#: ../../en/refs/unit.earth.ref:15 5497199bff2e4d68b4e75b957489c066 -msgid "get_digital_value.svg" +#: ../../en/refs/unit.earth.ref:12 46658e623e9641e297c8c0440f186b25 +msgid "get_digital_value.png" msgstr "" -#: ../../en/units/earth.rst:69 b1e5859a08d0448ba6b45b8b32f6e0d0 +#: ../../en/units/earth.rst:69 c118123f47d0465fa3c47880b97323fa msgid "" "This method allows you to read the voltage value collected by EARTH and " "return an integer value. It ranges from 0 to 3300." msgstr "此方法允许读取EARTH采集的电压值并返回一个整数型数值。范围0-3300。" -#: ../../en/units/earth.rst:73 cb3bdc60540e4ff7a79c20e1755cac5d -msgid "|get_voltage_mv.svg|" +#: ../../en/units/earth.rst:73 22fb9a40d2ab485997e734e1815062da +msgid "|get_voltage_mv.png|" msgstr "" -#: ../../en/refs/unit.earth.ref:18 f2d0e3d081b4460889645e1d4f9ca5ef -msgid "get_voltage_mv.svg" +#: ../../en/refs/unit.earth.ref:14 023cadd6df154a73ae41c18c3be5d7e6 +msgid "get_voltage_mv.png" msgstr "" -#: ../../en/units/earth.rst:78 6b15be3846404c659f6ace942a529c62 +#: ../../en/units/earth.rst:78 e5f7b75331e14f6ba4d0341643d16976 msgid "" "This method allows you to read the voltage value collected by EARTH and " "return a floating-point value. Range 0.0 to 1.0." msgstr "此方法允许读取EARTH采集的电压值并返回一个浮点型数值。范围0.0-1.0。" -#: ../../en/units/earth.rst:82 462c3b1f5dd649c4ad3b90ba71459760 -msgid "|humidity.svg|" +#: ../../en/units/earth.rst:82 4c82cef957944f6eb7220cb536f05796 +msgid "|humidity.png|" msgstr "" -#: ../../en/refs/unit.earth.ref:21 3fa91f5eba2d4856a1854c11a6b01c6a -msgid "humidity.svg" +#: ../../en/refs/unit.earth.ref:16 2f8006f6bfc1485cb60aafab92f6332f +msgid "humidity.png" msgstr "" -#: ../../en/units/earth.rst:87 f0ef391a7edd4acbbc1b4192495bfae0 +#: ../../en/units/earth.rst:87 e327fc425125479d8211f88c25eeb13b msgid "" "This method allows setting the maximum (0-3300) and minimum (0-3300) " "values for calibrating the EARTH sensor." msgstr "此方法允许设置校准EARTH传感器的最大(0-3300)和最小值(0-3300)。" -#: ../../en/units/earth.rst:91 2c987d1a21264e24aed817b58c374655 -msgid "|set_calibrate.svg|" +#: ../../en/units/earth.rst:91 99dc4a83526c40d9b770f2ea4f94a1ee +msgid "|set_calibrate.png|" msgstr "" -#: ../../en/refs/unit.earth.ref:24 c7f4b9a6c3a3407a86ebc071f5a31aba -msgid "set_calibrate.svg" +#: ../../en/refs/unit.earth.ref:18 b7d47fa4142345b89467640c5d606e16 +msgid "set_calibrate.png" msgstr "" #~ msgid "example.svg" @@ -176,3 +174,39 @@ msgstr "" #~ msgid "|example.svg|" #~ msgstr "" +#~ msgid "|init.svg|" +#~ msgstr "" + +#~ msgid "init.svg" +#~ msgstr "" + +#~ msgid "|get_analog_value.svg|" +#~ msgstr "" + +#~ msgid "get_analog_value.svg" +#~ msgstr "" + +#~ msgid "|get_digital_value.svg|" +#~ msgstr "" + +#~ msgid "get_digital_value.svg" +#~ msgstr "" + +#~ msgid "|get_voltage_mv.svg|" +#~ msgstr "" + +#~ msgid "get_voltage_mv.svg" +#~ msgstr "" + +#~ msgid "|humidity.svg|" +#~ msgstr "" + +#~ msgid "humidity.svg" +#~ msgstr "" + +#~ msgid "|set_calibrate.svg|" +#~ msgstr "" + +#~ msgid "set_calibrate.svg" +#~ msgstr "" + diff --git a/docs/locales/zh_CN/LC_MESSAGES/units/encoder.po b/docs/locales/zh_CN/LC_MESSAGES/units/encoder.po index abc59cc8..3a143b78 100644 --- a/docs/locales/zh_CN/LC_MESSAGES/units/encoder.po +++ b/docs/locales/zh_CN/LC_MESSAGES/units/encoder.po @@ -9,7 +9,7 @@ msgid "" msgstr "" "Project-Id-Version: UIFlow2 Programming Guide \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-01-16 15:35+0800\n" +"POT-Creation-Date: 2025-04-18 15:58+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language: zh_CN\n" @@ -20,65 +20,65 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.16.0\n" -#: ../../en/units/encoder.rst:2 b11da7f4566c4ef28273f261091dd97b +#: ../../en/units/encoder.rst:2 903d519f6cad4614a6a31bd58a802d22 msgid "Encoder Unit" msgstr "" -#: ../../en/units/encoder.rst:6 225cd0ec0c2849ea90e743ae7f898733 +#: ../../en/units/encoder.rst:6 baa2e848ae1c4a5b832e05faa1196d34 msgid "The following products are supported:" msgstr "" -#: ../../en/units/encoder.rst:8 97d46f1df2734e929feb38136f2b4d9d +#: ../../en/units/encoder.rst:8 3f366db5024744419e4b44081887ea24 msgid "|Encoder|" msgstr "" -#: ../../en/refs/unit.encoder.ref c54a50d6611a42638f32b6d37c96faea +#: ../../en/refs/unit.encoder.ref 8f0eaa4624444f6c876b446622483365 msgid "Encoder" msgstr "" -#: ../../en/units/encoder.rst:11 6083e3a547014a28bdbb1170d26c23b6 +#: ../../en/units/encoder.rst:11 57fde4b964fd4db48a376ea3812de03e msgid "Micropython Example:" msgstr "" -#: ../../en/units/encoder.rst:18 c9fe8fd09325432a965802b095e401a1 +#: ../../en/units/encoder.rst:18 f8ed7b6db8554d43a4e0d5ccf2abdf09 msgid "UIFLOW2 Example:" msgstr "" -#: ../../en/units/encoder.rst:20 27bf55e180b24b4a8d98e2736f9ba338 -msgid "|example.svg|" +#: ../../en/units/encoder.rst:20 001011490cf64158b1fec6e11b68fd0f +msgid "|example.png|" msgstr "" -#: ../../en/refs/unit.encoder.ref:7 ff06ed59ef3443c383d4f8898b2627f9 -msgid "example.svg" +#: ../../en/refs/unit.encoder.ref:7 94c2c6760f73406eb11976171e80608b +msgid "example.png" msgstr "" -#: ../../en/units/encoder.rst:25 1b9d631c16c74360bfc46e828aaceda5 +#: ../../en/units/encoder.rst:25 7d4ded7ad11d4bfb9c0c5281d4091b47 msgid "|stickc_plus2_encoder_example.m5f2|" msgstr "" -#: ../../en/units/encoder.rst:29 aff6123b27ce40fbad4ee930bace26c2 +#: ../../en/units/encoder.rst:29 c6ea00b47f214af6b32791bd5d3ecec2 msgid "class EncoderUnit" msgstr "" -#: ../../en/units/encoder.rst:32 1f7f7e7dee8447359c2741f721d1a317 +#: ../../en/units/encoder.rst:32 600bfc885c9e486f9f07db8f36eebdaf msgid "Constructors" msgstr "" -#: ../../en/units/encoder.rst:36 1695b66d5fe44182b965e25e79484b97 +#: ../../en/units/encoder.rst:36 af6b869e4b9b46a69825e18ce48e2d81 msgid "Creates a Rotary object." msgstr "" -#: ../../en/units/encoder.rst 1d22c00f394c4c18ade467454df8a777 -#: ab5817d3af6444c1b4adbce230f7350c d0d9a4f1781a43dd8ecaff519443832b -#: e2b1e9f173e8415fb39cdde93e8f340c +#: ../../en/units/encoder.rst 2b98b9d7d21647f5a06cb2a2c6474ca2 +#: 3eb97e153bf6456f9f69e7f7a45868b7 45e590c41b744164ba54c5f63391c3cf +#: fdcce70706f1404cb8287b60a30bf868 msgid "Parameters" msgstr "" -#: ../../en/units/encoder.rst:38 4a9ca5a1743b45c4ba51a80cecb4fe82 +#: ../../en/units/encoder.rst:38 dd42794601e64761932f12f969dc24ea msgid "I2C object." msgstr "" -#: ../../en/units/encoder.rst:39 d77ec2e000264ff9af8a24b10f94eaa6 +#: ../../en/units/encoder.rst:39 152b84bf5f8b43048ed2dfeeec1caf66 msgid "I2C address, Default is 0x40." msgstr "" @@ -86,131 +86,191 @@ msgstr "" #: ../../en/units/encoder.rst:62 ../../en/units/encoder.rst:72 #: ../../en/units/encoder.rst:81 ../../en/units/encoder.rst:92 #: ../../en/units/encoder.rst:101 ../../en/units/encoder.rst:113 -#: ../../en/units/encoder.rst:124 3a454cd6b14b44af96cc592fcaffadcf -#: 8eba2ae9d06c420a9a641e119fb3162e a4ddc6a486d847e08f500da954c3e269 -#: a8144994717d4b6aaeb2b1de010b0ea3 b3b54f9c17ba4ecd9cb70e6839581bf5 -#: cf83466a5ef74fd3979e725e2aafb071 f8a8716578af4868b01137df68d96ad5 -#: f9d3d622859f484f8a6616b0ed03ad9a fffc680a55c54b178e3ee975313eb929 +#: ../../en/units/encoder.rst:124 013d3c919cfe47a18342c7aa7e8f5414 +#: 1722252e67c34f09ba834fad8090c89b 29299707aa544b6dba8fe480179bb8d7 +#: 2963961bc70e45cc8c3ac8d94633c48d 774ee4665ea646d5965cbfe3903a60f1 +#: 9b78a3e79c8c421ba45ed54c8efcd2b7 b6cebb37c73f4449bd943cddcb27d2bf +#: ca5ac3f6014841e7880a0aba73f0c43f ef9efe168e0c4075a982499550851442 msgid "UIFLOW2:" msgstr "" -#: ../../en/units/encoder.rst:43 7e7ff7a028cd4a95bf3d7c9633181cfc -msgid "|init.svg|" +#: ../../en/units/encoder.rst:43 81a29d0d4cf34c379cda045d34387504 +msgid "|init.png|" msgstr "" -#: ../../en/refs/unit.encoder.ref:9 648b7ac4e1954b35b19c59bf58c8fb78 -msgid "init.svg" +#: ../../en/refs/unit.encoder.ref:9 4b098fe0d977481fb131a03f33bfeca4 +msgid "init.png" msgstr "" -#: ../../en/units/encoder.rst:47 063d158e6cd249dcbdff62edc0242031 +#: ../../en/units/encoder.rst:47 54eba84092b9497ca781c7219a4f39b5 msgid "Methods" msgstr "" -#: ../../en/units/encoder.rst:51 17827889a16e4b2ea5df70bff83a1a29 +#: ../../en/units/encoder.rst:51 4f94db23544e4ac1ba1e69e027313feb msgid "Gets the rotation status of the Rotary object." msgstr "" -#: ../../en/units/encoder.rst:55 a8c0ea26a9214b81a050007b0a5e0976 -msgid "|get_rotary_status.svg|" +#: ../../en/units/encoder.rst:55 efdeb2dab4154ae1a70f7e003a106612 +msgid "|get_rotary_status.png|" msgstr "" -#: ../../en/refs/unit.encoder.ref:11 bae61fc333a54336b79e5bffaf362faa -msgid "get_rotary_status.svg" +#: ../../en/refs/unit.encoder.ref:11 cee71bd309414551afe9fad85bf76488 +msgid "get_rotary_status.png" msgstr "" -#: ../../en/units/encoder.rst:60 1e57a3194f19485bbadcd1bbf44c0bea +#: ../../en/units/encoder.rst:60 27df269d74e4406d88f68a74a6a7aa3e msgid "Gets the rotation value of the Rotary object." msgstr "" -#: ../../en/units/encoder.rst:64 bf6edd85c37c4be9898d63236613e076 -msgid "|get_rotary_value.svg|" +#: ../../en/units/encoder.rst:64 af376dd182214dbfb2d3b04fff6aa935 +msgid "|get_rotary_value.png|" msgstr "" -#: ../../en/refs/unit.encoder.ref:13 1f0e6e309db348de924bf0b9ee4b82a2 -msgid "get_rotary_value.svg" +#: ../../en/refs/unit.encoder.ref:13 d6345d541cbe4738a5f0b5d63eaed23e +msgid "get_rotary_value.png" msgstr "" -#: ../../en/units/encoder.rst:69 8258d9da2e7f4531ac4bde82216b29a8 +#: ../../en/units/encoder.rst:69 5a148c0c5fa648fe8ba026dee836b165 msgid "" "Gets the rotation increment of the Rotary object. Can be used to " "determine the direction of rotation." msgstr "" -#: ../../en/units/encoder.rst:74 eba9c7c650a64f409b23b285f720285a -msgid "|get_rotary_increments.svg|" +#: ../../en/units/encoder.rst:74 d39dc15214e041db92eff6c0073e3cc6 +msgid "|get_rotary_increments.png|" msgstr "" -#: ../../en/refs/unit.encoder.ref:17 235d0e6b09e043d981be96ec89e2571c -msgid "get_rotary_increments.svg" +#: ../../en/refs/unit.encoder.ref:17 cf59680b50a74da8bb04e37d7b9df05e +msgid "get_rotary_increments.png" msgstr "" -#: ../../en/units/encoder.rst:79 a5370ec6a9e34f29bf5ee884c05383e3 +#: ../../en/units/encoder.rst:79 eb1a3a3d7b394a808e0185b75f6e5f12 msgid "Resets the rotation value of the Rotary object." msgstr "" -#: ../../en/units/encoder.rst:83 f1cb6a9f3af8465b9a3d6f3d39c32328 -msgid "|reset_rotary_value.svg|" +#: ../../en/units/encoder.rst:83 f74368d083974c86b8ee2dc1fc2ede6f +msgid "|reset_rotary_value.png|" msgstr "" -#: ../../en/refs/unit.encoder.ref:19 81053da12b2742448698508be177e2a7 -msgid "reset_rotary_value.svg" +#: ../../en/refs/unit.encoder.ref:19 94cfea311c8544859f33ab46c924f9df +msgid "reset_rotary_value.png" msgstr "" -#: ../../en/units/encoder.rst:88 20d0dc5f4b4044ceb4268a85020248de +#: ../../en/units/encoder.rst:88 b244e11a4d9c42b38fc2416b0405a840 msgid "Sets the rotation value of the Rotary object." msgstr "" -#: ../../en/units/encoder.rst:90 b02276ce1d854ea58a2aa31ac6ec3304 +#: ../../en/units/encoder.rst:90 3f221bb91a9640c48c818957acb19902 msgid "adjust the current value." msgstr "" -#: ../../en/units/encoder.rst:94 1f5c95f1d2404f72bdaabcafec899884 -msgid "|set_rotary_value.svg|" +#: ../../en/units/encoder.rst:94 80f9d8affb614a2baac7bc53d111f7ae +msgid "|set_rotary_value.png|" msgstr "" -#: ../../en/refs/unit.encoder.ref:21 06d9c5a54f2d4309818bd011dd74bbb6 -msgid "set_rotary_value.svg" +#: ../../en/refs/unit.encoder.ref:21 96ee5e0ae5d84cee8158526130a4219b +msgid "set_rotary_value.png" msgstr "" -#: ../../en/units/encoder.rst:99 848191dc111d40a585120f890b269c6b +#: ../../en/units/encoder.rst:99 5996059a6e82413ea8bb7fa1e64a8184 msgid "Get the current status of the rotary encoder keys." msgstr "" -#: ../../en/units/encoder.rst:103 bba98fd4dbcb422e99eab2f1263b589e -msgid "|get_button_status.svg|" +#: ../../en/units/encoder.rst:103 5d78be668702489e9f9b7bcbc4d0ff6d +msgid "|get_button_status.png|" msgstr "" -#: ../../en/refs/unit.encoder.ref:15 9b8f7ac033024789b35b5b1ea85111ef -msgid "get_button_status.svg" +#: ../../en/refs/unit.encoder.ref:15 1d69a86596c749758dc66655ee440e6a +msgid "get_button_status.png" msgstr "" #: ../../en/units/encoder.rst:108 ../../en/units/encoder.rst:120 -#: 5d2ac0d2a11c4af887566f5b2df79838 ab9d320336394df79068971b833665de +#: 477d2d4b552a4a85a273c2dfa28b6292 6cc618fd5ca94cb6b30cee73269871d8 msgid "Set the color of the LED" msgstr "" -#: ../../en/units/encoder.rst:110 6216872b52524d4cba91893ac254f9bc +#: ../../en/units/encoder.rst:110 ebda5fd016ce4b86bb0f80c57a9ab97a msgid "the index of the LED, 1 or 2." msgstr "" #: ../../en/units/encoder.rst:111 ../../en/units/encoder.rst:122 -#: 4a08536b60764ffb9ac964162fd10541 ee5d4cbb28294a4d9b3c786f38245bf5 +#: 52298222dc404ccfa947c7fd9b0a1db5 e396930f550d4d43aa50e97791cc087d msgid "the color of the LED, 0x000000 - 0xFFFFFF." msgstr "" -#: ../../en/units/encoder.rst:115 98953d947aa44432be1c504650ba21be -msgid "|set_color.svg|" +#: ../../en/units/encoder.rst:115 3a87b29f62ae4a67b09b4dbb19f5d612 +msgid "|set_color.png|" msgstr "" -#: ../../en/refs/unit.encoder.ref:23 7453a2c7b6c148e4b02d8bb44c404538 -msgid "set_color.svg" +#: ../../en/refs/unit.encoder.ref:23 16ab2c6d8a6d41579931bc54ae53472b +msgid "set_color.png" msgstr "" -#: ../../en/units/encoder.rst:126 123938467aff494eb75eea081f820478 -msgid "|fill_color.svg|" +#: ../../en/units/encoder.rst:126 a6914207e07042059397b3c0ca9e4882 +msgid "|fill_color.png|" msgstr "" -#: ../../en/refs/unit.encoder.ref:25 228cf0542ee241c89ac90aa37d1690fd -msgid "fill_color.svg" +#: ../../en/refs/unit.encoder.ref:25 01a447537f2a4d60bf247eae66ce7386 +msgid "fill_color.png" msgstr "" +#~ msgid "|example.svg|" +#~ msgstr "" + +#~ msgid "example.svg" +#~ msgstr "" + +#~ msgid "|init.svg|" +#~ msgstr "" + +#~ msgid "init.svg" +#~ msgstr "" + +#~ msgid "|get_rotary_status.svg|" +#~ msgstr "" + +#~ msgid "get_rotary_status.svg" +#~ msgstr "" + +#~ msgid "|get_rotary_value.svg|" +#~ msgstr "" + +#~ msgid "get_rotary_value.svg" +#~ msgstr "" + +#~ msgid "|get_rotary_increments.svg|" +#~ msgstr "" + +#~ msgid "get_rotary_increments.svg" +#~ msgstr "" + +#~ msgid "|reset_rotary_value.svg|" +#~ msgstr "" + +#~ msgid "reset_rotary_value.svg" +#~ msgstr "" + +#~ msgid "|set_rotary_value.svg|" +#~ msgstr "" + +#~ msgid "set_rotary_value.svg" +#~ msgstr "" + +#~ msgid "|get_button_status.svg|" +#~ msgstr "" + +#~ msgid "get_button_status.svg" +#~ msgstr "" + +#~ msgid "|set_color.svg|" +#~ msgstr "" + +#~ msgid "set_color.svg" +#~ msgstr "" + +#~ msgid "|fill_color.svg|" +#~ msgstr "" + +#~ msgid "fill_color.svg" +#~ msgstr "" + diff --git a/docs/locales/zh_CN/LC_MESSAGES/units/env.po b/docs/locales/zh_CN/LC_MESSAGES/units/env.po index 10cc7dfd..eccb181d 100644 --- a/docs/locales/zh_CN/LC_MESSAGES/units/env.po +++ b/docs/locales/zh_CN/LC_MESSAGES/units/env.po @@ -9,7 +9,7 @@ msgid "" msgstr "" "Project-Id-Version: UIFlow2 Programming Guide \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-03-11 15:31+0800\n" +"POT-Creation-Date: 2025-04-18 15:58+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language: zh_CN\n" @@ -18,162 +18,184 @@ msgstr "" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" -"Generated-By: Babel 2.12.1\n" +"Generated-By: Babel 2.16.0\n" -#: ../../en/units/env.rst:2 dbe0d34b44854bef8a639e5cf9495bf6 +#: ../../en/units/env.rst:2 8e8e101b39d74c65b633a491e328d296 msgid "ENV Unit" msgstr "" -#: ../../en/refs/unit.env.ref 40caa12dfc21415dba9a92cd624ada8a -#: cff6865a1121494dab3a3575291c42a0 -msgid "ENV" -msgstr "" - -#: ../../en/refs/unit.env.ref 446627f2f7524eeda5040822ac965044 -#: 4da03936626c4be792cc8a94ecb5aa06 -msgid "ENV II" -msgstr "" - -#: ../../en/refs/unit.env.ref 852208333ab74a90bd1022f203eb87d1 -#: 9dc42095ad3a43239f5fc210a9c4ef62 -msgid "ENV III" -msgstr "" - -#: ../../en/refs/unit.env.ref:14 2089b05b609743ba9c69b2c6fe6eed91 -#: 3779c8dd0bc14146b92ecd07d56dcd0d -msgid "example.svg" -msgstr "" - -#: ../../en/refs/unit.env.ref:16 a38988b745fe4da88179efb78847ba5d -#: f1d24442be044b2e94346ca75f366587 -msgid "init.svg" -msgstr "" +#: ../../en/units/env.rst:6 2e2d4854014848f1a4ad8a81f6fac836 +msgid "The following products are supported:" +msgstr "支持以下产品:" -#: ../../en/refs/unit.env.ref:18 d0a4582095b84b738e9c80fa9e3f02e4 -#: e900ae4860184fe5a29e169863841154 -msgid "read_temperature.svg" +#: ../../en/units/env.rst:9 a07d4573c60749cdb62de4bf3a196c49 +msgid "|ENV|" msgstr "" -#: ../../en/refs/unit.env.ref:20 0286846abb03460cb483b4a9206613c6 -#: 09d1753a711744fd9f00e9e5313f1072 -msgid "read_humidity.svg" +#: ../../en/refs/unit.env.ref afea3c9e842e484bad162123c890aa65 +msgid "ENV" msgstr "" -#: ../../en/refs/unit.env.ref:22 2e4a3b4a11e44370b64bcbcbe410b0f3 -#: e4ebb597a19a42b5a22884d1310faff2 -msgid "read_pressure.svg" +#: ../../en/units/env.rst:9 5b51c422074741089c7ecdba7ec09927 +msgid "|ENV II|" msgstr "" -#: ../../en/units/env.rst:6 9099849389b24bc383eb80a44af7a695 -msgid "The following products are supported:" -msgstr "支持以下产品:" - -#: ../../en/units/env.rst:9 40a102a67f6b45d398f547a59f80dc3c -msgid "|ENV|" +#: ../../en/refs/unit.env.ref d38bccb7194446ef9f62014265f064cf +msgid "ENV II" msgstr "" -#: ../../en/units/env.rst:9 e0efaf453d6b4d48800111ae0e32e65c -msgid "|ENV II|" +#: ../../en/units/env.rst:9 bccaba584c804fac97a60c729bfbce9c +msgid "|ENV III|" msgstr "" -#: ../../en/units/env.rst:9 895bb0e57a6947c79b757b706b2c4321 -msgid "|ENV III|" +#: ../../en/refs/unit.env.ref a34535301a3448d8aed871f27d16bd32 +msgid "ENV III" msgstr "" -#: ../../en/units/env.rst:13 e91f295f1e9345a2ae3d6c3c9334a06f +#: ../../en/units/env.rst:13 95ad6e7238214424ae34c6850b9a181e msgid "Micropython Example::" msgstr "" -#: ../../en/units/env.rst:32 867866698feb48678ebec5094582e060 +#: ../../en/units/env.rst:32 b7daa2d103e34bb3a2ec1ec26c1b11b9 msgid "UIFLOW2 Example:" msgstr "" -#: ../../en/units/env.rst:34 5ac687c5be5c4d7badf750c81ffd6842 -msgid "|example.svg|" +#: ../../en/units/env.rst:34 4bd5813894554f03913e56630d801ff0 +msgid "|example.png|" msgstr "" -#: ../../en/units/env.rst:38 350f90edc2114c6bbeb9fcb84367ec59 +#: ../../en/refs/unit.env.ref:14 1097786b1c7c458b8df072779c0aad34 +msgid "example.png" +msgstr "" + +#: ../../en/units/env.rst:38 c6c7208dab3b4cd4b5c8a8e8cb2f0b18 msgid "|env_cores3_example.m5f2|" msgstr "" -#: ../../en/units/env.rst:42 8deec86e0e8947a692079b964a67a55d +#: ../../en/units/env.rst:42 b954364a8cb54171a7efe66884b7e21c msgid "class ENVUnit" msgstr "" -#: ../../en/units/env.rst:45 f0021b354eb6478ca374a0614fd31c7f +#: ../../en/units/env.rst:45 ac54d8066d704b169902a675c0a4101d msgid "Constructors" msgstr "" -#: ../../en/units/env.rst:49 7a6456e441f242edb0834fc11ddd7612 +#: ../../en/units/env.rst:49 0fc9b9fe387645e3a9024ae92ca2c46a msgid "Create an ENVUnit object." msgstr "创建一个ENV对象。" -#: ../../en/units/env.rst:51 1393bd48aa2443c0b3d5ca12d3c7d5f1 +#: ../../en/units/env.rst:51 8b58968745044720b5c03aa953937fc2 msgid "parameter is:" msgstr "参数是:" -#: ../../en/units/env.rst:53 0afe99ca7ced4813a8edd5570ef7ae41 +#: ../../en/units/env.rst:53 1dc8abea6083424f9347198ce6a7252b msgid "``i2c`` is an I2C object." msgstr "``i2c`` 是一个 I2C 对象。" -#: ../../en/units/env.rst:54 3bce30d226d94c25a3edf4edf87f611e +#: ../../en/units/env.rst:54 d40e85c55f324cc487b89e0db636174c msgid "``type`` is the type of ENVUnit" msgstr "``type`` 是ENV的类型" -#: ../../en/units/env.rst:56 f308ad8bf59d446480e94d8c3fde2b10 +#: ../../en/units/env.rst:56 52e927a8867845fda918dafcc2835fde msgid "``1`` - ENV" msgstr "" -#: ../../en/units/env.rst:57 070f737b3e364132a560e1b0c8df0e3a +#: ../../en/units/env.rst:57 7e63cf781f5b43d0abed54ea9a48b61a msgid "``2`` - ENV II" msgstr "" -#: ../../en/units/env.rst:58 1fa32986bc604a7ea60cda445a63e065 +#: ../../en/units/env.rst:58 58fc096dc022496d853223db582dc535 msgid "``3`` - ENV III" msgstr "" #: ../../en/units/env.rst:60 ../../en/units/env.rst:72 #: ../../en/units/env.rst:81 ../../en/units/env.rst:90 -#: 26c9ef2482f04f27ab5bdcf57edd7c32 299a814af9194f7592a751cdc9904674 -#: 3419b84a980143ca97936939c2a43a8e ca8a6a55dda7495cbbaea73c680db9ca +#: 0b37b63129a54dd186e8498b1b40726b 386dbdf96af54755bc955dc92dad3f04 +#: 659051f08e70497aaf35fe2078236fb1 a7e2751aa8394e75865c32e081bb1916 msgid "UIFLOW2:" msgstr "" -#: ../../en/units/env.rst:62 4497179b4b12492d8726bfbb88c02c33 -msgid "|init.svg|" +#: ../../en/units/env.rst:62 a4920ec29cc14f72a26f558aabdfea11 +msgid "|init.png|" +msgstr "" + +#: ../../en/refs/unit.env.ref:16 7d644545d3784edc8c13717ad295160d +msgid "init.png" msgstr "" -#: ../../en/units/env.rst:66 459276a30a1b423ab5fba440ad25b253 +#: ../../en/units/env.rst:66 d0a4de58dad74d96b23158e2d62cdd66 msgid "Methods" msgstr "" -#: ../../en/units/env.rst:70 ad1e06b4adab48258138370aff913b54 +#: ../../en/units/env.rst:70 7ae8133ff20f4eefa6e61bcc5f1986fa msgid "" "This method allows to read the temperature value collected by ENV and " "returns a floating point value. The unit of measurement is °C." msgstr "此方法允许读取ENV采集的温度值并返回一个浮点型数值。计量单位为°C。" -#: ../../en/units/env.rst:74 1626cfc4f59c4e6cb6aa74795585dc38 -msgid "|read_temperature.svg|" +#: ../../en/units/env.rst:74 9936186875c74828b310b55fff79bcd4 +msgid "|read_temperature.png|" +msgstr "" + +#: ../../en/refs/unit.env.ref:18 dbd73ffa636d4977b3fe992ceaea9f82 +msgid "read_temperature.png" msgstr "" -#: ../../en/units/env.rst:79 521e8a12017a408084e37c5299a2d261 +#: ../../en/units/env.rst:79 16e6bfc817f34575be6a815d4f238b6b msgid "" "This method allows to read the relative humidity value collected by ENV " "and returns a floating point value. The unit of measurement is %RH." msgstr "此方法允许读取ENV采集的相对湿度值并返回一个浮点型数值。计量单位为%RH。" -#: ../../en/units/env.rst:83 bf729fb070a14835b78768bc762984fd -msgid "|read_humidity.svg|" +#: ../../en/units/env.rst:83 a2b2dd518c71418482de364481743027 +msgid "|read_humidity.png|" msgstr "" -#: ../../en/units/env.rst:88 8a3d529e4e304ea0bad8f7f8d8353695 +#: ../../en/refs/unit.env.ref:20 a033a04048ad40f589beee4cf7bf6012 +msgid "read_humidity.png" +msgstr "" + +#: ../../en/units/env.rst:88 490a190cd13b47bcbeed310159166b9b msgid "" "This method allows to read the atmospheric pressure collected by ENV and " "returns a floating point value. The unit of measurement is Pa." msgstr "此方法允许读取ENV采集的大气压并返回一个浮点型数值。计量单位为Pa。" -#: ../../en/units/env.rst:92 314860a1a59040608c8b7099ddc3432b -msgid "|read_pressure.svg|" +#: ../../en/units/env.rst:92 98d3c321452c4af48606fb56794b0bdf +msgid "|read_pressure.png|" msgstr "" +#: ../../en/refs/unit.env.ref:22 8b390430d4894f86adc138b2ad87e9a9 +msgid "read_pressure.png" +msgstr "" + +#~ msgid "example.svg" +#~ msgstr "" + +#~ msgid "init.svg" +#~ msgstr "" + +#~ msgid "read_temperature.svg" +#~ msgstr "" + +#~ msgid "read_humidity.svg" +#~ msgstr "" + +#~ msgid "read_pressure.svg" +#~ msgstr "" + +#~ msgid "|example.svg|" +#~ msgstr "" + +#~ msgid "|init.svg|" +#~ msgstr "" + +#~ msgid "|read_temperature.svg|" +#~ msgstr "" + +#~ msgid "|read_humidity.svg|" +#~ msgstr "" + +#~ msgid "|read_pressure.svg|" +#~ msgstr "" + diff --git a/docs/locales/zh_CN/LC_MESSAGES/units/flash_light.po b/docs/locales/zh_CN/LC_MESSAGES/units/flash_light.po index 946845eb..e379359c 100644 --- a/docs/locales/zh_CN/LC_MESSAGES/units/flash_light.po +++ b/docs/locales/zh_CN/LC_MESSAGES/units/flash_light.po @@ -9,7 +9,7 @@ msgid "" msgstr "" "Project-Id-Version: UIFlow2 Programming Guide \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-01-16 15:35+0800\n" +"POT-Creation-Date: 2025-04-03 10:58+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language: zh_CN\n" @@ -20,11 +20,11 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.16.0\n" -#: ../../en/units/flash_light.rst:3 ed2bf565f8484eb0b0bab3fd28a20d6b +#: ../../en/units/flash_light.rst:3 01cdb17cdd9840c7b7f344bcebba0ca2 msgid "FlashLight Unit" msgstr "" -#: ../../en/units/flash_light.rst:8 6069efeba92a49ea89888b4b75ab9297 +#: ../../en/units/flash_light.rst:8 ea165703da1344519c0ba5d17c2c9ee6 msgid "" "FlashLight UNIT is an I/O Unit with built-in flash, including AW3641 " "driver and a white LED, with a color temperature of 5000-5700K. There is " @@ -33,83 +33,83 @@ msgid "" "Unit can be used as a flash or lighting applications." msgstr "" -#: ../../en/units/flash_light.rst:15 63d03742e5064f6b8926a67c2a464f6b +#: ../../en/units/flash_light.rst:15 40723b0e9f2e4d959e918c47aabbb893 msgid "Support the following products:" msgstr "" -#: ../../en/units/flash_light.rst:17 81dae3f2a0914398b75a02ee0b267dbd +#: ../../en/units/flash_light.rst:17 a836570b198547ca83d66e83694b90af msgid "|FlashLightUnit|" msgstr "" -#: ../../en/refs/unit.flash_light.ref 72c015055d9d4da084c21d3a8f16ba9f +#: ../../en/refs/unit.flash_light.ref 13e57b883db04339b8642e9cb7483c95 msgid "FlashLightUnit" msgstr "" -#: ../../en/units/flash_light.rst:20 af3e95a6812d4797ae31deaee3121f6e +#: ../../en/units/flash_light.rst:20 55410bd308b94e1e96db702fb8fdcfaf msgid "Micropython Example:" msgstr "" -#: ../../en/units/flash_light.rst:27 d642943492eb40baafc9f969396dc19b +#: ../../en/units/flash_light.rst:27 edf6655b25d1462aa57fd033b92c723f msgid "UIFLOW2 Example:" msgstr "" -#: ../../en/units/flash_light.rst:29 8eb120c4c4ef47de8154f5c920f66b01 +#: ../../en/units/flash_light.rst:29 2050293e551949babc1ebd798059e8db msgid "|example.png|" msgstr "" -#: ../../en/refs/unit.flash_light.ref:10 33824a3097a44a009f11b2f5db75f45f +#: ../../en/refs/unit.flash_light.ref:10 555dbcd9bb28456baae422839a68ca30 msgid "example.png" msgstr "" -#: ../../en/units/flash_light.rst:34 e4b1a3342fce471aad63707a34d1a4f4 -msgid "|cores3_glass_example.m5f2|" +#: ../../en/units/flash_light.rst:34 05aad41a54f4454b871331519736d390 +msgid "|cores3_flashlight_example.m5f2|" msgstr "" -#: ../../en/units/flash_light.rst:38 438e83f2475545f0af21fca291c88ba6 +#: ../../en/units/flash_light.rst:38 2b415569ba0e48248f539d76cc025d1b msgid "class FlashLightUnit" msgstr "" -#: ../../en/units/flash_light.rst:41 ea01c027bd86458cbd781bf10924b6f8 +#: ../../en/units/flash_light.rst:41 adb48bb287564d749333b8e4273ff695 msgid "Constructors" msgstr "" -#: ../../en/units/flash_light.rst:45 49b3afd78bc14970887a0d4242713bf7 +#: ../../en/units/flash_light.rst:45 5fe094ea9e0d4381afcd7f4a15dd31be msgid "Initialize the FlashLightUnit." msgstr "" -#: ../../en/units/flash_light.rst 0830d2c30f0d488fb5375b1e9a691b3a -#: 9f4901c87dff48d69b4dd12734f72755 +#: ../../en/units/flash_light.rst 8634e92742424b299b06f3dfb9256a58 +#: 99df4c19bffc4d2192c84a22e3993a15 msgid "Parameters" msgstr "" -#: ../../en/units/flash_light.rst:47 49e002685fad49bea4b0ec5204943dfe +#: ../../en/units/flash_light.rst:47 91647853bccc42a79a07690008eb31c7 msgid "" "The port to which the FlashLightUnit is connected. port[0]: adc pin, " "port[1]: pump pin." msgstr "" #: ../../en/units/flash_light.rst:49 ../../en/units/flash_light.rst:83 -#: 033eaeb724434011bb3719008be93aa7 705870f9273c49b796a14cf4e931fac1 +#: 5bad6b1c8bf140b9805e0445e36468a5 e219af01e22448d4b7f0ec313bfe2579 msgid "UIFLOW2:" msgstr "" -#: ../../en/units/flash_light.rst:51 931e15d7ccad4f08a9c00af0c59f3b36 +#: ../../en/units/flash_light.rst:51 a56bfcade1f14fe39357cfc304667e26 msgid "|init.png|" msgstr "" -#: ../../en/refs/unit.flash_light.ref:7 5b139adb57ee415ebf3b8628bb1e78f5 +#: ../../en/refs/unit.flash_light.ref:7 0116fc64da034b76a7bd20bb45184229 msgid "init.png" msgstr "" -#: ../../en/units/flash_light.rst:55 1d3e110a1f234e6f87f6deb4b89dba68 +#: ../../en/units/flash_light.rst:55 111310cb13514abc8e46aca4d3d16302 msgid "Methods" msgstr "" -#: ../../en/units/flash_light.rst:59 259154f23f254f22a972521b5158b5aa +#: ../../en/units/flash_light.rst:59 c4c33376823444a4850a1ccd54e84f07 msgid "Flash the light." msgstr "" -#: ../../en/units/flash_light.rst:61 55029b8defd1480a87cc7c2952f311af +#: ../../en/units/flash_light.rst:61 888127f83ce248b489016f57dacf0db0 msgid "" "The brightness of the light. Options: - " "``FlashLightUnit.BRIGHTNESS_100``: 100% - " @@ -122,74 +122,77 @@ msgid "" "``FlashLightUnit.BRIGHTNESS_30``: 30%" msgstr "" -#: ../../en/units/flash_light.rst:61 2091a68322af473d9b2627f09ac848db +#: ../../en/units/flash_light.rst:61 45bddac61ce9446aa6c138fe0c5eaeb0 msgid "The brightness of the light." msgstr "" #: ../../en/units/flash_light.rst:63 ../../en/units/flash_light.rst:76 -#: 050c8126bf0943d6b7ec3dfb5229c800 8012ecfe7d3e4a3c9eb030ca03d45c89 +#: 1d50eacfb2fd431ab73854d97351e784 91d02e7f581e4729a65df08ec5f3689c msgid "Options:" msgstr "" -#: ../../en/units/flash_light.rst:65 0195637b732a4f88966240e8ba842404 +#: ../../en/units/flash_light.rst:65 e2df89c0ad2248e2ba51bc71e5d4f3ef msgid "``FlashLightUnit.BRIGHTNESS_100``: 100%" msgstr "" -#: ../../en/units/flash_light.rst:66 165ce2d4a17d4926a22eee8708243e0a +#: ../../en/units/flash_light.rst:66 e731d286f17e412082b9409ec1439f99 msgid "``FlashLightUnit.BRIGHTNESS_90``: 90%" msgstr "" -#: ../../en/units/flash_light.rst:67 7ebf496daf6349b781c654fba336cf76 +#: ../../en/units/flash_light.rst:67 63feaba7dda8474f8987044d893a6756 msgid "``FlashLightUnit.BRIGHTNESS_80``: 80%" msgstr "" -#: ../../en/units/flash_light.rst:68 14511533de0c4629a3e8d3168aba12da +#: ../../en/units/flash_light.rst:68 52c4459b28c2455ca7083dce4a273845 msgid "``FlashLightUnit.BRIGHTNESS_70``: 70%" msgstr "" -#: ../../en/units/flash_light.rst:69 c793c00883c94e09aa2740d741a3c046 +#: ../../en/units/flash_light.rst:69 5d2dd7858ac6411faef68e309ea16e06 msgid "``FlashLightUnit.BRIGHTNESS_60``: 60%" msgstr "" -#: ../../en/units/flash_light.rst:70 2fe06d0f80484ef2944210d02d23d96a +#: ../../en/units/flash_light.rst:70 54fa9d5f100845629290426e3ee96852 msgid "``FlashLightUnit.BRIGHTNESS_50``: 50%" msgstr "" -#: ../../en/units/flash_light.rst:71 59756cd9177a4179b97c69fb0b1a540c +#: ../../en/units/flash_light.rst:71 52d0ff60d6544f369315fa88fd3b8a42 msgid "``FlashLightUnit.BRIGHTNESS_40``: 40%" msgstr "" -#: ../../en/units/flash_light.rst:72 5f7c221fd9f04a68bcc9a88e474e684a +#: ../../en/units/flash_light.rst:72 c62d5f9d83cb41178ddc4829c5d4ea8e msgid "``FlashLightUnit.BRIGHTNESS_30``: 30%" msgstr "" -#: ../../en/units/flash_light.rst:74 15f35127564c4c4e9e10c89ac0ec345f +#: ../../en/units/flash_light.rst:74 8022cc6260e04687b7174308dd54e3e1 msgid "" "The time of the light. Options: - ``FlashLightUnit.TIME_220MS``: " "220ms - ``FlashLightUnit.TIME_1300MS``: 1300ms" msgstr "" -#: ../../en/units/flash_light.rst:74 910226df52f843bb98da4af43f1dfff1 +#: ../../en/units/flash_light.rst:74 ff64e7467e9e4116a3a2846e5eca0791 msgid "The time of the light." msgstr "" -#: ../../en/units/flash_light.rst:78 d36f16c6de9a4832bf15e25a1874ce6e +#: ../../en/units/flash_light.rst:78 887d59434f43440389636414b72d12b0 msgid "``FlashLightUnit.TIME_220MS``: 220ms" msgstr "" -#: ../../en/units/flash_light.rst:79 ee4a731834584cbdb74ca35a8b65d9fb +#: ../../en/units/flash_light.rst:79 d6f9fd9330b842969e4067aee24e7e17 msgid "``FlashLightUnit.TIME_1300MS``: 1300ms" msgstr "" -#: ../../en/units/flash_light.rst:81 a4bddeb880494660bc9238322132e090 +#: ../../en/units/flash_light.rst:81 26dcb36c45e7480d977a9402671be4b0 msgid "Turn off the light after flash." msgstr "" -#: ../../en/units/flash_light.rst:85 90e56f5f1fed41d5aecb5351043676dc +#: ../../en/units/flash_light.rst:85 c0219703b452494084f94f3f7d51da76 msgid "|flash.png|" msgstr "" -#: ../../en/refs/unit.flash_light.ref:8 654d0372416c4f3eb40cbf5d309766a5 +#: ../../en/refs/unit.flash_light.ref:8 68dbc9ccfe88455cbdec4538bcb1b8ad msgid "flash.png" msgstr "" +#~ msgid "|cores3_glass_example.m5f2|" +#~ msgstr "" + diff --git a/docs/locales/zh_CN/LC_MESSAGES/units/light.po b/docs/locales/zh_CN/LC_MESSAGES/units/light.po index ab24dc96..c8172db5 100644 --- a/docs/locales/zh_CN/LC_MESSAGES/units/light.po +++ b/docs/locales/zh_CN/LC_MESSAGES/units/light.po @@ -9,7 +9,7 @@ msgid "" msgstr "" "Project-Id-Version: UIFlow2 Programming Guide \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-01-16 15:35+0800\n" +"POT-Creation-Date: 2025-04-18 15:58+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language: zh_CN\n" @@ -20,115 +20,114 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.16.0\n" -#: ../../en/units/light.rst:2 774a3236ad8e48c180f6b0154dd5de06 +#: ../../en/units/light.rst:2 270e545afbe44a8f900b7b34ad594973 msgid "Light Unit" msgstr "" -#: ../../en/units/light.rst:6 f583d6d852dc4d60b9815df6020b47f9 +#: ../../en/units/light.rst:6 27ce67c639f74bf99b186d091652e1cb msgid "Support the following products:" msgstr "支持以下产品:" -#: ../../en/units/light.rst:8 5b414cf9151742ee86db362f9d248bf1 +#: ../../en/units/light.rst:8 607e5cceeecd4ac4b84312abdfbde25c msgid "|Light|" msgstr "" -#: ../../en/refs/unit.light.ref a5dc387a94724de6867c9b6c891c4ba7 +#: ../../en/refs/unit.light.ref f5ab442f5c8c4c5885e8c3a27b512cfa msgid "Light" msgstr "" -#: ../../en/units/light.rst:11 8fd0f3dd55514417864a3560387ab08c +#: ../../en/units/light.rst:11 8f681a609b35408199a3ecc7cb43fc1e msgid "Micropython Example:" msgstr "" -#: ../../en/units/light.rst:18 1784ef09e8ee4205bd4224df18cf235d +#: ../../en/units/light.rst:18 8e8f6834cbd24321a6a7d801a56bc4f0 msgid "UIFLOW2 Example:" msgstr "" -#: ../../en/units/light.rst:20 a41915b80d444b57bf4fb9f9e427e0db +#: ../../en/units/light.rst:20 0a3b040797e44bf6a0f900aa6d70a143 msgid "|example.png|" msgstr "" -#: ../../en/refs/unit.light.ref:7 675abee47859479d8ae7ac72a4b97763 +#: ../../en/refs/unit.light.ref:6 4f18ccfc5a4d40ca986b1548e9187fc2 msgid "example.png" msgstr "" -#: ../../en/units/light.rst:25 f2e238bca52f48babc4ede8bbbfcc4be +#: ../../en/units/light.rst:25 c4677d4928e24ae5b1bed9f2f13849da msgid "|light_core_example.m5f2|" msgstr "" -#: ../../en/units/light.rst:29 5fd7bc5b620b4c7fa3aa76533452fb7f +#: ../../en/units/light.rst:29 4dcc406c5e894400a5c54cd245cfe736 msgid "class Light" msgstr "" -#: ../../en/units/light.rst:32 7b2513bc657b479aa01303d95e0bb861 +#: ../../en/units/light.rst:32 febbb31290de45fe8f1230bbc6b999b8 msgid "Constructors" msgstr "" -#: ../../en/units/light.rst:36 5d87da9794454bbbab01a51857a2d418 +#: ../../en/units/light.rst:36 a39545fbfd574b7297eea4d115d9c8c2 msgid "Create a Light object." msgstr "创建一个Light对象." -#: ../../en/units/light.rst:39 144f50bccdaa4f7493139019c2177baa +#: ../../en/units/light.rst:39 00a8a604f1f54c7cafa3228102a37a65 msgid "The parameters are:" msgstr "参数是:" -#: ../../en/units/light.rst:39 e82438c5544244f38c7af2094e010cc7 +#: ../../en/units/light.rst:39 5cf66ebd4ce640d18649875798de4cbd msgid "``IO1,IO2`` Define digital and analog output pins." msgstr "``IO1,IO2`` 定义数字和模拟输出引脚。" #: ../../en/units/light.rst:41 ../../en/units/light.rst:53 #: ../../en/units/light.rst:62 ../../en/units/light.rst:71 -#: 38457e9273fb46619c1ef8adc2ff030a 7b6451fd8ae2469e902e4633eeabaa64 -#: 7f7fecc239ad463bb327c45a53aa2d94 bbb6cc1f40bf4d8fade3f51253b7373f +#: 12e82c7c07594d1ca3d119c2a011ff02 28effdb8c7e94b0c9aef2c4df94132a6 msgid "UIFLOW2:" msgstr "" -#: ../../en/units/light.rst:43 482e3b8e2c8047b799146d94e78486da -msgid "|init.svg|" +#: ../../en/units/light.rst:43 821b7b08c91f402cbf1c52f6772b2ad0 +msgid "|init.png|" msgstr "" -#: ../../en/refs/unit.light.ref:9 a4f04c3c92e04dd3a49e64fbd6b5e5f8 -msgid "init.svg" +#: ../../en/refs/unit.light.ref:8 67412b9c336c42afa6e5c4c320668e83 +msgid "init.png" msgstr "" -#: ../../en/units/light.rst:47 a4bec84b78e04f9eac320a1bdd510397 +#: ../../en/units/light.rst:47 88b8e9477c2645e9be5999a8780047f2 msgid "Methods" msgstr "" -#: ../../en/units/light.rst:51 608ac85523434113bb2f7065529c8d45 +#: ../../en/units/light.rst:51 3f1d5a0fa93945d4965b650084338490 msgid "Define digital and analog output pins." msgstr "获取数字量(0或者1)。" -#: ../../en/units/light.rst:55 55f8cd08650644de80f6bc0d65b58643 -msgid "|get_digital_value.svg|" +#: ../../en/units/light.rst:55 dfd5c258422b4a2b94629553370c37ce +msgid "|get_digital_value.png|" msgstr "" -#: ../../en/refs/unit.light.ref:12 7d744ff5dc364fc3b5c3cdfa945add94 -msgid "get_digital_value.svg" +#: ../../en/refs/unit.light.ref:10 c1d4b4af4dec4003afb5d858d4eda5b5 +msgid "get_digital_value.png" msgstr "" -#: ../../en/units/light.rst:60 e1b32cfd283947988ae08feaca49999b +#: ../../en/units/light.rst:60 2c0ab373b9f24920876749dd053bbae8 msgid "Gets the analog (returns 0-65535)." msgstr "获取模拟量(返回0-65535)." -#: ../../en/units/light.rst:64 e67336cd12f9411dbbb70e93d905fad9 -msgid "|get_analog_value.svg|" +#: ../../en/units/light.rst:64 efb70dafe9f14089aede50b40c994d5e +msgid "|get_analog_value.png|" msgstr "" -#: ../../en/refs/unit.light.ref:15 182ee82ed3574151ac1b84fdc189c346 -msgid "get_analog_value.svg" +#: ../../en/refs/unit.light.ref:12 c9c2fab4f5eb45f99e45b358207b14d0 +msgid "get_analog_value.png" msgstr "" -#: ../../en/units/light.rst:69 b4830d67aaa54480917d6b4cefb2b8d5 +#: ../../en/units/light.rst:69 0bb84576be0c4bffa9d242756c1de204 msgid "Gets the resistance value (returns an integer)." msgstr "获取阻值(返回整数)。" -#: ../../en/units/light.rst:73 92d5a42f3d3f4c4ca3a23c82e413a0f7 -msgid "|get_ohm.svg|" +#: ../../en/units/light.rst:73 495a6cd2cdce41a3b5236aad6b4e72d1 +msgid "|get_ohm.png|" msgstr "" -#: ../../en/refs/unit.light.ref:18 c92052a00b7d42759c8ae963ff4f9d63 -msgid "get_ohm.svg" +#: ../../en/refs/unit.light.ref:14 82206d1d0f3d416a9d2d11388eca3cfd +msgid "get_ohm.png" msgstr "" #~ msgid "example.svg" @@ -140,3 +139,27 @@ msgstr "" #~ msgid "|example.svg|" #~ msgstr "" +#~ msgid "|init.svg|" +#~ msgstr "" + +#~ msgid "init.svg" +#~ msgstr "" + +#~ msgid "|get_digital_value.svg|" +#~ msgstr "" + +#~ msgid "get_digital_value.svg" +#~ msgstr "" + +#~ msgid "|get_analog_value.svg|" +#~ msgstr "" + +#~ msgid "get_analog_value.svg" +#~ msgstr "" + +#~ msgid "|get_ohm.svg|" +#~ msgstr "" + +#~ msgid "get_ohm.svg" +#~ msgstr "" + diff --git a/docs/locales/zh_CN/LC_MESSAGES/units/lora_e220.po b/docs/locales/zh_CN/LC_MESSAGES/units/lora_e220.po index 8d590613..3a51d711 100644 --- a/docs/locales/zh_CN/LC_MESSAGES/units/lora_e220.po +++ b/docs/locales/zh_CN/LC_MESSAGES/units/lora_e220.po @@ -9,7 +9,7 @@ msgid "" msgstr "" "Project-Id-Version: UIFlow2 Programming Guide \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-03-11 18:23+0800\n" +"POT-Creation-Date: 2025-04-18 15:58+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language: zh_CN\n" @@ -18,437 +18,502 @@ msgstr "" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" -"Generated-By: Babel 2.14.0\n" +"Generated-By: Babel 2.16.0\n" -#: ../../en/units/lora_e220.rst:2 a87e349fd5d04067adacab6b3271f53a +#: ../../en/units/lora_e220.rst:2 d7692268451c4e14a34863f1ad827ef7 msgid "LoRaE220 Unit" msgstr "" -#: ../../en/units/lora_e220.rst:6 4c86730b18cb4a2b80da5bfce9187d68 +#: ../../en/units/lora_e220.rst:6 3f28915162384b4b9abd1e27b445d6ef msgid "Support the following products:" msgstr "支持以下产品:" -#: ../../en/units/lora_e220.rst:8 d6caaad28b734aceafecdb0c6004aa01 +#: ../../en/units/lora_e220.rst:8 61299eb3d64346c7a964d86fec9e2edb msgid "|LoRaE220|" msgstr "" -#: ../../en/refs/unit.lora_e220.ref 3b2a546162324bca919638f639f15fd7 +#: ../../en/refs/unit.lora_e220.ref 12c0a222746b4985814d55596d24d207 msgid "LoRaE220" msgstr "" -#: ../../en/units/lora_e220.rst:11 8cbf150e47c34d49b351c544f17889a7 +#: ../../en/units/lora_e220.rst:11 d6b67007f1df4eb4a22738a7297862c1 msgid "Micropython TX Example::" msgstr "" -#: ../../en/units/lora_e220.rst:26 f838e3f3e37d45c5ad6a68f63c8df1bd +#: ../../en/units/lora_e220.rst:26 f3265ab91dc44a9c8d9b77daf98d9841 msgid "Micropython RX Example::" msgstr "" -#: ../../en/units/lora_e220.rst:38 6c622b4793b443248303423d3bded235 +#: ../../en/units/lora_e220.rst:38 5b30bb217cb640bbb698b8aa75063b7d msgid "UIFLOW2 TX Example:" msgstr "" -#: ../../en/units/lora_e220.rst:40 bc8dcfab4a3e47ad93f38630aee67ace -msgid "|tx_example.svg|" +#: ../../en/units/lora_e220.rst:40 09e0261f9cd64915a2050ee8310d45b5 +msgid "|tx_example.png|" msgstr "" -#: ../../en/refs/unit.lora_e220.ref:6 323d841860f94106bf1ec1d3dc820d3f -msgid "tx_example.svg" +#: ../../en/refs/unit.lora_e220.ref:6 bd4db5e0c541422e803bc2194d56a604 +msgid "tx_example.png" msgstr "" -#: ../../en/units/lora_e220.rst:43 9cf66553c02a441d9a0ca05a953bdfc5 +#: ../../en/units/lora_e220.rst:43 94f4fd16633a4f7aa6c7654ef0c1b3e6 msgid "UIFLOW2 RX Example:" msgstr "" -#: ../../en/units/lora_e220.rst:45 36225b8b1fad43cfb7000326be590226 -msgid "|rx_example.svg|" +#: ../../en/units/lora_e220.rst:45 fcc5c656d8714f30a383d7dae7a05b72 +msgid "|rx_example.png|" msgstr "" -#: ../../en/refs/unit.lora_e220.ref:8 cd624f397e8f402b9b4393c5e70bf423 -msgid "rx_example.svg" +#: ../../en/refs/unit.lora_e220.ref:8 b0deefbe5b9849beb59fb06ad65d006b +msgid "rx_example.png" msgstr "" -#: ../../en/units/lora_e220.rst:50 b1ffd4e6fc1d4f4e9b8fe856ab21ca4e +#: ../../en/units/lora_e220.rst:50 a9dc19c349ac41faa9b8bdeddfbeb7a6 msgid "|lora_e220_tx_core2.m5f2|" msgstr "" -#: ../../en/units/lora_e220.rst:52 9be9dc53ecb3494db5b42dce248d61f9 +#: ../../en/units/lora_e220.rst:52 4b110c1c2a634f6384711d03fd97d8e7 msgid "|lora_e220_rx_dial.m5f2|" msgstr "" -#: ../../en/units/lora_e220.rst:56 fccb396eb8ce41a798eb8525a06c646c +#: ../../en/units/lora_e220.rst:56 65d0346e33f847aaa86a9dc3bab92b22 msgid "Working Mode" msgstr "" -#: ../../en/units/lora_e220.rst:58 7f51f641e967453292ab2ae55bf8adde +#: ../../en/units/lora_e220.rst:58 d2e7aed3ab8d46cba9755892f1dc33ca msgid "Working mode table" msgstr "工作模式" -#: ../../en/units/lora_e220.rst:62 2807d722171e4ebbbba28d0cfcb1ccc0 +#: ../../en/units/lora_e220.rst:63 f7b1288130ca4bf9aa6974feb98ea8bf msgid "Mode (0-3)" msgstr "" -#: ../../en/units/lora_e220.rst:62 aeb070401f0a40c2944bc12c4fa51ff1 +#: ../../en/units/lora_e220.rst:63 917901d210e442239e9c431c0fc7cec9 msgid "M1" msgstr "" -#: ../../en/units/lora_e220.rst:62 85a43dd7e98e440cbc555126bbf900d9 +#: ../../en/units/lora_e220.rst:63 bf79aea6c12848efa30b8cf7a1150a29 msgid "M0" msgstr "" -#: ../../en/units/lora_e220.rst:62 f74cf531b5234da0a5815e230b84425f +#: ../../en/units/lora_e220.rst:63 e2f0b184d09146358b7fbf36a5a84331 msgid "Function description" msgstr "功能描述" -#: ../../en/units/lora_e220.rst:64 39d23ade5620443098fada71f1d0e5a7 +#: ../../en/units/lora_e220.rst:65 6cfa3c58722c43d2935469b41a80bde3 msgid "0:Transmission Mode" msgstr "" -#: ../../en/units/lora_e220.rst:64 ../../en/units/lora_e220.rst:68 -#: ../../en/units/lora_e220.rst:74 553e0cf7c4144d6181183ac7421ba12c -#: 5af07708e54e499c930d4516f319f848 867e6ba4b5bb416e872441a55c4340f0 -#: 905e6639c0924937aad1bb88c475e57a +#: ../../en/units/lora_e220.rst:65 ../../en/units/lora_e220.rst:69 +#: ../../en/units/lora_e220.rst:75 393ff25855a04ae38120d1924560b937 +#: b8086330709740d9b645be1f34f22412 deb692e98bf84b7d92f902d36281aa25 +#: ffdc594921dd4f3c9f16a0497803d955 msgid "0" msgstr "" -#: ../../en/units/lora_e220.rst:64 4ba95a5a0be44d1e9c9335fdfe55c6ed +#: ../../en/units/lora_e220.rst:65 9fe02a2273ba4e58828032dfdb03eb3b msgid "" "SEND: Users can enter data through the serial port, and the module will " "start wireless transmission." msgstr "SEND: 用户可以通过串口输入数据,模块将开始无线传输。" -#: ../../en/units/lora_e220.rst:66 25e50a32562746889e3807fa5668a3fb +#: ../../en/units/lora_e220.rst:67 fca11a595aec434db6ce72a289cd03e1 msgid "" "RECEIVE: The wireless receiving function of the module is enabled, and " "the wireless data will be output through the TXD pin of the serial port " "after receiving it." msgstr "RECEIVE: 模块的无线接收功能打开, 无线数据接收后将通过串口的TXD引脚输出。" -#: ../../en/units/lora_e220.rst:68 88eda382228c4ecda785c98aebc024c3 +#: ../../en/units/lora_e220.rst:69 23fc28a277474a82b61b6474a2175e5c msgid "1:WOR Sending Mode" msgstr "" -#: ../../en/units/lora_e220.rst:68 ../../en/units/lora_e220.rst:74 -#: ../../en/units/lora_e220.rst:80 8200fefd3a60448dacc28840f1ced780 -#: 8cab0c82765d46038f30d3fdfda0ad44 94a94f7e328643109625eb0ac38df6ed -#: fc1eaa8be2504610820e6089b3014a1b +#: ../../en/units/lora_e220.rst:69 ../../en/units/lora_e220.rst:75 +#: ../../en/units/lora_e220.rst:81 2b5b24adb1de48b58fbd448d9fdd01e2 +#: 58baf16d0aaa47e0b67c2411c829fff5 70cf02842fdb4b27851417b20bab53ba +#: d0b389eb82c74387a7eba82e2175d4d6 msgid "1" msgstr "" -#: ../../en/units/lora_e220.rst:68 16e33eb4a37f4e3b9d81686ef267d57c +#: ../../en/units/lora_e220.rst:69 19a51528d7d2429a9e9004a09846bf16 msgid "SEND: Wirelessly sending data on" msgstr "SEND: 无线发送数据打开。" -#: ../../en/units/lora_e220.rst:70 ../../en/units/lora_e220.rst:76 -#: 241ae7deba6d44b888bba93611ae9bff 56059da8cbd24c73aed41d39df1dc7b2 +#: ../../en/units/lora_e220.rst:71 ../../en/units/lora_e220.rst:77 +#: 5d485cae8e2b4af687552541a54c1de0 9da354fb59e34fce8a0024f7ed68b789 msgid "RECEIVE: Wireless receiving data on" msgstr "RECEIVE: 无线接收数据打开。" -#: ../../en/units/lora_e220.rst:72 ../../en/units/lora_e220.rst:78 -#: 0d946ff8f22b4d81a7482fc50d036989 2fa53c359faa40599f9b413ac98421dd +#: ../../en/units/lora_e220.rst:73 ../../en/units/lora_e220.rst:79 +#: 400b60294f3a49c2aacc32fdd681dd95 8650923272cd48a69b593e21c7e9b14e msgid "NOTE: Support Air Wake Up" msgstr "NOTE: 支持空中唤醒。" -#: ../../en/units/lora_e220.rst:74 5b79da1ec2814b1a9b7d7558987b9569 +#: ../../en/units/lora_e220.rst:75 a26622fb3a8249c3896424d88f80b60e msgid "2:WOR Receiving Mode" msgstr "" -#: ../../en/units/lora_e220.rst:74 ../../en/units/lora_e220.rst:80 -#: 4e03e971eb5a4d018e1b017203fafd63 be71bc3219f24cbba1f0d0a959189988 +#: ../../en/units/lora_e220.rst:75 ../../en/units/lora_e220.rst:81 +#: b8194fcd57c5452d93a60bc6b7ba2e1d c0507d7cec1a4f03a3bcb5dfdcea6b7c msgid "SEND: Wirelessly sending data off" msgstr "SEND: 无线发送数据关闭。" -#: ../../en/units/lora_e220.rst:80 2134e38ee2ef45f5bf8a2bee1edac0c8 +#: ../../en/units/lora_e220.rst:81 65144de5012040019456d2a3aaa7b0f4 msgid "3:Configuration Mode" msgstr "" -#: ../../en/units/lora_e220.rst:82 993f2f30c05c4ea0be40f7cbe2af31b5 +#: ../../en/units/lora_e220.rst:83 cd0d33562c2349ddb96b539b962d2226 msgid "RECEIVE: Wireless receiving data off" msgstr "RECEIVE: 无线接收数据打开。" -#: ../../en/units/lora_e220.rst:84 ba2c8847716641ecb302d9a62a64bf2e +#: ../../en/units/lora_e220.rst:85 591212743b3040acab2cc4ba6341c5bb msgid "CONFIGURATION: Users can access registers to configure module status" msgstr "CONFIGURATION: 用户可以访问寄存器来配置模块状态。" -#: ../../en/units/lora_e220.rst:87 7c6d875cd0514710a32839fbf08fe3dc +#: ../../en/units/lora_e220.rst:88 8cadecb0c1a04031ba9a3285a74b6ec6 msgid "|working mode.jpg|" msgstr "" -#: ../../en/refs/unit.lora_e220.ref:12 838f6270216a4effa3a8dac03f4507d5 +#: ../../en/refs/unit.lora_e220.ref:12 a14524ee74364a018b96faa2e4c97979 msgid "working mode.jpg" msgstr "" -#: ../../en/units/lora_e220.rst:91 82b9bf7bcb5548d6acc612cd2353083d +#: ../../en/units/lora_e220.rst:92 8a67c0d2a25c4c5dbda10250cea6bf52 msgid "class LoRaE220JPUnit" msgstr "" -#: ../../en/units/lora_e220.rst:94 10ab282692084194bda00b611c65a769 +#: ../../en/units/lora_e220.rst:95 240f346fde3747cca70bd2aa529e52d7 msgid "Constructors" msgstr "" -#: ../../en/units/lora_e220.rst:98 801fbeff7dee44bc9ee9b142e4fe87e8 +#: ../../en/units/lora_e220.rst:99 c1e40fd0220147e0bc6017f4f64063ff msgid "Create a LoRaE220JPUnit object." msgstr "创建一个 LoRaE220JPUnit 对象." -#: ../../en/units/lora_e220.rst:102 ../../en/units/lora_e220.rst:129 -#: ../../en/units/lora_e220.rst:222 98eb40c1aafb47cb9bed1a291ac22243 -#: bcd46e820da84a39ae167363dcab81cd db7b6651abb441fca6130f2c7f335f86 +#: ../../en/units/lora_e220.rst:103 ../../en/units/lora_e220.rst:130 +#: ../../en/units/lora_e220.rst:223 26771fa24b1440868d2c2daf415cddca +#: 70f3afea113a4f6f8aab72686317f97e d979a1029bc34fdfb5bd2e0e28e3dabd msgid "The parameters is:" msgstr "参数为:" -#: ../../en/units/lora_e220.rst:101 ad3f12c0840b4e158b25a37d10d08e57 +#: ../../en/units/lora_e220.rst:102 62f105655750453cbb996e00df0e4aa0 msgid "``port`` uart pin tuple, which contains: ``(tx_pin, rx_pin)``." msgstr "``port`` uart的引脚元组,其中包含: ``(tx_pin, rx_pin)``。" -#: ../../en/units/lora_e220.rst:102 593d4993adf34297ad20af7345c3f7cb +#: ../../en/units/lora_e220.rst:103 867919bcad7d4966a8282a46a2cf48ce msgid "``port_id`` uart port ID." msgstr "``port_id`` uart端口ID。" -#: ../../en/units/lora_e220.rst:104 ../../en/units/lora_e220.rst:131 -#: ../../en/units/lora_e220.rst:150 ../../en/units/lora_e220.rst:169 -#: ../../en/units/lora_e220.rst:185 ../../en/units/lora_e220.rst:200 -#: ../../en/units/lora_e220.rst:211 ../../en/units/lora_e220.rst:228 -#: 391ea6b269dd4f14b0eab35ab7501a1f 3be12b49b68c4904ac3a8d868e3b7667 -#: 56e26d85b62d4e409e6ba98dc81a7d4b 985b898008094b6fb174a2958d67e4c6 -#: 9a51f587160b4c85a9b7834587be3601 9c3bc63b3e064489b39d19384290932e -#: ce06d6acc1e54231a7c7da0c42e7234f d442e37bfa584486b05304659bc5ffdd +#: ../../en/units/lora_e220.rst:105 ../../en/units/lora_e220.rst:132 +#: ../../en/units/lora_e220.rst:151 ../../en/units/lora_e220.rst:170 +#: ../../en/units/lora_e220.rst:186 ../../en/units/lora_e220.rst:201 +#: ../../en/units/lora_e220.rst:212 ../../en/units/lora_e220.rst:229 +#: 09dea6dcef264da9a12676a10c88210c 0fe367bb667048628d4b7d789653bf2a +#: 3405acabe37a4db9bc1e0d483199db49 567cf7335c2b4b498c8a9bf461067c80 +#: 7c3dcb3b4d4f4ee9982ec16adbef8d78 80dfc09f52ee492fb3e3d70f8edeb561 +#: aa8a906842be416d9cfa237980a604b1 d0d87676e66e477bafa28933ed727fe1 msgid "UIFLOW2:" msgstr "" -#: ../../en/units/lora_e220.rst:106 c91c7b9068c744e6ba80e649f97b63b0 -msgid "|init.svg|" +#: ../../en/units/lora_e220.rst:107 e53180b6a53f4c4c8545bc2003462813 +msgid "|init.png|" msgstr "" -#: ../../en/refs/unit.lora_e220.ref:14 00b4bef0f77347b3b38e27c3e1b866d6 -msgid "init.svg" +#: ../../en/refs/unit.lora_e220.ref:14 6a10ec3230db4acc97e6ba54001b6512 +msgid "init.png" msgstr "" -#: ../../en/units/lora_e220.rst:110 2da6718d9d3a471bb78ce1f97afa2a5f +#: ../../en/units/lora_e220.rst:111 b49cc1ebf5ae4b67b5247f3eaace9dc5 msgid "Methods" msgstr "" -#: ../../en/units/lora_e220.rst:114 8b96cc0c4d0a4745a5d3ce70b512818b +#: ../../en/units/lora_e220.rst:115 40224c5b32b64e868ca45ec9b425cdef msgid "" "Available when LoRaE220JPUnit working mode is 3. Please refer to " ":ref:`working mode table ` for the working mode." msgstr "当 LoRaE220JPUnit 的工作模式为 3 时可用。工作模式请参考 :ref:`工作模式表 ` 。" -#: ../../en/units/lora_e220.rst:116 6c8c0a71bcc74438a5362da332c0f110 +#: ../../en/units/lora_e220.rst:117 060ca803b89c42778274cb56d4b15f1d msgid "Set module parameters." msgstr "设置模块的参数。" -#: ../../en/units/lora_e220.rst:119 ba409719b3fb4fada93ac888b785a929 +#: ../../en/units/lora_e220.rst:120 c031b90f690549d393577a2a7347d811 msgid "``own_address``: Local address." msgstr "``own_address``: 本机地址。" -#: ../../en/units/lora_e220.rst:120 73e6722da98c436cb69f6bf1dc5a79bc +#: ../../en/units/lora_e220.rst:121 4e73b6770dfc427bbb7a9b67239ea893 msgid "``own_channel``: Native channel." msgstr "``own_channel``: 本机通道。" -#: ../../en/units/lora_e220.rst:121 b3b79500c5a844aab9ebfbca0641dfd4 +#: ../../en/units/lora_e220.rst:122 62b8285452e24747a83f9717968fd97c msgid "``encryption_key``: Encryption key." msgstr "``encryption_key``: 加密密钥。" -#: ../../en/units/lora_e220.rst:122 af6a180bf50241be82e0dc2adfd9ff9a +#: ../../en/units/lora_e220.rst:123 ec8f1de15317408c9eaba59419ceba64 msgid "``air_data_rate``: rate." msgstr "``air_data_rate``: 速率。" -#: ../../en/units/lora_e220.rst:123 ba06e33bdfb94c1e903a84b765629b7d +#: ../../en/units/lora_e220.rst:124 fb4ac8680c084f92a8f877d1b24b2bcb msgid "``subpacket_size``: Maximum packet length." msgstr "``subpacket_size``: 数据包最大长度。" -#: ../../en/units/lora_e220.rst:124 c18d83ff90b6401ea8aa0b1a788e8add +#: ../../en/units/lora_e220.rst:125 397bcd5bca434b47961a5b9ef0240281 msgid "``rssi_ambient_noise_flag``: RSSI Ambient Noise." msgstr "``rssi_ambient_noise_flag``: RSSI。" -#: ../../en/units/lora_e220.rst:125 b4147281ad5f4011943c8d3e14d89b4d +#: ../../en/units/lora_e220.rst:126 3508224e1e8f42969687138e6e77367a msgid "``transmitting_power``: Transmit power." msgstr "``transmitting_power``: 发射功率。" -#: ../../en/units/lora_e220.rst:126 b4c38143097147d687fb91adc6a307b1 +#: ../../en/units/lora_e220.rst:127 b4d23cbc412547b08881daa58cabe99d msgid "``rssi_byte_flag``: Output RSSI strength bytes." msgstr "``rssi_byte_flag``: RSSI。" -#: ../../en/units/lora_e220.rst:127 8b397480eef34c28b159e1a7f2649375 +#: ../../en/units/lora_e220.rst:128 5d9e73d993204dfea6cb2778d851b4a8 msgid "``transmission_method_type``: transmission mode." msgstr "``transmission_method_type``: 传输模式。" -#: ../../en/units/lora_e220.rst:128 a57dcef19aea40eeb1e06bdbec143de6 +#: ../../en/units/lora_e220.rst:129 cffbb758e4a848f4bc6f0b712b8bdc6a msgid "``lbt_flag``: Parameter no longer used." msgstr "``lbt_flag``: LBT Flag。" -#: ../../en/units/lora_e220.rst:129 8d56ab17673248ba8a0e2dd54de82b2b +#: ../../en/units/lora_e220.rst:130 08a45706ee6346b2bd7912d5634b944e msgid "``wor_cycle``: Wireless wake-up time." msgstr "``wor_cycle``: WOR。" -#: ../../en/units/lora_e220.rst:133 745d8c38a8504d4d87a0995dd3dd6e03 -msgid "|setup.svg|" +#: ../../en/units/lora_e220.rst:134 6791dffc18f54a598537d54eb24c1e38 +msgid "|setup.png|" msgstr "" -#: ../../en/refs/unit.lora_e220.ref:16 b69cf440cd84403b89b719bdc5bf0c7d -msgid "setup.svg" +#: ../../en/refs/unit.lora_e220.ref:16 97683cc274c54891b688b2a7792087c8 +msgid "setup.png" msgstr "" -#: ../../en/units/lora_e220.rst:140 ../../en/units/lora_e220.rst:179 -#: 25a8e19cd8f74b76ac7b5b11d256bac8 639a743f77da4434a4754065716ed2a0 +#: ../../en/units/lora_e220.rst:141 ../../en/units/lora_e220.rst:180 +#: 3b4874db6a754cb7b7df7e3be5c76c5c 959aaa4ecc914fab892d9e0dae7f3500 msgid "This method is deprecated and will be removed in version 2.0.2." msgstr "这个方法已被弃用,并将在 2.0.2 版本中移除。" -#: ../../en/units/lora_e220.rst:142 ../../en/units/lora_e220.rst:161 -#: ../../en/units/lora_e220.rst:181 ../../en/units/lora_e220.rst:196 -#: ../../en/units/lora_e220.rst:207 36780f11773f4b7ea90e2625751c91b1 -#: 495cd5fcc9fb434682af8069bd90bf0a 83006b6d79f94b83a40627c45a081337 -#: 8328f7bb0b83405483efbc730ec72032 f1d38202ee6f4c1ea4191f810c3c3aa3 +#: ../../en/units/lora_e220.rst:143 ../../en/units/lora_e220.rst:162 +#: ../../en/units/lora_e220.rst:182 ../../en/units/lora_e220.rst:197 +#: ../../en/units/lora_e220.rst:208 44bc8d9286754bba9b75b341b354d75f +#: 4a742e449cfc4b82866baed9a40bd5ce 76c9a42d01dc42ea8873b77205301bc1 +#: b7aaebbebaa74fe2b0bbf24439c5a9fe da41860564ce4a2b8408bb0f90e5db57 msgid "" "Available when the working mode of LoRaE220JPUnit is 0 / 1 / 2. Please " "refer to :ref:`working mode table ` for the working mode." -msgstr "当 LoRaE220JPUnit 的工作模式为 0 / 1 / 2 时可用。工作模式请参考 :ref:`工作模式表 ` 。" +msgstr "" +"当 LoRaE220JPUnit 的工作模式为 0 / 1 / 2 时可用。工作模式请参考 :ref:`工作模式表 `" +" 。" -#: ../../en/units/lora_e220.rst:144 ../../en/units/lora_e220.rst:163 -#: b0ced03d59fb43ce85c5aebf0a4eceb2 f898b3bb41c8471fb82cc4ffd62dcd4d +#: ../../en/units/lora_e220.rst:145 ../../en/units/lora_e220.rst:164 +#: 0cb115e7d7de4f60a9b1ebbf7bafd421 93f6b83b9eb34fd6b1a5554c215cf2fa msgid "" "Use non-blocking mode to receive data. ``receive_callback`` The callback " "function passed in will be called when data is received." msgstr "使用非阻塞模式接收数据。 ``receive_callback`` 传入的回调函数,当接收到数据时会被调用。" -#: ../../en/units/lora_e220.rst:146 ../../en/units/lora_e220.rst:165 -#: 62707d266c87467dac7fe228c5c6c2e4 c1d5c77b9f214b86961b2b4f10cc784a +#: ../../en/units/lora_e220.rst:147 ../../en/units/lora_e220.rst:166 +#: 28beb2fd89d6417aaa617e9d768efd7d 3226fb0909af41e8840f27de4c6cc0b2 msgid "The format of receive_callback is::" msgstr "receive_callback 的格式为::" -#: ../../en/units/lora_e220.rst:152 20142a623f43485c808acff1c924aa8d -msgid "|receiveNoneBlock.svg|" +#: ../../en/units/lora_e220.rst:153 cdcd75fd4b954784ab8e7f14e9c3c0ce +msgid "|receiveNoneBlock.png|" msgstr "" -#: ../../en/refs/unit.lora_e220.ref:18 650a091dc0824090a5bff2a7f7833b16 -#: a90add93d9f545d1a53afd4069006a57 -msgid "receiveNoneBlock.svg" +#: ../../en/refs/unit.lora_e220.ref:18 8aa4250c588d4adc98fd2c9a117d933d +#: d8a321c15296407d851002c8b34250f2 +msgid "receiveNoneBlock.png" msgstr "" -#: ../../en/units/lora_e220.rst:159 ../../en/units/lora_e220.rst:194 -#: 86a2412737584e11ab6fe1f755396139 e8d13c14be5c41d0952bd6ee1d4d4bda +#: ../../en/units/lora_e220.rst:160 ../../en/units/lora_e220.rst:195 +#: 5ff971354ff24fd5bbb45c5407958e29 ce74f8206eef4b78b6732ae3622b57a9 msgid "This method will be added in version 2.0.2." msgstr "这个方法将在 2.0.2 版本中添加。" -#: ../../en/units/lora_e220.rst:171 457cf3171c12447e82177dae385aa0b2 -msgid "|receiveNoneBlock.svg| |receive_callback1.svg| |receive_callback2.svg|" +#: ../../en/units/lora_e220.rst:172 c9d84c2f28214935acb583b217fc2390 +msgid "|receiveNoneBlock.png| |receive_callback1.png| |receive_callback2.png|" msgstr "" -#: ../../en/refs/unit.lora_e220.ref:20 5599024be8db4574970f218ada721afe -msgid "receive_callback1.svg" +#: ../../en/refs/unit.lora_e220.ref:20 a2a2c1eb9a4648758a9ec4d591310342 +msgid "receive_callback1.png" msgstr "" -#: ../../en/refs/unit.lora_e220.ref:22 c6b0b5ee191c4390bf4f1ab0e45fae82 -msgid "receive_callback2.svg" +#: ../../en/refs/unit.lora_e220.ref:22 3f7d43fd0d2c49f7b3af96be932151de +msgid "receive_callback2.png" msgstr "" -#: ../../en/units/lora_e220.rst:183 ../../en/units/lora_e220.rst:198 -#: 5a787e5debc143e782c458ace7cea23a 8a01cfbe72a34e31ad4dbb23f486344c +#: ../../en/units/lora_e220.rst:184 ../../en/units/lora_e220.rst:199 +#: 8a4cf14f624e43809bfafa415f916a76 8ced056e33ba471ba7cc0c86daa10330 msgid "Stop receiving data in non-blocking mode." msgstr "停止非阻塞模式接收数据。" -#: ../../en/units/lora_e220.rst:187 ../../en/units/lora_e220.rst:202 -#: 0dcba1543a1f44ad9f3223f556913bc0 7938b32d2eec496d97e5100e279613dd -msgid "|stopReceive.svg|" +#: ../../en/units/lora_e220.rst:188 ../../en/units/lora_e220.rst:203 +#: 0616bef3973247ee8f2fa74f11243a94 2cdaf078fe804a96870cbcc7f85df60e +msgid "|stopReceive.png|" msgstr "" -#: ../../en/refs/unit.lora_e220.ref:24 800928c644854ebab754c0cc7ff040ca -#: ce91be4b336c4754a9c6c8f6bb4768a2 -msgid "stopReceive.svg" +#: ../../en/refs/unit.lora_e220.ref:24 5c2dfaa1a31a49d1a80cfd19d3a9aa7f +#: b9b241edfe984daab857c4d7d8f890d1 +msgid "stopReceive.png" msgstr "" -#: ../../en/units/lora_e220.rst:209 008138468fb8431b81ed1fd94e4c752a +#: ../../en/units/lora_e220.rst:210 adf4a329feb047ac890d2055aebe8129 msgid "" "Use blocking method to receive data. ``timeout`` is used to set the " "reception timeout, the unit is ms." msgstr "使用阻塞方法接收数据。' ' timeout ' '用于设置“接待超时,单位是Ms。”" -#: ../../en/units/lora_e220.rst:213 d02219019ab54e5e98cf27bd77e8b347 -msgid "|receive.svg|" +#: ../../en/units/lora_e220.rst:214 1bcf1bdc15424e048ff22b77288ad16f +msgid "|receive.png|" msgstr "" -#: ../../en/refs/unit.lora_e220.ref:26 a93146551ad84ee8b1b3729f1cad2141 -msgid "receive.svg" +#: ../../en/refs/unit.lora_e220.ref:26 9081b3bc17e54096a3fc73f41c078ce1 +msgid "receive.png" msgstr "" -#: ../../en/units/lora_e220.rst:218 fe3560ac19404b499632905787b98434 +#: ../../en/units/lora_e220.rst:219 c167ed511d694c5089bc7d2bc44b1b8c msgid "" "Available when the working mode of LoRaE220JPUnit is 0 or 1. Please refer" " to :ref:`working mode table ` for the working mode." msgstr "当 LoRaE220JPUnit 的工作模式为 0 或者 1 时可用。工作模式请参考 :ref:`工作模式表 ` 。" -#: ../../en/units/lora_e220.rst:220 d9e8196cc6004aee96356d1aa5fce973 +#: ../../en/units/lora_e220.rst:221 1aaee33eb96c454f864f13ab1f1df444 msgid "Send data to the specified destination address and channel." msgstr "目标地址和信道发送数据。" -#: ../../en/units/lora_e220.rst:224 ee9da0951a6e4a03ad434eac88c3a3b4 +#: ../../en/units/lora_e220.rst:225 1746089845d54cd69e883e7a5ffec7d3 msgid "" "``target_address`` Target address, the address range is 0x0000 - 0xFFFF, " "where 0xFFFF is the broadcast address." msgstr "``target_address`` 目标地址,地址范围是 0x0000 - 0xFFFF ,其中 0xFFFF 是广播地址。" -#: ../../en/units/lora_e220.rst:225 1a2a8c8d3d354f4da87d9cf3525875a4 +#: ../../en/units/lora_e220.rst:226 08cb73c5065d430fbf81252fe413d756 msgid "``target_channel`` Target channel, valid channel range is 0 - 30." msgstr "``target_channel`` 目标信道,有效的信道范围是 0 - 30。" -#: ../../en/units/lora_e220.rst:226 6335ada9c735433ba4baf5b7f9b263ac +#: ../../en/units/lora_e220.rst:227 38f7e0b875bb4eaf888d941e361df3fa msgid "``send_data`` The data needs to be sent." msgstr "``send_data`` 需要发送的数据。" -#: ../../en/units/lora_e220.rst:230 eb584fcf2b5d432b87b06b3ff95f4af4 -msgid "|send1.svg| |send2.svg| |send3.svg|" +#: ../../en/units/lora_e220.rst:231 1e6fbda182a4482795736d0fe52a28aa +msgid "|send1.png| |send2.png| |send3.png|" msgstr "" -#: ../../en/refs/unit.lora_e220.ref:28 6c8bee49dbaf467497d011381ec5ed1c -msgid "send1.svg" +#: ../../en/refs/unit.lora_e220.ref:28 f6df746508c74ab29611ce74238ef84e +msgid "send1.png" msgstr "" -#: ../../en/refs/unit.lora_e220.ref:30 044f24b879ab4c239a3e126e630942e7 -msgid "send2.svg" +#: ../../en/refs/unit.lora_e220.ref:30 fa2187d4213f43279a8ee4440d4e91cd +msgid "send2.png" msgstr "" -#: ../../en/refs/unit.lora_e220.ref:32 827e6b78d94c49319c5ba3b458434ebe -msgid "send3.svg" +#: ../../en/refs/unit.lora_e220.ref:32 de510e3bd53a412a84c332705d57b936 +msgid "send3.png" msgstr "" -#: ../../en/units/lora_e220.rst:236 fbffc4b050934e47afbd3ff3b67e0ef9 +#: ../../en/units/lora_e220.rst:237 7b2dda7c64d746ccaba60e8570fcaa3c msgid "Constants" msgstr "" -#: ../../en/units/lora_e220.rst:248 485a9598c5334683a1cf15e4fafe0515 +#: ../../en/units/lora_e220.rst:249 7ab4d8860936431e8dcecb305fa3bd32 msgid "baud rate." msgstr "波特率" -#: ../../en/units/lora_e220.rst:270 f2cde4d1922945cf835c7d2e89efc4f4 +#: ../../en/units/lora_e220.rst:271 418437a86fd44ca297a00eadbad46171 msgid "rate." msgstr "速率" -#: ../../en/units/lora_e220.rst:278 874bad24034844ac831391df73ed14f8 +#: ../../en/units/lora_e220.rst:279 ae90638902e9407e82a7ead38f5a084c msgid "Maximum packet length." msgstr "数据包最大长度" -#: ../../en/units/lora_e220.rst:284 c01a44901e4c4ac48c19a4bafc2e37ce +#: ../../en/units/lora_e220.rst:285 7d183df56d444980af97e63c4b509c77 msgid "RSSI ambient noise." msgstr "RSSI 环境噪声" -#: ../../en/units/lora_e220.rst:292 7a8fa30f648e4c219f2997c1ce6585f5 +#: ../../en/units/lora_e220.rst:293 0ce12e4da6d24e4a87d39eaeaf01123a msgid "Transmit power." msgstr "发射功率" -#: ../../en/units/lora_e220.rst:298 58765d5c571e417894498dac85357719 +#: ../../en/units/lora_e220.rst:299 c067a87320d642afbc1f8431852c15ac msgid "" "RSSI bytes. When enabled, the module will append a byte of RSSI value " "after the data each time it receives data." msgstr "RSSI 字节。使能后,模块会在每次接收到数据后,会在数据后面追加一个字节的 RSSI 值。" -#: ../../en/units/lora_e220.rst:304 61c294a0451345f7b9e5b548826ae855 +#: ../../en/units/lora_e220.rst:305 9b77d07129b14971b5f498840d2ea764 msgid "transmission mode." msgstr "传输模式" -#: ../../en/units/lora_e220.rst:314 381d7b921b7541f7b7cc7623441b4600 +#: ../../en/units/lora_e220.rst:315 72d7cbfc0df2486f86671b496424128b msgid "Wireless wake-up time." msgstr "无线唤醒时间" #~ msgid "lora_e220_core_example.m5f2" #~ msgstr "" +#~ msgid "|tx_example.svg|" +#~ msgstr "" + +#~ msgid "tx_example.svg" +#~ msgstr "" + +#~ msgid "|rx_example.svg|" +#~ msgstr "" + +#~ msgid "rx_example.svg" +#~ msgstr "" + +#~ msgid "|init.svg|" +#~ msgstr "" + +#~ msgid "init.svg" +#~ msgstr "" + +#~ msgid "|setup.svg|" +#~ msgstr "" + +#~ msgid "setup.svg" +#~ msgstr "" + +#~ msgid "|receiveNoneBlock.svg|" +#~ msgstr "" + +#~ msgid "receiveNoneBlock.svg" +#~ msgstr "" + +#~ msgid "|receiveNoneBlock.svg| |receive_callback1.svg| |receive_callback2.svg|" +#~ msgstr "" + +#~ msgid "receive_callback1.svg" +#~ msgstr "" + +#~ msgid "receive_callback2.svg" +#~ msgstr "" + +#~ msgid "|stopReceive.svg|" +#~ msgstr "" + +#~ msgid "stopReceive.svg" +#~ msgstr "" + +#~ msgid "|receive.svg|" +#~ msgstr "" + +#~ msgid "receive.svg" +#~ msgstr "" + +#~ msgid "|send1.svg| |send2.svg| |send3.svg|" +#~ msgstr "" + +#~ msgid "send1.svg" +#~ msgstr "" + +#~ msgid "send2.svg" +#~ msgstr "" + +#~ msgid "send3.svg" +#~ msgstr "" + diff --git a/docs/locales/zh_CN/LC_MESSAGES/units/lorawan_rui3.po b/docs/locales/zh_CN/LC_MESSAGES/units/lorawan_rui3.po index 5e6cc9d3..f148c9b7 100644 --- a/docs/locales/zh_CN/LC_MESSAGES/units/lorawan_rui3.po +++ b/docs/locales/zh_CN/LC_MESSAGES/units/lorawan_rui3.po @@ -9,7 +9,7 @@ msgid "" msgstr "" "Project-Id-Version: UIFlow2 Programming Guide \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-01-17 14:57+0800\n" +"POT-Creation-Date: 2025-04-03 10:58+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language: zh_CN\n" @@ -20,11 +20,11 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.16.0\n" -#: ../../en/units/lorawan_rui3.rst:3 0add85957957454e93751c3283a98c4e +#: ../../en/units/lorawan_rui3.rst:3 79a1bacbac6243d387e562e0312b8b37 msgid "LoRaWAN-X Unit" msgstr "" -#: ../../en/units/lorawan_rui3.rst:7 ebd84df7653c43ba8f66163f64f9c41e +#: ../../en/units/lorawan_rui3.rst:7 03a23c1e4fb74a8ba61a8decd4adc057 msgid "" "Unit LoRaWAN-X is a LoRaWAN communication module based on LoRa " "technology, specifically designed for those frequency band. The module " @@ -36,166 +36,166 @@ msgid "" " the AT command set) for flexible configuration." msgstr "" -#: ../../en/units/lorawan_rui3.rst:9 40d671bdc7f747258dcc1f63b082c979 +#: ../../en/units/lorawan_rui3.rst:9 21e229fce3544f27a80a3fd6b4c5a97f msgid "Support the following products:" msgstr "" -#: ../../en/units/lorawan_rui3.rst:12 f3afbc7dacb3493d8065587eff5380c3 +#: ../../en/units/lorawan_rui3.rst:12 cd377df1b142496fb4817713e8f7b777 msgid "|LoRaWAN-CN470|" msgstr "" -#: ../../en/refs/unit.lorawan_rui3.ref a086f716cf0245668bc2012c3cea9745 +#: ../../en/refs/unit.lorawan_rui3.ref 427d6fa730154ea89bf4a0a500cfe3a8 msgid "LoRaWAN-CN470" msgstr "" -#: ../../en/units/lorawan_rui3.rst:12 a9096faea8244ffdaef4994df7886cab +#: ../../en/units/lorawan_rui3.rst:12 f2880c9119074b818b106f450bd708f4 msgid "|LoRaWAN-AS923|" msgstr "" -#: ../../en/refs/unit.lorawan_rui3.ref 4ee3c57e58224791a61e6af3460fc3af +#: ../../en/refs/unit.lorawan_rui3.ref 034d3cc40f5149b595e3e5f207489481 msgid "LoRaWAN-AS923" msgstr "" -#: ../../en/units/lorawan_rui3.rst:16 796d3b0957264aaf9cf939bc93ce7c4d +#: ../../en/units/lorawan_rui3.rst:16 7d2f8bd9849e4f68bb76fd1e3f53a1b3 msgid "|LoRaWAN-EU868|" msgstr "" -#: ../../en/refs/unit.lorawan_rui3.ref 828174ddf7db4544a49cdbd4886e55fc +#: ../../en/refs/unit.lorawan_rui3.ref 7703a7d45c2c4e17a30e2f1a63ac852c msgid "LoRaWAN-EU868" msgstr "" -#: ../../en/units/lorawan_rui3.rst:16 110bd8c0579e451eaae7c84199f457b4 +#: ../../en/units/lorawan_rui3.rst:16 30057a9f1de448679078da43c2bd3aee msgid "|LoRaWAN-US915|" msgstr "" -#: ../../en/refs/unit.lorawan_rui3.ref 247331dfc53343d7af82a8e6d664aa1a +#: ../../en/refs/unit.lorawan_rui3.ref 745268868a324a2388e52c3fc92aa8e7 msgid "LoRaWAN-US915" msgstr "" -#: ../../en/units/lorawan_rui3.rst:19 7a9e4bca119f42919b48c13c87d5a148 +#: ../../en/units/lorawan_rui3.rst:19 2914d1a3bf5c432d9a07ac0c7cf449c3 msgid "Micropython LoRaWAN-CN470 LoRaWAN OTAA Mode Example:" msgstr "" -#: ../../en/units/lorawan_rui3.rst:25 2a9fef4cb336430db94be75687b75d15 +#: ../../en/units/lorawan_rui3.rst:25 70fc435b99464f6b888ac3764db06369 msgid "Micropython LoRaWAN-CN470 P2P Mode TX Example:" msgstr "" -#: ../../en/units/lorawan_rui3.rst:31 e65268e9e9b240f688753e26bfdd824c +#: ../../en/units/lorawan_rui3.rst:31 db9d27f423494075bdc1e861d41fdf1c msgid "Micropython LoRaWAN-CN470 P2P Mode RX Example:" msgstr "" -#: ../../en/units/lorawan_rui3.rst:37 ee0dd540739f4408b348d5a83a1ab310 +#: ../../en/units/lorawan_rui3.rst:37 14f24bbbd04c479fa307579e7ab2676c msgid "UIFLOW2 LoRaWAN-CN470 LoRaWAN OTAA Mode Example:" msgstr "" -#: ../../en/units/lorawan_rui3.rst:39 d0615ad18a0c4e579359840a87d63354 +#: ../../en/units/lorawan_rui3.rst:39 e310ef605e624d4a911a010dfbd0fb77 msgid "|lorawan_otaa_cores3_example.png|" msgstr "" -#: ../../en/refs/unit.lorawan_rui3.ref:59 d32bd10ff0c44ee4b9fa5990d7c8f6a9 +#: ../../en/refs/unit.lorawan_rui3.ref:59 09f09f8b125d412faced76e61f08b843 msgid "lorawan_otaa_cores3_example.png" msgstr "" -#: ../../en/units/lorawan_rui3.rst:41 42fc384e7c704671be5803dd6db4ea33 +#: ../../en/units/lorawan_rui3.rst:41 f956c50f89ff4c84bb2f3ef06d906942 msgid "|lorawan_otaa_cores3_example.m5f2|" msgstr "" -#: ../../en/units/lorawan_rui3.rst:43 c7a4735da39d434dadc6cbe5f73e9459 +#: ../../en/units/lorawan_rui3.rst:43 7f60aad135164055a0936f6e47eae10b msgid "UIFLOW2 LoRaWAN-CN470 P2P Mode TX Example:" msgstr "" -#: ../../en/units/lorawan_rui3.rst:45 b452f46798e04d34bd09ffbdc075dfe3 +#: ../../en/units/lorawan_rui3.rst:45 eac6231ef17d4a818a39a6c1ee84793b msgid "|lorawan_p2p_cores3_example.png|" msgstr "" -#: ../../en/refs/unit.lorawan_rui3.ref:60 e7e51dfd5b704e368bdb954b8ac54c88 +#: ../../en/refs/unit.lorawan_rui3.ref:60 cca03bb70e264b2f8d21400973853eac msgid "lorawan_p2p_cores3_example.png" msgstr "" -#: ../../en/units/lorawan_rui3.rst:47 83ee3f14396f45ed9a06fad67804a4ff +#: ../../en/units/lorawan_rui3.rst:47 79c0b8f75d47489099ebfaaf94295e6c msgid "|lorawan_p2p_cores3_example.m5f2|" msgstr "" -#: ../../en/units/lorawan_rui3.rst:49 f1ac0b084e2b44399b20efe9f5d48214 +#: ../../en/units/lorawan_rui3.rst:49 46a28011304a4fe2b6c3b364cd45c091 msgid "UIFLOW2 LoRaWAN-CN470 P2P Mode RX Example:" msgstr "" -#: ../../en/units/lorawan_rui3.rst:51 9da23913e97245b9af79044a0f4d427a +#: ../../en/units/lorawan_rui3.rst:51 5fc28aca298d47539ae083cb0c42a206 msgid "|lorawan_p2p_rec_cores3_example.png|" msgstr "" -#: ../../en/refs/unit.lorawan_rui3.ref:61 da88b8f1c45c45a7abfb60b9fdff2f0a +#: ../../en/refs/unit.lorawan_rui3.ref:61 14828d4ed6c04f6789b7f1f55e6ad283 msgid "lorawan_p2p_rec_cores3_example.png" msgstr "" -#: ../../en/units/lorawan_rui3.rst:53 94a7fb570f6d41fcbd0420a66fd9cd9e +#: ../../en/units/lorawan_rui3.rst:53 bb302a5ee36e44c9b3a14c03eafe53be msgid "|lorawan_p2p_rec_cores3_example.m5f2|" msgstr "" -#: ../../en/units/lorawan_rui3.rst:56 5521d59e060240c2b03f53192a15b654 +#: ../../en/units/lorawan_rui3.rst:56 91325bfdfa77453f9039a5d27d9166ce msgid "class LoRaWAN_X" msgstr "" -#: ../../en/units/lorawan_rui3.rst:59 0d82a34bd5e640b7be1f2f8220bd9e31 +#: ../../en/units/lorawan_rui3.rst:59 41255befb72746978d31f08d42eb0640 msgid "Constructors" msgstr "" -#: ../../en/units/lorawan_rui3.rst:63 4c3fe2f410a04a2999688ad4aa4aab26 +#: ../../en/units/lorawan_rui3.rst:63 606092c3d5d447f5af75c528e8a98344 msgid "" "Initialize the LoRaWAN_X module by setting up UART communication with the" " specified parameters." msgstr "" -#: ../../en/units/lorawan_rui3.rst 01cf7ba3eeb44ad19808b36b2ebd2931 -#: 05b4aa9c9f354496b1a92aaef26802ea 069446e181384944a2d02d6924ac861c -#: 07e3c4d2b7bf4e5ea260c261952a3fcd 09c679c434b945d5a12216d0225c9e85 -#: 1115177fbf8847c7b50f5ab043dcd1fd 112b8bca058043699b710d8ce3ec9007 -#: 15acbca8991f4604ba068b503700d659 1b5a91b2915942669cd649c7bf71b793 -#: 1f148aecf94847a3be70059f48fd15e7 27239c8764904d708b2ad292740d525c -#: 29125751184643f08487c32ed7c86287 2f0c8f383c5e415c83ee67b6a6e9105e -#: 300d8cbb5c434a2b83d4cd75e66f72bb 321643e4501f44b3ab33a04464d80484 -#: 33329f7e479e4067b1e3ec5a773e723f 3e9b881f58d44f1b86adfe3edc61e96b -#: 41e1427ce4414e1fb0902dc37c8435f2 459f9bdfde0045a2bf58f478321d3216 -#: 4730cbcabd0648858616f968a21331b1 4b3f4c5d7c3d4f22ad319a96e65841fe -#: 4e30cda3bf1741a7ac046273ff2bfeef 5226ca0ab3c849abac7d1ae07ac26c4c -#: 5b24572b073644ef87b50d23a3374964 5fea317b98e04d50b08bb55591585187 -#: 62d2c039eaf04f4497cd22e7908869b9 651e233d98f14dbbae13d9342b6ef255 -#: 6c07b77af0de4698bd2d34dfc0491bd7 6e6c8412cd0f4130b60db871f7f146d7 -#: 756ebfacea89468b89c3673edd5654d1 7574018b8baf4489a9cd721d6bc92252 -#: 78bba6271b35438db0ef565a0564d844 7a6c7065af614b71b0013177af40b2e1 -#: 7a99f139ecb14c2a8d0cf4dbd9a7cd3c 7c4b075068d046a8b07c3510cb6ae331 -#: 7e18c0f193c64daab700577b2f191a27 84c7f40605a44acc90f5e81ec08216dc -#: 8594097dac1a43888e0de3fa8bf379cd 8a527a1aea684c10b5032bf6168d4713 -#: 8e74bc97f14743a1b2984741339b2aa1 8f7bceb02fbf4b0db21db90a40db38cc -#: 90e24017d154491289fba430a3d09d31 96ffd7162bae4a80ad0a35640bebfed3 -#: 9abf7a8e217a4fb0ac019ca901883fd2 9e06a196a85944feadadb36baaf398da -#: a9fa5538fa3e49a3a880ebac1a37724e ada213b9ea1748799b5ccb7d9149d8cb -#: ae1256ffb9944597ab1bb8cf54aa6b81 b650b61f4d6e4a0ea6072ff56b7974ef -#: b7dfb62bb2684b6da8f2e9688d5b114a b913ff2d05ad4fd48d080975ab85c109 -#: bae5ae4152ad47279a517da69b6b61bf c3f3710a75b641d78154b6a1ac441152 -#: c4e498dbf81b47eba08f429566d3600f c89d512060914772ac8d09525c91d779 -#: d027d8e61d90471a8bd3b67769b318de d362251d013c41dca3cbd15c82333bb6 -#: d98996c3a1a94d80a92ec73a8fa476b1 e9d4ad40752547ccacd71e12eec837f5 -#: f00709ab17164ddfb3aaeec2fbb88430 f13b10c75d334eb08ec279cc87dbb6c5 -#: f147f8e1ed2e4ec3a1ae1834f42794a4 f527f0cd09fd4a78b672700297e3479d -#: f7b5a1d5dc8e4a1dbbc3671a0df88f83 f9eafc753c484aba8fbd8f925f4374c0 -#: fce8d5573d5940448bd60c7340540011 +#: ../../en/units/lorawan_rui3.rst 06e01bd54b124e078efb96215bceb777 +#: 09175a60d5794e269fa19fabc7dd1568 12ff8faffb144d3e8d4dfa8293a97a27 +#: 15e782111da94154818afacedc14bfc0 169d7d1f461a4ef29f595e8b07e334f8 +#: 17bf715ed0ff471994c836bd05690eee 193188452f364e6a988796d31438999a +#: 1c06db93b2c94112aa9d42c793f6be87 2067062681df41a7a4a75438c395f5ce +#: 223c5f1b5f344d48b6e44d3c2279e633 24ea64d3ab7249c4b058fc389bc2d4f0 +#: 363fc844f8094e629badac24aa96a58e 3676b49c88434c39a6f2d27af9a9e6dc +#: 370deb2d224e42439315061abcede897 38b38abe338c4ee1945e17b1560e7eb1 +#: 394ecbbd783b4dd58985edd2e320b240 3d61c1bc88024dd7a78025679a9fb27e +#: 416cd417988e41fc8b24aa2221fb992b 4385cadf6be84753a79e4982fa304591 +#: 473a134fcc9b4cad9a3c153761a7243a 494283e925af4bab85e4619254f4ef1d +#: 4b58110e604648349ff87b73d4df914a 4d65684c7c05458da6b8830998db3099 +#: 4e302f2207d6411980a3cc17c2685935 4e731da117a94ae69e82f9051c449e59 +#: 5ad0f618c05e4f5bb37f16b77e6151af 67baaadd18cf4a46b4fc60f384f6540c +#: 71c9f9f955a540b7bd7f9d23f40ad9d8 79ffbe03898d4bc7bcc90f24bb30d33b +#: 86ac6a655c124727ab6111324cd814b2 89e6908f4cab40959dc3d29322dea47c +#: 89ec4eda26bf4ac898528f41c2cf0481 8ad5a990b3274475a13e7bd3143f5f84 +#: 927f05a24a864021b877594e4a8a7040 954d5eb832ec4cd489a9b3484904759c +#: 956a3d07818942c599a2030898bbd5de 977ca5741dd54b10b3eccad439d950be +#: 9948cda4eee347a8b7787fc1bc1ac272 9adc9b4db7554624810113ebd437a828 +#: 9b04e6bada7348668a7a2070a6b3d726 9d731fef200047569df38c2970320e6d +#: a8dcba9181dd4a8b8a1f1ea91be8cf58 aaf758540861441c8acaa32a0dc32dd3 +#: ae78038c4cb0489f8f59714cef1b5bcf af1c565d82d9417a8ea1e3b27bf264df +#: b180aedf42d34a478ec5e0449053a222 b3db023d173e4c918ea0ad6fe537b790 +#: b4026839798344a2ae8a98727637912a b69452783b994342abea035a565abf13 +#: bf679063914b4a8f94b37d3f30a854d7 c81e9137153f484bb5cbcd56b3e644be +#: cfde731d77fc4c32b094e2932419b36a d09e26ccee4c418bafc98e910f2cd904 +#: d32c0801bfa246e599a51f08f793e807 daa0fd8c1fc9485ba12181f7ccb6e9a2 +#: dbad5c759f8742ddaee511c973e21660 dd1397de3bdd4420a5d459f60c5a222c +#: dd6de3a1688e48b0be8623340b30b8c5 e731a1ec0e5b4fc1be627f5b941eae95 +#: ea30ce6cbb7f4b8a8fb5a7c8e2dd1a6a f0be3bfe290f4a2f8a79904e544256aa +#: f3418429befe498d81ef9fefe5551fc9 fb51a5b9edb94579a6b08164e708f35f +#: fb7edc1224514ad094ee09eb09235e60 fd57eaaf5fc741868e612961013657f2 +#: fdeca5ec6e334a079521a83a431787e8 msgid "Parameters" msgstr "" -#: ../../en/units/lorawan_rui3.rst:65 ad0da41626f74935b77c12a84dedc28f +#: ../../en/units/lorawan_rui3.rst:65 b48e5f2d09a94872acb2ee776ebe8e72 msgid "The UART ID used for communication." msgstr "" -#: ../../en/units/lorawan_rui3.rst:66 3ceaa47c69854952910bc5d83118ba70 +#: ../../en/units/lorawan_rui3.rst:66 5e466f13ae87488092bb94810e974491 msgid "The UART TX pin." msgstr "" -#: ../../en/units/lorawan_rui3.rst:67 34e6747ca3624c6288a0c656c6467167 +#: ../../en/units/lorawan_rui3.rst:67 30a777d548504abaa1ec1349c56c3b66 msgid "The UART RX pin." msgstr "" -#: ../../en/units/lorawan_rui3.rst:68 1781b43929ee4efda058da341ef002e1 +#: ../../en/units/lorawan_rui3.rst:68 af2082108d6c40309eaf2beda0dbe5ed msgid "Enables debug mode to log additional details, default is False." msgstr "" @@ -212,253 +212,253 @@ msgstr "" #: ../../en/units/lorawan_rui3.rst:977 ../../en/units/lorawan_rui3.rst:988 #: ../../en/units/lorawan_rui3.rst:1000 ../../en/units/lorawan_rui3.rst:1011 #: ../../en/units/lorawan_rui3.rst:1023 ../../en/units/lorawan_rui3.rst:1034 -#: ../../en/units/lorawan_rui3.rst:1046 ../../en/units/lorawan_rui3.rst:1074 -#: ../../en/units/lorawan_rui3.rst:1102 ../../en/units/lorawan_rui3.rst:1216 -#: ../../en/units/lorawan_rui3.rst:1228 01a94c63714a472d9d25beb0656a1236 -#: 01dc580566dd4bcd8881659b97bd684a 0bccfdaf3d17453aaa862f5cea9c0f42 -#: 0ed2d4833ba640629c7d5d19b2ecba7f 2097f68cfc8745969d4dd1aae97f29cb -#: 2a0673b8ad7a4893889355dbf04c98cb 3538bf8d746d49cb84af70a7ebf2ad2a -#: 42d8991de75c48a4bb7aee9e7c92d9f4 5647fb99774049be9c0e6b6c440dd3f6 -#: 5afc24040d504f47b8437c8ada4776b0 68465062127c46059c5aaf16a2be68c6 -#: 68531eeb7bec4b7e9c19fd4a9ec1de33 6fbabc4d54e4470d9aa15cf48c379b63 -#: 717154a0d665410d98c3a108823f7b98 7cbacc5756dd4e44bed4ed059f39d13f -#: 853cb490db0d471abd13d256edf2129c 8da9768f5b28432ca6f8ec2ba199c8c3 -#: 9aa7940dc6c446cabc29e274a970c50f a142632617c741d2b870e584d0a3ff7c -#: a1e12df443454b54a1c31979fbd306a4 a37b498441ad48e6a0cf95f8881e43a4 -#: a4538eb1d4be4d23baa28976ce811fa6 ad64a4e76eca4ac4a0bcf71cdd9dc3ad -#: af4979584d94476d81369b1e68fabe10 af5127c3166d4dacaec28ed3dc4d9a4c -#: b3a6481cfe8c45b693fc5ef557942640 ca64edfbf03e4f1eb983c4c89662c148 -#: cfa76dad5a4b40a8ab78146c4ae9d19c e1107c20526a4c29a23c94342e787cad -#: e9bf69b73e254dcebdc7b68052149e0a f1de8d7579e849bc9397d4df3856e2a9 +#: ../../en/units/lorawan_rui3.rst:1046 ../../en/units/lorawan_rui3.rst:1075 +#: ../../en/units/lorawan_rui3.rst:1104 ../../en/units/lorawan_rui3.rst:1218 +#: ../../en/units/lorawan_rui3.rst:1230 03858fd893eb4f35b659d64a68a53333 +#: 094648c6f87a459eb04a137a266e420b 09b627c9c8fa499ab94ea4c5cf334653 +#: 14be25ca9c0340918ffdea2c5c2c9e8a 1638f076c6004129a21a41fc7eecec36 +#: 18862260f87e4ee08afc35109d754989 1af29f1c835e4a91af09dc0112766446 +#: 1e9dd29efe714e79b9f3ab7ae8a5c819 26530b56ded14805b7d4e0ac2cb21a08 +#: 2a345d3e60f44607978eefbe1483e616 3a37df8cff684ee8b040698d10b22867 +#: 3ee15b161532422381131e85f79be35f 48ae4024ed6a461daacf2fe72072cd87 +#: 727f21a773cf45e0958147cf1c2b5eda 7756b90141264f00a437da2a39653286 +#: 7ba7147fb3fe415ab24b4f6176496ac5 82af4d1892fb458a8448c7674e8416be +#: 843b138c206247f889ef4b6c2a7eadad 8801139a4fe749d387226d3ff9fdf890 +#: 95d6b2f88af44c0693eefaece77c87e3 9709de65b2d44bfc8ef84ce412627600 +#: 99b5fecf08ec4f3a93c20e730cf1914e a0ede0e7c71a494fa3e44c58c0d8edb8 +#: a0fb6634942f4db49b3e695c3b96fbd8 a43debf924af434dbc22280a4adeb908 +#: aaee7a6ef44e4478ab3c0a835dc4cd5f ad6d2f151e2444efbb0cc98cae604b42 +#: cd5d6a93b8f14808af348f619a2c58cc dfac1057625d4657bdd59e72bbfb4c09 +#: e43fd5aa28a74d43b1f130304a41d291 f732d1abf37948bc9f8a9560dce86b5e msgid "UIFLOW2:" msgstr "" -#: ../../en/units/lorawan_rui3.rst:72 25bce1d91841482d997c864e1aa7291b +#: ../../en/units/lorawan_rui3.rst:72 0221d62883b645b18144bf0475e562e9 msgid "|init.png|" msgstr "" -#: ../../en/refs/unit.lorawan_rui3.ref:21 d3501efa5e9b42daa22add60a5c1941b +#: ../../en/refs/unit.lorawan_rui3.ref:21 cf87784feabe48e1a2536c0183aaeb68 msgid "init.png" msgstr "" -#: ../../en/units/lorawan_rui3.rst:76 4951bb43131544cbbaf2cbc7f74947bc +#: ../../en/units/lorawan_rui3.rst:76 9fdeba3056db44d7a1bfe057f6cacc8a msgid "Methods" msgstr "" -#: ../../en/units/lorawan_rui3.rst:80 34a35b5855e34c0cbd749a9e002ab2ef +#: ../../en/units/lorawan_rui3.rst:80 427f3bf309884267b8ddd7f162075f0c msgid "" "Configure the device for ABP (Activation By Personalization) mode using " "the provided device address, application session key, and network session" " key." msgstr "" -#: ../../en/units/lorawan_rui3.rst:82 6671f7555b4b4b088cbe84c53421b387 +#: ../../en/units/lorawan_rui3.rst:82 1d2c8bb73b734f5181316babe47212d2 msgid "The device address for ABP configuration." msgstr "" -#: ../../en/units/lorawan_rui3.rst:83 eceb1fc138834fd78e13b9cfb227c439 +#: ../../en/units/lorawan_rui3.rst:83 599f7a8e5bd348de890148b008355d76 msgid "The application session key for encryption." msgstr "" -#: ../../en/units/lorawan_rui3.rst:84 3d51b0003e7d429085d4d9ad9a427a88 +#: ../../en/units/lorawan_rui3.rst:84 4ccab10d5fd5481985a96db27ca520b9 msgid "The network session key for communication." msgstr "" -#: ../../en/units/lorawan_rui3.rst:88 4de5ec2ca8524e7aadcc4d2b50c4d09d +#: ../../en/units/lorawan_rui3.rst:88 be50dac7b29148d081dfa38b8f2bcc58 msgid "|set_abp_config.png|" msgstr "" -#: ../../en/refs/unit.lorawan_rui3.ref:53 6ad4181071d44eb4b3affe63756a88aa +#: ../../en/refs/unit.lorawan_rui3.ref:53 521d90a8bb57448d8947d7d9a259b94e msgid "set_abp_config.png" msgstr "" -#: ../../en/units/lorawan_rui3.rst:92 d6c24b6e35cd48c286854c963c3d6878 +#: ../../en/units/lorawan_rui3.rst:92 9446504940db48b496da85a07b5ab4fa msgid "" "Retrieve the current ABP configuration, including the device address, " "application session key, and network session key." msgstr "" -#: ../../en/units/lorawan_rui3.rst 02c53dfab4b3423d9d82115d0dfda014 -#: 043985f9b59e48c38094fd51b4b1e6aa 0502af79c48647719df5f26dfa2cb876 -#: 057ead38c41243009683f8d3089a6c39 07e473f6659141bdbf3ae237c3bc5055 -#: 0918d0d7af194e099968f84b44999dff 0b90a70b9eb94ba89acaf5030a60d8bd -#: 0f855526480743b085d4c72024ff7cea 0fdcc24eb06943a5800f701558cab90f -#: 11a0a329c08646b392aa5dcd3211da84 149493bfffa64ad8a3f71a856461912f -#: 15c8f9cacdd14298a8155335065b85e5 17d83f3a648d40a19dd9d316c8758764 -#: 18a9aef275f543d7b7cc5970b4480e4c 18dc987d8cbb4c5bba8f36b8439be257 -#: 1a90a33e74da4ddcb8ff57a55e913b8c 1ae9811f73374328854007c5fe8cc1a0 -#: 213eaa6c51324233bb5f2ec3c5f78bd4 21cf489050a444a9b83d0459a9e1cae2 -#: 246fc604541844dca33d0eb594046fbe 24b37ee5e9b34c99b129d4aa72f7e1d3 -#: 24b395e23aec41328c5c66aa92d9e116 2692c93b07b944429963f6df566e3a16 -#: 26adce5e35304180ae3f8162c04eb1e0 2d216127b52d4098832cbb87934c6634 -#: 2f004411204c487d81a241a1caf28f1a 3622da886af44e45b872b63db5d497a0 -#: 37c336c8a7dd450bbf60c90a7cd86ba6 383a533d5ddb4e0ba1515a506f1b1ccc -#: 3c69904225054c46a6ec46f3e08b07e4 3cee60db8e154932a46bc6bf252f45bf -#: 444627f721fd4e67be3df54a888ac205 466e6de353cc4ae895535bbc7c16787d -#: 4841cc9a097b4ea394555772552b2946 49487b3a18984217bfb814ed8084b7e1 -#: 49ccdc60cda8454293582f477b2a79f9 4be4e8578c1c4c769ae41207576dfe58 -#: 4dfcfbeade8c4262af8dfc76396b75af 4e6feeb8e8124ca6a9a076221afa2811 -#: 4f7d339a3d8644dc99e98bf2dd1c35fa 570b88d65c46484791a917ed84f18384 -#: 577b6eed68fb496e8b8cc0a232a058d6 57effe531ff942d1bc4f8da7bc8ebc32 -#: 5cdaf92988ef4cd0a8730eafb118446e 5cdf571d24a54231b0548afbca73fa55 -#: 5df021a7660443159e67c1dd1354e896 5eb710e993f14c2cbb9b654b9c8cc54b -#: 5f346f8415d24bffb79f0b5ab592e560 603934fd153a4b1e93a690579674b5d4 -#: 622af8b15c954b3dacf2aa287034890b 62bd3aa7bf1a4b65961c18cf481ecab2 -#: 62cdd601ac064712941931b5404b8c85 636e5eefc02e461a9898a23ea90ee4fc -#: 647cef77c368460897af11a2c8e3b1fd 69fa7b552b944e50a09c9d2da373f14b -#: 6a8214e3026e40e08c458fd7b0f07303 6ada2dd9ee0547c7a14f28440214745b -#: 6c02babdd9a2480d8bb2d23a708de9a6 6ca0f6534b6b466e9ff82af6e24a7fe9 -#: 6d795ff46bca48dab1b2e11319f6feea 702ab4c9ed694da5a11941f9ccb77aa0 -#: 7074fcf6add349229543c58aeedd51b7 74b26d20c6b5498b8c3d0c924253f3a8 -#: 7a982ef7488645939b6a1d500539cb12 7e539817d67b4b879a7f8d8338be0194 -#: 7ed7bb13f9634fb5b10aaad274e0c3d8 80e9925b9c014a0f8f804a301c9deac5 -#: 81734c5bda8f4e79949e38d8c3914060 8230eb64c02346d89670074239c152db -#: 8430cc6fa54c446b9eb565b6728d14b6 863514d48368470f83b19b06b4a5c504 -#: 8642c75c0d4143b191b0ab2f2ffcc192 86a5e6dd2c3c42308d012ba72aec8f5c -#: 8712254ab37c406cac6299390de9c979 8a62015d812e4b2f8d9b84f4b98f0599 -#: 8e29b678ec46443d9bb7bab0aef4242b 90aa58ddb5144510bd2420ce706fdead -#: 928b055f3e4642eb9ccc3f96b0505240 93150e76970c409691aa680256fcf61e -#: 936dce9ec85744bd9325136b9af2bf05 93bcdf44696f4f07b0aaf1cb5588802c -#: 97a245cafaa4420ba2b7202bd4448e60 97e8c7fcc6f94d50bcf6f407f5d5515b -#: 9ab67adb03a141e4a76a384d9d5f9aec 9ce3d1d47ea6486795816642a4895fa5 -#: 9ed4facc845a4127800824b3cc584a82 9fb1c6bc7452447fadd8f8967d4a15ff -#: a14be521790f409881b81992fc8cee5f a1c031c28cd94a8b8d5e8cbb03074647 -#: a52b93a920b0462fbd1af92fedde47ef a54963da626f45ff90286b30213a462c -#: a94888c306c5455ebe48226320bf2b3a aa929e4ea6f04cf2b992ec8204dfab9e -#: abbbe47a52f64d9ba41575345e524f94 b19d2bad06b04e76bb6164ba0646e229 -#: b316ff43bd99427fbce7bcef700359bb b61948d7a2a74a66ae32663d7678eb6a -#: b6d7082372dd4801904bb1b5744a48d7 b70aafd96d33456381724ece5a3fd4c4 -#: b883bcaf0cba4bca93bae8d136999754 b89b7a71bf4a4f78bd83e1b4fa2614af -#: baaa616b75064046b25a74cd5e0ef927 bc8130e612c74e6da1631a33d7b804c9 -#: be5432ff78744b65b67b6182a8835c3f c305b1a173b24538993776b4f4cb2c1f -#: c3907d5bfee34f75bc39d8709e78616c c5213e437b434ee68478c54c56629ccc -#: c537ae57b0ff4d4b97c511b7b5c668f7 c6133a1c5cc64b449185b426d91e7043 -#: c8beffa1954043f5a5c939f920b19e11 c9ce4408a2354d60b383941c4a8326bd -#: cac477f84d494fc08bcdbd9fc645dab2 ce364e440f9c45a29270e932d8d26745 -#: d001c0d7ae9b49e89bd8e63379360bb2 d14610d7189c49e687e67495a14cf26b -#: d149c345b88643079652b017b077fc32 d18d89b259324f8b932b9913c3853c8e -#: d1b649c9d8234ac08f1e3c97869d4ca3 d33d5606f739427bb95c2ca29154aa13 -#: d6f37a7499da4762a8aab6fd34bd39bb d728b5a1d0114e1a911aaa34fcf19531 -#: d745518aadf04b658fd415dbec847123 d84618c248a4470ea8053a03b4852289 -#: d8e8c7de335b4cc387fd0b02cd9cd327 df9cf7f671f14abf931206b1a1b78fb2 -#: e031b00d0b7e4e029987875acee70ab8 e0a1c4f6c3a44d11a403e3e65a4fb8c3 -#: e0efe26e850d46c3a72cbceaf01ab78d e215cd75edcb4180a5d150dee0448ede -#: e2dac8426aac4bc9bb6cc0a88dd3475f e481ecc75e9b4ab28ccbe86dc58c4a57 -#: f3d44bd5f3c54fcfbe5bcb7055427bb8 f69dfb72a22f43298d15ac2c695fad7a -#: f80a1e9e072c4436a03800c8c11c5dba fa88126e8ec54746be37b7fe6f35aafa -#: fb9dfe7fe62844b197149b2141df9871 fd101c65489e477180f8e6457b3b77ed -#: fd8c031b145e4a149fec17f9a9572e74 fd931a240bcd4bf487defe3db01f6436 -#: fdd063e2c073455d8f9fb3bf75822aea ff0d872f3ae74c73b8221ddff8e76b49 +#: ../../en/units/lorawan_rui3.rst 04cfae07a596458ba3d6544438eaa181 +#: 0527d158513e4feda35343c6b15e1fc0 05c7d928af054318a685a366b8edbc4d +#: 08163c10985e4b189bd4e4d94802706d 0e141facd9334a5baf3f27d80de25b52 +#: 0f2b9480aec3432eabc785636a79d53a 10a562ce616449a49c9b9ed64a4cfc06 +#: 10a6d643c6404d408068efca8bfa5824 1289e13c21e14874b538edceba9d5930 +#: 166c92c38d4a4614bc36bbaa5ad23e0e 1919b4cd38a94cd58cd3ab7df683d1b3 +#: 19562d9938c6426997665f4db0f0705c 1d6a1d32cf494c37a9d79fe266ba4579 +#: 21512d51f5844de0b2fbdcc1d1393c50 2174c82ac5f2496a896b238fe7a07e07 +#: 23d971e9c33f493098a3fbef64a143c7 252446fb49104d92bb5906900122fe5b +#: 252cb5efb82c4c05a43c3886d2e3fe9f 2559de626a2b4aab99ef5395cbc3a980 +#: 25c441c165af4bee971f9b88c1aff392 275d465236a243b891cbcc1ded64ef1b +#: 279eef28c3984056aa94a5846634179c 2a428aa91bb2493cbeec406d67e8370d +#: 2e546d8038a148d5a6eb51f4165c742a 30dcac99f851448d980a4c9a6b73d7d6 +#: 312e22bf01ba42369c1f62371bfc8e1c 31f9a1059e664d78b9a78c5a5e442e71 +#: 333b30e65f1b454b8dfaf39c2c45c192 33bdd3269a0a47b69f52be95ce5cba03 +#: 36f525959be84cf9bb53529a6f038c9a 3b6f36aefba2437d98ac26f94c68acb9 +#: 3be32ab050694ff992da90f99763be1c 3e7c0e36369c49cbb69fb1b0aca5d5bd +#: 406e39082b754414818da960b3d75c45 41f9e0816c6149568275d3b09c448757 +#: 43d0ce87bfc6425191451f002e5b7726 46c5f99d68a5480fafae0748de8ea521 +#: 47bfda8817304b43be59bb268ee0c7fe 49fb23ad109b41c682c1b081786ba346 +#: 4b6d03e1a03d46bb86131be45c38b579 4be4ad5e3c49434b9454d581bb96d750 +#: 4c0c8698ef65404983afba58b4be400f 4c480f1409dc4811a2d0e29fbbe98ea3 +#: 4d79e32b26254a168ab2ec714763c820 4f491d518adc4e66921fc0aa2a167b18 +#: 500466b073554c2e8bd927baa57e4820 50543041644b4372bd16f0ee36a19e7a +#: 53a68d97d7e14ad2adf31f2399727253 5481e8522f294c3091b9d2fc12c0f5ca +#: 5600f91c5b7b4857a3b1a7b901f6f19d 584778b9509e417b9161b42253035290 +#: 58dc23354cc54e1d9b75498a11d9b131 5cd8abe121e645f4829f5369ad887acd +#: 5d7175e21b474f18a29af71efac2c04c 5dc9c5a9f2b64503b9c330a6001b09e5 +#: 659eaf3634514a15809194554ecd34e6 66a421512bf941a9ac923827b0bda8ea +#: 67507bff8a7f4206b37f88e08ef39477 67c940f4b97743108ff2aba5cb470eda +#: 6d22e063f4594050b9afbc1cb243128d 723689ca460d4488b767ce07ae339c28 +#: 72567d857f374bfba0253bbc1a1716fb 72b0ad93c30946b1b7258a6480d4e80c +#: 76b9a896517444a7ab7df28a79a3e9d4 76eec87517584745ac83ef3ef2402034 +#: 780485651f2d4697ab320e14594f546a 7a0c473f346441919fc1934838a81ce8 +#: 7ac0277b598245ddad4ebf3d94b4a9ff 7bdd5f6841ea4be8949a1da8d807e1d0 +#: 7d1be9f5a0434fd6a60ba933c569ec61 7eef1c7f3a584c8e87d43e60331b663a +#: 7fb08142dfa4499aa3dcd5279784c3d2 80504022842e4c20a7ac8deda42f0315 +#: 817ecdca723a44df9d729e79553e34ce 8219bc6f5cb447cba4088922647270e5 +#: 823ac70e29754e66b29f9b690e2d4472 84a7590dd8a74c1a8ca06177a2b9fb62 +#: 84b01e9d16dc4dbfa7b3a3dd15e2bc95 84b74e8bffda483799911d141b4bc775 +#: 8630f30d72424b92b4ae59c61fc4a632 883583084d4e477c92387f6574ca84a1 +#: 8c4df1ee0b4a4e6aa83f4de3b2f3e1c7 8ccc3c31735a47e0b8a6f2dfb23e02e2 +#: 909f082effcc46778ee1b871b4d39806 91cf8b976ac84788a49125fbc124de0f +#: 92b530fd4f7d4c34b90f49c410a49e22 94320163d8324aab99f72da7bd134933 +#: 9654a1ebed9d444c9f6db43711bf5da2 96c63d0c09f9431c929ba42f13c8fcc5 +#: 982074226a2e499f84d2233bdadcb2e5 9934a11f78ac4fcb90b153db2d323468 +#: 9d07508d58444fb3a8633e68a1acf811 9e111e1cc1a946af8fd65779f685dc64 +#: a3fff8554dff4fb79635be3976b1c611 a4fd332d206c4fe28876cad3be1c2f41 +#: a596ed3621994534843932dc330a13ae a609f06870b24164a00ef4ff8e30406d +#: a94b31f18b574f76ad76854680ec2869 aa0b3a5c8f104df9954a7960573ae4d0 +#: ab67fd9204b741acbf06d06554ef685c ad6e0ee4b8cb4d309178c00e9c43dbe1 +#: ae4277a6e40d4945b0b5096e60bb6999 b16e217ab589483381afcb5a2c9d57d8 +#: b1bf139d2a4641328bff3fb3827d6be7 b3f06234d5764a098520c4d87e815008 +#: b7c47dda69904d9b8783077e0b55038f bb9c375496114a1a9ca2f0e2025e1326 +#: bde22b62145644c49b33e7aec4413898 bf7c58136fba426dacc9f065993550c5 +#: c6c537e9deac4c6895c6f2a32993e794 c8c822494041487bb92594e752ceb762 +#: c8e1312a72754c42aee255757ae5e1c1 cb8da083477f435fa847032010b8947a +#: cd4ca72c15af43bfb4903492668bedc2 cdb06fe35660431d94b32918e05b3f23 +#: d2990776c8d946aebfebe5357bc6defd d80de1d22fcc4888bcf63d7180bd5720 +#: da395ff1eeb24187adae8ae9df0bd352 dc9fee28f8e2452796f210ec4626407e +#: de7a0e6ed85c468383b7eb1024d4ba2b e00ff7b261074c66ac40d63e0a47833f +#: e22c298ee89043a7a5a26e3778c77a76 e2bb8c0cecf54636b13e8b6bce2af0e4 +#: e48ecddc1191498781d29adb952f23b0 e6303d0563c149f5ab77325358bd12fe +#: e85b9b60838940ec827f3d1dbd41035f e8eadd29f2084c9ea624d4ab03c28d5e +#: ea24316366f24de885eedcbad8b36f09 eac350ce8609430fad8968ba5d536a6e +#: ec01667b28be44e38b9bf36c645d4209 ec151c25821348939ec2f5aa2a2ac095 +#: ee0403e78d2f41cfbf9f5ab42851399a eeafb9da9aab4ccab6f8f82692813ac8 +#: eee4fac4571743d897ef1dd07b1014c8 ef77f38e30d74bbe8cf2748136449989 +#: f418610d6a0f46f698c4f76850f76bbc f76d5c2853844f1b82b01e751e4cc864 +#: f967620ced4842b287d49243d3961b08 faded577cac742648c8d5bae1181d46d +#: fc45aa1048a442de9124040b1705dba2 fd722846a002411f875a4f3bcb1b4aeb msgid "Returns" msgstr "" -#: ../../en/units/lorawan_rui3.rst:94 9cd26f723421486d937275f579a86e08 +#: ../../en/units/lorawan_rui3.rst:94 7be9c65764834e618cb87192a41de7fd msgid "" "A tuple containing the device address, application session key, and " "network session key. Returns None or False for missing or invalid " "configurations." msgstr "" -#: ../../en/units/lorawan_rui3.rst:98 dc99401410844afab419245d24182ca4 +#: ../../en/units/lorawan_rui3.rst:98 6006264a39cc4c1885febc045b4229d7 msgid "|get_abp_config.png|" msgstr "" -#: ../../en/refs/unit.lorawan_rui3.ref:54 a093e5bca25a4197b0672e0bb0a1d109 +#: ../../en/refs/unit.lorawan_rui3.ref:54 1fa91b5944934ee190a66bdc7db407d7 msgid "get_abp_config.png" msgstr "" -#: ../../en/units/lorawan_rui3.rst:102 03c17673d1764cd6a0827b39561d30f2 +#: ../../en/units/lorawan_rui3.rst:102 861e2754e26348889822f07c22343ed9 msgid "" "Configure the device for OTAA (Over-The-Air Activation) mode using the " "provided device EUI, application EUI, and application key." msgstr "" -#: ../../en/units/lorawan_rui3.rst:104 9228a6ed634f47c09f367330b46e6580 +#: ../../en/units/lorawan_rui3.rst:104 7c1b271df0464fa381e52edd3c0bedcc msgid "The device EUI for OTAA configuration." msgstr "" -#: ../../en/units/lorawan_rui3.rst:105 7814ab1e3e984d2eac208480c1920be0 +#: ../../en/units/lorawan_rui3.rst:105 4f511b6c91a34974b13d38914a1f3fc5 msgid "The application EUI for OTAA configuration." msgstr "" -#: ../../en/units/lorawan_rui3.rst:106 839a9ea461144154b4b259a964860152 +#: ../../en/units/lorawan_rui3.rst:106 e910a0e696434714b829708cbc12b1fd msgid "The application key for encryption." msgstr "" -#: ../../en/units/lorawan_rui3.rst:110 6f189b4b0460482e86c28de2bfa23031 +#: ../../en/units/lorawan_rui3.rst:110 fc5a6444267545d0be85087e9ff49cbe msgid "|set_otaa_config.png|" msgstr "" -#: ../../en/refs/unit.lorawan_rui3.ref:55 09fe70eca4cf44db8986a2684f8ca9de +#: ../../en/refs/unit.lorawan_rui3.ref:55 70ee7b28f654445d9f2c85a4448c4232 msgid "set_otaa_config.png" msgstr "" -#: ../../en/units/lorawan_rui3.rst:114 7278bfc48bc04a7cb2b98bb77adb4615 +#: ../../en/units/lorawan_rui3.rst:114 a4aa9b4654d545f597aa725c5654727a msgid "" "Retrieve the current OTAA configuration, including the device EUI, " "application key, and application EUI." msgstr "" -#: ../../en/units/lorawan_rui3.rst:116 2ac32b9830a34c62be727b754bf7a2b2 +#: ../../en/units/lorawan_rui3.rst:116 b2efef34cb3d4b90be07c33adf8d3548 msgid "A tuple containing the device EUI, application key, and application EUI." msgstr "" -#: ../../en/units/lorawan_rui3.rst:120 fe5c6ef7daa44570a5eb3d499d2d2103 +#: ../../en/units/lorawan_rui3.rst:120 66e2bbf22f4140d2905cff56e192e88c msgid "|get_otaa_config.png|" msgstr "" -#: ../../en/refs/unit.lorawan_rui3.ref:56 adc32e55a6854217ace588fc3f5a417d +#: ../../en/refs/unit.lorawan_rui3.ref:56 25d439e27c19482895a3ab65921950ef msgid "get_otaa_config.png" msgstr "" -#: ../../en/units/lorawan_rui3.rst:124 6c6db02698fc4da4b8dd65e3e438d2e7 +#: ../../en/units/lorawan_rui3.rst:124 981a2bda24254fd4ba054905f0675311 msgid "Sends an AT command to the module and optionally reads the response." msgstr "" -#: ../../en/units/lorawan_rui3.rst:126 12e09cfdd41d44fcaf9f106f5428c557 +#: ../../en/units/lorawan_rui3.rst:126 98632e3680f142aebe99b49f494047eb msgid "The AT command string to send." msgstr "" -#: ../../en/units/lorawan_rui3.rst:127 15d2b886589a42d88bcc9fb8afc00d11 +#: ../../en/units/lorawan_rui3.rst:127 c0544368317242169bcfbcc82c463b0d msgid "Specifies whether to read a response from the module." msgstr "" -#: ../../en/units/lorawan_rui3.rst:128 7fa72d322ee447f7aeab991653a29428 +#: ../../en/units/lorawan_rui3.rst:128 a6b58f48a8f64288be4ab3035463dff5 msgid "Indicates if the command expects a single-line response." msgstr "" #: ../../en/units/lorawan_rui3.rst:129 ../../en/units/lorawan_rui3.rst:140 -#: 021f2bff1aa345038683c6180cf5ef6d 111fb126366542b0a35211784ccaa619 +#: 8c205a6cc28e4b24b136e7ba6850dee6 b6e50c1288914fd1aa74178c84c37d7b msgid "Specifies whether to handle asynchronous events." msgstr "" -#: ../../en/units/lorawan_rui3.rst:130 a0be74a7cd9c4dab900afd05bf53263d +#: ../../en/units/lorawan_rui3.rst:130 adeec836614345c594360327b8abd808 msgid "" "The timeout duration in milliseconds for receiving a response, default is" " 100ms." msgstr "" -#: ../../en/units/lorawan_rui3.rst:132 fa6fa503146946c9b0f99b7d6c771d68 +#: ../../en/units/lorawan_rui3.rst:132 797a861e732d42758890f5fa8e3e6a44 msgid "The processed response from the module or None if no response is expected." msgstr "" -#: ../../en/units/lorawan_rui3.rst:136 c20d4f02b2f149e7ac298697c7d3f86a +#: ../../en/units/lorawan_rui3.rst:136 5f8c1c45f5014786adcb169efbedab3b msgid "Reads and processes the module response to an AT command." msgstr "" -#: ../../en/units/lorawan_rui3.rst:138 1dca275c8a5347389d6cda6afd73a440 +#: ../../en/units/lorawan_rui3.rst:138 6fb5a7beaaab4d3cb7f73a552a9d5fe7 msgid "The AT command sent to the module." msgstr "" -#: ../../en/units/lorawan_rui3.rst:139 f94b3a1e3022450384750d642b5516d1 +#: ../../en/units/lorawan_rui3.rst:139 cf98db6bcf7c4538b3d1d41a551178c5 msgid "Indicates if a single-line response is expected." msgstr "" -#: ../../en/units/lorawan_rui3.rst:141 17c408e8a1544472ada52b08181fc6a4 +#: ../../en/units/lorawan_rui3.rst:141 b66062c5a3d34ef0b35c14d2b96e245b msgid "The timeout duration in milliseconds for receiving a response." msgstr "" -#: ../../en/units/lorawan_rui3.rst:143 197c5a2afe5041ea99efdb7118c68189 +#: ../../en/units/lorawan_rui3.rst:143 5bc31e7aa4ab4473b1ee3eebe201aab7 msgid "" "- True if the response is valid and matches a single-line expectation. - " "A processed response string when CMD+RESPONSE is received. - An event " @@ -466,107 +466,107 @@ msgid "" "False if no valid response is received or an error occurs." msgstr "" -#: ../../en/units/lorawan_rui3.rst:144 4509509d70bb4bed9df74785189c898c +#: ../../en/units/lorawan_rui3.rst:144 86316fcb93b546f9b46d09d6831e86b7 msgid "True if the response is valid and matches a single-line expectation." msgstr "" -#: ../../en/units/lorawan_rui3.rst:145 0634a062c3b149ea9900c1140940cd1d +#: ../../en/units/lorawan_rui3.rst:145 59e39ab135e448cda5ee7ae477f885c3 msgid "A processed response string when CMD+RESPONSE is received." msgstr "" -#: ../../en/units/lorawan_rui3.rst:146 1c53d3b6a2aa4ebf8b350bbd472ec2e4 +#: ../../en/units/lorawan_rui3.rst:146 9f65f7fb28a140d7bb7e919272186b56 msgid "" "An event response string if async_event is enabled and an event is " "detected." msgstr "" -#: ../../en/units/lorawan_rui3.rst:147 74bb659699e849c38aef7089121f8b9f +#: ../../en/units/lorawan_rui3.rst:147 88c36617c594495c9d0b06476f583535 msgid "False if no valid response is received or an error occurs." msgstr "" -#: ../../en/units/lorawan_rui3.rst:151 d7a1ea45ecf44041816d23c669dd096d +#: ../../en/units/lorawan_rui3.rst:151 0868c040b38f4892beeae1ebe7cd9abc msgid "" "Checks the communication state by sending the AT command and expecting a " "response." msgstr "" -#: ../../en/units/lorawan_rui3.rst:154 95e951f38ebf442a95f4c4e8df7bee46 +#: ../../en/units/lorawan_rui3.rst:154 b8ba631b8d844fbf934cacf254b151cb msgid "The response from the module indicating the communication state." msgstr "" -#: ../../en/units/lorawan_rui3.rst:158 65219ec57b8a4bef9e6f6852ff046a33 +#: ../../en/units/lorawan_rui3.rst:158 4dd7cc9b863643bba04fd787e8fb50e0 msgid "Resets the module using the ATZ command." msgstr "" -#: ../../en/units/lorawan_rui3.rst:162 b295059ea60d4475a9442187a557a9ac +#: ../../en/units/lorawan_rui3.rst:162 dc9b7ae257874d0ab43c0ce2e148f076 msgid "" "Resets the module to its default settings using the ATR command. Waits " "for the reset process to complete." msgstr "" -#: ../../en/units/lorawan_rui3.rst:167 5c8b687737a642e994e39072a60d7aef +#: ../../en/units/lorawan_rui3.rst:167 9ea95205d4384e14baa7be49b9f319d5 msgid "|reset_module_to_default.png|" msgstr "" -#: ../../en/refs/unit.lorawan_rui3.ref:22 4ebb8a4bbb4b4e6590cd2c556d71c76b +#: ../../en/refs/unit.lorawan_rui3.ref:22 3528474993f345bb80754bea0d7991e7 msgid "reset_module_to_default.png" msgstr "" -#: ../../en/units/lorawan_rui3.rst:171 f072e0a2f1ff472b8d665d292bda537c +#: ../../en/units/lorawan_rui3.rst:171 d0d5738c3593444581ada2033bc196d4 msgid "Retrieves the serial number of the module." msgstr "" -#: ../../en/units/lorawan_rui3.rst:173 7ca2c80eb2c443dc9fb7851a6249e518 +#: ../../en/units/lorawan_rui3.rst:173 5401c2f6dee547479d07c67574eeb67c msgid "The serial number as a string." msgstr "" -#: ../../en/units/lorawan_rui3.rst:177 cc5f1ae8f0fc4f689c7dd076f39e2a4a +#: ../../en/units/lorawan_rui3.rst:177 57be4aa2081b44b1b52695e2c22514b5 msgid "Retrieves the firmware version of the module." msgstr "" -#: ../../en/units/lorawan_rui3.rst:179 706e4972cca0479ca6a5844f610c393e +#: ../../en/units/lorawan_rui3.rst:179 44b9791aa70343daa7ac398c5c13c4c1 msgid "The firmware version as a string." msgstr "" -#: ../../en/units/lorawan_rui3.rst:183 6b75822c45d04cb29599b16fa73c23c2 +#: ../../en/units/lorawan_rui3.rst:183 0642f6e865074d1facd7a38b8d35b78c msgid "Retrieves the AT command version supported by the module." msgstr "" -#: ../../en/units/lorawan_rui3.rst:185 88f98ff8c1dc47faadf412a3ce541e07 +#: ../../en/units/lorawan_rui3.rst:185 8b314875cc9d422fa829d5c2e20b5cbc msgid "The AT command version as a string." msgstr "" -#: ../../en/units/lorawan_rui3.rst:189 0a8c7a83ab6c413e9fe2277092cebe1d +#: ../../en/units/lorawan_rui3.rst:189 76fa92509f6a4ab0b76393a88173ea9e msgid "Retrieves the hardware version of the module." msgstr "" -#: ../../en/units/lorawan_rui3.rst:191 cbb48cf92bcc4bd6989e66580ebeb2b6 +#: ../../en/units/lorawan_rui3.rst:191 52c357d0e7374506ac94be7f85cdfb45 msgid "The hardware version as a string." msgstr "" -#: ../../en/units/lorawan_rui3.rst:195 b085d35e4ee9440bad0357e78ea4716f +#: ../../en/units/lorawan_rui3.rst:195 e354d5293c264bf9907841b0686cd3cd msgid "Retrieves the hardware ID of the module." msgstr "" -#: ../../en/units/lorawan_rui3.rst:197 4dfeb006329f444c8d52026855c037bd +#: ../../en/units/lorawan_rui3.rst:197 56ec0fc296f24473994a712d73e075ee msgid "The hardware ID as a string." msgstr "" -#: ../../en/units/lorawan_rui3.rst:201 c1738fa96d5b4a02b7b416c380ef573c +#: ../../en/units/lorawan_rui3.rst:201 027ed54b765347018287a922ace6ecbb msgid "Retrieves the Bluetooth MAC address of the module." msgstr "" -#: ../../en/units/lorawan_rui3.rst:203 ab140b25f6ac48d89979afd810a65421 +#: ../../en/units/lorawan_rui3.rst:203 c9d034da16d54a389471ed882740e8f8 msgid "The BLE MAC address as a string." msgstr "" -#: ../../en/units/lorawan_rui3.rst:207 e945948730024e6bb85d4c2ef5fa36f1 +#: ../../en/units/lorawan_rui3.rst:207 934d43d65b8c4366ad445b01df4dfb3e msgid "" "Configures the sleep time for the module. If no time is provided, the " "module enters sleep mode." msgstr "" -#: ../../en/units/lorawan_rui3.rst:209 aac76b1fefaf4e9e8dbe449d339a33b0 +#: ../../en/units/lorawan_rui3.rst:209 09d967012aac4fd481c2a6269cba4456 msgid "" "The sleep duration in seconds. If None, triggers sleep mode without " "specifying a duration." @@ -575,218 +575,218 @@ msgstr "" #: ../../en/units/lorawan_rui3.rst:211 ../../en/units/lorawan_rui3.rst:225 #: ../../en/units/lorawan_rui3.rst:233 ../../en/units/lorawan_rui3.rst:253 #: ../../en/units/lorawan_rui3.rst:267 ../../en/units/lorawan_rui3.rst:281 -#: 010ceb8f5ea04633a5db5e74e6ae77ac 2d9cee42d0944fbc99b5ca4edb4b7f62 -#: 5410ed17c12e4ad9ae079eee4277a7f9 60c8af3e40e9499cae4406adbaab7256 -#: a6cff4f2463a4660ab3031bb5fe14099 afc8228578a7494dbef83c654de9a3d4 +#: 034b1de8dd7f459d9e5d64521fd81f53 9827055ac71f4099b81c7cda54385796 +#: b112b33e9bf449d8a5e6ad45162d771b b2b6aa395a8043589437921abfc8d824 +#: fd753272a8e34ee8821e75affe58db8e ff39976a3b4649ef9dc827814d76769b msgid "True if the command is successfully sent, else False." msgstr "" -#: ../../en/units/lorawan_rui3.rst:215 dd2ae15d282144728f15fae46c15b692 +#: ../../en/units/lorawan_rui3.rst:215 c23f76d067a944fab26ad567cd9f34fa msgid "Checks if the module is in low-power mode." msgstr "" -#: ../../en/units/lorawan_rui3.rst:217 3857b174dfcf4629b7222715390a29e8 +#: ../../en/units/lorawan_rui3.rst:217 6431eefbd1894696a22ce8d72e400060 msgid "True if low-power mode is enabled, otherwise False." msgstr "" -#: ../../en/units/lorawan_rui3.rst:221 494a1ea21f124ab895769955cbb58567 +#: ../../en/units/lorawan_rui3.rst:221 fdbf778b28394c6eb2a01ba35e42761d msgid "Sets the module low-power mode." msgstr "" -#: ../../en/units/lorawan_rui3.rst:223 c5773d20d8904318b7ae1c0fc3955e51 +#: ../../en/units/lorawan_rui3.rst:223 d136225fbb8f4dfaa490aa95b78adce0 msgid "" "A boolean value indicating whether to enable (True) or disable (False) " "low-power mode." msgstr "" -#: ../../en/units/lorawan_rui3.rst:229 d94ea90eba9c49c3880746e89d2b84ab +#: ../../en/units/lorawan_rui3.rst:229 6fb119003b9b47a2a653064122fba1b7 msgid "Sets the baud rate for UART communication." msgstr "" -#: ../../en/units/lorawan_rui3.rst:231 77f1a864fcba45f99e0f813cd4bf690f +#: ../../en/units/lorawan_rui3.rst:231 f0e039ff33554e2d8c26a1dbd226e874 msgid "The desired baud rate value." msgstr "" -#: ../../en/units/lorawan_rui3.rst:237 44e598b70c82418c8f53881611fc282b +#: ../../en/units/lorawan_rui3.rst:237 128f28b8b93d410c985cc9cb491988d7 msgid "Retrieves the current UART baud rate setting." msgstr "" -#: ../../en/units/lorawan_rui3.rst:239 507c3a0850c843448e11ae6d7b1d20b6 +#: ../../en/units/lorawan_rui3.rst:239 1d203e031e3d490a9a08d62469e10763 msgid "The baud rate as an integer." msgstr "" -#: ../../en/units/lorawan_rui3.rst:243 6a072ab084e1405cb01dd87b04c1130c +#: ../../en/units/lorawan_rui3.rst:243 8b0b6b6019e24ecb9b5cdebe602222ee msgid "Retrieves the Device EUI for OTAA (Over-The-Air Activation) mode." msgstr "" -#: ../../en/units/lorawan_rui3.rst:245 3b01cf38b61e4b28b789bf67bfd44f85 +#: ../../en/units/lorawan_rui3.rst:245 41b2a440b8f04df0b6bc902cec5771a3 msgid "The Device EUI as a string." msgstr "" -#: ../../en/units/lorawan_rui3.rst:249 2e7dc194422f4032896f29ee92d57244 +#: ../../en/units/lorawan_rui3.rst:249 0a90e798c4104bdd8fe2696f18f129b8 msgid "Configures the Device EUI for OTAA mode." msgstr "" -#: ../../en/units/lorawan_rui3.rst:251 51e6f2db3bc348639e5aaff3d2bf6854 +#: ../../en/units/lorawan_rui3.rst:251 853ed688da00454ca7b05fcac2b0cf54 msgid "The Device EUI to set, as a string." msgstr "" -#: ../../en/units/lorawan_rui3.rst:257 80624cff67ad41359c7a0551ead3ad7f +#: ../../en/units/lorawan_rui3.rst:257 4e312f07b0ec467fa8df1e28412b53ef msgid "Retrieves the Application EUI for OTAA mode." msgstr "" -#: ../../en/units/lorawan_rui3.rst:259 c5dbb0cc721d487d8ac9a51c25f12d11 +#: ../../en/units/lorawan_rui3.rst:259 9caf74ab9951498ca1af09a7e3bba66e msgid "The Application EUI as a string." msgstr "" -#: ../../en/units/lorawan_rui3.rst:263 7bd51992dc2148cfa1c72842ea976803 +#: ../../en/units/lorawan_rui3.rst:263 bd9696cae07d425187772a9d85911f62 msgid "Configures the Application EUI for OTAA mode." msgstr "" -#: ../../en/units/lorawan_rui3.rst:265 f9d1f317e33b4aa785f2414a35cf2860 +#: ../../en/units/lorawan_rui3.rst:265 73bce7c109144eda8bbb46a96e5b35dc msgid "The Application EUI to set, as a string." msgstr "" -#: ../../en/units/lorawan_rui3.rst:271 2ff2d330b6e740d8b5e70f334605f6aa +#: ../../en/units/lorawan_rui3.rst:271 a78f1b643c664eff85d832b260d0e1d4 msgid "Retrieves the Application Key for OTAA mode." msgstr "" -#: ../../en/units/lorawan_rui3.rst:273 eeab19cd32da4989b6c7e5739005ad05 +#: ../../en/units/lorawan_rui3.rst:273 514f8d77d01343f6a12dc711f5d134ef msgid "The Application Key as a string." msgstr "" -#: ../../en/units/lorawan_rui3.rst:277 86f4913ac3c743569916d5a18a73654f +#: ../../en/units/lorawan_rui3.rst:277 12f30ea373ee46898e57e0dc6db327fd msgid "Configures the Application Key for OTAA mode." msgstr "" -#: ../../en/units/lorawan_rui3.rst:279 445b064d5270454696d351bf12cc2f99 +#: ../../en/units/lorawan_rui3.rst:279 089b7f6150d04d8191a169d57b623ef5 msgid "The Application Key to set, as a string." msgstr "" -#: ../../en/units/lorawan_rui3.rst:285 a1da2962b7e5455794dd324e28eaa0cd +#: ../../en/units/lorawan_rui3.rst:285 0b269c9ecc82408cae50cdbd6a09b543 msgid "Retrieves the device address." msgstr "" -#: ../../en/units/lorawan_rui3.rst:287 310165da31b74f669130b75a73a8167a +#: ../../en/units/lorawan_rui3.rst:287 640a581cb06945e5ba3a76ee35fa6123 msgid "The device address as a string." msgstr "" -#: ../../en/units/lorawan_rui3.rst:291 52484fe9994b4602ba4e558eed2a785c +#: ../../en/units/lorawan_rui3.rst:291 ba4c4e7c3d7f4fa1a6ed426a0f93d446 msgid "Sets the device address." msgstr "" -#: ../../en/units/lorawan_rui3.rst:293 b5329e6182b44964970cac10579123a4 +#: ../../en/units/lorawan_rui3.rst:293 6198c3fcbfdf44448a2b4b34c12637ff msgid "The device address to set, provided as a string." msgstr "" -#: ../../en/units/lorawan_rui3.rst:295 9af153ccb6334115a70270800059e742 +#: ../../en/units/lorawan_rui3.rst:295 de758058d385477a9c8715184d3fc6f9 msgid "The application session key as a string." msgstr "" -#: ../../en/units/lorawan_rui3.rst:299 14f730124cd5497080a5d80c740e1414 +#: ../../en/units/lorawan_rui3.rst:299 0e2f90745e014ef8ac17e76b3d66f2e6 msgid "Retrieves the application session key." msgstr "" #: ../../en/units/lorawan_rui3.rst:301 ../../en/units/lorawan_rui3.rst:314 #: ../../en/units/lorawan_rui3.rst:322 ../../en/units/lorawan_rui3.rst:348 -#: 5561e60d22984da7b0dbfa73825dc112 57f1c60c1dd24ebeb3e8b69498ae2334 -#: ad71bca11d71432dbbc6f74b4068084c f6bb332a36da449f90e33bf0540c0357 +#: 0736b68fefa14826aa39362feee92dde 514abd3fe5854049b2b246223ea43239 +#: d09fb60a5c184ba7bbbd5d9a4dd35ce1 e9bf08308eb64d318a85b52fb2df423d msgid "The result of the AT command execution" msgstr "" -#: ../../en/units/lorawan_rui3.rst:304 f33c2dbccc05445ca8cc0aef6bfcca3f +#: ../../en/units/lorawan_rui3.rst:304 0e5685ebb29f445dbefa90057e20c40f msgid "" "Sets the application session key. This operation is applicable only in " "ABP mode." msgstr "" -#: ../../en/units/lorawan_rui3.rst:306 f696afdc67f941ab975c4995bd225817 +#: ../../en/units/lorawan_rui3.rst:306 1c313695709049feacb58ab160b12cca msgid "The application session key to set, provided as a string." msgstr "" -#: ../../en/units/lorawan_rui3.rst:308 54548b4f7fd843819c55b62f6f5cc2cc +#: ../../en/units/lorawan_rui3.rst:308 c3008ced4a7b48a4968ccd0acfcefe7f msgid "The network session key as a string." msgstr "" -#: ../../en/units/lorawan_rui3.rst:312 73c60e59d4da44a3b027066528fd32f7 +#: ../../en/units/lorawan_rui3.rst:312 235942f2fff4431fae94422ce130ba84 msgid "Retrieves the network session key." msgstr "" -#: ../../en/units/lorawan_rui3.rst:318 bc5532517c1f4d4da6436cc3c8deae2e +#: ../../en/units/lorawan_rui3.rst:318 912b598f66474245a66b226d26ae3b8a msgid "" "Sets the network session key. This operation is applicable only in ABP " "mode." msgstr "" -#: ../../en/units/lorawan_rui3.rst:320 bebba640754a4d42ba510015983d75da +#: ../../en/units/lorawan_rui3.rst:320 70ca93c6fe6e42a9b90459becfdd24b0 msgid "The network session key to set, provided as a string." msgstr "" -#: ../../en/units/lorawan_rui3.rst:326 b3f8760a704a4881b237dd036b35451b +#: ../../en/units/lorawan_rui3.rst:326 a6acd2a179304fcaa6c28cd0ad09ee77 msgid "Sets the network ID." msgstr "" -#: ../../en/units/lorawan_rui3.rst:328 d2ed07f6bb0349c0822067b8c7adafc7 +#: ../../en/units/lorawan_rui3.rst:328 9251b30ad72e4f52ad4f539ed05464dc msgid "The network ID to set, provided as a string." msgstr "" -#: ../../en/units/lorawan_rui3.rst:330 d939d5709bff4836b5f01d676204fe5b +#: ../../en/units/lorawan_rui3.rst:330 b720913132974a8b8bde681cf5219d87 msgid "The network ID as a string." msgstr "" -#: ../../en/units/lorawan_rui3.rst:334 2e1e54a2c207439ca97f5c0dde6a030b +#: ../../en/units/lorawan_rui3.rst:334 a14107e70c3847608a1aaa2e5ebeccbb msgid "Retrieves the network ID." msgstr "" -#: ../../en/units/lorawan_rui3.rst:336 0eb4c097ca3c4005b4640c1ad4f4e5d4 +#: ../../en/units/lorawan_rui3.rst:336 833513331cbc488eaf23c4f6cd2266be msgid "The multicast root key as a string." msgstr "" -#: ../../en/units/lorawan_rui3.rst:340 192a18a3912f4011a87c06700ca3aadc +#: ../../en/units/lorawan_rui3.rst:340 8b596392fcef4fb1b2a426ab37def1ca msgid "Retrieves the multicast root key." msgstr "" -#: ../../en/units/lorawan_rui3.rst:342 687632fcdfc64b77929c4774e1b7987f +#: ../../en/units/lorawan_rui3.rst:342 1c71fda418d24cfcad7af19e5a94abda msgid "True if confirmation mode is enabled; otherwise, False." msgstr "" -#: ../../en/units/lorawan_rui3.rst:346 396f8c8ad50044b18a842c669f4ebe40 +#: ../../en/units/lorawan_rui3.rst:346 6b5417d4a5354dd2b244e08fb998a7f9 msgid "Retrieves the confirmation mode." msgstr "" -#: ../../en/units/lorawan_rui3.rst:352 298d2a9e753145f290cda694992d7f8c +#: ../../en/units/lorawan_rui3.rst:352 657463cbdee34958ba2e99166630cea5 msgid "Sets the confirmation mode." msgstr "" -#: ../../en/units/lorawan_rui3.rst:354 eef3f1c736a54349b54a867c3f713542 +#: ../../en/units/lorawan_rui3.rst:354 592983a4525645b29f11592926a42fe2 msgid "" "A boolean indicating whether to enable (True) or disable (False) " "confirmation mode." msgstr "" -#: ../../en/units/lorawan_rui3.rst:356 1212b123e2a7487bacc1837210440895 +#: ../../en/units/lorawan_rui3.rst:356 19ef4ca5907f4686a9363daad37b15a2 msgid "True if the last confirmed uplink succeeded; otherwise, False." msgstr "" -#: ../../en/units/lorawan_rui3.rst:360 2d6b218e26914f9a8930545763b9efdd +#: ../../en/units/lorawan_rui3.rst:360 3f09030f899d4aeeab30f53ab80cd2ad msgid "Retrieves the status of the last confirmed uplink." msgstr "" -#: ../../en/units/lorawan_rui3.rst:362 6456ee4ccafa4b2d8752e6bbe9113d5a +#: ../../en/units/lorawan_rui3.rst:362 68130e61fc254233bfad9ce038d8fb07 msgid "" "A tuple containing state, auto_join, retry_interval, and max_retry as " "integers, or False if retrieval failed." msgstr "" -#: ../../en/units/lorawan_rui3.rst:366 23eb5f8b561e4693ae021f71dfeb6607 +#: ../../en/units/lorawan_rui3.rst:366 a6597dfac1254612bb538d085437d746 msgid "Retrieves the current join configuration for LoRa." msgstr "" -#: ../../en/units/lorawan_rui3.rst:368 d7a1cbcb203c4ca7b40ef8386506e4f2 +#: ../../en/units/lorawan_rui3.rst:368 efc8a0d7463f41b5be6e631c75d0b7ed msgid "True if the module successfully joins the network, otherwise False" msgstr "" -#: ../../en/units/lorawan_rui3.rst:372 7dcf2d7fdf1b4edab25937504adaa2ff +#: ../../en/units/lorawan_rui3.rst:372 5e193de7b51042edbe0ed09dded4e00f msgid "" "Configures the join parameters for LoRa. The configuration does not " "confirm network join success." @@ -795,252 +795,252 @@ msgstr "" #: ../../en/units/lorawan_rui3.rst:374 ../../en/units/lorawan_rui3.rst:393 #: ../../en/units/lorawan_rui3.rst:447 ../../en/units/lorawan_rui3.rst:472 #: ../../en/units/lorawan_rui3.rst:492 ../../en/units/lorawan_rui3.rst:506 -#: 1fab1380376e4c799e09807138433500 2b0592ecdb8c4b9384821ac7282fb766 -#: 3a190c9bd80d4c7f82fe83a22b16e675 4831ba00d3184dfb9eb74c7b372c98fb -#: 5717708d8370412a91415ced32830952 e14bf3f1c5904892998e34cd70c43478 +#: 3ac2f7a0cfa24a75bdf6c23f47d6425b 45ced881b57f4218a2aa698cbba73930 +#: 947fc59755c941369fe064f9fd9095ab b6f1299dafa148ba8cc667794eeaa763 +#: cf36f7ccc7bd4839b222c98f33bc4510 df0d224a2e794222b79902a994caea28 msgid "True if the command is successfully set, else False." msgstr "" -#: ../../en/units/lorawan_rui3.rst:375 4fa841d1892a4f1f9d8ce08ac5ea6643 +#: ../../en/units/lorawan_rui3.rst:375 5f495defec5b4227b4a5503158bc8128 msgid "The join state to configure, as an integer." msgstr "" -#: ../../en/units/lorawan_rui3.rst:376 8516cf7cef4343a4b6cd5204b89b11ed +#: ../../en/units/lorawan_rui3.rst:376 e7e91fef7d0143078a6e576d25c9629c msgid "The auto-join flag, as an integer." msgstr "" -#: ../../en/units/lorawan_rui3.rst:377 2cd2ecc27ba2454f82f946889e3962c1 +#: ../../en/units/lorawan_rui3.rst:377 3b84c90b5ca74582a3f4aff0881b58ca msgid "The interval between join retries, default is 8 seconds." msgstr "" -#: ../../en/units/lorawan_rui3.rst:378 e52748ee5f2c47adad688e32219cc5ac +#: ../../en/units/lorawan_rui3.rst:378 0916336ebdac45b59f721e8042475031 msgid "The maximum number of retries, default is 0 (no limit)." msgstr "" -#: ../../en/units/lorawan_rui3.rst:379 d81cc2c80df34e2989f23927968c7bd3 +#: ../../en/units/lorawan_rui3.rst:379 ff547bda4fef46ada345b9de6cfa8381 msgid "The timeout duration in milliseconds for the command, default is 8000ms." msgstr "" -#: ../../en/units/lorawan_rui3.rst:381 bbea134bee9f4bcdb75bb9dbb538489c +#: ../../en/units/lorawan_rui3.rst:381 8e205cfba8074cbbba0559b54c7a1669 msgid "The join mode as an integer (0 for ABP, 1 for OTAA)." msgstr "" -#: ../../en/units/lorawan_rui3.rst:385 2deee68ede4849b0b43a56772e033d89 +#: ../../en/units/lorawan_rui3.rst:385 f385250917e7467185999d457815008b msgid "|set_join_config.png|" msgstr "" -#: ../../en/refs/unit.lorawan_rui3.ref:24 720aced4840848e1baea606d27193770 +#: ../../en/refs/unit.lorawan_rui3.ref:24 578574527da34ee5b112aa786660f332 msgid "set_join_config.png" msgstr "" -#: ../../en/units/lorawan_rui3.rst:389 b5d93b7bbcbf444e966113bffe20422f +#: ../../en/units/lorawan_rui3.rst:389 ccfcd80d166e4479bc647958b92c4c60 msgid "Joins the LoRa network using predefined join parameters." msgstr "" -#: ../../en/units/lorawan_rui3.rst:391 71e4f1ea6fa8465fb2590111fabe40b3 +#: ../../en/units/lorawan_rui3.rst:391 d2c52bd326fb441e9d2b3d80092f9614 msgid "" "The timeout duration in milliseconds for the join command, default is " "8000ms." msgstr "" -#: ../../en/units/lorawan_rui3.rst:397 204140ccfaa24d0e820f722459ffe1a0 +#: ../../en/units/lorawan_rui3.rst:397 68fadfcff4904342b3e1bca409271fb8 msgid "|join_network.png|" msgstr "" -#: ../../en/refs/unit.lorawan_rui3.ref:25 7202cb0b295b4a54817ee6cc2b825c63 +#: ../../en/refs/unit.lorawan_rui3.ref:25 dfaf76d094174aef86ced2c8aa07af68 msgid "join_network.png" msgstr "" -#: ../../en/units/lorawan_rui3.rst:399 11edb7a1f33a43ee86791f266e575c3d +#: ../../en/units/lorawan_rui3.rst:399 85ca803dad7047f2800b56899582274d msgid "|join_network_return.png|" msgstr "" -#: ../../en/refs/unit.lorawan_rui3.ref:26 bc4b43a91e544f158a1d5a5196930c66 +#: ../../en/refs/unit.lorawan_rui3.ref:26 a3159f6528a74da08ecc42539b1616fc msgid "join_network_return.png" msgstr "" -#: ../../en/units/lorawan_rui3.rst:403 44f98092df60490cac0ad8a46cc05215 +#: ../../en/units/lorawan_rui3.rst:403 3597b2182e6f49ff8d4062e2ab4cd4a1 msgid "" "Retrieves the current join mode. 0 indicates ABP mode, 1 indicates OTAA " "mode." msgstr "" -#: ../../en/units/lorawan_rui3.rst:405 973e04223ef44cc6a483c970b2d4156c +#: ../../en/units/lorawan_rui3.rst:405 eda3d385b8e848a6b94def5479fbd63c msgid "True if joined, otherwise False." msgstr "" -#: ../../en/units/lorawan_rui3.rst:409 7cf9eb334c64478a9555c87f67ea3c14 +#: ../../en/units/lorawan_rui3.rst:409 80e0cfdc81d44a8697ab545f4407157d msgid "Sets the join mode for the LoRa module." msgstr "" -#: ../../en/units/lorawan_rui3.rst:411 30d3ae6ecfcf480cabfc13af2130b7c3 +#: ../../en/units/lorawan_rui3.rst:411 2a10bfbdd77940ffb82c773ec5fd1395 msgid "The join mode to set, 0 for ABP or 1 for OTAA." msgstr "" -#: ../../en/units/lorawan_rui3.rst:413 8e1b937f03ff4960a91d8587385e9eaf +#: ../../en/units/lorawan_rui3.rst:413 6b81502170434b35b6573eec613fa382 msgid "The received data as a string." msgstr "" -#: ../../en/units/lorawan_rui3.rst:417 87385b4bff1142758f82c1445056e33b +#: ../../en/units/lorawan_rui3.rst:417 5e9f108cb35d4bdf87d2e47b04f11643 msgid "|set_join_mode.png|" msgstr "" -#: ../../en/refs/unit.lorawan_rui3.ref:27 8f8b8517a8f04f1caa66f1d076c8c2c3 +#: ../../en/refs/unit.lorawan_rui3.ref:27 3fb7bcd7a0c0442a9bced2ba60f72538 msgid "set_join_mode.png" msgstr "" -#: ../../en/units/lorawan_rui3.rst:421 5a3fa52e72724102ba371577d791cffa +#: ../../en/units/lorawan_rui3.rst:421 9170eb0d7bac4fdc90dfda7ba4bb0b92 msgid "" "Checks whether the module has successfully joined the network. 1 " "indicates joined, 0 indicates not joined." msgstr "" #: ../../en/units/lorawan_rui3.rst:423 ../../en/units/lorawan_rui3.rst:433 -#: 0b2088ae73694664b6528515acb22d08 a6b3d705b8ac4619a4ee5170935ff8ee +#: 104f2818022b4cf2b384749db8c4c711 7682b7da9bb3474fa6c870fd1ece0d61 msgid "True if the data was sent successfully, otherwise False." msgstr "" -#: ../../en/units/lorawan_rui3.rst:427 3cc9188c58404607940ba681ee9ebe9c +#: ../../en/units/lorawan_rui3.rst:427 7a42ae072e65455b94e78a2c5930d25e msgid "|get_join_state.png|" msgstr "" -#: ../../en/refs/unit.lorawan_rui3.ref:28 b4a0055914f84a7bb533d767a3c78feb +#: ../../en/refs/unit.lorawan_rui3.ref:28 e2dc069233b64848b848c09cf6b25dbf msgid "get_join_state.png" msgstr "" -#: ../../en/units/lorawan_rui3.rst:431 b6284db8c4634ad2a0f632686d984c04 +#: ../../en/units/lorawan_rui3.rst:431 9d4ce2ba4598410c8ed62bec02f02a15 msgid "Retrieves the data from the last received message." msgstr "" -#: ../../en/units/lorawan_rui3.rst:437 aceca32efa064ec299052f13a6001bcb +#: ../../en/units/lorawan_rui3.rst:437 3ded6ce34a134781b9000655937dadd2 msgid "|get_last_receive.png|" msgstr "" -#: ../../en/refs/unit.lorawan_rui3.ref:29 a390e74493d4478c8a700b30f3beca61 +#: ../../en/refs/unit.lorawan_rui3.ref:29 a905fb94c17e4e72bb6d584c87607162 msgid "get_last_receive.png" msgstr "" -#: ../../en/units/lorawan_rui3.rst:441 fe0d6eefa1a74385b0eb4a95dbc32186 +#: ../../en/units/lorawan_rui3.rst:441 caaa356a967b407398aba047960aaa24 msgid "Sends data through a specific port." msgstr "" #: ../../en/units/lorawan_rui3.rst:443 ../../en/units/lorawan_rui3.rst:459 -#: 32c1a99e0a26479e8781dfabf8ff8cd9 5e581bc42b6c4d0a86494e60860c80d9 +#: 81b8dc09c956435b9b323f71701d83eb bada990ecfe74a6383187e6ce3eb9095 msgid "The port number to send data through." msgstr "" -#: ../../en/units/lorawan_rui3.rst:444 68eeb3031da1407886f350c2b8c4710c +#: ../../en/units/lorawan_rui3.rst:444 e02a89963f9345278dbdadea97ab2144 msgid "The data to send, provided as bytes." msgstr "" -#: ../../en/units/lorawan_rui3.rst:445 3c126da48b774be38b7d24bfa32c062a +#: ../../en/units/lorawan_rui3.rst:445 a960c49ca574483da1e07f5b41cc44b4 msgid "" "The timeout duration in milliseconds for the send command, default is " "600ms." msgstr "" -#: ../../en/units/lorawan_rui3.rst:451 f6b1eeeaf30c40d2a0f8f95e24d52109 +#: ../../en/units/lorawan_rui3.rst:451 c74df539dcb64b60afce9451ffe05079 msgid "|send_data.png|" msgstr "" -#: ../../en/refs/unit.lorawan_rui3.ref:30 734ca0039b2c4b93b7500847336a9f87 +#: ../../en/refs/unit.lorawan_rui3.ref:30 887af6503ae648ae9e5c8c26484febef msgid "send_data.png" msgstr "" -#: ../../en/units/lorawan_rui3.rst:453 6de48c83ff914627b710b313e9f23797 +#: ../../en/units/lorawan_rui3.rst:453 8ca65c28aa194b0195bab79416a582e1 msgid "|send_data_return.png|" msgstr "" -#: ../../en/refs/unit.lorawan_rui3.ref:31 82dbf07dc02e4f14948dc18c859a5dfa +#: ../../en/refs/unit.lorawan_rui3.ref:31 18afc11c2bd74590992edd88c90db2d0 msgid "send_data_return.png" msgstr "" -#: ../../en/units/lorawan_rui3.rst:457 5800e553953f4dba8e208d0de9b59304 +#: ../../en/units/lorawan_rui3.rst:457 ca1d63a8893044eab0c53b1e471a3d2c msgid "Sends long data through a specific port with optional acknowledgment." msgstr "" -#: ../../en/units/lorawan_rui3.rst:460 8f9ca14d91f7447ba40db321c6bbaedf +#: ../../en/units/lorawan_rui3.rst:460 ec6b99da9d0d45b78570ab5ef3a3b45a msgid "Indicates whether acknowledgment is required (True or False)." msgstr "" -#: ../../en/units/lorawan_rui3.rst:461 f2c9211cc1fc436cb5166cde01ba3ca6 +#: ../../en/units/lorawan_rui3.rst:461 a866d7f675734877a587ed89c98d7fed msgid "The long data to send, provided as bytes." msgstr "" -#: ../../en/units/lorawan_rui3.rst:462 eea4ff97000e478e892e1b37ded86b3d +#: ../../en/units/lorawan_rui3.rst:462 5bd82ba978c544319a8a9494fd5ff82e msgid "" "The timeout duration in milliseconds for the send command, default is " "500ms." msgstr "" #: ../../en/units/lorawan_rui3.rst:464 ../../en/units/lorawan_rui3.rst:478 -#: b72d6cd01ac24652b561cd3839cea5b7 ef7ee1b89a774e8e979f24d0cc12719e +#: 96db61ace7ab4fc0b9ebf5fab9535851 c799d91b20b346bd9698badbeae1028b msgid "The retry cycle value as an integer." msgstr "" -#: ../../en/units/lorawan_rui3.rst:468 99539dd72f3b46958c4107beb615b305 +#: ../../en/units/lorawan_rui3.rst:468 abf709e895f64e438df89a3deddb6962 msgid "Configures the retry cycle for transmissions." msgstr "" -#: ../../en/units/lorawan_rui3.rst:470 43fe94912e854a5ba9a770aa80044f39 +#: ../../en/units/lorawan_rui3.rst:470 6f95d9d920c54734a479c686778f7f90 msgid "The retry cycle value, ranging from 0 to 7, default is 0." msgstr "" -#: ../../en/units/lorawan_rui3.rst:476 38d87a5df58941c086b823e25bc35799 +#: ../../en/units/lorawan_rui3.rst:476 02f206e00bb1456d8547b7d0f2b11ae7 msgid "Retrieves the current retry cycle configuration." msgstr "" -#: ../../en/units/lorawan_rui3.rst:482 ef39bd5f0b4a4d748816d4cf4b8f7dbb +#: ../../en/units/lorawan_rui3.rst:482 c8a4cb0f23aa439fa807f1600cf74ce4 msgid "" "Checks whether adaptive data rate (ADR) is enabled. 1 indicates enabled, " "0 indicates disabled." msgstr "" -#: ../../en/units/lorawan_rui3.rst:484 9d5a6e903feb420cb1011c2bc17e1dde +#: ../../en/units/lorawan_rui3.rst:484 2dd1498f53294ffa9a2e195a8879cd05 msgid "True if ADR is enabled, otherwise False." msgstr "" -#: ../../en/units/lorawan_rui3.rst:488 0baa081a392349bc87ebc43e18400ee1 +#: ../../en/units/lorawan_rui3.rst:488 4f299c6bacd547ceba2aad6ad32e5599 msgid "Configures the adaptive data rate (ADR) state." msgstr "" -#: ../../en/units/lorawan_rui3.rst:490 f9b00ace84fc4a52bb5a199fe13fd2d6 +#: ../../en/units/lorawan_rui3.rst:490 2bdc28a61b2949d98c1c37a006af835d msgid "A boolean indicating whether to enable (True) or disable (False) ADR." msgstr "" -#: ../../en/units/lorawan_rui3.rst:496 d8d9d919bae048d19739890cb37371f7 +#: ../../en/units/lorawan_rui3.rst:496 5a05d97e22294c52836b730772b1fb32 msgid "Retrieves the current LoRaWAN node class." msgstr "" -#: ../../en/units/lorawan_rui3.rst:498 4a2d1a97af3c466e9ff76674cf1739f1 +#: ../../en/units/lorawan_rui3.rst:498 b9d670c59b294e6fa89bcf2aa6f50318 msgid "The LoRaWAN node class as a string." msgstr "" -#: ../../en/units/lorawan_rui3.rst:502 97a06fb1b243477cb35d10c6c38abc60 +#: ../../en/units/lorawan_rui3.rst:502 1a37fcf23d4b444d9ad587d93bdd54d1 msgid "Sets the LoRaWAN node class." msgstr "" -#: ../../en/units/lorawan_rui3.rst:504 cb07ba6cf0aa42c0b4ae036fa7de9858 +#: ../../en/units/lorawan_rui3.rst:504 6522b89354ac424a96f741e88d11b70b msgid "The node class to set, provided as a string" msgstr "" -#: ../../en/units/lorawan_rui3.rst:510 2f195307a7654d01b578bb66f03d087a +#: ../../en/units/lorawan_rui3.rst:510 7936f19b9ff6420fb3541e3fff8ac148 msgid "" "Checks whether the ETSI duty cycle is enabled. 1 indicates enabled, 0 " "indicates disabled." msgstr "" -#: ../../en/units/lorawan_rui3.rst:512 c2faa237f93c45b58bd5082e19dc5950 +#: ../../en/units/lorawan_rui3.rst:512 aeedbdea775d48c595564268cef160b1 msgid "True if the duty cycle is enabled, otherwise False." msgstr "" -#: ../../en/units/lorawan_rui3.rst:516 99d091a158334a0fb0d94f3379660023 +#: ../../en/units/lorawan_rui3.rst:516 e3396a289b2442d199e8975fdd6b317b msgid "" "Sets the ETSI duty cycle state, which can be enabled or disabled " "depending on the region." msgstr "" -#: ../../en/units/lorawan_rui3.rst:518 7f9d54bde3e24427beb990929b3343a7 +#: ../../en/units/lorawan_rui3.rst:518 4e9f1ebeef114a9c9b8bd0c06d214abe msgid "" "A boolean indicating whether to enable (True) or disable (False) the duty" " cycle." @@ -1060,383 +1060,383 @@ msgstr "" #: ../../en/units/lorawan_rui3.rst:928 ../../en/units/lorawan_rui3.rst:950 #: ../../en/units/lorawan_rui3.rst:975 ../../en/units/lorawan_rui3.rst:998 #: ../../en/units/lorawan_rui3.rst:1021 ../../en/units/lorawan_rui3.rst:1044 -#: ../../en/units/lorawan_rui3.rst:1063 0079ca9fadd94e208dc7e048c13fa339 -#: 09f04c1f35bf4c8c93938f2a7518a4df 250e8e75d3ac4d03b4a72eeafcdb210f -#: 25de34033ba24d81a99bbd8a0983269c 273c5cf676b04d5897a1a870422d4591 -#: 29ee880a05fe479594bc5edf62422fd8 31174af6fb274fff874bd5888720895a -#: 3e093a89bc68414d91c3f9e1195bf241 4825de029b4e4280984f359fd9c0d545 -#: 4ea4eee6d83a4c099b92440706c6f4fc 5c9e957407134b1d93af1e2ea51bca42 -#: 6300fb7027274476bf9cb74b8f646609 67f5acc4d99c418aa9e18b2bdd534d6a -#: 740b3c20f2034b969c1f5cbb2d193dae 745146dd7f084a1daa21cfd3dbe91a9d -#: 7a888d1d9ad24cdea62f45b0454b833f 7bf0b13b5e764ee6b920892a1bcc9fa5 -#: 7f3f2f1e22de41c595690775d4753f8e 848ae6a7b4474db8a9bec12eb4acdaf8 -#: 9f1753b9ae8b4d00b78334598e9ba950 a154f0c9db704935874b5941a218bd9a -#: ba0d6a95b42649c9ac9a52a8bbccc9b4 d8a8d6bade2b4f3f832eeafbe9b1c29e -#: e2f9bc4e01304f49a5f42985275a55e0 e3f49506a052411980f1c6bc1c902dca -#: e7e84dff5b1843f3a6d263ef150d3974 f534bb31ee124a0b880972df3deefad7 -#: f7825f4e9cc548b081f8b37f0f4d813b fe6c30791a264802b326076eeb464b32 +#: ../../en/units/lorawan_rui3.rst:1063 0236570a80974311a6215fad4bc31541 +#: 17a065fa142a455293df40acded729f2 2e31d4fd32744877aa4edaed2b350bb3 +#: 302ce147f9964b079d1ff6d6c4367073 374ce075bd4d49ec9a22dfe487b892ab +#: 3a8f612ba1ad4d458a586dda2fc0761d 4ad3e245a1ae4e39985ef6fe8a87120e +#: 5e857c4dd155478db5daf5e54aa72b6a 666d17fc20be441ab7910b01dfb0c51a +#: 7322441d7a4c4464bbd337daddd3bd66 76924c1278834fd9bfec56f2c584f2e0 +#: 7cc93e472e6e4ea986e50d8aff9ef118 7ef2b7d64e0f45fe9a40bb07c2037549 +#: 820d97bb2be9420b978f16b28b8abefb 881c8f48142c42aa9a63bc9b7fca0593 +#: 9f6351c6cc7a4e93b9e9bb74fe05bbf5 af0460ff6b2240e78eab12142030023f +#: b78152a98c7147d38f23503324783bd2 ba35e4608f5344edacf9f3d0e9d4c7b2 +#: c0007b35a3624602ac8e85ddc88248ea cbc79cfe56e44efba04ae835c8130349 +#: ce014ec006dc4703baff302ccb8158bb cea90edceb39482d9023d6288524ed47 +#: da432c8fabd14fab8160811f04ce30b6 de8e05c0e990417b872e64cef4895c5f +#: de934d32b11f47e88a9cbfaf53dfbfce e69a379cbfcf4a7e98eadb74f5c5484e +#: f6f5692aadc447f2b60972ba3ffcbb57 fce3062d71b341469b0f5b84bb67d75f msgid "The result of the AT command execution." msgstr "" -#: ../../en/units/lorawan_rui3.rst:524 a3492d255a1e49d1a0d3a05f60938c7d +#: ../../en/units/lorawan_rui3.rst:524 6bea6da8c6bf454f8cba5abf0f79b983 msgid "Retrieves the current data rate configuration." msgstr "" -#: ../../en/units/lorawan_rui3.rst:526 7ea09801defc4c1abbe21ac1b5ace815 +#: ../../en/units/lorawan_rui3.rst:526 0a573198be624ddc99783b77c583edb6 msgid "The data rate as an integer." msgstr "" -#: ../../en/units/lorawan_rui3.rst:530 5c93b25a107d4d669603c0769cc2aff2 +#: ../../en/units/lorawan_rui3.rst:530 f66a1dcd7b1546988d7748e14ed964b5 msgid "Sets the data rate for communication." msgstr "" -#: ../../en/units/lorawan_rui3.rst:532 e221104eaae94c90ac86a167d4ca22bc +#: ../../en/units/lorawan_rui3.rst:532 39416fd0042b4f5bb3f576008fb76295 msgid "The data rate to set, provided as an integer." msgstr "" -#: ../../en/units/lorawan_rui3.rst:538 7a20a8f185074dc591e06f058ae2400c +#: ../../en/units/lorawan_rui3.rst:538 5fa7f4ed5ade4f50a34ee5c79f6a9531 msgid "Retrieves the delay for the join procedure on window 1." msgstr "" -#: ../../en/units/lorawan_rui3.rst:540 a5816cc13fe44b6e80acdc59843fdff1 +#: ../../en/units/lorawan_rui3.rst:540 6c96c3027b4b4be29ec1c70810ccaf43 msgid "The join delay for window 1 as an integer." msgstr "" -#: ../../en/units/lorawan_rui3.rst:544 db7219a1784d4c06a14dc7c15fd41354 +#: ../../en/units/lorawan_rui3.rst:544 5ba913a2c1ec4861b2b185fe10ae3976 msgid "Sets the join delay for window 1." msgstr "" -#: ../../en/units/lorawan_rui3.rst:546 1a62e167abc14bc2b54938781df9ccdb +#: ../../en/units/lorawan_rui3.rst:546 057955e835934ccf80f74054e3edbe1c msgid "The join delay to set for window 1 as an integer." msgstr "" -#: ../../en/units/lorawan_rui3.rst:552 f23ac9dbdd4a4eee8658d81cd5131868 +#: ../../en/units/lorawan_rui3.rst:552 8c840b4033664011978e09e6bccbc8f6 msgid "Retrieves the delay for the join procedure on window 2." msgstr "" -#: ../../en/units/lorawan_rui3.rst:554 41d22a9cedea4b13ab19cad2f1d931c7 +#: ../../en/units/lorawan_rui3.rst:554 d1afe727cf7942ca9c5ea1039bbbc70e msgid "The join delay for window 2 as an integer." msgstr "" -#: ../../en/units/lorawan_rui3.rst:558 20126078e74f4bfcb9f5d769b4ab2617 +#: ../../en/units/lorawan_rui3.rst:558 b58721484c754075893d4e7815ce6032 msgid "Sets the join delay for window 2." msgstr "" -#: ../../en/units/lorawan_rui3.rst:560 d06743fb6a6a4af9b625477194d40b37 +#: ../../en/units/lorawan_rui3.rst:560 595b1060b2dc4b20be4c1cbb9b5dfdb8 msgid "The join delay to set for window 2 as an integer." msgstr "" -#: ../../en/units/lorawan_rui3.rst:566 9ce5f37d953546f7bf6d3c93ace4b116 +#: ../../en/units/lorawan_rui3.rst:566 cc31b05b482949a0aff2f323980b9f27 msgid "Retrieves the public network mode (enabled or disabled)." msgstr "" -#: ../../en/units/lorawan_rui3.rst:568 86293e9978dd47f9b50ef8fcd53efe58 +#: ../../en/units/lorawan_rui3.rst:568 e05d93f963c949338dba682c06c3c237 msgid "True if public network mode is enabled, False otherwise." msgstr "" -#: ../../en/units/lorawan_rui3.rst:572 2484dea46a304e11beec2f5f8d98fb6a +#: ../../en/units/lorawan_rui3.rst:572 580e5507e386409d94f218e22163b6c4 msgid "Sets the public network mode to enabled or disabled." msgstr "" -#: ../../en/units/lorawan_rui3.rst:574 de7869faa5dd4513bb1b97cf52527916 +#: ../../en/units/lorawan_rui3.rst:574 deaee083cd584deb99f1120f6faa33bd msgid "" "A boolean indicating whether to enable (True) or disable (False) the " "public network mode." msgstr "" -#: ../../en/units/lorawan_rui3.rst:580 12c2f29285d149ccaeb0233c501a4e92 +#: ../../en/units/lorawan_rui3.rst:580 fc7231d2114349eb89f1d0df5479ceab msgid "Retrieves the receive window 1 delay." msgstr "" -#: ../../en/units/lorawan_rui3.rst:582 8b9a5a156de448d98058a5e8abd62d28 +#: ../../en/units/lorawan_rui3.rst:582 5a6347d21cfd413db666b07faf9e1f90 msgid "The receive window 1 delay as an integer." msgstr "" -#: ../../en/units/lorawan_rui3.rst:586 5ea789667dcc4fcf83a4b8a4d3978ee3 +#: ../../en/units/lorawan_rui3.rst:586 57d750d08e5a4f8a98adb3f1ebbb2116 msgid "Sets the receive delay for window 1." msgstr "" -#: ../../en/units/lorawan_rui3.rst:588 403c06191bca4bdca09d5aaa889842d3 +#: ../../en/units/lorawan_rui3.rst:588 de5c3387cd9e4b259820244995644293 msgid "The delay to set for receive window 1 as an integer." msgstr "" -#: ../../en/units/lorawan_rui3.rst:594 9c6fc70abdeb4e31baed8aafad8b6852 +#: ../../en/units/lorawan_rui3.rst:594 7718d9938d5740d2bb472fe117c60294 msgid "Retrieves the receive window 2 delay." msgstr "" -#: ../../en/units/lorawan_rui3.rst:596 c3fb6ef62bba4ab4b74e6d57453a9e94 +#: ../../en/units/lorawan_rui3.rst:596 95a18f557da04ca9aec740339e9d0798 msgid "The receive window 2 delay as an integer." msgstr "" -#: ../../en/units/lorawan_rui3.rst:600 ad4920aac6e34ad285cdec30b94639f5 +#: ../../en/units/lorawan_rui3.rst:600 00b12a3e614c442f82df9ae697f7c66e msgid "Sets the receive delay for window 2." msgstr "" -#: ../../en/units/lorawan_rui3.rst:602 ecb1d2ac30e74ee6a6255cc8bb4dc5db +#: ../../en/units/lorawan_rui3.rst:602 4ad33aeb0e2f4182a4685d063ecad4c9 msgid "The delay to set for receive window 2, with a range of 2 to 16." msgstr "" -#: ../../en/units/lorawan_rui3.rst:608 d67fd9d7460547309cdb2da2ccf7bb4c +#: ../../en/units/lorawan_rui3.rst:608 d13b05cdda1e41f88fe87e8bc2b34373 msgid "Retrieves the receive data rate for window 2." msgstr "" -#: ../../en/units/lorawan_rui3.rst:610 ede2f23d2a0e4222b9c0eea05cb23c3f +#: ../../en/units/lorawan_rui3.rst:610 d14725fb92d3431abfb2a8181d3e9200 msgid "The receive data rate for window 2 as an integer." msgstr "" -#: ../../en/units/lorawan_rui3.rst:614 be963a8e4b804d5b9f3114b0c216e35d +#: ../../en/units/lorawan_rui3.rst:614 95b779c683344ed78f53cbf3fa5a32aa msgid "Sets the receive data rate for window 2." msgstr "" -#: ../../en/units/lorawan_rui3.rst:616 8f78500a3ade43f49b9f7a102ecdef81 +#: ../../en/units/lorawan_rui3.rst:616 69df19b77766451693281eb48ba0caae msgid "The data rate to set for window 2." msgstr "" -#: ../../en/units/lorawan_rui3.rst:622 4a7bb66c106043b2949086538ba32308 +#: ../../en/units/lorawan_rui3.rst:622 207ce1ab3b3b4da886c3252c09533704 msgid "" "Retrieves the receive frequency for window 2, based on regional frequency" " settings." msgstr "" -#: ../../en/units/lorawan_rui3.rst:624 c00c008eed284873bfa8df84721cdade +#: ../../en/units/lorawan_rui3.rst:624 e0dae8b9d21c45ac852da3bf3ac9ff52 msgid "The receive frequency for window 2 as an integer." msgstr "" -#: ../../en/units/lorawan_rui3.rst:628 fc5e8b215a1f49e6858e2eb6c15002dc +#: ../../en/units/lorawan_rui3.rst:628 f6de3065c9bc4f7ba9317cc61676c61c msgid "Retrieves the current transmit power setting." msgstr "" -#: ../../en/units/lorawan_rui3.rst:630 5c478d2b953e4d699c88f9fa6762b487 +#: ../../en/units/lorawan_rui3.rst:630 d9cca23ce33140e69b4fc3e6ee9c4691 msgid "The transmit power setting as an integer." msgstr "" -#: ../../en/units/lorawan_rui3.rst:634 e48e70b6416e4ac3b0cee4fc909046fa +#: ../../en/units/lorawan_rui3.rst:634 63160ce6b64d4dbaa9d569451fa7aa5c msgid "Sets the transmit power." msgstr "" -#: ../../en/units/lorawan_rui3.rst:636 66ae97f234c64a0d8f604b2a64f342c1 +#: ../../en/units/lorawan_rui3.rst:636 91ce2ea754f24e8f8e9a14e583e41e85 msgid "The transmit power to set." msgstr "" -#: ../../en/units/lorawan_rui3.rst:642 afe088cf84464b27a846d94b17585973 +#: ../../en/units/lorawan_rui3.rst:642 28966cee1cdb4808b28fc5ada8f3994c msgid "Retrieves the network link state." msgstr "" -#: ../../en/units/lorawan_rui3.rst:644 eb5a8bda73e849d5ba21f1e871ca1a51 +#: ../../en/units/lorawan_rui3.rst:644 6f90649fa31c46d58a80af613929c10a msgid "The network link state as a string." msgstr "" -#: ../../en/units/lorawan_rui3.rst:648 14b10e571c3046a18f33ec4b1137f69d +#: ../../en/units/lorawan_rui3.rst:648 58f9d0f26d5f4b97bb6659dd3972e7f7 msgid "" "Sets the network link state for the device. 0 - Disable Link Check 1 - " "Execute Link Check once on the next payload uplink 2 - Automatically " "execute Link Check after every payload uplink" msgstr "" -#: ../../en/units/lorawan_rui3.rst:657 8f91d53da6c0422fb635b37f07dd2a44 +#: ../../en/units/lorawan_rui3.rst:657 1d4fa131523245f59a228a045f1e3ef5 msgid "Retrieves the Listen Before Talk (LBT) state." msgstr "" -#: ../../en/units/lorawan_rui3.rst:659 fcd78c9be8cc48d998102a0e942edc6c +#: ../../en/units/lorawan_rui3.rst:659 65e98fb7ef444ea1bb80c8e2d536698a msgid "True if LBT is enabled, False if not." msgstr "" -#: ../../en/units/lorawan_rui3.rst:663 fce26bf754264479beb2021c0f06f4f5 +#: ../../en/units/lorawan_rui3.rst:663 d418d27e27dd4b2bb9e27d5ab6a17f37 msgid "Sets the Listen Before Talk (LBT) state." msgstr "" -#: ../../en/units/lorawan_rui3.rst:665 f5d1bf640ca6480a9dfe723b4a4a55af +#: ../../en/units/lorawan_rui3.rst:665 ba89ee571e814b56a960b74d6bddb577 msgid "The state to set for LBT (True for enabled, False for disabled)." msgstr "" -#: ../../en/units/lorawan_rui3.rst:671 39c76801526e44f7bc630adf70a812a6 +#: ../../en/units/lorawan_rui3.rst:671 e2891c41d5a54de380ebef40d826f8d3 msgid "Sets the RSSI threshold for Listen Before Talk (LBT)." msgstr "" -#: ../../en/units/lorawan_rui3.rst:673 177f3c712cde4e3b97303feca1dda33f +#: ../../en/units/lorawan_rui3.rst:673 4ff9c1079d1941f3b82d8954b15023c9 msgid "The RSSI threshold to set for LBT." msgstr "" -#: ../../en/units/lorawan_rui3.rst:679 fc89921b27e545ba9b9573185be863fd +#: ../../en/units/lorawan_rui3.rst:679 a23a0148b37f4f0a81cd6747b1332554 msgid "Retrieves the RSSI threshold for Listen Before Talk (LBT)." msgstr "" -#: ../../en/units/lorawan_rui3.rst:681 b162677d0e49400786008ba7d40dd33e +#: ../../en/units/lorawan_rui3.rst:681 efe61bb452fb4bf5b7a81e9d98a2cd0a msgid "The RSSI threshold as an integer." msgstr "" -#: ../../en/units/lorawan_rui3.rst:685 bdb2fab86d114e4fae47f5ec491a560e +#: ../../en/units/lorawan_rui3.rst:685 fa222d0e255a49b39f762ae0c9f2d395 msgid "Retrieves the scan time for Listen Before Talk (LBT)." msgstr "" -#: ../../en/units/lorawan_rui3.rst:687 3dda4817a12e457ea144e12a9f95d7b6 +#: ../../en/units/lorawan_rui3.rst:687 de6252b484084cabbee992fea9befc86 msgid "The scan time for LBT as an integer." msgstr "" -#: ../../en/units/lorawan_rui3.rst:691 a7e3e9d1c5da4da2a1e34869603f1c17 +#: ../../en/units/lorawan_rui3.rst:691 5b5c976b46e84311b6c7ba3c246cb222 msgid "Sets the scan time for Listen Before Talk (LBT)." msgstr "" -#: ../../en/units/lorawan_rui3.rst:693 a5732f2b1bb34544bc939940e1dfecdc +#: ../../en/units/lorawan_rui3.rst:693 f7d02569e19540a590e93e150058eedc msgid "The scan time to set for LBT." msgstr "" -#: ../../en/units/lorawan_rui3.rst:699 28d136cf3b3b4bf79c096d980dc9974e +#: ../../en/units/lorawan_rui3.rst:699 9af7a18ace6347d5bf5caa669a60d572 msgid "Retrieves the time request state." msgstr "" -#: ../../en/units/lorawan_rui3.rst:701 9c04e98c387c41d0a5a1801e81bec3db +#: ../../en/units/lorawan_rui3.rst:701 3e40a1f53a384e1585a4357b3b58dd83 msgid "The time request state as an integer." msgstr "" -#: ../../en/units/lorawan_rui3.rst:705 335f452c36684fd389428f8254a1e177 +#: ../../en/units/lorawan_rui3.rst:705 35864133b24d4b2985941e0140136065 msgid "Sets the time request state." msgstr "" -#: ../../en/units/lorawan_rui3.rst:707 dd3dde364d174c2cae8f312755ecac3a +#: ../../en/units/lorawan_rui3.rst:707 7857d8ec87ab4765bec6e3caa212fc83 msgid "The state to set for time request (True to enable, False to disable)." msgstr "" -#: ../../en/units/lorawan_rui3.rst:713 eecc0b2cd8864e93b7967fbb4a180e57 +#: ../../en/units/lorawan_rui3.rst:713 e4b3aac3bbcc4c27b0ae38cc4b5fad33 msgid "Retrieves the location time in UTC+0." msgstr "" -#: ../../en/units/lorawan_rui3.rst:715 973ce522f8c94b8c92d6a9d346376852 +#: ../../en/units/lorawan_rui3.rst:715 8af00aed3ee54f20822c81070ad9f82e msgid "The location time in UTC+0 as a string." msgstr "" -#: ../../en/units/lorawan_rui3.rst:719 61eef2c807c84fba8d4e0c69c1f0059b +#: ../../en/units/lorawan_rui3.rst:719 9040e91b4ce640ecbdff06ec3bee9ebf msgid "Retrieves the unicast ping interval in Class B mode." msgstr "" -#: ../../en/units/lorawan_rui3.rst:721 c92e4fe7deca4638af863dec4fd81cec +#: ../../en/units/lorawan_rui3.rst:721 7758586034924701bc0bb928dfcc3e75 msgid "The unicast ping interval as an integer." msgstr "" -#: ../../en/units/lorawan_rui3.rst:725 d8b923bc26c042078b55f15b4a7db10c +#: ../../en/units/lorawan_rui3.rst:725 5a475089f0b7463ebb439f26fbb51a25 msgid "Sets the unicast ping interval for the device." msgstr "" -#: ../../en/units/lorawan_rui3.rst:727 3154ed85690848ef9724497baece42dd +#: ../../en/units/lorawan_rui3.rst:727 ed86f5cdd4cd4edc97d406b45cbd79f1 msgid "" "The interval to set for the unicast ping (0 to 7). 0 means approximately " "every second during the beacon window, and 7 means every 128 seconds " "(maximum ping period)." msgstr "" -#: ../../en/units/lorawan_rui3.rst:733 05a08dfe11ee4961ba22c86cc0031346 +#: ../../en/units/lorawan_rui3.rst:733 72e39897d4694ba8bdde32775ed9169b msgid "Retrieves the beacon frequency." msgstr "" -#: ../../en/units/lorawan_rui3.rst:735 e98005cc61ca46b38aa6b388bf06f75c +#: ../../en/units/lorawan_rui3.rst:735 3313b2d8c1bb4ee1a8420c90b5fff67d msgid "The beacon frequency as an integer." msgstr "" -#: ../../en/units/lorawan_rui3.rst:739 53ded4c21a02440fa8bae375a5b2caae +#: ../../en/units/lorawan_rui3.rst:739 e98bd7603a104d3cbe0b17658ab87014 msgid "Retrieves the beacon time." msgstr "" -#: ../../en/units/lorawan_rui3.rst:741 d4f825e145024072acc68fbd3c4e1692 +#: ../../en/units/lorawan_rui3.rst:741 36512ce12dab44e5adb8bba70ad808cd msgid "The beacon time as an integer." msgstr "" -#: ../../en/units/lorawan_rui3.rst:745 f71ff353e5784fb5b7da6307d0a729a3 +#: ../../en/units/lorawan_rui3.rst:745 953097cec69f415ca6d0eaa73a810141 msgid "Retrieves the beacon gateway GPS location." msgstr "" -#: ../../en/units/lorawan_rui3.rst:747 577e2f45087f46a683367993ae62a5aa +#: ../../en/units/lorawan_rui3.rst:747 c9fe47c3705a4f17aad0e04398740d2f msgid "The beacon gateway GPS information as a string." msgstr "" -#: ../../en/units/lorawan_rui3.rst:751 3369d201fc0144d2aa09b4a35d3c34ee +#: ../../en/units/lorawan_rui3.rst:751 7e936d54aa6a45138d2b662ec60e8f64 msgid "Retrieves the Received Signal Strength Indicator (RSSI)." msgstr "" -#: ../../en/units/lorawan_rui3.rst:753 3f0874e111b243d5aeef298173e9bd22 +#: ../../en/units/lorawan_rui3.rst:753 5a6b6272566340a9ab21aa9e1a1cb84f msgid "The RSSI value as a string." msgstr "" -#: ../../en/units/lorawan_rui3.rst:757 baa6fe55dc9c49839199c8250150b574 +#: ../../en/units/lorawan_rui3.rst:757 ed4b196caff64321b74c977e679ce879 msgid "Retrieves the RSSI values from all channels." msgstr "" -#: ../../en/units/lorawan_rui3.rst:759 72adcfe48c5648acbcad393d6d9cfd6e +#: ../../en/units/lorawan_rui3.rst:759 d91c27bd97d04fe6bf7ec4fea5a2521d msgid "The RSSI values for all channels as a string." msgstr "" -#: ../../en/units/lorawan_rui3.rst:763 4d98cac398454a4dbb6e5bdcdddd784e +#: ../../en/units/lorawan_rui3.rst:763 d4a8317a3af94242a05801656f2d7feb msgid "Retrieves the Signal-to-Noise Ratio (SNR)." msgstr "" -#: ../../en/units/lorawan_rui3.rst:765 8eb43d547e594cdb80e3410eb2544c5b +#: ../../en/units/lorawan_rui3.rst:765 da0ef9fed4284e4fb0f40174516a2889 msgid "The SNR value as a string." msgstr "" -#: ../../en/units/lorawan_rui3.rst:769 b18a7487d3624234afec92ad9ab453af +#: ../../en/units/lorawan_rui3.rst:769 a8e7aa3b90704a14990a7e88d855842a msgid "Retrieves the channel mask (only for US915, AU915, CN470)." msgstr "" -#: ../../en/units/lorawan_rui3.rst:771 c75d8b0db16d44b4b9509a6fc5c8058d +#: ../../en/units/lorawan_rui3.rst:771 082cd90b4fb84ed5868fea086bc5d0a4 msgid "The channel mask as a string." msgstr "" -#: ../../en/units/lorawan_rui3.rst:775 ba837ce6e1c242c6b03c81183d3c00e8 +#: ../../en/units/lorawan_rui3.rst:775 3984fb602497409fbb0b62a631897b41 msgid "" "Sets the channel mask to open or close specific channels (only for US915," " AU915, CN470)." msgstr "" -#: ../../en/units/lorawan_rui3.rst:777 d5390329d1f640c89046b5c9a17b2e9b +#: ../../en/units/lorawan_rui3.rst:777 6a9cebc8490748e6b9b6a9fc437cf0b0 msgid "The channel mask as a 4-length hexadecimal string." msgstr "" -#: ../../en/units/lorawan_rui3.rst:783 f45c76bcbc0c4ca7a4d9bf62a320d9f7 +#: ../../en/units/lorawan_rui3.rst:783 eed65f34b5e542e2992818dafa5cfbca msgid "Retrieves the state of the eight-channel mode." msgstr "" -#: ../../en/units/lorawan_rui3.rst:785 dca6b33ded7d4043862b315dc6bcb892 +#: ../../en/units/lorawan_rui3.rst:785 517c4dc1f49f4195b28d91a85b5878f8 msgid "The state of the eight-channel mode as a string." msgstr "" -#: ../../en/units/lorawan_rui3.rst:789 6c23eac4bdfc46e9b1df1d735ddd8859 +#: ../../en/units/lorawan_rui3.rst:789 8ca16e0de70a43729d238a1ae4405734 msgid "" "Sets the state of the eight-channel mode, with a group number between 1 " "and 12." msgstr "" -#: ../../en/units/lorawan_rui3.rst:791 3e68aadaa53a4c69b2d71ee282f512f0 +#: ../../en/units/lorawan_rui3.rst:791 0ac1c71dcaa44bd2a4409ddf953931e6 msgid "The maximum group number (1 to 12)." msgstr "" -#: ../../en/units/lorawan_rui3.rst:793 e87a07a0a7df4b74b75de2f6a1cb6d52 +#: ../../en/units/lorawan_rui3.rst:793 e6d9eee8f85f456ca7f8d05260a7a65b msgid "True if the AT command execution is successful, False otherwise." msgstr "" -#: ../../en/units/lorawan_rui3.rst:797 31c21895383b41c0be58984a8a0304d1 +#: ../../en/units/lorawan_rui3.rst:797 d2b07a5cec62480ea6b81f43487b525e msgid "Retrieves the single channel mode." msgstr "" -#: ../../en/units/lorawan_rui3.rst:799 c9a58cd75e204d0590e7eaa7158b7e24 +#: ../../en/units/lorawan_rui3.rst:799 02c6374ddb9b43f98f56093a0e49b442 msgid "The single channel mode as a string." msgstr "" -#: ../../en/units/lorawan_rui3.rst:803 b39c2ba8a5094c84b5251cd908c5c6ec +#: ../../en/units/lorawan_rui3.rst:803 e3001f0a4c4a4d40aab370edd73920a3 msgid "Sets the single channel mode." msgstr "" -#: ../../en/units/lorawan_rui3.rst:809 70695d1c9485402d8b231a14584ebe0b +#: ../../en/units/lorawan_rui3.rst:809 5139a782f89045859356f96a49731011 msgid "Retrieves the active region for the device." msgstr "" -#: ../../en/units/lorawan_rui3.rst:811 0fcc66323d44405abf0b9e10af8b30a5 +#: ../../en/units/lorawan_rui3.rst:811 1f47873e32d44dbbb4f3d02bab7b6cd3 msgid "The active region as a string." msgstr "" -#: ../../en/units/lorawan_rui3.rst:815 5ad07322d38741cabc6c8bf23edb7fc3 +#: ../../en/units/lorawan_rui3.rst:815 e6dc2fa99b46429aa8a829a9d8a26da6 msgid "Sets the active region for the device." msgstr "" -#: ../../en/units/lorawan_rui3.rst:817 2f2c81f9fc3c47719feabcec619d84db +#: ../../en/units/lorawan_rui3.rst:817 692edbfa76b348bfa3e927549da7e1ca msgid "" "The result of the AT command execution. - ``EU433``: 0 - ``CN470``: 1 - " "``RU864``: 2 - ``IN865``: 3 - ``EU868``: 4 - ``US915``: 5 - ``AU915,``: 6" @@ -1444,709 +1444,717 @@ msgid "" "``AS923-4``: 11 - ``LA915``: 12" msgstr "" -#: ../../en/units/lorawan_rui3.rst:819 fe00d3bcb4e346c4b8d7f40acc1b79dc +#: ../../en/units/lorawan_rui3.rst:819 ae409b0035ac4903a08bd30cdd66e1d2 msgid "``EU433``: 0" msgstr "" -#: ../../en/units/lorawan_rui3.rst:820 f66ee33fef79428cb672addad9c7cc31 +#: ../../en/units/lorawan_rui3.rst:820 ecc481feba9f481292eb40b7747530fd msgid "``CN470``: 1" msgstr "" -#: ../../en/units/lorawan_rui3.rst:821 2219810966774f428a2389117173cd87 +#: ../../en/units/lorawan_rui3.rst:821 a762e20d96cd46d1a9cb704b7a634b29 msgid "``RU864``: 2" msgstr "" -#: ../../en/units/lorawan_rui3.rst:822 78b1dc91f7ac45b1a703cfb27cacff84 +#: ../../en/units/lorawan_rui3.rst:822 36304b2e803b40b0bd9e89eda5987208 msgid "``IN865``: 3" msgstr "" -#: ../../en/units/lorawan_rui3.rst:823 bfe9e6e66c56486598997f1d3fadf43b +#: ../../en/units/lorawan_rui3.rst:823 e7aff2f88df6438ab85ddf933e5f0a57 msgid "``EU868``: 4" msgstr "" -#: ../../en/units/lorawan_rui3.rst:824 99cb5bd8f2224795a4d9a1247ccbd501 +#: ../../en/units/lorawan_rui3.rst:824 da86f9e76dad40539fd47e91e5d02a45 msgid "``US915``: 5" msgstr "" -#: ../../en/units/lorawan_rui3.rst:825 8b77b5a954944d27b825cec6f6b4ee03 +#: ../../en/units/lorawan_rui3.rst:825 2e8e884114524e00a76f2cd7f7dd4b05 msgid "``AU915,``: 6" msgstr "" -#: ../../en/units/lorawan_rui3.rst:826 326033f378a848cdb6bf9c29bdde5d4e +#: ../../en/units/lorawan_rui3.rst:826 d80935a7fa0143c3b5816dbd16e2ea05 msgid "``KR920,``: 7" msgstr "" -#: ../../en/units/lorawan_rui3.rst:827 ec1b4c2cb9c8413aae2a6c4df74d4804 +#: ../../en/units/lorawan_rui3.rst:827 b5781cfda1ed4463962615fe48f8a3ed msgid "``AS923-1``: 8" msgstr "" -#: ../../en/units/lorawan_rui3.rst:828 958f3f3b48c34db3a4978633517354e5 +#: ../../en/units/lorawan_rui3.rst:828 d0bb0fdb84db43a2be155d6fc785c943 msgid "``AS923-2``: 9" msgstr "" -#: ../../en/units/lorawan_rui3.rst:829 549cabe9208f41ccb7bc283062e1e2bc +#: ../../en/units/lorawan_rui3.rst:829 1ac3564fdb4e4bcf9ff8e5c5ea4ef5b2 msgid "``AS923-3``: 10" msgstr "" -#: ../../en/units/lorawan_rui3.rst:830 404eef38f16b48f1bcdeb4cad4fbdc95 +#: ../../en/units/lorawan_rui3.rst:830 720a3847bd2e450380d42a17c1f065d7 msgid "``AS923-4``: 11" msgstr "" -#: ../../en/units/lorawan_rui3.rst:831 d66331e0bf0a46ae9cb99a32326a94f6 +#: ../../en/units/lorawan_rui3.rst:831 314e2c1a7c6a483291707cd5b62e3b96 msgid "``LA915``: 12" msgstr "" -#: ../../en/units/lorawan_rui3.rst:835 e46c48360daa4c42b24580498b92626b +#: ../../en/units/lorawan_rui3.rst:835 48147eacef554e76932685920ac5c050 msgid "Adds a multicast group to the device." msgstr "" -#: ../../en/units/lorawan_rui3.rst:837 cc5410293c104728b2d7331754278294 +#: ../../en/units/lorawan_rui3.rst:837 ba73813d58284c8c8195590427461976 msgid "The class of the multicast group." msgstr "" -#: ../../en/units/lorawan_rui3.rst:838 67c4001e06fb47cb88ff63859b6b13a8 +#: ../../en/units/lorawan_rui3.rst:838 84d4989752004f2fb195435163b519da msgid "The device address." msgstr "" -#: ../../en/units/lorawan_rui3.rst:839 4fbfe395115f43f8b57f6723fcbef163 +#: ../../en/units/lorawan_rui3.rst:839 dcebdd22244e469cb628b110fb95d3cb msgid "The network session key." msgstr "" -#: ../../en/units/lorawan_rui3.rst:840 f341f608cba2487e8c44953daecf1597 +#: ../../en/units/lorawan_rui3.rst:840 2d3d4face950469e9da5be3445efa11b msgid "The application session key." msgstr "" -#: ../../en/units/lorawan_rui3.rst:841 282bbbd445854d25a8a706eb5cca7980 +#: ../../en/units/lorawan_rui3.rst:841 567a79b374874e75ae9b7c61bc41b942 msgid "The frequency for the multicast group." msgstr "" -#: ../../en/units/lorawan_rui3.rst:842 f61aac00881f48f7ac0cb663930f27e9 +#: ../../en/units/lorawan_rui3.rst:842 384ec1b8fdbf41dab7ddb92dd37883d9 msgid "The datarate for the multicast group." msgstr "" -#: ../../en/units/lorawan_rui3.rst:843 f97843b014e54cdd9016d0615ab13f7b +#: ../../en/units/lorawan_rui3.rst:843 4ec169a466054119afea45c9cb7a0032 msgid "The periodicity for the multicast group." msgstr "" -#: ../../en/units/lorawan_rui3.rst:849 548fb506ccd74e9290987bf7f522f9f2 +#: ../../en/units/lorawan_rui3.rst:849 a2669764b4574661b81eb41df9484469 msgid "Removes a multicast group by the given device address." msgstr "" -#: ../../en/units/lorawan_rui3.rst:851 cde999466ac04f6e88736103628946dc +#: ../../en/units/lorawan_rui3.rst:851 55faac07a2d94a9c85ac608dd48663c7 msgid "The device address of the multicast group to remove." msgstr "" -#: ../../en/units/lorawan_rui3.rst:857 c2f3ffd0080447238b7b8e3fe6296f39 +#: ../../en/units/lorawan_rui3.rst:857 f2d37d8593ed411fba352d3653df7b08 msgid "Retrieves the list of multicast groups." msgstr "" -#: ../../en/units/lorawan_rui3.rst:859 a7dd648aa6274f2cb1bcab40b0114575 +#: ../../en/units/lorawan_rui3.rst:859 8a2c1d7b46584ad9b7eaf1afc8646c88 msgid "The list of multicast groups as a string." msgstr "" -#: ../../en/units/lorawan_rui3.rst:863 c885d412fcd14d47ab34d8692f0cc1dd +#: ../../en/units/lorawan_rui3.rst:863 0b2209a331bc4270a4f9a620437c144f msgid "Retrieves the current network mode." msgstr "" -#: ../../en/units/lorawan_rui3.rst:865 e271cadc0bbf4986bf754b437da486b3 +#: ../../en/units/lorawan_rui3.rst:865 9957c150129e4b909a3fe063c4bbfc6a msgid "" "The current network mode as an integer - ``P2P_LORA``: 0 - ``LoRaWAN``: " "1 - ``P2P_FSK``: 2" msgstr "" -#: ../../en/units/lorawan_rui3.rst:865 647ece89be9f4d8e92f0f7eb9fe658fb +#: ../../en/units/lorawan_rui3.rst:865 708dbd8e425740eaa89a5cc80c9201a8 msgid "The current network mode as an integer" msgstr "" #: ../../en/units/lorawan_rui3.rst:867 ../../en/units/lorawan_rui3.rst:877 -#: 02b1284a74a84431ab2108ad0077ac51 37042a3536e8447d8d63abbea62b049a +#: 1d37eb55ada34d2ab6cc544d253f0375 d6a0b40d8ca446bf8f9f9d79500dcf1e msgid "``P2P_LORA``: 0" msgstr "" #: ../../en/units/lorawan_rui3.rst:868 ../../en/units/lorawan_rui3.rst:878 -#: ba8c6531c1374a06bca8dffeda7da2b2 e7defa7191d84b8e9944f964a235dfc6 +#: 010ea58d6daf47d785c7918b857cbd15 9123fd8e3b4f467b91801adc11b548ba msgid "``LoRaWAN``: 1" msgstr "" #: ../../en/units/lorawan_rui3.rst:869 ../../en/units/lorawan_rui3.rst:879 -#: 66e952cf189e45f58a7c0bdcce95804f dd38a74d8a964884a39c58fc5ca7c7d3 +#: 549396456ec44d3ca2f6480b660be130 7d03cb4b4ca342f3936c7c5477e0ec39 msgid "``P2P_FSK``: 2" msgstr "" -#: ../../en/units/lorawan_rui3.rst:873 83ed86f09829407cbf9dcb874cd156e9 +#: ../../en/units/lorawan_rui3.rst:873 5bac2bc25a934acbb27c0e143bd48922 msgid "Sets the network mode for the device." msgstr "" -#: ../../en/units/lorawan_rui3.rst:875 df4eec8e93c34f3dad25c4058520a938 +#: ../../en/units/lorawan_rui3.rst:875 7ab224315fca4c21b73ce449eb220245 msgid "" "The mode to set for the get_network_id. - ``P2P_LORA``: 0 - ``LoRaWAN``:" " 1 - ``P2P_FSK``: 2" msgstr "" -#: ../../en/units/lorawan_rui3.rst:875 de2f1d217a5340f294b9599a64adf350 +#: ../../en/units/lorawan_rui3.rst:875 de396b0963a74391a8671f293e5ce561 msgid "The mode to set for the get_network_id." msgstr "" -#: ../../en/units/lorawan_rui3.rst:885 7db8671a93ff47ee9f5bf69bb956cc62 +#: ../../en/units/lorawan_rui3.rst:885 bc5deeec1fc74a26b448748caa616856 msgid "|set_network_mode.png|" msgstr "" -#: ../../en/refs/unit.lorawan_rui3.ref:32 37a6f8af90e8484b858468f031fbfc55 +#: ../../en/refs/unit.lorawan_rui3.ref:32 c80ec44c2f6f45459b37f85e026927ef msgid "set_network_mode.png" msgstr "" -#: ../../en/units/lorawan_rui3.rst:889 0a4bf8ad32bc49099651874b46c48509 +#: ../../en/units/lorawan_rui3.rst:889 675f155b3db4404091fc10fbc9037fd6 msgid "Retrieves the current P2P frequency." msgstr "" -#: ../../en/units/lorawan_rui3.rst:891 85c52adff6c94c2bb4a2ce0c3db7fc3a +#: ../../en/units/lorawan_rui3.rst:891 1de6bf0d0d87453fb4fad9864badccba msgid "The current P2P frequency as an integer." msgstr "" -#: ../../en/units/lorawan_rui3.rst:895 6a353ebfff5548f8a1ab1422ba2c2857 +#: ../../en/units/lorawan_rui3.rst:895 d5aa71354f2c4bcdaf2ce9913b2d8d7f msgid "|get_p2p_frequency.png|" msgstr "" -#: ../../en/refs/unit.lorawan_rui3.ref:33 a73771e4847e420fb4b12fbc0c252ef8 +#: ../../en/refs/unit.lorawan_rui3.ref:33 ee60bb7317664a51bdb19308c4d8d722 msgid "get_p2p_frequency.png" msgstr "" -#: ../../en/units/lorawan_rui3.rst:899 2197e3b6fd15463bbb4b0c34d7c48314 +#: ../../en/units/lorawan_rui3.rst:899 be36f7753c0240e38960683adbddb08d msgid "Sets the P2P frequency for the device." msgstr "" -#: ../../en/units/lorawan_rui3.rst:901 483022d1bea24833b7f9e01607d2abce +#: ../../en/units/lorawan_rui3.rst:901 b2fb192d5d5b46d98d40e80db287ec6f msgid "" "The frequency to set for P2P communication. - ``Low-frequency`` : " "150000000-525000000 - ``High-frequency`` : 525000000-960000000" msgstr "" -#: ../../en/units/lorawan_rui3.rst:901 e9eea823bda143f7bddfc8623ffb7686 +#: ../../en/units/lorawan_rui3.rst:901 386c338ce3794d048c59bfe8a18eecef msgid "The frequency to set for P2P communication." msgstr "" -#: ../../en/units/lorawan_rui3.rst:903 7ac64cc6682d44ab914c0c86c6acd3c0 +#: ../../en/units/lorawan_rui3.rst:903 f0ba6071716b48678f5801a30fc40c57 msgid "``Low-frequency`` : 150000000-525000000" msgstr "" -#: ../../en/units/lorawan_rui3.rst:904 287919668ad049d1866496b4063c19ab +#: ../../en/units/lorawan_rui3.rst:904 882e803e1266460882db9979cbe76f2e msgid "``High-frequency`` : 525000000-960000000" msgstr "" -#: ../../en/units/lorawan_rui3.rst:910 d59c5b6d685a4c12a0d1a1039def34f5 +#: ../../en/units/lorawan_rui3.rst:910 55a11fac2ff3477c95d8a2e4352c2781 msgid "|set_p2p_frequency.png|" msgstr "" -#: ../../en/refs/unit.lorawan_rui3.ref:34 de7bd44f597e43ecb9d2c5b29d8411fc +#: ../../en/refs/unit.lorawan_rui3.ref:34 41ee35edfa314c4db07a066faae87817 msgid "set_p2p_frequency.png" msgstr "" -#: ../../en/units/lorawan_rui3.rst:914 9a40d0cd54ec4560b4cc21db8f218e99 +#: ../../en/units/lorawan_rui3.rst:914 e3d2c84d5b404f07bd80320eeaca61b3 msgid "Retrieves the current P2P spreading factor." msgstr "" -#: ../../en/units/lorawan_rui3.rst:916 5b9c87389ddc4d37877ef7f34c58569e +#: ../../en/units/lorawan_rui3.rst:916 0a7bdd3e30ce4f0599ad16a2b8d4fdfb msgid "The current P2P spreading factor as an integer." msgstr "" -#: ../../en/units/lorawan_rui3.rst:920 1a088336b3d8401e83c6f245334eb420 +#: ../../en/units/lorawan_rui3.rst:920 cf46cc301ced41ebbc1e15a5e7b99a69 msgid "|get_p2p_spreading_factor.png|" msgstr "" -#: ../../en/refs/unit.lorawan_rui3.ref:35 c911dd8a15e5476dbf5463289eb1517a +#: ../../en/refs/unit.lorawan_rui3.ref:35 8c895547dd044c38a3ed2572ff0608b9 msgid "get_p2p_spreading_factor.png" msgstr "" -#: ../../en/units/lorawan_rui3.rst:924 2dcc0e9a473047eda9d43428641787c4 +#: ../../en/units/lorawan_rui3.rst:924 0a2a1fabfa384eeea158e05ae9046099 msgid "Sets the P2P spreading factor." msgstr "" -#: ../../en/units/lorawan_rui3.rst:926 7c5412ef011a4a4fabccfac55b7e3e44 +#: ../../en/units/lorawan_rui3.rst:926 81e4dd8f229b46a8af28c04f8efaebef msgid "" "The spreading factor to set for P2P communication. The default value is " "7, and the range is 5 to 12." msgstr "" -#: ../../en/units/lorawan_rui3.rst:932 49391a24c44046ed80e603ac9ef03e4f +#: ../../en/units/lorawan_rui3.rst:932 a344292dcb3e4f2e87c9464d5442d1e5 msgid "|set_p2p_spreading_factor.png|" msgstr "" -#: ../../en/refs/unit.lorawan_rui3.ref:36 476fe6a218a2405fa9b725e9502a6194 +#: ../../en/refs/unit.lorawan_rui3.ref:36 4aa3185ef1b244549bc0dec2fa78f6c5 msgid "set_p2p_spreading_factor.png" msgstr "" -#: ../../en/units/lorawan_rui3.rst:936 e5f3929edc664b6fa35ea929fbb84194 +#: ../../en/units/lorawan_rui3.rst:936 f2d6ddc2b30c4f10aca29379d6ceeff3 msgid "Retrieves the current P2P bandwidth." msgstr "" -#: ../../en/units/lorawan_rui3.rst:939 802fe56d4fda4518b9fe15f7537d2803 +#: ../../en/units/lorawan_rui3.rst:939 807018bf3877442b81a2fee7b593796a msgid "The current P2P bandwidth as an integer." msgstr "" -#: ../../en/units/lorawan_rui3.rst:943 08dbfd5a50634e3c862ba40b34c2dd4f +#: ../../en/units/lorawan_rui3.rst:943 d136d41a09264577a392ff8d8b5f2533 msgid "|get_p2p_bandwidth.png|" msgstr "" -#: ../../en/refs/unit.lorawan_rui3.ref:37 b4aec7c530084c9eb0db7d5cefd34047 +#: ../../en/refs/unit.lorawan_rui3.ref:37 6d369caf83584f65b5ed8cb854cb4dc3 msgid "get_p2p_bandwidth.png" msgstr "" -#: ../../en/units/lorawan_rui3.rst:954 a947b7908ad8463fb7508b142d0e936d +#: ../../en/units/lorawan_rui3.rst:954 1a9bddb8aae640429d2d37ee224bb3f0 msgid "|set_p2p_fsk_bandwidth.png|" msgstr "" -#: ../../en/refs/unit.lorawan_rui3.ref:38 079b8cd4fe07484899d75811cd75f375 +#: ../../en/refs/unit.lorawan_rui3.ref:38 3ac0bbe721714dad90c9d7186428efc5 msgid "set_p2p_fsk_bandwidth.png" msgstr "" -#: ../../en/units/lorawan_rui3.rst:956 1df27130ec6340848af426ff89259c26 +#: ../../en/units/lorawan_rui3.rst:956 b7eb7b00110547a586ce3ca31a92d5db msgid "|set_p2p_lora_bandwidth.png|" msgstr "" -#: ../../en/refs/unit.lorawan_rui3.ref:39 aa7af08012df46528ecd4e89014e0b17 +#: ../../en/refs/unit.lorawan_rui3.ref:39 a3b9d9007f1c4582b8f720159664b1e5 msgid "set_p2p_lora_bandwidth.png" msgstr "" -#: ../../en/units/lorawan_rui3.rst:960 56e1d321af434610a6f19478ccec0bf6 +#: ../../en/units/lorawan_rui3.rst:960 30db32836da447bd9973ce4768d8d766 msgid "Retrieves the current P2P code rate." msgstr "" -#: ../../en/units/lorawan_rui3.rst:963 15c072e6f2bb412f91896ca7b3b97d33 +#: ../../en/units/lorawan_rui3.rst:963 5588374317d94aaca9a2529b069150bd msgid "The current P2P code rate as an integer." msgstr "" -#: ../../en/units/lorawan_rui3.rst:967 83579cba6b53409bb5cdf5a736983369 +#: ../../en/units/lorawan_rui3.rst:967 859cdf8a9ff045cb971c7a61d627ac90 msgid "|get_p2p_code_rate.png|" msgstr "" -#: ../../en/refs/unit.lorawan_rui3.ref:40 75d25b363a914a3d86feb9bb7910b695 +#: ../../en/refs/unit.lorawan_rui3.ref:40 2147615ae35c4f77b33de8f0adc08394 msgid "get_p2p_code_rate.png" msgstr "" -#: ../../en/units/lorawan_rui3.rst:971 cd4faa76895d4abbbd759f47ccb813c8 +#: ../../en/units/lorawan_rui3.rst:971 5c813899446544d09fccf0337937a6fb msgid "Sets the P2P code rate." msgstr "" -#: ../../en/units/lorawan_rui3.rst:973 f4e3291d1b904fe09c513f0473c43a40 +#: ../../en/units/lorawan_rui3.rst:973 9b80ab0d12be43d2bf2f8f4407ee289a msgid "" "The code rate to set for P2P communication. Default is 0, range is 0 to " "3. 0 = 4/5, 1 = 4/6, 2 = 4/7, 3 = 4/8." msgstr "" -#: ../../en/units/lorawan_rui3.rst:979 67dbf92a0a57484d90a0aa9bdfea5304 +#: ../../en/units/lorawan_rui3.rst:979 7d12be4fe5cf4f81a90b1f156c67a9e8 msgid "|set_p2p_code_rate.png|" msgstr "" -#: ../../en/refs/unit.lorawan_rui3.ref:41 613c54445cd748ee924860da95426c67 +#: ../../en/refs/unit.lorawan_rui3.ref:41 fff436e252ea4f41a589a8e3c2726f8f msgid "set_p2p_code_rate.png" msgstr "" -#: ../../en/units/lorawan_rui3.rst:983 e025b120656047a4a36f359ef56da001 +#: ../../en/units/lorawan_rui3.rst:983 91b59964643549b4b627206ac318b7dd msgid "Retrieves the current P2P preamble length." msgstr "" -#: ../../en/units/lorawan_rui3.rst:986 c2f830fe8e574834b6d95d9097e3d2bf +#: ../../en/units/lorawan_rui3.rst:986 38479f7dc9544d62a989b98f47d3c4fc msgid "The current P2P preamble length as an integer." msgstr "" -#: ../../en/units/lorawan_rui3.rst:990 780e656c48fb4a1f810e55b4967f92b6 +#: ../../en/units/lorawan_rui3.rst:990 a558506a257041c2ba7dd0b32e4a6cd0 msgid "|get_p2p_preamble_length.png|" msgstr "" -#: ../../en/refs/unit.lorawan_rui3.ref:42 7207814d952a4b298a8555fae0bb791a +#: ../../en/refs/unit.lorawan_rui3.ref:42 37f4237efed74d2d8ef0eccf56276542 msgid "get_p2p_preamble_length.png" msgstr "" -#: ../../en/units/lorawan_rui3.rst:994 eb799800e2b14105ab3dc570b7c68f66 +#: ../../en/units/lorawan_rui3.rst:994 90956409aae6478ab2524c39aaee4e48 msgid "Sets the P2P preamble length." msgstr "" -#: ../../en/units/lorawan_rui3.rst:996 91dff83b0054412e91ca4bb0907cd97c +#: ../../en/units/lorawan_rui3.rst:996 958979c0871545c3be64fa78f4665dac msgid "" "The preamble length to set for P2P communication. Default is 8, range is " "5 to 65535." msgstr "" -#: ../../en/units/lorawan_rui3.rst:1002 0dfdd10f434d4d19a6d30fb90a440ab7 +#: ../../en/units/lorawan_rui3.rst:1002 bfd3d2d116da43e68dadc41e46f4b1cb msgid "|set_p2p_preamble_length.png|" msgstr "" -#: ../../en/refs/unit.lorawan_rui3.ref:43 149eaac723284086affdff5ed89083c7 +#: ../../en/refs/unit.lorawan_rui3.ref:43 cf764a8d886c47b1ae07fa5874b45980 msgid "set_p2p_preamble_length.png" msgstr "" -#: ../../en/units/lorawan_rui3.rst:1006 2aec8502b749481f8a1227deb5a4794c +#: ../../en/units/lorawan_rui3.rst:1006 81c1026812294c72a9501de56da2a10a msgid "Retrieves the current P2P transmission power." msgstr "" -#: ../../en/units/lorawan_rui3.rst:1009 9ae32f7a2561416bb6390e78b4534be8 +#: ../../en/units/lorawan_rui3.rst:1009 eeefedfc3466430a9f69cc08ede64634 msgid "The current P2P transmission power as an integer." msgstr "" -#: ../../en/units/lorawan_rui3.rst:1013 85a6d72baf044a6889a4c46137afcd3f +#: ../../en/units/lorawan_rui3.rst:1013 70c7b31b389e479991abe41749a088f0 msgid "|get_p2p_tx_power.png|" msgstr "" -#: ../../en/refs/unit.lorawan_rui3.ref:44 a0afa8cf7fb444a39cd244ad295eb621 +#: ../../en/refs/unit.lorawan_rui3.ref:44 e49e8a9d5e714f03b4041d9805419dad msgid "get_p2p_tx_power.png" msgstr "" -#: ../../en/units/lorawan_rui3.rst:1017 11fcf6e8124745d4ae7e3f2cf69a5d4b +#: ../../en/units/lorawan_rui3.rst:1017 407ceea288f04383b0a911eca28670fa msgid "Sets the P2P transmission power." msgstr "" -#: ../../en/units/lorawan_rui3.rst:1019 04fc8df084d044ef85f350a681ce26b4 +#: ../../en/units/lorawan_rui3.rst:1019 3031a8bd34184f10a82128cf4cc5ce4c msgid "" "The transmission power to set for P2P communication. Default is 14 dBm, " "range is 5 to 22 dBm." msgstr "" -#: ../../en/units/lorawan_rui3.rst:1025 95ffee09f22e485c8fafd87b6ad57607 +#: ../../en/units/lorawan_rui3.rst:1025 d33a1b352eed4bce8879f05f6b42e7ed msgid "|set_p2p_tx_power.png|" msgstr "" -#: ../../en/refs/unit.lorawan_rui3.ref:45 1fcceff1b26b428a9b59f1a5c4adaaed +#: ../../en/refs/unit.lorawan_rui3.ref:45 8ae50c0507f84d2087e1c0006ac13efc msgid "set_p2p_tx_power.png" msgstr "" -#: ../../en/units/lorawan_rui3.rst:1029 8f57b1c6f08945109677e08a2d3da160 +#: ../../en/units/lorawan_rui3.rst:1029 0b96d4bec9804436a077fa41531e1d8b msgid "Retrieves the current P2P FSK bitrate." msgstr "" -#: ../../en/units/lorawan_rui3.rst:1032 aec3a15514184eb98fd8d428183c11ae +#: ../../en/units/lorawan_rui3.rst:1032 3d156fe3137d459fbea2a74e7e8fc1a8 msgid "The current P2P FSK bitrate as an integer." msgstr "" -#: ../../en/units/lorawan_rui3.rst:1036 76039acd616f4a0d99489d276fb2a631 +#: ../../en/units/lorawan_rui3.rst:1036 35ccd14ddc034771be7c3030432c9ccb msgid "|get_p2p_fsk_bitrate.png|" msgstr "" -#: ../../en/refs/unit.lorawan_rui3.ref:46 d1df64ad0de641ba925073eaf2e5ccd0 +#: ../../en/refs/unit.lorawan_rui3.ref:46 9630eedc01df457cb5b7e800fcf5c64a msgid "get_p2p_fsk_bitrate.png" msgstr "" -#: ../../en/units/lorawan_rui3.rst:1040 b1b49b9f2a68496e9da55fc58e07df5f +#: ../../en/units/lorawan_rui3.rst:1040 99f473f829bf4a0ab7a75993f2d33ebf msgid "Sets the P2P FSK bitrate." msgstr "" -#: ../../en/units/lorawan_rui3.rst:1042 c576ddc407974c0c85e1a8ac0cf21085 +#: ../../en/units/lorawan_rui3.rst:1042 fe0aed69dd3342c89d4420a840ee9140 msgid "" "The bitrate to set for P2P FSK communication. The range is 600 to 300000 " "b/s." msgstr "" -#: ../../en/units/lorawan_rui3.rst:1048 27d6a4400ee742249d9212d60edc803b +#: ../../en/units/lorawan_rui3.rst:1048 8dc7d84f489f47d2990cb8f406989440 msgid "|set_p2p_fsk_bitrate.png|" msgstr "" -#: ../../en/refs/unit.lorawan_rui3.ref:47 fb2caa15d52348b7885d7af2c3adaacc +#: ../../en/refs/unit.lorawan_rui3.ref:47 0f18ff7b62f44d6bb32442753b59fb84 msgid "set_p2p_fsk_bitrate.png" msgstr "" -#: ../../en/units/lorawan_rui3.rst:1052 6d1fde86b3a94841b8d890aa0d5fa97c +#: ../../en/units/lorawan_rui3.rst:1052 216108ac1299495d8d3eaa573dbcce49 msgid "Retrieves the current P2P FSK frequency deviation." msgstr "" -#: ../../en/units/lorawan_rui3.rst:1055 4b77ecaee6fa4f22b3f144e03b81954a +#: ../../en/units/lorawan_rui3.rst:1055 cb35826776ec4857a1401a056fbf6dcf msgid "The current P2P FSK frequency deviation as an integer." msgstr "" -#: ../../en/units/lorawan_rui3.rst:1059 d1ef5d4673a34dca965a0125b80189e5 +#: ../../en/units/lorawan_rui3.rst:1059 df4f13c8831044c5a4ebc25b26b885bd msgid "Sets the P2P FSK frequency deviation." msgstr "" -#: ../../en/units/lorawan_rui3.rst:1061 0bbcb12f757c4077936e04d176ec43b7 +#: ../../en/units/lorawan_rui3.rst:1061 5507beb409b54df48bcd9b2698192c4a msgid "" "The frequency deviation to set for P2P FSK communication. The range is " "600 to 200000 Hz." msgstr "" -#: ../../en/units/lorawan_rui3.rst:1067 3c5fa3a89a6d48f282604d2e6920b40a +#: ../../en/units/lorawan_rui3.rst:1067 21a67a89ed744b98a28be86046b68c01 msgid "Sends P2P data with a given payload." msgstr "" -#: ../../en/units/lorawan_rui3.rst:1069 f638f5da2ee84ff3867cf36ec54ecb5b +#: ../../en/units/lorawan_rui3.rst:1069 65b6b05f06c04862955b572bbb6b17ce msgid "" "The payload to send, 2 to 500 characters in length, must be an even " "number of characters composed of 0-9, a-f, A-F, representing 1 to 256 " "hexadecimal values." msgstr "" -#: ../../en/units/lorawan_rui3.rst:1070 baccab96faa347719e3cd2ce8367b40f +#: ../../en/units/lorawan_rui3.rst:1070 cdb04dfd21ae4c1b93b1ecc2a2a28e3b msgid "The timeout for the data transmission, default is 1000 ms." msgstr "" -#: ../../en/units/lorawan_rui3.rst:1072 75937f3039904b0381f2d3c550aeae90 +#: ../../en/units/lorawan_rui3.rst:1071 da2ed7ac6b8b48249b1cdfdf4bc68435 +msgid "A boolean indicating whether to convert the payload to hexadecimal format." +msgstr "" + +#: ../../en/units/lorawan_rui3.rst:1073 5bd06452361943f9a6f549cc79ab8996 msgid "" "True if the data was sent successfully (\"TXFSK DONE\" or \"TXP2P " "DONE\"), False otherwise." msgstr "" -#: ../../en/units/lorawan_rui3.rst:1076 671a81679772462381e0a07a2dd32e5f +#: ../../en/units/lorawan_rui3.rst:1077 570889c846284ddbb8dfdbd2e499aafd msgid "|send_p2p_data.png|" msgstr "" -#: ../../en/refs/unit.lorawan_rui3.ref:48 a85ff2b0edab476d91d9d1d3fac6ac71 +#: ../../en/refs/unit.lorawan_rui3.ref:48 a3f5aae5c0ef46e2a2b9f52dcbbee903 msgid "send_p2p_data.png" msgstr "" -#: ../../en/units/lorawan_rui3.rst:1078 6893fb4d2a7145ea91f95eef4d70b14e +#: ../../en/units/lorawan_rui3.rst:1079 62aab8a8fe5a4476a1870047a5059fa6 msgid "|send_p2p_data_return.png|" msgstr "" -#: ../../en/refs/unit.lorawan_rui3.ref:49 cb748997f44b4edc951e2d1557437b8e +#: ../../en/refs/unit.lorawan_rui3.ref:49 d65b2135c24b4bf7a2bfb6fb9e98fdff msgid "send_p2p_data_return.png" msgstr "" -#: ../../en/units/lorawan_rui3.rst:1082 4f2bd31087cc46bdade6b7e936d4163c +#: ../../en/units/lorawan_rui3.rst:1083 58914b7575bb48659a97a9c2c0c81f8b msgid "Get the current channel activity status in P2P mode." msgstr "" -#: ../../en/units/lorawan_rui3.rst:1084 1572aa4019de4ca39459609ab34ef65d +#: ../../en/units/lorawan_rui3.rst:1085 3703e084a385436eac386b35e8bcb456 msgid "The channel activity status as an integer (0 or 1)." msgstr "" -#: ../../en/units/lorawan_rui3.rst:1088 fceb46ae6a0641f0b546e33273eebcde +#: ../../en/units/lorawan_rui3.rst:1089 b2f081e1c3154d6ca01886f8cea6a98d msgid "Enable or disable channel activity in P2P mode." msgstr "" -#: ../../en/units/lorawan_rui3.rst:1090 08f1db641e7b4cf197a7cd74e35b92ba +#: ../../en/units/lorawan_rui3.rst:1091 75a9f30c9c044e869a3d18a3667ef92a msgid "0 to disable, 1 to enable channel activity." msgstr "" -#: ../../en/units/lorawan_rui3.rst:1092 ../../en/units/lorawan_rui3.rst:1119 -#: ../../en/units/lorawan_rui3.rst:1133 ../../en/units/lorawan_rui3.rst:1147 -#: ../../en/units/lorawan_rui3.rst:1161 ../../en/units/lorawan_rui3.rst:1175 -#: ../../en/units/lorawan_rui3.rst:1194 ../../en/units/lorawan_rui3.rst:1208 -#: ../../en/units/lorawan_rui3.rst:1226 ../../en/units/lorawan_rui3.rst:1244 -#: ../../en/units/lorawan_rui3.rst:1258 10caea0531394f4da7c6551fea9c0b17 -#: 135c3e524c1f4e06a0f7c03a9022707a 46281d5ce6d747fa830e39192bc43bf8 -#: 48a1253b36e04cc4ac9b8541f1a06057 958dcf51c23d4617a70559bd362a39a4 -#: a90fb27e015740cda92ff2a844a4e7c1 a9c3687471b8474b9541d05cb71b61e6 -#: b00bffa699da401d804f6a14680d381f e3eac22633bf4acfaffee0cfaacd750b -#: eb76fb13408543f8830e6b0ec1fe97b5 fef0ed011c4d4808a8990a741294c169 +#: ../../en/units/lorawan_rui3.rst:1093 ../../en/units/lorawan_rui3.rst:1121 +#: ../../en/units/lorawan_rui3.rst:1135 ../../en/units/lorawan_rui3.rst:1149 +#: ../../en/units/lorawan_rui3.rst:1163 ../../en/units/lorawan_rui3.rst:1177 +#: ../../en/units/lorawan_rui3.rst:1196 ../../en/units/lorawan_rui3.rst:1210 +#: ../../en/units/lorawan_rui3.rst:1228 ../../en/units/lorawan_rui3.rst:1246 +#: ../../en/units/lorawan_rui3.rst:1260 007e5d59659e49ba82af6f4b5a562075 +#: 1aadf26edd2f46378b41f8584db4969f 2619bd7f9a5142fdba25578e5fb1eae7 +#: 30308f3d6f5647dabafc74c49b596123 3f9d7d15950b4b4fa17ab46a1713cc61 +#: 522de18e8bbe41dba608c190240e6efa 5f195364f3534234afc38016d80b7533 +#: 8dee27bdf2004c358ff27a79d58d5ff6 99f234ce8c7848aa92bd6ad9bfd0bc72 +#: b52eec0b3977445a89a8cae7fc39903f ed0c075a6d2c4f8bb41b6f9c555f3db1 msgid "The response from the command execution." msgstr "" -#: ../../en/units/lorawan_rui3.rst:1097 996908560ffc4a12844ab680817ab79f +#: ../../en/units/lorawan_rui3.rst:1098 22626abd66664dafb8821651cbc348a7 msgid "" "Timeout for listening to P2P LoRa data packets. Valid values are 1~65535," " with special cases for continuous listening and no timeout." msgstr "" -#: ../../en/units/lorawan_rui3.rst:1100 037fc41b82db46debf0de69a13140b9e +#: ../../en/units/lorawan_rui3.rst:1100 817029f030fb458a83e8f5208660eae9 +msgid "A boolean indicating whether to convert the payload to a string." +msgstr "" + +#: ../../en/units/lorawan_rui3.rst:1102 11a1d9f30f044644a8490687e6e39a34 msgid "" "A tuple (RSSI, SNR, Payload) if data is received; False if no data is " "received." msgstr "" -#: ../../en/units/lorawan_rui3.rst:1104 c87c74170ecb42c7b77e8616dd808976 +#: ../../en/units/lorawan_rui3.rst:1106 1ac54ee2b2b945eca913c97358cbc21b msgid "|get_p2p_receive_data.png|" msgstr "" -#: ../../en/refs/unit.lorawan_rui3.ref:50 63e83a24146847739561f51a7bc4eba2 +#: ../../en/refs/unit.lorawan_rui3.ref:50 21971c83de6d49099516de7d43033d88 msgid "get_p2p_receive_data.png" msgstr "" -#: ../../en/units/lorawan_rui3.rst:1108 4c3c53454a30483888c68d9fa142ae3a +#: ../../en/units/lorawan_rui3.rst:1110 3a85696a47ab47f1811a9a137f802793 msgid "Get the current encryption state in P2P mode." msgstr "" -#: ../../en/units/lorawan_rui3.rst:1111 77fdac67270f48789e6165185380ddee +#: ../../en/units/lorawan_rui3.rst:1113 5877fd0aec194f6bae997b2d78dc4d63 msgid "The encryption state as an integer (0 or 1)." msgstr "" -#: ../../en/units/lorawan_rui3.rst:1115 3f49c5968f03424793f6b615dbd903b4 +#: ../../en/units/lorawan_rui3.rst:1117 50d4ba37c6844ab0adfb891610ee9ff0 msgid "Enable or disable encryption in P2P mode." msgstr "" -#: ../../en/units/lorawan_rui3.rst:1117 aea92b46f92f45cebbb7034dc359e406 +#: ../../en/units/lorawan_rui3.rst:1119 71432ce9674046bcb761c4f0c1ab443f msgid "0 to disable, 1 to enable encryption." msgstr "" -#: ../../en/units/lorawan_rui3.rst:1123 0f3a41007c764e73a137123fdff6800d +#: ../../en/units/lorawan_rui3.rst:1125 660ffc1f77b3462583f0237e005cc25f msgid "Get the current encryption key in P2P mode." msgstr "" -#: ../../en/units/lorawan_rui3.rst:1125 0588749a906542f98352df3fa326e70e +#: ../../en/units/lorawan_rui3.rst:1127 3b2696cfb7304b4ea8ac0b18583818f9 msgid "The encryption key as a string." msgstr "" -#: ../../en/units/lorawan_rui3.rst:1129 d24028cc29e94693b21e743480df0510 +#: ../../en/units/lorawan_rui3.rst:1131 85c9d89285bf453ea0ae1188d0af7e86 msgid "Set the encryption key in P2P mode." msgstr "" -#: ../../en/units/lorawan_rui3.rst:1131 749728e7c8b34c67b6119411ff2f69d9 +#: ../../en/units/lorawan_rui3.rst:1133 d48347d361304b8c97d9273fa31482d9 msgid "The encryption key, represented as a 16-character hexadecimal string." msgstr "" -#: ../../en/units/lorawan_rui3.rst:1137 dbdf949912774ee991fbda8e641f8c64 +#: ../../en/units/lorawan_rui3.rst:1139 3ced25c5776c4e419148e55e2668724d msgid "" "Get the current cryptographic state in P2P mode (not supported on " "RAK3172)." msgstr "" -#: ../../en/units/lorawan_rui3.rst:1139 9154294fc9004bf887d095a1c23c14b0 +#: ../../en/units/lorawan_rui3.rst:1141 1b8b0049df2748278524f77870cf432e msgid "The cryptographic state as an integer (0 or 1)." msgstr "" -#: ../../en/units/lorawan_rui3.rst:1143 8799631ab7f24800886c088dd74212d7 +#: ../../en/units/lorawan_rui3.rst:1145 e08aaef767d74478aeca0afc910d8941 msgid "" "Enable or disable cryptographic state in P2P mode (not supported on " "RAK3172)." msgstr "" -#: ../../en/units/lorawan_rui3.rst:1145 8b31e083ec1443f69f595feb77365642 +#: ../../en/units/lorawan_rui3.rst:1147 8b86c17332b544dd9236f08fe44d6aa2 msgid "0 to disable, 1 to enable cryptographic state." msgstr "" -#: ../../en/units/lorawan_rui3.rst:1151 bc09491ea62246fe9bb274522434d937 +#: ../../en/units/lorawan_rui3.rst:1153 7a0ebc1749204ef19ae1d18303db3d45 msgid "Get the cryptographic key in P2P mode (not supported on RAK3172)." msgstr "" -#: ../../en/units/lorawan_rui3.rst:1153 1c058676ff114a428bca6a89935ca38e +#: ../../en/units/lorawan_rui3.rst:1155 aaafb0132421427d96bb97f8413df5d8 msgid "The cryptographic key as a string." msgstr "" -#: ../../en/units/lorawan_rui3.rst:1157 dfd7bec6c8b943d08f38692a52ee040f +#: ../../en/units/lorawan_rui3.rst:1159 49f11df320154843adbb54caaf7ef2c9 msgid "Set the cryptographic key in P2P mode (not supported on RAK3172)." msgstr "" -#: ../../en/units/lorawan_rui3.rst:1159 1ede2947c2ad4824bf4c54be743b12ed +#: ../../en/units/lorawan_rui3.rst:1161 3406d7e4383c4bc6b7984a307efa385a msgid "The cryptographic key, represented as an 8-character hexadecimal string." msgstr "" -#: ../../en/units/lorawan_rui3.rst:1165 42c7c78fd1a04cad9f3afb3bfb228092 +#: ../../en/units/lorawan_rui3.rst:1167 89b5af510caa4ef1931b06d323ded907 msgid "Get the encryption IV in P2P mode." msgstr "" -#: ../../en/units/lorawan_rui3.rst:1167 78e981ef7ace489fa5e4d29f6e83e697 +#: ../../en/units/lorawan_rui3.rst:1169 c6471cc41f29420bbdac8d891ae5416f msgid "The encryption IV as a string." msgstr "" -#: ../../en/units/lorawan_rui3.rst:1171 d831ac61e9024b81bfb488b7cc1b6baf +#: ../../en/units/lorawan_rui3.rst:1173 e210adb236ef431ca2d24ad9695e9227 msgid "Set the encryption IV in P2P mode." msgstr "" -#: ../../en/units/lorawan_rui3.rst:1173 cb418740247e4dd0888f57c4fec36ef6 +#: ../../en/units/lorawan_rui3.rst:1175 687ca8f4aa204dbc9a5c829285f6058a msgid "The encryption IV, represented as an 8-character hexadecimal string." msgstr "" -#: ../../en/units/lorawan_rui3.rst:1179 f3d1136ac3804fe3a3d72f55ac41161c +#: ../../en/units/lorawan_rui3.rst:1181 60502509c047407cb937aeea9dafff0c msgid "Get the current P2P parameters." msgstr "" -#: ../../en/units/lorawan_rui3.rst:1181 481359cfe97442cfba934e813cb80caf +#: ../../en/units/lorawan_rui3.rst:1183 f871527bfb0342b99fe0502486ef5815 msgid "The current P2P parameters as a string." msgstr "" -#: ../../en/units/lorawan_rui3.rst:1185 9b193eae2ccd4bfaa115555d6121ca4e +#: ../../en/units/lorawan_rui3.rst:1187 04ef528743854dacacb3eb61f7fcc247 msgid "" "Set P2P LoRa parameters, including frequency, spreading factor, " "bandwidth, code rate, preamble length, and transmit power." msgstr "" -#: ../../en/units/lorawan_rui3.rst:1187 40a400fa3bc3479b8a3f6cff26dcb9ff +#: ../../en/units/lorawan_rui3.rst:1189 5bd94e81992b47b7b1222e64e40fe0dc msgid "The frequency to use for communication, range 150000000-960000000." msgstr "" -#: ../../en/units/lorawan_rui3.rst:1188 345bd309c07842b2b7f4cc98d992e3d5 +#: ../../en/units/lorawan_rui3.rst:1190 2580a5586ec54558843132eef1c2505d msgid "The spreading factor, which can be {6, 7, 8, 9, 10, 11, 12}." msgstr "" -#: ../../en/units/lorawan_rui3.rst:1189 529f2ffce134485f9b6c200b4a26e5a3 +#: ../../en/units/lorawan_rui3.rst:1191 521ae31e4ffc44b2a35ab9f6cad637de msgid "The bandwidth, which can be {125, 250, 500}." msgstr "" -#: ../../en/units/lorawan_rui3.rst:1190 f06e326b14034685933416330d7bfdc1 +#: ../../en/units/lorawan_rui3.rst:1192 462f37b8c8c743e59412deafbde84643 msgid "The code rate, where possible values are {4/5=0, 4/6=1, 4/7=2, 4/8=3}." msgstr "" -#: ../../en/units/lorawan_rui3.rst:1191 8cf2faac0d0e4cf69a7688bc59b38a7b +#: ../../en/units/lorawan_rui3.rst:1193 a552c650ecad44ae85c9b4b00903400d msgid "The length of the preamble, which can be from 2 to 65535." msgstr "" -#: ../../en/units/lorawan_rui3.rst:1192 0e3b75a2f6c142a797f437a66b3e8418 +#: ../../en/units/lorawan_rui3.rst:1194 c0b488470fb3405a91e6855310076f17 msgid "The transmit power, which can be in the range {5-22}." msgstr "" -#: ../../en/units/lorawan_rui3.rst:1198 740042e15b1f46a9aad53cf3474300c5 +#: ../../en/units/lorawan_rui3.rst:1200 4d68f69a645841baaa8663cde497fa23 msgid "Get the current IQ inversion state in P2P mode." msgstr "" -#: ../../en/units/lorawan_rui3.rst:1200 4bd343627a5a4d75a1563ef876e5fab3 +#: ../../en/units/lorawan_rui3.rst:1202 3272620a4c164e8c9517c2f4bdfaaa66 msgid "The IQ inversion state as an integer (0 or 1)." msgstr "" -#: ../../en/units/lorawan_rui3.rst:1204 b8bb6e0c687a4b19a8e8651c6d9f1c18 +#: ../../en/units/lorawan_rui3.rst:1206 201aeed2f01e48259f4a109360f19761 msgid "Enable or disable IQ inversion in P2P mode." msgstr "" -#: ../../en/units/lorawan_rui3.rst:1206 b8c46b470b1d4f45ac8aab5d6049c6f0 +#: ../../en/units/lorawan_rui3.rst:1208 0d725afdee13494f857ad82f8f20d9be msgid "0 to disable, 1 to enable IQ inversion." msgstr "" -#: ../../en/units/lorawan_rui3.rst:1212 cdf5d3d5056f415f860f75737a7ca18c +#: ../../en/units/lorawan_rui3.rst:1214 18fa8735aaa34c16b78b6464fb85baf0 msgid "Get the current sync word in P2P mode." msgstr "" -#: ../../en/units/lorawan_rui3.rst:1214 53a6bb0a50324965afff1b786e7eb744 +#: ../../en/units/lorawan_rui3.rst:1216 1e4137e71631446396dc0357d24f907a msgid "The sync word as a string." msgstr "" -#: ../../en/units/lorawan_rui3.rst:1218 394cb40009d944a2b1fb58e1520a94b9 +#: ../../en/units/lorawan_rui3.rst:1220 9a187506f19948e9afd70625ee96595c msgid "|get_p2p_sync_word.png|" msgstr "" -#: ../../en/refs/unit.lorawan_rui3.ref:51 a3ce770700bf414d83d27634fb92b0aa +#: ../../en/refs/unit.lorawan_rui3.ref:51 8fc2a4dfda464e32bff6a3e59ff35d4a msgid "get_p2p_sync_word.png" msgstr "" -#: ../../en/units/lorawan_rui3.rst:1222 0b6a70e0fdaa4d5aa7353547004dccc3 +#: ../../en/units/lorawan_rui3.rst:1224 1081f45cd464448e86dab20d37a2c90e msgid "Set the sync word in P2P mode." msgstr "" -#: ../../en/units/lorawan_rui3.rst:1224 9abe5173cfab4716a4656e32f0d038c0 +#: ../../en/units/lorawan_rui3.rst:1226 ef5a08fac1b843328f81ae373fab79f7 msgid "The sync word value, which should be in the range of 0x0000 to 0xFFFF." msgstr "" -#: ../../en/units/lorawan_rui3.rst:1230 912604fe555544139ed6b11aa88a8f5d +#: ../../en/units/lorawan_rui3.rst:1232 acd4ca6e6e954b728114cceaa6484fbb msgid "|set_p2p_sync_word.png|" msgstr "" -#: ../../en/refs/unit.lorawan_rui3.ref:52 d60936b49b994e43bac6e8cfa9e0eff4 +#: ../../en/refs/unit.lorawan_rui3.ref:52 657f8a286aac4c0186a3fbd4e2926c7e msgid "set_p2p_sync_word.png" msgstr "" -#: ../../en/units/lorawan_rui3.rst:1234 19bdd616fa1d44e28bcf434b25e55926 +#: ../../en/units/lorawan_rui3.rst:1236 400cd961d7094c8a87028a35ea6568d2 msgid "Get the current symbol timeout in P2P mode." msgstr "" -#: ../../en/units/lorawan_rui3.rst:1236 2a410cd7de7c4d94a95de7a95ca491b9 +#: ../../en/units/lorawan_rui3.rst:1238 ac6f6526acbe49b6a787cd3f18c3866e msgid "The symbol timeout as an integer." msgstr "" -#: ../../en/units/lorawan_rui3.rst:1240 777c5c3bfa2a486eadd06f254f8374aa +#: ../../en/units/lorawan_rui3.rst:1242 61cd12bad4484c75aff103d250cad880 msgid "Set the symbol timeout in P2P mode." msgstr "" -#: ../../en/units/lorawan_rui3.rst:1242 07418e75be5546bdaf179bd489b2c5a7 +#: ../../en/units/lorawan_rui3.rst:1244 cea1591496e3496da9b9ea5c1cccab22 msgid "The timeout value, which should be in the range of 0-248." msgstr "" -#: ../../en/units/lorawan_rui3.rst:1248 bd4fca1e04e04ebf9364a5319adefd84 +#: ../../en/units/lorawan_rui3.rst:1250 be4dcff7287d4e8183901f613064ec0f msgid "Get the current fixed length payload state in P2P mode." msgstr "" -#: ../../en/units/lorawan_rui3.rst:1250 324784b7eeaa42d2a65e5da4a20e8b03 +#: ../../en/units/lorawan_rui3.rst:1252 ffbe80bd96164d92b3bd5e2e78973731 msgid "The fixed length payload state as an integer (0 or 1)." msgstr "" -#: ../../en/units/lorawan_rui3.rst:1254 909d64d7b72d4dd6aab058a2fd9e897c +#: ../../en/units/lorawan_rui3.rst:1256 c5a3bfcf4f6c43588e04af494bd69b5f msgid "Enable or disable fixed length payload in P2P mode." msgstr "" -#: ../../en/units/lorawan_rui3.rst:1256 b1f01225b3e94a9ea2a93def66949120 +#: ../../en/units/lorawan_rui3.rst:1258 73041d603cc1468f8e635791ce953812 msgid "0 to disable, 1 to enable fixed length payload." msgstr "" diff --git a/docs/locales/zh_CN/LC_MESSAGES/units/miniscale.po b/docs/locales/zh_CN/LC_MESSAGES/units/miniscale.po index 0bf1855b..dbaa45f1 100644 --- a/docs/locales/zh_CN/LC_MESSAGES/units/miniscale.po +++ b/docs/locales/zh_CN/LC_MESSAGES/units/miniscale.po @@ -9,7 +9,7 @@ msgid "" msgstr "" "Project-Id-Version: UIFlow2 Programming Guide \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-01-16 15:35+0800\n" +"POT-Creation-Date: 2025-04-18 15:58+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language: zh_CN\n" @@ -20,11 +20,11 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.16.0\n" -#: ../../en/units/miniscale.rst:2 0f07b043b8f949b1a128dbb323ba8d4f +#: ../../en/units/miniscale.rst:2 cb191df082804ef4a8ff4daaa208f7b9 msgid "Miniscale Unit" msgstr "" -#: ../../en/units/miniscale.rst:6 9f9bd052a5f843a382cd51369973fd48 +#: ../../en/units/miniscale.rst:6 553b5fe9c6994505a517b4e4240f4072 msgid "" "The ``Miniscale`` class is designed for interfacing with a mini scale " "weight sensor, which includes a HX711 22-bit ADC. This sensor is capable " @@ -34,47 +34,47 @@ msgstr "" "``Miniscale`` 是为微型称重传感器设计的接口,其中包括一个HX711 " "22位ADC。此传感器能够测量重量,并且还包括其他功能,如LED控制和各种滤波器。" -#: ../../en/units/miniscale.rst:8 f28d313b74fb42058e207f6a879c6c70 +#: ../../en/units/miniscale.rst:8 f0bceb30305e4cb4b6fb9b50eee58794 msgid "Support the following products:" msgstr "支持以下产品:" -#: ../../en/units/miniscale.rst:11 9221e5d77b6c492e96468316c1a93ea7 +#: ../../en/units/miniscale.rst:11 b9710dba3ec8471d828122615b69258a msgid "|MINISCALE|" msgstr "" -#: ../../en/refs/unit.miniscale.ref 4b12053d2d734933a660d1b43b65474c +#: ../../en/refs/unit.miniscale.ref 91b21480c07742dd84c62d680fc06a42 msgid "MINISCALE" msgstr "" -#: ../../en/units/miniscale.rst:15 190bda458f0643008ec22e6fffde3922 +#: ../../en/units/miniscale.rst:15 1b52a066919b4bfd81c9240d2a274090 msgid "Micropython Example::" msgstr "" -#: ../../en/units/miniscale.rst:29 6b63a8dfe5e44e3ba7c260c2863a73f0 +#: ../../en/units/miniscale.rst:29 9fee616278fc4b89864b4e4a56b24619 msgid "UIFLOW2 Example:" msgstr "" -#: ../../en/units/miniscale.rst:31 49cbea3267a7475789a9a56fdc43b6db -msgid "|example.svg|" +#: ../../en/units/miniscale.rst:31 cdc1b0091a5e4522ac0c3dd3514167eb +msgid "|example.png|" msgstr "" -#: ../../en/refs/unit.miniscale.ref:34 7814c731e799497292d4c391c4519a28 -msgid "example.svg" +#: ../../en/refs/unit.miniscale.ref:34 09c6f381d09e47b38a2b435d1da06254 +msgid "example.png" msgstr "" -#: ../../en/units/miniscale.rst:38 0234008f081f4ed18dc81e60ab2b7878 +#: ../../en/units/miniscale.rst:38 e646f58bb6f244ebbb7955f910c2f05a msgid "class MiniScaleUnit" msgstr "" -#: ../../en/units/miniscale.rst:41 22238101a64147b19348c565a2622360 +#: ../../en/units/miniscale.rst:41 08bb6f07f5ca47988a600d5c2b603472 msgid "Constructors" msgstr "" -#: ../../en/units/miniscale.rst:45 36cdf84a0e834aeeb41bdfff25af04da +#: ../../en/units/miniscale.rst:45 b5d0957900f946c7aadbeeb7bbbd1bfb msgid "Create an MiniScaleUnit object." msgstr "创建一个 MiniScaleUnit 对象。" -#: ../../en/units/miniscale.rst:47 0a40aedcd1c54a9db986d014f359cfee +#: ../../en/units/miniscale.rst:47 bc534ecbe9864c2f978ca0595c303a32 msgid "``I2C0`` is I2C Port." msgstr "``I2C0`` is I2C Port." @@ -85,245 +85,337 @@ msgstr "``I2C0`` is I2C Port." #: ../../en/units/miniscale.rst:142 ../../en/units/miniscale.rst:152 #: ../../en/units/miniscale.rst:162 ../../en/units/miniscale.rst:170 #: ../../en/units/miniscale.rst:182 ../../en/units/miniscale.rst:190 -#: 1953fc25a2ee48a4af654b658944f382 34374985275146af91697d63e129f316 -#: 3e9bd31246c744b1b7ecd4094dedc8eb 487e92d7300145668e4767673bd3bcf1 -#: 4a80ea558a2e4e57937026b166135c59 4f0585a9fce24ff68bdbe7b36d3364e0 -#: 4fa554c595284a4eb180df15e800c1f8 534243eb2fa04842a751edbc343b8a9a -#: 5c81a2d19c1f4c87bda29b4449d27128 6c017dfac5834885a0eea16bd69c37ba -#: 88a2af97202549c495fdc13ec1f2cf1e 895e3ecbf1f24c9aa8fea6f2a81664a1 -#: ca66d9eb09b549b88f40fd8f8b434855 f2ca1771bcba4ff29611154c4fa72ea1 +#: 0a550e55796245cf9a4ee69e640070c7 2a76747a028546a38a925b7be305d18f +#: 3abd720fc10b4358b279ad641f1a2521 3ceba0716e274d8fb4ac902359c1625c +#: 3f41ca6121764710ada34fd71904b26b 4be817bc3f6e4afa9d5a88b4305d2bf3 +#: 77cb52295f9d42e1afe4f7b057e2ac1f c39a3c532aee4514824c5747c21091d5 +#: c7ba30908b6b4c82be444f8a64e020cd d5aaf28a1b744fc9a67bbef7766c4f14 +#: d6b4b1a83efe4a42a9124a326cfdd787 dd5b3a652a834f7094052cd592e93320 +#: dea68305a79b45f9b06ddabd684d6278 f03a26142c634a0899934f02c9744248 msgid "UIFLOW2:" msgstr "" -#: ../../en/units/miniscale.rst:52 94966d604d12467c8f07d4937a9de54e -msgid "|init.svg|" +#: ../../en/units/miniscale.rst:52 30c2a3dc405c48319189d77a676a4f0b +msgid "|init.png|" msgstr "" -#: ../../en/refs/unit.miniscale.ref:6 99b29a09485a4533be58d7663b475af3 -msgid "init.svg" +#: ../../en/refs/unit.miniscale.ref:6 0e745bc79f6e4e1dbe18bd8660bce5ec +msgid "init.png" msgstr "" -#: ../../en/units/miniscale.rst:56 ca8ada4c4db04b80878523bced04f219 +#: ../../en/units/miniscale.rst:56 f9e769c7df844b319e7d638cd7cb7a6c msgid "Methods" msgstr "" -#: ../../en/units/miniscale.rst:61 cc5798ce8dde4c93a7a77ff3f04ca8e3 +#: ../../en/units/miniscale.rst:61 ae2ed66e50564eb886a79f17716ee843 msgid "Gets the raw adc readout." msgstr "获取原始的 ADC 读数。" -#: ../../en/units/miniscale.rst:65 f7bbf9cfacd34ac39cf9931612e19397 -msgid "|get_adc.svg|" +#: ../../en/units/miniscale.rst:65 fa540129ae554bf3827b6f38f289d7b2 +msgid "|get_adc.png|" msgstr "" -#: ../../en/refs/unit.miniscale.ref:18 812f5e8c065447a783fdf70c04c34c24 -msgid "get_adc.svg" +#: ../../en/refs/unit.miniscale.ref:18 78c6c950063142e7bf43557198204bb6 +msgid "get_adc.png" msgstr "" -#: ../../en/units/miniscale.rst:70 c97a585bb2744168b6bae58dc3c7af25 +#: ../../en/units/miniscale.rst:70 1d940438409545abad533129e5d79024 msgid "Gets the weight readout in grams." msgstr "获取以克为单位的重量读数。" -#: ../../en/units/miniscale.rst:74 3c097f4d2d004f548d073ea0014f281a -msgid "|get_weight.svg|" +#: ../../en/units/miniscale.rst:74 bec7efa63604461192b57d435a5a18f8 +msgid "|get_weight.png|" msgstr "" -#: ../../en/refs/unit.miniscale.ref:22 a288a4172c874c50bd30a3de059e54dd -msgid "get_weight.svg" +#: ../../en/refs/unit.miniscale.ref:22 800f45d58f47461e918224233b8f3d90 +msgid "get_weight.png" msgstr "" -#: ../../en/units/miniscale.rst:80 04f307d54ed94c439f6d77f50674d324 +#: ../../en/units/miniscale.rst:80 b585e00644114f99ae4816c4825ac016 msgid "Gets the button state." msgstr "获取按钮状态。 " -#: ../../en/units/miniscale.rst:84 1ca882c7d1384f19895afdb54e8370a0 -msgid "|get_button.svg|" +#: ../../en/units/miniscale.rst:84 baf8e2a22d404ca9ad3d115ab8429cbe +msgid "|get_button.png|" msgstr "" -#: ../../en/refs/unit.miniscale.ref:20 4916aa4e56c2445ca92f2f788e248e4d -msgid "get_button.svg" +#: ../../en/refs/unit.miniscale.ref:20 dcf39b4c0669439fa6ed295ccddb5586 +msgid "get_button.png" msgstr "" -#: ../../en/units/miniscale.rst:89 4b28a0551f434469849624d7e9d2a1e1 +#: ../../en/units/miniscale.rst:89 ac94d56cfd634f5e9289907823c30096 msgid "Tare the scale." msgstr "" -#: ../../en/units/miniscale.rst:93 855fcbb167484c03aa6736ae254c08b6 -msgid "|tare.svg|" +#: ../../en/units/miniscale.rst:93 3b098a901e0743019e932684ecd94b29 +msgid "|tare.png|" msgstr "" -#: ../../en/refs/unit.miniscale.ref:10 87d469cf83bd4ceea5a7827ea21a65fb -msgid "tare.svg" +#: ../../en/refs/unit.miniscale.ref:10 fc8f4b7b5ffd44fe8cce785c26faebf0 +msgid "tare.png" msgstr "" -#: ../../en/units/miniscale.rst:97 d8f1aa1cc9624d858a0f058a4a83151c +#: ../../en/units/miniscale.rst:97 41177a0bb1ee45d59a3f994bc5d8ee10 msgid "Sets the RGB LED color." msgstr "设置 RGB LED 的颜色。" -#: ../../en/units/miniscale.rst:99 331fc0e72f0e40e0b8352705ae67cd50 +#: ../../en/units/miniscale.rst:99 6040786e7aa04237bf268060f9a51c47 msgid "``r``: Red value (0 - 255)." msgstr "``r``: Red value (0 - 255)." -#: ../../en/units/miniscale.rst:100 45db67b449f14a2c85d6c2cd36683c42 +#: ../../en/units/miniscale.rst:100 e78c8ac199b5493fa2786eb2bb0fb462 msgid "``g``: Green value (0 - 255)." msgstr "``g``: Green value (0 - 255)." -#: ../../en/units/miniscale.rst:101 217d8764bb6a4686b1dadc9aeb5fcd85 +#: ../../en/units/miniscale.rst:101 183968723565492784ce3b772d0ea76e msgid "``b``: Blue value (0 - 255)." msgstr "``b``: Blue value (0 - 255)." -#: ../../en/units/miniscale.rst:105 ea0e2cd236684e498e19bf8d31ce6400 -msgid "|setLed.svg|" +#: ../../en/units/miniscale.rst:105 f2089a2f6c5e4b99be34957a45dd9ace +msgid "|setLed.png|" msgstr "" -#: ../../en/refs/unit.miniscale.ref:30 72466e568f2e45bda776ec69712abac1 -msgid "setLed.svg" +#: ../../en/refs/unit.miniscale.ref:30 669a4f1cf6ce44eb96f9bcd99df1bc27 +msgid "setLed.png" msgstr "" -#: ../../en/units/miniscale.rst:109 ce6a4715c55d41528ec8964d28ba895e +#: ../../en/units/miniscale.rst:109 a4472c7367e041ecbfaea91472332bca msgid "Resets sensor." msgstr "重置传感器。" -#: ../../en/units/miniscale.rst:114 fe7d4ce4214c4f8ea02a966264b33cbb -msgid "|reset.svg|" +#: ../../en/units/miniscale.rst:114 7968b647e0334557814eb05bdfc36f63 +msgid "|reset.png|" msgstr "" -#: ../../en/refs/unit.miniscale.ref:24 a03d2deb51584bbba576da6eed522fe1 -msgid "reset.svg" +#: ../../en/refs/unit.miniscale.ref:24 f53a481663c44e3390cf4b0c7c979ede +msgid "reset.png" msgstr "" -#: ../../en/units/miniscale.rst:119 f4cb4458efdd4415ab5f78693dfbc881 +#: ../../en/units/miniscale.rst:119 140108aedf3d47b985fa9a30b2cd8a5a msgid "Calibrates the MiniScale sensor." msgstr "校准微型称重传感器。" -#: ../../en/units/miniscale.rst:121 addce5312b904f28afc934b42dc37e9b +#: ../../en/units/miniscale.rst:121 fe9fd18345ab425591cb11ca5968cc1a msgid "``weight1_g``: Weight1 in grams." msgstr "" -#: ../../en/units/miniscale.rst:122 c3dbd3a7020f4ff083d85790092a7d16 +#: ../../en/units/miniscale.rst:122 4dfeea577e474dea844d407b4848001b msgid "``weight1_adc``: Weight1 in ADC value." msgstr "" -#: ../../en/units/miniscale.rst:123 08c196b147c94a4987012fbefaf43dde +#: ../../en/units/miniscale.rst:123 06402f3db0594dffa89ad6c81fce2795 msgid "``weight2_g``: Weight2 in grams." msgstr "" -#: ../../en/units/miniscale.rst:124 af8015e899ad4dbc9b844b3043e2c433 +#: ../../en/units/miniscale.rst:124 cd644f8f2fe84e38ade3a23db990d145 msgid "``weight2_adc``: Weight2 in ADC value." msgstr "" -#: ../../en/units/miniscale.rst:126 f399723d76cb49f49ea9f3a1d5399764 +#: ../../en/units/miniscale.rst:126 69bcd897885946a2b78b8cc54a8a83b3 msgid "calibration steps:" msgstr "校准步骤:" -#: ../../en/units/miniscale.rst:128 6e703b77643a4e53aa0ca04270d55fb7 +#: ../../en/units/miniscale.rst:128 f71ab2747e1c4036b4418a85237af5a7 msgid "Reset sensor;" msgstr "重置传感器" -#: ../../en/units/miniscale.rst:129 936d31788b90429a9080452693c25dfb +#: ../../en/units/miniscale.rst:129 8fddb44435b44810a098de9f76272777 msgid "Get adc, this is weight1_adc (should be zero). And weight1_g is also 0." msgstr "得到adc,这是weight1_adc(应该是零)。而weight1_g也是0。" -#: ../../en/units/miniscale.rst:130 97ad385fd60b418ab718e37618befd96 +#: ../../en/units/miniscale.rst:130 80779f98aee34659b9b7f347e639e7dc msgid "" "Put some weight on it, then get adc, this is weight2_adc. And weight2_g " "is weight in gram you put on it." msgstr "AI润色104/5000给它加一些权重,然后得到adc,这是weight2_adc。而weight2_g是以克为单位的重量。" -#: ../../en/units/miniscale.rst:135 e9a8b10947c345b6a3d975b6a957ae13 -msgid "|calibration.svg|" -msgstr "" +#: ../../en/units/miniscale.rst:135 2ac5a189c3334d40a565cd8bce5c6291 +#, fuzzy +msgid "|calibration.png|" +msgstr "校准步骤:" -#: ../../en/refs/unit.miniscale.ref:8 5e8b8124ead24b4d962eb3a817f384b2 -msgid "calibration.svg" -msgstr "" +#: ../../en/refs/unit.miniscale.ref:8 3cde5a5480494c94bb03489f2f50a0fe +#, fuzzy +msgid "calibration.png" +msgstr "校准步骤:" -#: ../../en/units/miniscale.rst:139 6e1d296a3b7d4b588984c19f6a1bf440 +#: ../../en/units/miniscale.rst:139 335d4f778e0f4449979059b04156120d msgid "Enables or disables the low pass filter." msgstr "启用或禁用低通滤波器" -#: ../../en/units/miniscale.rst:144 9a15b366f54e4727947012c30ce47449 -msgid "|setLowPassFilter.svg|" +#: ../../en/units/miniscale.rst:144 54a8c46824ae46c088bc2c11223ee916 +msgid "|setLowPassFilter.png|" msgstr "" -#: ../../en/refs/unit.miniscale.ref:32 41ba08b032d644d5919b5293ef7348ea -msgid "setLowPassFilter.svg" +#: ../../en/refs/unit.miniscale.ref:32 f995623c681446948c7e8899faa113dd +msgid "setLowPassFilter.png" msgstr "" -#: ../../en/units/miniscale.rst:149 d35e141e4292490c9088f2da5f6074b8 +#: ../../en/units/miniscale.rst:149 f35a8a925134409493be750cd65723f6 msgid "Returns the status of the low pass filter (enabled or not)." msgstr "返回低通滤波器的状态(是否启用)" -#: ../../en/units/miniscale.rst:154 01d4555a0f314735a2927c149aa846e5 -msgid "|getLowPassFilter.svg|" +#: ../../en/units/miniscale.rst:154 4d39da164bd948e9b064b2f04f291a0f +msgid "|getLowPassFilter.png|" msgstr "" -#: ../../en/refs/unit.miniscale.ref:16 ba20a06330f54fdbb8cfcb3e14131fdb -msgid "getLowPassFilter.svg" +#: ../../en/refs/unit.miniscale.ref:16 edef98ebff8f456984cfad27b626c244 +msgid "getLowPassFilter.png" msgstr "" -#: ../../en/units/miniscale.rst:158 06767b0cf46d47eca3cd42274cc25265 +#: ../../en/units/miniscale.rst:158 79d3626162d8434aa1349bcc5a9764ff msgid "Sets the level of the average filter." msgstr "设置平均滤波器的级别" -#: ../../en/units/miniscale.rst:160 c236288ae9ce4ae8a04598b677fdc114 +#: ../../en/units/miniscale.rst:160 6d7dc6ab634f4aa29a03f55bd2efd46b msgid "" "``level``: Level of the average filter (0 - 50). Larger value for " "smoother result but more latency" msgstr "``level``:平均过滤器的电平(0 - 50)。更大的值可以获得更平滑的结果,但延迟更长" -#: ../../en/units/miniscale.rst:164 8d21b8663d7c4916ae96896b5d09c606 -msgid "|setAverageFilterLevel.svg|" +#: ../../en/units/miniscale.rst:164 1d58c8234ed446ab87fb8c467f76b7ee +msgid "|setAverageFilterLevel.png|" msgstr "" -#: ../../en/refs/unit.miniscale.ref:26 911e4b6830d546bc8eec35733b57772a -msgid "setAverageFilterLevel.svg" +#: ../../en/refs/unit.miniscale.ref:26 cc8d797eaf6140bc81d407f0eb623b92 +msgid "setAverageFilterLevel.png" msgstr "" -#: ../../en/units/miniscale.rst:168 9e90b298e0be4c41b81cb4c370181d95 +#: ../../en/units/miniscale.rst:168 d5973abdcdc341dabaaa2bfd6aff85d9 msgid "Returns the level of the average filter." msgstr "返回平均滤波器的级别" -#: ../../en/units/miniscale.rst:172 c33103e2cbae4e8d9d4d66d46c3429e3 -msgid "|getAverageFilterLevel.svg|" +#: ../../en/units/miniscale.rst:172 e0fea83835cf4c17a37b0c94e8cf4734 +msgid "|getAverageFilterLevel.png|" msgstr "" -#: ../../en/refs/unit.miniscale.ref:12 357ae698bc9d4fb5890d8eb5ed24653d -msgid "getAverageFilterLevel.svg" +#: ../../en/refs/unit.miniscale.ref:12 904db8a2384c4cc08c3b5b4e37f9336f +msgid "getAverageFilterLevel.png" msgstr "" -#: ../../en/units/miniscale.rst:176 57d72d9d9048418991a3ea3fef4ffc28 +#: ../../en/units/miniscale.rst:176 57f5c9f52fdb4921ae534cf247edbbad msgid "Sets the alpha value for the EMA filter." msgstr "设置EMA滤波器的alpha值。" -#: ../../en/units/miniscale.rst:178 d2887caef9e749daabfe8e3c8b89c54a +#: ../../en/units/miniscale.rst:178 8327385268954b628b39f59509c41b72 msgid "" "The EMA (Exponential Moving Average) filter is more sensitive to changes " "in data compared to the average filter." msgstr "与平均滤波器相比,EMA(指数移动平均)滤波器对数据的变化更敏感。" -#: ../../en/units/miniscale.rst:180 adaade4eab274ab28ad09acf0b5a1b1a +#: ../../en/units/miniscale.rst:180 a90ec332c6494a0898cba5b89c716933 msgid "" "``alpha``: Alpha value for the EMA filter (0 - 99). Smaller value for " "smoother result but more latency" msgstr "`` alpha``: EMA过滤器的alpha值(0 - 99)。较小的值可以获得更平滑的结果,但会增加延迟" -#: ../../en/units/miniscale.rst:184 d1207db77608463bac791febad071c6b -msgid "|setEMAFilterAlpha.svg|" +#: ../../en/units/miniscale.rst:184 92491b1188d7436fa428cff094913225 +msgid "|setEMAFilterAlpha.png|" msgstr "" -#: ../../en/refs/unit.miniscale.ref:28 ebf0fce30b344528adf9949e624b2f44 -msgid "setEMAFilterAlpha.svg" +#: ../../en/refs/unit.miniscale.ref:28 8a457529a6fc43a9bb1d65e830ecb1df +msgid "setEMAFilterAlpha.png" msgstr "" -#: ../../en/units/miniscale.rst:188 6dd9a2f98a7b442a9f39004a189d39bc +#: ../../en/units/miniscale.rst:188 366a404460d646bf8e0acce59ee27ea1 msgid "Returns the alpha value for the EMA filter." msgstr "返回EMA滤波器的alpha值。" -#: ../../en/units/miniscale.rst:192 a983509322b949699ad9e482b22753f4 -msgid "|getEMAFilterAlpha.svg|" +#: ../../en/units/miniscale.rst:192 6c900ab9aa534a729385e84785d53951 +msgid "|getEMAFilterAlpha.png|" msgstr "" -#: ../../en/refs/unit.miniscale.ref:14 ddb830a3a2bf4aac9c94345367320403 -msgid "getEMAFilterAlpha.svg" +#: ../../en/refs/unit.miniscale.ref:14 8ddbbea257524c60953feaa004754501 +msgid "getEMAFilterAlpha.png" msgstr "" +#~ msgid "|example.svg|" +#~ msgstr "" + +#~ msgid "example.svg" +#~ msgstr "" + +#~ msgid "|init.svg|" +#~ msgstr "" + +#~ msgid "init.svg" +#~ msgstr "" + +#~ msgid "|get_adc.svg|" +#~ msgstr "" + +#~ msgid "get_adc.svg" +#~ msgstr "" + +#~ msgid "|get_weight.svg|" +#~ msgstr "" + +#~ msgid "get_weight.svg" +#~ msgstr "" + +#~ msgid "|get_button.svg|" +#~ msgstr "" + +#~ msgid "get_button.svg" +#~ msgstr "" + +#~ msgid "|tare.svg|" +#~ msgstr "" + +#~ msgid "tare.svg" +#~ msgstr "" + +#~ msgid "|setLed.svg|" +#~ msgstr "" + +#~ msgid "setLed.svg" +#~ msgstr "" + +#~ msgid "|reset.svg|" +#~ msgstr "" + +#~ msgid "reset.svg" +#~ msgstr "" + +#~ msgid "|calibration.svg|" +#~ msgstr "" + +#~ msgid "calibration.svg" +#~ msgstr "" + +#~ msgid "|setLowPassFilter.svg|" +#~ msgstr "" + +#~ msgid "setLowPassFilter.svg" +#~ msgstr "" + +#~ msgid "|getLowPassFilter.svg|" +#~ msgstr "" + +#~ msgid "getLowPassFilter.svg" +#~ msgstr "" + +#~ msgid "|setAverageFilterLevel.svg|" +#~ msgstr "" + +#~ msgid "setAverageFilterLevel.svg" +#~ msgstr "" + +#~ msgid "|getAverageFilterLevel.svg|" +#~ msgstr "" + +#~ msgid "getAverageFilterLevel.svg" +#~ msgstr "" + +#~ msgid "|setEMAFilterAlpha.svg|" +#~ msgstr "" + +#~ msgid "setEMAFilterAlpha.svg" +#~ msgstr "" + +#~ msgid "|getEMAFilterAlpha.svg|" +#~ msgstr "" + +#~ msgid "getEMAFilterAlpha.svg" +#~ msgstr "" + diff --git a/docs/locales/zh_CN/LC_MESSAGES/units/op180.po b/docs/locales/zh_CN/LC_MESSAGES/units/op180.po index 67556ba5..654dd9bb 100644 --- a/docs/locales/zh_CN/LC_MESSAGES/units/op180.po +++ b/docs/locales/zh_CN/LC_MESSAGES/units/op180.po @@ -9,7 +9,7 @@ msgid "" msgstr "" "Project-Id-Version: UIFlow2 Programming Guide \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-01-16 15:35+0800\n" +"POT-Creation-Date: 2025-04-18 15:58+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language: zh_CN\n" @@ -20,103 +20,107 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.16.0\n" -#: ../../en/units/op180.rst:2 b0a61e6fe3814a77a69798e6d35a97da +#: ../../en/units/op180.rst:2 ecfe27b83e3842ca93f5e18838930d88 msgid "OP180 Unit" msgstr "" -#: ../../en/units/op180.rst:6 f707464649a84d8fadbb16dccd68a1ae +#: ../../en/units/op180.rst:6 00fbf6f2cc8b45a4bc7cba720525761e msgid "The following products are supported:" msgstr "支持以下产品:" -#: ../../en/units/op180.rst:8 4dc7b670b4714a51b55ef8ddf7761fd4 +#: ../../en/units/op180.rst:8 fb32e03ba8c143c6a72f53a1a52fabc3 msgid "|OP180|" msgstr "" -#: ../../en/refs/unit.op180.ref 3044d033b08b4bc58c265ee3d8fa62f0 +#: ../../en/refs/unit.op180.ref 068f61a553ef4fd2922b55f522e056e0 msgid "OP180" msgstr "" -#: ../../en/units/op180.rst:11 affe5a2ea665457786b4b4475661af9a +#: ../../en/units/op180.rst:11 97101d2c365d4a8dbd4829862fac760d msgid "Micropython Example::" msgstr "" -#: ../../en/units/op180.rst:36 3c0d727f381c4ad8a2cd241d15acc8ff +#: ../../en/units/op180.rst:36 b479af5fc5a14530a6eb88c753e787b6 msgid "UIFLOW2 Example:" msgstr "" -#: ../../en/units/op180.rst:38 faf1338a89d945e8a7d8a77c95238acc -msgid "|example.svg|" +#: ../../en/units/op180.rst:38 fdaa91243c464c70a89e112cb498dbc1 +msgid "|example.png|" msgstr "" -#: ../../en/refs/unit.op180.ref:6 f9389614d06849929f7875b3422289b7 -msgid "example.svg" +#: ../../en/refs/unit.op180.ref:6 792f19ae750a439e92904c44f06a2e46 +msgid "example.png" msgstr "" -#: ../../en/units/op180.rst:43 466f01fe429741f187d241a2767b0120 +#: ../../en/units/op180.rst:43 393040fb7cfd4028a9cd3997cf8c26fb msgid "|op180_core_example.m5f2|" msgstr "" -#: ../../en/units/op180.rst:47 9dc7558b7478436eb86be4a0f6f8ef7e +#: ../../en/units/op180.rst:47 ab1fa8b54feb409187841bb3701ad699 msgid "class OPUnit" msgstr "" -#: ../../en/units/op180.rst:50 f077fe831ead4d0e8ba42af0003f62d3 +#: ../../en/units/op180.rst:50 07a292bec7ec47dda2fbde43d8fb6f12 msgid "Constructors" msgstr "" -#: ../../en/units/op180.rst:54 ffd166907f0b42beb2b782ed48ccc021 +#: ../../en/units/op180.rst:54 da179d7f1d8645b4be5fa18b4454132b msgid "Create an OP180 object." msgstr "创建一个OP180对象." -#: ../../en/units/op180.rst:57 3f25797d61b744fd8910d308c5e7c5cf +#: ../../en/units/op180.rst:57 f4a35c8b457949898e10f522564864d9 msgid "The parameters is:" msgstr "参数如下:" -#: ../../en/units/op180.rst:57 ac25b56136e143009bf2a1085c526e02 +#: ../../en/units/op180.rst:57 a5587eebd6b04026b5b254bb756a1c84 msgid "``io`` is the detection pin." msgstr "``io`` 为检测引脚。" #: ../../en/units/op180.rst:59 ../../en/units/op180.rst:71 -#: ../../en/units/op180.rst:79 0a9e54e833684b058782924e84fab7c1 -#: a330f56039b146838668172457520b91 c124ad2615ca47d283ec7dd15b9b3561 +#: ../../en/units/op180.rst:79 8000a0c500d74d6cbcab6816e77c10c6 +#: a156d3a992ff408da4e017fba2549a10 dfa6eb35f1294996a4489b02f327c266 msgid "UIFLOW2:" msgstr "" -#: ../../en/units/op180.rst:61 5f644b8af1e84c6f8eca69af32bc6273 -msgid "|init.svg|" +#: ../../en/units/op180.rst:61 9d3d77bd0de44fc880b0656e2302f011 +msgid "|init.png|" msgstr "" -#: ../../en/refs/unit.op180.ref:8 0fe36e7d795c4abd8a10bd1254259662 -msgid "init.svg" +#: ../../en/refs/unit.op180.ref:8 2604a4a9d55f4656a263e67c78a87cb0 +msgid "init.png" msgstr "" -#: ../../en/units/op180.rst:65 36afcfe038ca448a809d694c0b4e236c +#: ../../en/units/op180.rst:65 fb816aa554eb446888a883701560b15d msgid "Methods" msgstr "" -#: ../../en/units/op180.rst:69 d99fbccb7f92436284953b8338c027c6 +#: ../../en/units/op180.rst:69 6b95764540d74cf795a449f611194b23 msgid "Get the count value." msgstr "获取计数值。" -#: ../../en/units/op180.rst:73 062c2f0c6ebd4ae886b91618d1fdeda2 -msgid "|get_count_value.svg|" -msgstr "" +#: ../../en/units/op180.rst:73 02a47b78bfd54576bafd107ae3b466f1 +#, fuzzy +msgid "|get_count_value.png|" +msgstr "获取计数值。" -#: ../../en/refs/unit.op180.ref:10 638bc15f891a46a69cc4db1a407b9ef0 -msgid "get_count_value.svg" -msgstr "" +#: ../../en/refs/unit.op180.ref:10 f7f5f944c2fd4269ae44fc049fe53097 +#, fuzzy +msgid "get_count_value.png" +msgstr "获取计数值。" -#: ../../en/units/op180.rst:77 6e5d07c5ce7a4529bfe7ce1b01f9021c +#: ../../en/units/op180.rst:77 910fd705918642c9bc2f8eaf5f8e9a8a msgid "Reset the meter value." msgstr "重置计数值。" -#: ../../en/units/op180.rst:81 064c748bd1d943b4a165899b7e4db13e -msgid "|reset_count_value.svg|" -msgstr "" +#: ../../en/units/op180.rst:81 995d6540a21e4a6c8f3e2431b6a95478 +#, fuzzy +msgid "|reset_count_value.png|" +msgstr "获取计数值。" -#: ../../en/refs/unit.op180.ref:12 a1ba047d7e064cae8b9adfb6cbb25860 -msgid "reset_count_value.svg" -msgstr "" +#: ../../en/refs/unit.op180.ref:12 ee36a10d30414c1da986237c8f394a69 +#, fuzzy +msgid "reset_count_value.png" +msgstr "获取计数值。" #~ msgid "get_switch_value.svg" #~ msgstr "" @@ -127,3 +131,27 @@ msgstr "" #~ msgid "|get_switch_value.svg|" #~ msgstr "" +#~ msgid "|example.svg|" +#~ msgstr "" + +#~ msgid "example.svg" +#~ msgstr "" + +#~ msgid "|init.svg|" +#~ msgstr "" + +#~ msgid "init.svg" +#~ msgstr "" + +#~ msgid "|get_count_value.svg|" +#~ msgstr "" + +#~ msgid "get_count_value.svg" +#~ msgstr "" + +#~ msgid "|reset_count_value.svg|" +#~ msgstr "" + +#~ msgid "reset_count_value.svg" +#~ msgstr "" + diff --git a/docs/locales/zh_CN/LC_MESSAGES/units/op90.po b/docs/locales/zh_CN/LC_MESSAGES/units/op90.po index 72dafc0b..8c5daf7e 100644 --- a/docs/locales/zh_CN/LC_MESSAGES/units/op90.po +++ b/docs/locales/zh_CN/LC_MESSAGES/units/op90.po @@ -9,7 +9,7 @@ msgid "" msgstr "" "Project-Id-Version: UIFlow2 Programming Guide \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-01-16 15:35+0800\n" +"POT-Creation-Date: 2025-04-18 15:58+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language: zh_CN\n" @@ -20,103 +20,107 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.16.0\n" -#: ../../en/units/op90.rst:2 3bf3aafecd9b4beea475132ab5155250 +#: ../../en/units/op90.rst:2 bdc559a7111a4b959e1a6cc3a7222090 msgid "OP90 Unit" msgstr "" -#: ../../en/units/op90.rst:6 e27a3ec9230a4c0580f4c1ae52dbd4cd +#: ../../en/units/op90.rst:6 727612ade21541ca936f2a6ea3cae11b msgid "The following products are supported:" msgstr "支持以下产品" -#: ../../en/units/op90.rst:8 45ab8cc43faf48c8a13aa081913ba849 +#: ../../en/units/op90.rst:8 69df8f9dc5ae47f69466a08c13609c2c msgid "|OP90|" msgstr "" -#: ../../en/refs/unit.op90.ref 904e0fcfcb3d4104b814438d7012297b +#: ../../en/refs/unit.op90.ref 5ccbbbda01ea4632ba61c4d63dd8d3c9 msgid "OP90" msgstr "" -#: ../../en/units/op90.rst:11 92b97ca5bed949b9896841e0e7be0d5d +#: ../../en/units/op90.rst:11 3b571d9402844531890eac4506cf21b2 msgid "Micropython Example::" msgstr "" -#: ../../en/units/op90.rst:39 a9db8a96b8f641b8ba1fe2cb6fe970e9 +#: ../../en/units/op90.rst:39 921b898d96df4fcfa97d765fbf643b2f msgid "UIFLOW2 Example:" msgstr "" -#: ../../en/units/op90.rst:41 6326593e77074daea9fa603a020d96ed -msgid "|example.svg|" +#: ../../en/units/op90.rst:41 43bde89eb8c64d91b5c8b46181433f0d +msgid "|example.png|" msgstr "" -#: ../../en/refs/unit.op90.ref:6 69a713489be845a4bae0bba01fdc0850 -msgid "example.svg" +#: ../../en/refs/unit.op90.ref:6 e5c3a1aa4cb54462a56ebd6c07a2e4e9 +msgid "example.png" msgstr "" -#: ../../en/units/op90.rst:46 dff3ef5de1d9483a8106f5513029558b +#: ../../en/units/op90.rst:46 caa9bc434d184dbba769871c6c96101b msgid "|op90_core_example.m5f2|" msgstr "" -#: ../../en/units/op90.rst:50 ca33a08fd43d493a81611ed238f82a90 +#: ../../en/units/op90.rst:50 0d03167c8d35424ea9dd0d105f7dce7c msgid "class OPUnit" msgstr "" -#: ../../en/units/op90.rst:53 3ade9dae69be44ed924230d94b14a4e6 +#: ../../en/units/op90.rst:53 b368327d22f740b099d67acb81739c31 msgid "Constructors" msgstr "" -#: ../../en/units/op90.rst:57 fa37e2e5fa2d4f538734906b3bc84284 +#: ../../en/units/op90.rst:57 647e61cfea2c41a1ad8e308242f350f9 msgid "Create an OPUnit object." msgstr "创建一个OPUnit对象" -#: ../../en/units/op90.rst:60 ef684cb6d57c47468762588b1fd75cfe +#: ../../en/units/op90.rst:60 6fb14d46763b43659687401e4b26bc93 msgid "The parameters is:" msgstr "参数如下:" -#: ../../en/units/op90.rst:60 b8dcac43aac3475b81069d8f217c3ecf +#: ../../en/units/op90.rst:60 fd58931d38f247a2b00e579469ac9a53 msgid "``io`` is the detection pin." msgstr "``io`` 为检测引脚。" #: ../../en/units/op90.rst:62 ../../en/units/op90.rst:74 -#: ../../en/units/op90.rst:83 37a20ab181aa4a87b7f6bec36d6c2486 -#: 6f5c2db0e1954cb18a30f10dbcb4dbaa c9eba650fe1b4376bf76b1d4de891476 +#: ../../en/units/op90.rst:83 26005537a8c84f4882e9d173c7d057b2 +#: 80c732535e8a4b1a8532773b1102ca85 aa94eefc00da43a8b0ad511ed7579010 msgid "UIFLOW2:" msgstr "" -#: ../../en/units/op90.rst:64 f938299b862b43de9897deaf7ad49687 -msgid "|init.svg|" +#: ../../en/units/op90.rst:64 49cdee4dfe0c4e00b56e0ea2a30ae98c +msgid "|init.png|" msgstr "" -#: ../../en/refs/unit.op90.ref:8 8ede87dbea6045b68281fb7c1f0beb16 -msgid "init.svg" +#: ../../en/refs/unit.op90.ref:8 bfeaa291cf4e4bb7b31c1515b88f6a52 +msgid "init.png" msgstr "" -#: ../../en/units/op90.rst:68 374c0e2fc74745b6a27f301b4b74ec86 +#: ../../en/units/op90.rst:68 a9e7ab99f26848589e252c91b698b370 msgid "Methods" msgstr "" -#: ../../en/units/op90.rst:72 7816385f5fd54653a23a779ba95a076e +#: ../../en/units/op90.rst:72 5e5f7f516f6e4a5db6f2447252498a90 msgid "Get the count value." msgstr "获取计数值。" -#: ../../en/units/op90.rst:76 0dfeea04239e463bb35bf6cd42262c65 -msgid "|get_count_value.svg|" -msgstr "" +#: ../../en/units/op90.rst:76 660449b683574a71bc6be85b5a8009d7 +#, fuzzy +msgid "|get_count_value.png|" +msgstr "获取计数值。" -#: ../../en/refs/unit.op90.ref:10 290714c8235340f18085b939e75b6749 -msgid "get_count_value.svg" -msgstr "" +#: ../../en/refs/unit.op90.ref:10 09edf154deca448e82e9fd3ba8d3f5d0 +#, fuzzy +msgid "get_count_value.png" +msgstr "获取计数值。" -#: ../../en/units/op90.rst:81 dccab558580048f59d816efc4a3b120b +#: ../../en/units/op90.rst:81 05ee6096934f44a092b691826df6df3f msgid "Reset the meter value." msgstr "重置计数值。" -#: ../../en/units/op90.rst:85 90e9266b8ce74915ac133b24a63e54df -msgid "|reset_count_value.svg|" -msgstr "" +#: ../../en/units/op90.rst:85 0501f220504348738aba6fd60fa311a8 +#, fuzzy +msgid "|reset_count_value.png|" +msgstr "获取计数值。" -#: ../../en/refs/unit.op90.ref:12 50f59a6ffbca4e649d54ef838ea2dc2f -msgid "reset_count_value.svg" -msgstr "" +#: ../../en/refs/unit.op90.ref:12 7b987131e4114d9e99c5637b2ba18d88 +#, fuzzy +msgid "reset_count_value.png" +msgstr "获取计数值。" #~ msgid "get_switch_value.svg" #~ msgstr "" @@ -127,3 +131,27 @@ msgstr "" #~ msgid "|get_switch_value.svg|" #~ msgstr "" +#~ msgid "|example.svg|" +#~ msgstr "" + +#~ msgid "example.svg" +#~ msgstr "" + +#~ msgid "|init.svg|" +#~ msgstr "" + +#~ msgid "init.svg" +#~ msgstr "" + +#~ msgid "|get_count_value.svg|" +#~ msgstr "" + +#~ msgid "get_count_value.svg" +#~ msgstr "" + +#~ msgid "|reset_count_value.svg|" +#~ msgstr "" + +#~ msgid "reset_count_value.svg" +#~ msgstr "" + diff --git a/docs/locales/zh_CN/LC_MESSAGES/units/rgb.po b/docs/locales/zh_CN/LC_MESSAGES/units/rgb.po index b8058a0a..6043cfb3 100644 --- a/docs/locales/zh_CN/LC_MESSAGES/units/rgb.po +++ b/docs/locales/zh_CN/LC_MESSAGES/units/rgb.po @@ -9,7 +9,7 @@ msgid "" msgstr "" "Project-Id-Version: UIFlow2 Programming Guide \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-03-11 15:31+0800\n" +"POT-Creation-Date: 2025-04-18 15:58+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language: zh_CN\n" @@ -18,132 +18,156 @@ msgstr "" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" -"Generated-By: Babel 2.12.1\n" +"Generated-By: Babel 2.16.0\n" -#: ../../en/units/rgb.rst:2 ba00c602b2ab4d73bf2790c52fbbb02d +#: ../../en/units/rgb.rst:2 c2eb204b666c4b559e738a5947f5a739 msgid "RGB Unit" msgstr "" -#: ../../en/refs/unit.rgb.ref 10490323322446809f67dfff8960d3c5 -#: c8170e90bb784b23b8bb697ebc3af749 -msgid "RGB" -msgstr "" - -#: ../../en/refs/unit.rgb.ref:6 2dbec74834a943cb8b2a44031c17ca68 -#: d406582ca72a41c0a86dd302e19a760b -msgid "example.svg" -msgstr "" - -#: ../../en/refs/unit.rgb.ref:8 47bfe18bb5c64086bce417088fd05d86 -#: 6d603fe9109049e0b972cf2c3469f7a8 -msgid "init.svg" -msgstr "" - -#: ../../en/refs/unit.rgb.ref:10 692eb1871f9843cda79e37887f50ef73 -#: e596e7a6403348f19ccba862fd9fef69 -msgid "set_brightness.svg" -msgstr "" - -#: ../../en/refs/unit.rgb.ref:12 2bec3a51ebb24b6b92281bc057a72a0b -#: 7a4b1c821cec4ea7887a7171f9c7c384 -msgid "fill_color.svg" -msgstr "" - -#: ../../en/refs/unit.rgb.ref:14 ac3da54544a34922818c9b6ed479eb37 -#: fb2816a31617492fbc66c7cee6d297a3 -msgid "set_color.svg" -msgstr "" - -#: ../../en/units/rgb.rst:6 9d362cf8490949f9aaa63a61707d919b +#: ../../en/units/rgb.rst:6 0d2b0869c44c45338c321ad8f608f675 msgid "The following products are supported:" msgstr "支持以下产品:" -#: ../../en/units/rgb.rst:8 4ea378848d6f47aca3b33c1443b4e3c0 +#: ../../en/units/rgb.rst:8 9fc8c89d44204fb6a226fe3036906b92 msgid "|RGB|" msgstr "" -#: ../../en/units/rgb.rst:11 47d6d120ae074a81a9f736c7fc170e03 +#: ../../en/refs/unit.rgb.ref b81ea351bfbf436b9fc2b7f19ad25678 +msgid "RGB" +msgstr "" + +#: ../../en/units/rgb.rst:11 4bd1b778afb543c3a2e0b77cdf01f4d5 msgid "Micropython Example::" msgstr "" -#: ../../en/units/rgb.rst:26 99eca9618d8743b189c712b4f5adc216 +#: ../../en/units/rgb.rst:26 3ef22899ea7a4c1a98119f993576faf9 msgid "UIFLOW2 Example:" msgstr "" -#: ../../en/units/rgb.rst:28 ca9e3cd7f2ef41c487ca213967490081 -msgid "|example.svg|" +#: ../../en/units/rgb.rst:28 c44b855e7ea74ddea7f4f4609e8ddd43 +msgid "|example.png|" +msgstr "" + +#: ../../en/refs/unit.rgb.ref:6 5de2f9c35a6f431fbf5688983bb0520f +msgid "example.png" msgstr "" -#: ../../en/units/rgb.rst:33 9f0bc713212e4802b09b218e724e9a27 +#: ../../en/units/rgb.rst:33 cdd8f22a9d7045f0bba23e7acd39e02f msgid "|rgb_core_example.m5f2|" msgstr "" -#: ../../en/units/rgb.rst:37 55b3fbdaf1a64ed28acb49570f778f54 +#: ../../en/units/rgb.rst:37 4561cb0e420d45f180e824e8b8f4a6cb msgid "class RGB" msgstr "" -#: ../../en/units/rgb.rst:40 54aaab02747f4eeea2f1a268b3fb640d +#: ../../en/units/rgb.rst:40 743fc309dda7478e8265c49a5c11d784 msgid "Constructors" msgstr "" -#: ../../en/units/rgb.rst:44 7733e65440be4e34adf6bfe02524d5b4 +#: ../../en/units/rgb.rst:44 095c789fd97e4ecf89e0fb64d0aa3a75 msgid "Create an RGB object." msgstr "创建一个RGB对象。" -#: ../../en/units/rgb.rst:48 c59938454f0b44eb894f3b8358f4ff29 +#: ../../en/units/rgb.rst:48 f6367784603040a1bb1255d91e8db4c4 msgid "parameter is:" msgstr "参数是:" -#: ../../en/units/rgb.rst:47 6d1922796e804324aa3f365b11ea19a1 +#: ../../en/units/rgb.rst:47 36c5e0484b8c416a9b33d46553ffef43 msgid "``port`` is the pins number of the port" msgstr "``port`` 是端口的引脚号" -#: ../../en/units/rgb.rst:48 4002a7e67b934cfcb2bfd402a41c8f9c +#: ../../en/units/rgb.rst:48 5c6de12f53a14eab911d05ee830e7b58 msgid "``number`` is the number of RGB lamp beads" msgstr "``number`` 是灯珠数量" #: ../../en/units/rgb.rst:50 ../../en/units/rgb.rst:62 #: ../../en/units/rgb.rst:71 ../../en/units/rgb.rst:80 -#: 07cb9859e4b94769b637a3c615c0d0bd dba569c06d8b48e48d0ef5af476f78b4 -#: e1f13b3b50dc404c9e9caaccf0ddcc78 f6e61df9dfa3456ba435a97088086f3f +#: 79ad51f6336a4fbd91635e6d8ba80015 875307679f1842d184926fcc27c2edf4 +#: d0388cc2741b4ecc898db57f276cafe4 d7c7a121de694620bca6d33600bf7b64 msgid "UIFLOW2:" msgstr "" -#: ../../en/units/rgb.rst:52 ba84770f2717480cbfca52462eac655e -msgid "|init.svg|" +#: ../../en/units/rgb.rst:52 b1d14b82d314473382a267bc239d578f +msgid "|init.png|" msgstr "" -#: ../../en/units/rgb.rst:56 559de38dc610451f9ad8048bd4bee7f0 +#: ../../en/refs/unit.rgb.ref:8 d32c9fd5052044cf9bf128ff12221ebd +msgid "init.png" +msgstr "" + +#: ../../en/units/rgb.rst:56 19009a60c5de4b76ab082ec3fd8744b0 msgid "Methods" msgstr "" -#: ../../en/units/rgb.rst:60 b5917c338d844683b90161bf0d73fa92 +#: ../../en/units/rgb.rst:60 84fc25f40b254674a8d0923f5bdd98ea msgid "" "This method is used to set the brightness of RGB lamp beads, and the " "setting range is 0-100." msgstr "此方法用于设置RGB灯珠亮度, 设置范围为0-100。" -#: ../../en/units/rgb.rst:64 2a1003adecbd46479703458e06fe8cce -msgid "|set_brightness.svg|" +#: ../../en/units/rgb.rst:64 1a1ba04d148149d8b25e2adb3f77e484 +msgid "|set_brightness.png|" msgstr "" -#: ../../en/units/rgb.rst:69 d4436e5148c64ece821de8ba258f6fec +#: ../../en/refs/unit.rgb.ref:10 ea14c4dea06749f7aa06f365ae480252 +msgid "set_brightness.png" +msgstr "" + +#: ../../en/units/rgb.rst:69 95bca7b672634a38861c6b4762f8233f msgid "" "This method is used to set the color of all RGB lamp beads, and the input" " value is 3-byte RGB888." msgstr "此方法用于设置全部RGB灯珠颜色, 传入值为3字节的RGB888。" -#: ../../en/units/rgb.rst:73 27cf2498f92a4b3ab419b66a631452b3 -msgid "|fill_color.svg|" +#: ../../en/units/rgb.rst:73 c5584bc1dac54357b2f151de19a789e4 +msgid "|fill_color.png|" msgstr "" -#: ../../en/units/rgb.rst:78 8abc5cea66cc4dab99e6572bd7265898 +#: ../../en/refs/unit.rgb.ref:12 4bf7ae197fdc4bd2ae2460fd49d3bca9 +msgid "fill_color.png" +msgstr "" + +#: ../../en/units/rgb.rst:78 4c4749a7dc99412395f519991db9d9ab msgid "" "This method is used to set the specified RGB lamp bead color. The input " "value is the lamp bead index and 3-byte RGB888." msgstr "此方法用于设置指定RGB灯珠颜色, 传入值为灯珠索引和3字节的RGB888。" -#: ../../en/units/rgb.rst:82 638d24049b3742fe8f713737a7381a42 -msgid "|set_color.svg|" +#: ../../en/units/rgb.rst:82 e2ccf98fc72f4ea3a3dabb5759cfc38a +msgid "|set_color.png|" +msgstr "" + +#: ../../en/refs/unit.rgb.ref:14 75962d214e5f4a3091669a289f23f103 +msgid "set_color.png" msgstr "" +#~ msgid "example.svg" +#~ msgstr "" + +#~ msgid "init.svg" +#~ msgstr "" + +#~ msgid "set_brightness.svg" +#~ msgstr "" + +#~ msgid "fill_color.svg" +#~ msgstr "" + +#~ msgid "set_color.svg" +#~ msgstr "" + +#~ msgid "|example.svg|" +#~ msgstr "" + +#~ msgid "|init.svg|" +#~ msgstr "" + +#~ msgid "|set_brightness.svg|" +#~ msgstr "" + +#~ msgid "|fill_color.svg|" +#~ msgstr "" + +#~ msgid "|set_color.svg|" +#~ msgstr "" + diff --git a/docs/locales/zh_CN/LC_MESSAGES/units/servos8.po b/docs/locales/zh_CN/LC_MESSAGES/units/servos8.po new file mode 100644 index 00000000..90b93018 --- /dev/null +++ b/docs/locales/zh_CN/LC_MESSAGES/units/servos8.po @@ -0,0 +1,616 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2016 - 2025 M5Stack Technology Co., Ltd +# This file is distributed under the same license as the UIFlow2 Programming +# Guide package. +# FIRST AUTHOR , 2025. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: UIFlow2 Programming Guide \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-04-18 15:58+0800\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: zh_CN\n" +"Language-Team: zh_CN \n" +"Plural-Forms: nplurals=1; plural=0;\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.16.0\n" + +#: ../../en/refs/unit.servos8.ref ../../en/units/servos8.rst:2 +#: 7de5752f3dc4476aaccdd886bfcd06cb 89039c0aefe14f859721e57e76a70683 +msgid "8Servos Unit" +msgstr "" + +#: ../../en/units/servos8.rst:8 3e63815f60ba46d4b0bdecd765d675bf +msgid "" +"This is the driver library for the 8Servos Unit. It is a 8-channel servo " +"controller that can control up to 8 servos. It can be used to control the" +" angles of the servos, set the pulse width, and set the mode of the " +"servos." +msgstr "" + +#: ../../en/units/servos8.rst:12 adc2ab5b640d441da38898024fe826be +msgid "Support the following products:" +msgstr "" + +#: ../../en/units/servos8.rst:14 f74e3601ee5246c7884b28300bb8c2b0 +msgid "|8Servos Unit|" +msgstr "" + +#: ../../en/units/servos8.rst:18 abd6b6dc430a41c087edf8f30a013253 +msgid "UiFlow2 Example" +msgstr "" + +#: ../../en/units/servos8.rst:21 ../../en/units/servos8.rst:56 +#: b3e2228f521843be822575a1e9e9ceaa d0947d44a3474cd69b8dac70f3b18b8c +msgid "servo control" +msgstr "" + +#: ../../en/units/servos8.rst:23 249d88e0d7b74682901e294317cd6944 +msgid "Open the |cores3_servo_example.m5f2| project in UiFlow2." +msgstr "" + +#: ../../en/units/servos8.rst:25 ../../en/units/servos8.rst:58 +#: 2d27eb3c2c8c41eea74cbd0689c3d08e b04b7b7ce22849848d451f0ebd77ffdd +msgid "This example controls the servo angle of the 8Servos Unit." +msgstr "" + +#: ../../en/units/servos8.rst:27 ../../en/units/servos8.rst:43 +#: 02a1f3845d8749bf9af4d581cfd74261 064be1b7bbff4221ab55d05e74e3c6ba +#: 125b2b0cf3144a1ca7d58c1dff682fb7 2b3731ee91dc4556abffed11865ff1c3 +#: 32f6d8871b0f477097274cbc85a24ccb 57a74b9c553a472b944d4715e9315f1b +#: 5ff004c8803a44e994ebd3c4d7b43d33 7e8c6bb4dc47494d8bd7205c4369a638 +#: 87b4a97be6fb441996cc0ee13fa22a8b 961c210ebd2f415485a7aff8efdf3e9d +#: b6b661ca9fc44afcb33ed3aeba7db045 bc7396835343413a9dd4461a75a781b9 +#: bfcab2417a514e96872c653e2e31544b c102d1cc8cd5415d99326555d877b0cf +#: c46e8b3c933842e6bfa4a6aa0db1e63f c801fc46071c471f9e4e75b807e3dcaa +#: ce4d7433bbe242ffac03b724627163e2 e40eb914d580464abd8cc779556c1802 +#: fb8d3c40b25c4cd394c468a90f33034a of unit.servos8.Servos8Unit:10 +#: unit.servos8.Servos8Unit.get_12bit_adc_raw:7 +#: unit.servos8.Servos8Unit.get_8bit_adc_raw:7 +#: unit.servos8.Servos8Unit.get_device_spec:6 +#: unit.servos8.Servos8Unit.get_digital_input:7 +#: unit.servos8.Servos8Unit.get_input_current:6 +#: unit.servos8.Servos8Unit.get_mode:7 unit.servos8.Servos8Unit.get_rgb_led:7 +#: unit.servos8.Servos8Unit.get_servo_angle:7 +#: unit.servos8.Servos8Unit.get_servo_pulse:7 +#: unit.servos8.Servos8Unit.set_i2c_address:5 +#: unit.servos8.Servos8Unit.set_mode:6 +#: unit.servos8.Servos8Unit.set_output_value:6 +#: unit.servos8.Servos8Unit.set_pwm_dutycycle:6 +#: unit.servos8.Servos8Unit.set_rgb_led:6 +#: unit.servos8.Servos8Unit.set_servo_angle:6 +#: unit.servos8.Servos8Unit.set_servo_pulse:6 +msgid "UiFlow2 Code Block:" +msgstr "" + +#: ../../en/units/servos8.rst:29 d6202a00719d459db06adb77367ca08f +msgid "|example_servo.png|" +msgstr "" + +#: ../../en/refs/unit.servos8.ref:6 fabff971e7c84f339c67830a0c79d2eb +msgid "example_servo.png" +msgstr "" + +#: ../../en/units/servos8.rst:31 ../../en/units/servos8.rst:47 +#: ../../en/units/servos8.rst:66 ../../en/units/servos8.rst:82 +#: 1fc3c2c6c7c54472ac5a8c7de11e564b 320a6785b6e14ec988e147bfc16e6769 +#: 573e95e9a30749e8829d91aae5c6586e fe3fd60b11b94662acb4ada67a3eb52a +msgid "Example output:" +msgstr "" + +#: ../../en/units/servos8.rst:33 ../../en/units/servos8.rst:49 +#: ../../en/units/servos8.rst:68 ../../en/units/servos8.rst:84 +#: 08d20fe961aa411e84b9103f18a1d971 1fe3ff29045d4f288e7d10b3e95a6720 +#: 349639a5bfdd4b33aa6c9aaf405359a6 91a1aaf9f144420d9d0aed470d5a0261 +msgid "None" +msgstr "" + +#: ../../en/units/servos8.rst:37 ../../en/units/servos8.rst:72 +#: 0402cf277e0a480e8c715d38ccf55cbb 1d1072672c2d49ada27cf983eb71cfec +msgid "rgb control" +msgstr "" + +#: ../../en/units/servos8.rst:39 448e0fe14c9e47d8863a2a20f8e0c7dd +msgid "Open the |cores3_rgb_example.m5f2| project in UiFlow2." +msgstr "" + +#: ../../en/units/servos8.rst:41 ../../en/units/servos8.rst:74 +#: adabe85255544759b32e58efe7069ce9 b4fdac3c8b564520ba81a4da22fc46ba +msgid "This example controls the RGB LED of the 8Servos Unit." +msgstr "" + +#: ../../en/units/servos8.rst:45 30a3d83edaae4aae88ae7a208c85b71e +msgid "|example_rgb.png|" +msgstr "" + +#: ../../en/refs/unit.servos8.ref:7 34ebbe4c0ba04f428234370bdea76810 +msgid "example_rgb.png" +msgstr "" + +#: ../../en/units/servos8.rst:53 27271b11b492474c9a3f389e1bbf07e3 +msgid "MicroPython Example" +msgstr "" + +#: ../../en/units/servos8.rst:60 ../../en/units/servos8.rst:76 +#: 0439e000c3fd412b93aeb1c6d38f206b 091f833c070a490d89b53e117692b553 +#: 15f4f3513e05438d92e72292e5e90f77 2546bdcc8e8844568b8ce58313a75d34 +#: 2b9739bcaece4a51841fb15fa62e0dd8 2f6d56750931442a837f952cd4c193b0 +#: 50eda940eaef4d218be8f6bf26a1afe5 51e2c3b07e514f8d80e4f703ce515d68 +#: 5f6d77713af94bc0a504fdaf027a688d 66ba2b224819475e92150f3a0253c495 +#: 6921f25ab7d8459ba5abbefebc879d51 696d944c19e5454596456dc8f161bd6c +#: 7b5289f6394a4d2d864201c000e45a86 97e225fcfa0a4013a2fbfe4b3492734d +#: ada502337d1b40cc87d229d8ddd60c10 b63aeb4b025c4a5db570e14ede864e30 +#: d2e24aad3df348ebb2c03f91e675fb7e d5725f246ec446bc804a7c1b4d2523b7 +#: f9f5c125dc834928a1ff7f290bef0c8d of unit.servos8.Servos8Unit:12 +#: unit.servos8.Servos8Unit.get_12bit_adc_raw:11 +#: unit.servos8.Servos8Unit.get_8bit_adc_raw:11 +#: unit.servos8.Servos8Unit.get_device_spec:10 +#: unit.servos8.Servos8Unit.get_digital_input:11 +#: unit.servos8.Servos8Unit.get_input_current:10 +#: unit.servos8.Servos8Unit.get_mode:11 unit.servos8.Servos8Unit.get_rgb_led:11 +#: unit.servos8.Servos8Unit.get_servo_angle:11 +#: unit.servos8.Servos8Unit.get_servo_pulse:11 +#: unit.servos8.Servos8Unit.set_i2c_address:9 +#: unit.servos8.Servos8Unit.set_mode:10 +#: unit.servos8.Servos8Unit.set_output_value:10 +#: unit.servos8.Servos8Unit.set_pwm_dutycycle:10 +#: unit.servos8.Servos8Unit.set_rgb_led:10 +#: unit.servos8.Servos8Unit.set_servo_angle:10 +#: unit.servos8.Servos8Unit.set_servo_pulse:10 +msgid "MicroPython Code Block:" +msgstr "" + +#: ../../en/units/servos8.rst:89 9e75f7dc5e2e43d380de19082e395294 +msgid "**API**" +msgstr "" + +#: ../../en/units/servos8.rst:92 5aed4cdafd77437d9d3eb494fc63c140 +msgid "Servos8Unit" +msgstr "" + +#: e8451a1e68404f59b51d1bcf65e00c98 of unit.servos8.Servos8Unit:1 +msgid "Bases: :py:class:`object`" +msgstr "" + +#: f849962c9ef749cfba7eb8117c0b2451 of unit.servos8.Servos8Unit:1 +msgid "Create a servos 8 unit object." +msgstr "" + +#: ../../en/units/servos8.rst 02f1bdf4334d48da8b0fe845c4d969c3 +#: 0b3b969737ef430dab415f2d68514ca6 14876db6cd604ddb99444c504d713fc3 +#: 15bdcd3ca59f4c0a8632aa32f8f5c377 1922062b26204cfa964911cd3a6c9ce5 +#: 31940762e9154c049749e817af8f54b8 40ac3fe7a2b84bbfb1b78096baf875a2 +#: 437d5ad852684962b93ee33f5e6420a1 76bdb01acce3422d9b171d6d03b34191 +#: 94bcec4ea8204c89b6e967a93544132f a3a164be7b334081b091d847844f18b9 +#: a3b3092af04a42af82ee501513a9a5d8 c800780eb5f74c1e9d876c3aba18fe03 +#: ead6b5378e1a4cac852880567a2b157f f757cf8a52ac490fa220fccf1e38d761 of +#: unit.servos8.Servos8Unit.get_12bit_adc_raw +#: unit.servos8.Servos8Unit.get_8bit_adc_raw +#: unit.servos8.Servos8Unit.get_digital_input unit.servos8.Servos8Unit.get_mode +#: unit.servos8.Servos8Unit.get_rgb_led +#: unit.servos8.Servos8Unit.get_servo_angle +#: unit.servos8.Servos8Unit.get_servo_pulse +#: unit.servos8.Servos8Unit.set_i2c_address unit.servos8.Servos8Unit.set_mode +#: unit.servos8.Servos8Unit.set_output_value +#: unit.servos8.Servos8Unit.set_pwm_dutycycle +#: unit.servos8.Servos8Unit.set_rgb_led +#: unit.servos8.Servos8Unit.set_servo_angle +#: unit.servos8.Servos8Unit.set_servo_pulse +msgid "Parameters" +msgstr "" + +#: 331dc2ce492740a0a59d38095484c7df of unit.servos8.Servos8Unit:3 +msgid "The I2C bus the servos 8 unit is connected to." +msgstr "" + +#: 03f17623797f433385e9394b4ea28aa2 of unit.servos8.Servos8Unit:5 +msgid "The I2C address of the device. Default is 0x25." +msgstr "" + +#: ../../en/units/servos8.rst 86686814e61e4d10ae2ceba1d175a70c +msgid "Raises" +msgstr "" + +#: 2ba1bba373f44547b08dd9a2c63a2190 of unit.servos8.Servos8Unit:7 +msgid "If the servos 8 unit is not connected." +msgstr "" + +#: abd0bd2fd7994b56a027fba6c2c41570 of unit.servos8.Servos8Unit:10 +msgid "|init.png|" +msgstr "" + +#: ../../en/refs/unit.servos8.ref:19 174a7067a6764c64b54adfa696556daa +msgid "init.png" +msgstr "" + +#: 4e635d7941654780b3a70013b7f32a61 of +#: unit.servos8.Servos8Unit.get_12bit_adc_raw:1 +msgid "Get the 12-bit ADC value of a specific channel." +msgstr "" + +#: ff2a046173074277bbfbdf641a183501 of +#: unit.servos8.Servos8Unit.get_12bit_adc_raw:3 +msgid "The channel number (0 to 7) to get the 12-bit ADC value for." +msgstr "" + +#: 38b52c46db68452980e24800f7d27fce 70d2ae7e092f41509fa55f40280179b1 +#: 757d894795514f4aa937c66a728cfb8d 913416191a87410e9d9edf068039a816 +#: b76dcd5898f54487980bca5d5fa61adf c772c78034ce4b9b8213c09fef3543ad +#: d478f4162183423bb4d01025e0aa3366 fb0091d492c546d6a49d29e679adf815 of +#: unit.servos8.Servos8Unit.get_12bit_adc_raw +#: unit.servos8.Servos8Unit.get_8bit_adc_raw +#: unit.servos8.Servos8Unit.get_digital_input +#: unit.servos8.Servos8Unit.get_input_current unit.servos8.Servos8Unit.get_mode +#: unit.servos8.Servos8Unit.get_rgb_led +#: unit.servos8.Servos8Unit.get_servo_angle +#: unit.servos8.Servos8Unit.get_servo_pulse +msgid "Returns" +msgstr "" + +#: bb40ddfc97ee42d984b9a5618e7c25b9 of +#: unit.servos8.Servos8Unit.get_12bit_adc_raw:4 +msgid "The 12-bit ADC value (0 to 4095) of the specified channel." +msgstr "" + +#: 191646407a4446d891e802e14d1f06a0 32db6b2d18a647ba9f4e7940ea9d3384 +#: 3fa42b9e249040cda29f313567c04364 54c8ed4a9a7042ca9b8cc3ee4226b543 +#: 57ee078ff5e34e7bb07b7611426e3575 5b20f9ee56ac45a09e272b9af87fc256 +#: 5f595c23bcbb4551bac8650e93e6d0a3 6adb815cf60648cdaf7cb957fa66ff7f +#: 8b3d8531c6544d9aaf2028f87a334541 95d755f77f774a4da5de44fee7ce3a2f +#: b0ee01f240aa4b8ba040f2c9f34fd38a b13e1d7a82f24e26a1ab3820776185fd +#: c66ab526e5bb44a29b0e9141374803fd d5141a8c012f4fcebd4d32029f55bad1 of +#: unit.servos8.Servos8Unit.get_12bit_adc_raw +#: unit.servos8.Servos8Unit.get_8bit_adc_raw +#: unit.servos8.Servos8Unit.get_digital_input +#: unit.servos8.Servos8Unit.get_input_current unit.servos8.Servos8Unit.get_mode +#: unit.servos8.Servos8Unit.get_rgb_led +#: unit.servos8.Servos8Unit.get_servo_angle +#: unit.servos8.Servos8Unit.get_servo_pulse unit.servos8.Servos8Unit.set_mode +#: unit.servos8.Servos8Unit.set_output_value +#: unit.servos8.Servos8Unit.set_pwm_dutycycle +#: unit.servos8.Servos8Unit.set_rgb_led +#: unit.servos8.Servos8Unit.set_servo_angle +#: unit.servos8.Servos8Unit.set_servo_pulse +msgid "Return type" +msgstr "" + +#: e8d684bd39a94f62a3fa64bb47757706 of +#: unit.servos8.Servos8Unit.get_12bit_adc_raw:9 +msgid "|get_12bit_adc_raw.png|" +msgstr "" + +#: ../../en/refs/unit.servos8.ref:10 ad50907194ef4736947dd2f64333a087 +msgid "get_12bit_adc_raw.png" +msgstr "" + +#: 608a85d7c75a46fb9a14af140fd9cf79 of +#: unit.servos8.Servos8Unit.get_8bit_adc_raw:1 +msgid "Get the 8-bit ADC value of a specific channel." +msgstr "" + +#: 70988dc048d747098f4c85fc512d12ad of +#: unit.servos8.Servos8Unit.get_8bit_adc_raw:3 +msgid "The channel number (0 to 7) to get the 8-bit ADC value for." +msgstr "" + +#: 10ea97eb805a4418be5ef822b0cd1c10 of +#: unit.servos8.Servos8Unit.get_8bit_adc_raw:4 +msgid "The 8-bit ADC value (0 to 255) of the specified channel." +msgstr "" + +#: 013b694f5b8e4de0bc02785e7c0b7f0e of +#: unit.servos8.Servos8Unit.get_8bit_adc_raw:9 +msgid "|get_8bit_adc_raw.png|" +msgstr "" + +#: ../../en/refs/unit.servos8.ref:11 dda98f76dd9640d7a5116cd7aff1d290 +msgid "get_8bit_adc_raw.png" +msgstr "" + +#: e26b48765bf344e08daaf69abc29a1d4 of +#: unit.servos8.Servos8Unit.get_device_spec:1 +msgid "" +"Get the device specification. :param int mode: The mode to get the " +"specification for. :return: The device specification value. :rtype: int" +msgstr "" + +#: a8d1f3f346ad4139bfc43e2f09c2cb98 of +#: unit.servos8.Servos8Unit.get_device_spec:8 +msgid "|get_device_spec.png|" +msgstr "" + +#: ../../en/refs/unit.servos8.ref:12 e2bd1ad7c91e4443bb581ae3605927d4 +msgid "get_device_spec.png" +msgstr "" + +#: a73a758c5df64afca22cff6e2aa04e07 of +#: unit.servos8.Servos8Unit.get_digital_input:1 +msgid "Get the digital input value of a specific channel." +msgstr "" + +#: 2561bb502dab41dab755604624ee5d68 of +#: unit.servos8.Servos8Unit.get_digital_input:3 +msgid "The channel number (0 to 7) to get the digital input for." +msgstr "" + +#: 23d0fdd48a8042e69b728a2017ad6f57 of +#: unit.servos8.Servos8Unit.get_digital_input:4 +msgid "The digital input value (True or False) of the specified channel." +msgstr "" + +#: 4cd23020ff46460b932f0a77731ee5b9 of +#: unit.servos8.Servos8Unit.get_digital_input:9 +msgid "|get_digital_input.png|" +msgstr "" + +#: ../../en/refs/unit.servos8.ref:13 9e55ef8ba60944018a3b896f226daaff +msgid "get_digital_input.png" +msgstr "" + +#: 8fbe16bf9996498294571a028899a984 of +#: unit.servos8.Servos8Unit.get_input_current:1 +msgid "Get the input current of the servos 8 unit." +msgstr "" + +#: f29938ba84c0493aa51942c1094a75bf of +#: unit.servos8.Servos8Unit.get_input_current:3 +msgid "The input current in Amperes." +msgstr "" + +#: 7782baa6172e4780b67a409813b82fa8 of +#: unit.servos8.Servos8Unit.get_input_current:8 +msgid "|get_input_current.png|" +msgstr "" + +#: ../../en/refs/unit.servos8.ref:14 a5416dfd777948cb9ac2151c0567e6a8 +msgid "get_input_current.png" +msgstr "" + +#: 040d77a708a844fb9cac681228024cf1 of unit.servos8.Servos8Unit.get_mode:1 +msgid "Get the current mode of a specific channel." +msgstr "" + +#: ad4078d2c95f495f8f8562323ce05a6d of unit.servos8.Servos8Unit.get_mode:3 +msgid "The channel number (0 to 7) to get the mode for." +msgstr "" + +#: 3c33b0e8a6fe42e2b116b4e78a07a27a of unit.servos8.Servos8Unit.get_mode:4 +msgid "The mode of the specified channel." +msgstr "" + +#: 035564b93bb54307903f992235cc68fb of unit.servos8.Servos8Unit.get_mode:9 +msgid "|get_mode.png|" +msgstr "" + +#: ../../en/refs/unit.servos8.ref:15 1fe2215a35d7409a8641ebd1bd907b29 +msgid "get_mode.png" +msgstr "" + +#: c6d371902b37433ba035435e73fd1bf9 of unit.servos8.Servos8Unit.get_rgb_led:1 +msgid "Get the RGB LED color of a specific channel." +msgstr "" + +#: e151d22dce6348dca5e0e805c0e898a6 of unit.servos8.Servos8Unit.get_rgb_led:3 +msgid "The channel number (0 to 7) to get the RGB LED color for." +msgstr "" + +#: 7d919c8c3063427ca345f5a2750e889d of unit.servos8.Servos8Unit.get_rgb_led:4 +msgid "The RGB color value (0x000000 to 0xffffff) of the specified channel." +msgstr "" + +#: 215d8425b6684e14831b2c3cb88cc794 of unit.servos8.Servos8Unit.get_rgb_led:9 +msgid "|get_rgb_led.png|" +msgstr "" + +#: ../../en/refs/unit.servos8.ref:16 f09713dd7b5d4888a449fcbd5419475d +msgid "get_rgb_led.png" +msgstr "" + +#: 8b8e3642062c4248af2197ffdf01c154 of +#: unit.servos8.Servos8Unit.get_servo_angle:1 +msgid "Get the servo angle of a specific channel." +msgstr "" + +#: 8daa270251db448689c4686031c33a9c of +#: unit.servos8.Servos8Unit.get_servo_angle:3 +msgid "The channel number (0 to 7) to get the servo angle for." +msgstr "" + +#: 1efcfac8b6aa42d08a6488128de86552 of +#: unit.servos8.Servos8Unit.get_servo_angle:4 +msgid "The servo angle (0 to 180) of the specified channel." +msgstr "" + +#: ee8c6af97ff2492c91256f071f60d586 of +#: unit.servos8.Servos8Unit.get_servo_angle:9 +msgid "|get_servo_angle.png|" +msgstr "" + +#: ../../en/refs/unit.servos8.ref:17 c0b0a10a3cd44a63a84788beb8b69a40 +msgid "get_servo_angle.png" +msgstr "" + +#: 8dd723e17c6543dd9386354e92a15c06 of +#: unit.servos8.Servos8Unit.get_servo_pulse:1 +msgid "Get the servo pulse of a specific channel." +msgstr "" + +#: 609cee31dd78429899fb7f972affda08 of +#: unit.servos8.Servos8Unit.get_servo_pulse:3 +msgid "The channel number (0 to 7) to get the servo pulse for." +msgstr "" + +#: 7a75b4302f914450834683c67c10c993 of +#: unit.servos8.Servos8Unit.get_servo_pulse:4 +msgid "The servo pulse (500 to 2500) of the specified channel." +msgstr "" + +#: cfc7ba4c6b584f2396e2a5490ead984f of +#: unit.servos8.Servos8Unit.get_servo_pulse:9 +msgid "|get_servo_pulse.png|" +msgstr "" + +#: ../../en/refs/unit.servos8.ref:18 3d120f96f2ef472fba1032913b09cc2d +msgid "get_servo_pulse.png" +msgstr "" + +#: 01d4243a9a43479ca1645eedcb4ed945 of +#: unit.servos8.Servos8Unit.set_i2c_address:1 +msgid "Set the I2C address of the servos 8 unit." +msgstr "" + +#: f6f6480cce354642aa8f4ab8c53d8f64 of +#: unit.servos8.Servos8Unit.set_i2c_address:3 +msgid "The new I2C address (1 to 127) to set for the servos 8 unit." +msgstr "" + +#: a34b30c0f5e14f29b93e0059bb601d03 of +#: unit.servos8.Servos8Unit.set_i2c_address:7 +msgid "|set_i2c_address.png|" +msgstr "" + +#: ../../en/refs/unit.servos8.ref:20 6f9618b682864c30a3fefaae1961d7b2 +msgid "set_i2c_address.png" +msgstr "" + +#: c4f23c06f4c948729d02a1214c93b7d6 of unit.servos8.Servos8Unit.set_mode:1 +msgid "Set the mode of a specific channel." +msgstr "" + +#: dc9c327e57b94fc7abd48cea1000009b of unit.servos8.Servos8Unit.set_mode:3 +msgid "The mode to set for the channel." +msgstr "" + +#: 22378d8bdf81469d91297239a6594cf5 of unit.servos8.Servos8Unit.set_mode:4 +msgid "The channel number (0 to 7) to set the mode for." +msgstr "" + +#: 0b044a4223e443bfbc2cf2c995a23fa1 of unit.servos8.Servos8Unit.set_mode:8 +msgid "|set_mode.png|" +msgstr "" + +#: ../../en/refs/unit.servos8.ref:21 f067400041d346759e76ea19e4dc2559 +msgid "set_mode.png" +msgstr "" + +#: 1fc8e682600e4f8a96bfabae7c6368dd of +#: unit.servos8.Servos8Unit.set_output_value:1 +msgid "Set the digital output value of a specific channel." +msgstr "" + +#: f96b4ad949c144cca3a052e5148b142a of +#: unit.servos8.Servos8Unit.set_output_value:3 +msgid "The digital output value (0 or 1) to set for the channel." +msgstr "" + +#: 0a2c40a03b16466395f8331767e72c15 of +#: unit.servos8.Servos8Unit.set_output_value:4 +msgid "The channel number (0 to 7) to set the digital output for." +msgstr "" + +#: 599e41111c174eb997e8ebfdae1a81b1 of +#: unit.servos8.Servos8Unit.set_output_value:8 +msgid "|set_output_value.png|" +msgstr "" + +#: ../../en/refs/unit.servos8.ref:22 858268aca1b14229a898b8655f0788d8 +msgid "set_output_value.png" +msgstr "" + +#: ca69599560b34046ba47966b1cb1b97d of +#: unit.servos8.Servos8Unit.set_pwm_dutycycle:1 +msgid "Set the PWM duty cycle of a specific channel." +msgstr "" + +#: 7d3d444ebe4d4c3a8a45f2c0c136bd84 of +#: unit.servos8.Servos8Unit.set_pwm_dutycycle:3 +msgid "The PWM duty cycle (0 to 100) to set for the channel." +msgstr "" + +#: 6f48f0251ac24c3fb8c94a8662656c6a of +#: unit.servos8.Servos8Unit.set_pwm_dutycycle:4 +msgid "The channel number (0 to 7) to set the PWM duty cycle for." +msgstr "" + +#: 621f87bd875a4938ac07f730ad5ec963 of +#: unit.servos8.Servos8Unit.set_pwm_dutycycle:8 +msgid "|set_pwm_dutycycle.png|" +msgstr "" + +#: ../../en/refs/unit.servos8.ref:23 b6072d014ffa4a13b8cc21ffcfd93b4e +msgid "set_pwm_dutycycle.png" +msgstr "" + +#: 9e421b6b168145cda1a6b7aad6a6c118 of unit.servos8.Servos8Unit.set_rgb_led:1 +msgid "Set the RGB LED color of a specific channel." +msgstr "" + +#: 0a4fb88d1c0f4355b4f832abd79c2ce4 of unit.servos8.Servos8Unit.set_rgb_led:3 +msgid "The RGB color value (0x000000 to 0xffffff) to set for the channel." +msgstr "" + +#: 334be7d7f7454765b254b3b00df04841 of unit.servos8.Servos8Unit.set_rgb_led:4 +msgid "The channel number (0 to 7) to set the RGB LED color for." +msgstr "" + +#: b17de669f5fd45f99a73081a79c24277 of unit.servos8.Servos8Unit.set_rgb_led:8 +msgid "|set_rgb_led.png|" +msgstr "" + +#: ../../en/refs/unit.servos8.ref:24 e95fc022ca024ec18c0342e31822ee37 +msgid "set_rgb_led.png" +msgstr "" + +#: 7aa9638d6e87468e8d031e03d3687ac8 of +#: unit.servos8.Servos8Unit.set_servo_angle:1 +msgid "Set the servo angle of a specific channel." +msgstr "" + +#: 83c2163176bb4554aeb8cf3b7d3a111c of +#: unit.servos8.Servos8Unit.set_servo_angle:3 +msgid "The servo angle (0 to 180) to set for the channel." +msgstr "" + +#: 7fb6665160914303960f32f80545e5c3 of +#: unit.servos8.Servos8Unit.set_servo_angle:4 +msgid "The channel number (0 to 7) to set the servo angle for." +msgstr "" + +#: 50c74a3848d84d86ad37751d087681a1 of +#: unit.servos8.Servos8Unit.set_servo_angle:8 +msgid "|set_servo_angle.png|" +msgstr "" + +#: ../../en/refs/unit.servos8.ref:25 46cd652053844baa8b1f3ddd0042ad22 +msgid "set_servo_angle.png" +msgstr "" + +#: 370c9a5b7beb4190a8a18ea1f44ef968 of +#: unit.servos8.Servos8Unit.set_servo_pulse:1 +msgid "Set the servo pulse of a specific channel." +msgstr "" + +#: cfb587759f7149969d447b53f5f3b03d of +#: unit.servos8.Servos8Unit.set_servo_pulse:3 +msgid "The servo pulse (500 to 2500) to set for the channel." +msgstr "" + +#: cbcd4d1952704e61882432cf24e96aa5 of +#: unit.servos8.Servos8Unit.set_servo_pulse:4 +msgid "The channel number (0 to 7) to set the servo pulse for." +msgstr "" + +#: 9e61f4f1490c46379b496df526043b03 of +#: unit.servos8.Servos8Unit.set_servo_pulse:8 +msgid "|set_servo_pulse.png|" +msgstr "" + +#: ../../en/refs/unit.servos8.ref:26 4d1f49cfd0994c1e924b7763b4cb12d0 +msgid "set_servo_pulse.png" +msgstr "" + diff --git a/docs/locales/zh_CN/LC_MESSAGES/units/thermal.po b/docs/locales/zh_CN/LC_MESSAGES/units/thermal.po index 0403cb5f..6b7a00f3 100644 --- a/docs/locales/zh_CN/LC_MESSAGES/units/thermal.po +++ b/docs/locales/zh_CN/LC_MESSAGES/units/thermal.po @@ -9,7 +9,7 @@ msgid "" msgstr "" "Project-Id-Version: UIFlow2 Programming Guide \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-01-16 15:35+0800\n" +"POT-Creation-Date: 2025-04-18 15:58+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language: zh_CN\n" @@ -20,44 +20,44 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.16.0\n" -#: ../../en/units/thermal.rst:2 f38bd80f859e47579eee6c5698ca2bea +#: ../../en/units/thermal.rst:2 bc7fe46db86547e6ae6654e33f0c1d4c msgid "Thermal Unit" msgstr "" -#: ../../en/units/thermal.rst:6 f3a1a4d7df6c4c22b6277e2400792333 +#: ../../en/units/thermal.rst:6 5d4e5b9379bd496c9d248d0a6e13f88f msgid "Support the following products:" msgstr "" -#: ../../en/units/thermal.rst:8 7c82070da5ec4e558d65e759dfe42792 +#: ../../en/units/thermal.rst:8 a0cf7af44a714ea3b7e7e90e22dc6318 msgid "|ThermalUnit|" msgstr "" -#: ../../en/refs/unit.thermal.ref 75a37a204fd64488903fd3c32d492ef2 +#: ../../en/refs/unit.thermal.ref c145d72f83484c24aeb93b20912c9f70 msgid "ThermalUnit" msgstr "" -#: ../../en/units/thermal.rst:12 8edc3e2c09d84563b684a1ef1a6248bf +#: ../../en/units/thermal.rst:12 9d2ed7ec80424eaf9aa29cfffa9568f0 msgid "class ThermalUnit" msgstr "" -#: ../../en/units/thermal.rst:15 f87a584a07f74bbc96b65d781cd29b06 +#: ../../en/units/thermal.rst:15 38d16677c6ca4d879630ff1857ca3fb0 msgid "Constructors" msgstr "" -#: ../../en/units/thermal.rst:19 9264523b14a24607afeba8b75e6fa4d4 +#: ../../en/units/thermal.rst:19 aaa3ad4cad2c43a9a1d4ccebff4a364a msgid "Create a ThermalUnit object." msgstr "" -#: ../../en/units/thermal.rst 02b5a7a126034f6bb269a9ce6b88d52a -#: 3d5bdd0381304df4beae24c61cab25d8 cba7506b8b204c7c8fd5acbc239e6f6d +#: ../../en/units/thermal.rst 398b68e2642e4710b70a01b79d15ffaf +#: 862fa595c3224c4d8dc29f33c0aaca05 ca8064cf5fbd4e86981033a13e9418a2 msgid "Parameters" msgstr "" -#: ../../en/units/thermal.rst:21 e4172667a33e479babcdcdd818b2aba8 +#: ../../en/units/thermal.rst:21 7dfac2416a5c421e84b57388319b64be msgid "the I2C object." msgstr "" -#: ../../en/units/thermal.rst:22 fd1f2d478e26449ab29677dfc4091646 +#: ../../en/units/thermal.rst:22 6b5b423451bb4c6a86e913e28a218934 msgid "the I2C address of the device. Default is 0x33." msgstr "" @@ -65,158 +65,212 @@ msgstr "" #: ../../en/units/thermal.rst:51 ../../en/units/thermal.rst:62 #: ../../en/units/thermal.rst:76 ../../en/units/thermal.rst:87 #: ../../en/units/thermal.rst:98 ../../en/units/thermal.rst:109 -#: ../../en/units/thermal.rst:120 1cbc4caeabf94f41aa25d88f491bb689 -#: 4344d717e5f64b25aa8b4bc51a1c3ae2 472ef45a21754f97ac9e0d9ecdfe2fb4 -#: 9796f9a6647d401e882e662afe9d4acf af12ce43717e4a75992251ff53502b88 -#: c29895c695594dc1980ca3fd203467e9 c856405444f74d93a4f4a2b534b17b69 -#: dfa5c7bab4124b7682964ca524ed9740 fa1d336e8c784cc79360732fb8e9c5d8 +#: ../../en/units/thermal.rst:120 01352358cf014d5a9e271dde9aab553f +#: 0a248af7341240b1affc7e4f1c899bae 0c23ca2b76e540a9839052a5e54c3264 +#: 5e5e0d5230f24f2294ced7ad7ea6f126 a1fe0b7c2d7a481584c8073be8a82780 +#: a5415b8a2e7f43cc90410114b23b185a e474c3e63978440f95dabdb6b69c26f7 +#: ec7519505bb148519f21b0973c6c461d ffeb8b3c7d7644a588a26bab5cb685b6 msgid "UIFLOW2:" msgstr "" -#: ../../en/units/thermal.rst:26 e333f8a20cf6465abc6829a5860bf354 -msgid "|init.svg|" +#: ../../en/units/thermal.rst:26 42f39f2a388242678d2a8149a41ea39d +msgid "|init.png|" msgstr "" -#: ../../en/refs/unit.thermal.ref:7 1725ed3e4dbf4973b35d292a59e6d27b -msgid "init.svg" +#: ../../en/refs/unit.thermal.ref:7 d9f19383c02f4618a4fd92275c0bf4ca +msgid "init.png" msgstr "" -#: ../../en/units/thermal.rst:32 42ba189100f541d0a9615f4d82a556ce +#: ../../en/units/thermal.rst:32 525de889e6574bb697320f12330119ea msgid "Methods" msgstr "" -#: ../../en/units/thermal.rst 05cbeff5193f418ab77f7c8d80499565 -#: 28d4c884efdf434187bbfeac047a5833 b7d877b6b9f849909c75999688a67a17 -#: c28e2f9fd75140a0ae815c38e5e79cb5 +#: ../../en/units/thermal.rst 049e1fd3ca1a4f2196e2a9e3fe555758 +#: 4d7bce23d2d4454aa84097cad815dad8 58f98a83c7094f2f81b3f1c97268f599 +#: dd54baf661714529afbf100e747cb51d msgid "type" msgstr "" #: ../../en/units/thermal.rst:36 ../../en/units/thermal.rst:47 #: ../../en/units/thermal.rst:58 ../../en/units/thermal.rst:83 -#: 00f30c344c344fcf9fd3f90b63d6b961 8085275606844b92a1925c6d64a42911 -#: 84038045729646319380eb53b31bff62 8f166760c5fc4bc6a5ef3598c39103cc +#: 3ee935b3080f416bb3a1e0a39bf92b91 62ecffaf3cba42eeba9af279cb73736c +#: 6b9b9bf8892d41e1a0d36e71a40f6c48 82effad1898b42d9a7bc3d7c27c239b1 msgid "float" msgstr "" -#: ../../en/units/thermal.rst:38 a94ee0c575dc437abc00bc4a26f533e0 +#: ../../en/units/thermal.rst:38 2099433d9cb24bc5ba7f5adf0a2931eb msgid "get the max temperature." msgstr "" -#: ../../en/units/thermal.rst:42 77f739c4cb834778bef75b59d3a713b1 -msgid "|get_max_temperature.svg|" +#: ../../en/units/thermal.rst:42 7a3f0752ccca4584bf6fa3d8028e1605 +msgid "|get_max_temperature.png|" msgstr "" -#: ../../en/refs/unit.thermal.ref:9 a07ca3c656f24ebdb1fb6088d8db864e -msgid "get_max_temperature.svg" +#: ../../en/refs/unit.thermal.ref:9 e05ad7e263f24cfcb07cc9070416f22e +msgid "get_max_temperature.png" msgstr "" -#: ../../en/units/thermal.rst:49 a6a9ca00a60646cbb8ce5dc3b1b77502 +#: ../../en/units/thermal.rst:49 a677f604f6e3446392df408290ac5a57 msgid "get the min temperature." msgstr "" -#: ../../en/units/thermal.rst:53 5c591d12696a4311a4c4c9b3ffba291d -msgid "|get_min_temperature.svg|" +#: ../../en/units/thermal.rst:53 ef776a1c464e40af9dcf16d25aa9b456 +msgid "|get_min_temperature.png|" msgstr "" -#: ../../en/refs/unit.thermal.ref:13 d388594bc57a443096f8cb48c7755372 -msgid "get_min_temperature.svg" +#: ../../en/refs/unit.thermal.ref:13 a16e7969c36b424598588200d3841315 +msgid "get_min_temperature.png" msgstr "" -#: ../../en/units/thermal.rst:60 2cc5169c2e454371ac7873ebfd10ad67 +#: ../../en/units/thermal.rst:60 570ff3eeb4fc4b66ba9cb54f6a927d11 msgid "get the midpoint temperature." msgstr "" -#: ../../en/units/thermal.rst:64 d3ceb2f14f60471c900b917efb20267a -msgid "|get_midpoint_temperature.svg|" +#: ../../en/units/thermal.rst:64 f14e82d4c59b479cb4b00d3dd689a8ed +msgid "|get_midpoint_temperature.png|" msgstr "" -#: ../../en/refs/unit.thermal.ref:11 a09a6b22fe624eed905973fd2526278d -msgid "get_midpoint_temperature.svg" +#: ../../en/refs/unit.thermal.ref:11 9334a8f0aa264c8dbfbc1105b6d3125e +msgid "get_midpoint_temperature.png" msgstr "" -#: ../../en/units/thermal.rst:69 247296e3effd44deb44c65a1911007d0 +#: ../../en/units/thermal.rst:69 3dade066921a4f0793e747aef5ebbe3b msgid "get the temperature of the pixel at the specified coordinates." msgstr "" -#: ../../en/units/thermal.rst:71 37fafb2f648e4a589781688bf580de8a +#: ../../en/units/thermal.rst:71 0e7e2fc443034680a973f4ae86ededab msgid "The x coordinate of the pixel." msgstr "" -#: ../../en/units/thermal.rst:72 257eab6bdcfb4057a7514225b27b6ca5 +#: ../../en/units/thermal.rst:72 4f66dbb70ac74d67965779304533ecec msgid "The y coordinate of the pixel." msgstr "" -#: ../../en/units/thermal.rst a24bdea95868491288f33414c5a1c81c -#: a38ad883f2aa44ffbaa2ed70dc162f62 dbf4817cd9964ef7af08e18d93e8ceb4 +#: ../../en/units/thermal.rst 006550580f004698ad1c71454631821f +#: a0bb1aec79674ce18c8ee3b2aeb59b84 fee9d1ebffba40c0a2e6e78c81055299 msgid "Returns" msgstr "" -#: ../../en/units/thermal.rst:74 83cfa732e00a4eb480de31eb2f4af3bf +#: ../../en/units/thermal.rst:74 ea0890d152a64b22b1068d0feb01d8fb msgid "The temperature of the pixel." msgstr "" -#: ../../en/units/thermal.rst:78 e03871fab8854a609ea916bca8a6c227 -msgid "|get_pixel_temperature.svg|" +#: ../../en/units/thermal.rst:78 99fa11ec585f4f148b18f7a08b045504 +msgid "|get_pixel_temperature.png|" msgstr "" -#: ../../en/refs/unit.thermal.ref:15 efcea9b4d3ca4e1ea00995e96a3f4f5b -msgid "get_pixel_temperature.svg" +#: ../../en/refs/unit.thermal.ref:15 2fd6e0e9f96c476988b85db0488333ae +msgid "get_pixel_temperature.png" msgstr "" -#: ../../en/units/thermal.rst:85 c83bef7a29c440389e2de816491ae7bb +#: ../../en/units/thermal.rst:85 77de2c6393d740058f3301fea6fbbb57 msgid "get the refresh rate." msgstr "" -#: ../../en/units/thermal.rst:89 46a10669ba4f45638f339c9db5fdef42 -msgid "|get_refresh_rate.svg|" +#: ../../en/units/thermal.rst:89 95e3b3512d654b5ead1e80da50ded998 +msgid "|get_refresh_rate.png|" msgstr "" -#: ../../en/refs/unit.thermal.ref:17 441f586fa641470d8c0b347a284610bf -msgid "get_refresh_rate.svg" +#: ../../en/refs/unit.thermal.ref:17 14e4617834a24eab957158065237530c +msgid "get_refresh_rate.png" msgstr "" -#: ../../en/units/thermal.rst:94 d4dd5e5b0db64c5d8c0a9eed63424502 +#: ../../en/units/thermal.rst:94 aaf3d261367944f9b59b4bf0e2f5699f msgid "get the temperature buffer." msgstr "" #: ../../en/units/thermal.rst:96 ../../en/units/thermal.rst:118 -#: 81ac6a7e026345aab9142c5c49508867 8ff832908cf943e883f2b23e71c9dd57 +#: be4f331a8801478e83155f604f3adcd5 eb9b33e2888740b583d86e72f9a4c2dc msgid "The temperature buffer." msgstr "" -#: ../../en/units/thermal.rst:100 c4c9b7141f3140cab4fe3ca2d3407161 -msgid "|get_temperature_buffer.svg|" +#: ../../en/units/thermal.rst:100 46b14f1531de4a0f9192adb9b4b693c5 +msgid "|get_temperature_buffer.png|" msgstr "" -#: ../../en/refs/unit.thermal.ref:19 f6a35d448ffe4c30af4c5ce5be13b272 -msgid "get_temperature_buffer.svg" +#: ../../en/refs/unit.thermal.ref:19 c95ada778d6d47a6994f3a2dde6d5280 +msgid "get_temperature_buffer.png" msgstr "" -#: ../../en/units/thermal.rst:105 52b82b1535ff48d59b087b643051598b +#: ../../en/units/thermal.rst:105 34247d27c1db4ab59f0f43ab8538cd4e msgid "Set the refresh rate." msgstr "" -#: ../../en/units/thermal.rst:107 2d91818c19e146de9617778f81044942 +#: ../../en/units/thermal.rst:107 55e2c91dac6140a493e20a4bfca84994 msgid "The refresh rate in Hz." msgstr "" -#: ../../en/units/thermal.rst:111 b944bd84105743d886ae944b002c331e -msgid "|set_refresh_rate.svg|" +#: ../../en/units/thermal.rst:111 03be5d9e4a13442ea8ebcd93cd348c62 +msgid "|set_refresh_rate.png|" msgstr "" -#: ../../en/refs/unit.thermal.ref:21 453e4d4248044218a2a17195aa2550c6 -msgid "set_refresh_rate.svg" +#: ../../en/refs/unit.thermal.ref:21 07985e6e0fcc4e74ae3c89462339510b +msgid "set_refresh_rate.png" msgstr "" -#: ../../en/units/thermal.rst:116 6f2b8906eaaa4cf9905535ea05d468ce +#: ../../en/units/thermal.rst:116 9419c062d24b40419bfe274d0bc7d7ff msgid "Update the temperature buffer." msgstr "" -#: ../../en/units/thermal.rst:122 b762e23155a1420bbe186165507d9b5e -msgid "|update_temperature_buffer.svg|" +#: ../../en/units/thermal.rst:122 99e425a7735245399f5190776fc69ab4 +msgid "|update_temperature_buffer.png|" msgstr "" -#: ../../en/refs/unit.thermal.ref:23 95f9207ff85d46e78e928297ee134903 -msgid "update_temperature_buffer.svg" +#: ../../en/refs/unit.thermal.ref:23 42b4062cf981411ab6abdf1a3374e173 +msgid "update_temperature_buffer.png" msgstr "" +#~ msgid "|init.svg|" +#~ msgstr "" + +#~ msgid "init.svg" +#~ msgstr "" + +#~ msgid "|get_max_temperature.svg|" +#~ msgstr "" + +#~ msgid "get_max_temperature.svg" +#~ msgstr "" + +#~ msgid "|get_min_temperature.svg|" +#~ msgstr "" + +#~ msgid "get_min_temperature.svg" +#~ msgstr "" + +#~ msgid "|get_midpoint_temperature.svg|" +#~ msgstr "" + +#~ msgid "get_midpoint_temperature.svg" +#~ msgstr "" + +#~ msgid "|get_pixel_temperature.svg|" +#~ msgstr "" + +#~ msgid "get_pixel_temperature.svg" +#~ msgstr "" + +#~ msgid "|get_refresh_rate.svg|" +#~ msgstr "" + +#~ msgid "get_refresh_rate.svg" +#~ msgstr "" + +#~ msgid "|get_temperature_buffer.svg|" +#~ msgstr "" + +#~ msgid "get_temperature_buffer.svg" +#~ msgstr "" + +#~ msgid "|set_refresh_rate.svg|" +#~ msgstr "" + +#~ msgid "set_refresh_rate.svg" +#~ msgstr "" + +#~ msgid "|update_temperature_buffer.svg|" +#~ msgstr "" + +#~ msgid "update_temperature_buffer.svg" +#~ msgstr "" + diff --git a/docs/locales/zh_CN/LC_MESSAGES/units/tof4m.po b/docs/locales/zh_CN/LC_MESSAGES/units/tof4m.po new file mode 100644 index 00000000..6d940454 --- /dev/null +++ b/docs/locales/zh_CN/LC_MESSAGES/units/tof4m.po @@ -0,0 +1,334 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2016 - 2025 M5Stack Technology Co., Ltd +# This file is distributed under the same license as the UIFlow2 Programming +# Guide package. +# FIRST AUTHOR , 2025. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: UIFlow2 Programming Guide\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-04-18 15:58+0800\n" +"PO-Revision-Date: 2025-04-09 17:37+0800\n" +"Last-Translator: \n" +"Language: zh_CN\n" +"Language-Team: zh_CN \n" +"Plural-Forms: nplurals=1; plural=0;\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.16.0\n" + +#: ../../en/units/tof4m.rst:2 b87f40539f374c6d86032a0916e4ff2a +msgid "ToF4M Unit" +msgstr "" + +#: ../../en/units/tof4m.rst:8 0235477273f84a4caa65b11e69b20fcf +msgid "" +"This is the driver library of ToF4M Unit, which is used to obtain " +"distance data from the VL53L1CXV0FY sensor." +msgstr "这是 ToF4M 单元的驱动程序库,用于从 VL53L1CXV0FY 传感器获取距离数据。" + +#: ../../en/units/tof4m.rst:11 12d9e3b79a8946f08ea79beb0adbadea +msgid "Support the following products:" +msgstr "支持以下产品:" + +#: ../../en/units/tof4m.rst:13 08b28110e15043f78a56f39858d21db8 +msgid "|ToF4M|" +msgstr "" + +#: ../../en/refs/unit.tof4m.ref ccb25c2931ad4e9aae940e2746458e6e +msgid "ToF4M" +msgstr "" + +#: ../../en/units/tof4m.rst:17 67c58c87fb614c23b02cf5f7bc9e238f +msgid "UiFlow2 Example" +msgstr "UiFlow2 应用示例" + +#: ../../en/units/tof4m.rst:20 ../../en/units/tof4m.rst:38 +#: 25e2e72819c7471cb0725ff9d741017f 715bec408e46460ca9fa1e1d86c0ec57 +msgid "get distance value" +msgstr "获取距离值" + +#: ../../en/units/tof4m.rst:22 33de2e2e0a954ed4b6ae08d196262279 +msgid "Open the |tof4m_core_example.m5f2| project in UiFlow2." +msgstr "在 UiFlow2 中打开 |tof4m_core_example.m5f2| 项目。" + +#: ../../en/units/tof4m.rst:24 ../../en/units/tof4m.rst:40 +#: d054362e9a934e978378f05f291a101c d083d4f99b144d85b5959925715597b3 +msgid "" +"This example gets the distance value of the ToF4M Unit and displays it on" +" the screen." +msgstr "此示例获取 ToF4M 单元的距离值并将其显示在屏幕上。" + +#: ../../en/units/tof4m.rst:26 0b5ab52455d342b98c3450b08f774a0d +#: 0fa8cf13fbd541e4a1161612910cac92 1319a9d37963479597541abf9677e16c +#: 14f49be8a17145f0a996a0cf6775a7a6 278bb8da4c914597ab427ebdbecd8bef +#: 2c63778b26944f95ab17ad6afe1f8318 47f407d58013433cb4b2fd23ac4dcfbe +#: 5429b3231d8e459fa1a0f3e2b79d769b 5812b29cdec74c8cb3dbba29c4de23ca +#: 777bad6ea16b46ec85fb437fb3aede11 7ef0b74254d246eba8c6d8e46be9e027 +#: driver.vl53l1x.VL53L1X:6 driver.vl53l1x.VL53L1X.get_data_ready:6 +#: driver.vl53l1x.VL53L1X.get_distance:6 +#: driver.vl53l1x.VL53L1X.get_distance_mode:6 +#: driver.vl53l1x.VL53L1X.get_measurement_timing_budget:6 +#: driver.vl53l1x.VL53L1X.set_continuous_start_measurement:3 +#: driver.vl53l1x.VL53L1X.set_continuous_stop_measurement:3 +#: driver.vl53l1x.VL53L1X.set_distance_mode:5 +#: driver.vl53l1x.VL53L1X.set_i2c_address:5 +#: driver.vl53l1x.VL53L1X.set_measurement_timing_budget:5 of +msgid "UiFlow2 Code Block:" +msgstr "UiFlow2 代码块:" + +#: ../../en/units/tof4m.rst:28 fd6943710860490b963a4766e3a8113c +msgid "|example.png|" +msgstr "" + +#: ../../en/refs/unit.tof4m.ref:19 fb00b390f29e47eda49fa224c90409f6 +msgid "example.png" +msgstr "" + +#: ../../en/units/tof4m.rst:30 ../../en/units/tof4m.rst:48 +#: 9b0c85a85eeb4d139041f2917b5c22aa ac7ae06e19ee4fa7a1b3314aa9d4c3f0 +msgid "Example output:" +msgstr "示例输出:" + +#: ../../en/units/tof4m.rst:32 ../../en/units/tof4m.rst:50 +#: d8d520712a2344aea4d3459f241a40fc e207a3b7d2964cc099f603bb4ea6496f +msgid "None" +msgstr "" + +#: ../../en/units/tof4m.rst:35 3621509eb6c24f96b8fff5762d96cae5 +msgid "MicroPython Example" +msgstr "MicroPython 示例" + +#: ../../en/units/tof4m.rst:42 1f7bba2743b644b0b3b520b0e5663547 +#: 21d04860068643dbbb31dfc29e0b4246 2f27fbce0d1a4119b14df807a8c9e9df +#: 38657f7e032e4250a5009b354296372c 5e7f99a0a609405fb2ed2943ae264ae6 +#: 7f188bde7c6a4cb6bb213ceee9f02c85 89cfe995130849c080c46e4fcb6a29db +#: bc67e2d379464e4fb020aac65099c24c bfc290702a6c4df082f7cad3bcaa4650 +#: driver.vl53l1x.VL53L1X:10 driver.vl53l1x.VL53L1X.get_data_ready:10 +#: driver.vl53l1x.VL53L1X.get_distance:10 +#: driver.vl53l1x.VL53L1X.get_distance_mode:10 +#: driver.vl53l1x.VL53L1X.get_measurement_timing_budget:10 +#: driver.vl53l1x.VL53L1X.set_continuous_start_measurement:7 +#: driver.vl53l1x.VL53L1X.set_continuous_stop_measurement:7 +#: driver.vl53l1x.VL53L1X.set_distance_mode:9 +#: driver.vl53l1x.VL53L1X.set_i2c_address:9 +#: driver.vl53l1x.VL53L1X.set_measurement_timing_budget:9 +#: e79da18ce15b44a68967e6d8e70e829b f7c3e0fa303645f38f98f4f7ce15a46c of +msgid "MicroPython Code Block:" +msgstr "MicroPython 代码块:" + +#: ../../en/units/tof4m.rst:54 8db67c48f2fb4b9daafc5a084ef8df01 +msgid "**API**" +msgstr "API参考" + +#: ../../en/units/tof4m.rst:57 5d1fcf5da03c43b69e8f86a9fe5edb7e +msgid "TOF4MUnit" +msgstr "" + +#: ../../en/units/tof4m.rst:63 6531b733fdee45abbfb58e79e3661d2f +msgid "VL53L1CXV0FY" +msgstr "" + +#: 0b2965a024674ec98bb0c33b17799adb driver.vl53l1x.VL53L1X:1 of +msgid "Bases: :py:class:`object`" +msgstr "" + +#: 527a3c0f739b45e4b2b5471d400758d8 driver.vl53l1x.VL53L1X:1 of +msgid "Create a VL53L1X object." +msgstr "创建一个 VL53L1X 对象。" + +#: ../../en/units/tof4m.rst 5fb388fcff4e4925bbebb2822445eeea +#: af378dd9be794d6ebc8f07cf3558005d de58fdd1153748e89b0541b862315ea9 +#: driver.vl53l1x.VL53L1X.set_distance_mode +#: driver.vl53l1x.VL53L1X.set_i2c_address +#: driver.vl53l1x.VL53L1X.set_measurement_timing_budget +#: fc6468fe9666486c9932a087d2c575b0 of +msgid "Parameters" +msgstr "参数" + +#: driver.vl53l1x.VL53L1X:3 f5dd9148ec504732bb0258acffe39c4f of +msgid "The I2C bus the ToF4M Unit is connected to." +msgstr "ToF4M Unit 所连接的 I2C 总线。" + +#: 0d2ff5217f6d45899a231f29d48d746f driver.vl53l1x.VL53L1X:4 of +msgid "The I2C address of the device. Default is 0x29." +msgstr "设备的 I2C 地址。默认为 0x29。" + +#: 021461010d5247249d1657b617f7ce02 driver.vl53l1x.VL53L1X:8 of +msgid "|init.png|" +msgstr "" + +#: ../../en/refs/unit.tof4m.ref:7 ecc0bf1f17ce47b9b57dcba7ab7d9d1d +msgid "init.png" +msgstr "" + +#: bc7c8f473902451e85781aa712c46758 driver.vl53l1x.VL53L1X.get_data_ready:1 of +msgid "Returns true if new data is ready, otherwise false." +msgstr "如果新数据已准备就绪,则返回 true,否则返回 false。" + +#: 3d17268287e04875b5efafdb3bbb8656 446a13eb7d504e64ac9db35057a00cb3 +#: d61090ed13cd4f8d8a298c7f337b2df1 driver.vl53l1x.VL53L1X.get_data_ready +#: driver.vl53l1x.VL53L1X.get_distance driver.vl53l1x.VL53L1X.get_distance_mode +#: driver.vl53l1x.VL53L1X.get_measurement_timing_budget +#: f6b89522cd374da5a36d7d492f66e9fc of +msgid "Returns" +msgstr "返回" + +#: driver.vl53l1x.VL53L1X.get_data_ready:3 f4571fa5347f40fd93d67648d658aac7 of +msgid "True if new data is ready." +msgstr "如果新数据已准备就绪,则返回 true。" + +#: 01cafcf47f534d0eae9238f0281d14e3 91ce102ad25f452187eec00102ba177d +#: dc06910000ff4c05869cc68cdbcf816f driver.vl53l1x.VL53L1X.get_data_ready +#: driver.vl53l1x.VL53L1X.get_distance driver.vl53l1x.VL53L1X.get_distance_mode +#: driver.vl53l1x.VL53L1X.get_measurement_timing_budget +#: e02bd7d6064a40ef92f791273cc00c8f of +msgid "Return type" +msgstr "返回类型" + +#: d217c15b10cf400a92c3beb1a18d72b4 driver.vl53l1x.VL53L1X.get_data_ready:8 of +msgid "|get_data_ready.png|" +msgstr "" + +#: ../../en/refs/unit.tof4m.ref:12 9c23b91c550041fe9256e46488752d9c +msgid "get_data_ready.png" +msgstr "" + +#: 57d117688cd5491db1bc3841036bdf59 driver.vl53l1x.VL53L1X.get_distance:1 of +msgid "The distance in units of millimeters." +msgstr "以毫米为单位的距离。" + +#: cff0b6799b7b49318825957940c829de driver.vl53l1x.VL53L1X.get_distance:3 of +msgid "Distance in millimeters or None if measurement is invalid." +msgstr "以毫米为单位的距离;如果测距无效,则为 \"无\"。" + +#: b90cc793b0a9487a92423cdb4d1002a1 driver.vl53l1x.VL53L1X.get_distance:8 of +msgid "|get_distance.png|" +msgstr "" + +#: ../../en/refs/unit.tof4m.ref:9 8a48fe859c1f46f597d43a6128aa2cab +msgid "get_distance.png" +msgstr "" + +#: driver.vl53l1x.VL53L1X.get_distance_mode:1 ff2cd5304c5d40f6b8cba7a6498a5238 +#: of +msgid "Get the distance mode." +msgstr "获取距离模式。" + +#: 0ee823e7ad5e435cb314270fb8dc6256 driver.vl53l1x.VL53L1X.get_distance_mode:3 +#: of +msgid "distance mode(1=short, 2=long)." +msgstr "距离模式(1=短距模式, 2=长距模式)." + +#: 0de2c5e12a294486a4f91549ca5fa566 driver.vl53l1x.VL53L1X.get_distance_mode:8 +#: of +msgid "|get_distance_mode.png|" +msgstr "" + +#: ../../en/refs/unit.tof4m.ref:15 35ae1ddc9d944d60a27c6413964e4f5b +msgid "get_distance_mode.png" +msgstr "" + +#: driver.vl53l1x.VL53L1X.get_measurement_timing_budget:1 +#: f73328f6b3eb4290bae6b78c4fe9d6ee of +msgid "Get measurement duration in milliseconds." +msgstr "获取测距持续时间(以毫秒为单位)。" + +#: ceea9559221a4c039d07f88679f14a73 +#: driver.vl53l1x.VL53L1X.get_measurement_timing_budget:3 of +msgid "The timing budget in milliseconds." +msgstr "以毫秒为单位的测距定时预算。" + +#: 0165006827734cbda1637435bbead8b8 +#: driver.vl53l1x.VL53L1X.get_measurement_timing_budget:8 of +msgid "|get_measurement_timing_budget.png|" +msgstr "" + +#: ../../en/refs/unit.tof4m.ref:13 71b7898356bc4894846e31af96f2df3d +msgid "get_measurement_timing_budget.png" +msgstr "" + +#: 17ac6328e1f940189d77be2d5a4d27f0 +#: driver.vl53l1x.VL53L1X.set_continuous_start_measurement:1 of +msgid "Starts continuous measure operation." +msgstr "开始连续测距操作。" + +#: 1eca89015ac54598b10729e44cf59df4 +#: driver.vl53l1x.VL53L1X.set_continuous_start_measurement:5 of +msgid "|set_continuous_start_measurement.png|" +msgstr "" + +#: ../../en/refs/unit.tof4m.ref:10 2365bb5c45974e5cafb66dc3448904be +msgid "set_continuous_start_measurement.png" +msgstr "" + +#: 96a4d4f9dbb843b98b492b5b5e9f2538 +#: driver.vl53l1x.VL53L1X.set_continuous_stop_measurement:1 of +msgid "Stops measure operation." +msgstr "停止测距操作。" + +#: 23a4919f57f04aa6beb1c2a7370d8caa +#: driver.vl53l1x.VL53L1X.set_continuous_stop_measurement:5 of +msgid "|set_continuous_stop_measurement.png|" +msgstr "" + +#: ../../en/refs/unit.tof4m.ref:11 edd229e5488d418dac240e05f847b0a0 +msgid "set_continuous_stop_measurement.png" +msgstr "" + +#: driver.vl53l1x.VL53L1X.set_distance_mode:1 f7f90d99a4d44afb90d084c123be53ba +#: of +msgid "Set the distance mode." +msgstr "" + +#: 07ed46eacbba43c696e39bab26ba3ac8 driver.vl53l1x.VL53L1X.set_distance_mode:3 +#: of +msgid "1=short, 2=long." +msgstr "" + +#: bd4148ac11b2422daa3d8d67ec27e7e2 driver.vl53l1x.VL53L1X.set_distance_mode:7 +#: of +msgid "|set_distance_mode.png|" +msgstr "" + +#: ../../en/refs/unit.tof4m.ref:16 323df9915b854f19b56c9964dbc82b2a +msgid "set_distance_mode.png" +msgstr "" + +#: 8550ec3767264bd88b7e58ae1b7e58bc driver.vl53l1x.VL53L1X.set_i2c_address:1 of +msgid "Set a new I2C address to the instantiated object." +msgstr "" + +#: 4015d4e1ca9b4c4680aff40c8c00a9ad driver.vl53l1x.VL53L1X.set_i2c_address:3 of +msgid "The new I2C address." +msgstr "" + +#: 19c54e1f01df4612a44748ac1aa157eb driver.vl53l1x.VL53L1X.set_i2c_address:7 of +msgid "|set_i2c_address.png|" +msgstr "" + +#: ../../en/refs/unit.tof4m.ref:17 3b3b21cea1e541f29fc4c7fb5d9e0169 +msgid "set_i2c_address.png" +msgstr "" + +#: d18e02b4e5e74fcd9457398bd02e15b2 +#: driver.vl53l1x.VL53L1X.set_measurement_timing_budget:1 of +msgid "Set the measurement timing budget in milliseconds." +msgstr "以毫秒为单位设置测距定时预算。" + +#: b49380ce1a65449eb4d46692316f30f4 +#: driver.vl53l1x.VL53L1X.set_measurement_timing_budget:3 of +msgid "Timing budget in milliseconds." +msgstr "以毫秒为单位的测距定时预算。" + +#: 819a6d526b5a470ab5e73912a35d649b +#: driver.vl53l1x.VL53L1X.set_measurement_timing_budget:7 of +msgid "|set_measurement_timing_budget.png|" +msgstr "" + +#: ../../en/refs/unit.tof4m.ref:14 2f10d0ec4abe407f8f915a3f816cb9f5 +msgid "set_measurement_timing_budget.png" +msgstr "" diff --git a/docs/locales/zh_CN/LC_MESSAGES/units/ultrasonic.po b/docs/locales/zh_CN/LC_MESSAGES/units/ultrasonic.po index 2d05e014..3e238dee 100644 --- a/docs/locales/zh_CN/LC_MESSAGES/units/ultrasonic.po +++ b/docs/locales/zh_CN/LC_MESSAGES/units/ultrasonic.po @@ -9,7 +9,7 @@ msgid "" msgstr "" "Project-Id-Version: UIFlow2 Programming Guide \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-03-11 15:31+0800\n" +"POT-Creation-Date: 2025-04-18 15:58+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language: zh_CN\n" @@ -18,94 +18,108 @@ msgstr "" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" -"Generated-By: Babel 2.12.1\n" +"Generated-By: Babel 2.16.0\n" -#: ../../en/units/ultrasonic.rst:2 2010a688e87b4172b7edf74c63fdb6e0 +#: ../../en/units/ultrasonic.rst:2 2d1c7199191a4886a380db2341fec0fe msgid "Ultrasonic Unit" msgstr "" -#: ../../en/refs/unit.ultrasonic.ref 4739453b5ee34455832a3e199a337f75 -#: cdacc292ae4f4c9e8e5aa7cf082e1303 -msgid "Ultrasonic" -msgstr "" - -#: ../../en/refs/unit.ultrasonic.ref:7 d22102cf16734931b91edad6459de5aa -#: ef6d4ba6bb284ad284a3514c2a0f3418 -msgid "example.svg" -msgstr "" - -#: ../../en/refs/unit.ultrasonic.ref:9 abbb8afb43a841f6b4f4bf1c13a9b4ef -#: afa95cc9f731435db877a76dd9aaa43f -msgid "init.svg" -msgstr "" - -#: ../../en/refs/unit.ultrasonic.ref:11 44f3e0a168484189b3faeee0e9c45f5b -#: 5bfe74041b9849dbb232b33916503ded -msgid "get_target_distance.svg" -msgstr "" - -#: ../../en/units/ultrasonic.rst:6 50b01a50cda349e99d1fa48f052947f0 +#: ../../en/units/ultrasonic.rst:6 0b19677ee6fd4e3a897fe8c9bfe576a1 msgid "Support the following products:" msgstr "支持以下产品:" -#: ../../en/units/ultrasonic.rst:8 9b08a48932aa47268f663c6bffb1a2bf +#: ../../en/units/ultrasonic.rst:8 016ccee5eb614dfcbd83de7a2d4bd1f7 msgid "|Ultrasonic|" msgstr "" -#: ../../en/units/ultrasonic.rst:11 3cf7ddef1fca45fe98b92a9c82a46482 +#: ../../en/refs/unit.ultrasonic.ref 6672f1a6417a47dd9ed769fb3cab3b7a +msgid "Ultrasonic" +msgstr "" + +#: ../../en/units/ultrasonic.rst:11 c3546b2e4cb24f5ca0f48881802a3802 msgid "Micropython Example::" msgstr "" -#: ../../en/units/ultrasonic.rst:31 66b5e3235c844288bf48be2a418a0cfa +#: ../../en/units/ultrasonic.rst:31 6db0856c0fe54bd4862e9daaa9ad212e msgid "UIFLOW2 Example:" msgstr "" -#: ../../en/units/ultrasonic.rst:33 47169b832b634598a2c7307c5d24116d -msgid "|example.svg|" +#: ../../en/units/ultrasonic.rst:33 8d8b83cd003341818e72f6c90325fe6c +msgid "|example.png|" msgstr "" -#: ../../en/units/ultrasonic.rst:38 715e6fec31f64550820f7422678a9dd2 +#: ../../en/refs/unit.ultrasonic.ref:7 45a304a861104a6897ad125c48be6cb1 +msgid "example.png" +msgstr "" + +#: ../../en/units/ultrasonic.rst:38 eb98e20571d048edb45ffb9661be541c msgid "|ultrasonic_core_example.m5f2|" msgstr "" -#: ../../en/units/ultrasonic.rst:42 66fd48e6f41a48d8801f7e891a5f7ffa +#: ../../en/units/ultrasonic.rst:42 01e60b7ce5964cb7abec5bfbaa107b10 msgid "class ULTRASONIC_I2C" msgstr "" -#: ../../en/units/ultrasonic.rst:45 94e4ceb1150e430bb0c755da223174d6 +#: ../../en/units/ultrasonic.rst:45 1abdfc7aea9840d896e019b27a4c5e74 msgid "Constructors" msgstr "" -#: ../../en/units/ultrasonic.rst:49 8b71dd0f072b4bad915478e0796f9797 +#: ../../en/units/ultrasonic.rst:49 be8c155d0d4f4823ab0390e2774b4149 msgid "Create a ULTRASONIC I2C object." msgstr "创建一个ULTRASONIC_I2C对象." -#: ../../en/units/ultrasonic.rst:52 5ee13a2cad1844f99ac561eb1e2a9b21 +#: ../../en/units/ultrasonic.rst:52 f24bd778ab3c47c695557a9997ac8fb2 msgid "The parameters is:" msgstr "参数是:" -#: ../../en/units/ultrasonic.rst:52 bc0c9b1672f541928d806f4bd498d26c +#: ../../en/units/ultrasonic.rst:52 7df1c8063ad747bb9427c0e2d50d0de9 msgid "``PORT`` Define an i2c port." msgstr "``PORT`` 定义i2c端口。" #: ../../en/units/ultrasonic.rst:54 ../../en/units/ultrasonic.rst:66 -#: 51d06c9fb2684b3b9e1f75d998eff284 c786bfc52f434e0395369a6e3c6ae83a +#: 4df1ca8c10c04c0a97658fa3408dea87 ad4284d905164d30867f77640beebc7f msgid "UIFLOW2:" msgstr "" -#: ../../en/units/ultrasonic.rst:56 db545a3389a142798a82dc0c34480fec -msgid "|init.svg|" +#: ../../en/units/ultrasonic.rst:56 07a9a98eb92e4e8a9c492996e0fe238f +msgid "|init.png|" +msgstr "" + +#: ../../en/refs/unit.ultrasonic.ref:9 f65c2e661f4d4f919cbca17858b5279a +msgid "init.png" msgstr "" -#: ../../en/units/ultrasonic.rst:60 d76f5e58fef44708bc998b5251bcb777 +#: ../../en/units/ultrasonic.rst:60 1b62db4b742c4a1b8c06742d78985f0b msgid "Methods" msgstr "" -#: ../../en/units/ultrasonic.rst:64 5f95b0f956054597bb373eb7aad73037 +#: ../../en/units/ultrasonic.rst:64 1f6e01cf908b43bc8810cd70cfe7a669 msgid "Acquire transmitting distance" msgstr "获取发射距离" -#: ../../en/units/ultrasonic.rst:68 6fd63686b78a4d07972759fc713c7235 -msgid "|get_target_distance.svg|" +#: ../../en/units/ultrasonic.rst:68 c32009c62fcd48759ec96a78b1b40282 +msgid "|get_target_distance.png|" +msgstr "" + +#: ../../en/refs/unit.ultrasonic.ref:11 69715c4484fe410d9de0ec3ef76c803b +msgid "get_target_distance.png" msgstr "" +#~ msgid "example.svg" +#~ msgstr "" + +#~ msgid "init.svg" +#~ msgstr "" + +#~ msgid "get_target_distance.svg" +#~ msgstr "" + +#~ msgid "|example.svg|" +#~ msgstr "" + +#~ msgid "|init.svg|" +#~ msgstr "" + +#~ msgid "|get_target_distance.svg|" +#~ msgstr "" + diff --git a/docs/locales/zh_CN/LC_MESSAGES/units/vibrator.po b/docs/locales/zh_CN/LC_MESSAGES/units/vibrator.po index bcec20aa..df0a1bbd 100644 --- a/docs/locales/zh_CN/LC_MESSAGES/units/vibrator.po +++ b/docs/locales/zh_CN/LC_MESSAGES/units/vibrator.po @@ -9,7 +9,7 @@ msgid "" msgstr "" "Project-Id-Version: UIFlow2 Programming Guide \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-01-16 15:35+0800\n" +"POT-Creation-Date: 2025-04-18 15:58+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language: zh_CN\n" @@ -20,158 +20,161 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.16.0\n" -#: ../../en/units/vibrator.rst:2 b1b2ccd5e4e64017abb0f1c4ff46cef0 +#: ../../en/units/vibrator.rst:2 7752010a10dc49b5849c8db3992ab479 msgid "Vibrator Unit" msgstr "" -#: ../../en/units/vibrator.rst:7 59c88a7b683448058872b5469987a0d1 +#: ../../en/units/vibrator.rst:7 cd2a5639e8574442874e92caf6c4fb2f msgid "Support the following products:" msgstr "" -#: ../../en/units/vibrator.rst:9 a7eda4e574d1430883e1ea01d155c673 +#: ../../en/units/vibrator.rst:9 f6464f928af7442f8f99a1564bc088bd msgid "|Vibrator|" msgstr "" -#: ../../en/refs/unit.vibrator.ref b103efb5b7c34e4eade78bae8414aa08 +#: ../../en/refs/unit.vibrator.ref ce3c3638a82f457da3cc38727cd6e808 msgid "Vibrator" msgstr "" -#: ../../en/units/vibrator.rst:12 a470e307328a4b08929b978cb3a1f20b +#: ../../en/units/vibrator.rst:12 7111b468aa8f48deb5af2ad0602f6926 msgid "Micropython Example:" msgstr "" -#: ../../en/units/vibrator.rst:19 3a1871069a484fa294f27125c508d2a2 +#: ../../en/units/vibrator.rst:19 3107c473ff3749f59f5aecf44355320c msgid "UIFLOW2 Example:" msgstr "" -#: ../../en/units/vibrator.rst:21 ff9afa5a206d4c6ca8947488d05c9cdf -msgid "|example.svg|" +#: ../../en/units/vibrator.rst:21 d71e9f89e0dc4eaaae1436fb5276e5e2 +msgid "|example.png|" msgstr "" -#: ../../en/refs/unit.vibrator.ref:6 46f710a4e3e247458f60d1591ce55a98 -msgid "example.svg" +#: ../../en/refs/unit.vibrator.ref:6 3aec66c841c844a6825847c3dc564599 +msgid "example.png" msgstr "" -#: ../../en/units/vibrator.rst:26 2681f5c398364eb59b3364fbe242dfeb +#: ../../en/units/vibrator.rst:26 303489e8db24402a8cd5e98490c84c1f msgid "|core_vibrator_example.m5f2|" msgstr "" -#: ../../en/units/vibrator.rst:30 aa2b1c164ea74f9fad7c9cf47663ece7 +#: ../../en/units/vibrator.rst:30 705a3d5e6da446dba21746d2f594d3ec msgid "class VibratorUnit" msgstr "" -#: ../../en/units/vibrator.rst:33 a5ac0dc9396e46918300837d29fc4e61 +#: ../../en/units/vibrator.rst:33 f9991c8aa589433cb72df6067d9d3c91 msgid "Constructors" msgstr "" -#: ../../en/units/vibrator.rst:37 6dea0c7be17948ef914a715f81ef0228 +#: ../../en/units/vibrator.rst:37 b2b7f8970cae4c4e9cece53f3bdfeb7a msgid "Create an VibratorUnit object." msgstr "" -#: ../../en/units/vibrator.rst a1f3c77460a349aaac64c2ea37b33e88 -#: beeecd26cea14a2ea88236cffa7ef284 e014ce154f8a4f56b8a9532ddada7a4f -#: fb6c83be273e43868fd8232516f81a2f +#: ../../en/units/vibrator.rst 1167a96f7fcf44d28d72148d56abfdc5 +#: ee0f8f1a1cf34bd7947984ec8b6ccb9e msgid "Parameters" msgstr "" -#: ../../en/units/vibrator.rst:39 ed6ad468e99849958a99e96af606257e +#: ../../en/units/vibrator.rst:39 0a377df9256f4402b73f1e0c59602fa4 msgid "The port where the VibratorUnit is connected to." msgstr "" #: ../../en/units/vibrator.rst:41 ../../en/units/vibrator.rst:57 #: ../../en/units/vibrator.rst:68 ../../en/units/vibrator.rst:79 #: ../../en/units/vibrator.rst:88 ../../en/units/vibrator.rst:97 -#: 726eca67c0474273b260ec95b6398cd9 7cd744302ab94a12a1c2e7083f1edf93 -#: 945547a0f545420286ae09b3cd849a48 c40ca8670db540688dfb13642c13c87b -#: e71f557c2cb74f05977c2c407d1d6aac f55aa4c94fed45eb8ef590a7c15178a3 +#: 96d50a6e0b2d4940b050860d0c1ff751 msgid "UIFLOW2:" msgstr "" -#: ../../en/units/vibrator.rst:43 8b58eb525b6745fb84a6b6a4e9a7eff0 +#: ../../en/units/vibrator.rst:43 c7ba412dab4b49c191f4181b8f8f05b4 msgid "|init.png|" msgstr "" -#: ../../en/refs/unit.vibrator.ref:10 cf683b0f37934443862ec5a4d49fb514 +#: ../../en/refs/unit.vibrator.ref:8 869b8b6fc20d461b9be9d7d85c2fe2a6 msgid "init.png" msgstr "" -#: ../../en/units/vibrator.rst:47 ce4be9bdc12a4e2981e8d83340d35377 +#: ../../en/units/vibrator.rst:47 9b04a85ac1be4dc5af71db7837fc08e6 msgid "Methods" msgstr "" -#: ../../en/units/vibrator.rst:51 552a699b8e9b4eea89d4474fe72796de +#: ../../en/units/vibrator.rst:51 4a5ce1490d1f40a681773794012c383b msgid "Play the haptic effect once on the motor." msgstr "" #: ../../en/units/vibrator.rst:53 ../../en/units/vibrator.rst:66 -#: 4e7ca5c7f4db46f3a02c0c09d0228571 c089fd000bb648139f2ec5d482ce07f4 +#: 526dc53f125f4a87b7899a2a34653adc fb6a4d7d0c6145b1960a31f50619b016 msgid "The frequency of vibration ranges from 10-55Hz." msgstr "" #: ../../en/units/vibrator.rst:54 ../../en/units/vibrator.rst:77 -#: a4fb06d958fa4f81a6dcbf24042751b1 c29f7ae8f3cf4442bb9b15a3a3b6876a +#: ea5acc3cad2247c6b097a707fddd6733 ef2859a4c9ef47e9bc2abb73dd52541b msgid "" "The duty cycle of vibration ranges from 0-100, representing the " "corresponding percentage." msgstr "" -#: ../../en/units/vibrator.rst:55 aaf31b032b3046838ac077098fe01f81 +#: ../../en/units/vibrator.rst:55 8223cf841edf4e318fc0abd90efee44a msgid "The duration of the vibration effect, in milliseconds." msgstr "" -#: ../../en/units/vibrator.rst:59 7b1def7005ef4be88e22c5559263864e +#: ../../en/units/vibrator.rst:59 d56ebdf6bbc746e386bceb9eb2a622de msgid "|once.png|" msgstr "" -#: ../../en/refs/unit.vibrator.ref:13 9ac195fdcc874a4090c56c632673a9ef +#: ../../en/refs/unit.vibrator.ref:10 a623aad17e1e48708676376c268866a1 msgid "once.png" msgstr "" -#: ../../en/units/vibrator.rst:64 81ea3cdbd80e4e868082b893f8d9c94f +#: ../../en/units/vibrator.rst:64 042af61dc3d84afab58150874856a314 msgid "Set the vibration frequency." msgstr "" -#: ../../en/units/vibrator.rst:70 d968f90e26134c6c9d58f3623e600feb +#: ../../en/units/vibrator.rst:70 11a7548e5b6548e38c0ba0abdc9c2de2 msgid "|set_freq.png|" msgstr "" -#: ../../en/refs/unit.vibrator.ref:16 4c80f55450864acca118d43f2936265a +#: ../../en/refs/unit.vibrator.ref:12 cd630eaa1f794b8180e7c80474e90298 msgid "set_freq.png" msgstr "" -#: ../../en/units/vibrator.rst:75 14a44d50e2b04f6cb2af086402a7bf25 +#: ../../en/units/vibrator.rst:75 ea4c132b49c041f49681c31aacb91798 msgid "Set the vibration duty cycle." msgstr "" -#: ../../en/units/vibrator.rst:81 f408674371b342b392336f422643c69b +#: ../../en/units/vibrator.rst:81 61f5bc36b7ef491b8ff45ddfa9877f4e msgid "|set_duty.png|" msgstr "" -#: ../../en/refs/unit.vibrator.ref:19 c2101a0a37d64413899171797547e865 +#: ../../en/refs/unit.vibrator.ref:14 8e22207d8f444adc9bbf5976d4b48580 msgid "set_duty.png" msgstr "" -#: ../../en/units/vibrator.rst:86 d9c01237c34245e38692b3a085ef367b +#: ../../en/units/vibrator.rst:86 c6fafb8ddffc49918f090beee2789328 msgid "Turn off the motor." msgstr "" -#: ../../en/units/vibrator.rst:90 18a590165c754741bfa08f62592c3b94 +#: ../../en/units/vibrator.rst:90 7e5d5fd8959547589a28e50424ed2d89 msgid "|turn_off.png|" msgstr "" -#: ../../en/refs/unit.vibrator.ref:22 d8ec27822d124be193cfc569a8c3b440 +#: ../../en/refs/unit.vibrator.ref:16 e84221156b514556b7d6a22f4895454e msgid "turn_off.png" msgstr "" -#: ../../en/units/vibrator.rst:95 726c822b81484516b2ee822dced38282 +#: ../../en/units/vibrator.rst:95 5f0be1f867c249a4ae960767fcb73696 msgid "Deinitialize the motor." msgstr "" -#: ../../en/units/vibrator.rst:99 62a3f4bb9e0c46ad91e11678d7f4d3f0 +#: ../../en/units/vibrator.rst:99 8ae959fc42144ca699ba9d8b088bd5af msgid "|deinit.png|" msgstr "" -#: ../../en/refs/unit.vibrator.ref:25 f08ed8724a7b4fd8b60f2b7cd7873c96 +#: ../../en/refs/unit.vibrator.ref:18 d0901c9c6a0b431984bb1864a79123cb msgid "deinit.png" msgstr "" +#~ msgid "|example.svg|" +#~ msgstr "" + +#~ msgid "example.svg" +#~ msgstr "" + diff --git a/docs/locales/zh_CN/LC_MESSAGES/units/weight_i2c.po b/docs/locales/zh_CN/LC_MESSAGES/units/weight_i2c.po index 765692ce..dbed19ad 100644 --- a/docs/locales/zh_CN/LC_MESSAGES/units/weight_i2c.po +++ b/docs/locales/zh_CN/LC_MESSAGES/units/weight_i2c.po @@ -9,7 +9,7 @@ msgid "" msgstr "" "Project-Id-Version: UIFlow2 Programming Guide \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-01-16 15:35+0800\n" +"POT-Creation-Date: 2025-04-18 15:58+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language: zh_CN\n" @@ -20,11 +20,11 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.16.0\n" -#: ../../en/units/weight_i2c.rst:2 f4d9f346235b4a109ee67c86c0e1c618 +#: ../../en/units/weight_i2c.rst:2 0cabd1ed42f0498795a2c9e09d5a0055 msgid "Weight I2C Unit" msgstr "" -#: ../../en/units/weight_i2c.rst:6 e4c7b7957c564a969ee7d5f0b3ecafe6 +#: ../../en/units/weight_i2c.rst:6 410f319089c14978bf7713bf63803b25 msgid "" "The ``Weight I2C`` Unit is a weighing acquisition transmitter unit that " "adopts the \"STM32+HX711 chip\" solution to achieve 24-bit precision " @@ -35,55 +35,55 @@ msgstr "" "Weight I2C " "单元是一个称重采集发射单元,它采用“STM32+HX711芯片”方案,通过I2C通信实现24位精度的重量测量,但不包括称重传感器。这个单元能够测量重量,并包含各种滤波器。" -#: ../../en/units/weight_i2c.rst:8 5a33a3611e064f19884a5e5b366568d3 +#: ../../en/units/weight_i2c.rst:8 58c38a68d3524204b201c8bdd18748b4 msgid "Support the following products:" msgstr "支持以下产品:" -#: ../../en/units/weight_i2c.rst:11 31bd5c082fe84a97ba3814b72d534b33 +#: ../../en/units/weight_i2c.rst:11 961e26722ab94a4d98ef0f0f6ed7342c msgid "|WEIGHT I2C|" msgstr "" -#: ../../en/refs/unit.weight_i2c.ref 5a29cb9fa4fd4e219a66dd0cd997990c +#: ../../en/refs/unit.weight_i2c.ref 450adc91387e4eaa82c19d517c953985 msgid "WEIGHT I2C" msgstr "" -#: ../../en/units/weight_i2c.rst:15 62d8ed62ddcf4157bbea7af3e8d705d8 +#: ../../en/units/weight_i2c.rst:15 5f7e2cf2c0974225bff6af74bbf25662 msgid "Micropython Example::" msgstr "" -#: ../../en/units/weight_i2c.rst:31 546c8297cafa4fb5a164c6cc2d0fd3a6 +#: ../../en/units/weight_i2c.rst:31 1dd993b4e9b54c9299c7b34a278b694c msgid "UIFLOW2 Example:" msgstr "" -#: ../../en/units/weight_i2c.rst:33 c585550d1c864bafa40b2a50b9e806ea -msgid "|example.svg|" +#: ../../en/units/weight_i2c.rst:33 188beeaa27694a608c7d29beb3f5dc4b +msgid "|example.png|" msgstr "" -#: ../../en/refs/unit.weight_i2c.ref:36 52def3f59c3b412fbd90354dc984d12b -msgid "example.svg" +#: ../../en/refs/unit.weight_i2c.ref:36 d9e59979602e4cccbd65bd192cffb0f8 +msgid "example.png" msgstr "" -#: ../../en/units/weight_i2c.rst:37 237d70dee2ad475bac92f759e84dd6b6 +#: ../../en/units/weight_i2c.rst:37 4aeca19d944c47bab2130aa7cdd39b4b msgid "|weight-i2c-demo.m5f2|" msgstr "" -#: ../../en/units/weight_i2c.rst:41 e863441ecd8640c495da6504df8df27b +#: ../../en/units/weight_i2c.rst:41 35721931626f42a49be4c39df11560c3 msgid "class WEIGHT_I2CUnit" msgstr "" -#: ../../en/units/weight_i2c.rst:44 c787beef343148d5807f52ac2991468d +#: ../../en/units/weight_i2c.rst:44 cc8ce60b034a4b49affbd7ae8df27e6a msgid "Constructors" msgstr "" -#: ../../en/units/weight_i2c.rst:48 b3fabca272384dc98c00b4c3e5b56cce +#: ../../en/units/weight_i2c.rst:48 65408828169349b6b300820ae057633e msgid "Create an WEIGHT_I2CUnit object." msgstr "创建一个 WEIGHT_I2CUnit 对象。" -#: ../../en/units/weight_i2c.rst:50 eb9249a079a54165b699bb5c5749cf66 +#: ../../en/units/weight_i2c.rst:50 dc6682f364ff430286fbceb78768aab6 msgid "``I2C0`` is I2C Port." msgstr "``I2C0`` is I2C Port." -#: ../../en/units/weight_i2c.rst:51 57652a72bfec4daeaa61a19ef5391cdc +#: ../../en/units/weight_i2c.rst:51 b08c0a0d18dd4345ba37274da4e30712 msgid "``0x26`` is default I2C address" msgstr "``0x26`` is default I2C address" @@ -94,249 +94,345 @@ msgstr "``0x26`` is default I2C address" #: ../../en/units/weight_i2c.rst:143 ../../en/units/weight_i2c.rst:153 #: ../../en/units/weight_i2c.rst:161 ../../en/units/weight_i2c.rst:173 #: ../../en/units/weight_i2c.rst:181 ../../en/units/weight_i2c.rst:191 -#: ../../en/units/weight_i2c.rst:201 28430d1288e34b92a0a1dcd983f40c86 -#: 30153777b3424764a6f87bad9a969523 362b26efac0e41cfa0244ffd11071a02 -#: 390f7415d48340a6bf23841022a60333 4687de3bd86a46cb8b37cf4a553b0753 -#: 72773eaae23c4c439320c6b0794238b6 811d101399bb4a2b890bd3ee1018180e -#: 82e9a218626647aeb88efc8c3ce51b53 86f4f4193dd049819b0478d4d9c85494 -#: 9ef8f9b1be704e508f6b259c4acfb4d6 cbd872ca0077458a914135cb789d06e8 -#: cec90d619bce4d2f982edce8644bf68b d0969010262740dcb0d446e4564c2a9e -#: d6f68f6e5eee42888886273e9b2adbfd fc1cf4f3d7054d7bb3a2a396775e6a10 +#: ../../en/units/weight_i2c.rst:201 0dd02d079e6d4b97906360b135692c39 +#: 0f611051e33c4596bf89894cfe31b89e 1ce210bdc63c4002980f6af9640b8cd0 +#: 331f354b9f2b4e828cc054b9861eefda 3610d014b6b4424aba773c91f95949f5 +#: 3638b71b036342f5862c226bd77320ae 5adc9c4dedd2457b988749ec303ef77e +#: 7e309d61522d421fb5fbdcee3b0db2c2 8d23690c6d9d4bfeb338636193d27d48 +#: ae247a267a6143d38f771292d0261298 ba1f8db60e1148fca5f506354b66d99c +#: c0c57ff04989406fbf36cea97c9f85ef c0ec5ffa6cf944208e53a74a7e776ed6 +#: d043f3adcc0b4d7fba2bd35fab12e75d d368106dbfcc44bbb99a2562c4ee0d06 msgid "UIFLOW2:" msgstr "" -#: ../../en/units/weight_i2c.rst:56 e03c0ef988a946198142708be2054352 -msgid "|init_i2c_address.svg|" +#: ../../en/units/weight_i2c.rst:56 d928264aa2f241e1b4f457480929945e +msgid "|init_i2c_address.png|" msgstr "" -#: ../../en/refs/unit.weight_i2c.ref:6 d5cb1a9fd64241158abadc936952ec75 -msgid "init_i2c_address.svg" +#: ../../en/refs/unit.weight_i2c.ref:6 c7debc45c9c744aea4503b043047f2f3 +msgid "init_i2c_address.png" msgstr "" -#: ../../en/units/weight_i2c.rst:60 996222c5f9de473da24b3264feda4dbd +#: ../../en/units/weight_i2c.rst:60 d4de0b406be74092a5dbe5a26144234c msgid "Methods" msgstr "" -#: ../../en/units/weight_i2c.rst:65 eb189d5045044c289fc57299c653ee97 +#: ../../en/units/weight_i2c.rst:65 d558d0646cf747a69498519dd640807a msgid "Gets the raw adc value." msgstr "获取原始的 ADC 值。" -#: ../../en/units/weight_i2c.rst:69 a3109c7e311c46c884dbf9e5cb69070b -msgid "|get_adc_raw.svg|" +#: ../../en/units/weight_i2c.rst:69 007eed6dfe3346daa04e5feb568edd74 +msgid "|get_adc_raw.png|" msgstr "" -#: ../../en/refs/unit.weight_i2c.ref:8 ac0a86167e504ff6bc289fb07f6aa633 -msgid "get_adc_raw.svg" +#: ../../en/refs/unit.weight_i2c.ref:8 f26c846f4452400786e2838700bd6461 +msgid "get_adc_raw.png" msgstr "" -#: ../../en/units/weight_i2c.rst:74 f99a0273e0ce4b58940bd24e3d6e501c +#: ../../en/units/weight_i2c.rst:74 33f73ff12bf7462ca30ae01083a81273 msgid "Gets the weight in grams float value." msgstr "获取以克为单位的重量浮点值。" -#: ../../en/units/weight_i2c.rst:78 693c525ca1de4ed78ba599cee5a837db -msgid "|get_weight_float.svg|" +#: ../../en/units/weight_i2c.rst:78 ba5c7f22d103424fb50a6f5626f32579 +msgid "|get_weight_float.png|" msgstr "" -#: ../../en/refs/unit.weight_i2c.ref:10 2ee712c64af6420382cb03d8a52a8c87 -msgid "get_weight_float.svg" +#: ../../en/refs/unit.weight_i2c.ref:10 f7ba754a0de14e428f7302f1ea866c3c +msgid "get_weight_float.png" msgstr "" -#: ../../en/units/weight_i2c.rst:83 17634abd90734293acd5c70085663bd9 +#: ../../en/units/weight_i2c.rst:83 af2ee3cd05f7466abe8792e0760074ac msgid "Gets the weight in grams int value." msgstr "获取以克为单位的重量整数值。 " -#: ../../en/units/weight_i2c.rst:87 3bdf9beef6054ec88ced7bda8107d2fa -msgid "|get_weight_int.svg|" +#: ../../en/units/weight_i2c.rst:87 5b91517e394e4e93a99172f60caf5a8f +msgid "|get_weight_int.png|" msgstr "" -#: ../../en/refs/unit.weight_i2c.ref:12 11854db5f3a44d4eac98e163eac4b647 -msgid "get_weight_int.svg" +#: ../../en/refs/unit.weight_i2c.ref:12 be2c7c651d47489b8b580a61f0f1c1f2 +msgid "get_weight_int.png" msgstr "" -#: ../../en/units/weight_i2c.rst:92 72666fe6f87e4563b4d0e387d112d0b0 +#: ../../en/units/weight_i2c.rst:92 3e4ef59c93eb4074bebe1224fa9b48c4 msgid "Gets the weight in grams string value." msgstr "获取以克为单位的重量字符串值。" -#: ../../en/units/weight_i2c.rst:96 849f63d00733446395178f71cd78e33f -msgid "|get_weight_str.svg|" +#: ../../en/units/weight_i2c.rst:96 ab7d9ac60de94f50b3f6fea0b2a02dfc +msgid "|get_weight_str.png|" msgstr "" -#: ../../en/refs/unit.weight_i2c.ref:14 1a72cce8248043e3b2d0dfc62dfcb1de -msgid "get_weight_str.svg" +#: ../../en/refs/unit.weight_i2c.ref:14 17a6c36b32bd4a59bd3927362db46f44 +msgid "get_weight_str.png" msgstr "" -#: ../../en/units/weight_i2c.rst:100 5bb3d81219d840eb933de7cc1bd11b17 +#: ../../en/units/weight_i2c.rst:100 6defa385d8dc415ea2c77696a7ee2122 msgid "Reset the offset value(Tare)." msgstr "重置偏移值(Tare)." -#: ../../en/units/weight_i2c.rst:105 412e558203354308bdaf2196e464ee32 -msgid "|set_reset_offset.svg|" +#: ../../en/units/weight_i2c.rst:105 c3678622f7ac4768a7bd64a363493be5 +msgid "|set_reset_offset.png|" msgstr "" -#: ../../en/refs/unit.weight_i2c.ref:16 9ab8857656f14f26938e6d9a0fd1f33c -msgid "set_reset_offset.svg" +#: ../../en/refs/unit.weight_i2c.ref:16 43c066f7488c49c19258c734bc7de3d2 +msgid "set_reset_offset.png" msgstr "" -#: ../../en/units/weight_i2c.rst:110 29c908d8a2f549d6a11ff4f86831d825 +#: ../../en/units/weight_i2c.rst:110 4908e29e0eee489c9db7904f2f977297 msgid "Calibrates the Load sensor." msgstr "校准称重传感器。" -#: ../../en/units/weight_i2c.rst:112 49c694de55e24f539124286e817405ea +#: ../../en/units/weight_i2c.rst:112 51964757e0ff4878b74b5d80c6937abd msgid "``weight1_g``: Weight1 in grams." msgstr "" -#: ../../en/units/weight_i2c.rst:113 3cd525c766644c07915dc9b5cefdd7fc +#: ../../en/units/weight_i2c.rst:113 a9b6c420f1ac467ab079f818346e4479 msgid "``weight1_adc``: Weight1 in ADC value." msgstr "" -#: ../../en/units/weight_i2c.rst:114 8d7a07d12dd0477f806a4f36b721238e +#: ../../en/units/weight_i2c.rst:114 788b8aaad4cf4c3db4ccc79cfa5fadca msgid "``weight2_g``: Weight2 in grams." msgstr "" -#: ../../en/units/weight_i2c.rst:115 3467e4437b774669a632b74558125805 +#: ../../en/units/weight_i2c.rst:115 aee0fae8f02d40bdbc2c6424ab31657b msgid "``weight2_adc``: Weight2 in ADC value." msgstr "" -#: ../../en/units/weight_i2c.rst:117 4faf2b8c692d473dba70d39963f99579 +#: ../../en/units/weight_i2c.rst:117 f5411ee7663e468dabbf7dace05e0227 msgid "calibration steps:" msgstr "" -#: ../../en/units/weight_i2c.rst:119 32227e7e1915440c8e52d0fc9b109210 +#: ../../en/units/weight_i2c.rst:119 029b24843a7e491bbe8373a104b177d1 msgid "" "1.Reset offset(Tare). 2.Get the raw ADC value at no-load weight, this is " "the Raw ADC of zero weight in 0g. 3.Put some weight on it, then get adc, " "this is the load weight adc value and the gram weight you put on it." msgstr "1.重置抵消(皮重)" -#: ../../en/units/weight_i2c.rst:126 bf56c25f2d6843d3b9cc2292d71a7942 -msgid "|set_calibration.svg|" +#: ../../en/units/weight_i2c.rst:126 ebcf03ec13bd4c39b66a472cc648d9dc +msgid "|set_calibration.png|" msgstr "" -#: ../../en/refs/unit.weight_i2c.ref:18 66ea3f1cbe184f14b8936da83c37118a -msgid "set_calibration.svg" +#: ../../en/refs/unit.weight_i2c.ref:18 b5cc8f5f5cbb4b18b27af931be51d4b8 +msgid "set_calibration.png" msgstr "" -#: ../../en/units/weight_i2c.rst:130 3bb5fb639403439fbbede10f71b0179f +#: ../../en/units/weight_i2c.rst:130 d5971385f6664c40ba5d064260f79e34 msgid "Enable or disable the low pass filter." msgstr "启用或禁用低通滤波器。" -#: ../../en/units/weight_i2c.rst:135 214bac8054e441af984e7c1e3ca569e5 -msgid "|set_lowpass_filter.svg|" +#: ../../en/units/weight_i2c.rst:135 243fbda9939e4baa88d9f46308fd0eeb +msgid "|set_lowpass_filter.png|" msgstr "" -#: ../../en/refs/unit.weight_i2c.ref:20 ad592f0aa7be40698f04cc3de05ac453 -msgid "set_lowpass_filter.svg" +#: ../../en/refs/unit.weight_i2c.ref:20 3f8ab4d4022345e48f52b452a11ffbfd +msgid "set_lowpass_filter.png" msgstr "" -#: ../../en/units/weight_i2c.rst:140 75da8d0aa0944c859cd4125fbc8ea254 +#: ../../en/units/weight_i2c.rst:140 98518a5c36df43fc98798c28ef3e5215 msgid "Returns the status of the low pass filter (enable or disable)." msgstr "返回低通滤波器的状态(启用或禁用)。" -#: ../../en/units/weight_i2c.rst:145 3ad7384249b84d9cac41d69c03cb0093 -msgid "|get_lowpass_filter.svg|" +#: ../../en/units/weight_i2c.rst:145 389c65caacfb49cba001e90649a53d89 +msgid "|get_lowpass_filter.png|" msgstr "" -#: ../../en/refs/unit.weight_i2c.ref:22 3d5267fe8f304da7bef88f94c0fd8bbb -msgid "get_lowpass_filter.svg" +#: ../../en/refs/unit.weight_i2c.ref:22 0d2be18c85b84a18a4efceace1333fb3 +msgid "get_lowpass_filter.png" msgstr "" -#: ../../en/units/weight_i2c.rst:149 91c296bd3c9a4cea8249178fe8a47400 +#: ../../en/units/weight_i2c.rst:149 1534cdd4b3df4b98824ce7496c3d972c msgid "Sets the level of the average filter." msgstr "设置平均滤波器的级别。" -#: ../../en/units/weight_i2c.rst:151 3140629343c34cf581061397a1417438 +#: ../../en/units/weight_i2c.rst:151 eabe4ac9bdde4687bb60095eae99bad5 msgid "" "``level``: Level of the average filter (0 - 50). Larger value for " "smoother result but more latency" msgstr "``level``:平均过滤器的电平(0 - 50)。更大的值可以获得更平滑的结果,但延迟更长" -#: ../../en/units/weight_i2c.rst:155 a532350fa18343fcbb8aa53c05c45c99 -msgid "|set_average_filter_level.svg|" +#: ../../en/units/weight_i2c.rst:155 152afbd5df2d448b830cb8432198f03a +msgid "|set_average_filter_level.png|" msgstr "" -#: ../../en/refs/unit.weight_i2c.ref:24 119a9f7723b7447db93f82bff6ebd32e -msgid "set_average_filter_level.svg" +#: ../../en/refs/unit.weight_i2c.ref:24 9920dd8efd144931b8e5a17e02c81b8b +msgid "set_average_filter_level.png" msgstr "" -#: ../../en/units/weight_i2c.rst:159 10bb31294a634bb4920265fcd5cd5772 +#: ../../en/units/weight_i2c.rst:159 a11ffccb134a49a0a147abd5ba236d2c msgid "Returns the level of the average filter." msgstr "返回平均滤波器的级别。" -#: ../../en/units/weight_i2c.rst:163 b0f38702eb164cc3b1b3e73a1ec5191c -msgid "|get_average_filter_level.svg|" +#: ../../en/units/weight_i2c.rst:163 60ba06147c1841cf9e264270a85fdfab +msgid "|get_average_filter_level.png|" msgstr "" -#: ../../en/refs/unit.weight_i2c.ref:26 38835311ed2843eeb7110e3ea1f1a44e -msgid "get_average_filter_level.svg" +#: ../../en/refs/unit.weight_i2c.ref:26 c9af1b8d43ed4acc90ca77e41643020f +msgid "get_average_filter_level.png" msgstr "" -#: ../../en/units/weight_i2c.rst:167 f345a72dc7f546708b80dcd0c44efaec +#: ../../en/units/weight_i2c.rst:167 7e0ca0dda0cc4f808de25290ca32f787 msgid "Sets the alpha value for the EMA filter." msgstr "设置EMA滤波器的alpha值。" -#: ../../en/units/weight_i2c.rst:169 88a7c366a8f34aafbd0016ac7b06b529 +#: ../../en/units/weight_i2c.rst:169 92fbc91ecc1a4de582d1f1383ecbb5eb msgid "" "The EMA (Exponential Moving Average) filter is more sensitive to changes " "in data compared to the average filter." msgstr "与平均滤波器相比,EMA(指数移动平均)滤波器对数据的变化更敏感。" -#: ../../en/units/weight_i2c.rst:171 0fddbcb00c8a4a93affb4b27094063f6 +#: ../../en/units/weight_i2c.rst:171 8c6d6acfa92a44218b75c637d33f52be msgid "" "``alpha``: Alpha value for the EMA filter (0 - 99). Smaller value for " "smoother result but more latency" msgstr "" -#: ../../en/units/weight_i2c.rst:175 abdcf28629ce4eeea69bee5c602acfd4 -msgid "|set_ema_filter_alpha.svg|" +#: ../../en/units/weight_i2c.rst:175 d92f71e7a0ca488d8baccfa8cf810b96 +msgid "|set_ema_filter_alpha.png|" msgstr "" -#: ../../en/refs/unit.weight_i2c.ref:28 e28008de45ca4febb51152a6556ce2d2 -msgid "set_ema_filter_alpha.svg" +#: ../../en/refs/unit.weight_i2c.ref:28 358675a4c63f45cab2c0e62323bc2eb0 +msgid "set_ema_filter_alpha.png" msgstr "" -#: ../../en/units/weight_i2c.rst:179 b62c126b9e584d1b8200b5f45e8b9141 +#: ../../en/units/weight_i2c.rst:179 b642656f95eb43e5ba15b618e868f731 msgid "Returns the alpha value for the EMA filter." msgstr "" -#: ../../en/units/weight_i2c.rst:183 c5969a40f23f401ca0b0f8c57f8efbaf -msgid "|get_ema_filter_alpha.svg|" +#: ../../en/units/weight_i2c.rst:183 80f3d352143e4abba7e54f1fa42a2976 +msgid "|get_ema_filter_alpha.png|" msgstr "" -#: ../../en/refs/unit.weight_i2c.ref:30 991f615c914f4156bf76bfe554e714e5 -msgid "get_ema_filter_alpha.svg" +#: ../../en/refs/unit.weight_i2c.ref:30 793dfc01337041f4870927debf8913ae +msgid "get_ema_filter_alpha.png" msgstr "" -#: ../../en/units/weight_i2c.rst:187 2fc28d6d20d54e5fabfde1a3a0d73f63 +#: ../../en/units/weight_i2c.rst:187 fa69d5852490429e9a054939a93a2e2f msgid "" "The i2c address can be changed by the user and this address should be " "between 0x01 and 0x7F." msgstr "i2c地址可以由用户更改,该地址应该在0x01和0x7F之间。" -#: ../../en/units/weight_i2c.rst:189 203359ae00424a2287642590872e3fb8 +#: ../../en/units/weight_i2c.rst:189 c3732df798f64d41bd3f440bb0e79a8b msgid "``address``: range of address(0x01 - 0x7F)." msgstr "``address``:地址范围(0x01 - 0x7F)。" -#: ../../en/units/weight_i2c.rst:193 6d6e1018b7aa4b93a391e929456ad370 -msgid "|set_i2c_address.svg|" +#: ../../en/units/weight_i2c.rst:193 16a55504d5e9436cb4bfa7b0d45876e2 +msgid "|set_i2c_address.png|" msgstr "" -#: ../../en/refs/unit.weight_i2c.ref:32 cc1c3934112049b9b382dcab25c8bed6 -msgid "set_i2c_address.svg" +#: ../../en/refs/unit.weight_i2c.ref:32 15513a6cf7424012a64661a93bafaa7e +msgid "set_i2c_address.png" msgstr "" -#: ../../en/units/weight_i2c.rst:197 5cdc07d1416f4f75b9ddbc7a257d8339 +#: ../../en/units/weight_i2c.rst:197 b17ef42cfe11484a8fa8fd8904604ffb msgid "Get the firmware version details and I2c address of this device." msgstr "获取此设备的固件版本详情和I2c地址。" -#: ../../en/units/weight_i2c.rst:199 8890158c82f648ec840d7e86b9b95bbf +#: ../../en/units/weight_i2c.rst:199 51c9b4c5e03d42238279b70873604388 msgid "``info``: (0xFE, 0xFF)" msgstr "" -#: ../../en/units/weight_i2c.rst:203 93e2700ca1da4693a48d17cb92274089 -msgid "|get_device_spec.svg|" +#: ../../en/units/weight_i2c.rst:203 033fef1326cd46c081ea8f57c53533f3 +msgid "|get_device_spec.png|" msgstr "" -#: ../../en/refs/unit.weight_i2c.ref:34 eb7c21696dcc4d2ebd7ad9efa6df7144 -msgid "get_device_spec.svg" +#: ../../en/refs/unit.weight_i2c.ref:34 26cc1468dff740ba967a4275a5874e65 +msgid "get_device_spec.png" msgstr "" +#~ msgid "|example.svg|" +#~ msgstr "" + +#~ msgid "example.svg" +#~ msgstr "" + +#~ msgid "|init_i2c_address.svg|" +#~ msgstr "" + +#~ msgid "init_i2c_address.svg" +#~ msgstr "" + +#~ msgid "|get_adc_raw.svg|" +#~ msgstr "" + +#~ msgid "get_adc_raw.svg" +#~ msgstr "" + +#~ msgid "|get_weight_float.svg|" +#~ msgstr "" + +#~ msgid "get_weight_float.svg" +#~ msgstr "" + +#~ msgid "|get_weight_int.svg|" +#~ msgstr "" + +#~ msgid "get_weight_int.svg" +#~ msgstr "" + +#~ msgid "|get_weight_str.svg|" +#~ msgstr "" + +#~ msgid "get_weight_str.svg" +#~ msgstr "" + +#~ msgid "|set_reset_offset.svg|" +#~ msgstr "" + +#~ msgid "set_reset_offset.svg" +#~ msgstr "" + +#~ msgid "|set_calibration.svg|" +#~ msgstr "" + +#~ msgid "set_calibration.svg" +#~ msgstr "" + +#~ msgid "|set_lowpass_filter.svg|" +#~ msgstr "" + +#~ msgid "set_lowpass_filter.svg" +#~ msgstr "" + +#~ msgid "|get_lowpass_filter.svg|" +#~ msgstr "" + +#~ msgid "get_lowpass_filter.svg" +#~ msgstr "" + +#~ msgid "|set_average_filter_level.svg|" +#~ msgstr "" + +#~ msgid "set_average_filter_level.svg" +#~ msgstr "" + +#~ msgid "|get_average_filter_level.svg|" +#~ msgstr "" + +#~ msgid "get_average_filter_level.svg" +#~ msgstr "" + +#~ msgid "|set_ema_filter_alpha.svg|" +#~ msgstr "" + +#~ msgid "set_ema_filter_alpha.svg" +#~ msgstr "" + +#~ msgid "|get_ema_filter_alpha.svg|" +#~ msgstr "" + +#~ msgid "get_ema_filter_alpha.svg" +#~ msgstr "" + +#~ msgid "|set_i2c_address.svg|" +#~ msgstr "" + +#~ msgid "set_i2c_address.svg" +#~ msgstr "" + +#~ msgid "|get_device_spec.svg|" +#~ msgstr "" + +#~ msgid "get_device_spec.svg" +#~ msgstr "" + diff --git a/examples/unit/tof4m/tof4m_core_example.m5f2 b/examples/unit/tof4m/tof4m_core_example.m5f2 new file mode 100644 index 00000000..2543081b --- /dev/null +++ b/examples/unit/tof4m/tof4m_core_example.m5f2 @@ -0,0 +1 @@ +{"version":"V2.0","versionNumber":"V2.2.5","type":"basic","components":[{"name":"screen","type":"screen","layer":0,"screenId":"builtin","screenName":"","id":"__basic_screen","createTime":1742259942536,"x":0,"y":0,"width":320,"height":240,"backgroundColor":"#222222","size":0,"isSelected":true},{"name":"title0","type":"title","layer":2,"screenId":"builtin","screenName":"","id":"r8wHbNu48tMRpglW","createTime":1742260115248,"x":0,"y":0,"color":"#ffffff","backgroundColor":"#0000FF","text":"ToF4MUnit CoreS3 Test","textOffset":3,"font":"Widgets.FONTS.DejaVu18","isSelected":false},{"name":"label1","type":"label","layer":3,"screenId":"builtin","screenName":"","id":"yzn%Yy1`GtN^=um+","createTime":1742260139267,"x":1,"y":121,"color":"#ffffff","backgroundColor":"#222222","text":"label1","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false}],"resources":[{"hardware":["hardware_button","hardware_pin_button","imu","rgb","i2c","speaker","timer"]},{"unit":["unit_tof4m"]}],"units":[{"type":"unit_tof4m","name":"tof4m_0","portList":["A","PAHUB","Custom"],"portType":"A","userPort":[22,21],"id":"v_ZbmVsdRq=ACi@b","createTime":1744189084168,"bus":"i2c1","pahubPortList":[0,1,2,3,4,5],"pahubPort":0,"initBlockId":"=pNNe1fLC~$rwObkjvY`"}],"hats":[],"bases":[],"i2cs":[{"id":"i2c1","portType":"A","userPort":[22,21],"freq":"100000","blockId":"[P/_;eyq6G4#!(S-EkJq"}],"blockly":"distancetrue11000002221tof4m_00x29tof4m_02tof4m_0500tof4m_0truetof4m_0label1LabelDistance:hello M5distancemm","screen":[{"simulationName":"Built-in","type":"builtin","width":320,"height":240,"scale":0.78,"screenName":"","blockId":"","screenColorType":0,"id":"builtin","createTime":1742259942534}],"logicWhenNum":0,"customList":[]} \ No newline at end of file diff --git a/examples/unit/tof4m/tof4m_core_example.py b/examples/unit/tof4m/tof4m_core_example.py new file mode 100644 index 00000000..799d5fd9 --- /dev/null +++ b/examples/unit/tof4m/tof4m_core_example.py @@ -0,0 +1,55 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import os, sys, io +import M5 +from M5 import * +from hardware import I2C +from hardware import Pin +from unit import TOF4MUnit + + +title0 = None +label1 = None +i2c1 = None +tof4m_0 = None + + +distance = None + + +def setup(): + global title0, label1, i2c1, tof4m_0, distance + + M5.begin() + Widgets.fillScreen(0x222222) + title0 = Widgets.Title("ToF4MUnit CoreS3 Test", 3, 0xFFFFFF, 0x0000FF, Widgets.FONTS.DejaVu18) + label1 = Widgets.Label("label1", 1, 121, 1.0, 0xFFFFFF, 0x222222, Widgets.FONTS.DejaVu18) + + i2c1 = I2C(1, scl=Pin(22), sda=Pin(21), freq=100000) + tof4m_0 = TOF4MUnit(i2c1, 0x29) + tof4m_0.set_distance_mode(2) + tof4m_0.set_measurement_timing_budget(500) + tof4m_0.set_continuous_start_measurement() + + +def loop(): + global title0, label1, i2c1, tof4m_0, distance + M5.update() + if tof4m_0.get_data_ready: + label1.setText(str((str("Distance:") + str((str(distance) + str("mm")))))) + + +if __name__ == "__main__": + try: + setup() + while True: + loop() + except (Exception, KeyboardInterrupt) as e: + try: + from utility import print_error_msg + + print_error_msg(e) + except ImportError: + print("please update to latest firmware") diff --git a/m5stack/libs/driver/vl53l1x.py b/m5stack/libs/driver/vl53l1x.py index cb82f2fb..513577b6 100644 --- a/m5stack/libs/driver/vl53l1x.py +++ b/m5stack/libs/driver/vl53l1x.py @@ -52,7 +52,25 @@ class VL53L1X: - """Driver for the VL53L1X distance sensor.""" + """Create a VL53L1X object. + + :param I2C i2c: The I2C bus the ToF4M Unit is connected to. + :param int address: The I2C address of the device. Default is 0x29. + + UiFlow2 Code Block: + + |init.png| + + MicroPython Code Block: + + .. code-block:: python + + from hardware import I2C + from unit import TOFUnit + + i2c0 = I2C(0, scl=Pin(1), sda=Pin(2), freq=100000) + tof_0 = TOFUnit(i2c0) + """ def __init__(self, i2c: I2C, address: int = 41): self._i2c = i2c @@ -172,47 +190,120 @@ def _sensor_init(self): @property def get_model_info(self): - """A 3 tuple of Model ID, Module Type, and Mask Revision.""" info = self._read_register(_VL53L1X_IDENTIFICATION__MODEL_ID, 3) return (info[0], info[1], info[2]) # Model ID, Module Type, Mask Rev @property def get_distance(self): - """The distance in units of millimeters.""" + """The distance in units of millimeters. + + :returns: Distance in millimeters or None if measurement is invalid. + :rtype: int or None + + UiFlow2 Code Block: + + |get_distance.png| + + MicroPython Code Block: + + .. code-block:: python + + distance = tof_0.get_distance + """ if self._read_register(_VL53L1X_RESULT__RANGE_STATUS)[0] != 0x09: return None dist = self._read_register(_VL53L1X_RESULT__FINAL_CROSSTALK_CORRECTED_RANGE_MM_SD0, 2) return struct.unpack(">H", dist)[0] def set_continuous_start_measurement(self): - """Starts ranging operation.""" + """Starts continuous measure operation. + + UiFlow2 Code Block: + + |set_continuous_start_measurement.png| + + MicroPython Code Block: + + .. code-block:: python + + tof_0.set_continuous_start_measurement() + """ self._write_register(_SYSTEM__MODE_START, b"\x40") def set_continuous_stop_measurement(self): - """Stops ranging operation.""" + """Stops measure operation. + + UiFlow2 Code Block: + + |set_continuous_stop_measurement.png| + + MicroPython Code Block: + + .. code-block:: python + + tof_0.set_continuous_stop_measurement() + """ self._write_register(_SYSTEM__MODE_START, b"\x00") def clear_interrupt(self): - """Clears new data interrupt.""" self._write_register(_SYSTEM__INTERRUPT_CLEAR, b"\x01") @property def get_data_ready(self): - """Returns true if new data is ready, otherwise false.""" + """Returns true if new data is ready, otherwise false. + + :returns: True if new data is ready. + :rtype: bool + + UiFlow2 Code Block: + + |get_data_ready.png| + + MicroPython Code Block: + + .. code-block:: python + + if tof_0.get_data_ready: + distance = tof_0.get_distance + """ if self._read_register(_GPIO__TIO_HV_STATUS)[0] & 0x01 == self._interrupt_polarity: return True return False @property def get_measurement_timing_budget(self): - """Ranging duration in milliseconds. Increasing the timing budget - increases the maximum distance the device can range and improves - the repeatability error. However, average power consumption augments - accordingly. ms = 15 (short mode only), 20, 33, 50, 100, 200, 500. - Defaults to 50.""" + """Get measurement duration in milliseconds. + + :returns: The timing budget in milliseconds. + :rtype: int + + UiFlow2 Code Block: + + |get_measurement_timing_budget.png| + + MicroPython Code Block: + + .. code-block:: python + + budget = tof_0.get_measurement_timing_budget + """ return self._timing_budget def set_measurement_timing_budget(self, val): + """Set the measurement timing budget in milliseconds. + + :param int val: Timing budget in milliseconds. + + UiFlow2 Code Block: + + |set_measurement_timing_budget.png| + + MicroPython Code Block: + + .. code-block:: python + + tof_0.set_measurement_timing_budget(100) + """ reg_vals = None mode = self.get_distance_mode if mode == 1: @@ -235,7 +326,21 @@ def _interrupt_polarity(self): @property def get_distance_mode(self): - """The distance mode. 1=short (up to 136cm) , 2=long (up to 360cm).""" + """Get the distance mode. + + :returns: distance mode(1=short, 2=long). + :rtype: int + + UiFlow2 Code Block: + + |get_distance_mode.png| + + MicroPython Code Block: + + .. code-block:: python + + mode = tof_0.get_distance_mode + """ mode = self._read_register(_PHASECAL_CONFIG__TIMEOUT_MACROP)[0] if mode == 0x14: return 1 # short distance @@ -244,6 +349,20 @@ def get_distance_mode(self): return None # unknown def set_distance_mode(self, mode): + """Set the distance mode. + + :param int mode: 1=short, 2=long. + + UiFlow2 Code Block: + + |set_distance_mode.png| + + MicroPython Code Block: + + .. code-block:: python + + tof_0.set_distance_mode(2) # Long distance mode + """ if mode == 1: # short distance self._write_register(_PHASECAL_CONFIG__TIMEOUT_MACROP, b"\x14") @@ -273,9 +392,18 @@ def _read_register(self, address, length=1): return self._i2c.readfrom_mem(self._addr, address, length, addrsize=16) def set_i2c_address(self, new_address): - """ - Set a new I2C address to the instantaited object. This is only called when using - multiple VL53L0X sensors on the same I2C bus (SDA & SCL pins). See also the - `example `_ for proper usage. + """Set a new I2C address to the instantiated object. + + :param int new_address: The new I2C address. + + UiFlow2 Code Block: + + |set_i2c_address.png| + + MicroPython Code Block: + + .. code-block:: python + + tof_0.set_i2c_address(42) """ self._write_register(_VL53L1X_I2C_SLAVE_DEVICE_ADDRESS, struct.pack(">B", new_address)) From 25dde7e1039119a30c4f138662b4139887dfba2a Mon Sep 17 00:00:00 2001 From: Tinyu Date: Thu, 17 Apr 2025 10:56:56 +0800 Subject: [PATCH 048/322] hardware: Fix hardware.can initialization issue. Signed-off-by: Tinyu --- docs/en/hardware/can.rst | 4 +-- m5stack/libs/hardware/__init__.py | 1 + m5stack/libs/hardware/can.py | 48 +++++++++++++++++++++++++++++++ m5stack/libs/hardware/manifest.py | 1 + 4 files changed, 52 insertions(+), 2 deletions(-) create mode 100644 m5stack/libs/hardware/can.py diff --git a/docs/en/hardware/can.rst b/docs/en/hardware/can.rst index 2b108b47..2959f044 100644 --- a/docs/en/hardware/can.rst +++ b/docs/en/hardware/can.rst @@ -12,8 +12,8 @@ voltage levels on the bus. Example usage for classic CAN controller in Loopback (transceiver-less) mode:: - from m5can import CAN - can = CAN(1, CAN.LOOPBACK, 1, 2) + from hardware import CAN + can = CAN(0, CAN.NORMAL, 0, 0, 25000) can.send('message!', 123) # send a message with id 123 can.recv(0) # receive message diff --git a/m5stack/libs/hardware/__init__.py b/m5stack/libs/hardware/__init__.py index c8b47e84..119d2203 100644 --- a/m5stack/libs/hardware/__init__.py +++ b/m5stack/libs/hardware/__init__.py @@ -5,6 +5,7 @@ _attrs = { "Keyboard": "keyboard", "Button": "button", + "CAN": "can", "IR": "ir", "MatrixKeyboard": "matrix_keyboard", "DigitalInput": "plcio", diff --git a/m5stack/libs/hardware/can.py b/m5stack/libs/hardware/can.py new file mode 100644 index 00000000..795dac8a --- /dev/null +++ b/m5stack/libs/hardware/can.py @@ -0,0 +1,48 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import m5can +import sys + +if sys.platform != "esp32": + from typing import Literal + + +class CAN(m5can.CAN): + _timing_table = { + # prescaler, sjw, bs1, bs2, triple_sampling + 25000: (128, 3, 16, 8, False), + 50000: (80, 3, 15, 4, False), + 100000: (40, 3, 15, 4, False), + 125000: (32, 3, 15, 4, False), + 250000: (16, 3, 15, 4, False), + 500000: (8, 3, 15, 4, False), + 800000: (4, 3, 16, 8, False), + 1000000: (4, 3, 15, 4, False), + } + + def __init__( + self, + id: Literal[0, 1], + mode: int = m5can.CAN.NORMAL, + tx: int = 0, + rx: int = 0, + *args, + ): + if len(args) == 1: + (prescaler, sjw, bs1, bs2, triple_sampling) = self._timing_table.get(args[0]) + elif len(args) == 5: + (prescaler, sjw, bs1, bs2, triple_sampling) = args + + super().__init__( + 0, + mode, + tx, + rx, + prescaler, + sjw, + bs1, + bs2, + triple_sampling, + ) diff --git a/m5stack/libs/hardware/manifest.py b/m5stack/libs/hardware/manifest.py index 850f91f5..4ecc3878 100644 --- a/m5stack/libs/hardware/manifest.py +++ b/m5stack/libs/hardware/manifest.py @@ -9,6 +9,7 @@ "keyboard/__init__.py", "keyboard/asciimap.py", "button.py", + "can.py", "ir.py", "matrix_keyboard.py", "plcio.py", From 96fa8a46a1323760f9bf55fc33af479280229917 Mon Sep 17 00:00:00 2001 From: lbuque Date: Wed, 11 Dec 2024 12:23:24 +0800 Subject: [PATCH 049/322] libs/driver/simcom: Add SIM7600 module. Signed-off-by: lbuque --- m5stack/libs/driver/simcom/common.py | 176 +++-- m5stack/libs/driver/simcom/sim7020.py | 21 +- m5stack/libs/driver/simcom/sim7028.py | 21 +- m5stack/libs/driver/simcom/sim7080.py | 21 +- m5stack/libs/driver/simcom/sim7600.py | 687 ++++++++++++++++++ m5stack/libs/driver/simcom/sim800.py | 386 ++++++++++ .../libs/driver/simcom/toolkit/requests2.py | 212 ++++++ m5stack/libs/driver/simcom/utils.py | 43 ++ m5stack/libs/driver/simcom/v25ter.py | 51 ++ 9 files changed, 1526 insertions(+), 92 deletions(-) create mode 100644 m5stack/libs/driver/simcom/sim7600.py create mode 100644 m5stack/libs/driver/simcom/sim800.py create mode 100644 m5stack/libs/driver/simcom/toolkit/requests2.py create mode 100644 m5stack/libs/driver/simcom/utils.py create mode 100644 m5stack/libs/driver/simcom/v25ter.py diff --git a/m5stack/libs/driver/simcom/common.py b/m5stack/libs/driver/simcom/common.py index 02fdcbbb..6c2e4960 100644 --- a/m5stack/libs/driver/simcom/common.py +++ b/m5stack/libs/driver/simcom/common.py @@ -3,12 +3,13 @@ # # SPDX-License-Identifier: MIT - +import sys import time -import json +from . import utils from collections import namedtuple AT_CMD = namedtuple("AT_CMD", ["command", "response", "timeout"]) +ATCommand = namedtuple("ATCommand", ["cmd", "rsp1", "rsp2", "timeout"]) class Response(object): @@ -18,113 +19,116 @@ def __init__(self, status_code, content): class Modem(object): + ERR_NONE = 0 + ERR_GENERIC = 1 + ERR_TIMEOUT = 2 + def __init__( self, uart=None, - MODEM_PWKEY_PIN=None, - MODEM_RST_PIN=None, - MODEM_POWER_ON_PIN=None, - MODEM_TX_PIN=None, - MODEM_RX_PIN=None, + pwrkey_pin=None, + reset_pin=None, + power_pin=None, + tx_pin=None, + rx_pin=None, + verbose=False, ): # Uart self.uart = uart - self.modem_debug = False + self._verbose = verbose self.downlink_callback = {} self.downlink_keyword = [] self.callback_keyword = [] if not self.uart: - from machine import UART, Pin + import machine # Pin initialization - MODEM_PWKEY_PIN_OBJ = Pin(MODEM_PWKEY_PIN, Pin.OUT) if MODEM_PWKEY_PIN else None # noqa: N806 - MODEM_RST_PIN_OBJ = Pin(MODEM_RST_PIN, Pin.OUT) if MODEM_RST_PIN else None # noqa: N806 - MODEM_POWER_ON_PIN_OBJ = ( # noqa: N806 - Pin(MODEM_POWER_ON_PIN, Pin.OUT) if MODEM_POWER_ON_PIN else None - ) + pwrkey_obj = machine.Pin(pwrkey_pin, machine.Pin.OUT) if pwrkey_pin else None + reset_obj = machine.Pin(reset_pin, machine.Pin.OUT) if reset_pin else None + power_obj = machine.Pin(power_pin, machine.Pin.OUT) if power_pin else None # Status setup - if MODEM_PWKEY_PIN_OBJ: - MODEM_PWKEY_PIN_OBJ.value(0) - if MODEM_RST_PIN_OBJ: - MODEM_RST_PIN_OBJ.value(1) - if MODEM_POWER_ON_PIN_OBJ: - MODEM_POWER_ON_PIN_OBJ.value(1) + if pwrkey_obj: + pwrkey_obj(0) + if reset_obj: + reset_obj(1) + if power_obj: + power_obj(1) # Setup UART - self.uart = UART(1, 115200, timeout=1000, rx=MODEM_TX_PIN, tx=MODEM_RX_PIN, rxbuf=1024) + self.uart = machine.UART(1, 115200, timeout=1000, rx=tx_pin, tx=rx_pin, rxbuf=1024) def check_modem_is_ready(self): # Check if modem is ready for AT command - AT = AT_CMD("AT", "OK", 10) # noqa: N806 - output, error = self.execute_at_command(AT, True) + at = AT_CMD("AT", "OK", 10) + output, error = self.execute_at_command(at, True) return not error def set_command_echo_mode(self, state=1): # Set echo mode off or on - ATE = AT_CMD("ATE{}".format(state), "OK", 3) # noqa: N806 - output, error = self.execute_at_command(ATE) + ate = AT_CMD("ATE{}".format(state), "OK", 3) + output, error = self.execute_at_command(ate) return not error def check_sim_is_connected(self): # Check the SIM card is connected or not? - CPIN = AT_CMD("AT+CPIN?", "+CPIN: READY", 10) # noqa: N806 - output, error = self.execute_at_command(CPIN) + cpin = AT_CMD("AT+CPIN?", "+CPIN: READY", 10) + output, error = self.execute_at_command(cpin) return not error def get_network_registration_status(self): # Get the registration status with the network - CREG = AT_CMD("AT+CREG?", "+CREG:", 3) # noqa: N806 - output, error = self.execute_at_command(CREG) + creg = AT_CMD("AT+CREG?", "+CREG:", 3) + output, error = self.execute_at_command(creg) return False if error else int(output[-1]) def get_signal_strength(self): # Get the signal strength - CSQ = AT_CMD("AT+CSQ", "+CSQ:", 3) # noqa: N806 - output, error = self.execute_at_command(CSQ) + csq = AT_CMD("AT+CSQ", "+CSQ:", 3) + output, error = self.execute_at_command(csq) return False if error else int(output.split(",")[0][-1]) def get_gprs_registration_status(self): # Get the registration status with the gprs network - CGREG = AT_CMD("AT+CGREG?", "+CGREG:", 3) # noqa: N806 - output, error = self.execute_at_command(CGREG) + cgreg = AT_CMD("AT+CGREG?", "+CGREG:", 3) + output, error = self.execute_at_command(cgreg) return False if error else int(output[-1]) def get_model_identification(self): # Query the model identification information - CGMM = AT_CMD("AT+CGMM", "SIM", 3) # noqa: N806 - output, error = self.execute_at_command(CGMM) + cgmm = AT_CMD("AT+CGMM", "SIM", 3) + output, error = self.execute_at_command(cgmm) return False if error else output def get_gprs_network_status(self): # Get attach or detach from the GPRS network - CGATT = AT_CMD("AT+CGATT?", "+CGATT:", 3) # noqa: N806 - output, error = self.execute_at_command(CGATT) + cgatt = AT_CMD("AT+CGATT?", "+CGATT:", 3) + output, error = self.execute_at_command(cgatt) return False if error else int(output[-1]) def set_gprs_network_state(self, enable=1): # Set attach or detach from the GPRS network - CGATT = AT_CMD("AT+CGATT={0}".format(enable), "OK", 75) # noqa: N806 - output, error = self.execute_at_command(CGATT) + cgatt = AT_CMD("AT+CGATT={0}".format(enable), "OK", 75) + output, error = self.execute_at_command(cgatt) return not error def set_pdp_context(self, apn="CMNET"): # Set Define PDP Context - CGDCONT = AT_CMD('AT+CGDCONT=1,"IP","{}"'.format(apn), "OK", 12) # noqa: N806 - output, error = self.execute_at_command(CGDCONT) + cgdcont = AT_CMD('AT+CGDCONT=1,"IP","{}"'.format(apn), "OK", 12) + output, error = self.execute_at_command(cgdcont) return not error def get_show_pdp_address(self, cid): # Get Show PDP address. - CGPADDR = AT_CMD("AT+CGPADDR={}".format(cid), "+CGPADDR:", 12) # noqa: N806 - output, error = self.execute_at_command(CGPADDR) + cgpaddr = AT_CMD("AT+CGPADDR={}".format(cid), "+CGPADDR:", 12) + output, error = self.execute_at_command(cgpaddr) return False if error else (output.split(",")[1]).replace('"', "") def get_selected_operator(self): # Get selected operator. - COPS = AT_CMD("AT+COPS?", "+COPS", 12) # noqa: N806 - output, error = self.execute_at_command(COPS) + cops = AT_CMD("AT+COPS?", "+COPS", 12) + output, error = self.execute_at_command(cops) if error: return False return output.split('"')[1] if output.find('"') != -1 else "" @@ -142,14 +146,13 @@ def get_keyword_index_in_list(self, list_item, keyword): # ---------------------- def execute_at_command(self, command: AT_CMD, repeat=False, clean_output=True): # Clear the uart buffer - DUMMY = AT_CMD("", "", 0) # noqa: N806 - self.response_at_command(DUMMY) + dummy = AT_CMD("", "", 0) + self.response_at_command(dummy) # Execute the AT command - command_string_for_at = "{}\r\n".format(command.command) - if self.modem_debug: - print('write AT command: "{}"'.format(command.command)) - self.uart.write(command_string_for_at) + cmdstr = "{}\r\n".format(command.command) + self._verbose and print("write AT command: {}".format(repr(cmdstr))) + self.uart.write(cmdstr) return self.response_at_command(command, repeat, clean_output) def response_at_command(self, command: AT_CMD, repeat=False, clean_output=True): @@ -183,8 +186,7 @@ def response_at_command(self, command: AT_CMD, repeat=False, clean_output=True): error = True break else: - if self.modem_debug: - print('response AT command: "{}"'.format(line)) + self._verbose and print('response AT command: "{}"'.format(line)) # Convert line to string try: line_str = line.decode("utf-8") @@ -245,3 +247,71 @@ def response_at_command(self, command: AT_CMD, repeat=False, clean_output=True): # Return return (output, error) + + def execute_at_command2( + self, cmd: ATCommand, repeat=False, clean_output=True, line_end="\r\n" + ): + # clear the uart buffer + self.uart.write(b"\r\n") + + # execute the AT command + cmdstr = "{}\r\n".format(cmd.cmd) + self._verbose and print("TE -> TA:", repr(cmdstr)) + self.uart.write(cmdstr.encode("utf-8")) + + # wait for response + return self.response_at_command2(cmd, repeat, clean_output, line_end) + + # @utils.measure_time + def response_at_command2( + self, command: ATCommand, repeat=False, clean_output=True, line_end="\r\n" + ): + # Support vars + find_keyword = False + output = bytearray() + error = self.ERR_NONE + rsp1 = command.rsp1.encode("utf-8") + rsp2 = command.rsp2.encode("utf-8") + line_end = line_end.encode("utf-8") + + ticks = time.ticks_ms() + while time.ticks_diff(time.ticks_ms(), ticks) < command.timeout: + if self.uart.any() == 0: + time.sleep_ms(10) + continue + + line = self.uart.read(self.uart.any()) + self._verbose and print("TE <- TA:", repr(line)) + output.extend(line) + + # Do we have an error? + if output.rfind(rsp2) != -1: + if output.endswith(line_end): + print("Get AT command error response:", repr(output)) + error = self.ERR_GENERIC + find_keyword = True + + # If we had a pre-end, do we have the expected end? + if output.rfind(rsp1) != -1: + if output.endswith(line_end): + find_keyword = True + + if find_keyword: + break + + if time.ticks_diff(time.ticks_ms(), ticks) > command.timeout: + print("Timeout for command:", repr(command.cmd)) + error = self.ERR_TIMEOUT + + # Also, clean output if needed + # if clean_output: + # output = output.replace("OK", "") + # output = output.replace("\r\n", "") + # output = output.replace("\r", "") + # if output.startswith("\n"): + # output = output[1:] + # if output.endswith("\n"): + # output = output[:-1] + + # Return + return (output.decode("utf-8"), error) diff --git a/m5stack/libs/driver/simcom/sim7020.py b/m5stack/libs/driver/simcom/sim7020.py index ac32f64f..e7ab158d 100644 --- a/m5stack/libs/driver/simcom/sim7020.py +++ b/m5stack/libs/driver/simcom/sim7020.py @@ -2,26 +2,21 @@ # # SPDX-License-Identifier: MIT -from driver.simcom.common import Modem -from collections import namedtuple - - -AT_CMD = namedtuple("AT_CMD", ["command", "response", "timeout"]) +from .common import Modem +from .common import AT_CMD class SIM7020(Modem): def __init__( self, uart=None, - MODEM_PWKEY_PIN=None, - MODEM_RST_PIN=None, - MODEM_POWER_ON_PIN=None, - MODEM_TX_PIN=None, - MODEM_RX_PIN=None, + pwrkey_pin=None, + reset_pin=None, + power_pin=None, + tx_pin=None, + rx_pin=None, ) -> None: - super().__init__( - uart, MODEM_PWKEY_PIN, MODEM_RST_PIN, MODEM_POWER_ON_PIN, MODEM_TX_PIN, MODEM_RX_PIN - ) + super().__init__(uart, pwrkey_pin, reset_pin, power_pin, tx_pin, rx_pin) def get_imei_number(self): # Request TA Serial Number Identification(IMEI) diff --git a/m5stack/libs/driver/simcom/sim7028.py b/m5stack/libs/driver/simcom/sim7028.py index 714d0806..18be5306 100644 --- a/m5stack/libs/driver/simcom/sim7028.py +++ b/m5stack/libs/driver/simcom/sim7028.py @@ -3,27 +3,22 @@ # SPDX-License-Identifier: MIT -from driver.simcom.common import Modem -from collections import namedtuple +from .common import Modem +from .common import AT_CMD import re -AT_CMD = namedtuple("AT_CMD", ["command", "response", "timeout"]) - - class SIM7028(Modem): def __init__( self, uart=None, - modem_pwkey_pin=None, - modem_rst_pin=None, - modem_power_on_pin=None, - modem_tx_pin=None, - modem_rx_pin=None, + pwrkey_pin=None, + reset_pin=None, + power_pin=None, + tx_pin=None, + rx_pin=None, ) -> None: - super().__init__( - uart, modem_pwkey_pin, modem_rst_pin, modem_power_on_pin, modem_tx_pin, modem_rx_pin - ) + super().__init__(uart, pwrkey_pin, reset_pin, power_pin, tx_pin, rx_pin) def get_imei_number(self) -> str | bool: # Request TA Serial Number Identification(IMEI) diff --git a/m5stack/libs/driver/simcom/sim7080.py b/m5stack/libs/driver/simcom/sim7080.py index 08e304bb..5af019cc 100644 --- a/m5stack/libs/driver/simcom/sim7080.py +++ b/m5stack/libs/driver/simcom/sim7080.py @@ -2,26 +2,21 @@ # # SPDX-License-Identifier: MIT -from driver.simcom.common import Modem -from collections import namedtuple - - -AT_CMD = namedtuple("AT_CMD", ["command", "response", "timeout"]) +from .common import Modem +from .common import AT_CMD class SIM7080(Modem): def __init__( self, uart=None, - MODEM_PWKEY_PIN=None, - MODEM_RST_PIN=None, - MODEM_POWER_ON_PIN=None, - MODEM_TX_PIN=None, - MODEM_RX_PIN=None, + pwrkey_pin=None, + reset_pin=None, + power_pin=None, + tx_pin=None, + rx_pin=None, ) -> None: - super().__init__( - uart, MODEM_PWKEY_PIN, MODEM_RST_PIN, MODEM_POWER_ON_PIN, MODEM_TX_PIN, MODEM_RX_PIN - ) + super().__init__(uart, pwrkey_pin, reset_pin, power_pin, tx_pin, rx_pin) def get_mode_selection(self): # get Preferred Selection between CAT-M and NB-IoT diff --git a/m5stack/libs/driver/simcom/sim7600.py b/m5stack/libs/driver/simcom/sim7600.py new file mode 100644 index 00000000..8eb5c8ca --- /dev/null +++ b/m5stack/libs/driver/simcom/sim7600.py @@ -0,0 +1,687 @@ +# SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +from .common import Modem +from .common import ATCommand +from . import utils +from .toolkit import requests2 +import re +import socket +import micropython +import time + + +class SIMComError(Exception): + D_GENERIC = 0 + D_TCPIP_ERR_INFO = 1 # 11.5 + D_TCPIP_ERR = 2 # 11.6 + D_DNS_ERROR_CODE = 3 + + _err_desc = { + D_GENERIC: { + 2: "AT command timeout({})", + }, + D_DNS_ERROR_CODE: { + 10: "DNS GENERAL ERROR({})", + }, + D_TCPIP_ERR_INFO: { + 0: "Connection time out({})", + 1: "Bind port failed({})", + 2: "Port overflow({})", + 3: "Create socket failed({})", + 4: "Network is already opened({})", + 5: "Network is already closed({})", + 6: "No clients connected({})", + 7: "No active client({})", + 8: "Network not opened({})", + 9: "Client index overflow({})", + 10: "Connection is already created({})", + 11: "Connection is not created({})", + 12: "Invalid parameter({})", + 13: "Operation not supported({})", + 14: "DNS query failed({})", + 15: "TCP busy({})", + 16: "Netclose failed for socket opened({})", + 17: "Sending time out({})", + 18: "Sending failure for network error({})", + 19: "Open failure for network error({})", + 20: "Server is already listening({})", + 21: "No data({})", + 22: "Port overflow({})", + }, + D_TCPIP_ERR: { + 0: "Operation succeeded({})", + 1: "Network failure({})", + 2: "Network not opened({})", + 3: "Wrong parameter({})", + 4: "Operation not supported({})", + 5: "Failed to create socket({})", + 6: "Failed to bind socket({})", + 7: "TCP server is already listening({})", + 8: "Busy({})", + 9: "Sockets opened({})", + 10: "Timeout({})", + 11: "DNS parse failed for AT+CIPOPEN({})", + 12: "Unknown error({})", + }, + } + + def __init__(self, *args): + if len(args) == 1: + super().__init__(args[0]) + return + elif len(args) == 3: + domain, errno, cmd = args + msg = self._err_desc[domain].get(errno, "Unknown error({})") + super().__init__(msg.format(repr(cmd))) + + +class _socket: + AF_INET = socket.AF_INET + AF_INET6 = socket.AF_INET6 + + SOCK_STREAM = socket.SOCK_STREAM + SOCK_DGRAM = socket.SOCK_DGRAM + SOCK_RAW = socket.SOCK_RAW + + IPPROTO_IP = socket.IPPROTO_IP + IPPROTO_TCP = socket.IPPROTO_TCP + IPPROTO_UDP = socket.IPPROTO_UDP + + _proto_type = { + IPPROTO_TCP: "TCP", + IPPROTO_UDP: "UDP", + } + + _STATE_OPEN = 0 + _STATE_CLOSE = 1 + + def __init__(self, modem, af=AF_INET, type=SOCK_STREAM, proto=IPPROTO_TCP): + self._modem = modem + self._domain = af + self._type = type + self._proto = proto + self._fd = self._modem.apply_fd() + self._address = None + if self._fd == -1: + raise SIMComError("No available socket") + + self._state = self._STATE_CLOSE + self._ringio = micropython.RingIO(1500) + self._timeout = 5000 + + def __del__(self): + self.close() + + def close(self) -> None: + if self._fd == -1: + return + close = ATCommand( + "AT+CIPCLOSE={}".format(self._fd), "OK\r\n", "ERROR\r\n", self._modem._default_timeout + ) + self._modem.execute_at_command2(close) + self._modem.release_fd(self._fd) + self._fd = -1 + self._state = self._STATE_CLOSE + if hasattr(self, "_local_port"): + self._modem.release_port(self._local_port) + + def bind(self, address): + pass + + def listen(self, backlog): + pass + + def accept(self): + pass + + def connect(self, address): + if self._fd == -1: + raise SIMComError("socket closed") + + if self._state == self._STATE_OPEN: + return + + self._address = address + cipstart = ATCommand( + "AT+CIPOPEN={},{},{},{}".format( + self._fd, + "".join(['"', self._proto_type.get(self._proto, ""), '"']), + "".join(['"', address[0], '"']), + address[1], + ), + "+CIPOPEN", + "ERROR\r\n", + 120000, # Maximum Response Time + ) + output, error = self._modem.execute_at_command2(cipstart) + if error == self._modem.ERR_NONE: + self._state = self._STATE_OPEN + return True + elif error == self._modem.ERR_TIMEOUT: + raise SIMComError(SIMComError.D_GENERIC, error, cipstart.cmd) + elif error == self._modem.ERR_GENERIC: + err = utils.extract_int(output, "\+CIPOPEN: {},".format(self._fd), "\r\n") + raise SIMComError(SIMComError.D_TCPIP_ERR, err, cipstart.cmd) + + def send(self, data: bytes | str | bytearray) -> int: + # data type check + if isinstance(data, str): + data = data.encode("utf-8") + if isinstance(data, bytearray): + data = bytes(data) + + # send cmd + to_send = len(data) + cipsend = ATCommand( + "AT+CIPSEND={},{}".format(self._fd, to_send), + ">", + "ERROR\r\n", + self._modem._default_timeout, + ) + output, error = self._modem.execute_at_command2(cipsend, line_end="") + if error == self._modem.ERR_GENERIC: + err = utils.extract_int(output, "\+CIPSEND: ", "\r\n") + raise SIMComError(SIMComError.D_TCPIP_ERR, err, cipsend.cmd) + elif error == self._modem.ERR_TIMEOUT: + raise SIMComError(SIMComError.D_GENERIC, error, cipsend.cmd) + + sented = 0 + while sented < to_send: + self._modem.uart.write(data[sented:to_send]) + cipsend = ATCommand( + "AT+CIPSEND={},{}".format(self._fd, to_send), + "+CIPSEND:", + "ERROR\r\n", + self._modem._default_timeout, + ) + output, error = self._modem.response_at_command2(cipsend) + if error == self._modem.ERR_GENERIC: + err = utils.extract_int(output, "\+CIPERROR: ", "\r\n") + raise SIMComError(SIMComError.D_TCPIP_ERR, err, cipsend.cmd) + elif error == self._modem.ERR_TIMEOUT: + raise SIMComError(SIMComError.D_GENERIC, error, cipsend.cmd) + elif error == self._modem.ERR_NONE: + cnf = utils.extract_text( + output, "\+CIPSEND: {},{},".format(self._fd, to_send), "\r\n" + ) + sented = int(cnf) if cnf else 0 + return to_send + + def sendall(self, data: bytes | str | bytearray) -> None: + self.send(data) + + def sendto(self, data: bytes | str | bytearray, address) -> int: + if self._state == self._STATE_CLOSE: + self._local_port = self._modem.apply_port() + cipopen = ATCommand( + "AT+CIPOPEN={},{},,,{}".format( + self._fd, + "".join(['"', self._proto_type.get(self._proto, ""), '"']), + self._local_port, + ), + "OK\r\n", + "ERROR\r\n", + self._modem._default_timeout, + ) + output, error = self._modem.execute_at_command2(cipopen) + if error == self._modem.ERR_GENERIC: + errno = utils.extract_int(output, "\+CIPOPEN: ", "\r\n") + raise SIMComError(SIMComError.D_TCPIP_ERR, errno, cipopen.cmd) + elif error == self._modem.ERR_TIMEOUT: + raise SIMComError(SIMComError.D_GENERIC, error, cipopen.cmd) + self._state = self._STATE_OPEN + + # data type check + if isinstance(data, str): + data = data.encode("utf-8") + if isinstance(data, bytearray): + data = bytes(data) + + # send cmd + to_send = len(data) + cipsend = ATCommand( + "AT+CIPSEND={},{},{},{}".format( + self._fd, to_send, "".join(['"', address[0], '"']), address[1] + ), + ">", + "ERROR\r\n", + self._modem._default_timeout, + ) + output, error = self._modem.execute_at_command2(cipsend, line_end="") + if error == self._modem.ERR_GENERIC: + errno = utils.extract_int(output, "\+CIPSEND: ", "\r\n") + raise SIMComError(SIMComError.D_TCPIP_ERR, errno, cipsend.cmd) + elif error == self._modem.ERR_TIMEOUT: + raise SIMComError(SIMComError.D_GENERIC, error, cipsend.cmd) + + sented = 0 + while sented < to_send: + self._modem.uart.write(data[sented:to_send]) + cipsend = ATCommand( + "AT+CIPSEND={},{}".format(self._fd, to_send), + "+CIPSEND:", + "ERROR\r\n", + self._modem._default_timeout, + ) + output, error = self._modem.response_at_command2(cipsend) + if error == self._modem.ERR_GENERIC: + errno = utils.extract_int(output, "\+CIPERROR: ", "\r\n") + raise SIMComError(SIMComError.D_TCPIP_ERR, errno, cipsend.cmd) + elif error == self._modem.ERR_TIMEOUT: + raise SIMComError(SIMComError.D_GENERIC, error, cipsend.cmd) + else: + cnf = utils.extract_text( + output, "\+CIPSEND: {},{},".format(self._fd, to_send), "\r\n" + ) + sented = int(cnf) if cnf else 0 + return to_send + + def _recv(self) -> None: + ciprxget = ATCommand( + "AT+CIPRXGET=4,{}".format(self._fd), + "OK\r\n", + "ERROR\r\n", + self._modem._default_timeout, + ) + output, error = self._modem.execute_at_command2(ciprxget) + if error == self._modem.ERR_GENERIC: + errno = utils.extract_int(output, "\+IP ERROR: ", "\r\n") + raise SIMComError(SIMComError.D_TCPIP_ERR_INFO, errno, ciprxget.cmd) + elif error == self._modem.ERR_TIMEOUT: + raise SIMComError(SIMComError.D_GENERIC, error, ciprxget.cmd) + + to_recv = int(utils.extract_text(output, "\+CIPRXGET: 4,{},".format(self._fd), "\r\n")) + if to_recv == 0: + return + + to_recv = (1500 - self._ringio.any()) if to_recv > (1500 - self._ringio.any()) else to_recv + + ciprxget = ATCommand( + "AT+CIPRXGET=2,{},{}".format(self._fd, to_recv), + "OK\r\n", + "ERROR\r\n", + self._modem._default_timeout, + ) + output, error = self._modem.execute_at_command2(ciprxget) + if error == self._modem.ERR_GENERIC: + errno = utils.extract_int(output, "\+IP ERROR: ", "\r\n") + raise SIMComError(SIMComError.D_TCPIP_ERR_INFO, errno, ciprxget.cmd) + elif error == self._modem.ERR_TIMEOUT: + raise SIMComError(SIMComError.D_GENERIC, error, ciprxget.cmd) + + # prase data + read_len = utils.extract_text(output, "\+CIPRXGET: 2,{},".format(self._fd), ",") + read_len = int(read_len) if read_len else 0 + rest_len = utils.extract_text( + output, "\+CIPRXGET: 2,{},{},".format(self._fd, read_len), "\r\n" + ) + rest_len = int(rest_len) if rest_len else 0 + fstr = "+CIPRXGET: 2,{},{},{}\r\n".format(self._fd, read_len, rest_len) + start = output.find(fstr) + len(fstr) + + self._ringio.write(output[start : start + read_len].encode("utf-8")) + + def recv(self, bufsize) -> bytes: + return self.recvfrom(bufsize) + + def readall(self) -> bytes: + out = bytearray(self._ringio.any()) + self._ringio.readinto(out) + last_time = time.ticks_ms() + while time.ticks_diff(time.ticks_ms(), last_time) < self._timeout: + self._recv() + buf = self._ringio.read() + out.extend(buf) + + return bytes(out) + + def recvfrom(self, bufsize) -> bytes: + if bufsize == -1: + return self.readall() + + if self._ringio.any() > bufsize: + return self._ringio.read(bufsize) + + read_len = self._ringio.any() + out = bytearray(read_len) + self._ringio.readinto(out) + last_time = time.ticks_ms() + while read_len < bufsize and time.ticks_diff(time.ticks_ms(), last_time) < self._timeout: + self._recv() + buf = self._ringio.read(bufsize - read_len) + out.extend(buf) + read_len += len(buf) + + return bytes(out) + + def setsockopt(self, level, optname, value): + raise NotImplementedError + + def settimeout(self, timeout): + if timeout is None: + self._timeout = 500 + + cipccfg = ATCommand("AT+CIPCCFG?", "OK\r\n", "ERROR\r\n", self._modem._default_timeout) + output, error = self._modem.execute_at_command2(cipccfg) + if error == self._modem.ERR_TIMEOUT: + raise SIMComError(SIMComError.D_GENERIC, error, cipccfg.cmd) + + params = utils.extract_number(output) + + cipccfg = ATCommand( + "AT+CIPCCFG={},{},{},{},{},{},{}".format( + params[0], params[1], params[2], params[3], params[4], params[5], self._timeout + ), + "OK\r\n", + "ERROR\r\n", + self._modem._default_timeout, + ) + + def setblocking(self): + raise NotImplementedError + + def makefile(self, mode): + return self + + def fileno(self): + return self._fd + + def read(self, *args) -> bytes: + return self.recvfrom(args[0] if len(args) > 0 else -1) + + def readinto(self, *args) -> int: + buf = args[0] + nbytes = len(buf) + if len(args) > 1: + nbytes = args[1] if args[1] < nbytes else nbytes + + if self._ringio.any() < nbytes: + self._recv() + + return self._ringio.readinto(buf, nbytes) + + def readline(self) -> bytes: + last_time = time.ticks_ms() + while ( + self._ringio.any() == 0 and time.ticks_diff(time.ticks_ms(), last_time) < self._timeout + ): + self._recv() + return self._ringio.readline() + + def write(self, data) -> int: + return self.send(data) + + +class SIM7600(Modem): + # tcp/udp socket fd list + _fds = [-1 for _ in range(10)] + + # tcp/udp socket port list + _used_port = [] + + def __init__( + self, + uart=None, + pwrkey_pin=None, + reset_pin=None, + power_pin=None, + tx_pin=None, + rx_pin=None, + verbose=False, + ): + super().__init__(uart, pwrkey_pin, reset_pin, power_pin, tx_pin, rx_pin, verbose) + self._default_timeout = 5000 + + # disable echo + self.execute_at_command2(ATCommand("ATE0", "OK\r\n", "ERROR\r\n", self._default_timeout)) + + # buffer access mode + self.execute_at_command2( + ATCommand("AT+CIPRXGET=1", "OK\r\n", "ERROR\r\n", self._default_timeout) + ) + # No transparent transmission mode + self.execute_at_command2( + ATCommand("AT+CIPMODE=0", "OK\r\n", "ERROR\r\n", self._default_timeout) + ) + # open network + self.execute_at_command2( + ATCommand("AT+NETOPEN", "OK\r\n", "ERROR\r\n", self._default_timeout) + ) + + """fd/port management""" + + def apply_fd(self) -> int: + for i in range(10): + if self._fds[i] == -1: + self._fds[i] = i + return i + return -1 + + def release_fd(self, fd: int) -> None: + self._fds[fd] = -1 + + def apply_port(self) -> int: + for i in range(1024, 65535): + if i not in self._used_port: + self._used_port.append(i) + return i + return -1 + + def release_port(self, port: int) -> None: + self._used_port.remove(port) + + """socket method""" + + def getaddrinfo(self, host, port, af=0, type=0, proto=0, flags=0): + """ + The resulting list of 5-tuples has the following structure: + (family, type, proto, canonname, sockaddr) + """ + res = [] + + at = ATCommand('AT+CDNSGIP="{}"'.format(host), "OK\r\n", "ERROR\r\n", 10000) + output, error = self.execute_at_command2(at) + + ip = "0.0.0.0" + if error == self.ERR_NONE: + ip = utils.extract_text(output, '"{}","'.format(host), '"') + elif error == self.ERR_TIMEOUT: + raise SIMComError(SIMComError.D_GENERIC, error, at.cmd) + elif error == self.ERR_GENERIC: + raise SIMComError(SIMComError.D_DNS_ERROR_CODE, 10, at.cmd) + + res.append((socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP, host, (ip, port))) + return res + + def socket(self, af=socket.AF_INET, type=socket.SOCK_STREAM, proto=socket.IPPROTO_TCP): + return _socket(self, af, type, proto) + + # def wrap_socket( + # self, + # sock, + # server_side=False, + # key=None, + # cert=None, + # cert_reqs=CERT_NONE, + # cadata=None, + # server_hostname=None, + # do_handshake=True, + # ): + # sock.close() + # s = _sockets(self, sock._domain, sock._type, sock._proto) + # s.connect(sock._address) + # return s + + """request method""" + + def request( + self, + method, + url, + data=None, + json=None, + headers={}, + stream=None, + auth=None, + timeout=None, + parse_headers=True, + ): + return requests2._request( + self, method, url, data, json, headers, stream, auth, timeout, parse_headers + ) + + def head(self, url, **kw): + return self.request("HEAD", url, **kw) + + def get(self, url, **kw): + return self.request("GET", url, **kw) + + def post(self, url, **kw): + return self.request("POST", url, **kw) + + def put(self, url, **kw): + return self.request("PUT", url, **kw) + + def patch(self, url, **kw): + return self.request("PATCH", url, **kw) + + def delete(self, url, **kw): + return self.request("DELETE", url, **kw) + + """common method""" + + def check_modem_is_ready(self) -> bool: + # Check if modem is ready for AT command + at = ATCommand("AT", "OK", "ERROR", self._default_timeout) + output, error = self.execute_at_command2(at, True) + return not error + + def set_command_echo_mode(self, state=1) -> bool: + # Set echo mode off or on + at = ATCommand("ATE{}".format(state), "OK", "ERROR", self._default_timeout) + output, error = self.execute_at_command2(at) + return not error + + def check_sim_is_connected(self) -> bool: + # Check the SIM card is connected or not? + at = ATCommand("AT+CPIN?", "OK", "ERROR", self._default_timeout) + output, error = self.execute_at_command2(at) + if error: + return False + return True if output.find("READY") != -1 else False + + REG_NO_RESULT = -1 + REG_UNREGISTERED = 0 + REG_SEARCHING = 2 + REG_DENIED = 3 + REG_OK_HOME = 1 + REG_OK_ROAMING = 5 + REG_UNKNOWN = 4 + + def get_network_registration_status(self) -> int: + # Get the registration status with the network + creg = ATCommand("AT+CGREG?", "OK", "ERROR", self._default_timeout) + output, error = self.execute_at_command2(creg) + if error: + return self.REG_NO_RESULT + n = utils.extract_int(output, "+CGREG: ", ",") + stat = utils.extract_int(output, "+CGREG: {}".format(n), "\r\n" if n == 0 else ",") + return stat + + def get_signal_strength(self) -> int: + # Get the signal strength + csq = ATCommand("AT+CSQ", "OK", "ERROR", self._default_timeout) + output, error = self.execute_at_command2(csq) + if error: + return 99 + + rssi = utils.extract_int(output, "+CSQ: ", ",") + return rssi + + def get_gprs_registration_status(self): + # Get the registration status with the gprs network + return self.get_network_registration_status() + + def get_model_identification(self) -> str: + # Query the model identification information + cgmm = ATCommand("AT+CGMM", "OK", "ERROR", self._default_timeout) + output, error = self.execute_at_command2(cgmm) + if error: + return "" + return utils.extract_text(output, "\r\n", "\r\n") + + def get_gprs_network_status(self) -> bool: + # Get attach or detach from the GPRS network + cgatt = ATCommand("AT+CGATT?", "OK", "ERROR", self._default_timeout) + output, error = self.execute_at_command2(cgatt) + if error: + return False + state = utils.extract_int(output, "+CGATT: ", "\r\n") + return True if state == 1 else False + + def set_gprs_network_state(self, enable=1) -> bool: + # Set attach or detach from the GPRS network + cgatt = ATCommand("AT+CGATT={0}".format(enable), "OK", "ERROR", self._default_timeout) + output, error = self.execute_at_command2(cgatt) + if error: + return False + state = utils.extract_int(output, "+CGATT: ", "\r\n") + return True if state == enable else False + + def set_pdp_context(self, apn=""): + # Set Define PDP Context + cgdcont = ATCommand( + 'AT+CGDCONT=1,"IP","{}"'.format(apn), "OK", "ERROR", self._default_timeout + ) + output, error = self.execute_at_command2(cgdcont) + return not error + + def get_show_pdp_address(self, cid) -> str: + # Get Show PDP address. + # cid: 1 - 24 + cgpaddr = ATCommand("AT+CGPADDR={}".format(cid), "OK", "ERROR", self._default_timeout) + output, error = self.execute_at_command2(cgpaddr) + if error: + return "0.0.0.0" + address = utils.extract_text(output, '+CGPADDR: {},"'.format(cid), '"') + return address + + def get_selected_operator(self) -> int: + # Get selected operator. + cops = ATCommand("AT+COPS?", "OK", "ERROR", self._default_timeout) + output, error = self.execute_at_command2(cops) + if error: + return 0 + return utils.extract_int(output, "+COPS: ", ",") + + def get_imei_number(self): + # Request TA Serial Number Identification(IMEI) + cgsn = ATCommand("AT+CGSN=1", "OK", "ERROR", self._default_timeout) + output, error = self.execute_at_command2(cgsn) + if error: + return "" + return utils.extract_text(output, "\r\n", "\r\n") + + def get_ccid_number(self) -> str: + # Show ICCID + iccid = ATCommand("AT+ICCID", "OK", "ERROR", self._default_timeout) + output, error = self.execute_at_command2(iccid) + if error: + return "" + return utils.extract_text(output, "+ICCID: ", "\r\n") + + PDP_PARAM_IP = 1 + PDP_PARAM_APN = 2 + + def get_pdp_context_dynamic_parameters(self, param=1) -> str: + # PDP Context Read Dynamic Parameters + if param == self.PDP_PARAM_IP: + return self.get_show_pdp_address(1) + elif param == self.PDP_PARAM_APN: + cgdcont = ATCommand("AT+CGDCONT?", "OK", "ERROR", self._default_timeout) + output, error = self.execute_at_command2(cgdcont) + if error: + return "" + return utils.extract_text(output, '+CGDCONT: 1,"IP","', '"') diff --git a/m5stack/libs/driver/simcom/sim800.py b/m5stack/libs/driver/simcom/sim800.py new file mode 100644 index 00000000..d487299e --- /dev/null +++ b/m5stack/libs/driver/simcom/sim800.py @@ -0,0 +1,386 @@ +# SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +from .common import Modem +from .common import AT_CMD +import re +import socket + + +class socket_simcom: + AF_INET = socket.AF_INET + AF_INET6 = socket.AF_INET6 + + SOCK_STREAM = socket.SOCK_STREAM + SOCK_DGRAM = socket.SOCK_DGRAM + SOCK_RAW = socket.SOCK_RAW + + IPPROTO_IP = socket.IPPROTO_IP + IPPROTO_TCP = socket.IPPROTO_TCP + IPPROTO_UDP = socket.IPPROTO_UDP + + def __init__(self, af=AF_INET, type=SOCK_STREAM, proto=IPPROTO_TCP): + self._domain = af + self._type = type + self._proto = proto + # todo: fd(cmux?) + self._fd = 0 + + def close(self): + pass + + def bind(self, address): + pass + + def listen(self, backlog): + pass + + def accept(self): + pass + + def connect(self, address): + cipstart = AT_CMD( # noqa: F841 + "AT+CIPSTART={},{},{},{}".format(self._fd, address[0], address[1], 0), "OK", 3 + ) + + def send(self, data): + pass + + def sendall(self, data): + pass + + def sendto(self, data, address): + pass + + def recv(self, size): + pass + + def recvfrom(self, size): + pass + + def setsockopt(self, level, optname, value): + pass + + def settimeout(self, timeout): + pass + + def setblocking(self): + pass + + def makefile(self, mode): + pass + + def fileno(self): + pass + + def read(self, size): + pass + + def readinto(self, buffer): + pass + + def readline(self): + pass + + def write(self, data): + pass + + +class SIM800(Modem): + def __init__( + self, uart=None, pwrkey_pin=None, reset_pin=None, power_pin=None, tx_pin=None, rx_pin=None + ): + super().__init__(uart, pwrkey_pin, reset_pin, power_pin, tx_pin, rx_pin) + + def getaddrinfo(self, host, port, af=0, type=0, proto=0, flags=0): + pass + + def socket(self, af=0, type=0, proto=0): + pass + + def connect(self, address): + # CIPSTART + pass + + def write(self, data): + # CIPSEND + pass + + def readline(self): + pass + + def read(self, size): + # CIPRXGET + pass + + def close(self): + pass + + def get_imei_number(self): + # Request TA Serial Number Identification(IMEI) + cgsn = AT_CMD("AT+CGSN=1", "OK", 3) + output, error = self.execute_at_command(cgsn) + return False if error else output + + def get_ccid_number(self) -> str | bool: + # Show ICCID + iccid = AT_CMD("AT+ICCID", "+ICCID:", 3) + output, error = self.execute_at_command(iccid) + return False if error else output.split(" ")[1] + + def get_pdp_context_dynamic_parameters(self, param=1) -> str | bool: + # PDP Context Read Dynamic Parameters + CGCONTRDP = AT_CMD("AT+CGCONTRDP", "+CGCONTRDP:", 5) # noqa: N806 + output, error = self.execute_at_command(CGCONTRDP) + if error: + return False + return ( + output.split(",")[4].replace('"', "") + if param == 1 + else output.split(",")[2].replace('"', "") + ) + + # MQTT Test Server:mqtt.m5stack.com, Port:1883. + def mqtt_server_configure(self, server, port, client_id, username, passwd, keepalive) -> bool: + #! Connect to MQTT broker. + self.mqtt_username = username + self.mqtt_passwd = passwd + self.mqtt_keepalive = keepalive + self.mqtt_host_port = "tcp://" + server + ":" + str(port) + self.clean_session = self.mqttclient_index = None + self.mqtt_connect_status = False + + # mqtt callback function keyword is set + self.downlink_keyword.append("+CMQTTRXTOPIC:") + self.downlink_keyword.append("+CMQTTRXPAYLOAD:") + self.downlink_keyword.append("+CMQTTCONNLOST:") + self.callback_keyword.append("MQTT_CB") + self.downlink_callback["MQTT_CB"] = self.mqtt_subscribe_cb + self.mqtt_subscribe_cb_list = {} + self.topic = self.message = None + + CMQTTSTART = AT_CMD("AT+CMQTTSTART", "+CMQTTSTART: 0", 12) # noqa: N806 + output, error = self.execute_at_command(CMQTTSTART) + if error or output[-1] != "0": + return False + + CMQTTACCQ = AT_CMD('AT+CMQTTACCQ=0,"{0}"'.format(client_id), "OK", 5) # noqa: N806 + _, error = self.execute_at_command(CMQTTACCQ) + if error: + return False + + return self.mqtt_server_connect(0) + + def mqtt_server_connect(self, clean_session=0) -> bool: + self.clean_session = clean_session + CMQTTCONNECT = AT_CMD( # noqa: N806 + ( + 'AT+CMQTTCONNECT=0,"{0}",{1},{2},"{3}","{4}"'.format( + self.mqtt_host_port, + self.mqtt_keepalive, + clean_session, + self.mqtt_username, + self.mqtt_passwd, + ) + if len(self.mqtt_username) and len(self.mqtt_passwd) + else 'AT+CMQTTCONNECT=0,"{0}",{1},{2}'.format( + self.mqtt_host_port, self.mqtt_keepalive, clean_session + ) + ), + "+CMQTTCONNECT:", + 30, + ) + output, error = self.execute_at_command(CMQTTCONNECT) + if error or output[-1] != "0": + return False + self.mqttclient_index = int(output[15]) + self.mqtt_connect_status = True + return True + + def mqtt_server_disconnect(self) -> None | bool: + CMQTTDISC = AT_CMD("AT+CMQTTDISC={0},120".format(self.mqttclient_index), "+CMQTTDISC:", 5) # noqa: N806 + _, error = self.execute_at_command(CMQTTDISC) + if error: + return False + CMQTTREL = AT_CMD("AT+CMQTTREL={0}".format(self.mqttclient_index), "OK", 5) # noqa: N806 + _, error = self.execute_at_command(CMQTTREL) + if error: + return False + CMQTTSTOP = AT_CMD("AT+CMQTTSTOP", "+CMQTTSTOP:", 5) # noqa: N806 + _, error = self.execute_at_command(CMQTTSTOP) + if error: + return False + + def mqtt_subscribe_topic(self, topic, cb, qos=0) -> bool: + # Subscribe topic(support wildcards). + CMQTTSUB = AT_CMD( # noqa: N806 + "AT+CMQTTSUB={0},{1},{2}".format(self.mqttclient_index, len(topic), qos), ">", 10 + ) + output, error = self.execute_at_command(CMQTTSUB) + if error: + return False + TOPIC = AT_CMD("{0}".format(topic), "+CMQTTSUB:", 10) # noqa: N806 + output, error = self.execute_at_command(TOPIC) + if error or output[-1] != "0": + return False + if topic not in self.mqtt_subscribe_cb_list.keys(): + self.mqtt_subscribe_cb_list[topic] = cb + return True + + def mqtt_unsubscribe_topic(self, topic) -> bool: + # Unsubscribe topic. + CMQTTUNSUB = AT_CMD( # noqa: N806 + "AT+CMQTTUNSUB={0},{1}".format(self.mqttclient_index, len(topic)), ">", 10 + ) + output, error = self.execute_at_command(CMQTTUNSUB) + if error: + return False + TOPIC = AT_CMD("{0}".format(topic), "+CMQTTUNSUB:", 10) # noqa: N806 + output, error = self.execute_at_command(TOPIC) + if error or output[-1] != "0": + return False + if topic in self.mqtt_subscribe_cb_list.keys(): + self.mqtt_subscribe_cb_list.pop(topic) + return True + + def mqtt_publish_topic(self, topic, payload, qos=0) -> None | bool: + # Publish message with topic. + CMQTTTOPIC = AT_CMD( # noqa: N806 + "AT+CMQTTTOPIC={0},{1}".format(self.mqttclient_index, len(topic)), ">", 10 + ) + _, error = self.execute_at_command(CMQTTTOPIC) + if error: + return False + TOPIC = AT_CMD("{0}".format(topic), "OK", 10) # noqa: N806 + _, error = self.execute_at_command(TOPIC) + if error: + return False + + CMQTTPAYLOAD = AT_CMD( # noqa: N806 + "AT+CMQTTPAYLOAD={0},{1}".format(self.mqttclient_index, len(payload)), ">", 10 + ) + _, error = self.execute_at_command(CMQTTPAYLOAD) + if error: + return False + TOPIC = AT_CMD("{0}".format(payload), "OK", 10) # noqa: N806 + _, error = self.execute_at_command(TOPIC) + if error: + return False + + CMQTTPUB = AT_CMD( # noqa: N806 + "AT+CMQTTPUB={0},{1},120".format(self.mqttclient_index, qos), "+CMQTTPUB:", 10 + ) + output, error = self.execute_at_command(CMQTTPUB) + if error or output[-1] != "0": + return False + + def mqtt_server_is_connect(self) -> bool: + # Check mqtt server connection. + CMQTTCONNECT = AT_CMD("AT+CMQTTCONNECT?", "+CMQTTCONNECT:", 5) # noqa: N806 + output, error = self.execute_at_command(CMQTTCONNECT) + if error: + return False + self.mqttclient_index = int(output[-1]) + return True if (output.split(",")[1].replace('"', "") == self.mqtt_host_port) else False + + def mqtt_polling_loop(self) -> None: + DUMMY = AT_CMD("", "", 0) # noqa: N806 + self.response_at_command(DUMMY) + if self.mqtt_connect_status is not True: + self.mqtt_server_connect(self.clean_session) + + def mqtt_subscribe_cb(self, buffer) -> None: + # main callback function + if "+CMQTTCONNLOST:" in buffer: + self.mqtt_connect_status = False + if "+CMQTTRXTOPIC:" in buffer: + self.topic = self.message = None + self.topic = buffer.split(",")[-1][:-2] + if "+CMQTTRXPAYLOAD:" in buffer: + self.message = buffer.split(",")[-1][:-1] + if self.topic in self.mqtt_subscribe_cb_list.keys(): + if self.message: + self.mqtt_subscribe_cb_list[self.topic](self.topic, self.message) + self.topic = self.message = None + + # Create & Request Http(s) + HTTPCLIENT_GET = 0 + HTTPCLIENT_POST = 1 + + def http_request( + self, method=HTTPCLIENT_GET, url="http://api.m5stack.com/v1", headers={}, data=None + ) -> str | bool: + # Create HTTP host instance + self.response_code = 0 + self.data_content = "" + + self.set_pdp_context("cmnbiot") + + for i in range(3): + HTTPINIT = AT_CMD("AT+HTTPINIT", "OK", 15) # noqa: N806 + output, error = self.execute_at_command(HTTPINIT) + if not error: + break + self.http_terminate() + + HTTPPARA = AT_CMD('AT+HTTPPARA="URL","{0}"'.format(url), "OK", 10) # noqa: N806 + output, error = self.execute_at_command(HTTPPARA) + if error: + return False + + if method == self.HTTPCLIENT_POST: + for head in headers.items(): + if head[0] == "Content-Type": + contenttype = head[1] + + HTTPPARA = AT_CMD('AT+HTTPPARA="CONTENT","{0}"'.format(contenttype), "OK", 5) # noqa: N806 + output, error = self.execute_at_command(HTTPPARA) + if error: + return False + HTTPPARA = AT_CMD("AT+HTTPDATA={0},3000".format(len(data)), "DOWNLOAD", 5) # noqa: N806 + output, error = self.execute_at_command(HTTPPARA) + if error: + return False + DATA = AT_CMD("{0}".format(data), "OK", 10) # noqa: N806 + _, error = self.execute_at_command(DATA) + if error: + return False + + HTTPACTION = AT_CMD("AT+HTTPACTION={0}".format(method), "+HTTPACTION:", 25) # noqa: N806 + output, error = self.execute_at_command(HTTPACTION) + if error: + return False + + self.response_code = int(output.split(",")[1]) + if self.response_code != 200: + print('Response code: "{0}"'.format(self.response_code)) + return False + + HTTPREAD = AT_CMD("AT+HTTPREAD=0,500", "+HTTPREAD:", 25) # noqa: N806 + output, error = self.execute_at_command(HTTPREAD) + if error: + return False + match = re.search(r"\+HTTPREAD: (\d+)", output) + if match: + data_len = match.group(1) + else: + return False + # data_len = ''.join(filter(str.isdigit, output.split(' ')[1])) + data_index = len(output.split(" ")[0]) + len(data_len) + 1 + self.data_content = output[data_index : (data_index + int(data_len))] + + def http_terminate(self) -> bool: + # http service terminate + HTTPTERM = AT_CMD("AT+HTTPTERM", "OK", 15) # noqa: N806 + _, error = self.execute_at_command(HTTPTERM) + return not error + + def make_header(self, key, value) -> str: + return str(key) + ":" + str(value) + "\n" + + def asciistr_to_hexstr(self, byte) -> bool: + return "".join(["%02X" % x for x in byte.encode()]).strip() + + def hexstr_to_asciistr(self, hex) -> str: + return bytes.fromhex(hex).decode() diff --git a/m5stack/libs/driver/simcom/toolkit/requests2.py b/m5stack/libs/driver/simcom/toolkit/requests2.py new file mode 100644 index 00000000..d857c001 --- /dev/null +++ b/m5stack/libs/driver/simcom/toolkit/requests2.py @@ -0,0 +1,212 @@ +# SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import socket + + +class Response: + def __init__(self, f): + self.raw = f + self.encoding = "utf-8" + self._cached = None + + def close(self): + if self.raw: + self.raw.close() + self.raw = None + self._cached = None + + @property + def content(self): + if self._cached is None: + try: + self._cached = self.raw.read() + finally: + self.raw.close() + self.raw = None + return self._cached + + @property + def text(self): + return str(self.content, self.encoding) + + def json(self): + import ujson + + return ujson.loads(self.content) + + +def _request( + modem, + method, + url, + data=None, + json=None, + headers={}, + stream=None, + auth=None, + timeout=None, + parse_headers=True, +): + redirect = None # redirection url, None means no redirection + chunked_data = data and getattr(data, "__next__", None) and not getattr(data, "__len__", None) + + if auth is not None: + import binascii + + username, password = auth + formated = b"{}:{}".format(username, password) + formated = str(binascii.b2a_base64(formated)[:-1], "ascii") + headers["Authorization"] = "Basic {}".format(formated) + + try: + proto, dummy, host, path = url.split("/", 3) + except ValueError: + proto, dummy, host = url.split("/", 2) + path = "" + if proto == "http:": + port = 80 + elif proto == "https:": + import ssl + + port = 443 + else: + raise ValueError("Unsupported protocol: " + proto) + + if ":" in host: + host, port = host.split(":", 1) + port = int(port) + + ai = modem.getaddrinfo(host, port, 0, socket.SOCK_STREAM) + ai = ai[0] + + resp_d = None + if parse_headers is not False: + resp_d = {} + + s = modem.socket(ai[0], socket.SOCK_STREAM, ai[2]) + if timeout is not None: + # Note: settimeout is not supported on all platforms, will raise + # an AttributeError if not available. + s.settimeout(timeout) + + try: + s.connect(ai[-1]) + if proto == "https:": + s = ssl.wrap_socket(s, server_hostname=host) + s.write(b"%s /%s HTTP/1.0\r\n" % (method, path)) + if "Host" not in headers: + s.write(b"Host: %s\r\n" % host) + # Iterate over keys to avoid tuple alloc + for k in headers: + s.write(k) + s.write(b": ") + s.write(headers[k]) + s.write(b"\r\n") + if json is not None: + assert data is None + import ujson + + data = ujson.dumps(json) + s.write(b"Content-Type: application/json\r\n") + if data: + if chunked_data: + s.write(b"Transfer-Encoding: chunked\r\n") + else: + if getattr(data, "readinto", None): + s.write(b"Content-Length: %d\r\n" % len(data.seek(0, 2))) + else: + s.write(b"Content-Length: %d\r\n" % len(data)) + s.write(b"Connection: close\r\n\r\n") + if data: + if chunked_data: + for chunk in data: + s.write(b"%x\r\n" % len(chunk)) + s.write(chunk) + s.write(b"\r\n") + s.write("0\r\n\r\n") + else: + if getattr(data, "readinto", None): + l = data.seek(0, 2) + to_read = data.seek(0, 0) + buf = bytearray(1024) + while to_read < l: + r = data.readinto(buf) + if r == 1024: + s.write(buf) + else: + s.write(buf[:r]) + to_read += r + data.close() + else: + s.write(data) + + l = s.readline() + # print(l) + l = l.split(None, 2) + if len(l) < 2: + # Invalid response + raise ValueError("HTTP error: BadStatusLine:\n%s" % l) + status = int(l[1]) + reason = "" + if len(l) > 2: + reason = l[2].rstrip() + while True: + l = s.readline() + if not l or l == b"\r\n": + break + # print(l) + if l.startswith(b"Transfer-Encoding:"): + if b"chunked" in l: + raise ValueError("Unsupported " + str(l, "utf-8")) + elif l.startswith(b"Location:") and not 200 <= status <= 299: + if status in [301, 302, 303, 307, 308]: + redirect = str(l[10:-2], "utf-8") + else: + raise NotImplementedError("Redirect %d not yet supported" % status) + if parse_headers is False: + pass + elif parse_headers is True: + l = str(l, "utf-8") + k, v = l.split(":", 1) + resp_d[k] = v.strip() + else: + parse_headers(l, resp_d) + except OSError: + s.close() + raise + + if redirect: + s.close() + if status in [301, 302, 303]: + return _request("GET", redirect, None, None, headers, stream) + else: + return _request(method, redirect, data, json, headers, stream) + else: + resp = Response(s) + resp.status_code = status + resp.reason = reason + if resp_d is not None: + resp.headers = resp_d + return resp + + +def urlencode(query): + if not isinstance(query, dict): + raise TypeError + + l = [] + for k, v in query.items(): + if isinstance(k, bytes) or isinstance(k, bytearray): + k = k.decode("utf-8") + else: + k = str(k) + + if isinstance(v, bytes) or isinstance(v, bytearray): + v = v.decode("utf-8") + else: + v = str(v) + l.append(k + "=" + v) + + return "&".join(l) diff --git a/m5stack/libs/driver/simcom/utils.py b/m5stack/libs/driver/simcom/utils.py new file mode 100644 index 00000000..c79f7fa4 --- /dev/null +++ b/m5stack/libs/driver/simcom/utils.py @@ -0,0 +1,43 @@ +# SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import re +import time + + +def extract_text(text, start_str, end_str) -> str: + # print("text:", repr(text)) + # print("start_str:", repr(start_str)) + # print("end_str:", repr(start_str)) + pattern = re.compile(f"{start_str}(.*?){end_str}") + match = re.search(pattern, text) + return match.group(1) if match else None + + +def extract_int(text, start_str, end_str) -> int: + pattern = re.compile(f"{start_str}(.*?){end_str}") + match = re.search(pattern, text) + return int(match.group(1)) if match else None + + +def extract_number(text) -> list: + if isinstance(text, bytes): + text = text.decode("utf-8") + pattern = r"\d+(?:,\d+)*" + match = re.search(pattern, text) + if match: + return [int(x) for x in match.group(0).split(",")] + return [] + + +def measure_time(func): + def wrapper(*args, **kwargs): + start_time = time.ticks_ms() + result = func(*args, **kwargs) # 执行原函数 + end_time = time.ticks_ms() # 记录结束时间 + execution_time = end_time - start_time # 计算执行时间 + print(f"total time consumed to execute {func}: {execution_time}ms") + return result + + return wrapper diff --git a/m5stack/libs/driver/simcom/v25ter.py b/m5stack/libs/driver/simcom/v25ter.py new file mode 100644 index 00000000..37e24d03 --- /dev/null +++ b/m5stack/libs/driver/simcom/v25ter.py @@ -0,0 +1,51 @@ +from .common import AT_CMD + +( + "A/", # Re-issues the Last Command Given + "ATA", # Call answer + "ATD", # Mobile Originated Call to Dial A Number + "ATD>", # Originate call from active memory(1) + "ATD>", # Originate call from active memory(2) + "ATDL", # Redial last telephone number used + "ATE", # Enable command echo + "ATH", # Disconnect existing call + "ATI", # Display product identification information + "ATL", # Set monitor speaker loudness + "ATM", # Set monitor speaker mode + "+++", # Switch from data mode to command mode + "ATO", # Switch from command mode to data mode + "ATP", # Select pulse dialling + "ATQ", # Set Result Code Presentation Mode + "ATS0", # Set number of rings before automatically answering the call + "ATS3", # Set command line termination character + "ATS4", # Set response formatting character + "ATS5", # Set command line editing character + "ATS6", # Pause before blind dialling + "ATS7", # Set number of seconds to wait for connection completion + "ATS8", # Set number of seconds to wait for comma dial modifier encountered in dial string of D command + "ATS10", # Set disconnect delay after indicating the absence of data carrier + "ATT", # Select tone dialing + "ATV", # TA response format + "ATX", # Set connect result code format and monitor call progress + "ATZ", # Restore the user setting from ME + "AT&C", # Set DCD function mode + "AT&D", # Set DTR function mode + "AT&F", # Factory defined configuration + "AT&V", # Display current configuration + "AT&W", # Save the user setting to ME + "AT+GCAP", # Request overall capabilities + "AT+GMI", # Request manufacturer identification + "AT+GMM", # Request TA model identification + "AT+GMR", # Request TA revision identification of software release + "AT+GOI", # Request global object identification + "AT+GSN", # Request TA serial number identification (IMEI) + "AT+ICF", # Set TE-TA control character framing + "AT+IFC", # Set TE-TA local data flow control + "AT+IPR", # Set TE-TA fixed local rate + "AT+HVOIC", # Disconnect Voice Call Only +) + + +class V25Ter: + def __init__(self): + pass From f7f7cfc52a8dbf6affe0dea4de833b3e2490fec6 Mon Sep 17 00:00:00 2001 From: lbuque Date: Fri, 27 Dec 2024 09:13:54 +0800 Subject: [PATCH 050/322] libs/driver/simcom: Support ssl function. Signed-off-by: lbuque --- m5stack/libs/driver/simcom/sim7600.py | 325 +++++++++++++++++- .../libs/driver/simcom/toolkit/requests2.py | 2 +- 2 files changed, 310 insertions(+), 17 deletions(-) diff --git a/m5stack/libs/driver/simcom/sim7600.py b/m5stack/libs/driver/simcom/sim7600.py index 8eb5c8ca..61a1f17f 100644 --- a/m5stack/libs/driver/simcom/sim7600.py +++ b/m5stack/libs/driver/simcom/sim7600.py @@ -109,7 +109,7 @@ def __init__(self, modem, af=AF_INET, type=SOCK_STREAM, proto=IPPROTO_TCP): self._state = self._STATE_CLOSE self._ringio = micropython.RingIO(1500) - self._timeout = 5000 + self._timeout = 500 def __del__(self): self.close() @@ -414,10 +414,273 @@ def write(self, data) -> int: return self.send(data) +class _sockets(_socket): + AF_INET = socket.AF_INET + AF_INET6 = socket.AF_INET6 + + SOCK_STREAM = socket.SOCK_STREAM + SOCK_DGRAM = socket.SOCK_DGRAM + SOCK_RAW = socket.SOCK_RAW + + IPPROTO_IP = socket.IPPROTO_IP + IPPROTO_TCP = socket.IPPROTO_TCP + IPPROTO_UDP = socket.IPPROTO_UDP + + _proto_type = { + IPPROTO_TCP: "TCP", + IPPROTO_UDP: "UDP", + } + + _STATE_OPEN = 0 + _STATE_CLOSE = 1 + + def __init__(self, modem, af=AF_INET, type=SOCK_STREAM, proto=IPPROTO_TCP): + self._modem = modem + self._domain = af + self._type = type + self._proto = proto + self._fd = self._modem.apply_session_id() + self._address = None + if self._fd == -1: + raise SIMComError("No available session") + + self._state = self._STATE_CLOSE + self._ringio = micropython.RingIO(1500) + self._timeout = 500 + + def __del__(self): + self.close() + + def close(self) -> None: + if self._fd == -1: + return + close = ATCommand( + "AT+CCHCLOSE={}".format(self._fd), "OK\r\n", "ERROR\r\n", self._modem._default_timeout + ) + self._modem.execute_at_command2(close) + self._modem.release_session_id(self._fd) + self._fd = -1 + self._state = self._STATE_CLOSE + if hasattr(self, "_local_port"): + self._modem.release_port(self._local_port) + + def bind(self, address): + pass + + def listen(self, backlog): + pass + + def accept(self): + pass + + def connect(self, address): + if self._fd == -1: + raise SIMComError("socket closed") + + if self._state == self._STATE_OPEN: + return + + # Set the first SSL context to be used in the SSL connection + at = ATCommand( + "AT+CCHSSLCFG={},0".format(self._fd), + "OK\r\n", + "ERROR\r\n", + self._modem._default_timeout, + ) + self._modem.execute_at_command2(at) + # TODO: check error + + self._address = address + cipstart = ATCommand( + "AT+CCHOPEN={},{},{},{}".format( + self._fd, "".join(['"', address[0], '"']), address[1], 2 + ), + "+CCHOPEN", + "ERROR\r\n", + 120000, # Maximum Response Time + ) + output, error = self._modem.execute_at_command2(cipstart) + if error == self._modem.ERR_NONE: + self._state = self._STATE_OPEN + return True + elif error == self._modem.ERR_TIMEOUT: + raise SIMComError(SIMComError.D_GENERIC, error, cipstart.cmd) + elif error == self._modem.ERR_GENERIC: + err = utils.extract_int(output, "\+CCHOPEN: {},".format(self._fd), "\r\n") + raise SIMComError(SIMComError.D_TCPIP_ERR, err, cipstart.cmd) + + def send(self, data: bytes | str | bytearray) -> int: + # data type check + if isinstance(data, str): + data = data.encode("utf-8") + if isinstance(data, bytearray): + data = bytes(data) + + # send cmd + to_send = len(data) + cipsend = ATCommand( + "AT+CCHSEND={},{}".format(self._fd, to_send), + ">", + "ERROR\r\n", + self._modem._default_timeout, + ) + output, error = self._modem.execute_at_command2(cipsend, line_end="") + if error == self._modem.ERR_GENERIC: + err = utils.extract_int(output, "\+CCHSEND: ", "\r\n") + raise SIMComError(SIMComError.D_TCPIP_ERR, err, cipsend.cmd) + elif error == self._modem.ERR_TIMEOUT: + raise SIMComError(SIMComError.D_GENERIC, error, cipsend.cmd) + + sented = 0 + while sented < to_send: + self._modem.uart.write(data[sented:to_send]) + cipsend = ATCommand( + "AT+CCHSEND={},{}".format(self._fd, to_send), + "+CCHSEND:", + "ERROR\r\n", + self._modem._default_timeout, + ) + output, error = self._modem.response_at_command2(cipsend) + if error == self._modem.ERR_GENERIC: + err = utils.extract_int(output, "\+CIPERROR: ", "\r\n") + raise SIMComError(SIMComError.D_TCPIP_ERR, err, cipsend.cmd) + elif error == self._modem.ERR_TIMEOUT: + raise SIMComError(SIMComError.D_GENERIC, error, cipsend.cmd) + elif error == self._modem.ERR_NONE: + # FIXME: check sented + sented = to_send - sented + return to_send + + def sendall(self, data: bytes | str | bytearray) -> None: + self.send(data) + + def _recv(self) -> None: + cchrecv = ATCommand( + "AT+CCHRECV?", + "OK", + "ERROR", + self._modem._default_timeout, + ) + output, error = self._modem.execute_at_command2(cchrecv) + if error == self._modem.ERR_GENERIC: + errno = utils.extract_int(output, "\+IP ERROR: ", "\r\n") + raise SIMComError(SIMComError.D_TCPIP_ERR_INFO, errno, cchrecv.cmd) + elif error == self._modem.ERR_TIMEOUT: + raise SIMComError(SIMComError.D_GENERIC, error, cchrecv.cmd) + + cache_len_0 = int(utils.extract_text(output, "\+CCHRECV: LEN,", ",")) + cache_len_1 = int( + utils.extract_text(output, "\+CCHRECV: LEN,{},".format(cache_len_0), "\r\n") + ) + to_recv = cache_len_0 if self._fd == 0 else cache_len_1 + if to_recv == 0: + return + + if to_recv > 1024: + to_recv = 1024 + + to_recv = (1500 - self._ringio.any()) if to_recv > (1500 - self._ringio.any()) else to_recv + + ciprxget = ATCommand( + "AT+CCHRECV={},{}".format(self._fd, to_recv), + "+CCHRECV: {},0".format(self._fd), + "ERROR\r\n", + self._modem._default_timeout, + ) + output, error = self._modem.execute_at_command2(ciprxget) + if error == self._modem.ERR_GENERIC: + errno = utils.extract_int(output, "\+CCHRECV: {},".format(self._fd), "\r\n") + raise SIMComError(SIMComError.D_TCPIP_ERR_INFO, errno, ciprxget.cmd) + elif error == self._modem.ERR_TIMEOUT: + raise SIMComError(SIMComError.D_GENERIC, error, ciprxget.cmd) + + # prase data + recv_len = utils.extract_int(output, "\+CCHRECV: DATA,{},".format(self._fd), "\r\n") + fstr = "+CCHRECV: DATA,{},{}\r\n".format(self._fd, recv_len) + start = output.find(fstr) + len(fstr) + + self._ringio.write(output[start : start + recv_len].encode("utf-8")) + + def recv(self, bufsize) -> bytes: + return self.recvfrom(bufsize) + + def readall(self) -> bytes: + out = bytearray(self._ringio.any()) + self._ringio.readinto(out) + last_time = time.ticks_ms() + while time.ticks_diff(time.ticks_ms(), last_time) < self._timeout: + self._recv() + buf = self._ringio.read() + out.extend(buf) + + return bytes(out) + + def recvfrom(self, bufsize) -> bytes: + if bufsize == -1: + return self.readall() + + if self._ringio.any() > bufsize: + return self._ringio.read(bufsize) + + read_len = self._ringio.any() + out = bytearray(read_len) + self._ringio.readinto(out) + last_time = time.ticks_ms() + while read_len < bufsize and time.ticks_diff(time.ticks_ms(), last_time) < self._timeout: + self._recv() + buf = self._ringio.read(bufsize - read_len) + out.extend(buf) + read_len += len(buf) + + return bytes(out) + + def setsockopt(self, level, optname, value): + raise NotImplementedError + + def settimeout(self, timeout): + raise NotImplementedError + + def setblocking(self): + raise NotImplementedError + + def makefile(self, mode): + return self + + def fileno(self): + return self._fd + + def read(self, *args) -> bytes: + return self.recvfrom(args[0] if len(args) > 0 else -1) + + def readinto(self, *args) -> int: + buf = args[0] + nbytes = len(buf) + if len(args) > 1: + nbytes = args[1] if args[1] < nbytes else nbytes + + if self._ringio.any() < nbytes: + self._recv() + + return self._ringio.readinto(buf, nbytes) + + def readline(self) -> bytes: + last_time = time.ticks_ms() + while ( + self._ringio.any() == 0 and time.ticks_diff(time.ticks_ms(), last_time) < self._timeout + ): + self._recv() + return self._ringio.readline() + + def write(self, data) -> int: + return self.send(data) + + class SIM7600(Modem): # tcp/udp socket fd list _fds = [-1 for _ in range(10)] + _session_id = [-1 for _ in range(2)] + # tcp/udp socket port list _used_port = [] @@ -450,6 +713,26 @@ def __init__( ATCommand("AT+NETOPEN", "OK\r\n", "ERROR\r\n", self._default_timeout) ) + # Set the SSL version of the first SSL context + self.execute_at_command2( + ATCommand('AT+CSSLCFG="sslversion",0,4', "OK\r\n", "ERROR\r\n", self._default_timeout) + ) + + # Set the authentication mode(not verify server) of the first SSL context + self.execute_at_command2( + ATCommand('AT+CSSLCFG="authmode",0,0', "OK\r\n", "ERROR\r\n", self._default_timeout) + ) + + # Enable reporting +CCHSEND result, + self.execute_at_command2( + ATCommand("AT+CCHSET=1,1", "OK\r\n", "ERROR\r\n", self._default_timeout) + ) + + # start SSL service, activate PDP context + self.execute_at_command2( + ATCommand("AT+CCHSTART", "+CCHSTART\r\n", "ERROR\r\n", self._default_timeout) + ) + """fd/port management""" def apply_fd(self) -> int: @@ -462,6 +745,16 @@ def apply_fd(self) -> int: def release_fd(self, fd: int) -> None: self._fds[fd] = -1 + def apply_session_id(self) -> int: + for i in range(2): + if self._session_id[i] == -1: + self._session_id[i] = i + return i + return -1 + + def release_session_id(self, session_id: int) -> None: + self._session_id[session_id] = -1 + def apply_port(self) -> int: for i in range(1024, 65535): if i not in self._used_port: @@ -498,21 +791,21 @@ def getaddrinfo(self, host, port, af=0, type=0, proto=0, flags=0): def socket(self, af=socket.AF_INET, type=socket.SOCK_STREAM, proto=socket.IPPROTO_TCP): return _socket(self, af, type, proto) - # def wrap_socket( - # self, - # sock, - # server_side=False, - # key=None, - # cert=None, - # cert_reqs=CERT_NONE, - # cadata=None, - # server_hostname=None, - # do_handshake=True, - # ): - # sock.close() - # s = _sockets(self, sock._domain, sock._type, sock._proto) - # s.connect(sock._address) - # return s + def wrap_socket( + self, + sock, + # server_side=False, + # key=None, + # cert=None, + # cert_reqs=CERT_NONE, + # cadata=None, + server_hostname=None, + # do_handshake=True, + ): + sock.close() + s = _sockets(self, sock._domain, sock._type, sock._proto) + s.connect(sock._address) + return s """request method""" diff --git a/m5stack/libs/driver/simcom/toolkit/requests2.py b/m5stack/libs/driver/simcom/toolkit/requests2.py index d857c001..969d0e0a 100644 --- a/m5stack/libs/driver/simcom/toolkit/requests2.py +++ b/m5stack/libs/driver/simcom/toolkit/requests2.py @@ -94,7 +94,7 @@ def _request( try: s.connect(ai[-1]) if proto == "https:": - s = ssl.wrap_socket(s, server_hostname=host) + s = modem.wrap_socket(s, server_hostname=host) s.write(b"%s /%s HTTP/1.0\r\n" % (method, path)) if "Host" not in headers: s.write(b"Host: %s\r\n" % host) From 0ee9c15d436dfb35106ac3b0cfa9dee6a46b5fe8 Mon Sep 17 00:00:00 2001 From: lbuque Date: Fri, 27 Dec 2024 14:49:37 +0800 Subject: [PATCH 051/322] libs/driver/simcom: Support mqtt function. Signed-off-by: lbuque --- m5stack/libs/driver/simcom/common.py | 2 +- m5stack/libs/driver/simcom/sim7600.py | 292 ++++++++++-------- .../driver/simcom/toolkit/umqtt/__init__.py | 67 ++++ .../driver/simcom/toolkit/umqtt/robust.py | 57 ++++ .../driver/simcom/toolkit/umqtt/simple.py | 224 ++++++++++++++ m5stack/libs/driver/simcom/utils.py | 68 ++-- 6 files changed, 565 insertions(+), 145 deletions(-) create mode 100644 m5stack/libs/driver/simcom/toolkit/umqtt/__init__.py create mode 100644 m5stack/libs/driver/simcom/toolkit/umqtt/robust.py create mode 100644 m5stack/libs/driver/simcom/toolkit/umqtt/simple.py diff --git a/m5stack/libs/driver/simcom/common.py b/m5stack/libs/driver/simcom/common.py index 6c2e4960..17f7d944 100644 --- a/m5stack/libs/driver/simcom/common.py +++ b/m5stack/libs/driver/simcom/common.py @@ -314,4 +314,4 @@ def response_at_command2( # output = output[:-1] # Return - return (output.decode("utf-8"), error) + return (output, error) diff --git a/m5stack/libs/driver/simcom/sim7600.py b/m5stack/libs/driver/simcom/sim7600.py index 61a1f17f..66cdcd77 100644 --- a/m5stack/libs/driver/simcom/sim7600.py +++ b/m5stack/libs/driver/simcom/sim7600.py @@ -6,7 +6,7 @@ from .common import ATCommand from . import utils from .toolkit import requests2 -import re +from .toolkit import umqtt import socket import micropython import time @@ -109,7 +109,8 @@ def __init__(self, modem, af=AF_INET, type=SOCK_STREAM, proto=IPPROTO_TCP): self._state = self._STATE_CLOSE self._ringio = micropython.RingIO(1500) - self._timeout = 500 + self._timeout = 2000 + self.blocking = False def __del__(self): self.close() @@ -118,7 +119,7 @@ def close(self) -> None: if self._fd == -1: return close = ATCommand( - "AT+CIPCLOSE={}".format(self._fd), "OK\r\n", "ERROR\r\n", self._modem._default_timeout + "AT+CIPCLOSE={}".format(self._fd), "OK", "ERROR", self._modem._default_timeout ) self._modem.execute_at_command2(close) self._modem.release_fd(self._fd) @@ -152,7 +153,7 @@ def connect(self, address): address[1], ), "+CIPOPEN", - "ERROR\r\n", + "ERROR", 120000, # Maximum Response Time ) output, error = self._modem.execute_at_command2(cipstart) @@ -162,27 +163,24 @@ def connect(self, address): elif error == self._modem.ERR_TIMEOUT: raise SIMComError(SIMComError.D_GENERIC, error, cipstart.cmd) elif error == self._modem.ERR_GENERIC: - err = utils.extract_int(output, "\+CIPOPEN: {},".format(self._fd), "\r\n") + err = utils.extract_int(output, "+CIPOPEN: {},".format(self._fd), "\r\n") raise SIMComError(SIMComError.D_TCPIP_ERR, err, cipstart.cmd) def send(self, data: bytes | str | bytearray) -> int: # data type check - if isinstance(data, str): - data = data.encode("utf-8") - if isinstance(data, bytearray): - data = bytes(data) + data = utils.converter(data, bytes) # send cmd to_send = len(data) cipsend = ATCommand( "AT+CIPSEND={},{}".format(self._fd, to_send), ">", - "ERROR\r\n", + "ERROR", self._modem._default_timeout, ) output, error = self._modem.execute_at_command2(cipsend, line_end="") if error == self._modem.ERR_GENERIC: - err = utils.extract_int(output, "\+CIPSEND: ", "\r\n") + err = utils.extract_int(output, "+CIPSEND: ", "\r\n") raise SIMComError(SIMComError.D_TCPIP_ERR, err, cipsend.cmd) elif error == self._modem.ERR_TIMEOUT: raise SIMComError(SIMComError.D_GENERIC, error, cipsend.cmd) @@ -193,18 +191,18 @@ def send(self, data: bytes | str | bytearray) -> int: cipsend = ATCommand( "AT+CIPSEND={},{}".format(self._fd, to_send), "+CIPSEND:", - "ERROR\r\n", + "ERROR", self._modem._default_timeout, ) output, error = self._modem.response_at_command2(cipsend) if error == self._modem.ERR_GENERIC: - err = utils.extract_int(output, "\+CIPERROR: ", "\r\n") + err = utils.extract_int(output, "+CIPERROR: ", "\r\n") raise SIMComError(SIMComError.D_TCPIP_ERR, err, cipsend.cmd) elif error == self._modem.ERR_TIMEOUT: raise SIMComError(SIMComError.D_GENERIC, error, cipsend.cmd) elif error == self._modem.ERR_NONE: cnf = utils.extract_text( - output, "\+CIPSEND: {},{},".format(self._fd, to_send), "\r\n" + output, "+CIPSEND: {},{},".format(self._fd, to_send), "\r\n" ) sented = int(cnf) if cnf else 0 return to_send @@ -221,23 +219,20 @@ def sendto(self, data: bytes | str | bytearray, address) -> int: "".join(['"', self._proto_type.get(self._proto, ""), '"']), self._local_port, ), - "OK\r\n", - "ERROR\r\n", + "OK", + "ERROR", self._modem._default_timeout, ) output, error = self._modem.execute_at_command2(cipopen) if error == self._modem.ERR_GENERIC: - errno = utils.extract_int(output, "\+CIPOPEN: ", "\r\n") + errno = utils.extract_int(output, "+CIPOPEN: ", "\r\n") raise SIMComError(SIMComError.D_TCPIP_ERR, errno, cipopen.cmd) elif error == self._modem.ERR_TIMEOUT: raise SIMComError(SIMComError.D_GENERIC, error, cipopen.cmd) self._state = self._STATE_OPEN # data type check - if isinstance(data, str): - data = data.encode("utf-8") - if isinstance(data, bytearray): - data = bytes(data) + data = utils.converter(data, bytes) # send cmd to_send = len(data) @@ -246,12 +241,12 @@ def sendto(self, data: bytes | str | bytearray, address) -> int: self._fd, to_send, "".join(['"', address[0], '"']), address[1] ), ">", - "ERROR\r\n", + "ERROR", self._modem._default_timeout, ) output, error = self._modem.execute_at_command2(cipsend, line_end="") if error == self._modem.ERR_GENERIC: - errno = utils.extract_int(output, "\+CIPSEND: ", "\r\n") + errno = utils.extract_int(output, "+CIPSEND: ", "\r\n") raise SIMComError(SIMComError.D_TCPIP_ERR, errno, cipsend.cmd) elif error == self._modem.ERR_TIMEOUT: raise SIMComError(SIMComError.D_GENERIC, error, cipsend.cmd) @@ -262,18 +257,18 @@ def sendto(self, data: bytes | str | bytearray, address) -> int: cipsend = ATCommand( "AT+CIPSEND={},{}".format(self._fd, to_send), "+CIPSEND:", - "ERROR\r\n", + "ERROR", self._modem._default_timeout, ) output, error = self._modem.response_at_command2(cipsend) if error == self._modem.ERR_GENERIC: - errno = utils.extract_int(output, "\+CIPERROR: ", "\r\n") + errno = utils.extract_int(output, "+CIPERROR: ", "\r\n") raise SIMComError(SIMComError.D_TCPIP_ERR, errno, cipsend.cmd) elif error == self._modem.ERR_TIMEOUT: raise SIMComError(SIMComError.D_GENERIC, error, cipsend.cmd) else: cnf = utils.extract_text( - output, "\+CIPSEND: {},{},".format(self._fd, to_send), "\r\n" + output, "+CIPSEND: {},{},".format(self._fd, to_send), "\r\n" ) sented = int(cnf) if cnf else 0 return to_send @@ -281,18 +276,18 @@ def sendto(self, data: bytes | str | bytearray, address) -> int: def _recv(self) -> None: ciprxget = ATCommand( "AT+CIPRXGET=4,{}".format(self._fd), - "OK\r\n", - "ERROR\r\n", + "OK", + "ERROR", self._modem._default_timeout, ) output, error = self._modem.execute_at_command2(ciprxget) if error == self._modem.ERR_GENERIC: - errno = utils.extract_int(output, "\+IP ERROR: ", "\r\n") + errno = utils.extract_int(output, "+IP ERROR: ", "\r\n") raise SIMComError(SIMComError.D_TCPIP_ERR_INFO, errno, ciprxget.cmd) elif error == self._modem.ERR_TIMEOUT: raise SIMComError(SIMComError.D_GENERIC, error, ciprxget.cmd) - to_recv = int(utils.extract_text(output, "\+CIPRXGET: 4,{},".format(self._fd), "\r\n")) + to_recv = int(utils.extract_text(output, "+CIPRXGET: 4,{},".format(self._fd), "\r\n")) if to_recv == 0: return @@ -300,30 +295,32 @@ def _recv(self) -> None: ciprxget = ATCommand( "AT+CIPRXGET=2,{},{}".format(self._fd, to_recv), - "OK\r\n", - "ERROR\r\n", + "OK", + "ERROR", self._modem._default_timeout, ) output, error = self._modem.execute_at_command2(ciprxget) if error == self._modem.ERR_GENERIC: - errno = utils.extract_int(output, "\+IP ERROR: ", "\r\n") + errno = utils.extract_int(output, "+IP ERROR: ", "\r\n") raise SIMComError(SIMComError.D_TCPIP_ERR_INFO, errno, ciprxget.cmd) elif error == self._modem.ERR_TIMEOUT: raise SIMComError(SIMComError.D_GENERIC, error, ciprxget.cmd) # prase data - read_len = utils.extract_text(output, "\+CIPRXGET: 2,{},".format(self._fd), ",") + read_len = utils.extract_text(output, "+CIPRXGET: 2,{},".format(self._fd), ",") read_len = int(read_len) if read_len else 0 rest_len = utils.extract_text( - output, "\+CIPRXGET: 2,{},{},".format(self._fd, read_len), "\r\n" + output, "+CIPRXGET: 2,{},{},".format(self._fd, read_len), "\r\n" ) rest_len = int(rest_len) if rest_len else 0 - fstr = "+CIPRXGET: 2,{},{},{}\r\n".format(self._fd, read_len, rest_len) + fstr = utils.converter( + "+CIPRXGET: 2,{},{},{}\r\n".format(self._fd, read_len, rest_len), type(output) + ) start = output.find(fstr) + len(fstr) - self._ringio.write(output[start : start + read_len].encode("utf-8")) + self._ringio.write(output[start : start + read_len]) - def recv(self, bufsize) -> bytes: + def recv(self, bufsize) -> bytes | None: return self.recvfrom(bufsize) def readall(self) -> bytes: @@ -337,7 +334,7 @@ def readall(self) -> bytes: return bytes(out) - def recvfrom(self, bufsize) -> bytes: + def recvfrom(self, bufsize: int) -> bytes | None: if bufsize == -1: return self.readall() @@ -354,33 +351,24 @@ def recvfrom(self, bufsize) -> bytes: out.extend(buf) read_len += len(buf) + # https://docs.python.org/3.4/library/io.html#io.RawIOBase.read + # "If the object is in non-blocking mode and no bytes are available, + # None is returned." + # This is actually very weird, as naive truth check will treat + # this as EOF. + if self.blocking is False and read_len == 0: + return None + return bytes(out) def setsockopt(self, level, optname, value): - raise NotImplementedError + print("setsockopt is not implemented") def settimeout(self, timeout): - if timeout is None: - self._timeout = 500 - - cipccfg = ATCommand("AT+CIPCCFG?", "OK\r\n", "ERROR\r\n", self._modem._default_timeout) - output, error = self._modem.execute_at_command2(cipccfg) - if error == self._modem.ERR_TIMEOUT: - raise SIMComError(SIMComError.D_GENERIC, error, cipccfg.cmd) + self._timeout = 2000 if timeout is None else timeout - params = utils.extract_number(output) - - cipccfg = ATCommand( - "AT+CIPCCFG={},{},{},{},{},{},{}".format( - params[0], params[1], params[2], params[3], params[4], params[5], self._timeout - ), - "OK\r\n", - "ERROR\r\n", - self._modem._default_timeout, - ) - - def setblocking(self): - raise NotImplementedError + def setblocking(self, flag): + self.blocking = flag def makefile(self, mode): return self @@ -388,7 +376,7 @@ def makefile(self, mode): def fileno(self): return self._fd - def read(self, *args) -> bytes: + def read(self, *args) -> bytes | None: return self.recvfrom(args[0] if len(args) > 0 else -1) def readinto(self, *args) -> int: @@ -410,8 +398,21 @@ def readline(self) -> bytes: self._recv() return self._ringio.readline() - def write(self, data) -> int: - return self.send(data) + def write(self, *args) -> int: + data = args[0] + l = len(data) + max_len = len(data) + off = 0 + if len(args) == 2: + max_len = args[1] + if len(args) == 3: + off = args[1] + max_len = args[2] + if off > l: + off = l + l = l - off + max_len = l if l < max_len else max_len + return self.send(data[off : off + max_len]) class _sockets(_socket): @@ -446,7 +447,8 @@ def __init__(self, modem, af=AF_INET, type=SOCK_STREAM, proto=IPPROTO_TCP): self._state = self._STATE_CLOSE self._ringio = micropython.RingIO(1500) - self._timeout = 500 + self._timeout = 2000 + self.blocking = False def __del__(self): self.close() @@ -455,7 +457,7 @@ def close(self) -> None: if self._fd == -1: return close = ATCommand( - "AT+CCHCLOSE={}".format(self._fd), "OK\r\n", "ERROR\r\n", self._modem._default_timeout + "AT+CCHCLOSE={}".format(self._fd), "OK", "ERROR", self._modem._default_timeout ) self._modem.execute_at_command2(close) self._modem.release_session_id(self._fd) @@ -483,8 +485,8 @@ def connect(self, address): # Set the first SSL context to be used in the SSL connection at = ATCommand( "AT+CCHSSLCFG={},0".format(self._fd), - "OK\r\n", - "ERROR\r\n", + "OK", + "ERROR", self._modem._default_timeout, ) self._modem.execute_at_command2(at) @@ -496,7 +498,7 @@ def connect(self, address): self._fd, "".join(['"', address[0], '"']), address[1], 2 ), "+CCHOPEN", - "ERROR\r\n", + "ERROR", 120000, # Maximum Response Time ) output, error = self._modem.execute_at_command2(cipstart) @@ -506,27 +508,24 @@ def connect(self, address): elif error == self._modem.ERR_TIMEOUT: raise SIMComError(SIMComError.D_GENERIC, error, cipstart.cmd) elif error == self._modem.ERR_GENERIC: - err = utils.extract_int(output, "\+CCHOPEN: {},".format(self._fd), "\r\n") + err = utils.extract_int(output, "+CCHOPEN: {},".format(self._fd), "\r\n") raise SIMComError(SIMComError.D_TCPIP_ERR, err, cipstart.cmd) def send(self, data: bytes | str | bytearray) -> int: # data type check - if isinstance(data, str): - data = data.encode("utf-8") - if isinstance(data, bytearray): - data = bytes(data) + data = utils.converter(data, bytes) # send cmd to_send = len(data) cipsend = ATCommand( "AT+CCHSEND={},{}".format(self._fd, to_send), ">", - "ERROR\r\n", + "ERROR", self._modem._default_timeout, ) output, error = self._modem.execute_at_command2(cipsend, line_end="") if error == self._modem.ERR_GENERIC: - err = utils.extract_int(output, "\+CCHSEND: ", "\r\n") + err = utils.extract_int(output, "+CCHSEND: ", "\r\n") raise SIMComError(SIMComError.D_TCPIP_ERR, err, cipsend.cmd) elif error == self._modem.ERR_TIMEOUT: raise SIMComError(SIMComError.D_GENERIC, error, cipsend.cmd) @@ -537,12 +536,12 @@ def send(self, data: bytes | str | bytearray) -> int: cipsend = ATCommand( "AT+CCHSEND={},{}".format(self._fd, to_send), "+CCHSEND:", - "ERROR\r\n", + "ERROR", self._modem._default_timeout, ) output, error = self._modem.response_at_command2(cipsend) if error == self._modem.ERR_GENERIC: - err = utils.extract_int(output, "\+CIPERROR: ", "\r\n") + err = utils.extract_int(output, "+CIPERROR: ", "\r\n") raise SIMComError(SIMComError.D_TCPIP_ERR, err, cipsend.cmd) elif error == self._modem.ERR_TIMEOUT: raise SIMComError(SIMComError.D_GENERIC, error, cipsend.cmd) @@ -563,14 +562,14 @@ def _recv(self) -> None: ) output, error = self._modem.execute_at_command2(cchrecv) if error == self._modem.ERR_GENERIC: - errno = utils.extract_int(output, "\+IP ERROR: ", "\r\n") + errno = utils.extract_int(output, "+IP ERROR: ", "\r\n") raise SIMComError(SIMComError.D_TCPIP_ERR_INFO, errno, cchrecv.cmd) elif error == self._modem.ERR_TIMEOUT: raise SIMComError(SIMComError.D_GENERIC, error, cchrecv.cmd) - cache_len_0 = int(utils.extract_text(output, "\+CCHRECV: LEN,", ",")) + cache_len_0 = int(utils.extract_text(output, "+CCHRECV: LEN,", ",")) cache_len_1 = int( - utils.extract_text(output, "\+CCHRECV: LEN,{},".format(cache_len_0), "\r\n") + utils.extract_text(output, "+CCHRECV: LEN,{},".format(cache_len_0), "\r\n") ) to_recv = cache_len_0 if self._fd == 0 else cache_len_1 if to_recv == 0: @@ -584,22 +583,22 @@ def _recv(self) -> None: ciprxget = ATCommand( "AT+CCHRECV={},{}".format(self._fd, to_recv), "+CCHRECV: {},0".format(self._fd), - "ERROR\r\n", + "ERROR", self._modem._default_timeout, ) output, error = self._modem.execute_at_command2(ciprxget) if error == self._modem.ERR_GENERIC: - errno = utils.extract_int(output, "\+CCHRECV: {},".format(self._fd), "\r\n") + errno = utils.extract_int(output, "+CCHRECV: {},".format(self._fd), "\r\n") raise SIMComError(SIMComError.D_TCPIP_ERR_INFO, errno, ciprxget.cmd) elif error == self._modem.ERR_TIMEOUT: raise SIMComError(SIMComError.D_GENERIC, error, ciprxget.cmd) # prase data - recv_len = utils.extract_int(output, "\+CCHRECV: DATA,{},".format(self._fd), "\r\n") - fstr = "+CCHRECV: DATA,{},{}\r\n".format(self._fd, recv_len) + recv_len = utils.extract_int(output, "+CCHRECV: DATA,{},".format(self._fd), "\r\n") + fstr = utils.converter("+CCHRECV: DATA,{},{}\r\n".format(self._fd, recv_len), type(output)) start = output.find(fstr) + len(fstr) - self._ringio.write(output[start : start + recv_len].encode("utf-8")) + self._ringio.write(output[start : start + recv_len]) def recv(self, bufsize) -> bytes: return self.recvfrom(bufsize) @@ -632,16 +631,27 @@ def recvfrom(self, bufsize) -> bytes: out.extend(buf) read_len += len(buf) + # https://docs.python.org/3.4/library/io.html#io.RawIOBase.read + # "If the object is in non-blocking mode and no bytes are available, + # None is returned." + # This is actually very weird, as naive truth check will treat + # this as EOF. + if self.blocking is False and read_len == 0: + return None + return bytes(out) def setsockopt(self, level, optname, value): - raise NotImplementedError + print("setsockopt is not implemented") + return None def settimeout(self, timeout): - raise NotImplementedError + print("settimeout is not implemented") + return None - def setblocking(self): - raise NotImplementedError + def setblocking(self, flag): + self.blocking = flag + return None def makefile(self, mode): return self @@ -698,41 +708,47 @@ def __init__( self._default_timeout = 5000 # disable echo - self.execute_at_command2(ATCommand("ATE0", "OK\r\n", "ERROR\r\n", self._default_timeout)) + self.execute_at_command2(ATCommand("ATE0", "OK", "ERROR", self._default_timeout)) # buffer access mode - self.execute_at_command2( - ATCommand("AT+CIPRXGET=1", "OK\r\n", "ERROR\r\n", self._default_timeout) - ) + self.execute_at_command2(ATCommand("AT+CIPRXGET=1", "OK", "ERROR", self._default_timeout)) # No transparent transmission mode - self.execute_at_command2( - ATCommand("AT+CIPMODE=0", "OK\r\n", "ERROR\r\n", self._default_timeout) - ) + self.execute_at_command2(ATCommand("AT+CIPMODE=0", "OK", "ERROR", self._default_timeout)) # open network - self.execute_at_command2( - ATCommand("AT+NETOPEN", "OK\r\n", "ERROR\r\n", self._default_timeout) - ) + self.execute_at_command2(ATCommand("AT+NETOPEN", "OK", "ERROR", self._default_timeout)) # Set the SSL version of the first SSL context self.execute_at_command2( - ATCommand('AT+CSSLCFG="sslversion",0,4', "OK\r\n", "ERROR\r\n", self._default_timeout) + ATCommand('AT+CSSLCFG="sslversion",0,4', "OK", "ERROR", self._default_timeout) ) # Set the authentication mode(not verify server) of the first SSL context self.execute_at_command2( - ATCommand('AT+CSSLCFG="authmode",0,0', "OK\r\n", "ERROR\r\n", self._default_timeout) + ATCommand('AT+CSSLCFG="authmode",0,0', "OK", "ERROR", self._default_timeout) ) # Enable reporting +CCHSEND result, - self.execute_at_command2( - ATCommand("AT+CCHSET=1,1", "OK\r\n", "ERROR\r\n", self._default_timeout) - ) + self.execute_at_command2(ATCommand("AT+CCHSET=1,1", "OK", "ERROR", self._default_timeout)) # start SSL service, activate PDP context self.execute_at_command2( - ATCommand("AT+CCHSTART", "+CCHSTART\r\n", "ERROR\r\n", self._default_timeout) + ATCommand("AT+CCHSTART", "+CCHSTART", "ERROR", self._default_timeout) ) + def deinit(self): + for i in range(10): + if self._fds[i] != -1: + self.execute_at_command2( + ATCommand("AT+CIPCLOSE={}".format(self._fds[i]), "OK", "ERROR", 10000) + ) + self._fds[i] = -1 + for i in range(2): + if self._session_id[i] != -1: + self.execute_at_command2( + ATCommand("AT+CCHCLOSE={}".format(self._session_id[i]), "OK", "ERROR", 10000) + ) + self._session_id[i] = -1 + """fd/port management""" def apply_fd(self) -> int: @@ -774,7 +790,7 @@ def getaddrinfo(self, host, port, af=0, type=0, proto=0, flags=0): """ res = [] - at = ATCommand('AT+CDNSGIP="{}"'.format(host), "OK\r\n", "ERROR\r\n", 10000) + at = ATCommand('AT+CDNSGIP="{}"'.format(host), "OK", "ERROR", 10000) output, error = self.execute_at_command2(at) ip = "0.0.0.0" @@ -785,7 +801,15 @@ def getaddrinfo(self, host, port, af=0, type=0, proto=0, flags=0): elif error == self.ERR_GENERIC: raise SIMComError(SIMComError.D_DNS_ERROR_CODE, 10, at.cmd) - res.append((socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP, host, (ip, port))) + res.append( + ( + socket.AF_INET, + socket.SOCK_STREAM, + socket.IPPROTO_TCP, + host, + (utils.converter(ip, str), port), + ) + ) return res def socket(self, af=socket.AF_INET, type=socket.SOCK_STREAM, proto=socket.IPPROTO_TCP): @@ -843,6 +867,31 @@ def patch(self, url, **kw): def delete(self, url, **kw): return self.request("DELETE", url, **kw) + """mqtt method""" + + def MQTTClient( # noqa: N802 + self, + client_id, + server, + port=0, + user=None, + password=None, + keepalive=0, + ssl=False, + ssl_params={}, + ): + return umqtt.MQTTClient( + self, + client_id, + server, + port=port, + user=user, + password=password, + keepalive=keepalive, + ssl=ssl, + ssl_params=ssl_params, + ) + """common method""" def check_modem_is_ready(self) -> bool: @@ -863,7 +912,7 @@ def check_sim_is_connected(self) -> bool: output, error = self.execute_at_command2(at) if error: return False - return True if output.find("READY") != -1 else False + return True if output.find(utils.converter("READY", type(output))) != -1 else False REG_NO_RESULT = -1 REG_UNREGISTERED = 0 @@ -880,7 +929,7 @@ def get_network_registration_status(self) -> int: if error: return self.REG_NO_RESULT n = utils.extract_int(output, "+CGREG: ", ",") - stat = utils.extract_int(output, "+CGREG: {}".format(n), "\r\n" if n == 0 else ",") + stat = utils.extract_int(output, "+CGREG: {},".format(n), "\r\n" if n == 0 else ",") return stat def get_signal_strength(self) -> int: @@ -903,7 +952,8 @@ def get_model_identification(self) -> str: output, error = self.execute_at_command2(cgmm) if error: return "" - return utils.extract_text(output, "\r\n", "\r\n") + id = utils.extract_text(output, "\r\n", "\r\n") + return utils.converter(id, str) def get_gprs_network_status(self) -> bool: # Get attach or detach from the GPRS network @@ -918,10 +968,7 @@ def set_gprs_network_state(self, enable=1) -> bool: # Set attach or detach from the GPRS network cgatt = ATCommand("AT+CGATT={0}".format(enable), "OK", "ERROR", self._default_timeout) output, error = self.execute_at_command2(cgatt) - if error: - return False - state = utils.extract_int(output, "+CGATT: ", "\r\n") - return True if state == enable else False + return not error def set_pdp_context(self, apn=""): # Set Define PDP Context @@ -938,8 +985,8 @@ def get_show_pdp_address(self, cid) -> str: output, error = self.execute_at_command2(cgpaddr) if error: return "0.0.0.0" - address = utils.extract_text(output, '+CGPADDR: {},"'.format(cid), '"') - return address + address = utils.extract_text(output, "+CGPADDR: {},".format(cid), "\r\n") + return utils.converter(address, str) def get_selected_operator(self) -> int: # Get selected operator. @@ -949,13 +996,14 @@ def get_selected_operator(self) -> int: return 0 return utils.extract_int(output, "+COPS: ", ",") - def get_imei_number(self): + def get_imei_number(self) -> str: # Request TA Serial Number Identification(IMEI) - cgsn = ATCommand("AT+CGSN=1", "OK", "ERROR", self._default_timeout) + cgsn = ATCommand("AT+CGSN", "OK", "ERROR", self._default_timeout) output, error = self.execute_at_command2(cgsn) if error: return "" - return utils.extract_text(output, "\r\n", "\r\n") + imei = utils.extract_text(output, "\r\n", "\r\n") + return utils.converter(imei, str) def get_ccid_number(self) -> str: # Show ICCID @@ -963,7 +1011,8 @@ def get_ccid_number(self) -> str: output, error = self.execute_at_command2(iccid) if error: return "" - return utils.extract_text(output, "+ICCID: ", "\r\n") + ccid = utils.extract_text(output, "+ICCID: ", "\r\n") + return utils.converter(ccid, str) PDP_PARAM_IP = 1 PDP_PARAM_APN = 2 @@ -977,4 +1026,5 @@ def get_pdp_context_dynamic_parameters(self, param=1) -> str: output, error = self.execute_at_command2(cgdcont) if error: return "" - return utils.extract_text(output, '+CGDCONT: 1,"IP","', '"') + anp = utils.extract_text(output, '+CGDCONT: 1,"IP","', '"') + return utils.converter(anp, str) diff --git a/m5stack/libs/driver/simcom/toolkit/umqtt/__init__.py b/m5stack/libs/driver/simcom/toolkit/umqtt/__init__.py new file mode 100644 index 00000000..b5455b9f --- /dev/null +++ b/m5stack/libs/driver/simcom/toolkit/umqtt/__init__.py @@ -0,0 +1,67 @@ +# SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +from . import robust +from micropython import schedule + + +class MQTTClient(robust.MQTTClient): + def __init__( + self, + modem, + client_id, + server, + port=0, + user=None, + password=None, + keepalive=0, + ssl=False, + ssl_params={}, + ): + ssl_params1 = ssl_params + if ssl: + pass + key_path = ssl_params1.get("key", None) + key_value = self._load_file(key_path) + if key_value: + ssl_params1["key"] = key_value + + cert_path = ssl_params1.get("cert", None) + cert_value = self._load_file(cert_path) + if cert_value: + ssl_params1["cert"] = cert_value + + super().__init__( + modem, client_id, server, port, user, password, keepalive, ssl, ssl_params1 + ) + self.set_callback(self._callback) + self._topics = {} + + def _callback(self, topic, msg): + if isinstance(topic, bytes): + handler = self._topics.get(topic.decode()) + if handler is None: + handler = self._topics.get(topic) + elif isinstance(topic, str): + handler = self._topics.get(topic) + if handler is None: + handler = self._topics.get(topic.encode()) + + if handler is not None: + schedule(handler, (topic, msg)) + + def subscribe(self, topic, handler, qos=0): + self._topics[topic] = handler + return super().subscribe(topic, qos) + + @staticmethod + def _load_file(path): + if isinstance(path, str) and path.startswith("/flash"): + try: + with open(path, "r") as f: + return f.read() + except: + return None + else: + return None diff --git a/m5stack/libs/driver/simcom/toolkit/umqtt/robust.py b/m5stack/libs/driver/simcom/toolkit/umqtt/robust.py new file mode 100644 index 00000000..e51fdf5e --- /dev/null +++ b/m5stack/libs/driver/simcom/toolkit/umqtt/robust.py @@ -0,0 +1,57 @@ +# SPDX-FileCopyrightText: Copyright (c) 2013-2014 micropython-lib contributors +# +# SPDX-License-Identifier: MIT + +import utime +from . import simple + + +class MQTTClient(simple.MQTTClient): + DELAY = 2 + DEBUG = False + + def delay(self, i): + utime.sleep(self.DELAY) + + def log(self, in_reconnect, e): + if self.DEBUG: + if in_reconnect: + print("mqtt reconnect: %r" % e) + else: + print("mqtt: %r" % e) + + def reconnect(self): + i = 0 + while 1: + try: + return super().connect(False) + except OSError as e: + self.log(True, e) + i += 1 + self.delay(i) + + def publish(self, topic, msg, retain=False, qos=0): + while 1: + try: + return super().publish(topic, msg, retain, qos) + except OSError as e: + self.log(False, e) + self.reconnect() + + def wait_msg(self): + while 1: + try: + return super().wait_msg() + except OSError as e: + self.log(False, e) + self.reconnect() + + def check_msg(self, attempts=2): + while attempts: + self.sock.setblocking(False) + try: + return super().wait_msg() + except OSError as e: + self.log(False, e) + self.reconnect() + attempts -= 1 diff --git a/m5stack/libs/driver/simcom/toolkit/umqtt/simple.py b/m5stack/libs/driver/simcom/toolkit/umqtt/simple.py new file mode 100644 index 00000000..cb4d5764 --- /dev/null +++ b/m5stack/libs/driver/simcom/toolkit/umqtt/simple.py @@ -0,0 +1,224 @@ +# SPDX-FileCopyrightText: Copyright (c) 2013-2014 micropython-lib contributors +# +# SPDX-License-Identifier: MIT + +import socket +import struct + + +class MQTTException(Exception): + pass + + +class MQTTClient: + def __init__( + self, + modem, + client_id, + server, + port=0, + user=None, + password=None, + keepalive=0, + ssl=False, + ssl_params={}, + ): + if port == 0: + port = 8883 if ssl else 1883 + self.client_id = client_id + self.sock = None + self.server = server + self.port = port + self.ssl = ssl + self.ssl_params = ssl_params + self.pid = 0 + self.cb = None + self.user = user + self.pswd = password + self.keepalive = keepalive + self.lw_topic = None + self.lw_msg = None + self.lw_qos = 0 + self.lw_retain = False + self.modem = modem + + def _send_str(self, s): + self.sock.write(struct.pack("!H", len(s))) + self.sock.write(s) + + def _recv_len(self): + n = 0 + sh = 0 + while 1: + b = self.sock.read(1)[0] + n |= (b & 0x7F) << sh + if not b & 0x80: + return n + sh += 7 + + def set_callback(self, f): + self.cb = f + + def set_last_will(self, topic, msg, retain=False, qos=0): + assert 0 <= qos <= 2 + assert topic + self.lw_topic = topic + self.lw_msg = msg + self.lw_qos = qos + self.lw_retain = retain + + def connect(self, clean_session=True): + self.sock = self.modem.socket() + addr = self.modem.getaddrinfo(self.server, self.port)[0][-1] + self.sock.connect(addr) + if self.ssl: + import ssl + + self.sock = self.modem.wrap_socket(self.sock, **self.ssl_params) + premsg = bytearray(b"\x10\0\0\0\0\0") + msg = bytearray(b"\x04MQTT\x04\x02\0\0") + + sz = 10 + 2 + len(self.client_id) + msg[6] = clean_session << 1 + if self.user is not None: + sz += 2 + len(self.user) + 2 + len(self.pswd) + msg[6] |= 0xC0 + if self.keepalive: + assert self.keepalive < 65536 + msg[7] |= self.keepalive >> 8 + msg[8] |= self.keepalive & 0x00FF + if self.lw_topic: + sz += 2 + len(self.lw_topic) + 2 + len(self.lw_msg) + msg[6] |= 0x4 | (self.lw_qos & 0x1) << 3 | (self.lw_qos & 0x2) << 3 + msg[6] |= self.lw_retain << 5 + + i = 1 + while sz > 0x7F: + premsg[i] = (sz & 0x7F) | 0x80 + sz >>= 7 + i += 1 + premsg[i] = sz + + self.sock.write(premsg, i + 2) + self.sock.write(msg) + self._send_str(self.client_id) + if self.lw_topic: + self._send_str(self.lw_topic) + self._send_str(self.lw_msg) + if self.user is not None: + self._send_str(self.user) + self._send_str(self.pswd) + resp = self.sock.read(4) + assert resp[0] == 0x20 and resp[1] == 0x02 + if resp[3] != 0: + raise MQTTException(resp[3]) + return resp[2] & 1 + + def disconnect(self): + self.sock.write(b"\xe0\0") + self.sock.close() + + def ping(self): + self.sock.write(b"\xc0\0") + + def isconnected(self): + try: + return self.sock.write(b"\xc0\0") == 2 + except OSError: + return False + + def publish(self, topic, msg, retain=False, qos=0): + pkt = bytearray(b"\x30\0\0\0") + pkt[0] |= qos << 1 | retain + sz = 2 + len(topic) + len(msg) + if qos > 0: + sz += 2 + assert sz < 2097152 + i = 1 + while sz > 0x7F: + pkt[i] = (sz & 0x7F) | 0x80 + sz >>= 7 + i += 1 + pkt[i] = sz + self.sock.write(pkt, i + 1) + self._send_str(topic) + if qos > 0: + self.pid += 1 + pid = self.pid + struct.pack_into("!H", pkt, 0, pid) + self.sock.write(pkt, 2) + self.sock.write(msg) + if qos == 1: + while 1: + op = self.wait_msg() + if op == 0x40: + sz = self.sock.read(1) + assert sz == b"\x02" + rcv_pid = self.sock.read(2) + rcv_pid = rcv_pid[0] << 8 | rcv_pid[1] + if pid == rcv_pid: + return + elif qos == 2: + assert 0 + + def subscribe(self, topic, qos=0): + assert self.cb is not None, "Subscribe callback is not set" + pkt = bytearray(b"\x82\0\0\0") + self.pid += 1 + struct.pack_into("!BH", pkt, 1, 2 + 2 + len(topic) + 1, self.pid) + self.sock.write(pkt) + self._send_str(topic) + self.sock.write(qos.to_bytes(1, "little")) + while 1: + op = self.wait_msg() + if op == 0x90: + resp = self.sock.read(4) + # print(resp) + assert resp[1] == pkt[2] and resp[2] == pkt[3] + if resp[3] == 0x80: + raise MQTTException(resp[3]) + return + + # Wait for a single incoming MQTT message and process it. + # Subscribed messages are delivered to a callback previously + # set by .set_callback() method. Other (internal) MQTT + # messages processed internally. + def wait_msg(self): + res = self.sock.read(1) + self.sock.setblocking(True) + if res is None: + return None + if res == b"": + raise OSError(-1) + if res == b"\xd0": # PINGRESP + sz = self.sock.read(1)[0] + assert sz == 0 + return None + op = res[0] + if op & 0xF0 != 0x30: + return op + sz = self._recv_len() + topic_len = self.sock.read(2) + topic_len = (topic_len[0] << 8) | topic_len[1] + topic = self.sock.read(topic_len) + sz -= topic_len + 2 + if op & 6: + pid = self.sock.read(2) + pid = pid[0] << 8 | pid[1] + sz -= 2 + msg = self.sock.read(sz) + self.cb(topic, msg) + if op & 6 == 2: + pkt = bytearray(b"\x40\x02\0\0") + struct.pack_into("!H", pkt, 2, pid) + self.sock.write(pkt) + elif op & 6 == 4: + assert 0 + return op + + # Checks whether a pending message from server is available. + # If not, returns immediately with None. Otherwise, does + # the same processing as wait_msg. + def check_msg(self): + self.sock.setblocking(False) + return self.wait_msg() diff --git a/m5stack/libs/driver/simcom/utils.py b/m5stack/libs/driver/simcom/utils.py index c79f7fa4..66040ddb 100644 --- a/m5stack/libs/driver/simcom/utils.py +++ b/m5stack/libs/driver/simcom/utils.py @@ -2,41 +2,63 @@ # # SPDX-License-Identifier: MIT -import re import time -def extract_text(text, start_str, end_str) -> str: - # print("text:", repr(text)) - # print("start_str:", repr(start_str)) - # print("end_str:", repr(start_str)) - pattern = re.compile(f"{start_str}(.*?){end_str}") - match = re.search(pattern, text) - return match.group(1) if match else None +def converter(data, type): + if isinstance(data, type): + return data + if isinstance(data, str): + if type == bytes: + return data.encode("utf-8") + if type == bytearray: + return bytearray(data, "utf-8") -def extract_int(text, start_str, end_str) -> int: - pattern = re.compile(f"{start_str}(.*?){end_str}") - match = re.search(pattern, text) - return int(match.group(1)) if match else None + if isinstance(data, bytes): + if type == str: + return data.decode("utf-8") + if type == bytearray: + return bytearray(data) + if isinstance(data, bytearray): + if type == str: + return str(data, "utf-8") + if type == bytes: + return bytes(data) -def extract_number(text) -> list: - if isinstance(text, bytes): - text = text.decode("utf-8") - pattern = r"\d+(?:,\d+)*" - match = re.search(pattern, text) - if match: - return [int(x) for x in match.group(0).split(",")] - return [] + +def extract_text(text, beg, end) -> str | bytes | bytearray: + # print("text:", repr(text), "start:", repr(beg), "end:", repr(end)) + beg = converter(beg, type(text)) + end = converter(end, type(text)) + start_pos = text.find(beg) + if start_pos == -1: + return type(text)() + start_pos += len(beg) + end_pos = text.find(end, start_pos) + if end_pos == -1: + return type(text)() + return text[start_pos:end_pos] + + +def extract_int(text, start_str, end_str) -> int | None: + text = extract_text(text, start_str, end_str) + return int(text) if text else None + + +def extract_list(text, start_str, end_str) -> list: + o = extract_text(text, start_str, end_str) + o = converter(o, str) + return o.split(",") if o else [] def measure_time(func): def wrapper(*args, **kwargs): start_time = time.ticks_ms() - result = func(*args, **kwargs) # 执行原函数 - end_time = time.ticks_ms() # 记录结束时间 - execution_time = end_time - start_time # 计算执行时间 + result = func(*args, **kwargs) + end_time = time.ticks_ms() + execution_time = end_time - start_time print(f"total time consumed to execute {func}: {execution_time}ms") return result From ac7789ce5ab44afb47836aaf4256a63607259db8 Mon Sep 17 00:00:00 2001 From: lbuque Date: Fri, 28 Feb 2025 17:12:50 +0800 Subject: [PATCH 052/322] driver/simcom: Optimizing the code for SIM7020. Signed-off-by: lbuque --- m5stack/libs/driver/simcom/sim7020.py | 748 +++++++++++++++--- m5stack/libs/driver/simcom/sim800.py | 386 --------- m5stack/libs/driver/simcom/sim800/__init__.py | 490 ++++++++++++ m5stack/libs/driver/simcom/v25ter.py | 51 -- m5stack/libs/driver/umodem/__init__.py | 4 + m5stack/libs/driver/umodem/modem.py | 130 +++ m5stack/libs/driver/umodem/parser.py | 44 ++ 7 files changed, 1285 insertions(+), 568 deletions(-) delete mode 100644 m5stack/libs/driver/simcom/sim800.py create mode 100644 m5stack/libs/driver/simcom/sim800/__init__.py delete mode 100644 m5stack/libs/driver/simcom/v25ter.py create mode 100644 m5stack/libs/driver/umodem/__init__.py create mode 100644 m5stack/libs/driver/umodem/modem.py create mode 100644 m5stack/libs/driver/umodem/parser.py diff --git a/m5stack/libs/driver/simcom/sim7020.py b/m5stack/libs/driver/simcom/sim7020.py index e7ab158d..329114ea 100644 --- a/m5stack/libs/driver/simcom/sim7020.py +++ b/m5stack/libs/driver/simcom/sim7020.py @@ -2,70 +2,514 @@ # # SPDX-License-Identifier: MIT -from .common import Modem -from .common import AT_CMD +import umodem +import machine +import socket +from .toolkit import requests2 +from .toolkit import umqtt -class SIM7020(Modem): +class SIM7020(umodem.UModem): def __init__( - self, - uart=None, - pwrkey_pin=None, - reset_pin=None, - power_pin=None, - tx_pin=None, - rx_pin=None, + self, uart=None, pwrkey_pin=None, reset_pin=None, power_pin=None, verbose=False ) -> None: - super().__init__(uart, pwrkey_pin, reset_pin, power_pin, tx_pin, rx_pin) - - def get_imei_number(self): - # Request TA Serial Number Identification(IMEI) - CGSN = AT_CMD("AT+CGSN", "OK", 3) # noqa: N806 - output, error = self.execute_at_command(CGSN) - return False if error else output - - def get_ccid_number(self): - # Show ICCID - CCID = AT_CMD("AT+CCID", "OK", 3) # noqa: N806 - output, error = self.execute_at_command(CCID) - return False if error else output - - def set_pdp_context(self, active=1): - # PDP Context Activate or Deactivate - CGACT = AT_CMD("AT+CGACT={0},1".format(active), "OK", 3) # noqa: N806 - output, error = self.execute_at_command(CGACT) - return False if error else output - - def get_pdp_context_status(self): - # PDP Context Activate or Deactivate - CGACT = AT_CMD("AT+CGACT?", "+CGACT:", 3) # noqa: N806 - output, error = self.execute_at_command(CGACT) - return False if error else int(output[-1]) - - def set_pdp_context_apn(self, apn="cmnbiot"): - # Set Default PSD Connection Settings - MCGDEFCONT = AT_CMD('AT*MCGDEFCONT="IP","{}"'.format(apn), "OK", 12) # noqa: N806 - output, error = self.execute_at_command(MCGDEFCONT) - return not error + # Pin initialization + pwrkey_obj = machine.Pin(pwrkey_pin, machine.Pin.OUT) if pwrkey_pin else None + reset_obj = machine.Pin(reset_pin, machine.Pin.OUT) if reset_pin else None + power_obj = machine.Pin(power_pin, machine.Pin.OUT) if power_pin else None + + # Status setup + pwrkey_obj and pwrkey_obj(0) + reset_obj and reset_obj(1) + power_obj and power_obj(1) + super().__init__(uart, verbose=verbose) + + # pdp + self._cid = 1 + + self._default_timeout = 3000 + self._is_active = True + self._low_power_mode() + + def _low_power_mode(self): + """enter low power mode""" + cmd = umodem.Command("+CFUN", umodem.Command.CMD_WRITE, 0, timeout=10000) + resp = self.execute(cmd) + if resp.status_code == resp.ERR_NONE: + self._is_active = False + + def active(self, is_active) -> bool: + """activate or deactivate the modem""" + if self._is_active == is_active: + return self._is_active + + if is_active: + cmd = umodem.Command( + "+CFUN", umodem.Command.CMD_WRITE, 1, timeout=self._default_timeout + ) + resp = self.execute(cmd) + if resp.status_code == resp.ERR_NONE: + self._is_active = True + else: + self._low_power_mode() + return self._is_active + + def connect(self, apn=None): + if apn is None: + # auto activate PDP context + # Check SIM card status + cmd = umodem.Command("+CPIN", umodem.Command.CMD_READ, timeout=self._default_timeout) + self.execute(cmd) + # Check RF signal + cmd = umodem.Command( + "+CSQ", umodem.Command.CMD_EXECUTION, timeout=self._default_timeout + ) + self.execute(cmd) + # Check PS service. 1 indicates PS has attached. + cmd = umodem.Command("+CGATT", umodem.Command.CMD_READ, timeout=self._default_timeout) + self.execute(cmd) + # PDN active success + cmd = umodem.Command("+CGACT", umodem.Command.CMD_READ, timeout=self._default_timeout) + self.execute(cmd) + # Query Network information, operator and network mode 9, NB-IOT network + cmd = umodem.Command("+COPS", umodem.Command.CMD_READ, timeout=self._default_timeout) + self.execute(cmd) + else: + # manually activate PDP context + # Disable RF + cmd = umodem.Command( + "+CFUN", umodem.Command.CMD_WRITE, 0, timeout=self._default_timeout + ) + self.execute(cmd) + # set the APN manually + cmd = umodem.Command( + "*MCGDEFCONT", umodem.Command.CMD_WRITE, "IP", apn, timeout=self._default_timeout + ) + self.execute(cmd) + # Enable RF + cmd = umodem.Command( + "+CFUN", umodem.Command.CMD_WRITE, 1, timeout=self._default_timeout + ) + self.execute(cmd) + # Inquiry PS service + cmd = umodem.Command("+CGATT", umodem.Command.CMD_READ, timeout=self._default_timeout) + self.execute(cmd) + + # Attached PS domain and got IP address automatically + cmd = umodem.Command( + "+CGCONTRDP", umodem.Command.CMD_EXECUTION, timeout=self._default_timeout + ) + self.execute(cmd) + + def disconnect(self): + # 断开PDP连接,但是RF还是开启的 + raise NotImplementedError + + def isconnected(self) -> bool: + parameters = self._get_pdp_context_dynamic_parameters() + return parameters[3] != "0.0.0.0" and parameters[3] != "" + + PIN_ERROR = -1 # PIN is not correct + PIN_READY = 0 # MT is not pending for any password + SIM_PIN = 1 # MT is waiting SIM PIN to be given + SIM_PUK = 2 # MT is waiting for SIM PUK to be given + PH_SIM_PIN = 3 # ME is waiting for phone to SIM card (antitheft) + PH_SIM_PUK = 4 # ME is waiting for SIM PUK (antitheft) + SIM_PIN2 = 5 # PIN2, e.g. for editing the FDN book possible only if preceding Command was acknowledged with +CME ERROR:17 + SIM_PUK2 = 6 # Possible only if preceding Command was acknowledged with error +CME ERROR: 18. + PH_SIM_PIN = 7 # ME is waiting for phone to SIM card (antitheft) + PH_NET_PIN = 8 # Network personalization password is required. + PH_NETSUB_PIN = 9 # Network subset is required. + PH_SP_PIN = 10 # Service provider personalization password is required. + PH_CORP_PIN = 11 # Corporate personalization password is required. + + _cpin_code = { + "READY": PIN_READY, + "SIM PIN": SIM_PIN, + "SIM PUK": SIM_PUK, + "PH_SIM PIN": PH_SIM_PIN, + "PH_SIM PUK": PH_SIM_PUK, + "SIM PIN2": SIM_PIN2, + "SIM PUK2": SIM_PUK2, + "PH-SIM PIN": PH_SIM_PIN, + "PH-NET PIN": PH_NET_PIN, + "PH-NETSUB PIN": PH_NETSUB_PIN, + "PH-SP PIN": PH_SP_PIN, + "PH-CORP PIN": PH_CORP_PIN, + } + + def _convert_rssi(self, rssi) -> int: + """return dbm""" + if rssi == 0: + return -110 + elif rssi == 1: + return -109 + elif rssi == 2: + return -107 + elif rssi >= 3 and rssi <= 30: + return -105 + (rssi - 3) * 2 + elif rssi == 31: + return -48 + return -115 + + def status(self, param=None) -> bool | int | str | tuple | None: + if param is None or param == "status": + parameters = self._get_pdp_context_dynamic_parameters() + return parameters[3] != "0.0.0.0" and parameters[3] != "" + elif param == "rssi": + cmd = umodem.Command( + "+CSQ", umodem.Command.CMD_EXECUTION, timeout=self._default_timeout + ) + resp = self.execute(cmd) + if resp.status_code == umodem.Response.ERR_NONE: + parser = umodem.Parser(resp.content) + parser.skipuntil("+CSQ: ") + rssi = parser.parseint() + return self._convert_rssi(rssi) + elif param == "pin": + cmd = umodem.Command("+CPIN", umodem.Command.CMD_READ, timeout=self._default_timeout) + resp = self.execute(cmd) + if resp.status_code == umodem.Response.ERR_NONE: + parser = umodem.Parser(resp.content) + parser.skipuntil("+CPIN: ") + code = parser.parseutil("\r\n").replace('"', "") + return self._cpin_code.get(code, self.PIN_ERROR) + return self.PIN_ERROR + elif param == "station": + station, _ = self._get_network_state() + return station + elif param == "neighbor": + _, neighbor = self._get_network_state() + return neighbor + + def ifconfig(self, addr=None, mask=None, gateway=None, dns=None): + params = self._get_pdp_context_dynamic_parameters() + return (params[3], params[4], params[5], params[6]) + + MODE_NB_IOT = 9 + + def config(self, *args, **kwargs): + if len(kwargs) != 0: + for key, value in kwargs.items(): + if key == "band": + self.set_band(value) + return + + if len(args) != 1: + raise TypeError("can query only one param") + + param = args[0] + if param == "apn": + return self._get_pdp_context_dynamic_parameters()[2] + elif param == "mode": + # sim7020 support NB-IOT only + return self.MODE_NB_IOT + elif param == "band": + return self.get_band() + elif param == "ccid": + return self.get_ccid_number() + elif param == "imei": + return self.get_imei_number() + elif param == "imsi": + return self.get_imsi_number() + elif param == "mfr": + return self.get_manufacturer() + elif param == "model": + return self.get_model_id() + elif param == "version": + return self.get_model_software_version() + + def _get_pdp_context_dynamic_parameters(self) -> tuple: + """PDP Context Read Dynamic Parameters""" + cmd = umodem.Command( + "+CGCONTRDP", umodem.Command.CMD_WRITE, self._cid, timeout=self._default_timeout + ) + resp = self.execute(cmd) + if resp.status_code == umodem.Response.ERR_NONE: + parser = umodem.Parser(resp.content) + parser.skipuntil("+CGCONTRDP: ") + cid = parser.parseint() # cid + bearer_id = parser.parseint() # bearer_id + apn = parser.parseutil(",").strip('"') # apn + locl_ip = parser.parseutil(",").strip('"') # local_ip and subnet_mask + if locl_ip.count(".") == 7: + # ipv4 + parts = locl_ip.split(".") + locl_ip = ".".join(parts[:4]) + subnet_mask = ".".join(parts[4:]) + elif locl_ip.count(".") == 31: + # ipv6 + parts = locl_ip.split(".") + locl_ip = ".".join(parts[:16]) + subnet_mask = ".".join(parts[16:]) + else: + locl_ip = "0.0.0.0" + subnet_mask = "0.0.0.0" + gateway = parser.parseutil(",").strip('"') # gateway + gateway = gateway if gateway != "" else "0.0.0.0" + dns1 = parser.parseutil(",").strip('"') + dns1 = dns1 if dns1 != "" else "0.0.0.0" + dns2 = parser.parseutil(",").strip('"') + dns2 = dns2 if dns2 != "" else "0.0.0.0" + return (cid, bearer_id, apn, locl_ip, subnet_mask, gateway, dns1, dns2) + else: + return (-9999, -9999, "", "0.0.0.0", "0.0.0.0", "0.0.0.0", "0.0.0.0", "0.0.0.0") + + def _get_network_state(self): + cmd = umodem.Command("+CENG", umodem.Command.CMD_READ, timeout=self._default_timeout) + resp = self.execute(cmd) + if resp.status_code == umodem.Response.ERR_NONE: + num = resp.content.count("+CENG: ") + if num == 0: + return ((), ()) + parser = umodem.Parser(resp.content) + parser.skipuntil("+CENG: ") + station = [] + station.append(parser.parseint()) + station.append(parser.parseint()) + station.append(parser.parseint()) + station.append(parser.parseutil(",").strip('"')) + station.append(parser.parseint()) + station.append(parser.parseint()) + station.append(parser.parseint()) + station.append(parser.parseint()) + station.append(parser.parseint()) + station.append(parser.parseutil(",").strip('"')) + station.append(parser.parseint()) + station.append(parser.parseint()) + station.append(parser.parseint(chr="\r")) + neighbors = [] + for _ in range(num - 1): + parser.skipuntil("+CENG: ") + neighbor = [] + neighbor.append(parser.parseint()) + neighbor.append(parser.parseint()) + neighbor.append(parser.parseint()) + neighbor.append(parser.parseint(chr="\r")) + neighbors.append(tuple(neighbor)) + return (tuple(station), tuple(neighbors)) + return ((), ()) + + def getaddrinfo(self, host, port, af=0, type=0, proto=0, flags=0): + """ + The resulting list of 5-tuples has the following structure: + (family, type, proto, canonname, sockaddr) + """ + + def socket(self, af=socket.AF_INET, type=socket.SOCK_STREAM, proto=socket.IPPROTO_TCP): + """see sim7600""" + raise NotImplementedError + + def wrap_socket( + self, + sock, + # server_side=False, + # key=None, + # cert=None, + # cert_reqs=CERT_NONE, + # cadata=None, + server_hostname=None, + # do_handshake=True, + ): + """see sim7600""" + raise NotImplementedError + + """request method""" + + def request( + self, + method, + url, + data=None, + json=None, + headers={}, + stream=None, + auth=None, + timeout=None, + parse_headers=True, + ): + return requests2._request( + self, method, url, data, json, headers, stream, auth, timeout, parse_headers + ) + + def head(self, url, **kw): + return self.request("HEAD", url, **kw) + + def get(self, url, **kw): + return self.request("GET", url, **kw) + + def post(self, url, **kw): + return self.request("POST", url, **kw) + + def put(self, url, **kw): + return self.request("PUT", url, **kw) + + def patch(self, url, **kw): + return self.request("PATCH", url, **kw) + + def delete(self, url, **kw): + return self.request("DELETE", url, **kw) + + """mqtt method""" + + def MQTTClient( # noqa: N802 + self, + client_id, + server, + port=0, + user=None, + password=None, + keepalive=0, + ssl=False, + ssl_params={}, + ): + return umqtt.MQTTClient( + self, + client_id, + server, + port=port, + user=user, + password=password, + keepalive=keepalive, + ssl=ssl, + ssl_params=ssl_params, + ) + + def set_band(self, band: tuple) -> bool: + cmd = umodem.Command( + "+CBAND", umodem.Command.CMD_WRITE, *band, timeout=self._default_timeout + ) + resp = self.execute(cmd) + return resp.status_code == umodem.Response.ERR_NONE + + def get_band(self) -> tuple: + cmd = umodem.Command("+CBAND", umodem.Command.CMD_READ, timeout=self._default_timeout) + resp = self.execute(cmd) + if resp.status_code == umodem.Response.ERR_NONE: + parser = umodem.Parser(resp.content) + parser.skipuntil("+CBAND: ") + parts = parser.parseutil("\r\n").split(",") + return tuple([int(part) for part in parts]) + return () + + def get_manufacturer(self) -> str: + cmd = umodem.Command("+CGMI", umodem.Command.CMD_EXECUTION, timeout=self._default_timeout) + resp = self.execute(cmd) + if resp.status_code == umodem.Response.ERR_NONE: + parser = umodem.Parser(resp.content) + parser.skipuntil("\n") + return parser.parseutil("\r\n") + return "" + + def get_model_id(self) -> str: + cmd = umodem.Command("+CGMM", umodem.Command.CMD_EXECUTION, timeout=self._default_timeout) + resp = self.execute(cmd) + if resp.status_code == umodem.Response.ERR_NONE: + parser = umodem.Parser(resp.content) + parser.skipuntil("\n") + return parser.parseutil("\r\n") + return "" + + def get_model_software_version(self) -> str: + cmd = umodem.Command("+CGMR", umodem.Command.CMD_EXECUTION, timeout=self._default_timeout) + resp = self.execute(cmd) + if resp.status_code == umodem.Response.ERR_NONE: + parser = umodem.Parser(resp.content) + parser.skipuntil("\n") + return parser.parseutil("\r\n") + return "" + + def get_imei_number(self) -> str: + """Request TA Serial Number Identification(IMEI)""" + cmd = umodem.Command("+CGSN", umodem.Command.CMD_EXECUTION, timeout=self._default_timeout) + resp = self.execute(cmd) + if resp.status_code == umodem.Response.ERR_NONE: + parser = umodem.Parser(resp.content) + parser.skipuntil("\n") + return parser.parseutil("\r\n") + return "" + + def get_ccid_number(self) -> str: + """Show ICCID""" + cmd = umodem.Command("+CCID", umodem.Command.CMD_EXECUTION, timeout=self._default_timeout) + resp = self.execute(cmd) + if resp.status_code == umodem.Response.ERR_NONE: + parser = umodem.Parser(resp.content) + parser.skipuntil("\n") + return parser.parseutil("\r\n") + return "" + + def get_imsi_number(self) -> str: + cmd = umodem.Command("+CIMI", umodem.Command.CMD_EXECUTION, timeout=self._default_timeout) + resp = self.execute(cmd) + if resp.status_code == umodem.Response.ERR_NONE: + parser = umodem.Parser(resp.content) + parser.skipuntil("\n") + return parser.parseutil("\r\n") + return "" + + """ + TODO: 下面的方法是pandian写的,需要测试,并且这些方法已经在 nb-iot unit使用了,需要做向前兼容 + """ + + def set_pdp_context(self, active: bool) -> bool: + """PDP Context Activate or Deactivate""" + cmd = umodem.Command( + "+CGACT", umodem.Command.CMD_WRITE, int(active), 1, timeout=self._default_timeout + ) + resp = self.execute(cmd) + return resp.status_code == umodem.Response.ERR_NONE + + def get_pdp_context_status(self) -> bool: + """PDP Context Activate or Deactivate""" + cmd = umodem.Command("+CGACT", umodem.Command.CMD_READ, timeout=self._default_timeout) + resp = self.execute(cmd) + if resp.status_code == umodem.Response.ERR_NONE: + parser = umodem.Parser(resp.content) + parser.skipuntil("+CGACT: ") + parser.parseint() + return bool(parser.parseint()) + return False + + def set_pdp_context_apn(self, apn="cmnbiot") -> bool: + """Set Default PSD Connection Settings""" + cmd = umodem.Command( + "*MCGDEFCONT", umodem.Command.CMD_WRITE, "IP", apn, timeout=self._default_timeout + ) + resp = self.execute(cmd) + return resp.status_code == umodem.Response.ERR_NONE def get_pdp_context_dynamic_parameters(self, param=1): - # PDP Context Read Dynamic Parameters - CGCONTRDP = AT_CMD("AT+CGCONTRDP", "+CGCONTRDP:", 5) # noqa: N806 - output, error = self.execute_at_command(CGCONTRDP) - if error: - return False - return ( - output.split(",")[-1].replace('"', "").rsplit(".", 4)[0] - if param == 1 - else output.split(",")[-2].replace('"', "") + """PDP Context Read Dynamic Parameters""" + cmd = umodem.Command( + "+CGCONTRDP", umodem.Command.CMD_EXECUTION, timeout=self._default_timeout ) + resp = self.execute(cmd) + if resp.status_code == umodem.Response.ERR_NONE: + parser = umodem.Parser(resp.content) + parser.parseint() # cid + parser.parseint() # bearer_id + apn = parser.parseutil(",").strip('"') # apn + locl_ip = parser.parseutil(",").strip('"') # local_ip and subnet_mask + parts = locl_ip.split(".") + locl_ip = ".".join(parts[:4]) # local_ip. FIXME: support ipv6 + if param == 1: + return locl_ip + elif param == 2: + return apn + else: + return "" # MQTT Test Server:mqtt.m5stack.com, Port:1883. def mqtt_server_connect(self, server, port, client_id, username, passwd, keepalive): - CMQNEW = AT_CMD( # noqa: N806 - 'AT+CMQNEW="{0}","{1}",{2},{3}'.format(server, port, 12000, 1024), # noqa: N806 - "+CMQNEW", # noqa: N806 - 3, + cmd = umodem.Command( + "+CMQNEW", + umodem.Command.CMD_WRITE, + server, + port, + 12000, + 1024, + timeout=self._default_timeout, ) self._mqtt_id = 0 # mqtt callback function keyword is set @@ -75,35 +519,50 @@ def mqtt_server_connect(self, server, port, client_id, username, passwd, keepali self.mqtt_subscribe_cb_list = {} - output, error = self.execute_at_command(CMQNEW) - if error: + resp = self.execute(cmd) + if resp.status_code != umodem.Response.ERR_NONE: return False - self._mqtt_id = int(output[-1]) + self._mqtt_id = int(resp.content[-1]) - CMQCON = AT_CMD( # noqa: N806 - 'AT+CMQCON={0},{1},"{2}",{3},{4},{5},"{6}","{7}"'.format( # noqa: N806 - self._mqtt_id, 3, client_id, keepalive, 1, 0, username, passwd - ), - "OK", # noqa: N806 + cmd = umodem.Command( + "+CMQCON", + umodem.Command.CMD_WRITE, + self._mqtt_id, 3, + client_id, + keepalive, + 1, + 0, + username, + passwd, + timeout=self._default_timeout, ) - _, error = self.execute_at_command(CMQCON) - if error: + resp = self.execute(cmd) + if resp.status_code != umodem.Response.ERR_NONE: return False result = self.mqtt_server_is_connect() return bool(result) def mqtt_server_disconnect(self): - SMDISC = AT_CMD("AT+CMQDISCON={0}".format(self._mqtt_id), "OK", 3) # noqa: N806 - _, error = self.execute_at_command(SMDISC) - return not error + cmd = umodem.Command( + "+CMQDISCON", umodem.Command.CMD_WRITE, self._mqtt_id, timeout=self._default_timeout + ) + resp = self.execute(cmd) + return resp.status_code == umodem.Response.ERR_NONE def mqtt_subscribe_topic(self, topic, cb, qos=0): # Subscribe topic(support wildcards). - CMQSUB = AT_CMD('AT+CMQSUB={0},"{1}",{2}'.format(self._mqtt_id, topic, qos), "OK", 3) # noqa: N806 - output, error = self.execute_at_command(CMQSUB) - if error: + cmd = umodem.Command( + "+CMQSUB", + umodem.Command.CMD_WRITE, + self._mqtt_id, + topic, + qos, + timeout=self._default_timeout, + ) + resp = self.execute(cmd) + if resp.status_code != umodem.Response.ERR_NONE: return False if topic not in self.mqtt_subscribe_cb_list.keys(): self.mqtt_subscribe_cb_list[topic] = cb @@ -111,9 +570,15 @@ def mqtt_subscribe_topic(self, topic, cb, qos=0): def mqtt_unsubscribe_topic(self, topic): # Unsubscribe topic. - CMQUNSUB = AT_CMD('AT+CMQUNSUB={0},"{1}"'.format(self._mqtt_id, topic), "OK", 3) # noqa: N806 - output, error = self.execute_at_command(CMQUNSUB) - if error: + cmd = umodem.Command( + "+CMQUNSUB", + umodem.Command.CMD_WRITE, + self._mqtt_id, + topic, + timeout=self._default_timeout, + ) + resp = self.execute(cmd) + if resp.status_code != umodem.Response.ERR_NONE: return False if topic in self.mqtt_subscribe_cb_list.keys(): self.mqtt_subscribe_cb_list.pop(topic) @@ -122,35 +587,46 @@ def mqtt_unsubscribe_topic(self, topic): def mqtt_publish_topic(self, topic, payload, qos=0, retained=None, duplicate=None): # Publish message with topic. if retained is None and duplicate is None: - CMQPUB = AT_CMD( # noqa: N806 - 'AT+CMQPUB={0},"{1}",{2},0,0,{3},"{4}"'.format( # noqa: N806 - self._mqtt_id, topic, qos, len(payload), payload - ), - "OK", # noqa: N806 - 3, + cmd = umodem.Command( + "+CMQPUB", + umodem.Command.CMD_WRITE, + self._mqtt_id, + topic, + qos, + len(payload), + payload, + timeout=self._default_timeout, ) else: - CMQPUB = AT_CMD( # noqa: N806 - 'AT+CMQPUB={0},"{1}",{2},{3},{4},{5},"{6}"'.format( # noqa: N806 - self._mqtt_id, topic, qos, retained, duplicate, len(payload), payload - ), - "OK", # noqa: N806 - 3, + cmd = umodem.Command( + "+CMQPUB", + umodem.Command.CMD_WRITE, + self._mqtt_id, + topic, + qos, + retained, + duplicate, + len(payload), + payload, + timeout=self._default_timeout, ) - _, error = self.execute_at_command(CMQPUB) - if error: + resp = self.execute(cmd) + if resp.status_code != umodem.Response.ERR_NONE: return False def mqtt_server_is_connect(self): # Check mqtt server connection. - CMQCON = AT_CMD("AT+CMQCON?", "+CMQCON:", 3) # noqa: N806 - output, error = self.execute_at_command(CMQCON) - return False if error else int(output.split(",")[1]) + cmd = umodem.Command("+CMQCON?", umodem.Command.CMD_READ, timeout=self._default_timeout) + resp = self.execute(cmd) + return ( + False + if resp.status_code != umodem.Response.ERR_NONE + else int(resp.content.split(",")[1]) + ) def mqtt_polling_loop(self): - DUMMY = AT_CMD("", "", 0) # noqa: N806 - self.response_at_command(DUMMY) + pass def mqtt_subscribe_cb(self, buffer): # main callback function @@ -176,11 +652,13 @@ def http_request( self.response_code = 0 self.data_content = "" - CHTTPCREATE = AT_CMD('AT+CHTTPCREATE="{0}"'.format(host), "+CHTTPCREATE:", 10) # noqa: N806 - output, error = self.execute_at_command(CHTTPCREATE) - if error: + cmd = umodem.Command( + 'AT+CHTTPCREATE="{0}"'.format(host), umodem.Command.CMD_EXECUTION, timeout=10 + ) + resp = self.execute(cmd) + if resp.status_code != umodem.Response.ERR_NONE: return False - self.http_client_id = int(output[-1]) + self.http_client_id = int(resp.content[-1]) output = self.http_server_connect() if output is False: @@ -194,40 +672,40 @@ def http_request( temp_header += self.make_header(head[0], head[1]) if method == self.HTTPCLIENT_GET: - CHTTPSEND = AT_CMD( # noqa: N806 - 'AT+CHTTPSEND={0},{1},"{2}"'.format( # noqa: N806 - self.http_client_id, self.HTTPCLIENT_GET, ("/" + path) - ), - "+CHTTPNMIC:", # noqa: N806 - 15, + cmd = umodem.Command( + "+CHTTPSEND", + umodem.Command.CMD_WRITE, + self.http_client_id, + self.HTTPCLIENT_GET, + ("/" + path), + timeout=10, ) elif method == self.HTTPCLIENT_POST: temp_header = self.asciistr_to_hexstr(temp_header) if temp_header != "" else 0 - CHTTPSEND = AT_CMD( # noqa: N806 - 'AT+CHTTPSEND={0},{1},"{2}",{3},"{4}",{5}'.format( # noqa: N806 - self.http_client_id, - self.HTTPCLIENT_POST, - ("/" + path), - temp_header, - contenttype, - self.asciistr_to_hexstr(data), - ), - "+CHTTPNMIC:", # noqa: N806 - 15, + cmd = umodem.Command( + "+CHTTPSEND", + umodem.Command.CMD_WRITE, + self.http_client_id, + self.HTTPCLIENT_POST, + ("/" + path), + temp_header, + contenttype, + self.asciistr_to_hexstr(data), + timeout=10, ) - output, error = self.execute_at_command(CHTTPSEND) - if error: + resp = self.execute(cmd) + if resp.status_code != umodem.Response.ERR_NONE: return False - self.response_code = int(output.split(",")[1]) + self.response_code = int(resp.content.split(",")[1]) if self.response_code != 200: print('Response code: "{0}"'.format(self.response_code)) return False - if output.find("+CHTTPNMIC:") != -1: # noqa: N806 - self.data_content = self.hexstr_to_asciistr(output.split(",")[-1]) + if resp.content.find("+CHTTPNMIC:") != -1: # noqa: N806 + self.data_content = self.hexstr_to_asciistr(resp.content.split(",")[-1]) output = self.http_server_disconnect() if output is False: @@ -239,29 +717,37 @@ def http_request( def http_server_connect(self): # Http server is connect - CHTTPCON = AT_CMD("AT+CHTTPCON={0}".format(self.http_client_id), "OK", 20) # noqa: N806 - _, error = self.execute_at_command(CHTTPCON) - return not error + cmd = umodem.Command("+CHTTPCON", umodem.Command.CMD_WRITE, self.http_client_id, timeout=3) + resp = self.execute(cmd) + return resp.status_code == umodem.Response.ERR_NONE def http_server_is_connect(self): # Is check http server connect - CHTTPCON = AT_CMD("AT+CHTTPCON?", "OK", 3) # noqa: N806 - output, error = self.execute_at_command(CHTTPCON) - output = output.split("+CHTTPCON") # noqa: N806 + cmd = umodem.Command("+CHTTPCON?", umodem.Command.CMD_READ, timeout=3) + resp = self.execute(cmd) + output = resp.content.split("+CHTTPCON") output[self.http_client_id + 1].split(",")[1] - return False if error else int(output[self.http_client_id + 1].split(",")[1]) + return ( + False + if resp.status_code != umodem.Response.ERR_NONE + else int(output[self.http_client_id + 1].split(",")[1]) + ) def http_server_disconnect(self): # http server disconnected - CHTTPDISCON = AT_CMD("AT+CHTTPDISCON={0}".format(self.http_client_id), "OK", 10) # noqa: N806 - _, error = self.execute_at_command(CHTTPDISCON) - return not error + cmd = umodem.Command( + "+CHTTPDISCON", umodem.Command.CMD_WRITE, self.http_client_id, timeout=10 + ) + resp = self.execute(cmd) + return resp.status_code == umodem.Response.ERR_NONE def http_server_destroy(self): # http server destroy - CHTTPDESTROY = AT_CMD("AT+CHTTPDESTROY={0}".format(self.http_client_id), "OK", 10) # noqa: N806 - _, error = self.execute_at_command(CHTTPDESTROY) - return not error + cmd = umodem.Command( + "+CHTTPDESTROY", umodem.Command.CMD_WRITE, self.http_client_id, timeout=10 + ) + resp = self.execute(cmd) + return resp.status_code == umodem.Response.ERR_NONE def make_header(self, key, value): return str(key) + ":" + str(value) + "\n" diff --git a/m5stack/libs/driver/simcom/sim800.py b/m5stack/libs/driver/simcom/sim800.py deleted file mode 100644 index d487299e..00000000 --- a/m5stack/libs/driver/simcom/sim800.py +++ /dev/null @@ -1,386 +0,0 @@ -# SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD -# -# SPDX-License-Identifier: MIT - -from .common import Modem -from .common import AT_CMD -import re -import socket - - -class socket_simcom: - AF_INET = socket.AF_INET - AF_INET6 = socket.AF_INET6 - - SOCK_STREAM = socket.SOCK_STREAM - SOCK_DGRAM = socket.SOCK_DGRAM - SOCK_RAW = socket.SOCK_RAW - - IPPROTO_IP = socket.IPPROTO_IP - IPPROTO_TCP = socket.IPPROTO_TCP - IPPROTO_UDP = socket.IPPROTO_UDP - - def __init__(self, af=AF_INET, type=SOCK_STREAM, proto=IPPROTO_TCP): - self._domain = af - self._type = type - self._proto = proto - # todo: fd(cmux?) - self._fd = 0 - - def close(self): - pass - - def bind(self, address): - pass - - def listen(self, backlog): - pass - - def accept(self): - pass - - def connect(self, address): - cipstart = AT_CMD( # noqa: F841 - "AT+CIPSTART={},{},{},{}".format(self._fd, address[0], address[1], 0), "OK", 3 - ) - - def send(self, data): - pass - - def sendall(self, data): - pass - - def sendto(self, data, address): - pass - - def recv(self, size): - pass - - def recvfrom(self, size): - pass - - def setsockopt(self, level, optname, value): - pass - - def settimeout(self, timeout): - pass - - def setblocking(self): - pass - - def makefile(self, mode): - pass - - def fileno(self): - pass - - def read(self, size): - pass - - def readinto(self, buffer): - pass - - def readline(self): - pass - - def write(self, data): - pass - - -class SIM800(Modem): - def __init__( - self, uart=None, pwrkey_pin=None, reset_pin=None, power_pin=None, tx_pin=None, rx_pin=None - ): - super().__init__(uart, pwrkey_pin, reset_pin, power_pin, tx_pin, rx_pin) - - def getaddrinfo(self, host, port, af=0, type=0, proto=0, flags=0): - pass - - def socket(self, af=0, type=0, proto=0): - pass - - def connect(self, address): - # CIPSTART - pass - - def write(self, data): - # CIPSEND - pass - - def readline(self): - pass - - def read(self, size): - # CIPRXGET - pass - - def close(self): - pass - - def get_imei_number(self): - # Request TA Serial Number Identification(IMEI) - cgsn = AT_CMD("AT+CGSN=1", "OK", 3) - output, error = self.execute_at_command(cgsn) - return False if error else output - - def get_ccid_number(self) -> str | bool: - # Show ICCID - iccid = AT_CMD("AT+ICCID", "+ICCID:", 3) - output, error = self.execute_at_command(iccid) - return False if error else output.split(" ")[1] - - def get_pdp_context_dynamic_parameters(self, param=1) -> str | bool: - # PDP Context Read Dynamic Parameters - CGCONTRDP = AT_CMD("AT+CGCONTRDP", "+CGCONTRDP:", 5) # noqa: N806 - output, error = self.execute_at_command(CGCONTRDP) - if error: - return False - return ( - output.split(",")[4].replace('"', "") - if param == 1 - else output.split(",")[2].replace('"', "") - ) - - # MQTT Test Server:mqtt.m5stack.com, Port:1883. - def mqtt_server_configure(self, server, port, client_id, username, passwd, keepalive) -> bool: - #! Connect to MQTT broker. - self.mqtt_username = username - self.mqtt_passwd = passwd - self.mqtt_keepalive = keepalive - self.mqtt_host_port = "tcp://" + server + ":" + str(port) - self.clean_session = self.mqttclient_index = None - self.mqtt_connect_status = False - - # mqtt callback function keyword is set - self.downlink_keyword.append("+CMQTTRXTOPIC:") - self.downlink_keyword.append("+CMQTTRXPAYLOAD:") - self.downlink_keyword.append("+CMQTTCONNLOST:") - self.callback_keyword.append("MQTT_CB") - self.downlink_callback["MQTT_CB"] = self.mqtt_subscribe_cb - self.mqtt_subscribe_cb_list = {} - self.topic = self.message = None - - CMQTTSTART = AT_CMD("AT+CMQTTSTART", "+CMQTTSTART: 0", 12) # noqa: N806 - output, error = self.execute_at_command(CMQTTSTART) - if error or output[-1] != "0": - return False - - CMQTTACCQ = AT_CMD('AT+CMQTTACCQ=0,"{0}"'.format(client_id), "OK", 5) # noqa: N806 - _, error = self.execute_at_command(CMQTTACCQ) - if error: - return False - - return self.mqtt_server_connect(0) - - def mqtt_server_connect(self, clean_session=0) -> bool: - self.clean_session = clean_session - CMQTTCONNECT = AT_CMD( # noqa: N806 - ( - 'AT+CMQTTCONNECT=0,"{0}",{1},{2},"{3}","{4}"'.format( - self.mqtt_host_port, - self.mqtt_keepalive, - clean_session, - self.mqtt_username, - self.mqtt_passwd, - ) - if len(self.mqtt_username) and len(self.mqtt_passwd) - else 'AT+CMQTTCONNECT=0,"{0}",{1},{2}'.format( - self.mqtt_host_port, self.mqtt_keepalive, clean_session - ) - ), - "+CMQTTCONNECT:", - 30, - ) - output, error = self.execute_at_command(CMQTTCONNECT) - if error or output[-1] != "0": - return False - self.mqttclient_index = int(output[15]) - self.mqtt_connect_status = True - return True - - def mqtt_server_disconnect(self) -> None | bool: - CMQTTDISC = AT_CMD("AT+CMQTTDISC={0},120".format(self.mqttclient_index), "+CMQTTDISC:", 5) # noqa: N806 - _, error = self.execute_at_command(CMQTTDISC) - if error: - return False - CMQTTREL = AT_CMD("AT+CMQTTREL={0}".format(self.mqttclient_index), "OK", 5) # noqa: N806 - _, error = self.execute_at_command(CMQTTREL) - if error: - return False - CMQTTSTOP = AT_CMD("AT+CMQTTSTOP", "+CMQTTSTOP:", 5) # noqa: N806 - _, error = self.execute_at_command(CMQTTSTOP) - if error: - return False - - def mqtt_subscribe_topic(self, topic, cb, qos=0) -> bool: - # Subscribe topic(support wildcards). - CMQTTSUB = AT_CMD( # noqa: N806 - "AT+CMQTTSUB={0},{1},{2}".format(self.mqttclient_index, len(topic), qos), ">", 10 - ) - output, error = self.execute_at_command(CMQTTSUB) - if error: - return False - TOPIC = AT_CMD("{0}".format(topic), "+CMQTTSUB:", 10) # noqa: N806 - output, error = self.execute_at_command(TOPIC) - if error or output[-1] != "0": - return False - if topic not in self.mqtt_subscribe_cb_list.keys(): - self.mqtt_subscribe_cb_list[topic] = cb - return True - - def mqtt_unsubscribe_topic(self, topic) -> bool: - # Unsubscribe topic. - CMQTTUNSUB = AT_CMD( # noqa: N806 - "AT+CMQTTUNSUB={0},{1}".format(self.mqttclient_index, len(topic)), ">", 10 - ) - output, error = self.execute_at_command(CMQTTUNSUB) - if error: - return False - TOPIC = AT_CMD("{0}".format(topic), "+CMQTTUNSUB:", 10) # noqa: N806 - output, error = self.execute_at_command(TOPIC) - if error or output[-1] != "0": - return False - if topic in self.mqtt_subscribe_cb_list.keys(): - self.mqtt_subscribe_cb_list.pop(topic) - return True - - def mqtt_publish_topic(self, topic, payload, qos=0) -> None | bool: - # Publish message with topic. - CMQTTTOPIC = AT_CMD( # noqa: N806 - "AT+CMQTTTOPIC={0},{1}".format(self.mqttclient_index, len(topic)), ">", 10 - ) - _, error = self.execute_at_command(CMQTTTOPIC) - if error: - return False - TOPIC = AT_CMD("{0}".format(topic), "OK", 10) # noqa: N806 - _, error = self.execute_at_command(TOPIC) - if error: - return False - - CMQTTPAYLOAD = AT_CMD( # noqa: N806 - "AT+CMQTTPAYLOAD={0},{1}".format(self.mqttclient_index, len(payload)), ">", 10 - ) - _, error = self.execute_at_command(CMQTTPAYLOAD) - if error: - return False - TOPIC = AT_CMD("{0}".format(payload), "OK", 10) # noqa: N806 - _, error = self.execute_at_command(TOPIC) - if error: - return False - - CMQTTPUB = AT_CMD( # noqa: N806 - "AT+CMQTTPUB={0},{1},120".format(self.mqttclient_index, qos), "+CMQTTPUB:", 10 - ) - output, error = self.execute_at_command(CMQTTPUB) - if error or output[-1] != "0": - return False - - def mqtt_server_is_connect(self) -> bool: - # Check mqtt server connection. - CMQTTCONNECT = AT_CMD("AT+CMQTTCONNECT?", "+CMQTTCONNECT:", 5) # noqa: N806 - output, error = self.execute_at_command(CMQTTCONNECT) - if error: - return False - self.mqttclient_index = int(output[-1]) - return True if (output.split(",")[1].replace('"', "") == self.mqtt_host_port) else False - - def mqtt_polling_loop(self) -> None: - DUMMY = AT_CMD("", "", 0) # noqa: N806 - self.response_at_command(DUMMY) - if self.mqtt_connect_status is not True: - self.mqtt_server_connect(self.clean_session) - - def mqtt_subscribe_cb(self, buffer) -> None: - # main callback function - if "+CMQTTCONNLOST:" in buffer: - self.mqtt_connect_status = False - if "+CMQTTRXTOPIC:" in buffer: - self.topic = self.message = None - self.topic = buffer.split(",")[-1][:-2] - if "+CMQTTRXPAYLOAD:" in buffer: - self.message = buffer.split(",")[-1][:-1] - if self.topic in self.mqtt_subscribe_cb_list.keys(): - if self.message: - self.mqtt_subscribe_cb_list[self.topic](self.topic, self.message) - self.topic = self.message = None - - # Create & Request Http(s) - HTTPCLIENT_GET = 0 - HTTPCLIENT_POST = 1 - - def http_request( - self, method=HTTPCLIENT_GET, url="http://api.m5stack.com/v1", headers={}, data=None - ) -> str | bool: - # Create HTTP host instance - self.response_code = 0 - self.data_content = "" - - self.set_pdp_context("cmnbiot") - - for i in range(3): - HTTPINIT = AT_CMD("AT+HTTPINIT", "OK", 15) # noqa: N806 - output, error = self.execute_at_command(HTTPINIT) - if not error: - break - self.http_terminate() - - HTTPPARA = AT_CMD('AT+HTTPPARA="URL","{0}"'.format(url), "OK", 10) # noqa: N806 - output, error = self.execute_at_command(HTTPPARA) - if error: - return False - - if method == self.HTTPCLIENT_POST: - for head in headers.items(): - if head[0] == "Content-Type": - contenttype = head[1] - - HTTPPARA = AT_CMD('AT+HTTPPARA="CONTENT","{0}"'.format(contenttype), "OK", 5) # noqa: N806 - output, error = self.execute_at_command(HTTPPARA) - if error: - return False - HTTPPARA = AT_CMD("AT+HTTPDATA={0},3000".format(len(data)), "DOWNLOAD", 5) # noqa: N806 - output, error = self.execute_at_command(HTTPPARA) - if error: - return False - DATA = AT_CMD("{0}".format(data), "OK", 10) # noqa: N806 - _, error = self.execute_at_command(DATA) - if error: - return False - - HTTPACTION = AT_CMD("AT+HTTPACTION={0}".format(method), "+HTTPACTION:", 25) # noqa: N806 - output, error = self.execute_at_command(HTTPACTION) - if error: - return False - - self.response_code = int(output.split(",")[1]) - if self.response_code != 200: - print('Response code: "{0}"'.format(self.response_code)) - return False - - HTTPREAD = AT_CMD("AT+HTTPREAD=0,500", "+HTTPREAD:", 25) # noqa: N806 - output, error = self.execute_at_command(HTTPREAD) - if error: - return False - match = re.search(r"\+HTTPREAD: (\d+)", output) - if match: - data_len = match.group(1) - else: - return False - # data_len = ''.join(filter(str.isdigit, output.split(' ')[1])) - data_index = len(output.split(" ")[0]) + len(data_len) + 1 - self.data_content = output[data_index : (data_index + int(data_len))] - - def http_terminate(self) -> bool: - # http service terminate - HTTPTERM = AT_CMD("AT+HTTPTERM", "OK", 15) # noqa: N806 - _, error = self.execute_at_command(HTTPTERM) - return not error - - def make_header(self, key, value) -> str: - return str(key) + ":" + str(value) + "\n" - - def asciistr_to_hexstr(self, byte) -> bool: - return "".join(["%02X" % x for x in byte.encode()]).strip() - - def hexstr_to_asciistr(self, hex) -> str: - return bytes.fromhex(hex).decode() diff --git a/m5stack/libs/driver/simcom/sim800/__init__.py b/m5stack/libs/driver/simcom/sim800/__init__.py new file mode 100644 index 00000000..fc9402bd --- /dev/null +++ b/m5stack/libs/driver/simcom/sim800/__init__.py @@ -0,0 +1,490 @@ +# SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import umodem +import re +import socket +import micropython +import umodem.parser +from .. import utils +import time +import machine + + +class SIMComError(Exception): + D_GENERIC = 0 + D_TCPIP_ERR_INFO = 1 # 11.5 + D_TCPIP_ERR = 2 # 11.6 + D_DNS_ERROR_CODE = 3 + + _err_desc = { + D_GENERIC: { + 2: "AT command timeout({})", + }, + D_DNS_ERROR_CODE: { + 10: "DNS GENERAL ERROR({})", + }, + D_TCPIP_ERR_INFO: { + 0: "Connection time out({})", + 1: "Bind port failed({})", + 2: "Port overflow({})", + 3: "Create socket failed({})", + 4: "Network is already opened({})", + 5: "Network is already closed({})", + 6: "No clients connected({})", + 7: "No active client({})", + 8: "Network not opened({})", + 9: "Client index overflow({})", + 10: "Connection is already created({})", + 11: "Connection is not created({})", + 12: "Invalid parameter({})", + 13: "Operation not supported({})", + 14: "DNS query failed({})", + 15: "TCP busy({})", + 16: "Netclose failed for socket opened({})", + 17: "Sending time out({})", + 18: "Sending failure for network error({})", + 19: "Open failure for network error({})", + 20: "Server is already listening({})", + 21: "No data({})", + 22: "Port overflow({})", + }, + D_TCPIP_ERR: { + 0: "Operation succeeded({})", + 1: "Network failure({})", + 2: "Network not opened({})", + 3: "Wrong parameter({})", + 4: "Operation not supported({})", + 5: "Failed to create socket({})", + 6: "Failed to bind socket({})", + 7: "TCP server is already listening({})", + 8: "Busy({})", + 9: "Sockets opened({})", + 10: "Timeout({})", + 11: "DNS parse failed for AT+CIPOPEN({})", + 12: "Unknown error({})", + }, + } + + def __init__(self, *args): + if len(args) == 1: + super().__init__(args[0]) + return + elif len(args) == 3: + domain, errno, cmd = args + msg = self._err_desc[domain].get(errno, "Unknown error({})") + super().__init__(msg.format(repr(cmd))) + + +class _socket: + AF_INET = socket.AF_INET + AF_INET6 = socket.AF_INET6 + + SOCK_STREAM = socket.SOCK_STREAM + SOCK_DGRAM = socket.SOCK_DGRAM + SOCK_RAW = socket.SOCK_RAW + + IPPROTO_IP = socket.IPPROTO_IP + IPPROTO_TCP = socket.IPPROTO_TCP + IPPROTO_UDP = socket.IPPROTO_UDP + + _proto_type = { + IPPROTO_TCP: "TCP", + IPPROTO_UDP: "UDP", + } + + _STATE_OPEN = 0 + _STATE_CLOSE = 1 + + def __init__(self, modem, af=AF_INET, type=SOCK_STREAM, proto=IPPROTO_TCP): + self._modem = modem + self._domain = af + self._type = type + self._proto = proto + self._fd = self._modem.apply_fd() + self._address = None + if self._fd == -1: + raise SIMComError("No available socket") + + self._state = self._STATE_CLOSE + self._ringio = micropython.RingIO(1500) + self._timeout = 2000 + self.blocking = False + + def accept(self): + raise NotImplementedError + + def bind(self, address): + raise NotImplementedError + + def close(self): + if self._fd == -1: + return + + cmd = umodem.Command( + "+CIPCLOSE", umodem.Command.CMD_WRITE, self._fd, timeout=self._modem._default_timeout + ) + self._modem.execute_at_command2(cmd) + self._modem.release_fd(self._fd) + self._fd = -1 + self._state = self._STATE_CLOSE + if hasattr(self, "_local_port"): + self._modem.release_port(self._local_port) + + def connect(self, address): + if self._fd == -1: + raise SIMComError("socket closed") + + if self._state == self._STATE_OPEN: + return + + self._address = address + cmd = umodem.Command( + "+CIPSTART", + umodem.Command.CMD_WRITE, + self._fd, + self._proto_type.get(self._proto, ""), + address[0], + address[1], + rsp1="CONNECT OK", + timeout=self._modem._default_timeout, + ) + + output, error = self._modem.execute_at_command2(cmd) + if error == self._modem.ERR_NONE: + self._state = self._STATE_OPEN + return True + elif error == self._modem.ERR_TIMEOUT: + raise SIMComError(SIMComError.D_GENERIC, error, cmd()) + elif error == self._modem.ERR_GENERIC: + err = utils.extract_int(output, "+CIPOPEN: {},".format(self._fd), "\r\n") + raise SIMComError(SIMComError.D_TCPIP_ERR, err, cmd()) + + def fileno(self) -> int: + return self._fd + + def listen(self, backlog): + raise NotImplementedError + + def makefile(self, mode): + return self + + def read(self, size=-1) -> bytes: + return self.recvfrom(size) + + def readinto(self, buf, nbytes=-1) -> int: + nbytes = min(nbytes, len(buf)) if nbytes != -1 else len(buf) + + if self._ringio.any() < nbytes: + self._recv() + + return self._ringio.readinto(buf, nbytes) + + def readline(self) -> str: + l = "" + while 1: + c = self._ringio.read(1) + l += c + if c == "\\n" or c == "": + return l + + def recv(self, bufsize) -> bytes: + return self.recvfrom(bufsize) + + def recvfrom(self, bufsize: int) -> bytes | None: + if bufsize == -1: + return self.readall() + + if self._ringio.any() > bufsize: + return self._ringio.read(bufsize) + + read_len = self._ringio.any() + out = bytearray(read_len) + self._ringio.readinto(out) + last_time = time.ticks_ms() + while read_len < bufsize and time.ticks_diff(time.ticks_ms(), last_time) < self._timeout: + self._recv() + buf = self._ringio.read(bufsize - read_len) + out.extend(buf) + read_len += len(buf) + + # https://docs.python.org/3.4/library/io.html#io.RawIOBase.read + # "If the object is in non-blocking mode and no bytes are available, + # None is returned." + # This is actually very weird, as naive truth check will treat + # this as EOF. + if self.blocking is False and read_len == 0: + return None + + return bytes(out) + + def send(self, buf) -> int: + # data type check + data = utils.converter(buf, bytes) + + # send cmd + to_send = len(data) + cipsend = umodem.Command( + "AT+CIPSEND={},{}".format(self._fd, to_send), + ">", + "ERROR", + self._modem._default_timeout, + ) + output, error = self._modem.execute_at_command2(cipsend, line_end="") + if error == self._modem.ERR_GENERIC: + err = utils.extract_int(output, "+CIPSEND: ", "\r\n") + raise SIMComError(SIMComError.D_TCPIP_ERR, err, cipsend.cmd) + elif error == self._modem.ERR_TIMEOUT: + raise SIMComError(SIMComError.D_GENERIC, error, cipsend.cmd) + + sented = 0 + while sented < to_send: + self._modem.uart.write(data[sented:to_send]) + cipsend = umodem.Command( + "AT+CIPSEND={},{}".format(self._fd, to_send), + "+CIPSEND:", + "ERROR", + self._modem._default_timeout, + ) + output, error = self._modem.response_at_command2(cipsend) + if error == self._modem.ERR_GENERIC: + err = utils.extract_int(output, "+CIPERROR: ", "\r\n") + raise SIMComError(SIMComError.D_TCPIP_ERR, err, cipsend.cmd) + elif error == self._modem.ERR_TIMEOUT: + raise SIMComError(SIMComError.D_GENERIC, error, cipsend.cmd) + elif error == self._modem.ERR_NONE: + cnf = utils.extract_text( + output, "+CIPSEND: {},{},".format(self._fd, to_send), "\r\n" + ) + sented = int(cnf) if cnf else 0 + return to_send + + def sendall(self, buf): + self.send(buf) + + def sendto(self, buf, address): + if self._state == self._STATE_CLOSE: + self._local_port = self._modem.apply_port() + cipopen = umodem.Command( + "AT+CIPOPEN={},{},,,{}".format( + self._fd, + "".join(['"', self._proto_type.get(self._proto, ""), '"']), + self._local_port, + ), + "OK", + "ERROR", + self._modem._default_timeout, + ) + output, error = self._modem.execute_at_command2(cipopen) + if error == self._modem.ERR_GENERIC: + errno = utils.extract_int(output, "+CIPOPEN: ", "\r\n") + raise SIMComError(SIMComError.D_TCPIP_ERR, errno, cipopen.cmd) + elif error == self._modem.ERR_TIMEOUT: + raise SIMComError(SIMComError.D_GENERIC, error, cipopen.cmd) + self._state = self._STATE_OPEN + + # data type check + data = utils.converter(buf, bytes) + + # send cmd + to_send = len(data) + cipsend = umodem.Command( + "AT+CIPSEND={},{},{},{}".format( + self._fd, to_send, "".join(['"', address[0], '"']), address[1] + ), + ">", + "ERROR", + self._modem._default_timeout, + ) + output, error = self._modem.execute_at_command2(cipsend, line_end="") + if error == self._modem.ERR_GENERIC: + errno = utils.extract_int(output, "+CIPSEND: ", "\r\n") + raise SIMComError(SIMComError.D_TCPIP_ERR, errno, cipsend.cmd) + elif error == self._modem.ERR_TIMEOUT: + raise SIMComError(SIMComError.D_GENERIC, error, cipsend.cmd) + + sented = 0 + while sented < to_send: + self._modem.uart.write(data[sented:to_send]) + cipsend = umodem.Command( + "AT+CIPSEND={},{}".format(self._fd, to_send), + "+CIPSEND:", + "ERROR", + self._modem._default_timeout, + ) + output, error = self._modem.response_at_command2(cipsend) + if error == self._modem.ERR_GENERIC: + errno = utils.extract_int(output, "+CIPERROR: ", "\r\n") + raise SIMComError(SIMComError.D_TCPIP_ERR, errno, cipsend.cmd) + elif error == self._modem.ERR_TIMEOUT: + raise SIMComError(SIMComError.D_GENERIC, error, cipsend.cmd) + else: + cnf = utils.extract_text( + output, "+CIPSEND: {},{},".format(self._fd, to_send), "\r\n" + ) + sented = int(cnf) if cnf else 0 + return to_send + + def setblocking(self, flag): + self.blocking = flag + + def setsockopt(self, level, optname, value): + raise NotImplementedError + + def settimeout(self, timeout): + raise NotImplementedError + + def write(self, buf): + return self.send(buf) + + +class SIM800(umodem.UModem): + def __init__(self, uart=None, pwrkey_pin=None, reset_pin=None, power_pin=None, verbose=False): + # Pin initialization + pwrkey_obj = machine.Pin(pwrkey_pin, machine.Pin.OUT) if pwrkey_pin else None + reset_obj = machine.Pin(reset_pin, machine.Pin.OUT) if reset_pin else None + power_obj = machine.Pin(power_pin, machine.Pin.OUT) if power_pin else None + + # Status setup + pwrkey_obj and pwrkey_obj(0) + reset_obj and reset_obj(1) + power_obj and power_obj(1) + super().__init__(uart, verbose=verbose) + + self._default_timeout = 2000 + self._is_active = True + self._low_power_mode() + + def _low_power_mode(self): + """enter low power mode""" + cmd = umodem.Command("+CFUN", umodem.Command.CMD_WRITE, 0, timeout=self._default_timeout) + resp = self.execute(cmd) + if resp.status_code == resp.ERR_NONE: + self._is_active = False + + def active(self, is_active) -> bool: + """activate or deactivate the modem""" + if self._is_active == is_active: + return self._is_active + + if is_active: + cmd = umodem.Command( + "+CFUN", umodem.Command.CMD_WRITE, 1, timeout=self._default_timeout + ) + resp = self.execute(cmd) + if resp.status_code == resp.ERR_NONE: + self._is_active = True + else: + self._low_power_mode() + return self._is_active + + def connect(self, apn=None): + if apn is None: + # auto activate PDP context + # Check SIM card status + cmd = umodem.Command("+CFIN?", umodem.Command.CMD_READ, timeout=self._default_timeout) + self.execute(cmd) + # Check RF signal + cmd = umodem.Command( + "+CSQ", umodem.Command.CMD_EXECUTION, timeout=self._default_timeout + ) + self.execute(cmd) + # Check PS service. 1 indicates PS has attached. + cmd = umodem.Command("+CGATT", umodem.Command.CMD_READ, timeout=self._default_timeout) + self.execute(cmd) + # PDN active success + cmd = umodem.Command("+CGACT", umodem.Command.CMD_READ, timeout=self._default_timeout) + self.execute(cmd) + # Query Network information, operator and network mode 9, NB-IOT network + cmd = umodem.Command("+COPS", umodem.Command.CMD_READ, timeout=self._default_timeout) + self.execute(cmd) + else: + # manually activate PDP context + # Disable RF + cmd = umodem.Command( + "+CFUN", umodem.Command.CMD_WRITE, 0, timeout=self._default_timeout + ) + self.execute(cmd) + # set the APN manually + cmd = umodem.Command( + "*MCGDEFCONT", umodem.Command.CMD_WRITE, "IP", apn, timeout=self._default_timeout + ) + self.execute(cmd) + # Enable RF + cmd = umodem.Command( + "+CFUN", umodem.Command.CMD_WRITE, 1, timeout=self._default_timeout + ) + self.execute(cmd) + # Inquiry PS service + cmd = umodem.Command("+CGATT", umodem.Command.CMD_READ, timeout=self._default_timeout) + self.execute(cmd) + + # Attached PS domain and got IP address automatically + cmd = umodem.Command( + "+CGCONTRDP", umodem.Command.CMD_EXECUTION, timeout=self._default_timeout + ) + self.execute(cmd) + + def disconnect(self): + raise NotImplementedError + + def isconnected(self) -> bool: + raise NotImplementedError + + def status(self) -> int: + raise NotImplementedError + + def ifconfig(self, addr=None, mask=None, gateway=None, dns=None): + raise NotImplementedError + + def config(self, param: str): + pass + + def getaddrinfo(self, host, port, af=0, type=0, proto=0, flags=0): + pass + + def socket(self, af=0, type=0, proto=0): + pass + + def get_imei_number(self) -> str: + # Request TA Serial Number Identification(IMEI) + cmd = umodem.Command("+CGSN", umodem.Command.CMD_EXECUTION, timeout=self._default_timeout) + resp = self.execute(cmd) + if resp.status_code == resp.ERR_NONE: + parser = umodem.parser.Parser(resp.content) + parser.skipuntil("\n") + return parser.parseutil("\r\n") + return "" + + def get_ccid_number(self) -> str: + # Show ICCID + cmd = umodem.Command("+ICCID", umodem.Command.CMD_EXECUTION, timeout=self._default_timeout) + resp = self.execute(cmd) + if resp.status_code == resp.ERR_NONE: + parser = umodem.parser.Parser(resp.content) + parser.skipuntil("\n") + return parser.parseutil("\r\n") + return "" + + def get_pdp_context_dynamic_parameters(self, cid: int = 1) -> tuple: + # PDP Context Read Dynamic Parameters + cmd = umodem.Command( + "+CGCONTRDP", umodem.Command.CMD_WRITE, cid, timeout=self._default_timeout + ) + resp = self.execute(cmd) + if resp.status_code == resp.ERR_NONE: + return False + parser = umodem.parser.Parser(resp.content) + parser.skipuntil("+CGCONTRDP: ") + cid = parser.parseint() + bearer_id = parser.parseint() + apn = parser.parseutil(",").strip('"') + locl_ip = parser.parseutil(",").strip('"') + parts = locl_ip.split(".") + locl_ip = ".".join(parts[:4]) + subnet_mask = parser.parseutil(",").strip('"') + gateway = parser.parseutil(",").strip('"') + dns1 = parser.parseutil(",") + dns2 = parser.parseutil("\r\n") + return cid, bearer_id, apn, locl_ip, subnet_mask, gateway, dns1, dns2 diff --git a/m5stack/libs/driver/simcom/v25ter.py b/m5stack/libs/driver/simcom/v25ter.py deleted file mode 100644 index 37e24d03..00000000 --- a/m5stack/libs/driver/simcom/v25ter.py +++ /dev/null @@ -1,51 +0,0 @@ -from .common import AT_CMD - -( - "A/", # Re-issues the Last Command Given - "ATA", # Call answer - "ATD", # Mobile Originated Call to Dial A Number - "ATD>", # Originate call from active memory(1) - "ATD>", # Originate call from active memory(2) - "ATDL", # Redial last telephone number used - "ATE", # Enable command echo - "ATH", # Disconnect existing call - "ATI", # Display product identification information - "ATL", # Set monitor speaker loudness - "ATM", # Set monitor speaker mode - "+++", # Switch from data mode to command mode - "ATO", # Switch from command mode to data mode - "ATP", # Select pulse dialling - "ATQ", # Set Result Code Presentation Mode - "ATS0", # Set number of rings before automatically answering the call - "ATS3", # Set command line termination character - "ATS4", # Set response formatting character - "ATS5", # Set command line editing character - "ATS6", # Pause before blind dialling - "ATS7", # Set number of seconds to wait for connection completion - "ATS8", # Set number of seconds to wait for comma dial modifier encountered in dial string of D command - "ATS10", # Set disconnect delay after indicating the absence of data carrier - "ATT", # Select tone dialing - "ATV", # TA response format - "ATX", # Set connect result code format and monitor call progress - "ATZ", # Restore the user setting from ME - "AT&C", # Set DCD function mode - "AT&D", # Set DTR function mode - "AT&F", # Factory defined configuration - "AT&V", # Display current configuration - "AT&W", # Save the user setting to ME - "AT+GCAP", # Request overall capabilities - "AT+GMI", # Request manufacturer identification - "AT+GMM", # Request TA model identification - "AT+GMR", # Request TA revision identification of software release - "AT+GOI", # Request global object identification - "AT+GSN", # Request TA serial number identification (IMEI) - "AT+ICF", # Set TE-TA control character framing - "AT+IFC", # Set TE-TA local data flow control - "AT+IPR", # Set TE-TA fixed local rate - "AT+HVOIC", # Disconnect Voice Call Only -) - - -class V25Ter: - def __init__(self): - pass diff --git a/m5stack/libs/driver/umodem/__init__.py b/m5stack/libs/driver/umodem/__init__.py new file mode 100644 index 00000000..889e46de --- /dev/null +++ b/m5stack/libs/driver/umodem/__init__.py @@ -0,0 +1,4 @@ +from .modem import UModem +from .modem import Command +from .modem import Response +from .parser import Parser diff --git a/m5stack/libs/driver/umodem/modem.py b/m5stack/libs/driver/umodem/modem.py new file mode 100644 index 00000000..edc8c325 --- /dev/null +++ b/m5stack/libs/driver/umodem/modem.py @@ -0,0 +1,130 @@ +# SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import time + + +def _measure_time(func): + def wrapper(*args, **kwargs): + start_time = time.ticks_ms() + result = func(*args, **kwargs) + end_time = time.ticks_ms() + execution_time = end_time - start_time + print(f"total time consumed to execute {func}: {execution_time}ms") + return result + + return wrapper + + +class Response: + ERR_NONE = 0 + ERR_GENERIC = 1 + ERR_TIMEOUT = 2 + + def __init__(self, status_code, content): + self.status_code = int(status_code) + self.content = content + + +class Command: + CMD_TEST = "=?" + CMD_READ = "?" + CMD_WRITE = "=" + CMD_EXECUTION = "" + + def __init__(self, cmd, type, *args, rsp1="OK", rsp2="ERROR", timeout=2000) -> None: + self.cmd = cmd + self.cmd_type = type + self.args = [] + for arg in args: + if isinstance(arg, str): + self.args.append(f'"{arg}"') + if isinstance(arg, int): + self.args.append(str(arg)) + + self.rsp1 = rsp1 + self.rsp2 = rsp2 + + self.timeout = timeout + + def __call__(self) -> str: + t = ["AT", self.cmd, self.cmd_type] + for arg in self.args: + t.append(arg) + t.append(",") + self.args and t.pop() + t.append("\r\n") + return "".join(t) + + +class UModem: + def __init__(self, uart, verbose=False): + self.uart = uart + self._verbose = verbose + + def execute(self, command: Command, repeat: bool = False, line_end: str = "\r\n") -> Response: + # clear the uart buffer + if self.uart.any(): + self.uart.read(self.uart.any()) + + # execute the AT command + self._verbose and print("TE -> TA:", repr(command())) + self.uart.write(command()) + + # wait for response + return self.response_at_command2(command, repeat, line_end=line_end) + + @_measure_time + def response_at_command2( + self, command: Command, repeat: bool = False, clean_output: bool = True, line_end="\r\n" + ) -> Response: + # Support vars + find_keyword = False + output = bytearray() + error = Response.ERR_NONE + rsp1 = command.rsp1.encode("utf-8") + rsp2 = command.rsp2.encode("utf-8") + line_end = line_end.encode("utf-8") + + ticks = time.ticks_ms() + while time.ticks_diff(time.ticks_ms(), ticks) < command.timeout: + if self.uart.any() == 0: + time.sleep_ms(10) + continue + + line = self.uart.read(self.uart.any()) + self._verbose and print("TE <- TA:", repr(line)) + output.extend(line) + + # Do we have an error? + if output.rfind(rsp2) != -1: + if output.endswith(line_end): + print("Get AT command error response:", repr(output)) + error = Response.ERR_GENERIC + find_keyword = True + + # If we had a pre-end, do we have the expected end? + if output.rfind(rsp1) != -1: + if output.endswith(line_end): + find_keyword = True + + if find_keyword: + break + + if time.ticks_diff(time.ticks_ms(), ticks) > command.timeout: + print("Timeout for command:", repr(command.cmd)) + error = Response.ERR_TIMEOUT + + # Also, clean output if needed + # if clean_output: + # output = output.replace("OK", "") + # output = output.replace("\r\n", "") + # output = output.replace("\r", "") + # if output.startswith("\n"): + # output = output[1:] + # if output.endswith("\n"): + # output = output[:-1] + + # Return + return Response(error, output.decode()) diff --git a/m5stack/libs/driver/umodem/parser.py b/m5stack/libs/driver/umodem/parser.py new file mode 100644 index 00000000..41b1c484 --- /dev/null +++ b/m5stack/libs/driver/umodem/parser.py @@ -0,0 +1,44 @@ +# SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + + +def _logging(func): + def wrapper(*args, **kwargs): + result = func(*args, **kwargs) + print(f"func {func.__name__} return: {result}") + return result + + return wrapper + + +class Parser: + def __init__(self, data): + self.data = data + self.index = 0 + + @_logging + def skipuntil(self, chr: str) -> int: + index = self.data.find(chr, self.index) + if index != -1: + self.index = index + len(chr) + return self.index + + @_logging + def parseint(self, chr=",") -> int: + s = self.parseutil(chr) + if s == "": + return -9999 + return int(s) + + @_logging + def parseutil(self, chr: str) -> str: + index = self.data.find(chr, self.index) + if index == -1: + return "" + ret = self.data[self.index : index] + self.index += len(ret) + len(chr) + return ret + + def reset(self): + self.index = 0 From eb73f17c459330741169eccb6d31d8143c37609e Mon Sep 17 00:00:00 2001 From: Tinyu Date: Fri, 18 Apr 2025 16:25:35 +0800 Subject: [PATCH 053/322] lib/base: Add DTU NB-IoT-Series base support. Signed-off-by: Tinyu --- docs/en/base/dtu_nbiot.rst | 95 ++ docs/en/base/index.rst | 1 + docs/en/conf.py | 1 + docs/en/refs/base.dtu_nbiot.ref | 51 + .../base_nbiot_atoms3_http_example.m5f2 | 1 + .../base_nbiot_atoms3_http_example.py | 56 + .../base_nbiot_atoms3_mqtt_example.m5f2 | 1 + .../base_nbiot_atoms3_mqtt_example.py | 52 + m5stack/libs/base/__init__.py | 1 + m5stack/libs/base/dtu_nbiot.py | 41 + m5stack/libs/base/manifest.py | 1 + m5stack/libs/driver/manifest.py | 8 + m5stack/libs/driver/simcom/__init__.py | 2 +- m5stack/libs/driver/simcom/common.py | 8 +- m5stack/libs/driver/simcom/sim7020.py | 1418 +++++++++++++---- m5stack/libs/driver/simcom/sim7028.py | 2 +- m5stack/libs/driver/simcom/sim7080.py | 2 +- m5stack/libs/driver/simcom/sim7600.py | 4 +- m5stack/libs/driver/simcom/sim800/__init__.py | 2 +- .../libs/driver/simcom/toolkit/__init__.py | 3 + .../libs/driver/simcom/toolkit/requests2.py | 13 +- .../driver/simcom/toolkit/umqtt/__init__.py | 7 +- .../driver/simcom/toolkit/umqtt/robust.py | 2 +- .../driver/simcom/toolkit/umqtt/simple.py | 17 + m5stack/libs/driver/simcom/utils.py | 18 +- m5stack/libs/driver/umodem/__init__.py | 4 - m5stack/libs/driver/umodem/modem.py | 13 +- m5stack/libs/driver/umodem/parser.py | 20 +- m5stack/libs/module/nbiot.py | 35 +- m5stack/libs/unit/nbiot.py | 35 +- 30 files changed, 1533 insertions(+), 381 deletions(-) create mode 100644 docs/en/base/dtu_nbiot.rst create mode 100644 docs/en/refs/base.dtu_nbiot.ref create mode 100644 examples/base/dtu_nbiot/base_nbiot_atoms3_http_example.m5f2 create mode 100644 examples/base/dtu_nbiot/base_nbiot_atoms3_http_example.py create mode 100644 examples/base/dtu_nbiot/base_nbiot_atoms3_mqtt_example.m5f2 create mode 100644 examples/base/dtu_nbiot/base_nbiot_atoms3_mqtt_example.py create mode 100644 m5stack/libs/base/dtu_nbiot.py create mode 100644 m5stack/libs/driver/simcom/toolkit/__init__.py diff --git a/docs/en/base/dtu_nbiot.rst b/docs/en/base/dtu_nbiot.rst new file mode 100644 index 00000000..64eb7fe7 --- /dev/null +++ b/docs/en/base/dtu_nbiot.rst @@ -0,0 +1,95 @@ +Atom DTU NBIoT Base +==================== + +.. sku: K059 + +.. include:: ../refs/base.dtu_nbiot.ref + +This is the driver library for the ATOM DTU NBIoT Base to accept and send data from the DTU NBIoT. + +Support the following products: + + ================== ==================== + |Atom DTU NBIoT| |Atom DTU NBIoT CN| + ================== ==================== + + +UiFlow2 Example +--------------- + +NBIoT HTTP Example +^^^^^^^^^^^^^^^^^^^ + +Open the |base_nbiot_atoms3_http_example.m5f2| project in UiFlow2. + +This example shows how to send HTTP request using the Atom DTU NBIoT Base. + +UiFlow2 Code Block: + + |example.png| + +Example output: + + Output of received NBIoT message data via serial port. + +MicroPython Example +------------------- + +NBIoT HTTP Example +^^^^^^^^^^^^^^^^^^^^ + +This example shows how to send HTTP request using the Atom DTU NBIoT Base. + +MicroPython Code Block: + + .. literalinclude:: ../../../examples/base/dtu_nbiot/base_nbiot_atoms3_http_example.py + :language: python + :linenos: + +Example output: + + Output of received NBIoT message data via serial port. + +MQTT Example +^^^^^^^^^^^^^^ + +Open the |base_nbiot_atoms3_mqtt_example.m5f2| project in UiFlow2. + +This example shows how to send MQTT message using the Atom DTU NBIoT Base. + +UiFlow2 Code Block: + + |example.png| + +Example output: + + Output of received NBIoT message data via serial port. + +MicroPython Example +------------------- + +MQTT Example +^^^^^^^^^^^^^^^^^^^^ + +This example shows how to send MQTT message using the Atom DTU NBIoT Base. + +MicroPython Code Block: + + .. literalinclude:: ../../../examples/base/dtu_nbiot/base_nbiot_atoms3_mqtt_example.py + :language: python + :linenos: + +Example output: + + Output of received NBIoT message data via serial port. + +**API** +------- + +AtomDTUNBIoT +^^^^^^^^^^^^ + +.. autoclass:: base.dtu_nbiot.AtomDTUNBIoT + :members: + +See :ref:`base.AtomDTUNBIoT.Methods ` for more details. diff --git a/docs/en/base/index.rst b/docs/en/base/index.rst index 5ae230eb..e2248a28 100644 --- a/docs/en/base/index.rst +++ b/docs/en/base/index.rst @@ -6,6 +6,7 @@ Base atom_socket.rst dtu_lorawan.rst + dtu_nbiot.rst atom_can.rst atom_gps.rst display.rst diff --git a/docs/en/conf.py b/docs/en/conf.py index 9cb2d838..bc7ca3f3 100644 --- a/docs/en/conf.py +++ b/docs/en/conf.py @@ -53,6 +53,7 @@ "network", "m5can", "rf433", + "utime", ] autodoc_default_options = { diff --git a/docs/en/refs/base.dtu_nbiot.ref b/docs/en/refs/base.dtu_nbiot.ref new file mode 100644 index 00000000..2872585c --- /dev/null +++ b/docs/en/refs/base.dtu_nbiot.ref @@ -0,0 +1,51 @@ +.. |Atom DTU NBIoT| image:: https://static-cdn.m5stack.com/resource/docs/products/atom/atom_dtu_nb/atom_dtu_nb_01.webp + :target: https://docs.m5stack.com/en/atom/atom_dtu_nb + :height: 200px + :width: 200px + +.. |Atom DTU NBIoT CN| image:: https://static-cdn.m5stack.com/resource/docs/products/atom/atom_dtu_nb_cn/atom_dtu_nb_cn_01.webp + :target: https://docs.m5stack.com/en/atom/atom_dtu_nb_cn + :height: 200px + :width: 200px + + +.. |init.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/lorawan470/init.png + +.. |get_abp_config.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/lorawan470/get_abp_config.png +.. |get_otaa_config.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/lorawan470/get_otaa_config.png +.. |check_join_status.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/lorawan470/check_join_status.png +.. |check_uplink_status.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/lorawan470/check_uplink_status.png +.. |check_downlink_data.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/lorawan470/check_downlink_data.png +.. |uart_port_id.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/lorawan470/uart_port_id.png +.. |config_otaa.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/lorawan470/config_otaa.png +.. |config_abp.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/lorawan470/config_abp.png +.. |config.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/lorawan470/config.png +.. |set_rx_window_param.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/lorawan470/set_rx_window_param.png +.. |set_class_mode.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/lorawan470/set_class_mode.png +.. |set_uplink_downlink_mode.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/lorawan470/set_uplink_downlink_mode.png +.. |set_join_mode.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/lorawan470/set_join_mode.png +.. |join.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/lorawan470/start_join.png +.. |join_stop.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/lorawan470/stop_join.png +.. |set_uplink_app_port.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/lorawan470/set_uplink_app_port.png +.. |send_data.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/lorawan470/send_data.png + +.. |example.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/lorawan470/example.png + +.. |base_nbiot_atoms3_mqtt_example.m5f2| raw:: html + + + base_nbiot_atoms3_mqtt_example.m5f2 + + + +.. |base_nbiot_atoms3_http_example.m5f2| raw:: html + + + base_nbiot_atoms3_http_example.m5f2 + \ No newline at end of file diff --git a/examples/base/dtu_nbiot/base_nbiot_atoms3_http_example.m5f2 b/examples/base/dtu_nbiot/base_nbiot_atoms3_http_example.m5f2 new file mode 100644 index 00000000..2b1556e0 --- /dev/null +++ b/examples/base/dtu_nbiot/base_nbiot_atoms3_http_example.m5f2 @@ -0,0 +1 @@ +{"version":"V2.0","versionNumber":"V2.2.5","type":"atoms3","components":[{"name":"screen","type":"screen","layer":0,"screenId":"builtin","screenName":"","id":"__atoms3_screen","createTime":1744876293891,"x":0,"y":0,"width":128,"height":128,"backgroundColor":"#000000","size":0,"isSelected":true},{"name":"title0","type":"title","layer":1,"screenId":"builtin","screenName":"","id":"x1u$Fd^Z=cgvW6n8","createTime":1744879590222,"x":0,"y":0,"color":"#ffffff","backgroundColor":"#0000FF","text":"NBIoT HTTP","textOffset":3,"font":"Widgets.FONTS.DejaVu18","isSelected":false},{"name":"label0","type":"label","layer":2,"screenId":"builtin","screenName":"","id":"iQepq+9_cG93ynY0","createTime":1744879603869,"x":23,"y":43,"color":"#ffffff","backgroundColor":"#222222","text":"Press to","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false},{"name":"label1","type":"label","layer":3,"screenId":"builtin","screenName":"","id":"xKtkpju^t2j-ITZ8","createTime":1744879645108,"x":23,"y":74,"color":"#ffffff","backgroundColor":"#222222","text":"Request","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false}],"resources":[{"hardware":["hardware_button","hardware_pin_button","imu","ir","uart"]},{"base":["base_nbiot"]}],"units":[],"hats":[],"bases":[{"type":"base_nbiot","name":"base_nbiot","id":"d=vp1Yya%YHwL4kz","createTime":1744881565192,"initBlockType":"base_nbiot_init","initBlockId":"/MCHV1uq:CIi}!{V]H1g"}],"i2cs":[],"blockly":"true2false1152008None156uart2trueBtnAwasPressed1http://httpbin.org/postContent-Typeapplication/jsonCustom-HeaderMyHeaderValuemessageHello from M5Stack!statusactivehello M5hello M5","screen":[{"simulationName":"Built-in","type":"builtin","width":128,"height":128,"scale":1.3,"screenName":"","blockId":"","screenColorType":0,"id":"builtin","createTime":1744876293889}],"logicWhenNum":0,"customList":[]} \ No newline at end of file diff --git a/examples/base/dtu_nbiot/base_nbiot_atoms3_http_example.py b/examples/base/dtu_nbiot/base_nbiot_atoms3_http_example.py new file mode 100644 index 00000000..aafe6e30 --- /dev/null +++ b/examples/base/dtu_nbiot/base_nbiot_atoms3_http_example.py @@ -0,0 +1,56 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import os, sys, io +import M5 +from M5 import * +from hardware import UART +from base import AtomDTUNBIoT + + +title0 = None +label0 = None +label1 = None +uart2 = None +base_nbiot = None + + +def setup(): + global title0, label0, label1, uart2, base_nbiot + + M5.begin() + title0 = Widgets.Title("NBIoT HTTP", 3, 0xFFFFFF, 0x0000FF, Widgets.FONTS.DejaVu18) + label0 = Widgets.Label("Press to", 23, 43, 1.0, 0xFFFFFF, 0x222222, Widgets.FONTS.DejaVu18) + label1 = Widgets.Label("Request", 23, 74, 1.0, 0xFFFFFF, 0x222222, Widgets.FONTS.DejaVu18) + + uart2 = UART(2, baudrate=115200, bits=8, parity=None, stop=1, tx=5, rx=6) + base_nbiot = AtomDTUNBIoT(uart2) + + +def loop(): + global title0, label0, label1, uart2, base_nbiot + M5.update() + if BtnA.wasPressed(): + base_nbiot.http_request( + 1, + "http://httpbin.org/post", + {"Content-Type": "application/json", "Custom-Header": "MyHeaderValue"}, + {"message": "Hello from M5Stack!", "status": "active"}, + ) + print(base_nbiot.data_content) + print(base_nbiot.response_code) + + +if __name__ == "__main__": + try: + setup() + while True: + loop() + except (Exception, KeyboardInterrupt) as e: + try: + from utility import print_error_msg + + print_error_msg(e) + except ImportError: + print("please update to latest firmware") diff --git a/examples/base/dtu_nbiot/base_nbiot_atoms3_mqtt_example.m5f2 b/examples/base/dtu_nbiot/base_nbiot_atoms3_mqtt_example.m5f2 new file mode 100644 index 00000000..7b0cd07a --- /dev/null +++ b/examples/base/dtu_nbiot/base_nbiot_atoms3_mqtt_example.m5f2 @@ -0,0 +1 @@ +{"version":"V2.0","versionNumber":"V2.2.5","type":"atoms3","components":[{"name":"screen","type":"screen","layer":0,"screenId":"builtin","screenName":"","id":"__atoms3_screen","createTime":1744871606162,"x":0,"y":0,"width":128,"height":128,"backgroundColor":"#000000","size":0,"isSelected":true},{"name":"title0","type":"title","layer":1,"screenId":"builtin","screenName":"","id":"zBM^0sj1ywyuVeBz","createTime":1744881300111,"x":0,"y":0,"color":"#ffffff","backgroundColor":"#0000FF","text":"NBIoT MQTT","textOffset":3,"font":"Widgets.FONTS.DejaVu18","isSelected":false}],"resources":[{"hardware":["hardware_button","hardware_pin_button","imu","ir","uart"]},{"base":["base_nbiot"]}],"units":[],"hats":[],"bases":[{"type":"base_nbiot","name":"base_nbiot","id":"vm@#L74^GT`lWzbS","createTime":1744880777065,"initBlockType":"base_nbiot_init","initBlockId":"eS2C4zy{LchY?s(?XZ!@"}],"i2cs":[],"blockly":"true2false1152008None156uart2mqtt.m5stack.com1883m5-mqtt-2024120trueSubTopic0hello M5hello M5","screen":[{"simulationName":"Built-in","type":"builtin","width":128,"height":128,"scale":1.3,"screenName":"","blockId":"","screenColorType":0,"id":"builtin","createTime":1744871606161}],"logicWhenNum":0,"customList":[]} \ No newline at end of file diff --git a/examples/base/dtu_nbiot/base_nbiot_atoms3_mqtt_example.py b/examples/base/dtu_nbiot/base_nbiot_atoms3_mqtt_example.py new file mode 100644 index 00000000..165ba087 --- /dev/null +++ b/examples/base/dtu_nbiot/base_nbiot_atoms3_mqtt_example.py @@ -0,0 +1,52 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import os, sys, io +import M5 +from M5 import * +from base import AtomDTUNBIoT +from hardware import UART + + +title0 = None +base_nbiot = None +uart2 = None + + +def base_nbiot_SubTopic_event(_topic, _msg): # noqa: N802 + global title0, base_nbiot, uart2 + print(_topic) + print(_msg) + + +def setup(): + global title0, base_nbiot, uart2 + + M5.begin() + title0 = Widgets.Title("NBIoT MQTT", 3, 0xFFFFFF, 0x0000FF, Widgets.FONTS.DejaVu18) + + uart2 = UART(2, baudrate=115200, bits=8, parity=None, stop=1, tx=5, rx=6) + base_nbiot = AtomDTUNBIoT(uart2) + base_nbiot.mqtt_server_connect("mqtt.m5stack.com", 1883, "m5-mqtt-2024", "", "", 120) + base_nbiot.mqtt_subscribe_topic("SubTopic", base_nbiot_SubTopic_event, 0) + + +def loop(): + global title0, base_nbiot, uart2 + M5.update() + base_nbiot.mqtt_polling_loop() + + +if __name__ == "__main__": + try: + setup() + while True: + loop() + except (Exception, KeyboardInterrupt) as e: + try: + from utility import print_error_msg + + print_error_msg(e) + except ImportError: + print("please update to latest firmware") diff --git a/m5stack/libs/base/__init__.py b/m5stack/libs/base/__init__.py index f48d67ba..89e0257c 100644 --- a/m5stack/libs/base/__init__.py +++ b/m5stack/libs/base/__init__.py @@ -5,6 +5,7 @@ _attrs = { "ATOMCANBase": "atom_can", "AtomDTULoRaWANBase": "dtu_lorawan", + "AtomDTUNBIoT": "dtu_nbiot", "ATOMGPSBase": "atom_gps", "ATOMSocketBase": "atom_socket", "ATOMEchoBase": "echo", diff --git a/m5stack/libs/base/dtu_nbiot.py b/m5stack/libs/base/dtu_nbiot.py new file mode 100644 index 00000000..c975f9e6 --- /dev/null +++ b/m5stack/libs/base/dtu_nbiot.py @@ -0,0 +1,41 @@ +# SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import machine +from driver.simcom.sim7020 import SIM7020 +from driver.simcom.common import Modem +import sys + +if sys.platform != "esp32": + from typing import Literal + + +class AtomDTUNBIoT(SIM7020, Modem): + """Create an AtomDTUNBIoT object + + :param int id: The UART ID to use (0, 1, or 2). Default is 2. + :param port: A list or tuple containing the TX and RX pin numbers. + :type port: list | tuple + + UiFlow2 Code Block: + + |init.png| + + MicroPython Code Block: + + .. code-block:: python + + from base import AtomDTUNBIoT + + dtu_nbiot = AtomDTUNBIoT(0, (22, 19)) + """ + + def __init__(self, uart, verbose=False): + self.uart = uart + self.verbose = verbose + Modem.__init__(self, uart=self.uart, verbose=verbose) + SIM7020.__init__(self, uart=self.uart, verbose=verbose) + + if not self.check_modem_is_ready(): + raise Exception("NBIoT Base not found in bus") diff --git a/m5stack/libs/base/manifest.py b/m5stack/libs/base/manifest.py index 375c7ab8..86e27dd8 100644 --- a/m5stack/libs/base/manifest.py +++ b/m5stack/libs/base/manifest.py @@ -10,6 +10,7 @@ "atom_socket.py", "display.py", "dtu_lorawan.py", + "dtu_nbiot.py", "echo.py", "hdriver.py", "motion.py", diff --git a/m5stack/libs/driver/manifest.py b/m5stack/libs/driver/manifest.py index 7c3bc309..bcb078f0 100644 --- a/m5stack/libs/driver/manifest.py +++ b/m5stack/libs/driver/manifest.py @@ -46,6 +46,14 @@ "simcom/sim7020.py", "simcom/sim7028.py", "simcom/sim7080.py", + "simcom/utils.py", + "simcom/toolkit/requests2.py", + "simcom/toolkit/umqtt/__init__.py", + "simcom/toolkit/umqtt/simple.py", + "simcom/toolkit/umqtt/robust.py", + "umodem/__init__.py", + "umodem/modem.py", + "umodem/parser.py", "ads1110.py", "ads1100.py", "adxl34x.py", diff --git a/m5stack/libs/driver/simcom/__init__.py b/m5stack/libs/driver/simcom/__init__.py index e46cb4b2..ae748eb3 100644 --- a/m5stack/libs/driver/simcom/__init__.py +++ b/m5stack/libs/driver/simcom/__init__.py @@ -1,3 +1,3 @@ -# SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD # # SPDX-License-Identifier: MIT diff --git a/m5stack/libs/driver/simcom/common.py b/m5stack/libs/driver/simcom/common.py index 17f7d944..e13cd858 100644 --- a/m5stack/libs/driver/simcom/common.py +++ b/m5stack/libs/driver/simcom/common.py @@ -87,7 +87,11 @@ def get_signal_strength(self): # Get the signal strength csq = AT_CMD("AT+CSQ", "+CSQ:", 3) output, error = self.execute_at_command(csq) - return False if error else int(output.split(",")[0][-1]) + return ( + False + if error + else int(utils.extract_text(output + "\n", "+CSQ: ", "\n").split(",")[0]) + ) def get_gprs_registration_status(self): # Get the registration status with the gprs network @@ -105,7 +109,7 @@ def get_gprs_network_status(self): # Get attach or detach from the GPRS network cgatt = AT_CMD("AT+CGATT?", "+CGATT:", 3) output, error = self.execute_at_command(cgatt) - return False if error else int(output[-1]) + return False if error else int(output[-1]) == 1 def set_gprs_network_state(self, enable=1): # Set attach or detach from the GPRS network diff --git a/m5stack/libs/driver/simcom/sim7020.py b/m5stack/libs/driver/simcom/sim7020.py index 329114ea..ac7ad4cd 100644 --- a/m5stack/libs/driver/simcom/sim7020.py +++ b/m5stack/libs/driver/simcom/sim7020.py @@ -1,15 +1,756 @@ -# SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD # # SPDX-License-Identifier: MIT -import umodem -import machine +import time import socket -from .toolkit import requests2 -from .toolkit import umqtt +import machine +import micropython +from driver.umodem import modem as umodem +from driver.umodem.parser import Parser +from driver.simcom.common import utils +from driver.simcom.toolkit import requests2 +from driver.simcom.toolkit import umqtt + + +class SIMComError(Exception): + D_GENERIC = 0 + D_TCPIP_ERR_INFO = 1 # 11.5 + D_TCPIP_ERR = 2 # 11.6 + D_DNS_ERROR_CODE = 3 + + _err_desc = { + D_GENERIC: { + 2: "AT command timeout({})", + }, + D_DNS_ERROR_CODE: { + 10: "DNS GENERAL ERROR({})", + }, + D_TCPIP_ERR_INFO: { + 0: "Connection time out({})", + 1: "Bind port failed({})", + 2: "Port overflow({})", + 3: "Create socket failed({})", + 4: "Network is already opened({})", + 5: "Network is already closed({})", + 6: "No clients connected({})", + 7: "No active client({})", + 8: "Network not opened({})", + 9: "Client index overflow({})", + 10: "Connection is already created({})", + 11: "Connection is not created({})", + 12: "Invalid parameter({})", + 13: "Operation not supported({})", + 14: "DNS query failed({})", + 15: "TCP busy({})", + 16: "Netclose failed for socket opened({})", + 17: "Sending time out({})", + 18: "Sending failure for network error({})", + 19: "Open failure for network error({})", + 20: "Server is already listening({})", + 21: "No data({})", + 22: "Port overflow({})", + }, + D_TCPIP_ERR: { + 0: "Operation succeeded({})", + 1: "Network failure({})", + 2: "Network not opened({})", + 3: "Wrong parameter({})", + 4: "Operation not supported({})", + 5: "Failed to create socket({})", + 6: "Failed to bind socket({})", + 7: "TCP server is already listening({})", + 8: "Busy({})", + 9: "Sockets opened({})", + 10: "Timeout({})", + 11: "DNS parse failed for AT+CIPOPEN({})", + 12: "Unknown error({})", + }, + } + + def __init__(self, *args): + if len(args) == 1: + super().__init__(args[0]) + return + elif len(args) == 3: + domain, errno, cmd = args + msg = self._err_desc[domain].get(errno, "Unknown error({})") + super().__init__(msg.format(repr(cmd))) + + +# TCP/UDP socket +class _socket: + AF_INET = socket.AF_INET + AF_INET6 = socket.AF_INET6 + + SOCK_STREAM = socket.SOCK_STREAM + SOCK_DGRAM = socket.SOCK_DGRAM + SOCK_RAW = socket.SOCK_RAW + + IPPROTO_IP = socket.IPPROTO_IP + IPPROTO_TCP = socket.IPPROTO_TCP + IPPROTO_UDP = socket.IPPROTO_UDP + + _proto_type = { + IPPROTO_TCP: "TCP", + IPPROTO_UDP: "UDP", + } + + _STATE_OPEN = 0 + _STATE_CLOSE = 1 + + def __init__(self, modem: "SIM7020", af=AF_INET, type=SOCK_STREAM, proto=IPPROTO_TCP): + self._modem = modem + self._domain = af + self._type = type + self._proto = proto + self._fd = self._modem.apply_fd() + self._address = None + if self._fd == -1: + raise SIMComError("No available socket") + + self._state = self._STATE_CLOSE + self._ringio = micropython.RingIO(1500) + self._timeout = 2000 + self.blocking = False + + def __del__(self): + self.close() + + def close(self) -> None: + if self._fd == -1: + return + cmd = umodem.Command( + "+CIPCLOSE", + umodem.Command.CMD_WRITE, + self._fd, + rsp1="CLOSE OK", + timeout=10000, + ) + + self._modem.execute(cmd) + self._modem.release_fd(self._fd) + self._fd = -1 + self._state = self._STATE_CLOSE + if hasattr(self, "_local_port"): + self._modem.release_port(self._local_port) + + def bind(self, address): + pass + + def listen(self, backlog): + pass + + def accept(self): + pass + + def connect(self, address: tuple[str, int]): + if self._fd == -1: + raise SIMComError("socket closed") + + if self._state == self._STATE_OPEN: + return + + self._address = address + # _fd is connect num + cmd = umodem.Command( + "+CIPSTART", + umodem.Command.CMD_WRITE, + self._fd, + str(self._proto_type.get(self._proto, "")), + str(address[0]), + str(address[1]), + rsp1="CONNECT OK", + timeout=160000, + ) + resp = self._modem.execute(cmd) + if resp.status_code == resp.ERR_NONE: + self._state = self._STATE_OPEN + return True + elif resp.status_code == resp.ERR_TIMEOUT: + raise SIMComError(SIMComError.D_GENERIC, resp.status_code, cmd) + elif resp.status_code == resp.ERR_GENERIC: + parser = Parser(resp.content) + parser.skipuntil("{},".format(self._fd).encode()) + err = parser.parseutil(b"\r\n") + raise SIMComError(err.decode()) + + def send(self, data: bytes | str | bytearray) -> int: + # data type check + data = utils.converter(data, bytes) + # print(f"tcp data: {data}") + total_length = len(data) + total_sent = 0 + + max_segment_size = 1460 + + while total_sent < total_length: + segment_size = min(max_segment_size, total_length - total_sent) + segment_data = data[total_sent : total_sent + segment_size] + + cmd = umodem.Command( + "+CIPSEND", + umodem.Command.CMD_WRITE, + self._fd, + segment_size, + rsp1=">", + timeout=self._modem._default_timeout, + ) + resp = self._modem.execute(cmd, line_end="") + if resp.status_code == resp.ERR_GENERIC: + parser = Parser(resp.content) + parser.skipuntil("+CME ERROR ".encode()) + err = parser.parseutil(b"\r\n") + raise SIMComError(SIMComError.D_TCPIP_ERR, err, cmd.cmd) + elif resp.status_code == resp.ERR_TIMEOUT: + raise SIMComError(SIMComError.D_GENERIC, resp.status_code, cmd.cmd) + + self._modem.uart.write(segment_data) + + cmd = umodem.Command( + "+CIPSEND", + umodem.Command.CMD_EXECUTION, + rsp1="SEND OK", + timeout=self._modem._default_timeout, + ) + resp = self._modem.response_at_command2(cmd) + + if resp.status_code == resp.ERR_GENERIC: + parser = Parser(resp.content) + parser.skipuntil("{},".format(self._fd).encode()) + err = parser.parseutil(b"\r\n") + raise SIMComError(err.decode()) + elif resp.status_code == resp.ERR_TIMEOUT: + raise SIMComError(SIMComError.D_GENERIC, resp.status_code, cmd.cmd) + + total_sent += segment_size + # print(f"Sent segment: {segment_size} bytes, Total: {total_sent}/{total_length}") + + return total_length + + def sendall(self, data: bytes | str | bytearray) -> None: + self.send(data) + + def sendto(self, data: bytes | str | bytearray, address) -> int: + if self._state == self._STATE_CLOSE: + self._local_port = self._modem.apply_port() + cipopen = umodem.Command( + "+CIPSTART", + umodem.Command.CMD_WRITE, + self._fd, + str(self._proto_type.get(self.IPPROTO_UDP, "")), + str(address[0]), + str(address[1]), + rsp1="CONNECT OK", + timeout=self._modem._default_timeout, + ) + resp: umodem.Response = self._modem.execute(cipopen) + if resp.status_code == resp.ERR_GENERIC: + parser = Parser(resp.content) + parser.skipuntil("{},".format(self._fd).encode()) + err = parser.parseutil(b"\r\n") + raise SIMComError(err.decode()) + raise SIMComError(err.decode()) + elif resp.status_code == resp.ERR_TIMEOUT: + raise SIMComError(SIMComError.D_GENERIC, resp.status_code, cipopen.cmd) + self._state = self._STATE_OPEN + + # data type check + data = utils.converter(data, bytes) + total_length = len(data) + total_sent = 0 + + max_segment_size = 1460 + + while total_sent < total_length: + segment_size = min(max_segment_size, total_length - total_sent) + segment_data = data[total_sent : total_sent + segment_size] + + cmd = umodem.Command( + "+CIPSEND", + umodem.Command.CMD_WRITE, + self._fd, + segment_size, + rsp1=">", + timeout=self._modem._default_timeout, + ) + resp = self._modem.execute(cmd, line_end="") + if resp.status_code == resp.ERR_GENERIC: + parser = Parser(resp.content) + parser.skipuntil("+CME ERROR ".encode()) + err = parser.parseutil(b"\r\n") + raise SIMComError(SIMComError.D_TCPIP_ERR, err, cmd.cmd) + elif resp.status_code == resp.ERR_TIMEOUT: + raise SIMComError(SIMComError.D_GENERIC, resp.status_code, cmd.cmd) + + self._modem.uart.write(segment_data) + + cmd = umodem.Command( + "+CIPSEND", + umodem.Command.CMD_EXECUTION, + rsp1="SEND OK", + timeout=self._modem._default_timeout, + ) + resp = self._modem.response_at_command2(cmd) + + if resp.status_code == resp.ERR_GENERIC: + parser = Parser(resp.content) + parser.skipuntil("{},".format(self._fd).encode()) + err = parser.parseutil(b"\r\n") + raise SIMComError(err.decode()) + elif resp.status_code == resp.ERR_TIMEOUT: + raise SIMComError(SIMComError.D_GENERIC, resp.status_code, cmd.cmd) + + total_sent += segment_size + # print(f"Sent segment: {segment_size} bytes, Total: {total_sent}/{total_length}") + + return total_length + + def _recv(self) -> None: + ciprxget = umodem.Command( + "+CIPRXGET", + umodem.Command.CMD_WRITE, + 4, + self._fd, + timeout=self._modem._default_timeout, + ) + resp = self._modem.execute(ciprxget) + if resp.status_code == resp.ERR_GENERIC: + parser = Parser(resp.content) + parser.skipuntil("+CME ERROR ".encode()) + err = parser.parseutil(b"\r\n") + raise SIMComError(SIMComError.D_TCPIP_ERR, err, ciprxget.cmd) + elif resp.status_code == resp.ERR_TIMEOUT: + raise SIMComError(SIMComError.D_GENERIC, resp.status_code, ciprxget.cmd) + + parser = Parser(resp.content) + parser.skipuntil("+CIPRXGET: 4,{},".format(self._fd).encode()) + to_recv = parser.parseint(b"\r\n") + # print(f"to_recv: {to_recv}") + if to_recv == 0: + return + + to_recv = (1500 - self._ringio.any()) if to_recv > (1500 - self._ringio.any()) else to_recv + + ciprxget = umodem.Command( + "+CIPRXGET", + umodem.Command.CMD_WRITE, + 2, + self._fd, + to_recv, + timeout=self._modem._default_timeout, + ) + resp = self._modem.execute(ciprxget) + if resp.status_code == resp.ERR_GENERIC: + parser = Parser(resp.content) + parser.skipuntil("+CME ERROR ".encode()) + errno = parser.parseutil(b"\r\n") + raise SIMComError(SIMComError.D_TCPIP_ERR_INFO, errno, ciprxget.cmd) + elif resp.status_code == resp.ERR_TIMEOUT: + raise SIMComError(SIMComError.D_GENERIC, resp.status_code, ciprxget.cmd) + + # prase data + parser = Parser(resp.content) + parser.skipuntil("+CIPRXGET: 2,{},".format(self._fd).encode()) + read_len = parser.parseint() + parser.skipuntil("+CIPRXGET: 2,{},{},".format(self._fd, read_len).encode()) + rest_len = parser.parseint(b"\r\n") + fstr = utils.converter( + "+CIPRXGET: 2,{},{},{}\r\n".format(self._fd, read_len, rest_len), type(resp.content) + ) + # print(f"fstr: {fstr}") + start = resp.content.find(fstr) + len(fstr) + + self._ringio.write(resp.content[start : start + read_len]) + + def recv(self, bufsize) -> bytes | None: + return self.recvfrom(bufsize) + + def readall(self) -> bytes: + out = bytearray(self._ringio.any()) + self._ringio.readinto(out) + last_time = time.ticks_ms() + while time.ticks_diff(time.ticks_ms(), last_time) < self._timeout: + self._recv() + buf = self._ringio.read() + out.extend(buf) + + return bytes(out) + + def recvfrom(self, bufsize: int) -> bytes | None: + if bufsize == -1: + return self.readall() + + if self._ringio.any() > bufsize: + return self._ringio.read(bufsize) + + read_len = self._ringio.any() + out = bytearray(read_len) + self._ringio.readinto(out) + last_time = time.ticks_ms() + while read_len < bufsize and time.ticks_diff(time.ticks_ms(), last_time) < self._timeout: + self._recv() + buf = self._ringio.read(bufsize - read_len) + out.extend(buf) + read_len += len(buf) + + # https://docs.python.org/3.4/library/io.html#io.RawIOBase.read + # "If the object is in non-blocking mode and no bytes are available, + # None is returned." + # This is actually very weird, as naive truth check will treat + # this as EOF. + if self.blocking is False and read_len == 0: + return None + + return bytes(out) + + def setsockopt(self, level, optname, value): + print("setsockopt is not implemented") + + def settimeout(self, timeout): + self._timeout = 2000 if timeout is None else timeout + + def setblocking(self, flag): + self.blocking = flag + + def makefile(self, mode): + return self + + def fileno(self): + return self._fd + + def read(self, *args) -> bytes | None: + return self.recvfrom(args[0] if len(args) > 0 else -1) + + def readinto(self, *args) -> int: + buf = args[0] + nbytes = len(buf) + if len(args) > 1: + nbytes = args[1] if args[1] < nbytes else nbytes + + if self._ringio.any() < nbytes: + self._recv() + + return self._ringio.readinto(buf, nbytes) + + def readline(self) -> bytes: + last_time = time.ticks_ms() + while ( + self._ringio.any() == 0 and time.ticks_diff(time.ticks_ms(), last_time) < self._timeout + ): + self._recv() + return self._ringio.readline() + + def write(self, *args) -> int: + data = args[0] + l = len(data) + max_len = len(data) + off = 0 + if len(args) == 2: + max_len = args[1] + if len(args) == 3: + off = args[1] + max_len = args[2] + if off > l: + off = l + l = l - off + max_len = l if l < max_len else max_len + # print(f"data[off : off + max_len]: {data[off : off + max_len]}") + return self.send(data[off : off + max_len]) + + +class _sockets(_socket): + AF_INET = socket.AF_INET + AF_INET6 = socket.AF_INET6 + + SOCK_STREAM = socket.SOCK_STREAM + SOCK_DGRAM = socket.SOCK_DGRAM + SOCK_RAW = socket.SOCK_RAW + + IPPROTO_IP = socket.IPPROTO_IP + IPPROTO_TCP = socket.IPPROTO_TCP + IPPROTO_UDP = socket.IPPROTO_UDP + + _proto_type = { + IPPROTO_TCP: "TCP", + IPPROTO_UDP: "UDP", + } + + _STATE_OPEN = 0 + _STATE_CLOSE = 1 + + def __init__(self, modem: "SIM7020", af=AF_INET, type=SOCK_STREAM, proto=IPPROTO_TCP): + self._modem = modem + self._domain = af + self._type = type + self._proto = proto + self._fd = self._modem.apply_session_id() + self._address = None + if self._fd == -1: + raise SIMComError("No available session") + + self._state = self._STATE_CLOSE + self._ringio = micropython.RingIO(1500) + self._timeout = 2000 + self.blocking = False + + def __del__(self): + self.close() + + def close(self) -> None: + if self._fd == -1: + return + # _fd is connect num + cmd = umodem.Command( + "+CTLSCLOSE", + umodem.Command.CMD_WRITE, + self._fd, + timeout=self._modem._default_timeout, + ) + self._modem.execute(cmd) + + self._modem.release_session_id(self._fd) + self._fd = -1 + self._state = self._STATE_CLOSE + if hasattr(self, "_local_port"): + self._modem.release_port(self._local_port) + + def bind(self, address): + pass + + def listen(self, backlog): + pass + + def accept(self): + pass + + def tls_config(self, address): + if self._fd == -1: + raise SIMComError("socket closed") + + if self._state == self._STATE_OPEN: + return + # Set the first SSL context to be used in the SSL connection + cmd = umodem.Command( + "+CTLSCFG", + umodem.Command.CMD_WRITE, + self._fd, # tid + 1, + str(address[0]), + 2, + address[1], + 3, + 0, # only support TCP + 4, + 0, + 5, + 2, + timeout=self._modem._default_timeout, + ) + self._modem.execute(cmd) + # TODO: check error + + def connect(self, address): + if self._fd == -1: + raise SIMComError("socket closed") + + if self._state == self._STATE_OPEN: + return + + self._address = address + cipstart = umodem.Command( + "+CTLSCONN", + umodem.Command.CMD_WRITE, + self._fd, + 1, + rsp1="CTLSCONN:", + timeout=60000, + ) + resp = self._modem.execute(cipstart) + + if resp.status_code == resp.ERR_NONE: + self._state = self._STATE_OPEN + self._connect_time = time.ticks_ms() + return True + elif resp.status_code == resp.ERR_TIMEOUT: + raise SIMComError(SIMComError.D_GENERIC, resp.status_code, cipstart.cmd) + elif resp.status_code == resp.ERR_GENERIC: + parser = Parser(resp.content) + parser.skipuntil("+CTLSCONN: {},".format(self._fd).encode()) + err = parser.parseutil(b"\r\n") + raise SIMComError(SIMComError.D_TCPIP_ERR, err, cipstart.cmd) + + def send(self, data: bytes | str | bytearray) -> int: + # data type check + data = utils.converter(data, bytes) + # print(f"raw data: {repr(data)}") + data = data.decode("utf-8").replace("\r\n", "\\r\\n") + # print(f"after raw data: {repr(data)}") + total_length = len(data) + total_sent = 0 + # print(f"data: {data}") + + max_segment_size = 1024 + + while total_sent < total_length: + segment_size = min(max_segment_size, total_length - total_sent) + segment_data = data[total_sent : total_sent + segment_size] + # print(f"segment_data: {segment_data}") + # print(f"raw segment_data: {repr(segment_data)}") + # print(f"segment_size: {segment_size}") + cmd = umodem.Command( + "+CTLSSEND", + umodem.Command.CMD_WRITE, + self._fd, + segment_size, + str(segment_data), + rsp1="CTLSSEND:", + timeout=self._modem._default_timeout, + ) + resp = self._modem.execute(cmd) + if resp.status_code == resp.ERR_GENERIC: + parser = Parser(resp.content) + parser.skipuntil("+CTLSSEND: {},".format(self._fd).encode()) + err = parser.parseutil(b"\r\n") + raise SIMComError(SIMComError.D_TCPIP_ERR, err, cmd.cmd) + elif resp.status_code == resp.ERR_TIMEOUT: + raise SIMComError(SIMComError.D_GENERIC, resp.status_code, cmd.cmd) + + total_sent += segment_size + # print(f"Sent segment: {segment_size} bytes, Total: {total_sent}/{total_length}") + + return total_length + + def sendall(self, data: bytes | str | bytearray) -> None: + self.send(data) + + def _recv(self) -> None: + cmd = umodem.Command( + "+CTLSRECV", + umodem.Command.CMD_WRITE, + self._fd, + 512, # max length to receive + 801, # encoder method + rsp1="CTLSRECV:", + timeout=self._modem._default_timeout, + ) + resp = self._modem.execute(cmd) + if resp.status_code == resp.ERR_GENERIC: + parser = Parser(resp.content) + parser.skipuntil("+CTLSRECV: {},".format(self._fd).encode()) + errno = parser.parseutil(b"\r\n") + raise SIMComError(SIMComError.D_TCPIP_ERR_INFO, errno, cmd.cmd) + elif resp.status_code == resp.ERR_TIMEOUT: + raise SIMComError(SIMComError.D_GENERIC, resp.status_code, cmd.cmd) + + # print(f"resp.content: {resp.content}") + parser = Parser(resp.content) + parser.skipuntil("+CTLSRECV: {},".format(self._fd).encode()) + recv_len = parser.parseint(b"\r\n") + # print(f"recv_len: {recv_len}") + if recv_len is not None and recv_len > 0: + fstr = utils.converter( + "+CTLSRECV: {},{},{}".format(self._fd, recv_len, '"'), type(resp.content) + ) + start = resp.content.find(fstr) + len(fstr) + data = resp.content[start : start + recv_len] + # print(f"data: {data}") + self._ringio.write(data) + + def recv(self, bufsize) -> bytes: + return self.recvfrom(bufsize) + + def readall(self) -> bytes: + out = bytearray(self._ringio.any()) + self._ringio.readinto(out) + last_time = time.ticks_ms() + while time.ticks_diff(time.ticks_ms(), last_time) < self._timeout: + self._recv() + buf = self._ringio.read() + out.extend(buf) + + return bytes(out) + + def recvfrom(self, bufsize) -> bytes: + if bufsize == -1: + return self.readall() + + if self._ringio.any() > bufsize: + return self._ringio.read(bufsize) + + read_len = self._ringio.any() + out = bytearray(read_len) + self._ringio.readinto(out) + last_time = time.ticks_ms() + while read_len < bufsize and time.ticks_diff(time.ticks_ms(), last_time) < self._timeout: + self._recv() + buf = self._ringio.read(bufsize - read_len) + out.extend(buf) + read_len += len(buf) + + # https://docs.python.org/3.4/library/io.html#io.RawIOBase.read + # "If the object is in non-blocking mode and no bytes are available, + # None is returned." + # This is actually very weird, as naive truth check will treat + # this as EOF. + if self.blocking is False and read_len == 0: + return None + + return bytes(out) + + def setsockopt(self, level, optname, value): + print("setsockopt is not implemented") + return None + + def settimeout(self, timeout): + print("settimeout is not implemented") + return None + + def setblocking(self, flag): + self.blocking = flag + return None + + def makefile(self, mode): + return self + + def fileno(self): + return self._fd + + def read(self, *args) -> bytes: + return self.recvfrom(args[0] if len(args) > 0 else -1) + + def readinto(self, *args) -> int: + buf = args[0] + nbytes = len(buf) + if len(args) > 1: + nbytes = args[1] if args[1] < nbytes else nbytes + + if self._ringio.any() < nbytes: + self._recv() + + return self._ringio.readinto(buf, nbytes) + + def readline(self) -> bytes: + last_time = time.ticks_ms() + while ( + self._ringio.any() == 0 and time.ticks_diff(time.ticks_ms(), last_time) < self._timeout + ): + self._recv() + return self._ringio.readline() + + def write(self, data) -> int: + return self.send(data) class SIM7020(umodem.UModem): + # tcp/udp socket fd list + _fds = [-1 for _ in range(6)] + + _session_id = [-1 for _ in range(6)] + + # tcp/udp socket port list + _used_port = [] + def __init__( self, uart=None, pwrkey_pin=None, reset_pin=None, power_pin=None, verbose=False ) -> None: @@ -29,7 +770,32 @@ def __init__( self._default_timeout = 3000 self._is_active = True - self._low_power_mode() + self._mqtt_client = None + self.reset() + self.active(True) + self.connect() + + """fd/port management""" + + def apply_fd(self) -> int: + for i in range(6): + if self._fds[i] == -1: + self._fds[i] = i + return i + return -1 + + def release_fd(self, fd: int) -> None: + self._fds[fd] = -1 + + def apply_session_id(self) -> int: + for i in range(6): + if self._session_id[i] == -1: + self._session_id[i] = i + 1 + return i + 1 + return -1 + + def release_session_id(self, session_id: int) -> None: + self._session_id[session_id] = -1 def _low_power_mode(self): """enter low power mode""" @@ -38,7 +804,7 @@ def _low_power_mode(self): if resp.status_code == resp.ERR_NONE: self._is_active = False - def active(self, is_active) -> bool: + def active(self, is_active=True) -> bool: """activate or deactivate the modem""" if self._is_active == is_active: return self._is_active @@ -48,9 +814,12 @@ def active(self, is_active) -> bool: "+CFUN", umodem.Command.CMD_WRITE, 1, timeout=self._default_timeout ) resp = self.execute(cmd) + self.connect() if resp.status_code == resp.ERR_NONE: self._is_active = True + time.sleep_ms(1000) else: + self.reset() self._low_power_mode() return self._is_active @@ -60,20 +829,41 @@ def connect(self, apn=None): # Check SIM card status cmd = umodem.Command("+CPIN", umodem.Command.CMD_READ, timeout=self._default_timeout) self.execute(cmd) + time.sleep_ms(100) # Check RF signal cmd = umodem.Command( "+CSQ", umodem.Command.CMD_EXECUTION, timeout=self._default_timeout ) self.execute(cmd) + # Enable getting data from network manually. + cmd = umodem.Command( + "+CIPRXGET", umodem.Command.CMD_WRITE, 1, timeout=self._default_timeout + ) + self.execute(cmd) + # Check PS service + cmd = umodem.Command("+CGREG", umodem.Command.CMD_READ, timeout=self._default_timeout) + self.execute(cmd) # Check PS service. 1 indicates PS has attached. cmd = umodem.Command("+CGATT", umodem.Command.CMD_READ, timeout=self._default_timeout) self.execute(cmd) - # PDN active success + # PDN automatically active success, cmd = umodem.Command("+CGACT", umodem.Command.CMD_READ, timeout=self._default_timeout) self.execute(cmd) # Query Network information, operator and network mode 9, NB-IOT network cmd = umodem.Command("+COPS", umodem.Command.CMD_READ, timeout=self._default_timeout) self.execute(cmd) + # Start Up Multi-IP Connection + cmd = umodem.Command( + "+CIPMUX", umodem.Command.CMD_WRITE, 1, timeout=self._default_timeout + ) + # When module is in multi-IP state, before this command is executed, it is necessary to process "AT+CSTT, AT+CIICR, AT+CIFSR". + self.execute(cmd) + # Start Task and Set APN,USER NAME,PASSWORD + cmd = umodem.Command( + "+CSTT", umodem.Command.CMD_WRITE, "CMNBIOT", timeout=self._default_timeout + ) + self.execute(cmd) + else: # manually activate PDP context # Disable RF @@ -95,9 +885,24 @@ def connect(self, apn=None): cmd = umodem.Command("+CGATT", umodem.Command.CMD_READ, timeout=self._default_timeout) self.execute(cmd) - # Attached PS domain and got IP address automatically + # Attached PS domain and got IP address and APN automatically from network + cmd = umodem.Command( + "+CGCONTRDP", + umodem.Command.CMD_EXECUTION, + rsp1="+CGCONTRDP:", + timeout=10000, + ) + self.execute(cmd) + # Bring Up Wireless Connection + cmd = umodem.Command("+CIICR", umodem.Command.CMD_EXECUTION, timeout=self._default_timeout) + self.execute(cmd) + # Get Local IP Address cmd = umodem.Command( - "+CGCONTRDP", umodem.Command.CMD_EXECUTION, timeout=self._default_timeout + "+CIFSR", + umodem.Command.CMD_EXECUTION, + rsp1="", + rsp2='"', + timeout=self._default_timeout, ) self.execute(cmd) @@ -109,6 +914,13 @@ def isconnected(self) -> bool: parameters = self._get_pdp_context_dynamic_parameters() return parameters[3] != "0.0.0.0" and parameters[3] != "" + def reset(self): + cmd = umodem.Command( + "+CRESET", umodem.Command.CMD_EXECUTION, rsp1="", rsp2="", timeout=500 + ) + self.execute(cmd) + time.sleep(3.5) + PIN_ERROR = -1 # PIN is not correct PIN_READY = 0 # MT is not pending for any password SIM_PIN = 1 # MT is waiting SIM PIN to be given @@ -162,17 +974,17 @@ def status(self, param=None) -> bool | int | str | tuple | None: ) resp = self.execute(cmd) if resp.status_code == umodem.Response.ERR_NONE: - parser = umodem.Parser(resp.content) - parser.skipuntil("+CSQ: ") + parser = Parser(resp.content) + parser.skipuntil(b"+CSQ: ") rssi = parser.parseint() return self._convert_rssi(rssi) elif param == "pin": cmd = umodem.Command("+CPIN", umodem.Command.CMD_READ, timeout=self._default_timeout) resp = self.execute(cmd) if resp.status_code == umodem.Response.ERR_NONE: - parser = umodem.Parser(resp.content) - parser.skipuntil("+CPIN: ") - code = parser.parseutil("\r\n").replace('"', "") + parser = Parser(resp.content) + parser.skipuntil(b"+CPIN: ") + code = parser.parseutil(b"\r\n").replace(b'"', b"") return self._cpin_code.get(code, self.PIN_ERROR) return self.PIN_ERROR elif param == "station": @@ -222,36 +1034,48 @@ def config(self, *args, **kwargs): def _get_pdp_context_dynamic_parameters(self) -> tuple: """PDP Context Read Dynamic Parameters""" cmd = umodem.Command( - "+CGCONTRDP", umodem.Command.CMD_WRITE, self._cid, timeout=self._default_timeout + "+CGCONTRDP", + umodem.Command.CMD_WRITE, + rsp1="+CGCONTRDP", + timeout=self._default_timeout, ) resp = self.execute(cmd) if resp.status_code == umodem.Response.ERR_NONE: - parser = umodem.Parser(resp.content) - parser.skipuntil("+CGCONTRDP: ") + parser = Parser(resp.content) + parser.skipuntil(b"+CGCONTRDP: ") cid = parser.parseint() # cid bearer_id = parser.parseint() # bearer_id - apn = parser.parseutil(",").strip('"') # apn - locl_ip = parser.parseutil(",").strip('"') # local_ip and subnet_mask - if locl_ip.count(".") == 7: + apn = parser.parseutil(b",").strip(b'"') # apn + locl_ip = parser.parseutil(b",").strip(b'"') # local_ip and subnet_mask + if locl_ip.count(b".") == 7: # ipv4 - parts = locl_ip.split(".") - locl_ip = ".".join(parts[:4]) - subnet_mask = ".".join(parts[4:]) - elif locl_ip.count(".") == 31: + parts = locl_ip.split(b".") + locl_ip = b".".join(parts[:4]) + subnet_mask = b".".join(parts[4:]) + elif locl_ip.count(b".") == 31: # ipv6 - parts = locl_ip.split(".") - locl_ip = ".".join(parts[:16]) - subnet_mask = ".".join(parts[16:]) + parts = locl_ip.split(b".") + locl_ip = b".".join(parts[:16]) + subnet_mask = b".".join(parts[16:]) else: - locl_ip = "0.0.0.0" - subnet_mask = "0.0.0.0" - gateway = parser.parseutil(",").strip('"') # gateway - gateway = gateway if gateway != "" else "0.0.0.0" - dns1 = parser.parseutil(",").strip('"') - dns1 = dns1 if dns1 != "" else "0.0.0.0" - dns2 = parser.parseutil(",").strip('"') - dns2 = dns2 if dns2 != "" else "0.0.0.0" - return (cid, bearer_id, apn, locl_ip, subnet_mask, gateway, dns1, dns2) + locl_ip = b"0.0.0.0" + subnet_mask = b"0.0.0.0" + gateway = parser.parseutil(b",").strip(b'"') # gateway + gateway = gateway if gateway != b"" else b"0.0.0.0" + dns1 = parser.parseutil(b",").strip(b'"') + dns1 = dns1 if dns1 != b"" else b"0.0.0.0" + dns2 = parser.parseutil(b",").strip(b'"') + dns2 = dns2 if dns2 != b"" else b"0.0.0.0" + return ( + cid, + bearer_id, + apn.decode(), + locl_ip.decode(), + subnet_mask.decode(), + gateway.decode(), + dns1.decode(), + dns2.decode(), + ) else: return (-9999, -9999, "", "0.0.0.0", "0.0.0.0", "0.0.0.0", "0.0.0.0", "0.0.0.0") @@ -262,57 +1086,139 @@ def _get_network_state(self): num = resp.content.count("+CENG: ") if num == 0: return ((), ()) - parser = umodem.Parser(resp.content) - parser.skipuntil("+CENG: ") + parser = Parser(resp.content) + parser.skipuntil(b"+CENG: ") station = [] station.append(parser.parseint()) station.append(parser.parseint()) station.append(parser.parseint()) - station.append(parser.parseutil(",").strip('"')) + station.append(parser.parseutil(",").strip(b'"')) station.append(parser.parseint()) station.append(parser.parseint()) station.append(parser.parseint()) station.append(parser.parseint()) station.append(parser.parseint()) - station.append(parser.parseutil(",").strip('"')) + station.append(parser.parseutil(",").strip(b'"')) station.append(parser.parseint()) station.append(parser.parseint()) - station.append(parser.parseint(chr="\r")) + station.append(parser.parseint(chr=b"\r")) neighbors = [] for _ in range(num - 1): - parser.skipuntil("+CENG: ") + parser.skipuntil(b"+CENG: ") neighbor = [] neighbor.append(parser.parseint()) neighbor.append(parser.parseint()) neighbor.append(parser.parseint()) - neighbor.append(parser.parseint(chr="\r")) + neighbor.append(parser.parseint(chr=b"\r")) neighbors.append(tuple(neighbor)) return (tuple(station), tuple(neighbors)) return ((), ()) - def getaddrinfo(self, host, port, af=0, type=0, proto=0, flags=0): + def apply_port(self) -> int: + for i in range(1024, 65535): + if i not in self._used_port: + self._used_port.append(i) + return i + return -1 + + def release_port(self, port: int) -> None: + self._used_port.remove(port) + + def getaddrinfo(self, host: str, port, af=0, type=0, proto=0, flags=0): """ The resulting list of 5-tuples has the following structure: (family, type, proto, canonname, sockaddr) """ + res = [] + + cmd = umodem.Command( + "+CDNSGIP={}".format(host.strip('"')), + umodem.Command.CMD_EXECUTION, + rsp1="+CDNSGIP:", + timeout=10000, + ) + + resp = self.execute(cmd) + self._verbose and print(resp.content) + self._verbose and print(resp.status_code) + + ip = "0.0.0.0" + if resp.status_code == umodem.Response.ERR_NONE: + parser = Parser(resp.content) + parser.skipuntil('+CDNSGIP: 1,"{}","'.format(host).encode()) + ip = parser.parseutil(b'"\r\n').decode() + elif resp.status_code == umodem.Response.ERR_TIMEOUT: + raise SIMComError(SIMComError.D_GENERIC, resp.status_code, cmd.cmd) + elif resp.status_code == umodem.Response.ERR_GENERIC: + raise SIMComError(SIMComError.D_DNS_ERROR_CODE, 10, cmd.cmd) + + res.append( + ( + socket.AF_INET, + socket.SOCK_STREAM, + socket.IPPROTO_TCP, + host, + (utils.converter(ip, str), port), + ) + ) + return res def socket(self, af=socket.AF_INET, type=socket.SOCK_STREAM, proto=socket.IPPROTO_TCP): - """see sim7600""" - raise NotImplementedError + return _socket(self, af, type, proto) def wrap_socket( self, - sock, + sock: _socket, # server_side=False, # key=None, # cert=None, - # cert_reqs=CERT_NONE, + # cert_reqs=None, # cadata=None, server_hostname=None, # do_handshake=True, ): - """see sim7600""" - raise NotImplementedError + # print(f"key: {key}") + # print(f"cert: {cert}") + # print(f"raw cert: {repr(cert)}") + # cert = cert.replace("\r\n", "\\r\\n") + # print(f"after replace cert: {cert}") + # print(f"cert_reqs: {cert_reqs}") + # print(f"cadata: {cadata}") + # print(f"server_hostname: {server_hostname}") + # print(f"do_handshake: {do_handshake}") + sock.close() + s = _sockets(self, sock._domain, sock._type, sock._proto) + s.tls_config(sock._address) + # if cert is not None: + # cmd = umodem.Command( + # "+CTLSCFG", + # umodem.Command.CMD_WRITE, + # 1, + # 6, + # timeout=self._default_timeout, + # ) + # self.execute(cmd) + # if cert is not None: + # cert_len = len(cert) + # chunk_size = 450 + # for i in range(0, cert_len, chunk_size): + # chunk = cert[i : i + chunk_size] + # last_chunk = i + chunk_size >= cert_len + # is_last_flag = 0 if last_chunk else 1 + + # cmd = umodem.Command( + # "+CTLSCERT", + # umodem.Command.CMD_WRITE, + # 1, + # 6, + # cert_len, + # is_last_flag, + # chunk, + # timeout=self._default_timeout, + # ) + # self.execute(cmd) + s.connect(sock._address) + return s """request method""" @@ -386,9 +1292,9 @@ def get_band(self) -> tuple: cmd = umodem.Command("+CBAND", umodem.Command.CMD_READ, timeout=self._default_timeout) resp = self.execute(cmd) if resp.status_code == umodem.Response.ERR_NONE: - parser = umodem.Parser(resp.content) - parser.skipuntil("+CBAND: ") - parts = parser.parseutil("\r\n").split(",") + parser = Parser(resp.content) + parser.skipuntil(b"+CBAND: ") + parts = parser.parseutil(b"\r\n").split(b",") return tuple([int(part) for part in parts]) return () @@ -396,37 +1302,38 @@ def get_manufacturer(self) -> str: cmd = umodem.Command("+CGMI", umodem.Command.CMD_EXECUTION, timeout=self._default_timeout) resp = self.execute(cmd) if resp.status_code == umodem.Response.ERR_NONE: - parser = umodem.Parser(resp.content) - parser.skipuntil("\n") - return parser.parseutil("\r\n") + parser = Parser(resp.content) + parser.skipuntil(b"\n") + return parser.parseutil(b"\r\n").decode() return "" def get_model_id(self) -> str: cmd = umodem.Command("+CGMM", umodem.Command.CMD_EXECUTION, timeout=self._default_timeout) resp = self.execute(cmd) if resp.status_code == umodem.Response.ERR_NONE: - parser = umodem.Parser(resp.content) - parser.skipuntil("\n") - return parser.parseutil("\r\n") + parser = Parser(resp.content) + parser.skipuntil(b"\n") + return parser.parseutil(b"\r\n").decode() return "" def get_model_software_version(self) -> str: cmd = umodem.Command("+CGMR", umodem.Command.CMD_EXECUTION, timeout=self._default_timeout) resp = self.execute(cmd) if resp.status_code == umodem.Response.ERR_NONE: - parser = umodem.Parser(resp.content) - parser.skipuntil("\n") - return parser.parseutil("\r\n") + parser = Parser(resp.content) + parser.skipuntil(b"\n") + return parser.parseutil(b"\r\n").decode() return "" def get_imei_number(self) -> str: """Request TA Serial Number Identification(IMEI)""" cmd = umodem.Command("+CGSN", umodem.Command.CMD_EXECUTION, timeout=self._default_timeout) resp = self.execute(cmd) + self._verbose and print(f"resp.content: {resp.content}") if resp.status_code == umodem.Response.ERR_NONE: - parser = umodem.Parser(resp.content) - parser.skipuntil("\n") - return parser.parseutil("\r\n") + parser = Parser(resp.content) + parser.skipuntil(b"\n") + return parser.parseutil(b"\r\n").decode("utf-8") return "" def get_ccid_number(self) -> str: @@ -434,23 +1341,21 @@ def get_ccid_number(self) -> str: cmd = umodem.Command("+CCID", umodem.Command.CMD_EXECUTION, timeout=self._default_timeout) resp = self.execute(cmd) if resp.status_code == umodem.Response.ERR_NONE: - parser = umodem.Parser(resp.content) - parser.skipuntil("\n") - return parser.parseutil("\r\n") + parser = Parser(resp.content) + parser.skipuntil(b"\n") + return parser.parseutil(b"\r\n").decode() return "" def get_imsi_number(self) -> str: cmd = umodem.Command("+CIMI", umodem.Command.CMD_EXECUTION, timeout=self._default_timeout) resp = self.execute(cmd) if resp.status_code == umodem.Response.ERR_NONE: - parser = umodem.Parser(resp.content) - parser.skipuntil("\n") - return parser.parseutil("\r\n") + parser = Parser(resp.content) + parser.skipuntil(b"\n") + return parser.parseutil(b"\r\n").decode() return "" - """ - TODO: 下面的方法是pandian写的,需要测试,并且这些方法已经在 nb-iot unit使用了,需要做向前兼容 - """ + ################################################################################### def set_pdp_context(self, active: bool) -> bool: """PDP Context Activate or Deactivate""" @@ -465,10 +1370,10 @@ def get_pdp_context_status(self) -> bool: cmd = umodem.Command("+CGACT", umodem.Command.CMD_READ, timeout=self._default_timeout) resp = self.execute(cmd) if resp.status_code == umodem.Response.ERR_NONE: - parser = umodem.Parser(resp.content) - parser.skipuntil("+CGACT: ") - parser.parseint() - return bool(parser.parseint()) + parser = Parser(resp.content) + parser.skipuntil(b"+CGACT: ") + parser.parseint(b",") + return bool(parser.parseint(b"\r\n")) return False def set_pdp_context_apn(self, apn="cmnbiot") -> bool: @@ -480,280 +1385,143 @@ def set_pdp_context_apn(self, apn="cmnbiot") -> bool: return resp.status_code == umodem.Response.ERR_NONE def get_pdp_context_dynamic_parameters(self, param=1): - """PDP Context Read Dynamic Parameters""" - cmd = umodem.Command( - "+CGCONTRDP", umodem.Command.CMD_EXECUTION, timeout=self._default_timeout - ) - resp = self.execute(cmd) - if resp.status_code == umodem.Response.ERR_NONE: - parser = umodem.Parser(resp.content) - parser.parseint() # cid - parser.parseint() # bearer_id - apn = parser.parseutil(",").strip('"') # apn - locl_ip = parser.parseutil(",").strip('"') # local_ip and subnet_mask - parts = locl_ip.split(".") - locl_ip = ".".join(parts[:4]) # local_ip. FIXME: support ipv6 - if param == 1: - return locl_ip - elif param == 2: - return apn + resp = self._get_pdp_context_dynamic_parameters() + if param == 1: + return resp[3] + elif param == 2: + return resp[2] else: return "" # MQTT Test Server:mqtt.m5stack.com, Port:1883. - def mqtt_server_connect(self, server, port, client_id, username, passwd, keepalive): - cmd = umodem.Command( - "+CMQNEW", - umodem.Command.CMD_WRITE, + def mqtt_server_connect( + self, server: str, port: int, client_id: str, username: str, passwd: str, keepalive: int + ): + self._mqtt_client = self.MQTTClient( + client_id, server, port, - 12000, - 1024, - timeout=self._default_timeout, - ) - self._mqtt_id = 0 - # mqtt callback function keyword is set - self.downlink_keyword.append("+CMQPUB:") - self.callback_keyword.append("MQTT_CB") - self.downlink_callback["MQTT_CB"] = self.mqtt_subscribe_cb - - self.mqtt_subscribe_cb_list = {} - - resp = self.execute(cmd) - if resp.status_code != umodem.Response.ERR_NONE: - return False - self._mqtt_id = int(resp.content[-1]) - - cmd = umodem.Command( - "+CMQCON", - umodem.Command.CMD_WRITE, - self._mqtt_id, - 3, - client_id, - keepalive, - 1, - 0, - username, - passwd, - timeout=self._default_timeout, + user=username, + password=passwd, + keepalive=keepalive, ) - resp = self.execute(cmd) - if resp.status_code != umodem.Response.ERR_NONE: - return False + self._mqtt_client.connect() - result = self.mqtt_server_is_connect() - return bool(result) + return self._mqtt_client.isconnected() def mqtt_server_disconnect(self): - cmd = umodem.Command( - "+CMQDISCON", umodem.Command.CMD_WRITE, self._mqtt_id, timeout=self._default_timeout - ) - resp = self.execute(cmd) - return resp.status_code == umodem.Response.ERR_NONE + if self._mqtt_client is not None: + self._mqtt_client.disconnect() + self._mqtt_client = None + else: + raise SIMComError("MQTT client is not create") - def mqtt_subscribe_topic(self, topic, cb, qos=0): - # Subscribe topic(support wildcards). - cmd = umodem.Command( - "+CMQSUB", - umodem.Command.CMD_WRITE, - self._mqtt_id, - topic, - qos, - timeout=self._default_timeout, - ) - resp = self.execute(cmd) - if resp.status_code != umodem.Response.ERR_NONE: - return False - if topic not in self.mqtt_subscribe_cb_list.keys(): - self.mqtt_subscribe_cb_list[topic] = cb - return True + def mqtt_subscribe_topic(self, topic, cb, qos=0) -> bool: + if self._mqtt_client is not None: + self._mqtt_client.subscribe(topic, qos) + self._mqtt_client.set_callback(cb) + return True + else: + raise SIMComError("MQTT client is not create") def mqtt_unsubscribe_topic(self, topic): - # Unsubscribe topic. - cmd = umodem.Command( - "+CMQUNSUB", - umodem.Command.CMD_WRITE, - self._mqtt_id, - topic, - timeout=self._default_timeout, - ) - resp = self.execute(cmd) - if resp.status_code != umodem.Response.ERR_NONE: - return False - if topic in self.mqtt_subscribe_cb_list.keys(): - self.mqtt_subscribe_cb_list.pop(topic) - return True - - def mqtt_publish_topic(self, topic, payload, qos=0, retained=None, duplicate=None): - # Publish message with topic. - if retained is None and duplicate is None: - cmd = umodem.Command( - "+CMQPUB", - umodem.Command.CMD_WRITE, - self._mqtt_id, - topic, - qos, - len(payload), - payload, - timeout=self._default_timeout, - ) + if self._mqtt_client is not None: + self._mqtt_client.unsubscribe(topic) + return True else: - cmd = umodem.Command( - "+CMQPUB", - umodem.Command.CMD_WRITE, - self._mqtt_id, - topic, - qos, - retained, - duplicate, - len(payload), - payload, - timeout=self._default_timeout, - ) + raise SIMComError("MQTT client is not create") - resp = self.execute(cmd) - if resp.status_code != umodem.Response.ERR_NONE: - return False + def mqtt_publish_topic(self, topic, payload, qos=0, retained=False, duplicate=None): + if self._mqtt_client is not None: + self._mqtt_client.publish(topic, msg=payload, retain=retained, qos=qos) + else: + raise SIMComError("MQTT client is not create") def mqtt_server_is_connect(self): # Check mqtt server connection. - cmd = umodem.Command("+CMQCON?", umodem.Command.CMD_READ, timeout=self._default_timeout) - resp = self.execute(cmd) - return ( - False - if resp.status_code != umodem.Response.ERR_NONE - else int(resp.content.split(",")[1]) - ) + if self._mqtt_client is not None: + return self._mqtt_client.isconnected() + else: + raise SIMComError("MQTT client is not create") def mqtt_polling_loop(self): - pass - - def mqtt_subscribe_cb(self, buffer): - # main callback function - topic = buffer.split('"')[1] - payload = buffer.split('"')[3] - if topic in self.mqtt_subscribe_cb_list.keys(): - self.mqtt_subscribe_cb_list[topic](topic, payload) + if self._mqtt_client is not None: + self._mqtt_client.check_msg() + else: + raise SIMComError("MQTT client is not create") # Create & Request Http(s) HTTPCLIENT_GET = 0 HTTPCLIENT_POST = 1 HTTPCLIENT_PUT = 2 HTTPCLIENT_DELETE = 3 + HTTPCLIENT_PATCH = 4 + HTTPCLIENT_HEAD = 5 def http_request( self, method=HTTPCLIENT_GET, url="http://api.m5stack.com/v1", headers={}, data=None ): - # Create HTTP host instance - proto, dummy, host, path = url.split("/", 3) - find_url = url.find("/", 10) - host = url[: find_url + 1] - self.http_client_id = None - self.response_code = 0 self.data_content = "" - - cmd = umodem.Command( - 'AT+CHTTPCREATE="{0}"'.format(host), umodem.Command.CMD_EXECUTION, timeout=10 - ) - resp = self.execute(cmd) - if resp.status_code != umodem.Response.ERR_NONE: - return False - self.http_client_id = int(resp.content[-1]) - - output = self.http_server_connect() - if output is False: - return False - - temp_header = "" - for head in headers.items(): - if head[0] == "Content-Type": - contenttype = head[1] - else: - temp_header += self.make_header(head[0], head[1]) - - if method == self.HTTPCLIENT_GET: - cmd = umodem.Command( - "+CHTTPSEND", - umodem.Command.CMD_WRITE, - self.http_client_id, - self.HTTPCLIENT_GET, - ("/" + path), - timeout=10, - ) - - elif method == self.HTTPCLIENT_POST: - temp_header = self.asciistr_to_hexstr(temp_header) if temp_header != "" else 0 - cmd = umodem.Command( - "+CHTTPSEND", - umodem.Command.CMD_WRITE, - self.http_client_id, - self.HTTPCLIENT_POST, - ("/" + path), - temp_header, - contenttype, - self.asciistr_to_hexstr(data), - timeout=10, - ) - - resp = self.execute(cmd) - if resp.status_code != umodem.Response.ERR_NONE: - return False - - self.response_code = int(resp.content.split(",")[1]) - if self.response_code != 200: - print('Response code: "{0}"'.format(self.response_code)) - return False - - if resp.content.find("+CHTTPNMIC:") != -1: # noqa: N806 - self.data_content = self.hexstr_to_asciistr(resp.content.split(",")[-1]) - - output = self.http_server_disconnect() - if output is False: - return False - - output = self.http_server_destroy() - if output is False: - return False - - def http_server_connect(self): - # Http server is connect - cmd = umodem.Command("+CHTTPCON", umodem.Command.CMD_WRITE, self.http_client_id, timeout=3) - resp = self.execute(cmd) - return resp.status_code == umodem.Response.ERR_NONE - - def http_server_is_connect(self): - # Is check http server connect - cmd = umodem.Command("+CHTTPCON?", umodem.Command.CMD_READ, timeout=3) - resp = self.execute(cmd) - output = resp.content.split("+CHTTPCON") - output[self.http_client_id + 1].split(",")[1] - return ( - False - if resp.status_code != umodem.Response.ERR_NONE - else int(output[self.http_client_id + 1].split(",")[1]) - ) - - def http_server_disconnect(self): - # http server disconnected - cmd = umodem.Command( - "+CHTTPDISCON", umodem.Command.CMD_WRITE, self.http_client_id, timeout=10 - ) - resp = self.execute(cmd) - return resp.status_code == umodem.Response.ERR_NONE - - def http_server_destroy(self): - # http server destroy - cmd = umodem.Command( - "+CHTTPDESTROY", umodem.Command.CMD_WRITE, self.http_client_id, timeout=10 - ) - resp = self.execute(cmd) - return resp.status_code == umodem.Response.ERR_NONE - - def make_header(self, key, value): - return str(key) + ":" + str(value) + "\n" - - def asciistr_to_hexstr(self, byte): - return "".join(["%02X" % x for x in byte.encode()]).strip() - - def hexstr_to_asciistr(self, hex): - return bytes.fromhex(hex).decode() + self.response_code = 0 + method_map = { + self.HTTPCLIENT_GET: "GET", + self.HTTPCLIENT_POST: "POST", + self.HTTPCLIENT_PUT: "PUT", + self.HTTPCLIENT_DELETE: "DELETE", + self.HTTPCLIENT_PATCH: "PATCH", + self.HTTPCLIENT_HEAD: "HEAD", + } + + if isinstance(method, int): + str_method = method_map.get(method, "GET") + if isinstance(data, dict): + import ujson + + data = ujson.dumps(data).encode() + + if "Content-Type" not in headers: + headers["Content-Type"] = "application/json" + + elif isinstance(data, str): + data = data.encode() + response = self.request(str_method, url, data=data, headers=headers) + self.data_content = response.reason + self.response_code = response.status_code + + def write_read(self, cmd: str, rsp1="OK", rsp2="ERROR", line_end="\r\n", timeout: int = 10000): + cmdstr = "AT+" + "{}\r\n".format(cmd) + self._verbose and print("TE -> TA:", repr(cmdstr)) + self.uart.write(cmdstr.encode("utf-8")) + output = bytearray() + ticks = time.ticks_ms() + + rsp1_bytes = rsp1.encode("utf-8") + rsp2_bytes = rsp2.encode("utf-8") + line_end_bytes = line_end.encode("utf-8") + + find_keyword = False + + while time.ticks_diff(time.ticks_ms(), ticks) < timeout: + if self.uart.any() == 0: + time.sleep_ms(10) + continue + + line = self.uart.read(self.uart.any()) + self._verbose and print("TE <- TA:", repr(line)) + output.extend(line) + + # Do we have an error? + if output.rfind(rsp2_bytes) != -1: + if output.endswith(line_end_bytes): + print("Get AT command error response:", repr(output)) + find_keyword = True + + # If we had a pre-end, do we have the expected end? + if output.rfind(rsp1_bytes) != -1: + if output.endswith(line_end_bytes): + find_keyword = True + + if find_keyword: + break + + return output.decode("utf-8") diff --git a/m5stack/libs/driver/simcom/sim7028.py b/m5stack/libs/driver/simcom/sim7028.py index 18be5306..d1ba14e0 100644 --- a/m5stack/libs/driver/simcom/sim7028.py +++ b/m5stack/libs/driver/simcom/sim7028.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD # # SPDX-License-Identifier: MIT diff --git a/m5stack/libs/driver/simcom/sim7080.py b/m5stack/libs/driver/simcom/sim7080.py index 5af019cc..a8f733a0 100644 --- a/m5stack/libs/driver/simcom/sim7080.py +++ b/m5stack/libs/driver/simcom/sim7080.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD # # SPDX-License-Identifier: MIT diff --git a/m5stack/libs/driver/simcom/sim7600.py b/m5stack/libs/driver/simcom/sim7600.py index 66cdcd77..59402c8c 100644 --- a/m5stack/libs/driver/simcom/sim7600.py +++ b/m5stack/libs/driver/simcom/sim7600.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD # # SPDX-License-Identifier: MIT @@ -77,6 +77,7 @@ def __init__(self, *args): super().__init__(msg.format(repr(cmd))) +# TCP/UDP socket class _socket: AF_INET = socket.AF_INET AF_INET6 = socket.AF_INET6 @@ -415,6 +416,7 @@ def write(self, *args) -> int: return self.send(data[off : off + max_len]) +# SSL/TLS socket class _sockets(_socket): AF_INET = socket.AF_INET AF_INET6 = socket.AF_INET6 diff --git a/m5stack/libs/driver/simcom/sim800/__init__.py b/m5stack/libs/driver/simcom/sim800/__init__.py index fc9402bd..8e350a50 100644 --- a/m5stack/libs/driver/simcom/sim800/__init__.py +++ b/m5stack/libs/driver/simcom/sim800/__init__.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD # # SPDX-License-Identifier: MIT diff --git a/m5stack/libs/driver/simcom/toolkit/__init__.py b/m5stack/libs/driver/simcom/toolkit/__init__.py new file mode 100644 index 00000000..ae748eb3 --- /dev/null +++ b/m5stack/libs/driver/simcom/toolkit/__init__.py @@ -0,0 +1,3 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT diff --git a/m5stack/libs/driver/simcom/toolkit/requests2.py b/m5stack/libs/driver/simcom/toolkit/requests2.py index 969d0e0a..d98e2c06 100644 --- a/m5stack/libs/driver/simcom/toolkit/requests2.py +++ b/m5stack/libs/driver/simcom/toolkit/requests2.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD # # SPDX-License-Identifier: MIT @@ -143,7 +143,7 @@ def _request( s.write(data) l = s.readline() - # print(l) + print(f"request2 l: {repr(l)}") l = l.split(None, 2) if len(l) < 2: # Invalid response @@ -168,9 +168,12 @@ def _request( if parse_headers is False: pass elif parse_headers is True: - l = str(l, "utf-8") - k, v = l.split(":", 1) - resp_d[k] = v.strip() + l = str(l, "utf-8").strip() + if ":" in l: + k, v = l.split(":", 1) + resp_d[k.strip()] = v.strip() + else: + pass else: parse_headers(l, resp_d) except OSError: diff --git a/m5stack/libs/driver/simcom/toolkit/umqtt/__init__.py b/m5stack/libs/driver/simcom/toolkit/umqtt/__init__.py index b5455b9f..d15c87ee 100644 --- a/m5stack/libs/driver/simcom/toolkit/umqtt/__init__.py +++ b/m5stack/libs/driver/simcom/toolkit/umqtt/__init__.py @@ -1,8 +1,8 @@ -# SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD # # SPDX-License-Identifier: MIT -from . import robust +from driver.simcom.toolkit.umqtt import robust from micropython import schedule @@ -21,7 +21,7 @@ def __init__( ): ssl_params1 = ssl_params if ssl: - pass + # pass key_path = ssl_params1.get("key", None) key_value = self._load_file(key_path) if key_value: @@ -29,6 +29,7 @@ def __init__( cert_path = ssl_params1.get("cert", None) cert_value = self._load_file(cert_path) + print(f"cert_value: {cert_value}") if cert_value: ssl_params1["cert"] = cert_value diff --git a/m5stack/libs/driver/simcom/toolkit/umqtt/robust.py b/m5stack/libs/driver/simcom/toolkit/umqtt/robust.py index e51fdf5e..38ad4832 100644 --- a/m5stack/libs/driver/simcom/toolkit/umqtt/robust.py +++ b/m5stack/libs/driver/simcom/toolkit/umqtt/robust.py @@ -3,7 +3,7 @@ # SPDX-License-Identifier: MIT import utime -from . import simple +from driver.simcom.toolkit.umqtt import simple class MQTTClient(simple.MQTTClient): diff --git a/m5stack/libs/driver/simcom/toolkit/umqtt/simple.py b/m5stack/libs/driver/simcom/toolkit/umqtt/simple.py index cb4d5764..f54ab732 100644 --- a/m5stack/libs/driver/simcom/toolkit/umqtt/simple.py +++ b/m5stack/libs/driver/simcom/toolkit/umqtt/simple.py @@ -179,6 +179,23 @@ def subscribe(self, topic, qos=0): raise MQTTException(resp[3]) return + def unsubscribe(self, topic): + pkt = bytearray(b"\xa2\0\0\0") + self.pid += 1 + struct.pack_into("!BH", pkt, 1, 2 + 2 + len(topic), self.pid) + self.sock.write(pkt) + self._send_str(topic) + + while 1: + op = self.wait_msg() + if op == 0xB0: + resp = self.sock.read(3) + pid_resp = resp[1] << 8 | resp[2] + if pid_resp == self.pid: + return True + else: + raise MQTTException("unsubscribe failed") + # Wait for a single incoming MQTT message and process it. # Subscribed messages are delivered to a callback previously # set by .set_callback() method. Other (internal) MQTT diff --git a/m5stack/libs/driver/simcom/utils.py b/m5stack/libs/driver/simcom/utils.py index 66040ddb..2bc45c5e 100644 --- a/m5stack/libs/driver/simcom/utils.py +++ b/m5stack/libs/driver/simcom/utils.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD # # SPDX-License-Identifier: MIT @@ -10,21 +10,21 @@ def converter(data, type): return data if isinstance(data, str): - if type == bytes: + if type is bytes: return data.encode("utf-8") - if type == bytearray: + if type is bytearray: return bytearray(data, "utf-8") if isinstance(data, bytes): - if type == str: + if type is str: return data.decode("utf-8") - if type == bytearray: + if type is bytearray: return bytearray(data) if isinstance(data, bytearray): - if type == str: + if type is str: return str(data, "utf-8") - if type == bytes: + if type is bytes: return bytes(data) @@ -58,8 +58,8 @@ def wrapper(*args, **kwargs): start_time = time.ticks_ms() result = func(*args, **kwargs) end_time = time.ticks_ms() - execution_time = end_time - start_time - print(f"total time consumed to execute {func}: {execution_time}ms") + execution_time = end_time - start_time # noqa: F841 + # print(f"total time consumed to execute {func}: {execution_time}ms") return result return wrapper diff --git a/m5stack/libs/driver/umodem/__init__.py b/m5stack/libs/driver/umodem/__init__.py index 889e46de..e69de29b 100644 --- a/m5stack/libs/driver/umodem/__init__.py +++ b/m5stack/libs/driver/umodem/__init__.py @@ -1,4 +0,0 @@ -from .modem import UModem -from .modem import Command -from .modem import Response -from .parser import Parser diff --git a/m5stack/libs/driver/umodem/modem.py b/m5stack/libs/driver/umodem/modem.py index edc8c325..80890fca 100644 --- a/m5stack/libs/driver/umodem/modem.py +++ b/m5stack/libs/driver/umodem/modem.py @@ -1,8 +1,9 @@ -# SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD # # SPDX-License-Identifier: MIT import time +from machine import UART def _measure_time(func): @@ -10,8 +11,8 @@ def wrapper(*args, **kwargs): start_time = time.ticks_ms() result = func(*args, **kwargs) end_time = time.ticks_ms() - execution_time = end_time - start_time - print(f"total time consumed to execute {func}: {execution_time}ms") + execution_time = end_time - start_time # noqa: F841 + # print(f"total time consumed to execute {func}: {execution_time}ms") return result return wrapper @@ -40,7 +41,7 @@ def __init__(self, cmd, type, *args, rsp1="OK", rsp2="ERROR", timeout=2000) -> N for arg in args: if isinstance(arg, str): self.args.append(f'"{arg}"') - if isinstance(arg, int): + elif isinstance(arg, int): self.args.append(str(arg)) self.rsp1 = rsp1 @@ -59,7 +60,7 @@ def __call__(self) -> str: class UModem: - def __init__(self, uart, verbose=False): + def __init__(self, uart: UART, verbose=False): self.uart = uart self._verbose = verbose @@ -127,4 +128,4 @@ def response_at_command2( # output = output[:-1] # Return - return Response(error, output.decode()) + return Response(error, output) diff --git a/m5stack/libs/driver/umodem/parser.py b/m5stack/libs/driver/umodem/parser.py index 41b1c484..94b2b0e7 100644 --- a/m5stack/libs/driver/umodem/parser.py +++ b/m5stack/libs/driver/umodem/parser.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD # # SPDX-License-Identifier: MIT @@ -6,7 +6,7 @@ def _logging(func): def wrapper(*args, **kwargs): result = func(*args, **kwargs) - print(f"func {func.__name__} return: {result}") + # print(f"func {func.__name__} return: {result}") return result return wrapper @@ -18,26 +18,30 @@ def __init__(self, data): self.index = 0 @_logging - def skipuntil(self, chr: str) -> int: + def skipuntil(self, chr: bytearray | bytes) -> int: index = self.data.find(chr, self.index) if index != -1: self.index = index + len(chr) return self.index @_logging - def parseint(self, chr=",") -> int: + def parseint(self, chr=b",") -> int: s = self.parseutil(chr) if s == "": return -9999 return int(s) @_logging - def parseutil(self, chr: str) -> str: - index = self.data.find(chr, self.index) + def parseutil(self, chr: str | bytearray | bytes): + if isinstance(chr, str): + chr_bytes = chr.encode() + else: + chr_bytes = chr + index = self.data.find(chr_bytes, self.index) if index == -1: - return "" + return b"" ret = self.data[self.index : index] - self.index += len(ret) + len(chr) + self.index += len(ret) + len(chr_bytes) return ret def reset(self): diff --git a/m5stack/libs/module/nbiot.py b/m5stack/libs/module/nbiot.py index b925ae62..119d94ba 100644 --- a/m5stack/libs/module/nbiot.py +++ b/m5stack/libs/module/nbiot.py @@ -4,18 +4,41 @@ import machine from driver.simcom.sim7020 import SIM7020 +from driver.simcom.common import Modem import sys if sys.platform != "esp32": from typing import Literal -class NBIOTModule(SIM7020): - def __init__(self, id: Literal[0, 1, 2], tx: int, rx: int) -> None: - self.uart = machine.UART( - id, tx=tx, rx=rx, baudrate=115200, bits=8, parity=None, stop=1, rxbuf=1024 - ) - super().__init__(uart=self.uart) +class NBIOTModule(SIM7020, Modem): + def __init__( + self, + uart_or_id: machine.UART | int, + tx: int = None, + rx: int = None, + verbose: bool = False, + ) -> None: + if isinstance(uart_or_id, machine.UART): + self.uart = uart_or_id + + elif isinstance(uart_or_id, int) and tx is not None and rx is not None: + self.uart = machine.UART( + uart_or_id, + tx=tx, + rx=rx, + baudrate=115200, + bits=8, + parity=None, + stop=1, + rxbuf=1024, + ) + else: + raise ValueError("Invalid arguments: please provide UART instance or (id, tx, rx)") + + Modem.__init__(self, uart=self.uart, verbose=verbose) + SIM7020.__init__(self, uart=self.uart, verbose=verbose) + if not self.check_modem_is_ready(): raise Exception("NBIOT module not found in mbus") self.set_command_echo_mode(0) diff --git a/m5stack/libs/unit/nbiot.py b/m5stack/libs/unit/nbiot.py index ed385890..20593a06 100644 --- a/m5stack/libs/unit/nbiot.py +++ b/m5stack/libs/unit/nbiot.py @@ -2,8 +2,9 @@ # # SPDX-License-Identifier: MIT -from machine import UART +import machine from driver.simcom.sim7020 import SIM7020 +from driver.simcom.common import Modem from collections import namedtuple from .unit_helper import UnitError import sys @@ -14,12 +15,32 @@ AT_CMD = namedtuple("AT_CMD", ["command", "response", "timeout"]) -class NBIOTUnit(SIM7020): - def __init__(self, id: Literal[0, 1, 2] = 1, port: list | tuple = None) -> None: - self.uart = UART( - id, tx=port[1], rx=port[0], baudrate=115200, bits=8, parity=None, stop=1, rxbuf=1024 - ) - super().__init__(uart=self.uart) +class NBIOTUnit(SIM7020, Modem): + def __init__( + self, + uart_or_id: machine.UART | int, + port: list | tuple = None, + verbose: bool = False, + ) -> None: + if isinstance(uart_or_id, machine.UART): + self.uart = uart_or_id + elif isinstance(uart_or_id, int) and port is not None: + self.uart = machine.UART( + uart_or_id, + tx=port[1], + rx=port[0], + baudrate=115200, + bits=8, + parity=None, + stop=1, + rxbuf=1024, + ) + else: + raise ValueError("Invalid arguments: must provide either UART or (id + port)") + + Modem.__init__(self, uart=self.uart, verbose=verbose) + SIM7020.__init__(self, uart=self.uart, verbose=verbose) + if not self.check_modem_is_ready(): raise UnitError("NBIOT unit not found in Grove") self.set_command_echo_mode(0) From 668b46b7764f94b66c2753e8f9dafc428472aa20 Mon Sep 17 00:00:00 2001 From: luoweiyuan Date: Thu, 17 Apr 2025 15:27:44 +0800 Subject: [PATCH 054/322] docs: Add Unit HBridge docs. Signed-off-by: luoweiyuan --- docs/en/refs/unit.hbridge.ref | 30 ++ docs/en/units/hbridge.rst | 64 +++ docs/en/units/index.rst | 1 + .../zh_CN/LC_MESSAGES/units/hbridge.po | 396 ++++++++++++++++++ .../hbridge/cores3_hbridge_motor_control.m5f2 | 1 + .../hbridge/cores3_hbridge_motor_control.py | 66 +++ m5stack/libs/unit/hbridge.py | 218 ++++++++-- 7 files changed, 731 insertions(+), 45 deletions(-) create mode 100644 docs/en/refs/unit.hbridge.ref create mode 100644 docs/en/units/hbridge.rst create mode 100644 docs/locales/zh_CN/LC_MESSAGES/units/hbridge.po create mode 100644 examples/unit/hbridge/cores3_hbridge_motor_control.m5f2 create mode 100644 examples/unit/hbridge/cores3_hbridge_motor_control.py diff --git a/docs/en/refs/unit.hbridge.ref b/docs/en/refs/unit.hbridge.ref new file mode 100644 index 00000000..b7524555 --- /dev/null +++ b/docs/en/refs/unit.hbridge.ref @@ -0,0 +1,30 @@ +.. |Unit HBridge| image:: https://static-cdn.m5stack.com/resource/docs/products/unit/Hbridge%20Unit/img-3f610a3a-12be-4bd9-83b7-32f24b16b982.webp + :target: https://docs.m5stack.com/zh_CN/unit/Hbridge%20Unit + :height: 200px + :width: 200px + +.. |Unit HBridge v1.1| image:: https://static-cdn.m5stack.com/resource/docs/products/unit/HBridge%20v1.1%20Unit/img-8b503691-1b66-49d7-b05d-5838eb4a6c36.webp + :target: https://docs.m5stack.com/zh_CN/unit/HBridge%20v1.1%20Unit + :height: 200px + :width: 200px + +.. |init.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/hbridge/init.png +.. |get_driver_config.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/hbridge/get_driver_config.png +.. |get_adc_value.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/hbridge/get_adc_value.png +.. |get_device_status.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/hbridge/get_device_status.png +.. |set_direction.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/hbridge/set_direction.png +.. |set_8bit_pwm.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/hbridge/set_8bit_pwm.png +.. |set_16bit_pwm.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/hbridge/set_16bit_pwm.png +.. |set_percentage_pwm.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/hbridge/set_percentage_pwm.png +.. |set_pwm_freq.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/hbridge/set_pwm_freq.png +.. |get_vin_current.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/hbridge/get_vin_current.png +.. |cores3_hbridge_motor_control.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/hbridge/example.png + +.. |cores3_hbridge_motor_control.m5f2| raw:: html + + + cores3_hbridge_motor_control.m5f2 + diff --git a/docs/en/units/hbridge.rst b/docs/en/units/hbridge.rst new file mode 100644 index 00000000..0089ee1a --- /dev/null +++ b/docs/en/units/hbridge.rst @@ -0,0 +1,64 @@ +Hbridge Unit +============================ + +.. sku: U160 + +.. sku: U160-V11 + +.. include:: ../refs/unit.hbridge.ref + +This library is the driver for Unit HBridge. Only version v1.1 supports current measurement. + +Support the following products: + + =================== ==================== + |Unit HBridge| |Unit HBridge v1.1| + =================== ==================== + + +UiFlow2 Example +--------------- + +Motor speed and rotate direction control +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Open the |cores3_hbridge_motor_control.m5f2| project in UiFlow2. + +This example demonstrates how to control the motor's speed and switch its rotation direction. + +UiFlow2 Code Block: + + |cores3_hbridge_motor_control.png| + +Example output: + + None + + +MicroPython Example +------------------- + +Motor speed and rotate direction control +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +This example demonstrates how to control the motor's speed and switch its rotation direction. + +MicroPython Code Block: + + .. literalinclude:: ../../../examples/unit/hbridge/cores3_hbridge_motor_control.py + :language: python + :linenos: + +Example output: + + None + + +**API** +------- + +HbridgeRUnit +^^^^^^^^^^^^ + +.. autoclass:: unit.hbridge.HbridgeUnit + :members: diff --git a/docs/en/units/index.rst b/docs/en/units/index.rst index c889fb86..b80e4949 100644 --- a/docs/en/units/index.rst +++ b/docs/en/units/index.rst @@ -48,6 +48,7 @@ Unit gps_v11.rst grove2grove.rst hall_effect.rst + hbridge.rst heart.rst id.rst imu.rst diff --git a/docs/locales/zh_CN/LC_MESSAGES/units/hbridge.po b/docs/locales/zh_CN/LC_MESSAGES/units/hbridge.po new file mode 100644 index 00000000..437d7228 --- /dev/null +++ b/docs/locales/zh_CN/LC_MESSAGES/units/hbridge.po @@ -0,0 +1,396 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2016 - 2025 M5Stack Technology Co., Ltd +# This file is distributed under the same license as the UIFlow2 Programming +# Guide package. +# FIRST AUTHOR , 2025. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: UIFlow2 Programming Guide \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-04-18 16:50+0800\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: zh_CN\n" +"Language-Team: zh_CN \n" +"Plural-Forms: nplurals=1; plural=0;\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.16.0\n" + +#: ../../en/units/hbridge.rst:2 ac8faaa77f4e41dd9303f4b913b8aa3c +msgid "Hbridge Unit" +msgstr "" + +#: ../../en/units/hbridge.rst:10 cc11a9a795bf47f5b81d68b693df9842 +msgid "" +"This library is the driver for Unit HBridge. Only version v1.1 supports " +"current measurement." +msgstr "这个库是 Unit HBridge 的驱动。只有 v1.1 版具备电流测量功能。" + +#: ../../en/units/hbridge.rst:12 bcd4bbc6ec7d48c984a9fc5fda66cd47 +msgid "Support the following products:" +msgstr "支持以下产品:" + +#: ../../en/units/hbridge.rst:15 16dc591facdf46e482dc31e6dec04854 +msgid "|Unit HBridge|" +msgstr "" + +#: ../../en/refs/unit.hbridge.ref 9dbc972f132e4ba484366ed9c00b9527 +msgid "Unit HBridge" +msgstr "" + +#: ../../en/units/hbridge.rst:15 5a014f3893f04238b38aa6eb498b6781 +msgid "|Unit HBridge v1.1|" +msgstr "" + +#: ../../en/refs/unit.hbridge.ref 3a188b263d844a5586defbcd4f837074 +msgid "Unit HBridge v1.1" +msgstr "" + +#: ../../en/units/hbridge.rst:20 5a6e5402d77243de82524591770f2fc6 +msgid "UiFlow2 Example" +msgstr "UiFlow2 应用示例" + +#: ../../en/units/hbridge.rst:23 ../../en/units/hbridge.rst:42 +#: 19bf4d6657004e43998ea71dbb862ae9 c5358e99b7cf4dc7a67304f251df5dea +msgid "Motor speed and rotate direction control" +msgstr "电机转速和旋转方向控制。" + +#: ../../en/units/hbridge.rst:25 f96df329818a41a2bdf53e0f058616c4 +msgid "Open the |cores3_hbridge_motor_control.m5f2| project in UiFlow2." +msgstr "在 UiFlow2 上打开 |cores3_hbridge_motor_control.m5f2| 项目。" + +#: ../../en/units/hbridge.rst:27 ../../en/units/hbridge.rst:44 +#: 67df8291047d436fbdd41d8e48858c3a 6ff5a51a7db043b2908adba4e11654a0 +msgid "" +"This example demonstrates how to control the motor's speed and switch its" +" rotation direction." +msgstr "示例程序实现电机转速控制及正反转方向的切换功能。" + +#: ../../en/units/hbridge.rst:29 01b8de9b3e7042649c0e2e43a302980e +#: 20d5ccb451704976beb55d655e6bab95 24df9194488b48429a7b74bc62dd0902 +#: 6ae77adb37664eb7acfabaa9cc42554a 95b565fe12e14f3c88ad8c97da7dc6ce +#: 9f7d8a8d9bee436eb95afaa176beb9c5 ad04c3763ee14119800bbeb3dce6337a +#: b636dde0e465481b941909e647ed0ecb bd610dadc37644c3872af9b4fb93df08 +#: d02bc449d38748f7b87e851f85e3779a de5729e113f34c46a1592263db131725 of +#: unit.hbridge.HbridgeUnit:7 unit.hbridge.HbridgeUnit.get_adc_value:15 +#: unit.hbridge.HbridgeUnit.get_device_status:7 +#: unit.hbridge.HbridgeUnit.get_driver_config:8 +#: unit.hbridge.HbridgeUnit.get_vin_current:6 +#: unit.hbridge.HbridgeUnit.set_16bit_pwm:5 +#: unit.hbridge.HbridgeUnit.set_8bit_pwm:5 +#: unit.hbridge.HbridgeUnit.set_direction:10 +#: unit.hbridge.HbridgeUnit.set_percentage_pwm:6 +#: unit.hbridge.HbridgeUnit.set_pwm_freq:5 +msgid "UiFlow2 Code Block:" +msgstr "UiFlow2 代码块:" + +#: ../../en/units/hbridge.rst:31 b8a36e6578d04f8297276b6fc3f6509a +msgid "|cores3_hbridge_motor_control.png|" +msgstr "" + +#: ../../en/refs/unit.hbridge.ref:21 45caeca003df45858e0c4fe5be74b8e1 +msgid "cores3_hbridge_motor_control.png" +msgstr "" + +#: ../../en/units/hbridge.rst:33 ../../en/units/hbridge.rst:52 +#: 5a5ff8f500304cf0b6cf621708b1ddac e1ff9fec2bb341faafee904f8ee8fb4b +msgid "Example output:" +msgstr "示例输出:" + +#: ../../en/units/hbridge.rst:35 ../../en/units/hbridge.rst:54 +#: 001231d0b1d64688b59facd893242dd3 6bea98f2d4da42aeb60cda5178116093 +msgid "None" +msgstr "无" + +#: ../../en/units/hbridge.rst:39 cc3ec70bbb304f9cb020c7d8bbea88c1 +msgid "MicroPython Example" +msgstr "MicroPython 应用示例" + +#: ../../en/units/hbridge.rst:46 0d1d1fea066441e6a1607f3ec9c5b444 +#: 1b73b36060694f0b89f3e8183a946571 3b4e3b36545a46b789c31ce4364e2b9e +#: 3e0d137091924ee2ae2a073825c84f56 700b3511dc3d4181b4a8d4a9a6500329 +#: 89a4e9844380457ab00a21a426a898fe 906798839f534fce9cab412f3e882420 +#: 98252dd94fc7410d8b8af34ca1d29a21 b48b032892264348874aa6dd566676d3 +#: e68fc6b8a5c64ed6a7d40a99764502d3 eb875fa259b746ffa0e96bdaa60219a7 of +#: unit.hbridge.HbridgeUnit:11 unit.hbridge.HbridgeUnit.get_adc_value:19 +#: unit.hbridge.HbridgeUnit.get_device_status:11 +#: unit.hbridge.HbridgeUnit.get_driver_config:12 +#: unit.hbridge.HbridgeUnit.get_vin_current:10 +#: unit.hbridge.HbridgeUnit.set_16bit_pwm:9 +#: unit.hbridge.HbridgeUnit.set_8bit_pwm:9 +#: unit.hbridge.HbridgeUnit.set_direction:14 +#: unit.hbridge.HbridgeUnit.set_percentage_pwm:10 +#: unit.hbridge.HbridgeUnit.set_pwm_freq:9 +msgid "MicroPython Code Block:" +msgstr "MicroPython 代码块:" + +#: ../../en/units/hbridge.rst:58 051965deb8884319a14f7c528fdde187 +msgid "**API**" +msgstr "API应用" + +#: ../../en/units/hbridge.rst:61 1ef42aa95ae34cf796aacf244856359c +msgid "HbridgeRUnit" +msgstr "" + +#: 58a190f6e7ae491b96edd7f1a225fb75 of unit.hbridge.HbridgeUnit:1 +msgid "Bases: :py:class:`object`" +msgstr "" + +#: 1f9a271996214a4398af8f2a182be509 of unit.hbridge.HbridgeUnit:1 +msgid "Create an HbridgeUnit object." +msgstr "创建一个 HbridgeUnit 对象。" + +#: ../../en/units/hbridge.rst 0841dce1b9de4e3986d0b8afbefafc62 +#: 11567801b0e745a6b651a5ee6406078a 2e7ca2f2ee5a4bd98ab8326973913420 +#: 444aeaa83dce47e1933629e2a5a344be 6113762c71fd45f182eef72b8410ca93 +#: 84fc3323e43241dc87e7894ec9c45d44 92ff89f7b1634fe2a63240bf97a40155 +#: dcb39ff27fe5436f8d232155d7e14b9c f4785460ae574cb3b4bddd10298ee9f2 of +#: unit.hbridge.HbridgeUnit.get_adc_value +#: unit.hbridge.HbridgeUnit.get_device_status +#: unit.hbridge.HbridgeUnit.get_driver_config +#: unit.hbridge.HbridgeUnit.set_16bit_pwm unit.hbridge.HbridgeUnit.set_8bit_pwm +#: unit.hbridge.HbridgeUnit.set_direction +#: unit.hbridge.HbridgeUnit.set_percentage_pwm +#: unit.hbridge.HbridgeUnit.set_pwm_freq +msgid "Parameters" +msgstr "" + +#: 3b71d171d2ac439ea6641ffe23164a08 of unit.hbridge.HbridgeUnit:3 +msgid "I2C port." +msgstr "I2C 端口。" + +#: 7eb67219682d46ffbc3710015e14fb02 of unit.hbridge.HbridgeUnit:5 +msgid "HbridgeUnit Slave Address." +msgstr "HbridgeUnit 从机地址。" + +#: a50c1be831df44b3a701bc35f1003162 of unit.hbridge.HbridgeUnit:9 +msgid "|init.png|" +msgstr "" + +#: ../../en/refs/unit.hbridge.ref:11 7f62f5d7a4b9484cb35a8d797b3d41ef +msgid "init.png" +msgstr "" + +#: ea4fa0c53ba444f280970c34b49f3fc2 of unit.hbridge.HbridgeUnit.get_adc_value:1 +msgid "Get ADC value." +msgstr "获取 ADC 数值。" + +#: 7035db6e371049d69ddfb35eb05d99da of unit.hbridge.HbridgeUnit.get_adc_value:3 +msgid "" +"This method retrieves the ADC value based on the specified resolution. It" +" supports both 8-bit and 16-bit ADC resolutions. If `raw` is set to `1`, " +"the raw ADC value is returned. Otherwise, the corresponding voltage is " +"calculated and returned." +msgstr "" +"此方法根据指定的分辨率获取 ADC 值。支持 8 位和 16 位的 ADC 分辨率。如果将 raw 设置为 1,则返回原始 ADC " +"值;否则返回计算后的电压值。" + +#: 36964cd6aea84b65ae343f4a863e2b78 of unit.hbridge.HbridgeUnit.get_adc_value:8 +msgid "" +"If 1, returns the raw ADC value. If 0, returns the voltage (calculated " +"based on ADC value)." +msgstr "如果为 1,则返回原始 ADC 值;如果为 0,则返回根据 ADC 值计算出的电压。" + +#: 19d4f428d16f4f658a7fd7c590251873 of +#: unit.hbridge.HbridgeUnit.get_adc_value:10 +msgid "ADC resolution (8 or 16 bits). Default is 8 bits." +msgstr "ADC 分辨率(8位或16位),默认为8位。" + +#: 06a830080aa94cb78392d8384eb03100 b56b576946be46a7b9afa17e2ded4017 +#: baff2ca7ef844390b6ffb46708d7fe42 of unit.hbridge.HbridgeUnit.get_adc_value +#: unit.hbridge.HbridgeUnit.get_driver_config +#: unit.hbridge.HbridgeUnit.get_vin_current +msgid "Returns" +msgstr "" + +#: 776d3227db714d96b70dc2d10fa6aa9f of +#: unit.hbridge.HbridgeUnit.get_adc_value:12 +msgid "The raw ADC value or the calculated voltage, depending on `raw`." +msgstr "根据 raw 参数,返回原始 ADC 值或计算后的电压。" + +#: 147c911be6b543c2b764f7bb810bcefc 18a11c2a9e1a450ca3aa7fae5b269c4a +#: 18b18a8917a6471d9a3d4b7f7434687d 41ddb5ee6f9b478da3aa92edefd51f15 +#: 66893509c93f4c898edf1678de1493d3 7f3b372a12bd4eaf8cf9f87ea0e7eec8 +#: ba768a4b37264261bcef1732605225d0 d87355f27e694f3a8eb792deb3a5a5ef +#: eee0d55a2cf84c62b5e035be452da226 of unit.hbridge.HbridgeUnit.get_adc_value +#: unit.hbridge.HbridgeUnit.get_device_status +#: unit.hbridge.HbridgeUnit.get_driver_config +#: unit.hbridge.HbridgeUnit.get_vin_current +#: unit.hbridge.HbridgeUnit.set_16bit_pwm unit.hbridge.HbridgeUnit.set_8bit_pwm +#: unit.hbridge.HbridgeUnit.set_direction +#: unit.hbridge.HbridgeUnit.set_percentage_pwm +#: unit.hbridge.HbridgeUnit.set_pwm_freq +msgid "Return type" +msgstr "" + +#: 20691eeb89c2433c8a1fc3edc2eb2480 of +#: unit.hbridge.HbridgeUnit.get_adc_value:17 +msgid "|get_adc_value.png|" +msgstr "" + +#: ../../en/refs/unit.hbridge.ref:13 7b4c5151270e45b18e231f190efc8a4f +msgid "get_adc_value.png" +msgstr "" + +#: 426b1394a13541cb83997d3e48157f9c of +#: unit.hbridge.HbridgeUnit.get_device_status:1 +msgid "Get device status." +msgstr "获取设备状态。" + +#: 20f51ab4f9d143a3aab6d8a1e2bf3d56 of +#: unit.hbridge.HbridgeUnit.get_device_status:3 +msgid "get firmware version and i2c address." +msgstr "获取固件版本和 I2C 地址。" + +#: c2767dbd87f24997ab88331b2e5ddd19 of +#: unit.hbridge.HbridgeUnit.get_device_status:5 +msgid "0xFE and 0xFF" +msgstr "0xFE 和 0xFF" + +#: f16dfece1ba343e991413627e06b0fb3 of +#: unit.hbridge.HbridgeUnit.get_device_status:9 +msgid "|get_device_status.png|" +msgstr "" + +#: ../../en/refs/unit.hbridge.ref:14 91cf1f28536a48199d68554711e50bc8 +msgid "get_device_status.png" +msgstr "" + +#: 8cf43233ed6b4f8a8f0f644cfd853e7d of +#: unit.hbridge.HbridgeUnit.get_driver_config:1 +msgid "Get driver config." +msgstr "获取驱动配置。" + +#: a866d155fcc244b2859eb1a1f3732174 of +#: unit.hbridge.HbridgeUnit.get_driver_config:5 +msgid "driver config." +msgstr "驱动配置。" + +#: ce9d1a5c2e0042aca24200263af097ec of +#: unit.hbridge.HbridgeUnit.get_driver_config:10 +msgid "|get_driver_config.png|" +msgstr "" + +#: ../../en/refs/unit.hbridge.ref:12 16175f179f6141f5ba6898e2fc3ddc58 +msgid "get_driver_config.png" +msgstr "" + +#: 8e23df224cd44832a6dbcdbbeadb1cac of +#: unit.hbridge.HbridgeUnit.get_vin_current:1 +msgid "Get the input voltage current (unit: A)." +msgstr "获取输入电流(单位:A)。" + +#: f81690b699594849a090ede7415268b3 of +#: unit.hbridge.HbridgeUnit.get_vin_current:3 +msgid "The input voltage current value." +msgstr "输入电流。" + +#: d37ce7a42c9b4eba8c3dd44fc33d87da of +#: unit.hbridge.HbridgeUnit.get_vin_current:8 +msgid "|get_vin_current.png|" +msgstr "" + +#: ../../en/refs/unit.hbridge.ref:20 713f5b5e5b6540079271cb70a728e678 +msgid "get_vin_current.png" +msgstr "" + +#: 2acf4b7b3b2c413abcad68ee7d139555 of unit.hbridge.HbridgeUnit.set_16bit_pwm:1 +msgid "Set 16-bit pwm duty cycle" +msgstr "设置 PWM 占空比,范围为 16 位(0~65535)" + +#: 19c9c7179439434e98f5ba7fcc651a86 of unit.hbridge.HbridgeUnit.set_16bit_pwm:3 +msgid "pwm duty, range: 0~65535" +msgstr "PWM 占空比,范围:0~65535。" + +#: 67d5550a83d247dcb83232ebd205fa88 of unit.hbridge.HbridgeUnit.set_16bit_pwm:7 +msgid "|set_16bit_pwm.png|" +msgstr "" + +#: ../../en/refs/unit.hbridge.ref:17 be4101c058e24b0c9371b717d7d4f6b1 +msgid "set_16bit_pwm.png" +msgstr "" + +#: 2cfaa4a655dc4c9aa984ec3e4d28f7e8 of unit.hbridge.HbridgeUnit.set_8bit_pwm:1 +msgid "Set 8-bit pwm duty cycle" +msgstr "设置 PWM 占空比,范围为 8 位(0~255)" + +#: 44ab07933cce46f09c82fc1d5ce51916 of unit.hbridge.HbridgeUnit.set_8bit_pwm:3 +msgid "PWM duty, range: 0~255" +msgstr "PWM 占空比,范围:0~255。" + +#: b437eadae5394f118f66783aaa4adc06 of unit.hbridge.HbridgeUnit.set_8bit_pwm:7 +msgid "|set_8bit_pwm.png|" +msgstr "" + +#: ../../en/refs/unit.hbridge.ref:16 02557a1892ec4b429d7a2fd891ab82d3 +msgid "set_8bit_pwm.png" +msgstr "" + +#: f1a7cf7d14624b62ae8deb6c72f6203b of unit.hbridge.HbridgeUnit.set_direction:1 +msgid "Set direction" +msgstr "设置方向。" + +#: cbb6658d5e2240f49cf86cf216d7e648 of unit.hbridge.HbridgeUnit.set_direction:3 +msgid "This method controls the motor's movement direction or stops it." +msgstr "该方法用于控制电机的运动方向或使其停止。" + +#: 8bbbf13cae33456e8ad71613448def49 of unit.hbridge.HbridgeUnit.set_direction:5 +msgid "Direction control parameter: - 0: Stop - 1: Forward - 2: Reverse" +msgstr "方向控制参数: - 0:停止 - 1:正转 - 2:反转。" + +#: 5fd4a7267164477f86e9847fa5f379c9 of +#: unit.hbridge.HbridgeUnit.set_direction:12 +msgid "|set_direction.png|" +msgstr "" + +#: ../../en/refs/unit.hbridge.ref:15 4a415f9464604601828a07f64aa25bda +msgid "set_direction.png" +msgstr "" + +#: 21e51de72ae2478fb0b869c61d4de81b of +#: unit.hbridge.HbridgeUnit.set_percentage_pwm:1 +msgid "Set the PWM output based on percentage." +msgstr "根据百分比设置 PWM 输出。" + +#: 2c34c368e3c042a69b2fb0c7199f49a6 of +#: unit.hbridge.HbridgeUnit.set_percentage_pwm:3 +msgid "PWM duty cycle as a percentage (0 to 100)." +msgstr "PWM 占空比的百分比(0 到 100)" + +#: 8c8176d9a19e4cf690b37eca590d68e1 of +#: unit.hbridge.HbridgeUnit.set_percentage_pwm:4 +msgid "PWM resolution (8 or 16 bits), default is 8." +msgstr "PWM 分辨率(88位或16位),默认位8位。" + +#: 4823c4e11cef4e69b480793ffbca8a49 of +#: unit.hbridge.HbridgeUnit.set_percentage_pwm:8 +msgid "|set_percentage_pwm.png|" +msgstr "" + +#: ../../en/refs/unit.hbridge.ref:18 ff6f9967ac474d599b9f6c19778ae303 +msgid "set_percentage_pwm.png" +msgstr "" + +#: 9c84036c9e36439787f6f951b586a8aa of unit.hbridge.HbridgeUnit.set_pwm_freq:1 +msgid "Set PWM frequency." +msgstr "设置 PWM 频率。" + +#: 5612e01febbc4e278b846594c2190c57 of unit.hbridge.HbridgeUnit.set_pwm_freq:3 +msgid "The PWM frequnecy." +msgstr "PWM 频率。" + +#: d89dd03b4a68476cb5c3b4a203a53801 of unit.hbridge.HbridgeUnit.set_pwm_freq:7 +msgid "|set_pwm_freq.png|" +msgstr "" + +#: ../../en/refs/unit.hbridge.ref:19 ea593d9fcca7487abca1b937bff5f57a +msgid "set_pwm_freq.png" +msgstr "" + +#~ msgid "Receive data" +#~ msgstr "" + diff --git a/examples/unit/hbridge/cores3_hbridge_motor_control.m5f2 b/examples/unit/hbridge/cores3_hbridge_motor_control.m5f2 new file mode 100644 index 00000000..5b2fdf99 --- /dev/null +++ b/examples/unit/hbridge/cores3_hbridge_motor_control.m5f2 @@ -0,0 +1 @@ +{"version":"V2.0","versionNumber":"V2.2.5","type":"cores3","components":[{"name":"screen","type":"screen","layer":0,"screenId":"builtin","screenName":"","id":"__cores3_screen","createTime":1744720281222,"x":0,"y":0,"width":320,"height":240,"backgroundColor":"#222222","size":0,"isSelected":true},{"name":"title0","type":"title","layer":1,"screenId":"builtin","screenName":"","id":"w#YEDYbu5nth8poG","createTime":1744723736347,"x":0,"y":0,"color":"#ffffff","backgroundColor":"#0000FF","text":"HBridge Motor Control","textOffset":3,"font":"Widgets.FONTS.DejaVu24","isSelected":false},{"name":"label0","type":"label","layer":2,"screenId":"builtin","screenName":"","id":"kamwbe2WpTcps$yU","createTime":1744724129095,"x":35,"y":60,"color":"#ffffff","backgroundColor":"#222222","text":"Speed:","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false,"width":65,"height":20},{"name":"label_speed","type":"label","layer":3,"screenId":"builtin","screenName":"","id":"xb#pH0=i3YYrs4K0","createTime":1744724153874,"x":110,"y":60,"color":"#ffffff","backgroundColor":"#222222","text":"0","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false,"width":58,"height":20}],"resources":[{"hardware":["hardware_button","hardware_pin_button","imu","speaker","touch","als","mic","i2c"]},{"unit":["unit_hbridge"]}],"units":[{"type":"unit_hbridge","name":"hbridge_0","portList":["A","PAHUB","Custom"],"portType":"A","userPort":[22,21],"id":"czCp8ohbpToFl4u`","createTime":1744723728373,"bus":"i2c0","pahubPortList":[0,1,2,3,4,5],"pahubPort":0,"initBlockId":"M%w^awe__||1]R;0EX}%"}],"hats":[],"bases":[],"i2cs":[{"id":"i2c0","portType":"A","userPort":[22,21],"freq":"100000","blockId":"ecw`8/e-FHU7?nEAG.2F"}],"blockly":"speeddirtrue010000012hbridge_00x20hbridge_01000speed0hbridge_00dirTRUEtruespeedADD1speed1label1LabelspeedGTspeed99speed0dirdirdirhbridge_01hbridge_021000hbridge_0850speed50","screen":[{"simulationName":"Built-in","type":"builtin","width":320,"height":240,"scale":0.78,"screenName":"","blockId":"","screenColorType":0,"id":"builtin","createTime":1744720281221}],"logicWhenNum":0,"customList":[]} \ No newline at end of file diff --git a/examples/unit/hbridge/cores3_hbridge_motor_control.py b/examples/unit/hbridge/cores3_hbridge_motor_control.py new file mode 100644 index 00000000..28f8f0d3 --- /dev/null +++ b/examples/unit/hbridge/cores3_hbridge_motor_control.py @@ -0,0 +1,66 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import os, sys, io +import M5 +from M5 import * +from hardware import I2C +from hardware import Pin +from unit import HbridgeUnit +import time + + +title0 = None +label0 = None +label_speed = None +i2c0 = None +hbridge_0 = None +speed = None +dir2 = None + + +def setup(): + global title0, label0, label_speed, i2c0, hbridge_0, speed, dir2 + M5.begin() + Widgets.fillScreen(0x222222) + title0 = Widgets.Title("HBridge Motor Control", 3, 0xFFFFFF, 0x0000FF, Widgets.FONTS.DejaVu24) + label0 = Widgets.Label("Speed:", 35, 60, 1.0, 0xFFFFFF, 0x222222, Widgets.FONTS.DejaVu18) + label_speed = Widgets.Label("0", 110, 60, 1.0, 0xFFFFFF, 0x222222, Widgets.FONTS.DejaVu18) + i2c0 = I2C(0, scl=Pin(1), sda=Pin(2), freq=100000) + hbridge_0 = HbridgeUnit(i2c0, 0x20) + hbridge_0.set_pwm_freq(1000) + speed = 0 + hbridge_0.set_direction(0) + dir2 = True + + +def loop(): + global title0, label0, label_speed, i2c0, hbridge_0, speed, dir2 + M5.update() + speed = speed + 1 + label0.setText(str(speed)) + if speed > 99: + speed = 0 + dir2 = not dir2 + if dir2: + hbridge_0.set_direction(1) + else: + hbridge_0.set_direction(2) + time.sleep_ms(1000) + hbridge_0.set_percentage_pwm(speed, 8) + time.sleep_ms(50) + + +if __name__ == "__main__": + try: + setup() + while True: + loop() + except (Exception, KeyboardInterrupt) as e: + try: + from utility import print_error_msg + + print_error_msg(e) + except ImportError: + print("please update to latest firmware") diff --git a/m5stack/libs/unit/hbridge.py b/m5stack/libs/unit/hbridge.py index 0750e2ae..ddddd34b 100644 --- a/m5stack/libs/unit/hbridge.py +++ b/m5stack/libs/unit/hbridge.py @@ -1,11 +1,14 @@ -# SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD # # SPDX-License-Identifier: MIT -from machine import I2C +import machine import struct from .pahub import PAHUBUnit from .unit_helper import UnitError +import sys +if sys.platform != "esp32": + from typing import Union HBRIDGE_ADDR = 0x20 @@ -21,31 +24,57 @@ class HbridgeUnit: + """Create an HbridgeUnit object. + + :param i2c: I2C port. + :type i2c: machine.I2C | PAHUBUnit + :param address: HbridgeUnit Slave Address. + :type address: int | list | tuple + + UiFlow2 Code Block: + + |init.png| + + MicroPython Code Block: + + .. code-block:: python + + from unit import HbridgeUnit + + unit_hbridge_0 = HbridgeUnit(i2c0, 0x20) + """ + def __init__( self, - i2c: I2C | PAHUBUnit, + i2c: Union[machine.I2C, PAHUBUnit], address: int | list | tuple = HBRIDGE_ADDR, ): - """ - Hbridge Initialize Function - Set I2C port, Hbridge Slave Address - """ self.hbridge_i2c = i2c self.init_i2c_address(address) - def init_i2c_address(self, slave_addr=HBRIDGE_ADDR): - """ - init the i2c address - slave_addr : 0x20 to 0x2F - """ + def init_i2c_address(self, slave_addr: int = HBRIDGE_ADDR) -> None: if slave_addr >= 0x20 and slave_addr <= 0x2F: self.i2c_addr = slave_addr if self.i2c_addr not in self.hbridge_i2c.scan(): raise UnitError("Hbridge unit maybe not connect") - def get_driver_config(self, reg=0): - """ - get driver config value + def get_driver_config(self, reg: int = 0) -> int: + """Get driver config. + + :param int reg: + + :returns: driver config. + :rtype: int + + UiFlow2 Code Block: + + |get_driver_config.png| + + MicroPython Code Block: + + .. code-block:: python + + unit_hbridge_0.get_driver_config(reg) """ leng = 1 if reg > 1: @@ -55,32 +84,77 @@ def get_driver_config(self, reg=0): else: return self.read_reg(reg, leng)[0] - def set_direction(self, dir=0): - """ - set direction - dir : 0 stop, 1 forward, 2 reverse + def set_direction(self, dir: int = 0) -> None: + """Set direction + + This method controls the motor's movement direction or stops it. + + :param int dir: Direction control parameter: + - 0: Stop + - 1: Forward + - 2: Reverse + + UiFlow2 Code Block: + + |set_direction.png| + + MicroPython Code Block: + + .. code-block:: python + + unit_hbridge_0.set_direction(dir) """ self.write_mem_list(DIRECTION_REG, [dir]) - def set_8bit_pwm(self, duty=0): - """ - set 8bit pwm dutycycle - duty : 0 to 255 + def set_8bit_pwm(self, duty: int = 0) -> None: + """Set 8-bit pwm duty cycle + + :param int duty: PWM duty, range: 0~255 + + UiFlow2 Code Block: + + |set_8bit_pwm.png| + + MicroPython Code Block: + + .. code-block:: python + + unit_hbridge_0.set_8bit_pwm(duty) """ self.write_mem_list(PWM8BIT_REG, [duty]) - def set_16bit_pwm(self, duty=0): - """ - set 16bit pwm dutycycle - duty : 0 to 65535 + def set_16bit_pwm(self, duty: int = 0) -> None: + """Set 16-bit pwm duty cycle + + :param int duty: pwm duty, range: 0~65535 + + UiFlow2 Code Block: + + |set_16bit_pwm.png| + + MicroPython Code Block: + + .. code-block:: python + + unit_hbridge_0.set_16bit_pwm(duty) """ self.write_mem_list(PWM16BIT_REG, [(duty & 0xFF), ((duty >> 8) & 0xFF)]) - def set_percentage_pwm(self, duty=0, res=8): - """ - set 8bit or 16bit pwm dutycycle - duty : 0 to 100% - resolution: 8 or 16 bit + def set_percentage_pwm(self, duty: int = 0, res: int = 8) -> None: + """Set the PWM output based on percentage. + + :param int duty: PWM duty cycle as a percentage (0 to 100). + :param int res: PWM resolution (8 or 16 bits), default is 8. + + UiFlow2 Code Block: + + |set_percentage_pwm.png| + + MicroPython Code Block: + + .. code-block:: python + + unit_hbridge_0.set_percentage_pwm(duty, reg) """ duty = max(min(duty, 100), 0) if res == 8: @@ -89,17 +163,48 @@ def set_percentage_pwm(self, duty=0, res=8): duty = self.map(duty, 0, 100, 0, 65535) self.write_mem_list(PWM8BIT_REG, [duty]) - def set_pwm_freq(self, freq=0): - """ - set direction - duty : 0 to 65535 + def set_pwm_freq(self, freq: int = 0) -> None: + """Set PWM frequency. + + :param int freq: The PWM frequnecy. + + UiFlow2 Code Block: + + |set_pwm_freq.png| + + MicroPython Code Block: + + .. code-block:: python + + unit_hbridge_0.set_pwm_freq(freq) """ freq = max(min(freq, 10000), 100) self.write_mem_list(PWMFREQ_REG, [(freq & 0xFF), ((freq >> 8) & 0xFF)]) - def get_adc_value(self, raw=0, res=8): - """ - get adc value + def get_adc_value(self, raw: int = 0, res: int = 8) -> None: + """Get ADC value. + + This method retrieves the ADC value based on the specified resolution. + It supports both 8-bit and 16-bit ADC resolutions. If `raw` is set to `1`, + the raw ADC value is returned. Otherwise, the corresponding voltage is + calculated and returned. + + :param int raw: If 1, returns the raw ADC value. If 0, returns the voltage + (calculated based on ADC value). + :param int res: ADC resolution (8 or 16 bits). Default is 8 bits. + + :returns: The raw ADC value or the calculated voltage, depending on `raw`. + :rtype: float or int + + UiFlow2 Code Block: + + |get_adc_value.png| + + MicroPython Code Block: + + .. code-block:: python + + unit_hbridge_0.get_adc_value(raw, res) """ leng = 1 if res > 8: @@ -117,19 +222,42 @@ def get_adc_value(self, raw=0, res=8): return round(val, 2) #############################support v1.1################################ - def get_vin_current(self): - """ - get vin current. + def get_vin_current(self) -> float: + """Get the input voltage current (unit: A). + + :returns: The input voltage current value. + :rtype: float + + UiFlow2 Code Block: + + |get_vin_current.png| + + MicroPython Code Block: + + .. code-block:: python + + unit_hbridge_0.get_vin_current() """ buf = self.read_reg(VIN_CURRENT_REG, 4) return struct.unpack(" int: + """Get device status. - def get_device_status(self, mode): - """ get firmware version and i2c address. - mode : 0xFE and 0xFF + + :param int mode: 0xFE and 0xFF + + UiFlow2 Code Block: + + |get_device_status.png| + + MicroPython Code Block: + + .. code-block:: python + + unit_hbridge_0.get_device_status(mode) """ if mode >= FW_VER_REG and mode <= I2C_ADDR_REG: return self.read_reg(mode, 1)[0] From ea4947b894eb998fde61277baa318ed12acead11 Mon Sep 17 00:00:00 2001 From: luoweiyuan Date: Thu, 17 Apr 2025 16:25:37 +0800 Subject: [PATCH 055/322] docs: Add Unit BLDCDriver docs. Signed-off-by: luoweiyuan --- docs/en/refs/unit.bldc_driver.ref | 42 + docs/en/units/bldc_driver.rst | 57 ++ docs/en/units/index.rst | 1 + .../zh_CN/LC_MESSAGES/units/bldc_driver.po | 817 ++++++++++++++++++ .../cores3_bldc_driver_example.m5f2 | 1 + .../bldc_driver/cores3_bldc_driver_example.py | 62 ++ m5stack/libs/unit/bldc_driver.py | 459 +++++++++- 7 files changed, 1395 insertions(+), 44 deletions(-) create mode 100644 docs/en/refs/unit.bldc_driver.ref create mode 100644 docs/en/units/bldc_driver.rst create mode 100644 docs/locales/zh_CN/LC_MESSAGES/units/bldc_driver.po create mode 100644 examples/unit/bldc_driver/cores3_bldc_driver_example.m5f2 create mode 100644 examples/unit/bldc_driver/cores3_bldc_driver_example.py diff --git a/docs/en/refs/unit.bldc_driver.ref b/docs/en/refs/unit.bldc_driver.ref new file mode 100644 index 00000000..4572ddc9 --- /dev/null +++ b/docs/en/refs/unit.bldc_driver.ref @@ -0,0 +1,42 @@ +.. |Unit BLDCDriver| image:: https://static-cdn.m5stack.com/resource/docs/products/unit/Unit-BLDC%20Driver/img-a97915ed-d14b-4dc2-a93d-e72f0d917e95.webp + :target: https://docs.m5stack.com/zh_CN/unit/Unit-BLDC%20Driver + :height: 200px + :width: 200px + +.. |init.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/bldc_driver/init.png +.. |get_current_mode.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/bldc_driver/get_current_mode.png +.. |get_motor_current_direction.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/bldc_driver/get_motor_current_direction.png +.. |get_motor_current_model.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/bldc_driver/get_motor_current_model.png +.. |get_motor_pole_pairs.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/bldc_driver/get_motor_pole_pairs.png +.. |get_motor_status.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/bldc_driver/get_motor_status.png +.. |get_open_loop_pwm.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/bldc_driver/get_open_loop_pwm.png +.. |get_read_back_rpm_float.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/bldc_driver/get_read_back_rpm_float.png +.. |get_read_back_rpm_int.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/bldc_driver/get_read_back_rpm_int.png +.. |get_read_back_rpm_str.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/bldc_driver/get_read_back_rpm_str.png +.. |get_read_back_freq_float.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/bldc_driver/get_read_back_freq_float.png +.. |get_read_back_freq_int.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/bldc_driver/get_read_back_freq_int.png +.. |get_read_back_freq_str.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/bldc_driver/get_read_back_freq_str.png +.. |get_rpm_float.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/bldc_driver/get_rpm_float.png +.. |get_rpm_int.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/bldc_driver/get_rpm_int.png +.. |get_pid_value.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/bldc_driver/get_pid_value.png +.. |get_device_spec.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/bldc_driver/get_device_spec.png +.. |set_mode.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/bldc_driver/set_mode.png +.. |set_direction.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/bldc_driver/set_direction.png +.. |set_motor_model.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/bldc_driver/set_motor_model.png +.. |set_pole_pairs.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/bldc_driver/set_pole_pairs.png +.. |set_open_loop_pwm.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/bldc_driver/set_open_loop_pwm.png +.. |set_rpm_float.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/bldc_driver/set_rpm_float.png +.. |set_rpm_int.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/bldc_driver/set_rpm_int.png +.. |set_pid_value.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/bldc_driver/set_pid_value.png +.. |save_data_in_flash.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/bldc_driver/save_data_in_flash.png +.. |set_i2c_address.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/bldc_driver/set_i2c_address.png +.. |cores3_bldc_driver_example.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/bldc_driver/example.png + +.. |cores3_bldc_driver_example.m5f2| raw:: html + + + cores3_bldc_driver_example.m5f2 + diff --git a/docs/en/units/bldc_driver.rst b/docs/en/units/bldc_driver.rst new file mode 100644 index 00000000..8be57851 --- /dev/null +++ b/docs/en/units/bldc_driver.rst @@ -0,0 +1,57 @@ +BLDCDriver Unit +=============== + +.. sku: U181 + +.. include:: ../refs/unit.bldc_driver.ref + +This library is the driver for Unit BLDCDriver. + +Support the following products: + + |Unit BLDCDriver| + +UiFlow2 Example +--------------- + +Motor speed control +^^^^^^^^^^^^^^^^^^^ + +Open the |cores3_bldc_driver_example.m5f2| project in UiFlow2. + +The example program gradually increases the motor speed and then stops the motor. + +UiFlow2 Code Block: + + |cores3_bldc_driver_example.png| + +Example output: + + None + +MicroPython Example +------------------- + +Motor speed control +^^^^^^^^^^^^^^^^^^^ + +The example program gradually increases the motor speed and then stops the motor. + +MicroPython Code Block: + + .. literalinclude:: ../../../examples/unit/bldc_driver/cores3_bldc_driver_example.py + :language: python + :linenos: + +Example output: + + None + +**API** +------- + +BLDCDriverUnit +^^^^^^^^^^^^^^ + +.. autoclass:: unit.bldc_driver.BLDCDriverUnit + :members: diff --git a/docs/en/units/index.rst b/docs/en/units/index.rst index b80e4949..0485be67 100644 --- a/docs/en/units/index.rst +++ b/docs/en/units/index.rst @@ -13,6 +13,7 @@ Unit angle.rst angle8.rst asr.rst + bldc_driver.rst bps.rst button.rst buzzer.rst diff --git a/docs/locales/zh_CN/LC_MESSAGES/units/bldc_driver.po b/docs/locales/zh_CN/LC_MESSAGES/units/bldc_driver.po new file mode 100644 index 00000000..308831af --- /dev/null +++ b/docs/locales/zh_CN/LC_MESSAGES/units/bldc_driver.po @@ -0,0 +1,817 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2016 - 2025 M5Stack Technology Co., Ltd +# This file is distributed under the same license as the UIFlow2 Programming +# Guide package. +# FIRST AUTHOR , 2025. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: UIFlow2 Programming Guide \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-04-18 17:17+0800\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: zh_CN\n" +"Language-Team: zh_CN \n" +"Plural-Forms: nplurals=1; plural=0;\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.16.0\n" + +#: ../../en/units/bldc_driver.rst:2 93dd7957b0e74a59a1bd84a473476ea0 +msgid "BLDCDriver Unit" +msgstr "" + +#: ../../en/units/bldc_driver.rst:8 62b4ad3c81db48f79a77fb9dc2da01ca +msgid "This library is the driver for Unit BLDCDriver." +msgstr "这个库是 Unit BLDCDrive 的驱动。" + +#: ../../en/units/bldc_driver.rst:10 0ad3ff0063c44eb0b0b7e2b330901d84 +msgid "Support the following products:" +msgstr "支持以下产品:" + +#: ../../en/units/bldc_driver.rst:12 c33cf626bc9e44a58e727f3c8e60f13e +msgid "|Unit BLDCDriver|" +msgstr "" + +#: ../../en/refs/unit.bldc_driver.ref d5619785b2074875a9e4924c28b811c9 +msgid "Unit BLDCDriver" +msgstr "" + +#: ../../en/units/bldc_driver.rst:15 8576d699c5934d21865f809dcaec54c6 +msgid "UiFlow2 Example" +msgstr "UiFlow2 应用示例" + +#: ../../en/units/bldc_driver.rst:18 ../../en/units/bldc_driver.rst:36 +#: 951ea1513620462fbfc37b6d08955abd a36cfd7e8de542d8ad11d8d74295cb6a +msgid "Motor speed control" +msgstr "电机速度控制。" + +#: ../../en/units/bldc_driver.rst:20 5e8fac875fc14318ad91a3a19823c76f +msgid "Open the |cores3_bldc_driver_example.m5f2| project in UiFlow2." +msgstr "在 UiFlow2 上打开 |cores3_bldc_driver_example.m5f2| 项目。" + +#: ../../en/units/bldc_driver.rst:22 ../../en/units/bldc_driver.rst:38 +#: 5e5f79af12354a5591b8300aa99f07fc db513e0c51c64c94b303fc8b50d4e1df +msgid "" +"The example program gradually increases the motor speed and then stops " +"the motor." +msgstr "示例程序逐渐增加电机转速,然后停止电机转动。" + +#: ../../en/units/bldc_driver.rst:24 0d206ff65c624bcda611b7fef1da9a3e +#: 235001b97e4d468dbbaad7963e51df9d 267fba6f5cd04baa9daed21f2c2ea515 +#: 32672c27af17461cb298063e2da611de 3574bcde3d5d4cd3894f5c11ee0361c3 +#: 3dc1b16d1f7e450fbbc303f4445e7780 3f76faa209d748ada36ce77755d8eb2e +#: 4dd43aca9675448489cabe697f8b85da 5a2a03dbaecb4df9a0e1f771a7956e96 +#: 6901115df0ba400696d7f1508c7b5469 71786a8b09054e49973b21fa8f4f308a +#: 7476edcb722a4cbe914f4c518392c633 7bad81ca6d8244be9f2b1980f0f80757 +#: 7e6206e880d7408aa84c2acc58c863bb 86fd1134001d487f8464cf55483ee25b +#: 8bc1450b12994c1a9dfa1c85d8d54f7c 9f9c2fd92ccf457982db56ae9e2cef5f +#: a9dd6f19bd0147b3878b9db8896e1cf3 ab7cdfc955974b71a4090720a3f9bd94 +#: ab8d6612ee284e1bbd893c611a9d5b44 b05a54835d824fed982245780fa32f57 +#: b5f545eca14542e4a05144f7c2c59672 c95bb8b606694642b5a8774059d084d8 +#: d8185c1841ca48ab9949c2d4dfc141da e9c9af6bd108432083103e74ca99a291 +#: efe3fc1c0c57474aaf0203dc4f0ec060 f0c1082361044cb68fc663be585644ed +#: ff0eea7434b24536b3dd88a09f5e6faf of unit.bldc_driver.BLDCDriverUnit:8 +#: unit.bldc_driver.BLDCDriverUnit.get_current_mode:6 +#: unit.bldc_driver.BLDCDriverUnit.get_device_spec:9 +#: unit.bldc_driver.BLDCDriverUnit.get_motor_current_direction:6 +#: unit.bldc_driver.BLDCDriverUnit.get_motor_current_model:6 +#: unit.bldc_driver.BLDCDriverUnit.get_motor_pole_pairs:6 +#: unit.bldc_driver.BLDCDriverUnit.get_motor_status:6 +#: unit.bldc_driver.BLDCDriverUnit.get_open_loop_pwm:6 +#: unit.bldc_driver.BLDCDriverUnit.get_pid_value:8 +#: unit.bldc_driver.BLDCDriverUnit.get_read_back_freq_float:6 +#: unit.bldc_driver.BLDCDriverUnit.get_read_back_freq_int:6 +#: unit.bldc_driver.BLDCDriverUnit.get_read_back_freq_str:6 +#: unit.bldc_driver.BLDCDriverUnit.get_read_back_rpm_float:6 +#: unit.bldc_driver.BLDCDriverUnit.get_read_back_rpm_int:6 +#: unit.bldc_driver.BLDCDriverUnit.get_read_back_rpm_str:6 +#: unit.bldc_driver.BLDCDriverUnit.get_rpm_float:6 +#: unit.bldc_driver.BLDCDriverUnit.get_rpm_int:6 +#: unit.bldc_driver.BLDCDriverUnit.save_data_in_flash:3 +#: unit.bldc_driver.BLDCDriverUnit.set_direction:5 +#: unit.bldc_driver.BLDCDriverUnit.set_i2c_address:5 +#: unit.bldc_driver.BLDCDriverUnit.set_mode:5 +#: unit.bldc_driver.BLDCDriverUnit.set_motor_model:5 +#: unit.bldc_driver.BLDCDriverUnit.set_open_loop_pwm:5 +#: unit.bldc_driver.BLDCDriverUnit.set_pid_value:9 +#: unit.bldc_driver.BLDCDriverUnit.set_pole_pairs:5 +#: unit.bldc_driver.BLDCDriverUnit.set_rpm_float:5 +#: unit.bldc_driver.BLDCDriverUnit.set_rpm_int:5 +msgid "UiFlow2 Code Block:" +msgstr "UiFlow2 代码块:" + +#: ../../en/units/bldc_driver.rst:26 eed6c8c04a5e476f81f82672cce07bab +msgid "|cores3_bldc_driver_example.png|" +msgstr "" + +#: ../../en/refs/unit.bldc_driver.ref:33 aa9181e86ad64dc88613f797ce19248b +msgid "cores3_bldc_driver_example.png" +msgstr "" + +#: ../../en/units/bldc_driver.rst:28 ../../en/units/bldc_driver.rst:46 +#: 5177a6266ce94ec9a5bc5e2c007892e0 865b3feb853d44799e77569e4fe998f3 +msgid "Example output:" +msgstr "示例输出:" + +#: ../../en/units/bldc_driver.rst:30 ../../en/units/bldc_driver.rst:48 +#: 4042049118334009a7dc90126b1a2014 92056efc0857449b91b6888b698f0a48 +msgid "None" +msgstr "无" + +#: ../../en/units/bldc_driver.rst:33 70a57387572b46debf57b2c2a6b1b4ec +msgid "MicroPython Example" +msgstr "MicroPython 应用示例" + +#: ../../en/units/bldc_driver.rst:40 2e9f78fe8c15485db8e28a0b6d44f7aa +#: 30aa71690d2a4807b3109d9b9e59d4b0 31a9611f7d0c491cbb75caac42cd53c2 +#: 3a8c648b96424f44a14fa3330a889ad3 42004a84b99043998431f4fee7ff94e5 +#: 42295365c9904acbb5af06dd5961df7b 490338f471f04a4081d1a1ffac6bce61 +#: 4e37fc64ff5048c5aa355465f1b7c4d3 5b8a248a9820436ea5f328d68ad3559f +#: 6ca1573558824a4b87c7bfa49da8ac63 6edd4290ca9a4f1eb5143a8cc442f96a +#: 70787f7f7e0d400aa0bdba35c4cbc8ab 750caa0cd41d40d385dfb7bac32c95f9 +#: 7dbbb3756e964feaba8261cc85501266 8448489338e64b24bc7e117fd1a0e175 +#: 88c9d471217c462ca3cf55c79320d519 8f033437ca0540d1a0cb9dd5dcda12c3 +#: 920a0221f31c4da6b512bbc078567b1f 9288a374a4b34cf4aea37bc0afa99569 +#: 9ae58cdd2b464b638f67a4cd9e5751e9 a377c4d5a6b947c9af6928f75021fca8 +#: a667f891774941b09b1cf78e2396f9fb b34e479facc948bea1c25365c1002bbf +#: b655980c12a64577b56881bb67dd4e24 bff5eea967794f89bf9d362d5288e748 +#: cb2872a74f29446bbb2b80108be6cc5b f7561ceb0d54430c8558e2d16a3d6509 +#: f789c3ca2541499ebccc16cd0c36976a of unit.bldc_driver.BLDCDriverUnit:12 +#: unit.bldc_driver.BLDCDriverUnit.get_current_mode:10 +#: unit.bldc_driver.BLDCDriverUnit.get_device_spec:13 +#: unit.bldc_driver.BLDCDriverUnit.get_motor_current_direction:10 +#: unit.bldc_driver.BLDCDriverUnit.get_motor_current_model:10 +#: unit.bldc_driver.BLDCDriverUnit.get_motor_pole_pairs:10 +#: unit.bldc_driver.BLDCDriverUnit.get_motor_status:10 +#: unit.bldc_driver.BLDCDriverUnit.get_open_loop_pwm:10 +#: unit.bldc_driver.BLDCDriverUnit.get_pid_value:12 +#: unit.bldc_driver.BLDCDriverUnit.get_read_back_freq_float:10 +#: unit.bldc_driver.BLDCDriverUnit.get_read_back_freq_int:10 +#: unit.bldc_driver.BLDCDriverUnit.get_read_back_freq_str:10 +#: unit.bldc_driver.BLDCDriverUnit.get_read_back_rpm_float:10 +#: unit.bldc_driver.BLDCDriverUnit.get_read_back_rpm_int:10 +#: unit.bldc_driver.BLDCDriverUnit.get_read_back_rpm_str:10 +#: unit.bldc_driver.BLDCDriverUnit.get_rpm_float:10 +#: unit.bldc_driver.BLDCDriverUnit.get_rpm_int:10 +#: unit.bldc_driver.BLDCDriverUnit.save_data_in_flash:7 +#: unit.bldc_driver.BLDCDriverUnit.set_direction:9 +#: unit.bldc_driver.BLDCDriverUnit.set_i2c_address:9 +#: unit.bldc_driver.BLDCDriverUnit.set_mode:9 +#: unit.bldc_driver.BLDCDriverUnit.set_motor_model:9 +#: unit.bldc_driver.BLDCDriverUnit.set_open_loop_pwm:9 +#: unit.bldc_driver.BLDCDriverUnit.set_pid_value:13 +#: unit.bldc_driver.BLDCDriverUnit.set_pole_pairs:9 +#: unit.bldc_driver.BLDCDriverUnit.set_rpm_float:9 +#: unit.bldc_driver.BLDCDriverUnit.set_rpm_int:9 +msgid "MicroPython Code Block:" +msgstr "MicroPython 代码块:" + +#: ../../en/units/bldc_driver.rst:51 50e0939b9d4249ebac749777a7e69111 +msgid "**API**" +msgstr "API参考" + +#: ../../en/units/bldc_driver.rst:54 a19463c66a4e4e1cb96cd20a2423ef8c +msgid "BLDCDriverUnit" +msgstr "" + +#: 30278f7fb28943dc8061295a73986b92 of unit.bldc_driver.BLDCDriverUnit:1 +msgid "Bases: :py:class:`object`" +msgstr "" + +#: 60ff480b61f041f0b9558596abcd2757 of unit.bldc_driver.BLDCDriverUnit:1 +msgid "Create an BLDCDriverUnit object." +msgstr "创建一个 BLDCDriverUnit 对象。" + +#: ../../en/units/bldc_driver.rst 1beb5a735bd54864a9e803d34654b559 +#: 541cda1fc85f48ff9c82731a243fa38e 7a9284c767e14b82990238844138cd17 +#: 8d3789598d3547539635d2d23c5c4e1f 9720f84ea3a24ec2922a25b2cf89f296 +#: 990140e0fed14cb4a295f6eeb704f745 9d865931d71546d58e192cd11e846c12 +#: c54d94f4d9144b929eaac25fa4b1680d e57066a9442a417284a656d37d484bab +#: efeadb09445e4c97adf95fe8bebf24eb fdb0f59a50ba44be9e78ff363ce8da52 of +#: unit.bldc_driver.BLDCDriverUnit.get_device_spec +#: unit.bldc_driver.BLDCDriverUnit.set_direction +#: unit.bldc_driver.BLDCDriverUnit.set_i2c_address +#: unit.bldc_driver.BLDCDriverUnit.set_mode +#: unit.bldc_driver.BLDCDriverUnit.set_motor_model +#: unit.bldc_driver.BLDCDriverUnit.set_open_loop_pwm +#: unit.bldc_driver.BLDCDriverUnit.set_pid_value +#: unit.bldc_driver.BLDCDriverUnit.set_pole_pairs +#: unit.bldc_driver.BLDCDriverUnit.set_rpm_float +#: unit.bldc_driver.BLDCDriverUnit.set_rpm_int +msgid "Parameters" +msgstr "参数" + +#: 8dee789d85d045b5b85b62e8b9a64ff6 of unit.bldc_driver.BLDCDriverUnit:3 +msgid "I2C port." +msgstr "I2C 端口。" + +#: f11d88dc84c746f0b9793f0ef1c8564f of unit.bldc_driver.BLDCDriverUnit:5 +msgid "BLDCDriverUnit Slave Address." +msgstr "BLDCDriverUnit 从机地址。" + +#: 8b50e56151de4ff89e221a469e42c766 of unit.bldc_driver.BLDCDriverUnit:10 +msgid "|init.png|" +msgstr "" + +#: ../../en/refs/unit.bldc_driver.ref:6 19248e61b1d14735903d7a34f8ad9316 +msgid "init.png" +msgstr "" + +#: 82ed379f21e243ffbb88ecb4b3f21ef8 of +#: unit.bldc_driver.BLDCDriverUnit.get_current_mode:1 +msgid "Get the current mode setting." +msgstr "获取电流模式设置。" + +#: 0804b8bec10642d3a084b6dbc3ce006d 12f48eb560ad456eb5e66f2b11da6175 +#: 1913af3ef91a4d6cbe56b3b05846c4fc 2303fce03ea14f8087a63e4e1394c8db +#: 31532b7d08504918ba322f21350d956c 393dbb0b71fe491a82248aa9fb755034 +#: 4bb302deb8974180b161a47d9f527920 51703ad55a994df988e7cbb6c3ec7aa7 +#: 56c0bbe8b0c74b75bd164bf0eb756014 a0a9b415f4cf406e897c802925d9da1d +#: a646b0e12e394ae59091766573456b7a e0b2a4be351c4248ae879dbecf0db0f8 +#: ea6205d520c9445399d8dfbf34a69f98 f8fd3b1b041f40208b6a6ccd2f502bed +#: fbe4f05ca67a46eaad5fc5865be0634a of +#: unit.bldc_driver.BLDCDriverUnit.get_current_mode +#: unit.bldc_driver.BLDCDriverUnit.get_motor_current_direction +#: unit.bldc_driver.BLDCDriverUnit.get_motor_current_model +#: unit.bldc_driver.BLDCDriverUnit.get_motor_pole_pairs +#: unit.bldc_driver.BLDCDriverUnit.get_motor_status +#: unit.bldc_driver.BLDCDriverUnit.get_open_loop_pwm +#: unit.bldc_driver.BLDCDriverUnit.get_pid_value +#: unit.bldc_driver.BLDCDriverUnit.get_read_back_freq_float +#: unit.bldc_driver.BLDCDriverUnit.get_read_back_freq_int +#: unit.bldc_driver.BLDCDriverUnit.get_read_back_freq_str +#: unit.bldc_driver.BLDCDriverUnit.get_read_back_rpm_float +#: unit.bldc_driver.BLDCDriverUnit.get_read_back_rpm_int +#: unit.bldc_driver.BLDCDriverUnit.get_read_back_rpm_str +#: unit.bldc_driver.BLDCDriverUnit.get_rpm_float +#: unit.bldc_driver.BLDCDriverUnit.get_rpm_int +msgid "Returns" +msgstr "" + +#: efdf553099e74380b72d0dedbf1a1643 of +#: unit.bldc_driver.BLDCDriverUnit.get_current_mode:3 +msgid "current mode." +msgstr "电流模式。" + +#: 02da2a04230a4e00b0ebb356be2b63d4 1e9903d9be444c55a8b31c5b11daea02 +#: 2c3d9e5114414646846b17fe80c1d456 37fa789836ae454abc50531b5bfa9cf7 +#: 53ce67bb3fa04a62bfb42b00500a5860 55825069bdb349f280af715dbedf3cc8 +#: 5e96b51589ea474b89bac2cd69281c20 61163af0aa864d1990d351170b79fe64 +#: 867d21241e664505ad0689d94a2a0d13 8acbf8c6f7474b8b8b7290197570a190 +#: a31bb87e054e4cce9c8515102a85abaa a320e20ee96449a597001d40d5f69100 +#: a3b1855b683c4de7a77401775cb54b33 a44e5bf7d3714163a1c6b45646b4b337 +#: a63b2f7b9e9b454290c18723aa13f519 a714729790ea41f18a93eabcf4b6c6a8 +#: ad434c74f9bc40d6a9a12cdbd1876fb4 b2693b178dba48bb88b972006ab775b6 +#: c5243ae6b1d64549a04ed9f1f6f9890a d1a5b345dea54ea0846434328c4d8d5d +#: da28066f60db4e16b4189c28312ff47e da2ca368e74646ea8feb64c9d36ab081 +#: da6d3194fac84f998aad5d94c7d96983 ec5a5b558c554758876de6e246845dae +#: fa0a8e1b21304733b77d3182bf25cd4c fcf80c27b62143a4b0e841b24de8344a of +#: unit.bldc_driver.BLDCDriverUnit.get_current_mode +#: unit.bldc_driver.BLDCDriverUnit.get_device_spec +#: unit.bldc_driver.BLDCDriverUnit.get_motor_current_direction +#: unit.bldc_driver.BLDCDriverUnit.get_motor_current_model +#: unit.bldc_driver.BLDCDriverUnit.get_motor_pole_pairs +#: unit.bldc_driver.BLDCDriverUnit.get_motor_status +#: unit.bldc_driver.BLDCDriverUnit.get_open_loop_pwm +#: unit.bldc_driver.BLDCDriverUnit.get_pid_value +#: unit.bldc_driver.BLDCDriverUnit.get_read_back_freq_float +#: unit.bldc_driver.BLDCDriverUnit.get_read_back_freq_int +#: unit.bldc_driver.BLDCDriverUnit.get_read_back_freq_str +#: unit.bldc_driver.BLDCDriverUnit.get_read_back_rpm_float +#: unit.bldc_driver.BLDCDriverUnit.get_read_back_rpm_int +#: unit.bldc_driver.BLDCDriverUnit.get_read_back_rpm_str +#: unit.bldc_driver.BLDCDriverUnit.get_rpm_float +#: unit.bldc_driver.BLDCDriverUnit.get_rpm_int +#: unit.bldc_driver.BLDCDriverUnit.save_data_in_flash +#: unit.bldc_driver.BLDCDriverUnit.set_direction +#: unit.bldc_driver.BLDCDriverUnit.set_i2c_address +#: unit.bldc_driver.BLDCDriverUnit.set_mode +#: unit.bldc_driver.BLDCDriverUnit.set_motor_model +#: unit.bldc_driver.BLDCDriverUnit.set_open_loop_pwm +#: unit.bldc_driver.BLDCDriverUnit.set_pid_value +#: unit.bldc_driver.BLDCDriverUnit.set_pole_pairs +#: unit.bldc_driver.BLDCDriverUnit.set_rpm_float +#: unit.bldc_driver.BLDCDriverUnit.set_rpm_int +msgid "Return type" +msgstr "" + +#: 0901c3d958e3476ca175682c8f71aaac of +#: unit.bldc_driver.BLDCDriverUnit.get_current_mode:8 +msgid "|get_current_mode.png|" +msgstr "" + +#: ../../en/refs/unit.bldc_driver.ref:7 87b932609f4f40ee8a5cf0938aadfab7 +msgid "get_current_mode.png" +msgstr "" + +#: 139622d25b5c4aa5be4f225e840c5034 of +#: unit.bldc_driver.BLDCDriverUnit.get_device_spec:1 +msgid "Get device firmware version and I2C address." +msgstr "获取设备固件和 I2C 地址。" + +#: f4c13b39f9c146a1ad5893584a2c9ec0 of +#: unit.bldc_driver.BLDCDriverUnit.get_device_spec:3 +msgid "" +"This method retrieves either the firmware version or the I2C address of " +"the device based on the provided mode." +msgstr "该方法根据提供的模式获取设备的固件版本或 I2C 地址。" + +#: e9feeab900844e9d82d82d1f5dbb583b of +#: unit.bldc_driver.BLDCDriverUnit.get_device_spec:5 +msgid "" +"The mode to determine what information to fetch. - `0xFE`: Retrieve " +"firmware version. - `0xFF`: Retrieve I2C address." +msgstr "mode 用于确定获取何种信息的模式。- 0xFE:获取固件版本;- 0xFF:获取 I2C 地址。" + +#: 6644c498a63f4dbf9e8ea72623721346 of +#: unit.bldc_driver.BLDCDriverUnit.get_device_spec:11 +msgid "|get_device_spec.png|" +msgstr "" + +#: ../../en/refs/unit.bldc_driver.ref:22 42dcffc47bb941f2a6166780c1ac60a8 +msgid "get_device_spec.png" +msgstr "" + +#: c421338a11564bbc9934ebef59509373 of +#: unit.bldc_driver.BLDCDriverUnit.get_motor_current_direction:1 +msgid "Get the current direction setting." +msgstr "获取电流方向设置。" + +#: 49350520b6e74877b26fd83058a9292c of +#: unit.bldc_driver.BLDCDriverUnit.get_motor_current_direction:3 +msgid "current direction." +msgstr "电流方向。" + +#: fa718d4db17146e7acb64c886b6bb5b8 of +#: unit.bldc_driver.BLDCDriverUnit.get_motor_current_direction:8 +msgid "|get_motor_current_direction.png|" +msgstr "" + +#: ../../en/refs/unit.bldc_driver.ref:8 5b600ac105ba46719f61b60736ada9be +msgid "get_motor_current_direction.png" +msgstr "" + +#: 2097fe38e0f24c9a97b2b55ab8e982d7 of +#: unit.bldc_driver.BLDCDriverUnit.get_motor_current_model:1 +msgid "Get the motor current model setting." +msgstr "获取电机电流模型设置。" + +#: 9c93ffefd1ef445d88cef3c3ef5f5066 of +#: unit.bldc_driver.BLDCDriverUnit.get_motor_current_model:3 +msgid "motor current model." +msgstr "电机电流模。" + +#: 1dcc30ab86e245d58cd5875cf25192d9 of +#: unit.bldc_driver.BLDCDriverUnit.get_motor_current_model:8 +msgid "|get_motor_current_model.png|" +msgstr "" + +#: ../../en/refs/unit.bldc_driver.ref:9 8aacfe750fa9457aa0009367b17af0ee +msgid "get_motor_current_model.png" +msgstr "" + +#: d67c992f96c84d50b010877f1194d7a5 of +#: unit.bldc_driver.BLDCDriverUnit.get_motor_pole_pairs:1 +msgid "Get the pole pairs setting." +msgstr "获取磁极对设置。" + +#: a3872d0ccce7409685c56473a314535d of +#: unit.bldc_driver.BLDCDriverUnit.get_motor_pole_pairs:3 +msgid "motor pole pairs." +msgstr "电机磁极对。" + +#: c5a5ba400a6148be8e274368ce6336a7 of +#: unit.bldc_driver.BLDCDriverUnit.get_motor_pole_pairs:8 +msgid "|get_motor_pole_pairs.png|" +msgstr "" + +#: ../../en/refs/unit.bldc_driver.ref:10 6cf9d7ae9d4c4fe59de919640a69c1ee +msgid "get_motor_pole_pairs.png" +msgstr "" + +#: 7e6d7e29e55c4e41b6fc35950e0f413b of +#: unit.bldc_driver.BLDCDriverUnit.get_motor_status:1 +msgid "Get motor status." +msgstr "获取电机状态。" + +#: 054efe2d13ca41de90a61cf450639579 of +#: unit.bldc_driver.BLDCDriverUnit.get_motor_status:3 +msgid "motor status." +msgstr "电机状态。" + +#: b55aec872f504607801d4ef5434c5f34 of +#: unit.bldc_driver.BLDCDriverUnit.get_motor_status:8 +msgid "|get_motor_status.png|" +msgstr "" + +#: ../../en/refs/unit.bldc_driver.ref:11 90111120ab7a4dd68dbfadd65cd54efc +msgid "get_motor_status.png" +msgstr "" + +#: 4f8ed39597eb49eb9d43330aae8b8e0a of +#: unit.bldc_driver.BLDCDriverUnit.get_open_loop_pwm:1 +msgid "Get the open loop pwm." +msgstr "获取开环 PWM。" + +#: 9069629b64274d1b8be2a8dd14d83efb of +#: unit.bldc_driver.BLDCDriverUnit.get_open_loop_pwm:3 +msgid "open loop pwm." +msgstr "开环 PWM。" + +#: bab93082863642f0b08ff0b232ba7393 of +#: unit.bldc_driver.BLDCDriverUnit.get_open_loop_pwm:8 +msgid "|get_open_loop_pwm.png|" +msgstr "" + +#: ../../en/refs/unit.bldc_driver.ref:12 b32d715e07f542439d03f12d2667ba5f +msgid "get_open_loop_pwm.png" +msgstr "" + +#: 6a885068e9394b21982282baf7a93cf2 of +#: unit.bldc_driver.BLDCDriverUnit.get_pid_value:1 +msgid "Get the PID value." +msgstr "获取 PID 值。" + +#: ab56c665e29d4f07884ccdb1b0e86ef8 of +#: unit.bldc_driver.BLDCDriverUnit.get_pid_value:3 +msgid "" +"This method retrieves the PID values from the specified register and " +"returns them as a tuple." +msgstr "该方法从指定的寄存器中读取 PID 参数,并以元组形式返回。" + +#: 9458a43482904a7d9f958c0a596bf5b0 of +#: unit.bldc_driver.BLDCDriverUnit.get_pid_value:5 +msgid "A tuple containing the PID values (proportional, integral, derivative)." +msgstr "一个包含 PID 参数(比例、积分、微分)的元组。" + +#: 22f85018741e4072a7aed4057772b2ce of +#: unit.bldc_driver.BLDCDriverUnit.get_pid_value:10 +msgid "|get_pid_value.png|" +msgstr "" + +#: ../../en/refs/unit.bldc_driver.ref:21 bc3ac6a43bca42d899c577c2debe2a12 +msgid "get_pid_value.png" +msgstr "" + +#: 7b9a124e61324e7f9c0710e93cf09c87 of +#: unit.bldc_driver.BLDCDriverUnit.get_read_back_freq_float:1 +msgid "Get the read back frequency in float." +msgstr "读取返回的频率,返回值为浮点数。" + +#: 1a7aad5e91c54256bddc905dc66683d4 a1be88e30fc84126a3ed89e0a19c2a7a +#: a682462ee58649e8bb71bec5b1548e41 of +#: unit.bldc_driver.BLDCDriverUnit.get_read_back_freq_float:3 +#: unit.bldc_driver.BLDCDriverUnit.get_read_back_freq_int:3 +#: unit.bldc_driver.BLDCDriverUnit.get_read_back_freq_str:3 +msgid "read back frequency." +msgstr "读取返回的频率" + +#: 9c813aced35c401789594e200afa8caa of +#: unit.bldc_driver.BLDCDriverUnit.get_read_back_freq_float:8 +msgid "|get_read_back_freq_float.png|" +msgstr "" + +#: ../../en/refs/unit.bldc_driver.ref:16 c90ae226fd43479e9f5ebb663e6cd4d4 +msgid "get_read_back_freq_float.png" +msgstr "" + +#: 47d292e193744d0bac3b395f6922f2b8 of +#: unit.bldc_driver.BLDCDriverUnit.get_read_back_freq_int:1 +msgid "Get the read back frequency in int." +msgstr "读取返回的频率,返回值为整数。" + +#: 48bf1664d9544f4dbbc9f856aebb7cf8 of +#: unit.bldc_driver.BLDCDriverUnit.get_read_back_freq_int:8 +msgid "|get_read_back_freq_int.png|" +msgstr "" + +#: ../../en/refs/unit.bldc_driver.ref:17 af756179821e40009d6fbc44bb26269b +msgid "get_read_back_freq_int.png" +msgstr "" + +#: 3f8d3fc3ab244a0bbe81773fa9718234 of +#: unit.bldc_driver.BLDCDriverUnit.get_read_back_freq_str:1 +msgid "Get the read back frequency in str." +msgstr "读取返回的频率,返回值为字符串。" + +#: 5725371535e444908ad1043d069cff1b of +#: unit.bldc_driver.BLDCDriverUnit.get_read_back_freq_str:8 +msgid "|get_read_back_freq_str.png|" +msgstr "" + +#: ../../en/refs/unit.bldc_driver.ref:18 fe0ee42cd47943359cda47d895b3c7bf +msgid "get_read_back_freq_str.png" +msgstr "" + +#: 4d365dacfdad43f7ba89be90d999e50e of +#: unit.bldc_driver.BLDCDriverUnit.get_read_back_rpm_float:1 +msgid "Get the read back rpm in float." +msgstr "读取返回的电机转速(单位:RPM),返回值为浮点数。" + +#: 182e935fa80649f986374be0e663cbc0 1e63886acd9a44b89db1f3da800f37ae +#: 30556b651ba74049ad531f954c0377d2 of +#: unit.bldc_driver.BLDCDriverUnit.get_read_back_rpm_float:3 +#: unit.bldc_driver.BLDCDriverUnit.get_read_back_rpm_int:3 +#: unit.bldc_driver.BLDCDriverUnit.get_read_back_rpm_str:3 +msgid "read back rpm." +msgstr "读取电机转速(单位:RPM)" + +#: 865752f6de2c42049dc5444b4bda92ae of +#: unit.bldc_driver.BLDCDriverUnit.get_read_back_rpm_float:8 +msgid "|get_read_back_rpm_float.png|" +msgstr "" + +#: ../../en/refs/unit.bldc_driver.ref:13 c2cf7bbc297140e4bdf3215bfaf76e67 +msgid "get_read_back_rpm_float.png" +msgstr "" + +#: 19597779e7c7423685a264aa81372598 of +#: unit.bldc_driver.BLDCDriverUnit.get_read_back_rpm_int:1 +msgid "Get the read back rpm in int." +msgstr "读取返回的电机转速(单位:RPM),返回值为整数。" + +#: 19fc2e81880f485b860468621c115490 of +#: unit.bldc_driver.BLDCDriverUnit.get_read_back_rpm_int:8 +msgid "|get_read_back_rpm_int.png|" +msgstr "" + +#: ../../en/refs/unit.bldc_driver.ref:14 3b3fb631ac47407a9355d2cd0a459906 +msgid "get_read_back_rpm_int.png" +msgstr "" + +#: af80016256e24fcb883d9ba394e8c2a9 of +#: unit.bldc_driver.BLDCDriverUnit.get_read_back_rpm_str:1 +msgid "Get the read back rpm in str." +msgstr "读取返回的电机转速(单位:RPM),返回值为字符串。" + +#: ee0f580779394bdebc82de38c5d47c5c of +#: unit.bldc_driver.BLDCDriverUnit.get_read_back_rpm_str:8 +msgid "|get_read_back_rpm_str.png|" +msgstr "" + +#: ../../en/refs/unit.bldc_driver.ref:15 3f6d2f7de7684d8099c1921c85e58b5d +msgid "get_read_back_rpm_str.png" +msgstr "" + +#: c13655345ec546b18c2e26e99e4eb27b of +#: unit.bldc_driver.BLDCDriverUnit.get_rpm_float:1 +msgid "Get the rpm in float." +msgstr "获取转速,返回值为浮点数。" + +#: 76e7c6c9fda84c3e8fc1b06ab43a8d90 of +#: unit.bldc_driver.BLDCDriverUnit.get_rpm_float:3 +msgid "rpm." +msgstr "" + +#: 917c2bbfb1a84a5fbdebf7d3fed7617c of +#: unit.bldc_driver.BLDCDriverUnit.get_rpm_float:8 +msgid "|get_rpm_float.png|" +msgstr "" + +#: ../../en/refs/unit.bldc_driver.ref:19 c82b0e4889554fb8bcbb0f55b234c02b +msgid "get_rpm_float.png" +msgstr "" + +#: 5be203d6e86d4312aa4b81e7c7bf3aa3 of +#: unit.bldc_driver.BLDCDriverUnit.get_rpm_int:1 +msgid "Get the rpm in int." +msgstr "获取转速,返回值为整数。" + +#: b2e2047056fc4c2c831a05b8f215390a c3ccc931678f436a8099277182298a2c +#: e84461b196f941f5a29a318ef0f0ca9c of +#: unit.bldc_driver.BLDCDriverUnit.get_rpm_int:3 +#: unit.bldc_driver.BLDCDriverUnit.set_rpm_float:3 +#: unit.bldc_driver.BLDCDriverUnit.set_rpm_int:3 +msgid "Revolutions per minute." +msgstr "每分钟转数。" + +#: 2c2adbe85cd340028f78066111b9b177 of +#: unit.bldc_driver.BLDCDriverUnit.get_rpm_int:8 +msgid "|get_rpm_int.png|" +msgstr "" + +#: ../../en/refs/unit.bldc_driver.ref:20 e98afbc5948d47e8b4423bb8ca7cd444 +msgid "get_rpm_int.png" +msgstr "" + +#: c6bf5150fbcb4e04b30ce2a4a26ac796 of +#: unit.bldc_driver.BLDCDriverUnit.save_data_in_flash:1 +msgid "Save motor data to flash." +msgstr "保存电机参数到 flash。" + +#: ecbb29f96848496587dcf8dc4e2d8ba7 of +#: unit.bldc_driver.BLDCDriverUnit.save_data_in_flash:5 +msgid "|save_data_in_flash.png|" +msgstr "" + +#: ../../en/refs/unit.bldc_driver.ref:31 2d209850e54a4a8eb40acf8f351a4140 +msgid "save_data_in_flash.png" +msgstr "" + +#: fd8af8c154284644b957738690d9ff8a of +#: unit.bldc_driver.BLDCDriverUnit.set_direction:1 +msgid "Set the direction." +msgstr "设置方向。" + +#: 0c29d608e8e94b26ad4ca2b73dff314e of +#: unit.bldc_driver.BLDCDriverUnit.set_direction:3 +msgid "0 forward, 1 backward." +msgstr "0 正传,1 反转。" + +#: 9d9d677c90534386a2c5fb1c853d6529 of +#: unit.bldc_driver.BLDCDriverUnit.set_direction:7 +msgid "|set_direction.png|" +msgstr "" + +#: ../../en/refs/unit.bldc_driver.ref:24 aa7435e109334041b602cccda5193fd8 +msgid "set_direction.png" +msgstr "" + +#: 631efce0cc604329af114094555bcd56 of +#: unit.bldc_driver.BLDCDriverUnit.set_i2c_address:1 +msgid "Set the I2C address." +msgstr "设置 I2C 地址。" + +#: 58253391c6fc48799e9e75eef2b3f24b of +#: unit.bldc_driver.BLDCDriverUnit.set_i2c_address:3 +msgid "The new I2C address, range: 1~127." +msgstr "新 I2C 地址,范围:1~127。" + +#: c271bd402a5e419ca8776a635ef8938b of +#: unit.bldc_driver.BLDCDriverUnit.set_i2c_address:7 +msgid "|set_i2c_address.png|" +msgstr "" + +#: ../../en/refs/unit.bldc_driver.ref:32 3479b41e4d0041998f360e63cff60add +msgid "set_i2c_address.png" +msgstr "" + +#: d30f1e5af02346c88798c7d59c5b43c3 of +#: unit.bldc_driver.BLDCDriverUnit.set_mode:1 +msgid "Set the mode setting." +msgstr "设置模式。" + +#: ef962b8670d04e0ba3e8ecfe80f89998 of unit.bldc_driver.BLDCDriverUnit.set_mode +msgid "param" +msgstr "" + +#: 6adcf46a7bff4ea9bffc9cb4bbf87ab0 of +#: unit.bldc_driver.BLDCDriverUnit.set_mode:3 +msgid "int mode: 0 mean open loop, 1 mean close loop." +msgstr "0 为开环,1 为闭环。" + +#: c3389ec6e32e48db9b3a41c09a4984ff of +#: unit.bldc_driver.BLDCDriverUnit.set_mode:7 +msgid "|set_mode.png|" +msgstr "" + +#: ../../en/refs/unit.bldc_driver.ref:23 92d09ced722a4950b429292a16f80346 +msgid "set_mode.png" +msgstr "" + +#: 5574586dbc5e432b985a6073bdbd35d8 of +#: unit.bldc_driver.BLDCDriverUnit.set_motor_model:1 +msgid "Set the motor model setting." +msgstr "设置电机模型。" + +#: 7036a6e4101c4dae82ff73be4f9df638 of +#: unit.bldc_driver.BLDCDriverUnit.set_motor_model:3 +msgid "0 mean low speed, 1 mean high speed." +msgstr "0 低速,1 高速。" + +#: aa93fbc45aed4e9fa0436d3986f200d0 of +#: unit.bldc_driver.BLDCDriverUnit.set_motor_model:7 +msgid "|set_motor_model.png|" +msgstr "" + +#: ../../en/refs/unit.bldc_driver.ref:25 77275e8d842642ae91dcbbad63e2ac3f +msgid "set_motor_model.png" +msgstr "" + +#: 4a301487e17c43f4a5f223d8f5f63f8d of +#: unit.bldc_driver.BLDCDriverUnit.set_open_loop_pwm:1 +msgid "Set the open loop pwm." +msgstr "设置开环 PWM。" + +#: da2a0733319148ae8fcd4a9e5fd8323f of +#: unit.bldc_driver.BLDCDriverUnit.set_open_loop_pwm:3 +msgid "open loop pwm., range: 0~2047." +msgstr "开环 PWM,范围:0~2047。" + +#: 6a1f4f8d40234347ae5ce9059ec49324 of +#: unit.bldc_driver.BLDCDriverUnit.set_open_loop_pwm:7 +msgid "|set_open_loop_pwm.png|" +msgstr "" + +#: ../../en/refs/unit.bldc_driver.ref:27 26c4affea4094553af31680acc598368 +msgid "set_open_loop_pwm.png" +msgstr "" + +#: a9d5c7fff4d04a8aa785ebb13ad8cadd of +#: unit.bldc_driver.BLDCDriverUnit.set_pid_value:1 +msgid "! Set the PID values (Proportional, Integral, Derivative)." +msgstr "设置 PID 参数 (比例,积分,微分)。" + +#: 393be65d54c14dee80e82cd537cb10c6 of +#: unit.bldc_driver.BLDCDriverUnit.set_pid_value:3 +msgid "" +"This method sets the PID values to the specified register, which will " +"control the motor's PID behavior." +msgstr "该方法将 PID 参数设置到指定寄存器,用于控制电机的 PID 行为。" + +#: 99fc6d0387404462993df40a4f013768 of +#: unit.bldc_driver.BLDCDriverUnit.set_pid_value:5 +msgid "The proportional value." +msgstr "比例" + +#: 64c1ed7aba3949dda6ed696ab48e71d3 of +#: unit.bldc_driver.BLDCDriverUnit.set_pid_value:6 +msgid "The integral value." +msgstr "积分" + +#: b91a78257ae347719f59b8476553a495 of +#: unit.bldc_driver.BLDCDriverUnit.set_pid_value:7 +msgid "The derivative value." +msgstr "微分" + +#: 1390022834c6496aac8bf88129607137 of +#: unit.bldc_driver.BLDCDriverUnit.set_pid_value:11 +msgid "|set_pid_value.png|" +msgstr "" + +#: ../../en/refs/unit.bldc_driver.ref:30 dac367d427ea42a9a03cfe800038650f +msgid "set_pid_value.png" +msgstr "" + +#: 700924d9a691422f87225ccd5043cb1a of +#: unit.bldc_driver.BLDCDriverUnit.set_pole_pairs:1 +msgid "Set pole pairs." +msgstr "设置磁极对。" + +#: 407c57df814a4146887c82286b923e94 of +#: unit.bldc_driver.BLDCDriverUnit.set_pole_pairs:3 +msgid "pole pairs, range: 0~255." +msgstr "磁极对,范围:0~255。" + +#: 9134493755824d4bb05f878f7fe6c335 of +#: unit.bldc_driver.BLDCDriverUnit.set_pole_pairs:7 +msgid "|set_pole_pairs.png|" +msgstr "" + +#: ../../en/refs/unit.bldc_driver.ref:26 f1e4f542c003481885f8521b786d13d6 +msgid "set_pole_pairs.png" +msgstr "" + +#: 28a461acdd7f4fd7bd6ef3480038a305 of +#: unit.bldc_driver.BLDCDriverUnit.set_rpm_float:1 +msgid "Set the rpm in float." +msgstr "设置转速。" + +#: 7661b6e9f3ab4e51ab1efb48a24561c1 of +#: unit.bldc_driver.BLDCDriverUnit.set_rpm_float:7 +msgid "|set_rpm_float.png|" +msgstr "" + +#: ../../en/refs/unit.bldc_driver.ref:28 e5d4ddcbe0ad453abeb977896ec1d6ea +msgid "set_rpm_float.png" +msgstr "" + +#: fd72dbbef75b41b695453d70df0fb1e9 of +#: unit.bldc_driver.BLDCDriverUnit.set_rpm_int:1 +msgid "Set the rpm in int." +msgstr "设置转速。" + +#: 277e554672874df38c8aba30a1a35183 of +#: unit.bldc_driver.BLDCDriverUnit.set_rpm_int:7 +msgid "|set_rpm_int.png|" +msgstr "" + +#: ../../en/refs/unit.bldc_driver.ref:29 e9e02d78242f4340b70ab84c511e2609 +msgid "set_rpm_int.png" +msgstr "" + +#~ msgid "Receive data" +#~ msgstr "" + +#~ msgid "Open the |basic_rf433r_recv_example.m5f2| project in UiFlow2." +#~ msgstr "" + +#~ msgid "|basic_rf433r_recv_example.png|" +#~ msgstr "" + +#~ msgid "BLDC Driver Unit" +#~ msgstr "" + +#~ msgid "This library is the driver for Unit BLDC Driver." +#~ msgstr "" + +#~ msgid "|Unit BLDC Driver|" +#~ msgstr "" + +#~ msgid "Unit BLDC Driver" +#~ msgstr "" + diff --git a/examples/unit/bldc_driver/cores3_bldc_driver_example.m5f2 b/examples/unit/bldc_driver/cores3_bldc_driver_example.m5f2 new file mode 100644 index 00000000..38d3dff8 --- /dev/null +++ b/examples/unit/bldc_driver/cores3_bldc_driver_example.m5f2 @@ -0,0 +1 @@ +{"version":"V2.0","versionNumber":"V2.2.5","type":"cores3","components":[{"name":"screen","type":"screen","layer":0,"screenId":"builtin","screenName":"","id":"__cores3_screen","createTime":1744720280169,"x":0,"y":0,"width":320,"height":240,"backgroundColor":"#222222","size":0,"isSelected":true},{"name":"title0","type":"title","layer":1,"screenId":"builtin","screenName":"","id":"tgax7ibfliYV$g`t","createTime":1744721750164,"x":0,"y":0,"color":"#ffffff","backgroundColor":"#0000FF","text":"BLDCDriver Example","textOffset":3,"font":"Widgets.FONTS.DejaVu24","isSelected":false},{"name":"label0","type":"label","layer":2,"screenId":"builtin","screenName":"","id":"uLSBXifLyYRSnHSS","createTime":1744721803506,"x":35,"y":60,"color":"#ffffff","backgroundColor":"#222222","text":"Speed: ","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false,"width":72,"height":20},{"name":"label_speed","type":"label","layer":5,"screenId":"builtin","screenName":"","id":"z*1SpNB_eWC5jViF","createTime":1744721917464,"x":115,"y":60,"color":"#ffffff","backgroundColor":"#222222","text":"0","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false,"width":11,"height":20}],"resources":[{"hardware":["hardware_button","hardware_pin_button","imu","speaker","touch","als","mic","i2c"]},{"unit":["unit_bldcdriver"]}],"units":[{"type":"unit_bldcdriver","name":"bldcdriver_0","portList":["A","PAHUB","Custom"],"portType":"A","userPort":[22,21],"id":"f8C7LG#&ho03BOE*","createTime":1744721551380,"bus":"i2c0","pahubPortList":[0,1,2,3,4,5],"pahubPort":0,"initBlockId":"^%9L^kzoDo:Rn=S+RN^]"}],"hats":[],"bases":[],"i2cs":[{"id":"i2c0","portType":"A","userPort":[22,21],"freq":"100000","blockId":"j:)HZI=DE:s*Qy51Q2WS"}],"blockly":"speedtrue010000012bldcdriver_00x65bldcdriver_00bldcdriver_0500bldcdriver_00speed0trueLTspeed300speedADD1speed5label_speedLabelspeedbldcdriver_0500speed100bldcdriver_01label_speed0500","screen":[{"simulationName":"Built-in","type":"builtin","width":320,"height":240,"scale":0.78,"screenName":"","blockId":"","screenColorType":0,"id":"builtin","createTime":1744720280168}],"logicWhenNum":0,"customList":[]} \ No newline at end of file diff --git a/examples/unit/bldc_driver/cores3_bldc_driver_example.py b/examples/unit/bldc_driver/cores3_bldc_driver_example.py new file mode 100644 index 00000000..ba9e0bc8 --- /dev/null +++ b/examples/unit/bldc_driver/cores3_bldc_driver_example.py @@ -0,0 +1,62 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import os, sys, io +import M5 +from M5 import * +from hardware import I2C +from hardware import Pin +from unit import BLDCDriverUnit +import time + + +title0 = None +label0 = None +label_speed = None +i2c0 = None +bldcdriver_0 = None +speed = None + + +def setup(): + global title0, label0, label_speed, i2c0, bldcdriver_0, speed + M5.begin() + Widgets.fillScreen(0x222222) + title0 = Widgets.Title("BLDCDriver Example", 3, 0xFFFFFF, 0x0000FF, Widgets.FONTS.DejaVu24) + label0 = Widgets.Label("Speed: ", 35, 60, 1.0, 0xFFFFFF, 0x222222, Widgets.FONTS.DejaVu18) + label_speed = Widgets.Label("0", 115, 60, 1.0, 0xFFFFFF, 0x222222, Widgets.FONTS.DejaVu18) + i2c0 = I2C(0, scl=Pin(1), sda=Pin(2), freq=100000) + bldcdriver_0 = BLDCDriverUnit(i2c0, 0x65) + bldcdriver_0.set_mode(0) + bldcdriver_0.set_open_loop_pwm(500) + bldcdriver_0.set_rpm_int(0) + speed = 0 + + +def loop(): + global title0, label0, label_speed, i2c0, bldcdriver_0, speed + M5.update() + if speed < 300: + speed = speed + 5 + label_speed.setText(str(speed)) + bldcdriver_0.set_rpm_int(speed) + time.sleep_ms(100) + else: + bldcdriver_0.set_mode(1) + label_speed.setText(str("0")) + time.sleep_ms(500) + + +if __name__ == "__main__": + try: + setup() + while True: + loop() + except (Exception, KeyboardInterrupt) as e: + try: + from utility import print_error_msg + + print_error_msg(e) + except ImportError: + print("please update to latest firmware") diff --git a/m5stack/libs/unit/bldc_driver.py b/m5stack/libs/unit/bldc_driver.py index 08f5750a..376d6aef 100644 --- a/m5stack/libs/unit/bldc_driver.py +++ b/m5stack/libs/unit/bldc_driver.py @@ -1,12 +1,16 @@ -# SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD # # SPDX-License-Identifier: MIT -from machine import I2C +import machine from .pahub import PAHUBUnit from .unit_helper import UnitError import time import struct +import sys + +if sys.platform != "esp32": + from typing import Union BLDC_I2C_ADDR = 0x65 @@ -31,9 +35,29 @@ class BLDCDriverUnit: + """Create an BLDCDriverUnit object. + + :param i2c: I2C port. + :type i2c: machine.I2C | PAHUBUnit + :param address: BLDCDriverUnit Slave Address. + :type address: int | list | tuple + + UiFlow2 Code Block: + + |init.png| + + MicroPython Code Block: + + .. code-block:: python + + from unit import BLDCDriverUnit + + unit_bldcdriver_0 = BLDCDriverUnit(i2c0, 0x65) + """ + def __init__( self, - i2c: I2C | PAHUBUnit, + i2c: Union[machine.I2C, PAHUBUnit], address: int | list | tuple = BLDC_I2C_ADDR, ) -> None: """Initialize the bldc unit.""" @@ -49,147 +73,494 @@ def _available(self): @property def get_current_mode(self) -> int: - """! Get the current mode setting.""" + """Get the current mode setting. + + :returns: current mode. + :rtype: int + + UiFlow2 Code Block: + + |get_current_mode.png| + + MicroPython Code Block: + + .. code-block:: python + + unit_bldcdriver_0.get_current_mode() + """ return self.i2c.readfrom_mem(self.unit_addr, MODE_REG, 1)[0] - def set_mode(self, mode) -> None: - """! Set the mode setting. - mode: 0~1(open loop or close loop) + def set_mode(self, mode: int) -> None: + """Set the mode setting. + + :param: int mode: 0 mean open loop, 1 mean close loop. + + UiFlow2 Code Block: + + |set_mode.png| + + MicroPython Code Block: + + .. code-block:: python + + unit_bldcdriver_0.set_mode(mode) """ self.i2c.writeto_mem(self.unit_addr, MODE_REG, bytes([mode])) @property def get_motor_current_direction(self) -> int: - """! Get the current direction setting.""" + """Get the current direction setting. + + :returns: current direction. + :rtype: int + + UiFlow2 Code Block: + + |get_motor_current_direction.png| + + MicroPython Code Block: + + .. code-block:: python + + unit_bldcdriver_0.get_motor_current_direction() + """ return self.i2c.readfrom_mem(self.unit_addr, DIRECTION_REG, 1)[0] def set_direction(self, direction) -> None: - """! Set the direction setting. - direction: 0~1(forward~backward) + """Set the direction. + + :param int model: 0 forward, 1 backward. + + UiFlow2 Code Block: + + |set_direction.png| + + MicroPython Code Block: + + .. code-block:: python + + unit_bldcdriver_0.set_direction() """ self.i2c.writeto_mem(self.unit_addr, DIRECTION_REG, bytes([direction])) @property def get_motor_current_model(self) -> int: - """! Get the motor model setting.""" + """Get the motor current model setting. + + :returns: motor current model. + :rtype: int + + UiFlow2 Code Block: + + |get_motor_current_model.png| + + MicroPython Code Block: + + .. code-block:: python + + unit_bldcdriver_0.get_motor_current_model() + """ return self.i2c.readfrom_mem(self.unit_addr, MOTOR_MODEL_REG, 1)[0] - def set_motor_model(self, model) -> int: - """! set the motor model setting. - model: 0~1(low speed~high speed) + def set_motor_model(self, model: int) -> int: + """Set the motor model setting. + + :param int model: 0 mean low speed, 1 mean high speed. + + UiFlow2 Code Block: + + |set_motor_model.png| + + MicroPython Code Block: + + .. code-block:: python + + unit_bldcdriver_0.set_motor_model(model) """ self.i2c.writeto_mem(self.unit_addr, MOTOR_MODEL_REG, bytes([model])) @property def get_motor_pole_pairs(self) -> int: - """! Get the pole pairs setting.""" + """Get the pole pairs setting. + + :returns: motor pole pairs. + :rtype: int + + UiFlow2 Code Block: + + |get_motor_pole_pairs.png| + + MicroPython Code Block: + + .. code-block:: python + + unit_bldcdriver_0.get_motor_pole_pairs() + """ return self.i2c.readfrom_mem(self.unit_addr, POLE_PAIRS_REG, 1)[0] - def set_pole_pairs(self, pole) -> None: - """! set the pole pairs setting. - pole: 0~255 + def set_pole_pairs(self, pole: int) -> None: + """Set pole pairs. + + :param int pole: pole pairs, range: 0~255. + + UiFlow2 Code Block: + + |set_pole_pairs.png| + + MicroPython Code Block: + + .. code-block:: python + + unit_bldcdriver_0.set_pole_pairs(pole) """ self.i2c.writeto_mem(self.unit_addr, POLE_PAIRS_REG, bytes([pole])) @property def get_motor_status(self) -> int: - """! Set the motor status.""" + """Get motor status. + + :returns: motor status. + :rtype: int + + UiFlow2 Code Block: + + |get_motor_status.png| + + MicroPython Code Block: + + .. code-block:: python + + unit_bldcdriver_0.get_motor_status() + """ return self.i2c.readfrom_mem(self.unit_addr, MOTOR_STATUS_REG, 1)[0] @property def get_open_loop_pwm(self) -> int: - """! Get the open loop pwm.""" + """Get the open loop pwm. + + :returns: open loop pwm. + :rtype: int + + UiFlow2 Code Block: + + |get_open_loop_pwm.png| + + MicroPython Code Block: + + .. code-block:: python + + unit_bldcdriver_0.get_open_loop_pwm() + """ buf = self.i2c.readfrom_mem(self.unit_addr, PWM_REG, 2) return struct.unpack(" int: - """! set the open loop pwm. - pwm: 0~2047 + def set_open_loop_pwm(self, pwm: int) -> int: + """Set the open loop pwm. + + :param int pwm: open loop pwm., range: 0~2047. + + UiFlow2 Code Block: + + |set_open_loop_pwm.png| + + MicroPython Code Block: + + .. code-block:: python + + unit_bldcdriver_0.set_open_loop_pwm(pwm) """ self.i2c.writeto_mem(self.unit_addr, PWM_REG, struct.pack(" float: - """! Get the read back rpm in float.""" + """Get the read back rpm in float. + + :returns: read back rpm. + :rtype: float + + UiFlow2 Code Block: + + |get_read_back_rpm_float.png| + + MicroPython Code Block: + + .. code-block:: python + + unit_bldcdriver_0.get_read_back_rpm_float() + """ buf = self.i2c.readfrom_mem(self.unit_addr, RB_RPM_FLOAT_REG, 4) return struct.unpack(" int: - """! Get the read back rpm in int.""" + """Get the read back rpm in int. + + :returns: read back rpm. + :rtype: int + + UiFlow2 Code Block: + + |get_read_back_rpm_int.png| + + MicroPython Code Block: + + .. code-block:: python + + unit_bldcdriver_0.get_read_back_rpm_int() + """ data = self.i2c.readfrom_mem(self.unit_addr, RB_RPM_INT_REG, 4) return struct.unpack(" str: - """! Get the read back rpm in str.""" + """Get the read back rpm in str. + + :returns: read back rpm. + :rtype: str + + UiFlow2 Code Block: + + |get_read_back_rpm_str.png| + + MicroPython Code Block: + + .. code-block:: python + + unit_bldcdriver_0.get_read_back_rpm_str() + """ data = self.i2c.readfrom_mem(self.unit_addr, RB_RPM_STR_REG, 16) return data.replace(b"\x00", b"").decode("utf8") @property def get_read_back_freq_float(self) -> float: - """! Get the read back frequency in float.""" + """Get the read back frequency in float. + + :returns: read back frequency. + :rtype: float + + UiFlow2 Code Block: + + |get_read_back_freq_float.png| + + MicroPython Code Block: + + .. code-block:: python + + unit_bldcdriver_0.get_read_back_freq_float() + """ buf = self.i2c.readfrom_mem(self.unit_addr, RB_FREQ_FLOAT_REG, 4) return struct.unpack(" int: - """! Get the read back frequency in int.""" + """Get the read back frequency in int. + + :returns: read back frequency. + :rtype: int + + UiFlow2 Code Block: + + |get_read_back_freq_int.png| + + MicroPython Code Block: + + .. code-block:: python + + unit_bldcdriver_0.get_read_back_freq_int() + """ data = self.i2c.readfrom_mem(self.unit_addr, RB_FREQ_INT_REG, 4) return struct.unpack(" str: - """! Get the read back frequency in str.""" + """Get the read back frequency in str. + + :returns: read back frequency. + :rtype: str + + UiFlow2 Code Block: + + |get_read_back_freq_str.png| + + MicroPython Code Block: + + .. code-block:: python + + unit_bldcdriver_0.get_read_back_freq_str() + """ data = self.i2c.readfrom_mem(self.unit_addr, RB_FREQ_STR_REG, 16) return data.replace(b"\x00", b"").decode("utf8") @property def get_rpm_float(self) -> float: - """! Get the rpm in float.""" + """Get the rpm in float. + + :returns: rpm. + :rtype: float + + UiFlow2 Code Block: + + |get_rpm_float.png| + + MicroPython Code Block: + + .. code-block:: python + + unit_bldcdriver_0.get_rpm_float() + """ buf = self.i2c.readfrom_mem(self.unit_addr, SET_RPM_FLOAT_REG, 4) return struct.unpack(" None: - """! Set the rpm in float.""" + def set_rpm_float(self, rpm: float) -> None: + """Set the rpm in float. + + :param float rpm: Revolutions per minute. + + UiFlow2 Code Block: + + |set_rpm_float.png| + + MicroPython Code Block: + + .. code-block:: python + + unit_bldcdriver_0.set_rpm_float(rpm) + """ self.i2c.writeto_mem(self.unit_addr, SET_RPM_FLOAT_REG, struct.pack(" int: - """! Get the rpm in int.""" + """Get the rpm in int. + + :returns: Revolutions per minute. + :rtype: int + + UiFlow2 Code Block: + + |get_rpm_int.png| + + MicroPython Code Block: + + .. code-block:: python + + unit_bldcdriver_0.get_rpm_int() + """ data = self.i2c.readfrom_mem(self.unit_addr, SET_RPM_INT_REG, 4) return struct.unpack(" None: - """! Set the rpm in int.""" + """Set the rpm in int. + + :param int rpm: Revolutions per minute. + + UiFlow2 Code Block: + + |set_rpm_int.png| + + MicroPython Code Block: + + .. code-block:: python + + unit_bldcdriver_0.set_rpm_int(rpm) + """ self.i2c.writeto_mem(self.unit_addr, SET_RPM_INT_REG, struct.pack(" tuple: - """! Get the pid value.""" + """Get the PID value. + + This method retrieves the PID values from the specified register and returns them as a tuple. + + :returns: A tuple containing the PID values (proportional, integral, derivative). + :rtype: tuple[int, int, int] + + UiFlow2 Code Block: + + |get_pid_value.png| + + MicroPython Code Block: + + .. code-block:: python + + unit_bldcdriver_0.get_pid_value() + """ data = self.i2c.readfrom_mem(self.unit_addr, PID_REG, 12) return struct.unpack(" None: - """! set the pid value.""" + """! Set the PID values (Proportional, Integral, Derivative). + + This method sets the PID values to the specified register, which will control the motor's PID behavior. + + :param int p: The proportional value. + :param int i: The integral value. + :param int d: The derivative value. + + UiFlow2 Code Block: + + |set_pid_value.png| + + MicroPython Code Block: + + .. code-block:: python + + unit_bldcdriver_0.set_pid_value(p, i, d) + """ self.i2c.writeto_mem(self.unit_addr, PID_REG, struct.pack(" None: + """Save motor data to flash. + + UiFlow2 Code Block: + + |save_data_in_flash.png| + + MicroPython Code Block: + + .. code-block:: python + + unit_bldcdriver_0.save_data_in_flash() + """ time.sleep_ms(10) self.i2c.writeto_mem(self.unit_addr, FLASH_WR_BK_REG, b"\x01") time.sleep_ms(200) - def get_device_spec(self, mode): - """! Get device firmware version and i2c address. - mode: 0xFE and 0xFF + def get_device_spec(self, mode: int) -> None: + """Get device firmware version and I2C address. + + This method retrieves either the firmware version or the I2C address of the device based on the provided mode. + + :param int mode: The mode to determine what information to fetch. + - `0xFE`: Retrieve firmware version. + - `0xFF`: Retrieve I2C address. + + UiFlow2 Code Block: + + |get_device_spec.png| + + MicroPython Code Block: + + .. code-block:: python + + unit_device.get_device_spec(mode) """ if mode >= FW_VER_REG and mode <= I2C_ADDR_REG: return self.i2c.readfrom_mem(self.unit_addr, mode, 1)[0] - def set_i2c_address(self, addr): - """! Set i2c address. - addr: 1 to 127 + def set_i2c_address(self, addr: int) -> None: + """Set the I2C address. + + :param int addr: The new I2C address, range: 1~127. + + UiFlow2 Code Block: + + |set_i2c_address.png| + + MicroPython Code Block: + + .. code-block:: python + + unit_device.set_i2c_address(addr) """ if addr >= 1 and addr <= 127: if addr != self.unit_addr: From 8796d9fad3b08f7a439b5843a7e097c6da8b313b Mon Sep 17 00:00:00 2001 From: luoweiyuan Date: Fri, 18 Apr 2025 10:27:07 +0800 Subject: [PATCH 056/322] docs: Add Unit Thermal example. Signed-off-by: luoweiyuan --- docs/en/refs/unit.thermal.ref | 11 + docs/en/units/thermal.rst | 60 ++++- .../zh_CN/LC_MESSAGES/units/thermal.po | 237 +++++++++--------- .../unit/thermal/cores3_thermal_imaging.m5f2 | 1 + .../unit/thermal/cores3_thermal_imaging.py | 123 +++++++++ m5stack/libs/driver/mlx90640.py | 24 +- 6 files changed, 311 insertions(+), 145 deletions(-) create mode 100644 examples/unit/thermal/cores3_thermal_imaging.m5f2 create mode 100644 examples/unit/thermal/cores3_thermal_imaging.py diff --git a/docs/en/refs/unit.thermal.ref b/docs/en/refs/unit.thermal.ref index 59efe605..235afe65 100644 --- a/docs/en/refs/unit.thermal.ref +++ b/docs/en/refs/unit.thermal.ref @@ -21,3 +21,14 @@ .. |set_refresh_rate.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/thermal/set_refresh_rate.png .. |update_temperature_buffer.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/thermal/update_temperature_buffer.png + +.. |cores3_thermal_imaging.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/thermal/example.png + +.. |cores3_thermal_imaging.m5f2| raw:: html + + + cores3_thermal_imaging.m5f2 + diff --git a/docs/en/units/thermal.rst b/docs/en/units/thermal.rst index 839c5e07..d25251f9 100644 --- a/docs/en/units/thermal.rst +++ b/docs/en/units/thermal.rst @@ -8,11 +8,49 @@ Support the following products: |ThermalUnit| +UiFlow2 Example +--------------- + +Thermal Imaging +^^^^^^^^^^^^^^^ + +Open the |cores3_thermal_imaging.m5f2| project in UiFlow2. + +This demo uses the M5Stack UnitThermal module to implement a basic thermal imaging function. + +UiFlow2 Code Block: + + |cores3_thermal_imaging.png| + +Example output: + + None + + +MicroPython Example +------------------- + +Thermal Imaging +^^^^^^^^^^^^^^^ + +This demo uses the M5Stack UnitThermal module to implement a basic thermal imaging function. + +MicroPython Code Block: + + .. literalinclude:: ../../../examples/unit/thermal/cores3_thermal_imaging.py + :language: python + :linenos: + +Example output: + + None + + class ThermalUnit ------------------ +------------------ Constructors ------------- +------------- .. class:: ThermalUnit(i2c, address: int = 0x33) @@ -21,7 +59,7 @@ Constructors :param i2c: the I2C object. :param address: the I2C address of the device. Default is 0x33. - UIFLOW2: + UiFlow2: |init.png| @@ -37,7 +75,7 @@ Methods get the max temperature. - UIFLOW2: + UiFlow2: |get_max_temperature.png| @@ -48,7 +86,7 @@ Methods get the min temperature. - UIFLOW2: + UiFlow2: |get_min_temperature.png| @@ -59,7 +97,7 @@ Methods get the midpoint temperature. - UIFLOW2: + UiFlow2: |get_midpoint_temperature.png| @@ -73,7 +111,7 @@ Methods :return: The temperature of the pixel. - UIFLOW2: + UiFlow2: |get_pixel_temperature.png| @@ -84,7 +122,7 @@ Methods get the refresh rate. - UIFLOW2: + UiFlow2: |get_refresh_rate.png| @@ -95,7 +133,7 @@ Methods :return: The temperature buffer. - UIFLOW2: + UiFlow2: |get_temperature_buffer.png| @@ -106,7 +144,7 @@ Methods :param int rate: The refresh rate in Hz. - UIFLOW2: + UiFlow2: |set_refresh_rate.png| @@ -117,6 +155,6 @@ Methods :return: The temperature buffer. - UIFLOW2: + UiFlow2: |update_temperature_buffer.png| diff --git a/docs/locales/zh_CN/LC_MESSAGES/units/thermal.po b/docs/locales/zh_CN/LC_MESSAGES/units/thermal.po index 6b7a00f3..8cf64026 100644 --- a/docs/locales/zh_CN/LC_MESSAGES/units/thermal.po +++ b/docs/locales/zh_CN/LC_MESSAGES/units/thermal.po @@ -9,7 +9,7 @@ msgid "" msgstr "" "Project-Id-Version: UIFlow2 Programming Guide \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-04-18 15:58+0800\n" +"POT-Creation-Date: 2025-04-18 17:28+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language: zh_CN\n" @@ -26,7 +26,7 @@ msgstr "" #: ../../en/units/thermal.rst:6 5d4e5b9379bd496c9d248d0a6e13f88f msgid "Support the following products:" -msgstr "" +msgstr "支持以下产品:" #: ../../en/units/thermal.rst:8 a0cf7af44a714ea3b7e7e90e22dc6318 msgid "|ThermalUnit|" @@ -36,44 +36,90 @@ msgstr "" msgid "ThermalUnit" msgstr "" -#: ../../en/units/thermal.rst:12 9d2ed7ec80424eaf9aa29cfffa9568f0 +#: ../../en/units/thermal.rst:12 41da890e539a4cbba94ed3195b161d5f +msgid "UiFlow2 Example" +msgstr "UiFlow2 应用示例" + +#: ../../en/units/thermal.rst:15 ../../en/units/thermal.rst:34 +#: a0cf7af44a714ea3b7e7e90e22dc6318 +msgid "Thermal Imaging" +msgstr "热成像" + +#: ../../en/units/thermal.rst:17 3e627941bc364e87bd084f40c0bb6208 +msgid "Open the |cores3_thermal_imaging.m5f2| project in UiFlow2." +msgstr "在 UiFlow2 上打开 |cores3_thermal_imaging.m5f2| 项目。" + +#: ../../en/units/thermal.rst:19 ../../en/units/thermal.rst:36 +#: 4f44ba3ca7b94acbad0cccbd06b11e1d 703d4ebef9c247eb8dc39e96e6091676 +msgid "" +"This demo uses the M5Stack UnitThermal module to implement a basic " +"thermal imaging function." +msgstr "案例使用 M5Stack 的 UnitThermal 热成像模块,实现了一个基础的热成像功能。" + +#: ../../en/units/thermal.rst:21 48c9b35685664a1b92a5e03e31f7b3ad +msgid "UiFlow2 Code Block:" +msgstr "UiFlow2 代码块:" + +#: ../../en/units/thermal.rst:23 fdabf6cb980543c8832d406f67b256eb +msgid "|cores3_thermal_imaging.png|" +msgstr "" + +#: ../../en/refs/unit.thermal.ref:25 d9f19383c02f4618a4fd92275c0bf4ca +msgid "cores3_thermal_imaging.png" +msgstr "" + +#: ../../en/units/thermal.rst:25 ../../en/units/thermal.rst:44 +#: cd877ab3f79d432fba27195586e22f74 e14ff46f2f824318a9cec6accfd48e21 +msgid "Example output:" +msgstr "示例输出:" + +#: ../../en/units/thermal.rst:27 ../../en/units/thermal.rst:46 +#: bcb0c67786cd4d71ae0aadc04f0ffd82 cfd3d7117e714caea1dffbfcc422bd74 +msgid "None" +msgstr "无" + +#: ../../en/units/thermal.rst:31 a5e2842060fc46babc476a3cd1192b77 +msgid "MicroPython Example" +msgstr "MicroPython 应用示例" + +#: ../../en/units/thermal.rst:38 6ba32736e8da4f8e9598fb40b121f972 +msgid "MicroPython Code Block:" +msgstr "MicroPython 代码块:" + +#: ../../en/units/thermal.rst:50 9d2ed7ec80424eaf9aa29cfffa9568f0 msgid "class ThermalUnit" msgstr "" -#: ../../en/units/thermal.rst:15 38d16677c6ca4d879630ff1857ca3fb0 +#: ../../en/units/thermal.rst:53 38d16677c6ca4d879630ff1857ca3fb0 msgid "Constructors" msgstr "" -#: ../../en/units/thermal.rst:19 aaa3ad4cad2c43a9a1d4ccebff4a364a +#: ../../en/units/thermal.rst:57 aaa3ad4cad2c43a9a1d4ccebff4a364a msgid "Create a ThermalUnit object." -msgstr "" +msgstr "创建一个 ThermalUnit 对象。" #: ../../en/units/thermal.rst 398b68e2642e4710b70a01b79d15ffaf -#: 862fa595c3224c4d8dc29f33c0aaca05 ca8064cf5fbd4e86981033a13e9418a2 msgid "Parameters" msgstr "" -#: ../../en/units/thermal.rst:21 7dfac2416a5c421e84b57388319b64be +#: ../../en/units/thermal.rst:59 7dfac2416a5c421e84b57388319b64be msgid "the I2C object." -msgstr "" +msgstr "I2C 对象" -#: ../../en/units/thermal.rst:22 6b5b423451bb4c6a86e913e28a218934 +#: ../../en/units/thermal.rst:60 6b5b423451bb4c6a86e913e28a218934 msgid "the I2C address of the device. Default is 0x33." -msgstr "" +msgstr "I2C 地址,默认为 0x33。" -#: ../../en/units/thermal.rst:24 ../../en/units/thermal.rst:40 -#: ../../en/units/thermal.rst:51 ../../en/units/thermal.rst:62 -#: ../../en/units/thermal.rst:76 ../../en/units/thermal.rst:87 -#: ../../en/units/thermal.rst:98 ../../en/units/thermal.rst:109 -#: ../../en/units/thermal.rst:120 01352358cf014d5a9e271dde9aab553f -#: 0a248af7341240b1affc7e4f1c899bae 0c23ca2b76e540a9839052a5e54c3264 -#: 5e5e0d5230f24f2294ced7ad7ea6f126 a1fe0b7c2d7a481584c8073be8a82780 -#: a5415b8a2e7f43cc90410114b23b185a e474c3e63978440f95dabdb6b69c26f7 -#: ec7519505bb148519f21b0973c6c461d ffeb8b3c7d7644a588a26bab5cb685b6 -msgid "UIFLOW2:" -msgstr "" +#: ../../en/units/thermal.rst:62 ../../en/units/thermal.rst:78 +#: ../../en/units/thermal.rst:89 ../../en/units/thermal.rst:100 +#: ../../en/units/thermal.rst:114 ../../en/units/thermal.rst:125 +#: ../../en/units/thermal.rst:136 ../../en/units/thermal.rst:147 +#: ../../en/units/thermal.rst:158 a1fe0b7c2d7a481584c8073be8a82780 +#, fuzzy +msgid "UiFlow2:" +msgstr "UiFlow2 应用示例:" -#: ../../en/units/thermal.rst:26 42f39f2a388242678d2a8149a41ea39d +#: ../../en/units/thermal.rst:64 42f39f2a388242678d2a8149a41ea39d msgid "|init.png|" msgstr "" @@ -81,28 +127,25 @@ msgstr "" msgid "init.png" msgstr "" -#: ../../en/units/thermal.rst:32 525de889e6574bb697320f12330119ea +#: ../../en/units/thermal.rst:70 525de889e6574bb697320f12330119ea msgid "Methods" msgstr "" -#: ../../en/units/thermal.rst 049e1fd3ca1a4f2196e2a9e3fe555758 -#: 4d7bce23d2d4454aa84097cad815dad8 58f98a83c7094f2f81b3f1c97268f599 -#: dd54baf661714529afbf100e747cb51d +#: ../../en/units/thermal.rst dd54baf661714529afbf100e747cb51d msgid "type" msgstr "" -#: ../../en/units/thermal.rst:36 ../../en/units/thermal.rst:47 -#: ../../en/units/thermal.rst:58 ../../en/units/thermal.rst:83 -#: 3ee935b3080f416bb3a1e0a39bf92b91 62ecffaf3cba42eeba9af279cb73736c -#: 6b9b9bf8892d41e1a0d36e71a40f6c48 82effad1898b42d9a7bc3d7c27c239b1 +#: ../../en/units/thermal.rst:74 ../../en/units/thermal.rst:85 +#: ../../en/units/thermal.rst:96 ../../en/units/thermal.rst:121 +#: 82effad1898b42d9a7bc3d7c27c239b1 msgid "float" msgstr "" -#: ../../en/units/thermal.rst:38 2099433d9cb24bc5ba7f5adf0a2931eb +#: ../../en/units/thermal.rst:76 2099433d9cb24bc5ba7f5adf0a2931eb msgid "get the max temperature." -msgstr "" +msgstr "获取最大温度/" -#: ../../en/units/thermal.rst:42 7a3f0752ccca4584bf6fa3d8028e1605 +#: ../../en/units/thermal.rst:80 7a3f0752ccca4584bf6fa3d8028e1605 msgid "|get_max_temperature.png|" msgstr "" @@ -110,11 +153,11 @@ msgstr "" msgid "get_max_temperature.png" msgstr "" -#: ../../en/units/thermal.rst:49 a677f604f6e3446392df408290ac5a57 +#: ../../en/units/thermal.rst:87 a677f604f6e3446392df408290ac5a57 msgid "get the min temperature." -msgstr "" +msgstr "获取最小温度。" -#: ../../en/units/thermal.rst:53 ef776a1c464e40af9dcf16d25aa9b456 +#: ../../en/units/thermal.rst:91 ef776a1c464e40af9dcf16d25aa9b456 msgid "|get_min_temperature.png|" msgstr "" @@ -122,11 +165,11 @@ msgstr "" msgid "get_min_temperature.png" msgstr "" -#: ../../en/units/thermal.rst:60 570ff3eeb4fc4b66ba9cb54f6a927d11 +#: ../../en/units/thermal.rst:98 570ff3eeb4fc4b66ba9cb54f6a927d11 msgid "get the midpoint temperature." -msgstr "" +msgstr "获取中间温度。" -#: ../../en/units/thermal.rst:64 f14e82d4c59b479cb4b00d3dd689a8ed +#: ../../en/units/thermal.rst:102 f14e82d4c59b479cb4b00d3dd689a8ed msgid "|get_midpoint_temperature.png|" msgstr "" @@ -134,28 +177,27 @@ msgstr "" msgid "get_midpoint_temperature.png" msgstr "" -#: ../../en/units/thermal.rst:69 3dade066921a4f0793e747aef5ebbe3b +#: ../../en/units/thermal.rst:107 3dade066921a4f0793e747aef5ebbe3b msgid "get the temperature of the pixel at the specified coordinates." -msgstr "" +msgstr "获取指定坐标像素的温度。" -#: ../../en/units/thermal.rst:71 0e7e2fc443034680a973f4ae86ededab +#: ../../en/units/thermal.rst:109 0e7e2fc443034680a973f4ae86ededab msgid "The x coordinate of the pixel." -msgstr "" +msgstr "坐标 x" -#: ../../en/units/thermal.rst:72 4f66dbb70ac74d67965779304533ecec +#: ../../en/units/thermal.rst:110 4f66dbb70ac74d67965779304533ecec msgid "The y coordinate of the pixel." -msgstr "" +msgstr "坐标 y" -#: ../../en/units/thermal.rst 006550580f004698ad1c71454631821f -#: a0bb1aec79674ce18c8ee3b2aeb59b84 fee9d1ebffba40c0a2e6e78c81055299 +#: ../../en/units/thermal.rst fee9d1ebffba40c0a2e6e78c81055299 msgid "Returns" msgstr "" -#: ../../en/units/thermal.rst:74 ea0890d152a64b22b1068d0feb01d8fb +#: ../../en/units/thermal.rst:112 ea0890d152a64b22b1068d0feb01d8fb msgid "The temperature of the pixel." -msgstr "" +msgstr "指定像素点的温度" -#: ../../en/units/thermal.rst:78 99fa11ec585f4f148b18f7a08b045504 +#: ../../en/units/thermal.rst:116 99fa11ec585f4f148b18f7a08b045504 msgid "|get_pixel_temperature.png|" msgstr "" @@ -163,11 +205,11 @@ msgstr "" msgid "get_pixel_temperature.png" msgstr "" -#: ../../en/units/thermal.rst:85 77de2c6393d740058f3301fea6fbbb57 +#: ../../en/units/thermal.rst:123 77de2c6393d740058f3301fea6fbbb57 msgid "get the refresh rate." -msgstr "" +msgstr "获取刷新率" -#: ../../en/units/thermal.rst:89 95e3b3512d654b5ead1e80da50ded998 +#: ../../en/units/thermal.rst:127 95e3b3512d654b5ead1e80da50ded998 msgid "|get_refresh_rate.png|" msgstr "" @@ -175,16 +217,16 @@ msgstr "" msgid "get_refresh_rate.png" msgstr "" -#: ../../en/units/thermal.rst:94 aaf3d261367944f9b59b4bf0e2f5699f +#: ../../en/units/thermal.rst:132 aaf3d261367944f9b59b4bf0e2f5699f msgid "get the temperature buffer." -msgstr "" +msgstr "获取温度缓冲区数据。" -#: ../../en/units/thermal.rst:96 ../../en/units/thermal.rst:118 -#: be4f331a8801478e83155f604f3adcd5 eb9b33e2888740b583d86e72f9a4c2dc +#: ../../en/units/thermal.rst:134 ../../en/units/thermal.rst:156 +#: eb9b33e2888740b583d86e72f9a4c2dc msgid "The temperature buffer." -msgstr "" +msgstr "温度缓冲区数据。" -#: ../../en/units/thermal.rst:100 46b14f1531de4a0f9192adb9b4b693c5 +#: ../../en/units/thermal.rst:138 46b14f1531de4a0f9192adb9b4b693c5 msgid "|get_temperature_buffer.png|" msgstr "" @@ -192,85 +234,30 @@ msgstr "" msgid "get_temperature_buffer.png" msgstr "" -#: ../../en/units/thermal.rst:105 34247d27c1db4ab59f0f43ab8538cd4e +#: ../../en/units/thermal.rst:143 34247d27c1db4ab59f0f43ab8538cd4e msgid "Set the refresh rate." -msgstr "" +msgstr "设置刷新率。" -#: ../../en/units/thermal.rst:107 55e2c91dac6140a493e20a4bfca84994 +#: ../../en/units/thermal.rst:145 55e2c91dac6140a493e20a4bfca84994 msgid "The refresh rate in Hz." -msgstr "" +msgstr "刷新率 Hz。" -#: ../../en/units/thermal.rst:111 03be5d9e4a13442ea8ebcd93cd348c62 +#: ../../en/units/thermal.rst:149 03be5d9e4a13442ea8ebcd93cd348c62 msgid "|set_refresh_rate.png|" msgstr "" #: ../../en/refs/unit.thermal.ref:21 07985e6e0fcc4e74ae3c89462339510b msgid "set_refresh_rate.png" -msgstr "" +msgstr "设置刷新率。" -#: ../../en/units/thermal.rst:116 9419c062d24b40419bfe274d0bc7d7ff +#: ../../en/units/thermal.rst:154 9419c062d24b40419bfe274d0bc7d7ff msgid "Update the temperature buffer." -msgstr "" +msgstr "读取更新数据到温度缓冲区。" -#: ../../en/units/thermal.rst:122 99e425a7735245399f5190776fc69ab4 +#: ../../en/units/thermal.rst:160 99e425a7735245399f5190776fc69ab4 msgid "|update_temperature_buffer.png|" msgstr "" #: ../../en/refs/unit.thermal.ref:23 42b4062cf981411ab6abdf1a3374e173 msgid "update_temperature_buffer.png" -msgstr "" - -#~ msgid "|init.svg|" -#~ msgstr "" - -#~ msgid "init.svg" -#~ msgstr "" - -#~ msgid "|get_max_temperature.svg|" -#~ msgstr "" - -#~ msgid "get_max_temperature.svg" -#~ msgstr "" - -#~ msgid "|get_min_temperature.svg|" -#~ msgstr "" - -#~ msgid "get_min_temperature.svg" -#~ msgstr "" - -#~ msgid "|get_midpoint_temperature.svg|" -#~ msgstr "" - -#~ msgid "get_midpoint_temperature.svg" -#~ msgstr "" - -#~ msgid "|get_pixel_temperature.svg|" -#~ msgstr "" - -#~ msgid "get_pixel_temperature.svg" -#~ msgstr "" - -#~ msgid "|get_refresh_rate.svg|" -#~ msgstr "" - -#~ msgid "get_refresh_rate.svg" -#~ msgstr "" - -#~ msgid "|get_temperature_buffer.svg|" -#~ msgstr "" - -#~ msgid "get_temperature_buffer.svg" -#~ msgstr "" - -#~ msgid "|set_refresh_rate.svg|" -#~ msgstr "" - -#~ msgid "set_refresh_rate.svg" -#~ msgstr "" - -#~ msgid "|update_temperature_buffer.svg|" -#~ msgstr "" - -#~ msgid "update_temperature_buffer.svg" -#~ msgstr "" - +msgstr "读取更新数据到温度缓冲区。" diff --git a/examples/unit/thermal/cores3_thermal_imaging.m5f2 b/examples/unit/thermal/cores3_thermal_imaging.m5f2 new file mode 100644 index 00000000..b06cef64 --- /dev/null +++ b/examples/unit/thermal/cores3_thermal_imaging.m5f2 @@ -0,0 +1 @@ +{"version":"V2.0","versionNumber":"V2.2.5","type":"cores3","components":[{"name":"screen","type":"screen","layer":0,"screenId":"builtin","screenName":"","id":"__cores3_screen","createTime":1744885276012,"x":0,"y":0,"width":320,"height":240,"backgroundColor":"#222222","size":0,"isSelected":true}],"resources":[{"hardware":["hardware_button","hardware_pin_button","imu","speaker","touch","als","mic","i2c"]},{"unit":["unit_thermal"]}],"units":[{"type":"unit_thermal","name":"thermal_0","portList":["A","PAHUB","Custom"],"portType":"A","userPort":[22,21],"id":"xOX^BUm@K9TFSyl6","createTime":1744937450869,"bus":"i2c0","pahubPortList":[0,1,2,3,4,5],"pahubPort":0,"initBlockId":"Z-N#aW9^Eqgdd3~4.Pn7"}],"hats":[],"bases":[],"i2cs":[{"id":"i2c0","portType":"A","userPort":[22,21],"freq":"100000","blockId":"Q0j]iXarwA:Hf;TBvz5n"}],"blockly":"tempminmaxcolorratiortemplistgmin_tempbmax_tempyxctrue040000012thermal_0thermal_01palette#000000temperature_to_colorDescribe this function...temp50temp1min100maxratioDIVIDE1MINUS1temp1min1MINUS1max1minrMULTIPLY2551ratiogMULTIPLY2551MULTIPLY1MINUS11ABS9MINUS1ratio0.52bMULTIPLY2551MINUS11ratiocolorADD1MULTIPLY1r655361ADD1MULTIPLY1g2561bcolortruethermal_0templistthermal_0min_tempthermal_0max_tempthermal_0FONTS.DejaVu18fill351025020palette#0000003510hello M5min: min_temppalette#3366ff16510hello M5max: max_temppalette#cc0000y24x32cGETFROM_STARTtemplistADD1MULTIPLY1y321x2040fill0ADD801MULTIPLY1x50ADD601MULTIPLY1y555c","screen":[{"simulationName":"Built-in","type":"builtin","width":320,"height":240,"scale":0.78,"screenName":"","blockId":"","screenColorType":0,"id":"builtin","createTime":1744885276010}],"logicWhenNum":0,"customList":[]} \ No newline at end of file diff --git a/examples/unit/thermal/cores3_thermal_imaging.py b/examples/unit/thermal/cores3_thermal_imaging.py new file mode 100644 index 00000000..11b13560 --- /dev/null +++ b/examples/unit/thermal/cores3_thermal_imaging.py @@ -0,0 +1,123 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import os, sys, io +import M5 +from M5 import * +from hardware import I2C +from hardware import Pin +from unit import ThermalUnit +import math + + +i2c0 = None +thermal_0 = None +temp = None +min2 = None +max2 = None +color = None +ratio = None +r = None +templist = None +g = None +min_temp = None +b = None +max_temp = None +y = None +x = None +c = None + + +def temperature_to_color(temp, min2, max2): + global color, ratio, r, templist, g, min_temp, b, max_temp, y, x, c, i2c0, thermal_0 + # Clamp the temperature value to be within the min2 and max2 range + temp = min(max(temp, min2), max2) + # Calculate the ratio of the temperature within the given range [0.0 - 1.0] + ratio = (temp - min2) / (max2 - min2) + # Red increases with temperature + r = int(255 * ratio) + # Green peaks in the middle of the range + g = int(255 * ((1 - math.fabs(ratio - 0.5)) * 2)) + # Blue decreases with temperature + b = int(255 * (1 - ratio)) + # Combine R, G, B into a single 24-bit color value (0xRRGGBB) + color = r * 65536 + (g * 256 + b) + # Return the color value + return color + + +def setup(): + global \ + i2c0, \ + thermal_0, \ + temp, \ + color, \ + ratio, \ + min2, \ + max2, \ + r, \ + templist, \ + g, \ + min_temp, \ + b, \ + max_temp, \ + c, \ + x, \ + y + + M5.begin() + Widgets.fillScreen(0x222222) + i2c0 = I2C(0, scl=Pin(1), sda=Pin(2), freq=400000) + thermal_0 = ThermalUnit(i2c0) + thermal_0.set_refresh_rate(1) + M5.Lcd.clear(0x000000) + + +def loop(): + global \ + i2c0, \ + thermal_0, \ + temp, \ + color, \ + ratio, \ + min2, \ + max2, \ + r, \ + templist, \ + g, \ + min_temp, \ + b, \ + max_temp, \ + c, \ + x, \ + y + M5.update() + thermal_0.update_temperature_buffer() + templist = thermal_0.get_temperature_buffer() + min_temp = thermal_0.get_min_temperature + max_temp = thermal_0.get_max_temperature + M5.Lcd.setFont(M5.Lcd.FONTS.DejaVu18) + M5.Lcd.fillRect(35, 10, 250, 20, 0x000000) + M5.Lcd.setCursor(35, 10) + M5.Lcd.print((str("min: ") + str(min_temp)), 0x3366FF) + M5.Lcd.setCursor(165, 10) + M5.Lcd.print((str("max: ") + str(max_temp)), 0xCC0000) + for y in range(24): + for x in range(32): + c = temperature_to_color(templist[int((y * 32 + x) - 1)], 20, 40) + M5.Lcd.fillRect(80 + x * 5, 60 + y * 5, 5, 5, c) + + +if __name__ == "__main__": + try: + setup() + while True: + loop() + except (Exception, KeyboardInterrupt) as e: + try: + from utility import print_error_msg + + print_error_msg(e) + except ImportError: + print("please update to latest firmware") diff --git a/m5stack/libs/driver/mlx90640.py b/m5stack/libs/driver/mlx90640.py index 0b90b348..90ad59cf 100644 --- a/m5stack/libs/driver/mlx90640.py +++ b/m5stack/libs/driver/mlx90640.py @@ -1,5 +1,5 @@ # SPDX-FileCopyrightText: 2019 ladyada for Adafruit Industries -# SPDX-FileCopyrightText: Copyright (c) 2024 M5Stack Technology CO LTD +# SPDX-FileCopyrightText: Copyright (c) 2025 M5Stack Technology CO LTD # # SPDX-License-Identifier: MIT @@ -742,27 +742,33 @@ def _extract_deviating_pixels(self) -> None: self.outlierPixels.append(pix_cnt) pix_cnt += 1 - if len(self.brokenPixels) > 4: - raise RuntimeError("More than 4 broken pixels") + if len(self.brokenPixels) > 6: + # raise RuntimeError("More than 6 broken pixels") + print("More than 6 broken pixels") if len(self.outlierPixels) > 4: - raise RuntimeError("More than 4 outlier pixels") - if (len(self.brokenPixels) + len(self.outlierPixels)) > 4: - raise RuntimeError("More than 4 faulty pixels") + # raise RuntimeError("More than 6 outlier pixels") + print(("More than 6 outlier pixels")) + if (len(self.brokenPixels) + len(self.outlierPixels)) > 6: + # raise RuntimeError("More than 6 faulty pixels") + print("More than 6 faulty pixels") # print("Found %d broken pixels, %d outliers" # % (len(self.brokenPixels), len(self.outlierPixels))) for broken_pixel1, broken_pixel2 in self._unique_list_pairs(self.brokenPixels): if self._are_pixels_adjacent(broken_pixel1, broken_pixel2): - raise RuntimeError("Adjacent broken pixels") + # raise RuntimeError("Adjacent broken pixels") + print("Adjacent broken pixels") for outlier_pixel1, outlier_pixel2 in self._unique_list_pairs(self.outlierPixels): if self._are_pixels_adjacent(outlier_pixel1, outlier_pixel2): - raise RuntimeError("Adjacent outlier pixels") + # raise RuntimeError("Adjacent outlier pixels") + print("Adjacent broken pixels") for broken_pixel in self.brokenPixels: for outlier_pixel in self.outlierPixels: if self._are_pixels_adjacent(broken_pixel, outlier_pixel): - raise RuntimeError("Adjacent broken and outlier pixels") + # raise RuntimeError("Adjacent broken and outlier pixels") + print("Adjacent broken and outlier pixels") def _unique_list_pairs(self, inputList: List[int]) -> Tuple[int, int]: # pylint: disable=no-self-use From 994bb0f90c6f1dc73850abca39a0064fba5cc6f6 Mon Sep 17 00:00:00 2001 From: luoweiyuan Date: Fri, 18 Apr 2025 12:49:58 +0800 Subject: [PATCH 057/322] lib/module:Fix LoRaSx1262Module has not attribute BANDWIDTHS error. Signed-off-by: luoweiyuan --- m5stack/libs/module/lora_sx1262.py | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/m5stack/libs/module/lora_sx1262.py b/m5stack/libs/module/lora_sx1262.py index b832dd63..86e895d4 100644 --- a/m5stack/libs/module/lora_sx1262.py +++ b/m5stack/libs/module/lora_sx1262.py @@ -9,10 +9,6 @@ from micropython import const, schedule -# Valid bandwidth -BANDWIDTHS = ("7.8", "10.4", "15.6", "20.8", "31.25", "41.7", "62.5", "125", "250", "500") - - class LoRaSx1262Module: """Create an LoRaSx1262Module object. @@ -66,6 +62,19 @@ def __init__( syncword: int = 0x12, output_power: int = 10, ): + # Valid bandwidth + self.BANDWIDTHS = ( + "7.8", + "10.4", + "15.6", + "20.8", + "31.25", + "41.7", + "62.5", + "125", + "250", + "500", + ) self._validate_range(sf, 6, 12) self._validate_range(coding_rate, 5, 8) From 9a25a5589e46aaa493aa8fb6811c948e9fef1244 Mon Sep 17 00:00:00 2001 From: luoweiyuan Date: Fri, 18 Apr 2025 14:33:07 +0800 Subject: [PATCH 058/322] boards: Add AtomS3R-CAM support. Signed-off-by: lbuque --- .github/workflows/build-release.yml | 1 + .github/workflows/nightly-build.yml | 5 + m5stack/CMakeListsDefault.cmake | 6 + m5stack/Makefile | 4 + m5stack/boards/M5STACK_AtomS3R_CAM/board.json | 18 + .../boards/M5STACK_AtomS3R_CAM/manifest.py | 5 + .../M5STACK_AtomS3R_CAM/mpconfigboard.cmake | 42 + .../M5STACK_AtomS3R_CAM/mpconfigboard.h | 21 + .../M5STACK_AtomS3R_CAM/sdkconfig.board | 24 + m5stack/cmodules/cmodules.cmake | 4 + m5stack/cmodules/m5unified/m5unified.c | 1 + m5stack/cmodules/omv/modules/py_camera.c | 67 +- m5stack/cmodules/omv/omv_atoms3r_cam.cmake | 43 + m5stack/modules/startup/__init__.py | 5 + m5stack/modules/startup/atoms3r_cam.py | 75 ++ .../modules/startup/manifest_atoms3r_cam.py | 13 + .../5001-Add-software-i2c-support.patch | 837 ++++++++++++++++++ tools/ci.sh | 1 + 18 files changed, 1171 insertions(+), 1 deletion(-) create mode 100644 m5stack/boards/M5STACK_AtomS3R_CAM/board.json create mode 100644 m5stack/boards/M5STACK_AtomS3R_CAM/manifest.py create mode 100644 m5stack/boards/M5STACK_AtomS3R_CAM/mpconfigboard.cmake create mode 100644 m5stack/boards/M5STACK_AtomS3R_CAM/mpconfigboard.h create mode 100644 m5stack/boards/M5STACK_AtomS3R_CAM/sdkconfig.board create mode 100644 m5stack/cmodules/omv/omv_atoms3r_cam.cmake create mode 100644 m5stack/modules/startup/atoms3r_cam.py create mode 100644 m5stack/modules/startup/manifest_atoms3r_cam.py create mode 100644 m5stack/patches/5001-Add-software-i2c-support.patch diff --git a/.github/workflows/build-release.yml b/.github/workflows/build-release.yml index 3d344cda..5f98350a 100644 --- a/.github/workflows/build-release.yml +++ b/.github/workflows/build-release.yml @@ -32,6 +32,7 @@ jobs: ./m5stack/build-M5STACK_AtomS3/uiflow-*-*.bin ./m5stack/build-M5STACK_AtomS3_Lite/uiflow-*-*.bin ./m5stack/build-M5STACK_AtomS3R/uiflow-*-*.bin + ./m5stack/build-M5STACK_AtomS3R_CAM/uiflow-*-*.bin ./m5stack/build-M5STACK_AtomS3U/uiflow-*-*.bin ./m5stack/build-M5STACK_AtomU/uiflow-*-*.bin ./m5stack/build-M5STACK_Basic/uiflow-*-*.bin diff --git a/.github/workflows/nightly-build.yml b/.github/workflows/nightly-build.yml index 15375bcb..7ef848bf 100644 --- a/.github/workflows/nightly-build.yml +++ b/.github/workflows/nightly-build.yml @@ -52,6 +52,11 @@ jobs: with: name: M5STACK_AtomS3R_firmware path: ./m5stack/build-M5STACK_AtomS3R/uiflow-*-*.bin + - name: Deliver AtomS3R-CAM firmware + uses: actions/upload-artifact@v3 + with: + name: M5STACK_AtomS3R_CAM_firmware + path: ./m5stack/build-M5STACK_AtomS3R_CAM/uiflow-*-*.bin - name: Deliver AtomS3U firmware uses: actions/upload-artifact@v3 with: diff --git a/m5stack/CMakeListsDefault.cmake b/m5stack/CMakeListsDefault.cmake index 63ed0ac0..67421853 100644 --- a/m5stack/CMakeListsDefault.cmake +++ b/m5stack/CMakeListsDefault.cmake @@ -235,6 +235,12 @@ list(APPEND IDF_COMPONENTS ) endif() +if (M5_CAMERA_MODULE_ENABLE AND BOARD_TYPE STREQUAL "atoms3r_cam") +list(APPEND IDF_COMPONENTS + esp-code-scanner +) +endif() + if (M5_EPDIY_ENABLE AND BOARD_TYPE STREQUAL "papers3") message(STATUS "Enable EPDiy component") list(APPEND IDF_COMPONENTS diff --git a/m5stack/Makefile b/m5stack/Makefile index 1c772f74..3512d7c7 100644 --- a/m5stack/Makefile +++ b/m5stack/Makefile @@ -40,6 +40,7 @@ boards := \ M5STACK_AtomU:atomu \ M5STACK_Atom_Echo:atomecho \ M5STACK_AtomS3R:atoms3r \ + M5STACK_AtomS3R_CAM:atoms3r_cam \ M5STACK_StamPLC:stamplc define find_board @@ -77,6 +78,7 @@ BOARD_TYPE_DEF := \ atomu \ atomecho \ atoms3r \ + atoms3r_cam \ stamplc # Select the board type to build, default is None @@ -306,6 +308,7 @@ patch: $(call Package/patche,$(abspath ./components/M5Unified/M5Unified),$(abspath ./patches/2005-Support-LTR553.patch)) $(call Package/patche,$(abspath $(ADF_PATH)),$(abspath ./patches/3002-Modify-i2s_stream_idf5.patch)) $(call Package/patche,$(abspath ./components/epdiy),$(abspath ./patches/4001-Avoid-epdiy-compilation-failure-on-esp32-c6.patch)) + $(call Package/patche,$(abspath ./components/esp32-camera),$(abspath ./patches/5001-Add-software-i2c-support.patch)) # Unapply patches unpatch: @@ -319,3 +322,4 @@ unpatch: $(call Package/unpatche,$(abspath ./components/M5Unified/M5Unified),$(abspath ./patches/2005-Support-LTR553.patch)) $(call Package/unpatche,$(abspath $(ADF_PATH)),$(abspath ./patches/3002-Modify-i2s_stream_idf5.patch)) $(call Package/unpatche,$(abspath ./components/epdiy),$(abspath ./patches/4001-Avoid-epdiy-compilation-failure-on-esp32-c6.patch)) + $(call Package/unpatche,$(abspath ./components/esp32-camera),$(abspath ./patches/5001-Add-software-i2c-support.patch)) diff --git a/m5stack/boards/M5STACK_AtomS3R_CAM/board.json b/m5stack/boards/M5STACK_AtomS3R_CAM/board.json new file mode 100644 index 00000000..95f6ce30 --- /dev/null +++ b/m5stack/boards/M5STACK_AtomS3R_CAM/board.json @@ -0,0 +1,18 @@ +{ + "deploy": [ + "../deploy_s3.md" + ], + "docs": "", + "features": [ + "BLE", + "WiFi" + ], + "images": [ + "generic_s3.jpg" + ], + "mcu": "esp32s3", + "product": "M5Stack S3 Serials", + "thumbnail": "", + "url": "https://www.espressif.com/en/products/modules", + "vendor": "M5Stack" +} \ No newline at end of file diff --git a/m5stack/boards/M5STACK_AtomS3R_CAM/manifest.py b/m5stack/boards/M5STACK_AtomS3R_CAM/manifest.py new file mode 100644 index 00000000..bb721620 --- /dev/null +++ b/m5stack/boards/M5STACK_AtomS3R_CAM/manifest.py @@ -0,0 +1,5 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +include("$(MPY_DIR)/../m5stack/modules/startup/manifest_atoms3r_cam.py") diff --git a/m5stack/boards/M5STACK_AtomS3R_CAM/mpconfigboard.cmake b/m5stack/boards/M5STACK_AtomS3R_CAM/mpconfigboard.cmake new file mode 100644 index 00000000..d279cfb9 --- /dev/null +++ b/m5stack/boards/M5STACK_AtomS3R_CAM/mpconfigboard.cmake @@ -0,0 +1,42 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +set(IDF_TARGET esp32s3) + +# atoms3r https://github.com/m5stack/m5stack-board-id/blob/main/board.csv#L20 +set(BOARD_ID 144) + +set(SDKCONFIG_DEFAULTS + ./boards/sdkconfig.base + ${SDKCONFIG_IDF_VERSION_SPECIFIC} + ./boards/sdkconfig.240mhz + ./boards/sdkconfig.disable_iram + ./boards/sdkconfig.ble + ./boards/sdkconfig.usb + ./boards/sdkconfig.usb_cdc + ./boards/sdkconfig.flash_8mb + ./boards/sdkconfig.spiram + ./boards/sdkconfig.spiram_oct + ./boards/sdkconfig.freertos + ./boards/M5STACK_AtomS3R_CAM/sdkconfig.board +) + +# If not enable LVGL, ignore this... +set(LV_CFLAGS -DLV_COLOR_DEPTH=16 -DLV_COLOR_16_SWAP=0) + +if(NOT MICROPY_FROZEN_MANIFEST) + set(MICROPY_FROZEN_MANIFEST ${CMAKE_SOURCE_DIR}/boards/manifest.py) +endif() + +# NOTE: 这里的配置是无效的,仅为了兼容ADF,保证编译通过 +set(ADF_COMPS "$ENV{ADF_PATH}/components") +set(ADF_BOARD_DIR "$ENV{ADF_PATH}/components/audio_board/esp32_s3_box_3") + +list(APPEND EXTRA_COMPONENT_DIRS + $ENV{ADF_PATH}/components/audio_pipeline + $ENV{ADF_PATH}/components/audio_sal + $ENV{ADF_PATH}/components/esp-adf-libs + $ENV{ADF_PATH}/components/esp-sr + ${CMAKE_SOURCE_DIR}/boards +) \ No newline at end of file diff --git a/m5stack/boards/M5STACK_AtomS3R_CAM/mpconfigboard.h b/m5stack/boards/M5STACK_AtomS3R_CAM/mpconfigboard.h new file mode 100644 index 00000000..8172f122 --- /dev/null +++ b/m5stack/boards/M5STACK_AtomS3R_CAM/mpconfigboard.h @@ -0,0 +1,21 @@ +/* + * SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD + * + * SPDX-License-Identifier: MIT + */ + +#define MICROPY_HW_BOARD_NAME "M5STACK AtomS3R-CAM" +#define MICROPY_HW_MCU_NAME "ESP32-S3-PICO-1" + +#define MICROPY_PY_MACHINE_DAC (0) + +// Enable UART REPL for modules that have an external USB-UART and don't use native USB. +#define MICROPY_HW_ENABLE_UART_REPL (1) + +#define MICROPY_HW_I2C0_SCL (0) +#define MICROPY_HW_I2C0_SDA (45) + +#define MICROPY_HW_USB_CDC_INTERFACE_STRING "M5Stack AtomS3R-CAM(UiFlow2)" + +// If not enable LVGL, ignore this... +#include "./../mpconfiglvgl.h" diff --git a/m5stack/boards/M5STACK_AtomS3R_CAM/sdkconfig.board b/m5stack/boards/M5STACK_AtomS3R_CAM/sdkconfig.board new file mode 100644 index 00000000..3ed3c045 --- /dev/null +++ b/m5stack/boards/M5STACK_AtomS3R_CAM/sdkconfig.board @@ -0,0 +1,24 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +CONFIG_FLASHMODE_DIO=y # QIO mode has some problem when mount fs +CONFIG_ESPTOOLPY_FLASHMODE_DIO=y +CONFIG_ESPTOOLPY_FLASHMODE="dio" +CONFIG_ESPTOOLPY_FLASHFREQ_80M=y +CONFIG_ESPTOOLPY_FLASHSIZE_DETECT=y +CONFIG_ESPTOOLPY_AFTER_NORESET=y + +CONFIG_SPIRAM_MEMTEST=y +# CONFIG_FREERTOS_UNICORE=y + +# M5STACK UiFlow USB description +CONFIG_TINYUSB_DESC_CDC_STRING="M5Stack AtomS3R-CAM(UiFlow2)" + +# SSL +CONFIG_MBEDTLS_AES_USE_INTERRUPT=n + +CONFIG_FREERTOS_ENABLE_BACKWARD_COMPATIBILITY=y + +# for component/esp32-camera +CONFIG_SCCB_SOFTWARE_SUPPORT=y \ No newline at end of file diff --git a/m5stack/cmodules/cmodules.cmake b/m5stack/cmodules/cmodules.cmake index ad4ea4fb..5bb28ee7 100644 --- a/m5stack/cmodules/cmodules.cmake +++ b/m5stack/cmodules/cmodules.cmake @@ -16,6 +16,10 @@ if (M5_CAMERA_MODULE_ENABLE) endif() endif() +if (BOARD_TYPE STREQUAL "atoms3r_cam") + include(${CMAKE_CURRENT_LIST_DIR}/omv/omv_atoms3r_cam.cmake) +endif() + # add m5can module include(${CMAKE_CURRENT_LIST_DIR}/m5can/m5can.cmake) diff --git a/m5stack/cmodules/m5unified/m5unified.c b/m5stack/cmodules/m5unified/m5unified.c index cbd44725..3a0067f0 100644 --- a/m5stack/cmodules/m5unified/m5unified.c +++ b/m5stack/cmodules/m5unified/m5unified.c @@ -45,6 +45,7 @@ static const mp_rom_map_elem_t m5_board_member_table[] = { { MP_ROM_QSTR(MP_QSTR_M5NanoC6), MP_ROM_INT(140) }, { MP_ROM_QSTR(MP_QSTR_M5AtomMatrix), MP_ROM_INT(141) }, { MP_ROM_QSTR(MP_QSTR_M5AtomEcho), MP_ROM_INT(142) }, + { MP_ROM_QSTR(MP_QSTR_M5AtomS3R_CAM), MP_ROM_INT(144) }, // external displays { MP_ROM_QSTR(MP_QSTR_M5ATOMDisplay), MP_ROM_INT(192) }, { MP_ROM_QSTR(MP_QSTR_M5UnitLCD), MP_ROM_INT(193) }, diff --git a/m5stack/cmodules/omv/modules/py_camera.c b/m5stack/cmodules/omv/modules/py_camera.c index 0cdbe327..3e1c24b6 100644 --- a/m5stack/cmodules/omv/modules/py_camera.c +++ b/m5stack/cmodules/omv/modules/py_camera.c @@ -26,9 +26,14 @@ #include "esp_spi_flash.h" #include "esp_camera.h" #include "esp_log.h" +#include "driver/gpio.h" #define TAG "camera" + + +#if BOARD_ID == 10 // CoreS3 + #define CORES3_CAMERA_POWER_DOWN_PIN -1 #define CORES3_CAMERA_RESET_PIN -1 #define CORES3_CAMERA_XCLK_PIN 2 @@ -50,7 +55,7 @@ static camera_config_t camera_config = { .pin_pwdn = CORES3_CAMERA_POWER_DOWN_PIN, .pin_reset = CORES3_CAMERA_RESET_PIN, .pin_xclk = CORES3_CAMERA_XCLK_PIN, - .pin_sscb_sda = -1,// CORES3_CAMERA_SDA_PIN, // 公用 I2C1 在其他地方初始化 + .pin_sscb_sda = -1,// CORES3_CAMERA_SDA_PIN, // 共用 I2C1 在其他地方初始化 .pin_sscb_scl = -1,// CORES3_CAMERA_SCL_PIN, .pin_d7 = CORES3_CAMERA_D7_PIN, .pin_d6 = CORES3_CAMERA_D6_PIN, @@ -74,6 +79,58 @@ static camera_config_t camera_config = { .sccb_i2c_port = 1, // use I2C1 }; + +#elif BOARD_ID == 144 // AtomS3R_CAM + +#define ATOMS3R_CAM_PIN_PWDN -1 +#define ATOMS3R_CAM_PIN_RESET -1 +#define ATOMS3R_CAM_PIN_HREF 14 // 水平 +#define ATOMS3R_CAM_PIN_VSYNC 10 // 垂直同步 +#define ATOMS3R_CAM_PIN_XCLK 21 // 像素时钟 +#define ATOMS3R_CAM_PIN_PCLK 40 // 时钟 +#define ATOMS3R_CAM_PIN_SIOC 9 // 串行时钟 +#define ATOMS3R_CAM_PIN_SIOD 12 // 串行数据 +#define ATOMS3R_CAM_PIN_D0 3 // 数据0 +#define ATOMS3R_CAM_PIN_D1 42 // 数据1 +#define ATOMS3R_CAM_PIN_D2 46 // 数据2 +#define ATOMS3R_CAM_PIN_D3 48 // 数据3 +#define ATOMS3R_CAM_PIN_D4 4 // 数据4 +#define ATOMS3R_CAM_PIN_D5 17 // 数据5 +#define ATOMS3R_CAM_PIN_D6 11 // 数据6 +#define ATOMS3R_CAM_PIN_D7 13 // 数据7 +#define ATOMS3R_CAM_PIN_EN 18 // 电源控制 + +camera_config_t camera_config = { + .pin_pwdn = ATOMS3R_CAM_PIN_PWDN, + .pin_reset = ATOMS3R_CAM_PIN_RESET, + .pin_sccb_scl = ATOMS3R_CAM_PIN_SIOC, + .pin_sccb_sda = ATOMS3R_CAM_PIN_SIOD, + .pin_d0 = ATOMS3R_CAM_PIN_D0, + .pin_d1 = ATOMS3R_CAM_PIN_D1, + .pin_d2 = ATOMS3R_CAM_PIN_D2, + .pin_d3 = ATOMS3R_CAM_PIN_D3, + .pin_d4 = ATOMS3R_CAM_PIN_D4, + .pin_d5 = ATOMS3R_CAM_PIN_D5, + .pin_d6 = ATOMS3R_CAM_PIN_D6, + .pin_d7 = ATOMS3R_CAM_PIN_D7, + .pin_vsync = ATOMS3R_CAM_PIN_VSYNC, + .pin_href = ATOMS3R_CAM_PIN_HREF, + .pin_pclk = ATOMS3R_CAM_PIN_PCLK, + .pin_xclk = ATOMS3R_CAM_PIN_XCLK, + .xclk_freq_hz = 20000000, + .ledc_timer = LEDC_TIMER_0, + .ledc_channel = LEDC_CHANNEL_0, + .pixel_format = PIXFORMAT_RGB565, + .frame_size = FRAMESIZE_QVGA, + .jpeg_quality = 6, + .fb_count = 2, + .fb_location = CAMERA_FB_IN_PSRAM, + .grab_mode = CAMERA_GRAB_WHEN_EMPTY, +}; + +#endif + + typedef struct { bool hmirror; bool vflip; @@ -95,6 +152,14 @@ static bool camera_init_helper(size_t n_args, const mp_obj_t *pos_args, mp_map_t { MP_QSTR_fb_count, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = 2 } }, { MP_QSTR_fb_location, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = CAMERA_FB_IN_PSRAM } }, }; + +#if BOARD_ID == 144 + gpio_reset_pin(ATOMS3R_CAM_PIN_EN); + gpio_set_direction(ATOMS3R_CAM_PIN_EN, GPIO_MODE_OUTPUT); + gpio_set_level(ATOMS3R_CAM_PIN_EN, 0); // 拉低开启电源 + vTaskDelay(pdMS_TO_TICKS(300)); +#endif + /* *FORMAT-ON* */ mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); diff --git a/m5stack/cmodules/omv/omv_atoms3r_cam.cmake b/m5stack/cmodules/omv/omv_atoms3r_cam.cmake new file mode 100644 index 00000000..9ea9ac74 --- /dev/null +++ b/m5stack/cmodules/omv/omv_atoms3r_cam.cmake @@ -0,0 +1,43 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +# Create an INTERFACE library for our C module. +add_library(moudle_omv INTERFACE) + +add_compile_definitions(USE_OMV) + +# Add our source files to the lib +target_sources(moudle_omv INTERFACE + ${CMAKE_CURRENT_LIST_DIR}/imlib/draw.c + ${CMAKE_CURRENT_LIST_DIR}/imlib/font.c + ${CMAKE_CURRENT_LIST_DIR}/imlib/fmath.c + ${CMAKE_CURRENT_LIST_DIR}/imlib/imlib.c + ${CMAKE_CURRENT_LIST_DIR}/modules/py_camera.c + ${CMAKE_CURRENT_LIST_DIR}/modules/py_helper.c + ${CMAKE_CURRENT_LIST_DIR}/modules/py_image.c + ${CMAKE_CURRENT_LIST_DIR}/modules/py_jpg.c + ${CMAKE_CURRENT_LIST_DIR}/modules/py_code_scanner.c + ${CMAKE_CURRENT_LIST_DIR}/utils/utils.c +) + +# Add the current directory as an include directory. +target_include_directories(moudle_omv INTERFACE + ${CMAKE_CURRENT_LIST_DIR} + ${CMAKE_CURRENT_LIST_DIR}/imlib/ + ${CMAKE_CURRENT_LIST_DIR}/utils/ + ${CMAKE_CURRENT_LIST_DIR}/modules/ + # esp32-camera + ${CMAKE_CURRENT_LIST_DIR}/../../components/esp32-camera/driver/include + ${CMAKE_CURRENT_LIST_DIR}/../../components/esp32-camera/driver/private_include + ${CMAKE_CURRENT_LIST_DIR}/../../components/esp32-camera/conversions/include + ${CMAKE_CURRENT_LIST_DIR}/../../components/esp32-camera/conversions/private_include + ${CMAKE_CURRENT_LIST_DIR}/../../components/esp32-camera/sensors/private_include + # esp-code-scanner + ${CMAKE_CURRENT_LIST_DIR}/../../components/esp-code-scanner/include +) + +# Link our INTERFACE library to the usermod target. +target_link_libraries(usermod INTERFACE moudle_omv) + + diff --git a/m5stack/modules/startup/__init__.py b/m5stack/modules/startup/__init__.py index 6c4b63f0..c4755c05 100644 --- a/m5stack/modules/startup/__init__.py +++ b/m5stack/modules/startup/__init__.py @@ -115,6 +115,11 @@ def startup(boot_opt, timeout: int = 60) -> None: atoms3r = AtomS3R_Startup() atoms3r.startup(ssid, pswd, timeout) + elif board_id == M5.BOARD.M5AtomS3R_CAM: + from .atoms3r_cam import AtomS3R_CAM_Startup + + atoms3r = AtomS3R_CAM_Startup() + atoms3r.startup(ssid, pswd, timeout) elif board_id == M5.BOARD.M5AtomMatrix: from .atommatrix import AtomMatrix_Startup diff --git a/m5stack/modules/startup/atoms3r_cam.py b/m5stack/modules/startup/atoms3r_cam.py new file mode 100644 index 00000000..32b56008 --- /dev/null +++ b/m5stack/modules/startup/atoms3r_cam.py @@ -0,0 +1,75 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT +# AtomS3R-CAM startup script +import M5 +import time +import network +import machine +import binascii +from . import Startup + +# AtomS3-CAM startup menu + +class AtomS3R_CAM_Startup(Startup): + + def __init__(self) -> None: + super().__init__() + + def show_hits(self, hits: str) -> None: + print(hits) + + def show_msg(self, msg: str) -> None: + print(msg) + + def show_ssid(self, ssid: str) -> None: + if len(ssid) > 9: + self.show_msg(ssid[:7] + "...") + else: + self.show_msg(ssid) + + def show_mac(self) -> None: + mac = binascii.hexlify(machine.unique_id()).decode("utf-8").upper() + print(mac[0:6] + "_" + mac[6:]) + + def show_error(self, ssid: str, error: str) -> None: + self.show_ssid(ssid) + self.show_hits(error) + self.show_mac() + print("SSID: " + ssid + "\r\nNotice: " + error) + + def startup(self, ssid: str, pswd: str, timeout: int = 60) -> None: + self.show_mac() + + if super().connect_network(ssid=ssid, pswd=pswd): + self.show_ssid(ssid) + count = 1 + status = super().connect_status() + start = time.time() + while status is not network.STAT_GOT_IP: + time.sleep_ms(300) + if status is network.STAT_NO_AP_FOUND: + self.show_error(ssid, "NO AP FOUND") + break + elif status is network.STAT_WRONG_PASSWORD: + self.show_error(ssid, "WRONG PASSWORD") + break + elif status is network.STAT_HANDSHAKE_TIMEOUT: + self.show_error(ssid, "HANDSHAKE ERR") + break + elif status is network.STAT_CONNECTING: + self.show_hits("." * count) + count = count + 1 + if count > 5: + count = 1 + status = super().connect_status() + # connect to network timeout + if (time.time() - start) > timeout: + self.show_error(ssid, "TIMEOUT") + break + + if status is network.STAT_GOT_IP: + self.show_hits(super().local_ip()) + print("Local IP: " + super().local_ip()) + else: + self.show_error("Not Found", "Use Burner setup") \ No newline at end of file diff --git a/m5stack/modules/startup/manifest_atoms3r_cam.py b/m5stack/modules/startup/manifest_atoms3r_cam.py new file mode 100644 index 00000000..95d5622a --- /dev/null +++ b/m5stack/modules/startup/manifest_atoms3r_cam.py @@ -0,0 +1,13 @@ +# SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +package( + "startup", + ( + "__init__.py", + "atoms3r_cam.py", + ), + base_path="..", + opt=3, +) diff --git a/m5stack/patches/5001-Add-software-i2c-support.patch b/m5stack/patches/5001-Add-software-i2c-support.patch new file mode 100644 index 00000000..d4c9f3b7 --- /dev/null +++ b/m5stack/patches/5001-Add-software-i2c-support.patch @@ -0,0 +1,837 @@ +diff --git a/Kconfig b/Kconfig +index 2632c82..2101ebe 100755 +--- a/Kconfig ++++ b/Kconfig +@@ -128,6 +128,10 @@ menu "Camera configuration" + + endchoice + ++ config SCCB_SOFTWARE_SUPPORT ++ bool "Enable software I2C for SCCB" ++ default n ++ + config SCCB_CLK_FREQ + int "SCCB clk frequency" + default 100000 +@@ -135,7 +139,7 @@ menu "Camera configuration" + help + Increasing this value can reduce the initialization time of the sensor. + Please refer to the relevant instructions of the sensor to adjust the value. +- ++ + choice GC_SENSOR_WINDOW_MODE + bool "GalaxyCore Sensor Window Mode" + depends on (GC2145_SUPPORT || GC032A_SUPPORT || GC0308_SUPPORT) +diff --git a/driver/sccb.c b/driver/sccb.c +index 307166d..9ad5576 100755 +--- a/driver/sccb.c ++++ b/driver/sccb.c +@@ -6,255 +6,560 @@ + * SCCB (I2C like) driver. + * + */ +-#include +-#include +-#include +-#include +-#include "sccb.h" +-#include "sensor.h" +-#include +-#include "sdkconfig.h" +-#if defined(ARDUINO_ARCH_ESP32) && defined(CONFIG_ARDUHAL_ESP_LOG) +-#include "esp32-hal-log.h" +-#else +-#include "esp_log.h" +-static const char* TAG = "sccb"; +-#endif + +-#define LITTLETOBIG(x) ((x<<8)|(x>>8)) ++ #include ++ #include ++ #include ++ #include ++ #include "sccb.h" ++ #include "sensor.h" ++ #include ++ #include "sdkconfig.h" ++ #if defined(ARDUINO_ARCH_ESP32) && defined(CONFIG_ARDUHAL_ESP_LOG) ++ #include "esp32-hal-log.h" ++ #else ++ #include "esp_log.h" ++ static const char *TAG = "sccb"; ++ #endif ++ ++ #define LITTLETOBIG(x) ((x << 8) | (x >> 8)) ++ ++ #include "driver/i2c.h" ++ ++ // support IDF 5.x ++ #ifndef portTICK_RATE_MS ++ #define portTICK_RATE_MS portTICK_PERIOD_MS ++ #endif ++ ++ #define SCCB_FREQ CONFIG_SCCB_CLK_FREQ /*!< I2C master frequency*/ ++ #define WRITE_BIT I2C_MASTER_WRITE /*!< I2C master write */ ++ #define READ_BIT I2C_MASTER_READ /*!< I2C master read */ ++ #define ACK_CHECK_EN 0x1 /*!< I2C master will check ack from slave*/ ++ #define ACK_CHECK_DIS 0x0 /*!< I2C master will not check ack from slave */ ++ #define ACK_VAL 0x0 /*!< I2C ack value */ ++ #define NACK_VAL 0x1 /*!< I2C nack value */ ++ #if CONFIG_SCCB_HARDWARE_I2C_PORT1 ++ const int SCCB_I2C_PORT_DEFAULT = 1; ++ #else ++ const int SCCB_I2C_PORT_DEFAULT = 0; ++ #endif ++ ++ static int sccb_i2c_port; ++ static bool sccb_owns_i2c_port; ++ ++ ++ ++ #if CONFIG_SCCB_SOFTWARE_SUPPORT ++ // ========================================================================================= ++ // software sccb implement ++ #include "driver/gpio.h" ++ #include "esp_err.h" ++ #include "esp_check.h" ++ #include "esp_timer.h" ++ ++ typedef struct { ++ int pin_scl; ++ int pin_sda; ++ uint32_t time_delay_us; ++ } soft_sccb_config_t; ++ ++ static soft_sccb_config_t g_soft_sccb_config; ++ ++ ++ static esp_err_t soft_bus_init(int pin_sda, int pin_scl) ++ { ++ ESP_LOGI(TAG, "soft bus init"); ++ ++ gpio_reset_pin(g_soft_sccb_config.pin_scl); ++ gpio_set_direction(g_soft_sccb_config.pin_scl, GPIO_MODE_OUTPUT); ++ gpio_set_pull_mode(g_soft_sccb_config.pin_scl, GPIO_PULLUP_ONLY); ++ gpio_reset_pin(g_soft_sccb_config.pin_sda); ++ gpio_set_direction(g_soft_sccb_config.pin_sda, GPIO_MODE_OUTPUT); ++ gpio_set_pull_mode(g_soft_sccb_config.pin_sda, GPIO_PULLUP_ONLY); + +-#include "driver/i2c.h" ++ // 空闲状态,两线均为高电平 ++ gpio_set_level(g_soft_sccb_config.pin_scl, 1); ++ gpio_set_level(g_soft_sccb_config.pin_sda, 1); + +-// support IDF 5.x +-#ifndef portTICK_RATE_MS +-#define portTICK_RATE_MS portTICK_PERIOD_MS +-#endif ++ g_soft_sccb_config.time_delay_us = (uint32_t)((1e6f / 100000) / 2.0f + 0.5f); + +-#define SCCB_FREQ CONFIG_SCCB_CLK_FREQ /*!< I2C master frequency*/ +-#define WRITE_BIT I2C_MASTER_WRITE /*!< I2C master write */ +-#define READ_BIT I2C_MASTER_READ /*!< I2C master read */ +-#define ACK_CHECK_EN 0x1 /*!< I2C master will check ack from slave*/ +-#define ACK_CHECK_DIS 0x0 /*!< I2C master will not check ack from slave */ +-#define ACK_VAL 0x0 /*!< I2C ack value */ +-#define NACK_VAL 0x1 /*!< I2C nack value */ +-#if CONFIG_SCCB_HARDWARE_I2C_PORT1 +-const int SCCB_I2C_PORT_DEFAULT = 1; +-#else +-const int SCCB_I2C_PORT_DEFAULT = 0; +-#endif +- +-static int sccb_i2c_port; +-static bool sccb_owns_i2c_port; +- +-int SCCB_Init(int pin_sda, int pin_scl) +-{ +- ESP_LOGI(TAG, "pin_sda %d pin_scl %d", pin_sda, pin_scl); +- i2c_config_t conf; +- esp_err_t ret; +- +- memset(&conf, 0, sizeof(i2c_config_t)); +- +- sccb_i2c_port = SCCB_I2C_PORT_DEFAULT; +- sccb_owns_i2c_port = true; +- ESP_LOGI(TAG, "sccb_i2c_port=%d", sccb_i2c_port); +- +- conf.mode = I2C_MODE_MASTER; +- conf.sda_io_num = pin_sda; +- conf.sda_pullup_en = GPIO_PULLUP_ENABLE; +- conf.scl_io_num = pin_scl; +- conf.scl_pullup_en = GPIO_PULLUP_ENABLE; +- conf.master.clk_speed = SCCB_FREQ; +- +- if ((ret = i2c_param_config(sccb_i2c_port, &conf)) != ESP_OK) { +- return ret; +- } +- +- return i2c_driver_install(sccb_i2c_port, conf.mode, 0, 0, 0); +-} +- +-int SCCB_Use_Port(int i2c_num) { // sccb use an already initialized I2C port +- if (sccb_owns_i2c_port) { +- SCCB_Deinit(); +- } +- if (i2c_num < 0 || i2c_num > I2C_NUM_MAX) { +- return ESP_ERR_INVALID_ARG; +- } +- sccb_i2c_port = i2c_num; + return ESP_OK; +-} +- +-int SCCB_Deinit(void) +-{ +- if (!sccb_owns_i2c_port) { +- return ESP_OK; +- } +- sccb_owns_i2c_port = false; +- return i2c_driver_delete(sccb_i2c_port); +-} +- +-uint8_t SCCB_Probe(void) +-{ +- uint8_t slave_addr = 0x0; +- +- for (size_t i = 0; i < CAMERA_MODEL_MAX; i++) { +- if (slave_addr == camera_sensor[i].sccb_addr) { +- continue; +- } +- slave_addr = camera_sensor[i].sccb_addr; +- i2c_cmd_handle_t cmd = i2c_cmd_link_create(); +- i2c_master_start(cmd); +- i2c_master_write_byte(cmd, ( slave_addr << 1 ) | WRITE_BIT, ACK_CHECK_EN); +- i2c_master_stop(cmd); +- esp_err_t ret = i2c_master_cmd_begin(sccb_i2c_port, cmd, 1000 / portTICK_RATE_MS); +- i2c_cmd_link_delete(cmd); +- if( ret == ESP_OK) { +- return slave_addr; +- } +- } +- return 0; +-} +- +-uint8_t SCCB_Read(uint8_t slv_addr, uint8_t reg) +-{ +- uint8_t data=0; +- esp_err_t ret = ESP_FAIL; +- i2c_cmd_handle_t cmd = i2c_cmd_link_create(); +- i2c_master_start(cmd); +- i2c_master_write_byte(cmd, ( slv_addr << 1 ) | WRITE_BIT, ACK_CHECK_EN); +- i2c_master_write_byte(cmd, reg, ACK_CHECK_EN); +- i2c_master_stop(cmd); +- ret = i2c_master_cmd_begin(sccb_i2c_port, cmd, 1000 / portTICK_RATE_MS); +- i2c_cmd_link_delete(cmd); +- if(ret != ESP_OK) return -1; +- cmd = i2c_cmd_link_create(); +- i2c_master_start(cmd); +- i2c_master_write_byte(cmd, ( slv_addr << 1 ) | READ_BIT, ACK_CHECK_EN); +- i2c_master_read_byte(cmd, &data, NACK_VAL); +- i2c_master_stop(cmd); +- ret = i2c_master_cmd_begin(sccb_i2c_port, cmd, 1000 / portTICK_RATE_MS); +- i2c_cmd_link_delete(cmd); +- if(ret != ESP_OK) { +- ESP_LOGE(TAG, "SCCB_Read Failed addr:0x%02x, reg:0x%02x, data:0x%02x, ret:%d", slv_addr, reg, data, ret); +- } +- return data; +-} +- +-int SCCB_Write(uint8_t slv_addr, uint8_t reg, uint8_t data) +-{ +- esp_err_t ret = ESP_FAIL; +- i2c_cmd_handle_t cmd = i2c_cmd_link_create(); +- i2c_master_start(cmd); +- i2c_master_write_byte(cmd, ( slv_addr << 1 ) | WRITE_BIT, ACK_CHECK_EN); +- i2c_master_write_byte(cmd, reg, ACK_CHECK_EN); +- i2c_master_write_byte(cmd, data, ACK_CHECK_EN); +- i2c_master_stop(cmd); +- ret = i2c_master_cmd_begin(sccb_i2c_port, cmd, 1000 / portTICK_RATE_MS); +- i2c_cmd_link_delete(cmd); +- if(ret != ESP_OK) { +- ESP_LOGE(TAG, "SCCB_Write Failed addr:0x%02x, reg:0x%02x, data:0x%02x, ret:%d", slv_addr, reg, data, ret); +- } +- return ret == ESP_OK ? 0 : -1; +-} +- +-uint8_t SCCB_Read16(uint8_t slv_addr, uint16_t reg) +-{ +- uint8_t data=0; +- esp_err_t ret = ESP_FAIL; +- uint16_t reg_htons = LITTLETOBIG(reg); +- uint8_t *reg_u8 = (uint8_t *)®_htons; +- i2c_cmd_handle_t cmd = i2c_cmd_link_create(); +- i2c_master_start(cmd); +- i2c_master_write_byte(cmd, ( slv_addr << 1 ) | WRITE_BIT, ACK_CHECK_EN); +- i2c_master_write_byte(cmd, reg_u8[0], ACK_CHECK_EN); +- i2c_master_write_byte(cmd, reg_u8[1], ACK_CHECK_EN); +- i2c_master_stop(cmd); +- ret = i2c_master_cmd_begin(sccb_i2c_port, cmd, 1000 / portTICK_RATE_MS); +- i2c_cmd_link_delete(cmd); +- if(ret != ESP_OK) return -1; +- cmd = i2c_cmd_link_create(); +- i2c_master_start(cmd); +- i2c_master_write_byte(cmd, ( slv_addr << 1 ) | READ_BIT, ACK_CHECK_EN); +- i2c_master_read_byte(cmd, &data, NACK_VAL); +- i2c_master_stop(cmd); +- ret = i2c_master_cmd_begin(sccb_i2c_port, cmd, 1000 / portTICK_RATE_MS); +- i2c_cmd_link_delete(cmd); +- if(ret != ESP_OK) { +- ESP_LOGE(TAG, "W [%04x]=%02x fail\n", reg, data); +- } +- return data; +-} +- +-int SCCB_Write16(uint8_t slv_addr, uint16_t reg, uint8_t data) +-{ +- static uint16_t i = 0; +- esp_err_t ret = ESP_FAIL; +- uint16_t reg_htons = LITTLETOBIG(reg); +- uint8_t *reg_u8 = (uint8_t *)®_htons; +- i2c_cmd_handle_t cmd = i2c_cmd_link_create(); +- i2c_master_start(cmd); +- i2c_master_write_byte(cmd, ( slv_addr << 1 ) | WRITE_BIT, ACK_CHECK_EN); +- i2c_master_write_byte(cmd, reg_u8[0], ACK_CHECK_EN); +- i2c_master_write_byte(cmd, reg_u8[1], ACK_CHECK_EN); +- i2c_master_write_byte(cmd, data, ACK_CHECK_EN); +- i2c_master_stop(cmd); +- ret = i2c_master_cmd_begin(sccb_i2c_port, cmd, 1000 / portTICK_RATE_MS); +- i2c_cmd_link_delete(cmd); +- if(ret != ESP_OK) { +- ESP_LOGE(TAG, "W [%04x]=%02x %d fail\n", reg, data, i++); +- } +- return ret == ESP_OK ? 0 : -1; +-} +- +-uint16_t SCCB_Read_Addr16_Val16(uint8_t slv_addr, uint16_t reg) +-{ +- uint16_t data = 0; +- uint8_t *data_u8 = (uint8_t *)&data; +- esp_err_t ret = ESP_FAIL; +- uint16_t reg_htons = LITTLETOBIG(reg); +- uint8_t *reg_u8 = (uint8_t *)®_htons; +- i2c_cmd_handle_t cmd = i2c_cmd_link_create(); +- i2c_master_start(cmd); +- i2c_master_write_byte(cmd, ( slv_addr << 1 ) | WRITE_BIT, ACK_CHECK_EN); +- i2c_master_write_byte(cmd, reg_u8[0], ACK_CHECK_EN); +- i2c_master_write_byte(cmd, reg_u8[1], ACK_CHECK_EN); +- i2c_master_stop(cmd); +- ret = i2c_master_cmd_begin(sccb_i2c_port, cmd, 1000 / portTICK_RATE_MS); +- i2c_cmd_link_delete(cmd); +- if(ret != ESP_OK) return -1; +- +- cmd = i2c_cmd_link_create(); +- i2c_master_start(cmd); +- i2c_master_write_byte(cmd, ( slv_addr << 1 ) | READ_BIT, ACK_CHECK_EN); +- i2c_master_read_byte(cmd, &data_u8[1], ACK_VAL); +- i2c_master_read_byte(cmd, &data_u8[0], NACK_VAL); +- i2c_master_stop(cmd); +- ret = i2c_master_cmd_begin(sccb_i2c_port, cmd, 1000 / portTICK_RATE_MS); +- i2c_cmd_link_delete(cmd); +- if(ret != ESP_OK) { +- ESP_LOGE(TAG, "W [%04x]=%04x fail\n", reg, data); +- } +- return data; +-} +- +-int SCCB_Write_Addr16_Val16(uint8_t slv_addr, uint16_t reg, uint16_t data) +-{ +- esp_err_t ret = ESP_FAIL; +- uint16_t reg_htons = LITTLETOBIG(reg); +- uint8_t *reg_u8 = (uint8_t *)®_htons; +- uint16_t data_htons = LITTLETOBIG(data); +- uint8_t *data_u8 = (uint8_t *)&data_htons; +- i2c_cmd_handle_t cmd = i2c_cmd_link_create(); +- i2c_master_start(cmd); +- i2c_master_write_byte(cmd, ( slv_addr << 1 ) | WRITE_BIT, ACK_CHECK_EN); +- i2c_master_write_byte(cmd, reg_u8[0], ACK_CHECK_EN); +- i2c_master_write_byte(cmd, reg_u8[1], ACK_CHECK_EN); +- i2c_master_write_byte(cmd, data_u8[0], ACK_CHECK_EN); +- i2c_master_write_byte(cmd, data_u8[1], ACK_CHECK_EN); +- i2c_master_stop(cmd); +- ret = i2c_master_cmd_begin(sccb_i2c_port, cmd, 1000 / portTICK_RATE_MS); +- i2c_cmd_link_delete(cmd); +- if(ret != ESP_OK) { +- ESP_LOGE(TAG, "W [%04x]=%04x fail\n", reg, data); +- } +- return ret == ESP_OK ? 0 : -1; +-} ++ } ++ ++ static esp_err_t soft_bus_start() ++ { ++ gpio_set_direction(g_soft_sccb_config.pin_sda, GPIO_MODE_OUTPUT); ++ gpio_set_pull_mode(g_soft_sccb_config.pin_sda, GPIO_PULLUP_ONLY); ++ // SDA 在 SCL 为高时下降,表示 START ++ ESP_RETURN_ON_ERROR(gpio_set_level(g_soft_sccb_config.pin_sda, 1), TAG, "Failed to set SDA low"); ++ ESP_RETURN_ON_ERROR(gpio_set_level(g_soft_sccb_config.pin_scl, 1), TAG, "Failed to set SCL high"); ++ esp_rom_delay_us(g_soft_sccb_config.time_delay_us); ++ ESP_RETURN_ON_ERROR(gpio_set_level(g_soft_sccb_config.pin_sda, 0), TAG, "Failed to set SDA high"); ++ ++ return ESP_OK; ++ } ++ ++ static esp_err_t soft_bus_stop() ++ { ++ gpio_set_direction(g_soft_sccb_config.pin_sda, GPIO_MODE_OUTPUT); ++ gpio_set_pull_mode(g_soft_sccb_config.pin_sda, GPIO_PULLUP_ONLY); ++ // SDA 在 SCL 为高时上升,表示 STOP ++ ESP_RETURN_ON_ERROR(gpio_set_level(g_soft_sccb_config.pin_sda, 0), TAG, "Failed to set SDA low"); ++ esp_rom_delay_us(2); ++ ESP_RETURN_ON_ERROR(gpio_set_level(g_soft_sccb_config.pin_scl, 1), TAG, "Failed to set SCL high"); ++ esp_rom_delay_us(g_soft_sccb_config.time_delay_us); ++ ESP_RETURN_ON_ERROR(gpio_set_level(g_soft_sccb_config.pin_sda, 1), TAG, "Failed to set SDA high"); ++ esp_rom_delay_us(g_soft_sccb_config.time_delay_us); ++ ++ return ESP_OK; ++ } ++ ++ static esp_err_t soft_bus_write_byte(uint8_t byte) ++ { ++ gpio_set_direction(g_soft_sccb_config.pin_sda, GPIO_MODE_OUTPUT); ++ gpio_set_pull_mode(g_soft_sccb_config.pin_sda, GPIO_PULLUP_ONLY); ++ for (int i = 0; i < 8; i++) { ++ // SDA 在 SCL 为低时设置数据,在 SCL 为高时被采样 ++ ESP_RETURN_ON_ERROR(gpio_set_level(g_soft_sccb_config.pin_scl, 0), TAG, "Failed to set SCL low"); ++ esp_rom_delay_us(2); ++ if (byte & 0x80) { ++ ESP_RETURN_ON_ERROR(gpio_set_level(g_soft_sccb_config.pin_sda, 1), TAG, "Failed to set SDA high"); ++ } else { ++ ESP_RETURN_ON_ERROR(gpio_set_level(g_soft_sccb_config.pin_sda, 0), TAG, "Failed to set SDA low"); ++ } ++ esp_rom_delay_us(g_soft_sccb_config.time_delay_us); ++ ESP_RETURN_ON_ERROR(gpio_set_level(g_soft_sccb_config.pin_scl, 1), TAG, "Failed to set SCL high"); ++ esp_rom_delay_us(g_soft_sccb_config.time_delay_us); ++ byte <<= 1; ++ } ++ ++ return ESP_OK; ++ } ++ ++ static esp_err_t soft_bus_read_byte(uint8_t *byte, bool ack) ++ { ++ esp_err_t ret = ESP_OK; ++ uint8_t value = 0; ++ ++ gpio_set_direction(g_soft_sccb_config.pin_sda, GPIO_MODE_INPUT); ++ gpio_set_pull_mode(g_soft_sccb_config.pin_sda, GPIO_PULLUP_ONLY); ++ ++ ESP_RETURN_ON_ERROR(gpio_set_level(g_soft_sccb_config.pin_sda, 1), TAG, "Failed to release SDA"); /*!< First release SDA */ ++ for (int i = 0; i < 8; i++) { ++ value <<= 1; ++ ESP_RETURN_ON_ERROR(gpio_set_level(g_soft_sccb_config.pin_scl, 1), TAG, "Failed to set SCL high"); ++ esp_rom_delay_us(g_soft_sccb_config.time_delay_us); ++ ++ if (gpio_get_level(g_soft_sccb_config.pin_sda)) { ++ value++; ++ } ++ ESP_RETURN_ON_ERROR(gpio_set_level(g_soft_sccb_config.pin_scl, 0), TAG, "Failed to set SCL low"); ++ esp_rom_delay_us(g_soft_sccb_config.time_delay_us); ++ } ++ ++ *byte = value; ++ ++ // 在SCL低电平期间设置ACK状态 ++ gpio_set_direction(g_soft_sccb_config.pin_sda, GPIO_MODE_OUTPUT); ++ gpio_set_pull_mode(g_soft_sccb_config.pin_sda, GPIO_PULLUP_ONLY); ++ ESP_RETURN_ON_ERROR(gpio_set_level(g_soft_sccb_config.pin_sda, ack ? 0 : 1), TAG, "SDA level fail"); ++ esp_rom_delay_us(g_soft_sccb_config.time_delay_us); ++ ++ // 生成ACK时钟脉冲 ++ ESP_RETURN_ON_ERROR(gpio_set_level(g_soft_sccb_config.pin_scl, 1), TAG, "SCL high fail"); ++ esp_rom_delay_us(g_soft_sccb_config.time_delay_us * 2); ++ ESP_RETURN_ON_ERROR(gpio_set_level(g_soft_sccb_config.pin_scl, 0), TAG, "SCL low fail"); ++ esp_rom_delay_us(g_soft_sccb_config.time_delay_us); ++ ++ return ret; ++ } ++ ++ static esp_err_t soft_bus_wait_ack() ++ { ++ // 第 9 个时钟周期,接收方拉低 SDA 表示 ACK ++ ESP_RETURN_ON_ERROR(gpio_set_level(g_soft_sccb_config.pin_scl, 0), TAG, "Failed to set SCL high"); ++ gpio_set_direction(g_soft_sccb_config.pin_sda, GPIO_MODE_INPUT); ++ gpio_set_pull_mode(g_soft_sccb_config.pin_sda, GPIO_PULLUP_ONLY); ++ esp_rom_delay_us(g_soft_sccb_config.time_delay_us); ++ ++ ESP_RETURN_ON_ERROR(gpio_set_level(g_soft_sccb_config.pin_scl, 1), TAG, "Failed to set SCL high"); ++ esp_rom_delay_us(g_soft_sccb_config.time_delay_us); ++ bool ack = ESP_ERR_NOT_FOUND; ++ if (!gpio_get_level(g_soft_sccb_config.pin_sda)) { ++ ack = ESP_OK; ++ } ++ ++ ESP_RETURN_ON_ERROR(gpio_set_level(g_soft_sccb_config.pin_scl, 0), TAG, "Failed to set SCL high"); ++ esp_rom_delay_us(g_soft_sccb_config.time_delay_us); ++ ++ return ack; ++ } ++ ++ #endif ++ // ========================================================================================= ++ ++ ++ int SCCB_Init(int pin_sda, int pin_scl) ++ { ++ #if CONFIG_SCCB_SOFTWARE_SUPPORT ++ ESP_LOGI(TAG, "Use software sccb, pin_sda %d pin_scl %d", pin_sda, pin_scl); ++ g_soft_sccb_config.pin_scl = pin_scl; ++ g_soft_sccb_config.pin_sda = pin_sda; ++ g_soft_sccb_config.time_delay_us = (uint32_t)((1e6f / 100000) / 2.0f + 0.5f); // 100kHz ++ soft_bus_init(g_soft_sccb_config.pin_sda, g_soft_sccb_config.pin_scl); ++ return ESP_OK; ++ #else ++ ESP_LOGI(TAG, "pin_sda %d pin_scl %d", pin_sda, pin_scl); ++ i2c_config_t conf; ++ esp_err_t ret; ++ ++ memset(&conf, 0, sizeof(i2c_config_t)); ++ ++ sccb_i2c_port = SCCB_I2C_PORT_DEFAULT; ++ sccb_owns_i2c_port = true; ++ ESP_LOGI(TAG, "sccb_i2c_port=%d", sccb_i2c_port); ++ ++ conf.mode = I2C_MODE_MASTER; ++ conf.sda_io_num = pin_sda; ++ conf.sda_pullup_en = GPIO_PULLUP_ENABLE; ++ conf.scl_io_num = pin_scl; ++ conf.scl_pullup_en = GPIO_PULLUP_ENABLE; ++ conf.master.clk_speed = SCCB_FREQ; ++ ++ if ((ret = i2c_param_config(sccb_i2c_port, &conf)) != ESP_OK) { ++ return ret; ++ } ++ ++ return i2c_driver_install(sccb_i2c_port, conf.mode, 0, 0, 0); ++ #endif ++ } ++ ++ int SCCB_Use_Port(int i2c_num) ++ { ++ #if CONFIG_SCCB_SOFTWARE_SUPPORT ++ return ESP_OK; ++ #else ++ // sccb use an already initialized I2C port ++ if (sccb_owns_i2c_port) { ++ SCCB_Deinit(); ++ } ++ if (i2c_num < 0 || i2c_num > I2C_NUM_MAX) { ++ return ESP_ERR_INVALID_ARG; ++ } ++ sccb_i2c_port = i2c_num; ++ ++ return ESP_OK; ++ #endif ++ } ++ ++ int SCCB_Deinit(void) ++ { ++ #if CONFIG_SCCB_SOFTWARE_SUPPORT ++ gpio_reset_pin(g_soft_sccb_config.pin_scl); ++ gpio_reset_pin(g_soft_sccb_config.pin_sda); ++ return ESP_OK; ++ #else ++ if (!sccb_owns_i2c_port) { ++ return ESP_OK; ++ } ++ sccb_owns_i2c_port = false; ++ return i2c_driver_delete(sccb_i2c_port); ++ #endif ++ } ++ ++ uint8_t SCCB_Probe(void) ++ { ++ uint8_t slave_addr = 0x0; ++ ++ #if CONFIG_SCCB_SOFTWARE_SUPPORT ++ for (size_t i = 0; i < CAMERA_MODEL_MAX; i++) { ++ if (slave_addr == camera_sensor[i].sccb_addr) { ++ continue; ++ } ++ slave_addr = camera_sensor[i].sccb_addr; ++ soft_bus_start(); ++ soft_bus_write_byte((slave_addr << 1) | WRITE_BIT); ++ esp_err_t ret = soft_bus_wait_ack(); ++ soft_bus_stop(); ++ if (ret == ESP_OK) { ++ return slave_addr; ++ } ++ esp_rom_delay_us(g_soft_sccb_config.time_delay_us); ++ } ++ #else ++ for (size_t i = 0; i < CAMERA_MODEL_MAX; i++) { ++ if (slave_addr == camera_sensor[i].sccb_addr) { ++ continue; ++ } ++ slave_addr = camera_sensor[i].sccb_addr; ++ i2c_cmd_handle_t cmd = i2c_cmd_link_create(); ++ i2c_master_start(cmd); ++ i2c_master_write_byte(cmd, ( slave_addr << 1 ) | WRITE_BIT, ACK_CHECK_EN); ++ i2c_master_stop(cmd); ++ esp_err_t ret = i2c_master_cmd_begin(sccb_i2c_port, cmd, 1000 / portTICK_RATE_MS); ++ i2c_cmd_link_delete(cmd); ++ if( ret == ESP_OK) { ++ return slave_addr; ++ } ++ } ++ #endif ++ ++ return 0; ++ } ++ ++ ++ uint8_t SCCB_Read(uint8_t slv_addr, uint8_t reg) ++ { ++ uint8_t data = 0; ++ esp_err_t ret = ESP_FAIL; ++ ++ #if CONFIG_SCCB_SOFTWARE_SUPPORT ++ soft_bus_start(); ++ soft_bus_write_byte((slv_addr << 1) | WRITE_BIT); ++ ret = soft_bus_wait_ack(); ++ soft_bus_write_byte(reg); ++ ret = soft_bus_wait_ack(); ++ // soft_bus_stop(); ++ if (ret != ESP_OK) return -1; ++ soft_bus_start(); ++ soft_bus_write_byte((slv_addr << 1) | READ_BIT); ++ ret = soft_bus_wait_ack(); ++ soft_bus_read_byte(&data, false); ++ soft_bus_stop(); ++ #else ++ i2c_cmd_handle_t cmd = i2c_cmd_link_create(); ++ i2c_master_start(cmd); ++ i2c_master_write_byte(cmd, ( slv_addr << 1 ) | WRITE_BIT, ACK_CHECK_EN); ++ i2c_master_write_byte(cmd, reg, ACK_CHECK_EN); ++ i2c_master_stop(cmd); ++ ret = i2c_master_cmd_begin(sccb_i2c_port, cmd, 1000 / portTICK_RATE_MS); ++ i2c_cmd_link_delete(cmd); ++ if(ret != ESP_OK) return -1; ++ cmd = i2c_cmd_link_create(); ++ i2c_master_start(cmd); ++ i2c_master_write_byte(cmd, ( slv_addr << 1 ) | READ_BIT, ACK_CHECK_EN); ++ i2c_master_read_byte(cmd, &data, NACK_VAL); ++ i2c_master_stop(cmd); ++ ret = i2c_master_cmd_begin(sccb_i2c_port, cmd, 1000 / portTICK_RATE_MS); ++ i2c_cmd_link_delete(cmd); ++ #endif ++ ++ if (ret != ESP_OK) { ++ ESP_LOGE(TAG, "SCCB_Read Failed addr:0x%02x, reg:0x%02x, data:0x%02x, ret:%d", slv_addr, reg, data, ret); ++ } ++ ++ return data; ++ } ++ ++ int SCCB_Write(uint8_t slv_addr, uint8_t reg, uint8_t data) ++ { ++ esp_err_t ret = ESP_FAIL; ++ ++ #if CONFIG_SCCB_SOFTWARE_SUPPORT ++ soft_bus_start(); ++ soft_bus_write_byte((slv_addr << 1) | WRITE_BIT); ++ ret = soft_bus_wait_ack(); ++ soft_bus_write_byte(reg); ++ ret = soft_bus_wait_ack(); ++ soft_bus_write_byte(data); ++ ret = soft_bus_wait_ack(); ++ soft_bus_stop(); ++ esp_rom_delay_us(g_soft_sccb_config.time_delay_us); ++ esp_rom_delay_us(g_soft_sccb_config.time_delay_us); ++ #else ++ i2c_cmd_handle_t cmd = i2c_cmd_link_create(); ++ i2c_master_start(cmd); ++ i2c_master_write_byte(cmd, ( slv_addr << 1 ) | WRITE_BIT, ACK_CHECK_EN); ++ i2c_master_write_byte(cmd, reg, ACK_CHECK_EN); ++ i2c_master_write_byte(cmd, data, ACK_CHECK_EN); ++ i2c_master_stop(cmd); ++ ret = i2c_master_cmd_begin(sccb_i2c_port, cmd, 1000 / portTICK_RATE_MS); ++ i2c_cmd_link_delete(cmd); ++ #endif ++ ++ if (ret != ESP_OK) { ++ ESP_LOGE(TAG, "SCCB_Write Failed addr:0x%02x, reg:0x%02x, data:0x%02x, ret:%d", slv_addr, reg, data, ret); ++ } ++ ++ return ret == ESP_OK ? 0 : -1; ++ } ++ ++ uint8_t SCCB_Read16(uint8_t slv_addr, uint16_t reg) ++ { ++ uint8_t data = 0; ++ esp_err_t ret = ESP_FAIL; ++ uint16_t reg_htons = LITTLETOBIG(reg); ++ uint8_t *reg_u8 = (uint8_t *)®_htons; ++ ++ #if CONFIG_SCCB_SOFTWARE_SUPPORT ++ soft_bus_start(); ++ soft_bus_write_byte((slv_addr << 1) | WRITE_BIT); ++ ret = soft_bus_wait_ack(); ++ soft_bus_write_byte(reg_u8[0]); ++ ret = soft_bus_wait_ack(); ++ soft_bus_write_byte(reg_u8[1]); ++ ret = soft_bus_wait_ack(); ++ soft_bus_stop(); ++ if (ret != ESP_OK) return -1; ++ soft_bus_start(); ++ soft_bus_write_byte((slv_addr << 1) | READ_BIT); ++ ret = soft_bus_wait_ack(); ++ soft_bus_read_byte(&data, true); ++ soft_bus_stop(); ++ #else ++ i2c_cmd_handle_t cmd = i2c_cmd_link_create(); ++ i2c_master_start(cmd); ++ i2c_master_write_byte(cmd, ( slv_addr << 1 ) | WRITE_BIT, ACK_CHECK_EN); ++ i2c_master_write_byte(cmd, reg_u8[0], ACK_CHECK_EN); ++ i2c_master_write_byte(cmd, reg_u8[1], ACK_CHECK_EN); ++ i2c_master_stop(cmd); ++ ret = i2c_master_cmd_begin(sccb_i2c_port, cmd, 1000 / portTICK_RATE_MS); ++ i2c_cmd_link_delete(cmd); ++ if(ret != ESP_OK) return -1; ++ cmd = i2c_cmd_link_create(); ++ i2c_master_start(cmd); ++ i2c_master_write_byte(cmd, ( slv_addr << 1 ) | READ_BIT, ACK_CHECK_EN); ++ i2c_master_read_byte(cmd, &data, NACK_VAL); ++ i2c_master_stop(cmd); ++ ret = i2c_master_cmd_begin(sccb_i2c_port, cmd, 1000 / portTICK_RATE_MS); ++ i2c_cmd_link_delete(cmd); ++ #endif ++ ++ if (ret != ESP_OK) { ++ ESP_LOGE(TAG, "W [%04x]=%02x fail\n", reg, data); ++ } ++ ++ return data; ++ } ++ ++ int SCCB_Write16(uint8_t slv_addr, uint16_t reg, uint8_t data) ++ { ++ static uint16_t i = 0; ++ esp_err_t ret = ESP_FAIL; ++ uint16_t reg_htons = LITTLETOBIG(reg); ++ uint8_t *reg_u8 = (uint8_t *)®_htons; ++ ++ #if CONFIG_SCCB_SOFTWARE_SUPPORT ++ soft_bus_start(); ++ soft_bus_write_byte((slv_addr << 1) | WRITE_BIT); ++ ret = soft_bus_wait_ack(); ++ soft_bus_write_byte(reg_u8[0]); ++ ret = soft_bus_wait_ack(); ++ soft_bus_write_byte(reg_u8[1]); ++ ret = soft_bus_wait_ack(); ++ soft_bus_write_byte(data); ++ ret = soft_bus_wait_ack(); ++ soft_bus_stop(); ++ #else ++ i2c_cmd_handle_t cmd = i2c_cmd_link_create(); ++ i2c_master_start(cmd); ++ i2c_master_write_byte(cmd, (slv_addr << 1) | WRITE_BIT, ACK_CHECK_EN); ++ i2c_master_write_byte(cmd, reg_u8[0], ACK_CHECK_EN); ++ i2c_master_write_byte(cmd, reg_u8[1], ACK_CHECK_EN); ++ i2c_master_write_byte(cmd, data, ACK_CHECK_EN); ++ i2c_master_stop(cmd); ++ ret = i2c_master_cmd_begin(sccb_i2c_port, cmd, 1000 / portTICK_RATE_MS); ++ i2c_cmd_link_delete(cmd); ++ #endif ++ ++ if (ret != ESP_OK) { ++ ESP_LOGE(TAG, "W [%04x]=%02x %d fail\n", reg, data, i++); ++ } ++ ++ return ret == ESP_OK ? 0 : -1; ++ } ++ ++ uint16_t SCCB_Read_Addr16_Val16(uint8_t slv_addr, uint16_t reg) ++ { ++ uint16_t data = 0; ++ uint8_t *data_u8 = (uint8_t *)&data; ++ esp_err_t ret = ESP_FAIL; ++ uint16_t reg_htons = LITTLETOBIG(reg); ++ uint8_t *reg_u8 = (uint8_t *)®_htons; ++ ++ #if CONFIG_SCCB_SOFTWARE_SUPPORT ++ // 未测试 ++ soft_bus_start(); ++ soft_bus_write_byte((slv_addr << 1) | WRITE_BIT); ++ ret = soft_bus_wait_ack(); ++ soft_bus_write_byte(reg_u8[0]); ++ ret = soft_bus_wait_ack(); ++ soft_bus_write_byte(reg_u8[1]); ++ ret = soft_bus_wait_ack(); ++ soft_bus_stop(); ++ if (ret != ESP_OK) return -1; ++ soft_bus_start(); ++ soft_bus_write_byte((slv_addr << 1) | READ_BIT); ++ ret = soft_bus_wait_ack(); ++ soft_bus_read_byte(&data_u8[1], true); ++ soft_bus_read_byte(&data_u8[0], false); ++ soft_bus_stop(); ++ #else ++ i2c_cmd_handle_t cmd = i2c_cmd_link_create(); ++ i2c_master_start(cmd); ++ i2c_master_write_byte(cmd, (slv_addr << 1) | WRITE_BIT, ACK_CHECK_EN); ++ i2c_master_write_byte(cmd, reg_u8[0], ACK_CHECK_EN); ++ i2c_master_write_byte(cmd, reg_u8[1], ACK_CHECK_EN); ++ i2c_master_stop(cmd); ++ ret = i2c_master_cmd_begin(sccb_i2c_port, cmd, 1000 / portTICK_RATE_MS); ++ i2c_cmd_link_delete(cmd); ++ if (ret != ESP_OK) return -1; ++ cmd = i2c_cmd_link_create(); ++ i2c_master_start(cmd); ++ i2c_master_write_byte(cmd, (slv_addr << 1) | READ_BIT, ACK_CHECK_EN); ++ i2c_master_read_byte(cmd, &data_u8[1], ACK_VAL); ++ i2c_master_read_byte(cmd, &data_u8[0], NACK_VAL); ++ i2c_master_stop(cmd); ++ ret = i2c_master_cmd_begin(sccb_i2c_port, cmd, 1000 / portTICK_RATE_MS); ++ i2c_cmd_link_delete(cmd); ++ #endif ++ ++ if (ret != ESP_OK) { ++ ESP_LOGE(TAG, "W [%04x]=%04x fail\n", reg, data); ++ } ++ ++ return data; ++ } ++ ++ int SCCB_Write_Addr16_Val16(uint8_t slv_addr, uint16_t reg, uint16_t data) ++ { ++ esp_err_t ret = ESP_FAIL; ++ uint16_t reg_htons = LITTLETOBIG(reg); ++ uint8_t *reg_u8 = (uint8_t *)®_htons; ++ uint16_t data_htons = LITTLETOBIG(data); ++ uint8_t *data_u8 = (uint8_t *)&data_htons; ++ ++ #if CONFIG_SCCB_SOFTWARE_SUPPORT ++ // 未测试 ++ soft_bus_start(); ++ soft_bus_write_byte((slv_addr << 1) | WRITE_BIT); ++ ret = soft_bus_wait_ack(); ++ soft_bus_write_byte(reg_u8[0]); ++ ret = soft_bus_wait_ack(); ++ soft_bus_write_byte(reg_u8[1]); ++ ret = soft_bus_wait_ack(); ++ soft_bus_write_byte(data_u8[0]); ++ ret = soft_bus_wait_ack(); ++ soft_bus_write_byte(data_u8[1]); ++ ret = soft_bus_wait_ack(); ++ soft_bus_stop(); ++ #else ++ i2c_cmd_handle_t cmd = i2c_cmd_link_create(); ++ i2c_master_start(cmd); ++ i2c_master_write_byte(cmd, (slv_addr << 1) | WRITE_BIT, ACK_CHECK_EN); ++ i2c_master_write_byte(cmd, reg_u8[0], ACK_CHECK_EN); ++ i2c_master_write_byte(cmd, reg_u8[1], ACK_CHECK_EN); ++ i2c_master_write_byte(cmd, data_u8[0], ACK_CHECK_EN); ++ i2c_master_write_byte(cmd, data_u8[1], ACK_CHECK_EN); ++ i2c_master_stop(cmd); ++ ret = i2c_master_cmd_begin(sccb_i2c_port, cmd, 1000 / portTICK_RATE_MS); ++ i2c_cmd_link_delete(cmd); ++ #endif ++ ++ if (ret != ESP_OK) { ++ ESP_LOGE(TAG, "W [%04x]=%04x fail\n", reg, data); ++ } ++ ++ return ret == ESP_OK ? 0 : -1; ++ } ++ +\ No newline at end of file diff --git a/tools/ci.sh b/tools/ci.sh index 9dbb512a..6cd6bbef 100644 --- a/tools/ci.sh +++ b/tools/ci.sh @@ -203,6 +203,7 @@ function ci_esp32_nightly_build { make ${MAKEOPTS} -C m5stack BOARD=M5STACK_AtomS3 pack_all make ${MAKEOPTS} -C m5stack BOARD=M5STACK_AtomS3_Lite pack_all make ${MAKEOPTS} -C m5stack BOARD=M5STACK_AtomS3R pack_all + make ${MAKEOPTS} -C m5stack BOARD=M5STACK_AtomS3R_CAM pack_all make ${MAKEOPTS} -C m5stack BOARD=M5STACK_AtomS3U pack_all make ${MAKEOPTS} -C m5stack BOARD=M5STACK_AtomU pack_all make ${MAKEOPTS} -C m5stack BOARD=M5STACK_Basic pack_all From 0f133c9fbdd71e2fdd6e0e21046e4b2130fef4de Mon Sep 17 00:00:00 2001 From: luoweiyuan Date: Fri, 18 Apr 2025 16:21:32 +0800 Subject: [PATCH 059/322] docs: Add Controllers/AtomS3R-CAM docs. Signed-off-by: lbuque --- .../controllers/atoms3r_cam/browser.png | Bin 0 -> 331047 bytes .../controllers/atoms3r_cam/configure.png | Bin 0 -> 233158 bytes .../controllers/atoms3r_cam/connect.png | Bin 0 -> 181965 bytes .../atoms3r_cam/copy_paste_example_code.png | Bin 0 -> 193957 bytes docs/_static/controllers/atoms3r_cam/run.png | Bin 0 -> 236226 bytes docs/en/controllers/atoms3r_cam.rst | 65 +++++++++++ docs/en/controllers/index.rst | 1 + docs/en/refs/controllers.atoms3r_cam.ref | 4 + .../atoms3r_cam/video_streaming.py | 105 ++++++++++++++++++ 9 files changed, 175 insertions(+) create mode 100644 docs/_static/controllers/atoms3r_cam/browser.png create mode 100644 docs/_static/controllers/atoms3r_cam/configure.png create mode 100644 docs/_static/controllers/atoms3r_cam/connect.png create mode 100644 docs/_static/controllers/atoms3r_cam/copy_paste_example_code.png create mode 100644 docs/_static/controllers/atoms3r_cam/run.png create mode 100644 docs/en/controllers/atoms3r_cam.rst create mode 100644 docs/en/refs/controllers.atoms3r_cam.ref create mode 100644 examples/controllers/atoms3r_cam/video_streaming.py diff --git a/docs/_static/controllers/atoms3r_cam/browser.png b/docs/_static/controllers/atoms3r_cam/browser.png new file mode 100644 index 0000000000000000000000000000000000000000..0391520a0e7781ec9c37f9af4f923bc92c0562f0 GIT binary patch literal 331047 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 z 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�+Xs4>@ooR9HES->pCP!8NdFF0d8V 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

PA4!hUb`j;Vg3!?q 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

T$l=!R@DG-;bRLUwf1FdA0 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 cQg_|O)!S)kuT9%UxHH9aqA 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%0D9<|x*c@725^J7q9!?nXQol(t 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%0DLMoy~mpQ1k!~M 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ⅅ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

0#7yr zdbS(6R(p7fB;*%;@LvT4Wj`3t*9$LUhwAu!p%7C12!<+7e zk2-*;QHof83hx&~c0UlOW$?rmArmcn-5g`w1wHBjkJKCeX@mQx;qMmkfA!%Blz68R zTGSZt^?=6r`oC4T!M6rO6W2mVdO%B8!((-aZ8wD`O^3b>!8Ma`t=h;iS|fAdqrEQN zpGJoL629aop4-u~%g~1${B3|HWQ5A%jRN1Qf-&!fdyPb!YNLfM;T;;`Dja0$De&r# z;B)>$AD*J0H=qsQ@!f;a?<;s^A0%)B^1OonKSz96j%?~0Jmyq9KMWiE7aF-2RyYx3 z^dICMhCW*0C2Bz84(Mi2+_58i(I0xz7M3J{{J+7kagmSR!CfCfpKsuLYw&w7avB$U zaSqopAm+b-1y_UBRe>D(K!@eXTlz!J4REdTkZBrx$5gyF4<2I$^lBlpxeL&-YluH< z@b?|gY1sKP#Nm0+wtpbONc6rIcC{lLgp;AZ3C1^q; z_|_@-R30)^E%Lut&WvkO;n1`Tn5|rg zJZ$g*F&Mi<=yMD-t~Oem4F6Oat(<^%Cqom{V3T!_`((ifG=%@j!ZSW66Po%8<8umm z(g}FbLx_A+;L+FPF&F-2BSv{2eCTTATrc29tmu^hex*6Iv>N{Jhb&KFt>8T@wg5=j zPmuRKg*KS6$DV_hTaam71l|wL@j+96z+#gTkAA^_731$P zeC7k*y^r=kgB|FgMeET|5`Eu=sCpU_2*FCOK+idl*3bWAjtekOUvS^n@HqF8aYaMV z;()Yw=l|X?3s-!L|DW*JPk81hxZbD#=awZH(YNrvCOkf(4HSCiLVl@%x6Q}h3*e~#`v3K@4y2ii@vQ*+xdJWnKm-5aN}ti5%NV=8=;`raan~w%w+g-^!sjcXO)glP0@|a-Yh~H| zC5*@)^g0h3UJG)p35hjBPg|gWUBeRgVj6;soBQykdSp&0mI;6n-pakoR4Y2kYi@AKC&@fnOeOTsU zeD5yCBMqY{haR_kt(bga~{J)_)l`xeT^{3K4Ju=6-izCp+*;*}Qo+e8vgH#)bIS75J8m_*B`v z={7XuBwAApog|?>wIPKv9cm0e+z-+oi5T7pGOmlhN5h&Ji39+&^MsfsJjW5ANy@u@1!!KPx6qv_(g*Ck# z%+h{gwelzMA=(3EO^*{30ItLM|ei75)5%XKL74Md+s=@izsw6%L;~2>0p;y_G`} z?a|k0XliqOQ;1BkY;OM+{{20keMc0&i#|WWJ=Snp8Wts^{$2wtxk`PFC0@i26% z%sXyD9C`@<_yoH16mreN^9N{20QRcDI#MiVS5esC&|^$YnAaELe_2-k6fM4pE6F(C zP{jYWB@K4xM6TQkziDJY?eJ=K^sp?;%7lIWh0T7zmCxXN-yzE*xbIa+tN=Yd1wAf{ zJr|+pQskcB(c={KDH*ZLhAf~N+Q49@+5;M`#rqEAa2j}%vbDxK7<~;s*#w%(KMxb7&f&F_j(8^K0-^f(Jl#kn1U-%@Hw&Y5yLSu7`pkrv?kVX%cr_{5)(LoCKN75>YMXDwi3Y4A~Me5Nw4sQ&-_UWE1)p`~?U zPc86jV_dfv{9HfyqAJkZvG7Ih@t6+lYX)un7gB46PgaKv+u<(J$Sg}B_ivDM4&-Eq zrfT4`GcfK?k%ubaSGZGxd>cYi;?l6@HPM9F%CIU80#8~QgIeVhWXLcyaXB0I^1gtP|IF-N(A=Uwq^3iPWSbo~$F zXA$zGQ|RADc=}qQxKLfpHDW^L5eXFV`%ST4&Svby=mypPiS8?Xs!wU&&Ie$A@BJNP2(Z2DYHNi`t}~< z@*8sg2|X?wlb6VC24XZ&WrlZ-VGLj5=T+D=4V`mi+5zn6pjx}nc);r+|vpcZ#6 z)1@-YKl%SSaSryEj{Jqjt2WHa8=);_dbb#D?gQlkmbgSdsAgx$Yer~{aT$8zie4q%z3+g;b^|-aF;im75o04BUu>|pF2&SRgjSFmGPgPo81lb@(k zJHyJMNm8&82oP^JBf4*ZjrT<)Yll|u!*)(Ade{qlA8JxYCSnhJHFj8+ko!?#)t_pF zO1)atU{qR-1}{!G)Cp~&mQw#<=XDg-htg6WGLmXeRR(%%BWfJA4xd|!Dw#J_HbrwI zxN+RJsBEqY?yo3r0$0ZUf*smD)F8@^o!K7PMVSqR;8E}!`$7?@0%TClRuY_q3d-(* zm;O2aR^WfA?bCa|cz1gfyy+gUw}Lm%^UOngyLv}~cVvS1uD6c0lw(O=&EsDn7@Y3!-#>FcTExd(jt8}4Wiaik}MP0>gV@aA>e7_>&og-8m)pn0mk)_Q0La zg+?M`E0AgR#xCP!*za&ofYTm3GWU_kEXN*s9qi+7MHNZ~Aeg)(lE{SXLmxq;zxcj!D~TK&{X(DSc4u(Z=~PQ5>c8+B=Q5RDqM7n zZbJVOZUM4mOTkS3UtTNTbnY0cJ2{m&$vGMt&$eWSqPFxlSfyur3q8|3N8EeCO83`! z!&%jN-*L?G++lLq93DqY=Ow4e)z&p1Oq|s{XTf;a!#m5@9#tW?{r!Q%Yyf)BXQngu z%4P7Wm;S#>RS)_8JMd}8pgQXw^gRJc@m3Cr-OmQ7n@PrQvjDr!^--}h3j5zL!GEy^ zbr%Zk|5@;}KDiCOKT94$wNetb0M$54DGPNC>^9-NabVp%ib}yzf=pB$I-R?1#!e1y&)aqr#{QksrB3t_aJu(H=L(K{mKMXvP1|(!W}{% z>LUk<#A1b*6zjw!Ftf72yLL)iP4-r5l}wUoC7Z=BMYL!#PCt0~O?gS&*3?Z@$=6^s z!OMZMey1nP_1-CPeg>!fPD@owJ5aA{LRS$gSirvLUeTxztH=B-xrQh6m3;B?5&}Z^3zh zSVnSQV-Lo{c0vRxTV*W&|2mU5Seva*+(q4UFLFH@f%>!p)bFgLGO5Gd=Dc}e5I6Il z@;8I|Bug+}IFX(piWh$npOSQ!UXUisUdyV=JIYVUL-I}XkStt&O;%rePwW#d64j&$ zK^o6P%^*jDQOg+!^Dp(<-9G0tdqrDKYpJ=B$!?fvsAM=(npWDN^hoKiQn4Z5P{Y*P z+`_WbS_tM6rE3n(o=o;t!WoKM>_Dvf4o6S>gLm>Uw?1z>Z!ND6Zvr@SZ*mpfzo@KB z0e6apXiuy`{_+L0r6RaBIRz@b&3X#jr=>q-WkSo9kM}UFXL3QJ< z<^Idv#I-`NrM$_!0$yAGdj2(j3aYa1;kzOQ_P|{vtAe@IYS2;L!HCAy&F;L-= z-;-y_=1FHu&WhKHq;xYuBVK)~JfRMWm=pdaZ$Edy8SeOE{cbLAx@S0F`dTm3zb+|z z}#%Xb=dkjbj~O4)*5VZ*u9JnlPG3}b3f@#RChe!gWRAC5?d*bf@H3soNMdZ7?sg{&qZe|LgtYdm5O@zTkW#E|Lql z>-pJ&3Bm(HEp0?CD2J{l;`J*c1~2jRW8O3;lk7 zUZ7L3Yj9U^2J?!wg=TX~IE93V+=fck1E?{t!Rx`x+a zNe#v8v0yXp&95aaq3enz((_v`4_~QXtSSXF(p|-2=|pjRkq9Rz zQ>l-kRHj9sp_lKNXHGELj9*II7TXH`E7(_1H$RkHC3jce{rm=nilQVS^c^+&&0foH zTSM0rUuS6bDNZ4EmtRTf5Y88^lIW!?Wf?M_ytX`2ZjzRY+lv0B51=ZzD|a?|iP*}i z!JZ4$^KbFp_D%(sXSjQ_yPPM%^VZ$mbJMFuh3{$q&pSHe|MI01)Bgg00lh(iU)PNFf%rJhosxMTUpg(JjsWJAJUC>y9m;e&Aw z+Zr)V(^0FAIT*b$>W!vf#3`JDp%FmU6ri-Cch&oq^}G9>)ckSS z2W$n-Szvg*3PDmHTFR`H@wWg+i?##(rdgud^5rKm>sAe@cHWp!`XPu zn`~@bqG)7!gs!;S$ z?TeThog5!sEkqNzGXJ|ZO?L=2dec*&^s5Qs9uk=On zGhO>XpMDg68~fS)f&RGW^NRGxKXsX({_M=o&0U;7s;FOSQ){8;OlUu^4;>Vbl1)}r zQ&vn{8e{uBZJ zZ)ke@*9Yl~e%H(0Rq(v9M$w`YyP=(#weX#n0>8Ll#7|}0!SwZBIa2kH>K=G855~yj ziFh{lNK8poes~}Z7@~41_)+@HPDxseRdiF{Yt9;G6y^cZ!R>)VI1xnoceu;j7n&y< z`xy3@HZDyleWw@ee-`g7T(3KpyDzs#-gMoqf@Z)M%r;-Mwzb!Ern_0+aONRVL6{|f z9bOdKQnNX#omLq$Ip$vMvV`5qp>pSvX9BoK8~rAtr^>JJD2^*{tB$}Hx2hECFUow$ zOkN6g6gc=@!Pe18Se`ErRdth=o`&`MQAKv$U){mNsYN$(EB!d}`SF)?->Q8*|E1se z2HDe!ngZD`rDUt|n{BTv$*&`?$j@uXB`OmuCdiX+mb+d)Hf2VtC#`+;q-qx`buS+u z-%ry^d0#3N2kBeFJvjn2*8L)t{`^T>EqZIB(GyRq_Bu2#a&xOs^U%Kej+ueGb&$R6@`1|{yQ4xc^R zQOkDR^s98Ger~Z{mykX3@0Ps1MMLx(4I#76Hs3zdrm-ZM)|-FWE4asaGQ2ASW0_ze z=*e^*b`E#%_m=o?FrSGi!5N(PCV?L>6J3;ike^ZIXs*S@Cb<&o#|R^{RLhkw!q$Ze zlqYcFwYO}&a54Fex$ST6yYAZ>OeQ+y=$!8Pq#POmQsTPEFRXW3H*(srzyNX@zZehMraU{;7j72yl}%LgB97n;YYR<3b+UYi=&RrZe;Zh6JiLayzTj&R2b=m>&sgsj z|0gzv%cEyY-b*Wsck#B8?c%N#*RX}m!XV?X?Wg<={Zj%8b}xBTI7Fsa^@{jc{RpTK z3uSA-oS>FelpYlQOAQS)bLvg)OFrptWjD{Ro7*(+OLm9M{NL8>Z91n;S~S5-`)ZJK z;bjS{>=#upW?Ia%*pkFa6|PsxPt8j{8J`y8h}a{K7Uq+NP|e_b_gcHb(%bkAc!ze~ zs{D#YJJ9#f#>=)mcW2CEV}N%sf>=)urb6J|>c)$s%%O?F?!M9PVUBm!WJ|VLW1V5U zY+GQ9v$u2J@{9~nq3zTXnp7MN&(uUkv(aa@t`Wf|I^F1qbSYG+UgQdfqOz5@fj&hzRHa@p}c51`avlV0& zM^~1oEleGg7#`hAbwk{MDi_Fg7Fg?>j+IO-99AIA*XR0j{JE71*6Mv`fup;}8+=IG z`Gvymz-Cg4YlxI|7hxa1k!%yn_jh#lu=v^jr~ZdTsM+{wD*1-XU6;?IVY)_iw1vx;&G>VO}!S=imMql(M2#*$lt zdgKvyNFc&}9k`f#i&x~|%*)H|l)EyoTH(6VJ677Wfr;ThrJu`K)kV#dm>!88%9~Pl zrraoZFX32p7nM$YoOh993H0{#byTzTDScSDTUSL_TF^}2&}?<+e5ssb{uHSkmKjVm zr}nM3x>i!{?5barb2LqqG13A2av{5CpZQcimHFuFsdwjIReY85_S@%eKUZfrEF56S zv&H$_kg=jws_dAT$q!QNrR7)ISS48TcG8CEvFiWiCq&zM*Fr13eQX;`j~7hOOV7QK z*QP*G(!|)?HrGx0QO}R?dO=**vL}&PGi9-RW^R&5rY2D)e1tq#M zc_(si=PcI+3J>b5TE@BeF&W$yl3%KuQT1cL#TCRKPxz70GhuYxaIGa$sEU_N;HC#Z zy2@LVj7#*pN(Sj;O0)Er_0LOfrYR1N|2OBWKq@<^l0-d@y_2}0{N2=^l@qEota7yC z`FKg#O|Fj_<4bT}HS0>&=H>o2rceBQ=EJd%L%wbO-Bx$MRAbNezF|e&iS%9BO!ckk z=JBe;JBjxbdneqFNr>nxox^L%xg1RNU-y=}w>i#Q<{CNR)2wfPX>++>2la%Oe_lA1 z4$>K-2OSN5@*orZKqLL87<|-CQ{}t61Y@_(0VqegC2h7b0Mxk++>4SB!^OU>T zGu$^WFpEv0&Ir#-*M`+p_lxMQu}6*2rpK&^>z2?f@m9h^?H9R)DD+kJ$XvJW!>p~0 zZHw64n7{RZXZF*ROMtasli`?V}bMj&fcxg z>9%Z3yt#p4R7u6+N+oR#`z?zczg){a0{<%J8|O476nI7JWNPJ^@NU(of^6PQ?hA4>XE~GRALC~2pRAp%H*8CtA3e|gbAykA({a}NRVa#kPB=@FF1w=` zsS-xyL=K56r~NNhkuW&%ef+1$wxX2KO8*aUjAx$nm1U~_S$^A`CYg`EfBBxDc|7lW zak{aZJ;mz|`a`ctfuNbRvg%j(w1}gTsZn2}ere*v8Cklp3AYLH59eQ;jM)`R3l8*M z@|3#AxHX>c{^M)`(T+MuT2TSFhBX9c`S`wvz866AKIW`zdxwnehOrGW&+R1#jkD}8 zz`W%26KoG+64%OaPOp@tD+20;k*6cCM_dg5M(paHK3zC zHFvkDY=a&5-HN~*HWT~j$AFx|5zG)1^5bFg>X8xu0XMr=R88%KxVDLXlA;rN(Qm~P zwx4GKFj)@UTbt{aSam0|lYVzeSAU!E^F+>>qCCSaTeSBeBP7g}P#7Ej?hPb zikhl@to@@|tDGi&%zI0WE+fyu8XB-aPV7 zL?QTa7s&UC*77b957}zLfBa2+v%O0^<6QsP!>xUB+V_KXkmI7K8Z(MGgEJZz$avmY z;W){1`8K5?{6EdJsGiZSwVh(mCM-*yU9NkQThpEH8$9ARI3GLiTJM*J6{O^J&zzSo z|1|1z{*Ns=m5aI?2H7IKeVP8m4qhGcTxE03i>S#_y`vk%h~k>ZorpfE>Md!(*JJ-@ z8QGfJK+J`A{_XAW&T#zeSm9O$RQIm-)E zXJd(xwSMr7V6(~Yd`c#b^4`uy!YA8?v< zI?#Qkj`FskMQELF9c26FsOEjiye2pDhVpayN?{(|OLAD=ST#0cr6wt=dsMCHvD#j- zM-xKHSIV7;8>yJadE~z2cxLZrO*2Fk*2x{1RVAbHNA~@bud6ec=+2Z}FfVd#3k>8m z-@lnz3lsZ~Yxq!DNC07yCL#|*yzuME@p|fV&p1Jt` z{=tZ#*gw{@%~{J<&D_Uy+`QPj+$OhGx308K0h^D}ddK#{De(8>RON?@)8u!;S}8w; zoeEo|^r&>|tLmGoA7R^MS@aZci_k@XfA?sx6gRZiu}!yy+p?@tww*SiJ=Ia-YT}<7 zTEJZ_WJRCFXT(3nqowN<#i}L|zao`UZ=!5bpHURqKkjiNFS%d*7}Yg0)$Ov*v1FLM zdQm~k>~_Bs(kFe$ejE8I{--_nR7tUUh-*`z2@xr%EbXCwqs@yiiO-8$8P_>plGrkF zSnTug4U$IO!Pt#yk6O7;OxwV5?=IId+ZeOL?6z)l#rf{~be`_6Qu}kT#$fQVrQJ zzP+wGwrQpxhHJ*I=0=uRmdn-(_OFg%uI`=@{zL3aYPrxSkt-|;TEUU`kPi&As4j=! ziI^RECbE6x`-r8HZKLyI7be)_{-|m0GS@M)39PaIl-$!b{CnqDYPVCzdGY! z&a{#)h@oSdyOdNkU-2RGdi=CA_9@CtxCL<5zlL zx?&t$3vKwKuW9UP>*=2By#wZ>o{j=*mYFt(0q3lNlW?DKhunuf>%1{QX8RiYLk9US zNjKH5$SInm5qs3)L-?o*EVvM-|;F*S6p zN_I@Oy|dbF=}yX95m*ct{ZWB5M$4H@KA_t0M07j($ne#gj+#CZQ`8yCHj0Ik3UqhD zMc!QU2s7Us<&;{t87>zKip~_BFPUOkV>Z~eG3bnS*(Rb9J*Z(Y9ohScW1Q_7> zPfye|mV=aG^X$^y;cg!!pw(?(X#t{^HmF0pFjq6Br!D^)$AEcnpT z*ZSA8*?QIb$l@~ZF$MMYi%u3+(-&KwxLf;=`7cAub*_odY)3WMVDE*%QLLudVB*=v z#1-yNu!#X|MEsAkTjWU1tB6`JNM%dU zn!ngvcvfI0n@fJ@_Y^&rJeREw8y!wY)r{_{`Ki7XwnCmI9VNLgega+_nRu9}nrOUu zvh26g5-~U&+r*US>??s=kd|2@9N%lO$e&?v*J+52aP5Dq0 z9p69YZ(7r;)=JG%h9q{6I~KcB`!;-`M9WEY)-qX2V+{k~k8c|v8@m}E6b~tUR|IZ! zM}NN_xDdAkS-yVWGoFyAoPQQm2?zs6QL&&V^LRet7kV)Eey+&)>LU?*!i$s*6&s|t z>A~Etq29rzej0g=&Pte%8LTCFMZuyZ@I_@>30HTYid|3k;+N1}q`$zC*+|(`t%}Uh z(3(8;H^pLU195x$uAn9u+cpZi3tsbw2%3tn%0envB&jBNcKiC0^~Kxt(@GBI8?sma zVSj(kX#AP?dDYKRg_*WTegQQ`JWRPpGcImQxz?5MRjXI^PU^s%{|01xBlJfN7gyOv$q1o29#LpM1SScTvR@!Ri9N{QG^e{&~TZp`SQ2 zzYsemF+7U?EgCA~i3Ui7VTaVm)V-7!O@LTOsDJMV3_{?CVB{Pf*l9N!P;x*3h|J#kWV@BtSm?c_t%oC-F6GcZv@v;`m>8grh z`=xb6EBRB1R)OwrnKR9C&o;nfGcGZ_Ev10}dDOVqYILsjb!9DN4MBk@MJ5Vkl(ST? zltN`CWtgg;>c23%bT@s3cY?H_9;krxlsHCCpayVX@wy3bU^~JCev@V5fJa$cp?HTr zx^!33)4ZBFU;es(f5=Er7i5X_^*uVyFduyp=TTMl);N0u35y zI%@uFOMsV{%w8e0_;*A;=}E1vESje;DormQonI%#)1MJPUt~8i^$+af zP8O#rb0dGoJWmv*JgGRm^7#sz;xi)VfUn^L?GyYVdHyu>`TT`B)$)vm-wk`L(;U@Z zcbqD_-ul_m(*KND!+*su$34l}!MMQ&I0U-{vpAEgF8py5(|`HG%vKdstUvq zv!bZ5_@!a7VR-TP{B^oPxxTDUzi0pbtMBjok1G{lQ2YqDMEw)DC#kf2T57eFPH~gf z9&s1`8*VC3Ln#=MV@C0e+=sdA3KtstI(B%b`I`EMxTia6Ig@;yIIZ|z!C_u+!Vzrc zPY1po&Fl-EB`RZ=SIVshHs7U^DzZ39E#YzgU*TZ!Cecp*b|Mhm;%)1Yn(c;M6JuS0 z6{2spZg!tT?auakgY$`s;6-0ZCyT2{MRHovR53%&k*=ew2oePSggW7B!3u75Vmqq~ zjt}l-DsoKZC2k$wVxC5DQglN8JG`;BdXz;p#mz0*U0Ai$ZyZqitMEepY2Bq9dseSM zuL@2&b`YQG*Rl?(s*wt9+xVABL&}dS|0O;);-F+5ZzO2~dUr51$~(&RA)m-QR4~gB z?@0Gf4}1=s_LaD5IDfgL86!1MFo@rb9LBu#T>>tgFObEyCrB!py9RhKdB}-p6U07MBP|gcKsaX|MCqbX2F83sX zulX)r%X@>9VHI$&dTJocwxGC!u6x1Z(h2sezR$sJj3H3bt3l3k-9MG+#J|jMPPJoM zAJ;qEyTQMYIm;1K@40t)5&XV_SdkRj!xixgK@DDOzDHo;zad96i@d4MdzO%)x8Wai zZ~J;@9&l^AxbJ{zqYi5)NWPTbBoc~ei04Z#NlT%kw(T^O3_YvHVFy=}Z!`pj_FavxZ@V)rrkU2l5e3A=^h^0tE?!zShc z^K_G>iu54({5pUU;X4q--jLrYFLi=!igk{W%ta<5bdZ?N-Nm0SXej6`5Yrna6T=!u zTBBP;^ySyJ?<H0dGTO}s0ZT(UXuJT)kE z*#FUU%d^wBH25I&fSklzDA+7)L2nbylVnKqC35-_w+IZL&AIo9*}=>1So;NYHfk>p z8mD;2Qf6kxmux3+SU_R(w^wNc>ftAn7X}LO9&~dhaq{$s^p2WCiw;uZd@w=cO;6xyboMjpDZzwx#WK zxcHo8n^Y?)5{#ox6SK)Xq&;-XU&mDrGt&%1n&FG-lb!H<^se-A0=Jn*oD`}P?>msq z_lfVJK5HW?whBe1^do@^81Ht}kgg<}qJr@$@T+g2DsKeajgv;@@s0?>g&ze`LV@V7 zw1#TA=6=-XuwdxA>1M&~qBn*#(?fl7Q9|LQg1)+Qd2@?M`zVe_^g!N5nXYaUnGvOl zX%yQtc5(DKrGSniZUkQhw*awnNzm-9YphiKqokrG!#$f}QK$Bfz2JZ7&U87wqu5e1 zn@T0N1($g9-G0v-{~R`sl=GGdCei0am&6YwW2MnjpXfLL3CVI=6JCyq8R%VT?_@R@ zK9qzr!a%TfsWPCVIX!q$H!J#}r167M3{Ti+&fFOReUs##hBP z3Lh8j$?vQ?Rq&7Lh<^?Lwxp+Aq6`cF5SfN`iE-NdQPtIb#IclvxfqmyH)2dE(YM`t zOE1#TG?#bRV@?r6$Znj?!AjoC*v&Z~ILav@+Y&-%uQ$p)-tF+73$Eh4r`GZFg-9q;eF1+2zuslPm6cUx`5^P zW@M%48QR6#{?W$pJ<@Ehg|!F$>_g%z$LBv~A7wbImzi6*Dh7e-O_YS%20wXQg z(ekX+U3jWsZ9&6)NxrP4s$(E$HQh zSi{inist^MF(%d#ALtA|>8eBu8}PMruX9cEQp^;b?aN|xzW44(cd4gVpoE=3?&h5o zHWOcz_$BAT*0DpplwQG`LafKhig}@?!C~$@;M`aSw9ek<2ewykSs*{Snc-n9hjSP3 z&k5cOV}N$sO7xEIM$Zz)3o7&6WD>D1va$Aw>weiw5yqBFm?{bS+Z!U4u(7Q#ps^~fKd|3TMWH?Q!l`A*;j|AV-S zY+_i`@PnGH=rnD;XiH=hUYdDR`e?zyuSF9Zj0;9{8;`6X3V1>nnIt8kE z(ts^Z2E@QY9?$ObJKbYkXWc)1XP8dJRPI8-5s_Fbm0gwYmMjyCMTEdg*2Ia#Q0O=l z=FPA@F?BG`HGVd|w61lv0XO(w<~v(~_(RG0?F0>lJLm(AYpL?AY5}-*GVPU8pmA*&lLua?N$0@oi!{5VN@x z1!F}IB@d-5rRkDdz=a$wNGFT1$1sA^hkfInU~dLgs0##6gW>$Zd_s=eP!ahqlmsL}5&RsV`YN?hl> z`k*`%K+Qp2&q&u$_cq^VW<1tZ_6v51;-n*Gqhzn7F3Bh{DMZ~PrypA0hV9_{U@tZO zG?W-n|6s{<_`J=6wV7<@TxbqipZ5*@=F&Yx^F>c-yRe>cpx_U04>gx)1VorP&IzI= zb%|O^*5-6)Te5eNx2&Uj@=o!)3Kk)fg^933gc(+Ym_n^TEIf3~xVhkLaRw@HtCft< zb;zrx>x=#A?2@aF`<#QcAFJ%ERhJ_D(M@A$ZH{Je_zl@D{u$0Y<~X~NC?NVXA6%Qw zI}ARv*8Mk_M%0C0*%fT-{p_seq&;&3bJ-?r@4!Zn*fqzs(z`dfk0anp1T{qEq(QLn zER#h^cZnnEXkH?+O9M_ceDP0l_BBflg{9ZP-?YqL(=#N{1X)N#Xb>@%D-}cv_X?NL zHAMA9m*}g)(}ErRr`&~PHcp|CoJ837HY%A4CnmD(fV0?-vxgjqocJg|$R8%~2$Lmm z6ssc~T4U^v$fx{9_HBi;3uVSNmM(@C1%30Ry1}}Ox<*CG7I9D^5KC&vPKH^+w?r+{ zzK*`7X&g~MY%V>7Y{8Z?cR6RFpYgtlw&~!1IPI|en_yLN98sMu^xbq_ax{0%@{MD* zvAcsoZ?UU|Yl){@pj+rSsmH2eCCLz3CwZ9svGj*{G2Mv2iaZ=jW~VSG0zEt%Y#GMu zrTqOnCqdI0aNTl6U#*OCrtRjNc1S z<#x?&lJ_3WnM(}|JaOE?;;YiOiW%w?np;{rCOxWdWII)B$zJZfked;Lwel?SDp1+k zz}(B!#eUScEHs!*C*C7>!CJoKile4ybKowjEFyd-Txrg?u8Y2kYz1;WZ@Dl_d{@?7 z(Nm$6^Q6Z`TLr(VTO0}a7&3yu8MV(hJt}=uN|_c~*E)B22M4wX?=Uw*bx~Q zp%2ry=mqo$`ls-upe278w=#JV=cuwnal{Vt8o7*^5bDh^!CP!s!cM9AO$GM^&4jb* zkhs3$Zg@*=dfd_&M!qd@S3fTQMDaCKZBu+vOm6R-KDoNw(fRF5-7XF#70;Lcl=oCy zH66gpvL<>%o znz)ATrR|KPEiD}LJU9LFU<}hLQojb@s9itIT5I41#^VLoCO znaK=r|7XcFHni^bOl3Qf_eddUP2in-tV8ao;J)ZzifsP0uh8|}amv}u8wgG&++4GO z1`lzh;;P~UP?8sm9}C;@_#_aU*aeKxKhepxWE#YV&c=AlOZ#_s6MuT(N^lh$MqHuF z@ox!+2#au@B${qVPp9t-X9+TSk0~AS^L(6W@+2^L=a6E~Q($1WMeTGoN(%h!MyL}% zhur?CY_7URbiKG>%$KlIrl(<7eq=FY`fkoFex0ky$|m5@mU)D6y4B`6&n^Wb@XrwIPjsuW2Uy$l zDNu&5hU09!2NZbppWFwi`zLgG% zIn9t?y2)70(%;_CO(0i25gZ5p=y}}6SW{Un45Q!EENuX0zg2ivaGqC->P)P}3AFn} z4r=xv5_3acn8(4^te&%k>dza-|I6=*^9e6SR@rxTC^{y7cWiZKH}*fn`26ujHBJ4@ zhl&^G-p{_4b27Jo{%d`vvm!}|S4ca_*Qi!$HpHBYRcJ>=Zc(j~#`70(LX3@l3e0GK zaHdOau^Zo8)4Z?PnHcX!q2GSH>yJIgalzd@aF$sdqxK)`T2!=jSCUUM&-t3cgX3SYt{ADn;dq|P*HcOS2jp>M)M%X z6WdqYF>)(U0@M$`0$!^C~&M#VlRjgL3Q>YtCiYw9vBO}z;WtRmdIIA%(^ogiV?m~{f)zZLp z-O757vGvGt9+HK1#RFu!6kWqw zDFm{k;*jtq?=xA9vrUhg9A5<|V~#PdHrzGZ%}RT9_dcJ^zb8b=2o%(lOFcvPmj_lq$A$ z+zIV~$n~lnGMiumxfhr)y>JRq%kbQ9Exk-Dt*EABzvIl?6;|i_$F<%5(oVSt_%nmP zz%_4Y*JsB_*Dl|7_B&b1n<|_wejzJSB!)edufaaf8X>{^L^R~g4((ya`_?;_n^TR9 zA#p0Pr;+?$S!36d&hb514kMffEd-x8)LD60rS1pKs5jQcuW#k(!ZS7p3D>!fXZSJK% zmD~95@$8h`X1YDa3fpETQ(zaHq!*OeG~uz`<92E*MIKg7k$0n)Pz0xKD1)<&kg!cW zg;u_Ky>+CgDJumo^1YBa(AG^m{sHIQ_Prn9Uq>|7UGt9${PRO$jY0JCj~E#~0)3ZQo@7?W*mc z2GsuL9=l_;eX#SE_X(pWEApNSgyPk*^9qOJvHY@hg8060JnuRAfk+{~v2lKlvp)Rf z8e?PdE}gY!xK;iTaH>OrVzvia8MWA(1OtVUbb0!faHnvsu&yvw(1ACdI!qeLbCj67 z0;d6sLLFHjQ!O-sxJV7>&F4!6+XP;rPW+$ZeZ=>ew1mYm-=&qk?jlznUEJOzGZy4u z`P=dD#_XJ&*ZF@8O79`=F7b6~zG8VqfmQ-8==t`hEgy z{95xKM{3|EXALqZRdAnsioLdNsiV9%I(R-<PdN|DpgMz92ojA1>Nqqzd)je+Q|8aB`&{bsHvQOLfctRjbaCdiicbA}p z!{9y)KDhhf?k>R{26qS&5{TQC_S3)KU#xlWtu-$sx!q@Ek2Q3KdvVd%5>y?aOSNmnv5@e~~Xt zqJC5w>fV>v*nLO}(pwt$)LXt+?z{M{$7nCiPF#+#$oj}}(XV>IjDTI}){fiTS?clz zo>n{=XOxvoxNqg;X5GvDl-V_BmV1(PO!=v>s-hJ$>mb!JL0n>qv2L**u&UMsTQU-h z{~V*(TG}rg7_oX~c=X-3V*!uqa!AR}D_P zoYd>rXWW*rf38SO`0_CE)7MQ&d(uDp-g0{!4gIDEh8EZwSu1u%Y`>^#1$&1qca0UV z5I-gdsXV{T#fp%BBeQL0m%KEYF*lIjj9p*oE1G{JH^N;}zOQdER;c};5Gv|oS{F?4b%w{NyttmDMy!UbeFbrPxwQ@DA|7b97JW?V40 zlBV1%UJ*`<7cF7-%YM~D&qVx;85Fh4zkzx>H6iKUualXRGnV~4n^-;3^|i^jxSxI* zQ>DrL6USeEor07CaZznzU&YjqtP{RG_=Qsx7m)REt@0zg=?HmB-lfd5nKAkMl?LW! zGKHzAzwjN;Z;^M<{Y|cFtThIxfxgae#eH2ms#hcn_{U;D+j8eezeNAy0d4&wT!yWY zI3647ciB43K5ep(%Ws==AZvPNWLAURd7e6QX|<)gADK}j%g=X<@JUmt_N3=0^VKL;FRB#mQ5pT=ZG>?u$( z_>;4lm_(A{(6p2Zq=GyzuXEPlSy%EuC|~JiW|MhJyX^Di-_9eRWI0LC)f1FF?+f== z&w06mVIe$tl4n+%bGhFu{}KVe{c1TI+5Qwa@eckcu!)j zeCdbWN~x$8#@6jL@`x{DDTyoqpCuYO3;!WM?uV@dZm1_5!l#1g|9@qz=gA;*;wuFzLJ%aQ7$Lj8xC|k z-0Y@*khZyp=k?3~*O#C@H?C=qrQ@Dqp4n2EUWlp2-4m8rvmJZ=uK4=}EcGAYs$#cT zeq*D*AHReo=tK_nRLpbabjzxeH96-|{#EZuX{2n*57mE+@61xZfn_T8yB}i{cD_Br z(bgf^uiLg-QiQE=@c$@0=6ABP=|v*fZex-8hWyRl;**3xi`yFMJQHvstU}b<*zkx( z_FEp|*R-EEQ)^}>{GR%4^5-I7S|`%4AAeTLs;u4?<~sKIiR zkXo+&VkdSxlLnW9r#kPOnImQN&1&y1r|zO2vlFeZF7S5F|DJcvvrhS`57jFvr@eO1 z2Jb@UiP4RCxH4jGAUBu%&*MxFb}g{)u+Fet6rTyRxCJJ!UGSylN9K;gzI7DR+u}Uo zz8qg;>6Cm(YiNFDUkNQOH!NMOrEF#FLmkDOj~)B%eztfZEf2&Tv4yytze$|v^V~*L zvn&uT_fh%k9kg~1pRE4dM+dE_wld!p8~!VN^JG@eNwH= zHOfw5nd78itB~-B{Fs-8N5uAu8dpdTec-pkat}yOZQ>%;jGDg6InE3=Ga+xG9ANCB z)s2qIJI~#`IeC{npOnhR34NW?%=g~&m#?21PY)71A0h_X?l_104?zNPc~?byQ)^Sp zE3um#}Yeya5l` z-+3RYK89EW%x(nU@gw|k@{HP$!(%nik}5nAKZ%nqKdeU_9RnVRZH#&q8(Cwuw)_-v4<%YlOC5obC95DXn(|iQQ2+HKtV5%tC8Jqx?cF z%h)KANyd;5`g-r(?7)m=nH}>N$VhB3K5O@+wQgT-fxHc#mC9iKoW?1gkxYv!rj0QB zvpxA}+|*}WXK_={@!#*bZ1uCs@Fj4H&AHoVKfSH|(A_(?d-jbiHm6VC4fiE)H=oT{ zN7{nDABDAwbuHg5RK9E58CEBhXg(;&@ei_aBiXX)_wW6lS6*)PL0o4ky0vg>Zd24k9?{Bwa8DJX{t9x zx1+kh5)vPg64S2mu$Y~Z^$UFrJ>fUj(v2O4grYdo+?eUxn6ohBd}ibPmU2~ng;7YK zBFB4{VcT`Rr<3B)MLQu+-%_jaD@T3&dy^2r^CiYy^S4IsD^cB{>9{; zKeJOKGQK9C{?hFe`BE}*&Q~evV5}{l1SI#@Gq0Zv=k=BK9DS2X_lS{st(l>p1s^xpcrxu@Xe17?5-nZK+JM&V_MyT64 zLDvd4j0uYCS$J{ufyi}*N``iKH5EU@dEy*-WacRQ^3P>{Nbi>&?o8ZH=>bu^i_2 zVh7tIEp&IzJDoEn$CcOC-P&6OX^%s^VY1WYO>_w?U!Z5yyy)w}w`q}#QAszGqtg4O z7XP;B!P0^3FF9q|`uC1;?gcyHEaGr3B_ zzv52oVJI@DhE9uo5&OH~7hzb=(Ih45Zd!JFjikRn&i!!p)3?v<6Z`)tn6XnSBc#}8 z29ykc5xq6;M4TAgCVEqpTJV0*EqgzH2Y%y0l5I{`-W>(k?NLwsAYi9Jqsc;&qG zoZh*So&?#YjaJLZO?-WQW#zGIvfjm9NesT3eU|_IpmM=S0*<BE=<>>;kOu-Nj`_Q^5X)yA)`-#k}E zs8+o8qK<#)={RN!HBR^0p|O$#rK@kHN=*cejFIZDVu zO3z3#!YJ(`^`zvM%ANtuUHhEc`J88|yK8=1 zHzWPfS~4OWJ01!ln3O6wt$rQ-NBJ-CE9#1N<~p(*?HpC?XRS5Fr|dPeBYj5~G7Z`5 z++d-e<*==n>qyY{f<2?>gjW~$=FLc1mb@)3BW?VTSs(pA&iuUS%ZbDxKQ5*Jk{9uB z?G*$6DmW(gd65xC{0j@QGh+ru^a;LT@5?6>%oofR&EIn_b8~89T2#&wk5|f*+esxn zz4QLdnV9SFER^@DUL{3J_MY@UlP;(YjMipLQdHROI20I%gvLsNOa06Euk$v@5m;c^ut1l7JhZxR2r-bLk_4Bn)v{}8`W zS3_qZr{);o;O*}%cEL;ffk*Jt{^+%9bG0y8c`Q2|*8)-s%#Q9G#RT-#9O)l_PW!zs zJw9dQm*XGUPp!UeP8{;hmvS%vlUc^v*6(!a`lvQV4ip_8S1Gnq%>2m6uqm!v!VTm^ zAsf>uFV)Ezopv`hJS)bXAT3aa%UivF=6}Za{i?jT-uLoBxw>zbXTSF_LWzgzdyJmU zE#awiQ_$v6E0p0(P|<)}ewAE{9Dmu?ifQbAI#RjoewE!jBRsuo#?7p)xs&rp=V#`P zboY~H=!C4`cJo1EOY3QSYu5^-Hnt3i_FwI~>fGUM>wM_o9JMj29Amd4U#p@CcV2Ry zZOcc9%dEE@B?C5u^^06lD91HTOUM|S^5^g6sjrhdes2EZ?MG)~^tX)f8-5$^I*e=$ z59nSXA-Z|d!o`lqrC_=k9N9B8##x(x1B87NGf$u8y_5AiRZQ!Ybv6HyuasO~>hC@8 zUXnj2KhQJScTh5Xe|uYa6MTQjZIn9d9zB(5EN*tp4p<$sFl>Kl{oo3L8UAzp0-U9AGWEkuR&uAE{j+fe1xm#&QERi z`(&z=JSg$Phn^q){gjd@e!uy1Z&o{X67T2g8agVnbKH<(e;556N8&ofv?=Hb=wR&& zm!1n|l3reN=e$l6Q$sTXa_f3l`i}Z0c-wmhx&O+)nBM@K#xs3ud~dyryjH2DQeT^7 z5auOU#QrkC9da)0V3>bsli*H)1N;^`hT3-H3Gbpkl`HP=*>ZYn>aeu7Nabd8N9Ik= zzw7BK@6k(x#pHn6ceTAi-~TJ%X5f{;@_{-2oBgJ_Mmpa)S~+&wILjxlCv(U6rI$6z zfqm*pBDkGGRofm{ui!of#X_Y5AF>0y$1@wJ?@t?&yg4!MbIfPY=Nn&V|Ja%`GN-au ziU00A5n3#=Y~iv+Cl&c#_)ctIq!8BGuY@I@J;Nl^+3F_GmF!XJscB4>GjFEni0`#; zqj!qupu3R!thv#}QG2~O|$vT2|okV^W8J_Gh9PgU|98tgy_4mW^8usq3G!FegU=Z$m&I> z`3*bJH{5bgyR3Ivp?TvyUSDCkmUPG4#~b85iPXqtUW@mGC&W9^YmmVJfKYydMyexc$5X z1Q3$iHurGGD^K<*BgCBV6Ki+SyE5}*KS>CbQ7G;Uz1=5vX*T-W{SAcZS zVjS!;zNmGsvv=UKu=)i_=qvkuMpy25U+3*gzxE?N@#ELSUsDnve)IjjoIcl`s-7nA z?eBs&6)X}}AZB#Tujs~+W5cfcN7#=G_t~b*2QAzemKT-vF8yUj`J8?EJ-pX_2YsrK zRUWI?)f}al(oN~2cGoIvxoW(2ON-It41e=I`_ai@S zhf}gQwGXguveXx{*{MvrA?g+2b;syW^egy&sZ0y*7y5atYiFPm8W-LpWTT@4zm3^u zlu*j$9sZs3Ge4#4ugl3<$>Q%z858p>NHw$swyh&Pa9&7uXrZvNVSPfY21YoCTZ6$oPlQk|Wg8IJXiijHyiBDM*ZL2wj_Boob9$ZegD9JzbuE9M<3 z$0hOK#Cf(C&gKCRf*Xak3;F5%k1qy2WR}v?-7|e^GN1DL*Ta;1$(CR3(l%!2yT{0p zX0~OGUr>-EBr#-IXs^)zL8V+DtQExr{7$Y1=>Qb|x$mGi)VE67B%hKQrG@&Jwn|&1 z7E^}7HSvtp-&Y16g6E~3Qf=v|WGDf~SF&^XpeVvf#<+JcRw=s8r?v%XU`KCM1D=KsJ3T7kHj$19Xx0ZFZarN-)>37A|*VWAR!uh}v zXy>iH#qE44_J=u)&Nku<#!&U!aP=z66eG9UP-wDq;W|_?U_fvwxGq$4r3g)+o~dJ6 z)!5uoY4PcYG76<_{q-g#=XX^0C3jojS#>{25a-%DJC?woVKs30an7B#&ej;~6N{g4 z&y3c3D)*I&T1~yB)>kQ{)K>4Q`_v3&og9gqxtF|3S_S8b55BLy%2JSAL#?l;8NV2w zzbTBfXkek**h@R+*yr0aFzw&t&VpA~uqXV^chNJ@-O#-cm|+)B8E*^B%o~jUrjz`` z%@J$ZO#2<^Dfhb4T@zf@T|ur+$Tr_$Z46!`fo(xvnycweW4y5pDV1Je7bTfC>|y?w zXtS+_3gRQSAq)D&LF05EzHLsks&Xs;KwdBR;Jk}joidkaUe4~2U&|Bk`=EVbW?}nu zHQ!hKX3eoRu>OhM%}lYirIlES8*2`Q7JITW1PO$r=?deE-dNwKWvM}G3*`>dTZ_ss zkVHC4ij=YMz-2s$&rru z1m8XHMce{Ed=2FTY8k^#r!fUsKmL<=(AL3G+PNEg#H;wHsB@a*Z+n`xkfowv;YyHM z<|S~$fqEpWT7q5<2!B^}=>vt5R?{Bo#{{~A`UZV=RkU^ycJP1Wv!0E9r-{S4q|sOvJ00N!V+6C?2uGtlh3T;+++o zkxtP$($UPm-nv=L;OB81JDvH6wqVI z&utCGqQX1A2LGIysC`sS^`bo1SaJ<+|i2b@h$#UiKXHwDaZ2 z!_>RlT3V2dVf!OXwhw<$cp>cMd%^RsGIxckPaErFH2f6GOgUbDCHGN8b)MQsi#Fa+ zBvCL{wjB3?KO|PRF0?hUSGF6raQh$j>GsdIkJc1RvUo@+$d6{jNf`4#htcxpJ!Gfa zXp~tT8!#X7mB+B>H}fi_XSjFWm0 zIj@`Knm%kO`xc{4PxW1v>T6HvTqMNBkOpiBe^+o=np+QACj$AXB&ggrvXOp6R@Db$ne)Qh*21&b={!owe-WAF-_`7ub9EzF-p za&IMeIB}WX^-&ttH0^`BQ)vcATsIuV9?0#KSe1d!AQ*VzDJ@H@f~}+x$jAI{TtR|T zF71MI^S?|S5w6SQ;8a}5@PY7g}# zeBzF3x3zxy2fZJ%=LaJXI@*TvXmYbGoVpV<|TOVin7Y#FPx~OMpy^z?w z%?Ia%{HXk<`RCpByf=MIdWdP{O)HT*H^;&XSYnVWYKv zMI+iwMV2C@FFwV06`lJ_-!o~j{7ZhXL~5ns23#Aym__LgoXQ8JH2WUDhtt?x{L}~F zbv%Ln3rg^2q!zRRdy!mzf@-w2S=Wq!ljt3D5;KbwW>2s$IZlWagWz=hOB{rIW4-me zwT-ov<&GFA+66y;0{GUY$ZaV_%CLjk+xR{%^1nQbJB*t&;{5sZ`0d+Uf~+l|&n;$| zX&Gkm6TMsob~n?Ow%58T`{nJ@74IbX;XEmCqdOCM4RKf`6U08M5&SVO^TJPDJQg{ z^cYhWY0%fnM4awzWC1%E`Q)$RWnGNDz=-BXqpq>TD28sC*`F#op6F%SD`p(&Brb@T<6FR%Gjt1e~RU%qn=FCo_H7Q*g-s z3x3k0xVPLtTsF6s?=3Ed2H+V!fg={XrHi;q(BVb;96y7CnQWFeTmI$!+ubz(Y~Jj= zUitIgPu;KG4?N|hE^1}tr#XvV%E#c|o+J(tUh@QQty%maZa-ORwl%V}XX;obN*S&s zVH<3Q{9C#wJ(BH8UAa7Z+?G(8{nm#VMUlW9Vm3fcIe~ngM0nNL!M54o@MPbNoq7try`^!8UM~A~l!UGTdxyZ6D%60@=_rS8nw%lIKQNzB(dfMW$+ykdn zLb!_akc8aQZ$@wJk$gh>?wjS?iki3*zSl|K0^Y7(L7J>~rVgl;DBi9!Yyr5IRu(Gp zHMnCeT#ro)EoEfs^Yje$tNciMCq0n|tAn*x+7s2IJe7OOeo8BK1>CX!!@hSZBg(j` zr|WNwwm^S-vVm~7=Hc<-&zz)(i~+_vqZ1v3JOaU-Wc;fosGBvnaTTb;GVUkm<3xTq zUqpz(@4nHJWJyDn%NBnLv-vVmb8Ln4em|x-+@<50mgZ&J+g!wKBqQ14Trh6jbi5}W z*z4>rezQ0n4BIcuamyc;?P9PH$XA9FZ7(56IKUr-cY0Os1DVXMhyPn^=7cF3XS6i= zn0LCns=KHAkh`t-AKwt)Pv9^|<*)iowv%vIxDKzhPGHwN3cZEJ{C{LQW%NgCQ!Pas z4rINW;+Ci4esHRz)UxU;rJ-6=o2c(Fn$mf+7VT*))644}^)UtmZ~e;1Qeez@Bo$6! zHj!QI1vs|#?POd=y=$kRQ17aL zXm|8E^rAT#31EVmY+Tmk(M^5Q78u9O|40y5gX_W~)q!q7mB_`X)`7OA)oH9zOj`xF z_F76Etsgx`^7)&VineaH7@KU(xB9`scda$mqKbnA2e+Q-M@Q?kwGsMkY%VN8l0pa^ z@YgYm$x?O-KTE7B%3KB#aety)$!F|H;a<&S|J?E$>dp+SV5@DNXW0N(`6OW(FGD#s zkSRw?=>Ng@zLKFDC1`tEmMZ9fx2u!k2Ruewq+g@SNEm8u)KU-2m(^Vc5;qt>W+nY$ z)WociZ%D{z|ChE#qGETZI1_{pvZfZPwow9<^2$1R>lc6ze?c`aR$5 z`1iMF!{NT1f!vER@Lt`3KUK^>=t6y$nyv_Hxb{>}HeO)PeTqDZhvr=D*c{`I*CwuVC+q7G6^sU4K! za$R|$5~#9jA)MlwtSMHo9u}^UAw~?GVH4Ds`Wy2T^O+{-9kh*VmiFH0K>q;F*cmD8 z8T7OftyNN7@JP_KEV=^8&{g4daY#R}b<$_(EAAkcg+J61GQJX$8`We|qq&NuP##DNWmZm&18w#1}aM)}k4h75Jz}j8x!@IcIxI53V zE7^Qz4PC0Y(jv4sTFL*NNoD~{!Ft{lbm55LFFfE`p_(uqs*QFe9hnmAk!k+Gs7zOz zA#fQez?Tq;iE)<6mT=1{v9Q=kILg=NM{^fg3!4Rp@N;lMJV!Nb3yV$EK{0J)t703T3@XoYC;pEE8Stnk%nwH)&^(uXx7Wx`2qYyKATVGL-`5VOs$Cb za34D#|9u5n+|!tac=w}?m&Q(7(5y{Y8fS3Enj*a|ifK#cutT{qd>LVe@KCUe{lxy_ zRQQQq=EAuyxRnoa)wv$*T63Fz6Ap~0aVNwfFU5m*{VZD#&g#pVJ+va-jw-MXTV&7a zeSMSqPJWw6A(Y>M|SCQux8?s1|M9;Pcv-Z;Z z#d_9S8Mwwuju3}Am!{H5@ZUegv@++w-L149Pb(1#=dd-ljRM$uq&0H@yA6qYDZM4| z#CT+ph_o;^6owlcp~C8eyn==Br;kMzNf>h)^WscI^)+JB$Yyx=pJyV>B}P$ALDI(? zIYlX_HP?fT<)~LPFcDWZ=Fsg-dp3_9%^gP?MFrfqMVZ+&*qER%(c9|T+FjiD9@O%- z`dH&0^_d0Hy{+cn@wbE>=$M{xy7Z)mN4s*LtK_{5ffCl!152sQuX|_%7Td63HAyrNTr~1SaE2HMmaK*o6pPKWVSJxj<-CI0ar{a!jsHx z_BY-u1AObXk`|pHH}X|2)3XiEzqmuJ5(nn zxG*@%pXOWgb@~3h!j~6%qPH2uz9A#X8K#Z-0T@(qLRI7)6cI(i!w(bs z39E#C!c)}ni$IbG>Ho-4-XZSy?lZn`YKk$_{D-vX--%9JjJ=<2s$~)Xk;yQA!}+|8 zGDZ5=!@I}1qr4;JwWu%)F#p<+bW#af61Di1>^x>YooHOs-{_{%j{a+OHa6-}`YB^H z{-2x7Kap!R0aL;d^Mk%o9iiNp3n??9fwY;4%tvyD)Wwq3nk(Qn?P{X8)=^(N60@NJ46f&DS$rSN<-SLPnY2^_6N%<(kqR z=?SZ~Ztz*QsGC*z#h4uH5M*SYY~gpaz2HS#TWahrAwXhWtCaiPdacC#W}+kxHJ@6dpUOKEWtt_yNn!kt z?HxRqGHH3T5sGgge}U^q4$;4LKYgv<+GsYB>VH$NL9QZi+xD{?S;U=BHjUh51x3O8GtUy1yZJwi*N8-JG#V~SFr@dsUIZa@y0 z2b_z58}J_a&Ax!Q^#!4j=oKP`)m%Jj3HOat*g2SqDQgw#)Mp?H@6r3MMHbgKIuI#; zr%@p{Lciof$Gwz1B(>O7_85HDn~4p@0>WOd6szLx{B9O7%Ta%0C2|K^q4F=%I%(B3 zm_!(l>2W#}JN9>QTlZv^vFD(P?jbZ2X7C-k6NE9}K?P-#_W3%>!P-YXRqv&j1A6@2 zn2WFDk=)pe;Yby>3%7t>VlD#KcS2um)?-(3o4G)4D0>vh)iLIrnPimG7a#}AuJuzM z`1*RryZ`c(lmfI(<{$8JSZ*0@yB!o`RXZU^oMjL zX^w=M-uxQ=4JuAMVHeUQW+M;gi13)($ow{3`e)1)nkE{Z=`5rlwr1{==j=tc47-EW z1Try894_Ybjks$h5uL#%?V75n)pP@15(C*RZXG|4uZccBSEy#0i0WV3GLc_J7MTrc zeRMQYdW?2Uy{r{C0+4+cqf_;Z_R-jiJ%n9i2}=oa1m6!eb3fxV7HBJ#V(Py}Jh_V6 zkZZ>1r__$xI(@24IV+?JEjD-XxL;GMh$Migse?fxi7uBYeQr4+IwKm3K?4S+FBA^a>A#IXYNH63(wT2Of?Tq65aB-Bit}WG8%zoT<&00jP#+o$B zXrssJ74*wsOqEZLoye!lq-U8$>}NI*Ms@emA9XP-dL`|Xnywbo|1ql4iS&ul8JOg6 zeZ4shC$or9Q#g(^mPXtR>?jPNRrM?CHuX6)6oawr{lhqI45O;qiFNY}EN|iHa?O6o z8Z7K43(S(Vo#8Tk`aQjlo~YJRe#qt3Fq|nr<{`UKEN@@tIAaSJSFj(=-Za&~jybXw zDJ?+HVn_Z3?S=VsCaKPq<~QKYo6dQ$Sv5|#YR9mh9F82sv1BG&h4XNq`KIWYuUYz7 zt_h2{CZs8C0w!pvEC4^1;jj=!-`Q`3IOn)A)T{FESpR-9NQxY-b0U7vb^Y zhue0NR*V+MlyHp|IWJd0I4q15hH>}lUusX~t>RV-=!3M&$_{ilO$>ibO_PvfbR0Q^ z3_BeaE}nfsn``To5M_Z<7%r0o%ui$!{KI_K%a%m2bL-4*v?d)#Cr}6dq4(7GA??kL z8KSf?-(by2Bp&Nw-z@9JQ@ouEVP~=Bxgl&A`4bt$caVv=%4}--qv}s)PO{(mo$yGQ z3$Nny$lD%|yu+gKKRIrk1&R<)AJPHn*IOB5@prahGk_H{Y!6Zr`%Z~u7jZESOfa>~ zLE_JjBwlpUNtj_e;Z{A(WpWwpU{ai!jJqYl*kH`WzqyT{w-|KF@7491x$4%E67w7mXHeoj#ermqN`Tp`W@l^K?g;!7=?S)oD>#ruO9-SjA z#4txOR~KhRTL)nlrrrd7&HqnPHEC^n8wq@6jGB6Pt%H7v2C{9%OSbFwAp11yMBx_U z%nC+9aI4|QcH@ol4{&sUAWFZ?NNh&Wz2BmJk=k~GiDetXU#k@#C;a47P}P?pjqDh^m^I+I zya&6p3$%G)bdJfA?5|eT=a`gzA*``@E!eNN&a{YP9`}IUGJ6^}ZK2Xjo*~slcFslQ zrM*{W%$EZ&xunDQNCaNs<`i&sP#zn6B~^&`Y!DE*Jm4Y zjoB{dKTsqe@=3ndiU=o{Xk^UU$vCcykRZhHbx8p;#5~9}Vo$Rj$ZFaJ9hRX7nmqXr z$#S)jEjO60#U-$p$wlNAS7iE=C*(P)Knj~3j9??fSZq!q_0Son@$>ju93>x_Cg@F8 zk;2?-{)3RsTex$~R-=q|Q8_EuR8Ff$w54Eh8qwdTgYC+n6R%j0*{a&ymQmt0z7t2- zIZR>n-pA121HaKWX@3AW5sfNHMXJv(L8jeovJz>#L8eK&Bbl@`I^gl_Rqh*Co)dvS zEd%P^f}CXkV=J>QnH73~vfj7SQ^s?}`$Gy;8O)t$4F+>>NAv+-IgdEYHUoKQyZA_R zHTuGD-WNWf{2UHyH<7(Ll@6x;={uv29;g|Z!5RQ(n{HlVW|Mrfgli!F10HpX^|avR zDv-DMc}^Om!G1gdSCKA#SDt9iz(dxf!9eQvv#0n#@jUp3WMDb%Enh?d_v-*Sn=He8 zGS6&I1N8SwV|gu@p6cpgy^i^o1ae=vYuKjySF~Z{E>OHLToZnZpTX{TBV*`z?IGL+ zni~UY9t}gfYdEuk8G+QtTR=!AfKU9z&f#9NWyu_~EL~*`0)lrEU49~)#=d6%W!%}Pk9686lVs@}HFsfyohbgY_@P)bm%A1#WJO7PmqrB6o!fHY~GBs=A)?R}Ro7G}t zp&t8-w$Wb7{g4UzLir4SdY8VD9zv4tdh-H$&U@&cI+>|t3%3+=8H0?_K30HRvYV3;$VwZUYY1%ShM%(Tf9NKms*br3c(st|HflQ`uf50}e$S%-PH} z@&$Zz2DW?y9K=5dZZ->F-LJzR$wl{lQf*|L}K7Hw-K4N zKj>E)jZU^F9bv4{tLmMM%9!rzW9F_4-GIs;;BRm(*ojO-RPJB&lDQksV}C<`@Ehkl z9ux3zAP5{V;b8pvg1^mXmNl~Ae zgo#X5HQl=rT8Z!YQ_x!w%JeE)lF=W|B4__Ue|ab~k!{X5R8DF&jjKS|J#gN;L-yjcEY5!C$_jgXZMk zvQ^+;6@>SGv#|v3%zx8M^ffKbTwo9KFTo;>Kpz;4&!I9VmP`N3#0Gm*mexkqYe2q{ zw{ZE*W8G{kb`{W^aNyYwxf?=h=o-3~hfTng zTb>Tl`>3VmO}_smUb{y>u`l6j_raQKy=>iKT?BlHAI4_={a;^LSnG3h&T0w8f`4opMkv zoG~{-{N-_x;0n~d)M*Y*wH7>#5030cNIJz+7 z*mc|!Ao9&v2iXJ_*K$mvTY%aB4P5v)`<2fU)(iE32yWmWp=&6L1jS~U!U=f7Rc1%@ zW8J{LJi=X?LwA{n7?ZgLG%?D2VQygvNyP0dBa?YC>eemR$-e>;8-;l)hM5nY&=+no z>Tw2N34K&A%MfuoUk{c0FLN}$elZKcg0zRZ#Vn0V_1yGlZ*U9wwfrEyxKJHg&?UqH z{7UjK9jfQ!sXwASnGGZoJ|7!df3`4OBv{m~y(F1!$34J3u>i`pBVg>OGS%7M+(rI^ zxW)28Y$0@DGifMr>3z(fU`c)vk-2Rg)hepZFm(nPDRd(CvePZ5^`*5a_SYM6k(hxl z2yXE&@t%;!M{p%bsF|)$(*9LNZL}U^Waw#HQFXX7PsR2EJa{T{3H)DRnVw^U*lj++ z_I5Cx$&6xef*q(uBeaeRk()?+WnJy8_cU6-mn(_r<^cVka$X%sO{m_o$$QktT|gFt z^#OR->p`6~U$yH8P>ZJOz0^=OQS;HdYyi{`<=G476=RL@&Ny%Q(fV`*^kc+~q<YA2unr8i<1 zp(osiszBAVl%30kaHog^%79XI3U0}jbT|D5HYkptDy~A_cZ_(3TSW{ej4UE)xB+L; zEtm##)gStIS_)4$0{lZ4(J9X0t=t@P##R z15b1eYr-3S6Bh+d!fx&d`vE?Y^^DHidi4j`$$Y&T`t-uuCZKX<@G0ImBUu2vj=bOHQ(p(ikk6Hw>X#C_NWnk13?f;lUmF4wnf+q6`zoDmOC&JpYw zbn$1*OyjhktpCQ{_mhm^*9el(5(sod!Nvzd<#e1a0fu8LiC|bWfSv-^Rsaaq2YB!m zYN;#Of4*N~{@C+Gx&!9AOp z{UEo?}7TJxzL{v0dCTUDT4jXR@zfw zG)JzXXy@$C8{$?!@&(TnOwIrW`O@G9VSdlbkJV=wfuI!Ol z$f3%A$`U+@6>4v-q+WzJAX9)0=W^rN8>l{m=~KOpR$tAN1LaDxzuZACr0UvvqYX0> zxWQqhfwy1_kU_X53(*=lL7(*}#xJ0eSIATHh>SyDG@96fjnxNg=mI9?VY{;aWD^*Q zeDaPx&i&>JbN7LuY%(T+N4C)QjE{Z82Vj$aH9rNc<3uyoJP#&02ou_C?3y*ewB4H7 zXijGiv#sH+u#j&Ig~MdVMBcd{oG}CO-4@~}o6N0YcapNC40^k%;Gk2WI_ZGhurT<< z^5$shv33IIO6PlG>wg<+c_FqfY0KP))8bn20xFYWX6UI(o@`Sb$_eGYS_7=?UEBl1 zkU31XgW6aq@}5B#x}FS$mSCBF9n9`Nx{>*WdoB}oqoYt9+klz>kGgL{qJ4lmM&9e& z=5={}o{Qc?(qgrlkz^ibAM)8keX+POkS%KFfV)cvZfr5m>SwicYAe-WU8siYlZ^dz zIkS|F!~W_`sNBDSaS-6~cZS`^HxM~7nx}*?4QW@Sv9Z&rL*t=Q_)Mm<9gyQ~=dQ6& zk$@_In=S=z>^rlAin!I98%N;3QGk@i`}`V~7VX4UVj-X*)uCaWhPN<^3^rE*SuSN3 zU=*^BE5sEB*SvzWp~L7dj^w`(4QkYXkgyzvv!u{c=3Ztsw5d;^OIl6lvUk~bxQ8ZT z@*TnKWxg;x=7591SjscA&1QH~t;}7}*WbF9#7|!7Nxf_ z7UPC5%T$G~u?6%m7BhqrL(!UR-_%{&7&y%o1-IOZ`qLEp(!9ldAPd>un0(>!mVNyddg_hfkpTXw+-%6W4Mp}P~jWj0hpkUX`(H7og6R+ zTeNKbg}ID9%@4u^`&67JUJ~=fzPPCe^2gZbBoDgb+u(~U0#UDxZRJ(yH8{o!{C&R} z3@zIaOnC>5H2nMO;JVW>AvDs@XiKzW_`9#Ev(=MYv=IUI>sQQ!ujvY=J2?mB`VP3D z>&zT*kZtj|x6{*=8giyIN>-JJ+BYa`FRDGYNzh_HXOhelGzoo7Yn+xVOl4HU2q;(y z7{Ve(jL}pt10`~X+Ec${lrz6GL2OC9HES`yKNQP}?SKw-C&iG)JOWy~PdMGv%_Csh zR-4Dc$%Uhf_?s=m^+js)zu2Um#JHH%<^(ea-P0_xkPIUJBpQj;Yq%-@?;BGIsL3Q~~Jtf>0G=nElLGlFi29DGq~rF&Vz_FNNn|RI5QpJ_K|B zZ01k8NoTYkIOFG0_cfYsjs(ZunKVVszQM+T(=Ulx;skgGY{D}J5aQCfn_rn}m>(yb zU1&ADS0UO5U_Z0BgTnTm?Gu+Gq4mrat$xLCc1G7t^am+B%7gN$fs7to8e?tAam%q#>0il~A zTtZhA&aEZm%|)0>R?^4#9umH;7*5DDu%HjgcIGQsqyhMOkF$1cVQr`5fQ9OqJC7p; zB#+_Q1$-$BYb$75Cl=&1%vpt?&5vbG^A;3tZ*hO`R*PuI)Yi%lB%FVd#;M=67wQz{ zx-vssNVj2wW-$vhZnB?Q&fXV}KoMpa&a&?ro2l!n$|{v5ip`A;a1Gxf4?=C3F8w2y z(>54&%~GT-_mtl%7{Ys@oKTeC#$IKP(_PS@FUEAyL}uj2Qd8uq2k66$V>H$L2W)tK zG71c1Hd0G+pgd3J;SS2J1!tcRu6-_4q)W&w_7*!G9H?e8(Bfv(3+6{=GbYX5qz<_F z2zDyli~UB9l72v>5iP^+W1`KelmLNWOh$8$_;KL2I|@C8gTe*jwx9`5gx&l{=#ej& zig8!Jh3vqu>TLaQ`W+MQWjLj^f#WR0WpF|KN~rPzxa#Z=W(yQbQ}j)k2!|S8b2#he zD)Y-Ze|*1C&IPXPC>e|zR)Fck>@>5ne-^6Mf^N`-yCzu;hBm3WmZZMZ;-TDn!6cBj zsD<~r2V5FE8P#hx{z)~9LtnZXJV!U>C{)%ne2aaLd}XA0a*%2RtGSXkHhHEw*-4g= zOJoPxNOHkq7eTk2j9KssZ9v;l8C~>fXe|HLS|Y!q9;)?J%wA8BQSNVyq_3bZIL_YZ zHUby>1q^EnIHLXhb^boIrYYPQOasZpMhfF8*Qei&A=okLs|V?(c0eCR4`MD_iS3v) ze!SoliipWVL7@bG{}^Pv96%3Qf!oe5ARD3CX^x-oEpySV4popJs`*7|q5BgXn~rDP zgD=QuVY2H^LU9M4W$a9zImFyUWY$M}Ti1gZBn`ws3=8X1p@$cCLU}O|+-fXaf*D1x=*QH(*zM>g)rYQmy*yl*t1j1OfaShG ztK#M@j6H~HOmC>Y4x5GWS@(l-ZzlNL0dyzTjqUmyt&%?67zM>?RrvPyHp;@OXDGC5 z

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%0Ak&y@!O>a+vZD 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.wavtrue1audio_01audio_0","screen":[{"simulationName":"Built-in","type":"builtin","width":320,"height":240,"scale":0.78,"screenName":"","blockId":"","screenColorType":0,"id":"builtin","createTime":1745910010560}],"logicWhenNum":0,"customList":[]} \ No newline at end of file diff --git a/examples/module/audio/cores3_play_wav_example.py b/examples/module/audio/cores3_play_wav_example.py new file mode 100644 index 00000000..a0d6122a --- /dev/null +++ b/examples/module/audio/cores3_play_wav_example.py @@ -0,0 +1,52 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import os, sys, io +import M5 +from M5 import * +from module import AudioModule + + +audio_0 = None + + +def setup(): + global audio_0 + + M5.begin() + Widgets.fillScreen(0x222222) + + audio_0 = AudioModule( + 0, + 16000, + i2s_sck=7, + i2s_ws=6, + i2s_di=14, + i2s_do=13, + i2s_mclk=0, + work_mode=AudioModule.MODE_HEADPHONE, + offset=False, + mux=AudioModule.MUX_NATIONAL, + ) + audio_0.play_wav_file("/flash/res/audio/66.wav") + + +def loop(): + global audio_0 + M5.update() + + +if __name__ == "__main__": + try: + setup() + while True: + loop() + except (Exception, KeyboardInterrupt) as e: + try: + audio_0.deinit() + from utility import print_error_msg + + print_error_msg(e) + except ImportError: + print("please update to latest firmware") diff --git a/examples/module/audio/cores3_playback_controls_example.m5f2 b/examples/module/audio/cores3_playback_controls_example.m5f2 new file mode 100644 index 00000000..746e3625 --- /dev/null +++ b/examples/module/audio/cores3_playback_controls_example.m5f2 @@ -0,0 +1 @@ +{"version":"V2.0","versionNumber":"V2.2.6","type":"cores3","components":[{"name":"screen","type":"screen","layer":0,"screenId":"builtin","screenName":"","id":"__cores3_screen","createTime":1745910010563,"x":0,"y":0,"width":320,"height":240,"backgroundColor":"#222222","size":0,"isSelected":true}],"resources":[{"hardware":["hardware_button","hardware_pin_button","imu","speaker","touch","als","mic"]},{"module":["module_audio"]}],"units":[],"hats":[],"bases":[],"i2cs":[],"blockly":"trueaudio_0AMODE_HEADPHONE16000MUX_NATIONALaudio_0flash66.wav1audio_01audio_0true","screen":[{"simulationName":"Built-in","type":"builtin","width":320,"height":240,"scale":0.78,"screenName":"","blockId":"","screenColorType":0,"id":"builtin","createTime":1745910010560}],"logicWhenNum":0,"customList":[]} \ No newline at end of file diff --git a/examples/module/audio/cores3_playback_controls_example.py b/examples/module/audio/cores3_playback_controls_example.py new file mode 100644 index 00000000..b037e3d5 --- /dev/null +++ b/examples/module/audio/cores3_playback_controls_example.py @@ -0,0 +1,57 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import os, sys, io +import M5 +from M5 import * +from module import AudioModule +import time + + +audio_0 = None + + +def setup(): + global audio_0 + + M5.begin() + Widgets.fillScreen(0x222222) + + audio_0 = AudioModule( + 0, + 16000, + i2s_sck=7, + i2s_ws=6, + i2s_di=14, + i2s_do=13, + i2s_mclk=0, + work_mode=AudioModule.MODE_HEADPHONE, + offset=False, + mux=AudioModule.MUX_NATIONAL, + ) + audio_0.play_wav_file("/flash/res/audio/66.wav") + time.sleep(1) + audio_0.pause() + time.sleep(1) + audio_0.resume() + + +def loop(): + global audio_0 + M5.update() + + +if __name__ == "__main__": + try: + setup() + while True: + loop() + except (Exception, KeyboardInterrupt) as e: + try: + audio_0.deinit() + from utility import print_error_msg + + print_error_msg(e) + except ImportError: + print("please update to latest firmware") diff --git a/examples/module/audio/cores3_record_audio_example.m5f2 b/examples/module/audio/cores3_record_audio_example.m5f2 new file mode 100644 index 00000000..4073bcb1 --- /dev/null +++ b/examples/module/audio/cores3_record_audio_example.m5f2 @@ -0,0 +1 @@ +{"version":"V2.0","versionNumber":"V2.2.6","type":"cores3","components":[{"name":"screen","type":"screen","layer":0,"screenId":"builtin","screenName":"","id":"__cores3_screen","createTime":1745910010563,"x":0,"y":0,"width":320,"height":240,"backgroundColor":"#222222","size":0,"isSelected":true}],"resources":[{"hardware":["hardware_button","hardware_pin_button","imu","speaker","touch","als","mic"]},{"module":["module_audio"]}],"units":[],"hats":[],"bases":[],"i2cs":[],"blockly":"trueaudio_0AMODE_HEADPHONE16000MUX_NATIONALaudio_016000STEREO163000audio_0audio_016000STEREO16-1true","screen":[{"simulationName":"Built-in","type":"builtin","width":320,"height":240,"scale":0.78,"screenName":"","blockId":"","screenColorType":0,"id":"builtin","createTime":1745910010560}],"logicWhenNum":0,"customList":[]} \ No newline at end of file diff --git a/examples/module/audio/cores3_record_audio_example.py b/examples/module/audio/cores3_record_audio_example.py new file mode 100644 index 00000000..c8349767 --- /dev/null +++ b/examples/module/audio/cores3_record_audio_example.py @@ -0,0 +1,55 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import os, sys, io +import M5 +from M5 import * +from module import AudioModule + + +audio_0 = None + + +def setup(): + global audio_0 + + M5.begin() + Widgets.fillScreen(0x222222) + + audio_0 = AudioModule( + 0, + 16000, + i2s_sck=7, + i2s_ws=6, + i2s_di=14, + i2s_do=13, + i2s_mclk=0, + work_mode=AudioModule.MODE_HEADPHONE, + offset=False, + mux=AudioModule.MUX_NATIONAL, + ) + audio_0.record(rate=16000, bits=16, channel=AudioModule.STEREO, duration=3000) + audio_0.play_raw( + audio_0.pcm_buffer, rate=16000, bits=16, channel=AudioModule.STEREO, duration=-1 + ) + + +def loop(): + global audio_0 + M5.update() + + +if __name__ == "__main__": + try: + setup() + while True: + loop() + except (Exception, KeyboardInterrupt) as e: + try: + audio_0.deinit() + from utility import print_error_msg + + print_error_msg(e) + except ImportError: + print("please update to latest firmware") diff --git a/m5stack/libs/module/audio.py b/m5stack/libs/module/audio.py index 0079a5d7..8830b7b8 100644 --- a/m5stack/libs/module/audio.py +++ b/m5stack/libs/module/audio.py @@ -107,13 +107,49 @@ def read_register(self, reg: int) -> int: class AudioModule: + """Initialize the audio module. + + :param i2s_port: I2S port number. + :param sample_rate: Sample rate (default is 16000). + :param i2s_sck: I2S clock pin. + :param i2s_ws: I2S word select pin. + :param i2s_di: I2S data input pin. + :param i2s_do: I2S data output pin. + :param i2s_mclk: I2S master clock pin. + :param work_mode: Work mode (0: headphone, 1: line in). + :param offset: Generally speaking, when using line in, offset is False; if the input is connected to an ADC microphone, offset is True. (Only valid in line in mode). + :param mux: Select the TRRS plug to be used. (default is MUX_NATIONAL). + + UiFlow2 Code Block: + + |init.png| + + MicroPython Code Block: + + .. code-block:: python + + from module import AudioModule + + audio_0 = AudioModule(0, 16000, i2s_sck=7, i2s_ws=6, i2s_di=14, i2s_do=13, i2s_mclk=0, work_mode=AudioModule.MODE_HEADPHONE, offset=False, mux=AudioModule.MUX_NATIONAL) + """ + MUX_NATIONAL = _Expander.AUDIO_HPMODE_NATIONAL + """National Standard audio mode (OMTP)""" + MUX_AMERICAN = _Expander.AUDIO_HPMODE_AMERICAN + """American Standard audio mode (CTIA)""" + MODE_LINE = 0 + """Line in mode""" + MODE_HEADPHONE = 1 + """Headphone mode""" MONO = 1 + """Mono""" + STEREO = 2 + """Stereo""" def __init__( self, @@ -172,60 +208,296 @@ def __init__( channel=2, ) - def play_wav_file(self, file): + def play_wav_file(self, file: str) -> None: + """Play a WAV file. + + :param str file: The path of the WAV file to play. + :return: None + + UiFlow2 Code Block: + + |play_wav_file.png| + + MicroPython Code Block: + + .. code-block:: python + + audio_0.play_wav_file("/flash/res/audio/test.wav") + """ if self.mic.is_running(): self.mic.deinit() self.spk.play_wav_file(file) - def tone(self, freq, duration): + def tone(self, freq: int, duration: int) -> None: + """Play simple tone sound. + + :param int freq: Frequency of the tone in Hz. + :param int duration: Duration of the tone in milliseconds. + :return: None + + UiFlow2 Code Block: + + |tone.png| + + MicroPython Code Block: + + .. code-block:: python + + audio_0.tone(2000, 50) + """ if self.mic.is_running(): self.mic.deinit() self.spk.tone(freq, duration) - def play_wav(self, buf, duration=-1): + def play_wav(self, buf: bytes, duration: int = -1) -> None: + """Play a WAV buffer. + + :param bytes buf: The WAV buffer to play. + :param int duration: Duration of the WAV buffer in milliseconds. when duration is -1, it will play until stopped. (default is -1). + :return: None + + UiFlow2 Code Block: + + |play_wav.png| + + MicroPython Code Block: + + .. code-block:: python + + audio_0.play_wav(wav_buffer, duration=1000) + """ if self.mic.is_running(): self.mic.deinit() self.spk.play_wav(buf, duration=duration) - def play_raw(self, buf, rate=16000, bits=16, channel=2, duration=-1): + def play_raw( + self, buf: bytes, rate: int = 16000, bits: int = 16, channel: int = 2, duration: int = -1 + ) -> None: + """Play a pcm buffer. + + :param bytes buf: The PCM buffer to play. + :param int rate: Sample rate (default is 16000). + :param int bits: Bit depth (default is 16). + :param int channel: Number of channels (default is 2). + :param int duration: Duration of the PCM buffer in milliseconds. when duration is -1, it will play until stopped. (default is -1). + :return: None + + UiFlow2 Code Block: + + |play_raw.png| + + MicroPython Code Block: + + .. code-block:: python + + audio_0.play_raw(pcm_buffer, rate=16000, bits=16, channel=2, duration=1000) + """ if self.mic.is_running(): self.mic.deinit() self.spk.play_raw(buf, rate=rate, bits=bits, channel=channel, duration=duration) - def pause(self): + def pause(self) -> None: + """Pause the playback. + + UiFlow2 Code Block: + + |pause.png| + + MicroPython Code Block: + + .. code-block:: python + + audio.tone(2000, 100) + time.sleep(0.05) + audio_0.pause() + time.sleep(0.05) + audio_0.resume() + """ self.spk.pause() def resume(self): + """Resume the playback. + + UiFlow2 Code Block: + + |resume.png| + + MicroPython Code Block: + + .. code-block:: python + + audio.tone(2000, 100) + time.sleep(0.05) + audio_0.pause() + time.sleep(0.05) + audio_0.resume() + """ self.spk.resume() def stop(self): + """Stop the playback. + + UiFlow2 Code Block: + + |stop.png| + + MicroPython Code Block: + + .. code-block:: python + + audio.tone(2000, 100) + time.sleep(0.05) + audio_0.stop() + """ self.spk.stop() - def get_volume(self): + def get_volume(self) -> int: + """Get the speaker volume level. + + :return: The volume level (0-100). + + UiFlow2 Code Block: + + |get_volume.png| + + MicroPython Code Block: + + .. code-block:: python + + audio_0.get_volume() + """ return self.es.get_dac_volume() def set_volume(self, volume): + """Set the speaker volume level. + + :param int volume: The volume level (0-100). + + UiFlow2 Code Block: + + |set_volume.png| + + MicroPython Code Block: + + .. code-block:: python + + audio_0.set_volume(50) + """ self.spk.es.set_dac_volume(volume) - def record_wav_file(self, path, rate=16000, bits=16, channel=2, duration=3000): + def record_wav_file( + self, path: str, rate: int = 16000, bits: int = 16, channel: int = 2, duration: int = 3000 + ): + """Record audio to a WAV file. + + :param str path: The path to save the WAV file. + :param int rate: Sample rate (default is 16000). + :param int bits: Bit depth (default is 16). + :param int channel: Number of channels (default is 2). + :param int duration: Duration of the recording in milliseconds (default is 3000). + + UiFlow2 Code Block: + + |record_wav_file.png| + + MicroPython Code Block: + + .. code-block:: python + + audio_0.record_wav_file("/flash/res/audio/test.wav", rate=16000, bits=16, channel=2, duration=3000) + """ self.spk.deinit() self.mic.record_wav_file(path, rate=rate, bits=bits, channel=channel, duration=duration) def record(self, rate=16000, bits=16, channel=2, duration=3000): + """Record audio to a PCM buffer. + + :param int rate: Sample rate (default is 16000). + :param int bits: Bit depth (default is 16). + :param int channel: Number of channels (default is 2). + :param int duration: Duration of the recording in milliseconds (default is 3000). + + UiFlow2 Code Block: + + |record.png| + + MicroPython Code Block: + + .. code-block:: python + + audio_0.record(rate=16000, bits=16, channel=2, duration=3000) + """ self.spk.deinit() return self.mic.record(rate=rate, bits=bits, channel=channel, duration=duration) @property - def pcm_buffer(self): + def pcm_buffer(self) -> bytes: + """Get the PCM buffer. + + :return: The PCM buffer. + + UiFlow2 Code Block: + + |pcm_buffer.png| + + MicroPython Code Block: + + .. code-block:: python + + audio_0.pcm_buffer + """ return self.mic.pcm_buffer def set_color(self, num: int, color: int): + """Set the RGB LED color. + + :param int num: The LED number (0-2). + :param int color: The color value (0xRRGGBB). + + UiFlow2 Code Block: + + |set_color.png| + + MicroPython Code Block: + + .. code-block:: python + + audio_0.set_color(0, 0xFF0000) + """ self.exp.set_color(num, color) def fill_color(self, color: int): + """Fill all RGB LEDs with the same color. + + :param int color: The color value (0xRRGGBB). + + UiFlow2 Code Block: + + |fill_color.png| + + MicroPython Code Block: + + .. code-block:: python + + audio_0.fill_color(0xFF0000) + """ self.exp.fill_color(color) def set_brightness(self, br: int): + """Set the RGB LED brightness. + + :param int br: The brightness level (0-100). + + UiFlow2 Code Block: + + |set_brightness.png| + + MicroPython Code Block: + + .. code-block:: python + + audio_0.set_brightness(50) + """ self.exp.set_brightness(br) def deinit(self): From 1a6ea7bf1ceee4493fe60171549cb3ad4057ad21 Mon Sep 17 00:00:00 2001 From: lbuque Date: Tue, 29 Apr 2025 16:30:16 +0800 Subject: [PATCH 066/322] cmodules: Fix compilation errors. Signed-off-by: lbuque --- m5stack/cmodules/cmodules.cmake | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/m5stack/cmodules/cmodules.cmake b/m5stack/cmodules/cmodules.cmake index cb0ea050..22fc8f70 100644 --- a/m5stack/cmodules/cmodules.cmake +++ b/m5stack/cmodules/cmodules.cmake @@ -4,8 +4,12 @@ # add cdrivers include(${CMAKE_CURRENT_LIST_DIR}/cdriver/cdriver.cmake) + # add m5audio2 module -include(${CMAKE_CURRENT_LIST_DIR}/m5audio2/m5audio2.cmake) +if(IDF_TARGET STREQUAL "esp32" OR IDF_TARGET STREQUAL "esp32s3") + include(${CMAKE_CURRENT_LIST_DIR}/m5audio2/m5audio2.cmake) +endif() + include(${CMAKE_CURRENT_LIST_DIR}/m5utils/m5utils.cmake) if (M5_CAMERA_MODULE_ENABLE) From 43666f5d4dfc93f9d42cc6d6648b4a652fce6b1f Mon Sep 17 00:00:00 2001 From: lbuque Date: Tue, 1 Apr 2025 17:56:36 +0800 Subject: [PATCH 067/322] boards: Add lvgl v9 support. Signed-off-by: lbuque --- .gitmodules | 4 +- m5stack/CMakeLists.txt | 12 +- m5stack/CMakeListsDefault.cmake | 3 +- m5stack/CMakeListsLvgl.cmake | 220 ++++++++++++------ m5stack/Makefile | 7 +- .../boards/M5STACK_AirQ/mpconfigboard.cmake | 1 + .../boards/M5STACK_AtomS3/mpconfigboard.cmake | 1 + .../M5STACK_AtomS3R/mpconfigboard.cmake | 1 + .../M5STACK_AtomS3U/mpconfigboard.cmake | 1 + .../M5STACK_AtomS3_Lite/mpconfigboard.cmake | 1 + .../boards/M5STACK_AtomU/mpconfigboard.cmake | 1 + .../M5STACK_Atom_Echo/mpconfigboard.cmake | 1 + .../M5STACK_Atom_Lite/mpconfigboard.cmake | 1 + .../M5STACK_Atom_Matrix/mpconfigboard.cmake | 1 + .../boards/M5STACK_Basic/mpconfigboard.cmake | 1 + .../M5STACK_Basic_4MB/mpconfigboard.cmake | 1 + .../M5STACK_Capsule/mpconfigboard.cmake | 1 + .../M5STACK_Cardputer/mpconfigboard.cmake | 1 + .../boards/M5STACK_Core2/mpconfigboard.cmake | 1 + .../M5STACK_CoreInk/mpconfigboard.cmake | 1 + .../boards/M5STACK_CoreS3/mpconfigboard.cmake | 1 + .../boards/M5STACK_Dial/mpconfigboard.cmake | 1 + .../M5STACK_DinMeter/mpconfigboard.cmake | 1 + .../boards/M5STACK_Fire/mpconfigboard.cmake | 1 + .../boards/M5STACK_NanoC6/mpconfigboard.cmake | 1 + .../boards/M5STACK_Paper/mpconfigboard.cmake | 1 + .../M5STACK_PaperS3/mpconfigboard.cmake | 1 + .../M5STACK_StamPLC/mpconfigboard.cmake | 1 + .../M5STACK_StampS3/mpconfigboard.cmake | 1 + .../M5STACK_Stamp_PICO/mpconfigboard.cmake | 1 + .../M5STACK_Station/mpconfigboard.cmake | 1 + .../boards/M5STACK_StickC/mpconfigboard.cmake | 1 + .../M5STACK_StickC_PLUS/mpconfigboard.cmake | 1 + .../M5STACK_StickC_PLUS2/mpconfigboard.cmake | 1 + .../boards/M5STACK_Tough/mpconfigboard.cmake | 1 + m5stack/boards/mpconfiglvgl.h | 10 +- m5stack/cmodules/cmodules.cmake | 4 + m5stack/cmodules/lv_binding_micropython | 1 + m5stack/cmodules/m5unified/m5unified.cmake | 2 + m5stack/cmodules/m5unified/m5unified.h | 6 +- m5stack/cmodules/m5unified/m5unified_gfx.c | 6 +- m5stack/components/M5Unified/mpy_lvgl.txt | 31 +-- m5stack/components/M5Unified/mpy_m5gfx.cpp | 4 +- .../components/M5Unified/mpy_m5unified.cpp | 11 +- m5stack/libs/lvgl/fs_driver.py | 90 +++++++ m5stack/libs/lvgl/lv_utils.py | 190 +++++++++++++++ m5stack/libs/lvgl/manifest.py | 6 + m5stack/libs/manifest.py | 1 + m5stack/partitions_16mb.csv | 6 +- ...0003-avoid-lv_bindings-compile-error.patch | 57 +++++ tests/lvgl/image.py | 52 +++++ tests/lvgl/label.py | 62 +++++ tests/lvgl/multiple_display.py | 155 ++++++------ 53 files changed, 780 insertions(+), 190 deletions(-) create mode 160000 m5stack/cmodules/lv_binding_micropython create mode 100644 m5stack/libs/lvgl/fs_driver.py create mode 100644 m5stack/libs/lvgl/lv_utils.py create mode 100644 m5stack/libs/lvgl/manifest.py create mode 100644 m5stack/patches/0003-avoid-lv_bindings-compile-error.patch create mode 100644 tests/lvgl/image.py create mode 100644 tests/lvgl/label.py diff --git a/.gitmodules b/.gitmodules index eb2a10d1..21dbbe29 100644 --- a/.gitmodules +++ b/.gitmodules @@ -28,7 +28,9 @@ [submodule "m5stack/components/epdiy"] path = m5stack/components/epdiy url = https://github.com/vroland/epdiy.git - [submodule "m5stack/components/esp_zigbee_host"] path = m5stack/components/esp_zigbee_host url = https://github.com/hlym123/esp_zigbee_host.git +[submodule "m5stack/cmodules/lv_binding_micropython"] + path = m5stack/cmodules/lv_binding_micropython + url = https://github.com/lvgl/lv_binding_micropython.git diff --git a/m5stack/CMakeLists.txt b/m5stack/CMakeLists.txt index 1a15b734..0a67b9c9 100644 --- a/m5stack/CMakeLists.txt +++ b/m5stack/CMakeLists.txt @@ -77,12 +77,12 @@ set(SDKCONFIG_DEFAULTS ${CMAKE_BINARY_DIR}/sdkconfig.combined) if(BUILD_WITH_LVGL) # Include LVGL component, ignore KCONFIG - idf_build_set_property(LV_MICROPYTHON 1) - idf_build_component(${CMAKE_SOURCE_DIR}/components/lv_bindings/lvgl) - idf_build_set_property(COMPILE_DEFINITIONS "-DLV_KCONFIG_IGNORE" APPEND) - separate_arguments(LV_CFLAGS_ENV UNIX_COMMAND $ENV{LV_CFLAGS}) - list(APPEND LV_CFLAGS ${LV_CFLAGS_ENV}) - idf_build_set_property(COMPILE_DEFINITIONS "${LV_CFLAGS}" APPEND) + # idf_build_set_property(LV_MICROPYTHON 1) + # idf_build_component(${CMAKE_SOURCE_DIR}/components/lv_bindings/lvgl) + # idf_build_set_property(COMPILE_DEFINITIONS "-DLV_KCONFIG_IGNORE" APPEND) + # separate_arguments(LV_CFLAGS_ENV UNIX_COMMAND $ENV{LV_CFLAGS}) + # list(APPEND LV_CFLAGS ${LV_CFLAGS_ENV}) + # idf_build_set_property(COMPILE_DEFINITIONS "${LV_CFLAGS}" APPEND) endif() # Include main IDF cmake file. diff --git a/m5stack/CMakeListsDefault.cmake b/m5stack/CMakeListsDefault.cmake index 67421853..db377425 100644 --- a/m5stack/CMakeListsDefault.cmake +++ b/m5stack/CMakeListsDefault.cmake @@ -308,6 +308,7 @@ target_compile_definitions(${MICROPY_TARGET} PUBLIC LFS1_NO_MALLOC LFS1_NO_DEBUG LFS1_NO_WARN LFS1_NO_ERROR LFS1_NO_ASSERT LFS2_NO_MALLOC LFS2_NO_DEBUG LFS2_NO_WARN LFS2_NO_ERROR LFS2_NO_ASSERT BOARD_ID=${BOARD_ID} + MICROPY_PY_LVGL=${MICROPY_PY_LVGL} ) # Disable some warnings to keep the build output clean. @@ -318,7 +319,7 @@ target_compile_options(${MICROPY_TARGET} PUBLIC ) target_link_options(${MICROPY_TARGET} PUBLIC - ${MICROPY_LINK_TINYUSB} + ${MICROPY_LINK_TINYUSB} ) # Additional include directories needed for private NimBLE headers. diff --git a/m5stack/CMakeListsLvgl.cmake b/m5stack/CMakeListsLvgl.cmake index ede38eef..1473ac08 100644 --- a/m5stack/CMakeListsLvgl.cmake +++ b/m5stack/CMakeListsLvgl.cmake @@ -7,9 +7,17 @@ if(NOT MICROPY_DIR) get_filename_component(MICROPY_DIR ${PROJECT_DIR}/../micropython/ ABSOLUTE) endif() +# Set location of the ESP32 port directory. +if(NOT MICROPY_PORT_DIR) + get_filename_component(MICROPY_PORT_DIR ${MICROPY_DIR}/ports/esp32 ABSOLUTE) +endif() + # Include core source components. include(${MICROPY_DIR}/py/py.cmake) +# CMAKE_BUILD_EARLY_EXPANSION is set during the component-discovery phase of +# `idf.py build`, so none of the extmod/usermod (and in reality, most of the +# micropython) rules need to happen. Specifically, you cannot invoke add_library. if(NOT CMAKE_BUILD_EARLY_EXPANSION) # Enable extmod components that will be configured by extmod.cmake. # A board may also have enabled additional components. @@ -19,40 +27,74 @@ if(NOT CMAKE_BUILD_EARLY_EXPANSION) include(${MICROPY_DIR}/extmod/extmod.cmake) endif() -set(MICROPY_QSTRDEFS_PORT +list(APPEND MICROPY_QSTRDEFS_PORT ${PROJECT_DIR}/../micropython/ports/esp32/qstrdefsport.h ) -set(MICROPY_SOURCE_SHARED +list(APPEND MICROPY_SOURCE_SHARED ${MICROPY_DIR}/shared/readline/readline.c ${MICROPY_DIR}/shared/netutils/netutils.c ${MICROPY_DIR}/shared/timeutils/timeutils.c ${MICROPY_DIR}/shared/runtime/interrupt_char.c + ${MICROPY_DIR}/shared/runtime/mpirq.c ${MICROPY_DIR}/shared/runtime/stdout_helpers.c ${MICROPY_DIR}/shared/runtime/sys_stdio_mphal.c ${MICROPY_DIR}/shared/runtime/pyexec.c ) -set(MICROPY_SOURCE_LIB +list(APPEND MICROPY_SOURCE_LIB ${MICROPY_DIR}/lib/littlefs/lfs1.c ${MICROPY_DIR}/lib/littlefs/lfs1_util.c ${MICROPY_DIR}/lib/littlefs/lfs2.c ${MICROPY_DIR}/lib/littlefs/lfs2_util.c - ${MICROPY_DIR}/lib/mbedtls_errors/mp_mbedtls_errors.c + ${MICROPY_DIR}/lib/mbedtls_errors/esp32_mbedtls_errors.c ${MICROPY_DIR}/lib/oofatfs/ff.c ${MICROPY_DIR}/lib/oofatfs/ffunicode.c ) -if(IDF_TARGET STREQUAL "esp32c3") - list(APPEND MICROPY_SOURCE_LIB ${MICROPY_DIR}/shared/runtime/gchelper_generic.c) -endif() -set(MICROPY_SOURCE_DRIVERS +list(APPEND MICROPY_SOURCE_DRIVERS ${MICROPY_DIR}/drivers/bus/softspi.c ${MICROPY_DIR}/drivers/dht/dht.c ) -set(MICROPY_SOURCE_PORT +string(CONCAT GIT_SUBMODULES "${GIT_SUBMODULES} " lib/tinyusb) +if(MICROPY_PY_TINYUSB) + set(TINYUSB_SRC "${MICROPY_DIR}/lib/tinyusb/src") + string(TOUPPER OPT_MCU_${IDF_TARGET} tusb_mcu) + + list(APPEND MICROPY_DEF_TINYUSB + CFG_TUSB_MCU=${tusb_mcu} + ) + + list(APPEND MICROPY_SOURCE_TINYUSB + ${TINYUSB_SRC}/tusb.c + ${TINYUSB_SRC}/common/tusb_fifo.c + ${TINYUSB_SRC}/device/usbd.c + ${TINYUSB_SRC}/device/usbd_control.c + ${TINYUSB_SRC}/class/cdc/cdc_device.c + ${TINYUSB_SRC}/portable/synopsys/dwc2/dcd_dwc2.c + ${MICROPY_DIR}/shared/tinyusb/mp_usbd.c + ${MICROPY_DIR}/shared/tinyusb/mp_usbd_cdc.c + ${MICROPY_DIR}/shared/tinyusb/mp_usbd_descriptor.c + ${MICROPY_DIR}/shared/tinyusb/mp_usbd_runtime.c + ) + + list(APPEND MICROPY_INC_TINYUSB + ${TINYUSB_SRC} + ${MICROPY_DIR}/shared/tinyusb/ + ) + + list(APPEND MICROPY_LINK_TINYUSB + -Wl,--wrap=dcd_event_handler + ) +endif() + +list(APPEND MICROPY_SOURCE_PORT + ${PROJECT_DIR}/board.cpp + ${PROJECT_DIR}/../micropython/ports/esp32/panichandler.c + ${PROJECT_DIR}/../micropython/ports/esp32/adc.c ${PROJECT_DIR}/main.c + ${PROJECT_DIR}/../micropython/ports/esp32/ppp_set_auth.c ${PROJECT_DIR}/../micropython/ports/esp32/uart.c ${PROJECT_DIR}/../micropython/ports/esp32/usb.c ${PROJECT_DIR}/../micropython/ports/esp32/usb_serial_jtag.c @@ -60,52 +102,53 @@ set(MICROPY_SOURCE_PORT ${PROJECT_DIR}/../micropython/ports/esp32/mphalport.c ${PROJECT_DIR}/../micropython/ports/esp32/fatfs_port.c ${PROJECT_DIR}/../micropython/ports/esp32/help.c - ${PROJECT_DIR}/modutime.c ${PROJECT_DIR}/../micropython/ports/esp32/machine_bitstream.c ${PROJECT_DIR}/../micropython/ports/esp32/machine_timer.c ${PROJECT_DIR}/../micropython/ports/esp32/machine_pin.c ${PROJECT_DIR}/../micropython/ports/esp32/machine_touchpad.c - ${PROJECT_DIR}/../micropython/ports/esp32/machine_adc.c - ${PROJECT_DIR}/../micropython/ports/esp32/machine_adcblock.c ${PROJECT_DIR}/../micropython/ports/esp32/machine_dac.c - ${PROJECT_DIR}/../micropython/ports/esp32/machine_i2c.c - ${PROJECT_DIR}/../micropython/ports/esp32/machine_i2s.c - ${PROJECT_DIR}/../micropython/ports/esp32/machine_uart.c - ${PROJECT_DIR}/../micropython/ports/esp32/modmachine.c + ${PROJECT_DIR}/machine_i2c.c ${PROJECT_DIR}/../micropython/ports/esp32/network_common.c ${PROJECT_DIR}/../micropython/ports/esp32/network_lan.c - ${PROJECT_DIR}/../micropython/ports/esp32/network_ppp.c - ${PROJECT_DIR}/../micropython/ports/esp32/network_wlan.c + ${PROJECT_DIR}/network_ppp.c + ${PROJECT_DIR}/network_wlan.c ${PROJECT_DIR}/../micropython/ports/esp32/mpnimbleport.c ${PROJECT_DIR}/../micropython/ports/esp32/modsocket.c + ${PROJECT_DIR}/../micropython/ports/esp32/lwip_patch.c ${PROJECT_DIR}/../micropython/ports/esp32/modesp.c ${PROJECT_DIR}/esp32_nvs.c ${PROJECT_DIR}/../micropython/ports/esp32/esp32_partition.c ${PROJECT_DIR}/../micropython/ports/esp32/esp32_rmt.c ${PROJECT_DIR}/../micropython/ports/esp32/esp32_ulp.c ${PROJECT_DIR}/modesp32.c - ${PROJECT_DIR}/../micropython/ports/esp32/machine_wdt.c + # ${PROJECT_DIR}/../micropython/ports/esp32/machine_hw_spi.c ${PROJECT_DIR}/../micropython/ports/esp32/mpthreadport.c ${PROJECT_DIR}/machine_rtc.c - ${PROJECT_DIR}/../micropython/ports/esp32/machine_sdcard.c + # ${PROJECT_DIR}/../micropython/ports/esp32/machine_sdcard.c + ${PROJECT_DIR}/../micropython/ports/esp32/modespnow.c ) if ( BOARD_TYPE STREQUAL "cores3" OR BOARD_TYPE STREQUAL "core2" OR BOARD_TYPE STREQUAL "paper" + OR BOARD_TYPE STREQUAL "papers3" OR BOARD_TYPE STREQUAL "basic" OR BOARD_TYPE STREQUAL "fire" OR BOARD_TYPE STREQUAL "capsule" OR BOARD_TYPE STREQUAL "tough" + OR BOARD_TYPE STREQUAL "stamplc" ) - LIST(APPEND MICROPY_SOURCE_PORT ${PROJECT_DIR}/machine_sdcard.c) LIST(APPEND MICROPY_SOURCE_PORT ${PROJECT_DIR}/machine_hw_spi.c) + LIST(APPEND MICROPY_SOURCE_PORT ${PROJECT_DIR}/machine_sdcard.c) else() - LIST(APPEND MICROPY_SOURCE_PORT ${PROJECT_DIR}/../micropython/ports/esp32/machine_sdcard.c) LIST(APPEND MICROPY_SOURCE_PORT ${PROJECT_DIR}/../micropython/ports/esp32/machine_hw_spi.c) + LIST(APPEND MICROPY_SOURCE_PORT ${PROJECT_DIR}/../micropython/ports/esp32/machine_sdcard.c) endif() +# list(TRANSFORM MICROPY_SOURCE_PORT PREPEND ${MICROPY_PORT_DIR}/) +list(APPEND MICROPY_SOURCE_PORT ${CMAKE_BINARY_DIR}/pins.c) + # Include LVGL bindings rules if(NOT CMAKE_BUILD_EARLY_EXPANSION) include(${PROJECT_DIR}/components/lv_bindings/mkrules.cmake) @@ -121,7 +164,7 @@ set(MICROPY_SOURCE_M5UNIFIED ${PROJECT_DIR}/components/M5Unified/mpy_m5widgets.cpp ) -if(M5_CAMERA_MODULE_ENABLE) +if (M5_CAMERA_MODULE_ENABLE) set(MICROPY_SOURCE_M5CAMERA ${PROJECT_DIR}/cmodules/m5camera/m5camera.c ) @@ -136,83 +179,84 @@ set(MICROPY_SOURCE_QSTR ${MICROPY_SOURCE_LIB} ${MICROPY_SOURCE_PORT} ${MICROPY_SOURCE_BOARD} + ${MICROPY_SOURCE_TINYUSB} ${MICROPY_SOURCE_M5UNIFIED} ${MICROPY_SOURCE_M5CAMERA} ${LV_SRC} ) -set(IDF_COMPONENTS +list(APPEND IDF_COMPONENTS app_update bootloader_support bt driver - # esp_adc_cal esp_adc + esp_app_format esp_common esp_eth esp_event + esp_hw_support + esp_netif + esp_partition + esp_pm + esp_psram esp_ringbuf esp_rom + esp_system + esp_timer esp_wifi freertos + hal heap log lwip mbedtls - mdns newlib nvs_flash sdmmc soc spi_flash - tcpip_adapter ulp + usb vfs - boards - audio_pipeline - audio_sal - esp-adf-libs - esp-sr - esp_codec_dev xtensa esp_http_client esp-tls - nghttp libffi json M5Unified esp32-camera uiflow_utility esp_dmx + esp_mm ) -if(IDF_VERSION_MINOR GREATER_EQUAL 1 OR IDF_VERSION_MAJOR GREATER_EQUAL 5) - list(APPEND IDF_COMPONENTS esp_netif) +if (M5_CAMERA_MODULE_ENABLE AND BOARD_TYPE STREQUAL "cores3") +list(APPEND IDF_COMPONENTS + esp-dl + human_face_detect + pedestrian_detect + human_face_recognition + esp-code-scanner +) endif() -if(IDF_VERSION_MINOR GREATER_EQUAL 2 OR IDF_VERSION_MAJOR GREATER_EQUAL 5) - list(APPEND IDF_COMPONENTS esp_system) - list(APPEND IDF_COMPONENTS esp_timer) +if (M5_EPDIY_ENABLE AND BOARD_TYPE STREQUAL "papers3") +message(STATUS "Enable EPDiy component") +list(APPEND IDF_COMPONENTS + epdiy +) endif() -if(IDF_VERSION_MINOR GREATER_EQUAL 3 OR IDF_VERSION_MAJOR GREATER_EQUAL 5) - list(APPEND IDF_COMPONENTS esp_hw_support) - list(APPEND IDF_COMPONENTS esp_pm) - list(APPEND IDF_COMPONENTS hal) +if(IDF_TARGET STREQUAL "esp32" OR IDF_TARGET STREQUAL "esp32s3") + list(APPEND IDF_COMPONENTS boards) + list(APPEND IDF_COMPONENTS audio_pipeline) + list(APPEND IDF_COMPONENTS audio_sal) + list(APPEND IDF_COMPONENTS esp-adf-libs) + list(APPEND IDF_COMPONENTS esp-sr) + list(APPEND IDF_COMPONENTS esp_codec_dev) endif() -if(IDF_TARGET STREQUAL "esp32") - list(APPEND IDF_COMPONENTS esp32) -elseif(IDF_TARGET STREQUAL "esp32c3") - list(APPEND IDF_COMPONENTS esp32c3) - list(APPEND IDF_COMPONENTS riscv) -elseif(IDF_TARGET STREQUAL "esp32s2") - list(APPEND IDF_COMPONENTS esp32s2) - list(APPEND IDF_COMPONENTS tinyusb) -elseif(IDF_TARGET STREQUAL "esp32s3") - list(APPEND IDF_COMPONENTS esp32s3) - list(APPEND IDF_COMPONENTS tinyusb) -endif() # Register the main IDF component. idf_component_register( @@ -223,15 +267,19 @@ idf_component_register( ${MICROPY_SOURCE_LIB} ${MICROPY_SOURCE_DRIVERS} ${MICROPY_SOURCE_PORT} - ${LV_SRC} ${MICROPY_SOURCE_BOARD} + ${MICROPY_SOURCE_TINYUSB} + ${LV_SRC} INCLUDE_DIRS ${MICROPY_INC_CORE} ${MICROPY_INC_USERMOD} + ${MICROPY_INC_TINYUSB} ${MICROPY_PORT_DIR} ${MICROPY_BOARD_DIR} ${CMAKE_BINARY_DIR} ${LV_INCLUDE} + LDFRAGMENTS + linker.lf REQUIRES ${IDF_COMPONENTS} ) @@ -240,11 +288,21 @@ idf_component_register( set(MICROPY_TARGET ${COMPONENT_TARGET}) # Define mpy-cross flags, for use with frozen code. +if(CONFIG_IDF_TARGET_ARCH STREQUAL "xtensa") set(MICROPY_CROSS_FLAGS -march=xtensawin) +endif() + +if (M5_CAMERA_MODULE_ENABLE AND BOARD_TYPE STREQUAL "cores3") +target_compile_definitions(${MICROPY_TARGET} PUBLIC + USE_OMV=1 +) +endif() # Set compile options for this port. target_compile_definitions(${MICROPY_TARGET} PUBLIC ${MICROPY_DEF_CORE} + ${MICROPY_DEF_BOARD} + ${MICROPY_DEF_TINYUSB} MICROPY_ESP_IDF_4=1 MICROPY_VFS_FAT=1 MICROPY_VFS_LFS2=1 @@ -252,6 +310,7 @@ target_compile_definitions(${MICROPY_TARGET} PUBLIC FFCONF_H=\"${MICROPY_OOFATFS_DIR}/ffconf.h\" LFS1_NO_MALLOC LFS1_NO_DEBUG LFS1_NO_WARN LFS1_NO_ERROR LFS1_NO_ASSERT LFS2_NO_MALLOC LFS2_NO_DEBUG LFS2_NO_WARN LFS2_NO_ERROR LFS2_NO_ASSERT + BOARD_ID=${BOARD_ID} LV_KCONFIG_IGNORE MICROPY_PY_LVGL=1 ) @@ -263,6 +322,10 @@ target_compile_options(${MICROPY_TARGET} PUBLIC -Wno-missing-field-initializers ) +target_link_options(${MICROPY_TARGET} PUBLIC + ${MICROPY_LINK_TINYUSB} +) + # Additional include directories needed for private NimBLE headers. target_include_directories(${MICROPY_TARGET} PUBLIC ${IDF_PATH}/components/bt/host/nimble/nimble @@ -272,25 +335,42 @@ target_include_directories(${MICROPY_TARGET} PUBLIC target_link_libraries(${MICROPY_TARGET} micropy_extmod_btree) target_link_libraries(${MICROPY_TARGET} usermod) - -# Collect all of the include directories and compile definitions for the IDF components. -foreach(comp ${IDF_COMPONENTS}) +# Collect all of the include directories and compile definitions for the IDF components, +# including those added by the IDF Component Manager via idf_components.yaml. +foreach(comp ${__COMPONENT_NAMES_RESOLVED}) micropy_gather_target_properties(__idf_${comp}) + micropy_gather_target_properties(${comp}) endforeach() -if(IDF_VERSION_MINOR GREATER_EQUAL 2 OR IDF_VERSION_MAJOR GREATER_EQUAL 5) - # These paths cannot currently be found by the IDF_COMPONENTS search loop above, - # so add them explicitly. - list(APPEND MICROPY_CPP_INC_EXTRA ${IDF_PATH}/components/soc/soc/${IDF_TARGET}/include) - list(APPEND MICROPY_CPP_INC_EXTRA ${IDF_PATH}/components/soc/soc/include) - if(IDF_VERSION_MINOR GREATER_EQUAL 3) - list(APPEND MICROPY_CPP_INC_EXTRA ${IDF_PATH}/components/tinyusb/additions/include) - list(APPEND MICROPY_CPP_INC_EXTRA ${IDF_PATH}/components/tinyusb/tinyusb/src) - endif() -endif() - # Include the main MicroPython cmake rules. include(${MICROPY_DIR}/py/mkrules.cmake) +# Generate source files for named pins (requires mkrules.cmake for MICROPY_GENHDR_DIR). + +set(GEN_PINS_PREFIX "${MICROPY_PORT_DIR}/boards/pins_prefix.c") +set(GEN_PINS_MKPINS "${MICROPY_PORT_DIR}/boards/make-pins.py") +set(GEN_PINS_SRC "${CMAKE_BINARY_DIR}/pins.c") +set(GEN_PINS_HDR "${MICROPY_GENHDR_DIR}/pins.h") + +if(EXISTS "${MICROPY_BOARD_DIR}/pins.csv") + set(GEN_PINS_BOARD_CSV "${MICROPY_BOARD_DIR}/pins.csv") + set(GEN_PINS_BOARD_CSV_ARG --board-csv "${GEN_PINS_BOARD_CSV}") +endif() + +target_sources(${MICROPY_TARGET} PRIVATE ${GEN_PINS_HDR}) + +add_custom_command( + OUTPUT ${GEN_PINS_SRC} ${GEN_PINS_HDR} + COMMAND ${Python3_EXECUTABLE} ${GEN_PINS_MKPINS} ${GEN_PINS_BOARD_CSV_ARG} + --prefix ${GEN_PINS_PREFIX} --output-source ${GEN_PINS_SRC} --output-header ${GEN_PINS_HDR} + DEPENDS + ${MICROPY_MPVERSION} + ${GEN_PINS_MKPINS} + ${GEN_PINS_BOARD_CSV} + ${GEN_PINS_PREFIX} + VERBATIM + COMMAND_EXPAND_LISTS +) + # Add lv_bindings rules all_lv_bindings() \ No newline at end of file diff --git a/m5stack/Makefile b/m5stack/Makefile index 3512d7c7..8a2fdb61 100644 --- a/m5stack/Makefile +++ b/m5stack/Makefile @@ -283,7 +283,8 @@ submodules: git submodule update --init ./components/epdiy git submodule update --init ./components/M5Unified/M5GFX git submodule update --init ./components/M5Unified/M5Unified - git submodule update --init --recursive ./components/lv_bindings + #git submodule update --init --recursive ./components/lv_bindings + git submodule update --init --recursive ./cmodules/lv_binding_micropython git submodule update --init ../micropython git submodule update --init ../esp-adf cd ../esp-adf && \ @@ -298,7 +299,7 @@ submodules: # Apply patches patch: - $(call Package/patche,$(abspath ./components/lv_bindings),$(abspath ./patches/0002_avoid_lv_bindings_compile_error.patch)) + $(call Package/patche,$(abspath ./cmodules/lv_binding_micropython),$(abspath ./patches/0003-avoid-lv_bindings-compile-error.patch)) $(call Package/patche,$(abspath ./../micropython),$(abspath ./patches/0004-micropython-1.24-machine-adc-v5.x.diff)) $(call Package/patche,$(abspath ./../micropython),$(abspath ./patches/0005-micropython-fix-SDCard-16223.patch)) $(call Package/patche,$(abspath ./../micropython),$(abspath ./patches/0006-modtime-add-timezone-method.patch)) @@ -312,7 +313,7 @@ patch: # Unapply patches unpatch: - $(call Package/unpatche,$(abspath ./components/lv_bindings),$(abspath ./patches/0002_avoid_lv_bindings_compile_error.patch)) + $(call Package/unpatche,$(abspath ./cmodules/lv_binding_micropython),$(abspath ./patches/0003-avoid-lv_bindings-compile-error.patch)) $(call Package/unpatche,$(abspath ./../micropython),$(abspath ./patches/0008-machine_uart-add-uart-mode.patch)) $(call Package/unpatche,$(abspath ./../micropython),$(abspath ./patches/0007-Add-set-default-netif-method.patch)) $(call Package/unpatche,$(abspath ./../micropython),$(abspath ./patches/0006-modtime-add-timezone-method.patch)) diff --git a/m5stack/boards/M5STACK_AirQ/mpconfigboard.cmake b/m5stack/boards/M5STACK_AirQ/mpconfigboard.cmake index 909f3848..f8c23a94 100644 --- a/m5stack/boards/M5STACK_AirQ/mpconfigboard.cmake +++ b/m5stack/boards/M5STACK_AirQ/mpconfigboard.cmake @@ -6,6 +6,7 @@ set(IDF_TARGET esp32s3) # airq https://github.com/m5stack/m5stack-board-id/blob/main/board.csv#L17 set(BOARD_ID 15) +set(MICROPY_PY_LVGL 0) set(SDKCONFIG_DEFAULTS ./boards/sdkconfig.base diff --git a/m5stack/boards/M5STACK_AtomS3/mpconfigboard.cmake b/m5stack/boards/M5STACK_AtomS3/mpconfigboard.cmake index f1049139..33ec9990 100644 --- a/m5stack/boards/M5STACK_AtomS3/mpconfigboard.cmake +++ b/m5stack/boards/M5STACK_AtomS3/mpconfigboard.cmake @@ -6,6 +6,7 @@ set(IDF_TARGET esp32s3) # atoms3 https://github.com/m5stack/m5stack-board-id/blob/main/board.csv#L13 set(BOARD_ID 11) +set(MICROPY_PY_LVGL 0) set(SDKCONFIG_DEFAULTS ./boards/sdkconfig.base diff --git a/m5stack/boards/M5STACK_AtomS3R/mpconfigboard.cmake b/m5stack/boards/M5STACK_AtomS3R/mpconfigboard.cmake index 58e4a68a..4b44c727 100644 --- a/m5stack/boards/M5STACK_AtomS3R/mpconfigboard.cmake +++ b/m5stack/boards/M5STACK_AtomS3R/mpconfigboard.cmake @@ -6,6 +6,7 @@ set(IDF_TARGET esp32s3) # atoms3r https://github.com/m5stack/m5stack-board-id/blob/main/board.csv#L20 set(BOARD_ID 18) +set(MICROPY_PY_LVGL 0) set(SDKCONFIG_DEFAULTS ./boards/sdkconfig.base diff --git a/m5stack/boards/M5STACK_AtomS3U/mpconfigboard.cmake b/m5stack/boards/M5STACK_AtomS3U/mpconfigboard.cmake index 3efd2e98..b1dcbe93 100644 --- a/m5stack/boards/M5STACK_AtomS3U/mpconfigboard.cmake +++ b/m5stack/boards/M5STACK_AtomS3U/mpconfigboard.cmake @@ -6,6 +6,7 @@ set(IDF_TARGET esp32s3) # atoms3u https://github.com/m5stack/m5stack-board-id/blob/main/board.csv#L31 set(BOARD_ID 138) +set(MICROPY_PY_LVGL 0) set(SDKCONFIG_DEFAULTS ./boards/sdkconfig.base diff --git a/m5stack/boards/M5STACK_AtomS3_Lite/mpconfigboard.cmake b/m5stack/boards/M5STACK_AtomS3_Lite/mpconfigboard.cmake index 3f7733d7..aa5c1bd4 100644 --- a/m5stack/boards/M5STACK_AtomS3_Lite/mpconfigboard.cmake +++ b/m5stack/boards/M5STACK_AtomS3_Lite/mpconfigboard.cmake @@ -6,6 +6,7 @@ set(IDF_TARGET esp32s3) # atoms3-lite https://github.com/m5stack/m5stack-board-id/blob/main/board.csv#L30 set(BOARD_ID 137) +set(MICROPY_PY_LVGL 0) set(SDKCONFIG_DEFAULTS ./boards/sdkconfig.base diff --git a/m5stack/boards/M5STACK_AtomU/mpconfigboard.cmake b/m5stack/boards/M5STACK_AtomU/mpconfigboard.cmake index d602a565..e0b51d08 100644 --- a/m5stack/boards/M5STACK_AtomU/mpconfigboard.cmake +++ b/m5stack/boards/M5STACK_AtomU/mpconfigboard.cmake @@ -4,6 +4,7 @@ # atomu https://github.com/m5stack/m5stack-board-id/blob/main/board.csv#L23 set(BOARD_ID 130) +set(MICROPY_PY_LVGL 0) set(SDKCONFIG_DEFAULTS ./boards/sdkconfig.base diff --git a/m5stack/boards/M5STACK_Atom_Echo/mpconfigboard.cmake b/m5stack/boards/M5STACK_Atom_Echo/mpconfigboard.cmake index 9640e4d8..da749afb 100644 --- a/m5stack/boards/M5STACK_Atom_Echo/mpconfigboard.cmake +++ b/m5stack/boards/M5STACK_Atom_Echo/mpconfigboard.cmake @@ -4,6 +4,7 @@ # atom-echo https://github.com/m5stack/m5stack-board-id/blob/main/board.csv#L35 set(BOARD_ID 142) +set(MICROPY_PY_LVGL 0) set(SDKCONFIG_DEFAULTS ./boards/sdkconfig.base diff --git a/m5stack/boards/M5STACK_Atom_Lite/mpconfigboard.cmake b/m5stack/boards/M5STACK_Atom_Lite/mpconfigboard.cmake index c34ddfeb..fb7e4b6c 100644 --- a/m5stack/boards/M5STACK_Atom_Lite/mpconfigboard.cmake +++ b/m5stack/boards/M5STACK_Atom_Lite/mpconfigboard.cmake @@ -4,6 +4,7 @@ # atom-lite https://github.com/m5stack/m5stack-board-id/blob/main/board.csv#L21 set(BOARD_ID 128) +set(MICROPY_PY_LVGL 0) set(SDKCONFIG_DEFAULTS ./boards/sdkconfig.base diff --git a/m5stack/boards/M5STACK_Atom_Matrix/mpconfigboard.cmake b/m5stack/boards/M5STACK_Atom_Matrix/mpconfigboard.cmake index edab5ecc..f4c039da 100644 --- a/m5stack/boards/M5STACK_Atom_Matrix/mpconfigboard.cmake +++ b/m5stack/boards/M5STACK_Atom_Matrix/mpconfigboard.cmake @@ -4,6 +4,7 @@ # atom-matrix https://github.com/m5stack/m5stack-board-id/blob/main/board.csv#L34 set(BOARD_ID 141) +set(MICROPY_PY_LVGL 0) set(SDKCONFIG_DEFAULTS ./boards/sdkconfig.base diff --git a/m5stack/boards/M5STACK_Basic/mpconfigboard.cmake b/m5stack/boards/M5STACK_Basic/mpconfigboard.cmake index e5ba9c4d..5f68ca12 100644 --- a/m5stack/boards/M5STACK_Basic/mpconfigboard.cmake +++ b/m5stack/boards/M5STACK_Basic/mpconfigboard.cmake @@ -4,6 +4,7 @@ # basic https://github.com/m5stack/m5stack-board-id/blob/main/board.csv#L3 set(BOARD_ID 1) +set(MICROPY_PY_LVGL 0) set(SDKCONFIG_DEFAULTS ./boards/sdkconfig.base diff --git a/m5stack/boards/M5STACK_Basic_4MB/mpconfigboard.cmake b/m5stack/boards/M5STACK_Basic_4MB/mpconfigboard.cmake index cf8542df..8c5790c9 100644 --- a/m5stack/boards/M5STACK_Basic_4MB/mpconfigboard.cmake +++ b/m5stack/boards/M5STACK_Basic_4MB/mpconfigboard.cmake @@ -4,6 +4,7 @@ # basic https://github.com/m5stack/m5stack-board-id/blob/main/board.csv#L3 set(BOARD_ID 1) +set(MICROPY_PY_LVGL 0) set(SDKCONFIG_DEFAULTS ./boards/sdkconfig.base diff --git a/m5stack/boards/M5STACK_Capsule/mpconfigboard.cmake b/m5stack/boards/M5STACK_Capsule/mpconfigboard.cmake index 55e88205..14f67548 100644 --- a/m5stack/boards/M5STACK_Capsule/mpconfigboard.cmake +++ b/m5stack/boards/M5STACK_Capsule/mpconfigboard.cmake @@ -6,6 +6,7 @@ set(IDF_TARGET esp32s3) # capsule https://github.com/m5stack/m5stack-board-id/blob/main/board.csv#L32 set(BOARD_ID 139) +set(MICROPY_PY_LVGL 0) set(SDKCONFIG_DEFAULTS ./boards/sdkconfig.base diff --git a/m5stack/boards/M5STACK_Cardputer/mpconfigboard.cmake b/m5stack/boards/M5STACK_Cardputer/mpconfigboard.cmake index c82f81a8..00e97354 100644 --- a/m5stack/boards/M5STACK_Cardputer/mpconfigboard.cmake +++ b/m5stack/boards/M5STACK_Cardputer/mpconfigboard.cmake @@ -6,6 +6,7 @@ set(IDF_TARGET esp32s3) # cardputer https://github.com/m5stack/m5stack-board-id/blob/main/board.csv#L16 set(BOARD_ID 14) +set(MICROPY_PY_LVGL 0) set(SDKCONFIG_DEFAULTS ./boards/sdkconfig.base diff --git a/m5stack/boards/M5STACK_Core2/mpconfigboard.cmake b/m5stack/boards/M5STACK_Core2/mpconfigboard.cmake index c89dc312..4932dfba 100644 --- a/m5stack/boards/M5STACK_Core2/mpconfigboard.cmake +++ b/m5stack/boards/M5STACK_Core2/mpconfigboard.cmake @@ -4,6 +4,7 @@ # core2 https://github.com/m5stack/m5stack-board-id/blob/main/board.csv#L4 set(BOARD_ID 2) +set(MICROPY_PY_LVGL 1) set(SDKCONFIG_DEFAULTS ./boards/sdkconfig.base diff --git a/m5stack/boards/M5STACK_CoreInk/mpconfigboard.cmake b/m5stack/boards/M5STACK_CoreInk/mpconfigboard.cmake index 9623ad5b..fa2ae4d8 100644 --- a/m5stack/boards/M5STACK_CoreInk/mpconfigboard.cmake +++ b/m5stack/boards/M5STACK_CoreInk/mpconfigboard.cmake @@ -4,6 +4,7 @@ # coreink https://github.com/m5stack/m5stack-board-id/blob/main/board.csv#L8 set(BOARD_ID 6) +set(MICROPY_PY_LVGL 0) set(SDKCONFIG_DEFAULTS ./boards/sdkconfig.base diff --git a/m5stack/boards/M5STACK_CoreS3/mpconfigboard.cmake b/m5stack/boards/M5STACK_CoreS3/mpconfigboard.cmake index 2156958d..c25a880f 100644 --- a/m5stack/boards/M5STACK_CoreS3/mpconfigboard.cmake +++ b/m5stack/boards/M5STACK_CoreS3/mpconfigboard.cmake @@ -6,6 +6,7 @@ set(IDF_TARGET esp32s3) # https://github.com/m5stack/m5stack-board-id/blob/main/board.csv#L12 set(BOARD_ID 10) +set(MICROPY_PY_LVGL 1) # Enable camera module set(M5_CAMERA_MODULE_ENABLE TRUE) diff --git a/m5stack/boards/M5STACK_Dial/mpconfigboard.cmake b/m5stack/boards/M5STACK_Dial/mpconfigboard.cmake index bc73770d..965ba1af 100644 --- a/m5stack/boards/M5STACK_Dial/mpconfigboard.cmake +++ b/m5stack/boards/M5STACK_Dial/mpconfigboard.cmake @@ -6,6 +6,7 @@ set(IDF_TARGET esp32s3) # dial https://github.com/m5stack/m5stack-board-id/blob/main/board.csv#L14 set(BOARD_ID 12) +set(MICROPY_PY_LVGL 1) set(SDKCONFIG_DEFAULTS ./boards/sdkconfig.base diff --git a/m5stack/boards/M5STACK_DinMeter/mpconfigboard.cmake b/m5stack/boards/M5STACK_DinMeter/mpconfigboard.cmake index e8d39b46..a107db82 100644 --- a/m5stack/boards/M5STACK_DinMeter/mpconfigboard.cmake +++ b/m5stack/boards/M5STACK_DinMeter/mpconfigboard.cmake @@ -6,6 +6,7 @@ set(IDF_TARGET esp32s3) # dinmeter https://github.com/m5stack/m5stack-board-id/blob/main/board.csv#L15 set(BOARD_ID 13) +set(MICROPY_PY_LVGL 0) set(SDKCONFIG_DEFAULTS ./boards/sdkconfig.base diff --git a/m5stack/boards/M5STACK_Fire/mpconfigboard.cmake b/m5stack/boards/M5STACK_Fire/mpconfigboard.cmake index 238bee15..63cee2f0 100644 --- a/m5stack/boards/M5STACK_Fire/mpconfigboard.cmake +++ b/m5stack/boards/M5STACK_Fire/mpconfigboard.cmake @@ -5,6 +5,7 @@ # NOTE: fire和 basic 共用一个 bid # fire https://github.com/m5stack/m5stack-board-id/blob/main/board.csv#L3 set(BOARD_ID 1) +set(MICROPY_PY_LVGL 1) set(SDKCONFIG_DEFAULTS ./boards/sdkconfig.base diff --git a/m5stack/boards/M5STACK_NanoC6/mpconfigboard.cmake b/m5stack/boards/M5STACK_NanoC6/mpconfigboard.cmake index a675dd8c..09d230fc 100644 --- a/m5stack/boards/M5STACK_NanoC6/mpconfigboard.cmake +++ b/m5stack/boards/M5STACK_NanoC6/mpconfigboard.cmake @@ -6,6 +6,7 @@ set(IDF_TARGET esp32c6) # nanoc6 https://github.com/m5stack/m5stack-board-id/blob/main/board.csv#L33 set(BOARD_ID 140) +set(MICROPY_PY_LVGL 0) set(SDKCONFIG_DEFAULTS boards/sdkconfig.base diff --git a/m5stack/boards/M5STACK_Paper/mpconfigboard.cmake b/m5stack/boards/M5STACK_Paper/mpconfigboard.cmake index 5de9cb68..d09d38ff 100644 --- a/m5stack/boards/M5STACK_Paper/mpconfigboard.cmake +++ b/m5stack/boards/M5STACK_Paper/mpconfigboard.cmake @@ -4,6 +4,7 @@ # paper https://github.com/m5stack/m5stack-board-id/blob/main/board.csv#L9 set(BOARD_ID 7) +set(MICROPY_PY_LVGL 0) set(SDKCONFIG_DEFAULTS ./boards/sdkconfig.base diff --git a/m5stack/boards/M5STACK_PaperS3/mpconfigboard.cmake b/m5stack/boards/M5STACK_PaperS3/mpconfigboard.cmake index ce6a54ad..31d1faa3 100644 --- a/m5stack/boards/M5STACK_PaperS3/mpconfigboard.cmake +++ b/m5stack/boards/M5STACK_PaperS3/mpconfigboard.cmake @@ -5,6 +5,7 @@ set(IDF_TARGET esp32s3) set(BOARD_ID 19) set(M5_EPDIY_ENABLE TRUE) +set(MICROPY_PY_LVGL 0) set(SDKCONFIG_DEFAULTS ./boards/sdkconfig.base diff --git a/m5stack/boards/M5STACK_StamPLC/mpconfigboard.cmake b/m5stack/boards/M5STACK_StamPLC/mpconfigboard.cmake index 1aa5839a..6325b237 100644 --- a/m5stack/boards/M5STACK_StamPLC/mpconfigboard.cmake +++ b/m5stack/boards/M5STACK_StamPLC/mpconfigboard.cmake @@ -6,6 +6,7 @@ set(IDF_TARGET esp32s3) # stamplc https://github.com/m5stack/m5stack-board-id/blob/main/board.csv#L29 set(BOARD_ID 21) +set(MICROPY_PY_LVGL 0) set(SDKCONFIG_DEFAULTS ./boards/sdkconfig.base diff --git a/m5stack/boards/M5STACK_StampS3/mpconfigboard.cmake b/m5stack/boards/M5STACK_StampS3/mpconfigboard.cmake index 378026a7..71780ea5 100644 --- a/m5stack/boards/M5STACK_StampS3/mpconfigboard.cmake +++ b/m5stack/boards/M5STACK_StampS3/mpconfigboard.cmake @@ -6,6 +6,7 @@ set(IDF_TARGET esp32s3) # stamps3 https://github.com/m5stack/m5stack-board-id/blob/main/board.csv#L29 set(BOARD_ID 136) +set(MICROPY_PY_LVGL 0) set(SDKCONFIG_DEFAULTS ./boards/sdkconfig.base diff --git a/m5stack/boards/M5STACK_Stamp_PICO/mpconfigboard.cmake b/m5stack/boards/M5STACK_Stamp_PICO/mpconfigboard.cmake index 9f53f13a..ebb83130 100644 --- a/m5stack/boards/M5STACK_Stamp_PICO/mpconfigboard.cmake +++ b/m5stack/boards/M5STACK_Stamp_PICO/mpconfigboard.cmake @@ -4,6 +4,7 @@ # stamppico https://github.com/m5stack/m5stack-board-id/blob/main/board.csv#L26 set(BOARD_ID 133) +set(MICROPY_PY_LVGL 0) set(SDKCONFIG_DEFAULTS ./boards/sdkconfig.base diff --git a/m5stack/boards/M5STACK_Station/mpconfigboard.cmake b/m5stack/boards/M5STACK_Station/mpconfigboard.cmake index c8d08605..03112e86 100644 --- a/m5stack/boards/M5STACK_Station/mpconfigboard.cmake +++ b/m5stack/boards/M5STACK_Station/mpconfigboard.cmake @@ -4,6 +4,7 @@ # station https://github.com/m5stack/m5stack-board-id/blob/main/board.csv#L11 set(BOARD_ID 9) +set(MICROPY_PY_LVGL 0) set(SDKCONFIG_DEFAULTS ./boards/sdkconfig.base diff --git a/m5stack/boards/M5STACK_StickC/mpconfigboard.cmake b/m5stack/boards/M5STACK_StickC/mpconfigboard.cmake index ce478795..60e18565 100644 --- a/m5stack/boards/M5STACK_StickC/mpconfigboard.cmake +++ b/m5stack/boards/M5STACK_StickC/mpconfigboard.cmake @@ -4,6 +4,7 @@ # stickc https://github.com/m5stack/m5stack-board-id/blob/main/board.csv#L5 set(BOARD_ID 3) +set(MICROPY_PY_LVGL 0) set(SDKCONFIG_DEFAULTS ./boards/sdkconfig.base diff --git a/m5stack/boards/M5STACK_StickC_PLUS/mpconfigboard.cmake b/m5stack/boards/M5STACK_StickC_PLUS/mpconfigboard.cmake index afda9995..e9c731af 100644 --- a/m5stack/boards/M5STACK_StickC_PLUS/mpconfigboard.cmake +++ b/m5stack/boards/M5STACK_StickC_PLUS/mpconfigboard.cmake @@ -4,6 +4,7 @@ # stickc-plus https://github.com/m5stack/m5stack-board-id/blob/main/board.csv#L6 set(BOARD_ID 4) +set(MICROPY_PY_LVGL 0) set(SDKCONFIG_DEFAULTS ./boards/sdkconfig.base diff --git a/m5stack/boards/M5STACK_StickC_PLUS2/mpconfigboard.cmake b/m5stack/boards/M5STACK_StickC_PLUS2/mpconfigboard.cmake index ccf51f91..45a696b7 100644 --- a/m5stack/boards/M5STACK_StickC_PLUS2/mpconfigboard.cmake +++ b/m5stack/boards/M5STACK_StickC_PLUS2/mpconfigboard.cmake @@ -4,6 +4,7 @@ # stickc-plus2 https://github.com/m5stack/m5stack-board-id/blob/main/board.csv#L7 set(BOARD_ID 5) +set(MICROPY_PY_LVGL 0) set(SDKCONFIG_DEFAULTS ./boards/sdkconfig.base diff --git a/m5stack/boards/M5STACK_Tough/mpconfigboard.cmake b/m5stack/boards/M5STACK_Tough/mpconfigboard.cmake index f21b6c95..aa193312 100644 --- a/m5stack/boards/M5STACK_Tough/mpconfigboard.cmake +++ b/m5stack/boards/M5STACK_Tough/mpconfigboard.cmake @@ -4,6 +4,7 @@ # tough https://github.com/m5stack/m5stack-board-id/blob/main/board.csv#L10 set(BOARD_ID 8) +set(MICROPY_PY_LVGL 1) set(SDKCONFIG_DEFAULTS ./boards/sdkconfig.base diff --git a/m5stack/boards/mpconfiglvgl.h b/m5stack/boards/mpconfiglvgl.h index bcd3feb2..e52d0566 100644 --- a/m5stack/boards/mpconfiglvgl.h +++ b/m5stack/boards/mpconfiglvgl.h @@ -4,24 +4,24 @@ * SPDX-License-Identifier: MIT */ -#if MICROPY_PY_LVGL +#if 0 #ifndef MICROPY_INCLUDED_PY_MPSTATE_H #define MICROPY_INCLUDED_PY_MPSTATE_H -#include "./../../../m5stack/components/lv_bindings/lvgl/src/misc/lv_gc.h" +#include "./../../../m5stack/cmodule/lv_binding_micropython/lvgl/src/misc/lv_gc.h" #undef MICROPY_INCLUDED_PY_MPSTATE_H #else -#include "./../../../m5stack/components/lv_bindings/lvgl/src/misc/lv_gc.h" +#include "./../../../m5stack/cmodule/lv_binding_micropython/lvgl/src/misc/lv_gc.h" #endif #else #define LV_ROOTS #endif -#if MICROPY_PY_LVGL +#if 0 extern void lvgl_deinit(); #define MICROPY_PORT_DEINIT_FUNC lvgl_deinit() #endif -#if MICROPY_PY_LVGL +#if 0 #define MICROPY_PORT_ROOT_POINTERS \ LV_ROOTS \ void *mp_lv_user_data; \ diff --git a/m5stack/cmodules/cmodules.cmake b/m5stack/cmodules/cmodules.cmake index 22fc8f70..29e448cd 100644 --- a/m5stack/cmodules/cmodules.cmake +++ b/m5stack/cmodules/cmodules.cmake @@ -41,3 +41,7 @@ include(${CMAKE_CURRENT_LIST_DIR}/esp_zigbee_host/micropython.cmake) if(ADF_MODULE_ENABLE) include(${CMAKE_CURRENT_LIST_DIR}/adf_module/micropython.cmake) endif() + +if(MICROPY_PY_LVGL) +include(${CMAKE_CURRENT_LIST_DIR}/lv_binding_micropython/micropython.cmake) +endif() diff --git a/m5stack/cmodules/lv_binding_micropython b/m5stack/cmodules/lv_binding_micropython new file mode 160000 index 00000000..44f70b1e --- /dev/null +++ b/m5stack/cmodules/lv_binding_micropython @@ -0,0 +1 @@ +Subproject commit 44f70b1e1adb087e00ea5d39fe45a0b0f3551646 diff --git a/m5stack/cmodules/m5unified/m5unified.cmake b/m5stack/cmodules/m5unified/m5unified.cmake index 62de3417..8886793d 100644 --- a/m5stack/cmodules/m5unified/m5unified.cmake +++ b/m5stack/cmodules/m5unified/m5unified.cmake @@ -30,6 +30,8 @@ endif() target_link_libraries(usermod INTERFACE usermod_M5UNIFIED) +# target_compile_options(usermod_M5UNIFIED INTERFACE "-g") + set_source_files_properties( ${CMAKE_CURRENT_LIST_DIR}/m5unified.c PROPERTIES COMPILE_FLAGS diff --git a/m5stack/cmodules/m5unified/m5unified.h b/m5stack/cmodules/m5unified/m5unified.h index b270fb99..604d0934 100644 --- a/m5stack/cmodules/m5unified/m5unified.h +++ b/m5stack/cmodules/m5unified/m5unified.h @@ -7,9 +7,9 @@ #include #if MICROPY_PY_LVGL -#include "lvgl/lvgl.h" -#include "lvgl/src/hal/lv_hal_disp.h" -#include "./../../components/lv_bindings/driver/include/common.h" +#include "./../../cmodules/lv_binding_micropython/lvgl/lvgl.h" +// #include "./../../cmodules/lv_binding_micropython/lvgl/src/hal/lv_hal_disp.h" +#include "./../../cmodules/lv_binding_micropython/driver/include/common.h" #endif /* *FORMAT-OFF* */ diff --git a/m5stack/cmodules/m5unified/m5unified_gfx.c b/m5stack/cmodules/m5unified/m5unified_gfx.c index a9f73b6f..640dea3d 100644 --- a/m5stack/cmodules/m5unified/m5unified_gfx.c +++ b/m5stack/cmodules/m5unified/m5unified_gfx.c @@ -16,9 +16,9 @@ extern mp_obj_t user_panel_make_new(const mp_obj_type_t *type, size_t n_args, si // -------- lvgl port funciton #if MICROPY_PY_LVGL -extern void gfx_lvgl_flush(void *_disp_drv, const lv_area_t *area, lv_color_t *color_p); -extern void user_lvgl_flush(void *_disp_drv, const lv_area_t *area, lv_color_t *color_p); -extern bool gfx_lvgl_touch_read(lv_indev_drv_t *indev_drv, lv_indev_data_t *data); +extern void gfx_lvgl_flush(lv_display_t *disp_drv, const lv_area_t *area, uint8_t *px_map); +extern void user_lvgl_flush(lv_disp_t *disp_drv, const lv_area_t *area, uint8_t *px_map); +extern bool gfx_lvgl_touch_read(lv_disp_t *indev_drv, lv_indev_data_t *data); DEFINE_PTR_OBJ(gfx_lvgl_flush); DEFINE_PTR_OBJ(user_lvgl_flush); DEFINE_PTR_OBJ(gfx_lvgl_touch_read); diff --git a/m5stack/components/M5Unified/mpy_lvgl.txt b/m5stack/components/M5Unified/mpy_lvgl.txt index 6eb1e131..7cfd61fb 100644 --- a/m5stack/components/M5Unified/mpy_lvgl.txt +++ b/m5stack/components/M5Unified/mpy_lvgl.txt @@ -1,12 +1,14 @@ #if MICROPY_PY_LVGL #include "lvgl/lvgl.h" -#include "lvgl/src/hal/lv_hal_disp.h" - -void gfx_lvgl_flush(void *_disp_drv, const lv_area_t *area, lv_color_t *color_p) { - lv_disp_drv_t *disp_drv = (lv_disp_drv_t*)_disp_drv; - - int idx = mp_obj_get_int(mp_obj_dict_get(disp_drv->user_data, MP_OBJ_NEW_QSTR(MP_QSTR_display_index))); - LovyanGFX *lvgl_gfx = (LovyanGFX *)&(M5.getDisplay(idx)); +// #include "lvgl/src/hal/lv_hal_disp.h" +#include "mpy_m5gfx.h" +#include "esp_log.h" + +void gfx_lvgl_flush(lv_display_t *disp_drv, const lv_area_t * area, uint8_t * px_map) +{ + mp_obj_t gfx_obj = mp_obj_dict_get(disp_drv->user_data, MP_OBJ_NEW_QSTR(MP_QSTR_display)); + M5GFX *lvgl_gfx = (M5GFX *)((((gfx_obj_t *)MP_OBJ_TO_PTR(gfx_obj))->gfx)); + // ESP_LOGE("lvgl", "gfx: %p", lvgl_gfx); if (lvgl_gfx == nullptr) { return; } @@ -16,12 +18,13 @@ void gfx_lvgl_flush(void *_disp_drv, const lv_area_t *area, lv_color_t *color_p) lvgl_gfx->startWrite(); lvgl_gfx->setAddrWindow(area->x1, area->y1, w, h); - lvgl_gfx->writePixels((lgfx::rgb565_t *)&color_p->full, w * h); + lvgl_gfx->writePixels((lgfx::rgb565_t *)px_map, w * h); lvgl_gfx->endWrite(); - lv_disp_flush_ready(disp_drv); + lv_display_flush_ready(disp_drv); } -bool gfx_lvgl_touch_read(lv_indev_drv_t *indev_drv, lv_indev_data_t *data) { +bool gfx_lvgl_touch_read(lv_disp_t *indev_drv, lv_indev_data_t *data) +{ M5.update(); if (!M5.Touch.getCount()) { @@ -37,16 +40,16 @@ bool gfx_lvgl_touch_read(lv_indev_drv_t *indev_drv, lv_indev_data_t *data) { } -void user_lvgl_flush(void *_disp_drv, const lv_area_t *area, lv_color_t *color_p) { - lv_disp_drv_t *disp_drv = (lv_disp_drv_t*)_disp_drv; +void user_lvgl_flush(lv_disp_t *disp_drv, const lv_area_t *area, uint8_t *px_map) +{ int w = (area->x2 - area->x1 + 1); int h = (area->y2 - area->y1 + 1); user_panel.startWrite(); user_panel.setAddrWindow(area->x1, area->y1, w, h); - user_panel.writePixels((lgfx::rgb565_t *)&color_p->full, w * h); + user_panel.writePixels((lgfx::rgb565_t *)px_map, w * h); user_panel.endWrite(); - lv_disp_flush_ready((lv_disp_drv_t *)disp_drv); + lv_display_flush_ready(disp_drv); } #endif diff --git a/m5stack/components/M5Unified/mpy_m5gfx.cpp b/m5stack/components/M5Unified/mpy_m5gfx.cpp index b7a86ee3..7f091d47 100644 --- a/m5stack/components/M5Unified/mpy_m5gfx.cpp +++ b/m5stack/components/M5Unified/mpy_m5gfx.cpp @@ -25,8 +25,8 @@ extern "C" #if MICROPY_PY_LVGL #include "lvgl/lvgl.h" -#include "lvgl/src/hal/lv_hal_disp.h" -#include "./../../components/lv_bindings/driver/include/common.h" +// #include "lvgl/src/hal/lv_hal_disp.h" +// #include "./../../components/lv_bindings/driver/include/common.h" #endif #include "mpy_m5lfs2.txt" diff --git a/m5stack/components/M5Unified/mpy_m5unified.cpp b/m5stack/components/M5Unified/mpy_m5unified.cpp index 1eb6d205..fd8f010a 100644 --- a/m5stack/components/M5Unified/mpy_m5unified.cpp +++ b/m5stack/components/M5Unified/mpy_m5unified.cpp @@ -550,13 +550,16 @@ mp_obj_t m5_getDisplayCount(void) { } mp_obj_t m5_Displays(mp_obj_t index) { - m5_display.gfx = (void *)&(M5.Displays(mp_obj_get_int(index))); - return MP_OBJ_FROM_PTR(&m5_display); + gfx_obj_t *o = mp_obj_malloc_with_finaliser(gfx_obj_t, &mp_gfxdevice_type); + o->gfx = (void *)&(M5.Displays(mp_obj_get_int(index))); + return MP_OBJ_FROM_PTR(o); } mp_obj_t m5_getDisplay(mp_obj_t index) { - m5_display.gfx = (void *)&(M5.getDisplay(mp_obj_get_int(index))); - return MP_OBJ_FROM_PTR(&m5_display); + mp_printf(&mp_plat_print, "display index: %d\n", mp_obj_get_int(index)); + gfx_obj_t *o = mp_obj_malloc_with_finaliser(gfx_obj_t, &mp_gfxdevice_type); + o->gfx = (void *)&(M5.getDisplay(mp_obj_get_int(index))); + return MP_OBJ_FROM_PTR(o); } mp_obj_t m5_getDisplayIndex(mp_obj_t board) { diff --git a/m5stack/libs/lvgl/fs_driver.py b/m5stack/libs/lvgl/fs_driver.py new file mode 100644 index 00000000..0ca2b18b --- /dev/null +++ b/m5stack/libs/lvgl/fs_driver.py @@ -0,0 +1,90 @@ +""" +Original author: mhepp(https://forum.lvgl.io/u/mhepp/summary) +""" + +import lvgl as lv +import struct + + +def fs_open_cb(drv, path, mode): + if mode == lv.FS_MODE.WR: + p_mode = "wb" + elif mode == lv.FS_MODE.RD: + p_mode = "rb" + elif mode == lv.FS_MODE.WR | lv.FS_MODE.RD: + p_mode = "rb+" + else: + raise RuntimeError("fs_open_callback() - open mode error, %s is invalid mode" % mode) + + try: + f = open(path, p_mode) + + except OSError as e: + raise RuntimeError("fs_open_callback(%s) exception: %s" % (path, e)) + + return {"file": f, "path": path} + + +def fs_close_cb(drv, fs_file): + try: + fs_file.__cast__()["file"].close() + except OSError as e: + raise RuntimeError("fs_close_callback(%s) exception: %s" % (fs_file.__cast__()["path"], e)) + + return lv.FS_RES.OK + + +def fs_read_cb(drv, fs_file, buf, btr, br): + try: + tmp_data = fs_file.__cast__()["file"].read(btr) + buf.__dereference__(btr)[0 : len(tmp_data)] = tmp_data + br.__dereference__(4)[0:4] = struct.pack("= 0: + fs_drv.cache_size = cache_size + + fs_drv.register() diff --git a/m5stack/libs/lvgl/lv_utils.py b/m5stack/libs/lvgl/lv_utils.py new file mode 100644 index 00000000..a8d51e1f --- /dev/null +++ b/m5stack/libs/lvgl/lv_utils.py @@ -0,0 +1,190 @@ +############################################################################## +# Event Loop module: advancing tick count and scheduling lvgl task handler. +# Import after lvgl module. +# This should be imported and used by display driver. +# Display driver should first check if an event loop is already running. +# +# Usage example with SDL: +# +# SDL.init(auto_refresh=False) +# # Register SDL display driver. +# # Register SDL mouse driver +# event_loop = lv_utils.event_loop() +# +# +# asyncio example with SDL: +# +# SDL.init(auto_refresh=False) +# # Register SDL display driver. +# # Register SDL mouse driver +# event_loop = lv_utils.event_loop(asynchronous=True) +# asyncio.Loop.run_forever() +# +# asyncio example with ili9341: +# +# event_loop = lv_utils.event_loop(asynchronous=True) # Optional! +# self.disp = ili9341(asynchronous=True) +# asyncio.Loop.run_forever() +# +# MIT license; Copyright (c) 2021 Amir Gonnen +# +############################################################################## + +import lvgl as lv +import micropython +import sys + +# Try standard machine.Timer, or custom timer from lv_timer, if available + +try: + from machine import Timer +except: + try: + from lv_timer import Timer + except: + if sys.platform != "darwin": + raise RuntimeError("Missing machine.Timer implementation!") + Timer = False + +# Try to determine default timer id + +default_timer_id = 0 +if sys.platform == "pyboard": + # stm32 only supports SW timer -1 + default_timer_id = -1 + +if sys.platform == "rp2": + # rp2 only supports SW timer -1 + default_timer_id = -1 + +# Try importing asyncio, if available + +try: + import asyncio + + asyncio_available = True +except: + asyncio_available = False + +############################################################################## + + +class event_loop: + _current_instance = None + + def __init__( + self, + freq=25, + timer_id=default_timer_id, + max_scheduled=2, + refresh_cb=None, + asynchronous=False, + exception_sink=None, + ): + if self.is_running(): + raise RuntimeError("Event loop is already running!") + + if not lv.is_initialized(): + lv.init() + + event_loop._current_instance = self + + self.delay = 1000 // freq + self.refresh_cb = refresh_cb + self.exception_sink = exception_sink if exception_sink else self.default_exception_sink + + self.asynchronous = asynchronous + if self.asynchronous: + if not asyncio_available: + raise RuntimeError("Cannot run asynchronous event loop. asyncio is not available!") + self.refresh_event = asyncio.Event() + self.refresh_task = asyncio.create_task(self.async_refresh()) + self.timer_task = asyncio.create_task(self.async_timer()) + else: + if Timer: + self.timer = Timer(timer_id) + self.timer.init(mode=Timer.PERIODIC, period=self.delay, callback=self.timer_cb) + self.task_handler_ref = self.task_handler # Allocation occurs here + self.max_scheduled = max_scheduled + self.scheduled = 0 + + def init_async(self): + self.refresh_event = asyncio.Event() + self.refresh_task = asyncio.create_task(self.async_refresh()) + self.timer_task = asyncio.create_task(self.async_timer()) + + def deinit(self): + if self.asynchronous: + self.refresh_task.cancel() + self.timer_task.cancel() + else: + if Timer: + self.timer.deinit() + event_loop._current_instance = None + + def disable(self): + self.scheduled += self.max_scheduled + + def enable(self): + self.scheduled -= self.max_scheduled + + @staticmethod + def is_running(): + return event_loop._current_instance is not None + + @staticmethod + def current_instance(): + return event_loop._current_instance + + def task_handler(self, _): + try: + if lv._nesting.value == 0: + lv.task_handler() + if self.refresh_cb: + self.refresh_cb() + self.scheduled -= 1 + except Exception as e: + if self.exception_sink: + self.exception_sink(e) + + def tick(self): + self.timer_cb(None) + + def run(self): + if sys.platform == "darwin": + while True: + self.tick() + + def timer_cb(self, t): + # Can be called in Interrupt context + # Use task_handler_ref since passing self.task_handler would cause allocation. + lv.tick_inc(self.delay) + if self.scheduled < self.max_scheduled: + try: + micropython.schedule(self.task_handler_ref, 0) + self.scheduled += 1 + except: + pass + + async def async_refresh(self): + while True: + await self.refresh_event.wait() + if lv._nesting.value == 0: + self.refresh_event.clear() + try: + lv.task_handler() + except Exception as e: + if self.exception_sink: + self.exception_sink(e) + if self.refresh_cb: + self.refresh_cb() + + async def async_timer(self): + while True: + await asyncio.sleep_ms(self.delay) + lv.tick_inc(self.delay) + self.refresh_event.set() + + def default_exception_sink(self, e): + sys.print_exception(e) + # event_loop.current_instance().deinit() diff --git a/m5stack/libs/lvgl/manifest.py b/m5stack/libs/lvgl/manifest.py new file mode 100644 index 00000000..778eca7d --- /dev/null +++ b/m5stack/libs/lvgl/manifest.py @@ -0,0 +1,6 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +module("lv_utils.py") +module("fs_driver.py") diff --git a/m5stack/libs/manifest.py b/m5stack/libs/manifest.py index e3f0b88f..1a1db72d 100644 --- a/m5stack/libs/manifest.py +++ b/m5stack/libs/manifest.py @@ -9,6 +9,7 @@ include("hardware/manifest.py") include("hat/manifest.py") include("image_plus/manifest.py") +include("lvgl/manifest.py") include("m5ble/manifest.py") include("m5espnow/manifest.py") include("modbus/modbus/manifest.py") diff --git a/m5stack/partitions_16mb.csv b/m5stack/partitions_16mb.csv index 4315d48d..034817bb 100644 --- a/m5stack/partitions_16mb.csv +++ b/m5stack/partitions_16mb.csv @@ -2,6 +2,6 @@ # Note: if you change the phy_init or app partition offset, make sure to change the offset in Kconfig.projbuild nvs, data, nvs, 0x9000, 0x6000, phy_init, data, phy, 0xf000, 0x1000, -factory, app, factory, 0x10000, 0x510000, -sys, data, fat, 0x520000, 0x100000, -vfs, data, fat, 0x620000, 0x9df000, +factory, app, factory, 0x10000, 0x710000, +sys, data, fat, 0x720000, 0x100000, +vfs, data, fat, 0x820000, 0x7df000, diff --git a/m5stack/patches/0003-avoid-lv_bindings-compile-error.patch b/m5stack/patches/0003-avoid-lv_bindings-compile-error.patch new file mode 100644 index 00000000..88c17650 --- /dev/null +++ b/m5stack/patches/0003-avoid-lv_bindings-compile-error.patch @@ -0,0 +1,57 @@ +diff --git a/driver/include/common.h b/driver/include/common.h +index 5cec282..c9f45d2 100644 +--- a/driver/include/common.h ++++ b/driver/include/common.h +@@ -16,7 +16,7 @@ typedef struct mp_ptr_t + void *ptr; + } mp_ptr_t; + +-STATIC mp_int_t mp_ptr_get_buffer(mp_obj_t self_in, mp_buffer_info_t *bufinfo, mp_uint_t flags) ++static mp_int_t mp_ptr_get_buffer(mp_obj_t self_in, mp_buffer_info_t *bufinfo, mp_uint_t flags) + { + mp_ptr_t *self = MP_OBJ_TO_PTR(self_in); + +@@ -34,7 +34,7 @@ STATIC mp_int_t mp_ptr_get_buffer(mp_obj_t self_in, mp_buffer_info_t *bufinfo, m + #define PTR_OBJ(ptr_global) ptr_global ## _obj + + #define DEFINE_PTR_OBJ_TYPE(ptr_obj_type, ptr_type_qstr)\ +-STATIC MP_DEFINE_CONST_OBJ_TYPE(\ ++static MP_DEFINE_CONST_OBJ_TYPE(\ + ptr_obj_type,\ + ptr_type_qstr,\ + MP_TYPE_FLAG_NONE,\ +@@ -43,7 +43,7 @@ STATIC MP_DEFINE_CONST_OBJ_TYPE(\ + + #define DEFINE_PTR_OBJ(ptr_global)\ + DEFINE_PTR_OBJ_TYPE(ptr_global ## _type, MP_QSTR_ ## ptr_global);\ +-STATIC const mp_ptr_t PTR_OBJ(ptr_global) = {\ ++static const mp_ptr_t PTR_OBJ(ptr_global) = {\ + { &ptr_global ## _type },\ + &ptr_global\ + } +diff --git a/lib/fs_driver.py b/lib/fs_driver.py +index bac84b3..0e0a989 100644 +--- a/lib/fs_driver.py ++++ b/lib/fs_driver.py +@@ -3,7 +3,7 @@ Original author: mhepp(https://forum.lvgl.io/u/mhepp/summary) + ''' + + import lvgl as lv +-import ustruct as struct ++import struct as struct + + def fs_open_cb(drv, path, mode): + +diff --git a/lv_conf.h b/lv_conf.h +index 2e0d08d..246ec8c 100644 +--- a/lv_conf.h ++++ b/lv_conf.h +@@ -704,7 +704,7 @@ extern void mp_lv_deinit_gc(); + #define LV_USE_LIBPNG 0 + + /*BMP decoder library*/ +-#define LV_USE_BMP 0 ++#define LV_USE_BMP 1 + + /* JPG + split JPG decoder library. + * Split JPG is a custom format optimized for embedded systems. */ diff --git a/tests/lvgl/image.py b/tests/lvgl/image.py new file mode 100644 index 00000000..3638201d --- /dev/null +++ b/tests/lvgl/image.py @@ -0,0 +1,52 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import sys +import M5 +import lvgl as lv +import fs_driver + +M5.begin() + +# lvgl init +M5.Lcd.lvgl_init() + +# built-in display +disp_buf0 = lv.draw_buf_create(M5.getDisplay(0).width(), 10, lv.COLOR_FORMAT.RGB565, 0) +disp_buf1 = lv.draw_buf_create(M5.getDisplay(0).width(), 10, lv.COLOR_FORMAT.RGB565, 0) + +disp_drv = lv.display_create(M5.getDisplay(0).width(), M5.getDisplay(0).height()) +disp_drv.set_color_format(lv.COLOR_FORMAT.RGB565) + +disp_drv.set_draw_buffers(disp_buf0, disp_buf1) +disp_drv.set_flush_cb(M5.Lcd.lvgl_flush) +disp_drv.set_user_data({"display": M5.Lcd}) +disp_drv.set_render_mode(lv.DISPLAY_RENDER_MODE.PARTIAL) + +# touch driver init +indev_drv = lv.indev_create() +indev_drv.set_type(lv.INDEV_TYPE.POINTER) +# indev_drv.set_display(lv.display_get_default()) +indev_drv.set_display(disp_drv) +indev_drv.set_read_cb(M5.Lcd.lvgl_read) + +# fs driver init +fs_drv = lv.fs_drv_t() +fs_driver.fs_register(fs_drv, "S") + + +scr = lv.obj() +img0 = lv.image(scr) +img0.set_src("S:/flash/res/img/uiflow.jpg") +img0.set_pos(0, 0) + +img1 = lv.image(scr) +img1.set_src("S:/flash/res/img/uiflow.png") +img1.set_pos(0, 80) + +img2 = lv.image(scr) +img2.set_src("S:/flash/res/img/uiflow.bmp") +img2.set_pos(0, 160) + +lv.screen_load(scr) diff --git a/tests/lvgl/label.py b/tests/lvgl/label.py new file mode 100644 index 00000000..bf47d154 --- /dev/null +++ b/tests/lvgl/label.py @@ -0,0 +1,62 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import sys +import M5 +import lvgl as lv +import fs_driver + +M5.begin() + +# lvgl init +M5.Lcd.lvgl_init() + +# built-in display +disp_buf0 = lv.draw_buf_create(M5.getDisplay(0).width(), 10, lv.COLOR_FORMAT.RGB565, 0) +disp_buf1 = lv.draw_buf_create(M5.getDisplay(0).width(), 10, lv.COLOR_FORMAT.RGB565, 0) + +disp_drv = lv.display_create(M5.getDisplay(0).width(), M5.getDisplay(0).height()) +disp_drv.set_color_format(lv.COLOR_FORMAT.RGB565) + +disp_drv.set_draw_buffers(disp_buf0, disp_buf1) +disp_drv.set_flush_cb(M5.Lcd.lvgl_flush) +disp_drv.set_user_data({"display": M5.Lcd}) +disp_drv.set_render_mode(lv.DISPLAY_RENDER_MODE.PARTIAL) + +# touch driver init +indev_drv = lv.indev_create() +indev_drv.set_type(lv.INDEV_TYPE.POINTER) +# indev_drv.set_display(lv.display_get_default()) +indev_drv.set_display(disp_drv) +indev_drv.set_read_cb(M5.Lcd.lvgl_read) + +# fs driver init +fs_drv = lv.fs_drv_t() +fs_driver.fs_register(fs_drv, "S") + + +scr = lv.screen_active() +scr.clean() + +myfont_cn = lv.binfont_create("S:/flash/res/font/font-PHT-cn-20.bin") + +label1 = lv.label(scr) +label1.set_style_text_font(myfont_cn, 0) # set the font +label1.set_text("上中下乎") +label1.align(lv.ALIGN.CENTER, 0, -25) + +myfont_en = lv.binfont_create("S:/flash/res/font/font-PHT-en-20.bin") + +label2 = lv.label(scr) +label2.set_style_text_font(myfont_en, 0) # set the font +label2.set_text("Hello LVGL!") +label2.align(lv.ALIGN.CENTER, 0, 25) + + +myfont_jp = lv.binfont_create("S:/flash/res/font/font-PHT-jp-20.bin") + +label3 = lv.label(scr) +label3.set_style_text_font(myfont_jp, 0) # set the font +label3.set_text("こんにちはありがとう") +label3.align(lv.ALIGN.CENTER, 0, 0) diff --git a/tests/lvgl/multiple_display.py b/tests/lvgl/multiple_display.py index b6864453..b30b88da 100644 --- a/tests/lvgl/multiple_display.py +++ b/tests/lvgl/multiple_display.py @@ -6,86 +6,91 @@ import sys import M5 import lvgl as lv +import fs_driver +from hardware import I2C +from hardware import Pin +from unit import LCDUnit +from unit import OLEDUnit M5.begin() +i2c0 = I2C(0, scl=Pin(33), sda=Pin(32), freq=100000) +lcd_0 = LCDUnit(i2c0, 0x3E) +# oled_0 = OLEDUnit(i2c0, 0x3c) + # lvgl init M5.Lcd.lvgl_init() -# create a display 0 buffer -disp_buf0 = lv.disp_draw_buf_t() -buf1_0 = bytearray(M5.getDisplay(0).width() * 10) -disp_buf0.init(buf1_0, None, len(buf1_0) // lv.color_t.__SIZE__) - -# register display 0 driver -disp_drv_0 = lv.disp_drv_t() -disp_drv_0.init() -disp_drv_0.draw_buf = disp_buf0 -disp_drv_0.flush_cb = M5.Lcd.lvgl_flush -disp_drv_0.hor_res = M5.getDisplay(0).width() -disp_drv_0.ver_res = M5.getDisplay(0).height() -disp_drv_0.user_data = {"display_index": 0} -disp0 = disp_drv_0.register() - -# create a display 1 buffer -disp_buf1 = lv.disp_draw_buf_t() -buf1_1 = bytearray(M5.getDisplay(1).width() * 10) -disp_buf1.init(buf1_1, None, len(buf1_1) // lv.color_t.__SIZE__) - -# register display 1 driver -disp_drv_1 = lv.disp_drv_t() -disp_drv_1.init() -disp_drv_1.draw_buf = disp_buf1 -disp_drv_1.flush_cb = M5.Lcd.lvgl_flush -disp_drv_1.hor_res = M5.getDisplay(1).width() -disp_drv_1.ver_res = M5.getDisplay(1).height() -disp_drv_1.user_data = {"display_index": 1} -disp1 = disp_drv_1.register() - -# set default display to screen 0 -lv.disp_t.set_default(disp0) -scr0 = lv.obj() -# create button widget on screen 0 -btn0 = lv.btn(scr0) -btn0.align(lv.ALIGN.CENTER, 0, -50) -label0 = lv.label(btn0) -label0.set_text("LVGL Screen 0") -lv.scr_load(scr0) - -# set default display to screen 1 -lv.disp_t.set_default(disp1) -scr1 = lv.obj() -# create button widget on screen 1 -btn1 = lv.btn(scr1) -btn1.align(lv.ALIGN.CENTER, 0, -50) -label1 = lv.label(btn1) -label1.set_text("LVGL Screen 1") -lv.scr_load(scr1) +# built-in display +disp_buf0 = lv.draw_buf_create(M5.Lcd.width(), 10, lv.COLOR_FORMAT.RGB565, 0) +disp_buf1 = lv.draw_buf_create(M5.Lcd.width(), 10, lv.COLOR_FORMAT.RGB565, 0) + +disp_drv = lv.display_create(M5.Lcd.width(), M5.Lcd.height()) +disp_drv.set_color_format(lv.COLOR_FORMAT.RGB565) + +disp_drv.set_draw_buffers(disp_buf0, disp_buf1) +disp_drv.set_flush_cb(M5.Lcd.lvgl_flush) +disp_drv.set_user_data({"display": M5.Lcd}) +disp_drv.set_render_mode(lv.DISPLAY_RENDER_MODE.PARTIAL) + +# create a display 1 +disp_buf0 = lv.draw_buf_create(lcd_0.width(), 10, lv.COLOR_FORMAT.RGB565, 0) +disp_buf1 = lv.draw_buf_create(lcd_0.width(), 10, lv.COLOR_FORMAT.RGB565, 0) + +disp_drv1 = lv.display_create(lcd_0.width(), lcd_0.height()) +disp_drv1.set_color_format(lv.COLOR_FORMAT.RGB565) + +disp_drv1.set_draw_buffers(disp_buf0, disp_buf1) +disp_drv1.set_flush_cb(M5.Lcd.lvgl_flush) +disp_drv1.set_user_data({"display": lcd_0}) +disp_drv1.set_render_mode(lv.DISPLAY_RENDER_MODE.PARTIAL) + +# create a display 2 +# disp_buf0 = lv.draw_buf_create(oled_0.width(), 10, lv.COLOR_FORMAT.RGB565, 0) +# disp_buf1 = lv.draw_buf_create(oled_0.width(), 10, lv.COLOR_FORMAT.RGB565, 0) + +# disp_drv2 = lv.display_create(oled_0.width(), oled_0.height()) +# disp_drv2.set_color_format(lv.COLOR_FORMAT.RGB565) + +# disp_drv2.set_draw_buffers(disp_buf0, disp_buf1) +# disp_drv2.set_flush_cb(M5.Lcd.lvgl_flush) +# disp_drv2.set_user_data({"display": oled_0}) +# disp_drv2.set_render_mode(lv.DISPLAY_RENDER_MODE.PARTIAL) # touch driver init -indev_drv = lv.indev_drv_t() -indev_drv.init() -indev_drv.disp = disp0 # input device assigned to display 0 -indev_drv.type = lv.INDEV_TYPE.POINTER -indev_drv.read_cb = M5.Lcd.lvgl_read -indev = indev_drv.register() - -# Create an image from the jpg file -try: - with open("res/img/uiflow.jpg", "rb") as f: - jpg_data = f.read() -except: - print("Could not find uiflow.jpg") - sys.exit() - -img_cogwheel_argb = lv.img_dsc_t({"data_size": len(jpg_data), "data": jpg_data}) - -# show image on screen 0 -img0 = lv.img(scr0) -img0.set_src(img_cogwheel_argb) -img0.align(lv.ALIGN.CENTER, 0, 0) - -# show image on screen 1 -img1 = lv.img(scr1) -img1.set_src(img_cogwheel_argb) -img1.align(lv.ALIGN.CENTER, 0, 0) +indev_drv = lv.indev_create() +indev_drv.set_type(lv.INDEV_TYPE.POINTER) +# indev_drv.set_display(lv.display_get_default()) +indev_drv.set_display(disp_drv) +indev_drv.set_read_cb(M5.Lcd.lvgl_read) + +# fs driver init +fs_drv = lv.fs_drv_t() +fs_driver.fs_register(fs_drv, "S") + +# display 0 +disp_drv.set_default() +scr = lv.screen_active() +scr.clean() + +img0 = lv.image(scr) +img0.set_src("S:/flash/res/img/uiflow.jpg") +img0.set_pos(0, 0) + +# display 1 +disp_drv1.set_default() +scr = lv.screen_active() +scr.clean() + +img0 = lv.image(scr) +img0.set_src("S:/flash/res/img/uiflow.jpg") +img0.set_pos(0, 0) + +# display 2 +# disp_drv2.set_default() +# scr = lv.screen_active() +# scr.clean() + +# img0 = lv.image(scr) +# img0.set_src("S:/flash/res/img/uiflow.jpg") +# img0.set_pos(0, 0) From a7a022b99c8a026d4dbefa6624e79b75118f56cc Mon Sep 17 00:00:00 2001 From: lbuque Date: Mon, 14 Apr 2025 10:52:20 +0800 Subject: [PATCH 068/322] libs/lv_utils: Add lv_utils module. Signed-off-by: lbuque --- m5stack/cmodules/adf_module/vfs_stream.c | 1 + m5stack/cmodules/cmodules.cmake | 1 + m5stack/cmodules/lv_utils/lv_utils.cmake | 21 ++ m5stack/cmodules/lv_utils/modlv_utils.c | 236 ++++++++++++++++++++ m5stack/cmodules/m5unified/m5unified.cmake | 2 +- m5stack/components/M5Unified/mpy_lvgl.txt | 1 - m5stack/components/M5Unified/mpy_m5lfs2.txt | 2 + m5stack/libs/lv_utils/__init__.py | 22 ++ m5stack/libs/{lvgl => lv_utils}/lv_utils.py | 0 m5stack/libs/{lvgl => lv_utils}/manifest.py | 8 +- m5stack/libs/lvgl/fs_driver.py | 90 -------- m5stack/libs/manifest.py | 2 +- tests/lvgl/image.py | 4 +- tests/lvgl/label.py | 24 +- tests/lvgl/multiple_display.py | 27 +-- 15 files changed, 313 insertions(+), 128 deletions(-) create mode 100644 m5stack/cmodules/lv_utils/lv_utils.cmake create mode 100644 m5stack/cmodules/lv_utils/modlv_utils.c create mode 100644 m5stack/libs/lv_utils/__init__.py rename m5stack/libs/{lvgl => lv_utils}/lv_utils.py (100%) rename m5stack/libs/{lvgl => lv_utils}/manifest.py (53%) delete mode 100644 m5stack/libs/lvgl/fs_driver.py diff --git a/m5stack/cmodules/adf_module/vfs_stream.c b/m5stack/cmodules/adf_module/vfs_stream.c index c4de2f45..f77e804a 100644 --- a/m5stack/cmodules/adf_module/vfs_stream.c +++ b/m5stack/cmodules/adf_module/vfs_stream.c @@ -59,6 +59,7 @@ typedef enum { STREAM_TYPE_AMRWB, } wr_stream_type_t; +// micropython/extmod/vfs_lfs.c line: 115 typedef struct _mp_obj_vfs_lfs2_t { mp_obj_base_t base; mp_vfs_blockdev_t blockdev; diff --git a/m5stack/cmodules/cmodules.cmake b/m5stack/cmodules/cmodules.cmake index 29e448cd..8e74c117 100644 --- a/m5stack/cmodules/cmodules.cmake +++ b/m5stack/cmodules/cmodules.cmake @@ -44,4 +44,5 @@ endif() if(MICROPY_PY_LVGL) include(${CMAKE_CURRENT_LIST_DIR}/lv_binding_micropython/micropython.cmake) +include(${CMAKE_CURRENT_LIST_DIR}/lv_utils/lv_utils.cmake) endif() diff --git a/m5stack/cmodules/lv_utils/lv_utils.cmake b/m5stack/cmodules/lv_utils/lv_utils.cmake new file mode 100644 index 00000000..758b191a --- /dev/null +++ b/m5stack/cmodules/lv_utils/lv_utils.cmake @@ -0,0 +1,21 @@ +# SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +add_library(usermod_lv_utils INTERFACE) + +target_sources(usermod_lv_utils INTERFACE + ${CMAKE_CURRENT_LIST_DIR}/modlv_utils.c +) + +target_include_directories(usermod_lv_utils INTERFACE + ${CMAKE_CURRENT_LIST_DIR} +) + +target_link_libraries(usermod INTERFACE usermod_lv_utils) + +set_source_files_properties( + ${CMAKE_CURRENT_LIST_DIR}/modlv_utils.c + PROPERTIES COMPILE_FLAGS + "-Wno-discarded-qualifiers -Wno-implicit-int" +) diff --git a/m5stack/cmodules/lv_utils/modlv_utils.c b/m5stack/cmodules/lv_utils/modlv_utils.c new file mode 100644 index 00000000..9fc06774 --- /dev/null +++ b/m5stack/cmodules/lv_utils/modlv_utils.c @@ -0,0 +1,236 @@ +/* + * SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD + * + * SPDX-License-Identifier: MIT + */ + +#include +#include "py/runtime.h" +#include "py/stream.h" + +#include +#include +#include "extmod/vfs_fat.h" +#include "lib/littlefs/lfs2.h" +#include "lib/oofatfs/ff.h" + +#include "lvgl/lvgl.h" +#include "./../../cmodules/lv_binding_micropython/driver/include/common.h" +#include "string.h" + +static const char *TAG = "lv_utils"; + +// micropython/extmod/vfs_lfs.c line: 115 +typedef struct _mp_obj_vfs_lfs2_t { + mp_obj_base_t base; + mp_vfs_blockdev_t blockdev; + bool enable_mtime; + vstr_t cur_dir; + struct lfs2_config config; + lfs2_t lfs; +} mp_obj_vfs_lfs2_t; + +typedef struct vfs_stream_t { + bool is_open; + FATFS *fatfs; + lfs2_t *lfs2; + struct lfs2_file_config lfs2_file_conf; + union { + FIL fat_file; + lfs2_file_t *lfs2_file; + } file; +} vfs_stream_t; + + +static void *lv_utils_fs_open_cb(lv_fs_drv_t *drv, const char *path, lv_fs_mode_t mode) { + LV_UNUSED(drv); + lv_fs_res_t res = LV_FS_RES_NOT_IMP; + + ESP_LOGI("lv_utils", "fs_open_cb: path=%s, mode=%d\n", path, mode); + + vfs_stream_t *vfs = lv_malloc(sizeof(vfs_stream_t)); + + const char *path_out; + mp_vfs_mount_t *existing_mount = mp_vfs_lookup_path(path, &path_out); + if (existing_mount == MP_VFS_NONE || existing_mount == MP_VFS_ROOT) { + ESP_LOGE(TAG, "No vfs mount"); + goto _vfs_init_exit; + } + if (strstr(path, "flash")) { + ESP_LOGD(TAG, "in flash"); + vfs->lfs2 = &((mp_obj_vfs_lfs2_t *)MP_OBJ_TO_PTR(existing_mount->obj))->lfs; + } else if (strstr(path, "sd")) { + ESP_LOGD(TAG, "in sd"); + vfs->fatfs = &((fs_user_mount_t *)MP_OBJ_TO_PTR(existing_mount->obj))->fatfs; + } + + if (vfs->lfs2) { + vfs->lfs2_file_conf.buffer = lv_malloc(vfs->lfs2->cfg->cache_size * sizeof(uint8_t)); + vfs->file.lfs2_file = (lfs2_file_t *)lv_malloc(1 * sizeof(lfs2_file_t)); + int flags = 0; + if (mode == LV_FS_MODE_WR) { + flags = LFS2_O_WRONLY; + } else if (mode == LV_FS_MODE_RD) { + flags = LFS2_O_RDONLY; + } else if (mode == (LV_FS_MODE_WR | LV_FS_MODE_RD)) { + flags = LFS2_O_RDWR; + } + int res = lfs2_file_opencfg(vfs->lfs2, vfs->file.lfs2_file, path_out, flags, &vfs->lfs2_file_conf); + if (res != LFS2_ERR_OK) { + ESP_LOGE(TAG, "failed to open %s(%d)", path_out, res); + goto _vfs_init_exit; + } + } else if (vfs->fatfs) { + BYTE fmode = 0; + if (mode == LV_FS_MODE_WR) { + fmode = FA_WRITE; + } else if (mode == LV_FS_MODE_RD) { + fmode = FA_READ; + } else if (mode == (LV_FS_MODE_WR | LV_FS_MODE_RD)) { + fmode = FA_OPEN_ALWAYS; + } + FRESULT ret = f_open(vfs->fatfs, &vfs->file.fat_file, path_out, fmode); + if (ret != FR_OK) { + ESP_LOGE(TAG, "failed to open %s(%d)", path_out, ret); + goto _vfs_init_exit; + } + } + + return vfs; +_vfs_init_exit: + if (vfs->lfs2_file_conf.buffer) { + lv_free(vfs->lfs2_file_conf.buffer); + } + if (vfs->file.lfs2_file) { + lv_free(vfs->file.lfs2_file); + } + lv_free(vfs); + + return NULL; +} +DEFINE_PTR_OBJ(lv_utils_fs_open_cb); + + +static lv_fs_res_t lv_utils_fs_read_cb(lv_fs_drv_t *drv, void *file_p, void *buf, uint32_t btr, uint32_t *br) { + LV_UNUSED(drv); + vfs_stream_t *vfs = file_p; + + if (vfs->lfs2) { + *br = lfs2_file_read(vfs->lfs2, vfs->file.lfs2_file, (uint8_t *)buf, btr); + return (int32_t)(*br) < 0 ? LV_FS_RES_UNKNOWN : LV_FS_RES_OK; + } else if (vfs->fatfs) { + FRESULT res = f_read(&vfs->file.fat_file, buf, btr, (UINT *)br); + return res == FR_OK ? LV_FS_RES_OK : LV_FS_RES_UNKNOWN; + } + + return LV_FS_RES_UNKNOWN; +} +DEFINE_PTR_OBJ(lv_utils_fs_read_cb); + + +static lv_fs_res_t lv_utils_fs_write_cb(lv_fs_drv_t *drv, void *file_p, const void *buf, uint32_t btw, uint32_t *bw) { + LV_UNUSED(drv); + vfs_stream_t *vfs = file_p; + + if (vfs->lfs2) { + *bw = lfs2_file_write(vfs->lfs2, vfs->file.lfs2_file, (uint8_t *)buf, btw); + lfs2_file_sync(vfs->lfs2, vfs->file.lfs2_file); + } else if (vfs->fatfs) { + f_write(&vfs->file.fat_file, (uint8_t *)buf, btw, (UINT *)bw); + f_sync(&vfs->file.fat_file); + } + return (int32_t)(*bw) < 0 ? LV_FS_RES_UNKNOWN : LV_FS_RES_OK; +} +DEFINE_PTR_OBJ(lv_utils_fs_write_cb); + + +static lv_fs_res_t lv_utils_fs_seek_cb(lv_fs_drv_t *drv, void *file_p, uint32_t pos, lv_fs_whence_t whence) { + LV_UNUSED(drv); + vfs_stream_t *vfs = file_p; + + if (vfs->lfs2) { + int mode = 0; + if (whence == LV_FS_SEEK_SET) { + mode = LFS2_SEEK_SET; + } else if (whence == LV_FS_SEEK_CUR) { + mode = LFS2_SEEK_CUR; + } else if (whence == LV_FS_SEEK_END) { + mode = LFS2_SEEK_END; + } + + int rc = lfs2_file_seek(vfs->lfs2, vfs->file.lfs2_file, pos, mode); + return rc < 0 ? LV_FS_RES_UNKNOWN : LV_FS_RES_OK; + } else if (vfs->fatfs) { + FRESULT res = FR_INT_ERR; + switch (whence) { + case LV_FS_SEEK_SET: + res = f_lseek(&vfs->file.fat_file, pos); + break; + case LV_FS_SEEK_CUR: + res = f_lseek(&vfs->file.fat_file, f_tell((FIL *)&vfs->file.fat_file) + pos); + break; + case LV_FS_SEEK_END: + res = f_lseek(&vfs->file.fat_file, f_size((FIL *)&vfs->file.fat_file) + pos); + break; + default: + break; + } + return res == FR_OK ? LV_FS_RES_OK : LV_FS_RES_UNKNOWN; + } + + return LV_FS_RES_UNKNOWN; +} +DEFINE_PTR_OBJ(lv_utils_fs_seek_cb); + + +static lv_fs_res_t lv_utils_fs_tell_cb(lv_fs_drv_t *drv, void *file_p, uint32_t *pos_p) { + LV_UNUSED(drv); + vfs_stream_t *vfs = file_p; + + if (vfs->lfs2) { + *pos_p = lfs2_file_tell(vfs->lfs2, vfs->file.lfs2_file); + } else if (vfs->fatfs) { + *pos_p = f_tell((FIL *)&vfs->file.fat_file); + } + + return (int32_t)(*pos_p) < 0 ? LV_FS_RES_UNKNOWN : LV_FS_RES_OK; +} +DEFINE_PTR_OBJ(lv_utils_fs_tell_cb); + +static lv_fs_res_t lv_utils_fs_close_cb(lv_fs_drv_t *drv, void *file_p) { + LV_UNUSED(drv); + vfs_stream_t *vfs = file_p; + + if (vfs->lfs2) { + lfs2_file_close(vfs->lfs2, vfs->file.lfs2_file); + lv_free(vfs->file.lfs2_file); + lv_free(vfs->lfs2_file_conf.buffer); + } else if (vfs->fatfs) { + f_close(&vfs->file.fat_file); + } + + return LV_FS_RES_OK; +} +DEFINE_PTR_OBJ(lv_utils_fs_close_cb); + + +static const mp_rom_map_elem_t lv_utils_module_globals_table[] = { + /* *FORMAT-OFF* */ + { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR__lv_utils) }, + { MP_ROM_QSTR(MP_QSTR_fs_open_cb), MP_ROM_PTR(&PTR_OBJ(lv_utils_fs_open_cb)) }, + { MP_ROM_QSTR(MP_QSTR_fs_read_cb), MP_ROM_PTR(&PTR_OBJ(lv_utils_fs_read_cb)) }, + { MP_ROM_QSTR(MP_QSTR_fs_write_cb), MP_ROM_PTR(&PTR_OBJ(lv_utils_fs_write_cb)) }, + { MP_ROM_QSTR(MP_QSTR_fs_seek_cb), MP_ROM_PTR(&PTR_OBJ(lv_utils_fs_seek_cb)) }, + { MP_ROM_QSTR(MP_QSTR_fs_tell_cb), MP_ROM_PTR(&PTR_OBJ(lv_utils_fs_tell_cb)) }, + { MP_ROM_QSTR(MP_QSTR_fs_close_cb), MP_ROM_PTR(&PTR_OBJ(lv_utils_fs_close_cb)) }, + /* *FORMAT-ON* */ +}; + +static MP_DEFINE_CONST_DICT(lv_utils_module_globals, lv_utils_module_globals_table); + +const mp_obj_module_t lv_utils_module = { + .base = {&mp_type_module}, + .globals = (mp_obj_dict_t *)&lv_utils_module_globals, +}; + +MP_REGISTER_MODULE(MP_QSTR__lv_utils, lv_utils_module); diff --git a/m5stack/cmodules/m5unified/m5unified.cmake b/m5stack/cmodules/m5unified/m5unified.cmake index 8886793d..55f1a6b0 100644 --- a/m5stack/cmodules/m5unified/m5unified.cmake +++ b/m5stack/cmodules/m5unified/m5unified.cmake @@ -30,7 +30,7 @@ endif() target_link_libraries(usermod INTERFACE usermod_M5UNIFIED) -# target_compile_options(usermod_M5UNIFIED INTERFACE "-g") +target_compile_options(usermod_M5UNIFIED INTERFACE "-g") set_source_files_properties( ${CMAKE_CURRENT_LIST_DIR}/m5unified.c diff --git a/m5stack/components/M5Unified/mpy_lvgl.txt b/m5stack/components/M5Unified/mpy_lvgl.txt index 7cfd61fb..9e7c22d1 100644 --- a/m5stack/components/M5Unified/mpy_lvgl.txt +++ b/m5stack/components/M5Unified/mpy_lvgl.txt @@ -8,7 +8,6 @@ void gfx_lvgl_flush(lv_display_t *disp_drv, const lv_area_t * area, uint8_t * px { mp_obj_t gfx_obj = mp_obj_dict_get(disp_drv->user_data, MP_OBJ_NEW_QSTR(MP_QSTR_display)); M5GFX *lvgl_gfx = (M5GFX *)((((gfx_obj_t *)MP_OBJ_TO_PTR(gfx_obj))->gfx)); - // ESP_LOGE("lvgl", "gfx: %p", lvgl_gfx); if (lvgl_gfx == nullptr) { return; } diff --git a/m5stack/components/M5Unified/mpy_m5lfs2.txt b/m5stack/components/M5Unified/mpy_m5lfs2.txt index e10ebc4b..dfee27dd 100644 --- a/m5stack/components/M5Unified/mpy_m5lfs2.txt +++ b/m5stack/components/M5Unified/mpy_m5lfs2.txt @@ -9,6 +9,8 @@ #include #include + +// micropython/extmod/vfs_lfs.c line: 115 typedef struct _mp_obj_vfs_lfs2_t { mp_obj_base_t base; mp_vfs_blockdev_t blockdev; diff --git a/m5stack/libs/lv_utils/__init__.py b/m5stack/libs/lv_utils/__init__.py new file mode 100644 index 00000000..52d72965 --- /dev/null +++ b/m5stack/libs/lv_utils/__init__.py @@ -0,0 +1,22 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import lvgl as lv +import _lv_utils + + +def fs_register(fs_drv, letter, cache_size=500): + fs_drv.init() + fs_drv.letter = ord(letter) + fs_drv.open_cb = _lv_utils.fs_open_cb + fs_drv.read_cb = _lv_utils.fs_read_cb + fs_drv.write_cb = _lv_utils.fs_write_cb + fs_drv.seek_cb = _lv_utils.fs_seek_cb + fs_drv.tell_cb = _lv_utils.fs_tell_cb + fs_drv.close_cb = _lv_utils.fs_close_cb + + if cache_size >= 0: + fs_drv.cache_size = cache_size + + fs_drv.register() diff --git a/m5stack/libs/lvgl/lv_utils.py b/m5stack/libs/lv_utils/lv_utils.py similarity index 100% rename from m5stack/libs/lvgl/lv_utils.py rename to m5stack/libs/lv_utils/lv_utils.py diff --git a/m5stack/libs/lvgl/manifest.py b/m5stack/libs/lv_utils/manifest.py similarity index 53% rename from m5stack/libs/lvgl/manifest.py rename to m5stack/libs/lv_utils/manifest.py index 778eca7d..9dba076c 100644 --- a/m5stack/libs/lvgl/manifest.py +++ b/m5stack/libs/lv_utils/manifest.py @@ -2,5 +2,9 @@ # # SPDX-License-Identifier: MIT -module("lv_utils.py") -module("fs_driver.py") +package( + "lv_utils", + ("__init__.py",), + base_path="..", + opt=0, +) diff --git a/m5stack/libs/lvgl/fs_driver.py b/m5stack/libs/lvgl/fs_driver.py deleted file mode 100644 index 0ca2b18b..00000000 --- a/m5stack/libs/lvgl/fs_driver.py +++ /dev/null @@ -1,90 +0,0 @@ -""" -Original author: mhepp(https://forum.lvgl.io/u/mhepp/summary) -""" - -import lvgl as lv -import struct - - -def fs_open_cb(drv, path, mode): - if mode == lv.FS_MODE.WR: - p_mode = "wb" - elif mode == lv.FS_MODE.RD: - p_mode = "rb" - elif mode == lv.FS_MODE.WR | lv.FS_MODE.RD: - p_mode = "rb+" - else: - raise RuntimeError("fs_open_callback() - open mode error, %s is invalid mode" % mode) - - try: - f = open(path, p_mode) - - except OSError as e: - raise RuntimeError("fs_open_callback(%s) exception: %s" % (path, e)) - - return {"file": f, "path": path} - - -def fs_close_cb(drv, fs_file): - try: - fs_file.__cast__()["file"].close() - except OSError as e: - raise RuntimeError("fs_close_callback(%s) exception: %s" % (fs_file.__cast__()["path"], e)) - - return lv.FS_RES.OK - - -def fs_read_cb(drv, fs_file, buf, btr, br): - try: - tmp_data = fs_file.__cast__()["file"].read(btr) - buf.__dereference__(btr)[0 : len(tmp_data)] = tmp_data - br.__dereference__(4)[0:4] = struct.pack("= 0: - fs_drv.cache_size = cache_size - - fs_drv.register() diff --git a/m5stack/libs/manifest.py b/m5stack/libs/manifest.py index 1a1db72d..220b622d 100644 --- a/m5stack/libs/manifest.py +++ b/m5stack/libs/manifest.py @@ -9,7 +9,7 @@ include("hardware/manifest.py") include("hat/manifest.py") include("image_plus/manifest.py") -include("lvgl/manifest.py") +include("lv_utils/manifest.py") include("m5ble/manifest.py") include("m5espnow/manifest.py") include("modbus/modbus/manifest.py") diff --git a/tests/lvgl/image.py b/tests/lvgl/image.py index 3638201d..2362aa36 100644 --- a/tests/lvgl/image.py +++ b/tests/lvgl/image.py @@ -5,7 +5,7 @@ import sys import M5 import lvgl as lv -import fs_driver +import lv_utils M5.begin() @@ -33,7 +33,7 @@ # fs driver init fs_drv = lv.fs_drv_t() -fs_driver.fs_register(fs_drv, "S") +lv_utils.fs_register(fs_drv, "S", 500) scr = lv.obj() diff --git a/tests/lvgl/label.py b/tests/lvgl/label.py index bf47d154..c111863a 100644 --- a/tests/lvgl/label.py +++ b/tests/lvgl/label.py @@ -5,7 +5,7 @@ import sys import M5 import lvgl as lv -import fs_driver +import lv_utils M5.begin() @@ -31,22 +31,26 @@ indev_drv.set_display(disp_drv) indev_drv.set_read_cb(M5.Lcd.lvgl_read) +# sd card driver init +from hardware import sdcard + +sdcard.SDCard(slot=2, width=1, sck=18, miso=38, mosi=23, cs=4, freq=20000000) + # fs driver init fs_drv = lv.fs_drv_t() -fs_driver.fs_register(fs_drv, "S") - +lv_utils.fs_register(fs_drv, "S", 500) scr = lv.screen_active() scr.clean() -myfont_cn = lv.binfont_create("S:/flash/res/font/font-PHT-cn-20.bin") +myfont_cn = lv.binfont_create("S:/sd/font-PHT-cn-20.bin") label1 = lv.label(scr) label1.set_style_text_font(myfont_cn, 0) # set the font label1.set_text("上中下乎") label1.align(lv.ALIGN.CENTER, 0, -25) -myfont_en = lv.binfont_create("S:/flash/res/font/font-PHT-en-20.bin") +myfont_en = lv.binfont_create("S:/sd/font-PHT-en-20.bin") label2 = lv.label(scr) label2.set_style_text_font(myfont_en, 0) # set the font @@ -54,9 +58,17 @@ label2.align(lv.ALIGN.CENTER, 0, 25) -myfont_jp = lv.binfont_create("S:/flash/res/font/font-PHT-jp-20.bin") +myfont_jp = lv.binfont_create("S:/sd/font-PHT-jp-20.bin") label3 = lv.label(scr) label3.set_style_text_font(myfont_jp, 0) # set the font label3.set_text("こんにちはありがとう") label3.align(lv.ALIGN.CENTER, 0, 0) + +img0 = lv.image(scr) +img0.set_src("S:/flash/res/img/uiflow.jpg") +img0.set_pos(0, 0) + +img1 = lv.image(scr) +img1.set_src("S:/flash/res/img/uiflow.png") +img1.set_pos(0, 80) diff --git a/tests/lvgl/multiple_display.py b/tests/lvgl/multiple_display.py index b30b88da..cff9eb27 100644 --- a/tests/lvgl/multiple_display.py +++ b/tests/lvgl/multiple_display.py @@ -6,17 +6,15 @@ import sys import M5 import lvgl as lv -import fs_driver +import lv_utils from hardware import I2C from hardware import Pin from unit import LCDUnit -from unit import OLEDUnit M5.begin() i2c0 = I2C(0, scl=Pin(33), sda=Pin(32), freq=100000) lcd_0 = LCDUnit(i2c0, 0x3E) -# oled_0 = OLEDUnit(i2c0, 0x3c) # lvgl init M5.Lcd.lvgl_init() @@ -45,18 +43,6 @@ disp_drv1.set_user_data({"display": lcd_0}) disp_drv1.set_render_mode(lv.DISPLAY_RENDER_MODE.PARTIAL) -# create a display 2 -# disp_buf0 = lv.draw_buf_create(oled_0.width(), 10, lv.COLOR_FORMAT.RGB565, 0) -# disp_buf1 = lv.draw_buf_create(oled_0.width(), 10, lv.COLOR_FORMAT.RGB565, 0) - -# disp_drv2 = lv.display_create(oled_0.width(), oled_0.height()) -# disp_drv2.set_color_format(lv.COLOR_FORMAT.RGB565) - -# disp_drv2.set_draw_buffers(disp_buf0, disp_buf1) -# disp_drv2.set_flush_cb(M5.Lcd.lvgl_flush) -# disp_drv2.set_user_data({"display": oled_0}) -# disp_drv2.set_render_mode(lv.DISPLAY_RENDER_MODE.PARTIAL) - # touch driver init indev_drv = lv.indev_create() indev_drv.set_type(lv.INDEV_TYPE.POINTER) @@ -66,7 +52,7 @@ # fs driver init fs_drv = lv.fs_drv_t() -fs_driver.fs_register(fs_drv, "S") +lv_utils.fs_register(fs_drv, "S") # display 0 disp_drv.set_default() @@ -85,12 +71,3 @@ img0 = lv.image(scr) img0.set_src("S:/flash/res/img/uiflow.jpg") img0.set_pos(0, 0) - -# display 2 -# disp_drv2.set_default() -# scr = lv.screen_active() -# scr.clean() - -# img0 = lv.image(scr) -# img0.set_src("S:/flash/res/img/uiflow.jpg") -# img0.set_pos(0, 0) From d40f28fec99b0c2bc3617e5569d6e13f37f81150 Mon Sep 17 00:00:00 2001 From: lbuque Date: Mon, 21 Apr 2025 09:22:56 +0800 Subject: [PATCH 069/322] components: Delete the lv_binding component. Signed-off-by: lbuque --- m5stack/components/lv_bindings | 1 - 1 file changed, 1 deletion(-) delete mode 160000 m5stack/components/lv_bindings diff --git a/m5stack/components/lv_bindings b/m5stack/components/lv_bindings deleted file mode 160000 index c544c51c..00000000 --- a/m5stack/components/lv_bindings +++ /dev/null @@ -1 +0,0 @@ -Subproject commit c544c51c97b38907709a0cd7ea7ca8ab96410cbb From c47766b033a539f5ae7d92b54233e8ff9629729f Mon Sep 17 00:00:00 2001 From: lbuque Date: Mon, 21 Apr 2025 09:49:44 +0800 Subject: [PATCH 070/322] boards: Fix compilation errors. Signed-off-by: lbuque --- m5stack/boards/M5STACK_AtomS3R_CAM/mpconfigboard.cmake | 1 + 1 file changed, 1 insertion(+) diff --git a/m5stack/boards/M5STACK_AtomS3R_CAM/mpconfigboard.cmake b/m5stack/boards/M5STACK_AtomS3R_CAM/mpconfigboard.cmake index d279cfb9..75aca677 100644 --- a/m5stack/boards/M5STACK_AtomS3R_CAM/mpconfigboard.cmake +++ b/m5stack/boards/M5STACK_AtomS3R_CAM/mpconfigboard.cmake @@ -6,6 +6,7 @@ set(IDF_TARGET esp32s3) # atoms3r https://github.com/m5stack/m5stack-board-id/blob/main/board.csv#L20 set(BOARD_ID 144) +set(MICROPY_PY_LVGL 0) set(SDKCONFIG_DEFAULTS ./boards/sdkconfig.base From 81c394b245cfe8cec0467fd95ff39ebb6ae1d884 Mon Sep 17 00:00:00 2001 From: lbuque Date: Tue, 29 Apr 2025 17:11:40 +0800 Subject: [PATCH 071/322] boards: Modify Dial's partition table. Signed-off-by: lbuque --- m5stack/boards/M5STACK_Dial/mpconfigboard.cmake | 2 +- m5stack/boards/M5STACK_Dial/sdkconfig.board | 4 ++++ m5stack/partitions_8mb_lvgl.csv | 7 +++++++ 3 files changed, 12 insertions(+), 1 deletion(-) create mode 100644 m5stack/partitions_8mb_lvgl.csv diff --git a/m5stack/boards/M5STACK_Dial/mpconfigboard.cmake b/m5stack/boards/M5STACK_Dial/mpconfigboard.cmake index 965ba1af..484ad534 100644 --- a/m5stack/boards/M5STACK_Dial/mpconfigboard.cmake +++ b/m5stack/boards/M5STACK_Dial/mpconfigboard.cmake @@ -16,7 +16,7 @@ set(SDKCONFIG_DEFAULTS ./boards/sdkconfig.ble ./boards/sdkconfig.usb ./boards/sdkconfig.usb_cdc - ./boards/sdkconfig.flash_8mb + # ./boards/sdkconfig.flash_8mb ./boards/sdkconfig.freertos ./boards/M5STACK_Dial/sdkconfig.board ) diff --git a/m5stack/boards/M5STACK_Dial/sdkconfig.board b/m5stack/boards/M5STACK_Dial/sdkconfig.board index a1e70995..750d8563 100644 --- a/m5stack/boards/M5STACK_Dial/sdkconfig.board +++ b/m5stack/boards/M5STACK_Dial/sdkconfig.board @@ -12,6 +12,10 @@ CONFIG_ESPTOOLPY_AFTER_NORESET=y CONFIG_SPIRAM_MEMTEST= # CONFIG_FREERTOS_UNICORE=y +CONFIG_ESPTOOLPY_FLASHSIZE_8MB=y +CONFIG_PARTITION_TABLE_CUSTOM=y +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions_8mb_lvgl.csv" + # M5STACK UiFlow USB description CONFIG_TINYUSB_DESC_CDC_STRING="M5Stack Dial(UiFlow2)" diff --git a/m5stack/partitions_8mb_lvgl.csv b/m5stack/partitions_8mb_lvgl.csv new file mode 100644 index 00000000..301e87a8 --- /dev/null +++ b/m5stack/partitions_8mb_lvgl.csv @@ -0,0 +1,7 @@ +# Name, Type, SubType, Offset, Size, Flags +# Note: if you change the phy_init or app partition offset, make sure to change the offset in Kconfig.projbuild +nvs, data, nvs, 0x9000, 0x6000, +phy_init, data, phy, 0xf000, 0x1000, +factory, app, factory, 0x10000, 0x590000, +sys, data, fat, 0x5A0000, 0x100000, +vfs, data, fat, 0x6A0000, 0x15f000, From 71dbdc9e1795b58204d354a0d006f8af4c97a862 Mon Sep 17 00:00:00 2001 From: lbuque Date: Tue, 29 Apr 2025 18:15:57 +0800 Subject: [PATCH 072/322] version.text: Update to V2.2.6. Signed-off-by: lbuque --- m5stack/version.txt | 2 +- third-party/version.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/m5stack/version.txt b/m5stack/version.txt index a014b0b6..c5ccc058 100644 --- a/m5stack/version.txt +++ b/m5stack/version.txt @@ -1 +1 @@ -V2.2.5 \ No newline at end of file +V2.2.6 \ No newline at end of file diff --git a/third-party/version.txt b/third-party/version.txt index a014b0b6..c5ccc058 100644 --- a/third-party/version.txt +++ b/third-party/version.txt @@ -1 +1 @@ -V2.2.5 \ No newline at end of file +V2.2.6 \ No newline at end of file From fbf7c1e80415c3d5b9c84d48585753986fc90d06 Mon Sep 17 00:00:00 2001 From: lbuque Date: Wed, 30 Apr 2025 10:08:08 +0800 Subject: [PATCH 073/322] partitions_8mb_lvgl.csv: Modify the partition table. Signed-off-by: lbuque --- m5stack/partitions_8mb_lvgl.csv | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/m5stack/partitions_8mb_lvgl.csv b/m5stack/partitions_8mb_lvgl.csv index 301e87a8..b1197054 100644 --- a/m5stack/partitions_8mb_lvgl.csv +++ b/m5stack/partitions_8mb_lvgl.csv @@ -2,6 +2,6 @@ # Note: if you change the phy_init or app partition offset, make sure to change the offset in Kconfig.projbuild nvs, data, nvs, 0x9000, 0x6000, phy_init, data, phy, 0xf000, 0x1000, -factory, app, factory, 0x10000, 0x590000, -sys, data, fat, 0x5A0000, 0x100000, -vfs, data, fat, 0x6A0000, 0x15f000, +factory, app, factory, 0x10000, 0x5B0000, +sys, data, fat, 0x5C0000, 0x100000, +vfs, data, fat, 0x6C0000, 0x13f000, From a00cddb85e14b9dda9eb41cb621591828a80d827 Mon Sep 17 00:00:00 2001 From: tinyu Date: Tue, 29 Apr 2025 18:13:40 +0800 Subject: [PATCH 074/322] lib/unit: Fix PDM Unit example recording sound irregularity. Signed-off-by: tinyu --- examples/unit/pdm/pdm_cores3_example.m5f2 | 2 +- examples/unit/pdm/pdm_cores3_example.py | 38 +++++++++++++---------- 2 files changed, 22 insertions(+), 18 deletions(-) diff --git a/examples/unit/pdm/pdm_cores3_example.m5f2 b/examples/unit/pdm/pdm_cores3_example.m5f2 index 7937c763..a654a26d 100644 --- a/examples/unit/pdm/pdm_cores3_example.m5f2 +++ b/examples/unit/pdm/pdm_cores3_example.m5f2 @@ -1 +1 @@ -{"version":"V2.0","versionNumber":"V2.2.3","type":"cores3","components":[{"name":"screen","type":"screen","layer":0,"screenId":"builtin","screenName":"","id":"__cores3_screen","createTime":1717660236218,"x":0,"y":0,"width":320,"height":240,"backgroundColor":"#222222","size":0,"isSelected":true},{"name":"label0","type":"label","layer":1,"screenId":"builtin","screenName":"","id":"rW^fX1*1vprMa8z@","createTime":1717660555376,"x":128,"y":114,"color":"#ffffff","backgroundColor":"#222222","text":"label0","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false,"width":58,"height":21},{"name":"title0","type":"title","layer":1,"screenId":"builtin","screenName":"","id":"fUyX^usfI`lUa7#%","createTime":1742349913202,"x":0,"y":0,"color":"#ffffff","backgroundColor":"#0000FF","text":"PDMUnit CoreS3 Example","textOffset":3,"font":"Widgets.FONTS.DejaVu18","isSelected":false}],"resources":[{"hardware":["hardware_button","hardware_pin_button","imu","speaker","touch","als","mic"]},{"unit":["unit_pdm"]}],"units":[{"type":"unit_pdm","name":"pdm_0","portList":["A","B","C","Custom"],"portType":"A","userPort":[22,21],"id":"gss%Q0aN!0e$&#RW","createTime":1742349854435,"initBlockId":"bMOGU7vj4{9Vi2SNi)9z"}],"hats":[],"bases":[],"i2cs":[],"blockly":"rec_datatruepdm_0244100100pdm_0rec_data1MULTIPLY4410010label0rec...pdm_0Falserec_data100WHILEpdm_0label0rec...100pdm_0label0play...rec_data16000MULTIPLY441002WHILE100label0donetrue","screen":[{"simulationName":"Built-in","type":"builtin","width":320,"height":240,"scale":0.78,"screenName":"","blockId":"","screenColorType":0,"id":"builtin","createTime":1717660236214}],"logicWhenNum":0,"customList":[]} \ No newline at end of file +{"version":"V2.0","versionNumber":"V2.2.6","type":"cores3","components":[{"name":"screen","type":"screen","layer":0,"screenId":"builtin","screenName":"","id":"__cores3_screen","createTime":1745542990818,"x":0,"y":0,"width":320,"height":240,"backgroundColor":"#222222","size":0,"isSelected":true},{"name":"title0","type":"title","layer":1,"screenId":"builtin","screenName":"","id":"pmFdx@hpgkF&dZ*Y","createTime":1745546428599,"x":0,"y":0,"color":"#ffffff","backgroundColor":"#0000FF","text":"PDMUnit CoreS3 Example","textOffset":3,"font":"Widgets.FONTS.DejaVu18","isSelected":false},{"name":"label0","type":"label","layer":2,"screenId":"builtin","screenName":"","id":"z3xtO^jX5L6NiSap","createTime":1745546458604,"x":20,"y":54,"color":"#ffffff","backgroundColor":"#222222","text":"Is Start:","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false},{"name":"label1","type":"label","layer":3,"screenId":"builtin","screenName":"","id":"j02KcYbv!cFfZ%6d","createTime":1745546460950,"x":20,"y":119,"color":"#ffffff","backgroundColor":"#222222","text":"Is Done:","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false},{"name":"label2","type":"label","layer":4,"screenId":"builtin","screenName":"","id":"nQ`mk`kRGg^_91OA","createTime":1745546464517,"x":131,"y":52,"color":"#ffffff","backgroundColor":"#222222","text":"label2","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false},{"name":"label3","type":"label","layer":5,"screenId":"builtin","screenName":"","id":"bo3#slwDGB+sP4qV","createTime":1745546466269,"x":133,"y":121,"color":"#ffffff","backgroundColor":"#222222","text":"label3","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false}],"resources":[{"hardware":["hardware_button","hardware_pin_button","imu","speaker","touch","als","mic"]},{"unit":["unit_pdm"]}],"units":[{"type":"unit_pdm","name":"pdm_0","portList":["A","B","C","Custom"],"portType":"A","userPort":[22,21],"id":"m%fNJStfZIUEVGNo","createTime":1745921027358,"initBlockId":"u-t5ezs22vVmOt;I(m?("}],"hats":[],"bases":[],"i2cs":[],"blockly":"rec_datatruepdm_0244100100pdm_0label2waiting...2rec_data1MULTIPLY4410015pdm_0Falserec_data44100150WHILEpdm_0label2recording...100pdm_0label2ending...label3playing...rec_data44100WHILE150label3donetrue","screen":[{"simulationName":"Built-in","type":"builtin","width":320,"height":240,"scale":0.78,"screenName":"","blockId":"","screenColorType":0,"id":"builtin","createTime":1745542990815}],"logicWhenNum":0,"customList":[]} \ No newline at end of file diff --git a/examples/unit/pdm/pdm_cores3_example.py b/examples/unit/pdm/pdm_cores3_example.py index 055168b6..dcf91ee6 100644 --- a/examples/unit/pdm/pdm_cores3_example.py +++ b/examples/unit/pdm/pdm_cores3_example.py @@ -1,7 +1,3 @@ -# SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD -# -# SPDX-License-Identifier: MIT - import os, sys, io import M5 from M5 import * @@ -9,8 +5,11 @@ import time -label0 = None title0 = None +label0 = None +label1 = None +label2 = None +label3 = None pdm_0 = None @@ -18,36 +17,41 @@ def setup(): - global label0, title0, pdm_0, rec_data + global title0, label0, label1, label2, label3, pdm_0, rec_data M5.begin() Widgets.fillScreen(0x222222) - label0 = Widgets.Label("label0", 128, 114, 1.0, 0xFFFFFF, 0x222222, Widgets.FONTS.DejaVu18) title0 = Widgets.Title("PDMUnit CoreS3 Example", 3, 0xFFFFFF, 0x0000FF, Widgets.FONTS.DejaVu18) + label0 = Widgets.Label("Is Start:", 20, 54, 1.0, 0xFFFFFF, 0x222222, Widgets.FONTS.DejaVu18) + label1 = Widgets.Label("Is Done:", 20, 119, 1.0, 0xFFFFFF, 0x222222, Widgets.FONTS.DejaVu18) + label2 = Widgets.Label("label2", 131, 52, 1.0, 0xFFFFFF, 0x222222, Widgets.FONTS.DejaVu18) + label3 = Widgets.Label("label3", 133, 121, 1.0, 0xFFFFFF, 0x222222, Widgets.FONTS.DejaVu18) pdm_0 = PDMUnit((1, 2), i2s_port=2, sample_rate=44100) Speaker.begin() Speaker.setVolumePercentage(1) Speaker.end() pdm_0.begin() - rec_data = bytearray(44100 * 10) - label0.setText(str("rec...")) - pdm_0.record(rec_data, _, False) - time.sleep_ms(100) + label2.setText(str("waiting...")) + time.sleep(2) + rec_data = bytearray(44100 * 15) + pdm_0.record(rec_data, 44100, False) + time.sleep_ms(150) while pdm_0.isRecording(): - label0.setText(str("rec...")) + label2.setText(str("recording...")) time.sleep_ms(100) pdm_0.end() + label2.setText(str("ending...")) Speaker.begin() - label0.setText(str("play...")) - Speaker.playRaw(rec_data, 44100 * 2) + label3.setText(str("playing...")) + Speaker.playRaw(rec_data, 44100) while Speaker.isPlaying(): - time.sleep_ms(100) - label0.setText(str("done")) + time.sleep_ms(150) + label3.setText(str("done")) def loop(): - global label0, title0, pdm_0, rec_data + global title0, label0, label1, label2, label3, pdm_0, rec_data M5.update() From 177b07a397330936d91abc4e95dc871d70107fc2 Mon Sep 17 00:00:00 2001 From: tinyu Date: Wed, 30 Apr 2025 12:31:12 +0800 Subject: [PATCH 075/322] lib/base: Add DTU NB-IoT-Series base support. Signed-off-by: tinyu --- m5stack/libs/base/__init__.py | 1 + m5stack/libs/base/dtu_lorawan_rui3.py | 2 +- m5stack/libs/base/manifest.py | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/m5stack/libs/base/__init__.py b/m5stack/libs/base/__init__.py index 1be1e3fb..5f359c07 100644 --- a/m5stack/libs/base/__init__.py +++ b/m5stack/libs/base/__init__.py @@ -6,6 +6,7 @@ "ATOMCANBase": "atom_can", "AtomDTULoRaWANBase": "dtu_lorawan", "AtomDTULoRaWANRUI3Base": "dtu_lorawan_rui3", + "AtomDTUNBIoT": "dtu_nbiot", "ATOMGPSBase": "atom_gps", "ATOMSocketBase": "atom_socket", "ATOMEchoBase": "echo", diff --git a/m5stack/libs/base/dtu_lorawan_rui3.py b/m5stack/libs/base/dtu_lorawan_rui3.py index 9bd8501a..fd6537b7 100644 --- a/m5stack/libs/base/dtu_lorawan_rui3.py +++ b/m5stack/libs/base/dtu_lorawan_rui3.py @@ -2,7 +2,7 @@ # # SPDX-License-Identifier: MIT -from rui3 import RUI3 +from driver.rui3 import RUI3 import sys if sys.platform != "esp32": diff --git a/m5stack/libs/base/manifest.py b/m5stack/libs/base/manifest.py index 50abda24..57500a2d 100644 --- a/m5stack/libs/base/manifest.py +++ b/m5stack/libs/base/manifest.py @@ -11,6 +11,7 @@ "display.py", "dtu_lorawan.py", "dtu_lorawan_rui3.py", + "dtu_nbiot.py", "echo.py", "hdriver.py", "motion.py", From 3058856683e1a433fd4a56dce2aa06f1844afa41 Mon Sep 17 00:00:00 2001 From: lbuque Date: Mon, 12 May 2025 14:58:10 +0800 Subject: [PATCH 076/322] cmodule/m5unified: Add EPD related APIs. Signed-off-by: lbuque --- docs/en/hardware/display.rst | 120 ++++++++++++--------- m5stack/cmodules/m5unified/m5unified_gfx.c | 34 ++++++ m5stack/components/M5Unified/mpy_m5gfx.cpp | 26 +++++ 3 files changed, 128 insertions(+), 52 deletions(-) diff --git a/docs/en/hardware/display.rst b/docs/en/hardware/display.rst index 01d713ed..33ad51d0 100644 --- a/docs/en/hardware/display.rst +++ b/docs/en/hardware/display.rst @@ -1,6 +1,6 @@ .. _hardware.Display: -Display +Display ======= A lcd display library @@ -9,11 +9,11 @@ A lcd display library :synopsis: A lcd display library -Micropython Example --------------------------------- +Micropython Example +------------------- draw test -++++++++++++++++++++++++++++ ++++++++++ :: @@ -65,21 +65,21 @@ draw test Functions --------------------------------- +--------- -.. function:: Display.width() -> int +.. method:: Display.width() -> int Get the horizontal resolution of the display. Returns An integer representing the horizontal resolution (width) in pixels. -.. function:: Display.height() -> int - +.. method:: Display.height() -> int + Get the vertical resolution of the display. Returns An integer representing the vertical resolution (height) in pixels. -.. function:: Display.getRotation() -> int +.. method:: Display.getRotation() -> int Get the current rotation of the display. @@ -90,13 +90,13 @@ Functions - ``3``: 180° rotation - ``4``: 270° rotation -.. function:: Display.getColorDepth() -> int +.. method:: Display.getColorDepth() -> int Get the color depth of the display. Returns An integer representing the display's color depth in bits. -.. function::Display.getCursor() -> Tuple[int, int] +.. method::Display.getCursor() -> Tuple[int, int] Get the current cursor position on the display. @@ -105,8 +105,7 @@ Functions - ``x`` is the horizontal position of the cursor. - ``y`` is the vertical position of the cursor. - -.. function:: Display.setRotation(r: int = -1) +.. method:: Display.setRotation(r: int = -1) Set the rotation of the display. @@ -117,15 +116,33 @@ Functions - ``3``: 180° rotation - ``4``: 270° rotation -.. function:: Display.setColorDepth(bpp: int = 1) +.. method:: Display.setColorDepth(bpp: int = 1) Set the color depth of the display. - ``bpp`` The desired color depth in bits per pixel. - + Notes: For CoreS3 devices, the color depth is fixed at 16 bits, and this method has no effect. -.. function:: Display.setFont(font) +.. method:: Display.setEpdMode(epd_mode) + + Set the EPD mode for the display. + + - ``epd_mode`` The desired EPD mode. + - 0: M5.Lcd.EPDMode.EPD_QUALITY + - 1: M5.Lcd.EPDMode.EPD_TEXT + - 2: M5.Lcd.EPDMode.EPD_FAST + - 3: M5.Lcd.EPDMode.EPD_FASTEST + + Notes: This method is only applicable to devices with EPD (Electronic Paper Display) capabilities. + +.. method:: Display.isEPD() -> bool + + Check if the display is an EPD (Electronic Paper Display). + + Returns A boolean indicating whether the display is an EPD. + +.. method:: Display.setFont(font) Set the font for the display. @@ -143,46 +160,46 @@ Functions - M5.Lcd.FONTS.EFontJA24 - M5.Lcd.FONTS.EFontKR24 -.. function:: Display.setTextColor(fgcolor: int = 0, bgcolor: int = 0) +.. method:: Display.setTextColor(fgcolor: int = 0, bgcolor: int = 0) Set the text color and background color. - ``fgcolor`` The text color in RGB888 format. Default is 0 (black). - ``bgcolor`` The background color in RGB888 format. Default is 0 (black). -.. function:: Display.setTextScroll(scroll: bool = False) +.. method:: Display.setTextScroll(scroll: bool = False) Enable or disable text scrolling. - ``scroll`` Set to True to enable text scrolling, or False to disable it. Default is False.\ -.. function:: Display.setTextSize(size) +.. method:: Display.setTextSize(size) Set the size of the text. - ``size`` The desired text size. -.. function:: Display.setCursor(x: int = 0, y: int = 0) +.. method:: Display.setCursor(x: int = 0, y: int = 0) Set the cursor position. - ``x`` The horizontal position of the cursor. Default is 0. - ``y`` The vertical position of the cursor. Default is 0. -.. function:: Display.clear(color: int = 0) +.. method:: Display.clear(color: int = 0) Clear the display with a specific color. - ``color`` The fill color in RGB888 format. Default is 0. -.. function:: Display.fillScreen(color: int = 0) +.. method:: Display.fillScreen(color: int = 0) Fill the entire screen with a specified color. - + - ``color`` The fill color in RGB888 format. Default is 0. -.. function:: Display.drawPixel(x: int = -1, y: int = -1, color: int = 0) +.. method:: Display.drawPixel(x: int = -1, y: int = -1, color: int = 0) Draw a single pixel on the screen. @@ -190,7 +207,7 @@ Functions - ``y`` The vertical coordinate of the pixel. Default is -1. - ``color`` The color of the pixel in RGB888 format. Default is 0. -.. function:: Display.drawCircle(x: int = -1, y: int = -1, r: int = -1, color: int = 0) +.. method:: Display.drawCircle(x: int = -1, y: int = -1, r: int = -1, color: int = 0) Draw an outline of a circle. @@ -199,7 +216,7 @@ Functions - ``r`` The radius of the circle. Default is -1. - ``color`` The color of the circle in RGB888 format. Default is 0. -.. function:: Display.fillCircle(x: int = -1, y: int = -1, r: int = -1, color: int = 0) +.. method:: Display.fillCircle(x: int = -1, y: int = -1, r: int = -1, color: int = 0) Draw a filled circle. @@ -208,17 +225,17 @@ Functions - ``r`` The radius of the circle. Default is -1. - ``color`` The fill color in RGB888 format. Default is 0. -.. function:: Display.drawEllipse(x: int = -1, y: int = -1, rx: int = -1, ry: int = -1, color: int = 0) +.. method:: Display.drawEllipse(x: int = -1, y: int = -1, rx: int = -1, ry: int = -1, color: int = 0) Draw an outline of an ellipse. - + - ``x`` The x-coordinate of the ellipse center. Default is -1. - ``y`` The y-coordinate of the ellipse center. Default is -1. - ``rx`` The horizontal radius of the ellipse. Default is -1. - ``ry`` The vertical radius of the ellipse. Default is -1. - ``color`` The color of the ellipse in RGB888 format. Default is 0. -.. function:: Display.fillEllipse(x: int = -1, y: int = -1, rx: int = -1, ry: int = -1, color: int = 0) +.. method:: Display.fillEllipse(x: int = -1, y: int = -1, rx: int = -1, ry: int = -1, color: int = 0) Draw a filled ellipse. @@ -228,7 +245,7 @@ Functions - ``ry`` The vertical radius of the ellipse. Default is -1. - ``color`` The fill color in RGB888 format. Default is 0. -.. function:: Display.drawLine(x0: int = -1, y0: int = -1, x1: int = -1, y1: int = -1, color: int = 0) +.. method:: Display.drawLine(x0: int = -1, y0: int = -1, x1: int = -1, y1: int = -1, color: int = 0) Draw a line. @@ -236,7 +253,7 @@ Functions - ``x1, y1`` Ending point coordinates of the line. Default is -1. - ``color`` Color in RGB888 format. Default is 0. -.. function:: Display.drawRect(x: int = -1, y: int = -1, w: int = -1, h: int = -1, color: int = 0) +.. method:: Display.drawRect(x: int = -1, y: int = -1, w: int = -1, h: int = -1, color: int = 0) Draw a rectangle. @@ -244,7 +261,7 @@ Functions - ``w, h`` Width and height of the rectangle. Default is -1. - ``color`` Color in RGB888 format. Default is 0. -.. function:: Display.fillRect(x: int = -1, y: int = -1, w: int = -1, h: int = -1, color: int = 0) +.. method:: Display.fillRect(x: int = -1, y: int = -1, w: int = -1, h: int = -1, color: int = 0) Draw a filled rectangle. @@ -252,7 +269,7 @@ Functions - ``w, h`` Width and height of the rectangle. Default is -1. - ``color`` Color in RGB888 format. Default is 0. -.. function:: Display.drawRoundRect(x: int = -1, y: int = -1, w: int = -1, h: int = -1, r: int = -1, color: int = 0) +.. method:: Display.drawRoundRect(x: int = -1, y: int = -1, w: int = -1, h: int = -1, r: int = -1, color: int = 0) Draw a rounded rectangle. @@ -261,7 +278,7 @@ Functions - ``r`` Radius of the corners. Default is -1. - ``color`` Color in RGB888 format. Default is 0. -.. function:: Display.fillRoundRect(x: int = -1, y: int = -1, w: int = -1, h: int = -1, r: int = -1, color: int = 0) +.. method:: Display.fillRoundRect(x: int = -1, y: int = -1, w: int = -1, h: int = -1, r: int = -1, color: int = 0) Draw a filled rounded rectangle. @@ -271,7 +288,7 @@ Functions - ``color`` Color in RGB888 format. Default is 0. -.. function:: Display.drawTriangle(x0: int = -1, y0: int = -1, x1: int = -1, y1: int = -1, x2: int = -1, y2: int = -1, color: int = 0) +.. method:: Display.drawTriangle(x0: int = -1, y0: int = -1, x1: int = -1, y1: int = -1, x2: int = -1, y2: int = -1, color: int = 0) Draw a triangle. @@ -280,7 +297,7 @@ Functions - ``x2, y2`` Coordinates of the third vertex. Default is -1. - ``color`` Color in RGB888 format. Default is 0. -.. function:: Display.fillTriangle(x0: int = -1, y0: int = -1, x1: int = -1, y1: int = -1, x2: int = -1, y2: int = -1, color: int = 0) +.. method:: Display.fillTriangle(x0: int = -1, y0: int = -1, x1: int = -1, y1: int = -1, x2: int = -1, y2: int = -1, color: int = 0) Draw a filled triangle. @@ -289,7 +306,7 @@ Functions - ``x2, y2`` Coordinates of the third vertex. Default is -1. - ``color:`` Color in RGB888 format. Default is 0. -.. function:: Display.drawArc(x: int = -1, y: int = -1, r0: int = -1, r1: int = -1, angle0: int = -1, angle1: int = -1, color: int = 0) +.. method:: Display.drawArc(x: int = -1, y: int = -1, r0: int = -1, r1: int = -1, angle0: int = -1, angle1: int = -1, color: int = 0) Draw an arc. @@ -300,7 +317,7 @@ Functions - ``angle1`` Ending angle of the arc (in degrees). Default is -1. - ``color`` Color in RGB888 format. Default is 0. -.. function:: Display.fillArc(x: int = -1, y: int = -1, r0: int = -1, r1: int = -1, angle0: int = -1, angle1: int = -1, color: int = 0) +.. method:: Display.fillArc(x: int = -1, y: int = -1, r0: int = -1, r1: int = -1, angle0: int = -1, angle1: int = -1, color: int = 0) Draw a filled arc. @@ -311,7 +328,7 @@ Functions - ``angle1`` Ending angle of the arc (in degrees). Default is -1. - ``color`` Color in RGB888 format. Default is 0. -.. function:: Display.drawEllipseArc(x: int = -1, y: int = -1, r0x: int = -1, r0y: int = -1, r1x: int = -1, r1y: int = -1, angle0: int = -1, angle1: int = -1, color: int = 0) +.. method:: Display.drawEllipseArc(x: int = -1, y: int = -1, r0x: int = -1, r0y: int = -1, r1x: int = -1, r1y: int = -1, angle0: int = -1, angle1: int = -1, color: int = 0) Draw an elliptical arc. @@ -322,7 +339,7 @@ Functions - ``angle1`` Ending angle of the arc (in degrees). Default is 0. - ``color`` Color in RGB888 format. Default is 0. -.. function:: Display.fillEllipseArc(x: int = -1, y: int = -1, r0x: int = -1, r0y: int = -1, r1x: int = -1, r1y: int = -1, angle0: int = -1, angle1: int = -1, color: int = 0) +.. method:: Display.fillEllipseArc(x: int = -1, y: int = -1, r0x: int = -1, r0y: int = -1, r1x: int = -1, r1y: int = -1, angle0: int = -1, angle1: int = -1, color: int = 0) Draw a filled elliptical arc. @@ -333,7 +350,7 @@ Functions - ``angle1`` Ending angle of the arc (in degrees). Default is -1. - ``color:`` Color in RGB888 format. Default is 0. -.. function:: Display.drawQR(text: str = None, x: int = 0, y: int = 0, w: int = 0, version: int = 1) +.. method:: Display.drawQR(text: str = None, x: int = 0, y: int = 0, w: int = 0, version: int = 1) Draw a QR code. @@ -350,7 +367,7 @@ Functions Display.drawQR("Hello", 0, 0, 200) -.. function:: Display.drawPng(img: str, x: int = 0, y: int = 0, maxW: int = 0, maxH: int = 0, offX: int = 0, offY: int = 0, scaleX=True, scaleY=False) +.. method:: Display.drawPng(img: str, x: int = 0, y: int = 0, maxW: int = 0, maxH: int = 0, offX: int = 0, offY: int = 0, scaleX=True, scaleY=False) Draw a PNG image. @@ -377,7 +394,7 @@ Functions Display.drawPng(img.read(), 0, 100) img.close() -.. function:: Display.drawJpg(img, x: int = 0, y: int = 0, maxW: int = 0, maxH: int = 0, offX: int = 0, offY: int = 0) +.. method:: Display.drawJpg(img, x: int = 0, y: int = 0, maxW: int = 0, maxH: int = 0, offX: int = 0, offY: int = 0) Draw a JPG image. @@ -386,7 +403,7 @@ Functions - ``maxW, maxH`` Width and height to be drawn. Draws the full image if ≤0. - ``offX, offY`` Offset in the image to start from. -.. function:: Display.drawBmp(img: str, x: int = 0, y: int = 0, maxW: int = 0, maxH: int = 0, offX: int = 0, offY: int = 0) +.. method:: Display.drawBmp(img: str, x: int = 0, y: int = 0, maxW: int = 0, maxH: int = 0, offX: int = 0, offY: int = 0) Draw a BMP image. @@ -395,7 +412,7 @@ Functions - ``maxW, maxH`` Width and height to be drawn. Draws the full image if ≤0. - ``offX, offY`` Offset in the image to start from. -.. function:: Display.drawImage(img: str, x: int = 0, y: int = 0, maxW: int = 0, maxH: int = 0, offX: int = 0, offY: int = 0) +.. method:: Display.drawImage(img: str, x: int = 0, y: int = 0, maxW: int = 0, maxH: int = 0, offX: int = 0, offY: int = 0) Draw an image. @@ -414,7 +431,7 @@ Functions img.seek(0) drawImage(img.read()) -.. function:: Display.drawRawBuf(buf, x: int = 0, y: int = 0, w: int = 0, h: int = 0, len: int = 0, swap: bool = False) +.. method:: Display.drawRawBuf(buf, x: int = 0, y: int = 0, w: int = 0, h: int = 0, len: int = 0, swap: bool = False) Draw an image from raw buffer data. @@ -424,20 +441,20 @@ Functions - ``len`` Length of the image data. - ``swap`` Whether to enable inverted display. -.. function:: Display.print(text: str = None, color: int = 0) +.. method:: Display.print(text: str = None, color: int = 0) Display a string (no formatting support). - ``text`` Text to display. - ``color`` Color in RGB888 format. Default is 0. -.. function:: Display.printf(text: str = None) +.. method:: Display.printf(text: str = None) Display a formatted string. - ``text`` Text to display with formatting. -.. function:: Display.newCanvas(w: int = 0, h: int = 0, bpp: int = -1, psram: bool = False) +.. method:: Display.newCanvas(w: int = 0, h: int = 0, bpp: int = -1, psram: bool = False) Create a canvas. @@ -455,11 +472,10 @@ Functions w1.drawImage("res/img/uiflow.jpg", 80, 0) w1.push(30, 0) -.. function:: Display.startWrite() +.. method:: Display.startWrite() Start writing to the display. -.. function:: Display.endWrite() +.. method:: Display.endWrite() End writing to the display. - diff --git a/m5stack/cmodules/m5unified/m5unified_gfx.c b/m5stack/cmodules/m5unified/m5unified_gfx.c index 640dea3d..01515704 100644 --- a/m5stack/cmodules/m5unified/m5unified_gfx.c +++ b/m5stack/cmodules/m5unified/m5unified_gfx.c @@ -29,9 +29,12 @@ MAKE_METHOD_0(gfx, width); MAKE_METHOD_0(gfx, height); MAKE_METHOD_0(gfx, getRotation); MAKE_METHOD_0(gfx, getColorDepth); +MAKE_METHOD_0(gfx, getEpdMode); MAKE_METHOD_0(gfx, getCursor); MAKE_METHOD_KW(gfx, setRotation, 1); MAKE_METHOD_KW(gfx, setColorDepth, 1); +MAKE_METHOD_KW(gfx, setEpdMode, 1); +MAKE_METHOD_0(gfx, isEPD); MAKE_METHOD_KW(gfx, loadFont, 1); MAKE_METHOD_0(gfx, unloadFont); MAKE_METHOD_KW(gfx, setFont, 1); @@ -92,9 +95,12 @@ MAKE_METHOD_0(gfx, lvgl_deinit); MAKE_TABLE(gfx, width), \ MAKE_TABLE(gfx, getRotation), \ MAKE_TABLE(gfx, getColorDepth), \ + MAKE_TABLE(gfx, getEpdMode), \ MAKE_TABLE(gfx, getCursor), \ MAKE_TABLE(gfx, setRotation), \ MAKE_TABLE(gfx, setColorDepth), \ + MAKE_TABLE(gfx, setEpdMode), \ + MAKE_TABLE(gfx, isEPD), \ MAKE_TABLE(gfx, setFont), \ MAKE_TABLE(gfx, loadFont), \ MAKE_TABLE(gfx, unloadFont), \ @@ -231,6 +237,30 @@ const mp_obj_type_t mp_color_type = { }; #endif +static const mp_rom_map_elem_t epd_mode_define_members_table[] = { + /* *FORMAT-OFF* */ + { MP_ROM_QSTR(MP_QSTR_EPD_QUALITY), MP_ROM_INT(1) }, + { MP_ROM_QSTR(MP_QSTR_EPD_TEXT), MP_ROM_INT(2) }, + { MP_ROM_QSTR(MP_QSTR_EPD_FAST), MP_ROM_INT(3) }, + { MP_ROM_QSTR(MP_QSTR_EPD_FASTEST), MP_ROM_INT(4) }, + /* *FORMAT-ON* */ +}; +static MP_DEFINE_CONST_DICT(epd_mode_define_members, epd_mode_define_members_table); +#ifdef MP_OBJ_TYPE_GET_SLOT +MP_DEFINE_CONST_OBJ_TYPE( + mp_epd_mode_type, + MP_QSTR_EPDMode, + MP_TYPE_FLAG_NONE, + locals_dict, (mp_obj_dict_t *)&epd_mode_define_members + ); +#else +const mp_obj_type_t mp_epd_mode_type = { + .base = { &mp_type_type }, + .name = MP_QSTR_EPDMode, + .locals_dict = (mp_obj_dict_t *)&epd_mode_define_members, +}; +#endif + static const mp_rom_map_elem_t gfxdevice_member_table[] = { TABLE_PARTS_GFX_BASE, MAKE_TABLE(gfx, startWrite), @@ -239,6 +269,8 @@ static const mp_rom_map_elem_t gfxdevice_member_table[] = { { MP_ROM_QSTR(MP_QSTR_FONTS), MP_OBJ_FROM_PTR(&mp_fonts_type) }, // colors { MP_ROM_QSTR(MP_QSTR_COLOR), MP_OBJ_FROM_PTR(&mp_color_type) }, + // epd mode + { MP_ROM_QSTR(MP_QSTR_EPDMode), MP_OBJ_FROM_PTR(&mp_epd_mode_type) }, // stream function { MP_ROM_QSTR(MP_QSTR_read), MP_ROM_PTR(&mp_stream_read_obj) }, { MP_ROM_QSTR(MP_QSTR_write), MP_ROM_PTR(&mp_stream_write_obj) }, @@ -315,6 +347,8 @@ static const mp_rom_map_elem_t gfxuserdevice_member_table[] = { { MP_ROM_QSTR(MP_QSTR_FONTS), MP_ROM_PTR(&mp_fonts_type) }, // color { MP_ROM_QSTR(MP_QSTR_COLOR), MP_ROM_PTR(&mp_color_type) }, + // epd mode + { MP_ROM_QSTR(MP_QSTR_EPDMode), MP_OBJ_FROM_PTR(&mp_epd_mode_type) }, // Panel { MP_ROM_QSTR(MP_QSTR_PANEL), MP_ROM_PTR(&mp_user_panel_type) }, // Touch diff --git a/m5stack/components/M5Unified/mpy_m5gfx.cpp b/m5stack/components/M5Unified/mpy_m5gfx.cpp index 7f091d47..387e6ea3 100644 --- a/m5stack/components/M5Unified/mpy_m5gfx.cpp +++ b/m5stack/components/M5Unified/mpy_m5gfx.cpp @@ -60,6 +60,11 @@ mp_obj_t gfx_getColorDepth(mp_obj_t self) { return mp_obj_new_int(gfx->getColorDepth()); } +mp_obj_t gfx_getEpdMode(mp_obj_t self) { + auto gfx = getGfx(&self); + return mp_obj_new_int((int)gfx->getEpdMode()); +} + mp_obj_t gfx_getCursor(mp_obj_t self) { auto gfx = getGfx(&self); mp_obj_t tuple[2] = { mp_obj_new_int(gfx->getCursorX()) @@ -99,6 +104,27 @@ mp_obj_t gfx_setColorDepth(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw return mp_const_none; } +mp_obj_t gfx_setEpdMode(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum {ARG_epd_mode}; + /* *FORMAT-OFF* */ + const mp_arg_t allowed_args[] = { + { MP_QSTR_epd_mode, MP_ARG_INT | MP_ARG_REQUIRED, {.u_int = 1 } } + }; + /* *FORMAT-ON* */ + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + // The first parameter is the GFX object, parse from second parameter. + mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + auto gfx = getGfx(&pos_args[0]); + gfx->setEpdMode((epd_mode_t)args[ARG_epd_mode].u_int); + return mp_const_none; +} + +mp_obj_t gfx_isEPD(mp_obj_t self) { + auto gfx = getGfx(&self); + return mp_obj_new_bool(gfx->isEPD()); +} + mp_obj_t gfx_loadFont(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { enum {ARG_font}; From c2651fccd0c2fc663764811a39aea766737c8061 Mon Sep 17 00:00:00 2001 From: lbuque Date: Tue, 13 May 2025 15:46:13 +0800 Subject: [PATCH 077/322] components/M5Unified: Add fatfs compatibility layer. M5Unified can read files from fatfs. Signed-off-by: lbuque --- m5stack/CMakeListsDefault.cmake | 2 + .../m5audio2/vfs_stream.c => _vfs_stream.c} | 65 ++++++--------- .../m5audio2/vfs_stream.h => _vfs_stream.h} | 7 +- m5stack/cmodules/adf_module/vfs_stream.h | 4 +- m5stack/cmodules/m5audio2/audio2_player.c | 4 +- m5stack/cmodules/m5audio2/m5audio2.cmake | 1 - m5stack/components/M5Unified/mpy_m5gfx.cpp | 4 +- m5stack/components/M5Unified/mpy_m5lfs2.txt | 79 +++++++------------ .../components/M5Unified/mpy_m5widgets.cpp | 20 ++--- 9 files changed, 74 insertions(+), 112 deletions(-) rename m5stack/{cmodules/m5audio2/vfs_stream.c => _vfs_stream.c} (81%) rename m5stack/{cmodules/m5audio2/vfs_stream.h => _vfs_stream.h} (83%) diff --git a/m5stack/CMakeListsDefault.cmake b/m5stack/CMakeListsDefault.cmake index db377425..0f2f2954 100644 --- a/m5stack/CMakeListsDefault.cmake +++ b/m5stack/CMakeListsDefault.cmake @@ -126,6 +126,7 @@ list(APPEND MICROPY_SOURCE_PORT ${PROJECT_DIR}/machine_rtc.c # ${PROJECT_DIR}/../micropython/ports/esp32/machine_sdcard.c ${PROJECT_DIR}/../micropython/ports/esp32/modespnow.c + ${PROJECT_DIR}/_vfs_stream.c ) if ( @@ -275,6 +276,7 @@ idf_component_register( ${MICROPY_PORT_DIR} ${MICROPY_BOARD_DIR} ${CMAKE_BINARY_DIR} + ${CMAKE_CURRENT_LIST_DIR} LDFRAGMENTS linker.lf REQUIRES diff --git a/m5stack/cmodules/m5audio2/vfs_stream.c b/m5stack/_vfs_stream.c similarity index 81% rename from m5stack/cmodules/m5audio2/vfs_stream.c rename to m5stack/_vfs_stream.c index d6be02fd..dcc1fde1 100644 --- a/m5stack/cmodules/m5audio2/vfs_stream.c +++ b/m5stack/_vfs_stream.c @@ -13,11 +13,11 @@ #include "extmod/vfs_fat.h" #include "lib/littlefs/lfs2.h" #include "lib/oofatfs/ff.h" - +#include "_vfs_stream.h" #include #include -static const char *TAG = "Audio2"; +static const char *TAG = "VFS"; // micropython/extmod/vfs_lfs.c line: 115 typedef struct _mp_obj_vfs_lfs2_t { @@ -41,52 +41,31 @@ typedef struct vfs_stream_t { } vfs_stream_t; -static int lfs2_get_mode(const char *mode_s) { - int flags = 0; - while (*mode_s) { - switch (*mode_s++) { - case 'r': - flags = LFS2_O_RDONLY; - break; - case 'w': - flags = LFS2_O_WRONLY; - break; - case 'a': - flags = LFS2_O_APPEND; - break; - case '+': - flags = LFS2_O_RDWR; - break; - } - } - return flags; +static int lfs2_get_mode(int flags) { + int ret = 0; + ret |= flags & VFS_READ ? LFS2_O_RDONLY : 0; + ret = flags & VFS_WRITE ? LFS2_O_WRONLY : 0; + ret |= flags & VFS_APPEND ? LFS2_O_APPEND : 0; + ret |= flags & VFS_CREATE ? LFS2_O_CREAT : 0; + + return ret; } -static int fatfs_get_mode(const char *mode_s) { - int flags = 0; - while (*mode_s) { - switch (*mode_s++) { - case 'r': - flags = FA_READ; - break; - case 'w': - flags = FA_WRITE; - break; - case 'a': - flags = FA_OPEN_APPEND; - break; - case '+': - flags = FA_OPEN_ALWAYS; - break; - } - } +static int fatfs_get_mode(int flags) { + int ret = 0; + + ret |= flags & VFS_READ ? FA_READ : 0; + ret |= flags & VFS_WRITE ? FA_WRITE : 0; + ret |= flags & VFS_APPEND ? FA_OPEN_APPEND : 0; + ret |= flags & VFS_CREATE ? FA_CREATE_ALWAYS : 0; + return flags; } -void *vfs_stream_open(const char *path, const char *mode_s) { - ESP_LOGI(TAG, "vfs_stream_open: path=%s, mode=%s\n", path, mode_s); +void *vfs_stream_open(const char *path, int flags) { + ESP_LOGI(TAG, "vfs_stream_open: path=%s, mode=%d\n", path, flags); vfs_stream_t *vfs = calloc(1, sizeof(vfs_stream_t)); @@ -119,7 +98,7 @@ void *vfs_stream_open(const char *path, const char *mode_s) { ESP_LOGE(TAG, "failed to allocate lfs2_file"); goto _vfs_init_exit; } - int flags = lfs2_get_mode(mode_s); + flags = lfs2_get_mode(flags); ESP_LOGD(TAG, "lfs2_get_mode: %d", flags); int res = lfs2_file_opencfg(vfs->lfs2, vfs->file.lfs2_file, path_out, flags, &vfs->lfs2_file_conf); if (res != LFS2_ERR_OK) { @@ -127,7 +106,7 @@ void *vfs_stream_open(const char *path, const char *mode_s) { goto _vfs_init_exit; } } else if (vfs->fatfs) { - BYTE fmode = fatfs_get_mode(mode_s); + BYTE fmode = fatfs_get_mode(flags); FRESULT ret = f_open(vfs->fatfs, &vfs->file.fat_file, path_out, fmode); if (ret != FR_OK) { ESP_LOGE(TAG, "failed to open %s(%d)", path_out, ret); diff --git a/m5stack/cmodules/m5audio2/vfs_stream.h b/m5stack/_vfs_stream.h similarity index 83% rename from m5stack/cmodules/m5audio2/vfs_stream.h rename to m5stack/_vfs_stream.h index 7d919a33..2a643da6 100644 --- a/m5stack/cmodules/m5audio2/vfs_stream.h +++ b/m5stack/_vfs_stream.h @@ -22,7 +22,12 @@ #define SEEK_END 2 /* seek relative to end of file */ #endif -void *vfs_stream_open(const char *path, const char *mode_s); +#define VFS_READ 0x01 +#define VFS_WRITE 0x02 +#define VFS_APPEND 0x04 +#define VFS_CREATE 0x08 + +void *vfs_stream_open(const char *path, int flags); uint32_t vfs_stream_read(void *file_p, void *buf, uint32_t btr); ssize_t vfs_stream_write(void *file_p, const void *buf, size_t len); int32_t vfs_stream_seek(void *file_p, uint32_t pos, int whence); diff --git a/m5stack/cmodules/adf_module/vfs_stream.h b/m5stack/cmodules/adf_module/vfs_stream.h index 7fa31287..62892826 100644 --- a/m5stack/cmodules/adf_module/vfs_stream.h +++ b/m5stack/cmodules/adf_module/vfs_stream.h @@ -23,8 +23,8 @@ * */ -#ifndef _VFS_STREAM_H_ -#define _VFS_STREAM_H_ +#ifndef VFS_STREAM_H_ +#define VFS_STREAM_H_ #include "audio_element.h" diff --git a/m5stack/cmodules/m5audio2/audio2_player.c b/m5stack/cmodules/m5audio2/audio2_player.c index f6abd4c2..034e2d30 100644 --- a/m5stack/cmodules/m5audio2/audio2_player.c +++ b/m5stack/cmodules/m5audio2/audio2_player.c @@ -9,7 +9,7 @@ #include "py/mphal.h" #include "py/stream.h" #include "extmod/vfs_fat.h" -#include "vfs_stream.h" +#include "_vfs_stream.h" #include "i2s_helper.h" #include "freertos/FreeRTOS.h" @@ -215,7 +215,7 @@ static void player_wav_file_task(void *arg) { ESP_LOGI(TAG, "open path %s", self->play_info.wav_file.path); - void *wav_file = vfs_stream_open(self->play_info.wav_file.path, "rb"); + void *wav_file = vfs_stream_open(self->play_info.wav_file.path, VFS_READ); vfs_stream_seek(wav_file, self->play_info.wav_file.data_offset, SEEK_SET); ESP_LOGI(TAG, "file pos: %ld", vfs_stream_tell(wav_file)); diff --git a/m5stack/cmodules/m5audio2/m5audio2.cmake b/m5stack/cmodules/m5audio2/m5audio2.cmake index 9214eaf9..9eb08344 100644 --- a/m5stack/cmodules/m5audio2/m5audio2.cmake +++ b/m5stack/cmodules/m5audio2/m5audio2.cmake @@ -11,7 +11,6 @@ target_sources(usermod_m5audio INTERFACE ${CMAKE_CURRENT_LIST_DIR}/audio2_player.c ${CMAKE_CURRENT_LIST_DIR}/audio2_recorder.c ${CMAKE_CURRENT_LIST_DIR}/i2s_helper.c - ${CMAKE_CURRENT_LIST_DIR}/vfs_stream.c ) # Add the current directory as an include directory. diff --git a/m5stack/components/M5Unified/mpy_m5gfx.cpp b/m5stack/components/M5Unified/mpy_m5gfx.cpp index 387e6ea3..c36c28c8 100644 --- a/m5stack/components/M5Unified/mpy_m5gfx.cpp +++ b/m5stack/components/M5Unified/mpy_m5gfx.cpp @@ -146,7 +146,7 @@ mp_obj_t gfx_loadFont(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args fontWrapper = new LFS2Wrapper(); ((gfx_obj_t *)MP_OBJ_TO_PTR(pos_args[0]))->font_wrapper = fontWrapper; } - fontWrapper->open(mp_obj_str_get_str(args[ARG_font].u_obj), LFS2_O_RDONLY); + fontWrapper->open(mp_obj_str_get_str(args[ARG_font].u_obj), VFS_READ); ret = gfx->loadFont((lgfx::DataWrapper *)fontWrapper); } else { // buffer mp_buffer_info_t bufinfo; @@ -184,7 +184,7 @@ mp_obj_t gfx_setFont(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) fontWrapper = new LFS2Wrapper(); ((gfx_obj_t *)MP_OBJ_TO_PTR(pos_args[0]))->font_wrapper = fontWrapper; } - fontWrapper->open(mp_obj_str_get_str(args[ARG_font].u_obj), LFS2_O_RDONLY); + fontWrapper->open(mp_obj_str_get_str(args[ARG_font].u_obj), VFS_READ); gfx->loadFont((lgfx::DataWrapper *)fontWrapper); } else { gfx->setFont((const m5gfx::IFont *)((font_obj_t *)args[ARG_font].u_obj)->font); diff --git a/m5stack/components/M5Unified/mpy_m5lfs2.txt b/m5stack/components/M5Unified/mpy_m5lfs2.txt index dfee27dd..abe6a3bf 100644 --- a/m5stack/components/M5Unified/mpy_m5lfs2.txt +++ b/m5stack/components/M5Unified/mpy_m5lfs2.txt @@ -5,20 +5,9 @@ */ #include -#include -#include -#include +#include "_vfs_stream.h" -// micropython/extmod/vfs_lfs.c line: 115 -typedef struct _mp_obj_vfs_lfs2_t { - mp_obj_base_t base; - mp_vfs_blockdev_t blockdev; - bool enable_mtime; - vstr_t cur_dir; - struct lfs2_config config; - lfs2_t lfs; -} mp_obj_vfs_lfs2_t; struct LFS2Wrapper : public m5gfx::DataWrapper { @@ -28,64 +17,52 @@ struct LFS2Wrapper : public m5gfx::DataWrapper } bool open(const char *path) override { - return open(path, LFS2_O_RDWR | LFS2_O_CREAT); + _file = vfs_stream_open(path, VFS_READ | VFS_WRITE); + return _file != nullptr; } bool open(const char *path, int flag) { - const char *full_path; - struct lfs2_info _finfo; - mp_vfs_mount_t *_fm = mp_vfs_lookup_path(path, &full_path); - if (_fm == MP_VFS_NONE || _fm == MP_VFS_ROOT) { - if (_fm == MP_VFS_NONE) { - mp_printf(&mp_plat_print, "file <%s> was not found\r\n", path); - } - if (_fm == MP_VFS_ROOT) { - mp_printf(&mp_plat_print, "file path is invalid\r\n"); - } - return false; - } - _fp = &((mp_obj_vfs_lfs2_t *)MP_OBJ_TO_PTR(_fm->obj))->lfs; - enum lfs2_error res = (lfs2_error)lfs2_stat(_fp, full_path, &_finfo); - if (res != LFS2_ERR_OK) { - mp_printf(&mp_plat_print, "%s\r\n", strerror(res)); - return false; - } - _file = (lfs2_file_t *)malloc(1 * sizeof(lfs2_file_t)); - memset(&_fcfg, 0, sizeof(lfs2_file_config)); - _fcfg.buffer = malloc(_fp->cfg->cache_size * sizeof(uint8_t)); - return (lfs2_file_opencfg(_fp, _file, full_path, flag, - &_fcfg) == LFS2_ERR_OK) ? true: false; + _file = vfs_stream_open(path, flag); + return _file != nullptr; } int read(uint8_t *buf, uint32_t len) override { - return lfs2_file_read(_fp, _file, (char *)buf, len); + if (_file == nullptr) { + return -1; + } + return vfs_stream_read(_file, buf, len); } void skip(int32_t offset) override { - lfs2_file_seek(_fp, _file, offset, LFS2_SEEK_CUR); + if (_file == nullptr) { + return; + } + vfs_stream_seek(_file, offset, SEEK_CUR); } bool seek(uint32_t offset) override { - return lfs2_file_seek(_fp, _file, offset, LFS2_SEEK_SET); + if (_file == nullptr) { + return false; + } + return vfs_stream_seek(_file, offset, SEEK_SET) >= 0; } bool seek(uint32_t offset, int origin) { - return lfs2_file_seek(_fp, _file, offset, origin); + if (_file == nullptr) { + return false; + } + return vfs_stream_seek(_file, offset, origin) >= 0; } void close() override { - if (_fp) { - lfs2_file_close(_fp, _file); - } if (_file) { - free(_file); - } - if (_fcfg.buffer) { - free(_fcfg.buffer); + vfs_stream_close(_file); + _file = nullptr; } } int32_t tell(void) override { - return lfs2_file_tell(_fp, _file); + if (_file == nullptr) { + return -1; + } + return vfs_stream_tell(_file); } protected: - lfs2_t *_fp = nullptr; - lfs2_file_t *_file = nullptr; - struct lfs2_file_config _fcfg; + void *_file = nullptr; }; diff --git a/m5stack/components/M5Unified/mpy_m5widgets.cpp b/m5stack/components/M5Unified/mpy_m5widgets.cpp index 9ecb5a1a..0456a52c 100644 --- a/m5stack/components/M5Unified/mpy_m5widgets.cpp +++ b/m5stack/components/M5Unified/mpy_m5widgets.cpp @@ -152,7 +152,7 @@ static void m5widgets_label_font_init_helper(widgets_label_obj_t *self, mp_obj_t if (font != mp_const_none) { if (mp_obj_is_str(font)) { // FIXME: check if the font is already - self->fontWrapper->open(mp_obj_str_get_str(font), LFS2_O_RDONLY); + self->fontWrapper->open(mp_obj_str_get_str(font), VFS_READ); if (self->rtfont->loadFont((lgfx::DataWrapper *)self->fontWrapper)) { self->font = self->rtfont; } else { @@ -383,7 +383,7 @@ static void m5widgets_title_font_init_helper(widgets_title_obj_t *self, mp_obj_t if (font != mp_const_none) { if (mp_obj_is_str(font)) { // FIXME: check if the font is already - self->fontWrapper->open(mp_obj_str_get_str(font), LFS2_O_RDONLY); + self->fontWrapper->open(mp_obj_str_get_str(font), VFS_READ); if (self->rtfont->loadFont((lgfx::DataWrapper *)self->fontWrapper)) { self->font = self->rtfont; } else { @@ -580,7 +580,7 @@ static inline void m5widgets_image_erase_helper(widgets_image_obj_t *self) { } static bool m5widgets_image_bmp_helper(LFS2Wrapper *file, widgets_image_obj_t *self) { - if (!file->open(self->img, LFS2_O_RDONLY)) { + if (!file->open(self->img, VFS_READ)) { return false; } @@ -591,7 +591,7 @@ static bool m5widgets_image_bmp_helper(LFS2Wrapper *file, widgets_image_obj_t *s if (buf[0] != 'B' && buf[1] != 'M') { return false; } - file->seek(16, LFS2_SEEK_CUR); + file->seek(16, SEEK_CUR); uint32_t w = file->read32(); uint32_t h = file->read32(); file->close(); @@ -607,7 +607,7 @@ static bool m5widgets_image_bmp_helper(LFS2Wrapper *file, widgets_image_obj_t *s } static bool m5widgets_image_jpg_helper(LFS2Wrapper *file, widgets_image_obj_t *self) { - if (!file->open(self->img, LFS2_O_RDONLY)) { + if (!file->open(self->img, VFS_READ)) { return false; } uint8_t idx, result = 0; @@ -622,7 +622,7 @@ static bool m5widgets_image_jpg_helper(LFS2Wrapper *file, widgets_image_obj_t *s if (idx >= 0xE0 && idx <= 0xEF) { value = file->read16swap(); - file->seek((uint32_t)(value - 2), LFS2_SEEK_CUR); + file->seek((uint32_t)(value - 2), SEEK_CUR); continue; } @@ -636,10 +636,10 @@ static bool m5widgets_image_jpg_helper(LFS2Wrapper *file, widgets_image_obj_t *s case 0xDC: case 0xDD: value = file->read16swap(); - file->seek((uint32_t)(value - 2), LFS2_SEEK_CUR); + file->seek((uint32_t)(value - 2), SEEK_CUR); break; case 0xC0: - file->seek(0x03, LFS2_SEEK_CUR); + file->seek(0x03, SEEK_CUR); h = (uint32_t)file->read16swap(); w = (uint32_t)file->read16swap(); result = 1; @@ -651,7 +651,7 @@ static bool m5widgets_image_jpg_helper(LFS2Wrapper *file, widgets_image_obj_t *s break; default: value = file->read16swap(); - if (file->seek((uint32_t)(value - 2), LFS2_SEEK_CUR) != 0) { + if (file->seek((uint32_t)(value - 2), SEEK_CUR) != 0) { result = 3; } break; @@ -669,7 +669,7 @@ static bool m5widgets_image_jpg_helper(LFS2Wrapper *file, widgets_image_obj_t *s } static bool m5widgets_image_png_helper(LFS2Wrapper *file, widgets_image_obj_t *self) { - if (!file->open(self->img, LFS2_O_RDONLY)) { + if (!file->open(self->img, VFS_READ)) { return false; } From f46f01aa8cba0b97a476e485046dbbca49f53bb3 Mon Sep 17 00:00:00 2001 From: lbuque Date: Thu, 15 May 2025 12:04:25 +0800 Subject: [PATCH 078/322] components/M5Unified: Update M5GFX and M5Unified. M5GFX no longer depends on EPDiy. Signed-off-by: lbuque --- .gitmodules | 3 --- m5stack/CMakeListsDefault.cmake | 7 ------- m5stack/CMakeListsLvgl.cmake | 7 ------- m5stack/Makefile | 3 --- m5stack/boards/M5STACK_PaperS3/mpconfigboard.cmake | 1 - m5stack/components/M5Unified/CMakeLists.txt | 2 +- m5stack/components/M5Unified/M5GFX | 2 +- m5stack/components/M5Unified/M5Unified | 2 +- m5stack/components/epdiy | 1 - 9 files changed, 3 insertions(+), 25 deletions(-) delete mode 160000 m5stack/components/epdiy diff --git a/.gitmodules b/.gitmodules index 21dbbe29..9a3ae1f9 100644 --- a/.gitmodules +++ b/.gitmodules @@ -25,9 +25,6 @@ [submodule "m5stack/components/esp-code-scanner"] path = m5stack/components/esp-code-scanner url = https://github.com/hlym123/esp-code-scanner.git -[submodule "m5stack/components/epdiy"] - path = m5stack/components/epdiy - url = https://github.com/vroland/epdiy.git [submodule "m5stack/components/esp_zigbee_host"] path = m5stack/components/esp_zigbee_host url = https://github.com/hlym123/esp_zigbee_host.git diff --git a/m5stack/CMakeListsDefault.cmake b/m5stack/CMakeListsDefault.cmake index 0f2f2954..501a4b83 100644 --- a/m5stack/CMakeListsDefault.cmake +++ b/m5stack/CMakeListsDefault.cmake @@ -242,13 +242,6 @@ list(APPEND IDF_COMPONENTS ) endif() -if (M5_EPDIY_ENABLE AND BOARD_TYPE STREQUAL "papers3") -message(STATUS "Enable EPDiy component") -list(APPEND IDF_COMPONENTS - epdiy -) -endif() - if(IDF_TARGET STREQUAL "esp32" OR IDF_TARGET STREQUAL "esp32s3") list(APPEND IDF_COMPONENTS boards) list(APPEND IDF_COMPONENTS audio_pipeline) diff --git a/m5stack/CMakeListsLvgl.cmake b/m5stack/CMakeListsLvgl.cmake index 1473ac08..ad7b5281 100644 --- a/m5stack/CMakeListsLvgl.cmake +++ b/m5stack/CMakeListsLvgl.cmake @@ -241,13 +241,6 @@ list(APPEND IDF_COMPONENTS ) endif() -if (M5_EPDIY_ENABLE AND BOARD_TYPE STREQUAL "papers3") -message(STATUS "Enable EPDiy component") -list(APPEND IDF_COMPONENTS - epdiy -) -endif() - if(IDF_TARGET STREQUAL "esp32" OR IDF_TARGET STREQUAL "esp32s3") list(APPEND IDF_COMPONENTS boards) list(APPEND IDF_COMPONENTS audio_pipeline) diff --git a/m5stack/Makefile b/m5stack/Makefile index 8a2fdb61..b8c9e0e1 100644 --- a/m5stack/Makefile +++ b/m5stack/Makefile @@ -280,7 +280,6 @@ submodules: git submodule update --init ./components/esp_dl git submodule update --init ./components/esp-code-scanner git submodule update --init ./components/esp_zigbee_host - git submodule update --init ./components/epdiy git submodule update --init ./components/M5Unified/M5GFX git submodule update --init ./components/M5Unified/M5Unified #git submodule update --init --recursive ./components/lv_bindings @@ -308,7 +307,6 @@ patch: $(call Package/patche,$(abspath $(IDF_PATH)),$(abspath ./patches/1003-WIP-Compatible-with-esp-adf-v2.7.diff)) $(call Package/patche,$(abspath ./components/M5Unified/M5Unified),$(abspath ./patches/2005-Support-LTR553.patch)) $(call Package/patche,$(abspath $(ADF_PATH)),$(abspath ./patches/3002-Modify-i2s_stream_idf5.patch)) - $(call Package/patche,$(abspath ./components/epdiy),$(abspath ./patches/4001-Avoid-epdiy-compilation-failure-on-esp32-c6.patch)) $(call Package/patche,$(abspath ./components/esp32-camera),$(abspath ./patches/5001-Add-software-i2c-support.patch)) # Unapply patches @@ -322,5 +320,4 @@ unpatch: $(call Package/unpatche,$(abspath $(IDF_PATH)),$(abspath ./patches/1003-WIP-Compatible-with-esp-adf-v2.7.diff)) $(call Package/unpatche,$(abspath ./components/M5Unified/M5Unified),$(abspath ./patches/2005-Support-LTR553.patch)) $(call Package/unpatche,$(abspath $(ADF_PATH)),$(abspath ./patches/3002-Modify-i2s_stream_idf5.patch)) - $(call Package/unpatche,$(abspath ./components/epdiy),$(abspath ./patches/4001-Avoid-epdiy-compilation-failure-on-esp32-c6.patch)) $(call Package/unpatche,$(abspath ./components/esp32-camera),$(abspath ./patches/5001-Add-software-i2c-support.patch)) diff --git a/m5stack/boards/M5STACK_PaperS3/mpconfigboard.cmake b/m5stack/boards/M5STACK_PaperS3/mpconfigboard.cmake index 31d1faa3..df62428a 100644 --- a/m5stack/boards/M5STACK_PaperS3/mpconfigboard.cmake +++ b/m5stack/boards/M5STACK_PaperS3/mpconfigboard.cmake @@ -4,7 +4,6 @@ set(IDF_TARGET esp32s3) set(BOARD_ID 19) -set(M5_EPDIY_ENABLE TRUE) set(MICROPY_PY_LVGL 0) set(SDKCONFIG_DEFAULTS diff --git a/m5stack/components/M5Unified/CMakeLists.txt b/m5stack/components/M5Unified/CMakeLists.txt index 51c62325..7c3b7bd3 100644 --- a/m5stack/components/M5Unified/CMakeLists.txt +++ b/m5stack/components/M5Unified/CMakeLists.txt @@ -27,7 +27,7 @@ file(GLOB SRCS set(COMPONENT_SRCS ${SRCS}) if (IDF_VERSION_MAJOR GREATER_EQUAL 5) - set(COMPONENT_REQUIRES esp_adc nvs_flash efuse driver esp_timer epdiy main_${IDF_TARGET}) + set(COMPONENT_REQUIRES esp_adc nvs_flash efuse driver esp_timer main_${IDF_TARGET}) else() set(COMPONENT_REQUIRES esp_adc_cal nvs_flash efuse main) endif() diff --git a/m5stack/components/M5Unified/M5GFX b/m5stack/components/M5Unified/M5GFX index 362589b9..1618b1e7 160000 --- a/m5stack/components/M5Unified/M5GFX +++ b/m5stack/components/M5Unified/M5GFX @@ -1 +1 @@ -Subproject commit 362589b9561bf533b5d0a2774e0508b017ab4a18 +Subproject commit 1618b1e7b0953b91987ecc8a8519607df6fa7624 diff --git a/m5stack/components/M5Unified/M5Unified b/m5stack/components/M5Unified/M5Unified index 364462a0..bdb7f89e 160000 --- a/m5stack/components/M5Unified/M5Unified +++ b/m5stack/components/M5Unified/M5Unified @@ -1 +1 @@ -Subproject commit 364462a01455c430fd82810ceed0016ecf39eee5 +Subproject commit bdb7f89e02b8f341079efd7094ee7475a895961d diff --git a/m5stack/components/epdiy b/m5stack/components/epdiy deleted file mode 160000 index cbdaaa7a..00000000 --- a/m5stack/components/epdiy +++ /dev/null @@ -1 +0,0 @@ -Subproject commit cbdaaa7a0d0f084b2d1e7ee3333a24ad3d67f720 From 29db638c66ba439940d68c820768555f88717670 Mon Sep 17 00:00:00 2001 From: luoweiyuan Date: Thu, 15 May 2025 15:05:32 +0800 Subject: [PATCH 079/322] libs/module: Add support for Module DCMotor. Signed-off-by: luoweiyuan --- docs/en/module/dc_motor.rst | 59 ++++ docs/en/module/index.rst | 1 + docs/en/refs/module.dc_motor.ref | 25 ++ .../zh_CN/LC_MESSAGES/module/dc_motor.po | 267 ++++++++++++++++++ .../cores3_dc_motor_module_speed_control.m5f2 | 1 + .../cores3_dc_motor_module_speed_control.py | 95 +++++++ m5stack/libs/module/__init__.py | 1 + m5stack/libs/module/dc_motor.py | 129 +++++++++ m5stack/libs/module/manifest.py | 1 + 9 files changed, 579 insertions(+) create mode 100644 docs/en/module/dc_motor.rst create mode 100644 docs/en/refs/module.dc_motor.ref create mode 100644 docs/locales/zh_CN/LC_MESSAGES/module/dc_motor.po create mode 100644 examples/module/dc_motor/cores3_dc_motor_module_speed_control.m5f2 create mode 100644 examples/module/dc_motor/cores3_dc_motor_module_speed_control.py create mode 100644 m5stack/libs/module/dc_motor.py diff --git a/docs/en/module/dc_motor.rst b/docs/en/module/dc_motor.rst new file mode 100644 index 00000000..c80eaf07 --- /dev/null +++ b/docs/en/module/dc_motor.rst @@ -0,0 +1,59 @@ +DCMotor Module +============== + +.. sku: M021 + +.. include:: ../refs/module.dc_motor.ref + +This library is the driver for Module DCMotor, and the module communicates via I2C. + +Support the following products: + + |Module DCMotor| + +UiFlow2 Example +--------------- + +Speed Control +^^^^^^^^^^^^^ + +Open the |cores3_dc_motor_module_speed_control.m5f2| project in UiFlow2. + +This example demonstrates the use of the DCMotor Module to control the speed of a DC motor and display the motor's encoder value in real-time. +The program automatically adjusts the motor speed, gradually increasing or decreasing the speed until it reaches the maximum or minimum value, then reverses the direction. + +UiFlow2 Code Block: + + |cores3_dc_motor_module_speed_control.png| + +Example output: + + None + +MicroPython Example +------------------- + +Speed Control +^^^^^^^^^^^^^ + +This example demonstrates the use of the DCMotor Module to control the speed of a DC motor and display the motor's encoder value in real-time. +The program automatically adjusts the motor speed, gradually increasing or decreasing the speed until it reaches the maximum or minimum value, then reverses the direction. + +MicroPython Code Block: + + .. literalinclude:: ../../../examples/module/dc_motor/cores3_dc_motor_module_speed_control.py + :language: python + :linenos: + +Example output: + + None + +**API** +------- + +DCMotorModule +^^^^^^^^^^^^^ + +.. autoclass:: module.dc_motor.DCMotorModule + :members: diff --git a/docs/en/module/index.rst b/docs/en/module/index.rst index 546223a5..a3574820 100644 --- a/docs/en/module/index.rst +++ b/docs/en/module/index.rst @@ -9,6 +9,7 @@ Module audio.rst bala2.rst commu.rst + dc_motor.rst display.rst dmx.rst dualkmeter.rst diff --git a/docs/en/refs/module.dc_motor.ref b/docs/en/refs/module.dc_motor.ref new file mode 100644 index 00000000..28d95804 --- /dev/null +++ b/docs/en/refs/module.dc_motor.ref @@ -0,0 +1,25 @@ + +.. |Module DCMotor| image:: https://static-cdn.m5stack.com/resource/docs/products/module/lego_plus/lego_plus_01.webp + :target: https://docs.m5stack.com/zh_CN/module/lego_plus + :height: 200px + :width: 200px + +.. |init.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/dcmotor/init.png +.. |set_motor_speed.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/dcmotor/set_motor_speed.png +.. |set_motor_speed_percent.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/dcmotor/set_motor_speed_percent.png +.. |get_encoder.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/dcmotor/get_encoder.png +.. |clear_encoder.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/dcmotor/clear_encoder.png + + + +.. |cores3_dc_motor_module_speed_control.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/dcmotor/example.png + +.. |cores3_dc_motor_module_speed_control.m5f2| raw:: html + + + cores3_dc_motor_module_speed_control.m5f2 + + diff --git a/docs/locales/zh_CN/LC_MESSAGES/module/dc_motor.po b/docs/locales/zh_CN/LC_MESSAGES/module/dc_motor.po new file mode 100644 index 00000000..4ffa789d --- /dev/null +++ b/docs/locales/zh_CN/LC_MESSAGES/module/dc_motor.po @@ -0,0 +1,267 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2016 - 2025 M5Stack Technology Co., Ltd +# This file is distributed under the same license as the UIFlow2 Programming +# Guide package. +# FIRST AUTHOR , 2025. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: UIFlow2 Programming Guide \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-05-15 10:13+0800\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: zh_CN\n" +"Language-Team: zh_CN \n" +"Plural-Forms: nplurals=1; plural=0;\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.16.0\n" + +#: ../../en/module/dc_motor.rst:2 1ca2119f25dd4309815b43ddc27bdcdd +msgid "DCMotor Module" +msgstr "" + +#: ../../en/module/dc_motor.rst:8 e81bd49d6f9845d7b85c8dbdf082d56e +msgid "" +"This library is the driver for Module DCMotor, and the module " +"communicates via I2C." +msgstr "这个库是 Module DCMotor 的驱动,该模块使用 I2C 通信。" + +#: ../../en/module/dc_motor.rst:10 c82a3f68ccab4e30bfd8630db272a7a9 +msgid "Support the following products:" +msgstr "支持以下产品:" + +#: ../../en/module/dc_motor.rst:12 6285a2bf95ee48f6b9a66fbfd0f1eade +msgid "|Module DCMotor|" +msgstr "" + +#: ../../en/refs/module.dc_motor.ref 11f13b042be9473b8422c409a1947ec0 +msgid "Module DCMotor" +msgstr "" + +#: ../../en/module/dc_motor.rst:15 af14860f380643c6b8e984583bfaf036 +msgid "UiFlow2 Example:" +msgstr "UiFlow2 应用示例:" + +#: ../../en/module/dc_motor.rst:18 ../../en/module/dc_motor.rst:37 +#: 52d95ba311144700a040e89950d2a949 6f39ae0d45264f148395235efd256299 +msgid "Speed Control" +msgstr "速度控制" + +#: ../../en/module/dc_motor.rst:20 1532b132c10842a4b0a938c799a3e093 +msgid "Open the |cores3_dc_motor_module_speed_control.m5f2| project in UiFlow2." +msgstr "在 UiFlow2 上打开 |cores3_dc_motor_module_speed_control.m5f2| 项目。" + +#: ../../en/module/dc_motor.rst:22 ../../en/module/dc_motor.rst:39 +#: 1f1774b78e474c9b83209d96299b802e 59d3aaae182146ee9962a64ab055d2a0 +msgid "" +"This example demonstrates the use of the DCMotor Module to control the " +"speed of a DC motor and display the motor's encoder value in real-time. " +"The program automatically adjusts the motor speed, gradually increasing " +"or decreasing the speed until it reaches the maximum or minimum value, " +"then reverses the direction." +msgstr "这个案例展示使用 DCMotor Module 控制直流电机的转速,并实时显示电机的编码器值。程序自动调整电机的转速,逐步增加或减少速度,直到达到最大或最小值后反转。" + +#: ../../en/module/dc_motor.rst:25 2943f702c7094a69b3d0373557c9b4f3 +#: 3cb845211c074b349ad639be579d6775 44321ac4fb574d598ac50e3b83095152 +#: 4a84b721a0464e008a87b5ab8bce1795 d0d18307a0954961a3e9f9ed1594861b +#: e09c9a10f8104eec8d573684e9a456d1 module.dc_motor.DCMotorModule:3 +#: module.dc_motor.DCMotorModule.clear_encoder:3 +#: module.dc_motor.DCMotorModule.get_encoder:7 +#: module.dc_motor.DCMotorModule.set_motor_speed:6 +#: module.dc_motor.DCMotorModule.set_motor_speed_percent:6 of +msgid "UiFlow2 Code Block:" +msgstr "UiFlow2 代码块:" + +#: ../../en/module/dc_motor.rst:27 e1e5ad2450ff447981838e43b63d5bff +msgid "|cores3_dc_motor_module_speed_control.png|" +msgstr "" + +#: ../../en/refs/module.dc_motor.ref:15 55b4a3e2badd45189fd661f95c09ef2b +msgid "cores3_dc_motor_module_speed_control.png" +msgstr "" + +#: ../../en/module/dc_motor.rst:29 ../../en/module/dc_motor.rst:48 +#: 13de64d11d5d4cb9a650fc1fb1edf994 c7d65dd2c29c4452b06b0e86d2b6ce7b +msgid "Example output:" +msgstr "示例输出:" + +#: ../../en/module/dc_motor.rst:31 ../../en/module/dc_motor.rst:50 +#: 26f33ac87dda4450a492d4fddfb13c4b 3e371d7e22564de1a5e55fd98e871529 +msgid "None" +msgstr "无" + +#: ../../en/module/dc_motor.rst:34 918155ed1f3c4a528781be32fcb91861 +msgid "MicroPython Example:" +msgstr "" + +#: ../../en/module/dc_motor.rst:42 13686e7397434fe7a70dc77687b2b239 +#: 2b3c4bfb8d8f4fda8d0605b850361801 d6bafe43a6a640ea8016b1a9b5de4ef7 +#: dc6f9130d75d4fe59546a3a607742c6b e52fc9d178dc42058c52ec50cff59c51 +#: f3a54b53a84e45aaa66bad04b1d39a38 module.dc_motor.DCMotorModule:7 +#: module.dc_motor.DCMotorModule.clear_encoder:7 +#: module.dc_motor.DCMotorModule.get_encoder:11 +#: module.dc_motor.DCMotorModule.set_motor_speed:10 +#: module.dc_motor.DCMotorModule.set_motor_speed_percent:10 of +msgid "MicroPython Code Block:" +msgstr "MicroPython 应用示例:" + +#: ../../en/module/dc_motor.rst:53 f42b9c6ab3cc439c839ee292370df1f6 +msgid "**API**" +msgstr "API应用" + +#: ../../en/module/dc_motor.rst:56 5f25239d589a45aaad5349a823368b5e +msgid "DCMotorModule" +msgstr "" + +#: 21322b7d0e0543feac9551f79cf6d421 module.dc_motor.DCMotorModule:1 of +msgid "Bases: :py:class:`object`" +msgstr "" + +#: 4214c0ee59cd41eca541591d6e4e1b56 module.dc_motor.DCMotorModule:1 of +msgid "Create an DCMotorModule object." +msgstr "创建一个 DCMotorModule 对象。" + +#: 52699e5ad1954e23bc5ea011eb713ef1 module.dc_motor.DCMotorModule:5 of +msgid "|init.png|" +msgstr "" + +#: ../../en/refs/module.dc_motor.ref:7 9add9e284bd94f58957c905f11d39147 +msgid "init.png" +msgstr "" + +#: 264b5f9257544b4688a8aa54c952ef00 +#: module.dc_motor.DCMotorModule.clear_encoder:1 of +msgid "Clear encoder value." +msgstr "清空编码器计数值" + +#: 11e69f0922c642d7a02a47317444d696 +#: module.dc_motor.DCMotorModule.clear_encoder:5 of +msgid "|clear_encoder.png|" +msgstr "" + +#: ../../en/refs/module.dc_motor.ref:11 a39c5320a1c348698a59bf8103198645 +msgid "clear_encoder.png" +msgstr "" + +#: 2c5543b5e3b84a2aa14b28c960e67035 73d20bae3fd74f60b0cf244cd17aef67 +#: 902bbffc94da43628e32346fe706c8ed ea1e79620ea54a87ae3160aa5989b940 +#: module.dc_motor.DCMotorModule.clear_encoder +#: module.dc_motor.DCMotorModule.get_encoder +#: module.dc_motor.DCMotorModule.set_motor_speed +#: module.dc_motor.DCMotorModule.set_motor_speed_percent of +msgid "Parameters" +msgstr "" + +#: 26fbec0de46f481e8943f387ae9f7308 84941e92e3c34bd99f4a8d9cd3b8cf67 +#: e83b8f75f040487a8c07e7a1a81d540f ff0f2a5c38914b1595dccc82f932a237 +#: module.dc_motor.DCMotorModule.clear_encoder +#: module.dc_motor.DCMotorModule.get_encoder +#: module.dc_motor.DCMotorModule.set_motor_speed +#: module.dc_motor.DCMotorModule.set_motor_speed_percent of +msgid "Return type" +msgstr "" + +#: 19e93569b76d47dab3187910c3ba2106 module.dc_motor.DCMotorModule.get_encoder:1 +#: of +msgid "Get encoder count." +msgstr "获取编码器计数值" + +#: 2b2b876814574b99b6d9770147611928 d91ddeb0ab984858b6440ca755e4e589 +#: module.dc_motor.DCMotorModule.get_encoder:3 +#: module.dc_motor.DCMotorModule.set_motor_speed_percent:3 of +msgid "port num, range: 1~4." +msgstr "端口号,范围:1~4。" + +#: a0f6b2913bf74eee9dd80ee11ff253e2 module.dc_motor.DCMotorModule.get_encoder +#: of +msgid "Returns" +msgstr "" + +#: dda5a1d5d1cd43808d5fd1dfbfc49a19 module.dc_motor.DCMotorModule.get_encoder:4 +#: of +msgid "encoder count." +msgstr "编码器计数值。" + +#: b598b73d7f524abcb9940231083e75c1 module.dc_motor.DCMotorModule.get_encoder:9 +#: of +msgid "|get_encoder.png|" +msgstr "" + +#: ../../en/refs/module.dc_motor.ref:10 65ea074a7ddc43bab82cbf72bf6c4fd9 +msgid "get_encoder.png" +msgstr "" + +#: 37be96c52c7241cd9675f578cd928d39 +#: module.dc_motor.DCMotorModule.set_motor_speed:1 of +msgid "Set speed of motor." +msgstr "设置电机转速。" + +#: e503d2d69518478898c80f76562dc54a +#: module.dc_motor.DCMotorModule.set_motor_speed:3 of +msgid "port num, range: 1~4" +msgstr "端口号:范围:1~4。" + +#: 6a9f117824d24ad9820c23bfb0c828a5 +#: module.dc_motor.DCMotorModule.set_motor_speed:4 of +msgid "motor speed, range: -255~255" +msgstr "电机转速,范围:-255~255。" + +#: 5d9370aa842b4a428a4cbf3f96413502 +#: module.dc_motor.DCMotorModule.set_motor_speed:8 of +msgid "|set_motor_speed.png|" +msgstr "" + +#: ../../en/refs/module.dc_motor.ref:8 88f0cf7a882d4254b6e10acc5b741947 +msgid "set_motor_speed.png" +msgstr "" + +#: b824e51187904e9592652cfc4847bd78 +#: module.dc_motor.DCMotorModule.set_motor_speed_percent:1 of +msgid "Set motor speed as a percentage." +msgstr "设置电机速度百分比。" + +#: beac87dce5cc45fdadac949c53e7ada4 +#: module.dc_motor.DCMotorModule.set_motor_speed_percent:4 of +msgid "motor speed percent, range: -100.0% ~ +100.0%." +msgstr "电机速度百分比,范围:-100.0% ~ +100.0%。 + +#: 820767ad36e34292a92880e081db935f +#: module.dc_motor.DCMotorModule.set_motor_speed_percent:8 of +msgid "|set_motor_speed_percent.png|" +msgstr "" + +#: ../../en/refs/module.dc_motor.ref:9 a2284276fc494c2b86cf93389a27e4be +msgid "set_motor_speed_percent.png" +msgstr "" + +#~ msgid "ECG Module" +#~ msgstr "" + +#~ msgid "|set_speed.png|" +#~ msgstr "" + +#~ msgid "set_speed.png" +#~ msgstr "" + +#~ msgid "|set_speed_percent.png|" +#~ msgstr "" + +#~ msgid "set_speed_percent.png" +#~ msgstr "" + +#~ msgid "Stop all motors by setting speed to 0." +#~ msgstr "" + +#~ msgid "|stop_all.png|" +#~ msgstr "" + +#~ msgid "stop_all.png" +#~ msgstr "" + +#~ msgid "The example demonstrates" +#~ msgstr "" + diff --git a/examples/module/dc_motor/cores3_dc_motor_module_speed_control.m5f2 b/examples/module/dc_motor/cores3_dc_motor_module_speed_control.m5f2 new file mode 100644 index 00000000..cb646d0e --- /dev/null +++ b/examples/module/dc_motor/cores3_dc_motor_module_speed_control.m5f2 @@ -0,0 +1 @@ +{"version":"V2.0","versionNumber":"V2.2.6","type":"cores3","components":[{"name":"screen","type":"screen","layer":0,"screenId":"builtin","screenName":"","id":"__cores3_screen","createTime":1747215032667,"x":0,"y":0,"width":320,"height":240,"backgroundColor":"#222222","size":0,"isSelected":true},{"name":"title0","type":"title","layer":1,"screenId":"builtin","screenName":"","id":"aRunpZm`_GhwY5BM","createTime":1747215395818,"x":0,"y":0,"color":"#ffffff","backgroundColor":"#0000FF","text":"DCMotor Control","textOffset":3,"font":"Widgets.FONTS.DejaVu24","isSelected":false},{"name":"label_speed","type":"label","layer":2,"screenId":"builtin","screenName":"","id":"w=FxR74t!9!9Bq`%","createTime":1747215416204,"x":5,"y":60,"color":"#ffffff","backgroundColor":"#222222","text":"Speed:","engine":"gfx","font":"Widgets.FONTS.DejaVu24","rotation":0,"isSelected":false},{"name":"label_speed_val","type":"label","layer":3,"screenId":"builtin","screenName":"","id":"aT41zj&IMch@vWvO","createTime":1747215427784,"x":105,"y":60,"color":"#ffffff","backgroundColor":"#222222","text":"0","engine":"gfx","font":"Widgets.FONTS.DejaVu24","rotation":0,"isSelected":false},{"name":"label_encoder","type":"label","layer":4,"screenId":"builtin","screenName":"","id":"l+yrXLLtl6PU+S&k","createTime":1747217366759,"x":5,"y":100,"color":"#ffffff","backgroundColor":"#222222","text":"Encoder Value:","engine":"gfx","font":"Widgets.FONTS.DejaVu24","rotation":0,"isSelected":false},{"name":"label_encoder_value","type":"label","layer":5,"screenId":"builtin","screenName":"","id":"gbbNysMb*jGHOo!m","createTime":1747217368824,"x":203,"y":100,"color":"#ffffff","backgroundColor":"#222222","text":"0","engine":"gfx","font":"Widgets.FONTS.DejaVu24","rotation":0,"isSelected":false}],"resources":[{"hardware":["hardware_button","hardware_pin_button","imu","speaker","touch","als","mic"]},{"module":["module_dcmotor"]}],"units":[],"hats":[],"bases":[],"i2cs":[],"blockly":"last_timedirectionspeedencoder_valuetruemodule_dcmotor_0module_dcmotor_01directionTRUEspeed0trueGT11last_time100last_timedirectionspeedADD1speed5GTEspeed255directionFALSEspeedMINUS1speed5LTEspeed-255directionTRUEmodule_dcmotor_010speedlabel_speed_valLabelspeedencoder_valuemodule_dcmotor_01label_encoder_valueLabelencoder_valuemodule_dcmotor_01","screen":[{"simulationName":"Built-in","type":"builtin","width":320,"height":240,"scale":0.78,"screenName":"","blockId":"","screenColorType":0,"id":"builtin","createTime":1747215032667}],"logicWhenNum":0,"customList":[]} \ No newline at end of file diff --git a/examples/module/dc_motor/cores3_dc_motor_module_speed_control.py b/examples/module/dc_motor/cores3_dc_motor_module_speed_control.py new file mode 100644 index 00000000..9a2ac681 --- /dev/null +++ b/examples/module/dc_motor/cores3_dc_motor_module_speed_control.py @@ -0,0 +1,95 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import os, sys, io +import M5 +from M5 import * +from module import DCMotorModule +import time + + +title0 = None +label_speed = None +label_speed_val = None +label_encoder = None +label_encoder_value = None +module_dcmotor_0 = None +last_time = None +direction = None +speed = None +encoder_value = None + + +def setup(): + global \ + title0, \ + label_speed, \ + label_speed_val, \ + label_encoder, \ + label_encoder_value, \ + module_dcmotor_0, \ + last_time, \ + direction, \ + speed, \ + encoder_value + + M5.begin() + Widgets.fillScreen(0x222222) + title0 = Widgets.Title("DCMotor Control", 3, 0xFFFFFF, 0x0000FF, Widgets.FONTS.DejaVu24) + label_speed = Widgets.Label("Speed:", 5, 60, 1.0, 0xFFFFFF, 0x222222, Widgets.FONTS.DejaVu24) + label_speed_val = Widgets.Label("0", 105, 60, 1.0, 0xFFFFFF, 0x222222, Widgets.FONTS.DejaVu24) + label_encoder = Widgets.Label( + "Encoder Value:", 5, 100, 1.0, 0xFFFFFF, 0x222222, Widgets.FONTS.DejaVu24 + ) + label_encoder_value = Widgets.Label( + "0", 203, 100, 1.0, 0xFFFFFF, 0x222222, Widgets.FONTS.DejaVu24 + ) + module_dcmotor_0 = DCMotorModule() + module_dcmotor_0.clear_encoder(1) + direction = True + speed = 0 + + +def loop(): + global \ + title0, \ + label_speed, \ + label_speed_val, \ + label_encoder, \ + label_encoder_value, \ + module_dcmotor_0, \ + last_time, \ + direction, \ + speed, \ + encoder_value + M5.update() + if (time.ticks_diff((time.ticks_ms()), last_time)) > 100: + last_time = time.ticks_ms() + if direction: + speed = speed + 5 + if speed >= 255: + direction = False + else: + speed = speed - 5 + if speed <= -255: + direction = True + module_dcmotor_0.set_motor_speed(1, speed) + label_speed_val.setText(str(speed)) + encoder_value = module_dcmotor_0.get_encoder(1) + label_encoder_value.setText(str(encoder_value)) + module_dcmotor_0.clear_encoder(1) + + +if __name__ == "__main__": + try: + setup() + while True: + loop() + except (Exception, KeyboardInterrupt) as e: + try: + from utility import print_error_msg + + print_error_msg(e) + except ImportError: + print("please update to latest firmware") diff --git a/m5stack/libs/module/__init__.py b/m5stack/libs/module/__init__.py index 005cef69..eb26f93b 100644 --- a/m5stack/libs/module/__init__.py +++ b/m5stack/libs/module/__init__.py @@ -11,6 +11,7 @@ "CommuModuleCAN": "commu", "CommuModuleI2C": "commu", "CommuModuleRS485": "commu", + "DCMotorModule": "dc_motor", "DisplayModule": "display", "DMX512Module": "dmx", "DualKmeterModule": "dual_kmeter", diff --git a/m5stack/libs/module/dc_motor.py b/m5stack/libs/module/dc_motor.py new file mode 100644 index 00000000..97b458d8 --- /dev/null +++ b/m5stack/libs/module/dc_motor.py @@ -0,0 +1,129 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +from module.mbus import i2c1 +import struct +from micropython import const + + +SLAVE_ADDR = const(0x56) # I2C slave address +MOTOR_ADDR_BASE = const(0x00) # Motor control base register +ENCODER_ADDR_BASE = const(0x08) # Encoder register base +MOTOR_COUNT = const(4) # Number of motors + + +class DCMotorModule: + """Create an DCMotorModule object. + + UiFlow2 Code Block: + + |init.png| + + MicroPython Code Block: + + .. code-block:: python + + from module import DCMotorModule + + dcmotor_module = DCMotorModule() + """ + + def __init__(self): + self.i2c = i2c1 + self.addr = SLAVE_ADDR + self.encoder_offset = [0] * MOTOR_COUNT # store clear offset + + def set_motor_speed(self, id: int, speed: int) -> None: + """Set speed of motor. + + :param int id: port num, range: 1~4 + :param int speed: motor speed, range: -255~255 + + UiFlow2 Code Block: + + |set_motor_speed.png| + + MicroPython Code Block: + + .. code-block:: python + + dcmotor_module.set_motor_speed(id, speed) + """ + if not 1 <= id <= MOTOR_COUNT: + raise ValueError("id must be 1~4") + speed = max(-255, min(255, speed)) # Clamp speed + reg = MOTOR_ADDR_BASE + (id - 1) * 2 + val = struct.pack(" None: + """Set motor speed as a percentage. + + :param int id: port num, range: 1~4. + :param float percent: motor speed percent, range: -100.0% ~ +100.0%. + + UiFlow2 Code Block: + + |set_motor_speed_percent.png| + + MicroPython Code Block: + + .. code-block:: python + + dcmotor_module.set_motor_speed_percent(id, percent) + """ + if not -100.0 <= percent <= 100.0: + raise ValueError("percent must be in -100.0 to 100.0") + speed = int(percent * 255 / 100) + self.set_motor_speed(id, speed) + + def get_encoder(self, id: int) -> int: + """Get encoder count. + + :param int id: port num, range: 1~4. + :returns: encoder count. + :rtype: int + + UiFlow2 Code Block: + + |get_encoder.png| + + MicroPython Code Block: + + .. code-block:: python + + dcmotor_module.get_encoder() + """ + if not 1 <= id <= MOTOR_COUNT: + raise ValueError("id must be 1~4") + reg = ENCODER_ADDR_BASE + (id - 1) * 4 + try: + val = self.i2c.readfrom_mem(self.addr, reg, 4) + raw_value = struct.unpack(" bool: + """Clear encoder value. + + UiFlow2 Code Block: + + |clear_encoder.png| + + MicroPython Code Block: + + .. code-block:: python + + dcmotor_module.clear_encoder() + """ + if not 1 <= id <= MOTOR_COUNT: + raise ValueError("id must be 1~4") + current = self.get_encoder(id) + self.encoder_offset[id - 1] + self.encoder_offset[id - 1] = current + return True diff --git a/m5stack/libs/module/manifest.py b/m5stack/libs/module/manifest.py index f40602d9..8917428d 100644 --- a/m5stack/libs/module/manifest.py +++ b/m5stack/libs/module/manifest.py @@ -10,6 +10,7 @@ "audio.py", "bala2.py", "commu.py", + "dc_motor.py", "display.py", "dmx.py", "dual_kmeter.py", From db925c88c86db3bed590458ac174972495d99b37 Mon Sep 17 00:00:00 2001 From: luoweiyuan Date: Fri, 9 May 2025 11:51:57 +0800 Subject: [PATCH 080/322] libs/module: Add support for LoRa868 v1.2 Module. Signed-off-by: luoweiyuan --- docs/en/module/index.rst | 2 +- .../{lora_sx1262.rst => lora868_v12.rst} | 158 ++++- ...lora_sx1262.ref => module.lora868_v12.ref} | 24 +- .../zh_CN/LC_MESSAGES/module/lora868_v12.po | 571 ++++++++++++++++++ .../zh_CN/LC_MESSAGES/module/lora_sx1262.po | 472 --------------- .../cores3_lora868_v12_rx_example.m5f2 | 1 + .../cores3_lora868_v12_rx_example.py} | 20 +- .../cores3_lora868_v12_tx_example.m5f2 | 1 + .../cores3_lora868_v12_tx_example.py} | 18 +- .../cores3_lora_sx1262_rx_example.m5f2 | 1 - .../cores3_lora_sx1262_tx_example.m5f2 | 1 - m5stack/libs/module/__init__.py | 2 +- .../module/{lora_sx1262.py => lora868_v12.py} | 159 ++++- m5stack/libs/module/manifest.py | 2 +- 14 files changed, 902 insertions(+), 530 deletions(-) rename docs/en/module/{lora_sx1262.rst => lora868_v12.rst} (65%) rename docs/en/refs/{module.lora_sx1262.ref => module.lora868_v12.ref} (61%) create mode 100644 docs/locales/zh_CN/LC_MESSAGES/module/lora868_v12.po delete mode 100644 docs/locales/zh_CN/LC_MESSAGES/module/lora_sx1262.po create mode 100644 examples/module/lora868_v12/cores3_lora868_v12_rx_example.m5f2 rename examples/module/{lora_sx1262/cores3_lora_sx1262_rx_example.py => lora868_v12/cores3_lora868_v12_rx_example.py} (86%) create mode 100644 examples/module/lora868_v12/cores3_lora868_v12_tx_example.m5f2 rename examples/module/{lora_sx1262/cores3_lora_sx1262_tx_example.py => lora868_v12/cores3_lora868_v12_tx_example.py} (82%) delete mode 100644 examples/module/lora_sx1262/cores3_lora_sx1262_rx_example.m5f2 delete mode 100644 examples/module/lora_sx1262/cores3_lora_sx1262_tx_example.m5f2 rename m5stack/libs/module/{lora_sx1262.py => lora868_v12.py} (62%) diff --git a/docs/en/module/index.rst b/docs/en/module/index.rst index a3574820..4f32ef38 100644 --- a/docs/en/module/index.rst +++ b/docs/en/module/index.rst @@ -25,7 +25,7 @@ Module hmi.rst llm.rst lora.rst - lora_sx1262.rst + lora868_v12.rst lorawan868.rst lte.rst nbiot.rst diff --git a/docs/en/module/lora_sx1262.rst b/docs/en/module/lora868_v12.rst similarity index 65% rename from docs/en/module/lora_sx1262.rst rename to docs/en/module/lora868_v12.rst index 8169089c..fdf9ea3e 100644 --- a/docs/en/module/lora_sx1262.rst +++ b/docs/en/module/lora868_v12.rst @@ -1,17 +1,16 @@ LoRa868 v1.2 Module ============================ -.. sku: +.. sku: M029-V12 -.. include:: ../refs/module.lora_sx1262.ref +.. include:: ../refs/module.lora868_v12.ref -The LoRa868 v1.2 Module is part of the M5Stack stackable module series. It is a LoRa communication module that operates at a 900MHz frequency and utilizes the SX1262 chip solution. +The LoRa868 v1.2 Module is part of the M5Stack stackable module series. It is a LoRa communication module that operates at a 900MHz frequency band and utilizes the SX1262 chip solution. Support the following products: |LoRa868Module v1.2| - UiFlow2 Example -------------------------- @@ -20,13 +19,13 @@ UiFlow2 Example Sender ^^^^^^^^^^^^^^^^^^^^^^^^ -Open the |cores3_lora_sx1262_tx_example.m5f2| project in UiFlow2. +Open the |cores3_lora868_v12_tx_example.m5f2| project in UiFlow2. This example sends data every second. UiFlow2 Code Block: - |cores3_lora_sx1262_tx_example.png| + |cores3_lora868_v12_tx_example.png| Example output: @@ -35,13 +34,13 @@ Example output: Receiver ^^^^^^^^^^^^^^^^^^^^^^^^ -Open the |cores3_lora_sx1262_rx_example.m5f2| project in UiFlow2. +Open the |cores3_lora868_v12_rx_example.m5f2| project in UiFlow2. This example receives and displays data. UiFlow2 Code Block: - |cores3_lora_sx1262_rx_example.png| + |cores3_lora868_v12_rx_example.png| Example output: @@ -59,7 +58,7 @@ This example sends data every second. MicroPython Code Block: - .. literalinclude:: ../../../examples/module/lora_sx1262/cores3_lora_sx1262_tx_example.py + .. literalinclude:: ../../../examples/module/lora868_v12/cores3_lora868_v12_tx_example.py :language: python :linenos: @@ -74,7 +73,7 @@ This example receives and displays data. MicroPython Code Block: - .. literalinclude:: ../../../examples/module/lora_sx1262/cores3_lora_sx1262_rx_example.py + .. literalinclude:: ../../../examples/module/lora868_v12/cores3_lora868_v12_rx_example.py :language: python :linenos: @@ -86,10 +85,10 @@ Example output: -------------------------- -class LoRaSx1262Module +class LoRa868V12Module ^^^^^^^^^^^^^^^^^^^^^^^^ -.. class:: module.lora_sx1262.LoRaSx1262Module(pin_rst = 5, \ +.. class:: module.lora868_v12.LoRa868V12Module(pin_rst = 5, \ pin_cs = 1, \ pin_irq = 10, \ pin_busy = 2, \ @@ -101,9 +100,8 @@ class LoRaSx1262Module syncword = 0x12, \ output_power = 10) - Create an LoRaSx1262Module object. + Create an LoRa868V12Module object. - :param int timer_id: The Timer ID. Range: 0~3. Default is 0. :param int pin_rst: (RST) Reset pin number. :param int pin_cs: (NSS) Chip select pin number. :param int pin_irq: (IRQ) Interrupt pin number. @@ -135,9 +133,123 @@ class LoRaSx1262Module .. code-block:: python - from module import LoRaSx1262Module + from module import LoRa868V12Module + + module_lora868v12_0 = LoRa868V12Module(5, 1, 10, 2, 868000, '250', 8, 8, 12, 0x12, 10) + + .. method:: set_freq(freq_khz) + + Set frequency in kHz. + + :param int freq_khz: Frequency in kHz (850000 ~ 930000), default is 868000. + + UiFlow2 Code Block: + + |set_freq.png| + + MicroPython Code Block: + + .. code-block:: python + + module_lora868v12_0.set_freq(freq_khz) + + .. method:: set_sf(sf) + + Set spreading factor (SF). + + :param int sf: Spreading factor (7 ~ 12) + + UiFlow2 Code Block: + + |set_sf.png| + + MicroPython Code Block: + + .. code-block:: python + + module_lora868v12_0.set_sf(sf) + + .. method:: set_bw(bw) + + Set bandwidth. + + :param str bw: Bandwidth in kHz as string. Must be one of: + '7.8', '10.4', '15.6', '20.8', '31.25', '41.7', + '62.5', '125', '250', '500'. + + UiFlow2 Code Block: + + |set_bw.png| + + MicroPython Code Block: + + .. code-block:: python + + module_lora868v12_0.set_bw(bw) + + .. method:: set_coding_rate(coding_rate) + + Set coding rate. + + :param int coding_rate: Coding rate (5 ~ 8) + + UiFlow2 Code Block: + + |set_coding_rate.png| + + MicroPython Code Block: + + .. code-block:: python + + module_lora868v12_0.set_coding_rate(coding_rate) + + .. method:: set_syncword(syncword) + + Set syncword. + + :param int syncword: Sync word (0 ~ 0xFF) + + UiFlow2 Code Block: + + |set_syncword.png| + + MicroPython Code Block: + + .. code-block:: python + + module_lora868v12_0.set_syncword(syncword) + + .. method:: set_preamble_len(preamble_len) + + Set preamble length. + + :param int preamble_len: Preamble length, range: 0~255. + + UiFlow2 Code Block: + + |set_preamble_len.png| + + MicroPython Code Block: + + .. code-block:: python + + module_lora868v12_0.set_preamble_len(preamble_len) + + .. method:: set_output_power(output_power) + + Set output power in dBm. + + :param int output_power: Output power in dBm (-9 ~ 22) + + UiFlow2 Code Block: + + |set_output_power.png| + + MicroPython Code Block: + + .. code-block:: python - lora868v12_0 = LoRaSx1262Module(5, 1, 10, 2, 868000, '250', 8, 8, 12, 0x12, 10) + module_lora868v12_0.set_output_power(output_power) .. method:: set_irq_callback(callback) @@ -156,7 +268,7 @@ class LoRaSx1262Module .. code-block:: python - lora868v12_0.set_irq_callback() + module_lora868v12_0.set_irq_callback() .. method:: start_recv() @@ -172,7 +284,7 @@ class LoRaSx1262Module .. code-block:: python - lora868v12_0.start_recv() + module_lora868v12_0.start_recv() .. method:: recv(self, timeout_ms, rx_length, rx_packet) @@ -194,7 +306,7 @@ class LoRaSx1262Module .. code-block:: python - data = lora868v12_0.recv() + data = module_lora868v12_0.recv() .. method:: send(buf, tx_at_ms=None) @@ -215,7 +327,7 @@ class LoRaSx1262Module .. code-block:: python - lora868v12_0.send() + module_lora868v12_0.send() .. method:: standby() @@ -231,7 +343,7 @@ class LoRaSx1262Module .. code-block:: python - lora868v12_0.standby() + module_lora868v12_0.standby() .. method:: sleep() @@ -247,7 +359,7 @@ class LoRaSx1262Module .. code-block:: python - lora868v12_0.sleep() + module_lora868v12_0.sleep() .. method:: irq_triggered() @@ -264,7 +376,7 @@ class LoRaSx1262Module .. code-block:: python - lora868v12_0.irq_triggered() + module_lora868v12_0.irq_triggered() class RxPacket ^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/docs/en/refs/module.lora_sx1262.ref b/docs/en/refs/module.lora868_v12.ref similarity index 61% rename from docs/en/refs/module.lora_sx1262.ref rename to docs/en/refs/module.lora868_v12.ref index e608e00e..e66c4450 100644 --- a/docs/en/refs/module.lora_sx1262.ref +++ b/docs/en/refs/module.lora868_v12.ref @@ -6,6 +6,13 @@ .. |init.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/lora868_v12/init.png +.. |set_freq.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/lora868_v12/set_freq.png +.. |set_sf.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/lora868_v12/set_sf.png +.. |set_bw.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/lora868_v12/set_bw.png +.. |set_coding_rate.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/lora868_v12/set_coding_rate.png +.. |set_syncword.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/lora868_v12/set_syncword.png +.. |set_preamble_len.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/lora868_v12/set_preamble_len.png +.. |set_output_power.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/lora868_v12/set_output_power.png .. |start_recv.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/lora868_v12/start_recv.png .. |recv.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/lora868_v12/recv.png .. |recv_data_param.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/lora868_v12/recv_data_param.png @@ -17,24 +24,23 @@ .. |standby.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/lora868_v12/standby.png .. |sleep.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/lora868_v12/sleep.png -.. |cores3_lora_sx1262_rx_example.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/lora868_v12/rx_example.png -.. |cores3_lora_sx1262_tx_example.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/lora868_v12/tx_example.png +.. |cores3_lora868_v12_rx_example.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/lora868_v12/rx_example.png +.. |cores3_lora868_v12_tx_example.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/lora868_v12/tx_example.png - -.. |cores3_lora_sx1262_rx_example.m5f2| raw:: html +.. |cores3_lora868_v12_rx_example.m5f2| raw:: html - cores3_lora_sx1262_rx_example.m5f2 + cores3_lora868_v12_rx_example.m5f2 -.. |cores3_lora_sx1262_tx_example.m5f2| raw:: html +.. |cores3_lora868_v12_tx_example.m5f2| raw:: html - cores3_lora_sx1262_tx_example.m5f2 + cores3_lora868_v12_tx_example.m5f2 diff --git a/docs/locales/zh_CN/LC_MESSAGES/module/lora868_v12.po b/docs/locales/zh_CN/LC_MESSAGES/module/lora868_v12.po new file mode 100644 index 00000000..d1117277 --- /dev/null +++ b/docs/locales/zh_CN/LC_MESSAGES/module/lora868_v12.po @@ -0,0 +1,571 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2016 - 2025 M5Stack Technology Co., Ltd +# This file is distributed under the same license as the UIFlow2 Programming +# Guide package. +# FIRST AUTHOR , 2025. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: UIFlow2 Programming Guide \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-05-11 10:24+0800\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: zh_CN\n" +"Language-Team: zh_CN \n" +"Plural-Forms: nplurals=1; plural=0;\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.16.0\n" + +#: ../../en/module/lora868_v12.rst:2 0c3c795297e0421296544a8ee042c987 +msgid "LoRa868 v1.2 Module" +msgstr "" + +#: ../../en/module/lora868_v12.rst:8 41652a9f483c4fe291385d9806aef58a +msgid "" +"The LoRa868 v1.2 Module is part of the M5Stack stackable module series. " +"It is a LoRa communication module that operates at a 900MHz frequency " +"band and utilizes the SX1262 chip solution." +msgstr "LoRa868 v1.2 模块是 M5Stack 可堆叠模块系列的一部分,它是一款工作在 900MHz 频段、基于 SX1262 芯片方案的 LoRa 通信模块。" + +#: ../../en/module/lora868_v12.rst:10 27d6f06b31ef468f8e79d6bb4e2875e4 +msgid "Support the following products:" +msgstr "支持以下产品:" + +#: ../../en/module/lora868_v12.rst:12 1f6c36f6efeb4120ab684d3ba872d472 +msgid "|LoRa868Module v1.2|" +msgstr "" + +#: ../../en/refs/module.lora868_v12.ref 3419ffff300e489c8fdf376c97738745 +msgid "LoRa868Module v1.2" +msgstr "" + +#: ../../en/module/lora868_v12.rst:15 b3d316eb8e054a608151c0416d4181ae +msgid "UiFlow2 Example" +msgstr "UiFlow2 应用示例" + +#: ../../en/module/lora868_v12.rst:17 ../../en/module/lora868_v12.rst:52 +#: 62be3459fdf946e98f754f500fd114aa a9be88fb9ebb46f1b5db584ac9d243ce +msgid "" +"Before using the following examples, please check the DIP switches on the" +" module to ensure that the pins used in the example match the DIP switch " +"positions. For specific configurations, please refer to the product " +"manual page. The SPI configuration has been implemented internally, so " +"users do not need to worry about it." +msgstr "在使用以下示例之前,请先检查模块上的 DIP 开关,确保示例中使用的引脚与 DIP 开关的位置一致。有关具体配置,请参考产品说明页面。SPI 配置已在内部实现,用户无需额外设置。" + +#: ../../en/module/lora868_v12.rst:20 ../../en/module/lora868_v12.rst:55 +#: 55de530dbdf943e6a507c620a627a2cd ca010698f05447d4a07b74db2b55935e +msgid "Sender" +msgstr "发送端" + +#: ../../en/module/lora868_v12.rst:22 78cbc7b2ee784f1592530aa40b43a7bd +msgid "Open the |cores3_lora868_v12_tx_example.m5f2| project in UiFlow2." +msgstr "在 UiFlow2 上打开 |cores3_lora868_v12_tx_example.m5f2| 项目。" + +#: ../../en/module/lora868_v12.rst:24 ../../en/module/lora868_v12.rst:57 +#: 69ed9a9974c64a07b5daa429c2d5e9b3 ddf32627480e48299ebcefb521c49af2 +msgid "This example sends data every second." +msgstr "该示例每秒发送一次数据。" + +#: ../../en/module/lora868_v12.rst:26 ../../en/module/lora868_v12.rst:41 +#: ../../en/module/lora868_v12.rst:128 ../../en/module/lora868_v12.rst:146 +#: ../../en/module/lora868_v12.rst:162 ../../en/module/lora868_v12.rst:180 +#: ../../en/module/lora868_v12.rst:196 ../../en/module/lora868_v12.rst:212 +#: ../../en/module/lora868_v12.rst:228 ../../en/module/lora868_v12.rst:244 +#: ../../en/module/lora868_v12.rst:263 ../../en/module/lora868_v12.rst:279 +#: ../../en/module/lora868_v12.rst:301 ../../en/module/lora868_v12.rst:322 +#: ../../en/module/lora868_v12.rst:338 ../../en/module/lora868_v12.rst:354 +#: ../../en/module/lora868_v12.rst:371 121a9dcbfef64fcdb5e2a57c31b19aac +#: d195d07c0e2c4de7aaba23e9db0a3b45 +msgid "UiFlow2 Code Block:" +msgstr "UiFlow2 代码块:" + +#: ../../en/module/lora868_v12.rst:28 c8ec5952578a4ae5bab2cee492ecb054 +msgid "|cores3_lora868_v12_tx_example.png|" +msgstr "" + +#: ../../en/refs/module.lora868_v12.ref:28 fe1b2af9b2b54e998a5147aa4b799ba2 +msgid "cores3_lora868_v12_tx_example.png" +msgstr "" + +#: ../../en/module/lora868_v12.rst:30 ../../en/module/lora868_v12.rst:45 +#: ../../en/module/lora868_v12.rst:65 ../../en/module/lora868_v12.rst:80 +#: 82ce40da0fe24aeaa4bca6920ea7a4f7 +msgid "Example output:" +msgstr "示例输出:" + +#: ../../en/module/lora868_v12.rst:32 ../../en/module/lora868_v12.rst:47 +#: ../../en/module/lora868_v12.rst:67 ../../en/module/lora868_v12.rst:82 +#: a48158e01ea34e42a860b1ca88d4e3f9 +msgid "None" +msgstr "无" + +#: ../../en/module/lora868_v12.rst:35 ../../en/module/lora868_v12.rst:70 +#: 58f7f2bf09824e10aa3f01b65cb04260 +msgid "Receiver" +msgstr "接收端" + +#: ../../en/module/lora868_v12.rst:37 8cc3b80843a241c9b1eb477705edf566 +msgid "Open the |cores3_lora868_v12_rx_example.m5f2| project in UiFlow2." +msgstr "在 UiFlow2 上打开 |cores3_lora868_v12_rx_example.m5f2| 项目。" + +#: ../../en/module/lora868_v12.rst:39 ../../en/module/lora868_v12.rst:72 +#: 34c28957e6f84475a241ea2c27b42b4a +msgid "This example receives and displays data." +msgstr "示例接收并显示数据。" + +#: ../../en/module/lora868_v12.rst:43 50662ae564d24e7dbcd49a5dc562741b +msgid "|cores3_lora868_v12_rx_example.png|" +msgstr "" + +#: ../../en/refs/module.lora868_v12.ref:27 fe1b2af9b2b54e998a5147aa4b799ba2 +msgid "cores3_lora868_v12_rx_example.png" +msgstr "" + +#: ../../en/module/lora868_v12.rst:50 2b9f1815722c4ed5968e9b60519cb5f6 +msgid "MicroPython Example" +msgstr "MicroPython 应用示例" + +#: ../../en/module/lora868_v12.rst:59 ../../en/module/lora868_v12.rst:74 +#: ../../en/module/lora868_v12.rst:132 ../../en/module/lora868_v12.rst:150 +#: ../../en/module/lora868_v12.rst:166 ../../en/module/lora868_v12.rst:184 +#: ../../en/module/lora868_v12.rst:200 ../../en/module/lora868_v12.rst:216 +#: ../../en/module/lora868_v12.rst:232 ../../en/module/lora868_v12.rst:248 +#: ../../en/module/lora868_v12.rst:267 ../../en/module/lora868_v12.rst:283 +#: ../../en/module/lora868_v12.rst:305 ../../en/module/lora868_v12.rst:326 +#: ../../en/module/lora868_v12.rst:342 ../../en/module/lora868_v12.rst:358 +#: ../../en/module/lora868_v12.rst:375 680377c5aff64533a09f1f3ffe4b5a9a +msgid "MicroPython Code Block:" +msgstr "MicroPython 代码块:" + +#: ../../en/module/lora868_v12.rst:85 1fe5d8c4a145436f9ba86ca6bf32c29f +msgid "**API**" +msgstr "API应用" + +#: ../../en/module/lora868_v12.rst:89 3dfbe1c1902640fa947525f9cedd47f7 +msgid "class LoRa868V12Module" +msgstr "" + +#: ../../en/module/lora868_v12.rst:103 57598055ee12457caf8e467bbb9a0c98 +msgid "Create an LoRa868V12Module object." +msgstr "创建一个 LoRa868V12Module 对象" + +#: ../../en/module/lora868_v12.rst 053f33aca7a84dc89ce71b943558744e +msgid "Parameters" +msgstr "" + +#: ../../en/module/lora868_v12.rst:105 dbef3f20b9f44449a2c4a999292eb195 +msgid "(RST) Reset pin number." +msgstr "复位引脚" + +#: ../../en/module/lora868_v12.rst:106 d151b1f7bde34e82b0cf7936759a3c4d +msgid "(NSS) Chip select pin number." +msgstr "片选引脚" + +#: ../../en/module/lora868_v12.rst:107 3e1f7680c357414b9c3cdf6aa8fdd959 +msgid "(IRQ) Interrupt pin number." +msgstr "中断引脚" + +#: ../../en/module/lora868_v12.rst:108 66038803aef940db9c7c0cc52c48377f +msgid "(BUSY) Busy pin number." +msgstr "忙信号输出引脚" + +#: ../../en/module/lora868_v12.rst:109 44276cce312f4cd0a3a03582bebcd010 +msgid "LoRa RF frequency in KHz, with a range of 850000 KHz to 930000 KHz." +msgstr "LoRa 通信频率,单位为 KHz,范围为 850000 KHz 到 930000 KHz。" + +#: ../../en/module/lora868_v12.rst:110 708ac07a961d46ccb926a11bb486234d +msgid "" +"Bandwidth, options include: - ``\"7.8\"``: 7.8 KHz - ``\"10.4\"``: 10.4 " +"KHz - ``\"15.6\"``: 15.6 KHz - ``\"20.8\"``: 20.8 KHz - ``\"31.25\"``: " +"31.25 KHz - ``\"41.7\"``: 41.7 KHz - ``\"62.5\"``: 62.5 KHz - " +"``\"125\"``: 125 KHz - ``\"250\"``: 250 KHz - ``\"500\"``: 500 KHz" +msgstr "带宽,可选项:- ``\"7.8\"``: 7.8 KHz - ``\"10.4\"``: 10.4 " +"KHz - ``\"15.6\"``: 15.6 KHz - ``\"20.8\"``: 20.8 KHz - ``\"31.25\"``: " +"31.25 KHz - ``\"41.7\"``: 41.7 KHz - ``\"62.5\"``: 62.5 KHz - " +"``\"125\"``: 125 KHz - ``\"250\"``: 250 KHz - ``\"500\"``: 500 KHz" + +#: ../../en/module/lora868_v12.rst:110 52564773ba1544fb9ae00af194e5cb88 +msgid "Bandwidth, options include:" +msgstr "带宽,可选项:" + +#: ../../en/module/lora868_v12.rst:112 d48a375e5d314e9aae43a155aacd402c +msgid "``\"7.8\"``: 7.8 KHz" +msgstr "" + +#: ../../en/module/lora868_v12.rst:113 9983b31c433c4895b6944228e14efd7a +msgid "``\"10.4\"``: 10.4 KHz" +msgstr "" + +#: ../../en/module/lora868_v12.rst:114 ec5c08323b144d5cbf2e028acbe0bacd +msgid "``\"15.6\"``: 15.6 KHz" +msgstr "" + +#: ../../en/module/lora868_v12.rst:115 a7214b8901e3413cbf96190dc1af51af +msgid "``\"20.8\"``: 20.8 KHz" +msgstr "" + +#: ../../en/module/lora868_v12.rst:116 1375eba23af9416b989e1ad8b15222a6 +msgid "``\"31.25\"``: 31.25 KHz" +msgstr "" + +#: ../../en/module/lora868_v12.rst:117 c1962e37492d41d7a2bb0f480b57ac83 +msgid "``\"41.7\"``: 41.7 KHz" +msgstr "" + +#: ../../en/module/lora868_v12.rst:118 c8d20b10f2fc46af9d78751d63e003f0 +msgid "``\"62.5\"``: 62.5 KHz" +msgstr "" + +#: ../../en/module/lora868_v12.rst:119 f414ad3596a347aeb636d9ffdbd32568 +msgid "``\"125\"``: 125 KHz" +msgstr "" + +#: ../../en/module/lora868_v12.rst:120 5e73a4006ee4408bafbf297c2a2990d9 +msgid "``\"250\"``: 250 KHz" +msgstr "" + +#: ../../en/module/lora868_v12.rst:121 db464107a7aa4ff9a880a8e8824257ea +msgid "``\"500\"``: 500 KHz" +msgstr "" + +#: ../../en/module/lora868_v12.rst:122 25d4fa5c14304b50b25208632543ad2e +msgid "" +"Spreading factor, range from 7 to 12. Higher spreading factors allow " +"reception of weaker signals but with slower data rates." +msgstr "扩频因子,范围为 7 到 12。较高的扩频因子可以接收较弱的信号,但数据传输速率会变慢。" + +#: ../../en/module/lora868_v12.rst:123 a2661b1f6f274cc9a9c6387ff5be59a8 +msgid "" +"Forward Error Correction (FEC) coding rate expressed as 4/N, with a range" +" from 5 to 8." +msgstr "前向纠错(FEC)编码率表示为 4/N,范围为 5 到 8。" + +#: ../../en/module/lora868_v12.rst:124 c558993656254985b21436c19c83f18d +msgid "Length of the preamble sequence in symbols, range from 0 to 255." +msgstr "前导码序列的长度(以符号为单位),范围为 0 到 255。" + +#: ../../en/module/lora868_v12.rst:125 0a8a32621107489fa955232abac46cdc +msgid "Sync word to mark the start of the data frame, default is 0x12." +msgstr "用于标记数据帧起始的同步字,默认值为 0x12。" + +#: ../../en/module/lora868_v12.rst:126 27040e7ac26a45e396816e1af0928b9e +msgid "Output power in dBm, range from -9 to 22." +msgstr "输出功率(以 dBm 为单位),范围为 -9 到 22。" + +#: ../../en/module/lora868_v12.rst:130 3808ebead3474ff5950c5252b9c32076 +msgid "|init.png|" +msgstr "" + +#: ../../en/refs/module.lora868_v12.ref:8 cfc2f4ea1e994a428d920a9263d4f3b8 +msgid "init.png" +msgstr "" + +#: ../../en/module/lora868_v12.rst:142 8d6d48dc1a464d7ab52fc1182a331723 +msgid "Set frequency in kHz." +msgstr "设置频率(单位:kHz)" + +#: ../../en/module/lora868_v12.rst:144 e9766219d2864e2eac3b3718a40d4666 +msgid "Frequency in kHz (850000 ~ 930000), default is 868000." +msgstr "频率(单位:kHz),默认值 868000 (kHz)。" + +#: ../../en/module/lora868_v12.rst:148 714d482273a444eab726723e5dc2e56a +msgid "|set_freq.png|" +msgstr "" + +#: ../../en/refs/module.lora868_v12.ref:9 823c0662378146288afd4e96661a1832 +msgid "set_freq.png" +msgstr "" + +#: ../../en/module/lora868_v12.rst:158 f60f61e53c53443bab04a729b069f4a5 +msgid "Set spreading factor (SF)." +msgstr "设置扩频因子。" + +#: ../../en/module/lora868_v12.rst:160 41976a2192e64cc8ae653b67a65f3d05 +msgid "Spreading factor (7 ~ 12)" +msgstr "扩频因子(7~12)。" + +#: ../../en/module/lora868_v12.rst:164 1d13c334918345868e67fc6dacc27ef9 +msgid "|set_sf.png|" +msgstr "" + +#: ../../en/refs/module.lora868_v12.ref:10 2502100fff58413bbf04f140af7648d1 +msgid "set_sf.png" +msgstr "" + +#: ../../en/module/lora868_v12.rst:174 bbd9ab655d1647a58a647d95eb0ee175 +msgid "Set bandwidth." +msgstr "设置带宽。" + +#: ../../en/module/lora868_v12.rst:176 45e7de14fc1a4265b20f44dd9e3540c7 +msgid "" +"Bandwidth in kHz as string. Must be one of: '7.8', '10.4', '15.6', " +"'20.8', '31.25', '41.7', '62.5', '125', '250', '500'." +msgstr "带宽(以 kHz 表示的字符串)。必须是以下值之一:'7.8'、'10.4'、'15.6'、'20.8'、'31.25'、'41.7'、'62.5'、'125'、'250'、'500'。" + +#: ../../en/module/lora868_v12.rst:182 4ed642ffc1924d7c86e9e2173eb8a6dc +msgid "|set_bw.png|" +msgstr "" + +#: ../../en/refs/module.lora868_v12.ref:11 93a7f4ef8c8a41059afd5185ffc0081a +msgid "set_bw.png" +msgstr "" + +#: ../../en/module/lora868_v12.rst:192 d9f04d81afad4050b514de906961fb51 +msgid "Set coding rate." +msgstr "设置编码率。" + +#: ../../en/module/lora868_v12.rst:194 0d83ed5be4b34a88b948aa613e683a07 +msgid "Coding rate (5 ~ 8)" +msgstr "编码率 (5 ~ 8)。" + +#: ../../en/module/lora868_v12.rst:198 675847d2182547298b76547bc761772a +msgid "|set_coding_rate.png|" +msgstr "" + +#: ../../en/refs/module.lora868_v12.ref:12 e204bdaee76f415bba042d485ee31b3c +msgid "set_coding_rate.png" +msgstr "" + +#: ../../en/module/lora868_v12.rst:208 b35b651e5fbe4b4aa7fe0901977e7500 +msgid "Set syncword." +msgstr "设置同步字。" + +#: ../../en/module/lora868_v12.rst:210 b56389dd721a4648904aac9e5a3a3ffa +msgid "Sync word (0 ~ 0xFF)" +msgstr "同步字 (0 ~ 0xFF)" + +#: ../../en/module/lora868_v12.rst:214 86a25b0b78bc4272b035f86c45d4b68c +msgid "|set_syncword.png|" +msgstr "" + +#: ../../en/refs/module.lora868_v12.ref:13 6765318b829848c690810896b4227416 +msgid "set_syncword.png" +msgstr "" + +#: ../../en/module/lora868_v12.rst:224 7964b0717bab434796544b994f1618af +msgid "Set preamble length." +msgstr "设置前导符长度。" + +#: ../../en/module/lora868_v12.rst:226 7a9b4c2a58dc42729d70eb0dacbe3e00 +msgid "Preamble length, range: 0~255." +msgstr "前导符长度,范围:0~255。" + +#: ../../en/module/lora868_v12.rst:230 23c3c7f110814b84bc31b392b505e2c8 +msgid "|set_preamble_len.png|" +msgstr "" + +#: ../../en/refs/module.lora868_v12.ref:14 1b3891f9aa8e436fbe8e81a4675df8d7 +msgid "set_preamble_len.png" +msgstr "" + +#: ../../en/module/lora868_v12.rst:240 14a3c09e823e427f8b048f4756b26909 +msgid "Set output power in dBm." +msgstr "设置输出功率(单位:dBm) 。" + +#: ../../en/module/lora868_v12.rst:242 dc4b0d1a65ec466d9831c92f8a8ff140 +msgid "Output power in dBm (-9 ~ 22)" +msgstr "输出功率(单位:dBm),范围:-9 ~ 22。" + +#: ../../en/module/lora868_v12.rst:246 c08d6cf0d99a4a988707e034e62061b4 +msgid "|set_output_power.png|" +msgstr "" + +#: ../../en/refs/module.lora868_v12.ref:15 d70c51164e7b44f2b75a445ab484198f +msgid "set_output_power.png" +msgstr "" + +#: ../../en/module/lora868_v12.rst:256 7768f6cefcbf44788ebaf4f81e56e4b8 +msgid "Set the interrupt callback function to be executed on IRQ." +msgstr "设置在中断请求(IRQ)发生时执行的中断回调函数。" + +#: ../../en/module/lora868_v12.rst:258 3dcd1ea34d5f432e8fef4c3309464e9a +msgid "" +"The callback function to be invoked when the interrupt is triggered. The " +"callback should not take any arguments and should return nothing." +msgstr "当中断触发时调用的回调函数。该回调函数不应接受任何参数,且不应有返回值。" + +#: ../../en/module/lora868_v12.rst:261 0e2b1d402a1d466f9336738971ab8b99 +msgid "Call `start_recv()` to begin receiving data." +msgstr "调用 `start_recv()` 开始接收数据。" + +#: ../../en/module/lora868_v12.rst:265 b0a6c0f9112841d6b3483fa9d6366a61 +msgid "|set_irq_callback.png|" +msgstr "" + +#: ../../en/refs/module.lora868_v12.ref:19 4de4bd972f954077b8cce69dde510f90 +msgid "set_irq_callback.png" +msgstr "" + +#: ../../en/module/lora868_v12.rst:275 d32a75703b95428b88b7f51206a8374f +msgid "Start receive data." +msgstr "开始接收数据。" + +#: ../../en/module/lora868_v12.rst:277 f589eaff5dcf45e98673c0e050329a78 +msgid "This method initiates the process to begin receiving data." +msgstr "此方法启动接收数据的过程。" + +#: ../../en/module/lora868_v12.rst:281 05fbaa4da2344871826a7c83eb7ab072 +msgid "|start_recv.png|" +msgstr "" + +#: ../../en/refs/module.lora868_v12.ref:16 a58d1c2e1ada4ff4838b20b412d2202e +msgid "start_recv.png" +msgstr "" + +#: ../../en/module/lora868_v12.rst:291 76aa1b13c6ce478cb2a6c130c3eb5849 +msgid "Receive data." +msgstr "接收数据。" + +#: ../../en/module/lora868_v12.rst:293 a89fd1221c7e49108fbda3373b370838 +msgid "Timeout in milliseconds (optional). Default is None." +msgstr "超时时间(以毫秒为单位,可选)。默认为 None。" + +#: ../../en/module/lora868_v12.rst:294 192f9999e13542369680bc407a1f9046 +msgid "Length of the data to be read. Default is 0xFF." +msgstr "读取数据的长度。默认为 0xFF。" + +#: ../../en/module/lora868_v12.rst:295 5697b77e80114cbca5d3fe9600871744 +msgid "An instance of `RxPacket` (optional) to reuse." +msgstr "一个可选的 RxPacket 实例,用于重用。" + +#: ../../en/module/lora868_v12.rst e6fe5c09d9a74d4581df239354bae091 +msgid "Returns" +msgstr "" + +#: ../../en/module/lora868_v12.rst:296 0a96ace5036441cfbd404d130c27d3e4 +msgid "Received packet instance" +msgstr "接收数据包实例。" + +#: ../../en/module/lora868_v12.rst ff0ad5628ed24d3daed293b97ff2495d +msgid "Return type" +msgstr "" + +#: ../../en/module/lora868_v12.rst:299 b3e2af93a81f4823b5afb930140fab0d +msgid "" +"Attempt to receive a LoRa packet. Returns `None` if timeout occurs, or " +"returns the received packet instance." +msgstr "尝试接收一个 LoRa 数据包。如果发生超时,返回 None;否则,返回接收到的数据包实例。" + +#: ../../en/module/lora868_v12.rst:303 1565abbb42b34ddab88a0228b267fca0 +msgid "|recv.png|" +msgstr "" + +#: ../../en/refs/module.lora868_v12.ref:17 51ed030cd63e44989504b4dccc3fd255 +msgid "recv.png" +msgstr "" + +#: ../../en/module/lora868_v12.rst:313 eb874ce851364c2785755cc27d3b68e3 +msgid "Send data." +msgstr "发送数据" + +#: ../../en/module/lora868_v12.rst:315 cb4717d1b2b444b68c5a695f952040ae +msgid "The data to be sent." +msgstr "要发送的数据" + +#: ../../en/module/lora868_v12.rst:316 f7633b036c5d4b6d9fdfeb76b9064ea3 +msgid "" +"The timestamp in milliseconds when to send the data (optional). Default " +"is None." +msgstr "发送数据的时间戳,单位为毫秒(可选)。默认为 None。" + +#: ../../en/module/lora868_v12.rst:317 a10883eadfed427cb25f81808f50ea32 +msgid "" +"Returns a timestamp (result of `time.ticks_ms()`) indicating when the " +"data packet was sent." +msgstr "返回一个时间戳(time.ticks_ms()的结果),表示数据包发送的时间。" + +#: ../../en/module/lora868_v12.rst:320 21cdf2908f22483b9ab7d8a6618dae7e +msgid "Send a data packet and return the timestamp after the packet is sent." +msgstr "发送一个数据包并返回数据包发送后的时间戳。" + +#: ../../en/module/lora868_v12.rst:324 673655cdbc224caeace328d57c1907bf +msgid "|send.png|" +msgstr "" + +#: ../../en/refs/module.lora868_v12.ref:22 5cd645d54fdd4c0ab6b3d1b959b80fb4 +msgid "send.png" +msgstr "" + +#: ../../en/module/lora868_v12.rst:334 034165c7403f4df3b51f96f5fd9a2c11 +msgid "Set module to standby mode." +msgstr "设置模块为待机模式。" + +#: ../../en/module/lora868_v12.rst:336 aa4168f61d7a4b21b130f456172a8c28 +msgid "Puts the LoRa module into standby mode, consuming less power." +msgstr "将 LoRa 模块置于待机模式,从而降低功耗。" + +#: ../../en/module/lora868_v12.rst:340 e4c03c31f7334697962080abd1c0db42 +msgid "|standby.png|" +msgstr "" + +#: ../../en/refs/module.lora868_v12.ref:24 82f4298ed314493ead299c5fe9bc6b51 +msgid "standby.png" +msgstr "" + +#: ../../en/module/lora868_v12.rst:350 1ed728bd5bfb4e9395af32a3c8f872e7 +msgid "Put the module to sleep mode." +msgstr "将 LoRa 模块置于睡眠模式" + +#: ../../en/module/lora868_v12.rst:352 6cdbccaf7120488b85f8b7d21b527e24 +msgid "Reduces the power consumption by putting the module into deep sleep mode." +msgstr "通过将模块置于深度睡眠模式来减少功耗。" + +#: ../../en/module/lora868_v12.rst:356 6700e4dea90649cfa363d6c953a1e0a4 +msgid "|sleep.png|" +msgstr "" + +#: ../../en/refs/module.lora868_v12.ref:25 fe1b2af9b2b54e998a5147aa4b799ba2 +msgid "sleep.png" +msgstr "" + +#: ../../en/module/lora868_v12.rst:366 1b296f3b92b54fd2862094e7d1c75c7e +msgid "Check IRQ trigger." +msgstr "检查 IRQ 触发。" + +#: ../../en/module/lora868_v12.rst:368 e1191f7a4b5d4bdc9dca0ca2546241e6 +msgid "" +"Returns `True` if an interrupt service routine (ISR) has been triggered " +"since the last send or receive started." +msgstr "如果自上次发送或接收开始以来,已经触发了中断服务例程(ISR),则返回 True。" + +#: ../../en/module/lora868_v12.rst:373 fdbbefde49b34ffc8b77b21e192defa9 +msgid "|irq_triggered.png|" +msgstr "" + +#: ../../en/refs/module.lora868_v12.ref:23 97ed07b9c8bd4e97b37a57b562e4fa8b +msgid "irq_triggered.png" +msgstr "" + +#: ../../en/module/lora868_v12.rst:382 e5c8a23151a94f8aac96b7b9a188f0a1 +msgid "class RxPacket" +msgstr "" + +#: ../../en/module/lora868_v12.rst:386 5fb65197a5c4424e85149435845f7ebd +msgid "Create an RxPacket object." +msgstr "创建一个 RxPacket 对象。" + +#: ../../en/module/lora868_v12.rst:390 a659c3f2dd6946f497d28d8f7b096c47 +msgid "Decode the received data." +msgstr "解码接收到的数据。" + +#: ../../en/module/lora868_v12.rst:394 a8939ad9b4d34a16a2c25c1e8b762ee0 +msgid "Timestamp of when the data was received." +msgstr "数据接收的时间戳。" + +#: ../../en/module/lora868_v12.rst:398 de285177f39e407ca89b2e36b5c6e1cf +msgid "Received signal strength (units: dBm)." +msgstr "接收信号强度(单位:dBm)。" + +#: ../../en/module/lora868_v12.rst:402 e1e7c5ac572f4fd2926f36d8b2945748 +msgid "Signal-to-noise ratio (units: dB * 4)." +msgstr "信噪比(单位:dB * 4)。" + +#: ../../en/module/lora868_v12.rst:406 4493f72062b744c1b8721f724f9a9f28 +msgid "CRC validity check." +msgstr "CRC 校验有效性。" + diff --git a/docs/locales/zh_CN/LC_MESSAGES/module/lora_sx1262.po b/docs/locales/zh_CN/LC_MESSAGES/module/lora_sx1262.po deleted file mode 100644 index e661e5aa..00000000 --- a/docs/locales/zh_CN/LC_MESSAGES/module/lora_sx1262.po +++ /dev/null @@ -1,472 +0,0 @@ -# SOME DESCRIPTIVE TITLE. -# Copyright (C) 2016 - 2025 M5Stack Technology Co., Ltd -# This file is distributed under the same license as the UIFlow2 Programming -# Guide package. -# FIRST AUTHOR , 2025. -# -#, fuzzy -msgid "" -msgstr "" -"Project-Id-Version: UIFlow2 Programming Guide \n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-04-03 10:58+0800\n" -"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" -"Last-Translator: FULL NAME \n" -"Language: zh_CN\n" -"Language-Team: zh_CN \n" -"Plural-Forms: nplurals=1; plural=0;\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=utf-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Generated-By: Babel 2.16.0\n" - -#: ../../en/module/lora_sx1262.rst:2 909ab2a439064ba180605c1e7223de89 -msgid "LoRa868 v1.2 Module" -msgstr "" - -#: ../../en/module/lora_sx1262.rst:8 65ebb5aae0ac424e887e01feac13320d -msgid "" -"The LoRa868 v1.2 Module is part of the M5Stack stackable module series. " -"It is a LoRa communication module that operates at a 900MHz frequency and" -" utilizes the SX1262 chip solution." -msgstr "" -"LoRa868 v1.2 模块是 M5Stack 堆叠模块系列的一部分,属于 LoRa 通信模块,工作在 900MHz 频率,采用 SX1262 " -"芯片解决方案。" - -#: ../../en/module/lora_sx1262.rst:10 8f2ff97b70384575b91b08b22a7a8e7e -msgid "Support the following products:" -msgstr "支持以下产品:" - -#: ../../en/module/lora_sx1262.rst:12 102f08a9024e4e46a8d181b54984516f -msgid "|LoRa868Module v1.2|" -msgstr "" - -#: ../../en/refs/module.lora_sx1262.ref e4d85310e30b4c85b75e1e73187fdb7f -msgid "LoRa868Module v1.2" -msgstr "" - -#: ../../en/module/lora_sx1262.rst:16 20c3089da354498e99b522894ed02349 -msgid "UiFlow2 Example" -msgstr "UiFlow2 应用示例:" - -#: ../../en/module/lora_sx1262.rst:18 ../../en/module/lora_sx1262.rst:53 -#: 49a15d310a9649d58bd842d195bc51e8 860982a5fe6f42348e9c18ae22ffb114 -msgid "" -"Before using the following examples, please check the DIP switches on the" -" module to ensure that the pins used in the example match the DIP switch " -"positions. For specific configurations, please refer to the product " -"manual page. The SPI configuration has been implemented internally, so " -"users do not need to worry about it." -msgstr "" -"在使用以下示例之前,请检查模块上的拨码开关,以确保示例中使用的引脚与拨码开关的位置相匹配。有关具体配置,请参考产品手册页面。SPI " -"配置已在内部实现。" - -#: ../../en/module/lora_sx1262.rst:21 ../../en/module/lora_sx1262.rst:56 -#: 25f4529af5f646db8109f7bf58c5e380 318eb348bc2f47fdab1e0a2e21b0c3d7 -msgid "Sender" -msgstr "发送端" - -#: ../../en/module/lora_sx1262.rst:23 0d4786701a5b4af59ba71caf6b7b343c -msgid "Open the |cores3_lora_sx1262_tx_example.m5f2| project in UiFlow2." -msgstr "在 UiFlow2 上打开 |cores3_lora_sx1262_tx_example.m5f2| 项目。" - -#: ../../en/module/lora_sx1262.rst:25 ../../en/module/lora_sx1262.rst:58 -#: 287d2c1f6b0c41e6a6f9d65a7dbeeecd d9a6f758da7e4b0b95de744c83b666be -msgid "This example sends data every second." -msgstr "此示例每秒发送一次数据。" - -#: ../../en/module/lora_sx1262.rst:27 ../../en/module/lora_sx1262.rst:42 -#: ../../en/module/lora_sx1262.rst:130 ../../en/module/lora_sx1262.rst:151 -#: ../../en/module/lora_sx1262.rst:167 ../../en/module/lora_sx1262.rst:189 -#: ../../en/module/lora_sx1262.rst:210 ../../en/module/lora_sx1262.rst:226 -#: ../../en/module/lora_sx1262.rst:242 ../../en/module/lora_sx1262.rst:259 -#: 3d8f430e2718482d9766194b3d7eb5b7 3e3c5dea533a4b27b749a1c9ed5a72f0 -#: 449565b6e23347e08f75fe5fc2f7a536 544e42871cf14b6b810aacc0cd30aaee -#: 6888fc0c73944feb89cc882c2974b814 8ac9d52c20b44c0693886e6d41a0f5e3 -#: c3b1eff99ae04d3f8b3af94b98ab95ed c786196438154e8a9a9428b167a36e15 -#: df0e05658bd04c6598ba316a0edf7acd eff05456e1cc421facb3b74a875595fc -msgid "UiFlow2 Code Block:" -msgstr "UiFlow2 代码块:" - -#: ../../en/module/lora_sx1262.rst:29 f8076474c14a46a9bc135cc5cd180722 -msgid "|cores3_lora_sx1262_tx_example.png|" -msgstr "" - -#: ../../en/refs/module.lora_sx1262.ref:21 4a0dcbd56d9c429790c8ab236c3fe362 -msgid "cores3_lora_sx1262_tx_example.png" -msgstr "" - -#: ../../en/module/lora_sx1262.rst:31 ../../en/module/lora_sx1262.rst:46 -#: ../../en/module/lora_sx1262.rst:66 ../../en/module/lora_sx1262.rst:81 -#: 0948dcfcabbf436298ff39bc6087c31d 1a296bc3de08431f8c0fb5113fc671c6 -#: 5e34dfad13fd4174afb49dd6b18b83bc e94bd28de62e457bad9c76c076de72f4 -msgid "Example output:" -msgstr "示例输出:" - -#: ../../en/module/lora_sx1262.rst:33 ../../en/module/lora_sx1262.rst:48 -#: ../../en/module/lora_sx1262.rst:68 ../../en/module/lora_sx1262.rst:83 -#: 5a66fd453e654282b0d4f12ea9f1a649 9cd45426bec944708adf04da58fedabd -#: abe69fedd95d4cd9bc74a9cc8ab3989f cbb4f56ea60c45e49c437c58aaf9c6ab -msgid "None" -msgstr "无" - -#: ../../en/module/lora_sx1262.rst:36 ../../en/module/lora_sx1262.rst:71 -#: 80f73e368e3f45fb9c71d6a8815f5e80 88a688570291428cbf28157844ab3a3f -msgid "Receiver" -msgstr "接收端" - -#: ../../en/module/lora_sx1262.rst:38 1e84d3cbe71a44069236ba1903bce9e1 -msgid "Open the |cores3_lora_sx1262_rx_example.m5f2| project in UiFlow2." -msgstr "在 UiFlow2 上打开 |cores3_lora_sx1262_rx_example.m5f2| 项目。" - -#: ../../en/module/lora_sx1262.rst:40 ../../en/module/lora_sx1262.rst:73 -#: 4ec17c9b8b774af7a90c9b53e811d78d 790d2371894040d7b3bdc73025419f4b -msgid "This example receives and displays data." -msgstr "此示例接收并显示数据。" - -#: ../../en/module/lora_sx1262.rst:44 4cc9de12bf00404a980a7a113a681969 -msgid "|cores3_lora_sx1262_rx_example.png|" -msgstr "" - -#: ../../en/refs/module.lora_sx1262.ref:20 16aefbc45bab4da2a0e168c240806a3f -msgid "cores3_lora_sx1262_rx_example.png" -msgstr "" - -#: ../../en/module/lora_sx1262.rst:51 3499850b3efc4a08b7035823681f4817 -msgid "MicroPython Example" -msgstr "MicroPython 应用示例:" - -#: ../../en/module/lora_sx1262.rst:60 ../../en/module/lora_sx1262.rst:75 -#: ../../en/module/lora_sx1262.rst:134 ../../en/module/lora_sx1262.rst:155 -#: ../../en/module/lora_sx1262.rst:171 ../../en/module/lora_sx1262.rst:193 -#: ../../en/module/lora_sx1262.rst:214 ../../en/module/lora_sx1262.rst:230 -#: ../../en/module/lora_sx1262.rst:246 ../../en/module/lora_sx1262.rst:263 -#: 17883b171c8b4cb39202ae99a60f5c72 35186c67c2734f5d9bff41ecb5f3fbed -#: 402822ec52264abd8c9d570c1acac4be 678ebde175b4436098db80798eb955e0 -#: a584c318918d4205a361a6464c3f1b5c b7c0567c96734d74a8dca609eede5016 -#: c3c221baaa0c4926a1d2268b8de182b6 c5770a2540bb4a7388f37c8731d318f0 -#: db27a69de1774fdcb4f667c964079e47 f7880fc4ea2d4107af4b1a1309ff62d9 -msgid "MicroPython Code Block:" -msgstr "MicroPython 代码块:" - -#: ../../en/module/lora_sx1262.rst:86 9356f25aba9b41e49aca0078c5a691af -msgid "**API**" -msgstr "API参考" - -#: ../../en/module/lora_sx1262.rst:90 46b51d3b82b94945ae6010e626f8fbab -msgid "class LoRaSx1262Module" -msgstr "" - -#: ../../en/module/lora_sx1262.rst:104 66bf6def07fe452a990eead3fd99db71 -msgid "Create an LoRaSx1262Module object." -msgstr "创建一个 LoRaSx1262Module 对象。" - -#: ../../en/module/lora_sx1262.rst 2ef70a40ba6a4682949f4c8d4c2f9d63 -#: 7fee3bd18a2f48949b01b40f5d7389a9 895fe81d42924c57a584e3d38470d1f9 -#: fcfc2388373a48169dcead2b4f820fa2 -msgid "Parameters" -msgstr "" - -#: ../../en/module/lora_sx1262.rst:106 ef9c70ede4444bffbefb59f69d173b7d -msgid "The Timer ID. Range: 0~3. Default is 0." -msgstr "定时器 ID,范围:0~3,默认为 0。" - -#: ../../en/module/lora_sx1262.rst:107 ddc74b06162d4cd093a50a8bcdb199ed -msgid "(RST) Reset pin number." -msgstr "(RST) 复位引脚号。" - -#: ../../en/module/lora_sx1262.rst:108 4d7dd9cbc6124caf888518656f5d8578 -msgid "(NSS) Chip select pin number." -msgstr "(NSS) 片选引脚号。" - -#: ../../en/module/lora_sx1262.rst:109 3f45bc3bc6d944bb91dccbc64132e044 -msgid "(IRQ) Interrupt pin number." -msgstr "(IRQ) 中断引脚号。" - -#: ../../en/module/lora_sx1262.rst:110 8258663f9b75401a8fd702dc8c33249d -msgid "(BUSY) Busy pin number." -msgstr "(BUSY) 忙检查引脚号。" - -#: ../../en/module/lora_sx1262.rst:111 55528275a37b485da286e32f84a6b905 -msgid "LoRa RF frequency in KHz, with a range of 850000 KHz to 930000 KHz." -msgstr "LoRa 射频通信频率,单位 kHz,范围:850000 KHz ~ 930000 KHz。" - -#: ../../en/module/lora_sx1262.rst:112 ec4539092b5a446cb0815303b99bfde7 -msgid "" -"Bandwidth, options include: - ``\"7.8\"``: 7.8 KHz - ``\"10.4\"``: 10.4 " -"KHz - ``\"15.6\"``: 15.6 KHz - ``\"20.8\"``: 20.8 KHz - ``\"31.25\"``: " -"31.25 KHz - ``\"41.7\"``: 41.7 KHz - ``\"62.5\"``: 62.5 KHz - " -"``\"125\"``: 125 KHz - ``\"250\"``: 250 KHz - ``\"500\"``: 500 KHz" -msgstr "" - -#: ../../en/module/lora_sx1262.rst:112 771ca968cc0a4194931b91aca3091317 -msgid "Bandwidth, options include:" -msgstr "带宽,包括如下:" - -#: ../../en/module/lora_sx1262.rst:114 951a1befa3d143d493396ccadc6bb298 -msgid "``\"7.8\"``: 7.8 KHz" -msgstr "" - -#: ../../en/module/lora_sx1262.rst:115 f039dcbc86f4482fa721b9ee43215168 -msgid "``\"10.4\"``: 10.4 KHz" -msgstr "" - -#: ../../en/module/lora_sx1262.rst:116 c665eb7fa415443fbd98a045aac6cf69 -msgid "``\"15.6\"``: 15.6 KHz" -msgstr "" - -#: ../../en/module/lora_sx1262.rst:117 183ebeaeb203414c9aa4a511980d9a06 -msgid "``\"20.8\"``: 20.8 KHz" -msgstr "" - -#: ../../en/module/lora_sx1262.rst:118 1bb30ade802845d6b6e8e146b6012034 -msgid "``\"31.25\"``: 31.25 KHz" -msgstr "" - -#: ../../en/module/lora_sx1262.rst:119 e29969ee2ff44f27a7640fce4be7e184 -msgid "``\"41.7\"``: 41.7 KHz" -msgstr "" - -#: ../../en/module/lora_sx1262.rst:120 0c25db03f62841ad978b20cc6cd67028 -msgid "``\"62.5\"``: 62.5 KHz" -msgstr "" - -#: ../../en/module/lora_sx1262.rst:121 4ca6f0f82829404195a5ba115648f3ef -msgid "``\"125\"``: 125 KHz" -msgstr "" - -#: ../../en/module/lora_sx1262.rst:122 55f9a62d93c84f808876637565a48a9f -msgid "``\"250\"``: 250 KHz" -msgstr "" - -#: ../../en/module/lora_sx1262.rst:123 17299775c067496a99b66c8c6bc7367f -msgid "``\"500\"``: 500 KHz" -msgstr "" - -#: ../../en/module/lora_sx1262.rst:124 04e14cd97d104f1484750337dbc5fa81 -msgid "" -"Spreading factor, range from 7 to 12. Higher spreading factors allow " -"reception of weaker signals but with slower data rates." -msgstr "扩频因子,范围从 7 到 12。较高的扩展因子可以接收较弱的信号,但数据传输速率较慢。" - -#: ../../en/module/lora_sx1262.rst:125 f02f612f3b47436f997f5c10f62ad629 -msgid "" -"Forward Error Correction (FEC) coding rate expressed as 4/N, with a range" -" from 5 to 8." -msgstr "前向纠错(FEC)编码率以 4/N 的形式表示,范围从 5 到 8。" - -#: ../../en/module/lora_sx1262.rst:126 5f9ee3f81f514890b067941c9c6c313c -msgid "Length of the preamble sequence in symbols, range from 0 to 255." -msgstr "前导符长度,范围 0~255。" - -#: ../../en/module/lora_sx1262.rst:127 bda069dab7b445bc96372487e8718fbd -msgid "Sync word to mark the start of the data frame, default is 0x12." -msgstr "同步字,用于标记数据帧的开始,默认值是 0x12。" - -#: ../../en/module/lora_sx1262.rst:128 d6dda6a7dc4b42368b6f4e307903fed5 -msgid "Output power in dBm, range from -9 to 22." -msgstr "输出功率以 dBm 为单位,范围从 -9 到 22。" - -#: ../../en/module/lora_sx1262.rst:132 4d95734b4ec6495f9576a8bc5e6ab86f -msgid "|init.png|" -msgstr "" - -#: ../../en/refs/module.lora_sx1262.ref:8 3f76cef93d7a430397693b1430a5f8ec -msgid "init.png" -msgstr "" - -#: ../../en/module/lora_sx1262.rst:144 4a9d84909c7240b99e18815870e9cb63 -msgid "Set the interrupt callback function to be executed on IRQ." -msgstr "设置中断回调函数。" - -#: ../../en/module/lora_sx1262.rst:146 8cbb4953b1794a10982e770e72d8fb32 -msgid "" -"The callback function to be invoked when the interrupt is triggered. The " -"callback should not take any arguments and should return nothing." -msgstr "当中断触发时调用的回调函数。回调函数不应接受任何参数,并且不应返回任何值。" - -#: ../../en/module/lora_sx1262.rst:149 14c55fcc8b104975a6b1537573226fcd -msgid "Call `start_recv()` to begin receiving data." -msgstr "调用 `start_recv()` 开始接收数据。" - -#: ../../en/module/lora_sx1262.rst:153 2510464b9ce844a1acf0ce914f3db6ca -msgid "|set_irq_callback.png|" -msgstr "" - -#: ../../en/refs/module.lora_sx1262.ref:12 bfd193b2c7424c3c93229f600068c574 -msgid "set_irq_callback.png" -msgstr "" - -#: ../../en/module/lora_sx1262.rst:163 f8d70fce93084bd5982bcaf3f948e76f -msgid "Start receive data." -msgstr "开始接收数据。" - -#: ../../en/module/lora_sx1262.rst:165 2a8eb07d42e04b5ab07942c298374146 -msgid "This method initiates the process to begin receiving data." -msgstr "该方法启动接收数据。" - -#: ../../en/module/lora_sx1262.rst:169 746dcce8bd2f4ad3a07f555e95899214 -msgid "|start_recv.png|" -msgstr "" - -#: ../../en/refs/module.lora_sx1262.ref:9 7236859b1d644950968fa84820744fe7 -msgid "start_recv.png" -msgstr "" - -#: ../../en/module/lora_sx1262.rst:179 087cceefd5b54e92847d6d8f5227cb7e -msgid "Receive data." -msgstr "接收数据。" - -#: ../../en/module/lora_sx1262.rst:181 2e173c85673243cd9ca78943d59ee87a -msgid "Timeout in milliseconds (optional). Default is None." -msgstr "超时时间(单位:ms,可选),默认为 None。" - -#: ../../en/module/lora_sx1262.rst:182 108618e127ce4877abdc5eeaaee6e2ba -msgid "Length of the data to be read. Default is 0xFF." -msgstr "要读取的数据长度,默认为 0xFF。" - -#: ../../en/module/lora_sx1262.rst:183 5835db152cf940b8bdafef8ebd095866 -msgid "An instance of `RxPacket` (optional) to reuse." -msgstr "一个 RxPacket 实例(可选参数),用于重用。" - -#: ../../en/module/lora_sx1262.rst 2f082ea9d73342bf9d01bc649cced612 -#: 3671e37a4fb840abb930b3a0f575e680 fab78bfa39304c9089a1c96b35b1f26a -msgid "Returns" -msgstr "" - -#: ../../en/module/lora_sx1262.rst:184 ffd83e5ad2314ba985f711d00c95466f -msgid "Received packet instance" -msgstr "接收数据实例。" - -#: ../../en/module/lora_sx1262.rst 996640002eb249c7849340c66a0897c0 -#: a60a39f6f62c4a2fb683de6b88932585 ccd2c0660f0a4f1e973c9f67c6682ac6 -msgid "Return type" -msgstr "" - -#: ../../en/module/lora_sx1262.rst:187 75445498032645bbbc2ae5acfe34617a -msgid "" -"Attempt to receive a LoRa packet. Returns `None` if timeout occurs, or " -"returns the received packet instance." -msgstr "尝试接收一个 LoRa 数据包。如果发生超时,则返回 None,否则返回接收到的数据包实例。" - -#: ../../en/module/lora_sx1262.rst:191 66e9953d9c4b49e7aba505495b2bd74b -msgid "|recv.png|" -msgstr "" - -#: ../../en/refs/module.lora_sx1262.ref:10 cdc5a492038c49f5aa65096d457a30bb -msgid "recv.png" -msgstr "" - -#: ../../en/module/lora_sx1262.rst:201 a87eb389cb8b415a850cb36d36d97124 -msgid "Send data." -msgstr "发送数据。" - -#: ../../en/module/lora_sx1262.rst:203 611f0120876d48dfbf8663ce391f83ed -msgid "The data to be sent." -msgstr "要发送的数据。" - -#: ../../en/module/lora_sx1262.rst:204 7625691270a6477589142282c1746209 -msgid "" -"The timestamp in milliseconds when to send the data (optional). Default " -"is None." -msgstr "发送数据的时间戳(以 ms 为单位,可选参数)。默认为 None。" - -#: ../../en/module/lora_sx1262.rst:205 6b1a15b2822c495ebf3f8663514213b8 -msgid "" -"Returns a timestamp (result of `time.ticks_ms()`) indicating when the " -"data packet was sent." -msgstr "返回一个时间戳(time.ticks_ms() 的结果),表示数据包发送的时间。" - -#: ../../en/module/lora_sx1262.rst:208 880f2a8cc6a046a5ab758cd530f3d1c8 -msgid "Send a data packet and return the timestamp after the packet is sent." -msgstr "发送一个数据包并返回数据包发送后的时间戳。" - -#: ../../en/module/lora_sx1262.rst:212 164bd9529d2d4376bfaaf4dbccfa5ea8 -msgid "|send.png|" -msgstr "" - -#: ../../en/refs/module.lora_sx1262.ref:15 f5fd52c7cdbf43c48d993591419027ee -msgid "send.png" -msgstr "" - -#: ../../en/module/lora_sx1262.rst:222 320259e97c8e4af1b0e7be18366e0d79 -msgid "Set module to standby mode." -msgstr "设置模块为待机模式。" - -#: ../../en/module/lora_sx1262.rst:224 ee61bdde37cb487d9760750402645704 -msgid "Puts the LoRa module into standby mode, consuming less power." -msgstr "设置 LoRa 模块为待机模式,降低功耗。" - -#: ../../en/module/lora_sx1262.rst:228 658ba4b4c3864f008296504d952c023a -msgid "|standby.png|" -msgstr "" - -#: ../../en/refs/module.lora_sx1262.ref:17 24857b73396b45a493aa5625014eba33 -msgid "standby.png" -msgstr "" - -#: ../../en/module/lora_sx1262.rst:238 9fccc302890e478c9382c94c920bead2 -msgid "Put the module to sleep mode." -msgstr "设置模块为休眠模式。" - -#: ../../en/module/lora_sx1262.rst:240 4cdeba7c430b45d08605ddf20cea8009 -msgid "Reduces the power consumption by putting the module into deep sleep mode." -msgstr "通过将模块置于休眠模式来降低功耗。" - -#: ../../en/module/lora_sx1262.rst:244 4c51d457314f41c09d16ff3a4050f23a -msgid "|sleep.png|" -msgstr "" - -#: ../../en/refs/module.lora_sx1262.ref:18 5b189b5a0aab4db487d193d7b83cba24 -msgid "sleep.png" -msgstr "" - -#: ../../en/module/lora_sx1262.rst:254 b71c7198ef074f2da3b81c7446e756ed -msgid "Check IRQ trigger." -msgstr "检查 IRQ 是否触发。" - -#: ../../en/module/lora_sx1262.rst:256 67e86b8b2c76433589535f189b48635c -msgid "" -"Returns `True` if an interrupt service routine (ISR) has been triggered " -"since the last send or receive started." -msgstr "如果自上次发送或接收开始以来中断服务例程(ISR)已被触发,则返回 True。" - -#: ../../en/module/lora_sx1262.rst:261 0a2d1720820049db963a1bcfbf751462 -msgid "|irq_triggered.png|" -msgstr "" - -#: ../../en/refs/module.lora_sx1262.ref:16 5447b337e0e04df8bb03639b7dd9c1f6 -msgid "irq_triggered.png" -msgstr "" - -#: ../../en/module/lora_sx1262.rst:270 e862112210f240629c3da7254ffc12de -msgid "class RxPacket" -msgstr "" - -#: ../../en/module/lora_sx1262.rst:274 3da775ff990241f881dff2f4b136f85c -msgid "Create an RxPacket object." -msgstr "创建一个 RxPacket 对象。" - -#: ../../en/module/lora_sx1262.rst:278 da50f30d65f7412f84c832ecee629e4e -msgid "Decode the received data." -msgstr "解码接收到的数据。" - -#: ../../en/module/lora_sx1262.rst:282 fa7755b2ee9e46bd9f5b13bf9d74f9bc -msgid "Timestamp of when the data was received." -msgstr "数据接收时的时间戳。" - -#: ../../en/module/lora_sx1262.rst:286 6c084d10a6914703abf0072d0a11e57f -msgid "Received signal strength (units: dBm)." -msgstr "接收信号强度(单位:dBm)。" - -#: ../../en/module/lora_sx1262.rst:290 70986fea0a744403a5cf1dea17948f49 -msgid "Signal-to-noise ratio (units: dB * 4)." -msgstr "信噪比(单位:dB * 4)。" - -#: ../../en/module/lora_sx1262.rst:294 51abcfa2a78c4d7f9f310d08f6af4c7f -msgid "CRC validity check." -msgstr "CRC 校验。" - -#~ msgid "|Module LoRa868 v1.2|" -#~ msgstr "" - diff --git a/examples/module/lora868_v12/cores3_lora868_v12_rx_example.m5f2 b/examples/module/lora868_v12/cores3_lora868_v12_rx_example.m5f2 new file mode 100644 index 00000000..cd44857f --- /dev/null +++ b/examples/module/lora868_v12/cores3_lora868_v12_rx_example.m5f2 @@ -0,0 +1 @@ +{"version":"V2.0","versionNumber":"V2.2.6","type":"cores3","components":[{"name":"screen","type":"screen","layer":0,"screenId":"builtin","screenName":"","id":"__cores3_screen","createTime":1737449694211,"x":0,"y":0,"width":320,"height":240,"backgroundColor":"#222222","size":0,"isSelected":true},{"name":"title0","type":"title","layer":1,"screenId":"builtin","screenName":"","id":"fZghG4ekSYQ$iOsz","createTime":1737449765298,"x":0,"y":0,"color":"#ffffff","backgroundColor":"#0000FF","text":"LoRa Module Rx","textOffset":3,"font":"Widgets.FONTS.DejaVu18","isSelected":false},{"name":"label_r","type":"label","layer":1,"screenId":"builtin","screenName":"","id":"yq=Jeun8%_&%6USl","createTime":1737528840028,"x":5,"y":50,"color":"#ffffff","backgroundColor":"#222222","text":"Recv:","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false},{"name":"label_rx","type":"label","layer":2,"screenId":"builtin","screenName":"","id":"xWyO=oScbJw*P4bh","createTime":1737528841197,"x":65,"y":50,"color":"#ffffff","backgroundColor":"#222222","text":" ","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false},{"name":"label_t","type":"label","layer":3,"screenId":"builtin","screenName":"","id":"m!MG6ke43xUSzPkp","createTime":1737528842542,"x":5,"y":150,"color":"#ffffff","backgroundColor":"#222222","text":"timestamp:","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false},{"name":"label_time","type":"label","layer":4,"screenId":"builtin","screenName":"","id":"wo`z#Aq_hi4v5nlY","createTime":1737528843789,"x":118,"y":150,"color":"#ffffff","backgroundColor":"#222222","text":"1","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false},{"name":"label_rssi","type":"label","layer":5,"screenId":"builtin","screenName":"","id":"gZ3bUs6%k`K%mrB-","createTime":1737529534968,"x":5,"y":80,"color":"#ffffff","backgroundColor":"#222222","text":"RSSI: ","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false},{"name":"label_snr","type":"label","layer":6,"screenId":"builtin","screenName":"","id":"xtHTVM@JrzZbGjS&","createTime":1737529536574,"x":5,"y":108,"color":"#ffffff","backgroundColor":"#222222","text":"SNR: ","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false},{"name":"label_rssi_v","type":"label","layer":7,"screenId":"builtin","screenName":"","id":"wKqG6^U@@=Ggcz_O","createTime":1737529604351,"x":65,"y":80,"color":"#ffffff","backgroundColor":"#222222","text":" ","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false},{"name":"label_snr_v","type":"label","layer":8,"screenId":"builtin","screenName":"","id":"z2K#mvk#KSD3jC8X","createTime":1737529606428,"x":65,"y":108,"color":"#ffffff","backgroundColor":"#222222","text":" ","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false}],"resources":[{"hardware":["hardware_button","hardware_pin_button","imu","speaker","touch","als","mic"]},{"module":["relay4","module_lora868v12"]}],"units":[],"hats":[],"bases":[],"i2cs":[],"blockly":"lora868v12_datarssisnrlast_timetruelora868v12_0True5110286800025088120x1210lora868v12_0last_timetrueGTE11last_time1000last_timelabel_timeLabellast_timelora868v12_0lora868v12_datalabel_rxLabellora868v12_datarssirssilora868v12_datasnrDIVIDE1snrlora868v12_data4label_rssi_vLabelrssilabel_snr_vLabelsnr","screen":[{"simulationName":"Built-in","type":"builtin","width":320,"height":240,"scale":0.78,"screenName":"","blockId":"","screenColorType":0,"id":"builtin","createTime":1737449694208}],"logicWhenNum":0,"customList":[]} \ No newline at end of file diff --git a/examples/module/lora_sx1262/cores3_lora_sx1262_rx_example.py b/examples/module/lora868_v12/cores3_lora868_v12_rx_example.py similarity index 86% rename from examples/module/lora_sx1262/cores3_lora_sx1262_rx_example.py rename to examples/module/lora868_v12/cores3_lora868_v12_rx_example.py index 6bc4290d..fe33f93e 100644 --- a/examples/module/lora_sx1262/cores3_lora_sx1262_rx_example.py +++ b/examples/module/lora868_v12/cores3_lora868_v12_rx_example.py @@ -5,7 +5,7 @@ import os, sys, io import M5 from M5 import * -from module import LoRaSx1262Module +from module import LoRa868V12Module import time @@ -19,8 +19,6 @@ label_rssi_v = None label_snr_v = None lora868v12_0 = None - - lora868v12_data = None rssi = None snr = None @@ -46,7 +44,7 @@ def lora868v12_0_receive_event(received_data): lora868v12_data = received_data label_rx.setText(str(lora868v12_data.decode())) rssi = lora868v12_data.rssi - snr = lora868v12_data.snr + snr = (lora868v12_data.snr) / 4 label_rssi_v.setText(str(rssi)) label_snr_v.setText(str(snr)) @@ -80,7 +78,19 @@ def setup(): label_rssi_v = Widgets.Label(" ", 65, 80, 1.0, 0xFFFFFF, 0x222222, Widgets.FONTS.DejaVu18) label_snr_v = Widgets.Label(" ", 65, 108, 1.0, 0xFFFFFF, 0x222222, Widgets.FONTS.DejaVu18) - lora868v12_0 = LoRaSx1262Module(5, 1, 10, 2, 868000, "250", 8, 8, 12, 0x12, 10) + lora868v12_0 = LoRa868V12Module( + pin_rst=5, + pin_cs=1, + pin_irq=10, + pin_busy=2, + freq_khz=868000, + bw="250", + sf=8, + coding_rate=8, + preamble_len=12, + syncword=0x12, + output_power=10, + ) lora868v12_0.set_irq_callback(lora868v12_0_receive_event) lora868v12_0.start_recv() last_time = time.ticks_ms() diff --git a/examples/module/lora868_v12/cores3_lora868_v12_tx_example.m5f2 b/examples/module/lora868_v12/cores3_lora868_v12_tx_example.m5f2 new file mode 100644 index 00000000..f5d0839c --- /dev/null +++ b/examples/module/lora868_v12/cores3_lora868_v12_tx_example.m5f2 @@ -0,0 +1 @@ +{"version":"V2.0","versionNumber":"V2.2.6","type":"cores3","components":[{"name":"screen","type":"screen","layer":0,"screenId":"builtin","screenName":"","id":"__cores3_screen","createTime":1737446502785,"x":0,"y":0,"width":320,"height":240,"backgroundColor":"#222222","size":0,"isSelected":true},{"name":"title0","type":"title","layer":1,"screenId":"builtin","screenName":"","id":"lq1#Hp=l*VwVD$*D","createTime":1737449153695,"x":0,"y":0,"color":"#ffffff","backgroundColor":"#0000FF","text":"LoRa Module Tx","textOffset":3,"font":"Widgets.FONTS.DejaVu18","isSelected":false},{"name":"label_time","type":"label","layer":2,"screenId":"builtin","screenName":"","id":"g9YBGsEq0Catblmg","createTime":1737449179528,"x":118,"y":150,"color":"#ffffff","backgroundColor":"#222222","text":"1","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false},{"name":"label_t","type":"label","layer":1,"screenId":"builtin","screenName":"","id":"dW=zgZK1+lDoweMx","createTime":1737526624836,"x":5,"y":50,"color":"#ffffff","backgroundColor":"#222222","text":"Send:","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false},{"name":"label_tx","type":"label","layer":2,"screenId":"builtin","screenName":"","id":"o=^d^UJlfpLDV2uW","createTime":1737526624836,"x":65,"y":50,"color":"#ffffff","backgroundColor":"#222222","text":"hello","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false},{"name":"label_ts","type":"label","layer":5,"screenId":"builtin","screenName":"","id":"kihcF0IlY!&J0fHm","createTime":1737526624836,"x":5,"y":150,"color":"#ffffff","backgroundColor":"#222222","text":"timestamp:","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false}],"resources":[{"hardware":["hardware_button","hardware_pin_button","imu","speaker","touch","als","mic"]},{"module":["module_lora868v12"]}],"units":[],"hats":[],"bases":[],"i2cs":[],"blockly":"countlast_timetimestamptruecount0lora868v12_0True5110286800025088120x1210last_timetrueGTE11last_time1000last_timecountADD1count1timestamplora868v12_0hello M5 countlabel_txLabelhello M5 countlabel_timeLabellast_time","screen":[{"simulationName":"Built-in","type":"builtin","width":320,"height":240,"scale":0.78,"screenName":"","blockId":"","screenColorType":0,"id":"builtin","createTime":1737446502783}],"logicWhenNum":0,"customList":[]} \ No newline at end of file diff --git a/examples/module/lora_sx1262/cores3_lora_sx1262_tx_example.py b/examples/module/lora868_v12/cores3_lora868_v12_tx_example.py similarity index 82% rename from examples/module/lora_sx1262/cores3_lora_sx1262_tx_example.py rename to examples/module/lora868_v12/cores3_lora868_v12_tx_example.py index a269fe96..400564a5 100644 --- a/examples/module/lora_sx1262/cores3_lora_sx1262_tx_example.py +++ b/examples/module/lora868_v12/cores3_lora868_v12_tx_example.py @@ -5,7 +5,7 @@ import os, sys, io import M5 from M5 import * -from module import LoRaSx1262Module +from module import LoRa868V12Module import time @@ -15,8 +15,6 @@ label_tx = None label_ts = None lora868v12_0 = None - - count = None last_time = None timestamp = None @@ -42,8 +40,20 @@ def setup(): label_tx = Widgets.Label("hello", 65, 50, 1.0, 0xFFFFFF, 0x222222, Widgets.FONTS.DejaVu18) label_ts = Widgets.Label("timestamp:", 5, 150, 1.0, 0xFFFFFF, 0x222222, Widgets.FONTS.DejaVu18) - lora868v12_0 = LoRaSx1262Module(5, 1, 10, 2, 868000, "250", 8, 8, 12, 0x12, 10) count = 0 + lora868v12_0 = LoRa868V12Module( + pin_rst=5, + pin_cs=1, + pin_irq=10, + pin_busy=2, + freq_khz=868000, + bw="250", + sf=8, + coding_rate=8, + preamble_len=12, + syncword=0x12, + output_power=10, + ) last_time = time.ticks_ms() diff --git a/examples/module/lora_sx1262/cores3_lora_sx1262_rx_example.m5f2 b/examples/module/lora_sx1262/cores3_lora_sx1262_rx_example.m5f2 deleted file mode 100644 index 12cb04cc..00000000 --- a/examples/module/lora_sx1262/cores3_lora_sx1262_rx_example.m5f2 +++ /dev/null @@ -1 +0,0 @@ -{"version":"V2.0","versionNumber":"V2.2.1","type":"cores3","components":[{"name":"screen","type":"screen","layer":0,"screenId":"builtin","screenName":"","id":"__cores3_screen","createTime":1737449694211,"x":0,"y":0,"width":320,"height":240,"backgroundColor":"#222222","size":0,"isSelected":true},{"name":"title0","type":"title","layer":1,"screenId":"builtin","screenName":"","id":"fZghG4ekSYQ$iOsz","createTime":1737449765298,"x":0,"y":0,"color":"#ffffff","backgroundColor":"#0000FF","text":"LoRa Module Rx","textOffset":3,"font":"Widgets.FONTS.DejaVu18","isSelected":false,"width":321,"height":19},{"name":"label_r","type":"label","layer":1,"screenId":"builtin","screenName":"","id":"yq=Jeun8%_&%6USl","createTime":1737528840028,"x":5,"y":50,"color":"#ffffff","backgroundColor":"#222222","text":"Recv:","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false,"width":51,"height":20},{"name":"label_rx","type":"label","layer":2,"screenId":"builtin","screenName":"","id":"xWyO=oScbJw*P4bh","createTime":1737528841197,"x":65,"y":50,"color":"#ffffff","backgroundColor":"#222222","text":" ","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false,"width":5,"height":20},{"name":"label_t","type":"label","layer":3,"screenId":"builtin","screenName":"","id":"m!MG6ke43xUSzPkp","createTime":1737528842542,"x":5,"y":150,"color":"#ffffff","backgroundColor":"#222222","text":"timestamp:","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false,"width":108,"height":21},{"name":"label_time","type":"label","layer":4,"screenId":"builtin","screenName":"","id":"wo`z#Aq_hi4v5nlY","createTime":1737528843789,"x":118,"y":150,"color":"#ffffff","backgroundColor":"#222222","text":"1","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false,"width":12,"height":21},{"name":"label_rssi","type":"label","layer":5,"screenId":"builtin","screenName":"","id":"gZ3bUs6%k`K%mrB-","createTime":1737529534968,"x":5,"y":80,"color":"#ffffff","backgroundColor":"#222222","text":"RSSI: ","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false,"width":54,"height":20},{"name":"label_snr","type":"label","layer":6,"screenId":"builtin","screenName":"","id":"xtHTVM@JrzZbGjS&","createTime":1737529536574,"x":5,"y":108,"color":"#ffffff","backgroundColor":"#222222","text":"SNR: ","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false,"width":51,"height":21},{"name":"label_rssi_v","type":"label","layer":7,"screenId":"builtin","screenName":"","id":"wKqG6^U@@=Ggcz_O","createTime":1737529604351,"x":65,"y":80,"color":"#ffffff","backgroundColor":"#222222","text":" ","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false,"width":6,"height":21},{"name":"label_snr_v","type":"label","layer":8,"screenId":"builtin","screenName":"","id":"z2K#mvk#KSD3jC8X","createTime":1737529606428,"x":65,"y":108,"color":"#ffffff","backgroundColor":"#222222","text":" ","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false,"width":58,"height":20}],"resources":[{"hardware":["hardware_button","hardware_pin_button","imu","speaker","touch","als","mic"]},{"module":["module_lora868v12"]}],"units":[],"hats":[],"bases":[],"i2cs":[],"blockly":"lora868v12_datarssisnrlast_timetruelora868v12_0True1105286800025088120x1210lora868v12_0last_timetrueGTE11last_time1000last_timelabel_timeLabellast_timelora868v12_0lora868v12_datalabel_rxLabellora868v12_datarssirssilora868v12_datasnrsnrlora868v12_datalabel_rssi_vLabelrssilabel_snr_vLabelsnr","screen":[{"simulationName":"Built-in","type":"builtin","width":320,"height":240,"scale":0.78,"screenName":"","blockId":"","screenColorType":0,"id":"builtin","createTime":1737449694208}],"logicWhenNum":0,"customList":[]} \ No newline at end of file diff --git a/examples/module/lora_sx1262/cores3_lora_sx1262_tx_example.m5f2 b/examples/module/lora_sx1262/cores3_lora_sx1262_tx_example.m5f2 deleted file mode 100644 index 5efa722e..00000000 --- a/examples/module/lora_sx1262/cores3_lora_sx1262_tx_example.m5f2 +++ /dev/null @@ -1 +0,0 @@ -{"version":"V2.0","versionNumber":"V2.2.1","type":"cores3","components":[{"name":"screen","type":"screen","layer":0,"screenId":"builtin","screenName":"","id":"__cores3_screen","createTime":1737446502785,"x":0,"y":0,"width":320,"height":240,"backgroundColor":"#222222","size":0,"isSelected":true},{"name":"title0","type":"title","layer":1,"screenId":"builtin","screenName":"","id":"lq1#Hp=l*VwVD$*D","createTime":1737449153695,"x":0,"y":0,"color":"#ffffff","backgroundColor":"#0000FF","text":"LoRa Module Tx","textOffset":3,"font":"Widgets.FONTS.DejaVu18","isSelected":false},{"name":"label_time","type":"label","layer":2,"screenId":"builtin","screenName":"","id":"g9YBGsEq0Catblmg","createTime":1737449179528,"x":118,"y":150,"color":"#ffffff","backgroundColor":"#222222","text":"1","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false,"width":12,"height":21},{"name":"label_t","type":"label","layer":1,"screenId":"builtin","screenName":"","id":"dW=zgZK1+lDoweMx","createTime":1737526624836,"x":5,"y":50,"color":"#ffffff","backgroundColor":"#222222","text":"Send:","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false,"width":54,"height":20},{"name":"label_tx","type":"label","layer":2,"screenId":"builtin","screenName":"","id":"o=^d^UJlfpLDV2uW","createTime":1737526624836,"x":65,"y":50,"color":"#ffffff","backgroundColor":"#222222","text":"hello","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false,"width":45,"height":21},{"name":"label_ts","type":"label","layer":5,"screenId":"builtin","screenName":"","id":"kihcF0IlY!&J0fHm","createTime":1737526624836,"x":5,"y":150,"color":"#ffffff","backgroundColor":"#222222","text":"timestamp:","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false,"width":108,"height":21}],"resources":[{"hardware":["hardware_button","hardware_pin_button","imu","speaker","touch","als","mic"]},{"module":["module_lora868v12"]}],"units":[],"hats":[],"bases":[],"i2cs":[],"blockly":"countlast_timetimestamptruelora868v12_0True1105286800025088120x1210count0last_timetrueGTE11last_time1000last_timecountADD1count1timestamplora868v12_0hello M5 countlabel_txLabelhello M5 countlabel_timeLabellast_time","screen":[{"simulationName":"Built-in","type":"builtin","width":320,"height":240,"scale":0.78,"screenName":"","blockId":"","screenColorType":0,"id":"builtin","createTime":1737446502783}],"logicWhenNum":0,"customList":[]} \ No newline at end of file diff --git a/m5stack/libs/module/__init__.py b/m5stack/libs/module/__init__.py index eb26f93b..f23ffb33 100644 --- a/m5stack/libs/module/__init__.py +++ b/m5stack/libs/module/__init__.py @@ -28,7 +28,7 @@ "IotBaseCatmModule": "iot_base_catm", "LlmModule": "llm", "LoraModule": "lora", - "LoRaSx1262Module": "lora_sx1262", + "LoRa868V12Module": "lora868_v12", "LoRaWANModule": "lorawan", "LoRaWAN868Module": "lorawan868", "LTEModule": "lte", diff --git a/m5stack/libs/module/lora_sx1262.py b/m5stack/libs/module/lora868_v12.py similarity index 62% rename from m5stack/libs/module/lora_sx1262.py rename to m5stack/libs/module/lora868_v12.py index 86e895d4..817e34a7 100644 --- a/m5stack/libs/module/lora_sx1262.py +++ b/m5stack/libs/module/lora868_v12.py @@ -9,8 +9,8 @@ from micropython import const, schedule -class LoRaSx1262Module: - """Create an LoRaSx1262Module object. +class LoRa868V12Module: + """Create an LoRa868V12Module object. :param int timer_id: The Timer ID. Range: 0~3. Default is 0. :param int pin_rst: (RST) Reset pin number. @@ -43,9 +43,9 @@ class LoRaSx1262Module: .. code-block:: python - from module import LoRaSx1262Module + from module import LoRa868V12Module - lora868v12_0 = LoRaSx1262Module(5, 1, 10, 2, 868000, '250', 8, 8, 12, 0x12, 10) + module_lora868v12_0 = LoRa868V12Module(5, 1, 10, 2, 868000, '250', 8, 8, 12, 0x12, 10) """ def __init__( @@ -77,7 +77,6 @@ def __init__( ) self._validate_range(sf, 6, 12) self._validate_range(coding_rate, 5, 8) - if bw not in self.BANDWIDTHS: raise ValueError(f"Invalid bandwidth {bw}") @@ -107,6 +106,142 @@ def _validate_range(self, value, min, max): if value < min or value > max: raise ValueError(f"Value {value} out of range {min} to {max}") + def set_freq(self, freq_khz: int = 868000) -> None: + """Set frequency in kHz. + + :param int freq_khz: Frequency in kHz (850000 ~ 930000), default is 868000. + + UiFlow2 Code Block: + + |set_freq.png| + + MicroPython Code Block: + + .. code-block:: python + + module_lora868v12_0.set_freq(868000) + """ + self._validate_range(freq_khz, 850000, 930000) + lora_cfg = {"freq_khz": freq_khz} + self.modem.configure(lora_cfg) + + def set_sf(self, sf: int) -> None: + """Set spreading factor (SF). + + :param int sf: Spreading factor (7 ~ 12) + + UiFlow2 Code Block: + + |set_sf.png| + + MicroPython Code Block: + + .. code-block:: python + + module_lora868v12_0.set_sf(7) + """ + self._validate_range(sf, 7, 12) + lora_cfg = {"sf": sf} + self.modem.configure(lora_cfg) + + def set_bw(self, bw: str) -> None: + """Set bandwidth. + + :param str bw: Bandwidth in kHz as string. Must be one of: + '7.8', '10.4', '15.6', '20.8', '31.25', '41.7', + '62.5', '125', '250', '500'. + + UiFlow2 Code Block: + + |set_bw.png| + + MicroPython Code Block: + + .. code-block:: python + + module_lora868v12_0.set_bw(bw) + """ + if bw not in self.BANDWIDTHS: + raise ValueError(f"Invalid bandwidth '{bw}', must be one of {self.BANDWIDTHS}") + lora_cfg = {"bw": bw} + self.modem.configure(lora_cfg) + + def set_coding_rate(self, coding_rate: int) -> None: + """Set coding rate. + + :param int coding_rate: Coding rate (5 ~ 8) + + UiFlow2 Code Block: + + |set_coding_rate.png| + + MicroPython Code Block: + + .. code-block:: python + + module_lora868v12_0.set_coding_rate(coding_rate) + """ + self._validate_range(coding_rate, 5, 8) + lora_cfg = {"coding_rate": coding_rate} + self.modem.configure(lora_cfg) + + def set_syncword(self, syncword: int) -> None: + """Set syncword. + + :param int syncword: Sync word (0 ~ 0xFF) + + UiFlow2 Code Block: + + |set_syncword.png| + + MicroPython Code Block: + + .. code-block:: python + + module_lora868v12_0.set_syncword(syncword) + """ + self._validate_range(syncword, 0, 0xFF) + lora_cfg = {"syncword": syncword} + self.modem.configure(lora_cfg) + + def set_preamble_len(self, preamble_len: int) -> None: + """Set preamble length. + + :param int preamble_len: Preamble length, range: 0~255. + + UiFlow2 Code Block: + + |set_preamble_len.png| + + MicroPython Code Block: + + .. code-block:: python + + module_lora868v12_0.set_preamble_len(preamble_len) + """ + self._validate_range(preamble_len, 6, 65535) + lora_cfg = {"preamble_len": preamble_len} + self.modem.configure(lora_cfg) + + def set_output_power(self, output_power: int) -> None: + """Set output power in dBm. + + :param int output_power: Output power in dBm (-9 ~ 22) + + UiFlow2 Code Block: + + |set_output_power.png| + + MicroPython Code Block: + + .. code-block:: python + + module_lora868v12_0.set_output_power(output_power) + """ + self._validate_range(output_power, -9, 22) + lora_cfg = {"output_power": output_power} + self.modem.configure(lora_cfg) + def send(self, packet: str | list | tuple | int | bytearray, tx_at_ms: int = None) -> int: """Send data @@ -125,7 +260,7 @@ def send(self, packet: str | list | tuple | int | bytearray, tx_at_ms: int = Non .. code-block:: python - lora868v12_0.send() + module_lora868v12_0.send() """ if isinstance(packet, str): packet = bytes(packet, "utf-8") @@ -156,7 +291,7 @@ def recv( .. code-block:: python - data = lora868v12_0.recv() + data = module_lora868v12_0.recv() """ return self.modem.recv(timeout_ms, rx_length, rx_packet) @@ -173,7 +308,7 @@ def start_recv(self) -> None: .. code-block:: python - lora868v12_0.start_recv() + module_lora868v12_0.start_recv() """ self.modem.start_recv(continuous=True) @@ -191,7 +326,7 @@ def set_irq_callback(self, callback) -> None: .. code-block:: python - lora868v12_0.set_irq_callback() + module_lora868v12_0.set_irq_callback() """ self.irq_callback = callback @@ -214,7 +349,7 @@ def standby(self) -> None: .. code-block:: python - lora868v12_0.standby() + module_lora868v12_0.standby() """ self.modem.standby() @@ -231,7 +366,7 @@ def sleep(self) -> None: .. code-block:: python - lora868v12_0.sleep() + module_lora868v12_0.sleep() """ self.modem.sleep() @@ -249,6 +384,6 @@ def irq_triggered(self) -> bool: .. code-block:: python - lora868v12_0.irq_triggered() + module_lora868v12_0.irq_triggered() """ return self.modem.irq_triggered() diff --git a/m5stack/libs/module/manifest.py b/m5stack/libs/module/manifest.py index 8917428d..eb3d8ec6 100644 --- a/m5stack/libs/module/manifest.py +++ b/m5stack/libs/module/manifest.py @@ -27,7 +27,7 @@ "iot_base_catm.py", "llm.py", "lora.py", - "lora_sx1262.py", + "lora868_v12.py", "lorawan.py", "lorawan868.py", "lte.py", From b5dc18388372adb6c8005a93b2f0317f19908172 Mon Sep 17 00:00:00 2001 From: luoweiyuan Date: Thu, 15 May 2025 11:32:37 +0800 Subject: [PATCH 081/322] boards: Add AtomS3R-M12 support. --- docs/en/advanced/camera.rst | 28 +- .../zh_CN/LC_MESSAGES/advanced/camera.po | 137 ++++--- m5stack/cmodules/omv/modules/py_camera.c | 343 ++++++++++-------- 3 files changed, 291 insertions(+), 217 deletions(-) diff --git a/docs/en/advanced/camera.rst b/docs/en/advanced/camera.rst index 95b79c58..0998120a 100644 --- a/docs/en/advanced/camera.rst +++ b/docs/en/advanced/camera.rst @@ -20,7 +20,7 @@ capture display :language: python :linenos: -UIFlow2.0 Example +UiFlow2 Example ------------------------------ capture display @@ -33,7 +33,6 @@ capture display |cores3_example_camera_display.m5f2| - Functions ------------------------------ @@ -41,19 +40,28 @@ Functions Initializes the camera sensor. - The ``pixformat`` supports: + The ``pixformat`` supports (note: camera.JPEG is only for AtomS3R-M12): - ``camera.RGB565`` + - ``camera.JPEG`` - The ``framesize`` supports: + The ``framesize`` supports (note: Resolutions higher than camera.QVGA are only supported when pixformat is set to JPEG.): - ``camera.QQVGA``: 160x120 - ``camera.QCIF``: 176x144 - ``camera.HQVGA``: 240x176 - ``camera.FRAME_240X240``: 240x240 - ``camera.QVGA``: 320x240 + - ``camera.VGA``: 640x480 + - ``camera.SVGA``: 800x600 + - ``camera.XGA``: 1024x768 + - ``camera.HD``: 1280x720 + - ``camera.SXGA``: 1280x1024 + - ``camera.UXGA``: 1600x1200 + - ``camera.FHD``: 1920x1080 + - ``camera.QXGA``: 2048x1536 - UIFlow2.0 + UiFlow2 |init.png| @@ -63,7 +71,7 @@ Functions Returns An ``image.Image`` object. - UIFlow2.0 + UiFlow2 |snapshot.png| @@ -71,7 +79,7 @@ Functions Turns horizontal mirror mode on (True) or off (False). Defaults to on. - UIFlow2.0 + UiFlow2 |set_hmirror.png| @@ -79,7 +87,7 @@ Functions Turns vertical flip mode on (True) or off (False). Defaults to off. - UIFlow2.0 + UiFlow2 |set_vflip.png| @@ -87,7 +95,7 @@ Functions Returns if horizontal mirror mode is enabled. - UIFlow2.0 + UiFlow2 |get_hmirror.png| @@ -95,7 +103,7 @@ Functions Returns if vertical flip mode is enabled. - UIFlow2.0 + UiFlow2 |get_vflip.png| diff --git a/docs/locales/zh_CN/LC_MESSAGES/advanced/camera.po b/docs/locales/zh_CN/LC_MESSAGES/advanced/camera.po index 797420c3..ed6d3216 100644 --- a/docs/locales/zh_CN/LC_MESSAGES/advanced/camera.po +++ b/docs/locales/zh_CN/LC_MESSAGES/advanced/camera.po @@ -9,7 +9,7 @@ msgid "" msgstr "" "Project-Id-Version: UIFlow2 Programming Guide \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-01-16 17:26+0800\n" +"POT-Creation-Date: 2025-05-15 11:27+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language: zh_CN\n" @@ -20,161 +20,198 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.16.0\n" -#: ../../en/advanced/camera.rst:2 3984ce41f7f3470db8fa662e6ddd528c +#: ../../en/advanced/camera.rst:2 5819f86957f24b609306f160ebb48622 msgid "camera" msgstr "" -#: ../../en/advanced/camera.rst:6 d43a7635472741dba92913f203d3d090 +#: ../../en/advanced/camera.rst:6 dd20919525884314a853a9adeafff50f msgid "The camera module is used for taking pictures." msgstr "camera 模块用于拍照。" -#: ../../en/advanced/camera.rst:8 592a91bd672d416eadb0000abc7663fa +#: ../../en/advanced/camera.rst:8 21d4b01f32c24103937bb12b5fe78ec2 msgid "This module is only applicable to the CoreS3 Controller" msgstr "当前模块只适用于 CoreS3 主机" -#: ../../en/advanced/camera.rst:14 2c54fd3a05904356925cce226acd22cd +#: ../../en/advanced/camera.rst:14 7dca11fba9704113a21b3d826888d3f0 msgid "Micropython Example" msgstr "Micropython 案例" #: ../../en/advanced/camera.rst:17 ../../en/advanced/camera.rst:27 -#: 133658221cab45ce88d4e12e57dd5473 da5ca964ace84e0b849b09ca98898c49 +#: a780a36d411c47fab1b52158d451585c f427ecaf130f42e3b633c9dd254c265f msgid "capture display" msgstr "拍摄显示" -#: ../../en/advanced/camera.rst:24 9a83468d0afa4fa3a8764513e053e624 -msgid "UIFlow2.0 Example" +#: ../../en/advanced/camera.rst:24 b80684e9fd654d02b9007f96b9f78d4d +#, fuzzy +msgid "UiFlow2 Example" msgstr "UIFlow2.0 案例" -#: ../../en/advanced/camera.rst:29 d5088898ca6441639165139abcb9bb3a +#: ../../en/advanced/camera.rst:29 04b43404070d4100a565d2997e3fb823 msgid "|camera_display_example.png|" msgstr "" -#: ../../en/refs/advanced.camera.ref:1 987fd079aefc4183af936a4653837ef5 +#: ../../en/refs/advanced.camera.ref:1 9b4ac6865649467e8dee4c7b4383910c msgid "camera_display_example.png" msgstr "" -#: ../../en/advanced/camera.rst:33 838c5a12d5944b8682a2f5238597e8ae +#: ../../en/advanced/camera.rst:33 95286e61dafa40518cf1337784a5f794 msgid "|cores3_example_camera_display.m5f2|" msgstr "" -#: ../../en/advanced/camera.rst:38 6d5c445b5c4146828a1b718ff0af9ae9 +#: ../../en/advanced/camera.rst:37 825788da433e47a5bf505df405f1d42a msgid "Functions" msgstr "" -#: ../../en/advanced/camera.rst:42 9a7ba996c08d4adf8e7128a33cbc7efa +#: ../../en/advanced/camera.rst:41 990b40b1b53d4f0daf05b6878b6ebdd3 msgid "Initializes the camera sensor." msgstr "初始化摄像头" -#: ../../en/advanced/camera.rst:44 ce4a88149b224099a7d4cda83977938f -msgid "The ``pixformat`` supports:" -msgstr "参数 ``pixformat`` 仅接受以下值:" +#: ../../en/advanced/camera.rst:43 6d92559b9cf240edabfb145c9ebf08c4 +msgid "The ``pixformat`` supports (note: camera.JPEG is only for AtomS3R-M12):" +msgstr "只有 AtomS3R-M12 支持 camera.JPEG" -#: ../../en/advanced/camera.rst:46 87322970341141789c6925438ee2ab1d +#: ../../en/advanced/camera.rst:45 2db30a05151a4290a440f719985a8faf msgid "``camera.RGB565``" msgstr "" -#: ../../en/advanced/camera.rst:48 14810423b00540ab96c4a5631287a48e -msgid "The ``framesize`` supports:" -msgstr "参数 ``framesize`` 仅接受以下值:" +#: ../../en/advanced/camera.rst:46 9e1742ac2f65492597b5c4d4aa63a711 +msgid "``camera.JPEG``" +msgstr "" -#: ../../en/advanced/camera.rst:50 65e81d0cbe234b0bb6c9d2291641b9ec +#: ../../en/advanced/camera.rst:48 31bc68206a6b42a2b33720ddc63f98bd +msgid "" +"The ``framesize`` supports (note: Resolutions higher than camera.QVGA are" +" only supported when pixformat is set to JPEG.):" +msgstr "分辨率高于 camera.QVGA 的设置,仅在 pixformat 为 JPEG 时才支持。" + +#: ../../en/advanced/camera.rst:50 9e1742ac2f65492597b5c4d4aa63a711 msgid "``camera.QQVGA``: 160x120" msgstr "" -#: ../../en/advanced/camera.rst:51 37a2de35f71c467a81fcbd179058b844 +#: ../../en/advanced/camera.rst:51 ace884c937d744eea4d557ccf87d7ae3 msgid "``camera.QCIF``: 176x144" msgstr "" -#: ../../en/advanced/camera.rst:52 394e7e2848b345d2868ef6817fdb924d +#: ../../en/advanced/camera.rst:52 78b15e105e094c3b9687521a3d16212d msgid "``camera.HQVGA``: 240x176" msgstr "" -#: ../../en/advanced/camera.rst:53 bc1309bf77a34cb3a0b26bfa4ad87c58 +#: ../../en/advanced/camera.rst:53 34e5c67cab8947cd9d78702f6eaec2d9 msgid "``camera.FRAME_240X240``: 240x240" msgstr "" -#: ../../en/advanced/camera.rst:54 e6538d5801c84fee8f25181ab5c9706b +#: ../../en/advanced/camera.rst:54 41b07f924b2f49fc8d4adac6051038da msgid "``camera.QVGA``: 320x240" msgstr "" -#: ../../en/advanced/camera.rst:56 ../../en/advanced/camera.rst:66 -#: ../../en/advanced/camera.rst:74 ../../en/advanced/camera.rst:82 -#: ../../en/advanced/camera.rst:90 ../../en/advanced/camera.rst:98 -#: 0165cffa9288442c8f7251b20b0693cc 1459823f79a64d17802a4960d4d5f0be -#: 1dc8c0d39bfe4f99b12f17f874cf6840 516ab77ed78c47379308ff6a15265763 -#: 91f31a216e8f40ba9976259d3cb5acb2 b924f97094b543558616c6eb858a0e54 -msgid "UIFlow2.0" +#: ../../en/advanced/camera.rst:55 41b07f924b2f49fc8d4adac6051038da +msgid "``camera.VGA``: 640x480" +msgstr "" + +#: ../../en/advanced/camera.rst:56 41b07f924b2f49fc8d4adac6051038da +msgid "``camera.SVGA``: 800x600" +msgstr "" + +#: ../../en/advanced/camera.rst:57 78b15e105e094c3b9687521a3d16212d +msgid "``camera.XGA``: 1024x768" +msgstr "" + +#: ../../en/advanced/camera.rst:58 9e1742ac2f65492597b5c4d4aa63a711 +msgid "``camera.HD``: 1280x720" +msgstr "" + +#: ../../en/advanced/camera.rst:59 9e1742ac2f65492597b5c4d4aa63a711 +msgid "``camera.SXGA``: 1280x1024" +msgstr "" + +#: ../../en/advanced/camera.rst:60 9e1742ac2f65492597b5c4d4aa63a711 +msgid "``camera.UXGA``: 1600x1200" +msgstr "" + +#: ../../en/advanced/camera.rst:61 9e1742ac2f65492597b5c4d4aa63a711 +msgid "``camera.FHD``: 1920x1080" +msgstr "" + +#: ../../en/advanced/camera.rst:62 78b15e105e094c3b9687521a3d16212d +msgid "``camera.QXGA``: 2048x1536" +msgstr "" + +#: ../../en/advanced/camera.rst:64 ../../en/advanced/camera.rst:74 +#: ../../en/advanced/camera.rst:82 ../../en/advanced/camera.rst:90 +#: ../../en/advanced/camera.rst:98 ../../en/advanced/camera.rst:106 +#: 059c1ce57c844e0394a12d52cb68305f 9a9de8447a7f4a6d8363fea8a640d047 +msgid "UiFlow2" msgstr "" -#: ../../en/advanced/camera.rst:58 522fc35766f54621bef8a19c2f3c9e7f +#: ../../en/advanced/camera.rst:66 44ec61a9923347a38eeea47d2a5a363f msgid "|init.png|" msgstr "" -#: ../../en/refs/advanced.camera.ref:2 c946fc480b9747048ead109fb9a5ae31 +#: ../../en/refs/advanced.camera.ref:2 b0bd7aa64dff485b937cc176036a7e11 msgid "init.png" msgstr "" -#: ../../en/advanced/camera.rst:62 65d30df72c5f43b5b17a041ff7532ffb +#: ../../en/advanced/camera.rst:70 2620a8b8e077439cb286f80b02c96446 msgid "Capture a single frame." msgstr "获取一帧图像" -#: ../../en/advanced/camera.rst:64 0b4ad89251fd4341b80d809be2aad902 +#: ../../en/advanced/camera.rst:72 60d704fd704240e5b8c3cf55b8841dd7 msgid "Returns An ``image.Image`` object." msgstr "返回一个 ``image.Image`` 对象。" -#: ../../en/advanced/camera.rst:68 8dc1378b87c745ebae2e0fdb66a007d3 +#: ../../en/advanced/camera.rst:76 4c590b298669448eb3aee000c4fbabb4 msgid "|snapshot.png|" msgstr "" -#: ../../en/refs/advanced.camera.ref:5 7a15c1cec835482b881e1d1cbca2f720 +#: ../../en/refs/advanced.camera.ref:5 4e9253a1803241369599c81013f1d2f9 msgid "snapshot.png" msgstr "" -#: ../../en/advanced/camera.rst:72 274b4f5023214a6a94fedbb0c4bceaeb +#: ../../en/advanced/camera.rst:80 38ad84926b8e476e80e5fc90bb92d148 msgid "Turns horizontal mirror mode on (True) or off (False). Defaults to on." msgstr "开启或关闭水平镜像模式(True 表示开启,False 表示关闭)。默认为开启。" -#: ../../en/advanced/camera.rst:76 68f65e6aeae94558b8b0f3d5eefd309d +#: ../../en/advanced/camera.rst:84 123c2c4eb6ae40e1b5a21673503873fd msgid "|set_hmirror.png|" msgstr "" -#: ../../en/refs/advanced.camera.ref:6 5644bc5d2f5242dc9156e3030f647205 +#: ../../en/refs/advanced.camera.ref:6 de35731578014ae6872eb45923fb8173 msgid "set_hmirror.png" msgstr "" -#: ../../en/advanced/camera.rst:80 8902b554043949468c7744165586a111 +#: ../../en/advanced/camera.rst:88 3a01a5446ca34d1d9dd17ba0627f9727 msgid "Turns vertical flip mode on (True) or off (False). Defaults to off." msgstr "开启或关闭垂直翻转模式(True 表示开启,False 表示关闭)。默认为关闭。" -#: ../../en/advanced/camera.rst:84 4e00af0c642244cb9f3ef39a54e9b6f7 +#: ../../en/advanced/camera.rst:92 d055629b3ae64d0783c376474fd9ec57 msgid "|set_vflip.png|" msgstr "" -#: ../../en/refs/advanced.camera.ref:7 8b9df031716e4c4b89ee929f19437095 +#: ../../en/refs/advanced.camera.ref:7 8d15dd7404e84c74a8577e11f99c91b8 msgid "set_vflip.png" msgstr "" -#: ../../en/advanced/camera.rst:88 49c69e9cd11f4f08a4c8de39f220e66d +#: ../../en/advanced/camera.rst:96 461f1b7eacf24695bfc86a5b0c03b16c msgid "Returns if horizontal mirror mode is enabled." msgstr "返回当前是否启用了水平镜像模式。" -#: ../../en/advanced/camera.rst:92 dd87a19ae39e4c79a5354c11ba29f93a +#: ../../en/advanced/camera.rst:100 8e1ce21913964e3b955d6fae7d3c76be msgid "|get_hmirror.png|" msgstr "" -#: ../../en/refs/advanced.camera.ref:4 54d37af2ae10493b99058f81b7d57479 +#: ../../en/refs/advanced.camera.ref:4 6a619cd0f0f84209b4da62299aa04e10 msgid "get_hmirror.png" msgstr "" -#: ../../en/advanced/camera.rst:96 b2b828db59304636aa25eefbf74f974a +#: ../../en/advanced/camera.rst:104 e745648cf2044d16a7264d9edf29bd00 msgid "Returns if vertical flip mode is enabled." msgstr "返回当前是否启用了垂直翻转模式。" -#: ../../en/advanced/camera.rst:100 1da064b12a5049b2995181e7817e08d4 +#: ../../en/advanced/camera.rst:108 78b04ae9313a48bba4d622cc0083843e msgid "|get_vflip.png|" msgstr "" -#: ../../en/refs/advanced.camera.ref:3 a645daea4595464db2c8fad6f74065c1 +#: ../../en/refs/advanced.camera.ref:3 e8d3431a403046deaaf9d06daa8e3173 msgid "get_vflip.png" msgstr "" diff --git a/m5stack/cmodules/omv/modules/py_camera.c b/m5stack/cmodules/omv/modules/py_camera.c index 3e1c24b6..b241f9d5 100644 --- a/m5stack/cmodules/omv/modules/py_camera.c +++ b/m5stack/cmodules/omv/modules/py_camera.c @@ -30,57 +30,54 @@ #define TAG "camera" - - -#if BOARD_ID == 10 // CoreS3 +#if BOARD_ID == 10 // CoreS3 #define CORES3_CAMERA_POWER_DOWN_PIN -1 -#define CORES3_CAMERA_RESET_PIN -1 -#define CORES3_CAMERA_XCLK_PIN 2 -#define CORES3_CAMERA_SDA_PIN 12 -#define CORES3_CAMERA_SCL_PIN 11 -#define CORES3_CAMERA_D7_PIN 47 -#define CORES3_CAMERA_D6_PIN 48 -#define CORES3_CAMERA_D5_PIN 16 -#define CORES3_CAMERA_D4_PIN 15 -#define CORES3_CAMERA_D3_PIN 42 -#define CORES3_CAMERA_D2_PIN 41 -#define CORES3_CAMERA_D1_PIN 40 -#define CORES3_CAMERA_D0_PIN 39 -#define CORES3_CAMERA_VSYNC_PIN 46 -#define CORES3_CAMERA_HREF_PIN 38 -#define CORES3_CAMERA_PCLK_PIN 45 +#define CORES3_CAMERA_RESET_PIN -1 +#define CORES3_CAMERA_XCLK_PIN 2 +#define CORES3_CAMERA_SDA_PIN 12 +#define CORES3_CAMERA_SCL_PIN 11 +#define CORES3_CAMERA_D7_PIN 47 +#define CORES3_CAMERA_D6_PIN 48 +#define CORES3_CAMERA_D5_PIN 16 +#define CORES3_CAMERA_D4_PIN 15 +#define CORES3_CAMERA_D3_PIN 42 +#define CORES3_CAMERA_D2_PIN 41 +#define CORES3_CAMERA_D1_PIN 40 +#define CORES3_CAMERA_D0_PIN 39 +#define CORES3_CAMERA_VSYNC_PIN 46 +#define CORES3_CAMERA_HREF_PIN 38 +#define CORES3_CAMERA_PCLK_PIN 45 static camera_config_t camera_config = { - .pin_pwdn = CORES3_CAMERA_POWER_DOWN_PIN, - .pin_reset = CORES3_CAMERA_RESET_PIN, - .pin_xclk = CORES3_CAMERA_XCLK_PIN, - .pin_sscb_sda = -1,// CORES3_CAMERA_SDA_PIN, // 共用 I2C1 在其他地方初始化 - .pin_sscb_scl = -1,// CORES3_CAMERA_SCL_PIN, - .pin_d7 = CORES3_CAMERA_D7_PIN, - .pin_d6 = CORES3_CAMERA_D6_PIN, - .pin_d5 = CORES3_CAMERA_D5_PIN, - .pin_d4 = CORES3_CAMERA_D4_PIN, - .pin_d3 = CORES3_CAMERA_D3_PIN, - .pin_d2 = CORES3_CAMERA_D2_PIN, - .pin_d1 = CORES3_CAMERA_D1_PIN, - .pin_d0 = CORES3_CAMERA_D0_PIN, - .pin_vsync = CORES3_CAMERA_VSYNC_PIN, - .pin_href = CORES3_CAMERA_HREF_PIN, - .pin_pclk = CORES3_CAMERA_PCLK_PIN, - .xclk_freq_hz = 20000000, - .ledc_timer = LEDC_TIMER_0, - .ledc_channel = LEDC_CHANNEL_0, - .pixel_format = PIXFORMAT_RGB565, - .frame_size = FRAMESIZE_QVGA, - .fb_count = 2, - .fb_location = CAMERA_FB_IN_PSRAM, - .grab_mode = CAMERA_GRAB_LATEST, - .sccb_i2c_port = 1, // use I2C1 + .pin_pwdn = CORES3_CAMERA_POWER_DOWN_PIN, + .pin_reset = CORES3_CAMERA_RESET_PIN, + .pin_xclk = CORES3_CAMERA_XCLK_PIN, + .pin_sscb_sda = -1, // CORES3_CAMERA_SDA_PIN, // 共用 I2C1 在其他地方初始化 + .pin_sscb_scl = -1, // CORES3_CAMERA_SCL_PIN, + .pin_d7 = CORES3_CAMERA_D7_PIN, + .pin_d6 = CORES3_CAMERA_D6_PIN, + .pin_d5 = CORES3_CAMERA_D5_PIN, + .pin_d4 = CORES3_CAMERA_D4_PIN, + .pin_d3 = CORES3_CAMERA_D3_PIN, + .pin_d2 = CORES3_CAMERA_D2_PIN, + .pin_d1 = CORES3_CAMERA_D1_PIN, + .pin_d0 = CORES3_CAMERA_D0_PIN, + .pin_vsync = CORES3_CAMERA_VSYNC_PIN, + .pin_href = CORES3_CAMERA_HREF_PIN, + .pin_pclk = CORES3_CAMERA_PCLK_PIN, + .xclk_freq_hz = 20000000, + .ledc_timer = LEDC_TIMER_0, + .ledc_channel = LEDC_CHANNEL_0, + .pixel_format = PIXFORMAT_RGB565, + .frame_size = FRAMESIZE_QVGA, + .fb_count = 2, + .fb_location = CAMERA_FB_IN_PSRAM, + .grab_mode = CAMERA_GRAB_LATEST, + .sccb_i2c_port = 1, // use I2C1 }; - -#elif BOARD_ID == 144 // AtomS3R_CAM +#elif BOARD_ID == 144 // AtomS3R_CAM #define ATOMS3R_CAM_PIN_PWDN -1 #define ATOMS3R_CAM_PIN_RESET -1 @@ -101,62 +98,58 @@ static camera_config_t camera_config = { #define ATOMS3R_CAM_PIN_EN 18 // 电源控制 camera_config_t camera_config = { - .pin_pwdn = ATOMS3R_CAM_PIN_PWDN, - .pin_reset = ATOMS3R_CAM_PIN_RESET, + .pin_pwdn = ATOMS3R_CAM_PIN_PWDN, + .pin_reset = ATOMS3R_CAM_PIN_RESET, .pin_sccb_scl = ATOMS3R_CAM_PIN_SIOC, .pin_sccb_sda = ATOMS3R_CAM_PIN_SIOD, - .pin_d0 = ATOMS3R_CAM_PIN_D0, - .pin_d1 = ATOMS3R_CAM_PIN_D1, - .pin_d2 = ATOMS3R_CAM_PIN_D2, - .pin_d3 = ATOMS3R_CAM_PIN_D3, - .pin_d4 = ATOMS3R_CAM_PIN_D4, - .pin_d5 = ATOMS3R_CAM_PIN_D5, - .pin_d6 = ATOMS3R_CAM_PIN_D6, - .pin_d7 = ATOMS3R_CAM_PIN_D7, - .pin_vsync = ATOMS3R_CAM_PIN_VSYNC, - .pin_href = ATOMS3R_CAM_PIN_HREF, - .pin_pclk = ATOMS3R_CAM_PIN_PCLK, - .pin_xclk = ATOMS3R_CAM_PIN_XCLK, + .pin_d0 = ATOMS3R_CAM_PIN_D0, + .pin_d1 = ATOMS3R_CAM_PIN_D1, + .pin_d2 = ATOMS3R_CAM_PIN_D2, + .pin_d3 = ATOMS3R_CAM_PIN_D3, + .pin_d4 = ATOMS3R_CAM_PIN_D4, + .pin_d5 = ATOMS3R_CAM_PIN_D5, + .pin_d6 = ATOMS3R_CAM_PIN_D6, + .pin_d7 = ATOMS3R_CAM_PIN_D7, + .pin_vsync = ATOMS3R_CAM_PIN_VSYNC, + .pin_href = ATOMS3R_CAM_PIN_HREF, + .pin_pclk = ATOMS3R_CAM_PIN_PCLK, + .pin_xclk = ATOMS3R_CAM_PIN_XCLK, .xclk_freq_hz = 20000000, - .ledc_timer = LEDC_TIMER_0, + .ledc_timer = LEDC_TIMER_0, .ledc_channel = LEDC_CHANNEL_0, .pixel_format = PIXFORMAT_RGB565, - .frame_size = FRAMESIZE_QVGA, + .frame_size = FRAMESIZE_QVGA, .jpeg_quality = 6, - .fb_count = 2, - .fb_location = CAMERA_FB_IN_PSRAM, - .grab_mode = CAMERA_GRAB_WHEN_EMPTY, + .fb_count = 2, + .fb_location = CAMERA_FB_IN_PSRAM, + .grab_mode = CAMERA_GRAB_WHEN_EMPTY, }; #endif - typedef struct { bool hmirror; bool vflip; } cam_config_t; cam_config_t g_cam_config; -static enum { - E_CAMERA_INIT, - E_CAMERA_DEINIT -} status = E_CAMERA_DEINIT; - +static enum { E_CAMERA_INIT, E_CAMERA_DEINIT } status = E_CAMERA_DEINIT; -static bool camera_init_helper(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { - enum {ARG_pixformat, ARG_framesize, ARG_fb_count, ARG_fb_location}; +static bool camera_init_helper(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) +{ + enum { ARG_pixformat, ARG_framesize, ARG_fb_count, ARG_fb_location }; /* *FORMAT-OFF* */ const mp_arg_t allowed_args[] = { - { MP_QSTR_pixformat, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = PIXFORMAT_RGB565 } }, - { MP_QSTR_framesize, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = FRAMESIZE_QVGA } }, - { MP_QSTR_fb_count, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = 2 } }, - { MP_QSTR_fb_location, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = CAMERA_FB_IN_PSRAM } }, + {MP_QSTR_pixformat, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = PIXFORMAT_RGB565}}, + {MP_QSTR_framesize, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = FRAMESIZE_QVGA}}, + {MP_QSTR_fb_count, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = 2}}, + {MP_QSTR_fb_location, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = CAMERA_FB_IN_PSRAM}}, }; #if BOARD_ID == 144 gpio_reset_pin(ATOMS3R_CAM_PIN_EN); gpio_set_direction(ATOMS3R_CAM_PIN_EN, GPIO_MODE_OUTPUT); - gpio_set_level(ATOMS3R_CAM_PIN_EN, 0); // 拉低开启电源 + gpio_set_level(ATOMS3R_CAM_PIN_EN, 0); // 拉低开启电源 vTaskDelay(pdMS_TO_TICKS(300)); #endif @@ -165,13 +158,13 @@ static bool camera_init_helper(size_t n_args, const mp_obj_t *pos_args, mp_map_t mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); int format = args[ARG_pixformat].u_int; - if ((format < 0) || (format > 1)) { + if ((format < 0) || (format > PIXFORMAT_RGB555)) { mp_raise_ValueError(MP_ERROR_TEXT("Pixelformat is not valid")); } camera_config.pixel_format = format; int size = args[ARG_framesize].u_int; - if ((size < 0) || (size > 8)) { + if ((size < 0) || (size > FRAMESIZE_QXGA)) { mp_raise_ValueError(MP_ERROR_TEXT("Image framesize is not valid")); } camera_config.frame_size = size; @@ -200,10 +193,11 @@ static bool camera_init_helper(size_t n_args, const mp_obj_t *pos_args, mp_map_t return true; } -static mp_obj_t camera_init(size_t n_pos_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { - bool init = camera_init_helper(n_pos_args, pos_args, kw_args); +static mp_obj_t camera_init(size_t n_pos_args, const mp_obj_t *pos_args, mp_map_t *kw_args) +{ + bool init = camera_init_helper(n_pos_args, pos_args, kw_args); g_cam_config.hmirror = true; - g_cam_config.vflip = false; + g_cam_config.vflip = false; if (init) { return mp_const_true; } else { @@ -213,7 +207,8 @@ static mp_obj_t camera_init(size_t n_pos_args, const mp_obj_t *pos_args, mp_map_ } static MP_DEFINE_CONST_FUN_OBJ_KW(camera_init_obj, 0, camera_init); -static mp_obj_t camera_deinit() { +static mp_obj_t camera_deinit() +{ esp_err_t err = esp_camera_deinit(); if (err != ESP_OK) { ESP_LOGE(TAG, "Camera deinit Failed"); @@ -224,9 +219,10 @@ static mp_obj_t camera_deinit() { } static MP_DEFINE_CONST_FUN_OBJ_0(camera_deinit_obj, camera_deinit); -static mp_obj_t camera_skip_frames(size_t n_args, const mp_obj_t *args, mp_map_t *kw_args) { +static mp_obj_t camera_skip_frames(size_t n_args, const mp_obj_t *args, mp_map_t *kw_args) +{ mp_map_elem_t *kw_arg = mp_map_lookup(kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_time), MP_MAP_LOOKUP); - mp_int_t time = 300; // OV Recommended. + mp_int_t time = 300; // OV Recommended. if (kw_arg != NULL) { time = mp_obj_get_int(kw_arg->value); @@ -235,7 +231,7 @@ static mp_obj_t camera_skip_frames(size_t n_args, const mp_obj_t *args, mp_map_t uint32_t millis = mp_hal_ticks_us() / 1000; if (!n_args) { - while ((mp_hal_ticks_us() / 1000 - millis) < time) { // 32-bit math handles wrap around... + while ((mp_hal_ticks_us() / 1000 - millis) < time) { // 32-bit math handles wrap around... camera_fb_t *fb = esp_camera_fb_get(); if (fb == NULL) { continue; @@ -260,7 +256,8 @@ static mp_obj_t camera_skip_frames(size_t n_args, const mp_obj_t *args, mp_map_t } static MP_DEFINE_CONST_FUN_OBJ_KW(camera_skip_frames_obj, 0, camera_skip_frames); -static mp_obj_t camera_capture() { +static mp_obj_t camera_capture() +{ // acquire a frame camera_fb_t *fb = esp_camera_fb_get(); if (!fb) { @@ -274,7 +271,8 @@ static mp_obj_t camera_capture() { } static MP_DEFINE_CONST_FUN_OBJ_0(camera_capture_obj, camera_capture); -static mp_obj_t camera_capture_to_jpg(mp_obj_t quality_in) { +static mp_obj_t camera_capture_to_jpg(mp_obj_t quality_in) +{ // acquire a frame camera_fb_t *fb = esp_camera_fb_get(); if (!fb) { @@ -287,8 +285,8 @@ static mp_obj_t camera_capture_to_jpg(mp_obj_t quality_in) { image = mp_obj_new_bytes(fb->buf, fb->len); } else { uint8_t quality = mp_obj_get_int(quality_in); - uint8_t *out = NULL; - size_t out_len = 0; + uint8_t *out = NULL; + size_t out_len = 0; if (frame2jpg(fb, quality, &out, &out_len)) { image = mp_obj_new_bytes(out, out_len); free(out); @@ -300,7 +298,8 @@ static mp_obj_t camera_capture_to_jpg(mp_obj_t quality_in) { } static MP_DEFINE_CONST_FUN_OBJ_1(camera_capture_to_jpg_obj, camera_capture_to_jpg); -static mp_obj_t camera_capture_to_bmp() { +static mp_obj_t camera_capture_to_bmp() +{ // acquire a frame camera_fb_t *fb = esp_camera_fb_get(); if (!fb) { @@ -309,7 +308,7 @@ static mp_obj_t camera_capture_to_bmp() { } mp_obj_t image = mp_const_none; - uint8_t *out = NULL; + uint8_t *out = NULL; size_t out_len = 0; if (frame2bmp(fb, &out, &out_len)) { image = mp_obj_new_bytes(out, out_len); @@ -321,11 +320,12 @@ static mp_obj_t camera_capture_to_bmp() { } static MP_DEFINE_CONST_FUN_OBJ_0(camera_capture_to_bmp_obj, camera_capture_to_bmp); -static mp_obj_t camera_pixformat(mp_obj_t pixformat) { +static mp_obj_t camera_pixformat(mp_obj_t pixformat) +{ int format = mp_obj_get_int(pixformat); - if ((format < 0) || (format > 1)) { - mp_raise_ValueError(MP_ERROR_TEXT("Pixelformat is not valid")); - } + // if ((format < 0) || (format > 1)) { + // mp_raise_ValueError(MP_ERROR_TEXT("Pixelformat is not valid")); + // } sensor_t *s = esp_camera_sensor_get(); if (!s) { @@ -342,7 +342,8 @@ static mp_obj_t camera_pixformat(mp_obj_t pixformat) { } static MP_DEFINE_CONST_FUN_OBJ_1(camera_pixformat_obj, camera_pixformat); -static mp_obj_t camera_framesize(mp_obj_t framesize) { +static mp_obj_t camera_framesize(mp_obj_t framesize) +{ int size = mp_obj_get_int(framesize); if ((size < 0) || (size > 8)) { mp_raise_ValueError(MP_ERROR_TEXT("Image framesize is not valid")); @@ -363,14 +364,15 @@ static mp_obj_t camera_framesize(mp_obj_t framesize) { } static MP_DEFINE_CONST_FUN_OBJ_1(camera_framesize_obj, camera_framesize); -static mp_obj_t camera_contrast(mp_obj_t contrast) { +static mp_obj_t camera_contrast(mp_obj_t contrast) +{ sensor_t *s = esp_camera_sensor_get(); if (!s) { ESP_LOGE(TAG, "Contrast Failed"); return mp_const_false; } - int val = mp_obj_get_int(contrast); // -2,2 (default 0). 2 highcontrast + int val = mp_obj_get_int(contrast); // -2,2 (default 0). 2 highcontrast int ret = s->set_contrast(s, val); if (ret == 0) { return mp_const_true; @@ -380,14 +382,15 @@ static mp_obj_t camera_contrast(mp_obj_t contrast) { } static MP_DEFINE_CONST_FUN_OBJ_1(camera_contrast_obj, camera_contrast); -static mp_obj_t camera_global_gain(mp_obj_t gain_level) { +static mp_obj_t camera_global_gain(mp_obj_t gain_level) +{ sensor_t *s = esp_camera_sensor_get(); if (!s) { ESP_LOGE(TAG, "Contrast Failed"); return mp_const_false; } - int val = mp_obj_get_int(gain_level); // -2,2 (default 0). 2 highcontrast + int val = mp_obj_get_int(gain_level); // -2,2 (default 0). 2 highcontrast int ret = s->set_gain_ctrl(s, val); if (ret == 0) { return mp_const_true; @@ -397,7 +400,8 @@ static mp_obj_t camera_global_gain(mp_obj_t gain_level) { } static MP_DEFINE_CONST_FUN_OBJ_1(camera_global_gain_obj, camera_global_gain); -static mp_obj_t camera_hmirror(mp_obj_t direction) { +static mp_obj_t camera_hmirror(mp_obj_t direction) +{ sensor_t *s = esp_camera_sensor_get(); if (!s) { ESP_LOGE(TAG, "Mirroring Failed"); @@ -413,7 +417,8 @@ static mp_obj_t camera_hmirror(mp_obj_t direction) { } static MP_DEFINE_CONST_FUN_OBJ_1(camera_hmirror_obj, camera_hmirror); -static mp_obj_t camera_vflip(mp_obj_t direction) { +static mp_obj_t camera_vflip(mp_obj_t direction) +{ sensor_t *s = esp_camera_sensor_get(); if (!s) { ESP_LOGE(TAG, "Flipping Failed"); @@ -429,7 +434,8 @@ static mp_obj_t camera_vflip(mp_obj_t direction) { } static MP_DEFINE_CONST_FUN_OBJ_1(camera_vflip_obj, camera_vflip); -static mp_obj_t camera_colorbar(mp_obj_t enable) { +static mp_obj_t camera_colorbar(mp_obj_t enable) +{ sensor_t *s = esp_camera_sensor_get(); if (!s) { ESP_LOGE(TAG, "Colorbar Failed"); @@ -454,18 +460,21 @@ static MP_DEFINE_CONST_FUN_OBJ_1(camera_colorbar_obj, camera_colorbar); static camera_fb_t *g_frame = NULL; -void swap_rgb565(uint16_t *pixel) { +void swap_rgb565(uint16_t *pixel) +{ *pixel = (*pixel >> 8) | (*pixel << 8); } -void image_endian_swap(image_t *img) { +void image_endian_swap(image_t *img) +{ uint16_t *pixel = (uint16_t *)img->data; for (int i = 0; i < img->w * img->h; i++) { swap_rgb565(&pixel[i]); } } -static mp_obj_t py_camera_snapshot() { +static mp_obj_t py_camera_snapshot() +{ if (g_frame != NULL) { esp_camera_fb_return(g_frame); g_frame = NULL; @@ -474,12 +483,14 @@ static mp_obj_t py_camera_snapshot() { image_t img; img.size = g_frame->len; - img.w = g_frame->width; - img.h = g_frame->height; + img.w = g_frame->width; + img.h = g_frame->height; if (g_frame->format == PIXFORMAT_RGB565) { img.pixfmt = OMV_PIXFORMAT_RGB565; } else if (g_frame->format == PIXFORMAT_GRAYSCALE) { img.pixfmt = OMV_PIXFORMAT_GRAYSCALE; + } else if (g_frame->format == PIXFORMAT_JPEG) { + img.pixfmt = OMV_PIXFORMAT_JPEG; } img.data = g_frame->buf; // image_endian_swap(&img); @@ -488,86 +499,104 @@ static mp_obj_t py_camera_snapshot() { } static MP_DEFINE_CONST_FUN_OBJ_0(py_camera_snapshot_obj, py_camera_snapshot); -static mp_obj_t py_camera_set_hmirror(mp_obj_t enable) { - sensor_t *s = esp_camera_sensor_get(); +static mp_obj_t py_camera_set_hmirror(mp_obj_t enable) +{ + sensor_t *s = esp_camera_sensor_get(); g_cam_config.hmirror = mp_obj_is_true(enable); s->set_hmirror(s, g_cam_config.hmirror); return mp_const_none; } static MP_DEFINE_CONST_FUN_OBJ_1(py_camera_set_hmirror_obj, py_camera_set_hmirror); -static mp_obj_t py_camera_set_vflip(mp_obj_t enable) { - sensor_t *s = esp_camera_sensor_get(); +static mp_obj_t py_camera_set_vflip(mp_obj_t enable) +{ + sensor_t *s = esp_camera_sensor_get(); g_cam_config.vflip = mp_obj_is_true(enable); s->set_vflip(s, g_cam_config.vflip); return mp_const_none; } static MP_DEFINE_CONST_FUN_OBJ_1(py_camera_set_vflip_obj, py_camera_set_vflip); -static mp_obj_t py_camera_get_hmirror(void) { +static mp_obj_t py_camera_get_hmirror(void) +{ return mp_obj_new_bool(g_cam_config.hmirror); } static MP_DEFINE_CONST_FUN_OBJ_0(py_camera_get_hmirror_obj, py_camera_get_hmirror); -static mp_obj_t py_camera_get_vflip(void) { +static mp_obj_t py_camera_get_vflip(void) +{ return mp_obj_new_bool(g_cam_config.vflip); } static MP_DEFINE_CONST_FUN_OBJ_0(py_camera_get_vflip_obj, py_camera_get_vflip); - - static const mp_rom_map_elem_t camera_globals_dict_table[] = { - { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_camera) }, + {MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_camera)}, // functions - { MP_ROM_QSTR(MP_QSTR_init), MP_ROM_PTR(&camera_init_obj) }, - { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&camera_deinit_obj) }, - { MP_ROM_QSTR(MP_QSTR_skip_frames), MP_ROM_PTR(&camera_skip_frames_obj) }, - { MP_ROM_QSTR(MP_QSTR_capture), MP_ROM_PTR(&camera_capture_obj) }, - { MP_ROM_QSTR(MP_QSTR_capture_to_jpg), MP_ROM_PTR(&camera_capture_to_jpg_obj) }, - { MP_ROM_QSTR(MP_QSTR_capture_to_bmp), MP_ROM_PTR(&camera_capture_to_bmp_obj) }, - { MP_ROM_QSTR(MP_QSTR_pixformat), MP_ROM_PTR(&camera_pixformat_obj) }, - { MP_ROM_QSTR(MP_QSTR_framesize), MP_ROM_PTR(&camera_framesize_obj) }, - { MP_ROM_QSTR(MP_QSTR_contrast), MP_ROM_PTR(&camera_contrast_obj) }, - { MP_ROM_QSTR(MP_QSTR_global_gain), MP_ROM_PTR(&camera_global_gain_obj) }, - { MP_ROM_QSTR(MP_QSTR_hmirror), MP_ROM_PTR(&camera_hmirror_obj) }, - { MP_ROM_QSTR(MP_QSTR_vflip), MP_ROM_PTR(&camera_vflip_obj) }, - { MP_ROM_QSTR(MP_QSTR_colorbar), MP_ROM_PTR(&camera_colorbar_obj) }, + {MP_ROM_QSTR(MP_QSTR_init), MP_ROM_PTR(&camera_init_obj)}, + {MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&camera_deinit_obj)}, + {MP_ROM_QSTR(MP_QSTR_skip_frames), MP_ROM_PTR(&camera_skip_frames_obj)}, + {MP_ROM_QSTR(MP_QSTR_capture), MP_ROM_PTR(&camera_capture_obj)}, + {MP_ROM_QSTR(MP_QSTR_capture_to_jpg), MP_ROM_PTR(&camera_capture_to_jpg_obj)}, + {MP_ROM_QSTR(MP_QSTR_capture_to_bmp), MP_ROM_PTR(&camera_capture_to_bmp_obj)}, + {MP_ROM_QSTR(MP_QSTR_pixformat), MP_ROM_PTR(&camera_pixformat_obj)}, + {MP_ROM_QSTR(MP_QSTR_framesize), MP_ROM_PTR(&camera_framesize_obj)}, + {MP_ROM_QSTR(MP_QSTR_contrast), MP_ROM_PTR(&camera_contrast_obj)}, + {MP_ROM_QSTR(MP_QSTR_global_gain), MP_ROM_PTR(&camera_global_gain_obj)}, + {MP_ROM_QSTR(MP_QSTR_hmirror), MP_ROM_PTR(&camera_hmirror_obj)}, + {MP_ROM_QSTR(MP_QSTR_vflip), MP_ROM_PTR(&camera_vflip_obj)}, + {MP_ROM_QSTR(MP_QSTR_colorbar), MP_ROM_PTR(&camera_colorbar_obj)}, // output format - { MP_ROM_QSTR(MP_QSTR_YUV422), MP_ROM_INT(PIXFORMAT_YUV422) }, - { MP_ROM_QSTR(MP_QSTR_GRAYSCALE), MP_ROM_INT(PIXFORMAT_GRAYSCALE) }, - { MP_ROM_QSTR(MP_QSTR_RGB565), MP_ROM_INT(PIXFORMAT_RGB565) }, + {MP_ROM_QSTR(MP_QSTR_YUV422), MP_ROM_INT(PIXFORMAT_YUV422)}, + {MP_ROM_QSTR(MP_QSTR_GRAYSCALE), MP_ROM_INT(PIXFORMAT_GRAYSCALE)}, + {MP_ROM_QSTR(MP_QSTR_RGB565), MP_ROM_INT(PIXFORMAT_RGB565)}, + {MP_ROM_QSTR(MP_QSTR_JPEG), MP_ROM_INT(PIXFORMAT_JPEG)}, // resolution - { MP_ROM_QSTR(MP_QSTR_FRAME_96X96), MP_ROM_INT(FRAMESIZE_96X96) }, - { MP_ROM_QSTR(MP_QSTR_FRAME_QQVGA), MP_ROM_INT(FRAMESIZE_QQVGA) }, - { MP_ROM_QSTR(MP_QSTR_FRAME_QCIF), MP_ROM_INT(FRAMESIZE_QCIF) }, - { MP_ROM_QSTR(MP_QSTR_FRAME_HQVGA), MP_ROM_INT(FRAMESIZE_HQVGA) }, - { MP_ROM_QSTR(MP_QSTR_FRAME_240X240), MP_ROM_INT(FRAMESIZE_240X240) }, - { MP_ROM_QSTR(MP_QSTR_FRAME_QVGA), MP_ROM_INT(FRAMESIZE_QVGA) }, - { MP_ROM_QSTR(MP_QSTR_FRAME_CIF), MP_ROM_INT(FRAMESIZE_CIF) }, - { MP_ROM_QSTR(MP_QSTR_FRAME_HVGA), MP_ROM_INT(FRAMESIZE_HVGA) }, - { MP_ROM_QSTR(MP_QSTR_FRAME_VGA), MP_ROM_INT(FRAMESIZE_VGA) }, + {MP_ROM_QSTR(MP_QSTR_FRAME_96X96), MP_ROM_INT(FRAMESIZE_96X96)}, + {MP_ROM_QSTR(MP_QSTR_FRAME_QQVGA), MP_ROM_INT(FRAMESIZE_QQVGA)}, + {MP_ROM_QSTR(MP_QSTR_FRAME_QCIF), MP_ROM_INT(FRAMESIZE_QCIF)}, + {MP_ROM_QSTR(MP_QSTR_FRAME_HQVGA), MP_ROM_INT(FRAMESIZE_HQVGA)}, + {MP_ROM_QSTR(MP_QSTR_FRAME_240X240), MP_ROM_INT(FRAMESIZE_240X240)}, + {MP_ROM_QSTR(MP_QSTR_FRAME_QVGA), MP_ROM_INT(FRAMESIZE_QVGA)}, + {MP_ROM_QSTR(MP_QSTR_FRAME_CIF), MP_ROM_INT(FRAMESIZE_CIF)}, + {MP_ROM_QSTR(MP_QSTR_FRAME_HVGA), MP_ROM_INT(FRAMESIZE_HVGA)}, + {MP_ROM_QSTR(MP_QSTR_FRAME_VGA), MP_ROM_INT(FRAMESIZE_VGA)}, + {MP_ROM_QSTR(MP_QSTR_FRAME_QVGA), MP_ROM_INT(FRAMESIZE_SVGA)}, + {MP_ROM_QSTR(MP_QSTR_FRAME_CIF), MP_ROM_INT(FRAMESIZE_XGA)}, + {MP_ROM_QSTR(MP_QSTR_FRAME_HVGA), MP_ROM_INT(FRAMESIZE_HD)}, + {MP_ROM_QSTR(MP_QSTR_FRAME_VGA), MP_ROM_INT(FRAMESIZE_SXGA)}, + {MP_ROM_QSTR(MP_QSTR_FRAME_QVGA), MP_ROM_INT(FRAMESIZE_UXGA)}, + {MP_ROM_QSTR(MP_QSTR_FRAME_CIF), MP_ROM_INT(FRAMESIZE_FHD)}, + {MP_ROM_QSTR(MP_QSTR_FRAME_QXGA), MP_ROM_INT(FRAMESIZE_QXGA)}, // - { MP_ROM_QSTR(MP_QSTR_DRAM), MP_ROM_INT(CAMERA_FB_IN_DRAM) }, - { MP_ROM_QSTR(MP_QSTR_PSRAM), MP_ROM_INT(CAMERA_FB_IN_PSRAM) }, + {MP_ROM_QSTR(MP_QSTR_DRAM), MP_ROM_INT(CAMERA_FB_IN_DRAM)}, + {MP_ROM_QSTR(MP_QSTR_PSRAM), MP_ROM_INT(CAMERA_FB_IN_PSRAM)}, // for omv - { MP_ROM_QSTR(MP_QSTR_snapshot), MP_ROM_PTR(&py_camera_snapshot_obj) }, - { MP_ROM_QSTR(MP_QSTR_set_hmirror), MP_ROM_PTR(&py_camera_set_hmirror_obj) }, - { MP_ROM_QSTR(MP_QSTR_set_vflip), MP_ROM_PTR(&py_camera_set_vflip_obj) }, - { MP_ROM_QSTR(MP_QSTR_get_hmirror), MP_ROM_PTR(&py_camera_get_hmirror_obj) }, - { MP_ROM_QSTR(MP_QSTR_get_vflip), MP_ROM_PTR(&py_camera_get_vflip_obj) }, - { MP_ROM_QSTR(MP_QSTR_QQVGA), MP_ROM_INT(FRAMESIZE_QQVGA) }, - { MP_ROM_QSTR(MP_QSTR_QCIF), MP_ROM_INT(FRAMESIZE_QCIF) }, - { MP_ROM_QSTR(MP_QSTR_HQVGA), MP_ROM_INT(FRAMESIZE_HQVGA) }, - { MP_ROM_QSTR(MP_QSTR_240X240), MP_ROM_INT(FRAMESIZE_240X240) }, - { MP_ROM_QSTR(MP_QSTR_QVGA), MP_ROM_INT(FRAMESIZE_QVGA) }, + {MP_ROM_QSTR(MP_QSTR_snapshot), MP_ROM_PTR(&py_camera_snapshot_obj)}, + {MP_ROM_QSTR(MP_QSTR_set_hmirror), MP_ROM_PTR(&py_camera_set_hmirror_obj)}, + {MP_ROM_QSTR(MP_QSTR_set_vflip), MP_ROM_PTR(&py_camera_set_vflip_obj)}, + {MP_ROM_QSTR(MP_QSTR_get_hmirror), MP_ROM_PTR(&py_camera_get_hmirror_obj)}, + {MP_ROM_QSTR(MP_QSTR_get_vflip), MP_ROM_PTR(&py_camera_get_vflip_obj)}, + {MP_ROM_QSTR(MP_QSTR_QQVGA), MP_ROM_INT(FRAMESIZE_QQVGA)}, // 160x120 + {MP_ROM_QSTR(MP_QSTR_QCIF), MP_ROM_INT(FRAMESIZE_QCIF)}, // 176x144 + {MP_ROM_QSTR(MP_QSTR_HQVGA), MP_ROM_INT(FRAMESIZE_HQVGA)}, // 240x176 + {MP_ROM_QSTR(MP_QSTR_240X240), MP_ROM_INT(FRAMESIZE_240X240)}, // 240x240 + {MP_ROM_QSTR(MP_QSTR_QVGA), MP_ROM_INT(FRAMESIZE_QVGA)}, // 320x240 + {MP_ROM_QSTR(MP_QSTR_VGA), MP_ROM_INT(FRAMESIZE_VGA)}, // 640x480 + {MP_ROM_QSTR(MP_QSTR_SVGA), MP_ROM_INT(FRAMESIZE_SVGA)}, // 800x600 + {MP_ROM_QSTR(MP_QSTR_XGA), MP_ROM_INT(FRAMESIZE_XGA)}, // 1024x768 + {MP_ROM_QSTR(MP_QSTR_HD), MP_ROM_INT(FRAMESIZE_HD)}, // 1280x720 + {MP_ROM_QSTR(MP_QSTR_SXGA), MP_ROM_INT(FRAMESIZE_SXGA)}, // 1280x1024 + {MP_ROM_QSTR(MP_QSTR_UXGA), MP_ROM_INT(FRAMESIZE_UXGA)}, // 1600x1200 + {MP_ROM_QSTR(MP_QSTR_FHD), MP_ROM_INT(FRAMESIZE_FHD)}, // 1920x1080 + {MP_ROM_QSTR(MP_QSTR_QXGA), MP_ROM_INT(FRAMESIZE_QXGA)}, // 2048x1536 }; static MP_DEFINE_CONST_DICT(camera_globals_dict, camera_globals_dict_table); // Define module object. const mp_obj_module_t py_module_camera = { - .base = { &mp_type_module }, + .base = {&mp_type_module}, .globals = (mp_obj_dict_t *)&camera_globals_dict, }; -MP_REGISTER_MODULE(MP_QSTR_camera, py_module_camera); +MP_REGISTER_MODULE(MP_QSTR_camera, py_module_camera); \ No newline at end of file From 91e3ef4abc267cb403473e625fdfd0370978f75c Mon Sep 17 00:00:00 2001 From: luoweiyuan Date: Thu, 15 May 2025 15:09:42 +0800 Subject: [PATCH 082/322] docs: Add Unit NCIR2 docs. Signed-off-by: luoweiyuan --- docs/en/refs/unit.ncir2.ref | 45 ++ docs/en/units/hbridge.rst | 2 +- docs/en/units/index.rst | 3 +- docs/en/units/ncir2.rst | 524 +++++++++++++ docs/locales/zh_CN/LC_MESSAGES/units/ncir2.po | 692 ++++++++++++++++++ .../ncir2/m5cores3_ncir2_base_example.m5f2 | 1 + .../unit/ncir2/m5cores3_ncir2_base_example.py | 64 ++ 7 files changed, 1329 insertions(+), 2 deletions(-) create mode 100644 docs/en/refs/unit.ncir2.ref create mode 100644 docs/en/units/ncir2.rst create mode 100644 docs/locales/zh_CN/LC_MESSAGES/units/ncir2.po create mode 100644 examples/unit/ncir2/m5cores3_ncir2_base_example.m5f2 create mode 100644 examples/unit/ncir2/m5cores3_ncir2_base_example.py diff --git a/docs/en/refs/unit.ncir2.ref b/docs/en/refs/unit.ncir2.ref new file mode 100644 index 00000000..0d3e045c --- /dev/null +++ b/docs/en/refs/unit.ncir2.ref @@ -0,0 +1,45 @@ +.. |Unit NCIR2| image:: https://static-cdn.m5stack.com/resource/docs/products/unit/NCIR2/img-2eb52d27-8e6d-4673-94fd-055628c26a53.webp + :target: https://docs.m5stack.com/zh_CN/unit/NCIR2 + :height: 200px + :width: 200px + + +.. |init.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/ncir2/init.png +.. |get_temperature_value.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/ncir2/get_temperature_value.png +.. |get_emissivity_value.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/ncir2/get_emissivity_value.png +.. |get_temperature_threshold.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/ncir2/get_temperature_threshold.png +.. |get_temp_alarm_led.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/ncir2/get_temp_alarm_led.png +.. |get_temp_buzzer_freq.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/ncir2/get_temp_buzzer_freq.png +.. |get_temp_alarm_interval.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/ncir2/get_temp_alarm_interval.png +.. |get_temp_buzzer_duty.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/ncir2/get_temp_buzzer_duty.png +.. |get_buzzer_freq.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/ncir2/get_buzzer_freq.png +.. |get_buzzer_duty.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/ncir2/get_buzzer_duty.png +.. |get_buzzer_control.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/ncir2/get_buzzer_control.png +.. |get_rgb_led.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/ncir2/get_rgb_led.png +.. |get_button_status.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/ncir2/get_button_status.png +.. |get_chip_temperature.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/ncir2/get_chip_temperature.png +.. |get_device_spec.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/ncir2/get_device_spec.png +.. |set_emissivity_value.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/ncir2/set_emissivity_value.png +.. |set_temperature_threshold.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/ncir2/set_temperature_threshold.png +.. |set_temp_alarm_led.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/ncir2/set_temp_alarm_led.png +.. |set_temp_buzzer_freq.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/ncir2/set_temp_buzzer_freq.png +.. |set_temp_alarm_interval.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/ncir2/set_temp_alarm_interval.png +.. |set_temp_buzzer_duty.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/ncir2/set_temp_buzzer_duty.png +.. |set_buzzer_freq.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/ncir2/set_buzzer_freq.png +.. |set_buzzer_duty.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/ncir2/set_buzzer_duty.png +.. |set_buzzer_control.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/ncir2/set_buzzer_control.png +.. |set_rgb_led.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/ncir2/set_rgb_led.png +.. |save_config_setting.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/ncir2/save_config_setting.png +.. |set_i2c_address.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/ncir2/set_i2c_address.png + +.. |m5cores3_ncir2_base_example.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/ncir2/example.png + +.. |m5cores3_ncir2_base_example.m5f2| raw:: html + + + m5cores3_ncir2_base_example.m5f2 + + \ No newline at end of file diff --git a/docs/en/units/hbridge.rst b/docs/en/units/hbridge.rst index 0089ee1a..0506b857 100644 --- a/docs/en/units/hbridge.rst +++ b/docs/en/units/hbridge.rst @@ -57,7 +57,7 @@ Example output: **API** ------- -HbridgeRUnit +HbridgeUnit ^^^^^^^^^^^^ .. autoclass:: unit.hbridge.HbridgeUnit diff --git a/docs/en/units/index.rst b/docs/en/units/index.rst index 0485be67..470a5e07 100644 --- a/docs/en/units/index.rst +++ b/docs/en/units/index.rst @@ -76,6 +76,7 @@ Unit nbiot.rst nbiot2.rst ncir.rst + ncir2.rst neco.rst oled.rst op90.rst @@ -113,4 +114,4 @@ Unit watering.rst weight.rst weight_i2c.rst - zigbee.rst \ No newline at end of file + zigbee.rst diff --git a/docs/en/units/ncir2.rst b/docs/en/units/ncir2.rst new file mode 100644 index 00000000..25c9bd11 --- /dev/null +++ b/docs/en/units/ncir2.rst @@ -0,0 +1,524 @@ +NCIR2 Unit +========== + +.. sku: U150 + +.. include:: ../refs/unit.ncir2.ref + +This library is the driver for Unit NCIR2. + +Support the following products: + + |Unit NCIR2| + +UiFlow2 Example +--------------- + +Infrared Temperature Display +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Open the |m5cores3_ncir2_base_example.m5f2| project in UiFlow2. + +This example uses the M5Stack CoreS3 board with the NCIR2 infrared temperature sensor to measure temperature in real time and display the current value along with the low and high temperature alarm thresholds on screen. + +UiFlow2 Code Block: + + |m5cores3_ncir2_base_example.png| + +Example output: + + None + +MicroPython Example +------------------- + +Infrared Temperature Display +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +This example uses the M5Stack CoreS3 board with the NCIR2 infrared temperature sensor to measure temperature in real time and display the current value along with the low and high temperature alarm thresholds on screen. + +MicroPython Code Block: + + .. literalinclude:: ../../../examples/unit/ncir2/m5cores3_ncir2_base_example.py + :language: python + :linenos: + +Example output: + + None + + +**API** +------- + +NCIR2Unit +^^^^^^^^^ + +.. class:: unit.hbridge.NCIR2Unit + + Create an NCIR2Unit object. + + :param I2C | PAHUBUnit i2c: I2C port, + :param int address: NCIR2Unit Slave Address, Default is 0x5A. + + UiFlow2 Code Block: + + |init.png| + + MicroPython Code Block: + + .. code-block:: python + + from unit import NCIR2Unit + + unit_ncir2_0 = NCIR2Unit(i2c0, 0x5A) + + .. method:: get_temperature_value() + + Get object temperature. + + :returns: object temperature(unit: ℃) + :rtype: float + + UiFlow2 Code Block: + + |get_temperature_value.png| + + MicroPython Code Block: + + .. code-block:: python + + unit_ncir2_0.get_temperature_value() + + .. method:: get_emissivity_value() + + Get current emissivity. + + :returns: emissivity. + :rtype: float + + UiFlow2 Code Block: + + |get_emissivity_value.png| + + MicroPython Code Block: + + .. code-block:: python + + unit_ncir2_0.get_emissivity_value() + + .. method:: set_emissivity_value(emissive) + + Set the emissivity. + + According to the material being measured; it affects temperature measurement accuracy. + + - A black body has an emissivity of 1.00 (ideal emitter). + - Shiny metals often have low emissivity values (below 0.1). + - Dark, rough surfaces like electrical tape or human skin typically have high emissivity (above 0.95). + + :param: int emissive: The emissivity, range: 0 ~ 1. + + UiFlow2 Code Block: + + |set_emissivity_value.png| + + MicroPython Code Block: + + .. code-block:: python + + unit_ncir2_0.set_emissivity_value(emissive) + + .. method:: get_temperature_threshold(alarm_reg) + + Get temperature alaram threshold. + + :param: int alarm_reg: ALARM_LOW_TEMP_REG: Low temperature alarm threshold register, ALARM_HIGH_TEMP_REG: High temperature alarm threshold register. + :returns: alarm threshold. + :rtype: float + + UiFlow2 Code Block: + + |get_temperature_threshold.png| + + MicroPython Code Block: + + .. code-block:: python + + unit_ncir2_0.get_temperature_threshold(alarm_reg) + + .. method:: set_temperature_threshold(alarm_reg, temp) + + Set temperature alarm threshold. + + :param: int alarm_reg: temperature alarm register, ALARM_LOW_TEMP_REG: Low temperature alarm threshold register, ALARM_HIGH_TEMP_REG: High temperature alarm threshold register. + :param: float temp: alarm threshold. + + UiFlow2 Code Block: + + |set_temperature_threshold.png| + + MicroPython Code Block: + + .. code-block:: python + + unit_ncir2_0.set_temperature_threshold(alarm_reg, temp) + + .. method:: get_temp_alarm_led(alarm_reg) + + Get temperature alaram RGB LED value. + + :param: int alarm_reg: temperature alarm RGB LED register, RGB_LOW_TEMP_REG: Low temperature alarm RGB LED value register, RGB_HIGH_TEMP_REG: High temperature alarm RGB LED value register. + :returns: temperature alarm RGB LED value. + :rtype: list, RGB color list in the format [R, G, B], values from 0 to 255. + + UiFlow2 Code Block: + + |get_temp_alarm_led.png| + + MicroPython Code Block: + + .. code-block:: python + + unit_ncir2_0.get_temp_alarm_led(alarm_reg) + + .. method:: set_temp_alarm_led(alarm_reg, rgb) + + Set temperature alaram RGB LED value. + + :param: int alarm_reg: temperature alarm RGB LED register, RGB_LOW_TEMP_REG: Low temperature alarm RGB LED value register, RGB_HIGH_TEMP_REG: High temperature alarm RGB LED value register. + :param int rgb: RGB color value (24-bit, range: 0 ~ 0xFFFFFF). + + UiFlow2 Code Block: + + |set_temp_alarm_led.png| + + MicroPython Code Block: + + .. code-block:: python + + unit_ncir2_0.set_temp_alarm_led(alarm_reg, rgb) + + .. method:: get_temp_buzzer_freq(alarm_reg) + + Get the buzzer frequency for temperature alarm. + + :param: int alarm_reg: temperature alarm buzzer frequency register, LOW_TEMP_FREQ_REG: Low temperature alarm buzzer frequency register, HIGH_TEMP_FREQ_REG: High temperature alarm buzzer frequency register. + :returns: buzzer frequency. + :rtype: int + + UiFlow2 Code Block: + + |get_temp_buzzer_freq.png| + + MicroPython Code Block: + + .. code-block:: python + + unit_ncir2_0.get_temp_buzzer_freq(alarm_reg) + + .. method:: set_temp_buzzer_freq(alarm_reg, freq) + + Set the buzzer frequency for temperature alarm. + + :param: int alarm_reg: temperature alarm buzzer frequency register, LOW_TEMP_FREQ_REG: Low temperature alarm buzzer frequency register, HIGH_TEMP_FREQ_REG: High temperature alarm buzzer frequency register. + :param: int freq: buzzer frequency, range: 20~20000Hz + + UiFlow2 Code Block: + + |set_temp_buzzer_freq.png| + + MicroPython Code Block: + + .. code-block:: python + + unit_ncir2_0.set_temp_buzzer_freq(alarm_reg, freq) + + + .. method:: get_temp_alarm_interval(alarm_reg) + + Get the buzzer alarm interval. + + :param: int alarm_reg: buzzer alarm interval register, LOW_ALARM_INTER_REG: Low temperature alarm interval register, HIGH_ALARM_INTER_REG: High temperature alarm interval register. + :returns: buzzer alarm interval. (unit: ms) + :rtype: int + + UiFlow2 Code Block: + + |get_temp_alarm_interval.png| + + MicroPython Code Block: + + .. code-block:: python + + unit_ncir2_0.get_temp_alarm_interval(alarm_reg) + + .. method:: set_temp_alarm_interval(alarm_reg, interval) + + Set the buzzer alarm interval. + + :param: int alarm_reg: buzzer alarm interval register, LOW_ALARM_INTER_REG: Low temperature alarm interval register, HIGH_ALARM_INTER_REG: High temperature alarm interval register. + :param: int interval: alarm interval, range: 1 ~ 5000(unit: ms). + + UiFlow2 Code Block: + + |set_temp_alarm_interval.png| + + MicroPython Code Block: + + .. code-block:: python + + unit_ncir2_0.set_temp_alarm_interval(alarm_reg, interval) + + .. method:: get_temp_buzzer_duty(duty_reg) + + Get the duty cycle of the temperature alarm buzzer signal. + + :param int duty_reg: Duty cycle register for the temperature alarm buzzer signal. LOW_ALARM_DUTY_REG: Register for low temperature alarm duty cycle. HIGH_ALARM_DUTY_REG: Register for high temperature alarm duty cycle. + :returns: duty cycle. + :rtype: int + + UiFlow2 Code Block: + + |get_temp_buzzer_duty.png| + + MicroPython Code Block: + + .. code-block:: python + + unit_ncir2_0.get_temp_buzzer_duty(duty_reg) + + .. method:: set_temp_buzzer_duty(duty_reg, duty) + + Set the duty cycle of the temperature alarm buzzer signal. + + :param int duty_reg: Duty cycle register for the temperature alarm buzzer signal. LOW_ALARM_DUTY_REG: Register for low temperature alarm duty cycle. HIGH_ALARM_DUTY_REG: Register for high temperature alarm duty cycle. + :param: int duty: Temperature alarm buzzer signal duty cycle, range: 0 ~ 255. + + UiFlow2 Code Block: + + |set_temp_buzzer_duty.png| + + MicroPython Code Block: + + .. code-block:: python + + unit_ncir2_0.set_temp_buzzer_duty(duty_reg, duty) + + .. method:: get_buzzer_freq() + + Get the frequeny of the buzzer signal. + + :returns: frequeny(Hz) + :rtype: int + + UiFlow2 Code Block: + + |get_buzzer_freq.png| + + MicroPython Code Block: + + .. code-block:: python + + unit_ncir2_0.get_buzzer_freq() + + .. method:: set_buzzer_freq(freq) + + Set the frequeny of the buzzer signal. + + :param: int freq: buzzer signal frequency, range: 20 ~ 20000 (Hz). + + UiFlow2 Code Block: + + |set_buzzer_freq.png| + + MicroPython Code Block: + + .. code-block:: python + + unit_ncir2_0.set_buzzer_freq(freq) + + .. method:: get_buzzer_duty() + + Get the duty cycle of the buzzer signal. + + :returns: Duty cycle + :rtype: int + + UiFlow2 Code Block: + + |get_buzzer_duty.png| + + MicroPython Code Block: + + .. code-block:: python + + unit_ncir2_0.get_buzzer_duty() + + .. method:: set_buzzer_duty(duty) + + Set the duty cycle of the buzzer signal. + + :param: int duty: Duty cycle, range: 0 ~ 255. + + UiFlow2 Code Block: + + |set_buzzer_duty.png| + + MicroPython Code Block: + + .. code-block:: python + + unit_ncir2_0.set_buzzer_duty(duty) + + .. method:: get_buzzer_control() + + Get the buzzer control status + + :returns: Returns the current buzzer control status + :rtype: int + + UiFlow2 Code Block: + + |get_buzzer_control.png| + + MicroPython Code Block: + + .. code-block:: python + + unit_ncir2_0.get_buzzer_control() + + .. method:: set_buzzer_control(ctrl) + + Set the buzzer control status + + :param: int ctrl: Control value, 0 to turn off the buzzer, 1 to turn on the buzzer + + UiFlow2 Code Block: + + |set_buzzer_control.png| + + MicroPython Code Block: + + .. code-block:: python + + unit_ncir2_0.set_buzzer_control(ctrl) + + + .. method:: get_rgb_led() + + Get the current RGB LED value + + :returns: The current RGB LED values in the format [r, g, b] + :rtype: list + + UiFlow2 Code Block: + + |get_rgb_led.png| + + MicroPython Code Block: + + .. code-block:: python + + unit_ncir2_0.get_rgb_led() + + .. method:: set_rgb_led(rgb) + + Set the RGB LED value + + :param: int rgb: RGB value in the range of 0 ~ 0xFFFFFF + + UiFlow2 Code Block: + + |set_rgb_led.png| + + MicroPython Code Block: + + .. code-block:: python + + unit_ncir2_0.set_rgb_led(rgb) + + + .. method:: get_button_status() + + Get the button status + + :returns: Button status, either 0 (not pressed) or 1 (pressed) + :rtype: bool + + UiFlow2 Code Block: + + |get_button_status.png| + + MicroPython Code Block: + + .. code-block:: python + + unit_ncir2_0.get_button_status() + + .. method:: save_config_setting() + + Save configuration settings + + UiFlow2 Code Block: + + |save_config_setting.png| + + MicroPython Code Block: + + .. code-block:: python + + unit_ncir2_0.save_config_setting() + + .. method:: get_chip_temperature() + + Get the chip temperature + + :returns: Chip temperature in Celsius (°C) + :rtype: float + + UiFlow2 Code Block: + + |get_chip_temperature.png| + + MicroPython Code Block: + + .. code-block:: python + + unit_ncir2_0.get_chip_temperature() + + .. method:: get_device_spec(mode) + + Get device specifications + + :returns: Device specifications + :rtype: int + + UiFlow2 Code Block: + + |get_device_spec.png| + + MicroPython Code Block: + + .. code-block:: python + + unit_ncir2_0.get_device_spec() + + .. method:: set_i2c_address(addr) + + Set the I2C address of the device + + :param: int addr: I2C address range: 1 ~ 127. + + UiFlow2 Code Block: + + |set_i2c_address.png| + + MicroPython Code Block: + + .. code-block:: python + + unit_ncir2_0.set_i2c_address(addr) + + + \ No newline at end of file diff --git a/docs/locales/zh_CN/LC_MESSAGES/units/ncir2.po b/docs/locales/zh_CN/LC_MESSAGES/units/ncir2.po new file mode 100644 index 00000000..4e4a2d20 --- /dev/null +++ b/docs/locales/zh_CN/LC_MESSAGES/units/ncir2.po @@ -0,0 +1,692 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2016 - 2025 M5Stack Technology Co., Ltd +# This file is distributed under the same license as the UIFlow2 Programming +# Guide package. +# FIRST AUTHOR , 2025. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: UIFlow2 Programming Guide \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-05-13 22:42+0800\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: zh_CN\n" +"Language-Team: zh_CN \n" +"Plural-Forms: nplurals=1; plural=0;\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.16.0\n" + +#: ../../en/units/ncir2.rst:2 8d7afa3735c64435acb13e4b3958550a +msgid "NCIR2 Unit" +msgstr "" + +#: ../../en/units/ncir2.rst:8 78926b34793548399b8468a0b5365522 +msgid "This library is the driver for Unit NCIR2." +msgstr "这个库是 Unit NCIR2 的驱动" + +#: ../../en/units/ncir2.rst:10 2efd0bc4139444c48a97da954e90365c +msgid "Support the following products:" +msgstr "支持以下产品:" + +#: ../../en/units/ncir2.rst:12 3d91262fde7b4f2e98f2f1e8233d5f70 +msgid "|Unit NCIR2|" +msgstr "" + +#: ../../en/refs/unit.ncir2.ref 5b54971100864b2cb26899c67d6db90a +msgid "Unit NCIR2" +msgstr "" + +#: ../../en/units/ncir2.rst:15 e2be2d1be5d54d71b1a6a15504c7bfdf +msgid "UiFlow2 Example:" +msgstr "UiFlow2 应用示例:" + +#: ../../en/units/ncir2.rst:18 ../../en/units/ncir2.rst:36 +#: c4e9a3ccfaaa49a1bff4d7655f63fe86 +msgid "Infrared Temperature Display" +msgstr "红外温度测量显示" + +#: ../../en/units/ncir2.rst:20 26a6aa05ee1141c3b8b0f4a0795d5c83 +msgid "Open the |m5cores3_ncir2_base_example.m5f2| project in UiFlow2." +msgstr "在 UiFlow2 上打开 |m5cores3_ncir2_base_example.m5f2| 项目。" + +#: ../../en/units/ncir2.rst:22 ../../en/units/ncir2.rst:38 +#: 51f30e834e3a472aadad8eae4aa2d4dc +msgid "" +"This example uses the M5Stack CoreS3 board with the NCIR2 infrared " +"temperature sensor to measure temperature in real time and display the " +"current value along with the low and high temperature alarm thresholds on" +" screen." +msgstr "本案例基于 M5Stack CoreS3,使用 NCIR2 红外测温模块实现实时温度测量,并在屏幕上显示当前温度、低温和高温报警阈值。" + +#: ../../en/units/ncir2.rst:24 ../../en/units/ncir2.rst:64 +#: ../../en/units/ncir2.rst:83 ../../en/units/ncir2.rst:100 +#: ../../en/units/ncir2.rst:122 ../../en/units/ncir2.rst:140 +#: ../../en/units/ncir2.rst:157 ../../en/units/ncir2.rst:175 +#: ../../en/units/ncir2.rst:192 ../../en/units/ncir2.rst:210 +#: ../../en/units/ncir2.rst:227 ../../en/units/ncir2.rst:246 +#: ../../en/units/ncir2.rst:263 ../../en/units/ncir2.rst:281 +#: ../../en/units/ncir2.rst:298 ../../en/units/ncir2.rst:315 +#: ../../en/units/ncir2.rst:331 ../../en/units/ncir2.rst:348 +#: ../../en/units/ncir2.rst:364 ../../en/units/ncir2.rst:381 +#: ../../en/units/ncir2.rst:397 ../../en/units/ncir2.rst:415 +#: ../../en/units/ncir2.rst:431 ../../en/units/ncir2.rst:449 +#: ../../en/units/ncir2.rst:463 ../../en/units/ncir2.rst:480 +#: ../../en/units/ncir2.rst:497 ../../en/units/ncir2.rst:513 +#: c638c51692184a69931afbb764f26c7c +msgid "UiFlow2 Code Block:" +msgstr "UiFlow2 代码块:" + +#: ../../en/units/ncir2.rst:26 b80a9bd331f047bcad6d73f4f32e2c14 +msgid "|m5cores3_ncir2_base_example.png|" +msgstr "" + +#: ../../en/refs/unit.ncir2.ref:35 ec4f41b75ca24aaea2b9722a6d38f980 +msgid "m5cores3_ncir2_base_example.png" +msgstr "" + +#: ../../en/units/ncir2.rst:28 ../../en/units/ncir2.rst:46 +#: db8886227aa14de08562ca836fd250ec +msgid "Example output:" +msgstr "示例输出:" + +#: ../../en/units/ncir2.rst:30 ../../en/units/ncir2.rst:48 +#: 0c1e746e370342ed8b45aa94525413ff +msgid "None" +msgstr "无" + +#: ../../en/units/ncir2.rst:33 e5ea8f4c88be4f5dbe201aeb5cf158c7 +msgid "MicroPython Example:" +msgstr "MicroPython 应用示例:" + +#: ../../en/units/ncir2.rst:40 ../../en/units/ncir2.rst:68 +#: ../../en/units/ncir2.rst:87 ../../en/units/ncir2.rst:104 +#: ../../en/units/ncir2.rst:126 ../../en/units/ncir2.rst:144 +#: ../../en/units/ncir2.rst:161 ../../en/units/ncir2.rst:179 +#: ../../en/units/ncir2.rst:196 ../../en/units/ncir2.rst:214 +#: ../../en/units/ncir2.rst:231 ../../en/units/ncir2.rst:250 +#: ../../en/units/ncir2.rst:267 ../../en/units/ncir2.rst:285 +#: ../../en/units/ncir2.rst:302 ../../en/units/ncir2.rst:319 +#: ../../en/units/ncir2.rst:335 ../../en/units/ncir2.rst:352 +#: ../../en/units/ncir2.rst:368 ../../en/units/ncir2.rst:385 +#: ../../en/units/ncir2.rst:401 ../../en/units/ncir2.rst:419 +#: ../../en/units/ncir2.rst:435 ../../en/units/ncir2.rst:453 +#: ../../en/units/ncir2.rst:467 ../../en/units/ncir2.rst:484 +#: ../../en/units/ncir2.rst:501 ../../en/units/ncir2.rst:517 +#: cd71690a9aad4762985495c1dfd9192d +msgid "MicroPython Code Block:" +msgstr "MicroPython 代码块:" + +#: ../../en/units/ncir2.rst:52 d82b02a79d514efeb5a7aef85d18f8d1 +msgid "**API**" +msgstr "API应用" + +#: ../../en/units/ncir2.rst:55 eff7c7b727c748c98006c360f3d0566f +msgid "NCIR2Unit" +msgstr "" + +#: ../../en/units/ncir2.rst:59 e4c524e38a26412c84bfa06fd0928521 +msgid "Create an NCIR2Unit object." +msgstr "创建一个 NCIR2Unit 对象。" + +#: ../../en/units/ncir2.rst 6977b2e4bab24419a4ddc336aa3b0610 +msgid "Parameters" +msgstr "" + +#: ../../en/units/ncir2.rst:61 1f2fcd12d8194435bb002a8c770b8e21 +msgid "I2C port," +msgstr "I2C 端口" + +#: ../../en/units/ncir2.rst:62 11ed80d9b6ca417b842d08a7ed249248 +msgid "NCIR2Unit Slave Address, Default is 0x5A." +msgstr "NCIR2Unit 从机地址,默认为 0x5A。" + +#: ../../en/units/ncir2.rst:66 6ed9f8df84c24de9ba3018ecfc2f6cc9 +msgid "|init.png|" +msgstr "" + +#: ../../en/refs/unit.ncir2.ref:7 b5cab2e151364e248aa639b8a29cb8c1 +msgid "init.png" +msgstr "" + +#: ../../en/units/ncir2.rst:78 35e6fc42486b4d80856ab77641b91297 +msgid "Get object temperature." +msgstr "获取物体温度。" + +#: ../../en/units/ncir2.rst bc6f7b83c8ff415388064aee9c8337b3 +msgid "returns" +msgstr "" + +#: ../../en/units/ncir2.rst:80 2816b7b046ff41d090584eaaf53c34ff +msgid "object temperature(unit: ℃)" +msgstr "物体温度(单位:℃)。" + +#: ../../en/units/ncir2.rst ea92a107b13b4a9c84705de78511a411 +msgid "rtype" +msgstr "" + +#: ../../en/units/ncir2.rst:81 47c1898464eb4d12837b95fb70026a9e +msgid "float" +msgstr "" + +#: ../../en/units/ncir2.rst:85 2b9c539bae304b2bbd4edffc93fd49e1 +msgid "|get_temperature_value.png|" +msgstr "" + +#: ../../en/refs/unit.ncir2.ref:8 75ab18039be0489aac5670377d63e882 +msgid "get_temperature_value.png" +msgstr "" + +#: ../../en/units/ncir2.rst:95 841b3e9ac2fa43f49e50dbdbaf5f2ce3 +msgid "Get current emissivity." +msgstr "获取当前发射率。" + +#: ../../en/units/ncir2.rst 87d473233ba548d7af6139be701f00bb +msgid "Returns" +msgstr "" + +#: ../../en/units/ncir2.rst:97 59dba6b3dc8b4cdaa957a1a0ea493f5e +msgid "emissivity." +msgstr "发射率" + +#: ../../en/units/ncir2.rst 3afaa2a91c8848f28082e25477cea736 +msgid "Return type" +msgstr "" + +#: ../../en/units/ncir2.rst:102 1db28b0af4fe4968b660808e27127751 +msgid "|get_emissivity_value.png|" +msgstr "" + +#: ../../en/refs/unit.ncir2.ref:9 21151727346348909f9bfafa61d7c8df +msgid "get_emissivity_value.png" +msgstr "" + +#: ../../en/units/ncir2.rst:112 7ef1ff25dc644ce299023bdee5f725e0 +msgid "Set the emissivity." +msgstr "设置发射率。" + +#: ../../en/units/ncir2.rst:114 84f80d41495f4f22b131013bdb1a70dc +msgid "" +"According to the material being measured; it affects temperature " +"measurement accuracy." +msgstr "根据被测材料的不同,会影响温度测量的准确性。" + +#: ../../en/units/ncir2.rst:116 c3f329d900b3473486fbb5ff15e338fa +msgid "A black body has an emissivity of 1.00 (ideal emitter)." +msgstr "黑体的发射率为 1.00(理想的辐射体)。" + +#: ../../en/units/ncir2.rst:117 5ba2f8b877914dbe84db259151c84e64 +msgid "Shiny metals often have low emissivity values (below 0.1)." +msgstr "光亮金属通常具有较低的发射率值(低于 0.1)。" + +#: ../../en/units/ncir2.rst:118 f773f252f32a4e0c8ee8f3168c7da2c4 +msgid "" +"Dark, rough surfaces like electrical tape or human skin typically have " +"high emissivity (above 0.95)." +msgstr "像电工胶带或人体皮肤这样的黑暗、粗糙表面通常具有较高的发射率(高于 0.95)。" + +#: ../../en/units/ncir2.rst 2a0f5094bc0343958db838679d9a0dc3 +#: 2b64f19c6fe94a47b1d8c152f973ce69 85c922de58e5418eae24a84c2960d519 +#: bb050cf16dea426a82666a4df5af706d +msgid "param" +msgstr "" + +#: ../../en/units/ncir2.rst:120 c44dc3529f88482dab7cb240a048ae99 +msgid "int emissive: The emissivity, range: 0 ~ 1." +msgstr "发射率:范围:0 ~ 1。" + +#: ../../en/units/ncir2.rst:124 543431e83a48416b9906cdcc6c3a56bf +msgid "|set_emissivity_value.png|" +msgstr "" + +#: ../../en/refs/unit.ncir2.ref:22 91b0e0c8b2404ce987909d94d19e2550 +msgid "set_emissivity_value.png" +msgstr "" + +#: ../../en/units/ncir2.rst:134 5ab229c71ff54a5ba2dd59ae35c7b306 +msgid "Get temperature alaram threshold." +msgstr "获取温度报警阈值。" + +#: ../../en/units/ncir2.rst:136 c5c1bdd0dfef4cc88504b6893dedbdab +msgid "" +"int alarm_reg: ALARM_LOW_TEMP_REG: Low temperature alarm threshold " +"register, ALARM_HIGH_TEMP_REG: High temperature alarm threshold register." +msgstr "int alarm_reg: ALARM_LOW_TEMP_REG:低温报警阈值寄存器,ALARM_HIGH_TEMP_REG:高温报警阈值寄存器。" + +#: ../../en/units/ncir2.rst:137 0ec4475be47842209af0fafa673e8130 +msgid "alarm threshold." +msgstr "报警阈值。" + +#: ../../en/units/ncir2.rst:142 6a8793a5eabd452ea88b456e300d9817 +msgid "|get_temperature_threshold.png|" +msgstr "" + +#: ../../en/refs/unit.ncir2.ref:10 1131c845af7f40e2bed938a18b0efa0f +msgid "get_temperature_threshold.png" +msgstr "" + +#: ../../en/units/ncir2.rst:152 430479bf597b476d909580cfc40f16b0 +msgid "Set temperature alarm threshold." +msgstr "设置温度报警阈值。" + +#: ../../en/units/ncir2.rst:154 a388c342516a4d2384b68ab150d3bc29 +msgid "" +"int alarm_reg: temperature alarm register, ALARM_LOW_TEMP_REG: Low " +"temperature alarm threshold register, ALARM_HIGH_TEMP_REG: High " +"temperature alarm threshold register." +msgstr "" +"int alarm_reg: " +"温度报警寄存器,ALARM_LOW_TEMP_REG:低温报警阈值寄存器,ALARM_HIGH_TEMP_REG:高温报警阈值寄存器。" + +#: ../../en/units/ncir2.rst:155 116580089d614bd0961255eb1ab39d25 +msgid "float temp: alarm threshold." +msgstr "float temp: 报警阈值。" + +#: ../../en/units/ncir2.rst:159 2856bc6a8c53414684980270fcc1bc06 +msgid "|set_temperature_threshold.png|" +msgstr "" + +#: ../../en/refs/unit.ncir2.ref:23 4d1b9314e9144ee8a8cdbf4f76d94f24 +msgid "set_temperature_threshold.png" +msgstr "" + +#: ../../en/units/ncir2.rst:169 f4a43f3f31c34642a0ac2d8501560624 +msgid "Get temperature alaram RGB LED value." +msgstr "获取温度报警 RGB LED 颜色值。" + +#: ../../en/units/ncir2.rst:171 ../../en/units/ncir2.rst:189 +#: ea9409aef6c548408676d1aa9cbc72f2 +msgid "" +"int alarm_reg: temperature alarm RGB LED register, RGB_LOW_TEMP_REG: Low " +"temperature alarm RGB LED value register, RGB_HIGH_TEMP_REG: High " +"temperature alarm RGB LED value register." +msgstr "" +"int alarm_reg: 温度报警 RGB LED 寄存器,RGB_LOW_TEMP_REG: 低温报警 RGB LED 寄存器,高温报警 " +"RGB LED 寄存器。" + +#: ../../en/units/ncir2.rst:172 6b2a0cd27c8f4ff08cb09865b1c0e7f0 +msgid "temperature alarm RGB LED value." +msgstr "温度报警 RGB LED 值。" + +#: ../../en/units/ncir2.rst:177 f54f836beb604c33b143bd824ee3a6f4 +msgid "|get_temp_alarm_led.png|" +msgstr "" + +#: ../../en/refs/unit.ncir2.ref:11 05148b4cc92741cda245ed2d6b627465 +msgid "get_temp_alarm_led.png" +msgstr "" + +#: ../../en/units/ncir2.rst:187 1163347d0dda40f799fccde642502045 +msgid "Set temperature alaram RGB LED value." +msgstr "设置温度报警 RGB LED 颜色值。" + +#: ../../en/units/ncir2.rst:190 2fb3d10a670047c5b066ff114b42fd61 +msgid "RGB color value (24-bit, range: 0 ~ 0xFFFFFF)." +msgstr "RG 颜色值(24位,范围:0~0xFFFFFF)。" + +#: ../../en/units/ncir2.rst:194 c009a4daa9bb4e35a5aa2e3e7ad4df3f +msgid "|set_temp_alarm_led.png|" +msgstr "" + +#: ../../en/refs/unit.ncir2.ref:24 058e9afe045e484284e8f6b5e8784581 +msgid "set_temp_alarm_led.png" +msgstr "" + +#: ../../en/units/ncir2.rst:204 c88b8e22136e4a7fbe69ba439f1ee7ff +msgid "Get the buzzer frequency for temperature alarm." +msgstr "获取温度报警蜂鸣器鸣响频率。" + +#: ../../en/units/ncir2.rst:206 ../../en/units/ncir2.rst:224 +#: 7c0c7c06ecde45fba12a13e29acc16ca +msgid "" +"int alarm_reg: temperature alarm buzzer frequency register, " +"LOW_TEMP_FREQ_REG: Low temperature alarm buzzer frequency register, " +"HIGH_TEMP_FREQ_REG: High temperature alarm buzzer frequency register." +msgstr "" +"int alarm_reg: 温度报警蜂鸣器频率寄存器,LOW_TEMP_FREQ_REG: " +"低温报警蜂鸣器频率寄存器,HIGH_TEMP_FREQ_REG:高温报警蜂鸣器频率寄存器。" + +#: ../../en/units/ncir2.rst:207 04af98094e9643ebb86ebcb0ce2d920a +msgid "buzzer frequency." +msgstr "蜂鸣器频率。" + +#: ../../en/units/ncir2.rst:212 bb9e88476a254a13857616f920ddcd3b +msgid "|get_temp_buzzer_freq.png|" +msgstr "" + +#: ../../en/refs/unit.ncir2.ref:12 d750fb48e5ea44038ca7420ecfc0c6ac +msgid "get_temp_buzzer_freq.png" +msgstr "" + +#: ../../en/units/ncir2.rst:222 d44b263b9fa644c6aafcba7145a555ca +msgid "Set the buzzer frequency for temperature alarm." +msgstr "设置温度报警蜂鸣器频率。" + +#: ../../en/units/ncir2.rst:225 0e692e5ac6a142cf849b317a25e75334 +msgid "int freq: buzzer frequency, range: 20~20000Hz" +msgstr "int freq: 蜂鸣器频率,范围:20 ~ 20000 Hz。" + +#: ../../en/units/ncir2.rst:229 ac9ac30f5ae74ef3831bc5c0cc9386ca +msgid "|set_temp_buzzer_freq.png|" +msgstr "" + +#: ../../en/refs/unit.ncir2.ref:25 a3663dcd6618489080d2123a24733d3d +msgid "set_temp_buzzer_freq.png" +msgstr "" + +#: ../../en/units/ncir2.rst:240 09685e9d96ee45a4a5b71869787d5b83 +msgid "Get the buzzer alarm interval." +msgstr "获取蜂鸣器报警间隔。" + +#: ../../en/units/ncir2.rst:242 ../../en/units/ncir2.rst:260 +#: 656641b3ac5c48ba8b0be063dc2ba7ca +msgid "" +"int alarm_reg: buzzer alarm interval register, LOW_ALARM_INTER_REG: Low " +"temperature alarm interval register, HIGH_ALARM_INTER_REG: High " +"temperature alarm interval register." +msgstr "" +"int alarm_reg: 蜂鸣器报警间隔寄存器,LOW_ALARM_INTER_REG: " +"低温报警间隔寄存器,HIGH_ALARM_INTER_REG:高温报警间隔寄存器。" + +#: ../../en/units/ncir2.rst:243 3b0e586b552543dfbc7b02a1c6d28e28 +msgid "buzzer alarm interval. (unit: ms)" +msgstr "蜂鸣器报警间隔(单位:ms)。" + +#: ../../en/units/ncir2.rst:248 0b952ea55fa847fb8388fcd9cb15848c +msgid "|get_temp_alarm_interval.png|" +msgstr "" + +#: ../../en/refs/unit.ncir2.ref:13 8745cd0b90be4782a13fca2e5752b3ab +msgid "get_temp_alarm_interval.png" +msgstr "" + +#: ../../en/units/ncir2.rst:258 673f332cd66d4f8892c3813f7e91d427 +msgid "Set the buzzer alarm interval." +msgstr "设置蜂鸣器报警间隔。" + +#: ../../en/units/ncir2.rst:261 a54acfc9da074ac39e46e0e8a329ca53 +msgid "int interval: alarm interval, range: 1 ~ 5000(unit: ms)." +msgstr "int interval: 报警间隔,范围:1 ~ 5000 (ms)。" + +#: ../../en/units/ncir2.rst:265 4f6574b3329a4598bf8154a2c938fb87 +msgid "|set_temp_alarm_interval.png|" +msgstr "" + +#: ../../en/refs/unit.ncir2.ref:26 5157e56bc89348209dfb39c7b5e51f55 +msgid "set_temp_alarm_interval.png" +msgstr "" + +#: ../../en/units/ncir2.rst:275 fce0d68caa2a4b59b5227f25a2ab6039 +msgid "Get the duty cycle of the temperature alarm buzzer signal." +msgstr "获取温度报警蜂鸣器控制信号占空比。" + +#: ../../en/units/ncir2.rst:277 ../../en/units/ncir2.rst:295 +#: cf50fece2e0745748e82cafdbb4d6f41 +msgid "" +"Duty cycle register for the temperature alarm buzzer signal. " +"LOW_ALARM_DUTY_REG: Register for low temperature alarm duty cycle. " +"HIGH_ALARM_DUTY_REG: Register for high temperature alarm duty cycle." +msgstr "" +"温度报警蜂鸣器控制信号占空比寄存器,LOW_ALARM_DUTY_REG: " +"低温报警占空比寄存器,HIGH_ALARM_DUTY_REG:高温报警占空比寄存器。" + +#: ../../en/units/ncir2.rst:278 0a32206f24384a7da09501dce7708901 +msgid "duty cycle." +msgstr "占空比。" + +#: ../../en/units/ncir2.rst:283 1dd1589f7c724c168ae0e4d04ffa29b7 +msgid "|get_temp_buzzer_duty.png|" +msgstr "" + +#: ../../en/refs/unit.ncir2.ref:14 38246b2bfa4e4f369ed3bd368088b857 +msgid "get_temp_buzzer_duty.png" +msgstr "" + +#: ../../en/units/ncir2.rst:293 705b4dacf61b41458e488937d8d882fe +msgid "Set the duty cycle of the temperature alarm buzzer signal." +msgstr "设置温度报警蜂鸣器控制信号占空比。" + +#: ../../en/units/ncir2.rst:296 1dacf59867ea42d78e4af6ba822493d1 +msgid "int duty: Temperature alarm buzzer signal duty cycle, range: 0 ~ 255." +msgstr "int duty: 温度报警蜂鸣器控制信号占空比,范围:0 ~ 255。" + +#: ../../en/units/ncir2.rst:300 697ad87532db41ac86d263811c596725 +msgid "|set_temp_buzzer_duty.png|" +msgstr "" + +#: ../../en/refs/unit.ncir2.ref:27 38246b2bfa4e4f369ed3bd368088b857 +msgid "set_temp_buzzer_duty.png" +msgstr "" + +#: ../../en/units/ncir2.rst:310 386f20a640e649b4b111a8fa79964ead +msgid "Get the frequeny of the buzzer signal." +msgstr "获取蜂鸣器控制信号频率。" + +#: ../../en/units/ncir2.rst:312 7f31443806cd4b8b8b9941f1126b3b86 +msgid "frequeny(Hz)" +msgstr "频率(Hz)" + +#: ../../en/units/ncir2.rst:317 6e81331b4aa44d798de6a8827803f002 +msgid "|get_buzzer_freq.png|" +msgstr "" + +#: ../../en/refs/unit.ncir2.ref:15 5d1dc44e9f5e44f0b03f3cf361e5246b +msgid "get_buzzer_freq.png" +msgstr "" + +#: ../../en/units/ncir2.rst:327 4266b3e9f93640ea8573928bf30824d0 +msgid "Set the frequeny of the buzzer signal." +msgstr "设置蜂鸣器控制信号频率。" + +#: ../../en/units/ncir2.rst:329 d2ab0e7ff4404bd0b012a2bd9547d26d +msgid "int freq: buzzer signal frequency, range: 20 ~ 20000 (Hz)." +msgstr "int freq: 蜂鸣器控制信号频率,范围:20 ~ 20000(Hz)。" + +#: ../../en/units/ncir2.rst:333 023495983d864e47a2d3666d53ba9bbb +msgid "|set_buzzer_freq.png|" +msgstr "" + +#: ../../en/refs/unit.ncir2.ref:28 04716bedd10e40c081176420f15feb5e +msgid "set_buzzer_freq.png" +msgstr "" + +#: ../../en/units/ncir2.rst:343 e291ec77866f4ce6ace9f1c0293be394 +msgid "Get the duty cycle of the buzzer signal." +msgstr "获取蜂鸣器控制信号占空比。" + +#: ../../en/units/ncir2.rst:345 d5452c39592246668a62b5a766ed4753 +msgid "Duty cycle" +msgstr "占空比" + +#: ../../en/units/ncir2.rst:350 9f987961d5784355b15e7234a7ead910 +msgid "|get_buzzer_duty.png|" +msgstr "" + +#: ../../en/refs/unit.ncir2.ref:16 bf9d6a6bd21b41218c2e992b004b3abd +msgid "get_buzzer_duty.png" +msgstr "" + +#: ../../en/units/ncir2.rst:360 21836360a832416ab5c7639cb59b8fdd +msgid "Set the duty cycle of the buzzer signal." +msgstr "设置蜂鸣器控制信号占空比。" + +#: ../../en/units/ncir2.rst:362 356f486fb8f941a3a332832c20c01e05 +msgid "int duty: Duty cycle, range: 0 ~ 255." +msgstr "int duty: 占空比,范围:0 ~ 255。" + +#: ../../en/units/ncir2.rst:366 7e09dabfc20144f087472815dc637232 +msgid "|set_buzzer_duty.png|" +msgstr "" + +#: ../../en/refs/unit.ncir2.ref:29 bf9d6a6bd21b41218c2e992b004b3abd +msgid "set_buzzer_duty.png" +msgstr "" + +#: ../../en/units/ncir2.rst:376 63fb5355168c4a89b7830e3ca78e5d2c +msgid "Get the buzzer control status" +msgstr "获取蜂鸣器控制状态。" + +#: ../../en/units/ncir2.rst:378 330bc1a4945d42dbb18050f509b05125 +msgid "Returns the current buzzer control status" +msgstr "返回蜂鸣器控制状态。" + +#: ../../en/units/ncir2.rst:383 5eb78383897744a58870946a55fd73be +msgid "|get_buzzer_control.png|" +msgstr "" + +#: ../../en/refs/unit.ncir2.ref:17 624b039ae5f0435b8dd13f784724ca1c +msgid "get_buzzer_control.png" +msgstr "" + +#: ../../en/units/ncir2.rst:393 b2a7564ef69d4ed5b0932eece41c72b1 +msgid "Set the buzzer control status" +msgstr "设置蜂鸣器控制状态。" + +#: ../../en/units/ncir2.rst:395 34c995ff7f4b48f28e04e58162e31ab1 +msgid "int ctrl: Control value, 0 to turn off the buzzer, 1 to turn on the buzzer" +msgstr "int ctrl: 控制值,0 为关闭蜂鸣器,1 为打开蜂鸣器。" + +#: ../../en/units/ncir2.rst:399 a5c8acf14740453e851b662d9ca52651 +msgid "|set_buzzer_control.png|" +msgstr "" + +#: ../../en/refs/unit.ncir2.ref:30 ed744467ccf74e4187b3267d81e9cf81 +msgid "set_buzzer_control.png" +msgstr "" + +#: ../../en/units/ncir2.rst:410 b39681db937e4ddc8be80df0f3a08988 +msgid "Get the current RGB LED value" +msgstr "获取当前 RGB LED 颜色值。" + +#: ../../en/units/ncir2.rst:412 22ba4a222da1447eaf37c0f6a0bcb8c7 +msgid "The current RGB LED values in the format [r, g, b]" +msgstr "当前 RG LED 颜色值,格式:[r, g, b]。" + +#: ../../en/units/ncir2.rst:417 9a7b42ce3f2a40ac808eb5bc2860f1fb +msgid "|get_rgb_led.png|" +msgstr "" + +#: ../../en/refs/unit.ncir2.ref:18 a98d858bc7bf448ba3f6acc027916e95 +msgid "get_rgb_led.png" +msgstr "" + +#: ../../en/units/ncir2.rst:427 302663ac0d1b41baaf5deb6df8dcba2b +msgid "Set the RGB LED value" +msgstr "设置 RGB LED 颜色值。" + +#: ../../en/units/ncir2.rst:429 9b52a8dc8f7940208a7ec538275b287d +msgid "int rgb: RGB value in the range of 0 ~ 0xFFFFFF" +msgstr "int rgb: RGB 值,范围:0 ~ 0xFFFFFF。" + +#: ../../en/units/ncir2.rst:433 3ee0fd4f7d494124828680af5743fd72 +msgid "|set_rgb_led.png|" +msgstr "" + +#: ../../en/refs/unit.ncir2.ref:31 ac243614d1df424a9d7ebe46f85d3bb0 +msgid "set_rgb_led.png" +msgstr "" + +#: ../../en/units/ncir2.rst:444 51ec1c1d03bc432bbb4b8555c04e5e46 +msgid "Get the button status" +msgstr "获取按键状态。" + +#: ../../en/units/ncir2.rst:446 252a958f7c094d38815c9cf212a42061 +msgid "Button status, either 0 (not pressed) or 1 (pressed)" +msgstr "按键状态,为 0(未按下)或 1(按下)。" + +#: ../../en/units/ncir2.rst:451 b91cf76ef46b49f9a9608b702e65e2af +msgid "|get_button_status.png|" +msgstr "" + +#: ../../en/refs/unit.ncir2.ref:19 5e79fd67dbc546bf955a298ecdfa6d8a +msgid "get_button_status.png" +msgstr "" + +#: ../../en/units/ncir2.rst:461 7f4a5106edbc46c1b587f28925cd7962 +msgid "Save configuration settings" +msgstr "保存配置。" + +#: ../../en/units/ncir2.rst:465 bccc64ac3a9f450abb31d233e363199f +msgid "|save_config_setting.png|" +msgstr "" + +#: ../../en/refs/unit.ncir2.ref:32 dfd832e15a504133a921bee6d073b216 +msgid "save_config_setting.png" +msgstr "" + +#: ../../en/units/ncir2.rst:475 c701050245674187872a31e3a4a4f793 +msgid "Get the chip temperature" +msgstr "获取芯片温度。" + +#: ../../en/units/ncir2.rst:477 7f3c8d25bfbd4880989d43e3790fef64 +msgid "Chip temperature in Celsius (°C)" +msgstr "芯片温度(℃)。" + +#: ../../en/units/ncir2.rst:482 aa08ea5c44bb483f9946a1bbed0a88de +msgid "|get_chip_temperature.png|" +msgstr "" + +#: ../../en/refs/unit.ncir2.ref:20 02d464e19db74a1d9c510d33a987036b +msgid "get_chip_temperature.png" +msgstr "" + +#: ../../en/units/ncir2.rst:492 414f280f503c494bbaabf94b603373c1 +msgid "Get device specifications" +msgstr "获取设备规格" + +#: ../../en/units/ncir2.rst:494 e19d980277994b0eba9fe5ebbfb1f068 +msgid "Device specifications" +msgstr "设备规格" + +#: ../../en/units/ncir2.rst:499 d42b695d01d347edba125d46d09e9b4f +msgid "|get_device_spec.png|" +msgstr "" + +#: ../../en/refs/unit.ncir2.ref:21 7f2604dd0ff847a786d03b0cce4c3fc4 +msgid "get_device_spec.png" +msgstr "" + +#: ../../en/units/ncir2.rst:509 7ffa02cab10d43c5a1258350dacc5d11 +msgid "Set the I2C address of the device" +msgstr "设置设备 I2C 地址" + +#: ../../en/units/ncir2.rst:511 748cd15b21ce4711934b59e6062e9eb5 +msgid "int addr: I2C address range: 1 ~ 127." +msgstr "int addr: I2C 地址,范围:1 ~ 127。" + +#: ../../en/units/ncir2.rst:515 6d3240f89852409d8a268e274824138c +msgid "|set_i2c_address.png|" +msgstr "" + +#: ../../en/refs/unit.ncir2.ref:33 a60e1b02292e4c8b9c0e167b78ab0875 +msgid "set_i2c_address.png" +msgstr "" + +#~ msgid "Hbridge Unit" +#~ msgstr "" + +#~ msgid "Receive data" +#~ msgstr "" + +#~ msgid "" +#~ "This example demonstrates how to control" +#~ " the motor's speed and switch its " +#~ "rotation direction." +#~ msgstr "" + +#~ msgid "发射率" +#~ msgstr "" + +#~ msgid "" +#~ "This example uses the M5Stack " +#~ "development board with the NCIR2 " +#~ "infrared temperature sensor to measure " +#~ "temperature in real time and display " +#~ "the current value along with the " +#~ "low and high temperature alarm " +#~ "thresholds on screen." +#~ msgstr "" + diff --git a/examples/unit/ncir2/m5cores3_ncir2_base_example.m5f2 b/examples/unit/ncir2/m5cores3_ncir2_base_example.m5f2 new file mode 100644 index 00000000..cefe8c9a --- /dev/null +++ b/examples/unit/ncir2/m5cores3_ncir2_base_example.m5f2 @@ -0,0 +1 @@ +{"version":"V2.0","versionNumber":"V2.2.6","type":"cores3","components":[{"name":"screen","type":"screen","layer":0,"screenId":"builtin","screenName":"","id":"__cores3_screen","createTime":1746578700643,"x":0,"y":0,"width":320,"height":240,"backgroundColor":"#222222","size":0,"isSelected":true},{"name":"title0","type":"title","layer":1,"screenId":"builtin","screenName":"","id":"n&Jjah!ju3_nxEo@","createTime":1746582376314,"x":0,"y":0,"color":"#ffffff","backgroundColor":"#0000FF","text":"Temperature meassure","textOffset":3,"font":"Widgets.FONTS.DejaVu24","isSelected":false},{"name":"label_temp","type":"label","layer":2,"screenId":"builtin","screenName":"","id":"ewA@kG_SfAp!&J_G","createTime":1746582404863,"x":10,"y":116,"color":"#ffffff","backgroundColor":"#222222","text":"Temp: ","engine":"gfx","font":"Widgets.FONTS.DejaVu24","rotation":0,"isSelected":false},{"name":"label_la","type":"label","layer":3,"screenId":"builtin","screenName":"","id":"m9JcaigIqc#m!aul","createTime":1746582404863,"x":10,"y":50,"color":"#0000ff","backgroundColor":"#222222","text":"low temp alarm value: ","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false,"width":217,"height":20},{"name":"label_ha","type":"label","layer":4,"screenId":"builtin","screenName":"","id":"lFic^t+Km1OYLZ%U","createTime":1746582492518,"x":10,"y":80,"color":"#ff0000","backgroundColor":"#222222","text":"high temp alarm value: ","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false,"width":228,"height":20},{"name":"label_temp_val","type":"label","layer":5,"screenId":"builtin","screenName":"","id":"d@Q01X^B#w0wZWK1","createTime":1746582644431,"x":95,"y":116,"color":"#ffffff","backgroundColor":"#222222","text":" ","engine":"gfx","font":"Widgets.FONTS.DejaVu24","rotation":0,"isSelected":false,"width":7,"height":28},{"name":"label_la_val","type":"label","layer":6,"screenId":"builtin","screenName":"","id":"nB-ozIO4UnUCT^nX","createTime":1746582816793,"x":232,"y":50,"color":"#0000ff","backgroundColor":"#222222","text":" ","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false,"width":5,"height":20},{"name":"label_ha_val","type":"label","layer":7,"screenId":"builtin","screenName":"","id":"iSZH$!mjNfO-La2`","createTime":1746582820352,"x":239,"y":80,"color":"#ff0000","backgroundColor":"#222222","text":" ","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false,"width":6,"height":21}],"resources":[{"hardware":["hardware_button","hardware_pin_button","imu","speaker","touch","als","mic","i2c"]},{"unit":["unit_ncir2"]}],"units":[{"type":"unit_ncir2","name":"ncir2_0","portList":["A","PAHUB","Custom"],"portType":"A","userPort":[22,21],"id":"gB=Oc5@GVQ=N7s3f","createTime":1747016910501,"bus":"i2c0","pahubPortList":[0,1,2,3,4,5],"pahubPort":0,"initBlockId":"|5y3FHCNy;HU:==;kdO8"}],"hats":[],"bases":[],"i2cs":[{"id":"i2c0","portType":"A","userPort":[22,21],"freq":"100000","blockId":"OT]PWjU9%[b3G5%kcR3W"}],"blockly":"last_timetrue010000012ncir2_00x5Alast_timelabel_la_valLabelncir2_00x20label_ha_valLabelncir2_00x22trueGT11last_time500last_timelabel_tempLabelTemp: ncir2_0 C","screen":[{"simulationName":"Built-in","type":"builtin","width":320,"height":240,"scale":0.78,"screenName":"","blockId":"","screenColorType":0,"id":"builtin","createTime":1746578700637}],"logicWhenNum":0,"customList":[]} \ No newline at end of file diff --git a/examples/unit/ncir2/m5cores3_ncir2_base_example.py b/examples/unit/ncir2/m5cores3_ncir2_base_example.py new file mode 100644 index 00000000..2c44a594 --- /dev/null +++ b/examples/unit/ncir2/m5cores3_ncir2_base_example.py @@ -0,0 +1,64 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import os, sys, io +import M5 +from M5 import * +from hardware import I2C +from hardware import Pin +from unit import NCIR2Unit +import time + + + +title0 = None +label_temp = None +label_la = None +label_ha = None +label_temp_val = None +label_la_val = None +label_ha_val = None +i2c0 = None +ncir2_0 = None +last_time = None + + +def setup(): + global title0, label_temp, label_la, label_ha, label_temp_val, label_la_val, label_ha_val, i2c0, ncir2_0, last_time + M5.begin() + Widgets.fillScreen(0x222222) + title0 = Widgets.Title("Temperature meassure", 3, 0xffffff, 0x0000FF, Widgets.FONTS.DejaVu24) + label_temp = Widgets.Label("Temp: ", 10, 116, 1.0, 0xffffff, 0x222222, Widgets.FONTS.DejaVu24) + label_la = Widgets.Label("low temp alarm value: ", 10, 50, 1.0, 0x0000ff, 0x222222, Widgets.FONTS.DejaVu18) + label_ha = Widgets.Label("high temp alarm value: ", 10, 80, 1.0, 0xff0000, 0x222222, Widgets.FONTS.DejaVu18) + label_temp_val = Widgets.Label(" ", 95, 116, 1.0, 0xffffff, 0x222222, Widgets.FONTS.DejaVu24) + label_la_val = Widgets.Label(" ", 232, 50, 1.0, 0x0000ff, 0x222222, Widgets.FONTS.DejaVu18) + label_ha_val = Widgets.Label(" ", 239, 80, 1.0, 0xff0000, 0x222222, Widgets.FONTS.DejaVu18) + + i2c0 = I2C(0, scl=Pin(1), sda=Pin(2), freq=100000) + ncir2_0 = NCIR2Unit(i2c0, 0x5A) + last_time = time.ticks_ms() + label_la_val.setText(str(ncir2_0.get_temperature_threshold(0x20))) + label_ha_val.setText(str(ncir2_0.get_temperature_threshold(0x22))) + + +def loop(): + global title0, label_temp, label_la, label_ha, label_temp_val, label_la_val, label_ha_val, i2c0, ncir2_0, last_time + M5.update() + if (time.ticks_diff((time.ticks_ms()), last_time)) > 500: + last_time = time.ticks_ms() + label_temp.setText(str((str((ncir2_0.get_temperature_value)) + str(' C')))) + + +if __name__ == '__main__': + try: + setup() + while True: + loop() + except (Exception, KeyboardInterrupt) as e: + try: + from utility import print_error_msg + print_error_msg(e) + except ImportError: + print("please update to latest firmware") From c4e1f036494e20d0071f3739b98f442ef93c23e3 Mon Sep 17 00:00:00 2001 From: lbuque Date: Thu, 15 May 2025 15:52:32 +0800 Subject: [PATCH 083/322] tools/codeformat.py: Code format. Signed-off-by: lbuque --- .../esp_zigbee_host/py_esp_zigbee_host.c | 133 ++++++-------- m5stack/cmodules/omv/modules/py_camera.c | 173 ++++++++---------- m5stack/cmodules/rf433/py_rf433.c | 109 +++++------ m5stack/modules/startup/atoms3r_cam.py | 4 +- tools/codeformat.py | 3 + 5 files changed, 189 insertions(+), 233 deletions(-) diff --git a/m5stack/cmodules/esp_zigbee_host/py_esp_zigbee_host.c b/m5stack/cmodules/esp_zigbee_host/py_esp_zigbee_host.c index e6193ad1..821010ff 100644 --- a/m5stack/cmodules/esp_zigbee_host/py_esp_zigbee_host.c +++ b/m5stack/cmodules/esp_zigbee_host/py_esp_zigbee_host.c @@ -41,10 +41,9 @@ typedef struct light_bulb_device_params_s { mp_obj_t g_bind_cb; -static void bdb_start_top_level_commissioning_cb(uint8_t mode_mask) -{ +static void bdb_start_top_level_commissioning_cb(uint8_t mode_mask) { ESP_RETURN_ON_FALSE(esp_zb_bdb_start_top_level_commissioning(mode_mask) == ESP_OK, , TAG, - "Failed to start Zigbee bdb commissioning"); + "Failed to start Zigbee bdb commissioning"); } // object: devinfo @@ -54,14 +53,12 @@ typedef struct _py_devinfo_obj { mp_obj_t endpoint; } py_devinfo_obj_t; -mp_obj_t mp_devinfo_addr(mp_obj_t self_in) -{ +mp_obj_t mp_devinfo_addr(mp_obj_t self_in) { return ((py_devinfo_obj_t *)self_in)->addr; } static MP_DEFINE_CONST_FUN_OBJ_1(mp_devinfo_addr_obj, mp_devinfo_addr); -mp_obj_t mp_devinfo_endpoint(mp_obj_t self_in) -{ +mp_obj_t mp_devinfo_endpoint(mp_obj_t self_in) { return ((py_devinfo_obj_t *)self_in)->endpoint; } static MP_DEFINE_CONST_FUN_OBJ_1(mp_devinfo_endpoint_obj, mp_devinfo_endpoint); @@ -75,53 +72,49 @@ static MP_DEFINE_CONST_DICT(mp_devinfo_locals_dict, mp_devinfo_locals_dict_table static mp_obj_t py_devinfo_make_new(mp_obj_t addr, mp_obj_t endpoint); MP_DEFINE_CONST_OBJ_TYPE(py_devinfo_type, MP_QSTR_devinfo, MP_TYPE_FLAG_NONE, make_new, py_devinfo_make_new, - locals_dict, &mp_devinfo_locals_dict); + locals_dict, &mp_devinfo_locals_dict); -static mp_obj_t py_devinfo_make_new(mp_obj_t addr, mp_obj_t endpoint) -{ +static mp_obj_t py_devinfo_make_new(mp_obj_t addr, mp_obj_t endpoint) { py_devinfo_obj_t *self = m_new_obj(py_devinfo_obj_t); - self->base.type = &py_devinfo_type; - self->addr = addr; - self->endpoint = endpoint; + self->base.type = &py_devinfo_type; + self->addr = addr; + self->endpoint = endpoint; return MP_OBJ_FROM_PTR(self); } -static void bind_cb(esp_zb_zdp_status_t zdo_status, void *user_ctx) -{ +static void bind_cb(esp_zb_zdp_status_t zdo_status, void *user_ctx) { if (zdo_status == ESP_ZB_ZDP_STATUS_SUCCESS) { if (user_ctx) { light_bulb_device_params_t *light = (light_bulb_device_params_t *)user_ctx; - uint16_t addr = light->short_addr; + uint16_t addr = light->short_addr; free(light); mp_sched_schedule(g_bind_cb, mp_obj_new_int(addr)); } } } -static void user_find_cb(esp_zb_zdp_status_t zdo_status, uint16_t addr, uint8_t endpoint, void *user_ctx) -{ +static void user_find_cb(esp_zb_zdp_status_t zdo_status, uint16_t addr, uint8_t endpoint, void *user_ctx) { if (zdo_status == ESP_ZB_ZDP_STATUS_SUCCESS) { esp_zb_zdo_bind_req_param_t bind_req; light_bulb_device_params_t *light = (light_bulb_device_params_t *)malloc(sizeof(light_bulb_device_params_t)); - light->endpoint = endpoint; - light->short_addr = addr; + light->endpoint = endpoint; + light->short_addr = addr; esp_zb_ieee_address_by_short(light->short_addr, light->ieee_addr); esp_zb_get_long_address(bind_req.src_address); - bind_req.src_endp = HA_ONOFF_SWITCH_ENDPOINT; - bind_req.cluster_id = ESP_ZB_ZCL_CLUSTER_ID_ON_OFF; + bind_req.src_endp = HA_ONOFF_SWITCH_ENDPOINT; + bind_req.cluster_id = ESP_ZB_ZCL_CLUSTER_ID_ON_OFF; bind_req.dst_addr_mode = ESP_ZB_ZDO_BIND_DST_ADDR_MODE_64_BIT_EXTENDED; memcpy(bind_req.dst_address_u.addr_long, light->ieee_addr, sizeof(esp_zb_ieee_addr_t)); - bind_req.dst_endp = endpoint; + bind_req.dst_endp = endpoint; bind_req.req_dst_addr = esp_zb_get_short_address(); /* TODO: Send bind request to self */ esp_zb_zdo_device_bind_req(&bind_req, bind_cb, (void *)light); } } -void esp_zb_app_signal_handler(esp_zb_app_signal_t *signal_struct) -{ - uint32_t *p_sg_p = signal_struct->p_app_signal; - esp_err_t err_status = signal_struct->esp_err_status; - esp_zb_app_signal_type_t sig_type = *p_sg_p; +void esp_zb_app_signal_handler(esp_zb_app_signal_t *signal_struct) { + uint32_t *p_sg_p = signal_struct->p_app_signal; + esp_err_t err_status = signal_struct->esp_err_status; + esp_zb_app_signal_type_t sig_type = *p_sg_p; esp_zb_zdo_signal_device_annce_params_t *dev_annce_params = NULL; switch (sig_type) { case ESP_ZB_ZDO_SIGNAL_SKIP_STARTUP: @@ -136,9 +129,9 @@ void esp_zb_app_signal_handler(esp_zb_app_signal_t *signal_struct) esp_zb_bdb_start_top_level_commissioning(ESP_ZB_BDB_MODE_NETWORK_FORMATION); } else { ESP_LOGW(TAG, "%s failed with status: %s, retrying", esp_zb_zdo_signal_to_string(sig_type), - esp_err_to_name(err_status)); + esp_err_to_name(err_status)); esp_zb_scheduler_alarm((esp_zb_callback_t)bdb_start_top_level_commissioning_cb, - ESP_ZB_BDB_MODE_INITIALIZATION, 1000); + ESP_ZB_BDB_MODE_INITIALIZATION, 1000); } break; case ESP_ZB_BDB_SIGNAL_FORMATION: // 设备尝试创建 Zigbee 网络,并完成网络形成的过程后,协议栈发出的信号。 @@ -146,16 +139,16 @@ void esp_zb_app_signal_handler(esp_zb_app_signal_t *signal_struct) esp_zb_ieee_addr_t extended_pan_id; esp_zb_get_extended_pan_id(extended_pan_id); ESP_LOGI(TAG, - "Formed network successfully (Extended PAN ID: %02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x, PAN " - "ID: 0x%04hx, Channel:%d, Short Address: 0x%04hx)", - extended_pan_id[7], extended_pan_id[6], extended_pan_id[5], extended_pan_id[4], - extended_pan_id[3], extended_pan_id[2], extended_pan_id[1], extended_pan_id[0], - esp_zb_get_pan_id(), esp_zb_get_current_channel(), esp_zb_get_short_address()); + "Formed network successfully (Extended PAN ID: %02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x, PAN " + "ID: 0x%04hx, Channel:%d, Short Address: 0x%04hx)", + extended_pan_id[7], extended_pan_id[6], extended_pan_id[5], extended_pan_id[4], + extended_pan_id[3], extended_pan_id[2], extended_pan_id[1], extended_pan_id[0], + esp_zb_get_pan_id(), esp_zb_get_current_channel(), esp_zb_get_short_address()); esp_zb_bdb_start_top_level_commissioning(ESP_ZB_BDB_MODE_NETWORK_STEERING); } else { ESP_LOGI(TAG, "Restart network formation (status: %s)", esp_err_to_name(err_status)); esp_zb_scheduler_alarm((esp_zb_callback_t)bdb_start_top_level_commissioning_cb, - ESP_ZB_BDB_MODE_NETWORK_FORMATION, 1000); + ESP_ZB_BDB_MODE_NETWORK_FORMATION, 1000); } break; case ESP_ZB_BDB_SIGNAL_STEERING: @@ -167,7 +160,7 @@ void esp_zb_app_signal_handler(esp_zb_app_signal_t *signal_struct) dev_annce_params = (esp_zb_zdo_signal_device_annce_params_t *)esp_zb_app_signal_get_params(p_sg_p); ESP_LOGI(TAG, "New device commissioned or rejoined (short: 0x%04hx)", dev_annce_params->device_short_addr); esp_zb_zdo_match_desc_req_param_t cmd_req; - cmd_req.dst_nwk_addr = dev_annce_params->device_short_addr; + cmd_req.dst_nwk_addr = dev_annce_params->device_short_addr; cmd_req.addr_of_interest = dev_annce_params->device_short_addr; esp_zb_zdo_find_on_off_light(&cmd_req, user_find_cb, NULL); break; @@ -175,7 +168,7 @@ void esp_zb_app_signal_handler(esp_zb_app_signal_t *signal_struct) if (err_status == ESP_OK) { if (*(uint8_t *)esp_zb_app_signal_get_params(p_sg_p)) { ESP_LOGI(TAG, "Network(0x%04hx) is open for %d seconds", esp_zb_get_pan_id(), - *(uint8_t *)esp_zb_app_signal_get_params(p_sg_p)); + *(uint8_t *)esp_zb_app_signal_get_params(p_sg_p)); } else { ESP_LOGW(TAG, "Network(0x%04hx) closed, devices joining not allowed.", esp_zb_get_pan_id()); } @@ -183,24 +176,23 @@ void esp_zb_app_signal_handler(esp_zb_app_signal_t *signal_struct) break; default: ESP_LOGI(TAG, "ZDO signal: %s (0x%x), status: %s", esp_zb_zdo_signal_to_string(sig_type), sig_type, - esp_err_to_name(err_status)); + esp_err_to_name(err_status)); break; } } -static void esp_zb_task(void *pvParameters) -{ +static void esp_zb_task(void *pvParameters) { esp_zb_cfg_t zb_nwk_cfg = { - .esp_zb_role = ESP_ZB_DEVICE_TYPE_COORDINATOR, + .esp_zb_role = ESP_ZB_DEVICE_TYPE_COORDINATOR, .install_code_policy = INSTALLCODE_POLICY_ENABLE, .nwk_cfg.zczr_cfg = - { - .max_children = MAX_CHILDREN, - }, + { + .max_children = MAX_CHILDREN, + }, }; esp_zb_init(&zb_nwk_cfg); - esp_zb_on_off_switch_cfg_t switch_cfg = ESP_ZB_DEFAULT_ON_OFF_SWITCH_CONFIG(); + esp_zb_on_off_switch_cfg_t switch_cfg = ESP_ZB_DEFAULT_ON_OFF_SWITCH_CONFIG(); esp_zb_ep_list_t *esp_zb_on_off_switch_ep = esp_zb_on_off_switch_ep_create(HA_ONOFF_SWITCH_ENDPOINT, &switch_cfg); esp_zb_device_register(esp_zb_on_off_switch_ep); @@ -221,8 +213,7 @@ typedef struct _switch_endpoint_obj_t { extern const mp_obj_type_t mp_switch_endpoint_type; static bool is_initialized; -static mp_obj_t switch_endpoint_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) -{ +static mp_obj_t switch_endpoint_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { switch_endpoint_obj_t *self = mp_obj_malloc_with_finaliser(switch_endpoint_obj_t, &mp_switch_endpoint_type); static const mp_arg_t allowed_args[] = { @@ -249,13 +240,13 @@ static mp_obj_t switch_endpoint_make_new(const mp_obj_type_t *type, size_t n_arg if (!is_initialized) { esp_zb_platform_config_t config = { .radio_config = - { - .radio_mode = RADIO_MODE_UART_NCP, - }, + { + .radio_mode = RADIO_MODE_UART_NCP, + }, .host_config = - { - .host_mode = HOST_CONNECTION_MODE_UART, - }, + { + .host_mode = HOST_CONNECTION_MODE_UART, + }, }; ESP_ERROR_CHECK(esp_zb_platform_config(&config)); xTaskCreate(esp_zb_task, "zigbee_main", 4096, NULL, 5, NULL); @@ -273,8 +264,7 @@ static mp_obj_t switch_endpoint_make_new(const mp_obj_type_t *type, size_t n_arg return MP_OBJ_FROM_PTR(self); } -static mp_obj_t switch_endpoint_set_bind_cb(mp_obj_t self_in, mp_obj_t cb) -{ +static mp_obj_t switch_endpoint_set_bind_cb(mp_obj_t self_in, mp_obj_t cb) { if (cb == mp_const_none || mp_obj_is_callable(cb)) { g_bind_cb = cb; } else { @@ -284,17 +274,16 @@ static mp_obj_t switch_endpoint_set_bind_cb(mp_obj_t self_in, mp_obj_t cb) } static MP_DEFINE_CONST_FUN_OBJ_2(switch_endpoint_set_bind_cb_obj, switch_endpoint_set_bind_cb); -static mp_obj_t switch_endpoint_on(size_t n_args, const mp_obj_t *args) -{ +static mp_obj_t switch_endpoint_on(size_t n_args, const mp_obj_t *args) { switch_endpoint_obj_t *self = MP_OBJ_TO_PTR(args[0]); esp_zb_zcl_on_off_cmd_t cmd_req; cmd_req.zcl_basic_cmd.src_endpoint = HA_ONOFF_SWITCH_ENDPOINT; - cmd_req.on_off_cmd_id = ESP_ZB_ZCL_CMD_ON_OFF_ON_ID; + cmd_req.on_off_cmd_id = ESP_ZB_ZCL_CMD_ON_OFF_ON_ID; if (n_args > 1) { - cmd_req.address_mode = ESP_ZB_APS_ADDR_MODE_16_ENDP_PRESENT; + cmd_req.address_mode = ESP_ZB_APS_ADDR_MODE_16_ENDP_PRESENT; cmd_req.zcl_basic_cmd.dst_addr_u.addr_short = mp_obj_get_int(args[1]); - cmd_req.zcl_basic_cmd.dst_endpoint = 10; + cmd_req.zcl_basic_cmd.dst_endpoint = 10; } else { cmd_req.address_mode = ESP_ZB_APS_ADDR_MODE_DST_ADDR_ENDP_NOT_PRESENT; } @@ -305,17 +294,16 @@ static mp_obj_t switch_endpoint_on(size_t n_args, const mp_obj_t *args) } static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(switch_endpoint_on_obj, 1, 2, switch_endpoint_on); -static mp_obj_t switch_endpoint_off(size_t n_args, const mp_obj_t *args) -{ +static mp_obj_t switch_endpoint_off(size_t n_args, const mp_obj_t *args) { switch_endpoint_obj_t *self = MP_OBJ_TO_PTR(args[0]); esp_zb_zcl_on_off_cmd_t cmd_req; cmd_req.zcl_basic_cmd.src_endpoint = HA_ONOFF_SWITCH_ENDPOINT; - cmd_req.on_off_cmd_id = ESP_ZB_ZCL_CMD_ON_OFF_OFF_ID; + cmd_req.on_off_cmd_id = ESP_ZB_ZCL_CMD_ON_OFF_OFF_ID; if (n_args > 1) { - cmd_req.address_mode = ESP_ZB_APS_ADDR_MODE_16_ENDP_PRESENT; + cmd_req.address_mode = ESP_ZB_APS_ADDR_MODE_16_ENDP_PRESENT; cmd_req.zcl_basic_cmd.dst_addr_u.addr_short = mp_obj_get_int(args[1]); - cmd_req.zcl_basic_cmd.dst_endpoint = 10; + cmd_req.zcl_basic_cmd.dst_endpoint = 10; } else { cmd_req.address_mode = ESP_ZB_APS_ADDR_MODE_DST_ADDR_ENDP_NOT_PRESENT; } @@ -326,17 +314,16 @@ static mp_obj_t switch_endpoint_off(size_t n_args, const mp_obj_t *args) } static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(switch_endpoint_off_obj, 1, 2, switch_endpoint_off); -static mp_obj_t switch_endpoint_toggle(size_t n_args, const mp_obj_t *args) -{ +static mp_obj_t switch_endpoint_toggle(size_t n_args, const mp_obj_t *args) { switch_endpoint_obj_t *self = MP_OBJ_TO_PTR(args[0]); esp_zb_zcl_on_off_cmd_t cmd_req; cmd_req.zcl_basic_cmd.src_endpoint = HA_ONOFF_SWITCH_ENDPOINT; - cmd_req.on_off_cmd_id = ESP_ZB_ZCL_CMD_ON_OFF_TOGGLE_ID; + cmd_req.on_off_cmd_id = ESP_ZB_ZCL_CMD_ON_OFF_TOGGLE_ID; if (n_args > 1) { - cmd_req.address_mode = ESP_ZB_APS_ADDR_MODE_16_ENDP_PRESENT; + cmd_req.address_mode = ESP_ZB_APS_ADDR_MODE_16_ENDP_PRESENT; cmd_req.zcl_basic_cmd.dst_addr_u.addr_short = mp_obj_get_int(args[1]); - cmd_req.zcl_basic_cmd.dst_endpoint = 10; + cmd_req.zcl_basic_cmd.dst_endpoint = 10; } else { cmd_req.address_mode = ESP_ZB_APS_ADDR_MODE_DST_ADDR_ENDP_NOT_PRESENT; } @@ -357,7 +344,7 @@ static const mp_rom_map_elem_t switch_endpoint_locals_dict_table[] = { MP_DEFINE_CONST_DICT(switch_endpoint_locals_dict, switch_endpoint_locals_dict_table); MP_DEFINE_CONST_OBJ_TYPE(mp_switch_endpoint_type, MP_QSTR_SwitchEndpoint, MP_TYPE_FLAG_NONE, make_new, - switch_endpoint_make_new, locals_dict, &switch_endpoint_locals_dict); + switch_endpoint_make_new, locals_dict, &switch_endpoint_locals_dict); // ================================================================================================= // module: esp_zigbee_host @@ -368,7 +355,7 @@ static const mp_rom_map_elem_t esp_zigbee_host_globals_table[] = { static MP_DEFINE_CONST_DICT(esp_zigbee_host_globals, esp_zigbee_host_globals_table); const mp_obj_module_t module_esp_zigbee_host = { - .base = {&mp_type_module}, + .base = {&mp_type_module}, .globals = (mp_obj_dict_t *)&esp_zigbee_host_globals, }; diff --git a/m5stack/cmodules/omv/modules/py_camera.c b/m5stack/cmodules/omv/modules/py_camera.c index b241f9d5..752224b1 100644 --- a/m5stack/cmodules/omv/modules/py_camera.c +++ b/m5stack/cmodules/omv/modules/py_camera.c @@ -50,30 +50,30 @@ #define CORES3_CAMERA_PCLK_PIN 45 static camera_config_t camera_config = { - .pin_pwdn = CORES3_CAMERA_POWER_DOWN_PIN, - .pin_reset = CORES3_CAMERA_RESET_PIN, - .pin_xclk = CORES3_CAMERA_XCLK_PIN, - .pin_sscb_sda = -1, // CORES3_CAMERA_SDA_PIN, // 共用 I2C1 在其他地方初始化 - .pin_sscb_scl = -1, // CORES3_CAMERA_SCL_PIN, - .pin_d7 = CORES3_CAMERA_D7_PIN, - .pin_d6 = CORES3_CAMERA_D6_PIN, - .pin_d5 = CORES3_CAMERA_D5_PIN, - .pin_d4 = CORES3_CAMERA_D4_PIN, - .pin_d3 = CORES3_CAMERA_D3_PIN, - .pin_d2 = CORES3_CAMERA_D2_PIN, - .pin_d1 = CORES3_CAMERA_D1_PIN, - .pin_d0 = CORES3_CAMERA_D0_PIN, - .pin_vsync = CORES3_CAMERA_VSYNC_PIN, - .pin_href = CORES3_CAMERA_HREF_PIN, - .pin_pclk = CORES3_CAMERA_PCLK_PIN, - .xclk_freq_hz = 20000000, - .ledc_timer = LEDC_TIMER_0, - .ledc_channel = LEDC_CHANNEL_0, - .pixel_format = PIXFORMAT_RGB565, - .frame_size = FRAMESIZE_QVGA, - .fb_count = 2, - .fb_location = CAMERA_FB_IN_PSRAM, - .grab_mode = CAMERA_GRAB_LATEST, + .pin_pwdn = CORES3_CAMERA_POWER_DOWN_PIN, + .pin_reset = CORES3_CAMERA_RESET_PIN, + .pin_xclk = CORES3_CAMERA_XCLK_PIN, + .pin_sscb_sda = -1, // CORES3_CAMERA_SDA_PIN, // 共用 I2C1 在其他地方初始化 + .pin_sscb_scl = -1, // CORES3_CAMERA_SCL_PIN, + .pin_d7 = CORES3_CAMERA_D7_PIN, + .pin_d6 = CORES3_CAMERA_D6_PIN, + .pin_d5 = CORES3_CAMERA_D5_PIN, + .pin_d4 = CORES3_CAMERA_D4_PIN, + .pin_d3 = CORES3_CAMERA_D3_PIN, + .pin_d2 = CORES3_CAMERA_D2_PIN, + .pin_d1 = CORES3_CAMERA_D1_PIN, + .pin_d0 = CORES3_CAMERA_D0_PIN, + .pin_vsync = CORES3_CAMERA_VSYNC_PIN, + .pin_href = CORES3_CAMERA_HREF_PIN, + .pin_pclk = CORES3_CAMERA_PCLK_PIN, + .xclk_freq_hz = 20000000, + .ledc_timer = LEDC_TIMER_0, + .ledc_channel = LEDC_CHANNEL_0, + .pixel_format = PIXFORMAT_RGB565, + .frame_size = FRAMESIZE_QVGA, + .fb_count = 2, + .fb_location = CAMERA_FB_IN_PSRAM, + .grab_mode = CAMERA_GRAB_LATEST, .sccb_i2c_port = 1, // use I2C1 }; @@ -98,31 +98,31 @@ static camera_config_t camera_config = { #define ATOMS3R_CAM_PIN_EN 18 // 电源控制 camera_config_t camera_config = { - .pin_pwdn = ATOMS3R_CAM_PIN_PWDN, - .pin_reset = ATOMS3R_CAM_PIN_RESET, + .pin_pwdn = ATOMS3R_CAM_PIN_PWDN, + .pin_reset = ATOMS3R_CAM_PIN_RESET, .pin_sccb_scl = ATOMS3R_CAM_PIN_SIOC, .pin_sccb_sda = ATOMS3R_CAM_PIN_SIOD, - .pin_d0 = ATOMS3R_CAM_PIN_D0, - .pin_d1 = ATOMS3R_CAM_PIN_D1, - .pin_d2 = ATOMS3R_CAM_PIN_D2, - .pin_d3 = ATOMS3R_CAM_PIN_D3, - .pin_d4 = ATOMS3R_CAM_PIN_D4, - .pin_d5 = ATOMS3R_CAM_PIN_D5, - .pin_d6 = ATOMS3R_CAM_PIN_D6, - .pin_d7 = ATOMS3R_CAM_PIN_D7, - .pin_vsync = ATOMS3R_CAM_PIN_VSYNC, - .pin_href = ATOMS3R_CAM_PIN_HREF, - .pin_pclk = ATOMS3R_CAM_PIN_PCLK, - .pin_xclk = ATOMS3R_CAM_PIN_XCLK, + .pin_d0 = ATOMS3R_CAM_PIN_D0, + .pin_d1 = ATOMS3R_CAM_PIN_D1, + .pin_d2 = ATOMS3R_CAM_PIN_D2, + .pin_d3 = ATOMS3R_CAM_PIN_D3, + .pin_d4 = ATOMS3R_CAM_PIN_D4, + .pin_d5 = ATOMS3R_CAM_PIN_D5, + .pin_d6 = ATOMS3R_CAM_PIN_D6, + .pin_d7 = ATOMS3R_CAM_PIN_D7, + .pin_vsync = ATOMS3R_CAM_PIN_VSYNC, + .pin_href = ATOMS3R_CAM_PIN_HREF, + .pin_pclk = ATOMS3R_CAM_PIN_PCLK, + .pin_xclk = ATOMS3R_CAM_PIN_XCLK, .xclk_freq_hz = 20000000, - .ledc_timer = LEDC_TIMER_0, + .ledc_timer = LEDC_TIMER_0, .ledc_channel = LEDC_CHANNEL_0, .pixel_format = PIXFORMAT_RGB565, - .frame_size = FRAMESIZE_QVGA, + .frame_size = FRAMESIZE_QVGA, .jpeg_quality = 6, - .fb_count = 2, - .fb_location = CAMERA_FB_IN_PSRAM, - .grab_mode = CAMERA_GRAB_WHEN_EMPTY, + .fb_count = 2, + .fb_location = CAMERA_FB_IN_PSRAM, + .grab_mode = CAMERA_GRAB_WHEN_EMPTY, }; #endif @@ -135,8 +135,7 @@ cam_config_t g_cam_config; static enum { E_CAMERA_INIT, E_CAMERA_DEINIT } status = E_CAMERA_DEINIT; -static bool camera_init_helper(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) -{ +static bool camera_init_helper(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { enum { ARG_pixformat, ARG_framesize, ARG_fb_count, ARG_fb_location }; /* *FORMAT-OFF* */ const mp_arg_t allowed_args[] = { @@ -193,11 +192,10 @@ static bool camera_init_helper(size_t n_args, const mp_obj_t *pos_args, mp_map_t return true; } -static mp_obj_t camera_init(size_t n_pos_args, const mp_obj_t *pos_args, mp_map_t *kw_args) -{ - bool init = camera_init_helper(n_pos_args, pos_args, kw_args); +static mp_obj_t camera_init(size_t n_pos_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + bool init = camera_init_helper(n_pos_args, pos_args, kw_args); g_cam_config.hmirror = true; - g_cam_config.vflip = false; + g_cam_config.vflip = false; if (init) { return mp_const_true; } else { @@ -207,8 +205,7 @@ static mp_obj_t camera_init(size_t n_pos_args, const mp_obj_t *pos_args, mp_map_ } static MP_DEFINE_CONST_FUN_OBJ_KW(camera_init_obj, 0, camera_init); -static mp_obj_t camera_deinit() -{ +static mp_obj_t camera_deinit() { esp_err_t err = esp_camera_deinit(); if (err != ESP_OK) { ESP_LOGE(TAG, "Camera deinit Failed"); @@ -219,10 +216,9 @@ static mp_obj_t camera_deinit() } static MP_DEFINE_CONST_FUN_OBJ_0(camera_deinit_obj, camera_deinit); -static mp_obj_t camera_skip_frames(size_t n_args, const mp_obj_t *args, mp_map_t *kw_args) -{ +static mp_obj_t camera_skip_frames(size_t n_args, const mp_obj_t *args, mp_map_t *kw_args) { mp_map_elem_t *kw_arg = mp_map_lookup(kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_time), MP_MAP_LOOKUP); - mp_int_t time = 300; // OV Recommended. + mp_int_t time = 300; // OV Recommended. if (kw_arg != NULL) { time = mp_obj_get_int(kw_arg->value); @@ -256,8 +252,7 @@ static mp_obj_t camera_skip_frames(size_t n_args, const mp_obj_t *args, mp_map_t } static MP_DEFINE_CONST_FUN_OBJ_KW(camera_skip_frames_obj, 0, camera_skip_frames); -static mp_obj_t camera_capture() -{ +static mp_obj_t camera_capture() { // acquire a frame camera_fb_t *fb = esp_camera_fb_get(); if (!fb) { @@ -271,8 +266,7 @@ static mp_obj_t camera_capture() } static MP_DEFINE_CONST_FUN_OBJ_0(camera_capture_obj, camera_capture); -static mp_obj_t camera_capture_to_jpg(mp_obj_t quality_in) -{ +static mp_obj_t camera_capture_to_jpg(mp_obj_t quality_in) { // acquire a frame camera_fb_t *fb = esp_camera_fb_get(); if (!fb) { @@ -285,8 +279,8 @@ static mp_obj_t camera_capture_to_jpg(mp_obj_t quality_in) image = mp_obj_new_bytes(fb->buf, fb->len); } else { uint8_t quality = mp_obj_get_int(quality_in); - uint8_t *out = NULL; - size_t out_len = 0; + uint8_t *out = NULL; + size_t out_len = 0; if (frame2jpg(fb, quality, &out, &out_len)) { image = mp_obj_new_bytes(out, out_len); free(out); @@ -298,8 +292,7 @@ static mp_obj_t camera_capture_to_jpg(mp_obj_t quality_in) } static MP_DEFINE_CONST_FUN_OBJ_1(camera_capture_to_jpg_obj, camera_capture_to_jpg); -static mp_obj_t camera_capture_to_bmp() -{ +static mp_obj_t camera_capture_to_bmp() { // acquire a frame camera_fb_t *fb = esp_camera_fb_get(); if (!fb) { @@ -308,7 +301,7 @@ static mp_obj_t camera_capture_to_bmp() } mp_obj_t image = mp_const_none; - uint8_t *out = NULL; + uint8_t *out = NULL; size_t out_len = 0; if (frame2bmp(fb, &out, &out_len)) { image = mp_obj_new_bytes(out, out_len); @@ -320,8 +313,7 @@ static mp_obj_t camera_capture_to_bmp() } static MP_DEFINE_CONST_FUN_OBJ_0(camera_capture_to_bmp_obj, camera_capture_to_bmp); -static mp_obj_t camera_pixformat(mp_obj_t pixformat) -{ +static mp_obj_t camera_pixformat(mp_obj_t pixformat) { int format = mp_obj_get_int(pixformat); // if ((format < 0) || (format > 1)) { // mp_raise_ValueError(MP_ERROR_TEXT("Pixelformat is not valid")); @@ -342,8 +334,7 @@ static mp_obj_t camera_pixformat(mp_obj_t pixformat) } static MP_DEFINE_CONST_FUN_OBJ_1(camera_pixformat_obj, camera_pixformat); -static mp_obj_t camera_framesize(mp_obj_t framesize) -{ +static mp_obj_t camera_framesize(mp_obj_t framesize) { int size = mp_obj_get_int(framesize); if ((size < 0) || (size > 8)) { mp_raise_ValueError(MP_ERROR_TEXT("Image framesize is not valid")); @@ -364,8 +355,7 @@ static mp_obj_t camera_framesize(mp_obj_t framesize) } static MP_DEFINE_CONST_FUN_OBJ_1(camera_framesize_obj, camera_framesize); -static mp_obj_t camera_contrast(mp_obj_t contrast) -{ +static mp_obj_t camera_contrast(mp_obj_t contrast) { sensor_t *s = esp_camera_sensor_get(); if (!s) { ESP_LOGE(TAG, "Contrast Failed"); @@ -382,8 +372,7 @@ static mp_obj_t camera_contrast(mp_obj_t contrast) } static MP_DEFINE_CONST_FUN_OBJ_1(camera_contrast_obj, camera_contrast); -static mp_obj_t camera_global_gain(mp_obj_t gain_level) -{ +static mp_obj_t camera_global_gain(mp_obj_t gain_level) { sensor_t *s = esp_camera_sensor_get(); if (!s) { ESP_LOGE(TAG, "Contrast Failed"); @@ -400,8 +389,7 @@ static mp_obj_t camera_global_gain(mp_obj_t gain_level) } static MP_DEFINE_CONST_FUN_OBJ_1(camera_global_gain_obj, camera_global_gain); -static mp_obj_t camera_hmirror(mp_obj_t direction) -{ +static mp_obj_t camera_hmirror(mp_obj_t direction) { sensor_t *s = esp_camera_sensor_get(); if (!s) { ESP_LOGE(TAG, "Mirroring Failed"); @@ -417,8 +405,7 @@ static mp_obj_t camera_hmirror(mp_obj_t direction) } static MP_DEFINE_CONST_FUN_OBJ_1(camera_hmirror_obj, camera_hmirror); -static mp_obj_t camera_vflip(mp_obj_t direction) -{ +static mp_obj_t camera_vflip(mp_obj_t direction) { sensor_t *s = esp_camera_sensor_get(); if (!s) { ESP_LOGE(TAG, "Flipping Failed"); @@ -434,8 +421,7 @@ static mp_obj_t camera_vflip(mp_obj_t direction) } static MP_DEFINE_CONST_FUN_OBJ_1(camera_vflip_obj, camera_vflip); -static mp_obj_t camera_colorbar(mp_obj_t enable) -{ +static mp_obj_t camera_colorbar(mp_obj_t enable) { sensor_t *s = esp_camera_sensor_get(); if (!s) { ESP_LOGE(TAG, "Colorbar Failed"); @@ -460,21 +446,18 @@ static MP_DEFINE_CONST_FUN_OBJ_1(camera_colorbar_obj, camera_colorbar); static camera_fb_t *g_frame = NULL; -void swap_rgb565(uint16_t *pixel) -{ +void swap_rgb565(uint16_t *pixel) { *pixel = (*pixel >> 8) | (*pixel << 8); } -void image_endian_swap(image_t *img) -{ +void image_endian_swap(image_t *img) { uint16_t *pixel = (uint16_t *)img->data; for (int i = 0; i < img->w * img->h; i++) { swap_rgb565(&pixel[i]); } } -static mp_obj_t py_camera_snapshot() -{ +static mp_obj_t py_camera_snapshot() { if (g_frame != NULL) { esp_camera_fb_return(g_frame); g_frame = NULL; @@ -483,8 +466,8 @@ static mp_obj_t py_camera_snapshot() image_t img; img.size = g_frame->len; - img.w = g_frame->width; - img.h = g_frame->height; + img.w = g_frame->width; + img.h = g_frame->height; if (g_frame->format == PIXFORMAT_RGB565) { img.pixfmt = OMV_PIXFORMAT_RGB565; } else if (g_frame->format == PIXFORMAT_GRAYSCALE) { @@ -499,32 +482,28 @@ static mp_obj_t py_camera_snapshot() } static MP_DEFINE_CONST_FUN_OBJ_0(py_camera_snapshot_obj, py_camera_snapshot); -static mp_obj_t py_camera_set_hmirror(mp_obj_t enable) -{ - sensor_t *s = esp_camera_sensor_get(); +static mp_obj_t py_camera_set_hmirror(mp_obj_t enable) { + sensor_t *s = esp_camera_sensor_get(); g_cam_config.hmirror = mp_obj_is_true(enable); s->set_hmirror(s, g_cam_config.hmirror); return mp_const_none; } static MP_DEFINE_CONST_FUN_OBJ_1(py_camera_set_hmirror_obj, py_camera_set_hmirror); -static mp_obj_t py_camera_set_vflip(mp_obj_t enable) -{ - sensor_t *s = esp_camera_sensor_get(); +static mp_obj_t py_camera_set_vflip(mp_obj_t enable) { + sensor_t *s = esp_camera_sensor_get(); g_cam_config.vflip = mp_obj_is_true(enable); s->set_vflip(s, g_cam_config.vflip); return mp_const_none; } static MP_DEFINE_CONST_FUN_OBJ_1(py_camera_set_vflip_obj, py_camera_set_vflip); -static mp_obj_t py_camera_get_hmirror(void) -{ +static mp_obj_t py_camera_get_hmirror(void) { return mp_obj_new_bool(g_cam_config.hmirror); } static MP_DEFINE_CONST_FUN_OBJ_0(py_camera_get_hmirror_obj, py_camera_get_hmirror); -static mp_obj_t py_camera_get_vflip(void) -{ +static mp_obj_t py_camera_get_vflip(void) { return mp_obj_new_bool(g_cam_config.vflip); } static MP_DEFINE_CONST_FUN_OBJ_0(py_camera_get_vflip_obj, py_camera_get_vflip); @@ -595,8 +574,8 @@ static MP_DEFINE_CONST_DICT(camera_globals_dict, camera_globals_dict_table); // Define module object. const mp_obj_module_t py_module_camera = { - .base = {&mp_type_module}, + .base = {&mp_type_module}, .globals = (mp_obj_dict_t *)&camera_globals_dict, }; -MP_REGISTER_MODULE(MP_QSTR_camera, py_module_camera); \ No newline at end of file +MP_REGISTER_MODULE(MP_QSTR_camera, py_module_camera); diff --git a/m5stack/cmodules/rf433/py_rf433.c b/m5stack/cmodules/rf433/py_rf433.c index 6665c935..7ac572b5 100644 --- a/m5stack/cmodules/rf433/py_rf433.c +++ b/m5stack/cmodules/rf433/py_rf433.c @@ -53,18 +53,17 @@ typedef struct _tx_obj_t { extern const mp_obj_type_t mp_tx_type; // 编码单个比特 -static rmt_item32_t encode_bit(bool bit) -{ +static rmt_item32_t encode_bit(bool bit) { rmt_item32_t item; if (bit) { - item.level0 = 1; + item.level0 = 1; item.duration0 = BIT_HIGH_US; // 1 高电平持续时间 - item.level1 = 0; + item.level1 = 0; item.duration1 = BIT1_LOW_US; // 1 低电平持续时间 } else { - item.level0 = 1; + item.level0 = 1; item.duration0 = BIT_HIGH_US; // 0 高电平持续时间 - item.level1 = 0; + item.level1 = 0; item.duration1 = BIT0_LOW_US; // 0 低电平持续时间 } @@ -72,23 +71,21 @@ static rmt_item32_t encode_bit(bool bit) } // 编码一个字节 -static void encode_byte(rmt_item32_t *items, int *index, uint8_t byte) -{ +static void encode_byte(rmt_item32_t *items, int *index, uint8_t byte) { for (int bit_idx = 7; bit_idx >= 0; bit_idx--) { items[(*index)++] = encode_bit((byte >> bit_idx) & 1); } } // 发送 RMT 数据 -void send_data(const uint8_t *payload, size_t len) -{ +void send_data(const uint8_t *payload, size_t len) { // 计算 RMT 数据的最大数量(同步位 + 帧头 + 数据长度 + 数据 + 帧尾,每个字节占 8 个项) size_t item_max_count = 1 + (2 + len + 1) * 8; rmt_item32_t items[item_max_count]; int item_idx = 0; // 添加同步位 - items[item_idx++] = (rmt_item32_t){.duration0 = SYNC_HIGH_US, .level0 = 1, .duration1 = SYNC_LOW_US, .level1 = 0}; + items[item_idx++] = (rmt_item32_t) {.duration0 = SYNC_HIGH_US, .level0 = 1, .duration1 = SYNC_LOW_US, .level1 = 0}; // 添加帧头 0xAA encode_byte(items, &item_idx, FRAME_HEADER); @@ -110,8 +107,7 @@ void send_data(const uint8_t *payload, size_t len) } static bool tx_is_initialized; -static mp_obj_t tx_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) -{ +static mp_obj_t tx_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { tx_obj_t *self = mp_obj_malloc_with_finaliser(tx_obj_t, &mp_tx_type); static const mp_arg_t allowed_args[] = { @@ -126,15 +122,15 @@ static mp_obj_t tx_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_k if (!tx_is_initialized) { // TODO: deinit() 实现 tx_is_initialized = true; rmt_config_t tx_cfg; - tx_cfg.rmt_mode = RMT_MODE_TX; - tx_cfg.channel = RMT_TX_CHANNEL; - tx_cfg.gpio_num = self->out; - tx_cfg.mem_block_num = 1; - tx_cfg.tx_config.loop_en = false; - tx_cfg.tx_config.carrier_en = false; + tx_cfg.rmt_mode = RMT_MODE_TX; + tx_cfg.channel = RMT_TX_CHANNEL; + tx_cfg.gpio_num = self->out; + tx_cfg.mem_block_num = 1; + tx_cfg.tx_config.loop_en = false; + tx_cfg.tx_config.carrier_en = false; tx_cfg.tx_config.idle_output_en = true; - tx_cfg.tx_config.idle_level = (rmt_idle_level_t)0; - tx_cfg.clk_div = 80; // RMT_CLK_DIV; // 时钟分频 + tx_cfg.tx_config.idle_level = (rmt_idle_level_t)0; + tx_cfg.clk_div = 80; // RMT_CLK_DIV; // 时钟分频 ESP_ERROR_CHECK(rmt_config(&tx_cfg)); ESP_ERROR_CHECK(rmt_driver_install(tx_cfg.channel, 0, 0)); } @@ -142,8 +138,7 @@ static mp_obj_t tx_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_k return MP_OBJ_FROM_PTR(self); } -static mp_obj_t tx_send(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) -{ +static mp_obj_t tx_send(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { const mp_arg_t allowed_args[] = { {MP_QSTR_data, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_obj = mp_const_none}}, }; @@ -167,7 +162,7 @@ static const mp_rom_map_elem_t tx_locals_dict_table[] = { MP_DEFINE_CONST_DICT(tx_locals_dict, tx_locals_dict_table); MP_DEFINE_CONST_OBJ_TYPE(mp_tx_type, MP_QSTR_Tx, MP_TYPE_FLAG_NONE, make_new, tx_make_new, locals_dict, - &tx_locals_dict); + &tx_locals_dict); // ================================================================================================= // class: rf433.Rx @@ -180,28 +175,26 @@ extern const mp_obj_type_t mp_rx_type; static mp_obj_t rx_callback = mp_const_none; static uint8_t rx_buffer[256]; -static size_t rx_len = 0; +static size_t rx_len = 0; static const size_t RX_MAX_LEN = 64; -static bool decode_bit(rmt_item32_t item) -{ +static bool decode_bit(rmt_item32_t item) { int high_us = item.duration0; - int low_us = item.duration1; + int low_us = item.duration1; // 根据 endode 参数修改 - return (high_us > 350 && high_us < 650 && low_us > 1350 && low_us < 1650); + return high_us > 350 && high_us < 650 && low_us > 1350 && low_us < 1650; } -static void decode_data(rmt_item32_t *items, size_t num_items) -{ - uint8_t byte = 0; - int bit_count = 0; - int rx_state = 0; - size_t data_len = 0; +static void decode_data(rmt_item32_t *items, size_t num_items) { + uint8_t byte = 0; + int bit_count = 0; + int rx_state = 0; + size_t data_len = 0; size_t buf_index = 0; for (int i = 0; i < num_items; i++) { bool bit = decode_bit(items[i]); - byte = (byte << 1) | bit; + byte = (byte << 1) | bit; bit_count++; if (bit_count == 8) { if (rx_state == 0 && byte == FRAME_HEADER) { @@ -217,11 +210,11 @@ static void decode_data(rmt_item32_t *items, size_t num_items) } } } else { - rx_state = 0; + rx_state = 0; buf_index = 0; } bit_count = 0; - byte = 0; + byte = 0; } } @@ -235,14 +228,13 @@ static void decode_data(rmt_item32_t *items, size_t num_items) } } -void app_rf433r_rx_decode(void *param) -{ +void app_rf433r_rx_decode(void *param) { RingbufHandle_t rb = NULL; rmt_get_ringbuf_handle(RMT_RX_CHANNEL, &rb); rmt_rx_start(RMT_RX_CHANNEL, true); while (rb) { - size_t rx_size = 0; + size_t rx_size = 0; rmt_item32_t *items = (rmt_item32_t *)xRingbufferReceive(rb, &rx_size, portMAX_DELAY); if (items) { size_t num_items = rx_size / sizeof(rmt_item32_t); @@ -253,8 +245,7 @@ void app_rf433r_rx_decode(void *param) } static bool rx_is_initialized; -static mp_obj_t rx_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) -{ +static mp_obj_t rx_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { rx_obj_t *self = mp_obj_malloc_with_finaliser(rx_obj_t, &mp_rx_type); static const mp_arg_t allowed_args[] = { @@ -269,14 +260,14 @@ static mp_obj_t rx_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_k if (!rx_is_initialized) { // TODO: deinit() 实现 rx_is_initialized = true; rmt_config_t rxconfig; - rxconfig.rmt_mode = RMT_MODE_RX; - rxconfig.channel = RMT_RX_CHANNEL; - rxconfig.gpio_num = self->in; - rxconfig.mem_block_num = 6; - rxconfig.clk_div = RMT_CLK_DIV; - rxconfig.rx_config.filter_en = true; + rxconfig.rmt_mode = RMT_MODE_RX; + rxconfig.channel = RMT_RX_CHANNEL; + rxconfig.gpio_num = self->in; + rxconfig.mem_block_num = 6; + rxconfig.clk_div = RMT_CLK_DIV; + rxconfig.rx_config.filter_en = true; rxconfig.rx_config.filter_ticks_thresh = 100 * RMT_1US_TICKS; - rxconfig.rx_config.idle_threshold = 3 * RMT_1MS_TICKS; + rxconfig.rx_config.idle_threshold = 3 * RMT_1MS_TICKS; ESP_ERROR_CHECK(rmt_config(&rxconfig)); ESP_ERROR_CHECK(rmt_driver_install(rxconfig.channel, 4096, 0)); @@ -286,8 +277,7 @@ static mp_obj_t rx_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_k return MP_OBJ_FROM_PTR(self); } -static mp_obj_t rx_start_recv(mp_obj_t self_in) -{ +static mp_obj_t rx_start_recv(mp_obj_t self_in) { if (rb == NULL) { rmt_get_ringbuf_handle(RMT_RX_CHANNEL, &rb); rmt_rx_start(RMT_RX_CHANNEL, true); @@ -296,16 +286,14 @@ static mp_obj_t rx_start_recv(mp_obj_t self_in) } static MP_DEFINE_CONST_FUN_OBJ_1(rx_start_recv_obj, rx_start_recv); -static mp_obj_t rx_stop_recv(mp_obj_t self_in) -{ +static mp_obj_t rx_stop_recv(mp_obj_t self_in) { rb = NULL; rmt_rx_stop(RMT_RX_CHANNEL); return mp_const_none; } static MP_DEFINE_CONST_FUN_OBJ_1(rx_stop_recv_obj, rx_stop_recv); -static mp_obj_t rx_set_recv_callback(mp_obj_t self_in, mp_obj_t callback) -{ +static mp_obj_t rx_set_recv_callback(mp_obj_t self_in, mp_obj_t callback) { if (mp_obj_is_callable(callback)) { rx_callback = callback; } else { @@ -315,13 +303,12 @@ static mp_obj_t rx_set_recv_callback(mp_obj_t self_in, mp_obj_t callback) } static MP_DEFINE_CONST_FUN_OBJ_2(rx_set_recv_callback_obj, rx_set_recv_callback); -static mp_obj_t rx_read(mp_obj_t self_in) -{ +static mp_obj_t rx_read(mp_obj_t self_in) { if (rx_len == 0) { return mp_const_none; } mp_obj_t data = mp_obj_new_bytes(rx_buffer, rx_len); - rx_len = 0; + rx_len = 0; return data; } static MP_DEFINE_CONST_FUN_OBJ_1(rx_read_obj, rx_read); @@ -336,7 +323,7 @@ static const mp_rom_map_elem_t rx_locals_dict_table[] = { MP_DEFINE_CONST_DICT(rx_locals_dict, rx_locals_dict_table); MP_DEFINE_CONST_OBJ_TYPE(mp_rx_type, MP_QSTR_Rx, MP_TYPE_FLAG_NONE, make_new, rx_make_new, locals_dict, - &rx_locals_dict); + &rx_locals_dict); // ================================================================================================= // module: rf433 @@ -348,7 +335,7 @@ static const mp_rom_map_elem_t rf433_globals_table[] = { static MP_DEFINE_CONST_DICT(rf433_globals, rf433_globals_table); const mp_obj_module_t module_rf433 = { - .base = {&mp_type_module}, + .base = {&mp_type_module}, .globals = (mp_obj_dict_t *)&rf433_globals, }; diff --git a/m5stack/modules/startup/atoms3r_cam.py b/m5stack/modules/startup/atoms3r_cam.py index 32b56008..90031b8b 100644 --- a/m5stack/modules/startup/atoms3r_cam.py +++ b/m5stack/modules/startup/atoms3r_cam.py @@ -11,8 +11,8 @@ # AtomS3-CAM startup menu -class AtomS3R_CAM_Startup(Startup): +class AtomS3R_CAM_Startup(Startup): def __init__(self) -> None: super().__init__() @@ -72,4 +72,4 @@ def startup(self, ssid: str, pswd: str, timeout: int = 60) -> None: self.show_hits(super().local_ip()) print("Local IP: " + super().local_ip()) else: - self.show_error("Not Found", "Use Burner setup") \ No newline at end of file + self.show_error("Not Found", "Use Burner setup") diff --git a/tools/codeformat.py b/tools/codeformat.py index 5754e237..4e33c443 100755 --- a/tools/codeformat.py +++ b/tools/codeformat.py @@ -47,6 +47,9 @@ EXCLUSIONS = [ # micropython upstream files that we don't want to format + "esp-adf/*", + "esp-idf/*", + "m5stack/cmodules/lv_binding_micropython/*", "micropython/*", "tools/*", ] From 941a1d59989de07583d340ea9df091d24c63ffe5 Mon Sep 17 00:00:00 2001 From: lbuque Date: Thu, 15 May 2025 16:08:34 +0800 Subject: [PATCH 084/322] docs: Fix some compilation errors. dut_lorawan_rui3.po caused the Chinese translation to fail to compile successfully, and the Chinese translation has now been deleted. Signed-off-by: lbuque --- docs/locales/zh_CN/LC_MESSAGES/advanced/dl.po | 347 ++++--- .../LC_MESSAGES/base/dtu_lorawan_rui3.po | 946 +++++++----------- .../zh_CN/LC_MESSAGES/base/dtu_nbiot.po | 171 ++++ .../LC_MESSAGES/controllers/atoms3r_cam.po | 91 ++ .../zh_CN/LC_MESSAGES/hardware/display.po | 370 +++---- .../zh_CN/LC_MESSAGES/module/dc_motor.po | 119 +-- .../zh_CN/LC_MESSAGES/module/lora_sx1262.po | 465 +++++++++ .../zh_CN/LC_MESSAGES/units/hbridge.po | 13 +- docs/locales/zh_CN/LC_MESSAGES/units/ncir2.po | 345 ++++--- 9 files changed, 1762 insertions(+), 1105 deletions(-) create mode 100644 docs/locales/zh_CN/LC_MESSAGES/base/dtu_nbiot.po create mode 100644 docs/locales/zh_CN/LC_MESSAGES/controllers/atoms3r_cam.po create mode 100644 docs/locales/zh_CN/LC_MESSAGES/module/lora_sx1262.po diff --git a/docs/locales/zh_CN/LC_MESSAGES/advanced/dl.po b/docs/locales/zh_CN/LC_MESSAGES/advanced/dl.po index b1468c2b..f063fc82 100644 --- a/docs/locales/zh_CN/LC_MESSAGES/advanced/dl.po +++ b/docs/locales/zh_CN/LC_MESSAGES/advanced/dl.po @@ -9,7 +9,7 @@ msgid "" msgstr "" "Project-Id-Version: UIFlow2 Programming Guide \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-01-16 17:26+0800\n" +"POT-Creation-Date: 2025-05-15 12:10+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language: zh_CN\n" @@ -20,419 +20,494 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.16.0\n" -#: ../../en/advanced/dl.rst:2 d33efed191fe49fd802092a0ec6d830c +#: ../../en/advanced/dl.rst:2 0adc36e72a7445f1998c3f59068b7b20 msgid "dl --- deep learning" msgstr "" -#: ../../en/advanced/dl.rst:6 302c4a9020344051b2d2a562b9fa7988 +#: ../../en/advanced/dl.rst:6 92885139b08a418ebb44ca76535d14dc msgid "This module is only applicable to the CoreS3 Controller" msgstr "当前模块只适用于 CoreS3 主机" -#: ../../en/advanced/dl.rst:12 1ae06fc3919a4badba9ff4f0a8aa4197 -msgid "Micropython Example" -msgstr "Micropython 案例" +#: ../../en/advanced/dl.rst:13 f5b4dcdc3db4439a845e19731c5e20c5 +#, fuzzy +msgid "UiFlow2 Example" +msgstr "UIFlow2.0 案例" -#: ../../en/advanced/dl.rst:15 ../../en/advanced/dl.rst:41 -#: 4897b5577909401da028285bdc1cdefc e211f4b02ca546f688391f2527868629 +#: ../../en/advanced/dl.rst:16 ../../en/advanced/dl.rst:80 +#: cdad876619c740f49f6341d0a5e6bff8 e0e940e0b264401b93990c8882d48df6 msgid "human face detect" msgstr "人脸检测" -#: ../../en/advanced/dl.rst:23 ../../en/advanced/dl.rst:50 -#: 6dfc0918dd2443f99dabbdea2e3f2654 dde1fb6f53564e16a244c066dc5f724f -msgid "pedestrain detect" -msgstr "行人检测" - -#: ../../en/advanced/dl.rst:30 ../../en/advanced/dl.rst:59 -#: 7d2b2723bd824b3d879e86bdf0128251 f8a7211b5f24437eba74782669542bca -msgid "human face recognition" -msgstr "人脸识别" - -#: ../../en/advanced/dl.rst:38 a7f59d96bd984cfead0f6aa4166ae9cd -msgid "UIFlow2.0 Example" -msgstr "UIFlow2.0 案例" +#: ../../en/advanced/dl.rst:18 b22e8a0c8a504469919f665234f50f9b +msgid "Open the |cores3_example_human_face_detect.m5f2| project in UiFlow2." +msgstr "" -#: ../../en/advanced/dl.rst:43 1b1e437ccca44e499493b66a937f86b1 +#: ../../en/advanced/dl.rst:20 d4e3d1e04c0a42d68930e260aee9b702 +msgid "" +"This example uses a face detection algorithm to detect faces in real time" +" from the camera feed. When a face is detected, a bounding box is drawn " +"on the screen to mark the face's position, providing an intuitive " +"visualization of the detection results." +msgstr "" + +#: ../../en/advanced/dl.rst:25 ../../en/advanced/dl.rst:44 +#: ../../en/advanced/dl.rst:67 ../../en/advanced/dl.rst:135 +#: ../../en/advanced/dl.rst:150 ../../en/advanced/dl.rst:164 +#: ../../en/advanced/dl.rst:183 ../../en/advanced/dl.rst:191 +#: ../../en/advanced/dl.rst:202 ../../en/advanced/dl.rst:212 +#: ../../en/advanced/dl.rst:222 ../../en/advanced/dl.rst:236 +#: ../../en/advanced/dl.rst:244 ../../en/advanced/dl.rst:252 +#: ../../en/advanced/dl.rst:260 ../../en/advanced/dl.rst:268 +#: ../../en/advanced/dl.rst:276 ../../en/advanced/dl.rst:290 +#: ../../en/advanced/dl.rst:304 ../../en/advanced/dl.rst:312 +#: 03ee6ae152d547d8b715d9179eb122d4 1232b0fe5bea413bab4fba7dcb06b8f6 +#: 2a9d10c490674b0c982c32c0932eb8d3 2d359b5b43774c68ae79feae59d55c12 +#: 4196c15f6758406091d074bf47cd5571 4956594219b44315952373bcde265ce3 +#: 4ae2c2042ea147f4a97c7062ca890036 4e3f747b08514013bc6d9c5e951db6cc +#: 50646e065d2e4e13a785f16ed30809d8 5d1b8c2e196a487bbec53886cc4346dd +#: 839bc8ee900f42d18b2a66a705a66c29 84f6a5a4d3944f80b51f4b04d1b019e8 +#: 8d5e2499263d48a29fd99d2eba476f39 8ebca032e0d8435bad5d34991fe2fada +#: aa348547ae1842abbe21b54a43e4aceb b143862e987c49109c0bd9b4bdcf527b +#: bc0dcec7abf941b28dad6cda9905304e c662d826b37640069eaf50e9e099ba58 +#: d0a5524802c44919ab25bec52790a276 f1e9bd7bb20a4184802c8db3599e609f +msgid "UiFlow2 Code Block:" +msgstr "" + +#: ../../en/advanced/dl.rst:27 b80d40ea807e468abcc81ea3449ea76a msgid "|human_face_detect_example.png|" msgstr "" -#: ../../en/refs/advanced.dl.ref:1 64021cc8592b4bfea400256f20c39120 +#: ../../en/refs/advanced.dl.ref:1 29578e8bf70d498c8da30e6c8b26a4f7 msgid "human_face_detect_example.png" msgstr "" -#: ../../en/advanced/dl.rst:47 91efe9f5e9364f08b213a3d08ee28c04 -msgid "|cores3_example_human_face_detect.m5f2|" +#: ../../en/advanced/dl.rst:29 ../../en/advanced/dl.rst:48 +#: ../../en/advanced/dl.rst:71 ../../en/advanced/dl.rst:89 +#: ../../en/advanced/dl.rst:102 ../../en/advanced/dl.rst:116 +#: 4113776e219747c0a344096df7293161 69a0975b5aaa485b87cd21fa412bc858 +#: 95c538bcea874420b14c9d3ce9eec21d b3493b37edf8417096c0606be043a5ea +#: d3507bc2432e4a9c8f6cfc52b87aff9e e6fa5aa841ec4be9b6a94bab0dc65d36 +msgid "Example output:" +msgstr "" + +#: ../../en/advanced/dl.rst:31 ../../en/advanced/dl.rst:50 +#: ../../en/advanced/dl.rst:73 ../../en/advanced/dl.rst:91 +#: ../../en/advanced/dl.rst:104 ../../en/advanced/dl.rst:118 +#: 27be72cb58bf448dbaf9c2eac3f1a4e0 47bdac7fa9504d479af826587206181a +#: 56f58ff987a74b5ba86aa16748bbe425 608a9161fd334245ada60adcfa87c2a6 +#: 7fade0cdcf894d8b81665456e6fea0ae d19297dcd5e4481c82aef863209c8109 +msgid "None" +msgstr "" + +#: ../../en/advanced/dl.rst:35 ../../en/advanced/dl.rst:94 +#: dd004bf56fae41a9b13f2542fa55ff4a f369fbd3900e4c74a1d43c4da4c52f71 +msgid "pedestrain detect" +msgstr "行人检测" + +#: ../../en/advanced/dl.rst:37 1bad0ac794154af897ca37115ebb100c +msgid "Open the |cores3_example_pedestrian_detect.m5f2| project in UiFlow2." +msgstr "" + +#: ../../en/advanced/dl.rst:39 5e130d1e9c864b539f8eb1b6c887fa05 +msgid "" +"This example uses a pedestrian detection algorithm to detect pedestrian " +"targets in real time from the camera feed. When a pedestrian is detected," +" a bounding box is drawn on the screen to highlight the pedestrian's " +"position, providing an intuitive demonstration of the detection results." msgstr "" -#: ../../en/advanced/dl.rst:52 e09293a14f3842288988320c3d76d909 +#: ../../en/advanced/dl.rst:46 636c65ad64d547b1a771e8dce05f05f1 msgid "|pedestrian_detect_example.png|" msgstr "" -#: ../../en/refs/advanced.dl.ref:3 9cbe8bfa56394065a448c0d702fcf80a +#: ../../en/refs/advanced.dl.ref:3 85d29e19a3e54ae19596282ce69e4efa msgid "pedestrian_detect_example.png" msgstr "" -#: ../../en/advanced/dl.rst:56 4a3bea2bf1e84c32869c0cf5e482281e -msgid "|cores3_example_pedestrian_detect.m5f2|" +#: ../../en/advanced/dl.rst:53 ../../en/advanced/dl.rst:108 +#: dac98ebcd0144546a5e33ddffadf965a +msgid "human face recognition" +msgstr "人脸识别" + +#: ../../en/advanced/dl.rst:55 916ba375157a4f5a9964d4fc8c021833 +msgid "Open the |cores3_example_human_face_recognition.m5f2| project in UiFlow2." +msgstr "" + +#: ../../en/advanced/dl.rst:57 21839992474848a0adc7810f861c124b +msgid "" +"To run this example, you will need the `CoreS3 " +"`_ and the `Unit Dual Button " +"`_." +msgstr "" + +#: ../../en/advanced/dl.rst:60 0bb99911d13548c983d40d9cdc0daec1 +msgid "" +"This example uses a face recognition algorithm to detect faces in real " +"time from the camera feed." +msgstr "" + +#: ../../en/advanced/dl.rst:63 333525ec65c44427bde039f25d54f553 +msgid "" +"By pressing different buttons, you can either enroll new face data or " +"perform face recognition. The detected faces and recognition results are " +"displayed on the screen with bounding boxes." msgstr "" -#: ../../en/advanced/dl.rst:61 01cd930e6889417997de60a086c885c9 +#: ../../en/advanced/dl.rst:69 58d246b8c8b948db930fd9eb72961209 msgid "|human_face_recognition_example.png|" msgstr "" -#: ../../en/refs/advanced.dl.ref:2 1ffa34386fcd4c5aad3ef2b131fafc6e +#: ../../en/refs/advanced.dl.ref:2 e866bbbd58bf419a8819a55eba8c31d4 msgid "human_face_recognition_example.png" msgstr "" -#: ../../en/advanced/dl.rst:65 ae38dfdd769e40018acdeb6f48c81805 -msgid "|cores3_example_human_face_recognition.m5f2|" -msgstr "" +#: ../../en/advanced/dl.rst:77 93ec7127e6e84ae29c5c0c5b5bd9a4d8 +msgid "Micropython Example" +msgstr "Micropython 案例" -#: ../../en/advanced/dl.rst:68 a7e46f9a56fb44a88380e1f80a876cff +#: ../../en/advanced/dl.rst:83 ../../en/advanced/dl.rst:96 +#: ../../en/advanced/dl.rst:110 93ec7127e6e84ae29c5c0c5b5bd9a4d8 +#, fuzzy +msgid "MicroPython Code Block:" +msgstr "Micropython 案例" + +#: ../../en/advanced/dl.rst:122 ddb66941acea4093a342f7c997497750 msgid "Funtions" msgstr "" -#: ../../en/advanced/dl.rst:72 5ad54cc2888b4d5786abe34731098649 +#: ../../en/advanced/dl.rst:126 b3b188566fa04d8d979d87e595aa6567 msgid "Create an object detector instance." msgstr "" -#: ../../en/advanced/dl.rst 8b85f87decc14fb981adb0e1130d36c1 +#: ../../en/advanced/dl.rst c2e93afe726b4dacb39ae02a4989da03 msgid "Parameters" msgstr "" -#: ../../en/advanced/dl.rst:74 e176a594bfec481691869865c84d07c8 +#: ../../en/advanced/dl.rst:128 0ee6f31ebea14d028a2bc4c7a9594248 msgid "Load a detection model. Supported values:" msgstr "参数 ``model`` 仅接受以下值:" -#: ../../en/advanced/dl.rst:76 74ee0be5803a44e8baf362a2efa15569 +#: ../../en/advanced/dl.rst:130 f3ec4dfc2dd042d3aec2426dc6ed1747 msgid "``dl.model.HUMAN_FACE_DETECT``: Human face detection." msgstr "``dl.model.HUMAN_FACE_DETECT`` 人脸检测" -#: ../../en/advanced/dl.rst:77 eb540191401045f196aab8cefd1d28ca +#: ../../en/advanced/dl.rst:131 39809ca838a24d97bc5677872b514437 msgid "``dl.model.PEDESTRIAN_DETECT``: Pedestrian detection." msgstr "``dl.model.PEDESTRIAN_DETECT`` 行人检测" -#: ../../en/advanced/dl.rst:79 9748c29165ad42b49578a2b1aa7bbfe3 +#: ../../en/advanced/dl.rst:133 a9d2fd70f85c4b12a8e52701f04b4aab msgid "Returns An ``ObjectDetector`` instance." msgstr "返回 ``ObjectDetector`` 对象." -#: ../../en/advanced/dl.rst:81 ../../en/advanced/dl.rst:96 -#: ../../en/advanced/dl.rst:108 ../../en/advanced/dl.rst:125 -#: ../../en/advanced/dl.rst:133 ../../en/advanced/dl.rst:145 -#: ../../en/advanced/dl.rst:155 ../../en/advanced/dl.rst:165 -#: ../../en/advanced/dl.rst:178 ../../en/advanced/dl.rst:186 -#: ../../en/advanced/dl.rst:194 ../../en/advanced/dl.rst:202 -#: ../../en/advanced/dl.rst:210 ../../en/advanced/dl.rst:218 -#: ../../en/advanced/dl.rst:232 ../../en/advanced/dl.rst:245 -#: ../../en/advanced/dl.rst:253 07fb675ec67643059c7213481da28c04 -#: 1367559b07194e1c82e42655c6f19406 153cb69936a24366872e73fe89baf70a -#: 1d282ee7c9b44e35bd2620b833ae6c62 30d54d7a3fcd46aba113d0c5825f8472 -#: 4cc9916c74764e4f8c82ccae9765f517 4d26f9b930754536a9341742d8c5952a -#: 4d3c7031b26942dea318e8c09259d1af 5d40b6517a874edca63802263a664685 -#: 7a470c53f02c4523a0d1fce11f82205c 896b84c3c1f046728092c1c549ced36e -#: 927e937acb2d476888d258da146dbb7f 9a52f6888c2a45ef8d0777dd2a8fbf35 -#: e484bf2b6f8348b9ac7c906b07cbad72 e69efae75ec14ea9825c24dd06a10741 -#: ede89a7da6f44a71a731eb2141c53e53 efcc23b835db43859827275709b1c333 -msgid "UIFlow2.0" -msgstr "" - -#: ../../en/advanced/dl.rst:83 83f9106efcf34f80ada9a56e0d167760 +#: ../../en/advanced/dl.rst:137 ba88d8b262ad4a86beab068d04b827fd msgid "|ObjectDetector.png|" msgstr "" -#: ../../en/refs/advanced.dl.ref:4 8ddf37e1f9be4576b00b7e4aa5b363d1 +#: ../../en/refs/advanced.dl.ref:4 0afd6bdda13a46b09a3c01c4eb980981 msgid "ObjectDetector.png" msgstr "" -#: ../../en/advanced/dl.rst:85 5af68c1d7bfa42c68198534cf5b77788 +#: ../../en/advanced/dl.rst:139 3633e618d0714096b794be7bcab44e91 msgid "**Example**::" msgstr "" -#: ../../en/advanced/dl.rst:92 838ceffef8e04829988beea5c3ee3b93 +#: ../../en/advanced/dl.rst:146 a2230e44146b4da6884c551f5d92338d msgid "Create a human face recognizer instance." msgstr "创建一个人脸识别器" -#: ../../en/advanced/dl.rst:94 8c6b637858984bfc81df464ac6ffdc4a +#: ../../en/advanced/dl.rst:148 ec2c0b97281c425ba85e7e404a0fe7e9 msgid "Returns: A ``HumanFaceRecognizer`` instance." msgstr "返回 ``HumanFaceRecognizer`` 对象." -#: ../../en/advanced/dl.rst:98 3183f6c605bd4d2783b32a2ed838cd31 +#: ../../en/advanced/dl.rst:152 6fe9a0094bff4793a53f9e84bcfc5a58 msgid "|HumanFaceRecognizer.png|" msgstr "" -#: ../../en/refs/advanced.dl.ref:15 761201ed851a450c96eaa3d268be1023 +#: ../../en/refs/advanced.dl.ref:15 9875f5938cfb49e39280004e708d8f0d msgid "HumanFaceRecognizer.png" msgstr "" -#: ../../en/advanced/dl.rst:101 e159ec9dc1154be5909a15a1f3af2f02 +#: ../../en/advanced/dl.rst:156 cab4c806fad4463a90a75cfd27cc9fa3 msgid "class ObjectDetector" msgstr "" -#: ../../en/advanced/dl.rst:102 a9f7643fee2e4a319888d5e883e15137 +#: ../../en/advanced/dl.rst:158 2770e6871d1e45f2b31fc162e42805e7 msgid "The ObjectDetector object is returned by `dl.ObjectDetector(model)`." msgstr "``ObjectDetector`` 对象由 dl.ObjectDetector(model) 返回" -#: ../../en/advanced/dl.rst:106 4c0821cd4449468c9304d2c9dee5033e +#: ../../en/advanced/dl.rst:162 d9376a08b1f04860a7cdf758e489f5a7 msgid "Returns: A ``DetectionResult`` instance." msgstr "返回一个 ``DetectionResult`` 实例." -#: ../../en/advanced/dl.rst:110 bd7c9ca23d334931a5093819097bc53f +#: ../../en/advanced/dl.rst:166 605b7407bd754b66a84ca31373f410b2 msgid "|infer.png|" msgstr "" -#: ../../en/refs/advanced.dl.ref:5 524bf3f88bf34468bb3f5179d5036ddf +#: ../../en/refs/advanced.dl.ref:5 52e994401a054f8fb6ca17853f7e31f2 msgid "infer.png" msgstr "" -#: ../../en/advanced/dl.rst:113 5fc1ac04f2784f91af7ae8c71666a3b2 +#: ../../en/advanced/dl.rst:170 96976a8f378d461cb32ac834b70fd9bb msgid "class HumanFaceRecognizer" msgstr "" -#: ../../en/advanced/dl.rst:114 3d9c03e16580445dbace823731fbbcca +#: ../../en/advanced/dl.rst:172 ba5fe84c548940ae8109c2bab04326d4 msgid "The HumanFaceRecognizer object is returned by `dl.HumanFaceRecognizer()`." msgstr "``HumanFaceRecognizer`` 对象由 dl.HumanFaceRecognizer() 返回" -#: ../../en/advanced/dl.rst:118 68372be3847b44ee95543f3f7897ad59 +#: ../../en/advanced/dl.rst:176 1a3673e19a7a442582eed1d7aa5ef464 msgid "Face recognize" msgstr "人脸识别" -#: ../../en/advanced/dl.rst:120 ../../en/advanced/dl.rst:142 -#: 0ddb5dfd31b9497b8437f6e4f4b349b4 e5cdaf44d45344d580e26d9dd58cc1fa +#: ../../en/advanced/dl.rst:178 ../../en/advanced/dl.rst:199 +#: 21ff0fc75eb34de9a62cc85622e6aeca msgid "``img`` imput image" msgstr "``img`` 输入图像" -#: ../../en/advanced/dl.rst:121 ../../en/advanced/dl.rst:143 -#: b9f2296be2c14517a9f0ed6a3548c564 f412636bf8d24ad7b2fab70730c74354 +#: ../../en/advanced/dl.rst:179 ../../en/advanced/dl.rst:200 +#: 44e6719d82c94f1faf53304321359749 msgid "``keypoint`` face keypoint, ref: DetectionResult.keypoint()" msgstr "``keypoint`` 人脸关键点数据,详情参考 DetectionResult.keypoint() 解析" -#: ../../en/advanced/dl.rst:123 4279cdf0c2ba4fda9e90feae13094b12 +#: ../../en/advanced/dl.rst:181 be10e961f9874ddbb1ee939328c789f8 msgid "Returns an ``RecognitionResult`` object" msgstr "返回 ``RecognitionResult`` 对象。" -#: ../../en/advanced/dl.rst:127 e5b163fcfe3a4965b798cb2d26300464 +#: ../../en/advanced/dl.rst:185 ab20dccee05a476fadf061513f742981 msgid "|recognize.png|" msgstr "" -#: ../../en/refs/advanced.dl.ref:21 d3104cb373f74968a794ae240300bcd0 +#: ../../en/refs/advanced.dl.ref:21 f5d702aa57fa4145a2c7a673a8c18fff msgid "recognize.png" msgstr "" -#: ../../en/advanced/dl.rst:131 1949b8a3c4504ec3958f2333d01f0118 +#: ../../en/advanced/dl.rst:189 d26fca4cc2e94c4888a8802186b535f7 msgid "clear id" msgstr "清空所有 id" -#: ../../en/advanced/dl.rst:135 1a2f7a850bc94cd8be59c9e89d0a6bbc +#: ../../en/advanced/dl.rst:193 9e61d856d8dc49a6992bc246afcd3fca msgid "|clear_id.png|" msgstr "" -#: ../../en/refs/advanced.dl.ref:20 23e2c638b4444fafbd76156a3709026f +#: ../../en/refs/advanced.dl.ref:20 b5af9258a66b4d0da37fad5df9632bac msgid "clear_id.png" msgstr "" -#: ../../en/advanced/dl.rst:140 279ec529d6994852aa22af85591a0c54 +#: ../../en/advanced/dl.rst:197 030d75265d84488791bd7c4dd3ed8d52 msgid "enroll id" msgstr "" -#: ../../en/advanced/dl.rst:147 16f0e90486da4f509d575d260fbc362e +#: ../../en/advanced/dl.rst:204 160731ee5d2c4b758cbda570941813e5 msgid "|enroll_id.png|" msgstr "" -#: ../../en/refs/advanced.dl.ref:19 9844eac0d9194478a6037490fe69321f +#: ../../en/refs/advanced.dl.ref:19 3548978c06d746c2aeeb2aabc24b758f msgid "enroll_id.png" msgstr "" -#: ../../en/advanced/dl.rst:151 2522a530edaa4f07a39833c51223400d +#: ../../en/advanced/dl.rst:208 3eda184de7f848f2ad37c9e3a17f5924 msgid "delete id" msgstr "" -#: ../../en/advanced/dl.rst:153 73e57285e453465eb1d98d0633a0c66f +#: ../../en/advanced/dl.rst:210 32b1dcd2b01a4d05a738cd95fd4c9994 msgid "" "id is an optional parameter. If provided, it deletes the specified face " "information. By default, it deletes the most recently recorded id." msgstr "``id`` 为可选参数,输入 ``id`` 为删除指定的人脸信息,默认为删除最近一次录入的 ``id``。" -#: ../../en/advanced/dl.rst:157 a62758126fdb4d6da3867980a5a84613 +#: ../../en/advanced/dl.rst:214 3e2b7cbe29c849519debd5fba0c32e43 msgid "|delete_id.png|" msgstr "" -#: ../../en/refs/advanced.dl.ref:18 ba2a60ad4a054acaad12c812849a2266 +#: ../../en/refs/advanced.dl.ref:18 1b1a162abe5842609647533ef38b694d msgid "delete_id.png" msgstr "" -#: ../../en/advanced/dl.rst:159 73b86eab52404146aa95ded93941a770 +#: ../../en/advanced/dl.rst:216 662553085b374a07a8d9707b54f73ead msgid "|delete_last_id.png|" msgstr "" -#: ../../en/refs/advanced.dl.ref:17 e9277588d5a84cfc89dfc897dcf8aedc +#: ../../en/refs/advanced.dl.ref:17 fea80d3efa3f4610990244ab8de30d4a msgid "delete_last_id.png" msgstr "" -#: ../../en/advanced/dl.rst:163 4aaa17a6946f4f658e9ce657237014f6 +#: ../../en/advanced/dl.rst:220 302bd69d69554ce2b526026d5b6be112 msgid "get enrolled id num" msgstr "返回已录入的 id 数" -#: ../../en/advanced/dl.rst:167 4e0535e0a53749ebab2ecb18adffaa1a +#: ../../en/advanced/dl.rst:224 1e8dfbfd5dbe44d284c2435a3345abf3 msgid "|enrolled_id_num.png|" msgstr "" -#: ../../en/refs/advanced.dl.ref:16 92e9a241fbdf489b92b214bee927b68e +#: ../../en/refs/advanced.dl.ref:16 8eb6911bf60c44e48a36dd340f58c121 msgid "enrolled_id_num.png" msgstr "" -#: ../../en/advanced/dl.rst:170 9f36457706f54592a312cd1492957c7b +#: ../../en/advanced/dl.rst:228 dc774aca3e0348b4a9d5fdaf6c043649 msgid "class DetectionResult -- DetectionResult object" msgstr "" -#: ../../en/advanced/dl.rst:172 c20656456e6d4bdbb5b2807bba3a8a23 +#: ../../en/advanced/dl.rst:230 7f0b80e2b0f340b2b5fdcf83b5200bf9 msgid "The line object is returned by `ObjectDetector.infer()`." msgstr "``DetectionResult`` 对象由 ObjectDetector.infer(img) 返回" -#: ../../en/advanced/dl.rst:176 78f088f37edf44e0b6d54094396436e2 +#: ../../en/advanced/dl.rst:234 d3c08d5f36c84b9f85e9ecdc73dcce08 msgid "Get the bounding box of the object detection." msgstr "获取目标检测的边界框。" -#: ../../en/advanced/dl.rst:180 cbb8541164114576ab51fb877e16b75b +#: ../../en/advanced/dl.rst:238 6650262cd9864578ad90900d4191006e msgid "|get_bbox.png|" msgstr "" -#: ../../en/refs/advanced.dl.ref:7 b9d63b32388046b58c9a0409aefd982e +#: ../../en/refs/advanced.dl.ref:7 3a8817ec1d2f4ab48b230cf9b049b5b2 msgid "get_bbox.png" msgstr "" -#: ../../en/advanced/dl.rst:184 46e2769efa1a495b8e7f9db14f8ef3c0 +#: ../../en/advanced/dl.rst:242 f7c1b62ee4d047628dc4df86d6bffd9f msgid "The x-coordinate of the top-left corner of the bounding box." msgstr "获取边界框的左上角坐标 x。" -#: ../../en/advanced/dl.rst:188 a9aecb9d5a664a0ea2d63ab85971b9c2 +#: ../../en/advanced/dl.rst:246 51d54820ed1945f7863a3a06f3c81f83 msgid "|get_x.png|" msgstr "" -#: ../../en/refs/advanced.dl.ref:8 ee8b77531d034626a8b45fdce70f169d +#: ../../en/refs/advanced.dl.ref:8 7dce69b6582c441abe400b5498453c38 msgid "get_x.png" msgstr "" -#: ../../en/advanced/dl.rst:192 d3804b8457694656a4897947694634bb +#: ../../en/advanced/dl.rst:250 5ca2d8a95c0c46e9b149e588dbd60662 msgid "The y-coordinate of the top-left corner of the bounding box." msgstr "获取边界框的左上角坐标 y。" -#: ../../en/advanced/dl.rst:196 53f342c8c4d84665a0d08136be1e374a +#: ../../en/advanced/dl.rst:254 6674203c68224aaa95474a2e981d6c03 msgid "|get_y.png|" msgstr "" -#: ../../en/refs/advanced.dl.ref:9 6e511967007047ac9b6be233f9ab6200 +#: ../../en/refs/advanced.dl.ref:9 3396fb765065424da6eb0b829dc6aca3 msgid "get_y.png" msgstr "" -#: ../../en/advanced/dl.rst:200 12e55a1c291145dbbf6f90de1fbf6be9 +#: ../../en/advanced/dl.rst:258 7465eacfb7644bd89e2ee6810599cfe6 msgid "The width of the bounding box." msgstr "获取边界框的宽度。" -#: ../../en/advanced/dl.rst:204 a23ee16a73fd4570a64ff125a7d8dc30 +#: ../../en/advanced/dl.rst:262 5264216010df4ddfb51c7612e574101a msgid "|get_w.png|" msgstr "" -#: ../../en/refs/advanced.dl.ref:10 51bfd41ddc42445db5f01bf742059145 +#: ../../en/refs/advanced.dl.ref:10 3774e8f02e8f43b6b54d5409cb9fbfeb msgid "get_w.png" msgstr "" -#: ../../en/advanced/dl.rst:208 72ba6ec500ca40649a57b4b2623b3495 +#: ../../en/advanced/dl.rst:266 33fa3095f1bc42d684445f8378ef0268 msgid "The height of the bounding box." msgstr "获取边界框的高度。" -#: ../../en/advanced/dl.rst:212 57196987d9fc4de3b42d63521578cf8b +#: ../../en/advanced/dl.rst:270 25e4b03e75774ec590cade4a7a473a00 msgid "|get_h.png|" msgstr "" -#: ../../en/refs/advanced.dl.ref:11 dad9fd75753541fc93b1b314a8fae1a2 +#: ../../en/refs/advanced.dl.ref:11 a7e0923183a545959824680ae34a8efb msgid "get_h.png" msgstr "" -#: ../../en/advanced/dl.rst:216 57871d19effd48519d311d397446f340 +#: ../../en/advanced/dl.rst:274 43dae163cc8743219a13d658b94921a6 msgid "The detected object's category." msgstr "检测到的目标类别。" -#: ../../en/advanced/dl.rst:220 1e86efcfd7ac41d4b545608c228fcbbb +#: ../../en/advanced/dl.rst:278 2d4d15ea159449c4bf1befd025e4be35 msgid "|get_category.png|" msgstr "" -#: ../../en/refs/advanced.dl.ref:12 e6ede510dd42429f9d9f4afd8bd98926 +#: ../../en/refs/advanced.dl.ref:12 071965154d6b4d25bf1d8205654ddd5d msgid "get_category.png" msgstr "" -#: ../../en/advanced/dl.rst:224 064bada17d0f4bb396afea6d511aed95 +#: ../../en/advanced/dl.rst:282 9369cbaed92448e0b67eec08a34fabe9 msgid "" "Keypoint information (currently, only the face detection model outputs " "this data):" msgstr "关键点信息(当前只有人脸检测模型输出包含此数据)" -#: ../../en/advanced/dl.rst:226 d7d09ed5c6874e3e854b543c4a771a89 +#: ../../en/advanced/dl.rst:284 9557e0c64f96463d8374c2d1c54eadb6 msgid "``keypoint()[0], keypoint()[1]`` are the coordinates of the left eye." msgstr "``keypoint()[0], keypoint()[1]`` 为左眼坐标" -#: ../../en/advanced/dl.rst:227 955152265c204701b1f0e2e6e1a8e982 +#: ../../en/advanced/dl.rst:285 7599988577de4037a7c3bf381d1e6e01 msgid "" "``keypoint()[2], keypoint()[3]`` are the coordinates of the left corner " "of the mouth." msgstr "``keypoint()[2], keypoint()[3]`` 为嘴巴左角坐标" -#: ../../en/advanced/dl.rst:228 87f9a08b13054e2d9ca8c8b192c45b66 +#: ../../en/advanced/dl.rst:286 2f6942b26fe640039a3915d8c54d1e46 msgid "``keypoint()[4], keypoint()[5]`` are the coordinates of the nose." msgstr "``keypoint()[4], keypoint()[5]`` 为鼻子坐标" -#: ../../en/advanced/dl.rst:229 6b239273d2f84d18953350fbd85d2365 +#: ../../en/advanced/dl.rst:287 1d3fb6376711482ea26077576d5160e2 msgid "``keypoint()[6], keypoint()[7]`` are the coordinates of the right eye." msgstr "``keypoint()[6], keypoint()[7]`` 为右眼坐标" -#: ../../en/advanced/dl.rst:230 c34557883708492595543b7211a58fe1 +#: ../../en/advanced/dl.rst:288 811aed150d604208b3616b93b23a3278 msgid "" "``keypoint()[8], keypoint()[9]`` are the coordinates of the right corner " "of the mouth." msgstr "``keypoint()[8], keypoint()[9]`` 嘴巴右角" -#: ../../en/advanced/dl.rst:234 c238466dc37444f9acdf04bc679a1f46 +#: ../../en/advanced/dl.rst:292 38c27328950b414296498feae581eb21 msgid "|get_keypoint.png|" msgstr "" -#: ../../en/refs/advanced.dl.ref:13 fea3f4e817df4876a91b213e7532571a +#: ../../en/refs/advanced.dl.ref:13 f72ae1edda384d2ebb926239f851abd2 msgid "get_keypoint.png" msgstr "" -#: ../../en/advanced/dl.rst:237 b43aa1300bb44b1f8a585b99272fa50c +#: ../../en/advanced/dl.rst:296 d513bbc502154cc29c308b3a0d45f9c6 msgid "class RecognitionResult -- RecognitionResult object" msgstr "" -#: ../../en/advanced/dl.rst:239 0349bc3a184e40e79fb4a35a75adc93f +#: ../../en/advanced/dl.rst:298 9c980b0815c04c099229c1f668faa569 msgid "" "The ``RecognitionResult`` is returned by " "`HumanFaceRecognizer.recognize(img, keypoint)`." -msgstr "``DetectionResult`` 对象由 HumanFaceRecognizer.recognize(img, keypoint) 返回 -" +msgstr "``DetectionResult`` 对象由 HumanFaceRecognizer.recognize(img, keypoint) 返" -#: ../../en/advanced/dl.rst:243 52589d8f3de3416eb3984756fbc3cfd5 +#: ../../en/advanced/dl.rst:302 ac7ddafd708a4803b63be7acb2828eb2 msgid "" "Gets the face similarity, with a value closer to 1 indicating higher " "similarity." msgstr "获取人脸相似度,越接近1表示相似度越高。" -#: ../../en/advanced/dl.rst:247 3d09dc4af773482e882a3b0c105e4cf1 +#: ../../en/advanced/dl.rst:306 449101af0cf149f2b34aa6b0ce63babd msgid "|similarity.png|" msgstr "" -#: ../../en/refs/advanced.dl.ref:22 b46bd8df9e6442df831f13dd7bd2176d +#: ../../en/refs/advanced.dl.ref:22 f8b4908fd6f34423a22c49a9bd38d465 msgid "similarity.png" msgstr "" -#: ../../en/advanced/dl.rst:251 576c91016aac44f39e45c3d37c24a820 +#: ../../en/advanced/dl.rst:310 a14c9d95372b427695a01c5e60cd2129 msgid "" "Gets the face ID. A value greater than 0 indicates that the face " "recognition was successful." msgstr "获取人脸id,大于0表示人脸识别成功。" -#: ../../en/advanced/dl.rst:255 4ebd8213ce1d4382a93e201b550b927c +#: ../../en/advanced/dl.rst:314 c7cefb7579884ebd81d3e7c179b3cbfe msgid "|id.png|" msgstr "" -#: ../../en/refs/advanced.dl.ref:23 914e6a54ceb84dd6ba1a56b609b7de73 +#: ../../en/refs/advanced.dl.ref:23 aaff043715eb443890ae9f919a5da692 msgid "id.png" msgstr "" +#~ msgid "|cores3_example_human_face_detect.m5f2|" +#~ msgstr "" + +#~ msgid "|cores3_example_pedestrian_detect.m5f2|" +#~ msgstr "" + +#~ msgid "|cores3_example_human_face_recognition.m5f2|" +#~ msgstr "" + +#~ msgid "UIFlow2.0" +#~ msgstr "" + diff --git a/docs/locales/zh_CN/LC_MESSAGES/base/dtu_lorawan_rui3.po b/docs/locales/zh_CN/LC_MESSAGES/base/dtu_lorawan_rui3.po index d8b1bd7f..ffada158 100644 --- a/docs/locales/zh_CN/LC_MESSAGES/base/dtu_lorawan_rui3.po +++ b/docs/locales/zh_CN/LC_MESSAGES/base/dtu_lorawan_rui3.po @@ -7,11 +7,11 @@ #, fuzzy msgid "" msgstr "" -"Project-Id-Version: UIFlow2 Programming Guide\n" +"Project-Id-Version: UIFlow2 Programming Guide \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-04-27 09:51+0800\n" -"PO-Revision-Date: 2025-04-26 18:03+0800\n" -"Last-Translator: \n" +"POT-Creation-Date: 2025-05-15 15:04+0800\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" "Language: zh_CN\n" "Language-Team: zh_CN \n" "Plural-Forms: nplurals=1; plural=0;\n" @@ -20,15 +20,15 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.16.0\n" -#: ../../en/base/dtu_lorawan_rui3.rst:3 7743a1b5d13144f1a942ca037e761fd3 +#: ../../en/base/dtu_lorawan_rui3.rst:2 88420f6b9aa44bef8ba8e1c014f13b7b msgid "Atom DTU LoRaWAN-Series(RAK3172) Base" msgstr "" -#: ../../en/base/dtu_lorawan_rui3.rst:7 b1feb5944ee44d3fba9355f2e4fee756 +#: ../../en/base/dtu_lorawan_rui3.rst:8 97c70f55bded4569b0e62a9987295ee9 msgid "SKU: A152-CN470, A152-US915, A152-EU868" msgstr "" -#: ../../en/base/dtu_lorawan_rui3.rst:9 8661ccacabf94f7d98ec2dd6d87a73fa +#: ../../en/base/dtu_lorawan_rui3.rst:10 dced6dd2b6414b1f8da2e444d92679ce msgid "" "The Atom DTU LoRaWAN-Series is a LoRaWAN programmable data transfer unit " "(DTU) based on the STM32WLE5 chip. The module supports long-range " @@ -36,195 +36,199 @@ msgid "" " making it suitable for IoT communication needs in a variety of complex " "environments." msgstr "" -"Atom DTU LoRaWAN-Series 是一款基于 STM32WLE5 芯片的 LoRaWAN " -"可编程数据传输单元(DTU)。该模块支持远距离通信、低功耗运行,并具备高灵敏度特性,适用于多种复杂环境的物联网通信需求。" -#: ../../en/base/dtu_lorawan_rui3.rst:11 176536663f524c008630fe4894b4bf88 +#: ../../en/base/dtu_lorawan_rui3.rst:12 a8963334c0b84fc0a3a3cbe383cf3071 msgid "**Frequency band support**: CN470 (470MHz), EU868 (868MHz), US915 (915MHz)" -msgstr "**频段支持**:CN470(470MHz),EU868(868MHz),US915(915MHz)" +msgstr "" -#: ../../en/base/dtu_lorawan_rui3.rst:12 825ae53602e54a9fa86de11ce04e325c +#: ../../en/base/dtu_lorawan_rui3.rst:13 274318a6c2e34acd80d618d5250fb5b8 msgid "**Communication protocol**:" -msgstr "**通信协议**:" +msgstr "" -#: ../../en/base/dtu_lorawan_rui3.rst:14 4a8f346871d54d05aaecbac9991aeaf2 +#: ../../en/base/dtu_lorawan_rui3.rst:15 35b649a8a1d747e39fd31456e82f6337 msgid "Supports LoRaWAN Class A, Class B, Class C modes" -msgstr "支持 LoRaWAN Class A、Class B、Class C 模式" +msgstr "" -#: ../../en/base/dtu_lorawan_rui3.rst:15 b459d414995241c88807679f82f7150e +#: ../../en/base/dtu_lorawan_rui3.rst:16 c6e31598d62a427c8b2431373b5e11ed msgid "Supports LoRa Point-to-Point (P2P) communication mode." -msgstr "支持 LoRa 点对点(P2P)通信模式" +msgstr "" -#: ../../en/base/dtu_lorawan_rui3.rst:17 9215f0cb920d4cd59d5c16e73e732bdf +#: ../../en/base/dtu_lorawan_rui3.rst:18 730f337f6e64466caa765255f32e9415 msgid "**Communication Interface**:" -msgstr "**通信接口**:" +msgstr "" -#: ../../en/base/dtu_lorawan_rui3.rst:19 893fd621776f43419c1c600e89b7d03b +#: ../../en/base/dtu_lorawan_rui3.rst:20 27eec14aa3114078840c99146b3483fc msgid "" "UART interface: Used to send AT commands to control LoRaWAN network " "access, data sending/receiving, P2P mode communication, etc." -msgstr "UART 接口:用于发送 AT 指令,控制 LoRaWAN 入网、数据发送/接收、P2P 模式通信等" +msgstr "" -#: ../../en/base/dtu_lorawan_rui3.rst:20 7aeff81323ad4558b9b6a3549b850252 +#: ../../en/base/dtu_lorawan_rui3.rst:21 3b342846ee1b45df823e3c33cef4fe60 msgid "" "RS485 interface: supports wired communication of industrial equipment " "with high reliability." -msgstr "RS485 接口:支持工业设备有线通信,可靠性高" +msgstr "" -#: ../../en/base/dtu_lorawan_rui3.rst:22 0eee0282167b4a71a160d0157f8c4dab +#: ../../en/base/dtu_lorawan_rui3.rst:23 e06c162f98a740678359d13e3840e3ef msgid "**Internet access method**:" -msgstr "**入网方式**:" +msgstr "" -#: ../../en/base/dtu_lorawan_rui3.rst:24 47bd24c5457349b7bfcfa57775c14d6a +#: ../../en/base/dtu_lorawan_rui3.rst:25 e51e2011e2fe41e1a98219f401ec33af msgid "OTAA (Over-The-Air Activation)" -msgstr "OTAA(Over-The-Air Activation,空中激活)" +msgstr "" -#: ../../en/base/dtu_lorawan_rui3.rst:25 b3d229cc4f5f4eb485ab96840ca09264 +#: ../../en/base/dtu_lorawan_rui3.rst:26 4c1c22648e1f4a24a975c30dcf07f285 msgid "ABP (Activation By Personalization)" -msgstr "ABP(Activation By Personalization,手动激活)" +msgstr "" -#: ../../en/base/dtu_lorawan_rui3.rst:27 59eea72ceb0b444f983b95b7628e836a +#: ../../en/base/dtu_lorawan_rui3.rst:28 78c682f0ee3c43658d753bac9e4cefbb msgid "Support the following products:" -msgstr "支持以下产品:" +msgstr "" -#: ../../en/base/dtu_lorawan_rui3.rst:30 d1472c7b9a9d4c09a8fe1ed6a7f9053c +#: ../../en/base/dtu_lorawan_rui3.rst:31 c48fec0a5ec54e1796d0c5fefb0dad68 msgid "|LoRaWAN-CN470|" msgstr "" -#: ../../en/refs/base.lorawan_rui3.ref 46ac5f78cd3d4f818855faad18aa9f55 +#: ../../en/refs/base.lorawan_rui3.ref a13d648b38074beca4b56a0cc0401c18 msgid "LoRaWAN-CN470" msgstr "" -#: ../../en/base/dtu_lorawan_rui3.rst:30 78b7237b55a34d79aa84ec93e124dab9 +#: ../../en/base/dtu_lorawan_rui3.rst:31 8435c0c7f3ce48a6a533395910454643 msgid "|LoRaWAN-EU868|" msgstr "" -#: ../../en/refs/base.lorawan_rui3.ref d190b6dc1a544a31a6f8c66c52d52c2f +#: ../../en/refs/base.lorawan_rui3.ref a587937de9864f6dbe072bb8787db136 msgid "LoRaWAN-EU868" msgstr "" -#: ../../en/base/dtu_lorawan_rui3.rst:30 1d44c2a4a78744a38efa3f8ba2ced35f +#: ../../en/base/dtu_lorawan_rui3.rst:31 e05094eba64841f8b71a8b1adc294045 msgid "|LoRaWAN-US915|" msgstr "" -#: ../../en/refs/base.lorawan_rui3.ref 32a33872472b4a53a7ea824a9e0c8c18 +#: ../../en/refs/base.lorawan_rui3.ref 1126e89996eb48a098745fa72033eff3 msgid "LoRaWAN-US915" msgstr "" -#: ../../en/base/dtu_lorawan_rui3.rst:34 28b4908fe7e040778e869f4640c16b21 +#: ../../en/base/dtu_lorawan_rui3.rst:35 436429c3dfdc4fbb9faaef60cca29e19 msgid "Micropython LoRaWAN-EU868 LoRaWAN OTAA Mode Example:" -msgstr "Micropython LoRaWAN-EU868 LoRaWAN OTAA 模式示例程序:" +msgstr "" -#: ../../en/base/dtu_lorawan_rui3.rst:40 1bde4096dca94410ae593907cb123d28 +#: ../../en/base/dtu_lorawan_rui3.rst:41 eb4af407e0d64c0c89b3283c8d5572a5 msgid "Micropython LoRaWAN-EU868 P2P Mode TX Example:" -msgstr "Micropython LoRaWAN-EU868 P2P 模式 TX 示例程序:" +msgstr "" -#: ../../en/base/dtu_lorawan_rui3.rst:46 734ea01e9b144beb9dde45c6971ffbac +#: ../../en/base/dtu_lorawan_rui3.rst:47 72be77a23c974f35be0e542aee5cf18f msgid "Micropython LoRaWAN-EU868 P2P Mode RX Example:" -msgstr "Micropython LoRaWAN-EU868 P2P 模式 RX 示例程序:" +msgstr "" -#: ../../en/base/dtu_lorawan_rui3.rst:52 624f1fa7c17c45ae89c92a09ea14a667 +#: ../../en/base/dtu_lorawan_rui3.rst:53 60fef8d4dd7644268fa7a29f4476e403 msgid "UIFLOW2 LoRaWAN-EU868 LoRaWAN OTAA Mode Example:" -msgstr "UIFLOW2 LoRaWAN-EU868 LoRaWAN OTAA 模式示例程序:" +msgstr "" -#: ../../en/base/dtu_lorawan_rui3.rst:54 42a232d64bda4fc3815b62487539ad02 +#: ../../en/base/dtu_lorawan_rui3.rst:55 df1a10d4e5694ce6bf79349bb5b6a517 msgid "|lorawan_otaa_cores3_example.png|" msgstr "" -#: ../../en/refs/base.lorawan_rui3.ref:55 aa0e2b0c63084db48356bfa7738b5311 +#: ../../en/refs/base.lorawan_rui3.ref:55 3795392ecd0a441a9f11f773f3249fa2 msgid "lorawan_otaa_cores3_example.png" msgstr "" -#: ../../en/base/dtu_lorawan_rui3.rst:56 b563f16c0c3147109f5c34f0395c5812 +#: ../../en/base/dtu_lorawan_rui3.rst:57 cd5d486dc6524090a4a847b7b0b71ba2 msgid "|base_lorawan868_otaa_atom_lite_example.m5f2|" msgstr "" -#: ../../en/base/dtu_lorawan_rui3.rst:58 cc8bb6e6e2294625895a405ccf227620 +#: ../../en/base/dtu_lorawan_rui3.rst:59 837365cf847d4b56b70fd34cc33fe572 msgid "UIFLOW2 LoRaWAN-EU868 P2P Mode TX Example:" -msgstr "UIFLOW2 LoRaWAN-EU868 P2P 模式 TX 示例程序:" +msgstr "" -#: ../../en/base/dtu_lorawan_rui3.rst:60 01605a58213d417387156358470b7b7b +#: ../../en/base/dtu_lorawan_rui3.rst:61 f2973246351648558333f728857cc75e msgid "|lorawan_p2p_cores3_example.png|" msgstr "" -#: ../../en/refs/base.lorawan_rui3.ref:56 d6e06211c287428f899d782fd11f9c6b +#: ../../en/refs/base.lorawan_rui3.ref:56 daa4284a257e48eaa777e75e0868d75e msgid "lorawan_p2p_cores3_example.png" msgstr "" -#: ../../en/base/dtu_lorawan_rui3.rst:62 b6651f12039b4c22858da342c75b7678 +#: ../../en/base/dtu_lorawan_rui3.rst:63 ff3a182bf53e41c5b413fed0869b695d msgid "|base_lorawan868_p2p_tx_atom_lite_example.m5f2|" msgstr "" -#: ../../en/base/dtu_lorawan_rui3.rst:64 d9a97eeb1b9e4b0e8acf1be58cc7b2ee +#: ../../en/base/dtu_lorawan_rui3.rst:65 00048f844b8c4c08ac052b8a35870374 msgid "UIFLOW2 LoRaWAN-EU868 P2P Mode RX Example:" -msgstr "UIFLOW2 LoRaWAN-EU868 P2P 模式 RX 示例程序:" +msgstr "" -#: ../../en/base/dtu_lorawan_rui3.rst:66 94014852fff042e88941c1e8757d2cf9 +#: ../../en/base/dtu_lorawan_rui3.rst:67 c113f83ce70649cd8e289f9cf1dd6ebd msgid "|lorawan_p2p_rec_cores3_example.png|" msgstr "" -#: ../../en/refs/base.lorawan_rui3.ref:57 b25983232368433ca9c727f7de39a59c +#: ../../en/refs/base.lorawan_rui3.ref:57 4f8c0642e1014691829184989683ca78 msgid "lorawan_p2p_rec_cores3_example.png" msgstr "" -#: ../../en/base/dtu_lorawan_rui3.rst:68 496628df51204af391852b87b3a8d427 +#: ../../en/base/dtu_lorawan_rui3.rst:69 39029b9036ad4c95b99b1ce9b488adaa msgid "|base_lorawan868_p2p_rx_atom_lite_example.m5f2|" msgstr "" -#: ../../en/base/dtu_lorawan_rui3.rst:71 9ea022feee57408f93d3861d725ae416 +#: ../../en/base/dtu_lorawan_rui3.rst:72 ec146dbe7efd4798bd3bd6751322bc61 msgid "**API**" msgstr "" -#: ../../en/base/dtu_lorawan_rui3.rst:74 130545b7251744fd87cde9298799f90b +#: ../../en/base/dtu_lorawan_rui3.rst:75 c75338dd87bf4a9c959a581c8b5124e8 msgid "AtomDTULoRaWANRUI3Base" msgstr "" #: base.dtu_lorawan_rui3.AtomDTULoRaWANRUI3Base:1 -#: bbdc142a85fe42d9afb54ac51d760c78 of -msgid "Bases: :py:class:`~rui3.RUI3`" +#: c9b5f9b1ef26400aad10a0962d94be04 of +msgid "Bases: :py:class:`~driver.rui3.RUI3`" msgstr "" -#: a01548b9a3ec4c8282accac93cd3f837 +#: 3aaae31c54ec493f9d019d70106aac96 #: base.dtu_lorawan_rui3.AtomDTULoRaWANRUI3Base:1 of msgid "Create an AtomDTULoRaWANRUI3Base object." -msgstr "创建 AtomDTULoRaWANRUI3Base 对象。" +msgstr "" -#: ../../en/base/dtu_lorawan_rui3.rst 6eacd9c4df224d9db4ad595f57868a8b +#: ../../en/base/dtu_lorawan_rui3.rst 0f231107bc9c423bacd0f10eff05e00a +#: 0f85c73c6c644894bd61dac4fa8471b0 29a6076936124aa5899cc71fcadf3526 +#: 2bc46f1d19654aff82cbf925d1502db5 aec4d07c52784701b2b74caf0366a4fd #: base.dtu_lorawan_rui3.AtomDTULoRaWANRUI3Base.set_abp_config #: base.dtu_lorawan_rui3.AtomDTULoRaWANRUI3Base.set_otaa_config -#: driver.rui3.RUI3.get_p2p_receive_data driver.rui3.RUI3.join_network -#: driver.rui3.RUI3.send_data driver.rui3.RUI3.send_p2p_data -#: driver.rui3.RUI3.set_join_config driver.rui3.RUI3.set_join_mode -#: driver.rui3.RUI3.set_network_mode driver.rui3.RUI3.set_p2p_bandwidth -#: driver.rui3.RUI3.set_p2p_code_rate driver.rui3.RUI3.set_p2p_frequency -#: driver.rui3.RUI3.set_p2p_fsk_bitrate +#: c54486a366274c69b25c6f554a97373c driver.rui3.RUI3.get_p2p_receive_data +#: driver.rui3.RUI3.join_network driver.rui3.RUI3.send_data +#: driver.rui3.RUI3.send_p2p_data driver.rui3.RUI3.set_join_config +#: driver.rui3.RUI3.set_join_mode driver.rui3.RUI3.set_network_mode +#: driver.rui3.RUI3.set_p2p_bandwidth driver.rui3.RUI3.set_p2p_code_rate +#: driver.rui3.RUI3.set_p2p_frequency driver.rui3.RUI3.set_p2p_fsk_bitrate #: driver.rui3.RUI3.set_p2p_preamble_length #: driver.rui3.RUI3.set_p2p_spreading_factor driver.rui3.RUI3.set_p2p_sync_word -#: driver.rui3.RUI3.set_p2p_tx_power of +#: driver.rui3.RUI3.set_p2p_tx_power e568800edbfc472d8ce8052c0228d7dd of msgid "Parameters" -msgstr "参数" +msgstr "" -#: 17771d8479fc499392593ad4d50b41a1 -#: base.dtu_lorawan_rui3.AtomDTULoRaWANRUI3Base:3 of +#: base.dtu_lorawan_rui3.AtomDTULoRaWANRUI3Base:3 +#: ff7d8c0db2cc408caf7652155ee93a3c of msgid "The UART ID to use (0, 1, or 2). Default is 2." -msgstr "要使用的 UART ID(0、1 或 2)。默认是 2。" +msgstr "" -#: base.dtu_lorawan_rui3.AtomDTULoRaWANRUI3Base:4 -#: c3d062303e2443bfb00b16b67e03feab of +#: 89446325236b455098b7e61f84d77b9c +#: base.dtu_lorawan_rui3.AtomDTULoRaWANRUI3Base:4 of msgid "A list or tuple containing the TX and RX pin numbers." -msgstr "包含 TX 和 RX 引脚编号的列表或元组。" +msgstr "" -#: 21cb11cf7f964200a5b2605b36280659 +#: 968f75a4d22844deb816bc365cbf711e #: base.dtu_lorawan_rui3.AtomDTULoRaWANRUI3Base:6 of msgid "Whether to enable debug mode. Default is False." -msgstr "是否启用调试模式。默认是 False。" +msgstr "" -#: 902951a49a9e4c5baf4791db634ec7d0 +#: 0371911d831a473ca8ebea9bffcb7894 0ff8ec42152a476499c3e353c8bc4d30 +#: 38693d5ea27740df8f53b4abdc47a49e 428d64a8e66e4d3fb1d2296e8655f8a1 +#: 876a6b64d84b499b9fe9d298b9b60335 96d850e2f6be4695885c89d61f6ba765 +#: 979beca0c9774c5fa408b6a392e2f51c b6771ced5cde49fc8d32022fa3c57fe6 #: base.dtu_lorawan_rui3.AtomDTULoRaWANRUI3Base:8 #: base.dtu_lorawan_rui3.AtomDTULoRaWANRUI3Base.get_abp_config:6 #: base.dtu_lorawan_rui3.AtomDTULoRaWANRUI3Base.get_otaa_config:6 #: base.dtu_lorawan_rui3.AtomDTULoRaWANRUI3Base.set_abp_config:7 #: base.dtu_lorawan_rui3.AtomDTULoRaWANRUI3Base.set_otaa_config:7 +#: bdc94900a8214988a77a1221d2eaea93 d1e7df8e4c434fa68cd3ceb23e57df89 #: driver.rui3.RUI3.get_device_eui:6 driver.rui3.RUI3.get_join_state:6 #: driver.rui3.RUI3.get_last_receive:6 driver.rui3.RUI3.get_p2p_bandwidth:6 #: driver.rui3.RUI3.get_p2p_code_rate:6 driver.rui3.RUI3.get_p2p_frequency:6 @@ -245,19 +249,22 @@ msgstr "是否启用调试模式。默认是 False。" #: driver.rui3.RUI3.set_p2p_preamble_length:10 #: driver.rui3.RUI3.set_p2p_spreading_factor:10 #: driver.rui3.RUI3.set_p2p_sync_word:10 driver.rui3.RUI3.set_p2p_tx_power:10 -#: of +#: e4077dce7eae4bd9b6d5617998f49553 f683268d740544e392459125ed7f5db2 +#: f8e2745bb6d849d3bb7271d246b34df0 of msgid "MicroPython Code Block:" -msgstr "MicroPython 代码块:" +msgstr "" -#: 6562f39a98de4274bed1f70250740ad5 +#: b9212192568b4e3e933beff57599b0aa #: base.dtu_lorawan_rui3.AtomDTULoRaWANRUI3Base.get_abp_config:1 of -#, fuzzy msgid "Retrieve the current ABP configuration." -msgstr "检索当前 P2P 频率。" +msgstr "" -#: 5257fb90533449d99b3d539b7e39ed6c +#: 22e987cb4d184d4eb7daef030f8a5320 3bc6d797640543039f15490174430a8d +#: 6369410c715d4b4dae7707da3738f449 6a25a2aa609b41579c4e46762451aab7 +#: 9f659160324e4677a8408ac9e121013e a7d4343de55041d688d1d8f8502a1704 #: base.dtu_lorawan_rui3.AtomDTULoRaWANRUI3Base.get_abp_config #: base.dtu_lorawan_rui3.AtomDTULoRaWANRUI3Base.get_otaa_config +#: c3ef348607ce424f8bc9d03c8d2b0ccd c9124a0afaab49dea09ed7ee408ed66f #: driver.rui3.RUI3.get_device_eui driver.rui3.RUI3.get_join_state #: driver.rui3.RUI3.get_last_receive driver.rui3.RUI3.get_p2p_bandwidth #: driver.rui3.RUI3.get_p2p_code_rate driver.rui3.RUI3.get_p2p_frequency @@ -275,23 +282,27 @@ msgstr "检索当前 P2P 频率。" #: driver.rui3.RUI3.set_p2p_fsk_bitrate #: driver.rui3.RUI3.set_p2p_preamble_length #: driver.rui3.RUI3.set_p2p_spreading_factor driver.rui3.RUI3.set_p2p_sync_word -#: driver.rui3.RUI3.set_p2p_tx_power of +#: driver.rui3.RUI3.set_p2p_tx_power ff1cdf1cb2fd40b0905e3f70a26abb4f of msgid "Returns" -msgstr "返回值" +msgstr "" -#: 73e86df276824f689fd24e69e252f316 -#: base.dtu_lorawan_rui3.AtomDTULoRaWANRUI3Base.get_abp_config:3 of +#: base.dtu_lorawan_rui3.AtomDTULoRaWANRUI3Base.get_abp_config:3 +#: f008313eca7147738bbfb7a01a3ddac4 of msgid "A tuple containing (device_address, apps_key, networks_key)." -msgstr "包含(设备地址、应用程序密钥、网络密钥)的元组。" +msgstr "" +#: 2db6d06f5e134dc6bcb96ffc91704242 3043d07ff3ba48a78b5b69699230937a +#: 3b3a310d328249fd8dacd944364735e1 505759319ff843e38bc80c5459e61732 +#: 739edfe99eaa4b24b374f475786c79f5 83c03cdc5c044ce89662f80b334769c0 +#: b3f1fc234d0d4daf87a463f359ed1364 #: base.dtu_lorawan_rui3.AtomDTULoRaWANRUI3Base.get_abp_config #: base.dtu_lorawan_rui3.AtomDTULoRaWANRUI3Base.get_otaa_config #: base.dtu_lorawan_rui3.AtomDTULoRaWANRUI3Base.set_abp_config #: base.dtu_lorawan_rui3.AtomDTULoRaWANRUI3Base.set_otaa_config -#: driver.rui3.RUI3.get_device_eui driver.rui3.RUI3.get_join_state -#: driver.rui3.RUI3.get_last_receive driver.rui3.RUI3.get_p2p_bandwidth -#: driver.rui3.RUI3.get_p2p_code_rate driver.rui3.RUI3.get_p2p_frequency -#: driver.rui3.RUI3.get_p2p_fsk_bitrate +#: cf1f83096081495f95d47b5e22783858 driver.rui3.RUI3.get_device_eui +#: driver.rui3.RUI3.get_join_state driver.rui3.RUI3.get_last_receive +#: driver.rui3.RUI3.get_p2p_bandwidth driver.rui3.RUI3.get_p2p_code_rate +#: driver.rui3.RUI3.get_p2p_frequency driver.rui3.RUI3.get_p2p_fsk_bitrate #: driver.rui3.RUI3.get_p2p_preamble_length #: driver.rui3.RUI3.get_p2p_receive_data #: driver.rui3.RUI3.get_p2p_spreading_factor driver.rui3.RUI3.get_p2p_sync_word @@ -305,228 +316,229 @@ msgstr "包含(设备地址、应用程序密钥、网络密钥)的元组。 #: driver.rui3.RUI3.set_p2p_fsk_bitrate #: driver.rui3.RUI3.set_p2p_preamble_length #: driver.rui3.RUI3.set_p2p_spreading_factor driver.rui3.RUI3.set_p2p_sync_word -#: driver.rui3.RUI3.set_p2p_tx_power f262b70685f949d79b49bdea048e0a25 of +#: driver.rui3.RUI3.set_p2p_tx_power eaf48d953a6c4ca685b02c9344cdaf52 +#: f1f8493c9abf42eaae4c8742ce8ce7ff f2586d70e689486e88843e293fd56900 of msgid "Return type" -msgstr "返回类型" +msgstr "" -#: 6562f39a98de4274bed1f70250740ad5 -#: base.dtu_lorawan_rui3.AtomDTULoRaWANRUI3Base.get_otaa_config:1 of +#: base.dtu_lorawan_rui3.AtomDTULoRaWANRUI3Base.get_otaa_config:1 +#: fbd6775f55764ac88ae2b5fe52c03bfd of msgid "Retrieve the current OTAA configuration." -msgstr "获取当前 OTAA 配置。" +msgstr "" -#: 0f95fa6022e545dab434e05430d56085 -#: base.dtu_lorawan_rui3.AtomDTULoRaWANRUI3Base.get_otaa_config:3 of +#: base.dtu_lorawan_rui3.AtomDTULoRaWANRUI3Base.get_otaa_config:3 +#: fc379ce02b18496a8fceb469e58ed6ae of msgid "A tuple containing (device_eui, app_key, app_eui)." -msgstr "包含(设备 EUI、应用程序密钥、应用程序 EUI)的元组。" +msgstr "" -#: 8d2ff7b2cb6d4df6bcbb4b18d706763d +#: 37df0a9c10a9458791e1ff9043881a33 #: base.dtu_lorawan_rui3.AtomDTULoRaWANRUI3Base.set_abp_config:1 of -#, fuzzy msgid "Configure the device for ABP (Activation By Personalization) mode." -msgstr "ABP(Activation By Personalization,手动激活)" +msgstr "" -#: base.dtu_lorawan_rui3.AtomDTULoRaWANRUI3Base.set_abp_config:3 -#: fe996cb318ae4e6799f38db7e08127a0 of +#: 4312db10df7f4b13abffaa3bd9c9428f +#: base.dtu_lorawan_rui3.AtomDTULoRaWANRUI3Base.set_abp_config:3 of msgid "The device address for ABP configuration." -msgstr "用于 ABP 配置的设备地址。" +msgstr "" -#: 62fc244a5ed4486099fcfb3b033fc804 +#: 29352944ea1240b7acbd0828ed90925c #: base.dtu_lorawan_rui3.AtomDTULoRaWANRUI3Base.set_abp_config:4 of msgid "The application session key for encryption." -msgstr "用于加密的应用会话密钥。" +msgstr "" -#: base.dtu_lorawan_rui3.AtomDTULoRaWANRUI3Base.set_abp_config:5 -#: fe996cb318ae4e6799f38db7e08127a0 of +#: 591069d570364c33ab1d497b0e7df9e7 +#: base.dtu_lorawan_rui3.AtomDTULoRaWANRUI3Base.set_abp_config:5 of msgid "The network session key for communication." -msgstr "用于通信的网络会话密钥。" +msgstr "" -#: 721170a760214d5496c7e3718bd1777b +#: 885eeeb1170e453391279d409ec50f38 #: base.dtu_lorawan_rui3.AtomDTULoRaWANRUI3Base.set_otaa_config:1 of msgid "Configure the device for OTAA (Over-The-Air Activation) mode." -msgstr "配置设备为 OTAA(Over-The-Air Activation,空中激活)模式。" +msgstr "" -#: base.dtu_lorawan_rui3.AtomDTULoRaWANRUI3Base.set_otaa_config:3 -#: fe996cb318ae4e6799f38db7e08127a0 of +#: a5973efd33f24c4c82758db256207ff6 +#: base.dtu_lorawan_rui3.AtomDTULoRaWANRUI3Base.set_otaa_config:3 of msgid "The device EUI for OTAA configuration." -msgstr "用于 OTAA 配置的设备 EUI。" +msgstr "" -#: 021870fea55042b591e8a70a12b26a85 +#: 8dd5e96ec6fc490292b3c13b03bfe3db #: base.dtu_lorawan_rui3.AtomDTULoRaWANRUI3Base.set_otaa_config:4 of msgid "The application key for encryption." -msgstr "用于加密的应用密钥。" +msgstr "" -#: 021870fea55042b591e8a70a12b26a85 +#: 84ae6601a5234fa591981c106987d35c #: base.dtu_lorawan_rui3.AtomDTULoRaWANRUI3Base.set_otaa_config:5 of msgid "The application EUI for OTAA configuration." -msgstr "用于 OTAA 配置的应用 EUI。" +msgstr "" -#: bbdc142a85fe42d9afb54ac51d760c78 driver.rui3.RUI3:1 of +#: 4142d8073a3e4dfd8937b2f1094f6c53 driver.rui3.RUI3:1 of msgid "Bases: :py:class:`object`" msgstr "" -#: aa776326beaf49c7bf4617468b4b3d4b driver.rui3.RUI3.get_last_receive:1 -#: driver.rui3.RUI3.get_received_data:1 of +#: 06202e227c804a64b91089189741296c d0916fdf894846249e0e0bf2bb27f301 +#: driver.rui3.RUI3.get_last_receive:1 driver.rui3.RUI3.get_received_data:1 of msgid "Retrieve the data from the last received message." -msgstr "检索最后一次接收到的消息数据。" +msgstr "" -#: 73e86df276824f689fd24e69e252f316 driver.rui3.RUI3.get_last_receive:3 -#: driver.rui3.RUI3.get_received_data:3 of +#: 267d599a29954e07ab900cf122f2cf3b 50cb23c060284427b47080c54bec0376 +#: driver.rui3.RUI3.get_last_receive:3 driver.rui3.RUI3.get_received_data:3 of msgid "" "A tuple containing the port number (int) and the received data (bytes), " "or False if no data was received." -msgstr "包含端口号(整数)和接收到的数据(字节)的元组,如果没有数据接收,返回 False。" +msgstr "" -#: 867ad5954bed449986797d37e04497f8 driver.rui3.RUI3.get_received_data_string:1 +#: 85caeb0245254dd79d9ce720036f8dfb driver.rui3.RUI3.get_received_data_string:1 #: of msgid "Retrieve the received data as a string." -msgstr "检索接收到的数据作为字符串。" +msgstr "" -#: 6901c0006ed24e1685bdc924c7231cd2 driver.rui3.RUI3.get_received_data_string:3 +#: 008c2e4518f9454aa95f3f6aaafa8bdf driver.rui3.RUI3.get_received_data_string:3 #: of msgid "The received data as a string, or an empty string if no data was received." -msgstr "接收到的数据作为字符串,如果没有数据接收,返回空字符串。" +msgstr "" -#: aa776326beaf49c7bf4617468b4b3d4b driver.rui3.RUI3.get_received_data_count:1 +#: cb7b081ace934aedbbc7c658faadd34e driver.rui3.RUI3.get_received_data_count:1 #: of msgid "Retrieve the number of received data." -msgstr "检索接收到的数据数量。" +msgstr "" -#: 04142f2491d6492984d9600e87372c82 driver.rui3.RUI3.get_received_data_count:3 +#: 191e4c364904444089a0315b88d6e8f8 driver.rui3.RUI3.get_received_data_count:3 #: of msgid "The number of received data." -msgstr "接收到的数据数量。" +msgstr "" -#: 844d3630535849ea8ea4ef4a3e38de84 driver.rui3.RUI3.reset_module_to_default:1 +#: b1c74c187cd346028896e677326ad113 driver.rui3.RUI3.reset_module_to_default:1 #: of msgid "Reset the module to its factory default settings." -msgstr "将模块重置为出厂默认设置。" +msgstr "" -#: driver.rui3.RUI3.get_device_eui:1 e2fddd30eea249a697d435c41375f132 of +#: ae221c4704f547dc8fc1f19c4f563fdd driver.rui3.RUI3.get_device_eui:1 of msgid "Get the device EUI." -msgstr "获取设备 EUI。" +msgstr "" -#: driver.rui3.RUI3.get_device_eui:3 f8c61272754c4ef49b6836610c6aa047 of +#: 964a6c5fc15f46c1b61565d2e5fafb13 driver.rui3.RUI3.get_device_eui:3 of msgid "The device EUI." -msgstr "设备 EUI" +msgstr "" -#: 557a4c78a80f497ea02beac27ae73374 driver.rui3.RUI3.set_join_config:1 of +#: b9da2516db434721ad4ae8ee5c59e866 driver.rui3.RUI3.set_join_config:1 of msgid "Configure the join parameters for LoRaWAN." -msgstr "为 LoRaWAN 配置连接参数。" +msgstr "" -#: bf97938c95924d119afecf070b57278f driver.rui3.RUI3.set_join_config:3 of +#: c3365a36ff1949c79230c69d63c06c38 driver.rui3.RUI3.set_join_config:3 of msgid "The configuration does not confirm network join success." -msgstr "配置不确认网络连接成功。" +msgstr "" -#: 4103493a99234b34924cb5021f83e3a8 driver.rui3.RUI3.set_join_config:5 of +#: 2249b820e83941b88bf7087fe2701a0e driver.rui3.RUI3.set_join_config:5 of msgid "The join state to configure, as an integer." -msgstr "要配置的连接状态,整数。" +msgstr "" -#: b64e9f6865c5426b859d7147d6effa07 driver.rui3.RUI3.set_join_config:6 of +#: 398ea9105c7a4bdd9ed93e3dae0e2df4 driver.rui3.RUI3.set_join_config:6 of msgid "The auto-join flag, as an integer." -msgstr "自动连接标志位,整数。" +msgstr "" -#: 17771d8479fc499392593ad4d50b41a1 driver.rui3.RUI3.set_join_config:7 of +#: c778f30f64df49248ab7020da2f824cd driver.rui3.RUI3.set_join_config:7 of msgid "The interval between join retries, in seconds. Default is 8." -msgstr "连接重试间隔,单位为秒。默认值为 8秒 。" +msgstr "" -#: 34db71de4433451891c1a2b9f7903e08 driver.rui3.RUI3.set_join_config:8 of +#: driver.rui3.RUI3.set_join_config:8 f6393f1a024b4c4f876b155a24284fec of msgid "The maximum number of retries. Default is 0 (no limit)." -msgstr "最大重试次数。默认值为 0(无限制)。" +msgstr "" -#: 6eebe2aa6f6242ef8f5e7eabff2da4c6 driver.rui3.RUI3.set_join_config:9 of +#: 8d936ccdae154079a4923504b544deaa driver.rui3.RUI3.set_join_config:9 of msgid "The timeout duration in milliseconds for the command. Default is 8000ms." -msgstr "命令超时时间,单位为毫秒。默认值为 8000ms。" +msgstr "" -#: 5623f4d6e5fa4693b88af23002518abd driver.rui3.RUI3.join_network:5 +#: 561cc7bbaae346f4b3e809c5967df0f1 576e997eee1a4f3181f3941c013eb8ce +#: 6c3ac9cd4c524ba19c22efe0869ba2ce driver.rui3.RUI3.join_network:5 #: driver.rui3.RUI3.set_join_config:11 driver.rui3.RUI3.set_join_mode:5 of msgid "True if the command is successfully set, else False." -msgstr "如果命令设置成功,返回 True,否则返回 False。" +msgstr "" -#: 100f24aca1184bd8bd4f995861e68e49 driver.rui3.RUI3.join_network:1 of +#: 8defe63ef3534a6a806ae8f64db64202 driver.rui3.RUI3.join_network:1 of msgid "Join the LoRa network using predefined join parameters." -msgstr "使用预定义的连接参数加入 LoRa 网络。" +msgstr "" -#: aec229cfe96e4ea1984bebf3ed5d4421 driver.rui3.RUI3.join_network:3 of +#: 55e4cfe1e9974546aa996379c0f55b74 driver.rui3.RUI3.join_network:3 of msgid "" "The timeout duration in milliseconds for the join command. Default is " "8000ms." -msgstr "连接命令超时时间,单位为毫秒。默认值为 8000ms。" +msgstr "" -#: a2f3ec97468347fda94d0a16c715f25a driver.rui3.RUI3.join_network:6 of +#: 3ccf5377e9294df7886cd5f60b987990 driver.rui3.RUI3.join_network:6 of msgid "bool |join_network_return.png|" msgstr "" -#: 2d2c091600134f8aa518a37bbb818cc4 caf3437aebbc4c43982ebeb856a7446b -#: driver.rui3.RUI3.join_network:6 driver.rui3.RUI3.send_data:9 -#: driver.rui3.RUI3.send_p2p_data:11 e8de16726b63416783e477da671d2602 of +#: 1d97e1939e914af18b7453ef5171a682 6711e53f26a24a6bbf979ed4088a39d1 +#: 83f7b76b348b4ad98b5ccde371151ae2 driver.rui3.RUI3.join_network:6 +#: driver.rui3.RUI3.send_data:9 driver.rui3.RUI3.send_p2p_data:11 of msgid "bool" msgstr "" -#: a2f3ec97468347fda94d0a16c715f25a driver.rui3.RUI3.join_network:8 of +#: 431199f02d69477cb255b9486177e815 driver.rui3.RUI3.join_network:8 of msgid "|join_network_return.png|" msgstr "" -#: ../../en/refs/base.lorawan_rui3.ref:22 1629c37aa2c147cb96d4a2e3d3842b7e +#: ../../en/refs/base.lorawan_rui3.ref:22 ed0731838ab746709dd3abc797e5ffbe msgid "join_network_return.png" msgstr "" -#: a042c1ac524a4f9587b4999da1d7cae6 driver.rui3.RUI3.set_join_mode:1 of +#: driver.rui3.RUI3.set_join_mode:1 f2e43987734c40d788c971e61cdc082b of msgid "Set the join mode for the LoRa module." -msgstr "设置 LoRa 模块的连接模式。" +msgstr "" -#: 81159c683b0b4e3294f735438ccc42c3 driver.rui3.RUI3.set_join_mode:3 of +#: a9a3a91252234b0481f6c247331a7f27 driver.rui3.RUI3.set_join_mode:3 of msgid "The join mode to set, 0 for ABP or 1 for OTAA." -msgstr "要设置的连接模式,0 表示 ABP,1 表示 OTAA。" +msgstr "" -#: a60d7ed97ab145eb893c4df4be612fab driver.rui3.RUI3.get_join_state:1 of +#: driver.rui3.RUI3.get_join_state:1 e21fc70e1706436f8702d4ae76b98782 of msgid "Check whether the module has successfully joined the network." -msgstr "检查模块是否成功加入网络。" +msgstr "" -#: b7430cc0814844aeba20a2640370d016 driver.rui3.RUI3.get_join_state:3 of +#: driver.rui3.RUI3.get_join_state:3 f3707d22a67f4dcfa65cdaa4454df2fa of msgid "True if joined, otherwise False." -msgstr "如果已加入网络,返回 True,否则返回 False。" +msgstr "" -#: 0904a20616f14be0be54a8653575095e driver.rui3.RUI3.send_data:1 of +#: 031f3ae204a64a799b394d071e2130f0 driver.rui3.RUI3.send_data:1 of msgid "Send data through a specific port." -msgstr "通过特定端口发送数据。" +msgstr "" -#: 04142f2491d6492984d9600e87372c82 driver.rui3.RUI3.send_data:3 of +#: d8e8d65629874bbd8582a378e0078339 driver.rui3.RUI3.send_data:3 of msgid "The port number to send data through." -msgstr "要发送数据的端口号。" +msgstr "" -#: b01aa61d57df4db8ad54abf6cec294e0 driver.rui3.RUI3.send_data:4 of +#: 475386e62aaa40a3ba85b901dc660118 driver.rui3.RUI3.send_data:4 of msgid "" "The data to send, provided as bytes or string(if data is bytes, it will " "be converted to string)." -msgstr "要发送的数据,提供字节或字符串(如果数据是字节,将转换为字符串)。" +msgstr "" -#: 19cffe38a4c846f5aedd98c9f47335ef driver.rui3.RUI3.send_data:6 of +#: 6a69de073dd4414ba73c43f29684ab3e driver.rui3.RUI3.send_data:6 of msgid "" "The timeout duration in milliseconds for the send command. Default is " "600ms." -msgstr "发送命令的超时时间,单位为毫秒。默认值为 600ms。" +msgstr "" -#: d2935087f7fa406c8b88ef83701efd3a driver.rui3.RUI3.send_data:8 of +#: 04a5fd1230584e3d91ebd9b2af76298d driver.rui3.RUI3.send_data:8 of msgid "True if the data was sent successfully, otherwise False." -msgstr "如果数据发送成功,返回 True,否则返回 False。" +msgstr "" -#: a12435b737cf40f4bbb9dd80af518582 driver.rui3.RUI3.send_data:9 of +#: driver.rui3.RUI3.send_data:9 e0eae8bd18f749839ec6b5e33ea2a081 of msgid "bool |send_data_return.png|" msgstr "" -#: a12435b737cf40f4bbb9dd80af518582 driver.rui3.RUI3.send_data:11 of +#: d7c17d382e1149e0b64872c3f9cde90f driver.rui3.RUI3.send_data:11 of msgid "|send_data_return.png|" msgstr "" -#: ../../en/refs/base.lorawan_rui3.ref:27 74dfdca1daa349a9bbadef6b33757f19 +#: ../../en/refs/base.lorawan_rui3.ref:27 019f0556b9e44ca28e9b9e99850777ba msgid "send_data_return.png" msgstr "" -#: 2e3a7351f465410799090a8aea2c25ab driver.rui3.RUI3.set_network_mode:1 of +#: 2ac9af6d06e940cebd4cfc3d7cbf0e4e driver.rui3.RUI3.set_network_mode:1 of msgid "Set the network mode for the device." -msgstr "设置设备网络模式。" +msgstr "" -#: 3d366888a88443f8b446f379bb515037 driver.rui3.RUI3.get_p2p_fsk_bitrate:3 +#: 8e98a2c4afb84115a9f6b421d4cf8881 driver.rui3.RUI3.get_p2p_fsk_bitrate:3 #: driver.rui3.RUI3.set_network_mode:3 driver.rui3.RUI3.set_p2p_bandwidth:20 #: driver.rui3.RUI3.set_p2p_code_rate:10 driver.rui3.RUI3.set_p2p_frequency:3 #: driver.rui3.RUI3.set_p2p_fsk_bitrate:7 @@ -534,659 +546,419 @@ msgstr "设置设备网络模式。" #: driver.rui3.RUI3.set_p2p_spreading_factor:7 #: driver.rui3.RUI3.set_p2p_tx_power:7 of msgid "The result of the AT command execution." -msgstr "AT 命令执行结果。" +msgstr "" -#: 796559359ec7443b87d8da15e1d1fcba driver.rui3.RUI3.set_network_mode:6 of +#: 83dddfab4d7e4611ab8ec171cda18e41 driver.rui3.RUI3.set_network_mode:6 of msgid "" "The mode to set for the network: - 0 = P2P_LORA - 1 = LoRaWAN - 2 = " "P2P_FSK" msgstr "" -"要设置的网络模式: \n" -" - 0 = P2P_LORA \n" -" - 1 = LoRaWAN \n" -" - 2 = P2P_FSK" -#: 8fd4b897cf7d475d83e3c056b8eadc4f driver.rui3.RUI3.set_network_mode:6 of +#: b02655725770435fba3ac5a04242488c driver.rui3.RUI3.set_network_mode:6 of msgid "The mode to set for the network:" -msgstr "要设置的网络模式:" +msgstr "" -#: b054c11ce8c04ab3835e28b5257e027f driver.rui3.RUI3.set_network_mode:8 of +#: 3fce94292d2e48b19bb523a25a852c5a driver.rui3.RUI3.set_network_mode:8 of msgid "0 = P2P_LORA" msgstr "" -#: 675e48587b164ea1b6226074eb3c7d56 driver.rui3.RUI3.set_network_mode:9 of +#: d30a55ae64c549098902aac70c6c3fb6 driver.rui3.RUI3.set_network_mode:9 of msgid "1 = LoRaWAN" msgstr "" -#: driver.rui3.RUI3.set_network_mode:10 fd46b3fc5cab4fb198ed13e1df2dda1a of +#: 323a0005c5014da0b2ad1baae928534b driver.rui3.RUI3.set_network_mode:10 of msgid "2 = P2P_FSK" msgstr "" -#: driver.rui3.RUI3.get_p2p_frequency:1 f46876dcd44148759d2bd9b86b220a98 of +#: 9e91328e43b845ad92c9b337c96febd8 driver.rui3.RUI3.get_p2p_frequency:1 of msgid "Retrieve the current P2P frequency." -msgstr "检索当前 P2P 频率。" +msgstr "" -#: 10e6243508284e90b23f4fd38537f809 driver.rui3.RUI3.get_p2p_frequency:3 of +#: 41d813c2bff9432b9b59e07c47e91696 driver.rui3.RUI3.get_p2p_frequency:3 of msgid "The current P2P frequency as an integer." -msgstr "当前 P2P 频率作为整数。" +msgstr "" -#: 4bbf9970a9184bc7ba53136433ba6000 driver.rui3.RUI3.set_p2p_frequency:1 of +#: 0bca632025594ff099afe499dcc08a5b driver.rui3.RUI3.set_p2p_frequency:1 of msgid "Set the P2P frequency for the device." -msgstr "设置设备 P2P 频率。" +msgstr "" -#: bd2c218f2d794188b30a8f1bfe379165 driver.rui3.RUI3.set_p2p_frequency:6 of +#: driver.rui3.RUI3.set_p2p_frequency:6 e51916660ee847b68e253f7bea60f531 of msgid "" "The frequency to set for P2P communication. - Low-frequency range: " "150000000-600000000 - High-frequency range: 600000000-960000000" msgstr "" -"要设置的 P2P 通信频率。\n" -" - 低频率范围:150000000-600000000 \n" -" - 高频率范围:600000000-960000000" -#: 17aac352e42642b5b473cba2b5c712d1 driver.rui3.RUI3.set_p2p_frequency:6 of +#: b81376d51e3f4a909a8d0dc6312e7859 driver.rui3.RUI3.set_p2p_frequency:6 of msgid "The frequency to set for P2P communication." -msgstr "要设置的 P2P 通信频率。" +msgstr "" -#: 1bc87601f25c49aebcc529b1c66622f3 driver.rui3.RUI3.set_p2p_frequency:8 of +#: 9a533731b92c4c689677854e1fb979e3 driver.rui3.RUI3.set_p2p_frequency:8 of msgid "Low-frequency range: 150000000-600000000" -msgstr "低频率范围:150000000-600000000" +msgstr "" -#: 757164e675f4494183d691a1914be0ea driver.rui3.RUI3.set_p2p_frequency:9 of +#: 052e70c059f94bb885758311d8fa48f9 driver.rui3.RUI3.set_p2p_frequency:9 of msgid "High-frequency range: 600000000-960000000" -msgstr "高频率范围:600000000-960000000" +msgstr "" -#: 268d9be7654e4d139c8e5c912780eb05 driver.rui3.RUI3.get_p2p_spreading_factor:1 +#: 44e7f0f919f14a5dba1e525889c25d38 driver.rui3.RUI3.get_p2p_spreading_factor:1 #: of msgid "Retrieve the current P2P spreading factor." -msgstr "获取当前 P2P 扩展因子。" +msgstr "" -#: 7895420eea224c74a8c3cf1a14b6c74f driver.rui3.RUI3.get_p2p_spreading_factor:3 +#: 6bc0986ef63c443b9cf3b517e5ce4188 driver.rui3.RUI3.get_p2p_spreading_factor:3 #: of msgid "The current P2P spreading factor as an integer." -msgstr "当前 P2P 扩展因子作为整数。" +msgstr "" -#: 0680dea68cbb41a48ea6c9c462d2b81a driver.rui3.RUI3.set_p2p_spreading_factor:1 +#: 93236fc05f6249e5ab1784e1b08b30b1 driver.rui3.RUI3.set_p2p_spreading_factor:1 #: of msgid "Set the P2P spreading factor." -msgstr "设置 P2P 扩展因子。" +msgstr "" -#: 9c4c3c9a34514ca8a454e4077d735bf4 driver.rui3.RUI3.set_p2p_spreading_factor:3 +#: c578780bb2a24ea19f02d7aa2289e140 driver.rui3.RUI3.set_p2p_spreading_factor:3 #: of msgid "The spreading factor to set for P2P communication. - Range is 5 to 12." msgstr "" -"要设置的 P2P 通信扩展因子。 \n" -" - 范围是 5 到 12。" -#: ab80efd274204b46977f58766c71af70 driver.rui3.RUI3.set_p2p_spreading_factor:3 +#: dba7567a84a14f77a5107f76c9de4279 driver.rui3.RUI3.set_p2p_spreading_factor:3 #: of msgid "The spreading factor to set for P2P communication." -msgstr "用于 P2P 通信的扩展因子。" +msgstr "" -#: 5b74849620d7407d8e0a5e3a9cb3fa33 driver.rui3.RUI3.set_p2p_spreading_factor:5 +#: d2ae81b1bbed4c9e9eb1f5a4f6c37351 driver.rui3.RUI3.set_p2p_spreading_factor:5 #: of msgid "Range is 5 to 12." -msgstr "范围是 5 到 12。" +msgstr "" -#: 40d63b0206a44b1ab3299fc2053814b3 driver.rui3.RUI3.get_p2p_bandwidth:1 of +#: daa1615f35aa426dbe3d65bb5c70bb79 driver.rui3.RUI3.get_p2p_bandwidth:1 of msgid "Retrieve the current P2P bandwidth." -msgstr "获取当前 P2P 带宽。" +msgstr "" -#: c8e92947bf4943dd8118417baefdad82 driver.rui3.RUI3.get_p2p_bandwidth:3 of +#: a6da8ccbf8d74644ad308473ba8635c8 driver.rui3.RUI3.get_p2p_bandwidth:3 of msgid "The current P2P bandwidth as an integer." -msgstr "当前 P2P 带宽作为整数。" +msgstr "" -#: 6a9373310a644a91a2136e33d2743a90 driver.rui3.RUI3.set_p2p_bandwidth:1 of +#: 13a0a126bfc9445ea338019a49da5a3c driver.rui3.RUI3.set_p2p_bandwidth:1 of msgid "Set the P2P bandwidth." msgstr "" -#: 4b23a93b582d4bf5b227ea7ae23f50f6 driver.rui3.RUI3.set_p2p_bandwidth:3 of +#: a90ff29fc41c47039a114c1901056c34 driver.rui3.RUI3.set_p2p_bandwidth:3 of msgid "" "The bandwidth to set for P2P communication. - For LoRa: - 0 = 125 " "kHz - 1 = 250 kHz - 2 = 500 kHz - 3 = 7.8 kHz - 4 = 10.4 " "kHz - 5 = 15.63 kHz - 6 = 20.83 kHz - 7 = 31.25 kHz - 8 =" " 41.67 kHz - 9 = 62.5 kHz - For FSK: Range: 4800-467000 Hz" msgstr "" -"为 P2P 通信设置的带宽 \n" -" - 对于 LoRa:\n" -"- 0 = 125 kHz \n" -" - 1 = 250 kHz \n" -" - 2 = 500 kHz \n" -" - 3 = 7.8 kHz \n" -" - 4 = 10.4 kHz \n" -" - 5 = 15.63 kHz \n" -" - 6 = 20.83 kHz \n" -" - 7 = 31.25 kHz \n" -" - 8 = 41.67 kHz \n" -" - 9 = 62.5 kHz \n" -" - 对于 FSK:范围:4800-467000 Hz" - -#: 021870fea55042b591e8a70a12b26a85 driver.rui3.RUI3.set_p2p_bandwidth:3 of + +#: c53f497025804218a62d52d833861ec4 driver.rui3.RUI3.set_p2p_bandwidth:3 of msgid "The bandwidth to set for P2P communication." -msgstr "用于 P2P 通信的带宽。" +msgstr "" -#: driver.rui3.RUI3.set_p2p_bandwidth:15 fb1d34a81c7b4b539b1eb36fcf754774 of +#: 52fbdcf0c33e4ad38eae4964955938ce driver.rui3.RUI3.set_p2p_bandwidth:15 of msgid "For LoRa:" -msgstr "对于 LoRa:" +msgstr "" -#: 7e8028ab765c4e458d04afabfc993506 driver.rui3.RUI3.set_p2p_bandwidth:6 of +#: 622df72172544440b1278b8d71a69471 driver.rui3.RUI3.set_p2p_bandwidth:6 of msgid "0 = 125 kHz" msgstr "" -#: 23a4fba35b8047e1acce90b13a0458cf driver.rui3.RUI3.set_p2p_bandwidth:7 of +#: 85be6ee462de4f0ba3b4242bf15921bf driver.rui3.RUI3.set_p2p_bandwidth:7 of msgid "1 = 250 kHz" msgstr "" -#: 1c66aa60364f4538b5b955c22dffc413 driver.rui3.RUI3.set_p2p_bandwidth:8 of +#: 4fc23df25500451b84d90ae6d5db88ec driver.rui3.RUI3.set_p2p_bandwidth:8 of msgid "2 = 500 kHz" msgstr "" -#: 925b9686ede345e18683ca2c4ae9d24e driver.rui3.RUI3.set_p2p_bandwidth:9 of +#: 6bcdb907863b4cbdbf40e00e9b5e75bd driver.rui3.RUI3.set_p2p_bandwidth:9 of msgid "3 = 7.8 kHz" msgstr "" -#: 4509ec90788a414586289112aa01b3b1 driver.rui3.RUI3.set_p2p_bandwidth:10 of +#: b25bd1ae85c24586a734aa0e3308b9e3 driver.rui3.RUI3.set_p2p_bandwidth:10 of msgid "4 = 10.4 kHz" msgstr "" -#: 4173514e23ae428fba64364afeb8dd10 driver.rui3.RUI3.set_p2p_bandwidth:11 of +#: 538832795b3b47f3a5d74dd211adbf13 driver.rui3.RUI3.set_p2p_bandwidth:11 of msgid "5 = 15.63 kHz" msgstr "" -#: 3d430cf91b7b4342b41d3e768e790137 driver.rui3.RUI3.set_p2p_bandwidth:12 of +#: 12e514301e494779b44f19d8052ee284 driver.rui3.RUI3.set_p2p_bandwidth:12 of msgid "6 = 20.83 kHz" msgstr "" -#: 8449149800ad4274b6d91686d911e959 driver.rui3.RUI3.set_p2p_bandwidth:13 of +#: 0fa7832b51a3419a96107fcba2360117 driver.rui3.RUI3.set_p2p_bandwidth:13 of msgid "7 = 31.25 kHz" msgstr "" -#: ac26e0f45b844b459c4645937802192a driver.rui3.RUI3.set_p2p_bandwidth:14 of +#: 61ab079755b64ca490d3f2ee534e634f driver.rui3.RUI3.set_p2p_bandwidth:14 of msgid "8 = 41.67 kHz" msgstr "" -#: dc1dad5c63494adaa57bf7fac5eb177d driver.rui3.RUI3.set_p2p_bandwidth:15 of +#: 76090f24b8fa4a1c9e6714e08175198b driver.rui3.RUI3.set_p2p_bandwidth:15 of msgid "9 = 62.5 kHz" msgstr "" -#: c6558b894df6452cbd9e7c970e128481 driver.rui3.RUI3.set_p2p_bandwidth:18 of +#: ce4c89b91917413bbc32678a47b9e30a driver.rui3.RUI3.set_p2p_bandwidth:18 of msgid "For FSK:" -msgstr "对于 FSK:" +msgstr "" -#: 430a26a38ce340ca82f1bdfb798439f2 driver.rui3.RUI3.set_p2p_bandwidth:18 of +#: 655be027473549848be5c5be9fe2e593 driver.rui3.RUI3.set_p2p_bandwidth:18 of msgid "Range: 4800-467000 Hz" -msgstr "范围:4800-467000 Hz" +msgstr "" -#: 054eb5eb0076486b9b5d4bfe0afc7829 driver.rui3.RUI3.set_p2p_bandwidth:21 of +#: 1c9d208bd5f543709c8c1ee3cc4ccac0 driver.rui3.RUI3.set_p2p_bandwidth:21 of msgid "bool |set_p2p_fsk_bandwidth.png| |set_p2p_lora_bandwidth.png|" msgstr "" -#: a3ded662c5444b62906a9b59ba2b1990 driver.rui3.RUI3.set_p2p_bandwidth:21 of +#: 33e15bce21a04cbd96ac9290528fa7bc driver.rui3.RUI3.set_p2p_bandwidth:21 of msgid "bool |set_p2p_fsk_bandwidth.png|" msgstr "" -#: ../../en/refs/base.lorawan_rui3.ref:34 4bc9d0f8100b4567a6ae7d5183485212 +#: ../../en/refs/base.lorawan_rui3.ref:34 2069c438842c44fabf7c94feb5b523a3 msgid "set_p2p_fsk_bandwidth.png" msgstr "" -#: 5b237226aca54e549639d771a32d69af driver.rui3.RUI3.set_p2p_bandwidth:24 of +#: 2b40d2e92a42439cb2fc5f3eca224e99 driver.rui3.RUI3.set_p2p_bandwidth:24 of msgid "|set_p2p_lora_bandwidth.png|" msgstr "" -#: ../../en/refs/base.lorawan_rui3.ref:35 0d8e42acb36b4603bf043cd584790bff +#: ../../en/refs/base.lorawan_rui3.ref:35 9a084e75a020449bb642438fed7c5edc msgid "set_p2p_lora_bandwidth.png" msgstr "" -#: 6562f39a98de4274bed1f70250740ad5 driver.rui3.RUI3.get_p2p_code_rate:1 of +#: driver.rui3.RUI3.get_p2p_code_rate:1 f5a6c97c6b6f4aa88d624e096eddce32 of msgid "Retrieve the current P2P code rate." -msgstr "获取当前 P2P 编码速率。" +msgstr "" -#: c27fcd1292d24c3e90b640e82ded8834 driver.rui3.RUI3.get_p2p_code_rate:3 of +#: c0b886881cc54b0cad71e59d570cb175 driver.rui3.RUI3.get_p2p_code_rate:3 of msgid "The current P2P code rate as an integer." -msgstr "当前 P2P 编码速率作为整数。" +msgstr "" -#: 3835a819f9e5435ea1013351b73d1ffe driver.rui3.RUI3.set_p2p_code_rate:1 of +#: 4b566eee16444c26afb2485478a2107d driver.rui3.RUI3.set_p2p_code_rate:1 of msgid "Set the P2P code rate." -msgstr "设置 P2P 编码速率。" +msgstr "" -#: 3f590f8ca6784307b94a73ffc1ce16b4 driver.rui3.RUI3.set_p2p_code_rate:3 of +#: 67a7dac05dbb4d29bc39923f682e866d driver.rui3.RUI3.set_p2p_code_rate:3 of msgid "" "The code rate to set for P2P communication. - 0 = 4/5 - 1 = 4/6 - 2 = " "4/7 - 3 = 4/8" msgstr "" -"用于 P2P 通信的编码速率。 \n" -" - 0 = 4/5 \n" -" - 1 = 4/6 \n" -" - 2 = 4/7 \n" -" - 3 = 4/8" -#: driver.rui3.RUI3.set_p2p_code_rate:3 fe996cb318ae4e6799f38db7e08127a0 of +#: 9e42cc0aa0344377a60c9ef83f8827f5 driver.rui3.RUI3.set_p2p_code_rate:3 of msgid "The code rate to set for P2P communication." -msgstr "用于 P2P 通信的编码速率。" +msgstr "" -#: deacde94e21a476a8cf5a704ac3c5dfb driver.rui3.RUI3.set_p2p_code_rate:5 of +#: 9f04ff5a8443416c8ba4ad0144408fd7 driver.rui3.RUI3.set_p2p_code_rate:5 of msgid "0 = 4/5" msgstr "" -#: 6033560f4dc742d1b53718964fad38f0 driver.rui3.RUI3.set_p2p_code_rate:6 of +#: 44ea893685604429b83deb3c522d2282 driver.rui3.RUI3.set_p2p_code_rate:6 of msgid "1 = 4/6" msgstr "" -#: 7710d652cde2435dad4099abcc453f5c driver.rui3.RUI3.set_p2p_code_rate:7 of +#: 6d79063507074ce8ada2a2763d178b11 driver.rui3.RUI3.set_p2p_code_rate:7 of msgid "2 = 4/7" msgstr "" -#: 3063f77577e1421eaf0d9df1329658f3 driver.rui3.RUI3.set_p2p_code_rate:8 of +#: a85f6dee7094440cbcfff37e6c20887c driver.rui3.RUI3.set_p2p_code_rate:8 of msgid "3 = 4/8" msgstr "" -#: d46e81e54d0549e89aa99d20150e648e driver.rui3.RUI3.get_p2p_preamble_length:1 +#: de8e20048ed6402cad489150c0bf90b6 driver.rui3.RUI3.get_p2p_preamble_length:1 #: of msgid "Retrieve the current P2P preamble length." -msgstr "获取当前 P2P 前导码长度。" +msgstr "" -#: 9b9e7741a4eb444bae16c589ad5e92d9 driver.rui3.RUI3.get_p2p_preamble_length:3 +#: b7e1969581e94fe58e604f7b1d1d35d6 driver.rui3.RUI3.get_p2p_preamble_length:3 #: of msgid "The current P2P preamble length as an integer." -msgstr "当前 P2P 前导码长度作为整数。" +msgstr "" -#: bc4eb0d4591742f3b2594ca60e3ac41a driver.rui3.RUI3.set_p2p_preamble_length:1 +#: 9cb42b8641574fa6b7bf18e418cdce00 driver.rui3.RUI3.set_p2p_preamble_length:1 #: of msgid "Set the P2P preamble length." -msgstr "设置 P2P 前导码长度。" +msgstr "" -#: 819d679dd6674b2e8b2772b174521aab driver.rui3.RUI3.set_p2p_preamble_length:6 +#: 42486c7e059e43cfb19ddec5a53b82d3 driver.rui3.RUI3.set_p2p_preamble_length:6 #: of msgid "The preamble length to set for P2P communication. - Range is 5 to 65535." msgstr "" -"用于 P2P 通信的前导码长度。 \n" -" - 范围是 5 到 65535。" -#: 8e377ee7c74649d4a9d99104d2cc9213 driver.rui3.RUI3.set_p2p_preamble_length:6 +#: 2a162fa775294934a8122e43cf591c98 driver.rui3.RUI3.set_p2p_preamble_length:6 #: of msgid "The preamble length to set for P2P communication." -msgstr "用于 P2P 通信的前导码长度。" +msgstr "" -#: driver.rui3.RUI3.set_p2p_preamble_length:8 fba62a8b8d3e49d989c06dad0233d36c +#: 37e41b65ea1b4ea38418e01fda7638fc driver.rui3.RUI3.set_p2p_preamble_length:8 #: of msgid "Range is 5 to 65535." -msgstr "范围是 5 到 65535。" +msgstr "" -#: driver.rui3.RUI3.get_p2p_tx_power:1 ec5a5913e31b4c50a37b3ff6f44aa32f of +#: 7e318d5d0b214131bfbfc2c358812595 driver.rui3.RUI3.get_p2p_tx_power:1 of msgid "Retrieve the current P2P transmission power." -msgstr "获取当前 P2P 发射功率。" +msgstr "" -#: ac069af04d0047f0b745e0ecc7aa5f4c driver.rui3.RUI3.get_p2p_tx_power:3 of +#: bf87d0c946d84dd18c90abe58258210b driver.rui3.RUI3.get_p2p_tx_power:3 of msgid "The current P2P transmission power as an integer." -msgstr "当前 P2P 发射功率作为整数。" +msgstr "" -#: 8ac15481ae6c4e8784a331abef649128 driver.rui3.RUI3.set_p2p_tx_power:1 of +#: 5d08f6447ad449e894fc6f85add974cf driver.rui3.RUI3.set_p2p_tx_power:1 of msgid "Set the P2P transmission power." -msgstr "设置 P2P 发射功率。" +msgstr "" -#: 1198d0f067054f169ce628b32c8e9004 driver.rui3.RUI3.set_p2p_tx_power:3 of +#: driver.rui3.RUI3.set_p2p_tx_power:3 f1d764e305df4bb9ac34ababaee84dfb of msgid "" "The transmission power to set for P2P communication. - Range is 5 to 22 " "dBm." msgstr "" -"用于 P2P 通信的发射功率。 \n" -" - 范围是 5 到 22 dBm。" -#: 62fc244a5ed4486099fcfb3b033fc804 driver.rui3.RUI3.set_p2p_tx_power:3 of +#: 16ef0fd9455f4a3fa2ed45f113174672 driver.rui3.RUI3.set_p2p_tx_power:3 of msgid "The transmission power to set for P2P communication." -msgstr "用于 P2P 通信的发射功率。" +msgstr "" -#: 84e69392f756496eb4b33d9126c8f492 driver.rui3.RUI3.set_p2p_tx_power:5 of +#: 1af1697e38e445bcb0c31006c341d8e3 driver.rui3.RUI3.set_p2p_tx_power:5 of msgid "Range is 5 to 22 dBm." -msgstr "范围是 5 到 22 dBm。" +msgstr "" -#: 867ad5954bed449986797d37e04497f8 driver.rui3.RUI3.get_p2p_fsk_bitrate:1 of +#: d0393790858a4d68832162f9cf34bd22 driver.rui3.RUI3.get_p2p_fsk_bitrate:1 of msgid "Retrieve the current P2P FSK bitrate." -msgstr "获取当前 P2P FSK 比特率。" +msgstr "" -#: 46f4478ed595436c9ea9eac6304647e0 driver.rui3.RUI3.set_p2p_fsk_bitrate:1 of +#: 22b42f9a40f848a296830392906cc5af driver.rui3.RUI3.set_p2p_fsk_bitrate:1 of msgid "Set the P2P FSK bitrate." -msgstr "设置 P2P FSK 比特率。" +msgstr "" -#: 29c44650c00d4097a6eeefaee736d4a6 driver.rui3.RUI3.set_p2p_fsk_bitrate:3 of +#: 5e7ae0eaacdc4c299342f3b6490b3b59 driver.rui3.RUI3.set_p2p_fsk_bitrate:3 of msgid "" "The bitrate to set for P2P FSK communication. - Range is 600 to 300000 " "b/s." msgstr "" -"用于 P2P FSK 通信的比特率。 \n" -" - 范围是 600 到 300000 b/s。" -#: 152b3fca8d634bfd864209deae6f3ccb driver.rui3.RUI3.set_p2p_fsk_bitrate:3 of +#: c36b266b958d4de7b01517cd2d399896 driver.rui3.RUI3.set_p2p_fsk_bitrate:3 of msgid "The bitrate to set for P2P FSK communication." -msgstr "用于 P2P FSK 通信的比特率。" +msgstr "" -#: bbb7ebaf6899497d9a13ca40df32af58 driver.rui3.RUI3.set_p2p_fsk_bitrate:5 of +#: 83e3b88edfa8448fb4531394ea47f9b9 driver.rui3.RUI3.set_p2p_fsk_bitrate:5 of msgid "Range is 600 to 300000 b/s." -msgstr "范围是 600 到 300000 b/s。" +msgstr "" -#: driver.rui3.RUI3.send_p2p_data:1 f62791378a374e84b03093bb4bf079a6 of +#: 9115f65eb3e241f0bdd9e6b80d3d0984 driver.rui3.RUI3.send_p2p_data:1 of msgid "Send P2P data with a given payload." -msgstr "发送带有给定负载的 P2P 数据。" +msgstr "" -#: 71a2103727c54a4388c4dce0626abe71 driver.rui3.RUI3.send_p2p_data:3 of +#: ab006735201247a2b12d3c9dfc4f3961 driver.rui3.RUI3.send_p2p_data:3 of msgid "" "The payload to send. - Length must be between 2 and 500 characters. - " "Must consist of an even number of characters composed of 0-9, a-f, A-F, " "representing 1 to 256 hexadecimal values." msgstr "" -"要发送的负载。 \n" -" - 长度必须在 2 到 500 个字符之间。 \n" -" - 必须由偶数个字符组成,字符为 0-9、a-f、A-F,表示 1 到 256 的十六进制值。" -#: 7ea318b680224736ac997c88b2cea7a3 driver.rui3.RUI3.send_p2p_data:3 of +#: 6f8aa055e420425bbf93b6398af00a7d driver.rui3.RUI3.send_p2p_data:3 of msgid "The payload to send." -msgstr "要发送的负载。" +msgstr "" -#: 8c875925d1fa473085a1ff75c9e08a3a driver.rui3.RUI3.send_p2p_data:5 of +#: 61840d1cfddb4b9db4116e23d9d2376e driver.rui3.RUI3.send_p2p_data:5 of msgid "Length must be between 2 and 500 characters." -msgstr "长度必须在 2 到 500 个字符之间。" +msgstr "" -#: driver.rui3.RUI3.send_p2p_data:6 eb94f62ea24743d384a842259d9c305b of +#: abe569709d16407f96616668925200a6 driver.rui3.RUI3.send_p2p_data:6 of msgid "" "Must consist of an even number of characters composed of 0-9, a-f, A-F, " "representing 1 to 256 hexadecimal values." -msgstr "必须由偶数个字符组成,字符为 0-9、a-f、A-F,表示 1 到 256 的十六进制值。" +msgstr "" -#: cd5d63d2f08a4606b7af7b90fb05015a driver.rui3.RUI3.send_p2p_data:7 of +#: 9045358cc1af44608ce5c9160d48ec49 driver.rui3.RUI3.send_p2p_data:7 of msgid "" "The timeout for the data transmission, in milliseconds. Default is 1000 " "ms." -msgstr "数据传输的超时时间,单位为毫秒。默认值为 1000 毫秒。" +msgstr "" -#: b8bfadb16fb6474ab8374c96d35ada22 driver.rui3.RUI3.send_p2p_data:8 of +#: driver.rui3.RUI3.send_p2p_data:8 f11ba76a35fe4294bf35d6c5a667c2f7 of msgid "" "Indicates whether to convert the payload to hexadecimal format. Default " "is False." -msgstr "是否将负载转换为十六进制格式。默认值为 False。" +msgstr "" -#: 70de31682ab44558b9f0abf375faa7fc driver.rui3.RUI3.send_p2p_data:10 of +#: driver.rui3.RUI3.send_p2p_data:10 eb3bdf72bf7e417fa6ceea56345d407f of msgid "" "True if the data was sent successfully (\"TXFSK DONE\" or \"TXP2P " "DONE\"), False otherwise." -msgstr "如果数据发送成功,则返回 True(\"TXFSK DONE\" 或 \"TXP2P DONE\"),否则返回 False。" +msgstr "" -#: 527c936609464580a0e1329a73da7fc2 driver.rui3.RUI3.send_p2p_data:11 of +#: 550ae9a6093c48359f57b6a371c26975 driver.rui3.RUI3.send_p2p_data:11 of msgid "bool |send_p2p_data_return.png|" msgstr "" -#: 527c936609464580a0e1329a73da7fc2 driver.rui3.RUI3.send_p2p_data:13 of +#: driver.rui3.RUI3.send_p2p_data:13 f51dce8afb8644acadb8a1e1778a2e6a of msgid "|send_p2p_data_return.png|" msgstr "" -#: ../../en/refs/base.lorawan_rui3.ref:45 918f82cbe3d84354adf9bd167b21a7ac +#: ../../en/refs/base.lorawan_rui3.ref:45 27b2d75799ad4beca809bf71f9be4bd1 msgid "send_p2p_data_return.png" msgstr "" -#: 2c1b4d2821184b2a918fcd8442b5f9c9 driver.rui3.RUI3.get_p2p_receive_data:1 of +#: df7941bc051b487a872095b9dad450db driver.rui3.RUI3.get_p2p_receive_data:1 of msgid "Receive data in P2P mode, including RSSI, SNR, and payload." -msgstr "在 P2P 模式下接收数据,包括 RSSI、SNR 和负载。" +msgstr "" -#: driver.rui3.RUI3.get_p2p_receive_data:3 f0bbac03238f47e6ba398e7e8061053a of +#: driver.rui3.RUI3.get_p2p_receive_data:3 ef84e6d2d6d44dab93d4243dc04d6b40 of msgid "" "Timeout for listening to P2P LoRa data packets, in milliseconds. - Valid" " values are 1 to 65535. - 0: Continuous listening. - 65535: No " "timeout." msgstr "" -"在 P2P LoRa 数据包中监听的超时时间,单位为毫秒。 \n" -" - 有效值为 1 到 65535。 \n" -" - 0:连续监听。 \n" -" - 65535:无超时。" -#: ba4871c38f674e42a59d98002a6a22ea driver.rui3.RUI3.get_p2p_receive_data:3 of +#: 2b9d8427aef0419fa64ecabf351a30f4 driver.rui3.RUI3.get_p2p_receive_data:3 of msgid "Timeout for listening to P2P LoRa data packets, in milliseconds." -msgstr "在 P2P LoRa 数据包中监听的超时时间,单位为毫秒。" +msgstr "" -#: cb3dba63a3424843925a6973e41b5eb4 driver.rui3.RUI3.get_p2p_receive_data:7 of +#: cb15861960bb42c1aca1525242426e2e driver.rui3.RUI3.get_p2p_receive_data:7 of msgid "Valid values are 1 to 65535." -msgstr "有效值为 1 到 65535。" +msgstr "" -#: driver.rui3.RUI3.get_p2p_receive_data:6 eebb8abcb3ac4355b0c77231588c7b5b of +#: driver.rui3.RUI3.get_p2p_receive_data:6 ed3b1ad55cca430dbb2edcdc75c414b1 of msgid "0: Continuous listening." -msgstr "0:连续监听。" +msgstr "" -#: a661be946b744074868f3e20609a9d70 driver.rui3.RUI3.get_p2p_receive_data:7 of +#: 5caac679d8144264a153c8456ac8423a driver.rui3.RUI3.get_p2p_receive_data:7 of msgid "65535: No timeout." -msgstr "65535:无超时。" +msgstr "" -#: 21cb11cf7f964200a5b2605b36280659 driver.rui3.RUI3.get_p2p_receive_data:9 of +#: 7e1227cbd84c4b519289bba7d8889f55 driver.rui3.RUI3.get_p2p_receive_data:9 of msgid "Indicates whether to convert the payload to a string. Default is False." -msgstr "是否将负载转换为字符串。默认值为 False。" +msgstr "" -#: 6901c0006ed24e1685bdc924c7231cd2 driver.rui3.RUI3.get_p2p_receive_data:10 of +#: 102da5e11ad545bf825ddb366f990eb5 driver.rui3.RUI3.get_p2p_receive_data:10 of msgid "" "A tuple (RSSI, SNR, Payload) if data is received; False if no data is " "received." -msgstr "如果收到数据,则返回一个元组 (RSSI, SNR, Payload);如果没有收到数据,则返回 False。" +msgstr "" -#: d99d0e4009614376b14b68057805145b driver.rui3.RUI3.get_p2p_sync_word:1 of +#: 374cf7315f5c413ea4231cdc2b8efe41 driver.rui3.RUI3.get_p2p_sync_word:1 of msgid "Get the current sync word in P2P mode." -msgstr "获取当前 P2P 同步字。" +msgstr "" -#: be4b379f4c424ac79741d1fc75d5c758 driver.rui3.RUI3.get_p2p_sync_word:3 of +#: c324dea1e30a43f08f141eeea1099940 driver.rui3.RUI3.get_p2p_sync_word:3 of msgid "The sync word as a string." -msgstr "当前 P2P 同步字作为字符串。" +msgstr "" -#: bf6897d86d584c89978358e7db2b84bd driver.rui3.RUI3.set_p2p_sync_word:1 of +#: 1a8374babbde4a87804706e8f105566f driver.rui3.RUI3.set_p2p_sync_word:1 of msgid "Set the sync word in P2P mode." -msgstr "设置 P2P 同步字。" +msgstr "" -#: driver.rui3.RUI3.set_p2p_sync_word:3 e664ccbe4973497899cd860e374be8af of +#: 318821eb2da94b02b599457990eb5eb1 driver.rui3.RUI3.set_p2p_sync_word:3 of msgid "The sync word value. - Must be in the range of 0x0000 to 0xFFFF." msgstr "" -"P2P 同步字值。 \n" -" - 必须在 0x0000 到 0xFFFF 之间。" -#: ba9815b992894ec5a7c757a7c9e48329 driver.rui3.RUI3.set_p2p_sync_word:3 of +#: 6207641a484245e797d76da1b10a05af driver.rui3.RUI3.set_p2p_sync_word:3 of msgid "The sync word value." -msgstr "P2P 同步字值。" +msgstr "" -#: 94ffbd7e8b2241d1a8a47131f838052e driver.rui3.RUI3.set_p2p_sync_word:5 of +#: 90416118db3647f4a361273b4ae03e19 driver.rui3.RUI3.set_p2p_sync_word:5 of msgid "Must be in the range of 0x0000 to 0xFFFF." -msgstr "必须在 0x0000 到 0xFFFF 之间。" +msgstr "" -#: b6f0d68b99454db5b1cfbb50886fcb31 driver.rui3.RUI3.set_p2p_sync_word:7 of +#: 868a50422ade4390a80863c1a1117eac driver.rui3.RUI3.set_p2p_sync_word:7 of msgid "The response from the command execution." -msgstr "命令执行的响应。" - -#~ msgid "UiFlow2 Code Block:" -#~ msgstr "UiFlow2 代码块:" - -#~ msgid "|init.png|" -#~ msgstr "" - -#~ msgid "init.png" -#~ msgstr "" - -#~ msgid "|get_abp_config.png|" -#~ msgstr "" - -#~ msgid "get_abp_config.png" -#~ msgstr "" - -#~ msgid "|get_otaa_config.png|" -#~ msgstr "" - -#~ msgid "get_otaa_config.png" -#~ msgstr "" - -#~ msgid "|set_abp_config.png|" -#~ msgstr "" - -#~ msgid "set_abp_config.png" -#~ msgstr "" - -#~ msgid "|set_otaa_config.png|" -#~ msgstr "" - -#~ msgid "set_otaa_config.png" -#~ msgstr "" - -#~ msgid "|get_received_data.png|" -#~ msgstr "" - -#~ msgid "|get_received_data_string.png|" -#~ msgstr "" - -#~ msgid "|get_received_data_count.png|" -#~ msgstr "" - -#~ msgid "|reset_module_to_default.png|" -#~ msgstr "" - -#~ msgid "reset_module_to_default.png" -#~ msgstr "" - -#~ msgid "|get_device_eui.png|" -#~ msgstr "" - -#~ msgid "|set_join_config.png|" -#~ msgstr "" - -#~ msgid "set_join_config.png" -#~ msgstr "" - -#~ msgid "|join_network.png|" -#~ msgstr "" - -#~ msgid "join_network.png" -#~ msgstr "" - -#~ msgid "|set_join_mode.png|" -#~ msgstr "" - -#~ msgid "set_join_mode.png" -#~ msgstr "" - -#~ msgid "|get_join_state.png|" -#~ msgstr "" - -#~ msgid "get_join_state.png" -#~ msgstr "" - -#~ msgid "|get_last_receive.png|" -#~ msgstr "" - -#~ msgid "get_last_receive.png" -#~ msgstr "" - -#~ msgid "|send_data.png|" -#~ msgstr "" - -#~ msgid "send_data.png" -#~ msgstr "" - -#~ msgid "|set_network_mode.png|" -#~ msgstr "" - -#~ msgid "set_network_mode.png" -#~ msgstr "" - -#~ msgid "|get_p2p_frequency.png|" -#~ msgstr "" - -#~ msgid "get_p2p_frequency.png" -#~ msgstr "" - -#~ msgid "|set_p2p_frequency.png|" -#~ msgstr "" - -#~ msgid "set_p2p_frequency.png" -#~ msgstr "" - -#~ msgid "|get_p2p_spreading_factor.png|" -#~ msgstr "" - -#~ msgid "get_p2p_spreading_factor.png" -#~ msgstr "" - -#~ msgid "|set_p2p_spreading_factor.png|" -#~ msgstr "" - -#~ msgid "set_p2p_spreading_factor.png" -#~ msgstr "" - -#~ msgid "|get_p2p_bandwidth.png|" -#~ msgstr "" - -#~ msgid "get_p2p_bandwidth.png" -#~ msgstr "" - -#~ msgid "|set_p2p_fsk_bandwidth.png|" -#~ msgstr "" - -#~ msgid "|get_p2p_code_rate.png|" -#~ msgstr "" - -#~ msgid "get_p2p_code_rate.png" -#~ msgstr "" - -#~ msgid "|set_p2p_code_rate.png|" -#~ msgstr "" - -#~ msgid "set_p2p_code_rate.png" -#~ msgstr "" - -#~ msgid "|get_p2p_preamble_length.png|" -#~ msgstr "" - -#~ msgid "get_p2p_preamble_length.png" -#~ msgstr "" - -#~ msgid "|set_p2p_preamble_length.png|" -#~ msgstr "" - -#~ msgid "set_p2p_preamble_length.png" -#~ msgstr "" - -#~ msgid "|get_p2p_tx_power.png|" -#~ msgstr "" - -#~ msgid "get_p2p_tx_power.png" -#~ msgstr "" - -#~ msgid "|set_p2p_tx_power.png|" -#~ msgstr "" - -#~ msgid "set_p2p_tx_power.png" -#~ msgstr "" - -#~ msgid "|get_p2p_fsk_bitrate.png|" -#~ msgstr "" - -#~ msgid "get_p2p_fsk_bitrate.png" -#~ msgstr "" - -#~ msgid "|set_p2p_fsk_bitrate.png|" -#~ msgstr "" - -#~ msgid "set_p2p_fsk_bitrate.png" -#~ msgstr "" - -#~ msgid "|send_p2p_data.png|" -#~ msgstr "" - -#~ msgid "send_p2p_data.png" -#~ msgstr "" - -#~ msgid "|get_p2p_receive_data.png|" -#~ msgstr "" - -#~ msgid "get_p2p_receive_data.png" -#~ msgstr "" - -#~ msgid "|get_p2p_sync_word.png|" -#~ msgstr "" - -#~ msgid "get_p2p_sync_word.png" -#~ msgstr "" - -#~ msgid "|set_p2p_sync_word.png|" -#~ msgstr "" - -#~ msgid "set_p2p_sync_word.png" -#~ msgstr "" +msgstr "" diff --git a/docs/locales/zh_CN/LC_MESSAGES/base/dtu_nbiot.po b/docs/locales/zh_CN/LC_MESSAGES/base/dtu_nbiot.po new file mode 100644 index 00000000..d91960f3 --- /dev/null +++ b/docs/locales/zh_CN/LC_MESSAGES/base/dtu_nbiot.po @@ -0,0 +1,171 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2016 - 2025 M5Stack Technology Co., Ltd +# This file is distributed under the same license as the UIFlow2 Programming +# Guide package. +# FIRST AUTHOR , 2025. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: UIFlow2 Programming Guide \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-05-15 12:10+0800\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: zh_CN\n" +"Language-Team: zh_CN \n" +"Plural-Forms: nplurals=1; plural=0;\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.16.0\n" + +#: ../../en/base/dtu_nbiot.rst:2 dc6bf45326a74bf49be5fb34d0ecfbd3 +msgid "Atom DTU NBIoT Base" +msgstr "" + +#: ../../en/base/dtu_nbiot.rst:8 a64f48860caf4af99a0f4ac6d0d3e425 +msgid "" +"This is the driver library for the ATOM DTU NBIoT Base to accept and send" +" data from the DTU NBIoT." +msgstr "" + +#: ../../en/base/dtu_nbiot.rst:10 2165c65bc0c54864a47c27ecf0c338a8 +msgid "Support the following products:" +msgstr "" + +#: ../../en/base/dtu_nbiot.rst:13 ebcc9ef01434446b85db48f792391283 +msgid "|Atom DTU NBIoT|" +msgstr "" + +#: ../../en/refs/base.dtu_nbiot.ref 427f07832075481687a8e30ef21d6a42 +msgid "Atom DTU NBIoT" +msgstr "" + +#: ../../en/base/dtu_nbiot.rst:13 9f58f650259a4dfcad0f7f33be94efd4 +msgid "|Atom DTU NBIoT CN|" +msgstr "" + +#: ../../en/refs/base.dtu_nbiot.ref 6d59451623804771852c0a65e45dcdd3 +msgid "Atom DTU NBIoT CN" +msgstr "" + +#: ../../en/base/dtu_nbiot.rst:18 e9bb27675f694103b13e8895645c6f60 +msgid "UiFlow2 Example" +msgstr "" + +#: ../../en/base/dtu_nbiot.rst:21 ../../en/base/dtu_nbiot.rst:39 +#: e7a588f082484f72be4346dfb3a73572 +msgid "NBIoT HTTP Example" +msgstr "" + +#: ../../en/base/dtu_nbiot.rst:23 5b9f6108c98a47a4b0065aa5851b1541 +msgid "Open the |base_nbiot_atoms3_http_example.m5f2| project in UiFlow2." +msgstr "" + +#: ../../en/base/dtu_nbiot.rst:25 ../../en/base/dtu_nbiot.rst:41 +#: f86be4b0a1024f558f50dea7183e2948 +msgid "This example shows how to send HTTP request using the Atom DTU NBIoT Base." +msgstr "" + +#: ../../en/base/dtu_nbiot.rst:27 ../../en/base/dtu_nbiot.rst:60 +#: 82cd66bf4be54279a2af5f8331bd1523 base.dtu_nbiot.AtomDTUNBIoT:7 of +msgid "UiFlow2 Code Block:" +msgstr "" + +#: ../../en/base/dtu_nbiot.rst:29 d985345df7114d6494691b72239d7ea2 +msgid "|http_example.png|" +msgstr "" + +#: ../../en/refs/base.dtu_nbiot.ref:32 480bc719553243a392517bc01d2397d4 +msgid "http_example.png" +msgstr "" + +#: ../../en/base/dtu_nbiot.rst:31 ../../en/base/dtu_nbiot.rst:49 +#: ../../en/base/dtu_nbiot.rst:64 ../../en/base/dtu_nbiot.rst:82 +#: 2cbae97da3ff4fa0b9fc0267dc7a88c0 +msgid "Example output:" +msgstr "" + +#: ../../en/base/dtu_nbiot.rst:33 ../../en/base/dtu_nbiot.rst:51 +#: ../../en/base/dtu_nbiot.rst:66 ../../en/base/dtu_nbiot.rst:84 +#: f03725e51ee04c7f82f9ddd2128e27cd +msgid "Output of received NBIoT message data via serial port." +msgstr "" + +#: ../../en/base/dtu_nbiot.rst:36 ../../en/base/dtu_nbiot.rst:69 +#: 164e8cc36a8b4a19a67bdd6a8a695028 +msgid "MicroPython Example" +msgstr "" + +#: ../../en/base/dtu_nbiot.rst:43 ../../en/base/dtu_nbiot.rst:76 +#: 00e701fe788241bcba91711e77a8a642 base.dtu_nbiot.AtomDTUNBIoT:11 of +msgid "MicroPython Code Block:" +msgstr "" + +#: ../../en/base/dtu_nbiot.rst:54 ../../en/base/dtu_nbiot.rst:72 +#: ee0cd7437df8499c9b6269cede315b3a +msgid "MQTT Example" +msgstr "" + +#: ../../en/base/dtu_nbiot.rst:56 7a7ffad9589c4e2580626e34c90b8880 +msgid "Open the |base_nbiot_atoms3_mqtt_example.m5f2| project in UiFlow2." +msgstr "" + +#: ../../en/base/dtu_nbiot.rst:58 ../../en/base/dtu_nbiot.rst:74 +#: 94c724f5f3ad49199e9de96c93131d80 +msgid "This example shows how to send MQTT message using the Atom DTU NBIoT Base." +msgstr "" + +#: ../../en/base/dtu_nbiot.rst:62 d985345df7114d6494691b72239d7ea2 +msgid "|mqtt_example.png|" +msgstr "" + +#: ../../en/refs/base.dtu_nbiot.ref:33 480bc719553243a392517bc01d2397d4 +msgid "mqtt_example.png" +msgstr "" + +#: ../../en/base/dtu_nbiot.rst:87 19ee992b3a13490f8b4a65b332482a23 +msgid "**API**" +msgstr "" + +#: ../../en/base/dtu_nbiot.rst:90 6a0d1dd4e87043d6a1eef17d691073f4 +msgid "AtomDTUNBIoT" +msgstr "" + +#: base.dtu_nbiot.AtomDTUNBIoT:1 dce00b205c734efd98a9dbb2783cc9b5 of +msgid "" +"Bases: :py:class:`~driver.simcom.sim7020.SIM7020`, " +":py:class:`~driver.simcom.common.Modem`" +msgstr "" + +#: 3f424382f8084268a30aec7990f6787f base.dtu_nbiot.AtomDTUNBIoT:1 of +msgid "Create an AtomDTUNBIoT object" +msgstr "" + +#: 84118ec1ff59477594fad4ead16a202f base.dtu_nbiot.AtomDTUNBIoT of +msgid "Parameters" +msgstr "" + +#: base.dtu_nbiot.AtomDTUNBIoT:3 c02d0ea8ce0e4aea9b32db7472b4f531 of +msgid "The UART ID to use (0, 1, or 2). Default is 2." +msgstr "" + +#: 7433dd66688b4fc9a50fe34fcbc9c633 base.dtu_nbiot.AtomDTUNBIoT:4 of +msgid "A list or tuple containing the TX and RX pin numbers." +msgstr "" + +#: base.dtu_nbiot.AtomDTUNBIoT:9 e27c7db01eb9489892b7de86551d7d69 of +msgid "|init.png|" +msgstr "" + +#: ../../en/refs/base.dtu_nbiot.ref:12 5c580082af8843538abfac23ba1b191a +msgid "init.png" +msgstr "" + +#: ../../en/base/dtu_nbiot.rst:95 edef0992b959438fbd86a620bca4538f +msgid "" +"See :ref:`base.AtomDTUNBIoT.Methods ` for more " +"details." +msgstr "" + diff --git a/docs/locales/zh_CN/LC_MESSAGES/controllers/atoms3r_cam.po b/docs/locales/zh_CN/LC_MESSAGES/controllers/atoms3r_cam.po new file mode 100644 index 00000000..3acd358a --- /dev/null +++ b/docs/locales/zh_CN/LC_MESSAGES/controllers/atoms3r_cam.po @@ -0,0 +1,91 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2016 - 2025 M5Stack Technology Co., Ltd +# This file is distributed under the same license as the UIFlow2 Programming +# Guide package. +# FIRST AUTHOR , 2025. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: UIFlow2 Programming Guide \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-04-29 15:31+0800\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: zh_CN\n" +"Language-Team: zh_CN \n" +"Plural-Forms: nplurals=1; plural=0;\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.16.0\n" + +#: ../../en/controllers/atoms3r_cam.rst:2 +#: ../../en/refs/controllers.atoms3r_cam.ref 8eb2843da2ab445da4a8f26153ca3cc3 +#: de6ee942676846508fa0cfcf01e7cb3f +msgid "AtomS3R-CAM" +msgstr "" + +#: ../../en/controllers/atoms3r_cam.rst:8 2623b3b683194821a82301ead5facba1 +msgid "Support the following products:" +msgstr "" + +#: ../../en/controllers/atoms3r_cam.rst:10 d8af0b88a3744b0a87d9cdc48da40bca +msgid "|AtomS3R-CAM|" +msgstr "" + +#: ../../en/controllers/atoms3r_cam.rst:14 981a43b769b34dbd873875b1cecd050d +msgid "MicroPython Example:" +msgstr "" + +#: ../../en/controllers/atoms3r_cam.rst:17 c344552ed48f4ff991a7c46114d7821c +msgid "Video Streaming" +msgstr "" + +#: ../../en/controllers/atoms3r_cam.rst:19 4877248df4104da8a4df83d0fbd05931 +msgid "" +"This example implements a real-time video streaming server, with " +"integrated QR code recognition." +msgstr "" + +#: ../../en/controllers/atoms3r_cam.rst:21 a91f99561fb34b529ea8dae55a249c04 +msgid "MicroPython Code Block:" +msgstr "" + +#: ../../en/controllers/atoms3r_cam.rst:27 222306a53a454cf08dc1a59627595a11 +msgid "Example output:" +msgstr "" + +#: ../../en/controllers/atoms3r_cam.rst:29 bb8fb513fab24e71810644b12aafb6c5 +msgid "None" +msgstr "" + +#: ../../en/controllers/atoms3r_cam.rst:32 ee4e08c6d90944bb81fae6a6e26a4f3a +msgid "**How to Use**" +msgstr "" + +#: ../../en/controllers/atoms3r_cam.rst:34 d819c4b676e440288a9975a8dfd59727 +msgid "Configure Wi-Fi settings." +msgstr "" + +#: ../../en/controllers/atoms3r_cam.rst:40 e66bb36038744b1f88cd6b81dd8441c2 +msgid "Copy the example code into the editor." +msgstr "" + +#: ../../en/controllers/atoms3r_cam.rst:46 0cc32a52d2ae42c181f97a359ee4b8ae +msgid "" +"Run the program, After uploading, the console will print the local IP " +"address assigned to the device." +msgstr "" + +#: ../../en/controllers/atoms3r_cam.rst:56 234baef105454c1db278df4e10b13166 +msgid "Open the video stream in your browser" +msgstr "" + +#: ../../en/controllers/atoms3r_cam.rst:58 624ea92618eb402582848878d035097e +msgid "" +"On any device connected to the same Wi-Fi network, open a browser and " +"visit: http://:8080/, Replace `` with the actual IP" +" address printed in the console." +msgstr "" + diff --git a/docs/locales/zh_CN/LC_MESSAGES/hardware/display.po b/docs/locales/zh_CN/LC_MESSAGES/hardware/display.po index af891c0b..f163a778 100644 --- a/docs/locales/zh_CN/LC_MESSAGES/hardware/display.po +++ b/docs/locales/zh_CN/LC_MESSAGES/hardware/display.po @@ -9,7 +9,7 @@ msgid "" msgstr "" "Project-Id-Version: UIFlow2 Programming Guide \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-04-03 10:58+0800\n" +"POT-Creation-Date: 2025-05-15 15:04+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language: zh_CN\n" @@ -68,22 +68,22 @@ msgstr "" msgid "Returns An integer representing the display's rotation:" msgstr "" -#: ../../en/hardware/display.rst:88 ../../en/hardware/display.rst:115 +#: ../../en/hardware/display.rst:88 ../../en/hardware/display.rst:114 #: 33efa20d8dac4b28b612bec7fb31efed 3dd7de9babf541a38a01b18b33830b98 msgid "``1``: 0° rotation" msgstr "" -#: ../../en/hardware/display.rst:89 ../../en/hardware/display.rst:116 +#: ../../en/hardware/display.rst:89 ../../en/hardware/display.rst:115 #: 3f16b99bfc85490986676b259f65714f 66d53eee62a34a9db1f037123a4e0f05 msgid "``2``: 90° rotation" msgstr "" -#: ../../en/hardware/display.rst:90 ../../en/hardware/display.rst:117 +#: ../../en/hardware/display.rst:90 ../../en/hardware/display.rst:116 #: 0e8f3509025f474e897895a034724b2d fb3f6b1b88d8480e8a7b2a5fe196e4b9 msgid "``3``: 180° rotation" msgstr "" -#: ../../en/hardware/display.rst:91 ../../en/hardware/display.rst:118 +#: ../../en/hardware/display.rst:91 ../../en/hardware/display.rst:117 #: 70b952cf1148452bbc4d2303bff4dff9 afe0828ae5014383be6b479330e42c8e msgid "``4``: 270° rotation" msgstr "" @@ -96,523 +96,547 @@ msgstr "" msgid "Returns An integer representing the display's color depth in bits." msgstr "" -#: ../../en/hardware/display.rst:111 2029e6fa89cf4097ab82a722ee6309c9 +#: ../../en/hardware/display.rst:110 2029e6fa89cf4097ab82a722ee6309c9 msgid "Set the rotation of the display." msgstr "" -#: ../../en/hardware/display.rst:113 4ffd7df483ff48e7899cfc2756b1a52c +#: ../../en/hardware/display.rst:112 4ffd7df483ff48e7899cfc2756b1a52c msgid "The ``r`` parameter only accepts the following values:" msgstr "" -#: ../../en/hardware/display.rst:122 84fa0e39d9e742158d5dd2c2889a44ea +#: ../../en/hardware/display.rst:121 84fa0e39d9e742158d5dd2c2889a44ea msgid "Set the color depth of the display." msgstr "" -#: ../../en/hardware/display.rst:124 d8544534a76a4bd78c9f243ba430c6d6 +#: ../../en/hardware/display.rst:123 d8544534a76a4bd78c9f243ba430c6d6 msgid "``bpp`` The desired color depth in bits per pixel." msgstr "" -#: ../../en/hardware/display.rst:126 187162d692de4e54a8dfcbdb1f2eec9c +#: ../../en/hardware/display.rst:125 187162d692de4e54a8dfcbdb1f2eec9c msgid "" "Notes: For CoreS3 devices, the color depth is fixed at 16 bits, and this " "method has no effect." msgstr "" -#: ../../en/hardware/display.rst:130 0f81544fd03048eaa0472b7ae184a76b +#: ../../en/hardware/display.rst:129 0f81544fd03048eaa0472b7ae184a76b +msgid "Set the EPD mode for the display." +msgstr "" + +#: ../../en/hardware/display.rst:135 a8b963a4b4664b5397a7747f5a050ea0 +msgid "``epd_mode`` The desired EPD mode." +msgstr "" + +#: ../../en/hardware/display.rst:132 ba6ca73de649468c80bd836c4b046fa9 +msgid "0: M5.Lcd.EPDMode.EPD_QUALITY" +msgstr "" + +#: ../../en/hardware/display.rst:133 0deec3adbca749ebb35279fc63ab679e +msgid "1: M5.Lcd.EPDMode.EPD_TEXT" +msgstr "" + +#: ../../en/hardware/display.rst:134 e696e419d89c470e9357e1dc02c938bf +msgid "2: M5.Lcd.EPDMode.EPD_FAST" +msgstr "" + +#: ../../en/hardware/display.rst:135 c90f9e2f71684befa54d9d6da3e02159 +msgid "3: M5.Lcd.EPDMode.EPD_FASTEST" +msgstr "" + +#: ../../en/hardware/display.rst:137 f96cfa699bfc411a9edd95b86309e7d3 +msgid "" +"Notes: This method is only applicable to devices with EPD (Electronic " +"Paper Display) capabilities." +msgstr "" + +#: ../../en/hardware/display.rst:141 006618c93ed7484f9607db44272aab83 +msgid "Check if the display is an EPD (Electronic Paper Display)." +msgstr "" + +#: ../../en/hardware/display.rst:143 d94f0da60e934f63b29fa80c71b07673 +msgid "Returns A boolean indicating whether the display is an EPD." +msgstr "" + +#: ../../en/hardware/display.rst:147 0f81544fd03048eaa0472b7ae184a76b msgid "Set the font for the display." msgstr "" -#: ../../en/hardware/display.rst:132 0ac8b90657b34f5a81178b6b28ec7006 +#: ../../en/hardware/display.rst:149 0ac8b90657b34f5a81178b6b28ec7006 msgid "The ``font`` parameter only accepts the following values:" msgstr "" -#: ../../en/hardware/display.rst:134 70ecf03e47e9413e970c5fd8d9f464da +#: ../../en/hardware/display.rst:151 70ecf03e47e9413e970c5fd8d9f464da msgid "M5.Lcd.FONTS.ASCII7" msgstr "" -#: ../../en/hardware/display.rst:135 55c76b14f10c4f38a676ecbf9eef7684 +#: ../../en/hardware/display.rst:152 55c76b14f10c4f38a676ecbf9eef7684 msgid "M5.Lcd.FONTS.DejaVu9" msgstr "" -#: ../../en/hardware/display.rst:136 6003f869a00d4a72b842fe8f67b93ad5 +#: ../../en/hardware/display.rst:153 6003f869a00d4a72b842fe8f67b93ad5 msgid "M5.Lcd.FONTS.DejaVu12" msgstr "" -#: ../../en/hardware/display.rst:137 5f4d68fcee714f4ea6e43caeaa67d8c5 +#: ../../en/hardware/display.rst:154 5f4d68fcee714f4ea6e43caeaa67d8c5 msgid "M5.Lcd.FONTS.DejaVu18" msgstr "" -#: ../../en/hardware/display.rst:138 25ba25b358454e95afeab920787f75d1 +#: ../../en/hardware/display.rst:155 25ba25b358454e95afeab920787f75d1 msgid "M5.Lcd.FONTS.DejaVu24" msgstr "" -#: ../../en/hardware/display.rst:139 42875ff7686d42e09e27c4392984a15d +#: ../../en/hardware/display.rst:156 42875ff7686d42e09e27c4392984a15d msgid "M5.Lcd.FONTS.DejaVu40" msgstr "" -#: ../../en/hardware/display.rst:140 730c79be54874468babdb22af5d2120a +#: ../../en/hardware/display.rst:157 730c79be54874468babdb22af5d2120a msgid "M5.Lcd.FONTS.DejaVu56" msgstr "" -#: ../../en/hardware/display.rst:141 0c2287e86f80436f8ccf800f0a5c9c21 +#: ../../en/hardware/display.rst:158 0c2287e86f80436f8ccf800f0a5c9c21 msgid "M5.Lcd.FONTS.DejaVu72" msgstr "" -#: ../../en/hardware/display.rst:142 81c4b6f55192451ca643b15d81bd0667 +#: ../../en/hardware/display.rst:159 81c4b6f55192451ca643b15d81bd0667 msgid "M5.Lcd.FONTS.EFontCN24" msgstr "" -#: ../../en/hardware/display.rst:143 14f75c4e5a334be6bd48f8c462fc02ee +#: ../../en/hardware/display.rst:160 14f75c4e5a334be6bd48f8c462fc02ee msgid "M5.Lcd.FONTS.EFontJA24" msgstr "" -#: ../../en/hardware/display.rst:144 5c6ff79e48be41899aa2ff7f90fb0988 +#: ../../en/hardware/display.rst:161 5c6ff79e48be41899aa2ff7f90fb0988 msgid "M5.Lcd.FONTS.EFontKR24" msgstr "" -#: ../../en/hardware/display.rst:148 cb5cb858ab004c379656a012bb4196e7 +#: ../../en/hardware/display.rst:165 cb5cb858ab004c379656a012bb4196e7 msgid "Set the text color and background color." msgstr "" -#: ../../en/hardware/display.rst:150 073f682993e64b4d94935306ea75dba7 +#: ../../en/hardware/display.rst:167 073f682993e64b4d94935306ea75dba7 msgid "``fgcolor`` The text color in RGB888 format. Default is 0 (black)." msgstr "" -#: ../../en/hardware/display.rst:151 9e331907256c4f72873cb730285745a6 +#: ../../en/hardware/display.rst:168 9e331907256c4f72873cb730285745a6 msgid "``bgcolor`` The background color in RGB888 format. Default is 0 (black)." msgstr "" -#: ../../en/hardware/display.rst:155 2eb30dfd0ba44c9ba485d63dbe08f319 +#: ../../en/hardware/display.rst:172 2eb30dfd0ba44c9ba485d63dbe08f319 msgid "Enable or disable text scrolling." msgstr "" -#: ../../en/hardware/display.rst:157 ebe4596068a1488e83049188301a408b +#: ../../en/hardware/display.rst:174 ebe4596068a1488e83049188301a408b msgid "" "``scroll`` Set to True to enable text scrolling, or False to disable it. " "Default is False.\\" msgstr "" -#: ../../en/hardware/display.rst:161 f5b4f369bfa34bb799785cd6b43faa12 +#: ../../en/hardware/display.rst:178 f5b4f369bfa34bb799785cd6b43faa12 msgid "Set the size of the text." msgstr "" -#: ../../en/hardware/display.rst:163 a8b963a4b4664b5397a7747f5a050ea0 +#: ../../en/hardware/display.rst:180 a8b963a4b4664b5397a7747f5a050ea0 msgid "``size`` The desired text size." msgstr "" -#: ../../en/hardware/display.rst:167 98e181ccd619448297da49ed1700b3d0 +#: ../../en/hardware/display.rst:184 98e181ccd619448297da49ed1700b3d0 msgid "Set the cursor position." msgstr "" -#: ../../en/hardware/display.rst:169 771bde5a833740c29ecfd8d7aee8622c +#: ../../en/hardware/display.rst:186 771bde5a833740c29ecfd8d7aee8622c msgid "``x`` The horizontal position of the cursor. Default is 0." msgstr "" -#: ../../en/hardware/display.rst:170 e157a26ce0ac410d924ac5eeddf40640 +#: ../../en/hardware/display.rst:187 e157a26ce0ac410d924ac5eeddf40640 msgid "``y`` The vertical position of the cursor. Default is 0." msgstr "" -#: ../../en/hardware/display.rst:174 83c1a89ee02f45e8915a963ba30c3550 +#: ../../en/hardware/display.rst:191 83c1a89ee02f45e8915a963ba30c3550 msgid "Clear the display with a specific color." msgstr "" -#: ../../en/hardware/display.rst:176 ../../en/hardware/display.rst:182 -#: ../../en/hardware/display.rst:209 ../../en/hardware/display.rst:229 -#: 5c6c191600e7470580955dee132263de 6158bc48b710428d85d9c0b22f47beca -#: 660360a923c04931867e1eeac0178bf5 6e69b2a76f7045649ede8d046b131ddf +#: ../../en/hardware/display.rst:193 ../../en/hardware/display.rst:199 +#: ../../en/hardware/display.rst:226 ../../en/hardware/display.rst:246 +#: 660360a923c04931867e1eeac0178bf5 msgid "``color`` The fill color in RGB888 format. Default is 0." msgstr "" -#: ../../en/hardware/display.rst:180 ad36a6ff28054197a23b24047899fea3 +#: ../../en/hardware/display.rst:197 ad36a6ff28054197a23b24047899fea3 msgid "Fill the entire screen with a specified color." msgstr "" -#: ../../en/hardware/display.rst:187 2418963bed0d48e9a5746a9cbdd66e81 +#: ../../en/hardware/display.rst:204 2418963bed0d48e9a5746a9cbdd66e81 msgid "Draw a single pixel on the screen." msgstr "" -#: ../../en/hardware/display.rst:189 337ede9ed7534f1bbd55ae51ee52f79f +#: ../../en/hardware/display.rst:206 337ede9ed7534f1bbd55ae51ee52f79f msgid "``x`` The horizontal coordinate of the pixel. Default is -1." msgstr "" -#: ../../en/hardware/display.rst:190 e74c3aec349c4a228afeed6dacb11a76 +#: ../../en/hardware/display.rst:207 e74c3aec349c4a228afeed6dacb11a76 msgid "``y`` The vertical coordinate of the pixel. Default is -1." msgstr "" -#: ../../en/hardware/display.rst:191 d6c1bd4334564b6d9aa1d220e51020cd +#: ../../en/hardware/display.rst:208 d6c1bd4334564b6d9aa1d220e51020cd msgid "``color`` The color of the pixel in RGB888 format. Default is 0." msgstr "" -#: ../../en/hardware/display.rst:195 015370504d89468bb1929b4536fb4fa2 +#: ../../en/hardware/display.rst:212 015370504d89468bb1929b4536fb4fa2 msgid "Draw an outline of a circle." msgstr "" -#: ../../en/hardware/display.rst:197 ../../en/hardware/display.rst:206 -#: 77d5adb379934dfa8ff74bda63295f9c 863926a6763441dbaae2892725d2f62a +#: ../../en/hardware/display.rst:214 ../../en/hardware/display.rst:223 +#: 77d5adb379934dfa8ff74bda63295f9c msgid "``x`` The x-coordinate of the circle center. Default is -1." msgstr "" -#: ../../en/hardware/display.rst:198 ../../en/hardware/display.rst:207 -#: 3b87435835a14267863f424d4794080f 3e5c3ba1bc4642859a2004361cb622b1 +#: ../../en/hardware/display.rst:215 ../../en/hardware/display.rst:224 +#: 3b87435835a14267863f424d4794080f msgid "``y`` The y-coordinate of the circle center. Default is -1." msgstr "" -#: ../../en/hardware/display.rst:199 ../../en/hardware/display.rst:208 -#: 1009c0a41f5b48abb15e8a4f11dc215b a28630daff04430ca801619b9e052925 +#: ../../en/hardware/display.rst:216 ../../en/hardware/display.rst:225 +#: 1009c0a41f5b48abb15e8a4f11dc215b msgid "``r`` The radius of the circle. Default is -1." msgstr "" -#: ../../en/hardware/display.rst:200 1c23e3a4cb474ca4a7bcd0449faa8192 +#: ../../en/hardware/display.rst:217 1c23e3a4cb474ca4a7bcd0449faa8192 msgid "``color`` The color of the circle in RGB888 format. Default is 0." msgstr "" -#: ../../en/hardware/display.rst:204 07bc7ab86d564853906e0740823c4e98 +#: ../../en/hardware/display.rst:221 07bc7ab86d564853906e0740823c4e98 msgid "Draw a filled circle." msgstr "" -#: ../../en/hardware/display.rst:213 1d42c9f07f2b44ecab79ca8bf55d644a +#: ../../en/hardware/display.rst:230 1d42c9f07f2b44ecab79ca8bf55d644a msgid "Draw an outline of an ellipse." msgstr "" -#: ../../en/hardware/display.rst:215 ../../en/hardware/display.rst:225 -#: 24d77e38257545f2965cf9a073ed023c 6e050933b5614f6a98a6ea2a610477c6 +#: ../../en/hardware/display.rst:232 ../../en/hardware/display.rst:242 +#: 24d77e38257545f2965cf9a073ed023c msgid "``x`` The x-coordinate of the ellipse center. Default is -1." msgstr "" -#: ../../en/hardware/display.rst:216 ../../en/hardware/display.rst:226 -#: 81ae0a1839594f7483626452d9572e62 ffe2e71aee0b46b09b5de368b3a6a8fb +#: ../../en/hardware/display.rst:233 ../../en/hardware/display.rst:243 +#: 81ae0a1839594f7483626452d9572e62 msgid "``y`` The y-coordinate of the ellipse center. Default is -1." msgstr "" -#: ../../en/hardware/display.rst:217 ../../en/hardware/display.rst:227 -#: 07f7db561b86411b97ac4781ee35c2f2 7a32fe81ff0c4dcd972b621b65c24d1d +#: ../../en/hardware/display.rst:234 ../../en/hardware/display.rst:244 +#: 07f7db561b86411b97ac4781ee35c2f2 msgid "``rx`` The horizontal radius of the ellipse. Default is -1." msgstr "" -#: ../../en/hardware/display.rst:218 ../../en/hardware/display.rst:228 -#: 330704d9440847e18716fa06f2bb9081 ff96fdcea2544f139029752d08c31d97 +#: ../../en/hardware/display.rst:235 ../../en/hardware/display.rst:245 +#: 330704d9440847e18716fa06f2bb9081 msgid "``ry`` The vertical radius of the ellipse. Default is -1." msgstr "" -#: ../../en/hardware/display.rst:219 af5d142b59c747f9b7fcc9d1e980a2b3 +#: ../../en/hardware/display.rst:236 af5d142b59c747f9b7fcc9d1e980a2b3 msgid "``color`` The color of the ellipse in RGB888 format. Default is 0." msgstr "" -#: ../../en/hardware/display.rst:223 f9fdc102118b4bb98b24f6a83795ec93 +#: ../../en/hardware/display.rst:240 f9fdc102118b4bb98b24f6a83795ec93 msgid "Draw a filled ellipse." msgstr "" -#: ../../en/hardware/display.rst:233 48fd5a39d6a34732aa85dc90bbd64fd7 +#: ../../en/hardware/display.rst:250 48fd5a39d6a34732aa85dc90bbd64fd7 msgid "Draw a line." msgstr "" -#: ../../en/hardware/display.rst:235 c0fe69d7f5724e1aae9b2e306402fba5 +#: ../../en/hardware/display.rst:252 c0fe69d7f5724e1aae9b2e306402fba5 msgid "``x0, y0`` Starting point coordinates of the line. Default is -1." msgstr "" -#: ../../en/hardware/display.rst:236 79e8969ca13c432d8e0b540bb3014841 +#: ../../en/hardware/display.rst:253 79e8969ca13c432d8e0b540bb3014841 msgid "``x1, y1`` Ending point coordinates of the line. Default is -1." msgstr "" -#: ../../en/hardware/display.rst:237 ../../en/hardware/display.rst:245 -#: ../../en/hardware/display.rst:253 ../../en/hardware/display.rst:262 -#: ../../en/hardware/display.rst:271 ../../en/hardware/display.rst:281 -#: ../../en/hardware/display.rst:301 ../../en/hardware/display.rst:312 -#: ../../en/hardware/display.rst:323 ../../en/hardware/display.rst:432 -#: 24788bfe1cbc41e29ecc10abbbc4637b 539e8765c9434fd58fa18bc8de903e07 -#: 57f6f9a941a1441d83ab0ff66aac7cb8 6f337ea89150442e9167af30eea8de58 -#: 74d2ff1f47364f49b540c723fc9df79b 942ef45baaab438bb5825266fef2e054 -#: c2e89a55cf6c49598c7d4c12d5e1949e c4cac61a47ca4c5b911500c98a5cc756 -#: d54dd490e6554e55b6bd35123a6a2550 e2afc30dc1ef42e9ae00ea9bdb5b3b2c +#: ../../en/hardware/display.rst:254 ../../en/hardware/display.rst:262 +#: ../../en/hardware/display.rst:270 ../../en/hardware/display.rst:279 +#: ../../en/hardware/display.rst:288 ../../en/hardware/display.rst:298 +#: ../../en/hardware/display.rst:318 ../../en/hardware/display.rst:329 +#: ../../en/hardware/display.rst:340 ../../en/hardware/display.rst:449 +#: c4cac61a47ca4c5b911500c98a5cc756 d54dd490e6554e55b6bd35123a6a2550 msgid "``color`` Color in RGB888 format. Default is 0." msgstr "" -#: ../../en/hardware/display.rst:241 0a29cdb2f0f64aa7b6d9366107d5e8bc +#: ../../en/hardware/display.rst:258 0a29cdb2f0f64aa7b6d9366107d5e8bc msgid "Draw a rectangle." msgstr "" -#: ../../en/hardware/display.rst:243 ../../en/hardware/display.rst:251 -#: ../../en/hardware/display.rst:259 ../../en/hardware/display.rst:268 +#: ../../en/hardware/display.rst:260 ../../en/hardware/display.rst:268 +#: ../../en/hardware/display.rst:276 ../../en/hardware/display.rst:285 #: 42eb9ae3cb1149f6a226479725d40ec1 6ac97cb9143f4f11aa512938ba83d13a -#: a88789b2c3534cc988cdeaffe6cc9427 bcef1c53b3a74f71ae30c1df84bab91b msgid "``x, y`` Top-left corner coordinates of the rectangle. Default is -1." msgstr "" -#: ../../en/hardware/display.rst:244 ../../en/hardware/display.rst:252 -#: ../../en/hardware/display.rst:260 ../../en/hardware/display.rst:269 -#: 04a95103cf414d22b04b244d9e91f188 5b5d1796383940d2a689464495d31bcd -#: b333928906f0460bbda516b8ae35531f e8baf4ca71744fd1b712269867852fc6 +#: ../../en/hardware/display.rst:261 ../../en/hardware/display.rst:269 +#: ../../en/hardware/display.rst:277 ../../en/hardware/display.rst:286 +#: 04a95103cf414d22b04b244d9e91f188 b333928906f0460bbda516b8ae35531f msgid "``w, h`` Width and height of the rectangle. Default is -1." msgstr "" -#: ../../en/hardware/display.rst:249 8f29ed2e67f148abb837e252f69d4e4c +#: ../../en/hardware/display.rst:266 8f29ed2e67f148abb837e252f69d4e4c msgid "Draw a filled rectangle." msgstr "" -#: ../../en/hardware/display.rst:257 85acb75a70574c3a8d76199c7cc964dc +#: ../../en/hardware/display.rst:274 85acb75a70574c3a8d76199c7cc964dc msgid "Draw a rounded rectangle." msgstr "" -#: ../../en/hardware/display.rst:261 ../../en/hardware/display.rst:270 -#: 5472da802071497ba0e7f752a5ab4735 c1dcd70992ab41199f8324e91583bda5 +#: ../../en/hardware/display.rst:278 ../../en/hardware/display.rst:287 +#: c1dcd70992ab41199f8324e91583bda5 msgid "``r`` Radius of the corners. Default is -1." msgstr "" -#: ../../en/hardware/display.rst:266 8ed5f96f0d664a51857a29d010eb4ea4 +#: ../../en/hardware/display.rst:283 8ed5f96f0d664a51857a29d010eb4ea4 msgid "Draw a filled rounded rectangle." msgstr "" -#: ../../en/hardware/display.rst:276 4816740775324eff9f5e4d8ef6b6937a +#: ../../en/hardware/display.rst:293 4816740775324eff9f5e4d8ef6b6937a msgid "Draw a triangle." msgstr "" -#: ../../en/hardware/display.rst:278 ../../en/hardware/display.rst:287 -#: 484283251a0b4e598e8545244bd3264d f4f90969963749fd97d902bc63bfb54a +#: ../../en/hardware/display.rst:295 ../../en/hardware/display.rst:304 +#: 484283251a0b4e598e8545244bd3264d msgid "``x0, y0`` Coordinates of the first vertex. Default is -1." msgstr "" -#: ../../en/hardware/display.rst:279 ../../en/hardware/display.rst:288 -#: 55b060b3691f48b2b374797cdac2048f cc94adf56a2e499b9641097a6df2d8f0 +#: ../../en/hardware/display.rst:296 ../../en/hardware/display.rst:305 +#: cc94adf56a2e499b9641097a6df2d8f0 msgid "``x1, y1`` Coordinates of the second vertex. Default is -1." msgstr "" -#: ../../en/hardware/display.rst:280 ../../en/hardware/display.rst:289 -#: 3af3ce7444b445abb9da82f32777248c a9663440d96b40eea392c36e43893dcf +#: ../../en/hardware/display.rst:297 ../../en/hardware/display.rst:306 +#: 3af3ce7444b445abb9da82f32777248c msgid "``x2, y2`` Coordinates of the third vertex. Default is -1." msgstr "" -#: ../../en/hardware/display.rst:285 4508fb8c0c854ac3b7dfffc3eb25efea +#: ../../en/hardware/display.rst:302 4508fb8c0c854ac3b7dfffc3eb25efea msgid "Draw a filled triangle." msgstr "" -#: ../../en/hardware/display.rst:290 ../../en/hardware/display.rst:334 -#: d763684a13ad430793765840c8763d9a ffc99121de0d42e2b351a5d2677aaf91 +#: ../../en/hardware/display.rst:307 ../../en/hardware/display.rst:351 +#: ffc99121de0d42e2b351a5d2677aaf91 msgid "``color:`` Color in RGB888 format. Default is 0." msgstr "" -#: ../../en/hardware/display.rst:294 74c911debb5b4a588ad51a12579f2ea7 +#: ../../en/hardware/display.rst:311 74c911debb5b4a588ad51a12579f2ea7 msgid "Draw an arc." msgstr "" -#: ../../en/hardware/display.rst:296 ../../en/hardware/display.rst:307 -#: 72f177a7ab0f4b43ac3f2cfdad4c6feb e99fd046015041e3a8cf216fcd72c389 +#: ../../en/hardware/display.rst:313 ../../en/hardware/display.rst:324 +#: 72f177a7ab0f4b43ac3f2cfdad4c6feb msgid "``x, y`` Center coordinates of the arc. Default is -1." msgstr "" -#: ../../en/hardware/display.rst:297 ../../en/hardware/display.rst:308 -#: 0db0325821c8428ab37ed79e14bb5b5f 2c850d418e57494090dcfe01371bfbfd +#: ../../en/hardware/display.rst:314 ../../en/hardware/display.rst:325 +#: 0db0325821c8428ab37ed79e14bb5b5f msgid "``r0`` Inner radius of the arc. Default is -1." msgstr "" -#: ../../en/hardware/display.rst:298 ../../en/hardware/display.rst:309 -#: 4bb6720c490d4aa58a843f4fc1808579 e4fb7219caec4466a0ebcc623273c8fb +#: ../../en/hardware/display.rst:315 ../../en/hardware/display.rst:326 +#: 4bb6720c490d4aa58a843f4fc1808579 msgid "``r1`` Outer radius of the arc. Default is -1." msgstr "" -#: ../../en/hardware/display.rst:299 ../../en/hardware/display.rst:310 -#: ../../en/hardware/display.rst:321 ../../en/hardware/display.rst:332 -#: 287d6b2235704e999d0846b54ad44bf6 714a761ff87948a699ef2f210d77815a -#: a8f5218579934eb8a5389cbcaecdf202 c567be8ad7ec4c2fb2b8962d11b9ac38 +#: ../../en/hardware/display.rst:316 ../../en/hardware/display.rst:327 +#: ../../en/hardware/display.rst:338 ../../en/hardware/display.rst:349 +#: a8f5218579934eb8a5389cbcaecdf202 msgid "``angle0`` Starting angle of the arc (in degrees). Default is -1." msgstr "" -#: ../../en/hardware/display.rst:300 ../../en/hardware/display.rst:311 -#: ../../en/hardware/display.rst:333 40bd66bf56df47db83cac75a454a229b -#: 49467d333f4742f5bdbb188f01da4b81 b16c8fca83e944918433ee5c36c7eb1a +#: ../../en/hardware/display.rst:317 ../../en/hardware/display.rst:328 +#: ../../en/hardware/display.rst:350 b16c8fca83e944918433ee5c36c7eb1a msgid "``angle1`` Ending angle of the arc (in degrees). Default is -1." msgstr "" -#: ../../en/hardware/display.rst:305 e41b306671c7438ca1f58318f470d733 +#: ../../en/hardware/display.rst:322 e41b306671c7438ca1f58318f470d733 msgid "Draw a filled arc." msgstr "" -#: ../../en/hardware/display.rst:316 1f2b686832704358aa5ed7f0c6330d50 +#: ../../en/hardware/display.rst:333 1f2b686832704358aa5ed7f0c6330d50 msgid "Draw an elliptical arc." msgstr "" -#: ../../en/hardware/display.rst:318 ../../en/hardware/display.rst:329 -#: 2c800cef0f274ff481799f2ae3e8a3e4 f2560e1d1dee4a10ae037ce2a9a6ed5e +#: ../../en/hardware/display.rst:335 ../../en/hardware/display.rst:346 +#: 2c800cef0f274ff481799f2ae3e8a3e4 msgid "``x, y`` Center coordinates of the elliptical arc. Default is -1." msgstr "" -#: ../../en/hardware/display.rst:319 ../../en/hardware/display.rst:330 -#: a7c02230a9074a97981e167fc1cdf87e fba42ecaffbe4b9686a5483957ecf91f +#: ../../en/hardware/display.rst:336 ../../en/hardware/display.rst:347 +#: fba42ecaffbe4b9686a5483957ecf91f msgid "" "``r0x, r0y`` Radii of the inner ellipse (horizontal and vertical). " "Default is -1." msgstr "" -#: ../../en/hardware/display.rst:320 ../../en/hardware/display.rst:331 -#: 5fcd2eb12ecf4aa09840ab6ddd362e70 a1c4d1d2427f4ae980c42c2cd2d03257 +#: ../../en/hardware/display.rst:337 ../../en/hardware/display.rst:348 +#: 5fcd2eb12ecf4aa09840ab6ddd362e70 msgid "" "``r1x, r1y`` Radii of the outer ellipse (horizontal and vertical). " "Default is -1." msgstr "" -#: ../../en/hardware/display.rst:322 58c1ccb2b1354a08a54c9cf871275df0 +#: ../../en/hardware/display.rst:339 58c1ccb2b1354a08a54c9cf871275df0 msgid "``angle1`` Ending angle of the arc (in degrees). Default is 0." msgstr "" -#: ../../en/hardware/display.rst:327 2faaa85a742d42178cd966a211e133d1 +#: ../../en/hardware/display.rst:344 2faaa85a742d42178cd966a211e133d1 msgid "Draw a filled elliptical arc." msgstr "" -#: ../../en/hardware/display.rst:338 ba2e249576d04aadb7cbadbc59a09e5a +#: ../../en/hardware/display.rst:355 ba2e249576d04aadb7cbadbc59a09e5a msgid "Draw a QR code." msgstr "" -#: ../../en/hardware/display.rst:340 79210a88c6ee4810b7f041725ea5b38f +#: ../../en/hardware/display.rst:357 79210a88c6ee4810b7f041725ea5b38f msgid "``text`` QR code content." msgstr "" -#: ../../en/hardware/display.rst:341 a3cef6d055244ec286e010e5e05779d8 +#: ../../en/hardware/display.rst:358 a3cef6d055244ec286e010e5e05779d8 msgid "``x, y`` Starting coordinates for displaying the QR code." msgstr "" -#: ../../en/hardware/display.rst:342 c1d3be9e7dc545f3a76e5d982c9c50e5 +#: ../../en/hardware/display.rst:359 c1d3be9e7dc545f3a76e5d982c9c50e5 msgid "``w:`` Width of the QR code. Default is 0." msgstr "" -#: ../../en/hardware/display.rst:343 a5f85a36391c45dd8733951eb36c89c2 +#: ../../en/hardware/display.rst:360 a5f85a36391c45dd8733951eb36c89c2 msgid "``version`` QR code version. Default is 1." msgstr "" -#: ../../en/hardware/display.rst:345 ../../en/hardware/display.rst:407 -#: ../../en/hardware/display.rst:450 201b779c6f2440debdb0af3078ec4356 -#: 32c707e618b442be97f4e897d177347d 8d9af6d08eca41939bd66fcdbbf1ff30 +#: ../../en/hardware/display.rst:362 ../../en/hardware/display.rst:424 +#: ../../en/hardware/display.rst:467 8d9af6d08eca41939bd66fcdbbf1ff30 msgid "**Example**:" msgstr "" -#: ../../en/hardware/display.rst:347 b47c7735d57a492194c428c4976273ad +#: ../../en/hardware/display.rst:364 b47c7735d57a492194c428c4976273ad msgid "Generate and display a QR code with the content \"hello\":" msgstr "" -#: ../../en/hardware/display.rst:355 b975d4c7cd3c49ed9fa4dcf8b05eda2f +#: ../../en/hardware/display.rst:372 b975d4c7cd3c49ed9fa4dcf8b05eda2f msgid "Draw a PNG image." msgstr "" -#: ../../en/hardware/display.rst:357 ../../en/hardware/display.rst:384 -#: ../../en/hardware/display.rst:393 ../../en/hardware/display.rst:402 -#: 79be809b16c842e5a12e3dfa65d07fba b566f3bd7bdb489391bd2f8234113fee -#: dece691fe2d24348926d5ae06cc5585e f090d98913de40ef953695d1c25d66d4 +#: ../../en/hardware/display.rst:374 ../../en/hardware/display.rst:401 +#: ../../en/hardware/display.rst:410 ../../en/hardware/display.rst:419 +#: 79be809b16c842e5a12e3dfa65d07fba f090d98913de40ef953695d1c25d66d4 msgid "``img`` Image file path or opened image data." msgstr "" -#: ../../en/hardware/display.rst:358 ../../en/hardware/display.rst:385 -#: ../../en/hardware/display.rst:394 ../../en/hardware/display.rst:403 -#: ../../en/hardware/display.rst:422 34fb9005f6c94f4a9a6986bf02d6e0fe -#: 4af54c7322d3459c9b7515c00999e1dd 6bb64bba96ee4295aba5c02f1ca322ea -#: 7e07f8e2a6a7424198294038cd83d4ba ef7fc8e0a4a04b8792d9b5876438597a +#: ../../en/hardware/display.rst:375 ../../en/hardware/display.rst:402 +#: ../../en/hardware/display.rst:411 ../../en/hardware/display.rst:420 +#: ../../en/hardware/display.rst:439 4af54c7322d3459c9b7515c00999e1dd +#: 6bb64bba96ee4295aba5c02f1ca322ea msgid "``x, y`` Starting coordinates on the display screen." msgstr "" -#: ../../en/hardware/display.rst:359 ../../en/hardware/display.rst:386 -#: ../../en/hardware/display.rst:395 ../../en/hardware/display.rst:404 -#: 12180cc8126a43b3bb7e118687d46ffc 200ede19b3764eaa813e40adec41a5e3 -#: 822b9e0d360447228fda3427df0fab4d 97c1255d66704a1e9affc7ec29b37412 +#: ../../en/hardware/display.rst:376 ../../en/hardware/display.rst:403 +#: ../../en/hardware/display.rst:412 ../../en/hardware/display.rst:421 +#: 12180cc8126a43b3bb7e118687d46ffc 97c1255d66704a1e9affc7ec29b37412 msgid "``maxW, maxH`` Width and height to be drawn. Draws the full image if ≤0." msgstr "" -#: ../../en/hardware/display.rst:360 ../../en/hardware/display.rst:387 -#: ../../en/hardware/display.rst:396 ../../en/hardware/display.rst:405 +#: ../../en/hardware/display.rst:377 ../../en/hardware/display.rst:404 +#: ../../en/hardware/display.rst:413 ../../en/hardware/display.rst:422 #: 03fd294c6cc44f33ae0da0205d7848c8 39058bccc2c844fc93df956b4ea8dbd6 -#: 43fbb5b38e764d8790d7a7f1b203d560 9addf43e2bbf42b0924faa075a2b351a msgid "``offX, offY`` Offset in the image to start from." msgstr "" -#: ../../en/hardware/display.rst:361 d64fa358355e46edaeda95a3a787fa6e +#: ../../en/hardware/display.rst:378 d64fa358355e46edaeda95a3a787fa6e msgid "``scaleX, scaleY`` Whether to scale the image horizontally or vertically." msgstr "" -#: ../../en/hardware/display.rst:363 28809e09f07441e8a9c910aee3e6768b +#: ../../en/hardware/display.rst:380 28809e09f07441e8a9c910aee3e6768b msgid "**Examples**:" msgstr "" -#: ../../en/hardware/display.rst:365 24e73e088a0f442fbe33bc81c0735e50 +#: ../../en/hardware/display.rst:382 24e73e088a0f442fbe33bc81c0735e50 msgid "Display a PNG image from a specified path:" msgstr "" -#: ../../en/hardware/display.rst:371 eb3d314b190c4bc7accc561e07b0f258 +#: ../../en/hardware/display.rst:388 eb3d314b190c4bc7accc561e07b0f258 msgid "Display a PNG image from read data:" msgstr "" -#: ../../en/hardware/display.rst:382 1e0735b2a7cf461cab5478faf3230470 +#: ../../en/hardware/display.rst:399 1e0735b2a7cf461cab5478faf3230470 msgid "Draw a JPG image." msgstr "" -#: ../../en/hardware/display.rst:391 db765317835c4e5dbbab843ff7ad012d +#: ../../en/hardware/display.rst:408 db765317835c4e5dbbab843ff7ad012d msgid "Draw a BMP image." msgstr "" -#: ../../en/hardware/display.rst:400 53f9191c8b814e5bb1701c8cba9e2601 +#: ../../en/hardware/display.rst:417 53f9191c8b814e5bb1701c8cba9e2601 msgid "Draw an image." msgstr "" -#: ../../en/hardware/display.rst:409 a0194c06cdbd486db9198e2f053f2741 +#: ../../en/hardware/display.rst:426 a0194c06cdbd486db9198e2f053f2741 msgid "Draw an image from the buffer:" msgstr "" -#: ../../en/hardware/display.rst:419 b39f7270fcba47b3a22e9c464ff5f4da +#: ../../en/hardware/display.rst:436 b39f7270fcba47b3a22e9c464ff5f4da msgid "Draw an image from raw buffer data." msgstr "" -#: ../../en/hardware/display.rst:421 c19d5aaa9bd4435894441c67dbc787ed +#: ../../en/hardware/display.rst:438 c19d5aaa9bd4435894441c67dbc787ed msgid "``buf`` Image buffer." msgstr "" -#: ../../en/hardware/display.rst:423 143de434b9be404c9e7180f316f7bfbc +#: ../../en/hardware/display.rst:440 143de434b9be404c9e7180f316f7bfbc msgid "``w, h`` Width and height of the image." msgstr "" -#: ../../en/hardware/display.rst:424 a362c0fbd93549a2a798b68040814f7b +#: ../../en/hardware/display.rst:441 a362c0fbd93549a2a798b68040814f7b msgid "``len`` Length of the image data." msgstr "" -#: ../../en/hardware/display.rst:425 c8a93adb99de4c0ab03be719b94bdc01 +#: ../../en/hardware/display.rst:442 c8a93adb99de4c0ab03be719b94bdc01 msgid "``swap`` Whether to enable inverted display." msgstr "" -#: ../../en/hardware/display.rst:429 4aa65d2a875c483b80361de5066ca48c +#: ../../en/hardware/display.rst:446 4aa65d2a875c483b80361de5066ca48c msgid "Display a string (no formatting support)." msgstr "" -#: ../../en/hardware/display.rst:431 b151e79c0092448cb791bc9fd677b7f9 +#: ../../en/hardware/display.rst:448 b151e79c0092448cb791bc9fd677b7f9 msgid "``text`` Text to display." msgstr "" -#: ../../en/hardware/display.rst:436 6d89be9b5d624e4db14ed4c3dafbe561 +#: ../../en/hardware/display.rst:453 6d89be9b5d624e4db14ed4c3dafbe561 msgid "Display a formatted string." msgstr "" -#: ../../en/hardware/display.rst:438 a734cf86fbd5442ebb437d18d8bf1093 +#: ../../en/hardware/display.rst:455 a734cf86fbd5442ebb437d18d8bf1093 msgid "``text`` Text to display with formatting." msgstr "" -#: ../../en/hardware/display.rst:442 07f9beaeb59240bc9c71894c8925be64 +#: ../../en/hardware/display.rst:459 07f9beaeb59240bc9c71894c8925be64 msgid "Create a canvas." msgstr "" -#: ../../en/hardware/display.rst:444 ef728efc93ab4200b3115d0602a53053 +#: ../../en/hardware/display.rst:461 ef728efc93ab4200b3115d0602a53053 msgid "``w, h`` Width and height of the canvas." msgstr "" -#: ../../en/hardware/display.rst:445 495468b16ce54d23839cddadec74b5eb +#: ../../en/hardware/display.rst:462 495468b16ce54d23839cddadec74b5eb msgid "``bpp`` Color depth. Default is -1." msgstr "" -#: ../../en/hardware/display.rst:446 68c065ae6f58410e87d8154ac17685da +#: ../../en/hardware/display.rst:463 68c065ae6f58410e87d8154ac17685da msgid "``psram`` Whether to use PSRAM. Default is False." msgstr "" -#: ../../en/hardware/display.rst:448 0b5d6b29cce9420da7e1922708fc4d71 +#: ../../en/hardware/display.rst:465 0b5d6b29cce9420da7e1922708fc4d71 msgid "Returns Created canvas object." msgstr "" -#: ../../en/hardware/display.rst:460 b0f6baf4c8e74b5f81460c1d46c5a660 +#: ../../en/hardware/display.rst:477 b0f6baf4c8e74b5f81460c1d46c5a660 msgid "Start writing to the display." msgstr "" -#: ../../en/hardware/display.rst:464 4ddd560d23a94a1fb468c36211b42145 +#: ../../en/hardware/display.rst:481 4ddd560d23a94a1fb468c36211b42145 msgid "End writing to the display." msgstr "" diff --git a/docs/locales/zh_CN/LC_MESSAGES/module/dc_motor.po b/docs/locales/zh_CN/LC_MESSAGES/module/dc_motor.po index 4ffa789d..1051f8c8 100644 --- a/docs/locales/zh_CN/LC_MESSAGES/module/dc_motor.po +++ b/docs/locales/zh_CN/LC_MESSAGES/module/dc_motor.po @@ -9,7 +9,7 @@ msgid "" msgstr "" "Project-Id-Version: UIFlow2 Programming Guide \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-05-15 10:13+0800\n" +"POT-Creation-Date: 2025-05-15 15:53+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language: zh_CN\n" @@ -20,55 +20,58 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.16.0\n" -#: ../../en/module/dc_motor.rst:2 1ca2119f25dd4309815b43ddc27bdcdd +#: ../../en/module/dc_motor.rst:2 ebb43f8043544fc39c9c76c5576f4683 msgid "DCMotor Module" msgstr "" -#: ../../en/module/dc_motor.rst:8 e81bd49d6f9845d7b85c8dbdf082d56e +#: ../../en/module/dc_motor.rst:8 a5adc7dee4aa45a9acec8be86e0aa94e msgid "" "This library is the driver for Module DCMotor, and the module " "communicates via I2C." msgstr "这个库是 Module DCMotor 的驱动,该模块使用 I2C 通信。" -#: ../../en/module/dc_motor.rst:10 c82a3f68ccab4e30bfd8630db272a7a9 +#: ../../en/module/dc_motor.rst:10 32122e2c13204a40a5d71e5ba80b2db1 msgid "Support the following products:" msgstr "支持以下产品:" -#: ../../en/module/dc_motor.rst:12 6285a2bf95ee48f6b9a66fbfd0f1eade +#: ../../en/module/dc_motor.rst:12 645c1390aa7a4e9f86f0486574198d90 msgid "|Module DCMotor|" msgstr "" -#: ../../en/refs/module.dc_motor.ref 11f13b042be9473b8422c409a1947ec0 +#: ../../en/refs/module.dc_motor.ref 571cb040b67440b2a58e24ed143c163c msgid "Module DCMotor" msgstr "" -#: ../../en/module/dc_motor.rst:15 af14860f380643c6b8e984583bfaf036 -msgid "UiFlow2 Example:" +#: ../../en/module/dc_motor.rst:15 8da532073e1d4472b8e077c63028b339 +#, fuzzy +msgid "UiFlow2 Example" msgstr "UiFlow2 应用示例:" #: ../../en/module/dc_motor.rst:18 ../../en/module/dc_motor.rst:37 -#: 52d95ba311144700a040e89950d2a949 6f39ae0d45264f148395235efd256299 +#: 1ff7eea4bf474ef8a9d19b094f9895f5 61923d9f525e4fcaa355c7a0579aaec9 msgid "Speed Control" msgstr "速度控制" -#: ../../en/module/dc_motor.rst:20 1532b132c10842a4b0a938c799a3e093 +#: ../../en/module/dc_motor.rst:20 83dd9c2213ff4b0588c3894fac6812a5 msgid "Open the |cores3_dc_motor_module_speed_control.m5f2| project in UiFlow2." msgstr "在 UiFlow2 上打开 |cores3_dc_motor_module_speed_control.m5f2| 项目。" #: ../../en/module/dc_motor.rst:22 ../../en/module/dc_motor.rst:39 -#: 1f1774b78e474c9b83209d96299b802e 59d3aaae182146ee9962a64ab055d2a0 +#: b27e2c21d2244d58b1fb6bee7c3aaa48 d4056930a8114a9ab298cb46c689fe02 msgid "" "This example demonstrates the use of the DCMotor Module to control the " "speed of a DC motor and display the motor's encoder value in real-time. " "The program automatically adjusts the motor speed, gradually increasing " "or decreasing the speed until it reaches the maximum or minimum value, " "then reverses the direction." -msgstr "这个案例展示使用 DCMotor Module 控制直流电机的转速,并实时显示电机的编码器值。程序自动调整电机的转速,逐步增加或减少速度,直到达到最大或最小值后反转。" +msgstr "" +"这个案例展示使用 DCMotor Module " +"控制直流电机的转速,并实时显示电机的编码器值。程序自动调整电机的转速,逐步增加或减少速度,直到达到最大或最小值后反转。" -#: ../../en/module/dc_motor.rst:25 2943f702c7094a69b3d0373557c9b4f3 -#: 3cb845211c074b349ad639be579d6775 44321ac4fb574d598ac50e3b83095152 -#: 4a84b721a0464e008a87b5ab8bce1795 d0d18307a0954961a3e9f9ed1594861b -#: e09c9a10f8104eec8d573684e9a456d1 module.dc_motor.DCMotorModule:3 +#: ../../en/module/dc_motor.rst:25 244e7818183841cb883e6ccb4d728f6a +#: 6837ab1fbb364541873a103b6d981712 928fd55782c94ae4b696aa65046d505e +#: 99c40eacf6f64abbb534c823011148f6 dde9713a47774435bf29c605ad26833d +#: f3f29e66f6874c4899791781e06f2227 module.dc_motor.DCMotorModule:3 #: module.dc_motor.DCMotorModule.clear_encoder:3 #: module.dc_motor.DCMotorModule.get_encoder:7 #: module.dc_motor.DCMotorModule.set_motor_speed:6 @@ -76,32 +79,33 @@ msgstr "这个案例展示使用 DCMotor Module 控制直流电机的转速, msgid "UiFlow2 Code Block:" msgstr "UiFlow2 代码块:" -#: ../../en/module/dc_motor.rst:27 e1e5ad2450ff447981838e43b63d5bff +#: ../../en/module/dc_motor.rst:27 35e7942a456746429ef271e0b6da3011 msgid "|cores3_dc_motor_module_speed_control.png|" msgstr "" -#: ../../en/refs/module.dc_motor.ref:15 55b4a3e2badd45189fd661f95c09ef2b +#: ../../en/refs/module.dc_motor.ref:15 23d969a0eb4943d897c01cbfb622d752 msgid "cores3_dc_motor_module_speed_control.png" msgstr "" #: ../../en/module/dc_motor.rst:29 ../../en/module/dc_motor.rst:48 -#: 13de64d11d5d4cb9a650fc1fb1edf994 c7d65dd2c29c4452b06b0e86d2b6ce7b +#: 3b852393153f4319b0bf7e6cb5c9e98a d32b6a0086b84293939f5d4e89e96a9c msgid "Example output:" msgstr "示例输出:" #: ../../en/module/dc_motor.rst:31 ../../en/module/dc_motor.rst:50 -#: 26f33ac87dda4450a492d4fddfb13c4b 3e371d7e22564de1a5e55fd98e871529 +#: 09cfa792d713455baaae546ebf456a54 c706b28b16774e46bdedd4838089eaf0 msgid "None" msgstr "无" -#: ../../en/module/dc_motor.rst:34 918155ed1f3c4a528781be32fcb91861 -msgid "MicroPython Example:" -msgstr "" +#: ../../en/module/dc_motor.rst:34 a77c6f7506444733bee119b70a5f7a80 +#, fuzzy +msgid "MicroPython Example" +msgstr "MicroPython 应用示例:" -#: ../../en/module/dc_motor.rst:42 13686e7397434fe7a70dc77687b2b239 -#: 2b3c4bfb8d8f4fda8d0605b850361801 d6bafe43a6a640ea8016b1a9b5de4ef7 -#: dc6f9130d75d4fe59546a3a607742c6b e52fc9d178dc42058c52ec50cff59c51 -#: f3a54b53a84e45aaa66bad04b1d39a38 module.dc_motor.DCMotorModule:7 +#: ../../en/module/dc_motor.rst:42 1b3d2932e7ca4b1489863d6e1b76550b +#: 3d924687477547ceb56dcd4b7c69897a 61fc37bba4a34354afa4aa122f0f49f3 +#: 620f9cbbdf054039873cf5ea5feff5a7 82f22eda3b824ce98703fedcda0527d8 +#: 87a0a8b9447c455690640f0c1947cc0e module.dc_motor.DCMotorModule:7 #: module.dc_motor.DCMotorModule.clear_encoder:7 #: module.dc_motor.DCMotorModule.get_encoder:11 #: module.dc_motor.DCMotorModule.set_motor_speed:10 @@ -109,46 +113,46 @@ msgstr "" msgid "MicroPython Code Block:" msgstr "MicroPython 应用示例:" -#: ../../en/module/dc_motor.rst:53 f42b9c6ab3cc439c839ee292370df1f6 +#: ../../en/module/dc_motor.rst:53 60635f96360741e9ac5b4751ad1c9cdc msgid "**API**" msgstr "API应用" -#: ../../en/module/dc_motor.rst:56 5f25239d589a45aaad5349a823368b5e +#: ../../en/module/dc_motor.rst:56 980d2ee4a01b4818a3bb8ea4cb26be42 msgid "DCMotorModule" msgstr "" -#: 21322b7d0e0543feac9551f79cf6d421 module.dc_motor.DCMotorModule:1 of +#: bc0f297a04834a6eb424d24793df110b module.dc_motor.DCMotorModule:1 of msgid "Bases: :py:class:`object`" msgstr "" -#: 4214c0ee59cd41eca541591d6e4e1b56 module.dc_motor.DCMotorModule:1 of +#: b8f981f3589d487ba1e9fbbe6bac9b76 module.dc_motor.DCMotorModule:1 of msgid "Create an DCMotorModule object." msgstr "创建一个 DCMotorModule 对象。" -#: 52699e5ad1954e23bc5ea011eb713ef1 module.dc_motor.DCMotorModule:5 of +#: e5ae5fbacd89444295c22ab024be6bfb module.dc_motor.DCMotorModule:5 of msgid "|init.png|" msgstr "" -#: ../../en/refs/module.dc_motor.ref:7 9add9e284bd94f58957c905f11d39147 +#: ../../en/refs/module.dc_motor.ref:7 9933b640e6154c7f86a0a0fc6ba4a7f7 msgid "init.png" msgstr "" -#: 264b5f9257544b4688a8aa54c952ef00 +#: 9772a6cc00c841ddb47f522daeeb569d #: module.dc_motor.DCMotorModule.clear_encoder:1 of msgid "Clear encoder value." msgstr "清空编码器计数值" -#: 11e69f0922c642d7a02a47317444d696 +#: 808e3d6c4fa44fd09ee8f7f880a16866 #: module.dc_motor.DCMotorModule.clear_encoder:5 of msgid "|clear_encoder.png|" msgstr "" -#: ../../en/refs/module.dc_motor.ref:11 a39c5320a1c348698a59bf8103198645 +#: ../../en/refs/module.dc_motor.ref:11 801ae2c026134632ac83dfddc36c84be msgid "clear_encoder.png" msgstr "" -#: 2c5543b5e3b84a2aa14b28c960e67035 73d20bae3fd74f60b0cf244cd17aef67 -#: 902bbffc94da43628e32346fe706c8ed ea1e79620ea54a87ae3160aa5989b940 +#: 57901467c0f94694b47f80e120d87671 7ae32051e03c46db9d9a2940b7a5b03b +#: 9490c0f6315042b68553e03afd4ee525 b82639cf52224d40a2d9d93b3ed3af84 #: module.dc_motor.DCMotorModule.clear_encoder #: module.dc_motor.DCMotorModule.get_encoder #: module.dc_motor.DCMotorModule.set_motor_speed @@ -156,8 +160,8 @@ msgstr "" msgid "Parameters" msgstr "" -#: 26fbec0de46f481e8943f387ae9f7308 84941e92e3c34bd99f4a8d9cd3b8cf67 -#: e83b8f75f040487a8c07e7a1a81d540f ff0f2a5c38914b1595dccc82f932a237 +#: 13daacf9664141079b7fdb1ab93acbe1 6359acf562e34273ae63ec66aa47a1ed +#: 9cb8d7901b1c49cdac161d9c1899c8d7 ef1cbfba93f34ce49ece35a9841fb277 #: module.dc_motor.DCMotorModule.clear_encoder #: module.dc_motor.DCMotorModule.get_encoder #: module.dc_motor.DCMotorModule.set_motor_speed @@ -165,76 +169,76 @@ msgstr "" msgid "Return type" msgstr "" -#: 19e93569b76d47dab3187910c3ba2106 module.dc_motor.DCMotorModule.get_encoder:1 +#: d32c74e25c244431b86f248c17a6dacc module.dc_motor.DCMotorModule.get_encoder:1 #: of msgid "Get encoder count." msgstr "获取编码器计数值" -#: 2b2b876814574b99b6d9770147611928 d91ddeb0ab984858b6440ca755e4e589 +#: 67a9fd8ced654071a20c2d75e548cb01 7e863f885c48495395a703bf1d8e733f #: module.dc_motor.DCMotorModule.get_encoder:3 #: module.dc_motor.DCMotorModule.set_motor_speed_percent:3 of msgid "port num, range: 1~4." msgstr "端口号,范围:1~4。" -#: a0f6b2913bf74eee9dd80ee11ff253e2 module.dc_motor.DCMotorModule.get_encoder +#: d642fbac3038481db87878bb171b371c module.dc_motor.DCMotorModule.get_encoder #: of msgid "Returns" msgstr "" -#: dda5a1d5d1cd43808d5fd1dfbfc49a19 module.dc_motor.DCMotorModule.get_encoder:4 +#: b666e3e9066446e190fd5b1c9541faa8 module.dc_motor.DCMotorModule.get_encoder:4 #: of msgid "encoder count." msgstr "编码器计数值。" -#: b598b73d7f524abcb9940231083e75c1 module.dc_motor.DCMotorModule.get_encoder:9 +#: 1c5666a787c649368487ffd99852a173 module.dc_motor.DCMotorModule.get_encoder:9 #: of msgid "|get_encoder.png|" msgstr "" -#: ../../en/refs/module.dc_motor.ref:10 65ea074a7ddc43bab82cbf72bf6c4fd9 +#: ../../en/refs/module.dc_motor.ref:10 73282189900f47e3be659d50c684b962 msgid "get_encoder.png" msgstr "" -#: 37be96c52c7241cd9675f578cd928d39 +#: 0f65d1e5bac8469d89ed87fa6ded8633 #: module.dc_motor.DCMotorModule.set_motor_speed:1 of msgid "Set speed of motor." msgstr "设置电机转速。" -#: e503d2d69518478898c80f76562dc54a +#: c36d9aaf44b74723b7de93f15eab7b1c #: module.dc_motor.DCMotorModule.set_motor_speed:3 of msgid "port num, range: 1~4" msgstr "端口号:范围:1~4。" -#: 6a9f117824d24ad9820c23bfb0c828a5 +#: 34a2103d02ad470babd04c5eafde6ae0 #: module.dc_motor.DCMotorModule.set_motor_speed:4 of msgid "motor speed, range: -255~255" msgstr "电机转速,范围:-255~255。" -#: 5d9370aa842b4a428a4cbf3f96413502 +#: 6e96e77bf2214609b6d03570a473299c #: module.dc_motor.DCMotorModule.set_motor_speed:8 of msgid "|set_motor_speed.png|" msgstr "" -#: ../../en/refs/module.dc_motor.ref:8 88f0cf7a882d4254b6e10acc5b741947 +#: ../../en/refs/module.dc_motor.ref:8 3a7c6ab6f89d430ab1fe3f33cd460a9d msgid "set_motor_speed.png" msgstr "" -#: b824e51187904e9592652cfc4847bd78 +#: 691a9bb4b08545d8a426cf579ceb8206 #: module.dc_motor.DCMotorModule.set_motor_speed_percent:1 of msgid "Set motor speed as a percentage." msgstr "设置电机速度百分比。" -#: beac87dce5cc45fdadac949c53e7ada4 +#: 797e2188cd1649c68484129a117b7e9b #: module.dc_motor.DCMotorModule.set_motor_speed_percent:4 of msgid "motor speed percent, range: -100.0% ~ +100.0%." -msgstr "电机速度百分比,范围:-100.0% ~ +100.0%。 +msgstr "电机速度百分比,范围:-100.0% ~ +100.0%" -#: 820767ad36e34292a92880e081db935f +#: 5a59bacaa83a452aba4e71c1c9ddb37c #: module.dc_motor.DCMotorModule.set_motor_speed_percent:8 of msgid "|set_motor_speed_percent.png|" msgstr "" -#: ../../en/refs/module.dc_motor.ref:9 a2284276fc494c2b86cf93389a27e4be +#: ../../en/refs/module.dc_motor.ref:9 621e3e515f7c4277ab48ec507842ecfc msgid "set_motor_speed_percent.png" msgstr "" @@ -265,3 +269,6 @@ msgstr "" #~ msgid "The example demonstrates" #~ msgstr "" +#~ msgid "MicroPython Example:" +#~ msgstr "" + diff --git a/docs/locales/zh_CN/LC_MESSAGES/module/lora_sx1262.po b/docs/locales/zh_CN/LC_MESSAGES/module/lora_sx1262.po new file mode 100644 index 00000000..a47c31b0 --- /dev/null +++ b/docs/locales/zh_CN/LC_MESSAGES/module/lora_sx1262.po @@ -0,0 +1,465 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2016 - 2025 M5Stack Technology Co., Ltd +# This file is distributed under the same license as the UIFlow2 Programming +# Guide package. +# FIRST AUTHOR , 2025. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: UIFlow2 Programming Guide \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-04-03 10:58+0800\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: zh_CN\n" +"Language-Team: zh_CN \n" +"Plural-Forms: nplurals=1; plural=0;\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.16.0\n" + +#: ../../en/module/lora_sx1262.rst:2 909ab2a439064ba180605c1e7223de89 +msgid "LoRa868 v1.2 Module" +msgstr "" + +#: ../../en/module/lora_sx1262.rst:8 65ebb5aae0ac424e887e01feac13320d +msgid "" +"The LoRa868 v1.2 Module is part of the M5Stack stackable module series. " +"It is a LoRa communication module that operates at a 900MHz frequency and" +" utilizes the SX1262 chip solution." +msgstr "" + +#: ../../en/module/lora_sx1262.rst:10 8f2ff97b70384575b91b08b22a7a8e7e +msgid "Support the following products:" +msgstr "" + +#: ../../en/module/lora_sx1262.rst:12 102f08a9024e4e46a8d181b54984516f +msgid "|LoRa868Module v1.2|" +msgstr "" + +#: ../../en/refs/module.lora_sx1262.ref e4d85310e30b4c85b75e1e73187fdb7f +msgid "LoRa868Module v1.2" +msgstr "" + +#: ../../en/module/lora_sx1262.rst:16 20c3089da354498e99b522894ed02349 +msgid "UiFlow2 Example" +msgstr "" + +#: ../../en/module/lora_sx1262.rst:18 ../../en/module/lora_sx1262.rst:53 +#: 49a15d310a9649d58bd842d195bc51e8 860982a5fe6f42348e9c18ae22ffb114 +msgid "" +"Before using the following examples, please check the DIP switches on the" +" module to ensure that the pins used in the example match the DIP switch " +"positions. For specific configurations, please refer to the product " +"manual page. The SPI configuration has been implemented internally, so " +"users do not need to worry about it." +msgstr "" + +#: ../../en/module/lora_sx1262.rst:21 ../../en/module/lora_sx1262.rst:56 +#: 25f4529af5f646db8109f7bf58c5e380 318eb348bc2f47fdab1e0a2e21b0c3d7 +msgid "Sender" +msgstr "" + +#: ../../en/module/lora_sx1262.rst:23 0d4786701a5b4af59ba71caf6b7b343c +msgid "Open the |cores3_lora_sx1262_tx_example.m5f2| project in UiFlow2." +msgstr "" + +#: ../../en/module/lora_sx1262.rst:25 ../../en/module/lora_sx1262.rst:58 +#: 287d2c1f6b0c41e6a6f9d65a7dbeeecd d9a6f758da7e4b0b95de744c83b666be +msgid "This example sends data every second." +msgstr "" + +#: ../../en/module/lora_sx1262.rst:27 ../../en/module/lora_sx1262.rst:42 +#: ../../en/module/lora_sx1262.rst:130 ../../en/module/lora_sx1262.rst:151 +#: ../../en/module/lora_sx1262.rst:167 ../../en/module/lora_sx1262.rst:189 +#: ../../en/module/lora_sx1262.rst:210 ../../en/module/lora_sx1262.rst:226 +#: ../../en/module/lora_sx1262.rst:242 ../../en/module/lora_sx1262.rst:259 +#: 3d8f430e2718482d9766194b3d7eb5b7 3e3c5dea533a4b27b749a1c9ed5a72f0 +#: 449565b6e23347e08f75fe5fc2f7a536 544e42871cf14b6b810aacc0cd30aaee +#: 6888fc0c73944feb89cc882c2974b814 8ac9d52c20b44c0693886e6d41a0f5e3 +#: c3b1eff99ae04d3f8b3af94b98ab95ed c786196438154e8a9a9428b167a36e15 +#: df0e05658bd04c6598ba316a0edf7acd eff05456e1cc421facb3b74a875595fc +msgid "UiFlow2 Code Block:" +msgstr "" + +#: ../../en/module/lora_sx1262.rst:29 f8076474c14a46a9bc135cc5cd180722 +msgid "|cores3_lora_sx1262_tx_example.png|" +msgstr "" + +#: ../../en/refs/module.lora_sx1262.ref:21 4a0dcbd56d9c429790c8ab236c3fe362 +msgid "cores3_lora_sx1262_tx_example.png" +msgstr "" + +#: ../../en/module/lora_sx1262.rst:31 ../../en/module/lora_sx1262.rst:46 +#: ../../en/module/lora_sx1262.rst:66 ../../en/module/lora_sx1262.rst:81 +#: 0948dcfcabbf436298ff39bc6087c31d 1a296bc3de08431f8c0fb5113fc671c6 +#: 5e34dfad13fd4174afb49dd6b18b83bc e94bd28de62e457bad9c76c076de72f4 +msgid "Example output:" +msgstr "" + +#: ../../en/module/lora_sx1262.rst:33 ../../en/module/lora_sx1262.rst:48 +#: ../../en/module/lora_sx1262.rst:68 ../../en/module/lora_sx1262.rst:83 +#: 5a66fd453e654282b0d4f12ea9f1a649 9cd45426bec944708adf04da58fedabd +#: abe69fedd95d4cd9bc74a9cc8ab3989f cbb4f56ea60c45e49c437c58aaf9c6ab +msgid "None" +msgstr "" + +#: ../../en/module/lora_sx1262.rst:36 ../../en/module/lora_sx1262.rst:71 +#: 80f73e368e3f45fb9c71d6a8815f5e80 88a688570291428cbf28157844ab3a3f +msgid "Receiver" +msgstr "" + +#: ../../en/module/lora_sx1262.rst:38 1e84d3cbe71a44069236ba1903bce9e1 +msgid "Open the |cores3_lora_sx1262_rx_example.m5f2| project in UiFlow2." +msgstr "" + +#: ../../en/module/lora_sx1262.rst:40 ../../en/module/lora_sx1262.rst:73 +#: 4ec17c9b8b774af7a90c9b53e811d78d 790d2371894040d7b3bdc73025419f4b +msgid "This example receives and displays data." +msgstr "" + +#: ../../en/module/lora_sx1262.rst:44 4cc9de12bf00404a980a7a113a681969 +msgid "|cores3_lora_sx1262_rx_example.png|" +msgstr "" + +#: ../../en/refs/module.lora_sx1262.ref:20 16aefbc45bab4da2a0e168c240806a3f +msgid "cores3_lora_sx1262_rx_example.png" +msgstr "" + +#: ../../en/module/lora_sx1262.rst:51 3499850b3efc4a08b7035823681f4817 +msgid "MicroPython Example" +msgstr "" + +#: ../../en/module/lora_sx1262.rst:60 ../../en/module/lora_sx1262.rst:75 +#: ../../en/module/lora_sx1262.rst:134 ../../en/module/lora_sx1262.rst:155 +#: ../../en/module/lora_sx1262.rst:171 ../../en/module/lora_sx1262.rst:193 +#: ../../en/module/lora_sx1262.rst:214 ../../en/module/lora_sx1262.rst:230 +#: ../../en/module/lora_sx1262.rst:246 ../../en/module/lora_sx1262.rst:263 +#: 17883b171c8b4cb39202ae99a60f5c72 35186c67c2734f5d9bff41ecb5f3fbed +#: 402822ec52264abd8c9d570c1acac4be 678ebde175b4436098db80798eb955e0 +#: a584c318918d4205a361a6464c3f1b5c b7c0567c96734d74a8dca609eede5016 +#: c3c221baaa0c4926a1d2268b8de182b6 c5770a2540bb4a7388f37c8731d318f0 +#: db27a69de1774fdcb4f667c964079e47 f7880fc4ea2d4107af4b1a1309ff62d9 +msgid "MicroPython Code Block:" +msgstr "" + +#: ../../en/module/lora_sx1262.rst:86 9356f25aba9b41e49aca0078c5a691af +msgid "**API**" +msgstr "" + +#: ../../en/module/lora_sx1262.rst:90 46b51d3b82b94945ae6010e626f8fbab +msgid "class LoRaSx1262Module" +msgstr "" + +#: ../../en/module/lora_sx1262.rst:104 66bf6def07fe452a990eead3fd99db71 +msgid "Create an LoRaSx1262Module object." +msgstr "" + +#: ../../en/module/lora_sx1262.rst 2ef70a40ba6a4682949f4c8d4c2f9d63 +#: 7fee3bd18a2f48949b01b40f5d7389a9 895fe81d42924c57a584e3d38470d1f9 +#: fcfc2388373a48169dcead2b4f820fa2 +msgid "Parameters" +msgstr "" + +#: ../../en/module/lora_sx1262.rst:106 ef9c70ede4444bffbefb59f69d173b7d +msgid "The Timer ID. Range: 0~3. Default is 0." +msgstr "" + +#: ../../en/module/lora_sx1262.rst:107 ddc74b06162d4cd093a50a8bcdb199ed +msgid "(RST) Reset pin number." +msgstr "" + +#: ../../en/module/lora_sx1262.rst:108 4d7dd9cbc6124caf888518656f5d8578 +msgid "(NSS) Chip select pin number." +msgstr "" + +#: ../../en/module/lora_sx1262.rst:109 3f45bc3bc6d944bb91dccbc64132e044 +msgid "(IRQ) Interrupt pin number." +msgstr "" + +#: ../../en/module/lora_sx1262.rst:110 8258663f9b75401a8fd702dc8c33249d +msgid "(BUSY) Busy pin number." +msgstr "" + +#: ../../en/module/lora_sx1262.rst:111 55528275a37b485da286e32f84a6b905 +msgid "LoRa RF frequency in KHz, with a range of 850000 KHz to 930000 KHz." +msgstr "" + +#: ../../en/module/lora_sx1262.rst:112 ec4539092b5a446cb0815303b99bfde7 +msgid "" +"Bandwidth, options include: - ``\"7.8\"``: 7.8 KHz - ``\"10.4\"``: 10.4 " +"KHz - ``\"15.6\"``: 15.6 KHz - ``\"20.8\"``: 20.8 KHz - ``\"31.25\"``: " +"31.25 KHz - ``\"41.7\"``: 41.7 KHz - ``\"62.5\"``: 62.5 KHz - " +"``\"125\"``: 125 KHz - ``\"250\"``: 250 KHz - ``\"500\"``: 500 KHz" +msgstr "" + +#: ../../en/module/lora_sx1262.rst:112 771ca968cc0a4194931b91aca3091317 +msgid "Bandwidth, options include:" +msgstr "" + +#: ../../en/module/lora_sx1262.rst:114 951a1befa3d143d493396ccadc6bb298 +msgid "``\"7.8\"``: 7.8 KHz" +msgstr "" + +#: ../../en/module/lora_sx1262.rst:115 f039dcbc86f4482fa721b9ee43215168 +msgid "``\"10.4\"``: 10.4 KHz" +msgstr "" + +#: ../../en/module/lora_sx1262.rst:116 c665eb7fa415443fbd98a045aac6cf69 +msgid "``\"15.6\"``: 15.6 KHz" +msgstr "" + +#: ../../en/module/lora_sx1262.rst:117 183ebeaeb203414c9aa4a511980d9a06 +msgid "``\"20.8\"``: 20.8 KHz" +msgstr "" + +#: ../../en/module/lora_sx1262.rst:118 1bb30ade802845d6b6e8e146b6012034 +msgid "``\"31.25\"``: 31.25 KHz" +msgstr "" + +#: ../../en/module/lora_sx1262.rst:119 e29969ee2ff44f27a7640fce4be7e184 +msgid "``\"41.7\"``: 41.7 KHz" +msgstr "" + +#: ../../en/module/lora_sx1262.rst:120 0c25db03f62841ad978b20cc6cd67028 +msgid "``\"62.5\"``: 62.5 KHz" +msgstr "" + +#: ../../en/module/lora_sx1262.rst:121 4ca6f0f82829404195a5ba115648f3ef +msgid "``\"125\"``: 125 KHz" +msgstr "" + +#: ../../en/module/lora_sx1262.rst:122 55f9a62d93c84f808876637565a48a9f +msgid "``\"250\"``: 250 KHz" +msgstr "" + +#: ../../en/module/lora_sx1262.rst:123 17299775c067496a99b66c8c6bc7367f +msgid "``\"500\"``: 500 KHz" +msgstr "" + +#: ../../en/module/lora_sx1262.rst:124 04e14cd97d104f1484750337dbc5fa81 +msgid "" +"Spreading factor, range from 7 to 12. Higher spreading factors allow " +"reception of weaker signals but with slower data rates." +msgstr "" + +#: ../../en/module/lora_sx1262.rst:125 f02f612f3b47436f997f5c10f62ad629 +msgid "" +"Forward Error Correction (FEC) coding rate expressed as 4/N, with a range" +" from 5 to 8." +msgstr "" + +#: ../../en/module/lora_sx1262.rst:126 5f9ee3f81f514890b067941c9c6c313c +msgid "Length of the preamble sequence in symbols, range from 0 to 255." +msgstr "" + +#: ../../en/module/lora_sx1262.rst:127 bda069dab7b445bc96372487e8718fbd +msgid "Sync word to mark the start of the data frame, default is 0x12." +msgstr "" + +#: ../../en/module/lora_sx1262.rst:128 d6dda6a7dc4b42368b6f4e307903fed5 +msgid "Output power in dBm, range from -9 to 22." +msgstr "" + +#: ../../en/module/lora_sx1262.rst:132 4d95734b4ec6495f9576a8bc5e6ab86f +msgid "|init.png|" +msgstr "" + +#: ../../en/refs/module.lora_sx1262.ref:8 3f76cef93d7a430397693b1430a5f8ec +msgid "init.png" +msgstr "" + +#: ../../en/module/lora_sx1262.rst:144 4a9d84909c7240b99e18815870e9cb63 +msgid "Set the interrupt callback function to be executed on IRQ." +msgstr "" + +#: ../../en/module/lora_sx1262.rst:146 8cbb4953b1794a10982e770e72d8fb32 +msgid "" +"The callback function to be invoked when the interrupt is triggered. The " +"callback should not take any arguments and should return nothing." +msgstr "" + +#: ../../en/module/lora_sx1262.rst:149 14c55fcc8b104975a6b1537573226fcd +msgid "Call `start_recv()` to begin receiving data." +msgstr "" + +#: ../../en/module/lora_sx1262.rst:153 2510464b9ce844a1acf0ce914f3db6ca +msgid "|set_irq_callback.png|" +msgstr "" + +#: ../../en/refs/module.lora_sx1262.ref:12 bfd193b2c7424c3c93229f600068c574 +msgid "set_irq_callback.png" +msgstr "" + +#: ../../en/module/lora_sx1262.rst:163 f8d70fce93084bd5982bcaf3f948e76f +msgid "Start receive data." +msgstr "" + +#: ../../en/module/lora_sx1262.rst:165 2a8eb07d42e04b5ab07942c298374146 +msgid "This method initiates the process to begin receiving data." +msgstr "" + +#: ../../en/module/lora_sx1262.rst:169 746dcce8bd2f4ad3a07f555e95899214 +msgid "|start_recv.png|" +msgstr "" + +#: ../../en/refs/module.lora_sx1262.ref:9 7236859b1d644950968fa84820744fe7 +msgid "start_recv.png" +msgstr "" + +#: ../../en/module/lora_sx1262.rst:179 087cceefd5b54e92847d6d8f5227cb7e +msgid "Receive data." +msgstr "" + +#: ../../en/module/lora_sx1262.rst:181 2e173c85673243cd9ca78943d59ee87a +msgid "Timeout in milliseconds (optional). Default is None." +msgstr "" + +#: ../../en/module/lora_sx1262.rst:182 108618e127ce4877abdc5eeaaee6e2ba +msgid "Length of the data to be read. Default is 0xFF." +msgstr "" + +#: ../../en/module/lora_sx1262.rst:183 5835db152cf940b8bdafef8ebd095866 +msgid "An instance of `RxPacket` (optional) to reuse." +msgstr "" + +#: ../../en/module/lora_sx1262.rst 2f082ea9d73342bf9d01bc649cced612 +#: 3671e37a4fb840abb930b3a0f575e680 fab78bfa39304c9089a1c96b35b1f26a +msgid "Returns" +msgstr "" + +#: ../../en/module/lora_sx1262.rst:184 ffd83e5ad2314ba985f711d00c95466f +msgid "Received packet instance" +msgstr "" + +#: ../../en/module/lora_sx1262.rst 996640002eb249c7849340c66a0897c0 +#: a60a39f6f62c4a2fb683de6b88932585 ccd2c0660f0a4f1e973c9f67c6682ac6 +msgid "Return type" +msgstr "" + +#: ../../en/module/lora_sx1262.rst:187 75445498032645bbbc2ae5acfe34617a +msgid "" +"Attempt to receive a LoRa packet. Returns `None` if timeout occurs, or " +"returns the received packet instance." +msgstr "" + +#: ../../en/module/lora_sx1262.rst:191 66e9953d9c4b49e7aba505495b2bd74b +msgid "|recv.png|" +msgstr "" + +#: ../../en/refs/module.lora_sx1262.ref:10 cdc5a492038c49f5aa65096d457a30bb +msgid "recv.png" +msgstr "" + +#: ../../en/module/lora_sx1262.rst:201 a87eb389cb8b415a850cb36d36d97124 +msgid "Send data." +msgstr "" + +#: ../../en/module/lora_sx1262.rst:203 611f0120876d48dfbf8663ce391f83ed +msgid "The data to be sent." +msgstr "" + +#: ../../en/module/lora_sx1262.rst:204 7625691270a6477589142282c1746209 +msgid "" +"The timestamp in milliseconds when to send the data (optional). Default " +"is None." +msgstr "" + +#: ../../en/module/lora_sx1262.rst:205 6b1a15b2822c495ebf3f8663514213b8 +msgid "" +"Returns a timestamp (result of `time.ticks_ms()`) indicating when the " +"data packet was sent." +msgstr "" + +#: ../../en/module/lora_sx1262.rst:208 880f2a8cc6a046a5ab758cd530f3d1c8 +msgid "Send a data packet and return the timestamp after the packet is sent." +msgstr "" + +#: ../../en/module/lora_sx1262.rst:212 164bd9529d2d4376bfaaf4dbccfa5ea8 +msgid "|send.png|" +msgstr "" + +#: ../../en/refs/module.lora_sx1262.ref:15 f5fd52c7cdbf43c48d993591419027ee +msgid "send.png" +msgstr "" + +#: ../../en/module/lora_sx1262.rst:222 320259e97c8e4af1b0e7be18366e0d79 +msgid "Set module to standby mode." +msgstr "" + +#: ../../en/module/lora_sx1262.rst:224 ee61bdde37cb487d9760750402645704 +msgid "Puts the LoRa module into standby mode, consuming less power." +msgstr "" + +#: ../../en/module/lora_sx1262.rst:228 658ba4b4c3864f008296504d952c023a +msgid "|standby.png|" +msgstr "" + +#: ../../en/refs/module.lora_sx1262.ref:17 24857b73396b45a493aa5625014eba33 +msgid "standby.png" +msgstr "" + +#: ../../en/module/lora_sx1262.rst:238 9fccc302890e478c9382c94c920bead2 +msgid "Put the module to sleep mode." +msgstr "" + +#: ../../en/module/lora_sx1262.rst:240 4cdeba7c430b45d08605ddf20cea8009 +msgid "Reduces the power consumption by putting the module into deep sleep mode." +msgstr "" + +#: ../../en/module/lora_sx1262.rst:244 4c51d457314f41c09d16ff3a4050f23a +msgid "|sleep.png|" +msgstr "" + +#: ../../en/refs/module.lora_sx1262.ref:18 5b189b5a0aab4db487d193d7b83cba24 +msgid "sleep.png" +msgstr "" + +#: ../../en/module/lora_sx1262.rst:254 b71c7198ef074f2da3b81c7446e756ed +msgid "Check IRQ trigger." +msgstr "" + +#: ../../en/module/lora_sx1262.rst:256 67e86b8b2c76433589535f189b48635c +msgid "" +"Returns `True` if an interrupt service routine (ISR) has been triggered " +"since the last send or receive started." +msgstr "" + +#: ../../en/module/lora_sx1262.rst:261 0a2d1720820049db963a1bcfbf751462 +msgid "|irq_triggered.png|" +msgstr "" + +#: ../../en/refs/module.lora_sx1262.ref:16 5447b337e0e04df8bb03639b7dd9c1f6 +msgid "irq_triggered.png" +msgstr "" + +#: ../../en/module/lora_sx1262.rst:270 e862112210f240629c3da7254ffc12de +msgid "class RxPacket" +msgstr "" + +#: ../../en/module/lora_sx1262.rst:274 3da775ff990241f881dff2f4b136f85c +msgid "Create an RxPacket object." +msgstr "" + +#: ../../en/module/lora_sx1262.rst:278 da50f30d65f7412f84c832ecee629e4e +msgid "Decode the received data." +msgstr "" + +#: ../../en/module/lora_sx1262.rst:282 fa7755b2ee9e46bd9f5b13bf9d74f9bc +msgid "Timestamp of when the data was received." +msgstr "" + +#: ../../en/module/lora_sx1262.rst:286 6c084d10a6914703abf0072d0a11e57f +msgid "Received signal strength (units: dBm)." +msgstr "" + +#: ../../en/module/lora_sx1262.rst:290 70986fea0a744403a5cf1dea17948f49 +msgid "Signal-to-noise ratio (units: dB * 4)." +msgstr "" + +#: ../../en/module/lora_sx1262.rst:294 51abcfa2a78c4d7f9f310d08f6af4c7f +msgid "CRC validity check." +msgstr "" + diff --git a/docs/locales/zh_CN/LC_MESSAGES/units/hbridge.po b/docs/locales/zh_CN/LC_MESSAGES/units/hbridge.po index 437d7228..c23d0aee 100644 --- a/docs/locales/zh_CN/LC_MESSAGES/units/hbridge.po +++ b/docs/locales/zh_CN/LC_MESSAGES/units/hbridge.po @@ -9,7 +9,7 @@ msgid "" msgstr "" "Project-Id-Version: UIFlow2 Programming Guide \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-04-18 16:50+0800\n" +"POT-Creation-Date: 2025-05-15 15:53+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language: zh_CN\n" @@ -76,7 +76,7 @@ msgstr "示例程序实现电机转速控制及正反转方向的切换功能。 #: 9f7d8a8d9bee436eb95afaa176beb9c5 ad04c3763ee14119800bbeb3dce6337a #: b636dde0e465481b941909e647ed0ecb bd610dadc37644c3872af9b4fb93df08 #: d02bc449d38748f7b87e851f85e3779a de5729e113f34c46a1592263db131725 of -#: unit.hbridge.HbridgeUnit:7 unit.hbridge.HbridgeUnit.get_adc_value:15 +#: unit.hbridge.HbridgeUnit:8 unit.hbridge.HbridgeUnit.get_adc_value:15 #: unit.hbridge.HbridgeUnit.get_device_status:7 #: unit.hbridge.HbridgeUnit.get_driver_config:8 #: unit.hbridge.HbridgeUnit.get_vin_current:6 @@ -116,7 +116,7 @@ msgstr "MicroPython 应用示例" #: 89a4e9844380457ab00a21a426a898fe 906798839f534fce9cab412f3e882420 #: 98252dd94fc7410d8b8af34ca1d29a21 b48b032892264348874aa6dd566676d3 #: e68fc6b8a5c64ed6a7d40a99764502d3 eb875fa259b746ffa0e96bdaa60219a7 of -#: unit.hbridge.HbridgeUnit:11 unit.hbridge.HbridgeUnit.get_adc_value:19 +#: unit.hbridge.HbridgeUnit:12 unit.hbridge.HbridgeUnit.get_adc_value:19 #: unit.hbridge.HbridgeUnit.get_device_status:11 #: unit.hbridge.HbridgeUnit.get_driver_config:12 #: unit.hbridge.HbridgeUnit.get_vin_current:10 @@ -133,7 +133,7 @@ msgid "**API**" msgstr "API应用" #: ../../en/units/hbridge.rst:61 1ef42aa95ae34cf796aacf244856359c -msgid "HbridgeRUnit" +msgid "HbridgeUnit" msgstr "" #: 58a190f6e7ae491b96edd7f1a225fb75 of unit.hbridge.HbridgeUnit:1 @@ -167,7 +167,7 @@ msgstr "I2C 端口。" msgid "HbridgeUnit Slave Address." msgstr "HbridgeUnit 从机地址。" -#: a50c1be831df44b3a701bc35f1003162 of unit.hbridge.HbridgeUnit:9 +#: a50c1be831df44b3a701bc35f1003162 of unit.hbridge.HbridgeUnit:10 msgid "|init.png|" msgstr "" @@ -394,3 +394,6 @@ msgstr "" #~ msgid "Receive data" #~ msgstr "" +#~ msgid "HbridgeRUnit" +#~ msgstr "" + diff --git a/docs/locales/zh_CN/LC_MESSAGES/units/ncir2.po b/docs/locales/zh_CN/LC_MESSAGES/units/ncir2.po index 4e4a2d20..0a32509b 100644 --- a/docs/locales/zh_CN/LC_MESSAGES/units/ncir2.po +++ b/docs/locales/zh_CN/LC_MESSAGES/units/ncir2.po @@ -9,7 +9,7 @@ msgid "" msgstr "" "Project-Id-Version: UIFlow2 Programming Guide \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-05-13 22:42+0800\n" +"POT-Creation-Date: 2025-05-15 15:53+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language: zh_CN\n" @@ -20,41 +20,42 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.16.0\n" -#: ../../en/units/ncir2.rst:2 8d7afa3735c64435acb13e4b3958550a +#: ../../en/units/ncir2.rst:2 662de0c636614e7c8d950fcdbc4307e7 msgid "NCIR2 Unit" msgstr "" -#: ../../en/units/ncir2.rst:8 78926b34793548399b8468a0b5365522 +#: ../../en/units/ncir2.rst:8 3b2032d21eb14ef7a479b2a3c63a9cf4 msgid "This library is the driver for Unit NCIR2." msgstr "这个库是 Unit NCIR2 的驱动" -#: ../../en/units/ncir2.rst:10 2efd0bc4139444c48a97da954e90365c +#: ../../en/units/ncir2.rst:10 62d80afc3e494b90bd3eb435cd57abb0 msgid "Support the following products:" msgstr "支持以下产品:" -#: ../../en/units/ncir2.rst:12 3d91262fde7b4f2e98f2f1e8233d5f70 +#: ../../en/units/ncir2.rst:12 955e9c4555ec4eeeb635f2e5d827346d msgid "|Unit NCIR2|" msgstr "" -#: ../../en/refs/unit.ncir2.ref 5b54971100864b2cb26899c67d6db90a +#: ../../en/refs/unit.ncir2.ref 166d71ce3a934672b606b4c2c55dd209 msgid "Unit NCIR2" msgstr "" -#: ../../en/units/ncir2.rst:15 e2be2d1be5d54d71b1a6a15504c7bfdf -msgid "UiFlow2 Example:" +#: ../../en/units/ncir2.rst:15 a64396b9901c45f2837b5324d52d4694 +#, fuzzy +msgid "UiFlow2 Example" msgstr "UiFlow2 应用示例:" #: ../../en/units/ncir2.rst:18 ../../en/units/ncir2.rst:36 -#: c4e9a3ccfaaa49a1bff4d7655f63fe86 +#: 775b39a95d794a2c85fcd12c4e384dcf e1c608150c2c44f88e87193bb0035472 msgid "Infrared Temperature Display" msgstr "红外温度测量显示" -#: ../../en/units/ncir2.rst:20 26a6aa05ee1141c3b8b0f4a0795d5c83 +#: ../../en/units/ncir2.rst:20 6261eecd67044ea884b5d45b2d3e265a msgid "Open the |m5cores3_ncir2_base_example.m5f2| project in UiFlow2." msgstr "在 UiFlow2 上打开 |m5cores3_ncir2_base_example.m5f2| 项目。" #: ../../en/units/ncir2.rst:22 ../../en/units/ncir2.rst:38 -#: 51f30e834e3a472aadad8eae4aa2d4dc +#: 10a1aa50ee394014845d3b3e52cf8896 33945e782c4c42c6ad610acc73e41af8 msgid "" "This example uses the M5Stack CoreS3 board with the NCIR2 infrared " "temperature sensor to measure temperature in real time and display the " @@ -76,30 +77,44 @@ msgstr "本案例基于 M5Stack CoreS3,使用 NCIR2 红外测温模块实现 #: ../../en/units/ncir2.rst:431 ../../en/units/ncir2.rst:449 #: ../../en/units/ncir2.rst:463 ../../en/units/ncir2.rst:480 #: ../../en/units/ncir2.rst:497 ../../en/units/ncir2.rst:513 -#: c638c51692184a69931afbb764f26c7c +#: 122ed5fe60734388b88ae0177773f876 1317ed5f416247e1ab683070eb0406e0 +#: 17657ccc962a4475892df761453c8862 1848c0d8c08f43c39de74862b27b6bf5 +#: 1bea41c4682e4d2a9b819fc2e720e9f9 21bd20f58ee741b88c4cef8b91142587 +#: 28d221c9186f4adbbb4df84e8b13cdd4 2cb09f16d178452ba71ceed888d341b5 +#: 3ccee873a6564d929100fc2d0dfd4788 4ec21144746b4482b3294a4370b832da +#: 54e7b08454b34250a16fa95e5d644019 6297423bfcdd45c680e7ff917723de09 +#: 6b31987ec8aa418ead6f37fcd8c70da9 71b653d37341444ab5a2c906b5103d26 +#: 79fdac55d23a463f9c621863e1bce0db 7a723bbd0e5c469da244251f43c51a45 +#: 7ed3d694f1144ef1ba25bbee4671ae26 8b86b4950b6d46a2956f42c50a95e335 +#: 982323966ebb4303bb790ccbcc209042 9de229560e324e57b63d97bf76c003c5 +#: a20c79ed3a8341f1bfe447b93b73c38b a468a92f4f26494c99f2b3cde345b5cc +#: c32f8a7f44b44885a2aec4d1d481bf48 c5fdfe0d4cda4c689a7f600d6f16d27a +#: c9ad665055144a1cb3fb9ada802c616e d83c87797363431aaef5ecfb24a6dc35 +#: dd52649fddbc4c4496c4fbe9f699d5ae f0848e91d5444ab68d2a65646d8774c6 msgid "UiFlow2 Code Block:" msgstr "UiFlow2 代码块:" -#: ../../en/units/ncir2.rst:26 b80a9bd331f047bcad6d73f4f32e2c14 +#: ../../en/units/ncir2.rst:26 e2d486089e7548d38cca1c025d49f2d2 msgid "|m5cores3_ncir2_base_example.png|" msgstr "" -#: ../../en/refs/unit.ncir2.ref:35 ec4f41b75ca24aaea2b9722a6d38f980 +#: ../../en/refs/unit.ncir2.ref:35 a4ede9657d534cafa6dc8eaeb88adaf4 msgid "m5cores3_ncir2_base_example.png" msgstr "" #: ../../en/units/ncir2.rst:28 ../../en/units/ncir2.rst:46 -#: db8886227aa14de08562ca836fd250ec +#: bca0e6549fa440ad87d2a5e2845fa39c f6bb838a3fdf467c8b61046312d4da40 msgid "Example output:" msgstr "示例输出:" #: ../../en/units/ncir2.rst:30 ../../en/units/ncir2.rst:48 -#: 0c1e746e370342ed8b45aa94525413ff +#: c432f23f34944db1b92e44da4976a85e f892382cbf1f481cb7eb1518742e96e4 msgid "None" msgstr "无" -#: ../../en/units/ncir2.rst:33 e5ea8f4c88be4f5dbe201aeb5cf158c7 -msgid "MicroPython Example:" +#: ../../en/units/ncir2.rst:33 0a292434273847de843bd196ec686e78 +#, fuzzy +msgid "MicroPython Example" msgstr "MicroPython 应用示例:" #: ../../en/units/ncir2.rst:40 ../../en/units/ncir2.rst:68 @@ -116,163 +131,197 @@ msgstr "MicroPython 应用示例:" #: ../../en/units/ncir2.rst:435 ../../en/units/ncir2.rst:453 #: ../../en/units/ncir2.rst:467 ../../en/units/ncir2.rst:484 #: ../../en/units/ncir2.rst:501 ../../en/units/ncir2.rst:517 -#: cd71690a9aad4762985495c1dfd9192d +#: 022c5936227846a4a579764fd9253814 0296dd98f1e94bdf8305c2a5a7d6e420 +#: 06959cfab87044d4993e60fb951181cd 06f9dedb4848448bbf82ee8eb1a676b3 +#: 1bd8cf9a476b401b88797181b342dce0 1fd68226f4584deab5cc45b5c0c46172 +#: 2e4412fd4f90453e9f161a91b2dce994 388c6c06bbfb4e26b55ca60cbe7bd800 +#: 3c7d085ee24b4b459ff503343635ec93 3d367da6b4ce4342be73884e3b9407ea +#: 4e727da3233241e89168b6510acb9887 4fb73316ed08432c9bdd2d8e03140f79 +#: 5a6f2980d9ee42c6ab320e17ea9e78bf 9220939173fd4ff98749760c033a093d +#: a4af0934079044a783d8931617cfb51e af165864abed49d7acc41053de9821d5 +#: b1b0d6dd8f9244fc8cc5e5afe79ce5db b6bf5d486fdc4087a37960e28b2f5dab +#: b9ed4cdbd4fc441d8ee948e073e46918 b9f35711c1424ec0903ca0cccc9291dc +#: c42582bfeeee41878d2ba97469e2328f cdf57e100eea48ebb2a2d22bc1c42970 +#: cdfc737144504a0a82bf434e4885352c e246c23dbfb342fd86fa992d361d0038 +#: ea9e6f8940734d2b80b3c89eef0b9774 ef3c0d5fb8fc46e288b2b79cf97081a0 +#: f7252f627dec4c7c97dd82cd1880d381 ff244ec0cad8461bb957fc37bcb52686 msgid "MicroPython Code Block:" msgstr "MicroPython 代码块:" -#: ../../en/units/ncir2.rst:52 d82b02a79d514efeb5a7aef85d18f8d1 +#: ../../en/units/ncir2.rst:52 75d52b0c35c6439da77e0b5418068d34 msgid "**API**" msgstr "API应用" -#: ../../en/units/ncir2.rst:55 eff7c7b727c748c98006c360f3d0566f +#: ../../en/units/ncir2.rst:55 d513510e12fa49cb82e4e22b4ff2d9f5 msgid "NCIR2Unit" msgstr "" -#: ../../en/units/ncir2.rst:59 e4c524e38a26412c84bfa06fd0928521 +#: ../../en/units/ncir2.rst:59 7f86deb1469540888171a3d4fd712bbf msgid "Create an NCIR2Unit object." msgstr "创建一个 NCIR2Unit 对象。" -#: ../../en/units/ncir2.rst 6977b2e4bab24419a4ddc336aa3b0610 +#: ../../en/units/ncir2.rst 00bec99432cf40d4b04e846e2402d6de +#: 3f96301b6e8a4fbeaa7d48d3e9589fd5 6d80b7713e7e46b18d545d3f532026bd +#: bd153097feed48429f68ef492a8a0f82 msgid "Parameters" msgstr "" -#: ../../en/units/ncir2.rst:61 1f2fcd12d8194435bb002a8c770b8e21 +#: ../../en/units/ncir2.rst:61 259da2bcf35a46e3883f83c37a5799fe msgid "I2C port," msgstr "I2C 端口" -#: ../../en/units/ncir2.rst:62 11ed80d9b6ca417b842d08a7ed249248 +#: ../../en/units/ncir2.rst:62 dd48cbcc60d041f3b0e72a710e8976af msgid "NCIR2Unit Slave Address, Default is 0x5A." msgstr "NCIR2Unit 从机地址,默认为 0x5A。" -#: ../../en/units/ncir2.rst:66 6ed9f8df84c24de9ba3018ecfc2f6cc9 +#: ../../en/units/ncir2.rst:66 7b7d22ee4a804a0a99cf899781c8a1ce msgid "|init.png|" msgstr "" -#: ../../en/refs/unit.ncir2.ref:7 b5cab2e151364e248aa639b8a29cb8c1 +#: ../../en/refs/unit.ncir2.ref:7 b1975870e36e404fba9ca78e89638266 msgid "init.png" msgstr "" -#: ../../en/units/ncir2.rst:78 35e6fc42486b4d80856ab77641b91297 +#: ../../en/units/ncir2.rst:78 df97ff240bd14dfbab61b51c3bb4dcda msgid "Get object temperature." msgstr "获取物体温度。" -#: ../../en/units/ncir2.rst bc6f7b83c8ff415388064aee9c8337b3 +#: ../../en/units/ncir2.rst 9d2f4523e8874122805fca8b38d02fa6 msgid "returns" msgstr "" -#: ../../en/units/ncir2.rst:80 2816b7b046ff41d090584eaaf53c34ff +#: ../../en/units/ncir2.rst:80 5fdcc6cc283f40db8017233ed696f0cb msgid "object temperature(unit: ℃)" msgstr "物体温度(单位:℃)。" -#: ../../en/units/ncir2.rst ea92a107b13b4a9c84705de78511a411 +#: ../../en/units/ncir2.rst 2bfaf01a76224f92ad74e59b8b1bd8b0 msgid "rtype" msgstr "" -#: ../../en/units/ncir2.rst:81 47c1898464eb4d12837b95fb70026a9e +#: ../../en/units/ncir2.rst:81 d84d7ed712d94d028d5f83adc795e1da msgid "float" msgstr "" -#: ../../en/units/ncir2.rst:85 2b9c539bae304b2bbd4edffc93fd49e1 +#: ../../en/units/ncir2.rst:85 0f4f376964494c2cb174365032732557 msgid "|get_temperature_value.png|" msgstr "" -#: ../../en/refs/unit.ncir2.ref:8 75ab18039be0489aac5670377d63e882 +#: ../../en/refs/unit.ncir2.ref:8 9bc1136dfaf64088a27dea1c1faaf992 msgid "get_temperature_value.png" msgstr "" -#: ../../en/units/ncir2.rst:95 841b3e9ac2fa43f49e50dbdbaf5f2ce3 +#: ../../en/units/ncir2.rst:95 60fc3cb06fc14074846da571e2f5226c msgid "Get current emissivity." msgstr "获取当前发射率。" -#: ../../en/units/ncir2.rst 87d473233ba548d7af6139be701f00bb +#: ../../en/units/ncir2.rst 01cd9a23a95341ce863d9d1a183fa6a3 +#: 146eadcf9d71405e8a8b51c8882692e9 31af6cb813cb42a8be96153902329510 +#: 5c8181df749c4cbc84471088c838ff73 654f7a2cd3264b77a7bbc3f45f78ee80 +#: 67502240a0db4fa3b479e96568d1919d 697848117c4d40f8a8265b383dd24b62 +#: 89bc5f65b10f4e1fba0eccc4a4adc9cd 8dd1485de7084c8d9b2cad20cf1aeff5 +#: a02ad0c24b6e4cd7b1ac4f2b82f2200d a17509f6da9a4fa093e2017e38987f40 +#: cb6e6bb9cdd94cf082e832cd5ee886bf fdd82ed8922841e698e53c4bcba79054 msgid "Returns" msgstr "" -#: ../../en/units/ncir2.rst:97 59dba6b3dc8b4cdaa957a1a0ea493f5e +#: ../../en/units/ncir2.rst:97 267f44dc1cde4f848ceadc57c864471d msgid "emissivity." msgstr "发射率" -#: ../../en/units/ncir2.rst 3afaa2a91c8848f28082e25477cea736 +#: ../../en/units/ncir2.rst 0019d6145fb946598f0e223d3d8c6d2f +#: 037f5f3d657d45688ffc086af4857297 1940cf434fbe41fc9fffd4c909527a60 +#: 27563fa3cc1b4b839fecc9922474d281 2893df1c29df4da6b698aed85dbae3ff +#: 28bbe2be8ef746ba86212778de94b098 29ee383b6b26462c8230409470b08d3e +#: 4d0eaefb99e14ab58534c6ca140dd3f8 6835dfba7ffb4ce0926837daceec44cf +#: 71d93ddd56b045f8a37c0573ba2240b2 cdb1cff0a55c4a66a0e6f8962c5ef53c +#: dd2842682fb44b1a99c02009db9ab8b2 fd4a5b311e6642d69b8173614c68ca62 msgid "Return type" msgstr "" -#: ../../en/units/ncir2.rst:102 1db28b0af4fe4968b660808e27127751 +#: ../../en/units/ncir2.rst:102 61391b5e10a74871abea5a405e034d30 msgid "|get_emissivity_value.png|" msgstr "" -#: ../../en/refs/unit.ncir2.ref:9 21151727346348909f9bfafa61d7c8df +#: ../../en/refs/unit.ncir2.ref:9 a4ec2952e19c401c90acf07b95be5ffc msgid "get_emissivity_value.png" msgstr "" -#: ../../en/units/ncir2.rst:112 7ef1ff25dc644ce299023bdee5f725e0 +#: ../../en/units/ncir2.rst:112 a888cba965984dcca30c475e72cb37ff msgid "Set the emissivity." msgstr "设置发射率。" -#: ../../en/units/ncir2.rst:114 84f80d41495f4f22b131013bdb1a70dc +#: ../../en/units/ncir2.rst:114 76df530e7c1844dea2b1c9b76e777b0c msgid "" "According to the material being measured; it affects temperature " "measurement accuracy." msgstr "根据被测材料的不同,会影响温度测量的准确性。" -#: ../../en/units/ncir2.rst:116 c3f329d900b3473486fbb5ff15e338fa +#: ../../en/units/ncir2.rst:116 f363b7112c6e463386a41ec152acf2b3 msgid "A black body has an emissivity of 1.00 (ideal emitter)." msgstr "黑体的发射率为 1.00(理想的辐射体)。" -#: ../../en/units/ncir2.rst:117 5ba2f8b877914dbe84db259151c84e64 +#: ../../en/units/ncir2.rst:117 74e9c57631cd4ec8b0a9ea93f5e28796 msgid "Shiny metals often have low emissivity values (below 0.1)." msgstr "光亮金属通常具有较低的发射率值(低于 0.1)。" -#: ../../en/units/ncir2.rst:118 f773f252f32a4e0c8ee8f3168c7da2c4 +#: ../../en/units/ncir2.rst:118 5b5f7e80454f4028af9e1d235aa06e8e msgid "" "Dark, rough surfaces like electrical tape or human skin typically have " "high emissivity (above 0.95)." msgstr "像电工胶带或人体皮肤这样的黑暗、粗糙表面通常具有较高的发射率(高于 0.95)。" -#: ../../en/units/ncir2.rst 2a0f5094bc0343958db838679d9a0dc3 -#: 2b64f19c6fe94a47b1d8c152f973ce69 85c922de58e5418eae24a84c2960d519 -#: bb050cf16dea426a82666a4df5af706d +#: ../../en/units/ncir2.rst 0c697f0bdda24430b277474ee0dccaac +#: 0decfe0b98c547e393a76da035f7d30d 193150ee68044de8979916ea61f3abec +#: 29bf0d597ecd4f8581c5bc968d6265d2 2a53da2cb73140c19ae5fd2fb6b73df8 +#: 3f3fa37e7ae04be5acccac8ee3db84de 44c14df576034929afe5f0eb309e91ef +#: 4bbb687227784378b5bdf53e4970a605 582d660c1f9a4263800755778550a39a +#: bdbfdd41aa8c4e7e8c7a40f5a142db9f c32eb610aafc42829c76422211169c72 +#: c6d98d5e1282428bbe44b81ea73ae442 d5e138b05b8944d4aa737be602fa8f9b +#: de00516af2724fec8d665a73d4a43d1d ebcfc74e426c4ecfbde64c94431bd9c6 +#: f920a3db468c4adf9f75139146842cef f9c8cd97247045a0bd91e901fde8d659 +#: fd45a0dc37d746fd8a0914f519727d53 msgid "param" msgstr "" -#: ../../en/units/ncir2.rst:120 c44dc3529f88482dab7cb240a048ae99 +#: ../../en/units/ncir2.rst:120 149d49bb36da4fb1b77e46e396b188f8 msgid "int emissive: The emissivity, range: 0 ~ 1." msgstr "发射率:范围:0 ~ 1。" -#: ../../en/units/ncir2.rst:124 543431e83a48416b9906cdcc6c3a56bf +#: ../../en/units/ncir2.rst:124 695906281362490ca2cefb7372233230 msgid "|set_emissivity_value.png|" msgstr "" -#: ../../en/refs/unit.ncir2.ref:22 91b0e0c8b2404ce987909d94d19e2550 +#: ../../en/refs/unit.ncir2.ref:22 33f196cc1aee4a9cbede535e112756bd msgid "set_emissivity_value.png" msgstr "" -#: ../../en/units/ncir2.rst:134 5ab229c71ff54a5ba2dd59ae35c7b306 +#: ../../en/units/ncir2.rst:134 27351baf87da432f9d71b45ec90b32d7 msgid "Get temperature alaram threshold." msgstr "获取温度报警阈值。" -#: ../../en/units/ncir2.rst:136 c5c1bdd0dfef4cc88504b6893dedbdab +#: ../../en/units/ncir2.rst:136 e26d620c3fbe4dada0d37faea2e42f22 msgid "" "int alarm_reg: ALARM_LOW_TEMP_REG: Low temperature alarm threshold " "register, ALARM_HIGH_TEMP_REG: High temperature alarm threshold register." msgstr "int alarm_reg: ALARM_LOW_TEMP_REG:低温报警阈值寄存器,ALARM_HIGH_TEMP_REG:高温报警阈值寄存器。" -#: ../../en/units/ncir2.rst:137 0ec4475be47842209af0fafa673e8130 +#: ../../en/units/ncir2.rst:137 910c7f692152438ebf857d95c9594fd8 msgid "alarm threshold." msgstr "报警阈值。" -#: ../../en/units/ncir2.rst:142 6a8793a5eabd452ea88b456e300d9817 +#: ../../en/units/ncir2.rst:142 da7abf5fec794d468a33479c0a96193a msgid "|get_temperature_threshold.png|" msgstr "" -#: ../../en/refs/unit.ncir2.ref:10 1131c845af7f40e2bed938a18b0efa0f +#: ../../en/refs/unit.ncir2.ref:10 fd43734e99d3477fb67e0653e7c1a3cb msgid "get_temperature_threshold.png" msgstr "" -#: ../../en/units/ncir2.rst:152 430479bf597b476d909580cfc40f16b0 +#: ../../en/units/ncir2.rst:152 ac7044a7c633436cab8c0a1c658f131f msgid "Set temperature alarm threshold." msgstr "设置温度报警阈值。" -#: ../../en/units/ncir2.rst:154 a388c342516a4d2384b68ab150d3bc29 +#: ../../en/units/ncir2.rst:154 ff07390cd27c4fe6ade58b87c13784bb msgid "" "int alarm_reg: temperature alarm register, ALARM_LOW_TEMP_REG: Low " "temperature alarm threshold register, ALARM_HIGH_TEMP_REG: High " @@ -281,24 +330,24 @@ msgstr "" "int alarm_reg: " "温度报警寄存器,ALARM_LOW_TEMP_REG:低温报警阈值寄存器,ALARM_HIGH_TEMP_REG:高温报警阈值寄存器。" -#: ../../en/units/ncir2.rst:155 116580089d614bd0961255eb1ab39d25 +#: ../../en/units/ncir2.rst:155 2d4243109f014f8195583b18e385d92d msgid "float temp: alarm threshold." msgstr "float temp: 报警阈值。" -#: ../../en/units/ncir2.rst:159 2856bc6a8c53414684980270fcc1bc06 +#: ../../en/units/ncir2.rst:159 3e32720125c34c6998b96ef93edcae5d msgid "|set_temperature_threshold.png|" msgstr "" -#: ../../en/refs/unit.ncir2.ref:23 4d1b9314e9144ee8a8cdbf4f76d94f24 +#: ../../en/refs/unit.ncir2.ref:23 da535f1ce95c44c5ba982dc0723c7f8f msgid "set_temperature_threshold.png" msgstr "" -#: ../../en/units/ncir2.rst:169 f4a43f3f31c34642a0ac2d8501560624 +#: ../../en/units/ncir2.rst:169 3a0cb8657a34485f804648454bd91ade msgid "Get temperature alaram RGB LED value." msgstr "获取温度报警 RGB LED 颜色值。" #: ../../en/units/ncir2.rst:171 ../../en/units/ncir2.rst:189 -#: ea9409aef6c548408676d1aa9cbc72f2 +#: 2b65fb94c44c47ae9b3a1bec84913f49 65aa5a1154e74141a7fbd7302b6771e7 msgid "" "int alarm_reg: temperature alarm RGB LED register, RGB_LOW_TEMP_REG: Low " "temperature alarm RGB LED value register, RGB_HIGH_TEMP_REG: High " @@ -307,40 +356,40 @@ msgstr "" "int alarm_reg: 温度报警 RGB LED 寄存器,RGB_LOW_TEMP_REG: 低温报警 RGB LED 寄存器,高温报警 " "RGB LED 寄存器。" -#: ../../en/units/ncir2.rst:172 6b2a0cd27c8f4ff08cb09865b1c0e7f0 +#: ../../en/units/ncir2.rst:172 f26c5f71d7034d52b30b37a2bdfd0fc0 msgid "temperature alarm RGB LED value." msgstr "温度报警 RGB LED 值。" -#: ../../en/units/ncir2.rst:177 f54f836beb604c33b143bd824ee3a6f4 +#: ../../en/units/ncir2.rst:177 31f1fa479d8847d3b7992a809f4efcd3 msgid "|get_temp_alarm_led.png|" msgstr "" -#: ../../en/refs/unit.ncir2.ref:11 05148b4cc92741cda245ed2d6b627465 +#: ../../en/refs/unit.ncir2.ref:11 ec3ec9d2531341eba657c2a99ea22f08 msgid "get_temp_alarm_led.png" msgstr "" -#: ../../en/units/ncir2.rst:187 1163347d0dda40f799fccde642502045 +#: ../../en/units/ncir2.rst:187 a4eda7bfc8fb4d92be7712f213642636 msgid "Set temperature alaram RGB LED value." msgstr "设置温度报警 RGB LED 颜色值。" -#: ../../en/units/ncir2.rst:190 2fb3d10a670047c5b066ff114b42fd61 +#: ../../en/units/ncir2.rst:190 c6c47db0df484f419942978ba99ca9a5 msgid "RGB color value (24-bit, range: 0 ~ 0xFFFFFF)." msgstr "RG 颜色值(24位,范围:0~0xFFFFFF)。" -#: ../../en/units/ncir2.rst:194 c009a4daa9bb4e35a5aa2e3e7ad4df3f +#: ../../en/units/ncir2.rst:194 b8cdbdd72bc341f7a47714b8bdb8f8e0 msgid "|set_temp_alarm_led.png|" msgstr "" -#: ../../en/refs/unit.ncir2.ref:24 058e9afe045e484284e8f6b5e8784581 +#: ../../en/refs/unit.ncir2.ref:24 397a4c560ecd44cc8bb88531d59323a0 msgid "set_temp_alarm_led.png" msgstr "" -#: ../../en/units/ncir2.rst:204 c88b8e22136e4a7fbe69ba439f1ee7ff +#: ../../en/units/ncir2.rst:204 aa245a4190e34b0b9603167ebd3f0cb5 msgid "Get the buzzer frequency for temperature alarm." msgstr "获取温度报警蜂鸣器鸣响频率。" #: ../../en/units/ncir2.rst:206 ../../en/units/ncir2.rst:224 -#: 7c0c7c06ecde45fba12a13e29acc16ca +#: 3a857ea2a7b7478eb9a5a7953008b5df 6d0dace931fe4c93984f69b63c7c028a msgid "" "int alarm_reg: temperature alarm buzzer frequency register, " "LOW_TEMP_FREQ_REG: Low temperature alarm buzzer frequency register, " @@ -349,40 +398,40 @@ msgstr "" "int alarm_reg: 温度报警蜂鸣器频率寄存器,LOW_TEMP_FREQ_REG: " "低温报警蜂鸣器频率寄存器,HIGH_TEMP_FREQ_REG:高温报警蜂鸣器频率寄存器。" -#: ../../en/units/ncir2.rst:207 04af98094e9643ebb86ebcb0ce2d920a +#: ../../en/units/ncir2.rst:207 4f1c4bbba7e048f8b5f186ce75fb3c3e msgid "buzzer frequency." msgstr "蜂鸣器频率。" -#: ../../en/units/ncir2.rst:212 bb9e88476a254a13857616f920ddcd3b +#: ../../en/units/ncir2.rst:212 55aa9b81fedb4f9393d940dfb969dfa0 msgid "|get_temp_buzzer_freq.png|" msgstr "" -#: ../../en/refs/unit.ncir2.ref:12 d750fb48e5ea44038ca7420ecfc0c6ac +#: ../../en/refs/unit.ncir2.ref:12 ab09217c884641c5b1d9649ed927e3cc msgid "get_temp_buzzer_freq.png" msgstr "" -#: ../../en/units/ncir2.rst:222 d44b263b9fa644c6aafcba7145a555ca +#: ../../en/units/ncir2.rst:222 7126d0def5344d0babb437752982e3d4 msgid "Set the buzzer frequency for temperature alarm." msgstr "设置温度报警蜂鸣器频率。" -#: ../../en/units/ncir2.rst:225 0e692e5ac6a142cf849b317a25e75334 +#: ../../en/units/ncir2.rst:225 9f72cde6323e4ca6a91a39b1846a8381 msgid "int freq: buzzer frequency, range: 20~20000Hz" msgstr "int freq: 蜂鸣器频率,范围:20 ~ 20000 Hz。" -#: ../../en/units/ncir2.rst:229 ac9ac30f5ae74ef3831bc5c0cc9386ca +#: ../../en/units/ncir2.rst:229 71a9edce33884ba88e24b498ad2a23fa msgid "|set_temp_buzzer_freq.png|" msgstr "" -#: ../../en/refs/unit.ncir2.ref:25 a3663dcd6618489080d2123a24733d3d +#: ../../en/refs/unit.ncir2.ref:25 7ef239b76d4540b4afa6fc9ed600e7ca msgid "set_temp_buzzer_freq.png" msgstr "" -#: ../../en/units/ncir2.rst:240 09685e9d96ee45a4a5b71869787d5b83 +#: ../../en/units/ncir2.rst:240 90fef05b0bc4492d9ed989c21a679cd5 msgid "Get the buzzer alarm interval." msgstr "获取蜂鸣器报警间隔。" #: ../../en/units/ncir2.rst:242 ../../en/units/ncir2.rst:260 -#: 656641b3ac5c48ba8b0be063dc2ba7ca +#: f42a6d8c51ac4891b9646f7447795417 fd0d97dd77eb4539863a074e81e1c5a2 msgid "" "int alarm_reg: buzzer alarm interval register, LOW_ALARM_INTER_REG: Low " "temperature alarm interval register, HIGH_ALARM_INTER_REG: High " @@ -391,40 +440,40 @@ msgstr "" "int alarm_reg: 蜂鸣器报警间隔寄存器,LOW_ALARM_INTER_REG: " "低温报警间隔寄存器,HIGH_ALARM_INTER_REG:高温报警间隔寄存器。" -#: ../../en/units/ncir2.rst:243 3b0e586b552543dfbc7b02a1c6d28e28 +#: ../../en/units/ncir2.rst:243 4a7ed7c42757401a98ea03999bdf8df0 msgid "buzzer alarm interval. (unit: ms)" msgstr "蜂鸣器报警间隔(单位:ms)。" -#: ../../en/units/ncir2.rst:248 0b952ea55fa847fb8388fcd9cb15848c +#: ../../en/units/ncir2.rst:248 46f3a6bd4ee14f7da71afbcecb0d96d1 msgid "|get_temp_alarm_interval.png|" msgstr "" -#: ../../en/refs/unit.ncir2.ref:13 8745cd0b90be4782a13fca2e5752b3ab +#: ../../en/refs/unit.ncir2.ref:13 a841745574ef4e3aa7a385fdd9be1fcb msgid "get_temp_alarm_interval.png" msgstr "" -#: ../../en/units/ncir2.rst:258 673f332cd66d4f8892c3813f7e91d427 +#: ../../en/units/ncir2.rst:258 e55096d250e643728ddea9e2b2804603 msgid "Set the buzzer alarm interval." msgstr "设置蜂鸣器报警间隔。" -#: ../../en/units/ncir2.rst:261 a54acfc9da074ac39e46e0e8a329ca53 +#: ../../en/units/ncir2.rst:261 1d40ea9926324e4ea2638d23c72e3f00 msgid "int interval: alarm interval, range: 1 ~ 5000(unit: ms)." msgstr "int interval: 报警间隔,范围:1 ~ 5000 (ms)。" -#: ../../en/units/ncir2.rst:265 4f6574b3329a4598bf8154a2c938fb87 +#: ../../en/units/ncir2.rst:265 93a94d2c284c4e738486dc0ab3f8f9db msgid "|set_temp_alarm_interval.png|" msgstr "" -#: ../../en/refs/unit.ncir2.ref:26 5157e56bc89348209dfb39c7b5e51f55 +#: ../../en/refs/unit.ncir2.ref:26 0bd3f3665800446ca6ac889936f63ecf msgid "set_temp_alarm_interval.png" msgstr "" -#: ../../en/units/ncir2.rst:275 fce0d68caa2a4b59b5227f25a2ab6039 +#: ../../en/units/ncir2.rst:275 59409c501fa049a3a8aa277737a0ebdd msgid "Get the duty cycle of the temperature alarm buzzer signal." msgstr "获取温度报警蜂鸣器控制信号占空比。" #: ../../en/units/ncir2.rst:277 ../../en/units/ncir2.rst:295 -#: cf50fece2e0745748e82cafdbb4d6f41 +#: 7742a5b1da274f28afefb414989df1b6 d35c6bc7882e4c4eaf1c8bfc1b1fee69 msgid "" "Duty cycle register for the temperature alarm buzzer signal. " "LOW_ALARM_DUTY_REG: Register for low temperature alarm duty cycle. " @@ -433,235 +482,235 @@ msgstr "" "温度报警蜂鸣器控制信号占空比寄存器,LOW_ALARM_DUTY_REG: " "低温报警占空比寄存器,HIGH_ALARM_DUTY_REG:高温报警占空比寄存器。" -#: ../../en/units/ncir2.rst:278 0a32206f24384a7da09501dce7708901 +#: ../../en/units/ncir2.rst:278 210b70372272400597014f3e43e5a219 msgid "duty cycle." msgstr "占空比。" -#: ../../en/units/ncir2.rst:283 1dd1589f7c724c168ae0e4d04ffa29b7 +#: ../../en/units/ncir2.rst:283 43cc7067f257412495cc2da66736340e msgid "|get_temp_buzzer_duty.png|" msgstr "" -#: ../../en/refs/unit.ncir2.ref:14 38246b2bfa4e4f369ed3bd368088b857 +#: ../../en/refs/unit.ncir2.ref:14 d61d8f1b258b4c53b54075ed9fef8bc4 msgid "get_temp_buzzer_duty.png" msgstr "" -#: ../../en/units/ncir2.rst:293 705b4dacf61b41458e488937d8d882fe +#: ../../en/units/ncir2.rst:293 5bf99ddb59ea41aea3dc2e2400aa058d msgid "Set the duty cycle of the temperature alarm buzzer signal." msgstr "设置温度报警蜂鸣器控制信号占空比。" -#: ../../en/units/ncir2.rst:296 1dacf59867ea42d78e4af6ba822493d1 +#: ../../en/units/ncir2.rst:296 57ce5a4157a34a7f97c19e3903b12512 msgid "int duty: Temperature alarm buzzer signal duty cycle, range: 0 ~ 255." msgstr "int duty: 温度报警蜂鸣器控制信号占空比,范围:0 ~ 255。" -#: ../../en/units/ncir2.rst:300 697ad87532db41ac86d263811c596725 +#: ../../en/units/ncir2.rst:300 587fbb12aa5642aa8714e14fb7564a0e msgid "|set_temp_buzzer_duty.png|" msgstr "" -#: ../../en/refs/unit.ncir2.ref:27 38246b2bfa4e4f369ed3bd368088b857 +#: ../../en/refs/unit.ncir2.ref:27 a3b190691b46435c971a7277b1a13073 msgid "set_temp_buzzer_duty.png" msgstr "" -#: ../../en/units/ncir2.rst:310 386f20a640e649b4b111a8fa79964ead +#: ../../en/units/ncir2.rst:310 5809cd9fdfa24cd689d6b1f2fdba8e23 msgid "Get the frequeny of the buzzer signal." msgstr "获取蜂鸣器控制信号频率。" -#: ../../en/units/ncir2.rst:312 7f31443806cd4b8b8b9941f1126b3b86 +#: ../../en/units/ncir2.rst:312 ca2001887b114aa9b91364b03e6a30b6 msgid "frequeny(Hz)" msgstr "频率(Hz)" -#: ../../en/units/ncir2.rst:317 6e81331b4aa44d798de6a8827803f002 +#: ../../en/units/ncir2.rst:317 3a891657d5d444a9bf3d04d58386e38e msgid "|get_buzzer_freq.png|" msgstr "" -#: ../../en/refs/unit.ncir2.ref:15 5d1dc44e9f5e44f0b03f3cf361e5246b +#: ../../en/refs/unit.ncir2.ref:15 53a8f98866bb46edb056f5ef9c995677 msgid "get_buzzer_freq.png" msgstr "" -#: ../../en/units/ncir2.rst:327 4266b3e9f93640ea8573928bf30824d0 +#: ../../en/units/ncir2.rst:327 853b3f4956384e2fa69b1e4be6d0db9e msgid "Set the frequeny of the buzzer signal." msgstr "设置蜂鸣器控制信号频率。" -#: ../../en/units/ncir2.rst:329 d2ab0e7ff4404bd0b012a2bd9547d26d +#: ../../en/units/ncir2.rst:329 60a5d216801f43bebff3110f6005ad71 msgid "int freq: buzzer signal frequency, range: 20 ~ 20000 (Hz)." msgstr "int freq: 蜂鸣器控制信号频率,范围:20 ~ 20000(Hz)。" -#: ../../en/units/ncir2.rst:333 023495983d864e47a2d3666d53ba9bbb +#: ../../en/units/ncir2.rst:333 8fa7b7ca48fc46769f80765f67843029 msgid "|set_buzzer_freq.png|" msgstr "" -#: ../../en/refs/unit.ncir2.ref:28 04716bedd10e40c081176420f15feb5e +#: ../../en/refs/unit.ncir2.ref:28 8a6e898c1d25422f82c11227f65db9fc msgid "set_buzzer_freq.png" msgstr "" -#: ../../en/units/ncir2.rst:343 e291ec77866f4ce6ace9f1c0293be394 +#: ../../en/units/ncir2.rst:343 389cb198f70b4a8d9690602997b7ae96 msgid "Get the duty cycle of the buzzer signal." msgstr "获取蜂鸣器控制信号占空比。" -#: ../../en/units/ncir2.rst:345 d5452c39592246668a62b5a766ed4753 +#: ../../en/units/ncir2.rst:345 b36883e58af34f849144153806b47d6a msgid "Duty cycle" msgstr "占空比" -#: ../../en/units/ncir2.rst:350 9f987961d5784355b15e7234a7ead910 +#: ../../en/units/ncir2.rst:350 73e3a332bb6e4c38975072a6d5759f62 msgid "|get_buzzer_duty.png|" msgstr "" -#: ../../en/refs/unit.ncir2.ref:16 bf9d6a6bd21b41218c2e992b004b3abd +#: ../../en/refs/unit.ncir2.ref:16 8647253d76e849ebb88c4946fcb009b8 msgid "get_buzzer_duty.png" msgstr "" -#: ../../en/units/ncir2.rst:360 21836360a832416ab5c7639cb59b8fdd +#: ../../en/units/ncir2.rst:360 5f6eebb81ab44e4fa72e9b7dd0d0ad0a msgid "Set the duty cycle of the buzzer signal." msgstr "设置蜂鸣器控制信号占空比。" -#: ../../en/units/ncir2.rst:362 356f486fb8f941a3a332832c20c01e05 +#: ../../en/units/ncir2.rst:362 327f14a93ccf48a483164417d08fa4a5 msgid "int duty: Duty cycle, range: 0 ~ 255." msgstr "int duty: 占空比,范围:0 ~ 255。" -#: ../../en/units/ncir2.rst:366 7e09dabfc20144f087472815dc637232 +#: ../../en/units/ncir2.rst:366 45b6e2e74a1a4bf3aa86c524ad4551b5 msgid "|set_buzzer_duty.png|" msgstr "" -#: ../../en/refs/unit.ncir2.ref:29 bf9d6a6bd21b41218c2e992b004b3abd +#: ../../en/refs/unit.ncir2.ref:29 399b708799e84c51bb3badb9107b2320 msgid "set_buzzer_duty.png" msgstr "" -#: ../../en/units/ncir2.rst:376 63fb5355168c4a89b7830e3ca78e5d2c +#: ../../en/units/ncir2.rst:376 b360dd992cb54347aa6244c349ecc32a msgid "Get the buzzer control status" msgstr "获取蜂鸣器控制状态。" -#: ../../en/units/ncir2.rst:378 330bc1a4945d42dbb18050f509b05125 +#: ../../en/units/ncir2.rst:378 5c3d663cf0d04cbe983c0b416d56c25d msgid "Returns the current buzzer control status" msgstr "返回蜂鸣器控制状态。" -#: ../../en/units/ncir2.rst:383 5eb78383897744a58870946a55fd73be +#: ../../en/units/ncir2.rst:383 646271d006b84b3ebb4bae22c2de8589 msgid "|get_buzzer_control.png|" msgstr "" -#: ../../en/refs/unit.ncir2.ref:17 624b039ae5f0435b8dd13f784724ca1c +#: ../../en/refs/unit.ncir2.ref:17 26c95f36e95743818e7ec58607e56cd3 msgid "get_buzzer_control.png" msgstr "" -#: ../../en/units/ncir2.rst:393 b2a7564ef69d4ed5b0932eece41c72b1 +#: ../../en/units/ncir2.rst:393 d57478b169a6482e98f25e8cbdb2c435 msgid "Set the buzzer control status" msgstr "设置蜂鸣器控制状态。" -#: ../../en/units/ncir2.rst:395 34c995ff7f4b48f28e04e58162e31ab1 +#: ../../en/units/ncir2.rst:395 62f7ad82b30342d38b7545ac8257173c msgid "int ctrl: Control value, 0 to turn off the buzzer, 1 to turn on the buzzer" msgstr "int ctrl: 控制值,0 为关闭蜂鸣器,1 为打开蜂鸣器。" -#: ../../en/units/ncir2.rst:399 a5c8acf14740453e851b662d9ca52651 +#: ../../en/units/ncir2.rst:399 4bb21866af194cde9ddb0b6fb96fa4f5 msgid "|set_buzzer_control.png|" msgstr "" -#: ../../en/refs/unit.ncir2.ref:30 ed744467ccf74e4187b3267d81e9cf81 +#: ../../en/refs/unit.ncir2.ref:30 f0467a13dfc44afe8307a0741a26841d msgid "set_buzzer_control.png" msgstr "" -#: ../../en/units/ncir2.rst:410 b39681db937e4ddc8be80df0f3a08988 +#: ../../en/units/ncir2.rst:410 399c6ab5688f4955a699977e6a99ac93 msgid "Get the current RGB LED value" msgstr "获取当前 RGB LED 颜色值。" -#: ../../en/units/ncir2.rst:412 22ba4a222da1447eaf37c0f6a0bcb8c7 +#: ../../en/units/ncir2.rst:412 2250924d2d6049afac7a345f9283eca2 msgid "The current RGB LED values in the format [r, g, b]" msgstr "当前 RG LED 颜色值,格式:[r, g, b]。" -#: ../../en/units/ncir2.rst:417 9a7b42ce3f2a40ac808eb5bc2860f1fb +#: ../../en/units/ncir2.rst:417 15c4f226401243e29a0e61b5bb06b1ec msgid "|get_rgb_led.png|" msgstr "" -#: ../../en/refs/unit.ncir2.ref:18 a98d858bc7bf448ba3f6acc027916e95 +#: ../../en/refs/unit.ncir2.ref:18 fdd450681356449e8bfbbdb3ab78e6f4 msgid "get_rgb_led.png" msgstr "" -#: ../../en/units/ncir2.rst:427 302663ac0d1b41baaf5deb6df8dcba2b +#: ../../en/units/ncir2.rst:427 7e2e1dcf57824f6e8206e2ce58a89c19 msgid "Set the RGB LED value" msgstr "设置 RGB LED 颜色值。" -#: ../../en/units/ncir2.rst:429 9b52a8dc8f7940208a7ec538275b287d +#: ../../en/units/ncir2.rst:429 11a1b3e1809f43d9ad7d2cc698fb1f07 msgid "int rgb: RGB value in the range of 0 ~ 0xFFFFFF" msgstr "int rgb: RGB 值,范围:0 ~ 0xFFFFFF。" -#: ../../en/units/ncir2.rst:433 3ee0fd4f7d494124828680af5743fd72 +#: ../../en/units/ncir2.rst:433 93cdeab531c542e4bfa700d36686a8db msgid "|set_rgb_led.png|" msgstr "" -#: ../../en/refs/unit.ncir2.ref:31 ac243614d1df424a9d7ebe46f85d3bb0 +#: ../../en/refs/unit.ncir2.ref:31 79b2c94f83fc4970b30a30bf6f32dce5 msgid "set_rgb_led.png" msgstr "" -#: ../../en/units/ncir2.rst:444 51ec1c1d03bc432bbb4b8555c04e5e46 +#: ../../en/units/ncir2.rst:444 40cd03933d3f40a596159dcebd1b0c7b msgid "Get the button status" msgstr "获取按键状态。" -#: ../../en/units/ncir2.rst:446 252a958f7c094d38815c9cf212a42061 +#: ../../en/units/ncir2.rst:446 33d585c2adc14735a0fa4eb4f771c3e3 msgid "Button status, either 0 (not pressed) or 1 (pressed)" msgstr "按键状态,为 0(未按下)或 1(按下)。" -#: ../../en/units/ncir2.rst:451 b91cf76ef46b49f9a9608b702e65e2af +#: ../../en/units/ncir2.rst:451 f3db2facbfee43ce966f6be1171b40c4 msgid "|get_button_status.png|" msgstr "" -#: ../../en/refs/unit.ncir2.ref:19 5e79fd67dbc546bf955a298ecdfa6d8a +#: ../../en/refs/unit.ncir2.ref:19 025ec56ef3fc42428167d3727683a165 msgid "get_button_status.png" msgstr "" -#: ../../en/units/ncir2.rst:461 7f4a5106edbc46c1b587f28925cd7962 +#: ../../en/units/ncir2.rst:461 f2f6deac0576414491f56e43e413b8ef msgid "Save configuration settings" msgstr "保存配置。" -#: ../../en/units/ncir2.rst:465 bccc64ac3a9f450abb31d233e363199f +#: ../../en/units/ncir2.rst:465 d803cec1be7c4b7ca58d05bd7c1c310a msgid "|save_config_setting.png|" msgstr "" -#: ../../en/refs/unit.ncir2.ref:32 dfd832e15a504133a921bee6d073b216 +#: ../../en/refs/unit.ncir2.ref:32 de640fe9f4a24c74a89df43a9e3181d8 msgid "save_config_setting.png" msgstr "" -#: ../../en/units/ncir2.rst:475 c701050245674187872a31e3a4a4f793 +#: ../../en/units/ncir2.rst:475 d16379dac0314752b26738a7b62032cd msgid "Get the chip temperature" msgstr "获取芯片温度。" -#: ../../en/units/ncir2.rst:477 7f3c8d25bfbd4880989d43e3790fef64 +#: ../../en/units/ncir2.rst:477 71cef4d33faa49cf8c4b605f799b855b msgid "Chip temperature in Celsius (°C)" msgstr "芯片温度(℃)。" -#: ../../en/units/ncir2.rst:482 aa08ea5c44bb483f9946a1bbed0a88de +#: ../../en/units/ncir2.rst:482 40e5eebea3744bafbf7b5bcbb21e707f msgid "|get_chip_temperature.png|" msgstr "" -#: ../../en/refs/unit.ncir2.ref:20 02d464e19db74a1d9c510d33a987036b +#: ../../en/refs/unit.ncir2.ref:20 36e07ca7effd4953a628d1c4b1bad624 msgid "get_chip_temperature.png" msgstr "" -#: ../../en/units/ncir2.rst:492 414f280f503c494bbaabf94b603373c1 +#: ../../en/units/ncir2.rst:492 545deccd769c4c21acce4bcfebbaacd0 msgid "Get device specifications" msgstr "获取设备规格" -#: ../../en/units/ncir2.rst:494 e19d980277994b0eba9fe5ebbfb1f068 +#: ../../en/units/ncir2.rst:494 7bd4115ebbe54e96a5a870baf9e257df msgid "Device specifications" msgstr "设备规格" -#: ../../en/units/ncir2.rst:499 d42b695d01d347edba125d46d09e9b4f +#: ../../en/units/ncir2.rst:499 5e98c728ac0141d2b5f9e760f966f3fe msgid "|get_device_spec.png|" msgstr "" -#: ../../en/refs/unit.ncir2.ref:21 7f2604dd0ff847a786d03b0cce4c3fc4 +#: ../../en/refs/unit.ncir2.ref:21 b13d742e249a46a7850fc4461030a05a msgid "get_device_spec.png" msgstr "" -#: ../../en/units/ncir2.rst:509 7ffa02cab10d43c5a1258350dacc5d11 +#: ../../en/units/ncir2.rst:509 76df4322eeb04416a02d9eabaf4d868a msgid "Set the I2C address of the device" msgstr "设置设备 I2C 地址" -#: ../../en/units/ncir2.rst:511 748cd15b21ce4711934b59e6062e9eb5 +#: ../../en/units/ncir2.rst:511 fea6048ab9a34a2786095d5d14a30704 msgid "int addr: I2C address range: 1 ~ 127." msgstr "int addr: I2C 地址,范围:1 ~ 127。" -#: ../../en/units/ncir2.rst:515 6d3240f89852409d8a268e274824138c +#: ../../en/units/ncir2.rst:515 ef037cae5f034c55a4af5db5feac0a61 msgid "|set_i2c_address.png|" msgstr "" -#: ../../en/refs/unit.ncir2.ref:33 a60e1b02292e4c8b9c0e167b78ab0875 +#: ../../en/refs/unit.ncir2.ref:33 d94baa9eb4884a26853327c273b5d37c msgid "set_i2c_address.png" msgstr "" From 28ad1b7ff2c48cd26d467868a650fc53bebc5757 Mon Sep 17 00:00:00 2001 From: lbuque Date: Thu, 15 May 2025 16:18:19 +0800 Subject: [PATCH 085/322] .gitlab-ci.yml: Add code-format job. Signed-off-by: lbuque --- .gitlab-ci.yml | 15 +++++++++++++++ tests/hardware/button_cb.py | 6 +++--- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index a0572bc0..3443d76f 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -9,11 +9,26 @@ cache: - $ESP_IDF_SRC_DIR stages: + - code_format - build - docs - release +code-format: + stage: code_format + script: + - source tools/ci.sh && ci_code_formatting_setup + - source tools/ci.sh && ci_code_formatting_run + tags: + - uiflow-firmware + only: + changes: + - "m5stack/**" + - "examples/**" + - "tests/**" + - "third-party/**" + build-job: stage: build script: diff --git a/tests/hardware/button_cb.py b/tests/hardware/button_cb.py index 53b48958..6c0b20ca 100644 --- a/tests/hardware/button_cb.py +++ b/tests/hardware/button_cb.py @@ -8,19 +8,19 @@ from M5 import Lcd, BtnA, BtnB, BtnC -def btnA_wasPressed_cb(state): +def btnA_wasPressed_cb(state): # noqa: N802 Lcd.clear(random.randint(0, 0xFFFF)) Lcd.setCursor(0, 0) Lcd.print("btnA_wasPressed_cb") -def btnB_wasHold_cb(state): +def btnB_wasHold_cb(state): # noqa: N802 Lcd.clear(random.randint(0, 0xFFFF)) Lcd.setCursor(0, 0) Lcd.print("btnB_wasHold_cb") -def btnC_wasDoubleClicked_cb(state): +def btnC_wasDoubleClicked_cb(state): # noqa: N802 Lcd.clear(random.randint(0, 0xFFFF)) Lcd.setCursor(0, 0) Lcd.print("btnC_wasDoubleClicked_cb") From 417c76f3494b2fb2f0e0c21c9d0fc0a85fe5ba9e Mon Sep 17 00:00:00 2001 From: lbuque Date: Thu, 15 May 2025 17:25:09 +0800 Subject: [PATCH 086/322] tools/ci.sh: Fix apt update error. Signed-off-by: lbuque --- tools/ci.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tools/ci.sh b/tools/ci.sh index 6cd6bbef..823119cb 100644 --- a/tools/ci.sh +++ b/tools/ci.sh @@ -14,7 +14,8 @@ fi # code formatting function ci_code_formatting_setup { - sudo apt-add-repository --yes --update ppa:pybricks/ppa + sudo apt-add-repository --yes ppa:pybricks/ppa + sudo apt update sudo apt-get install uncrustify pip3 install black pip3 install micropython-typesheds From 27aeba2b17181507c76567c4d89cd4819cfb25df Mon Sep 17 00:00:00 2001 From: lbuque Date: Fri, 16 May 2025 09:46:25 +0800 Subject: [PATCH 087/322] tools/ci.sh: Repair environment installation failure. Signed-off-by: lbuque --- .gitlab-ci.yml | 32 ++++++++++-- .../unit/ncir2/m5cores3_ncir2_base_example.py | 50 ++++++++++++++----- pyproject.toml | 2 +- tools/ci.sh | 37 ++++++++++++-- 4 files changed, 98 insertions(+), 23 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 3443d76f..f01f0954 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -10,13 +10,16 @@ cache: stages: - code_format - - build + - build_quick - docs + - build - release code-format: stage: code_format + before_script: + - export PATH="$HOME/.local/bin:$PATH" script: - source tools/ci.sh && ci_code_formatting_setup - source tools/ci.sh && ci_code_formatting_run @@ -29,13 +32,13 @@ code-format: - "tests/**" - "third-party/**" -build-job: - stage: build +build-quick-job: + stage: build_quick script: - sudo apt-get update -qy - sudo apt-get install git wget flex bison gperf python3 python3-pip python3-venv cmake ninja-build ccache libffi-dev libssl-dev dfu-util libusb-1.0-0 -y - source tools/ci.sh && ci_esp32_idf522_setup - - source tools/ci.sh && ci_esp32_nightly_build + - source tools/ci.sh && ci_esp32_quick_build artifacts: paths: - m5stack/build-*/uiflow-*-*.bin @@ -49,6 +52,27 @@ build-job: - "third-party/**" +build-job: + stage: build + script: + - sudo apt-get update -qy + - sudo apt-get install git wget flex bison gperf python3 python3-pip python3-venv cmake ninja-build ccache libffi-dev libssl-dev dfu-util libusb-1.0-0 -y + - source tools/ci.sh && ci_esp32_idf522_setup + - source tools/ci.sh && ci_esp32_nightly_build + artifacts: + paths: + - m5stack/build-*/uiflow-*-*.bin + - third-party/build-*/uiflow-*-*.bin + tags: + - uiflow-firmware + only: + refs: + - tags + variables: + - $CI_COMMIT_TAG =~ /^release\/[0-9]+\.[0-9]+\.[0-9]+$/ + - $CI_COMMIT_REF_SLUG == "develop_m5things" + + build-docs: stage: docs script: diff --git a/examples/unit/ncir2/m5cores3_ncir2_base_example.py b/examples/unit/ncir2/m5cores3_ncir2_base_example.py index 2c44a594..43a06376 100644 --- a/examples/unit/ncir2/m5cores3_ncir2_base_example.py +++ b/examples/unit/ncir2/m5cores3_ncir2_base_example.py @@ -11,7 +11,6 @@ import time - title0 = None label_temp = None label_la = None @@ -25,16 +24,30 @@ def setup(): - global title0, label_temp, label_la, label_ha, label_temp_val, label_la_val, label_ha_val, i2c0, ncir2_0, last_time + global \ + title0, \ + label_temp, \ + label_la, \ + label_ha, \ + label_temp_val, \ + label_la_val, \ + label_ha_val, \ + i2c0, \ + ncir2_0, \ + last_time M5.begin() Widgets.fillScreen(0x222222) - title0 = Widgets.Title("Temperature meassure", 3, 0xffffff, 0x0000FF, Widgets.FONTS.DejaVu24) - label_temp = Widgets.Label("Temp: ", 10, 116, 1.0, 0xffffff, 0x222222, Widgets.FONTS.DejaVu24) - label_la = Widgets.Label("low temp alarm value: ", 10, 50, 1.0, 0x0000ff, 0x222222, Widgets.FONTS.DejaVu18) - label_ha = Widgets.Label("high temp alarm value: ", 10, 80, 1.0, 0xff0000, 0x222222, Widgets.FONTS.DejaVu18) - label_temp_val = Widgets.Label(" ", 95, 116, 1.0, 0xffffff, 0x222222, Widgets.FONTS.DejaVu24) - label_la_val = Widgets.Label(" ", 232, 50, 1.0, 0x0000ff, 0x222222, Widgets.FONTS.DejaVu18) - label_ha_val = Widgets.Label(" ", 239, 80, 1.0, 0xff0000, 0x222222, Widgets.FONTS.DejaVu18) + title0 = Widgets.Title("Temperature meassure", 3, 0xFFFFFF, 0x0000FF, Widgets.FONTS.DejaVu24) + label_temp = Widgets.Label("Temp: ", 10, 116, 1.0, 0xFFFFFF, 0x222222, Widgets.FONTS.DejaVu24) + label_la = Widgets.Label( + "low temp alarm value: ", 10, 50, 1.0, 0x0000FF, 0x222222, Widgets.FONTS.DejaVu18 + ) + label_ha = Widgets.Label( + "high temp alarm value: ", 10, 80, 1.0, 0xFF0000, 0x222222, Widgets.FONTS.DejaVu18 + ) + label_temp_val = Widgets.Label(" ", 95, 116, 1.0, 0xFFFFFF, 0x222222, Widgets.FONTS.DejaVu24) + label_la_val = Widgets.Label(" ", 232, 50, 1.0, 0x0000FF, 0x222222, Widgets.FONTS.DejaVu18) + label_ha_val = Widgets.Label(" ", 239, 80, 1.0, 0xFF0000, 0x222222, Widgets.FONTS.DejaVu18) i2c0 = I2C(0, scl=Pin(1), sda=Pin(2), freq=100000) ncir2_0 = NCIR2Unit(i2c0, 0x5A) @@ -44,21 +57,32 @@ def setup(): def loop(): - global title0, label_temp, label_la, label_ha, label_temp_val, label_la_val, label_ha_val, i2c0, ncir2_0, last_time + global \ + title0, \ + label_temp, \ + label_la, \ + label_ha, \ + label_temp_val, \ + label_la_val, \ + label_ha_val, \ + i2c0, \ + ncir2_0, \ + last_time M5.update() if (time.ticks_diff((time.ticks_ms()), last_time)) > 500: last_time = time.ticks_ms() - label_temp.setText(str((str((ncir2_0.get_temperature_value)) + str(' C')))) + label_temp.setText(str((str((ncir2_0.get_temperature_value)) + str(" C")))) -if __name__ == '__main__': +if __name__ == "__main__": try: setup() while True: loop() except (Exception, KeyboardInterrupt) as e: try: - from utility import print_error_msg + from utility import print_error_msg + print_error_msg(e) except ImportError: print("please update to latest firmware") diff --git a/pyproject.toml b/pyproject.toml index fb3bad92..cc6da16e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -43,4 +43,4 @@ max-complexity = 40 "m5stack/modules/tiny_gui/**.py" = ["F821"] [tool.ruff.format] -exclude = ["docs/**/*.py", "micropython/**/*.py", "tools/**/*.py"] +exclude = ["docs/**/*.py", "esp-adf/**/*.py", "esp-idf/**/*.py", "m5stack/cmodules/lv_binding_micropython/**/*.py", "m5stack/cmodules/lv_binding_micropython/**/*.pyi", "m5stack/components/**/*.py", "micropython/**/*.py", "tools/**/*.py"] diff --git a/tools/ci.sh b/tools/ci.sh index 823119cb..10d46c60 100644 --- a/tools/ci.sh +++ b/tools/ci.sh @@ -17,16 +17,21 @@ function ci_code_formatting_setup { sudo apt-add-repository --yes ppa:pybricks/ppa sudo apt update sudo apt-get install uncrustify - pip3 install black - pip3 install micropython-typesheds - pip3 install ruff==0.3.0 - pip3 install codespell==2.2.6 tomli==2.0.1 - pip3 install pre-commit==3.6.2 + sudo apt install pipx + pipx install uv + uv venv + source .venv/bin/activate + uv pip install black + uv pip install micropython-typesheds + uv pip install ruff==0.3.0 + uv pip install codespell==2.2.6 tomli==2.0.1 + uv pip install pre-commit==3.6.2 uncrustify --version black --version } function ci_code_formatting_run { + source .venv/bin/activate tools/codeformat.py -v } @@ -189,6 +194,28 @@ function ci_esp32_build { fi } +function ci_esp32_quick_build { + source esp-idf/export.sh + pip install future + make ${MAKEOPTS} -C m5stack unpatch + make ${MAKEOPTS} -C m5stack submodules + make ${MAKEOPTS} -C m5stack patch + make ${MAKEOPTS} -C m5stack littlefs + make ${MAKEOPTS} -C m5stack mpy-cross + make ${MAKEOPTS} -C m5stack BOARD=M5STACK_AirQ pack_all + make ${MAKEOPTS} -C m5stack BOARD=M5STACK_Atom_Lite pack_all + make ${MAKEOPTS} -C m5stack BOARD=M5STACK_AtomS3 pack_all + make ${MAKEOPTS} -C m5stack BOARD=M5STACK_Basic pack_all + make ${MAKEOPTS} -C m5stack BOARD=M5STACK_Basic_4MB pack_all + make ${MAKEOPTS} -C m5stack BOARD=M5STACK_Core2 pack_all + make ${MAKEOPTS} -C m5stack BOARD=M5STACK_CoreInk pack_all + make ${MAKEOPTS} -C m5stack BOARD=M5STACK_CoreS3 pack_all + make ${MAKEOPTS} -C m5stack BOARD=M5STACK_NanoC6 pack_all + make ${MAKEOPTS} -C m5stack BOARD=M5STACK_Stamp_PICO pack_all + make ${MAKEOPTS} -C m5stack BOARD=M5STACK_StickC pack_all + make ${MAKEOPTS} -C third-party BOARD=SEEED_STUDIO_XIAO_ESP32S3 pack_all +} + function ci_esp32_nightly_build { source esp-idf/export.sh pip install future From 5e43bb99ceeb976c8eb2b8274f0f8996141d87de Mon Sep 17 00:00:00 2001 From: luoweiyuan Date: Fri, 16 May 2025 14:47:50 +0800 Subject: [PATCH 088/322] libs/module: Add support for Unit GatewayH2. Signed-off-by: luoweiyuan --- docs/en/module/gateway_h2.rst | 30 +-- docs/en/refs/unit.gateway_h2.ref | 24 +++ docs/en/units/gateway_h2.rst | 96 +++++++++ docs/en/units/index.rst | 1 + .../zh_CN/LC_MESSAGES/module/gateway_h2.po | 134 ++++++------ .../zh_CN/LC_MESSAGES/units/gateway_h2.po | 197 ++++++++++++++++++ .../cores3_switch_endpoint_example.m5f2 | 1 + .../cores3_switch_endpoint_example.py | 130 ++++++++++++ m5stack/libs/unit/__init__.py | 1 + m5stack/libs/unit/gateway_h2.py | 26 +++ m5stack/libs/unit/manifest.py | 1 + 11 files changed, 563 insertions(+), 78 deletions(-) create mode 100644 docs/en/refs/unit.gateway_h2.ref create mode 100644 docs/en/units/gateway_h2.rst create mode 100644 docs/locales/zh_CN/LC_MESSAGES/units/gateway_h2.po create mode 100644 examples/unit/gateway_h2/cores3_switch_endpoint_example.m5f2 create mode 100644 examples/unit/gateway_h2/cores3_switch_endpoint_example.py create mode 100644 m5stack/libs/unit/gateway_h2.py diff --git a/docs/en/module/gateway_h2.rst b/docs/en/module/gateway_h2.rst index d6705825..1368a302 100644 --- a/docs/en/module/gateway_h2.rst +++ b/docs/en/module/gateway_h2.rst @@ -1,5 +1,5 @@ GatewayH2 Module -============================ +================ .. sku: M141 @@ -13,11 +13,11 @@ Support the following products: .. note:: When using this module, you need to flash the NCP firmware to the module. For details, refer to the `ESP Zigbee NCP `_ documentation. -UiFlow2 Example: --------------------------- +UiFlow2 Example +--------------- -Switch Control -^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Switch Control +^^^^^^^^^^^^^^ .. note:: To use this example, you need to flash this program onto an ESP32C6 or similar module as a light node device. For details, refer to `HA_on_off_light `_ @@ -33,11 +33,11 @@ Example output: None -MicroPython Example: --------------------------- +MicroPython Example +------------------- -Switch Control -^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Switch Control +^^^^^^^^^^^^^^ The example demonstrates group control and targeted device operation for light nodes through SwitchEndpoint of Gateway H2 module. @@ -52,10 +52,10 @@ Example output: None **API** --------------------------- +------- GatewayH2Module -^^^^^^^^^^^^^^^^^^^^^^^^ +^^^^^^^^^^^^^^^ .. class:: module.gateway_h2.GatewayH2Module @@ -94,9 +94,14 @@ GatewayH2Module h2_switch_endpoint = module_gateway_h2.create_switch_endpoint() +.. _switchendpoint: + +SwitchEndpoint +^^^^^^^^^^^^^^ + .. class:: SwitchEndpoint - Return by GatewayH2Module.create_switch_endpoint() + Return by GatewayH2Module.create_switch_endpoint() or GatewayH2Unit.create_switch_endpoint() .. method:: on([addr]) @@ -160,4 +165,3 @@ GatewayH2Module h2_switch_endpoint.toggle(addr) h2_switch_endpoint.toggle() - diff --git a/docs/en/refs/unit.gateway_h2.ref b/docs/en/refs/unit.gateway_h2.ref new file mode 100644 index 00000000..ea17ab9f --- /dev/null +++ b/docs/en/refs/unit.gateway_h2.ref @@ -0,0 +1,24 @@ +.. |Unit Gateway H2| image:: https://m5stack-doc.oss-cn-shenzhen.aliyuncs.com/1128/U195_01.webp + :target: https://docs.m5stack.com/zh_CN/unit/Unit%20Gateway%20H2 + :height: 200px + :width: 200px + +.. |init.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/gateway_h2/init.png +.. |on.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/gateway_h2/on.png +.. |all_on.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/gateway_h2/all_on.png +.. |off.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/gateway_h2/off.png +.. |all_off.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/gateway_h2/all_off.png +.. |toggle.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/gateway_h2/toggle.png +.. |all_toggle.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/gateway_h2/all_toggle.png +.. |cores3_switch_endpoint_example.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/gateway_h2/example.png + + +.. |cores3_switch_endpoint_example.m5f2| raw:: html + + + cores3_switch_endpoint_example.m5f2 + + diff --git a/docs/en/units/gateway_h2.rst b/docs/en/units/gateway_h2.rst new file mode 100644 index 00000000..86f69362 --- /dev/null +++ b/docs/en/units/gateway_h2.rst @@ -0,0 +1,96 @@ +GatewayH2 Unit +============== + +.. sku: U195 + +.. include:: ../refs/unit.gateway_h2.ref + +This library is the driver for Unit Gateway H2, and the unit communicates via UART. + +Support the following products: + + |Unit Gateway H2| + +.. note:: When using this unit, you need to flash the NCP firmware to the unit. For details, refer to the `ESP Zigbee NCP `_ documentation. + +UiFlow2 Example +--------------- + +Switch Control +^^^^^^^^^^^^^^ + +.. note:: To use this example, you need to flash this program onto an ESP32C6 or similar unit as a light node device. For details, refer to `HA_on_off_light `_ + +Open the |cores3_switch_endpoint_example.m5f2| project in UiFlow2. + +The example demonstrates group control and targeted device operation for light nodes through SwitchEndpoint of Gateway H2 unit. + +UiFlow2 Code Block: + + |cores3_switch_endpoint_example.png| + +Example output: + + None + +MicroPython Example +------------------- + +Switch Control +^^^^^^^^^^^^^^ + +The example demonstrates group control and targeted device operation for light nodes through SwitchEndpoint of Gateway H2 unit. + +MicroPython Code Block: + + .. literalinclude:: ../../../examples/unit/gateway_h2/cores3_switch_endpoint_example.py + :language: python + :linenos: + +Example output: + + None + +**API** +------- + +GatewayH2Unit +^^^^^^^^^^^^^ + +.. class:: unit.gateway_h2.GatewayH2Unit + + Create an GatewayH2Unit object. + + :param int id: The UART ID for communication with the GatewayH2 Unit. It can be 1, 2. + :param port: A list or tuple containing the TX and RX pins for UART communication. + + UiFlow2 Code Block: + + |init.png| + + MicroPython Code Block: + + .. code-block:: python + + from unit import GatewayH2Unit + + gateway_h2_unit = GatewayH2Unit(2, port=(1, 2)) + + .. method:: create_switch_endpoint() + + Create Switch Endpoint. + + :returns SwitchEndpoint: zigbee switch endpoint object. + :return type: SwitchEndpoint + + UiFlow2 Code Block: + + |init.png| + + MicroPython Code Block: + + .. code-block:: python + + h2_switch_endpoint = gateway_h2_unit.create_switch_endpoint() + +Refer to :ref:`switchendpoint` for more details about SwitchEndpoint. \ No newline at end of file diff --git a/docs/en/units/index.rst b/docs/en/units/index.rst index 470a5e07..46c1ac8f 100644 --- a/docs/en/units/index.rst +++ b/docs/en/units/index.rst @@ -44,6 +44,7 @@ Unit fader.rst finger.rst flash_light.rst + gateway_h2.rst glass.rst glass2.rst gps_v11.rst diff --git a/docs/locales/zh_CN/LC_MESSAGES/module/gateway_h2.po b/docs/locales/zh_CN/LC_MESSAGES/module/gateway_h2.po index 7fc683a6..4de4688a 100644 --- a/docs/locales/zh_CN/LC_MESSAGES/module/gateway_h2.po +++ b/docs/locales/zh_CN/LC_MESSAGES/module/gateway_h2.po @@ -9,7 +9,7 @@ msgid "" msgstr "" "Project-Id-Version: UIFlow2 Programming Guide \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-04-18 15:58+0800\n" +"POT-Creation-Date: 2025-05-16 11:52+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language: zh_CN\n" @@ -20,30 +20,30 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.16.0\n" -#: ../../en/module/gateway_h2.rst:2 e19e55afc022403693a4976a9db3aea6 +#: ../../en/module/gateway_h2.rst:2 f51e3c3233e7453e8f2f4acffa57bef8 #, fuzzy msgid "GatewayH2 Module" msgstr "创建一个 GatewayH2Module 对象。" -#: ../../en/module/gateway_h2.rst:8 aa9c851722064a0199671b9293d09e49 +#: ../../en/module/gateway_h2.rst:8 a8b4537c868c4ce590331498cb4608cf msgid "" "This library is the driver for Module Gateway H2, and the module " "communicates via UART." msgstr "这个库是 Module Gateway H2 的驱动,该模块使用 UART 通信。" -#: ../../en/module/gateway_h2.rst:10 d86d4e9f66204a8babc5e2d194d635ca +#: ../../en/module/gateway_h2.rst:10 e73c9c0e646b439d8eafacd1167a243f msgid "Support the following products:" msgstr "支持以下产品:" -#: ../../en/module/gateway_h2.rst:12 a4ec1e708cda4aff96cf787f76fdc5c5 +#: ../../en/module/gateway_h2.rst:12 9839c353b8db480b98accf5093a92606 msgid "|Module Gateway H2|" msgstr "" -#: ../../en/refs/module.gateway_h2.ref 773736b1f3254bbb8e1a6628973aab6c +#: ../../en/refs/module.gateway_h2.ref e1ee99b7bf6a4b20b008efa8d07dd091 msgid "Module Gateway H2" msgstr "" -#: ../../en/module/gateway_h2.rst:14 c8ec17fe2c86438c8cfc213301a43a20 +#: ../../en/module/gateway_h2.rst:14 5c3e4eff4b614605b51ce079da114ac4 msgid "" "When using this module, you need to flash the NCP firmware to the module." " For details, refer to the `ESP Zigbee NCP " @@ -53,16 +53,17 @@ msgstr "" "当使用此模块时,需要先给模块烧录 NCP 固件,详情请查看 `ESP Zigbee NCP " "`_" -#: ../../en/module/gateway_h2.rst:17 1181fbeb5bea4791bb3de6793704b859 -msgid "UiFlow2 Example:" +#: ../../en/module/gateway_h2.rst:17 419d3eef03ed43fc8677d85698ab1002 +#, fuzzy +msgid "UiFlow2 Example" msgstr "UiFlow2 应用示例:" #: ../../en/module/gateway_h2.rst:20 ../../en/module/gateway_h2.rst:40 -#: 29e7430bb1914308b4e0286371d50c9e a515a86dc99e452c8f277c86ec46e009 +#: 864632c1ae8d46ff85c50a183e915071 c7cb0a8fbcef48db91283d5003d8988e msgid "Switch Control" msgstr "开关控制" -#: ../../en/module/gateway_h2.rst:22 db4dfa574e0e4fad808edd3e25a06552 +#: ../../en/module/gateway_h2.rst:22 6ba87e9712d64ffcaf0b666af4504c4d msgid "" "To use this example, you need to flash this program onto an ESP32C6 or " "similar module as a light node device. For details, refer to " @@ -73,12 +74,12 @@ msgstr "" "`_" -#: ../../en/module/gateway_h2.rst:24 39110b3a782e49f096b1257054f1f6ca +#: ../../en/module/gateway_h2.rst:24 67b2d07a29e04fb4aba5fd01e9472f18 msgid "Open the |cores3_switch_endpoint_example.m5f2| project in UiFlow2." msgstr "在 UiFlow2 上打开 |cores3_switch_endpoint_example.m5f2| 项目。" #: ../../en/module/gateway_h2.rst:26 ../../en/module/gateway_h2.rst:42 -#: cc7ff6b8a3ff4fc3985877fe1f607cf4 f4053911fc3e40b7a35b6540b14b75a8 +#: 031d9e6d91df4cd6a29911dc6843f1a8 fff1cf0877574d22a8c490356b0097e4 msgid "" "The example demonstrates group control and targeted device operation for " "light nodes through SwitchEndpoint of Gateway H2 module." @@ -87,182 +88,185 @@ msgstr "示例程序演示通过 Gateway H2 模块的 SwitchEndpoint 对灯节 #: ../../en/module/gateway_h2.rst:28 ../../en/module/gateway_h2.rst:68 #: ../../en/module/gateway_h2.rst:87 ../../en/module/gateway_h2.rst:110 #: ../../en/module/gateway_h2.rst:131 ../../en/module/gateway_h2.rst:152 -#: 2f6658ae25494530890cd84bb968b848 47b82bb79100491c92be76ab1f9e63f2 -#: 8f5c1a7ee3424fa5a3c91aa743c42e96 cc180cdd81b24c89b0a45418b594750c -#: cf912c8a810349caa5d2672525e3c67c d91d269cc6c943db8223006762f0dc97 +#: 15d2867f93cd455185035c9a81a68b98 76d62b9ee5834f759b788ba421c0ff25 +#: 80b5e4288569430088331ee387a6fa25 8190f58cfb4645429bb112307e5612a2 +#: 9df527ca3909425197a898778bb86824 dbc2021e823948d7816c19b3edd82298 msgid "UiFlow2 Code Block:" msgstr "UiFlow2 代码块:" -#: ../../en/module/gateway_h2.rst:30 d5c35632775e4a4b9410cc55457e3ccf +#: ../../en/module/gateway_h2.rst:30 142691f989cf47f9b7f66539d2b2540c msgid "|cores3_switch_endpoint_example.png|" msgstr "" -#: ../../en/refs/module.gateway_h2.ref:13 8720d54fbade418281f2769387097397 +#: ../../en/refs/module.gateway_h2.ref:13 bc02d9aab35c4dd69da49e2e3ceeabac msgid "cores3_switch_endpoint_example.png" msgstr "" #: ../../en/module/gateway_h2.rst:32 ../../en/module/gateway_h2.rst:50 -#: 7c65e468a40a41f59ad85576fddbc585 e41ffe3b90354f15af140a75c498a2ce +#: 3e32b0fa8af44327aeb06455b9fd49db 71fc06245ca14d5aac86729c00d988e2 msgid "Example output:" msgstr "示例输出:" #: ../../en/module/gateway_h2.rst:34 ../../en/module/gateway_h2.rst:52 -#: 01c8437c8ba044b1a82e6cb912c6440b 10868a5fed744bab8dc4e928ed8c0f35 +#: 1fc68aa5849a470fb3096c8d33ac9918 6b03eeefe4fa40798707d62fd6452795 msgid "None" msgstr "无" -#: ../../en/module/gateway_h2.rst:37 824f70f7b2ff40fa8d0447e23c185511 -msgid "MicroPython Example:" +#: ../../en/module/gateway_h2.rst:37 4091daf5717048aba494edd7d792036f +#, fuzzy +msgid "MicroPython Example" msgstr "MicroPython 应用示例:" #: ../../en/module/gateway_h2.rst:44 ../../en/module/gateway_h2.rst:72 #: ../../en/module/gateway_h2.rst:91 ../../en/module/gateway_h2.rst:115 #: ../../en/module/gateway_h2.rst:136 ../../en/module/gateway_h2.rst:157 -#: 2d2af19b49734f5ca224127927653653 3dc8c4347fff4eacacfee8305afba39e -#: 6a010522357e475db129e50d97f9653c a483406eb0d6449eacb7b55c07c28e9c -#: b12d7fb4a8dc4480b63556fafb117f08 f49b82712c4e4d2f93139c250d4eca77 +#: 50f9cf45632a4d39a9506a9f6da683b0 55f46e52d0444830a462098546462025 +#: 5d97e61381564d95b5c8546538cc8c5a b85789ce303e4b4bb005c366ca41f135 +#: d47989de58b542829ea0917a2e169509 f40b2359681f45fba30186e367428633 msgid "MicroPython Code Block:" msgstr "MicroPython 代码块:" -#: ../../en/module/gateway_h2.rst:55 e4eb486c928f4e51bf676f19c1dd4ffb +#: ../../en/module/gateway_h2.rst:55 a161b02b82324c6e9e2f587c9c686720 msgid "**API**" msgstr "API应用" -#: ../../en/module/gateway_h2.rst:58 a568092b9dc04fe492b4155ed9115958 +#: ../../en/module/gateway_h2.rst:58 3abd5ed4cc8545d29bb56145409a54cf msgid "GatewayH2Module" msgstr "" -#: ../../en/module/gateway_h2.rst:62 8537114f70814414b3f58c2955b0d65a +#: ../../en/module/gateway_h2.rst:62 bf24ade126444d74aab6c4942412a571 msgid "Create an GatewayH2Module object." msgstr "创建一个 GatewayH2Module 对象。" -#: ../../en/module/gateway_h2.rst 3070fc6f968a43c1ba0c4ae56a69a0a0 -#: 44b7126bac6f46c08bdc85044234a25b 62583fcd99b444caacf9fe153482ce32 -#: 9309b42129d2473f9272d571e9d00dd6 +#: ../../en/module/gateway_h2.rst 18a3578e4bd3479fb2bafe96e26c78ba +#: 734839f039724b0e843f31905012a47d b8cdfcdca9824a11b6b9cfcfad80a60c +#: c79f089a0c2747a08dc25f76922533d1 msgid "Parameters" msgstr "" -#: ../../en/module/gateway_h2.rst:64 e02ea7abe71a4590b0d46ad0df2d1f4a +#: ../../en/module/gateway_h2.rst:64 20f3c0c352fc44b49aa2d10729f43888 msgid "UART id." msgstr "UART 端口号。" -#: ../../en/module/gateway_h2.rst:65 2127f6d4cade47f59a3f510577295eff +#: ../../en/module/gateway_h2.rst:65 9da76a8314354eb2ae3c2fe1f4cae457 msgid "the UART TX pin." msgstr "UART 发送引脚。" -#: ../../en/module/gateway_h2.rst:66 1ddf0cad1349499baeaf41483ab6273b +#: ../../en/module/gateway_h2.rst:66 0344a6e28bd24eac989d34005dc9a02e msgid "the UART RX pin." msgstr "UART 接收引脚。" #: ../../en/module/gateway_h2.rst:70 ../../en/module/gateway_h2.rst:89 -#: 9111b896e2014345b8c5d1b25f8bd7e5 ca24a8deff564343a5d4449648c11981 +#: 6023dcfc6afe4a85a7d5dc89ba3a2bc5 602a8c18b92a4424979c29ce3b77a0d6 msgid "|init.png|" msgstr "" -#: ../../en/refs/module.gateway_h2.ref:6 3b908e3c324747e68de2b3a70d549c7a -#: bbffccefaf724868b68e4312323f9f0a +#: ../../en/refs/module.gateway_h2.ref:6 034b356e5bd64fb78ff4030872783bce +#: 5c6ff76bf01b413a9fbf1e5bdb2df8cd msgid "init.png" msgstr "" -#: ../../en/module/gateway_h2.rst:82 d5628482070d4d16aa9d18b95227efb4 +#: ../../en/module/gateway_h2.rst:82 94149be51ec24ee49f74aebd89317d2f msgid "Create Switch Endpoint." msgstr "创建 Switch Endpoint。" -#: ../../en/module/gateway_h2.rst 2231cc84a222440aa5447f871a297331 +#: ../../en/module/gateway_h2.rst 247f973d0a8240218b56ccc4496f781b msgid "returns SwitchEndpoint" msgstr "" -#: ../../en/module/gateway_h2.rst:84 98d661a71d9b4242981cb7748aaa585e +#: ../../en/module/gateway_h2.rst:84 cab664d2da194982b44e1019d30b4004 msgid "zigbee switch endpoint object." msgstr "" -#: ../../en/module/gateway_h2.rst 05a8105697c74bad8f7b10874d473842 +#: ../../en/module/gateway_h2.rst ad1bf522b3fb484bad4bd5718fea0556 msgid "return type" msgstr "" -#: ../../en/module/gateway_h2.rst:85 e1b84a056feb4b05a6c73841e7881726 +#: ../../en/module/gateway_h2.rst:85 ea97719c4e914c298711a5371e190ae2 msgid "SwitchEndpoint" msgstr "" -#: ../../en/module/gateway_h2.rst:99 30b6ea2f2b3a46b68a65b55f74fd7a4b -msgid "Return by GatewayH2Module.create_switch_endpoint()" -msgstr "由 GatewayH2Module.create_switch_endpoint() 返回" +#: ../../en/module/gateway_h2.rst:99 dc2fc7ec3b5e470cbdb30cb48f3a4cb0 +msgid "" +"Return by GatewayH2Module.create_switch_endpoint() or " +"GatewayH2Unit.create_switch_endpoint()" +msgstr "由 GatewayH2Module.create_switch_endpoint() 或者 GatewayH2Unit.create_switch_endpoint() 返回" -#: ../../en/module/gateway_h2.rst:103 66b8ead44d6a49c9ac840db2cfcc9f77 +#: ../../en/module/gateway_h2.rst:103 0723a76de1044081b0478570b1d8c7a9 msgid "Turn on the light." msgstr "开灯" #: ../../en/module/gateway_h2.rst:105 ../../en/module/gateway_h2.rst:126 -#: ../../en/module/gateway_h2.rst:147 286135c737584b74b09f445aeab7d6a3 -#: 2ea0861bb4ab402fa2e3d86634d90e04 f5266d4fa2de4bb4a542508621eab5c6 +#: ../../en/module/gateway_h2.rst:147 9c0576d9b56f49938c927fb343a302aa +#: d48d4ea6b07248c280fdac5771b33c13 f045a9701f194796a25a9161029631e6 msgid "The device address (optional)." msgstr "设备地址(可选)" -#: ../../en/module/gateway_h2.rst:107 914a3e1934344784aa8329a40222cf5a +#: ../../en/module/gateway_h2.rst:107 99de559ec82b4a61b955df429f793b57 msgid "If called as ``on()``, turn on all devices." msgstr "调用 ``on()`` 打开所有设备。" -#: ../../en/module/gateway_h2.rst:108 f134e000848d4c4a9bd213ef83aa2340 +#: ../../en/module/gateway_h2.rst:108 3f4666b72140449ba6dfc3e191caebf2 msgid "If called as ``on(addr)``, turn on special address devices." msgstr "调用 ``on(addr)`` 打开地址为 addr 的设备。" -#: ../../en/module/gateway_h2.rst:112 367452a58d1044e5a6981ba6e68cf4f1 +#: ../../en/module/gateway_h2.rst:112 2e0534793ecf49628007be4d9665f2ad msgid "|on.png| |all_on.png|" msgstr "" -#: ../../en/refs/module.gateway_h2.ref:7 475ea9a989aa4799ad950140b9abba0f +#: ../../en/refs/module.gateway_h2.ref:7 47b37f6737294553bc03f01f9db41187 msgid "on.png" msgstr "" -#: ../../en/refs/module.gateway_h2.ref:8 16e38960f83a496e832af54799473f09 +#: ../../en/refs/module.gateway_h2.ref:8 b88cc7b4d0eb408989318117e6d8c764 msgid "all_on.png" msgstr "" -#: ../../en/module/gateway_h2.rst:124 47691d2689cd41f5b7a5edd75dac69f7 +#: ../../en/module/gateway_h2.rst:124 94e69dc4f1a8488d9f228b96f43459c8 msgid "Turn off the light." msgstr "关灯" -#: ../../en/module/gateway_h2.rst:128 cbda5ec47d154658908c3e32b290e909 +#: ../../en/module/gateway_h2.rst:128 42d1cce7a982432bb787be76831327d0 msgid "If called as ``off()``, turn off all devices." msgstr "调用 ``off()`` 关闭所有设备。" -#: ../../en/module/gateway_h2.rst:129 4d7c81222dd843cbb6a8dbe303122419 +#: ../../en/module/gateway_h2.rst:129 cd943dc9c57a41ec9ca959f8f668b3cc msgid "If called as ``off(addr)``, turn off special address devices." msgstr "调用 ``off(addr)`` 关闭地址为 addr 的设备。" -#: ../../en/module/gateway_h2.rst:133 1ad21bd02ced4aa8aa8fa7494ee296b2 +#: ../../en/module/gateway_h2.rst:133 f7a5bac5b0db4f99a9841929d79aaaef msgid "|off.png| |all_off.png|" msgstr "" -#: ../../en/refs/module.gateway_h2.ref:9 ad474273949c49359db12d470fdd7fb7 +#: ../../en/refs/module.gateway_h2.ref:9 71df67820f71447aa389fdc8e78e69dc msgid "off.png" msgstr "" -#: ../../en/refs/module.gateway_h2.ref:10 ffcad8fcbb674e62a9a940f4853ab268 +#: ../../en/refs/module.gateway_h2.ref:10 444480478b9f49d7bceeb63d7d563d3e msgid "all_off.png" msgstr "" -#: ../../en/module/gateway_h2.rst:145 5db16a9263374b239875e25088de04fd +#: ../../en/module/gateway_h2.rst:145 3cc17e6102d44a48b44cfc319da32019 msgid "Toggle the light state." msgstr "翻转灯状态" -#: ../../en/module/gateway_h2.rst:149 2123233fe6964ee3880b918811db9645 +#: ../../en/module/gateway_h2.rst:149 d006765d4d2340dea679a9844aa64d3d msgid "If called as ``toggle()``, toggle all devices." msgstr "调用 ``toggle()`` 翻转所有设备的状态。" -#: ../../en/module/gateway_h2.rst:150 9e0e58cdbae74ea980c76d76d88c42c0 +#: ../../en/module/gateway_h2.rst:150 d2e3e57ba91c44a89b0edcf43547dec6 msgid "If called as ``toggle(addr)``, toggle special address devices." msgstr "调用 ``toggle(addr)`` 翻转地址为 addr 的设备的状态。" -#: ../../en/module/gateway_h2.rst:154 eb61c645907a424f9d3eceabf08ef0f2 +#: ../../en/module/gateway_h2.rst:154 56b680fd12e248979a95d174f172a6fa msgid "|toggle.png| |all_toggle.png|" msgstr "" -#: ../../en/refs/module.gateway_h2.ref:11 a2eacaac1b1d4c8cb38e1ee5195768a8 +#: ../../en/refs/module.gateway_h2.ref:11 47d5a08b9dcd4e4c901b3bc23f988b7d msgid "toggle.png" msgstr "" -#: ../../en/refs/module.gateway_h2.ref:12 f5b4821391ed49a79db61d3c968ac3bb +#: ../../en/refs/module.gateway_h2.ref:12 a36b7bac5c93400d830ab6390e655294 msgid "all_toggle.png" msgstr "" diff --git a/docs/locales/zh_CN/LC_MESSAGES/units/gateway_h2.po b/docs/locales/zh_CN/LC_MESSAGES/units/gateway_h2.po new file mode 100644 index 00000000..acbc6f72 --- /dev/null +++ b/docs/locales/zh_CN/LC_MESSAGES/units/gateway_h2.po @@ -0,0 +1,197 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2016 - 2025 M5Stack Technology Co., Ltd +# This file is distributed under the same license as the UIFlow2 Programming +# Guide package. +# FIRST AUTHOR , 2025. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: UIFlow2 Programming Guide \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-05-16 14:43+0800\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: zh_CN\n" +"Language-Team: zh_CN \n" +"Plural-Forms: nplurals=1; plural=0;\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.16.0\n" + +#: ../../en/units/gateway_h2.rst:2 00fd5c9b1bb640b08a8136df45f53064 +msgid "GatewayH2 Unit" +msgstr "" + +#: ../../en/units/gateway_h2.rst:8 af38035430784624b2bb20f3f36dd6cf +msgid "" +"This library is the driver for Unit Gateway H2, and the unit communicates" +" via UART." +msgstr "这个库是 Unit Gateway H2 的驱动,该模块使用 UART 通信。" + +#: ../../en/units/gateway_h2.rst:10 bf8ee5ec8951475b8f1138d3a6b8f3a9 +msgid "Support the following products:" +msgstr "支持以下产品:" + +#: ../../en/units/gateway_h2.rst:12 faf931d7845d49eda50fb955b3cf352a +msgid "|Unit Gateway H2|" +msgstr "" + +#: ../../en/refs/unit.gateway_h2.ref a676443a238440a391d26f5c38d559e6 +msgid "Unit Gateway H2" +msgstr "" + +#: ../../en/units/gateway_h2.rst:14 53917815b30c4d408e9915a275a7bad2 +msgid "" +"When using this unit, you need to flash the NCP firmware to the unit. For" +" details, refer to the `ESP Zigbee NCP " +"`_" +" documentation." +msgstr "" +"当使用此模块时,需要先给模块烧录 NCP 固件,详情请查看 `ESP Zigbee NCP " +"`_" + +#: ../../en/units/gateway_h2.rst:17 94d3bed7cf134d988fd819a6f70b6d95 +msgid "UiFlow2 Example" +msgstr "UiFlow2 应用示例" + +#: ../../en/units/gateway_h2.rst:20 ../../en/units/gateway_h2.rst:40 +#: c100ad358015413a9eeae7b616f664e5 c782745885a94a2e82578d001b705cb8 +msgid "Switch Control" +msgstr "开关控制" + +#: ../../en/units/gateway_h2.rst:22 00ff7b965c6b495b80bc9cbbf18e9024 +msgid "" +"To use this example, you need to flash this program onto an ESP32C6 or " +"similar unit as a light node device. For details, refer to " +"`HA_on_off_light `_" +msgstr "" +"要使用此示例,您还需要 ESP32C6 或类似模块,作为灯节点设备。详情请查看 `HA_on_off_light " +"`_" + +#: ../../en/units/gateway_h2.rst:24 c1cdce59267a41db868d2edc82d072fb +msgid "Open the |cores3_switch_endpoint_example.m5f2| project in UiFlow2." +msgstr "在 UiFlow2 上打开 |cores3_switch_endpoint_example.m5f2| 项目。" + +#: ../../en/units/gateway_h2.rst:26 ../../en/units/gateway_h2.rst:42 +#: d4dd1a358ed6427cbe642b13b71232fc d54e4bb7a66146788f97928b97fb6212 +msgid "" +"The example demonstrates group control and targeted device operation for " +"light nodes through SwitchEndpoint of Gateway H2 unit." +msgstr "示例程序演示通过 Gateway H2 模块的 SwitchEndpoint 对灯节点进开关控制。" + +#: ../../en/units/gateway_h2.rst:28 ../../en/units/gateway_h2.rst:67 +#: ../../en/units/gateway_h2.rst:86 193f88b522af4785885e96c300e6f838 +#: ae55947be3f24026a61c2e1a9a3a6152 +msgid "UiFlow2 Code Block:" +msgstr "UiFlow2 代码块:" + +#: ../../en/units/gateway_h2.rst:30 16f5936162be481291bfa8b30a9cb6bd +msgid "|cores3_switch_endpoint_example.png|" +msgstr "" + +#: ../../en/refs/unit.gateway_h2.ref:13 2e8ac7f831b24a95af782ccf576998b8 +msgid "cores3_switch_endpoint_example.png" +msgstr "" + +#: ../../en/units/gateway_h2.rst:32 ../../en/units/gateway_h2.rst:50 +#: 3977fdae371c4c95bfdda9d35e87cf98 8240f07386a1415fbae1623b6a7c50ef +msgid "Example output:" +msgstr "示例输出:" + +#: ../../en/units/gateway_h2.rst:34 ../../en/units/gateway_h2.rst:52 +#: 2ec875f4a97345d6bf485b351e286274 5401194be05446c9809c4f8cdfcde14a +msgid "None" +msgstr "无" + +#: ../../en/units/gateway_h2.rst:37 a627431acfff4896bd66d73d9937ad43 +msgid "MicroPython Example" +msgstr "MicroPython 应用示例" + +#: ../../en/units/gateway_h2.rst:44 ../../en/units/gateway_h2.rst:71 +#: ../../en/units/gateway_h2.rst:90 1a396482ee65498fb4498a3df629375c +#: 7779328d589041b78bdc8300d35d278d +msgid "MicroPython Code Block:" +msgstr "MicroPython 代码块:" + +#: ../../en/units/gateway_h2.rst:55 3d736c5b33284573bf88239548edfbb6 +msgid "**API**" +msgstr "API应用" + +#: ../../en/units/gateway_h2.rst:58 03f47d1842694c7da9d5ad7152b106c7 +msgid "GatewayH2Unit" +msgstr "" + +#: ../../en/units/gateway_h2.rst:62 12ba050162674d6e8b58734725b311e8 +msgid "Create an GatewayH2Unit object." +msgstr "创建一个 GatewayH2Unit 对象。" + +#: ../../en/units/gateway_h2.rst 1e635756d7a74411b5f9ee3fee2c67e1 +msgid "Parameters" +msgstr "" + +#: ../../en/units/gateway_h2.rst:64 e186baa1fc72459fab8b63040bd70375 +msgid "The UART ID for communication with the GatewayH2 Unit. It can be 1, 2." +msgstr "与 GatewayH2 单元通信所使用的 UART 编号,可以为 1 或 2。" + +#: ../../en/units/gateway_h2.rst:65 2599ea56e7304b7392af2391c8d6deef +msgid "A list or tuple containing the TX and RX pins for UART communication." +msgstr "一个包含用于 UART 通信的 TX 和 RX 引脚的列表或元组。" + +#: ../../en/units/gateway_h2.rst:69 ../../en/units/gateway_h2.rst:88 +#: 61dfa00bda83435dbffcd6a828fd4e0c +msgid "|init.png|" +msgstr "" + +#: ../../en/refs/unit.gateway_h2.ref:6 97b5d00ff803403bb697aa57120f679c +msgid "init.png" +msgstr "" + +#: ../../en/units/gateway_h2.rst:81 ef16fa07d97a477a9ed9f4ebff04be59 +msgid "Create Switch Endpoint." +msgstr "创建 Switch Endpoint。" + +#: ../../en/units/gateway_h2.rst b14dc63fd9924d8994bf7849aed72520 +msgid "returns SwitchEndpoint" +msgstr "" + +#: ../../en/units/gateway_h2.rst:83 376ea1f6e02b4c548ccd15ea8c971674 +msgid "zigbee switch endpoint object." +msgstr "zigbee 开关节点对象。" + +#: ../../en/units/gateway_h2.rst c16b42954f754494bf5dabb42e488316 +msgid "return type" +msgstr "" + +#: ../../en/units/gateway_h2.rst:84 a811f82b732645b984ae0401406c6fb1 +#, fuzzy +msgid "SwitchEndpoint" +msgstr "创建 Switch Endpoint。" + +#: ../../en/units/gateway_h2.rst:96 95fa2b2ca8b44bd1bc8b176794afecd2 +msgid "Refer to :ref:`switchendpoint` for more details about SwitchEndpoint." +msgstr "请参阅 :ref:`switchendpoint` 了解 SwitchEndpoint 的详细信息。" + +#~ msgid "Parameters" +#~ msgstr "" + +#~ msgid "" +#~ "`SwitchEndpoint `_" +#~ msgstr "" + +#~ msgid "param int id" +#~ msgstr "" + +#~ msgid "param port" +#~ msgstr "" + +#~ msgid "ref" +#~ msgstr "" + +#~ msgid "``class:: SwitchEndpoint``" +#~ msgstr "创建 Switch Endpoint。" + diff --git a/examples/unit/gateway_h2/cores3_switch_endpoint_example.m5f2 b/examples/unit/gateway_h2/cores3_switch_endpoint_example.m5f2 new file mode 100644 index 00000000..e802f11f --- /dev/null +++ b/examples/unit/gateway_h2/cores3_switch_endpoint_example.m5f2 @@ -0,0 +1 @@ +{"version":"V2.0","versionNumber":"V2.2.6","type":"cores3","components":[{"name":"screen","type":"screen","layer":0,"screenId":"builtin","screenName":"","id":"__cores3_screen","createTime":1747284501361,"x":0,"y":0,"width":320,"height":240,"backgroundColor":"#222222","size":0,"isSelected":true},{"name":"title0","type":"title","layer":1,"screenId":"builtin","screenName":"","id":"uE9g@p^91ez9h#T*","createTime":1747284774838,"x":0,"y":0,"color":"#ffffff","backgroundColor":"#0000FF","text":"GatewayH2Unit Example","textOffset":3,"font":"Widgets.FONTS.DejaVu18","isSelected":false},{"name":"label0","type":"label","layer":2,"screenId":"builtin","screenName":"","id":"m^&ylK1QQ!LOGYHZ","createTime":1747284812176,"x":2,"y":26,"color":"#ffffff","backgroundColor":"#222222","text":"if has device connect","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false},{"name":"label1","type":"label","layer":3,"screenId":"builtin","screenName":"","id":"qe5%WMfErHm6DwUO","createTime":1747284813634,"x":2,"y":50,"color":"#ffffff","backgroundColor":"#222222","text":"press the power button toggle","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false},{"name":"label2","type":"label","layer":4,"screenId":"builtin","screenName":"","id":"mO=V8mTVC&2%Et$7","createTime":1747284857012,"x":2,"y":90,"color":"#ffffff","backgroundColor":"#222222","text":"connect device: ","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false},{"name":"label_addr","type":"label","layer":5,"screenId":"builtin","screenName":"","id":"vN5kYG0bDy%QJ=rI","createTime":1747284877763,"x":5,"y":115,"color":"#00ff00","backgroundColor":"#222222","text":"None","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false}],"resources":[{"hardware":["hardware_button","hardware_pin_button","imu","speaker","touch","als","mic"]},{"unit":["unit_gateway_h2"]}],"units":[{"type":"unit_gateway_h2","name":"gateway_h2_0","portList":["A","B","C","Custom"],"portType":"A","userPort":[22,21],"id":"aGOw2uzj`sr-iibm","createTime":1747291259664,"initBlockId":"N)iC~fq#Ay/RK#$/m8uq"}],"hats":[],"bases":[],"i2cs":[],"blockly":"device_addrdevice_countdevice_listtruegateway_h2_01device_count0device_listtruegateway_h2_0device_addrhello M5device_addrEQFIRSTdevice_listdevice_addr0INSERTLASTdevice_listdevice_addrdevice_countADD1device_count1label_addrLabelnew device addr: device_addrgateway_h2_00device_addrBtnPWRWAS_CLICKEDdevice_listgateway_h2_0","screen":[{"simulationName":"Built-in","type":"builtin","width":320,"height":240,"scale":0.78,"screenName":"","blockId":"","screenColorType":0,"id":"builtin","createTime":1747284501361}],"logicWhenNum":0,"customList":[]} \ No newline at end of file diff --git a/examples/unit/gateway_h2/cores3_switch_endpoint_example.py b/examples/unit/gateway_h2/cores3_switch_endpoint_example.py new file mode 100644 index 00000000..73f6281d --- /dev/null +++ b/examples/unit/gateway_h2/cores3_switch_endpoint_example.py @@ -0,0 +1,130 @@ +# SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import os, sys, io +import M5 +from M5 import * +from unit import GatewayH2Unit + + +title0 = None +label0 = None +label1 = None +label2 = None +label_addr = None +gateway_h2_0 = None +gateway_h2_0_ep = None +device_addr = None +device_count = None +device_list = None + + +def first_index(my_list, elem): + try: + index = my_list.index(elem) + 1 + except: + index = 0 + return index + + +def gateway_h2_0_ep_bind_event(addr): + global \ + title0, \ + label0, \ + label1, \ + label2, \ + label_addr, \ + gateway_h2_0, \ + gateway_h2_0_ep, \ + device_addr, \ + device_count, \ + device_list + device_addr = addr + print(device_addr) + if first_index(device_list, device_addr) == 0: + device_list.append(device_addr) + device_count = device_count + 1 + label_addr.setText(str((str("new device addr: ") + str(device_addr)))) + gateway_h2_0_ep.off(device_addr) + + +def btnPWR_wasClicked_event(state): + global \ + title0, \ + label0, \ + label1, \ + label2, \ + label_addr, \ + gateway_h2_0, \ + gateway_h2_0_ep, \ + device_addr, \ + device_count, \ + device_list + if not not len(device_list): + gateway_h2_0_ep.toggle() + + +def setup(): + global \ + title0, \ + label0, \ + label1, \ + label2, \ + label_addr, \ + gateway_h2_0, \ + gateway_h2_0_ep, \ + device_addr, \ + device_count, \ + device_list + + M5.begin() + Widgets.fillScreen(0x222222) + title0 = Widgets.Title("GatewayH2Unit Example", 3, 0xFFFFFF, 0x0000FF, Widgets.FONTS.DejaVu18) + label0 = Widgets.Label( + "if has device connect", 2, 26, 1.0, 0xFFFFFF, 0x222222, Widgets.FONTS.DejaVu18 + ) + label1 = Widgets.Label( + "press the power button toggle", 2, 50, 1.0, 0xFFFFFF, 0x222222, Widgets.FONTS.DejaVu18 + ) + label2 = Widgets.Label( + "connect device: ", 2, 90, 1.0, 0xFFFFFF, 0x222222, Widgets.FONTS.DejaVu18 + ) + label_addr = Widgets.Label("None", 5, 115, 1.0, 0x00FF00, 0x222222, Widgets.FONTS.DejaVu18) + + BtnPWR.setCallback(type=BtnPWR.CB_TYPE.WAS_CLICKED, cb=btnPWR_wasClicked_event) + + gateway_h2_0 = GatewayH2Unit(1, port=(1, 2)) + gateway_h2_0_ep = gateway_h2_0.create_switch_ep() + gateway_h2_0_ep.set_bind_callback(gateway_h2_0_ep_bind_event) + device_count = 0 + device_list = [] + + +def loop(): + global \ + title0, \ + label0, \ + label1, \ + label2, \ + label_addr, \ + gateway_h2_0, \ + gateway_h2_0_ep, \ + device_addr, \ + device_count, \ + device_list + M5.update() + + +if __name__ == "__main__": + try: + setup() + while True: + loop() + except (Exception, KeyboardInterrupt) as e: + try: + from utility import print_error_msg + + print_error_msg(e) + except ImportError: + print("please update to latest firmware") diff --git a/m5stack/libs/unit/__init__.py b/m5stack/libs/unit/__init__.py index 975d487a..72028eff 100644 --- a/m5stack/libs/unit/__init__.py +++ b/m5stack/libs/unit/__init__.py @@ -49,6 +49,7 @@ "FanUnit": "fan", "FingerUnit": "finger", "FlashLightUnit": "flash_light", + "GatewayH2Unit": "gateway_h2", "GestureUnit": "gesture", "GlassUnit": "glass", "Glass2Unit": "glass2", diff --git a/m5stack/libs/unit/gateway_h2.py b/m5stack/libs/unit/gateway_h2.py new file mode 100644 index 00000000..115a123e --- /dev/null +++ b/m5stack/libs/unit/gateway_h2.py @@ -0,0 +1,26 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import esp_zigbee_host +from esp_zigbee_host import SwitchEndpoint +import sys + +if sys.platform != "esp32": + from typing import Literal + + +class GatewayH2Unit: + """Create GatewayH2Unit object. + + :param int id: The UART ID for communication with the GatewayH2 Unit. It can be 1, 2. + :param port: A list or tuple containing the TX and RX pins for UART communication. + """ + + def __init__(self, id: Literal[1, 2] = 2, port: list | tuple = None): + self.id = id + self.rx = port[0] # 交叉 + self.tx = port[1] + + def create_switch_ep(self): + return SwitchEndpoint(id=self.id, tx=self.tx, rx=self.rx) diff --git a/m5stack/libs/unit/manifest.py b/m5stack/libs/unit/manifest.py index f1dc5116..7a4d27e3 100644 --- a/m5stack/libs/unit/manifest.py +++ b/m5stack/libs/unit/manifest.py @@ -47,6 +47,7 @@ "fan.py", "finger.py", "flash_light.py", + "gateway_h2.py", "gesture.py", "glass.py", "glass2.py", From dff9bb67835fb167a332e9bc7d3d94ecdce57348 Mon Sep 17 00:00:00 2001 From: lbuque Date: Fri, 16 May 2025 15:18:42 +0800 Subject: [PATCH 089/322] version.text: Update to V2.2.7. Signed-off-by: lbuque --- m5stack/version.txt | 2 +- third-party/version.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/m5stack/version.txt b/m5stack/version.txt index c5ccc058..92d0d3c8 100644 --- a/m5stack/version.txt +++ b/m5stack/version.txt @@ -1 +1 @@ -V2.2.6 \ No newline at end of file +V2.2.7 \ No newline at end of file diff --git a/third-party/version.txt b/third-party/version.txt index c5ccc058..92d0d3c8 100644 --- a/third-party/version.txt +++ b/third-party/version.txt @@ -1 +1 @@ -V2.2.6 \ No newline at end of file +V2.2.7 \ No newline at end of file From 7ba5dc4aaeccbfe75e9e97707e2ea7ea875e6b21 Mon Sep 17 00:00:00 2001 From: lbuque Date: Wed, 7 May 2025 15:52:09 +0800 Subject: [PATCH 090/322] libs/base: Optimizing Echo Base. Signed-off-by: lbuque --- docs/en/base/echo.rst | 142 +++-- docs/en/refs/base.echo.ref | 58 ++- docs/en/refs/module.audio.ref | 6 +- docs/locales/zh_CN/LC_MESSAGES/base/echo.po | 488 +++++++++++++++--- .../base/echo/atoms3_play_wav_example.m5f2 | 1 + examples/base/echo/atoms3_play_wav_example.py | 42 ++ .../atoms3_playback_controls_example.m5f2 | 1 + .../echo/atoms3_playback_controls_example.py | 47 ++ .../echo/atoms3_record_audio_example.m5f2 | 1 + .../base/echo/atoms3_record_audio_example.py | 45 ++ m5stack/cmodules/m5audio2/audio2_player.c | 11 +- m5stack/cmodules/m5audio2/audio2_recorder.c | 41 +- m5stack/cmodules/m5audio2/i2s_helper.c | 3 + m5stack/libs/base/__init__.py | 2 +- m5stack/libs/base/echo.py | 399 ++++++++++++-- m5stack/libs/driver/es8311/__init__.py | 6 + m5stack/libs/module/audio.py | 2 +- 17 files changed, 1085 insertions(+), 210 deletions(-) create mode 100644 examples/base/echo/atoms3_play_wav_example.m5f2 create mode 100644 examples/base/echo/atoms3_play_wav_example.py create mode 100644 examples/base/echo/atoms3_playback_controls_example.m5f2 create mode 100644 examples/base/echo/atoms3_playback_controls_example.py create mode 100644 examples/base/echo/atoms3_record_audio_example.m5f2 create mode 100644 examples/base/echo/atoms3_record_audio_example.py diff --git a/docs/en/base/echo.rst b/docs/en/base/echo.rst index 8667207f..ec90d656 100644 --- a/docs/en/base/echo.rst +++ b/docs/en/base/echo.rst @@ -9,7 +9,7 @@ The following products are supported: |Atomic Echo Base| -Below is the detailed support for Speaker on the host: +Below is the detailed support for Atomic Echo Base on the host: .. table:: :widths: auto @@ -39,89 +39,123 @@ Below is the detailed support for Speaker on the host: .. |O| unicode:: U+2B55 -Micropython Example: +UiFlow2 Example +--------------- - .. literalinclude:: ../../../examples/base/echo/atoms3_echo_example.py - :language: python - :linenos: +Play WAV file +^^^^^^^^^^^^^ + +Open the |atoms3_play_wav_example.m5f2| project in UiFlow2. + +.. only:: builder_html + + :download:`66.wav <../../../examples/module/audio/66.wav>` + +This example reads an audio file from the file system and plays it. + +UiFlow2 Code Block: + |atoms3_play_wav_example.png| -UIFLOW2 Example: +Example output: - |example.png| + None +Playback Controls +^^^^^^^^^^^^^^^^^ + +Open the |atoms3_playback_controls_example.m5f2| project in UiFlow2. + .. only:: builder_html - |atoms3_echo_example.m5f2| + :download:`66.wav <../../../examples/module/audio/66.wav>` + +This example demonstrates how to control playback using the AtomicEchoBase class. + +Play the audio for 1 second, pause for 1 second, and then resume playing. +UiFlow2 Code Block: -class ATOMEchoBase ------------------- + |atoms3_playback_controls_example.png| -Constructors ------------- +Example output: -.. class:: ATOMEchoBase(i2c, address: int = 0x18, i2s_port: int = 1, sample_rate: int = 44100, i2s_sck: int = -1, i2s_ws: int = -1, i2s_di: int = -1, i2s_do: int = -1) + None - Create an ATOMEchoBase object. - :param I2C i2c: I2C object - :param int address: The I2C address of the ES8311. Default is 0x18. - :param int i2s_port: The I2S port number. Default is 1. - :param int sample_rate: The sample rate of the audio. Default is 44100. - :param int i2s_sck: The I2S SCK pin. Default is -1. - :param int i2s_ws: The I2S WS pin. Default is -1. - :param int i2s_di: The I2S DI pin. Default is -1. - :param int i2s_do: The I2S DO pin. Default is -1. +Record Audio +^^^^^^^^^^^^ - UIFLOW2: +Open the |atoms3_record_audio_example.m5f2| project in UiFlow2. - |init.png| +This example records audio from the microphone and saves it to a PCM buffer, then plays it out through the speaker. - Micropython:: +UiFlow2 Code Block: - from hardware import I2C - from hardware import Pin - from base import ATOMEchoBase + |atoms3_record_audio_example.png| - # atom echo - i2c1 = I2C(1, scl=Pin(21), sda=Pin(25), freq=100000) - echo = ATOMEchoBase(i2c1, address=0x18, i2s_port=1, sample_rate=44100, i2s_sck=33, i2s_ws=19, i2s_di=23, i2s_do=22) +Example output: - # atom lite - i2c1 = I2C(1, scl=Pin(21), sda=Pin(25), freq=100000) - echo = ATOMEchoBase(i2c1, address=0x18, i2s_port=1, sample_rate=44100, i2s_sck=33, i2s_ws=19, i2s_di=23, i2s_do=22) + None - # atom matrix - i2c1 = I2C(1, scl=Pin(21), sda=Pin(25), freq=100000) - echo = ATOMEchoBase(i2c1, address=0x18, i2s_port=1, sample_rate=44100, i2s_sck=33, i2s_ws=19, i2s_di=23, i2s_do=22) - # atoms3 / atoms3 lite - i2c1 = I2C(1, scl=Pin(39), sda=Pin(38), freq=100000) - echo = ATOMEchoBase(i2c1, address=0x18, i2s_port=1, sample_rate=44100, i2s_sck=8, i2s_ws=6, i2s_di=7, i2s_do=5) +MicroPython Example +------------------- - # atoms3r / atoms3r-cam / atoms3-ext - i2c1 = I2C(1, scl=Pin(39), sda=Pin(38), freq=100000) - echo = ATOMEchoBase(i2c1, address=0x18, i2s_port=1, sample_rate=44100, i2s_sck=8, i2s_ws=6, i2s_di=7, i2s_do=5) +Play WAV file +^^^^^^^^^^^^^ - echo.speaker.tone(2000, 1000) - echo.speaker.playWavFile('res/audio/66.wav') +This example reads an audio file from the file system and plays it. + +MicroPython Code Block: + + .. literalinclude:: ../../../examples/base/echo/atoms3_playback_controls_example.py + :language: python + :linenos: +Example output: -Attributes ----------- + None -.. attribute:: ATOMEchoBase.speaker - Objects of the Speaker class. +Playback Controls +^^^^^^^^^^^^^^^^^ + +This example demonstrates how to control playback using the AtomicEchoBase class. + +Play the audio for 1 second, pause for 1 second, and then resume playing. + +MicroPython Code Block: + + .. literalinclude:: ../../../examples/base/echo/atoms3_playback_controls_example.py + :language: python + :linenos: + +Example output: + + None + + +Record Audio +^^^^^^^^^^^^ + +This example records audio from the microphone and saves it to a PCM buffer, then plays it out through the speaker. + +MicroPython Code Block: + + .. literalinclude:: ../../../examples/base/echo/atoms3_record_audio_example.py + :language: python + :linenos: - See :ref:`hardware.Speaker.Methods ` for more details on how to use the ATOMEchoBase.speaker properties. +Example output: -.. attribute:: ATOMEchoBase.microphone + None - Objects of the Microphone class. - See :ref:`hardware.Mic.Methods ` for more details on how to use the ATOMEchoBase.microphone properties. +AtomicEchoBase +^^^^^^^^^^^^^^ - .. Note:: Microphone is not quite ready yet. +.. autoclass:: base.echo.AtomicEchoBase + :members: + :member-order: bysource diff --git a/docs/en/refs/base.echo.ref b/docs/en/refs/base.echo.ref index b5a53e8c..68a533e4 100644 --- a/docs/en/refs/base.echo.ref +++ b/docs/en/refs/base.echo.ref @@ -8,11 +8,63 @@ .. |example.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/echo/example.png -.. |atoms3_echo_example.m5f2| raw:: html +.. |atoms3_play_wav_example.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/echo/atoms3_play_wav_example.png +.. |atoms3_playback_controls_example.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/echo/atoms3_playback_controls_example.png +.. |atoms3_record_audio_example.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/echo/atoms3_record_audio_example.png +.. |begin.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/echo/begin.png +.. |begin_return.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/echo/begin_return.png +.. |end.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/echo/end.png +.. |getChannelVolume.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/echo/getChannelVolume.png +.. |getPlayingChannels.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/echo/getPlayingChannels.png +.. |getVolume.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/echo/getVolume.png +.. |getVolumePercentage.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/echo/getVolumePercentage.png +.. |get_volume.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/echo/get_volume.png +.. |isEnabled.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/echo/isEnabled.png +.. |isPlaying.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/echo/isPlaying.png +.. |isRunning.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/echo/isRunning.png +.. |pause.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/echo/pause.png +.. |pcm_buffer.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/echo/pcm_buffer.png +.. |playRaw.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/echo/playRaw.png +.. |playRaw_return.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/echo/playRaw_return.png +.. |playWav.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/echo/playWav.png +.. |playWavFile.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/echo/playWavFile.png +.. |play_raw.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/echo/play_raw.png +.. |play_wav.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/echo/play_wav.png +.. |play_wav_file.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/echo/play_wav_file.png +.. |record.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/echo/record.png +.. |record_wav_file.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/echo/record_wav_file.png +.. |resume.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/echo/resume.png +.. |setAllChannelVolume.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/echo/setAllChannelVolume.png +.. |setChannelVolume.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/echo/setChannelVolume.png +.. |setVolume.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/echo/setVolume.png +.. |setVolumePercentage.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/echo/setVolumePercentage.png +.. |set_volume.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/echo/set_volume.png +.. |stop.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/echo/stop.png +.. |tone.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/echo/tone.png + +.. |atoms3_play_wav_example.m5f2| raw:: html + + + atoms3_play_wav_example.m5f2 + + +.. |atoms3_playback_controls_example.m5f2| raw:: html + + + atoms3_playback_controls_example.m5f2 + + +.. |atoms3_record_audio_example.m5f2| raw:: html - atoms3_echo_example.m5f2 + atoms3_record_audio_example.m5f2 diff --git a/docs/en/refs/module.audio.ref b/docs/en/refs/module.audio.ref index dec6bd58..92bd01b8 100644 --- a/docs/en/refs/module.audio.ref +++ b/docs/en/refs/module.audio.ref @@ -31,7 +31,7 @@ href="https://uiflow2.m5stack.com/?example=https://raw.githubusercontent.com/m5stack/uiflow-micropython/develop/examples/module/audio/cores3_play_wav_example.m5f2" target="_blank" > - ain4_core2_example.m5f2 + cores3_play_wav_example.m5f2 .. |cores3_playback_controls_example.m5f2| raw:: html @@ -40,7 +40,7 @@ href="https://uiflow2.m5stack.com/?example=https://raw.githubusercontent.com/m5stack/uiflow-micropython/develop/examples/module/audio/cores3_playback_controls_example.m5f2" target="_blank" > - ain4_core2_example.m5f2 + cores3_playback_controls_example.m5f2 .. |cores3_record_audio_example.m5f2| raw:: html @@ -49,5 +49,5 @@ href="https://uiflow2.m5stack.com/?example=https://raw.githubusercontent.com/m5stack/uiflow-micropython/develop/examples/module/audio/cores3_record_audio_example.m5f2" target="_blank" > - ain4_core2_example.m5f2 + cores3_record_audio_example.m5f2 diff --git a/docs/locales/zh_CN/LC_MESSAGES/base/echo.po b/docs/locales/zh_CN/LC_MESSAGES/base/echo.po index aa3433af..508cbce8 100644 --- a/docs/locales/zh_CN/LC_MESSAGES/base/echo.po +++ b/docs/locales/zh_CN/LC_MESSAGES/base/echo.po @@ -9,7 +9,7 @@ msgid "" msgstr "" "Project-Id-Version: UIFlow2 Programming Guide \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-01-17 14:57+0800\n" +"POT-Creation-Date: 2025-05-28 17:56+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language: zh_CN\n" @@ -21,182 +21,520 @@ msgstr "" "Generated-By: Babel 2.16.0\n" #: ../../en/base/echo.rst:2 ../../en/base/echo.rst:19 -#: ../../en/refs/base.echo.ref 1672452b1a124d5c92dd43519fba9e86 -#: c307ebf52d5a48b39c14cb2afefe8fd9 ca6c03104f6047f8a418a11acc5c0b0c +#: ../../en/refs/base.echo.ref 5f049f83a46a4b5f8e8240050b257f13 +#: c9a35e466d4642d1b5dc0c2f24ab5e15 ec8081641c774487b4794a4d9207ada2 msgid "Atomic Echo Base" msgstr "" -#: ../../en/base/echo.rst:8 1e77f1dbf760458f85347e8b6f005eae +#: ../../en/base/echo.rst:8 edcd69f7db224417885be92a1bc76f96 msgid "The following products are supported:" -msgstr "" +msgstr "支持以下产品:" -#: ../../en/base/echo.rst:10 8d0f7dd12e644a5b8881a2e770f2ebcc +#: ../../en/base/echo.rst:10 3c75f10fceb648fa938c60a839f62565 msgid "|Atomic Echo Base|" msgstr "" -#: ../../en/base/echo.rst:12 f4bc9b1d9ed449c6aededa13b3a3e07b -msgid "Below is the detailed support for Speaker on the host:" -msgstr "" +#: ../../en/base/echo.rst:12 22d2a6a6af54411190217eef1282cd19 +msgid "Below is the detailed support for Atomic Echo Base on the host:" +msgstr "以下是主机对 Atomic Echo Base 的详细支持:" -#: ../../en/base/echo.rst:19 b74b018952104a2b95b6301441541af8 +#: ../../en/base/echo.rst:19 fc1cc5d593f742d59aca2d01820b6c2d msgid "Controller" msgstr "" -#: ../../en/base/echo.rst:21 6430edc486844513aacda944b4c1c026 +#: ../../en/base/echo.rst:21 43581d4138e644fab1e7f01ae80d872a msgid "Atom Echo" msgstr "" -#: ../../en/base/echo.rst:21 bfe41c41e0644374bba99b4cc7f1574a +#: ../../en/base/echo.rst:21 f30114af282c402d94658e4e92b090ab msgid "|O|" msgstr "" -#: ../../en/base/echo.rst:23 b9e0777744ec4c359c36acdccbedb349 +#: ../../en/base/echo.rst:23 028c3647ca2c4f34a0716840c5bd60a5 msgid "Atom Lite" msgstr "" #: ../../en/base/echo.rst:23 ../../en/base/echo.rst:25 #: ../../en/base/echo.rst:27 ../../en/base/echo.rst:29 #: ../../en/base/echo.rst:31 ../../en/base/echo.rst:33 -#: ../../en/base/echo.rst:35 0d50258cc19540ca847fb7e6ffadd7f6 -#: 2f37fdbdcf9f4ae9b52bb50b2ca70728 3246582f50cf4583b1e151a927ba9480 -#: 63dde4b7f95f44f9b1037ebab10f3661 b654a6d5bff74ca0be2dcd0229048615 -#: decc54e00fc44ae68f5f274b55fc1b53 df829d5bc0584b90ba918e52dcf14dda +#: ../../en/base/echo.rst:35 50188ef8ab184003a37ea52b52f13672 msgid "|S|" msgstr "" -#: ../../en/base/echo.rst:25 863d940c5c76468393e6c179bbc9e439 +#: ../../en/base/echo.rst:25 95fdae71ffd34aeebc4ac573f752664c msgid "Atom Matrix" msgstr "" -#: ../../en/base/echo.rst:27 b86a9d2193c540eb847ad4621fb9f318 +#: ../../en/base/echo.rst:27 d4689bfef8334ffaac2abfa5afba9e1f msgid "AtomS3" msgstr "" -#: ../../en/base/echo.rst:29 d3848673f84a494bb46f8a89a01b2a12 +#: ../../en/base/echo.rst:29 fee8fa2f30e741d797e68f6d18d523f3 msgid "AtomS3 Lite" msgstr "" -#: ../../en/base/echo.rst:31 e13c4aab0950436180f56a1bf0845906 +#: ../../en/base/echo.rst:31 5073dae9dec748289bb56307217bf002 msgid "AtomS3R" msgstr "" -#: ../../en/base/echo.rst:33 bc837a904e1b4d2d9a44559542e7fbdf +#: ../../en/base/echo.rst:33 30c6384d664d4877a73ff47d5783bdbf msgid "AtomS3R-CAM" msgstr "" -#: ../../en/base/echo.rst:35 c4fe3b2b3f834cc7865811382e2983d5 +#: ../../en/base/echo.rst:35 350f587ba22b4d78a4737a51db6c6c6b msgid "AtomS3R-Ext" msgstr "" -#: ../../en/base/echo.rst:42 fed7ed4706684547851f3fb977483a20 -msgid "Micropython Example:" -msgstr "" +#: ../../en/base/echo.rst:43 22a3ceaf3bf7471f891599c815be1863 +msgid "UiFlow2 Example" +msgstr "UiFlow2 应用示例" + +#: ../../en/base/echo.rst:46 ../../en/base/echo.rst:107 +#: 54c8237d02984371be4bcd7d5d4bb0ec f0179591cee64276919c7b3ab051075c +msgid "Play WAV file" +msgstr "播放 WAV 文件" + +#: ../../en/base/echo.rst:48 81de4bc66134413e9312d8a450443ce7 +msgid "Open the |atoms3_play_wav_example.m5f2| project in UiFlow2." +msgstr "在 UiFlow2 中打开 |atoms3_play_wav_example.m5f2| 项目。" + +#: ../../en/base/echo.rst:52 ../../en/base/echo.rst:72 +#: 24c49eedadda43479f1ca9b6837e7c13 d739d86b65de40d6aac623a559540130 +msgid ":download:`66.wav <../../../examples/module/audio/66.wav>`" +msgstr "" + +#: ../../en/base/echo.rst:54 ../../en/base/echo.rst:109 +#: 46ac747d23af49c18cf8c508ab39ff00 563329cfe24e44ec86831fbef7fa3e66 +msgid "This example reads an audio file from the file system and plays it." +msgstr "此示例从文件系统读取音频文件并播放。" + +#: ../../en/base/echo.rst:56 ../../en/base/echo.rst:78 +#: ../../en/base/echo.rst:94 14d051d10b5742b091a958be8fd9024a +#: 1be8dea2b25d4e8a91c0a3384d4de3b2 413193a9b9d34ef087f37a48a9050909 +#: 54a78be711244f3d9273f4041393d5f0 7732f63485d941f2939e654880394885 +#: 81067491d3c0415292b2aa3531d68085 88c875fd42474def9e3be3366f258aee +#: 934d38b178274121b1f29b51d88a89da a6964b5b51764990a4b0666c65d1b34f +#: a75b910ab6fc4ea997fb722a04b2a76d b86e36d162dc4b5f9d216f7fc38a58f1 +#: b86e9a8f9e6c465280bcc335a05123b7 base.echo.AtomicEchoBase.get_volume:5 +#: base.echo.AtomicEchoBase.pause:3 base.echo.AtomicEchoBase.pcm_buffer:5 +#: base.echo.AtomicEchoBase.play_raw:10 base.echo.AtomicEchoBase.play_wav:7 +#: base.echo.AtomicEchoBase.play_wav_file:6 base.echo.AtomicEchoBase.record:8 +#: base.echo.AtomicEchoBase.record_wav_file:9 base.echo.AtomicEchoBase.resume:3 +#: base.echo.AtomicEchoBase.set_volume:5 base.echo.AtomicEchoBase.stop:3 +#: base.echo.AtomicEchoBase.tone:7 c03f45728f5844d0b39ba6b88119dbc7 +#: cefa0b234cad461c975f17edd7ea8f97 eb7b8cd49e0c482dabd765e686c22eb5 of +msgid "UiFlow2 Code Block:" +msgstr "UiFlow2 代码块:" + +#: ../../en/base/echo.rst:58 dcd2e50a203d40c1ac50da9c7c4a55fc +msgid "|atoms3_play_wav_example.png|" +msgstr "" + +#: ../../en/refs/base.echo.ref:11 92f6a4ffadf049769a2169a16d51d693 +msgid "atoms3_play_wav_example.png" +msgstr "" + +#: ../../en/base/echo.rst:60 ../../en/base/echo.rst:82 +#: ../../en/base/echo.rst:98 ../../en/base/echo.rst:117 +#: ../../en/base/echo.rst:135 ../../en/base/echo.rst:151 +#: 34d2e4ee563244db95761d03ad26b3a0 598e46e5189b4c8a9b84d0de0aadaaa3 +#: 87cc6e078b074394984b15b034c4eec0 a01625ac65c9431aac73b95161c442f2 +#: a05170aab8934f22baf5c659a685192f fd83dd12dc444b62b18496c16d53d833 +msgid "Example output:" +msgstr "示例输出:" + +#: ../../en/base/echo.rst:62 ../../en/base/echo.rst:84 +#: ../../en/base/echo.rst:100 ../../en/base/echo.rst:119 +#: ../../en/base/echo.rst:137 ../../en/base/echo.rst:153 +#: 1b9e2cc8a6124fdaaeb8726a9a7ea638 31b7e7134b59496dace42e85aef9b40e +#: 3ef5d48f1fbd42cb90a5515d327e4628 421d134f4d2446f6b14ceb6e91ba021e +#: 4e4fd8a02f0d419dafdd4c374c481fde 5af1524ca2424d729befec81b59bcbca +#: 60360328f3a54462ab76ceaa33e222dd 9783d926f8d9441491352793b5fd740c +#: base.echo.AtomicEchoBase.play_raw:8 base.echo.AtomicEchoBase.play_wav:5 +#: base.echo.AtomicEchoBase.play_wav_file:4 base.echo.AtomicEchoBase.tone:5 +#: d7a696a1f95e49cb85d7ae83b3481db4 f1e13a859add484aae02cbbb111ba1d2 of +msgid "None" +msgstr "" + +#: ../../en/base/echo.rst:66 ../../en/base/echo.rst:123 +#: 7f1130cdc46f4c7e89bc25cdc75586be d632f72886134d86b7589baeba6274a4 +msgid "Playback Controls" +msgstr "播放控制" + +#: ../../en/base/echo.rst:68 99d9427403fa4834ab32b514be75253a +msgid "Open the |atoms3_playback_controls_example.m5f2| project in UiFlow2." +msgstr "在 UiFlow2 中打开 |atoms3_playback_controls_example.m5f2| 项目。" + +#: ../../en/base/echo.rst:74 ../../en/base/echo.rst:125 +#: 38298f145b214d50864291716d313a0e 41a493e1c5684c979ba8b609ed6d6e66 +msgid "" +"This example demonstrates how to control playback using the " +"AtomicEchoBase class." +msgstr "此示例演示如何使用 AtomicEchoBase 类控制播放。" + +#: ../../en/base/echo.rst:76 ../../en/base/echo.rst:127 +#: 6e58ee8a9c384dbd8093e88ffb132177 b9d09c5d6a22491a823d8959c652ef8f +msgid "Play the audio for 1 second, pause for 1 second, and then resume playing." +msgstr "播放音频 1 秒,暂停 1 秒,然后继续播放。" -#: ../../en/base/echo.rst:49 22de2793120145f9bd4db15cee35e5ab -msgid "UIFLOW2 Example:" +#: ../../en/base/echo.rst:80 957d89a93bf2431884060b5592736cce +msgid "|atoms3_playback_controls_example.png|" msgstr "" -#: ../../en/base/echo.rst:51 b94d66061365424d863766a89dee54c9 -msgid "|example.png|" +#: ../../en/refs/base.echo.ref:12 92f6a4ffadf049769a2169a16d51d693 +msgid "atoms3_playback_controls_example.png" msgstr "" -#: ../../en/refs/base.echo.ref:9 475f061a525042ff9b83c97d479bcde9 -msgid "example.png" +#: ../../en/base/echo.rst:88 ../../en/base/echo.rst:141 +#: 25363cb8c3804e618a47d81077953797 da6f6131439e41428604ce5f5c0a28b1 +msgid "Record Audio" +msgstr "录制音频" + +#: ../../en/base/echo.rst:90 7e48d4acd98f44d7b36093341fbf7c6f +msgid "Open the |atoms3_record_audio_example.m5f2| project in UiFlow2." +msgstr "在 UiFlow2 中打开 |atoms3_record_audio_example.m5f2| 项目。" + +#: ../../en/base/echo.rst:92 ../../en/base/echo.rst:143 +#: 13fcc48373af41229ceb4ea84ee6ca81 d77541d8a49c4ad089adcbf5cd1745f4 +msgid "" +"This example records audio from the microphone and saves it to a PCM " +"buffer, then plays it out through the speaker." +msgstr "此示例从麦克风录制音频并将其保存到 PCM 缓冲区,然后通过扬声器播放。" + +#: ../../en/base/echo.rst:96 dcd2e50a203d40c1ac50da9c7c4a55fc +msgid "|atoms3_record_audio_example.png|" msgstr "" -#: ../../en/base/echo.rst:56 1e11ed954c0b4ad59497aebb9c3de286 -msgid "|atoms3_echo_example.m5f2|" +#: ../../en/refs/base.echo.ref:13 92f6a4ffadf049769a2169a16d51d693 +msgid "atoms3_record_audio_example.png" msgstr "" -#: ../../en/base/echo.rst:60 3ea0023021964f0f8b777fcc3239c9d9 -msgid "class ATOMEchoBase" +#: ../../en/base/echo.rst:104 569c69c572b247a59559b1ad3c7c5439 +msgid "MicroPython Example" msgstr "" -#: ../../en/base/echo.rst:63 0ad0c0d3eaef4d51a200259453dd6e41 -msgid "Constructors" +#: ../../en/base/echo.rst:111 ../../en/base/echo.rst:129 +#: ../../en/base/echo.rst:145 569c69c572b247a59559b1ad3c7c5439 +#: base.echo.AtomicEchoBase.get_volume:9 base.echo.AtomicEchoBase.pause:7 +#: base.echo.AtomicEchoBase.pcm_buffer:9 base.echo.AtomicEchoBase.play_raw:14 +#: base.echo.AtomicEchoBase.play_wav:11 +#: base.echo.AtomicEchoBase.play_wav_file:10 base.echo.AtomicEchoBase.record:12 +#: base.echo.AtomicEchoBase.record_wav_file:13 +#: base.echo.AtomicEchoBase.resume:7 base.echo.AtomicEchoBase.set_volume:9 +#: base.echo.AtomicEchoBase.stop:7 base.echo.AtomicEchoBase.tone:11 of +msgid "MicroPython Code Block:" +msgstr "MicroPython 代码块:" + +#: ../../en/base/echo.rst:157 a8c372a35dea4b269ee7d70a9b167657 +msgid "AtomicEchoBase" msgstr "" -#: ../../en/base/echo.rst:67 f2aad4e0be7246bd85977eb4e3937678 -msgid "Create an ATOMEchoBase object." +#: 73fe6a9dcb50490bbdd88f90090f2f48 base.echo.AtomicEchoBase:1 of +msgid "Bases: :py:class:`object`" msgstr "" -#: ../../en/base/echo.rst 6aa28f915b7d4d6299293897cc51f509 +#: 73fe6a9dcb50490bbdd88f90090f2f48 base.echo.AtomicEchoBase:1 of +msgid "Create an AtomicEchoBase object." +msgstr "创建一个 AtomicEchoBase 对象。" + +#: ../../en/base/echo.rst 64e9d105fa2e45ae86fda8a9425076d4 +#: base.echo.AtomicEchoBase.play_raw base.echo.AtomicEchoBase.play_wav +#: base.echo.AtomicEchoBase.play_wav_file base.echo.AtomicEchoBase.record +#: base.echo.AtomicEchoBase.record_wav_file base.echo.AtomicEchoBase.set_volume +#: base.echo.AtomicEchoBase.tone of msgid "Parameters" msgstr "" -#: ../../en/base/echo.rst:69 21c74170d2e04ee2baf29ab3f647bb78 +#: base.echo.AtomicEchoBase:3 d1f6a0a0a4bd47bea72a8019a488c609 of msgid "I2C object" msgstr "" -#: ../../en/base/echo.rst:70 3b1018cd013c44ed87a877478fcebf33 +#: af6de1136e454bd4898c6e0a64a5f610 base.echo.AtomicEchoBase:4 of msgid "The I2C address of the ES8311. Default is 0x18." -msgstr "" +msgstr "ES8311的I2C地址。默认值为0x18。" -#: ../../en/base/echo.rst:71 b1919aee0ad14142a246ce614b6362b4 +#: base.echo.AtomicEchoBase:5 fd283593f6a9455d8a7b573f4371287e of msgid "The I2S port number. Default is 1." -msgstr "" +msgstr "I2S 端口号。默认值为 1。" -#: ../../en/base/echo.rst:72 654eff122e4f4d0faf7248cfef12d64d -msgid "The sample rate of the audio. Default is 44100." -msgstr "" +#: 0cbd496010cc42b18c834492f19c6c84 base.echo.AtomicEchoBase:6 of +msgid "The sample rate of the audio. Default is 16000." +msgstr "音频的采样率。默认值为 16000。" -#: ../../en/base/echo.rst:73 2268b224134940d4b2ccd3bdcfd853f7 +#: 9d7b0aebe6e44ea49d91b2bec685d646 base.echo.AtomicEchoBase:7 of msgid "The I2S SCK pin. Default is -1." -msgstr "" +msgstr "I2S SCK 引脚。默认值为 -1。" -#: ../../en/base/echo.rst:74 897066ce2fac4f18a247d4fac5bdc676 +#: base.echo.AtomicEchoBase:8 e99b94d5f5f54013a4ac3546e93325fa of msgid "The I2S WS pin. Default is -1." -msgstr "" +msgstr "I2S WS 引脚。默认值为 -1。" -#: ../../en/base/echo.rst:75 a37ef0bc0b744d4e9b9b05ce5fde6775 +#: a68760852bba4d7eab02e7a40a199e1b base.echo.AtomicEchoBase:9 of msgid "The I2S DI pin. Default is -1." -msgstr "" +msgstr "I2S DI 引脚。默认值为 -1。" -#: ../../en/base/echo.rst:76 1a5ba9a6cab74a25962c5b4c94962777 +#: 1a86635ec5eb456bab51a5da25d9afae base.echo.AtomicEchoBase:10 of msgid "The I2S DO pin. Default is -1." -msgstr "" +msgstr "I2S DO 引脚。默认值为 -1。" -#: ../../en/base/echo.rst:78 8bc105ae2489499286bd09bc5ba8198b +#: 8a1c994c008b44d28c2f7499f61bf178 base.echo.AtomicEchoBase:12 of msgid "UIFLOW2:" msgstr "" -#: ../../en/base/echo.rst:80 d6b1109c83b3475cb8eef35a87ec9870 +#: 550d7b3601174053a1f8a613d26ca00f base.echo.AtomicEchoBase:14 of msgid "|init.png|" msgstr "" -#: ../../en/refs/base.echo.ref:7 101a832b225a45c983541132d2d10070 +#: ../../en/refs/base.echo.ref:7 0514f1bc25d546039cf0fe510ed20947 msgid "init.png" msgstr "" -#: ../../en/base/echo.rst:82 820309d24a8948c0985f6076405cbffb +#: 7befb6ba64534ec4a86c216b397d31c6 base.echo.AtomicEchoBase:16 of msgid "Micropython::" msgstr "" -#: ../../en/base/echo.rst:113 c3bf1a6107e947e08a93b0ba57fdfcc8 -msgid "Attributes" +#: ../../docstring b8f42cf422644b6f89d7abb93cbaaaec +#: base.echo.AtomicEchoBase.MONO:1 of +msgid "Mono" +msgstr "单声道" + +#: ../../docstring b537faa4b8454451ba10a6ed0078111b +#: base.echo.AtomicEchoBase.STEREO:1 of +msgid "Stereo" +msgstr "立体声" + +#: adcc944b3f6b448c863e0cb31eeac702 base.echo.AtomicEchoBase.get_volume:1 of +msgid "Get the speaker volume level." +msgstr "获取扬声器音量级别。" + +#: 05d3d0b0b6154838af350e17a9c22b82 172d30301e8742c1987b26c117442d40 +#: 6524e46b6a984a52bd7cd29ddc4fc634 ab1efac2881d4e2da3f5ef42dd99f53a +#: base.echo.AtomicEchoBase.get_volume base.echo.AtomicEchoBase.pcm_buffer +#: base.echo.AtomicEchoBase.play_raw base.echo.AtomicEchoBase.play_wav +#: base.echo.AtomicEchoBase.play_wav_file base.echo.AtomicEchoBase.tone +#: de3fdb1b19c1471aa1a4f65394db70c4 f8469f753f0146a0b5cfd07eb9ccf938 of +msgid "Returns" msgstr "" -#: ../../en/base/echo.rst:117 acdab87e9884401bb220057eacb513eb -msgid "Objects of the Speaker class." +#: 2f6c0acf42d144f08cfe274b9596e8ac 9a53641708274719b0194827d325adbe +#: base.echo.AtomicEchoBase.get_volume:3 base.echo.AtomicEchoBase.set_volume:3 +#: of +msgid "The volume level (0-100)." +msgstr "音量级别(0-100)。" + +#: 1ee56f1b4c004174b6ef467c1a40f416 35020b112b8a44f0a36cb2d8d886240d +#: 397cedf59a3e4844adca7709f6f10b93 533aa8c1f2c94a8b8a18ea77b1ef71ca +#: base.echo.AtomicEchoBase.get_volume base.echo.AtomicEchoBase.pause +#: base.echo.AtomicEchoBase.play_raw base.echo.AtomicEchoBase.play_wav +#: base.echo.AtomicEchoBase.play_wav_file base.echo.AtomicEchoBase.tone +#: de68e59691d8402d8b2d465bb487c294 ebfd7a2409d74aa8b8a652f8c50834e6 of +msgid "Return type" msgstr "" -#: ../../en/base/echo.rst:119 10eb2e6f0f6b458cac83f4c53b5f6ab3 +#: 3fbce9c2703d4a58967f1a608416bf0b base.echo.AtomicEchoBase.get_volume:7 of +msgid "|get_volume.png|" +msgstr "" + +#: ../../en/refs/base.echo.ref:21 92f6a4ffadf049769a2169a16d51d693 +msgid "get_volume.png" +msgstr "" + +#: base.echo.AtomicEchoBase.pause:1 c2e4bb8b465c4fc5b7170a3edc7e849d of +msgid "Pause the playback." +msgstr "暂停播放。" + +#: 3fbce9c2703d4a58967f1a608416bf0b base.echo.AtomicEchoBase.pause:5 of +msgid "|pause.png|" +msgstr "" + +#: ../../en/refs/base.echo.ref:25 92f6a4ffadf049769a2169a16d51d693 +msgid "pause.png" +msgstr "" + +#: 26dcda15b6864bb49b2facad3379daf3 base.echo.AtomicEchoBase.pcm_buffer:1 of +msgid "Get the PCM buffer." +msgstr "获取 PCM 缓冲区。" + +#: 62bea066c71143a09e9e7c740f7a93b0 base.echo.AtomicEchoBase.pcm_buffer:3 of +msgid "The PCM buffer." +msgstr "PCM缓冲区。" + +#: 8f7e55889d0e46b4aa9c7870302fc1a8 base.echo.AtomicEchoBase.pcm_buffer:7 of +msgid "|pcm_buffer.png|" +msgstr "" + +#: ../../en/refs/base.echo.ref:26 92f6a4ffadf049769a2169a16d51d693 +msgid "pcm_buffer.png" +msgstr "" + +#: 85cf820b2dab453598028afc982fcd19 base.echo.AtomicEchoBase.play_raw:1 of +msgid "Play a pcm buffer." +msgstr "播放 pcm 缓冲区。" + +#: aa149f56aa4840aa9fd0b5d84c722974 base.echo.AtomicEchoBase.play_raw:3 of +msgid "The PCM buffer to play." +msgstr "要播放的 PCM 缓冲区。" + +#: 0cbd496010cc42b18c834492f19c6c84 base.echo.AtomicEchoBase.play_raw:4 +#: base.echo.AtomicEchoBase.record:3 base.echo.AtomicEchoBase.record_wav_file:4 +#: of +msgid "Sample rate (default is 16000)." +msgstr "采样率(默认为 16000)。" + +#: base.echo.AtomicEchoBase.play_raw:5 base.echo.AtomicEchoBase.record:4 +#: base.echo.AtomicEchoBase.record_wav_file:5 e99b94d5f5f54013a4ac3546e93325fa +#: of +msgid "Bit depth (default is 16)." +msgstr "位深度(默认为 16)。" + +#: 0cbd496010cc42b18c834492f19c6c84 base.echo.AtomicEchoBase.play_raw:6 +#: base.echo.AtomicEchoBase.record:5 base.echo.AtomicEchoBase.record_wav_file:6 +#: of +msgid "Number of channels (default is 2)." +msgstr "通道数(默认为 2)。" + +#: ba19c6dbee6047068ce5b5cbcc43a957 base.echo.AtomicEchoBase.play_raw:7 of msgid "" -"See :ref:`hardware.Speaker.Methods ` for more " -"details on how to use the ATOMEchoBase.speaker properties." +"Duration of the PCM buffer in milliseconds. when duration is -1, it will " +"play until stopped. (default is -1)." +msgstr "PCM 缓冲区的持续时间(以毫秒为单位)。当持续时间为 -1 时,它将播放直到停止。(默认值为 -1)。" + +#: 3fbce9c2703d4a58967f1a608416bf0b base.echo.AtomicEchoBase.play_raw:12 of +msgid "|play_raw.png|" msgstr "" -#: ../../en/base/echo.rst:123 f5efd98e3a3b4cf39a93f40cad838222 -msgid "Objects of the Microphone class." +#: ../../en/refs/base.echo.ref:31 92f6a4ffadf049769a2169a16d51d693 +msgid "play_raw.png" msgstr "" -#: ../../en/base/echo.rst:125 d447c5a69bfb40198457e5f25782586a +#: base.echo.AtomicEchoBase.play_wav:1 d978e45cd85a43c8a82843749961a5f3 of +msgid "Play a WAV buffer." +msgstr "播放 WAV 缓冲区。" + +#: 3270485256fe4ef3a3705a970089e727 base.echo.AtomicEchoBase.play_wav:3 of +msgid "The WAV buffer to play." +msgstr "要播放的 WAV 缓冲区。" + +#: a2a0b2624a8d4e20810ece06c58e2591 base.echo.AtomicEchoBase.play_wav:4 of msgid "" -"See :ref:`hardware.Mic.Methods ` for more details " -"on how to use the ATOMEchoBase.microphone properties." +"Duration of the WAV buffer in milliseconds. when duration is -1, it will " +"play until stopped. (default is -1)." +msgstr "WAV 缓冲区的持续时间(以毫秒为单位)。当持续时间为 -1 时,它将播放直到停止。(默认值为 -1)。" + +#: 3fbce9c2703d4a58967f1a608416bf0b base.echo.AtomicEchoBase.play_wav:9 of +msgid "|play_wav.png|" +msgstr "" + +#: ../../en/refs/base.echo.ref:32 92f6a4ffadf049769a2169a16d51d693 +msgid "play_wav.png" +msgstr "" + +#: base.echo.AtomicEchoBase.play_wav_file:1 c8009ca2a92d483b83f23e83c1ce6512 of +msgid "Play a WAV file." +msgstr "播放 WAV 文件。" + +#: 0cbd496010cc42b18c834492f19c6c84 base.echo.AtomicEchoBase.play_wav_file:3 of +msgid "The path of the WAV file to play." +msgstr "要播放的 WAV 文件的路径。" + +#: a0a86de22c4c4912bd343253fb49f9af base.echo.AtomicEchoBase.play_wav_file:8 of +msgid "|play_wav_file.png|" +msgstr "" + +#: ../../en/refs/base.echo.ref:33 92f6a4ffadf049769a2169a16d51d693 +msgid "play_wav_file.png" +msgstr "" + +#: base.echo.AtomicEchoBase.record:1 bb19ebe7d8694a0c98069b48bc86455e of +msgid "Record audio to a PCM buffer." +msgstr "将音频录制到 PCM 缓冲区。" + +#: 7f9bea49f8714a8d89827722524466c5 ae1d3e95ea5e4c37966dcd8d20a118df +#: base.echo.AtomicEchoBase.record:6 base.echo.AtomicEchoBase.record_wav_file:7 +#: of +msgid "Duration of the recording in milliseconds (default is 3000)." +msgstr "录音时长(以毫秒为单位)(默认为 3000)。" + +#: 3fbce9c2703d4a58967f1a608416bf0b base.echo.AtomicEchoBase.record:10 of +msgid "|record.png|" +msgstr "" + +#: ../../en/refs/base.echo.ref:34 0514f1bc25d546039cf0fe510ed20947 +msgid "record.png" +msgstr "" + +#: 9cfbeae565b646c3a7cd07e3d4aca0bb base.echo.AtomicEchoBase.record_wav_file:1 +#: of +msgid "Record audio to a WAV file." +msgstr "将音频录制为 WAV 文件。" + +#: 1e36b059207d4f30aa15e3bda373a5ab base.echo.AtomicEchoBase.record_wav_file:3 +#: of +msgid "The path to save the WAV file." +msgstr "保存 WAV 文件的路径。" + +#: aa728c2f1eee4ffcad73ad4f7a0e47c2 base.echo.AtomicEchoBase.record_wav_file:11 +#: of +msgid "|record_wav_file.png|" +msgstr "" + +#: ../../en/refs/base.echo.ref:35 92f6a4ffadf049769a2169a16d51d693 +msgid "record_wav_file.png" +msgstr "" + +#: 0f11aaba27e64d608959949d0ad3d58f base.echo.AtomicEchoBase.resume:1 of +msgid "Resume the playback." +msgstr "继续播放。" + +#: 3fbce9c2703d4a58967f1a608416bf0b base.echo.AtomicEchoBase.resume:5 of +msgid "|resume.png|" +msgstr "" + +#: ../../en/refs/base.echo.ref:36 92f6a4ffadf049769a2169a16d51d693 +msgid "resume.png" +msgstr "" + +#: b70c82ebf58544b9b4221cfb7fb29b3d base.echo.AtomicEchoBase.set_volume:1 of +msgid "Set the speaker volume level." +msgstr "设置扬声器音量。" + +#: 3fbce9c2703d4a58967f1a608416bf0b base.echo.AtomicEchoBase.set_volume:7 of +msgid "|set_volume.png|" +msgstr "" + +#: ../../en/refs/base.echo.ref:41 92f6a4ffadf049769a2169a16d51d693 +msgid "set_volume.png" +msgstr "" + +#: 0f11aaba27e64d608959949d0ad3d58f base.echo.AtomicEchoBase.stop:1 of +msgid "Stop the playback." +msgstr "停止播放。" + +#: 550d7b3601174053a1f8a613d26ca00f base.echo.AtomicEchoBase.stop:5 of +msgid "|stop.png|" +msgstr "" + +#: ../../en/refs/base.echo.ref:42 0514f1bc25d546039cf0fe510ed20947 +msgid "stop.png" +msgstr "" + +#: 6a80291bad9f4410a8d96ba4a36f691f base.echo.AtomicEchoBase.tone:1 of +msgid "Play simple tone sound." +msgstr "播放简单的音调声音。" + +#: 7ba2f4913420490989871afc1df95430 base.echo.AtomicEchoBase.tone:3 of +msgid "Frequency of the tone in Hz." +msgstr "音调的频率(以赫兹为单位)。" + +#: 178b4f20b9ec4436a7b98401a96c0407 base.echo.AtomicEchoBase.tone:4 of +msgid "Duration of the tone in milliseconds." +msgstr "音调的持续时间(以毫秒为单位)。" + +#: 550d7b3601174053a1f8a613d26ca00f base.echo.AtomicEchoBase.tone:9 of +msgid "|tone.png|" msgstr "" -#: ../../en/base/echo.rst:127 c3e4b64c403b4ce59d8657ce651b6428 -msgid "Microphone is not quite ready yet." +#: ../../en/refs/base.echo.ref:43 0514f1bc25d546039cf0fe510ed20947 +msgid "tone.png" msgstr "" diff --git a/examples/base/echo/atoms3_play_wav_example.m5f2 b/examples/base/echo/atoms3_play_wav_example.m5f2 new file mode 100644 index 00000000..3679279b --- /dev/null +++ b/examples/base/echo/atoms3_play_wav_example.m5f2 @@ -0,0 +1 @@ +{"version":"V2.0","versionNumber":"V2.2.7","type":"atoms3","components":[{"name":"screen","type":"screen","layer":0,"screenId":"builtin","screenName":"","id":"__atoms3_screen","createTime":1748420187832,"x":0,"y":0,"width":128,"height":128,"backgroundColor":"#000000","size":0,"isSelected":true}],"resources":[{"hardware":["hardware_button","hardware_pin_button","imu","ir","i2c"]},{"base":["base_echo"]}],"units":[],"hats":[],"bases":[{"type":"base_echo","name":"base_echo","id":"w%V&Q@u96vI3^h_p","createTime":1748420192052,"bus":"i2c1","busList":["i2c1"],"initBlockType":"base_echo_init2","initBlockId":"e|dTMiR(JFt`Gg?/TpPf"}],"i2cs":[{"id":"i2c1","portType":"base","userPort":[22,21],"freq":"100000","blockId":"*ud~%*gel**KFWdY,O@3"}],"blockly":"true11000003938116000flash66.wavtrue","screen":[{"simulationName":"Built-in","type":"builtin","width":128,"height":128,"scale":1.3,"screenName":"","blockId":"","screenColorType":0,"id":"builtin","createTime":1748420187830}],"logicWhenNum":0,"customList":[]} \ No newline at end of file diff --git a/examples/base/echo/atoms3_play_wav_example.py b/examples/base/echo/atoms3_play_wav_example.py new file mode 100644 index 00000000..525cc73e --- /dev/null +++ b/examples/base/echo/atoms3_play_wav_example.py @@ -0,0 +1,42 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import os, sys, io +import M5 +from M5 import * +from hardware import I2C +from hardware import Pin +from base import AtomicEchoBase + + +i2c1 = None +base_echo = None + + +def setup(): + global i2c1, base_echo + + M5.begin() + i2c1 = I2C(1, scl=Pin(39), sda=Pin(38), freq=100000) + base_echo = AtomicEchoBase(i2c1, 0x18, 1, 16000, 8, 6, 7, 5) + base_echo.play_wav_file("/flash/res/audio/66.wav") + + +def loop(): + global i2c1, base_echo + M5.update() + + +if __name__ == "__main__": + try: + setup() + while True: + loop() + except (Exception, KeyboardInterrupt) as e: + try: + from utility import print_error_msg + + print_error_msg(e) + except ImportError: + print("please update to latest firmware") diff --git a/examples/base/echo/atoms3_playback_controls_example.m5f2 b/examples/base/echo/atoms3_playback_controls_example.m5f2 new file mode 100644 index 00000000..ea381787 --- /dev/null +++ b/examples/base/echo/atoms3_playback_controls_example.m5f2 @@ -0,0 +1 @@ +{"version":"V2.0","versionNumber":"V2.2.7","type":"atoms3","components":[{"name":"screen","type":"screen","layer":0,"screenId":"builtin","screenName":"","id":"__atoms3_screen","createTime":1748420187832,"x":0,"y":0,"width":128,"height":128,"backgroundColor":"#000000","size":0,"isSelected":true}],"resources":[{"hardware":["hardware_button","hardware_pin_button","imu","ir","i2c"]},{"base":["base_echo"]}],"units":[],"hats":[],"bases":[{"type":"base_echo","name":"base_echo","id":"w%V&Q@u96vI3^h_p","createTime":1748420192052,"bus":"i2c1","busList":["i2c1"],"initBlockType":"base_echo_init2","initBlockId":"e|dTMiR(JFt`Gg?/TpPf"}],"i2cs":[{"id":"i2c1","portType":"base","userPort":[22,21],"freq":"100000","blockId":"*ud~%*gel**KFWdY,O@3"}],"blockly":"true11000003938116000flash66.wav11true","screen":[{"simulationName":"Built-in","type":"builtin","width":128,"height":128,"scale":1.3,"screenName":"","blockId":"","screenColorType":0,"id":"builtin","createTime":1748420187830}],"logicWhenNum":0,"customList":[]} \ No newline at end of file diff --git a/examples/base/echo/atoms3_playback_controls_example.py b/examples/base/echo/atoms3_playback_controls_example.py new file mode 100644 index 00000000..b6891705 --- /dev/null +++ b/examples/base/echo/atoms3_playback_controls_example.py @@ -0,0 +1,47 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import os, sys, io +import M5 +from M5 import * +from hardware import I2C +from hardware import Pin +from base import AtomicEchoBase +import time + + +i2c1 = None +base_echo = None + + +def setup(): + global i2c1, base_echo + + M5.begin() + i2c1 = I2C(1, scl=Pin(39), sda=Pin(38), freq=100000) + base_echo = AtomicEchoBase(i2c1, 0x18, 1, 16000, 8, 6, 7, 5) + base_echo.play_wav_file("/flash/res/audio/66.wav") + time.sleep(1) + base_echo.pause() + time.sleep(1) + base_echo.resume() + + +def loop(): + global i2c1, base_echo + M5.update() + + +if __name__ == "__main__": + try: + setup() + while True: + loop() + except (Exception, KeyboardInterrupt) as e: + try: + from utility import print_error_msg + + print_error_msg(e) + except ImportError: + print("please update to latest firmware") diff --git a/examples/base/echo/atoms3_record_audio_example.m5f2 b/examples/base/echo/atoms3_record_audio_example.m5f2 new file mode 100644 index 00000000..79e99287 --- /dev/null +++ b/examples/base/echo/atoms3_record_audio_example.m5f2 @@ -0,0 +1 @@ +{"version":"V2.0","versionNumber":"V2.2.7","type":"atoms3","components":[{"name":"screen","type":"screen","layer":0,"screenId":"builtin","screenName":"","id":"__atoms3_screen","createTime":1748420187832,"x":0,"y":0,"width":128,"height":128,"backgroundColor":"#000000","size":0,"isSelected":true}],"resources":[{"hardware":["hardware_button","hardware_pin_button","imu","ir","i2c"]},{"base":["base_echo"]}],"units":[],"hats":[],"bases":[{"type":"base_echo","name":"base_echo","id":"w%V&Q@u96vI3^h_p","createTime":1748420192052,"bus":"i2c1","busList":["i2c1"],"initBlockType":"base_echo_init2","initBlockId":"e|dTMiR(JFt`Gg?/TpPf"}],"i2cs":[{"id":"i2c1","portType":"base","userPort":[22,21],"freq":"100000","blockId":"*ud~%*gel**KFWdY,O@3"}],"blockly":"true1100000393811600016000STEREO1650016000STEREO16-1true","screen":[{"simulationName":"Built-in","type":"builtin","width":128,"height":128,"scale":1.3,"screenName":"","blockId":"","screenColorType":0,"id":"builtin","createTime":1748420187830}],"logicWhenNum":0,"customList":[]} \ No newline at end of file diff --git a/examples/base/echo/atoms3_record_audio_example.py b/examples/base/echo/atoms3_record_audio_example.py new file mode 100644 index 00000000..7d974aa2 --- /dev/null +++ b/examples/base/echo/atoms3_record_audio_example.py @@ -0,0 +1,45 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import os, sys, io +import M5 +from M5 import * +from hardware import I2C +from hardware import Pin +from base import AtomicEchoBase + + +i2c1 = None +base_echo = None + + +def setup(): + global i2c1, base_echo + + M5.begin() + i2c1 = I2C(1, scl=Pin(39), sda=Pin(38), freq=100000) + base_echo = AtomicEchoBase(i2c1, 0x18, 1, 16000, 8, 6, 7, 5) + base_echo.record(rate=16000, bits=16, channel=AtomicEchoBase.STEREO, duration=500) + base_echo.play_raw( + base_echo.pcm_buffer, rate=16000, bits=16, channel=AtomicEchoBase.STEREO, duration=-1 + ) + + +def loop(): + global i2c1, base_echo + M5.update() + + +if __name__ == "__main__": + try: + setup() + while True: + loop() + except (Exception, KeyboardInterrupt) as e: + try: + from utility import print_error_msg + + print_error_msg(e) + except ImportError: + print("please update to latest firmware") diff --git a/m5stack/cmodules/m5audio2/audio2_player.c b/m5stack/cmodules/m5audio2/audio2_player.c index 034e2d30..38cd3315 100644 --- a/m5stack/cmodules/m5audio2/audio2_player.c +++ b/m5stack/cmodules/m5audio2/audio2_player.c @@ -596,7 +596,7 @@ static void player_wav_task(void *arg) { if (data_offset + len > self->play_info.wav_buf.data_len) { mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("File is too short")); } - memcpy(wav_data[idx], &self->play_info.wav_buf.data[data_offset], len); + memcpy(wav_data[idx], &((uint8_t *)self->play_info.wav_buf.data)[data_offset], len); data_offset += len; data_len -= len; @@ -671,7 +671,8 @@ static mp_obj_t player_play_wav(size_t n_args, const mp_obj_t *pos_args, mp_map_ mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("File is too short")); } struct wav_header_t header; - memcpy(&header, (const char *)&bufinfo.buf[buf_offset], sizeof(struct wav_header_t)); + const uint8_t *ptr = (const uint8_t *)bufinfo.buf; + memcpy(&header, ptr + buf_offset, sizeof(struct wav_header_t)); buf_offset += sizeof(struct wav_header_t); ESP_LOGE(TAG, "wav bit_per_sample: %d, channel: %d, sample_rate: %ld", header.bit_per_sample, header.channel, header.sample_rate); @@ -693,7 +694,7 @@ static mp_obj_t player_play_wav(size_t n_args, const mp_obj_t *pos_args, mp_map_ if (buf_offset + sizeof(struct sub_chunk_t) > bufinfo.len) { mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("File is too short")); } - memcpy(&sub_chunk, &bufinfo.buf[buf_offset], sizeof(struct sub_chunk_t)); + memcpy(&sub_chunk, ptr + buf_offset, sizeof(struct sub_chunk_t)); buf_offset += sizeof(struct sub_chunk_t); if (strncmp(sub_chunk.identifier, "data", 4) == 0) { @@ -708,7 +709,7 @@ static mp_obj_t player_play_wav(size_t n_args, const mp_obj_t *pos_args, mp_map_ ESP_LOGE(TAG, "buf_offset: %d", buf_offset); - self->play_info.wav_buf.data = &bufinfo.buf[buf_offset]; + self->play_info.wav_buf.data = bufinfo.buf + buf_offset; self->play_info.wav_buf.sample_rate = header.sample_rate; self->play_info.wav_buf.channel = header.channel; self->play_info.wav_buf.bit_per_sample = header.bit_per_sample; @@ -794,7 +795,7 @@ static mp_obj_t player_play_raw(size_t n_args, const mp_obj_t *pos_args, mp_map_ return mp_const_none; } - self->play_info.wav_buf.data = &bufinfo.buf[0]; + self->play_info.wav_buf.data = bufinfo.buf; self->play_info.wav_buf.sample_rate = sample_rate; self->play_info.wav_buf.channel = channel; self->play_info.wav_buf.bit_per_sample = bit_per_sample; diff --git a/m5stack/cmodules/m5audio2/audio2_recorder.c b/m5stack/cmodules/m5audio2/audio2_recorder.c index b3394d39..42dfecc3 100644 --- a/m5stack/cmodules/m5audio2/audio2_recorder.c +++ b/m5stack/cmodules/m5audio2/audio2_recorder.c @@ -150,7 +150,7 @@ static void recorder_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) { if (dest[0] == MP_OBJ_NULL) { // Load if (attr == MP_QSTR_pcm_buffer) { - dest[0] = mp_obj_new_bytes(self->pcm_buf.buf, self->pcm_buf.cur_len); + dest[0] = mp_obj_new_bytearray_by_ref(self->pcm_buf.cur_len, self->pcm_buf.buf); } else { // Continue lookup in locals_dict. dest[1] = MP_OBJ_SENTINEL; @@ -281,11 +281,6 @@ static mp_obj_t recorder_record_wav_file(size_t n_args, const mp_obj_t *args_in, } size_t src_len = round((double)(args[ARG_duration].u_int / 1000.0) * self->sample_rate * self->channel * (self->bits >> 3)); - if (src_len > self->pcm_buf.total) { - self->pcm_buf.buf = m_realloc(self->pcm_buf.buf, src_len); - self->pcm_buf.total = src_len; - } - self->pcm_buf.cur_len = src_len; wav_header_t wav_header = WAV_HEADER_PCM_DEFAULT(src_len, self->bits, self->sample_rate, self->channel); int rlen = mp_stream_posix_write(wav_file, &wav_header, sizeof(wav_header_t)); @@ -294,20 +289,44 @@ static mp_obj_t recorder_record_wav_file(size_t n_args, const mp_obj_t *args_in, mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("File is too short")); } + static const size_t I2S_CHUNK_SIZE = 512; + int16_t *chunk_buf = m_malloc(I2S_CHUNK_SIZE); + if (!chunk_buf) { + mp_stream_close(wav_file); + mp_raise_OSError(MP_ENOMEM); + } size_t read_len = 0; + if (self->channel == 1) { + src_len *= 2; // 实际读取立体声的大小(假设用户是期望录 N 秒的单声道) + } while (read_len < src_len) { + size_t to_read = I2S_CHUNK_SIZE; + if (src_len - read_len < I2S_CHUNK_SIZE) { + to_read = src_len - read_len; + } + size_t bytes_read = 0; i2s_channel_read( self->i2s_chan_handle, - &self->pcm_buf.buf[read_len], - src_len - read_len, + chunk_buf, + to_read, &bytes_read, portMAX_DELAY ); - - int wlen = mp_stream_posix_write(wav_file, &self->pcm_buf.buf[read_len], bytes_read); - read_len += bytes_read; + if (bytes_read > 0) { + if (self->channel == 1) { + // 从立体声中提取左声道,每组 2 个 int16_t(4 字节)取一个 + int sample_count = bytes_read / (self->bits >> 2); // 每帧立体声 4 字节 + for (int i = 0; i < sample_count; i++) { + chunk_buf[i] = chunk_buf[i * 2]; // 取左声道 + } + mp_stream_posix_write(wav_file, chunk_buf, bytes_read / 2); + } else { + mp_stream_posix_write(wav_file, chunk_buf, bytes_read); + } + read_len += bytes_read; + } } mp_stream_close(wav_file); diff --git a/m5stack/cmodules/m5audio2/i2s_helper.c b/m5stack/cmodules/m5audio2/i2s_helper.c index 93f966d8..3ce607d4 100644 --- a/m5stack/cmodules/m5audio2/i2s_helper.c +++ b/m5stack/cmodules/m5audio2/i2s_helper.c @@ -72,6 +72,9 @@ esp_err_t i2s_std_init_helper(int id, i2s_mode_t mode, int sck, int ws, int sd, esp_err_t i2s_std_deinit_helper(i2s_chan_handle_t i2s_chan_handle) { + if (i2s_chan_handle == NULL) { + return ESP_ERR_INVALID_ARG; + } esp_err_t ret = i2s_channel_disable(i2s_chan_handle); if (ret != ESP_OK) { ESP_LOGE(TAG, "Failed to disable I2S channel"); diff --git a/m5stack/libs/base/__init__.py b/m5stack/libs/base/__init__.py index 5f359c07..ace94b7b 100644 --- a/m5stack/libs/base/__init__.py +++ b/m5stack/libs/base/__init__.py @@ -9,7 +9,7 @@ "AtomDTUNBIoT": "dtu_nbiot", "ATOMGPSBase": "atom_gps", "ATOMSocketBase": "atom_socket", - "ATOMEchoBase": "echo", + "AtomicEchoBase": "echo", "AtomicDisplayBase": "display", "AtomicHDriverBase": "hdriver", "Motion": "motion", diff --git a/m5stack/libs/base/echo.py b/m5stack/libs/base/echo.py index 69e47093..60cdd017 100644 --- a/m5stack/libs/base/echo.py +++ b/m5stack/libs/base/echo.py @@ -3,18 +3,73 @@ # SPDX-License-Identifier: MIT import driver.es8311 as es8311 +import m5audio2 +import machine import M5 -class ATOMEchoBase: +class AtomicEchoBase: + """Create an AtomicEchoBase object. + + :param I2C i2c: I2C object + :param int address: The I2C address of the ES8311. Default is 0x18. + :param int i2s_port: The I2S port number. Default is 1. + :param int sample_rate: The sample rate of the audio. Default is 16000. + :param int i2s_sck: The I2S SCK pin. Default is -1. + :param int i2s_ws: The I2S WS pin. Default is -1. + :param int i2s_di: The I2S DI pin. Default is -1. + :param int i2s_do: The I2S DO pin. Default is -1. + + UIFLOW2: + + |init.png| + + Micropython:: + + from hardware import I2C + from hardware import Pin + from base import AtomicEchoBase + + # atom echo + i2c1 = I2C(1, scl=Pin(21), sda=Pin(25), freq=100000) + base_echo = AtomicEchoBase(i2c1, address=0x18, i2s_port=1, sample_rate=44100, i2s_sck=33, i2s_ws=19, i2s_di=23, i2s_do=22) + + # atom lite + i2c1 = I2C(1, scl=Pin(21), sda=Pin(25), freq=100000) + base_echo = AtomicEchoBase(i2c1, address=0x18, i2s_port=1, sample_rate=44100, i2s_sck=33, i2s_ws=19, i2s_di=23, i2s_do=22) + + # atom matrix + i2c1 = I2C(1, scl=Pin(21), sda=Pin(25), freq=100000) + base_echo = AtomicEchoBase(i2c1, address=0x18, i2s_port=1, sample_rate=44100, i2s_sck=33, i2s_ws=19, i2s_di=23, i2s_do=22) + + # atoms3 / atoms3 lite + i2c1 = I2C(1, scl=Pin(39), sda=Pin(38), freq=100000) + base_echo = AtomicEchoBase(i2c1, address=0x18, i2s_port=1, sample_rate=44100, i2s_sck=8, i2s_ws=6, i2s_di=7, i2s_do=5) + + # atoms3r / atoms3r-cam / atoms3-ext + i2c1 = I2C(1, scl=Pin(39), sda=Pin(38), freq=100000) + base_echo = AtomicEchoBase(i2c1, address=0x18, i2s_port=1, sample_rate=44100, i2s_sck=8, i2s_ws=6, i2s_di=7, i2s_do=5) + + base_echo.speaker.tone(2000, 1000) + base_echo.speaker.playWavFile('res/audio/66.wav') + """ + _instance = None PI4IOE_REG_CTRL = 0x00 - PI4IOE_REG_IO_PP = 0x07 + PI4IOE_REG_CHIP_RESET = 0x01 PI4IOE_REG_IO_DIR = 0x03 PI4IOE_REG_IO_OUT = 0x05 + PI4IOE_REG_IO_PP = 0x07 + PI4IOE_REG_PULL_EN = 0x0B PI4IOE_REG_IO_PULLUP = 0x0D + MONO = 1 + """Mono""" + + STEREO = 2 + """Stereo""" + def __new__(cls, *args, **kwargs): if cls._instance is None: cls._instance = super().__new__(cls) @@ -26,7 +81,7 @@ def __init__( i2c, address: int = 0x18, i2s_port: int = 1, - sample_rate: int = 44100, + sample_rate: int = 16000, i2s_sck: int = -1, i2s_ws: int = -1, i2s_di: int = -1, @@ -36,76 +91,306 @@ def __init__( return self._i2c = i2c self._es8311 = es8311.ES8311(i2c, address) - es_clk = es8311.es8311_clock_config_t() - es_clk.mclk_inverted = False - es_clk.sclk_inverted = False - es_clk.mclk_from_mclk_pin = False # MCLK taken from SCK pin - es_clk.mclk_frequency = 0 # Not used - es_clk.sample_frequency = sample_rate + self.es_clk = es8311.es8311_clock_config_t() + self.es_clk.mclk_inverted = False + self.es_clk.sclk_inverted = False + self.es_clk.mclk_from_mclk_pin = False # MCLK taken from SCK pin + self.es_clk.mclk_frequency = 0 # Not used + self.es_clk.sample_frequency = sample_rate self._es8311.init( - es_clk, es8311.ES8311.ES8311_RESOLUTION_32, es8311.ES8311.ES8311_RESOLUTION_32 + self.es_clk, es8311.ES8311.ES8311_RESOLUTION_32, es8311.ES8311.ES8311_RESOLUTION_32 ) - self._es8311.voice_volume_set(100) + self._es8311.voice_volume_set(80) self._es8311.microphone_config(False) - self.speaker = M5.createSpeaker() - self.speaker.config( - pin_data_out=i2s_do, - pin_bck=i2s_sck, - pin_ws=i2s_ws, - sample_rate=sample_rate, - stereo=True, - buzzer=False, - use_dac=False, - dac_zero_level=False, - magnification=1, - dma_buf_len=256, - dma_buf_count=8, - task_priority=2, - task_pinned_core=255, - i2s_port=i2s_port, + M5.Speaker.end() + M5.Mic.end() + + self.spk = m5audio2.Player( + i2s_port, + sck=machine.Pin(i2s_sck), + ws=machine.Pin(i2s_ws), + sd=machine.Pin(i2s_do), + rate=sample_rate, + bits=16, + channel=2, ) - self.microphone = M5.createMic() - self.microphone.config( - pin_data_in=i2s_di, - pin_bck=i2s_sck, - pin_mck=-1, - pin_ws=i2s_ws, - sample_rate=sample_rate, - stereo=True, - over_sampling=1, - magnification=1, - noise_filter_level=0, - use_adc=False, - dma_buf_len=240, - dma_buf_count=6, - task_priority=2, - task_pinned_core=255, - i2s_port=i2s_port, + self.mic = m5audio2.Recorder( + i2s_port, + sck=machine.Pin(i2s_sck), + ws=machine.Pin(i2s_ws), + sd=machine.Pin(i2s_di), + rate=sample_rate, + bits=16, + channel=2, ) + self.pi4ioe_init() self._initialized = True def pi4ioe_init(self): - self._i2c.readfrom_mem(0x43, self.PI4IOE_REG_CTRL, 1) - self._i2c.writeto_mem(0x43, self.PI4IOE_REG_IO_PP, b"\x00") - self._i2c.readfrom_mem(0x43, self.PI4IOE_REG_IO_PP, 1) - self._i2c.writeto_mem(0x43, self.PI4IOE_REG_IO_PULLUP, b"\xff") self._i2c.writeto_mem(0x43, self.PI4IOE_REG_IO_DIR, b"\x6e") - self._i2c.readfrom_mem(0x43, self.PI4IOE_REG_IO_DIR, 1) - self._i2c.writeto_mem(0x43, self.PI4IOE_REG_IO_OUT, b"\xff") - self._i2c.readfrom_mem(0x43, self.PI4IOE_REG_IO_OUT, 1) def set_mute(self, mute): - if mute: - self._i2c.writeto_mem(0x43, 0x05, b"\x00") - else: - self._i2c.writeto_mem(0x43, 0x05, b"\xff") + self._i2c.writeto_mem(0x43, self.PI4IOE_REG_IO_OUT, b"\x00" if mute else b"\xff") + + def change_sample_rate(self, sample_rate: int) -> None: + self.es_clk.sample_frequency = sample_rate + self._es8311.clock_config(self.es_clk, es8311.ES8311.ES8311_RESOLUTION_32) + + def play_wav_file(self, file: str) -> None: + """Play a WAV file. + + :param str file: The path of the WAV file to play. + :return: None + + UiFlow2 Code Block: + + |play_wav_file.png| + + MicroPython Code Block: + + .. code-block:: python + + base_echo.play_wav_file("/flash/res/audio/test.wav") + """ + if self.mic.is_running(): + self.mic.deinit() + self.set_mute(False) + self.spk.play_wav_file(file) + + def tone(self, freq: int, duration: int) -> None: + """Play simple tone sound. + + :param int freq: Frequency of the tone in Hz. + :param int duration: Duration of the tone in milliseconds. + :return: None + + UiFlow2 Code Block: + + |tone.png| + + MicroPython Code Block: + + .. code-block:: python + + base_echo.tone(2000, 50) + """ + if self.mic.is_running(): + self.mic.deinit() + self.set_mute(False) + self.spk.tone(freq, duration) + + def play_wav(self, buf: bytes, duration: int = -1) -> None: + """Play a WAV buffer. + + :param bytes buf: The WAV buffer to play. + :param int duration: Duration of the WAV buffer in milliseconds. when duration is -1, it will play until stopped. (default is -1). + :return: None + + UiFlow2 Code Block: + + |play_wav.png| + + MicroPython Code Block: + + .. code-block:: python + + base_echo.play_wav(wav_buffer, duration=1000) + """ + if self.mic.is_running(): + self.mic.deinit() + self.set_mute(False) + self.spk.play_wav(buf, duration=duration) + + def play_raw( + self, buf: bytes, rate: int = 16000, bits: int = 16, channel: int = 2, duration: int = -1 + ) -> None: + """Play a pcm buffer. + + :param bytes buf: The PCM buffer to play. + :param int rate: Sample rate (default is 16000). + :param int bits: Bit depth (default is 16). + :param int channel: Number of channels (default is 2). + :param int duration: Duration of the PCM buffer in milliseconds. when duration is -1, it will play until stopped. (default is -1). + :return: None + + UiFlow2 Code Block: + + |play_raw.png| + + MicroPython Code Block: + + .. code-block:: python + + base_echo.play_raw(pcm_buffer, rate=16000, bits=16, channel=2, duration=1000) + """ + if self.mic.is_running(): + self.mic.deinit() + self.set_mute(False) + self.spk.play_raw(buf, rate=rate, bits=bits, channel=channel, duration=duration) + + def pause(self) -> None: + """Pause the playback. + + UiFlow2 Code Block: + + |pause.png| + + MicroPython Code Block: + + .. code-block:: python + + audio.tone(2000, 100) + time.sleep(0.05) + base_echo.pause() + time.sleep(0.05) + base_echo.resume() + """ + self.spk.pause() + + def resume(self): + """Resume the playback. + + UiFlow2 Code Block: + + |resume.png| + + MicroPython Code Block: + + .. code-block:: python + + audio.tone(2000, 100) + time.sleep(0.05) + base_echo.pause() + time.sleep(0.05) + base_echo.resume() + """ + self.spk.resume() + + def stop(self): + """Stop the playback. + + UiFlow2 Code Block: + + |stop.png| + + MicroPython Code Block: + + .. code-block:: python + + audio.tone(2000, 100) + time.sleep(0.05) + base_echo.stop() + """ + self.spk.stop() + + def get_volume(self) -> int: + """Get the speaker volume level. + + :return: The volume level (0-100). + + UiFlow2 Code Block: + + |get_volume.png| + + MicroPython Code Block: + + .. code-block:: python + + base_echo.get_volume() + """ + return self._es8311.voice_volume_get() + + def set_volume(self, volume): + """Set the speaker volume level. + + :param int volume: The volume level (0-100). + + UiFlow2 Code Block: + + |set_volume.png| + + MicroPython Code Block: + + .. code-block:: python + + base_echo.set_volume(50) + """ + self._es8311.voice_volume_set(volume) + + def record_wav_file( + self, path: str, rate: int = 16000, bits: int = 16, channel: int = 2, duration: int = 3000 + ): + """Record audio to a WAV file. + + :param str path: The path to save the WAV file. + :param int rate: Sample rate (default is 16000). + :param int bits: Bit depth (default is 16). + :param int channel: Number of channels (default is 2). + :param int duration: Duration of the recording in milliseconds (default is 3000). + + UiFlow2 Code Block: + + |record_wav_file.png| + + MicroPython Code Block: + + .. code-block:: python + + base_echo.record_wav_file("/flash/res/audio/test.wav", rate=16000, bits=16, channel=2, duration=3000) + """ + self.spk.deinit() + self.set_mute(True) + self.change_sample_rate(rate) + self.mic.record_wav_file(path, rate=rate, bits=bits, channel=channel, duration=duration) + + def record(self, rate=16000, bits=16, channel=2, duration=3000): + """Record audio to a PCM buffer. + + :param int rate: Sample rate (default is 16000). + :param int bits: Bit depth (default is 16). + :param int channel: Number of channels (default is 2). + :param int duration: Duration of the recording in milliseconds (default is 3000). + + UiFlow2 Code Block: + + |record.png| + + MicroPython Code Block: + + .. code-block:: python + + base_echo.record(rate=16000, bits=16, channel=2, duration=3000) + """ + self.spk.deinit() + self.set_mute(True) + self.change_sample_rate(rate) + return self.mic.record(rate=rate, bits=bits, channel=channel, duration=duration) + + @property + def pcm_buffer(self) -> bytes: + """Get the PCM buffer. + + :return: The PCM buffer. + + UiFlow2 Code Block: + + |pcm_buffer.png| + + MicroPython Code Block: + + .. code-block:: python + + base_echo.pcm_buffer + """ + return self.mic.pcm_buffer def deinit(self): - self.speaker.deinit() - self.microphone.deinit() + self.spk.deinit() + self.mic.deinit(True) + self.set_mute(True) diff --git a/m5stack/libs/driver/es8311/__init__.py b/m5stack/libs/driver/es8311/__init__.py index fe487d69..98e3423c 100644 --- a/m5stack/libs/driver/es8311/__init__.py +++ b/m5stack/libs/driver/es8311/__init__.py @@ -153,6 +153,7 @@ class ES8311: def __init__(self, i2c, address: int = 0x18): self._i2c = i2c self._address = address + self._dac_volume = 0 def init(self, clk_cfg, res_in, res_out): self._i2c.writeto_mem(self._address, reg.ES8311_RESET_REG00, b"\x1f") @@ -284,6 +285,8 @@ def voice_volume_set(self, volume: int): elif volume > 100: volume = 100 + self._dac_volume = volume + reg32 = 0 if volume == 0: reg32 = 0 @@ -292,6 +295,9 @@ def voice_volume_set(self, volume: int): self._i2c.writeto_mem(self._address, reg.ES8311_DAC_REG32, reg32.to_bytes(1, "big")) + def voice_volume_get(self) -> int: + return self._dac_volume + def microphone_config(self, digital_mic: bool): reg14 = 0x1A # enable analog MIC and max PGA gain diff --git a/m5stack/libs/module/audio.py b/m5stack/libs/module/audio.py index 8830b7b8..9f3846e4 100644 --- a/m5stack/libs/module/audio.py +++ b/m5stack/libs/module/audio.py @@ -383,7 +383,7 @@ def set_volume(self, volume): audio_0.set_volume(50) """ - self.spk.es.set_dac_volume(volume) + self.es.set_dac_volume(volume) def record_wav_file( self, path: str, rate: int = 16000, bits: int = 16, channel: int = 2, duration: int = 3000 From 54f9a709aa54ac7ba9605532d40aa072156f2a6e Mon Sep 17 00:00:00 2001 From: tinyu Date: Wed, 28 May 2025 10:16:04 +0800 Subject: [PATCH 091/322] lib/unit: Add Unit Mini ToF90 support. Signed-off-by: tinyu --- docs/en/refs/unit.tof.ref | 2 +- docs/en/refs/unit.tof90.ref | 25 ++ docs/en/units/index.rst | 1 + docs/en/units/tof90.rst | 67 ++++ docs/locales/zh_CN/LC_MESSAGES/units/tof90.po | 332 ++++++++++++++++++ examples/unit/tof90/tof90_core2_example.m5f2 | 1 + examples/unit/tof90/tof90_core2_example.py | 54 +++ m5stack/libs/driver/vl53l0x.py | 166 +++++++-- m5stack/libs/unit/__init__.py | 2 + m5stack/libs/unit/tof.py | 24 ++ m5stack/libs/unit/tof4m.py | 2 + 11 files changed, 649 insertions(+), 27 deletions(-) create mode 100644 docs/en/refs/unit.tof90.ref create mode 100644 docs/en/units/tof90.rst create mode 100644 docs/locales/zh_CN/LC_MESSAGES/units/tof90.po create mode 100644 examples/unit/tof90/tof90_core2_example.m5f2 create mode 100644 examples/unit/tof90/tof90_core2_example.py diff --git a/docs/en/refs/unit.tof.ref b/docs/en/refs/unit.tof.ref index 48fa2adb..a2dc39b3 100644 --- a/docs/en/refs/unit.tof.ref +++ b/docs/en/refs/unit.tof.ref @@ -1,5 +1,5 @@ .. |ToFUnit| image:: https://static-cdn.m5stack.com/resource/docs/products/unit/TOF/img-1389686c-b643-4b4f-a74b-4dc070a32103.webp - :target: https://docs.m5stack.com/zh_CN/unit/TOF + :target: https://docs.m5stack.com/en/unit/ToF :height: 200px :width: 200px diff --git a/docs/en/refs/unit.tof90.ref b/docs/en/refs/unit.tof90.ref new file mode 100644 index 00000000..b239b64c --- /dev/null +++ b/docs/en/refs/unit.tof90.ref @@ -0,0 +1,25 @@ +.. |ToF90Unit| image:: https://static-cdn.m5stack.com/resource/docs/products/unit/TOF90/img-1389686c-b643-4b4f-a74b-4dc070a32103.webp + :target: https://docs.m5stack.com/en/unit/TOF + :height: 200px + :width: 200px + +.. |example.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/minitof90/example.png + +.. |init.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/minitof90/init.png +.. |get_data_ready.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/minitof90/get_data_ready.png +.. |get_range.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/minitof90/get_range.png +.. |is_continuous_mode.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/minitof90/is_continuous_mode.png +.. |get_measurement_timing_budget.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/minitof90/get_measurement_timing_budget.png +.. |set_measurement_timing_budget.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/minitof90/set_measurement_timing_budget.png +.. |set_address.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/minitof90/set_address.png +.. |start_continuous.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/minitof90/start_continuous.png +.. |stop_continuous.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/minitof90/stop_continuous.png + +.. |tof90_core2_example.m5f2| raw:: html + + + tof90_core2_example.m5f2 + diff --git a/docs/en/units/index.rst b/docs/en/units/index.rst index 46c1ac8f..76f8a31d 100644 --- a/docs/en/units/index.rst +++ b/docs/en/units/index.rst @@ -72,6 +72,7 @@ Unit midi.rst minioled.rst miniscale.rst + tof90.rst mqtt.rst mqttpoe.rst nbiot.rst diff --git a/docs/en/units/tof90.rst b/docs/en/units/tof90.rst new file mode 100644 index 00000000..08b46b12 --- /dev/null +++ b/docs/en/units/tof90.rst @@ -0,0 +1,67 @@ +Mini ToF-90° Unit +=================== + +.. sku: U196 + +.. include:: ../refs/unit.tof90.ref + +This is the driver library of Mini ToF-90° Unit, which is used to obtain data from the distance sensor. + +Support the following products: + + |ToF90Unit| + + +UiFlow2 Example +--------------- + +get distance value +^^^^^^^^^^^^^^^^^^^ + +Open the |tof90_core2_example.m5f2| project in UiFlow2. + +This example gets the distance value of the Mini ToF-90° Unit and displays it on the screen. + +UiFlow2 Code Block: + + |example.png| + +Example output: + + None + +MicroPython Example +------------------- + +get distance value +^^^^^^^^^^^^^^^^^^^ + +This example gets the distance value of the Mini ToF-90° Unit and displays it on the screen. + +MicroPython Code Block: + + .. literalinclude:: ../../../examples/unit/tof90/tof90_core2_example.py + :language: python + :linenos: + +Example output: + + None + + +**API** +------- + +ToF90Unit +^^^^^^^^^ + +.. autoclass:: unit.tof.ToF90Unit + :members: + +VL53L0X +^^^^^^^ + +.. autoclass:: driver.vl53l0x.VL53L0X + :members: + :member-order: bysource + :exclude-members: read_range, do_range_measurement, continuous_mode diff --git a/docs/locales/zh_CN/LC_MESSAGES/units/tof90.po b/docs/locales/zh_CN/LC_MESSAGES/units/tof90.po new file mode 100644 index 00000000..6e3b6981 --- /dev/null +++ b/docs/locales/zh_CN/LC_MESSAGES/units/tof90.po @@ -0,0 +1,332 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2016 - 2025 M5Stack Technology Co., Ltd +# This file is distributed under the same license as the UIFlow2 Programming +# Guide package. +# FIRST AUTHOR , 2025. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: UIFlow2 Programming Guide\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-05-19 17:52+0800\n" +"PO-Revision-Date: 2025-05-19 18:01+0800\n" +"Last-Translator: \n" +"Language-Team: zh_CN \n" +"Language: zh_CN\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=1; plural=0;\n" +"Generated-By: Babel 2.16.0\n" +"X-Generator: Poedit 3.6\n" + +#: ../../en/units/tof90.rst:2 defc892e112d45fa9f0344b33a1f3d7c +msgid "Mini ToF-90° Unit" +msgstr "" + +#: ../../en/units/tof90.rst:8 74d959daf6c94871828fba942e082484 +msgid "" +"This is the driver library of Mini ToF-90° Unit, which is used to obtain " +"data from the distance sensor." +msgstr "这是Mini ToF-90° Unit的驱动程序库,用于从距离传感器获取数据。" + +#: ../../en/units/tof90.rst:10 eca9ce7e729948d7b8e75bcccc8c9b6e +msgid "Support the following products:" +msgstr "支持以下产品:" + +#: ../../en/units/tof90.rst:12 fbb6535d88ca4692b690491f23d0d9c3 +msgid "|ToF90Unit|" +msgstr "" + +#: ../../en/refs/unit.tof90.ref ../../en/units/tof90.rst:56 +#: 9cbab2977a4341d3b3bff8a52ba813b1 dc225db623d645c18c6382a5c9870d9b +msgid "ToF90Unit" +msgstr "" + +#: ../../en/units/tof90.rst:16 dcb84f10258c44e0b1eca23004c1921a +msgid "UiFlow2 Example" +msgstr "UiFlow2 示例" + +#: ../../en/units/tof90.rst:19 ../../en/units/tof90.rst:37 +#: 299f7e2c0a1144aa9baf7a083da41891 3a5a6df1d09b4736931ea686679f5bfd +msgid "get distance value" +msgstr "获取距离值" + +#: ../../en/units/tof90.rst:21 67d6305fe81947558674f9a325f06239 +msgid "Open the |tof90_core2_example.m5f2| project in UiFlow2." +msgstr "在 UiFlow2 中打开 |tof90_core2_example.m5f2| 项目。" + +#: ../../en/units/tof90.rst:23 ../../en/units/tof90.rst:39 +#: 90315518a9a749dba627f80e0c179c06 fb638862e2f94f50b30c43a26e6aa072 +msgid "" +"This example gets the distance value of the Mini ToF-90° Unit and displays " +"it on the screen." +msgstr "此示例获取 Mini ToF-90° 设备的距离值并将其显示在屏幕上。" + +#: ../../en/units/tof90.rst:25 07f3792aa6ab4b6dbb260969aee13242 +#: 103bed9398714321836809f81cda7362 2b6f206ef5c74fb39542d3e273b53d92 +#: 2ceee2bf83bb4a1ab800ec907e4e6929 4f7ef10bc7f04d3b92cd67a4c6aec0c5 +#: 5b355f22c47b4f8cb9b2b8b898c22cb6 698c2d551e024767b150fcbd293cdba1 +#: ab3a8f4de5af45e48e75d096174b77ff d54657e63f444e83a650537cda9c3cfa +#: de88a37ac3a844ac9ee4a2e001bdc6e5 driver.vl53l0x.VL53L0X.get_data_ready:6 +#: driver.vl53l0x.VL53L0X.get_measurement_timing_budget:6 +#: driver.vl53l0x.VL53L0X.get_range:6 +#: driver.vl53l0x.VL53L0X.is_continuous_mode:6 +#: driver.vl53l0x.VL53L0X.set_address:5 +#: driver.vl53l0x.VL53L0X.set_measurement_timing_budget:5 +#: driver.vl53l0x.VL53L0X.start_continuous:3 +#: driver.vl53l0x.VL53L0X.stop_continuous:3 of unit.tof.ToF90Unit:6 +msgid "UiFlow2 Code Block:" +msgstr "UiFlow2 代码块:" + +#: ../../en/units/tof90.rst:27 acc943c9e9a64e258df534f8cc72144c +msgid "|example.png|" +msgstr "" + +#: ../../en/refs/unit.tof90.ref:6 dfdc5c0d58bc484ab0a0efba2671d7b0 +msgid "example.png" +msgstr "" + +#: ../../en/units/tof90.rst:29 ../../en/units/tof90.rst:47 +#: 051148b9b0e340b3af6ad955f5d0a74c a5bddc23664948599f96156bf833fa3a +msgid "Example output:" +msgstr "示例输出:" + +#: ../../en/units/tof90.rst:31 ../../en/units/tof90.rst:49 +#: 381dc06a748049e39a5c054c92d78d96 754c7929c6674d0397fd1cf410682a37 +msgid "None" +msgstr "" + +#: ../../en/units/tof90.rst:34 71db5e7e9be0456bac9d27296779fcf4 +msgid "MicroPython Example" +msgstr "MicroPython 示例" + +#: ../../en/units/tof90.rst:41 105e73fea3b042efa2f0a37519930e0a +#: 1eba19dfe9be4bd9b129e610266f1360 73500a9ce26c4e358b1a78e187d7f7c8 +#: 7c327f8743a345a7b3e64a72696e5a06 8013d15729604fe9a50bd99442d66b20 +#: 88d0c67f15824d79891db0ee2d9908cb 9251e4555cda49289e5aa1f9d4277b54 +#: cf20cdbac97645cbb525299b18d8265f d016a41928644c1894314de370c157e9 +#: driver.vl53l0x.VL53L0X:7 driver.vl53l0x.VL53L0X.get_data_ready:10 +#: driver.vl53l0x.VL53L0X.get_distance:6 +#: driver.vl53l0x.VL53L0X.get_measurement_timing_budget:10 +#: driver.vl53l0x.VL53L0X.get_range:10 +#: driver.vl53l0x.VL53L0X.is_continuous_mode:10 +#: driver.vl53l0x.VL53L0X.set_address:9 +#: driver.vl53l0x.VL53L0X.set_measurement_timing_budget:9 +#: driver.vl53l0x.VL53L0X.start_continuous:7 +#: driver.vl53l0x.VL53L0X.stop_continuous:7 e887fbebe2a349dd9d7acd4b59921775 +#: f68b0c057e9e4e1cbe46060cff70d066 fc2efb90f70e491da8118b05bc58466c of +#: unit.tof.ToF90Unit:10 +msgid "MicroPython Code Block:" +msgstr "MicroPython 代码块:" + +#: ../../en/units/tof90.rst:53 10e37bec5f684092991fc6df1bd36b94 +msgid "**API**" +msgstr "" + +#: 487c3f5a446c486a8fad578319014d30 of unit.tof.ToF90Unit:1 +msgid "Bases: :py:class:`~driver.vl53l0x.VL53L0X`" +msgstr "" + +#: 87dddd3f8ae84cfeb915263bc9eb86d6 dba958eda6df42b1a7c24515c90278de +#: driver.vl53l0x.VL53L0X:1 of unit.tof.ToF90Unit:1 +msgid "Create an VL53L0X object." +msgstr "创建一个 VL53L0X 对象。" + +#: ../../en/units/tof90.rst 1abfedd636654bcca880e383bfe71d66 +#: 796dae69ee40484ca2aba4b8276acc78 driver.vl53l0x.VL53L0X.set_address +#: driver.vl53l0x.VL53L0X.set_measurement_timing_budget +#: eefa763796c6421fb7e414cfaade0958 f99d89247595426f8b4ec798fd165267 of +#: unit.tof.ToF90Unit +msgid "Parameters" +msgstr "参数" + +#: 1885e79ef94540d185970e505a66742d 7ba5e6d504bf4ddc9dbf752760408415 +#: driver.vl53l0x.VL53L0X:3 of unit.tof.ToF90Unit:3 +msgid "The I2C bus the VL53L0X is connected to." +msgstr "VL53L0X 连接的 I2C 总线。" + +#: 0289c89690cf4d19be94221150f4d355 7a5a0ad99f744ccda47c528399fceb97 +#: driver.vl53l0x.VL53L0X:4 of unit.tof.ToF90Unit:4 +msgid "The I2C address of VL53L0X. Default is 0x29." +msgstr "VL53L0X 的 I2C 地址。默认为 0x29。" + +#: a82967b27db744f7b3b486a9c8e27321 of unit.tof.ToF90Unit:8 +msgid "|init.png|" +msgstr "" + +#: ../../en/refs/unit.tof90.ref:8 8466c08c4a0b48d59ce288df40c62ded +msgid "init.png" +msgstr "" + +#: ../../en/units/tof90.rst:62 2748c78c94344990b71907e1a06b030d +msgid "VL53L0X" +msgstr "" + +#: driver.vl53l0x.VL53L0X:1 ed40f8374f644d279da1c658d8a2cbca of +msgid "Bases: :py:class:`object`" +msgstr "" + +#: 7a5f9bbaa37148898362d87fcd0e601a driver.vl53l0x.VL53L0X:5 of +msgid "The timeout for the I/O operations. Default is 0." +msgstr "I/O 操作的超时时间。默认为 0。" + +#: a3b970d50fe4424285118cadb524847b +#: driver.vl53l0x.VL53L0X.get_measurement_timing_budget:1 of +msgid "Get the measurement timing budget in microseconds." +msgstr "获取以微秒为单位的测量定时预算。" + +#: 15263311b82f41808423944dc8875bff 9e25f2ff0ccc49d88fbd02d91a76e25f +#: c9fcd6e5e5134eba9b56ec907ec2952a d82c33b29c6540789164da0b545cc6c7 +#: driver.vl53l0x.VL53L0X.get_data_ready driver.vl53l0x.VL53L0X.get_distance +#: driver.vl53l0x.VL53L0X.get_measurement_timing_budget +#: driver.vl53l0x.VL53L0X.get_range driver.vl53l0x.VL53L0X.is_continuous_mode +#: e190ee0fffd44c6e8f8f15186bc0341d of +msgid "Returns" +msgstr "" + +#: 41058d2fb08149ccafd99af967594f6a +#: driver.vl53l0x.VL53L0X.get_measurement_timing_budget:3 of +msgid "The measurement timing budget in microseconds." +msgstr "以微秒为单位的测量定时预算。" + +#: 450c39564d5e47dfbf572b2998286a25 633f81c8926d418384cc47ea2b037ec3 +#: 743dd7fddb774b55b153efe2fe6c6089 a11d53ba27ee41cc9b0b69bcc3552f44 +#: b96a1b1f3d874fb4bdea398176640778 be400c28684a4484a135eb8c417b087a +#: c0fded0644c746f3ac6a8012c85ae044 c2ad65e27a72418aaadc708e170d6352 +#: driver.vl53l0x.VL53L0X.get_data_ready driver.vl53l0x.VL53L0X.get_distance +#: driver.vl53l0x.VL53L0X.get_measurement_timing_budget +#: driver.vl53l0x.VL53L0X.get_range driver.vl53l0x.VL53L0X.is_continuous_mode +#: driver.vl53l0x.VL53L0X.set_address +#: driver.vl53l0x.VL53L0X.set_measurement_timing_budget +#: driver.vl53l0x.VL53L0X.start_continuous +#: driver.vl53l0x.VL53L0X.stop_continuous feb537ff67004cd0b4368cd88d2bec5a of +msgid "Return type" +msgstr "返回类型" + +#: 55f2372ddddd4a2c9c6aea79c765147c a9ad03d235374a06a9b165ffb1519420 +#: driver.vl53l0x.VL53L0X.get_measurement_timing_budget:8 +#: driver.vl53l0x.VL53L0X.set_measurement_timing_budget:7 of +msgid "|get_measurement_timing_budget.png|" +msgstr "" + +#: ../../en/refs/unit.tof90.ref:12 87874dc4582b41f99f6dd53d2333c8db +#: 8e2b669ddf194a53b8dcd5a067f835cb +msgid "get_measurement_timing_budget.png" +msgstr "" + +#: 459812deafdc456987668a2d63db4a06 +#: driver.vl53l0x.VL53L0X.set_measurement_timing_budget:1 of +msgid "Set the measurement timing budget in microseconds." +msgstr "以微秒为单位设置测量定时预算。" + +#: 5d6393a84600418ba5fe7ed23f967399 +#: driver.vl53l0x.VL53L0X.set_measurement_timing_budget:3 of +msgid "The measurement timing budget in microseconds(range 20000 - 200000)." +msgstr "以微秒为单位的测量定时预算(范围 20000 - 200000)。" + +#: 93efc59c54964eb8abb3028e931a9dd6 driver.vl53l0x.VL53L0X.get_distance:1 of +msgid "" +"Perform a single reading of the range for an object in front of the sensor " +"and return the distance in centimeters." +msgstr "单次读取传感器前方物体的距离,并以厘米为单位返回。" + +#: driver.vl53l0x.VL53L0X.get_distance:3 ec1f3ea170c640b3af09061435fcfdb5 of +msgid "The distance in centimeters." +msgstr "以厘米为单位的距离。" + +#: dea292ebe67c4499b1e1a708d71b623d driver.vl53l0x.VL53L0X.get_range:1 of +msgid "" +"Perform a single reading of the range for an object in front of the sensor " +"and return the distance in millimeters." +msgstr "单次读取传感器前方物体的距离,并以毫米为单位返回。" + +#: 0ef26f9681e046a0b6e87befe023101a driver.vl53l0x.VL53L0X.get_range:3 of +msgid "The distance in millimeters." +msgstr "以毫米为单位的距离。" + +#: 12e1167260ed4b6590b7a94a29cc5a4d driver.vl53l0x.VL53L0X.get_range:8 of +msgid "|get_range.png|" +msgstr "" + +#: ../../en/refs/unit.tof90.ref:10 843aa13dd42b43b386caefb0aae4cefc +msgid "get_range.png" +msgstr "" + +#: 15528034d69142ecb6732f9134db18c5 driver.vl53l0x.VL53L0X.get_data_ready:1 of +msgid "Get the data ready status of the sensor." +msgstr "获取传感器的数据就绪状态。" + +#: 01d9aaa0e21a4230a84f6b01ed8ea342 driver.vl53l0x.VL53L0X.get_data_ready:3 of +msgid "The data ready status of the sensor." +msgstr "传感器的数据就绪状态。" + +#: 9cc572bfe2e646a4bf4aa8347828e1aa driver.vl53l0x.VL53L0X.get_data_ready:8 of +msgid "|get_data_ready.png|" +msgstr "" + +#: ../../en/refs/unit.tof90.ref:9 9230cae9964646868e49e780de6d08f9 +msgid "get_data_ready.png" +msgstr "" + +#: 6c11f89b30b74768bf0b5531165b0f06 driver.vl53l0x.VL53L0X.is_continuous_mode:1 +#: of +msgid "Get the continuous mode status of the sensor." +msgstr "获取传感器的连续模式状态。" + +#: driver.vl53l0x.VL53L0X.is_continuous_mode:3 fe9340ffe27345fd8fd7c2dcf5bcb4c5 +#: of +msgid "The continuous mode status of the sensor." +msgstr "传感器的连续模式状态。" + +#: 36f7dfb8b95c4034bdc65ed901a9293a driver.vl53l0x.VL53L0X.is_continuous_mode:8 +#: of +msgid "|is_continuous_mode.png|" +msgstr "" + +#: ../../en/refs/unit.tof90.ref:11 b76b41352e494c6a8111605d92f4f00e +msgid "is_continuous_mode.png" +msgstr "" + +#: 66ee3834bad5486997fd143ec7393896 driver.vl53l0x.VL53L0X.start_continuous:1 +#: of +msgid "Set the sensor to continuous mode." +msgstr "将传感器设置为连续模式。" + +#: 7210fb9541764db09e14d32f693409f8 driver.vl53l0x.VL53L0X.start_continuous:5 +#: of +msgid "|start_continuous.png|" +msgstr "" + +#: ../../en/refs/unit.tof90.ref:15 e6a535b0edfa4a5995724acc0b87dc05 +msgid "start_continuous.png" +msgstr "" + +#: 6e299f710fb1407c902b987480b31a37 driver.vl53l0x.VL53L0X.stop_continuous:1 of +msgid "Set the sensor to single ranging mode." +msgstr "将传感器设置为单次测距模式。" + +#: driver.vl53l0x.VL53L0X.stop_continuous:5 fe6c557498cf4e8d8c253c725089a392 of +msgid "|stop_continuous.png|" +msgstr "" + +#: ../../en/refs/unit.tof90.ref:16 3a2dbf24d9d146e0bad7307af96c7e84 +msgid "stop_continuous.png" +msgstr "" + +#: 3ad83955ba56428ba771973cfa7dfc47 driver.vl53l0x.VL53L0X.set_address:1 of +msgid "Set a new I2C address to the sensor." +msgstr "为传感器设置新的 I2C 地址。" + +#: c3587b4f7aa04195b351c7b8fc1de86b driver.vl53l0x.VL53L0X.set_address:3 of +msgid "The 7-bit int that is to be assigned to the VL53L0X sensor." +msgstr "将分配给 VL53L0X 传感器的I2C地址" + +#: 62f72e1bc0024212a00294f19b0a149d driver.vl53l0x.VL53L0X.set_address:7 of +msgid "|set_address.png|" +msgstr "" + +#: ../../en/refs/unit.tof90.ref:14 94dccd65cf4c46008a91c10fc3a8aebb +msgid "set_address.png" +msgstr "" diff --git a/examples/unit/tof90/tof90_core2_example.m5f2 b/examples/unit/tof90/tof90_core2_example.m5f2 new file mode 100644 index 00000000..0627c79d --- /dev/null +++ b/examples/unit/tof90/tof90_core2_example.m5f2 @@ -0,0 +1 @@ +{"version":"V2.0","versionNumber":"V2.2.7","type":"core2","components":[{"name":"screen","type":"screen","layer":0,"screenId":"builtin","screenName":"","id":"__core2_screen","createTime":1747725188678,"x":0,"y":0,"width":320,"height":240,"backgroundColor":"#222222","size":0,"isSelected":true},{"name":"title0","type":"title","layer":1,"screenId":"builtin","screenName":"","id":"ljIz4N+nI6BY*9t$","createTime":1747725202349,"x":0,"y":0,"color":"#ffffff","backgroundColor":"#0000FF","text":"Core2 Mini ToF-90 Example","textOffset":3,"font":"Widgets.FONTS.DejaVu18","isSelected":false},{"name":"label0","type":"label","layer":2,"screenId":"builtin","screenName":"","id":"tvm6ke+!F4KU`4HP","createTime":1747725295255,"x":2,"y":110,"color":"#ffffff","backgroundColor":"#222222","text":"label0","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false,"width":58,"height":20},{"name":"label1","type":"label","layer":3,"screenId":"builtin","screenName":"","id":"bpeT1pgUk9Re60`G","createTime":1747725298910,"x":-85,"y":149,"color":"#ffffff","backgroundColor":"#222222","text":"label1","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false,"width":58,"height":20}],"resources":[{"hardware":["hardware_button","hardware_pin_button","imu","speaker","touch","mic","i2c"]},{"unit":["unit_minitof90"]}],"units":[{"type":"unit_minitof90","name":"minitof90_0","portList":["A","PAHUB","Custom"],"portType":"A","userPort":[22,21],"id":"htgjN=67#&89aXXG","createTime":1747725192157,"bus":"i2c0","pahubPortList":[0,1,2,3,4,5],"pahubPort":0,"initBlockId":";)G2sD;uaogs/7;1DZG_"}],"hats":[],"bases":[],"i2cs":[{"id":"i2c0","portType":"A","userPort":[22,21],"freq":"100000","blockId":"e(#2ayx3|^Ll5J5w*vH3"}],"blockly":"true01000003332minitof90_00x29minitof90_0trueminitof90_0label0LabelDistance:hello M5minitof90_0mm","screen":[{"simulationName":"Built-in","type":"builtin","width":320,"height":240,"scale":0.78,"screenName":"","blockId":"","screenColorType":0,"id":"builtin","createTime":1747725188676}],"logicWhenNum":0,"customList":[]} \ No newline at end of file diff --git a/examples/unit/tof90/tof90_core2_example.py b/examples/unit/tof90/tof90_core2_example.py new file mode 100644 index 00000000..4c4a95fd --- /dev/null +++ b/examples/unit/tof90/tof90_core2_example.py @@ -0,0 +1,54 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import os, sys, io +import M5 +from M5 import * +from hardware import I2C +from hardware import Pin +from unit import ToF90Unit + + +title0 = None +label0 = None +label1 = None +i2c0 = None +minitof90_0 = None + + +def setup(): + global title0, label0, label1, i2c0, minitof90_0 + + M5.begin() + Widgets.fillScreen(0x222222) + title0 = Widgets.Title( + "Core2 Mini ToF-90 Example", 3, 0xFFFFFF, 0x0000FF, Widgets.FONTS.DejaVu18 + ) + label0 = Widgets.Label("label0", 2, 110, 1.0, 0xFFFFFF, 0x222222, Widgets.FONTS.DejaVu18) + label1 = Widgets.Label("label1", -85, 149, 1.0, 0xFFFFFF, 0x222222, Widgets.FONTS.DejaVu18) + + i2c0 = I2C(0, scl=Pin(33), sda=Pin(32), freq=100000) + minitof90_0 = ToF90Unit(i2c0, 0x29) + minitof90_0.start_continuous() + + +def loop(): + global title0, label0, label1, i2c0, minitof90_0 + M5.update() + if minitof90_0.get_data_ready(): + label0.setText(str((str("Distance:") + str((str((minitof90_0.get_range())) + str("mm")))))) + + +if __name__ == "__main__": + try: + setup() + while True: + loop() + except (Exception, KeyboardInterrupt) as e: + try: + from utility import print_error_msg + + print_error_msg(e) + except ImportError: + print("please update to latest firmware") diff --git a/m5stack/libs/driver/vl53l0x.py b/m5stack/libs/driver/vl53l0x.py index 74cce6f6..fddab550 100644 --- a/m5stack/libs/driver/vl53l0x.py +++ b/m5stack/libs/driver/vl53l0x.py @@ -110,7 +110,21 @@ def _timeout_microseconds_to_mclks(timeout_period_us: int, vcsel_period_pclks: i class VL53L0X: - """Driver for the VL53L0X distance sensor.""" + """Create an VL53L0X object. + + :param I2C i2c: The I2C bus the VL53L0X is connected to. + :param int address: The I2C address of VL53L0X. Default is 0x29. + :param int io_timeout_ms: The timeout for the I/O operations. Default is 0. + + MicroPython Code Block: + + .. code-block:: python + + from driver import VL53L0X + + i2c0 = I2C(0, scl=Pin(1), sda=Pin(2), freq=100000) + vl53l0x_0 = VL53L0X(i2c0) + """ # Class-level buffer for reading and writing data with the sensor. # This reduces memory allocations but means the code is not re-entrant or @@ -126,6 +140,8 @@ def __init__(self, i2c: I2C, address: int = 41, io_timeout_ms: int = 0) -> None: self._addr = address self.io_timeout_ms = io_timeout_ms self._data_ready = False + if self._i2c not in self._i2c.scan(): + raise Exception("VL53L0X maybe not connect.") self._reset() # Check identification registers for expected values. @@ -427,7 +443,7 @@ def _get_sequence_step_timeouts(self, pre_range: int) -> Tuple[int, int, int, in ) def get_signal_rate_limit(self) -> float: - """The signal rate limit in mega counts per second.""" + # The signal rate limit in mega counts per second. val = self._read_u16(_FINAL_RANGE_CONFIG_MIN_COUNT_RATE_RTN_LIMIT) # Return value converted from 16-bit 9.7 fixed point to float. return val / (1 << 7) @@ -439,7 +455,21 @@ def set_signal_rate_limit(self, val: float) -> None: self._write_u16(_FINAL_RANGE_CONFIG_MIN_COUNT_RATE_RTN_LIMIT, val) def get_measurement_timing_budget(self) -> int: - """The measurement timing budget in microseconds.""" + """Get the measurement timing budget in microseconds. + + :returns: The measurement timing budget in microseconds. + :rtype: int + + UiFlow2 Code Block: + + |get_measurement_timing_budget.png| + + MicroPython Code Block: + + .. code-block:: python + + budget_ms = vl53l0x_0.get_measurement_timing_budget() + """ budget_us = 1910 + 960 # Start overhead + end overhead. tcc, dss, msrc, pre_range, final_range = self._get_sequence_step_enables() step_timeouts = self._get_sequence_step_timeouts(pre_range) @@ -458,6 +488,20 @@ def get_measurement_timing_budget(self) -> int: return budget_us def set_measurement_timing_budget(self, budget_us: int) -> None: + """Set the measurement timing budget in microseconds. + + :param int budget_us: The measurement timing budget in microseconds(range 20000 - 200000). + + UiFlow2 Code Block: + + |get_measurement_timing_budget.png| + + MicroPython Code Block: + + .. code-block:: python + + budget_ms = vl53l0x_0.get_measurement_timing_budget() + """ # pylint: disable=too-many-locals assert budget_us >= 20000 used_budget_us = 1320 + 960 # Start (diff from get) + end overhead @@ -495,15 +539,34 @@ def set_measurement_timing_budget(self, budget_us: int) -> None: self._measurement_timing_budget_us = budget_us def get_distance(self) -> float: - """Perform a single reading of the range for an object in front of - the sensor and return the distance in centimeters. + """Perform a single reading of the range for an object in front of the sensor and return the distance in centimeters. + + :returns: The distance in centimeters. + :rtype: float + + MicroPython Code Block: + + .. code-block:: python + + distance = vl53l0x_0.get_distance() """ return self.get_range() / 10 def get_range(self) -> int: - """Perform a single (or continuous if `start_continuous` called) - reading of the range for an object in front of the sensor and - return the distance in millimeters. + """Perform a single reading of the range for an object in front of the sensor and return the distance in millimeters. + + :returns: The distance in millimeters. + :rtype: float + + UiFlow2 Code Block: + + |get_range.png| + + MicroPython Code Block: + + .. code-block:: python + + distance = vl53l0x_0.get_range() """ # Adapted from readRangeSingleMillimeters in pololu code at: # https://github.com/pololu/vl53l0x-arduino/blob/master/VL53L0X.cpp @@ -512,9 +575,21 @@ def get_range(self) -> int: return self.read_range() def get_data_ready(self) -> bool: - """Check if data is available from the sensor. If true a call to .range - will return quickly. If false, calls to .range will wait for the sensor's - next reading to be available.""" + """Get the data ready status of the sensor. + + :returns: The data ready status of the sensor. + :rtype: bool + + UiFlow2 Code Block: + + |get_data_ready.png| + + MicroPython Code Block: + + .. code-block:: python + + data_ready = vl53l0x_0.get_data_ready() + """ if not self._data_ready: self._data_ready = self._read_u8(_RESULT_INTERRUPT_STATUS) & 0x07 != 0 return self._data_ready @@ -558,7 +633,22 @@ def read_range(self) -> int: return range_mm def is_continuous_mode(self) -> bool: - """Is the sensor currently in continuous mode?""" + """ + Get the continuous mode status of the sensor. + + :returns: The continuous mode status of the sensor. + :rtype: bool + + UiFlow2 Code Block: + + |is_continuous_mode.png| + + MicroPython Code Block: + + .. code-block:: python + + continuous_mode = vl53l0x_0.is_continuous_mode() + """ return self._continuous_mode def continuous_mode(self) -> "VL53L0X": @@ -580,8 +670,18 @@ def __exit__( self.stop_continuous() def start_continuous(self) -> None: - """Perform a continuous reading of the range for an object in front of - the sensor. + """ + Set the sensor to continuous mode. + + UiFlow2 Code Block: + + |start_continuous.png| + + MicroPython Code Block: + + .. code-block:: python + + vl53l0x_0.start_continuous() """ # Adapted from startContinuous in pololu code at: # https://github.com/pololu/vl53l0x-arduino/blob/master/VL53L0X.cpp @@ -600,7 +700,19 @@ def start_continuous(self) -> None: self._continuous_mode = True def stop_continuous(self) -> None: - """Stop continuous readings.""" + """ + Set the sensor to single ranging mode. + + UiFlow2 Code Block: + + |stop_continuous.png| + + MicroPython Code Block: + + .. code-block:: python + + vl53l0x_0.stop_continuous() + """ # Adapted from stopContinuous in pololu code at: # https://github.com/pololu/vl53l0x-arduino/blob/master/VL53L0X.cpp self._write_u8(_SYSRANGE_START, 0x01) @@ -614,19 +726,21 @@ def stop_continuous(self) -> None: # restore the sensor to single ranging mode self.do_range_measurement() - def set_address(self, new_address: int) -> None: - """Set a new I2C address to the instantaited object. This is only called when using - multiple VL53L0X sensors on the same I2C bus (SDA & SCL pins). See also the - `example `_ for proper usage. + def set_address(self, new_address: int = 0x29) -> None: + """ + Set a new I2C address to the sensor. + + :param int new_address: The 7-bit int that is to be assigned to the VL53L0X sensor. + + UiFlow2 Code Block: + + |set_address.png| + + MicroPython Code Block: - :param int new_address: The 7-bit `int` that is to be assigned to the VL53L0X sensor. - The address that is assigned should NOT be already in use by another device on the - I2C bus. + .. code-block:: python - .. important:: To properly set the address to an individual VL53L0X sensor, you must - first ensure that all other VL53L0X sensors (using the default address of ``0x29``) - on the same I2C bus are in their off state by pulling the "SHDN" pins LOW. When the - "SHDN" pin is pulled HIGH again the default I2C address is ``0x29``. + vl53l0x_0.set_address(0x2A) """ self._write_u8(_I2C_SLAVE_DEVICE_ADDRESS, new_address & 0x7F) self._addr = new_address diff --git a/m5stack/libs/unit/__init__.py b/m5stack/libs/unit/__init__.py index 72028eff..15aac283 100644 --- a/m5stack/libs/unit/__init__.py +++ b/m5stack/libs/unit/__init__.py @@ -118,6 +118,8 @@ "TimerPWRUnit": "timerpwr", "TMOSUnit": "tmos", "ToFUnit": "tof", + "ToF90Unit": "tof", + "ToF4MUnit": "tof4m", "TOF4MUnit": "tof4m", "TubePressureUnit": "tube_pressure", "TVOCUnit": "tvoc", diff --git a/m5stack/libs/unit/tof.py b/m5stack/libs/unit/tof.py index 984fb57d..23d2720f 100644 --- a/m5stack/libs/unit/tof.py +++ b/m5stack/libs/unit/tof.py @@ -3,3 +3,27 @@ # SPDX-License-Identifier: MIT from driver.vl53l0x import VL53L0X as ToFUnit + + +class ToF90Unit(ToFUnit): + """Create an VL53L0X object. + + :param I2C i2c: The I2C bus the VL53L0X is connected to. + :param int address: The I2C address of VL53L0X. Default is 0x29. + + UiFlow2 Code Block: + + |init.png| + + MicroPython Code Block: + + .. code-block:: python + + from unit import ToF90Unit + + i2c0 = I2C(0, scl=Pin(1), sda=Pin(2), freq=100000) + tof_0 = ToF90Unit(i2c0) + """ + + def __init__(self, i2c, address=0x29): + super().__init__(i2c, address=address) diff --git a/m5stack/libs/unit/tof4m.py b/m5stack/libs/unit/tof4m.py index 7dcd56e7..c18b5dc5 100644 --- a/m5stack/libs/unit/tof4m.py +++ b/m5stack/libs/unit/tof4m.py @@ -3,3 +3,5 @@ # SPDX-License-Identifier: MIT from driver.vl53l1x import VL53L1X as TOF4MUnit + +ToF4MUnit = TOF4MUnit From dda983e3f20296ea1f63d67005e8b2c1f42799f2 Mon Sep 17 00:00:00 2001 From: tinyu Date: Thu, 29 May 2025 10:15:51 +0800 Subject: [PATCH 092/322] docs: Add Relay2 Unit and Tube Pressure Unit docs. Signed-off-by: tinyu --- docs/en/refs/unit.relay2.ref | 21 ++ docs/en/refs/unit.tube_pressure.ref | 23 ++ docs/en/units/index.rst | 2 + docs/en/units/relay.rst | 2 +- docs/en/units/relay2.rst | 59 +++++ docs/en/units/tube_pressure.rst | 59 +++++ .../locales/zh_CN/LC_MESSAGES/units/relay2.po | 191 ++++++++++++++++ .../zh_CN/LC_MESSAGES/units/tube_pressure.po | 216 ++++++++++++++++++ .../unit/relay2/relay2_core2_example.m5f2 | 1 + examples/unit/relay2/relay2_core2_example.py | 57 +++++ .../tube_pressure_core2_example.m5f2 | 1 + .../tube_pressure_core2_example.py | 52 +++++ m5stack/libs/unit/relay2.py | 48 ++++ m5stack/libs/unit/tube_pressure.py | 63 +++++ 14 files changed, 794 insertions(+), 1 deletion(-) create mode 100644 docs/en/refs/unit.relay2.ref create mode 100644 docs/en/refs/unit.tube_pressure.ref create mode 100644 docs/en/units/relay2.rst create mode 100644 docs/en/units/tube_pressure.rst create mode 100644 docs/locales/zh_CN/LC_MESSAGES/units/relay2.po create mode 100644 docs/locales/zh_CN/LC_MESSAGES/units/tube_pressure.po create mode 100644 examples/unit/relay2/relay2_core2_example.m5f2 create mode 100644 examples/unit/relay2/relay2_core2_example.py create mode 100644 examples/unit/tube_pressure/tube_pressure_core2_example.m5f2 create mode 100644 examples/unit/tube_pressure/tube_pressure_core2_example.py diff --git a/docs/en/refs/unit.relay2.ref b/docs/en/refs/unit.relay2.ref new file mode 100644 index 00000000..fc1ecee4 --- /dev/null +++ b/docs/en/refs/unit.relay2.ref @@ -0,0 +1,21 @@ +.. |RELAY2| image:: https://static-cdn.m5stack.com/resource/docs/products/unit/2relay/2relay_01.webp + :target: https://docs.m5stack.com/en/unit/2relay + :height: 200px + :width: 200px + +.. |example.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/2relay/example.png + +.. |init.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/2relay/init.png + +.. |get_relay_status.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/2relay/get_relay_status.png + +.. |set_relay_cntrl.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/2relay/set_relay_cntrl.png + +.. |relay2_core2_example.m5f2| raw:: html + + + relay2_core2_example.m5f2 + diff --git a/docs/en/refs/unit.tube_pressure.ref b/docs/en/refs/unit.tube_pressure.ref new file mode 100644 index 00000000..a82bd5c4 --- /dev/null +++ b/docs/en/refs/unit.tube_pressure.ref @@ -0,0 +1,23 @@ +.. |Tube Pressure| image:: https://static-cdn.m5stack.com/resource/docs/products/unit/tube_pressure/tube_pressure_01.webp + :target: https://docs.m5stack.com/en/unit/tube_pressure + :height: 200px + :width: 200px + +.. |example.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/tube_pressure/example.png + +.. |init.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/tube_pressure/init.png + +.. |get_pressure.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/tube_pressure/get_pressure.png + +.. |get_voltage.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/tube_pressure/get_voltage.png + +.. |get_analog_value.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/tube_pressure/get_analog_value.png + +.. |tube_pressure_core2_example.m5f2| raw:: html + + + tube_pressure_core2_example.m5f2 + diff --git a/docs/en/units/index.rst b/docs/en/units/index.rst index 76f8a31d..787693ef 100644 --- a/docs/en/units/index.rst +++ b/docs/en/units/index.rst @@ -90,6 +90,7 @@ Unit rca.rst reflective_ir.rst relay.rst + relay2.rst relay4.rst rf433r.rst rf433t.rst @@ -107,6 +108,7 @@ Unit tmos.rst tof.rst tof4m.rst + tube_pressure.rst tvoc.rst uhf_rfid.rst ultrasonic.rst diff --git a/docs/en/units/relay.rst b/docs/en/units/relay.rst index c5affa41..5634aeaf 100644 --- a/docs/en/units/relay.rst +++ b/docs/en/units/relay.rst @@ -1,4 +1,4 @@ -RELAY Unit +Relay Unit ================== .. include:: ../refs/unit.relay.ref diff --git a/docs/en/units/relay2.rst b/docs/en/units/relay2.rst new file mode 100644 index 00000000..f9c9792d --- /dev/null +++ b/docs/en/units/relay2.rst @@ -0,0 +1,59 @@ +Relay2 Unit +================== + +.. sku: U131 + +.. include:: ../refs/unit.relay2.ref + +This is the driver library of Relay2 Unit, which is used to control the relay. + +Support the following products: + + |RELAY2| + + +UiFlow2 Example +--------------- + +control relay +^^^^^^^^^^^^^^^ + +Open the |relay2_core2_example.m5f2| project in UiFlow2. + +This example controls the relay of the Relay2 Unit and displays it on the screen. + +UiFlow2 Code Block: + + |example.png| + +Example output: + + None + +MicroPython Example +------------------- + +control relay +^^^^^^^^^^^^^^^ + +This example controls the relay of the Relay2 Unit and displays it on the screen. + +MicroPython Code Block: + + .. literalinclude:: ../../../examples/unit/relay2/relay2_core2_example.py + :language: python + :linenos: + +Example output: + + None + + +**API** +------- + +Relay2Unit +^^^^^^^^^^ + +.. autoclass:: unit.relay2.Relay2Unit + :members: diff --git a/docs/en/units/tube_pressure.rst b/docs/en/units/tube_pressure.rst new file mode 100644 index 00000000..0e0d1625 --- /dev/null +++ b/docs/en/units/tube_pressure.rst @@ -0,0 +1,59 @@ +Tube Pressure Unit +================== + +.. sku: U131 + +.. include:: ../refs/unit.tube_pressure.ref + +This is the driver library of Tube Pressure Unit, which is used to control the pressure sensor. + +Support the following products: + + |Tube Pressure| + + +UiFlow2 Example +--------------- + +get pressure value +^^^^^^^^^^^^^^^^^^ + +Open the |tube_pressure_core2_example.m5f2| project in UiFlow2. + +The example shows the pressure value of the tube pressure unit. + +UiFlow2 Code Block: + + |example.png| + +Example output: + + None + +MicroPython Example +------------------- + +get pressure value +^^^^^^^^^^^^^^^^^^ + +The example shows the pressure value of the tube pressure unit. + +MicroPython Code Block: + + .. literalinclude:: ../../../examples/unit/tube_pressure/tube_pressure_core2_example.py + :language: python + :linenos: + +Example output: + + None + + +**API** +------- + +TubePressureUnit +^^^^^^^^^^^^^^^^ + +.. autoclass:: unit.tube_pressure.TubePressureUnit + :members: diff --git a/docs/locales/zh_CN/LC_MESSAGES/units/relay2.po b/docs/locales/zh_CN/LC_MESSAGES/units/relay2.po new file mode 100644 index 00000000..99e952e7 --- /dev/null +++ b/docs/locales/zh_CN/LC_MESSAGES/units/relay2.po @@ -0,0 +1,191 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2016 - 2025 M5Stack Technology Co., Ltd +# This file is distributed under the same license as the UIFlow2 Programming +# Guide package. +# FIRST AUTHOR , 2025. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: UIFlow2 Programming Guide\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-05-27 17:57+0800\n" +"PO-Revision-Date: 2025-05-27 18:00+0800\n" +"Last-Translator: \n" +"Language-Team: zh_CN \n" +"Language: zh_CN\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=1; plural=0;\n" +"Generated-By: Babel 2.16.0\n" +"X-Generator: Poedit 3.6\n" + +#: ../../en/units/relay2.rst:2 a2e720fca2a34d97aea3c075208200ae +msgid "Relay2 Unit" +msgstr "" + +#: ../../en/units/relay2.rst:8 7ab6b43c52f44c1198b9080f6ac68efa +msgid "" +"This is the driver library of Relay2 Unit, which is used to control the " +"relay." +msgstr "Relay2 Unit 驱动库,用于控制继电器。" + +#: ../../en/units/relay2.rst:10 e37bc949a4794cd5a89cd18bf9252d36 +msgid "Support the following products:" +msgstr "支持以下产品:" + +#: ../../en/units/relay2.rst:12 8b410f03be324787bab54d1c718bd103 +msgid "|RELAY2|" +msgstr "" + +#: ../../en/refs/unit.relay2.ref fd1b8eb90a9e4d9ebe7d3ef8b0050dd8 +msgid "RELAY2" +msgstr "" + +#: ../../en/units/relay2.rst:16 19d84e2547de4b5f8bc7a2ed1c4c2ac3 +msgid "UiFlow2 Example" +msgstr "UiFlow2 示例" + +#: ../../en/units/relay2.rst:19 ../../en/units/relay2.rst:37 +#: 628867eba78e48c5990a30572f081378 c943c88b52d7418ca18eded1a226f283 +msgid "control relay" +msgstr "控制继电器" + +#: ../../en/units/relay2.rst:21 357871833610416097c51afa073c8367 +msgid "Open the |relay2_core2_example.m5f2| project in UiFlow2." +msgstr "在 UiFlow2 中打开 |relay2_core2_example.m5f2| 项目。" + +#: ../../en/units/relay2.rst:23 ../../en/units/relay2.rst:39 +#: 0340a02062a547fb8b0cca28f16d9786 26f16f0acfb94afc815161975ce27a99 +msgid "" +"This example controls the relay of the Relay2 Unit and displays it on the " +"screen." +msgstr "该示例控制 Relay2 Unit 的继电器并将其状态显示在屏幕上。" + +#: ../../en/units/relay2.rst:25 1cc34e995c1b4a9781bc4eb0b857069b +#: 336cae8b0ab44cda925a85feca672aab 7cd59b02af0145f28223a1d3f772c263 +#: 967643f139fa43ffac0fe6b8139ba9af of unit.relay2.Relay2Unit:5 +#: unit.relay2.Relay2Unit.get_relay_status:7 +#: unit.relay2.Relay2Unit.set_relay_cntrl:6 +msgid "UiFlow2 Code Block:" +msgstr "UiFlow2 代码块:" + +#: ../../en/units/relay2.rst:27 d0df8bacfdbf42dc9f98b347117bc6c0 +msgid "|example.png|" +msgstr "" + +#: ../../en/refs/unit.relay2.ref:6 9b9d0efdd56f4987b617dea76ab53073 +msgid "example.png" +msgstr "" + +#: ../../en/units/relay2.rst:29 ../../en/units/relay2.rst:47 +#: 035dc573357a41f7a04cd7f9ba5918b4 579bbbb31c004f4da53dbe2f1288c0dd +msgid "Example output:" +msgstr "示例输出:" + +#: ../../en/units/relay2.rst:31 ../../en/units/relay2.rst:49 +#: 9de65933dabf40e99986927c2da6af3d 9e7fc9c7a8204cf2a148552c743e11b7 +msgid "None" +msgstr "" + +#: ../../en/units/relay2.rst:34 ff58355d656547ffab9e64eb34ed928d +msgid "MicroPython Example" +msgstr "MicroPython 示例" + +#: ../../en/units/relay2.rst:41 010d1dfe83a5435093e83e774cdf93e3 +#: 12fa7ade91414eaab0c0f4eba9cce32c 2a153b58ffaf453fbec1051169aa1665 +#: f04624064ced4c7bb0a67208e655087a of unit.relay2.Relay2Unit:9 +#: unit.relay2.Relay2Unit.get_relay_status:11 +#: unit.relay2.Relay2Unit.set_relay_cntrl:10 +msgid "MicroPython Code Block:" +msgstr "MicroPython 代码块:" + +#: ../../en/units/relay2.rst:53 e09b3cbf9a534dd599ad1a31526904da +msgid "**API**" +msgstr "" + +#: ../../en/units/relay2.rst:56 61a98a582b824f3e9caac4529c964fc8 +msgid "AccelUnit" +msgstr "" + +#: 8f6d5aeffb894461acc918cc8543466a of unit.relay2.Relay2Unit:1 +msgid "Bases: :py:class:`object`" +msgstr "" + +#: a98ea6afa72042efacb0a910a1a35137 of unit.relay2.Relay2Unit:1 +msgid "Create an Relay2Unit object." +msgstr "创建 Relay2Unit 对象。" + +#: ../../en/units/relay2.rst 5a55789323d4452ba6dd1a3375da6a28 +#: 9dd1435f6ca44930bf84b4af3177584f ee841ed6f3e8488ebb5db44c5413ebe5 of +#: unit.relay2.Relay2Unit.get_relay_status +#: unit.relay2.Relay2Unit.set_relay_cntrl +msgid "Parameters" +msgstr "参数" + +#: 849892e558e84319867acaac325c5386 of unit.relay2.Relay2Unit:3 +msgid "The port of the relay." +msgstr "继电器使用到的端口。" + +#: 5dddccb482e24fcf96ede2482a8a062b of unit.relay2.Relay2Unit:7 +msgid "|init.png|" +msgstr "" + +#: ../../en/refs/unit.relay2.ref:8 b3cf7e2256ad44ddb88e55192715c674 +msgid "init.png" +msgstr "" + +#: 271b21a2d06a4cb08572b4f64c5ef666 of +#: unit.relay2.Relay2Unit.get_relay_status:1 +msgid "Getting the on/off status of a relay" +msgstr "获取继电器的开/关状态" + +#: 39e3a49e0e044648a1694bae035e84db of +#: unit.relay2.Relay2Unit.get_relay_status:3 +msgid "The relay number." +msgstr "继电器编号。" + +#: a30248f6b96d4dc48c67cf8247addd06 of unit.relay2.Relay2Unit.get_relay_status +msgid "Returns" +msgstr "返回" + +#: ab57f8bab81a4d4bb2657016f17d207c of +#: unit.relay2.Relay2Unit.get_relay_status:4 +msgid "relay status." +msgstr "继电器状态。" + +#: 56dd1ca224c6434db0413d9dda1dd59a ed1148c81e2e454581a3cc00e6b41563 of +#: unit.relay2.Relay2Unit.get_relay_status +#: unit.relay2.Relay2Unit.set_relay_cntrl +msgid "Return type" +msgstr "返回类型" + +#: 01ce5af7c3e747f699cfbe9374d22814 of +#: unit.relay2.Relay2Unit.get_relay_status:9 +msgid "|get_relay_status.png|" +msgstr "" + +#: ../../en/refs/unit.relay2.ref:10 02b1fe9c5e2047fdaedd2909f4414081 +msgid "get_relay_status.png" +msgstr "" + +#: 1335f290630842c8abaf5a9ac2ec3da6 of unit.relay2.Relay2Unit.set_relay_cntrl:1 +msgid "Set the on/off status of a relay" +msgstr "设置继电器的开/关状态" + +#: 2cb8317c08474323a4fcb6764bfc0c58 of unit.relay2.Relay2Unit.set_relay_cntrl:3 +msgid "The relay number(the range is 1-2)." +msgstr "继电器编号(范围为 1-2)。" + +#: 7a8642dae5714864911bd4eb826c2ee2 of unit.relay2.Relay2Unit.set_relay_cntrl:4 +msgid "The control value(0: off, 1: on)." +msgstr "控制值(0:关闭,1:打开)。" + +#: c8220c50dbe94c14af7e1248e3f0e4e9 of unit.relay2.Relay2Unit.set_relay_cntrl:8 +msgid "|set_relay_cntrl.png|" +msgstr "" + +#: ../../en/refs/unit.relay2.ref:12 5356ff314c414204a05c83c0dba0bf8e +msgid "set_relay_cntrl.png" +msgstr "" diff --git a/docs/locales/zh_CN/LC_MESSAGES/units/tube_pressure.po b/docs/locales/zh_CN/LC_MESSAGES/units/tube_pressure.po new file mode 100644 index 00000000..32c33e6c --- /dev/null +++ b/docs/locales/zh_CN/LC_MESSAGES/units/tube_pressure.po @@ -0,0 +1,216 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2016 - 2025 M5Stack Technology Co., Ltd +# This file is distributed under the same license as the UIFlow2 Programming +# Guide package. +# FIRST AUTHOR , 2025. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: UIFlow2 Programming Guide \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-05-27 17:57+0800\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: zh_CN\n" +"Language-Team: zh_CN \n" +"Plural-Forms: nplurals=1; plural=0;\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.16.0\n" + +#: ../../en/units/tube_pressure.rst:2 74293ed1c48c4df18d46487cdda83e8b +msgid "Tube Pressure Unit" +msgstr "" + +#: ../../en/units/tube_pressure.rst:8 ccb7bd82664040c0995a3189ac403f7a +msgid "" +"This is the driver library of Tube Pressure Unit, which is used to " +"control the pressure sensor." +msgstr "Tube Pressure Unit 驱动库,用于控制压力传感器。" + +#: ../../en/units/tube_pressure.rst:10 dadc5ac8070e4def8717523cc04491be +msgid "Support the following products:" +msgstr "支持以下产品:" + +#: ../../en/units/tube_pressure.rst:12 23e6a8e5036142e98e06905e6551b12c +msgid "|Tube Pressure|" +msgstr "" + +#: ../../en/refs/unit.tube_pressure.ref 78a5c75de99b41078d7d907aea5b18e4 +msgid "Tube Pressure" +msgstr "" + +#: ../../en/units/tube_pressure.rst:16 de5244f87ea345ddb001808f2400d87c +msgid "UiFlow2 Example" +msgstr "UiFlow2 示例" + +#: ../../en/units/tube_pressure.rst:19 ../../en/units/tube_pressure.rst:37 +#: 11b7c067a6394aa4845a0b512fa0cf4e a8ba3b4b4775409f897792cfd06e7ad9 +msgid "get pressure value" +msgstr "获取压力值" + +#: ../../en/units/tube_pressure.rst:21 e12dcd9606484cdfb2584eefbff65a1a +msgid "Open the |tube_pressure_core2_example.m5f2| project in UiFlow2." +msgstr "在 UiFlow2 中打开 |tube_pressure_core2_example.m5f2| 项目。" + +#: ../../en/units/tube_pressure.rst:23 ../../en/units/tube_pressure.rst:39 +#: e2fdb48019c945f695091c15e7062c03 e3453d3f43994fd39febbd0406e470a4 +msgid "The example shows the pressure value of the tube pressure unit." +msgstr "该示例显示 Tube Pressure Unit 的压力值。" + +#: ../../en/units/tube_pressure.rst:25 32bb3326deee41a0b8dcbf3e56a7b382 +#: 6963c0e846ab4a0ebfc7709de45c7a36 6bf097877f7b4e6899dafc2d6a744e0d +#: c6465fc483a6403d9a4be175a8e25506 f8855aef1f7b44a89ea5a08cfcf96e78 of +#: unit.tube_pressure.TubePressureUnit:5 +#: unit.tube_pressure.TubePressureUnit.get_analog_value:7 +#: unit.tube_pressure.TubePressureUnit.get_pressure:6 +#: unit.tube_pressure.TubePressureUnit.get_voltage:6 +msgid "UiFlow2 Code Block:" +msgstr "UiFlow2 代码块:" + +#: ../../en/units/tube_pressure.rst:27 942a8073542a483091bd5387ebd413be +msgid "|example.png|" +msgstr "" + +#: ../../en/refs/unit.tube_pressure.ref:6 a4488e7ae8324aa4b6c86a65f9125f89 +msgid "example.png" +msgstr "" + +#: ../../en/units/tube_pressure.rst:29 ../../en/units/tube_pressure.rst:47 +#: b1165c85940746079cec83595b4c9e58 daf7f7e827be460b82c46079d08e6a22 +msgid "Example output:" +msgstr "示例输出:" + +#: ../../en/units/tube_pressure.rst:31 ../../en/units/tube_pressure.rst:49 +#: 744e1aa0352546f1ae4f2154d5d14278 b0c0076ce5374d928b50abfde1247f37 +msgid "None" +msgstr "" + +#: ../../en/units/tube_pressure.rst:34 565b12c3ddf049c4a153c8338ba0132a +msgid "MicroPython Example" +msgstr "MicroPython 示例" + +#: ../../en/units/tube_pressure.rst:41 1cbf937c5edf440d812b5062bec7860b +#: 8bfca9c92ab44f7aa3ac773457dfc235 9caf6301cff44c058d088f39ace5bb12 +#: a1a0a604ed094b948fae13a54c918aea c9f816807f6c4d169477fa764ba6e560 of +#: unit.tube_pressure.TubePressureUnit:9 +#: unit.tube_pressure.TubePressureUnit.get_analog_value:11 +#: unit.tube_pressure.TubePressureUnit.get_pressure:10 +#: unit.tube_pressure.TubePressureUnit.get_voltage:10 +msgid "MicroPython Code Block:" +msgstr "MicroPython 代码块:" + +#: ../../en/units/tube_pressure.rst:53 6a932cd9430e409897f8ea3d418c1677 +msgid "**API**" +msgstr "" + +#: ../../en/units/tube_pressure.rst:56 038c8ba109a7402ea6997e006da6ceb8 +msgid "TubePressureUnit" +msgstr "" + +#: dd9ad6c60e3f494aabf4d4eef5f50b70 of unit.tube_pressure.TubePressureUnit:1 +msgid "Bases: :py:class:`object`" +msgstr "" + +#: 7396f23cf3b24515afba9e74b31b663b of unit.tube_pressure.TubePressureUnit:1 +msgid "Create an TubePressureUnit object." +msgstr "" + +#: ../../en/units/tube_pressure.rst 372ad79b4cce4927a123c24c0e5388c9 +#: 8076fda6343f4784b4cb8b85f37f5816 of +#: unit.tube_pressure.TubePressureUnit.get_analog_value +msgid "Parameters" +msgstr "参数" + +#: aa41907a68ec4dfbb7c24add0668ce81 of unit.tube_pressure.TubePressureUnit:3 +msgid "The port of the tube pressure." +msgstr "Tube Pressure Unit 使用到的端口。" + +#: cca2428eb79c4750b2baf51341c0ba50 of unit.tube_pressure.TubePressureUnit:7 +msgid "|init.png|" +msgstr "" + +#: ../../en/refs/unit.tube_pressure.ref:8 9b5b5c32ed6f4a9cab201f11ceeb45cd +msgid "init.png" +msgstr "" + +#: 15fc09f860de43b0a644d9d7e4832555 of +#: unit.tube_pressure.TubePressureUnit.get_analog_value:1 +msgid "Getting the analog value of the tube pressure." +msgstr "获取 Tube Pressure Unit 的模拟值。" + +#: 285391cee93d49b6a05405dece73ff44 of +#: unit.tube_pressure.TubePressureUnit.get_analog_value:3 +msgid "The bits of the analog value." +msgstr "模拟值的位数。" + +#: 3a17e10e0f8f4e4095101d79c5829e2a 4b3dd585259d4cec9b265c415d37e74f +#: a586c02da1184eb4b46a2126b53f0992 of +#: unit.tube_pressure.TubePressureUnit.get_analog_value +#: unit.tube_pressure.TubePressureUnit.get_pressure +#: unit.tube_pressure.TubePressureUnit.get_voltage +msgid "Returns" +msgstr "返回" + +#: f60f100604f24074acaca5f75519d048 of +#: unit.tube_pressure.TubePressureUnit.get_analog_value:4 +msgid "analog value." +msgstr "模拟值。" + +#: 8acb7370a8c641daa302a406d77c1d0a d157a7e7cdfb4222b2f86be8f5341ea3 +#: d1f54d17f67341d6a68fb64eae9e8060 of +#: unit.tube_pressure.TubePressureUnit.get_analog_value +#: unit.tube_pressure.TubePressureUnit.get_pressure +#: unit.tube_pressure.TubePressureUnit.get_voltage +msgid "Return type" +msgstr "返回类型" + +#: 7442ca16c8314124a71266f759cfae39 of +#: unit.tube_pressure.TubePressureUnit.get_analog_value:9 +msgid "|get_analog_value.png|" +msgstr "" + +#: ../../en/refs/unit.tube_pressure.ref:14 abfcf7b29921438bb14b5b46742405d6 +msgid "get_analog_value.png" +msgstr "" + +#: eac0c384c35347d886a1276660fbd1f0 of +#: unit.tube_pressure.TubePressureUnit.get_pressure:1 +msgid "Getting the pressure value of the tube pressure." +msgstr "获取 Tube Pressure Unit 的压力值。" + +#: 47ee1d628605438bb139761eb06c57e6 of +#: unit.tube_pressure.TubePressureUnit.get_pressure:3 +msgid "pressure value." +msgstr "压力值。" + +#: 45b28a8de6c2472fb596455031b20c19 of +#: unit.tube_pressure.TubePressureUnit.get_pressure:8 +msgid "|get_pressure.png|" +msgstr "" + +#: ../../en/refs/unit.tube_pressure.ref:10 b529dbae7d4a4da49251148cd93ef0b5 +msgid "get_pressure.png" +msgstr "" + +#: 3f399e2a7a7a4d7e9f88c5b7b7e93f65 of +#: unit.tube_pressure.TubePressureUnit.get_voltage:1 +msgid "Getting the voltage value of the tube pressure." +msgstr "获取 Tube Pressure Unit 的电压值。" + +#: bff780cba3604f86879da87c8e8b5ebb of +#: unit.tube_pressure.TubePressureUnit.get_voltage:3 +msgid "voltage value." +msgstr "电压值。" + +#: 66023d90c8f447f1bf6d83deb32c5750 of +#: unit.tube_pressure.TubePressureUnit.get_voltage:8 +msgid "|get_voltage.png|" +msgstr "" + +#: ../../en/refs/unit.tube_pressure.ref:12 0d962a1a48ca4cb6bec2d7af2c7eaaa4 +msgid "get_voltage.png" +msgstr "" + diff --git a/examples/unit/relay2/relay2_core2_example.m5f2 b/examples/unit/relay2/relay2_core2_example.m5f2 new file mode 100644 index 00000000..a8637edc --- /dev/null +++ b/examples/unit/relay2/relay2_core2_example.m5f2 @@ -0,0 +1 @@ +{"version":"V2.0","versionNumber":"V2.2.7","type":"core2","components":[{"name":"screen","type":"screen","layer":0,"screenId":"builtin","screenName":"","id":"__core2_screen","createTime":1748332677533,"x":0,"y":0,"width":320,"height":240,"backgroundColor":"#222222","size":0,"isSelected":true},{"name":"title0","type":"title","layer":1,"screenId":"builtin","screenName":"","id":"m$+mFlY^&m@q$IUl","createTime":1748332683136,"x":0,"y":0,"color":"#ffffff","backgroundColor":"#0000FF","text":"Relay2Unit Core2 Example","textOffset":3,"font":"Widgets.FONTS.DejaVu18","isSelected":false},{"name":"label0","type":"label","layer":2,"screenId":"builtin","screenName":"","id":"f3qyBi6lklEr0hym","createTime":1748332686381,"x":2,"y":91,"color":"#ffffff","backgroundColor":"#222222","text":"label0","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false},{"name":"label1","type":"label","layer":3,"screenId":"builtin","screenName":"","id":"iFkGb5GZbfJX&#q-","createTime":1748332689987,"x":2,"y":136,"color":"#ffffff","backgroundColor":"#222222","text":"label1","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false},{"name":"label2","type":"label","layer":1,"screenId":"builtin","screenName":"","id":"nDh_otzBL^0Rq1LD","createTime":1748333475805,"x":38,"y":214,"color":"#ffffff","backgroundColor":"#222222","text":"Relay1","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false,"width":64,"height":21},{"name":"label3","type":"label","layer":2,"screenId":"builtin","screenName":"","id":"qWzg!lS_oMo#8LFJ","createTime":1748333486351,"x":220,"y":214,"color":"#ffffff","backgroundColor":"#222222","text":"Relay2","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false,"width":64,"height":21}],"resources":[{"hardware":["hardware_button","hardware_pin_button","imu","speaker","touch","mic"]},{"unit":["unit_relay2"]}],"units":[{"type":"unit_relay2","name":"relay2_0","portList":["A","B","C","Custom"],"portType":"A","userPort":[22,21],"id":"oIlCZYB84fcI8ol+","createTime":1748333331314,"initBlockId":"=pU1Od2b|4K$z})!oUjd"}],"hats":[],"bases":[],"i2cs":[],"blockly":"truerelay2_0truelabel0LabelRelay1 State:relay2_01label1LabelRelay2 State:relay2_02BtnArelay2_011relay2_01BtnCrelay2_021relay2_02","screen":[{"simulationName":"Built-in","type":"builtin","width":320,"height":240,"scale":0.78,"screenName":"","blockId":"","screenColorType":0,"id":"builtin","createTime":1748332677521}],"logicWhenNum":0,"customList":[]} \ No newline at end of file diff --git a/examples/unit/relay2/relay2_core2_example.py b/examples/unit/relay2/relay2_core2_example.py new file mode 100644 index 00000000..98ee65ea --- /dev/null +++ b/examples/unit/relay2/relay2_core2_example.py @@ -0,0 +1,57 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import os, sys, io +import M5 +from M5 import * +from unit import Relay2Unit + + +title0 = None +label2 = None +label0 = None +label3 = None +label1 = None +relay2_0 = None + + +def setup(): + global title0, label2, label0, label3, label1, relay2_0 + + M5.begin() + Widgets.fillScreen(0x222222) + title0 = Widgets.Title( + "Relay2Unit Core2 Example", 3, 0xFFFFFF, 0x0000FF, Widgets.FONTS.DejaVu18 + ) + label2 = Widgets.Label("Relay1", 38, 214, 1.0, 0xFFFFFF, 0x222222, Widgets.FONTS.DejaVu18) + label0 = Widgets.Label("label0", 2, 91, 1.0, 0xFFFFFF, 0x222222, Widgets.FONTS.DejaVu18) + label3 = Widgets.Label("Relay2", 220, 214, 1.0, 0xFFFFFF, 0x222222, Widgets.FONTS.DejaVu18) + label1 = Widgets.Label("label1", 2, 136, 1.0, 0xFFFFFF, 0x222222, Widgets.FONTS.DejaVu18) + + relay2_0 = Relay2Unit((33, 32)) + + +def loop(): + global title0, label2, label0, label3, label1, relay2_0 + M5.update() + label0.setText(str((str("Relay1 State:") + str((relay2_0.get_relay_status(1)))))) + label1.setText(str((str("Relay2 State:") + str((relay2_0.get_relay_status(2)))))) + if BtnA.wasPressed(): + relay2_0.set_relay_cntrl(1, not (relay2_0.get_relay_status(1))) + elif BtnC.wasPressed(): + relay2_0.set_relay_cntrl(2, not (relay2_0.get_relay_status(2))) + + +if __name__ == "__main__": + try: + setup() + while True: + loop() + except (Exception, KeyboardInterrupt) as e: + try: + from utility import print_error_msg + + print_error_msg(e) + except ImportError: + print("please update to latest firmware") diff --git a/examples/unit/tube_pressure/tube_pressure_core2_example.m5f2 b/examples/unit/tube_pressure/tube_pressure_core2_example.m5f2 new file mode 100644 index 00000000..59aa4f90 --- /dev/null +++ b/examples/unit/tube_pressure/tube_pressure_core2_example.m5f2 @@ -0,0 +1 @@ +{"version":"V2.0","versionNumber":"V2.2.7","type":"core2","components":[{"name":"screen","type":"screen","layer":0,"screenId":"builtin","screenName":"","id":"__core2_screen","createTime":1748332677533,"x":0,"y":0,"width":320,"height":240,"backgroundColor":"#222222","size":0,"isSelected":true},{"name":"title0","type":"title","layer":1,"screenId":"builtin","screenName":"","id":"m$+mFlY^&m@q$IUl","createTime":1748332683136,"x":0,"y":0,"color":"#ffffff","backgroundColor":"#0000FF","text":"TubePressureUnit Core2 Example","textOffset":3,"font":"Widgets.FONTS.DejaVu18","isSelected":false},{"name":"label0","type":"label","layer":2,"screenId":"builtin","screenName":"","id":"f3qyBi6lklEr0hym","createTime":1748332686381,"x":1,"y":73,"color":"#ffffff","backgroundColor":"#222222","text":"label0","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false,"width":58,"height":20},{"name":"label1","type":"label","layer":3,"screenId":"builtin","screenName":"","id":"iFkGb5GZbfJX&#q-","createTime":1748332689987,"x":1,"y":116,"color":"#ffffff","backgroundColor":"#222222","text":"label1","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false,"width":58,"height":20},{"name":"label2","type":"label","layer":1,"screenId":"builtin","screenName":"","id":"nDh_otzBL^0Rq1LD","createTime":1748333475805,"x":1,"y":159,"color":"#ffffff","backgroundColor":"#222222","text":"label2","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false,"width":58,"height":20}],"resources":[{"hardware":["hardware_button","hardware_pin_button","imu","speaker","touch","mic"]},{"unit":["unit_tubepressure"]}],"units":[{"type":"unit_tubepressure","name":"tubepressure_0","portList":["A","B","C","Custom"],"portType":"B","userPort":[22,21],"id":"wegL`JstHqFuP3#_","createTime":1748335410082,"initBlockId":"Oq][BttDZhWH2i@IyQ8I"}],"hats":[],"bases":[],"i2cs":[],"blockly":"truetubepressure_0truelabel0LabelPressure:tubepressure_0label1LabelADC 12Bits Value:tubepressure_012label2LabelVoltage:tubepressure_0","screen":[{"simulationName":"Built-in","type":"builtin","width":320,"height":240,"scale":0.78,"screenName":"","blockId":"","screenColorType":0,"id":"builtin","createTime":1748332677521}],"logicWhenNum":0,"customList":[]} \ No newline at end of file diff --git a/examples/unit/tube_pressure/tube_pressure_core2_example.py b/examples/unit/tube_pressure/tube_pressure_core2_example.py new file mode 100644 index 00000000..e6706bed --- /dev/null +++ b/examples/unit/tube_pressure/tube_pressure_core2_example.py @@ -0,0 +1,52 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import os, sys, io +import M5 +from M5 import * +from unit import TubePressureUnit + + +title0 = None +label2 = None +label0 = None +label1 = None +tubepressure_0 = None + + +def setup(): + global title0, label2, label0, label1, tubepressure_0 + + M5.begin() + Widgets.fillScreen(0x222222) + title0 = Widgets.Title( + "TubePressureUnit Core2 Example", 3, 0xFFFFFF, 0x0000FF, Widgets.FONTS.DejaVu18 + ) + label2 = Widgets.Label("label2", 1, 159, 1.0, 0xFFFFFF, 0x222222, Widgets.FONTS.DejaVu18) + label0 = Widgets.Label("label0", 1, 73, 1.0, 0xFFFFFF, 0x222222, Widgets.FONTS.DejaVu18) + label1 = Widgets.Label("label1", 1, 116, 1.0, 0xFFFFFF, 0x222222, Widgets.FONTS.DejaVu18) + + tubepressure_0 = TubePressureUnit((36, 26)) + + +def loop(): + global title0, label2, label0, label1, tubepressure_0 + M5.update() + label0.setText(str((str("Pressure:") + str((tubepressure_0.get_pressure()))))) + label1.setText(str((str("ADC 12Bits Value:") + str((tubepressure_0.get_analog_value(12)))))) + label2.setText(str((str("Voltage:") + str((tubepressure_0.get_voltage()))))) + + +if __name__ == "__main__": + try: + setup() + while True: + loop() + except (Exception, KeyboardInterrupt) as e: + try: + from utility import print_error_msg + + print_error_msg(e) + except ImportError: + print("please update to latest firmware") diff --git a/m5stack/libs/unit/relay2.py b/m5stack/libs/unit/relay2.py index 8549b95b..5d3d8d75 100644 --- a/m5stack/libs/unit/relay2.py +++ b/m5stack/libs/unit/relay2.py @@ -5,12 +5,60 @@ class Relay2Unit: + """Create an Relay2Unit object. + + :param tuple port: The port of the relay. + + UiFlow2 Code Block: + + |init.png| + + MicroPython Code Block: + + .. code-block:: python + + from unit import Relay2Unit + + relay2_0 = Relay2Unit((32, 26)) + """ + def __init__(self, port: tuple) -> None: self._relay1 = Pin(port[0], Pin.OUT) self._relay2 = Pin(port[1], Pin.OUT) def set_relay_cntrl(self, num: int = 1, control: int = 0) -> None: + """Set the on/off status of a relay + + :param int num: The relay number(the range is 1-2). + :param int control: The control value(0: off, 1: on). + + UiFlow2 Code Block: + + |set_relay_cntrl.png| + + MicroPython Code Block: + + .. code-block:: python + + relay2_0.set_relay_cntrl(1, 1) + """ self._relay1(control) if num == 1 else self._relay2(control) def get_relay_status(self, num: int = 1) -> bool: + """Getting the on/off status of a relay + + :param int num: The relay number. + :returns: relay status. + :rtype: bool + + UiFlow2 Code Block: + + |get_relay_status.png| + + MicroPython Code Block: + + .. code-block:: python + + relay2_0.get_relay_status() + """ return bool(self._relay1() if num == 1 else self._relay2()) diff --git a/m5stack/libs/unit/tube_pressure.py b/m5stack/libs/unit/tube_pressure.py index a47c7c55..175044bf 100644 --- a/m5stack/libs/unit/tube_pressure.py +++ b/m5stack/libs/unit/tube_pressure.py @@ -9,21 +9,84 @@ class TubePressureUnit: + """Create an TubePressureUnit object. + + :param tuple port: The port of the tube pressure. + + UiFlow2 Code Block: + + |init.png| + + MicroPython Code Block: + + .. code-block:: python + + from unit import TubePressureUnit + + tube_pressure_0 = TubePressureUnit((32, 26)) + """ + def __init__(self, port: tuple) -> None: self._adc = ADC(port[0]) self._adc.atten(ADC.ATTN_11DB) def get_pressure(self): + """Getting the pressure value of the tube pressure. + + :returns: pressure value. + :rtype: float + + UiFlow2 Code Block: + + |get_pressure.png| + + MicroPython Code Block: + + .. code-block:: python + + tube_pressure_0.get_pressure() + """ mv = self._adc.read_uv() mv /= 1000000 pressure = (mv - 0.1) / 3.0 * 300.0 - 100.0 return round(pressure, 2) def get_voltage(self): + """Getting the voltage value of the tube pressure. + + :returns: voltage value. + :rtype: float + + UiFlow2 Code Block: + + |get_voltage.png| + + MicroPython Code Block: + + .. code-block:: python + + tube_pressure_0.get_voltage() + """ mv = self._adc.read_uv() / 1000000 return round(mv, 3) def get_analog_value(self, bits: int = RAW16BIT): + """Getting the analog value of the tube pressure. + + :param int bits: The bits of the analog value. + :returns: analog value. + :rtype: int + + UiFlow2 Code Block: + + |get_analog_value.png| + + MicroPython Code Block: + + .. code-block:: python + + tube_pressure_0.get_analog_value() + """ data = 0 max = 0 min = 65536 if bits == RAW16BIT else 4096 From fd308e07855fdc94b0769b7bdb913d8f3f669883 Mon Sep 17 00:00:00 2001 From: lbuque Date: Thu, 29 May 2025 17:36:47 +0800 Subject: [PATCH 093/322] docs: Fix some example code errors. Signed-off-by: lbuque --- docs/en/units/relay4.rst | 2 +- docs/en/units/ultrasonic.rst | 21 ++++--------------- examples/unit/ain4/ain4_core2_example.py | 7 ++++--- .../ultrasonic/ultrasonic_core_example.py | 3 ++- 4 files changed, 11 insertions(+), 22 deletions(-) diff --git a/docs/en/units/relay4.rst b/docs/en/units/relay4.rst index ec72019b..c6bce5b5 100644 --- a/docs/en/units/relay4.rst +++ b/docs/en/units/relay4.rst @@ -17,7 +17,7 @@ Support the following products: Micropython Example: - .. literalinclude:: ../../../examples/unit/relay/relay_core_example.py + .. literalinclude:: ../../../examples/unit/relay4/cores3_relay4_example.py :language: python :linenos: diff --git a/docs/en/units/ultrasonic.rst b/docs/en/units/ultrasonic.rst index 0b6c2691..4624d88c 100644 --- a/docs/en/units/ultrasonic.rst +++ b/docs/en/units/ultrasonic.rst @@ -8,24 +8,11 @@ Support the following products: |Ultrasonic| -Micropython Example:: +Micropython Example: - import os, sys, io - import M5 - from M5 import * - from hardware import * - from unit import * - - i2c0 = None - ultrasonic_0 = None - - def setup(): - global i2c0, ultrasonic_0 - - i2c0 = I2C(0, scl=Pin(1), sda=Pin(2), freq=100000) - ultrasonic_0 = ULTRASONIC_I2C(i2c0) - M5.begin() - Widgets.fillScreen(0x222222) + .. literalinclude:: ../../../examples/unit/ultrasonic/ultrasonic_core_example.py + :language: python + :linenos: UIFLOW2 Example: diff --git a/examples/unit/ain4/ain4_core2_example.py b/examples/unit/ain4/ain4_core2_example.py index 41d826b0..48093bf5 100644 --- a/examples/unit/ain4/ain4_core2_example.py +++ b/examples/unit/ain4/ain4_core2_example.py @@ -5,7 +5,8 @@ import os, sys, io import M5 from M5 import * -from hardware import * +from hardware import I2C +from hardware import Pin from unit import AIN4_20MAUnit @@ -33,8 +34,8 @@ def setup(): def loop(): global title0, label0, label1, i2c0, ain4_20ma_0 M5.update() - label0.setText(str((str("CH1 Current:") + str((ain4_20ma_0.get_4_20ma_current_value()))))) - label1.setText(str((str("CH1 ADC:") + str((ain4_20ma_0.get_adc_raw16_value()))))) + label0.setText(str((str("CH1 Current:") + str((ain4_20ma_0.get_current_value()))))) + label1.setText(str((str("CH1 ADC:") + str((ain4_20ma_0.get_adc_raw_value()))))) if __name__ == "__main__": diff --git a/examples/unit/ultrasonic/ultrasonic_core_example.py b/examples/unit/ultrasonic/ultrasonic_core_example.py index 7da99478..667b8dce 100644 --- a/examples/unit/ultrasonic/ultrasonic_core_example.py +++ b/examples/unit/ultrasonic/ultrasonic_core_example.py @@ -5,7 +5,8 @@ import os, sys, io import M5 from M5 import * -from hardware import * +from hardware import I2C +from hardware import Pin from unit import UltrasoundI2CUnit From 3ad8b1f7523161a3b96df6f77e21a50cdc081b3b Mon Sep 17 00:00:00 2001 From: LittleMouse Date: Thu, 29 May 2025 09:40:14 +0800 Subject: [PATCH 094/322] libs/module/llm.py: Fixed MeloTTS initialization bug. Signed-off-by: LittleMouse --- m5stack/libs/module/llm.py | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/m5stack/libs/module/llm.py b/m5stack/libs/module/llm.py index 887b07d9..fa5e5d17 100644 --- a/m5stack/libs/module/llm.py +++ b/m5stack/libs/module/llm.py @@ -486,7 +486,7 @@ def __init__(self, module_msg): def setup( self, - model="melotts_zh-cn", + model="melotts-zh-cn", response_format="sys.pcm", input="tts.utf-8.stream", enoutput=False, @@ -903,6 +903,8 @@ def llm_setup( if self.version == "v1.0": enkws = True else: + if isinstance(input, str): + input = [input] input.append(enkws) else: enkws = bool(enkws) @@ -979,6 +981,8 @@ def tts_setup( if self.version == "v1.0": enkws = True else: + if isinstance(input, str): + input = [input] input.append(enkws) else: enkws = bool(enkws) @@ -995,7 +999,7 @@ def tts_inference(self, work_id, input_data, timeout=0, request_id="tts_inferenc def melotts_setup( self, language="en_US", - model="melotts_zh-cn", + model="melotts-en-default", response_format="sys.pcm", input=None, enoutput=False, @@ -1003,11 +1007,15 @@ def melotts_setup( request_id="tts_setup", ) -> str: if language == "zh_CN": - model = "melotts_zh-cn" + model = "melotts-zh-cn" + if language == "ja_JP": + model = "melotts-ja-jp" if input is None: input = ["tts.utf-8.stream"] if enkws: + if isinstance(input, str): + input = [input] input.append(enkws) self.latest_melotts_work_id = self.melotts.setup( @@ -1058,6 +1066,8 @@ def asr_setup( if self.version == "v1.0": enkws = True else: + if isinstance(input, str): + input = [input] input.append(enkws) else: enkws = bool(enkws) @@ -1079,6 +1089,8 @@ def vad_setup( if input is None: input = ["sys.pcm"] if enkws: + if isinstance(input, str): + input = [input] input.append(enkws) self.latest_vad_work_id = self.vad.setup( model, response_format, input, enoutput, request_id @@ -1099,8 +1111,12 @@ def whisper_setup( if input is None: input = ["sys.pcm"] if enkws: + if isinstance(input, str): + input = [input] input.append(enkws) if envad: + if isinstance(input, str): + input = [input] input.append(envad) self.latest_whisper_work_id = self.whisper.setup( model, response_format, input, enoutput, language, request_id From e986468074072d51ba2b1af2aa646765890ddb9c Mon Sep 17 00:00:00 2001 From: lbuque Date: Thu, 29 May 2025 17:53:52 +0800 Subject: [PATCH 095/322] version.text: Update to V2.2.8. Signed-off-by: lbuque --- m5stack/version.txt | 2 +- third-party/version.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/m5stack/version.txt b/m5stack/version.txt index 92d0d3c8..f44d1570 100644 --- a/m5stack/version.txt +++ b/m5stack/version.txt @@ -1 +1 @@ -V2.2.7 \ No newline at end of file +V2.2.8 \ No newline at end of file diff --git a/third-party/version.txt b/third-party/version.txt index 92d0d3c8..f44d1570 100644 --- a/third-party/version.txt +++ b/third-party/version.txt @@ -1 +1 @@ -V2.2.7 \ No newline at end of file +V2.2.8 \ No newline at end of file From d8c9a4e919cc9627edb98c67c71607cd0903e6f7 Mon Sep 17 00:00:00 2001 From: lbuque Date: Tue, 20 May 2025 15:43:05 +0800 Subject: [PATCH 096/322] all: Updated MicroPython version to 1.25.0. 1. support esp32-p4. 2. still using i2c-driver. Signed-off-by: lbuque --- m5stack/CMakeLists.txt | 23 +- m5stack/CMakeListsDefault.cmake | 60 +- m5stack/Makefile | 30 +- m5stack/board.cpp | 2 +- m5stack/boards/M5STACK_CoreS3/board_init.c | 6 +- m5stack/boards/sdkconfig.base | 6 +- m5stack/cmodules/m5unified/m5unified_lvgl.c | 2 +- m5stack/cmodules/omv/modules/py_jpg.c | 2 +- m5stack/components/M5Unified/CMakeLists.txt | 6 +- m5stack/components/M5Unified/M5GFX | 2 +- m5stack/components/M5Unified/M5Unified | 2 +- .../components/M5Unified/mpy_m5unified.cpp | 3 +- m5stack/components/esp_dmx/src/dmx/hal/uart.c | 31 +- m5stack/components/libffi/CMakeLists.txt | 2 +- m5stack/include/files.mk | 4 +- m5stack/machine_i2c.c | 15 +- m5stack/machine_rtc.c | 67 +- m5stack/machine_sdcard.c | 165 ++- m5stack/main.c | 19 + m5stack/main/CMakeLists.txt | 15 + m5stack/main/idf_component.yml | 20 + m5stack/modesp32.c | 5 +- m5stack/network_wlan.c | 68 +- ...-micropython-1.25.0-add-esp32p4-pins.patch | 80 ++ ...-micropython-1.25.0-machine-adc-v5.x.patch | 224 +++ ...-micropython-1.25.0-fix-esp32-p4-pwm.patch | 16 + ...opython-1.25.0-fix-esp32-p4-modesp32.patch | 22 + ...0013-micropython-1.25.0-fix-touchpad.patch | 26 + .../0014-micropython-1.25.0-fix-timer.patch | 34 + ...0015-micropython-1.25.0-fix-hostname.patch | 13 + ...-micropython-1.25.0-fix-mpnimbleport.patch | 14 + ...017-micropython-1.25.0-add-uart-mode.patch | 43 + m5stack/patches/1004-idf_v5.4_freertos.patch | 122 ++ .../patches/4002-M5GFX-use-i2c-driver.patch | 22 + .../5001-Add-software-i2c-support.patch | 1267 ++++++----------- m5stack/patches/README.md | 4 +- micropython | 2 +- .../ESPRESSIF_ESP32_S3_BOX_3/board_init.c | 2 +- 38 files changed, 1476 insertions(+), 970 deletions(-) create mode 100644 m5stack/main/CMakeLists.txt create mode 100644 m5stack/main/idf_component.yml create mode 100644 m5stack/patches/0009-micropython-1.25.0-add-esp32p4-pins.patch create mode 100644 m5stack/patches/0010-micropython-1.25.0-machine-adc-v5.x.patch create mode 100644 m5stack/patches/0011-micropython-1.25.0-fix-esp32-p4-pwm.patch create mode 100644 m5stack/patches/0012-micropython-1.25.0-fix-esp32-p4-modesp32.patch create mode 100644 m5stack/patches/0013-micropython-1.25.0-fix-touchpad.patch create mode 100644 m5stack/patches/0014-micropython-1.25.0-fix-timer.patch create mode 100644 m5stack/patches/0015-micropython-1.25.0-fix-hostname.patch create mode 100644 m5stack/patches/0016-micropython-1.25.0-fix-mpnimbleport.patch create mode 100644 m5stack/patches/0017-micropython-1.25.0-add-uart-mode.patch create mode 100644 m5stack/patches/1004-idf_v5.4_freertos.patch create mode 100644 m5stack/patches/4002-M5GFX-use-i2c-driver.patch diff --git a/m5stack/CMakeLists.txt b/m5stack/CMakeLists.txt index 0a67b9c9..ece147c7 100644 --- a/m5stack/CMakeLists.txt +++ b/m5stack/CMakeLists.txt @@ -3,7 +3,10 @@ # SPDX-License-Identifier: MIT # Top-level cmake file for building MicroPython on ESP32. - +# +# Note for maintainers: Where possible, functionality should be put into +# esp32_common.cmake not this file. This is because this CMakeLists.txt file +# needs to be duplicated for out-of-tree builds, and can easily get out of date. cmake_minimum_required(VERSION 3.12) set(CMAKE_C_FLAGS "-Wno-unused-variable -Wno-unused-but-set-variable") @@ -42,12 +45,6 @@ set(SDKCONFIG ${CMAKE_BINARY_DIR}/sdkconfig) # Save the manifest file set from the cmake command line. set(MICROPY_USER_FROZEN_MANIFEST ${MICROPY_FROZEN_MANIFEST}) -# Specific options for IDF v5.2 and later -set(SDKCONFIG_IDF_VERSION_SPECIFIC "") -if (IDF_VERSION VERSION_GREATER_EQUAL "5.2.0") - set(SDKCONFIG_IDF_VERSION_SPECIFIC boards/sdkconfig.idf52) -endif() - # Include board config; this is expected to set (among other options): # - SDKCONFIG_DEFAULTS # - IDF_TARGET @@ -88,11 +85,6 @@ endif() # Include main IDF cmake file. include($ENV{IDF_PATH}/tools/cmake/project.cmake) -# Set the location of the main component for the project (one per target). -list(APPEND EXTRA_COMPONENT_DIRS - main_${IDF_TARGET} -) - if (M5_CAMERA_MODULE_ENABLE AND BOARD_TYPE STREQUAL "cores3") list(APPEND EXTRA_COMPONENT_DIRS components/esp_dl/esp-dl @@ -102,12 +94,5 @@ list(APPEND EXTRA_COMPONENT_DIRS ) endif() -# Enable the panic handler wrapper -idf_build_set_property(LINK_OPTIONS "-Wl,--wrap=esp_panic_handler" APPEND) - -# Patch LWIP memory pool allocators (see lwip_patch.c) -idf_build_set_property(LINK_OPTIONS "-Wl,--wrap=memp_malloc" APPEND) -idf_build_set_property(LINK_OPTIONS "-Wl,--wrap=memp_free" APPEND) - # Define the project. project(micropython) diff --git a/m5stack/CMakeListsDefault.cmake b/m5stack/CMakeListsDefault.cmake index 501a4b83..33cb4ee9 100644 --- a/m5stack/CMakeListsDefault.cmake +++ b/m5stack/CMakeListsDefault.cmake @@ -12,6 +12,20 @@ if(NOT MICROPY_PORT_DIR) get_filename_component(MICROPY_PORT_DIR ${MICROPY_DIR}/ports/esp32 ABSOLUTE) endif() +# RISC-V specific inclusions +if(CONFIG_IDF_TARGET_ARCH_RISCV) + list(APPEND MICROPY_SOURCE_LIB + ${MICROPY_DIR}/shared/runtime/gchelper_native.c + ${MICROPY_DIR}/shared/runtime/gchelper_rv32i.s + ) +endif() + +if(NOT DEFINED MICROPY_PY_TINYUSB) + if(CONFIG_IDF_TARGET_ESP32S2 OR CONFIG_IDF_TARGET_ESP32S3) + set(MICROPY_PY_TINYUSB ON) + endif() +endif() + # Include core source components. include(${MICROPY_DIR}/py/py.cmake) @@ -57,7 +71,7 @@ list(APPEND MICROPY_SOURCE_DRIVERS ${MICROPY_DIR}/drivers/dht/dht.c ) -string(CONCAT GIT_SUBMODULES "${GIT_SUBMODULES} " lib/tinyusb) +list(APPEND GIT_SUBMODULES lib/tinyusb) if(MICROPY_PY_TINYUSB) set(TINYUSB_SRC "${MICROPY_DIR}/lib/tinyusb/src") string(TOUPPER OPT_MCU_${IDF_TARGET} tusb_mcu) @@ -166,7 +180,6 @@ if (M5_CAMERA_MODULE_ENABLE) ) endif() - set(MICROPY_SOURCE_QSTR ${MICROPY_SOURCE_PY} ${MICROPY_SOURCE_EXTMOD} @@ -214,7 +227,6 @@ list(APPEND IDF_COMPONENTS ulp usb vfs - xtensa esp_http_client esp-tls libffi @@ -226,6 +238,10 @@ list(APPEND IDF_COMPONENTS esp_mm ) +if(CONFIG_IDF_TARGET_ESP32 OR CONFIG_IDF_TARGET_ESP32S2 OR CONFIG_IDF_TARGET_ESP32S3) + list(APPEND IDF_COMPONENTS xtensa) +endif() + if (M5_CAMERA_MODULE_ENABLE AND BOARD_TYPE STREQUAL "cores3") list(APPEND IDF_COMPONENTS esp-dl @@ -251,6 +267,22 @@ if(IDF_TARGET STREQUAL "esp32" OR IDF_TARGET STREQUAL "esp32s3") list(APPEND IDF_COMPONENTS esp_codec_dev) endif() +# Provide the default LD fragment if not set +if (MICROPY_USER_LDFRAGMENTS) + set(MICROPY_LDFRAGMENTS ${MICROPY_USER_LDFRAGMENTS}) +endif() + +if (UPDATE_SUBMODULES) + # ESP-IDF checks if some paths exist before CMake does. Some paths don't + # yet exist if this is an UPDATE_SUBMODULES pass on a brand new checkout, so remove + # any path which might not exist yet. A "real" build will not set UPDATE_SUBMODULES. + unset(MICROPY_SOURCE_TINYUSB) + unset(MICROPY_SOURCE_EXTMOD) + unset(MICROPY_SOURCE_LIB) + unset(MICROPY_INC_TINYUSB) + unset(MICROPY_INC_CORE) +endif() + # Register the main IDF component. idf_component_register( SRCS @@ -271,7 +303,7 @@ idf_component_register( ${CMAKE_BINARY_DIR} ${CMAKE_CURRENT_LIST_DIR} LDFRAGMENTS - linker.lf + ${MICROPY_LDFRAGMENTS} REQUIRES ${IDF_COMPONENTS} ) @@ -280,8 +312,10 @@ idf_component_register( set(MICROPY_TARGET ${COMPONENT_TARGET}) # Define mpy-cross flags, for use with frozen code. -if(CONFIG_IDF_TARGET_ARCH STREQUAL "xtensa") -set(MICROPY_CROSS_FLAGS -march=xtensawin) +if(CONFIG_IDF_TARGET_ARCH_XTENSA) + set(MICROPY_CROSS_FLAGS -march=xtensawin) +elseif(CONFIG_IDF_TARGET_ARCH_RISCV) + set(MICROPY_CROSS_FLAGS -march=rv32imc) endif() if (M5_CAMERA_MODULE_ENABLE AND BOARD_TYPE STREQUAL "cores3") @@ -326,6 +360,20 @@ target_include_directories(${MICROPY_TARGET} PUBLIC target_link_libraries(${MICROPY_TARGET} micropy_extmod_btree) target_link_libraries(${MICROPY_TARGET} usermod) +# Extra linker options +# (when wrap symbols are in standalone files, --undefined ensures +# the linker doesn't skip that file.) +target_link_options(${MICROPY_TARGET} PUBLIC + # Patch LWIP memory pool allocators (see lwip_patch.c) + -Wl,--undefined=memp_malloc + -Wl,--wrap=memp_malloc + -Wl,--wrap=memp_free + + # Enable the panic handler wrapper + -Wl,--undefined=esp_panic_handler + -Wl,--wrap=esp_panic_handler +) + # Collect all of the include directories and compile definitions for the IDF components, # including those added by the IDF Component Manager via idf_components.yaml. foreach(comp ${__COMPONENT_NAMES_RESOLVED}) diff --git a/m5stack/Makefile b/m5stack/Makefile index b8c9e0e1..cb83cf98 100644 --- a/m5stack/Makefile +++ b/m5stack/Makefile @@ -299,25 +299,39 @@ submodules: # Apply patches patch: $(call Package/patche,$(abspath ./cmodules/lv_binding_micropython),$(abspath ./patches/0003-avoid-lv_bindings-compile-error.patch)) - $(call Package/patche,$(abspath ./../micropython),$(abspath ./patches/0004-micropython-1.24-machine-adc-v5.x.diff)) - $(call Package/patche,$(abspath ./../micropython),$(abspath ./patches/0005-micropython-fix-SDCard-16223.patch)) $(call Package/patche,$(abspath ./../micropython),$(abspath ./patches/0006-modtime-add-timezone-method.patch)) $(call Package/patche,$(abspath ./../micropython),$(abspath ./patches/0007-Add-set-default-netif-method.patch)) - $(call Package/patche,$(abspath ./../micropython),$(abspath ./patches/0008-machine_uart-add-uart-mode.patch)) - $(call Package/patche,$(abspath $(IDF_PATH)),$(abspath ./patches/1003-WIP-Compatible-with-esp-adf-v2.7.diff)) + $(call Package/patche,$(abspath ./../micropython),$(abspath ./patches/0009-micropython-1.25.0-add-esp32p4-pins.patch)) + $(call Package/patche,$(abspath ./../micropython),$(abspath ./patches/0010-micropython-1.25.0-machine-adc-v5.x.patch)) + $(call Package/patche,$(abspath ./../micropython),$(abspath ./patches/0011-micropython-1.25.0-fix-esp32-p4-pwm.patch)) + $(call Package/patche,$(abspath ./../micropython),$(abspath ./patches/0012-micropython-1.25.0-fix-esp32-p4-modesp32.patch)) + $(call Package/patche,$(abspath ./../micropython),$(abspath ./patches/0013-micropython-1.25.0-fix-touchpad.patch)) + $(call Package/patche,$(abspath ./../micropython),$(abspath ./patches/0014-micropython-1.25.0-fix-timer.patch)) + $(call Package/patche,$(abspath ./../micropython),$(abspath ./patches/0015-micropython-1.25.0-fix-hostname.patch)) + $(call Package/patche,$(abspath ./../micropython),$(abspath ./patches/0016-micropython-1.25.0-fix-mpnimbleport.patch)) + $(call Package/patche,$(abspath ./../micropython),$(abspath ./patches/0017-micropython-1.25.0-add-uart-mode.patch)) + $(call Package/patche,$(abspath $(IDF_PATH)),$(abspath ./patches/1004-idf_v5.4_freertos.patch)) $(call Package/patche,$(abspath ./components/M5Unified/M5Unified),$(abspath ./patches/2005-Support-LTR553.patch)) $(call Package/patche,$(abspath $(ADF_PATH)),$(abspath ./patches/3002-Modify-i2s_stream_idf5.patch)) + $(call Package/patche,$(abspath ./components/M5Unified/M5GFX),$(abspath ./patches/4002-M5GFX-use-i2c-driver.patch)) $(call Package/patche,$(abspath ./components/esp32-camera),$(abspath ./patches/5001-Add-software-i2c-support.patch)) # Unapply patches unpatch: $(call Package/unpatche,$(abspath ./cmodules/lv_binding_micropython),$(abspath ./patches/0003-avoid-lv_bindings-compile-error.patch)) - $(call Package/unpatche,$(abspath ./../micropython),$(abspath ./patches/0008-machine_uart-add-uart-mode.patch)) + $(call Package/unpatche,$(abspath ./../micropython),$(abspath ./patches/0017-micropython-1.25.0-add-uart-mode.patch)) + $(call Package/unpatche,$(abspath ./../micropython),$(abspath ./patches/0016-micropython-1.25.0-fix-mpnimbleport.patch)) + $(call Package/unpatche,$(abspath ./../micropython),$(abspath ./patches/0015-micropython-1.25.0-fix-hostname.patch)) + $(call Package/unpatche,$(abspath ./../micropython),$(abspath ./patches/0014-micropython-1.25.0-fix-timer.patch)) + $(call Package/unpatche,$(abspath ./../micropython),$(abspath ./patches/0013-micropython-1.25.0-fix-touchpad.patch)) + $(call Package/unpatche,$(abspath ./../micropython),$(abspath ./patches/0012-micropython-1.25.0-fix-esp32-p4-modesp32.patch)) + $(call Package/unpatche,$(abspath ./../micropython),$(abspath ./patches/0011-micropython-1.25.0-fix-esp32-p4-pwm.patch)) + $(call Package/unpatche,$(abspath ./../micropython),$(abspath ./patches/0010-micropython-1.25.0-machine-adc-v5.x.patch)) + $(call Package/unpatche,$(abspath ./../micropython),$(abspath ./patches/0009-micropython-1.25.0-add-esp32p4-pins.patch)) $(call Package/unpatche,$(abspath ./../micropython),$(abspath ./patches/0007-Add-set-default-netif-method.patch)) $(call Package/unpatche,$(abspath ./../micropython),$(abspath ./patches/0006-modtime-add-timezone-method.patch)) - $(call Package/unpatche,$(abspath ./../micropython),$(abspath ./patches/0005-micropython-fix-SDCard-16223.patch)) - $(call Package/unpatche,$(abspath ./../micropython),$(abspath ./patches/0004-micropython-1.24-machine-adc-v5.x.diff)) - $(call Package/unpatche,$(abspath $(IDF_PATH)),$(abspath ./patches/1003-WIP-Compatible-with-esp-adf-v2.7.diff)) + $(call Package/unpatche,$(abspath $(IDF_PATH)),$(abspath ./patches/1004-idf_v5.4_freertos.patch)) $(call Package/unpatche,$(abspath ./components/M5Unified/M5Unified),$(abspath ./patches/2005-Support-LTR553.patch)) $(call Package/unpatche,$(abspath $(ADF_PATH)),$(abspath ./patches/3002-Modify-i2s_stream_idf5.patch)) + $(call Package/unpatche,$(abspath ./components/M5Unified/M5GFX),$(abspath ./patches/4002-M5GFX-use-i2c-driver.patch)) $(call Package/unpatche,$(abspath ./components/esp32-camera),$(abspath ./patches/5001-Add-software-i2c-support.patch)) diff --git a/m5stack/board.cpp b/m5stack/board.cpp index 6a10dcd9..dc0ab1f8 100644 --- a/m5stack/board.cpp +++ b/m5stack/board.cpp @@ -48,8 +48,8 @@ extern "C" { auto cfg = M5.config(); cfg.output_power = false; M5.begin(cfg); - in_i2c_init(); M5.In_I2C.release(); + in_i2c_init(); } void power_init() diff --git a/m5stack/boards/M5STACK_CoreS3/board_init.c b/m5stack/boards/M5STACK_CoreS3/board_init.c index ac1a8624..3f2320fa 100644 --- a/m5stack/boards/M5STACK_CoreS3/board_init.c +++ b/m5stack/boards/M5STACK_CoreS3/board_init.c @@ -23,7 +23,7 @@ #include "esp_codec_dev.h" #include "esp_codec_dev_defaults.h" -#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0) +#if 0 //ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0) #include "driver/i2c_master.h" #define USE_IDF_I2C_MASTER #else @@ -177,8 +177,8 @@ static int ut_i2c_init(uint8_t port) i2c_master_bus_config_t i2c_bus_config = {0}; i2c_bus_config.clk_source = I2C_CLK_SRC_DEFAULT; i2c_bus_config.i2c_port = port; - i2c_bus_config.scl_io_num = TEST_BOARD_I2C_SCL_PIN; - i2c_bus_config.sda_io_num = TEST_BOARD_I2C_SDA_PIN; + i2c_bus_config.scl_io_num = 12; + i2c_bus_config.sda_io_num = 11; i2c_bus_config.glitch_ignore_cnt = 7; i2c_bus_config.flags.enable_internal_pullup = true; return i2c_new_master_bus(&i2c_bus_config, &i2c_bus_handle); diff --git a/m5stack/boards/sdkconfig.base b/m5stack/boards/sdkconfig.base index 72b87fc4..4fd93a45 100644 --- a/m5stack/boards/sdkconfig.base +++ b/m5stack/boards/sdkconfig.base @@ -49,7 +49,8 @@ CONFIG_ESP_SYSTEM_MEMPROT_FEATURE=n # FreeRTOS CONFIG_FREERTOS_THREAD_LOCAL_STORAGE_POINTERS=2 CONFIG_FREERTOS_SUPPORT_STATIC_ALLOCATION=y -CONFIG_FREERTOS_ENABLE_STATIC_TASK_CLEAN_UP=y +CONFIG_FREERTOS_ENABLE_STATIC_TASK_CLEAN_UP=n +CONFIG_FREERTOS_TASK_PRE_DELETION_HOOK=y # UDP CONFIG_LWIP_PPP_SUPPORT=y @@ -63,6 +64,9 @@ CONFIG_MBEDTLS_HAVE_TIME_DATE=y CONFIG_MBEDTLS_PLATFORM_TIME_ALT=y CONFIG_MBEDTLS_HAVE_TIME=y +# Enable DTLS +CONFIG_MBEDTLS_SSL_PROTO_DTLS=y + # Disable ALPN support as it's not implemented in MicroPython CONFIG_MBEDTLS_SSL_ALPN=n diff --git a/m5stack/cmodules/m5unified/m5unified_lvgl.c b/m5stack/cmodules/m5unified/m5unified_lvgl.c index 2e66728f..b401f607 100644 --- a/m5stack/cmodules/m5unified/m5unified_lvgl.c +++ b/m5stack/cmodules/m5unified/m5unified_lvgl.c @@ -21,7 +21,7 @@ mp_obj_t mp_lv_task_handler(mp_obj_t self_in) { static MP_DEFINE_CONST_FUN_OBJ_1(mp_lv_task_handler_obj, mp_lv_task_handler); static void vTimerCallback(TimerHandle_t plvgl_timer) { - lv_tick_inc(portTICK_RATE_MS * 10); + lv_tick_inc(portTICK_PERIOD_MS * 10); mp_sched_schedule((mp_obj_t)&mp_lv_task_handler_obj, mp_const_none); } diff --git a/m5stack/cmodules/omv/modules/py_jpg.c b/m5stack/cmodules/omv/modules/py_jpg.c index 8fc013ed..fa030969 100644 --- a/m5stack/cmodules/omv/modules/py_jpg.c +++ b/m5stack/cmodules/omv/modules/py_jpg.c @@ -42,7 +42,7 @@ static mp_obj_t py_jpg_encode(size_t n_args, const mp_obj_t *args) { if (img_jpg.data) { free(img_jpg.data); } - fmt2jpg(img->data, img->size, img->w, img->h, PIXFORMAT_RGB565, quality, &img_jpg.data, &img_jpg.size); + fmt2jpg(img->data, img->size, img->w, img->h, PIXFORMAT_RGB565, quality, &img_jpg.data, (size_t *)&img_jpg.size); return py_image_from_struct(&img_jpg); } diff --git a/m5stack/components/M5Unified/CMakeLists.txt b/m5stack/components/M5Unified/CMakeLists.txt index 7c3b7bd3..9ede28c6 100644 --- a/m5stack/components/M5Unified/CMakeLists.txt +++ b/m5stack/components/M5Unified/CMakeLists.txt @@ -18,6 +18,8 @@ file(GLOB SRCS M5GFX/src/lgfx/v1/panel/*.cpp M5GFX/src/lgfx/v1/platforms/esp32/*.cpp M5GFX/src/lgfx/v1/platforms/esp32c3/*.cpp + # M5GFX/src/lgfx/v1/platforms/esp32s3/*.cpp + M5GFX/src/lgfx/v1/platforms/esp32p4/*.cpp M5GFX/src/lgfx/v1/touch/*.cpp M5Unified/src/*.cpp M5Unified/src/utility/*.cpp @@ -27,9 +29,9 @@ file(GLOB SRCS set(COMPONENT_SRCS ${SRCS}) if (IDF_VERSION_MAJOR GREATER_EQUAL 5) - set(COMPONENT_REQUIRES esp_adc nvs_flash efuse driver esp_timer main_${IDF_TARGET}) + set(COMPONENT_REQUIRES esp_adc nvs_flash efuse driver esp_timer esp_lcd main) else() - set(COMPONENT_REQUIRES esp_adc_cal nvs_flash efuse main) + set(COMPONENT_REQUIRES esp_adc_cal nvs_flash efuse esp_lcd main) endif() register_component() diff --git a/m5stack/components/M5Unified/M5GFX b/m5stack/components/M5Unified/M5GFX index 1618b1e7..801217f3 160000 --- a/m5stack/components/M5Unified/M5GFX +++ b/m5stack/components/M5Unified/M5GFX @@ -1 +1 @@ -Subproject commit 1618b1e7b0953b91987ecc8a8519607df6fa7624 +Subproject commit 801217f35569dd83a344937cbe182ee912c5841d diff --git a/m5stack/components/M5Unified/M5Unified b/m5stack/components/M5Unified/M5Unified index bdb7f89e..10e6f0be 160000 --- a/m5stack/components/M5Unified/M5Unified +++ b/m5stack/components/M5Unified/M5Unified @@ -1 +1 @@ -Subproject commit bdb7f89e02b8f341079efd7094ee7475a895961d +Subproject commit 10e6f0be22575429ce19557f85df98fea206d6e6 diff --git a/m5stack/components/M5Unified/mpy_m5unified.cpp b/m5stack/components/M5Unified/mpy_m5unified.cpp index fd8f010a..4ad633b7 100644 --- a/m5stack/components/M5Unified/mpy_m5unified.cpp +++ b/m5stack/components/M5Unified/mpy_m5unified.cpp @@ -469,6 +469,7 @@ mp_obj_t m5_begin(size_t n_args, const mp_obj_t *args) { // initial M5.begin(cfg); + M5.In_I2C.release(); in_i2c_init(); // if (M5.getBoard() != m5::board_t::board_M5StackCoreS3 // && M5.getBoard() != m5::board_t::board_M5StackCoreS3SE @@ -476,7 +477,7 @@ mp_obj_t m5_begin(size_t n_args, const mp_obj_t *args) { // && M5.getBoard() != m5::board_t::board_M5Tough // && M5.getBoard() != m5::board_t::board_M5AtomS3 // ) { - M5.In_I2C.release(); + // } M5.Display.clear(); diff --git a/m5stack/components/esp_dmx/src/dmx/hal/uart.c b/m5stack/components/esp_dmx/src/dmx/hal/uart.c index 1231bb1b..2b3d44b3 100644 --- a/m5stack/components/esp_dmx/src/dmx/hal/uart.c +++ b/m5stack/components/esp_dmx/src/dmx/hal/uart.c @@ -18,6 +18,7 @@ #include "driver/periph_ctrl.h" #include "driver/timer.h" #endif +#include "esp_private/periph_ctrl.h" #define DMX_UART_FULL_DEFAULT 1 #define DMX_UART_EMPTY_DEFAULT 8 @@ -320,18 +321,24 @@ static void DMX_ISR_ATTR dmx_uart_isr(void *arg) { } } +#if SOC_PERIPH_CLK_CTRL_SHARED +#define HP_UART_SRC_CLK_ATOMIC() PERIPH_RCC_ATOMIC() +#else +#define HP_UART_SRC_CLK_ATOMIC() +#endif + bool dmx_uart_init(dmx_port_t dmx_num, void *isr_context, int isr_flags) { struct dmx_uart_t *uart = &dmx_uart_context[dmx_num]; - periph_module_enable(uart_periph_signal[dmx_num].module); + periph_module_enable(PERIPH_UART0_MODULE + dmx_num); if (dmx_num != 0) { // Default UART port for console #if SOC_UART_REQUIRE_CORE_RESET // ESP32C3 workaround to prevent UART outputting garbage data uart_ll_set_reset_core(uart->dev, true); - periph_module_reset(uart_periph_signal[dmx_num].module); + periph_module_reset(PERIPH_UART0_MODULE + dmx_num); uart_ll_set_reset_core(uart->dev, false); #else - periph_module_reset(uart_periph_signal[dmx_num].module); + periph_module_reset(PERIPH_UART0_MODULE + dmx_num); #endif } #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0) @@ -345,10 +352,14 @@ bool dmx_uart_init(dmx_port_t dmx_num, void *isr_context, int isr_flags) { } uart_get_sclk_freq(UART_SCLK_DEFAULT, &sclk_freq); #else - uart_ll_set_sclk(uart->dev, UART_SCLK_DEFAULT); + HP_UART_SRC_CLK_ATOMIC() { + uart_ll_set_sclk(uart->dev, UART_SCLK_DEFAULT); + } uart_get_sclk_freq(UART_SCLK_DEFAULT, &sclk_freq); #endif - uart_ll_set_baudrate(uart->dev, DMX_BAUD_RATE, sclk_freq); + HP_UART_SRC_CLK_ATOMIC() { + uart_ll_set_baudrate(uart->dev, DMX_BAUD_RATE, sclk_freq); + } #else uart_ll_set_sclk(uart->dev, UART_SCLK_APB); uart_ll_set_baudrate(uart->dev, DMX_BAUD_RATE); @@ -376,7 +387,7 @@ bool dmx_uart_init(dmx_port_t dmx_num, void *isr_context, int isr_flags) { void dmx_uart_deinit(dmx_port_t dmx_num) { struct dmx_uart_t *uart = &dmx_uart_context[dmx_num]; if (uart->num != 0) { // Default UART port for console - periph_module_disable(uart_periph_signal[uart->num].module); + periph_module_disable(PERIPH_UART0_MODULE + dmx_num); } } @@ -402,7 +413,9 @@ void dmx_uart_set_baud_rate(dmx_port_t dmx_num, uint32_t baud_rate) { #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0) uint32_t sclk_freq; uart_get_sclk_freq(UART_SCLK_DEFAULT, &sclk_freq); - uart_ll_set_baudrate(uart->dev, baud_rate, sclk_freq); + HP_UART_SRC_CLK_ATOMIC() { + uart_ll_set_baudrate(uart->dev, baud_rate, sclk_freq); + } #else uart_ll_set_baudrate(uart->dev, baud_rate); #endif @@ -410,7 +423,7 @@ void dmx_uart_set_baud_rate(dmx_port_t dmx_num, uint32_t baud_rate) { void DMX_ISR_ATTR dmx_uart_invert_tx(dmx_port_t dmx_num, int invert) { struct dmx_uart_t *uart = &dmx_uart_context[dmx_num]; - #if CONFIG_IDF_TARGET_ESP32C6 + #if CONFIG_IDF_TARGET_ESP32C6 || CONFIG_IDF_TARGET_ESP32P4 uart->dev->conf0_sync.txd_inv = invert; uart_ll_update(uart->dev); #else @@ -420,7 +433,7 @@ void DMX_ISR_ATTR dmx_uart_invert_tx(dmx_port_t dmx_num, int invert) { int dmx_uart_get_rts(dmx_port_t dmx_num) { struct dmx_uart_t *uart = &dmx_uart_context[dmx_num]; - #if CONFIG_IDF_TARGET_ESP32C6 + #if CONFIG_IDF_TARGET_ESP32C6 || CONFIG_IDF_TARGET_ESP32P4 return uart->dev->conf0_sync.sw_rts; #else return uart->dev->conf0.sw_rts; diff --git a/m5stack/components/libffi/CMakeLists.txt b/m5stack/components/libffi/CMakeLists.txt index 6ffe5c56..7232b925 100644 --- a/m5stack/components/libffi/CMakeLists.txt +++ b/m5stack/components/libffi/CMakeLists.txt @@ -8,7 +8,7 @@ if(target STREQUAL "esp32" OR target STREQUAL "esp32s3") "libffi/src/xtensa/sysv.S" ) set(include_dirs libffi/include libffi/include/xtensa) -elseif(target STREQUAL "esp32c3" OR target STREQUAL "esp32c6") +elseif(target STREQUAL "esp32c3" OR target STREQUAL "esp32c6" OR target STREQUAL "esp32p4") set(srcs "libffi/src/prep_cif.c" "libffi/src/types.c" diff --git a/m5stack/include/files.mk b/m5stack/include/files.mk index 9bfce28a..98053834 100644 --- a/m5stack/include/files.mk +++ b/m5stack/include/files.mk @@ -19,7 +19,7 @@ endef ## ## define Package/patche - @echo "Push patch on $(1)" && \ + @echo "Push $(2) on $(1)" && \ file_name=$(shell basename $(2)) && \ if [ ! -e $(1)/prereq_$$file_name ]; then \ cd $(1) ; \ @@ -35,7 +35,7 @@ endef ## ## define Package/unpatche - @echo "Pop on $(1)" && \ + @echo "Pop $(2) on $(1)" && \ file_name=$(shell basename $(2)) && \ if [ -e $(1)/prereq_$$file_name ]; then \ cd $(1) ; \ diff --git a/m5stack/machine_i2c.c b/m5stack/machine_i2c.c index 0b9c3918..c8fc1014 100644 --- a/m5stack/machine_i2c.c +++ b/m5stack/machine_i2c.c @@ -49,9 +49,9 @@ #endif #endif -#if CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32C6 || CONFIG_IDF_TARGET_ESP32S3 +#if SOC_I2C_SUPPORT_XTAL #define I2C_SCLK_FREQ XTAL_CLK_FREQ -#elif CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32S2 +#elif SOC_I2C_SUPPORT_APB #define I2C_SCLK_FREQ APB_CLK_FREQ #else #error "unsupported I2C for ESP32 SoC variant" @@ -300,12 +300,15 @@ static void machine_hw_i2c_print(const mp_print_t *print, mp_obj_t self_in, mp_p } mp_obj_t machine_hw_i2c_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { - // MP_MACHINE_I2C_CHECK_FOR_LEGACY_SOFTI2C_CONSTRUCTION(n_args, n_kw, all_args); + // Create a SoftI2C instance if no id is specified (or is -1) but other arguments are given + if (n_args != 0) { + MP_MACHINE_I2C_CHECK_FOR_LEGACY_SOFTI2C_CONSTRUCTION(n_args, n_kw, all_args); + } // Parse args enum { ARG_id, ARG_scl, ARG_sda, ARG_freq, ARG_timeout }; static const mp_arg_t allowed_args[] = { - { MP_QSTR_id, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} }, + { MP_QSTR_id, MP_ARG_INT, {.u_int = I2C_NUM_0} }, { MP_QSTR_scl, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} }, { MP_QSTR_sda, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} }, { MP_QSTR_freq, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 400000} }, @@ -315,7 +318,9 @@ mp_obj_t machine_hw_i2c_make_new(const mp_obj_type_t *type, size_t n_args, size_ mp_arg_parse_all_kw_array(n_args, n_kw, all_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); // Get I2C bus - mp_int_t i2c_id = mp_obj_get_int(args[ARG_id].u_obj); + mp_int_t i2c_id = args[ARG_id].u_int; + + // Check if the I2C bus is valid if (!(I2C_NUM_0 <= i2c_id && i2c_id < I2C_NUM_MAX)) { mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("I2C(%d) doesn't exist"), i2c_id); } diff --git a/m5stack/machine_rtc.c b/m5stack/machine_rtc.c index 14eb98ad..f2c1c95b 100644 --- a/m5stack/machine_rtc.c +++ b/m5stack/machine_rtc.c @@ -63,14 +63,24 @@ typedef struct _machine_rtc_obj_t { #define MICROPY_HW_RTC_USER_MEM_MAX 2048 #endif +// A board can enable MICROPY_HW_RTC_MEM_INIT_ALWAYS to always clear out RTC memory on boot. +// Defaults to RTC_NOINIT_ATTR so the user memory survives WDT resets and the like. +#if MICROPY_HW_RTC_MEM_INIT_ALWAYS +#define _USER_MEM_ATTR RTC_DATA_ATTR +#else +#define _USER_MEM_ATTR RTC_NOINIT_ATTR +#endif + // Optionally compile user memory functionality if the size of memory is greater than 0 #if MICROPY_HW_RTC_USER_MEM_MAX > 0 #define MEM_MAGIC 0x75507921 -RTC_DATA_ATTR uint32_t rtc_user_mem_magic; -RTC_DATA_ATTR uint16_t rtc_user_mem_len; -RTC_DATA_ATTR uint8_t rtc_user_mem_data[MICROPY_HW_RTC_USER_MEM_MAX]; +_USER_MEM_ATTR uint32_t rtc_user_mem_magic; +_USER_MEM_ATTR uint16_t rtc_user_mem_len; +_USER_MEM_ATTR uint8_t rtc_user_mem_data[MICROPY_HW_RTC_USER_MEM_MAX]; #endif +#undef _USER_MEM_ATTR + // singleton RTC object static const machine_rtc_obj_t machine_rtc_obj = {{&machine_rtc_type}}; @@ -83,22 +93,31 @@ static mp_obj_t machine_rtc_make_new(const mp_obj_type_t *type, size_t n_args, s // check arguments mp_arg_check_num(n_args, n_kw, 0, 0, false); + #if MICROPY_HW_RTC_USER_MEM_MAX > 0 + if (rtc_user_mem_magic != MEM_MAGIC) { + rtc_user_mem_magic = MEM_MAGIC; + rtc_user_mem_len = 0; + } + #endif + // return constant object return (mp_obj_t)&machine_rtc_obj; } -static mp_obj_t machine_rtc_datetime_helper(mp_uint_t n_args, const mp_obj_t *args) { +static mp_obj_t machine_rtc_datetime_helper(mp_uint_t n_args, const mp_obj_t *args, int hour_index) { if (n_args == 1) { // Get time + struct timeval tv; + gettimeofday(&tv, NULL); + timeutils_struct_time_t tm; - struct tm tm; - gmtime_r(&tv.tv_sec, &tm); + timeutils_seconds_since_epoch_to_struct_time(tv.tv_sec, &tm); mp_obj_t tuple[8] = { - mp_obj_new_int(tm.tm_year + 1900), - mp_obj_new_int(tm.tm_mon + 1), + mp_obj_new_int(tm.tm_year), + mp_obj_new_int(tm.tm_mon), mp_obj_new_int(tm.tm_mday), mp_obj_new_int(tm.tm_wday), mp_obj_new_int(tm.tm_hour), @@ -110,22 +129,19 @@ static mp_obj_t machine_rtc_datetime_helper(mp_uint_t n_args, const mp_obj_t *ar return mp_obj_new_tuple(8, tuple); } else { // Set time + mp_obj_t *items; mp_obj_get_array_fixed_n(args[1], 8, &items); struct timeval tv = {0}; - - #if MICROPY_EPOCH_IS_1970 - tv.tv_sec = timeutils_seconds_since_epoch(mp_obj_get_int(items[0]), - mp_obj_get_int(items[1]), mp_obj_get_int(items[2]), - mp_obj_get_int(items[4]), mp_obj_get_int(items[5]), - mp_obj_get_int(items[6])); - #else - tv.tv_sec = timeutils_seconds_since_epoch(mp_obj_get_int(items[0]), - mp_obj_get_int(items[1]), mp_obj_get_int(items[2]), - mp_obj_get_int(items[4]), mp_obj_get_int(items[5]), - mp_obj_get_int(items[6]) + TIMEUTILS_SECONDS_1970_TO_2000); - #endif + tv.tv_sec = timeutils_seconds_since_epoch( + mp_obj_get_int(items[0]), + mp_obj_get_int(items[1]), + mp_obj_get_int(items[2]), + mp_obj_get_int(items[hour_index]), + mp_obj_get_int(items[hour_index + 1]), + mp_obj_get_int(items[hour_index + 2]) + ); tv.tv_usec = mp_obj_get_int(items[7]); settimeofday(&tv, NULL); @@ -133,7 +149,7 @@ static mp_obj_t machine_rtc_datetime_helper(mp_uint_t n_args, const mp_obj_t *ar } } static mp_obj_t machine_rtc_datetime(size_t n_args, const mp_obj_t *args) { - return machine_rtc_datetime_helper(n_args, args); + return machine_rtc_datetime_helper(n_args, args, 4); } static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(machine_rtc_datetime_obj, 1, 2, machine_rtc_datetime); @@ -208,14 +224,7 @@ static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(machine_rtc_timezone_obj, 1, 2, machi static mp_obj_t machine_rtc_init(mp_obj_t self_in, mp_obj_t date) { mp_obj_t args[2] = {self_in, date}; - machine_rtc_datetime_helper(2, args); - - #if MICROPY_HW_RTC_USER_MEM_MAX > 0 - if (rtc_user_mem_magic != MEM_MAGIC) { - rtc_user_mem_magic = MEM_MAGIC; - rtc_user_mem_len = 0; - } - #endif + machine_rtc_datetime_helper(2, args, 3); return mp_const_none; } diff --git a/m5stack/machine_sdcard.c b/m5stack/machine_sdcard.c index 72e00864..04f41799 100644 --- a/m5stack/machine_sdcard.c +++ b/m5stack/machine_sdcard.c @@ -73,18 +73,34 @@ typedef struct _sdcard_obj_t { #define _SECTOR_SIZE(self) (self->card.csd.sector_size) +// Number SPI buses available for firmware app (including for SD) +#define NUM_SD_SPI_BUS (SOC_SPI_PERIPH_NUM - 1) + +#if CONFIG_IDF_TARGET_ESP32 +#define SD_SLOT_MIN 1 +#elif SOC_SDMMC_HOST_SUPPORTED +#define SD_SLOT_MIN 0 +#else +#define SD_SLOT_MIN 2 +#endif +#define SD_SLOT_MAX (NUM_SD_SPI_BUS + 1) // Inclusive + // SPI bus default bus and device configuration. -static const spi_bus_config_t spi_bus_defaults[2] = { +static const spi_bus_config_t spi_bus_defaults[NUM_SD_SPI_BUS] = { { #if CONFIG_IDF_TARGET_ESP32 .miso_io_num = GPIO_NUM_19, .mosi_io_num = GPIO_NUM_23, .sclk_io_num = GPIO_NUM_18, - #else + #elif CONFIG_IDF_TARGET_ESP32S3 .miso_io_num = GPIO_NUM_36, .mosi_io_num = GPIO_NUM_35, .sclk_io_num = GPIO_NUM_37, + #else + .miso_io_num = GPIO_NUM_NC, + .mosi_io_num = GPIO_NUM_NC, + .sclk_io_num = GPIO_NUM_NC, #endif .data2_io_num = GPIO_NUM_NC, .data3_io_num = GPIO_NUM_NC, @@ -96,6 +112,7 @@ static const spi_bus_config_t spi_bus_defaults[2] = { .flags = SPICOMMON_BUSFLAG_MASTER | SPICOMMON_BUSFLAG_SCLK | SPICOMMON_BUSFLAG_MISO | SPICOMMON_BUSFLAG_MOSI, .intr_flags = 0, }, + #if NUM_SD_SPI_BUS > 1 { .miso_io_num = GPIO_NUM_2, .mosi_io_num = GPIO_NUM_15, @@ -110,28 +127,34 @@ static const spi_bus_config_t spi_bus_defaults[2] = { .flags = SPICOMMON_BUSFLAG_MASTER | SPICOMMON_BUSFLAG_SCLK | SPICOMMON_BUSFLAG_MISO | SPICOMMON_BUSFLAG_MOSI, .intr_flags = 0, }, + #endif }; #if CONFIG_IDF_TARGET_ESP32 -static const uint8_t spi_dma_channel_defaults[2] = { +static const uint8_t spi_dma_channel_defaults[NUM_SD_SPI_BUS] = { 2, 1, }; #endif -static const sdspi_device_config_t spi_dev_defaults[2] = { +static const sdspi_device_config_t spi_dev_defaults[NUM_SD_SPI_BUS] = { + #if NUM_SD_SPI_BUS > 1 { #if CONFIG_IDF_TARGET_ESP32 .host_id = VSPI_HOST, .gpio_cs = GPIO_NUM_5, - #else + #elif CONFIG_IDF_TARGET_ESP32S3 .host_id = SPI3_HOST, .gpio_cs = GPIO_NUM_34, + #else + .host_id = SPI3_HOST, + .gpio_cs = GPIO_NUM_NC, #endif .gpio_cd = SDSPI_SLOT_NO_CD, .gpio_wp = SDSPI_SLOT_NO_WP, .gpio_int = SDSPI_SLOT_NO_INT, }, + #endif SDSPI_DEVICE_CONFIG_DEFAULT(), // HSPI (ESP32) / SPI2 (ESP32S3) }; @@ -163,12 +186,15 @@ static esp_err_t sdcard_ensure_card_init(sdcard_card_obj_t *self, bool force) { // Expose the SD card or MMC as an object with the block protocol. // Create a new SDCard object -// The driver supports either the host SD/MMC controller (default) or SPI mode -// In both cases there are two "slots". Slot 0 on the SD/MMC controller is -// typically tied up with the flash interface in most ESP32 modules but in -// theory supports 1, 4 or 8-bit transfers. Slot 1 supports only 1 and 4-bit -// transfers. Only 1-bit is supported on the SPI interfaces. -// card = SDCard(slot=1, width=None, present_pin=None, wp_pin=None) +// +// SD/MMC or SPI mode is determined by the slot argument +// 0,1 is SD/MMC mode where supported. +// 2,3 is SPI mode where supported (1-bit only) +// +// Original ESP32 can't use 0 +// ESP32-C3/C6/etc can only use 2 (only one SPI bus, no SD/MMC controller) +// +// Consult machine.SDCard docs for more details. static mp_obj_t machine_sdcard_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { // check arguments @@ -181,10 +207,19 @@ static mp_obj_t machine_sdcard_make_new(const mp_obj_type_t *type, size_t n_args ARG_mosi, ARG_sck, ARG_cs, + #if SOC_SDMMC_USE_GPIO_MATRIX + ARG_cmd, + ARG_data, + #endif ARG_freq, }; + #if SOC_SDMMC_HOST_SUPPORTED + static const int DEFAULT_SLOT = 1; + #else + static const int DEFAULT_SLOT = SD_SLOT_MAX; + #endif static const mp_arg_t allowed_args[] = { - { MP_QSTR_slot, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 1} }, + { MP_QSTR_slot, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = DEFAULT_SLOT} }, { MP_QSTR_width, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 1} }, { MP_QSTR_cd, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} }, { MP_QSTR_wp, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} }, @@ -193,6 +228,11 @@ static mp_obj_t machine_sdcard_make_new(const mp_obj_type_t *type, size_t n_args { MP_QSTR_mosi, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} }, { MP_QSTR_sck, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} }, { MP_QSTR_cs, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} }, + // Optional assignment of SDMMC interface pins, if host supports this + #if SOC_SDMMC_USE_GPIO_MATRIX + { MP_QSTR_cmd, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} }, + { MP_QSTR_data, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} }, + #endif // freq is valid for both SPI and SDMMC interfaces { MP_QSTR_freq, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 20000000} }, }; @@ -207,7 +247,7 @@ static mp_obj_t machine_sdcard_make_new(const mp_obj_type_t *type, size_t n_args mp_arg_parse_all(n_args, args, &kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, arg_vals); - DEBUG_printf(" slot=%ld, width=%ld, cd=%p, wp=%p", + DEBUG_printf(" slot=%d, width=%d, cd=%p, wp=%p", arg_vals[ARG_slot].u_int, arg_vals[ARG_width].u_int, arg_vals[ARG_cd].u_obj, arg_vals[ARG_wp].u_obj); @@ -215,23 +255,45 @@ static mp_obj_t machine_sdcard_make_new(const mp_obj_type_t *type, size_t n_args arg_vals[ARG_miso].u_obj, arg_vals[ARG_mosi].u_obj, arg_vals[ARG_sck].u_obj, arg_vals[ARG_cs].u_obj); + #if SOC_SDMMC_USE_GPIO_MATRIX + DEBUG_printf(" cmd=%p, data=%p", + arg_vals[ARG_cmd].u_obj, arg_vals[ARG_data].u_obj); + #endif + int slot_num = arg_vals[ARG_slot].u_int; - if (slot_num < 0 || slot_num > 3) { - mp_raise_ValueError(MP_ERROR_TEXT("slot number must be between 0 and 3 inclusive")); + if (slot_num < SD_SLOT_MIN || slot_num > SD_SLOT_MAX) { + mp_raise_ValueError(MP_ERROR_TEXT("invalid slot number")); } + #if SOC_SDMMC_HOST_SUPPORTED // Slots 0 and 1 are native SD/MMC, slots 2 and 3 are SPI bool is_spi = (slot_num >= 2); + #else + bool is_spi = true; + #endif if (is_spi) { slot_num -= 2; + assert(slot_num < NUM_SD_SPI_BUS); } + // Verify valid argument combinations + #if SOC_SDMMC_USE_GPIO_MATRIX + if (is_spi && (arg_vals[ARG_cmd].u_obj != mp_const_none + || arg_vals[ARG_data].u_obj != mp_const_none)) { + mp_raise_ValueError(MP_ERROR_TEXT("invalid config: SPI slot with SDMMC pin arguments")); + } + #endif + #if SOC_SDMMC_HOST_SUPPORTED + if (!is_spi && (arg_vals[ARG_miso].u_obj != mp_const_none + || arg_vals[ARG_mosi].u_obj != mp_const_none + || arg_vals[ARG_cs].u_obj != mp_const_none)) { + mp_raise_ValueError(MP_ERROR_TEXT("invalid config: SDMMC slot with SPI pin arguments")); + } + #endif + DEBUG_printf(" Setting up host configuration"); sdcard_card_obj_t *self = mp_obj_malloc_with_finaliser(sdcard_card_obj_t, &machine_sdcard_type); - // Start of modification section, by M5Stack - self->shared_bus = false; - // End of modification section, by M5Stack self->flags = 0; // Note that these defaults are macros that expand to structure // constants so we can't directly assign them to fields. @@ -239,21 +301,17 @@ static mp_obj_t machine_sdcard_make_new(const mp_obj_type_t *type, size_t n_args if (is_spi) { sdmmc_host_t _temp_host = SDSPI_HOST_DEFAULT(); _temp_host.max_freq_khz = freq / 1000; + // SPI SDMMC sets the slot to the SPI host ID + _temp_host.slot = spi_dev_defaults[slot_num].host_id; self->host = _temp_host; - } else { + } + #if SOC_SDMMC_HOST_SUPPORTED + else { sdmmc_host_t _temp_host = SDMMC_HOST_DEFAULT(); _temp_host.max_freq_khz = freq / 1000; self->host = _temp_host; } - - if (is_spi) { - // Needs to match spi_dev_defaults above. - #if CONFIG_IDF_TARGET_ESP32 - self->host.slot = slot_num ? HSPI_HOST : VSPI_HOST; - #else - self->host.slot = slot_num ? SPI2_HOST : SPI3_HOST; - #endif - } + #endif DEBUG_printf(" Calling host.init()"); @@ -280,6 +338,16 @@ static mp_obj_t machine_sdcard_make_new(const mp_obj_type_t *type, size_t n_args SET_CONFIG_PIN(dev_config, gpio_cd, ARG_cd); SET_CONFIG_PIN(dev_config, gpio_wp, ARG_wp); + // On chips other than original ESP32 and S3, there are not + // always default SPI pins assigned + if (dev_config.gpio_cs == GPIO_NUM_NC + || bus_config.miso_io_num == GPIO_NUM_NC + || bus_config.mosi_io_num == GPIO_NUM_NC + || bus_config.sclk_io_num == GPIO_NUM_NC) { + mp_raise_ValueError(MP_ERROR_TEXT("SPI pin values required")); + } + + DEBUG_printf(" Calling spi_bus_initialize()"); esp_err_t result = spi_bus_initialize(spi_host_id, &bus_config, dma_channel); if (result == ESP_ERR_INVALID_STATE) { // Already initialised, so just return the existing object. @@ -303,7 +371,9 @@ static mp_obj_t machine_sdcard_make_new(const mp_obj_type_t *type, size_t n_args // mp_raise_ValueError(MP_ERROR_TEXT("SPI bus already in use")); // } // End of modification section, by M5Stack - } else { + } + #if SOC_SDMMC_HOST_SUPPORTED + else { // SD/MMC interface DEBUG_printf(" Setting up SDMMC slot configuration"); sdmmc_slot_config_t slot_config = SDMMC_SLOT_CONFIG_DEFAULT(); @@ -322,9 +392,36 @@ static mp_obj_t machine_sdcard_make_new(const mp_obj_type_t *type, size_t n_args mp_raise_ValueError(MP_ERROR_TEXT("width must be 1 or 4 (or 8 on slot 0)")); } + #if SOC_SDMMC_USE_GPIO_MATRIX + // Optionally configure all the SDMMC pins, if chip supports this + SET_CONFIG_PIN(slot_config, clk, ARG_sck); // reuse SPI SCK for CLK + SET_CONFIG_PIN(slot_config, cmd, ARG_cmd); + if (arg_vals[ARG_data].u_obj != mp_const_none) { + mp_obj_t *data_vals; + size_t data_len; + mp_obj_get_array(arg_vals[ARG_data].u_obj, &data_len, &data_vals); + if (data_len != width) { + mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("data argument length must match width %d"), width); + } + slot_config.d0 = machine_pin_get_id(data_vals[0]); + if (width > 1) { + slot_config.d1 = machine_pin_get_id(data_vals[1]); + slot_config.d2 = machine_pin_get_id(data_vals[2]); + slot_config.d3 = machine_pin_get_id(data_vals[3]); + } + if (width == 8) { + slot_config.d4 = machine_pin_get_id(data_vals[4]); + slot_config.d5 = machine_pin_get_id(data_vals[5]); + slot_config.d6 = machine_pin_get_id(data_vals[6]); + slot_config.d7 = machine_pin_get_id(data_vals[7]); + } + } + #endif + DEBUG_printf(" Calling init_slot()"); check_esp_err(sdmmc_host_init_slot(self->host.slot, &slot_config)); } + #endif // SOC_SDMMC_HOST_SUPPORTED DEBUG_printf(" Returning new card object: %p", self); return MP_OBJ_FROM_PTR(self); @@ -382,14 +479,13 @@ static mp_obj_t machine_sdcard_readblocks(mp_obj_t self_in, mp_obj_t block_num, err = sdcard_ensure_card_init((sdcard_card_obj_t *)self, false); if (err != ESP_OK) { - return false; + return mp_const_false; } mp_get_buffer_raise(buf, &bufinfo, MP_BUFFER_WRITE); err = sdmmc_read_sectors(&(self->card), bufinfo.buf, mp_obj_get_int(block_num), bufinfo.len / _SECTOR_SIZE(self)); - int ret = err == ESP_OK ? 0 : -MP_EIO; - return MP_OBJ_NEW_SMALL_INT(ret); + return mp_obj_new_bool(err == ESP_OK); } static MP_DEFINE_CONST_FUN_OBJ_3(machine_sdcard_readblocks_obj, machine_sdcard_readblocks); @@ -400,14 +496,13 @@ static mp_obj_t machine_sdcard_writeblocks(mp_obj_t self_in, mp_obj_t block_num, err = sdcard_ensure_card_init((sdcard_card_obj_t *)self, false); if (err != ESP_OK) { - return false; + return mp_const_false; } mp_get_buffer_raise(buf, &bufinfo, MP_BUFFER_READ); err = sdmmc_write_sectors(&(self->card), bufinfo.buf, mp_obj_get_int(block_num), bufinfo.len / _SECTOR_SIZE(self)); - int ret = err == ESP_OK ? 0 : -MP_EIO; - return MP_OBJ_NEW_SMALL_INT(ret); + return mp_obj_new_bool(err == ESP_OK); } static MP_DEFINE_CONST_FUN_OBJ_3(machine_sdcard_writeblocks_obj, machine_sdcard_writeblocks); diff --git a/m5stack/main.c b/m5stack/main.c index b6c7c672..295fb76e 100644 --- a/m5stack/main.c +++ b/m5stack/main.c @@ -40,6 +40,7 @@ #include "esp_task.h" #include "esp_event.h" #include "esp_log.h" +#include "esp_memory_utils.h" #include "esp_psram.h" #include "py/cstack.h" @@ -110,9 +111,11 @@ void mp_task(void *pvParameter) { #elif MICROPY_HW_ENABLE_USBDEV usb_init(); #endif + // Start of modification section, by M5Stack #if MICROPY_HW_ENABLE_USB_RUNTIME_DEVICE mp_usbd_init(); #endif + // End of modification section, by M5Stack #if MICROPY_HW_ENABLE_UART_REPL uart_stdout_init(); #endif @@ -145,9 +148,12 @@ void mp_task(void *pvParameter) { #if MICROPY_PY_MACHINE_I2S machine_i2s_init0(); #endif + + // Start of modification section, by M5Stack #if MICROPY_HW_ENABLE_USB_RUNTIME_DEVICE mp_usbd_deinit(); #endif + // End of modification section, by M5Stack // run boot-up scripts pyexec_frozen_module("_boot.py", false); @@ -193,6 +199,12 @@ void mp_task(void *pvParameter) { mp_thread_deinit(); #endif + // Start of modification section, by M5Stack + #if MICROPY_HW_ENABLE_USB_RUNTIME_DEVICE + mp_usbd_deinit(); + #endif + // End of modification section, by M5Stack + gc_sweep_all(); // Free any native code pointers that point to iRAM. @@ -256,6 +268,13 @@ void *esp_native_code_commit(void *buf, size_t len, void *reloc) { len = (len + 3) & ~3; size_t len_node = sizeof(native_code_node_t) + len; native_code_node_t *node = heap_caps_malloc(len_node, MALLOC_CAP_EXEC); + #if CONFIG_IDF_TARGET_ESP32S2 + // Workaround for ESP-IDF bug https://github.com/espressif/esp-idf/issues/14835 + if (node != NULL && !esp_ptr_executable(node)) { + free(node); + node = NULL; + } + #endif // CONFIG_IDF_TARGET_ESP32S2 if (node == NULL) { m_malloc_fail(len_node); } diff --git a/m5stack/main/CMakeLists.txt b/m5stack/main/CMakeLists.txt new file mode 100644 index 00000000..12d8a30a --- /dev/null +++ b/m5stack/main/CMakeLists.txt @@ -0,0 +1,15 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +# Set location of base MicroPython directory. +if(NOT MICROPY_DIR) + get_filename_component(MICROPY_DIR ${PROJECT_DIR}/../micropython/ ABSOLUTE) +endif() + +# Set location of the ESP32 port directory. +if(NOT MICROPY_PORT_DIR) + get_filename_component(MICROPY_PORT_DIR ${CMAKE_CURRENT_LIST_DIR}/.. ABSOLUTE) +endif() + +include(${CMAKE_CURRENT_LIST_DIR}/../esp32_common.cmake) diff --git a/m5stack/main/idf_component.yml b/m5stack/main/idf_component.yml new file mode 100644 index 00000000..d495fbcb --- /dev/null +++ b/m5stack/main/idf_component.yml @@ -0,0 +1,20 @@ +## IDF Component Manager Manifest File +dependencies: + espressif/mdns: "~1.1.0" + espressif/esp_tinyusb: + rules: + - if: "target in [esp32s2, esp32s3]" + version: "~1.0.0" + idf: + version: ">=5.2.0" + espressif/esp_codec_dev: "^1.3.2" + espressif/esp_hosted: + rules: + - if: "target in [esp32p4]" + version: + 1.4.0 + espressif/esp_wifi_remote: + rules: + - if: "target in [esp32p4]" + version: + 0.8.5 diff --git a/m5stack/modesp32.c b/m5stack/modesp32.c index 4021c90d..d7619ad7 100644 --- a/m5stack/modesp32.c +++ b/m5stack/modesp32.c @@ -54,7 +54,6 @@ static mp_obj_t esp32_wake_on_touch(const mp_obj_t wake) { if (machine_rtc_config.ext0_pin != -1) { mp_raise_ValueError(MP_ERROR_TEXT("no resources")); } - // mp_raise_msg(&mp_type_RuntimeError, MP_ERROR_TEXT("touchpad wakeup not available for this version of ESP-IDF")); machine_rtc_config.wake_on_touch = mp_obj_is_true(wake); return mp_const_none; @@ -138,7 +137,7 @@ static mp_obj_t esp32_wake_on_ulp(const mp_obj_t wake) { } static MP_DEFINE_CONST_FUN_OBJ_1(esp32_wake_on_ulp_obj, esp32_wake_on_ulp); -#if !SOC_GPIO_SUPPORT_HOLD_SINGLE_IO_IN_DSLP +#if SOC_GPIO_SUPPORT_HOLD_IO_IN_DSLP && !SOC_GPIO_SUPPORT_HOLD_SINGLE_IO_IN_DSLP static mp_obj_t esp32_gpio_deep_sleep_hold(const mp_obj_t enable) { if (mp_obj_is_true(enable)) { gpio_deep_sleep_hold_en(); @@ -249,7 +248,7 @@ static const mp_rom_map_elem_t esp32_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR_wake_on_ext0), MP_ROM_PTR(&esp32_wake_on_ext0_obj) }, { MP_ROM_QSTR(MP_QSTR_wake_on_ext1), MP_ROM_PTR(&esp32_wake_on_ext1_obj) }, { MP_ROM_QSTR(MP_QSTR_wake_on_ulp), MP_ROM_PTR(&esp32_wake_on_ulp_obj) }, - #if !SOC_GPIO_SUPPORT_HOLD_SINGLE_IO_IN_DSLP + #if SOC_GPIO_SUPPORT_HOLD_IO_IN_DSLP && !SOC_GPIO_SUPPORT_HOLD_SINGLE_IO_IN_DSLP { MP_ROM_QSTR(MP_QSTR_gpio_deep_sleep_hold), MP_ROM_PTR(&esp32_gpio_deep_sleep_hold_obj) }, #endif #if CONFIG_IDF_TARGET_ESP32 diff --git a/m5stack/network_wlan.c b/m5stack/network_wlan.c index eca66c5f..24340058 100644 --- a/m5stack/network_wlan.c +++ b/m5stack/network_wlan.c @@ -113,7 +113,6 @@ static void network_wlan_wifi_event_handler(void *event_handler_arg, esp_event_b // AP may not exist, or it may have momentarily dropped out; try to reconnect. message = "no AP found"; break; - #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 2, 0) case WIFI_REASON_NO_AP_FOUND_IN_RSSI_THRESHOLD: // No AP with RSSI within given threshold exists, or it may have momentarily dropped out; try to reconnect. message = "no AP with RSSI within threshold found"; @@ -126,7 +125,6 @@ static void network_wlan_wifi_event_handler(void *event_handler_arg, esp_event_b // No AP with compatible security exists, or it may have momentarily dropped out; try to reconnect. message = "no AP with compatible security found"; break; - #endif case WIFI_REASON_AUTH_FAIL: // Password may be wrong, or it just failed to connect; try to reconnect. message = "authentication failed"; @@ -367,14 +365,12 @@ static mp_obj_t network_wlan_status(size_t n_args, const mp_obj_t *args) { return MP_OBJ_NEW_SMALL_INT(STAT_GOT_IP); } else if (wifi_sta_disconn_reason == WIFI_REASON_NO_AP_FOUND) { return MP_OBJ_NEW_SMALL_INT(WIFI_REASON_NO_AP_FOUND); - #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 2, 0) } else if (wifi_sta_disconn_reason == WIFI_REASON_NO_AP_FOUND_IN_RSSI_THRESHOLD) { return MP_OBJ_NEW_SMALL_INT(WIFI_REASON_NO_AP_FOUND_IN_RSSI_THRESHOLD); } else if (wifi_sta_disconn_reason == WIFI_REASON_NO_AP_FOUND_IN_AUTHMODE_THRESHOLD) { return MP_OBJ_NEW_SMALL_INT(WIFI_REASON_NO_AP_FOUND_IN_AUTHMODE_THRESHOLD); } else if (wifi_sta_disconn_reason == WIFI_REASON_NO_AP_FOUND_W_COMPATIBLE_SECURITY) { return MP_OBJ_NEW_SMALL_INT(WIFI_REASON_NO_AP_FOUND_W_COMPATIBLE_SECURITY); - #endif } else if ((wifi_sta_disconn_reason == WIFI_REASON_AUTH_FAIL) || (wifi_sta_disconn_reason == WIFI_REASON_CONNECTION_FAIL)) { // wrong password return MP_OBJ_NEW_SMALL_INT(WIFI_REASON_AUTH_FAIL); @@ -549,21 +545,38 @@ static mp_obj_t network_wlan_config(size_t n_args, const mp_obj_t *args, mp_map_ break; } case MP_QSTR_channel: { - uint8_t primary; - wifi_second_chan_t secondary; - // Get the current value of secondary - esp_exceptions(esp_wifi_get_channel(&primary, &secondary)); - primary = mp_obj_get_int(kwargs->table[i].value); - esp_err_t err = esp_wifi_set_channel(primary, secondary); - if (err == ESP_ERR_INVALID_ARG) { - // May need to swap secondary channel above to below or below to above - secondary = ( - (secondary == WIFI_SECOND_CHAN_ABOVE) - ? WIFI_SECOND_CHAN_BELOW - : (secondary == WIFI_SECOND_CHAN_BELOW) + uint8_t channel = mp_obj_get_int(kwargs->table[i].value); + if (self->if_id == ESP_IF_WIFI_AP) { + cfg.ap.channel = channel; + } else { + // This setting is only used to determine the + // starting channel for a scan, so it can result in + // slightly faster connection times. + cfg.sta.channel = channel; + + // This additional code to directly set the channel + // on the STA interface is only relevant for ESP-NOW + // (when there is no STA connection attempt.) + uint8_t old_primary; + wifi_second_chan_t secondary; + // Get the current value of secondary + esp_exceptions(esp_wifi_get_channel(&old_primary, &secondary)); + esp_err_t err = esp_wifi_set_channel(channel, secondary); + if (err == ESP_ERR_INVALID_ARG) { + // May need to swap secondary channel above to below or below to above + secondary = ( + (secondary == WIFI_SECOND_CHAN_ABOVE) + ? WIFI_SECOND_CHAN_BELOW + : (secondary == WIFI_SECOND_CHAN_BELOW) ? WIFI_SECOND_CHAN_ABOVE : WIFI_SECOND_CHAN_NONE); - esp_exceptions(esp_wifi_set_channel(primary, secondary)); + err = esp_wifi_set_channel(channel, secondary); + } + esp_exceptions(err); + if (channel != old_primary) { + // Workaround the ESP-IDF Wi-Fi stack sometimes taking a moment to change channels + mp_hal_delay_ms(1); + } } break; } @@ -730,6 +743,7 @@ static const mp_rom_map_elem_t wlan_if_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_ifconfig), MP_ROM_PTR(&esp_network_ifconfig_obj) }, { MP_ROM_QSTR(MP_QSTR_ipconfig), MP_ROM_PTR(&esp_nic_ipconfig_obj) }, { MP_ROM_QSTR(MP_QSTR_set_default), MP_ROM_PTR(&esp_nic_set_default_obj) }, + // Constants { MP_ROM_QSTR(MP_QSTR_IF_STA), MP_ROM_INT(WIFI_IF_STA)}, { MP_ROM_QSTR(MP_QSTR_IF_AP), MP_ROM_INT(WIFI_IF_AP)}, @@ -744,6 +758,16 @@ static const mp_rom_map_elem_t wlan_if_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_SEC_WPA2_WPA3), MP_ROM_INT(WIFI_AUTH_WPA2_WPA3_PSK) }, { MP_ROM_QSTR(MP_QSTR_SEC_WAPI), MP_ROM_INT(WIFI_AUTH_WAPI_PSK) }, { MP_ROM_QSTR(MP_QSTR_SEC_OWE), MP_ROM_INT(WIFI_AUTH_OWE) }, + { MP_ROM_QSTR(MP_QSTR_SEC_WPA3_ENT_192), MP_ROM_INT(WIFI_AUTH_WPA3_ENT_192) }, + { MP_ROM_QSTR(MP_QSTR_SEC_WPA3_EXT_PSK), MP_ROM_INT(WIFI_AUTH_WPA3_EXT_PSK) }, + { MP_ROM_QSTR(MP_QSTR_SEC_WPA3_EXT_PSK_MIXED_MODE), MP_ROM_INT(WIFI_AUTH_WPA3_EXT_PSK_MIXED_MODE) }, + #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0) + { MP_ROM_QSTR(MP_QSTR_SEC_DPP), MP_ROM_INT(WIFI_AUTH_DPP) }, + #endif + #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 4, 0) + { MP_ROM_QSTR(MP_QSTR_SEC_WPA3_ENT), MP_ROM_INT(WIFI_AUTH_WPA3_ENTERPRISE) }, + { MP_ROM_QSTR(MP_QSTR_SEC_WPA2_WPA3_ENT), MP_ROM_INT(WIFI_AUTH_WPA2_WPA3_ENTERPRISE) }, + #endif { MP_ROM_QSTR(MP_QSTR_PM_NONE), MP_ROM_INT(WIFI_PS_NONE) }, { MP_ROM_QSTR(MP_QSTR_PM_PERFORMANCE), MP_ROM_INT(WIFI_PS_MIN_MODEM) }, @@ -751,6 +775,16 @@ static const mp_rom_map_elem_t wlan_if_locals_dict_table[] = { }; static MP_DEFINE_CONST_DICT(wlan_if_locals_dict, wlan_if_locals_dict_table); +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 4, 0) +_Static_assert(WIFI_AUTH_MAX == 16, "Synchronize WIFI_AUTH_XXX constants with the ESP-IDF. Look at esp-idf/components/esp_wifi/include/esp_wifi_types_generic.h"); +#elif ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0) +_Static_assert(WIFI_AUTH_MAX == 14, "Synchronize WIFI_AUTH_XXX constants with the ESP-IDF. Look at esp-idf/components/esp_wifi/include/esp_wifi_types_generic.h"); +#elif ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 2, 0) +_Static_assert(WIFI_AUTH_MAX == 13, "Synchronize WIFI_AUTH_XXX constants with the ESP-IDF. Look at esp-idf/components/esp_wifi/include/esp_wifi_types.h"); +#else +#error "Error in macro logic, all supported versions should be covered." +#endif + MP_DEFINE_CONST_OBJ_TYPE( esp_network_wlan_type, MP_QSTR_WLAN, diff --git a/m5stack/patches/0009-micropython-1.25.0-add-esp32p4-pins.patch b/m5stack/patches/0009-micropython-1.25.0-add-esp32p4-pins.patch new file mode 100644 index 00000000..7f37562b --- /dev/null +++ b/m5stack/patches/0009-micropython-1.25.0-add-esp32p4-pins.patch @@ -0,0 +1,80 @@ +Index: micropython-origin/ports/esp32/boards/make-pins.py +=================================================================== +--- micropython-origin.orig/ports/esp32/boards/make-pins.py ++++ micropython-origin/ports/esp32/boards/make-pins.py +@@ -8,7 +8,7 @@ import boardgen + + + # Pins start at zero, and the highest pin index on any ESP32* chip is 48. +-NUM_GPIOS = 49 ++NUM_GPIOS = 55 + + + class Esp32Pin(boardgen.Pin): +Index: micropython-origin/ports/esp32/machine_pin.h +=================================================================== +--- micropython-origin.orig/ports/esp32/machine_pin.h ++++ micropython-origin/ports/esp32/machine_pin.h +@@ -174,6 +174,62 @@ + #define MICROPY_HW_ENABLE_GPIO48 (1) + #endif + ++#elif CONFIG_IDF_TARGET_ESP32P4 ++#define MICROPY_HW_ENABLE_GPIO0 (1) ++#define MICROPY_HW_ENABLE_GPIO1 (1) ++#define MICROPY_HW_ENABLE_GPIO2 (1) ++#define MICROPY_HW_ENABLE_GPIO3 (1) ++#define MICROPY_HW_ENABLE_GPIO4 (1) ++#define MICROPY_HW_ENABLE_GPIO5 (1) ++#define MICROPY_HW_ENABLE_GPIO6 (1) ++#define MICROPY_HW_ENABLE_GPIO7 (1) ++#define MICROPY_HW_ENABLE_GPIO8 (1) ++#define MICROPY_HW_ENABLE_GPIO9 (1) ++#define MICROPY_HW_ENABLE_GPIO10 (1) ++#define MICROPY_HW_ENABLE_GPIO11 (1) ++#define MICROPY_HW_ENABLE_GPIO12 (1) ++#define MICROPY_HW_ENABLE_GPIO13 (1) ++#define MICROPY_HW_ENABLE_GPIO14 (1) ++#define MICROPY_HW_ENABLE_GPIO15 (1) ++#define MICROPY_HW_ENABLE_GPIO16 (1) ++#define MICROPY_HW_ENABLE_GPIO17 (1) ++#define MICROPY_HW_ENABLE_GPIO18 (1) ++#define MICROPY_HW_ENABLE_GPIO19 (1) ++#define MICROPY_HW_ENABLE_GPIO20 (1) ++#define MICROPY_HW_ENABLE_GPIO21 (1) ++#define MICROPY_HW_ENABLE_GPIO22 (1) ++#define MICROPY_HW_ENABLE_GPIO23 (1) ++#define MICROPY_HW_ENABLE_GPIO24 (0) ++#define MICROPY_HW_ENABLE_GPIO25 (0) ++#define MICROPY_HW_ENABLE_GPIO26 (0) ++#define MICROPY_HW_ENABLE_GPIO27 (0) ++#define MICROPY_HW_ENABLE_GPIO28 (1) ++#define MICROPY_HW_ENABLE_GPIO29 (1) ++#define MICROPY_HW_ENABLE_GPIO30 (1) ++#define MICROPY_HW_ENABLE_GPIO31 (1) ++#define MICROPY_HW_ENABLE_GPIO32 (1) ++#define MICROPY_HW_ENABLE_GPIO33 (1) ++#define MICROPY_HW_ENABLE_GPIO34 (1) ++#define MICROPY_HW_ENABLE_GPIO35 (1) ++#define MICROPY_HW_ENABLE_GPIO36 (1) ++#define MICROPY_HW_ENABLE_GPIO37 (1) ++#define MICROPY_HW_ENABLE_GPIO38 (1) ++#define MICROPY_HW_ENABLE_GPIO39 (1) ++#define MICROPY_HW_ENABLE_GPIO40 (1) ++#define MICROPY_HW_ENABLE_GPIO41 (1) ++#define MICROPY_HW_ENABLE_GPIO42 (1) ++#define MICROPY_HW_ENABLE_GPIO43 (1) ++#define MICROPY_HW_ENABLE_GPIO44 (1) ++#define MICROPY_HW_ENABLE_GPIO45 (1) ++#define MICROPY_HW_ENABLE_GPIO46 (1) ++#define MICROPY_HW_ENABLE_GPIO47 (1) ++#define MICROPY_HW_ENABLE_GPIO48 (1) ++#define MICROPY_HW_ENABLE_GPIO49 (1) ++#define MICROPY_HW_ENABLE_GPIO50 (1) ++#define MICROPY_HW_ENABLE_GPIO51 (1) ++#define MICROPY_HW_ENABLE_GPIO52 (1) ++#define MICROPY_HW_ENABLE_GPIO53 (1) ++#define MICROPY_HW_ENABLE_GPIO54 (1) + #endif + + typedef struct _machine_pin_irq_obj_t { diff --git a/m5stack/patches/0010-micropython-1.25.0-machine-adc-v5.x.patch b/m5stack/patches/0010-micropython-1.25.0-machine-adc-v5.x.patch new file mode 100644 index 00000000..6ec7e4ed --- /dev/null +++ b/m5stack/patches/0010-micropython-1.25.0-machine-adc-v5.x.patch @@ -0,0 +1,224 @@ +Index: micropython-origin/ports/esp32/adc.c +=================================================================== +--- micropython-origin.orig/ports/esp32/adc.c ++++ micropython-origin/ports/esp32/adc.c +@@ -27,8 +27,6 @@ + + #include "py/mphal.h" + #include "adc.h" +-#include "driver/adc.h" +-#include "esp_adc/adc_cali_scheme.h" + + #define DEFAULT_VREF 1100 + +@@ -57,51 +55,52 @@ void madcblock_bits_helper(machine_adc_b + mp_raise_ValueError(MP_ERROR_TEXT("invalid bits")); + } + self->bits = bits; +- +- if (self->unit_id == ADC_UNIT_1) { +- adc1_config_width(self->width); +- } + } + + mp_int_t madcblock_read_helper(machine_adc_block_obj_t *self, adc_channel_t channel_id) { + int raw = 0; +- if (self->unit_id == ADC_UNIT_1) { +- raw = adc1_get_raw(channel_id); +- } else { +- #if (SOC_ADC_PERIPH_NUM >= 2) +- check_esp_err(adc2_get_raw(channel_id, self->width, &raw)); +- #endif +- } ++ check_esp_err(adc_oneshot_read(self->adc_handle, channel_id, &raw)); + return raw; + } + +-static esp_err_t ensure_adc_calibration(machine_adc_block_obj_t *self, adc_atten_t atten) { ++static esp_err_t ensure_adc_calibration(machine_adc_block_obj_t *self, adc_channel_t channel, adc_atten_t atten) { + if (self->handle[atten] != NULL) { + return ESP_OK; + } ++ esp_err_t ret = ESP_FAIL; + +- #if ADC_CALI_SCHEME_CURVE_FITTING_SUPPORTED ++#if ADC_CALI_SCHEME_CURVE_FITTING_SUPPORTED ++ // DEBUG_printf("calibration scheme version is %s", "Curve Fitting"); + adc_cali_curve_fitting_config_t cali_config = { + .unit_id = self->unit_id, ++ .chan = channel, + .atten = atten, + .bitwidth = self->width, + }; +- return adc_cali_create_scheme_curve_fitting(&cali_config, &self->handle[atten]); +- #else ++ ret = adc_cali_create_scheme_curve_fitting(&cali_config, &self->handle[atten]); ++ if (ret == ESP_OK) { ++ return ESP_OK; ++ } ++#endif ++ ++#if ADC_CALI_SCHEME_LINE_FITTING_SUPPORTED ++ // DEBUG_printf("calibration scheme version is %s", "Line Fitting"); + adc_cali_line_fitting_config_t cali_config = { + .unit_id = self->unit_id, + .atten = atten, + .bitwidth = self->width, + }; +- return adc_cali_create_scheme_line_fitting(&cali_config, &self->handle[atten]); +- #endif ++ ret = adc_cali_create_scheme_line_fitting(&cali_config, &self->handle[atten]); ++#endif ++ ++ return ret; + } + + mp_int_t madcblock_read_uv_helper(machine_adc_block_obj_t *self, adc_channel_t channel_id, adc_atten_t atten) { + int raw = madcblock_read_helper(self, channel_id); + int uv; + +- check_esp_err(ensure_adc_calibration(self, atten)); ++ check_esp_err(ensure_adc_calibration(self, channel_id, atten)); + check_esp_err(adc_cali_raw_to_voltage(self->handle[atten], raw, &uv)); + + return (mp_int_t)uv * 1000; +Index: micropython-origin/ports/esp32/adc.h +=================================================================== +--- micropython-origin.orig/ports/esp32/adc.h ++++ micropython-origin/ports/esp32/adc.h +@@ -30,6 +30,7 @@ + + #include "py/runtime.h" + #include "esp_adc_cal.h" ++#include "esp_adc/adc_oneshot.h" + #include "esp_adc/adc_cali_scheme.h" + + #define ADC_ATTEN_MAX SOC_ADC_ATTEN_NUM +@@ -38,7 +39,8 @@ typedef struct _machine_adc_block_obj_t + mp_obj_base_t base; + adc_unit_t unit_id; + mp_int_t bits; +- adc_bits_width_t width; ++ adc_bitwidth_t width; ++ adc_oneshot_unit_handle_t adc_handle; + adc_cali_handle_t handle[ADC_ATTEN_MAX]; + } machine_adc_block_obj_t; + +Index: micropython-origin/ports/esp32/machine_adc.c +=================================================================== +--- micropython-origin.orig/ports/esp32/machine_adc.c ++++ micropython-origin/ports/esp32/machine_adc.c +@@ -30,7 +30,6 @@ + + #include "py/mphal.h" + #include "adc.h" +-#include "driver/adc.h" + + #define ADCBLOCK1 (&madcblock_obj[0]) + #define ADCBLOCK2 (&madcblock_obj[1]) +@@ -123,6 +122,21 @@ static const machine_adc_obj_t madc_obj[ + {{&machine_adc_type}, ADCBLOCK2, ADC_CHANNEL_7, GPIO_NUM_18}, + {{&machine_adc_type}, ADCBLOCK2, ADC_CHANNEL_8, GPIO_NUM_19}, + {{&machine_adc_type}, ADCBLOCK2, ADC_CHANNEL_9, GPIO_NUM_20}, ++ #elif CONFIG_IDF_TARGET_ESP32P4 ++ {{&machine_adc_type}, ADCBLOCK1, ADC_CHANNEL_0, GPIO_NUM_16}, ++ {{&machine_adc_type}, ADCBLOCK1, ADC_CHANNEL_1, GPIO_NUM_17}, ++ {{&machine_adc_type}, ADCBLOCK1, ADC_CHANNEL_2, GPIO_NUM_18}, ++ {{&machine_adc_type}, ADCBLOCK1, ADC_CHANNEL_3, GPIO_NUM_19}, ++ {{&machine_adc_type}, ADCBLOCK1, ADC_CHANNEL_4, GPIO_NUM_20}, ++ {{&machine_adc_type}, ADCBLOCK1, ADC_CHANNEL_5, GPIO_NUM_21}, ++ {{&machine_adc_type}, ADCBLOCK1, ADC_CHANNEL_6, GPIO_NUM_22}, ++ {{&machine_adc_type}, ADCBLOCK1, ADC_CHANNEL_7, GPIO_NUM_23}, ++ {{&machine_adc_type}, ADCBLOCK2, ADC_CHANNEL_0, GPIO_NUM_49}, ++ {{&machine_adc_type}, ADCBLOCK2, ADC_CHANNEL_1, GPIO_NUM_50}, ++ {{&machine_adc_type}, ADCBLOCK2, ADC_CHANNEL_2, GPIO_NUM_52}, ++ {{&machine_adc_type}, ADCBLOCK2, ADC_CHANNEL_3, GPIO_NUM_52}, ++ {{&machine_adc_type}, ADCBLOCK2, ADC_CHANNEL_4, GPIO_NUM_53}, ++ {{&machine_adc_type}, ADCBLOCK2, ADC_CHANNEL_5, GPIO_NUM_54}, + #endif + }; + +@@ -155,21 +169,28 @@ static void mp_machine_adc_print(const m + mp_printf(print, "ADC(Pin(%u), atten=%u)", self->gpio_id, madc_atten_get(self)); + } + ++static esp_err_t madc_config_helper(const machine_adc_obj_t *self, adc_atten_t atten, adc_bitwidth_t bitwidth) { ++ adc_oneshot_chan_cfg_t config = { ++ .bitwidth = bitwidth, ++ .atten = atten, ++ }; ++ return adc_oneshot_config_channel(self->block->adc_handle, self->channel_id, &config); ++} ++ + static void madc_atten_helper(const machine_adc_obj_t *self, mp_int_t atten) { +- esp_err_t err = ESP_FAIL; +- if (self->block->unit_id == ADC_UNIT_1) { +- err = adc1_config_channel_atten(self->channel_id, atten); +- } else { +- #if SOC_ADC_PERIPH_NUM >= 2 +- err = adc2_config_channel_atten(self->channel_id, atten); +- #endif +- } +- if (err != ESP_OK) { ++ if (madc_config_helper(self, atten, self->block->width) != ESP_OK) { + mp_raise_ValueError(MP_ERROR_TEXT("invalid atten")); + } + madc_atten_set(self, atten); + } + ++static void madc_bits_helper(const machine_adc_obj_t *self, mp_int_t bitwidth) { ++ madcblock_bits_helper(self->block, bitwidth); ++ if (madc_config_helper(self, madc_atten_get(self), self->block->width) != ESP_OK) { ++ mp_raise_ValueError(MP_ERROR_TEXT("invalid bits")); ++ } ++} ++ + void madc_init_helper(const machine_adc_obj_t *self, size_t n_pos_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { + ARG_atten, +@@ -182,11 +203,18 @@ void madc_init_helper(const machine_adc_ + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_pos_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + ++ if (self->block->adc_handle == NULL) { ++ adc_oneshot_unit_init_cfg_t init_config = { ++ .unit_id = self->block->unit_id, ++ }; ++ check_esp_err(adc_oneshot_new_unit(&init_config, &self->block->adc_handle)); ++ } ++ + mp_int_t atten = args[ARG_atten].u_int; + if (atten != -1) { + madc_atten_helper(self, atten); + } else if (madc_atten_get(self) == ADC_ATTEN_MAX) { +- madc_atten_helper(self, ADC_ATTEN_DB_0); ++ madc_atten_helper(self, ADC_ATTEN_DB_12); + } + } + +@@ -240,5 +268,5 @@ static void mp_machine_adc_atten_set(mac + } + + static void mp_machine_adc_width_set(machine_adc_obj_t *self, mp_int_t width) { +- madcblock_bits_helper(self->block, width); ++ madc_bits_helper(self, width); + } +Index: micropython-origin/ports/esp32/machine_adc_block.c +=================================================================== +--- micropython-origin.orig/ports/esp32/machine_adc_block.c ++++ micropython-origin/ports/esp32/machine_adc_block.c +@@ -29,12 +29,11 @@ + + #include "py/mphal.h" + #include "adc.h" +-#include "driver/adc.h" + + machine_adc_block_obj_t madcblock_obj[] = { +- {{&machine_adc_block_type}, ADC_UNIT_1, SOC_ADC_RTC_MAX_BITWIDTH, -1, {0}}, ++ {{&machine_adc_block_type}, ADC_UNIT_1, SOC_ADC_RTC_MAX_BITWIDTH, ADC_BITWIDTH_DEFAULT, NULL, {0}}, + #if SOC_ADC_PERIPH_NUM > 1 +- {{&machine_adc_block_type}, ADC_UNIT_2, SOC_ADC_RTC_MAX_BITWIDTH, -1, {0}}, ++ {{&machine_adc_block_type}, ADC_UNIT_2, SOC_ADC_RTC_MAX_BITWIDTH, ADC_BITWIDTH_DEFAULT, NULL, {0}}, + #endif + }; + diff --git a/m5stack/patches/0011-micropython-1.25.0-fix-esp32-p4-pwm.patch b/m5stack/patches/0011-micropython-1.25.0-fix-esp32-p4-pwm.patch new file mode 100644 index 00000000..f2dae336 --- /dev/null +++ b/m5stack/patches/0011-micropython-1.25.0-fix-esp32-p4-pwm.patch @@ -0,0 +1,16 @@ +Index: micropython-origin/ports/esp32/machine_pwm.c +=================================================================== +--- micropython-origin.orig/ports/esp32/machine_pwm.c ++++ micropython-origin/ports/esp32/machine_pwm.c +@@ -177,7 +177,11 @@ static void pwm_deinit(int channel_idx) + // Disable ledc signal for the pin + // esp_rom_gpio_connect_out_signal(pin, SIG_GPIO_OUT_IDX, false, false); + if (mode == LEDC_LOW_SPEED_MODE) { ++#if CONFIG_IDF_TARGET_ESP32P4 ++ esp_rom_gpio_connect_out_signal(pin, LEDC_LS_SIG_OUT_PAD_OUT0_IDX + channel, false, true); ++#else + esp_rom_gpio_connect_out_signal(pin, LEDC_LS_SIG_OUT0_IDX + channel, false, true); ++#endif + } else { + #if LEDC_SPEED_MODE_MAX > 1 + #if CONFIG_IDF_TARGET_ESP32 diff --git a/m5stack/patches/0012-micropython-1.25.0-fix-esp32-p4-modesp32.patch b/m5stack/patches/0012-micropython-1.25.0-fix-esp32-p4-modesp32.patch new file mode 100644 index 00000000..33d1378e --- /dev/null +++ b/m5stack/patches/0012-micropython-1.25.0-fix-esp32-p4-modesp32.patch @@ -0,0 +1,22 @@ +Index: micropython-origin/ports/esp32/modesp32.c +=================================================================== +--- micropython-origin.orig/ports/esp32/modesp32.c ++++ micropython-origin/ports/esp32/modesp32.c +@@ -136,7 +136,7 @@ static mp_obj_t esp32_wake_on_ulp(const + } + static MP_DEFINE_CONST_FUN_OBJ_1(esp32_wake_on_ulp_obj, esp32_wake_on_ulp); + +-#if !SOC_GPIO_SUPPORT_HOLD_SINGLE_IO_IN_DSLP ++#if SOC_GPIO_SUPPORT_HOLD_IO_IN_DSLP && !SOC_GPIO_SUPPORT_HOLD_SINGLE_IO_IN_DSLP + static mp_obj_t esp32_gpio_deep_sleep_hold(const mp_obj_t enable) { + if (mp_obj_is_true(enable)) { + gpio_deep_sleep_hold_en(); +@@ -219,7 +219,7 @@ static const mp_rom_map_elem_t esp32_mod + { MP_ROM_QSTR(MP_QSTR_wake_on_ext0), MP_ROM_PTR(&esp32_wake_on_ext0_obj) }, + { MP_ROM_QSTR(MP_QSTR_wake_on_ext1), MP_ROM_PTR(&esp32_wake_on_ext1_obj) }, + { MP_ROM_QSTR(MP_QSTR_wake_on_ulp), MP_ROM_PTR(&esp32_wake_on_ulp_obj) }, +- #if !SOC_GPIO_SUPPORT_HOLD_SINGLE_IO_IN_DSLP ++ #if SOC_GPIO_SUPPORT_HOLD_IO_IN_DSLP && !SOC_GPIO_SUPPORT_HOLD_SINGLE_IO_IN_DSLP + { MP_ROM_QSTR(MP_QSTR_gpio_deep_sleep_hold), MP_ROM_PTR(&esp32_gpio_deep_sleep_hold_obj) }, + #endif + #if CONFIG_IDF_TARGET_ESP32 diff --git a/m5stack/patches/0013-micropython-1.25.0-fix-touchpad.patch b/m5stack/patches/0013-micropython-1.25.0-fix-touchpad.patch new file mode 100644 index 00000000..a17f225c --- /dev/null +++ b/m5stack/patches/0013-micropython-1.25.0-fix-touchpad.patch @@ -0,0 +1,26 @@ +Index: micropython-origin/ports/esp32/machine_touchpad.c +=================================================================== +--- micropython-origin.orig/ports/esp32/machine_touchpad.c ++++ micropython-origin/ports/esp32/machine_touchpad.c +@@ -29,7 +29,7 @@ + #include "modmachine.h" + #include "driver/gpio.h" + +-#if SOC_TOUCH_SENSOR_SUPPORTED ++#if SOC_TOUCH_SENSOR_SUPPORTED && !CONFIG_IDF_TARGET_ESP32P4 + + #if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 3, 0) + #if SOC_TOUCH_VERSION_1 +Index: micropython-origin/ports/esp32/modmachine.c +=================================================================== +--- micropython-origin.orig/ports/esp32/modmachine.c ++++ micropython-origin/ports/esp32/modmachine.c +@@ -45,7 +45,7 @@ + #define MICROPY_PY_MACHINE_SDCARD_ENTRY + #endif + +-#if SOC_TOUCH_SENSOR_SUPPORTED ++#if SOC_TOUCH_SENSOR_SUPPORTED && !CONFIG_IDF_TARGET_ESP32P4 + #define MICROPY_PY_MACHINE_TOUCH_PAD_ENTRY { MP_ROM_QSTR(MP_QSTR_TouchPad), MP_ROM_PTR(&machine_touchpad_type) }, + #else + #define MICROPY_PY_MACHINE_TOUCH_PAD_ENTRY diff --git a/m5stack/patches/0014-micropython-1.25.0-fix-timer.patch b/m5stack/patches/0014-micropython-1.25.0-fix-timer.patch new file mode 100644 index 00000000..73649e7c --- /dev/null +++ b/m5stack/patches/0014-micropython-1.25.0-fix-timer.patch @@ -0,0 +1,34 @@ +Index: micropython-origin/ports/esp32/machine_timer.c +=================================================================== +--- micropython-origin.orig/ports/esp32/machine_timer.c ++++ micropython-origin/ports/esp32/machine_timer.c +@@ -38,6 +38,7 @@ + #include "hal/timer_hal.h" + #include "hal/timer_ll.h" + #include "soc/timer_periph.h" ++#include "esp_private/periph_ctrl.h" + #include "machine_timer.h" + + #define TIMER_DIVIDER 8 +@@ -155,11 +156,20 @@ static void machine_timer_isr(void *self + } + } + ++#if SOC_PERIPH_CLK_CTRL_SHARED ++#define GPTIMER_CLOCK_SRC_ATOMIC() PERIPH_RCC_ATOMIC() ++#else ++#define GPTIMER_CLOCK_SRC_ATOMIC() ++#endif ++ + void machine_timer_enable(machine_timer_obj_t *self, void (*timer_isr)) { + // Initialise the timer. + timer_hal_init(&self->hal_context, self->group, self->index); + timer_ll_enable_counter(self->hal_context.dev, self->index, false); +- timer_ll_set_clock_source(self->hal_context.dev, self->index, GPTIMER_CLK_SRC_DEFAULT); ++ GPTIMER_CLOCK_SRC_ATOMIC() { ++ timer_ll_set_clock_source(self->hal_context.dev, self->index, GPTIMER_CLK_SRC_DEFAULT); ++ timer_ll_enable_clock(self->hal_context.dev, self->index, true); ++ } + timer_ll_set_clock_prescale(self->hal_context.dev, self->index, TIMER_DIVIDER); + timer_hal_set_counter_value(&self->hal_context, 0); + timer_ll_set_count_direction(self->hal_context.dev, self->index, GPTIMER_COUNT_UP); diff --git a/m5stack/patches/0015-micropython-1.25.0-fix-hostname.patch b/m5stack/patches/0015-micropython-1.25.0-fix-hostname.patch new file mode 100644 index 00000000..34733aba --- /dev/null +++ b/m5stack/patches/0015-micropython-1.25.0-fix-hostname.patch @@ -0,0 +1,13 @@ +Index: micropython-origin/ports/esp32/mpconfigport.h +=================================================================== +--- micropython-origin.orig/ports/esp32/mpconfigport.h ++++ micropython-origin/ports/esp32/mpconfigport.h +@@ -167,6 +167,8 @@ + #define MICROPY_PY_NETWORK_HOSTNAME_DEFAULT "mpy-esp32c3" + #elif CONFIG_IDF_TARGET_ESP32C6 + #define MICROPY_PY_NETWORK_HOSTNAME_DEFAULT "mpy-esp32c6" ++#elif CONFIG_IDF_TARGET_ESP32P4 ++#define MICROPY_PY_NETWORK_HOSTNAME_DEFAULT "mpy-esp32p4" + #endif + #endif + #define MICROPY_PY_NETWORK_INCLUDEFILE "ports/esp32/modnetwork.h" diff --git a/m5stack/patches/0016-micropython-1.25.0-fix-mpnimbleport.patch b/m5stack/patches/0016-micropython-1.25.0-fix-mpnimbleport.patch new file mode 100644 index 00000000..afc858b7 --- /dev/null +++ b/m5stack/patches/0016-micropython-1.25.0-fix-mpnimbleport.patch @@ -0,0 +1,14 @@ +Index: micropython-origin/ports/esp32/mpnimbleport.c +=================================================================== +--- micropython-origin.orig/ports/esp32/mpnimbleport.c ++++ micropython-origin/ports/esp32/mpnimbleport.c +@@ -71,7 +71,9 @@ void mp_bluetooth_nimble_port_shutdown(v + nimble_port_stop(); + + // Shuts down the event queue. ++#if !CONFIG_IDF_TARGET_ESP32P4 + nimble_port_deinit(); ++#endif + + #if MICROPY_PY_BLUETOOTH_USE_SYNC_EVENTS_WITH_INTERLOCK + MP_THREAD_GIL_ENTER(); diff --git a/m5stack/patches/0017-micropython-1.25.0-add-uart-mode.patch b/m5stack/patches/0017-micropython-1.25.0-add-uart-mode.patch new file mode 100644 index 00000000..a7078f56 --- /dev/null +++ b/m5stack/patches/0017-micropython-1.25.0-add-uart-mode.patch @@ -0,0 +1,43 @@ +Index: micropython/ports/esp32/machine_uart.c +=================================================================== +--- micropython.orig/ports/esp32/machine_uart.c ++++ micropython/ports/esp32/machine_uart.c +@@ -108,6 +108,11 @@ static const char *_parity_name[] = {"No + { MP_ROM_QSTR(MP_QSTR_IRQ_RX), MP_ROM_INT(UART_IRQ_RX) }, \ + { MP_ROM_QSTR(MP_QSTR_IRQ_RXIDLE), MP_ROM_INT(UART_IRQ_RXIDLE) }, \ + { MP_ROM_QSTR(MP_QSTR_IRQ_BREAK), MP_ROM_INT(UART_IRQ_BREAK) }, \ ++ { MP_ROM_QSTR(MP_QSTR_MODE_UART), MP_ROM_INT(UART_MODE_UART) }, \ ++ { MP_ROM_QSTR(MP_QSTR_MODE_RS485_HALF_DUPLEX), MP_ROM_INT(UART_MODE_RS485_HALF_DUPLEX) }, \ ++ { MP_ROM_QSTR(MP_QSTR_MODE_IRDA), MP_ROM_INT(UART_MODE_IRDA) }, \ ++ { MP_ROM_QSTR(MP_QSTR_MODE_RS485_COLLISION_DETECT), MP_ROM_INT(UART_MODE_RS485_COLLISION_DETECT) }, \ ++ { MP_ROM_QSTR(MP_QSTR_MODE_RS485_APP_CTRL), MP_ROM_INT(UART_MODE_RS485_APP_CTRL) }, \ + + static void uart_timer_callback(void *self_in) { + machine_timer_obj_t *self = self_in; +@@ -227,7 +232,7 @@ static void mp_machine_uart_print(const + } + + static void mp_machine_uart_init_helper(machine_uart_obj_t *self, size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { +- enum { ARG_baudrate, ARG_bits, ARG_parity, ARG_stop, ARG_tx, ARG_rx, ARG_rts, ARG_cts, ARG_txbuf, ARG_rxbuf, ARG_timeout, ARG_timeout_char, ARG_invert, ARG_flow }; ++ enum { ARG_baudrate, ARG_bits, ARG_parity, ARG_stop, ARG_tx, ARG_rx, ARG_rts, ARG_cts, ARG_txbuf, ARG_rxbuf, ARG_timeout, ARG_timeout_char, ARG_invert, ARG_flow, ARG_mode }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_baudrate, MP_ARG_INT, {.u_int = 0} }, + { MP_QSTR_bits, MP_ARG_INT, {.u_int = 0} }, +@@ -243,6 +248,7 @@ static void mp_machine_uart_init_helper( + { MP_QSTR_timeout_char, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1} }, + { MP_QSTR_invert, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1} }, + { MP_QSTR_flow, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1} }, ++ { MP_QSTR_mode, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = UART_MODE_UART} }, + }; + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); +@@ -401,6 +407,9 @@ static void mp_machine_uart_init_helper( + } + uint8_t uart_fifo_len = UART_HW_FIFO_LEN(self->uart_num); + check_esp_err(uart_set_hw_flow_ctrl(self->uart_num, self->flowcontrol, uart_fifo_len - uart_fifo_len / 4)); ++ ++ // Set mode ++ check_esp_err(uart_set_mode(self->uart_num, args[ARG_mode].u_int)); + } + + static mp_obj_t mp_machine_uart_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { diff --git a/m5stack/patches/1004-idf_v5.4_freertos.patch b/m5stack/patches/1004-idf_v5.4_freertos.patch new file mode 100644 index 00000000..b45e33f8 --- /dev/null +++ b/m5stack/patches/1004-idf_v5.4_freertos.patch @@ -0,0 +1,122 @@ +Index: esp-idf/components/fatfs/CMakeLists.txt +=================================================================== +--- esp-idf.orig/components/fatfs/CMakeLists.txt ++++ /dev/null +@@ -1,34 +0,0 @@ +-idf_build_get_property(target IDF_TARGET) +- +-set(srcs "diskio/diskio.c" +- "diskio/diskio_rawflash.c" +- "diskio/diskio_wl.c" +- "src/ff.c" +- "src/ffunicode.c") +- +-set(include_dirs "diskio" "src") +- +-set(requires "wear_levelling") +- +-# for linux, we do not have support for vfs and sdmmc, for real targets, add respective sources +-if(${target} STREQUAL "linux") +- list(APPEND srcs "port/linux/ffsystem.c") +-else() +- list(APPEND srcs "port/freertos/ffsystem.c" +- "diskio/diskio_sdmmc.c" +- "vfs/vfs_fat.c" +- "vfs/vfs_fat_sdmmc.c" +- "vfs/vfs_fat_spiflash.c") +- +- list(APPEND include_dirs "vfs") +- +- list(APPEND requires "sdmmc" "esp_driver_sdmmc" "esp_driver_sdspi") +- +- list(APPEND priv_requires "vfs" "esp_driver_gpio") +-endif() +- +-idf_component_register(SRCS ${srcs} +- INCLUDE_DIRS ${include_dirs} +- REQUIRES ${requires} +- PRIV_REQUIRES ${priv_requires} +- ) +Index: esp-idf/components/freertos/esp_additions/freertos_tasks_c_additions.h +=================================================================== +--- esp-idf.orig/components/freertos/esp_additions/freertos_tasks_c_additions.h ++++ esp-idf/components/freertos/esp_additions/freertos_tasks_c_additions.h +@@ -377,6 +377,53 @@ _Static_assert( tskNO_AFFINITY == ( Base + #endif /* ( configSUPPORT_STATIC_ALLOCATION == 1 ) */ + /*----------------------------------------------------------*/ + ++#if ( configSUPPORT_STATIC_ALLOCATION == 1 ) ++ ++ BaseType_t xTaskCreateRestrictedPinnedToCore( const TaskParameters_t * const pxTaskDefinition, TaskHandle_t *pxCreatedTask, const BaseType_t xCoreID) ++ { ++ TCB_t *pxNewTCB; ++ BaseType_t xReturn = errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY; ++ ++ configASSERT( pxTaskDefinition->puxStackBuffer ); ++ ++ if( pxTaskDefinition->puxStackBuffer != NULL ) ++ { ++ /* Allocate space for the TCB. Where the memory comes from depends ++ on the implementation of the port malloc function and whether or ++ not static allocation is being used. */ ++ pxNewTCB = ( TCB_t * ) pvPortMalloc( sizeof( TCB_t ) ); ++ ++ if( pxNewTCB != NULL ) ++ { ++ memset( pxNewTCB, 0, sizeof( TCB_t ) ); ++ /* Store the stack location in the TCB. */ ++ pxNewTCB->pxStack = pxTaskDefinition->puxStackBuffer; ++ ++ /* Tasks can be created statically or dynamically, so note ++ this task had a statically allocated stack in case it is ++ later deleted. The TCB was allocated dynamically. */ ++ pxNewTCB->ucStaticallyAllocated = tskDYNAMICALLY_ALLOCATED_STACK_AND_TCB; ++ ++ prvInitialiseNewTask( pxTaskDefinition->pvTaskCode, ++ pxTaskDefinition->pcName, ++ pxTaskDefinition->usStackDepth, ++ pxTaskDefinition->pvParameters, ++ pxTaskDefinition->uxPriority, ++ pxCreatedTask, pxNewTCB, ++ pxTaskDefinition->xRegions, ++ xCoreID ); ++ ++ prvAddNewTaskToReadyList( pxNewTCB ); ++ xReturn = pdPASS; ++ } ++ } ++ ++ return xReturn; ++ } ++ ++#endif /* ( configSUPPORT_STATIC_ALLOCATION == 1 ) */ ++/*----------------------------------------------------------*/ ++ + #if ( configUSE_TIMERS == 1 ) + + /* +Index: esp-idf/components/freertos/esp_additions/include/freertos/idf_additions.h +=================================================================== +--- esp-idf.orig/components/freertos/esp_additions/include/freertos/idf_additions.h ++++ esp-idf/components/freertos/esp_additions/include/freertos/idf_additions.h +@@ -107,6 +107,8 @@ + StaticTask_t * const pxTaskBuffer, + const BaseType_t xCoreID ); + ++ BaseType_t xTaskCreateRestrictedPinnedToCore( const TaskParameters_t * const pxTaskDefinition, TaskHandle_t *pxCreatedTask, const BaseType_t xCoreID); ++ + #endif /* configSUPPORT_STATIC_ALLOCATION */ + + /* ------------------------------------------------- Task Utilities ------------------------------------------------- */ +Index: esp-idf/components/freertos/linker_common.lf +=================================================================== +--- esp-idf.orig/components/freertos/linker_common.lf ++++ esp-idf/components/freertos/linker_common.lf +@@ -24,6 +24,7 @@ entries: + # Task Creation + tasks:xTaskCreatePinnedToCore (default) + tasks:xTaskCreateStaticPinnedToCore (default) ++ tasks:xTaskCreateRestrictedPinnedToCore (default) + # Task Utilities + tasks:xTaskGetCoreID (default) + tasks:xTaskGetIdleTaskHandleForCore (default) diff --git a/m5stack/patches/4002-M5GFX-use-i2c-driver.patch b/m5stack/patches/4002-M5GFX-use-i2c-driver.patch new file mode 100644 index 00000000..0bfbefaf --- /dev/null +++ b/m5stack/patches/4002-M5GFX-use-i2c-driver.patch @@ -0,0 +1,22 @@ +Index: m5stack/src/lgfx/v1/platforms/esp32/common.cpp +=================================================================== +--- m5stack.orig/src/lgfx/v1/platforms/esp32/common.cpp ++++ m5stack/src/lgfx/v1/platforms/esp32/common.cpp +@@ -27,7 +27,7 @@ Contributors: + #include + #include + +-#if __has_include() ++#if 0 //__has_include() + #include + #else + #include +@@ -964,7 +964,7 @@ namespace lgfx + + static void set_pin(i2c_port_t i2c_num, gpio_num_t pin_sda, gpio_num_t pin_scl) + { +-#if __has_include() ++#if 0 // __has_include() + if ((int8_t)pin_sda >= 0) { + gpio_set_level(pin_sda, true); + gpio_iomux_out(pin_sda, PIN_FUNC_GPIO, false); diff --git a/m5stack/patches/5001-Add-software-i2c-support.patch b/m5stack/patches/5001-Add-software-i2c-support.patch index d4c9f3b7..c14ed5d6 100644 --- a/m5stack/patches/5001-Add-software-i2c-support.patch +++ b/m5stack/patches/5001-Add-software-i2c-support.patch @@ -1,7 +1,7 @@ -diff --git a/Kconfig b/Kconfig -index 2632c82..2101ebe 100755 ---- a/Kconfig -+++ b/Kconfig +Index: m5stack/components/esp32-camera/Kconfig +=================================================================== +--- m5stack.orig/Kconfig ++++ m5stack/Kconfig @@ -128,6 +128,10 @@ menu "Camera configuration" endchoice @@ -9,829 +9,454 @@ index 2632c82..2101ebe 100755 + config SCCB_SOFTWARE_SUPPORT + bool "Enable software I2C for SCCB" + default n -+ ++ config SCCB_CLK_FREQ int "SCCB clk frequency" default 100000 -@@ -135,7 +139,7 @@ menu "Camera configuration" - help - Increasing this value can reduce the initialization time of the sensor. - Please refer to the relevant instructions of the sensor to adjust the value. -- -+ - choice GC_SENSOR_WINDOW_MODE - bool "GalaxyCore Sensor Window Mode" - depends on (GC2145_SUPPORT || GC032A_SUPPORT || GC0308_SUPPORT) -diff --git a/driver/sccb.c b/driver/sccb.c -index 307166d..9ad5576 100755 ---- a/driver/sccb.c -+++ b/driver/sccb.c -@@ -6,255 +6,560 @@ - * SCCB (I2C like) driver. - * - */ --#include --#include --#include --#include --#include "sccb.h" --#include "sensor.h" --#include --#include "sdkconfig.h" --#if defined(ARDUINO_ARCH_ESP32) && defined(CONFIG_ARDUHAL_ESP_LOG) --#include "esp32-hal-log.h" --#else --#include "esp_log.h" --static const char* TAG = "sccb"; --#endif +Index: m5stack/components/esp32-camera/driver/sccb.c +=================================================================== +--- m5stack.orig/driver/sccb.c ++++ m5stack/driver/sccb.c +@@ -46,8 +46,162 @@ const int SCCB_I2C_PORT_DEFAULT = 0; + static int sccb_i2c_port; + static bool sccb_owns_i2c_port; --#define LITTLETOBIG(x) ((x<<8)|(x>>8)) -+ #include -+ #include -+ #include -+ #include -+ #include "sccb.h" -+ #include "sensor.h" -+ #include -+ #include "sdkconfig.h" -+ #if defined(ARDUINO_ARCH_ESP32) && defined(CONFIG_ARDUHAL_ESP_LOG) -+ #include "esp32-hal-log.h" -+ #else -+ #include "esp_log.h" -+ static const char *TAG = "sccb"; -+ #endif -+ -+ #define LITTLETOBIG(x) ((x << 8) | (x >> 8)) -+ -+ #include "driver/i2c.h" -+ -+ // support IDF 5.x -+ #ifndef portTICK_RATE_MS -+ #define portTICK_RATE_MS portTICK_PERIOD_MS -+ #endif -+ -+ #define SCCB_FREQ CONFIG_SCCB_CLK_FREQ /*!< I2C master frequency*/ -+ #define WRITE_BIT I2C_MASTER_WRITE /*!< I2C master write */ -+ #define READ_BIT I2C_MASTER_READ /*!< I2C master read */ -+ #define ACK_CHECK_EN 0x1 /*!< I2C master will check ack from slave*/ -+ #define ACK_CHECK_DIS 0x0 /*!< I2C master will not check ack from slave */ -+ #define ACK_VAL 0x0 /*!< I2C ack value */ -+ #define NACK_VAL 0x1 /*!< I2C nack value */ -+ #if CONFIG_SCCB_HARDWARE_I2C_PORT1 -+ const int SCCB_I2C_PORT_DEFAULT = 1; -+ #else -+ const int SCCB_I2C_PORT_DEFAULT = 0; -+ #endif -+ -+ static int sccb_i2c_port; -+ static bool sccb_owns_i2c_port; -+ -+ -+ -+ #if CONFIG_SCCB_SOFTWARE_SUPPORT -+ // ========================================================================================= -+ // software sccb implement -+ #include "driver/gpio.h" -+ #include "esp_err.h" -+ #include "esp_check.h" -+ #include "esp_timer.h" -+ -+ typedef struct { -+ int pin_scl; -+ int pin_sda; -+ uint32_t time_delay_us; -+ } soft_sccb_config_t; -+ -+ static soft_sccb_config_t g_soft_sccb_config; -+ -+ -+ static esp_err_t soft_bus_init(int pin_sda, int pin_scl) -+ { -+ ESP_LOGI(TAG, "soft bus init"); -+ -+ gpio_reset_pin(g_soft_sccb_config.pin_scl); -+ gpio_set_direction(g_soft_sccb_config.pin_scl, GPIO_MODE_OUTPUT); -+ gpio_set_pull_mode(g_soft_sccb_config.pin_scl, GPIO_PULLUP_ONLY); -+ gpio_reset_pin(g_soft_sccb_config.pin_sda); ++#if CONFIG_SCCB_SOFTWARE_SUPPORT ++// ========================================================================================= ++// software sccb implement ++#include "driver/gpio.h" ++#include "esp_err.h" ++#include "esp_check.h" ++#include "esp_timer.h" ++ ++typedef struct { ++ int pin_scl; ++ int pin_sda; ++ uint32_t time_delay_us; ++} soft_sccb_config_t; ++ ++static soft_sccb_config_t g_soft_sccb_config; ++ ++static esp_err_t soft_bus_init(int pin_sda, int pin_scl) ++{ ++ ESP_LOGI(TAG, "soft bus init"); ++ ++ gpio_reset_pin(g_soft_sccb_config.pin_scl); ++ gpio_set_direction(g_soft_sccb_config.pin_scl, GPIO_MODE_OUTPUT); ++ gpio_set_pull_mode(g_soft_sccb_config.pin_scl, GPIO_PULLUP_ONLY); ++ gpio_reset_pin(g_soft_sccb_config.pin_sda); ++ gpio_set_direction(g_soft_sccb_config.pin_sda, GPIO_MODE_OUTPUT); ++ gpio_set_pull_mode(g_soft_sccb_config.pin_sda, GPIO_PULLUP_ONLY); ++ // 空闲状态,两线均为高电平 ++ gpio_set_level(g_soft_sccb_config.pin_scl, 1); ++ gpio_set_level(g_soft_sccb_config.pin_sda, 1); ++ g_soft_sccb_config.time_delay_us = (uint32_t)((1e6f / 100000) / 2.0f + 0.5f); ++ return ESP_OK; ++} ++ ++static esp_err_t soft_bus_start() ++{ + gpio_set_direction(g_soft_sccb_config.pin_sda, GPIO_MODE_OUTPUT); + gpio_set_pull_mode(g_soft_sccb_config.pin_sda, GPIO_PULLUP_ONLY); ++ // SDA 在 SCL 为高时下降,表示 START ++ ESP_RETURN_ON_ERROR(gpio_set_level(g_soft_sccb_config.pin_sda, 1), TAG, "Failed to set SDA low"); ++ ESP_RETURN_ON_ERROR(gpio_set_level(g_soft_sccb_config.pin_scl, 1), TAG, "Failed to set SCL high"); ++ esp_rom_delay_us(g_soft_sccb_config.time_delay_us); ++ ESP_RETURN_ON_ERROR(gpio_set_level(g_soft_sccb_config.pin_sda, 0), TAG, "Failed to set SDA high"); ++ ++ return ESP_OK; ++} ++ ++static esp_err_t soft_bus_stop() ++{ ++ gpio_set_direction(g_soft_sccb_config.pin_sda, GPIO_MODE_OUTPUT); ++ gpio_set_pull_mode(g_soft_sccb_config.pin_sda, GPIO_PULLUP_ONLY); ++ // SDA 在 SCL 为高时上升,表示 STOP ++ ESP_RETURN_ON_ERROR(gpio_set_level(g_soft_sccb_config.pin_sda, 0), TAG, "Failed to set SDA low"); ++ esp_rom_delay_us(2); ++ ESP_RETURN_ON_ERROR(gpio_set_level(g_soft_sccb_config.pin_scl, 1), TAG, "Failed to set SCL high"); ++ esp_rom_delay_us(g_soft_sccb_config.time_delay_us); ++ ESP_RETURN_ON_ERROR(gpio_set_level(g_soft_sccb_config.pin_sda, 1), TAG, "Failed to set SDA high"); ++ esp_rom_delay_us(g_soft_sccb_config.time_delay_us); ++ ++ return ESP_OK; ++} ++ ++static esp_err_t soft_bus_write_byte(uint8_t byte) ++{ ++ gpio_set_direction(g_soft_sccb_config.pin_sda, GPIO_MODE_OUTPUT); ++ gpio_set_pull_mode(g_soft_sccb_config.pin_sda, GPIO_PULLUP_ONLY); ++ for (int i = 0; i < 8; i++) { ++ // SDA 在 SCL 为低时设置数据,在 SCL 为高时被采样 ++ ESP_RETURN_ON_ERROR(gpio_set_level(g_soft_sccb_config.pin_scl, 0), TAG, "Failed to set SCL low"); ++ esp_rom_delay_us(2); ++ if (byte & 0x80) { ++ ESP_RETURN_ON_ERROR(gpio_set_level(g_soft_sccb_config.pin_sda, 1), TAG, "Failed to set SDA high"); ++ } else { ++ ESP_RETURN_ON_ERROR(gpio_set_level(g_soft_sccb_config.pin_sda, 0), TAG, "Failed to set SDA low"); ++ } ++ esp_rom_delay_us(g_soft_sccb_config.time_delay_us); ++ ESP_RETURN_ON_ERROR(gpio_set_level(g_soft_sccb_config.pin_scl, 1), TAG, "Failed to set SCL high"); ++ esp_rom_delay_us(g_soft_sccb_config.time_delay_us); ++ byte <<= 1; ++ } ++ ++ return ESP_OK; ++} ++ ++static esp_err_t soft_bus_read_byte(uint8_t *byte, bool ack) ++{ ++ esp_err_t ret = ESP_OK; ++ uint8_t value = 0; ++ ++ gpio_set_direction(g_soft_sccb_config.pin_sda, GPIO_MODE_INPUT); ++ gpio_set_pull_mode(g_soft_sccb_config.pin_sda, GPIO_PULLUP_ONLY); ++ ++ ESP_RETURN_ON_ERROR(gpio_set_level(g_soft_sccb_config.pin_sda, 1), TAG, "Failed to release SDA"); /*!< First release SDA */ ++ for (int i = 0; i < 8; i++) { ++ value <<= 1; ++ ESP_RETURN_ON_ERROR(gpio_set_level(g_soft_sccb_config.pin_scl, 1), TAG, "Failed to set SCL high"); ++ esp_rom_delay_us(g_soft_sccb_config.time_delay_us); ++ ++ if (gpio_get_level(g_soft_sccb_config.pin_sda)) { ++ value++; ++ } ++ ESP_RETURN_ON_ERROR(gpio_set_level(g_soft_sccb_config.pin_scl, 0), TAG, "Failed to set SCL low"); ++ esp_rom_delay_us(g_soft_sccb_config.time_delay_us); ++ } ++ ++ *byte = value; ++ ++ // 在SCL低电平期间设置ACK状态 ++ gpio_set_direction(g_soft_sccb_config.pin_sda, GPIO_MODE_OUTPUT); ++ gpio_set_pull_mode(g_soft_sccb_config.pin_sda, GPIO_PULLUP_ONLY); ++ ESP_RETURN_ON_ERROR(gpio_set_level(g_soft_sccb_config.pin_sda, ack ? 0 : 1), TAG, "SDA level fail"); ++ esp_rom_delay_us(g_soft_sccb_config.time_delay_us); ++ ++ // 生成ACK时钟脉冲 ++ ESP_RETURN_ON_ERROR(gpio_set_level(g_soft_sccb_config.pin_scl, 1), TAG, "SCL high fail"); ++ esp_rom_delay_us(g_soft_sccb_config.time_delay_us * 2); ++ ESP_RETURN_ON_ERROR(gpio_set_level(g_soft_sccb_config.pin_scl, 0), TAG, "SCL low fail"); ++ esp_rom_delay_us(g_soft_sccb_config.time_delay_us); ++ ++ return ret; ++} ++ ++static esp_err_t soft_bus_wait_ack() ++{ ++ // 第 9 个时钟周期,接收方拉低 SDA 表示 ACK ++ ESP_RETURN_ON_ERROR(gpio_set_level(g_soft_sccb_config.pin_scl, 0), TAG, "Failed to set SCL high"); ++ gpio_set_direction(g_soft_sccb_config.pin_sda, GPIO_MODE_INPUT); ++ gpio_set_pull_mode(g_soft_sccb_config.pin_sda, GPIO_PULLUP_ONLY); ++ esp_rom_delay_us(g_soft_sccb_config.time_delay_us); ++ ++ ESP_RETURN_ON_ERROR(gpio_set_level(g_soft_sccb_config.pin_scl, 1), TAG, "Failed to set SCL high"); ++ esp_rom_delay_us(g_soft_sccb_config.time_delay_us); ++ bool ack = ESP_ERR_NOT_FOUND; ++ if (!gpio_get_level(g_soft_sccb_config.pin_sda)) { ++ ack = ESP_OK; ++ } ++ ++ ESP_RETURN_ON_ERROR(gpio_set_level(g_soft_sccb_config.pin_scl, 0), TAG, "Failed to set SCL high"); ++ esp_rom_delay_us(g_soft_sccb_config.time_delay_us); ++ ++ return ack; ++} ++ ++#endif ++// ========================================================================================= ++ ++ + int SCCB_Init(int pin_sda, int pin_scl) + { ++#if CONFIG_SCCB_SOFTWARE_SUPPORT ++ ESP_LOGI(TAG, "Use software sccb, pin_sda %d pin_scl %d", pin_sda, pin_scl); ++ g_soft_sccb_config.pin_scl = pin_scl; ++ g_soft_sccb_config.pin_sda = pin_sda; ++ g_soft_sccb_config.time_delay_us = (uint32_t)((1e6f / 100000) / 2.0f + 0.5f); // 100kHz ++ soft_bus_init(g_soft_sccb_config.pin_sda, g_soft_sccb_config.pin_scl); ++ return ESP_OK; ++#else + ESP_LOGI(TAG, "pin_sda %d pin_scl %d", pin_sda, pin_scl); + i2c_config_t conf; + esp_err_t ret; +@@ -70,9 +224,13 @@ int SCCB_Init(int pin_sda, int pin_scl) + } --#include "driver/i2c.h" -+ // 空闲状态,两线均为高电平 -+ gpio_set_level(g_soft_sccb_config.pin_scl, 1); -+ gpio_set_level(g_soft_sccb_config.pin_sda, 1); - --// support IDF 5.x --#ifndef portTICK_RATE_MS --#define portTICK_RATE_MS portTICK_PERIOD_MS --#endif -+ g_soft_sccb_config.time_delay_us = (uint32_t)((1e6f / 100000) / 2.0f + 0.5f); + return i2c_driver_install(sccb_i2c_port, conf.mode, 0, 0, 0); ++#endif + } --#define SCCB_FREQ CONFIG_SCCB_CLK_FREQ /*!< I2C master frequency*/ --#define WRITE_BIT I2C_MASTER_WRITE /*!< I2C master write */ --#define READ_BIT I2C_MASTER_READ /*!< I2C master read */ --#define ACK_CHECK_EN 0x1 /*!< I2C master will check ack from slave*/ --#define ACK_CHECK_DIS 0x0 /*!< I2C master will not check ack from slave */ --#define ACK_VAL 0x0 /*!< I2C ack value */ --#define NACK_VAL 0x1 /*!< I2C nack value */ --#if CONFIG_SCCB_HARDWARE_I2C_PORT1 --const int SCCB_I2C_PORT_DEFAULT = 1; --#else --const int SCCB_I2C_PORT_DEFAULT = 0; --#endif -- --static int sccb_i2c_port; --static bool sccb_owns_i2c_port; -- --int SCCB_Init(int pin_sda, int pin_scl) --{ -- ESP_LOGI(TAG, "pin_sda %d pin_scl %d", pin_sda, pin_scl); -- i2c_config_t conf; -- esp_err_t ret; -- -- memset(&conf, 0, sizeof(i2c_config_t)); -- -- sccb_i2c_port = SCCB_I2C_PORT_DEFAULT; -- sccb_owns_i2c_port = true; -- ESP_LOGI(TAG, "sccb_i2c_port=%d", sccb_i2c_port); -- -- conf.mode = I2C_MODE_MASTER; -- conf.sda_io_num = pin_sda; -- conf.sda_pullup_en = GPIO_PULLUP_ENABLE; -- conf.scl_io_num = pin_scl; -- conf.scl_pullup_en = GPIO_PULLUP_ENABLE; -- conf.master.clk_speed = SCCB_FREQ; -- -- if ((ret = i2c_param_config(sccb_i2c_port, &conf)) != ESP_OK) { -- return ret; -- } -- -- return i2c_driver_install(sccb_i2c_port, conf.mode, 0, 0, 0); --} -- --int SCCB_Use_Port(int i2c_num) { // sccb use an already initialized I2C port -- if (sccb_owns_i2c_port) { -- SCCB_Deinit(); -- } -- if (i2c_num < 0 || i2c_num > I2C_NUM_MAX) { -- return ESP_ERR_INVALID_ARG; -- } -- sccb_i2c_port = i2c_num; + int SCCB_Use_Port(int i2c_num) { // sccb use an already initialized I2C port ++#if CONFIG_SCCB_SOFTWARE_SUPPORT ++ return ESP_OK; ++#else + if (sccb_owns_i2c_port) { + SCCB_Deinit(); + } +@@ -81,21 +239,44 @@ int SCCB_Use_Port(int i2c_num) { // sccb + } + sccb_i2c_port = i2c_num; return ESP_OK; --} -- --int SCCB_Deinit(void) --{ -- if (!sccb_owns_i2c_port) { -- return ESP_OK; -- } -- sccb_owns_i2c_port = false; -- return i2c_driver_delete(sccb_i2c_port); --} -- --uint8_t SCCB_Probe(void) --{ -- uint8_t slave_addr = 0x0; -- -- for (size_t i = 0; i < CAMERA_MODEL_MAX; i++) { -- if (slave_addr == camera_sensor[i].sccb_addr) { -- continue; -- } -- slave_addr = camera_sensor[i].sccb_addr; -- i2c_cmd_handle_t cmd = i2c_cmd_link_create(); -- i2c_master_start(cmd); -- i2c_master_write_byte(cmd, ( slave_addr << 1 ) | WRITE_BIT, ACK_CHECK_EN); -- i2c_master_stop(cmd); -- esp_err_t ret = i2c_master_cmd_begin(sccb_i2c_port, cmd, 1000 / portTICK_RATE_MS); -- i2c_cmd_link_delete(cmd); -- if( ret == ESP_OK) { -- return slave_addr; -- } -- } -- return 0; --} -- --uint8_t SCCB_Read(uint8_t slv_addr, uint8_t reg) --{ -- uint8_t data=0; -- esp_err_t ret = ESP_FAIL; -- i2c_cmd_handle_t cmd = i2c_cmd_link_create(); -- i2c_master_start(cmd); -- i2c_master_write_byte(cmd, ( slv_addr << 1 ) | WRITE_BIT, ACK_CHECK_EN); -- i2c_master_write_byte(cmd, reg, ACK_CHECK_EN); -- i2c_master_stop(cmd); -- ret = i2c_master_cmd_begin(sccb_i2c_port, cmd, 1000 / portTICK_RATE_MS); -- i2c_cmd_link_delete(cmd); -- if(ret != ESP_OK) return -1; -- cmd = i2c_cmd_link_create(); -- i2c_master_start(cmd); -- i2c_master_write_byte(cmd, ( slv_addr << 1 ) | READ_BIT, ACK_CHECK_EN); -- i2c_master_read_byte(cmd, &data, NACK_VAL); -- i2c_master_stop(cmd); -- ret = i2c_master_cmd_begin(sccb_i2c_port, cmd, 1000 / portTICK_RATE_MS); -- i2c_cmd_link_delete(cmd); -- if(ret != ESP_OK) { -- ESP_LOGE(TAG, "SCCB_Read Failed addr:0x%02x, reg:0x%02x, data:0x%02x, ret:%d", slv_addr, reg, data, ret); -- } -- return data; --} -- --int SCCB_Write(uint8_t slv_addr, uint8_t reg, uint8_t data) --{ -- esp_err_t ret = ESP_FAIL; -- i2c_cmd_handle_t cmd = i2c_cmd_link_create(); -- i2c_master_start(cmd); -- i2c_master_write_byte(cmd, ( slv_addr << 1 ) | WRITE_BIT, ACK_CHECK_EN); -- i2c_master_write_byte(cmd, reg, ACK_CHECK_EN); -- i2c_master_write_byte(cmd, data, ACK_CHECK_EN); -- i2c_master_stop(cmd); -- ret = i2c_master_cmd_begin(sccb_i2c_port, cmd, 1000 / portTICK_RATE_MS); -- i2c_cmd_link_delete(cmd); -- if(ret != ESP_OK) { -- ESP_LOGE(TAG, "SCCB_Write Failed addr:0x%02x, reg:0x%02x, data:0x%02x, ret:%d", slv_addr, reg, data, ret); -- } -- return ret == ESP_OK ? 0 : -1; --} -- --uint8_t SCCB_Read16(uint8_t slv_addr, uint16_t reg) --{ -- uint8_t data=0; -- esp_err_t ret = ESP_FAIL; -- uint16_t reg_htons = LITTLETOBIG(reg); -- uint8_t *reg_u8 = (uint8_t *)®_htons; -- i2c_cmd_handle_t cmd = i2c_cmd_link_create(); -- i2c_master_start(cmd); -- i2c_master_write_byte(cmd, ( slv_addr << 1 ) | WRITE_BIT, ACK_CHECK_EN); -- i2c_master_write_byte(cmd, reg_u8[0], ACK_CHECK_EN); -- i2c_master_write_byte(cmd, reg_u8[1], ACK_CHECK_EN); -- i2c_master_stop(cmd); -- ret = i2c_master_cmd_begin(sccb_i2c_port, cmd, 1000 / portTICK_RATE_MS); -- i2c_cmd_link_delete(cmd); -- if(ret != ESP_OK) return -1; -- cmd = i2c_cmd_link_create(); -- i2c_master_start(cmd); -- i2c_master_write_byte(cmd, ( slv_addr << 1 ) | READ_BIT, ACK_CHECK_EN); -- i2c_master_read_byte(cmd, &data, NACK_VAL); -- i2c_master_stop(cmd); -- ret = i2c_master_cmd_begin(sccb_i2c_port, cmd, 1000 / portTICK_RATE_MS); -- i2c_cmd_link_delete(cmd); -- if(ret != ESP_OK) { -- ESP_LOGE(TAG, "W [%04x]=%02x fail\n", reg, data); -- } -- return data; --} -- --int SCCB_Write16(uint8_t slv_addr, uint16_t reg, uint8_t data) --{ -- static uint16_t i = 0; -- esp_err_t ret = ESP_FAIL; -- uint16_t reg_htons = LITTLETOBIG(reg); -- uint8_t *reg_u8 = (uint8_t *)®_htons; -- i2c_cmd_handle_t cmd = i2c_cmd_link_create(); -- i2c_master_start(cmd); -- i2c_master_write_byte(cmd, ( slv_addr << 1 ) | WRITE_BIT, ACK_CHECK_EN); -- i2c_master_write_byte(cmd, reg_u8[0], ACK_CHECK_EN); -- i2c_master_write_byte(cmd, reg_u8[1], ACK_CHECK_EN); -- i2c_master_write_byte(cmd, data, ACK_CHECK_EN); -- i2c_master_stop(cmd); -- ret = i2c_master_cmd_begin(sccb_i2c_port, cmd, 1000 / portTICK_RATE_MS); -- i2c_cmd_link_delete(cmd); -- if(ret != ESP_OK) { -- ESP_LOGE(TAG, "W [%04x]=%02x %d fail\n", reg, data, i++); -- } -- return ret == ESP_OK ? 0 : -1; --} -- --uint16_t SCCB_Read_Addr16_Val16(uint8_t slv_addr, uint16_t reg) --{ -- uint16_t data = 0; -- uint8_t *data_u8 = (uint8_t *)&data; -- esp_err_t ret = ESP_FAIL; -- uint16_t reg_htons = LITTLETOBIG(reg); -- uint8_t *reg_u8 = (uint8_t *)®_htons; -- i2c_cmd_handle_t cmd = i2c_cmd_link_create(); -- i2c_master_start(cmd); -- i2c_master_write_byte(cmd, ( slv_addr << 1 ) | WRITE_BIT, ACK_CHECK_EN); -- i2c_master_write_byte(cmd, reg_u8[0], ACK_CHECK_EN); -- i2c_master_write_byte(cmd, reg_u8[1], ACK_CHECK_EN); -- i2c_master_stop(cmd); -- ret = i2c_master_cmd_begin(sccb_i2c_port, cmd, 1000 / portTICK_RATE_MS); -- i2c_cmd_link_delete(cmd); -- if(ret != ESP_OK) return -1; -- -- cmd = i2c_cmd_link_create(); -- i2c_master_start(cmd); -- i2c_master_write_byte(cmd, ( slv_addr << 1 ) | READ_BIT, ACK_CHECK_EN); -- i2c_master_read_byte(cmd, &data_u8[1], ACK_VAL); -- i2c_master_read_byte(cmd, &data_u8[0], NACK_VAL); -- i2c_master_stop(cmd); -- ret = i2c_master_cmd_begin(sccb_i2c_port, cmd, 1000 / portTICK_RATE_MS); -- i2c_cmd_link_delete(cmd); -- if(ret != ESP_OK) { -- ESP_LOGE(TAG, "W [%04x]=%04x fail\n", reg, data); -- } -- return data; --} -- --int SCCB_Write_Addr16_Val16(uint8_t slv_addr, uint16_t reg, uint16_t data) --{ -- esp_err_t ret = ESP_FAIL; -- uint16_t reg_htons = LITTLETOBIG(reg); -- uint8_t *reg_u8 = (uint8_t *)®_htons; -- uint16_t data_htons = LITTLETOBIG(data); -- uint8_t *data_u8 = (uint8_t *)&data_htons; -- i2c_cmd_handle_t cmd = i2c_cmd_link_create(); -- i2c_master_start(cmd); -- i2c_master_write_byte(cmd, ( slv_addr << 1 ) | WRITE_BIT, ACK_CHECK_EN); -- i2c_master_write_byte(cmd, reg_u8[0], ACK_CHECK_EN); -- i2c_master_write_byte(cmd, reg_u8[1], ACK_CHECK_EN); -- i2c_master_write_byte(cmd, data_u8[0], ACK_CHECK_EN); -- i2c_master_write_byte(cmd, data_u8[1], ACK_CHECK_EN); -- i2c_master_stop(cmd); -- ret = i2c_master_cmd_begin(sccb_i2c_port, cmd, 1000 / portTICK_RATE_MS); -- i2c_cmd_link_delete(cmd); -- if(ret != ESP_OK) { -- ESP_LOGE(TAG, "W [%04x]=%04x fail\n", reg, data); -- } -- return ret == ESP_OK ? 0 : -1; --} -+ } -+ -+ static esp_err_t soft_bus_start() -+ { -+ gpio_set_direction(g_soft_sccb_config.pin_sda, GPIO_MODE_OUTPUT); -+ gpio_set_pull_mode(g_soft_sccb_config.pin_sda, GPIO_PULLUP_ONLY); -+ // SDA 在 SCL 为高时下降,表示 START -+ ESP_RETURN_ON_ERROR(gpio_set_level(g_soft_sccb_config.pin_sda, 1), TAG, "Failed to set SDA low"); -+ ESP_RETURN_ON_ERROR(gpio_set_level(g_soft_sccb_config.pin_scl, 1), TAG, "Failed to set SCL high"); -+ esp_rom_delay_us(g_soft_sccb_config.time_delay_us); -+ ESP_RETURN_ON_ERROR(gpio_set_level(g_soft_sccb_config.pin_sda, 0), TAG, "Failed to set SDA high"); -+ -+ return ESP_OK; -+ } -+ -+ static esp_err_t soft_bus_stop() -+ { -+ gpio_set_direction(g_soft_sccb_config.pin_sda, GPIO_MODE_OUTPUT); -+ gpio_set_pull_mode(g_soft_sccb_config.pin_sda, GPIO_PULLUP_ONLY); -+ // SDA 在 SCL 为高时上升,表示 STOP -+ ESP_RETURN_ON_ERROR(gpio_set_level(g_soft_sccb_config.pin_sda, 0), TAG, "Failed to set SDA low"); -+ esp_rom_delay_us(2); -+ ESP_RETURN_ON_ERROR(gpio_set_level(g_soft_sccb_config.pin_scl, 1), TAG, "Failed to set SCL high"); -+ esp_rom_delay_us(g_soft_sccb_config.time_delay_us); -+ ESP_RETURN_ON_ERROR(gpio_set_level(g_soft_sccb_config.pin_sda, 1), TAG, "Failed to set SDA high"); -+ esp_rom_delay_us(g_soft_sccb_config.time_delay_us); -+ -+ return ESP_OK; -+ } -+ -+ static esp_err_t soft_bus_write_byte(uint8_t byte) -+ { -+ gpio_set_direction(g_soft_sccb_config.pin_sda, GPIO_MODE_OUTPUT); -+ gpio_set_pull_mode(g_soft_sccb_config.pin_sda, GPIO_PULLUP_ONLY); -+ for (int i = 0; i < 8; i++) { -+ // SDA 在 SCL 为低时设置数据,在 SCL 为高时被采样 -+ ESP_RETURN_ON_ERROR(gpio_set_level(g_soft_sccb_config.pin_scl, 0), TAG, "Failed to set SCL low"); -+ esp_rom_delay_us(2); -+ if (byte & 0x80) { -+ ESP_RETURN_ON_ERROR(gpio_set_level(g_soft_sccb_config.pin_sda, 1), TAG, "Failed to set SDA high"); -+ } else { -+ ESP_RETURN_ON_ERROR(gpio_set_level(g_soft_sccb_config.pin_sda, 0), TAG, "Failed to set SDA low"); -+ } -+ esp_rom_delay_us(g_soft_sccb_config.time_delay_us); -+ ESP_RETURN_ON_ERROR(gpio_set_level(g_soft_sccb_config.pin_scl, 1), TAG, "Failed to set SCL high"); -+ esp_rom_delay_us(g_soft_sccb_config.time_delay_us); -+ byte <<= 1; -+ } -+ -+ return ESP_OK; -+ } -+ -+ static esp_err_t soft_bus_read_byte(uint8_t *byte, bool ack) -+ { -+ esp_err_t ret = ESP_OK; -+ uint8_t value = 0; -+ -+ gpio_set_direction(g_soft_sccb_config.pin_sda, GPIO_MODE_INPUT); -+ gpio_set_pull_mode(g_soft_sccb_config.pin_sda, GPIO_PULLUP_ONLY); -+ -+ ESP_RETURN_ON_ERROR(gpio_set_level(g_soft_sccb_config.pin_sda, 1), TAG, "Failed to release SDA"); /*!< First release SDA */ -+ for (int i = 0; i < 8; i++) { -+ value <<= 1; -+ ESP_RETURN_ON_ERROR(gpio_set_level(g_soft_sccb_config.pin_scl, 1), TAG, "Failed to set SCL high"); -+ esp_rom_delay_us(g_soft_sccb_config.time_delay_us); -+ -+ if (gpio_get_level(g_soft_sccb_config.pin_sda)) { -+ value++; -+ } -+ ESP_RETURN_ON_ERROR(gpio_set_level(g_soft_sccb_config.pin_scl, 0), TAG, "Failed to set SCL low"); -+ esp_rom_delay_us(g_soft_sccb_config.time_delay_us); -+ } -+ -+ *byte = value; -+ -+ // 在SCL低电平期间设置ACK状态 -+ gpio_set_direction(g_soft_sccb_config.pin_sda, GPIO_MODE_OUTPUT); -+ gpio_set_pull_mode(g_soft_sccb_config.pin_sda, GPIO_PULLUP_ONLY); -+ ESP_RETURN_ON_ERROR(gpio_set_level(g_soft_sccb_config.pin_sda, ack ? 0 : 1), TAG, "SDA level fail"); -+ esp_rom_delay_us(g_soft_sccb_config.time_delay_us); -+ -+ // 生成ACK时钟脉冲 -+ ESP_RETURN_ON_ERROR(gpio_set_level(g_soft_sccb_config.pin_scl, 1), TAG, "SCL high fail"); -+ esp_rom_delay_us(g_soft_sccb_config.time_delay_us * 2); -+ ESP_RETURN_ON_ERROR(gpio_set_level(g_soft_sccb_config.pin_scl, 0), TAG, "SCL low fail"); -+ esp_rom_delay_us(g_soft_sccb_config.time_delay_us); -+ -+ return ret; -+ } -+ -+ static esp_err_t soft_bus_wait_ack() -+ { -+ // 第 9 个时钟周期,接收方拉低 SDA 表示 ACK -+ ESP_RETURN_ON_ERROR(gpio_set_level(g_soft_sccb_config.pin_scl, 0), TAG, "Failed to set SCL high"); -+ gpio_set_direction(g_soft_sccb_config.pin_sda, GPIO_MODE_INPUT); -+ gpio_set_pull_mode(g_soft_sccb_config.pin_sda, GPIO_PULLUP_ONLY); -+ esp_rom_delay_us(g_soft_sccb_config.time_delay_us); -+ -+ ESP_RETURN_ON_ERROR(gpio_set_level(g_soft_sccb_config.pin_scl, 1), TAG, "Failed to set SCL high"); -+ esp_rom_delay_us(g_soft_sccb_config.time_delay_us); -+ bool ack = ESP_ERR_NOT_FOUND; -+ if (!gpio_get_level(g_soft_sccb_config.pin_sda)) { -+ ack = ESP_OK; -+ } -+ -+ ESP_RETURN_ON_ERROR(gpio_set_level(g_soft_sccb_config.pin_scl, 0), TAG, "Failed to set SCL high"); -+ esp_rom_delay_us(g_soft_sccb_config.time_delay_us); -+ -+ return ack; -+ } -+ -+ #endif -+ // ========================================================================================= -+ -+ -+ int SCCB_Init(int pin_sda, int pin_scl) -+ { -+ #if CONFIG_SCCB_SOFTWARE_SUPPORT -+ ESP_LOGI(TAG, "Use software sccb, pin_sda %d pin_scl %d", pin_sda, pin_scl); -+ g_soft_sccb_config.pin_scl = pin_scl; -+ g_soft_sccb_config.pin_sda = pin_sda; -+ g_soft_sccb_config.time_delay_us = (uint32_t)((1e6f / 100000) / 2.0f + 0.5f); // 100kHz -+ soft_bus_init(g_soft_sccb_config.pin_sda, g_soft_sccb_config.pin_scl); -+ return ESP_OK; -+ #else -+ ESP_LOGI(TAG, "pin_sda %d pin_scl %d", pin_sda, pin_scl); -+ i2c_config_t conf; -+ esp_err_t ret; -+ -+ memset(&conf, 0, sizeof(i2c_config_t)); -+ -+ sccb_i2c_port = SCCB_I2C_PORT_DEFAULT; -+ sccb_owns_i2c_port = true; -+ ESP_LOGI(TAG, "sccb_i2c_port=%d", sccb_i2c_port); -+ -+ conf.mode = I2C_MODE_MASTER; -+ conf.sda_io_num = pin_sda; -+ conf.sda_pullup_en = GPIO_PULLUP_ENABLE; -+ conf.scl_io_num = pin_scl; -+ conf.scl_pullup_en = GPIO_PULLUP_ENABLE; -+ conf.master.clk_speed = SCCB_FREQ; -+ -+ if ((ret = i2c_param_config(sccb_i2c_port, &conf)) != ESP_OK) { -+ return ret; -+ } -+ -+ return i2c_driver_install(sccb_i2c_port, conf.mode, 0, 0, 0); -+ #endif -+ } -+ -+ int SCCB_Use_Port(int i2c_num) -+ { -+ #if CONFIG_SCCB_SOFTWARE_SUPPORT -+ return ESP_OK; -+ #else -+ // sccb use an already initialized I2C port -+ if (sccb_owns_i2c_port) { -+ SCCB_Deinit(); -+ } -+ if (i2c_num < 0 || i2c_num > I2C_NUM_MAX) { -+ return ESP_ERR_INVALID_ARG; -+ } -+ sccb_i2c_port = i2c_num; -+ -+ return ESP_OK; -+ #endif -+ } -+ -+ int SCCB_Deinit(void) -+ { -+ #if CONFIG_SCCB_SOFTWARE_SUPPORT -+ gpio_reset_pin(g_soft_sccb_config.pin_scl); -+ gpio_reset_pin(g_soft_sccb_config.pin_sda); -+ return ESP_OK; -+ #else -+ if (!sccb_owns_i2c_port) { -+ return ESP_OK; -+ } -+ sccb_owns_i2c_port = false; -+ return i2c_driver_delete(sccb_i2c_port); -+ #endif -+ } -+ -+ uint8_t SCCB_Probe(void) -+ { -+ uint8_t slave_addr = 0x0; -+ -+ #if CONFIG_SCCB_SOFTWARE_SUPPORT -+ for (size_t i = 0; i < CAMERA_MODEL_MAX; i++) { -+ if (slave_addr == camera_sensor[i].sccb_addr) { -+ continue; -+ } -+ slave_addr = camera_sensor[i].sccb_addr; -+ soft_bus_start(); -+ soft_bus_write_byte((slave_addr << 1) | WRITE_BIT); -+ esp_err_t ret = soft_bus_wait_ack(); -+ soft_bus_stop(); -+ if (ret == ESP_OK) { -+ return slave_addr; -+ } -+ esp_rom_delay_us(g_soft_sccb_config.time_delay_us); -+ } -+ #else -+ for (size_t i = 0; i < CAMERA_MODEL_MAX; i++) { -+ if (slave_addr == camera_sensor[i].sccb_addr) { -+ continue; -+ } -+ slave_addr = camera_sensor[i].sccb_addr; -+ i2c_cmd_handle_t cmd = i2c_cmd_link_create(); -+ i2c_master_start(cmd); -+ i2c_master_write_byte(cmd, ( slave_addr << 1 ) | WRITE_BIT, ACK_CHECK_EN); -+ i2c_master_stop(cmd); -+ esp_err_t ret = i2c_master_cmd_begin(sccb_i2c_port, cmd, 1000 / portTICK_RATE_MS); -+ i2c_cmd_link_delete(cmd); -+ if( ret == ESP_OK) { -+ return slave_addr; -+ } -+ } -+ #endif -+ -+ return 0; -+ } -+ -+ -+ uint8_t SCCB_Read(uint8_t slv_addr, uint8_t reg) -+ { -+ uint8_t data = 0; -+ esp_err_t ret = ESP_FAIL; -+ -+ #if CONFIG_SCCB_SOFTWARE_SUPPORT -+ soft_bus_start(); -+ soft_bus_write_byte((slv_addr << 1) | WRITE_BIT); -+ ret = soft_bus_wait_ack(); -+ soft_bus_write_byte(reg); -+ ret = soft_bus_wait_ack(); -+ // soft_bus_stop(); -+ if (ret != ESP_OK) return -1; -+ soft_bus_start(); -+ soft_bus_write_byte((slv_addr << 1) | READ_BIT); -+ ret = soft_bus_wait_ack(); -+ soft_bus_read_byte(&data, false); -+ soft_bus_stop(); -+ #else -+ i2c_cmd_handle_t cmd = i2c_cmd_link_create(); -+ i2c_master_start(cmd); -+ i2c_master_write_byte(cmd, ( slv_addr << 1 ) | WRITE_BIT, ACK_CHECK_EN); -+ i2c_master_write_byte(cmd, reg, ACK_CHECK_EN); -+ i2c_master_stop(cmd); -+ ret = i2c_master_cmd_begin(sccb_i2c_port, cmd, 1000 / portTICK_RATE_MS); -+ i2c_cmd_link_delete(cmd); -+ if(ret != ESP_OK) return -1; -+ cmd = i2c_cmd_link_create(); -+ i2c_master_start(cmd); -+ i2c_master_write_byte(cmd, ( slv_addr << 1 ) | READ_BIT, ACK_CHECK_EN); -+ i2c_master_read_byte(cmd, &data, NACK_VAL); -+ i2c_master_stop(cmd); -+ ret = i2c_master_cmd_begin(sccb_i2c_port, cmd, 1000 / portTICK_RATE_MS); -+ i2c_cmd_link_delete(cmd); -+ #endif -+ -+ if (ret != ESP_OK) { -+ ESP_LOGE(TAG, "SCCB_Read Failed addr:0x%02x, reg:0x%02x, data:0x%02x, ret:%d", slv_addr, reg, data, ret); -+ } -+ -+ return data; -+ } -+ -+ int SCCB_Write(uint8_t slv_addr, uint8_t reg, uint8_t data) -+ { -+ esp_err_t ret = ESP_FAIL; -+ -+ #if CONFIG_SCCB_SOFTWARE_SUPPORT -+ soft_bus_start(); -+ soft_bus_write_byte((slv_addr << 1) | WRITE_BIT); -+ ret = soft_bus_wait_ack(); -+ soft_bus_write_byte(reg); -+ ret = soft_bus_wait_ack(); -+ soft_bus_write_byte(data); -+ ret = soft_bus_wait_ack(); -+ soft_bus_stop(); -+ esp_rom_delay_us(g_soft_sccb_config.time_delay_us); -+ esp_rom_delay_us(g_soft_sccb_config.time_delay_us); -+ #else -+ i2c_cmd_handle_t cmd = i2c_cmd_link_create(); -+ i2c_master_start(cmd); -+ i2c_master_write_byte(cmd, ( slv_addr << 1 ) | WRITE_BIT, ACK_CHECK_EN); -+ i2c_master_write_byte(cmd, reg, ACK_CHECK_EN); -+ i2c_master_write_byte(cmd, data, ACK_CHECK_EN); -+ i2c_master_stop(cmd); -+ ret = i2c_master_cmd_begin(sccb_i2c_port, cmd, 1000 / portTICK_RATE_MS); -+ i2c_cmd_link_delete(cmd); -+ #endif -+ -+ if (ret != ESP_OK) { -+ ESP_LOGE(TAG, "SCCB_Write Failed addr:0x%02x, reg:0x%02x, data:0x%02x, ret:%d", slv_addr, reg, data, ret); -+ } -+ -+ return ret == ESP_OK ? 0 : -1; -+ } -+ -+ uint8_t SCCB_Read16(uint8_t slv_addr, uint16_t reg) -+ { -+ uint8_t data = 0; -+ esp_err_t ret = ESP_FAIL; -+ uint16_t reg_htons = LITTLETOBIG(reg); -+ uint8_t *reg_u8 = (uint8_t *)®_htons; -+ -+ #if CONFIG_SCCB_SOFTWARE_SUPPORT -+ soft_bus_start(); -+ soft_bus_write_byte((slv_addr << 1) | WRITE_BIT); -+ ret = soft_bus_wait_ack(); -+ soft_bus_write_byte(reg_u8[0]); -+ ret = soft_bus_wait_ack(); -+ soft_bus_write_byte(reg_u8[1]); -+ ret = soft_bus_wait_ack(); -+ soft_bus_stop(); -+ if (ret != ESP_OK) return -1; -+ soft_bus_start(); -+ soft_bus_write_byte((slv_addr << 1) | READ_BIT); -+ ret = soft_bus_wait_ack(); -+ soft_bus_read_byte(&data, true); -+ soft_bus_stop(); -+ #else -+ i2c_cmd_handle_t cmd = i2c_cmd_link_create(); -+ i2c_master_start(cmd); -+ i2c_master_write_byte(cmd, ( slv_addr << 1 ) | WRITE_BIT, ACK_CHECK_EN); -+ i2c_master_write_byte(cmd, reg_u8[0], ACK_CHECK_EN); -+ i2c_master_write_byte(cmd, reg_u8[1], ACK_CHECK_EN); -+ i2c_master_stop(cmd); -+ ret = i2c_master_cmd_begin(sccb_i2c_port, cmd, 1000 / portTICK_RATE_MS); -+ i2c_cmd_link_delete(cmd); -+ if(ret != ESP_OK) return -1; -+ cmd = i2c_cmd_link_create(); -+ i2c_master_start(cmd); -+ i2c_master_write_byte(cmd, ( slv_addr << 1 ) | READ_BIT, ACK_CHECK_EN); -+ i2c_master_read_byte(cmd, &data, NACK_VAL); -+ i2c_master_stop(cmd); -+ ret = i2c_master_cmd_begin(sccb_i2c_port, cmd, 1000 / portTICK_RATE_MS); -+ i2c_cmd_link_delete(cmd); -+ #endif -+ -+ if (ret != ESP_OK) { -+ ESP_LOGE(TAG, "W [%04x]=%02x fail\n", reg, data); -+ } -+ -+ return data; -+ } -+ -+ int SCCB_Write16(uint8_t slv_addr, uint16_t reg, uint8_t data) -+ { -+ static uint16_t i = 0; -+ esp_err_t ret = ESP_FAIL; -+ uint16_t reg_htons = LITTLETOBIG(reg); -+ uint8_t *reg_u8 = (uint8_t *)®_htons; -+ -+ #if CONFIG_SCCB_SOFTWARE_SUPPORT -+ soft_bus_start(); -+ soft_bus_write_byte((slv_addr << 1) | WRITE_BIT); -+ ret = soft_bus_wait_ack(); -+ soft_bus_write_byte(reg_u8[0]); -+ ret = soft_bus_wait_ack(); -+ soft_bus_write_byte(reg_u8[1]); -+ ret = soft_bus_wait_ack(); -+ soft_bus_write_byte(data); -+ ret = soft_bus_wait_ack(); -+ soft_bus_stop(); -+ #else -+ i2c_cmd_handle_t cmd = i2c_cmd_link_create(); -+ i2c_master_start(cmd); -+ i2c_master_write_byte(cmd, (slv_addr << 1) | WRITE_BIT, ACK_CHECK_EN); -+ i2c_master_write_byte(cmd, reg_u8[0], ACK_CHECK_EN); -+ i2c_master_write_byte(cmd, reg_u8[1], ACK_CHECK_EN); -+ i2c_master_write_byte(cmd, data, ACK_CHECK_EN); -+ i2c_master_stop(cmd); -+ ret = i2c_master_cmd_begin(sccb_i2c_port, cmd, 1000 / portTICK_RATE_MS); -+ i2c_cmd_link_delete(cmd); -+ #endif -+ -+ if (ret != ESP_OK) { -+ ESP_LOGE(TAG, "W [%04x]=%02x %d fail\n", reg, data, i++); -+ } -+ -+ return ret == ESP_OK ? 0 : -1; -+ } -+ -+ uint16_t SCCB_Read_Addr16_Val16(uint8_t slv_addr, uint16_t reg) -+ { -+ uint16_t data = 0; -+ uint8_t *data_u8 = (uint8_t *)&data; -+ esp_err_t ret = ESP_FAIL; -+ uint16_t reg_htons = LITTLETOBIG(reg); -+ uint8_t *reg_u8 = (uint8_t *)®_htons; -+ -+ #if CONFIG_SCCB_SOFTWARE_SUPPORT -+ // 未测试 -+ soft_bus_start(); -+ soft_bus_write_byte((slv_addr << 1) | WRITE_BIT); -+ ret = soft_bus_wait_ack(); -+ soft_bus_write_byte(reg_u8[0]); -+ ret = soft_bus_wait_ack(); -+ soft_bus_write_byte(reg_u8[1]); -+ ret = soft_bus_wait_ack(); -+ soft_bus_stop(); -+ if (ret != ESP_OK) return -1; -+ soft_bus_start(); -+ soft_bus_write_byte((slv_addr << 1) | READ_BIT); -+ ret = soft_bus_wait_ack(); -+ soft_bus_read_byte(&data_u8[1], true); -+ soft_bus_read_byte(&data_u8[0], false); -+ soft_bus_stop(); -+ #else -+ i2c_cmd_handle_t cmd = i2c_cmd_link_create(); -+ i2c_master_start(cmd); -+ i2c_master_write_byte(cmd, (slv_addr << 1) | WRITE_BIT, ACK_CHECK_EN); -+ i2c_master_write_byte(cmd, reg_u8[0], ACK_CHECK_EN); -+ i2c_master_write_byte(cmd, reg_u8[1], ACK_CHECK_EN); -+ i2c_master_stop(cmd); -+ ret = i2c_master_cmd_begin(sccb_i2c_port, cmd, 1000 / portTICK_RATE_MS); -+ i2c_cmd_link_delete(cmd); -+ if (ret != ESP_OK) return -1; -+ cmd = i2c_cmd_link_create(); -+ i2c_master_start(cmd); -+ i2c_master_write_byte(cmd, (slv_addr << 1) | READ_BIT, ACK_CHECK_EN); -+ i2c_master_read_byte(cmd, &data_u8[1], ACK_VAL); -+ i2c_master_read_byte(cmd, &data_u8[0], NACK_VAL); -+ i2c_master_stop(cmd); -+ ret = i2c_master_cmd_begin(sccb_i2c_port, cmd, 1000 / portTICK_RATE_MS); -+ i2c_cmd_link_delete(cmd); -+ #endif -+ -+ if (ret != ESP_OK) { -+ ESP_LOGE(TAG, "W [%04x]=%04x fail\n", reg, data); -+ } -+ -+ return data; -+ } -+ -+ int SCCB_Write_Addr16_Val16(uint8_t slv_addr, uint16_t reg, uint16_t data) -+ { -+ esp_err_t ret = ESP_FAIL; -+ uint16_t reg_htons = LITTLETOBIG(reg); -+ uint8_t *reg_u8 = (uint8_t *)®_htons; -+ uint16_t data_htons = LITTLETOBIG(data); -+ uint8_t *data_u8 = (uint8_t *)&data_htons; -+ -+ #if CONFIG_SCCB_SOFTWARE_SUPPORT -+ // 未测试 -+ soft_bus_start(); -+ soft_bus_write_byte((slv_addr << 1) | WRITE_BIT); -+ ret = soft_bus_wait_ack(); -+ soft_bus_write_byte(reg_u8[0]); -+ ret = soft_bus_wait_ack(); -+ soft_bus_write_byte(reg_u8[1]); -+ ret = soft_bus_wait_ack(); -+ soft_bus_write_byte(data_u8[0]); -+ ret = soft_bus_wait_ack(); -+ soft_bus_write_byte(data_u8[1]); -+ ret = soft_bus_wait_ack(); -+ soft_bus_stop(); -+ #else -+ i2c_cmd_handle_t cmd = i2c_cmd_link_create(); -+ i2c_master_start(cmd); -+ i2c_master_write_byte(cmd, (slv_addr << 1) | WRITE_BIT, ACK_CHECK_EN); -+ i2c_master_write_byte(cmd, reg_u8[0], ACK_CHECK_EN); -+ i2c_master_write_byte(cmd, reg_u8[1], ACK_CHECK_EN); -+ i2c_master_write_byte(cmd, data_u8[0], ACK_CHECK_EN); -+ i2c_master_write_byte(cmd, data_u8[1], ACK_CHECK_EN); -+ i2c_master_stop(cmd); -+ ret = i2c_master_cmd_begin(sccb_i2c_port, cmd, 1000 / portTICK_RATE_MS); -+ i2c_cmd_link_delete(cmd); -+ #endif -+ -+ if (ret != ESP_OK) { -+ ESP_LOGE(TAG, "W [%04x]=%04x fail\n", reg, data); -+ } -+ -+ return ret == ESP_OK ? 0 : -1; -+ } -+ -\ No newline at end of file ++#endif + } + + int SCCB_Deinit(void) + { ++#if CONFIG_SCCB_SOFTWARE_SUPPORT ++ gpio_reset_pin(g_soft_sccb_config.pin_scl); ++ gpio_reset_pin(g_soft_sccb_config.pin_sda); ++ return ESP_OK; ++#else + if (!sccb_owns_i2c_port) { + return ESP_OK; + } + sccb_owns_i2c_port = false; + return i2c_driver_delete(sccb_i2c_port); ++#endif + } + + uint8_t SCCB_Probe(void) + { + uint8_t slave_addr = 0x0; + ++#if CONFIG_SCCB_SOFTWARE_SUPPORT ++ for (size_t i = 0; i < CAMERA_MODEL_MAX; i++) { ++ if (slave_addr == camera_sensor[i].sccb_addr) { ++ continue; ++ } ++ slave_addr = camera_sensor[i].sccb_addr; ++ soft_bus_start(); ++ soft_bus_write_byte((slave_addr << 1) | WRITE_BIT); ++ esp_err_t ret = soft_bus_wait_ack(); ++ soft_bus_stop(); ++ if (ret == ESP_OK) { ++ return slave_addr; ++ } ++ esp_rom_delay_us(g_soft_sccb_config.time_delay_us); ++ } ++#else + for (size_t i = 0; i < CAMERA_MODEL_MAX; i++) { + if (slave_addr == camera_sensor[i].sccb_addr) { + continue; +@@ -111,13 +292,31 @@ uint8_t SCCB_Probe(void) + return slave_addr; + } + } ++#endif ++ + return 0; + } + ++ + uint8_t SCCB_Read(uint8_t slv_addr, uint8_t reg) + { + uint8_t data=0; + esp_err_t ret = ESP_FAIL; ++ ++#if CONFIG_SCCB_SOFTWARE_SUPPORT ++ soft_bus_start(); ++ soft_bus_write_byte((slv_addr << 1) | WRITE_BIT); ++ ret = soft_bus_wait_ack(); ++ soft_bus_write_byte(reg); ++ ret = soft_bus_wait_ack(); ++ // soft_bus_stop(); ++ if (ret != ESP_OK) return -1; ++ soft_bus_start(); ++ soft_bus_write_byte((slv_addr << 1) | READ_BIT); ++ ret = soft_bus_wait_ack(); ++ soft_bus_read_byte(&data, false); ++ soft_bus_stop(); ++#else + i2c_cmd_handle_t cmd = i2c_cmd_link_create(); + i2c_master_start(cmd); + i2c_master_write_byte(cmd, ( slv_addr << 1 ) | WRITE_BIT, ACK_CHECK_EN); +@@ -133,15 +332,31 @@ uint8_t SCCB_Read(uint8_t slv_addr, uint + i2c_master_stop(cmd); + ret = i2c_master_cmd_begin(sccb_i2c_port, cmd, 1000 / portTICK_RATE_MS); + i2c_cmd_link_delete(cmd); ++#endif ++ + if(ret != ESP_OK) { + ESP_LOGE(TAG, "SCCB_Read Failed addr:0x%02x, reg:0x%02x, data:0x%02x, ret:%d", slv_addr, reg, data, ret); + } ++ + return data; + } + + int SCCB_Write(uint8_t slv_addr, uint8_t reg, uint8_t data) + { + esp_err_t ret = ESP_FAIL; ++ ++#if CONFIG_SCCB_SOFTWARE_SUPPORT ++ soft_bus_start(); ++ soft_bus_write_byte((slv_addr << 1) | WRITE_BIT); ++ ret = soft_bus_wait_ack(); ++ soft_bus_write_byte(reg); ++ ret = soft_bus_wait_ack(); ++ soft_bus_write_byte(data); ++ ret = soft_bus_wait_ack(); ++ soft_bus_stop(); ++ esp_rom_delay_us(g_soft_sccb_config.time_delay_us); ++ esp_rom_delay_us(g_soft_sccb_config.time_delay_us); ++#else + i2c_cmd_handle_t cmd = i2c_cmd_link_create(); + i2c_master_start(cmd); + i2c_master_write_byte(cmd, ( slv_addr << 1 ) | WRITE_BIT, ACK_CHECK_EN); +@@ -150,9 +365,12 @@ int SCCB_Write(uint8_t slv_addr, uint8_t + i2c_master_stop(cmd); + ret = i2c_master_cmd_begin(sccb_i2c_port, cmd, 1000 / portTICK_RATE_MS); + i2c_cmd_link_delete(cmd); ++#endif ++ + if(ret != ESP_OK) { + ESP_LOGE(TAG, "SCCB_Write Failed addr:0x%02x, reg:0x%02x, data:0x%02x, ret:%d", slv_addr, reg, data, ret); + } ++ + return ret == ESP_OK ? 0 : -1; + } + +@@ -162,6 +380,23 @@ uint8_t SCCB_Read16(uint8_t slv_addr, ui + esp_err_t ret = ESP_FAIL; + uint16_t reg_htons = LITTLETOBIG(reg); + uint8_t *reg_u8 = (uint8_t *)®_htons; ++ ++#if CONFIG_SCCB_SOFTWARE_SUPPORT ++ soft_bus_start(); ++ soft_bus_write_byte((slv_addr << 1) | WRITE_BIT); ++ ret = soft_bus_wait_ack(); ++ soft_bus_write_byte(reg_u8[0]); ++ ret = soft_bus_wait_ack(); ++ soft_bus_write_byte(reg_u8[1]); ++ ret = soft_bus_wait_ack(); ++ soft_bus_stop(); ++ if (ret != ESP_OK) return -1; ++ soft_bus_start(); ++ soft_bus_write_byte((slv_addr << 1) | READ_BIT); ++ ret = soft_bus_wait_ack(); ++ soft_bus_read_byte(&data, true); ++ soft_bus_stop(); ++#else + i2c_cmd_handle_t cmd = i2c_cmd_link_create(); + i2c_master_start(cmd); + i2c_master_write_byte(cmd, ( slv_addr << 1 ) | WRITE_BIT, ACK_CHECK_EN); +@@ -178,9 +413,12 @@ uint8_t SCCB_Read16(uint8_t slv_addr, ui + i2c_master_stop(cmd); + ret = i2c_master_cmd_begin(sccb_i2c_port, cmd, 1000 / portTICK_RATE_MS); + i2c_cmd_link_delete(cmd); ++#endif ++ + if(ret != ESP_OK) { + ESP_LOGE(TAG, "W [%04x]=%02x fail\n", reg, data); + } ++ + return data; + } + +@@ -190,6 +428,19 @@ int SCCB_Write16(uint8_t slv_addr, uint1 + esp_err_t ret = ESP_FAIL; + uint16_t reg_htons = LITTLETOBIG(reg); + uint8_t *reg_u8 = (uint8_t *)®_htons; ++ ++#if CONFIG_SCCB_SOFTWARE_SUPPORT ++ soft_bus_start(); ++ soft_bus_write_byte((slv_addr << 1) | WRITE_BIT); ++ ret = soft_bus_wait_ack(); ++ soft_bus_write_byte(reg_u8[0]); ++ ret = soft_bus_wait_ack(); ++ soft_bus_write_byte(reg_u8[1]); ++ ret = soft_bus_wait_ack(); ++ soft_bus_write_byte(data); ++ ret = soft_bus_wait_ack(); ++ soft_bus_stop(); ++#else + i2c_cmd_handle_t cmd = i2c_cmd_link_create(); + i2c_master_start(cmd); + i2c_master_write_byte(cmd, ( slv_addr << 1 ) | WRITE_BIT, ACK_CHECK_EN); +@@ -199,9 +450,12 @@ int SCCB_Write16(uint8_t slv_addr, uint1 + i2c_master_stop(cmd); + ret = i2c_master_cmd_begin(sccb_i2c_port, cmd, 1000 / portTICK_RATE_MS); + i2c_cmd_link_delete(cmd); ++#endif ++ + if(ret != ESP_OK) { + ESP_LOGE(TAG, "W [%04x]=%02x %d fail\n", reg, data, i++); + } ++ + return ret == ESP_OK ? 0 : -1; + } + +@@ -212,6 +466,25 @@ uint16_t SCCB_Read_Addr16_Val16(uint8_t + esp_err_t ret = ESP_FAIL; + uint16_t reg_htons = LITTLETOBIG(reg); + uint8_t *reg_u8 = (uint8_t *)®_htons; ++ ++#if CONFIG_SCCB_SOFTWARE_SUPPORT ++ // 未测试 ++ soft_bus_start(); ++ soft_bus_write_byte((slv_addr << 1) | WRITE_BIT); ++ ret = soft_bus_wait_ack(); ++ soft_bus_write_byte(reg_u8[0]); ++ ret = soft_bus_wait_ack(); ++ soft_bus_write_byte(reg_u8[1]); ++ ret = soft_bus_wait_ack(); ++ soft_bus_stop(); ++ if (ret != ESP_OK) return -1; ++ soft_bus_start(); ++ soft_bus_write_byte((slv_addr << 1) | READ_BIT); ++ ret = soft_bus_wait_ack(); ++ soft_bus_read_byte(&data_u8[1], true); ++ soft_bus_read_byte(&data_u8[0], false); ++ soft_bus_stop(); ++#else + i2c_cmd_handle_t cmd = i2c_cmd_link_create(); + i2c_master_start(cmd); + i2c_master_write_byte(cmd, ( slv_addr << 1 ) | WRITE_BIT, ACK_CHECK_EN); +@@ -230,9 +503,12 @@ uint16_t SCCB_Read_Addr16_Val16(uint8_t + i2c_master_stop(cmd); + ret = i2c_master_cmd_begin(sccb_i2c_port, cmd, 1000 / portTICK_RATE_MS); + i2c_cmd_link_delete(cmd); ++#endif ++ + if(ret != ESP_OK) { + ESP_LOGE(TAG, "W [%04x]=%04x fail\n", reg, data); + } ++ + return data; + } + +@@ -243,6 +519,22 @@ int SCCB_Write_Addr16_Val16(uint8_t slv_ + uint8_t *reg_u8 = (uint8_t *)®_htons; + uint16_t data_htons = LITTLETOBIG(data); + uint8_t *data_u8 = (uint8_t *)&data_htons; ++ ++#if CONFIG_SCCB_SOFTWARE_SUPPORT ++ // 未测试 ++ soft_bus_start(); ++ soft_bus_write_byte((slv_addr << 1) | WRITE_BIT); ++ ret = soft_bus_wait_ack(); ++ soft_bus_write_byte(reg_u8[0]); ++ ret = soft_bus_wait_ack(); ++ soft_bus_write_byte(reg_u8[1]); ++ ret = soft_bus_wait_ack(); ++ soft_bus_write_byte(data_u8[0]); ++ ret = soft_bus_wait_ack(); ++ soft_bus_write_byte(data_u8[1]); ++ ret = soft_bus_wait_ack(); ++ soft_bus_stop(); ++#else + i2c_cmd_handle_t cmd = i2c_cmd_link_create(); + i2c_master_start(cmd); + i2c_master_write_byte(cmd, ( slv_addr << 1 ) | WRITE_BIT, ACK_CHECK_EN); +@@ -253,8 +545,11 @@ int SCCB_Write_Addr16_Val16(uint8_t slv_ + i2c_master_stop(cmd); + ret = i2c_master_cmd_begin(sccb_i2c_port, cmd, 1000 / portTICK_RATE_MS); + i2c_cmd_link_delete(cmd); ++#endif ++ + if(ret != ESP_OK) { + ESP_LOGE(TAG, "W [%04x]=%04x fail\n", reg, data); + } ++ + return ret == ESP_OK ? 0 : -1; + } diff --git a/m5stack/patches/README.md b/m5stack/patches/README.md index 86547f8e..861ef390 100644 --- a/m5stack/patches/README.md +++ b/m5stack/patches/README.md @@ -3,4 +3,6 @@ - 0000 - 0999 这个范围的补丁 id, 用于分配给 micropython 相关的补丁。 - 1000 - 1999 这个范围的补丁 id, 用于分配给 esp-idf 相关的补丁。 - 2000 - 2999 这个范围的补丁 id, 用于分配给 M5Unified 相关的补丁。 -- 3000 - 3999 这个范围的补丁 id, 用于分配给 esp-idf 相关的补丁。 +- 3000 - 3999 这个范围的补丁 id, 用于分配给 esp-adf 相关的补丁。 +- 4000 - 4999 这个范围的补丁 id, 用于分配给 M5GFX 相关的补丁。 +- 5000 - 5999 这个范围的补丁 id, 用于分配给 esp32-camera 相关的补丁。 diff --git a/micropython b/micropython index f212bbe8..f498a16c 160000 --- a/micropython +++ b/micropython @@ -1 +1 @@ -Subproject commit f212bbe837489f297c9d25d608bdb7b8c3da988d +Subproject commit f498a16c7db6d4b2de200b3e0856528dfe0613c3 diff --git a/third-party/boards/ESPRESSIF_ESP32_S3_BOX_3/board_init.c b/third-party/boards/ESPRESSIF_ESP32_S3_BOX_3/board_init.c index 20c6906f..0edc3465 100644 --- a/third-party/boards/ESPRESSIF_ESP32_S3_BOX_3/board_init.c +++ b/third-party/boards/ESPRESSIF_ESP32_S3_BOX_3/board_init.c @@ -16,7 +16,7 @@ #include "driver/i2s.h" #endif -#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0) +#if 0 //ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0) #include "driver/i2c_master.h" #define USE_IDF_I2C_MASTER #else From b5e2289c12e2a6ad5e8b66431ab626af7e24b77f Mon Sep 17 00:00:00 2001 From: lbuque Date: Wed, 21 May 2025 16:16:10 +0800 Subject: [PATCH 097/322] Makefile: Add patches for specific packages. Signed-off-by: lbuque --- m5stack/Makefile | 135 +++++++++++++----- m5stack/include/files.mk | 50 +++++++ ...0003-avoid-lv_bindings-compile-error.patch | 28 ++-- ...-micropython-1.25.0-add-esp32p4-pins.patch | 12 +- ...-micropython-1.25.0-machine-adc-v5.x.patch | 24 ++-- ...-micropython-1.25.0-fix-esp32-p4-pwm.patch | 6 +- ...opython-1.25.0-fix-esp32-p4-modesp32.patch | 6 +- ...0013-micropython-1.25.0-fix-touchpad.patch | 12 +- .../0014-micropython-1.25.0-fix-timer.patch | 6 +- ...0015-micropython-1.25.0-fix-hostname.patch | 8 +- ...-micropython-1.25.0-fix-mpnimbleport.patch | 6 +- m5stack/patches/2005-Support-LTR553.patch | 10 +- .../patches/3002-Modify-i2s_stream_idf5.patch | 6 +- .../patches/4002-M5GFX-use-i2c-driver.patch | 6 +- .../5001-Add-software-i2c-support.patch | 12 +- 15 files changed, 221 insertions(+), 106 deletions(-) diff --git a/m5stack/Makefile b/m5stack/Makefile index cb83cf98..4b31dbc8 100644 --- a/m5stack/Makefile +++ b/m5stack/Makefile @@ -296,42 +296,107 @@ submodules: git submodule update --init lib/micropython-lib && \ cd - +LV_BINDING_PATH = $(abspath ./cmodules/lv_binding_micropython) +MICROPYTHON_PATH = $(abspath ./../micropython) +M5UNIFIED_PATH = $(abspath ./components/M5Unified/M5Unified) +M5GFX_PATH = $(abspath ./components/M5Unified/M5GFX) +ESP32_CAMERA_PATH = $(abspath ./components/esp32-camera) + +LV_BINDING_PATCH_SERIES = \ + 0003-avoid-lv_bindings-compile-error.patch + +MICROPYTHON_PATCH_SERIES = \ + 0006-modtime-add-timezone-method.patch \ + 0007-Add-set-default-netif-method.patch \ + 0009-micropython-1.25.0-add-esp32p4-pins.patch \ + 0010-micropython-1.25.0-machine-adc-v5.x.patch \ + 0011-micropython-1.25.0-fix-esp32-p4-pwm.patch \ + 0012-micropython-1.25.0-fix-esp32-p4-modesp32.patch \ + 0013-micropython-1.25.0-fix-touchpad.patch \ + 0014-micropython-1.25.0-fix-timer.patch \ + 0015-micropython-1.25.0-fix-hostname.patch \ + 0016-micropython-1.25.0-fix-mpnimbleport.patch \ + 0017-micropython-1.25.0-add-uart-mode.patch + +IDF_PATH_PATCH_SERIES = \ + 1004-idf_v5.4_freertos.patch + +M5UNIFIED_PATCH_SERIES = \ + 2005-Support-LTR553.patch + +ADF_PATCH_SERIES = \ + 3002-Modify-i2s_stream_idf5.patch + +M5GFX_PATCH_SERIES = \ + 4002-M5GFX-use-i2c-driver.patch + +ESP32_CAMERA_PATCH_SERIES = \ + 5001-Add-software-i2c-support.patch + +PACKAGES = \ + lv_binding_micropython \ + micropython \ + esp-idf \ + M5Unified \ + esp-adf \ + M5GFX \ + esp32-camera + +PACKAGES_PATH = \ + lv_binding_micropython:$(LV_BINDING_PATH) \ + micropython:$(MICROPYTHON_PATH) \ + esp-idf:$(IDF_PATH) \ + M5Unified:$(M5UNIFIED_PATH) \ + esp-adf:$(ADF_PATH) \ + M5GFX:$(M5GFX_PATH) \ + esp32-camera:$(ESP32_CAMERA_PATH) + +define find_package +$(if $(filter $(1):%,$(PACKAGES_PATH)),$(word 2,$(subst :, ,$(filter $(1):%,$(PACKAGES_PATH)))),none) +endef + +.PHONY: $(PACKAGES) prepare +PKG := $(firstword $(MAKECMDGOALS)) + +PKG_PATH ?= $(call find_package,$(PKG)) +PKG_PATCH_SERIES := $(strip \ + $(if $(filter lv_binding_micropython,$(PKG)),$(LV_BINDING_PATCH_SERIES)) \ + $(if $(filter micropython,$(PKG)),$(MICROPYTHON_PATCH_SERIES)) \ + $(if $(filter esp-idf,$(PKG)),$(IDF_PATH_PATCH_SERIES)) \ + $(if $(filter M5Unified,$(PKG)),$(M5UNIFIED_PATCH_SERIES)) \ + $(if $(filter esp-adf,$(PKG)),$(ADF_PATCH_SERIES)) \ + $(if $(filter M5GFX,$(PKG)),$(M5GFX_PATCH_SERIES)) \ + $(if $(filter esp32-camera,$(PKG)),$(ESP32_CAMERA_PATCH_SERIES)) \ +) +# $(info PKG_PATCH_SERIES for $(PKG) is [$(PKG_PATCH_SERIES)]) + +prepare: + $(call Patch/prepare,$(abspath $(PKG_PATH)),$(PKG_PATCH_SERIES)) + +.PHONY: $(PACKAGES) update +update: + $(call Patch/update,$(abspath $(PKG_PATH)),$(abspath ./patches)) + +.PHONY: $(PACKAGES) unprepare +unprepare: + $(call Patch/clean,$(abspath $(PKG_PATH)),$(PKG_PATCH_SERIES)) + # Apply patches -patch: - $(call Package/patche,$(abspath ./cmodules/lv_binding_micropython),$(abspath ./patches/0003-avoid-lv_bindings-compile-error.patch)) - $(call Package/patche,$(abspath ./../micropython),$(abspath ./patches/0006-modtime-add-timezone-method.patch)) - $(call Package/patche,$(abspath ./../micropython),$(abspath ./patches/0007-Add-set-default-netif-method.patch)) - $(call Package/patche,$(abspath ./../micropython),$(abspath ./patches/0009-micropython-1.25.0-add-esp32p4-pins.patch)) - $(call Package/patche,$(abspath ./../micropython),$(abspath ./patches/0010-micropython-1.25.0-machine-adc-v5.x.patch)) - $(call Package/patche,$(abspath ./../micropython),$(abspath ./patches/0011-micropython-1.25.0-fix-esp32-p4-pwm.patch)) - $(call Package/patche,$(abspath ./../micropython),$(abspath ./patches/0012-micropython-1.25.0-fix-esp32-p4-modesp32.patch)) - $(call Package/patche,$(abspath ./../micropython),$(abspath ./patches/0013-micropython-1.25.0-fix-touchpad.patch)) - $(call Package/patche,$(abspath ./../micropython),$(abspath ./patches/0014-micropython-1.25.0-fix-timer.patch)) - $(call Package/patche,$(abspath ./../micropython),$(abspath ./patches/0015-micropython-1.25.0-fix-hostname.patch)) - $(call Package/patche,$(abspath ./../micropython),$(abspath ./patches/0016-micropython-1.25.0-fix-mpnimbleport.patch)) - $(call Package/patche,$(abspath ./../micropython),$(abspath ./patches/0017-micropython-1.25.0-add-uart-mode.patch)) - $(call Package/patche,$(abspath $(IDF_PATH)),$(abspath ./patches/1004-idf_v5.4_freertos.patch)) - $(call Package/patche,$(abspath ./components/M5Unified/M5Unified),$(abspath ./patches/2005-Support-LTR553.patch)) - $(call Package/patche,$(abspath $(ADF_PATH)),$(abspath ./patches/3002-Modify-i2s_stream_idf5.patch)) - $(call Package/patche,$(abspath ./components/M5Unified/M5GFX),$(abspath ./patches/4002-M5GFX-use-i2c-driver.patch)) - $(call Package/patche,$(abspath ./components/esp32-camera),$(abspath ./patches/5001-Add-software-i2c-support.patch)) +patch: unpatch + $(call Patch/prepare,$(LV_BINDING_PATH),$(LV_BINDING_PATCH_SERIES)) + $(call Patch/prepare,$(MICROPYTHON_PATH),$(MICROPYTHON_PATCH_SERIES)) + $(call Patch/prepare,$(IDF_PATH),$(IDF_PATH_PATCH_SERIES)) + $(call Patch/prepare,$(M5UNIFIED_PATH),$(M5UNIFIED_PATCH_SERIES)) + $(call Patch/prepare,$(ADF_PATH),$(ADF_PATCH_SERIES)) + $(call Patch/prepare,$(M5GFX_PATH),$(M5GFX_PATCH_SERIES)) + $(call Patch/prepare,$(ESP32_CAMERA_PATH),$(ESP32_CAMERA_PATCH_SERIES)) # Unapply patches unpatch: - $(call Package/unpatche,$(abspath ./cmodules/lv_binding_micropython),$(abspath ./patches/0003-avoid-lv_bindings-compile-error.patch)) - $(call Package/unpatche,$(abspath ./../micropython),$(abspath ./patches/0017-micropython-1.25.0-add-uart-mode.patch)) - $(call Package/unpatche,$(abspath ./../micropython),$(abspath ./patches/0016-micropython-1.25.0-fix-mpnimbleport.patch)) - $(call Package/unpatche,$(abspath ./../micropython),$(abspath ./patches/0015-micropython-1.25.0-fix-hostname.patch)) - $(call Package/unpatche,$(abspath ./../micropython),$(abspath ./patches/0014-micropython-1.25.0-fix-timer.patch)) - $(call Package/unpatche,$(abspath ./../micropython),$(abspath ./patches/0013-micropython-1.25.0-fix-touchpad.patch)) - $(call Package/unpatche,$(abspath ./../micropython),$(abspath ./patches/0012-micropython-1.25.0-fix-esp32-p4-modesp32.patch)) - $(call Package/unpatche,$(abspath ./../micropython),$(abspath ./patches/0011-micropython-1.25.0-fix-esp32-p4-pwm.patch)) - $(call Package/unpatche,$(abspath ./../micropython),$(abspath ./patches/0010-micropython-1.25.0-machine-adc-v5.x.patch)) - $(call Package/unpatche,$(abspath ./../micropython),$(abspath ./patches/0009-micropython-1.25.0-add-esp32p4-pins.patch)) - $(call Package/unpatche,$(abspath ./../micropython),$(abspath ./patches/0007-Add-set-default-netif-method.patch)) - $(call Package/unpatche,$(abspath ./../micropython),$(abspath ./patches/0006-modtime-add-timezone-method.patch)) - $(call Package/unpatche,$(abspath $(IDF_PATH)),$(abspath ./patches/1004-idf_v5.4_freertos.patch)) - $(call Package/unpatche,$(abspath ./components/M5Unified/M5Unified),$(abspath ./patches/2005-Support-LTR553.patch)) - $(call Package/unpatche,$(abspath $(ADF_PATH)),$(abspath ./patches/3002-Modify-i2s_stream_idf5.patch)) - $(call Package/unpatche,$(abspath ./components/M5Unified/M5GFX),$(abspath ./patches/4002-M5GFX-use-i2c-driver.patch)) - $(call Package/unpatche,$(abspath ./components/esp32-camera),$(abspath ./patches/5001-Add-software-i2c-support.patch)) + $(call Patch/clean,$(LV_BINDING_PATH),$(LV_BINDING_PATCH_SERIES)) + $(call Patch/clean,$(MICROPYTHON_PATH),$(MICROPYTHON_PATCH_SERIES)) + $(call Patch/clean,$(IDF_PATH),$(IDF_PATH_PATCH_SERIES)) + $(call Patch/clean,$(M5UNIFIED_PATH),$(M5UNIFIED_PATCH_SERIES)) + $(call Patch/clean,$(ADF_PATH),$(ADF_PATCH_SERIES)) + $(call Patch/clean,$(M5GFX_PATH),$(M5GFX_PATCH_SERIES)) + $(call Patch/clean,$(ESP32_CAMERA_PATH),$(ESP32_CAMERA_PATCH_SERIES)) diff --git a/m5stack/include/files.mk b/m5stack/include/files.mk index 98053834..a76977de 100644 --- a/m5stack/include/files.mk +++ b/m5stack/include/files.mk @@ -13,6 +13,56 @@ define base-files/install endef +## +## $(1): TARGET_DIR +## $(2): PATCH_SERIES (space-separated list) +## +define abs_path + $(abspath $(CURDIR)/patches/$(1)) +endef + +## +## $(1): TARGET_DIR +## $(2): PATCH_SERIES (space-separated list) +## +define Patch/prepare + @echo "Preparing $(1) ..." + @(cd $(1) && [ -e patches ] || mkdir patches) + @(cd $(1) && \ + quilt import $(foreach patch,$(2),$(call abs_path,$(patch))) && \ + cd - >/dev/null) + @echo "Applying all patches in $(1) ..." + @cd $(1) && quilt push -a && cd - +endef + + +## +## $(1): TARGET_DIR +## $(2): OUTPUT_DIR +## +## +define Patch/update + @echo "Exporting patches from $(1) to $(2) ..." + @mkdir -p $(2) + @cd $(1) && \ + for p in `quilt series`; do \ + echo "Exporting $$p to $(2) ..." ; \ + cp $$p $(2) ; \ + done +endef + + +## +## $(1) TARGET_DIR +## +define Patch/clean + @echo "Cleaning patch state in $(1) ..." + @cd $(1) && \ + (quilt pop -a || true) && \ + rm -rf .pc patches +endef + + ## ## $(1) TARGET_DIR ## $(2) PATCH_FILE diff --git a/m5stack/patches/0003-avoid-lv_bindings-compile-error.patch b/m5stack/patches/0003-avoid-lv_bindings-compile-error.patch index 88c17650..907eff20 100644 --- a/m5stack/patches/0003-avoid-lv_bindings-compile-error.patch +++ b/m5stack/patches/0003-avoid-lv_bindings-compile-error.patch @@ -1,7 +1,7 @@ -diff --git a/driver/include/common.h b/driver/include/common.h -index 5cec282..c9f45d2 100644 ---- a/driver/include/common.h -+++ b/driver/include/common.h +Index: lv_binding_micropython/driver/include/common.h +=================================================================== +--- lv_binding_micropython.orig/driver/include/common.h ++++ lv_binding_micropython/driver/include/common.h @@ -16,7 +16,7 @@ typedef struct mp_ptr_t void *ptr; } mp_ptr_t; @@ -11,7 +11,7 @@ index 5cec282..c9f45d2 100644 { mp_ptr_t *self = MP_OBJ_TO_PTR(self_in); -@@ -34,7 +34,7 @@ STATIC mp_int_t mp_ptr_get_buffer(mp_obj_t self_in, mp_buffer_info_t *bufinfo, m +@@ -34,7 +34,7 @@ STATIC mp_int_t mp_ptr_get_buffer(mp_obj #define PTR_OBJ(ptr_global) ptr_global ## _obj #define DEFINE_PTR_OBJ_TYPE(ptr_obj_type, ptr_type_qstr)\ @@ -29,11 +29,11 @@ index 5cec282..c9f45d2 100644 { &ptr_global ## _type },\ &ptr_global\ } -diff --git a/lib/fs_driver.py b/lib/fs_driver.py -index bac84b3..0e0a989 100644 ---- a/lib/fs_driver.py -+++ b/lib/fs_driver.py -@@ -3,7 +3,7 @@ Original author: mhepp(https://forum.lvgl.io/u/mhepp/summary) +Index: lv_binding_micropython/lib/fs_driver.py +=================================================================== +--- lv_binding_micropython.orig/lib/fs_driver.py ++++ lv_binding_micropython/lib/fs_driver.py +@@ -3,7 +3,7 @@ Original author: mhepp(https://forum.lvg ''' import lvgl as lv @@ -42,10 +42,10 @@ index bac84b3..0e0a989 100644 def fs_open_cb(drv, path, mode): -diff --git a/lv_conf.h b/lv_conf.h -index 2e0d08d..246ec8c 100644 ---- a/lv_conf.h -+++ b/lv_conf.h +Index: lv_binding_micropython/lv_conf.h +=================================================================== +--- lv_binding_micropython.orig/lv_conf.h ++++ lv_binding_micropython/lv_conf.h @@ -704,7 +704,7 @@ extern void mp_lv_deinit_gc(); #define LV_USE_LIBPNG 0 diff --git a/m5stack/patches/0009-micropython-1.25.0-add-esp32p4-pins.patch b/m5stack/patches/0009-micropython-1.25.0-add-esp32p4-pins.patch index 7f37562b..1c7ba91b 100644 --- a/m5stack/patches/0009-micropython-1.25.0-add-esp32p4-pins.patch +++ b/m5stack/patches/0009-micropython-1.25.0-add-esp32p4-pins.patch @@ -1,7 +1,7 @@ -Index: micropython-origin/ports/esp32/boards/make-pins.py +Index: micropython/ports/esp32/boards/make-pins.py =================================================================== ---- micropython-origin.orig/ports/esp32/boards/make-pins.py -+++ micropython-origin/ports/esp32/boards/make-pins.py +--- micropython.orig/ports/esp32/boards/make-pins.py ++++ micropython/ports/esp32/boards/make-pins.py @@ -8,7 +8,7 @@ import boardgen @@ -11,10 +11,10 @@ Index: micropython-origin/ports/esp32/boards/make-pins.py class Esp32Pin(boardgen.Pin): -Index: micropython-origin/ports/esp32/machine_pin.h +Index: micropython/ports/esp32/machine_pin.h =================================================================== ---- micropython-origin.orig/ports/esp32/machine_pin.h -+++ micropython-origin/ports/esp32/machine_pin.h +--- micropython.orig/ports/esp32/machine_pin.h ++++ micropython/ports/esp32/machine_pin.h @@ -174,6 +174,62 @@ #define MICROPY_HW_ENABLE_GPIO48 (1) #endif diff --git a/m5stack/patches/0010-micropython-1.25.0-machine-adc-v5.x.patch b/m5stack/patches/0010-micropython-1.25.0-machine-adc-v5.x.patch index 6ec7e4ed..a1924d35 100644 --- a/m5stack/patches/0010-micropython-1.25.0-machine-adc-v5.x.patch +++ b/m5stack/patches/0010-micropython-1.25.0-machine-adc-v5.x.patch @@ -1,7 +1,7 @@ -Index: micropython-origin/ports/esp32/adc.c +Index: micropython/ports/esp32/adc.c =================================================================== ---- micropython-origin.orig/ports/esp32/adc.c -+++ micropython-origin/ports/esp32/adc.c +--- micropython.orig/ports/esp32/adc.c ++++ micropython/ports/esp32/adc.c @@ -27,8 +27,6 @@ #include "py/mphal.h" @@ -82,10 +82,10 @@ Index: micropython-origin/ports/esp32/adc.c check_esp_err(adc_cali_raw_to_voltage(self->handle[atten], raw, &uv)); return (mp_int_t)uv * 1000; -Index: micropython-origin/ports/esp32/adc.h +Index: micropython/ports/esp32/adc.h =================================================================== ---- micropython-origin.orig/ports/esp32/adc.h -+++ micropython-origin/ports/esp32/adc.h +--- micropython.orig/ports/esp32/adc.h ++++ micropython/ports/esp32/adc.h @@ -30,6 +30,7 @@ #include "py/runtime.h" @@ -104,10 +104,10 @@ Index: micropython-origin/ports/esp32/adc.h adc_cali_handle_t handle[ADC_ATTEN_MAX]; } machine_adc_block_obj_t; -Index: micropython-origin/ports/esp32/machine_adc.c +Index: micropython/ports/esp32/machine_adc.c =================================================================== ---- micropython-origin.orig/ports/esp32/machine_adc.c -+++ micropython-origin/ports/esp32/machine_adc.c +--- micropython.orig/ports/esp32/machine_adc.c ++++ micropython/ports/esp32/machine_adc.c @@ -30,7 +30,6 @@ #include "py/mphal.h" @@ -203,10 +203,10 @@ Index: micropython-origin/ports/esp32/machine_adc.c - madcblock_bits_helper(self->block, width); + madc_bits_helper(self, width); } -Index: micropython-origin/ports/esp32/machine_adc_block.c +Index: micropython/ports/esp32/machine_adc_block.c =================================================================== ---- micropython-origin.orig/ports/esp32/machine_adc_block.c -+++ micropython-origin/ports/esp32/machine_adc_block.c +--- micropython.orig/ports/esp32/machine_adc_block.c ++++ micropython/ports/esp32/machine_adc_block.c @@ -29,12 +29,11 @@ #include "py/mphal.h" diff --git a/m5stack/patches/0011-micropython-1.25.0-fix-esp32-p4-pwm.patch b/m5stack/patches/0011-micropython-1.25.0-fix-esp32-p4-pwm.patch index f2dae336..beb52f62 100644 --- a/m5stack/patches/0011-micropython-1.25.0-fix-esp32-p4-pwm.patch +++ b/m5stack/patches/0011-micropython-1.25.0-fix-esp32-p4-pwm.patch @@ -1,7 +1,7 @@ -Index: micropython-origin/ports/esp32/machine_pwm.c +Index: micropython/ports/esp32/machine_pwm.c =================================================================== ---- micropython-origin.orig/ports/esp32/machine_pwm.c -+++ micropython-origin/ports/esp32/machine_pwm.c +--- micropython.orig/ports/esp32/machine_pwm.c ++++ micropython/ports/esp32/machine_pwm.c @@ -177,7 +177,11 @@ static void pwm_deinit(int channel_idx) // Disable ledc signal for the pin // esp_rom_gpio_connect_out_signal(pin, SIG_GPIO_OUT_IDX, false, false); diff --git a/m5stack/patches/0012-micropython-1.25.0-fix-esp32-p4-modesp32.patch b/m5stack/patches/0012-micropython-1.25.0-fix-esp32-p4-modesp32.patch index 33d1378e..a61030c4 100644 --- a/m5stack/patches/0012-micropython-1.25.0-fix-esp32-p4-modesp32.patch +++ b/m5stack/patches/0012-micropython-1.25.0-fix-esp32-p4-modesp32.patch @@ -1,7 +1,7 @@ -Index: micropython-origin/ports/esp32/modesp32.c +Index: micropython/ports/esp32/modesp32.c =================================================================== ---- micropython-origin.orig/ports/esp32/modesp32.c -+++ micropython-origin/ports/esp32/modesp32.c +--- micropython.orig/ports/esp32/modesp32.c ++++ micropython/ports/esp32/modesp32.c @@ -136,7 +136,7 @@ static mp_obj_t esp32_wake_on_ulp(const } static MP_DEFINE_CONST_FUN_OBJ_1(esp32_wake_on_ulp_obj, esp32_wake_on_ulp); diff --git a/m5stack/patches/0013-micropython-1.25.0-fix-touchpad.patch b/m5stack/patches/0013-micropython-1.25.0-fix-touchpad.patch index a17f225c..cba847f2 100644 --- a/m5stack/patches/0013-micropython-1.25.0-fix-touchpad.patch +++ b/m5stack/patches/0013-micropython-1.25.0-fix-touchpad.patch @@ -1,7 +1,7 @@ -Index: micropython-origin/ports/esp32/machine_touchpad.c +Index: micropython/ports/esp32/machine_touchpad.c =================================================================== ---- micropython-origin.orig/ports/esp32/machine_touchpad.c -+++ micropython-origin/ports/esp32/machine_touchpad.c +--- micropython.orig/ports/esp32/machine_touchpad.c ++++ micropython/ports/esp32/machine_touchpad.c @@ -29,7 +29,7 @@ #include "modmachine.h" #include "driver/gpio.h" @@ -11,10 +11,10 @@ Index: micropython-origin/ports/esp32/machine_touchpad.c #if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 3, 0) #if SOC_TOUCH_VERSION_1 -Index: micropython-origin/ports/esp32/modmachine.c +Index: micropython/ports/esp32/modmachine.c =================================================================== ---- micropython-origin.orig/ports/esp32/modmachine.c -+++ micropython-origin/ports/esp32/modmachine.c +--- micropython.orig/ports/esp32/modmachine.c ++++ micropython/ports/esp32/modmachine.c @@ -45,7 +45,7 @@ #define MICROPY_PY_MACHINE_SDCARD_ENTRY #endif diff --git a/m5stack/patches/0014-micropython-1.25.0-fix-timer.patch b/m5stack/patches/0014-micropython-1.25.0-fix-timer.patch index 73649e7c..b7075910 100644 --- a/m5stack/patches/0014-micropython-1.25.0-fix-timer.patch +++ b/m5stack/patches/0014-micropython-1.25.0-fix-timer.patch @@ -1,7 +1,7 @@ -Index: micropython-origin/ports/esp32/machine_timer.c +Index: micropython/ports/esp32/machine_timer.c =================================================================== ---- micropython-origin.orig/ports/esp32/machine_timer.c -+++ micropython-origin/ports/esp32/machine_timer.c +--- micropython.orig/ports/esp32/machine_timer.c ++++ micropython/ports/esp32/machine_timer.c @@ -38,6 +38,7 @@ #include "hal/timer_hal.h" #include "hal/timer_ll.h" diff --git a/m5stack/patches/0015-micropython-1.25.0-fix-hostname.patch b/m5stack/patches/0015-micropython-1.25.0-fix-hostname.patch index 34733aba..271c761f 100644 --- a/m5stack/patches/0015-micropython-1.25.0-fix-hostname.patch +++ b/m5stack/patches/0015-micropython-1.25.0-fix-hostname.patch @@ -1,8 +1,8 @@ -Index: micropython-origin/ports/esp32/mpconfigport.h +Index: micropython/ports/esp32/mpconfigport.h =================================================================== ---- micropython-origin.orig/ports/esp32/mpconfigport.h -+++ micropython-origin/ports/esp32/mpconfigport.h -@@ -167,6 +167,8 @@ +--- micropython.orig/ports/esp32/mpconfigport.h ++++ micropython/ports/esp32/mpconfigport.h +@@ -168,6 +168,8 @@ #define MICROPY_PY_NETWORK_HOSTNAME_DEFAULT "mpy-esp32c3" #elif CONFIG_IDF_TARGET_ESP32C6 #define MICROPY_PY_NETWORK_HOSTNAME_DEFAULT "mpy-esp32c6" diff --git a/m5stack/patches/0016-micropython-1.25.0-fix-mpnimbleport.patch b/m5stack/patches/0016-micropython-1.25.0-fix-mpnimbleport.patch index afc858b7..6136433f 100644 --- a/m5stack/patches/0016-micropython-1.25.0-fix-mpnimbleport.patch +++ b/m5stack/patches/0016-micropython-1.25.0-fix-mpnimbleport.patch @@ -1,7 +1,7 @@ -Index: micropython-origin/ports/esp32/mpnimbleport.c +Index: micropython/ports/esp32/mpnimbleport.c =================================================================== ---- micropython-origin.orig/ports/esp32/mpnimbleport.c -+++ micropython-origin/ports/esp32/mpnimbleport.c +--- micropython.orig/ports/esp32/mpnimbleport.c ++++ micropython/ports/esp32/mpnimbleport.c @@ -71,7 +71,9 @@ void mp_bluetooth_nimble_port_shutdown(v nimble_port_stop(); diff --git a/m5stack/patches/2005-Support-LTR553.patch b/m5stack/patches/2005-Support-LTR553.patch index 0b05ee21..11431352 100644 --- a/m5stack/patches/2005-Support-LTR553.patch +++ b/m5stack/patches/2005-Support-LTR553.patch @@ -2,7 +2,7 @@ Index: M5Unified/src/M5Unified.cpp =================================================================== --- M5Unified.orig/src/M5Unified.cpp +++ M5Unified/src/M5Unified.cpp -@@ -998,6 +998,7 @@ static void in_i2c_bulk_write(const uint +@@ -1303,6 +1303,7 @@ static constexpr const uint8_t _pin_tabl #elif defined (CONFIG_IDF_TARGET_ESP32S3) case board_t::board_M5StackCoreS3: case board_t::board_M5StackCoreS3SE: @@ -10,7 +10,7 @@ Index: M5Unified/src/M5Unified.cpp if (cfg.internal_mic) { mic_cfg.magnification = 2; -@@ -1472,6 +1473,9 @@ static void in_i2c_bulk_write(const uint +@@ -1786,6 +1787,9 @@ static constexpr const uint8_t _pin_tabl { port_a_used = M5.Imu.begin(&M5.Ex_I2C) || port_a_used; } @@ -24,7 +24,7 @@ Index: M5Unified/src/M5Unified.hpp =================================================================== --- M5Unified.orig/src/M5Unified.hpp +++ M5Unified/src/M5Unified.hpp -@@ -58,6 +58,7 @@ namespace m5 +@@ -64,6 +64,7 @@ namespace m5 #include "utility/Touch_Class.hpp" #include "utility/Log_Class.hpp" #include "utility/IMU_Class.hpp" @@ -32,7 +32,7 @@ Index: M5Unified/src/M5Unified.hpp #include #include -@@ -133,6 +134,9 @@ namespace m5 +@@ -139,6 +140,9 @@ namespace m5 /// use the speaker. bool internal_spk = true; @@ -42,7 +42,7 @@ Index: M5Unified/src/M5Unified.hpp /// use Unit Accel & Gyro. bool external_imu = false; -@@ -207,6 +211,7 @@ namespace m5 +@@ -215,6 +219,7 @@ namespace m5 Power_Class Power; RTC8563_Class Rtc; Touch_Class Touch; diff --git a/m5stack/patches/3002-Modify-i2s_stream_idf5.patch b/m5stack/patches/3002-Modify-i2s_stream_idf5.patch index ef99e9d1..a48dc47d 100644 --- a/m5stack/patches/3002-Modify-i2s_stream_idf5.patch +++ b/m5stack/patches/3002-Modify-i2s_stream_idf5.patch @@ -1,7 +1,7 @@ -Index: b/components/audio_stream/i2s_stream_idf5.c +Index: esp-adf/components/audio_stream/i2s_stream_idf5.c =================================================================== ---- a/components/audio_stream/i2s_stream_idf5.c -+++ b/components/audio_stream/i2s_stream_idf5.c +--- esp-adf.orig/components/audio_stream/i2s_stream_idf5.c ++++ esp-adf/components/audio_stream/i2s_stream_idf5.c @@ -79,10 +79,10 @@ struct i2s_key_slot_s { int i2s_refcount; }; diff --git a/m5stack/patches/4002-M5GFX-use-i2c-driver.patch b/m5stack/patches/4002-M5GFX-use-i2c-driver.patch index 0bfbefaf..62ddbf22 100644 --- a/m5stack/patches/4002-M5GFX-use-i2c-driver.patch +++ b/m5stack/patches/4002-M5GFX-use-i2c-driver.patch @@ -1,7 +1,7 @@ -Index: m5stack/src/lgfx/v1/platforms/esp32/common.cpp +Index: M5GFX/src/lgfx/v1/platforms/esp32/common.cpp =================================================================== ---- m5stack.orig/src/lgfx/v1/platforms/esp32/common.cpp -+++ m5stack/src/lgfx/v1/platforms/esp32/common.cpp +--- M5GFX.orig/src/lgfx/v1/platforms/esp32/common.cpp ++++ M5GFX/src/lgfx/v1/platforms/esp32/common.cpp @@ -27,7 +27,7 @@ Contributors: #include #include diff --git a/m5stack/patches/5001-Add-software-i2c-support.patch b/m5stack/patches/5001-Add-software-i2c-support.patch index c14ed5d6..ce561780 100644 --- a/m5stack/patches/5001-Add-software-i2c-support.patch +++ b/m5stack/patches/5001-Add-software-i2c-support.patch @@ -1,7 +1,7 @@ -Index: m5stack/components/esp32-camera/Kconfig +Index: esp32-camera/Kconfig =================================================================== ---- m5stack.orig/Kconfig -+++ m5stack/Kconfig +--- esp32-camera.orig/Kconfig ++++ esp32-camera/Kconfig @@ -128,6 +128,10 @@ menu "Camera configuration" endchoice @@ -13,10 +13,10 @@ Index: m5stack/components/esp32-camera/Kconfig config SCCB_CLK_FREQ int "SCCB clk frequency" default 100000 -Index: m5stack/components/esp32-camera/driver/sccb.c +Index: esp32-camera/driver/sccb.c =================================================================== ---- m5stack.orig/driver/sccb.c -+++ m5stack/driver/sccb.c +--- esp32-camera.orig/driver/sccb.c ++++ esp32-camera/driver/sccb.c @@ -46,8 +46,162 @@ const int SCCB_I2C_PORT_DEFAULT = 0; static int sccb_i2c_port; static bool sccb_owns_i2c_port; From 0c6d004ede6be44ec81ceb9c4bf0d84502b9c9b6 Mon Sep 17 00:00:00 2001 From: lbuque Date: Thu, 12 Jun 2025 12:07:41 +0800 Subject: [PATCH 098/322] github/workflows: Update Workflow. Signed-off-by: lbuque --- .github/workflows/build-release.yml | 77 +++++----- .github/workflows/code_formatting.yml | 9 +- .github/workflows/nightly-build.yml | 73 +++++----- .github/workflows/ports_m5stack.yml | 194 +++++++++++++++++++++++++- .gitlab-ci.yml | 7 +- m5stack/Makefile | 20 +-- tools/ci.sh | 21 ++- 7 files changed, 313 insertions(+), 88 deletions(-) diff --git a/.github/workflows/build-release.yml b/.github/workflows/build-release.yml index 5f98350a..94bb49b9 100644 --- a/.github/workflows/build-release.yml +++ b/.github/workflows/build-release.yml @@ -6,14 +6,23 @@ on: - '[0-9]+.[0-9]+.[0-9]+' workflow_dispatch: +permissions: + contents: read + packages: write + jobs: build: runs-on: [self-hosted, Linux, X64] steps: - uses: actions/checkout@v3.3.0 + - name: Install dependencies with apt + run: | + sudo apt-get update + sudo apt-get install git wget flex bison gperf quilt python3 python3-pip python3-venv cmake ninja-build ccache libffi-dev libssl-dev dfu-util libusb-1.0-0 -y + - name: Install packages - run: source tools/ci.sh && ci_esp32_idf522_setup + run: source tools/ci.sh && ci_esp32_idf541_setup - name: Build run: source tools/ci.sh && ci_esp32_nightly_build @@ -25,36 +34,36 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: files: | - ./m5stack/build-M5STACK_AirQ/uiflow-*-*.bin - ./m5stack/build-M5STACK_Atom_Echo/uiflow-*-*.bin - ./m5stack/build-M5STACK_Atom_Lite/uiflow-*-*.bin - ./m5stack/build-M5STACK_Atom_Matrix/uiflow-*-*.bin - ./m5stack/build-M5STACK_AtomS3/uiflow-*-*.bin - ./m5stack/build-M5STACK_AtomS3_Lite/uiflow-*-*.bin - ./m5stack/build-M5STACK_AtomS3R/uiflow-*-*.bin - ./m5stack/build-M5STACK_AtomS3R_CAM/uiflow-*-*.bin - ./m5stack/build-M5STACK_AtomS3U/uiflow-*-*.bin - ./m5stack/build-M5STACK_AtomU/uiflow-*-*.bin - ./m5stack/build-M5STACK_Basic/uiflow-*-*.bin - ./m5stack/build-M5STACK_Basic_4MB/uiflow-*-*.bin - ./m5stack/build-M5STACK_Capsule/uiflow-*-*.bin - ./m5stack/build-M5STACK_Cardputer/uiflow-*-*.bin - ./m5stack/build-M5STACK_Core2/uiflow-*-*.bin - ./m5stack/build-M5STACK_CoreInk/uiflow-*-*.bin - ./m5stack/build-M5STACK_CoreS3/uiflow-*-*.bin - ./m5stack/build-M5STACK_Dial/uiflow-*-*.bin - ./m5stack/build-M5STACK_DinMeter/uiflow-*-*.bin - ./m5stack/build-M5STACK_Fire/uiflow-*-*.bin - ./m5stack/build-M5STACK_NanoC6/uiflow-*-*.bin - ./m5stack/build-M5STACK_Paper/uiflow-*-*.bin - ./m5stack/build-M5STACK_PaperS3/uiflow-*-*.bin - ./m5stack/build-M5STACK_Stamp_PICO/uiflow-*-*.bin - ./m5stack/build-M5STACK_StamPLC/uiflow-*-*.bin - ./m5stack/build-M5STACK_StampS3/uiflow-*-*.bin - ./m5stack/build-M5STACK_Station/uiflow-*-*.bin - ./m5stack/build-M5STACK_StickC/uiflow-*-*.bin - ./m5stack/build-M5STACK_StickC_PLUS/uiflow-*-*.bin - ./m5stack/build-M5STACK_StickC_PLUS2/uiflow-*-*.bin - ./m5stack/build-M5STACK_Tough/uiflow-*-*.bin - ./third-party/build-SEEED_STUDIO_XIAO_ESP32S3/uiflow-*-*.bin - ./third-party/build-ESPRESSIF_ESP32_S3_BOX_3/uiflow-*-*.bin + $GITHUB_WORKSPACE/m5stack/build-M5STACK_AirQ/uiflow-*-*.bin + $GITHUB_WORKSPACE/m5stack/build-M5STACK_Atom_Echo/uiflow-*-*.bin + $GITHUB_WORKSPACE/m5stack/build-M5STACK_Atom_Lite/uiflow-*-*.bin + $GITHUB_WORKSPACE/m5stack/build-M5STACK_Atom_Matrix/uiflow-*-*.bin + $GITHUB_WORKSPACE/m5stack/build-M5STACK_AtomS3/uiflow-*-*.bin + $GITHUB_WORKSPACE/m5stack/build-M5STACK_AtomS3_Lite/uiflow-*-*.bin + $GITHUB_WORKSPACE/m5stack/build-M5STACK_AtomS3R/uiflow-*-*.bin + $GITHUB_WORKSPACE/m5stack/build-M5STACK_AtomS3R_CAM/uiflow-*-*.bin + $GITHUB_WORKSPACE/m5stack/build-M5STACK_AtomS3U/uiflow-*-*.bin + $GITHUB_WORKSPACE/m5stack/build-M5STACK_AtomU/uiflow-*-*.bin + $GITHUB_WORKSPACE/m5stack/build-M5STACK_Basic/uiflow-*-*.bin + $GITHUB_WORKSPACE/m5stack/build-M5STACK_Basic_4MB/uiflow-*-*.bin + $GITHUB_WORKSPACE/m5stack/build-M5STACK_Capsule/uiflow-*-*.bin + $GITHUB_WORKSPACE/m5stack/build-M5STACK_Cardputer/uiflow-*-*.bin + $GITHUB_WORKSPACE/m5stack/build-M5STACK_Core2/uiflow-*-*.bin + $GITHUB_WORKSPACE/m5stack/build-M5STACK_CoreInk/uiflow-*-*.bin + $GITHUB_WORKSPACE/m5stack/build-M5STACK_CoreS3/uiflow-*-*.bin + $GITHUB_WORKSPACE/m5stack/build-M5STACK_Dial/uiflow-*-*.bin + $GITHUB_WORKSPACE/m5stack/build-M5STACK_DinMeter/uiflow-*-*.bin + $GITHUB_WORKSPACE/m5stack/build-M5STACK_Fire/uiflow-*-*.bin + $GITHUB_WORKSPACE/m5stack/build-M5STACK_NanoC6/uiflow-*-*.bin + $GITHUB_WORKSPACE/m5stack/build-M5STACK_Paper/uiflow-*-*.bin + $GITHUB_WORKSPACE/m5stack/build-M5STACK_PaperS3/uiflow-*-*.bin + $GITHUB_WORKSPACE/m5stack/build-M5STACK_Stamp_PICO/uiflow-*-*.bin + $GITHUB_WORKSPACE/m5stack/build-M5STACK_StamPLC/uiflow-*-*.bin + $GITHUB_WORKSPACE/m5stack/build-M5STACK_StampS3/uiflow-*-*.bin + $GITHUB_WORKSPACE/m5stack/build-M5STACK_Station/uiflow-*-*.bin + $GITHUB_WORKSPACE/m5stack/build-M5STACK_StickC/uiflow-*-*.bin + $GITHUB_WORKSPACE/m5stack/build-M5STACK_StickC_PLUS/uiflow-*-*.bin + $GITHUB_WORKSPACE/m5stack/build-M5STACK_StickC_PLUS2/uiflow-*-*.bin + $GITHUB_WORKSPACE/m5stack/build-M5STACK_Tough/uiflow-*-*.bin + $GITHUB_WORKSPACE/third-party/build-SEEED_STUDIO_XIAO_ESP32S3/uiflow-*-*.bin + $GITHUB_WORKSPACE/third-party/build-ESPRESSIF_ESP32_S3_BOX_3/uiflow-*-*.bin diff --git a/.github/workflows/code_formatting.yml b/.github/workflows/code_formatting.yml index f9bd0c5c..3b445976 100644 --- a/.github/workflows/code_formatting.yml +++ b/.github/workflows/code_formatting.yml @@ -1,6 +1,13 @@ name: Check code formatting -on: [push, pull_request] +on: + push: {} + pull_request: {} + workflow_dispatch: + +permissions: + contents: read + packages: write jobs: build: diff --git a/.github/workflows/nightly-build.yml b/.github/workflows/nightly-build.yml index 7ef848bf..90b3de91 100644 --- a/.github/workflows/nightly-build.yml +++ b/.github/workflows/nightly-build.yml @@ -7,178 +7,183 @@ on: permissions: contents: read + packages: write jobs: build: runs-on: [self-hosted, Linux, X64] steps: - uses: actions/checkout@v3.3.0 + - name: Install dependencies with apt + run: | + sudo apt-get update + sudo apt-get install git wget flex bison gperf quilt python3 python3-pip python3-venv cmake ninja-build ccache libffi-dev libssl-dev dfu-util libusb-1.0-0 -y - name: Install packages - run: source tools/ci.sh && ci_esp32_idf522_setup + run: source tools/ci.sh && ci_esp32_idf541_setup - name: Build run: source tools/ci.sh && ci_esp32_nightly_build - name: Deliver AirQ firmware - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: M5STACK_AirQ_firmware path: ./m5stack/build-M5STACK_AirQ/uiflow-*-*.bin - name: Deliver Atom Echo firmware - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: M5STACK_Atom_Echo_firmware path: ./m5stack/build-M5STACK_Atom_Echo/uiflow-*-*.bin - name: Deliver Atom Lite firmware - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: M5STACK_Atom_Lite_firmware path: ./m5stack/build-M5STACK_Atom_Lite/uiflow-*-*.bin - name: Deliver Atom Matrix firmware - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: M5STACK_Atom_Matrix_firmware path: ./m5stack/build-M5STACK_Atom_Matrix/uiflow-*-*.bin - name: Deliver AtomS3 firmware - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: M5STACK_AtomS3_firmware path: ./m5stack/build-M5STACK_AtomS3/uiflow-*-*.bin - name: Deliver AtomS3-Lite firmware - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: M5STACK_AtomS3-Lite_firmware path: ./m5stack/build-M5STACK_AtomS3_Lite/uiflow-*-*.bin - name: Deliver AtomS3R firmware - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: M5STACK_AtomS3R_firmware path: ./m5stack/build-M5STACK_AtomS3R/uiflow-*-*.bin - name: Deliver AtomS3R-CAM firmware - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: M5STACK_AtomS3R_CAM_firmware path: ./m5stack/build-M5STACK_AtomS3R_CAM/uiflow-*-*.bin - name: Deliver AtomS3U firmware - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: M5STACK_AtomS3U_firmware path: ./m5stack/build-M5STACK_AtomS3U/uiflow-*-*.bin - name: Deliver AtomU firmware - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: M5STACK_AtomU_firmware path: ./m5stack/build-M5STACK_AtomU/uiflow-*-*.bin - name: Deliver Basic firmware - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: M5STACK_Basic_firmware path: ./m5stack/build-M5STACK_Basic/uiflow-*-*.bin - name: Deliver Basic(4MB Flash) firmware - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: M5STACK_Basic_4MB_Flash_firmware path: ./m5stack/build-M5STACK_Basic_4MB/uiflow-*-*.bin - name: Deliver Capsule firmware - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: M5STACK_Capsule_firmware path: ./m5stack/build-M5STACK_Capsule/uiflow-*-*.bin - name: Deliver Cardputer firmware - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: M5STACK_Cardputer_firmware path: ./m5stack/build-M5STACK_Cardputer/uiflow-*-*.bin - name: Deliver Core2 firmware - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: M5STACK_Core2_firmware path: ./m5stack/build-M5STACK_Core2/uiflow-*-*.bin - name: Deliver CoreInk firmware - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: M5STACK_CoreInk_firmware path: ./m5stack/build-M5STACK_CoreInk/uiflow-*-*.bin - name: Deliver CoreS3 firmware - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: M5STACK_CoreS3_firmware path: ./m5stack/build-M5STACK_CoreS3/uiflow-*-*.bin - name: Deliver Dial firmware - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: M5STACK_Dial_firmware path: ./m5stack/build-M5STACK_Dial/uiflow-*-*.bin - name: Deliver DinMeter firmware - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: M5STACK_DinMeter_firmware path: ./m5stack/build-M5STACK_DinMeter/uiflow-*-*.bin - name: Deliver Fire firmware - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: M5STACK_Fire_firmware path: ./m5stack/build-M5STACK_Fire/uiflow-*-*.bin - name: Deliver NanoC6 firmware - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: M5STACK_NanoC6_firmware path: ./m5stack/build-M5STACK_NanoC6/uiflow-*-*.bin - name: Deliver Paper firmware - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: M5STACK_Paper_firmware path: ./m5stack/build-M5STACK_Paper/uiflow-*-*.bin - name: Deliver PaperS3 firmware - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: M5STACK_Paper_firmware path: ./m5stack/build-M5STACK_PaperS3/uiflow-*-*.bin - name: Deliver Stamp PICO firmware - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: M5STACK_Stamp_PICO_firmware path: ./m5stack/build-M5STACK_Stamp_PICO/uiflow-*-*.bin - name: Deliver StamPLC firmware - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: M5STACK_StamPLC_firmware path: ./m5stack/build-M5STACK_StamPLC/uiflow-*-*.bin - name: Deliver StampS3 firmware - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: M5STACK_StampS3_firmware path: ./m5stack/build-M5STACK_StampS3/uiflow-*-*.bin - name: Deliver Station firmware - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: M5STACK_Station_firmware path: ./m5stack/build-M5STACK_Station/uiflow-*-*.bin - name: Deliver StickC firmware - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: M5STACK_StickC_firmware path: ./m5stack/build-M5STACK_StickC/uiflow-*-*.bin - name: Deliver StickC_PLUS firmware - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: M5STACK_StickC_PLUS_firmware path: ./m5stack/build-M5STACK_StickC_PLUS/uiflow-*-*.bin - name: Deliver StickC_PLUS2 firmware - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: M5STACK_StickC_PLUS2_firmware path: ./m5stack/build-M5STACK_StickC_PLUS2/uiflow-*-*.bin - name: Deliver Tough firmware - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: M5STACK_Tough_firmware path: ./m5stack/build-M5STACK_Tough/uiflow-*-*.bin - name: Deliver XIAOS3 firmware - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: M5STACK_XIAOS3_firmware path: ./third-party/build-SEEED_STUDIO_XIAO_ESP32S3/uiflow-*-*.bin - name: Deliver BOX-3 firmware - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: M5STACK_BOX_3_firmware path: ./third-party/build-ESPRESSIF_ESP32_S3_BOX_3/uiflow-*-*.bin diff --git a/.github/workflows/ports_m5stack.yml b/.github/workflows/ports_m5stack.yml index 1900b530..4ff3b0cc 100644 --- a/.github/workflows/ports_m5stack.yml +++ b/.github/workflows/ports_m5stack.yml @@ -1,7 +1,7 @@ name: m5stack port on: - push: + push: {} pull_request: paths: - '.github/workflows/*.yml' @@ -11,13 +11,199 @@ on: - 'micropython/lib/**' - 'micropython/drivers/**' - 'm5stack/**' + - "third-party/**" + workflow_dispatch: {} jobs: - build_idf44: + build: runs-on: [self-hosted, Linux, X64] steps: - uses: actions/checkout@v3.3.0 + - name: Install dependencies with apt + run: | + sudo apt-get update + sudo apt-get install git wget flex bison gperf quilt python3 python3-pip python3-venv cmake ninja-build ccache libffi-dev libssl-dev dfu-util libusb-1.0-0 -y + - name: Cache esp-idf + uses: actions/cache@v4 + id: cache-esp-idf + with: + path: | + ~/.espressif + ${{ github.workspace }}/esp-idf + key: ${{ runner.os }}-idf-${{ hashFiles('idf_version.txt') }} + - name: Install ESP-IDF + if: steps.cache-idf.outputs.cache-hit != 'true' + run: | + git clone --depth=1 -b $IDF_VERSION https://github.com/espressif/esp-idf.git + ./esp-idf/install.sh + env: + IDF_VERSION: "v5.4.1" - name: Install packages - run: source tools/ci.sh && ci_esp32_idf504_setup + run: source tools/ci.sh && ci_esp32_idf541_setup - name: Build - run: source tools/ci.sh && ci_esp32_build + run: source tools/ci.sh && ci_esp32_nightly_build + - name: Deliver AirQ firmware + uses: actions/upload-artifact@v4 + with: + name: M5STACK_AirQ_firmware + path: $GITHUB_WORKSPACE/m5stack/build-M5STACK_AirQ/uiflow-*-*.bin + - name: Deliver Atom Echo firmware + uses: actions/upload-artifact@v4 + with: + name: M5STACK_Atom_Echo_firmware + path: $GITHUB_WORKSPACE/m5stack/build-M5STACK_Atom_Echo/uiflow-*-*.bin + - name: Deliver Atom Lite firmware + uses: actions/upload-artifact@v4 + with: + name: M5STACK_Atom_Lite_firmware + path: $GITHUB_WORKSPACE/m5stack/build-M5STACK_Atom_Lite/uiflow-*-*.bin + - name: Deliver Atom Matrix firmware + uses: actions/upload-artifact@v4 + with: + name: M5STACK_Atom_Matrix_firmware + path: $GITHUB_WORKSPACE/m5stack/build-M5STACK_Atom_Matrix/uiflow-*-*.bin + - name: Deliver AtomS3 firmware + uses: actions/upload-artifact@v4 + with: + name: M5STACK_AtomS3_firmware + path: $GITHUB_WORKSPACE/m5stack/build-M5STACK_AtomS3/uiflow-*-*.bin + - name: Deliver AtomS3-Lite firmware + uses: actions/upload-artifact@v4 + with: + name: M5STACK_AtomS3-Lite_firmware + path: $GITHUB_WORKSPACE/m5stack/build-M5STACK_AtomS3_Lite/uiflow-*-*.bin + - name: Deliver AtomS3R firmware + uses: actions/upload-artifact@v4 + with: + name: M5STACK_AtomS3R_firmware + path: $GITHUB_WORKSPACE/m5stack/build-M5STACK_AtomS3R/uiflow-*-*.bin + - name: Deliver AtomS3R-CAM firmware + uses: actions/upload-artifact@v4 + with: + name: M5STACK_AtomS3R_CAM_firmware + path: $GITHUB_WORKSPACE/m5stack/build-M5STACK_AtomS3R_CAM/uiflow-*-*.bin + - name: Deliver AtomS3U firmware + uses: actions/upload-artifact@v4 + with: + name: M5STACK_AtomS3U_firmware + path: $GITHUB_WORKSPACE/m5stack/build-M5STACK_AtomS3U/uiflow-*-*.bin + - name: Deliver AtomU firmware + uses: actions/upload-artifact@v4 + with: + name: M5STACK_AtomU_firmware + path: $GITHUB_WORKSPACE/m5stack/build-M5STACK_AtomU/uiflow-*-*.bin + - name: Deliver Basic firmware + uses: actions/upload-artifact@v4 + with: + name: M5STACK_Basic_firmware + path: $GITHUB_WORKSPACE/m5stack/build-M5STACK_Basic/uiflow-*-*.bin + - name: Deliver Basic(4MB Flash) firmware + uses: actions/upload-artifact@v4 + with: + name: M5STACK_Basic_4MB_Flash_firmware + path: $GITHUB_WORKSPACE/m5stack/build-M5STACK_Basic_4MB/uiflow-*-*.bin + - name: Deliver Capsule firmware + uses: actions/upload-artifact@v4 + with: + name: M5STACK_Capsule_firmware + path: $GITHUB_WORKSPACE/m5stack/build-M5STACK_Capsule/uiflow-*-*.bin + - name: Deliver Cardputer firmware + uses: actions/upload-artifact@v4 + with: + name: M5STACK_Cardputer_firmware + path: $GITHUB_WORKSPACE/m5stack/build-M5STACK_Cardputer/uiflow-*-*.bin + - name: Deliver Core2 firmware + uses: actions/upload-artifact@v4 + with: + name: M5STACK_Core2_firmware + path: $GITHUB_WORKSPACE/m5stack/build-M5STACK_Core2/uiflow-*-*.bin + - name: Deliver CoreInk firmware + uses: actions/upload-artifact@v4 + with: + name: M5STACK_CoreInk_firmware + path: $GITHUB_WORKSPACE/m5stack/build-M5STACK_CoreInk/uiflow-*-*.bin + - name: Deliver CoreS3 firmware + uses: actions/upload-artifact@v4 + with: + name: M5STACK_CoreS3_firmware + path: $GITHUB_WORKSPACE/m5stack/build-M5STACK_CoreS3/uiflow-*-*.bin + - name: Deliver Dial firmware + uses: actions/upload-artifact@v4 + with: + name: M5STACK_Dial_firmware + path: $GITHUB_WORKSPACE/m5stack/build-M5STACK_Dial/uiflow-*-*.bin + - name: Deliver DinMeter firmware + uses: actions/upload-artifact@v4 + with: + name: M5STACK_DinMeter_firmware + path: $GITHUB_WORKSPACE/m5stack/build-M5STACK_DinMeter/uiflow-*-*.bin + - name: Deliver Fire firmware + uses: actions/upload-artifact@v4 + with: + name: M5STACK_Fire_firmware + path: $GITHUB_WORKSPACE/m5stack/build-M5STACK_Fire/uiflow-*-*.bin + - name: Deliver NanoC6 firmware + uses: actions/upload-artifact@v4 + with: + name: M5STACK_NanoC6_firmware + path: $GITHUB_WORKSPACE/m5stack/build-M5STACK_NanoC6/uiflow-*-*.bin + - name: Deliver Paper firmware + uses: actions/upload-artifact@v4 + with: + name: M5STACK_Paper_firmware + path: $GITHUB_WORKSPACE/m5stack/build-M5STACK_Paper/uiflow-*-*.bin + - name: Deliver PaperS3 firmware + uses: actions/upload-artifact@v4 + with: + name: M5STACK_Paper_firmware + path: $GITHUB_WORKSPACE/m5stack/build-M5STACK_PaperS3/uiflow-*-*.bin + - name: Deliver Stamp PICO firmware + uses: actions/upload-artifact@v4 + with: + name: M5STACK_Stamp_PICO_firmware + path: $GITHUB_WORKSPACE/m5stack/build-M5STACK_Stamp_PICO/uiflow-*-*.bin + - name: Deliver StamPLC firmware + uses: actions/upload-artifact@v4 + with: + name: M5STACK_StamPLC_firmware + path: $GITHUB_WORKSPACE/m5stack/build-M5STACK_StamPLC/uiflow-*-*.bin + - name: Deliver StampS3 firmware + uses: actions/upload-artifact@v4 + with: + name: M5STACK_StampS3_firmware + path: $GITHUB_WORKSPACE/m5stack/build-M5STACK_StampS3/uiflow-*-*.bin + - name: Deliver Station firmware + uses: actions/upload-artifact@v4 + with: + name: M5STACK_Station_firmware + path: $GITHUB_WORKSPACE/m5stack/build-M5STACK_Station/uiflow-*-*.bin + - name: Deliver StickC firmware + uses: actions/upload-artifact@v4 + with: + name: M5STACK_StickC_firmware + path: $GITHUB_WORKSPACE/m5stack/build-M5STACK_StickC/uiflow-*-*.bin + - name: Deliver StickC_PLUS firmware + uses: actions/upload-artifact@v4 + with: + name: M5STACK_StickC_PLUS_firmware + path: $GITHUB_WORKSPACE/m5stack/build-M5STACK_StickC_PLUS/uiflow-*-*.bin + - name: Deliver StickC_PLUS2 firmware + uses: actions/upload-artifact@v4 + with: + name: M5STACK_StickC_PLUS2_firmware + path: $GITHUB_WORKSPACE/m5stack/build-M5STACK_StickC_PLUS2/uiflow-*-*.bin + - name: Deliver Tough firmware + uses: actions/upload-artifact@v4 + with: + name: M5STACK_Tough_firmware + path: $GITHUB_WORKSPACE/m5stack/build-M5STACK_Tough/uiflow-*-*.bin + - name: Deliver XIAOS3 firmware + uses: actions/upload-artifact@v4 + with: + name: M5STACK_XIAOS3_firmware + path: $GITHUB_WORKSPACE/third-party/build-SEEED_STUDIO_XIAO_ESP32S3/uiflow-*-*.bin + - name: Deliver BOX-3 firmware + uses: actions/upload-artifact@v4 + with: + name: M5STACK_BOX_3_firmware + path: $GITHUB_WORKSPACE/third-party/build-ESPRESSIF_ESP32_S3_BOX_3/uiflow-*-*.bin diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index f01f0954..078952b5 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -23,6 +23,7 @@ code-format: script: - source tools/ci.sh && ci_code_formatting_setup - source tools/ci.sh && ci_code_formatting_run + - git diff --exit-code tags: - uiflow-firmware only: @@ -36,8 +37,8 @@ build-quick-job: stage: build_quick script: - sudo apt-get update -qy - - sudo apt-get install git wget flex bison gperf python3 python3-pip python3-venv cmake ninja-build ccache libffi-dev libssl-dev dfu-util libusb-1.0-0 -y - - source tools/ci.sh && ci_esp32_idf522_setup + - sudo apt-get install git wget flex bison gperf quilt python3 python3-pip python3-venv cmake ninja-build ccache libffi-dev libssl-dev dfu-util libusb-1.0-0 -y + - source tools/ci.sh && ci_esp32_idf541_setup - source tools/ci.sh && ci_esp32_quick_build artifacts: paths: @@ -57,7 +58,7 @@ build-job: script: - sudo apt-get update -qy - sudo apt-get install git wget flex bison gperf python3 python3-pip python3-venv cmake ninja-build ccache libffi-dev libssl-dev dfu-util libusb-1.0-0 -y - - source tools/ci.sh && ci_esp32_idf522_setup + - source tools/ci.sh && ci_esp32_idf541_setup - source tools/ci.sh && ci_esp32_nightly_build artifacts: paths: diff --git a/m5stack/Makefile b/m5stack/Makefile index 4b31dbc8..cdfe4aa6 100644 --- a/m5stack/Makefile +++ b/m5stack/Makefile @@ -275,17 +275,17 @@ mpy-cross: # output and passes the list of submodules to py/mkrules.mk which does the # `git submodule init` on each. submodules: - git submodule update --init ../tools/littlefs/mbed-littlefs - git submodule update --init ./components/esp32-camera - git submodule update --init ./components/esp_dl - git submodule update --init ./components/esp-code-scanner - git submodule update --init ./components/esp_zigbee_host - git submodule update --init ./components/M5Unified/M5GFX - git submodule update --init ./components/M5Unified/M5Unified + rm -rf ../tools/littlefs/mbed-littlefs && git submodule update --init ../tools/littlefs/mbed-littlefs + rm -rf ./components/esp32-camera && git submodule update --init ./components/esp32-camera + rm -rf ./components/esp_dl && git submodule update --init ./components/esp_dl + rm -rf ./components/esp-code-scanner && git submodule update --init ./components/esp-code-scanner + rm -rf ./components/esp_zigbee_host && git submodule update --init ./components/esp_zigbee_host + rm -rf ./components/M5Unified/M5GFX && git submodule update --init ./components/M5Unified/M5GFX + rm -rf ./components/M5Unified/M5Unified && git submodule update --init ./components/M5Unified/M5Unified #git submodule update --init --recursive ./components/lv_bindings - git submodule update --init --recursive ./cmodules/lv_binding_micropython - git submodule update --init ../micropython - git submodule update --init ../esp-adf + rm -rf ./cmodules/lv_binding_micropython && git submodule update --init --recursive ./cmodules/lv_binding_micropython + rm -rf ../micropython && git submodule update --init ../micropython + rm -rf ../esp-adf && git submodule update --init ../esp-adf cd ../esp-adf && \ git submodule update --init components/esp-adf-libs && \ git submodule update --init components/esp-sr && \ diff --git a/tools/ci.sh b/tools/ci.sh index 10d46c60..e6f8cbd0 100644 --- a/tools/ci.sh +++ b/tools/ci.sh @@ -16,8 +16,8 @@ fi function ci_code_formatting_setup { sudo apt-add-repository --yes ppa:pybricks/ppa sudo apt update - sudo apt-get install uncrustify - sudo apt install pipx + sudo apt-get install uncrustify -y + sudo apt install pipx -y pipx install uv uv venv source .venv/bin/activate @@ -160,6 +160,23 @@ function ci_esp32_idf522_setup { ./esp-idf/install.sh } +function ci_esp32_idf541_setup { + if [ -d esp-idf ]; then + echo "esp-idf is already cloned." + if [ "$(git -C esp-idf rev-parse --abbrev-ref HEAD)" == "v5.4.1" ]; then + echo "esp-idf is on v5.4.1 branch." + return 0 + else + echo "esp-idf is not on v5.4.1 branch." + rm -rf esp-idf + fi + fi + + git clone --depth 1 --branch v5.4.1 https://github.com/espressif/esp-idf.git + git -C esp-idf submodule update --init + ./esp-idf/install.sh +} + function ci_esp32_build { source esp-idf/export.sh make ${MAKEOPTS} -C m5stack submodules From ab8e711c854484fcedd0d1b43abf25ac7154c1ce Mon Sep 17 00:00:00 2001 From: lbuque Date: Thu, 12 Jun 2025 16:46:09 +0800 Subject: [PATCH 099/322] boards.cpp: Fix i2c port error. Signed-off-by: lbuque --- m5stack/board.cpp | 2 +- m5stack/components/M5Unified/mpy_m5unified.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/m5stack/board.cpp b/m5stack/board.cpp index dc0ab1f8..53b87b2c 100644 --- a/m5stack/board.cpp +++ b/m5stack/board.cpp @@ -18,7 +18,7 @@ extern "C" { gpio_num_t ex_scl = (gpio_num_t)M5.getPin(m5::pin_name_t::ex_i2c_scl); gpio_num_t ex_sda = (gpio_num_t)M5.getPin(m5::pin_name_t::ex_i2c_sda); i2c_port_t ex_port = I2C_NUM_0; -#if SOC_I2C_NUM == 1 +#if SOC_I2C_NUM == 1 || defined(CONFIG_IDF_TARGET_ESP32C6) i2c_port_t in_port = I2C_NUM_0; #else i2c_port_t in_port = I2C_NUM_1; diff --git a/m5stack/components/M5Unified/mpy_m5unified.cpp b/m5stack/components/M5Unified/mpy_m5unified.cpp index 4ad633b7..02d64887 100644 --- a/m5stack/components/M5Unified/mpy_m5unified.cpp +++ b/m5stack/components/M5Unified/mpy_m5unified.cpp @@ -421,7 +421,7 @@ static void in_i2c_init(void) { gpio_num_t ex_scl = (gpio_num_t)M5.getPin(m5::pin_name_t::ex_i2c_scl); gpio_num_t ex_sda = (gpio_num_t)M5.getPin(m5::pin_name_t::ex_i2c_sda); i2c_port_t ex_port = I2C_NUM_0; - #if SOC_I2C_NUM == 1 + #if SOC_I2C_NUM == 1 || defined(CONFIG_IDF_TARGET_ESP32C6) i2c_port_t in_port = I2C_NUM_0; #else i2c_port_t in_port = I2C_NUM_1; From 082a0e89bec69ac0610db64475375c129322dfcd Mon Sep 17 00:00:00 2001 From: lbuque Date: Thu, 12 Jun 2025 17:20:36 +0800 Subject: [PATCH 100/322] github/workflows: Compile firmware in parallel using a matrix strategy. Signed-off-by: lbuque --- .github/workflows/ports_m5stack.yml | 300 ++++++++++------------------ 1 file changed, 110 insertions(+), 190 deletions(-) diff --git a/.github/workflows/ports_m5stack.yml b/.github/workflows/ports_m5stack.yml index 4ff3b0cc..a60acfbf 100644 --- a/.github/workflows/ports_m5stack.yml +++ b/.github/workflows/ports_m5stack.yml @@ -15,195 +15,115 @@ on: workflow_dispatch: {} jobs: + setup: + runs-on: ubuntu-latest + outputs: + cache-hit: ${{ steps.cache-esp-idf.outputs.cache-hit }} + steps: + - uses: actions/checkout@v4 + + - name: Install dependencies + run: | + sudo apt-get update + sudo apt-get install -y git wget flex bison gperf quilt python3 python3-pip \ + python3-venv cmake ninja-build ccache libffi-dev libssl-dev dfu-util libusb-1.0-0 + + - name: Cache esp-idf + uses: actions/cache@v4 + id: cache-esp-idf + with: + path: | + ~/.espressif + ${{ github.workspace }}/esp-idf + key: ${{ runner.os }}-idf-v5.4.1 + + - name: Install ESP-IDF + if: steps.cache-esp-idf.outputs.cache-hit != 'true' + run: | + git clone --depth=1 -b $IDF_VERSION https://github.com/espressif/esp-idf.git + ./esp-idf/install.sh + env: + IDF_VERSION: "v5.4.1" + + - name: Setup environment + run: | + source tools/ci.sh && ci_esp32_idf541_setup + source esp-idf/export.sh + pip install future + make -C m5stack submodules + make -C m5stack patch + make -C m5stack littlefs + build: - runs-on: [self-hosted, Linux, X64] + needs: setup + runs-on: ubuntu-latest + strategy: + matrix: + board: + - M5STACK_AirQ + - M5STACK_Atom_Echo + - M5STACK_Atom_Lite + - M5STACK_Atom_Matrix + - M5STACK_AtomS3 + - M5STACK_AtomS3_Lite + - M5STACK_AtomS3R + - M5STACK_AtomS3R_CAM + - M5STACK_AtomS3U + - M5STACK_AtomU + - M5STACK_Basic + - M5STACK_Basic_4MB + - M5STACK_Capsule + - M5STACK_Cardputer + - M5STACK_Core2 + - M5STACK_CoreInk + - M5STACK_CoreS3 + - M5STACK_Dial + - M5STACK_DinMeter + - M5STACK_Fire + - M5STACK_NanoC6 + - M5STACK_Paper + - M5STACK_PaperS3 + - M5STACK_Stamp_PICO + - M5STACK_StamPLC + - M5STACK_StampS3 + - M5STACK_Station + - M5STACK_StickC + - M5STACK_StickC_PLUS + - M5STACK_StickC_PLUS2 + - M5STACK_Tough + max-parallel: 3 steps: - - uses: actions/checkout@v3.3.0 - - name: Install dependencies with apt - run: | - sudo apt-get update - sudo apt-get install git wget flex bison gperf quilt python3 python3-pip python3-venv cmake ninja-build ccache libffi-dev libssl-dev dfu-util libusb-1.0-0 -y - - name: Cache esp-idf - uses: actions/cache@v4 - id: cache-esp-idf - with: - path: | - ~/.espressif - ${{ github.workspace }}/esp-idf - key: ${{ runner.os }}-idf-${{ hashFiles('idf_version.txt') }} - - name: Install ESP-IDF - if: steps.cache-idf.outputs.cache-hit != 'true' - run: | - git clone --depth=1 -b $IDF_VERSION https://github.com/espressif/esp-idf.git - ./esp-idf/install.sh - env: - IDF_VERSION: "v5.4.1" - - name: Install packages - run: source tools/ci.sh && ci_esp32_idf541_setup - - name: Build - run: source tools/ci.sh && ci_esp32_nightly_build - - name: Deliver AirQ firmware - uses: actions/upload-artifact@v4 - with: - name: M5STACK_AirQ_firmware - path: $GITHUB_WORKSPACE/m5stack/build-M5STACK_AirQ/uiflow-*-*.bin - - name: Deliver Atom Echo firmware - uses: actions/upload-artifact@v4 - with: - name: M5STACK_Atom_Echo_firmware - path: $GITHUB_WORKSPACE/m5stack/build-M5STACK_Atom_Echo/uiflow-*-*.bin - - name: Deliver Atom Lite firmware - uses: actions/upload-artifact@v4 - with: - name: M5STACK_Atom_Lite_firmware - path: $GITHUB_WORKSPACE/m5stack/build-M5STACK_Atom_Lite/uiflow-*-*.bin - - name: Deliver Atom Matrix firmware - uses: actions/upload-artifact@v4 - with: - name: M5STACK_Atom_Matrix_firmware - path: $GITHUB_WORKSPACE/m5stack/build-M5STACK_Atom_Matrix/uiflow-*-*.bin - - name: Deliver AtomS3 firmware - uses: actions/upload-artifact@v4 - with: - name: M5STACK_AtomS3_firmware - path: $GITHUB_WORKSPACE/m5stack/build-M5STACK_AtomS3/uiflow-*-*.bin - - name: Deliver AtomS3-Lite firmware - uses: actions/upload-artifact@v4 - with: - name: M5STACK_AtomS3-Lite_firmware - path: $GITHUB_WORKSPACE/m5stack/build-M5STACK_AtomS3_Lite/uiflow-*-*.bin - - name: Deliver AtomS3R firmware - uses: actions/upload-artifact@v4 - with: - name: M5STACK_AtomS3R_firmware - path: $GITHUB_WORKSPACE/m5stack/build-M5STACK_AtomS3R/uiflow-*-*.bin - - name: Deliver AtomS3R-CAM firmware - uses: actions/upload-artifact@v4 - with: - name: M5STACK_AtomS3R_CAM_firmware - path: $GITHUB_WORKSPACE/m5stack/build-M5STACK_AtomS3R_CAM/uiflow-*-*.bin - - name: Deliver AtomS3U firmware - uses: actions/upload-artifact@v4 - with: - name: M5STACK_AtomS3U_firmware - path: $GITHUB_WORKSPACE/m5stack/build-M5STACK_AtomS3U/uiflow-*-*.bin - - name: Deliver AtomU firmware - uses: actions/upload-artifact@v4 - with: - name: M5STACK_AtomU_firmware - path: $GITHUB_WORKSPACE/m5stack/build-M5STACK_AtomU/uiflow-*-*.bin - - name: Deliver Basic firmware - uses: actions/upload-artifact@v4 - with: - name: M5STACK_Basic_firmware - path: $GITHUB_WORKSPACE/m5stack/build-M5STACK_Basic/uiflow-*-*.bin - - name: Deliver Basic(4MB Flash) firmware - uses: actions/upload-artifact@v4 - with: - name: M5STACK_Basic_4MB_Flash_firmware - path: $GITHUB_WORKSPACE/m5stack/build-M5STACK_Basic_4MB/uiflow-*-*.bin - - name: Deliver Capsule firmware - uses: actions/upload-artifact@v4 - with: - name: M5STACK_Capsule_firmware - path: $GITHUB_WORKSPACE/m5stack/build-M5STACK_Capsule/uiflow-*-*.bin - - name: Deliver Cardputer firmware - uses: actions/upload-artifact@v4 - with: - name: M5STACK_Cardputer_firmware - path: $GITHUB_WORKSPACE/m5stack/build-M5STACK_Cardputer/uiflow-*-*.bin - - name: Deliver Core2 firmware - uses: actions/upload-artifact@v4 - with: - name: M5STACK_Core2_firmware - path: $GITHUB_WORKSPACE/m5stack/build-M5STACK_Core2/uiflow-*-*.bin - - name: Deliver CoreInk firmware - uses: actions/upload-artifact@v4 - with: - name: M5STACK_CoreInk_firmware - path: $GITHUB_WORKSPACE/m5stack/build-M5STACK_CoreInk/uiflow-*-*.bin - - name: Deliver CoreS3 firmware - uses: actions/upload-artifact@v4 - with: - name: M5STACK_CoreS3_firmware - path: $GITHUB_WORKSPACE/m5stack/build-M5STACK_CoreS3/uiflow-*-*.bin - - name: Deliver Dial firmware - uses: actions/upload-artifact@v4 - with: - name: M5STACK_Dial_firmware - path: $GITHUB_WORKSPACE/m5stack/build-M5STACK_Dial/uiflow-*-*.bin - - name: Deliver DinMeter firmware - uses: actions/upload-artifact@v4 - with: - name: M5STACK_DinMeter_firmware - path: $GITHUB_WORKSPACE/m5stack/build-M5STACK_DinMeter/uiflow-*-*.bin - - name: Deliver Fire firmware - uses: actions/upload-artifact@v4 - with: - name: M5STACK_Fire_firmware - path: $GITHUB_WORKSPACE/m5stack/build-M5STACK_Fire/uiflow-*-*.bin - - name: Deliver NanoC6 firmware - uses: actions/upload-artifact@v4 - with: - name: M5STACK_NanoC6_firmware - path: $GITHUB_WORKSPACE/m5stack/build-M5STACK_NanoC6/uiflow-*-*.bin - - name: Deliver Paper firmware - uses: actions/upload-artifact@v4 - with: - name: M5STACK_Paper_firmware - path: $GITHUB_WORKSPACE/m5stack/build-M5STACK_Paper/uiflow-*-*.bin - - name: Deliver PaperS3 firmware - uses: actions/upload-artifact@v4 - with: - name: M5STACK_Paper_firmware - path: $GITHUB_WORKSPACE/m5stack/build-M5STACK_PaperS3/uiflow-*-*.bin - - name: Deliver Stamp PICO firmware - uses: actions/upload-artifact@v4 - with: - name: M5STACK_Stamp_PICO_firmware - path: $GITHUB_WORKSPACE/m5stack/build-M5STACK_Stamp_PICO/uiflow-*-*.bin - - name: Deliver StamPLC firmware - uses: actions/upload-artifact@v4 - with: - name: M5STACK_StamPLC_firmware - path: $GITHUB_WORKSPACE/m5stack/build-M5STACK_StamPLC/uiflow-*-*.bin - - name: Deliver StampS3 firmware - uses: actions/upload-artifact@v4 - with: - name: M5STACK_StampS3_firmware - path: $GITHUB_WORKSPACE/m5stack/build-M5STACK_StampS3/uiflow-*-*.bin - - name: Deliver Station firmware - uses: actions/upload-artifact@v4 - with: - name: M5STACK_Station_firmware - path: $GITHUB_WORKSPACE/m5stack/build-M5STACK_Station/uiflow-*-*.bin - - name: Deliver StickC firmware - uses: actions/upload-artifact@v4 - with: - name: M5STACK_StickC_firmware - path: $GITHUB_WORKSPACE/m5stack/build-M5STACK_StickC/uiflow-*-*.bin - - name: Deliver StickC_PLUS firmware - uses: actions/upload-artifact@v4 - with: - name: M5STACK_StickC_PLUS_firmware - path: $GITHUB_WORKSPACE/m5stack/build-M5STACK_StickC_PLUS/uiflow-*-*.bin - - name: Deliver StickC_PLUS2 firmware - uses: actions/upload-artifact@v4 - with: - name: M5STACK_StickC_PLUS2_firmware - path: $GITHUB_WORKSPACE/m5stack/build-M5STACK_StickC_PLUS2/uiflow-*-*.bin - - name: Deliver Tough firmware - uses: actions/upload-artifact@v4 - with: - name: M5STACK_Tough_firmware - path: $GITHUB_WORKSPACE/m5stack/build-M5STACK_Tough/uiflow-*-*.bin - - name: Deliver XIAOS3 firmware - uses: actions/upload-artifact@v4 - with: - name: M5STACK_XIAOS3_firmware - path: $GITHUB_WORKSPACE/third-party/build-SEEED_STUDIO_XIAO_ESP32S3/uiflow-*-*.bin - - name: Deliver BOX-3 firmware - uses: actions/upload-artifact@v4 - with: - name: M5STACK_BOX_3_firmware - path: $GITHUB_WORKSPACE/third-party/build-ESPRESSIF_ESP32_S3_BOX_3/uiflow-*-*.bin + - uses: actions/checkout@v4 + + - name: Install dependencies + run: | + sudo apt-get update + sudo apt-get install -y git wget flex bison gperf quilt python3 python3-pip \ + python3-venv cmake ninja-build ccache libffi-dev libssl-dev dfu-util libusb-1.0-0 + + - name: Restore ESP-IDF cache + uses: actions/cache@v4 + with: + path: | + ~/.espressif + ${{ github.workspace }}/esp-idf + key: ${{ runner.os }}-idf-v5.4.1 + + - name: Prepare environment + run: | + source esp-idf/export.sh + + - name: Build ${{ matrix.board }} + run: | + source esp-idf/export.sh + pip install future + make -C m5stack submodules + make -C m5stack patch + make -C m5stack littlefs + make -C m5stack BOARD=${{ matrix.board }} pack_all + + - name: Upload firmware artifact + uses: actions/upload-artifact@v4 + with: + name: firmware-${{ matrix.board }} + path: m5stack/build-${{ matrix.board }}/uiflow-*-*.bin From 0161aba7fcab32e24a6b23eafd98f56b6ed640f3 Mon Sep 17 00:00:00 2001 From: lbuque Date: Wed, 4 Jun 2025 15:54:31 +0800 Subject: [PATCH 101/322] components/M5Unified: Fix the problem of wrong touch value. Signed-off-by: lbuque --- m5stack/Makefile | 9 +-------- m5stack/cmodules/lv_utils/modlv_utils.c | 3 +++ m5stack/components/M5Unified/M5GFX | 2 +- m5stack/components/M5Unified/M5Unified | 2 +- m5stack/components/M5Unified/mpy_lvgl.txt | 2 +- m5stack/components/M5Unified/mpy_m5touch.cpp | 4 ++-- 6 files changed, 9 insertions(+), 13 deletions(-) diff --git a/m5stack/Makefile b/m5stack/Makefile index cdfe4aa6..afee5df0 100644 --- a/m5stack/Makefile +++ b/m5stack/Makefile @@ -109,14 +109,7 @@ else TINY_FLAG ?= 0 endif -# esp32c3's bootloader is different with esp32 -ifeq (C3, $(findstring C3,${BOARD})) - CHIP ?= esp32c3 -else ifeq (S3, $(findstring S3,${BOARD})) - CHIP ?= esp32s3 -else - CHIP ?= esp32 -endif +CHIP ?= auto # If the build directory is not given, make it reflect the board name. BUILD ?= build-$(BOARD) diff --git a/m5stack/cmodules/lv_utils/modlv_utils.c b/m5stack/cmodules/lv_utils/modlv_utils.c index 9fc06774..17566d44 100644 --- a/m5stack/cmodules/lv_utils/modlv_utils.c +++ b/m5stack/cmodules/lv_utils/modlv_utils.c @@ -59,6 +59,9 @@ static void *lv_utils_fs_open_cb(lv_fs_drv_t *drv, const char *path, lv_fs_mode_ if (strstr(path, "flash")) { ESP_LOGD(TAG, "in flash"); vfs->lfs2 = &((mp_obj_vfs_lfs2_t *)MP_OBJ_TO_PTR(existing_mount->obj))->lfs; + } else if (strstr(path, "system")) { + ESP_LOGD(TAG, "in system"); + vfs->lfs2 = &((mp_obj_vfs_lfs2_t *)MP_OBJ_TO_PTR(existing_mount->obj))->lfs; } else if (strstr(path, "sd")) { ESP_LOGD(TAG, "in sd"); vfs->fatfs = &((fs_user_mount_t *)MP_OBJ_TO_PTR(existing_mount->obj))->fatfs; diff --git a/m5stack/components/M5Unified/M5GFX b/m5stack/components/M5Unified/M5GFX index 801217f3..30f92321 160000 --- a/m5stack/components/M5Unified/M5GFX +++ b/m5stack/components/M5Unified/M5GFX @@ -1 +1 @@ -Subproject commit 801217f35569dd83a344937cbe182ee912c5841d +Subproject commit 30f9232182bf4032ff1f412b73a18f66860d4404 diff --git a/m5stack/components/M5Unified/M5Unified b/m5stack/components/M5Unified/M5Unified index 10e6f0be..2d84e624 160000 --- a/m5stack/components/M5Unified/M5Unified +++ b/m5stack/components/M5Unified/M5Unified @@ -1 +1 @@ -Subproject commit 10e6f0be22575429ce19557f85df98fea206d6e6 +Subproject commit 2d84e624e56d002451e1002e26817b07088428ba diff --git a/m5stack/components/M5Unified/mpy_lvgl.txt b/m5stack/components/M5Unified/mpy_lvgl.txt index 9e7c22d1..7b10cc51 100644 --- a/m5stack/components/M5Unified/mpy_lvgl.txt +++ b/m5stack/components/M5Unified/mpy_lvgl.txt @@ -32,7 +32,7 @@ bool gfx_lvgl_touch_read(lv_disp_t *indev_drv, lv_indev_data_t *data) return false; } - auto tp = M5.Touch.getTouchPointRaw(1); + auto tp = M5.Touch.getDetail(0); data->point = (lv_point_t) { tp.x, tp.y }; data->state = LV_INDEV_STATE_PRESSED; return true; diff --git a/m5stack/components/M5Unified/mpy_m5touch.cpp b/m5stack/components/M5Unified/mpy_m5touch.cpp index 41e117d7..ba99b997 100644 --- a/m5stack/components/M5Unified/mpy_m5touch.cpp +++ b/m5stack/components/M5Unified/mpy_m5touch.cpp @@ -19,11 +19,11 @@ namespace m5 } mp_obj_t touch_getX(mp_obj_t self) { - return mp_obj_new_int(getTouch(&self)->getTouchPointRaw(0).x); + return mp_obj_new_int(getTouch(&self)->getDetail(0).x); } mp_obj_t touch_getY(mp_obj_t self) { - return mp_obj_new_int(getTouch(&self)->getTouchPointRaw(0).y); + return mp_obj_new_int(getTouch(&self)->getDetail(0).y); } mp_obj_t touch_getCount(mp_obj_t self) { From 5080e4cc40762e4a94d7748f7a26aa86d65007f2 Mon Sep 17 00:00:00 2001 From: lbuque Date: Thu, 22 May 2025 18:05:19 +0800 Subject: [PATCH 102/322] boards: Add Tab5 support. Signed-off-by: lbuque --- m5stack/CMakeListsDefault.cmake | 2 +- m5stack/Makefile | 6 +- m5stack/boards/CMakeLists.txt | 2 +- m5stack/boards/Kconfig | 10 +- .../boards/M5STACK_Tab5/audioconfigboard.h | 121 + m5stack/boards/M5STACK_Tab5/board.json | 28 + m5stack/boards/M5STACK_Tab5/board.md | 18 + m5stack/boards/M5STACK_Tab5/board_init.c | 344 ++ m5stack/boards/M5STACK_Tab5/manifest.py | 5 + .../boards/M5STACK_Tab5/mpconfigboard.cmake | 49 + m5stack/boards/M5STACK_Tab5/mpconfigboard.h | 17 + m5stack/boards/M5STACK_Tab5/sdkconfig.adf | 19 + m5stack/boards/M5STACK_Tab5/sdkconfig.board | 3210 +++++++++++++++++ .../boards/M5STACK_Tab5/sdkconfig.esp_hosted | 91 + .../boards/M5STACK_Tab5/sdkconfig.spiram_hex | 31 + m5stack/boards/include/board_init.h | 2 + m5stack/cmodules/cmodules.cmake | 2 +- m5stack/cmodules/m5unified/m5unified.c | 1 + m5stack/components/M5Unified/M5GFX | 2 +- m5stack/libs/module/ain4.py | 4 +- m5stack/libs/module/audio.py | 4 +- m5stack/libs/module/commu.py | 8 +- m5stack/libs/module/dmx.py | 2 + m5stack/libs/module/dual_kmeter.py | 4 +- m5stack/libs/module/gnss.py | 4 +- m5stack/libs/module/goplus2.py | 4 +- m5stack/libs/module/grbl.py | 4 +- m5stack/libs/module/hmi.py | 4 +- m5stack/libs/module/iot_base_catm.py | 18 +- m5stack/libs/module/llm.py | 6 +- m5stack/libs/module/lora.py | 22 +- m5stack/libs/module/lora868_v12.py | 18 +- m5stack/libs/module/mbus.py | 16 +- m5stack/libs/module/module_4in8out.py | 4 +- m5stack/libs/module/plus.py | 4 +- m5stack/libs/module/pm25.py | 6 +- m5stack/libs/module/pps.py | 4 +- m5stack/libs/module/relay_2.py | 4 +- m5stack/libs/module/relay_4.py | 4 +- m5stack/libs/module/servo2.py | 4 +- m5stack/libs/module/step_motor_driver.py | 58 +- m5stack/libs/module/usb.py | 42 +- m5stack/main/idf_component.yml | 6 +- 43 files changed, 4086 insertions(+), 128 deletions(-) create mode 100644 m5stack/boards/M5STACK_Tab5/audioconfigboard.h create mode 100644 m5stack/boards/M5STACK_Tab5/board.json create mode 100644 m5stack/boards/M5STACK_Tab5/board.md create mode 100644 m5stack/boards/M5STACK_Tab5/board_init.c create mode 100644 m5stack/boards/M5STACK_Tab5/manifest.py create mode 100644 m5stack/boards/M5STACK_Tab5/mpconfigboard.cmake create mode 100644 m5stack/boards/M5STACK_Tab5/mpconfigboard.h create mode 100644 m5stack/boards/M5STACK_Tab5/sdkconfig.adf create mode 100644 m5stack/boards/M5STACK_Tab5/sdkconfig.board create mode 100644 m5stack/boards/M5STACK_Tab5/sdkconfig.esp_hosted create mode 100644 m5stack/boards/M5STACK_Tab5/sdkconfig.spiram_hex diff --git a/m5stack/CMakeListsDefault.cmake b/m5stack/CMakeListsDefault.cmake index 33cb4ee9..9a8f0628 100644 --- a/m5stack/CMakeListsDefault.cmake +++ b/m5stack/CMakeListsDefault.cmake @@ -258,7 +258,7 @@ list(APPEND IDF_COMPONENTS ) endif() -if(IDF_TARGET STREQUAL "esp32" OR IDF_TARGET STREQUAL "esp32s3") +if(IDF_TARGET STREQUAL "esp32" OR IDF_TARGET STREQUAL "esp32s3" OR IDF_TARGET STREQUAL "esp32p4") list(APPEND IDF_COMPONENTS boards) list(APPEND IDF_COMPONENTS audio_pipeline) list(APPEND IDF_COMPONENTS audio_sal) diff --git a/m5stack/Makefile b/m5stack/Makefile index afee5df0..b1fc6ad8 100644 --- a/m5stack/Makefile +++ b/m5stack/Makefile @@ -41,7 +41,8 @@ boards := \ M5STACK_Atom_Echo:atomecho \ M5STACK_AtomS3R:atoms3r \ M5STACK_AtomS3R_CAM:atoms3r_cam \ - M5STACK_StamPLC:stamplc + M5STACK_StamPLC:stamplc \ + M5STACK_Tab5:tab5 define find_board $(if $(filter $(1):%,$(boards)),$(word 2,$(subst :, ,$(filter $(1):%,$(boards)))),none) @@ -79,7 +80,8 @@ BOARD_TYPE_DEF := \ atomecho \ atoms3r \ atoms3r_cam \ - stamplc + stamplc \ + tab5 # Select the board type to build, default is None # This value affects which folder in the "./fs/system/" directory is pack into "fs-system.bin" diff --git a/m5stack/boards/CMakeLists.txt b/m5stack/boards/CMakeLists.txt index 97b21a66..ba124b37 100644 --- a/m5stack/boards/CMakeLists.txt +++ b/m5stack/boards/CMakeLists.txt @@ -2,7 +2,7 @@ # # SPDX-License-Identifier: MIT -if(IDF_TARGET STREQUAL "esp32" OR IDF_TARGET STREQUAL "esp32s3") +if(IDF_TARGET STREQUAL "esp32" OR IDF_TARGET STREQUAL "esp32s3" OR IDF_TARGET STREQUAL "esp32p4") set( srcs ${ADF_BOARD_INIT_SRC} diff --git a/m5stack/boards/Kconfig b/m5stack/boards/Kconfig index 8fbb313a..dd22ec7c 100755 --- a/m5stack/boards/Kconfig +++ b/m5stack/boards/Kconfig @@ -2,7 +2,13 @@ menu "Audio Board Configuration" config CORES3 bool "select CORES3" - default y + default n help - Enable this option to support codec ES8311. + Enable this option to support m5stack cores3. + + config TAB5 + bool "select M5STACK TAB5" + default n + help + Enable this option to support m5stack tab5. endmenu diff --git a/m5stack/boards/M5STACK_Tab5/audioconfigboard.h b/m5stack/boards/M5STACK_Tab5/audioconfigboard.h new file mode 100644 index 00000000..d26228c8 --- /dev/null +++ b/m5stack/boards/M5STACK_Tab5/audioconfigboard.h @@ -0,0 +1,121 @@ +/* +* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD +* +* SPDX-License-Identifier: MIT +*/ + +#if (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 2, 0) && ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0)) +#include "driver/i2s.h" +#else +#include "driver/i2s_pdm.h" +#include "driver/i2s_tdm.h" +#include "driver/i2s_std.h" +#endif + +#define BOARD_TASK_CORE (1) + +// mp3 decoder +#define BOARD_MP3_DECODER_CONFIG() { \ + .out_rb_size = MP3_DECODER_RINGBUFFER_SIZE, \ + .task_stack = MP3_DECODER_TASK_STACK_SIZE, \ + .task_core = BOARD_TASK_CORE, \ + .task_prio = MP3_DECODER_TASK_PRIO, \ + .stack_in_ext = true, \ +} + +// amr decoder +#define BOARD_AMR_DECODER_CONFIG() { \ + .out_rb_size = AMR_DECODER_RINGBUFFER_SIZE, \ + .task_stack = AMR_DECODER_TASK_STACK_SIZE, \ + .task_core = BOARD_TASK_CORE, \ + .task_prio = AMR_DECODER_TASK_PRIO, \ + .stack_in_ext = true, \ + } + +// wav decoder +#define BOARD_WAV_DECODER_CONFIG() { \ + .out_rb_size = WAV_DECODER_RINGBUFFER_SIZE, \ + .task_stack = WAV_DECODER_TASK_STACK, \ + .task_core = BOARD_TASK_CORE, \ + .task_prio = WAV_DECODER_TASK_PRIO, \ + .stack_in_ext = true, \ +} + +// pcm decoder +#define BOARD_PCM_DECODER_CONFIG() { \ + .out_rb_size = PCM_DECODER_RINGBUFFER_SIZE, \ + .task_stack = PCM_DECODER_TASK_STACK, \ + .task_core = BOARD_TASK_CORE, \ + .task_prio = PCM_DECODER_TASK_PRIO, \ + .stack_in_ext = true, \ + .rate = 8000, \ + .bits = 16, \ + .channels = 1, \ +} + +// i2s stream +#if (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 2, 0) && ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0)) +#define BOARD_I2S_STREAM_CFG_DEFAULT() { \ + .type = AUDIO_STREAM_WRITER, \ + .i2s_config = { \ + .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX | I2S_MODE_RX), \ + .sample_rate = 48000, \ + .bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT, \ + .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT, \ + .communication_format = I2S_COMM_FORMAT_STAND_I2S, \ + .intr_alloc_flags = ESP_INTR_FLAG_LEVEL2 | ESP_INTR_FLAG_IRAM, \ + .dma_buf_count = 3, \ + .dma_buf_len = 300, \ + .use_apll = true, \ + .tx_desc_auto_clear = true, \ + .fixed_mclk = 0 \ + }, \ + .i2s_port = I2S_NUM_1, \ + .use_alc = false, \ + .volume = 0, \ + .out_rb_size = I2S_STREAM_RINGBUFFER_SIZE, \ + .task_stack = I2S_STREAM_TASK_STACK, \ + .task_core = BOARD_TASK_CORE, \ + .task_prio = I2S_STREAM_TASK_PRIO, \ + .stack_in_ext = false, \ + .multi_out_num = 0, \ + .uninstall_drv = false, \ + .need_expand = false, \ + .expand_src_bits = I2S_BITS_PER_SAMPLE_16BIT, \ + .buffer_len = I2S_STREAM_BUF_SIZE, \ +} +#else +#define BOARD_I2S_STREAM_CFG_DEFAULT() { \ + .type = AUDIO_STREAM_WRITER, \ + .std_cfg = { \ + .clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(48000), \ + .slot_cfg = I2S_STD_PHILIPS_SLOT_DEFAULT_ADF_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_STEREO), \ + .gpio_cfg = { \ + .invert_flags = { \ + .mclk_inv = false, \ + .bclk_inv = false, \ + }, \ + }, \ + }, \ + .transmit_mode = I2S_COMM_MODE_STD, \ + .chan_cfg = { \ + .id = I2S_NUM_1, \ + .role = I2S_ROLE_MASTER, \ + .dma_desc_num = 3, \ + .dma_frame_num = 300, \ + .auto_clear = true, \ + }, \ + .use_alc = false, \ + .volume = 0, \ + .out_rb_size = I2S_STREAM_RINGBUFFER_SIZE, \ + .task_stack = I2S_STREAM_TASK_STACK, \ + .task_core = BOARD_TASK_CORE, \ + .task_prio = I2S_STREAM_TASK_PRIO, \ + .stack_in_ext = false, \ + .multi_out_num = 0, \ + .uninstall_drv = false, \ + .need_expand = false, \ + .expand_src_bits = I2S_DATA_BIT_WIDTH_16BIT, \ + .buffer_len = I2S_STREAM_BUF_SIZE, \ +} +#endif diff --git a/m5stack/boards/M5STACK_Tab5/board.json b/m5stack/boards/M5STACK_Tab5/board.json new file mode 100644 index 00000000..81c38a6a --- /dev/null +++ b/m5stack/boards/M5STACK_Tab5/board.json @@ -0,0 +1,28 @@ +{ + "deploy": [ + "../deploy.md" + ], + "deploy_options": { + "flash_offset": "0x1000" + }, + "docs": "", + "features": [ + "BLE", + "External Flash", + "WiFi" + ], + "images": [ + "esp32_devkitc.jpg" + ], + "mcu": "esp32", + "product": "ESP32 / WROOM", + "thumbnail": "", + "url": "https://www.espressif.com/en/products/modules", + "variants": { + "D2WD": "ESP32 D2WD", + "SPIRAM": "Support for SPIRAM / WROVER", + "UNICORE": "ESP32 Unicore", + "OTA": "Support for OTA" + }, + "vendor": "Espressif" +} diff --git a/m5stack/boards/M5STACK_Tab5/board.md b/m5stack/boards/M5STACK_Tab5/board.md new file mode 100644 index 00000000..9346d18d --- /dev/null +++ b/m5stack/boards/M5STACK_Tab5/board.md @@ -0,0 +1,18 @@ +The following files are firmware that should work on most ESP32-based boards +with 4MiB of flash, including WROOM WROVER, SOLO, PICO, and MINI modules. + +This board has multiple variants available: + +* If your board is based on a WROVER module, or otherwise has SPIRAM (also known + as PSRAM) then it's recommended to use the "spiram" variant. Look for heading + **Support for SPIRAM / WROVER)**. +* If your board has a ESP32-D2WD chip (with only 2MiB flash) then use the "d2wd" + variant. Look for heading **ESP32 D2WD**. +* If your board has a single-core ESP32 (e.g. the "SOLO" modules) then choose + the "unicore" variant. Look for heading **ESP32 Unicore**. +* If you'd like to perform Over-the-Air updates of the MicroPython firmware, + then choose the "ota" variant. This variant has less room in the flash for + Python files as a result of supporting OTA. Look for heading **Support for + OTA**. + +Otherwise, download the generic variant (under the first heading below). diff --git a/m5stack/boards/M5STACK_Tab5/board_init.c b/m5stack/boards/M5STACK_Tab5/board_init.c new file mode 100644 index 00000000..a1fd57e6 --- /dev/null +++ b/m5stack/boards/M5STACK_Tab5/board_init.c @@ -0,0 +1,344 @@ +/* +* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD +* +* SPDX-License-Identifier: MIT +*/ + +// NOTE: 使用IDF5的 i2s 驱动 +#define USE_IDF5 (1) + +// #include "esp_idf_version.h" +#if USE_IDF5 +#include "driver/i2s_std.h" +#include "driver/i2s_tdm.h" +#include "soc/soc_caps.h" +#else +#include "driver/i2s.h" +#endif + +#include "string.h" +#include "board_init.h" +#include "esp_log.h" +#include "driver/gpio.h" +#include "esp_codec_dev.h" +#include "esp_codec_dev_defaults.h" + +#if 0 //ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0) +#include "driver/i2c_master.h" +#define USE_IDF_I2C_MASTER +#else +#include "driver/i2c.h" +#endif + +static char *TAG = "tab5"; +static void *audio_hal = NULL; + + +#if USE_IDF5 + +#define I2S_MAX_KEEP SOC_I2S_NUM + +typedef struct { + i2s_chan_handle_t tx_handle; + i2s_chan_handle_t rx_handle; +} i2s_keep_t; + +static i2s_comm_mode_t i2s_in_mode = I2S_COMM_MODE_STD; +static i2s_comm_mode_t i2s_out_mode = I2S_COMM_MODE_STD; +static i2s_keep_t *i2s_keep[I2S_MAX_KEEP]; +#endif + +#ifdef USE_IDF_I2C_MASTER +static i2c_master_bus_handle_t i2c_bus_handle; +#endif + +static int ut_i2c_init(uint8_t port); +static int ut_i2c_deinit(uint8_t port); + +static int ut_i2s_init(uint8_t port); +static int ut_i2s_deinit(uint8_t port); + +esp_err_t get_i2s_pins(int port, board_i2s_pin_t *i2s_config) +{ + ESP_LOGI(TAG, "get_i2s_pins !!!"); + AUDIO_NULL_CHECK(TAG, i2s_config, return ESP_FAIL); + if (port == 1) { + i2s_config->bck_io_num = GPIO_NUM_27; + i2s_config->ws_io_num = GPIO_NUM_29; + i2s_config->data_out_num = GPIO_NUM_26; + i2s_config->data_in_num = GPIO_NUM_28; + i2s_config->mck_io_num = GPIO_NUM_30; + } else { + memset(i2s_config, -1, sizeof(board_i2s_pin_t)); + ESP_LOGE(TAG, "I2S PORT %d is not supported, please use I2S PORT 0", port); + return ESP_FAIL; + } + return ESP_OK; +} + +void * board_codec_init(void) +{ + if (audio_hal) { + return audio_hal; + } + ESP_LOGI(TAG, "init"); + + int ret = ut_i2c_init(1); + ret |= ut_i2s_init(1); + + audio_codec_i2s_cfg_t i2s_cfg = { + .port = 1, +#if USE_IDF5 + .rx_handle = i2s_keep[1]->rx_handle, + .tx_handle = i2s_keep[1]->tx_handle, +#endif + }; + const audio_codec_data_if_t *data_if = audio_codec_new_i2s_data(&i2s_cfg); + + audio_codec_i2c_cfg_t i2c_cfg = { + .port = 1, + .addr = ES8388_CODEC_DEFAULT_ADDR, + .bus_handle = NULL, + }; +#ifdef USE_IDF_I2C_MASTER + i2c_cfg.bus_handle = i2c_bus_handle; +#endif + const audio_codec_ctrl_if_t *out_ctrl_if = audio_codec_new_i2c_ctrl(&i2c_cfg); + + i2c_cfg.addr = ES7210_CODEC_DEFAULT_ADDR; + const audio_codec_ctrl_if_t *in_ctrl_if = audio_codec_new_i2c_ctrl(&i2c_cfg); + + const audio_codec_gpio_if_t *gpio_if = audio_codec_new_gpio(); + + esp_codec_dev_hw_gain_t gain = { + .pa_voltage = 5.0, + .codec_dac_voltage = 3.3, + }; + + es8388_codec_cfg_t es8388_cfg = { + .codec_mode = ESP_CODEC_DEV_WORK_MODE_DAC, + .master_mode = false, + .ctrl_if = out_ctrl_if, + .pa_pin = -1, // PI4IOE1 P1 控制 + }; + const audio_codec_if_t *out_codec_if = es8388_codec_new(&es8388_cfg); + + // New input codec interface + es7210_codec_cfg_t es7210_cfg = { + .ctrl_if = in_ctrl_if, + .mic_selected = ES7120_SEL_MIC1 | ES7120_SEL_MIC2 | ES7120_SEL_MIC3 | ES7120_SEL_MIC4, + }; + const audio_codec_if_t *in_codec_if = es7210_codec_new(&es7210_cfg); + + esp_codec_dev_cfg_t dev_cfg = { + .codec_if = out_codec_if, // aw88298_codec_new 获取到的接口实现 + .data_if = data_if, // 这里不实例化 i2s; 后续的 i2s_stream_init 会实例化 i2s。 + .dev_type = ESP_CODEC_DEV_TYPE_OUT, // 设备只播放 + }; + audio_hal = esp_codec_dev_new(&dev_cfg); + + esp_codec_dev_sample_info_t fs = { + .sample_rate = 48000, + .channel = 2, + .bits_per_sample = 16, + }; + esp_codec_dev_open(audio_hal, &fs); + + // New input codec device + dev_cfg.codec_if = in_codec_if; + dev_cfg.dev_type = ESP_CODEC_DEV_TYPE_IN; + esp_codec_dev_handle_t record_dev = esp_codec_dev_new(&dev_cfg); + esp_codec_dev_set_in_gain(record_dev, 30.0); + + fs.channel = 4; + // fs.channel_mask = ESP_CODEC_DEV_MAKE_CHANNEL_MASK(0) | ESP_CODEC_DEV_MAKE_CHANNEL_MASK(3); + ret = esp_codec_dev_open(record_dev, &fs); + + // i2s_stream_init 会实例化 i2s。初始化 codec 之后,需要将 i2s 释放。 + ut_i2s_deinit(1); + + return audio_hal; +} + + +// NOTE: 使用内联函数??? +int board_codec_volume_set(void *hd, int vol) +{ + return esp_codec_dev_set_out_vol(hd, vol); +} + + +// NOTE: 使用内联函数??? +int board_codec_volume_get(void *hd, int *vol) +{ + return esp_codec_dev_get_out_vol(hd, vol); +} + + +static int ut_i2c_init(uint8_t port) +{ +#ifdef USE_IDF_I2C_MASTER + i2c_master_bus_config_t i2c_bus_config = {0}; + i2c_bus_config.clk_source = I2C_CLK_SRC_DEFAULT; + i2c_bus_config.i2c_port = port; + i2c_bus_config.scl_io_num = 31; + i2c_bus_config.sda_io_num = 32; + i2c_bus_config.glitch_ignore_cnt = 7; + i2c_bus_config.flags.enable_internal_pullup = true; + return i2c_new_master_bus(&i2c_bus_config, &i2c_bus_handle); +#else + i2c_config_t i2c_cfg = { + .mode = I2C_MODE_MASTER, + .sda_pullup_en = GPIO_PULLUP_ENABLE, + .scl_pullup_en = GPIO_PULLUP_ENABLE, + .master.clk_speed = 100000, + }; + i2c_cfg.sda_io_num = 31; + i2c_cfg.scl_io_num = 32; + esp_err_t ret = i2c_param_config(port, &i2c_cfg); + if (ret != ESP_OK) { + return -1; + } + return i2c_driver_install(port, i2c_cfg.mode, 0, 0, 0); +#endif +} + + +static int ut_i2c_deinit(uint8_t port) +{ +#ifdef USE_IDF_I2C_MASTER + if (i2c_bus_handle) { + i2c_del_master_bus(i2c_bus_handle); + } + i2c_bus_handle = NULL; + return 0; +#else + return i2c_driver_delete(port); +#endif +} + + +#if USE_IDF5 +static void ut_set_i2s_mode(i2s_comm_mode_t out_mode, i2s_comm_mode_t in_mode) +{ + i2s_in_mode = in_mode; + i2s_out_mode = out_mode; +} + + +static void ut_clr_i2s_mode(void) +{ + i2s_in_mode = I2S_COMM_MODE_STD; + i2s_out_mode = I2S_COMM_MODE_STD; +} +#endif + + +static int ut_i2s_init(uint8_t port) +{ +#if USE_IDF5 + if (port >= I2S_MAX_KEEP) { + return -1; + } + // Already installed + if (i2s_keep[port]) { + return 0; + } + i2s_chan_config_t chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(port, I2S_ROLE_MASTER); + i2s_std_config_t std_cfg = { + .clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(16000), + .slot_cfg = I2S_STD_PHILIPS_SLOT_DEFAULT_CONFIG(16, I2S_SLOT_MODE_STEREO), + .gpio_cfg ={ + .mclk = 30, + .bclk = 27, + .ws = 29, + .dout = 26, + .din = 28, + }, + }; + i2s_keep[port] = (i2s_keep_t *) calloc(1, sizeof(i2s_keep_t)); + if (i2s_keep[port] == NULL) { + return -1; + } +#if SOC_I2S_SUPPORTS_TDM + i2s_tdm_slot_mask_t slot_mask = I2S_TDM_SLOT0 | I2S_TDM_SLOT1 | I2S_TDM_SLOT2 | I2S_TDM_SLOT3; + i2s_tdm_config_t tdm_cfg = { + .slot_cfg = I2S_TDM_PHILIPS_SLOT_DEFAULT_CONFIG(16, I2S_SLOT_MODE_STEREO, slot_mask), + .clk_cfg = I2S_TDM_CLK_DEFAULT_CONFIG(16000), + .gpio_cfg = { + .mclk = 0, + .bclk = 34, + .ws = 33, + .dout = 13, + .din = -1, + }, + }; + tdm_cfg.slot_cfg.total_slot = 4; +#endif + int ret = i2s_new_channel(&chan_cfg, &i2s_keep[port]->tx_handle, &i2s_keep[port]->rx_handle); + if (i2s_out_mode == I2S_COMM_MODE_STD) { + ret = i2s_channel_init_std_mode(i2s_keep[port]->tx_handle, &std_cfg); + } +#if SOC_I2S_SUPPORTS_TDM + else if (i2s_out_mode == I2S_COMM_MODE_TDM) { + ret = i2s_channel_init_tdm_mode(i2s_keep[port]->tx_handle, &tdm_cfg); + } +#endif + if (i2s_in_mode == I2S_COMM_MODE_STD) { + ret = i2s_channel_init_std_mode(i2s_keep[port]->rx_handle, &std_cfg); + } +#if SOC_I2S_SUPPORTS_TDM + else if (i2s_in_mode == I2S_COMM_MODE_TDM) { + ret = i2s_channel_init_tdm_mode(i2s_keep[port]->rx_handle, &tdm_cfg); + } +#endif + // For tx master using duplex mode + i2s_channel_enable(i2s_keep[port]->tx_handle); +#else + i2s_config_t i2s_config = { + .mode = (i2s_mode_t) (I2S_MODE_TX | I2S_MODE_RX | I2S_MODE_MASTER), + .sample_rate = 48000, + .bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT, + .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT, + .communication_format = I2S_COMM_FORMAT_STAND_I2S, + .intr_alloc_flags = ESP_INTR_FLAG_LEVEL2 | ESP_INTR_FLAG_IRAM, + .dma_buf_count = 2, + .dma_buf_len = 128, + .use_apll = true, + .tx_desc_auto_clear = true, + }; + int ret = i2s_driver_install(port, &i2s_config, 0, NULL); + i2s_pin_config_t i2s_pin_cfg = { + .mck_io_num = 0, + .bck_io_num = 34, + .ws_io_num = 33, + .data_out_num = 13, + .data_in_num = 14, + }; + i2s_set_pin(port, &i2s_pin_cfg); +#endif + return ret; +} + + +static int ut_i2s_deinit(uint8_t port) +{ +#if USE_IDF5 + if (port >= I2S_MAX_KEEP) { + return -1; + } + // already installed + if (i2s_keep[port] == NULL) { + return 0; + } + i2s_channel_disable(i2s_keep[port]->tx_handle); + i2s_channel_disable(i2s_keep[port]->rx_handle); + i2s_del_channel(i2s_keep[port]->tx_handle); + i2s_del_channel(i2s_keep[port]->rx_handle); + free(i2s_keep[port]); + i2s_keep[port] = NULL; +#else + i2s_driver_uninstall(port); +#endif + return 0; +} diff --git a/m5stack/boards/M5STACK_Tab5/manifest.py b/m5stack/boards/M5STACK_Tab5/manifest.py new file mode 100644 index 00000000..275c55bd --- /dev/null +++ b/m5stack/boards/M5STACK_Tab5/manifest.py @@ -0,0 +1,5 @@ +# SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +include("$(MPY_DIR)/../m5stack/modules/startup/manifest_cores3.py") diff --git a/m5stack/boards/M5STACK_Tab5/mpconfigboard.cmake b/m5stack/boards/M5STACK_Tab5/mpconfigboard.cmake new file mode 100644 index 00000000..f84dbc55 --- /dev/null +++ b/m5stack/boards/M5STACK_Tab5/mpconfigboard.cmake @@ -0,0 +1,49 @@ +set(IDF_TARGET esp32p4) + +# https://github.com/m5stack/m5stack-board-id/blob/main/board.csv#L12 +set(BOARD_ID 22) +set(MICROPY_PY_LVGL 1) +# Enable camera module +# set(M5_CAMERA_MODULE_ENABLE TRUE) + +set(SDKCONFIG_DEFAULTS + # boards/M5STACK_Tab5/sdkconfig.board + ./boards/sdkconfig.base + ${SDKCONFIG_IDF_VERSION_SPECIFIC} + # ./boards/sdkconfig.240mhz + ./boards/sdkconfig.disable_iram + # ./boards/sdkconfig.ble + # ./boards/sdkconfig.usb + # ./boards/sdkconfig.usb_cdc + ./boards/sdkconfig.flash_16mb_omv + ./boards/sdkconfig.freertos + ./boards/M5STACK_Tab5/sdkconfig.spiram_hex + ./boards/M5STACK_Tab5/sdkconfig.adf + ./boards/M5STACK_Tab5/sdkconfig.esp_hosted +) + +# If not enable LVGL, ignore this... +set(LV_CFLAGS -DLV_COLOR_DEPTH=16 -DLV_COLOR_16_SWAP=0) + +if(NOT MICROPY_FROZEN_MANIFEST) + set(MICROPY_FROZEN_MANIFEST ${CMAKE_SOURCE_DIR}/boards/manifest.py) +endif() + +set(ADF_MODULE_ENABLE TRUE) + +set(ADF_COMPS "$ENV{ADF_PATH}/components") + +set(ADF_BOARD_INIT_SRC + $ENV{ADF_PATH}/components + M5STACK_Tab5/board_init.c +) + +list(APPEND EXTRA_COMPONENT_DIRS + ${CMAKE_SOURCE_DIR}/boards + $ENV{ADF_PATH}/components/audio_pipeline + $ENV{ADF_PATH}/components/audio_sal + $ENV{ADF_PATH}/components/esp-adf-libs + $ENV{ADF_PATH}/components/esp-sr +) + +message(STATUS "M5STACK_Tab5/CMakeLists.txt: EXTRA_COMPONENT_DIRS=${EXTRA_COMPONENT_DIRS}") diff --git a/m5stack/boards/M5STACK_Tab5/mpconfigboard.h b/m5stack/boards/M5STACK_Tab5/mpconfigboard.h new file mode 100644 index 00000000..13ddebf7 --- /dev/null +++ b/m5stack/boards/M5STACK_Tab5/mpconfigboard.h @@ -0,0 +1,17 @@ +// Both of these can be set by mpconfigboard.cmake if a BOARD_VARIANT is +// specified. + +#ifndef MICROPY_HW_BOARD_NAME +#define MICROPY_HW_BOARD_NAME "M5STACK Tab5" +#endif + +#ifndef MICROPY_HW_MCU_NAME +#define MICROPY_HW_MCU_NAME "ESP32P4" +#endif + +#define MICROPY_HW_ENABLE_USBDEV (0) +#define MICROPY_PY_ESPNOW (0) + +// #ifndef MICROPY_HW_ENABLE_USB_RUNTIME_DEVICE +// #define MICROPY_HW_ENABLE_USB_RUNTIME_DEVICE (1) // Support machine.USBDevice +// #endif \ No newline at end of file diff --git a/m5stack/boards/M5STACK_Tab5/sdkconfig.adf b/m5stack/boards/M5STACK_Tab5/sdkconfig.adf new file mode 100644 index 00000000..49a4ca92 --- /dev/null +++ b/m5stack/boards/M5STACK_Tab5/sdkconfig.adf @@ -0,0 +1,19 @@ + +CONFIG_TAB5=y +CONFIG_CODEC_ES8388_SUPPORT=y +CONFIG_CODEC_ES7210_SUPPORT=y +CONFIG_FREERTOS_ENABLE_BACKWARD_COMPATIBILITY=y +CONFIG_CODEC_I2C_BACKWARD_COMPATIBLE=y + +CONFIG_FREERTOS_PLACE_SNAPSHOT_FUNS_INTO_FLASH=y +CONFIG_ESP_TLS_INSECURE=y +CONFIG_ESP_TLS_SKIP_SERVER_CERT_VERIFY=y + +CONFIG_ESP_COREDUMP_TO_FLASH_OR_UART=y +CONFIG_ESP_COREDUMP_CAPTURE_DRAM=y +CONFIG_ESP_COREDUMP_DECODE=y + + +# CONFIG_ULP_COPROC_ENABLED is not set +# CONFIG_ULP_COPROC_TYPE_FSM is not set +# CONFIG_ULP_COPROC_RESERVE_MEM is not set diff --git a/m5stack/boards/M5STACK_Tab5/sdkconfig.board b/m5stack/boards/M5STACK_Tab5/sdkconfig.board new file mode 100644 index 00000000..364416e6 --- /dev/null +++ b/m5stack/boards/M5STACK_Tab5/sdkconfig.board @@ -0,0 +1,3210 @@ +# +# Automatically generated file. DO NOT EDIT. +# Espressif IoT Development Framework (ESP-IDF) 5.4.1 Project Configuration +# +CONFIG_SOC_ADC_SUPPORTED=y +CONFIG_SOC_ANA_CMPR_SUPPORTED=y +CONFIG_SOC_DEDICATED_GPIO_SUPPORTED=y +CONFIG_SOC_UART_SUPPORTED=y +CONFIG_SOC_GDMA_SUPPORTED=y +CONFIG_SOC_AHB_GDMA_SUPPORTED=y +CONFIG_SOC_AXI_GDMA_SUPPORTED=y +CONFIG_SOC_DW_GDMA_SUPPORTED=y +CONFIG_SOC_DMA2D_SUPPORTED=y +CONFIG_SOC_GPTIMER_SUPPORTED=y +CONFIG_SOC_PCNT_SUPPORTED=y +CONFIG_SOC_LCDCAM_SUPPORTED=y +CONFIG_SOC_LCDCAM_CAM_SUPPORTED=y +CONFIG_SOC_LCDCAM_I80_LCD_SUPPORTED=y +CONFIG_SOC_LCDCAM_RGB_LCD_SUPPORTED=y +CONFIG_SOC_MIPI_CSI_SUPPORTED=y +CONFIG_SOC_MIPI_DSI_SUPPORTED=y +CONFIG_SOC_MCPWM_SUPPORTED=y +CONFIG_SOC_TWAI_SUPPORTED=y +CONFIG_SOC_ETM_SUPPORTED=y +CONFIG_SOC_PARLIO_SUPPORTED=y +CONFIG_SOC_ASYNC_MEMCPY_SUPPORTED=y +CONFIG_SOC_EMAC_SUPPORTED=y +CONFIG_SOC_USB_OTG_SUPPORTED=y +CONFIG_SOC_WIRELESS_HOST_SUPPORTED=y +CONFIG_SOC_USB_SERIAL_JTAG_SUPPORTED=y +CONFIG_SOC_TEMP_SENSOR_SUPPORTED=y +CONFIG_SOC_SUPPORTS_SECURE_DL_MODE=y +CONFIG_SOC_ULP_SUPPORTED=y +CONFIG_SOC_LP_CORE_SUPPORTED=y +CONFIG_SOC_EFUSE_KEY_PURPOSE_FIELD=y +CONFIG_SOC_EFUSE_SUPPORTED=y +CONFIG_SOC_RTC_FAST_MEM_SUPPORTED=y +CONFIG_SOC_RTC_MEM_SUPPORTED=y +CONFIG_SOC_RMT_SUPPORTED=y +CONFIG_SOC_I2S_SUPPORTED=y +CONFIG_SOC_SDM_SUPPORTED=y +CONFIG_SOC_GPSPI_SUPPORTED=y +CONFIG_SOC_LEDC_SUPPORTED=y +CONFIG_SOC_ISP_SUPPORTED=y +CONFIG_SOC_I2C_SUPPORTED=y +CONFIG_SOC_SYSTIMER_SUPPORTED=y +CONFIG_SOC_AES_SUPPORTED=y +CONFIG_SOC_MPI_SUPPORTED=y +CONFIG_SOC_SHA_SUPPORTED=y +CONFIG_SOC_HMAC_SUPPORTED=y +CONFIG_SOC_DIG_SIGN_SUPPORTED=y +CONFIG_SOC_ECC_SUPPORTED=y +CONFIG_SOC_ECC_EXTENDED_MODES_SUPPORTED=y +CONFIG_SOC_FLASH_ENC_SUPPORTED=y +CONFIG_SOC_SECURE_BOOT_SUPPORTED=y +CONFIG_SOC_BOD_SUPPORTED=y +CONFIG_SOC_APM_SUPPORTED=y +CONFIG_SOC_PMU_SUPPORTED=y +CONFIG_SOC_DCDC_SUPPORTED=y +CONFIG_SOC_PAU_SUPPORTED=y +CONFIG_SOC_LP_TIMER_SUPPORTED=y +CONFIG_SOC_ULP_LP_UART_SUPPORTED=y +CONFIG_SOC_LP_GPIO_MATRIX_SUPPORTED=y +CONFIG_SOC_LP_PERIPHERALS_SUPPORTED=y +CONFIG_SOC_LP_I2C_SUPPORTED=y +CONFIG_SOC_LP_I2S_SUPPORTED=y +CONFIG_SOC_LP_SPI_SUPPORTED=y +CONFIG_SOC_LP_ADC_SUPPORTED=y +CONFIG_SOC_LP_VAD_SUPPORTED=y +CONFIG_SOC_SPIRAM_SUPPORTED=y +CONFIG_SOC_PSRAM_DMA_CAPABLE=y +CONFIG_SOC_SDMMC_HOST_SUPPORTED=y +CONFIG_SOC_CLK_TREE_SUPPORTED=y +CONFIG_SOC_ASSIST_DEBUG_SUPPORTED=y +CONFIG_SOC_DEBUG_PROBE_SUPPORTED=y +CONFIG_SOC_WDT_SUPPORTED=y +CONFIG_SOC_SPI_FLASH_SUPPORTED=y +CONFIG_SOC_TOUCH_SENSOR_SUPPORTED=y +CONFIG_SOC_RNG_SUPPORTED=y +CONFIG_SOC_GP_LDO_SUPPORTED=y +CONFIG_SOC_PPA_SUPPORTED=y +CONFIG_SOC_LIGHT_SLEEP_SUPPORTED=y +CONFIG_SOC_DEEP_SLEEP_SUPPORTED=y +CONFIG_SOC_PM_SUPPORTED=y +CONFIG_SOC_SIMD_INSTRUCTION_SUPPORTED=y +CONFIG_SOC_XTAL_SUPPORT_40M=y +CONFIG_SOC_AES_SUPPORT_DMA=y +CONFIG_SOC_AES_SUPPORT_GCM=y +CONFIG_SOC_AES_GDMA=y +CONFIG_SOC_AES_SUPPORT_AES_128=y +CONFIG_SOC_AES_SUPPORT_AES_256=y +CONFIG_SOC_ADC_RTC_CTRL_SUPPORTED=y +CONFIG_SOC_ADC_DIG_CTRL_SUPPORTED=y +CONFIG_SOC_ADC_DMA_SUPPORTED=y +CONFIG_SOC_ADC_PERIPH_NUM=2 +CONFIG_SOC_ADC_MAX_CHANNEL_NUM=8 +CONFIG_SOC_ADC_ATTEN_NUM=4 +CONFIG_SOC_ADC_DIGI_CONTROLLER_NUM=2 +CONFIG_SOC_ADC_PATT_LEN_MAX=16 +CONFIG_SOC_ADC_DIGI_MAX_BITWIDTH=12 +CONFIG_SOC_ADC_DIGI_MIN_BITWIDTH=12 +CONFIG_SOC_ADC_DIGI_IIR_FILTER_NUM=2 +CONFIG_SOC_ADC_DIGI_MONITOR_NUM=2 +CONFIG_SOC_ADC_DIGI_RESULT_BYTES=4 +CONFIG_SOC_ADC_DIGI_DATA_BYTES_PER_CONV=4 +CONFIG_SOC_ADC_SAMPLE_FREQ_THRES_HIGH=83333 +CONFIG_SOC_ADC_SAMPLE_FREQ_THRES_LOW=611 +CONFIG_SOC_ADC_RTC_MIN_BITWIDTH=12 +CONFIG_SOC_ADC_RTC_MAX_BITWIDTH=12 +CONFIG_SOC_ADC_SHARED_POWER=y +CONFIG_SOC_BROWNOUT_RESET_SUPPORTED=y +CONFIG_SOC_SHARED_IDCACHE_SUPPORTED=y +CONFIG_SOC_CACHE_WRITEBACK_SUPPORTED=y +CONFIG_SOC_CACHE_FREEZE_SUPPORTED=y +CONFIG_SOC_CACHE_INTERNAL_MEM_VIA_L1CACHE=y +CONFIG_SOC_CPU_CORES_NUM=2 +CONFIG_SOC_CPU_INTR_NUM=32 +CONFIG_SOC_CPU_HAS_FLEXIBLE_INTC=y +CONFIG_SOC_INT_CLIC_SUPPORTED=y +CONFIG_SOC_INT_HW_NESTED_SUPPORTED=y +CONFIG_SOC_BRANCH_PREDICTOR_SUPPORTED=y +CONFIG_SOC_CPU_COPROC_NUM=3 +CONFIG_SOC_CPU_HAS_FPU=y +CONFIG_SOC_CPU_HAS_FPU_EXT_ILL_BUG=y +CONFIG_SOC_CPU_HAS_HWLOOP=y +CONFIG_SOC_CPU_HAS_PIE=y +CONFIG_SOC_HP_CPU_HAS_MULTIPLE_CORES=y +CONFIG_SOC_CPU_BREAKPOINTS_NUM=3 +CONFIG_SOC_CPU_WATCHPOINTS_NUM=3 +CONFIG_SOC_CPU_WATCHPOINT_MAX_REGION_SIZE=0x100 +CONFIG_SOC_CPU_HAS_PMA=y +CONFIG_SOC_CPU_IDRAM_SPLIT_USING_PMP=y +CONFIG_SOC_CPU_PMP_REGION_GRANULARITY=128 +CONFIG_SOC_CPU_HAS_LOCKUP_RESET=y +CONFIG_SOC_SIMD_PREFERRED_DATA_ALIGNMENT=16 +CONFIG_SOC_DS_SIGNATURE_MAX_BIT_LEN=4096 +CONFIG_SOC_DS_KEY_PARAM_MD_IV_LENGTH=16 +CONFIG_SOC_DS_KEY_CHECK_MAX_WAIT_US=1100 +CONFIG_SOC_DMA_CAN_ACCESS_FLASH=y +CONFIG_SOC_AHB_GDMA_VERSION=2 +CONFIG_SOC_GDMA_SUPPORT_CRC=y +CONFIG_SOC_GDMA_NUM_GROUPS_MAX=2 +CONFIG_SOC_GDMA_PAIRS_PER_GROUP_MAX=3 +CONFIG_SOC_AXI_GDMA_SUPPORT_PSRAM=y +CONFIG_SOC_GDMA_SUPPORT_ETM=y +CONFIG_SOC_GDMA_SUPPORT_SLEEP_RETENTION=y +CONFIG_SOC_AXI_DMA_EXT_MEM_ENC_ALIGNMENT=16 +CONFIG_SOC_DMA2D_GROUPS=1 +CONFIG_SOC_DMA2D_TX_CHANNELS_PER_GROUP=3 +CONFIG_SOC_DMA2D_RX_CHANNELS_PER_GROUP=2 +CONFIG_SOC_ETM_GROUPS=1 +CONFIG_SOC_ETM_CHANNELS_PER_GROUP=50 +CONFIG_SOC_ETM_SUPPORT_SLEEP_RETENTION=y +CONFIG_SOC_GPIO_PORT=1 +CONFIG_SOC_GPIO_PIN_COUNT=55 +CONFIG_SOC_GPIO_SUPPORT_PIN_GLITCH_FILTER=y +CONFIG_SOC_GPIO_FLEX_GLITCH_FILTER_NUM=8 +CONFIG_SOC_GPIO_SUPPORT_PIN_HYS_FILTER=y +CONFIG_SOC_GPIO_SUPPORT_ETM=y +CONFIG_SOC_GPIO_SUPPORT_RTC_INDEPENDENT=y +CONFIG_SOC_GPIO_SUPPORT_DEEPSLEEP_WAKEUP=y +CONFIG_SOC_LP_IO_HAS_INDEPENDENT_WAKEUP_SOURCE=y +CONFIG_SOC_LP_IO_CLOCK_IS_INDEPENDENT=y +CONFIG_SOC_GPIO_VALID_GPIO_MASK=0x007FFFFFFFFFFFFF +CONFIG_SOC_GPIO_IN_RANGE_MAX=54 +CONFIG_SOC_GPIO_OUT_RANGE_MAX=54 +CONFIG_SOC_GPIO_DEEP_SLEEP_WAKE_VALID_GPIO_MASK=0 +CONFIG_SOC_GPIO_DEEP_SLEEP_WAKE_SUPPORTED_PIN_CNT=16 +CONFIG_SOC_GPIO_VALID_DIGITAL_IO_PAD_MASK=0x007FFFFFFFFF0000 +CONFIG_SOC_GPIO_CLOCKOUT_BY_GPIO_MATRIX=y +CONFIG_SOC_GPIO_CLOCKOUT_CHANNEL_NUM=2 +CONFIG_SOC_CLOCKOUT_SUPPORT_CHANNEL_DIVIDER=y +CONFIG_SOC_DEBUG_PROBE_NUM_UNIT=1 +CONFIG_SOC_DEBUG_PROBE_MAX_OUTPUT_WIDTH=16 +CONFIG_SOC_GPIO_SUPPORT_FORCE_HOLD=y +CONFIG_SOC_RTCIO_PIN_COUNT=16 +CONFIG_SOC_RTCIO_INPUT_OUTPUT_SUPPORTED=y +CONFIG_SOC_RTCIO_HOLD_SUPPORTED=y +CONFIG_SOC_RTCIO_WAKE_SUPPORTED=y +CONFIG_SOC_DEDIC_GPIO_OUT_CHANNELS_NUM=8 +CONFIG_SOC_DEDIC_GPIO_IN_CHANNELS_NUM=8 +CONFIG_SOC_DEDIC_PERIPH_ALWAYS_ENABLE=y +CONFIG_SOC_ANA_CMPR_NUM=2 +CONFIG_SOC_ANA_CMPR_CAN_DISTINGUISH_EDGE=y +CONFIG_SOC_ANA_CMPR_SUPPORT_ETM=y +CONFIG_SOC_I2C_NUM=3 +CONFIG_SOC_HP_I2C_NUM=2 +CONFIG_SOC_I2C_FIFO_LEN=32 +CONFIG_SOC_I2C_CMD_REG_NUM=8 +CONFIG_SOC_I2C_SUPPORT_SLAVE=y +CONFIG_SOC_I2C_SUPPORT_HW_FSM_RST=y +CONFIG_SOC_I2C_SUPPORT_HW_CLR_BUS=y +CONFIG_SOC_I2C_SUPPORT_XTAL=y +CONFIG_SOC_I2C_SUPPORT_RTC=y +CONFIG_SOC_I2C_SUPPORT_10BIT_ADDR=y +CONFIG_SOC_I2C_SLAVE_SUPPORT_BROADCAST=y +CONFIG_SOC_I2C_SLAVE_CAN_GET_STRETCH_CAUSE=y +CONFIG_SOC_I2C_SLAVE_SUPPORT_I2CRAM_ACCESS=y +CONFIG_SOC_I2C_SLAVE_SUPPORT_SLAVE_UNMATCH=y +CONFIG_SOC_I2C_SUPPORT_SLEEP_RETENTION=y +CONFIG_SOC_LP_I2C_NUM=1 +CONFIG_SOC_LP_I2C_FIFO_LEN=16 +CONFIG_SOC_I2S_NUM=3 +CONFIG_SOC_I2S_HW_VERSION_2=y +CONFIG_SOC_I2S_SUPPORTS_ETM=y +CONFIG_SOC_I2S_SUPPORTS_XTAL=y +CONFIG_SOC_I2S_SUPPORTS_APLL=y +CONFIG_SOC_I2S_SUPPORTS_PCM=y +CONFIG_SOC_I2S_SUPPORTS_PDM=y +CONFIG_SOC_I2S_SUPPORTS_PDM_TX=y +CONFIG_SOC_I2S_SUPPORTS_PDM_RX=y +CONFIG_SOC_I2S_SUPPORTS_PDM_RX_HP_FILTER=y +CONFIG_SOC_I2S_SUPPORTS_TX_SYNC_CNT=y +CONFIG_SOC_I2S_SUPPORTS_TDM=y +CONFIG_SOC_I2S_PDM_MAX_TX_LINES=2 +CONFIG_SOC_I2S_PDM_MAX_RX_LINES=4 +CONFIG_SOC_I2S_TDM_FULL_DATA_WIDTH=y +CONFIG_SOC_I2S_SUPPORT_SLEEP_RETENTION=y +CONFIG_SOC_LP_I2S_NUM=1 +CONFIG_SOC_ISP_BF_SUPPORTED=y +CONFIG_SOC_ISP_CCM_SUPPORTED=y +CONFIG_SOC_ISP_DEMOSAIC_SUPPORTED=y +CONFIG_SOC_ISP_DVP_SUPPORTED=y +CONFIG_SOC_ISP_SHARPEN_SUPPORTED=y +CONFIG_SOC_ISP_COLOR_SUPPORTED=y +CONFIG_SOC_ISP_LSC_SUPPORTED=y +CONFIG_SOC_ISP_SHARE_CSI_BRG=y +CONFIG_SOC_ISP_NUMS=1 +CONFIG_SOC_ISP_DVP_CTLR_NUMS=1 +CONFIG_SOC_ISP_AE_CTLR_NUMS=1 +CONFIG_SOC_ISP_AE_BLOCK_X_NUMS=5 +CONFIG_SOC_ISP_AE_BLOCK_Y_NUMS=5 +CONFIG_SOC_ISP_AF_CTLR_NUMS=1 +CONFIG_SOC_ISP_AF_WINDOW_NUMS=3 +CONFIG_SOC_ISP_BF_TEMPLATE_X_NUMS=3 +CONFIG_SOC_ISP_BF_TEMPLATE_Y_NUMS=3 +CONFIG_SOC_ISP_CCM_DIMENSION=3 +CONFIG_SOC_ISP_DEMOSAIC_GRAD_RATIO_INT_BITS=2 +CONFIG_SOC_ISP_DEMOSAIC_GRAD_RATIO_DEC_BITS=4 +CONFIG_SOC_ISP_DEMOSAIC_GRAD_RATIO_RES_BITS=26 +CONFIG_SOC_ISP_DVP_DATA_WIDTH_MAX=16 +CONFIG_SOC_ISP_SHARPEN_TEMPLATE_X_NUMS=3 +CONFIG_SOC_ISP_SHARPEN_TEMPLATE_Y_NUMS=3 +CONFIG_SOC_ISP_SHARPEN_H_FREQ_COEF_INT_BITS=3 +CONFIG_SOC_ISP_SHARPEN_H_FREQ_COEF_DEC_BITS=5 +CONFIG_SOC_ISP_SHARPEN_H_FREQ_COEF_RES_BITS=24 +CONFIG_SOC_ISP_SHARPEN_M_FREQ_COEF_INT_BITS=3 +CONFIG_SOC_ISP_SHARPEN_M_FREQ_COEF_DEC_BITS=5 +CONFIG_SOC_ISP_SHARPEN_M_FREQ_COEF_RES_BITS=24 +CONFIG_SOC_ISP_HIST_CTLR_NUMS=1 +CONFIG_SOC_ISP_HIST_BLOCK_X_NUMS=5 +CONFIG_SOC_ISP_HIST_BLOCK_Y_NUMS=5 +CONFIG_SOC_ISP_HIST_SEGMENT_NUMS=16 +CONFIG_SOC_ISP_HIST_INTERVAL_NUMS=15 +CONFIG_SOC_ISP_LSC_GRAD_RATIO_INT_BITS=2 +CONFIG_SOC_ISP_LSC_GRAD_RATIO_DEC_BITS=8 +CONFIG_SOC_ISP_LSC_GRAD_RATIO_RES_BITS=22 +CONFIG_SOC_LEDC_SUPPORT_PLL_DIV_CLOCK=y +CONFIG_SOC_LEDC_SUPPORT_XTAL_CLOCK=y +CONFIG_SOC_LEDC_TIMER_NUM=4 +CONFIG_SOC_LEDC_CHANNEL_NUM=8 +CONFIG_SOC_LEDC_TIMER_BIT_WIDTH=20 +CONFIG_SOC_LEDC_GAMMA_CURVE_FADE_SUPPORTED=y +CONFIG_SOC_LEDC_GAMMA_CURVE_FADE_RANGE_MAX=16 +CONFIG_SOC_LEDC_SUPPORT_FADE_STOP=y +CONFIG_SOC_LEDC_FADE_PARAMS_BIT_WIDTH=10 +CONFIG_SOC_LEDC_SUPPORT_SLEEP_RETENTION=y +CONFIG_SOC_MMU_PERIPH_NUM=2 +CONFIG_SOC_MMU_LINEAR_ADDRESS_REGION_NUM=2 +CONFIG_SOC_MMU_DI_VADDR_SHARED=y +CONFIG_SOC_MMU_PER_EXT_MEM_TARGET=y +CONFIG_SOC_MPU_MIN_REGION_SIZE=0x20000000 +CONFIG_SOC_MPU_REGIONS_MAX_NUM=8 +CONFIG_SOC_PCNT_GROUPS=1 +CONFIG_SOC_PCNT_UNITS_PER_GROUP=4 +CONFIG_SOC_PCNT_CHANNELS_PER_UNIT=2 +CONFIG_SOC_PCNT_THRES_POINT_PER_UNIT=2 +CONFIG_SOC_PCNT_SUPPORT_RUNTIME_THRES_UPDATE=y +CONFIG_SOC_PCNT_SUPPORT_CLEAR_SIGNAL=y +CONFIG_SOC_PCNT_SUPPORT_SLEEP_RETENTION=y +CONFIG_SOC_RMT_GROUPS=1 +CONFIG_SOC_RMT_TX_CANDIDATES_PER_GROUP=4 +CONFIG_SOC_RMT_RX_CANDIDATES_PER_GROUP=4 +CONFIG_SOC_RMT_CHANNELS_PER_GROUP=8 +CONFIG_SOC_RMT_MEM_WORDS_PER_CHANNEL=48 +CONFIG_SOC_RMT_SUPPORT_RX_PINGPONG=y +CONFIG_SOC_RMT_SUPPORT_RX_DEMODULATION=y +CONFIG_SOC_RMT_SUPPORT_TX_ASYNC_STOP=y +CONFIG_SOC_RMT_SUPPORT_TX_LOOP_COUNT=y +CONFIG_SOC_RMT_SUPPORT_TX_LOOP_AUTO_STOP=y +CONFIG_SOC_RMT_SUPPORT_TX_SYNCHRO=y +CONFIG_SOC_RMT_SUPPORT_TX_CARRIER_DATA_ONLY=y +CONFIG_SOC_RMT_SUPPORT_XTAL=y +CONFIG_SOC_RMT_SUPPORT_RC_FAST=y +CONFIG_SOC_RMT_SUPPORT_DMA=y +CONFIG_SOC_RMT_SUPPORT_SLEEP_RETENTION=y +CONFIG_SOC_LCD_I80_SUPPORTED=y +CONFIG_SOC_LCD_RGB_SUPPORTED=y +CONFIG_SOC_LCDCAM_I80_NUM_BUSES=1 +CONFIG_SOC_LCDCAM_I80_BUS_WIDTH=24 +CONFIG_SOC_LCDCAM_RGB_NUM_PANELS=1 +CONFIG_SOC_LCDCAM_RGB_DATA_WIDTH=24 +CONFIG_SOC_LCD_SUPPORT_RGB_YUV_CONV=y +CONFIG_SOC_MCPWM_GROUPS=2 +CONFIG_SOC_MCPWM_TIMERS_PER_GROUP=3 +CONFIG_SOC_MCPWM_OPERATORS_PER_GROUP=3 +CONFIG_SOC_MCPWM_COMPARATORS_PER_OPERATOR=2 +CONFIG_SOC_MCPWM_EVENT_COMPARATORS_PER_OPERATOR=2 +CONFIG_SOC_MCPWM_GENERATORS_PER_OPERATOR=2 +CONFIG_SOC_MCPWM_TRIGGERS_PER_OPERATOR=2 +CONFIG_SOC_MCPWM_GPIO_FAULTS_PER_GROUP=3 +CONFIG_SOC_MCPWM_CAPTURE_TIMERS_PER_GROUP=y +CONFIG_SOC_MCPWM_CAPTURE_CHANNELS_PER_TIMER=3 +CONFIG_SOC_MCPWM_GPIO_SYNCHROS_PER_GROUP=3 +CONFIG_SOC_MCPWM_SWSYNC_CAN_PROPAGATE=y +CONFIG_SOC_MCPWM_SUPPORT_ETM=y +CONFIG_SOC_MCPWM_SUPPORT_EVENT_COMPARATOR=y +CONFIG_SOC_MCPWM_CAPTURE_CLK_FROM_GROUP=y +CONFIG_SOC_MCPWM_SUPPORT_SLEEP_RETENTION=y +CONFIG_SOC_USB_OTG_PERIPH_NUM=2 +CONFIG_SOC_USB_UTMI_PHY_NUM=1 +CONFIG_SOC_PARLIO_GROUPS=1 +CONFIG_SOC_PARLIO_TX_UNITS_PER_GROUP=1 +CONFIG_SOC_PARLIO_RX_UNITS_PER_GROUP=1 +CONFIG_SOC_PARLIO_TX_UNIT_MAX_DATA_WIDTH=16 +CONFIG_SOC_PARLIO_RX_UNIT_MAX_DATA_WIDTH=16 +CONFIG_SOC_PARLIO_TX_CLK_SUPPORT_GATING=y +CONFIG_SOC_PARLIO_RX_CLK_SUPPORT_GATING=y +CONFIG_SOC_PARLIO_RX_CLK_SUPPORT_OUTPUT=y +CONFIG_SOC_PARLIO_TRANS_BIT_ALIGN=y +CONFIG_SOC_PARLIO_TX_SIZE_BY_DMA=y +CONFIG_SOC_PARLIO_SUPPORT_SLEEP_RETENTION=y +CONFIG_SOC_MPI_MEM_BLOCKS_NUM=4 +CONFIG_SOC_MPI_OPERATIONS_NUM=3 +CONFIG_SOC_RSA_MAX_BIT_LEN=4096 +CONFIG_SOC_SDMMC_USE_IOMUX=y +CONFIG_SOC_SDMMC_USE_GPIO_MATRIX=y +CONFIG_SOC_SDMMC_NUM_SLOTS=2 +CONFIG_SOC_SDMMC_DELAY_PHASE_NUM=4 +CONFIG_SOC_SDMMC_IO_POWER_EXTERNAL=y +CONFIG_SOC_SDMMC_PSRAM_DMA_CAPABLE=y +CONFIG_SOC_SDMMC_UHS_I_SUPPORTED=y +CONFIG_SOC_SHA_DMA_MAX_BUFFER_SIZE=3968 +CONFIG_SOC_SHA_SUPPORT_DMA=y +CONFIG_SOC_SHA_SUPPORT_RESUME=y +CONFIG_SOC_SHA_GDMA=y +CONFIG_SOC_SHA_SUPPORT_SHA1=y +CONFIG_SOC_SHA_SUPPORT_SHA224=y +CONFIG_SOC_SHA_SUPPORT_SHA256=y +CONFIG_SOC_SHA_SUPPORT_SHA384=y +CONFIG_SOC_SHA_SUPPORT_SHA512=y +CONFIG_SOC_SHA_SUPPORT_SHA512_224=y +CONFIG_SOC_SHA_SUPPORT_SHA512_256=y +CONFIG_SOC_SHA_SUPPORT_SHA512_T=y +CONFIG_SOC_ECDSA_SUPPORT_EXPORT_PUBKEY=y +CONFIG_SOC_ECDSA_SUPPORT_DETERMINISTIC_MODE=y +CONFIG_SOC_ECDSA_USES_MPI=y +CONFIG_SOC_SDM_GROUPS=1 +CONFIG_SOC_SDM_CHANNELS_PER_GROUP=8 +CONFIG_SOC_SDM_CLK_SUPPORT_PLL_F80M=y +CONFIG_SOC_SDM_CLK_SUPPORT_XTAL=y +CONFIG_SOC_SPI_PERIPH_NUM=3 +CONFIG_SOC_SPI_MAX_CS_NUM=6 +CONFIG_SOC_SPI_MAXIMUM_BUFFER_SIZE=64 +CONFIG_SOC_SPI_SUPPORT_SLEEP_RETENTION=y +CONFIG_SOC_SPI_SUPPORT_SLAVE_HD_VER2=y +CONFIG_SOC_SPI_SLAVE_SUPPORT_SEG_TRANS=y +CONFIG_SOC_SPI_SUPPORT_DDRCLK=y +CONFIG_SOC_SPI_SUPPORT_CD_SIG=y +CONFIG_SOC_SPI_SUPPORT_OCT=y +CONFIG_SOC_SPI_SUPPORT_CLK_XTAL=y +CONFIG_SOC_SPI_SUPPORT_CLK_RC_FAST=y +CONFIG_SOC_SPI_SUPPORT_CLK_SPLL=y +CONFIG_SOC_MEMSPI_IS_INDEPENDENT=y +CONFIG_SOC_SPI_MAX_PRE_DIVIDER=16 +CONFIG_SOC_LP_SPI_PERIPH_NUM=y +CONFIG_SOC_LP_SPI_MAXIMUM_BUFFER_SIZE=64 +CONFIG_SOC_SPIRAM_XIP_SUPPORTED=y +CONFIG_SOC_SPI_MEM_SUPPORT_AUTO_WAIT_IDLE=y +CONFIG_SOC_SPI_MEM_SUPPORT_AUTO_SUSPEND=y +CONFIG_SOC_SPI_MEM_SUPPORT_AUTO_RESUME=y +CONFIG_SOC_SPI_MEM_SUPPORT_IDLE_INTR=y +CONFIG_SOC_SPI_MEM_SUPPORT_SW_SUSPEND=y +CONFIG_SOC_SPI_MEM_SUPPORT_CHECK_SUS=y +CONFIG_SOC_SPI_MEM_SUPPORT_TIMING_TUNING=y +CONFIG_SOC_MEMSPI_TIMING_TUNING_BY_DQS=y +CONFIG_SOC_SPI_MEM_SUPPORT_CACHE_32BIT_ADDR_MAP=y +CONFIG_SOC_SPI_PERIPH_SUPPORT_CONTROL_DUMMY_OUT=y +CONFIG_SOC_MEMSPI_SRC_FREQ_80M_SUPPORTED=y +CONFIG_SOC_MEMSPI_SRC_FREQ_40M_SUPPORTED=y +CONFIG_SOC_MEMSPI_SRC_FREQ_20M_SUPPORTED=y +CONFIG_SOC_MEMSPI_FLASH_PSRAM_INDEPENDENT=y +CONFIG_SOC_SYSTIMER_COUNTER_NUM=2 +CONFIG_SOC_SYSTIMER_ALARM_NUM=3 +CONFIG_SOC_SYSTIMER_BIT_WIDTH_LO=32 +CONFIG_SOC_SYSTIMER_BIT_WIDTH_HI=20 +CONFIG_SOC_SYSTIMER_FIXED_DIVIDER=y +CONFIG_SOC_SYSTIMER_SUPPORT_RC_FAST=y +CONFIG_SOC_SYSTIMER_INT_LEVEL=y +CONFIG_SOC_SYSTIMER_ALARM_MISS_COMPENSATE=y +CONFIG_SOC_SYSTIMER_SUPPORT_ETM=y +CONFIG_SOC_LP_TIMER_BIT_WIDTH_LO=32 +CONFIG_SOC_LP_TIMER_BIT_WIDTH_HI=16 +CONFIG_SOC_TIMER_GROUPS=2 +CONFIG_SOC_TIMER_GROUP_TIMERS_PER_GROUP=2 +CONFIG_SOC_TIMER_GROUP_COUNTER_BIT_WIDTH=54 +CONFIG_SOC_TIMER_GROUP_SUPPORT_XTAL=y +CONFIG_SOC_TIMER_GROUP_SUPPORT_RC_FAST=y +CONFIG_SOC_TIMER_GROUP_TOTAL_TIMERS=4 +CONFIG_SOC_TIMER_SUPPORT_ETM=y +CONFIG_SOC_TIMER_SUPPORT_SLEEP_RETENTION=y +CONFIG_SOC_MWDT_SUPPORT_XTAL=y +CONFIG_SOC_MWDT_SUPPORT_SLEEP_RETENTION=y +CONFIG_SOC_TOUCH_SENSOR_VERSION=3 +CONFIG_SOC_TOUCH_SENSOR_NUM=14 +CONFIG_SOC_TOUCH_SUPPORT_SLEEP_WAKEUP=y +CONFIG_SOC_TOUCH_SUPPORT_WATERPROOF=y +CONFIG_SOC_TOUCH_SUPPORT_PROX_SENSING=y +CONFIG_SOC_TOUCH_PROXIMITY_CHANNEL_NUM=3 +CONFIG_SOC_TOUCH_PROXIMITY_MEAS_DONE_SUPPORTED=y +CONFIG_SOC_TOUCH_SUPPORT_FREQ_HOP=y +CONFIG_SOC_TOUCH_SAMPLE_CFG_NUM=3 +CONFIG_SOC_TWAI_CONTROLLER_NUM=3 +CONFIG_SOC_TWAI_CLK_SUPPORT_XTAL=y +CONFIG_SOC_TWAI_BRP_MIN=2 +CONFIG_SOC_TWAI_BRP_MAX=32768 +CONFIG_SOC_TWAI_SUPPORTS_RX_STATUS=y +CONFIG_SOC_TWAI_SUPPORT_SLEEP_RETENTION=y +CONFIG_SOC_EFUSE_DIS_PAD_JTAG=y +CONFIG_SOC_EFUSE_DIS_USB_JTAG=y +CONFIG_SOC_EFUSE_DIS_DIRECT_BOOT=y +CONFIG_SOC_EFUSE_SOFT_DIS_JTAG=y +CONFIG_SOC_EFUSE_DIS_DOWNLOAD_MSPI=y +CONFIG_SOC_EFUSE_ECDSA_KEY=y +CONFIG_SOC_KEY_MANAGER_ECDSA_KEY_DEPLOY=y +CONFIG_SOC_KEY_MANAGER_FE_KEY_DEPLOY=y +CONFIG_SOC_SECURE_BOOT_V2_RSA=y +CONFIG_SOC_SECURE_BOOT_V2_ECC=y +CONFIG_SOC_EFUSE_SECURE_BOOT_KEY_DIGESTS=3 +CONFIG_SOC_EFUSE_REVOKE_BOOT_KEY_DIGESTS=y +CONFIG_SOC_SUPPORT_SECURE_BOOT_REVOKE_KEY=y +CONFIG_SOC_FLASH_ENCRYPTED_XTS_AES_BLOCK_MAX=64 +CONFIG_SOC_FLASH_ENCRYPTION_XTS_AES=y +CONFIG_SOC_FLASH_ENCRYPTION_XTS_AES_OPTIONS=y +CONFIG_SOC_FLASH_ENCRYPTION_XTS_AES_128=y +CONFIG_SOC_FLASH_ENCRYPTION_XTS_AES_256=y +CONFIG_SOC_UART_NUM=6 +CONFIG_SOC_UART_HP_NUM=5 +CONFIG_SOC_UART_LP_NUM=1 +CONFIG_SOC_UART_FIFO_LEN=128 +CONFIG_SOC_LP_UART_FIFO_LEN=16 +CONFIG_SOC_UART_BITRATE_MAX=5000000 +CONFIG_SOC_UART_SUPPORT_PLL_F80M_CLK=y +CONFIG_SOC_UART_SUPPORT_RTC_CLK=y +CONFIG_SOC_UART_SUPPORT_XTAL_CLK=y +CONFIG_SOC_UART_SUPPORT_WAKEUP_INT=y +CONFIG_SOC_UART_HAS_LP_UART=y +CONFIG_SOC_UART_SUPPORT_SLEEP_RETENTION=y +CONFIG_SOC_UART_SUPPORT_FSM_TX_WAIT_SEND=y +CONFIG_SOC_LP_I2S_SUPPORT_VAD=y +CONFIG_SOC_COEX_HW_PTI=y +CONFIG_SOC_PHY_DIG_REGS_MEM_SIZE=21 +CONFIG_SOC_WIFI_LIGHT_SLEEP_CLK_WIDTH=12 +CONFIG_SOC_PM_SUPPORT_EXT1_WAKEUP=y +CONFIG_SOC_PM_SUPPORT_EXT1_WAKEUP_MODE_PER_PIN=y +CONFIG_SOC_PM_EXT1_WAKEUP_BY_PMU=y +CONFIG_SOC_PM_SUPPORT_WIFI_WAKEUP=y +CONFIG_SOC_PM_SUPPORT_TOUCH_SENSOR_WAKEUP=y +CONFIG_SOC_PM_SUPPORT_XTAL32K_PD=y +CONFIG_SOC_PM_SUPPORT_RC32K_PD=y +CONFIG_SOC_PM_SUPPORT_RC_FAST_PD=y +CONFIG_SOC_PM_SUPPORT_VDDSDIO_PD=y +CONFIG_SOC_PM_SUPPORT_TOP_PD=y +CONFIG_SOC_PM_SUPPORT_CNNT_PD=y +CONFIG_SOC_PM_SUPPORT_RTC_PERIPH_PD=y +CONFIG_SOC_PM_SUPPORT_DEEPSLEEP_CHECK_STUB_ONLY=y +CONFIG_SOC_PM_CPU_RETENTION_BY_SW=y +CONFIG_SOC_PM_CACHE_RETENTION_BY_PAU=y +CONFIG_SOC_PM_PAU_LINK_NUM=4 +CONFIG_SOC_PM_PAU_REGDMA_LINK_MULTI_ADDR=y +CONFIG_SOC_PAU_IN_TOP_DOMAIN=y +CONFIG_SOC_CPU_IN_TOP_DOMAIN=y +CONFIG_SOC_PM_PAU_REGDMA_UPDATE_CACHE_BEFORE_WAIT_COMPARE=y +CONFIG_SOC_SLEEP_SYSTIMER_STALL_WORKAROUND=y +CONFIG_SOC_SLEEP_TGWDT_STOP_WORKAROUND=y +CONFIG_SOC_PM_RETENTION_MODULE_NUM=64 +CONFIG_SOC_PSRAM_VDD_POWER_MPLL=y +CONFIG_SOC_CLK_RC_FAST_SUPPORT_CALIBRATION=y +CONFIG_SOC_CLK_APLL_SUPPORTED=y +CONFIG_SOC_CLK_MPLL_SUPPORTED=y +CONFIG_SOC_CLK_SDIO_PLL_SUPPORTED=y +CONFIG_SOC_CLK_XTAL32K_SUPPORTED=y +CONFIG_SOC_CLK_RC32K_SUPPORTED=y +CONFIG_SOC_CLK_LP_FAST_SUPPORT_LP_PLL=y +CONFIG_SOC_CLK_LP_FAST_SUPPORT_XTAL=y +CONFIG_SOC_PERIPH_CLK_CTRL_SHARED=y +CONFIG_SOC_TEMPERATURE_SENSOR_LP_PLL_SUPPORT=y +CONFIG_SOC_TEMPERATURE_SENSOR_INTR_SUPPORT=y +CONFIG_SOC_TSENS_IS_INDEPENDENT_FROM_ADC=y +CONFIG_SOC_TEMPERATURE_SENSOR_SUPPORT_ETM=y +CONFIG_SOC_TEMPERATURE_SENSOR_SUPPORT_SLEEP_RETENTION=y +CONFIG_SOC_MEM_TCM_SUPPORTED=y +CONFIG_SOC_MEM_NON_CONTIGUOUS_SRAM=y +CONFIG_SOC_ASYNCHRONOUS_BUS_ERROR_MODE=y +CONFIG_SOC_EMAC_IEEE_1588_SUPPORT=y +CONFIG_SOC_EMAC_USE_MULTI_IO_MUX=y +CONFIG_SOC_EMAC_MII_USE_GPIO_MATRIX=y +CONFIG_SOC_JPEG_CODEC_SUPPORTED=y +CONFIG_SOC_JPEG_DECODE_SUPPORTED=y +CONFIG_SOC_JPEG_ENCODE_SUPPORTED=y +CONFIG_SOC_LCDCAM_CAM_SUPPORT_RGB_YUV_CONV=y +CONFIG_SOC_LCDCAM_CAM_PERIPH_NUM=1 +CONFIG_SOC_LCDCAM_CAM_DATA_WIDTH_MAX=16 +CONFIG_SOC_LP_CORE_SUPPORT_ETM=y +CONFIG_SOC_LP_CORE_SUPPORT_LP_ADC=y +CONFIG_SOC_LP_CORE_SUPPORT_LP_VAD=y +CONFIG_IDF_CMAKE=y +CONFIG_IDF_TOOLCHAIN="gcc" +CONFIG_IDF_TOOLCHAIN_GCC=y +CONFIG_IDF_TARGET_ARCH_RISCV=y +CONFIG_IDF_TARGET_ARCH="riscv" +CONFIG_IDF_TARGET="esp32p4" +CONFIG_IDF_INIT_VERSION="5.5.0" +CONFIG_IDF_TARGET_ESP32P4=y +CONFIG_IDF_FIRMWARE_CHIP_ID=0x0012 + +# +# Build type +# +CONFIG_APP_BUILD_TYPE_APP_2NDBOOT=y +# CONFIG_APP_BUILD_TYPE_RAM is not set +CONFIG_APP_BUILD_GENERATE_BINARIES=y +CONFIG_APP_BUILD_BOOTLOADER=y +CONFIG_APP_BUILD_USE_FLASH_SECTIONS=y +# CONFIG_APP_REPRODUCIBLE_BUILD is not set +# CONFIG_APP_NO_BLOBS is not set +# end of Build type + +# +# Bootloader config +# + +# +# Bootloader manager +# +CONFIG_BOOTLOADER_COMPILE_TIME_DATE=y +CONFIG_BOOTLOADER_PROJECT_VER=1 +# end of Bootloader manager + +CONFIG_BOOTLOADER_OFFSET_IN_FLASH=0x2000 +CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_SIZE=y +# CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_DEBUG is not set +# CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_PERF is not set + +# +# Log +# +# CONFIG_BOOTLOADER_LOG_LEVEL_NONE is not set +# CONFIG_BOOTLOADER_LOG_LEVEL_ERROR is not set +# CONFIG_BOOTLOADER_LOG_LEVEL_WARN is not set +CONFIG_BOOTLOADER_LOG_LEVEL_INFO=y +# CONFIG_BOOTLOADER_LOG_LEVEL_DEBUG is not set +# CONFIG_BOOTLOADER_LOG_LEVEL_VERBOSE is not set +CONFIG_BOOTLOADER_LOG_LEVEL=3 + +# +# Format +# +# CONFIG_BOOTLOADER_LOG_COLORS is not set +CONFIG_BOOTLOADER_LOG_TIMESTAMP_SOURCE_CPU_TICKS=y +# end of Format +# end of Log + +# +# Serial Flash Configurations +# +# CONFIG_BOOTLOADER_FLASH_DC_AWARE is not set +CONFIG_BOOTLOADER_FLASH_XMC_SUPPORT=y +# end of Serial Flash Configurations + +# CONFIG_BOOTLOADER_FACTORY_RESET is not set +# CONFIG_BOOTLOADER_APP_TEST is not set +CONFIG_BOOTLOADER_REGION_PROTECTION_ENABLE=y +CONFIG_BOOTLOADER_WDT_ENABLE=y +# CONFIG_BOOTLOADER_WDT_DISABLE_IN_USER_CODE is not set +CONFIG_BOOTLOADER_WDT_TIME_MS=9000 +# CONFIG_BOOTLOADER_APP_ROLLBACK_ENABLE is not set +# CONFIG_BOOTLOADER_SKIP_VALIDATE_IN_DEEP_SLEEP is not set +# CONFIG_BOOTLOADER_SKIP_VALIDATE_ON_POWER_ON is not set +# CONFIG_BOOTLOADER_SKIP_VALIDATE_ALWAYS is not set +CONFIG_BOOTLOADER_RESERVE_RTC_SIZE=0 +# CONFIG_BOOTLOADER_CUSTOM_RESERVE_RTC is not set +# end of Bootloader config + +# +# Security features +# +CONFIG_SECURE_BOOT_V2_RSA_SUPPORTED=y +CONFIG_SECURE_BOOT_V2_ECC_SUPPORTED=y +CONFIG_SECURE_BOOT_V2_PREFERRED=y +# CONFIG_SECURE_SIGNED_APPS_NO_SECURE_BOOT is not set +# CONFIG_SECURE_BOOT is not set +# CONFIG_SECURE_FLASH_ENC_ENABLED is not set +CONFIG_SECURE_ROM_DL_MODE_ENABLED=y +# end of Security features + +# +# Application manager +# +CONFIG_APP_COMPILE_TIME_DATE=y +# CONFIG_APP_EXCLUDE_PROJECT_VER_VAR is not set +# CONFIG_APP_EXCLUDE_PROJECT_NAME_VAR is not set +# CONFIG_APP_PROJECT_VER_FROM_CONFIG is not set +CONFIG_APP_RETRIEVE_LEN_ELF_SHA=9 +# end of Application manager + +CONFIG_ESP_ROM_HAS_CRC_LE=y +CONFIG_ESP_ROM_HAS_CRC_BE=y +CONFIG_ESP_ROM_UART_CLK_IS_XTAL=y +CONFIG_ESP_ROM_USB_SERIAL_DEVICE_NUM=6 +CONFIG_ESP_ROM_USB_OTG_NUM=5 +CONFIG_ESP_ROM_HAS_RETARGETABLE_LOCKING=y +CONFIG_ESP_ROM_GET_CLK_FREQ=y +CONFIG_ESP_ROM_HAS_RVFPLIB=y +CONFIG_ESP_ROM_HAS_HAL_WDT=y +CONFIG_ESP_ROM_HAS_HAL_SYSTIMER=y +CONFIG_ESP_ROM_HAS_LAYOUT_TABLE=y +CONFIG_ESP_ROM_WDT_INIT_PATCH=y +CONFIG_ESP_ROM_HAS_LP_ROM=y +CONFIG_ESP_ROM_WITHOUT_REGI2C=y +CONFIG_ESP_ROM_HAS_NEWLIB=y +CONFIG_ESP_ROM_HAS_NEWLIB_NANO_FORMAT=y +CONFIG_ESP_ROM_HAS_NEWLIB_NANO_PRINTF_FLOAT_BUG=y +CONFIG_ESP_ROM_HAS_VERSION=y +CONFIG_ESP_ROM_CLIC_INT_TYPE_PATCH=y +CONFIG_ESP_ROM_HAS_OUTPUT_PUTC_FUNC=y + +# +# Boot ROM Behavior +# +CONFIG_BOOT_ROM_LOG_ALWAYS_ON=y +# CONFIG_BOOT_ROM_LOG_ALWAYS_OFF is not set +# CONFIG_BOOT_ROM_LOG_ON_GPIO_HIGH is not set +# CONFIG_BOOT_ROM_LOG_ON_GPIO_LOW is not set +# end of Boot ROM Behavior + +# +# Serial flasher config +# +# CONFIG_ESPTOOLPY_NO_STUB is not set +CONFIG_ESPTOOLPY_FLASHMODE_QIO=y +# CONFIG_ESPTOOLPY_FLASHMODE_QOUT is not set +# CONFIG_ESPTOOLPY_FLASHMODE_DIO is not set +# CONFIG_ESPTOOLPY_FLASHMODE_DOUT is not set +CONFIG_ESPTOOLPY_FLASH_SAMPLE_MODE_STR=y +CONFIG_ESPTOOLPY_FLASHMODE="dio" +CONFIG_ESPTOOLPY_FLASHFREQ_80M=y +# CONFIG_ESPTOOLPY_FLASHFREQ_40M is not set +# CONFIG_ESPTOOLPY_FLASHFREQ_20M is not set +CONFIG_ESPTOOLPY_FLASHFREQ_VAL=80 +CONFIG_ESPTOOLPY_FLASHFREQ="80m" +# CONFIG_ESPTOOLPY_FLASHSIZE_1MB is not set +# CONFIG_ESPTOOLPY_FLASHSIZE_2MB is not set +# CONFIG_ESPTOOLPY_FLASHSIZE_4MB is not set +# CONFIG_ESPTOOLPY_FLASHSIZE_8MB is not set +CONFIG_ESPTOOLPY_FLASHSIZE_16MB=y +# CONFIG_ESPTOOLPY_FLASHSIZE_32MB is not set +# CONFIG_ESPTOOLPY_FLASHSIZE_64MB is not set +# CONFIG_ESPTOOLPY_FLASHSIZE_128MB is not set +CONFIG_ESPTOOLPY_FLASHSIZE="16MB" +# CONFIG_ESPTOOLPY_HEADER_FLASHSIZE_UPDATE is not set +CONFIG_ESPTOOLPY_BEFORE_RESET=y +# CONFIG_ESPTOOLPY_BEFORE_NORESET is not set +CONFIG_ESPTOOLPY_BEFORE="default_reset" +CONFIG_ESPTOOLPY_AFTER_RESET=y +# CONFIG_ESPTOOLPY_AFTER_NORESET is not set +CONFIG_ESPTOOLPY_AFTER="hard_reset" +CONFIG_ESPTOOLPY_MONITOR_BAUD=115200 +# end of Serial flasher config + +# +# Partition Table +# +# CONFIG_PARTITION_TABLE_SINGLE_APP is not set +# CONFIG_PARTITION_TABLE_SINGLE_APP_LARGE is not set +# CONFIG_PARTITION_TABLE_TWO_OTA is not set +# CONFIG_PARTITION_TABLE_TWO_OTA_LARGE is not set +CONFIG_PARTITION_TABLE_CUSTOM=y +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions_16mb.csv" +CONFIG_PARTITION_TABLE_FILENAME="partitions_16mb.csv" +CONFIG_PARTITION_TABLE_OFFSET=0x8000 +CONFIG_PARTITION_TABLE_MD5=y +# end of Partition Table + +# +# Compiler options +# +# CONFIG_COMPILER_OPTIMIZATION_DEBUG is not set +# CONFIG_COMPILER_OPTIMIZATION_SIZE is not set +CONFIG_COMPILER_OPTIMIZATION_PERF=y +# CONFIG_COMPILER_OPTIMIZATION_NONE is not set +CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_ENABLE=y +# CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_SILENT is not set +# CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_DISABLE is not set +CONFIG_COMPILER_ASSERT_NDEBUG_EVALUATE=y +# CONFIG_COMPILER_FLOAT_LIB_FROM_GCCLIB is not set +CONFIG_COMPILER_FLOAT_LIB_FROM_RVFPLIB=y +CONFIG_COMPILER_OPTIMIZATION_ASSERTION_LEVEL=2 +# CONFIG_COMPILER_OPTIMIZATION_CHECKS_SILENT is not set +CONFIG_COMPILER_HIDE_PATHS_MACROS=y +# CONFIG_COMPILER_CXX_EXCEPTIONS is not set +# CONFIG_COMPILER_CXX_RTTI is not set +CONFIG_COMPILER_STACK_CHECK_MODE_NONE=y +# CONFIG_COMPILER_STACK_CHECK_MODE_NORM is not set +# CONFIG_COMPILER_STACK_CHECK_MODE_STRONG is not set +# CONFIG_COMPILER_STACK_CHECK_MODE_ALL is not set +# CONFIG_COMPILER_NO_MERGE_CONSTANTS is not set +# CONFIG_COMPILER_WARN_WRITE_STRINGS is not set +# CONFIG_COMPILER_SAVE_RESTORE_LIBCALLS is not set +CONFIG_COMPILER_DISABLE_DEFAULT_ERRORS=y +# CONFIG_COMPILER_DISABLE_GCC12_WARNINGS is not set +# CONFIG_COMPILER_DISABLE_GCC13_WARNINGS is not set +# CONFIG_COMPILER_DISABLE_GCC14_WARNINGS is not set +# CONFIG_COMPILER_DUMP_RTL_FILES is not set +CONFIG_COMPILER_RT_LIB_GCCLIB=y +CONFIG_COMPILER_RT_LIB_NAME="gcc" +CONFIG_COMPILER_ORPHAN_SECTIONS_WARNING=y +# CONFIG_COMPILER_ORPHAN_SECTIONS_PLACE is not set +# CONFIG_COMPILER_STATIC_ANALYZER is not set +# end of Compiler options + +# +# Component config +# + +# +# Application Level Tracing +# +# CONFIG_APPTRACE_DEST_JTAG is not set +CONFIG_APPTRACE_DEST_NONE=y +# CONFIG_APPTRACE_DEST_UART0 is not set +# CONFIG_APPTRACE_DEST_UART1 is not set +# CONFIG_APPTRACE_DEST_UART2 is not set +CONFIG_APPTRACE_DEST_UART_NONE=y +CONFIG_APPTRACE_UART_TASK_PRIO=1 +CONFIG_APPTRACE_LOCK_ENABLE=y +# end of Application Level Tracing + +# +# Bluetooth +# +CONFIG_BT_ENABLED=y +CONFIG_BT_CONTROLLER_DISABLED=y +CONFIG_BT_BLUEDROID_ENABLED=n +CONFIG_BT_NIMBLE_ENABLED=y +CONFIG_BT_NIMBLE_TRANSPORT_UART=n +CONFIG_BT_NIMBLE_SVC_GAP_DEVICE_NAME="MPY ESP32" +CONFIG_BT_ALARM_MAX_NUM=50 +# end of Bluetooth + +# +# Console Library +# +# CONFIG_CONSOLE_SORTED_HELP is not set +# end of Console Library + +# +# Driver Configurations +# + +# +# TWAI Configuration +# +# CONFIG_TWAI_ISR_IN_IRAM is not set +# end of TWAI Configuration + +# +# Legacy ADC Driver Configuration +# +# CONFIG_ADC_SUPPRESS_DEPRECATE_WARN is not set +# CONFIG_ADC_SKIP_LEGACY_CONFLICT_CHECK is not set + +# +# Legacy ADC Calibration Configuration +# +# CONFIG_ADC_CALI_SUPPRESS_DEPRECATE_WARN is not set +# end of Legacy ADC Calibration Configuration +# end of Legacy ADC Driver Configuration + +# +# Legacy MCPWM Driver Configurations +# +# CONFIG_MCPWM_SUPPRESS_DEPRECATE_WARN is not set +# CONFIG_MCPWM_SKIP_LEGACY_CONFLICT_CHECK is not set +# end of Legacy MCPWM Driver Configurations + +# +# Legacy Timer Group Driver Configurations +# +# CONFIG_GPTIMER_SUPPRESS_DEPRECATE_WARN is not set +# CONFIG_GPTIMER_SKIP_LEGACY_CONFLICT_CHECK is not set +# end of Legacy Timer Group Driver Configurations + +# +# Legacy RMT Driver Configurations +# +# CONFIG_RMT_SUPPRESS_DEPRECATE_WARN is not set +# CONFIG_RMT_SKIP_LEGACY_CONFLICT_CHECK is not set +# end of Legacy RMT Driver Configurations + +# +# Legacy I2S Driver Configurations +# +# CONFIG_I2S_SUPPRESS_DEPRECATE_WARN is not set +# CONFIG_I2S_SKIP_LEGACY_CONFLICT_CHECK is not set +# end of Legacy I2S Driver Configurations + +# +# Legacy PCNT Driver Configurations +# +# CONFIG_PCNT_SUPPRESS_DEPRECATE_WARN is not set +# CONFIG_PCNT_SKIP_LEGACY_CONFLICT_CHECK is not set +# end of Legacy PCNT Driver Configurations + +# +# Legacy SDM Driver Configurations +# +# CONFIG_SDM_SUPPRESS_DEPRECATE_WARN is not set +# CONFIG_SDM_SKIP_LEGACY_CONFLICT_CHECK is not set +# end of Legacy SDM Driver Configurations + +# +# Legacy Temperature Sensor Driver Configurations +# +# CONFIG_TEMP_SENSOR_SUPPRESS_DEPRECATE_WARN is not set +# CONFIG_TEMP_SENSOR_SKIP_LEGACY_CONFLICT_CHECK is not set +# end of Legacy Temperature Sensor Driver Configurations +# end of Driver Configurations + +# +# eFuse Bit Manager +# +# CONFIG_EFUSE_CUSTOM_TABLE is not set +# CONFIG_EFUSE_VIRTUAL is not set +CONFIG_EFUSE_MAX_BLK_LEN=256 +# end of eFuse Bit Manager + +# +# ESP-TLS +# +CONFIG_ESP_TLS_USING_MBEDTLS=y +CONFIG_ESP_TLS_USE_DS_PERIPHERAL=y +# CONFIG_ESP_TLS_CLIENT_SESSION_TICKETS is not set +# CONFIG_ESP_TLS_SERVER_SESSION_TICKETS is not set +# CONFIG_ESP_TLS_SERVER_CERT_SELECT_HOOK is not set +# CONFIG_ESP_TLS_SERVER_MIN_AUTH_MODE_OPTIONAL is not set +# CONFIG_ESP_TLS_PSK_VERIFICATION is not set +# CONFIG_ESP_TLS_INSECURE is not set +# end of ESP-TLS + +# +# ADC and ADC Calibration +# +# CONFIG_ADC_ONESHOT_CTRL_FUNC_IN_IRAM is not set +# CONFIG_ADC_CONTINUOUS_ISR_IRAM_SAFE is not set +# CONFIG_ADC_ENABLE_DEBUG_LOG is not set +# end of ADC and ADC Calibration + +# +# Wireless Coexistence +# +# CONFIG_ESP_COEX_GPIO_DEBUG is not set +# end of Wireless Coexistence + +# +# Common ESP-related +# +CONFIG_ESP_ERR_TO_NAME_LOOKUP=y +# end of Common ESP-related + +# +# ESP-Driver:Analog Comparator Configurations +# +# CONFIG_ANA_CMPR_ISR_IRAM_SAFE is not set +# CONFIG_ANA_CMPR_CTRL_FUNC_IN_IRAM is not set +# CONFIG_ANA_CMPR_ENABLE_DEBUG_LOG is not set +# end of ESP-Driver:Analog Comparator Configurations + +# +# ESP-Driver:Camera Controller Configurations +# +# CONFIG_CAM_CTLR_MIPI_CSI_ISR_IRAM_SAFE is not set +# CONFIG_CAM_CTLR_ISP_DVP_ISR_IRAM_SAFE is not set +# CONFIG_CAM_CTLR_DVP_CAM_ISR_IRAM_SAFE is not set +# end of ESP-Driver:Camera Controller Configurations + +# +# ESP-Driver:GPIO Configurations +# +# CONFIG_GPIO_CTRL_FUNC_IN_IRAM is not set +# end of ESP-Driver:GPIO Configurations + +# +# ESP-Driver:GPTimer Configurations +# +CONFIG_GPTIMER_ISR_HANDLER_IN_IRAM=y +# CONFIG_GPTIMER_CTRL_FUNC_IN_IRAM is not set +# CONFIG_GPTIMER_ISR_IRAM_SAFE is not set +# CONFIG_GPTIMER_ENABLE_DEBUG_LOG is not set +# end of ESP-Driver:GPTimer Configurations + +# +# ESP-Driver:I2C Configurations +# +# CONFIG_I2C_ISR_IRAM_SAFE is not set +# CONFIG_I2C_ENABLE_DEBUG_LOG is not set +# CONFIG_I2C_ENABLE_SLAVE_DRIVER_VERSION_2 is not set +# end of ESP-Driver:I2C Configurations + +# +# ESP-Driver:I2S Configurations +# +# CONFIG_I2S_ISR_IRAM_SAFE is not set +# CONFIG_I2S_ENABLE_DEBUG_LOG is not set +# end of ESP-Driver:I2S Configurations + +# +# ESP-Driver:ISP Configurations +# +# CONFIG_ISP_ISR_IRAM_SAFE is not set +# CONFIG_ISP_CTRL_FUNC_IN_IRAM is not set +# end of ESP-Driver:ISP Configurations + +# +# ESP-Driver:JPEG-Codec Configurations +# +# CONFIG_JPEG_ENABLE_DEBUG_LOG is not set +# end of ESP-Driver:JPEG-Codec Configurations + +# +# ESP-Driver:LEDC Configurations +# +# CONFIG_LEDC_CTRL_FUNC_IN_IRAM is not set +# end of ESP-Driver:LEDC Configurations + +# +# ESP-Driver:MCPWM Configurations +# +# CONFIG_MCPWM_ISR_IRAM_SAFE is not set +# CONFIG_MCPWM_CTRL_FUNC_IN_IRAM is not set +# CONFIG_MCPWM_ENABLE_DEBUG_LOG is not set +# end of ESP-Driver:MCPWM Configurations + +# +# ESP-Driver:Parallel IO Configurations +# +# CONFIG_PARLIO_ENABLE_DEBUG_LOG is not set +# CONFIG_PARLIO_ISR_IRAM_SAFE is not set +# end of ESP-Driver:Parallel IO Configurations + +# +# ESP-Driver:PCNT Configurations +# +# CONFIG_PCNT_CTRL_FUNC_IN_IRAM is not set +# CONFIG_PCNT_ISR_IRAM_SAFE is not set +# CONFIG_PCNT_ENABLE_DEBUG_LOG is not set +# end of ESP-Driver:PCNT Configurations + +# +# ESP-Driver:RMT Configurations +# +# CONFIG_RMT_ISR_IRAM_SAFE is not set +# CONFIG_RMT_RECV_FUNC_IN_IRAM is not set +# CONFIG_RMT_ENABLE_DEBUG_LOG is not set +# end of ESP-Driver:RMT Configurations + +# +# ESP-Driver:Sigma Delta Modulator Configurations +# +# CONFIG_SDM_CTRL_FUNC_IN_IRAM is not set +# CONFIG_SDM_ENABLE_DEBUG_LOG is not set +# end of ESP-Driver:Sigma Delta Modulator Configurations + +# +# ESP-Driver:SPI Configurations +# +# CONFIG_SPI_MASTER_IN_IRAM is not set +CONFIG_SPI_MASTER_ISR_IN_IRAM=y +# CONFIG_SPI_SLAVE_IN_IRAM is not set +CONFIG_SPI_SLAVE_ISR_IN_IRAM=y +# end of ESP-Driver:SPI Configurations + +# +# ESP-Driver:Touch Sensor Configurations +# +# CONFIG_TOUCH_CTRL_FUNC_IN_IRAM is not set +# CONFIG_TOUCH_ISR_IRAM_SAFE is not set +# CONFIG_TOUCH_ENABLE_DEBUG_LOG is not set +# end of ESP-Driver:Touch Sensor Configurations + +# +# ESP-Driver:Temperature Sensor Configurations +# +# CONFIG_TEMP_SENSOR_ENABLE_DEBUG_LOG is not set +# CONFIG_TEMP_SENSOR_ISR_IRAM_SAFE is not set +# end of ESP-Driver:Temperature Sensor Configurations + +# +# ESP-Driver:UART Configurations +# +# CONFIG_UART_ISR_IN_IRAM is not set +# end of ESP-Driver:UART Configurations + +# +# ESP-Driver:USB Serial/JTAG Configuration +# +CONFIG_USJ_ENABLE_USB_SERIAL_JTAG=y +# end of ESP-Driver:USB Serial/JTAG Configuration + +# +# Ethernet +# +CONFIG_ETH_ENABLED=y +CONFIG_ETH_USE_ESP32_EMAC=y +CONFIG_ETH_PHY_INTERFACE_RMII=y +CONFIG_ETH_DMA_BUFFER_SIZE=512 +CONFIG_ETH_DMA_RX_BUFFER_NUM=20 +CONFIG_ETH_DMA_TX_BUFFER_NUM=10 +# CONFIG_ETH_SOFT_FLOW_CONTROL is not set +# CONFIG_ETH_IRAM_OPTIMIZATION is not set +CONFIG_ETH_USE_SPI_ETHERNET=y +# CONFIG_ETH_SPI_ETHERNET_DM9051 is not set +# CONFIG_ETH_SPI_ETHERNET_W5500 is not set +# CONFIG_ETH_SPI_ETHERNET_KSZ8851SNL is not set +# CONFIG_ETH_USE_OPENETH is not set +# CONFIG_ETH_TRANSMIT_MUTEX is not set +# end of Ethernet + +# +# Event Loop Library +# +# CONFIG_ESP_EVENT_LOOP_PROFILING is not set +CONFIG_ESP_EVENT_POST_FROM_ISR=y +CONFIG_ESP_EVENT_POST_FROM_IRAM_ISR=y +# end of Event Loop Library + +# +# GDB Stub +# +CONFIG_ESP_GDBSTUB_ENABLED=y +# CONFIG_ESP_SYSTEM_GDBSTUB_RUNTIME is not set +CONFIG_ESP_GDBSTUB_SUPPORT_TASKS=y +CONFIG_ESP_GDBSTUB_MAX_TASKS=32 +# end of GDB Stub + +# +# ESP HID +# +CONFIG_ESPHID_TASK_SIZE_BT=2048 +CONFIG_ESPHID_TASK_SIZE_BLE=4096 +# end of ESP HID + +# +# ESP HTTP client +# +CONFIG_ESP_HTTP_CLIENT_ENABLE_HTTPS=y +# CONFIG_ESP_HTTP_CLIENT_ENABLE_BASIC_AUTH is not set +# CONFIG_ESP_HTTP_CLIENT_ENABLE_DIGEST_AUTH is not set +# CONFIG_ESP_HTTP_CLIENT_ENABLE_CUSTOM_TRANSPORT is not set +CONFIG_ESP_HTTP_CLIENT_EVENT_POST_TIMEOUT=2000 +# end of ESP HTTP client + +# +# HTTP Server +# +CONFIG_HTTPD_MAX_REQ_HDR_LEN=512 +CONFIG_HTTPD_MAX_URI_LEN=512 +CONFIG_HTTPD_ERR_RESP_NO_DELAY=y +CONFIG_HTTPD_PURGE_BUF_LEN=32 +# CONFIG_HTTPD_LOG_PURGE_DATA is not set +# CONFIG_HTTPD_WS_SUPPORT is not set +# CONFIG_HTTPD_QUEUE_WORK_BLOCKING is not set +CONFIG_HTTPD_SERVER_EVENT_POST_TIMEOUT=2000 +# end of HTTP Server + +# +# ESP HTTPS OTA +# +# CONFIG_ESP_HTTPS_OTA_DECRYPT_CB is not set +# CONFIG_ESP_HTTPS_OTA_ALLOW_HTTP is not set +CONFIG_ESP_HTTPS_OTA_EVENT_POST_TIMEOUT=2000 +# end of ESP HTTPS OTA + +# +# ESP HTTPS server +# +# CONFIG_ESP_HTTPS_SERVER_ENABLE is not set +CONFIG_ESP_HTTPS_SERVER_EVENT_POST_TIMEOUT=2000 +# end of ESP HTTPS server + +# +# Hardware Settings +# + +# +# Chip revision +# +# CONFIG_ESP32P4_REV_MIN_0 is not set +CONFIG_ESP32P4_REV_MIN_1=y +# CONFIG_ESP32P4_REV_MIN_100 is not set +CONFIG_ESP32P4_REV_MIN_FULL=1 +CONFIG_ESP_REV_MIN_FULL=1 + +# +# Maximum Supported ESP32-P4 Revision (Rev v1.99) +# +CONFIG_ESP32P4_REV_MAX_FULL=199 +CONFIG_ESP_REV_MAX_FULL=199 +CONFIG_ESP_EFUSE_BLOCK_REV_MIN_FULL=0 +CONFIG_ESP_EFUSE_BLOCK_REV_MAX_FULL=99 + +# +# Maximum Supported ESP32-P4 eFuse Block Revision (eFuse Block Rev v0.99) +# +# end of Chip revision + +# +# MAC Config +# +CONFIG_ESP_MAC_ADDR_UNIVERSE_ETH=y +CONFIG_ESP_MAC_UNIVERSAL_MAC_ADDRESSES_ONE=y +CONFIG_ESP_MAC_UNIVERSAL_MAC_ADDRESSES=1 +CONFIG_ESP32P4_UNIVERSAL_MAC_ADDRESSES_ONE=y +CONFIG_ESP32P4_UNIVERSAL_MAC_ADDRESSES=1 +# CONFIG_ESP_MAC_USE_CUSTOM_MAC_AS_BASE_MAC is not set +# end of MAC Config + +# +# Sleep Config +# +CONFIG_ESP_SLEEP_FLASH_LEAKAGE_WORKAROUND=y +CONFIG_ESP_SLEEP_PSRAM_LEAKAGE_WORKAROUND=y +# CONFIG_ESP_SLEEP_MSPI_NEED_ALL_IO_PU is not set +# CONFIG_ESP_SLEEP_GPIO_RESET_WORKAROUND is not set +CONFIG_ESP_SLEEP_WAIT_FLASH_READY_EXTRA_DELAY=0 +# CONFIG_ESP_SLEEP_CACHE_SAFE_ASSERTION is not set +# CONFIG_ESP_SLEEP_DEBUG is not set +CONFIG_ESP_SLEEP_GPIO_ENABLE_INTERNAL_RESISTORS=y +# end of Sleep Config + +# +# RTC Clock Config +# +CONFIG_RTC_CLK_SRC_INT_RC=y +# CONFIG_RTC_CLK_SRC_EXT_CRYS is not set +CONFIG_RTC_CLK_CAL_CYCLES=1024 +CONFIG_RTC_FAST_CLK_SRC_RC_FAST=y +# CONFIG_RTC_FAST_CLK_SRC_XTAL is not set +# end of RTC Clock Config + +# +# Peripheral Control +# +CONFIG_PERIPH_CTRL_FUNC_IN_IRAM=y +# end of Peripheral Control + +# +# ETM Configuration +# +# CONFIG_ETM_ENABLE_DEBUG_LOG is not set +# end of ETM Configuration + +# +# GDMA Configurations +# +CONFIG_GDMA_CTRL_FUNC_IN_IRAM=y +# CONFIG_GDMA_ISR_IRAM_SAFE is not set +# CONFIG_GDMA_ENABLE_DEBUG_LOG is not set +# end of GDMA Configurations + +# +# DW_GDMA Configurations +# +# CONFIG_DW_GDMA_ENABLE_DEBUG_LOG is not set +# end of DW_GDMA Configurations + +# +# 2D-DMA Configurations +# +# CONFIG_DMA2D_OPERATION_FUNC_IN_IRAM is not set +# CONFIG_DMA2D_ISR_IRAM_SAFE is not set +# end of 2D-DMA Configurations + +# +# Main XTAL Config +# +CONFIG_XTAL_FREQ_40=y +CONFIG_XTAL_FREQ=40 +# end of Main XTAL Config + +# +# DCDC Regulator Configurations +# +CONFIG_ESP_SLEEP_KEEP_DCDC_ALWAYS_ON=y +CONFIG_ESP_SLEEP_DCM_VSET_VAL_IN_SLEEP=14 +# end of DCDC Regulator Configurations + +# +# LDO Regulator Configurations +# +CONFIG_ESP_LDO_RESERVE_SPI_NOR_FLASH=y +CONFIG_ESP_LDO_CHAN_SPI_NOR_FLASH_DOMAIN=1 +CONFIG_ESP_LDO_VOLTAGE_SPI_NOR_FLASH_3300_MV=y +CONFIG_ESP_LDO_VOLTAGE_SPI_NOR_FLASH_DOMAIN=3300 +CONFIG_ESP_LDO_RESERVE_PSRAM=y +CONFIG_ESP_LDO_CHAN_PSRAM_DOMAIN=2 +CONFIG_ESP_LDO_VOLTAGE_PSRAM_1900_MV=y +CONFIG_ESP_LDO_VOLTAGE_PSRAM_DOMAIN=1900 +# end of LDO Regulator Configurations + +CONFIG_ESP_SPI_BUS_LOCK_ISR_FUNCS_IN_IRAM=y +# end of Hardware Settings + +# +# ESP-Driver:LCD Controller Configurations +# +# CONFIG_LCD_ENABLE_DEBUG_LOG is not set +# CONFIG_LCD_RGB_ISR_IRAM_SAFE is not set +# CONFIG_LCD_RGB_RESTART_IN_VSYNC is not set +# CONFIG_LCD_DSI_ISR_IRAM_SAFE is not set +# end of ESP-Driver:LCD Controller Configurations + +# +# ESP-MM: Memory Management Configurations +# +# CONFIG_ESP_MM_CACHE_MSYNC_C2M_CHUNKED_OPS is not set +# end of ESP-MM: Memory Management Configurations + +# +# ESP NETIF Adapter +# +CONFIG_ESP_NETIF_IP_LOST_TIMER_INTERVAL=120 +# CONFIG_ESP_NETIF_PROVIDE_CUSTOM_IMPLEMENTATION is not set +CONFIG_ESP_NETIF_TCPIP_LWIP=y +# CONFIG_ESP_NETIF_LOOPBACK is not set +CONFIG_ESP_NETIF_USES_TCPIP_WITH_BSD_API=y +CONFIG_ESP_NETIF_REPORT_DATA_TRAFFIC=y +# CONFIG_ESP_NETIF_RECEIVE_REPORT_ERRORS is not set +# CONFIG_ESP_NETIF_L2_TAP is not set +# CONFIG_ESP_NETIF_BRIDGE_EN is not set +# CONFIG_ESP_NETIF_SET_DNS_PER_DEFAULT_NETIF is not set +# end of ESP NETIF Adapter + +# +# Partition API Configuration +# +# end of Partition API Configuration + +# +# PHY +# +# end of PHY + +# +# Power Management +# +# CONFIG_PM_ENABLE is not set +# CONFIG_PM_SLP_IRAM_OPT is not set +CONFIG_PM_SLP_DEFAULT_PARAMS_OPT=y +# CONFIG_PM_POWER_DOWN_PERIPHERAL_IN_LIGHT_SLEEP is not set +# end of Power Management + +# +# ESP PSRAM +# +CONFIG_SPIRAM=y + +# +# PSRAM config +# +CONFIG_SPIRAM_MODE_HEX=y +CONFIG_SPIRAM_SPEED_200M=y +# CONFIG_SPIRAM_SPEED_20M is not set +CONFIG_SPIRAM_SPEED=200 +CONFIG_SPIRAM_FETCH_INSTRUCTIONS=y +CONFIG_SPIRAM_RODATA=y +CONFIG_SPIRAM_XIP_FROM_PSRAM=y +CONFIG_SPIRAM_FLASH_LOAD_TO_PSRAM=y +# CONFIG_SPIRAM_ECC_ENABLE is not set +CONFIG_SPIRAM_BOOT_INIT=y +# CONFIG_SPIRAM_IGNORE_NOTFOUND is not set +# CONFIG_SPIRAM_USE_MEMMAP is not set +# CONFIG_SPIRAM_USE_CAPS_ALLOC is not set +CONFIG_SPIRAM_USE_MALLOC=y +CONFIG_SPIRAM_MEMTEST=y +CONFIG_SPIRAM_MALLOC_ALWAYSINTERNAL=16384 +CONFIG_SPIRAM_TRY_ALLOCATE_WIFI_LWIP=y +CONFIG_SPIRAM_MALLOC_RESERVE_INTERNAL=32768 +# CONFIG_SPIRAM_ALLOW_BSS_SEG_EXTERNAL_MEMORY is not set +# CONFIG_SPIRAM_ALLOW_NOINIT_SEG_EXTERNAL_MEMORY is not set +# end of PSRAM config +# end of ESP PSRAM + +# +# ESP Ringbuf +# +# CONFIG_RINGBUF_PLACE_FUNCTIONS_INTO_FLASH is not set +# end of ESP Ringbuf + +# +# ESP Security Specific +# +# end of ESP Security Specific + +# +# ESP System Settings +# +CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_360=y +CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ=360 + +# +# Cache config +# +# CONFIG_CACHE_L2_CACHE_128KB is not set +CONFIG_CACHE_L2_CACHE_256KB=y +# CONFIG_CACHE_L2_CACHE_512KB is not set +CONFIG_CACHE_L2_CACHE_SIZE=0x40000 +# CONFIG_CACHE_L2_CACHE_LINE_64B is not set +CONFIG_CACHE_L2_CACHE_LINE_128B=y +CONFIG_CACHE_L2_CACHE_LINE_SIZE=128 +CONFIG_CACHE_L1_CACHE_LINE_SIZE=64 +# end of Cache config + +# CONFIG_ESP_SYSTEM_PANIC_PRINT_HALT is not set +CONFIG_ESP_SYSTEM_PANIC_PRINT_REBOOT=y +# CONFIG_ESP_SYSTEM_PANIC_SILENT_REBOOT is not set +# CONFIG_ESP_SYSTEM_PANIC_GDBSTUB is not set +CONFIG_ESP_SYSTEM_PANIC_REBOOT_DELAY_SECONDS=0 +CONFIG_ESP_SYSTEM_RTC_FAST_MEM_AS_HEAP_DEPCHECK=y +CONFIG_ESP_SYSTEM_ALLOW_RTC_FAST_MEM_AS_HEAP=y +# CONFIG_ESP_SYSTEM_USE_EH_FRAME is not set + +# +# Memory protection +# +CONFIG_ESP_SYSTEM_PMP_IDRAM_SPLIT=y +# CONFIG_ESP_SYSTEM_PMP_LP_CORE_RESERVE_MEM_EXECUTABLE is not set +# end of Memory protection + +CONFIG_ESP_SYSTEM_EVENT_QUEUE_SIZE=32 +CONFIG_ESP_SYSTEM_EVENT_TASK_STACK_SIZE=4096 +CONFIG_ESP_MAIN_TASK_STACK_SIZE=4000 +CONFIG_ESP_MAIN_TASK_AFFINITY_CPU0=y +# CONFIG_ESP_MAIN_TASK_AFFINITY_CPU1 is not set +# CONFIG_ESP_MAIN_TASK_AFFINITY_NO_AFFINITY is not set +CONFIG_ESP_MAIN_TASK_AFFINITY=0x0 +CONFIG_ESP_MINIMAL_SHARED_STACK_SIZE=2048 +# CONFIG_ESP_CONSOLE_UART_DEFAULT is not set +CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG=y +# CONFIG_ESP_CONSOLE_UART_CUSTOM is not set +# CONFIG_ESP_CONSOLE_NONE is not set +CONFIG_ESP_CONSOLE_SECONDARY_NONE=y +CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG_ENABLED=y +CONFIG_ESP_CONSOLE_UART_NUM=-1 +CONFIG_ESP_CONSOLE_ROM_SERIAL_PORT_NUM=6 +CONFIG_ESP_INT_WDT=y +CONFIG_ESP_INT_WDT_TIMEOUT_MS=300 +CONFIG_ESP_INT_WDT_CHECK_CPU1=y +CONFIG_ESP_TASK_WDT_EN=y +CONFIG_ESP_TASK_WDT_INIT=y +# CONFIG_ESP_TASK_WDT_PANIC is not set +CONFIG_ESP_TASK_WDT_TIMEOUT_S=5 +CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU0=y +CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU1=y +# CONFIG_ESP_PANIC_HANDLER_IRAM is not set +# CONFIG_ESP_DEBUG_STUBS_ENABLE is not set +CONFIG_ESP_DEBUG_OCDAWARE=y +CONFIG_ESP_SYSTEM_CHECK_INT_LEVEL_4=y + +# +# Brownout Detector +# +CONFIG_ESP_BROWNOUT_DET=y +CONFIG_ESP_BROWNOUT_DET_LVL_SEL_7=y +# CONFIG_ESP_BROWNOUT_DET_LVL_SEL_6 is not set +# CONFIG_ESP_BROWNOUT_DET_LVL_SEL_5 is not set +CONFIG_ESP_BROWNOUT_DET_LVL=7 +# end of Brownout Detector + +CONFIG_ESP_SYSTEM_BROWNOUT_INTR=y +CONFIG_ESP_SYSTEM_HW_STACK_GUARD=y +CONFIG_ESP_SYSTEM_HW_PC_RECORD=y +# end of ESP System Settings + +# +# IPC (Inter-Processor Call) +# +CONFIG_ESP_IPC_TASK_STACK_SIZE=1024 +CONFIG_ESP_IPC_USES_CALLERS_PRIORITY=y +CONFIG_ESP_IPC_ISR_ENABLE=y +# end of IPC (Inter-Processor Call) + +# +# ESP Timer (High Resolution Timer) +# +# CONFIG_ESP_TIMER_PROFILING is not set +CONFIG_ESP_TIME_FUNCS_USE_RTC_TIMER=y +CONFIG_ESP_TIME_FUNCS_USE_ESP_TIMER=y +CONFIG_ESP_TIMER_TASK_STACK_SIZE=3584 +CONFIG_ESP_TIMER_INTERRUPT_LEVEL=1 +# CONFIG_ESP_TIMER_SHOW_EXPERIMENTAL is not set +CONFIG_ESP_TIMER_TASK_AFFINITY=0x0 +CONFIG_ESP_TIMER_TASK_AFFINITY_CPU0=y +CONFIG_ESP_TIMER_ISR_AFFINITY_CPU0=y +# CONFIG_ESP_TIMER_SUPPORTS_ISR_DISPATCH_METHOD is not set +CONFIG_ESP_TIMER_IMPL_SYSTIMER=y +# end of ESP Timer (High Resolution Timer) + +# +# Wi-Fi +# +# CONFIG_ESP_HOST_WIFI_ENABLED is not set +CONFIG_ESP_WIFI_STATIC_RX_BUFFER_NUM=10 +CONFIG_ESP_WIFI_DYNAMIC_RX_BUFFER_NUM=32 +CONFIG_ESP_WIFI_TX_BUFFER_TYPE=1 +CONFIG_ESP_WIFI_DYNAMIC_TX_BUFFER_NUM=32 +CONFIG_ESP_WIFI_DYNAMIC_RX_MGMT_BUF=0 +CONFIG_ESP_WIFI_RX_MGMT_BUF_NUM_DEF=5 +CONFIG_ESP_WIFI_AMPDU_TX_ENABLED=y +CONFIG_ESP_WIFI_TX_BA_WIN=6 +CONFIG_ESP_WIFI_AMPDU_RX_ENABLED=y +CONFIG_ESP_WIFI_RX_BA_WIN=6 +CONFIG_ESP_WIFI_NVS_ENABLED=y +CONFIG_ESP_WIFI_SOFTAP_BEACON_MAX_LEN=752 +CONFIG_ESP_WIFI_MGMT_SBUF_NUM=32 +CONFIG_ESP_WIFI_IRAM_OPT=y +CONFIG_ESP_WIFI_EXTRA_IRAM_OPT=y +CONFIG_ESP_WIFI_RX_IRAM_OPT=y +CONFIG_ESP_WIFI_ENABLE_WPA3_SAE=y +CONFIG_ESP_WIFI_ENABLE_SAE_PK=y +CONFIG_ESP_WIFI_SOFTAP_SAE_SUPPORT=y +CONFIG_ESP_WIFI_ENABLE_WPA3_OWE_STA=y +CONFIG_ESP_WIFI_SLP_IRAM_OPT=y +CONFIG_ESP_WIFI_SLP_DEFAULT_MIN_ACTIVE_TIME=50 +CONFIG_ESP_WIFI_SLP_DEFAULT_MAX_ACTIVE_TIME=10 +CONFIG_ESP_WIFI_SLP_DEFAULT_WAIT_BROADCAST_DATA_TIME=15 +CONFIG_ESP_WIFI_STA_DISCONNECTED_PM_ENABLE=y +CONFIG_ESP_WIFI_GMAC_SUPPORT=y +CONFIG_ESP_WIFI_SOFTAP_SUPPORT=y +CONFIG_ESP_WIFI_ESPNOW_MAX_ENCRYPT_NUM=7 +CONFIG_ESP_WIFI_MBEDTLS_CRYPTO=y +CONFIG_ESP_WIFI_MBEDTLS_TLS_CLIENT=y +CONFIG_ESP_WIFI_TX_HETB_QUEUE_NUM=3 +CONFIG_ESP_WIFI_ENTERPRISE_SUPPORT=y +# end of Wi-Fi + +# +# Core dump +# +# CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH is not set +# CONFIG_ESP_COREDUMP_ENABLE_TO_UART is not set +CONFIG_ESP_COREDUMP_ENABLE_TO_NONE=y +# end of Core dump + +# +# FAT Filesystem support +# +CONFIG_FATFS_VOLUME_COUNT=8 +# CONFIG_FATFS_LFN_NONE is not set +CONFIG_FATFS_LFN_HEAP=y +# CONFIG_FATFS_LFN_STACK is not set +# CONFIG_FATFS_SECTOR_512 is not set +CONFIG_FATFS_SECTOR_4096=y +# CONFIG_FATFS_CODEPAGE_DYNAMIC is not set +CONFIG_FATFS_CODEPAGE_437=y +# CONFIG_FATFS_CODEPAGE_720 is not set +# CONFIG_FATFS_CODEPAGE_737 is not set +# CONFIG_FATFS_CODEPAGE_771 is not set +# CONFIG_FATFS_CODEPAGE_775 is not set +# CONFIG_FATFS_CODEPAGE_850 is not set +# CONFIG_FATFS_CODEPAGE_852 is not set +# CONFIG_FATFS_CODEPAGE_855 is not set +# CONFIG_FATFS_CODEPAGE_857 is not set +# CONFIG_FATFS_CODEPAGE_860 is not set +# CONFIG_FATFS_CODEPAGE_861 is not set +# CONFIG_FATFS_CODEPAGE_862 is not set +# CONFIG_FATFS_CODEPAGE_863 is not set +# CONFIG_FATFS_CODEPAGE_864 is not set +# CONFIG_FATFS_CODEPAGE_865 is not set +# CONFIG_FATFS_CODEPAGE_866 is not set +# CONFIG_FATFS_CODEPAGE_869 is not set +# CONFIG_FATFS_CODEPAGE_932 is not set +# CONFIG_FATFS_CODEPAGE_936 is not set +# CONFIG_FATFS_CODEPAGE_949 is not set +# CONFIG_FATFS_CODEPAGE_950 is not set +CONFIG_FATFS_CODEPAGE=437 +CONFIG_FATFS_MAX_LFN=255 +CONFIG_FATFS_API_ENCODING_ANSI_OEM=y +# CONFIG_FATFS_API_ENCODING_UTF_8 is not set +CONFIG_FATFS_FS_LOCK=0 +CONFIG_FATFS_TIMEOUT_MS=10000 +CONFIG_FATFS_PER_FILE_CACHE=y +CONFIG_FATFS_ALLOC_PREFER_EXTRAM=y +# CONFIG_FATFS_USE_FASTSEEK is not set +CONFIG_FATFS_USE_STRFUNC_NONE=y +# CONFIG_FATFS_USE_STRFUNC_WITHOUT_CRLF_CONV is not set +# CONFIG_FATFS_USE_STRFUNC_WITH_CRLF_CONV is not set +CONFIG_FATFS_VFS_FSTAT_BLKSIZE=0 +# CONFIG_FATFS_IMMEDIATE_FSYNC is not set +# CONFIG_FATFS_USE_LABEL is not set +CONFIG_FATFS_LINK_LOCK=y +# end of FAT Filesystem support + +# +# FreeRTOS +# + +# +# Kernel +# +# CONFIG_FREERTOS_UNICORE is not set +CONFIG_FREERTOS_HZ=1000 +# CONFIG_FREERTOS_CHECK_STACKOVERFLOW_NONE is not set +# CONFIG_FREERTOS_CHECK_STACKOVERFLOW_PTRVAL is not set +CONFIG_FREERTOS_CHECK_STACKOVERFLOW_CANARY=y +CONFIG_FREERTOS_THREAD_LOCAL_STORAGE_POINTERS=1 +CONFIG_FREERTOS_IDLE_TASK_STACKSIZE=1536 +# CONFIG_FREERTOS_USE_IDLE_HOOK is not set +# CONFIG_FREERTOS_USE_TICK_HOOK is not set +CONFIG_FREERTOS_MAX_TASK_NAME_LEN=16 +# CONFIG_FREERTOS_ENABLE_BACKWARD_COMPATIBILITY is not set +CONFIG_FREERTOS_USE_TIMERS=y +CONFIG_FREERTOS_TIMER_SERVICE_TASK_NAME="Tmr Svc" +# CONFIG_FREERTOS_TIMER_TASK_AFFINITY_CPU0 is not set +# CONFIG_FREERTOS_TIMER_TASK_AFFINITY_CPU1 is not set +CONFIG_FREERTOS_TIMER_TASK_NO_AFFINITY=y +CONFIG_FREERTOS_TIMER_SERVICE_TASK_CORE_AFFINITY=0x7FFFFFFF +CONFIG_FREERTOS_TIMER_TASK_PRIORITY=1 +CONFIG_FREERTOS_TIMER_TASK_STACK_DEPTH=2048 +CONFIG_FREERTOS_TIMER_QUEUE_LENGTH=10 +CONFIG_FREERTOS_QUEUE_REGISTRY_SIZE=0 +CONFIG_FREERTOS_TASK_NOTIFICATION_ARRAY_ENTRIES=1 +CONFIG_FREERTOS_USE_TRACE_FACILITY=y +CONFIG_FREERTOS_USE_STATS_FORMATTING_FUNCTIONS=y +# CONFIG_FREERTOS_USE_LIST_DATA_INTEGRITY_CHECK_BYTES is not set +CONFIG_FREERTOS_VTASKLIST_INCLUDE_COREID=y +CONFIG_FREERTOS_GENERATE_RUN_TIME_STATS=y +CONFIG_FREERTOS_RUN_TIME_COUNTER_TYPE_U32=y +# CONFIG_FREERTOS_RUN_TIME_COUNTER_TYPE_U64 is not set +# CONFIG_FREERTOS_USE_APPLICATION_TASK_TAG is not set +# end of Kernel + +# +# Port +# +# CONFIG_FREERTOS_WATCHPOINT_END_OF_STACK is not set +CONFIG_FREERTOS_TLSP_DELETION_CALLBACKS=y +# CONFIG_FREERTOS_TASK_PRE_DELETION_HOOK is not set +# CONFIG_FREERTOS_ENABLE_STATIC_TASK_CLEAN_UP is not set +CONFIG_FREERTOS_CHECK_MUTEX_GIVEN_BY_OWNER=y +CONFIG_FREERTOS_ISR_STACKSIZE=1536 +CONFIG_FREERTOS_INTERRUPT_BACKTRACE=y +CONFIG_FREERTOS_TICK_SUPPORT_SYSTIMER=y +CONFIG_FREERTOS_CORETIMER_SYSTIMER_LVL1=y +# CONFIG_FREERTOS_CORETIMER_SYSTIMER_LVL3 is not set +CONFIG_FREERTOS_SYSTICK_USES_SYSTIMER=y +CONFIG_FREERTOS_RUN_TIME_STATS_USING_ESP_TIMER=y +# CONFIG_FREERTOS_PLACE_FUNCTIONS_INTO_FLASH is not set +# CONFIG_FREERTOS_CHECK_PORT_CRITICAL_COMPLIANCE is not set +# end of Port + +# +# Extra +# +CONFIG_FREERTOS_TASK_CREATE_ALLOW_EXT_MEM=y +# end of Extra + +CONFIG_FREERTOS_PORT=y +CONFIG_FREERTOS_NO_AFFINITY=0x7FFFFFFF +CONFIG_FREERTOS_SUPPORT_STATIC_ALLOCATION=y +CONFIG_FREERTOS_DEBUG_OCDAWARE=y +CONFIG_FREERTOS_ENABLE_TASK_SNAPSHOT=y +CONFIG_FREERTOS_PLACE_SNAPSHOT_FUNS_INTO_FLASH=y +CONFIG_FREERTOS_NUMBER_OF_CORES=2 +# end of FreeRTOS + +# +# Hardware Abstraction Layer (HAL) and Low Level (LL) +# +CONFIG_HAL_ASSERTION_EQUALS_SYSTEM=y +# CONFIG_HAL_ASSERTION_DISABLE is not set +# CONFIG_HAL_ASSERTION_SILENT is not set +# CONFIG_HAL_ASSERTION_ENABLE is not set +CONFIG_HAL_DEFAULT_ASSERTION_LEVEL=0 +# CONFIG_HAL_SYSTIMER_USE_ROM_IMPL is not set +# CONFIG_HAL_WDT_USE_ROM_IMPL is not set +# CONFIG_HAL_SPI_MASTER_FUNC_IN_IRAM is not set +# CONFIG_HAL_SPI_SLAVE_FUNC_IN_IRAM is not set +# end of Hardware Abstraction Layer (HAL) and Low Level (LL) + +# +# Heap memory debugging +# +CONFIG_HEAP_POISONING_DISABLED=y +# CONFIG_HEAP_POISONING_LIGHT is not set +# CONFIG_HEAP_POISONING_COMPREHENSIVE is not set +CONFIG_HEAP_TRACING_OFF=y +# CONFIG_HEAP_TRACING_STANDALONE is not set +# CONFIG_HEAP_TRACING_TOHOST is not set +# CONFIG_HEAP_USE_HOOKS is not set +# CONFIG_HEAP_TASK_TRACKING is not set +# CONFIG_HEAP_ABORT_WHEN_ALLOCATION_FAILS is not set +# CONFIG_HEAP_PLACE_FUNCTION_INTO_FLASH is not set +# end of Heap memory debugging + +# +# Log +# + +# +# Log Level +# +# CONFIG_LOG_DEFAULT_LEVEL_NONE is not set +# CONFIG_LOG_DEFAULT_LEVEL_ERROR is not set +# CONFIG_LOG_DEFAULT_LEVEL_WARN is not set +CONFIG_LOG_DEFAULT_LEVEL_INFO=y +# CONFIG_LOG_DEFAULT_LEVEL_DEBUG is not set +# CONFIG_LOG_DEFAULT_LEVEL_VERBOSE is not set +CONFIG_LOG_DEFAULT_LEVEL=3 +CONFIG_LOG_MAXIMUM_EQUALS_DEFAULT=y +# CONFIG_LOG_MAXIMUM_LEVEL_DEBUG is not set +# CONFIG_LOG_MAXIMUM_LEVEL_VERBOSE is not set +CONFIG_LOG_MAXIMUM_LEVEL=3 + +# +# Level Settings +# +# CONFIG_LOG_MASTER_LEVEL is not set +CONFIG_LOG_DYNAMIC_LEVEL_CONTROL=y +# CONFIG_LOG_TAG_LEVEL_IMPL_NONE is not set +# CONFIG_LOG_TAG_LEVEL_IMPL_LINKED_LIST is not set +CONFIG_LOG_TAG_LEVEL_IMPL_CACHE_AND_LINKED_LIST=y +# CONFIG_LOG_TAG_LEVEL_CACHE_ARRAY is not set +CONFIG_LOG_TAG_LEVEL_CACHE_BINARY_MIN_HEAP=y +CONFIG_LOG_TAG_LEVEL_IMPL_CACHE_SIZE=31 +# end of Level Settings +# end of Log Level + +# +# Format +# +# CONFIG_LOG_COLORS is not set +CONFIG_LOG_TIMESTAMP_SOURCE_RTOS=y +# CONFIG_LOG_TIMESTAMP_SOURCE_SYSTEM is not set +# end of Format +# end of Log + +# +# LWIP +# +CONFIG_LWIP_ENABLE=y +CONFIG_LWIP_LOCAL_HOSTNAME="espressif" +# CONFIG_LWIP_NETIF_API is not set +CONFIG_LWIP_TCPIP_TASK_PRIO=18 +# CONFIG_LWIP_TCPIP_CORE_LOCKING is not set +# CONFIG_LWIP_CHECK_THREAD_SAFETY is not set +CONFIG_LWIP_DNS_SUPPORT_MDNS_QUERIES=y +# CONFIG_LWIP_L2_TO_L3_COPY is not set +# CONFIG_LWIP_IRAM_OPTIMIZATION is not set +# CONFIG_LWIP_EXTRA_IRAM_OPTIMIZATION is not set +CONFIG_LWIP_TIMERS_ONDEMAND=y +CONFIG_LWIP_ND6=y +# CONFIG_LWIP_FORCE_ROUTER_FORWARDING is not set +CONFIG_LWIP_MAX_SOCKETS=10 +# CONFIG_LWIP_USE_ONLY_LWIP_SELECT is not set +# CONFIG_LWIP_SO_LINGER is not set +CONFIG_LWIP_SO_REUSE=y +CONFIG_LWIP_SO_REUSE_RXTOALL=y +# CONFIG_LWIP_SO_RCVBUF is not set +# CONFIG_LWIP_NETBUF_RECVINFO is not set +CONFIG_LWIP_IP_DEFAULT_TTL=64 +CONFIG_LWIP_IP4_FRAG=y +CONFIG_LWIP_IP6_FRAG=y +# CONFIG_LWIP_IP4_REASSEMBLY is not set +# CONFIG_LWIP_IP6_REASSEMBLY is not set +CONFIG_LWIP_IP_REASS_MAX_PBUFS=10 +# CONFIG_LWIP_IP_FORWARD is not set +# CONFIG_LWIP_STATS is not set +CONFIG_LWIP_ESP_GRATUITOUS_ARP=y +CONFIG_LWIP_GARP_TMR_INTERVAL=60 +CONFIG_LWIP_ESP_MLDV6_REPORT=y +CONFIG_LWIP_MLDV6_TMR_INTERVAL=40 +CONFIG_LWIP_TCPIP_RECVMBOX_SIZE=32 +CONFIG_LWIP_DHCP_DOES_ARP_CHECK=y +# CONFIG_LWIP_DHCP_DOES_ACD_CHECK is not set +# CONFIG_LWIP_DHCP_DOES_NOT_CHECK_OFFERED_IP is not set +# CONFIG_LWIP_DHCP_DISABLE_CLIENT_ID is not set +CONFIG_LWIP_DHCP_DISABLE_VENDOR_CLASS_ID=y +# CONFIG_LWIP_DHCP_RESTORE_LAST_IP is not set +CONFIG_LWIP_DHCP_OPTIONS_LEN=68 +CONFIG_LWIP_NUM_NETIF_CLIENT_DATA=0 +CONFIG_LWIP_DHCP_COARSE_TIMER_SECS=1 + +# +# DHCP server +# +CONFIG_LWIP_DHCPS=y +CONFIG_LWIP_DHCPS_LEASE_UNIT=60 +CONFIG_LWIP_DHCPS_MAX_STATION_NUM=8 +CONFIG_LWIP_DHCPS_STATIC_ENTRIES=y +CONFIG_LWIP_DHCPS_ADD_DNS=y +# end of DHCP server + +# CONFIG_LWIP_AUTOIP is not set +CONFIG_LWIP_IPV4=y +CONFIG_LWIP_IPV6=y +# CONFIG_LWIP_IPV6_AUTOCONFIG is not set +CONFIG_LWIP_IPV6_NUM_ADDRESSES=3 +# CONFIG_LWIP_IPV6_FORWARD is not set +# CONFIG_LWIP_NETIF_STATUS_CALLBACK is not set +CONFIG_LWIP_NETIF_LOOPBACK=y +CONFIG_LWIP_LOOPBACK_MAX_PBUFS=8 + +# +# TCP +# +CONFIG_LWIP_MAX_ACTIVE_TCP=16 +CONFIG_LWIP_MAX_LISTENING_TCP=16 +CONFIG_LWIP_TCP_HIGH_SPEED_RETRANSMISSION=y +CONFIG_LWIP_TCP_MAXRTX=12 +CONFIG_LWIP_TCP_SYNMAXRTX=12 +CONFIG_LWIP_TCP_MSS=1440 +CONFIG_LWIP_TCP_TMR_INTERVAL=250 +CONFIG_LWIP_TCP_MSL=60000 +CONFIG_LWIP_TCP_FIN_WAIT_TIMEOUT=20000 +CONFIG_LWIP_TCP_SND_BUF_DEFAULT=5760 +CONFIG_LWIP_TCP_WND_DEFAULT=5760 +CONFIG_LWIP_TCP_RECVMBOX_SIZE=6 +CONFIG_LWIP_TCP_ACCEPTMBOX_SIZE=6 +CONFIG_LWIP_TCP_QUEUE_OOSEQ=y +CONFIG_LWIP_TCP_OOSEQ_TIMEOUT=6 +CONFIG_LWIP_TCP_OOSEQ_MAX_PBUFS=4 +# CONFIG_LWIP_TCP_SACK_OUT is not set +CONFIG_LWIP_TCP_OVERSIZE_MSS=y +# CONFIG_LWIP_TCP_OVERSIZE_QUARTER_MSS is not set +# CONFIG_LWIP_TCP_OVERSIZE_DISABLE is not set +CONFIG_LWIP_TCP_RTO_TIME=1500 +# end of TCP + +# +# UDP +# +CONFIG_LWIP_MAX_UDP_PCBS=16 +CONFIG_LWIP_UDP_RECVMBOX_SIZE=6 +# end of UDP + +# +# Checksums +# +# CONFIG_LWIP_CHECKSUM_CHECK_IP is not set +# CONFIG_LWIP_CHECKSUM_CHECK_UDP is not set +CONFIG_LWIP_CHECKSUM_CHECK_ICMP=y +# end of Checksums + +CONFIG_LWIP_TCPIP_TASK_STACK_SIZE=3072 +CONFIG_LWIP_TCPIP_TASK_AFFINITY_NO_AFFINITY=y +# CONFIG_LWIP_TCPIP_TASK_AFFINITY_CPU0 is not set +# CONFIG_LWIP_TCPIP_TASK_AFFINITY_CPU1 is not set +CONFIG_LWIP_TCPIP_TASK_AFFINITY=0x7FFFFFFF +CONFIG_LWIP_IPV6_MEMP_NUM_ND6_QUEUE=3 +CONFIG_LWIP_IPV6_ND6_NUM_NEIGHBORS=5 +CONFIG_LWIP_IPV6_ND6_NUM_PREFIXES=5 +CONFIG_LWIP_IPV6_ND6_NUM_ROUTERS=3 +CONFIG_LWIP_IPV6_ND6_NUM_DESTINATIONS=10 +CONFIG_LWIP_PPP_SUPPORT=y +CONFIG_LWIP_PPP_ENABLE_IPV4=y +CONFIG_LWIP_PPP_ENABLE_IPV6=y +# CONFIG_LWIP_PPP_NOTIFY_PHASE_SUPPORT is not set +CONFIG_LWIP_PPP_PAP_SUPPORT=y +CONFIG_LWIP_PPP_CHAP_SUPPORT=y +# CONFIG_LWIP_PPP_MSCHAP_SUPPORT is not set +# CONFIG_LWIP_PPP_MPPE_SUPPORT is not set +CONFIG_LWIP_PPP_SERVER_SUPPORT=y +CONFIG_LWIP_PPP_VJ_HEADER_COMPRESSION=y +# CONFIG_LWIP_ENABLE_LCP_ECHO is not set +# CONFIG_LWIP_PPP_DEBUG_ON is not set +# CONFIG_LWIP_USE_EXTERNAL_MBEDTLS is not set +# CONFIG_LWIP_SLIP_SUPPORT is not set + +# +# ICMP +# +CONFIG_LWIP_ICMP=y +# CONFIG_LWIP_MULTICAST_PING is not set +# CONFIG_LWIP_BROADCAST_PING is not set +# end of ICMP + +# +# LWIP RAW API +# +CONFIG_LWIP_MAX_RAW_PCBS=16 +# end of LWIP RAW API + +# +# SNTP +# +CONFIG_LWIP_SNTP_MAX_SERVERS=1 +# CONFIG_LWIP_DHCP_GET_NTP_SRV is not set +CONFIG_LWIP_SNTP_UPDATE_DELAY=3600000 +CONFIG_LWIP_SNTP_STARTUP_DELAY=y +CONFIG_LWIP_SNTP_MAXIMUM_STARTUP_DELAY=5000 +# end of SNTP + +# +# DNS +# +CONFIG_LWIP_DNS_MAX_HOST_IP=1 +CONFIG_LWIP_DNS_MAX_SERVERS=3 +# CONFIG_LWIP_FALLBACK_DNS_SERVER_SUPPORT is not set +# CONFIG_LWIP_DNS_SETSERVER_WITH_NETIF is not set +# end of DNS + +CONFIG_LWIP_BRIDGEIF_MAX_PORTS=7 +CONFIG_LWIP_ESP_LWIP_ASSERT=y + +# +# Hooks +# +# CONFIG_LWIP_HOOK_TCP_ISN_NONE is not set +CONFIG_LWIP_HOOK_TCP_ISN_DEFAULT=y +# CONFIG_LWIP_HOOK_TCP_ISN_CUSTOM is not set +CONFIG_LWIP_HOOK_IP6_ROUTE_NONE=y +# CONFIG_LWIP_HOOK_IP6_ROUTE_DEFAULT is not set +# CONFIG_LWIP_HOOK_IP6_ROUTE_CUSTOM is not set +CONFIG_LWIP_HOOK_ND6_GET_GW_NONE=y +# CONFIG_LWIP_HOOK_ND6_GET_GW_DEFAULT is not set +# CONFIG_LWIP_HOOK_ND6_GET_GW_CUSTOM is not set +CONFIG_LWIP_HOOK_IP6_SELECT_SRC_ADDR_NONE=y +# CONFIG_LWIP_HOOK_IP6_SELECT_SRC_ADDR_DEFAULT is not set +# CONFIG_LWIP_HOOK_IP6_SELECT_SRC_ADDR_CUSTOM is not set +CONFIG_LWIP_HOOK_NETCONN_EXT_RESOLVE_NONE=y +# CONFIG_LWIP_HOOK_NETCONN_EXT_RESOLVE_DEFAULT is not set +# CONFIG_LWIP_HOOK_NETCONN_EXT_RESOLVE_CUSTOM is not set +CONFIG_LWIP_HOOK_DNS_EXT_RESOLVE_NONE=y +# CONFIG_LWIP_HOOK_DNS_EXT_RESOLVE_CUSTOM is not set +# CONFIG_LWIP_HOOK_IP6_INPUT_NONE is not set +CONFIG_LWIP_HOOK_IP6_INPUT_DEFAULT=y +# CONFIG_LWIP_HOOK_IP6_INPUT_CUSTOM is not set +# end of Hooks + +# CONFIG_LWIP_DEBUG is not set +# end of LWIP + +# +# mbedTLS +# +CONFIG_MBEDTLS_INTERNAL_MEM_ALLOC=y +# CONFIG_MBEDTLS_EXTERNAL_MEM_ALLOC is not set +# CONFIG_MBEDTLS_DEFAULT_MEM_ALLOC is not set +# CONFIG_MBEDTLS_CUSTOM_MEM_ALLOC is not set +CONFIG_MBEDTLS_ASYMMETRIC_CONTENT_LEN=y +CONFIG_MBEDTLS_SSL_IN_CONTENT_LEN=16384 +CONFIG_MBEDTLS_SSL_OUT_CONTENT_LEN=4096 +# CONFIG_MBEDTLS_DYNAMIC_BUFFER is not set +# CONFIG_MBEDTLS_DEBUG is not set + +# +# mbedTLS v3.x related +# +# CONFIG_MBEDTLS_SSL_PROTO_TLS1_3 is not set +# CONFIG_MBEDTLS_SSL_VARIABLE_BUFFER_LENGTH is not set +# CONFIG_MBEDTLS_X509_TRUSTED_CERT_CALLBACK is not set +# CONFIG_MBEDTLS_SSL_CONTEXT_SERIALIZATION is not set +CONFIG_MBEDTLS_SSL_KEEP_PEER_CERTIFICATE=y +CONFIG_MBEDTLS_PKCS7_C=y +# end of mbedTLS v3.x related + +# +# Certificate Bundle +# +CONFIG_MBEDTLS_CERTIFICATE_BUNDLE=y +CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_DEFAULT_FULL=y +# CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_DEFAULT_CMN is not set +# CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_DEFAULT_NONE is not set +# CONFIG_MBEDTLS_CUSTOM_CERTIFICATE_BUNDLE is not set +# CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_DEPRECATED_LIST is not set +CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_MAX_CERTS=200 +# end of Certificate Bundle + +# CONFIG_MBEDTLS_ECP_RESTARTABLE is not set +CONFIG_MBEDTLS_CMAC_C=y +CONFIG_MBEDTLS_HARDWARE_AES=y +CONFIG_MBEDTLS_AES_USE_INTERRUPT=y +CONFIG_MBEDTLS_AES_INTERRUPT_LEVEL=0 +CONFIG_MBEDTLS_HARDWARE_GCM=y +CONFIG_MBEDTLS_GCM_SUPPORT_NON_AES_CIPHER=y +CONFIG_MBEDTLS_HARDWARE_MPI=y +# CONFIG_MBEDTLS_LARGE_KEY_SOFTWARE_MPI is not set +CONFIG_MBEDTLS_MPI_USE_INTERRUPT=y +CONFIG_MBEDTLS_MPI_INTERRUPT_LEVEL=0 +CONFIG_MBEDTLS_HARDWARE_SHA=y +CONFIG_MBEDTLS_HARDWARE_ECC=y +CONFIG_MBEDTLS_ECC_OTHER_CURVES_SOFT_FALLBACK=y +CONFIG_MBEDTLS_ROM_MD5=y +# CONFIG_MBEDTLS_ATCA_HW_ECDSA_SIGN is not set +# CONFIG_MBEDTLS_ATCA_HW_ECDSA_VERIFY is not set +CONFIG_MBEDTLS_HAVE_TIME=y +CONFIG_MBEDTLS_PLATFORM_TIME_ALT=y +CONFIG_MBEDTLS_HAVE_TIME_DATE=y +CONFIG_MBEDTLS_ECDSA_DETERMINISTIC=y +CONFIG_MBEDTLS_SHA512_C=y +# CONFIG_MBEDTLS_SHA3_C is not set +CONFIG_MBEDTLS_TLS_SERVER_AND_CLIENT=y +# CONFIG_MBEDTLS_TLS_SERVER_ONLY is not set +# CONFIG_MBEDTLS_TLS_CLIENT_ONLY is not set +# CONFIG_MBEDTLS_TLS_DISABLED is not set +CONFIG_MBEDTLS_TLS_SERVER=y +CONFIG_MBEDTLS_TLS_CLIENT=y +CONFIG_MBEDTLS_TLS_ENABLED=y + +# +# TLS Key Exchange Methods +# +# CONFIG_MBEDTLS_PSK_MODES is not set +CONFIG_MBEDTLS_KEY_EXCHANGE_RSA=y +CONFIG_MBEDTLS_KEY_EXCHANGE_ELLIPTIC_CURVE=y +CONFIG_MBEDTLS_KEY_EXCHANGE_ECDHE_RSA=y +CONFIG_MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA=y +CONFIG_MBEDTLS_KEY_EXCHANGE_ECDH_ECDSA=y +CONFIG_MBEDTLS_KEY_EXCHANGE_ECDH_RSA=y +# end of TLS Key Exchange Methods + +CONFIG_MBEDTLS_SSL_RENEGOTIATION=y +CONFIG_MBEDTLS_SSL_PROTO_TLS1_2=y +# CONFIG_MBEDTLS_SSL_PROTO_GMTSSL1_1 is not set +# CONFIG_MBEDTLS_SSL_PROTO_DTLS is not set +CONFIG_MBEDTLS_SSL_ALPN=y +CONFIG_MBEDTLS_CLIENT_SSL_SESSION_TICKETS=y +CONFIG_MBEDTLS_SERVER_SSL_SESSION_TICKETS=y + +# +# Symmetric Ciphers +# +CONFIG_MBEDTLS_AES_C=y +# CONFIG_MBEDTLS_CAMELLIA_C is not set +# CONFIG_MBEDTLS_DES_C is not set +# CONFIG_MBEDTLS_BLOWFISH_C is not set +# CONFIG_MBEDTLS_XTEA_C is not set +CONFIG_MBEDTLS_CCM_C=y +CONFIG_MBEDTLS_GCM_C=y +# CONFIG_MBEDTLS_NIST_KW_C is not set +# end of Symmetric Ciphers + +# CONFIG_MBEDTLS_RIPEMD160_C is not set + +# +# Certificates +# +CONFIG_MBEDTLS_PEM_PARSE_C=y +CONFIG_MBEDTLS_PEM_WRITE_C=y +CONFIG_MBEDTLS_X509_CRL_PARSE_C=y +CONFIG_MBEDTLS_X509_CSR_PARSE_C=y +# end of Certificates + +CONFIG_MBEDTLS_ECP_C=y +CONFIG_MBEDTLS_PK_PARSE_EC_EXTENDED=y +CONFIG_MBEDTLS_PK_PARSE_EC_COMPRESSED=y +# CONFIG_MBEDTLS_DHM_C is not set +CONFIG_MBEDTLS_ECDH_C=y +CONFIG_MBEDTLS_ECDSA_C=y +# CONFIG_MBEDTLS_ECJPAKE_C is not set +CONFIG_MBEDTLS_ECP_DP_SECP192R1_ENABLED=y +CONFIG_MBEDTLS_ECP_DP_SECP224R1_ENABLED=y +CONFIG_MBEDTLS_ECP_DP_SECP256R1_ENABLED=y +CONFIG_MBEDTLS_ECP_DP_SECP384R1_ENABLED=y +CONFIG_MBEDTLS_ECP_DP_SECP521R1_ENABLED=y +CONFIG_MBEDTLS_ECP_DP_SECP192K1_ENABLED=y +CONFIG_MBEDTLS_ECP_DP_SECP224K1_ENABLED=y +CONFIG_MBEDTLS_ECP_DP_SECP256K1_ENABLED=y +CONFIG_MBEDTLS_ECP_DP_BP256R1_ENABLED=y +CONFIG_MBEDTLS_ECP_DP_BP384R1_ENABLED=y +CONFIG_MBEDTLS_ECP_DP_BP512R1_ENABLED=y +CONFIG_MBEDTLS_ECP_DP_CURVE25519_ENABLED=y +CONFIG_MBEDTLS_ECP_NIST_OPTIM=y +# CONFIG_MBEDTLS_ECP_FIXED_POINT_OPTIM is not set +# CONFIG_MBEDTLS_POLY1305_C is not set +# CONFIG_MBEDTLS_CHACHA20_C is not set +# CONFIG_MBEDTLS_HKDF_C is not set +# CONFIG_MBEDTLS_THREADING_C is not set +CONFIG_MBEDTLS_ERROR_STRINGS=y +CONFIG_MBEDTLS_FS_IO=y +# end of mbedTLS + +# +# ESP-MQTT Configurations +# +CONFIG_MQTT_PROTOCOL_311=y +# CONFIG_MQTT_PROTOCOL_5 is not set +CONFIG_MQTT_TRANSPORT_SSL=y +CONFIG_MQTT_TRANSPORT_WEBSOCKET=y +CONFIG_MQTT_TRANSPORT_WEBSOCKET_SECURE=y +# CONFIG_MQTT_MSG_ID_INCREMENTAL is not set +# CONFIG_MQTT_SKIP_PUBLISH_IF_DISCONNECTED is not set +# CONFIG_MQTT_REPORT_DELETED_MESSAGES is not set +# CONFIG_MQTT_USE_CUSTOM_CONFIG is not set +# CONFIG_MQTT_TASK_CORE_SELECTION_ENABLED is not set +# CONFIG_MQTT_CUSTOM_OUTBOX is not set +# end of ESP-MQTT Configurations + +# +# Newlib +# +CONFIG_NEWLIB_STDOUT_LINE_ENDING_CRLF=y +# CONFIG_NEWLIB_STDOUT_LINE_ENDING_LF is not set +# CONFIG_NEWLIB_STDOUT_LINE_ENDING_CR is not set +# CONFIG_NEWLIB_STDIN_LINE_ENDING_CRLF is not set +# CONFIG_NEWLIB_STDIN_LINE_ENDING_LF is not set +CONFIG_NEWLIB_STDIN_LINE_ENDING_CR=y +# CONFIG_NEWLIB_NANO_FORMAT is not set +CONFIG_NEWLIB_TIME_SYSCALL_USE_RTC_HRT=y +# CONFIG_NEWLIB_TIME_SYSCALL_USE_RTC is not set +# CONFIG_NEWLIB_TIME_SYSCALL_USE_HRT is not set +# CONFIG_NEWLIB_TIME_SYSCALL_USE_NONE is not set +# end of Newlib + +# +# NVS +# +# CONFIG_NVS_ENCRYPTION is not set +# CONFIG_NVS_ASSERT_ERROR_CHECK is not set +# CONFIG_NVS_LEGACY_DUP_KEYS_COMPATIBILITY is not set +# CONFIG_NVS_ALLOCATE_CACHE_IN_SPIRAM is not set +# end of NVS + +# +# OpenThread +# +# CONFIG_OPENTHREAD_ENABLED is not set + +# +# OpenThread Spinel +# +# CONFIG_OPENTHREAD_SPINEL_ONLY is not set +# end of OpenThread Spinel +# end of OpenThread + +# +# Protocomm +# +CONFIG_ESP_PROTOCOMM_SUPPORT_SECURITY_VERSION_0=y +CONFIG_ESP_PROTOCOMM_SUPPORT_SECURITY_VERSION_1=y +CONFIG_ESP_PROTOCOMM_SUPPORT_SECURITY_VERSION_2=y +CONFIG_ESP_PROTOCOMM_SUPPORT_SECURITY_PATCH_VERSION=y +# end of Protocomm + +# +# PThreads +# +CONFIG_PTHREAD_TASK_PRIO_DEFAULT=5 +CONFIG_PTHREAD_TASK_STACK_SIZE_DEFAULT=3072 +CONFIG_PTHREAD_STACK_MIN=768 +CONFIG_PTHREAD_DEFAULT_CORE_NO_AFFINITY=y +# CONFIG_PTHREAD_DEFAULT_CORE_0 is not set +# CONFIG_PTHREAD_DEFAULT_CORE_1 is not set +CONFIG_PTHREAD_TASK_CORE_DEFAULT=-1 +CONFIG_PTHREAD_TASK_NAME_DEFAULT="pthread" +# end of PThreads + +# +# MMU Config +# +CONFIG_MMU_PAGE_SIZE_64KB=y +CONFIG_MMU_PAGE_MODE="64KB" +CONFIG_MMU_PAGE_SIZE=0x10000 +# end of MMU Config + +# +# Main Flash configuration +# + +# +# SPI Flash behavior when brownout +# +CONFIG_SPI_FLASH_BROWNOUT_RESET_XMC=y +CONFIG_SPI_FLASH_BROWNOUT_RESET=y +# end of SPI Flash behavior when brownout + +# +# Optional and Experimental Features (READ DOCS FIRST) +# + +# +# Features here require specific hardware (READ DOCS FIRST!) +# +# CONFIG_SPI_FLASH_AUTO_SUSPEND is not set +CONFIG_SPI_FLASH_SUSPEND_TSUS_VAL_US=50 +# CONFIG_SPI_FLASH_FORCE_ENABLE_XMC_C_SUSPEND is not set +# end of Optional and Experimental Features (READ DOCS FIRST) +# end of Main Flash configuration + +# +# SPI Flash driver +# +# CONFIG_SPI_FLASH_VERIFY_WRITE is not set +# CONFIG_SPI_FLASH_ENABLE_COUNTERS is not set +CONFIG_SPI_FLASH_ROM_DRIVER_PATCH=y +CONFIG_SPI_FLASH_DANGEROUS_WRITE_ABORTS=y +# CONFIG_SPI_FLASH_DANGEROUS_WRITE_FAILS is not set +# CONFIG_SPI_FLASH_DANGEROUS_WRITE_ALLOWED is not set +# CONFIG_SPI_FLASH_BYPASS_BLOCK_ERASE is not set +CONFIG_SPI_FLASH_YIELD_DURING_ERASE=y +CONFIG_SPI_FLASH_ERASE_YIELD_DURATION_MS=20 +CONFIG_SPI_FLASH_ERASE_YIELD_TICKS=1 +CONFIG_SPI_FLASH_WRITE_CHUNK_SIZE=8192 +# CONFIG_SPI_FLASH_SIZE_OVERRIDE is not set +# CONFIG_SPI_FLASH_CHECK_ERASE_TIMEOUT_DISABLED is not set +# CONFIG_SPI_FLASH_OVERRIDE_CHIP_DRIVER_LIST is not set + +# +# Auto-detect flash chips +# +CONFIG_SPI_FLASH_VENDOR_XMC_SUPPORTED=y +# CONFIG_SPI_FLASH_SUPPORT_ISSI_CHIP is not set +# CONFIG_SPI_FLASH_SUPPORT_MXIC_CHIP is not set +# CONFIG_SPI_FLASH_SUPPORT_GD_CHIP is not set +# CONFIG_SPI_FLASH_SUPPORT_WINBOND_CHIP is not set +# CONFIG_SPI_FLASH_SUPPORT_BOYA_CHIP is not set +# CONFIG_SPI_FLASH_SUPPORT_TH_CHIP is not set +# end of Auto-detect flash chips + +CONFIG_SPI_FLASH_ENABLE_ENCRYPTED_READ_WRITE=y +# end of SPI Flash driver + +# +# SPIFFS Configuration +# +CONFIG_SPIFFS_MAX_PARTITIONS=3 + +# +# SPIFFS Cache Configuration +# +CONFIG_SPIFFS_CACHE=y +CONFIG_SPIFFS_CACHE_WR=y +# CONFIG_SPIFFS_CACHE_STATS is not set +# end of SPIFFS Cache Configuration + +CONFIG_SPIFFS_PAGE_CHECK=y +CONFIG_SPIFFS_GC_MAX_RUNS=10 +# CONFIG_SPIFFS_GC_STATS is not set +CONFIG_SPIFFS_PAGE_SIZE=256 +CONFIG_SPIFFS_OBJ_NAME_LEN=32 +# CONFIG_SPIFFS_FOLLOW_SYMLINKS is not set +CONFIG_SPIFFS_USE_MAGIC=y +CONFIG_SPIFFS_USE_MAGIC_LENGTH=y +CONFIG_SPIFFS_META_LENGTH=4 +CONFIG_SPIFFS_USE_MTIME=y + +# +# Debug Configuration +# +# CONFIG_SPIFFS_DBG is not set +# CONFIG_SPIFFS_API_DBG is not set +# CONFIG_SPIFFS_GC_DBG is not set +# CONFIG_SPIFFS_CACHE_DBG is not set +# CONFIG_SPIFFS_CHECK_DBG is not set +# CONFIG_SPIFFS_TEST_VISUALISATION is not set +# end of Debug Configuration +# end of SPIFFS Configuration + +# +# TCP Transport +# + +# +# Websocket +# +CONFIG_WS_TRANSPORT=y +CONFIG_WS_BUFFER_SIZE=1024 +# CONFIG_WS_DYNAMIC_BUFFER is not set +# end of Websocket +# end of TCP Transport + +# +# Ultra Low Power (ULP) Co-processor +# +# CONFIG_ULP_COPROC_ENABLED is not set + +# +# ULP Debugging Options +# +# end of ULP Debugging Options +# end of Ultra Low Power (ULP) Co-processor + +# +# Unity unit testing library +# +CONFIG_UNITY_ENABLE_FLOAT=y +CONFIG_UNITY_ENABLE_DOUBLE=y +# CONFIG_UNITY_ENABLE_64BIT is not set +# CONFIG_UNITY_ENABLE_COLOR is not set +CONFIG_UNITY_ENABLE_IDF_TEST_RUNNER=y +# CONFIG_UNITY_ENABLE_FIXTURE is not set +# CONFIG_UNITY_ENABLE_BACKTRACE_ON_FAIL is not set +# end of Unity unit testing library + +# +# USB-OTG +# +CONFIG_USB_HOST_CONTROL_TRANSFER_MAX_SIZE=256 +CONFIG_USB_HOST_HW_BUFFER_BIAS_BALANCED=y +# CONFIG_USB_HOST_HW_BUFFER_BIAS_IN is not set +# CONFIG_USB_HOST_HW_BUFFER_BIAS_PERIODIC_OUT is not set + +# +# Hub Driver Configuration +# + +# +# Root Port configuration +# +CONFIG_USB_HOST_DEBOUNCE_DELAY_MS=250 +CONFIG_USB_HOST_RESET_HOLD_MS=30 +CONFIG_USB_HOST_RESET_RECOVERY_MS=30 +CONFIG_USB_HOST_SET_ADDR_RECOVERY_MS=10 +# end of Root Port configuration + +# CONFIG_USB_HOST_HUBS_SUPPORTED is not set +# end of Hub Driver Configuration + +# CONFIG_USB_HOST_ENABLE_ENUM_FILTER_CALLBACK is not set +CONFIG_USB_OTG_SUPPORTED=y +# end of USB-OTG + +# +# Virtual file system +# +CONFIG_VFS_SUPPORT_IO=y +CONFIG_VFS_SUPPORT_DIR=y +CONFIG_VFS_SUPPORT_SELECT=y +CONFIG_VFS_SUPPRESS_SELECT_DEBUG_OUTPUT=y +# CONFIG_VFS_SELECT_IN_RAM is not set +CONFIG_VFS_SUPPORT_TERMIOS=y +CONFIG_VFS_MAX_COUNT=8 + +# +# Host File System I/O (Semihosting) +# +CONFIG_VFS_SEMIHOSTFS_MAX_MOUNT_POINTS=1 +# end of Host File System I/O (Semihosting) + +CONFIG_VFS_INITIALIZE_DEV_NULL=y +# end of Virtual file system + +# +# Wear Levelling +# +# CONFIG_WL_SECTOR_SIZE_512 is not set +CONFIG_WL_SECTOR_SIZE_4096=y +CONFIG_WL_SECTOR_SIZE=4096 +# end of Wear Levelling + +# +# Wi-Fi Provisioning Manager +# +CONFIG_WIFI_PROV_SCAN_MAX_ENTRIES=16 +CONFIG_WIFI_PROV_AUTOSTOP_TIMEOUT=30 +CONFIG_WIFI_PROV_STA_ALL_CHANNEL_SCAN=y +# CONFIG_WIFI_PROV_STA_FAST_SCAN is not set +# end of Wi-Fi Provisioning Manager + +# +# LVGL configuration +# +CONFIG_LV_CONF_SKIP=y +# CONFIG_LV_CONF_MINIMAL is not set + +# +# Color Settings +# +# CONFIG_LV_COLOR_DEPTH_32 is not set +# CONFIG_LV_COLOR_DEPTH_24 is not set +CONFIG_LV_COLOR_DEPTH_16=y +# CONFIG_LV_COLOR_DEPTH_8 is not set +# CONFIG_LV_COLOR_DEPTH_1 is not set +CONFIG_LV_COLOR_DEPTH=16 +# end of Color Settings + +# +# Memory Settings +# +# CONFIG_LV_USE_BUILTIN_MALLOC is not set +CONFIG_LV_USE_CLIB_MALLOC=y +# CONFIG_LV_USE_MICROPYTHON_MALLOC is not set +# CONFIG_LV_USE_RTTHREAD_MALLOC is not set +# CONFIG_LV_USE_CUSTOM_MALLOC is not set +CONFIG_LV_USE_BUILTIN_STRING=y +# CONFIG_LV_USE_CLIB_STRING is not set +# CONFIG_LV_USE_CUSTOM_STRING is not set +CONFIG_LV_USE_BUILTIN_SPRINTF=y +# CONFIG_LV_USE_CLIB_SPRINTF is not set +# CONFIG_LV_USE_CUSTOM_SPRINTF is not set +# end of Memory Settings + +# +# HAL Settings +# +CONFIG_LV_DEF_REFR_PERIOD=33 +CONFIG_LV_DPI_DEF=130 +# end of HAL Settings + +# +# Operating System (OS) +# +CONFIG_LV_OS_NONE=y +# CONFIG_LV_OS_PTHREAD is not set +# CONFIG_LV_OS_FREERTOS is not set +# CONFIG_LV_OS_CMSIS_RTOS2 is not set +# CONFIG_LV_OS_RTTHREAD is not set +# CONFIG_LV_OS_WINDOWS is not set +# CONFIG_LV_OS_MQX is not set +# CONFIG_LV_OS_CUSTOM is not set +CONFIG_LV_USE_OS=0 +# end of Operating System (OS) + +# +# Rendering Configuration +# +CONFIG_LV_DRAW_BUF_STRIDE_ALIGN=1 +CONFIG_LV_DRAW_BUF_ALIGN=4 +CONFIG_LV_DRAW_LAYER_SIMPLE_BUF_SIZE=24576 +CONFIG_LV_USE_DRAW_SW=y +CONFIG_LV_DRAW_SW_SUPPORT_RGB565=y +CONFIG_LV_DRAW_SW_SUPPORT_RGB565A8=y +CONFIG_LV_DRAW_SW_SUPPORT_RGB888=y +CONFIG_LV_DRAW_SW_SUPPORT_XRGB8888=y +CONFIG_LV_DRAW_SW_SUPPORT_ARGB8888=y +CONFIG_LV_DRAW_SW_SUPPORT_L8=y +CONFIG_LV_DRAW_SW_SUPPORT_AL88=y +CONFIG_LV_DRAW_SW_SUPPORT_A8=y +CONFIG_LV_DRAW_SW_SUPPORT_I1=y +CONFIG_LV_DRAW_SW_DRAW_UNIT_CNT=1 +# CONFIG_LV_USE_DRAW_ARM2D_SYNC is not set +# CONFIG_LV_USE_NATIVE_HELIUM_ASM is not set +CONFIG_LV_DRAW_SW_COMPLEX=y +# CONFIG_LV_USE_DRAW_SW_COMPLEX_GRADIENTS is not set +CONFIG_LV_DRAW_SW_SHADOW_CACHE_SIZE=0 +CONFIG_LV_DRAW_SW_CIRCLE_CACHE_SIZE=4 +CONFIG_LV_DRAW_SW_ASM_NONE=y +# CONFIG_LV_DRAW_SW_ASM_NEON is not set +# CONFIG_LV_DRAW_SW_ASM_HELIUM is not set +# CONFIG_LV_DRAW_SW_ASM_CUSTOM is not set +CONFIG_LV_USE_DRAW_SW_ASM=0 +# CONFIG_LV_USE_DRAW_VGLITE is not set +# CONFIG_LV_USE_PXP is not set +# CONFIG_LV_USE_DRAW_DAVE2D is not set +# CONFIG_LV_USE_DRAW_SDL is not set +# CONFIG_LV_USE_DRAW_VG_LITE is not set +# CONFIG_LV_USE_VECTOR_GRAPHIC is not set +# end of Rendering Configuration + +# +# Feature Configuration +# + +# +# Logging +# +CONFIG_LV_USE_LOG=y +# CONFIG_LV_LOG_LEVEL_TRACE is not set +# CONFIG_LV_LOG_LEVEL_INFO is not set +CONFIG_LV_LOG_LEVEL_WARN=y +# CONFIG_LV_LOG_LEVEL_ERROR is not set +# CONFIG_LV_LOG_LEVEL_USER is not set +# CONFIG_LV_LOG_LEVEL_NONE is not set +CONFIG_LV_LOG_LEVEL=2 +CONFIG_LV_LOG_PRINTF=y +CONFIG_LV_LOG_USE_TIMESTAMP=y +CONFIG_LV_LOG_USE_FILE_LINE=y +CONFIG_LV_LOG_TRACE_MEM=y +CONFIG_LV_LOG_TRACE_TIMER=y +CONFIG_LV_LOG_TRACE_INDEV=y +CONFIG_LV_LOG_TRACE_DISP_REFR=y +CONFIG_LV_LOG_TRACE_EVENT=y +CONFIG_LV_LOG_TRACE_OBJ_CREATE=y +CONFIG_LV_LOG_TRACE_LAYOUT=y +CONFIG_LV_LOG_TRACE_ANIM=y +CONFIG_LV_LOG_TRACE_CACHE=y +# end of Logging + +# +# Asserts +# +CONFIG_LV_USE_ASSERT_NULL=y +CONFIG_LV_USE_ASSERT_MALLOC=y +# CONFIG_LV_USE_ASSERT_STYLE is not set +# CONFIG_LV_USE_ASSERT_MEM_INTEGRITY is not set +# CONFIG_LV_USE_ASSERT_OBJ is not set +CONFIG_LV_ASSERT_HANDLER_INCLUDE="assert.h" +# end of Asserts + +# +# Debug +# +# CONFIG_LV_USE_REFR_DEBUG is not set +# CONFIG_LV_USE_LAYER_DEBUG is not set +# CONFIG_LV_USE_PARALLEL_DRAW_DEBUG is not set +# end of Debug + +# +# Others +# +# CONFIG_LV_ENABLE_GLOBAL_CUSTOM is not set +CONFIG_LV_CACHE_DEF_SIZE=0 +CONFIG_LV_IMAGE_HEADER_CACHE_DEF_CNT=0 +CONFIG_LV_GRADIENT_MAX_STOPS=2 +CONFIG_LV_COLOR_MIX_ROUND_OFS=128 +# CONFIG_LV_OBJ_STYLE_CACHE is not set +# CONFIG_LV_USE_OBJ_ID is not set +# CONFIG_LV_USE_OBJ_PROPERTY is not set +# end of Others +# end of Feature Configuration + +# +# Compiler Settings +# +# CONFIG_LV_BIG_ENDIAN_SYSTEM is not set +CONFIG_LV_ATTRIBUTE_MEM_ALIGN_SIZE=1 +CONFIG_LV_ATTRIBUTE_FAST_MEM_USE_IRAM=y +# CONFIG_LV_USE_FLOAT is not set +# CONFIG_LV_USE_MATRIX is not set +# CONFIG_LV_USE_PRIVATE_API is not set +# end of Compiler Settings + +# +# Font Usage +# + +# +# Enable built-in fonts +# +CONFIG_LV_FONT_MONTSERRAT_8=y +CONFIG_LV_FONT_MONTSERRAT_10=y +CONFIG_LV_FONT_MONTSERRAT_12=y +CONFIG_LV_FONT_MONTSERRAT_14=y +CONFIG_LV_FONT_MONTSERRAT_16=y +CONFIG_LV_FONT_MONTSERRAT_18=y +CONFIG_LV_FONT_MONTSERRAT_20=y +CONFIG_LV_FONT_MONTSERRAT_22=y +CONFIG_LV_FONT_MONTSERRAT_24=y +CONFIG_LV_FONT_MONTSERRAT_26=y +CONFIG_LV_FONT_MONTSERRAT_28=y +CONFIG_LV_FONT_MONTSERRAT_30=y +CONFIG_LV_FONT_MONTSERRAT_32=y +CONFIG_LV_FONT_MONTSERRAT_34=y +CONFIG_LV_FONT_MONTSERRAT_36=y +CONFIG_LV_FONT_MONTSERRAT_38=y +CONFIG_LV_FONT_MONTSERRAT_40=y +CONFIG_LV_FONT_MONTSERRAT_42=y +CONFIG_LV_FONT_MONTSERRAT_44=y +# CONFIG_LV_FONT_MONTSERRAT_46 is not set +# CONFIG_LV_FONT_MONTSERRAT_48 is not set +# CONFIG_LV_FONT_MONTSERRAT_28_COMPRESSED is not set +# CONFIG_LV_FONT_DEJAVU_16_PERSIAN_HEBREW is not set +# CONFIG_LV_FONT_SIMSUN_14_CJK is not set +# CONFIG_LV_FONT_SIMSUN_16_CJK is not set +# CONFIG_LV_FONT_UNSCII_8 is not set +# CONFIG_LV_FONT_UNSCII_16 is not set +# end of Enable built-in fonts + +# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_8 is not set +# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_10 is not set +# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_12 is not set +# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_14 is not set +# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_16 is not set +# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_18 is not set +# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_20 is not set +CONFIG_LV_FONT_DEFAULT_MONTSERRAT_22=y +# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_24 is not set +# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_26 is not set +# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_28 is not set +# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_30 is not set +# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_32 is not set +# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_34 is not set +# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_36 is not set +# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_38 is not set +# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_40 is not set +# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_42 is not set +# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_44 is not set +# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_46 is not set +# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_48 is not set +# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_28_COMPRESSED is not set +# CONFIG_LV_FONT_DEFAULT_DEJAVU_16_PERSIAN_HEBREW is not set +# CONFIG_LV_FONT_DEFAULT_SIMSUN_14_CJK is not set +# CONFIG_LV_FONT_DEFAULT_SIMSUN_16_CJK is not set +# CONFIG_LV_FONT_DEFAULT_UNSCII_8 is not set +# CONFIG_LV_FONT_DEFAULT_UNSCII_16 is not set +CONFIG_LV_FONT_FMT_TXT_LARGE=y +CONFIG_LV_USE_FONT_COMPRESSED=y +CONFIG_LV_USE_FONT_PLACEHOLDER=y +# end of Font Usage + +# +# Text Settings +# +CONFIG_LV_TXT_ENC_UTF8=y +# CONFIG_LV_TXT_ENC_ASCII is not set +CONFIG_LV_TXT_BREAK_CHARS=" ,.;:-_" +CONFIG_LV_TXT_LINE_BREAK_LONG_LEN=0 +# CONFIG_LV_USE_BIDI is not set +# CONFIG_LV_USE_ARABIC_PERSIAN_CHARS is not set +# end of Text Settings + +# +# Widget Usage +# +CONFIG_LV_WIDGETS_HAS_DEFAULT_VALUE=y +CONFIG_LV_USE_ANIMIMG=y +CONFIG_LV_USE_ARC=y +CONFIG_LV_USE_BAR=y +CONFIG_LV_USE_BUTTON=y +CONFIG_LV_USE_BUTTONMATRIX=y +CONFIG_LV_USE_CALENDAR=y +# CONFIG_LV_CALENDAR_WEEK_STARTS_MONDAY is not set +CONFIG_LV_USE_CALENDAR_HEADER_ARROW=y +CONFIG_LV_USE_CALENDAR_HEADER_DROPDOWN=y +# CONFIG_LV_USE_CALENDAR_CHINESE is not set +CONFIG_LV_USE_CANVAS=y +CONFIG_LV_USE_CHART=y +CONFIG_LV_USE_CHECKBOX=y +CONFIG_LV_USE_DROPDOWN=y +CONFIG_LV_USE_IMAGE=y +CONFIG_LV_USE_IMAGEBUTTON=y +CONFIG_LV_USE_KEYBOARD=y +CONFIG_LV_USE_LABEL=y +CONFIG_LV_LABEL_TEXT_SELECTION=y +CONFIG_LV_LABEL_LONG_TXT_HINT=y +CONFIG_LV_LABEL_WAIT_CHAR_COUNT=3 +CONFIG_LV_USE_LED=y +CONFIG_LV_USE_LINE=y +CONFIG_LV_USE_LIST=y +CONFIG_LV_USE_MENU=y +CONFIG_LV_USE_MSGBOX=y +CONFIG_LV_USE_ROLLER=y +CONFIG_LV_USE_SCALE=y +CONFIG_LV_USE_SLIDER=y +CONFIG_LV_USE_SPAN=y +CONFIG_LV_SPAN_SNIPPET_STACK_SIZE=64 +CONFIG_LV_USE_SPINBOX=y +CONFIG_LV_USE_SPINNER=y +CONFIG_LV_USE_SWITCH=y +CONFIG_LV_USE_TEXTAREA=y +CONFIG_LV_TEXTAREA_DEF_PWD_SHOW_TIME=1500 +CONFIG_LV_USE_TABLE=y +CONFIG_LV_USE_TABVIEW=y +CONFIG_LV_USE_TILEVIEW=y +CONFIG_LV_USE_WIN=y +# end of Widget Usage + +# +# Themes +# +CONFIG_LV_USE_THEME_DEFAULT=y +CONFIG_LV_THEME_DEFAULT_DARK=y +CONFIG_LV_THEME_DEFAULT_GROW=y +CONFIG_LV_THEME_DEFAULT_TRANSITION_TIME=80 +CONFIG_LV_USE_THEME_SIMPLE=y +# CONFIG_LV_USE_THEME_MONO is not set +# end of Themes + +# +# Layouts +# +CONFIG_LV_USE_FLEX=y +CONFIG_LV_USE_GRID=y +# end of Layouts + +# +# 3rd Party Libraries +# +CONFIG_LV_FS_DEFAULT_DRIVE_LETTER=0 +# CONFIG_LV_USE_FS_STDIO is not set +# CONFIG_LV_USE_FS_POSIX is not set +# CONFIG_LV_USE_FS_WIN32 is not set +# CONFIG_LV_USE_FS_FATFS is not set +# CONFIG_LV_USE_FS_MEMFS is not set +# CONFIG_LV_USE_FS_LITTLEFS is not set +# CONFIG_LV_USE_FS_ARDUINO_ESP_LITTLEFS is not set +# CONFIG_LV_USE_FS_ARDUINO_SD is not set +# CONFIG_LV_USE_LODEPNG is not set +# CONFIG_LV_USE_LIBPNG is not set +# CONFIG_LV_USE_BMP is not set +# CONFIG_LV_USE_TJPGD is not set +# CONFIG_LV_USE_LIBJPEG_TURBO is not set +# CONFIG_LV_USE_GIF is not set +# CONFIG_LV_BIN_DECODER_RAM_LOAD is not set +# CONFIG_LV_USE_RLE is not set +# CONFIG_LV_USE_QRCODE is not set +# CONFIG_LV_USE_BARCODE is not set +# CONFIG_LV_USE_FREETYPE is not set +# CONFIG_LV_USE_TINY_TTF is not set +# CONFIG_LV_USE_RLOTTIE is not set +# CONFIG_LV_USE_THORVG is not set +# CONFIG_LV_USE_LZ4 is not set +# CONFIG_LV_USE_FFMPEG is not set +# end of 3rd Party Libraries + +# +# Others +# +CONFIG_LV_USE_SNAPSHOT=y +# CONFIG_LV_USE_SYSMON is not set +# CONFIG_LV_USE_PROFILER is not set +# CONFIG_LV_USE_MONKEY is not set +# CONFIG_LV_USE_GRIDNAV is not set +# CONFIG_LV_USE_FRAGMENT is not set +# CONFIG_LV_USE_IMGFONT is not set +CONFIG_LV_USE_OBSERVER=y +# CONFIG_LV_USE_IME_PINYIN is not set +# CONFIG_LV_USE_FILE_EXPLORER is not set +CONFIG_LVGL_VERSION_MAJOR=9 +CONFIG_LVGL_VERSION_MINOR=2 +CONFIG_LVGL_VERSION_PATCH=2 +# end of Others + +# +# Devices +# +# CONFIG_LV_USE_SDL is not set +# CONFIG_LV_USE_X11 is not set +# CONFIG_LV_USE_WAYLAND is not set +# CONFIG_LV_USE_LINUX_FBDEV is not set +# CONFIG_LV_USE_NUTTX is not set +# CONFIG_LV_USE_LINUX_DRM is not set +# CONFIG_LV_USE_TFT_ESPI is not set +# CONFIG_LV_USE_EVDEV is not set +# CONFIG_LV_USE_LIBINPUT is not set +# CONFIG_LV_USE_ST7735 is not set +# CONFIG_LV_USE_ST7789 is not set +# CONFIG_LV_USE_ST7796 is not set +# CONFIG_LV_USE_ILI9341 is not set +# CONFIG_LV_USE_GENERIC_MIPI is not set +# CONFIG_LV_USE_RENESAS_GLCDC is not set +# CONFIG_LV_USE_OPENGLES is not set +# CONFIG_LV_USE_QNX is not set +# end of Devices + +# +# Examples +# +CONFIG_LV_BUILD_EXAMPLES=y +# end of Examples + +# +# Demos +# +CONFIG_LV_USE_DEMO_WIDGETS=y +CONFIG_LV_USE_DEMO_KEYPAD_AND_ENCODER=y +CONFIG_LV_USE_DEMO_BENCHMARK=y +# CONFIG_LV_USE_DEMO_RENDER is not set +# CONFIG_LV_USE_DEMO_SCROLL is not set +# CONFIG_LV_USE_DEMO_STRESS is not set +# CONFIG_LV_USE_DEMO_TRANSFORM is not set +CONFIG_LV_USE_DEMO_MUSIC=y +# CONFIG_LV_DEMO_MUSIC_SQUARE is not set +CONFIG_LV_DEMO_MUSIC_LANDSCAPE=y +# CONFIG_LV_DEMO_MUSIC_ROUND is not set +# CONFIG_LV_DEMO_MUSIC_LARGE is not set +# CONFIG_LV_DEMO_MUSIC_AUTO_PLAY is not set +# CONFIG_LV_USE_DEMO_FLEX_LAYOUT is not set +# CONFIG_LV_USE_DEMO_MULTILANG is not set +# end of Demos +# end of LVGL configuration + +# +# Espressif Camera Sensors Configurations +# +# CONFIG_CAMERA_BF3925 is not set +# CONFIG_CAMERA_GC0308 is not set +# CONFIG_CAMERA_GC2145 is not set +# CONFIG_CAMERA_OV2640 is not set +# CONFIG_CAMERA_OV2710 is not set +# CONFIG_CAMERA_OV5645 is not set +# CONFIG_CAMERA_OV5647 is not set +# CONFIG_CAMERA_SC030IOT is not set +# CONFIG_CAMERA_SC035HGS is not set +# CONFIG_CAMERA_SC101IOT is not set +CONFIG_CAMERA_SC202CS=y +CONFIG_CAMERA_SC202CS_AUTO_DETECT=y +CONFIG_CAMERA_SC202CS_AUTO_DETECT_MIPI_INTERFACE_SENSOR=y +CONFIG_CAMERA_SC202CS_MIPI_RAW8_1280x720_30FPS=y +# CONFIG_CAMERA_SC202CS_MIPI_RAW8_1600x1200_30FPS is not set +# CONFIG_CAMERA_SC202CS_MIPI_RAW10_1600x1200_30FPS is not set +# CONFIG_CAMERA_SC202CS_MIPI_RAW10_1600x900_30FPS is not set +CONFIG_CAMERA_SC202CS_MIPI_IF_FORMAT_INDEX_DAFAULT=0 +CONFIG_CAMERA_SC202CS_ABSOLUTE_GAIN_LIMIT=63008 +# CONFIG_CAMERA_SC202CS_ANA_GAIN_PRIORITY is not set +CONFIG_CAMERA_SC202CS_DIG_GAIN_PRIORITY=y +# CONFIG_CAMERA_SC2336 is not set +# end of Espressif Camera Sensors Configurations + +# +# Espressif Image Process Algorithm Configuration +# +CONFIG_ESP_IPA_AWB_ALGORITHM=y +CONFIG_ESP_IPA_AWB_GRAY_WORLD=y +CONFIG_ESP_IPA_AGC_ALGORITHM=y +CONFIG_ESP_IPA_AGC_THRESHOLD=y +CONFIG_ESP_IPA_DENOISING_ALGORITHM=y +CONFIG_ESP_IPA_DENOISING_GAIN_FEEDBACK=y +CONFIG_ESP_IPA_SHARPEN_ALGORITHM=y +CONFIG_ESP_IPA_SHARPEN_FREQUENCY_FEEDBACK=y +CONFIG_ESP_IPA_GAMMA_ALGORITHM=y +CONFIG_ESP_IPA_GAMMA_LUMA=y +CONFIG_ESP_IPA_CC_ALGORITHM=y +CONFIG_ESP_IPA_CC_LINEAR=y +# end of Espressif Image Process Algorithm Configuration + +# +# Espressif SCCB Configurations +# +CONFIG_ESP_SCCB_TRANS_TIMEOUT_DEFAULT=500 +# end of Espressif SCCB Configurations + +# +# Espressif Video Configuration +# +CONFIG_ESP_VIDEO_ENABLE_JPEG_VIDEO_DEVICE=y +CONFIG_ESP_VIDEO_ENABLE_ISP=y +CONFIG_ESP_VIDEO_CHECK_PARAMETERS=y +CONFIG_ESP_VIDEO_ENABLE_MIPI_CSI_VIDEO_DEVICE=y +CONFIG_ESP_VIDEO_DISABLE_MIPI_CSI_DRIVER_BACKUP_BUFFER=y +CONFIG_ESP_VIDEO_ENABLE_DVP_VIDEO_DEVICE=y +# CONFIG_ESP_VIDEO_ENABLE_HW_H264_VIDEO_DEVICE is not set +CONFIG_ESP_VIDEO_ENABLE_HW_JPEG_VIDEO_DEVICE=y +CONFIG_ESP_VIDEO_ENABLE_ISP_VIDEO_DEVICE=y +CONFIG_ESP_VIDEO_ENABLE_ISP_PIPELINE_CONTROLLER=y +# end of Espressif Video Configuration + +# +# Board Support Package (ESP32-P4) +# +CONFIG_BSP_ERROR_CHECK=y + +# +# I2C +# +CONFIG_BSP_I2C_NUM=1 +# CONFIG_BSP_I2C_FAST_MODE is not set +CONFIG_BSP_I2C_CLK_SPEED_HZ=100000 +# end of I2C + +# +# I2S +# +CONFIG_BSP_I2S_NUM=1 +# end of I2S + +# +# uSD card - Virtual File System +# +# CONFIG_BSP_SD_FORMAT_ON_MOUNT_FAIL is not set +CONFIG_BSP_SD_MOUNT_POINT="/sdcard" +# end of uSD card - Virtual File System + +# +# SPIFFS - Virtual File System +# +# CONFIG_BSP_SPIFFS_FORMAT_ON_MOUNT_FAIL is not set +CONFIG_BSP_SPIFFS_MOUNT_POINT="/spiffs" +CONFIG_BSP_SPIFFS_PARTITION_LABEL="storage" +CONFIG_BSP_SPIFFS_MAX_FILES=5 +# end of SPIFFS - Virtual File System + +# +# Display +# +CONFIG_BSP_LCD_DPI_BUFFER_NUMS=1 +CONFIG_BSP_DISPLAY_BRIGHTNESS_LEDC_CH=1 +CONFIG_BSP_LCD_COLOR_FORMAT_RGB565=y +# CONFIG_BSP_LCD_COLOR_FORMAT_RGB888 is not set +# end of Display +# end of Board Support Package (ESP32-P4) + +# +# Audio playback +# +CONFIG_AUDIO_PLAYER_ENABLE_MP3=y +CONFIG_AUDIO_PLAYER_ENABLE_WAV=y +CONFIG_AUDIO_PLAYER_LOG_LEVEL=0 +# end of Audio playback + +# +# CMake Utilities +# +# CONFIG_CU_RELINKER_ENABLE is not set +# CONFIG_CU_DIAGNOSTICS_COLOR_NEVER is not set +CONFIG_CU_DIAGNOSTICS_COLOR_ALWAYS=y +# CONFIG_CU_DIAGNOSTICS_COLOR_AUTO is not set +# CONFIG_CU_GCC_LTO_ENABLE is not set +# CONFIG_CU_GCC_STRING_1BYTE_ALIGN is not set +# end of CMake Utilities + +# +# eppp_link +# +CONFIG_EPPP_LINK_USES_LWIP=y +CONFIG_EPPP_LINK_DEVICE_UART=y +# CONFIG_EPPP_LINK_DEVICE_SPI is not set +# CONFIG_EPPP_LINK_DEVICE_SDIO is not set +CONFIG_EPPP_LINK_CONN_MAX_RETRY=6 +# end of eppp_link + +# +# Audio Codec Device Configuration +# +# CONFIG_CODEC_I2C_BACKWARD_COMPATIBLE is not set +CONFIG_CODEC_ES8311_SUPPORT=y +CONFIG_CODEC_ES7210_SUPPORT=y +CONFIG_CODEC_ES7243_SUPPORT=y +CONFIG_CODEC_ES7243E_SUPPORT=y +CONFIG_CODEC_ES8156_SUPPORT=y +CONFIG_CODEC_AW88298_SUPPORT=y +CONFIG_CODEC_ES8374_SUPPORT=y +CONFIG_CODEC_ES8388_SUPPORT=y +CONFIG_CODEC_TAS5805M_SUPPORT=y +# CONFIG_CODEC_ZL38063_SUPPORT is not set +# end of Audio Codec Device Configuration + +CONFIG_ESP_HOSTED_ENABLED=y + +# +# ESP-Hosted config +# + +# +# ESP32-C6 is Slave Target from Wi-Fi Remote Component +# +CONFIG_ESP_HOSTED_PRIV_SDIO_OPTION=y +CONFIG_ESP_HOSTED_PRIV_SPI_HD_OPTION=y +# CONFIG_ESP_HOSTED_SPI_HOST_INTERFACE is not set +CONFIG_ESP_HOSTED_SDIO_HOST_INTERFACE=y +# CONFIG_ESP_HOSTED_SPI_HD_HOST_INTERFACE is not set +# CONFIG_ESP_HOSTED_UART_HOST_INTERFACE is not set +CONFIG_ESP_HOSTED_IDF_SLAVE_TARGET="esp32c6" + +# +# Hosted SDIO Configuration +# +# CONFIG_ESP_HOSTED_SDIO_RESET_ACTIVE_HIGH is not set +CONFIG_ESP_HOSTED_SDIO_RESET_ACTIVE_LOW=y +# CONFIG_ESP_HOSTED_SDIO_OPTIMIZATION_RX_NONE is not set +# CONFIG_ESP_HOSTED_SDIO_OPTIMIZATION_RX_MAX_SIZE is not set +CONFIG_ESP_HOSTED_SDIO_OPTIMIZATION_RX_STREAMING_MODE=y +CONFIG_ESP_HOSTED_SDIO_GPIO_RESET_SLAVE=15 +CONFIG_ESP_HOSTED_SDIO_4_BIT_BUS=y +# CONFIG_ESP_HOSTED_SDIO_1_BIT_BUS is not set +CONFIG_ESP_HOSTED_SDIO_BUS_WIDTH=4 +CONFIG_ESP_HOSTED_SDIO_CLOCK_FREQ_KHZ=40000 +CONFIG_ESP_HOSTED_SDIO_PIN_CMD=13 +CONFIG_ESP_HOSTED_SDIO_PIN_CLK=12 +CONFIG_ESP_HOSTED_SDIO_PIN_D0=11 +CONFIG_ESP_HOSTED_SDIO_PRIV_PIN_D1_4BIT_BUS=10 +CONFIG_ESP_HOSTED_SDIO_PIN_D2=9 +CONFIG_ESP_HOSTED_SDIO_PIN_D3=8 +CONFIG_ESP_HOSTED_SDIO_PIN_D1=10 +CONFIG_ESP_HOSTED_SDIO_TX_Q_SIZE=20 +CONFIG_ESP_HOSTED_SDIO_RX_Q_SIZE=20 +# CONFIG_ESP_HOSTED_SDIO_CHECKSUM is not set +# end of Hosted SDIO Configuration + +CONFIG_ESP_HOSTED_GPIO_SLAVE_RESET_SLAVE=15 +CONFIG_ESP_HOSTED_RESET_GPIO_ACTIVE_LOW=y + +# +# Bluetooth Support +# +CONFIG_ESP_HOSTED_ENABLE_BT_NIMBLE=y +CONFIG_ESP_HOSTED_NIMBLE_HCI_VHCI=y + +# +# Following options must be set before this option can be enabled +# + +# +# 'Component config->Bluetooth' must be enabled +# +# end of Bluetooth Support + +# +# Task defaults +# +CONFIG_ESP_HOSTED_RPC_TASK_STACK=4096 +CONFIG_ESP_HOSTED_DFLT_TASK_STACK=3072 +# end of Task defaults + +CONFIG_ESP_HOSTED_USE_MEMPOOL=y +CONFIG_ESP_HOSTED_MAX_SIMULTANEOUS_SYNC_RPC_REQUESTS=5 +CONFIG_ESP_HOSTED_MAX_SIMULTANEOUS_ASYNC_RPC_REQUESTS=5 + +# +# Debug Settings +# +# CONFIG_ESP_HOSTED_RAW_THROUGHPUT_TRANSPORT is not set +# CONFIG_ESP_HOSTED_PKT_STATS is not set +# end of Debug Settings + +# +# Data path options +# +CONFIG_ESP_HOSTED_HOST_TO_ESP_WIFI_DATA_THROTTLE=y +CONFIG_ESP_HOSTED_PRIV_WIFI_TX_SDIO_HIGH_THRESHOLD=80 +CONFIG_ESP_HOSTED_TO_WIFI_DATA_THROTTLE_HIGH_THRESHOLD=80 +CONFIG_ESP_HOSTED_TO_WIFI_DATA_THROTTLE_LOW_THRESHOLD=60 +# end of Data path options +# end of ESP-Hosted config + +# +# ESP LCD TOUCH +# +CONFIG_ESP_LCD_TOUCH_MAX_POINTS=5 +CONFIG_ESP_LCD_TOUCH_MAX_BUTTONS=1 +# end of ESP LCD TOUCH + +# +# Wi-Fi Remote +# +CONFIG_ESP_WIFI_REMOTE_ENABLED=y +# CONFIG_SLAVE_IDF_TARGET_ESP32 is not set +# CONFIG_SLAVE_IDF_TARGET_ESP32S2 is not set +# CONFIG_SLAVE_IDF_TARGET_ESP32C3 is not set +# CONFIG_SLAVE_IDF_TARGET_ESP32S3 is not set +# CONFIG_SLAVE_IDF_TARGET_ESP32C2 is not set +CONFIG_SLAVE_IDF_TARGET_ESP32C6=y +# CONFIG_SLAVE_IDF_TARGET_ESP32C5 is not set +# CONFIG_SLAVE_IDF_TARGET_ESP32C61 is not set +CONFIG_SLAVE_SOC_WIFI_SUPPORTED=y +CONFIG_SLAVE_SOC_WIFI_WAPI_SUPPORT=y +CONFIG_SLAVE_SOC_WIFI_CSI_SUPPORT=y +CONFIG_SLAVE_SOC_WIFI_MESH_SUPPORT=y +CONFIG_SLAVE_SOC_WIFI_LIGHT_SLEEP_CLK_WIDTH=12 +CONFIG_SLAVE_SOC_WIFI_HW_TSF=y +CONFIG_SLAVE_SOC_WIFI_FTM_SUPPORT=y +CONFIG_SLAVE_FREERTOS_UNICORE=y +CONFIG_SLAVE_SOC_WIFI_GCMP_SUPPORT=y +CONFIG_SLAVE_IDF_TARGET_ARCH_RISCV=y +CONFIG_SLAVE_SOC_WIFI_HE_SUPPORT=y +CONFIG_SLAVE_SOC_WIFI_MAC_VERSION_NUM=2 +CONFIG_ESP_WIFI_REMOTE_LIBRARY_HOSTED=y +# CONFIG_ESP_WIFI_REMOTE_LIBRARY_EPPP is not set + +# +# Wi-Fi configuration +# +CONFIG_WIFI_RMT_STATIC_RX_BUFFER_NUM=10 +CONFIG_WIFI_RMT_DYNAMIC_RX_BUFFER_NUM=32 +# CONFIG_WIFI_RMT_STATIC_TX_BUFFER is not set +CONFIG_WIFI_RMT_DYNAMIC_TX_BUFFER=y +CONFIG_WIFI_RMT_TX_BUFFER_TYPE=1 +CONFIG_WIFI_RMT_DYNAMIC_TX_BUFFER_NUM=32 +CONFIG_WIFI_RMT_STATIC_RX_MGMT_BUFFER=y +# CONFIG_WIFI_RMT_DYNAMIC_RX_MGMT_BUFFER is not set +CONFIG_WIFI_RMT_DYNAMIC_RX_MGMT_BUF=0 +CONFIG_WIFI_RMT_RX_MGMT_BUF_NUM_DEF=5 +# CONFIG_WIFI_RMT_CSI_ENABLED is not set +CONFIG_WIFI_RMT_AMPDU_TX_ENABLED=y +CONFIG_WIFI_RMT_TX_BA_WIN=6 +CONFIG_WIFI_RMT_AMPDU_RX_ENABLED=y +CONFIG_WIFI_RMT_RX_BA_WIN=6 +CONFIG_WIFI_RMT_NVS_ENABLED=y +CONFIG_WIFI_RMT_SOFTAP_BEACON_MAX_LEN=752 +CONFIG_WIFI_RMT_MGMT_SBUF_NUM=32 +CONFIG_WIFI_RMT_IRAM_OPT=y +CONFIG_WIFI_RMT_EXTRA_IRAM_OPT=y +CONFIG_WIFI_RMT_RX_IRAM_OPT=y +CONFIG_WIFI_RMT_ENABLE_WPA3_SAE=y +CONFIG_WIFI_RMT_ENABLE_SAE_PK=y +CONFIG_WIFI_RMT_SOFTAP_SAE_SUPPORT=y +CONFIG_WIFI_RMT_ENABLE_WPA3_OWE_STA=y +CONFIG_WIFI_RMT_SLP_IRAM_OPT=y +CONFIG_WIFI_RMT_SLP_DEFAULT_MIN_ACTIVE_TIME=50 +CONFIG_WIFI_RMT_SLP_DEFAULT_MAX_ACTIVE_TIME=10 +CONFIG_WIFI_RMT_SLP_DEFAULT_WAIT_BROADCAST_DATA_TIME=15 +# CONFIG_WIFI_RMT_FTM_ENABLE is not set +CONFIG_WIFI_RMT_STA_DISCONNECTED_PM_ENABLE=y +# CONFIG_WIFI_RMT_GCMP_SUPPORT is not set +CONFIG_WIFI_RMT_GMAC_SUPPORT=y +CONFIG_WIFI_RMT_SOFTAP_SUPPORT=y +# CONFIG_WIFI_RMT_SLP_BEACON_LOST_OPT is not set +CONFIG_WIFI_RMT_ESPNOW_MAX_ENCRYPT_NUM=7 +CONFIG_WIFI_RMT_MBEDTLS_CRYPTO=y +CONFIG_WIFI_RMT_MBEDTLS_TLS_CLIENT=y +# CONFIG_WIFI_RMT_EAP_TLS1_3 is not set +# CONFIG_WIFI_RMT_WAPI_PSK is not set +# CONFIG_WIFI_RMT_SUITE_B_192 is not set +# CONFIG_WIFI_RMT_11KV_SUPPORT is not set +# CONFIG_WIFI_RMT_MBO_SUPPORT is not set +# CONFIG_WIFI_RMT_ENABLE_ROAMING_APP is not set +# CONFIG_WIFI_RMT_DPP_SUPPORT is not set +# CONFIG_WIFI_RMT_11R_SUPPORT is not set +# CONFIG_WIFI_RMT_WPS_SOFTAP_REGISTRAR is not set +# CONFIG_WIFI_RMT_ENABLE_WIFI_TX_STATS is not set +# CONFIG_WIFI_RMT_ENABLE_WIFI_RX_STATS is not set +CONFIG_WIFI_RMT_TX_HETB_QUEUE_NUM=3 + +# +# WPS Configuration Options +# +# CONFIG_WIFI_RMT_WPS_STRICT is not set +# CONFIG_WIFI_RMT_WPS_PASSPHRASE is not set +# end of WPS Configuration Options + +# CONFIG_WIFI_RMT_DEBUG_PRINT is not set +# CONFIG_WIFI_RMT_TESTING_OPTIONS is not set +CONFIG_WIFI_RMT_ENTERPRISE_SUPPORT=y +# CONFIG_WIFI_RMT_ENT_FREE_DYNAMIC_BUFFER is not set +# end of Wi-Fi configuration +# end of Wi-Fi Remote +# end of Component config + +CONFIG_IDF_EXPERIMENTAL_FEATURES=y + +# Deprecated options for backward compatibility +# CONFIG_APP_BUILD_TYPE_ELF_RAM is not set +# CONFIG_NO_BLOBS is not set +# CONFIG_LOG_BOOTLOADER_LEVEL_NONE is not set +# CONFIG_LOG_BOOTLOADER_LEVEL_ERROR is not set +# CONFIG_LOG_BOOTLOADER_LEVEL_WARN is not set +CONFIG_LOG_BOOTLOADER_LEVEL_INFO=y +# CONFIG_LOG_BOOTLOADER_LEVEL_DEBUG is not set +# CONFIG_LOG_BOOTLOADER_LEVEL_VERBOSE is not set +CONFIG_LOG_BOOTLOADER_LEVEL=3 +# CONFIG_APP_ROLLBACK_ENABLE is not set +# CONFIG_FLASH_ENCRYPTION_ENABLED is not set +CONFIG_FLASHMODE_QIO=y +# CONFIG_FLASHMODE_QOUT is not set +# CONFIG_FLASHMODE_DIO is not set +# CONFIG_FLASHMODE_DOUT is not set +CONFIG_MONITOR_BAUD=115200 +# CONFIG_OPTIMIZATION_LEVEL_DEBUG is not set +# CONFIG_COMPILER_OPTIMIZATION_LEVEL_DEBUG is not set +# CONFIG_COMPILER_OPTIMIZATION_DEFAULT is not set +# CONFIG_OPTIMIZATION_LEVEL_RELEASE is not set +# CONFIG_COMPILER_OPTIMIZATION_LEVEL_RELEASE is not set +CONFIG_OPTIMIZATION_ASSERTIONS_ENABLED=y +# CONFIG_OPTIMIZATION_ASSERTIONS_SILENT is not set +# CONFIG_OPTIMIZATION_ASSERTIONS_DISABLED is not set +CONFIG_OPTIMIZATION_ASSERTION_LEVEL=2 +# CONFIG_CXX_EXCEPTIONS is not set +CONFIG_STACK_CHECK_NONE=y +# CONFIG_STACK_CHECK_NORM is not set +# CONFIG_STACK_CHECK_STRONG is not set +# CONFIG_STACK_CHECK_ALL is not set +# CONFIG_WARN_WRITE_STRINGS is not set +# CONFIG_ESP32_APPTRACE_DEST_TRAX is not set +CONFIG_ESP32_APPTRACE_DEST_NONE=y +CONFIG_ESP32_APPTRACE_LOCK_ENABLE=y +# CONFIG_MCPWM_ISR_IN_IRAM is not set +# CONFIG_EVENT_LOOP_PROFILING is not set +CONFIG_POST_EVENTS_FROM_ISR=y +CONFIG_POST_EVENTS_FROM_IRAM_ISR=y +CONFIG_GDBSTUB_SUPPORT_TASKS=y +CONFIG_GDBSTUB_MAX_TASKS=32 +# CONFIG_OTA_ALLOW_HTTP is not set +CONFIG_SYSTEM_EVENT_QUEUE_SIZE=32 +CONFIG_SYSTEM_EVENT_TASK_STACK_SIZE=4096 +CONFIG_MAIN_TASK_STACK_SIZE=4000 +# CONFIG_CONSOLE_UART_DEFAULT is not set +# CONFIG_CONSOLE_UART_CUSTOM is not set +# CONFIG_CONSOLE_UART_NONE is not set +# CONFIG_ESP_CONSOLE_UART_NONE is not set +CONFIG_CONSOLE_UART_NUM=-1 +CONFIG_INT_WDT=y +CONFIG_INT_WDT_TIMEOUT_MS=300 +CONFIG_INT_WDT_CHECK_CPU1=y +CONFIG_TASK_WDT=y +CONFIG_ESP_TASK_WDT=y +# CONFIG_TASK_WDT_PANIC is not set +CONFIG_TASK_WDT_TIMEOUT_S=5 +CONFIG_TASK_WDT_CHECK_IDLE_TASK_CPU0=y +CONFIG_TASK_WDT_CHECK_IDLE_TASK_CPU1=y +# CONFIG_ESP32_DEBUG_STUBS_ENABLE is not set +CONFIG_BROWNOUT_DET=y +CONFIG_BROWNOUT_DET_LVL_SEL_7=y +# CONFIG_BROWNOUT_DET_LVL_SEL_6 is not set +# CONFIG_BROWNOUT_DET_LVL_SEL_5 is not set +CONFIG_BROWNOUT_DET_LVL=7 +CONFIG_IPC_TASK_STACK_SIZE=1024 +CONFIG_TIMER_TASK_STACK_SIZE=3584 +CONFIG_ESP32_WIFI_STATIC_RX_BUFFER_NUM=10 +CONFIG_ESP32_WIFI_DYNAMIC_RX_BUFFER_NUM=32 +CONFIG_ESP32_WIFI_TX_BUFFER_TYPE=1 +CONFIG_ESP32_WIFI_DYNAMIC_TX_BUFFER_NUM=32 +CONFIG_ESP32_WIFI_AMPDU_TX_ENABLED=y +CONFIG_ESP32_WIFI_TX_BA_WIN=6 +CONFIG_ESP32_WIFI_AMPDU_RX_ENABLED=y +CONFIG_ESP32_WIFI_RX_BA_WIN=6 +CONFIG_ESP32_WIFI_NVS_ENABLED=y +CONFIG_ESP32_WIFI_SOFTAP_BEACON_MAX_LEN=752 +CONFIG_ESP32_WIFI_MGMT_SBUF_NUM=32 +CONFIG_ESP32_WIFI_IRAM_OPT=y +CONFIG_ESP32_WIFI_RX_IRAM_OPT=y +CONFIG_ESP32_WIFI_ENABLE_WPA3_SAE=y +CONFIG_ESP32_WIFI_ENABLE_WPA3_OWE_STA=y +CONFIG_WPA_MBEDTLS_CRYPTO=y +CONFIG_WPA_MBEDTLS_TLS_CLIENT=y +# CONFIG_ESP32_ENABLE_COREDUMP_TO_FLASH is not set +# CONFIG_ESP32_ENABLE_COREDUMP_TO_UART is not set +CONFIG_ESP32_ENABLE_COREDUMP_TO_NONE=y +CONFIG_TIMER_TASK_PRIORITY=1 +CONFIG_TIMER_TASK_STACK_DEPTH=2048 +CONFIG_TIMER_QUEUE_LENGTH=10 +# CONFIG_ENABLE_STATIC_TASK_CLEAN_UP_HOOK is not set +CONFIG_SPIRAM_ALLOW_STACK_EXTERNAL_MEMORY=y +# CONFIG_HAL_ASSERTION_SILIENT is not set +# CONFIG_L2_TO_L3_COPY is not set +CONFIG_ESP_GRATUITOUS_ARP=y +CONFIG_GARP_TMR_INTERVAL=60 +CONFIG_TCPIP_RECVMBOX_SIZE=32 +CONFIG_TCP_MAXRTX=12 +CONFIG_TCP_SYNMAXRTX=12 +CONFIG_TCP_MSS=1440 +CONFIG_TCP_MSL=60000 +CONFIG_TCP_SND_BUF_DEFAULT=5760 +CONFIG_TCP_WND_DEFAULT=5760 +CONFIG_TCP_RECVMBOX_SIZE=6 +CONFIG_TCP_QUEUE_OOSEQ=y +CONFIG_TCP_OVERSIZE_MSS=y +# CONFIG_TCP_OVERSIZE_QUARTER_MSS is not set +# CONFIG_TCP_OVERSIZE_DISABLE is not set +CONFIG_UDP_RECVMBOX_SIZE=6 +CONFIG_TCPIP_TASK_STACK_SIZE=3072 +CONFIG_TCPIP_TASK_AFFINITY_NO_AFFINITY=y +# CONFIG_TCPIP_TASK_AFFINITY_CPU0 is not set +# CONFIG_TCPIP_TASK_AFFINITY_CPU1 is not set +CONFIG_TCPIP_TASK_AFFINITY=0x7FFFFFFF +CONFIG_PPP_SUPPORT=y +# CONFIG_PPP_NOTIFY_PHASE_SUPPORT is not set +# CONFIG_PPP_PAP_SUPPORT is not set +# CONFIG_PPP_CHAP_SUPPORT is not set +# CONFIG_PPP_MSCHAP_SUPPORT is not set +# CONFIG_PPP_MPPE_SUPPORT is not set +# CONFIG_PPP_DEBUG_ON is not set +CONFIG_ESP32_PTHREAD_TASK_PRIO_DEFAULT=5 +CONFIG_ESP32_PTHREAD_TASK_STACK_SIZE_DEFAULT=3072 +CONFIG_ESP32_PTHREAD_STACK_MIN=768 +CONFIG_ESP32_DEFAULT_PTHREAD_CORE_NO_AFFINITY=y +# CONFIG_ESP32_DEFAULT_PTHREAD_CORE_0 is not set +# CONFIG_ESP32_DEFAULT_PTHREAD_CORE_1 is not set +CONFIG_ESP32_PTHREAD_TASK_CORE_DEFAULT=-1 +CONFIG_ESP32_PTHREAD_TASK_NAME_DEFAULT="pthread" +CONFIG_SPI_FLASH_WRITING_DANGEROUS_REGIONS_ABORTS=y +# CONFIG_SPI_FLASH_WRITING_DANGEROUS_REGIONS_FAILS is not set +# CONFIG_SPI_FLASH_WRITING_DANGEROUS_REGIONS_ALLOWED is not set +CONFIG_SUPPRESS_SELECT_DEBUG_OUTPUT=y +CONFIG_SUPPORT_TERMIOS=y +CONFIG_SEMIHOSTFS_MAX_MOUNT_POINTS=1 +# CONFIG_ESP_SPI_HOST_INTERFACE is not set +CONFIG_ESP_SDIO_HOST_INTERFACE=y +# CONFIG_ESP_SPI_HD_HOST_INTERFACE is not set +# CONFIG_ESP_UART_HOST_INTERFACE is not set +CONFIG_IDF_SLAVE_TARGET="esp32c6" +# CONFIG_SDIO_RESET_ACTIVE_HIGH is not set +# CONFIG_ESP_SDIO_OPTIMIZATION_RX_NONE is not set +# CONFIG_ESP_SDIO_OPTIMIZATION_RX_MAX_SIZE is not set +CONFIG_ESP_SDIO_OPTIMIZATION_RX_STREAMING_MODE=y +CONFIG_ESP_SDIO_GPIO_RESET_SLAVE=15 +CONFIG_ESP_SDIO_4_BIT_BUS=y +# CONFIG_ESP_SDIO_1_BIT_BUS is not set +CONFIG_ESP_SDIO_BUS_WIDTH=4 +CONFIG_ESP_SDIO_CLOCK_FREQ_KHZ=40000 +CONFIG_ESP_SDIO_PIN_CMD=13 +CONFIG_ESP_SDIO_PIN_CLK=12 +CONFIG_ESP_SDIO_PIN_D0=11 +CONFIG_ESP_SDIO_PIN_D2=9 +CONFIG_ESP_SDIO_PIN_D3=8 +CONFIG_ESP_SDIO_PIN_D1=10 +CONFIG_ESP_SDIO_TX_Q_SIZE=20 +CONFIG_ESP_SDIO_RX_Q_SIZE=20 +# CONFIG_ESP_SDIO_CHECKSUM is not set +CONFIG_ESP_GPIO_SLAVE_RESET_SLAVE=15 +CONFIG_RESET_GPIO_ACTIVE_LOW=y +CONFIG_ESP_RPC_TASK_STACK=4096 +CONFIG_ESP_DFLT_TASK_STACK=3072 +CONFIG_ESP_USE_MEMPOOL=y +CONFIG_ESP_MAX_SIMULTANEOUS_SYNC_RPC_REQUESTS=5 +CONFIG_ESP_MAX_SIMULTANEOUS_ASYNC_RPC_REQUESTS=5 +# CONFIG_ESP_RAW_THROUGHPUT_TRANSPORT is not set +# CONFIG_ESP_PKT_STATS is not set +CONFIG_HOST_TO_ESP_WIFI_DATA_THROTTLE=y +CONFIG_PRIV_WIFI_TX_SDIO_HIGH_THRESHOLD=80 +CONFIG_TO_WIFI_DATA_THROTTLE_HIGH_THRESHOLD=80 +CONFIG_TO_WIFI_DATA_THROTTLE_LOW_THRESHOLD=60 +CONFIG_ESP32_WIFI_STATIC_RX_BUFFER_NUM=10 +CONFIG_ESP32_WIFI_DYNAMIC_RX_BUFFER_NUM=32 +CONFIG_ESP32_WIFI_TX_BUFFER_TYPE=1 +CONFIG_ESP32_WIFI_DYNAMIC_TX_BUFFER_NUM=32 +CONFIG_ESP32_WIFI_AMPDU_TX_ENABLED=y +CONFIG_ESP32_WIFI_TX_BA_WIN=6 +CONFIG_ESP32_WIFI_AMPDU_RX_ENABLED=y +CONFIG_ESP32_WIFI_RX_BA_WIN=6 +CONFIG_ESP32_WIFI_NVS_ENABLED=y +CONFIG_ESP32_WIFI_SOFTAP_BEACON_MAX_LEN=752 +CONFIG_ESP32_WIFI_MGMT_SBUF_NUM=32 +CONFIG_ESP32_WIFI_IRAM_OPT=y +CONFIG_ESP32_WIFI_RX_IRAM_OPT=y +CONFIG_ESP32_WIFI_ENABLE_WPA3_SAE=y +CONFIG_ESP32_WIFI_ENABLE_WPA3_OWE_STA=y +CONFIG_WPA_MBEDTLS_CRYPTO=y +CONFIG_WPA_MBEDTLS_TLS_CLIENT=y +# End of deprecated options diff --git a/m5stack/boards/M5STACK_Tab5/sdkconfig.esp_hosted b/m5stack/boards/M5STACK_Tab5/sdkconfig.esp_hosted new file mode 100644 index 00000000..f52190b9 --- /dev/null +++ b/m5stack/boards/M5STACK_Tab5/sdkconfig.esp_hosted @@ -0,0 +1,91 @@ +CONFIG_ESP_HOSTED_ENABLED=y + +# +# ESP-Hosted config +# + +# +# ESP32-C6 is Slave Target from Wi-Fi Remote Component +# +CONFIG_ESP_HOSTED_PRIV_SDIO_OPTION=y +CONFIG_ESP_HOSTED_PRIV_SPI_HD_OPTION=y +# CONFIG_ESP_HOSTED_SPI_HOST_INTERFACE is not set +CONFIG_ESP_HOSTED_SDIO_HOST_INTERFACE=y +# CONFIG_ESP_HOSTED_SPI_HD_HOST_INTERFACE is not set +# CONFIG_ESP_HOSTED_UART_HOST_INTERFACE is not set +CONFIG_ESP_HOSTED_IDF_SLAVE_TARGET="esp32c6" + +# +# Hosted SDIO Configuration +# +# CONFIG_ESP_HOSTED_SDIO_RESET_ACTIVE_HIGH is not set +CONFIG_ESP_HOSTED_SDIO_RESET_ACTIVE_LOW=y +# CONFIG_ESP_HOSTED_SDIO_OPTIMIZATION_RX_NONE is not set +# CONFIG_ESP_HOSTED_SDIO_OPTIMIZATION_RX_MAX_SIZE is not set +CONFIG_ESP_HOSTED_SDIO_OPTIMIZATION_RX_STREAMING_MODE=y +CONFIG_ESP_HOSTED_SDIO_GPIO_RESET_SLAVE=15 +CONFIG_ESP_HOSTED_SDIO_4_BIT_BUS=y +# CONFIG_ESP_HOSTED_SDIO_1_BIT_BUS is not set +CONFIG_ESP_HOSTED_SDIO_BUS_WIDTH=4 +CONFIG_ESP_HOSTED_SDIO_CLOCK_FREQ_KHZ=40000 +CONFIG_ESP_HOSTED_SDIO_PIN_CMD=13 +CONFIG_ESP_HOSTED_SDIO_PIN_CLK=12 +CONFIG_ESP_HOSTED_SDIO_PIN_D0=11 +CONFIG_ESP_HOSTED_SDIO_PRIV_PIN_D1_4BIT_BUS=10 +CONFIG_ESP_HOSTED_SDIO_PIN_D2=9 +CONFIG_ESP_HOSTED_SDIO_PIN_D3=8 +CONFIG_ESP_HOSTED_SDIO_PIN_D1=10 +CONFIG_ESP_HOSTED_SDIO_TX_Q_SIZE=20 +CONFIG_ESP_HOSTED_SDIO_RX_Q_SIZE=20 +# CONFIG_ESP_HOSTED_SDIO_CHECKSUM is not set +# end of Hosted SDIO Configuration + +CONFIG_ESP_HOSTED_GPIO_SLAVE_RESET_SLAVE=15 +CONFIG_ESP_HOSTED_RESET_GPIO_ACTIVE_LOW=y + +# +# Bluetooth Support +# +CONFIG_ESP_HOSTED_ENABLE_BT_NIMBLE=y +CONFIG_ESP_HOSTED_NIMBLE_HCI_VHCI=y + +# +# Wi-Fi Remote +# +CONFIG_ESP_WIFI_REMOTE_ENABLED=y +# CONFIG_SLAVE_IDF_TARGET_ESP32 is not set +# CONFIG_SLAVE_IDF_TARGET_ESP32S2 is not set +# CONFIG_SLAVE_IDF_TARGET_ESP32C3 is not set +# CONFIG_SLAVE_IDF_TARGET_ESP32S3 is not set +# CONFIG_SLAVE_IDF_TARGET_ESP32C2 is not set +CONFIG_SLAVE_IDF_TARGET_ESP32C6=y +# CONFIG_SLAVE_IDF_TARGET_ESP32C5 is not set +# CONFIG_SLAVE_IDF_TARGET_ESP32C61 is not set +CONFIG_SLAVE_SOC_WIFI_SUPPORTED=y +CONFIG_SLAVE_SOC_WIFI_WAPI_SUPPORT=y +CONFIG_SLAVE_SOC_WIFI_CSI_SUPPORT=y +CONFIG_SLAVE_SOC_WIFI_MESH_SUPPORT=y +CONFIG_SLAVE_SOC_WIFI_LIGHT_SLEEP_CLK_WIDTH=12 +CONFIG_SLAVE_SOC_WIFI_HW_TSF=y +CONFIG_SLAVE_SOC_WIFI_FTM_SUPPORT=y +CONFIG_SLAVE_FREERTOS_UNICORE=y +CONFIG_SLAVE_SOC_WIFI_GCMP_SUPPORT=y +CONFIG_SLAVE_IDF_TARGET_ARCH_RISCV=y +CONFIG_SLAVE_SOC_WIFI_HE_SUPPORT=y +CONFIG_SLAVE_SOC_WIFI_MAC_VERSION_NUM=2 +CONFIG_ESP_WIFI_REMOTE_LIBRARY_HOSTED=y +# CONFIG_ESP_WIFI_REMOTE_LIBRARY_EPPP is not set + + +# +# Bluetooth +# +CONFIG_BT_ENABLED=y +CONFIG_BT_CONTROLLER_DISABLED=y +CONFIG_BT_BLUEDROID_ENABLED=n +CONFIG_BT_NIMBLE_ENABLED=y +CONFIG_BT_NIMBLE_TRANSPORT_UART=n +CONFIG_BT_NIMBLE_SVC_GAP_DEVICE_NAME="MPY ESP32" +CONFIG_BT_ALARM_MAX_NUM=50 +# end of Bluetooth + diff --git a/m5stack/boards/M5STACK_Tab5/sdkconfig.spiram_hex b/m5stack/boards/M5STACK_Tab5/sdkconfig.spiram_hex new file mode 100644 index 00000000..d8f82021 --- /dev/null +++ b/m5stack/boards/M5STACK_Tab5/sdkconfig.spiram_hex @@ -0,0 +1,31 @@ +CONFIG_IDF_EXPERIMENTAL_FEATURES=y +# +# ESP PSRAM +# +CONFIG_SPIRAM=y + +# +# PSRAM config +# +CONFIG_SPIRAM_MODE_HEX=y +CONFIG_SPIRAM_SPEED_200M=y +# CONFIG_SPIRAM_SPEED_20M is not set +CONFIG_SPIRAM_SPEED=200 +CONFIG_SPIRAM_FETCH_INSTRUCTIONS=y +CONFIG_SPIRAM_RODATA=y +CONFIG_SPIRAM_XIP_FROM_PSRAM=y +CONFIG_SPIRAM_FLASH_LOAD_TO_PSRAM=y +# CONFIG_SPIRAM_ECC_ENABLE is not set +CONFIG_SPIRAM_BOOT_INIT=y +# CONFIG_SPIRAM_IGNORE_NOTFOUND is not set +# CONFIG_SPIRAM_USE_MEMMAP is not set +# CONFIG_SPIRAM_USE_CAPS_ALLOC is not set +CONFIG_SPIRAM_USE_MALLOC=y +CONFIG_SPIRAM_MEMTEST=y +CONFIG_SPIRAM_MALLOC_ALWAYSINTERNAL=16384 +CONFIG_SPIRAM_TRY_ALLOCATE_WIFI_LWIP=y +CONFIG_SPIRAM_MALLOC_RESERVE_INTERNAL=32768 +# CONFIG_SPIRAM_ALLOW_BSS_SEG_EXTERNAL_MEMORY is not set +# CONFIG_SPIRAM_ALLOW_NOINIT_SEG_EXTERNAL_MEMORY is not set +# end of PSRAM config +# end of ESP PSRAM diff --git a/m5stack/boards/include/board_init.h b/m5stack/boards/include/board_init.h index 0ecbda66..0158f464 100644 --- a/m5stack/boards/include/board_init.h +++ b/m5stack/boards/include/board_init.h @@ -10,6 +10,8 @@ #if CONFIG_CORES3 #include "../M5STACK_CoreS3/audioconfigboard.h" +#elif CONFIG_TAB5 +#include "../M5STACK_Tab5/audioconfigboard.h" #endif /** diff --git a/m5stack/cmodules/cmodules.cmake b/m5stack/cmodules/cmodules.cmake index 8e74c117..62f59a57 100644 --- a/m5stack/cmodules/cmodules.cmake +++ b/m5stack/cmodules/cmodules.cmake @@ -6,7 +6,7 @@ include(${CMAKE_CURRENT_LIST_DIR}/cdriver/cdriver.cmake) # add m5audio2 module -if(IDF_TARGET STREQUAL "esp32" OR IDF_TARGET STREQUAL "esp32s3") +if(IDF_TARGET STREQUAL "esp32" OR IDF_TARGET STREQUAL "esp32s3" OR IDF_TARGET STREQUAL "esp32p4") include(${CMAKE_CURRENT_LIST_DIR}/m5audio2/m5audio2.cmake) endif() diff --git a/m5stack/cmodules/m5unified/m5unified.c b/m5stack/cmodules/m5unified/m5unified.c index 3a0067f0..5e4a4573 100644 --- a/m5stack/cmodules/m5unified/m5unified.c +++ b/m5stack/cmodules/m5unified/m5unified.c @@ -29,6 +29,7 @@ static const mp_rom_map_elem_t m5_board_member_table[] = { { MP_ROM_QSTR(MP_QSTR_M5AtomS3R), MP_ROM_INT(18) }, { MP_ROM_QSTR(MP_QSTR_M5PaperS3), MP_ROM_INT(19) }, { MP_ROM_QSTR(MP_QSTR_M5StamPLC), MP_ROM_INT(21) }, + { MP_ROM_QSTR(MP_QSTR_M5Tab5), MP_ROM_INT(22) }, // non display boards { MP_ROM_QSTR(MP_QSTR_M5Atom), MP_ROM_INT(128) }, { MP_ROM_QSTR(MP_QSTR_M5AtomPsram), MP_ROM_INT(129) }, diff --git a/m5stack/components/M5Unified/M5GFX b/m5stack/components/M5Unified/M5GFX index 30f92321..c7b2726f 160000 --- a/m5stack/components/M5Unified/M5GFX +++ b/m5stack/components/M5Unified/M5GFX @@ -1 +1 @@ -Subproject commit 30f9232182bf4032ff1f412b73a18f66860d4404 +Subproject commit c7b2726fc043d5720bcbbd4f2036f41649749078 diff --git a/m5stack/libs/module/ain4.py b/m5stack/libs/module/ain4.py index 738b880f..9b0490dd 100644 --- a/m5stack/libs/module/ain4.py +++ b/m5stack/libs/module/ain4.py @@ -2,7 +2,7 @@ # # SPDX-License-Identifier: MIT -from .mbus import i2c1 +from . import mbus import struct import time @@ -36,7 +36,7 @@ def __init__(self, address: int | list | tuple = _AIN_ADDR) -> None: @param i2c I2C port to use. @param address I2C address of the AIN4Module. """ - self._i2c = i2c1 + self._i2c = mbus.i2c1 self._i2c_addr = address if self._i2c_addr not in self._i2c.scan(): raise Exception("AIN 4-20mA Module not found in Base") diff --git a/m5stack/libs/module/audio.py b/m5stack/libs/module/audio.py index 9f3846e4..bb26bc7c 100644 --- a/m5stack/libs/module/audio.py +++ b/m5stack/libs/module/audio.py @@ -2,7 +2,7 @@ # # SPDX-License-Identifier: MIT from driver import es8388 -from .mbus import i2c1 +from . import mbus import m5audio2 import machine import M5 @@ -164,7 +164,7 @@ def __init__( offset=1, mux=MUX_NATIONAL, ): - self._i2c = i2c1 + self._i2c = mbus.i2c1 self.exp = _Expander(self._i2c) self.es = es8388.ES8388(self._i2c) self.exp.fill_color(0x000000) diff --git a/m5stack/libs/module/commu.py b/m5stack/libs/module/commu.py index 79151efb..d679f493 100644 --- a/m5stack/libs/module/commu.py +++ b/m5stack/libs/module/commu.py @@ -4,7 +4,7 @@ from driver.mcp2515.mcp2515_spi import MCP2515_CAN from driver.mcp2515.can_frame import CANFrame -from machine import UART, I2C +import machine import time ERROR_OK = 0 @@ -25,6 +25,8 @@ M5.BOARD.M5Stack: MBusIO(12, 15, 18, 23, 19), M5.BOARD.M5StackCore2: MBusIO(27, 2, 18, 23, 38), M5.BOARD.M5StackCoreS3: MBusIO(6, 13, 36, 37, 35), + M5.BOARD.M5Tough: MBusIO(27, 2, 18, 23, 38), + M5.BOARD.M5Tab5: MBusIO(2, 47, 5, 18, 19), }.get(M5.getBoard()) @@ -273,7 +275,7 @@ def __new__( id, **kwargs, ): - return UART( + return machine.UART( id, **kwargs, ) @@ -285,7 +287,7 @@ def __new__( id, **kwargs, ): - return I2C( + return machine.I2C( id, **kwargs, ) diff --git a/m5stack/libs/module/dmx.py b/m5stack/libs/module/dmx.py index 47328b11..492a5b85 100644 --- a/m5stack/libs/module/dmx.py +++ b/m5stack/libs/module/dmx.py @@ -12,6 +12,8 @@ M5.BOARD.M5Stack: MBusIO(13, 35, 12), M5.BOARD.M5StackCore2: MBusIO(19, 35, 27), M5.BOARD.M5StackCoreS3: MBusIO(7, 10, 6), + M5.BOARD.M5Tough: MBusIO(19, 35, 27), + M5.BOARD.M5Tab5: MBusIO(48, 16, 2), }.get(M5.getBoard()) diff --git a/m5stack/libs/module/dual_kmeter.py b/m5stack/libs/module/dual_kmeter.py index b7c75027..4b4bcd98 100644 --- a/m5stack/libs/module/dual_kmeter.py +++ b/m5stack/libs/module/dual_kmeter.py @@ -3,7 +3,7 @@ # SPDX-License-Identifier: MIT from micropython import const -from .mbus import i2c1 +from . import mbus import struct @@ -75,4 +75,4 @@ def _int_convert(self, value) -> int: class DualKmeterModule(DualKmeterBase): def __init__(self, address: int = _DUAL_KMETER_DEFAULT_ADDRESS): - super().__init__(i2c1, address) + super().__init__(mbus.i2c1, address) diff --git a/m5stack/libs/module/gnss.py b/m5stack/libs/module/gnss.py index 1d8eae8a..f0cb6a01 100644 --- a/m5stack/libs/module/gnss.py +++ b/m5stack/libs/module/gnss.py @@ -2,7 +2,7 @@ # # SPDX-License-Identifier: MIT import machine -from .mbus import i2c1 +from . import mbus from .module_helper import ModuleError from driver.bmi270_bmm150 import BMI270_BMM150 from driver.bmp280 import BMP280 @@ -79,7 +79,7 @@ def __init__( '0x69': '0x69' '0x68': '0x68' """ - self._i2c = i2c1 + self._i2c = mbus.i2c1 self._addr = address self.uart_data = "" self.gps_time = "00:00:00" diff --git a/m5stack/libs/module/goplus2.py b/m5stack/libs/module/goplus2.py index d99e37a5..00d42e87 100644 --- a/m5stack/libs/module/goplus2.py +++ b/m5stack/libs/module/goplus2.py @@ -2,7 +2,7 @@ # # SPDX-License-Identifier: MIT -from .mbus import i2c1 +from . import mbus import struct @@ -37,7 +37,7 @@ def __init__(self, address: int | list | tuple = _GOPLUS2_ADDR) -> None: @param address The I2C address of the GoPlus2 module (default is 0x38). """ - self._i2c = i2c1 + self._i2c = mbus.i2c1 self._i2c_addr = address if self._i2c_addr not in self._i2c.scan(): raise Exception("GoPlus2 Module not found in Base") diff --git a/m5stack/libs/module/grbl.py b/m5stack/libs/module/grbl.py index 1983ee35..cbf1e84c 100644 --- a/m5stack/libs/module/grbl.py +++ b/m5stack/libs/module/grbl.py @@ -2,7 +2,7 @@ # # SPDX-License-Identifier: MIT -from .mbus import i2c1 +from . import mbus from .module_helper import ModuleError from micropython import const import re @@ -52,7 +52,7 @@ def __init__( note: The I2C address of the device. """ - self.i2c = i2c1 + self.i2c = mbus.i2c1 self.addr = address # Check if the devices are connected and accessible diff --git a/m5stack/libs/module/hmi.py b/m5stack/libs/module/hmi.py index 732b7100..688aab1b 100644 --- a/m5stack/libs/module/hmi.py +++ b/m5stack/libs/module/hmi.py @@ -2,7 +2,7 @@ # # SPDX-License-Identifier: MIT -from .mbus import i2c1 +from . import mbus import struct import time @@ -35,7 +35,7 @@ def __init__(self, address: int | list | tuple = _HMI_ADDR) -> None: @param address I2C address of the HMIModule. """ - self._i2c = i2c1 + self._i2c = mbus.i2c1 self._i2c_addr = address if self._i2c_addr not in self._i2c.scan(): raise Exception("HMI Module not found in Base") diff --git a/m5stack/libs/module/iot_base_catm.py b/m5stack/libs/module/iot_base_catm.py index f0a988ac..01bf8a21 100644 --- a/m5stack/libs/module/iot_base_catm.py +++ b/m5stack/libs/module/iot_base_catm.py @@ -2,10 +2,10 @@ # # SPDX-License-Identifier: MIT -from machine import UART, Pin +import machine from driver.simcom.sim7080 import SIM7080 from driver.modbus.master.uSerial import uSerial -from M5 import getBoard, BOARD +import M5 from collections import namedtuple from .module_helper import ModuleError import time @@ -14,15 +14,17 @@ MBusIO = namedtuple("MBusIO", ["modem_tx", "modem_rx", "rs485_tx", "rs485_rx", "pwr_ctrl"]) iomap = { - BOARD.M5Stack: MBusIO(0, 35, 15, 13, 12), - BOARD.M5StackCore2: MBusIO(0, 35, 2, 19, 27), - BOARD.M5StackCoreS3: MBusIO(0, 10, 13, 7, 6), -}.get(getBoard()) + M5.BOARD.M5Stack: MBusIO(0, 35, 15, 13, 12), + M5.BOARD.M5StackCore2: MBusIO(0, 35, 2, 19, 27), + M5.BOARD.M5StackCoreS3: MBusIO(0, 10, 13, 7, 6), + M5.BOARD.M5Tough: MBusIO(0, 35, 2, 19, 27), + M5.BOARD.M5Tab5: MBusIO(35, 16, 47, 48, 2), +}.get(M5.getBoard()) class IotBaseCatmModule(SIM7080, uSerial): def __init__(self) -> None: - self.modem_uart = UART( + self.modem_uart = machine.UART( 1, tx=iomap.modem_tx, rx=iomap.modem_rx, @@ -33,7 +35,7 @@ def __init__(self) -> None: rxbuf=1024, ) SIM7080.__init__(self, uart=self.modem_uart) - self.pwr_ctrl = Pin(iomap.pwr_ctrl, Pin.OUT) + self.pwr_ctrl = machine.Pin(iomap.pwr_ctrl, machine.Pin.OUT) self.modem_power_ctrl(1) if not self.check_modem_is_ready(): raise ModuleError("IoT Base CATM Module maybe not connect") diff --git a/m5stack/libs/module/llm.py b/m5stack/libs/module/llm.py index fa5e5d17..5fed5991 100644 --- a/m5stack/libs/module/llm.py +++ b/m5stack/libs/module/llm.py @@ -2,11 +2,9 @@ # # SPDX-License-Identifier: MIT -from machine import UART +import machine import time import ujson -from M5 import getBoard, BOARD -from collections import namedtuple class ModuleComm: @@ -781,7 +779,7 @@ def _free_temp(self): class LlmModule: def __init__(self, uart_id=1, tx=17, rx=16) -> None: - self._uart = UART( + self._uart = machine.UART( uart_id, tx=tx, rx=rx, diff --git a/m5stack/libs/module/lora.py b/m5stack/libs/module/lora.py index f307cb05..1b3030c0 100644 --- a/m5stack/libs/module/lora.py +++ b/m5stack/libs/module/lora.py @@ -2,13 +2,13 @@ # # SPDX-License-Identifier: MIT -from .mbus import spi2 +from . import mbus -from machine import Pin, SPI +import machine +import micropython from lora import SX1278 from lora import SX1276 from lora import RxPacket -from micropython import const, schedule class LoraModule: @@ -44,8 +44,8 @@ def callback(received_data): """ constant: Select the LoRa frequency band. """ - LORA_433 = const(1) - LORA_868 = const(2) + LORA_433 = micropython.const(1) + LORA_868 = micropython.const(2) """ constant: Valid bandwidth @@ -109,11 +109,11 @@ def __init__( } self.modem = sx_instance( - spi=spi2, - cs=Pin(pin_cs), - dio0=Pin(pin_irq), - # dio1=Pin(35), - reset=Pin(pin_rst), + spi=mbus.spi2, + cs=machine.Pin(pin_cs), + dio0=machine.Pin(pin_irq), + # dio1=machine.Pin(35), + reset=machine.Pin(pin_rst), lora_cfg=lora_cfg, ) @@ -188,7 +188,7 @@ def set_irq_callback(self, callback): def _irq_callback(): if self.irq_callback: - schedule(self.irq_callback, self.modem.poll_recv()) + micropython.schedule(self.irq_callback, self.modem.poll_recv()) self.irq_callback = callback self.modem.set_irq_callback(_irq_callback) diff --git a/m5stack/libs/module/lora868_v12.py b/m5stack/libs/module/lora868_v12.py index 817e34a7..5637b402 100644 --- a/m5stack/libs/module/lora868_v12.py +++ b/m5stack/libs/module/lora868_v12.py @@ -2,11 +2,11 @@ # # SPDX-License-Identifier: MIT -from .mbus import spi2 -from machine import Pin, SPI +from . import mbus +import machine +import micropython from lora import SX1262 from lora import RxPacket -from micropython import const, schedule class LoRa868V12Module: @@ -91,11 +91,11 @@ def __init__( } self.modem = SX1262( - spi=spi2, - reset=Pin(pin_rst), - cs=Pin(pin_cs), - busy=Pin(pin_busy), - dio1=Pin(pin_irq), + spi=mbus.spi2, + reset=machine.Pin(pin_rst), + cs=machine.Pin(pin_cs), + busy=machine.Pin(pin_busy), + dio1=machine.Pin(pin_irq), dio3_tcxo_millivolts=3300, # 3300mV lora_cfg=lora_cfg, ) @@ -332,7 +332,7 @@ def set_irq_callback(self, callback) -> None: def _irq_callback(): if self.irq_callback: - schedule(self.irq_callback, self.modem.poll_recv()) + micropython.schedule(self.irq_callback, self.modem.poll_recv()) self.modem.set_irq_callback(_irq_callback) diff --git a/m5stack/libs/module/mbus.py b/m5stack/libs/module/mbus.py index 967915ca..dabf82a6 100644 --- a/m5stack/libs/module/mbus.py +++ b/m5stack/libs/module/mbus.py @@ -2,7 +2,7 @@ # # SPDX-License-Identifier: MIT -from machine import I2C, Pin, SPI +import machine import M5 from collections import namedtuple @@ -23,21 +23,29 @@ M5.BOARD.M5Tough: MBusIO( sda0=32, scl0=33, sda1=21, scl1=22, spi2_sck=18, spi2_mosi=23, spi2_miso=38 ), + M5.BOARD.M5Tab5: MBusIO( + sda0=53, scl0=54, sda1=31, scl1=32, spi2_sck=5, spi2_mosi=18, spi2_miso=19 + ), }.get(M5.getBoard()) def _i2c0_init(): - return I2C(0, scl=Pin(iomap.scl0), sda=Pin(iomap.sda0), freq=100000) + return machine.I2C(0, scl=machine.Pin(iomap.scl0), sda=machine.Pin(iomap.sda0), freq=100000) def _i2c1_init(): if iomap.scl1 == iomap.scl0 and iomap.sda1 == iomap.sda0: return _i2c0_init() - return I2C(1, scl=Pin(iomap.scl1), sda=Pin(iomap.sda1), freq=100000) + return machine.I2C(1, scl=machine.Pin(iomap.scl1), sda=machine.Pin(iomap.sda1), freq=100000) def _spi2_init(): - return SPI(1, sck=Pin(iomap.spi2_sck), mosi=Pin(iomap.spi2_mosi), miso=Pin(iomap.spi2_miso)) + return machine.SPI( + 1, + sck=machine.Pin(iomap.spi2_sck), + mosi=machine.Pin(iomap.spi2_mosi), + miso=machine.Pin(iomap.spi2_miso), + ) _attrs = { diff --git a/m5stack/libs/module/module_4in8out.py b/m5stack/libs/module/module_4in8out.py index aee9e685..b15db4a9 100644 --- a/m5stack/libs/module/module_4in8out.py +++ b/m5stack/libs/module/module_4in8out.py @@ -2,7 +2,7 @@ # # SPDX-License-Identifier: MIT -from .mbus import i2c1 +from . import mbus import struct import time @@ -32,7 +32,7 @@ def __init__(self, address: int | list | tuple = _MODULE4IN8OUT_ADDR) -> None: @param address I2C address of the 4In8OutModule. """ - self._i2c = i2c1 + self._i2c = mbus.i2c1 self._i2c_addr = address if self._i2c_addr not in self._i2c.scan(): raise Exception("4In8Out Module not found in Base") diff --git a/m5stack/libs/module/plus.py b/m5stack/libs/module/plus.py index 2b2600a6..d4df0986 100644 --- a/m5stack/libs/module/plus.py +++ b/m5stack/libs/module/plus.py @@ -2,7 +2,7 @@ # # SPDX-License-Identifier: MIT -from .mbus import i2c1 +from . import mbus class PLUSModule: @@ -24,7 +24,7 @@ def __init__(self, address: int | list | tuple = _PLUS_ADDR) -> None: @param address I2C address of the PLUSModule. """ - self._i2c = i2c1 + self._i2c = mbus.i2c1 self._i2c_addr = address if self._i2c_addr not in self._i2c.scan(): raise Exception("PLUS Module not found in Base") diff --git a/m5stack/libs/module/pm25.py b/m5stack/libs/module/pm25.py index 25ca7a8b..dddf55a4 100644 --- a/m5stack/libs/module/pm25.py +++ b/m5stack/libs/module/pm25.py @@ -7,7 +7,7 @@ from driver.sht30 import SHT30 from driver.sht20 import SHT20 from collections import namedtuple -from .mbus import i2c1 +from . import mbus import struct import sys @@ -20,6 +20,8 @@ M5.BOARD.M5Stack: MBusIO(17, 16, 13), M5.BOARD.M5StackCore2: MBusIO(14, 13, 19), M5.BOARD.M5StackCoreS3: MBusIO(17, 16, 7), + M5.BOARD.M5Tough: MBusIO(14, 13, 19), + M5.BOARD.M5Tab5: MBusIO(6, 7, 48), }.get(M5.getBoard()) @@ -32,7 +34,7 @@ def __init__(self, id: Literal[0, 1, 2]): self.uart = machine.UART( id, baudrate=9600, tx=machine.Pin(iomap.bus_tx), rx=machine.Pin(iomap.bus_rx) ) - self.i2c = i2c1 + self.i2c = mbus.i2c1 self.sht30 = self.sht20 = None if 0x44 in self.i2c.scan(): self.sht30 = SHT30(self.i2c) diff --git a/m5stack/libs/module/pps.py b/m5stack/libs/module/pps.py index 80aa795c..fe32a6ed 100644 --- a/m5stack/libs/module/pps.py +++ b/m5stack/libs/module/pps.py @@ -3,7 +3,7 @@ # SPDX-License-Identifier: MIT # Import necessary libraries -from .mbus import i2c1 +from . import mbus from .module_helper import ModuleError import struct @@ -51,7 +51,7 @@ def __init__(self, address=0x35): @param addr I2C address of the device. """ - self.i2c = i2c1 + self.i2c = mbus.i2c1 self.addr = address # Check if the devices are connected and accessible diff --git a/m5stack/libs/module/relay_2.py b/m5stack/libs/module/relay_2.py index d8ca57e7..957ad2c9 100644 --- a/m5stack/libs/module/relay_2.py +++ b/m5stack/libs/module/relay_2.py @@ -2,7 +2,7 @@ # # SPDX-License-Identifier: MIT -from .mbus import i2c1 +from . import mbus from .module_helper import ModuleError import time @@ -30,7 +30,7 @@ def __init__(self, address: int | list | tuple = _RELAY2_ADDR) -> None: @param address: I2C address of the Relay2Module. """ - self._i2c = i2c1 + self._i2c = mbus.i2c1 self._i2c_addr = address if self._i2c_addr not in self._i2c.scan(): raise Exception("2 Relay Module not found in Base") diff --git a/m5stack/libs/module/relay_4.py b/m5stack/libs/module/relay_4.py index 413988b4..d48dacfc 100644 --- a/m5stack/libs/module/relay_4.py +++ b/m5stack/libs/module/relay_4.py @@ -2,7 +2,7 @@ # # SPDX-License-Identifier: MIT -from .mbus import i2c1 +from . import mbus from .module_helper import ModuleError import struct import time @@ -99,5 +99,5 @@ def set_i2c_address(self, addr): class Relay4Module(Relay_Stack): def __init__(self, address: int = DEV_I2C_ADDR): - super().__init__(i2c1, address) + super().__init__(mbus.i2c1, address) self._available() diff --git a/m5stack/libs/module/servo2.py b/m5stack/libs/module/servo2.py index 0c910710..15f9da9a 100644 --- a/m5stack/libs/module/servo2.py +++ b/m5stack/libs/module/servo2.py @@ -1,5 +1,5 @@ from driver.pca9685 import Servos -from .mbus import i2c1 +from . import mbus class Servo2Module(Servos): @@ -8,7 +8,7 @@ def __init__(self, address=0x40, freq=50, min_us=400, max_us=2350, degrees=180): self.min_us = min_us self.max_us = max_us self.degrees = degrees - self.i2c = i2c1 + self.i2c = mbus.i2c1 if self._addr not in self.i2c.scan(): raise Exception("Servo2 Module not found at I2C address 0x%02X" % self._addr) diff --git a/m5stack/libs/module/step_motor_driver.py b/m5stack/libs/module/step_motor_driver.py index 6c4f4bf6..2e8cce3e 100644 --- a/m5stack/libs/module/step_motor_driver.py +++ b/m5stack/libs/module/step_motor_driver.py @@ -2,11 +2,11 @@ # # SPDX-License-Identifier: MIT -from .mbus import i2c1 +from . import mbus from .module_helper import ModuleError import struct -from micropython import const -from machine import Pin, PWM +import micropython +import machine class StepMotorDriverModule: @@ -32,37 +32,37 @@ class StepMotorDriverModule: """ constant: Motor IDs """ - MOTOR_X = const(0) - MOTOR_Y = const(1) - MOTOR_Z = const(2) + MOTOR_X = micropython.const(0) + MOTOR_Y = micropython.const(1) + MOTOR_Z = micropython.const(2) """ constant: Motor states """ - MOTOR_STATE_ENABLE = const(1) - MOTOR_STATE_DISABLE = const(0) + MOTOR_STATE_ENABLE = micropython.const(1) + MOTOR_STATE_DISABLE = micropython.const(0) """ constant: Register addresses """ - INPUT_REG = const(0x00) - OUTPUT_REG = const(0x01) - POLINV_REG = const(0x02) - CONFIG_REG = const(0x03) - FAULT_REG = const(0x04) - RESET_REG = const(0x05) - FIRM_REG = const(0xFE) - I2C_REG = const(0xFF) + INPUT_REG = micropython.const(0x00) + OUTPUT_REG = micropython.const(0x01) + POLINV_REG = micropython.const(0x02) + CONFIG_REG = micropython.const(0x03) + FAULT_REG = micropython.const(0x04) + RESET_REG = micropython.const(0x05) + FIRM_REG = micropython.const(0xFE) + I2C_REG = micropython.const(0xFF) """ constant: Microstep values """ - STEP_FULL = const(0x00) - STEP1_2 = const(0x04) - STEP1_4 = const(0x02) - STEP1_8 = const(0x06) - STEP1_16 = const(0x01) - STEP1_32 = const(0x07) + STEP_FULL = micropython.const(0x00) + STEP1_2 = micropython.const(0x04) + STEP1_4 = micropython.const(0x02) + STEP1_8 = micropython.const(0x06) + STEP1_16 = micropython.const(0x01) + STEP1_32 = micropython.const(0x07) def __init__( self, @@ -84,7 +84,7 @@ def __init__( note: The dir pin (X, Y, Z) of the motor. """ - self.i2c = i2c1 + self.i2c = mbus.i2c1 self.addr = address # Check if the devices are connected and accessible @@ -96,12 +96,12 @@ def __init__( self.reset_motor(self.MOTOR_Y, 0) self.reset_motor(self.MOTOR_Z, 0) self.set_microstep(self.STEP_FULL) - self.pwm_x = PWM(step_pin[0], freq=500, duty=50) - self.pwm_y = PWM(step_pin[1], freq=500, duty=50) - self.pwm_z = PWM(step_pin[2], freq=500, duty=50) - self.dir_x = Pin(dir_pin[0], Pin.OUT, value=1) - self.dir_y = Pin(dir_pin[1], Pin.OUT, value=1) - self.dir_z = Pin(dir_pin[2], Pin.OUT, value=1) + self.pwm_x = machine.PWM(step_pin[0], freq=500, duty=50) + self.pwm_y = machine.PWM(step_pin[1], freq=500, duty=50) + self.pwm_z = machine.PWM(step_pin[2], freq=500, duty=50) + self.dir_x = machine.Pin(dir_pin[0], machine.Pin.OUT, value=1) + self.dir_y = machine.Pin(dir_pin[1], machine.Pin.OUT, value=1) + self.dir_z = machine.Pin(dir_pin[2], machine.Pin.OUT, value=1) def reset_motor(self, motor_id, state: bool = False): """ diff --git a/m5stack/libs/module/usb.py b/m5stack/libs/module/usb.py index 1e5a5ad7..763c4ad0 100644 --- a/m5stack/libs/module/usb.py +++ b/m5stack/libs/module/usb.py @@ -1,17 +1,17 @@ # SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD # # SPDX-License-Identifier: MIT -import time -from micropython import const -from .mbus import spi2 + +import micropython +from . import mbus from .usb_hid import * from driver.max3421e import Max3421e -from machine import Pin +import machine -HID_PROTOCOL_NONE = const(0x00) -HID_PROTOCOL_KEYBOARD = const(0x01) -HID_PROTOCOL_MOUSE = const(0x02) +HID_PROTOCOL_NONE = micropython.const(0x00) +HID_PROTOCOL_KEYBOARD = micropython.const(0x01) +HID_PROTOCOL_MOUSE = micropython.const(0x02) DEVADDR = 1 @@ -90,7 +90,9 @@ def keycode_to_ascii(keycode, shift=False): class USBModule(UsbHID): def __init__(self, pin_cs=0, pin_int=14): - super().__init__(spi=spi2, cs=Pin(pin_cs, Pin.OUT), irq=Pin(pin_int)) + super().__init__( + spi=mbus.spi2, cs=machine.Pin(pin_cs, machine.Pin.OUT), irq=machine.Pin(pin_int) + ) self.event = 0 self.event_callback = None # mouse @@ -99,18 +101,18 @@ def __init__(self, pin_cs=0, pin_int=14): self.cursor_y = 0 self.wheel = 0 # mouse event - self.EVENT_NONE = const(0) - self.EVENT_MOVE = const(1) - self.EVENT_LB_DOWN = const(2) - self.EVENT_RB_DOWN = const(3) - self.EVENT_MB_DOWN = const(4) - self.EVENT_LB_UP = const(5) - self.EVENT_RB_UP = const(6) - self.EVENT_MB_UP = const(7) - self.EVENT_LB_DBCLICK = const(8) - self.EVENT_RB_DBCLICK = const(9) - self.EVENT_MB_DBCLICK = const(10) - self.EVENT_SCROLL = const(11) + self.EVENT_NONE = micropython.const(0) + self.EVENT_MOVE = micropython.const(1) + self.EVENT_LB_DOWN = micropython.const(2) + self.EVENT_RB_DOWN = micropython.const(3) + self.EVENT_MB_DOWN = micropython.const(4) + self.EVENT_LB_UP = micropython.const(5) + self.EVENT_RB_UP = micropython.const(6) + self.EVENT_MB_UP = micropython.const(7) + self.EVENT_LB_DBCLICK = micropython.const(8) + self.EVENT_RB_DBCLICK = micropython.const(9) + self.EVENT_MB_DBCLICK = micropython.const(10) + self.EVENT_SCROLL = micropython.const(11) # keyboard event self.modifier = 0 self.input = [] diff --git a/m5stack/main/idf_component.yml b/m5stack/main/idf_component.yml index d495fbcb..c7fdf6b1 100644 --- a/m5stack/main/idf_component.yml +++ b/m5stack/main/idf_component.yml @@ -11,10 +11,8 @@ dependencies: espressif/esp_hosted: rules: - if: "target in [esp32p4]" - version: - 1.4.0 + version: 1.4.0 espressif/esp_wifi_remote: rules: - if: "target in [esp32p4]" - version: - 0.8.5 + version: 0.8.5 From 949c2518aa57137cfd141b72b21d11df4f46a21f Mon Sep 17 00:00:00 2001 From: lbuque Date: Thu, 5 Jun 2025 17:42:42 +0800 Subject: [PATCH 103/322] components/M5Unified: Optimize the refresh speed of lvgl. Signed-off-by: lbuque --- m5stack/CMakeListsDefault.cmake | 1 + m5stack/boards/M5STACK_Tab5/mpconfigboard.h | 4 +- m5stack/cmodules/m5unified/m5unified_gfx.c | 6 + m5stack/cmodules/m5unified/m5unified_lvgl.c | 52 ++++++ m5stack/components/M5Unified/mpy_lvgl.txt | 172 +++++++++++++++++- m5stack/libs/lv_utils/__init__.py | 1 + m5stack/libs/lv_utils/lv_utils.py | 20 +- m5stack/libs/lv_utils/manifest.py | 2 +- .../patches/0004-add-lv-demo-benchmark.patch | 95 ++++++++++ tests/lvgl/benchmark.py | 35 ++++ 10 files changed, 369 insertions(+), 19 deletions(-) create mode 100644 m5stack/patches/0004-add-lv-demo-benchmark.patch create mode 100644 tests/lvgl/benchmark.py diff --git a/m5stack/CMakeListsDefault.cmake b/m5stack/CMakeListsDefault.cmake index 9a8f0628..1d5c9e50 100644 --- a/m5stack/CMakeListsDefault.cmake +++ b/m5stack/CMakeListsDefault.cmake @@ -236,6 +236,7 @@ list(APPEND IDF_COMPONENTS uiflow_utility esp_dmx esp_mm + esp_driver_ppa ) if(CONFIG_IDF_TARGET_ESP32 OR CONFIG_IDF_TARGET_ESP32S2 OR CONFIG_IDF_TARGET_ESP32S3) diff --git a/m5stack/boards/M5STACK_Tab5/mpconfigboard.h b/m5stack/boards/M5STACK_Tab5/mpconfigboard.h index 13ddebf7..cba13ce2 100644 --- a/m5stack/boards/M5STACK_Tab5/mpconfigboard.h +++ b/m5stack/boards/M5STACK_Tab5/mpconfigboard.h @@ -14,4 +14,6 @@ // #ifndef MICROPY_HW_ENABLE_USB_RUNTIME_DEVICE // #define MICROPY_HW_ENABLE_USB_RUNTIME_DEVICE (1) // Support machine.USBDevice -// #endif \ No newline at end of file +// #endif + +#define MICROPY_GC_INITIAL_HEAP_SIZE (128 * 1024) diff --git a/m5stack/cmodules/m5unified/m5unified_gfx.c b/m5stack/cmodules/m5unified/m5unified_gfx.c index 01515704..22220465 100644 --- a/m5stack/cmodules/m5unified/m5unified_gfx.c +++ b/m5stack/cmodules/m5unified/m5unified_gfx.c @@ -88,6 +88,9 @@ MAKE_METHOD_1(gfx, readData32); #if MICROPY_PY_LVGL MAKE_METHOD_0(gfx, lvgl_init); MAKE_METHOD_0(gfx, lvgl_deinit); +#if MICROPY_PY_LVGL_BENCHMARK +MAKE_METHOD_0(gfx, lvgl_benchmark); +#endif #endif #define TABLE_PARTS_GFX_BASE \ @@ -279,6 +282,9 @@ static const mp_rom_map_elem_t gfxdevice_member_table[] = { // lvgl port function { MP_ROM_QSTR(MP_QSTR_lvgl_init), MP_ROM_PTR(&gfx_lvgl_init_obj) }, { MP_ROM_QSTR(MP_QSTR_lvgl_deinit), MP_ROM_PTR(&gfx_lvgl_deinit_obj) }, + #if MICROPY_PY_LVGL_BENCHMARK + { MP_ROM_QSTR(MP_QSTR_lvgl_benchmark), MP_ROM_PTR(&gfx_lvgl_benchmark_obj) }, + #endif { MP_ROM_QSTR(MP_QSTR_lvgl_flush), MP_ROM_PTR(&PTR_OBJ(gfx_lvgl_flush)) }, { MP_ROM_QSTR(MP_QSTR_lvgl_read), MP_ROM_PTR(&PTR_OBJ(gfx_lvgl_touch_read)) }, { MP_ROM_QSTR(MP_QSTR_user_lvgl_flush), MP_ROM_PTR(&PTR_OBJ(user_lvgl_flush)) }, diff --git a/m5stack/cmodules/m5unified/m5unified_lvgl.c b/m5stack/cmodules/m5unified/m5unified_lvgl.c index b401f607..7c3a1afe 100644 --- a/m5stack/cmodules/m5unified/m5unified_lvgl.c +++ b/m5stack/cmodules/m5unified/m5unified_lvgl.c @@ -11,6 +11,7 @@ #include "freertos/FreeRTOS.h" #include "freertos/timers.h" #include "esp_log.h" +#include "lvgl/demos/benchmark/lv_demo_benchmark.h" static TimerHandle_t lvgl_timer = NULL; @@ -33,11 +34,54 @@ void lvgl_deinit() { } } +#if 0 +#include "esp_lcd_types.h" +#include "esp_lcd_mipi_dsi.h" + + +/** + * @brief BSP display configuration structure + * + */ +typedef struct { + int dummy; +} bsp_display_config_t; + + +esp_lcd_panel_handle_t tab5_panel_handle; +esp_lcd_panel_io_handle_t tab5_panel_io_handle; + + +esp_err_t bsp_display_new(const bsp_display_config_t *config, esp_lcd_panel_handle_t *ret_panel, + esp_lcd_panel_io_handle_t *ret_io) { + esp_err_t ret = ESP_OK; + bsp_lcd_handles_t handles; + ret = bsp_display_new_with_handles(config, &handles); + + *ret_panel = handles.panel; + *ret_io = handles.io; + + return ret; +} + +#endif +#include "driver/ppa.h" +ppa_client_handle_t ppa_srm_handle = NULL; + mp_obj_t gfx_lvgl_init(mp_obj_t self) { if (lvgl_timer) { return mp_const_none; } + #if 0 + bsp_display_new(NULL, &tab5_panel_handle, &tab5_panel_io_handle); + #endif + + ppa_client_config_t ppa_srm_config = { + .oper_type = PPA_OPERATION_SRM, + }; + ppa_register_client(&ppa_srm_config, &ppa_srm_handle); + lv_init(); lvgl_timer = xTimerCreate("lvgl_timer", 10, pdTRUE, NULL, vTimerCallback); @@ -51,4 +95,12 @@ mp_obj_t gfx_lvgl_deinit(mp_obj_t self) { lvgl_deinit(); return mp_const_none; } + +#if MICROPY_PY_LVGL_BENCHMARK +mp_obj_t gfx_lvgl_benchmark(mp_obj_t self) { + lv_demo_benchmark(); + return mp_const_none; +} +#endif + #endif diff --git a/m5stack/components/M5Unified/mpy_lvgl.txt b/m5stack/components/M5Unified/mpy_lvgl.txt index 7b10cc51..1c02d8f8 100644 --- a/m5stack/components/M5Unified/mpy_lvgl.txt +++ b/m5stack/components/M5Unified/mpy_lvgl.txt @@ -3,6 +3,120 @@ // #include "lvgl/src/hal/lv_hal_disp.h" #include "mpy_m5gfx.h" #include "esp_log.h" +#include "esp_err.h" +#include "esp_check.h" +#include "driver/ppa.h" +#include "esp_heap_caps.h" +#include "esp_private/esp_cache_private.h" +#include + +#define ALIGN_UP_BY(num, align) (((num) + ((align)-1)) & ~((align)-1)) + +extern ppa_client_handle_t ppa_srm_handle; +static size_t data_cache_line_size = 0; + +static lv_color_t* ppa_buf = NULL; + +IRAM_ATTR static void rotate_copy_pixel(const uint16_t* from, uint16_t* to, uint16_t x_start, uint16_t y_start, + uint16_t x_end, uint16_t y_end, uint16_t w, uint16_t h, uint16_t rotation) +{ + ppa_srm_rotation_angle_t ppa_rotation; + int x_offset = 0, y_offset = 0; + + // Determine rotation settings once and reuse + switch (rotation) { + case 90: + ppa_rotation = PPA_SRM_ROTATION_ANGLE_270; + x_offset = h - y_end - 1; + y_offset = x_start; + break; + case 180: + ppa_rotation = PPA_SRM_ROTATION_ANGLE_180; + x_offset = w - x_end - 1; + y_offset = h - y_end - 1; + break; + case 270: + ppa_rotation = PPA_SRM_ROTATION_ANGLE_90; + x_offset = y_start; + y_offset = w - x_end - 1; + break; + default: + ppa_rotation = PPA_SRM_ROTATION_ANGLE_0; + break; + } + + esp_cache_get_alignment(MALLOC_CAP_SPIRAM, &data_cache_line_size); + + // Fill operation config for PPA rotation, without recalculating each time + ppa_srm_oper_config_t oper_config; + memset(&oper_config, 0, sizeof(oper_config)); + + oper_config.in.buffer = from, + oper_config.in.pic_w = w, + oper_config.in.pic_h = h, + oper_config.in.block_w = x_end - x_start + 1, + oper_config.in.block_h = y_end - y_start + 1, + oper_config.in.block_offset_x = x_start, + oper_config.in.block_offset_y = y_start, + oper_config.in.srm_cm = (LV_COLOR_DEPTH == 24) ? PPA_SRM_COLOR_MODE_RGB888 : PPA_SRM_COLOR_MODE_RGB565, + + oper_config.out.buffer = to, + oper_config.out.buffer_size = ALIGN_UP_BY(sizeof(lv_color_t) * w * h, data_cache_line_size), + oper_config.out.pic_w = (ppa_rotation == PPA_SRM_ROTATION_ANGLE_90 || ppa_rotation == PPA_SRM_ROTATION_ANGLE_270) ? h : w, + oper_config.out.pic_h = (ppa_rotation == PPA_SRM_ROTATION_ANGLE_90 || ppa_rotation == PPA_SRM_ROTATION_ANGLE_270) ? w : h, + oper_config.out.block_offset_x = x_offset, + oper_config.out.block_offset_y = y_offset, + oper_config.out.srm_cm = (LV_COLOR_DEPTH == 24) ? PPA_SRM_COLOR_MODE_RGB888 : PPA_SRM_COLOR_MODE_RGB565, + + oper_config.rotation_angle = ppa_rotation, + oper_config.scale_x = 1.0, + oper_config.scale_y = 1.0, + oper_config.rgb_swap = 0, + oper_config.byte_swap = 0, + oper_config.mode = PPA_TRANS_MODE_BLOCKING, + + ppa_do_scale_rotate_mirror(ppa_srm_handle, &oper_config); +} + + +void lvgl_port_rotate_area(lv_display_t* disp, lv_area_t* area) +{ + lv_display_rotation_t rotation = lv_display_get_rotation(disp); + + int32_t w = lv_area_get_width(area); + int32_t h = lv_area_get_height(area); + + int32_t hres = lv_display_get_horizontal_resolution(disp); + int32_t vres = lv_display_get_vertical_resolution(disp); + if (rotation == LV_DISPLAY_ROTATION_90 || rotation == LV_DISPLAY_ROTATION_270) { + vres = lv_display_get_horizontal_resolution(disp); + hres = lv_display_get_vertical_resolution(disp); + } + + switch (rotation) { + case LV_DISPLAY_ROTATION_0: + return; + case LV_DISPLAY_ROTATION_90: + area->y2 = vres - area->x1 - 1; + area->x1 = area->y1; + area->x2 = area->x1 + h - 1; + area->y1 = area->y2 - w + 1; + break; + case LV_DISPLAY_ROTATION_180: + area->y2 = vres - area->y1 - 1; + area->y1 = area->y2 - h + 1; + area->x2 = hres - area->x1 - 1; + area->x1 = area->x2 - w + 1; + break; + case LV_DISPLAY_ROTATION_270: + area->x1 = hres - area->y2 - 1; + area->y2 = area->x2; + area->x2 = area->x1 + h - 1; + area->y1 = area->y2 - w + 1; + break; + } +} + void gfx_lvgl_flush(lv_display_t *disp_drv, const lv_area_t * area, uint8_t * px_map) { @@ -12,13 +126,61 @@ void gfx_lvgl_flush(lv_display_t *disp_drv, const lv_area_t * area, uint8_t * px return; } + int offsetx1 = area->x1; + int offsetx2 = area->x2; + int offsety1 = area->y1; + int offsety2 = area->y2; + + + + lv_display_rotation_t rotation = lv_display_get_rotation(disp_drv); + + if (rotation > LV_DISPLAY_ROTATION_0) { + if (ppa_buf == NULL) { + size_t buf_size = lv_display_get_draw_buf_size(disp_drv); + ppa_buf = (lv_color_t*)heap_caps_malloc(buf_size, MALLOC_CAP_SPIRAM | MALLOC_CAP_DMA); + if (ppa_buf == NULL) { + ESP_LOGE("gfx_lvgl_flush", "Failed to allocate memory for PPA buffer"); + return; + } + } + int32_t ww = lv_area_get_width(area); + int32_t hh = lv_area_get_height(area); + lv_color_format_t cf = lv_display_get_color_format(disp_drv); + uint32_t w_stride = lv_draw_buf_width_to_stride(ww, cf); + uint32_t h_stride = lv_draw_buf_width_to_stride(hh, cf); + if (rotation == LV_DISPLAY_ROTATION_180) { + lv_draw_sw_rotate(px_map, ppa_buf, hh, ww, h_stride, h_stride, + LV_DISPLAY_ROTATION_180, cf); + } else if (rotation == LV_DISPLAY_ROTATION_90) { + // printf("%ld %ld\n", w_stride, h_stride); + // lv_draw_sw_rotate(px_map, ppa_buf, ww, hh, w_stride, h_stride, + // LV_DISPLAY_ROTATION_90, cf); + // rotate_copy_pixel((uint16_t*)px_map, (uint16_t*)ppa_buf, offsetx1, offsety1, + // offsetx2, offsety2, LV_HOR_RES, LV_VER_RES, 270); + rotate_copy_pixel((uint16_t*)px_map, (uint16_t*)ppa_buf, 0, 0, offsetx2 - offsetx1, + offsety2 - offsety1, offsetx2 - offsetx1 + 1, offsety2 - offsety1 + 1, 270); + } else if (rotation == LV_DISPLAY_ROTATION_270) { + lv_draw_sw_rotate(px_map, ppa_buf, ww, hh, w_stride, h_stride, + LV_DISPLAY_ROTATION_270, cf); + } + px_map = (uint8_t*)ppa_buf; + lvgl_port_rotate_area(disp_drv, (lv_area_t*)area); + offsetx1 = area->x1; + offsetx2 = area->x2; + offsety1 = area->y1; + offsety2 = area->y2; + } + int w = (area->x2 - area->x1 + 1); int h = (area->y2 - area->y1 + 1); - lvgl_gfx->startWrite(); - lvgl_gfx->setAddrWindow(area->x1, area->y1, w, h); - lvgl_gfx->writePixels((lgfx::rgb565_t *)px_map, w * h); - lvgl_gfx->endWrite(); + // lvgl_gfx->startWrite(); + // lvgl_gfx->setAddrWindow(area->x1, area->y1, w, h); + // lvgl_gfx->writePixels((lgfx::rgb565_t *)px_map, w * h); + // lvgl_gfx->endWrite(); + + lvgl_gfx->pushImage(area->x1, area->y1, w, h, (lgfx::rgb565_t *)px_map); lv_display_flush_ready(disp_drv); } @@ -51,4 +213,6 @@ void user_lvgl_flush(lv_disp_t *disp_drv, const lv_area_t *area, uint8_t *px_map user_panel.endWrite(); lv_display_flush_ready(disp_drv); } + + #endif diff --git a/m5stack/libs/lv_utils/__init__.py b/m5stack/libs/lv_utils/__init__.py index 52d72965..f183d808 100644 --- a/m5stack/libs/lv_utils/__init__.py +++ b/m5stack/libs/lv_utils/__init__.py @@ -4,6 +4,7 @@ import lvgl as lv import _lv_utils +from .lv_utils import event_loop def fs_register(fs_drv, letter, cache_size=500): diff --git a/m5stack/libs/lv_utils/lv_utils.py b/m5stack/libs/lv_utils/lv_utils.py index a8d51e1f..0977eb10 100644 --- a/m5stack/libs/lv_utils/lv_utils.py +++ b/m5stack/libs/lv_utils/lv_utils.py @@ -36,15 +36,7 @@ # Try standard machine.Timer, or custom timer from lv_timer, if available -try: - from machine import Timer -except: - try: - from lv_timer import Timer - except: - if sys.platform != "darwin": - raise RuntimeError("Missing machine.Timer implementation!") - Timer = False +import machine # Try to determine default timer id @@ -101,9 +93,11 @@ def __init__( self.refresh_task = asyncio.create_task(self.async_refresh()) self.timer_task = asyncio.create_task(self.async_timer()) else: - if Timer: - self.timer = Timer(timer_id) - self.timer.init(mode=Timer.PERIODIC, period=self.delay, callback=self.timer_cb) + if machine.Timer: + self.timer = machine.Timer(timer_id) + self.timer.init( + mode=machine.Timer.PERIODIC, period=self.delay, callback=self.timer_cb + ) self.task_handler_ref = self.task_handler # Allocation occurs here self.max_scheduled = max_scheduled self.scheduled = 0 @@ -118,7 +112,7 @@ def deinit(self): self.refresh_task.cancel() self.timer_task.cancel() else: - if Timer: + if machine.Timer: self.timer.deinit() event_loop._current_instance = None diff --git a/m5stack/libs/lv_utils/manifest.py b/m5stack/libs/lv_utils/manifest.py index 9dba076c..a6f99751 100644 --- a/m5stack/libs/lv_utils/manifest.py +++ b/m5stack/libs/lv_utils/manifest.py @@ -4,7 +4,7 @@ package( "lv_utils", - ("__init__.py",), + ("__init__.py", "lv_utils.py"), base_path="..", opt=0, ) diff --git a/m5stack/patches/0004-add-lv-demo-benchmark.patch b/m5stack/patches/0004-add-lv-demo-benchmark.patch new file mode 100644 index 00000000..ff829667 --- /dev/null +++ b/m5stack/patches/0004-add-lv-demo-benchmark.patch @@ -0,0 +1,95 @@ +Index: lv_binding_micropython/micropython.cmake +=================================================================== +--- lv_binding_micropython.orig/micropython.cmake ++++ lv_binding_micropython/micropython.cmake +@@ -34,10 +34,12 @@ all_lv_bindings() + # target_link_libraries(usermod INTERFACE usermod_lvgl) + + file(GLOB_RECURSE SOURCES ${CMAKE_CURRENT_LIST_DIR}/lvgl/src/*.c) ++file(GLOB_RECURSE DEMO_WIDGETS_SOURCES ${CMAKE_CURRENT_LIST_DIR}/lvgl/demos/widgets/*.c) ++file(GLOB_RECURSE DEMO_BENCHMARK_SOURCES ${CMAKE_CURRENT_LIST_DIR}/lvgl/demos/benchmark/*.c) + + add_library(lvgl_interface INTERFACE) + +-target_sources(lvgl_interface INTERFACE ${SOURCES}) ++target_sources(lvgl_interface INTERFACE ${SOURCES} ${DEMO_WIDGETS_SOURCES} ${DEMO_BENCHMARK_SOURCES}) + target_compile_options(lvgl_interface INTERFACE ${LV_CFLAGS}) + + # # lvgl bindings target (the mpy module) +Index: lv_binding_micropython/lv_conf.h +=================================================================== +--- lv_binding_micropython.orig/lv_conf.h ++++ lv_binding_micropython/lv_conf.h +@@ -40,7 +40,7 @@ + * - LV_STDLIB_RTTHREAD: RT-Thread implementation + * - LV_STDLIB_CUSTOM: Implement the functions externally + */ +-#define LV_USE_STDLIB_MALLOC LV_STDLIB_MICROPYTHON ++#define LV_USE_STDLIB_MALLOC LV_STDLIB_BUILTIN + #define LV_USE_STDLIB_STRING LV_STDLIB_BUILTIN + #define LV_USE_STDLIB_SPRINTF LV_STDLIB_BUILTIN + +@@ -53,7 +53,7 @@ + + #if LV_USE_STDLIB_MALLOC == LV_STDLIB_BUILTIN + /*Size of the memory available for `lv_malloc()` in bytes (>= 2kB)*/ +- #define LV_MEM_SIZE (64 * 1024U) /*[bytes]*/ ++ #define LV_MEM_SIZE (128 * 1024U) /*[bytes]*/ + + /*Size of the memory expand for `lv_malloc()` in bytes*/ + #define LV_MEM_POOL_EXPAND_SIZE 0 +@@ -427,10 +427,10 @@ extern void mp_lv_deinit_gc(); + #define LV_FONT_MONTSERRAT_14 1 + #define LV_FONT_MONTSERRAT_16 1 + #define LV_FONT_MONTSERRAT_18 0 +-#define LV_FONT_MONTSERRAT_20 0 ++#define LV_FONT_MONTSERRAT_20 1 + #define LV_FONT_MONTSERRAT_22 0 + #define LV_FONT_MONTSERRAT_24 1 +-#define LV_FONT_MONTSERRAT_26 0 ++#define LV_FONT_MONTSERRAT_26 1 + #define LV_FONT_MONTSERRAT_28 0 + #define LV_FONT_MONTSERRAT_30 0 + #define LV_FONT_MONTSERRAT_32 0 +@@ -799,14 +799,14 @@ extern void mp_lv_deinit_gc(); + #define LV_USE_SNAPSHOT 1 + + /*1: Enable system monitor component*/ +-#define LV_USE_SYSMON 0 ++#define LV_USE_SYSMON 1 + #if LV_USE_SYSMON + /*Get the idle percentage. E.g. uint32_t my_get_idle(void);*/ + #define LV_SYSMON_GET_IDLE lv_timer_get_idle + + /*1: Show CPU usage and FPS count + * Requires `LV_USE_SYSMON = 1`*/ +- #define LV_USE_PERF_MONITOR 0 ++ #define LV_USE_PERF_MONITOR 1 + #if LV_USE_PERF_MONITOR + #define LV_USE_PERF_MONITOR_POS LV_ALIGN_BOTTOM_RIGHT + +@@ -817,7 +817,7 @@ extern void mp_lv_deinit_gc(); + /*1: Show the used memory and the memory fragmentation + * Requires `LV_USE_STDLIB_MALLOC = LV_STDLIB_BUILTIN` + * Requires `LV_USE_SYSMON = 1`*/ +- #define LV_USE_MEM_MONITOR 0 ++ #define LV_USE_MEM_MONITOR 1 + #if LV_USE_MEM_MONITOR + #define LV_USE_MEM_MONITOR_POS LV_ALIGN_BOTTOM_LEFT + #endif +@@ -1010,13 +1010,13 @@ extern void mp_lv_deinit_gc(); + ====================*/ + + /*Show some widget. It might be required to increase `LV_MEM_SIZE` */ +-#define LV_USE_DEMO_WIDGETS 0 ++#define LV_USE_DEMO_WIDGETS 1 + + /*Demonstrate the usage of encoder and keyboard*/ + #define LV_USE_DEMO_KEYPAD_AND_ENCODER 0 + + /*Benchmark your system*/ +-#define LV_USE_DEMO_BENCHMARK 0 ++#define LV_USE_DEMO_BENCHMARK 1 + + /*Render test for each primitives. Requires at least 480x272 display*/ + #define LV_USE_DEMO_RENDER 0 diff --git a/tests/lvgl/benchmark.py b/tests/lvgl/benchmark.py new file mode 100644 index 00000000..90e9a057 --- /dev/null +++ b/tests/lvgl/benchmark.py @@ -0,0 +1,35 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import sys +import M5 +import lvgl as lv +import lv_utils + +M5.begin() + +# lvgl init +M5.Lcd.lvgl_init() + +# built-in display +disp_buf0 = lv.draw_buf_create(M5.getDisplay(0).width(), 10, lv.COLOR_FORMAT.RGB565, 0) +disp_buf1 = lv.draw_buf_create(M5.getDisplay(0).width(), 10, lv.COLOR_FORMAT.RGB565, 0) + +disp_drv = lv.display_create(M5.getDisplay(0).width(), M5.getDisplay(0).height()) +disp_drv.set_color_format(lv.COLOR_FORMAT.RGB565) + +disp_drv.set_draw_buffers(disp_buf0, disp_buf1) +disp_drv.set_flush_cb(M5.Lcd.lvgl_flush) +disp_drv.set_user_data({"display": M5.Lcd}) +disp_drv.set_render_mode(lv.DISPLAY_RENDER_MODE.PARTIAL) +disp_drv.set_rotation(lv.DISPLAY_ROTATION._270) + +# touch driver init +indev_drv = lv.indev_create() +indev_drv.set_type(lv.INDEV_TYPE.POINTER) +# indev_drv.set_display(lv.display_get_default()) +indev_drv.set_display(disp_drv) +indev_drv.set_read_cb(M5.Lcd.lvgl_read) + +M5.Lcd.lv_demo_benchmark() From 861d69d803a72c7aa1a42227e5de4fc98f32acce Mon Sep 17 00:00:00 2001 From: lbuque Date: Fri, 13 Jun 2025 14:25:07 +0800 Subject: [PATCH 104/322] gitlab-ci.yml: Clear patches before running code inspection. Signed-off-by: lbuque --- .gitlab-ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 078952b5..62923b74 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -21,6 +21,7 @@ code-format: before_script: - export PATH="$HOME/.local/bin:$PATH" script: + - make -C m5stack submodules - source tools/ci.sh && ci_code_formatting_setup - source tools/ci.sh && ci_code_formatting_run - git diff --exit-code From a0acbeff23216e027abb219ead60878271babac0 Mon Sep 17 00:00:00 2001 From: Forairaaaaa Date: Fri, 13 Jun 2025 12:12:43 +0800 Subject: [PATCH 105/322] modules/startup: Add tab5 startup. Signed-off-by: Forairaaaaa --- m5stack/boards/M5STACK_Tab5/manifest.py | 2 +- .../boards/M5STACK_Tab5/mpconfigboard.cmake | 10 +- m5stack/modules/startup/__init__.py | 6 + m5stack/modules/startup/manifest_tab5.py | 35 +++ m5stack/modules/startup/tab5/__init__.py | 52 ++++ m5stack/modules/startup/tab5/hal_tab5.py | 242 +++++++++++++++ .../modules/startup/tab5/launcher/__init__.py | 6 + .../startup/tab5/launcher/apps/__init__.py | 12 + .../modules/startup/tab5/launcher/apps/app.py | 118 ++++++++ .../tab5/launcher/apps/app_app_list.py | 131 ++++++++ .../startup/tab5/launcher/apps/app_dummy.py | 21 ++ .../startup/tab5/launcher/apps/app_ezdata.py | 279 +++++++++++++++++ .../tab5/launcher/apps/app_i2c_scan.py | 118 ++++++++ .../startup/tab5/launcher/apps/app_uart.py | 170 +++++++++++ .../startup/tab5/launcher/apps/app_uiflow.py | 0 .../startup/tab5/launcher/apps/app_wifi.py | 109 +++++++ .../tab5/launcher/apps/app_wifi_scan.py | 136 +++++++++ .../startup/tab5/launcher/common/__init__.py | 3 + .../startup/tab5/launcher/common/common.py | 6 + .../startup/tab5/launcher/common/ezdata.py | 253 ++++++++++++++++ .../startup/tab5/launcher/common/indicator.py | 24 ++ .../tab5/launcher/components/__init__.py | 7 + .../tab5/launcher/components/app_dock.py | 50 +++ .../tab5/launcher/components/ezdata_dock.py | 134 ++++++++ .../tab5/launcher/components/status_bar.py | 285 ++++++++++++++++++ m5stack/modules/startup/tab5/launcher/hal.py | 143 +++++++++ .../modules/startup/tab5/launcher/launcher.py | 105 +++++++ 27 files changed, 2455 insertions(+), 2 deletions(-) create mode 100644 m5stack/modules/startup/manifest_tab5.py create mode 100644 m5stack/modules/startup/tab5/__init__.py create mode 100644 m5stack/modules/startup/tab5/hal_tab5.py create mode 100644 m5stack/modules/startup/tab5/launcher/__init__.py create mode 100644 m5stack/modules/startup/tab5/launcher/apps/__init__.py create mode 100644 m5stack/modules/startup/tab5/launcher/apps/app.py create mode 100644 m5stack/modules/startup/tab5/launcher/apps/app_app_list.py create mode 100644 m5stack/modules/startup/tab5/launcher/apps/app_dummy.py create mode 100644 m5stack/modules/startup/tab5/launcher/apps/app_ezdata.py create mode 100644 m5stack/modules/startup/tab5/launcher/apps/app_i2c_scan.py create mode 100644 m5stack/modules/startup/tab5/launcher/apps/app_uart.py create mode 100644 m5stack/modules/startup/tab5/launcher/apps/app_uiflow.py create mode 100644 m5stack/modules/startup/tab5/launcher/apps/app_wifi.py create mode 100644 m5stack/modules/startup/tab5/launcher/apps/app_wifi_scan.py create mode 100644 m5stack/modules/startup/tab5/launcher/common/__init__.py create mode 100644 m5stack/modules/startup/tab5/launcher/common/common.py create mode 100644 m5stack/modules/startup/tab5/launcher/common/ezdata.py create mode 100644 m5stack/modules/startup/tab5/launcher/common/indicator.py create mode 100644 m5stack/modules/startup/tab5/launcher/components/__init__.py create mode 100644 m5stack/modules/startup/tab5/launcher/components/app_dock.py create mode 100644 m5stack/modules/startup/tab5/launcher/components/ezdata_dock.py create mode 100644 m5stack/modules/startup/tab5/launcher/components/status_bar.py create mode 100644 m5stack/modules/startup/tab5/launcher/hal.py create mode 100644 m5stack/modules/startup/tab5/launcher/launcher.py diff --git a/m5stack/boards/M5STACK_Tab5/manifest.py b/m5stack/boards/M5STACK_Tab5/manifest.py index 275c55bd..ed273839 100644 --- a/m5stack/boards/M5STACK_Tab5/manifest.py +++ b/m5stack/boards/M5STACK_Tab5/manifest.py @@ -2,4 +2,4 @@ # # SPDX-License-Identifier: MIT -include("$(MPY_DIR)/../m5stack/modules/startup/manifest_cores3.py") +include("$(MPY_DIR)/../m5stack/modules/startup/manifest_tab5.py") diff --git a/m5stack/boards/M5STACK_Tab5/mpconfigboard.cmake b/m5stack/boards/M5STACK_Tab5/mpconfigboard.cmake index f84dbc55..00c4ef5a 100644 --- a/m5stack/boards/M5STACK_Tab5/mpconfigboard.cmake +++ b/m5stack/boards/M5STACK_Tab5/mpconfigboard.cmake @@ -23,7 +23,15 @@ set(SDKCONFIG_DEFAULTS ) # If not enable LVGL, ignore this... -set(LV_CFLAGS -DLV_COLOR_DEPTH=16 -DLV_COLOR_16_SWAP=0) +set(LV_CFLAGS + -DLV_COLOR_DEPTH=16 + -DLV_COLOR_16_SWAP=0 + -DLV_FONT_MONTSERRAT_24=1 + -DLV_FONT_MONTSERRAT_22=1 + -DLV_FONT_MONTSERRAT_20=1 + -DLV_FONT_MONTSERRAT_18=1 + -DLV_FONT_MONTSERRAT_14=1 +) if(NOT MICROPY_FROZEN_MANIFEST) set(MICROPY_FROZEN_MANIFEST ${CMAKE_SOURCE_DIR}/boards/manifest.py) diff --git a/m5stack/modules/startup/__init__.py b/m5stack/modules/startup/__init__.py index c4755c05..8a91e18d 100644 --- a/m5stack/modules/startup/__init__.py +++ b/m5stack/modules/startup/__init__.py @@ -242,6 +242,12 @@ def startup(boot_opt, timeout: int = 60) -> None: station = StampPLC_Startup() station.startup(ssid, pswd, timeout) + elif board_id == M5.BOARD.M5Tab5: + from .tab5 import Tab5_Startup + + tab5 = Tab5_Startup() + tab5.startup(ssid, pswd, timeout) + # Only connect to network, not show any menu elif boot_opt is BOOT_OPT_NETWORK: startup = Startup() diff --git a/m5stack/modules/startup/manifest_tab5.py b/m5stack/modules/startup/manifest_tab5.py new file mode 100644 index 00000000..10b4f1a6 --- /dev/null +++ b/m5stack/modules/startup/manifest_tab5.py @@ -0,0 +1,35 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +package( + "startup", + ( + "__init__.py", + "tab5/launcher/common/ezdata.py", + "tab5/launcher/common/common.py", + "tab5/launcher/common/__init__.py", + "tab5/launcher/common/indicator.py", + "tab5/launcher/apps/app.py", + "tab5/launcher/apps/app_i2c_scan.py", + "tab5/launcher/apps/app_app_list.py", + "tab5/launcher/apps/app_wifi_scan.py", + "tab5/launcher/apps/app_dummy.py", + "tab5/launcher/apps/app_uiflow.py", + "tab5/launcher/apps/__init__.py", + "tab5/launcher/apps/app_ezdata.py", + "tab5/launcher/apps/app_wifi.py", + "tab5/launcher/apps/app_uart.py", + "tab5/launcher/hal.py", + "tab5/launcher/components/ezdata_dock.py", + "tab5/launcher/components/status_bar.py", + "tab5/launcher/components/__init__.py", + "tab5/launcher/components/app_dock.py", + "tab5/launcher/launcher.py", + "tab5/launcher/__init__.py", + "tab5/hal_tab5.py", + "tab5/__init__.py", + ), + base_path="..", + opt=3, +) diff --git a/m5stack/modules/startup/tab5/__init__.py b/m5stack/modules/startup/tab5/__init__.py new file mode 100644 index 00000000..2a2479ed --- /dev/null +++ b/m5stack/modules/startup/tab5/__init__.py @@ -0,0 +1,52 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import startup +import M5 +import time +import lvgl as lv +import lv_utils +from .launcher import Launcher, set_hal +from .hal_tab5 import HALTab5 + + +class Tab5_Startup: + def __init__(self) -> None: + self._wlan = startup.Startup() + + def startup(self, ssid: str, pswd: str, timeout: int = 60) -> None: + self._wlan.connect_network(ssid, pswd) + + self._init_lvgl() + + set_hal(HALTab5(self._wlan)) + + self.launcher = Launcher() + + def _init_lvgl(self): + M5.Lcd.lvgl_init() + + disp_buf0 = lv.draw_buf_create( + M5.getDisplay(0).width(), M5.getDisplay(0).height(), lv.COLOR_FORMAT.RGB565, 0 + ) + disp_buf1 = lv.draw_buf_create( + M5.getDisplay(0).width(), M5.getDisplay(0).height(), lv.COLOR_FORMAT.RGB565, 0 + ) + + disp_drv = lv.display_create(M5.getDisplay(0).width(), M5.getDisplay(0).height()) + disp_drv.set_color_format(lv.COLOR_FORMAT.RGB565) + + disp_drv.set_draw_buffers(disp_buf0, disp_buf1) + disp_drv.set_flush_cb(M5.Lcd.lvgl_flush) + disp_drv.set_user_data({"display": M5.Lcd}) + disp_drv.set_render_mode(lv.DISPLAY_RENDER_MODE.PARTIAL) + disp_drv.set_rotation(lv.DISPLAY_ROTATION._90) + + indev_drv = lv.indev_create() + indev_drv.set_type(lv.INDEV_TYPE.POINTER) + indev_drv.set_display(disp_drv) + indev_drv.set_read_cb(M5.Lcd.lvgl_read) + + fs_drv = lv.fs_drv_t() + lv_utils.fs_register(fs_drv, "S", 500) diff --git a/m5stack/modules/startup/tab5/hal_tab5.py b/m5stack/modules/startup/tab5/hal_tab5.py new file mode 100644 index 00000000..f1a9776b --- /dev/null +++ b/m5stack/modules/startup/tab5/hal_tab5.py @@ -0,0 +1,242 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +from .launcher import * +from machine import Pin, I2C, UART, reset, unique_id, SoftI2C +import network +import esp32 +import time +import sys +import os +import M5 + +try: + import M5Things + + _HAS_SERVER = True +except ImportError: + _HAS_SERVER = False + + +class HALTab5(HALBase): + def __init__(self, wifi): + super().__init__() + + self._wifi = wifi + + # Default values + self.set_volume(self._volume) + self.set_backlight(self._backlight) + self.set_charge_mode(self._charge_mode) + + self._load_network_config_from_nvs() + + self._pin_485_de = None + + def _load_network_config_from_nvs(self) -> NetworkConfig: + nvs = esp32.NVS("uiflow") + self._network_config = NetworkConfig( + nvs.get_str("ssid0"), nvs.get_str("pswd0"), nvs.get_str("server") + ) + + def _save_network_config_to_nvs(self): + nvs = esp32.NVS("uiflow") + nvs.set_str("ssid0", self._network_config.ssid) + nvs.set_str("pswd0", self._network_config.password) + nvs.set_str("server", self._network_config.server) + nvs.commit() + + def get_asset_path(self, asset_path: str) -> str: + return "S:" + "/system/tab5/" + asset_path + + def play_click_sfx(self): + M5.Speaker.playWavFile("/system/common/wav/click.wav") + + def power_off(self): + M5.Power.powerOff() + + def sleep(self): + M5.Power.deepSleep() + + def set_volume(self, volume: int): + self._volume = volume + # 0~100 to 0~255 + M5.Speaker.setVolume(int(volume * 255 / 100)) + + def set_backlight(self, backlight: int): + self._backlight = backlight + # 0~100 to 0~255 + M5.Lcd.setBrightness(int(backlight * 255 / 100)) + + def set_charge_mode(self, mode: ChargeMode): + self._charge_mode = mode + + if mode == ChargeMode.DISABLE: + M5.Power.setBatteryCharge(False) + return + + M5.Power.setBatteryCharge(True) + if mode == ChargeMode.NORMAL: + M5.Power.setChargeCurrent(500) + elif mode == ChargeMode.QC: + M5.Power.setChargeCurrent(1000) + + def get_battery_level(self) -> int: + """TODO""" + return 0 + + def get_output_current(self) -> float: + """TODO""" + return 0.0 + + def is_charging(self) -> bool: + """TODO""" + return False + + def get_network_status(self) -> NetworkStatus: + status = self._wifi.connect_status() + if status is network.STAT_GOT_IP: + rssi = self._wifi.get_rssi() + if rssi <= -80: + return NetworkStatus.RSSI_WORSE + elif rssi <= -60: + return NetworkStatus.RSSI_MID + else: + return NetworkStatus.RSSI_GOOD + else: + return NetworkStatus.DISCONNECTED + + def get_cloud_status(self) -> CloudStatus: + if _HAS_SERVER is True: + status = M5Things.status() + return { + -2: CloudStatus.DISCONNECTED, + -1: CloudStatus.DISCONNECTED, + 0: CloudStatus.INIT, + 1: CloudStatus.INIT, + 2: CloudStatus.CONNECTED, + 3: CloudStatus.DISCONNECTED, + }[status] + else: + return CloudStatus.DISCONNECTED + + def get_network_config(self) -> NetworkConfig: + self._load_network_config_from_nvs() + return NetworkConfig( + ssid=self._network_config.ssid, + password=self._network_config.password, + server=self._network_config.server, + ) + + def set_network_config(self, config: NetworkConfig): + is_changed = False + if self._network_config.ssid != config.ssid: + self._network_config.ssid = config.ssid + is_changed = True + if self._network_config.password != config.password: + self._network_config.password = config.password + is_changed = True + if self._network_config.server != config.server: + self._network_config.server = config.server + is_changed = True + + if is_changed: + self._save_network_config_to_nvs() + + # Reconnect + self._wifi.wlan.disconnect() + self._wifi.wlan.active(False) + self._wifi.wlan.active(True) + self._wifi.connect_network(self._network_config.ssid, self._network_config.password) + + def get_py_app_list(self) -> list[str]: + py_apps = [] + for file in os.listdir("apps"): + if file.endswith(".py"): + py_apps.append(file) + return py_apps + + def run_py_app(self, app_name: str, once: bool): + if not app_name: + print("invalid py app name") + + if once: + execfile("/".join(["apps", app_name]), {"__name__": "__main__"}) # noqa: F821 + raise KeyboardInterrupt + else: + nvs = esp32.NVS("uiflow") + nvs.set_u8("boot_option", 2) + nvs.commit() + with open("apps/" + app_name, "rb") as f_src, open("main.py", "wb") as f_dst: + while True: + chunk = f_src.read(1024) + if not chunk: + break + f_dst.write(chunk) + time.sleep(1) + reset() + + async def scan_wifi(self): + return self._wifi.wlan.scan() + + def i2c_init(self): + # self._i2c_0 = I2C(0, scl=Pin(32), sda=Pin(31), + # freq=400000, timeout=100) + # self._i2c_1 = I2C(1, scl=Pin(54), sda=Pin(53), + # freq=400000, timeout=100) + self._i2c_0 = SoftI2C(scl=Pin(32), sda=Pin(31), freq=400000, timeout=100) + self._i2c_1 = SoftI2C(scl=Pin(54), sda=Pin(53), freq=400000, timeout=100) + + def i2c_deinit(self): + self._i2c_0 = None + self._i2c_1 = None + + def i2c_scan(self, port: int) -> list[int]: + if port == 0: + return self._i2c_0.scan() + return self._i2c_1.scan() + + def get_mac(self) -> bytes: + return unique_id() + + def uart_init(self, baudrate: int, tx_pin: int, rx_pin: int): + self._uart = UART(1, baudrate, tx=Pin(tx_pin), rx=Pin(rx_pin)) + + # RS485 + if tx_pin == 20: + self._pin_485_de = Pin(34, Pin.OUT) + self._pin_485_de.off() + + def uart_deinit(self): + self._uart = None + self._pin_485_de = None + + def uart_write(self, msg: str): + if self._pin_485_de: + self._pin_485_de.on() + + self._uart.write(msg + "\n") + + if self._pin_485_de: + time.sleep_ms(1) + self._pin_485_de.off() + + def uart_read(self) -> bytes | None: + return self._uart.read() + + def store_ezdata_user_token(self, user_token: str): + nvs = esp32.NVS("uiflow") + nvs.set_str("ustoken", user_token) + + def get_ezdata_user_token(self) -> str: + nvs = esp32.NVS("uiflow") + try: + token = nvs.get_str("ustoken") + return token + except: + return "" + + def reset_ezdata_user_token(self): + nvs = esp32.NVS("uiflow") + nvs.set_str("ustoken", "") diff --git a/m5stack/modules/startup/tab5/launcher/__init__.py b/m5stack/modules/startup/tab5/launcher/__init__.py new file mode 100644 index 00000000..69aa9964 --- /dev/null +++ b/m5stack/modules/startup/tab5/launcher/__init__.py @@ -0,0 +1,6 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +from .launcher import Launcher +from .hal import * diff --git a/m5stack/modules/startup/tab5/launcher/apps/__init__.py b/m5stack/modules/startup/tab5/launcher/apps/__init__.py new file mode 100644 index 00000000..06fb7e67 --- /dev/null +++ b/m5stack/modules/startup/tab5/launcher/apps/__init__.py @@ -0,0 +1,12 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +from .app import AppManager, AppBase +from .app_dummy import AppDummy +from .app_wifi import AppWifi +from .app_app_list import AppAppList +from .app_wifi_scan import AppWifiScan +from .app_i2c_scan import AppI2cScan +from .app_uart import AppUart +from .app_ezdata import AppEzdata, AppEzdataSettings diff --git a/m5stack/modules/startup/tab5/launcher/apps/app.py b/m5stack/modules/startup/tab5/launcher/apps/app.py new file mode 100644 index 00000000..facfc80b --- /dev/null +++ b/m5stack/modules/startup/tab5/launcher/apps/app.py @@ -0,0 +1,118 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +from collections import deque +import lvgl as lv +import asyncio +import gc + + +class AppBase: + """Base class for all apps""" + + def __init__(self, app_name: str, app_panel: lv.obj): + self._task = None + self._app_name = app_name + self._app_panel = app_panel + + async def main(self): + """Override this to implement app logic""" + raise NotImplementedError("App must implement main()") + + def on_cleanup(self): + """Override this to clean up""" + pass + + def get_app_name(self) -> str: + return self._app_name + + def get_app_panel(self) -> lv.obj: + return self._app_panel + + async def on_open(self): + # print(self._app_name, "on open") + self._task = asyncio.create_task(self.main()) + + async def on_close(self): + # print(self._app_name, "on close") + if self._task: + self._task.cancel() + try: + await self._task + except asyncio.CancelledError: + pass + self._task = None + self.on_cleanup() + + +class AppManager: + """Single App Manager""" + + _event_queue = deque([], 1) + _installed_apps = {} + _current_app = None + _current_app_name = None + _app_panel = None + + @staticmethod + def set_app_panel(app_panel: lv.obj): + AppManager._app_panel = app_panel + + @staticmethod + def install_app(app_name: str, app_class): + AppManager._installed_apps[app_name] = app_class + + @staticmethod + def uninstall_app(app_name: str): + if app_name == AppManager._current_app_name: + AppManager._event_queue.append({"close_app": app_name}) + AppManager._installed_apps.pop(app_name, None) + + @staticmethod + def open_app(app_name: str): + if app_name == AppManager._current_app_name: + return + AppManager._event_queue.append({"open_app": app_name}) + + @staticmethod + def close_app(app_name: str): + AppManager._event_queue.append({"close_app": AppManager._current_app_name}) + + @staticmethod + async def update(): + # Consume events + while AppManager._event_queue: + event = AppManager._event_queue.popleft() + # Handle open app + if "open_app" in event: + app_name = event["open_app"] + # Check installed list + if app_name in AppManager._installed_apps: + # Close current app + if AppManager._current_app: + await AppManager._current_app.on_close() + AppManager._cleanup() + # Create new app + AppManager._current_app = AppManager._installed_apps[app_name]( + app_name, AppManager._app_panel + ) + await AppManager._current_app.on_open() + # Update current app name + AppManager._current_app_name = app_name + else: + print("open app failed: app not found:", app_name) + # Handle close app + elif "close_app" in event: + if AppManager._current_app: + await AppManager._current_app.on_close() + AppManager._current_app = None + AppManager._current_app_name = None + AppManager._cleanup() + + @staticmethod + def _cleanup(): + # Clean up children widgets + if AppManager._app_panel: + AppManager._app_panel.clean() + gc.collect() diff --git a/m5stack/modules/startup/tab5/launcher/apps/app_app_list.py b/m5stack/modules/startup/tab5/launcher/apps/app_app_list.py new file mode 100644 index 00000000..eb4da21d --- /dev/null +++ b/m5stack/modules/startup/tab5/launcher/apps/app_app_list.py @@ -0,0 +1,131 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +from .app import AppBase +from ..hal import * +import lvgl as lv +import asyncio + + +class PyAppOption: + def __init__(self, parent: lv.obj, py_app_name: str, pos_y: int, on_clicked): + self._py_app_name = py_app_name + self._on_clicked = on_clicked + + self._btn = lv.button(parent) + self._btn.set_size(740, 54) + self._btn.align(lv.ALIGN.TOP_MID, 0, pos_y) + self._btn.set_style_bg_color(lv.color_hex(0xEDEDED), lv.PART.MAIN) + self._btn.set_style_radius(10, lv.PART.MAIN) + self._btn.set_style_shadow_width(0, lv.PART.MAIN) + self._btn.set_style_border_color(lv.color_hex(0x119FE6), lv.PART.MAIN) + self._btn.set_style_border_width(3, lv.PART.MAIN) + self._btn.add_event_cb(self._on_btn_clicked, lv.EVENT.CLICKED, None) + + self._label = lv.label(self._btn) + self._label.set_text(py_app_name) + self._label.align(lv.ALIGN.LEFT_MID, 0, 0) + self._label.set_style_text_font(lv.font_montserrat_24, lv.PART.MAIN) + self._label.set_style_text_color(lv.color_hex(0x4A4A4A), lv.PART.MAIN) + + self.reset_border() + + def reset_border(self): + self._btn.set_style_border_opa(lv.OPA.TRANSP, lv.PART.MAIN) + + def get_py_app_name(self) -> str: + return self._py_app_name + + def _on_btn_clicked(self, e: lv.event_t): + get_hal().play_click_sfx() + if self._on_clicked: + self._on_clicked(self._py_app_name) + self._btn.set_style_border_opa(lv.OPA.COVER, lv.PART.MAIN) + + def cleanup(self): + self._label.delete() + self._label = None + self._btn.delete() + self._btn = None + + +class AppAppList(AppBase): + async def main(self): + self._selected_option = None + + self._btn_run_once = lv.button(self.get_app_panel()) + self._btn_run_once.set_size(200, 150) + self._btn_run_once.align(lv.ALIGN.TOP_RIGHT, -45, 100) + self._btn_run_once.set_style_bg_color(lv.color_hex(0x119FE6), lv.PART.MAIN) + self._btn_run_once.set_style_radius(10, lv.PART.MAIN) + self._btn_run_once.set_style_shadow_width(0, lv.PART.MAIN) + self._btn_run_once.add_event_cb(self._on_run_once_clicked, lv.EVENT.CLICKED, None) + + self._label_run_once = lv.label(self._btn_run_once) + self._label_run_once.set_text("RUN ONCE") + self._label_run_once.set_align(lv.ALIGN.CENTER) + self._label_run_once.set_style_text_font(lv.font_montserrat_24, lv.PART.MAIN) + + self._btn_run_always = lv.button(self.get_app_panel()) + self._btn_run_always.set_size(200, 150) + self._btn_run_always.align(lv.ALIGN.TOP_RIGHT, -45, 310) + self._btn_run_always.set_style_bg_color(lv.color_hex(0xF1677B), lv.PART.MAIN) + self._btn_run_always.set_style_radius(10, lv.PART.MAIN) + self._btn_run_always.set_style_shadow_width(0, lv.PART.MAIN) + self._btn_run_always.add_event_cb(self._on_run_always_clicked, lv.EVENT.CLICKED, None) + + self._label_run_always = lv.label(self._btn_run_always) + self._label_run_always.set_text("RUN ALWAYS") + self._label_run_always.set_align(lv.ALIGN.CENTER) + self._label_run_always.set_style_text_font(lv.font_montserrat_24, lv.PART.MAIN) + + self._py_app_panel = lv.obj(self.get_app_panel()) + self._py_app_panel.set_size(800, 440) + self._py_app_panel.align(lv.ALIGN.LEFT_MID, 30, 0) + self._py_app_panel.set_style_radius(10, lv.PART.MAIN) + self._py_app_panel.set_style_bg_color(lv.color_hex(0xFFFFFF), lv.PART.MAIN) + self._py_app_panel.set_style_border_width(0, lv.PART.MAIN) + + self._app_options = [] + for i, app_name in enumerate(get_hal().get_py_app_list()): + self._app_options.append( + PyAppOption( + self._py_app_panel, app_name, 10 + i * 70, self._on_py_app_option_clicked + ) + ) + + def _on_py_app_option_clicked(self, py_app_name: str): + if self._selected_option: + self._selected_option.reset_border() + self._selected_option = next( + option for option in self._app_options if option.get_py_app_name() == py_app_name + ) + + def _on_run_once_clicked(self, e: lv.event_t): + get_hal().play_click_sfx() + if self._selected_option: + get_hal().run_py_app(self._selected_option.get_py_app_name(), True) + + def _on_run_always_clicked(self, e: lv.event_t): + get_hal().play_click_sfx() + if self._selected_option: + get_hal().run_py_app(self._selected_option.get_py_app_name(), False) + + def on_cleanup(self): + self._label_run_once.delete() + self._label_run_once = None + self._btn_run_once.delete() + self._btn_run_once = None + + self._label_run_always.delete() + self._label_run_always = None + self._btn_run_always.delete() + self._btn_run_always = None + + for option in self._app_options: + option.cleanup() + self._app_options.clear() + + self._py_app_panel.delete() + self._py_app_panel = None diff --git a/m5stack/modules/startup/tab5/launcher/apps/app_dummy.py b/m5stack/modules/startup/tab5/launcher/apps/app_dummy.py new file mode 100644 index 00000000..a8997ead --- /dev/null +++ b/m5stack/modules/startup/tab5/launcher/apps/app_dummy.py @@ -0,0 +1,21 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +from .app import AppBase +import lvgl as lv +import asyncio + + +class AppDummy(AppBase): + async def main(self): + label = lv.label(self.get_app_panel()) + label.align(lv.ALIGN.CENTER, 0, 0) + label.set_style_text_font(lv.font_montserrat_24, lv.PART.MAIN) + label.set_text(self.get_app_name() + ": TODO") + + while True: + await asyncio.sleep(1) + + def on_cleanup(self): + pass diff --git a/m5stack/modules/startup/tab5/launcher/apps/app_ezdata.py b/m5stack/modules/startup/tab5/launcher/apps/app_ezdata.py new file mode 100644 index 00000000..523530a0 --- /dev/null +++ b/m5stack/modules/startup/tab5/launcher/apps/app_ezdata.py @@ -0,0 +1,279 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +from .app import AppBase +from ..hal import * +from ..common import * +import lvgl as lv +import asyncio +import time + + +class ViewBase: + def cleanup(self): + pass + + +class ViewInit(ViewBase): + """View for nothing when ezdata is initializing""" + + def __init__(self, parent: lv.obj): + label = lv.label(parent) + label.set_text("EzData Initializing...") + label.align(lv.ALIGN.CENTER, 0, -0) + label.set_style_text_font(lv.font_montserrat_24, lv.PART.MAIN) + + +class ViewWaitUserToken(ViewBase): + """View for no user token, display qrcode and tips""" + + def __init__(self, parent: lv.obj, qrcode_data: str): + qr_add = lv.qrcode(parent) + qr_add.align(lv.ALIGN.CENTER, -167, -62) + qr_add.set_size(280) + qr_add.set_dark_color(lv.color_hex(0x0075B0)) + qr_add.update(qrcode_data, len(qrcode_data)) + + label_qr_add = lv.label(parent) + label_qr_add.set_text('Scan with the "Ez Data" app to begin.') + label_qr_add.align(lv.ALIGN.CENTER, -167, 127) + label_qr_add.set_style_text_font(lv.font_montserrat_24, lv.PART.MAIN) + + install_url = "https://apps.apple.com/us/app/ezdata/id6738713869" + qr_install = lv.qrcode(parent) + qr_install.align(lv.ALIGN.CENTER, 413, -46) + qr_install.set_size(170) + qr_install.set_dark_color(lv.color_hex(0x0075B0)) + qr_install.update(install_url, len(install_url)) + + label_qr_install = lv.label(parent) + label_qr_install.set_width(300) + label_qr_install.set_text('Install "Ez Data" from App Store') + label_qr_install.align(lv.ALIGN.CENTER, 413, 110) + label_qr_install.set_style_text_font(lv.font_montserrat_24, lv.PART.MAIN) + label_qr_install.set_style_text_align(lv.TEXT_ALIGN.CENTER, lv.PART.MAIN) + + divider = lv.obj(parent) + divider.set_size(6, 345) + divider.set_style_border_width(0, lv.PART.MAIN) + divider.align(lv.ALIGN.CENTER, 249, 0) + divider.set_style_radius(2, lv.PART.MAIN) + divider.set_style_bg_color(lv.color_hex(0xE5E5E5), lv.PART.MAIN) + + +class DataItem: + """Item class to render a data key-value pair""" + + def __init__(self, parent: lv.obj, data, pos_x: int, pos_y: int): + # Transparent container for easier position handle + container = lv.obj(parent) + container.set_size(140, 210) + container.set_style_bg_opa(lv.OPA.TRANSP, lv.PART.MAIN) + container.set_style_border_width(0, lv.PART.MAIN) + container.remove_flag(lv.obj.FLAG.SCROLLABLE) + container.align(lv.ALIGN.TOP_LEFT, pos_x, pos_y) + + panel = lv.obj(container) + panel.align(lv.ALIGN.CENTER, 0, -20) + panel.set_size(140, 170) + panel.set_style_radius(18, lv.PART.MAIN) + panel.set_style_bg_color(lv.color_hex(0xFFFFFF), lv.PART.MAIN) + panel.remove_flag(lv.obj.FLAG.SCROLLABLE) + + value_panel = lv.obj(panel) + value_panel.set_size(120, 110) + value_panel.align(lv.ALIGN.CENTER, 0, -12) + value_panel.set_style_bg_color(lv.color_hex(0xFFFFFF), lv.PART.MAIN) + value_panel.set_style_border_width(0, lv.PART.MAIN) + value_panel.set_scroll_dir(lv.DIR.VER) + + label_value = lv.label(value_panel) + label_value.set_width(100) + label_value.align(lv.ALIGN.CENTER, 0, 0) + label_value.set_style_text_font(lv.font_montserrat_20, lv.PART.MAIN) + label_value.set_style_text_align(lv.TEXT_ALIGN.CENTER, lv.PART.MAIN) + + if not self._check_data_valid(data): + label_value.set_text("Invalid data") + return + + self._label_last_update = lv.label(panel) + self._label_last_update.align(lv.ALIGN.CENTER, 0, 60) + self._label_last_update.set_style_text_font(lv.font_montserrat_20, lv.PART.MAIN) + self._label_last_update.set_style_text_color(lv.color_hex(0x6E6E6E), lv.PART.MAIN) + + label_name = lv.label(container) + label_name.align(lv.ALIGN.CENTER, 0, 88) + label_name.set_style_text_font(lv.font_montserrat_20, lv.PART.MAIN) + + # Apply texts + label_value.set_text(str(data.get("value"))) + label_name.set_text(data.get("alias")) + self._update_time = data.get("updateTime") + self.update_update_time() + + def _check_data_valid(self, data) -> bool: + if not data: + return False + keys = ["alias", "value", "updateTime", "valueType"] + for key in keys: + if key not in data: + return False + return True + + def _get_time_since_last_update(self, last_update_str): + try: + year, month, day = ( + int(last_update_str[0:4]), + int(last_update_str[5:7]), + int(last_update_str[8:10]), + ) + hour, minute, second = ( + int(last_update_str[11:13]), + int(last_update_str[14:16]), + int(last_update_str[17:19]), + ) + last_time = time.mktime((year, month, day, hour, minute, second, 0, 0)) + + now = time.time() + diff = int(now - last_time) + + if diff >= 3600: + return "{}H".format(min(diff // 3600, 99)) + elif diff >= 60: + return "{}Min".format(min(diff // 60, 99)) + else: + return "{}Sec".format(min(diff, 99)) + except Exception as e: + print(e) + return "" + + def update_update_time(self): + self._label_last_update.set_text(self._get_time_since_last_update(self._update_time)) + + def cleanup(self): + self._label_last_update = None + + +class ViewData(ViewBase): + """View for a data group, render key-value pairs by api json response""" + + def __init__(self, parent: lv.obj): + self._parent = parent + self._last_data = None + self._data_items = [] + + def update(self, data): + if self._last_data == data: + return + + self._create_data_items(data) + self._last_data = data + + def update_data_update_time(self): + for item in self._data_items: + item.update_update_time() + + def _create_data_items(self, data): + # Clean up old items + for item in self._data_items: + item.cleanup() + self._data_items.clear() + self._parent.clean() + + # Create new items + start_x, start_y = 56, 30 + offset_x, offset_y = 220, 237 + items_per_row = 5 + for i, item in enumerate(data): + row = i // items_per_row + col = i % items_per_row + x = start_x + col * offset_x + y = start_y + row * offset_y + self._data_items.append(DataItem(self._parent, item, x, y)) + + def cleanup(self): + self._parent = None + + +class AppEzdata(AppBase): + async def main(self): + self._last_ezdata_state = None + self._view = None + self._last_update_time = 0 + self._last_data_update_time = 0 + + while True: + self._update_view() + await asyncio.sleep(0.5) + + def _destroy_view(self): + if self._view: + self._view.cleanup() + self._view = None + self.get_app_panel().clean() + + def _update_view(self): + # If state changed + state = Ezdata.get_state() + if state != self._last_ezdata_state: + # Destroy old + self._destroy_view() + # Create new + if state == Ezdata.State.INIT: + self._view = ViewInit(self.get_app_panel()) + elif state == Ezdata.State.WAIT_USER_TOKEN: + self._view = ViewWaitUserToken(self.get_app_panel(), Ezdata.get_add_user_qr_code()) + elif state == Ezdata.State.NORMAL: + self._view = ViewData(self.get_app_panel()) + + self._last_ezdata_state = state + self._last_update_time = 0 + + # If is data view, update data + if isinstance(self._view, ViewData): + go_refresh = False + + # If some thing changed, refresh data + if EzdataAppState.needs_refresh(): + EzdataAppState.clear_needs_refresh() + go_refresh = True + + # Auto refresh in every 10s + if time.time() - self._last_update_time > 10: + go_refresh = True + + if go_refresh: + new_data = Ezdata.get_user_data_list(EzdataAppState.get_current_group_id()) + self._view.update(new_data) + self._last_update_time = time.time() + + # Update data's last update time in every 10s + if time.time() - self._last_data_update_time > 10: + self._view.update_data_update_time() + self._last_data_update_time = time.time() + + def on_cleanup(self): + self._destroy_view() + + +class AppEzdataSettings(AppBase): + async def main(self): + btn_reset_ezdata = lv.button(self.get_app_panel()) + btn_reset_ezdata.align(lv.ALIGN.TOP_MID, -350, 80) + btn_reset_ezdata.set_size(280, 42) + btn_reset_ezdata.set_style_bg_color(lv.color_hex(0xF1677B), lv.PART.MAIN) + btn_reset_ezdata.set_style_shadow_width(0, lv.PART.MAIN) + btn_reset_ezdata.add_event_cb( + self._handle_btn_reset_ezdata_clicked, lv.EVENT.CLICKED, None + ) + + label_reset_ezdata = lv.label(btn_reset_ezdata) + label_reset_ezdata.set_align(lv.ALIGN.CENTER) + label_reset_ezdata.set_style_text_font(lv.font_montserrat_20, lv.PART.MAIN) + label_reset_ezdata.set_text("Reset Ezdata") + + def _handle_btn_reset_ezdata_clicked(self, e: lv.event_t): + get_hal().play_click_sfx() + Ezdata.reset_user_token() diff --git a/m5stack/modules/startup/tab5/launcher/apps/app_i2c_scan.py b/m5stack/modules/startup/tab5/launcher/apps/app_i2c_scan.py new file mode 100644 index 00000000..6d1dbefb --- /dev/null +++ b/m5stack/modules/startup/tab5/launcher/apps/app_i2c_scan.py @@ -0,0 +1,118 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +from .app import AppBase +from ..hal import * +from ..common import * +import lvgl as lv +import asyncio + + +class AppI2cScan(AppBase): + async def main(self): + get_hal().i2c_init() + + img_panel_port_a = lv.image(self.get_app_panel()) + img_panel_port_a.set_src(get_hal().get_asset_path("utils/i2c_panel_port_a" + IMAGE_SUFFIX)) + img_panel_port_a.align(lv.ALIGN.CENTER, -279, 34) + + img_panel_internal = lv.image(self.get_app_panel()) + img_panel_internal.set_src( + get_hal().get_asset_path("utils/i2c_panel_internal" + IMAGE_SUFFIX) + ) + img_panel_internal.align(lv.ALIGN.CENTER, 279, 34) + + btn_scan_port_a = lv.button(self.get_app_panel()) + btn_scan_port_a.set_size(260, 54) + btn_scan_port_a.align(lv.ALIGN.CENTER, -279, -158) + btn_scan_port_a.set_style_bg_color(lv.color_hex(0xFD909F), lv.PART.MAIN) + btn_scan_port_a.set_style_shadow_width(0, lv.PART.MAIN) + btn_scan_port_a.add_event_cb(self._handle_scan_port_a, lv.EVENT.CLICKED, None) + + btn_label_port_a = lv.label(btn_scan_port_a) + btn_label_port_a.set_text("SCAN PORT.A") + btn_label_port_a.align(lv.ALIGN.CENTER, 0, 0) + btn_label_port_a.set_style_text_font(lv.font_montserrat_24, lv.PART.MAIN) + btn_label_port_a.set_style_text_color(lv.color_hex(0x373737), lv.PART.MAIN) + + btn_scan_internal = lv.button(self.get_app_panel()) + btn_scan_internal.set_size(260, 54) + btn_scan_internal.align(lv.ALIGN.CENTER, 279, -158) + btn_scan_internal.set_style_bg_color(lv.color_hex(0xFFD733), lv.PART.MAIN) + btn_scan_internal.set_style_shadow_width(0, lv.PART.MAIN) + btn_scan_internal.add_event_cb(self._handle_scan_internal, lv.EVENT.CLICKED, None) + + btn_label_internal = lv.label(btn_scan_internal) + btn_label_internal.set_text("SCAN INTERNAL") + btn_label_internal.align(lv.ALIGN.CENTER, 0, 0) + btn_label_internal.set_style_text_font(lv.font_montserrat_24, lv.PART.MAIN) + btn_label_internal.set_style_text_color(lv.color_hex(0x373737), lv.PART.MAIN) + + self._addr_labels_port_a: list[lv.label] = [] + self._addr_labels_internal: list[lv.label] = [] + + self._update_internal = False + self._update_port_a = False + + while True: + await asyncio.sleep_ms(100) + if self._update_internal: + self._update_addr_labels(0, get_hal().i2c_scan(0)) + self._update_internal = False + if self._update_port_a: + self._update_addr_labels(1, get_hal().i2c_scan(1)) + self._update_port_a = False + + def _handle_scan_port_a(self, e: lv.event_t): + get_hal().play_click_sfx() + self._update_port_a = True + + def _handle_scan_internal(self, e: lv.event_t): + get_hal().play_click_sfx() + self._update_internal = True + + def _clear_addr_labels_port_a(self): + for label in self._addr_labels_port_a: + label.delete() + self._addr_labels_port_a.clear() + + def _clear_addr_labels_internal(self): + for label in self._addr_labels_internal: + label.delete() + self._addr_labels_internal.clear() + + def _update_addr_labels(self, port: int, scan_result: list[int]): + """port: 0 for internal, 1 for port A""" + + if port == 0: + self._clear_addr_labels_internal() + else: + self._clear_addr_labels_port_a() + + for addr in scan_result: + label = lv.label(self.get_app_panel()) + label.set_text(f"{addr:02X}") + label.set_style_text_font(lv.font_montserrat_16, lv.PART.MAIN) + label.set_style_text_color(lv.color_hex(0x352B2A), lv.PART.MAIN) + + row = addr >> 4 + col = addr & 0x0F + x = col * 27 + if port == 0: + x = x + 100 + else: + x = x - 458 + y = row * 27 - 39 + label.align(lv.ALIGN.CENTER, int(x), int(y)) + + if port == 0: + self._addr_labels_internal.append(label) + else: + self._addr_labels_port_a.append(label) + + def on_cleanup(self): + get_hal().i2c_deinit() + + self._clear_addr_labels_port_a() + self._clear_addr_labels_internal() diff --git a/m5stack/modules/startup/tab5/launcher/apps/app_uart.py b/m5stack/modules/startup/tab5/launcher/apps/app_uart.py new file mode 100644 index 00000000..81259ba2 --- /dev/null +++ b/m5stack/modules/startup/tab5/launcher/apps/app_uart.py @@ -0,0 +1,170 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +from .app import AppBase +from ..hal import * +from ..common import * +import lvgl as lv +import asyncio + + +class AppUart(AppBase): + class _UartState: + CLOSED = 0 + OPENED = 1 + + _BAUDRATE_OPTIONS = [115200] + _TX_PIN_OPTIONS = [20] + _RX_PIN_OPTIONS = [21] + + async def main(self): + self._state = self._UartState.CLOSED + + self._ta_rx = lv.textarea(self.get_app_panel()) + self._ta_rx.align(lv.ALIGN.TOP_LEFT, 14, 15) + self._ta_rx.set_size(660, 145) + self._ta_rx.set_style_border_width(0, lv.PART.MAIN) + self._ta_rx.set_style_text_font(lv.font_montserrat_18, lv.PART.MAIN) + self._ta_rx.set_style_bg_color(lv.color_hex(0xFFFFFF), lv.PART.MAIN) + + self._ta_tx = lv.textarea(self.get_app_panel()) + self._ta_tx.align(lv.ALIGN.TOP_LEFT, 14, 183) + self._ta_tx.set_size(660, 46) + self._ta_tx.set_one_line(True) + self._ta_tx.set_style_border_width(0, lv.PART.MAIN) + self._ta_tx.set_style_text_font(lv.font_montserrat_18, lv.PART.MAIN) + self._ta_tx.set_style_bg_color(lv.color_hex(0xFFFFFF), lv.PART.MAIN) + + self._kb = lv.keyboard(self.get_app_panel()) + self._kb.set_style_text_font(lv.font_montserrat_20, lv.PART.MAIN) + self._kb.set_height(lv.pct(52)) + self._kb.add_event_cb(self._handle_kb_clicked, lv.EVENT.CLICKED, None) + self._kb.set_textarea(self._ta_tx) + + self._btn_open = lv.button(self.get_app_panel()) + self._btn_open.align(lv.ALIGN.TOP_MID, 410, 183) + self._btn_open.set_size(280, 42) + self._btn_open.set_style_shadow_width(0, lv.PART.MAIN) + self._btn_open.add_event_cb(self._handle_open_btn_clicked, lv.EVENT.CLICKED, None) + + self._label_open = lv.label(self._btn_open) + self._label_open.set_align(lv.ALIGN.CENTER) + self._label_open.set_style_text_font(lv.font_montserrat_20, lv.PART.MAIN) + + self._btn_clear = lv.button(self.get_app_panel()) + self._btn_clear.align(lv.ALIGN.TOP_MID, 185, 102) + self._btn_clear.set_size(110, 42) + self._btn_clear.set_style_bg_color(lv.color_hex(0x2DA4E0), lv.PART.MAIN) + self._btn_clear.set_style_shadow_width(0, lv.PART.MAIN) + self._btn_clear.add_event_cb(self._handle_clear_btn_clicked, lv.EVENT.CLICKED, None) + + self._label_clear = lv.label(self._btn_clear) + self._label_clear.set_align(lv.ALIGN.CENTER) + self._label_clear.set_style_text_font(lv.font_montserrat_20, lv.PART.MAIN) + self._label_clear.set_text("CLEAR") + + self._btn_send = lv.button(self.get_app_panel()) + self._btn_send.align(lv.ALIGN.TOP_MID, 185, 183) + self._btn_send.set_size(110, 42) + self._btn_send.set_style_bg_color(lv.color_hex(0x2DA4E0), lv.PART.MAIN) + self._btn_send.set_style_shadow_width(0, lv.PART.MAIN) + self._btn_send.add_event_cb(self._handle_send_btn_clicked, lv.EVENT.CLICKED, None) + + self._label_send = lv.label(self._btn_send) + self._label_send.set_align(lv.ALIGN.CENTER) + self._label_send.set_style_text_font(lv.font_montserrat_20, lv.PART.MAIN) + self._label_send.set_text("SEND") + + self._dd_baudrate = lv.dropdown(self.get_app_panel()) + self._dd_baudrate.align(lv.ALIGN.TOP_MID, 470, 15) + self._dd_baudrate.set_size(164, 48) + self._dd_baudrate.set_style_shadow_width(0, lv.PART.MAIN) + self._dd_baudrate.set_style_text_font(lv.font_montserrat_20, lv.PART.MAIN) + self._dd_baudrate.set_style_border_width(0, lv.PART.MAIN) + self._dd_baudrate.set_options("\n".join([str(b) for b in self._BAUDRATE_OPTIONS])) + + self._dd_tx_pin = lv.dropdown(self.get_app_panel()) + self._dd_tx_pin.align(lv.ALIGN.TOP_MID, 352, 98) + self._dd_tx_pin.set_size(98, 48) + self._dd_tx_pin.set_style_shadow_width(0, lv.PART.MAIN) + self._dd_tx_pin.set_style_text_font(lv.font_montserrat_20, lv.PART.MAIN) + self._dd_tx_pin.set_style_border_width(0, lv.PART.MAIN) + self._dd_tx_pin.set_options("\n".join([str(p) for p in self._TX_PIN_OPTIONS])) + + self._dd_rx_pin = lv.dropdown(self.get_app_panel()) + self._dd_rx_pin.align(lv.ALIGN.TOP_MID, 503, 98) + self._dd_rx_pin.set_size(98, 48) + self._dd_rx_pin.set_style_shadow_width(0, lv.PART.MAIN) + self._dd_rx_pin.set_style_text_font(lv.font_montserrat_20, lv.PART.MAIN) + self._dd_rx_pin.set_style_border_width(0, lv.PART.MAIN) + self._dd_rx_pin.set_options("\n".join([str(p) for p in self._RX_PIN_OPTIONS])) + + self._label_baudrate = lv.label(self.get_app_panel()) + self._label_baudrate.align(lv.ALIGN.TOP_MID, 327, 28) + self._label_baudrate.set_style_text_font(lv.font_montserrat_18, lv.PART.MAIN) + self._label_baudrate.set_text("Baudrate:") + + self._label_tx_pin = lv.label(self.get_app_panel()) + self._label_tx_pin.align(lv.ALIGN.TOP_MID, 278, 112) + self._label_tx_pin.set_style_text_font(lv.font_montserrat_18, lv.PART.MAIN) + self._label_tx_pin.set_text("TX:") + + self._label_rx_pin = lv.label(self.get_app_panel()) + self._label_rx_pin.align(lv.ALIGN.TOP_MID, 427, 112) + self._label_rx_pin.set_style_text_font(lv.font_montserrat_18, lv.PART.MAIN) + self._label_rx_pin.set_text("RX:") + + self._set_state_to(self._UartState.CLOSED) + + while True: + await asyncio.sleep(0.2) + if self._state == self._UartState.OPENED: + data = get_hal().uart_read() + if data: + for c in data: + self._ta_rx.add_char(c) + + def _handle_kb_clicked(self, e: lv.event_t): + get_hal().play_click_sfx() + + def _handle_open_btn_clicked(self, e: lv.event_t): + get_hal().play_click_sfx() + if self._state == self._UartState.CLOSED: + self._set_state_to(self._UartState.OPENED) + else: + self._set_state_to(self._UartState.CLOSED) + + def _handle_clear_btn_clicked(self, e: lv.event_t): + get_hal().play_click_sfx() + self._ta_rx.set_text("") + + def _handle_send_btn_clicked(self, e: lv.event_t): + get_hal().play_click_sfx() + if self._state == self._UartState.OPENED: + get_hal().uart_write(self._ta_tx.get_text()) + + def _set_state_to(self, new_state: _UartState): + if new_state == self._UartState.OPENED: + # Update ui + self._btn_open.set_style_bg_color(lv.color_hex(0x1EB561), lv.PART.MAIN) + self._label_open.set_text("CLOSE") + + # Init uart + get_hal().uart_init( + self._BAUDRATE_OPTIONS[self._dd_baudrate.get_selected()], + self._TX_PIN_OPTIONS[self._dd_tx_pin.get_selected()], + self._RX_PIN_OPTIONS[self._dd_rx_pin.get_selected()], + ) + else: + # Update ui + self._btn_open.set_style_bg_color(lv.color_hex(0x2DA4E0), lv.PART.MAIN) + self._label_open.set_text("OPEN") + + # Deinit uart + get_hal().uart_deinit() + + self._state = new_state + + def on_cleanup(self): + get_hal().uart_deinit() diff --git a/m5stack/modules/startup/tab5/launcher/apps/app_uiflow.py b/m5stack/modules/startup/tab5/launcher/apps/app_uiflow.py new file mode 100644 index 00000000..e69de29b diff --git a/m5stack/modules/startup/tab5/launcher/apps/app_wifi.py b/m5stack/modules/startup/tab5/launcher/apps/app_wifi.py new file mode 100644 index 00000000..94bf1746 --- /dev/null +++ b/m5stack/modules/startup/tab5/launcher/apps/app_wifi.py @@ -0,0 +1,109 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +from .app import AppBase +from ..hal import * +import lvgl as lv +import asyncio + + +COLOR_TEXT_AREA_BG = 0xE6E2E6 +COLOR_LABEL = 0x038FD5 +COLOR_BTN_BG = 0x1DA4E7 + +TEXT_AREA_CONFIG = [ + {"x": 30, "y": 40, "label": "WiFi SSID :"}, + {"x": 600, "y": 40, "label": "WiFi Password :"}, + {"x": 30, "y": 135, "label": "UIFlow Server :"}, +] + + +class AppWifi(AppBase): + async def main(self): + self._network_config = get_hal().get_network_config() + + # Text areas + self._text_areas: list[lv.textarea] = [] + self._labels: list[lv.label] = [] + for config in TEXT_AREA_CONFIG: + self._text_areas.append(lv.textarea(self.get_app_panel())) + self._text_areas[-1].align(lv.ALIGN.TOP_LEFT, config["x"], config["y"]) + self._text_areas[-1].set_width(500) + self._text_areas[-1].set_one_line(True) + self._text_areas[-1].set_style_border_width(0, lv.PART.MAIN) + self._text_areas[-1].set_style_text_font(lv.font_montserrat_20, lv.PART.MAIN) + self._text_areas[-1].set_style_bg_color(lv.color_hex(COLOR_TEXT_AREA_BG), lv.PART.MAIN) + self._text_areas[-1].add_event_cb(self._ta_event_cb, lv.EVENT.ALL, None) + + self._labels.append(lv.label(self.get_app_panel())) + self._labels[-1].set_text(config["label"]) + self._labels[-1].align_to(self._text_areas[-1], lv.ALIGN.OUT_TOP_LEFT, 10, -15) + self._labels[-1].set_style_text_font(lv.font_montserrat_18, lv.PART.MAIN) + self._labels[-1].set_style_text_color(lv.color_hex(COLOR_LABEL), lv.PART.MAIN) + + # Enable * for password + self._text_areas[1].set_password_mode(True) + + # Create keyboard + self._kb = lv.keyboard(self.get_app_panel()) + self._kb.set_style_text_font(lv.font_montserrat_20, lv.PART.MAIN) + self._kb.set_height(lv.pct(60)) + self._kb.add_event_cb(self._on_kb_clicked, lv.EVENT.CLICKED, None) + + # Create confirm button + self._btn = lv.button(self.get_app_panel()) + self._btn.align(lv.ALIGN.TOP_LEFT, 890, 135) + self._btn.set_style_shadow_width(0, lv.PART.MAIN) + self._btn.set_width(200) + self._btn.set_style_bg_color(lv.color_hex(COLOR_BTN_BG), lv.PART.MAIN) + self._btn.add_event_cb(self._on_confim, lv.EVENT.CLICKED, None) + self._btn_label = lv.label(self._btn) + self._btn_label.set_text("Save And Link") + self._btn_label.set_align(lv.ALIGN.CENTER) + self._btn_label.set_style_text_font(lv.font_montserrat_20, lv.PART.MAIN) + + self._update_text_areas() + + def _update_text_areas(self): + self._text_areas[0].set_text(self._network_config.ssid) + self._text_areas[1].set_text(self._network_config.password) + self._text_areas[2].set_text(self._network_config.server) + + def _ta_event_cb(self, e: lv.event_t): + code = e.get_code() + ta = e.get_target_obj() + if code == lv.EVENT.CLICKED or code == lv.EVENT.FOCUSED: + # Remove border from all textareas first + for textarea in self._text_areas: + textarea.set_style_border_width(0, lv.PART.MAIN) + # Set keyboard to current textarea + self._kb.set_textarea(ta) + + def _on_kb_clicked(self, e: lv.event_t): + get_hal().play_click_sfx() + + def _on_confim(self, e: lv.event_t): + get_hal().play_click_sfx() + self._network_config.ssid = self._text_areas[0].get_text() + self._network_config.password = self._text_areas[1].get_text() + self._network_config.server = self._text_areas[2].get_text() + get_hal().set_network_config(self._network_config) + + def on_cleanup(self): + for ta in self._text_areas: + ta.delete() + self._text_areas.clear() + + for label in self._labels: + label.delete() + self._labels.clear() + + self._kb.delete() + self._kb = None + + self._btn_label.delete() + self._btn_label = None + + self._btn.delete() + self._btn = None diff --git a/m5stack/modules/startup/tab5/launcher/apps/app_wifi_scan.py b/m5stack/modules/startup/tab5/launcher/apps/app_wifi_scan.py new file mode 100644 index 00000000..49927046 --- /dev/null +++ b/m5stack/modules/startup/tab5/launcher/apps/app_wifi_scan.py @@ -0,0 +1,136 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +from .app import AppBase +from ..hal import * +import lvgl as lv +import asyncio + + +class NetworkInfo: + def __init__(self, parent: lv.obj, network_info: tuple, pos_y: int): + self._panel = lv.obj(parent) + self._panel.align(lv.ALIGN.TOP_LEFT, 0, pos_y) + self._panel.set_size(880, 40) + self._panel.set_style_pad_all(0, lv.PART.MAIN) + self._panel.set_style_border_width(0, lv.PART.MAIN) + self._panel.set_style_radius(10, lv.PART.MAIN) + + self._label = lv.label(self._panel) + self._label.align(lv.ALIGN.LEFT_MID, 10, 0) + self._label.set_style_text_font(lv.font_montserrat_14, lv.PART.MAIN) + self._label.set_style_text_color(lv.color_hex(0x4A4A4A), lv.PART.MAIN) + + self._parse_network_info(network_info) + + def _parse_network_info(self, network_info: tuple): + ssid = network_info[0].decode() # SSID 是 bytes,要 decode + bssid = ":".join("{:02x}".format(b) for b in network_info[1]) # BSSID 转换为 MAC 地址格式 + channel = network_info[2] + rssi = network_info[3] + auth = network_info[4] + hidden = network_info[5] + + auth_dict = {0: "OPEN", 1: "WEP", 2: "WPA-PSK", 3: "WPA2-PSK", 4: "WPA/WPA2-PSK"} + auth_str = auth_dict.get(auth, "UNKNOWN") + + self._label.set_text( + f"SSID: {ssid} | RSSI: {rssi:3} dBm | CH: {channel} | Auth: {auth_str} | Hidden: {hidden} | BSSID: {bssid}" + ) + self._panel.set_style_bg_color( + lv.color_hex(0xC8F3D1 if rssi >= -60 else 0xFFE7D7), lv.PART.MAIN + ) + + def cleanup(self): + self._label.delete() + self._label = None + + self._panel.delete() + self._panel = None + + +class AppWifiScan(AppBase): + class ScanState: + IDLE = 0 + SCANNING = 1 + + async def main(self): + self._task = None + self._current_state = self.ScanState.IDLE + self._results = [] + + self._btn_scan = lv.button(self.get_app_panel()) + self._btn_scan.set_size(150, 100) + self._btn_scan.align(lv.ALIGN.TOP_RIGHT, -18, 350) + self._btn_scan.set_style_bg_color(lv.color_hex(0x119FE6), lv.PART.MAIN) + self._btn_scan.set_style_radius(10, lv.PART.MAIN) + self._btn_scan.set_style_shadow_width(0, lv.PART.MAIN) + self._btn_scan.add_event_cb(self._on_scan_btn_clicked, lv.EVENT.CLICKED, None) + + self._label_scan = lv.label(self._btn_scan) + self._label_scan.set_text("SCAN") + self._label_scan.set_align(lv.ALIGN.CENTER) + self._label_scan.set_style_text_font(lv.font_montserrat_20, lv.PART.MAIN) + + self._wifi_panel = lv.obj(self.get_app_panel()) + self._wifi_panel.set_size(920, 440) + self._wifi_panel.align(lv.ALIGN.LEFT_MID, 15, 0) + self._wifi_panel.set_style_radius(10, lv.PART.MAIN) + self._wifi_panel.set_style_bg_color(lv.color_hex(0xFFFFFF), lv.PART.MAIN) + self._wifi_panel.set_style_border_width(0, lv.PART.MAIN) + + self._spinner = lv.spinner(self._wifi_panel) + self._spinner.align(lv.ALIGN.CENTER, 0, 0) + self._spinner.set_size(60, 60) + self._spinner.set_style_arc_width(5, lv.PART.MAIN) + self._spinner.set_style_arc_width(5, lv.PART.INDICATOR) + + self._update_state(self.ScanState.IDLE) + + def _update_state(self, state: ScanState): + self._current_state = state + self._update_ui() + + def _update_ui(self): + if self._current_state == self.ScanState.IDLE: + self._btn_scan.add_flag(lv.obj.FLAG.CLICKABLE) + self._label_scan.set_text("SCAN") + self._spinner.set_style_opa(lv.OPA.TRANSP, lv.PART.MAIN) + elif self._current_state == self.ScanState.SCANNING: + self._btn_scan.remove_flag(lv.obj.FLAG.CLICKABLE) + self._label_scan.set_text("SCANNING...") + self._spinner.set_style_opa(lv.OPA.COVER, lv.PART.MAIN) + + def _on_scan_btn_clicked(self, e: lv.event_t): + get_hal().play_click_sfx() + if self._current_state == self.ScanState.SCANNING: + return + self._update_state(self.ScanState.SCANNING) + self._task = asyncio.create_task(self._wifi_scan_task()) + + def _clear_result_labels(self): + for result in self._results: + result.cleanup() + self._results.clear() + + def _create_result_labels(self, new_result: list[tuple]): + for i, network_info in enumerate(new_result): + self._results.append(NetworkInfo(self._wifi_panel, network_info, 10 + i * 70)) + + async def _wifi_scan_task(self): + self._clear_result_labels() + result = await get_hal().scan_wifi() + self._create_result_labels(result) + self._update_state(self.ScanState.IDLE) + + def on_cleanup(self): + self._label_scan.delete() + self._label_scan = None + self._btn_scan.delete() + self._btn_scan = None + + self._clear_result_labels() + + self._wifi_panel.delete() + self._wifi_panel = None diff --git a/m5stack/modules/startup/tab5/launcher/common/__init__.py b/m5stack/modules/startup/tab5/launcher/common/__init__.py new file mode 100644 index 00000000..5e3a551e --- /dev/null +++ b/m5stack/modules/startup/tab5/launcher/common/__init__.py @@ -0,0 +1,3 @@ +from .common import * +from .indicator import IconIndicator +from .ezdata import Ezdata, EzdataAppState diff --git a/m5stack/modules/startup/tab5/launcher/common/common.py b/m5stack/modules/startup/tab5/launcher/common/common.py new file mode 100644 index 00000000..0b2b3d58 --- /dev/null +++ b/m5stack/modules/startup/tab5/launcher/common/common.py @@ -0,0 +1,6 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +IMAGE_SUFFIX = ".bin" +VERSION = "1.0.0" diff --git a/m5stack/modules/startup/tab5/launcher/common/ezdata.py b/m5stack/modules/startup/tab5/launcher/common/ezdata.py new file mode 100644 index 00000000..f7360ad5 --- /dev/null +++ b/m5stack/modules/startup/tab5/launcher/common/ezdata.py @@ -0,0 +1,253 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MI + +from ..hal import * +import asyncio +import requests +import json +from umqtt import MQTTClient + + +class Ezdata: + class State: + INIT = 0 + WAIT_USER_TOKEN = 1 + NORMAL = 2 + + _state: State = State.INIT + _device_token: str | None = None + _user_token: str | None = None + _mqtt_client: MQTTClient | None = None + _task = None + + @staticmethod + def start(): + Ezdata._task = asyncio.create_task(Ezdata._ezdata_task()) + + @staticmethod + def get_state() -> State: + return Ezdata._state + + @staticmethod + def _set_state(state: State): + Ezdata._state = state + + @staticmethod + async def _ezdata_task(): + try: + Ezdata._set_state(Ezdata.State.INIT) + await Ezdata._wait_network_connected() + await Ezdata._get_device_token() + await Ezdata._connect_mqtt() + Ezdata._get_user_token_from_storage() + + # Keep mqtt update + while True: + Ezdata._mqtt_client.check_msg() + await asyncio.sleep(0.2) + + except Exception as e: + print("ezdata task failed:", e) + await asyncio.sleep(2) + asyncio.create_task(Ezdata._ezdata_task()) + + @staticmethod + async def _wait_network_connected(): + while get_hal().get_network_status() in {NetworkStatus.DISCONNECTED, NetworkStatus.INIT}: + await asyncio.sleep(1) + + @staticmethod + def _check_token_valid(token: str | None) -> bool: + if token is None: + return False + if token == "": + return False + if len(token) != 32: + return False + return True + + @staticmethod + async def _get_device_token(): + url = "https://ezdata2.m5stack.com/api/v2/device/registerMac" + headers = {"Content-Type": "application/json"} + data = { + "deviceType": "tab5", + "mac": "".join(f"{byte:02x}" for byte in get_hal().get_mac()), + } + json_data = json.dumps(data) + + # Fetch device token + while True: + try: + response = requests.post(url, data=json_data, headers=headers) + if response.status_code == 200: + # Parse and check + response_data = response.json() + Ezdata._device_token = response_data.get("data") + if Ezdata._check_token_valid(Ezdata._device_token): + # print("get device token:", Ezdata._device_token) + break + print("invalid device token:", Ezdata._device_token) + else: + print( + f"get device token failed, status code: {response.status_code} , text: {response.text}" + ) + except Exception as e: + print("get device token failed:", e) + + Ezdata._device_token = None + await asyncio.sleep(2) + + @staticmethod + async def _connect_mqtt(): + client_id = "".join(f"{byte:02x}" for byte in get_hal().get_mac()) + client_id = f"m5{client_id}m5" + + Ezdata._mqtt_client = MQTTClient( + client_id, + "uiflow2.m5stack.com", + port=1883, + user=Ezdata._device_token, + password="", + keepalive=60, + ) + + # Try connect and subscribe + while True: + try: + Ezdata._mqtt_client.connect(clean_session=True) + Ezdata._mqtt_client.subscribe( + f"$ezdata/{Ezdata._device_token}/down", Ezdata._on_ezdata_down_message + ) + break + except Exception as e: + print("connect mqtt failed:", e) + + await asyncio.sleep(2) + + @staticmethod + def _on_ezdata_down_message(data: tuple[bytes, bytes]): + try: + topic = data[0].decode("utf-8") + payload = data[1].decode("utf-8") + print("mqtt >>", topic, ":", payload) + + msg = json.loads(payload) + if msg.get("userToken"): + Ezdata._user_token = msg.get("userToken") + if Ezdata._check_token_valid(Ezdata._user_token): + get_hal().store_ezdata_user_token(Ezdata._user_token) + Ezdata._set_state(Ezdata.State.NORMAL) + else: + Ezdata._user_token = None + raise Exception("invalid user token") + + except Exception as e: + print("on ezdata down message failed:", e) + + @staticmethod + def _get_user_token_from_storage(): + Ezdata._user_token = get_hal().get_ezdata_user_token() + if Ezdata._check_token_valid(Ezdata._user_token): + Ezdata._set_state(Ezdata.State.NORMAL) + else: + Ezdata._user_token = "" + Ezdata._set_state(Ezdata.State.WAIT_USER_TOKEN) + + @staticmethod + def get_add_user_qr_code(): + data = {"deviceToken": Ezdata._device_token, "deviceType": "tab5", "type": "device"} + return json.dumps(data) + + @staticmethod + def _simplify_user_group_list_response_json(data: dict): + return [{k: item.get(k) for k in ["id", "domainName"]} for item in data.get("data", [])] + + @staticmethod + def get_user_group_list(): + result = [] + + try: + if not Ezdata._check_token_valid(Ezdata._user_token): + raise Exception("user token not valid") + + url = f"https://ezdata2.m5stack.com/api/v2/device/userGroupList/{Ezdata._user_token}" + + response = requests.get(url) + if response.status_code == 200: + result = Ezdata._simplify_user_group_list_response_json(response.json()) + + # Filter out invalid items + result = [item for item in result if "id" in item and "domainName" in item] + + else: + raise Exception( + f"get user group list failed, status code: {response.status_code} , text: {response.text}" + ) + + except Exception as e: + print("get user group list failed:", e) + + return result + + @staticmethod + def _simplify_user_data_list_response_json(data: dict): + return [ + {k: item.get(k) for k in ["alias", "value", "updateTime", "valueType"]} + for item in data.get("data", []) + ] + + @staticmethod + def get_user_data_list(group_id: str): + result = [] + + try: + if not group_id: + raise Exception("group id not valid") + + if not Ezdata._check_token_valid(Ezdata._user_token): + raise Exception("user token not valid") + + url = f"https://ezdata2.m5stack.com/api/v2/device/userDataList/{Ezdata._user_token}/{group_id}" + + response = requests.get(url) + if response.status_code == 200: + result = Ezdata._simplify_user_data_list_response_json(response.json()) + else: + raise Exception( + f"get user data list failed, status code: {response.status_code} , text: {response.text}" + ) + + except Exception as e: + print("get user data list failed:", e) + + return result + + @staticmethod + def reset_user_token(): + get_hal().reset_ezdata_user_token() + Ezdata._user_token = None + Ezdata._set_state(Ezdata.State.WAIT_USER_TOKEN) + + +class EzdataAppState: + _current_group_id: str = "" + _needs_refresh: bool = True + + @staticmethod + def set_new_group_id(group_id: str): + EzdataAppState._current_group_id = group_id + EzdataAppState._needs_refresh = True + + @staticmethod + def get_current_group_id(): + return EzdataAppState._current_group_id + + @staticmethod + def needs_refresh(): + return EzdataAppState._needs_refresh + + @staticmethod + def clear_needs_refresh(): + EzdataAppState._needs_refresh = False diff --git a/m5stack/modules/startup/tab5/launcher/common/indicator.py b/m5stack/modules/startup/tab5/launcher/common/indicator.py new file mode 100644 index 00000000..9a39d515 --- /dev/null +++ b/m5stack/modules/startup/tab5/launcher/common/indicator.py @@ -0,0 +1,24 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +from ..hal import get_hal +from ..common import IMAGE_SUFFIX +import lvgl as lv + + +class IconIndicator: + _img: lv.image = None + + @staticmethod + def create_indicator(target: lv.obj): + if not IconIndicator._img: + IconIndicator._img = lv.image(lv.screen_active()) + IconIndicator._img.set_src(get_hal().get_asset_path("icons/indicator" + IMAGE_SUFFIX)) + IconIndicator._img.align_to(target, lv.ALIGN.OUT_BOTTOM_MID, 0, 3) + + @staticmethod + def destroy_indicator(): + if IconIndicator._img: + IconIndicator._img.delete() + IconIndicator._img = None diff --git a/m5stack/modules/startup/tab5/launcher/components/__init__.py b/m5stack/modules/startup/tab5/launcher/components/__init__.py new file mode 100644 index 00000000..dc765a09 --- /dev/null +++ b/m5stack/modules/startup/tab5/launcher/components/__init__.py @@ -0,0 +1,7 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +from .app_dock import AppDock +from .ezdata_dock import EzdataDock +from .status_bar import StatusBar diff --git a/m5stack/modules/startup/tab5/launcher/components/app_dock.py b/m5stack/modules/startup/tab5/launcher/components/app_dock.py new file mode 100644 index 00000000..5c8cab27 --- /dev/null +++ b/m5stack/modules/startup/tab5/launcher/components/app_dock.py @@ -0,0 +1,50 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +from ..hal import get_hal +from ..apps.app import AppManager +from ..common import IMAGE_SUFFIX, IconIndicator +import lvgl as lv + + +class AppIcon: + _TODO_LIST = ["ADC", "GPIO", "I2CScan"] + + def __init__(self, pos_x: int, pos_y: int, app_name: str, icon_name: str): + self._app_name = app_name + self._icon_pos_x = pos_x + + img = lv.image(lv.screen_active()) + img.set_src(get_hal().get_asset_path("icons/" + icon_name + IMAGE_SUFFIX)) + img.align(lv.ALIGN.TOP_LEFT, pos_x, pos_y) + img.add_flag(lv.obj.FLAG.CLICKABLE) + img.add_event_cb(self._on_clicked, lv.EVENT.CLICKED, None) + + if app_name in self._TODO_LIST: + img.remove_flag(lv.obj.FLAG.CLICKABLE) + img.set_style_image_recolor_opa(123, lv.PART.MAIN) + + def _on_clicked(self, e: lv.event_t): + get_hal().play_click_sfx() + IconIndicator.create_indicator(e.get_target_obj()) + AppManager.open_app(self._app_name) + + +class AppDock: + def __init__(self): + # UIFlow app list + self._app_app_list = AppIcon(1046, 35, "AppList", "app_list") + + # Tools + tools = [ + ("ADC", "adc"), + ("GPIO", "gpio"), + ("UART", "uart"), + ("WifiScan", "wifi_scan"), + ("I2CScan", "i2c_scan"), + ] + self._tools = {} + for i, (tool_name, icon_name) in enumerate(tools): + x = 481 + i * 110 + self._tools[tool_name] = AppIcon(x, 35, tool_name, icon_name) diff --git a/m5stack/modules/startup/tab5/launcher/components/ezdata_dock.py b/m5stack/modules/startup/tab5/launcher/components/ezdata_dock.py new file mode 100644 index 00000000..9bd433f3 --- /dev/null +++ b/m5stack/modules/startup/tab5/launcher/components/ezdata_dock.py @@ -0,0 +1,134 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +from ..hal import get_hal +from ..apps.app import AppManager +from ..common import IconIndicator, Ezdata, EzdataAppState +import lvgl as lv +import asyncio +import time + + +class EzdataIcon: + def __init__(self, parent: lv.obj, pos_x: int, group_id: str, name: str): + self._group_id = group_id + self._icon_pos_x = pos_x + + btn = lv.obj(parent) + btn.set_size(99, 99) + btn.set_style_border_width(0, lv.PART.MAIN) + btn.align(lv.ALIGN.LEFT_MID, pos_x, 0) + btn.add_flag(lv.obj.FLAG.CLICKABLE) + btn.set_style_pad_all(0, lv.PART.MAIN) + btn.add_event_cb(self._on_clicked, lv.EVENT.CLICKED, None) + + if not group_id: + btn.remove_flag(lv.obj.FLAG.CLICKABLE) + btn.set_style_bg_color(lv.color_hex(0x808080), lv.PART.MAIN) + elif group_id == "settings": + btn.set_style_bg_color(lv.color_hex(0x9E9E9E), lv.PART.MAIN) + else: + btn.set_style_bg_color(lv.color_hex(0xE6E6E6), lv.PART.MAIN) + + label = lv.label(btn) + label.set_text(name) + label.set_width(82) + label.align(lv.ALIGN.CENTER, 0, 0) + label.set_style_text_color(lv.color_hex(0x4A4949), lv.PART.MAIN) + label.set_style_text_align(lv.TEXT_ALIGN.CENTER, lv.PART.MAIN) + if group_id == "add": + label.set_style_text_font(lv.font_montserrat_24, lv.PART.MAIN) + else: + label.set_style_text_font(lv.font_montserrat_14, lv.PART.MAIN) + + def _on_clicked(self, e: lv.event_t): + get_hal().play_click_sfx() + IconIndicator.create_indicator(e.get_target_obj()) + + if self._group_id == "settings": + AppManager.open_app("EzDataSettings") + else: + EzdataAppState.set_new_group_id(self._group_id) + AppManager.open_app("EzData") + + +class EzdataDock: + def __init__(self): + self._last_ezdata_state = Ezdata.State.INIT + self._icons = [] + self._last_update_group_list_time = 0 + self._last_group_list = [] + + self._dock_panel = lv.obj(lv.screen_active()) + self._dock_panel.set_size(445, 115) + self._dock_panel.align(lv.ALIGN.TOP_LEFT, 17, 27) + self._dock_panel.set_style_bg_opa(lv.OPA.TRANSP, lv.PART.MAIN) + self._dock_panel.set_style_border_width(0, lv.PART.MAIN) + self._dock_panel.set_style_pad_all(8, lv.PART.MAIN) + self._dock_panel.set_scrollbar_mode(lv.SCROLLBAR_MODE.ACTIVE) + + self._create_init_dock() + + self._task = asyncio.create_task(self._update_task()) + + async def _update_task(self): + while True: + await asyncio.sleep(0.5) + + state = Ezdata.get_state() + + # If state changed + if not state == self._last_ezdata_state: + self._handle_state_changed(state) + self._last_ezdata_state = state + + # Check if group list is updated in every 10s + if state == Ezdata.State.NORMAL: + if time.time() - self._last_update_group_list_time > 10: + new_group_list = Ezdata.get_user_group_list() + if new_group_list != self._last_group_list: + self._create_data_dock(new_group_list) + self._last_group_list = new_group_list + self._last_update_group_list_time = time.time() + + def _handle_state_changed(self, new_state: Ezdata.State): + # State init and wait token share the same dock + if new_state == Ezdata.State.INIT: + if self._last_ezdata_state == Ezdata.State.WAIT_USER_TOKEN: + return + self._create_init_dock() + elif new_state == Ezdata.State.WAIT_USER_TOKEN: + if self._last_ezdata_state == Ezdata.State.INIT: + return + self._create_init_dock() + + # Create data dock when ezdata in normal state + elif new_state == Ezdata.State.NORMAL: + new_group_list = Ezdata.get_user_group_list() + self._create_data_dock(new_group_list) + self._last_update_group_list_time = time.time() + self._last_group_list = new_group_list + + def _create_init_dock(self): + self._icons.clear() + self._dock_panel.clean() + + for i in range(4): + if i == 0: + self._icons.append(EzdataIcon(self._dock_panel, 0, "add", "+")) + else: + self._icons.append(EzdataIcon(self._dock_panel, i * 110, "", "")) + + def _create_data_dock(self, group_list: list[dict]): + self._icons.clear() + self._dock_panel.clean() + + for i, group in enumerate(group_list): + self._icons.append( + EzdataIcon(self._dock_panel, i * 110, group.get("id"), group.get("domainName")) + ) + + self._icons.append( + EzdataIcon(self._dock_panel, len(self._icons) * 110, "settings", "settings") + ) diff --git a/m5stack/modules/startup/tab5/launcher/components/status_bar.py b/m5stack/modules/startup/tab5/launcher/components/status_bar.py new file mode 100644 index 00000000..189f7983 --- /dev/null +++ b/m5stack/modules/startup/tab5/launcher/components/status_bar.py @@ -0,0 +1,285 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +from ..hal import get_hal +from ..apps.app import AppManager +from ..common import * +import lvgl as lv +import time as t +import asyncio + + +class ItemPowerOff: + def __init__(self, parent: lv.obj): + self._press_start_time = 0 + self._press_count = 0 + + self._img = lv.image(parent) + self._img.add_flag(lv.obj.FLAG.CLICKABLE) + self._img.add_event_cb(self._on_pressed, lv.EVENT.PRESSED, None) + self._img.add_event_cb(self._on_long_pressed_repeat, lv.EVENT.LONG_PRESSED_REPEAT, None) + self._img.add_event_cb(self._on_released, lv.EVENT.RELEASED, None) + + self._update_img() + + def _on_pressed(self, e: lv.event_t): + get_hal().play_click_sfx() + self._press_start_time = t.time() + self._press_count = 1 + self._update_img() + + def _on_long_pressed_repeat(self, e: lv.event_t): + delta_time = t.time() - self._press_start_time + if delta_time > 0.5: + get_hal().play_click_sfx() + self._press_count += 1 + if self._press_count > 5: + get_hal().power_off() + return + self._update_img() + self._press_start_time = t.time() + + def _on_released(self, e: lv.event_t): + self._press_count = 0 + self._update_img() + + def _update_img(self): + self._img.set_src( + get_hal().get_asset_path("status_bar/off_" + str(self._press_count) + IMAGE_SUFFIX) + ) + + +class ItemSleep: + def __init__(self, parent: lv.obj): + self._press_start_time = 0 + self._press_count = 0 + + self._img = lv.image(parent) + self._img.add_flag(lv.obj.FLAG.CLICKABLE) + self._img.add_event_cb(self._on_pressed, lv.EVENT.PRESSED, None) + self._img.add_event_cb(self._on_long_pressed_repeat, lv.EVENT.LONG_PRESSED_REPEAT, None) + self._img.add_event_cb(self._on_released, lv.EVENT.RELEASED, None) + + self._update_img() + + def _on_pressed(self, e: lv.event_t): + get_hal().play_click_sfx() + self._press_start_time = t.time() + self._press_count = 1 + self._update_img() + + def _on_long_pressed_repeat(self, e: lv.event_t): + delta_time = t.time() - self._press_start_time + if delta_time > 0.5: + get_hal().play_click_sfx() + self._press_count += 1 + if self._press_count > 4: + get_hal().sleep() + return + self._update_img() + self._press_start_time = t.time() + + def _on_released(self, e: lv.event_t): + self._press_count = 0 + self._update_img() + + def _update_img(self): + self._img.set_src( + get_hal().get_asset_path("status_bar/z_" + str(self._press_count) + IMAGE_SUFFIX) + ) + + +class ItemBattery: + def __init__(self, parent: lv.obj): + self._img = lv.image(parent) + + self._label_bat_level = lv.label(self._img) + self._label_bat_level.set_style_text_color(lv.color_hex(0x000000), lv.PART.MAIN) + self._label_bat_level.set_style_text_font(lv.font_montserrat_20, lv.PART.MAIN) + self._label_bat_level.align(lv.ALIGN.CENTER, -9, -11) + + self._label_output_current = lv.label(self._img) + self._label_output_current.set_style_text_color(lv.color_hex(0x000000), lv.PART.MAIN) + self._label_output_current.set_style_text_font(lv.font_montserrat_20, lv.PART.MAIN) + self._label_output_current.align(lv.ALIGN.CENTER, -9, 19) + + self._update_img() + self._update_labels() + + # TODO: Update task + self._img.set_style_image_recolor_opa(123, lv.PART.MAIN) + + def _update_img(self): + if get_hal().is_charging(): + self._img.set_src(get_hal().get_asset_path("status_bar/bat_2" + IMAGE_SUFFIX)) + elif get_hal().get_battery_level() <= 20: + self._img.set_src(get_hal().get_asset_path("status_bar/bat_1" + IMAGE_SUFFIX)) + else: + self._img.set_src(get_hal().get_asset_path("status_bar/bat_0" + IMAGE_SUFFIX)) + + def _update_labels(self): + self._label_bat_level.set_text(str(get_hal().get_battery_level()) + "%") + self._label_output_current.set_text(str(round(get_hal().get_output_current(), 2)) + "A") + + +class ItemCharge: + def __init__(self, parent: lv.obj): + self._img = lv.image(parent) + self._img.add_flag(lv.obj.FLAG.CLICKABLE) + self._img.add_event_cb(self._on_clicked, lv.EVENT.CLICKED, None) + + self._label_chg = lv.label(self._img) + self._label_chg.set_style_text_color(lv.color_hex(0x000000), lv.PART.MAIN) + self._label_chg.set_style_text_font(lv.font_montserrat_20, lv.PART.MAIN) + self._label_chg.align(lv.ALIGN.CENTER, -10, 19) + + self._update_img() + self._update_label() + + def _update_img(self): + self._img.set_src( + get_hal().get_asset_path( + "status_bar/chg_" + str(get_hal().get_charge_mode()) + IMAGE_SUFFIX + ) + ) + + def _update_label(self): + texts = ["0.5A", "1A", "NO"] + self._label_chg.set_text(texts[get_hal().get_charge_mode()]) + + def _on_clicked(self, e: lv.event_t): + get_hal().play_click_sfx() + get_hal().set_charge_mode((get_hal().get_charge_mode() + 1) % 3) + self._update_img() + self._update_label() + + +class ItemWifi: + def __init__(self, parent: lv.obj): + self._img = lv.image(parent) + self._img.set_src(get_hal().get_asset_path("status_bar/wifi_0" + IMAGE_SUFFIX)) + self._img.add_flag(lv.obj.FLAG.CLICKABLE) + self._img.add_event_cb(self._on_clicked, lv.EVENT.CLICKED, None) + + self._task = asyncio.create_task(self.update_task()) + + def _on_clicked(self, e: lv.event_t): + get_hal().play_click_sfx() + IconIndicator.destroy_indicator() + AppManager.open_app("Wifi") + + async def update_task(self): + while True: + await asyncio.sleep_ms(2000) + self._update_img() + + def _update_img(self): + self._img.set_src( + get_hal().get_asset_path( + "status_bar/wifi_" + str(get_hal().get_network_status()) + IMAGE_SUFFIX + ) + ) + + def __del__(self): + self._task.cancel() + + +class ItemServer: + def __init__(self, parent: lv.obj): + self._img = lv.image(parent) + self._img.set_src(get_hal().get_asset_path("status_bar/server_0" + IMAGE_SUFFIX)) + + self._task = asyncio.create_task(self.update_task()) + + async def update_task(self): + while True: + await asyncio.sleep_ms(2000) + self._update_img() + + def _update_img(self): + self._img.set_src( + get_hal().get_asset_path( + "status_bar/server_" + str(get_hal().get_cloud_status()) + IMAGE_SUFFIX + ) + ) + + def __del__(self): + self._task.cancel() + + +class ItemVolume: + def __init__(self, parent: lv.obj): + self._img = lv.image(parent) + self._img.add_flag(lv.obj.FLAG.CLICKABLE) + self._img.add_event_cb(self._on_clicked, lv.EVENT.CLICKED, None) + + self._update_img() + + def _on_clicked(self, e: lv.event_t): + new_volume = get_hal().get_volume() + 20 + if new_volume > 100: + new_volume = 0 + get_hal().set_volume(new_volume) + + self._update_img() + + get_hal().play_click_sfx() + + def _update_img(self): + self._img.set_src( + get_hal().get_asset_path( + "status_bar/vol_" + str(get_hal().get_volume()) + IMAGE_SUFFIX + ) + ) + + +class ItemBacklight: + def __init__(self, parent: lv.obj): + self._img = lv.image(parent) + self._img.add_flag(lv.obj.FLAG.CLICKABLE) + self._img.add_event_cb(self._on_clicked, lv.EVENT.CLICKED, None) + + self._update_img() + + def _on_clicked(self, e: lv.event_t): + get_hal().play_click_sfx() + + new_volume = get_hal().get_backlight() + 10 + if new_volume > 100: + new_volume = 30 + get_hal().set_backlight(new_volume) + + self._update_img() + + def _update_img(self): + self._img.set_src( + get_hal().get_asset_path( + "status_bar/light_" + str(get_hal().get_backlight()) + IMAGE_SUFFIX + ) + ) + + +class StatusBar: + def __init__(self): + self._panel = lv.obj(lv.screen_active()) + self._panel.set_size(110, 685) + self._panel.set_style_bg_color(lv.color_hex(0x000000), lv.PART.MAIN) + self._panel.set_style_radius(0, lv.PART.MAIN) + self._panel.set_style_border_width(0, lv.PART.MAIN) + self._panel.align(lv.ALIGN.TOP_RIGHT, 0, 0) + self._panel.set_style_pad_all(0, lv.PART.MAIN) + self._panel.set_flex_flow(lv.FLEX_FLOW.COLUMN) + self._panel.set_flex_align( + lv.FLEX_ALIGN.CENTER, lv.FLEX_ALIGN.CENTER, lv.FLEX_ALIGN.CENTER + ) + self._panel.set_style_pad_gap(0, lv.PART.MAIN) + + self._item_power_off = ItemPowerOff(self._panel) + self._item_sleep = ItemSleep(self._panel) + self._item_battery = ItemBattery(self._panel) + self._item_charge = ItemCharge(self._panel) + self._item_wifi = ItemWifi(self._panel) + self._item_server = ItemServer(self._panel) + self._item_volume = ItemVolume(self._panel) + self._item_backlight = ItemBacklight(self._panel) diff --git a/m5stack/modules/startup/tab5/launcher/hal.py b/m5stack/modules/startup/tab5/launcher/hal.py new file mode 100644 index 00000000..dfc5a2d0 --- /dev/null +++ b/m5stack/modules/startup/tab5/launcher/hal.py @@ -0,0 +1,143 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + + +class ChargeMode: + NORMAL = 0 + QC = 1 + DISABLE = 2 + + +class NetworkStatus: + INIT = 0 + RSSI_GOOD = 1 + RSSI_MID = 2 + RSSI_WORSE = 3 + DISCONNECTED = 4 + + +class CloudStatus: + INIT = 0 + CONNECTED = 1 + DISCONNECTED = 2 + + +class NetworkConfig: + def __init__(self, ssid: str, password: str, server: str): + self.ssid = ssid + self.password = password + self.server = server + + +class HALBase: + def __init__(self): + self._volume = 100 + self._backlight = 100 + self._charge_mode = ChargeMode.NORMAL + + def power_off(self): + ... + + def sleep(self): + ... + + def get_battery_level(self) -> int: + return 66 + + def get_output_current(self) -> float: + return 0.23 + + def is_charging(self) -> bool: + return False + + def set_charge_mode(self, mode: ChargeMode): + self._charge_mode = mode + + def get_charge_mode(self) -> ChargeMode: + return self._charge_mode + + def get_asset_path(self, asset_path: str) -> str: + raise NotImplementedError() + + def get_volume(self) -> int: + return self._volume + + def set_volume(self, volume: int): + self._volume = volume + + def play_click_sfx(self): + ... + + def get_backlight(self) -> int: + return self._backlight + + def set_backlight(self, backlight: int): + self._backlight = backlight + + def get_network_status(self) -> NetworkStatus: + return NetworkStatus.RSSI_GOOD + + def get_cloud_status(self) -> CloudStatus: + return CloudStatus.CONNECTED + + def get_network_config(self) -> NetworkConfig: + ... + + def set_network_config(self, config: NetworkConfig): + ... + + def get_py_app_list(self) -> list[str]: + ... + + def run_py_app(self, app_name: str, once: bool): + ... + + async def scan_wifi(self): + ... + + def i2c_init(self): + ... + + def i2c_deinit(self): + ... + + def i2c_scan(self, port: int) -> list[int]: + ... + + def uart_init(self, baudrate: int, tx_pin: int, rx_pin: int): + ... + + def uart_deinit(self): + ... + + def uart_write(self, msg: str): + ... + + def uart_read(self) -> bytes | None: + ... + + def get_mac(self) -> bytes: + ... + + def store_ezdata_user_token(self, user_token: str): + ... + + def get_ezdata_user_token(self) -> str: + ... + + def reset_ezdata_user_token(self): + ... + + +_instance: HALBase = None + + +def set_hal(hal: HALBase): + global _instance + _instance = hal + + +def get_hal(): + global _instance + return _instance diff --git a/m5stack/modules/startup/tab5/launcher/launcher.py b/m5stack/modules/startup/tab5/launcher/launcher.py new file mode 100644 index 00000000..5776526d --- /dev/null +++ b/m5stack/modules/startup/tab5/launcher/launcher.py @@ -0,0 +1,105 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +from .components import * +from .common import * +from .apps import * +import lvgl as lv +import asyncio + + +class Launcher: + def __init__(self): + self._init_background() + self._create_app_panel() + + # Create main loop + asyncio.run(self._main()) + + def _init_background(self): + screen = lv.screen_active() + screen.set_style_bg_color(lv.color_hex(0x4D4D4D), lv.PART.MAIN) + + self._bottom_bar = lv.obj(screen) + self._bottom_bar.set_size(lv.pct(100), 35) + self._bottom_bar.set_style_bg_color(lv.color_hex(0x008FD7), lv.PART.MAIN) + self._bottom_bar.set_style_radius(0, lv.PART.MAIN) + self._bottom_bar.set_style_border_width(0, lv.PART.MAIN) + self._bottom_bar.align(lv.ALIGN.BOTTOM_MID, 0, 0) + self._bottom_bar.set_style_pad_left(12, lv.PART.MAIN) + + self._bottom_label = lv.label(self._bottom_bar) + self._bottom_label.set_text("UIFLOW2") + self._bottom_label.set_style_text_font(lv.font_montserrat_24, lv.PART.MAIN) + self._bottom_label.set_style_text_color(lv.color_hex(0xFFFFFF), lv.PART.MAIN) + self._bottom_label.align(lv.ALIGN.LEFT_MID, 0, 0) + + self._divider_1 = lv.obj(screen) + self._divider_1.set_size(5, 120) + self._divider_1.set_style_bg_color(lv.color_hex(0xCCCCCC), lv.PART.MAIN) + self._divider_1.align(lv.ALIGN.TOP_RIGHT, -245, 25) + self._divider_1.set_style_radius(0, lv.PART.MAIN) + self._divider_1.set_style_border_width(0, lv.PART.MAIN) + + self._divider_2 = lv.obj(screen) + self._divider_2.set_size(5, 120) + self._divider_2.set_style_bg_color(lv.color_hex(0xCCCCCC), lv.PART.MAIN) + self._divider_2.align(lv.ALIGN.TOP_RIGHT, -810, 25) + self._divider_2.set_style_radius(0, lv.PART.MAIN) + self._divider_2.set_style_border_width(0, lv.PART.MAIN) + + self._label_dock_uiflow = lv.label(screen) + self._label_dock_uiflow.set_text("UIFLOW") + self._label_dock_uiflow.set_style_text_font(lv.font_montserrat_22, lv.PART.MAIN) + self._label_dock_uiflow.set_style_text_color(lv.color_hex(0xFFFFFF), lv.PART.MAIN) + self._label_dock_uiflow.align(lv.ALIGN.TOP_MID, 457, 6) + + self._label_dock_tools = lv.label(screen) + self._label_dock_tools.set_text("TOOLS") + self._label_dock_tools.set_style_text_font(lv.font_montserrat_22, lv.PART.MAIN) + self._label_dock_tools.set_style_text_color(lv.color_hex(0xFFFFFF), lv.PART.MAIN) + self._label_dock_tools.align(lv.ALIGN.TOP_MID, -110, 6) + + self._label_dock_ezdata = lv.label(screen) + self._label_dock_ezdata.set_text("EzData") + self._label_dock_ezdata.set_style_text_font(lv.font_montserrat_22, lv.PART.MAIN) + self._label_dock_ezdata.set_style_text_color(lv.color_hex(0xFFFFFF), lv.PART.MAIN) + self._label_dock_ezdata.align(lv.ALIGN.TOP_MID, -565, 6) + + def _create_app_panel(self): + self._app_panel = lv.obj(lv.screen_active()) + self._app_panel.set_size(1150, 520) + self._app_panel.set_style_radius(10, lv.PART.MAIN) + self._app_panel.set_style_bg_color(lv.color_hex(0xF6F6F6), lv.PART.MAIN) + self._app_panel.set_style_border_width(0, lv.PART.MAIN) + self._app_panel.align(lv.ALIGN.CENTER, -55, 55) + self._app_panel.set_style_pad_all(10, lv.PART.MAIN) + + async def _main(self): + # Set a lvgl object as app panel + AppManager.set_app_panel(self._app_panel) + + # Install apps + AppManager.install_app("Wifi", AppWifi) + AppManager.install_app("AppList", AppAppList) + # AppManager.install_app("I2CScan", AppI2cScan) + AppManager.install_app("WifiScan", AppWifiScan) + AppManager.install_app("UART", AppUart) + AppManager.install_app("GPIO", AppDummy) + AppManager.install_app("ADC", AppDummy) + AppManager.install_app("EzData", AppEzdata) + AppManager.install_app("EzDataSettings", AppEzdataSettings) + + # Create components + self._status_bar = StatusBar() + self._app_dock = AppDock() + self._ezdata_dock = EzdataDock() + + # Start ezdata service + Ezdata.start() + + # Keep app manager running + while True: + await asyncio.sleep_ms(50) + await AppManager.update() From 91ce49bdabf8eaaf6747bf9e27e5332bd000aa95 Mon Sep 17 00:00:00 2001 From: Forairaaaaa Date: Fri, 13 Jun 2025 15:11:48 +0800 Subject: [PATCH 106/322] patches: Add avoid lvgl font redefine patch. Signed-off-by: Forairaaaaa --- m5stack/Makefile | 3 +- .../tab5/launcher/components/app_dock.py | 2 +- .../0018-avoid-lvgl-font-redefine.patch | 95 +++++++++++++++++++ 3 files changed, 98 insertions(+), 2 deletions(-) create mode 100644 m5stack/patches/0018-avoid-lvgl-font-redefine.patch diff --git a/m5stack/Makefile b/m5stack/Makefile index b1fc6ad8..eeaa97e4 100644 --- a/m5stack/Makefile +++ b/m5stack/Makefile @@ -298,7 +298,8 @@ M5GFX_PATH = $(abspath ./components/M5Unified/M5GFX) ESP32_CAMERA_PATH = $(abspath ./components/esp32-camera) LV_BINDING_PATCH_SERIES = \ - 0003-avoid-lv_bindings-compile-error.patch + 0003-avoid-lv_bindings-compile-error.patch \ + 0018-avoid-lvgl-font-redefine.patch MICROPYTHON_PATCH_SERIES = \ 0006-modtime-add-timezone-method.patch \ diff --git a/m5stack/modules/startup/tab5/launcher/components/app_dock.py b/m5stack/modules/startup/tab5/launcher/components/app_dock.py index 5c8cab27..5e284f28 100644 --- a/m5stack/modules/startup/tab5/launcher/components/app_dock.py +++ b/m5stack/modules/startup/tab5/launcher/components/app_dock.py @@ -9,7 +9,7 @@ class AppIcon: - _TODO_LIST = ["ADC", "GPIO", "I2CScan"] + _TODO_LIST = ["ADC", "GPIO", "UART", "I2CScan"] def __init__(self, pos_x: int, pos_y: int, app_name: str, icon_name: str): self._app_name = app_name diff --git a/m5stack/patches/0018-avoid-lvgl-font-redefine.patch b/m5stack/patches/0018-avoid-lvgl-font-redefine.patch new file mode 100644 index 00000000..cf0d0b0d --- /dev/null +++ b/m5stack/patches/0018-avoid-lvgl-font-redefine.patch @@ -0,0 +1,95 @@ +diff --git a/lv_conf.h b/lv_conf.h +index 2e0d08d..5d36bca 100644 +--- a/lv_conf.h ++++ b/lv_conf.h +@@ -421,27 +421,69 @@ extern void mp_lv_deinit_gc(); + + /*Montserrat fonts with ASCII range and some symbols using bpp = 4 + *https://fonts.google.com/specimen/Montserrat*/ +-#define LV_FONT_MONTSERRAT_8 0 +-#define LV_FONT_MONTSERRAT_10 0 +-#define LV_FONT_MONTSERRAT_12 0 +-#define LV_FONT_MONTSERRAT_14 1 +-#define LV_FONT_MONTSERRAT_16 1 +-#define LV_FONT_MONTSERRAT_18 0 +-#define LV_FONT_MONTSERRAT_20 0 +-#define LV_FONT_MONTSERRAT_22 0 +-#define LV_FONT_MONTSERRAT_24 1 +-#define LV_FONT_MONTSERRAT_26 0 +-#define LV_FONT_MONTSERRAT_28 0 +-#define LV_FONT_MONTSERRAT_30 0 +-#define LV_FONT_MONTSERRAT_32 0 +-#define LV_FONT_MONTSERRAT_34 0 +-#define LV_FONT_MONTSERRAT_36 0 +-#define LV_FONT_MONTSERRAT_38 0 +-#define LV_FONT_MONTSERRAT_40 0 +-#define LV_FONT_MONTSERRAT_42 0 +-#define LV_FONT_MONTSERRAT_44 0 +-#define LV_FONT_MONTSERRAT_46 0 +-#define LV_FONT_MONTSERRAT_48 0 ++#ifndef LV_FONT_MONTSERRAT_8 ++ #define LV_FONT_MONTSERRAT_8 0 ++#endif ++#ifndef LV_FONT_MONTSERRAT_10 ++ #define LV_FONT_MONTSERRAT_10 0 ++#endif ++#ifndef LV_FONT_MONTSERRAT_12 ++ #define LV_FONT_MONTSERRAT_12 0 ++#endif ++#ifndef LV_FONT_MONTSERRAT_14 ++ #define LV_FONT_MONTSERRAT_14 1 ++#endif ++#ifndef LV_FONT_MONTSERRAT_16 ++ #define LV_FONT_MONTSERRAT_16 1 ++#endif ++#ifndef LV_FONT_MONTSERRAT_18 ++ #define LV_FONT_MONTSERRAT_18 0 ++#endif ++#ifndef LV_FONT_MONTSERRAT_20 ++ #define LV_FONT_MONTSERRAT_20 0 ++#endif ++#ifndef LV_FONT_MONTSERRAT_22 ++ #define LV_FONT_MONTSERRAT_22 0 ++#endif ++#ifndef LV_FONT_MONTSERRAT_24 ++ #define LV_FONT_MONTSERRAT_24 1 ++#endif ++#ifndef LV_FONT_MONTSERRAT_26 ++ #define LV_FONT_MONTSERRAT_26 0 ++#endif ++#ifndef LV_FONT_MONTSERRAT_28 ++ #define LV_FONT_MONTSERRAT_28 0 ++#endif ++#ifndef LV_FONT_MONTSERRAT_30 ++ #define LV_FONT_MONTSERRAT_30 0 ++#endif ++#ifndef LV_FONT_MONTSERRAT_32 ++ #define LV_FONT_MONTSERRAT_32 0 ++#endif ++#ifndef LV_FONT_MONTSERRAT_34 ++ #define LV_FONT_MONTSERRAT_34 0 ++#endif ++#ifndef LV_FONT_MONTSERRAT_36 ++ #define LV_FONT_MONTSERRAT_36 0 ++#endif ++#ifndef LV_FONT_MONTSERRAT_38 ++ #define LV_FONT_MONTSERRAT_38 0 ++#endif ++#ifndef LV_FONT_MONTSERRAT_40 ++ #define LV_FONT_MONTSERRAT_40 0 ++#endif ++#ifndef LV_FONT_MONTSERRAT_42 ++ #define LV_FONT_MONTSERRAT_42 0 ++#endif ++#ifndef LV_FONT_MONTSERRAT_44 ++ #define LV_FONT_MONTSERRAT_44 0 ++#endif ++#ifndef LV_FONT_MONTSERRAT_46 ++ #define LV_FONT_MONTSERRAT_46 0 ++#endif ++#ifndef LV_FONT_MONTSERRAT_48 ++ #define LV_FONT_MONTSERRAT_48 0 ++#endif + + /*Demonstrate special features*/ + #define LV_FONT_MONTSERRAT_28_COMPRESSED 0 /*bpp = 3*/ From 8d34ffe84b72b37e56b6038e1d1242a8cee8d76f Mon Sep 17 00:00:00 2001 From: Forairaaaaa Date: Fri, 13 Jun 2025 15:53:48 +0800 Subject: [PATCH 107/322] fs/system/tab5: Add startup image assets. Signed-off-by: Forairaaaaa --- m5stack/fs/system/tab5/icons/adc.bin | Bin 0 -> 10837 bytes m5stack/fs/system/tab5/icons/app_list.bin | Bin 0 -> 10837 bytes m5stack/fs/system/tab5/icons/gpio.bin | Bin 0 -> 10837 bytes m5stack/fs/system/tab5/icons/i2c_scan.bin | Bin 0 -> 10837 bytes m5stack/fs/system/tab5/icons/indicator.bin | Bin 0 -> 1372 bytes m5stack/fs/system/tab5/icons/uart.bin | Bin 0 -> 10837 bytes m5stack/fs/system/tab5/icons/wifi_scan.bin | Bin 0 -> 10837 bytes m5stack/fs/system/tab5/status_bar/bat_0.bin | Bin 0 -> 10386 bytes m5stack/fs/system/tab5/status_bar/bat_1.bin | Bin 0 -> 10386 bytes m5stack/fs/system/tab5/status_bar/bat_2.bin | Bin 0 -> 10386 bytes m5stack/fs/system/tab5/status_bar/chg_0.bin | Bin 0 -> 10386 bytes m5stack/fs/system/tab5/status_bar/chg_1.bin | Bin 0 -> 10386 bytes m5stack/fs/system/tab5/status_bar/chg_2.bin | Bin 0 -> 10386 bytes m5stack/fs/system/tab5/status_bar/light_100.bin | Bin 0 -> 10386 bytes m5stack/fs/system/tab5/status_bar/light_30.bin | Bin 0 -> 10386 bytes m5stack/fs/system/tab5/status_bar/light_40.bin | Bin 0 -> 10386 bytes m5stack/fs/system/tab5/status_bar/light_50.bin | Bin 0 -> 10386 bytes m5stack/fs/system/tab5/status_bar/light_60.bin | Bin 0 -> 10386 bytes m5stack/fs/system/tab5/status_bar/light_70.bin | Bin 0 -> 10386 bytes m5stack/fs/system/tab5/status_bar/light_80.bin | Bin 0 -> 10386 bytes m5stack/fs/system/tab5/status_bar/light_90.bin | Bin 0 -> 10386 bytes m5stack/fs/system/tab5/status_bar/off_0.bin | Bin 0 -> 10386 bytes m5stack/fs/system/tab5/status_bar/off_1.bin | Bin 0 -> 10386 bytes m5stack/fs/system/tab5/status_bar/off_2.bin | Bin 0 -> 10386 bytes m5stack/fs/system/tab5/status_bar/off_3.bin | Bin 0 -> 10386 bytes m5stack/fs/system/tab5/status_bar/off_4.bin | Bin 0 -> 10386 bytes m5stack/fs/system/tab5/status_bar/off_5.bin | Bin 0 -> 10386 bytes m5stack/fs/system/tab5/status_bar/server_0.bin | Bin 0 -> 10386 bytes m5stack/fs/system/tab5/status_bar/server_1.bin | Bin 0 -> 10386 bytes m5stack/fs/system/tab5/status_bar/server_2.bin | Bin 0 -> 10386 bytes m5stack/fs/system/tab5/status_bar/vol_0.bin | Bin 0 -> 10386 bytes m5stack/fs/system/tab5/status_bar/vol_100.bin | Bin 0 -> 10386 bytes m5stack/fs/system/tab5/status_bar/vol_20.bin | Bin 0 -> 10386 bytes m5stack/fs/system/tab5/status_bar/vol_40.bin | Bin 0 -> 10386 bytes m5stack/fs/system/tab5/status_bar/vol_60.bin | Bin 0 -> 10386 bytes m5stack/fs/system/tab5/status_bar/vol_80.bin | Bin 0 -> 10386 bytes m5stack/fs/system/tab5/status_bar/wifi_0.bin | Bin 0 -> 10386 bytes m5stack/fs/system/tab5/status_bar/wifi_1.bin | Bin 0 -> 10386 bytes m5stack/fs/system/tab5/status_bar/wifi_2.bin | Bin 0 -> 10386 bytes m5stack/fs/system/tab5/status_bar/wifi_3.bin | Bin 0 -> 10386 bytes m5stack/fs/system/tab5/status_bar/wifi_4.bin | Bin 0 -> 10386 bytes m5stack/fs/system/tab5/status_bar/z_0.bin | Bin 0 -> 10386 bytes m5stack/fs/system/tab5/status_bar/z_1.bin | Bin 0 -> 10386 bytes m5stack/fs/system/tab5/status_bar/z_2.bin | Bin 0 -> 10386 bytes m5stack/fs/system/tab5/status_bar/z_3.bin | Bin 0 -> 10386 bytes m5stack/fs/system/tab5/status_bar/z_4.bin | Bin 0 -> 10386 bytes 46 files changed, 0 insertions(+), 0 deletions(-) create mode 100755 m5stack/fs/system/tab5/icons/adc.bin create mode 100755 m5stack/fs/system/tab5/icons/app_list.bin create mode 100755 m5stack/fs/system/tab5/icons/gpio.bin create mode 100755 m5stack/fs/system/tab5/icons/i2c_scan.bin create mode 100755 m5stack/fs/system/tab5/icons/indicator.bin create mode 100755 m5stack/fs/system/tab5/icons/uart.bin create mode 100755 m5stack/fs/system/tab5/icons/wifi_scan.bin create mode 100755 m5stack/fs/system/tab5/status_bar/bat_0.bin create mode 100755 m5stack/fs/system/tab5/status_bar/bat_1.bin create mode 100755 m5stack/fs/system/tab5/status_bar/bat_2.bin create mode 100755 m5stack/fs/system/tab5/status_bar/chg_0.bin create mode 100755 m5stack/fs/system/tab5/status_bar/chg_1.bin create mode 100755 m5stack/fs/system/tab5/status_bar/chg_2.bin create mode 100755 m5stack/fs/system/tab5/status_bar/light_100.bin create mode 100755 m5stack/fs/system/tab5/status_bar/light_30.bin create mode 100755 m5stack/fs/system/tab5/status_bar/light_40.bin create mode 100755 m5stack/fs/system/tab5/status_bar/light_50.bin create mode 100755 m5stack/fs/system/tab5/status_bar/light_60.bin create mode 100755 m5stack/fs/system/tab5/status_bar/light_70.bin create mode 100755 m5stack/fs/system/tab5/status_bar/light_80.bin create mode 100755 m5stack/fs/system/tab5/status_bar/light_90.bin create mode 100755 m5stack/fs/system/tab5/status_bar/off_0.bin create mode 100755 m5stack/fs/system/tab5/status_bar/off_1.bin create mode 100755 m5stack/fs/system/tab5/status_bar/off_2.bin create mode 100755 m5stack/fs/system/tab5/status_bar/off_3.bin create mode 100755 m5stack/fs/system/tab5/status_bar/off_4.bin create mode 100755 m5stack/fs/system/tab5/status_bar/off_5.bin create mode 100755 m5stack/fs/system/tab5/status_bar/server_0.bin create mode 100755 m5stack/fs/system/tab5/status_bar/server_1.bin create mode 100755 m5stack/fs/system/tab5/status_bar/server_2.bin create mode 100755 m5stack/fs/system/tab5/status_bar/vol_0.bin create mode 100755 m5stack/fs/system/tab5/status_bar/vol_100.bin create mode 100755 m5stack/fs/system/tab5/status_bar/vol_20.bin create mode 100755 m5stack/fs/system/tab5/status_bar/vol_40.bin create mode 100755 m5stack/fs/system/tab5/status_bar/vol_60.bin create mode 100755 m5stack/fs/system/tab5/status_bar/vol_80.bin create mode 100755 m5stack/fs/system/tab5/status_bar/wifi_0.bin create mode 100755 m5stack/fs/system/tab5/status_bar/wifi_1.bin create mode 100755 m5stack/fs/system/tab5/status_bar/wifi_2.bin create mode 100755 m5stack/fs/system/tab5/status_bar/wifi_3.bin create mode 100755 m5stack/fs/system/tab5/status_bar/wifi_4.bin create mode 100755 m5stack/fs/system/tab5/status_bar/z_0.bin create mode 100755 m5stack/fs/system/tab5/status_bar/z_1.bin create mode 100755 m5stack/fs/system/tab5/status_bar/z_2.bin create mode 100755 m5stack/fs/system/tab5/status_bar/z_3.bin create mode 100755 m5stack/fs/system/tab5/status_bar/z_4.bin diff --git a/m5stack/fs/system/tab5/icons/adc.bin b/m5stack/fs/system/tab5/icons/adc.bin new file mode 100755 index 0000000000000000000000000000000000000000..5b328f67d3d4f458953f401c1b5c3abd8133521d GIT binary patch literal 10837 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 huSFXN*#@=?b!Z*?2dO@rm91 literal 0 HcmV?d00001 diff --git a/m5stack/fs/system/tab5/icons/gpio.bin b/m5stack/fs/system/tab5/icons/gpio.bin new file mode 100755 index 0000000000000000000000000000000000000000..21309d66cf87d2f6ea088f3d922e488b3210f170 GIT binary patch literal 10837 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 literal 0 HcmV?d00001 diff --git a/m5stack/fs/system/tab5/icons/wifi_scan.bin b/m5stack/fs/system/tab5/icons/wifi_scan.bin new file mode 100755 index 0000000000000000000000000000000000000000..ba576d0c4624a64b8dd7ca04d84541a3ca953491 GIT binary patch literal 10837 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 literal 0 HcmV?d00001 diff --git a/m5stack/fs/system/tab5/status_bar/bat_1.bin b/m5stack/fs/system/tab5/status_bar/bat_1.bin new file mode 100755 index 0000000000000000000000000000000000000000..428bbcdfd2ae29cd4012e6c11dab0b0f836b29f5 GIT binary patch literal 10386 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 H3nKp)OV5BT-mJNMq*6_#DvWs@&o&-d-lnYnZ4 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 literal 0 HcmV?d00001 diff --git a/m5stack/fs/system/tab5/status_bar/light_100.bin b/m5stack/fs/system/tab5/status_bar/light_100.bin new file mode 100755 index 0000000000000000000000000000000000000000..5098cdfdff7173e3017459f722a4cdafb8bdd223 GIT binary patch literal 10386 zcmd^_`CAmp8OPf}XoedUR1gG_TP`_71r>2|Spk(5j}74N!m>eD;7H(w8Jvea+0^qe_10Te-%nL_UESKp zLlEkOQu4zehuw02-Rg($G4q7>yI6v3vJ! zgolUY<;$0FadAOWQ4sG`Eopc`V^NhU&j3T^UgI1ppUj>WZ2 z3-S2zV>C20VCvMVxPJXQ8XFrC5fOnxGG;e!+`znf^KkFpJ%ogWpt7A4aj2XCn`!*&_ zn1D5F*5J&UGiYjRLS0=Qo;-Pi*49>BxpD>L#*IT}W+q;|c!4cjwjeGp4hIh&L{wB1 z?%cV9l9Cb}IB)>|{{HCRy*nmOoQU}Ncnlpn6b~Oh#O>W{v1d#lv{a8oQc@Cj?b?N9 z%a$Q0CkIE49KowsuMis>i>+I?;_ltM2o4U0mzNiso12l6l7gC=8f@FP4gLG~N6(%; zk(!!{J$v?`tgH;1Hf=&+U?5hmT!}-64q^ND?HDj%05)&lj9a&EAv81;t5&VT`Sa)D z>+6eCr%u7$-5t5PxkyV(Ls(cC#*ZJ5)vH(I!i5W{udhd9Vj`-mtFd9j23)ZVB! z#-c@wke!{4nKNf%`t<2IckUcSQABxpIf84g8k`IEMQpZ{-ApI?&Y@Zq5s z*>PxS_Y(U=M(6eYhkYd_nw#0x8q4-c z`!vo8!b1B>I)ipW5I*WUTD~AGaE#WE+|{M9Sn93=BS~oI9ix#K3OTshq7ATCJ}U@` z*1j5FYY$EH=f+uPvn^VcFm0k%X}%z|X+7e|zDI{yxoDEpLdhGJ6g)ca+aNN;AAPJ* z`nK=9Q5uJsO$xPWT`a|EcCUbCzb8sVVl+~{>orMnBq<^s*ZNq>VVbx-fJpt?@Uh7H zLn$oY0H%zj-F*|q>3Kvf#Yjx@)y^aN$6SmGNPS!E;UyWCU)nywsD>#1CQcB#8)s4P zbW(Ay@mXb&x3W5*i4}jIZ<&@cvANBkPTi-Pg1IxI4SUAtq$hd5=fP9Lc0^P4D|Q6w zC<}W&w|x>w(r%XJW^=u%P~y%ZGKxiUS?+1ZZRLz zF|49Ee<8Et{;oiG^iXn*j1QotF-A_WznNwzG~thvj40gKd0JsQWqcDtl!7fZVSPu> zuG2JMAByNjTOFl>3BpL;5reqXG`2luGDQ~+ZJq8!vS1||TW@A_C|z@QuD2k9D3W)} z?=-kg7IkY&qB*|a`cuHKWVL}BTBJ3@lI|>xo?tmqvd#((t#AA1gX+0yO(tMz6}3{6 z29=N9n#!DeNxnbN?n#H$64;g*lT;BNUQK7LDrga^6sxQ%D2iEwnXj)m^?pHY%iCS} zE|w+#d`t4fSF=O}E3C-K`mJHa@T6A-e)O=yN-=7DB8yF?iA0iEN)0I#s+SxkOqC0N z!b`ANrY)komMh(x`^g?XEg^#W(%BtkA+gzFlhw+n$?_zMCB9iw;lKt1nisojF`8NK z9ILD;DvnF=_RjeF5Oek+`T3gc(ZAj8lgQ45TI#Qi-SF8G??IX(%1t3u?ovO#H5<^> zHUCQT1A3}G%q}DD!R_x|5@eBij7%G2{z2uZ{iNk(sh1O z{QW-dU^=pjTwE91MH5X0P5e$7Fg#h?6oZsRaq#T+o}6ou3OR8ke#G#U9?D9O`qF>1 zC0k>4hL%G5ouN_DB>o@>5ua&1NL1z?@?7_x&X5I7R4k~H5x__h@ z!;Rn17}=q$QUG9 zH$?t|Fq7-au}hLwizGj%@YpuZN|z)FZMNQPkMtKAe`2>!N?=^&B1-rN@&(8KmN93m1CXc&r286}|3%c^(@&%zw|DS>& d_+{F@1KT^KeBgAkJd!Nx!v9~Q#{Nv~{{S-$(Te~8 literal 0 HcmV?d00001 diff --git a/m5stack/fs/system/tab5/status_bar/light_30.bin b/m5stack/fs/system/tab5/status_bar/light_30.bin new file mode 100755 index 0000000000000000000000000000000000000000..8966844306140f63123e54dab999aeb13b70c681 GIT binary patch literal 10386 zcmeI2`BxU#8OH}-hv5Z*7kp75f)EzNEvc$G@K3LEq<1_~*rcAUZl4*REYd zLqh}Z+_{5@hzRW4w-0;v?7_i<2XW)Z4dmtJ;q2M7=2p!IC9Q(AL(5)vH(I!Gi~wKYu=2T3XQ9*oeZy zLUeR=;Of<@sI9Gq%jH6Oc{vUpI)uA-@8aailQ?kT0M@Qui>+I?B0M}CnVFd=DJj9_ z-Y$GKH4ZyInT*`rTy%GLW7)E0SigQf&Ye4l`1p7%U%njMw{ORsIdd>`=1lbV_97!A z0~Ry($cVQ-8v*DCZevc4xOEyC@U+&xN+k!d-iPP&{dU`Nn!UW8iF#{D96{xSTM^;uAva_>MSy_o&+rN~Do;(2m|N9U1fwBaA z^RKVGHiHbgmk)_#VKEKg_VnSX1xaMo2PTlIX&xADWL986RK*-ctSW8Jm;k~g8(R9z zK*H>3s{;zNp;b8slQu4}FdN?z29*{zsI&k-Hk+OduxtN|wA^GrJHhu_;-84vGG&v0 zH5FQrGuRtT*Gd00z7m9w{HqxV+TR3W^Wf3S1mVN}(bC9SSP89)yZz`%Li=m~XygSz z4MBFaP;cd4LCEvAg_W2n!uZAj?w&{0gfi36wCK!*l=EXG^rxi^R|J}q}psE^)u7Q zqUtBhuy_-grzidPO_HKrL~OK~m|}BBTESFWYj~8>rP|fU49G|j#ktnBXab8dFV^I} zQa)%>T+#>HIEgD+zwYhNI`cLC(%5fl87`$TBFOtS3lWOR7fq*7GJYbhDF`d;>~{i1 zI!K#tw)0Jgl9qf&MbT(tB|lX4HI?+s&>WSc^e~bx7;7ZxEeefZgPB`2M|Jcyjp-Vd zHStd#=+9loYC6kH&|asEcM~T&zVOh5g;oQEG6zn3>lZSkA(zNxYBb>|zMkEHX(L9; zh(!68i*+zT_%Zdu{4!wLn=+!1ijuE&x-c)=oFrQn8l7)WXNo-M{G4yE5Wv(Citv{@ zm>?{&qxFBj#maycs%|4kD+oNTngHG{R&{^C(Wa;#n?QBXjZabmzCozlJz_+KGUk3o z%ICAfi9V$?zrgaIBhwlb$Ns?M;kLa8ow{^G@_V5x6Uq7uD#u5FV$Fr&`he+ zZAla(`F&Q^-JcX&dNggTxzIHQj>8IR}h<&VDncq}Z+Q;&$6)dmK zX{yCY3zK(Aa=T+>$Va4{PBuA1!h}_{0lk!v{3NyV8?=;N6zwQ_)S@(}>YR}#RfG*6 z9+shuf!9cR855zg*zwDed2~;p{uF0~k?L-)isz(|qPmpEmSz*0zUA*pdAipaAm!7D zd9C%@r9eJ*maDXK=iil-4b3H&|5VSX=>lSHG*~?bMOxczJa0QTG{;m@AIV1IQ;8wS)ufE?;ajgs!8PjcJ@2tr?2*XlVIk&d0vb zr()NxUFfe*!@Ya=uxHO6+`M@cj~+e3)2C1I@Zm$8Jb4lc2?@A=|2|HiK8?u8NTj8u z!Dh4J$dMzMH*X%UU%!qkSFYg1i4*wulRFsrvKjxn_)lEBb`3LT%s^OJ820bqkKMa> z!{Kn?_U+raeEBjC9Xf;?H*O#|Hy7v5ox|hDk1;SXfJKWIp{S?`ix)3OOG^ue4I75> zsB;0G$11*1I5L~ z$jZvX<-TrwGbIK)XHG&+P7Zo{da!)?a+H;oVf*&&*sx&(&YwSz*w|R?*s%k1=g!5f zS+mgB*N61D6rU0oeku3U+{ygW2FH^c3AW7Vov=;-LcrcIlWl9GbB zxHud;b`0U+;W%*M0D^*oP+ne+fPetx=jUU~mMwVj-~m!oQ?Y*idc?=aWBT;z=<4c1 zK|ulL%$bAi>}+h_ycuiPu0?coH0tZ?v3m7tIGs)`S+WGBrKKn=EX3^Dv%#_~E?&Hd zWy_Y~?Afz$xm-AW_%M1)Loj^!aP;=}qNJn*RaI4JY-~hkW+tkut8r_`*IZZRI^_TV z{L}{g6!6`@zLGYBbgh?9@nljlOy85*aP&_qNDuceSh%J3cX~9EE^Yd%7Xh}bZ_P`h)g&hG`!<+M5jXiEp%&5HZcUYQB+|Sd0eL?rxFYp~2S=)vvk1)ouG%&@ z8vFBDg6kMzpe33TI9QBWQvUiJ8_PCT5?qZ^M4fksnZbnjHZvrg$I4ft$S`UdvGjus zh8Zpv^uG}VHd1fqEV8^N=Yhtyto&H+meFOKzpne~n%89giE*WJL6zefq2x?0rcmDG ziKb8}D<*-`^y_!5R{~x-Kpt+E<4u8Lmlp{r`jb)XR8|_~UxsF@9j%0se8D&agu=wr z=rvFbi-UOwGTS|PI-<|A@j1n(TCUS|dUZ<0wSl-nwZOOKuwLt(;dl1u7m%MbIGa>&gm z_Hlxmh{{p&_B9S0?c={;QhGRa5v8C(W#tdy0#$ zLEU_x)i?`9L7NN#A(@YEtpLAKm^y`9Pgyp*@pX<~8K-qqmIYc+I6v3|MVNJLtc@FU zB)$Iq_X>-b@%b@!rMNCH(t?7*lLL|^mhgR2=dLo?-l5^}9C{t)F*0#3k(pAC-mMZh zD9lw&{-f(WbVqu(q^`|)MPqHVHi&!5*anl$zv3x*h>vS`Y#@~6FDx7SINV;jNhR4bxCE@_eNnxhia+8 zDhO`UYN@EmNa{kS%4&xvVa;Y2E`5JLj z??`x}Ha1I|du-UBHL~!r`8acFghtx!&wroXXhV=g_ eFwE#e%{FAPfsy+xW8>z_A^iU(bb9`7UH$_uKhlc; literal 0 HcmV?d00001 diff --git a/m5stack/fs/system/tab5/status_bar/light_50.bin b/m5stack/fs/system/tab5/status_bar/light_50.bin new file mode 100755 index 0000000000000000000000000000000000000000..267a441afc35f05874942c4f0875762a414ba18d GIT binary patch literal 10386 zcmeI2`CAmp8OIyPVP?1)K>>x8bLAE}!U7(^24%SuU}2ZQ?&7)&EW}k4b>k+Qcmzpe zG}#z*#e|Ip6N5aD_k~Bi9`*6Q?>j%|FZiaqx_kPbp22x|l6_LoLwD6%)!$FO)phm| z?!z!$Oda{70jIhJ@r38L(lnDH8`iuf&TNFECJ( zfCmpA;Le>pc>er3_Uze%ef#!d^5n^Q`0yc4pFWM-w{PRrsZ)6JTUx>{(p9b`Af2b`Qf}HsN2F|A`A1E}*}^ANBS12nYzk!Gi~} zckf=DJ9iE?n+n`@bEBh+_-@yOO_xvHy2BnE=6;5 zGscV=gY4{Vl$V#|*|TT3bmo{=W04`s?jH;?C1O)}* z{Q2`JE-uF5!-uhB#}3ri)*>k>33+*WNJ&Y-)&3rQGdmo+=gmS|S{izLd(qg~h?Of> zV*2#y*tKgHo<4nwO`A3$A|e82vl&A}Lzq8*KHA#akeHZ=&6_tPCME_UAt9)!sKDyg ztC5kBfu^P=gocKqva%9u)~rECM+dfS*@C#ZI7CK9;>3v)2n-Cwp+kpYwOUbFScuHb zOgwt@2=Vdp*tl^cqN1WuU0sdt?rvmdWuddP6RD}G*t&Hq)~{cWu&^-H)YM?@+O=4= zY#9m)3NSc0h@6}pw70jzVzFSwiWLYB4#v@=N6}Z{hp}VFqOY$H`T6-MDJel+T^*8> zlTlh)iaSGJv)z;Jr2qdLK^t&Wz_P z<{mAM?2Eb3$+_!BOA^|kdo*&0;DeVDEkLQflVLKHHb2dYL3nQh%Vsm8ZDHPhL$3WP z!~9)tv7{w^5TY=#Nr4taP9I$Gaqe3YY2uDlnbIfIc6?W6u*@Xqhh({lrUaVHBj86r zCAd6!#RJF7mD32!+#LV%*ZE>((wBRj-;SQ^!q$3S%Jhp02?z)Y z5{htlIYN@656j9XHBsBN1lGEd#KqIytlx+%hWUi#2iVy~&KxXXXLfQjd0-|>5yZq6Hj5vMU|(0#?D&a?A#p3Thz41CDc|&_aN;gP6r#0 zOMaJT(#s5DOJ$c-;+wcKu?0D8f4bG8G#l?6eU6vnZxxLR3H5)TOH*%>{MoW>^G|*| zE`zQKbdylf92hu$e3Yz+xaZ&Ggq!@x<4i;d&7V?Z;NkpdY?FY4K+Y8~RL2uJgBbPiWr(!vgJ zCR?tTwwTza7|xSjHXAzxtkzhYt&6JwE2gvOqKJ^M*#do1TuU!&Qq(|NK-hD|*lgau zKE9+^#&9t1tD3B+BJm|F&HPd9DR#OBZP1^$gb`pRLx2zZqgz3p+by4Efd#BER{|E6RVwcNBXoNuDZKKvzX@pN!U|{zo)bL zm(-G@xVd(Z51lDWOx^Uw`}yxx+8ra^6hA?JniR{9gvK5QoqXNPB2&LkHb-;o)Ikq} zI&!QVZ;;B^wcnuXeF+BL_eNn_hiXTIy-jel);p4th#)T9=NRqqn)Oeff~={S;1b12 zMzE_#fMYe#^X)^Tr09Hwv+VMQ^~)rMq6ubmx1eCX|4lT_&$a3*yKHgpn|%6nSyZ>6bK2)yifxEi6S-(JYDqd>j^h89 JP~Nxe@*hDA)QbQB literal 0 HcmV?d00001 diff --git a/m5stack/fs/system/tab5/status_bar/light_60.bin b/m5stack/fs/system/tab5/status_bar/light_60.bin new file mode 100755 index 0000000000000000000000000000000000000000..6c440e7ad583ad3d110d98a9fc2170b96e05aeee GIT binary patch literal 10386 zcmeI2`CAp&8OH~&m&;|nY+(~*S7ej06j@>b#TfPks3ZiMKuCZrG}^i$ZLPMc zMI*MPT5K)S;=bTgw?}>4_kHir{R_T5b7tnwa_8Q;7w*H;=IME!J9Ex^&U`=TJ^KuV zxHC*AQ$v0<;8b@z%0d{teEAX!7A(N<@Gu^a4C2+RSLjV=z=py4bR77i94}tHz(7?z z9z1w}=g*&G-@bjgbLS2sBO~$f;X~}%vj?Y6ox+nRPvGb0hyMP4#K*@&r_nu9{;-hPh7Zg0kyTYm@r`io;`a8Z*Olre*74F_wL2n zvu9y68gcjTT^u=b1h;P8LTYL%TwPtUYSk)?jEvyMjT>0JcrmiGvvKg?K`dFa1THQv z*uH%`8X6j~W5*6OHa243ym`pX%tUEvDK1^QgmdT4Vb`u*xO(*}_V3@1`}gnT%$YMd ze*8F2o;-<-8#m(GwQKP5@C3<>#FfcHHsHiBElD4~e@gf#2T7;&iCd9K)~$$*jm725mk}Nwj3v) z@bU4%p+kq@=H`Zif&!$cr{mG1M~I7y!=_D}5D^i9%F0S~cXwmy(xu4A$UsL&2a=PM z(bCd_ZQHhC!-fqwfBrmzgM+bb*)rti&WY~;7Od^YuYN#@| zVd$E-$cS}LAX7>`2sFPe=YXh+I*M3%+Sln$gc&qwLGL&drbX*_Dole`qZ?1!51b3r z@GW6nX};r1b8@$7w4{?=`(LD`ylrPA*j`KQ6VaDUAGEI|M{_Z{DzS8(v`^#j4D*?N zC7nSVW|)rgqm?pDyJNIyavtWw=bXC^v?QVZ!!a6pk>G=i7R_6!Jj5`mN}H$Z!XWgU zz%m-OX!Xo{;d1RZhS@E*7>W`;@>iJHq(F-#Z#-OZx9wXoY2uC~nNsWYzs;2y44;s9 zh61^XrUaVZGhopQf_p<=ao@>u$}3_vXogRJ z+a?Z6|H9^h>UCRQ7xh00ds9rvVjRPRyrT=jgEQHpNfZia5=Uw1eP4Sg;G|u|>83f~ zBq+niH+U4iCBEw^%~Xkxg{FHqR0<>U%or7f#Kh6))m3ndrl^jjph|Qe&q~~BhHfya zloL&3pk+|T$CKvh{%oc(Ypo3siX1(yBAaD2r4X4ZB8^#XtM5ild(DqUOk%4XFM%=4 zD(Z#$Wz@8C7Lh|mJ!Q0?TkJ?8nWNG9W;8~!bI#8B7D5n}{A&3Z8LnGYSH?J+tt6gS9WcV<}H@A z+@;i(P0t{okvN@fJg(q9nn^E(#FoTvsl+#NXJQL-+FtaiMQKLfIr8I_S7jQgr4E2>B=XQi1waio~&9<<5&x+Tm2H!=nIV10Hg zXa??}P*cwCrv^iEejH0L2~!r6rg=}?ytv8cD!{aTGw9ekM?%~eO*u1v#l@%SrQ#NG zf#&As73C6TX7S%AEsDzwy7#F&9EV>0Yr^M1#yeJ zT{MGk{tn?x8I)(`@Gq$)M{#iP4z6t$B~t$QRsPJpG?RIz>tX_v#B5n?G_*A0_Y9ee zERjx@ek&jUIGRIm9UriboGSy!65(X0F{$^UMSlqftNpf~&B;^!WWYn;BDh(IRuv_E;1rlpa^&KZeY!~Z%IbI95#EFN#0MMuM?d7M8XjT zig4GYMf@!84~l9R>-CC1zW9Xclg(pPbWK>51sFM-XnmcDm-|7q0`>Z2i6o6GjtG3V z8N)}kX-Zv6`_$5^W^oeD!Z$URuuh8eP5V2Sr4DJyzgk+=ESzr+u3QSC2)%xe!ek}s z3|f*&aY(ho5?WH(dn9dbzNsnc6jMU247I6nIFlnZtLn;(|0bGzg;Lhk@w9E0^PGS{o(|+OPj(w9mlyHmO!i(&cg-|G$LtzF(LB0Gx@`$^ZZW literal 0 HcmV?d00001 diff --git a/m5stack/fs/system/tab5/status_bar/light_70.bin b/m5stack/fs/system/tab5/status_bar/light_70.bin new file mode 100755 index 0000000000000000000000000000000000000000..ff3cb79ecac16913f5c2f9911a019c044b1297bb GIT binary patch literal 10386 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 G_J07T_|rE4 literal 0 HcmV?d00001 diff --git a/m5stack/fs/system/tab5/status_bar/light_90.bin b/m5stack/fs/system/tab5/status_bar/light_90.bin new file mode 100755 index 0000000000000000000000000000000000000000..865fbc48ba37160ea4b78f18ece71b7b4e0dab66 GIT binary patch literal 10386 zcmd^_`CAmp8OKXT7={}`mqU>ImSaH$1@Fam1wUzX0UO^M@BY4E?Q6KO7zVmbbf^Vv-yQaIR8)gQ1$dgSy&vd=@R`vJOZ*^65bwjWR zAvL6g{&+%RFv0%)`|;+@8`Ra+;aOWN&Mv9O+qZAACX;}r*3wKInN@_>uU})wgg9*8 zycrP@5iprdC@d_*^XJdet5+`^I&=uHUcG{giwol7;&AfhNj!f17`3&vc=F^4?%%(U z$&)9;$HxbE@7~3~Up_?Jx25>kwSVI9;lprsbwze|HqM?qz!=;sT z@ZiA%tXZ=LQ>RYF<;$1R($az*J9Z#4G7<%}->zJ_g0{9c)YsSJ_U+pU4Gl$ESsCu! zxr6BFXcQF{VeZ_yIC}Ibs;jH9b?a7?mzQJNvSm1a{5VFA9EsVpXCo^s3o~ZSz=R1C zuypBCoH%g;t*x!7sj0!OTeooJ$PtVmKOXbu&BOKU*YWh}Q>|{{EOWX%do>k}!DiVBEWR4>vZqV9S_b?5`M()YMdL+O!Gt=g&u8ULN-D z-HR75Uf|)whe$|Bz}mHI5fTytFE20b+qVzt>FKDfti-x?>kt(cg}}f-EL^w{85tSa zvSkZOOG~kGni z>=@kL-7#Xs2xMkvB0M}C#l^)~vSbO)oH>K0rY7|7-yanf6ZGB#b3xl|C zf@QJTqm`3SCu_C8A>@SCV@^)23bk>uqM$|4w<0NdbR1hI?cxtU)F{=Z9UG}}m}k)U zxLmD^mjun>8StCmQQTndh4*dLDkoEzOQqJ=Ihy&$0E(O2%Ro!GMlsk3QBvnl$7VKH zji$H^wTRZ6-SWkhLS5+OZBL7xXxSe)L?Jc_)H)d9^sebYC} zunbN3YG27GTPG;`GlGUF1&t*!Jn1{VNS>0bBbrJfVK@zxr_G<)e-cR2Zpw7CJKt0& z^M=7Pif2g^z0{Qk_Pfwb6aCaMYR8T+kOWt{~YT3Y+h2i1Mknp{A+DrUW(o|!vt&HLQBh1UCv>fWqd zmBg3K(Ddoi(G_gOs*)C=D)Ez54bA4n9PUf=VcyTFt-ReuZ&X->)Y1CrMcfm^8(U>$ z{ezLz@MK#B@#x`=t<9M6k*qj{6;f&8J7!3!P(9@s;S;&>N1_GKW!fCJYPqttIZF2E zX(=(>H<0f^8mP@Do2-`mDX-**1nQe5HSY5#1DZGAYH^xH4vwFvX=*M`@$nh-S?DIllzXSWT_ENv7% zE|6BIieY^ieRPm*Gj&;x#NAt(Y_0-kKQ^<8%{iL-d|RD8e63`MyQsx=wxGGYdq=uO zT3PbAglCiK5Im``@|rDwO8r_et@1>kREMAxHJsVC0cvb8?-u z5t?7FV`uj zE+*mNvFVl#r?mBrg}5%~wC?@;+W`ILy^}{5lW_3ZR(J7@6XLp@(}>Nt@OKJ~o~)w? zheQkL@FSVia3QYCB^s&k&cEdo(yjleK!{(q{XMXwU8?Ir;gEZhXWjV!C2H(X#QqPJ Cht($l literal 0 HcmV?d00001 diff --git a/m5stack/fs/system/tab5/status_bar/off_0.bin b/m5stack/fs/system/tab5/status_bar/off_0.bin new file mode 100755 index 0000000000000000000000000000000000000000..f302e112a58cacadf3d50ce8dd55cd8d11a73107 GIT binary patch literal 10386 zcmeI2=~EO(7{(V+W(EN{5>CS*$SI%#f*^_<5fBVYgeU~OP*6jRSE7m0n8Y*2BbOS3 z2U^C`^7x6Q+ z@IYPNNj!i491kBp#Lk^N5fc-G6)RR?)22;${P;1VqM{HG5P-C_G~B#-6L;_4RT**c z_xHzC*@CL7Dx5oa z4uOGzNK8z`sZ*!0bm>wYJ9Z2U7cRt{Idf1_QUY0)F>BT=?B2Z_5fKqMfBrmzgM+bQ z!v+)<7NVu41wDH7Ky7U;e0+ScYu7GZy?Pa?si_z{b}aJp@^J9rK|Fc#1Wip%m@r`i z%FD}f^5jYNTD?(RT#TMQd*aZcL&(a?!h;77aOu(|R8&-8`0(L4eE2Z3v$N6G)`p~{ zBwW6H8K+O5R&!a7ef#zyIXM~C)zw(HZXM2?IfF-!9-+0h6*VFVYqPe*l;o;#}zI-{BELj3iPft91_6##-%)qT%x77Pshy46} z#Ky+r%9SfvxpF1;@86Hc#zx$^a|iYH^;o}tJt`|Jv1-*S`1$#vw6qkeq3X2%=f@y*@(Yg%?P>J=Vv4#7 zx#l1%wCIh|f(N(;L|jB5qUp4hFc-pPJG78SSHkSka$E|tLksloPTD}%!tC@dqFZU< z-AZ%uZL^a}7kBM7&1DI-y>ht>qZ|F_XzSI7{(w}6(%^!=d1;$duYa93Vgx}LXpOD5 zppTeX7Vrm$ZIeNw1 z7%{F3H2*%-GbE8(8fbF;&$O;wyfb!zHklfRzNcTmktUB1rnT#YopYWxiP}xAWPY7! za%>%`-;m9!jU}V0U#8uaUFy5150NaSjWJR`_g_S63OepgXPO+FMFPqNZRy^v&VY`? z%{J{L;?eL3Y^A zWIy8cgLug4rU~?l8W^Laq-hhp6W5M7c9u(Yee5&Qx*SM?GOcBq-Z!;x98XLO%Fy)9 z<|`o~EREEnYD{Q!o!sf(W?*XZ{5n}o$U2!uXrkfP3HJ>N z8{A3LJ~&A$(QG2$=4`Bl0fe^IN_CpHQL_o;T&#s~LMybEo$cE@noR-c!U>DDxFSw= zGLye(E}@(YCy*I8m6I9G!L{T&jaJ9GZ~~!uak7)Nbd9!_bFmh@eYH5_YDOcRr2VA1 zNOpfi`GAD2u)YX7N(j>Gjl^@|biRGe=~lu_(z@PC)n|YehMv0zNT02Zp7|w)7pW*niZFKfaA&rZTnvtevFr9${s~RRtq8sq_?8z&j&t=(wlP4XtC6* zlpg}RyMHKY8YtRviM1#B)BFX!6&jMjB><3yP!l{N|=!jnjRTy~#cxNl8J1 zRLKcd6qY2abpF1{@|gbAFjVmCc3B{;ixXaoKD!+sliDSZCJ#B1lrhL^ti)tiP*}Rj z?oNk<JqpR6DGv`W&X=ctMoue?}HBFQ*8yZU-SHAL@G})hR z??BqH~bUE)&Kwi literal 0 HcmV?d00001 diff --git a/m5stack/fs/system/tab5/status_bar/off_1.bin b/m5stack/fs/system/tab5/status_bar/off_1.bin new file mode 100755 index 0000000000000000000000000000000000000000..927cff870c34a6723c784c474717406dba1a9c77 GIT binary patch literal 10386 zcmeI232YQq7{`}(+0K-n+nZ7-Y-#B!PznX)76gHMQdDXY4{ocp7HJU?L_|@OLZVSn z6OEA=)5LI<7D}NlM@w&d(1QZqZAnxN!H{UgzccUcY^OWN?v}L)!b^7M?Kf}!zxlrR z%{Onh4RP0KGBiuL58u9kMza{})|DYKF&})(%F2RfB{;hV-QC?dc<>LQzo>Hh*tKV`C#OUAlzI%1WF#aRRNattc)o#>tZ>(bd(3 z!oot-*4E(GO}KF30;;O2(B9sT>gsCF=S9fL$w6jjCQ?&V z(bCb4&dyHG=SaA^x*{VZ1A|rp6%`dItTbZJo;?_~@fKEm-UHWF|6o-1EhKHdf$C-x z^2>}^w)Z-UxN!$cjL81h1Oqp=rp1Kl=x8)?{L(rjVq#)oFc`Slr6G93ZEX3$1f!`3 z(|7!eDya+;x3fV8qJ3&DdAKX*HOzW?wgIxogsA{E4M` z*Abh06XW0c9rJemf`&E|yg1ILZ(5L&Qh?mt3T|$0@@b-Z@VWP6;C7$1#~xiY*col! zAcgjbC6EhW4q%+sNQIW$2(*WXI}63>$bpE>OiLf;M3^nww3*I?*`nn-6=s9>lGi}e z<~kQCZPpwqs z5t^%3%kA~9o+36`mnfaKAe@KE({#Ee^OepM(Ok6LtFoo3(LzEAOo^s@=4}OBe`vwO zDU9_aqN1GKq@!)~a(P@oXg+Zi7CM4TifGK*w*zLqIVm^h-u8@*%Y_T zNwg6ZHpWNL>qBE+NF%rnFA9gnd%-ss7Z-Q!C*0OWPfQx6Eb*a}Hagm((wD}hzCv)* zEyqP2fu^-IS9RJP;##N>Mo!9##w>lB;GP#zgs!7BFPi+~w}VOEToYI2X9;b!fDr8o zP0vSAXzGz1MqC>$Qjm7%zfN$^NsrL5$!rzoEr8I>xi6KYmW_m#B|R+oCgI7`yaIWe ztyj7?rtr8Y6=<|h?sorJ#FVE+64xT6I(;pxc-rC_eU02VqEBh|)X7*vOOnV~8D8aS zx~*1LhiRma1lnA%uP3x^cS|El5-w4(Hq0Qj53J3OMsf@vN%j0E2$Ci{okEL}s7MSS z^3J_4F$-B@DO_6B%0;S_9lpKu5Z6AsTUs(7$xw-k#Nf@36IU}j!a?6&<5k#riMo{` zkqk?*vfBD}Mlzh3`m zMj~Tn2;*sCRz~itS>02}Nz0N_p2pB;fQ{c8=wc#BiDrRmwE|9oMxSpUQX#P=js!AN zY@&VY8yCfK9L94T7frLb3N#;X`Oh^~=cbY&aZ(tKB+{x*J{n{1HD%3Pq|98c&a76a|NtV6uBPR(oLjY$Xf^LYPBxvG=|2BmB|6Z z^P+hqA8R7*h;Ix-o&zj9QT8e4Gl_V{P83}2vnMo1=8a*3$=$@VQ8I7#3G`Kq4UoMQ z^ZjOjPV$!;NtQw^8#v1^%=2R*h&u8KLe3OulD*RX zX6E@!5b2A%-ZO_HPL`>}y)4TU zM*>rPTNIznEQlRqNsh399MMZ%pJA3SQjFPRhi_CQezw8a*AUYCu4>VicJ7`g)l{}T zpee`3u_O0yB$aBpWAW=9tsY6$rB$zp-92^M!2bPhuH9E{-#~TW);M|M)M;){*muqL zj1vj^anaXP@H9zEz+4PNnst(})myn0} H|7-Ivg47cM literal 0 HcmV?d00001 diff --git a/m5stack/fs/system/tab5/status_bar/off_2.bin b/m5stack/fs/system/tab5/status_bar/off_2.bin new file mode 100755 index 0000000000000000000000000000000000000000..2b0fd397cffcf7afe4a5ae7e031c644f3da84113 GIT binary patch literal 10386 zcmeI233L-h7{_CqwVP5X4Oa^&rG-ih2v|XGx$)pofhvl4%W07o5Kutm7Oxh;1MmRl zQmY6pj$rNDwK#qHG}f(KheL-BVg35`*u8r_+4 z%(NjZD+|`d-(Yu?V8ezDC_Ldn<&l4232psY^ zqQ+i9-Q-_!?AS3B9JgcV&YdVKD#E^f`*4_?XZ#=_GcyxA4hqP~vB74u;rQ|6*uQ^2 zi9;_;m@olJNlBQ#NmE=MWeeh{VK1 za6_--(Uh||b?Ov0>=BTcmxn_|cBH1J;dN0YuAgz(YVFlo6RPriQ^Ehb$;Zh?UJ zRtso6@iID2Kac5O2{>`$1X@qMhzIBXj0O!Fpx&6PSiRGRoSYnN+qMmpmka1V>m0^^ zVMomPUyygqj-X-JF?jG`v?1-{Ctt$I#SZkCeGUUt&!W?e^Wca7gLn@ z1{RB&W;Um-(!rIH)+0phEG?Qj{+{X{d6X8&aU>57tgK+ub&02D58oxiv}tB@TyszD z6tn=2Y?bAtN2?!BVV-E_wktf~%0jDEo53tKsHkF0er9HAS!Zor8E9es87w@KNh)Z3 zuTL1R^_`lyGSKQUSQB=)@w}48Kk*R5jq(#MlELbQNqR0cJ~@fv7Ic#?D|5-WfPjEt z?t5|!2&E>Ir%@8dI_>rzvWhE>f97?HYb{?_6eJoar$Bw$eblvZDU3wQjmAIQhT?`R zC{oc^S`{|P3tWCV5Xc7=kv*t`YB4N+fVVbeHz z=(lJ}bB=wr9J72(X?@j~CErv$ZCaITBF$(^H~%pa_p}EZnzi zEwe;gdMVh&>o7HC;okyw}F4*6pv=_=tql4rwm5RHe z1Eno=H~SjNdSWE?$3I1k7I7c>h1Nu+qB77sZl=mCjS_j{;&f*&I!k%s8>LMvFKxLP z$#9j5yW!5IVkBR5H_QFzrEl+wG)qI3jGN(85oTWGW;J>{qpHsCw^)_k&F~VX^>VZN zOpB$y8O|jGDXqIlT1?eojtj0DBhOvhGZ&jDo2g`64b_{9w5IB{iBr9@)Nk^VQk%xJ zXMn-Gh8fhgv94c1JYzH&#&HtP1C2f3DyfAOmg;RO?MsEpS!6U4#XAV5@*O0aPH#QX z!pQQUBwgoHDC})DjQK`fYs4UpWam84_$8FqOkQ!RHM=EJ+HCa|Mytwk&h2IpsRURra~}DXg8=-h)a9&|L>Da$JBujUPmvv(#~A$Y*`}QJBWVjZ~r1zWB!T z^f_R$OwfGFHIHMk0Ww}$pfq3ljpu81W!^Q>*x7e)$6)>XC?Rs0h0XghDK>0RmqHdx zwN7qhrE}kx8SK?BWPnS7(Y(LkXyeG5%sWfhPLG$WjeKJmW0@B}Rcdu9F`7xnNjJC0 zeE@B3Wdl=W_OBCFqv!aQW9B+HHw2hE(FLJ6anej)&XZEh`fMxgbkXAUiDd({#y)E( z(Nwvo`_0J<>nM@LTtRE2v&m<7fC@zn1~i_(m#!KZvy>LsMRH#&*8)+SA&uv)GS5ki z|L`>}MyahlG@gGrDx{ed$)+RbO)u5eE|rfaYBuZ{Yi&HP%ahvruGaF?T=n$NZqoGQ z75YDgprC{gIvRatudf^WCaxGN@c$)rf%5-4`~$bUB_aR- literal 0 HcmV?d00001 diff --git a/m5stack/fs/system/tab5/status_bar/off_3.bin b/m5stack/fs/system/tab5/status_bar/off_3.bin new file mode 100755 index 0000000000000000000000000000000000000000..333f8090e758b4a40e235e498ea824cba9bb5d03 GIT binary patch literal 10386 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®_I-CiS#R;^TT$)T?p?bLpgVQL2nqQ_P@JY-jRm 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 literal 0 HcmV?d00001 diff --git a/m5stack/fs/system/tab5/status_bar/server_0.bin b/m5stack/fs/system/tab5/status_bar/server_0.bin new file mode 100755 index 0000000000000000000000000000000000000000..2d67e3ca804a2f1b1c6099018545f901a4b93933 GIT binary patch literal 10386 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 PvXc}4Ut)^=p5*@v_=7Om literal 0 HcmV?d00001 diff --git a/m5stack/fs/system/tab5/status_bar/server_2.bin b/m5stack/fs/system/tab5/status_bar/server_2.bin new file mode 100755 index 0000000000000000000000000000000000000000..77b1ad3a373365c4379a0966780137ff7a0a0642 GIT binary patch literal 10386 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 literal 0 HcmV?d00001 diff --git a/m5stack/fs/system/tab5/status_bar/vol_0.bin b/m5stack/fs/system/tab5/status_bar/vol_0.bin new file mode 100755 index 0000000000000000000000000000000000000000..8e99c69e33ee4b451943f8ec53f2c44fb2ea8870 GIT binary patch literal 10386 zcmeI2$!`-^7{KjjrkSxFZ&@5Cfdt|hoUkUPltK$#=>l!3R8CQj+#0Egdf>pZhf2MG z#G!}w&`MQLZKWg-DhLR&DGG$J2153Ps>l8XU%xkRzUSHO36957DNl0V%*5~e`CH%j zoFoH2U$^fId}#x|>HG~klE>l6lPAE&Dhy{xl6MD=RQR zKMylAGcYwZ1&<#;hSk+oc=+%kJbLs9rl+T2ad8pu-@gw(oEd_@m;Zs0krB9e?;b2H zEWmfC`r!BT6L91Ix?HFg7*@mo8m`{{DU#7#M)LxjATRYJzw?4i_$5 zfT5uwxOnj*jE;^%d-52ZK7AT`dV1jG$&-*yr{Ttp8&FnO2HoA=@ZiA%ICt(GT)A=u zGMNlqy?PY}2M3|GwH2D1o1wF_6Rusm2B}mE#>dB@tE&sHU%w7#&z^;ei3zxT`7$$| z@5Sqd4gO0pz)QgM|MSf^-g>zhUldqVR|NC~v!OES^wy`xD1 z8?DH0(=Dm#=0Gd5YX1wGM8gy%URjc0=9u(`7WRu#0^JBQoMmN7OWGO6JTAGqV zV^C%^L6-Xq8s?)>Y*wd8CS>WLhLCmAzdzt!{t$$7bQ%WqFJF z34NTbcqQB_Nj2hry|l(x%gf8lNSroT$+8?bJ)s*RT~Wel%x9)Wk!7WY+7Q=71mm2g zWiU|0UaIh(%lJ;G>&=HXm_;{rS45VhHgBQ;qQwFjCDn9?^Li|~{c{ep(p2=Eyv5<; z>GD|y?0nV?sDDj|*=TAb;;QU8PAe$8BYO#Er-jg#4Mn7Ft`IB{B~{tjWN~R%&fqEY zrHkJx-!;H=-8LBlKhz-0Z8qi12*aHdZP$r(MMXtAePH)tmSx$&b!tL=8i~MecF_&Z zTz+*x8#_~B!E>VR&dN7maT?P{-l|JZMrC;uQE2AUMU=FNByge`U1h@Q{;EToC?auL z-a!*rF=IVPm}yv<=|N$-G4qk*cF6MEj+WFjphl8v zH%3b$txiW^P&PZ#LzQO_umNj1?=5=6{SeiN%ywQ|2fHN7uEng~MH zblJab6w<91E;Kb{Y#X?4b@8m}m}p4zev*03)+dP0 zJd7+h#1`OKxy&@R>L^*0>t^^h2Bd>(eumh#xv6@(P>e8h~Nqi z3DU_ee;4FHQ_J=sjW$sJ25g07GoKc6T0-w5!;(ZN{q^`w6OufzH%-#`jK?W!`=h^s z(~we&JwmJ|fA?w@D(BvL0HJ?QgY4&Ajzng4M|yLs9n9Z)iSNB(Y2igL-I5%~`P^ zoi|U|4+f^JMxL8$?;M*reg{Jzl2Qs H9L@g%Qfw>c literal 0 HcmV?d00001 diff --git a/m5stack/fs/system/tab5/status_bar/vol_100.bin b/m5stack/fs/system/tab5/status_bar/vol_100.bin new file mode 100755 index 0000000000000000000000000000000000000000..dcddb7b60bdc807b4a9fb05dce78d092e6781c12 GIT binary patch literal 10386 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 literal 0 HcmV?d00001 diff --git a/m5stack/fs/system/tab5/status_bar/vol_40.bin b/m5stack/fs/system/tab5/status_bar/vol_40.bin new file mode 100755 index 0000000000000000000000000000000000000000..e7c4bbe04c728c19c6d5dd8a534879a67c43b747 GIT binary patch literal 10386 zcmeI2>rWI{6u`&D*`3+l?!qn)5kZufPz4mx;-mG^mQvodP-rR82U6M!6)FW<{9>Be z1V8wS;X`ZEM$%6$1|PMFCK^plqar?0j3_>ue(YaxdS>puvoo`^yRhu;2klLkd+*HL z``dHwIp>}`OQMzII=EW=$OQ@#%VF&76r4JB3c9}k%*;%9@ZbTA zkB`HN6DQ#M_3O~m(gLZeso-+C;Nr!Luy^lXSd~}?{r&w=UtbR$9UYLJoek;f>ELua zp|G$JL{Wr!^X5TrZZ0%5G{FA-`=O?$2F{#00|NsCaPs6y=;`T!yu3VcyWLP$Rt9#v z9d_;71w79~ettf*x3@!5QWBWWW;k~2806&SKw4TFG&VLuZEY=7R8&B5aWNDX6~U!T zm!Pt;687xb1Lw}2gVE7ZIC}IbG&MCrLP7#mRaL?H^XH+Yqy$VR6ATXz!;vFLpt-pj z_U+pT)z#Iod-razTCLF5)&@gEL(tjT2?q`wfWg5*$jHcmk&zKNc<>ewhl-Lc>e#cTKU@iP?UTO4u)leqQhk$vgax8?h7=Zn_P#BWw7k&5sBCt#gP4ok%FIT#lESdi%py`uS@;n( zv<2a#i6$>vh?k9OQkKnt7Glv}2pXnuq-d-pT$*Gstxbl9X2)!jmI%nk5UN`C?(oQF zRumZtO-7bxNk-waI|{4WD(C*)0}3Ds#3pTfY3Q zqS$nDZOcL2V$CJm@z65SP?WvYLUJyn5$bCTn`9WLr3tACJnxKGKd7TQOlT!B9`=lA z>sAq%o+jYR@vD8)BoA0VTMua6LSbGsAq{b{-Z&ynFy3w3_yvP`(`=~A^bpb#S12rw z(P9%7fMl26!l_nD+mz>0275IU-c-RTZK2BWZRtFp<26c)Q1y&x(X}Njs#bjTRpYCl zcd22Nwix9}V+5(Ab$Yx*=8wU*8(5Yn+ZCP>tyqIlb%+N@^6*P2F2l+53mKZ#WOl_$ zQWPgJqP?U7XqwdD3XrA*k!YS@!zipKGzY$&!GfU+b4pTzp$Ur+$GjR>(W#G?Y;saN zM3Lz-D4L``wsP@EPGUmQT1&&s+)S?oMH7~KEYXH!y$|SUt~t^YQ6RS4q0Q*e5n38X zW_BSs*(CL`gZ-HrrxBvrzXiR;U7-o_|%GQsxK1y5dOOpyscQ)0jJ+N%waoiis5YZ-? z@3DCl4r$Dl*2{65=+rhe7=54&E$vHG3 zcX#7g{EWf`bT?%`VAqH!iof}fCE2pT27kzo^9|nFl0avc0<=C%DyMwqNaatHLjC%h z?DFi5r91K}HB6b7^g0^k*GvrDyS>f_l*SOMTAqy!^{)AM)i{}!inN{VNh#^_e%Yv+ zU)u~o6Ovw4;fN-Cwq`5ar<9hkq=9_{mY3imz=|Q+d1e))RrAUd z6q+4bQc<=5!^owYPHKMI#&BLVG)M!{*t+2jNWfO#)trVxHEkKxlw~@Xd zQC*BE%HiMb-vgupni5Zcr_|A;9Xx?;;pzc|W)-tY!+)lr9!T5k-;>1C(&BN^oZcpa zBN&aI-27)j1~j3p4ry;|?qDj!drk!ETZHDOd8Azw*-2ks5%rV4+6z`L`%p1_`5`<` zv0XtMo$QIpdC7N?dhyzAzc;-pIzl*74hs6Hu3VwhAE z8b(dZi{-FNya=otK{zDwB1Cgb&h6RY!l_Az72i&ZDf)K`+W6-`PR!&kQ5^1X7`ZZ; z7NuI2%|~#Z$;5`w5CHmHoMMgSl1vNF?{6_~<&;QRRJQQ^w#X2wT9$2dh!;*ZzqTpU zxVH?|AM`~nLe(R*B9_(%4bTi!&97||8rR^RUTenBuA@b$dX&aR^#2s___uA{>;&-h Vv`QLnlgTF45&ZuWHTF4a{|DVj+dlvR literal 0 HcmV?d00001 diff --git a/m5stack/fs/system/tab5/status_bar/vol_60.bin b/m5stack/fs/system/tab5/status_bar/vol_60.bin new file mode 100755 index 0000000000000000000000000000000000000000..ae8a9fe07dd5b7e2e0ca1de579f9a5fbbf5f71f1 GIT binary patch literal 10386 zcmeI2`)?Fg6vv0Hv%9mqZoAt;sijcb(iil>ODUp&MM`N46m11dksg6Ec!T7WqbRNBnLx&Dw#flX;bLI@zHT;StG-W5Ms;bah@e?{~f5F+aXK}0ZFKli31AqQ_0k>}5!lg@>aQ*sq+`W4j=gyr& zS63IVT)BcDI*#J+u78k_kbwL5@8iae8@PAx9&X;eiSL)}$8TFt_IDGgp+S}Vv zUtf>&=g;Hv<;&>o?8L>37jfeE>o|DuAZluA(AL(5va&KPU%nixR;|LRQ>T!dn~MVn z4&dR#hj{Sd0j^!Uh6@)iz~k{CGcyyxU=Sxyon*@D%pS7XqiLD;ox7mACEv3vJ!EL^w{^XJdU#*G_sxz z%$YL>6%`fOyLT^^En9}1oE$VZHX)oa5x+&Dk{R_#f!0d^JbhreHyj3wb-y>1JrimO+zs?#q0|9P@)z*4}I|93*(%TGn4jHP5? zTVB#_m@&Q89j)RDR|M_(QPDz?oNzD~r@dYfMOY--6gir(NVHW^g+-vf>5V5XH@Yyc zo6qTI?h^C}x6!<0VoYdGg(!|}`Y0yakl4`_M+hy-)5a~S>1IQVvTFYcnj(tHOJZe7 z0W4JT(&lI!k96%q|4?_SXR(q-hAx^8k`Ggx`>f#sOv@CKIeJypdJ(t}GW8cVk zG?-+jxzmQqa?)_)jTV~UL0*#B&F&TLg$WF1rMYS5N`)hA2!%TVPEGi7dSvO=Fu^bD;sz&I^e zl1fwg4cYFYkk09mDK~LW>`HA$l zg36e5pU$(R)tC^53l;%GUZk#R9QRz3ERPguQ8VefmkiT`z>fB`0bpwKc>9DpONHdo zvOHR}Ma^h_diz9$QWxzhS?LeW{V9zq;-W`Vgj=(v5YB49q6k~|i#9$(lHTXE;WTl= zXUw|kOo!s+?S9eRUrN#kT(p-a&Kd(chcyBNq;=3kKErVo1J-kdnMRpebo5WRA{r(R zM&u^n3KR003Fi?IP?Y3>(Z&<4nu{JR%R)^7Wy19ca0n-{T>~OTfZMIomPexT2uRnX z&3=80OdCbC#gS;je6B>{lKs?eCeg|{tt^}+b(-aDD(ubc$hy5JNtI%cKt4IiB0%S` z#z1MWN>UpqcqzKAi88vhcgc|BI4!d`xkTV;oFrKc5y_HiYa}V-b3Q11p43lNfdNJL zT6(}d@q`p^A)893iXLx0=aLdlVVgf}#i^igzRBk_b$zp@N&S?sAE|n>qRb_B(xedf|fX^jtG(|L?$_*$K{kbQ#K z1-X30Qj5L>*k&$+IJH^6Lf}GZ;mSyIL2f5mZD+7N7fhuf-$(isMV$0C6N$Tes)yudysbOFb|W39 ztlJ+hfYFGOOnU@>py%Cd7^r@*;oo~x>@^ef<|6k{9ORhHS=2}9wQgY^!M2;}=F)JA z(tQkwC5^1;%kp^iO-XEZa}M(e?-0ty(30xWIi=XQkv8a z8beEJljZlQyg1AoL-)yz^G@3YO8b=JI%|s^#^;072;JU@;e~SWPs5RY|M0w&2 z^36?+CRGf?ZlPn%2*VYlO&JDa*FXRO literal 0 HcmV?d00001 diff --git a/m5stack/fs/system/tab5/status_bar/vol_80.bin b/m5stack/fs/system/tab5/status_bar/vol_80.bin new file mode 100755 index 0000000000000000000000000000000000000000..c4f4da969cb1c6d9b3019eb712bef67768a1415c GIT binary patch literal 10386 zcmeI2`%_fO703IIjx%?d3=9Z}h~mQXQUnFum0hyzx-PhYJQ9c|sCj9k1`||_kDzOm z=<1rRZj2%2sq9p7tXs%V@yDvR=G+@d8@; z^5Z@{fBqaDZ|_B2T^+h=4xqdFE8Oq@Cw6uI9sl^^Dz03)f?>mk;mMOHxPSjX9zA-5 zJ9qBj^X{+luP6V;-Me@3`0-=3wYA~WrAzo^;W7Mm*9Ba@d>N-sox@#Kc4#J9Z3ZWo4K*Zyx5)pO33ouVVJ>+1R*oBPuE?uypBC^!D~*=gys2 zw{9Kw?b`>x-;bL&Z=$xg77G?Ez@$l&uz&x4%$YL>EiEnBwrv~w`uebG(TMpO-&74E*Gk+tFdIs66EIQV(#3z$jZt>V`C$>Y}tY{XU<^5 zh7CA)@E{&Oe2Be!_u}^L+t|H(H`3G7(bUw0vuDp@=FFK`yLK%c4hM>hi?L(J4qUi! z0nN?L*t&Hqrca-aBS(%PKR+Lpm6hn|=s;Ij7Y-aafE6oNpr@w?GiJ=dty{N{mX?O{ z@^USV*#Ce2yL`ZxfN%cq;;dP;R1CemPDyB6rW-yS`EHLpT7}ZtzDU}%*P?|YJ@zP) z3fj-V9Yxqcv`QtKuz_e>qY4{<_N#<=(sH5;6SjGsJ{~T9j|>|S%QbGas%8Y5 zuT1;Rk6BTw1(&WUAKK#r>RsM!RZTM5uS#oKKs~J|r8K%VmRtC;VQ6=`yyVQm(h^?R zKh-d)POlSE2$yLQiAI1njsnHlOU0SXZiHDjWw{PxR+>9?yrPU9W9}G4^EoI=5(8|0 z!m}A*7QI!>VZk&v&7AU~xu=z;WkB9wGhp2e0SlqIHxMo@6vwTJB)bzPZjrE1S_1j< zjVRKp3k(($wAAh&7?Y*NWvFqcnX*3Wxg3N2l9 zrsPG}CVPo`AewsC!M*FSM{in9J3W*Ulr-=;6FtW{l ze;b(g8qt0!RR4vs-L^F8K3Ad)$p~$0B-#f8F_p2_@U)=2X;9~ZZClCMWO+vDWh^U5 zfngPY7%J^2jD0K!qsYu%F{Qmvgo%PsLeGDUB`x(+#zt-v1UikCN~cOFcc5{jN*Ypj z?<8}Ksj6YDMp9(WX-2+Me0ne3QJr@t=QFlr0I z+!%o7POmfJxF;KTG#lHe_J^<%gCZX!!0VwU+2NN>`;|)$;`Q2v22Y$60`J>bFg7*N zlutsvmsnacwgB6eOAZqC2OA|Wm^Pt--n<{wotlD1w*ZgRnQXdu=!!!7TO=u(Qmq8j zW|Ay;jnqbRTB54z%fCN-23Sy9V~nPjN)T-siHuY*58^bBO5YZ0CHzUjJWxDWED5Fk zp_G=9&=8>!9O98TxA3PR8=52ZNHB4#D!3#K&P>$FdsC6S5kRM&1se6BhdNsAt4kg;50Tho`1_{EUy2jKsS}z bw_fg=LB5fW3#o-C^(v14Ut-37L!SQwaz4;2 literal 0 HcmV?d00001 diff --git a/m5stack/fs/system/tab5/status_bar/wifi_0.bin b/m5stack/fs/system/tab5/status_bar/wifi_0.bin new file mode 100755 index 0000000000000000000000000000000000000000..f113c46157a4cf58cb656a9da32d63f05a81660c GIT binary patch literal 10386 zcmd^_c~Dfz9miRjff)lR2nNt_%jJ;>1O*mX0R_PYL?I%Gx~{mcE{X?-E5ag+BA)1~ z+4VkFji6C5o)GW*j`w|6Hd{$5sU(%8QuR&G>v!{J-b1JOW4EhjUiYtG|33Zu^{;=g zAA?6n8=C@~O!Bd@!DSoFoH-K{Cr(7cSU-4rdSb(d4d~e`9H#C!Z{CnA5FH(jihLW~ zuJ4FDckW>0#*KK^WRKd}-Ej5lRb1TWfUnEl@#4h`R9F8Ek&*w!KhJc)qep+lfddEd z^5sjse*GGoH*ZGSr`|}8?}_H-W=x*^54?Ky3PXqf8~OS9sIUJs3JMCaY11Y$_7Q2< zMS=PM$o8z`#K4*|P^XZ{Eb|(|^L{%a@Uxn~U}9 z*W=o?Yq)Xa1~M`-h#wlrryA?lt;5>2YcXlkB&4OKVf*&&sI9HV;>C+mT3U*Q3m2lX z&IMzJ2O&HAU-0qqfs2a^5)u+{>((utJ$n}A6K&w{?~fTXW?)o zShQ#njvYIO`1p9(+S(Ew*+@xA!NG$Ev1`{ZY}v8}B_)4HW@aWTDk?B#$`s6*GY5C? z-bHe9GUm^pkJ#8)96x@X*!x#3UAh#Fjg6Q(bt=xEKaaAqGVIu~gRIG4h`py#RaJ$Z zJ9pyw^XJ58z6c2kfuElrmMmF9=6Vz5<>d$s4aI~B6L9X_IYdQ8VfXIc2nYzk>eZ{U zY}qm-CH(>6;V($soq(6ue~_1#N3LF|sd3mOtE@Fg4mPtHM-h zQ@XV$ZJc#sDzYS^?g4Ioenz9w&(AGDK3U8AO@6m}JH&^8gyJS0VP%?| z0HU?7>0+K$Y3?Qu%1XSu2xnDVR~d6rYRP>-?^TZLkTwIL~?XG}gMEefv^`)WF%-wU$&}{8Q#X*G@?jduaUf$L_6$cR>yB%tU ziBj9zK=X|Z*SVvipq*-oSSPmxM+$_!3&X$iu?BHASz{Jx#(eY7J=#Y zrhU#8%{0Rxy*|*H8)h)<7&P5qMB=J*2N7G`scCcGCt(#P3=yJa0!fs1cAZV>(oCN8 zjP(A1AKAP60#3jDx*(x zEO>A=KPy?~ZD=19QngE}X|Cs0I`;pH_8b?yz;~J|l%_pTk|EwqX!!R{mMCHy1DkS8 zz`^p^rQMXK@U4(b=m!jU0dU*YVFVN5hmCuGt0u6u$jiBBq1|>l&1Y@`UQcF=Cqo*a!JzIs=uwYLplBl z5+w_}pRGAPt|o8HNnXruH={XP8)7mpj@N213><%=BVTgAVnPf_JTo>aY5b`~hPOY7IY9LK*l6@= z4P$rrM=mruir8wjv>cQAtZe={-ZLr7B=P43VmC-^BQg22Ur2!}wBZ#*^Y0RTB`QmD ziLfX}B9kbtUN4e5o+0$ z9cK$Id5vEw0Z0#CDzv!K1Ldk{d*NtqA1L(mG*V%KYVQFGTC|G9G=ekvv&}SDXG?J? zo{2zn;MUIKdw_T>Q|~tB^PM`~lZ$ zs+;ZrFCImt+GwF4axk6l*OG%OHyz1Upn1{tVolRVa~vwbbh;tEC8`w~0!@}qG}$&- z4AbeRD*z-S))a2tH1BRkNl=}XHdHNAILF!8@IXzwo1yiW!gM-|#}zU}pphB{YTs`w z<+N!MzqM|NX?;GL(6-(Eg`763H9#WaX)KE1wD0ahIZb+Gk${*6rSU1*j^A=xVlqrl zQ`>QrMt8S>B2q0hSIKtM=_*=VBb5j=-YP26LNnw^jSH6QZr!4)J9m*L5{} Pt{wlsgn6v~u)h8eVO<}- literal 0 HcmV?d00001 diff --git a/m5stack/fs/system/tab5/status_bar/wifi_1.bin b/m5stack/fs/system/tab5/status_bar/wifi_1.bin new file mode 100755 index 0000000000000000000000000000000000000000..33405df8038ffa0a33e8e38d355cbdc29969de8c GIT binary patch literal 10386 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 literal 0 HcmV?d00001 diff --git a/m5stack/fs/system/tab5/status_bar/z_0.bin b/m5stack/fs/system/tab5/status_bar/z_0.bin new file mode 100755 index 0000000000000000000000000000000000000000..7c560737c915d8f6ffb153eb3d9adc9e632a4177 GIT binary patch literal 10386 zcmeI2{ZAA}7{~887H;v5SC5_^S}2G>v9zFA71~l-`T}jCEwr?hmcGzZ3gx{(p->V` zOiTn56HS2N55g}7VvK?sV`4OFG>{O93Tk{${42hl-J4mq%iiuC>|GP=wVJ1_(sg!&~!PwXs9zJ}C{{DV6H#eiHsR>zGS$Oi~3975BAxJ|*11uH` zW@l&d`0-=hzkeTfb#>_O?nYNv7e+=#aOlt>jE;`t^y$-h`t&Jsb8~Uvzyajt<>Bhp zs~8?0#{T{Lk(`{2M~@!i@ZrPg=;*-Q+#IH-r_tKliVGJmU~zE~@$vD9j*iB)Yu9l7 z`gNQ-bqbl8nJ6nO!|~(CasK>y+`M@cCr+F|eSJNGgM(o8vMic6O+!QpTq zJUkq|y}bwx4MkE?60BA$jvhUVBS((l?%lgcPftf>WhDj%22fmFj6HkyfaiGx1qI>Y z!Gjna9E8*9L{wB1^7HeNlaqs6w{9UNB?U1tG2l231qB7TeEBk3T3RqMF@dvZ&*J3C zlW1>mM_gPS?%cV9l9CdHg@s{!d>juRJixwv`_R|dhuyn(V`yjy^YioQ?Ciwk60)VQ#EctLv$tz^l8zUras0*Vdrx^nhwWy;=E2X`c zY9K6BhqgJzNSGe2+EADdt=Jw&TD-9^9ot?FC@mwPG=qmt$CCz|_MB#zL#4iT40M*=FM%5fKjF78n}OM_8>9JZ}KaPWJ0e zl+l5zcXJG(ttY#FCNC<6;y)Oc9q*A8; z-_=#ow*SR(-G4|BsX`Gpwyt$O>5Xl6C&$GnNQ|Wlr3nx-Pv)Z`v`&r-PFi9t)r_UR zBPz}&)A98sgskQ}ZOt-~Cd>!o&U*bej&sQnvZ~KC*>`ERi6)m!MO%cHS~4Ajdq^UM zh*@#<>S;Ucm~C;QZedw7iS$I1);HyQS^|f!3x}6{-oJ|XRsQ&nC&F9#NaQG~4U zCyitjh&XLY%rtk-+)uVSgv1QF-S9la`O`L@PZZK6v9bIn>P=6yjVu?UTI#Gox9&(j z)@t2FQZ$6=G%OI7kPPwo+rgc9>D*Xh`eX(@DIsGynFxv literal 0 HcmV?d00001 diff --git a/m5stack/fs/system/tab5/status_bar/z_1.bin b/m5stack/fs/system/tab5/status_bar/z_1.bin new file mode 100755 index 0000000000000000000000000000000000000000..943a7cac3deb97c3e718dbd7315138ad081169b9 GIT binary patch literal 10386 zcmeI2`%e^C6vx*^W_Lh#mqi3&71l)rYKw&mDo7Rjep8S_3$&D$mbTEASAjxHNi;E< z7)boh=MQS)Gsb9qM1SxRpV4T1KjPcOzvA0FGxyFeJ3BiwFuP5xC)v4Y?!9w9d(OG{ z?#!K?9%r$%Tb7ZJC@3r}#MZ4_@%Zs$?Ax~wot>TN=;*++XV1{m(gHy)U%rgx%a^0B zt_}|$KE$OXi|p)dT)cP@`T6Qj+_-^*2M=QX`t`Va^(tawVsQKRZS?i^;r#jY z=u$&)9MnVAW<+l@nq4xyl+05vr=IDY&%Zr;3!{rmT$y}ccyqoeToeAu~jCq_m_ zaO~JI6crU=(xgc^efl(ddU~*C%^F;}as_+$?nQrpKhB;#i`A=F~4fiHV6QEiJ{ZTeq-k z)hguVbLV0;P zN=i!5)zyVvyLMsY#*L`0t;M->=TK2mf!(`zMHE_U+rTW5*6SolazAWMJF2ZFuzP5r&6{apcGmJb3T`D^{$)x^?St?bi7=Z6ZSK2f!nA0`rouF6^Ig%T%`q3I;afpeX|G3>W^&pzB5AT~FKMO)R9?B< zR;yb+A&R(8nuei<7$B7=lFH5^O!EjZp7;21k@U^Mr#hXfydyd^o_E^qPM$Y`<|5B3 zOoCSjYF^@)Ldzjf4@@$HhS`5aBhNloo+k&&ANMHvXPP8SIIiVeHQ%H`NsbycFL?w~ zl?1=_StOe6{!>YlBdM2`L>?MdB~doNG?(Lovt~2CD(jYK7q%bPCR zUw?DlKqq7PONw!@d99f@Jv5hxag9HB9qg%Cr(TM3bbNoKlYSvBt6G#A$JVkPQw&e)vW-T}_5#?9g;ktYcwp z1_#eAT`&L02ApyHZ#{g3#bmG2GESofRv*74EA;yLSWMbXbZk) zu&S6qlVSAn(|&4Yuy?#+0ArfQP0K1}u#e`Au^8?|rdh2H-XYuw(HhZ~`jv@`>nW`= zS-n?qi%c`NawTMb!Cd<|*kqXV@zGLd&0w%8VT>!9C{0;}jpWig23r_#Q>QSls0hZ$ zHz?s~aZL=?5$f(k0g)?$X?jLU_L4=E*4-u>(xr@pmR|fdg)N$Fh3 zC@nD~B!yM#qp?7elJdptm0!>l8bt&Yk))9w1tK0t9&=4?FUS<%QiY8f@^i!U45#8- z!j~*XAxnLE7s zix>8shJ}NJg;LEoBgPednQtV3X~MHy6>o> z`~Gc?R%i^ihmYhZCMPaDjcz6x^gzA425m0g1XMR*s6~Tj`5>C_@-3FA{!hVTnf>W| WhN2a&vsNU+mn3xXbeZ literal 0 HcmV?d00001 diff --git a/m5stack/fs/system/tab5/status_bar/z_2.bin b/m5stack/fs/system/tab5/status_bar/z_2.bin new file mode 100755 index 0000000000000000000000000000000000000000..f14884f9570587a9cc7419fe77c101d895bccdbf GIT binary patch literal 10386 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 literal 0 HcmV?d00001 diff --git a/m5stack/fs/system/tab5/status_bar/z_4.bin b/m5stack/fs/system/tab5/status_bar/z_4.bin new file mode 100755 index 0000000000000000000000000000000000000000..8dca6b268c22206908aedb24cf8f29921c09dbbd GIT binary patch literal 10386 zcmeI2Nl)WQ6oAdRCX8bs-2pl*-H`5Nm?1MgJsoE2ZF*+@!*D_zxxoR%fdfZwTtMQ; zl`99t0XQJB2nn$Y1c*iKNc@V|RdH3>v16B$lzpIiQtaodxIVw77FW_3Rg^yEfBeY_ z?C$PDM@I)tO-%ujfq?q^AtI*!w4w*~_QmGVlb#*~Hordo2Zdh7cg0ZnN z*xTEKtE($GJ3E7&ogJ8-o+hR%kDo^$d`&rE3;_@S_*45Y-^!35zHyNsxes5z`}mzK z(cFa5UVSeMh1+q1X_WRyqePg9mikR5Oho%66(*qlRTUYLT2hZh{&wC?ZF8R7;^vE1RS}8&8+WDY ztX~V`=8M)CiPU{|!r1&qAS%N7SRU=EL3n|KM%8>I&k`B5I-~7>FzjVMhF%p-bGDLZ zN&3X7Yr;LP*7-G3Qa-JM!7@f#kaUdH*5t&#W-FW4%3zf=Qlwim(QPJjn%~}SyZPROGA<7r@fNBmaIJLN7xoyRbT+JNj zTzt5YNO5VjcnKL|p;4&iLp%&AP-SU9r}71g7mZJ8mHq}S&b8D0WQk)+FgnOf`8-=xs&$>O>@BZY={6tJ}DZPRryGPA z&c&0>9`C`p%3s_N`ju>#G&G=>)Tu5sL34s?qwRXsplNUM77ZpEPIdF7pgBRcL5q?- zr~k-7XRZv?HtfI#@@^Ui4KG@=S%mNgB?(|o0<=xbZBJrHz9j(+oEFn@LSob25N`%f z)3kb{qeW>-2APx89_uN{xLG-d{@`ih{*Ghc?+^@3!fI#0Xh|+^;r{*>?r)3lB?r#i zlS+p$6h(bOKuZ Date: Fri, 13 Jun 2025 11:58:07 +0800 Subject: [PATCH 108/322] libs/color_conv.py: Added HSV color conversion to RGB color. Signed-off-by: lbuque --- m5stack/libs/color_conv.py | 35 +++++++++++++++++++++++++++++++++++ m5stack/libs/manifest.py | 1 + 2 files changed, 36 insertions(+) create mode 100644 m5stack/libs/color_conv.py diff --git a/m5stack/libs/color_conv.py b/m5stack/libs/color_conv.py new file mode 100644 index 00000000..1f7fc826 --- /dev/null +++ b/m5stack/libs/color_conv.py @@ -0,0 +1,35 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import math + + +def hsv_to_rgb(hue: int = 0, sat: int = 0, val: int = 0) -> int: + h = max(0, min(360, hue)) / 360.0 + s = max(0, min(100, sat)) / 100.0 + v = max(0, min(100, val)) / 100.0 + + if s == 0: + r = g = b = v + else: + h *= 6.0 + sector = math.floor(h) + frac = h - sector + p = v * (1 - s) + q = v * (1 - s * frac) + t = v * (1 - s * (1 - frac)) + + r, g, b = [ + (v, t, p), + (q, v, p), + (p, v, t), + (p, q, v), + (t, p, v), + (v, p, q), + ][int(sector % 6)] + + r = int(r * 255) + g = int(g * 255) + b = int(b * 255) + return (r << 16) | (g << 8) | b diff --git a/m5stack/libs/manifest.py b/m5stack/libs/manifest.py index 220b622d..e068bc61 100644 --- a/m5stack/libs/manifest.py +++ b/m5stack/libs/manifest.py @@ -21,6 +21,7 @@ include("utility/manifest.py") # freeze("$(MPY_DIR)/../m5stack/libs/unit") module("boot_option.py") +module("color_conv.py") module("attitude_estimator.py") module("label_plus.py") module("m5camera.py") From f576e564055cf401724648485b4307c2f05c716a Mon Sep 17 00:00:00 2001 From: lbuque Date: Thu, 12 Jun 2025 10:22:44 +0800 Subject: [PATCH 109/322] modules/startup: Fix the problem of incomplete mac address display. Signed-off-by: lbuque --- m5stack/modules/startup/dial/apps/dev.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/m5stack/modules/startup/dial/apps/dev.py b/m5stack/modules/startup/dial/apps/dev.py index e8a03b6e..bfa71de6 100644 --- a/m5stack/modules/startup/dial/apps/dev.py +++ b/m5stack/modules/startup/dial/apps/dev.py @@ -58,7 +58,7 @@ def on_view(self): "aabbcc112233", 20, 140, - w=100, + w=120, h=15, fg_color=0x000000, bg_color=0xFEFEFE, From f5cb59e92a0b26afc7d4b794b88bd5494e810574 Mon Sep 17 00:00:00 2001 From: lbuque Date: Thu, 12 Jun 2025 16:17:06 +0800 Subject: [PATCH 110/322] libs/boot_option.py: Fix NVS opening failure. Frequent opening of NVS can cause memory leaks. Signed-off-by: lbuque --- m5stack/libs/boot_option.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/m5stack/libs/boot_option.py b/m5stack/libs/boot_option.py index f7dd50b5..2a37a270 100644 --- a/m5stack/libs/boot_option.py +++ b/m5stack/libs/boot_option.py @@ -4,13 +4,15 @@ import esp32 +nvs = esp32.NVS("uiflow") + def set_boot_option(option): - nvs = esp32.NVS("uiflow") + global nvs nvs.set_u8("boot_option", option) nvs.commit() def get_boot_option(): - nvs = esp32.NVS("uiflow") + global nvs return nvs.get_u8("boot_option") From a158ed365c38c9a48a9acae1a49784c4717e548a Mon Sep 17 00:00:00 2001 From: tinyu Date: Fri, 6 Jun 2025 10:37:36 +0800 Subject: [PATCH 111/322] lib/driver: Fixed VL53L0X I2C address verification error. Signed-off-by: tinyu --- m5stack/libs/driver/vl53l0x.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/m5stack/libs/driver/vl53l0x.py b/m5stack/libs/driver/vl53l0x.py index fddab550..73a68b05 100644 --- a/m5stack/libs/driver/vl53l0x.py +++ b/m5stack/libs/driver/vl53l0x.py @@ -140,7 +140,7 @@ def __init__(self, i2c: I2C, address: int = 41, io_timeout_ms: int = 0) -> None: self._addr = address self.io_timeout_ms = io_timeout_ms self._data_ready = False - if self._i2c not in self._i2c.scan(): + if self._addr not in self._i2c.scan(): raise Exception("VL53L0X maybe not connect.") self._reset() From 457021bb2acc426f0c2cbc184d2015aa165ef15f Mon Sep 17 00:00:00 2001 From: tinyu Date: Thu, 12 Jun 2025 16:34:01 +0800 Subject: [PATCH 112/322] lib/driver: Add detection to the returned distance value. Signed-off-by: tinyu --- m5stack/libs/driver/vl53l0x.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/m5stack/libs/driver/vl53l0x.py b/m5stack/libs/driver/vl53l0x.py index 73a68b05..cf33e209 100644 --- a/m5stack/libs/driver/vl53l0x.py +++ b/m5stack/libs/driver/vl53l0x.py @@ -630,6 +630,8 @@ def read_range(self) -> int: range_mm = self._read_u16(_RESULT_RANGE_STATUS + 10) self._write_u8(_SYSTEM_INTERRUPT_CLEAR, 0x01) self._data_ready = False + if range_mm > 4000: + return -1 return range_mm def is_continuous_mode(self) -> bool: From 3781e47bc1ab97a004fa8d52045b13fe6b5c886e Mon Sep 17 00:00:00 2001 From: tinyu Date: Fri, 13 Jun 2025 12:16:37 +0800 Subject: [PATCH 113/322] lib/unit: Corrected I2C address error in Unit encoder8. Signed-off-by: tinyu --- docs/en/units/encoder8.rst | 2 +- m5stack/libs/unit/encoder8.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/en/units/encoder8.rst b/docs/en/units/encoder8.rst index d85d5a66..53d30b8e 100644 --- a/docs/en/units/encoder8.rst +++ b/docs/en/units/encoder8.rst @@ -37,7 +37,7 @@ Constructors :param i2c: The I2C interface or PAHUBUnit instance for communication. :param int slave_addr: Deprecated parameter, kept for backward compatibility. - :param int address: The I2C address of the Encoder8 Unit. Default is 0x59. + :param int address: The I2C address of the Encoder8 Unit. Default is 0x41. UIFLOW2: diff --git a/m5stack/libs/unit/encoder8.py b/m5stack/libs/unit/encoder8.py index a3e11423..5422f9ec 100644 --- a/m5stack/libs/unit/encoder8.py +++ b/m5stack/libs/unit/encoder8.py @@ -47,7 +47,7 @@ def __init__( self, i2c: I2C | PAHUBUnit, slave_addr: int = _ENCODER8_ADDR, - address: int | list | tuple = 0x59, + address: int | list | tuple = 0x41, ) -> None: """ note: @@ -59,7 +59,7 @@ def __init__( slave_addr: note: Deprecated parameter, kept for backward compatibility. address: - note: The I2C address of the Encoder8 Unit. Default is 0x59. + note: The I2C address of the Encoder8 Unit. Default is 0x41. """ address = slave_addr self.encoder8_i2c = i2c From 11b2f9b3ba9ca074e3286e8210141e7bc8e8da6a Mon Sep 17 00:00:00 2001 From: tinyu Date: Mon, 9 Jun 2025 15:53:14 +0800 Subject: [PATCH 114/322] docs: Update system wlan sta example. Signed-off-by: tinyu --- .../wlan_sta/wlan_sta_cores3_example.m5f2 | 2 +- .../wlan_sta/wlan_sta_cores3_example.py | 35 ++++++++++--------- 2 files changed, 20 insertions(+), 17 deletions(-) diff --git a/examples/system/wlan_sta/wlan_sta_cores3_example.m5f2 b/examples/system/wlan_sta/wlan_sta_cores3_example.m5f2 index d3bd2035..0506bdc0 100644 --- a/examples/system/wlan_sta/wlan_sta_cores3_example.m5f2 +++ b/examples/system/wlan_sta/wlan_sta_cores3_example.m5f2 @@ -1 +1 @@ -{"version":"V2.0","versionNumber":"V2.1.3","type":"cores3","components":[{"name":"screen","type":"screen","layer":0,"screenId":"builtin","screenName":"","id":"__cores3_screen","createTime":1725245944614,"x":0,"y":0,"width":320,"height":240,"backgroundColor":"#222222","size":0,"isSelected":true},{"name":"title0","type":"title","layer":1,"screenId":"builtin","screenName":"","id":"g&yYOFuxgkn8yZEg","createTime":1725248359523,"x":0,"y":0,"color":"#ffffff","backgroundColor":"#0000FF","text":"WLAN STA CoreS3 Example","textOffset":3,"font":"Widgets.FONTS.DejaVu18","isSelected":false},{"name":"label0","type":"label","layer":2,"screenId":"builtin","screenName":"","id":"jm&Q+1-5oV++QQ#a","createTime":1725248481306,"x":2,"y":81,"color":"#ffffff","backgroundColor":"#222222","text":"label0","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false},{"name":"label1","type":"label","layer":3,"screenId":"builtin","screenName":"","id":"su%YD#SWT^=MBW90","createTime":1725248640382,"x":2,"y":114,"color":"#ffffff","backgroundColor":"#222222","text":"label1","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false},{"name":"label2","type":"label","layer":4,"screenId":"builtin","screenName":"","id":"xB=uIAqCI=xyv=LW","createTime":1725248644817,"x":2,"y":143,"color":"#ffffff","backgroundColor":"#222222","text":"label2","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false}],"resources":[{"hardware":["hardware_button","hardware_pin_button","imu","speaker","touch","als","mic"]}],"units":[],"hats":[],"bases":[],"i2cs":[],"blockly":"true3M5-R&Decho\"password\">/dev/nulltruelabel0LabelConnected?:label1LabelRSSI:label2LabelIP:","screen":[{"simulationName":"Built-in","type":"builtin","width":320,"height":240,"scale":0.78,"screenName":"","blockId":"","screenColorType":0,"id":"builtin","createTime":1725245944607}],"logicWhenNum":0,"customList":[]} \ No newline at end of file +{"version":"V2.0","versionNumber":"V2.2.8","type":"cores3","components":[{"name":"screen","type":"screen","layer":0,"screenId":"builtin","screenName":"","id":"__cores3_screen","createTime":1725245944614,"x":0,"y":0,"width":320,"height":240,"backgroundColor":"#222222","size":0,"isSelected":true},{"name":"title0","type":"title","layer":1,"screenId":"builtin","screenName":"","id":"g&yYOFuxgkn8yZEg","createTime":1725248359523,"x":0,"y":0,"color":"#ffffff","backgroundColor":"#0000FF","text":"WLAN STA CoreS3 Example","textOffset":3,"font":"Widgets.FONTS.DejaVu18","isSelected":false},{"name":"label0","type":"label","layer":2,"screenId":"builtin","screenName":"","id":"jm&Q+1-5oV++QQ#a","createTime":1725248481306,"x":2,"y":81,"color":"#ffffff","backgroundColor":"#222222","text":"label0","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false},{"name":"label1","type":"label","layer":3,"screenId":"builtin","screenName":"","id":"su%YD#SWT^=MBW90","createTime":1725248640382,"x":2,"y":114,"color":"#ffffff","backgroundColor":"#222222","text":"label1","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false},{"name":"label2","type":"label","layer":4,"screenId":"builtin","screenName":"","id":"xB=uIAqCI=xyv=LW","createTime":1725248644817,"x":2,"y":143,"color":"#ffffff","backgroundColor":"#222222","text":"label2","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false}],"resources":[{"hardware":["hardware_button","hardware_pin_button","imu","speaker","touch","als","mic"]}],"units":[],"hats":[],"bases":[],"i2cs":[],"blockly":"true3your-wlanyour-passwordtruelabel0LabelConnected?:label1LabelRSSI:label2LabelIP:","screen":[{"simulationName":"Built-in","type":"builtin","width":320,"height":240,"scale":0.78,"screenName":"","blockId":"","screenColorType":0,"id":"builtin","createTime":1725245944607}],"logicWhenNum":0,"customList":[]} \ No newline at end of file diff --git a/examples/system/wlan_sta/wlan_sta_cores3_example.py b/examples/system/wlan_sta/wlan_sta_cores3_example.py index 988edeed..9712606d 100644 --- a/examples/system/wlan_sta/wlan_sta_cores3_example.py +++ b/examples/system/wlan_sta/wlan_sta_cores3_example.py @@ -1,40 +1,43 @@ -# SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD # # SPDX-License-Identifier: MIT import os, sys, io import M5 from M5 import * -from image_plus import ImagePlus +import network title0 = None -image_plus0 = None +label0 = None +label1 = None +label2 = None +wlan = None def setup(): - global title0, image_plus0 + global title0, label0, label1, label2, wlan M5.begin() Widgets.fillScreen(0x222222) - title0 = Widgets.Title("Image+ CoreS3 Example", 3, 0xFFFFFF, 0x0000FF, Widgets.FONTS.DejaVu18) - image_plus0 = ImagePlus( - "https://static-cdn.m5stack.com/resource/public/assets/aboutus/m5logo2022.png", - 43, - 51, - True, - 3000, - default_img="res/img/default.png", + title0 = Widgets.Title( + "WLAN STA CoreS3 Example", 3, 0xFFFFFF, 0x0000FF, Widgets.FONTS.DejaVu18 ) + label0 = Widgets.Label("label0", 2, 81, 1.0, 0xFFFFFF, 0x222222, Widgets.FONTS.DejaVu18) + label1 = Widgets.Label("label1", 2, 114, 1.0, 0xFFFFFF, 0x222222, Widgets.FONTS.DejaVu18) + label2 = Widgets.Label("label2", 2, 143, 1.0, 0xFFFFFF, 0x222222, Widgets.FONTS.DejaVu18) - image_plus0.setVisible(True) - image_plus0.set_update_period(5000) - image_plus0.set_update_enable(True) + wlan = network.WLAN(network.STA_IF) + wlan.config(reconnects=3) + wlan.connect("your-wlan", "your-password") def loop(): - global title0, image_plus0 + global title0, label0, label1, label2, wlan M5.update() + label0.setText(str((str("Connected?:") + str((wlan.isconnected()))))) + label1.setText(str((str("RSSI:") + str((wlan.status("rssi")))))) + label2.setText(str((str("IP:") + str((wlan.ifconfig()[0]))))) if __name__ == "__main__": From 13ed667be2ebc0522aa552b2a12ba32a6925d790 Mon Sep 17 00:00:00 2001 From: LittleMouse Date: Thu, 5 Jun 2025 11:34:42 +0800 Subject: [PATCH 115/322] libs/module/llm.py: Add vlm unit. Signed-off-by: LittleMouse --- m5stack/libs/module/llm.py | 123 +++++++++++++++++++++++++++++++++++++ 1 file changed, 123 insertions(+) diff --git a/m5stack/libs/module/llm.py b/m5stack/libs/module/llm.py index 5fed5991..f0da8234 100644 --- a/m5stack/libs/module/llm.py +++ b/m5stack/libs/module/llm.py @@ -304,6 +304,96 @@ def _free_temp(self): self._on_result = None +class ApiVlm: + _MODULE_LLM_OK = 0 + _MODULE_LLM_WAIT_RESPONSE_TIMEOUT = -97 + + def __init__(self, module_msg): + self._module_msg = module_msg + self._llm_work_id = None + self._is_msg_finish = None + self._on_result = None + + def setup( + self, + prompt="", + model="internvl2.5-1B-364-ax630c", + response_format="vlm.utf-8.stream", + input="vlm.utf-8", + enoutput=True, + max_token_len=256, + request_id="vlm_setup", + ) -> str: + cmd = { + "request_id": request_id, + "work_id": "vlm", + "action": "setup", + "object": "vlm.setup", + "data": { + "model": model, + "response_format": response_format, + "input": input, + "enoutput": enoutput, + "max_token_len": max_token_len, + "prompt": prompt, + }, + } + success = self._module_msg.send_cmd_and_wait_to_take_msg( + ujson.dumps(cmd), request_id, self._set_llm_work_id, 30000 + ) + + ret_work_id = self._llm_work_id if success else "" + self._free_temp() + return ret_work_id + + def inference(self, work_id, input_data, request_id="llm_inference") -> str: + cmd = { + "request_id": request_id, + "work_id": work_id, + "action": "inference", + "object": "vlm.utf-8.stream", + "data": {"delta": input_data, "index": 0, "finish": True}, + } + self._module_msg.send_cmd(ujson.dumps(cmd)) + return self._MODULE_LLM_OK + + def inference_and_wait_result( + self, work_id, input_data, on_result, timeout=5000, request_id="llm_inference" + ) -> int: + self.inference(work_id, input_data, request_id) + self._is_msg_finish = False + self._on_result = on_result + + start_time = time.ticks_ms() + while time.ticks_diff(time.ticks_ms(), start_time) < timeout: + self._module_msg.update() + if self._module_msg.take_msg(request_id, self._on_inference_result): + start_time = time.ticks_ms() + + if self._is_msg_finish: + break + + self._free_temp() + + return self._MODULE_LLM_OK + + def _set_llm_work_id(self, msg): + if "work_id" in msg: + self._llm_work_id = msg["work_id"] + + def _on_inference_result(self, msg): + if "data" in msg and "delta" in msg["data"]: + if self._on_result: + self._on_result(msg["data"]["delta"]) + if "data" in msg and "finish" in msg["data"]: + self._is_msg_finish = msg["data"]["finish"] + + def _free_temp(self): + self._llm_work_id = None + self._is_msg_finish = None + self._on_result = None + + class ApiAudio: _MODULE_LLM_OK = 0 _MODULE_LLM_WAIT_RESPONSE_TIMEOUT = -97 @@ -795,6 +885,7 @@ def __init__(self, uart_id=1, tx=17, rx=16) -> None: self.msg = ModuleMsg(self.comm) self.sys = ApiSys(self.msg) self.llm = ApiLlm(self.msg) + self.vlm = ApiVlm(self.msg) self.audio = ApiAudio(self.msg) self.tts = ApiTts(self.msg) self.melotts = ApiMelotts(self.msg) @@ -812,6 +903,7 @@ def __init__(self, uart_id=1, tx=17, rx=16) -> None: # Latest work id and error code self.latest_llm_work_id = "llm" + self.latest_vlm_work_id = "vlm" self.latest_audio_work_id = "audio" self.latest_tts_work_id = "tts" self.latest_melotts_work_id = "melotts" @@ -916,6 +1008,34 @@ def llm_inference(self, work_id, input_data, request_id="llm_inference") -> str: self.latest_error_code = self.llm.inference(work_id, input_data, request_id) return self.latest_error_code + def vlm_setup( + self, + prompt="", + model="internvl2.5-1B-364-ax630c", + response_format="vlm.utf-8.stream", + input=None, + enoutput=True, + enkws=None, + max_token_len=256, + request_id="vlm_setup", + ) -> str: + if input is None: + input = ["vlm.utf-8"] + + if enkws: + if isinstance(input, str): + input = [input] + input.append(enkws) + + self.latest_vlm_work_id = self.vlm.setup( + prompt, model, response_format, input, enoutput, max_token_len, request_id + ) + return self.latest_vlm_work_id + + def vlm_inference(self, work_id, input_data, request_id="vlm_inference") -> str: + self.latest_error_code = self.vlm.inference(work_id, input_data, request_id) + return self.latest_error_code + def audio_setup( self, capcard=0, @@ -1124,6 +1244,9 @@ def whisper_setup( def get_latest_llm_work_id(self) -> str: return self.latest_llm_work_id + def get_latest_vlm_work_id(self) -> str: + return self.latest_vlm_work_id + def get_latest_audio_work_id(self) -> str: return self.latest_audio_work_id From 83a69452b854c183807c243e349c8a27029d77c3 Mon Sep 17 00:00:00 2001 From: LittleMouse Date: Mon, 9 Jun 2025 19:06:33 +0800 Subject: [PATCH 116/322] libs/module/llm.py: Update melotts model. Signed-off-by: LittleMouse --- m5stack/libs/module/llm.py | 28 ++++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/m5stack/libs/module/llm.py b/m5stack/libs/module/llm.py index f0da8234..674a7740 100644 --- a/m5stack/libs/module/llm.py +++ b/m5stack/libs/module/llm.py @@ -948,6 +948,17 @@ def rm_mode( self.latest_error_code = self.sys.rmmode(model) return self.latest_error_code + def send_image(self, image_size, image_bytearray, request_id, work_id) -> None: + cmd = { + "RAW": image_size, + "request_id": request_id, + "work_id": work_id, + "action": "inference", + "object": "cv.jpeg.base64", + } + self.msg.send_cmd(ujson.dumps(cmd)) + self.msg.send_cmd(image_bytearray) + def get_response_msg_list(self) -> list: """ @@ -1092,9 +1103,10 @@ def tts_setup( response_format = "tts.base64.wav" if language == "zh_CN": model = "single_speaker_fast" + model = "single-speaker-fast" if float(self.version.lstrip("v")) >= 1.6 else model if input is None: input = "tts.utf-8.stream" if self.version == "v1.0" else ["tts.utf-8.stream"] - + model = "single-speaker-english-fast" if float(self.version.lstrip("v")) >= 1.6 else model if enkws: if self.version == "v1.0": enkws = True @@ -1117,17 +1129,21 @@ def tts_inference(self, work_id, input_data, timeout=0, request_id="tts_inferenc def melotts_setup( self, language="en_US", - model="melotts-en-default", + model="melotts_zh-cn", response_format="sys.pcm", input=None, enoutput=False, enkws=None, request_id="tts_setup", ) -> str: - if language == "zh_CN": - model = "melotts-zh-cn" - if language == "ja_JP": - model = "melotts-ja-jp" + model = "melotts-zh-cn" if float(self.version.lstrip("v")) >= 1.6 else model + if float(self.version.lstrip("v")) >= 1.6: + if language == "zh_CN": + model = "melotts-zh-cn" + elif language == "ja_JP": + model = "melotts-ja-jp" + else: + model = "melotts-en-default" if input is None: input = ["tts.utf-8.stream"] From ebc73ddb998143d0f48b88191314e4d9a7993bb3 Mon Sep 17 00:00:00 2001 From: LittleMouse Date: Tue, 10 Jun 2025 17:14:47 +0800 Subject: [PATCH 117/322] libs/module/llm.py: Add vlm inference img function. Signed-off-by: LittleMouse --- m5stack/libs/module/llm.py | 40 ++++++++++++++++++++++++++++++++++---- 1 file changed, 36 insertions(+), 4 deletions(-) diff --git a/m5stack/libs/module/llm.py b/m5stack/libs/module/llm.py index 674a7740..525a3227 100644 --- a/m5stack/libs/module/llm.py +++ b/m5stack/libs/module/llm.py @@ -13,6 +13,7 @@ def __init__(self, uart): def send_cmd(self, cmd): # print(f"[DEBUG] Sending command: {cmd}") + print("[DEBUG] Sending command (first 100 characters):", cmd[:200]) self._serial.write(cmd) def get_response(self, timeout=5000): @@ -346,7 +347,7 @@ def setup( self._free_temp() return ret_work_id - def inference(self, work_id, input_data, request_id="llm_inference") -> str: + def inference(self, work_id, input_data, request_id="vlm_inference") -> str: cmd = { "request_id": request_id, "work_id": work_id, @@ -357,6 +358,17 @@ def inference(self, work_id, input_data, request_id="llm_inference") -> str: self._module_msg.send_cmd(ujson.dumps(cmd)) return self._MODULE_LLM_OK + def inference_img(self, work_id, image_data, request_id="vlm_inference") -> str: + cmd = { + "request_id": request_id, + "work_id": work_id, + "action": "inference", + "object": "cv.jpeg.stream.base64", + "data": {"delta": image_data, "index": 0, "finish": True}, + } + self._module_msg.send_cmd(ujson.dumps(cmd)) + return self._MODULE_LLM_OK + def inference_and_wait_result( self, work_id, input_data, on_result, timeout=5000, request_id="llm_inference" ) -> int: @@ -954,10 +966,26 @@ def send_image(self, image_size, image_bytearray, request_id, work_id) -> None: "request_id": request_id, "work_id": work_id, "action": "inference", - "object": "cv.jpeg.base64", + "object": "cv.jpeg.stream.base64", } - self.msg.send_cmd(ujson.dumps(cmd)) - self.msg.send_cmd(image_bytearray) + payload = ujson.dumps(cmd).encode("utf-8") + image_bytearray + self.msg.send_cmd(payload) + + def base64_encode(self, data: bytes) -> str: + base64_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" + encoded = "" + i = 0 + while i < len(data): + b = data[i : i + 3] + b_len = len(b) + b += b"\x00" * (3 - b_len) + n = (b[0] << 16) | (b[1] << 8) | b[2] + encoded += base64_chars[(n >> 18) & 0x3F] + encoded += base64_chars[(n >> 12) & 0x3F] + encoded += base64_chars[(n >> 6) & 0x3F] if b_len > 1 else "=" + encoded += base64_chars[n & 0x3F] if b_len > 2 else "=" + i += 3 + return encoded def get_response_msg_list(self) -> list: """ @@ -1047,6 +1075,10 @@ def vlm_inference(self, work_id, input_data, request_id="vlm_inference") -> str: self.latest_error_code = self.vlm.inference(work_id, input_data, request_id) return self.latest_error_code + def vlm_inference_img(self, work_id, image_data, request_id="vlm_inference") -> str: + self.latest_error_code = self.vlm.inference_img(work_id, image_data, request_id) + return self.latest_error_code + def audio_setup( self, capcard=0, From 21f848b4eb3e24ce8e076be8d6a89500cd4f7a33 Mon Sep 17 00:00:00 2001 From: LittleMouse Date: Tue, 10 Jun 2025 17:37:17 +0800 Subject: [PATCH 118/322] libs/module/llm.py: Update vlm inference img function. Signed-off-by: LittleMouse --- m5stack/libs/module/llm.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/m5stack/libs/module/llm.py b/m5stack/libs/module/llm.py index 525a3227..325c749b 100644 --- a/m5stack/libs/module/llm.py +++ b/m5stack/libs/module/llm.py @@ -1076,7 +1076,8 @@ def vlm_inference(self, work_id, input_data, request_id="vlm_inference") -> str: return self.latest_error_code def vlm_inference_img(self, work_id, image_data, request_id="vlm_inference") -> str: - self.latest_error_code = self.vlm.inference_img(work_id, image_data, request_id) + encoded_image = self.base64_encode(image_data) + self.latest_error_code = self.vlm.inference_img(work_id, encoded_image, request_id) return self.latest_error_code def audio_setup( From a85f92e2b2be51a753d6f765dcd310c9656c6746 Mon Sep 17 00:00:00 2001 From: LittleMouse Date: Wed, 11 Jun 2025 11:23:01 +0800 Subject: [PATCH 119/322] libs/module/llm.py: Add yolo inference img function. Signed-off-by: LittleMouse --- m5stack/libs/module/llm.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/m5stack/libs/module/llm.py b/m5stack/libs/module/llm.py index 325c749b..0f29c35b 100644 --- a/m5stack/libs/module/llm.py +++ b/m5stack/libs/module/llm.py @@ -836,6 +836,7 @@ def _free_temp(self): class ApiYolo: + _MODULE_LLM_OK = 0 _MODULE_LLM_WAIT_RESPONSE_TIMEOUT = -97 def __init__(self, module_msg): @@ -871,6 +872,17 @@ def setup( self._free_temp() return ret_work_id + def inference_img(self, work_id, image_data, request_id="yolo_inference") -> str: + cmd = { + "request_id": request_id, + "work_id": work_id, + "action": "inference", + "object": "cv.jpeg.stream.base64", + "data": {"delta": image_data, "index": 0, "finish": True}, + } + self._module_msg.send_cmd(ujson.dumps(cmd)) + return self._MODULE_LLM_OK + def _set_yolo_work_id(self, msg): if "work_id" in msg: self._yolo_work_id = msg["work_id"] @@ -1122,6 +1134,11 @@ def yolo_setup( ) return self.latest_yolo_work_id + def yolo_inference_img(self, work_id, image_data, request_id="yolo_inference") -> str: + encoded_image = self.base64_encode(image_data) + self.latest_error_code = self.yolo.inference_img(work_id, encoded_image, request_id) + return self.latest_error_code + def tts_setup( self, language="en_US", From 6b3133a641307bf4f121dc15b8039d97dce8c128 Mon Sep 17 00:00:00 2001 From: LittleMouse Date: Wed, 11 Jun 2025 11:39:56 +0800 Subject: [PATCH 120/322] libs/module/llm.py: Delete debug print. Signed-off-by: LittleMouse --- m5stack/libs/module/llm.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/m5stack/libs/module/llm.py b/m5stack/libs/module/llm.py index 0f29c35b..383eee2e 100644 --- a/m5stack/libs/module/llm.py +++ b/m5stack/libs/module/llm.py @@ -12,8 +12,6 @@ def __init__(self, uart): self._serial = uart def send_cmd(self, cmd): - # print(f"[DEBUG] Sending command: {cmd}") - print("[DEBUG] Sending command (first 100 characters):", cmd[:200]) self._serial.write(cmd) def get_response(self, timeout=5000): @@ -58,7 +56,6 @@ def add_msg_from_response(self, response): doc = ujson.loads(json_str) # Push into resonse msg list self.response_msg_list.append(doc) - # print(f"[DEBUG] Received message:\n{doc}") except ValueError: continue return From 279676805fe757e412d2cc190f6254d321db9ac1 Mon Sep 17 00:00:00 2001 From: LittleMouse Date: Thu, 12 Jun 2025 19:17:01 +0800 Subject: [PATCH 121/322] libs/module/llm.py: Fix MeloTTS setup function. Signed-off-by: LittleMouse --- m5stack/libs/module/llm.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/m5stack/libs/module/llm.py b/m5stack/libs/module/llm.py index 383eee2e..1a54e489 100644 --- a/m5stack/libs/module/llm.py +++ b/m5stack/libs/module/llm.py @@ -889,12 +889,12 @@ def _free_temp(self): class LlmModule: - def __init__(self, uart_id=1, tx=17, rx=16) -> None: + def __init__(self, uart_id=1, tx=17, rx=16, baudrate=115200) -> None: self._uart = machine.UART( uart_id, tx=tx, rx=rx, - baudrate=115200, + baudrate=baudrate, bits=8, parity=None, stop=1, @@ -1175,21 +1175,22 @@ def tts_inference(self, work_id, input_data, timeout=0, request_id="tts_inferenc def melotts_setup( self, - language="en_US", + language=None, model="melotts_zh-cn", response_format="sys.pcm", input=None, enoutput=False, enkws=None, - request_id="tts_setup", + request_id="melotts_setup", ) -> str: - model = "melotts-zh-cn" if float(self.version.lstrip("v")) >= 1.6 else model + if float(self.version.lstrip("v")) >= 1.6 and model == "melotts_zh-cn": + model = "melotts-zh-cn" if float(self.version.lstrip("v")) >= 1.6: if language == "zh_CN": model = "melotts-zh-cn" elif language == "ja_JP": model = "melotts-ja-jp" - else: + elif language == "en_US": model = "melotts-en-default" if input is None: input = ["tts.utf-8.stream"] From 5c8fac314241403da3f21995860d658be522102b Mon Sep 17 00:00:00 2001 From: tinyu Date: Fri, 13 Jun 2025 12:04:39 +0800 Subject: [PATCH 122/322] makeing.py: Change the prompt file path to an absolute path. Signed-off-by: tinyu --- m5stack/makeimg.py | 8 ++++---- third-party/makeimg.py | 5 +++-- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/m5stack/makeimg.py b/m5stack/makeimg.py index 63f52a30..865f0cf6 100644 --- a/m5stack/makeimg.py +++ b/m5stack/makeimg.py @@ -132,6 +132,7 @@ def load_partition_table(filename): files_in.pop() file_out = arg_output_bin +file_out_complete = os.path.abspath(arg_output_bin) # Write output file with combined firmware. cur_offset = 0 @@ -161,7 +162,7 @@ def load_partition_table(filename): "\r\nWrote 0x%x bytes to file m5stack/%s, ready to flash to offset 0x%x.\r\n\r\n" "\033[1;32mExample command:\033[0m\r\n" " \033[1;33m1.\033[0m make BOARD=%s BOARD_TYPE=%s PORT=/dev/ttyUSBx flash\r\n" - " \033[1;33m2.\033[0m esptool.py --chip %s --port /dev/ttyUSBx --baud 1500000 write_flash 0x%x m5stack/%s" + " \033[1;33m2.\033[0m esptool.py --chip %s --port /dev/ttyUSBx --baud 1500000 write_flash 0x%x %s" % ( cur_offset, file_out, @@ -170,7 +171,7 @@ def load_partition_table(filename): arg_board_type_flag.lower(), idf_target.lower(), 0x0, - file_out, + file_out_complete, ) ) @@ -206,7 +207,7 @@ def load_partition_table(filename): uiflow_version = f.readline() + "-" release_file_out = "{}-{}-{}{}-{}{}{}{}.bin".format( - file_out.split(".bin")[0], + file_out_complete.split(".bin")[0], idf_target.lower(), feature_str.lower(), load_sdkconfig_flash_size_value(arg_sdkconfig).lower(), @@ -219,7 +220,6 @@ def load_partition_table(filename): "\033[1;32mRelease Firmware:\033[0m\r\n \033[1;33m" + arg_board_type_flag[:-1].upper() + ":\033[0m " - + "m5stack/" + release_file_out ) os.system("cp {} {}".format(file_out, release_file_out)) diff --git a/third-party/makeimg.py b/third-party/makeimg.py index d946fa33..576dd8d0 100644 --- a/third-party/makeimg.py +++ b/third-party/makeimg.py @@ -132,6 +132,7 @@ def load_partition_table(filename): files_in.pop() file_out = arg_output_bin +file_out_complete = os.path.abspath(arg_output_bin) # Write output file with combined firmware. cur_offset = 0 @@ -170,7 +171,7 @@ def load_partition_table(filename): arg_board_type_flag.lower(), idf_target.lower(), 0x0, - file_out, + file_out_complete, ) ) @@ -206,7 +207,7 @@ def load_partition_table(filename): uiflow_version = f.readline() + "-" release_file_out = "{}-{}-{}{}-{}{}{}{}.bin".format( - file_out.split(".bin")[0], + file_out_complete.split(".bin")[0], idf_target.lower(), feature_str.lower(), load_sdkconfig_flash_size_value(arg_sdkconfig).lower(), From e177268be66710410960590cd2c03bac9e14c52f Mon Sep 17 00:00:00 2001 From: luoweiyuan Date: Thu, 12 Jun 2025 15:02:51 +0800 Subject: [PATCH 123/322] lib/base: Fix missing ADC pin config for AtomicStepmotoeBase. Signed-off-by: luoweiyuan --- docs/en/base/stepmotor.rst | 22 +-- .../zh_CN/LC_MESSAGES/base/stepmotor.po | 187 ++++++++++-------- ...r_stepmotor_direction_control_example.m5f2 | 2 +- ...s3r_stepmotor_direction_control_example.py | 6 +- ...ms3r_stepmotor_rotate_control_example.m5f2 | 2 +- ...toms3r_stepmotor_rotate_control_example.py | 13 +- m5stack/libs/base/stepmotor.py | 21 +- 7 files changed, 144 insertions(+), 109 deletions(-) diff --git a/docs/en/base/stepmotor.rst b/docs/en/base/stepmotor.rst index 09c43ac0..a3e0bf27 100644 --- a/docs/en/base/stepmotor.rst +++ b/docs/en/base/stepmotor.rst @@ -1,5 +1,5 @@ Atomic Stepmotor Base -============================ +===================== .. sku: A132 @@ -11,11 +11,11 @@ Support the following products: |Atomic Stepmotor Base| -UiFlow2 Example: --------------------------- +UiFlow2 Example +--------------- Direction control -^^^^^^^^^^^^^^^^^^^^^^^^ +^^^^^^^^^^^^^^^^^ Open the |atoms3r_stepmotor_direction_control_example.m5f2| project in UiFlow2. @@ -30,7 +30,7 @@ Example output: None Rotate control -^^^^^^^^^^^^^^^^^^^^^^^^ +^^^^^^^^^^^^^^ Open the |atoms3r_stepmotor_rotate_control_example.m5f2| project in UiFlow2. @@ -44,11 +44,11 @@ Example output: None -MicroPython Example: --------------------------- +MicroPython Example +------------------- Direction control -^^^^^^^^^^^^^^^^^^^^^^^^ +^^^^^^^^^^^^^^^^^ The example demonstrates motor direction control. Pressing the screen button toggles the rotation direction. @@ -63,7 +63,7 @@ Example output: None Rotate control -^^^^^^^^^^^^^^^^^^^^^^^^ +^^^^^^^^^^^^^^ The example demonstrates the motor continuously rotating for multiple turns, then reversing for multiple turns, and repeating the cycle after a 2-second pause. @@ -78,10 +78,10 @@ Example output: None **API** --------------------------- +------- AtomicStepmotorBase -^^^^^^^^^^^^^^^^^^^^^^^^ +^^^^^^^^^^^^^^^^^^^ .. autoclass:: base.stepmotor.AtomicStepmotorBase :members: diff --git a/docs/locales/zh_CN/LC_MESSAGES/base/stepmotor.po b/docs/locales/zh_CN/LC_MESSAGES/base/stepmotor.po index 102d1b49..f7f7b4ae 100644 --- a/docs/locales/zh_CN/LC_MESSAGES/base/stepmotor.po +++ b/docs/locales/zh_CN/LC_MESSAGES/base/stepmotor.po @@ -9,7 +9,7 @@ msgid "" msgstr "" "Project-Id-Version: UIFlow2 Programming Guide \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-02-28 18:06+0800\n" +"POT-Creation-Date: 2025-06-12 10:08+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language: zh_CN\n" @@ -18,46 +18,52 @@ msgstr "" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" -"Generated-By: Babel 2.16.0\n" +"Generated-By: Babel 2.17.0\n" #: ../../en/base/stepmotor.rst:2 ../../en/refs/base.stepmotor.ref -#: 0760d96173f948af9f06a4ce25e8be9c 0e9f6255029e4d319da8238ec5b76707 +#: 61b2280ec89d48cf8592e457b5201206 ec0308f044394de7bc1c92e009fe7e96 #, fuzzy msgid "Atomic Stepmotor Base" msgstr "创建一个 Stepmotor 对象。" -#: ../../en/base/stepmotor.rst:9 f3d588e6c2994b4095ebb3b3ee122e91 +#: ../../en/base/stepmotor.rst:9 58c6a5a0772646a1a7c162e4969d5b1e msgid "Support the following products:" msgstr "支持以下产品:" -#: ../../en/base/stepmotor.rst:11 1b19666dfcfb49e4b9ad38efe5f8a532 +#: ../../en/base/stepmotor.rst:11 9b52592b9cd24537acebe21093bb2d01 msgid "|Atomic Stepmotor Base|" msgstr "" -#: ../../en/base/stepmotor.rst:15 c6988c41ec3b4204913eb821e09d519f -msgid "UiFlow2 Example:" +#: ../../en/base/stepmotor.rst:15 8680424a662142458c5cb1ed5479245b +#, fuzzy +msgid "UiFlow2 Example" msgstr "UiFlow2 应用示例:" #: ../../en/base/stepmotor.rst:18 ../../en/base/stepmotor.rst:51 -#: f39989ba3f39496c984bcd711cfaf434 +#: bd2b57a98c47446aab7203da186d8211 ea5849d487b04b3aae7633ba983c106d msgid "Direction control" msgstr "方向控制" -#: ../../en/base/stepmotor.rst:20 8729b223faea46acb1d3abbb01535f4a +#: ../../en/base/stepmotor.rst:20 595341fb45494d3896b5456ebdaec9c9 msgid "" "Open the |atoms3r_stepmotor_direction_control_example.m5f2| project in " "UiFlow2." msgstr "在 UiFlow2 上打开 |atoms3r_stepmotor_direction_control_example.m5f2| 项目。" #: ../../en/base/stepmotor.rst:22 ../../en/base/stepmotor.rst:53 -#: 35002b2a22ab4c84a5806681613410dc +#: 77437c42e34c4b8bb60f09046f1af90a 8457256bae3843eb90cbabfa6e9aac48 msgid "" "The example demonstrates motor direction control. Pressing the screen " "button toggles the rotation direction." msgstr "示例程序演示电机旋转方向控制,按下屏幕按键,切换旋转方向" #: ../../en/base/stepmotor.rst:24 ../../en/base/stepmotor.rst:39 -#: 0d45dbc88a5d4dc9903c786af65f9016 base.stepmotor.AtomicStepmotorBase:9 +#: 01bb4497839d4ac3994aa01550a090c2 3315562191124df1b301ee213edfe280 +#: 33fc0fdd5beb4047b3ab192eca978850 3a5e7f60c0214e759de351210560a1d0 +#: 440fde6530774d9388dee11e073a5f8c 639715640c194c6fad5a6a62522fc9f3 +#: 728d5e16ea89471e8d6aed4dc8c4da28 77431f5595d44b159ec8966f173f0319 +#: 813a671c35d54e1c84778297fad48592 89a5ae8d88914e38a52de60102a7d62e +#: base.stepmotor.AtomicStepmotorBase:10 #: base.stepmotor.AtomicStepmotorBase.disable:3 #: base.stepmotor.AtomicStepmotorBase.enable:3 #: base.stepmotor.AtomicStepmotorBase.get_status:6 @@ -66,63 +72,71 @@ msgstr "示例程序演示电机旋转方向控制,按下屏幕按键,切换 #: base.stepmotor.AtomicStepmotorBase.rotate:9 #: base.stepmotor.AtomicStepmotorBase.set_direction:5 #: base.stepmotor.AtomicStepmotorBase.step:3 -#: base.stepmotor.AtomicStepmotorBase.stop:3 of +#: base.stepmotor.AtomicStepmotorBase.stop:3 e307b1500a7044c3bf4a26f09eae5094 +#: e8e9555137594bd2b57eb534c0a6a738 of msgid "UiFlow2 Code Block:" msgstr "UiFlow2 代码块:" -#: ../../en/base/stepmotor.rst:26 ab3e3763a95b41bb8f732dfab2d7d7cf +#: ../../en/base/stepmotor.rst:26 df990fccab0d4fba8cc75d8a368382bb msgid "|atoms3r_stepmotor_direction_control_example.png|" msgstr "" -#: ../../en/refs/base.stepmotor.ref:17 02afcc4751664593bea876f6002344d5 +#: ../../en/refs/base.stepmotor.ref:17 5ce9dcf8ba7c4edda3658910f382e9c0 msgid "atoms3r_stepmotor_direction_control_example.png" msgstr "" #: ../../en/base/stepmotor.rst:28 ../../en/base/stepmotor.rst:43 #: ../../en/base/stepmotor.rst:61 ../../en/base/stepmotor.rst:76 -#: 53befa15fbba414fae94b4666799adcb +#: 421ee83df6a048c38f313084c3586489 493f1106a05a4508aff81d5ed80e953a +#: f1e145c22903443f84bf631aaa61392c f483d986c1a347a582a7e648aea2cdc8 msgid "Example output:" msgstr "示例输出:" #: ../../en/base/stepmotor.rst:30 ../../en/base/stepmotor.rst:45 #: ../../en/base/stepmotor.rst:63 ../../en/base/stepmotor.rst:78 -#: 491e7d3d321f4a6b809ba99f08dc1228 +#: 2d410f383c024188b9555e43f850be7f 448575eda8ce43b7970ddae78caf730e +#: 500dafb979564b28ac80431a69aea109 9b215e02d0ac41168a2e945fed8a793e msgid "None" msgstr "无" #: ../../en/base/stepmotor.rst:33 ../../en/base/stepmotor.rst:66 -#: 8f0e8a7ac03844998e1458cb5ce6fd8c +#: 8846af9bbdf54d21bd0f7a568b5fdd5a f268b61c506745ada57170d564013f0b msgid "Rotate control" msgstr "旋转控制" -#: ../../en/base/stepmotor.rst:35 8729b223faea46acb1d3abbb01535f4a +#: ../../en/base/stepmotor.rst:35 aa5557d5d4234ed29fbf7d995178ff3d msgid "" "Open the |atoms3r_stepmotor_rotate_control_example.m5f2| project in " "UiFlow2." msgstr "在 UiFlow2 上打开 |atoms3r_stepmotor_rotate_control_example.m5f2| 项目。" #: ../../en/base/stepmotor.rst:37 ../../en/base/stepmotor.rst:68 -#: 9399b52d3a544f39b8cd492e7064d705 +#: cae6b558aa8841f0855da408e8839a37 fa33652409c043de981c9b87c24ec758 msgid "" "The example demonstrates the motor continuously rotating for multiple " "turns, then reversing for multiple turns, and repeating the cycle after a" " 2-second pause." msgstr "示例程序演示电机连续旋转控制多圈,再反向旋转多圈,等待2秒后重复。 " -#: ../../en/base/stepmotor.rst:41 7daa32ac857d49f59f8efc7f5cd558b5 +#: ../../en/base/stepmotor.rst:41 c960968b63624f7bbdef4cbcb8b83b13 msgid "|atoms3r_stepmotor_rotate_control_example.png|" msgstr "" -#: ../../en/refs/base.stepmotor.ref:18 c8764e1d822f4e51b11420b4c9c3766f +#: ../../en/refs/base.stepmotor.ref:18 4bb3adcd4275400d8e6a8c3f0447dd89 msgid "atoms3r_stepmotor_rotate_control_example.png" msgstr "" -#: ../../en/base/stepmotor.rst:48 11a6b4dd714d489f909ca9e00fdd76f3 -msgid "MicroPython Example:" +#: ../../en/base/stepmotor.rst:48 8df63250bf914c1e842757051cbf1658 +#, fuzzy +msgid "MicroPython Example" msgstr "MicroPython 应用示例:" #: ../../en/base/stepmotor.rst:55 ../../en/base/stepmotor.rst:70 -#: 0663e2b02ab946d8bef40857721f7681 base.stepmotor.AtomicStepmotorBase:13 +#: 010c06a5ac924fa69c92427118c60d8c 162e63e184ff494d9ccdbfb779b131fb +#: 620c36ed302743cea47d08489f9d5fa0 6d7e0219dc3e43f1a941e6181392eb5c +#: 8ea55010fcd64f3ca7034784cd1963fe 9aa121d2391646a197f4ef870a86ec68 +#: a428a9f8a24f4cfe8136056f93854551 a5b8d00166054b0798bd63244c522359 +#: ba9335d5ec6044e6be8ed2b923ea7ab1 base.stepmotor.AtomicStepmotorBase:14 #: base.stepmotor.AtomicStepmotorBase.disable:7 #: base.stepmotor.AtomicStepmotorBase.enable:7 #: base.stepmotor.AtomicStepmotorBase.get_status:10 @@ -131,240 +145,249 @@ msgstr "MicroPython 应用示例:" #: base.stepmotor.AtomicStepmotorBase.rotate:13 #: base.stepmotor.AtomicStepmotorBase.set_direction:9 #: base.stepmotor.AtomicStepmotorBase.step:7 -#: base.stepmotor.AtomicStepmotorBase.stop:7 of +#: base.stepmotor.AtomicStepmotorBase.stop:7 d04595bee22d40df86c758f2a3a0e55b +#: e132e5b944cc4e7282689fa5b8ebcc4c e527aaa394b947a1b84693aa8df5123e of msgid "MicroPython Code Block:" msgstr "MicroPython 代码块:" -#: ../../en/base/stepmotor.rst:81 f1921bde992f45ca8f705b1865f4a90d +#: ../../en/base/stepmotor.rst:81 4753a7ae4b1345aa97079cb45fe5b99f msgid "**API**" msgstr "API应用" -#: base.stepmotor.AtomicStepmotorBase:1 e6ddec277e8f4ffd8c27649b8850a489 of +#: ../../en/base/stepmotor.rst:84 a8ea1c24c2014498a51fac641dd4ef95 msgid "AtomicStepmotorBase" msgstr "" -#: base.stepmotor.AtomicStepmotorBase:1 c8eb7e1c7c3e4962a34fda30400394f8 of +#: base.stepmotor.AtomicStepmotorBase:1 c54c4457aab34108876d3949c3b1b061 of msgid "Bases: :py:class:`object`" msgstr "" -#: base.stepmotor.AtomicStepmotorBase:1 e6ddec277e8f4ffd8c27649b8850a489 of +#: 1dfebda23f3d416ebf8bfa3832ded9c9 base.stepmotor.AtomicStepmotorBase:1 of msgid "Create an AtomicStepmotorBase object." msgstr "创建一个 AtomicStepmotorBase 对象。" -#: ../../en/base/stepmotor.rst 82d6c9722e9d4148bad28eb830a0d17f -#: base.stepmotor.AtomicStepmotorBase.rotate -#: base.stepmotor.AtomicStepmotorBase.set_direction of +#: ../../en/base/stepmotor.rst 7937b51a6f7849b9a0b73215ad13d0a5 +#: 7dc53894b19e4db9be5399a5d661bfae base.stepmotor.AtomicStepmotorBase.rotate +#: base.stepmotor.AtomicStepmotorBase.set_direction +#: c4f38ebfda024b949e474f436ae7b898 of msgid "Parameters" msgstr "" -#: 956e1f8c62c443f780658ca63d22cec7 base.stepmotor.AtomicStepmotorBase:3 of +#: 8058c1a421d340298bc528f6a84b8f3a base.stepmotor.AtomicStepmotorBase:3 of msgid "Enable pin, used to enable or disable the stepper motor." msgstr "使能引脚,用于启用或禁用步进电机。" -#: base.stepmotor.AtomicStepmotorBase:4 d4944507ad5c4e52baec38d8b0c95e13 of +#: base.stepmotor.AtomicStepmotorBase:4 c62844479f0a485582ae0d97f7b464cf of msgid "Direction pin, used to control the rotation direction of the motor." msgstr "方向引脚,用于控制电机的旋转方向。" -#: base.stepmotor.AtomicStepmotorBase:5 c99e9343f1124ad6b7d517d9ffd0c568 of +#: base.stepmotor.AtomicStepmotorBase:5 dfaea44bd7704b7f95f14f390d399363 of msgid "Step pin, used for step control of the motor." msgstr "步进引脚,用于步进控制。" -#: 695ed6f18cd4437d80116f3cabc35952 base.stepmotor.AtomicStepmotorBase:6 of +#: 5b3b725f5e764b97a655fb2161a3ae7e base.stepmotor.AtomicStepmotorBase:6 of msgid "Fault pin, used to monitor the motor's fault status." msgstr "故障引脚,用于监控电机故障状态。" -#: 0494fe5d9b8447918e9d45366bea3e63 base.stepmotor.AtomicStepmotorBase:7 of +#: base.stepmotor.AtomicStepmotorBase:7 f1af178db7964dee83898d98d581a5fe of msgid "Reset pin, used to reset the motor driver." msgstr "复位引脚,用于复位电机驱动器。" -#: 02c1c594f76847a68adbcea45370ab35 base.stepmotor.AtomicStepmotorBase:11 of +#: 589c13e694d845f0a5c6a7ad9863e384 base.stepmotor.AtomicStepmotorBase:8 of +msgid "Power ADC monitoring pin, used to measure the input power supply voltage." +msgstr "电源ADC监测引脚,用于测量输入电源电压。" + +#: 4638f5959e044303b65605f1cc4694c5 base.stepmotor.AtomicStepmotorBase:12 of msgid "|init.png|" msgstr "" -#: ../../en/refs/base.stepmotor.ref:7 8afc8f9b69e740079fb4bd041c2843a7 +#: ../../en/refs/base.stepmotor.ref:7 7dd11705cbb94a1e9093df2291535caf msgid "init.png" msgstr "" #: base.stepmotor.AtomicStepmotorBase.disable:1 -#: cd8ad572fec8430ea18fa56a0737fd5b of +#: e6b334330a6b4fb4ab916d09110c7350 of msgid "Disable the stepper motor driver." msgstr "禁用电机驱动。" -#: 05117074a45e48d9b3dd29eeb9cc5c2c +#: 4bd8c0d346aa4412b09e63a3450e34f6 #: base.stepmotor.AtomicStepmotorBase.disable:5 of msgid "|disable.png|" msgstr "" -#: ../../en/refs/base.stepmotor.ref:9 630a952504c1413abaa4d700ec290e30 +#: ../../en/refs/base.stepmotor.ref:9 87e768bd0b2343d9a9f45c37aa06ad46 msgid "disable.png" msgstr "" -#: 1554d9427c334840a92c8bd727f32dae base.stepmotor.AtomicStepmotorBase.enable:1 +#: base.stepmotor.AtomicStepmotorBase.enable:1 d6b007f65d0243ec9384e478a5364bbf #: of msgid "Enable the stepper motor driver." msgstr "启用电机驱动。" -#: b03032626afb4e29911c8a24a32e1414 base.stepmotor.AtomicStepmotorBase.enable:5 +#: 0e371847a71c4fcb8ba84fd8d53c8fb0 base.stepmotor.AtomicStepmotorBase.enable:5 #: of msgid "|enable.png|" msgstr "" -#: ../../en/refs/base.stepmotor.ref:8 a8730defa5904c3694a7ace61c021e39 +#: ../../en/refs/base.stepmotor.ref:8 a44ff18dda4d4666816d57c65362a797 msgid "enable.png" msgstr "" -#: 719977b7033849e18d056f786715da18 base.stepmotor.AtomicStepmotorBase.enable +#: 142ad02c61fd4a2c9811f2a39c0dea58 6b29260bc22f47c297b028b605e90a7e +#: 96da3240e04c40c48be2dc989dbba15c ab2bf2d9ca744e3ba90791a004cefd13 +#: adec7d402ff04a65a0885adcd5c2b269 base.stepmotor.AtomicStepmotorBase.enable #: base.stepmotor.AtomicStepmotorBase.get_status #: base.stepmotor.AtomicStepmotorBase.get_voltage #: base.stepmotor.AtomicStepmotorBase.reset #: base.stepmotor.AtomicStepmotorBase.rotate #: base.stepmotor.AtomicStepmotorBase.set_direction #: base.stepmotor.AtomicStepmotorBase.step -#: base.stepmotor.AtomicStepmotorBase.stop of +#: base.stepmotor.AtomicStepmotorBase.stop d15f932012214753895aafa9621182bf +#: e8fcc34ce37d44f4b59d46979b74323c f039e656b6e642069e48ebc6a12d9f92 of msgid "Return type" msgstr "" -#: 814b3f08d42744c5886200fa941e0668 +#: 391d6ec1042c41a9bc59e75404774255 #: base.stepmotor.AtomicStepmotorBase.get_status:1 of msgid "Get motor driver status." msgstr "获取电机驱动状态" -#: 41e39715d3804e7094ed383720dfa8ec +#: a3cb5741134c4f3e8cfbac5cfb20a8f2 #: base.stepmotor.AtomicStepmotorBase.get_status -#: base.stepmotor.AtomicStepmotorBase.get_voltage of +#: base.stepmotor.AtomicStepmotorBase.get_voltage +#: efa668a6a4b547a99fc4b645649ea630 of msgid "Returns" msgstr "" -#: 08b9d473d1904987928596fd29d45194 +#: 4905761396a4443f81112a52365450d5 #: base.stepmotor.AtomicStepmotorBase.get_status:3 of msgid "" "Returns True if the driver is operating normally, or False if a fault is " "detected." msgstr "正常工作时返回 True,故障时返回 False。 " -#: 5c86fca2044c43ada204d1bc191fc411 +#: 816f566fd7e04dd59c2bcd1a1302be68 #: base.stepmotor.AtomicStepmotorBase.get_status:8 of msgid "|get_status.png|" msgstr "" -#: ../../en/refs/base.stepmotor.ref:14 359adc03d5574bb5bea82ce964c7d2e2 +#: ../../en/refs/base.stepmotor.ref:14 36e8653292ca4aa4bd1bee8ea5769abe msgid "get_status.png" msgstr "" -#: 09121e271dbe449495a6f2585b6251c4 +#: 9fcdbc8f0051456aadef8e17e1308b7e #: base.stepmotor.AtomicStepmotorBase.get_voltage:1 of msgid "Get voltage." msgstr "获取电压" -#: 5fe1c99937bd46ea9f62a95cff9b9601 +#: 9620c67bb05b486ab39868e37086cea8 #: base.stepmotor.AtomicStepmotorBase.get_voltage:3 of msgid "The driver input voltage. unit: V" msgstr "驱动输入电压,单位:V。" -#: 3b15a2eecbd94724aa78ecbd8cda47e6 +#: 962862a9ad754eeb975bf9422031cd7f #: base.stepmotor.AtomicStepmotorBase.get_voltage:8 of msgid "|get_voltage.png|" msgstr "" -#: ../../en/refs/base.stepmotor.ref:16 caee51cc65bc42c9954af7945167ca29 +#: ../../en/refs/base.stepmotor.ref:16 91f90f416c164eaeabc3156a24c6d2dd msgid "get_voltage.png" msgstr "" -#: b9cc0761da5843e39781e569e48af880 base.stepmotor.AtomicStepmotorBase.reset:1 +#: 37ada378cbe443f0829f2a7fcee68cba base.stepmotor.AtomicStepmotorBase.reset:1 #: of msgid "Reset the stepper motor driver." msgstr "复位电机驱动。" -#: base.stepmotor.AtomicStepmotorBase.reset:5 df229ad0ad8c428880d5b6c9d02b9727 +#: base.stepmotor.AtomicStepmotorBase.reset:5 d35a6d8564304ccaa7848ac9cbee1067 #: of msgid "|reset.png|" msgstr "" -#: ../../en/refs/base.stepmotor.ref:15 d513ba7d45a64ce280d85749c4d12fb7 +#: ../../en/refs/base.stepmotor.ref:15 6fa8968260dc4ef3991a66c40cccd1d1 msgid "reset.png" msgstr "" -#: 6c18afbe4f2c42d4bd4b4ab91c228f20 base.stepmotor.AtomicStepmotorBase.rotate:1 +#: 41ac7abe89034db3a4bce3806a1c419a base.stepmotor.AtomicStepmotorBase.rotate:1 #: of msgid "Rotate the stepper motor for a specified number of steps." msgstr "旋转步进电机一定的步数。" -#: 6bf481d425e64c20875c16095a16b98e base.stepmotor.AtomicStepmotorBase.rotate:3 +#: aa0a4f4c02444fb79b3bade021951a27 base.stepmotor.AtomicStepmotorBase.rotate:3 #: of msgid "Number of steps to rotate." msgstr "转动步数。" -#: base.stepmotor.AtomicStepmotorBase.rotate:4 bdf00014e91b4d0591917c99d4b60aa9 +#: 823f67853657437486b5b99de73f6fb9 base.stepmotor.AtomicStepmotorBase.rotate:4 #: of msgid "Delay between steps (in milliseconds), default is 0ms." msgstr "步间延时(ms),默认为 0 ms。" -#: 46cb58fc0f5d488fbc281414428a5210 base.stepmotor.AtomicStepmotorBase.rotate:5 +#: base.stepmotor.AtomicStepmotorBase.rotate:5 c8d1774846e447149b85105dc907ccdf #: of msgid "Rotation direction (True or False)." msgstr "旋转方向" -#: 5d8512f584284d6dad2505ed96016f69 base.stepmotor.AtomicStepmotorBase.rotate:7 +#: base.stepmotor.AtomicStepmotorBase.rotate:7 c395e38acd1c45d5bf4782646c4db515 #: of msgid "" "The actual rotation direction (clockwise or counterclockwise) depends on " "the motor wiring." msgstr "实际旋转方向(顺时针或逆时针)取决于电机的接线方式。" -#: a76b1da5a85f4d25ba780a70784a5acf -#: base.stepmotor.AtomicStepmotorBase.rotate:11 of +#: base.stepmotor.AtomicStepmotorBase.rotate:11 +#: d580ccd0b09c49ffb9bfe85fd19350c5 of msgid "|rotate.png|" msgstr "" -#: ../../en/refs/base.stepmotor.ref:12 68f2b93a75f048fe92075cfbab18c9ad +#: ../../en/refs/base.stepmotor.ref:12 33084ecbe552453d9474b99eea398059 msgid "rotate.png" msgstr "" -#: 14d6bcbed2634f479005974785a4d60a -#: base.stepmotor.AtomicStepmotorBase.set_direction:1 of +#: base.stepmotor.AtomicStepmotorBase.set_direction:1 +#: c70cf6f8c40141dca14f6ae7b59192ad of msgid "Set direction." msgstr "设置方向。" -#: base.stepmotor.AtomicStepmotorBase.set_direction:3 -#: c1e0f16f17b54e41845e4e90c2272bc9 of +#: 1880247ab8e14520938afe1695e47681 +#: base.stepmotor.AtomicStepmotorBase.set_direction:3 of msgid "Rotation direction. True or False." msgstr "旋转方向 True 或 False。" -#: 854d2de00ee346239c8d3719f1a4a7fe -#: base.stepmotor.AtomicStepmotorBase.set_direction:7 of +#: base.stepmotor.AtomicStepmotorBase.set_direction:7 +#: e4ea88c330fc460192c3ef1e158b35ed of msgid "|set_direction.png|" msgstr "" -#: ../../en/refs/base.stepmotor.ref:10 26b7682884864ad5b0b27bf5f90a987c +#: ../../en/refs/base.stepmotor.ref:10 cabfaaaf209b44f9a4902587d0733fd9 msgid "set_direction.png" msgstr "" -#: base.stepmotor.AtomicStepmotorBase.step:1 fbf2512e755a46c8b481237424ab7713 +#: 424999e172b2476ea6e1810c43b4bf5e base.stepmotor.AtomicStepmotorBase.step:1 #: of msgid "Move the stepper motor one step." msgstr "电机步进一步" -#: b6c229bacdf044d98359e138473abb14 base.stepmotor.AtomicStepmotorBase.step:5 +#: b2fa804e47b247198f891f5494e7ccfc base.stepmotor.AtomicStepmotorBase.step:5 #: of msgid "|step.png|" msgstr "" -#: ../../en/refs/base.stepmotor.ref:11 55c2574f2716453f921292e2f1e7c655 +#: ../../en/refs/base.stepmotor.ref:11 bc1987d54f0f49999327077b551ae54f msgid "step.png" msgstr "" -#: base.stepmotor.AtomicStepmotorBase.stop:1 e50a34281aa34d669b43780891c95491 +#: 70e54e7e4865465abca5db992776fa63 base.stepmotor.AtomicStepmotorBase.stop:1 #: of msgid "Stop motor." msgstr "停止电机。" -#: 5a91a575bd9149638caa25c07e1e68e0 base.stepmotor.AtomicStepmotorBase.stop:5 +#: 71f9e37576804ab8a910eaa274decdc9 base.stepmotor.AtomicStepmotorBase.stop:5 #: of msgid "|stop.png|" msgstr "" -#: ../../en/refs/base.stepmotor.ref:13 cd1c5470354c49688ea2884d928161cb +#: ../../en/refs/base.stepmotor.ref:13 655a075d3698491881f852ae9ecc36a7 msgid "stop.png" msgstr "" - \ No newline at end of file diff --git a/examples/base/stepmotor/atoms3r_stepmotor_direction_control_example.m5f2 b/examples/base/stepmotor/atoms3r_stepmotor_direction_control_example.m5f2 index 3bc1f8bb..1c67f160 100644 --- a/examples/base/stepmotor/atoms3r_stepmotor_direction_control_example.m5f2 +++ b/examples/base/stepmotor/atoms3r_stepmotor_direction_control_example.m5f2 @@ -1 +1 @@ -{"version":"V2.0","versionNumber":"V2.2.2","type":"atoms3r","components":[{"name":"screen","type":"screen","layer":0,"screenId":"builtin","screenName":"","id":"__atoms3r_screen","createTime":1740735576486,"x":0,"y":0,"width":128,"height":128,"backgroundColor":"#000000","size":0,"isSelected":true},{"name":"title0","type":"title","layer":1,"screenId":"builtin","screenName":"","id":"j!zIttWWxtgzlq`x","createTime":1740736169832,"x":0,"y":0,"color":"#ffffff","backgroundColor":"#0000FF","text":"Steps Ctrl","textOffset":3,"font":"Widgets.FONTS.DejaVu18","isSelected":false},{"name":"label0","type":"label","layer":2,"screenId":"builtin","screenName":"","id":"nLEPjZXSscSdg*RF","createTime":1740736186436,"x":5,"y":35,"color":"#ffffff","backgroundColor":"#000000","text":"vol:","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false,"width":34,"height":21},{"name":"label_vol","type":"label","layer":3,"screenId":"builtin","screenName":"","id":"hkttkqz!C#ErJJKv","createTime":1740736192580,"x":43,"y":35,"color":"#ffffff","backgroundColor":"#000000","text":"12.0V","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false,"width":83,"height":21}],"resources":[{"hardware":["hardware_button","hardware_pin_button","imu","ir"]},{"base":["base_stepmotor"]}],"units":[],"hats":[],"bases":[{"type":"base_stepmotor","name":"base_stepmotor","id":"g^j+lkCs+^ClCb5Y","createTime":1740735581899,"initBlockType":"base_stepmotor_init","initBlockId":"9A|M.A3U1za_:=:aKv:["}],"i2cs":[],"blockly":"directiontrue5763839label_volLabelhello M5VdirectionTRUEBtnAWAS_CLICKEDdirectiondirectionFalsedirectiontrue1","screen":[{"simulationName":"Built-in","type":"builtin","width":128,"height":128,"scale":1.3,"screenName":"","blockId":"","screenColorType":0,"id":"builtin","createTime":1740735576485}],"logicWhenNum":0,"customList":[]} \ No newline at end of file +{"version":"V2.0","versionNumber":"V2.2.8","type":"atoms3r","components":[{"name":"screen","type":"screen","layer":0,"screenId":"builtin","screenName":"","id":"__atoms3r_screen","createTime":1740735576486,"x":0,"y":0,"width":128,"height":128,"backgroundColor":"#000000","size":0,"isSelected":true},{"name":"title0","type":"title","layer":1,"screenId":"builtin","screenName":"","id":"j!zIttWWxtgzlq`x","createTime":1740736169832,"x":0,"y":0,"color":"#ffffff","backgroundColor":"#0000FF","text":"Steps Ctrl","textOffset":3,"font":"Widgets.FONTS.DejaVu18","isSelected":false},{"name":"label0","type":"label","layer":2,"screenId":"builtin","screenName":"","id":"nLEPjZXSscSdg*RF","createTime":1740736186436,"x":5,"y":35,"color":"#ffffff","backgroundColor":"#000000","text":"vol:","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false},{"name":"label_vol","type":"label","layer":3,"screenId":"builtin","screenName":"","id":"hkttkqz!C#ErJJKv","createTime":1740736192580,"x":43,"y":35,"color":"#ffffff","backgroundColor":"#000000","text":"12.0V","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false}],"resources":[{"hardware":["hardware_button","hardware_pin_button","imu","ir"]},{"base":["base_stepmotor"]}],"units":[],"hats":[],"bases":[{"type":"base_stepmotor","name":"base_stepmotor","id":"oS!FQxmu&Vu4sg`a","createTime":1749689891438,"initBlockType":"base_stepmotor_init"}],"i2cs":[],"blockly":"directiontrue57638398label_volLabelhello M5VdirectionTRUEBtnAWAS_CLICKEDdirectiondirectionFalsedirectiontrue1","screen":[{"simulationName":"Built-in","type":"builtin","width":128,"height":128,"scale":1.3,"screenName":"","blockId":"","screenColorType":0,"id":"builtin","createTime":1740735576485}],"logicWhenNum":0,"customList":[]} \ No newline at end of file diff --git a/examples/base/stepmotor/atoms3r_stepmotor_direction_control_example.py b/examples/base/stepmotor/atoms3r_stepmotor_direction_control_example.py index 98f15f3d..4391d3d8 100644 --- a/examples/base/stepmotor/atoms3r_stepmotor_direction_control_example.py +++ b/examples/base/stepmotor/atoms3r_stepmotor_direction_control_example.py @@ -16,7 +16,7 @@ direction = None -def btn_was_clicked_event(state): +def btna_cliked_cb(state): global title0, label0, label_vol, base_stepmotor, direction direction = not direction base_stepmotor.set_direction(direction) @@ -28,8 +28,8 @@ def setup(): title0 = Widgets.Title("Steps Ctrl", 3, 0xFFFFFF, 0x0000FF, Widgets.FONTS.DejaVu18) label0 = Widgets.Label("vol:", 5, 35, 1.0, 0xFFFFFF, 0x000000, Widgets.FONTS.DejaVu18) label_vol = Widgets.Label("12.0V", 43, 35, 1.0, 0xFFFFFF, 0x000000, Widgets.FONTS.DejaVu18) - BtnA.setCallback(type=BtnA.CB_TYPE.WAS_CLICKED, cb=btn_was_clicked_event) - base_stepmotor = AtomicStepmotorBase(5, 7, 6, 38, 39) + BtnA.setCallback(type=BtnA.CB_TYPE.WAS_CLICKED, cb=btna_cliked_cb) + base_stepmotor = AtomicStepmotorBase(5, 7, 6, 38, 39, 8) label_vol.setText(str((str((base_stepmotor.get_voltage())) + str("V")))) direction = True diff --git a/examples/base/stepmotor/atoms3r_stepmotor_rotate_control_example.m5f2 b/examples/base/stepmotor/atoms3r_stepmotor_rotate_control_example.m5f2 index d3f69f3e..73a458fb 100644 --- a/examples/base/stepmotor/atoms3r_stepmotor_rotate_control_example.m5f2 +++ b/examples/base/stepmotor/atoms3r_stepmotor_rotate_control_example.m5f2 @@ -1 +1 @@ -{"version":"V2.0","versionNumber":"V2.2.2","type":"atoms3r","components":[{"name":"screen","type":"screen","layer":0,"screenId":"builtin","screenName":"","id":"__atoms3r_screen","createTime":1740735576486,"x":0,"y":0,"width":128,"height":128,"backgroundColor":"#000000","size":0,"isSelected":true},{"name":"title0","type":"title","layer":1,"screenId":"builtin","screenName":"","id":"j!zIttWWxtgzlq`x","createTime":1740736169832,"x":0,"y":0,"color":"#ffffff","backgroundColor":"#0000FF","text":"Steps Ctrl","textOffset":3,"font":"Widgets.FONTS.DejaVu18","isSelected":false},{"name":"label0","type":"label","layer":2,"screenId":"builtin","screenName":"","id":"nLEPjZXSscSdg*RF","createTime":1740736186436,"x":5,"y":35,"color":"#ffffff","backgroundColor":"#000000","text":"vol:","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false,"width":34,"height":21},{"name":"label_vol","type":"label","layer":3,"screenId":"builtin","screenName":"","id":"hkttkqz!C#ErJJKv","createTime":1740736192580,"x":43,"y":35,"color":"#ffffff","backgroundColor":"#000000","text":"12.0V","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false,"width":83,"height":21}],"resources":[{"hardware":["hardware_button","hardware_pin_button","imu","ir"]},{"base":["base_stepmotor"]}],"units":[],"hats":[],"bases":[{"type":"base_stepmotor","name":"base_stepmotor","id":"g^j+lkCs+^ClCb5Y","createTime":1740735581899,"initBlockType":"base_stepmotor_init","initBlockId":"9A|M.A3U1za_:=:aKv:["}],"i2cs":[],"blockly":"step_per_revmicrosteptotal_stepsrotate_circletrue5763839step_per_rev200microstepDIVIDE12rotate_circle5total_stepsMULTIPLY1DIVIDE1step_per_rev1microstep1rotate_circlelabel_volLabelhello M5Vtruehello M50total_steps1True1000total_steps1False100label_volLabelhello M5V2000","screen":[{"simulationName":"Built-in","type":"builtin","width":128,"height":128,"scale":1.3,"screenName":"","blockId":"","screenColorType":0,"id":"builtin","createTime":1740735576485}],"logicWhenNum":0,"customList":[]} \ No newline at end of file +{"version":"V2.0","versionNumber":"V2.2.8","type":"atoms3r","components":[{"name":"screen","type":"screen","layer":0,"screenId":"builtin","screenName":"","id":"__atoms3r_screen","createTime":1740735576486,"x":0,"y":0,"width":128,"height":128,"backgroundColor":"#000000","size":0,"isSelected":true},{"name":"title0","type":"title","layer":1,"screenId":"builtin","screenName":"","id":"j!zIttWWxtgzlq`x","createTime":1740736169832,"x":0,"y":0,"color":"#ffffff","backgroundColor":"#0000FF","text":"Steps Ctrl","textOffset":3,"font":"Widgets.FONTS.DejaVu18","isSelected":false},{"name":"label0","type":"label","layer":2,"screenId":"builtin","screenName":"","id":"nLEPjZXSscSdg*RF","createTime":1740736186436,"x":5,"y":35,"color":"#ffffff","backgroundColor":"#000000","text":"vol:","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false},{"name":"label_vol","type":"label","layer":3,"screenId":"builtin","screenName":"","id":"hkttkqz!C#ErJJKv","createTime":1740736192580,"x":43,"y":35,"color":"#ffffff","backgroundColor":"#000000","text":"12.0V","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false}],"resources":[{"hardware":["hardware_button","hardware_pin_button","imu","ir"]},{"base":["base_stepmotor"]}],"units":[],"hats":[],"bases":[{"type":"base_stepmotor","name":"base_stepmotor","id":"dS^8QW!WsQOx0%9R","createTime":1749689855436,"initBlockType":"base_stepmotor_init"}],"i2cs":[],"blockly":"step_per_revmicrosteprotate_circletotal_stepstruestep_per_rev200microstepDIVIDE12rotate_circle5total_stepsMULTIPLY1DIVIDE1step_per_rev1microstep1rotate_circle57638398label_volLabelhello M5Vtruehello M50total_steps1True1000total_steps1False100label_volLabelhello M5V2000","screen":[{"simulationName":"Built-in","type":"builtin","width":128,"height":128,"scale":1.3,"screenName":"","blockId":"","screenColorType":0,"id":"builtin","createTime":1740735576485}],"logicWhenNum":0,"customList":[]} \ No newline at end of file diff --git a/examples/base/stepmotor/atoms3r_stepmotor_rotate_control_example.py b/examples/base/stepmotor/atoms3r_stepmotor_rotate_control_example.py index 4642f345..5610a951 100644 --- a/examples/base/stepmotor/atoms3r_stepmotor_rotate_control_example.py +++ b/examples/base/stepmotor/atoms3r_stepmotor_rotate_control_example.py @@ -15,8 +15,8 @@ base_stepmotor = None step_per_rev = None microstep = None -total_steps = None rotate_circle = None +total_steps = None def setup(): @@ -27,18 +27,17 @@ def setup(): base_stepmotor, \ step_per_rev, \ microstep, \ - total_steps, \ - rotate_circle - + rotate_circle, \ + total_steps M5.begin() title0 = Widgets.Title("Steps Ctrl", 3, 0xFFFFFF, 0x0000FF, Widgets.FONTS.DejaVu18) label0 = Widgets.Label("vol:", 5, 35, 1.0, 0xFFFFFF, 0x000000, Widgets.FONTS.DejaVu18) label_vol = Widgets.Label("12.0V", 43, 35, 1.0, 0xFFFFFF, 0x000000, Widgets.FONTS.DejaVu18) - base_stepmotor = AtomicStepmotorBase(5, 7, 6, 38, 39) step_per_rev = 200 microstep = 1 / 2 rotate_circle = 5 total_steps = (step_per_rev / microstep) * rotate_circle + base_stepmotor = AtomicStepmotorBase(5, 7, 6, 38, 39, 8) label_vol.setText(str((str((base_stepmotor.get_voltage())) + str("V")))) @@ -50,8 +49,8 @@ def loop(): base_stepmotor, \ step_per_rev, \ microstep, \ - total_steps, \ - rotate_circle + rotate_circle, \ + total_steps M5.update() print(base_stepmotor.get_voltage()) base_stepmotor.rotate(total_steps, 1, True) diff --git a/m5stack/libs/base/stepmotor.py b/m5stack/libs/base/stepmotor.py index 8a7fe8bd..32bfd242 100644 --- a/m5stack/libs/base/stepmotor.py +++ b/m5stack/libs/base/stepmotor.py @@ -14,6 +14,7 @@ class AtomicStepmotorBase: :param int stp: Step pin, used for step control of the motor. :param int flt: Fault pin, used to monitor the motor's fault status. :param int rst: Reset pin, used to reset the motor driver. + :param int pwr_adc: Power ADC monitoring pin, used to measure the input power supply voltage. UiFlow2 Code Block: @@ -25,16 +26,25 @@ class AtomicStepmotorBase: from base import AtomicStepmotorBase - base_stepmotor = AtomicStepmotorBase(en=5, dir=7, stp=6, flt=38, rst=39) + base_stepmotor = AtomicStepmotorBase(en=5, dir=7, stp=6, flt=38, rst=39, pwr_adc=8) """ - def __init__(self, en: int = 5, dir: int = 7, stp: int = 6, flt: int = 38, rst: int = 39): + def __init__( + self, + en: int = 5, + dir: int = 7, + stp: int = 6, + flt: int = 38, + rst: int = 39, + pwr_adc: int = 8, + ): self.en_pin = machine.Pin(en, machine.Pin.OUT) self.dir_pin = machine.Pin(dir, machine.Pin.OUT) self.stp_pin = machine.Pin(stp, machine.Pin.OUT) self.flt_pin = machine.Pin(flt, machine.Pin.IN) if flt else None self.rst_pin = machine.Pin(rst, machine.Pin.OUT) if rst else None - self.adc = machine.ADC(machine.Pin(8), atten=machine.ADC.ATTN_11DB) + self.adc = machine.ADC(machine.Pin(pwr_adc), atten=machine.ADC.ATTN_11DB) + self.use_read_uv = hasattr(self.adc, "read_uv") self.enable() def enable(self) -> None: @@ -194,4 +204,7 @@ def get_voltage(self) -> float: base_stepmotor.get_voltage() """ - return self.adc.read_uv() * (6 / 1_000_000.0) # x6 是因为输入为 VIN 的 1/ 6 + if self.use_read_uv: + return self.adc.read_uv() * (6 / 1_000_000.0) # x6 是因为输入为 VIN 的 1/ 6 + else: + return ((self.adc.read() / 4096) * 3.6) / (1.5 / 9) * 0.99 From 9af5d74ec49e4cfaae5f7440be2b6c8ed33491e8 Mon Sep 17 00:00:00 2001 From: luoweiyuan Date: Thu, 12 Jun 2025 15:27:08 +0800 Subject: [PATCH 124/322] libs/module: Add support for Module13.2 LAN. Signed-off-by: luoweiyuan --- docs/en/module/index.rst | 1 + docs/en/module/lan.rst | 303 ++++++++++++ docs/en/refs/module.lan.ref | 33 ++ docs/locales/zh_CN/LC_MESSAGES/module/lan.po | 376 ++++++++++++++ .../lan/m5cores3_lan_module_example.m5f2 | 1 + .../module/lan/m5cores3_lan_module_example.py | 65 +++ m5stack/CMakeListsDefault.cmake | 2 +- m5stack/boards/M5STACK_Basic/sdkconfig.board | 2 + .../boards/M5STACK_Basic_4MB/sdkconfig.board | 2 + m5stack/boards/M5STACK_Core2/sdkconfig.board | 2 + m5stack/boards/M5STACK_CoreS3/sdkconfig.board | 2 + m5stack/boards/M5STACK_Fire/sdkconfig.board | 2 + m5stack/boards/M5STACK_Tough/sdkconfig.board | 2 + m5stack/libs/module/__init__.py | 1 + m5stack/libs/module/lan.py | 27 ++ m5stack/libs/module/manifest.py | 1 + m5stack/network_lan.c | 458 ++++++++++++++++++ m5stack/network_wlan.c | 13 + 18 files changed, 1292 insertions(+), 1 deletion(-) create mode 100644 docs/en/module/lan.rst create mode 100644 docs/en/refs/module.lan.ref create mode 100644 docs/locales/zh_CN/LC_MESSAGES/module/lan.po create mode 100644 examples/module/lan/m5cores3_lan_module_example.m5f2 create mode 100644 examples/module/lan/m5cores3_lan_module_example.py create mode 100644 m5stack/libs/module/lan.py create mode 100644 m5stack/network_lan.c diff --git a/docs/en/module/index.rst b/docs/en/module/index.rst index 4f32ef38..1aabf962 100644 --- a/docs/en/module/index.rst +++ b/docs/en/module/index.rst @@ -23,6 +23,7 @@ Module grbl.rst goplus2.rst hmi.rst + lan.rst llm.rst lora.rst lora868_v12.rst diff --git a/docs/en/module/lan.rst b/docs/en/module/lan.rst new file mode 100644 index 00000000..cd31a58a --- /dev/null +++ b/docs/en/module/lan.rst @@ -0,0 +1,303 @@ +LAN Module +========== + +.. include:: ../refs/module.lan.ref + +Supported Products: + + +-------------------------+ + | |LAN Module| | + +-------------------------+ + +UiFlow2 Example +--------------- + +Get the weather +^^^^^^^^^^^^^^^ + +This example connects to the network using the LAN module and sends an HTTP request to query the geographical location of the current public IP address. + +UiFlow2 Code Block: + + |m5cores3_lan_module_example.png| + +Example output: + + None + +MicroPython Example +------------------- + +Get the weather +^^^^^^^^^^^^^^^ + +This example connects to the network using the LAN module and sends an HTTP request to query the geographical location of the current public IP address. + +MicroPython Code Block: + + .. literalinclude:: ../../../examples/module/lan/m5cores3_lan_module_example.py + :language: python + :linenos: + +Example output: + + None + +**API** +------- + +class LANModule +--------------- + +.. class:: module.lan.LANModule(cs=-1, rst=-1, int=-1) + + Create a LANModule object. + + :param cs: chip select pin + :param rst: reset pin + :param int: interrupt pin + + UiFlow2 Code Block: + + |init.png| + + MicroPython Code Block: + + .. code-block:: python + + from module import LANModule + + lan_0 = LANModule(cs=1, rst=0, int=10) + + .. method:: LANModule.deinit() + + Deinitialize the LAN module. + + UiFlow2 Code Block: + + |deinit.png| + + MicroPython Code Block: + + .. code-block:: python + + lan_0.deinit() + + .. method:: LANModule.isconnected() + + Check whether the physical Ethernet link is active. + + :return: `True` if the Ethernet cable is connected and the link is up, `False` otherwise. + :rtype: bool + + UiFlow2 Code Block: + + |isconnected.png| + + MicroPython Code Block: + + .. code-block:: python + + lan_0.isconnected() + + .. method:: LANModule.status() + + Get the LAN connect status. + + :return: LAN status code, possible values: + - network.ETH_INITIALIZED: Ethernet interface initialized + - network.ETH_STARTED: Ethernet driver started + - network.ETH_STOPPED: Ethernet driver stopped + - network.ETH_CONNECTED: Physical link established (cable connected) + - network.ETH_DISCONNECTED: Physical link lost (cable disconnected) + - network.ETH_GOT_IP: IP address obtained, network ready + :rtype: int + + UiFlow2 Code Block: + + |status.png| + + MicroPython Code Block: + + .. code-block:: python + + lan_0.status() + + .. method:: LANModule.ifconfig()[0] + + Get the local IP address. + + :return: Local IP address as a string, e.g. "192.168.1.100" + :rtype: str + + UiFlow2 Code Block: + + |get_localip.png| + + MicroPython Code Block: + + .. code-block:: python + + lan_0.ifconfig()[0] + + .. method:: LANModule.ifconfig()[1] + + Get the subnet mask. + + :return: Subnet mask as a string, e.g. "255.255.255.0" + :rtype: str + + UiFlow2 Code Block: + + |get_subnet.png| + + MicroPython Code Block: + + .. code-block:: python + + lan_0.ifconfig()[1] + + .. method:: LANModule.ifconfig()[2] + + Get the gateway address. + + :return: Gateway IP as a string, e.g. "192.168.1.1" + :rtype: str + + UiFlow2 Code Block: + + |get_gateway.png| + + MicroPython Code Block: + + .. code-block:: python + + lan_0.ifconfig()[2] + + .. method:: LANModule.ifconfig()[3] + + Get the DNS server address. + + :return: DNS server IP as a string, e.g. "8.8.8.8" + :rtype: str + + UiFlow2 Code Block: + + |get_dns.png| + + MicroPython Code Block: + + .. code-block:: python + + lan_0.ifconfig()[3] + + .. method:: LANModule.config('mac') + + Get the MAC address of the LAN module. + + :param param: Configuration parameter name, must be 'mac' + :type param: str + + :return: MAC address as a string or bytes, e.g. "00:11:22:33:44:55" + :rtype: str or bytes + + UiFlow2 Code Block: + + |get_mac.png| + + MicroPython Code Block: + + .. code-block:: python + + mac_address = lan_0.config('mac') + + .. method:: LANModule.active([state]) + + Enable or disable the LAN interface. + + :param bool | None state: Optional boolean value. If `True`, activates the LAN interface; if `False`, deactivates it. + :return: Current active state of the interface if no parameter is given. + :rtype: bool + + UiFlow2 Code Block: + + |active.png| + + MicroPython Code Block: + + .. code-block:: python + + lan_0.active([state]) + + .. method:: LANModule.config(mac=bytearray) + :no-index: + + Set the MAC address of the LAN module. + + :param mac: MAC address to set, as a bytearray of 6 bytes + :type mac: bytearray + + :return: None + + UiFlow2 Code Block: + + |set_mac.png| + + MicroPython Code Block: + + .. code-block:: python + + lan_0.config(mac=bytearray([0x02, 0x00, 0x00, 0x12, 0x34, 0x56])) + + .. method:: LANModule.set_default_netif() + + Sets the default network interface. + + UiFlow2 Code Block: + + |set_default_netif.png| + + MicroPython Code Block: + + .. code-block:: python + + lan_0.set_default_netif() + + .. method:: LANModule.ifconfig([(ip, subnet, gateway, dns)]) + + Get or set the IP address, subnet mask, gateway, and DNS server for the LAN interface. + + :param str ip: Static IP address to assign to the LAN interface. + :param str subnet: Subnet mask (usually '255.255.255.0'). + :param str gateway: IP address of the network gateway/router. + :param str dns: DNS server IP address. + + UiFlow2 Code Block: + + |ifconfig_subnet.png| + + MicroPython Code Block: + + .. code-block:: python + + lan_0.ifconfig([(ip, subnet, gateway, dns)]) + + .. method:: LANModule.ifconfig([(ip, subnet, gateway, dns)]) + :no-index: + + Get or set the IP address, subnet mask, gateway, and DNS server for the LAN interface. + + :param str ip: Static IP address to assign to the LAN interface. + :param int subnet: Subnet mask as a CIDR prefix length (e.g. `24` means `255.255.255.0`). + :param str gateway: IP address of the network gateway/router. + :param str dns: DNS server IP address. + + UiFlow2 Code Block: + + |ifconfig_netmask.png| + + MicroPython Code Block: + + .. code-block:: python + + lan_0.ifconfig([(ip, subnet, gateway, dns)]) diff --git a/docs/en/refs/module.lan.ref b/docs/en/refs/module.lan.ref new file mode 100644 index 00000000..97613396 --- /dev/null +++ b/docs/en/refs/module.lan.ref @@ -0,0 +1,33 @@ + +.. |LAN Module| image:: https://static-cdn.m5stack.com/resource/docs/products/module/LAN%20Module%2013.2/img-bb8b9bf1-a7ca-46ae-8e5c-bcc4ae7a6184.webp + :target: https://docs.m5stack.com/zh_CN/module/LAN%20Module%2013.2 + :height: 200px + :width: 200px + +.. |init.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/lan/init.png +.. |deinit.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/lan/deinit.png +.. |isconnected.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/lan/isconnected.png +.. |status.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/lan/status.png +.. |get_localip.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/lan/get_localip.png +.. |get_subnet.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/lan/get_subnet.png +.. |get_gateway.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/lan/get_gateway.png +.. |get_dns.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/lan/get_dns.png +.. |get_mac.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/lan/get_mac.png +.. |eth_status.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/lan/eth_status.png +.. |active.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/lan/active.png +.. |set_mac.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/lan/set_mac.png +.. |set_default_netif.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/lan/set_default_netif.png +.. |ifconfig_subnet.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/lan/ifconfig_subnet.png +.. |ifconfig_netmask.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/lan/ifconfig_netmask.png + +.. |m5cores3_lan_module_example.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/lan/example.png + +.. |m5cores3_lan_module_example.m5f2| raw:: html + + + m5cores3_lan_module_example.m5f2 + + diff --git a/docs/locales/zh_CN/LC_MESSAGES/module/lan.po b/docs/locales/zh_CN/LC_MESSAGES/module/lan.po new file mode 100644 index 00000000..1fad8a5e --- /dev/null +++ b/docs/locales/zh_CN/LC_MESSAGES/module/lan.po @@ -0,0 +1,376 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2016 - 2025 M5Stack Technology Co., Ltd +# This file is distributed under the same license as the UIFlow2 Programming +# Guide package. +# FIRST AUTHOR , 2025. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: UIFlow2 Programming Guide \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-06-12 14:52+0800\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: zh_CN\n" +"Language-Team: zh_CN \n" +"Plural-Forms: nplurals=1; plural=0;\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.17.0\n" + +#: ../../en/module/lan.rst:2 ../../en/refs/module.lan.ref +#: 3099f56e48624597aceb41b66dbbab81 920ef01384d546db9d1115df6bcee1d5 +msgid "LAN Module" +msgstr "" + +#: ../../en/module/lan.rst:6 518f5d674aae47b5a0da3b12fa4bdefe +msgid "Supported Products:" +msgstr "支持以下产品:" + +#: ../../en/module/lan.rst:9 37d7484339ad4d77b5b03b23cdcf1b71 +msgid "|LAN Module|" +msgstr "" + +#: ../../en/module/lan.rst:13 884c9faa1ada46448e1216d87b18ce69 +msgid "UiFlow2 Example" +msgstr "UiFlow2 应用示例" + +#: ../../en/module/lan.rst:16 ../../en/module/lan.rst:32 +#: 083fe69b180d4cfba2774e8600ca308d +msgid "Get the weather" +msgstr "获取天气" + +#: ../../en/module/lan.rst:18 ../../en/module/lan.rst:34 +#: a4be9d46d4f94b4f953e90014e98bbcf +msgid "" +"This example connects to the network using the LAN module and sends an " +"HTTP request to query the geographical location of the current public IP " +"address." +msgstr "示例使用 LAN 模块接入网络,并通过 HTTP 请求获取当前公网 IP 所在的地理位置。" + +#: ../../en/module/lan.rst:20 ../../en/module/lan.rst:60 +#: ../../en/module/lan.rst:76 ../../en/module/lan.rst:93 +#: ../../en/module/lan.rst:116 ../../en/module/lan.rst:133 +#: ../../en/module/lan.rst:150 ../../en/module/lan.rst:167 +#: ../../en/module/lan.rst:184 ../../en/module/lan.rst:204 +#: ../../en/module/lan.rst:222 ../../en/module/lan.rst:242 +#: ../../en/module/lan.rst:256 ../../en/module/lan.rst:275 +#: ../../en/module/lan.rst:295 2328617b3df342f9911a3e98d443c3b4 +msgid "UiFlow2 Code Block:" +msgstr "UiFlow2 代码块:" + +#: ../../en/module/lan.rst:22 e9f013c7b98b4cfe9fc0c3dc4c3ff64f +msgid "|m5cores3_lan_module_example.png|" +msgstr "" + +#: ../../en/refs/module.lan.ref:23 b6e73c629de74772896be8f63fcf0685 +msgid "m5cores3_lan_module_example.png" +msgstr "" + +#: ../../en/module/lan.rst:24 ../../en/module/lan.rst:42 +#: cfe126a6ceb0499faf4c65c66cb2f06c +msgid "Example output:" +msgstr "示例输出:" + +#: ../../en/module/lan.rst:26 ../../en/module/lan.rst:44 +#: ../../en/module/lan.rst:240 17a517c36b8e4c56853d13e8ad536f33 +#: b516b0db02af416da16527523aa08a30 +msgid "None" +msgstr "无" + +#: ../../en/module/lan.rst:29 530de5da0d8e41a28d29407e289b085a +msgid "MicroPython Example" +msgstr "MicroPython 应用示例" + +#: ../../en/module/lan.rst:36 ../../en/module/lan.rst:64 +#: ../../en/module/lan.rst:80 ../../en/module/lan.rst:97 +#: ../../en/module/lan.rst:120 ../../en/module/lan.rst:137 +#: ../../en/module/lan.rst:154 ../../en/module/lan.rst:171 +#: ../../en/module/lan.rst:188 ../../en/module/lan.rst:208 +#: ../../en/module/lan.rst:226 ../../en/module/lan.rst:246 +#: ../../en/module/lan.rst:260 ../../en/module/lan.rst:279 +#: ../../en/module/lan.rst:299 b0487b813df14ea9a0d75e69043a66a4 +msgid "MicroPython Code Block:" +msgstr "MicroPython 代码块:" + +#: ../../en/module/lan.rst:47 f25024aac2654f50a13fb8e0d8512e6d +msgid "**API**" +msgstr "API应用" + + +#: ../../en/module/lan.rst:54 39932b6148ea4f13b06e675bbc41abed +#, fuzzy +msgid "Create a LANModule object." +msgstr "创建一个 LANModule 对象。" + +#: ../../en/module/lan.rst f30df10928bf43309502a38ff7e06297 +msgid "Parameters" +msgstr "" + +#: ../../en/module/lan.rst:56 2426de7edf764ff78c2d2fb35fe72db1 +msgid "chip select pin" +msgstr "片选引脚" + +#: ../../en/module/lan.rst:57 ed21443df3be47bf97c7fe2098ca3d8e +msgid "reset pin" +msgstr "复位引脚" + +#: ../../en/module/lan.rst:58 c094176ea4fa4de9a3cd5348f8246e6c +msgid "interrupt pin" +msgstr "中断引脚" + +#: ../../en/module/lan.rst:62 5fe37eb180384964a06d857155e82fd6 +msgid "|init.png|" +msgstr "" + +#: ../../en/refs/module.lan.ref:7 fdc7f635449e4ae4ae33fd3f8267cf5f +msgid "init.png" +msgstr "" + +#: ../../en/module/lan.rst:74 a73dfec22efd48ce8404f4c5851117b9 +msgid "Deinitialize the LAN module." +msgstr "反初始化 LANModule。" + +#: ../../en/module/lan.rst:78 7bc4d9e4f26d43dbb586f7db931811dd +msgid "|deinit.png|" +msgstr "" + +#: ../../en/refs/module.lan.ref:8 fdc7f635449e4ae4ae33fd3f8267cf5f +msgid "deinit.png" +msgstr "" + +#: ../../en/module/lan.rst:88 de6e4a9e69e749519092b2a613e98524 +msgid "Check whether the physical Ethernet link is active." +msgstr "检查以太网物理连接是否已激活。" + +#: ../../en/module/lan.rst cebf190c732d4ae19412daa23da0eb73 +msgid "Returns" +msgstr "" + +#: ../../en/module/lan.rst:90 12b5d4300cd0493991f52d8d8bc70f72 +msgid "" +"`True` if the Ethernet cable is connected and the link is up, `False` " +"otherwise." +msgstr "如果以太网网线已连接且链路已建立,则为 True,否则为 False。" + +#: ../../en/module/lan.rst 4e865a5850e54dcc8827303f41adeded +msgid "Return type" +msgstr "" + +#: ../../en/module/lan.rst:95 891426c33f5e4db2b7a5d1a507917a82 +msgid "|isconnected.png|" +msgstr "" + +#: ../../en/refs/module.lan.ref:9 0eaf3c0daa314349b6061933989e09d5 +msgid "isconnected.png" +msgstr "" + +#: ../../en/module/lan.rst:105 a30645cae9654baabf0c59af0803cbeb +msgid "Get the LAN connect status." +msgstr "获取 LAN 连接状态。" + +#: ../../en/module/lan.rst:107 9579e41ef9844298b338b2610eb48040 +msgid "" +"LAN status code, possible values: - network.ETH_INITIALIZED: Ethernet " +"interface initialized - network.ETH_STARTED: Ethernet driver started - " +"network.ETH_STOPPED: Ethernet driver stopped - network.ETH_CONNECTED: " +"Physical link established (cable connected) - network.ETH_DISCONNECTED: " +"Physical link lost (cable disconnected) - network.ETH_GOT_IP: IP address " +"obtained, network ready" +msgstr "LAN 状态码,可能的取值包括: +- network.ETH_INITIALIZED:以太网接口已初始化 +- network.ETH_STARTED:以太网驱动已启动 +- network.ETH_STOPPED:以太网驱动已停止 +- network.ETH_CONNECTED:物理链路已建立(网线已连接) +- network.ETH_DISCONNECTED:物理链路丢失(网线断开) +- network.ETH_GOT_IP:已获取 IP 地址,网络准备就绪" + +#: ../../en/module/lan.rst:118 aa2368f53a0a463e9c4e0fde760e807d +msgid "|status.png|" +msgstr "" + +#: ../../en/refs/module.lan.ref:10 2ad8360c2e274193845983e15037f817 +msgid "status.png" +msgstr "" + +#: ../../en/module/lan.rst:128 d6a1924642064fc6bf9694b5d5ec0657 +msgid "Get the local IP address." +msgstr "获取本地 IP 地址。" + +#: ../../en/module/lan.rst:130 73e1d96eb51b42bf87d3cb7865ceadc6 +msgid "Local IP address as a string, e.g. \"192.168.1.100\"" +msgstr "本地 IP 地址,字符串格式,例如:"192.168.1.100"" + +#: ../../en/module/lan.rst:135 8682225861b547a590ab8d419f4b4e2e +msgid "|get_localip.png|" +msgstr "" + +#: ../../en/refs/module.lan.ref:11 85d7420f864944d1bbfd0ec7b71014aa +msgid "get_localip.png" +msgstr "" + +#: ../../en/module/lan.rst:145 d4548bcd446748b5b533d37f740a8bba +msgid "Get the subnet mask." +msgstr "获取子网掩码。" + +#: ../../en/module/lan.rst:147 e54c10dade3f4559a77898f374c077a5 +msgid "Subnet mask as a string, e.g. \"255.255.255.0\"" +msgstr "子网掩码,字符串格式,例如:"255.255.255.0"" + +#: ../../en/module/lan.rst:152 ee1ac31653c64d12a1b0d0767388126c +msgid "|get_subnet.png|" +msgstr "" + +#: ../../en/refs/module.lan.ref:12 b6e73c629de74772896be8f63fcf0685 +msgid "get_subnet.png" +msgstr "" + +#: ../../en/module/lan.rst:162 7ae812f12be64ca488796eb620e1e2be +msgid "Get the gateway address." +msgstr "获取网关地址。" + +#: ../../en/module/lan.rst:164 7698e7fcc9f14542a27edc81b6211fb7 +msgid "Gateway IP as a string, e.g. \"192.168.1.1\"" +msgstr "网关 IP,字符串格式,例如:"192.168.1.1"" + +#: ../../en/module/lan.rst:169 d78606b5eb2e41148f10e03a12686f7e +msgid "|get_gateway.png|" +msgstr "" + +#: ../../en/refs/module.lan.ref:13 2ad8360c2e274193845983e15037f817 +msgid "get_gateway.png" +msgstr "" + +#: ../../en/module/lan.rst:179 eb5242c6eaa54bfba7be16bbbddeb21b +msgid "Get the DNS server address." +msgstr "获取 DNS 服务地址。" + +#: ../../en/module/lan.rst:181 6370db3ca1cb4f8d88949e9368786cd7 +msgid "DNS server IP as a string, e.g. \"8.8.8.8\"" +msgstr "DNS 服务器 IP,字符串格式,例如:"8.8.8.8"" + +#: ../../en/module/lan.rst:186 aa2368f53a0a463e9c4e0fde760e807d +msgid "|get_dns.png|" +msgstr "" + +#: ../../en/refs/module.lan.ref:14 2ad8360c2e274193845983e15037f817 +msgid "get_dns.png" +msgstr "" + +#: ../../en/module/lan.rst:196 a4673f2150a34587a965a3e2674b14f7 +msgid "Get the MAC address of the LAN module." +msgstr "获取 LAN module MAC 地址。" + +#: ../../en/module/lan.rst:198 26fce5f905ff41a4ba5c5c8f7d560933 +msgid "Configuration parameter name, must be 'mac'" +msgstr "配置参数名,必须是 'mac'" + +#: ../../en/module/lan.rst:201 1bf9a84db3d74b2f9b6d181502df8d29 +msgid "MAC address as a string or bytes, e.g. \"00:11:22:33:44:55\"" +msgstr "MAC 地址,字符串或字节格式,例如:"00:11:22:33:44:55"" + +#: ../../en/module/lan.rst:206 302d1cf000094bf3b35f7289bb2b02fc +msgid "|get_mac.png|" +msgstr "" + +#: ../../en/refs/module.lan.ref:15 b6e73c629de74772896be8f63fcf0685 +msgid "get_mac.png" +msgstr "" + +#: ../../en/module/lan.rst:216 ab00d04c338a421aa9a3a0a96f02e0db +msgid "Enable or disable the LAN interface." +msgstr "使能或失能 LAN 接口。" + +#: ../../en/module/lan.rst:218 c4f55e11fa594249a0aa3856938c03f0 +msgid "" +"Optional boolean value. If `True`, activates the LAN interface; if " +"`False`, deactivates it." +msgstr "可选的布尔值。若为 True,激活 LAN 接口;若为 False,则停用它。" + +#: ../../en/module/lan.rst:219 e8bfb53b60d8419e95aed8d6de5bdacb +msgid "Current active state of the interface if no parameter is given." +msgstr "如果没有提供参数,则返回接口当前的激活状态。" + +#: ../../en/module/lan.rst:224 51f1d130cdcf4252a5ccdf5345593ed8 +msgid "|active.png|" +msgstr "" + +#: ../../en/refs/module.lan.ref:17 b6e73c629de74772896be8f63fcf0685 +msgid "active.png" +msgstr "" + +#: ../../en/module/lan.rst:235 642b8a1d2b9049b9b7565573a4240f4d +msgid "Set the MAC address of the LAN module." +msgstr "设置 LAN module MAC 地址。" + +#: ../../en/module/lan.rst:237 2de8bb672fa44cddbbdc98529fdd12c2 +msgid "MAC address to set, as a bytearray of 6 bytes" +msgstr "要设置的 MAC 地址,格式为包含 6 个字节的 bytearray。" + +#: ../../en/module/lan.rst:244 302d1cf000094bf3b35f7289bb2b02fc +msgid "|set_mac.png|" +msgstr "" + +#: ../../en/refs/module.lan.ref:18 2ad8360c2e274193845983e15037f817 +msgid "set_mac.png" +msgstr "" + +#: ../../en/module/lan.rst:254 4772e66c5acd4232b51254a24adc53e6 +msgid "Sets the default network interface." +msgstr "设置默认网络接口。" + +#: ../../en/module/lan.rst:258 8682225861b547a590ab8d419f4b4e2e +msgid "|set_default_netif.png|" +msgstr "" + +#: ../../en/refs/module.lan.ref:19 1b79377b28644d4b894d0fe008ffc413 +msgid "set_default_netif.png" +msgstr "" + +#: ../../en/module/lan.rst:268 ../../en/module/lan.rst:288 +#: cb3d648463f3431f983f46989ecd2e51 ede96d92d034428e8202837549a34394 +msgid "" +"Get or set the IP address, subnet mask, gateway, and DNS server for the " +"LAN interface." +msgstr "获取或设置 LAN 接口的 IP 地址、子网掩码、网关和 DNS 服务器。" + +#: ../../en/module/lan.rst:270 ../../en/module/lan.rst:290 +#: eddc35892d174948ba1599003e18649a +msgid "Static IP address to assign to the LAN interface." +msgstr "分配给 LAN 接口的静态 IP 地址。" + +#: ../../en/module/lan.rst:271 2840e605c36247cdaaacd7b7a3ff9131 +msgid "Subnet mask (usually '255.255.255.0')." +msgstr "子网掩码(通常为 '255.255.255.0')。" + +#: ../../en/module/lan.rst:272 ../../en/module/lan.rst:292 +#: 4081a2d2458643478c21afbbe3d676fa +msgid "IP address of the network gateway/router." +msgstr "网络网关/路由器的 IP 地址。" + +#: ../../en/module/lan.rst:273 ../../en/module/lan.rst:293 +#: 2f2fab1ea6a0460785e72becbc0880ac +msgid "DNS server IP address." +msgstr "DNS 服务 IP 地址。" + +#: ../../en/module/lan.rst:277 ee1ac31653c64d12a1b0d0767388126c +msgid "|ifconfig_subnet.png|" +msgstr "" + +#: ../../en/refs/module.lan.ref:20 85d7420f864944d1bbfd0ec7b71014aa +msgid "ifconfig_subnet.png" +msgstr "" + +#: ../../en/module/lan.rst:291 05c24b899db14306a76e84591700dfd4 +msgid "Subnet mask as a CIDR prefix length (e.g. `24` means `255.255.255.0`)." +msgstr "子网掩码,使用 CIDR 前缀长度表示(例如 24 表示 255.255.255.0)。" + +#: ../../en/module/lan.rst:297 ee1ac31653c64d12a1b0d0767388126c +msgid "|ifconfig_netmask.png|" +msgstr "" + +#: ../../en/refs/module.lan.ref:21 85d7420f864944d1bbfd0ec7b71014aa +msgid "ifconfig_netmask.png" +msgstr "" diff --git a/examples/module/lan/m5cores3_lan_module_example.m5f2 b/examples/module/lan/m5cores3_lan_module_example.m5f2 new file mode 100644 index 00000000..89e1922c --- /dev/null +++ b/examples/module/lan/m5cores3_lan_module_example.m5f2 @@ -0,0 +1 @@ +{"version":"V2.0","versionNumber":"V2.2.8","type":"cores3","components":[{"name":"screen","type":"screen","layer":0,"screenId":"builtin","screenName":"","id":"__cores3_screen","createTime":1748422625772,"x":0,"y":0,"width":320,"height":240,"backgroundColor":"#222222","size":0,"isSelected":true},{"name":"label_status","type":"label","layer":1,"screenId":"builtin","screenName":"","id":"byRSGlu1-t7H81U2","createTime":1748424803687,"x":5,"y":50,"color":"#ff0000","backgroundColor":"#222222","text":"Waiting for network connection","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false},{"name":"title0","type":"title","layer":2,"screenId":"builtin","screenName":"","id":"wmO&`aUY6TpscCAZ","createTime":1748426082878,"x":0,"y":0,"color":"#ffffff","backgroundColor":"#0000FF","text":"LANModule Example","textOffset":3,"font":"Widgets.FONTS.DejaVu24","isSelected":false},{"name":"label_info","type":"label","layer":3,"screenId":"builtin","screenName":"","id":"x31VscmN1zkDrnkX","createTime":1748426108309,"x":5,"y":90,"color":"#ffffff","backgroundColor":"#222222","text":"Get info","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false}],"resources":[{"software":["http","tcp"]},{"hardware":["hardware_button","hardware_pin_button","imu","speaker","touch","als","mic"]},{"module":["module_lan"]}],"units":[],"hats":[],"bases":[],"i2cs":[],"blockly":"trueFalselan_0TrueFalseTrueFalseTrueFalselan_0TrueWHILElan_01.local network is connectedlabel_statusNetwork connected!label_statusrgb02550hex0x222222GETjsonhttps://wttr.in/?format=%22%C,%20%t%22application/jsonhello M5label_infoLabeltrue","screen":[{"simulationName":"Built-in","type":"builtin","width":320,"height":240,"scale":0.77,"screenName":"","blockId":"","screenColorType":0,"rotation":1,"id":"builtin","createTime":1748422625772}],"logicWhenNum":0,"customList":[]} \ No newline at end of file diff --git a/examples/module/lan/m5cores3_lan_module_example.py b/examples/module/lan/m5cores3_lan_module_example.py new file mode 100644 index 00000000..91d54940 --- /dev/null +++ b/examples/module/lan/m5cores3_lan_module_example.py @@ -0,0 +1,65 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import os, sys, io +import M5 +from M5 import * +import network +from module import LANModule +import time +import requests2 + + +label_status = None +title0 = None +label_info = None +wlan = None +lan_0 = None +http_req = None + + +def setup(): + global label_status, title0, label_info, wlan, lan_0, http_req + M5.begin() + Widgets.fillScreen(0x222222) + label_status = Widgets.Label( + "Waiting for network connection", 5, 50, 1.0, 0xFF0000, 0x222222, Widgets.FONTS.DejaVu18 + ) + title0 = Widgets.Title("LANModule Example", 3, 0xFFFFFF, 0x0000FF, Widgets.FONTS.DejaVu24) + label_info = Widgets.Label("Get info", 5, 90, 1.0, 0xFFFFFF, 0x222222, Widgets.FONTS.DejaVu18) + wlan = network.WLAN(network.STA_IF) + wlan.active(False) + lan_0 = LANModule(cs=1, rst=0, int=10) + lan_0.active(True) + while not (lan_0.isconnected()): + time.sleep(1) + print(".") + print("local network is connected") + label_status.setText(str("Network connected!")) + label_status.setColor(0x00FF00, 0x222222) + http_req = requests2.get( + "https://wttr.in/?format=%22%C,%20%t%22", headers={"Content-Type": "application/json"} + ) + print(http_req.text) + label_info.setText(str(http_req.text)) + + +def loop(): + global label_status, title0, label_info, wlan, lan_0, http_req + M5.update() + + +if __name__ == "__main__": + try: + setup() + while True: + loop() + except (Exception, KeyboardInterrupt) as e: + try: + lan_0.deinit() + from utility import print_error_msg + + print_error_msg(e) + except ImportError: + print("please update to latest firmware") diff --git a/m5stack/CMakeListsDefault.cmake b/m5stack/CMakeListsDefault.cmake index 1d5c9e50..f8d5e35d 100644 --- a/m5stack/CMakeListsDefault.cmake +++ b/m5stack/CMakeListsDefault.cmake @@ -123,7 +123,7 @@ list(APPEND MICROPY_SOURCE_PORT ${PROJECT_DIR}/../micropython/ports/esp32/machine_dac.c ${PROJECT_DIR}/machine_i2c.c ${PROJECT_DIR}/../micropython/ports/esp32/network_common.c - ${PROJECT_DIR}/../micropython/ports/esp32/network_lan.c + ${PROJECT_DIR}/network_lan.c ${PROJECT_DIR}/network_ppp.c ${PROJECT_DIR}/network_wlan.c ${PROJECT_DIR}/../micropython/ports/esp32/mpnimbleport.c diff --git a/m5stack/boards/M5STACK_Basic/sdkconfig.board b/m5stack/boards/M5STACK_Basic/sdkconfig.board index 724b3624..a67411e3 100644 --- a/m5stack/boards/M5STACK_Basic/sdkconfig.board +++ b/m5stack/boards/M5STACK_Basic/sdkconfig.board @@ -12,3 +12,5 @@ CONFIG_ESPTOOLPY_FLASHFREQ="80m" CONFIG_ESPTOOLPY_FLASHMODE_QIO=y CONFIG_FREERTOS_ENABLE_BACKWARD_COMPATIBILITY=y + +CONFIG_ETH_SPI_ETHERNET_W5500=y diff --git a/m5stack/boards/M5STACK_Basic_4MB/sdkconfig.board b/m5stack/boards/M5STACK_Basic_4MB/sdkconfig.board index 724b3624..a67411e3 100644 --- a/m5stack/boards/M5STACK_Basic_4MB/sdkconfig.board +++ b/m5stack/boards/M5STACK_Basic_4MB/sdkconfig.board @@ -12,3 +12,5 @@ CONFIG_ESPTOOLPY_FLASHFREQ="80m" CONFIG_ESPTOOLPY_FLASHMODE_QIO=y CONFIG_FREERTOS_ENABLE_BACKWARD_COMPATIBILITY=y + +CONFIG_ETH_SPI_ETHERNET_W5500=y diff --git a/m5stack/boards/M5STACK_Core2/sdkconfig.board b/m5stack/boards/M5STACK_Core2/sdkconfig.board index 79ad8ba1..f9afc7ed 100644 --- a/m5stack/boards/M5STACK_Core2/sdkconfig.board +++ b/m5stack/boards/M5STACK_Core2/sdkconfig.board @@ -24,3 +24,5 @@ CONFIG_SPIRAM_SPEED_80M=y CONFIG_SPIRAM_TRY_ALLOCATE_WIFI_LWIP=y CONFIG_FREERTOS_ENABLE_BACKWARD_COMPATIBILITY=y + +CONFIG_ETH_SPI_ETHERNET_W5500=y diff --git a/m5stack/boards/M5STACK_CoreS3/sdkconfig.board b/m5stack/boards/M5STACK_CoreS3/sdkconfig.board index f9e45780..f3b83fb2 100644 --- a/m5stack/boards/M5STACK_CoreS3/sdkconfig.board +++ b/m5stack/boards/M5STACK_CoreS3/sdkconfig.board @@ -38,3 +38,5 @@ CONFIG_BSP_SPIFFS_PARTITION_LABEL="storage" CONFIG_BSP_SPIFFS_MAX_FILES=5 # end of models: human_face_recognition +# LAN +CONFIG_ETH_SPI_ETHERNET_W5500=y diff --git a/m5stack/boards/M5STACK_Fire/sdkconfig.board b/m5stack/boards/M5STACK_Fire/sdkconfig.board index a99e5e79..1de1af04 100644 --- a/m5stack/boards/M5STACK_Fire/sdkconfig.board +++ b/m5stack/boards/M5STACK_Fire/sdkconfig.board @@ -40,3 +40,5 @@ CONFIG_SPIRAM_SPEED_80M=y CONFIG_SPIRAM_TRY_ALLOCATE_WIFI_LWIP=y CONFIG_FREERTOS_ENABLE_BACKWARD_COMPATIBILITY=y + +CONFIG_ETH_SPI_ETHERNET_W5500=y diff --git a/m5stack/boards/M5STACK_Tough/sdkconfig.board b/m5stack/boards/M5STACK_Tough/sdkconfig.board index 79ad8ba1..f9afc7ed 100644 --- a/m5stack/boards/M5STACK_Tough/sdkconfig.board +++ b/m5stack/boards/M5STACK_Tough/sdkconfig.board @@ -24,3 +24,5 @@ CONFIG_SPIRAM_SPEED_80M=y CONFIG_SPIRAM_TRY_ALLOCATE_WIFI_LWIP=y CONFIG_FREERTOS_ENABLE_BACKWARD_COMPATIBILITY=y + +CONFIG_ETH_SPI_ETHERNET_W5500=y diff --git a/m5stack/libs/module/__init__.py b/m5stack/libs/module/__init__.py index f23ffb33..36771d59 100644 --- a/m5stack/libs/module/__init__.py +++ b/m5stack/libs/module/__init__.py @@ -26,6 +26,7 @@ "GoPlus2Module": "goplus2", "HMIModule": "hmi", "IotBaseCatmModule": "iot_base_catm", + "LANModule": "lan", "LlmModule": "llm", "LoraModule": "lora", "LoRa868V12Module": "lora868_v12", diff --git a/m5stack/libs/module/lan.py b/m5stack/libs/module/lan.py new file mode 100644 index 00000000..c8248713 --- /dev/null +++ b/m5stack/libs/module/lan.py @@ -0,0 +1,27 @@ +# SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +from .mbus import spi2 +import network +import machine +import builtins + + +class LANModule: + def __new__(cls, cs=-1, rst=-1, int=-1): + nic = network.LAN( + 0, + phy_addr=0, + phy_type=network.PHY_W5500, + spi=spi2, + cs=machine.Pin(cs), + int=machine.Pin(int), + power=machine.Pin(rst), + ) + mac = builtins.int.from_bytes(machine.unique_id(), "big") + mac = mac + 3 + # esp32-s3 no eth mac, so use sta mac + 3 + nic.config(mac=mac.to_bytes(6, "big")) + nic.active(True) + return nic diff --git a/m5stack/libs/module/manifest.py b/m5stack/libs/module/manifest.py index eb3d8ec6..e3981eb0 100644 --- a/m5stack/libs/module/manifest.py +++ b/m5stack/libs/module/manifest.py @@ -25,6 +25,7 @@ "goplus2.py", "hmi.py", "iot_base_catm.py", + "lan.py", "llm.py", "lora.py", "lora868_v12.py", diff --git a/m5stack/network_lan.c b/m5stack/network_lan.c new file mode 100644 index 00000000..7fb51747 --- /dev/null +++ b/m5stack/network_lan.c @@ -0,0 +1,458 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2017 "Eric Poulsen" + * Copyright (c) 2021 "Tobias Eydam" + * + * Based on the ESP IDF example code which is Public Domain / CC0 + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "py/runtime.h" +#include "py/mphal.h" + +#include "esp_idf_version.h" + +#if MICROPY_PY_NETWORK_LAN + +#include "esp_eth.h" +#include "esp_eth_mac.h" +#include "esp_event.h" +#include "esp_log.h" +#include "esp_netif.h" +#if CONFIG_ETH_USE_SPI_ETHERNET +#include "driver/spi_master.h" +#endif + +#include "modnetwork.h" +#include "extmod/modnetwork.h" + +#define ETH_MAC_CUSTOM_CONFIG() \ + { \ + .sw_reset_timeout_ms = 100, \ + .rx_task_stack_size = 3072, \ + .rx_task_prio = 15, \ + .flags = 0, \ + } + +typedef struct _lan_if_obj_t { + base_if_obj_t base; + bool initialized; + int8_t mdc_pin; + int8_t mdio_pin; + int8_t phy_power_pin; + int8_t phy_cs_pin; + int8_t phy_int_pin; + uint8_t phy_addr; + uint8_t phy_type; + esp_eth_mac_t *mac; + esp_eth_phy_t *phy; + esp_eth_handle_t eth_handle; + esp_eth_netif_glue_handle_t eth_glue; +} lan_if_obj_t; + +const mp_obj_type_t lan_if_type; +static lan_if_obj_t lan_obj = {{{&lan_if_type}, ESP_IF_ETH, NULL}, false, false}; +static uint8_t eth_status = 0; + +static void eth_event_handler(void *arg, esp_event_base_t event_base, + int32_t event_id, void *event_data) { + switch (event_id) { + case ETHERNET_EVENT_CONNECTED: + eth_status = ETH_CONNECTED; + ESP_LOGI("ethernet", "Ethernet Link Up"); + break; + case ETHERNET_EVENT_DISCONNECTED: + eth_status = ETH_DISCONNECTED; + ESP_LOGI("ethernet", "Ethernet Link Down"); + break; + case ETHERNET_EVENT_START: + eth_status = ETH_STARTED; + ESP_LOGI("ethernet", "Ethernet Started"); + break; + case ETHERNET_EVENT_STOP: + eth_status = ETH_STOPPED; + ESP_LOGI("ethernet", "Ethernet Stopped"); + break; + case IP_EVENT_ETH_GOT_IP: + eth_status = ETH_GOT_IP; + ESP_LOGI("ethernet", "Ethernet Got IP"); + break; + default: + break; + } +} + +static mp_obj_t get_lan(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + lan_if_obj_t *self = &lan_obj; + + if (self->initialized) { + return MP_OBJ_FROM_PTR(&lan_obj); + } + + enum { ARG_id, ARG_mdc, ARG_mdio, ARG_power, ARG_phy_addr, ARG_phy_type, + ARG_spi, ARG_cs, ARG_int, ARG_ref_clk_mode, ARG_ref_clk }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_id, MP_ARG_OBJ, {.u_obj = mp_const_none} }, + { MP_QSTR_mdc, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} }, + { MP_QSTR_mdio, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} }, + { MP_QSTR_power, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} }, + { MP_QSTR_phy_addr, MP_ARG_KW_ONLY | MP_ARG_REQUIRED | MP_ARG_INT }, + { MP_QSTR_phy_type, MP_ARG_KW_ONLY | MP_ARG_REQUIRED | MP_ARG_INT }, + { MP_QSTR_spi, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} }, + { MP_QSTR_cs, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} }, + { MP_QSTR_int, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} }, + { MP_QSTR_ref_clk_mode, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1} }, + { MP_QSTR_ref_clk, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} }, + }; + + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + if (args[ARG_id].u_obj != mp_const_none) { + if (mp_obj_get_int(args[ARG_id].u_obj) != 0) { + mp_raise_ValueError(MP_ERROR_TEXT("invalid LAN interface identifier")); + } + } + + #define GET_PIN(XXX) args[XXX].u_obj == mp_const_none ? -1 : machine_pin_get_id(args[XXX].u_obj); + + self->mdc_pin = GET_PIN(ARG_mdc); + self->mdio_pin = GET_PIN(ARG_mdio); + self->phy_power_pin = GET_PIN(ARG_power); + self->phy_cs_pin = GET_PIN(ARG_cs); + self->phy_int_pin = GET_PIN(ARG_int); + + if (args[ARG_phy_addr].u_int < 0x00 || args[ARG_phy_addr].u_int > 0x1f) { + mp_raise_ValueError(MP_ERROR_TEXT("invalid phy address")); + } + self->phy_addr = args[ARG_phy_addr].u_int; + + if (args[ARG_phy_type].u_int != PHY_LAN8710 && + args[ARG_phy_type].u_int != PHY_LAN8720 && + args[ARG_phy_type].u_int != PHY_IP101 && + args[ARG_phy_type].u_int != PHY_RTL8201 && + args[ARG_phy_type].u_int != PHY_KSZ8041 && + args[ARG_phy_type].u_int != PHY_KSZ8081 && + #if CONFIG_ETH_USE_SPI_ETHERNET + #if CONFIG_ETH_SPI_ETHERNET_KSZ8851SNL + args[ARG_phy_type].u_int != PHY_KSZ8851SNL && + #endif + #if CONFIG_ETH_SPI_ETHERNET_DM9051 + args[ARG_phy_type].u_int != PHY_DM9051 && + #endif + #if CONFIG_ETH_SPI_ETHERNET_W5500 + args[ARG_phy_type].u_int != PHY_W5500 && + #endif + #endif + args[ARG_phy_type].u_int != PHY_DP83848) { + mp_raise_ValueError(MP_ERROR_TEXT("invalid phy type")); + } + + eth_mac_config_t mac_config = ETH_MAC_CUSTOM_CONFIG(); + #if CONFIG_IDF_TARGET_ESP32 + eth_esp32_emac_config_t esp32_config = ETH_ESP32_EMAC_DEFAULT_CONFIG(); + #endif + + // esp_eth_mac_t *mac = NULL; + + #if CONFIG_IDF_TARGET_ESP32 + // Dynamic ref_clk configuration. + if (args[ARG_ref_clk_mode].u_int != -1) { + // Map the GPIO_MODE constants to EMAC_CLK constants. + esp32_config.clock_config.rmii.clock_mode = + args[ARG_ref_clk_mode].u_int == GPIO_MODE_INPUT ? EMAC_CLK_EXT_IN : EMAC_CLK_OUT; + } + if (args[ARG_ref_clk].u_obj != mp_const_none) { + esp32_config.clock_config.rmii.clock_gpio = machine_pin_get_id(args[ARG_ref_clk].u_obj); + } + #endif + + eth_phy_config_t phy_config = ETH_PHY_DEFAULT_CONFIG(); + phy_config.phy_addr = self->phy_addr; + phy_config.reset_gpio_num = self->phy_power_pin; + self->phy = NULL; + #if CONFIG_ETH_USE_SPI_ETHERNET + spi_device_interface_config_t devcfg = { + .mode = 0, + .clock_speed_hz = MICROPY_PY_NETWORK_LAN_SPI_CLOCK_SPEED_MZ * 1000 * 1000, + .queue_size = 20, + .spics_io_num = self->phy_cs_pin, + .command_bits = 0, // Can both be set to 0, as the respective + .address_bits = 0, // driver fills in proper default values. + }; + #endif + + switch (args[ARG_phy_type].u_int) { + #if CONFIG_IDF_TARGET_ESP32 + case PHY_LAN8710: + case PHY_LAN8720: + self->phy = esp_eth_phy_new_lan87xx(&phy_config); + break; + case PHY_IP101: + self->phy = esp_eth_phy_new_ip101(&phy_config); + break; + case PHY_RTL8201: + self->phy = esp_eth_phy_new_rtl8201(&phy_config); + break; + case PHY_DP83848: + self->phy = esp_eth_phy_new_dp83848(&phy_config); + break; + case PHY_KSZ8041: + case PHY_KSZ8081: + self->phy = esp_eth_phy_new_ksz80xx(&phy_config); + break; + #endif + #if CONFIG_ETH_USE_SPI_ETHERNET + #if CONFIG_ETH_SPI_ETHERNET_KSZ8851SNL + case PHY_KSZ8851SNL: { + spi_host_device_t host = machine_hw_spi_get_host(args[ARG_spi].u_obj); + eth_ksz8851snl_config_t chip_config = ETH_KSZ8851SNL_DEFAULT_CONFIG(host, &devcfg); + chip_config.int_gpio_num = self->phy_int_pin; + self->mac = esp_eth_mac_new_ksz8851snl(&chip_config, &mac_config); + self->phy = esp_eth_phy_new_ksz8851snl(&phy_config); + break; + } + #endif + #if CONFIG_ETH_SPI_ETHERNET_DM9051 + case PHY_DM9051: { + spi_host_device_t host = machine_hw_spi_get_host(args[ARG_spi].u_obj); + eth_dm9051_config_t chip_config = ETH_DM9051_DEFAULT_CONFIG(host, &devcfg); + chip_config.int_gpio_num = self->phy_int_pin; + self->mac = esp_eth_mac_new_dm9051(&chip_config, &mac_config); + self->phy = esp_eth_phy_new_dm9051(&phy_config); + break; + } + #endif + #if CONFIG_ETH_SPI_ETHERNET_W5500 + case PHY_W5500: { + spi_host_device_t host = machine_hw_spi_get_host(args[ARG_spi].u_obj); + eth_w5500_config_t chip_config = ETH_W5500_DEFAULT_CONFIG(host, &devcfg); + chip_config.int_gpio_num = self->phy_int_pin; + self->mac = esp_eth_mac_new_w5500(&chip_config, &mac_config); + self->phy = esp_eth_phy_new_w5500(&phy_config); + break; + } + #endif + #endif + } + + #if CONFIG_IDF_TARGET_ESP32 + if (!IS_SPI_PHY(args[ARG_phy_type].u_int)) { + if (self->mdc_pin == -1 || self->mdio_pin == -1) { + mp_raise_ValueError(MP_ERROR_TEXT("mdc and mdio must be specified")); + } + esp32_config.smi_mdc_gpio_num = self->mdc_pin; + esp32_config.smi_mdio_gpio_num = self->mdio_pin; + self->mac = esp_eth_mac_new_esp32(&esp32_config, &mac_config); + } + #endif + + if (esp_netif_init() != ESP_OK) { + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("esp_netif_init failed")); + } + + esp_netif_config_t cfg = ESP_NETIF_DEFAULT_ETH(); + self->base.netif = esp_netif_new(&cfg); + + if (esp_event_handler_register(ETH_EVENT, ESP_EVENT_ANY_ID, ð_event_handler, NULL) != ESP_OK) { + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("esp_event_handler_register failed")); + } + + if (esp_event_handler_register(IP_EVENT, ESP_EVENT_ANY_ID, ð_event_handler, NULL) != ESP_OK) { + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("esp_event_handler_register failed")); + } + + esp_eth_config_t config = ETH_DEFAULT_CONFIG(self->mac, self->phy); + + esp_err_t esp_err = esp_eth_driver_install(&config, &self->eth_handle); + if (esp_err == ESP_OK) { + self->base.active = false; + self->initialized = true; + } else { + if (esp_err == ESP_ERR_INVALID_ARG) { + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("esp_eth_driver_install failed with invalid argument")); + } else if (esp_err == ESP_ERR_NO_MEM) { + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("esp_eth_driver_install failed with no memory for driver")); + } else { + mp_printf(&mp_plat_print, "esp_eth_driver_install failed with error code %d\n", esp_err); + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("esp_eth_driver_install failed")); + } + } + + self->eth_glue = esp_eth_new_netif_glue(self->eth_handle); + if (esp_netif_attach(self->base.netif, self->eth_glue) != ESP_OK) { + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("esp_netif_attach failed")); + } + + eth_status = ETH_INITIALIZED; + + return MP_OBJ_FROM_PTR(&lan_obj); +} +MP_DEFINE_CONST_FUN_OBJ_KW(esp_network_get_lan_obj, 0, get_lan); + +static mp_obj_t lan_active(size_t n_args, const mp_obj_t *args) { + lan_if_obj_t *self = MP_OBJ_TO_PTR(args[0]); + + if (n_args > 1) { + if (mp_obj_is_true(args[1])) { + if (self->base.active) { + return mp_const_true; + } + esp_netif_set_hostname(self->base.netif, mod_network_hostname_data); + self->base.active = (esp_eth_start(self->eth_handle) == ESP_OK); + if (!self->base.active) { + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("ethernet enable failed")); + } + } else { + self->base.active = !(esp_eth_stop(self->eth_handle) == ESP_OK); + if (self->base.active) { + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("ethernet disable failed")); + } + } + } + + return mp_obj_new_bool(self->base.active); +} +static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(lan_active_obj, 1, 2, lan_active); + +static mp_obj_t lan_deinit(mp_obj_t self_in) { + lan_if_obj_t *self = MP_OBJ_TO_PTR(self_in); + + esp_event_handler_unregister(IP_EVENT, ESP_EVENT_ANY_ID, ð_event_handler); + esp_event_handler_unregister(ETH_EVENT, ESP_EVENT_ANY_ID, ð_event_handler); + + self->base.active = !(esp_eth_stop(self->eth_handle) == ESP_OK); + if (self->base.active) { + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("ethernet disable failed")); + } + + ESP_ERROR_CHECK(esp_eth_del_netif_glue(self->eth_glue)); + ESP_ERROR_CHECK(esp_eth_driver_uninstall(self->eth_handle)); + + ESP_ERROR_CHECK(self->phy->del(self->phy)); + ESP_ERROR_CHECK(self->mac->del(self->mac)); + + esp_netif_destroy(self->base.netif); + self->initialized = false; + + return mp_obj_new_bool(self->base.active == false); +} +static MP_DEFINE_CONST_FUN_OBJ_1(lan_deinit_obj, lan_deinit); + +static mp_obj_t lan_status(mp_obj_t self_in) { + return MP_OBJ_NEW_SMALL_INT(eth_status); +} +static MP_DEFINE_CONST_FUN_OBJ_1(lan_status_obj, lan_status); + +static mp_obj_t lan_isconnected(mp_obj_t self_in) { + lan_if_obj_t *self = MP_OBJ_TO_PTR(self_in); + return mp_obj_new_bool(self->base.active && (eth_status == ETH_GOT_IP)); +} +static MP_DEFINE_CONST_FUN_OBJ_1(lan_isconnected_obj, lan_isconnected); + +static mp_obj_t lan_config(size_t n_args, const mp_obj_t *args, mp_map_t *kwargs) { + if (n_args != 1 && kwargs->used != 0) { + mp_raise_TypeError(MP_ERROR_TEXT("either pos or kw args are allowed")); + } + lan_if_obj_t *self = MP_OBJ_TO_PTR(args[0]); + + if (kwargs->used != 0) { + + for (size_t i = 0; i < kwargs->alloc; i++) { + if (mp_map_slot_is_filled(kwargs, i)) { + switch (mp_obj_str_get_qstr(kwargs->table[i].key)) { + case MP_QSTR_mac: { + mp_buffer_info_t bufinfo; + mp_get_buffer_raise(kwargs->table[i].value, &bufinfo, MP_BUFFER_READ); + if (bufinfo.len != 6) { + mp_raise_ValueError(MP_ERROR_TEXT("invalid buffer length")); + } + if ( + (esp_eth_ioctl(self->eth_handle, ETH_CMD_S_MAC_ADDR, bufinfo.buf) != ESP_OK) || + (esp_netif_set_mac(self->base.netif, bufinfo.buf) != ESP_OK) + ) { + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("failed setting MAC address")); + } + break; + } + default: + break; + } + } + } + return mp_const_none; + } + + if (n_args != 2) { + mp_raise_TypeError(MP_ERROR_TEXT("can query only one param")); + } + + mp_obj_t val = mp_const_none; + + switch (mp_obj_str_get_qstr(args[1])) { + case MP_QSTR_mac: { + uint8_t mac[6]; + esp_eth_ioctl(self->eth_handle, ETH_CMD_G_MAC_ADDR, mac); + return mp_obj_new_bytes(mac, sizeof(mac)); + } + case MP_QSTR_ifname: { + val = esp_ifname(self->base.netif); + break; + } + default: + mp_raise_ValueError(MP_ERROR_TEXT("unknown config param")); + } + + return val; +} +static MP_DEFINE_CONST_FUN_OBJ_KW(lan_config_obj, 1, lan_config); + +static mp_obj_t lan_set_default_netif(mp_obj_t self_in) { + lan_if_obj_t *self = MP_OBJ_TO_PTR(self_in); + esp_err_t err = esp_netif_set_default_netif(self->base.netif); + return mp_obj_new_int(err); +} +static MP_DEFINE_CONST_FUN_OBJ_1(lan_set_default_netif_obj, lan_set_default_netif); + +static const mp_rom_map_elem_t lan_if_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&lan_deinit_obj) }, + { MP_ROM_QSTR(MP_QSTR_active), MP_ROM_PTR(&lan_active_obj) }, + { MP_ROM_QSTR(MP_QSTR_isconnected), MP_ROM_PTR(&lan_isconnected_obj) }, + { MP_ROM_QSTR(MP_QSTR_status), MP_ROM_PTR(&lan_status_obj) }, + { MP_ROM_QSTR(MP_QSTR_config), MP_ROM_PTR(&lan_config_obj) }, + { MP_ROM_QSTR(MP_QSTR_set_default_netif), MP_ROM_PTR(&lan_set_default_netif_obj) }, + { MP_ROM_QSTR(MP_QSTR_ifconfig), MP_ROM_PTR(&esp_network_ifconfig_obj) }, + + { MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&lan_deinit_obj) }, +}; + +static MP_DEFINE_CONST_DICT(lan_if_locals_dict, lan_if_locals_dict_table); + +MP_DEFINE_CONST_OBJ_TYPE( + lan_if_type, + MP_QSTR_LAN, + MP_TYPE_FLAG_NONE, + locals_dict, &lan_if_locals_dict + ); + +#endif diff --git a/m5stack/network_wlan.c b/m5stack/network_wlan.c index 24340058..964b5c05 100644 --- a/m5stack/network_wlan.c +++ b/m5stack/network_wlan.c @@ -732,6 +732,16 @@ static mp_obj_t network_wlan_config(size_t n_args, const mp_obj_t *args, mp_map_ } MP_DEFINE_CONST_FUN_OBJ_KW(network_wlan_config_obj, 1, network_wlan_config); +static mp_obj_t network_wlan_set_default_netif(mp_obj_t self_in) { + wlan_if_obj_t *self = MP_OBJ_TO_PTR(self_in); + if (self->if_id == ESP_IF_WIFI_STA) { + esp_err_t err = esp_netif_set_default_netif(self->netif); + return mp_obj_new_int(err); + } + return mp_obj_new_int(ESP_ERR_NOT_SUPPORTED); +} +static MP_DEFINE_CONST_FUN_OBJ_1(network_wlan_set_default_netif_obj, network_wlan_set_default_netif); + static const mp_rom_map_elem_t wlan_if_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_active), MP_ROM_PTR(&network_wlan_active_obj) }, { MP_ROM_QSTR(MP_QSTR_connect), MP_ROM_PTR(&network_wlan_connect_obj) }, @@ -769,6 +779,9 @@ static const mp_rom_map_elem_t wlan_if_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_SEC_WPA2_WPA3_ENT), MP_ROM_INT(WIFI_AUTH_WPA2_WPA3_ENTERPRISE) }, #endif + { MP_ROM_QSTR(MP_QSTR_set_default_netif), MP_ROM_PTR(&network_wlan_set_default_netif_obj) }, + + // Constants { MP_ROM_QSTR(MP_QSTR_PM_NONE), MP_ROM_INT(WIFI_PS_NONE) }, { MP_ROM_QSTR(MP_QSTR_PM_PERFORMANCE), MP_ROM_INT(WIFI_PS_MIN_MODEM) }, { MP_ROM_QSTR(MP_QSTR_PM_POWERSAVE), MP_ROM_INT(WIFI_PS_MAX_MODEM) }, From 985eb0af6ac4cd59bb982a414c4940bb4a8301bf Mon Sep 17 00:00:00 2001 From: lbuque Date: Fri, 13 Jun 2025 19:44:00 +0800 Subject: [PATCH 125/322] cmodule/m5unified: Fix ppa driver compilation error. Signed-off-by: lbuque --- m5stack/cmodules/m5unified/m5unified_lvgl.c | 40 +++------------------ m5stack/components/M5Unified/mpy_lvgl.txt | 8 +++-- 2 files changed, 9 insertions(+), 39 deletions(-) diff --git a/m5stack/cmodules/m5unified/m5unified_lvgl.c b/m5stack/cmodules/m5unified/m5unified_lvgl.c index 7c3a1afe..34cb04e7 100644 --- a/m5stack/cmodules/m5unified/m5unified_lvgl.c +++ b/m5stack/cmodules/m5unified/m5unified_lvgl.c @@ -34,53 +34,21 @@ void lvgl_deinit() { } } -#if 0 -#include "esp_lcd_types.h" -#include "esp_lcd_mipi_dsi.h" - - -/** - * @brief BSP display configuration structure - * - */ -typedef struct { - int dummy; -} bsp_display_config_t; - - -esp_lcd_panel_handle_t tab5_panel_handle; -esp_lcd_panel_io_handle_t tab5_panel_io_handle; - - -esp_err_t bsp_display_new(const bsp_display_config_t *config, esp_lcd_panel_handle_t *ret_panel, - esp_lcd_panel_io_handle_t *ret_io) { - esp_err_t ret = ESP_OK; - bsp_lcd_handles_t handles; - ret = bsp_display_new_with_handles(config, &handles); - - *ret_panel = handles.panel; - *ret_io = handles.io; - - return ret; -} - -#endif #include "driver/ppa.h" +#if SOC_PPA_SUPPORTED ppa_client_handle_t ppa_srm_handle = NULL; - +#endif mp_obj_t gfx_lvgl_init(mp_obj_t self) { if (lvgl_timer) { return mp_const_none; } - #if 0 - bsp_display_new(NULL, &tab5_panel_handle, &tab5_panel_io_handle); - #endif - + #if SOC_PPA_SUPPORTED ppa_client_config_t ppa_srm_config = { .oper_type = PPA_OPERATION_SRM, }; ppa_register_client(&ppa_srm_config, &ppa_srm_handle); + #endif lv_init(); lvgl_timer = xTimerCreate("lvgl_timer", 10, pdTRUE, NULL, vTimerCallback); diff --git a/m5stack/components/M5Unified/mpy_lvgl.txt b/m5stack/components/M5Unified/mpy_lvgl.txt index 1c02d8f8..1d51e307 100644 --- a/m5stack/components/M5Unified/mpy_lvgl.txt +++ b/m5stack/components/M5Unified/mpy_lvgl.txt @@ -10,6 +10,7 @@ #include "esp_private/esp_cache_private.h" #include +#if SOC_PPA_SUPPORTED #define ALIGN_UP_BY(num, align) (((num) + ((align)-1)) & ~((align)-1)) extern ppa_client_handle_t ppa_srm_handle; @@ -116,7 +117,7 @@ void lvgl_port_rotate_area(lv_display_t* disp, lv_area_t* area) break; } } - +#endif void gfx_lvgl_flush(lv_display_t *disp_drv, const lv_area_t * area, uint8_t * px_map) { @@ -126,13 +127,13 @@ void gfx_lvgl_flush(lv_display_t *disp_drv, const lv_area_t * area, uint8_t * px return; } +#if SOC_PPA_SUPPORTED + int offsetx1 = area->x1; int offsetx2 = area->x2; int offsety1 = area->y1; int offsety2 = area->y2; - - lv_display_rotation_t rotation = lv_display_get_rotation(disp_drv); if (rotation > LV_DISPLAY_ROTATION_0) { @@ -171,6 +172,7 @@ void gfx_lvgl_flush(lv_display_t *disp_drv, const lv_area_t * area, uint8_t * px offsety1 = area->y1; offsety2 = area->y2; } +#endif int w = (area->x2 - area->x1 + 1); int h = (area->y2 - area->y1 + 1); From 9ac0f5f991412e90b55e023dd8226aa5b23f3b68 Mon Sep 17 00:00:00 2001 From: lbuque Date: Fri, 13 Jun 2025 19:44:57 +0800 Subject: [PATCH 126/322] version.text: Update to V2.2.9. Signed-off-by: lbuque --- m5stack/version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/m5stack/version.txt b/m5stack/version.txt index f44d1570..06784eab 100644 --- a/m5stack/version.txt +++ b/m5stack/version.txt @@ -1 +1 @@ -V2.2.8 \ No newline at end of file +V2.2.9 \ No newline at end of file From 1b9ef9e835c024408596b6e978f3e5cb837272bd Mon Sep 17 00:00:00 2001 From: lbuque Date: Wed, 18 Jun 2025 11:51:34 +0800 Subject: [PATCH 127/322] third-party: Fix compilation errors. Signed-off-by: lbuque --- third-party/CMakeLists.txt | 35 ++++------ third-party/CMakeListsDefault.cmake | 70 ++++++++++++++++--- .../mpconfigboard.cmake | 3 +- .../SEEED_STUDIO_XIAO_ESP32S3/sdkconfig.board | 5 +- third-party/boards/sdkconfig.base | 14 ++-- third-party/boards/sdkconfig.flash_12mb | 2 +- third-party/boards/sdkconfig.flash_16mb | 2 +- third-party/boards/sdkconfig.flash_4mb | 2 +- third-party/boards/sdkconfig.flash_8mb | 2 +- third-party/boards/sdkconfig.spiram | 18 ++--- third-party/boards/sdkconfig.spiram_sx | 20 ++---- third-party/main.c | 7 ++ .../{main_esp32s3 => main}/CMakeLists.txt | 0 .../{main_esp32s3 => main}/idf_component.yml | 0 third-party/{main_esp32s3 => main}/linker.lf | 0 15 files changed, 117 insertions(+), 63 deletions(-) rename third-party/{main_esp32s3 => main}/CMakeLists.txt (100%) rename third-party/{main_esp32s3 => main}/idf_component.yml (100%) rename third-party/{main_esp32s3 => main}/linker.lf (100%) diff --git a/third-party/CMakeLists.txt b/third-party/CMakeLists.txt index 976ce3fc..6665bae1 100644 --- a/third-party/CMakeLists.txt +++ b/third-party/CMakeLists.txt @@ -3,7 +3,10 @@ # SPDX-License-Identifier: MIT # Top-level cmake file for building MicroPython on ESP32. - +# +# Note for maintainers: Where possible, functionality should be put into +# esp32_common.cmake not this file. This is because this CMakeLists.txt file +# needs to be duplicated for out-of-tree builds, and can easily get out of date. cmake_minimum_required(VERSION 3.12) set(CMAKE_C_FLAGS "-Wno-unused-variable -Wno-unused-but-set-variable") @@ -18,7 +21,7 @@ set(IDF_VERSION "${IDF_VERSION_MAJOR}.${IDF_VERSION_MINOR}.${IDF_VERSION_PATCH}" # Set the board if it's not already set. if(NOT MICROPY_BOARD) - set(MICROPY_BOARD M5STACK_4MB) + set(MICROPY_BOARD ESPRESSIF_ESP32_S3_BOX_3) endif() # Set the board directory and check that it exists. @@ -42,12 +45,6 @@ set(SDKCONFIG ${CMAKE_BINARY_DIR}/sdkconfig) # Save the manifest file set from the cmake command line. set(MICROPY_USER_FROZEN_MANIFEST ${MICROPY_FROZEN_MANIFEST}) -# Specific options for IDF v5.2 and later -set(SDKCONFIG_IDF_VERSION_SPECIFIC "") -if (IDF_VERSION VERSION_GREATER_EQUAL "5.2.0") - set(SDKCONFIG_IDF_VERSION_SPECIFIC boards/sdkconfig.idf52) -endif() - # Include board config; this is expected to set (among other options): # - SDKCONFIG_DEFAULTS # - IDF_TARGET @@ -77,19 +74,19 @@ set(SDKCONFIG_DEFAULTS ${CMAKE_BINARY_DIR}/sdkconfig.combined) if(BUILD_WITH_LVGL) # Include LVGL component, ignore KCONFIG - idf_build_set_property(LV_MICROPYTHON 1) - idf_build_component(${CMAKE_SOURCE_DIR}/components/lv_bindings/lvgl) - idf_build_set_property(COMPILE_DEFINITIONS "-DLV_KCONFIG_IGNORE" APPEND) - separate_arguments(LV_CFLAGS_ENV UNIX_COMMAND $ENV{LV_CFLAGS}) - list(APPEND LV_CFLAGS ${LV_CFLAGS_ENV}) - idf_build_set_property(COMPILE_DEFINITIONS "${LV_CFLAGS}" APPEND) + # idf_build_set_property(LV_MICROPYTHON 1) + # idf_build_component(${CMAKE_SOURCE_DIR}/components/lv_bindings/lvgl) + # idf_build_set_property(COMPILE_DEFINITIONS "-DLV_KCONFIG_IGNORE" APPEND) + # separate_arguments(LV_CFLAGS_ENV UNIX_COMMAND $ENV{LV_CFLAGS}) + # list(APPEND LV_CFLAGS ${LV_CFLAGS_ENV}) + # idf_build_set_property(COMPILE_DEFINITIONS "${LV_CFLAGS}" APPEND) endif() # Include main IDF cmake file. include($ENV{IDF_PATH}/tools/cmake/project.cmake) # Set the location of the main component for the project (one per target). -list(APPEND EXTRA_COMPONENT_DIRS main_${IDF_TARGET}) +list(APPEND EXTRA_COMPONENT_DIRS main) list(APPEND EXTRA_COMPONENT_DIRS ../m5stack/components/esp32-camera) list(APPEND EXTRA_COMPONENT_DIRS ../m5stack/components/libffi) # list(APPEND EXTRA_COMPONENT_DIRS ../m5stack/components/lv_bindings) @@ -97,14 +94,8 @@ list(APPEND EXTRA_COMPONENT_DIRS ../m5stack/components/M5Unified/M5GFX) list(APPEND EXTRA_COMPONENT_DIRS ../m5stack/components/M5Unified/M5Unified) list(APPEND EXTRA_COMPONENT_DIRS ./components/BOX3GFX) list(APPEND EXTRA_COMPONENT_DIRS ../m5stack/components/uiflow_utility) +list(APPEND EXTRA_COMPONENT_DIRS ../m5stack/components/esp_dmx) # list(APPEND EXTRA_COMPONENT_DIRS ./boards) -# Enable the panic handler wrapper -idf_build_set_property(LINK_OPTIONS "-Wl,--wrap=esp_panic_handler" APPEND) - -# Patch LWIP memory pool allocators (see lwip_patch.c) -idf_build_set_property(LINK_OPTIONS "-Wl,--wrap=memp_malloc" APPEND) -idf_build_set_property(LINK_OPTIONS "-Wl,--wrap=memp_free" APPEND) - # Define the project. project(micropython) diff --git a/third-party/CMakeListsDefault.cmake b/third-party/CMakeListsDefault.cmake index ed503ca2..761594fd 100644 --- a/third-party/CMakeListsDefault.cmake +++ b/third-party/CMakeListsDefault.cmake @@ -12,6 +12,20 @@ if(NOT MICROPY_PORT_DIR) get_filename_component(MICROPY_PORT_DIR ${MICROPY_DIR}/ports/esp32 ABSOLUTE) endif() +# RISC-V specific inclusions +if(CONFIG_IDF_TARGET_ARCH_RISCV) + list(APPEND MICROPY_SOURCE_LIB + ${MICROPY_DIR}/shared/runtime/gchelper_native.c + ${MICROPY_DIR}/shared/runtime/gchelper_rv32i.s + ) +endif() + +if(NOT DEFINED MICROPY_PY_TINYUSB) + if(CONFIG_IDF_TARGET_ESP32S2 OR CONFIG_IDF_TARGET_ESP32S3) + set(MICROPY_PY_TINYUSB ON) + endif() +endif() + # Include core source components. include(${MICROPY_DIR}/py/py.cmake) @@ -57,7 +71,7 @@ list(APPEND MICROPY_SOURCE_DRIVERS ${MICROPY_DIR}/drivers/dht/dht.c ) -string(CONCAT GIT_SUBMODULES "${GIT_SUBMODULES} " lib/tinyusb) +list(APPEND GIT_SUBMODULES lib/tinyusb) if(MICROPY_PY_TINYUSB) set(TINYUSB_SRC "${MICROPY_DIR}/lib/tinyusb/src") string(TOUPPER OPT_MCU_${IDF_TARGET} tusb_mcu) @@ -76,6 +90,7 @@ if(MICROPY_PY_TINYUSB) ${MICROPY_DIR}/shared/tinyusb/mp_usbd.c ${MICROPY_DIR}/shared/tinyusb/mp_usbd_cdc.c ${MICROPY_DIR}/shared/tinyusb/mp_usbd_descriptor.c + ${MICROPY_DIR}/shared/tinyusb/mp_usbd_runtime.c ) list(APPEND MICROPY_INC_TINYUSB @@ -121,7 +136,7 @@ set(MICROPY_SOURCE_PORT ${PROJECT_DIR}/../m5stack/modesp32.c ${PROJECT_DIR}/../micropython/ports/esp32/machine_hw_spi.c ${PROJECT_DIR}/../micropython/ports/esp32/mpthreadport.c - ${PROJECT_DIR}/../micropython/ports/esp32/machine_rtc.c + ${PROJECT_DIR}/../m5stack/machine_rtc.c ${PROJECT_DIR}/../micropython/ports/esp32/machine_sdcard.c ${PROJECT_DIR}/../micropython/ports/esp32/modespnow.c ) @@ -153,6 +168,7 @@ set(MICROPY_SOURCE_QSTR ${MICROPY_SOURCE_LIB} ${MICROPY_SOURCE_PORT} ${MICROPY_SOURCE_BOARD} + ${MICROPY_SOURCE_TINYUSB} # ${MICROPY_SOURCE_M5UNIFIED} ${MICROPY_SOURCE_M5CAMERA} ) @@ -191,7 +207,6 @@ set(IDF_COMPONENTS ulp usb vfs - xtensa esp_http_client esp-tls libffi @@ -200,8 +215,15 @@ set(IDF_COMPONENTS BOX3GFX esp32-camera uiflow_utility + esp_dmx + esp_mm + esp_driver_ppa ) +if(CONFIG_IDF_TARGET_ESP32 OR CONFIG_IDF_TARGET_ESP32S2 OR CONFIG_IDF_TARGET_ESP32S3) + list(APPEND IDF_COMPONENTS xtensa) +endif() + if(IDF_TARGET STREQUAL "esp32" OR IDF_TARGET STREQUAL "esp32s3") list(APPEND IDF_COMPONENTS boards) list(APPEND IDF_COMPONENTS audio_pipeline) @@ -211,6 +233,22 @@ if(IDF_TARGET STREQUAL "esp32" OR IDF_TARGET STREQUAL "esp32s3") list(APPEND IDF_COMPONENTS esp_codec_dev) endif() +# Provide the default LD fragment if not set +if (MICROPY_USER_LDFRAGMENTS) + set(MICROPY_LDFRAGMENTS ${MICROPY_USER_LDFRAGMENTS}) +endif() + +if (UPDATE_SUBMODULES) + # ESP-IDF checks if some paths exist before CMake does. Some paths don't + # yet exist if this is an UPDATE_SUBMODULES pass on a brand new checkout, so remove + # any path which might not exist yet. A "real" build will not set UPDATE_SUBMODULES. + unset(MICROPY_SOURCE_TINYUSB) + unset(MICROPY_SOURCE_EXTMOD) + unset(MICROPY_SOURCE_LIB) + unset(MICROPY_INC_TINYUSB) + unset(MICROPY_INC_CORE) +endif() + # Register the main IDF component. idf_component_register( SRCS @@ -230,7 +268,7 @@ idf_component_register( ${MICROPY_BOARD_DIR} ${CMAKE_BINARY_DIR} LDFRAGMENTS - linker.lf + ${MICROPY_LDFRAGMENTS} REQUIRES ${IDF_COMPONENTS} ) @@ -239,8 +277,10 @@ idf_component_register( set(MICROPY_TARGET ${COMPONENT_TARGET}) # Define mpy-cross flags, for use with frozen code. -if(CONFIG_IDF_TARGET_ARCH STREQUAL "xtensa") -set(MICROPY_CROSS_FLAGS -march=xtensawin) +if(CONFIG_IDF_TARGET_ARCH_XTENSA) + set(MICROPY_CROSS_FLAGS -march=xtensawin) +elseif(CONFIG_IDF_TARGET_ARCH_RISCV) + set(MICROPY_CROSS_FLAGS -march=rv32imc) endif() # Set compile options for this port. @@ -266,7 +306,7 @@ target_compile_options(${MICROPY_TARGET} PUBLIC ) target_link_options(${MICROPY_TARGET} PUBLIC - ${MICROPY_LINK_TINYUSB} + ${MICROPY_LINK_TINYUSB} ) # Additional include directories needed for private NimBLE headers. @@ -278,6 +318,20 @@ target_include_directories(${MICROPY_TARGET} PUBLIC target_link_libraries(${MICROPY_TARGET} micropy_extmod_btree) target_link_libraries(${MICROPY_TARGET} usermod) +# Extra linker options +# (when wrap symbols are in standalone files, --undefined ensures +# the linker doesn't skip that file.) +target_link_options(${MICROPY_TARGET} PUBLIC + # Patch LWIP memory pool allocators (see lwip_patch.c) + -Wl,--undefined=memp_malloc + -Wl,--wrap=memp_malloc + -Wl,--wrap=memp_free + + # Enable the panic handler wrapper + -Wl,--undefined=esp_panic_handler + -Wl,--wrap=esp_panic_handler +) + # Collect all of the include directories and compile definitions for the IDF components, # including those added by the IDF Component Manager via idf_components.yaml. foreach(comp ${__COMPONENT_NAMES_RESOLVED}) @@ -313,4 +367,4 @@ add_custom_command( ${GEN_PINS_PREFIX} VERBATIM COMMAND_EXPAND_LISTS -) \ No newline at end of file +) diff --git a/third-party/boards/SEEED_STUDIO_XIAO_ESP32S3/mpconfigboard.cmake b/third-party/boards/SEEED_STUDIO_XIAO_ESP32S3/mpconfigboard.cmake index 275a75c0..42238e8e 100644 --- a/third-party/boards/SEEED_STUDIO_XIAO_ESP32S3/mpconfigboard.cmake +++ b/third-party/boards/SEEED_STUDIO_XIAO_ESP32S3/mpconfigboard.cmake @@ -11,8 +11,8 @@ set(BOARD_ID 136) set(M5_CAMERA_MODULE_ENABLE TRUE) set(SDKCONFIG_DEFAULTS - ./boards/SEEED_STUDIO_XIAO_ESP32S3/sdkconfig.board ./boards/sdkconfig.base + ${SDKCONFIG_IDF_VERSION_SPECIFIC} ./boards/sdkconfig.240mhz ./boards/sdkconfig.disable_iram ./boards/sdkconfig.ble @@ -20,6 +20,7 @@ set(SDKCONFIG_DEFAULTS ./boards/sdkconfig.flash_8mb ./boards/sdkconfig.spiram_sx ./boards/sdkconfig.spiram_oct + ./boards/SEEED_STUDIO_XIAO_ESP32S3/sdkconfig.board ) # If not enable LVGL, ignore this... diff --git a/third-party/boards/SEEED_STUDIO_XIAO_ESP32S3/sdkconfig.board b/third-party/boards/SEEED_STUDIO_XIAO_ESP32S3/sdkconfig.board index bf0ff636..3a24337b 100644 --- a/third-party/boards/SEEED_STUDIO_XIAO_ESP32S3/sdkconfig.board +++ b/third-party/boards/SEEED_STUDIO_XIAO_ESP32S3/sdkconfig.board @@ -14,7 +14,10 @@ CONFIG_SPIRAM_MEMTEST= # M5STACK UiFlow USB description CONFIG_TINYUSB_DESC_CDC_STRING="Seeed XIAO-ESP32S3(UiFlow2)" +# SSL +CONFIG_MBEDTLS_AES_USE_INTERRUPT=n + # # Audio HAL # -CONFIG_FREERTOS_ENABLE_BACKWARD_COMPATIBILITY=y \ No newline at end of file +CONFIG_FREERTOS_ENABLE_BACKWARD_COMPATIBILITY=y diff --git a/third-party/boards/sdkconfig.base b/third-party/boards/sdkconfig.base index d2349081..4fd93a45 100644 --- a/third-party/boards/sdkconfig.base +++ b/third-party/boards/sdkconfig.base @@ -8,8 +8,8 @@ CONFIG_COMPILER_OPTIMIZATION_PERF=y CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_DISABLE=y # Application manager -CONFIG_APP_EXCLUDE_PROJECT_VER_VAR=y -CONFIG_APP_EXCLUDE_PROJECT_NAME_VAR=y +# CONFIG_APP_EXCLUDE_PROJECT_VER_VAR=y +# CONFIG_APP_EXCLUDE_PROJECT_NAME_VAR=y # Bootloader config CONFIG_BOOTLOADER_LOG_LEVEL_WARN=y @@ -17,7 +17,7 @@ CONFIG_BOOTLOADER_SKIP_VALIDATE_IN_DEEP_SLEEP=y CONFIG_BOOTLOADER_APP_ROLLBACK_ENABLE=y # Change default log level to "ERROR" (instead of "INFO") -CONFIG_LOG_DEFAULT_LEVEL_INFO=y +CONFIG_LOG_DEFAULT_LEVEL_ERROR=y # Set the maximum included log level higher than the default, # so esp.osdebug() can enable more logging at runtime. @@ -49,7 +49,8 @@ CONFIG_ESP_SYSTEM_MEMPROT_FEATURE=n # FreeRTOS CONFIG_FREERTOS_THREAD_LOCAL_STORAGE_POINTERS=2 CONFIG_FREERTOS_SUPPORT_STATIC_ALLOCATION=y -CONFIG_FREERTOS_ENABLE_STATIC_TASK_CLEAN_UP=y +CONFIG_FREERTOS_ENABLE_STATIC_TASK_CLEAN_UP=n +CONFIG_FREERTOS_TASK_PRE_DELETION_HOOK=y # UDP CONFIG_LWIP_PPP_SUPPORT=y @@ -63,6 +64,9 @@ CONFIG_MBEDTLS_HAVE_TIME_DATE=y CONFIG_MBEDTLS_PLATFORM_TIME_ALT=y CONFIG_MBEDTLS_HAVE_TIME=y +# Enable DTLS +CONFIG_MBEDTLS_SSL_PROTO_DTLS=y + # Disable ALPN support as it's not implemented in MicroPython CONFIG_MBEDTLS_SSL_ALPN=n @@ -126,7 +130,7 @@ CONFIG_ETH_SPI_ETHERNET_DM9051=y # functions are in ROM. Note some newer chips (c2,c6) have "full" newlib # formatting in ROM instead and should override this, check # ESP_ROM_HAS_NEWLIB_NANO_FORMAT. -CONFIG_NEWLIB_NANO_FORMAT=y +CONFIG_NEWLIB_NANO_FORMAT=n # IRAM/DRAM split protection is a memory protection feature on some parts # that support SOC_CPU_IDRAM_SPLIT_USING_PMP, eg. C2, C5, C6, H2 diff --git a/third-party/boards/sdkconfig.flash_12mb b/third-party/boards/sdkconfig.flash_12mb index f34ebe71..1ff99318 100644 --- a/third-party/boards/sdkconfig.flash_12mb +++ b/third-party/boards/sdkconfig.flash_12mb @@ -5,4 +5,4 @@ # For cmake build CONFIG_ESPTOOLPY_FLASHSIZE_16MB=y CONFIG_PARTITION_TABLE_CUSTOM=y -CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions_16mb.csv" \ No newline at end of file +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions_16mb.csv" diff --git a/third-party/boards/sdkconfig.flash_16mb b/third-party/boards/sdkconfig.flash_16mb index f34ebe71..1ff99318 100644 --- a/third-party/boards/sdkconfig.flash_16mb +++ b/third-party/boards/sdkconfig.flash_16mb @@ -5,4 +5,4 @@ # For cmake build CONFIG_ESPTOOLPY_FLASHSIZE_16MB=y CONFIG_PARTITION_TABLE_CUSTOM=y -CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions_16mb.csv" \ No newline at end of file +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions_16mb.csv" diff --git a/third-party/boards/sdkconfig.flash_4mb b/third-party/boards/sdkconfig.flash_4mb index 56560a4f..1d78c602 100644 --- a/third-party/boards/sdkconfig.flash_4mb +++ b/third-party/boards/sdkconfig.flash_4mb @@ -5,4 +5,4 @@ # For cmake build CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y CONFIG_PARTITION_TABLE_CUSTOM=y -CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions_4mb.csv" \ No newline at end of file +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions_4mb.csv" diff --git a/third-party/boards/sdkconfig.flash_8mb b/third-party/boards/sdkconfig.flash_8mb index f754adf0..083e187f 100644 --- a/third-party/boards/sdkconfig.flash_8mb +++ b/third-party/boards/sdkconfig.flash_8mb @@ -5,4 +5,4 @@ # For cmake build CONFIG_ESPTOOLPY_FLASHSIZE_8MB=y CONFIG_PARTITION_TABLE_CUSTOM=y -CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions_8mb.csv" \ No newline at end of file +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions_8mb.csv" diff --git a/third-party/boards/sdkconfig.spiram b/third-party/boards/sdkconfig.spiram index bc9008ff..35fe3c67 100644 --- a/third-party/boards/sdkconfig.spiram +++ b/third-party/boards/sdkconfig.spiram @@ -1,16 +1,18 @@ -# SPDX-FileCopyrightText: Copyright (c) 2014 Damien P. George -# -# SPDX-License-Identifier: MIT - # MicroPython on ESP32, ESP IDF configuration with SPIRAM support -CONFIG_ESP32_SPIRAM_SUPPORT=y +CONFIG_SPIRAM=y CONFIG_SPIRAM_CACHE_WORKAROUND=y CONFIG_SPIRAM_IGNORE_NOTFOUND=y -#CONFIG_SPIRAM_USE_MEMMAP=y +CONFIG_SPIRAM_USE_MALLOC=y + +# This is the threshold for preferring small allocations from internal memory +# first, before failing over to PSRAM. +CONFIG_SPIRAM_MALLOC_ALWAYSINTERNAL=8192 +# SPIRAM increases the size of the firmware and overflows iram0_0_seg, due +# to PSRAM bug workarounds. Apply some options to reduce the firmware size. CONFIG_COMPILER_OPTIMIZATION_SIZE=y CONFIG_COMPILER_OPTIMIZATION_CHECKS_SILENT=y -CONFIG_RINGBUF_PLACE_FUNCTIONS_INTO_FLASH=y +# CONFIG_RINGBUF_PLACE_FUNCTIONS_INTO_FLASH=y # Workaround required: see main_esp32/linker.lf CONFIG_FREERTOS_PLACE_FUNCTIONS_INTO_FLASH=y -CONFIG_FREERTOS_PLACE_SNAPSHOT_FUNS_INTO_FLASH=y \ No newline at end of file +CONFIG_FREERTOS_PLACE_SNAPSHOT_FUNS_INTO_FLASH=y diff --git a/third-party/boards/sdkconfig.spiram_sx b/third-party/boards/sdkconfig.spiram_sx index 71f07da0..efe53ada 100644 --- a/third-party/boards/sdkconfig.spiram_sx +++ b/third-party/boards/sdkconfig.spiram_sx @@ -1,22 +1,14 @@ -# SPDX-FileCopyrightText: Copyright (c) 2014 Damien P. George -# -# SPDX-License-Identifier: MIT - # MicroPython on ESP32-S2 and ESP32-PAD1_subscript_3, ESP IDF configuration with SPIRAM support -CONFIG_ESP32S2_SPIRAM_SUPPORT=y -CONFIG_ESP32S3_SPIRAM_SUPPORT=y CONFIG_SPIRAM_MODE_QUAD=y CONFIG_SPIRAM_TYPE_AUTO=y -CONFIG_SPIRAM_ALLOW_STACK_EXTERNAL_MEMORY=y -CONFIG_DEFAULT_PSRAM_CLK_IO=30 -CONFIG_DEFAULT_PSRAM_CS_IO=26 +CONFIG_SPIRAM_CLK_IO=30 +CONFIG_SPIRAM_CS_IO=26 CONFIG_SPIRAM_SPEED_80M=y -CONFIG_SPIRAM_SPEED=80 CONFIG_SPIRAM=y CONFIG_SPIRAM_BOOT_INIT=y CONFIG_SPIRAM_IGNORE_NOTFOUND=y -# CONFIG_SPIRAM_USE_MEMMAP=y CONFIG_SPIRAM_USE_MALLOC=y -CONFIG_SPIRAM_MEMTEST=y -CONFIG_SPIRAM_MALLOC_ALWAYSINTERNAL=16384 -CONFIG_SPIRAM_MALLOC_RESERVE_INTERNAL=32768 + +# This is the threshold for preferring small allocations from internal memory +# first, before failing over to PSRAM. +CONFIG_SPIRAM_MALLOC_ALWAYSINTERNAL=8192 diff --git a/third-party/main.c b/third-party/main.c index 55ab4357..0a576e23 100644 --- a/third-party/main.c +++ b/third-party/main.c @@ -238,6 +238,13 @@ void *esp_native_code_commit(void *buf, size_t len, void *reloc) { len = (len + 3) & ~3; size_t len_node = sizeof(native_code_node_t) + len; native_code_node_t *node = heap_caps_malloc(len_node, MALLOC_CAP_EXEC); + #if CONFIG_IDF_TARGET_ESP32S2 + // Workaround for ESP-IDF bug https://github.com/espressif/esp-idf/issues/14835 + if (node != NULL && !esp_ptr_executable(node)) { + free(node); + node = NULL; + } + #endif // CONFIG_IDF_TARGET_ESP32S2 if (node == NULL) { m_malloc_fail(len_node); } diff --git a/third-party/main_esp32s3/CMakeLists.txt b/third-party/main/CMakeLists.txt similarity index 100% rename from third-party/main_esp32s3/CMakeLists.txt rename to third-party/main/CMakeLists.txt diff --git a/third-party/main_esp32s3/idf_component.yml b/third-party/main/idf_component.yml similarity index 100% rename from third-party/main_esp32s3/idf_component.yml rename to third-party/main/idf_component.yml diff --git a/third-party/main_esp32s3/linker.lf b/third-party/main/linker.lf similarity index 100% rename from third-party/main_esp32s3/linker.lf rename to third-party/main/linker.lf From 29cd412f2a1c66bdc17ff8460c6da4f83c600bc2 Mon Sep 17 00:00:00 2001 From: lbuque Date: Tue, 17 Jun 2025 17:18:31 +0800 Subject: [PATCH 128/322] boards/M5STACK_Tab5: Disable TLSP deletion callbacks. Signed-off-by: lbuque --- m5stack/boards/M5STACK_Tab5/mpconfigboard.cmake | 1 + m5stack/boards/M5STACK_Tab5/sdkconfig.freertos | 1 + 2 files changed, 2 insertions(+) create mode 100644 m5stack/boards/M5STACK_Tab5/sdkconfig.freertos diff --git a/m5stack/boards/M5STACK_Tab5/mpconfigboard.cmake b/m5stack/boards/M5STACK_Tab5/mpconfigboard.cmake index 00c4ef5a..d0b3fcb8 100644 --- a/m5stack/boards/M5STACK_Tab5/mpconfigboard.cmake +++ b/m5stack/boards/M5STACK_Tab5/mpconfigboard.cmake @@ -17,6 +17,7 @@ set(SDKCONFIG_DEFAULTS # ./boards/sdkconfig.usb_cdc ./boards/sdkconfig.flash_16mb_omv ./boards/sdkconfig.freertos + ./boards/M5STACK_Tab5/sdkconfig.freertos ./boards/M5STACK_Tab5/sdkconfig.spiram_hex ./boards/M5STACK_Tab5/sdkconfig.adf ./boards/M5STACK_Tab5/sdkconfig.esp_hosted diff --git a/m5stack/boards/M5STACK_Tab5/sdkconfig.freertos b/m5stack/boards/M5STACK_Tab5/sdkconfig.freertos new file mode 100644 index 00000000..7ea08365 --- /dev/null +++ b/m5stack/boards/M5STACK_Tab5/sdkconfig.freertos @@ -0,0 +1 @@ +CONFIG_FREERTOS_TLSP_DELETION_CALLBACKS=n From 816eb7f6a2626386790ef7a6448de1dfacd72cb1 Mon Sep 17 00:00:00 2001 From: lbuque Date: Tue, 17 Jun 2025 17:20:45 +0800 Subject: [PATCH 129/322] components/M5Unified: Change the default orientation of the screen. 1. The M5GFX rotates the screen 270 degrees. 2. Use lvgl and do not rotate the screen. Signed-off-by: lbuque --- m5stack/components/M5Unified/mpy_m5unified.cpp | 3 +++ m5stack/modules/startup/tab5/__init__.py | 1 + 2 files changed, 4 insertions(+) diff --git a/m5stack/components/M5Unified/mpy_m5unified.cpp b/m5stack/components/M5Unified/mpy_m5unified.cpp index 02d64887..5b07f8ab 100644 --- a/m5stack/components/M5Unified/mpy_m5unified.cpp +++ b/m5stack/components/M5Unified/mpy_m5unified.cpp @@ -480,6 +480,9 @@ mp_obj_t m5_begin(size_t n_args, const mp_obj_t *args) { // } + if (M5.getBoard() == m5::board_t::board_M5Tab5) { + M5.Lcd.setRotation(3); + } M5.Display.clear(); // default display m5_display.gfx = (void *)(&(M5.Display)); diff --git a/m5stack/modules/startup/tab5/__init__.py b/m5stack/modules/startup/tab5/__init__.py index 2a2479ed..1fd8eb55 100644 --- a/m5stack/modules/startup/tab5/__init__.py +++ b/m5stack/modules/startup/tab5/__init__.py @@ -25,6 +25,7 @@ def startup(self, ssid: str, pswd: str, timeout: int = 60) -> None: self.launcher = Launcher() def _init_lvgl(self): + M5.Lcd.setRotation(0) M5.Lcd.lvgl_init() disp_buf0 = lv.draw_buf_create( From fc0a29f5a8d03f8b873a61e960d6facae7430b09 Mon Sep 17 00:00:00 2001 From: lbuque Date: Tue, 17 Jun 2025 17:23:01 +0800 Subject: [PATCH 130/322] modules/startup/tab5: Temporary ban on the use of ezdata. The MQTT links of ezdata and m5things will conflict, causing m5things to be unable to connect normally. Signed-off-by: lbuque --- m5stack/modules/startup/tab5/launcher/launcher.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/m5stack/modules/startup/tab5/launcher/launcher.py b/m5stack/modules/startup/tab5/launcher/launcher.py index 5776526d..153859c2 100644 --- a/m5stack/modules/startup/tab5/launcher/launcher.py +++ b/m5stack/modules/startup/tab5/launcher/launcher.py @@ -88,16 +88,16 @@ async def _main(self): AppManager.install_app("UART", AppUart) AppManager.install_app("GPIO", AppDummy) AppManager.install_app("ADC", AppDummy) - AppManager.install_app("EzData", AppEzdata) - AppManager.install_app("EzDataSettings", AppEzdataSettings) + # AppManager.install_app("EzData", AppEzdata) + # AppManager.install_app("EzDataSettings", AppEzdataSettings) # Create components self._status_bar = StatusBar() self._app_dock = AppDock() - self._ezdata_dock = EzdataDock() + # self._ezdata_dock = EzdataDock() # Start ezdata service - Ezdata.start() + # Ezdata.start() # Keep app manager running while True: From b15b1236d562af25bddbd4c0137b751dfd79265b Mon Sep 17 00:00:00 2001 From: lbuque Date: Wed, 18 Jun 2025 12:12:03 +0800 Subject: [PATCH 131/322] tools/ci.sh: Update CI strategy. Signed-off-by: lbuque --- .gitlab-ci.yml | 193 +++++++++++++++++++++++++++++++++++++++++++++++-- tools/ci.sh | 66 ++++++++++++++++- 2 files changed, 252 insertions(+), 7 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 62923b74..16882c21 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -10,7 +10,13 @@ cache: stages: - code_format - - build_quick + - build_uint + - build_module + - build_base + - build_hat + - build_cmodule + - build_third_party + - build_board - docs - build - release @@ -29,18 +35,21 @@ code-format: - uiflow-firmware only: changes: + - ".gitlab-ci.yml" - "m5stack/**" - "examples/**" - "tests/**" - "third-party/**" + - "tools/ci.sh" -build-quick-job: - stage: build_quick + +build-uint-and-common-job: + stage: build_uint script: - sudo apt-get update -qy - sudo apt-get install git wget flex bison gperf quilt python3 python3-pip python3-venv cmake ninja-build ccache libffi-dev libssl-dev dfu-util libusb-1.0-0 -y - source tools/ci.sh && ci_esp32_idf541_setup - - source tools/ci.sh && ci_esp32_quick_build + - source tools/ci.sh && ci_unit_build artifacts: paths: - m5stack/build-*/uiflow-*-*.bin @@ -49,11 +58,185 @@ build-quick-job: - uiflow-firmware only: changes: - - "m5stack/**" + - "m5stack/libs/driver/**" + - "m5stack/libs/unit/**" + - "m5stack/libs/bleuart/**" + - "m5stack/libs/ezdata/**" + - "m5stack/libs/hardware/**" + - "m5stack/libs/image_plus/**" + - "m5stack/libs/m5espnow/**" + - "m5stack/libs/modbus/**" + - "m5stack/libs/requests2/**" + - "m5stack/libs/umqtt/**" + - "m5stack/libs/usb/**" + - "m5stack/libs/utility/**" + - "m5stack/libs/*.py" + + +build-module-job: + stage: build_module + script: + - sudo apt-get update -qy + - sudo apt-get install git wget flex bison gperf quilt python3 python3-pip python3-venv cmake ninja-build ccache libffi-dev libssl-dev dfu-util libusb-1.0-0 -y + - source tools/ci.sh && ci_esp32_idf541_setup + - source tools/ci.sh && ci_module_build + artifacts: + paths: + - m5stack/build-*/uiflow-*-*.bin + - third-party/build-*/uiflow-*-*.bin + tags: + - uiflow-firmware + only: + changes: + - "m5stack/libs/driver/**" + - "m5stack/libs/unit/**" + - "m5stack/libs/module/**" + + +build-base-job: + stage: build_base + script: + - sudo apt-get update -qy + - sudo apt-get install git wget flex bison gperf quilt python3 python3-pip python3-venv cmake ninja-build ccache libffi-dev libssl-dev dfu-util libusb-1.0-0 -y + - source tools/ci.sh && ci_esp32_idf541_setup + - source tools/ci.sh && ci_base_build + artifacts: + paths: + - m5stack/build-*/uiflow-*-*.bin + - third-party/build-*/uiflow-*-*.bin + tags: + - uiflow-firmware + only: + changes: + - "m5stack/libs/driver/**" + - "m5stack/libs/unit/**" + - "m5stack/libs/base/**" + + +build-hat-job: + stage: build_hat + script: + - sudo apt-get update -qy + - sudo apt-get install git wget flex bison gperf quilt python3 python3-pip python3-venv cmake ninja-build ccache libffi-dev libssl-dev dfu-util libusb-1.0-0 -y + - source tools/ci.sh && ci_esp32_idf541_setup + - source tools/ci.sh && ci_hat_build + artifacts: + paths: + - m5stack/build-*/uiflow-*-*.bin + - third-party/build-*/uiflow-*-*.bin + tags: + - uiflow-firmware + only: + changes: + - "m5stack/libs/driver/**" + - "m5stack/libs/unit/**" + - "m5stack/libs/hat/**" + + +build-cmodule-job: + stage: build_cmodule + script: + - sudo apt-get update -qy + - sudo apt-get install git wget flex bison gperf quilt python3 python3-pip python3-venv cmake ninja-build ccache libffi-dev libssl-dev dfu-util libusb-1.0-0 -y + - source tools/ci.sh && ci_esp32_idf541_setup + - source tools/ci.sh && ci_esp32_nightly_build + artifacts: + paths: + - m5stack/build-*/uiflow-*-*.bin + - third-party/build-*/uiflow-*-*.bin + tags: + - uiflow-firmware + only: + changes: + - ".gitlab-ci.yml" + - "esp-adf/**" + - "m5stack/boards/include/**" + - "m5stack/boards/*.txt" + - "m5stack/boards/Kconfig" + - "m5stack/boards/*.py" + - "m5stack/boards/sdkconfig.*" + - "m5stack/cmodule/**" + - "m5stack/components/**" + - "m5stack/fs/**" + - "m5stack/include/**" + - "m5stack/main/**" + - "m5stack/*.c" + - "m5stack/*.cpp" + - "m5stack/*.h" + - "m5stack/*.cmake" + - "m5stack/*.csv" + - "m5stack/*.txt" - "tools/**" + +build-third-party-job: + stage: build_third_party + script: + - sudo apt-get update -qy + - sudo apt-get install git wget flex bison gperf quilt python3 python3-pip python3-venv cmake ninja-build ccache libffi-dev libssl-dev dfu-util libusb-1.0-0 -y + - source tools/ci.sh && ci_esp32_idf541_setup + - source tools/ci.sh && ci_esp32_nightly_build + artifacts: + paths: + - m5stack/build-*/uiflow-*-*.bin + - third-party/build-*/uiflow-*-*.bin + tags: + - uiflow-firmware + only: + changes: - "third-party/**" +.def_job_template: &def_job_template + stage: build_board + script: + - sudo apt-get update -qy + - sudo apt-get install git wget flex bison gperf quilt python3 python3-pip python3-venv cmake ninja-build ccache libffi-dev libssl-dev dfu-util libusb-1.0-0 -y + - source tools/ci.sh && ci_esp32_idf541_setup + - source tools/ci.sh && source esp-idf/export.sh && pip install future && make -C m5stack submodules && make -C m5stack patch && make -C m5stack littlefs && make -C m5stack mpy-cross && make -C m5stack BOARD=${BOARD} pack_all + only: + changes: + - "m5stack/${BOARD}/*" + + +boards_jobs: + parallel: + matrix: + - BOARD: [ + "M5STACK_AirQ", + "M5STACK_Atom_Echo", + "M5STACK_Atom_Lite", + "M5STACK_Atom_Matrix", + "M5STACK_AtomS3", + "M5STACK_AtomS3_Lite", + "M5STACK_AtomS3R", + "M5STACK_AtomS3R_CAM", + "M5STACK_AtomS3U", + "M5STACK_AtomU", + "M5STACK_Basic", + "M5STACK_Capsule", + "M5STACK_Cardputer", + "M5STACK_Core2", + "M5STACK_CoreInk", + "M5STACK_CoreS3", + "M5STACK_Dial", + "M5STACK_DinMeter", + "M5STACK_Fire", + "M5STACK_NanoC6", + "M5STACK_Paper", + "M5STACK_PaperS3", + "M5STACK_StamPLC", + "M5STACK_Stamp_PICO", + "M5STACK_StampS3", + "M5STACK_Station", + "M5STACK_StickC", + "M5STACK_StickC_PLUS", + "M5STACK_StickC_PLUS2", + "M5STACK_Tab5", + "M5STACK_Tough" + ] + extends: .def_job_template + + build-job: stage: build script: diff --git a/tools/ci.sh b/tools/ci.sh index e6f8cbd0..41df994a 100644 --- a/tools/ci.sh +++ b/tools/ci.sh @@ -32,7 +32,8 @@ function ci_code_formatting_setup { function ci_code_formatting_run { source .venv/bin/activate - tools/codeformat.py -v + tools/codeformat.py -v -c -f + deactivate } ######################################################################################## @@ -163,7 +164,7 @@ function ci_esp32_idf522_setup { function ci_esp32_idf541_setup { if [ -d esp-idf ]; then echo "esp-idf is already cloned." - if [ "$(git -C esp-idf rev-parse --abbrev-ref HEAD)" == "v5.4.1" ]; then + if [ "$(git -C esp-idf describe --tags)" == "v5.4.1" ]; then echo "esp-idf is on v5.4.1 branch." return 0 else @@ -233,6 +234,66 @@ function ci_esp32_quick_build { make ${MAKEOPTS} -C third-party BOARD=SEEED_STUDIO_XIAO_ESP32S3 pack_all } +function ci_unit_build { + source esp-idf/export.sh + pip install future + make ${MAKEOPTS} -C m5stack unpatch + make ${MAKEOPTS} -C m5stack submodules + make ${MAKEOPTS} -C m5stack patch + make ${MAKEOPTS} -C m5stack littlefs + make ${MAKEOPTS} -C m5stack mpy-cross + make ${MAKEOPTS} -C m5stack BOARD=M5STACK_Basic_4MB pack_all + make ${MAKEOPTS} -C m5stack BOARD=M5STACK_CoreS3 pack_all + make ${MAKEOPTS} -C m5stack BOARD=M5STACK_NanoC6 pack_all + make ${MAKEOPTS} -C m5stack BOARD=M5STACK_Tab5 pack_all + make ${MAKEOPTS} -C third-party BOARD=ESPRESSIF_ESP32_S3_BOX_3 pack_all +} + +function ci_module_build { + source esp-idf/export.sh + pip install future + make ${MAKEOPTS} -C m5stack unpatch + make ${MAKEOPTS} -C m5stack submodules + make ${MAKEOPTS} -C m5stack patch + make ${MAKEOPTS} -C m5stack littlefs + make ${MAKEOPTS} -C m5stack mpy-cross + make ${MAKEOPTS} -C m5stack BOARD=M5STACK_Basic_4MB pack_all + make ${MAKEOPTS} -C m5stack BOARD=M5STACK_Fire pack_all + make ${MAKEOPTS} -C m5stack BOARD=M5STACK_Core2 pack_all + make ${MAKEOPTS} -C m5stack BOARD=M5STACK_Tough pack_all + make ${MAKEOPTS} -C m5stack BOARD=M5STACK_CoreS3 pack_all + make ${MAKEOPTS} -C m5stack BOARD=M5STACK_Tab5 pack_all + make ${MAKEOPTS} -C third-party BOARD=ESPRESSIF_ESP32_S3_BOX_3 pack_all +} + + +function ci_base_build { + source esp-idf/export.sh + pip install future + make ${MAKEOPTS} -C m5stack unpatch + make ${MAKEOPTS} -C m5stack submodules + make ${MAKEOPTS} -C m5stack patch + make ${MAKEOPTS} -C m5stack littlefs + make ${MAKEOPTS} -C m5stack mpy-cross + make ${MAKEOPTS} -C m5stack BOARD=M5STACK_Atom_Lite pack_all + make ${MAKEOPTS} -C m5stack BOARD=M5STACK_AtomS3 pack_all + make ${MAKEOPTS} -C third-party BOARD=ESPRESSIF_ESP32_S3_BOX_3 pack_all +} + +function ci_hat_build { + source esp-idf/export.sh + pip install future + make ${MAKEOPTS} -C m5stack unpatch + make ${MAKEOPTS} -C m5stack submodules + make ${MAKEOPTS} -C m5stack patch + make ${MAKEOPTS} -C m5stack littlefs + make ${MAKEOPTS} -C m5stack mpy-cross + make ${MAKEOPTS} -C m5stack BOARD=M5STACK_StickC pack_all + make ${MAKEOPTS} -C m5stack BOARD=M5STACK_StickC_PLUS2 pack_all + make ${MAKEOPTS} -C third-party BOARD=ESPRESSIF_ESP32_S3_BOX_3 pack_all +} + + function ci_esp32_nightly_build { source esp-idf/export.sh pip install future @@ -271,6 +332,7 @@ function ci_esp32_nightly_build { make ${MAKEOPTS} -C m5stack BOARD=M5STACK_StickC pack_all make ${MAKEOPTS} -C m5stack BOARD=M5STACK_StickC_PLUS pack_all make ${MAKEOPTS} -C m5stack BOARD=M5STACK_StickC_PLUS2 pack_all + make ${MAKEOPTS} -C m5stack BOARD=M5STACK_Tab5 pack_all make ${MAKEOPTS} -C m5stack BOARD=M5STACK_Tough pack_all make ${MAKEOPTS} -C third-party BOARD=ESPRESSIF_ESP32_S3_BOX_3 pack_all make ${MAKEOPTS} -C third-party BOARD=SEEED_STUDIO_XIAO_ESP32S3 pack_all From 4d5b1e547329183b7f677a8a68b8154b696c6877 Mon Sep 17 00:00:00 2001 From: lbuque Date: Wed, 18 Jun 2025 14:25:52 +0800 Subject: [PATCH 132/322] github/workflows: Update Workflow. Signed-off-by: lbuque --- .github/workflows/build-release.yml | 1 + .github/workflows/ports_m5stack.yml | 1 + 2 files changed, 2 insertions(+) diff --git a/.github/workflows/build-release.yml b/.github/workflows/build-release.yml index 94bb49b9..6463c808 100644 --- a/.github/workflows/build-release.yml +++ b/.github/workflows/build-release.yml @@ -64,6 +64,7 @@ jobs: $GITHUB_WORKSPACE/m5stack/build-M5STACK_StickC/uiflow-*-*.bin $GITHUB_WORKSPACE/m5stack/build-M5STACK_StickC_PLUS/uiflow-*-*.bin $GITHUB_WORKSPACE/m5stack/build-M5STACK_StickC_PLUS2/uiflow-*-*.bin + $GITHUB_WORKSPACE/m5stack/build-M5STACK_Tab5/uiflow-*-*.bin $GITHUB_WORKSPACE/m5stack/build-M5STACK_Tough/uiflow-*-*.bin $GITHUB_WORKSPACE/third-party/build-SEEED_STUDIO_XIAO_ESP32S3/uiflow-*-*.bin $GITHUB_WORKSPACE/third-party/build-ESPRESSIF_ESP32_S3_BOX_3/uiflow-*-*.bin diff --git a/.github/workflows/ports_m5stack.yml b/.github/workflows/ports_m5stack.yml index a60acfbf..577d4848 100644 --- a/.github/workflows/ports_m5stack.yml +++ b/.github/workflows/ports_m5stack.yml @@ -90,6 +90,7 @@ jobs: - M5STACK_StickC - M5STACK_StickC_PLUS - M5STACK_StickC_PLUS2 + - M5STACK_Tab5 - M5STACK_Tough max-parallel: 3 steps: From 228514aa21727912c21e5aeb464a54ef695eb490 Mon Sep 17 00:00:00 2001 From: lbuque Date: Thu, 19 Jun 2025 14:28:53 +0800 Subject: [PATCH 133/322] .gitlab-ci.yml: Fix path typo. Signed-off-by: lbuque --- .gitlab-ci.yml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 16882c21..08fd1a03 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,12 +1,12 @@ variables: - DEBIAN_FRONTEND: noninteractive # 设置 DEBIAN_FRONTEND 环境变量 - ESP_IDF_SRC_DIR: $CI_PROJECT_DIR/esp-idf - + DEBIAN_FRONTEND: noninteractive # 设置 DEBIAN_FRONTEND 环境变量 + ESP_IDF_SRC_DIR: $CI_PROJECT_DIR/esp-idf cache: - key: "$CI_COMMIT_REF_NAME-$CI_JOB_NAME" # 缓存键 + key: "$CI_PROJECT_ID-esp-idf-v541" # 缓存键 paths: - $ESP_IDF_SRC_DIR + policy: pull-push stages: - code_format @@ -14,7 +14,7 @@ stages: - build_module - build_base - build_hat - - build_cmodule + - build_cmodules - build_third_party - build_board - docs @@ -133,8 +133,8 @@ build-hat-job: - "m5stack/libs/hat/**" -build-cmodule-job: - stage: build_cmodule +build-cmodules-job: + stage: build_cmodules script: - sudo apt-get update -qy - sudo apt-get install git wget flex bison gperf quilt python3 python3-pip python3-venv cmake ninja-build ccache libffi-dev libssl-dev dfu-util libusb-1.0-0 -y @@ -155,7 +155,7 @@ build-cmodule-job: - "m5stack/boards/Kconfig" - "m5stack/boards/*.py" - "m5stack/boards/sdkconfig.*" - - "m5stack/cmodule/**" + - "m5stack/cmodules/**" - "m5stack/components/**" - "m5stack/fs/**" - "m5stack/include/**" @@ -195,7 +195,7 @@ build-third-party-job: - source tools/ci.sh && source esp-idf/export.sh && pip install future && make -C m5stack submodules && make -C m5stack patch && make -C m5stack littlefs && make -C m5stack mpy-cross && make -C m5stack BOARD=${BOARD} pack_all only: changes: - - "m5stack/${BOARD}/*" + - "m5stack/boards/${BOARD}/**" boards_jobs: From 8c736c0832053f9921f5953571a9a8631d4525ab Mon Sep 17 00:00:00 2001 From: lbuque Date: Fri, 20 Jun 2025 12:00:47 +0800 Subject: [PATCH 134/322] .gitlab-ci.yml: Delete redundant jobs. gitlab-runner's rules cannot be executed as expected. Signed-off-by: lbuque --- .gitlab-ci.yml | 233 ++----------------------------------------------- 1 file changed, 6 insertions(+), 227 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 08fd1a03..6e3e4de9 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,22 +1,16 @@ variables: - DEBIAN_FRONTEND: noninteractive # 设置 DEBIAN_FRONTEND 环境变量 + DEBIAN_FRONTEND: noninteractive ESP_IDF_SRC_DIR: $CI_PROJECT_DIR/esp-idf cache: - key: "$CI_PROJECT_ID-esp-idf-v541" # 缓存键 + key: "$CI_PROJECT_ID-esp-idf-v541" paths: - $ESP_IDF_SRC_DIR policy: pull-push + when: on_success stages: - code_format - - build_uint - - build_module - - build_base - - build_hat - - build_cmodules - - build_third_party - - build_board - docs - build - release @@ -33,215 +27,13 @@ code-format: - git diff --exit-code tags: - uiflow-firmware - only: - changes: - - ".gitlab-ci.yml" - - "m5stack/**" - - "examples/**" - - "tests/**" - - "third-party/**" - - "tools/ci.sh" - - -build-uint-and-common-job: - stage: build_uint - script: - - sudo apt-get update -qy - - sudo apt-get install git wget flex bison gperf quilt python3 python3-pip python3-venv cmake ninja-build ccache libffi-dev libssl-dev dfu-util libusb-1.0-0 -y - - source tools/ci.sh && ci_esp32_idf541_setup - - source tools/ci.sh && ci_unit_build - artifacts: - paths: - - m5stack/build-*/uiflow-*-*.bin - - third-party/build-*/uiflow-*-*.bin - tags: - - uiflow-firmware - only: - changes: - - "m5stack/libs/driver/**" - - "m5stack/libs/unit/**" - - "m5stack/libs/bleuart/**" - - "m5stack/libs/ezdata/**" - - "m5stack/libs/hardware/**" - - "m5stack/libs/image_plus/**" - - "m5stack/libs/m5espnow/**" - - "m5stack/libs/modbus/**" - - "m5stack/libs/requests2/**" - - "m5stack/libs/umqtt/**" - - "m5stack/libs/usb/**" - - "m5stack/libs/utility/**" - - "m5stack/libs/*.py" - - -build-module-job: - stage: build_module - script: - - sudo apt-get update -qy - - sudo apt-get install git wget flex bison gperf quilt python3 python3-pip python3-venv cmake ninja-build ccache libffi-dev libssl-dev dfu-util libusb-1.0-0 -y - - source tools/ci.sh && ci_esp32_idf541_setup - - source tools/ci.sh && ci_module_build - artifacts: - paths: - - m5stack/build-*/uiflow-*-*.bin - - third-party/build-*/uiflow-*-*.bin - tags: - - uiflow-firmware - only: - changes: - - "m5stack/libs/driver/**" - - "m5stack/libs/unit/**" - - "m5stack/libs/module/**" - - -build-base-job: - stage: build_base - script: - - sudo apt-get update -qy - - sudo apt-get install git wget flex bison gperf quilt python3 python3-pip python3-venv cmake ninja-build ccache libffi-dev libssl-dev dfu-util libusb-1.0-0 -y - - source tools/ci.sh && ci_esp32_idf541_setup - - source tools/ci.sh && ci_base_build - artifacts: - paths: - - m5stack/build-*/uiflow-*-*.bin - - third-party/build-*/uiflow-*-*.bin - tags: - - uiflow-firmware - only: - changes: - - "m5stack/libs/driver/**" - - "m5stack/libs/unit/**" - - "m5stack/libs/base/**" - - -build-hat-job: - stage: build_hat - script: - - sudo apt-get update -qy - - sudo apt-get install git wget flex bison gperf quilt python3 python3-pip python3-venv cmake ninja-build ccache libffi-dev libssl-dev dfu-util libusb-1.0-0 -y - - source tools/ci.sh && ci_esp32_idf541_setup - - source tools/ci.sh && ci_hat_build - artifacts: - paths: - - m5stack/build-*/uiflow-*-*.bin - - third-party/build-*/uiflow-*-*.bin - tags: - - uiflow-firmware - only: - changes: - - "m5stack/libs/driver/**" - - "m5stack/libs/unit/**" - - "m5stack/libs/hat/**" - - -build-cmodules-job: - stage: build_cmodules - script: - - sudo apt-get update -qy - - sudo apt-get install git wget flex bison gperf quilt python3 python3-pip python3-venv cmake ninja-build ccache libffi-dev libssl-dev dfu-util libusb-1.0-0 -y - - source tools/ci.sh && ci_esp32_idf541_setup - - source tools/ci.sh && ci_esp32_nightly_build - artifacts: - paths: - - m5stack/build-*/uiflow-*-*.bin - - third-party/build-*/uiflow-*-*.bin - tags: - - uiflow-firmware - only: - changes: - - ".gitlab-ci.yml" - - "esp-adf/**" - - "m5stack/boards/include/**" - - "m5stack/boards/*.txt" - - "m5stack/boards/Kconfig" - - "m5stack/boards/*.py" - - "m5stack/boards/sdkconfig.*" - - "m5stack/cmodules/**" - - "m5stack/components/**" - - "m5stack/fs/**" - - "m5stack/include/**" - - "m5stack/main/**" - - "m5stack/*.c" - - "m5stack/*.cpp" - - "m5stack/*.h" - - "m5stack/*.cmake" - - "m5stack/*.csv" - - "m5stack/*.txt" - - "tools/**" - -build-third-party-job: - stage: build_third_party - script: - - sudo apt-get update -qy - - sudo apt-get install git wget flex bison gperf quilt python3 python3-pip python3-venv cmake ninja-build ccache libffi-dev libssl-dev dfu-util libusb-1.0-0 -y - - source tools/ci.sh && ci_esp32_idf541_setup - - source tools/ci.sh && ci_esp32_nightly_build - artifacts: - paths: - - m5stack/build-*/uiflow-*-*.bin - - third-party/build-*/uiflow-*-*.bin - tags: - - uiflow-firmware - only: - changes: - - "third-party/**" - - -.def_job_template: &def_job_template - stage: build_board - script: - - sudo apt-get update -qy - - sudo apt-get install git wget flex bison gperf quilt python3 python3-pip python3-venv cmake ninja-build ccache libffi-dev libssl-dev dfu-util libusb-1.0-0 -y - - source tools/ci.sh && ci_esp32_idf541_setup - - source tools/ci.sh && source esp-idf/export.sh && pip install future && make -C m5stack submodules && make -C m5stack patch && make -C m5stack littlefs && make -C m5stack mpy-cross && make -C m5stack BOARD=${BOARD} pack_all - only: - changes: - - "m5stack/boards/${BOARD}/**" - - -boards_jobs: - parallel: - matrix: - - BOARD: [ - "M5STACK_AirQ", - "M5STACK_Atom_Echo", - "M5STACK_Atom_Lite", - "M5STACK_Atom_Matrix", - "M5STACK_AtomS3", - "M5STACK_AtomS3_Lite", - "M5STACK_AtomS3R", - "M5STACK_AtomS3R_CAM", - "M5STACK_AtomS3U", - "M5STACK_AtomU", - "M5STACK_Basic", - "M5STACK_Capsule", - "M5STACK_Cardputer", - "M5STACK_Core2", - "M5STACK_CoreInk", - "M5STACK_CoreS3", - "M5STACK_Dial", - "M5STACK_DinMeter", - "M5STACK_Fire", - "M5STACK_NanoC6", - "M5STACK_Paper", - "M5STACK_PaperS3", - "M5STACK_StamPLC", - "M5STACK_Stamp_PICO", - "M5STACK_StampS3", - "M5STACK_Station", - "M5STACK_StickC", - "M5STACK_StickC_PLUS", - "M5STACK_StickC_PLUS2", - "M5STACK_Tab5", - "M5STACK_Tough" - ] - extends: .def_job_template build-job: stage: build script: - sudo apt-get update -qy - - sudo apt-get install git wget flex bison gperf python3 python3-pip python3-venv cmake ninja-build ccache libffi-dev libssl-dev dfu-util libusb-1.0-0 -y + - sudo apt-get install git wget flex bison gperf quilt python3 python3-pip python3-venv cmake ninja-build ccache libffi-dev libssl-dev dfu-util libusb-1.0-0 -y - source tools/ci.sh && ci_esp32_idf541_setup - source tools/ci.sh && ci_esp32_nightly_build artifacts: @@ -250,12 +42,6 @@ build-job: - third-party/build-*/uiflow-*-*.bin tags: - uiflow-firmware - only: - refs: - - tags - variables: - - $CI_COMMIT_TAG =~ /^release\/[0-9]+\.[0-9]+\.[0-9]+$/ - - $CI_COMMIT_REF_SLUG == "develop_m5things" build-docs: @@ -274,9 +60,6 @@ build-docs: - docs/build/ tags: - uiflow-firmware - only: - changes: - - "docs/**" release_job: @@ -284,11 +67,7 @@ release_job: script: - echo "Releasing the M5Burn..." - python ./tools/release.py - only: - refs: - - tags - variables: - - $CI_COMMIT_TAG =~ /^release\/[0-9]+\.[0-9]+\.[0-9]+$/ - - $CI_COMMIT_REF_SLUG == "develop_m5things" tags: - uiflow-firmware + rules: + - if: '$CI_COMMIT_TAG =~ /^release\/[0-9]+\.[0-9]+\.[0-9]+$/ && $CI_COMMIT_REF_SLUG == "develop_m5things"' From c285cd5fe68d1f50a8980984bfcbc58e9b1dc4e2 Mon Sep 17 00:00:00 2001 From: tinyu Date: Wed, 25 Jun 2025 18:37:49 +0800 Subject: [PATCH 135/322] partitions_8mb_lvgl.csv: Modify the partition table. Signed-off-by: tinyu --- m5stack/partitions_8mb_lvgl.csv | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/m5stack/partitions_8mb_lvgl.csv b/m5stack/partitions_8mb_lvgl.csv index b1197054..4fc4905d 100644 --- a/m5stack/partitions_8mb_lvgl.csv +++ b/m5stack/partitions_8mb_lvgl.csv @@ -2,6 +2,6 @@ # Note: if you change the phy_init or app partition offset, make sure to change the offset in Kconfig.projbuild nvs, data, nvs, 0x9000, 0x6000, phy_init, data, phy, 0xf000, 0x1000, -factory, app, factory, 0x10000, 0x5B0000, -sys, data, fat, 0x5C0000, 0x100000, -vfs, data, fat, 0x6C0000, 0x13f000, +factory, app, factory, 0x10000, 0x5C0000, +sys, data, fat, 0x5D0000, 0x100000, +vfs, data, fat, 0x6D0000, 0x12f000, From 67154bcef912f779e7ac4f4fecf0333789a34cf8 Mon Sep 17 00:00:00 2001 From: tinyu Date: Tue, 17 Jun 2025 16:49:26 +0800 Subject: [PATCH 136/322] lib/unit: Add Unit Audioplayer support. Signed-off-by: tinyu --- docs/en/refs/unit.audioplayer.ref | 35 + docs/en/units/audioplayer.rst | 60 ++ docs/en/units/index.rst | 1 + .../audioplayer/audioplayer_core2_example.py | 68 ++ m5stack/libs/unit/__init__.py | 1 + m5stack/libs/unit/audioplayer.py | 679 ++++++++++++++++++ m5stack/libs/unit/manifest.py | 1 + 7 files changed, 845 insertions(+) create mode 100644 docs/en/refs/unit.audioplayer.ref create mode 100644 docs/en/units/audioplayer.rst create mode 100644 examples/unit/audioplayer/audioplayer_core2_example.py create mode 100644 m5stack/libs/unit/audioplayer.py diff --git a/docs/en/refs/unit.audioplayer.ref b/docs/en/refs/unit.audioplayer.ref new file mode 100644 index 00000000..244ef1f3 --- /dev/null +++ b/docs/en/refs/unit.audioplayer.ref @@ -0,0 +1,35 @@ +.. |AudioPlayer| image:: https://static-cdn.m5stack.com/resource/docs/products/unit/audioplayer_01.webp + :target: https://docs.m5stack.com/en/unit/Unit_AudioPlayer + :height: 200px + :width: 200px + +.. |init.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/audioplayer/init.png + +.. |check_play_status.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/audioplayer/check_play_status.png +.. |get_total_audio_number.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/audioplayer/get_total_audio_number.png +.. |get_current_audio_number.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/audioplayer/get_current_audio_number.png +.. |get_play_mode.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/audioplayer/get_play_mode.png +.. |play_audio.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/audioplayer/play_audio.png +.. |pause_audio.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/audioplayer/pause_audio.png +.. |stop_audio.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/audioplayer/stop_audio.png +.. |next_audio.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/audioplayer/next_audio.png +.. |previous_audio.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/audioplayer/previous_audio.png +.. |play_audio_by_index.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/audioplayer/play_audio_by_index.png +.. |select_audio_num.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/audioplayer/select_audio_num.png +.. |set_play_mode.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/audioplayer/set_play_mode.png +.. |get_volume.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/audioplayer/get_volume.png +.. |increase_volume.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/audioplayer/increase_volume.png +.. |decrease_volume.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/audioplayer/decrease_volume.png +.. |set_volume.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/audioplayer/set_volume.png + +.. |example.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/audioplayer/example.png + + +.. |audioplayer_core2_example.m5f2| raw:: html + + + audioplayer_core2_example.m5f2 + \ No newline at end of file diff --git a/docs/en/units/audioplayer.rst b/docs/en/units/audioplayer.rst new file mode 100644 index 00000000..541db630 --- /dev/null +++ b/docs/en/units/audioplayer.rst @@ -0,0 +1,60 @@ +AudioPlayer Unit +================== + +.. sku: U197 + +.. include:: ../refs/unit.audioplayer.ref + +This is the driver library of AudioPlayer Unit, which is used to play audio files. + +Support the following products: + + |AudioPlayer| + + +UiFlow2 Example +--------------- + +play audio +^^^^^^^^^^^^^^^ + +Open the |audioplayer_core2_example.m5f2| project in UiFlow2. + +This example plays the audio file on the AudioPlayer Unit. + +UiFlow2 Code Block: + + |example.png| + +Example output: + + None + +MicroPython Example +------------------- + +play audio +^^^^^^^^^^^^^^^ + +This example plays the audio file on the AudioPlayer Unit. + +MicroPython Code Block: + + .. literalinclude:: ../../../examples/unit/audioplayer/audioplayer_core2_example.py + :language: python + :linenos: + +Example output: + + None + + +**API** +------- + +AudioPlayerUnit +^^^^^^^^^^^^^^^ + +.. autoclass:: unit.audioplayer.AudioPlayerUnit + :members: + :member-order: bysource \ No newline at end of file diff --git a/docs/en/units/index.rst b/docs/en/units/index.rst index 787693ef..52ba37ca 100644 --- a/docs/en/units/index.rst +++ b/docs/en/units/index.rst @@ -13,6 +13,7 @@ Unit angle.rst angle8.rst asr.rst + audioplayer.rst bldc_driver.rst bps.rst button.rst diff --git a/examples/unit/audioplayer/audioplayer_core2_example.py b/examples/unit/audioplayer/audioplayer_core2_example.py new file mode 100644 index 00000000..7927ef9e --- /dev/null +++ b/examples/unit/audioplayer/audioplayer_core2_example.py @@ -0,0 +1,68 @@ +import os, sys, io +import M5 +from M5 import * +from unit import AudioPlayerUnit +import time + + +title0 = None +label0 = None +label1 = None +label2 = None +audioplayer_0 = None + + +play_state = None + + +def btn_b_was_pressed_event(state): + global title0, label0, label1, label2, audioplayer_0, play_state + if play_state: + audioplayer_0.pause_audio() + else: + audioplayer_0.play_audio() + + +def setup(): + global title0, label0, label1, label2, audioplayer_0, play_state + + M5.begin() + Widgets.fillScreen(0x222222) + title0 = Widgets.Title( + "AudioPlayerUnit Core2 Example", 3, 0xFFFFFF, 0x0000FF, Widgets.FONTS.DejaVu18 + ) + label0 = Widgets.Label(">||", 145, 214, 1.0, 0xFFFFFF, 0x222222, Widgets.FONTS.DejaVu18) + label1 = Widgets.Label("label1", 1, 71, 1.0, 0xFFFFFF, 0x222222, Widgets.FONTS.DejaVu18) + label2 = Widgets.Label("label2", 1, 123, 1.0, 0xFFFFFF, 0x222222, Widgets.FONTS.DejaVu18) + + BtnB.setCallback(type=BtnB.CB_TYPE.WAS_PRESSED, cb=btn_b_was_pressed_event) + + audioplayer_0 = AudioPlayerUnit(2, port=(33, 32)) + audioplayer_0.set_play_mode(0) + play_state = 0 + + +def loop(): + global title0, label0, label1, label2, audioplayer_0, play_state + M5.update() + play_state = audioplayer_0.check_play_status() + if play_state: + label1.setText(str("Play Status: Playing")) + else: + label1.setText(str("Play Status: Paused")) + label2.setText(str((str("Audio Num: ") + str((audioplayer_0.get_current_audio_number()))))) + time.sleep_ms(100) + + +if __name__ == "__main__": + try: + setup() + while True: + loop() + except (Exception, KeyboardInterrupt) as e: + try: + from utility import print_error_msg + + print_error_msg(e) + except ImportError: + print("please update to latest firmware") diff --git a/m5stack/libs/unit/__init__.py b/m5stack/libs/unit/__init__.py index 15aac283..672ce52f 100644 --- a/m5stack/libs/unit/__init__.py +++ b/m5stack/libs/unit/__init__.py @@ -14,6 +14,7 @@ "AngleUnit": "angle", "Angle8Unit": "angle8", "ASRUnit": "asr", + "AudioPlayerUnit": "audioplayer", "BLDCDriverUnit": "bldc_driver", "BPSUnit": "bps", "ButtonUnit": "button", diff --git a/m5stack/libs/unit/audioplayer.py b/m5stack/libs/unit/audioplayer.py new file mode 100644 index 00000000..b7b9ddbf --- /dev/null +++ b/m5stack/libs/unit/audioplayer.py @@ -0,0 +1,679 @@ +# SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT +from machine import UART +import sys +import time + +if sys.platform != "esp32": + from typing import Literal + + +class AudioPlayerUnit: + def __init__(self, id: Literal[0, 1, 2] = 1, port: list | tuple = None, verbose=False): + """Create an AudioPlayerUnit object. + + :param int id: The UART ID of the device. Default is 1. + :param list | tuple port: The UART port of the device. + :param bool verbose: The verbose mode of the device. Default is False. + + UiFlow2 Code Block: + + |init.png| + + MicroPython Code Block: + + .. code-block:: python + + from unit import AudioPlayerUnit + + audio_player_0 = AudioPlayerUnit(0, port=(33, 32)) + """ + + self.uart = UART(id, tx=port[1], rx=port[0]) + self.uart.init(9600, bits=8, parity=None, stop=1) + self.uart.irq(handler=self._handler, trigger=UART.IRQ_RXIDLE) + self.uart.read() + self.verbose = verbose + self.raw_message = "" + self.command_num = 0 + self.is_recieved = False + self.received_data = [False] + self.play_status = None + + def _handler(self, uart) -> None: + data = uart.read() + if data is not None and len(data) > 5: + self.verbose and print( + "Received message:", " ".join(f"0x{byte:02X}" for byte in data).split() + ) + # self.verbose and print(data[0] == self.command) + # self.verbose and print(data[1] == (~self.command) & 0xFF) + # if self.retun_value is not None: + # self.verbose and print(data[2:4] == bytes(self.retun_value)) + # else: + # self.verbose and print("True") + # self.verbose and print( + # data[-1] + # == ( + # self.command + ~self.command + # & 0xFF + # + sum(data[4:-1]) + # + (0 if self.retun_value is None else sum(self.retun_value)) + # ) + # & 0xFF + # ) + # self.verbose and print( + # ( + # hex( + # self.command + # + (~self.command & 0xFF) + # + sum(data[4:-1]) + # + (0 if self.retun_value is None else sum(self.retun_value)) + # & 0xFF + # ) + # ) + # ) + if (data[0] == 0x0A and self.command == 0x05) or ( + data[0] == self.command and data[1] == (~self.command & 0xFF) + ): + if (self.retun_value is None or data[2:4] == bytes(self.retun_value)) and data[ + -1 + ] == ( + self.command + ~self.command + & 0xFF + + sum(data[4:-1]) + + (0 if self.retun_value is None else sum(self.retun_value)) + ) & 0xFF: + self.is_recieved = True + self.raw_message = " ".join(f"0x{byte:02X}" for byte in data) + self.received_data = data[4:-1] + self.verbose and print( + ( + "Parsed Data:", + " ".join(f"0x{byte:02X}" for byte in self.received_data).split(), + ) + ) + + # self.check_tick_callback() + else: + self.verbose and print("Invalid frame received: header/footer mismatch") + uart.read() + + def _wait_for_message(self, time_out: int = 500): + self.is_recieved = False + self.received_data = [False] + start_time = time.ticks_ms() + while not self.is_recieved: + if time.ticks_ms() - start_time > time_out: + if self.verbose: + print(f"Message timeout after {time_out}ms") + return [-1] * 11 + time.sleep_ms(10) + return self.received_data + + def _send_message(self, command: int, data: list[int], retun_value=None) -> None: + message = [command, (~command) & 0xFF, len(data)] + data + sum = 0 + for i in message: + sum += i + + sum = sum & 0xFF + # print(f"Sum:0x{sum:02X}") + message.append(sum & 0xFF) + if len(message) > 32: + raise ValueError("Message length is too long, max length is 32") + self.command = command + self.retun_value = retun_value + self.verbose and print("--> ", " ".join(f"0x{byte:02X}" for byte in message)) + self.uart.write(bytes(message)) + + def check_play_status(self): + """Check the play status of the audio player. + + :returns: The play status of the audio player. + :rtype: bool + + UiFlow2 Code Block: + + |check_play_status.png| + + MicroPython Code Block: + + .. code-block:: python + + audio_player_0.check_play_status() + """ + self._send_message(0x04, [0x00], [0x02, 0x00]) + return self._wait_for_message(500)[0] + + def play_audio(self) -> int: # 可从暂停处开始播放 + """Play the audio. + + :returns: The play status of the audio player. + :rtype: int + + UiFlow2 Code Block: + + |play_audio.png| + + MicroPython Code Block: + + .. code-block:: python + + audio_player_0.play_audio() + """ + self._send_message(0x04, [0x01], [0x02, 0x00]) + return self._wait_for_message(500)[0] + + def pause_audio(self) -> int: # 暂停播放 + """Pause the audio. + + :returns: The play status of the audio player. + :rtype: bool + + UiFlow2 Code Block: + + |pause_audio.png| + + MicroPython Code Block: + + .. code-block:: python + + audio_player_0.pause_audio() + """ + self._send_message(0x04, [0x02], [0x02, 0x00]) + return self._wait_for_message(500)[0] + + def stop_audio(self) -> int: # 停止播放(从头开始) + """Stop the audio. + + :returns: The play status of the audio player. + :rtype: int + + UiFlow2 Code Block: + + |stop_audio.png| + + MicroPython Code Block: + + .. code-block:: python + + audio_player_0.stop_audio() + """ + self._send_message(0x04, [0x03], [0x02, 0x00]) + return self._wait_for_message(500)[0] + + def next_audio(self) -> int: + """Play the next audio. + + :returns: Current play audio index. + :rtype: int + + UiFlow2 Code Block: + + |next_audio.png| + + MicroPython Code Block: + + .. code-block:: python + + audio_player_0.next_audio() + """ + self._send_message(0x04, [0x05], [0x03, 0x0E]) + buf = self._wait_for_message(600)[0:2] + return buf[0] & 0xFF << 8 | buf[1] + + def previous_audio(self) -> int: + """Play the previous audio. + + :returns: Current play audio index. + :rtype: int + + UiFlow2 Code Block: + + |previous_audio.png| + + MicroPython Code Block: + + .. code-block:: python + + audio_player_0.previous_audio() + """ + self._send_message(0x04, [0x04], [0x03, 0x0E]) + buf = self._wait_for_message(600)[0:2] + return buf[0] & 0xFF << 8 | buf[1] + + def play_audio_by_index(self, index: int) -> int: + """Play audio by index number. + + :param int index: The index of the audio to play. + :returns: Current play audio index. + :rtype: int + + UiFlow2 Code Block: + + |play_audio_by_index.png| + + MicroPython Code Block: + + .. code-block:: python + + audio_player_0.play_audio_by_index(1) + """ + index_high = (index >> 8) & 0xFF + index_low = index & 0xFF + self._send_message(0x04, [0x06, index_high, index_low], [0x03, 0x0E]) + buf = self._wait_for_message(500)[0:2] + return buf[0] & 0xFF << 8 | buf[1] + + def play_audio_by_name(self, name: str) -> int: + """Play audio by file name. + + :param str name: The name of the audio file to play. + :returns: Current play audio index. + :rtype: int + + MicroPython Code Block: + + .. code-block:: python + + audio_player_0.play_audio_by_name("music.mp3") + """ + hex_values = list(bytearray(name, "utf-8")) + if len(hex_values) <= 27: + self._send_message(0x04, [0x07] + hex_values, [0x03, 0x0E]) + return self._wait_for_message(500)[0] + raise ValueError("Name length is too long, max length is 27") + + def get_current_online_device_type(self) -> int: + """Get the current online device type. + + :returns: Device type code + :rtype: int + + Device type: + - 1: USB + - 2: SD + - 3: UDISK or SD + - 4: Flash + - 5: Flash or UDISK + - 6: Flash or SD + + MicroPython Code Block: + + .. code-block:: python + + audio_player_0.get_current_online_device_type() + """ + self._send_message(0x04, [0x08], [0x02, 0x08]) + return self._wait_for_message(500)[0] + + def get_current_play_device_type(self) -> int: + """Get the current play device type. + + :returns: Device type code (0: USB, 1: SD, 2: SPI FLASH). + :rtype: int + + MicroPython Code Block: + + .. code-block:: python + + audio_player_0.get_current_play_device_type() + """ + self._send_message(0x04, [0x09], [0x02, 0x09]) + return self._wait_for_message(500)[0] + + def get_total_audio_number(self) -> int: + """Get the total number of audio files available. + + :returns: The total number of audio files. + :rtype: int + + UiFlow2 Code Block: + + |get_total_audio_number.png| + + MicroPython Code Block: + + .. code-block:: python + + audio_player_0.get_total_audio_number() + """ + self._send_message(0x04, [0x0D], [0x03, 0x0D]) + buf = self._wait_for_message(500)[0:2] + return buf[0] & 0xFF << 8 | buf[1] + + def get_current_audio_number(self) -> int: + """Get the current audio file number. + + :returns: The current audio file number. + :rtype: int + + UiFlow2 Code Block: + + |get_current_audio_number.png| + + MicroPython Code Block: + + .. code-block:: python + + audio_player_0.get_current_audio_number() + """ + self._send_message(0x04, [0x0E], [0x03, 0x0E]) + buf = self._wait_for_message(500)[0:2] + return buf[0] & 0xFF << 8 | buf[1] + + def play_current_audio_at_time(self, time_min: int, time_sec: int) -> None: + """Play the current audio from a specific time position. + + :param int time_min: The minute position to start playing from. + :param int time_sec: The second position to start playing from. + + MicroPython Code Block: + + .. code-block:: python + + audio_player_0.play_current_audio_at_time(1, 30) + """ + self._send_message(0x04, [0x0F, time_min, time_sec]) + time.sleep_ms(100) + + def play_audio_at_time(self, audio_index: int, time_min: int, time_sec: int) -> None: + """Play a specific audio file from a specific time position. + + :param int audio_index: The index of the audio file to play. + :param int time_min: The minute position to start playing from. + :param int time_sec: The second position to start playing from. + + MicroPython Code Block: + + .. code-block:: python + + audio_player_0.play_audio_at_time(1, 0, 30) + """ + self._send_message( + 0x04, [0x10, (audio_index >> 8) & 0xFF, audio_index & 0xFF, time_min, time_sec] + ) + time.sleep_ms(100) + + def next_directory(self) -> None: + """Navigate to the next directory. + + MicroPython Code Block: + + .. code-block:: python + + audio_player_0.next_directory() + """ + self._send_message(0x04, [0x13], [0x03, 0x0E]) + time.sleep_ms(100) + + def previous_directory(self) -> None: + """Navigate to the previous directory. + + MicroPython Code Block: + + .. code-block:: python + + audio_player_0.previous_directory() + """ + self._send_message(0x04, [0x12], [0x03, 0x0E]) + time.sleep_ms(100) + + def end_audio(self) -> None: + """End playing the current audio. + + MicroPython Code Block: + + .. code-block:: python + + audio_player_0.end_audio() + """ + self._send_message(0x04, [0x14]) + time.sleep_ms(100) + + def get_file_name(self) -> list: + """Get the name of the current audio file. + + :returns: The name of the current audio file as a list of bytes. + :rtype: list + + MicroPython Code Block: + + .. code-block:: python + + audio_player_0.get_file_name() + """ + self._send_message(0x04, [0x15], [0x0C, 0x15]) + return self._wait_for_message(500)[0:11] + + def select_audio_num(self, audio_num: int) -> int: + """Select an audio file by number without playing it. + + :param int audio_num: The number of the audio file to select. + :returns: The current selected audio file number. + :rtype: int + + UiFlow2 Code Block: + + |select_audio_num.png| + + MicroPython Code Block: + + .. code-block:: python + + audio_player_0.select_audio_num(1) + """ + self._send_message(0x04, [0x16, (audio_num >> 8) & 0xFF, audio_num & 0xFF], [0x03, 0x0E]) + buf = self._wait_for_message(600)[0:2] + return buf[0] & 0xFF << 8 | buf[1] + + def get_file_count(self) -> int: + """Get the total number of files in the current directory. + + :returns: The total number of files. + :rtype: int + + MicroPython Code Block: + + .. code-block:: python + + audio_player_0.get_file_count() + """ + self._send_message(0x04, [0x18], [0x03, 0x18]) + buf = self._wait_for_message(500)[0:2] + return buf[0] & 0xFF << 8 | buf[1] + + def get_total_play_time(self) -> tuple: + """Get the total play time of the current audio file. + + :returns: A tuple containing (hour, minute, second) of the total play time. + :rtype: tuple + + MicroPython Code Block: + + .. code-block:: python + + audio_player_0.get_total_play_time() + """ + self._send_message(0x05, [0x00], [0x04, 0x00]) + buf = self._wait_for_message(500)[0:3] + return (buf[0], buf[1], buf[2]) + + def decrease_volume(self) -> None: + """Decrease the volume of the audio player. + + UiFlow2 Code Block: + + |decrease_volume.png| + + MicroPython Code Block: + + .. code-block:: python + + audio_player_0.decrease_volume() + """ + self._send_message(0x06, [0x03]) + time.sleep_ms(100) + + def increase_volume(self) -> None: + """Increase the volume of the audio player. + + UiFlow2 Code Block: + + |increase_volume.png| + + MicroPython Code Block: + + .. code-block:: python + + audio_player_0.increase_volume() + """ + self._send_message(0x06, [0x02]) + time.sleep_ms(100) + + def get_volume(self) -> int: + """Get the current volume level of the audio player. + + :returns: The current volume level. + :rtype: int + + UiFlow2 Code Block: + + |get_volume.png| + + MicroPython Code Block: + + .. code-block:: python + + audio_player_0.get_volume() + """ + self._send_message(0x06, [0x00], [0x02, 0x00]) + return self._wait_for_message(600)[0] + + def set_volume(self, volume: int) -> None: + """Set the volume level of the audio player. + + :param int volume: The volume level to set (0-30). + + UiFlow2 Code Block: + + |set_volume.png| + + MicroPython Code Block: + + .. code-block:: python + + audio_player_0.set_volume(15) + """ + self._send_message(0x06, [0x01, volume]) + time.sleep_ms(100) + + def repeat_at_time(self, start_min: int, start_sec: int, end_min: int, end_sec: int) -> None: + """Set repeat playback between two time positions. + + :param int start_min: The start minute position. + :param int start_sec: The start second position. + :param int end_min: The end minute position. + :param int end_sec: The end second position. + + MicroPython Code Block: + + .. code-block:: python + + audio_player_0.repeat_at_time(0, 30, 1, 30) + """ + self._send_message(0x08, [0x00, start_min, start_sec, end_min, end_sec]) + time.sleep_ms(100) + + def end_repeat(self) -> None: + """End the repeat playback mode. + + MicroPython Code Block: + + .. code-block:: python + + audio_player_0.end_repeat() + """ + self._send_message(0x08, [0x01]) + time.sleep_ms(100) + + def get_play_mode(self) -> int: + """Get the current play mode. + + :returns: The current play mode. + :rtype: int + + UiFlow2 Code Block: + + |get_play_mode.png| + + MicroPython Code Block: + + .. code-block:: python + + audio_player_0.get_play_mode() + """ + self._send_message(0x0B, [0x00], [0x02, 0x00]) + return self._wait_for_message(500)[0] + + def set_play_mode(self, mode: int) -> None: + """Set the play mode. + + :param int mode: The play mode to set. + + UiFlow2 Code Block: + + |set_play_mode.png| + + MicroPython Code Block: + + .. code-block:: python + + audio_player_0.set_play_mode(1) + """ + self._send_message(0x0B, [0x01, mode]) + time.sleep_ms(100) + + def start_combine_play(self, mode: int, data: list[int]) -> None: + """Start combined play mode. + + :param int mode: The combined play mode. + :param list[int] data: The data for combined play. + + MicroPython Code Block: + + .. code-block:: python + + audio_player_0.start_combine_play(1, [1, 2, 3]) + """ + self._send_message(0x0C, [mode] + data) + time.sleep_ms(100) + + def end_combine_play(self) -> None: + """End the combined play mode. + + MicroPython Code Block: + + .. code-block:: python + + audio_player_0.end_combine_play() + """ + self._send_message(0x0C, [0x02]) + time.sleep_ms(100) + + def into_sleep_mode(self) -> bool: + """Put the audio player into sleep mode. + + :returns: True if the command was sent successfully. + :rtype: bool + + MicroPython Code Block: + + .. code-block:: python + + audio_player_0.into_sleep_mode() + """ + self.uart.write(bytes([0x0D, 0xF3, 0x01, 0x01, 0x02])) + return True diff --git a/m5stack/libs/unit/manifest.py b/m5stack/libs/unit/manifest.py index 7a4d27e3..1bc08eba 100644 --- a/m5stack/libs/unit/manifest.py +++ b/m5stack/libs/unit/manifest.py @@ -15,6 +15,7 @@ "angle.py", "angle8.py", "asr.py", + "audioplayer.py", "bldc_driver.py", "bps.py", "button.py", From c5a540910769bda2b46ac38c7c603f80e2eaf6e2 Mon Sep 17 00:00:00 2001 From: tinyu Date: Mon, 23 Jun 2025 14:46:32 +0800 Subject: [PATCH 137/322] docs: Add AudioPlayer Unit zh_CN docs. Signed-off-by: tinyu --- docs/en/refs/unit.audioplayer.ref | 2 +- docs/en/units/audioplayer.rst | 3 +- .../zh_CN/LC_MESSAGES/units/audioplayer.po | 768 ++++++++++++++++++ .../audioplayer/audioplayer_core2_example.py | 4 + m5stack/libs/unit/audioplayer.py | 33 +- 5 files changed, 791 insertions(+), 19 deletions(-) create mode 100644 docs/locales/zh_CN/LC_MESSAGES/units/audioplayer.po diff --git a/docs/en/refs/unit.audioplayer.ref b/docs/en/refs/unit.audioplayer.ref index 244ef1f3..c64885fe 100644 --- a/docs/en/refs/unit.audioplayer.ref +++ b/docs/en/refs/unit.audioplayer.ref @@ -1,4 +1,4 @@ -.. |AudioPlayer| image:: https://static-cdn.m5stack.com/resource/docs/products/unit/audioplayer_01.webp +.. |AudioPlayer| image:: https://m5stack-doc.oss-cn-shenzhen.aliyuncs.com/1155/U197-01.webp :target: https://docs.m5stack.com/en/unit/Unit_AudioPlayer :height: 200px :width: 200px diff --git a/docs/en/units/audioplayer.rst b/docs/en/units/audioplayer.rst index 541db630..23d781ef 100644 --- a/docs/en/units/audioplayer.rst +++ b/docs/en/units/audioplayer.rst @@ -1,7 +1,7 @@ AudioPlayer Unit ================== -.. sku: U197 +.. sku:U197 .. include:: ../refs/unit.audioplayer.ref @@ -11,7 +11,6 @@ Support the following products: |AudioPlayer| - UiFlow2 Example --------------- diff --git a/docs/locales/zh_CN/LC_MESSAGES/units/audioplayer.po b/docs/locales/zh_CN/LC_MESSAGES/units/audioplayer.po new file mode 100644 index 00000000..7e40cc76 --- /dev/null +++ b/docs/locales/zh_CN/LC_MESSAGES/units/audioplayer.po @@ -0,0 +1,768 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2016 - 2025 M5Stack Technology Co., Ltd +# This file is distributed under the same license as the UIFlow2 Programming +# Guide package. +# FIRST AUTHOR , 2025. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: UIFlow2 Programming Guide \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-06-19 17:50+0800\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: zh_CN\n" +"Language-Team: zh_CN \n" +"Plural-Forms: nplurals=1; plural=0;\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.16.0\n" + +#: ../../en/units/audioplayer.rst:2 a26db49711f34d50a6d29a8608333010 +msgid "AudioPlayer Unit" +msgstr "" + +#: ../../en/units/audioplayer.rst:8 cbbc256d7e3c43a9862e628a71f8a816 +msgid "" +"This is the driver library of AudioPlayer Unit, which is used to play " +"audio files." +msgstr "该驱动库用于控制AudioPlayer Unit播放音频文件。" + +#: ../../en/units/audioplayer.rst:10 32387a7dde734bb8a9ff6f1199be39ca +msgid "Support the following products:" +msgstr "支持以下产品:" + +#: ../../en/units/audioplayer.rst:12 b26b62d96f944c6386a90470b716d857 +msgid "|AudioPlayer|" +msgstr "" + +#: ../../en/refs/unit.audioplayer.ref 329f759bb65a4e358284ba99b8a6399c +msgid "AudioPlayer" +msgstr "" + +#: ../../en/units/audioplayer.rst:16 e5c6a8eefce24e8291f54e47dcc0a27e +msgid "UiFlow2 Example" +msgstr "UiFlow2 示例" + +#: ../../en/units/audioplayer.rst:19 ../../en/units/audioplayer.rst:37 +#: 9205b5431c7a4e4d87399b0ff0f29176 d4630a0fce364fbcb44d0ddb50df271b +msgid "play audio" +msgstr "播放音频" + +#: ../../en/units/audioplayer.rst:21 1d27dee1858941da8b38a16e47b38cbe +msgid "Open the |audioplayer_core2_example.m5f2| project in UiFlow2." +msgstr "在 UiFlow2 中打开 |audioplayer_core2_example.m5f2| 项目。" + +#: ../../en/units/audioplayer.rst:23 ../../en/units/audioplayer.rst:39 +#: e2d8d5dd3a65401c9b1e7b7fd8f6d273 f1968c2385de4298a38e2e25efa998b7 +msgid "This example plays the audio file on the AudioPlayer Unit." +msgstr "这个示例展示了使用 AudioPlayer Unit 播放音频文件。" + +#: ../../en/units/audioplayer.rst:25 0211030675134dc5ae8d449e20ad92c8 +#: 092e255bb3a84786acad4904e87e854a 18deccd4698d4c50836405b5f43f590d +#: 343fd050f0b24feeacc44f62c76288d1 4200b5d298e940e6bf115acae8789437 +#: 50f5b98d006049fea138f6b53fc07883 53f0e82c82424f28868fe6d6a19d47d0 +#: 72babb1387d24566a7c2a617f118bf48 849507d02ad4452fa20bab1ba9d6047a +#: a6218996b37d448391d24f4cf0afff04 bae86bb21d3f4f60bc162a48f5ca0d9d of +#: unit.audioplayer.AudioPlayerUnit:8 +#: unit.audioplayer.AudioPlayerUnit.check_play_status:6 +#: unit.audioplayer.AudioPlayerUnit.decrease_volume:3 +#: unit.audioplayer.AudioPlayerUnit.get_current_audio_number:6 +#: unit.audioplayer.AudioPlayerUnit.get_play_mode:6 +#: unit.audioplayer.AudioPlayerUnit.get_total_audio_number:6 +#: unit.audioplayer.AudioPlayerUnit.get_volume:6 +#: unit.audioplayer.AudioPlayerUnit.increase_volume:3 +#: unit.audioplayer.AudioPlayerUnit.next_audio:6 +#: unit.audioplayer.AudioPlayerUnit.pause_audio:6 +#: unit.audioplayer.AudioPlayerUnit.play_audio:6 +#: unit.audioplayer.AudioPlayerUnit.play_audio_by_index:7 +#: unit.audioplayer.AudioPlayerUnit.previous_audio:6 +#: unit.audioplayer.AudioPlayerUnit.select_audio_num:7 +#: unit.audioplayer.AudioPlayerUnit.set_play_mode:5 +#: unit.audioplayer.AudioPlayerUnit.set_volume:5 +#: unit.audioplayer.AudioPlayerUnit.stop_audio:6 +msgid "UiFlow2 Code Block:" +msgstr "UiFlow2 代码块:" + +#: ../../en/units/audioplayer.rst:27 14e121cc88d047e9bf321b968ac8e453 +msgid "|example.png|" +msgstr "" + +#: ../../en/refs/unit.audioplayer.ref:25 a341c64d42c0417ba9342101e7f1591b +msgid "example.png" +msgstr "" + +#: ../../en/units/audioplayer.rst:29 ../../en/units/audioplayer.rst:47 +#: 01f775e4ff7e43d6b1da4286be487db1 d8012660afaf4646b54d11d49b54bd2c +msgid "Example output:" +msgstr "示例输出:" + +#: ../../en/units/audioplayer.rst:31 ../../en/units/audioplayer.rst:49 +#: 98e75398d2b440cca1615920793ead22 cbe2167752ab40e89c147c1c2f6dda2a +msgid "None" +msgstr "无" + +#: ../../en/units/audioplayer.rst:34 f773c007ab1447f7bf89458885c3a05e +msgid "MicroPython Example" +msgstr "MicroPython 示例" + +#: ../../en/units/audioplayer.rst:41 11071e46178d439aa9b62c7c0a20b3b7 +#: 214cb8058d284dbb9016bff47ce0470c 2e65e3a3ae1c4586a77953470d39d3b1 +#: 4cbd40fc915b4ba0a5e50d7a8e49c1f2 52d1ac57838d4b2890e87775a5828d8c +#: 5580917dad9647eea7f91140b0ea9abf 77661596eba644c8bb9a1378de89c700 +#: 77e0048f899541c0b62514a3728fd63e 851f08d4981d4a71b889eeea83883a6f +#: 9fc425905c394ae4b3b6e4a231c9514f a1faee5e89c840de8f443f6aa04563f0 +#: af0f1e1acf494522b18acf80f11970d5 c4b6eeae9d734dbc88416401f10b07f5 +#: d189571f13a64c62966fdf474b8f637d d5bc2f9dd8db4df68ad484f57422711a +#: f03c3c651e304e1e93032a4778499f0b of unit.audioplayer.AudioPlayerUnit:12 +#: unit.audioplayer.AudioPlayerUnit.check_play_status:10 +#: unit.audioplayer.AudioPlayerUnit.decrease_volume:7 +#: unit.audioplayer.AudioPlayerUnit.end_audio:3 +#: unit.audioplayer.AudioPlayerUnit.end_combine_play:3 +#: unit.audioplayer.AudioPlayerUnit.end_repeat:3 +#: unit.audioplayer.AudioPlayerUnit.get_current_audio_number:10 +#: unit.audioplayer.AudioPlayerUnit.get_current_online_device_type:14 +#: unit.audioplayer.AudioPlayerUnit.get_current_play_device_type:6 +#: unit.audioplayer.AudioPlayerUnit.get_file_count:6 +#: unit.audioplayer.AudioPlayerUnit.get_file_name:6 +#: unit.audioplayer.AudioPlayerUnit.get_play_mode:10 +#: unit.audioplayer.AudioPlayerUnit.get_total_audio_number:10 +#: unit.audioplayer.AudioPlayerUnit.get_total_play_time:6 +#: unit.audioplayer.AudioPlayerUnit.get_volume:10 +#: unit.audioplayer.AudioPlayerUnit.increase_volume:7 +#: unit.audioplayer.AudioPlayerUnit.into_sleep_mode:6 +#: unit.audioplayer.AudioPlayerUnit.next_audio:10 +#: unit.audioplayer.AudioPlayerUnit.next_directory:3 +#: unit.audioplayer.AudioPlayerUnit.pause_audio:10 +#: unit.audioplayer.AudioPlayerUnit.play_audio:10 +#: unit.audioplayer.AudioPlayerUnit.play_audio_at_time:7 +#: unit.audioplayer.AudioPlayerUnit.play_audio_by_index:11 +#: unit.audioplayer.AudioPlayerUnit.play_audio_by_name:7 +#: unit.audioplayer.AudioPlayerUnit.play_current_audio_at_time:6 +#: unit.audioplayer.AudioPlayerUnit.previous_audio:10 +#: unit.audioplayer.AudioPlayerUnit.previous_directory:3 +#: unit.audioplayer.AudioPlayerUnit.repeat_at_time:8 +#: unit.audioplayer.AudioPlayerUnit.select_audio_num:11 +#: unit.audioplayer.AudioPlayerUnit.set_play_mode:9 +#: unit.audioplayer.AudioPlayerUnit.set_volume:9 +#: unit.audioplayer.AudioPlayerUnit.start_combine_play:6 +#: unit.audioplayer.AudioPlayerUnit.stop_audio:10 +msgid "MicroPython Code Block:" +msgstr "MicroPython 代码块:" + +#: ../../en/units/audioplayer.rst:53 9d0259c5de8c4105bffe0f32038ddf6d +msgid "**API**" +msgstr "" + +#: ../../en/units/audioplayer.rst:56 7e925058540446d7bd197f23218316b4 +msgid "AudioPlayerUnit" +msgstr "" + +#: 442b88a620604d49bf8f5d5035392911 of unit.audioplayer.AudioPlayerUnit:1 +msgid "Bases: :py:class:`object`" +msgstr "" + +#: 09b395d0f5b245858ab67fbf0bed5318 of unit.audioplayer.AudioPlayerUnit:1 +msgid "Create an AudioPlayerUnit object." +msgstr "" + +#: ../../en/units/audioplayer.rst 18968de0684f497bb2346b00b698bd65 +#: 3869fbd9f3384599bb3a2de5ddf5c540 4893e35b382b4ec1a5e64b87e5285f39 of +#: unit.audioplayer.AudioPlayerUnit.play_audio_at_time +#: unit.audioplayer.AudioPlayerUnit.play_audio_by_index +#: unit.audioplayer.AudioPlayerUnit.play_audio_by_name +#: unit.audioplayer.AudioPlayerUnit.play_current_audio_at_time +#: unit.audioplayer.AudioPlayerUnit.repeat_at_time +#: unit.audioplayer.AudioPlayerUnit.select_audio_num +#: unit.audioplayer.AudioPlayerUnit.set_play_mode +#: unit.audioplayer.AudioPlayerUnit.set_volume +#: unit.audioplayer.AudioPlayerUnit.start_combine_play +msgid "Parameters" +msgstr "参数" + +#: 3e24e405473a494b82d1f855ddc7794a of unit.audioplayer.AudioPlayerUnit:3 +msgid "The UART ID of the device. Default is 2." +msgstr "设备 UART ID。默认是 2。" + +#: 2dfdf795109a46e19b621e1cad5a48a7 of unit.audioplayer.AudioPlayerUnit:4 +msgid "The UART port of the device." +msgstr "获取当前在线设备类型。" + +#: 79f6686860e44bb68b0e2cf1edbf7e5d of unit.audioplayer.AudioPlayerUnit:6 +msgid "The verbose mode of the device. Default is False." +msgstr "Debug模式开关。默认关闭。" + +#: 12a37fa0f90c4266959aa6e33f2a7d0c of unit.audioplayer.AudioPlayerUnit:10 +msgid "|init.png|" +msgstr "" + +#: ../../en/refs/unit.audioplayer.ref:6 b8a311ea2c524aa2aad5635b0faa9284 +msgid "init.png" +msgstr "" + +#: d49861a52f684798966fdfcd8060a092 of +#: unit.audioplayer.AudioPlayerUnit.check_play_status:1 +msgid "Check the play status of the audio player." +msgstr "检查 AudioPlayer 的播放状态。" + +#: 25f1f20915d741be9e62248536c5a306 2ae2a63022f04f649c909d644ecef964 +#: 4433cd31d2d94053b6f429aa3c6dba76 51901386cb70482fb400777a12cb31b8 +#: 9c2fb21364b0401f80f205a7cf25847f 9ec3b6f97ff545e38ec46480270e0241 +#: 9f4bdc0dc684491fa82c00b883979cc9 fccb7e56d62b45b2bccb546a325ed515 of +#: unit.audioplayer.AudioPlayerUnit.check_play_status +#: unit.audioplayer.AudioPlayerUnit.get_current_audio_number +#: unit.audioplayer.AudioPlayerUnit.get_current_online_device_type +#: unit.audioplayer.AudioPlayerUnit.get_current_play_device_type +#: unit.audioplayer.AudioPlayerUnit.get_file_count +#: unit.audioplayer.AudioPlayerUnit.get_file_name +#: unit.audioplayer.AudioPlayerUnit.get_play_mode +#: unit.audioplayer.AudioPlayerUnit.get_total_audio_number +#: unit.audioplayer.AudioPlayerUnit.get_total_play_time +#: unit.audioplayer.AudioPlayerUnit.get_volume +#: unit.audioplayer.AudioPlayerUnit.into_sleep_mode +#: unit.audioplayer.AudioPlayerUnit.next_audio +#: unit.audioplayer.AudioPlayerUnit.pause_audio +#: unit.audioplayer.AudioPlayerUnit.play_audio +#: unit.audioplayer.AudioPlayerUnit.play_audio_by_index +#: unit.audioplayer.AudioPlayerUnit.play_audio_by_name +#: unit.audioplayer.AudioPlayerUnit.previous_audio +#: unit.audioplayer.AudioPlayerUnit.select_audio_num +#: unit.audioplayer.AudioPlayerUnit.stop_audio +msgid "Returns" +msgstr "返回值" + +#: 2637e29f0e284e9290739d50ffc3c530 2dfdf795109a46e19b621e1cad5a48a7 +#: 492b140c29b444f9bc93479b448237ba 61ffe9ff20d3476cb054454e8ab8afb5 of +#: unit.audioplayer.AudioPlayerUnit.check_play_status:3 +#: unit.audioplayer.AudioPlayerUnit.pause_audio:3 +#: unit.audioplayer.AudioPlayerUnit.play_audio:3 +#: unit.audioplayer.AudioPlayerUnit.stop_audio:3 +msgid "The play status of the audio player." +msgstr "AudioPlayer 的播放状态。" + +#: 02b074de2ffe473fa1f2c290b7febca7 1716874eca27450dadae351f68db5084 +#: 35b051b3084c4ccba49b3d6c01a998fe 55508fbf20ca4e86abb44b4c398e0325 +#: 6ddd4b19bed846d88fcb6628cf6cbd3a 9b23c6adf5b248d9824692d2a25dec4e +#: 9bf5115732b341a29f580259c54397c8 af90960e5b0f42a092e1759322c67a05 +#: c331b9db7aad41169adedcc84a8f8ac4 d042b84309ec465b9a8c28d8e3ee4143 +#: d416b746fc884ff4a65120fa586970ed eae0d2f390434987b8923092b8b38490 of +#: unit.audioplayer.AudioPlayerUnit.check_play_status +#: unit.audioplayer.AudioPlayerUnit.decrease_volume +#: unit.audioplayer.AudioPlayerUnit.end_audio +#: unit.audioplayer.AudioPlayerUnit.end_combine_play +#: unit.audioplayer.AudioPlayerUnit.end_repeat +#: unit.audioplayer.AudioPlayerUnit.get_current_audio_number +#: unit.audioplayer.AudioPlayerUnit.get_current_online_device_type +#: unit.audioplayer.AudioPlayerUnit.get_current_play_device_type +#: unit.audioplayer.AudioPlayerUnit.get_file_count +#: unit.audioplayer.AudioPlayerUnit.get_file_name +#: unit.audioplayer.AudioPlayerUnit.get_play_mode +#: unit.audioplayer.AudioPlayerUnit.get_total_audio_number +#: unit.audioplayer.AudioPlayerUnit.get_total_play_time +#: unit.audioplayer.AudioPlayerUnit.get_volume +#: unit.audioplayer.AudioPlayerUnit.increase_volume +#: unit.audioplayer.AudioPlayerUnit.into_sleep_mode +#: unit.audioplayer.AudioPlayerUnit.next_audio +#: unit.audioplayer.AudioPlayerUnit.next_directory +#: unit.audioplayer.AudioPlayerUnit.pause_audio +#: unit.audioplayer.AudioPlayerUnit.play_audio +#: unit.audioplayer.AudioPlayerUnit.play_audio_at_time +#: unit.audioplayer.AudioPlayerUnit.play_audio_by_index +#: unit.audioplayer.AudioPlayerUnit.play_audio_by_name +#: unit.audioplayer.AudioPlayerUnit.play_current_audio_at_time +#: unit.audioplayer.AudioPlayerUnit.previous_audio +#: unit.audioplayer.AudioPlayerUnit.previous_directory +#: unit.audioplayer.AudioPlayerUnit.repeat_at_time +#: unit.audioplayer.AudioPlayerUnit.select_audio_num +#: unit.audioplayer.AudioPlayerUnit.set_play_mode +#: unit.audioplayer.AudioPlayerUnit.set_volume +#: unit.audioplayer.AudioPlayerUnit.start_combine_play +#: unit.audioplayer.AudioPlayerUnit.stop_audio +msgid "Return type" +msgstr "返回类型" + +#: 12fa051af585407e9c2f38dfff153a24 of +#: unit.audioplayer.AudioPlayerUnit.check_play_status:8 +msgid "|check_play_status.png|" +msgstr "" + +#: ../../en/refs/unit.audioplayer.ref:8 a651755eb3c647318d97d0be436b201b +msgid "check_play_status.png" +msgstr "" + +#: b2adb9e449a540b588cd4f4ecb48cddd of +#: unit.audioplayer.AudioPlayerUnit.play_audio:1 +msgid "Play the audio." +msgstr "播放音频。" + +#: 12a37fa0f90c4266959aa6e33f2a7d0c of +#: unit.audioplayer.AudioPlayerUnit.play_audio:8 +msgid "|play_audio.png|" +msgstr "" + +#: ../../en/refs/unit.audioplayer.ref:12 b8a311ea2c524aa2aad5635b0faa9284 +msgid "play_audio.png" +msgstr "" + +#: 02b1b835f3c244c9b210fd9aa541e8b6 of +#: unit.audioplayer.AudioPlayerUnit.pause_audio:1 +msgid "Pause the audio." +msgstr "暂停音频。" + +#: 7b4e7e15c0e0459584bb9b2b8fdb62d5 of +#: unit.audioplayer.AudioPlayerUnit.pause_audio:8 +msgid "|pause_audio.png|" +msgstr "" + +#: ../../en/refs/unit.audioplayer.ref:13 b7944a63d3184de185960f5ec4ff180a +msgid "pause_audio.png" +msgstr "" + +#: 7b67531093e24918ae8bf13a5ae7f136 of +#: unit.audioplayer.AudioPlayerUnit.stop_audio:1 +msgid "Stop the audio." +msgstr "停止音频。" + +#: 832b8176291c43acaf5fcd50d5b259e8 of +#: unit.audioplayer.AudioPlayerUnit.stop_audio:8 +msgid "|stop_audio.png|" +msgstr "" + +#: ../../en/refs/unit.audioplayer.ref:14 dddcc4fb5da444c5b4275329b8ec9efb +msgid "stop_audio.png" +msgstr "" + +#: 7ae26a231f31426c95055ddfbd68feaa of +#: unit.audioplayer.AudioPlayerUnit.next_audio:1 +msgid "Play the next audio." +msgstr "播放下一个音频。" + +#: a4d0ead2c8804e9d9871e7c4ecd5272f aa13114cebb44eb69da29d926d62577f of +#: unit.audioplayer.AudioPlayerUnit.next_audio:3 +#: unit.audioplayer.AudioPlayerUnit.play_audio_by_index:4 +#: unit.audioplayer.AudioPlayerUnit.play_audio_by_name:4 +#: unit.audioplayer.AudioPlayerUnit.previous_audio:3 +msgid "Current play audio index." +msgstr "当前播放音频的索引。" + +#: e12b2f3d5d484fd9a70a739ffb1f3d0c of +#: unit.audioplayer.AudioPlayerUnit.next_audio:8 +msgid "|next_audio.png|" +msgstr "" + +#: ../../en/refs/unit.audioplayer.ref:15 49d49dac879742bea5ddf468fc7bf821 +msgid "next_audio.png" +msgstr "" + +#: 8a63f94b5ba241ad8692f518f3d0e24a of +#: unit.audioplayer.AudioPlayerUnit.previous_audio:1 +msgid "Play the previous audio." +msgstr "播放上一个音频。" + +#: 303f3dafcc2545a1b6a64a0518d76697 of +#: unit.audioplayer.AudioPlayerUnit.previous_audio:8 +msgid "|previous_audio.png|" +msgstr "" + +#: ../../en/refs/unit.audioplayer.ref:16 e5069bf830eb4bbd8503c34f98c97d6a +msgid "previous_audio.png" +msgstr "" + +#: 83d3ecc984854216ab6dcc9e47ac5eef of +#: unit.audioplayer.AudioPlayerUnit.play_audio_by_index:1 +msgid "Play audio by index number." +msgstr "按索引播放音频。" + +#: bb41be9022554e2db86817ff1c70c8c8 of +#: unit.audioplayer.AudioPlayerUnit.play_audio_by_index:3 +msgid "The index of the audio to play." +msgstr "要播放的音频的索引。" + +#: 79c89da52e2b43ef9f975f94416e6352 of +#: unit.audioplayer.AudioPlayerUnit.play_audio_by_index:9 +msgid "|play_audio_by_index.png|" +msgstr "" + +#: ../../en/refs/unit.audioplayer.ref:17 5ff53cf1db6f4120b1648019ffd62cf0 +msgid "play_audio_by_index.png" +msgstr "" + +#: 9a2e459318e54edbb57ce1e23fec3365 of +#: unit.audioplayer.AudioPlayerUnit.play_audio_by_name:1 +msgid "Play audio by file name." +msgstr "按文件名播放音频。" + +#: cfae957eafbb4e6da20315b8e6b3c493 of +#: unit.audioplayer.AudioPlayerUnit.play_audio_by_name:3 +msgid "The name of the audio file to play." +msgstr "要播放的音频文件的名称。" + +#: 87a67011d91e445d8a3adebec021e9f4 of +#: unit.audioplayer.AudioPlayerUnit.get_current_online_device_type:1 +msgid "Get the current online device type." +msgstr "获取当前在线设备类型。" + +#: b78b17c6cbe2412397b2bd285da19aff of +#: unit.audioplayer.AudioPlayerUnit.get_current_online_device_type:3 +msgid "Device type code" +msgstr "设备类型代码" + +#: ff99d1bf74fb437fbf198467ea198034 of +#: unit.audioplayer.AudioPlayerUnit.get_current_online_device_type:4 +msgid "" +"int Device type: - 1: USB - 2: SD - 3: UDISK or SD - 4: " +"Flash - 5: Flash or UDISK - 6: Flash or SD" +msgstr "" +"int \n 设备类型: - 1: USB - 2: SD - 3: UDISK 或 SD - 4: +"Flash \n - 5: Flash 或 UDISK - 6: Flash 或 SD" + +#: 7cc0a4179eac4713a1a9e440acd34680 of +#: unit.audioplayer.AudioPlayerUnit.get_current_online_device_type:4 +msgid "int" +msgstr "" + +#: 537d28612ca24856b692fb9a432e5cb5 of +#: unit.audioplayer.AudioPlayerUnit.get_current_online_device_type:12 +msgid "Device type:" +msgstr "设备类型:" + +#: bb943c916f494ff4a37c0c6b03317f4e of +#: unit.audioplayer.AudioPlayerUnit.get_current_online_device_type:7 +msgid "1: USB" +msgstr "" + +#: 49d8919dd93048c8a5a6abce6e9638d6 of +#: unit.audioplayer.AudioPlayerUnit.get_current_online_device_type:8 +msgid "2: SD" +msgstr "" + +#: 49f8a8206fc644dbb72ccf1630bdb96d of +#: unit.audioplayer.AudioPlayerUnit.get_current_online_device_type:9 +msgid "3: UDISK or SD" +msgstr "3: U盘 或 SD卡" + +#: a30e911e4235431d9b64fd7a89fee6a0 of +#: unit.audioplayer.AudioPlayerUnit.get_current_online_device_type:10 +msgid "4: Flash" +msgstr "" + +#: ea7f145995df47ba8722f417ebb603b1 of +#: unit.audioplayer.AudioPlayerUnit.get_current_online_device_type:11 +msgid "5: Flash or UDISK" +msgstr "5: Flash 或 U盘" + +#: f1a16c46baac4c608dc973a0aa5e99af of +#: unit.audioplayer.AudioPlayerUnit.get_current_online_device_type:12 +msgid "6: Flash or SD" +msgstr "6: Flash 或 SD卡" + +#: 7dc43e010aa24bdbac5a9fac8e4838c1 of +#: unit.audioplayer.AudioPlayerUnit.get_current_play_device_type:1 +msgid "Get the current play device type." +msgstr "获取当前播放设备类型。" + +#: 4daf6114780d43e789f26dfe7d5e1d27 of +#: unit.audioplayer.AudioPlayerUnit.get_current_play_device_type:3 +msgid "Device type code (0: USB, 1: SD, 2: SPI FLASH)." +msgstr "设备类型代码 (0: USB, 1: SD, 2: SPI FLASH)." + +#: a74b53c43d6d4be6b7093afb5f315e97 of +#: unit.audioplayer.AudioPlayerUnit.get_total_audio_number:1 +msgid "Get the total number of audio files available." +msgstr "获取可用的音频文件总数。" + +#: c47d2cc5e20a4e098546b036c3836f1a of +#: unit.audioplayer.AudioPlayerUnit.get_total_audio_number:3 +msgid "The total number of audio files." +msgstr "音频文件总数。" + +#: 035903b21270412388dd233748bca7cb of +#: unit.audioplayer.AudioPlayerUnit.get_total_audio_number:8 +msgid "|get_total_audio_number.png|" +msgstr "" + +#: ../../en/refs/unit.audioplayer.ref:9 03792bc004914d8a9cdabb95157a04f2 +msgid "get_total_audio_number.png" +msgstr "" + +#: be3ce3d14b8544cf9a9bd1890bffb813 of +#: unit.audioplayer.AudioPlayerUnit.get_current_audio_number:1 +msgid "Get the current audio file number." +msgstr "获取当前音频文件编号。" + +#: 79dbf1f8f252468abb1932750bb96c37 of +#: unit.audioplayer.AudioPlayerUnit.get_current_audio_number:3 +msgid "The current audio file number." +msgstr "当前音频文件编号。" + +#: da55c90bd06b43a9aa76566b386ef0f6 of +#: unit.audioplayer.AudioPlayerUnit.get_current_audio_number:8 +msgid "|get_current_audio_number.png|" +msgstr "" + +#: ../../en/refs/unit.audioplayer.ref:10 0609876df2e846e98d2ebb14ba70b9c2 +msgid "get_current_audio_number.png" +msgstr "" + +#: 700ee685df974d9bb2390995e3591f61 of +#: unit.audioplayer.AudioPlayerUnit.play_current_audio_at_time:1 +msgid "Play the current audio from a specific time position." +msgstr "从特定时间位置播放当前音频。" + +#: 178ac5eaa6aa46bdb84379ed94881efe 86c1e2563ac449788864333a3e04c0bd of +#: unit.audioplayer.AudioPlayerUnit.play_audio_at_time:4 +#: unit.audioplayer.AudioPlayerUnit.play_current_audio_at_time:3 +msgid "The minute position to start playing from." +msgstr "开始播放的分钟位置。" + +#: 36bca64455fa42ecadabbdf2c93ed72e 5eabed4cad6149b4a1ba75fbca58104a of +#: unit.audioplayer.AudioPlayerUnit.play_audio_at_time:5 +#: unit.audioplayer.AudioPlayerUnit.play_current_audio_at_time:4 +msgid "The second position to start playing from." +msgstr "开始播放的秒位置。" + +#: 70d88c91ba6c4f1c82fa8b8884d15d37 of +#: unit.audioplayer.AudioPlayerUnit.play_audio_at_time:1 +msgid "Play a specific audio file from a specific time position." +msgstr "从特定时间位置播放特定音频文件。" + +#: d32143d0e1d54a3292bf2172d4b81b4a of +#: unit.audioplayer.AudioPlayerUnit.play_audio_at_time:3 +msgid "The index of the audio file to play." +msgstr "要播放的音频文件的索引。" + +#: c9d6012629c945a19d616b360b65c0e1 of +#: unit.audioplayer.AudioPlayerUnit.next_directory:1 +msgid "Navigate to the next directory." +msgstr "导航到下一个目录。" + +#: 65c7b81acd98430694a5aff2b7f7e7b8 of +#: unit.audioplayer.AudioPlayerUnit.previous_directory:1 +msgid "Navigate to the previous directory." +msgstr "导航到上一个目录。" + +#: 774fc636f90045c8b2ab5ef284a31ec1 of +#: unit.audioplayer.AudioPlayerUnit.end_audio:1 +msgid "End playing the current audio." +msgstr "结束播放当前音频。" + +#: 5fe5364ca6574b25a9970a97a7c13ecd of +#: unit.audioplayer.AudioPlayerUnit.get_file_name:1 +msgid "Get the name of the current audio file." +msgstr "获取当前音频文件的名称。" + +#: 69479d451aa94ebfa13d283fa808f6ad of +#: unit.audioplayer.AudioPlayerUnit.get_file_name:3 +msgid "The name of the current audio file as a list of bytes." +msgstr "当前音频文件的名称作为字节列表。" + +#: f7d9343d87204295a11ea1f972a12611 of +#: unit.audioplayer.AudioPlayerUnit.select_audio_num:1 +msgid "Select an audio file by number without playing it." +msgstr "按编号选择一个音频文件但不播放该音频。" + +#: 9b3e8151a2bf4192b0b99b63c9590b2a of +#: unit.audioplayer.AudioPlayerUnit.select_audio_num:3 +msgid "The number of the audio file to select." +msgstr "要选择的音频文件的编号。" + +#: 66bde3a10e164f7cb2313c95e9716754 of +#: unit.audioplayer.AudioPlayerUnit.select_audio_num:4 +msgid "The current selected audio file number." +msgstr "当前选中的音频文件编号。" + +#: 43b8c929cfcc447599689f41f6e8a03c of +#: unit.audioplayer.AudioPlayerUnit.select_audio_num:9 +msgid "|select_audio_num.png|" +msgstr "" + +#: ../../en/refs/unit.audioplayer.ref:18 50f55bc2e3b940eba9a1ba3df0c1510d +msgid "select_audio_num.png" +msgstr "" + +#: 4965d6d8a27247289f7a0327e3d59494 of +#: unit.audioplayer.AudioPlayerUnit.get_file_count:1 +msgid "Get the total number of files in the current directory." +msgstr "获取当前目录中的文件总数。" + +#: da2f27d3d21d42fb881e842cd6c5e8b5 of +#: unit.audioplayer.AudioPlayerUnit.get_file_count:3 +msgid "The total number of files." +msgstr "文件总数。" + +#: 95647c91790a4b3bada000226ee9c2ee of +#: unit.audioplayer.AudioPlayerUnit.get_total_play_time:1 +msgid "Get the total play time of the current audio file." +msgstr "获取当前音频文件的总播放时间。" + +#: 5c2592f0e2ed4564a3ac471153f21b04 of +#: unit.audioplayer.AudioPlayerUnit.get_total_play_time:3 +msgid "A tuple containing (hour, minute, second) of the total play time." +msgstr "包含 (小时, 分钟, 秒) 的元组,表示总播放时间。" + +#: 49c0d81baace4c369b3944af5314a6da of +#: unit.audioplayer.AudioPlayerUnit.decrease_volume:1 +msgid "Decrease the volume of the audio player." +msgstr "降低音频播放器的音量。" + +#: f8e15e3ca57c4e84a2a58978c5fd8e30 of +#: unit.audioplayer.AudioPlayerUnit.decrease_volume:5 +msgid "|decrease_volume.png|" +msgstr "" + +#: ../../en/refs/unit.audioplayer.ref:22 0bfb89df2b4045da88a8c3d638529ad4 +msgid "decrease_volume.png" +msgstr "" + +#: b683b697c3544e07b51802412246e69a of +#: unit.audioplayer.AudioPlayerUnit.increase_volume:1 +msgid "Increase the volume of the audio player." +msgstr "增加AudioPlayer Unit的音量。" + +#: af961b291443440ebd604adb8290c4c1 of +#: unit.audioplayer.AudioPlayerUnit.increase_volume:5 +msgid "|increase_volume.png|" +msgstr "" + +#: ../../en/refs/unit.audioplayer.ref:21 d0c45ed0dfbe4dc5916b10801b597347 +msgid "increase_volume.png" +msgstr "" + +#: ef15c03578ac463da0d0b337a0ac6936 of +#: unit.audioplayer.AudioPlayerUnit.get_volume:1 +msgid "Get the current volume level of the audio player." +msgstr "获取AudioPlayer Unit的当前音量级别。" + +#: d6cf17193fc84e4ea5ab269f763962fa of +#: unit.audioplayer.AudioPlayerUnit.get_volume:3 +msgid "The current volume level." +msgstr "当前音量级别。" + +#: 1259d1d7d2534e2a89627e22ab35cf99 of +#: unit.audioplayer.AudioPlayerUnit.get_volume:8 +msgid "|get_volume.png|" +msgstr "" + +#: ../../en/refs/unit.audioplayer.ref:20 3b8e8b77d72e4933a6b8f6fdb25466f0 +msgid "get_volume.png" +msgstr "" + +#: dc59ac9a0c9b46a785267a80d18e7665 of +#: unit.audioplayer.AudioPlayerUnit.set_volume:1 +msgid "Set the volume level of the audio player." +msgstr "设置AudioPlayer Unit的音量级别。" + +#: b5d70024764b453db8544b3591d913b1 of +#: unit.audioplayer.AudioPlayerUnit.set_volume:3 +msgid "The volume level to set (0-30)." +msgstr "要设置的音量级别 (0-30)。" + +#: ba0baa8cebb141b6832cda5c039af6d3 of +#: unit.audioplayer.AudioPlayerUnit.set_volume:7 +msgid "|set_volume.png|" +msgstr "" + +#: ../../en/refs/unit.audioplayer.ref:23 b48619350e2848e6b7eaf3c5f6a70473 +msgid "set_volume.png" +msgstr "" + +#: 14b199813a424f528f495d98e2fb99de of +#: unit.audioplayer.AudioPlayerUnit.repeat_at_time:1 +msgid "Set repeat playback between two time positions." +msgstr "设置在两个时间位置之间重复播放。" + +#: 0ceb6e88c3164db7a40cc74c34b7a42e of +#: unit.audioplayer.AudioPlayerUnit.repeat_at_time:3 +msgid "The start minute position." +msgstr "开始播放的分钟位置。" + +#: 31d35dd6134040fea1503044f54466e5 of +#: unit.audioplayer.AudioPlayerUnit.repeat_at_time:4 +msgid "The start second position." +msgstr "开始播放的秒位置。" + +#: 4d36c9ef4d594ada98fcc0b6c0427df1 of +#: unit.audioplayer.AudioPlayerUnit.repeat_at_time:5 +msgid "The end minute position." +msgstr "结束播放的分钟位置。" + +#: 5105da71972f4312a1828128b090dc59 of +#: unit.audioplayer.AudioPlayerUnit.repeat_at_time:6 +msgid "The end second position." +msgstr "结束播放的秒位置。" + +#: 14957dd782bd445fbafb52bcf96bc396 of +#: unit.audioplayer.AudioPlayerUnit.end_repeat:1 +msgid "End the repeat playback mode." +msgstr "结束重复播放模式。" + +#: 18e0c81f269840f4be802b9d75e72bdb of +#: unit.audioplayer.AudioPlayerUnit.get_play_mode:1 +msgid "Get the current play mode." +msgstr "获取当前播放模式。" + +#: b6cd442097324f4bb2cc3f0055bdd4eb of +#: unit.audioplayer.AudioPlayerUnit.get_play_mode:3 +msgid "The current play mode." +msgstr "当前播放模式。" + +#: b20af508de6649dbb8480bb32d7be985 of +#: unit.audioplayer.AudioPlayerUnit.get_play_mode:8 +msgid "|get_play_mode.png|" +msgstr "" + +#: ../../en/refs/unit.audioplayer.ref:11 7f7e7d4d1007494d92d0d6457d547151 +msgid "get_play_mode.png" +msgstr "" + +#: 8d7db2cc425f437a8cb9d0933dc3fa3d of +#: unit.audioplayer.AudioPlayerUnit.set_play_mode:1 +msgid "Set the play mode." +msgstr "设置播放模式。" + +#: c9b10dac3cec4a41bc8fb4548f37f3e8 of +#: unit.audioplayer.AudioPlayerUnit.set_play_mode:3 +msgid "The play mode to set." +msgstr "要设置的播放模式。" + +#: bab6a283d02d4204adf1bac026a7a60f of +#: unit.audioplayer.AudioPlayerUnit.set_play_mode:7 +msgid "|set_play_mode.png|" +msgstr "" + +#: ../../en/refs/unit.audioplayer.ref:19 cfca8eb5688e412eb01798464567c381 +msgid "set_play_mode.png" +msgstr "" + +#: dd6bbe68521943ab8be78fa8e1b5019a of +#: unit.audioplayer.AudioPlayerUnit.start_combine_play:1 +msgid "Start combined play mode." +msgstr "开始组合播放模式。" + +#: 0589659445474640a31b4dfc3942f532 of +#: unit.audioplayer.AudioPlayerUnit.start_combine_play:3 +msgid "The combined play mode." +msgstr "组合播放模式。" + +#: 16557a08132244888955142a689464dd of +#: unit.audioplayer.AudioPlayerUnit.start_combine_play:4 +msgid "The data for combined play." +msgstr "组合播放的数据。" + +#: 8549665ad72145469e05810b2faeb337 of +#: unit.audioplayer.AudioPlayerUnit.end_combine_play:1 +msgid "End the combined play mode." +msgstr "结束组合播放模式。" + +#: 2df9fd727b2c4fc288e8fa38f75879bf of +#: unit.audioplayer.AudioPlayerUnit.into_sleep_mode:1 +msgid "Put the audio player into sleep mode." +msgstr "将AudioPlayer Unit 置于睡眠模式。" + +#: 8a318924a37043feae62fcf23cdde5c1 of +#: unit.audioplayer.AudioPlayerUnit.into_sleep_mode:3 +msgid "True if the command was sent successfully." +msgstr "如果命令发送成功,则为 True。" + diff --git a/examples/unit/audioplayer/audioplayer_core2_example.py b/examples/unit/audioplayer/audioplayer_core2_example.py index 7927ef9e..9c5bf467 100644 --- a/examples/unit/audioplayer/audioplayer_core2_example.py +++ b/examples/unit/audioplayer/audioplayer_core2_example.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + import os, sys, io import M5 from M5 import * diff --git a/m5stack/libs/unit/audioplayer.py b/m5stack/libs/unit/audioplayer.py index b7b9ddbf..e323a072 100644 --- a/m5stack/libs/unit/audioplayer.py +++ b/m5stack/libs/unit/audioplayer.py @@ -1,38 +1,39 @@ -# SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD # # SPDX-License-Identifier: MIT -from machine import UART import sys import time +import machine if sys.platform != "esp32": from typing import Literal class AudioPlayerUnit: - def __init__(self, id: Literal[0, 1, 2] = 1, port: list | tuple = None, verbose=False): - """Create an AudioPlayerUnit object. + """Create an AudioPlayerUnit object. - :param int id: The UART ID of the device. Default is 1. - :param list | tuple port: The UART port of the device. - :param bool verbose: The verbose mode of the device. Default is False. + :param int id: The UART ID of the device. Default is 2. + :param port: The UART port of the device. + :type port: list | tuple + :param bool verbose: The verbose mode of the device. Default is False. - UiFlow2 Code Block: + UiFlow2 Code Block: - |init.png| + |init.png| - MicroPython Code Block: + MicroPython Code Block: - .. code-block:: python + .. code-block:: python - from unit import AudioPlayerUnit + from unit import AudioPlayerUnit - audio_player_0 = AudioPlayerUnit(0, port=(33, 32)) - """ + audio_player_0 = AudioPlayerUnit(2, port=(33, 32)) + """ - self.uart = UART(id, tx=port[1], rx=port[0]) + def __init__(self, id: Literal[0, 1, 2] = 2, port: list | tuple = None, verbose=False): + self.uart = machine.UART(id, tx=port[1], rx=port[0]) self.uart.init(9600, bits=8, parity=None, stop=1) - self.uart.irq(handler=self._handler, trigger=UART.IRQ_RXIDLE) + self.uart.irq(handler=self._handler, trigger=machine.UART.IRQ_RXIDLE) self.uart.read() self.verbose = verbose self.raw_message = "" From 63efb24f77afda5a41be0044ee66893b6eadfd3b Mon Sep 17 00:00:00 2001 From: tinyu Date: Fri, 16 May 2025 11:44:00 +0800 Subject: [PATCH 138/322] lib/unit: Add Unit ID ECHD & AES function. Signed-off-by: tinyu --- docs/en/units/id.rst | 36 ++++++++ m5stack/libs/driver/atecc608b_tngtls/atecc.py | 10 +- m5stack/libs/unit/id.py | 91 ++++++++++++++++++- 3 files changed, 129 insertions(+), 8 deletions(-) diff --git a/docs/en/units/id.rst b/docs/en/units/id.rst index aaf0504c..4f40b099 100644 --- a/docs/en/units/id.rst +++ b/docs/en/units/id.rst @@ -222,4 +222,40 @@ Methods |set_certificate_signing_request.png| +.. method:: IDUnit.aes_ecb_encrypt(position, data) -> bytearray + Performs AES-ECB mode encryption on data. The AES key is stored in slot 9 of the chip. + + :param int position: Position for encryption operation (used after left shift by 6) + :param bytearray data: Data to be encrypted, must be 16 bytes + + - Return: ``bytearray``: Returns 16 bytes of encrypted data + + +.. method:: IDUnit.aes_ecb_decrypt(position, data) -> bytearray + + Performs AES-ECB mode decryption on data. The AES key is stored in slot 9 of the chip. + + :param int position: Position for decryption operation (used after left shift by 6) + :param bytearray data: Data to be decrypted, must be 16 bytes + + - Return: ``bytearray``: Returns 16 bytes of decrypted data + + +.. method:: IDUnit.aes_gfm_encrypt(data) -> bytearray + + Performs Galois Field Multiplication (GFM) encryption operation in AES-GCM mode. + + :param bytearray data: Data to be encrypted, must be 16 bytes + + - Return: ``bytearray``: Returns 16 bytes of encrypted data + +.. method:: IDUnit.ecdh_stored_key(slot_num, mode, external_public_key=None) -> bytearray + + Performs an ECDH key exchange operation using stored keys. + + :param int slot_num: The key slot number + :param int mode: ECDH operation mode + :param bytearray external_public_key: External public key, must be 64 bytes. If not provided, a new public key will be generated + + - Return: ``bytearray``: Returns 32 bytes shared secret when mode is 0x0C or 0x08; \ No newline at end of file diff --git a/m5stack/libs/driver/atecc608b_tngtls/atecc.py b/m5stack/libs/driver/atecc608b_tngtls/atecc.py index 7fa3cb57..d4e9a531 100755 --- a/m5stack/libs/driver/atecc608b_tngtls/atecc.py +++ b/m5stack/libs/driver/atecc608b_tngtls/atecc.py @@ -13,12 +13,12 @@ # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA import time @@ -409,7 +409,9 @@ def _read(self, zone: int, address: int, buffer: bytearray) -> None: time.sleep(0.001) self.idle() - def _send_command(self, opcode: int, param_1: int, param_2: int = 0x00, data="") -> None: + def _send_command( + self, opcode: int, param_1: int, param_2: int = 0x00, data: bytearray = bytearray(b"") + ) -> None: #! Sends a security command packet over i2c. #! assembling command packet command_packet = bytearray(8 + len(data)) @@ -451,7 +453,7 @@ def _get_response(self, buf, length: int = None, retries: int = 20) -> int: raise RuntimeError("Failed to read data from chip") if self._debug: print("\tReceived: ", [hex(i) for i in response]) - crc = response[-2] | (response[-1] << 8) + crc = (response[-1] << 8) | response[-2] crc2 = self._at_crc(response[0:-2]) if crc != crc2: raise RuntimeError("CRC Mismatch") diff --git a/m5stack/libs/unit/id.py b/m5stack/libs/unit/id.py index 43ddc493..888f3f58 100644 --- a/m5stack/libs/unit/id.py +++ b/m5stack/libs/unit/id.py @@ -3,10 +3,11 @@ # SPDX-License-Identifier: MIT from machine import I2C -from .pahub import PAHUBUnit -from .unit_helper import UnitError +from unit.pahub import PAHUBUnit +from unit.unit_helper import UnitError import binascii import struct +import time from driver.atecc608b_tngtls.atecc import ATECC from driver.atecc608b_tngtls.atecc_cert_util import CSR @@ -24,7 +25,6 @@ def __init__(self, i2c: I2C | PAHUBUnit, address: int = _I2C_ADDR) -> None: self._i2c_addr = address if address not in self._i2c.scan(): raise UnitError("ID unit maybe not found in Grove") - self.atecc = ATECC(self._i2c, self._i2c_addr) def get_revision_number(self) -> int: @@ -55,7 +55,7 @@ def uniform(self, min: float = 0, max: float = 0) -> float: min, max = (max, min) if min > max else (min, max) return min + (max - min) * self.random() - def get_generate_key(self, slot_num: int, private_key: bool = False) -> bytearray: + def get_generate_key(self, slot_num: int, private_key: bool = False): #! Generates a private or public key. assert 0 <= slot_num <= 4, "Provided slot must be between 0 and 4." # Create a new key @@ -63,6 +63,26 @@ def get_generate_key(self, slot_num: int, private_key: bool = False) -> bytearra self.atecc.gen_key(key, slot_num, private_key) return key + def ecdh_stored_key(self, slot_num: int, mode: int, external_public_key: bytearray = None): + if external_public_key is None: + external_public_key = bytearray(64) + self.atecc.gen_key(external_public_key, 0, private_key=False) + + if len(external_public_key) != 64: + raise ValueError("External public key must be exactly 64 bytes.") + + self.atecc._send_command( + opcode=0x43, param_1=mode, param_2=slot_num, data=external_public_key + ) + + if mode in (0x0C, 0x08): + time.sleep(1) + res = bytearray(32) + self.atecc._get_response(res) + return res + else: + return None + def get_ecdsa_sign(self, slot: int, message: str | list | bytearray) -> bytearray | None: #! Generates and returns a signature using the ECDSA algorithm. if len(message) == 32: @@ -120,3 +140,66 @@ def set_certificate_signing_request( ) with open(file_path, "w+") as pem_file: pem_file.write(pem_content) + + # ref: ATECC608A-TFLXTLS 4.4.1 Table 4-5 + def read_data_zone(self, data_zone: int): + self.atecc._send_command( + opcode=0x02, + param_1=0x00, + param_2=data_zone, + ) + time.sleep(0.1) + temp = bytearray(4) + self.atecc._get_response(temp) + return temp + + def write_data_zone(self, data_zone: int, data: bytearray): + self.atecc._send_command( + opcode=0x12, + param_1=0x02, + param_2=data_zone, + data=data, + ) + + def aes_ecb_encrypt(self, position: int, data: bytearray): + if len(data) != 16: + raise ValueError("Data must be 16 bytes") + self.atecc._send_command( + opcode=0x51, + param_1=(position << 6), + param_2=0x09, # ATECC608B AES Key only stored in slot 9 + data=data, + ) + time.sleep(1) + res = bytearray(16) + self.atecc._get_response(res) + return res + + def aes_ecb_decrypt(self, position: int, data: bytearray): + if len(data) != 16: + raise ValueError("Data must be 16 bytes") + self.atecc._send_command( + opcode=0x51, + param_1=(position << 6) + 1, + param_2=0x09, # ATECC608B AES Key only stored in slot 9 + data=data, + ) + time.sleep(1) + res = bytearray(16) + self.atecc._get_response(res) + return res + + def get_h_field(self): + zero_block = bytearray([0x00] * 16) + return self.aes_ecb_encrypt(0x00, zero_block) + + def aes_gfm_encrypt(self, data: bytearray): + if len(data) != 16: + raise ValueError("Data must be 16 bytes") + data = self.get_h_field() + data + time.sleep(1) + self.atecc._send_command(opcode=0x51, param_1=0x03, param_2=0x00, data=data) + time.sleep(1) + res = bytearray(16) + self.atecc._get_response(res) + return res From 75fde34941d78599184e5fd91d3e546c3ce998a2 Mon Sep 17 00:00:00 2001 From: tinyu Date: Wed, 18 Jun 2025 18:01:48 +0800 Subject: [PATCH 139/322] READMD.md: Update the link to esp-idf. Signed-off-by: tinyu --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 7bb193bc..7f4e8d36 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ ```shell mkdir uiflow_workspace && cd uiflow_workspace -git clone -b v5.2.2 https://github.com/espressif/esp-idf.git +git clone --depth 1 --branch v5.4.1 https://github.com/espressif/esp-idf.git git -C esp-idf submodule update --init --recursive ./esp-idf/install.sh . ./esp-idf/export.sh @@ -24,7 +24,7 @@ make mpy-cross make flash_all ``` -The default board build the M5STACK_AtomS3 one, You can specify a different board by passing `BOARD=` to the make commands. More BOARD type define is under [m5satck/boards](./m5stack/boards/) path. +The default board build the M5STACK_AtomS3 one, You can use the `make BOARD= pack_all` command to specify different development boards for compilation. More BOARD type definitions are located in the [m5stack/boards](./m5stack/boards/) path. More command support, you can check the [Makefile](./m5stack/Makefile). From 589abd51e768f8344f296c41ec3622481bca7ac0 Mon Sep 17 00:00:00 2001 From: tinyu Date: Thu, 19 Jun 2025 10:18:51 +0800 Subject: [PATCH 140/322] lib/unit: Fixe 8EncoderUnit incorrect reading opposite btn status. Signed-off-by: tinyu --- m5stack/libs/unit/encoder8.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/m5stack/libs/unit/encoder8.py b/m5stack/libs/unit/encoder8.py index 5422f9ec..5ef2e472 100644 --- a/m5stack/libs/unit/encoder8.py +++ b/m5stack/libs/unit/encoder8.py @@ -181,7 +181,7 @@ def get_button_status(self, channel: int = 1) -> bool: channel >= self._ENCODER_BUTTON_START_REG and channel <= self._ENCODER_BUTTON_END_REG ): channel = self._ENCODER_BUTTON_START_REG - return bool(self.read_reg_data(channel, 1)[0]) + return self.read_reg_data(channel, 1)[0] == 0 def get_switch_status(self) -> bool: """ From c115948f07a0451901315495e63979eb361a073d Mon Sep 17 00:00:00 2001 From: tinyu Date: Thu, 26 Jun 2025 10:50:32 +0800 Subject: [PATCH 141/322] lib/base: Add Atomic GPS Base v2.0 base support. Signed-off-by: tinyu --- docs/en/base/gpsv2.rst | 64 +++ docs/en/base/index.rst | 1 + docs/en/refs/base.gpsv2.ref | 35 ++ docs/locales/zh_CN/LC_MESSAGES/base/gpsv2.po | 510 ++++++++++++++++++ .../base/gpsv2/base_gpsv2_atom_example.m5f2 | 1 + .../base/gpsv2/base_gpsv2_atom_example.py | 50 ++ m5stack/libs/base/__init__.py | 1 + m5stack/libs/base/gpsv2.py | 45 ++ m5stack/libs/base/manifest.py | 1 + m5stack/libs/driver/atgm336h.py | 384 ++++++++----- 10 files changed, 956 insertions(+), 136 deletions(-) create mode 100644 docs/en/base/gpsv2.rst create mode 100644 docs/en/refs/base.gpsv2.ref create mode 100644 docs/locales/zh_CN/LC_MESSAGES/base/gpsv2.po create mode 100644 examples/base/gpsv2/base_gpsv2_atom_example.m5f2 create mode 100644 examples/base/gpsv2/base_gpsv2_atom_example.py create mode 100644 m5stack/libs/base/gpsv2.py diff --git a/docs/en/base/gpsv2.rst b/docs/en/base/gpsv2.rst new file mode 100644 index 00000000..b6759dc8 --- /dev/null +++ b/docs/en/base/gpsv2.rst @@ -0,0 +1,64 @@ +Atomic GPS Base v2.0 +====================== + +.. sku: A134-V2 + +.. include:: ../refs/base.gpsv2.ref + +This is the driver library for the Atomic GPS Base v2.0, which is used to get the GPS data. + +Support the following products: + + |Atom GPS Base v2.0| + +UiFlow2 Example +--------------- + +get GPS data +^^^^^^^^^^^^^^^^^ + +Open the |base_gpsv2_atom_example.m5f2| project in UiFlow2. + +This example demonstrates how to get the GPS data using Atomic GPS Base v2.0. + +UiFlow2 Code Block: + + |example.png| + +Example output: + + None + + +MicroPython Example +------------------- + +get GPS data +^^^^^^^^^^^^^^^^^^ + +This example demonstrates how to get the GPS data using Atomic GPS Base v2.0. + +MicroPython Code Block: + + .. literalinclude:: ../../../examples/base/gpsv2/base_gpsv2_atom_example.py + :language: python + :linenos: + +Example output: + + None + + +**API** +------- + +AtomicGPSV2Base +^^^^^^^^^^^^^^^ + +.. autoclass:: base.gpsv2.AtomicGPSV2Base + :members: + :member-order: bysource + +.. autoclass:: driver.atgm336h.ATGM336H + :members: + :member-order: bysource diff --git a/docs/en/base/index.rst b/docs/en/base/index.rst index d473055a..5273b0de 100644 --- a/docs/en/base/index.rst +++ b/docs/en/base/index.rst @@ -12,6 +12,7 @@ Base atom_gps.rst display.rst echo.rst + gpsv2.rst hdriver.rst motion.rst pwm.rst diff --git a/docs/en/refs/base.gpsv2.ref b/docs/en/refs/base.gpsv2.ref new file mode 100644 index 00000000..5f5341fb --- /dev/null +++ b/docs/en/refs/base.gpsv2.ref @@ -0,0 +1,35 @@ +.. |Atom GPS Base v2.0| image:: https://static-cdn.m5stack.com/resource/docs/products/atom/atom_can/atom_can_01.webp + :target: https://docs.m5stack.com/en/atom/atom_can + :height: 200px + :width: 200px + +.. |init.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/gps_v2/init.png +.. |set_work_mode.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/gps_v2/set_work_mode.png +.. |get_work_mode.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/gps_v2/get_work_mode.png +.. |get_antenna_state.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/gps_v2/get_antenna_state.png +.. |get_gps_time.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/gps_v2/get_gps_time.png +.. |get_gps_date.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/gps_v2/get_gps_date.png +.. |get_gps_date_time.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/gps_v2/get_gps_date_time.png +.. |get_timestamp.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/gps_v2/get_timestamp.png +.. |get_latitude.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/gps_v2/get_latitude.png +.. |get_longitude.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/gps_v2/get_longitude.png +.. |get_altitude.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/gps_v2/get_altitude.png +.. |get_satellite_num.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/gps_v2/get_satellite_num.png +.. |get_pos_quality.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/gps_v2/get_pos_quality.png +.. |get_course_over_ground.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/gps_v2/get_course_over_ground.png +.. |get_speed_over_ground.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/gps_v2/get_speed_over_ground.png +.. |set_time_zone.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/gps_v2/set_time_zone.png +.. |get_time_zone.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/gps_v2/get_time_zone.png +.. |deinit.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/gps_v2/deinit.png + +.. |example.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/gps_v2/example.png + +.. |base_gpsv2_atom_example.m5f2| raw:: html + + + base_gpsv2_atom_example.m5f2 + + diff --git a/docs/locales/zh_CN/LC_MESSAGES/base/gpsv2.po b/docs/locales/zh_CN/LC_MESSAGES/base/gpsv2.po new file mode 100644 index 00000000..d1d82ee3 --- /dev/null +++ b/docs/locales/zh_CN/LC_MESSAGES/base/gpsv2.po @@ -0,0 +1,510 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2016 - 2025 M5Stack Technology Co., Ltd +# This file is distributed under the same license as the UIFlow2 Programming +# Guide package. +# FIRST AUTHOR , 2025. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: UIFlow2 Programming Guide \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-06-26 10:44+0800\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: zh_CN\n" +"Language-Team: zh_CN \n" +"Plural-Forms: nplurals=1; plural=0;\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.16.0\n" + +#: ../../en/base/gpsv2.rst:2 123adce5f48f4239ba7ee9c12839e921 +msgid "Atomic GPS Base v2.0" +msgstr "" + +#: ../../en/base/gpsv2.rst:8 4b1277338d98479794f0d9955303186c +msgid "" +"This is the driver library for the Atomic GPS Base v2.0, which is used to" +" get the GPS data." +msgstr "该库用于获取Atom GPS Base v2.0的GPS数据。" + +#: ../../en/base/gpsv2.rst:10 913e6c2512454c7aa7b2205e82f907f6 +msgid "Support the following products:" +msgstr "支持以下产品:" + +#: ../../en/base/gpsv2.rst:12 123adce5f48f4239ba7ee9c12839e921 +msgid "|Atom GPS Base v2.0|" +msgstr "" + +#: ../../en/refs/base.gpsv2.ref 1683783b265f45318b3558b3b068983d +msgid "Atom GPS Base v2.0" +msgstr "" + +#: ../../en/base/gpsv2.rst:15 e4b3bf989e4245cb88396525a8cbcc26 +msgid "UiFlow2 Example" +msgstr "UiFlow2示例" + +#: ../../en/base/gpsv2.rst:18 ../../en/base/gpsv2.rst:37 +#: bfba1e425c6e494ca68d515dceea6276 +msgid "get GPS data" +msgstr "获取GPS数据" + +#: ../../en/base/gpsv2.rst:20 82b8562361934e3091a9d05e1266598e +msgid "Open the |base_gpsv2_atom_example.m5f2| project in UiFlow2." +msgstr "在UiFlow2中打开 |base_gpsv2_atom_example.m5f2| 项目。" + +#: ../../en/base/gpsv2.rst:22 ../../en/base/gpsv2.rst:39 +#: 12fa607e2bc748c8933f7a1af037f2a4 +msgid "" +"This example demonstrates how to get the GPS data using Atomic GPS Base " +"v2.0." +msgstr "该示例演示了如何使用Atomic GPS Base v2.0获取GPS数据。" + +#: ../../en/base/gpsv2.rst:24 driver.atgm336h.ATGM336H.deinit:3 +#: driver.atgm336h.ATGM336H.get_altitude:6 +#: driver.atgm336h.ATGM336H.get_course_over_ground:6 +#: driver.atgm336h.ATGM336H.get_gps_date:6 +#: driver.atgm336h.ATGM336H.get_gps_date_time:6 +#: driver.atgm336h.ATGM336H.get_gps_time:6 +#: driver.atgm336h.ATGM336H.get_latitude:6 +#: driver.atgm336h.ATGM336H.get_longitude:6 +#: driver.atgm336h.ATGM336H.get_pos_quality:6 +#: driver.atgm336h.ATGM336H.get_satellite_num:6 +#: driver.atgm336h.ATGM336H.get_speed_over_ground:6 +#: driver.atgm336h.ATGM336H.get_time_zone:6 +#: driver.atgm336h.ATGM336H.get_timestamp:6 +#: driver.atgm336h.ATGM336H.get_work_mode:6 +#: driver.atgm336h.ATGM336H.set_time_zone:5 +#: driver.atgm336h.ATGM336H.set_work_mode:5 e8381c785a5541798bc2f21846022f74 of +msgid "UiFlow2 Code Block:" +msgstr "UiFlow2代码块:" + +#: ../../en/base/gpsv2.rst:26 f39ee44efc3047b5b5a5df849778f26b +msgid "|example.png|" +msgstr "" + +#: ../../en/refs/base.gpsv2.ref:25 81744794dabc46799313e568337e4a18 +msgid "example.png" +msgstr "" + +#: ../../en/base/gpsv2.rst:28 ../../en/base/gpsv2.rst:47 +#: 3065fe0132774410b034efcdab21ebf8 +msgid "Example output:" +msgstr "示例输出:" + +#: ../../en/base/gpsv2.rst:30 ../../en/base/gpsv2.rst:49 +#: 292d13c718644630b6e7884f34c552c0 +msgid "None" +msgstr "" + +#: ../../en/base/gpsv2.rst:34 872da499e9c14dcea58624da6c248d16 +msgid "MicroPython Example" +msgstr "MicroPython示例" + +#: ../../en/base/gpsv2.rst:41 82b2389723594dc1b7d13b39529e2f38 +#: base.gpsv2.AtomicGPSV2Base:12 driver.atgm336h.ATGM336H:9 +#: driver.atgm336h.ATGM336H.deinit:7 driver.atgm336h.ATGM336H.get_altitude:10 +#: driver.atgm336h.ATGM336H.get_antenna_state:6 +#: driver.atgm336h.ATGM336H.get_course_over_ground:10 +#: driver.atgm336h.ATGM336H.get_gps_date:10 +#: driver.atgm336h.ATGM336H.get_gps_date_time:10 +#: driver.atgm336h.ATGM336H.get_gps_time:10 +#: driver.atgm336h.ATGM336H.get_latitude:10 +#: driver.atgm336h.ATGM336H.get_longitude:10 +#: driver.atgm336h.ATGM336H.get_pos_quality:10 +#: driver.atgm336h.ATGM336H.get_satellite_num:10 +#: driver.atgm336h.ATGM336H.get_speed_over_ground:10 +#: driver.atgm336h.ATGM336H.get_time_zone:10 +#: driver.atgm336h.ATGM336H.get_timestamp:10 +#: driver.atgm336h.ATGM336H.get_work_mode:10 +#: driver.atgm336h.ATGM336H.set_time_zone:9 +#: driver.atgm336h.ATGM336H.set_work_mode:9 of +msgid "MicroPython Code Block:" +msgstr "MicroPython代码块:" + +#: ../../en/base/gpsv2.rst:53 a98e9b0688d94dd8b02338037bcd7ac7 +msgid "**API**" +msgstr "" + +#: ../../en/base/gpsv2.rst:56 60e796c11291454f85290626cc648667 +msgid "AtomicGPSV2Base" +msgstr "" + +#: base.gpsv2.AtomicGPSV2Base:1 bd88175e25c74a9cb07282afb90eb03f of +msgid "Bases: :py:class:`~driver.atgm336h.ATGM336H`" +msgstr "" + +#: base.gpsv2.AtomicGPSV2Base:1 ef2f98608b024173a3df0c6c6e4d5152 of +msgid "Create an AtomicGPSV2Base object." +msgstr "创建一个AtomicGPSV2Base对象。" + +#: ../../en/base/gpsv2.rst 225dfa09367847b2904875ab873d066b +#: base.gpsv2.AtomicGPSV2Base driver.atgm336h.ATGM336H.set_time_zone +#: driver.atgm336h.ATGM336H.set_work_mode of +msgid "Parameters" +msgstr "参数" + +#: 3daea53ce1c04d50ad05224d51874821 base.gpsv2.AtomicGPSV2Base:3 +#: driver.atgm336h.ATGM336H:3 of +msgid "The UART ID for communication with the GPS module. It can be 1, or 2." +msgstr "与GPS模块通信的UART ID。可以是1或2。" + +#: base.gpsv2.AtomicGPSV2Base:4 fe2c21d1a5c84ab6a48775c0451815a5 of +msgid "A list or tuple containing the TX and RX pins for UART communication." +msgstr "包含TX和RX引脚的列表或元组,用于UART通信。" + +#: base.gpsv2.AtomicGPSV2Base:6 driver.atgm336h.ATGM336H:7 +#: f3d1cd36e0e24156b7eac87f789ac400 of +msgid "Whether to print verbose output." +msgstr "是否打印详细输出。" + +#: 8f4d6a8b98e242118ac98c44bbdc9cf2 base.gpsv2.AtomicGPSV2Base:8 of +msgid "UIFlow Code Block:" +msgstr "UIFlow代码块:" + +#: 82cc40a472c8495b9edb606243216319 base.gpsv2.AtomicGPSV2Base:10 of +msgid "|init.png|" +msgstr "" + +#: ../../en/refs/base.gpsv2.ref:6 e5d46299c5ea48f78e2697c6b4c28007 +msgid "init.png" +msgstr "" + +#: 153b704dbdc0472b992a77fa0aaf7e00 driver.atgm336h.ATGM336H:1 of +msgid "Bases: :py:class:`object`" +msgstr "" + +#: 6fd4f69cc70b42d2980e85e9b9fa7c0b driver.atgm336h.ATGM336H:1 of +msgid "Create an ATGM336H object." +msgstr "创建一个ATGM336H对象。" + +#: a33b25595ddc4887a4c8c14cd288249e driver.atgm336h.ATGM336H:4 of +msgid "The TX pin is the pin that sends data to the GPS module." +msgstr "TX引脚是发送数据到GPS模块的引脚。" + +#: driver.atgm336h.ATGM336H:5 ebce545999f247d8beb4856eb47068f9 of +msgid "The RX pin is the pin that receives data from the GPS module." +msgstr "RX引脚是接收来自GPS模块的数据的引脚。" + +#: driver.atgm336h.ATGM336H:6 f3d89f0f7bf14007b4ce707e9e0f062f of +msgid "The PPS pin is the pin that receives the PPS signal from the GPS module." +msgstr "PPS引脚是接收来自GPS模块的PPS信号的引脚。" + +#: 964508e9954e41908cd1a81bef7cd54d driver.atgm336h.ATGM336H.set_work_mode:1 of +msgid "Set the working mode of the GPS module." +msgstr "设置GPS模块的工作模式。" + +#: 595091db3fe840ff9e0b17278084cc8f driver.atgm336h.ATGM336H.set_work_mode:3 of +msgid "The mode to set, defined by the GPS module." +msgstr "要设置的模式,由GPS模块定义。" + +#: dac12dc2f32d4c28abd558e7517d7950 driver.atgm336h.ATGM336H.set_work_mode:7 of +msgid "|set_work_mode.png|" +msgstr "" + +#: ../../en/refs/base.gpsv2.ref:7 5a22caf99f81488a8c422d43eb73a819 +msgid "set_work_mode.png" +msgstr "" + +#: driver.atgm336h.ATGM336H.get_work_mode:1 e737b25794494910a833b9ba9e31ef00 of +msgid "Get the current working mode of the GPS module." +msgstr "获取当前GPS模块的工作模式。" + +#: c1cec223e74b42f988ae43e22661f95b driver.atgm336h.ATGM336H.get_altitude +#: driver.atgm336h.ATGM336H.get_antenna_state +#: driver.atgm336h.ATGM336H.get_course_over_ground +#: driver.atgm336h.ATGM336H.get_gps_date +#: driver.atgm336h.ATGM336H.get_gps_date_time +#: driver.atgm336h.ATGM336H.get_gps_time driver.atgm336h.ATGM336H.get_latitude +#: driver.atgm336h.ATGM336H.get_longitude +#: driver.atgm336h.ATGM336H.get_pos_quality +#: driver.atgm336h.ATGM336H.get_satellite_num +#: driver.atgm336h.ATGM336H.get_speed_over_ground +#: driver.atgm336h.ATGM336H.get_time_zone +#: driver.atgm336h.ATGM336H.get_timestamp +#: driver.atgm336h.ATGM336H.get_work_mode of +msgid "Returns" +msgstr "返回" + +#: 1398c3f577da44ed9be9b83f84dad3c8 driver.atgm336h.ATGM336H.get_work_mode:3 of +msgid "The current working mode of the GPS module." +msgstr "当前GPS模块的工作模式。" + +#: 49a7f809e1ba480fababa9704b2f12a9 driver.atgm336h.ATGM336H.get_altitude +#: driver.atgm336h.ATGM336H.get_antenna_state +#: driver.atgm336h.ATGM336H.get_course_over_ground +#: driver.atgm336h.ATGM336H.get_gps_date +#: driver.atgm336h.ATGM336H.get_gps_date_time +#: driver.atgm336h.ATGM336H.get_gps_time driver.atgm336h.ATGM336H.get_latitude +#: driver.atgm336h.ATGM336H.get_longitude +#: driver.atgm336h.ATGM336H.get_pos_quality +#: driver.atgm336h.ATGM336H.get_satellite_num +#: driver.atgm336h.ATGM336H.get_speed_over_ground +#: driver.atgm336h.ATGM336H.get_time_zone +#: driver.atgm336h.ATGM336H.get_timestamp +#: driver.atgm336h.ATGM336H.get_work_mode of +msgid "Return type" +msgstr "返回类型" + +#: 9c5e676b7d90434684bfde3942235912 driver.atgm336h.ATGM336H.get_work_mode:8 of +msgid "|get_work_mode.png|" +msgstr "" + +#: ../../en/refs/base.gpsv2.ref:8 6b0fb2bdee734caa872106bb1c84ad4a +msgid "get_work_mode.png" +msgstr "" + +#: c61c0282681e4a91bf30880700da978e +#: driver.atgm336h.ATGM336H.get_antenna_state:1 of +msgid "Get the state of the antenna." +msgstr "获取天线状态。" + +#: bc89c10b86894c96921ac823fc208d0c +#: driver.atgm336h.ATGM336H.get_antenna_state:3 of +msgid "The antenna state." +msgstr "天线状态。" + +#: d7d1021fbe024ce8a1a3a26c94b5df1d driver.atgm336h.ATGM336H.get_gps_time:1 of +msgid "Get the current GPS time." +msgstr "获取当前GPS时间。" + +#: 1f456aeddfa34b6f8e4517a7a05804a6 driver.atgm336h.ATGM336H.get_gps_time:3 of +msgid "The GPS time as a list of strings [hour, minute, second]." +msgstr "GPS时间作为字符串列表[小时,分钟,秒]。" + +#: c88cda92c1514cc1bec4a16d4f9971ab driver.atgm336h.ATGM336H.get_gps_time:8 of +msgid "|get_gps_time.png|" +msgstr "" + +#: ../../en/refs/base.gpsv2.ref:10 9c06c5f63b7244e98d5260cb75914e66 +msgid "get_gps_time.png" +msgstr "" + +#: de620385033e47a097e990684729a4a7 driver.atgm336h.ATGM336H.get_gps_date:1 of +msgid "Get the current GPS date." +msgstr "获取当前GPS日期。" + +#: b5a0984ddb3b4c7bad066cc4edf48671 driver.atgm336h.ATGM336H.get_gps_date:3 of +msgid "The GPS date as a list of strings [day, month, year]." +msgstr "GPS日期作为字符串列表[日,月,年]。" + +#: 11eeba1802d547f4b5ef6270ef81d10f driver.atgm336h.ATGM336H.get_gps_date:8 of +msgid "|get_gps_date.png|" +msgstr "" + +#: ../../en/refs/base.gpsv2.ref:11 9e8eec9f29944ed48689d8f89138018c +msgid "get_gps_date.png" +msgstr "" + +#: driver.atgm336h.ATGM336H.get_gps_date_time:1 +#: f57e6e21b7b64ebe86504ead9bf81774 of +msgid "Get the current GPS date and time combined." +msgstr "获取当前GPS日期和时间组合。" + +#: ce021a306d0344ea9bc3aac4b1b24a28 +#: driver.atgm336h.ATGM336H.get_gps_date_time:3 of +msgid "" +"The GPS date and time as a list of strings [year, month, day, hour, " +"minute, second]." +msgstr "GPS日期和时间作为字符串列表[年,月,日,小时,分钟,秒]。" + +#: 64fcd5e3f2f843749dfbbdb436acba5a +#: driver.atgm336h.ATGM336H.get_gps_date_time:8 of +msgid "|get_gps_date_time.png|" +msgstr "" + +#: ../../en/refs/base.gpsv2.ref:12 dabee3252b99430ebc512b92d13efa35 +msgid "get_gps_date_time.png" +msgstr "" + +#: 23f41b33283d4f77b0de6941e358f1d3 driver.atgm336h.ATGM336H.get_timestamp:1 of +msgid "Get the timestamp of the current GPS time." +msgstr "获取当前GPS时间的时间戳。" + +#: d1e43cae1dc849b0b7254b29e1a92f1d driver.atgm336h.ATGM336H.get_timestamp:3 of +msgid "The timestamp representing the current GPS time." +msgstr "表示当前GPS时间的时间戳。" + +#: 7478014646b2438093eb713dc787b79b driver.atgm336h.ATGM336H.get_timestamp:8 of +msgid "|get_timestamp.png|" +msgstr "" + +#: ../../en/refs/base.gpsv2.ref:13 263814b2a83f49f6a2491e80ecb78290 +msgid "get_timestamp.png" +msgstr "" + +#: 44b8864fec2941a8ad74164ea6796462 driver.atgm336h.ATGM336H.get_latitude:1 of +msgid "Get the current latitude." +msgstr "获取当前纬度。" + +#: bbce4630e86048d8bac5eb4f5338097a driver.atgm336h.ATGM336H.get_latitude:3 of +msgid "The current latitude in string format." +msgstr "当前纬度作为字符串格式。" + +#: 7b3f48ed105f4c158e9067525591f42b driver.atgm336h.ATGM336H.get_latitude:8 of +msgid "|get_latitude.png|" +msgstr "" + +#: ../../en/refs/base.gpsv2.ref:14 c10a6fc0f6a3450f9d89905174e515b2 +msgid "get_latitude.png" +msgstr "" + +#: 6af07c582e9740c7bfe2d8dc45ee7201 driver.atgm336h.ATGM336H.get_longitude:1 of +msgid "Get the current longitude." +msgstr "获取当前经度。" + +#: 7b72f25a72a2432a9880ede72979667f driver.atgm336h.ATGM336H.get_longitude:3 of +msgid "The current longitude in string format." +msgstr "当前经度作为字符串格式。" + +#: 70d3b691881c4f4b8c39b3842edb1b59 driver.atgm336h.ATGM336H.get_longitude:8 of +msgid "|get_longitude.png|" +msgstr "" + +#: ../../en/refs/base.gpsv2.ref:15 08ca7258eef54be88181d5075c6138ca +msgid "get_longitude.png" +msgstr "" + +#: 1defe2265a9d40d6b21a708273431047 driver.atgm336h.ATGM336H.get_altitude:1 of +msgid "Get the current altitude." +msgstr "获取当前海拔高度。" + +#: driver.atgm336h.ATGM336H.get_altitude:3 fa8a91a88d1841e981b132a5dc4c245b of +msgid "The current altitude in string format." +msgstr "当前海拔高度作为字符串格式。" + +#: 1eeba883ef5845f7bc4c348c13b2eeb0 driver.atgm336h.ATGM336H.get_altitude:8 of +msgid "|get_altitude.png|" +msgstr "" + +#: ../../en/refs/base.gpsv2.ref:16 aaf60307b0134cd78d75ae652e8eb81a +msgid "get_altitude.png" +msgstr "" + +#: ad7393c8d23448789d5916e8515030c1 +#: driver.atgm336h.ATGM336H.get_satellite_num:1 of +msgid "Get the number of satellites used for positioning." +msgstr "获取用于定位的卫星数量。" + +#: 62f084fc2bcd492283a3a9ff1e580958 +#: driver.atgm336h.ATGM336H.get_satellite_num:3 of +msgid "The number of satellites." +msgstr "卫星数量。" + +#: 28b921cb3b5645739310867de3c78384 +#: driver.atgm336h.ATGM336H.get_satellite_num:8 of +msgid "|get_satellite_num.png|" +msgstr "" + +#: ../../en/refs/base.gpsv2.ref:17 f1d7799b2b19489ab4a1f0f2e02de5fd +msgid "get_satellite_num.png" +msgstr "" + +#: a4eb512e3b224c329b574f7a260bdfd5 driver.atgm336h.ATGM336H.get_pos_quality:1 +#: of +msgid "Get the quality of the GPS position." +msgstr "获取GPS位置的质量。" + +#: driver.atgm336h.ATGM336H.get_pos_quality:3 f81e1bd623e84a039c9eed4eb3e32713 +#: of +msgid "The position quality indicator." +msgstr "位置质量。" + +#: 9045afbc8a5c487d872204c09ceef187 driver.atgm336h.ATGM336H.get_pos_quality:8 +#: of +msgid "|get_pos_quality.png|" +msgstr "" + +#: ../../en/refs/base.gpsv2.ref:18 72c715f188a54677872460822fd1c842 +msgid "get_pos_quality.png" +msgstr "" + +#: 0f8267e1bdb34dc4a752bc18979779ee +#: driver.atgm336h.ATGM336H.get_course_over_ground:1 of +msgid "Get the course over ground (COG)." +msgstr "获取航向角(COG)。" + +#: driver.atgm336h.ATGM336H.get_course_over_ground:3 +#: f0d897d38c5f43e087a73c7b7087bd7b of +msgid "The course over ground in degrees." +msgstr "航向角(COG)以度为单位。" + +#: driver.atgm336h.ATGM336H.get_course_over_ground:8 +#: e1c86b68c7b241efb94bc4b0ec6fa23e of +msgid "|get_course_over_ground.png|" +msgstr "" + +#: ../../en/refs/base.gpsv2.ref:19 0538c973872b486eb15bbb7b03a0f081 +msgid "get_course_over_ground.png" +msgstr "" + +#: 45065bafe5b646dda665429ec6ee7de4 +#: driver.atgm336h.ATGM336H.get_speed_over_ground:1 of +msgid "Get the speed over ground (SOG)." +msgstr "获取地面速度(SOG)。" + +#: 5c24f03884f74219af99f122af730e12 +#: driver.atgm336h.ATGM336H.get_speed_over_ground:3 of +msgid "The speed over ground in knots." +msgstr "地面速度(SOG)以节为单位。" + +#: 589432a2dbe64444b07ccd6388a980a4 +#: driver.atgm336h.ATGM336H.get_speed_over_ground:8 of +msgid "|get_speed_over_ground.png|" +msgstr "" + +#: ../../en/refs/base.gpsv2.ref:20 17baf2dc71ff45c6a1efbe4a5e3ff0a2 +msgid "get_speed_over_ground.png" +msgstr "" + +#: driver.atgm336h.ATGM336H.set_time_zone:1 f95e2688779743ed993da7d27817c7f7 of +msgid "Set the time zone offset for the GPS time." +msgstr "设置GPS时间的时间区偏移。" + +#: 79d45ff6b37f43ccbcd7e21aa8e88755 driver.atgm336h.ATGM336H.set_time_zone:3 of +msgid "The time zone offset value to set." +msgstr "要设置的时间区偏移值。" + +#: 15f33217382a4b0089c44dbfdf1ff192 driver.atgm336h.ATGM336H.set_time_zone:7 of +msgid "|set_time_zone.png|" +msgstr "" + +#: ../../en/refs/base.gpsv2.ref:21 5b4e69277c824f3580a220e504ba298c +msgid "set_time_zone.png" +msgstr "" + +#: 0f160ed49e904118bdefe4bc4a3d59b3 driver.atgm336h.ATGM336H.get_time_zone:1 of +msgid "Get the current time zone offset." +msgstr "获取当前时间区偏移。" + +#: 658b3b772c174fb1a4657d86aaa26161 driver.atgm336h.ATGM336H.get_time_zone:3 of +msgid "The current time zone offset." +msgstr "当前时间区偏移。" + +#: 5484c54f8b60433ba03d11b04b398b71 driver.atgm336h.ATGM336H.get_time_zone:8 of +msgid "|get_time_zone.png|" +msgstr "" + +#: ../../en/refs/base.gpsv2.ref:22 d8ab83b809024691b039ee164d64b300 +msgid "get_time_zone.png" +msgstr "" + +#: driver.atgm336h.ATGM336H.deinit:1 eb919356acff4d2f81d57d8482f6f3bf of +msgid "" +"Deinitialize the GPS unit, stopping any running tasks and releasing " +"resources." +msgstr "取消初始化GPS单元,停止任何正在运行的任务并释放资源。" + +#: 68e814ddd5f4496dbf06d8f54b2a6a81 driver.atgm336h.ATGM336H.deinit:5 of +msgid "|deinit.png|" +msgstr "" + +#: ../../en/refs/base.gpsv2.ref:23 f10da922c87648be991a9c6392179e5c +msgid "deinit.png" +msgstr "" + +#~ msgid "The UART ID for communication with the GPS module. It can be 1 or 2." +#~ msgstr "与GPS模块通信的UART ID。可以是1或2。" + diff --git a/examples/base/gpsv2/base_gpsv2_atom_example.m5f2 b/examples/base/gpsv2/base_gpsv2_atom_example.m5f2 new file mode 100644 index 00000000..eb959c4b --- /dev/null +++ b/examples/base/gpsv2/base_gpsv2_atom_example.m5f2 @@ -0,0 +1 @@ +{"version":"V2.0","versionNumber":"V2.2.9","type":"atom-matrix","components":[{"name":"screen","type":"screen","layer":0,"screenId":"builtin","screenName":"","id":"__atom-matrix_screen","createTime":1750823072273,"x":0,"y":0,"width":160,"height":160,"backgroundColor":"#000000","size":0}],"resources":[{"hardware":["hardware_button","hardware_pin_button","imu","rgb","ir"]}],"units":[],"hats":[],"bases":[],"i2cs":[],"blockly":"truebuiltinrgb20rgbpalette#33ff33False70truelongitude:altitude:latitude:1","screen":[{"simulationName":"Built-in","type":"builtin","width":160,"height":160,"scale":1.3,"screenName":"","blockId":"","screenColorType":0,"id":"builtin","createTime":1750823072268,"currentAtomMatrixColor":"#55cd4c","atomMatrix":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]}],"logicWhenNum":0,"customList":[]} \ No newline at end of file diff --git a/examples/base/gpsv2/base_gpsv2_atom_example.py b/examples/base/gpsv2/base_gpsv2_atom_example.py new file mode 100644 index 00000000..0a1a9b0c --- /dev/null +++ b/examples/base/gpsv2/base_gpsv2_atom_example.py @@ -0,0 +1,50 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import os, sys, io +import M5 +from M5 import * +from hardware import RGB +from base import AtomicGPSV2Base +import time + + +rgb = None +base_gpsv2 = None + + +def setup(): + global rgb, base_gpsv2 + + M5.begin() + rgb = RGB() + rgb.set_screen([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) + rgb.set_brightness(20) + rgb.fill_color(0x33FF33) + base_gpsv2 = AtomicGPSV2Base(2, port=(22, 19)) + base_gpsv2.set_work_mode(7) + base_gpsv2.set_time_zone(0) + + +def loop(): + global rgb, base_gpsv2 + M5.update() + print((str("longitude:") + str((base_gpsv2.get_longitude())))) + print((str("altitude:") + str((base_gpsv2.get_altitude())))) + print((str("latitude:") + str((base_gpsv2.get_latitude())))) + time.sleep(1) + + +if __name__ == "__main__": + try: + setup() + while True: + loop() + except (Exception, KeyboardInterrupt) as e: + try: + from utility import print_error_msg + + print_error_msg(e) + except ImportError: + print("please update to latest firmware") diff --git a/m5stack/libs/base/__init__.py b/m5stack/libs/base/__init__.py index ace94b7b..2e49e3a8 100644 --- a/m5stack/libs/base/__init__.py +++ b/m5stack/libs/base/__init__.py @@ -14,6 +14,7 @@ "AtomicHDriverBase": "hdriver", "Motion": "motion", "MotionBase": "motion", + "AtomicGPSV2Base": "gpsv2", "AtomicPWMBase": "pwm", "AtomicQRCodeBase": "qrcode", "AtomicQRCode2Base": "qrcode2", diff --git a/m5stack/libs/base/gpsv2.py b/m5stack/libs/base/gpsv2.py new file mode 100644 index 00000000..d7213eb5 --- /dev/null +++ b/m5stack/libs/base/gpsv2.py @@ -0,0 +1,45 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT +from driver.atgm336h import ATGM336H +import sys + +if sys.platform != "esp32": + from typing import Literal + + +class AtomicGPSV2Base(ATGM336H): + """Create an AtomicGPSV2Base object. + + :param int id: The UART ID for communication with the GPS module. It can be 1, or 2. + :param port: A list or tuple containing the TX and RX pins for UART communication. + :type port: list | tuple + :param bool verbose: Whether to print verbose output. + + UIFlow Code Block: + + |init.png| + + MicroPython Code Block: + + .. code-block:: python + + from base.gpsv2 import AtomicGPSV2Base + + gps_0 = AtomicGPSV2Base(id=1, tx=5, rx=6) + """ + + def __init__(self, id: Literal[0, 1, 2] = 1, port: list | tuple = None, verbose: bool = False): + """ + note: + en: Initialize the GPSUnit with a specific UART id and port for communication. + + params: + id: + note: The UART ID for communication with the GPS module. It can be 0, 1, or 2. + port: + note: A list or tuple containing the TX and RX pins for UART communication. + verbose: + note: Whether to print verbose output. + """ + super().__init__(id, port[1], port[0], verbose=verbose) diff --git a/m5stack/libs/base/manifest.py b/m5stack/libs/base/manifest.py index 57500a2d..f51141eb 100644 --- a/m5stack/libs/base/manifest.py +++ b/m5stack/libs/base/manifest.py @@ -13,6 +13,7 @@ "dtu_lorawan_rui3.py", "dtu_nbiot.py", "echo.py", + "gpsv2.py", "hdriver.py", "motion.py", "pwm.py", diff --git a/m5stack/libs/driver/atgm336h.py b/m5stack/libs/driver/atgm336h.py index 9f81003f..5d50cb16 100644 --- a/m5stack/libs/driver/atgm336h.py +++ b/m5stack/libs/driver/atgm336h.py @@ -2,7 +2,8 @@ # # SPDX-License-Identifier: MIT from machine import UART -from .timer_thread import TimerThread +from machine import Pin +from driver.timer_thread import TimerThread import time import sys @@ -13,17 +14,25 @@ class ATGM336H: - def __init__(self, id: Literal[0, 1, 2], tx: int, rx: int, pps=None): - """ - note: - en: Initialize the GPSUnit with a specific UART id and port for communication. + """ + Create an ATGM336H object. - params: - id: - note: The UART ID for communication with the GPS module. It can be 0, 1, or 2. - port: - note: A list or tuple containing the TX and RX pins for UART communication. - """ + :param int id: The UART ID for communication with the GPS module. It can be 1, or 2. + :param int tx: The TX pin is the pin that sends data to the GPS module. + :param int rx: The RX pin is the pin that receives data from the GPS module. + :param int pps: The PPS pin is the pin that receives the PPS signal from the GPS module. + :param bool verbose: Whether to print verbose output. + + MicroPython Code Block: + + .. code-block:: python + + from driver.atgm336h import ATGM336H + + gps_0 = ATGM336H(id=2, tx=5, rx=6) + """ + + def __init__(self, id: Literal[0, 1, 2], tx: int, rx: int, pps=None, rst=None, verbose=False): self.mode = 0 self.antenna_state = "0" self.gps_time = ["00", "00", "00"] @@ -35,9 +44,17 @@ def __init__(self, id: Literal[0, 1, 2], tx: int, rx: int, pps=None): self.altitude = "0" self.satellite_num = "0" self.pos_quality = "0" - self.corse_ground_degree = "0" + self.course_ground_degree = "0" self.speed_ground_knot = "0" self.time_offset = 0 + self.verbose = verbose + self.rst = rst + if self.rst: + print(self.rst) + self.rst = Pin(self.rst, Pin.OUT) + self.rst.value(1) + time.sleep(1) + self.rst.value(0) self.uart = UART(id, tx=tx, rx=rx) self.uart.init(115200, bits=8, parity=None, stop=1, rxbuf=1024) self._timer = timTh.add_timer(200, timTh.PERIODIC, self._monitor) @@ -45,12 +62,19 @@ def __init__(self, id: Literal[0, 1, 2], tx: int, rx: int, pps=None): def set_work_mode(self, mode: int): """ - note: - en: Set the working mode of the GPS module. + Set the working mode of the GPS module. - params: - mode: - note: The mode to set, defined by the GPS module. + :param int mode: The mode to set, defined by the GPS module. + + UiFlow2 Code Block: + + |set_work_mode.png| + + MicroPython Code Block: + + .. code-block:: python + + gps_0.set_work_mode(7) """ self.mode = mode buf = self._add_checksum(f"PCAS04,{mode}") @@ -58,159 +82,300 @@ def set_work_mode(self, mode: int): def get_work_mode(self): """ - note: - en: Get the current working mode of the GPS module. + Get the current working mode of the GPS module. + + :returns: The current working mode of the GPS module. + :rtype: int + + UiFlow2 Code Block: - return: - note: The current working mode of the GPS module. + |get_work_mode.png| + + MicroPython Code Block: + + .. code-block:: python + + gps_0.get_work_mode() """ return self.mode def get_antenna_state(self) -> str: """ - note: - en: Get the state of the antenna. + Get the state of the antenna. + + :returns: The antenna state. + :rtype: str - return: - note: The antenna state. + MicroPython Code Block: + + .. code-block:: python + + gps_0.get_antenna_state() """ return self.antenna_state def get_gps_time(self): """ - note: - en: Get the current GPS time. + Get the current GPS time. + + :returns: The GPS time as a list of strings [hour, minute, second]. + :rtype: list[str] - return: - note: The GPS time as a list of strings [hour, minute, second]. + UiFlow2 Code Block: + + |get_gps_time.png| + + MicroPython Code Block: + + .. code-block:: python + + gps_0.get_gps_time() """ return self.gps_time def get_gps_date(self): """ - note: - en: Get the current GPS date. + Get the current GPS date. + + :returns: The GPS date as a list of strings [day, month, year]. + :rtype: list[str] + + UiFlow2 Code Block: + + |get_gps_date.png| - return: - note: The GPS date as a list of strings [day, month, year]. + MicroPython Code Block: + + .. code-block:: python + + gps_0.get_gps_date() """ return self.gps_date def get_gps_date_time(self): """ - note: - en: Get the current GPS date and time combined. + Get the current GPS date and time combined. + + :returns: The GPS date and time as a list of strings [year, month, day, hour, minute, second]. + :rtype: list[str] + + UiFlow2 Code Block: + + |get_gps_date_time.png| + + MicroPython Code Block: + + .. code-block:: python - return: - note: The GPS date and time as a list of strings [year, month, day, hour, minute, second]. + gps_0.get_gps_date_time() """ return self.gps_date_time def get_timestamp(self) -> int | float: """ - note: - en: Get the timestamp of the current GPS time. + Get the timestamp of the current GPS time. - return: - note: The timestamp representing the current GPS time. + :returns: The timestamp representing the current GPS time. + :rtype: int | float + + UiFlow2 Code Block: + + |get_timestamp.png| + + MicroPython Code Block: + + .. code-block:: python + + gps_0.get_timestamp() """ return self.timestamp def get_latitude(self): """ - note: - en: Get the current latitude. + Get the current latitude. + + :returns: The current latitude in string format. + :rtype: str + + UiFlow2 Code Block: - return: - note: The current latitude in string format. + |get_latitude.png| + + MicroPython Code Block: + + .. code-block:: python + + gps_0.get_latitude() """ return self.latitude def get_longitude(self): """ - note: - en: Get the current longitude. + Get the current longitude. + + :returns: The current longitude in string format. + :rtype: str + + UiFlow2 Code Block: + + |get_longitude.png| + + MicroPython Code Block: - return: - note: The current longitude in string format. + .. code-block:: python + + gps_0.get_longitude() """ return self.longitude def get_altitude(self): """ - note: - en: Get the current altitude. + Get the current altitude. + + :returns: The current altitude in string format. + :rtype: str + + UiFlow2 Code Block: + + |get_altitude.png| + + MicroPython Code Block: + + .. code-block:: python - return: - note: The current altitude in string format. + gps_0.get_altitude() """ return self.altitude def get_satellite_num(self): """ - note: - en: Get the number of satellites used for positioning. + Get the number of satellites used for positioning. - return: - note: The number of satellites. + :returns: The number of satellites. + :rtype: str + + UiFlow2 Code Block: + + |get_satellite_num.png| + + MicroPython Code Block: + + .. code-block:: python + + gps_0.get_satellite_num() """ return self.satellite_num def get_pos_quality(self): """ - note: - en: Get the quality of the GPS position. + Get the quality of the GPS position. + + :returns: The position quality indicator. + :rtype: str + + UiFlow2 Code Block: - return: - note: The position quality indicator. + |get_pos_quality.png| + + MicroPython Code Block: + + .. code-block:: python + + gps_0.get_pos_quality() """ return self.pos_quality def get_corse_over_ground(self) -> str: + return self.get_course_over_ground() + + def get_course_over_ground(self) -> str: """ - note: - en: Get the course over ground (COG). + Get the course over ground (COG). + + :returns: The course over ground in degrees. + :rtype: str + + UiFlow2 Code Block: + + |get_course_over_ground.png| - return: - note: The course over ground in degrees. + MicroPython Code Block: + + .. code-block:: python + + gps_0.get_course_over_ground() """ - return self.corse_ground_degree + return self.course_ground_degree def get_speed_over_ground(self) -> str: """ - note: - en: Get the speed over ground (SOG). + Get the speed over ground (SOG). + + :returns: The speed over ground in knots. + :rtype: str + + UiFlow2 Code Block: + + |get_speed_over_ground.png| + + MicroPython Code Block: + + .. code-block:: python - return: - note: The speed over ground in knots. + gps_0.get_speed_over_ground() """ return self.speed_ground_knot def set_time_zone(self, value): """ - note: - en: Set the time zone offset for the GPS time. + Set the time zone offset for the GPS time. - params: - value: - note: The time zone offset value to set. + :param int value: The time zone offset value to set. + + UiFlow2 Code Block: + + |set_time_zone.png| + + MicroPython Code Block: + + .. code-block:: python + + gps_0.set_time_zone(1) """ self.time_offset = value def get_time_zone(self): """ - note: - en: Get the current time zone offset. + Get the current time zone offset. + + :returns: The current time zone offset. + :rtype: int + + UiFlow2 Code Block: - return: - note: The current time zone offset. + |get_time_zone.png| + + MicroPython Code Block: + + .. code-block:: python + + gps_0.get_time_zone() """ return self.time_offset def deinit(self): """ - note: - en: Deinitialize the GPS unit, stopping any running tasks and releasing resources. + Deinitialize the GPS unit, stopping any running tasks and releasing resources. + + UiFlow2 Code Block: + + |deinit.png| + + MicroPython Code Block: + + .. code-block:: python + + gps_0.deinit() """ self._timer.deinit() try: @@ -219,31 +384,12 @@ def deinit(self): pass def _add_checksum(self, message: str) -> str: - """ - note: - en: Add checksum to the message for communication with the GPS module. - - params: - message: - note: The message to which the checksum will be added. - - return: - note: The message with added checksum. - """ checksum = 0 for char in message: checksum ^= ord(char) return f"${message}*{checksum:02X}\r\n" def _decode_gga(self, data: str): - """ - note: - en: Decode the GGA sentence to extract GPS quality, number of satellites, and altitude. - - params: - data: - note: The GGA sentence to decode. - """ gps_list = data.split(",") self.pos_quality = gps_list[6] if self.pos_quality == "0": @@ -253,14 +399,6 @@ def _decode_gga(self, data: str): self.altitude = gps_list[9] + gps_list[10] def _decode_rmc(self, data: str): - """ - note: - en: Decode the RMC sentence to extract GPS time, latitude, longitude, speed, course, and date. - - params: - data: - note: The RMC sentence to decode. - """ gps_list = data.split(",") if gps_list[2] == "A": time_buf = gps_list[1] @@ -272,7 +410,7 @@ def _decode_rmc(self, data: str): self.latitude = self._convert_to_decimal(gps_list[3], gps_list[4]) self.longitude = self._convert_to_decimal(gps_list[5], gps_list[6], False) self.speed_ground_knot = gps_list[7] - self.corse_ground_degree = gps_list[8] + self.course_ground_degree = gps_list[8] data_buf = gps_list[9] self.gps_date = [int(data_buf[4:7]) + 2000, int(data_buf[2:4]), int(data_buf[0:2])] t = ( @@ -290,21 +428,6 @@ def _decode_rmc(self, data: str): self.timestamp = buf def _convert_to_decimal(self, degrees_minutes, direction, latitude: bool = True) -> float: - """ - note: - en: Convert latitude or longitude from degrees minutes format to decimal format. - - params: - degrees_minutes: - note: Latitude or Longitude in DDMM.MMMM format (e.g., "2242.10772"). - direction: - note: Direction of the coordinate ("N", "S", "E", "W"). - latitude: - note: True if latitude, False if longitude. - - returns: - note: The decimal value of the coordinate. - """ if latitude: degrees = degrees_minutes[:2] # First two digits are degrees minutes = degrees_minutes[2:] # The rest are minutes @@ -317,28 +440,17 @@ def _convert_to_decimal(self, degrees_minutes, direction, latitude: bool = True) return decimal def _decode_txt(self, data: str): - """ - note: - en: Decode the TXT sentence to extract antenna state. - - params: - data: - note: The TXT sentence to decode. - """ gps_list = data.split(",") if gps_list[4] is not None and gps_list[4][0:7] == "ANTENNA": self.antenna_state = gps_list[4][8:].split("*")[0] def _monitor(self): - """ - note: - en: Monitor the GPS data and decode incoming sentences. - """ while True: if self.uart.any() < 25: break gps_data = self.uart.readline() + self.verbose and print(gps_data) if gps_data is not None and isinstance(gps_data, bytes): if gps_data[3:6] == b"GGA" and gps_data[-1] == b"\n"[0]: self._decode_gga(gps_data.decode()) From 95b8ce465a92f25b37db05bb8c470795d2ec2cfb Mon Sep 17 00:00:00 2001 From: lbuque Date: Wed, 25 Jun 2025 12:11:17 +0800 Subject: [PATCH 142/322] machine_rtc.c: Fix the time zone inversion issue. For example, to set the Eastern Time Zone, please use GMT+8 or UTC+8. Signed-off-by: lbuque --- docs/en/system/time.rst | 2 +- m5stack/machine_rtc.c | 24 +++++++++++-- .../0006-modtime-add-timezone-method.patch | 34 ++++++++++++------- 3 files changed, 45 insertions(+), 15 deletions(-) diff --git a/docs/en/system/time.rst b/docs/en/system/time.rst index 2edd381a..692adfc9 100644 --- a/docs/en/system/time.rst +++ b/docs/en/system/time.rst @@ -100,7 +100,7 @@ Functions This is inverse function of localtime. It's argument is a full 8-tuple which expresses a time as per localtime. It returns an integer which is - the number of seconds since Jan 1, 2000. + the number of seconds since Jan 1, 1970. UIFLOW2: diff --git a/m5stack/machine_rtc.c b/m5stack/machine_rtc.c index f2c1c95b..c83bb55d 100644 --- a/m5stack/machine_rtc.c +++ b/m5stack/machine_rtc.c @@ -208,11 +208,31 @@ static mp_obj_t machine_rtc_timezone(size_t n_args, const mp_obj_t *args) { if (tz == NULL) { return mp_const_none; } else { + char *ptr = strchr(tz, '+'); + if (ptr != NULL) { + *ptr = '-'; + } else { + ptr = strchr(tz, '-'); + if (ptr != NULL) { + *ptr = '+'; + } + } return mp_obj_new_str(tz, strlen(tz)); } } else { - char tz[64]; - snprintf(tz, sizeof(tz), "%s", mp_obj_str_get_str(args[1])); + char tz[64] = { 0 }; + snprintf(tz, sizeof(tz), "%s", mp_obj_str_get_str(args[0])); + + char *ptr = strchr(tz, '-'); + if (ptr != NULL) { + *ptr = '+'; + } else { + ptr = strchr(tz, '+'); + if (ptr != NULL) { + *ptr = '-'; + } + } + setenv("TZ", tz, 1); tzset(); diff --git a/m5stack/patches/0006-modtime-add-timezone-method.patch b/m5stack/patches/0006-modtime-add-timezone-method.patch index f58e394a..3600e06f 100644 --- a/m5stack/patches/0006-modtime-add-timezone-method.patch +++ b/m5stack/patches/0006-modtime-add-timezone-method.patch @@ -118,7 +118,7 @@ Index: micropython/ports/esp32/modtime.c struct timeval tv; gettimeofday(&tv, NULL); timeutils_struct_time_t tm; -@@ -56,3 +95,36 @@ static mp_obj_t mp_time_time_get(void) { +@@ -56,3 +95,46 @@ static mp_obj_t mp_time_time_get(void) { gettimeofday(&tv, NULL); return mp_obj_new_int(tv.tv_sec); } @@ -129,23 +129,33 @@ Index: micropython/ports/esp32/modtime.c + if (tz == NULL) { + return mp_const_none; + } else { ++ char *ptr = strchr(tz, '+'); ++ if (ptr != NULL) { ++ *ptr = '-'; ++ } else { ++ ptr = strchr(tz, '-'); ++ if (ptr != NULL) { ++ *ptr = '+'; ++ } ++ } + return mp_obj_new_str(tz, strlen(tz)); + } + } else { -+ char tz[64]; ++ char tz[64] = { 0 }; + snprintf(tz, sizeof(tz), "%s", mp_obj_str_get_str(args[0])); -+ setenv("TZ", tz, 1); -+ tzset(); + -+ time_t now; -+ char strftime_buf[64]; -+ struct tm timeinfo; -+ -+ time(&now); ++ char *ptr = strchr(tz, '-'); ++ if (ptr != NULL) { ++ *ptr = '+'; ++ } else { ++ ptr = strchr(tz, '+'); ++ if (ptr != NULL) { ++ *ptr = '-'; ++ } ++ } + -+ localtime_r(&now, &timeinfo); -+ strftime(strftime_buf, sizeof(strftime_buf), "%c", &timeinfo); -+ ESP_LOGE("time", "The current date/time in Shanghai is: %s", strftime_buf); ++ setenv("TZ", tz, 1); ++ tzset(); + + nvs_write_str_helper(UIFLOW_NVS_NAMESPACE, "tz", tz); + return mp_const_none; From 9aac99f02a5951e374c9a9b22466c7bc00b4d3e5 Mon Sep 17 00:00:00 2001 From: Forairaaaaa Date: Wed, 25 Jun 2025 15:20:56 +0800 Subject: [PATCH 143/322] modules/startup/tab5: Add app adc, gpio and i2c. Signed-off-by: Forairaaaaa --- .../system/tab5/utils/i2c_panel_internal.png | Bin 0 -> 42800 bytes .../fs/system/tab5/utils/i2c_panel_port_a.png | Bin 0 -> 38983 bytes m5stack/modules/startup/manifest_tab5.py | 2 + m5stack/modules/startup/tab5/hal_tab5.py | 40 ++++- .../startup/tab5/launcher/apps/__init__.py | 2 + .../startup/tab5/launcher/apps/app_adc.py | 138 ++++++++++++++++++ .../startup/tab5/launcher/apps/app_gpio.py | 131 +++++++++++++++++ .../tab5/launcher/apps/app_i2c_scan.py | 9 +- .../tab5/launcher/components/app_dock.py | 6 - m5stack/modules/startup/tab5/launcher/hal.py | 27 ++++ .../modules/startup/tab5/launcher/launcher.py | 6 +- 11 files changed, 345 insertions(+), 16 deletions(-) create mode 100755 m5stack/fs/system/tab5/utils/i2c_panel_internal.png create mode 100755 m5stack/fs/system/tab5/utils/i2c_panel_port_a.png create mode 100644 m5stack/modules/startup/tab5/launcher/apps/app_adc.py create mode 100644 m5stack/modules/startup/tab5/launcher/apps/app_gpio.py diff --git a/m5stack/fs/system/tab5/utils/i2c_panel_internal.png b/m5stack/fs/system/tab5/utils/i2c_panel_internal.png new file mode 100755 index 0000000000000000000000000000000000000000..db22a2794423739427a7e0440ecdc9331a79c068 GIT binary patch literal 42800 zcmV*iKuy1iP)?t9(+x;r7=Nq{VX5E2#vaTEeL3JR!f0*<4O zBCc%iztIsGL}x%{bX-Rn701y5WL#JR?js0pSwLlxH6aX;o$TrK`rfVc`=hGv_U?P# z9dK06=aY2zUFz1U|FnI9ohLfkqjd=Pz+#jVEymUrk|~#{CUBmR8XxEn;shU zqEwQ$B)qOBX-Ne{LB%3!Ch#*n?|vE?p`zO`Is{VI=T`Wb!ZT$!;``k|sSs-oYkG<4 zL8$`j@Sae=%YQb3$05>DBDQDnYBwKceV@oY;^vDktaQ_0DoTV9`+LUY-?|NjJU(LG z{Q94{`NCotC9+$H7@Y)O6Iq{``BJwIW4ei@8!NpIFV(bvq5oo$52C>6T}k;!BrAn+d)J*YsMc~(A=|0K!v*dnp-D#%3f+b>@irwb%;u*suKQ3;6S*Bp6o0wYK z*QWhZGB0h`TX-(po>7Zom`JxFejT=7gYEFig3Ubd_Lr5aqZiVRrHWF?(dEhFLl&3J z__(zvc6i61MiwfkD)QIJ_G4Vi^N%pmgTn+f_V@pss(B3{hnx!VU~ElbMIf zfSa!_EW2S9lnxAIfTBdG7(|QnXA6HbVSFp>q{>Dj*;#1onE+2<#iH6lgI*%0QCEwV zid!FA7)Q+ubeg$7v7fI06&<3=1|o)1oF>kDbC2f)AH~xc5b7wMjOjE)>Y!s@5G+}T ztzf+S02ok<5EIED6*gw@`eUp^i_%G~yE&Rc9ZQF!v6=6Q?|W7#X}6DB-4|l23=pxA zh+&?&HCWsi4zC8I#JU?$PZg3$(1VFQCu%XoL=1Yo2K|j)Lc~LvU;!cxES(5-tTc#i zhZmT}?`Jta&GaOzOQ}{cTB9PkMd=ns45FUsPiR&D1wh$sIV~@bWBGFii&X@M-2TsUPj8Q}t6$=`^C5V>IAL-w@&t#+)}oauotw3NR$+>cH^DBUbq)DU*8LZVkO|^e@X+R4mSTXUNzg7ZJaYbht%+tpu72wi!8AB@A`g?APD7MKo zT zjDNPd&tc=)@V!DGrD-mU5KJ$EHqG-ysC6@b4sR)aN)Rt43gv#8vOcsM!-=g`qs9|H z|4gFo#{Ry$V+B-grd{>lX~NRqb{ox#8*8fuAv&C9+QJ&UsWO$@fL&pe3h^BW`Q*J2LG+cTjbWzRJW9wxb- z&R5odo;M9YZ7_Kpv+Ft~#fmEVzBg%xMMT1zS=*NqwcS&bhi0Z0G-AOD#Hy&YX7w7F zP2qZ&Gox<3stF7#yT0S}_fr0p?NxRTo9C}RdoEjBrf|I-P0UL8W#5s^I+TP4IYvL> z4n14I6#n5EZ(yR6-+8H|oBrJjCKJF?@^;BJllz`ArsX#h9j)Yc*|pLhByBgpuq$I% z@`X&>1!k^!fu}7LOhQH_Rpc^lTggb8Q1~Oz?}-wK0W$C2}%&uI> zXuj5Fo?8jbO@BA9(_I%>(mv((gf{PkB2*@}JpOVaE2SG_usJ1GOBV3NanG9ejQ>Wn z31-*8`7N7G8D+nVM5$6SVJ2uH1Q{e!ZOPKsjHayWzY{pt_$IDoxvOGwX27BeI{9GB zmW(lI)0j*We59hm&z0siIraX@Ah#>Vtr4+#Q*IIW+}}RZF*g@^%}|9LwGhG>r*q+c zDb9%}R&ArL%OtFF;vz=dt{LOUBDEB>a@hoptn4q_MzbuKowM|--C7>kH(%&o!8B;p zmRQ4{InCF(2tuQ_Em?8eZ$Z-^2d`$+`ZEp9>zTq8Rrk+A(~`^X6)9YY^q$2e-4C%E z?S+v%uB9gHHqW}j8vs1Y_3#Rh`f2`n}KN&azRn$yV-KvcF zP8`Ixez|M`?UH6aL0%pvc0A@3CVNtNi*-br*sYQ5zv829Oz%%0#>X$;nRdd9zv5GP zvbYKPfEXdgbuxa`yk;s**Bw*WoIIb!Zz6M)ZC4m)jb-O7_{YUQWAo*@lEo+AZW%sl z@0;g!yzvX`^3NUGrMopf`wR*1e|(r2PG!@RwKzdxp7~>rLC|=gDgSu74k4ZAKtEco z-y&4XFQ-`n+K)leH(MmmCYgX>nsC2OoQMYvG56JyK4tNu z;KOe94wU&{Gw}h*J4RjS!{uf?3twtw{uV)6`S?nFHu-+rlG$wK^DURiU6U5H>U~z4 zFp46fD4(NoCN39AUB)zmCBbVfA=lR9;<9)N4Cq$aooh=Tm|9-Hx zw-TD1Z`+M!e%-e6(|VtJ;*azaz8Y*s`*q(N-$*J%gvjM=|04IX1jBf|;FzSrqNbR} zQT$OM_%Dmy$yu-_-)u4;iDqB4E9nh%+?#6n}~24b~QO@c7e3=qxo)>u9sce z10i@U%3ZahIl`O@x6SW1ce5k}XhQh?`G6J}k^@1h~Jal<&#OW(G91Dh3z+7=b!gnvEDwEN9lX)PgIJD&|?);?H|oj}K|( z9VeG40@{H~@(<$w7(yK+MzrX|Doj{G!zqFzGD0Y1A|j|w8Imgq0ZBA#n|6%g`xAox zlTI|_nGifjvWv2u`j3@IGe%8U)3-b>Q~*ggLPqk=>DhwweW)+6Q5Kdj7E16E%}UDo z&7wh1^b1^xF=3-7;r&|JOlh=p?z-=b(=0>5naKNji@sNrGm#2)uf68Ms1py0F_SJG z$dqUTg8FqBQo;-kjA>x)nAc20?s*xc8gx3V>AIUJjIbY4@Rc_7^7%*&0l$q2hyYWAm~YJO~!AyAYmOYeM;O`CT* z1|)TkS$GKj{oNTR94t`5l96xKXffp{tiWelvoDL&DBcfqnbOc!BQ5x1I8<16=lyKj zH0<`840H5RhcmseoBX=7e(uBHtt^18`dB^=<*~{WfW+r(Q5DBev=ghHJoeb*)M_f}sv<2|un%+Q^wHgEsmLfw z-KTYwrJn>SOmvtKjS;D^dFvpXwhj`AVlbsRjjzGHd2>jsXiXMeP>@o^YA05!43AjW zu3Zlr(N(K5clPdxj9{p{%0jtsWqlyyZ&9~N?q$>F0Z-fwSYJxdn@iQ32n`X?;GhHc zV%~mpN;K|CIi3lGD%Nh6uUrdsfi|d^o$NKcpIWU&YKJ5i%F;G-xGlSzO`C^NF&NZa zF8tFO0xvqsDW@CmUy6P`h|^BUt@Jv7ZLi0j)sUPZ1V-5D3x0kC7CRt%`JmxE&>2}i4PPS|v zWzE`+Jn_W(gy3ZNS0oK!1S2-J6isjho$%}!yrqN3RzAV%Rcjd>9Axi(<}#zNk6E*) z<4WYT3r_np0X6*m=Sx_;>^{_1F=mW|7FEpvIdPNr_ZJARc5anedswx0g#WzydKNER?lrm!s33AX zMh3ayoHIG?q{FDl4xH3pXsn5ul$ypz~}T)fxlCUEF%d{oMThU$Ji8HY^pC3c?r>!<>1>ah!C*Uom%1FOlqUlGp?m zYBmbm`RXEWyZugx{FYyP z+|p`e&Ap5m=H!!3Z~{$13r_QZ$putdQj3YDlY!xGZoA_TeC4VeXlOS`l=+scUNm=B z7iYcx2#!DYP(;U2iA($A&@fI2(mH6ws zCh4w{{O?);?Lera#K{*#1hKZsms*78>+OBOt$ne@{gH9ip`|eb;-x1M!Pu;O$mzb> zPqg&~YXqLISgB-UA)30M{dx|+)B@uLQct{-m;^6K=kjoiN^&e}APHMdjK>K}jkphPsZi~Anf$Uk2CCF;66vG$WQ zS!fN>9b;>|21rft7Dw1Sw4n}~u*4G~mp z-1EQ&F1hrJjM^DkdVy)A2WVJgrf~hDyI6GdPx z9R`|iSW%@c;G_(!K$KI1!h4EUgK8J6*NpI)FMf}@orw}5Cgz>*d}kuO#NMA++_Yiy zwFRxPWXbPPreU>;k$R>+bmlSaE?F*}uiPEt@#NZJKKk*muzGEsSa&Ba%I^d-*Z)q| zuHQmWcVAOmr4htgbF*=#-&i`q)?85#!JFHCe*|Zy_5{mPA)XEBeQ*;JL(wNl~^YLSl|Tr932OmOFT|EENx1!_R8vroK|z#5!q+lX-C8Pc2Nr51g5!#E3mUa!HQ4TGx~YMMW@(7wj{F zCy~cDs5Q|fvtitL42@Vm@h_i4b(#|*7)6LV=%58GfB0UGTKK}|jjKLIDN{JhAzO(h zA4`&^br>C^Vuny#$4UoirzgZ|dy>%xttX7_0<^4v)rk8ZTE#_|ev>hsi&6tDkqm*3 zx?Es7h_#!BPNP8&mtAoU*M9vXcJJ#XJ^3Ya=`Iu6xG@Xn%}M+=&M#`+*hz)QA79V9 zb=&*~XkZxQ`G+5zYQH%M{;5ps>U_AWnpDC8M-&EzyZP*w{*y5~6QzTyJjDmz`&#yz zT|?OBpLAxikOGqhLn~8s;VECfQK)m&(TBU5#NpHuu|9fW45ZPd{k8)U#ZqO*a1ZCb z_cBJ}*{DuK7$uUOL^8lp3;zl<=7}d)vwr$Vx##X6aV~(+7#CcU8Xd!84?<)o1H)ZheCfY4X7|EMCl%Sj zoLN1b_|g~C)7#00joZ0t(QSeN~J2Q8m#i=J9}qBpySP7>U9+ zjjIHluaxJQl=IhTtQG!12az$HB})<}m1Vr?SAyWGg)D>oQwhThuAWSEvucbH$q5tf zp;^EW^|!?+YOVKOdb7SnE_Op=XXVjGE}A_Fuq3rkg?@yf$L3yndN3 z-}~N9SFOsntsAM;Vn;K8;D$;&oX+vHk^w^nt6eO*`RCMaKQX;j*ukL(?a8@se;f1n zo5jdzo!f5zJy(A1hs4@Tto>a5ty_7;3D0Ndv<|<)+EZ{hK|ZL;OJOISbS$rY<%xh0 zQ{#*?-->E$|H*ieR%sGy9UUBa&;ss%@F|cwvu5>i)@%QkxpQX#@bL0AeEDlXaQM*0 zm`>%MdslMear3=qVri%gk+%|;qn9zY2n}BInp50H%?KY>s|*hdAHD2fS+{O0E--4C z%P+r(*|TP%Ix^uLO;=1HF?{2WNSwlT-@Tm&9^QyD1CddVIci^yKkfh`*@E#VyKKtU zpw8HHUv_~Y9}v0 z{;xUbU7sOl8V#nhbm_xv-8#(7KBrxe=ju`_tXa2-CClzb+0Elx=Ntd=1*T8$Bpj)E z`m0Xi&2RevXeU-`eD$hpdC^ga)7M`GuLY-+wOOdi3NtRoq64d4sMc8V*czT(wT_XI zF#u|{PWGO=2m8;PMZ_?|Fy)5?Obn8~X(y^R)~tJq6)RRSG&n+cPZ#s&@6ViB)18JY z4bSaDau_)GMSyb7mK4z;!rBT}Yp8a!;;}WXTD_j(VQ2AMFn=HB?>8HfVShYr-M?S# zugU){&HF-qWbp8avSQ`qta@?{05hjge1(sC2P;Yn|oGpJdbK z?Fg3n3-)2&yxCNB5S39xvIkX6^4nfGRIT#ls;9gw#2xPPyu%Krt5!v5(AVD!$$=PN zaB9)yvyuTT5u3IuH{E;(%6>#zrz6_PKYr+4%Vg^46i!rAU1B?$afblrJ;S) zWhyr({ktmCo-NVa{I|Rc35W?Y8 zb8l8UYcj=%brPg$B%4dcFg85KKY#3tJo514I781Umt1^4haIvvLfu)_5=)k2=vLH4 z5jWA?AYs%|trBZ5_uapN8y791VS5qTNr!CVgYP?+4%y_1LO8uCXcy`C0zq)|M)~*D z@YrLIr>010FmK*0A{j)ao~sU(vs8rkGkE33Du+`iTejA@anY|)rehePDqFbf%J;I* z>{6`&7vDA(Jx4s$s;FL)+kOa#P5A4d#e%&6~CCrj_Ti}mZ*1D$SQ zOr2wnJRCzk(GYSzyPV%FfE)9Tn|CBBMPTFWd}3*dvaD1QU9-rN(n2LKjBT33R>svv(8|8Uj@T3!EZgiug3Z3yn(NL z?K+eyN(cAczntSY(|5Ni)Mb3CF*gwYY^ z?Agit-t{`3cSt{x?DR~yEQTPpPH2BQgB%qNGEmM&kuic7hk#RW@7DeIq~Amz=mHD$pGh{^EaG& z((|dZ10%y;;4M=kyw;zo;O4&WTrWTT$uIcoS2OW2$z(S;_nfz3=}a*YazcLUO)h42 z2-ymTiXqls9$C4QZ-4KnEME37ss9 zZ(PH}53a-!@&;#~eliP>JRI@obxu?h&G~NCfON5Gv*F^8e381&LPV&_WW;apJAL`~+kZij z#f{WC{q$GT*WUv&+%n)Q3t0he=Jef&OoQ0EOvwB0eUQ^mdVvoDGoV$5hLt;){T8I3 z$hd>m=g!_8(Sg=xNU<5h27|*vAW6qR^(B@VKh>ZTvE(*`$ekuUSni~o+Jj@-vzWJBt|9qmWXFso$MT`@zKk^#C;ELCe|4! zkqgjNuv$UIrPF-ns+-xkaW!v0`;{0Sb;UA#pWO)N_np)5f-vv2-haC6<2?A#DpY&i zxM$Ml#710o)zv=ksTIfJ=j@K5j!WTjPXYAx)es%=jK;8b-Bzr2qDB(0rG*k^ zoSuf1)rMT~iSrrdqEW53i{vi_r=g|h61S1Y$XRXG1&GAN#9HHt)fx`PTTxH-ErSfBHL+ZU7ECXdb7Xc8Vu_6++-k6y9r!g3d4w z2}gkmXqATc@};X5F{U$7?Ie-`{^s;!IPAHzL5FED%~=zqK_r8qBVGb`XTrEZl8NL4 z3WLMmO5wkK_q~sSb!h}W-JQ&xJDdIY+Y6Nukh)|3Nd>0JM<@pQXH^bj$KNY17-J3|agZ81+_|>vi zeD2Dh@zP_TPghqLTQ=|H#zjA%uKjMrWGk;ZgDhP5JPZxc3XAW2fLN!}&}oe6?p%JwH~G%DKFMyiy3_o9ZE-`0&@bdH zCsZZ-w-W$`Iz~oVxbOuGY}?L*53fY2fplWk@Uc%^$v408X=e0yAQ=;9qD>UqbRvf( zrjyw2#%Df%HTOO^NP`&|MybdEb7yt2_dauY;)xBcS-;&0jMB|bH{Z^F`^@CTnz3nu{F24W$pYXr~t3hgB>mNmAn4=fIfbH7`dGO&C#rtyB001BWNkljN%-e4^JBP-&`ddGu zK`#xR%CDBK;=9-Xg4ey~MMSdA`@5?-j9ST|VqI}1p^93UNuxv%$37s^$5wb#e{+jz zu&9aBERAO+0|vw0e|Xs2=mCk@Ywn%|r??|0KFCJv9KGlV651oz*ZOa+(rHo`Fr58};doy6XAJtHPCd&ad=IMeZ%6KL!x znq`>k^2UGq(_f(4iHJdr<=_MNW&VD1CZ$a!APsuDgfD*aUwOy7zX&!Wrk9`o?Dzc7 zt-o;;Qme#T!_ti~%F#z0&TIenG?0x=T5faufgQ%o-+ymr@7d3qwIjG9Ivu?J4d-y? z8LwpbJ*Kl^{U#RO{8N-3vXioY-3CG=w;{7-BnY-mqv&rpn9(ssvT_JYPsmcpXZ6SU~MOjXbMY~J;*DL+q)chxo!6T=|?c+vSBeZ)Q( zsbh&a`IMLQ(aWylz6W;_Yn7|7`zgmCe-wN4S0JMWXK^Y65$6h;K4UkIIqC&G_V{Mj zt>5DJnX>1eGkDH(W)n$+dGq#Y?sa0h$(y2|9XUq@aDfz2;^zy5)?1oy1 zWCyP}@o*N*>qlfL6R8OayO_XH9$xB=!gFvF*^|%K~;#gn;UPsov(c5dTv^DhZ8K+CZ@~~NnAXeIyaiq z!c`(Mkq+>8uX!OS9=8uuYCDN!kcd$tMw#AM<*je|Tetp=MYYOfPprpMZRzS64}=CU zKjBDTdfZ_|vXcq}7#U#h?5TX{gXcMaH-J{SY0*!xbkHhz$qJ~rJ?Db&T(-y0Z@-sV zT_Bs0oxJPZQ+d&md(tWE>68t0$~sOwZeKqA@wag8*DvCnvtP!P+6Y3!M?yQ3zqeuG#QW6}e{CTs zAt?Z!4HQc99xcf+QWMlv|JYDgKi74=N~~S%9PZ*Hmw%llR$>88HD<56O;$` zC7?csb6}){fBN`Wsp~Y9Du!XsdB<7Io3|IrQ^eG$>s0DG71gO=!P;rWS|gGnpTAo$ zZMQ;GT}nt#&2Y?7hk8xaV7&Qj>-L>I^zft37pE00T|D^EDt`3i+d1W=7c)_dNRoS+ zJYPX|)TPjzegd<0?_vE@o4MhZUlYqz8afq#%Rc_^T=k_(5Q&L29=F0!t5^ibXq`{{ z=ePeFv;&KA`LR+@efDjR%CpiOR^-TVmDOtp_~3`G0G$J-PDOWe@PV^=@rz%H7|VtY z+qvnMpZVc3EM4{o);{?l|MuAn=&enD1A`;r`TE zNI}L*g>~yU&AT5Bh!dF5=M+fl1opLr*pmokEOm{DbIZ6mfKX9x zV$9=N$$-J~{KF1l@v=v0FaIOSWRMQo%0FH5 z5A@bX9HG;SH%o?|mYLRD;TTMzZnRE$ zfCJ{uz{nt?ja-EyNs1i!?go>-G&uGphjZ+4FG#f1kb0z67#xlH&Ue1gjf-w|=?zr* zJ=k{U;Y?6V*%`&EP9srU=cn;WF9TedT!Z}$R?ch(S-^Ns{diLh?{29!MzEJiHd zUB+v-mGoKEF=QpFPY@!Vt=FiNu$PQ=Aq}P!A7$?tG^*5UKH}cv<$}}#e%^glps%Wy zq|i(zh9rxwN|lY9Ts;66Xcn>L&IkDIvOCE}*t<1V0jUvDM`VPrGjm1{5u-RGa3_yFzLA*2sCfUL^_y+W);NilHd1HytUhOEorJHp z45V;a!PCK}xJX=m>l#n48|B=0eTXrgj@2%V40G5)(|P~9&t>n~kx!3kr_ov%$M+J7 z5V&FhikNyv%8!{dYX%D!zJMi59wji(wQDvof8OrCfqc47(KNYc{SN-;*1L$Ag-Qpa zbyN%=_=kTYGA?+`Y85NJgc5`z6+ZWcYl+M@s78 zT0Q_V(9_k)>tFY3U*N&{^B;bAd1m8eEfU2=;wpj(V;p_t^SNWmRt!}PF{hpON@ny~ zA~w64$aK=%4QIXKRK9-g?+JCwAAa<44&49Hw(~L}(1>Ms-N)MXTY+9b26YxLd>$eA zDlv~`#;TpR77jJ}pIes_>mERakvgw`?MYP4CQQ(D#L!#o#q?Fz0&$F2)yEF`yN; zZQYSRC`w=dZYe%(PdU?MkI&k3CQq)7UA%}j3=Jz&dO|Rl_1e1-sXYQf4xM9ydX0h)+{X~S+yR7Xq zEseTh|LKIa@$_d-bHQWo%Ea4!Rx)6u!Gig-5E=G$J8Rr<%da{8RVOmDzmv#IsCMVqmMd-UoN@bdv8N@JQ%@i_CAU^QyB*_sWy}!u`r9iOYT|7AByGw%VMHYFCpX?doYF(_B?QCz38_H&aY+RoSDmO}yu_-w<2myf zwJr%5PI~etNRfsYmS-ox>*M93}XO1)$LxW?EdcKQt#ga`oOBkkvl7LRQ#8^}- zY}yJxx%D2;d>g!I;of}s!nabDO&DV8I+aIO4)XpFUP(+((oR}vf3jk?fJWT$i^Uvz z$o}4}*&vkRe01en9(?Et@P%BATBc8%);^Uh*N+&H5e_+Ie^#%1kcM^>YY(^GaWAKx zd?*#!0urOPgP3madtf!QXU}GOUxzd0hN%1&L38POz!ou*8mSGkz{T(WS_7=y)(43y+1bp{wodxrsLrDDsc+Nh1aLI*#M@6+|7+Q|B{$aM24x#Hco%l@vgANGnT{qbRleRS9l_p-FR}% z7#FEN=d7N^9jwsYQ{IK~%W(S5Lz;MeCZfEm0 zp{~;zwf!{g3^r|rZ+-jwzTG+^!yGt&Zzg(TR_T;_hP8I>W)?5K+XY{V>(h(>`bbCM zGl6^orwJ)?;!Nrb7amF^&c$J=^6l^bl)9Zk!}c?3r_r#}**V;PI-jw%&Wef0hP_dmwUM>k-pp;|+6m7|v2`2ZKZ`$A_u@V>Y+&v-d~ecer(NmJ9s z+RY+t+Opl5Wqm5){CO@)*wKVK2Fr>SD<}prju*w7pQob6RN-g0F6NG3{tmT0Sgm2T zht+F$@Tt%IJDO;vW1N1*NjUdG5dz_FCh3Mc$G_xoA{jt@D(08}<3{eee=~J`l^eepi?H}R0H~gHTQ7`mmP4~I}`^3U9Z#L$6haQN*m3~w)EM0mJ-}tW^ zTu`I+bYt@vSA6P=E^VyfBFaP2%9JuTkum1Zp2nP6-Hyg%xpUd?x#sHY7#VJ)LM_6! zZ9DnYzkY$W>(+VZJJ@0k8YVA5Jw13oe4uj;kaCV0qr*M{(O749=w(8Qcwrn2a zrkid}=bQ!Vea}M91a1ZA0>H6Qp}_(3rtZDf7jskWNIQk@A?wZhUn z|G=8{1E@~rns5DxYp?qenoG?0Iey|&6@+0?kRp4!4qH~$>fegGob$zLzr-_^U8oh{adC$y5) zK=Yttu(rZaZv7>ePAn0Tj&krp`?CLjdm#+xUl2QvCoCj3xv@1e%;~2d&9Xc1Bi1e~ zJ$!G`-7H?Voa0}53_U$HhubZ;(a>p#bYmE!BHK9n=mV*k5g%1saIa`e&=Zm?25UR` z;+L*v?fR|kIcp~S?!6BHOP2lCSt+y|mwE0^clx{6o(jaJrdccA?o-^)>M~2`&Y3~P zFflQzom_L>&)944{n>rGBbC3s`yQ^n?#IMTE3xim%=m4_viWbpQsXn9{U)FN+}Bum z)C<|ZZ3hoM{3!S!BcYC%5zaX6#6+_P$QFyqxsD}7E<3^QutWCbm?NLVua<2j)~VEO zKNnv54d%?6!rpt&;mIe~v3C7dVx5DLQLeu3hs>PW!->bvFJRLOGq)f5r`LGnS!Zzd zw|?aME}h)8=r(R%^b-~?Jc8}p2RvTR66Gv!abX}egIAtj1u|-gsW8YzfBy#F|Dn$_ zrh5?URKB<9Uar4+DMv3n98`JyiPc^~sPOBhpXO^{PUtYpB>+*W61Unifu{9Rmh>6J8?hNb4rMw(r$4JxvoSDttXXP$Zl6&Z#? zdB0uiLB=$bPRL{({@!Bh`x;Z_g7^F*F?)b?IXtMNG`Q}+Z@{Hkc}8k07#YQwZ7i7o z0cP}fWz0q6b>0!DWy_Y~3JRoYOWw7UG0EV*t7E+Sj1xKeq+>yf=_$F!NsW&&Musp9 z6Oj@`aYqDsOIj#UFo2O!Z;l@&Vl!1UicyEZNlc?f1hXbt2e;pT8^LVqOpq>GSTm*} zz}JNy;gXBq$G-dS=?lphH=vekq0VPM%4x5BAx}NEiSI4?EipY<>E*^nza}~GL3_M=kq)){~ys%+!Zi{8qN-6GHtoRHT{%*uLp=Wl<{ znsu9iZq}{c#@aPoz1Hd?*j=F8D6?iy&S|H+6%4u9@6uKLp7^WuejQI)5tupOg2i5R9T+vzatc;`8< z;JxoWo$lHY%BYJT6}L7lIKo`A!SekJKA`Yd*kJ0@Wc=}I*ruqza(rD|*^-jUMi6yc zrVJx8iV>HtVx@x8fy)z&$X4abGN5#!=La|uO2nmcopjQ1eCA&-;pHbDfk~<~jrxCe z_fLkIGixdrocDU(`sPZpI;HV=PV8lhvr_H6&nJE_jpSUlJ z*ZSW6<`ej*i(k(^vtp_Y_%VtxI_7G5vyr#G@n}B&k+bNjjbTi^1eb#UxhxMjv#x%A z$FFe2&SDtDFv`Ls4nv4b3bGZ}K?xyD1y*OdsIY^#zvVDcLCDB|!+3!>)Zli%yKfnR3Ojk--~2Uazwsqh%?6K?tAb?&=FaNk^Pl=4 z6*g0m9YhR!i&=AgKx=RTPMENV2Kn;8U%~8Iy%;lw(m+faJ^!Mk58-p4{V+!_Jb=7k zG+3`3ZD_795S~(mQKobc@#&AhgLj{EJRP!;NVYpS4x`RDYIaaHt9keNuizbTK7p!i zb;5;a=}s{zEz=i3)Kcpb{^g1bIq%%pV8rh)nb`**;wn@8LUOhS19 zpQ5F~dX2ln4qkM`UVQb+_w(YT_oQkzd8^nkk&byfzm2`-SgyGITwZ$Y;a;GDlsagp zWl3->vr-xjyWE0}Gx_J`bDz-l7PTZ~Wc>{~w(}7xGJv7ZpG$e@)(1#W76hv`>e|nm zwL5rt#ghyVIq7)sy?1B+yglftjZu*uPMa)zFXWv$?PVGbo#uSTSd6wW;iuZk6`%a7 z%aK*UjB@dX=Xqk^8sCT^I@o|=Amgi$#1z|LYBHhC0;x%U3L``^#9Q8UGW*P_xLU#9 zpXBD+6^O9XM}uCMCs+tR3&rixDvv()B%3yGVdIv~7&UZvce7yre$3vpABjdh^NL$Y zvPvX_bm&tUcDO(|naKImg4G_ref@8EGDHVo>l{M?0V)YZN85(h##q{ZY%-?@sruX%Fp(ArVTLCqr4zEZz5Xpe= zvXV_Bb2V#wX)uM=YX{h}Wr$U)R?*#6WA0van7`j1RLmBq`TAq-(s^_<>;mdC^QF^> zrJBPl8ENg{zy9lctXaPeOBY5P9COU!9QTsHa@GW5_k3b5D+?M?A{n65Zty;~!YWfx zsSs;7b-LNS*|K`w7FMlVo3y)N{&U!Cb{{>p1{E3bfiEGz l0Hi)UZGlk7m>N>>- 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 NCo82SStel+@IQ3$R&oFU literal 0 HcmV?d00001 diff --git a/m5stack/fs/system/tab5/utils/i2c_panel_port_a.png b/m5stack/fs/system/tab5/utils/i2c_panel_port_a.png new file mode 100755 index 0000000000000000000000000000000000000000..4899259edade901e73d5067c56a6066cb94ec035 GIT binary patch literal 38983 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 NetworkConfig: nvs = esp32.NVS("uiflow") @@ -240,3 +242,39 @@ def get_ezdata_user_token(self) -> str: def reset_ezdata_user_token(self): nvs = esp32.NVS("uiflow") nvs.set_str("ustoken", "") + + def gpio_init(self, pin: int, mode: int): + """ + mode: + 0: input + 1: output + """ + self._pin_obj_map[pin] = Pin(pin, Pin.OUT if mode == 1 else Pin.IN) + + def gpio_set_level(self, pin: int, level: bool): + """ + level: + False: low + True: high + """ + if pin not in self._pin_obj_map: + return + self._pin_obj_map[pin].value(level) + + def gpio_deinit(self, pin: int): + if pin not in self._pin_obj_map: + return + del self._pin_obj_map[pin] + + def adc_init(self, pin: int): + self._adc_obj_map[pin] = ADC(Pin(pin)) + + def adc_deinit(self, pin: int): + if pin not in self._adc_obj_map: + return + del self._adc_obj_map[pin] + + def adc_read(self, pin: int) -> int: + if pin not in self._adc_obj_map: + return 0 + return self._adc_obj_map[pin].read() diff --git a/m5stack/modules/startup/tab5/launcher/apps/__init__.py b/m5stack/modules/startup/tab5/launcher/apps/__init__.py index 06fb7e67..7b106295 100644 --- a/m5stack/modules/startup/tab5/launcher/apps/__init__.py +++ b/m5stack/modules/startup/tab5/launcher/apps/__init__.py @@ -9,4 +9,6 @@ from .app_wifi_scan import AppWifiScan from .app_i2c_scan import AppI2cScan from .app_uart import AppUart +from .app_gpio import AppGpio +from .app_adc import AppAdc from .app_ezdata import AppEzdata, AppEzdataSettings diff --git a/m5stack/modules/startup/tab5/launcher/apps/app_adc.py b/m5stack/modules/startup/tab5/launcher/apps/app_adc.py new file mode 100644 index 00000000..acf8f926 --- /dev/null +++ b/m5stack/modules/startup/tab5/launcher/apps/app_adc.py @@ -0,0 +1,138 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +from .app import AppBase +from ..hal import get_hal +import lvgl as lv +import asyncio + + +class AdcPanel: + class _AdcState: + CLOSED = 0 + OPENED = 1 + + def __init__(self, parent: lv.obj, pos_x: int, pos_y: int, pins: list[int]): + self._state = self._AdcState.CLOSED + self._pins = pins + self._current_pin = 0 + + panel = lv.obj(parent) + panel.set_size(530, 200) + panel.align(lv.ALIGN.TOP_LEFT, pos_x, pos_y) + panel.set_style_border_width(0, lv.PART.MAIN) + panel.set_style_bg_opa(lv.OPA.TRANSP, lv.PART.MAIN) + panel.set_style_pad_all(0, lv.PART.MAIN) + + self._chart = lv.chart(panel) + self._chart.set_size(360, 200) + self._chart.align(lv.ALIGN.LEFT_MID, 0, 0) + self._chart.set_style_bg_color(lv.color_hex(0xFFFFFF), lv.PART.MAIN) + self._chart.set_type(lv.chart.TYPE.LINE) + self._chart.set_div_line_count(13, 22) + self._chart.set_update_mode(lv.chart.UPDATE_MODE.SHIFT) + self._chart.set_point_count(100) + self._chart.set_axis_range(lv.chart.AXIS.PRIMARY_Y, 1800, 4096) + self._chart.set_style_size(0, 0, lv.PART.INDICATOR) + + self._chart_series = self._chart.add_series( + lv.palette_main(lv.PALETTE.RED), lv.chart.AXIS.PRIMARY_Y + ) + + self._label_value = lv.label(self._chart) + self._label_value.align(lv.ALIGN.TOP_RIGHT, -5, -5) + self._label_value.set_style_text_font(lv.font_montserrat_18, lv.PART.MAIN) + self._label_value.set_style_text_color(lv.color_hex(0x6A6A6A), lv.PART.MAIN) + self._label_value.set_style_text_align(lv.TEXT_ALIGN.RIGHT, lv.PART.MAIN) + + self._btn_open = lv.button(panel) + self._btn_open.set_size(140, 48) + self._btn_open.align(lv.ALIGN.TOP_LEFT, 383, 146) + self._btn_open.set_style_shadow_width(0, lv.PART.MAIN) + self._btn_open.add_event_cb(self._handle_open_btn_clicked, lv.EVENT.CLICKED, None) + + self._label_open = lv.label(self._btn_open) + self._label_open.set_align(lv.ALIGN.CENTER) + self._label_open.set_style_text_font(lv.font_montserrat_20, lv.PART.MAIN) + + self._dd_pins = lv.dropdown(panel) + self._dd_pins.align(lv.ALIGN.TOP_LEFT, 426, 71) + self._dd_pins.set_size(97, 48) + self._dd_pins.set_style_shadow_width(0, lv.PART.MAIN) + self._dd_pins.set_style_text_font(lv.font_montserrat_20, lv.PART.MAIN) + self._dd_pins.set_style_border_width(0, lv.PART.MAIN) + self._dd_pins.set_options("\n".join([str(b) for b in self._pins])) + + label_dd_pins = lv.label(panel) + label_dd_pins.align_to(self._dd_pins, lv.ALIGN.OUT_LEFT_MID, -48, -2) + label_dd_pins.set_text("PIN:") + label_dd_pins.set_style_text_font(lv.font_montserrat_18, lv.PART.MAIN) + + self._set_state_to(self._AdcState.CLOSED) + + def _handle_open_btn_clicked(self, e: lv.event_t): + get_hal().play_click_sfx() + if self._state == self._AdcState.CLOSED: + self._set_state_to(self._AdcState.OPENED) + else: + self._set_state_to(self._AdcState.CLOSED) + + def _set_state_to(self, new_state: _AdcState): + if new_state == self._AdcState.OPENED: + # Update ui + self._btn_open.set_style_bg_color(lv.color_hex(0x1EB561), lv.PART.MAIN) + self._label_open.set_text("CLOSE") + + # Init adc + self._current_pin = self._pins[self._dd_pins.get_selected()] + get_hal().adc_init(self._current_pin) + else: + # Update ui + self._btn_open.set_style_bg_color(lv.color_hex(0x2DA4E0), lv.PART.MAIN) + self._label_open.set_text("OPEN") + + # Deinit adc + get_hal().adc_deinit(self._current_pin) + + self._state = new_state + + def update(self): + if self._state == self._AdcState.OPENED: + value = get_hal().adc_read(self._current_pin) + self._label_value.set_text(f"{value}") + self._chart.set_next_value(self._chart_series, value) + self._chart.refresh() + + def cleanup(self): + get_hal().adc_deinit(self._current_pin) + + self._label_value.delete() + self._label_value = None + self._chart_series = None + self._chart.delete() + self._chart = None + self._label_open.delete() + self._label_open = None + self._btn_open.delete() + self._btn_open = None + self._dd_pins.delete() + self._dd_pins = None + + +class AppAdc(AppBase): + async def main(self): + self._adc_panels = [] + self._adc_panels.append(AdcPanel(self.get_app_panel(), 32, 29, [54])) + self._adc_panels.append(AdcPanel(self.get_app_panel(), 586, 29, [53])) + self._adc_panels.append(AdcPanel(self.get_app_panel(), 32, 270, [50])) + self._adc_panels.append(AdcPanel(self.get_app_panel(), 586, 270, [49])) + + while True: + await asyncio.sleep_ms(500) + for panel in self._adc_panels: + panel.update() + + def on_cleanup(self): + for panel in self._adc_panels: + panel.cleanup() diff --git a/m5stack/modules/startup/tab5/launcher/apps/app_gpio.py b/m5stack/modules/startup/tab5/launcher/apps/app_gpio.py new file mode 100644 index 00000000..8d586e3d --- /dev/null +++ b/m5stack/modules/startup/tab5/launcher/apps/app_gpio.py @@ -0,0 +1,131 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +from .app import AppBase +from ..hal import * +import lvgl as lv +import asyncio + + +class OutputTestPanel: + def __init__(self, parent: lv.obj, pin: int, pos_x: int, pos_y: int): + self._pin = pin + self._is_on = False + + panel = lv.obj(parent) + panel.remove_flag(lv.obj.FLAG.SCROLLABLE) + panel.set_style_bg_color(lv.color_hex(0xE4E3E4), lv.PART.MAIN) + panel.set_style_border_width(0, lv.PART.MAIN) + panel.set_style_radius(10, lv.PART.MAIN) + panel.set_style_pad_all(0, lv.PART.MAIN) + panel.align(lv.ALIGN.TOP_MID, pos_x, pos_y) + panel.set_size(230, 62) + + label_io_num = lv.label(panel) + label_io_num.align(lv.ALIGN.CENTER, -67, 0) + label_io_num.set_style_text_font(lv.font_montserrat_24, lv.PART.MAIN) + label_io_num.set_style_text_color(lv.color_hex(0x262626), lv.PART.MAIN) + label_io_num.set_text(f"G{pin}") + + self._btn_io_toggle = lv.button(panel) + self._btn_io_toggle.align(lv.ALIGN.CENTER, 47, 0) + self._btn_io_toggle.set_style_radius(6, lv.PART.MAIN) + self._btn_io_toggle.set_style_shadow_width(0, lv.PART.MAIN) + self._btn_io_toggle.set_size(114, 42) + self._btn_io_toggle.add_event_cb(self._handle_toggle, lv.EVENT.CLICKED, None) + + self._label_btn = lv.label(self._btn_io_toggle) + self._label_btn.align(lv.ALIGN.CENTER, 0, 0) + self._label_btn.set_style_text_font(lv.font_montserrat_22, lv.PART.MAIN) + + self._update_btn_style() + + # Setup pin + get_hal().gpio_deinit(self._pin) + get_hal().gpio_init(self._pin, 1) + get_hal().gpio_set_level(self._pin, False) + + def _update_btn_style(self): + if self._is_on: + self._btn_io_toggle.set_style_bg_color(lv.color_hex(0xFF5959), lv.PART.MAIN) + self._label_btn.set_text("HIGH") + else: + self._btn_io_toggle.set_style_bg_color(lv.color_hex(0x58B358), lv.PART.MAIN) + self._label_btn.set_text("LOW") + + def _handle_toggle(self, e: lv.event_t): + self._is_on = not self._is_on + self._update_btn_style() + get_hal().gpio_set_level(self._pin, self._is_on) + + def cleanup(self): + self._btn_io_toggle.delete() + self._btn_io_toggle = None + get_hal().gpio_deinit(self._pin) + + +class ExtPortPanel: + _PINS = [49, 50, 0, 1, 54, 53] + + def __init__(self, parent: lv.obj): + panel = lv.obj(parent) + panel.set_size(273, 460) + panel.align(lv.ALIGN.CENTER, -321, 0) + panel.set_style_border_width(0, lv.PART.MAIN) + panel.set_style_bg_color(lv.color_hex(0xFFFFFF), lv.PART.MAIN) + panel.set_style_radius(10, lv.PART.MAIN) + + label_title = lv.label(panel) + label_title.align(lv.ALIGN.TOP_MID, 0, 0) + label_title.set_style_text_font(lv.font_montserrat_18, lv.PART.MAIN) + label_title.set_style_text_color(lv.color_hex(0x000000), lv.PART.MAIN) + label_title.set_text("Ext.Port1") + + self._test_panels = [] + for i, pin in enumerate(self._PINS): + self._test_panels.append(OutputTestPanel(panel, pin, 0, 40 + 81 * i)) + + def cleanup(self): + for panel in self._test_panels: + panel.cleanup() + self._test_panels = [] + + +class MbusPanel: + _PINS = [18, 19, 5, 38, 7, 3, 2, 47, 16, 17, 45, 52, 37, 6, 4, 48, 35, 51] + + def __init__(self, parent: lv.obj): + panel = lv.obj(parent) + panel.set_size(520, 460) + panel.align(lv.ALIGN.CENTER, 230, 0) + panel.set_style_border_width(0, lv.PART.MAIN) + panel.set_style_bg_color(lv.color_hex(0xFFFFFF), lv.PART.MAIN) + panel.set_style_radius(10, lv.PART.MAIN) + + label_title = lv.label(panel) + label_title.align(lv.ALIGN.TOP_MID, 0, 0) + label_title.set_style_text_font(lv.font_montserrat_18, lv.PART.MAIN) + label_title.set_style_text_color(lv.color_hex(0x000000), lv.PART.MAIN) + label_title.set_text("MBUS") + + self._test_panels = [] + for i, pin in enumerate(self._PINS): + self._test_panels.append( + OutputTestPanel(panel, pin, 124 if i > 8 else -124, 40 + 81 * (i % 9)) + ) + + def cleanup(self): + for panel in self._test_panels: + panel.cleanup() + self._test_panels = [] + + +class AppGpio(AppBase): + async def main(self): + self._panel_ext = ExtPortPanel(self.get_app_panel()) + self._panel_mbus = MbusPanel(self.get_app_panel()) + + def on_cleanup(self): + self._panel_ext.cleanup() + self._panel_mbus.cleanup() diff --git a/m5stack/modules/startup/tab5/launcher/apps/app_i2c_scan.py b/m5stack/modules/startup/tab5/launcher/apps/app_i2c_scan.py index 6d1dbefb..f6074f34 100644 --- a/m5stack/modules/startup/tab5/launcher/apps/app_i2c_scan.py +++ b/m5stack/modules/startup/tab5/launcher/apps/app_i2c_scan.py @@ -3,8 +3,7 @@ # SPDX-License-Identifier: MIT from .app import AppBase -from ..hal import * -from ..common import * +from ..hal import get_hal import lvgl as lv import asyncio @@ -14,13 +13,11 @@ async def main(self): get_hal().i2c_init() img_panel_port_a = lv.image(self.get_app_panel()) - img_panel_port_a.set_src(get_hal().get_asset_path("utils/i2c_panel_port_a" + IMAGE_SUFFIX)) + img_panel_port_a.set_src(get_hal().get_asset_path("utils/i2c_panel_port_a.png")) img_panel_port_a.align(lv.ALIGN.CENTER, -279, 34) img_panel_internal = lv.image(self.get_app_panel()) - img_panel_internal.set_src( - get_hal().get_asset_path("utils/i2c_panel_internal" + IMAGE_SUFFIX) - ) + img_panel_internal.set_src(get_hal().get_asset_path("utils/i2c_panel_internal.png")) img_panel_internal.align(lv.ALIGN.CENTER, 279, 34) btn_scan_port_a = lv.button(self.get_app_panel()) diff --git a/m5stack/modules/startup/tab5/launcher/components/app_dock.py b/m5stack/modules/startup/tab5/launcher/components/app_dock.py index 5e284f28..7666c2cf 100644 --- a/m5stack/modules/startup/tab5/launcher/components/app_dock.py +++ b/m5stack/modules/startup/tab5/launcher/components/app_dock.py @@ -9,8 +9,6 @@ class AppIcon: - _TODO_LIST = ["ADC", "GPIO", "UART", "I2CScan"] - def __init__(self, pos_x: int, pos_y: int, app_name: str, icon_name: str): self._app_name = app_name self._icon_pos_x = pos_x @@ -21,10 +19,6 @@ def __init__(self, pos_x: int, pos_y: int, app_name: str, icon_name: str): img.add_flag(lv.obj.FLAG.CLICKABLE) img.add_event_cb(self._on_clicked, lv.EVENT.CLICKED, None) - if app_name in self._TODO_LIST: - img.remove_flag(lv.obj.FLAG.CLICKABLE) - img.set_style_image_recolor_opa(123, lv.PART.MAIN) - def _on_clicked(self, e: lv.event_t): get_hal().play_click_sfx() IconIndicator.create_indicator(e.get_target_obj()) diff --git a/m5stack/modules/startup/tab5/launcher/hal.py b/m5stack/modules/startup/tab5/launcher/hal.py index dfc5a2d0..5431d514 100644 --- a/m5stack/modules/startup/tab5/launcher/hal.py +++ b/m5stack/modules/startup/tab5/launcher/hal.py @@ -129,6 +129,33 @@ def get_ezdata_user_token(self) -> str: def reset_ezdata_user_token(self): ... + def gpio_init(self, pin: int, mode: int): + """ + mode: + 0: input + 1: output + """ + ... + + def gpio_set_level(self, pin: int, level: bool): + """ + level: + False: low + True: high + """ + + def gpio_deinit(self, pin: int): + ... + + def adc_init(self, pin: int): + ... + + def adc_deinit(self, pin: int): + ... + + def adc_read(self, pin: int) -> int: + ... + _instance: HALBase = None diff --git a/m5stack/modules/startup/tab5/launcher/launcher.py b/m5stack/modules/startup/tab5/launcher/launcher.py index 153859c2..87fa744f 100644 --- a/m5stack/modules/startup/tab5/launcher/launcher.py +++ b/m5stack/modules/startup/tab5/launcher/launcher.py @@ -83,11 +83,11 @@ async def _main(self): # Install apps AppManager.install_app("Wifi", AppWifi) AppManager.install_app("AppList", AppAppList) - # AppManager.install_app("I2CScan", AppI2cScan) + AppManager.install_app("I2CScan", AppI2cScan) AppManager.install_app("WifiScan", AppWifiScan) AppManager.install_app("UART", AppUart) - AppManager.install_app("GPIO", AppDummy) - AppManager.install_app("ADC", AppDummy) + AppManager.install_app("GPIO", AppGpio) + AppManager.install_app("ADC", AppAdc) # AppManager.install_app("EzData", AppEzdata) # AppManager.install_app("EzDataSettings", AppEzdataSettings) From 1297f0580b2ccff59f9c98cb501a4b4cb7125cd9 Mon Sep 17 00:00:00 2001 From: Forairaaaaa Date: Thu, 26 Jun 2025 09:34:16 +0800 Subject: [PATCH 144/322] modules/startup/tab5/launcher/apps: Disable internal i2c scan. Signed-off-by: Forairaaaaa --- .../tab5/launcher/apps/app_i2c_scan.py | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/m5stack/modules/startup/tab5/launcher/apps/app_i2c_scan.py b/m5stack/modules/startup/tab5/launcher/apps/app_i2c_scan.py index f6074f34..bafa5c72 100644 --- a/m5stack/modules/startup/tab5/launcher/apps/app_i2c_scan.py +++ b/m5stack/modules/startup/tab5/launcher/apps/app_i2c_scan.py @@ -16,9 +16,9 @@ async def main(self): img_panel_port_a.set_src(get_hal().get_asset_path("utils/i2c_panel_port_a.png")) img_panel_port_a.align(lv.ALIGN.CENTER, -279, 34) - img_panel_internal = lv.image(self.get_app_panel()) - img_panel_internal.set_src(get_hal().get_asset_path("utils/i2c_panel_internal.png")) - img_panel_internal.align(lv.ALIGN.CENTER, 279, 34) + # img_panel_internal = lv.image(self.get_app_panel()) + # img_panel_internal.set_src(get_hal().get_asset_path("utils/i2c_panel_internal.png")) + # img_panel_internal.align(lv.ALIGN.CENTER, 279, 34) btn_scan_port_a = lv.button(self.get_app_panel()) btn_scan_port_a.set_size(260, 54) @@ -33,18 +33,18 @@ async def main(self): btn_label_port_a.set_style_text_font(lv.font_montserrat_24, lv.PART.MAIN) btn_label_port_a.set_style_text_color(lv.color_hex(0x373737), lv.PART.MAIN) - btn_scan_internal = lv.button(self.get_app_panel()) - btn_scan_internal.set_size(260, 54) - btn_scan_internal.align(lv.ALIGN.CENTER, 279, -158) - btn_scan_internal.set_style_bg_color(lv.color_hex(0xFFD733), lv.PART.MAIN) - btn_scan_internal.set_style_shadow_width(0, lv.PART.MAIN) - btn_scan_internal.add_event_cb(self._handle_scan_internal, lv.EVENT.CLICKED, None) - - btn_label_internal = lv.label(btn_scan_internal) - btn_label_internal.set_text("SCAN INTERNAL") - btn_label_internal.align(lv.ALIGN.CENTER, 0, 0) - btn_label_internal.set_style_text_font(lv.font_montserrat_24, lv.PART.MAIN) - btn_label_internal.set_style_text_color(lv.color_hex(0x373737), lv.PART.MAIN) + # btn_scan_internal = lv.button(self.get_app_panel()) + # btn_scan_internal.set_size(260, 54) + # btn_scan_internal.align(lv.ALIGN.CENTER, 279, -158) + # btn_scan_internal.set_style_bg_color(lv.color_hex(0xFFD733), lv.PART.MAIN) + # btn_scan_internal.set_style_shadow_width(0, lv.PART.MAIN) + # btn_scan_internal.add_event_cb(self._handle_scan_internal, lv.EVENT.CLICKED, None) + + # btn_label_internal = lv.label(btn_scan_internal) + # btn_label_internal.set_text("SCAN INTERNAL") + # btn_label_internal.align(lv.ALIGN.CENTER, 0, 0) + # btn_label_internal.set_style_text_font(lv.font_montserrat_24, lv.PART.MAIN) + # btn_label_internal.set_style_text_color(lv.color_hex(0x373737), lv.PART.MAIN) self._addr_labels_port_a: list[lv.label] = [] self._addr_labels_internal: list[lv.label] = [] From 933dfe2a47e843bacb267f97d7d93ebdeee9c481 Mon Sep 17 00:00:00 2001 From: lbuque Date: Wed, 28 May 2025 14:28:52 +0800 Subject: [PATCH 145/322] libs: Add m5ui support. Signed-off-by: lbuque --- docs/en/conf.py | 1 + docs/en/index.rst | 1 + docs/en/m5ui/button.rst | 412 +++++++++++++ docs/en/m5ui/index.rst | 37 ++ docs/en/m5ui/label.rst | 317 ++++++++++ docs/en/m5ui/page.rst | 193 ++++++ docs/en/refs/m5ui.button.ref | 33 + docs/en/refs/m5ui.label.ref | 32 + docs/en/refs/m5ui.page.ref | 18 + docs/en/refs/m5ui.ref | 0 docs/locales/zh_CN/LC_MESSAGES/m5ui/button.po | 569 ++++++++++++++++++ docs/locales/zh_CN/LC_MESSAGES/m5ui/index.po | 68 +++ docs/locales/zh_CN/LC_MESSAGES/m5ui/label.po | 503 ++++++++++++++++ docs/locales/zh_CN/LC_MESSAGES/m5ui/page.po | 285 +++++++++ .../button/cores3_button_event_example.m5f2 | 1 + .../button/cores3_button_event_example.py | 87 +++ .../label/cores3_scroll_label_example.m5f2 | 1 + .../m5ui/label/cores3_scroll_label_example.py | 56 ++ .../m5ui/page/cores3_page_event_example.m5f2 | 1 + .../m5ui/page/cores3_page_event_example.py | 82 +++ m5stack/boards/M5STACK_Core2/manifest.py | 1 + m5stack/boards/M5STACK_CoreS3/manifest.py | 1 + m5stack/boards/M5STACK_Dial/manifest.py | 1 + m5stack/boards/M5STACK_Tab5/manifest.py | 1 + m5stack/boards/M5STACK_Tab5/sdkconfig.esp_dl | 16 + m5stack/boards/M5STACK_Tough/manifest.py | 1 + m5stack/libs/lv_utils/__init__.py | 62 +- m5stack/libs/m5ui/__init__.py | 20 + m5stack/libs/m5ui/base.py | 90 +++ m5stack/libs/m5ui/button.py | 171 ++++++ m5stack/libs/m5ui/label.py | 113 ++++ m5stack/libs/m5ui/manifest.py | 10 + m5stack/libs/m5ui/page.py | 54 ++ m5stack/libs/m5ui/port.py | 80 +++ 34 files changed, 3317 insertions(+), 1 deletion(-) create mode 100644 docs/en/m5ui/button.rst create mode 100644 docs/en/m5ui/index.rst create mode 100644 docs/en/m5ui/label.rst create mode 100644 docs/en/m5ui/page.rst create mode 100644 docs/en/refs/m5ui.button.ref create mode 100644 docs/en/refs/m5ui.label.ref create mode 100644 docs/en/refs/m5ui.page.ref create mode 100644 docs/en/refs/m5ui.ref create mode 100644 docs/locales/zh_CN/LC_MESSAGES/m5ui/button.po create mode 100644 docs/locales/zh_CN/LC_MESSAGES/m5ui/index.po create mode 100644 docs/locales/zh_CN/LC_MESSAGES/m5ui/label.po create mode 100644 docs/locales/zh_CN/LC_MESSAGES/m5ui/page.po create mode 100644 examples/m5ui/button/cores3_button_event_example.m5f2 create mode 100644 examples/m5ui/button/cores3_button_event_example.py create mode 100644 examples/m5ui/label/cores3_scroll_label_example.m5f2 create mode 100644 examples/m5ui/label/cores3_scroll_label_example.py create mode 100644 examples/m5ui/page/cores3_page_event_example.m5f2 create mode 100644 examples/m5ui/page/cores3_page_event_example.py create mode 100644 m5stack/boards/M5STACK_Tab5/sdkconfig.esp_dl create mode 100644 m5stack/libs/m5ui/__init__.py create mode 100644 m5stack/libs/m5ui/base.py create mode 100644 m5stack/libs/m5ui/button.py create mode 100644 m5stack/libs/m5ui/label.py create mode 100644 m5stack/libs/m5ui/manifest.py create mode 100644 m5stack/libs/m5ui/page.py create mode 100644 m5stack/libs/m5ui/port.py diff --git a/docs/en/conf.py b/docs/en/conf.py index e70f6c49..03160a65 100644 --- a/docs/en/conf.py +++ b/docs/en/conf.py @@ -59,6 +59,7 @@ "utime", "rui3", "m5audio2", + "lvgl", ] autodoc_default_options = { diff --git a/docs/en/index.rst b/docs/en/index.rst index fc21bfc5..108f0e86 100644 --- a/docs/en/index.rst +++ b/docs/en/index.rst @@ -8,6 +8,7 @@ UiFlow2 documentation and references library/index.rst controllers/index.rst system/index.rst + m5ui/index.rst widgets/index.rst software/index.rst hardware/index.rst diff --git a/docs/en/m5ui/button.rst b/docs/en/m5ui/button.rst new file mode 100644 index 00000000..9b10e209 --- /dev/null +++ b/docs/en/m5ui/button.rst @@ -0,0 +1,412 @@ +.. currentmodule:: m5ui + +M5Button +======== + +.. include:: ../refs/m5ui.button.ref + +M5Button is a widget that can be used to create buttons in the user interface. It can be used to trigger actions when clicked. + +UiFlow2 Example +--------------- + +event button +^^^^^^^^^^^^ + +Open the |cores3_button_event_example.m5f2| project in UiFlow2. + +This example creates a button that triggers an event when clicked. + +UiFlow2 Code Block: + + |cores3_button_event_example.png| + +Example output: + + None + + +MicroPython Example +------------------- + +event button +^^^^^^^^^^^^ + +This example creates a button that triggers an event when clicked. + +MicroPython Code Block: + + .. literalinclude:: ../../../examples/m5ui/button/cores3_button_event_example.py + :language: python + :linenos: + +Example output: + + None + + +**API** +------- + +M5Button +^^^^^^^^ + +.. autoclass:: m5ui.button.M5Button + :members: + + .. py:method:: set_flag(flag, value) + + Set a flag on the object. If ``value`` is True, the flag is added; if False, the flag is removed. + + :param int flag: The flag to set. + :param bool value: If True, the flag is added; if False, the flag is removed. + :return: None + + UiFlow2 Code Block: + + |set_flag.png| + + MicroPython Code Block: + + .. code-block:: python + + button_0.set_flag(lv.obj.FLAG.HIDDEN, True) + + + .. py:method:: toggle_flag(flag) + + Toggle a flag on the object. If the flag is set, it is removed; if not set, it is added. + + :param int flag: The flag to toggle. + :return: None + + UiFlow2 Code Block: + + |toggle_flag.png| + + MicroPython Code Block: + + .. code-block:: python + + button_0.toggle_flag(lv.obj.FLAG.HIDDEN) + + + .. py:method:: set_state(state, value) + + Set the state of the button. If ``value`` is True, the state is set; if False, the state is unset. + + :param int state: The state to set. + :param bool value: If True, the state is set; if False, the state is unset. + :return: None + + UiFlow2 Code Block: + + |set_state.png| + + MicroPython Code Block: + + .. code-block:: python + + button_0.set_state(lv.STATE.PRESSED, True) + + + .. py:method:: toggle_state(state) + + Toggle the state of the button. If the state is set, it is unset; if not set, it is set. + + :param int state: The state to toggle. + :return: None + + UiFlow2 Code Block: + + |toggle_state.png| + + MicroPython Code Block: + + .. code-block:: python + + button_0.toggle_state(lv.STATE.PRESSED) + + .. py:method:: set_style_text_font(font, part) + + Set the font of the button text. + + :param lv.lv_font_t font: The font to set. + :param int part: The part of the object to apply the style to (e.g., lv.PART.MAIN). + :return: None + + UiFlow2 Code Block: + + |set_style_text_font.png| + + MicroPython Code Block: + + .. code-block:: python + + button_0.set_style_text_font(lv.font_montserrat_14, lv.PART.MAIN | lv.STATE.DEFAULT) + + + .. py:method:: set_text_color(color, opa, part) + + Set the color of the button text. + + :param int color: The color to set. + :param int opa: The opacity of the color. The value should be between 0 (transparent) and 255 (opaque). + :param int part: The part of the object to apply the style to (e.g., lv.PART.MAIN). + :return: None + + UiFlow2 Code Block: + + |set_text_color_pressed.png| + + |set_text_color_released.png| + + MicroPython Code Block: + + .. code-block:: python + + button_0.set_text_color(lv.color_hex(0x000000), 255, lv.PART.MAIN | lv.STATE.DEFAULT) + button_0.set_text_color(lv.color_hex(0x000000), 255, lv.PART.MAIN | lv.STATE.PRESSED) + + .. py:method:: set_bg_color(color, opa, part) + + Set the background color of the button. + + :param int color: The color to set. + :param int opa: The opacity of the color. The value should be between 0 (transparent) and 255 (opaque). + :param int part: The part of the object to apply the style to (e.g., lv.PART.MAIN). + :return: None + + UiFlow2 Code Block: + + |set_bg_color_pressed.png| + + |set_bg_color_released.png| + + MicroPython Code Block: + + .. code-block:: python + + button_0.set_bg_color(lv.color_hex(0x000000), 255, lv.PART.MAIN | lv.STATE.DEFAULT) + button_0.set_bg_color(lv.color_hex(0x000000), 255, lv.PART.MAIN | lv.STATE.PRESSED) + + .. py:method:: set_pos(x, y) + + Set the position of the button. + + :param int x: The x-coordinate of the button. + :param int y: The y-coordinate of the button. + :return: None + + UiFlow2 Code Block: + + |set_pos.png| + + MicroPython Code Block: + + .. code-block:: python + + button_0.set_pos(100, 100) + + .. py:method:: set_x(x) + + Set the x-coordinate of the button. + + :param int x: The x-coordinate of the button. + :return: None + + UiFlow2 Code Block: + + |set_x.png| + + MicroPython Code Block: + + .. code-block:: python + + button_0.set_x(100) + + .. py:method:: set_y(y) + + Set the y-coordinate of the button. + + :param int y: The y-coordinate of the button. + :return: None + + UiFlow2 Code Block: + + |set_y.png| + + MicroPython Code Block: + + .. code-block:: python + + button_0.set_y(100) + + .. py:method:: set_size(width, height) + + Set the size of the button. + + :param int width: The width of the button. + :param int height: The height of the button. + :return: None + + UiFlow2 Code Block: + + |set_size.png| + + MicroPython Code Block: + + .. code-block:: python + + button_0.set_size(100, 50) + + .. py:method:: set_width(width) + + Set the width of the button. + + :param int width: The width of the button. + :return: None + + UiFlow2 Code Block: + + |set_width.png| + + MicroPython Code Block: + + .. code-block:: python + + button_0.set_width(100) + + .. py::method:: get_width() + + Get the width of the button. + + :return: The width of the button. + :rtype: int + + UiFlow2 Code Block: + + |get_width.png| + + MicroPython Code Block: + + .. code-block:: python + + button_0.get_width() + + .. py:method:: set_height(height) + + Set the height of the button. + + :param int height: The height of the button. + :return: None + + UiFlow2 Code Block: + + |set_height.png| + + MicroPython Code Block: + + .. code-block:: python + + button_0.set_height(50) + + .. py::method:: get_height() + + Get the height of the button. + + :return: The height of the button. + :rtype: int + + UiFlow2 Code Block: + + |get_height.png| + + MicroPython Code Block: + + .. code-block:: python + + button_0.get_height() + + .. py:method:: align_to(obj, align, x, y) + + Align the button to another object. + + :param lv.obj obj: The object to align to. + :param int align: The alignment type. + :param int x: The x-offset from the aligned object. + :param int y: The y-offset from the aligned object. + :return: None + + UiFlow2 Code Block: + + |align_to.png| + + MicroPython Code Block: + + .. code-block:: python + + button_0.align_to(page_0, lv.ALIGN.CENTER, 0, 0) + + .. py:method:: set_style_radius(radius, part) + + Set the corner radius of the button. + + :param int radius: The radius to set. + :param int part: The part of the object to apply the style to (e.g., lv.PART.MAIN). + :return: None + + UiFlow2 Code Block: + + |set_style_radius.png| + + MicroPython Code Block: + + .. code-block:: python + + button_0.set_style_radius(10, lv.PART.MAIN | lv.STATE.DEFAULT) + + + .. py:method:: add_event_cb(handler, event, user_data) + + Add an event callback to the button. The callback will be called when the specified event occurs. + + :param function handler: The callback function to call. + :param int event: The event to listen for. + :param Any user_data: Optional user data to pass to the callback. + :return: None + + UiFlow2 Code Block: + + |event.png| + + MicroPython Code Block: + + .. code-block:: python + + def button_0_pressed_event(event_struct): + global button_0 + button_0.set_bg_color(0x000000, 255, 0) + + def button_0_released_event(event_struct): + global button_0 + button_0.set_bg_color(0xffffff, 255, 0) + + def button_0_clicked_event(event_struct): + global button_0 + button_0.set_bg_color(0x000000, 255, 0) + + def button_0_event_handler(event_struct): + global button_0 + event = event_struct.code + if event == lv.EVENT.PRESSED and True: + button_0_pressed_event(event_struct) + if event == lv.EVENT.RELEASED and True: + button_0_released_event(event_struct) + if event == lv.EVENT.CLICKED and True: + button_0_clicked_event(event_struct) + if event == lv.EVENT.LONG_PRESSED and True: + button_0_long_pressed_event(event_struct) + return + + page_0.add_event_cb(button_0_event_handler, lv.EVENT.ALL, None) diff --git a/docs/en/m5ui/index.rst b/docs/en/m5ui/index.rst new file mode 100644 index 00000000..fe7b2283 --- /dev/null +++ b/docs/en/m5ui/index.rst @@ -0,0 +1,37 @@ +M5UI +==== + +.. module:: m5ui + :synopsis: A UI library based on LVGL v9.3 + +M5UI is a UI library based on LVGL v9.3. It provides a set of widgets and functions to create user interfaces for M5Stack devices. + +It has been adapted for M5Stack devices and you only need to call ``m5ui.init()`` to start using it. + + +Functions +--------- + +.. function:: m5ui.init() + + Initialize the M5UI library. This function must be called before using any other M5UI functions. + + :return: None + + +.. function:: m5ui.deinit() + + Deinitialize the M5UI library. This function should be called when you no longer need to use M5UI. + + :return: None + + +Classes +------- + +.. toctree:: + :maxdepth: 1 + + page.rst + label.rst + button.rst diff --git a/docs/en/m5ui/label.rst b/docs/en/m5ui/label.rst new file mode 100644 index 00000000..f39f146c --- /dev/null +++ b/docs/en/m5ui/label.rst @@ -0,0 +1,317 @@ +.. currentmodule:: m5ui + +N5Label +======= + +.. include:: ../refs/m5ui.label.ref + +M5Label is a widget that can be used to create labels in the user interface. It can display text and can be styled with different fonts, colors, and sizes. + + +UiFlow2 Example +--------------- + +scroll label +^^^^^^^^^^^^ + +Open the |cores3_scroll_label_example.m5f2| project in UiFlow2. + +This example demonstrates how to create a label that scrolls text in a circular manner. + +UiFlow2 Code Block: + + |cores3_scroll_label_example.png| + +Example output: + + None + + +MicroPython Example +------------------- + +scroll label +^^^^^^^^^^^^ + +This example demonstrates how to create a label that scrolls text in a circular manner. + +MicroPython Code Block: + + .. literalinclude:: ../../../examples/m5ui/label/cores3_scroll_label_example.py + :language: python + :linenos: + +Example output: + + None + + +**API** +------- + +M5Label +^^^^^^^^ + +.. autoclass:: m5ui.label.M5Label + :members: + + .. py:method:: set_flag(flag, value) + + Set a flag on the object. If ``value`` is True, the flag is added; if False, the flag is removed. + + :param int flag: The flag to set. + :param bool value: If True, the flag is added; if False, the flag is removed. + :return: None + + UiFlow2 Code Block: + + |set_flag.png| + + MicroPython Code Block: + + .. code-block:: python + + label_0.set_flag(lv.obj.FLAG.HIDDEN, True) + + .. py:method:: toggle_flag(flag) + + Toggle a flag on the object. If the flag is set, it is removed; if not set, it is added. + + :param int flag: The flag to toggle. + :return: None + + UiFlow2 Code Block: + + |toggle_flag.png| + + MicroPython Code Block: + + .. code-block:: python + + label_0.toggle_flag(lv.obj.FLAG.HIDDEN) + + .. py:method:: set_state(state, value) + + Set the state of the label. If ``value`` is True, the state is set; if False, the state is unset. + + :param int state: The state to set. + :param bool value: If True, the state is set; if False, the state is unset. + :return: None + + UiFlow2 Code Block: + + |set_state.png| + + MicroPython Code Block: + + .. code-block:: python + + label_0.set_state(lv.STATE.PRESSED, True) + + .. py:method:: toggle_state(state) + + Toggle the state of the label. If the state is set, it is unset; if not set, it is set. + + :param int state: The state to toggle. + :return: None + + UiFlow2 Code Block: + + |toggle_state.png| + + MicroPython Code Block: + + .. code-block:: python + + label_0.toggle_state(lv.STATE.PRESSED) + + .. py:method:: set_style_text_font(font, part) + + Set the font of the label text. + + :param lv.lv_font_t font: The font to set. + :param int part: The part of the object to apply the style to (e.g., lv.PART.MAIN). + :return: None + + UiFlow2 Code Block: + + |set_style_text_font.png| + + MicroPython Code Block: + + .. code-block:: python + + label_0.set_style_text_font(lv.font_montserrat_14, lv.PART.MAIN | lv.STATE.DEFAULT) + + + .. py:method:: set_text_color(color, opa, part) + + Set the color of the text. + + :param int color: The color to set. + :param int opa: The opacity of the color. + :param int part: The part of the object to apply the style to (e.g., lv.PART.MAIN). + :return: None + + UiFlow2 Code Block: + + |set_text_color.png| + + MicroPython Code Block: + + .. code-block:: python + + label_0.set_text_color(lv.color_hex(0x000000), 255, lv.PART.MAIN | lv.STATE.DEFAULT) + + + .. py:method:: set_bg_color(color, opa, part) + + Set the background color of the label. + + :param int color: The color to set. + :param int opa: The opacity of the color. + :param int part: The part of the object to apply the style to (e.g., lv.PART.MAIN). + :return: None + + UiFlow2 Code Block: + + |set_text_color.png| + + MicroPython Code Block: + + .. code-block:: python + + label_0.set_bg_color(lv.color_hex(0x000000), 255, lv.PART.MAIN | lv.STATE.DEFAULT) + + + .. py:method:: set_pos(x, y) + + Set the position of the label. + + :param int x: The x-coordinate of the label. + :param int y: The y-coordinate of the label. + :return: None + + UiFlow2 Code Block: + + |set_pos.png| + + MicroPython Code Block: + + .. code-block:: python + + label_0.set_pos(100, 100) + + + .. py:method:: set_x(x) + + Set the x-coordinate of the label. + + :param int x: The x-coordinate of the label. + :return: None + + UiFlow2 Code Block: + + |set_x.png| + + MicroPython Code Block: + + .. code-block:: python + + label_0.set_x(100) + + + .. py:method:: set_y(y) + + Set the y-coordinate of the label. + + :param int y: The y-coordinate of the label. + :return: None + + UiFlow2 Code Block: + + |set_y.png| + + MicroPython Code Block: + + .. code-block:: python + + label_0.set_y(100) + + + .. py:method:: set_size(width, height) + + Set the size of the label. + + :param int width: The width of the label. + :param int height: The height of the label. + :return: None + + UiFlow2 Code Block: + + |set_size.png| + + MicroPython Code Block: + + .. code-block:: python + + label_0.set_size(100, 50) + + + .. py:method:: set_width(width) + + Set the width of the label. + + :param int width: The width of the label. + :return: None + + UiFlow2 Code Block: + + |set_width.png| + + |set_width1.png| + + MicroPython Code Block: + + .. code-block:: python + + label_0.set_width(100) + + + .. py::method:: get_width() + + Get the width of the label. + + :return: The width of the label. + :rtype: int + + UiFlow2 Code Block: + + |get_width.png| + + MicroPython Code Block: + + .. code-block:: python + + label_0.get_width() + + + .. py:method:: align_to(obj, align, x, y) + + Align the label to another object. + + :param lv.obj obj: The object to align to. + :param int align: The alignment type. + :param int x: The x-offset from the aligned object. + :param int y: The y-offset from the aligned object. + :return: None + + UiFlow2 Code Block: + + |align_to.png| + + MicroPython Code Block: + + .. code-block:: python + + label_0.align_to(page_0, lv.ALIGN.CENTER, 0, 0) diff --git a/docs/en/m5ui/page.rst b/docs/en/m5ui/page.rst new file mode 100644 index 00000000..f03e1915 --- /dev/null +++ b/docs/en/m5ui/page.rst @@ -0,0 +1,193 @@ +.. currentmodule:: m5ui + +M5Page +====== + +.. include:: ../refs/m5ui.page.ref + +M5Page is a widget that can be used to create pages in the user interface. It can be used to organize other widgets and provide navigation between different pages. + +UiFlow2 Example +--------------- + +page event +^^^^^^^^^^ + +Open the |cores3_page_event_example.m5f2| project in UiFlow2. + +When you press and hold the screen, the screen background color turns black. When you release the screen, the background color returns to white. + +UiFlow2 Code Block: + + |cores3_page_event_example.png| + +Example output: + + None + + +MicroPython Example +------------------- + +page event +^^^^^^^^^^ + +When you press and hold the screen, the screen background color turns black. When you release the screen, the background color returns to white. + +MicroPython Code Block: + + .. literalinclude:: ../../../examples/m5ui/page/cores3_page_event_example.py + :language: python + :linenos: + +Example output: + + None + + +**API** +------- + +M5Button +^^^^^^^^ + +.. autoclass:: m5ui.page.M5Page + :members: + + .. py:method:: set_flag(flag, value) + + Set a flag on the object. If ``value`` is True, the flag is added; if False, the flag is removed. + + :param int flag: The flag to set. + :param bool value: If True, the flag is added; if False, the flag is removed. + :return: None + + UiFlow2 Code Block: + + |set_flag.png| + + MicroPython Code Block: + + .. code-block:: python + + page_0.set_flag(lv.obj.FLAG.HIDDEN, True) + + + .. py:method:: toggle_flag(flag) + + Toggle a flag on the object. If the flag is set, it is removed; if not set, it is added. + + :param int flag: The flag to toggle. + :return: None + + UiFlow2 Code Block: + + |toggle_flag.png| + + MicroPython Code Block: + + .. code-block:: python + + page_0.toggle_flag(lv.obj.FLAG.HIDDEN) + + + .. py:method:: set_state(state, value) + + Set the state of the page. If ``value`` is True, the state is set; if False, the state is unset. + + :param int state: The state to set. + :param bool value: If True, the state is set; if False, the state is unset. + :return: None + + UiFlow2 Code Block: + + |set_state.png| + + MicroPython Code Block: + + .. code-block:: python + + page_0.set_state(lv.STATE.PRESSED, True) + + + .. py:method:: toggle_state(state) + + Toggle the state of the page. If the state is set, it is unset; if not set, it is set. + + :param int state: The state to toggle. + :return: None + + UiFlow2 Code Block: + + |toggle_state.png| + + MicroPython Code Block: + + .. code-block:: python + + page_0.toggle_state(lv.STATE.PRESSED) + + + .. py:method:: set_bg_color(color, opa, part) + + Set the background color of the page. + + :param int color: The color to set. + :param int opa: The opacity of the color. + :param int part: The part of the object to apply the style to (e.g., lv.PART.MAIN). + :return: None + + UiFlow2 Code Block: + + |set_bg_color.png| + + MicroPython Code Block: + + .. code-block:: python + + page_0.set_bg_color(lv.color_hex(0x000000), 255, lv.PART.MAIN | lv.STATE.DEFAULT) + + + .. py:method:: add_event_cb(handler, event, user_data) + + Add an event callback to the page. The callback will be called when the specified event occurs. + + :param function handler: The callback function to call. + :param int event: The event to listen for. + :param Any user_data: Optional user data to pass to the callback. + :return: None + + UiFlow2 Code Block: + + |event.png| + + MicroPython Code Block: + + .. code-block:: python + + def page0_pressed_event(event_struct): + global page0 + page0.set_bg_color(0x000000, 255, 0) + + def page0_released_event(event_struct): + global page0 + page0.set_bg_color(0xffffff, 255, 0) + + def page0_clicked_event(event_struct): + global page0 + page0.set_bg_color(0x000000, 255, 0) + + def page0_event_handler(event_struct): + global page0 + event = event_struct.code + if event == lv.EVENT.PRESSED and True: + page0_pressed_event(event_struct) + if event == lv.EVENT.RELEASED and True: + page0_released_event(event_struct) + if event == lv.EVENT.CLICKED and True: + page0_clicked_event(event_struct) + if event == lv.EVENT.LONG_PRESSED and True: + page0_long_pressed_event(event_struct) + return + + page_0.add_event_cb(page0_event_handler, lv.EVENT.ALL, None) diff --git a/docs/en/refs/m5ui.button.ref b/docs/en/refs/m5ui.button.ref new file mode 100644 index 00000000..80ea2b91 --- /dev/null +++ b/docs/en/refs/m5ui.button.ref @@ -0,0 +1,33 @@ +.. |cores3_button_event_example.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/button/cores3_button_event_example.png + +.. |align_to.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/button/align_to.png +.. |event.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/button/event.png +.. |get_btn_text.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/button/get_btn_text.png +.. |get_height.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/button/get_height.png +.. |get_width.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/button/get_width.png +.. |set_bg_color_pressed.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/button/set_bg_color_pressed.png +.. |set_bg_color_released.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/button/set_bg_color_released.png +.. |set_btn_text.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/button/set_btn_text.png +.. |set_flag.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/button/set_flag.png +.. |set_height.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/button/set_height.png +.. |set_pos.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/button/set_pos.png +.. |set_size.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/button/set_size.png +.. |set_state.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/button/set_state.png +.. |set_style_radius.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/button/set_style_radius.png +.. |set_style_text_font.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/button/set_style_text_font.png +.. |set_text_color_pressed.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/button/set_text_color_pressed.png +.. |set_text_color_released.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/button/set_text_color_released.png +.. |set_width.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/button/set_width.png +.. |set_x.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/button/set_x.png +.. |set_y.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/button/set_y.png +.. |toggle_flag.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/button/toggle_flag.png +.. |toggle_state.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/button/toggle_state.png + +.. |cores3_button_event_example.m5f2| raw:: html + + + cores3_button_event_example.m5f2 + diff --git a/docs/en/refs/m5ui.label.ref b/docs/en/refs/m5ui.label.ref new file mode 100644 index 00000000..407c2c86 --- /dev/null +++ b/docs/en/refs/m5ui.label.ref @@ -0,0 +1,32 @@ +.. |cores3_scroll_label_example.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/label/cores3_scroll_label_example.png + +.. |align_to.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/label/align_to.png +.. |event.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/label/event.png +.. |get_text.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/label/get_text.png +.. |get_width.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/label/get_width.png +.. |set_bg_color.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/label/set_bg_color.png +.. |set_flag.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/label/set_flag.png +.. |set_long_mode.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/label/set_long_mode.png +.. |set_pos.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/label/set_pos.png +.. |set_shadow.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/label/set_shadow.png +.. |set_size.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/label/set_size.png +.. |set_state.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/label/set_state.png +.. |set_style_text_font.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/label/set_style_text_font.png +.. |set_text.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/label/set_text.png +.. |set_text_color.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/label/set_text_color.png +.. |set_width.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/label/set_width.png +.. |set_width1.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/label/set_width1.png +.. |set_x.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/label/set_x.png +.. |set_y.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/label/set_y.png +.. |toggle_flag.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/label/toggle_flag.png +.. |toggle_state.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/label/toggle_state.png +.. |unset_shadow.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/label/unset_shadow.png + +.. |cores3_scroll_label_example.m5f2| raw:: html + + + cores3_scroll_label_example.m5f2 + diff --git a/docs/en/refs/m5ui.page.ref b/docs/en/refs/m5ui.page.ref new file mode 100644 index 00000000..4d03ef44 --- /dev/null +++ b/docs/en/refs/m5ui.page.ref @@ -0,0 +1,18 @@ +.. |cores3_page_event_example.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/page/cores3_page_event_example.png + +.. |event.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/page/event.png +.. |screen_load.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/page/screen_load.png +.. |set_bg_color.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/page/set_bg_color.png +.. |set_flag.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/page/set_flag.png +.. |set_state.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/page/set_state.png +.. |toggle_flag.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/page/toggle_flag.png +.. |toggle_state.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/page/toggle_state.png + +.. |cores3_page_event_example.m5f2| raw:: html + + + cores3_page_event_example.m5f2 + diff --git a/docs/en/refs/m5ui.ref b/docs/en/refs/m5ui.ref new file mode 100644 index 00000000..e69de29b diff --git a/docs/locales/zh_CN/LC_MESSAGES/m5ui/button.po b/docs/locales/zh_CN/LC_MESSAGES/m5ui/button.po new file mode 100644 index 00000000..3da234c6 --- /dev/null +++ b/docs/locales/zh_CN/LC_MESSAGES/m5ui/button.po @@ -0,0 +1,569 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2016 - 2025 M5Stack Technology Co., Ltd +# This file is distributed under the same license as the UIFlow2 Programming +# Guide package. +# FIRST AUTHOR , 2025. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: UIFlow2 Programming Guide \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-06-26 16:19+0800\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: zh_CN\n" +"Language-Team: zh_CN \n" +"Plural-Forms: nplurals=1; plural=0;\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.16.0\n" + +#: ../../en/m5ui/button.rst:4 ../../en/m5ui/button.rst:52 +#: a1a70aefa3aa4548bdb0f5616050fb18 c5e9821cd18547a38177f96c0645d4ba +msgid "M5Button" +msgstr "" + +#: ../../en/m5ui/button.rst:8 82e97f75b3bb4b1db3ed7a6de9e61ab7 +msgid "" +"M5Button is a widget that can be used to create buttons in the user " +"interface. It can be used to trigger actions when clicked." +msgstr "M5Button 是一个可用于在用户界面中创建按钮的小部件。点击按钮时,按钮会触发相应的操作。" + +#: ../../en/m5ui/button.rst:11 ffb9e2ee33f749cbac6b2b348ee935ce +msgid "UiFlow2 Example" +msgstr "UiFlow2 应用示例" + +#: ../../en/m5ui/button.rst:14 ../../en/m5ui/button.rst:33 +#: 23976169f7e64d0faad5501e9da689c7 8464b5f4e387418f92ea22d3c0ca7b40 +msgid "event button" +msgstr "事件按钮" + +#: ../../en/m5ui/button.rst:16 717ca8dfee5f4d8fb871c4e7aa954628 +msgid "Open the |cores3_button_event_example.m5f2| project in UiFlow2." +msgstr "在 UiFlow2 中打开 |cores3_button_event_example.m5f2| 项目。" + +#: ../../en/m5ui/button.rst:18 ../../en/m5ui/button.rst:35 +#: 8bcd10aa56874f2d98e1081ea44563ae a2cf7cebe0da46608a7dafc4211af79d +msgid "This example creates a button that triggers an event when clicked." +msgstr "此示例创建一个按钮,单击时会触发一个事件。" + +#: ../../en/m5ui/button.rst:20 ../../en/m5ui/button.rst:65 +#: ../../en/m5ui/button.rst:83 ../../en/m5ui/button.rst:102 +#: ../../en/m5ui/button.rst:120 ../../en/m5ui/button.rst:138 +#: ../../en/m5ui/button.rst:158 ../../en/m5ui/button.rst:180 +#: ../../en/m5ui/button.rst:201 ../../en/m5ui/button.rst:218 +#: ../../en/m5ui/button.rst:235 ../../en/m5ui/button.rst:253 +#: ../../en/m5ui/button.rst:270 ../../en/m5ui/button.rst:304 +#: ../../en/m5ui/button.rst:341 ../../en/m5ui/button.rst:359 +#: ../../en/m5ui/button.rst:379 0dc440ec59f14beb891aa579c9505c5d +#: 197935c2dc444898bfcefa93e1d8d5f2 1df927cd4d444b318262a31993650e86 +#: 313d109908884e3c835bdcec82228b0a 349d5e22f3344d24a924e007867f8a4b +#: 56ed4363e4d74cffb8d3fb3f579e367a 67c97c03e8fb40669cc097a464aa3418 +#: 703776c23d124ddbb85d813273d98ae0 72c9a18c993b40c78aba320b5bcdbd4b +#: 747b523933434309b68edd7ddc41bd4d 87f3daf6235546929075b990a2f0ba46 +#: 9ab916eb98384c9ea2e0ba642c10cb5c b938750a63bc4855be5d7b060c1490ee +#: bbaa412065ef4987bdcaf690f713c3a5 d611fd71b0b048cb83617e569131fc61 +#: d6f16d65a0674f18be7bc31cc737c52a d99901a0e091447cb14f9c32453c86bb +#: dca9950b352f47469ba02ee266990417 f52754e5e5a048ac97de807934cd1627 +#: m5ui.button.M5Button:13 m5ui.button.M5Button.get_btn_text:6 +#: m5ui.button.M5Button.set_btn_text:5 of +msgid "UiFlow2 Code Block:" +msgstr "UiFlow2 代码块:" + +#: ../../en/m5ui/button.rst:22 639475443b5142c5b28e25f954602709 +msgid "|cores3_button_event_example.png|" +msgstr "" + +#: ../../en/refs/m5ui.button.ref:1 e5b9df1d2486488fb95dcacd2a3e8c50 +msgid "cores3_button_event_example.png" +msgstr "" + +#: ../../en/m5ui/button.rst:24 ../../en/m5ui/button.rst:43 +#: 2313c1e553c8469795534d09490c5fb1 9be948fef26e4dfca222f81daf81e627 +msgid "Example output:" +msgstr "示例输出:" + +#: ../../en/m5ui/button.rst:26 ../../en/m5ui/button.rst:45 +#: ../../en/m5ui/button.rst:63 ../../en/m5ui/button.rst:81 +#: ../../en/m5ui/button.rst:100 ../../en/m5ui/button.rst:118 +#: ../../en/m5ui/button.rst:136 ../../en/m5ui/button.rst:156 +#: ../../en/m5ui/button.rst:178 ../../en/m5ui/button.rst:199 +#: ../../en/m5ui/button.rst:216 ../../en/m5ui/button.rst:233 +#: ../../en/m5ui/button.rst:251 ../../en/m5ui/button.rst:268 +#: ../../en/m5ui/button.rst:302 ../../en/m5ui/button.rst:339 +#: ../../en/m5ui/button.rst:357 ../../en/m5ui/button.rst:377 +#: 00e4561109fc4ea7937e57566beb0311 30a338c776904db78dd3d717e4795f0d +#: 359e514942004dad88e9543204ecf0eb 465d07fc0045466f91156570c0f1510c +#: 4cfb3964c6424e62bd4b16bb9a3f8936 5753f3f832074a31a6a4fa614785516e +#: 625da44470344fedb8e782b73f9eadfb 78d7de006fa5484aa820b5c4cfa5d344 +#: a1c9bd61d34643f1a145b7031001207f a9cde579092d482db5bf703c00ea6d1b +#: b609aead1a2444d4b8a34da5b3a613af b94eac021202476ea5553eafb454f384 +#: bc859a7f7e774cfdace1cb57f85ff29e db9871df146a41948cbf0cd2cfbe3050 +#: dfd6df46a54d4ae4bb9d2d8cb73fb2e0 e5470428099245e9bf1493a7202fbceb +#: ecb138449e4d40db92c93467a66adcbe f573e57589b14df9b3e2ef9729306b5b +#: fd718ca10db348a49132ae732aedc3fa m5ui.button.M5Button:15 of +msgid "None" +msgstr "" + +#: ../../en/m5ui/button.rst:30 74336c63f45941d79bbbfd8d2da4b349 +msgid "MicroPython Example" +msgstr "MicroPython 应用示例" + +#: ../../en/m5ui/button.rst:37 ../../en/m5ui/button.rst:69 +#: ../../en/m5ui/button.rst:87 ../../en/m5ui/button.rst:106 +#: ../../en/m5ui/button.rst:124 ../../en/m5ui/button.rst:142 +#: ../../en/m5ui/button.rst:164 ../../en/m5ui/button.rst:186 +#: ../../en/m5ui/button.rst:205 ../../en/m5ui/button.rst:222 +#: ../../en/m5ui/button.rst:239 ../../en/m5ui/button.rst:257 +#: ../../en/m5ui/button.rst:274 ../../en/m5ui/button.rst:308 +#: ../../en/m5ui/button.rst:345 ../../en/m5ui/button.rst:363 +#: ../../en/m5ui/button.rst:383 0670910428b446c9bd5a283265b2be3c +#: 11bf950dbd3b4258a39eb8912cdd5a91 175067d4d5754768ae4958a8c546455e +#: 1a02244ba0d34a439ebf04273aeb6d8e 2666425e87d84fada9729ca6256d56b0 +#: 276a6e3826bb49d9aa377de6a7904974 2945232885f74034a2ed1f7babbd0189 +#: 463eee67714049ac99c7b4c9d1d70e9d 479e1965844f4b928c0fc097dd672f76 +#: 4f3d84dbe9e4420d8123b7bd6d24ee28 595fb815aead4451a196c799020d6930 +#: 599c725eba1140d6a16aedb16d66f5b2 62f036f1030b43a7baa2f7d690eaf6ba +#: 83af701806834d1eae2e57737ddd2f27 8d3ebec8662a4bed9ffab48a56b79baa +#: 995e0b2215594eedb04e68daf08868c2 a29828924ad14a8ca87e4b873b69e1b1 +#: ad945cbb1e324e3697140c526d47b6d2 fbf8ad19e399439d937c212226ed13f3 +#: m5ui.button.M5Button:17 m5ui.button.M5Button.get_btn_text:12 +#: m5ui.button.M5Button.set_btn_text:9 of +msgid "MicroPython Code Block:" +msgstr "MicroPython 代码块:" + +#: ../../en/m5ui/button.rst:49 3bf910125da547409d0b15f7dce1e895 +msgid "**API**" +msgstr "API参考" + +#: 7b886feab5714d109c2f2eca6f30a796 m5ui.button.M5Button:1 of +msgid "Bases: :py:class:`~lvgl.button`" +msgstr "" + +#: b8b7af14941444b38ceadbe0ec4096d1 m5ui.button.M5Button:1 of +msgid "Create a button object." +msgstr "创建一个按钮对象。" + +#: ../../en/m5ui/button.rst 012ee65c6c724b228691f95787e9d9d2 +#: 156a17e69c9d484ea1cf7b3019a796b0 15e999769c6347fc901b9a138f971bab +#: 1786a82d855448c2a52a8ae49145f7a4 26f12a3f65fe4f82abe0e3f189a5d431 +#: 39af93c4cd4645b9ae2a48cbd2a3d319 44085ea9739b48e69e6f670ef82d9dad +#: 58cde2cea94541d2bf6093fefc083c2c 6ba385b79a9c4ac2bfcb6717ecc41879 +#: 74b1dcf957ac4bb1a7e563a5bff9ea2d a065cb85294e43d9bb0ff542ec9669c3 +#: a9e7971164274fcca9fe53c3c0dc6522 b41ebb1f9b4441e3a4305b454f164407 +#: bd5c2cb703b249a9a01a57ba2aed6dd0 dfaaa47a4c79433089506f6c8ea16abc +#: e8bbee70b17f4d17b942ffbff0fdb596 fae9bdea63a64021a487e57d65d0f5f7 +#: fd10aca938854bd39003fe78d480d13f m5ui.button.M5Button.set_btn_text of +msgid "Parameters" +msgstr "" + +#: 81666252c52f4fee887f7c0b5e4529d3 m5ui.button.M5Button:3 of +msgid "The text to display on the button." +msgstr "按钮上显示的文本。" + +#: 5cad1c9eece649969612d1b2ee78d695 m5ui.button.M5Button:4 of +msgid "The x position of the button." +msgstr "The x position of the button." + +#: fc551b92b7954cef9db6a1979496d3b9 m5ui.button.M5Button:5 of +msgid "The y position of the button." +msgstr "按钮的 y 位置。" + +#: b0e55d3de04d43e3bd782d2f25110c70 m5ui.button.M5Button:6 of +msgid "" +"The width of the button. when set to 0, the button will automatically " +"size to fit the text." +msgstr "按钮的宽度。设置为 0 时,按钮将自动调整大小以适合文本。" + +#: 0b26c44db1c94cba98e1c7fb5d9072ef m5ui.button.M5Button:7 of +msgid "" +"The height of the button. when set to 0, the button will automatically " +"size to fit the text." +msgstr "按钮的高度。设置为 0 时,按钮将自动调整大小以适应文本。" + +#: c2d792e368be4d839d85b93965988072 m5ui.button.M5Button:8 of +msgid "The background color of the button in hexadecimal format." +msgstr "按钮的背景颜色(十六进制格式)。" + +#: 7b8e4c3f127b48a8928d5e628a585f54 m5ui.button.M5Button:9 of +msgid "The text color of the button in hexadecimal format." +msgstr "按钮的文本颜色(十六进制格式)。" + +#: 547b0d43a95d4d27bac74cf65e774636 m5ui.button.M5Button:10 of +msgid "The font to use for the button text." +msgstr "按钮文本使用的字体。" + +#: 87b46bc04d6e458d9d23873e2303d0d3 m5ui.button.M5Button:11 of +msgid "" +"The parent object to attach the button to. If not specified, the button " +"will be attached to the default screen." +msgstr "按钮所附加到的父对象。如果未指定,则按钮将附加到默认屏幕。" + +#: ../../en/m5ui/button.rst:59 d7c96aeb26f74622a6052e478c7216fd +msgid "" +"Set a flag on the object. If ``value`` is True, the flag is added; if " +"False, the flag is removed." +msgstr "在对象上设置一个标志。如果 `value` 为 True,则添加该标志;如果 `value` 为 False,则移除该标志。" + +#: ../../en/m5ui/button.rst:61 0660f085382746c1903584bc2d187bcb +msgid "The flag to set." +msgstr "要设置的标志。" + +#: ../../en/m5ui/button.rst:62 7acae1b001a8488294889dd1852b418d +msgid "If True, the flag is added; if False, the flag is removed." +msgstr "如果为 True,则添加标志;如果为 False,则删除标志。" + +#: ../../en/m5ui/button.rst 051f384e5cda4303a48d0ee7b372751e +#: 3904659e573b4225a7706a27d2bd5577 3a75ce63c58144d8920d70950fa7d155 +#: 456086960b9a4216b9af5b2cd8949bc0 5f8c91cd32ad45bcb2dc55db15c8792c +#: 6270af545a524f2bb76c4ce282b068c7 6cc76afb12e54035a57cdc65c43d259c +#: 75b1cf87b3e241abbd780a07eeb79b78 84ec2eb454544a619e72c2e4da4775ff +#: 87cdb40744504777816d5c4718a179be aef24970dfbb419a81e68b94eafcca09 +#: b9fce0eecbe94d22aa4749ba4e33364e cc90462e99cc44be8269c09a6c04fb1a +#: e3de31d7176c440581bd199e00c9b471 f26c15b9c4584f14a4b4cc5f279a153e +#: fa9e5e93b99d495293be2e637538c8a8 fb7d273e64014d668b5436dc4cc1051f +msgid "Returns" +msgstr "" + +#: ../../en/m5ui/button.rst:67 c0726966c37545f7bd45e5e5ae24130d +msgid "|set_flag.png|" +msgstr "" + +#: ../../en/refs/m5ui.button.ref:11 d17a9547b9494c2cb665c7e08de69e35 +msgid "set_flag.png" +msgstr "" + +#: ../../en/m5ui/button.rst:78 81194da1e9ef4cd39a95d874c09475f5 +msgid "" +"Toggle a flag on the object. If the flag is set, it is removed; if not " +"set, it is added." +msgstr "切换对象上的标志。如果设置了标志,则移除该标志;如果未设置,则添加该标志。" + +#: ../../en/m5ui/button.rst:80 b43c27e01e1d44688caa9010cfc967e3 +msgid "The flag to toggle." +msgstr "要切换的标志。" + +#: ../../en/m5ui/button.rst:85 e8ef28d6f2324158a6f4d63b287d4306 +msgid "|toggle_flag.png|" +msgstr "" + +#: ../../en/refs/m5ui.button.ref:23 c469e757ef7d48a08e3b2c7521f2da67 +msgid "toggle_flag.png" +msgstr "" + +#: ../../en/m5ui/button.rst:96 02143614da6f41cdb9dc00ce6256c9b1 +msgid "" +"Set the state of the button. If ``value`` is True, the state is set; if " +"False, the state is unset." +msgstr "设置按钮的状态。如果 ``value`` 为 True,则设置状态;如果为 False,则取消设置状态。" + +#: ../../en/m5ui/button.rst:98 3326bbee72894fc4a557cce717bc8fa2 +msgid "The state to set." +msgstr "要设置的状态。" + +#: ../../en/m5ui/button.rst:99 244b2165f45c4961af4c1ae06cb20f15 +msgid "If True, the state is set; if False, the state is unset." +msgstr "如果为 True,则状态已设置;如果为 False,则状态未设置。" + +#: ../../en/m5ui/button.rst:104 8a058ed79e6049b8b969300a768ed8f8 +msgid "|set_state.png|" +msgstr "" + +#: ../../en/refs/m5ui.button.ref:15 8a99b86e96174b59a6dd41b4bade8ed2 +msgid "set_state.png" +msgstr "" + +#: ../../en/m5ui/button.rst:115 39229c1a528e4a6cbcd143c07be98b6a +msgid "" +"Toggle the state of the button. If the state is set, it is unset; if not " +"set, it is set." +msgstr "切换按钮的状态。如果按钮状态已设置,则表示未设置;如果按钮状态未设置,则表示已设置。" + +#: ../../en/m5ui/button.rst:117 c63c2aaabfaf4ec19fd7652ddd334413 +msgid "The state to toggle." +msgstr "要切换的状态。" + +#: ../../en/m5ui/button.rst:122 4e2124e902504f9ba7d53dd280144dd6 +msgid "|toggle_state.png|" +msgstr "" + +#: ../../en/refs/m5ui.button.ref:24 ee794217482f477da481690a5bb68979 +msgid "toggle_state.png" +msgstr "" + +#: ../../en/m5ui/button.rst:132 60ac633511c54fd995fd09b8fd6d8e47 +msgid "Set the font of the button text." +msgstr "设置按钮文本的字体。" + +#: ../../en/m5ui/button.rst:134 1acf306ba9894ba79f3a9af889fe7008 +msgid "The font to set." +msgstr "要设置的字体。" + +#: ../../en/m5ui/button.rst:135 ../../en/m5ui/button.rst:155 +#: ../../en/m5ui/button.rst:177 ../../en/m5ui/button.rst:356 +#: 2bd36f1f054f480c9073ef53fc4ce87e 2e6861f5dc5a4d3c8ba311937d7db531 +#: a6e90522c59e46c0b146e60aee863a80 f2425375db5f480488568ceacb639a7c +msgid "The part of the object to apply the style to (e.g., lv.PART.MAIN)." +msgstr "应用样式的对象部分(例如,lv.PART.MAIN)。" + +#: ../../en/m5ui/button.rst:140 f8448d78fc944457938d2d45ee43d349 +msgid "|set_style_text_font.png|" +msgstr "" + +#: ../../en/refs/m5ui.button.ref:17 2200ed1264044fb6868852631d8e67c0 +msgid "set_style_text_font.png" +msgstr "" + +#: ../../en/m5ui/button.rst:151 b7195d411319496c9940ed5ed04c8e52 +msgid "Set the color of the button text." +msgstr "设置按钮文本的颜色。" + +#: ../../en/m5ui/button.rst:153 ../../en/m5ui/button.rst:175 +#: 0e1e1f8b143e411697f82d53cf9325c0 586497afb2294cde95fc3fa14084e7c3 +msgid "The color to set." +msgstr "要设置的颜色。" + +#: ../../en/m5ui/button.rst:154 ../../en/m5ui/button.rst:176 +#: ba641cc694c744f99da1730c0368d697 c83c2cd7cf9f4f7dbd840be39e3a4d2d +msgid "" +"The opacity of the color. The value should be between 0 (transparent) and" +" 255 (opaque)." +msgstr "颜色的不透明度。该值应介于 0(透明)和 255(不透明)之间。" + +#: ../../en/m5ui/button.rst:160 c417e9d9642743868839603552831021 +msgid "|set_text_color_pressed.png|" +msgstr "" + +#: ../../en/refs/m5ui.button.ref:18 4d12ecd2c5464571a00a032b300729dc +msgid "set_text_color_pressed.png" +msgstr "" + +#: ../../en/m5ui/button.rst:162 cb67d04c82a8457a91dbf47a17b932a0 +msgid "|set_text_color_released.png|" +msgstr "" + +#: ../../en/refs/m5ui.button.ref:19 deb145d5db714d3583ea6f05f54f8849 +msgid "set_text_color_released.png" +msgstr "" + +#: ../../en/m5ui/button.rst:173 30c1c2a1bbc44a49aaf3b828d6d7a97d +msgid "Set the background color of the button." +msgstr "设置按钮的背景颜色。" + +#: ../../en/m5ui/button.rst:182 c089c7446e0741e89d051c0a30763ad7 +msgid "|set_bg_color_pressed.png|" +msgstr "" + +#: ../../en/refs/m5ui.button.ref:8 fcfbf67092df4b80832b3131625eb75d +msgid "set_bg_color_pressed.png" +msgstr "" + +#: ../../en/m5ui/button.rst:184 22078a55970c4715873fe072adc3efde +msgid "|set_bg_color_released.png|" +msgstr "" + +#: ../../en/refs/m5ui.button.ref:9 d94388fb81434a80bdc05fe20dbd5ddc +msgid "set_bg_color_released.png" +msgstr "" + +#: ../../en/m5ui/button.rst:195 44682d499e254d88bd2c756604d4a627 +msgid "Set the position of the button." +msgstr "设置按钮的位置。" + +#: ../../en/m5ui/button.rst:197 ../../en/m5ui/button.rst:215 +#: 2768436ac58c4cc59c4136338e30e1d5 f8a25699f6e64bb0892b089b6418d6e0 +msgid "The x-coordinate of the button." +msgstr "按钮的 x 坐标。" + +#: ../../en/m5ui/button.rst:198 ../../en/m5ui/button.rst:232 +#: 7cf802297da14990883533d9c3228482 9737a8f44c9847af8141eb81206ebb1b +msgid "The y-coordinate of the button." +msgstr "按钮的 y 坐标。" + +#: ../../en/m5ui/button.rst:203 e9fb0f2db76f400c9cfbf8b976e84791 +msgid "|set_pos.png|" +msgstr "" + +#: ../../en/refs/m5ui.button.ref:13 6d9b967e8aaa46f9ab61266abf05a065 +msgid "set_pos.png" +msgstr "" + +#: ../../en/m5ui/button.rst:213 50e2881128984d53bc2d8b9506b48fbc +msgid "Set the x-coordinate of the button." +msgstr "设置按钮的 x 坐标。" + +#: ../../en/m5ui/button.rst:220 6f17adb3165a450ea2e8641f63e18d4f +msgid "|set_x.png|" +msgstr "" + +#: ../../en/refs/m5ui.button.ref:21 8f7be6e9d0934522a6c82f6df2638bda +msgid "set_x.png" +msgstr "" + +#: ../../en/m5ui/button.rst:230 3f7bb75f90424a7380b8877aede6792d +msgid "Set the y-coordinate of the button." +msgstr "设置按钮的 y 坐标。”" + +#: ../../en/m5ui/button.rst:237 469d929a1a6d417c9f9c39a90c7dd22c +msgid "|set_y.png|" +msgstr "" + +#: ../../en/refs/m5ui.button.ref:22 329cb206ce32435ba7ef2482dcb1025a +msgid "set_y.png" +msgstr "" + +#: ../../en/m5ui/button.rst:247 86dcdc2a97ed43e9a46106efbd1fc831 +msgid "Set the size of the button." +msgstr "设置按钮的大小。" + +#: ../../en/m5ui/button.rst:249 ../../en/m5ui/button.rst:267 +#: 58e2f0aa8e5649e7a36838e003e0438a a4507ccccb284c84b3fe60dd269588dd +msgid "The width of the button." +msgstr "按钮的宽度。" + +#: ../../en/m5ui/button.rst:250 ../../en/m5ui/button.rst:301 +#: 5d562607827241bda5805a43ada3650e d5a1c1174c65470698c2726033875337 +msgid "The height of the button." +msgstr "按钮的高度。" + +#: ../../en/m5ui/button.rst:255 41581ca8d2f7445fa194847b6d67acf5 +msgid "|set_size.png|" +msgstr "" + +#: ../../en/refs/m5ui.button.ref:14 9b8510023ab64842896fb22e8ebabb48 +msgid "set_size.png" +msgstr "" + +#: ../../en/m5ui/button.rst:265 49ca5fbdb9dd44e6bc2fa67220a73bb9 +msgid "Set the width of the button." +msgstr "设置按钮的宽度。" + +#: ../../en/m5ui/button.rst:272 842abfa55f334801a93c747994c03d1a +msgid "|set_width.png|" +msgstr "" + +#: ../../en/refs/m5ui.button.ref:20 0ada190cec3f4e1b9444aec75e08333a +msgid "set_width.png" +msgstr "" + +#: ../../en/m5ui/button.rst:299 393c7465a4f946dabe590f46ae0e5c5d +msgid "Set the height of the button." +msgstr "设置按钮的高度。" + +#: ../../en/m5ui/button.rst:306 3f97f7e554ea428b8889a6bea75cd583 +msgid "|set_height.png|" +msgstr "" + +#: ../../en/refs/m5ui.button.ref:12 0fc9d1e12768450291611cb7d21c4f8d +msgid "set_height.png" +msgstr "" + +#: ../../en/m5ui/button.rst:333 7fb47f222c494949a4b3cf30aa64a200 +msgid "Align the button to another object." +msgstr "将按钮与另一个对象对齐。" + +#: ../../en/m5ui/button.rst:335 46fb6b3be0bf48ca866900591a8cfb1a +msgid "The object to align to." +msgstr "要对齐的对象。" + +#: ../../en/m5ui/button.rst:336 78ccfbe7b56a4cd9818eb3cd53acf926 +msgid "The alignment type." +msgstr "对齐类型。" + +#: ../../en/m5ui/button.rst:337 03567407cd654e6baa62ead1812a2d5a +msgid "The x-offset from the aligned object." +msgstr "与对齐对象的 x 偏移量。" + +#: ../../en/m5ui/button.rst:338 56fe4996bc9a46a7942a17d30a99e6df +msgid "The y-offset from the aligned object." +msgstr "与对齐对象的 y 偏移。" + +#: ../../en/m5ui/button.rst:343 d3e7e5ae70874571bdc82d5d84d30047 +msgid "|align_to.png|" +msgstr "" + +#: ../../en/refs/m5ui.button.ref:3 1c5ab6c302bc4bdd87323b503e7e5e85 +msgid "align_to.png" +msgstr "" + +#: ../../en/m5ui/button.rst:353 3ae12d54daca4f23a2bc1d5a4fe8fce5 +msgid "Set the corner radius of the button." +msgstr "设置按钮的角半径。" + +#: ../../en/m5ui/button.rst:355 60889a8d5e944fceb6192fc2d2face24 +msgid "The radius to set." +msgstr "要设置的半径。" + +#: ../../en/m5ui/button.rst:361 36c93735cdf54a28881e4feca6b5337f +msgid "|set_style_radius.png|" +msgstr "" + +#: ../../en/refs/m5ui.button.ref:16 27d8a1f3c91c457ca73a2046919daba9 +msgid "set_style_radius.png" +msgstr "" + +#: ../../en/m5ui/button.rst:372 77ad3810ede349e5bbb2251a07b12ca5 +msgid "" +"Add an event callback to the button. The callback will be called when the" +" specified event occurs." +msgstr "为按钮添加事件回调。当指定事件发生时,将调用该回调。" + +#: ../../en/m5ui/button.rst:374 271f6e94cb234120bcc3118f66c77dec +msgid "The callback function to call." +msgstr "要调用的回调函数。" + +#: ../../en/m5ui/button.rst:375 0d8cb9b64a754e32b6866a01652649e9 +msgid "The event to listen for." +msgstr "要监听的事件。" + +#: ../../en/m5ui/button.rst:376 962cf79bb8104da593ca2dee98ecdee5 +msgid "Optional user data to pass to the callback." +msgstr "传递给回调的可选用户数据。" + +#: ../../en/m5ui/button.rst:381 8a6a140be76a4cab9045d02e459eb541 +msgid "|event.png|" +msgstr "" + +#: ../../en/refs/m5ui.button.ref:4 597b9d102a344e048223563a980e7eb6 +msgid "event.png" +msgstr "" + +#: b9ad3060048942169511989af6d36798 m5ui.button.M5Button.get_btn_text:1 of +msgid "Get the text of the button." +msgstr "获取按钮的文本。" + +#: 9ef5eaad217c4a5891645d417e4ff650 m5ui.button.M5Button.get_btn_text:3 of +msgid "The text of the button." +msgstr "按钮的文本。" + +#: ../../en/m5ui/button.rst aef24970dfbb419a81e68b94eafcca09 +#: m5ui.button.M5Button.set_btn_text of +msgid "Return type" +msgstr "" + +#: 0b813624c377489e98ae5d9a2fc050fa m5ui.button.M5Button.get_btn_text:8 of +msgid "|get_btn_text.png|" +msgstr "" + +#: ../../en/refs/m5ui.button.ref:5 83f745316ba74d4391291b5850ffaae2 +msgid "get_btn_text.png" +msgstr "" + +#: 9ef5eaad217c4a5891645d417e4ff650 m5ui.button.M5Button.set_btn_text:1 of +msgid "Set the text of the button." +msgstr "设置按钮的文本。" + +#: 93db7e740d96458c875e6c40e9859304 m5ui.button.M5Button.set_btn_text:3 of +msgid "The text to set on the button." +msgstr "要在按钮上设置的文本。" + +#: fdf448472bb748e69a1a2ee87e61843a m5ui.button.M5Button.set_btn_text:7 of +msgid "|set_btn_text.png|" +msgstr "" + +#: ../../en/refs/m5ui.button.ref:10 3c847044033c4fc797ac84f6f1197955 +msgid "set_btn_text.png" +msgstr "" + diff --git a/docs/locales/zh_CN/LC_MESSAGES/m5ui/index.po b/docs/locales/zh_CN/LC_MESSAGES/m5ui/index.po new file mode 100644 index 00000000..f7271690 --- /dev/null +++ b/docs/locales/zh_CN/LC_MESSAGES/m5ui/index.po @@ -0,0 +1,68 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2016 - 2025 M5Stack Technology Co., Ltd +# This file is distributed under the same license as the UIFlow2 Programming +# Guide package. +# FIRST AUTHOR , 2025. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: UIFlow2 Programming Guide \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-06-26 15:06+0800\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: zh_CN\n" +"Language-Team: zh_CN \n" +"Plural-Forms: nplurals=1; plural=0;\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.16.0\n" + +#: ../../en/m5ui/index.rst:2 7cf56d7cd22e4f0d9fc5892bc9653652 +msgid "M5UI" +msgstr "" + +#: ../../en/m5ui/index.rst:7 1f54d93f6547485685ad7c4ee3f758e9 +msgid "" +"M5UI is a UI library based on LVGL v9.3. It provides a set of widgets and" +" functions to create user interfaces for M5Stack devices." +msgstr "M5UI 是一个基于 LVGL v9.3 的 UI 库。它提供了一组用于创建 M5Stack 设备用户界面的小部件和函数。" + +#: ../../en/m5ui/index.rst:9 f0ff465507ef443dbcc943023f445901 +msgid "" +"It has been adapted for M5Stack devices and you only need to call " +"``m5ui.init()`` to start using it." +msgstr "它已适用于 M5Stack 设备,您只需调用 ``m5ui.init()`` 即可开始使用它。" + +#: ../../en/m5ui/index.rst:13 9f83572a079f4c05bb00b4f02644e266 +msgid "Functions" +msgstr "" + +#: ../../en/m5ui/index.rst:17 c4264d75fb94489e9097c8ffba561275 +msgid "" +"Initialize the M5UI library. This function must be called before using " +"any other M5UI functions." +msgstr "初始化 M5UI 库。使用任何其他 M5UI 函数之前必须调用此函数" + +#: ../../en/m5ui/index.rst 1d8bffeebb3847e3890345a8514b8880 +#: 5997afce9da645fbb58bd18819ff4269 +msgid "Returns" +msgstr "" + +#: ../../en/m5ui/index.rst:19 ../../en/m5ui/index.rst:26 +#: 3efafbfbb7e04b799b65b12c8861fb5a 422bba168c324167b20ff975239d9c1a +msgid "None" +msgstr "" + +#: ../../en/m5ui/index.rst:24 9fca89b6e5bf47c1966b794858aa4abd +msgid "" +"Deinitialize the M5UI library. This function should be called when you no" +" longer need to use M5UI." +msgstr "取消初始化 M5UI 库。当您不再需要使用 M5UI 时,应调用此函数。" + +#: ../../en/m5ui/index.rst:30 2101ab814a3146218b046b976e577481 +msgid "Classes" +msgstr "" + diff --git a/docs/locales/zh_CN/LC_MESSAGES/m5ui/label.po b/docs/locales/zh_CN/LC_MESSAGES/m5ui/label.po new file mode 100644 index 00000000..3fa8fdee --- /dev/null +++ b/docs/locales/zh_CN/LC_MESSAGES/m5ui/label.po @@ -0,0 +1,503 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2016 - 2025 M5Stack Technology Co., Ltd +# This file is distributed under the same license as the UIFlow2 Programming +# Guide package. +# FIRST AUTHOR , 2025. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: UIFlow2 Programming Guide \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-06-26 16:19+0800\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: zh_CN\n" +"Language-Team: zh_CN \n" +"Plural-Forms: nplurals=1; plural=0;\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.16.0\n" + +#: ../../en/m5ui/label.rst:4 8b659a8bce9246ef9187c22235ea8eff +msgid "N5Label" +msgstr "" + +#: ../../en/m5ui/label.rst:8 b31fb5aa3870484a9531fbbf243821dc +msgid "" +"M5Label is a widget that can be used to create labels in the user " +"interface. It can display text and can be styled with different fonts, " +"colors, and sizes." +msgstr "M5Label 是一个可用于在用户界面中创建标签的小部件。它可以显示文本,并可以使用不同的字体、颜色和大小进行样式设置。" + +#: ../../en/m5ui/label.rst:12 cadecff3920c41c0bc4d1a1e03a35f74 +msgid "UiFlow2 Example" +msgstr "UiFlow2 应用示例" + +#: ../../en/m5ui/label.rst:15 ../../en/m5ui/label.rst:34 +#: 3127ef8a8a874f6fbe713e36be5180b2 4a431834462140c095f3ea588ad39a74 +msgid "scroll label" +msgstr "滚动标签" + +#: ../../en/m5ui/label.rst:17 396ade604fb14775a4b0f0f579b5661e +msgid "Open the |cores3_scroll_label_example.m5f2| project in UiFlow2." +msgstr "在 UiFlow2 中打开 |cores3_scroll_label_example.m5f2| 项目。" + +#: ../../en/m5ui/label.rst:19 ../../en/m5ui/label.rst:36 +#: 5a72173892474ff5badc285d332c658e 63298ada59c148e08583271c139a32bf +msgid "" +"This example demonstrates how to create a label that scrolls text in a " +"circular manner." +msgstr "此示例演示如何创建以循环方式滚动文本的标签。" + +#: ../../en/m5ui/label.rst:21 ../../en/m5ui/label.rst:66 +#: ../../en/m5ui/label.rst:83 ../../en/m5ui/label.rst:101 +#: ../../en/m5ui/label.rst:118 ../../en/m5ui/label.rst:136 +#: ../../en/m5ui/label.rst:156 ../../en/m5ui/label.rst:176 +#: ../../en/m5ui/label.rst:195 ../../en/m5ui/label.rst:213 +#: ../../en/m5ui/label.rst:231 ../../en/m5ui/label.rst:250 +#: ../../en/m5ui/label.rst:268 ../../en/m5ui/label.rst:309 +#: 02a1b2cd91ce4ceb8f2440942be8c8fd 1e32f52d56a8427aa5fdbdf49ccdc24c +#: 282c1978f5d546ccb873b841206f8e2b 2b0805ac60524ee099ed606bbad12541 +#: 7b638c6168b2405688eba1ac2c1d234f 8487ef7504a04190b8ccee24b969adfb +#: 88cfaae41b4444a1aaa28a212caab13a 894511c55e8045ddb004bcb69655ca92 +#: 8eaa1b1963764294b38bc710ca6a817e 96c2f030b56d451c9c079676d28aa9d4 +#: a4c994b6eadb4ff6b179ce78711b6b04 a8dc89b8746146db8d0041f484ffdc43 +#: ad1d5e8f84b24e938d38a54e3bb54d00 b36047682400440f8a174afd65b3380d +#: b7c78f23f6ff4cd5a5c0a731f3f44db6 db84251bd3ae4ce5acacc073e8e34d48 +#: ec3e6c62f38e4f3eb0784d55c30f7799 m5ui.label.M5Label:12 +#: m5ui.label.M5Label.set_shadow:10 m5ui.label.M5Label.unset_shadow:3 of +msgid "UiFlow2 Code Block:" +msgstr "UiFlow2 代码块:" + +#: ../../en/m5ui/label.rst:23 bef77983b8d0438387998eeb337a4d6f +msgid "|cores3_scroll_label_example.png|" +msgstr "" + +#: ../../en/refs/m5ui.label.ref:1 e3ec00a2a47149eeba1ae40bb9a6cf60 +msgid "cores3_scroll_label_example.png" +msgstr "" + +#: ../../en/m5ui/label.rst:25 ../../en/m5ui/label.rst:44 +#: 25f04a6b398046c1b9cb8509f933982f 63f2e30e5661454e9043085901c8b2a9 +msgid "Example output:" +msgstr "示例输出:" + +#: ../../en/m5ui/label.rst:27 ../../en/m5ui/label.rst:46 +#: ../../en/m5ui/label.rst:64 ../../en/m5ui/label.rst:81 +#: ../../en/m5ui/label.rst:99 ../../en/m5ui/label.rst:116 +#: ../../en/m5ui/label.rst:134 ../../en/m5ui/label.rst:154 +#: ../../en/m5ui/label.rst:174 ../../en/m5ui/label.rst:193 +#: ../../en/m5ui/label.rst:211 ../../en/m5ui/label.rst:229 +#: ../../en/m5ui/label.rst:248 ../../en/m5ui/label.rst:266 +#: ../../en/m5ui/label.rst:307 03e90f2fe8454860aafb13502d6a0355 +#: 0cce67cbad7941a5a9c5f92c8cd4308a 18ed67d824794195a118f95369501219 +#: 22ea1e2fb58e478f8c0b60dbc4189b4f 3fef67191abe4c30b6c202a4c8210634 +#: 537103503371452a9d34a1efdf620d5e 54d93b9dafee409d958099bb8ccf7ff5 +#: 6fcf90bb8541407a95834aac0dd92ecd 78e2bd1684d24598ae5a802c22851e9e +#: 80289f7b943940198fb83f98d31fc632 9119488df6234ac8a796f6dcfe6de136 +#: 9889f36d45614f7cbcd7017a325450b4 bd912e8f58e04672bdbed79064dbc13e +#: da6867096e584291ac66fe14d954ea32 dbd4da74d8da4d1bb31efba4de43d4c7 +#: dfb8eac9875849fcac152977e073f564 f42afb98c8364b7983f0877ae7f49807 +#: m5ui.label.M5Label:14 m5ui.label.M5Label.set_shadow:8 of +msgid "None" +msgstr "" + +#: ../../en/m5ui/label.rst:31 09e36183e5da48e2a6ccada0ed7dd0be +msgid "MicroPython Example" +msgstr "MicroPython 应用示例" + +#: ../../en/m5ui/label.rst:38 ../../en/m5ui/label.rst:70 +#: ../../en/m5ui/label.rst:87 ../../en/m5ui/label.rst:105 +#: ../../en/m5ui/label.rst:122 ../../en/m5ui/label.rst:140 +#: ../../en/m5ui/label.rst:160 ../../en/m5ui/label.rst:180 +#: ../../en/m5ui/label.rst:199 ../../en/m5ui/label.rst:217 +#: ../../en/m5ui/label.rst:235 ../../en/m5ui/label.rst:254 +#: ../../en/m5ui/label.rst:274 ../../en/m5ui/label.rst:313 +#: 00e42519be614cb8bb87130e087cb606 063873f4b18449b1ac613fcb9ffa2b95 +#: 08b0e650b4c147e6a664e3f126a52173 0a5c82ba67ef4e3da43a6b204a29edd1 +#: 24218454642a4b8e9d0736a69b58a5ce 347f6962b7a94b02b1c39e27023ba96d +#: 37d7638d7da441e69c735f335892448b 4940bfe7715e41b8aeba9674dc0863a5 +#: 55dac99c323f4394b060ec4167a91ac8 68a4982c22274dabbc1de1c36ba44d58 +#: 7b4c83fa524744ea9beb5de2fdea31cd 8a21ce73103043deb77e8e692ce7c555 +#: a1f2b32b22cc408eb8007dc2b1727e5e a5bbf49f033142c593a0c876da1455cb +#: bcac3191a88f4b659c663005fabdf472 c9c46dd0be364f60b1a011a34ff9cea1 +#: d11b2f30641b470bb0e91f08b5bc40e4 m5ui.label.M5Label:16 +#: m5ui.label.M5Label.set_shadow:14 m5ui.label.M5Label.unset_shadow:7 of +msgid "MicroPython Code Block:" +msgstr "MicroPython 代码块:" + +#: ../../en/m5ui/label.rst:50 3db2d1d60dff4ba1a14dde2cb7026442 +msgid "**API**" +msgstr "API参考" + +#: ../../en/m5ui/label.rst:53 a7fc49a82ed94331a4a37b74bb845f5a +msgid "M5Label" +msgstr "" + +#: 4aed3d9ba67b4a45a89ba049a7a1f4de m5ui.label.M5Label:1 of +msgid "Bases: :py:class:`~lvgl.label`" +msgstr "" + +#: 1ecec18d6f0d4a9d8f5135e8ad3e3cfe m5ui.label.M5Label:1 of +msgid "Create a label object." +msgstr "创建标签对象。" + +#: ../../en/m5ui/label.rst 0d51798b448d41dcb4c2d5c2341a7728 +#: 161189e4cc4c4153a7f52b41dde1f8fb 383590fe3ebe48b5b8c56c0992f71a6e +#: 6d82484925f84796a70919700158cc6a 6deedd0da1c243fa80ce84cfb8086342 +#: 71376a52fbd84ee8877d4151fd0a0b7d 96a641f7da164c5796837898a73bce59 +#: 99f070739d014ce4a5b7bb7746c1d922 b2f3b6902a994d2d9f1f031be48bd350 +#: b483a32b59ea4daeb3debd0b14bf6b2e b87eca9923634ddcb546cafb5a1fab05 +#: c5fe20fe83be43f28591f16f4651d965 d26de94acc5145078db742b39d12d1c6 +#: f5fd38bf153d4b1a8ee5bc4b434f2d85 f871049b38c84e05845ff027cec498cc +#: m5ui.label.M5Label.set_shadow of +msgid "Parameters" +msgstr "" + +#: 05c90064dad0456aaba45d8cde951631 m5ui.label.M5Label:3 of +msgid "The text to display on the label." +msgstr "标签上显示的文本。" + +#: ace2ae1e1e92492abee189f57f39e8ee m5ui.label.M5Label:4 of +msgid "The x position of the label." +msgstr "标签的 x 位置。" + +#: be89188e73844634a40bf31b1000a11e m5ui.label.M5Label:5 of +msgid "The y position of the label." +msgstr "标签的 y 位置。" + +#: f96cdec781f444b9b122379cf9c9097f m5ui.label.M5Label:6 of +msgid "The text color of the label in hexadecimal format." +msgstr "标签的文本颜色(十六进制格式)。" + +#: b8420b1ece624041bd10f85e16e80fd1 m5ui.label.M5Label:7 of +msgid "The background color of the label in hexadecimal format." +msgstr "标签的背景颜色(十六进制格式)。" + +#: fb2b69cfce31499397c5aea1478751ff m5ui.label.M5Label:8 of +msgid "The background opacity of the label (0-255)." +msgstr "标签的背景不透明度(0-255)。" + +#: 51457fd0b0d64bdbae5ff2c67d0f8211 m5ui.label.M5Label:9 of +msgid "The font to use for the button text." +msgstr "按钮文本使用的字体。" + +#: 62a3724259804778b5218fb039c759e7 m5ui.label.M5Label:10 of +msgid "" +"The parent object to attach the button to. If not specified, the button " +"will be attached to the default screen." +msgstr "按钮要附加到的父对象。如果未指定,按钮将附加到默认屏幕。" + +#: ../../en/m5ui/label.rst:60 5ab71ea553c74a73baa09b3147f62982 +msgid "" +"Set a flag on the object. If ``value`` is True, the flag is added; if " +"False, the flag is removed." +msgstr "在对象上设置一个标志。如果 ``value`` 为 True,则添加该标志;如果为 False,则移除该标志。" + +#: ../../en/m5ui/label.rst:62 e078ef2c8d394bc8816b715b2d463fad +msgid "The flag to set." +msgstr "要设置的标志。" + +#: ../../en/m5ui/label.rst:63 039282c2bfc34b708eb95de89d001c58 +msgid "If True, the flag is added; if False, the flag is removed." +msgstr "如果为 True,则添加标志;如果为 False,则删除标志。" + +#: ../../en/m5ui/label.rst 167fded2d00f49758d5b036f401faa86 +#: 231b44a91b55431d8133cdff806fc354 252973756bb94138bcac50781648b388 +#: 5234ecefb0804d389f1eb5424f8a3996 5ffd014b312044e5a6bb183f900dedf5 +#: 6f4141e18382436f982f9228e6b5ae98 7aedd1da44fa418a9293ab6e5656cee3 +#: 859d49000ae34028b09ecee0d7577025 86475bda4e77466f8d2d4adec3b57928 +#: 8feb7c045dcd4fd093b0b73a72529089 95706eaa079b4063bd5ea472cf042c69 +#: d5eee0574deb46bd8a206a8af3b9872e e4a87265f65c4990b2dc84b230272747 +#: ec5d55a544184d72badeed7c5b37b403 m5ui.label.M5Label.set_shadow of +msgid "Returns" +msgstr "" + +#: ../../en/m5ui/label.rst:68 662715e5f32d43afa5bdb11b8051f073 +msgid "|set_flag.png|" +msgstr "" + +#: ../../en/refs/m5ui.label.ref:8 4d5731ec0e5a420cbc453a911e32d92e +msgid "set_flag.png" +msgstr "" + +#: ../../en/m5ui/label.rst:78 f9693a46660d4b3aa655329ed954be79 +msgid "" +"Toggle a flag on the object. If the flag is set, it is removed; if not " +"set, it is added." +msgstr "切换对象上的标志。如果设置了标志,则移除该标志;如果未设置,则添加该标志。" + +#: ../../en/m5ui/label.rst:80 f71821a639d540abb8c5a2e173b74a36 +msgid "The flag to toggle." +msgstr "要切换的标志。" + +#: ../../en/m5ui/label.rst:85 9a33637061a04c94928a59267813a250 +msgid "|toggle_flag.png|" +msgstr "" + +#: ../../en/refs/m5ui.label.ref:21 7df1b8f0f07a42be992e8402c493ecca +msgid "toggle_flag.png" +msgstr "" + +#: ../../en/m5ui/label.rst:95 00ab9e5b69a340d6a9c82cb8b0b9e580 +msgid "" +"Set the state of the label. If ``value`` is True, the state is set; if " +"False, the state is unset." +msgstr "设置标签的状态。如果 ``value`` 为 True,则设置状态;如果为 False,则取消设置状态。" + +#: ../../en/m5ui/label.rst:97 2ae16659d2a84435bb4ebf822ecf3945 +msgid "The state to set." +msgstr "要设置的状态。" + +#: ../../en/m5ui/label.rst:98 1cd0a2c12f9c40d3bae37831fcf8daf9 +msgid "If True, the state is set; if False, the state is unset." +msgstr "如果为 True,则状态已设置;如果为 False,则状态未设置。" + +#: ../../en/m5ui/label.rst:103 8304d41049e34904abecca8d1d731df2 +msgid "|set_state.png|" +msgstr "" + +#: ../../en/refs/m5ui.label.ref:13 02411e929ee849c88c217e5a3d2b284a +msgid "set_state.png" +msgstr "" + +#: ../../en/m5ui/label.rst:113 564f1185d7104ce88d8663e26edadb54 +msgid "" +"Toggle the state of the label. If the state is set, it is unset; if not " +"set, it is set." +msgstr "切换标签的状态。如果标签状态已设置,则取消设置;如果标签状态未设置,则设置标签状态。" + +#: ../../en/m5ui/label.rst:115 2dcb2d7b2bff400e9099d99ff2c9ee1b +msgid "The state to toggle." +msgstr "要切换的状态。" + +#: ../../en/m5ui/label.rst:120 aba56bf8a82c42cab7f2300e343a2475 +msgid "|toggle_state.png|" +msgstr "" + +#: ../../en/refs/m5ui.label.ref:22 70e28948deaf4416a99c5e4dad21b5a0 +msgid "toggle_state.png" +msgstr "" + +#: ../../en/m5ui/label.rst:130 77c6b46672444e338283c051041a7378 +msgid "Set the font of the label text." +msgstr "设置标签文本的字体。" + +#: ../../en/m5ui/label.rst:132 15d32cb36319452a8287b92064a8970b +msgid "The font to set." +msgstr "要设置的字体。" + +#: ../../en/m5ui/label.rst:133 ../../en/m5ui/label.rst:153 +#: ../../en/m5ui/label.rst:173 4aafbcbcbce147408f4fd5aae289f267 +#: 57118c0e1e124496a40480f714105b46 93ef0bd7bdcd43d8832046b0211c79a2 +msgid "The part of the object to apply the style to (e.g., lv.PART.MAIN)." +msgstr "应用样式的对象部分(例如,lv.PART.MAIN)。" + +#: ../../en/m5ui/label.rst:138 c8b8f53fa5c54ca4b2b0c64188ba5ed6 +msgid "|set_style_text_font.png|" +msgstr "" + +#: ../../en/refs/m5ui.label.ref:14 94c5bbe42d714176b8ed780c7918b617 +msgid "set_style_text_font.png" +msgstr "" + +#: ../../en/m5ui/label.rst:149 a32f6cd1c9ab441589c5fcb4b193d5f7 +msgid "Set the color of the text." +msgstr "设置文本的颜色。" + +#: ../../en/m5ui/label.rst:151 ../../en/m5ui/label.rst:171 +#: 4661530fb4e841cbbbe236d0d4acef61 87a34b53a6164c7b8b85e8edc3ed916c +msgid "The color to set." +msgstr "要设置的颜色。" + +#: ../../en/m5ui/label.rst:152 ../../en/m5ui/label.rst:172 +#: 7bd4d9f477f64d4f80e71e55c8f4fd6c aa6d09e0a83449b2bada99837b115332 +msgid "The opacity of the color." +msgstr "颜色的不透明度。" + +#: ../../en/m5ui/label.rst:158 ../../en/m5ui/label.rst:178 +#: 282e1d02ec3b45cfa97242c3518d2a1e ef450737aa784fe98b3b711c1ff209a3 +msgid "|set_text_color.png|" +msgstr "" + +#: ../../en/refs/m5ui.label.ref:16 e80b742357f243a5bba5dd1a5346e1e3 +#: f7bd10d0b0da4b77a6b3a9c11ac82f10 +msgid "set_text_color.png" +msgstr "" + +#: ../../en/m5ui/label.rst:169 7eab7e26b0e244f18eeb2dfe29075d08 +msgid "Set the background color of the label." +msgstr "设置标签的背景颜色。" + +#: ../../en/m5ui/label.rst:189 6d3c3aff77674949937924b83186987d +msgid "Set the position of the label." +msgstr "设置标签的位置。" + +#: ../../en/m5ui/label.rst:191 ../../en/m5ui/label.rst:210 +#: 191fcfbbc5e54885880480a59beafafa 7759551df81148adab2c99967a52320e +msgid "The x-coordinate of the label." +msgstr "标签的 x 坐标。" + +#: ../../en/m5ui/label.rst:192 ../../en/m5ui/label.rst:228 +#: 98ea68ba37dd4de58756d7687166ee6e 9c7ac128d7df4fbf8b5a8f2fc8152a8c +msgid "The y-coordinate of the label." +msgstr "标签的 y 坐标。" + +#: ../../en/m5ui/label.rst:197 145fc9482f444e819d325f1f2456a83d +msgid "|set_pos.png|" +msgstr "" + +#: ../../en/refs/m5ui.label.ref:10 b7a35318e55f4f7eb8bec4c20afeecd7 +msgid "set_pos.png" +msgstr "" + +#: ../../en/m5ui/label.rst:208 e33597de2bf04b61aad9918baeb22111 +msgid "Set the x-coordinate of the label." +msgstr "设置标签的 x 坐标。" + +#: ../../en/m5ui/label.rst:215 7fdb98fc9111462a9aea537b176b96bf +msgid "|set_x.png|" +msgstr "" + +#: ../../en/refs/m5ui.label.ref:19 7597690bc7a94d5cbc1ca1a75378e896 +msgid "set_x.png" +msgstr "" + +#: ../../en/m5ui/label.rst:226 4fd9ddee60ac4848a6dfce87190e0595 +msgid "Set the y-coordinate of the label." +msgstr "设置标签的 y 坐标。" + +#: ../../en/m5ui/label.rst:233 e3abf9e7ca8c4cde8038ce3fec0cd14b +msgid "|set_y.png|" +msgstr "" + +#: ../../en/refs/m5ui.label.ref:20 2f97ae3175b14c08b4d4f523d990efa3 +msgid "set_y.png" +msgstr "" + +#: ../../en/m5ui/label.rst:244 4ccac23199774b519d37d798ea06a45b +msgid "Set the size of the label." +msgstr "设置标签的大小。" + +#: ../../en/m5ui/label.rst:246 ../../en/m5ui/label.rst:265 +#: 23a78ae6df1641368ccd43a0d8fa0947 36d2ce8395064a94a320da067fa6dc3e +msgid "The width of the label." +msgstr "标签的宽度。" + +#: ../../en/m5ui/label.rst:247 ce0c42b6e712493db8b6cfe8903fb1d1 +msgid "The height of the label." +msgstr "标签的高度。" + +#: ../../en/m5ui/label.rst:252 727f9f8edcdd47b98c532bb675d0534e +msgid "|set_size.png|" +msgstr "" + +#: ../../en/refs/m5ui.label.ref:12 53358830cce54d2e83310e5ef0d6eb76 +msgid "set_size.png" +msgstr "" + +#: ../../en/m5ui/label.rst:263 88fc065164584954974283b885da38d6 +msgid "Set the width of the label." +msgstr "设置标签的宽度。" + +#: ../../en/m5ui/label.rst:270 23d03de0d65f498083b061b135b0ea6b +msgid "|set_width.png|" +msgstr "" + +#: ../../en/refs/m5ui.label.ref:17 04f45c68211b44c0afa1759ba9fce86b +msgid "set_width.png" +msgstr "" + +#: ../../en/m5ui/label.rst:272 8804a6fbefe946cf9946366ce8b293ae +msgid "|set_width1.png|" +msgstr "" + +#: ../../en/refs/m5ui.label.ref:18 2b3bb5ea4eb34490942463a0f82feb97 +msgid "set_width1.png" +msgstr "" + +#: ../../en/m5ui/label.rst:301 919cb0c6c6ea4f1684ded3347a6814a2 +msgid "Align the label to another object." +msgstr "将标签与另一个对象对齐。" + +#: ../../en/m5ui/label.rst:303 d486ddef42a945e7ac23b32fd33cd944 +msgid "The object to align to." +msgstr "要对齐的对象。" + +#: ../../en/m5ui/label.rst:304 f18524d14d2f4a9b8e6399dfcfc79f3a +msgid "The alignment type." +msgstr "对齐类型。" + +#: ../../en/m5ui/label.rst:305 0e0e0c887c2b47358ae59c19bcf794d3 +msgid "The x-offset from the aligned object." +msgstr "与对齐对象的 x 偏移量。" + +#: ../../en/m5ui/label.rst:306 5aa44bb28c604aeabd412b6f92596f50 +msgid "The y-offset from the aligned object." +msgstr "与对齐对象的 y 偏移。" + +#: ../../en/m5ui/label.rst:311 0300ccb58adf4f64998b2ad3e974f387 +msgid "|align_to.png|" +msgstr "" + +#: ../../en/refs/m5ui.label.ref:3 fea132afe3924d80b5bce4f0210db256 +msgid "align_to.png" +msgstr "" + +#: 401a0c71f7514f19a5ef360f19f88f52 m5ui.label.M5Label.set_shadow:1 of +msgid "Set a shadow for the label." +msgstr "为标签设置阴影。" + +#: bbf53b99e7264b04a996dcb3b8879809 m5ui.label.M5Label.set_shadow:3 of +msgid "The color of the shadow in hexadecimal format or an integer." +msgstr "阴影的颜色(十六进制格式或整数)。" + +#: d9f72b33334d4da68f5bee35d078eddb m5ui.label.M5Label.set_shadow:4 of +msgid "The opacity of the shadow (0-255)." +msgstr "阴影的不透明度(0-255)。" + +#: 8e6bd76adb6b4a088127eeb41e3ece91 m5ui.label.M5Label.set_shadow:5 of +msgid "The alignment of the shadow relative to the label." +msgstr "阴影相对于标签的对齐方式。" + +#: 0a7c18a02f304b7b92675781a263289d m5ui.label.M5Label.set_shadow:6 of +msgid "The horizontal offset of the shadow." +msgstr "阴影的水平偏移。" + +#: 3f80f9f4d5224a0c93ba5ec711c1631d m5ui.label.M5Label.set_shadow:7 of +msgid "The vertical offset of the shadow." +msgstr "阴影的垂直偏移。" + +#: c96d18513f174f14a3642a32ced2a6a4 d575e562ac86428b8b25378f268ffd23 +#: m5ui.label.M5Label.set_shadow m5ui.label.M5Label.unset_shadow of +msgid "Return type" +msgstr "" + +#: 3894fbc143784dddb4b6bcc68b3163e2 m5ui.label.M5Label.set_shadow:12 of +msgid "|set_shadow.png|" +msgstr "" + +#: ../../en/refs/m5ui.label.ref:11 8f275e1a751441a3a7dde09fae424239 +msgid "set_shadow.png" +msgstr "" + +#: b0f0e960d255463d8dd3421cdb9fb360 m5ui.label.M5Label.unset_shadow:1 of +msgid "Remove the shadow from the label." +msgstr "删除标签上的阴影。" + +#: 9cc3d025916240ba99702f152c94d1b5 m5ui.label.M5Label.unset_shadow:5 of +msgid "|unset_shadow.png|" +msgstr "" + +#: ../../en/refs/m5ui.label.ref:23 6c5673a8880a457dbd06c4a7209855a2 +msgid "unset_shadow.png" +msgstr "" + +#~ msgid "Set the color of the button text." +#~ msgstr "" + +#~ msgid "Set the position of the button." +#~ msgstr "设置按钮的位置。" + diff --git a/docs/locales/zh_CN/LC_MESSAGES/m5ui/page.po b/docs/locales/zh_CN/LC_MESSAGES/m5ui/page.po new file mode 100644 index 00000000..cdfaa6fc --- /dev/null +++ b/docs/locales/zh_CN/LC_MESSAGES/m5ui/page.po @@ -0,0 +1,285 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2016 - 2025 M5Stack Technology Co., Ltd +# This file is distributed under the same license as the UIFlow2 Programming +# Guide package. +# FIRST AUTHOR , 2025. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: UIFlow2 Programming Guide \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-06-26 16:30+0800\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: zh_CN\n" +"Language-Team: zh_CN \n" +"Plural-Forms: nplurals=1; plural=0;\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.16.0\n" + +#: ../../en/m5ui/page.rst:4 a43df9774723465ebf5cf5c8bbe35dd3 +msgid "M5Page" +msgstr "" + +#: ../../en/m5ui/page.rst:8 f11f3c11fc2b4101b96b0a42e812a0de +msgid "" +"M5Page is a widget that can be used to create pages in the user " +"interface. It can be used to organize other widgets and provide " +"navigation between different pages." +msgstr "M5Page 是一个可用于在用户界面中创建页面的小部件。它可以用来组织其他小部件,并在不同页面之间提供导航。" + +#: ../../en/m5ui/page.rst:11 915e8575ff7f4cd9be5f24c9f5e47cb4 +msgid "UiFlow2 Example" +msgstr "UiFlow2 应用示例" + +#: ../../en/m5ui/page.rst:14 ../../en/m5ui/page.rst:33 +#: 893510e276064828b4bf9882e418aa50 99011b5459a64248b7ba4ab340a58d80 +msgid "page event" +msgstr "页面事件" + +#: ../../en/m5ui/page.rst:16 2015febd3fc04e59b4bd2c979fa48ae4 +msgid "Open the |cores3_page_event_example.m5f2| project in UiFlow2." +msgstr "在 UiFlow2 中打开 |cores3_page_event_example.m5f2| 项目。" + +#: ../../en/m5ui/page.rst:18 ../../en/m5ui/page.rst:35 +#: 22ee82421a794628bff4242f69510b60 66c871c9ec8a4e1680adb3262ecf4e00 +msgid "" +"When you press and hold the screen, the screen background color turns " +"black. When you release the screen, the background color returns to " +"white." +msgstr "按住屏幕时,屏幕背景色变为黑色。松开屏幕时,背景色恢复为白色。" + +#: ../../en/m5ui/page.rst:20 ../../en/m5ui/page.rst:65 +#: ../../en/m5ui/page.rst:83 ../../en/m5ui/page.rst:102 +#: ../../en/m5ui/page.rst:120 ../../en/m5ui/page.rst:140 +#: ../../en/m5ui/page.rst:160 1ab795bfc2e54e50888e8d942d052963 +#: 1e9bf44e6d794e96901aa246493633f0 40851e08bc954dd684ef07e1a8786140 +#: 5ec1de80da6249b6a9f0e55553ad76b1 ac7d9cc66a6145f89ba30d7b6c3e8de4 +#: b6677ea43a1e4d0d896dbb2c40242f27 d8fb9f61bc9d475ea70cb6f4dcc63f72 +#: e3d1ec62fff04e0da4fd64409e45be20 f596636a5c454f9c8c468abbaaa57230 +#: m5ui.page.M5Page:5 m5ui.page.M5Page.screen_load:3 of +msgid "UiFlow2 Code Block:" +msgstr "UiFlow2 代码块:" + +#: ../../en/m5ui/page.rst:22 38b668605a494e29a3cfe1d5159b53e5 +msgid "|cores3_page_event_example.png|" +msgstr "" + +#: ../../en/refs/m5ui.page.ref:1 88201c5bb8294eb8ac8495c41067ae23 +msgid "cores3_page_event_example.png" +msgstr "" + +#: ../../en/m5ui/page.rst:24 ../../en/m5ui/page.rst:43 +#: 237fbe67dc0542b685e7219263f39528 77f0f8222ed24e02b9f629827813bce6 +msgid "Example output:" +msgstr "示例输出:" + +#: ../../en/m5ui/page.rst:26 ../../en/m5ui/page.rst:45 +#: ../../en/m5ui/page.rst:63 ../../en/m5ui/page.rst:81 +#: ../../en/m5ui/page.rst:100 ../../en/m5ui/page.rst:118 +#: ../../en/m5ui/page.rst:138 ../../en/m5ui/page.rst:158 +#: 0ba86a6175b1428bb095328dce5133fb 0f2b4825a5354b278dac10be3a6819d7 +#: 4bf7253e371a4654aec00d5741c6ad63 676284cce49b4bd2ad91b8ae74d6b975 +#: 6ed664eb6f8b49ffaa51d677dee4f0a8 6fce59b1a2a44b99ba00972358326ac4 +#: d0f70785df63491192bc9d818b361ae8 ec617e9c5b914e8e99c21e36d3ce24c2 +#: f044aaeb50c84f2cbd73e358a2e234f1 m5ui.page.M5Page:7 of +msgid "None" +msgstr "" + +#: ../../en/m5ui/page.rst:30 fec5ac6035ce4f2faaa49f226d06220b +msgid "MicroPython Example" +msgstr "MicroPython 应用示例" + +#: ../../en/m5ui/page.rst:37 ../../en/m5ui/page.rst:69 +#: ../../en/m5ui/page.rst:87 ../../en/m5ui/page.rst:106 +#: ../../en/m5ui/page.rst:124 ../../en/m5ui/page.rst:144 +#: ../../en/m5ui/page.rst:164 16fc095b26174629a7863351d227ec25 +#: 28dba4feccac405f90c48cfaf7a97e06 61c10c35bade41b5991ff332a098a13c +#: 830e2a31b4634d71a1da4cffbb4fdd30 8503c2a2d47240908bdabece4234dd35 +#: 9c9d5f7c15bf4fc582d5ebc60eea2b3c b455b4ff8bcc46b1b66c5fed71613621 +#: db7c8f2b94834d1a875e7f930ab51dcf e51e98e0daa44f21add660017d1c0c7f +#: m5ui.page.M5Page:9 m5ui.page.M5Page.screen_load:7 of +msgid "MicroPython Code Block:" +msgstr "MicroPython 代码块:" + +#: ../../en/m5ui/page.rst:49 cfb87ddf279244778639a0b9f0d7b899 +msgid "**API**" +msgstr "API参考" + +#: ../../en/m5ui/page.rst:52 549b7efff10842da9cfaa058edac5de3 +msgid "M5Button" +msgstr "" + +#: d17cee2225ff40d7b182b612c5373931 m5ui.page.M5Page:1 of +msgid "Bases: :py:class:`~lvgl.obj`" +msgstr "" + +#: b8542b14abf34b368fc6040f1b1ab109 m5ui.page.M5Page:1 of +msgid "Create a page object." +msgstr "创建一个页面对象。" + +#: ../../en/m5ui/page.rst 14e81398e2234101bd6f83499d6e1722 +#: 1c33b0421b8b42f39bf59f2975da2df7 6dbec6b6c0684554bbfce2701a184067 +#: 9dd50981c19f49f280b8d1c9ead1ad00 bc3be99669f64178a8abbc3a14dfe988 +#: e03a9806f9d64ff99350e0c0922da54c f5ebb5edc97a40f2b0df248be2f858de +msgid "Parameters" +msgstr "" + +#: e809ce6a8d20405f836996f176123b27 m5ui.page.M5Page:3 of +msgid "" +"The background color of the page in hexadecimal format. Default is " +"0xFFFFFF (white)." +msgstr "页面背景颜色(十六进制格式)。默认值为 0xFFFFFF(白色)。" + +#: ../../en/m5ui/page.rst:59 ddd4ed68ae9546e581961519f667bb3c +msgid "" +"Set a flag on the object. If ``value`` is True, the flag is added; if " +"False, the flag is removed." +msgstr "在对象上设置一个标志。如果 ``value`` 为 True,则添加该标志;如果 ``value`` 为 False,则移除该标志。" + +#: ../../en/m5ui/page.rst:61 42a7ab37ec244c9d9d4a0b2881d6c63d +msgid "The flag to set." +msgstr "要设置的标志。" + +#: ../../en/m5ui/page.rst:62 fc8395093ad04f29923948a4be8ae610 +msgid "If True, the flag is added; if False, the flag is removed." +msgstr "如果为 True,则添加标志;如果为 False,则删除标志。" + +#: ../../en/m5ui/page.rst 24cc9ce92cd14683988ee2a9831465ac +#: 272d21f216e74d0a93b07a005f166d9b 97e3f472fe50400c944562393d91acf4 +#: ae27834b79c248bb9b448d483dc71e70 d579c5c6e7bd4024910c105001277345 +#: f6fe9889c5f94529a15350b83d289552 +msgid "Returns" +msgstr "" + +#: ../../en/m5ui/page.rst:67 8de3364aad99457db4139a5e31925a8e +msgid "|set_flag.png|" +msgstr "" + +#: ../../en/refs/m5ui.page.ref:6 03b87e63079f44f6ba1dc26ed75dd8d9 +msgid "set_flag.png" +msgstr "" + +#: ../../en/m5ui/page.rst:78 5bcf9c5381b14441a98582c06724ed9c +msgid "" +"Toggle a flag on the object. If the flag is set, it is removed; if not " +"set, it is added." +msgstr "切换对象上的标志。如果设置了标志,则移除该标志;如果未设置,则添加该标志。" + +#: ../../en/m5ui/page.rst:80 00ac02a8ee474ce1ba9ff734aafa82e7 +msgid "The flag to toggle." +msgstr "要切换的标志。" + +#: ../../en/m5ui/page.rst:85 4e30b68981a64d1d872ee45fd86d1213 +msgid "|toggle_flag.png|" +msgstr "" + +#: ../../en/refs/m5ui.page.ref:8 079180eabfe1471f8b0dc72423947a04 +msgid "toggle_flag.png" +msgstr "" + +#: ../../en/m5ui/page.rst:96 20f383ac25d2476fbbcc07e7aecbbba3 +msgid "" +"Set the state of the page. If ``value`` is True, the state is set; if " +"False, the state is unset." +msgstr "设置页面状态。如果 ``value`` 为 True,则设置状态;如果为 False,则取消设置状态。" + +#: ../../en/m5ui/page.rst:98 e18f033b8fce43998790d7352006905e +msgid "The state to set." +msgstr "要设置的状态。" + +#: ../../en/m5ui/page.rst:99 65cd12bf47a9435d9592a316d50804cd +msgid "If True, the state is set; if False, the state is unset." +msgstr "如果为 True,则状态已设置;如果为 False,则状态未设置。" + +#: ../../en/m5ui/page.rst:104 18f3fa3d69dd4a05ba32a6cc52be333c +msgid "|set_state.png|" +msgstr "" + +#: ../../en/refs/m5ui.page.ref:7 3b9df6acd926459e804b036f501bb8ea +msgid "set_state.png" +msgstr "" + +#: ../../en/m5ui/page.rst:115 b74bde986dac45c8b0f3ec6d93d50941 +msgid "" +"Toggle the state of the page. If the state is set, it is unset; if not " +"set, it is set." +msgstr "切换页面状态。如果状态已设置,则取消设置;如果未设置,则设置状态。" + +#: ../../en/m5ui/page.rst:117 329df1be14eb499fbb10d2b0be90dbd4 +msgid "The state to toggle." +msgstr "要切换的状态。" + +#: ../../en/m5ui/page.rst:122 1946327743c54b77bfe7cf853e8816c3 +msgid "|toggle_state.png|" +msgstr "" + +#: ../../en/refs/m5ui.page.ref:9 a70917ce85f84853baf2180262c949e3 +msgid "toggle_state.png" +msgstr "" + +#: ../../en/m5ui/page.rst:133 f17e07978ca04391ab74d6b2f68c1d94 +msgid "Set the background color of the page." +msgstr "设置页面的背景颜色。" + +#: ../../en/m5ui/page.rst:135 4f18a8949bec47fa8d18b834f6c5c294 +msgid "The color to set." +msgstr "要设置的颜色。" + +#: ../../en/m5ui/page.rst:136 bbd69b367a084000a8907455121f81a9 +msgid "The opacity of the color." +msgstr "颜色的不透明度。" + +#: ../../en/m5ui/page.rst:137 43d30fabe6c34de2b3d72fa7d0e3dcaa +msgid "The part of the object to apply the style to (e.g., lv.PART.MAIN)." +msgstr "应用样式的对象部分(例如,lv.PART.MAIN)。" + +#: ../../en/m5ui/page.rst:142 939495b630fc43539241ab30152d8abc +msgid "|set_bg_color.png|" +msgstr "" + +#: ../../en/refs/m5ui.page.ref:5 e0dac9d6c3244d0987a45e01a8f8596f +msgid "set_bg_color.png" +msgstr "" + +#: ../../en/m5ui/page.rst:153 942c6f31f2ad4d9cabe9c771a93116c0 +msgid "" +"Add an event callback to the page. The callback will be called when the " +"specified event occurs." +msgstr "向页面添加事件回调。当指定事件发生时,将调用该回调。" + +#: ../../en/m5ui/page.rst:155 a68f58f33056416899f9090305acb1bf +msgid "The callback function to call." +msgstr "要调用的回调函数。" + +#: ../../en/m5ui/page.rst:156 53239a14ea394213b1deb2e0b761e76c +msgid "The event to listen for." +msgstr "要监听的事件。" + +#: ../../en/m5ui/page.rst:157 fa4ea20ea380440a8726323ad31b9935 +msgid "Optional user data to pass to the callback." +msgstr "传递给回调的可选用户数据。" + +#: ../../en/m5ui/page.rst:162 4a6f21db99814974b3efce50953d4fe9 +msgid "|event.png|" +msgstr "" + +#: ../../en/refs/m5ui.page.ref:3 e64b33e45ca04e8f8eb1b925c1fa86b6 +msgid "event.png" +msgstr "" + +#: dddbda12a6fc487db3d5c74d7ceb782e m5ui.page.M5Page.screen_load:1 of +msgid "Load the page as the active screen." +msgstr "加载该页面作为活动屏幕。" + +#: 5e00c50b875f438da1e08fdb984f5f01 m5ui.page.M5Page.screen_load:5 of +msgid "|screen_load.png|" +msgstr "" + +#: ../../en/refs/m5ui.page.ref:4 11bbc9f255b94239836e199fdb87449c +msgid "screen_load.png" +msgstr "" + diff --git a/examples/m5ui/button/cores3_button_event_example.m5f2 b/examples/m5ui/button/cores3_button_event_example.m5f2 new file mode 100644 index 00000000..56ec3f30 --- /dev/null +++ b/examples/m5ui/button/cores3_button_event_example.m5f2 @@ -0,0 +1 @@ +{"version":"V2.0","versionNumber":"V2.2.9","type":"cores3","components":[{"name":"page0","type":"lvgl_page","layer":0,"screenId":"builtin","screenName":"","id":"cdDSFe1omz4+Z1jP","createTime":1750844647115,"backgroundColor":"#ffffff","isLVGL":true,"isSelected":true},{"name":"button0","type":"lvgl_button","layer":1,"screenId":"builtin","screenName":"","id":"aio8$lRTxBaA+JEx","createTime":1750902792331,"x":117,"y":102,"width":84,"height":34,"color":"#ffffff","backgroundColor":"#2196f3","text":"click me","font":"lv.font_montserrat_14","pageId":"cdDSFe1omz4+Z1jP","isLVGL":true,"isSelected":false},{"name":"label0","type":"lvgl_label","layer":2,"screenId":"builtin","screenName":"","id":"k&_w_=z7br7p&7wH","createTime":1750902902948,"x":136,"y":33,"color":"#000000","backgroundColor":"#ffffff","bg_opacity":0,"text":"label0","font":"lv.font_montserrat_14","pageId":"cdDSFe1omz4+Z1jP","isLVGL":true,"isSelected":false,"width":43,"height":15}],"resources":[{"hardware":["hardware_button","hardware_pin_button","imu","speaker","touch","als","mic","sdcard"]}],"units":[],"hats":[],"bases":[],"i2cs":[],"blockly":"truepage0truebutton0PRESSEDlabel0pressedbutton0RELEASEDlabel0released","screen":[{"simulationName":"Built-in","type":"builtin","width":320,"height":240,"scale":0.77,"screenName":"","blockId":"","screenColorType":0,"rotation":1,"id":"builtin","createTime":1750838628949}],"logicWhenNum":0,"customList":[]} \ No newline at end of file diff --git a/examples/m5ui/button/cores3_button_event_example.py b/examples/m5ui/button/cores3_button_event_example.py new file mode 100644 index 00000000..27d38741 --- /dev/null +++ b/examples/m5ui/button/cores3_button_event_example.py @@ -0,0 +1,87 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import os, sys, io +import M5 +from M5 import * +import m5ui +import lvgl as lv + + +page0 = None +button0 = None +label0 = None + + +def button0_pressed_event(event_struct): + global page0, button0, label0 + + label0.set_text(str("pressed")) + + +def button0_released_event(event_struct): + global page0, button0, label0 + + label0.set_text(str("released")) + + +def button0_event_handler(event_struct): + global page0, button0, label0 + event = event_struct.code + if event == lv.EVENT.PRESSED and True: + button0_pressed_event(event_struct) + if event == lv.EVENT.RELEASED and True: + button0_released_event(event_struct) + return + + +def setup(): + global page0, button0, label0 + + M5.begin() + m5ui.init() + page0 = m5ui.M5Screen(bg_c=0xFFFFFF) + button0 = m5ui.M5Button( + text="click me", + x=117, + y=102, + bg_c=0x2196F3, + text_c=0xFFFFFF, + font=lv.font_montserrat_14, + parent=page0, + ) + label0 = m5ui.M5Label( + "label0", + x=136, + y=33, + text_c=0x000000, + bg_c=0xFFFFFF, + bg_opa=0, + font=lv.font_montserrat_14, + parent=page0, + ) + + button0.add_event_cb(button0_event_handler, lv.EVENT.ALL, None) + + page0.screen_load() + + +def loop(): + global page0, button0, label0 + M5.update() + + +if __name__ == "__main__": + try: + setup() + while True: + loop() + except (Exception, KeyboardInterrupt) as e: + try: + m5ui.deinit() + from utility import print_error_msg + + print_error_msg(e) + except ImportError: + print("please update to latest firmware") diff --git a/examples/m5ui/label/cores3_scroll_label_example.m5f2 b/examples/m5ui/label/cores3_scroll_label_example.m5f2 new file mode 100644 index 00000000..fa9668dc --- /dev/null +++ b/examples/m5ui/label/cores3_scroll_label_example.m5f2 @@ -0,0 +1 @@ +{"version":"V2.0","versionNumber":"V2.3.0","type":"cores3","components":[{"name":"page0","type":"lvgl_page","layer":0,"screenId":"builtin","screenName":"","id":"zGUSgLk%P9BF_bji","createTime":1750906906496,"backgroundColor":"#ffffff","isLVGL":true,"isSelected":true},{"name":"label0","type":"lvgl_label","layer":1,"screenId":"builtin","screenName":"","id":"ktg+xZQJ^lvI#N=M","createTime":1750907034945,"x":60,"y":110,"color":"#000000","backgroundColor":"#ffffff","bg_opacity":0,"text":"It is a circularly scrolling text. ","font":"lv.font_montserrat_14","pageId":"zGUSgLk%P9BF_bji","isLVGL":true,"isSelected":false,"width":207,"height":15}],"resources":[{"hardware":["hardware_button","hardware_pin_button","imu","speaker","touch","als","mic","sdcard"]}],"units":[],"hats":[],"bases":[],"i2cs":[],"blockly":"truepage0label0SCROLL_CIRCULARlabel0150label0CENTERpage000true","screen":[{"simulationName":"Built-in","type":"builtin","width":320,"height":240,"scale":0.77,"screenName":"","blockId":"","screenColorType":0,"rotation":1,"id":"builtin","createTime":1750906899526}],"logicWhenNum":0,"customList":[]} \ No newline at end of file diff --git a/examples/m5ui/label/cores3_scroll_label_example.py b/examples/m5ui/label/cores3_scroll_label_example.py new file mode 100644 index 00000000..859d648f --- /dev/null +++ b/examples/m5ui/label/cores3_scroll_label_example.py @@ -0,0 +1,56 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import os, sys, io +import M5 +from M5 import * +import m5ui +import lvgl as lv + + +page0 = None +label0 = None + + +def setup(): + global page0, label0 + + M5.begin() + m5ui.init() + page0 = m5ui.M5Screen(bg_c=0xFFFFFF) + label0 = m5ui.M5Label( + "It is a circularly scrolling text. ", + x=60, + y=110, + text_c=0x000000, + bg_c=0xFFFFFF, + bg_opa=0, + font=lv.font_montserrat_14, + parent=page0, + ) + + page0.screen_load() + label0.set_long_mode(lv.label.LONG_MODE.SCROLL_CIRCULAR) + label0.set_width(150) + label0.align_to(page0, lv.ALIGN.CENTER, 0, 0) + + +def loop(): + global page0, label0 + M5.update() + + +if __name__ == "__main__": + try: + setup() + while True: + loop() + except (Exception, KeyboardInterrupt) as e: + try: + m5ui.deinit() + from utility import print_error_msg + + print_error_msg(e) + except ImportError: + print("please update to latest firmware") diff --git a/examples/m5ui/page/cores3_page_event_example.m5f2 b/examples/m5ui/page/cores3_page_event_example.m5f2 new file mode 100644 index 00000000..6ce77353 --- /dev/null +++ b/examples/m5ui/page/cores3_page_event_example.m5f2 @@ -0,0 +1 @@ +{"version":"V2.0","versionNumber":"V2.3.0","type":"cores3","components":[{"name":"page0","type":"lvgl_page","layer":0,"screenId":"builtin","screenName":"","id":"zGUSgLk%P9BF_bji","createTime":1750906906496,"backgroundColor":"#ffffff","isLVGL":true,"isSelected":true}],"resources":[{"hardware":["hardware_button","hardware_pin_button","imu","speaker","touch","als","mic","sdcard"]}],"units":[],"hats":[],"bases":[],"i2cs":[],"blockly":"truepage0truepage0CLICKEDpage0palette#000000255page0PRESSEDpage0palette#000000255page0RELEASEDpage0palette#ffffff255page0LONG_PRESSEDpage0palette#000000255","screen":[{"simulationName":"Built-in","type":"builtin","width":320,"height":240,"scale":0.77,"screenName":"","blockId":"","screenColorType":0,"rotation":1,"id":"builtin","createTime":1750906899526}],"logicWhenNum":0,"customList":[]} \ No newline at end of file diff --git a/examples/m5ui/page/cores3_page_event_example.py b/examples/m5ui/page/cores3_page_event_example.py new file mode 100644 index 00000000..38b7ef9a --- /dev/null +++ b/examples/m5ui/page/cores3_page_event_example.py @@ -0,0 +1,82 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import os, sys, io +import M5 +from M5 import * +import m5ui +import lvgl as lv + + +page0 = None + + +def page0_pressed_event(event_struct): + global page0 + + page0.set_bg_color(0x000000, 255, 0) + + +def page0_released_event(event_struct): + global page0 + + page0.set_bg_color(0xFFFFFF, 255, 0) + + +def page0_clicked_event(event_struct): + global page0 + + page0.set_bg_color(0x000000, 255, 0) + + +def page0_long_pressed_event(event_struct): + global page0 + + page0.set_bg_color(0x000000, 255, 0) + + +def page0_event_handler(event_struct): + global page0 + event = event_struct.code + if event == lv.EVENT.PRESSED and True: + page0_pressed_event(event_struct) + if event == lv.EVENT.RELEASED and True: + page0_released_event(event_struct) + if event == lv.EVENT.CLICKED and True: + page0_clicked_event(event_struct) + if event == lv.EVENT.LONG_PRESSED and True: + page0_long_pressed_event(event_struct) + return + + +def setup(): + global page0 + + M5.begin() + m5ui.init() + page0 = m5ui.M5Screen(bg_c=0xFFFFFF) + + page0.add_event_cb(page0_event_handler, lv.EVENT.ALL, None) + + page0.screen_load() + + +def loop(): + global page0 + M5.update() + + +if __name__ == "__main__": + try: + setup() + while True: + loop() + except (Exception, KeyboardInterrupt) as e: + try: + m5ui.deinit() + from utility import print_error_msg + + print_error_msg(e) + except ImportError: + print("please update to latest firmware") diff --git a/m5stack/boards/M5STACK_Core2/manifest.py b/m5stack/boards/M5STACK_Core2/manifest.py index ff55ec94..cafeb383 100644 --- a/m5stack/boards/M5STACK_Core2/manifest.py +++ b/m5stack/boards/M5STACK_Core2/manifest.py @@ -3,3 +3,4 @@ # SPDX-License-Identifier: MIT include("$(MPY_DIR)/../m5stack/modules/startup/manifest_core2.py") +include("$(MPY_DIR)/../m5stack/libs/m5ui/manifest.py") diff --git a/m5stack/boards/M5STACK_CoreS3/manifest.py b/m5stack/boards/M5STACK_CoreS3/manifest.py index 275c55bd..11d2efb7 100644 --- a/m5stack/boards/M5STACK_CoreS3/manifest.py +++ b/m5stack/boards/M5STACK_CoreS3/manifest.py @@ -3,3 +3,4 @@ # SPDX-License-Identifier: MIT include("$(MPY_DIR)/../m5stack/modules/startup/manifest_cores3.py") +include("$(MPY_DIR)/../m5stack/libs/m5ui/manifest.py") diff --git a/m5stack/boards/M5STACK_Dial/manifest.py b/m5stack/boards/M5STACK_Dial/manifest.py index 0dbbb218..33d29bf5 100644 --- a/m5stack/boards/M5STACK_Dial/manifest.py +++ b/m5stack/boards/M5STACK_Dial/manifest.py @@ -3,3 +3,4 @@ # SPDX-License-Identifier: MIT include("$(MPY_DIR)/../m5stack/modules/startup/manifest_dial.py") +include("$(MPY_DIR)/../m5stack/libs/m5ui/manifest.py") diff --git a/m5stack/boards/M5STACK_Tab5/manifest.py b/m5stack/boards/M5STACK_Tab5/manifest.py index ed273839..d9b00aae 100644 --- a/m5stack/boards/M5STACK_Tab5/manifest.py +++ b/m5stack/boards/M5STACK_Tab5/manifest.py @@ -3,3 +3,4 @@ # SPDX-License-Identifier: MIT include("$(MPY_DIR)/../m5stack/modules/startup/manifest_tab5.py") +include("$(MPY_DIR)/../m5stack/libs/m5ui/manifest.py") diff --git a/m5stack/boards/M5STACK_Tab5/sdkconfig.esp_dl b/m5stack/boards/M5STACK_Tab5/sdkconfig.esp_dl new file mode 100644 index 00000000..49248961 --- /dev/null +++ b/m5stack/boards/M5STACK_Tab5/sdkconfig.esp_dl @@ -0,0 +1,16 @@ +# +# models: human_face_recognition +# +# CONFIG_DB_FATFS_FLASH is not set +# CONFIG_DB_FATFS_SDCARD is not set +CONFIG_DB_SPIFFS=y +CONFIG_DB_FILE_SYSTEM=2 +CONFIG_SPIFLASH_MOUNT_POINT="spiffs" +CONFIG_SPIFLASH_MOUNT_PARTITION="storage" +CONFIG_HUMAN_FACE_FEAT_MFN_S8_V1=y +CONFIG_BSP_SPIFFS_MOUNT_POINT="/spiffs" +CONFIG_BSP_SPIFFS_PARTITION_LABEL="storage" +CONFIG_BSP_SPIFFS_MAX_FILES=5 +# end of models: human_face_recognition + +CONFIG_DEFAULT_HUMAN_FACE_DETECT_MODEL=0 diff --git a/m5stack/boards/M5STACK_Tough/manifest.py b/m5stack/boards/M5STACK_Tough/manifest.py index 5988a6ad..73062f3c 100644 --- a/m5stack/boards/M5STACK_Tough/manifest.py +++ b/m5stack/boards/M5STACK_Tough/manifest.py @@ -3,3 +3,4 @@ # SPDX-License-Identifier: MIT include("$(MPY_DIR)/../m5stack/modules/startup/manifest_tough.py") +include("$(MPY_DIR)/../m5stack/libs/m5ui/manifest.py") diff --git a/m5stack/libs/lv_utils/__init__.py b/m5stack/libs/lv_utils/__init__.py index f183d808..62e050da 100644 --- a/m5stack/libs/lv_utils/__init__.py +++ b/m5stack/libs/lv_utils/__init__.py @@ -3,11 +3,12 @@ # SPDX-License-Identifier: MIT import lvgl as lv -import _lv_utils from .lv_utils import event_loop def fs_register(fs_drv, letter, cache_size=500): + import _lv_utils + fs_drv.init() fs_drv.letter = ord(letter) fs_drv.open_cb = _lv_utils.fs_open_cb @@ -21,3 +22,62 @@ def fs_register(fs_drv, letter, cache_size=500): fs_drv.cache_size = cache_size fs_drv.register() + + +import asyncio +import sys + + +class LVTaskHandler: + _current_instance = None + + def __init__(self, freq=25, refresh_cb=None, exception_sink=None): + if self.is_running(): + raise RuntimeError("Event loop is already running!") + + if not lv.is_initialized(): + lv.init() + + LVTaskHandler._current_instance = self + + self.delay = 1000 // freq + self.refresh_cb = refresh_cb + self.exception_sink = exception_sink if exception_sink else self.default_exception_sink + self.refresh_event = asyncio.Event() + self.refresh_task = asyncio.create_task(self.async_refresh()) + self.timer_task = asyncio.create_task(self.async_timer()) + + @staticmethod + def is_running(): + return LVTaskHandler._current_instance is not None + + @staticmethod + def current_instance(): + return LVTaskHandler._current_instance + + async def async_refresh(self): + while True: + await self.refresh_event.wait() + if lv._nesting.value == 0: + self.refresh_event.clear() + try: + lv.task_handler() + except Exception as e: + if self.exception_sink: + self.exception_sink(e) + if self.refresh_cb: + self.refresh_cb() + + async def async_timer(self): + while True: + await asyncio.sleep_ms(self.delay) + lv.tick_inc(self.delay) + self.refresh_event.set() + + def deinit(self): + self.refresh_task.cancel() + self.timer_task.cancel() + LVTaskHandler._current_instance = None + + def default_exception_sink(self, e): + sys.print_exception(e) diff --git a/m5stack/libs/m5ui/__init__.py b/m5stack/libs/m5ui/__init__.py new file mode 100644 index 00000000..0f02e9ec --- /dev/null +++ b/m5stack/libs/m5ui/__init__.py @@ -0,0 +1,20 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +_attrs = { + "init": "port", + "deinit": "port", + "M5Button": "button", + "M5Label": "label", + "M5Page": "page", +} + + +def __getattr__(attr): + mod = _attrs.get(attr, None) + if mod is None: + raise AttributeError(attr) + value = getattr(__import__(mod, None, None, True, 1), attr) + globals()[attr] = value + return value diff --git a/m5stack/libs/m5ui/base.py b/m5stack/libs/m5ui/base.py new file mode 100644 index 00000000..8530e9e3 --- /dev/null +++ b/m5stack/libs/m5ui/base.py @@ -0,0 +1,90 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import lvgl as lv +import time + + +class M5Base: + @staticmethod + def set_flag(self, flag: int, value: bool) -> None: + """Set a flag on the object. If `value` is True, the flag is added; if False, the flag is removed. + + :param int flag: The flag to set. + :param bool value: If True, the flag is added; if False, the flag is removed. + :return: None + """ + if value: + self.add_flag(flag) + else: + self.remove_flag(flag) + + @staticmethod + def toggle_flag(self, flag): + """Toggle a flag on the object. If the flag is set, it is removed; if not set, it is added. + + :param int flag: The flag to toggle. + """ + if self.has_flag(flag): + self.remove_flag(flag) + else: + self.add_flag(flag) + + @staticmethod + def set_state(self, state: int, value: bool) -> None: + """Set a state on the object. + + :param int state: The state to set. + :param bool value: If True, the state is added; if False, the state is removed. + :return: None + """ + if value: + self.add_state(state) + else: + self.clear_state(state) + + @staticmethod + def toggle_state(self, state: int) -> None: + """Toggle a state on the object. If the state is set, it is removed; if not set, it is added. + + :param int state: The state to toggle. + :return: None + """ + if self.has_state(state): + self.remove_state(state) + else: + self.add_state(state) + return + + @staticmethod + def set_text_color(self, color: int, opa: int, part: int) -> None: + """Set the text color and opacity for a given part of the object. + + :param int color: The color to set, can be an integer (hex) or a lv.color object. + :param int opa: The opacity level (0-255). + :param int part: The part of the object to apply the style to (e.g., lv.PART.MAIN). + :return: None + """ + if isinstance(color, int): + color = lv.color_hex(color) + + self.set_style_text_color(color, part) + time.sleep(0.01) + self.set_style_text_opa(opa, part) + + @staticmethod + def set_bg_color(self, color: int, opa: int, part: int) -> None: + """Set the background color and opacity for a given part of the object. + + :param int color: The color to set, can be an integer (hex) or a lv.color object. + :param int opa: The opacity level (0-255). + :param int part: The part of the object to apply the style to (e.g., lv.PART.MAIN). + :return: None + """ + if isinstance(color, int): + color = lv.color_hex(color) + + self.set_style_bg_color(color, part) + time.sleep(0.01) + self.set_style_bg_opa(opa, part) diff --git a/m5stack/libs/m5ui/button.py b/m5stack/libs/m5ui/button.py new file mode 100644 index 00000000..e1a5766d --- /dev/null +++ b/m5stack/libs/m5ui/button.py @@ -0,0 +1,171 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +from .base import M5Base +import lvgl as lv + + +class M5Button(lv.button): + """Create a button object. + + :param str text: The text to display on the button. + :param int x: The x position of the button. + :param int y: The y position of the button. + :param int w: The width of the button. when set to 0, the button will automatically size to fit the text. + :param int h: The height of the button. when set to 0, the button will automatically size to fit the text. + :param int bg_c: The background color of the button in hexadecimal format. + :param int text_c: The text color of the button in hexadecimal format. + :param lv.lv_font_t font: The font to use for the button text. + :param lv.obj parent: The parent object to attach the button to. If not specified, the button will be attached to the default screen. + + UiFlow2 Code Block: + + None + + MicroPython Code Block: + + .. code-block:: python + + from m5ui import M5Button + import lvgl as lv + + m5ui.init() + button_0 = M5Button(text="Click Me", x=10, y=10, bg_c=0x2196F3, text_c=0xFFFFFF, parent=page0) + """ + + EFFECT_SIMPLE = 0 + EFFECT_WAVE = 1 + EFFECT_GUMMY = 2 + + def __init__( + self, + text="button0", + x=0, + y=0, + w=0, + h=0, + bg_c=0x2196F3, + text_c=0xFFFFFF, + font=lv.font_montserrat_14, + parent=None, + ): + if parent is None: + parent = lv.screen_active() + super().__init__(parent) + self.set_pos(x, y) + self.set_text_color(text_c, 255, lv.PART.MAIN | lv.STATE.DEFAULT) + self.set_bg_color(bg_c, 255, lv.PART.MAIN | lv.STATE.DEFAULT) + self.set_style_text_font(font, lv.PART.MAIN | lv.STATE.DEFAULT) + if w > 0 and h > 0: + self.set_size(w, h) + self.label = lv.label(self) + self.label.set_text(text) + self.label.set_align(lv.ALIGN.CENTER) + + self.trans_def = self.get_style_transition(lv.PART.MAIN | lv.STATE.DEFAULT) + self.trans_pr = self.get_style_transition(lv.PART.MAIN | lv.STATE.PRESSED) + + def set_btn_text(self, text: str) -> None: + """Set the text of the button. + + :param str text: The text to set on the button. + + UiFlow2 Code Block: + + |set_btn_text.png| + + MicroPython Code Block: + + .. code-block:: python + + button_0.set_btn_text("Click Me") + """ + self.label.set_text(text) + + def get_btn_text(self) -> str: + """Get the text of the button. + + :return: The text of the button. + :rtype: str + + UiFlow2 Code Block: + + |get_btn_text.png| + + MicroPython Code Block: + .. code-block:: python + + text = button_0.get_btn_text() + """ + return self.label.get_text() + + def set_shadow(self, color, opa, align, offset_x, offset_y): + if isinstance(color, int): + color = lv.color_hex(color) + + self.set_style_text_color(color, lv.PART.MAIN | lv.STATE.DEFAULT) + self.set_style_text_opa(opa, lv.PART.MAIN | lv.STATE.DEFAULT) + self.align_to(self, align, offset_x, offset_y) + + def unset_shadow(self): + raise NotImplementedError + + def _effect_simple(self): + self.set_style_transition(self.trans_def, lv.PART.MAIN | lv.STATE.DEFAULT) + self.set_style_transition(self.trans_pr, lv.PART.MAIN | lv.STATE.PRESSED) + + def _effect_wave(self): + pass + + def _effect_gummy(self): + trans_def = lv.style_transition_dsc_t() + trans_def.init( + [ + lv.STYLE.TRANSFORM_WIDTH, + lv.STYLE.TRANSFORM_HEIGHT, + lv.STYLE.TEXT_LETTER_SPACE, + 0, + ], + lv.anim_t.path_overshoot, + 250, + 100, + None, + ) + + trans_pr = lv.style_transition_dsc_t() + trans_pr.init( + [ + lv.STYLE.TRANSFORM_WIDTH, + lv.STYLE.TRANSFORM_HEIGHT, + lv.STYLE.TEXT_LETTER_SPACE, + 0, + ], + lv.anim_t.path_overshoot, + 250, + 0, + None, + ) + + self.set_style_transition(trans_def, lv.PART.MAIN | lv.STATE.DEFAULT) + self.set_style_transform_width(10, lv.PART.MAIN | lv.STATE.PRESSED) + self.set_style_transform_height(-10, lv.PART.MAIN | lv.STATE.PRESSED) + self.set_style_text_letter_space(10, lv.PART.MAIN | lv.STATE.PRESSED) + + def set_pressed_effect(self, effect): + if effect == self.EFFECT_SIMPLE: + self._effect_simple() + elif effect == self.EFFECT_WAVE: + raise NotImplementedError("Wave effect is not implemented yet.") + elif effect == self.EFFECT_GUMMY: + self._effect_gummy() + else: + raise ValueError(f"Unknown effect: {effect}") + + def __getattr__(self, name): + if hasattr(M5Base, name): + method = getattr(M5Base, name) + bound_method = lambda *args, **kwargs: method(self, *args, **kwargs) + setattr(self, name, bound_method) + return bound_method + raise AttributeError(f"'{self.__class__.__name__}' object has no attribute '{name}'") diff --git a/m5stack/libs/m5ui/label.py b/m5stack/libs/m5ui/label.py new file mode 100644 index 00000000..b1d1b1ab --- /dev/null +++ b/m5stack/libs/m5ui/label.py @@ -0,0 +1,113 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +from .base import M5Base +import lvgl as lv + + +class M5Label(lv.label): + """Create a label object. + + :param str text: The text to display on the label. + :param int x: The x position of the label. + :param int y: The y position of the label. + :param int text_c: The text color of the label in hexadecimal format. + :param int bg_c: The background color of the label in hexadecimal format. + :param int bg_opa: The background opacity of the label (0-255). + :param lv.lv_font_t font: The font to use for the button text. + :param lv.obj parent: The parent object to attach the button to. If not specified, the button will be attached to the default screen. + + UiFlow2 Code Block: + + None + + MicroPython Code Block: + + .. code-block:: python + + from m5ui import M5Label + import lvgl as lv + + m5ui.init() + label_0 = M5Label(text="Hello, World!", x=10, y=10, text_c=0x212121, bg_c=0xFFFFFF, bg_opa=0, font=lv.font_montserrat_14, parent=page0) + """ + + def __init__( + self, + text, + x=0, + y=0, + text_c=0x212121, + bg_c=0xFFFFFF, + bg_opa=0, + font=lv.font_montserrat_14, + parent=None, + ): + if parent is None: + parent = lv.screen_active() + super().__init__(parent) + self.set_text(text) + self.set_pos(x, y) + self.set_text_color(text_c, 255, lv.PART.MAIN | lv.STATE.DEFAULT) + self.set_bg_color(bg_c, bg_opa, lv.PART.MAIN | lv.STATE.DEFAULT) + self.set_style_text_font(font, lv.PART.MAIN | lv.STATE.DEFAULT) + self._shadow_label = lv.label(self) + self.unset_shadow() + + def set_shadow(self, color: int, opa: int, align: int, offset_x: int, offset_y: int) -> None: + """Set a shadow for the label. + + :param int color: The color of the shadow in hexadecimal format or an integer. + :param int opa: The opacity of the shadow (0-255). + :param int align: The alignment of the shadow relative to the label. + :param int offset_x: The horizontal offset of the shadow. + :param int offset_y: The vertical offset of the shadow. + :return: None + + UiFlow2 Code Block: + + |set_shadow.png| + + MicroPython Code Block: + + .. code-block:: python + + label_0.set_shadow(color=0x000000, opa=128, align=lv.ALIGN.BOTTOM_RIGHT, offset_x=5, offset_y=5) + """ + if isinstance(color, int): + color = lv.color_hex(color) + + self._shadow_label.remove_flag(lv.obj.FLAG.HIDDEN) + + self._shadow_label.set_text(self.get_text()) + self._shadow_label.set_style_text_color(color, lv.PART.MAIN | lv.STATE.DEFAULT) + self._shadow_label.set_style_text_opa(opa, lv.PART.MAIN | lv.STATE.DEFAULT) + self._shadow_label.align_to(self, align, offset_x, offset_y) + + def unset_shadow(self) -> None: + """Remove the shadow from the label. + + UiFlow2 Code Block: + + |unset_shadow.png| + + MicroPython Code Block: + + .. code-block:: python + + label_0.unset_shadow() + """ + self._shadow_label.add_flag(lv.obj.FLAG.HIDDEN) + + def __del__(self): + self._shadow_label.delete() + super().__delete__() + + def __getattr__(self, name): + if hasattr(M5Base, name): + method = getattr(M5Base, name) + bound_method = lambda *args, **kwargs: method(self, *args, **kwargs) + setattr(self, name, bound_method) + return bound_method + raise AttributeError(f"'{self.__class__.__name__}' object has no attribute '{name}'") diff --git a/m5stack/libs/m5ui/manifest.py b/m5stack/libs/m5ui/manifest.py new file mode 100644 index 00000000..5f40e6d5 --- /dev/null +++ b/m5stack/libs/m5ui/manifest.py @@ -0,0 +1,10 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +package( + "m5ui", + ("__init__.py", "base.py", "button.py", "label.py", "page.py", "port.py"), + base_path="..", + opt=0, +) diff --git a/m5stack/libs/m5ui/page.py b/m5stack/libs/m5ui/page.py new file mode 100644 index 00000000..d7817ca7 --- /dev/null +++ b/m5stack/libs/m5ui/page.py @@ -0,0 +1,54 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +from .base import M5Base +import lvgl as lv + + +class M5Page(lv.obj): + """Create a page object. + + :param int bg_c: The background color of the page in hexadecimal format. Default is 0xFFFFFF (white). + + UiFlow2 Code Block: + + None + + MicroPython Code Block: + + .. code-block:: python + + from m5ui import M5Page + import lvgl as lv + + m5ui.init() + page_0 = M5Page(bg_c=0xFFFFFF) + """ + + def __init__(self, bg_c=0xFFFFFF): + super().__init__() + self.set_bg_color(bg_c, lv.OPA.COVER, lv.PART.MAIN | lv.STATE.DEFAULT) + + def screen_load(self): + """Load the page as the active screen. + + UiFlow2 Code Block: + + |screen_load.png| + + MicroPython Code Block: + + .. code-block:: python + + page_0.screen_load() + """ + lv.screen_load(self) + + def __getattr__(self, name): + if hasattr(M5Base, name): + method = getattr(M5Base, name) + bound_method = lambda *args, **kwargs: method(self, *args, **kwargs) + setattr(self, name, bound_method) + return bound_method + raise AttributeError(f"'{self.__class__.__name__}' object has no attribute '{name}'") diff --git a/m5stack/libs/m5ui/port.py b/m5stack/libs/m5ui/port.py new file mode 100644 index 00000000..47525058 --- /dev/null +++ b/m5stack/libs/m5ui/port.py @@ -0,0 +1,80 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import lvgl as lv +import sys +import lv_utils + + +def _sdl_init(width=320, height=240): + lv.init() + + # Create an event loop and Register SDL display/mouse/keyboard drivers. + from lv_utils import event_loop + + event_loop = event_loop() + + disp_drv = lv.sdl_window_create(width, height) + disp_drv.set_default() + display = lv.display_get_default() + + group = lv.group_create() + group.set_default() + + mouse = lv.sdl_mouse_create() + mouse.set_display(display) + mouse.set_group(group) + + keyboard = lv.sdl_keyboard_create() + keyboard.set_display(display) + keyboard.set_group(group) + print("SDL initialized with display size:", width, "x", height) + + +def _m5_init(): + import M5 + + M5.begin() + + # lvgl init + M5.Lcd.lvgl_init() + + # built-in display + disp_buf0 = lv.draw_buf_create(M5.getDisplay(0).width(), 10, lv.COLOR_FORMAT.RGB565, 0) + disp_buf1 = lv.draw_buf_create(M5.getDisplay(0).width(), 10, lv.COLOR_FORMAT.RGB565, 0) + + disp_drv = lv.display_create(M5.getDisplay(0).width(), M5.getDisplay(0).height()) + disp_drv.set_color_format(lv.COLOR_FORMAT.RGB565) + + disp_drv.set_draw_buffers(disp_buf0, disp_buf1) + disp_drv.set_flush_cb(M5.Lcd.lvgl_flush) + disp_drv.set_user_data({"display": M5.Lcd}) + disp_drv.set_render_mode(lv.DISPLAY_RENDER_MODE.PARTIAL) + + # touch driver init + indev_drv = lv.indev_create() + indev_drv.set_type(lv.INDEV_TYPE.POINTER) + # indev_drv.set_display(lv.display_get_default()) + indev_drv.set_display(disp_drv) + indev_drv.set_read_cb(M5.Lcd.lvgl_read) + + # fs driver init + fs_drv = lv.fs_drv_t() + lv_utils.fs_register(fs_drv, "S", 500) + + +def init(): + if sys.platform == "esp32": + _m5_init() + elif sys.platform == "linux": + _sdl_init() + + +def deinit(): + if sys.platform == "esp32": + import M5 + + M5.Lcd.lvgl_deinit() + else: + pass From 9ab3abfe723bd6dc5db153e9b2552a1068ea6947 Mon Sep 17 00:00:00 2001 From: lbuque Date: Thu, 26 Jun 2025 18:38:01 +0800 Subject: [PATCH 146/322] modules/startup/tab5: Delete lvgl related resources. Signed-off-by: lbuque --- m5stack/modules/startup/tab5/launcher/launcher.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/m5stack/modules/startup/tab5/launcher/launcher.py b/m5stack/modules/startup/tab5/launcher/launcher.py index 87fa744f..0582b11d 100644 --- a/m5stack/modules/startup/tab5/launcher/launcher.py +++ b/m5stack/modules/startup/tab5/launcher/launcher.py @@ -7,6 +7,7 @@ from .apps import * import lvgl as lv import asyncio +import M5 class Launcher: @@ -99,7 +100,10 @@ async def _main(self): # Start ezdata service # Ezdata.start() - # Keep app manager running - while True: - await asyncio.sleep_ms(50) - await AppManager.update() + try: + # Keep app manager running + while True: + await asyncio.sleep_ms(50) + await AppManager.update() + except KeyboardInterrupt: + M5.Lcd.lvgl_deinit() From 3ef7606efec3c06d1c9556710a3996ef92c8ab50 Mon Sep 17 00:00:00 2001 From: lbuque Date: Fri, 27 Jun 2025 09:28:10 +0800 Subject: [PATCH 147/322] version.text: Update to V2.3.0. Signed-off-by: lbuque --- m5stack/version.txt | 2 +- third-party/version.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/m5stack/version.txt b/m5stack/version.txt index 06784eab..0d52044a 100644 --- a/m5stack/version.txt +++ b/m5stack/version.txt @@ -1 +1 @@ -V2.2.9 \ No newline at end of file +V2.3.0 \ No newline at end of file diff --git a/third-party/version.txt b/third-party/version.txt index f44d1570..0d52044a 100644 --- a/third-party/version.txt +++ b/third-party/version.txt @@ -1 +1 @@ -V2.2.8 \ No newline at end of file +V2.3.0 \ No newline at end of file From 5bd0654edd1b383866fd0ac002ef00b0cd3239e2 Mon Sep 17 00:00:00 2001 From: lbuque Date: Fri, 27 Jun 2025 09:31:00 +0800 Subject: [PATCH 148/322] .gitlab-ci.yml: Fix the issue that release job does not trigger. Signed-off-by: lbuque --- .gitlab-ci.yml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 6e3e4de9..9be21e3c 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -67,7 +67,11 @@ release_job: script: - echo "Releasing the M5Burn..." - python ./tools/release.py + only: + refs: + - tags + variables: + - $CI_COMMIT_TAG =~ /^release\/[0-9]+\.[0-9]+\.[0-9]+$/ + - $CI_COMMIT_REF_SLUG == "develop_m5things" tags: - uiflow-firmware - rules: - - if: '$CI_COMMIT_TAG =~ /^release\/[0-9]+\.[0-9]+\.[0-9]+$/ && $CI_COMMIT_REF_SLUG == "develop_m5things"' From 728af2c4a471ffa20ac7e8d6a6dbe76d10e4ed3e Mon Sep 17 00:00:00 2001 From: tinyu Date: Tue, 1 Jul 2025 10:12:23 +0800 Subject: [PATCH 149/322] lib/m5ui: Fix not working correctly on macOS. Signed-off-by: tinyu --- m5stack/libs/m5ui/port.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/m5stack/libs/m5ui/port.py b/m5stack/libs/m5ui/port.py index 47525058..50e1c4b5 100644 --- a/m5stack/libs/m5ui/port.py +++ b/m5stack/libs/m5ui/port.py @@ -67,7 +67,7 @@ def _m5_init(): def init(): if sys.platform == "esp32": _m5_init() - elif sys.platform == "linux": + elif sys.platform in ["linux", "darwin"]: _sdl_init() From 647a3f024e46265abc604d471f48371a78d81357 Mon Sep 17 00:00:00 2001 From: tinyu Date: Tue, 1 Jul 2025 11:53:24 +0800 Subject: [PATCH 150/322] lib/m5ui: Allow duplicate allow programs in unix platform. Signed-off-by: tinyu --- m5stack/libs/m5ui/port.py | 36 ++++++++++++++++++++++-------------- 1 file changed, 22 insertions(+), 14 deletions(-) diff --git a/m5stack/libs/m5ui/port.py b/m5stack/libs/m5ui/port.py index 50e1c4b5..c99087ee 100644 --- a/m5stack/libs/m5ui/port.py +++ b/m5stack/libs/m5ui/port.py @@ -6,30 +6,38 @@ import sys import lv_utils +_event_loop_instance = None + def _sdl_init(width=320, height=240): + global _event_loop_instance + lv.init() # Create an event loop and Register SDL display/mouse/keyboard drivers. - from lv_utils import event_loop + if _event_loop_instance is None: + from lv_utils import event_loop - event_loop = event_loop() + _event_loop_instance = event_loop() - disp_drv = lv.sdl_window_create(width, height) - disp_drv.set_default() - display = lv.display_get_default() + disp_drv = lv.sdl_window_create(width, height) + disp_drv.set_default() + display = lv.display_get_default() - group = lv.group_create() - group.set_default() + group = lv.group_create() + group.set_default() - mouse = lv.sdl_mouse_create() - mouse.set_display(display) - mouse.set_group(group) + mouse = lv.sdl_mouse_create() + mouse.set_display(display) + mouse.set_group(group) - keyboard = lv.sdl_keyboard_create() - keyboard.set_display(display) - keyboard.set_group(group) - print("SDL initialized with display size:", width, "x", height) + keyboard = lv.sdl_keyboard_create() + keyboard.set_display(display) + keyboard.set_group(group) + print("SDL initialized with display size:", width, "x", height) + + else: + print("Event loop already running, skip creating new one") def _m5_init(): From 0f5342634746bfd9d91f30526bbd9810f932f11b Mon Sep 17 00:00:00 2001 From: tinyu Date: Mon, 7 Jul 2025 16:21:40 +0800 Subject: [PATCH 151/322] lib/m5ui: Add LVGL Switch widget and docs. Signed-off-by: tinyu --- docs/en/m5ui/index.rst | 1 + docs/en/m5ui/switch.rst | 304 +++++++++++++ docs/en/refs/m5ui.switch.ref | 27 ++ docs/locales/zh_CN/LC_MESSAGES/m5ui/switch.po | 427 ++++++++++++++++++ .../switch/cores3_switch_event_example.m5f2 | 1 + .../switch/cores3_switch_event_example.py | 82 ++++ m5stack/libs/m5ui/__init__.py | 1 + m5stack/libs/m5ui/manifest.py | 2 +- m5stack/libs/m5ui/switch.py | 62 +++ 9 files changed, 906 insertions(+), 1 deletion(-) create mode 100644 docs/en/m5ui/switch.rst create mode 100644 docs/en/refs/m5ui.switch.ref create mode 100644 docs/locales/zh_CN/LC_MESSAGES/m5ui/switch.po create mode 100644 examples/m5ui/switch/cores3_switch_event_example.m5f2 create mode 100644 examples/m5ui/switch/cores3_switch_event_example.py create mode 100644 m5stack/libs/m5ui/switch.py diff --git a/docs/en/m5ui/index.rst b/docs/en/m5ui/index.rst index fe7b2283..2e310624 100644 --- a/docs/en/m5ui/index.rst +++ b/docs/en/m5ui/index.rst @@ -35,3 +35,4 @@ Classes page.rst label.rst button.rst + switch.rst diff --git a/docs/en/m5ui/switch.rst b/docs/en/m5ui/switch.rst new file mode 100644 index 00000000..9d4f2a20 --- /dev/null +++ b/docs/en/m5ui/switch.rst @@ -0,0 +1,304 @@ +.. currentmodule:: m5ui + +M5Switch +======== + +.. include:: ../refs/m5ui.switch.ref + +M5Switch is a widget that can be used to create switch in the user interface. It can be used to trigger actions when checked and uncheked. + +UiFlow2 Example +--------------- + +event switch +^^^^^^^^^^^^ + +Open the |cores3_switch_event_example.m5f2| project in UiFlow2. + +This example creates a switch that triggers an event when checked and uncheked. + +UiFlow2 Code Block: + + |cores3_switch_event_example.png| + +Example output: + + None + + +MicroPython Example +------------------- + +event switch +^^^^^^^^^^^^ + +This example creates a switch that triggers an event when checked and uncheked. + +MicroPython Code Block: + + .. literalinclude:: ../../../examples/m5ui/switch/cores3_switch_event_example.py + :language: python + :linenos: + +Example output: + + None + + +**API** +------- + +M5Switch +^^^^^^^^ + +.. autoclass:: m5ui.switch.M5Switch + :members: + :member-order: bysource + + .. py:method:: set_bg_color(color, opa, part) + + Set the background color of the switch. + + :param int color: The color to set. + :param int opa: The opacity of the color. The value should be between 0 (transparent) and 255 (opaque). + :param int part: The part of the object to apply the style to (e.g., lv.PART.MAIN). + + UiFlow2 Code Block: + + |set_bg_color_default.png| + + |set_bg_color_checked.png| + + |set_knob_color_checked.png| + + |set_knob_color_default.png| + + MicroPython Code Block: + + .. code-block:: python + + switch_0.set_bg_color(0xE7E3E7, 255, lv.PART.MAIN | lv.STATE.DEFAULT) + switch_0.set_bg_color(0x2196F3, 255, lv.PART.INDICATOR | lv.STATE.CHECKED) + + .. py:method:: set_state(state, value) + + Set the state of the Switch. + + :param int state: The state to set. + :param bool value: If True, the state is set; if False, the state is unset. + + UiFlow2 Code Block: + + |set_state.png| + + MicroPython Code Block: + + .. code-block:: python + + switch_0.set_state(lv.STATE.CHECKED, True) + + .. py:method:: has_state(state) + + Get the state of the Switch. + + :param int state: The state to get. + :return: True if the state is set, False otherwise. + :rtype: bool + + UiFlow2 Code Block: + + |has_state.png| + + MicroPython Code Block: + + .. code-block:: python + + switch_0.has_state(lv.STATE.CHECKED) + + .. py:method:: set_pos(x, y) + + Set the position of the switch. + + :param int x: The x-coordinate of the switch. + :param int y: The y-coordinate of the switch. + + UiFlow2 Code Block: + + |set_pos.png| + + MicroPython Code Block: + + .. code-block:: python + + switch_0.set_pos(100, 100) + + .. py:method:: set_x(x) + + Set the x-coordinate of the switch. + + :param int x: The x-coordinate of the switch. + + UiFlow2 Code Block: + + |set_x.png| + + MicroPython Code Block: + + .. code-block:: python + + switch_0.set_x(100) + + .. py:method:: set_y(y) + + Set the y-coordinate of the switch. + + :param int y: The y-coordinate of the switch. + + UiFlow2 Code Block: + + |set_y.png| + + MicroPython Code Block: + + .. code-block:: python + + switch_0.set_y(100) + + .. py:method:: set_size(width, height) + + Set the size of the switch. + + :param int width: The width of the switch. + :param int height: The height of the switch. + + UiFlow2 Code Block: + + |set_size.png| + + MicroPython Code Block: + + .. code-block:: python + + switch_0.set_size(100, 50) + + .. py:method:: set_width(width) + + Set the width of the switch. + + :param int width: The width of the switch. + + UiFlow2 Code Block: + + |set_width.png| + + MicroPython Code Block: + + .. code-block:: python + + switch_0.set_width(100) + + .. py::method:: get_width() + + Get the width of the switch. + + :return: The width of the switch. + :rtype: int + + UiFlow2 Code Block: + + |get_width.png| + + MicroPython Code Block: + + .. code-block:: python + + switch_0.get_width() + + .. py:method:: set_height(height) + + Set the height of the switch. + + :param int height: The height of the switch. + + UiFlow2 Code Block: + + |set_height.png| + + MicroPython Code Block: + + .. code-block:: python + + switch_0.set_height(50) + + .. py::method:: get_height() + + Get the height of the switch. + + :return: The height of the switch. + :rtype: int + + UiFlow2 Code Block: + + |get_height.png| + + MicroPython Code Block: + + .. code-block:: python + + switch_0.get_height() + + .. py:method:: align_to(obj, align, x, y) + + Align the switch to another object. + + :param lv.obj obj: The object to align to. + :param int align: The alignment type. + :param int x: The x-offset from the aligned object. + :param int y: The y-offset from the aligned object. + + UiFlow2 Code Block: + + |align_to.png| + + MicroPython Code Block: + + .. code-block:: python + + switch_0.align_to(page_0, lv.ALIGN.CENTER, 0, 0) + + .. py:method:: add_event_cb(handler, event, user_data) + + Add an event callback to the switch. The callback will be called when the specified event occurs. + + :param function handler: The callback function to call. + :param int event: The event to listen for. + :param Any user_data: Optional user data to pass to the callback. + + UiFlow2 Code Block: + + |event.png| + + MicroPython Code Block: + + .. code-block:: python + + def switch0_checked_event(event_struct): + global page0, button0 + print("checked") + + def switch0_unchecked_event(event_struct): + global page0, button0 + print("unchecked") + + def switch0_event_handler(event_struct): + global page0, button0 + event = event_struct.code + obj = event_struct.get_target_obj() + if event == lv.EVENT.VALUE_CHANGED: + if obj.has_state(lv.STATE.CHECKED): + switch0_checked_event(event_struct) + else: + switch0_unchecked_event(event_struct) + return + + switch_0.add_event_cb(switch0_event_handler, lv.EVENT.ALL, None) diff --git a/docs/en/refs/m5ui.switch.ref b/docs/en/refs/m5ui.switch.ref new file mode 100644 index 00000000..1ef2b0c7 --- /dev/null +++ b/docs/en/refs/m5ui.switch.ref @@ -0,0 +1,27 @@ +.. |align_to.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/switch/align_to.png +.. |event.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/switch/event.png +.. |has_state.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/switch/has_state.png +.. |set_bg_color_default.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/switch/set_bg_color_default.png +.. |set_bg_color_checked.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/switch/set_bg_color_checked.png +.. |set_height.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/switch/set_height.png +.. |set_pos.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/switch/set_pos.png +.. |set_size.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/switch/set_size.png +.. |set_state.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/switch/set_state.png +.. |set_knob_color_checked.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/switch/set_knob_color_checked.png +.. |set_knob_color_default.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/switch/set_knob_color_default.png +.. |set_width.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/switch/set_width.png +.. |set_x.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/switch/set_x.png +.. |set_y.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/switch/set_y.png + +.. |cores3_switch_event_example.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/switch/example.png + + + +.. |cores3_switch_event_example.m5f2| raw:: html + + + cores3_switch_event_example.m5f2 + diff --git a/docs/locales/zh_CN/LC_MESSAGES/m5ui/switch.po b/docs/locales/zh_CN/LC_MESSAGES/m5ui/switch.po new file mode 100644 index 00000000..f95459e9 --- /dev/null +++ b/docs/locales/zh_CN/LC_MESSAGES/m5ui/switch.po @@ -0,0 +1,427 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2016 - 2025 M5Stack Technology Co., Ltd +# This file is distributed under the same license as the UIFlow2 Programming +# Guide package. +# FIRST AUTHOR , 2025. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: UIFlow2 Programming Guide \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-07-07 15:19+0800\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: zh_CN\n" +"Language-Team: zh_CN \n" +"Plural-Forms: nplurals=1; plural=0;\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.16.0\n" + +#: ../../en/m5ui/switch.rst:4 ../../en/m5ui/switch.rst:52 +#: 144bc5926f274852891e514d61248e3a ae69ec18e8ce453f82020a7bca0a60ae +msgid "M5Switch" +msgstr "M5Switch" + +#: ../../en/m5ui/switch.rst:8 e8120d01253541799205e70886cf651c +msgid "" +"M5Switch is a widget that can be used to create switch in the user " +"interface. It can be used to trigger actions when checked and uncheked." +msgstr "M5Switch 是一个用于在用户界面中创建开关的控件,可在开关被选中或取消选中时触发操作。" + +#: ../../en/m5ui/switch.rst:11 f843eaaab08f469c9ef7ddaadb0becbf +msgid "UiFlow2 Example" +msgstr "UiFlow2 示例" + +#: ../../en/m5ui/switch.rst:14 ../../en/m5ui/switch.rst:33 +#: 2eca1caa159b40b9a53b95f440339b52 4414bd7ae42e4df3959743bda4f06854 +msgid "event switch" +msgstr "开关事件" + +#: ../../en/m5ui/switch.rst:16 5ba93c2b354a469dbee81beeb00233bd +msgid "Open the |cores3_switch_event_example.m5f2| project in UiFlow2." +msgstr "" + +#: ../../en/m5ui/switch.rst:18 ../../en/m5ui/switch.rst:35 +#: 3a42e2385da54548872aed34038cb4b3 3a6f5a1a1b5540e8bd4badc40fda7273 +msgid "" +"This example creates a switch that triggers an event when checked and " +"uncheked." +msgstr "本示例创建了一个开关,当其被选中或取消选中时会触发事件。" + +#: ../../en/m5ui/switch.rst:20 ../../en/m5ui/switch.rst:67 +#: ../../en/m5ui/switch.rst:92 ../../en/m5ui/switch.rst:110 +#: ../../en/m5ui/switch.rst:128 ../../en/m5ui/switch.rst:145 +#: ../../en/m5ui/switch.rst:162 ../../en/m5ui/switch.rst:180 +#: ../../en/m5ui/switch.rst:197 ../../en/m5ui/switch.rst:231 +#: ../../en/m5ui/switch.rst:268 ../../en/m5ui/switch.rst:287 +#: 01853a4cbb4540d28d2e9c0116600706 0424f6afa3d94c2b9a16a657e5fbcd98 +#: 075e1d03e602404dab1c47d841573cc4 0c511725339b4c88956dbbf665ec8ed7 +#: 523a444188304789a4a2b7829047c253 560b835a4c2b457ca0200e7c32cb5299 +#: 57a5453538c049baa1c363abe2782504 5c3b65d3ea1442b89884c105a9976607 +#: 7bf3eb87bacc48a7af4256d85f13270a c86121c5cd78401f958e2972312c751a +#: e24286979f504e6cb2a9354f850c0150 eddf23c9ab414b77b52527029f3ee734 +#: f44dce660e114656b0a3aeca192ceb7b m5ui.switch.M5Switch:12 of +msgid "UiFlow2 Code Block:" +msgstr "UiFlow2 代码块:" + +#: ../../en/m5ui/switch.rst:22 de5619fd24684366a0772f1d8984930a +msgid "|cores3_switch_event_example.png|" +msgstr "" + +#: ../../en/refs/m5ui.switch.ref:16 6302fd9454dc45528989cd6abc3cd7dc +msgid "cores3_switch_event_example.png" +msgstr "" + +#: ../../en/m5ui/switch.rst:24 ../../en/m5ui/switch.rst:43 +#: 7e5a1fd5134f43afb64fcbbd603ca1a3 f6c6674aa1dc48559f37e66e5025f109 +msgid "Example output:" +msgstr "示例输出:" + +#: ../../en/m5ui/switch.rst:26 ../../en/m5ui/switch.rst:45 +#: ../../en/m5ui/switch.rst:65 ../../en/m5ui/switch.rst:90 +#: ../../en/m5ui/switch.rst:126 ../../en/m5ui/switch.rst:143 +#: ../../en/m5ui/switch.rst:160 ../../en/m5ui/switch.rst:178 +#: ../../en/m5ui/switch.rst:195 ../../en/m5ui/switch.rst:229 +#: ../../en/m5ui/switch.rst:266 ../../en/m5ui/switch.rst:285 +#: 01cc6ec217bc4efbb82226f78ac9bddb 03aeffdcaa344a47bf570030b3469427 +#: 309c4b1717d442389b488f246ef27ff9 3b2660d27b534e1c97e95b54a25c75ea +#: 4d15aba6ee1f46c7869b39a27fb8e4ae 804dcbd5cdee482c86d0092bdbc4cf25 +#: 865aea79a85743c5af0b9316bcc85f4d a40dd4c82038497ea28f7df84f405e71 +#: b8257793d81449af8eba30888d4a2515 d107e8b8bcf1443bb9ddff4461d723dc +#: e7a968d5a98f440d9d5ae784f4aa6464 e857ad3f16ab47179a71a2d61d8322bf +#: f9355921e11748d7a567d8c07712d6dd m5ui.switch.M5Switch:14 of +msgid "None" +msgstr "无" + +#: ../../en/m5ui/switch.rst:30 95cab63deb5f4d5f85b0c9fb9092e3bb +msgid "MicroPython Example" +msgstr "MicroPython 示例" + +#: ../../en/m5ui/switch.rst:37 ../../en/m5ui/switch.rst:77 +#: ../../en/m5ui/switch.rst:96 ../../en/m5ui/switch.rst:114 +#: ../../en/m5ui/switch.rst:132 ../../en/m5ui/switch.rst:149 +#: ../../en/m5ui/switch.rst:166 ../../en/m5ui/switch.rst:184 +#: ../../en/m5ui/switch.rst:201 ../../en/m5ui/switch.rst:235 +#: ../../en/m5ui/switch.rst:272 ../../en/m5ui/switch.rst:291 +#: 193f872f16f54b128e61bd26461c7ce4 5c7cc888c53e4c55804777194a38f2f4 +#: 5cdbf26f3a2743a6ba7e42b375cce3f2 5e4f7f1e39b44e99a55326cd5dabd386 +#: 760e8bab26be42e7b6f833be20c13321 7e9e3519808d41d9a7845f9a056c362c +#: 863f402a224a4f9cadabc85cfc1a763d a9edb27db62543fd8c0886e50d56cada +#: c136c379f3094a32b3f4eec45da6f436 daf2a6adb2624020886a5ac49dfd4076 +#: e233e2611a9546d69b6fd9aa0578b1db eef2013d30ff4b199587cfa0b41a4dae +#: f8053399e629420c897bebea835c5e22 m5ui.switch.M5Switch:16 of +msgid "MicroPython Code Block:" +msgstr "MicroPython 代码块:" + +#: ../../en/m5ui/switch.rst:49 7d8762e883e3422d9575ab3f2ec61129 +msgid "**API**" +msgstr "**API**" + +#: aebc09d502de4d40a690cf2d04baac75 m5ui.switch.M5Switch:1 of +msgid "Bases: :py:class:`~lvgl.switch`" +msgstr "" + +#: 30d7b60cbe77476bbaf58d99a5949a6b m5ui.switch.M5Switch:1 of +msgid "Create a switch object." +msgstr "创建一个开关对象。" + +#: ../../en/m5ui/switch.rst 0f52ae4b17e8494d995e51a33599a2ce +#: 100a3aa80366401bb3bddf0ad09382ae 1a21f9655a154fda9db3a0036377cec9 +#: 1a7f2a1c872b450aa08751fbef8acf92 1d2c69b90c124fb4b6551cdcffbc35f3 +#: 2c9bfa3adc5b43a5adbaf636229f9f45 645c976a05334742ab998b08e334f0d8 +#: 762c53f7ee694f7cb2ece141c8acf0dd 7a2813f6b993491080a8823310b359e3 +#: c9e247f55ab04108beef8351981d3139 dab8ca298e634ef582025efc4d607dde +#: f68899b6b1fb47e7a55343891e95ee95 +msgid "Parameters" +msgstr "参数" + +#: 8084ba15851e449db0ba19f543177916 m5ui.switch.M5Switch:3 of +msgid "The x position of the switch." +msgstr "开关的 x 坐标。" + +#: bed123eba1f341548411ead6edea76d5 m5ui.switch.M5Switch:4 of +msgid "The y position of the switch." +msgstr "开关的 y 坐标。" + +#: ab4f2542216f44a59b7b0cf45deb2df0 m5ui.switch.M5Switch:5 of +msgid "" +"The width of the switch." +msgstr "开关的宽度。 + +#: 50e48ea8e28d4a11a8ce8321ba3446d3 m5ui.switch.M5Switch:6 of +msgid "" +"The height of the switch." +msgstr "开关的高度。 + +#: da8c3de9de314a318834840bc5326860 m5ui.switch.M5Switch:7 of +msgid "The color of the switch in the off state in hexadecimal format." +msgstr "开关在关闭状态下的颜色(十六进制)。" + +#: 0e9653609e6149efa27a49b3f6eeca00 m5ui.switch.M5Switch:8 of +msgid "The color of the switch in the on state in hexadecimal format." +msgstr "开关在开启状态下的颜色(十六进制)。" + +#: 559f5e1f78be4fcdab74d10f1c00f095 m5ui.switch.M5Switch:9 of +msgid "" +"This color refers to the color of the circle on the switch in hexadecimal" +" format." +msgstr "此颜色指开关圆钮的颜色(十六进制)。" + +#: 1785451c0e804514a087578146f9944c m5ui.switch.M5Switch:10 of +msgid "" +"The parent object to attach the switch to. If not specified, the switch " +"will be attached to the default screen." +msgstr "开关要附加到的父对象;若未指定,则附加到默认屏幕。" + +#: ../../en/m5ui/switch.rst:60 62dc326fb9f04e04875e9e6d62b52021 +msgid "Set the background color of the switch." +msgstr "设置开关的背景颜色。" + +#: ../../en/m5ui/switch.rst:62 342c4ec62d884a0a9af96464117c556e +msgid "The color to set." +msgstr "要设置的颜色。" + +#: ../../en/m5ui/switch.rst:63 94bf8505647646beb0b0aad47d4ccf4c +msgid "" +"The opacity of the color. The value should be between 0 (transparent) and" +" 255 (opaque)." +msgstr "颜色的不透明度,取值范围 0(透明)至 255(不透明)。" + +#: ../../en/m5ui/switch.rst:64 5cced61eb3464308a02b5e80c4ce9a5e +msgid "The part of the object to apply the style to (e.g., lv.PART.MAIN)." +msgstr "应用样式的对象部分(如 lv.PART.MAIN)。" + +#: ../../en/m5ui/switch.rst 0c8fe987f81d4cd5a0c8b89967ec1e1a +#: 1e2b8b8013254cdaa126d2cf73749319 1e73944b4ebe4add9d538b6c62930917 +#: 2ea508f721e9426280b422140c7b48fb 4eafedcec364431291d60f27c6e41dc9 +#: 7019e89729df42b0b027404d0e296dff 7e35a552ee69425aa017aa0ba976de27 +#: 9ab07ba0f7b54b4b92fba81507de973e b3602d5ddbac435d9ac87a8fe9ed3784 +#: cd28e03636884adfb87f9333a8f0e879 d7d8f3b14bc1497f8e7bb37262009ee8 +msgid "Returns" +msgstr "" + +#: ../../en/m5ui/switch.rst:69 e968e78e617342a4a9e52985a0de16d9 +msgid "|set_bg_color_default.png|" +msgstr "" + +#: ../../en/refs/m5ui.switch.ref:4 4a2b4dcae3ad491aa9448290d978c0e9 +msgid "set_bg_color_default.png" +msgstr "" + +#: ../../en/m5ui/switch.rst:71 095b90d472674bc89c60259aaf0082b2 +msgid "|set_bg_color_checked.png|" +msgstr "" + +#: ../../en/refs/m5ui.switch.ref:5 e79cf68104e24cf2a78a0da0103368fa +msgid "set_bg_color_checked.png" +msgstr "" + +#: ../../en/m5ui/switch.rst:73 869291a671fd4fcaac95f18c17f796fe +msgid "|set_knob_color_checked.png|" +msgstr "" + +#: ../../en/refs/m5ui.switch.ref:10 ea0edb2c8e5f4e99b21c52cde5d320e7 +msgid "set_knob_color_checked.png" +msgstr "" + +#: ../../en/m5ui/switch.rst:75 95ac7defc8bc4247a1a34ba83b13b88b +msgid "|set_knob_color_default.png|" +msgstr "" + +#: ../../en/refs/m5ui.switch.ref:11 9f7dd38e187b41cc8b6989b516d5d969 +msgid "set_knob_color_default.png" +msgstr "" + +#: ../../en/m5ui/switch.rst:86 849a8d24abcd479c91aa3c5db5f9d39d +msgid "Set the state of the Switch." +msgstr "设置开关的状态。" + +#: ../../en/m5ui/switch.rst:88 a2e9d1ae64e44201bc8dd71e4c31eba4 +msgid "The state to set." +msgstr "要设置的状态。" + +#: ../../en/m5ui/switch.rst:89 2c4b99e1d007479988081ce5e55a59b7 +msgid "If True, the state is set; if False, the state is unset." +msgstr "若为 True 表示设置状态;为 False 则取消状态。" + +#: ../../en/m5ui/switch.rst:94 22309eb864f44e79a24af878c3a6df67 +msgid "|set_state.png|" +msgstr "" + +#: ../../en/refs/m5ui.switch.ref:9 a9ad005e47cd4e69abcdf1fc92ba7bb6 +msgid "set_state.png" +msgstr "" + +#: ../../en/m5ui/switch.rst:104 9fdd83843760449a9ebf23c0b9f39ab9 +msgid "Get the state of the Switch." +msgstr "获取开关的状态。" + +#: ../../en/m5ui/switch.rst:106 a546270ad5ad4f1d8970c2024216ac2c +msgid "The state to get." +msgstr "要获取的状态。" + +#: ../../en/m5ui/switch.rst:107 a239a42ae833475480dc1ee8cafdd9ec +msgid "True if the state is set, False otherwise." +msgstr "若状态已设置则返回 True,否则返回 False。" + +#: ../../en/m5ui/switch.rst d413beb2ddbc4709801204011f1af9ce +msgid "Return type" +msgstr "返回类型" + +#: ../../en/m5ui/switch.rst:112 be35675f7e704862b6a31ef04c966037 +msgid "|has_state.png|" +msgstr "" + +#: ../../en/refs/m5ui.switch.ref:3 5768c2fa1d194646ba7d44efc3ef92a5 +msgid "has_state.png" +msgstr "" + +#: ../../en/m5ui/switch.rst:122 c8c692c7dcde4f9d8c3ae04e388c20dc +msgid "Set the position of the switch." +msgstr "设置开关的位置。" + +#: ../../en/m5ui/switch.rst:124 ../../en/m5ui/switch.rst:142 +#: 7e7d4f99e24f4abe88dc15233a92e2cf f9b03d88a0b949a8ab091946c8295688 +msgid "The x-coordinate of the switch." +msgstr "开关的 x 坐标。" + +#: ../../en/m5ui/switch.rst:125 ../../en/m5ui/switch.rst:159 +#: c9913dab3f70421093a7a42d8d8dd406 e23e3ca7bbd04270813db1cdd11278ed +msgid "The y-coordinate of the switch." +msgstr "开关的 y 坐标。" + +#: ../../en/m5ui/switch.rst:130 9354fb27fb4c47579053924ca3726022 +msgid "|set_pos.png|" +msgstr "" + +#: ../../en/refs/m5ui.switch.ref:7 379764c9fce443479fbc3ac603f384a9 +msgid "set_pos.png" +msgstr "" + +#: ../../en/m5ui/switch.rst:140 f19c85b772144063b5649f922d8363ff +msgid "Set the x-coordinate of the switch." +msgstr "设置开关的 x 坐标。" + +#: ../../en/m5ui/switch.rst:147 f5107c8219074e5baef497e022eee811 +msgid "|set_x.png|" +msgstr "" + +#: ../../en/refs/m5ui.switch.ref:13 e07cc4171bda4dd38589694dd899803c +msgid "set_x.png" +msgstr "" + +#: ../../en/m5ui/switch.rst:157 4d99ecb7c44f4139bc528d54f60e0fe7 +msgid "Set the y-coordinate of the switch." +msgstr "设置开关的 y 坐标。" + +#: ../../en/m5ui/switch.rst:164 ad968ee9f52049749994ed65a717d555 +msgid "|set_y.png|" +msgstr "" + +#: ../../en/refs/m5ui.switch.ref:14 2461393db1e6476d9f8f80546192cd64 +msgid "set_y.png" +msgstr "" + +#: ../../en/m5ui/switch.rst:174 fd8d1cfd7d8940f984765fa1f82fa8eb +msgid "Set the size of the switch." +msgstr "设置开关的尺寸。" + +#: ../../en/m5ui/switch.rst:176 ../../en/m5ui/switch.rst:194 +#: 79010dacfc20429cb530555c8924faf0 a012dd7a36ee44deafa9ea7c06ff1feb +msgid "The width of the switch." +msgstr "开关的宽度。" + +#: ../../en/m5ui/switch.rst:177 ../../en/m5ui/switch.rst:228 +#: 064b526b488843c6b99193d0d180da6b c9e4f5820c5842159b31f077b3643993 +msgid "The height of the switch." +msgstr "开关的高度。" + +#: ../../en/m5ui/switch.rst:182 d3b4a47d886a46f0b2368fbc33c86ea1 +msgid "|set_size.png|" +msgstr "" + +#: ../../en/refs/m5ui.switch.ref:8 5e8424792d59402eb45a692733b7f812 +msgid "set_size.png" +msgstr "" + +#: ../../en/m5ui/switch.rst:192 c64fd4a7fc294fdc923dd111de7fe485 +msgid "Set the width of the switch." +msgstr "设置开关的宽度。" + +#: ../../en/m5ui/switch.rst:199 b5ee9c5345354f7eb55ee65b7f5f3988 +msgid "|set_width.png|" +msgstr "" + +#: ../../en/refs/m5ui.switch.ref:12 5005fb7afa8b455688f290503a303699 +msgid "set_width.png" +msgstr "" + +#: ../../en/m5ui/switch.rst:226 0c8d53f532a04212bec52a9218366cd4 +msgid "Set the height of the switch." +msgstr "设置开关的高度。" + +#: ../../en/m5ui/switch.rst:233 18b0e98c1b1a4869a2497264483fa7e6 +msgid "|set_height.png|" +msgstr "" + +#: ../../en/refs/m5ui.switch.ref:6 c0bd8d06c83f4c36a6683d0e829ae241 +msgid "set_height.png" +msgstr "" + +#: ../../en/m5ui/switch.rst:260 9e52c48b59ea442f92020c5d3c0cfd8b +msgid "Align the switch to another object." +msgstr "将开关对齐到另一个对象。" + +#: ../../en/m5ui/switch.rst:262 23eda6e5670b41dfbf52d35fb46298bd +msgid "The object to align to." +msgstr "要对齐到的对象。" + +#: ../../en/m5ui/switch.rst:263 4c6816a83b0c4425aaeb356d801ab185 +msgid "The alignment type." +msgstr "对齐类型。" + +#: ../../en/m5ui/switch.rst:264 7ab11305d2844166827a925dbc613258 +msgid "The x-offset from the aligned object." +msgstr "相对于对齐对象的 x 偏移量。" + +#: ../../en/m5ui/switch.rst:265 20d0c0d3c9f142b78dc5b900177b10e5 +msgid "The y-offset from the aligned object." +msgstr "相对于对齐对象的 y 偏移量。" + +#: ../../en/m5ui/switch.rst:270 b12d9fe7086a442e954be27ea8fa1d80 +msgid "|align_to.png|" +msgstr "" + +#: ../../en/refs/m5ui.switch.ref:1 afba3bd73dac48579a7fc40c12c722e2 +msgid "align_to.png" +msgstr "" + +#: ../../en/m5ui/switch.rst:280 b4ea4bec4f404c29b5bcd321510c3177 +msgid "" +"Add an event callback to the switch. The callback will be called when the" +" specified event occurs." +msgstr "为开关添加事件回调;当指定事件发生时将调用该回调。" + +#: ../../en/m5ui/switch.rst:282 715794ce79f2430eac35d80ae17137dc +msgid "The callback function to call." +msgstr "要调用的回调函数。" + +#: ../../en/m5ui/switch.rst:283 70b470e696224c95b5b768225c4591f4 +msgid "The event to listen for." +msgstr "要监听的事件。" + +#: ../../en/m5ui/switch.rst:284 a9e2c4f962fb4957969a51effe2e2389 +msgid "Optional user data to pass to the callback." +msgstr "可选的用户数据,将传递给回调函数。" + +#: ../../en/m5ui/switch.rst:289 1b26a6816bb843c28fb11298e766a040 +msgid "|event.png|" +msgstr "" + +#: ../../en/refs/m5ui.switch.ref:2 5a7859c39560401a952fe530d9385eb3 +msgid "event.png" +msgstr "" + diff --git a/examples/m5ui/switch/cores3_switch_event_example.m5f2 b/examples/m5ui/switch/cores3_switch_event_example.m5f2 new file mode 100644 index 00000000..29e726ee --- /dev/null +++ b/examples/m5ui/switch/cores3_switch_event_example.m5f2 @@ -0,0 +1 @@ +{"version":"V2.0","versionNumber":"V2.3.0","type":"cores3","components":[{"name":"page0","type":"lvgl_page","layer":0,"screenId":"builtin","screenName":"","id":"m9@lpi+_=W%+Mg*k","createTime":1751869561848,"backgroundColor":"#ffffff","isLVGL":true,"isSelected":true},{"name":"switch0","type":"lvgl_switch","layer":1,"screenId":"builtin","screenName":"","id":"s-ESKHmEhR7aT8SQ","createTime":1751869566947,"x":128,"y":91,"width":60,"height":30,"knobColor":"#ffffff","backgroundColor":"#e7e3e7","checkedBackgroundColor":"#2196f3","pageId":"m9@lpi+_=W%+Mg*k","isLVGL":true,"isSelected":false}],"resources":[{"hardware":["hardware_button","hardware_pin_button","imu","speaker","touch","als","mic","sdcard"]}],"units":[],"hats":[],"bases":[],"i2cs":[],"blockly":"truepage0switch0palette#666666255switch0palette#33ff33255trueswitch0CHECKEDswitch0 checkedswitch0UNCHECKEDswitch0 unchecked","screen":[{"simulationName":"Built-in","type":"builtin","width":320,"height":240,"scale":0.77,"screenName":"","blockId":"","screenColorType":0,"rotation":1,"id":"builtin","createTime":1751869561847}],"logicWhenNum":0,"customList":[]} \ No newline at end of file diff --git a/examples/m5ui/switch/cores3_switch_event_example.py b/examples/m5ui/switch/cores3_switch_event_example.py new file mode 100644 index 00000000..b23895cb --- /dev/null +++ b/examples/m5ui/switch/cores3_switch_event_example.py @@ -0,0 +1,82 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import os, sys, io +import M5 +from M5 import * +import m5ui +import lvgl as lv + + +page0 = None +switch0 = None + + +def switch0_checked_event(event_struct): + global page0, switch0 + + print("switch0 checked") + + +def switch0_unchecked_event(event_struct): + global page0, switch0 + + print("switch0 unchecked") + + +def switch0_event_handler(event_struct): + global page0, switch0 + event = event_struct.code + obj = event_struct.get_target_obj() + if event == lv.EVENT.VALUE_CHANGED: + if obj.has_state(lv.STATE.CHECKED): + switch0_checked_event(event_struct) + else: + switch0_unchecked_event(event_struct) + return + + +def setup(): + global page0, switch0 + + M5.begin() + Widgets.setRotation(1) + m5ui.init() + page0 = m5ui.M5Page(bg_c=0xFFFFFF) + switch0 = m5ui.M5Switch( + x=128, + y=91, + w=60, + h=30, + bg_c=0xE7E3E7, + bg_c_checked=0x2196F3, + circle_c=0xFFFFFF, + parent=page0, + ) + + switch0.add_event_cb(switch0_event_handler, lv.EVENT.ALL, None) + + page0.screen_load() + switch0.set_bg_color(0x666666, 255, lv.PART.MAIN | lv.STATE.DEFAULT) + switch0.set_bg_color(0x33FF33, 255, lv.PART.INDICATOR | lv.STATE.CHECKED) + + +def loop(): + global page0, switch0 + M5.update() + + +if __name__ == "__main__": + try: + setup() + while True: + loop() + except (Exception, KeyboardInterrupt) as e: + try: + m5ui.deinit() + from utility import print_error_msg + + print_error_msg(e) + except ImportError: + print("please update to latest firmware") diff --git a/m5stack/libs/m5ui/__init__.py b/m5stack/libs/m5ui/__init__.py index 0f02e9ec..178beb73 100644 --- a/m5stack/libs/m5ui/__init__.py +++ b/m5stack/libs/m5ui/__init__.py @@ -8,6 +8,7 @@ "M5Button": "button", "M5Label": "label", "M5Page": "page", + "M5Switch": "switch", } diff --git a/m5stack/libs/m5ui/manifest.py b/m5stack/libs/m5ui/manifest.py index 5f40e6d5..4c1c75a1 100644 --- a/m5stack/libs/m5ui/manifest.py +++ b/m5stack/libs/m5ui/manifest.py @@ -4,7 +4,7 @@ package( "m5ui", - ("__init__.py", "base.py", "button.py", "label.py", "page.py", "port.py"), + ("__init__.py", "base.py", "button.py", "label.py", "page.py", "switch.py", "port.py"), base_path="..", opt=0, ) diff --git a/m5stack/libs/m5ui/switch.py b/m5stack/libs/m5ui/switch.py new file mode 100644 index 00000000..01f4bd81 --- /dev/null +++ b/m5stack/libs/m5ui/switch.py @@ -0,0 +1,62 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +from .base import M5Base +import lvgl as lv + + +class M5Switch(lv.switch): + """Create a switch object. + + :param int x: The x position of the switch. + :param int y: The y position of the switch. + :param int w: The width of the switch. + :param int h: The height of the switch. + :param int bg_c: The color of the switch in the off state in hexadecimal format. + :param int bg_c_checked: The color of the switch in the on state in hexadecimal format. + :param int circle_c: This color refers to the color of the circle on the switch in hexadecimal format. + :param lv.obj parent: The parent object to attach the switch to. If not specified, the switch will be attached to the default screen. + + UiFlow2 Code Block: + + None + + MicroPython Code Block: + + .. code-block:: python + + from m5ui import M5Switch + import lvgl as lv + + m5ui.init() + switch_0 = M5Switch(x=120, y=80, w=60, h=30, bg_c=0xE7E3E7, color=0x2196F3, parent=page0) + """ + + def __init__( + self, + x=0, + y=0, + w=0, + h=0, + bg_c=0xE7E3E7, + bg_c_checked=0x0288FB, + circle_c=0xFFFFFF, + parent=None, + ): + if parent is None: + parent = lv.screen_active() + super().__init__(parent) + self.set_pos(x, y) + self.set_bg_color(bg_c, 255, lv.PART.MAIN | lv.STATE.DEFAULT) + self.set_bg_color(bg_c_checked, 255, lv.PART.INDICATOR | lv.STATE.CHECKED) + self.set_bg_color(circle_c, 255, lv.PART.KNOB | lv.STATE.DEFAULT) + self.set_size(w, h) + + def __getattr__(self, name): + if hasattr(M5Base, name): + method = getattr(M5Base, name) + bound_method = lambda *args, **kwargs: method(self, *args, **kwargs) + setattr(self, name, bound_method) + return bound_method + raise AttributeError(f"'{self.__class__.__name__}' object has no attribute '{name}'") From 7d186bedd8c4699b825eb62cc294a5f7681698c8 Mon Sep 17 00:00:00 2001 From: tinyu Date: Thu, 10 Jul 2025 09:35:20 +0800 Subject: [PATCH 152/322] docs: Update m5ui.switch pic url. Signed-off-by: tinyu --- docs/en/m5ui/switch.rst | 8 ++------ docs/en/refs/m5ui.switch.ref | 8 ++------ 2 files changed, 4 insertions(+), 12 deletions(-) diff --git a/docs/en/m5ui/switch.rst b/docs/en/m5ui/switch.rst index 9d4f2a20..0cbea024 100644 --- a/docs/en/m5ui/switch.rst +++ b/docs/en/m5ui/switch.rst @@ -65,13 +65,9 @@ M5Switch UiFlow2 Code Block: - |set_bg_color_default.png| + |set_bg_color.png| - |set_bg_color_checked.png| - - |set_knob_color_checked.png| - - |set_knob_color_default.png| + |set_knob_color.png| MicroPython Code Block: diff --git a/docs/en/refs/m5ui.switch.ref b/docs/en/refs/m5ui.switch.ref index 1ef2b0c7..9566e61c 100644 --- a/docs/en/refs/m5ui.switch.ref +++ b/docs/en/refs/m5ui.switch.ref @@ -1,22 +1,18 @@ .. |align_to.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/switch/align_to.png .. |event.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/switch/event.png .. |has_state.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/switch/has_state.png -.. |set_bg_color_default.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/switch/set_bg_color_default.png -.. |set_bg_color_checked.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/switch/set_bg_color_checked.png +.. |set_bg_color.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/switch/set_bg_color.png .. |set_height.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/switch/set_height.png .. |set_pos.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/switch/set_pos.png .. |set_size.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/switch/set_size.png .. |set_state.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/switch/set_state.png -.. |set_knob_color_checked.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/switch/set_knob_color_checked.png -.. |set_knob_color_default.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/switch/set_knob_color_default.png +.. |set_knob_color.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/switch/set_knob_color.png .. |set_width.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/switch/set_width.png .. |set_x.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/switch/set_x.png .. |set_y.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/switch/set_y.png .. |cores3_switch_event_example.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/switch/example.png - - .. |cores3_switch_event_example.m5f2| raw:: html Date: Fri, 4 Jul 2025 09:37:18 +0800 Subject: [PATCH 153/322] docs: Fix configuration option parameter error. Signed-off-by: tinyu --- docs/en/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/en/conf.py b/docs/en/conf.py index 03160a65..e7a96041 100644 --- a/docs/en/conf.py +++ b/docs/en/conf.py @@ -65,7 +65,7 @@ autodoc_default_options = { "members": "", "show-inheritance": "", - "members-order": "bysource", + "member-order": "bysource", } autodoc_typehints = "description" From 95f0bf25db32ccf64432ccbecded40e82337e8c0 Mon Sep 17 00:00:00 2001 From: tinyu Date: Wed, 9 Jul 2025 09:53:25 +0800 Subject: [PATCH 154/322] lib/m5ui: Add LVGL Image widget and docs. Signed-off-by: tinyu --- docs/en/m5ui/image.rst | 174 +++++++++++++ docs/en/m5ui/index.rst | 1 + docs/en/refs/m5ui.image.ref | 21 ++ docs/locales/zh_CN/LC_MESSAGES/m5ui/image.po | 241 ++++++++++++++++++ .../m5ui/image/cores3_show_image_example.m5f2 | 1 + .../m5ui/image/cores3_show_image_example.py | 49 ++++ m5stack/libs/m5ui/__init__.py | 1 + m5stack/libs/m5ui/image.py | 67 +++++ m5stack/libs/m5ui/manifest.py | 2 +- 9 files changed, 556 insertions(+), 1 deletion(-) create mode 100644 docs/en/m5ui/image.rst create mode 100644 docs/en/refs/m5ui.image.ref create mode 100644 docs/locales/zh_CN/LC_MESSAGES/m5ui/image.po create mode 100644 examples/m5ui/image/cores3_show_image_example.m5f2 create mode 100644 examples/m5ui/image/cores3_show_image_example.py create mode 100644 m5stack/libs/m5ui/image.py diff --git a/docs/en/m5ui/image.rst b/docs/en/m5ui/image.rst new file mode 100644 index 00000000..a41941a1 --- /dev/null +++ b/docs/en/m5ui/image.rst @@ -0,0 +1,174 @@ +.. currentmodule:: m5ui + +M5Image +======== + +.. include:: ../refs/m5ui.image.ref + +M5Image is a widget that can be used to create image in the user interface. + +UiFlow2 Example +--------------- + +show image +^^^^^^^^^^^^ + +Open the |cores3_show_image_example.m5f2| project in UiFlow2. + +This example shows how to display an image on the screen. + +UiFlow2 Code Block: + + |cores3_show_image_example.png| + +Example output: + + None + + +MicroPython Example +------------------- + +show image +^^^^^^^^^^^^ + +This example shows how to display an image on the screen. + +MicroPython Code Block: + + .. literalinclude:: ../../../examples/m5ui/image/cores3_show_image_example.py + :language: python + :linenos: + +Example output: + + None + + +**API** +------- + +M5Image +^^^^^^^^ + +.. autoclass:: m5ui.image.M5Image + :members: + + .. py:method:: set_flag(flag, value) + + Set a flag on the object. If ``value`` is True, the flag is added; if False, the flag is removed. + + :param int flag: The flag to set. + :param bool value: If True, the flag is added; if False, the flag is removed. + + UiFlow2 Code Block: + + |set_hidden.png| + + MicroPython Code Block: + + .. code-block:: python + + image_0.set_flag(lv.obj.FLAG.HIDDEN, True) + + .. py:method:: set_pos(x, y) + + Set the position of the image. + + :param int x: The x-coordinate of the image. + :param int y: The y-coordinate of the image. + + UiFlow2 Code Block: + + |set_pos.png| + + MicroPython Code Block: + + .. code-block:: python + + image_0.set_pos(100, 100) + + .. py:method:: set_x(x) + + Set the x-coordinate of the image. + + :param int x: The x-coordinate of the image. + + UiFlow2 Code Block: + + |set_x.png| + + MicroPython Code Block: + + .. code-block:: python + + image_0.set_x(100) + + .. py:method:: set_y(y) + + Set the y-coordinate of the image. + + :param int y: The y-coordinate of the image. + + UiFlow2 Code Block: + + |set_y.png| + + MicroPython Code Block: + + .. code-block:: python + + image_0.set_y(100) + + .. py::method:: get_width() + + Get the width of the image. + + :return: The width of the image. + :rtype: int + + UiFlow2 Code Block: + + |get_width.png| + + MicroPython Code Block: + + .. code-block:: python + + image_0.get_width() + + .. py::method:: get_height() + + Get the height of the image. + + :return: The height of the image. + :rtype: int + + UiFlow2 Code Block: + + |get_height.png| + + MicroPython Code Block: + + .. code-block:: python + + image_0.get_height() + + .. py:method:: align_to(obj, align, x, y) + + Align the image to another object. + + :param lv.obj obj: The object to align to. + :param int align: The alignment type. + :param int x: The x-offset from the aligned object. + :param int y: The y-offset from the aligned object. + + UiFlow2 Code Block: + + |align_to.png| + + MicroPython Code Block: + + .. code-block:: python + + image_0.align_to(page_0, lv.ALIGN.CENTER, 0, 0) diff --git a/docs/en/m5ui/index.rst b/docs/en/m5ui/index.rst index 2e310624..3f41ab34 100644 --- a/docs/en/m5ui/index.rst +++ b/docs/en/m5ui/index.rst @@ -36,3 +36,4 @@ Classes label.rst button.rst switch.rst + image.rst diff --git a/docs/en/refs/m5ui.image.ref b/docs/en/refs/m5ui.image.ref new file mode 100644 index 00000000..79354172 --- /dev/null +++ b/docs/en/refs/m5ui.image.ref @@ -0,0 +1,21 @@ +.. |align_to.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/image/align_to.png +.. |get_height.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/image/get_height.png +.. |get_width.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/image/get_width.png +.. |set_hidden.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/image/set_hidden.png +.. |set_pos.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/image/set_pos.png +.. |set_size.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/image/set_size.png +.. |set_image.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/image/set_image.png +.. |set_x.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/image/set_x.png +.. |set_y.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/image/set_y.png + + +.. |cores3_show_image_example.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/image/example.png + +.. |cores3_show_image_example.m5f2| raw:: html + + + cores3_show_image_example.m5f2 + diff --git a/docs/locales/zh_CN/LC_MESSAGES/m5ui/image.po b/docs/locales/zh_CN/LC_MESSAGES/m5ui/image.po new file mode 100644 index 00000000..b4728ea8 --- /dev/null +++ b/docs/locales/zh_CN/LC_MESSAGES/m5ui/image.po @@ -0,0 +1,241 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2016 - 2025 M5Stack Technology Co., Ltd +# This file is distributed under the same license as the UIFlow2 Programming +# Guide package. +# FIRST AUTHOR , 2025. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: UIFlow2 Programming Guide \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-07-09 09:51+0800\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: zh_CN\n" +"Language-Team: zh_CN \n" +"Plural-Forms: nplurals=1; plural=0;\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.16.0\n" + +#: ../../en/m5ui/image.rst:4 ../../en/m5ui/image.rst:52 +#: 59ef4add906b41b1804e3973bf4a51a7 b16cbe9a816d4817b4f9deddfe26f513 +msgid "M5Image" +msgstr "" + +#: ../../en/m5ui/image.rst:8 80f11e838d2b431fbe8e44bbc44c7365 +msgid "" +"M5Image is a widget that can be used to create image in the user " +"interface." +msgstr "M5Image 是一个可在用户界面中显示图像的控件。" + +#: ../../en/m5ui/image.rst:11 fba241f4f15a4a9da102afd3b911b692 +msgid "UiFlow2 Example" +msgstr "UiFlow2 示例" + +#: ../../en/m5ui/image.rst:14 ../../en/m5ui/image.rst:33 +#: 5d49c0d041244751a234dd8bc5a0a49b +msgid "show image" +msgstr "显示图像" + +#: ../../en/m5ui/image.rst:16 c23ec2641c4e463896cecb9755e24e2d +msgid "Open the |cores3_show_image_example.m5f2| project in UiFlow2." +msgstr "在 UiFlow2 中打开 |cores3_show_image_example.m5f2| 项目。" + +#: ../../en/m5ui/image.rst:18 ../../en/m5ui/image.rst:35 +#: 1df0f203a7d64485aee4e85e84d6d0b5 +msgid "This example shows how to display an image on the screen." +msgstr "该示例演示如何在屏幕上显示图像。" + +#: ../../en/m5ui/image.rst:20 ../../en/m5ui/image.rst:64 +#: ../../en/m5ui/image.rst:81 ../../en/m5ui/image.rst:97 +#: ../../en/m5ui/image.rst:113 ../../en/m5ui/image.rst:166 +#: 1a10b6b6eac849379defe1f60f01e63c 31cb071512d148b89648a79439bccdaa +#: c951f731ddfc4e2b8026d967f5d261f0 m5ui.image.M5Image.set_image:5 of +msgid "UiFlow2 Code Block:" +msgstr "UiFlow2 代码块:" + +#: ../../en/m5ui/image.rst:22 8c81ea4af0da4b4b947ac731c495add0 +msgid "|cores3_show_image_example.png|" +msgstr "" + +#: ../../en/refs/m5ui.image.ref:14 eff9728ed324422f82fa19876de13c62 +msgid "cores3_show_image_example.png" +msgstr "" + +#: ../../en/m5ui/image.rst:24 ../../en/m5ui/image.rst:43 +#: 4d95d498196842dab975c900b9a55ddc +msgid "Example output:" +msgstr "示例输出:" + +#: ../../en/m5ui/image.rst:26 ../../en/m5ui/image.rst:45 +#: d921d19fb06941f58e35e462941733c8 +msgid "None" +msgstr "" + +#: ../../en/m5ui/image.rst:30 aecdee844f1543c993e1d78ab48580c9 +msgid "MicroPython Example" +msgstr "MicroPython 示例" + +#: ../../en/m5ui/image.rst:37 ../../en/m5ui/image.rst:68 +#: ../../en/m5ui/image.rst:85 ../../en/m5ui/image.rst:101 +#: ../../en/m5ui/image.rst:117 ../../en/m5ui/image.rst:170 +#: 0a00eccad0b04f3aaa2c1d7a7394410f 1d7f94f68a834722a4358d61315754e8 +#: 297b0cb0f5784bf597709765289779e5 m5ui.image.M5Image:8 +#: m5ui.image.M5Image.set_image:9 of +msgid "MicroPython Code Block:" +msgstr "MicroPython 代码块:" + +#: ../../en/m5ui/image.rst:49 c2ceb3f64d0546be8007e88058e20538 +msgid "**API**" +msgstr "" + +#: 049a75f9354943ed8657fe78a5cbe3f7 m5ui.image.M5Image:1 of +msgid "Bases: :py:class:`~lvgl.image`" +msgstr "" + +#: f292f253e25a4d718f5dbec4e0b02088 m5ui.image.M5Image:1 of +msgid "Create a image object." +msgstr "创建一个图像对象。" + +#: ../../en/m5ui/image.rst 16927f2d78b84d7995d7757586c95c3d +#: 86729ee12c674d32aea2dae2da7894e3 ec61dd436e75448f913e8b24464169a1 +#: m5ui.image.M5Image.set_image of +msgid "Parameters" +msgstr "参数" + +#: 3742e69c06194b94b9c11afe4f4bfaa5 49e9d407a7d64059a13227027da17dba +#: m5ui.image.M5Image:3 m5ui.image.M5Image.set_image:3 of +msgid "The path of the image file." +msgstr "图像文件的路径。" + +#: b541baf1a7874a98a09246196908bb1f m5ui.image.M5Image:4 of +msgid "The x position of the image." +msgstr "图像的 x 坐标。" + +#: f58674b306cf4e8aa6ea38b4a49d0232 m5ui.image.M5Image:5 of +msgid "The y position of the image." +msgstr "图像的 y 坐标。" + +#: 16f395d4ce6d45ce97986ef95d5b1ed1 m5ui.image.M5Image:6 of +msgid "" +"The parent object to attach the image to. If not specified, the image " +"will be attached to the default screen." +msgstr "要将图像附加到的父对象;若未指定,则附加到默认屏幕。" + +#: ../../en/m5ui/image.rst:59 20c504d46cce4553ba3bb68e9857060a +msgid "" +"Set a flag on the object. If ``value`` is True, the flag is added; if " +"False, the flag is removed." +msgstr "在对象上设置标志。若 ``value`` 为 True,则添加该标志;为 False 则移除。" + +#: ../../en/m5ui/image.rst:61 3e032fc3fccf44aab1e76ac2ccd94c0a +msgid "The flag to set." +msgstr "要设置的标志。" + +#: ../../en/m5ui/image.rst:62 15241b4c3aee401d95d1d777df2ed4ff +msgid "If True, the flag is added; if False, the flag is removed." +msgstr "若为 True 则添加标志;若为 False 则移除标志。" + +#: ../../en/m5ui/image.rst:66 17a44a0c4f0f48b2924caf80c479e6dd +msgid "|set_hidden.png|" +msgstr "" + +#: ../../en/refs/m5ui.image.ref:4 27d4b0e3420a47d59f4aca49083ca4a9 +msgid "set_hidden.png" +msgstr "" + +#: ../../en/m5ui/image.rst:76 3bb74b0aee3748e7bf173a389cf180ba +msgid "Set the position of the image." +msgstr "设置图像的位置。" + +#: ../../en/m5ui/image.rst:78 ../../en/m5ui/image.rst:95 +#: f5e4c7e526d246c0b4132a2ef9a69913 +msgid "The x-coordinate of the image." +msgstr "图像的 x 坐标。" + +#: ../../en/m5ui/image.rst:79 ../../en/m5ui/image.rst:111 +#: 54e475225c644dfbbcb171633990f0ba +msgid "The y-coordinate of the image." +msgstr "图像的 y 坐标。" + +#: ../../en/m5ui/image.rst:83 3a8cf141b542438eb416b2ab54ca393f +msgid "|set_pos.png|" +msgstr "" + +#: ../../en/refs/m5ui.image.ref:6 24a4191acef241d9a4969e04504367a9 +msgid "set_pos.png" +msgstr "" + +#: ../../en/m5ui/image.rst:93 611e471bd31749bb99c619e9793f6168 +msgid "Set the x-coordinate of the image." +msgstr "设置图像的 x 坐标。" + +#: ../../en/m5ui/image.rst:99 5eda9f9a336b44ab9149b2c9025580b0 +msgid "|set_x.png|" +msgstr "" + +#: ../../en/refs/m5ui.image.ref:10 7b8998924bc6499f93fc7be7de337fc0 +msgid "set_x.png" +msgstr "" + +#: ../../en/m5ui/image.rst:109 c395debe7cd14c63995fcd2d6af994b7 +msgid "Set the y-coordinate of the image." +msgstr "设置图像的 y 坐标。" + +#: ../../en/m5ui/image.rst:115 d689cef4eb664e75863cc7811103a6f5 +msgid "|set_y.png|" +msgstr "" + +#: ../../en/refs/m5ui.image.ref:11 3e6941e1a24c442c89a1d8f70ba3d8b5 +msgid "set_y.png" +msgstr "" + +#: ../../en/m5ui/image.rst:159 92c076d0e0b4422987c865b109da27a9 +msgid "Align the image to another object." +msgstr "将图像对齐到另一个对象。" + +#: ../../en/m5ui/image.rst:161 61374108f5f345b6819c66b4129d89d0 +msgid "The object to align to." +msgstr "要对齐到的对象。" + +#: ../../en/m5ui/image.rst:162 361946d1d10747cd9a6f20aba544d9a4 +msgid "The alignment type." +msgstr "对齐类型。" + +#: ../../en/m5ui/image.rst:163 4794a10c97644ef6b1dff87cfdd86707 +msgid "The x-offset from the aligned object." +msgstr "相对于对齐对象的 x 偏移量。" + +#: ../../en/m5ui/image.rst:164 c3ee4b8072d44b879a121cad26445c0f +msgid "The y-offset from the aligned object." +msgstr "相对于对齐对象的 y 偏移量。" + +#: ../../en/m5ui/image.rst:168 f3a36705019a4260b94cc1b79eed94e6 +msgid "|align_to.png|" +msgstr "" + +#: ../../en/refs/m5ui.image.ref:1 69dcdbfc60284f939a79fe2699ad5fc3 +msgid "align_to.png" +msgstr "" + +#: c8c7a9a0479a422eaf5d1039d7bf5beb m5ui.image.M5Image.set_image:1 of +msgid "Set the image to be displayed." +msgstr "设置要显示的图像。" + +#: 17a44a0c4f0f48b2924caf80c479e6dd m5ui.image.M5Image.set_image:7 of +msgid "|set_image.png|" +msgstr "" + +#: ../../en/refs/m5ui.image.ref:8 eff9728ed324422f82fa19876de13c62 +msgid "set_image.png" +msgstr "" + +#~ msgid "Returns" +#~ msgstr "返回值" + +#~ msgid "|set_flag.png|" +#~ msgstr "" + diff --git a/examples/m5ui/image/cores3_show_image_example.m5f2 b/examples/m5ui/image/cores3_show_image_example.m5f2 new file mode 100644 index 00000000..f68ec30f --- /dev/null +++ b/examples/m5ui/image/cores3_show_image_example.m5f2 @@ -0,0 +1 @@ +{"version":"V2.0","versionNumber":"V2.3.0","type":"cores3","components":[{"name":"page0","type":"lvgl_page","layer":0,"screenId":"builtin","screenName":"","id":"gZfU*rlhMb&n-=-O","createTime":1751937540078,"backgroundColor":"#ffffff","isLVGL":true,"isSelected":true},{"name":"image0","type":"lvgl_image","layer":1,"screenId":"builtin","screenName":"","id":"tPpI0R3JvO0+FZe8","createTime":1751937547072,"x":129,"y":54,"imagePath":"uiflow.jpg","pageId":"gZfU*rlhMb&n-=-O","isLVGL":true,"isSelected":false},{"name":"image1","type":"lvgl_image","layer":2,"screenId":"builtin","screenName":"","id":"oKECYl!eNEy=TJ`v","createTime":1751937877046,"x":129,"y":118,"imagePath":"uiflow.png","pageId":"gZfU*rlhMb&n-=-O","isLVGL":true,"isSelected":false}],"resources":[{"hardware":["hardware_button","hardware_pin_button","imu","speaker","touch","als","mic","sdcard"]}],"units":[],"hats":[],"bases":[],"i2cs":[],"blockly":"truepage0image0uiflow.jpgimage1uiflow.pngtrue","screen":[{"simulationName":"Built-in","type":"builtin","width":320,"height":240,"scale":0.77,"screenName":"","blockId":"","screenColorType":0,"rotation":1,"id":"builtin","createTime":1751937540076}],"logicWhenNum":0,"customList":[]} \ No newline at end of file diff --git a/examples/m5ui/image/cores3_show_image_example.py b/examples/m5ui/image/cores3_show_image_example.py new file mode 100644 index 00000000..49d9215b --- /dev/null +++ b/examples/m5ui/image/cores3_show_image_example.py @@ -0,0 +1,49 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import os, sys, io +import M5 +from M5 import * +import m5ui +import lvgl as lv + + +page0 = None +image0 = None +image1 = None + + +def setup(): + global page0, image0, image1 + + M5.begin() + Widgets.setRotation(1) + m5ui.init() + page0 = m5ui.M5Page(bg_c=0xFFFFFF) + image0 = m5ui.M5Image("/flash/res/img/uiflow.jpg", x=129, y=54, parent=page0) + image1 = m5ui.M5Image("/flash/res/img/uiflow.png", x=129, y=118, parent=page0) + + page0.screen_load() + image0.set_image("/flash/res/img/uiflow.jpg") + image1.set_image("/flash/res/img/uiflow.png") + + +def loop(): + global page0, image0, image1 + M5.update() + + +if __name__ == "__main__": + try: + setup() + while True: + loop() + except (Exception, KeyboardInterrupt) as e: + try: + m5ui.deinit() + from utility import print_error_msg + + print_error_msg(e) + except ImportError: + print("please update to latest firmware") diff --git a/m5stack/libs/m5ui/__init__.py b/m5stack/libs/m5ui/__init__.py index 178beb73..5ea146af 100644 --- a/m5stack/libs/m5ui/__init__.py +++ b/m5stack/libs/m5ui/__init__.py @@ -6,6 +6,7 @@ "init": "port", "deinit": "port", "M5Button": "button", + "M5Image": "image", "M5Label": "label", "M5Page": "page", "M5Switch": "switch", diff --git a/m5stack/libs/m5ui/image.py b/m5stack/libs/m5ui/image.py new file mode 100644 index 00000000..77cf80b9 --- /dev/null +++ b/m5stack/libs/m5ui/image.py @@ -0,0 +1,67 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +from m5ui.base import M5Base +import lvgl as lv + + +class M5Image(lv.image): + """Create a image object. + + :param str path: The path of the image file. + :param int x: The x position of the image. + :param int y: The y position of the image. + :param lv.obj parent: The parent object to attach the image to. If not specified, the image will be attached to the default screen. + + MicroPython Code Block: + + .. code-block:: python + + from m5ui import M5Image + import lvgl as lv + + m5ui.init() + image_0 = M5Image("/flash/res/img/defalut.jpg", x=10, y=10, parent=page0) + image_1 = M5Image("/flash/res/img/uiflow.jpg", x=50, y=50, parent=page0) + """ + + def __init__( + self, + path, + x=0, + y=0, + parent=None, + ): + if parent is None: + parent = lv.screen_active() + super().__init__(parent) + + self.set_src("S:" + path) + self.set_pos(x, y) + + def set_image(self, path): + """Set the image to be displayed. + + :param str path: The path of the image file. + + UiFlow2 Code Block: + + |set_image.png| + + MicroPython Code Block: + + .. code-block:: python + + image_0.set_image("/flash/res/img/uiflow.jpg") + image_1.set_image("/sd/uiflow.png") + """ + self.set_src("S:" + path) + + def __getattr__(self, name): + if hasattr(M5Base, name): + method = getattr(M5Base, name) + bound_method = lambda *args, **kwargs: method(self, *args, **kwargs) + setattr(self, name, bound_method) + return bound_method + raise AttributeError(f"'{self.__class__.__name__}' object has no attribute '{name}'") diff --git a/m5stack/libs/m5ui/manifest.py b/m5stack/libs/m5ui/manifest.py index 4c1c75a1..68069c04 100644 --- a/m5stack/libs/m5ui/manifest.py +++ b/m5stack/libs/m5ui/manifest.py @@ -4,7 +4,7 @@ package( "m5ui", - ("__init__.py", "base.py", "button.py", "label.py", "page.py", "switch.py", "port.py"), + ("__init__.py", "base.py", "button.py", "image.py", "label.py", "page.py", "switch.py", "port.py"), base_path="..", opt=0, ) From 0f3d5b30c329327630ca8a4caf5382c0c106b25a Mon Sep 17 00:00:00 2001 From: lbuque Date: Mon, 7 Jul 2025 16:15:09 +0800 Subject: [PATCH 155/322] modules/startup: Dial's mac address is not fully displayed. Signed-off-by: lbuque --- .../system/common/font/MontserratMedium10.py | 563 -------- .../system/common/font/MontserratMedium14.py | 840 ------------ .../system/common/font/MontserratMedium16.py | 1031 -------------- .../system/common/font/MontserratMedium18.py | 1205 ----------------- m5stack/modules/startup/dial/apps/dev.py | 8 +- 5 files changed, 4 insertions(+), 3643 deletions(-) delete mode 100644 m5stack/fs/system/common/font/MontserratMedium10.py delete mode 100644 m5stack/fs/system/common/font/MontserratMedium14.py delete mode 100644 m5stack/fs/system/common/font/MontserratMedium16.py delete mode 100644 m5stack/fs/system/common/font/MontserratMedium18.py diff --git a/m5stack/fs/system/common/font/MontserratMedium10.py b/m5stack/fs/system/common/font/MontserratMedium10.py deleted file mode 100644 index c2765d72..00000000 --- a/m5stack/fs/system/common/font/MontserratMedium10.py +++ /dev/null @@ -1,563 +0,0 @@ -FONT = ( - b"\x00\x00\x00\x63\x00\x00\x00\x0b\x00\x00\x00\x0a" - b"\x00\x00\x00\x00\x00\x00\x00\x08\x00\x00\x00\x02" - b"\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x01" - b"\x00\x00\x00\x00\x00\x00\x00\x14\xff\xff\xff\xf6" - b"\x00\x00\x00\x00\x00\x00\x00\x09\x00\x00\x00\x01" - b"\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x14" - b"\xff\xff\xff\xf6\x00\x00\x00\x00\x00\x00\x00\x0a" - b"\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x00" - b"\x00\x00\x00\x14\xff\xff\xff\xf6\x00\x00\x00\x00" - b"\x00\x00\x00\x0d\x00\x00\x00\x01\x00\x00\x00\x01" - b"\x00\x00\x00\x00\x00\x00\x00\x14\xff\xff\xff\xf6" - b"\x00\x00\x00\x00\x00\x00\x00\x20\x00\x00\x00\x01" - b"\x00\x00\x00\x01\x00\x00\x00\x03\x00\x00\x00\x14" - b"\xff\xff\xff\xf6\x00\x00\x00\x00\x00\x00\x00\x21" - b"\x00\x00\x00\x08\x00\x00\x00\x02\x00\x00\x00\x03" - b"\x00\x00\x00\x07\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x22\x00\x00\x00\x03\x00\x00\x00\x04" - b"\x00\x00\x00\x04\x00\x00\x00\x07\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x23\x00\x00\x00\x07" - b"\x00\x00\x00\x07\x00\x00\x00\x07\x00\x00\x00\x07" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x24" - b"\x00\x00\x00\x0b\x00\x00\x00\x06\x00\x00\x00\x06" - b"\x00\x00\x00\x09\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x25\x00\x00\x00\x09\x00\x00\x00\x09" - b"\x00\x00\x00\x08\x00\x00\x00\x08\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x26\x00\x00\x00\x09" - b"\x00\x00\x00\x07\x00\x00\x00\x07\x00\x00\x00\x08" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x27" - b"\x00\x00\x00\x03\x00\x00\x00\x02\x00\x00\x00\x02" - b"\x00\x00\x00\x07\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x28\x00\x00\x00\x0a\x00\x00\x00\x03" - b"\x00\x00\x00\x03\x00\x00\x00\x08\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x29\x00\x00\x00\x0a" - b"\x00\x00\x00\x03\x00\x00\x00\x03\x00\x00\x00\x08" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x2a" - b"\x00\x00\x00\x05\x00\x00\x00\x04\x00\x00\x00\x04" - b"\x00\x00\x00\x08\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x2b\x00\x00\x00\x05\x00\x00\x00\x06" - b"\x00\x00\x00\x06\x00\x00\x00\x06\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x2c\x00\x00\x00\x04" - b"\x00\x00\x00\x02\x00\x00\x00\x02\x00\x00\x00\x02" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x2d" - b"\x00\x00\x00\x02\x00\x00\x00\x04\x00\x00\x00\x04" - b"\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x2e\x00\x00\x00\x03\x00\x00\x00\x02" - b"\x00\x00\x00\x02\x00\x00\x00\x02\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x2f\x00\x00\x00\x0a" - b"\x00\x00\x00\x05\x00\x00\x00\x04\x00\x00\x00\x09" - b"\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x30" - b"\x00\x00\x00\x09\x00\x00\x00\x07\x00\x00\x00\x07" - b"\x00\x00\x00\x08\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x31\x00\x00\x00\x07\x00\x00\x00\x03" - b"\x00\x00\x00\x04\x00\x00\x00\x07\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x32\x00\x00\x00\x08" - b"\x00\x00\x00\x06\x00\x00\x00\x06\x00\x00\x00\x08" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x33" - b"\x00\x00\x00\x08\x00\x00\x00\x06\x00\x00\x00\x06" - b"\x00\x00\x00\x07\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x34\x00\x00\x00\x07\x00\x00\x00\x07" - b"\x00\x00\x00\x07\x00\x00\x00\x07\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x35\x00\x00\x00\x08" - b"\x00\x00\x00\x06\x00\x00\x00\x06\x00\x00\x00\x07" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x36" - b"\x00\x00\x00\x09\x00\x00\x00\x06\x00\x00\x00\x06" - b"\x00\x00\x00\x08\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x37\x00\x00\x00\x07\x00\x00\x00\x06" - b"\x00\x00\x00\x06\x00\x00\x00\x07\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x38\x00\x00\x00\x09" - b"\x00\x00\x00\x06\x00\x00\x00\x06\x00\x00\x00\x08" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x39" - b"\x00\x00\x00\x09\x00\x00\x00\x06\x00\x00\x00\x06" - b"\x00\x00\x00\x08\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x3a\x00\x00\x00\x07\x00\x00\x00\x02" - b"\x00\x00\x00\x02\x00\x00\x00\x06\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x3b\x00\x00\x00\x08" - b"\x00\x00\x00\x02\x00\x00\x00\x02\x00\x00\x00\x06" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x3c" - b"\x00\x00\x00\x05\x00\x00\x00\x06\x00\x00\x00\x06" - b"\x00\x00\x00\x06\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x3d\x00\x00\x00\x05\x00\x00\x00\x06" - b"\x00\x00\x00\x06\x00\x00\x00\x06\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x3e\x00\x00\x00\x05" - b"\x00\x00\x00\x06\x00\x00\x00\x06\x00\x00\x00\x06" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x3f" - b"\x00\x00\x00\x09\x00\x00\x00\x06\x00\x00\x00\x06" - b"\x00\x00\x00\x08\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x40\x00\x00\x00\x0a\x00\x00\x00\x0a" - b"\x00\x00\x00\x0a\x00\x00\x00\x08\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x41\x00\x00\x00\x07" - b"\x00\x00\x00\x08\x00\x00\x00\x07\x00\x00\x00\x07" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x42" - b"\x00\x00\x00\x07\x00\x00\x00\x07\x00\x00\x00\x08" - b"\x00\x00\x00\x07\x00\x00\x00\x01\x00\x00\x00\x00" - b"\x00\x00\x00\x43\x00\x00\x00\x09\x00\x00\x00\x07" - b"\x00\x00\x00\x07\x00\x00\x00\x08\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x44\x00\x00\x00\x07" - b"\x00\x00\x00\x07\x00\x00\x00\x08\x00\x00\x00\x07" - b"\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x45" - b"\x00\x00\x00\x07\x00\x00\x00\x06\x00\x00\x00\x07" - b"\x00\x00\x00\x07\x00\x00\x00\x01\x00\x00\x00\x00" - b"\x00\x00\x00\x46\x00\x00\x00\x07\x00\x00\x00\x05" - b"\x00\x00\x00\x06\x00\x00\x00\x07\x00\x00\x00\x01" - b"\x00\x00\x00\x00\x00\x00\x00\x47\x00\x00\x00\x09" - b"\x00\x00\x00\x07\x00\x00\x00\x08\x00\x00\x00\x08" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x48" - b"\x00\x00\x00\x07\x00\x00\x00\x07\x00\x00\x00\x08" - b"\x00\x00\x00\x07\x00\x00\x00\x01\x00\x00\x00\x00" - b"\x00\x00\x00\x49\x00\x00\x00\x07\x00\x00\x00\x02" - b"\x00\x00\x00\x03\x00\x00\x00\x07\x00\x00\x00\x01" - b"\x00\x00\x00\x00\x00\x00\x00\x4a\x00\x00\x00\x08" - b"\x00\x00\x00\x06\x00\x00\x00\x05\x00\x00\x00\x07" - b"\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x4b" - b"\x00\x00\x00\x07\x00\x00\x00\x07\x00\x00\x00\x07" - b"\x00\x00\x00\x07\x00\x00\x00\x01\x00\x00\x00\x00" - b"\x00\x00\x00\x4c\x00\x00\x00\x07\x00\x00\x00\x05" - b"\x00\x00\x00\x06\x00\x00\x00\x07\x00\x00\x00\x01" - b"\x00\x00\x00\x00\x00\x00\x00\x4d\x00\x00\x00\x07" - b"\x00\x00\x00\x08\x00\x00\x00\x0a\x00\x00\x00\x07" - b"\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x4e" - b"\x00\x00\x00\x07\x00\x00\x00\x07\x00\x00\x00\x08" - b"\x00\x00\x00\x07\x00\x00\x00\x01\x00\x00\x00\x00" - b"\x00\x00\x00\x4f\x00\x00\x00\x09\x00\x00\x00\x08" - b"\x00\x00\x00\x08\x00\x00\x00\x08\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x50\x00\x00\x00\x07" - b"\x00\x00\x00\x06\x00\x00\x00\x07\x00\x00\x00\x07" - b"\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x51" - b"\x00\x00\x00\x0a\x00\x00\x00\x09\x00\x00\x00\x08" - b"\x00\x00\x00\x08\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x52\x00\x00\x00\x07\x00\x00\x00\x06" - b"\x00\x00\x00\x07\x00\x00\x00\x07\x00\x00\x00\x01" - b"\x00\x00\x00\x00\x00\x00\x00\x53\x00\x00\x00\x09" - b"\x00\x00\x00\x06\x00\x00\x00\x06\x00\x00\x00\x08" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x54" - b"\x00\x00\x00\x07\x00\x00\x00\x06\x00\x00\x00\x06" - b"\x00\x00\x00\x07\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x55\x00\x00\x00\x08\x00\x00\x00\x07" - b"\x00\x00\x00\x08\x00\x00\x00\x07\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x56\x00\x00\x00\x07" - b"\x00\x00\x00\x08\x00\x00\x00\x07\x00\x00\x00\x07" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x57" - b"\x00\x00\x00\x07\x00\x00\x00\x0b\x00\x00\x00\x0b" - b"\x00\x00\x00\x07\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x58\x00\x00\x00\x07\x00\x00\x00\x07" - b"\x00\x00\x00\x07\x00\x00\x00\x07\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x59\x00\x00\x00\x07" - b"\x00\x00\x00\x07\x00\x00\x00\x06\x00\x00\x00\x07" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x5a" - b"\x00\x00\x00\x07\x00\x00\x00\x07\x00\x00\x00\x07" - b"\x00\x00\x00\x07\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x5b\x00\x00\x00\x0a\x00\x00\x00\x03" - b"\x00\x00\x00\x03\x00\x00\x00\x08\x00\x00\x00\x01" - b"\x00\x00\x00\x00\x00\x00\x00\x5c\x00\x00\x00\x0a" - b"\x00\x00\x00\x05\x00\x00\x00\x04\x00\x00\x00\x09" - b"\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x5d" - b"\x00\x00\x00\x0a\x00\x00\x00\x03\x00\x00\x00\x03" - b"\x00\x00\x00\x08\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x5e\x00\x00\x00\x05\x00\x00\x00\x06" - b"\x00\x00\x00\x06\x00\x00\x00\x06\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x5f\x00\x00\x00\x01" - b"\x00\x00\x00\x05\x00\x00\x00\x05\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x60" - b"\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00\x06" - b"\x00\x00\x00\x08\x00\x00\x00\x01\x00\x00\x00\x00" - b"\x00\x00\x00\x61\x00\x00\x00\x07\x00\x00\x00\x06" - b"\x00\x00\x00\x06\x00\x00\x00\x06\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x62\x00\x00\x00\x09" - b"\x00\x00\x00\x07\x00\x00\x00\x07\x00\x00\x00\x08" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x63" - b"\x00\x00\x00\x07\x00\x00\x00\x06\x00\x00\x00\x06" - b"\x00\x00\x00\x06\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x64\x00\x00\x00\x09\x00\x00\x00\x06" - b"\x00\x00\x00\x07\x00\x00\x00\x08\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x65\x00\x00\x00\x07" - b"\x00\x00\x00\x06\x00\x00\x00\x06\x00\x00\x00\x06" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x66" - b"\x00\x00\x00\x08\x00\x00\x00\x04\x00\x00\x00\x04" - b"\x00\x00\x00\x08\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x67\x00\x00\x00\x08\x00\x00\x00\x06" - b"\x00\x00\x00\x07\x00\x00\x00\x06\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x68\x00\x00\x00\x08" - b"\x00\x00\x00\x06\x00\x00\x00\x07\x00\x00\x00\x08" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x69" - b"\x00\x00\x00\x08\x00\x00\x00\x02\x00\x00\x00\x03" - b"\x00\x00\x00\x08\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x6a\x00\x00\x00\x0a\x00\x00\x00\x03" - b"\x00\x00\x00\x03\x00\x00\x00\x08\xff\xff\xff\xff" - b"\x00\x00\x00\x00\x00\x00\x00\x6b\x00\x00\x00\x08" - b"\x00\x00\x00\x07\x00\x00\x00\x06\x00\x00\x00\x08" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x6c" - b"\x00\x00\x00\x08\x00\x00\x00\x02\x00\x00\x00\x03" - b"\x00\x00\x00\x08\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x6d\x00\x00\x00\x06\x00\x00\x00\x0a" - b"\x00\x00\x00\x0b\x00\x00\x00\x06\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x6e\x00\x00\x00\x06" - b"\x00\x00\x00\x06\x00\x00\x00\x07\x00\x00\x00\x06" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x6f" - b"\x00\x00\x00\x07\x00\x00\x00\x06\x00\x00\x00\x06" - b"\x00\x00\x00\x06\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x70\x00\x00\x00\x08\x00\x00\x00\x07" - b"\x00\x00\x00\x07\x00\x00\x00\x06\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x71\x00\x00\x00\x08" - b"\x00\x00\x00\x06\x00\x00\x00\x07\x00\x00\x00\x06" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x72" - b"\x00\x00\x00\x06\x00\x00\x00\x04\x00\x00\x00\x04" - b"\x00\x00\x00\x06\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x73\x00\x00\x00\x07\x00\x00\x00\x05" - b"\x00\x00\x00\x05\x00\x00\x00\x06\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x74\x00\x00\x00\x08" - b"\x00\x00\x00\x04\x00\x00\x00\x04\x00\x00\x00\x07" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x75" - b"\x00\x00\x00\x07\x00\x00\x00\x06\x00\x00\x00\x07" - b"\x00\x00\x00\x06\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x76\x00\x00\x00\x06\x00\x00\x00\x06" - b"\x00\x00\x00\x06\x00\x00\x00\x06\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x77\x00\x00\x00\x06" - b"\x00\x00\x00\x09\x00\x00\x00\x09\x00\x00\x00\x06" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x78" - b"\x00\x00\x00\x06\x00\x00\x00\x06\x00\x00\x00\x06" - b"\x00\x00\x00\x06\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x79\x00\x00\x00\x08\x00\x00\x00\x07" - b"\x00\x00\x00\x06\x00\x00\x00\x06\xff\xff\xff\xff" - b"\x00\x00\x00\x00\x00\x00\x00\x7a\x00\x00\x00\x06" - b"\x00\x00\x00\x05\x00\x00\x00\x05\x00\x00\x00\x06" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x7b" - b"\x00\x00\x00\x0a\x00\x00\x00\x04\x00\x00\x00\x04" - b"\x00\x00\x00\x08\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x7c\x00\x00\x00\x0a\x00\x00\x00\x01" - b"\x00\x00\x00\x03\x00\x00\x00\x08\x00\x00\x00\x01" - b"\x00\x00\x00\x00\x00\x00\x00\x7d\x00\x00\x00\x0a" - b"\x00\x00\x00\x03\x00\x00\x00\x04\x00\x00\x00\x08" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x7e" - b"\x00\x00\x00\x03\x00\x00\x00\x06\x00\x00\x00\x06" - b"\x00\x00\x00\x05\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x34\xe2\x2a\xd8\x21\xce\x18" - b"\xc4\x0c\x95\x01\x1b\x32\xdb\x00\x02\x5e\x74\x5e" - b"\x74\x58\x6f\x58\x6f\x3a\x4a\x3a\x4a\x00\x00\xb7" - b"\x00\x2c\x87\x00\x15\x34\xea\x34\x7f\x9c\x27\x37" - b"\x8d\xff\x88\xf3\xd1\x68\x00\x22\x93\x00\x8a\x29" - b"\x00\x63\xca\xfb\x88\xff\x92\x3b\x26\x95\x87\x34" - b"\xe8\x34\x16\x00\x80\x33\x00\xb4\x00\x00\x00\x00" - b"\x08\x19\x00\x00\x00\x00\x2c\x86\x00\x00\x05\x9f" - b"\xff\xff\xd6\x40\x56\xb9\x2a\x7c\x12\x14\x43\xd9" - b"\x53\x7c\x00\x00\x00\x65\xf7\xff\x8d\x0e\x00\x00" - b"\x28\x93\x9f\x9f\x31\x2e\x28\x7c\x5c\xb5\x3c\xc9" - b"\xff\xff\xce\x2d\x00\x00\x2b\x87\x00\x00\x00\x00" - b"\x08\x19\x00\x00\x00\x01\x01\x00\x00\x00\x00\x00" - b"\x00\x2a\xa3\xa2\x2a\x00\x28\x9b\x00\x00\x8c\x13" - b"\x13\x8c\x04\xae\x10\x00\x00\x7d\x2a\x29\x7c\x7c" - b"\x47\x00\x00\x00\x0e\x8e\x8d\x3f\x92\x5a\x91\x45" - b"\x00\x00\x00\x07\xb0\x1d\x97\x00\xa9\x00\x00\x00" - b"\x88\x3c\x1e\x82\x00\x9f\x04\x00\x3b\x88\x00\x00" - b"\x94\x8e\x7f\x00\x00\x00\x00\x00\x00\x00\x03\x00" - b"\x00\x00\x00\x00\x04\x00\x00\x00\x00\x3d\xc9\xbb" - b"\x80\x00\x00\x00\xa2\x42\x03\xd1\x00\x00\x00\x54" - b"\xd6\xb6\x64\x00\x00\x00\x70\xff\xe4\x06\x1c\x0b" - b"\x4f\xab\x01\x76\xb2\xad\x35\x7c\x8a\x00\x00\xbe" - b"\xff\x07\x15\xb7\xc8\xc9\xac\x85\x79\x00\x00\x06" - b"\x05\x00\x00\x02\x5e\x74\x58\x6f\x3a\x4a\x00\x01" - b"\x60\x00\x54\x9d\x00\xaf\x44\x00\xe4\x0d\x02\xec" - b"\x00\x07\xe8\x00\x00\xef\x01\x00\xcf\x23\x00\x87" - b"\x6b\x00\x1e\xc0\x3f\x25\x00\x3e\xb3\x00\x00\xdf" - b"\x12\x00\xac\x47\x00\x8f\x64\x00\x88\x6b\x00\x99" - b"\x5a\x00\xc3\x2f\x10\xdf\x01\x6d\x72\x00\x00\x20" - b"\x20\x00\x6d\x7d\x7d\x6d\x27\xf2\xf4\x28\x5d\x69" - b"\x69\x5d\x00\x17\x17\x00\x00\x00\x5a\x39\x00\x00" - b"\x00\x00\x84\x54\x00\x00\x42\xcc\xff\xff\xcc\x1f" - b"\x00\x00\x84\x54\x00\x00\x00\x00\x5a\x39\x00\x00" - b"\x10\x24\x6b\xc8\x3a\x7d\x32\x1d\x11\x28\x28\x0a" - b"\x4b\xac\xac\x2d\x11\x24\x6c\xac\x00\x02\x00\x00" - b"\x00\x06\x58\x00\x00\x00\x4f\x91\x00\x00\x00\xa8" - b"\x37\x00\x00\x0c\xd3\x00\x00\x00\x5c\x84\x00\x00" - b"\x00\xb5\x2a\x00\x00\x13\xcb\x00\x00\x00\x68\x77" - b"\x00\x00\x00\xc0\x1f\x00\x00\x1c\xc2\x00\x00\x00" - b"\x00\x00\x00\x0a\x00\x00\x00\x00\x52\xdf\xdb\xc7" - b"\x1f\x00\x1b\xe4\x1f\x00\x61\xbe\x00\x67\x99\x00" - b"\x00\x01\xeb\x14\x7e\x80\x00\x00\x00\xd5\x2a\x67" - b"\x99\x00\x00\x01\xeb\x14\x1b\xe3\x1f\x00\x60\xbe" - b"\x00\x00\x52\xdf\xda\xc7\x1f\x00\x00\x00\x01\x0a" - b"\x00\x00\x00\xce\xff\xa9\x00\x58\xa8\x00\x58\xa8" - b"\x00\x58\xa8\x00\x58\xa8\x00\x58\xa8\x00\x58\xa8" - b"\x00\x00\x0b\x00\x00\x00\x55\xdb\xdb\xe2\x71\x00" - b"\x47\x20\x00\x1a\xf5\x0a\x00\x00\x00\x10\xed\x05" - b"\x00\x00\x06\xb6\x6f\x00\x00\x08\xb7\x80\x00\x00" - b"\x0b\xbd\x76\x00\x00\x00\x93\xff\xe0\xe0\xe0\x5b" - b"\x8c\xe0\xe0\xf5\xff\x00\x00\x00\x05\xc0\x54\x00" - b"\x00\x00\x9f\x85\x00\x00\x00\x02\xce\xe0\x83\x00" - b"\x00\x00\x00\x08\xde\x2b\x4b\x0f\x00\x10\xe3\x28" - b"\x75\xe2\xda\xe7\x7d\x00\x00\x00\x0b\x03\x00\x00" - b"\x00\x00\x00\x71\xa7\x00\x00\x00\x00\x3d\xd0\x0b" - b"\x00\x00\x00\x19\xd9\x27\x10\x05\x00\x04\xc2\x53" - b"\x00\xb4\x3c\x00\x7e\xf3\x94\x94\xff\xd0\x53\x2d" - b"\x48\x48\x48\xff\x84\x28\x00\x00\x00\x00\xbc\x3c" - b"\x00\x03\xff\xe0\xe0\xe0\x00\x1a\xd3\x00\x00\x00" - b"\x00\x33\xe2\x31\x19\x00\x00\x2f\xb3\xab\xd4\xb8" - b"\x07\x00\x00\x00\x00\xc1\x52\x42\x18\x00\x07\xcd" - b"\x45\x5e\xdc\xdb\xe6\x91\x01\x00\x00\x09\x04\x00" - b"\x00\x00\x00\x00\x0a\x03\x00\x00\x3a\xd0\xd7\xdc" - b"\x4d\x14\xe5\x35\x00\x00\x03\x62\x9f\x12\x37\x14" - b"\x00\x7e\xce\xc4\x9b\xdd\x4f\x6d\xee\x04\x00\x2e" - b"\xd8\x24\xdf\x0f\x00\x4a\xc7\x00\x60\xd8\xc8\xcf" - b"\x29\x00\x00\x00\x0d\x00\x00\xba\xff\xe0\xe0\xff" - b"\x8e\xb4\x44\x00\x00\xd3\x36\x16\x08\x00\x48\xc3" - b"\x00\x00\x00\x00\xbc\x51\x00\x00\x00\x2f\xdb\x02" - b"\x00\x00\x00\xa3\x6b\x00\x00\x00\x1b\xe8\x0b\x00" - b"\x00\x00\x00\x01\x07\x00\x00\x02\x8e\xd1\xc4\xcb" - b"\x2d\x3f\xcf\x02\x00\x5f\xae\x25\xdc\x27\x0e\x90" - b"\x8f\x08\xbd\xff\xff\xff\x3e\x73\x9f\x00\x00\x31" - b"\xe3\x77\xa0\x00\x00\x33\xe7\x0e\xad\xce\xc3\xd0" - b"\x48\x00\x00\x01\x07\x00\x00\x00\x00\x0b\x02\x00" - b"\x00\x15\xbd\xce\xd9\x84\x00\x99\x77\x00\x04\xc3" - b"\x4c\xa8\x5d\x00\x00\xc6\x98\x32\xdc\x9e\xb9\xc7" - b"\xa9\x00\x08\x32\x1a\x74\x8d\x00\x04\x00\x1d\xdc" - b"\x31\x28\xdf\xd2\xda\x57\x00\x00\x02\x0c\x00\x00" - b"\x00\x11\x24\x6c\xac\x00\x02\x00\x00\x11\x24\x6c" - b"\xac\x00\x02\x11\x24\x6c\xac\x00\x02\x00\x00\x10" - b"\x24\x6b\xc8\x3a\x7d\x32\x1d\x00\x00\x00\x13\x71" - b"\x1a\x01\x43\xa6\xbd\x61\x06\x51\xff\x4f\x00\x00" - b"\x00\x01\x44\xa7\xbc\x60\x06\x00\x00\x00\x14\x72" - b"\x1a\x06\x14\x14\x14\x14\x03\x3b\xb4\xb4\xb4\xb4" - b"\x1c\x00\x00\x00\x00\x00\x00\x3b\xb4\xb4\xb4\xb4" - b"\x1c\x06\x14\x14\x14\x14\x03\x35\x60\x0a\x00\x00" - b"\x00\x0f\x72\xc6\x96\x31\x00\x00\x00\x00\x70\xff" - b"\x25\x0f\x71\xc6\x97\x32\x00\x35\x61\x0a\x00\x00" - b"\x00\x00\x00\x0c\x00\x00\x00\x47\xd9\xd0\xdf\x68" - b"\x00\x4f\x1b\x00\x1b\xf9\x03\x00\x00\x00\x3b\xd5" - b"\x00\x00\x00\x32\xd5\x25\x00\x00\x00\x83\x4e\x00" - b"\x00\x00\x00\x18\x05\x00\x00\x00\x00\xbc\x51\x00" - b"\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x03" - b"\x09\x00\x00\x00\x00\x00\x00\x51\xb1\xa7\x9e\xae" - b"\x8b\x0c\x00\x00\x74\x96\x0b\x04\x03\x00\x49\xb9" - b"\x0a\x1e\xbb\x01\x7b\xd6\xc5\xa8\xb0\x54\x77\x68" - b"\x53\x44\xbc\x03\x00\xaf\xd1\x01\xae\x7e\x39\x71" - b"\x79\x00\x00\x5a\xf5\x00\xa8\x65\x57\x42\xbe\x03" - b"\x00\xad\xd7\x0b\xa0\x19\xbe\x02\x7a\xd9\xc9\x76" - b"\xd5\xca\x3d\x00\x67\x9d\x0f\x04\x04\x01\x04\x04" - b"\x00\x00\x00\x45\xae\xad\xaa\xb2\x23\x00\x00\x00" - b"\x00\x19\xff\x61\x00\x00\x00\x00\x00\x87\x92\xd2" - b"\x01\x00\x00\x00\x0b\xdf\x0d\xb2\x49\x00\x00\x00" - b"\x6f\x8d\x00\x42\xbd\x00\x00\x03\xff\xac\x8c\x8c" - b"\xff\x31\x00\x57\xd3\x40\x40\x40\x94\xa5\x00\xca" - b"\x3c\x00\x00\x00\x07\xe4\x1e\xf4\xd2\xd0\xd3\xc1" - b"\x23\x00\xf4\x0c\x00\x00\x77\xa0\x00\xf4\x0c\x00" - b"\x04\x8f\x85\x00\xfd\xdc\xd0\xe2\xff\x32\x00\xf4" - b"\x0c\x00\x00\x29\xea\x02\xf4\x0c\x00\x00\x20\xf3" - b"\x03\xf4\xd2\xd0\xd0\xd3\x58\x00\x00\x00\x00\x05" - b"\x0b\x00\x00\x00\x18\xa8\xec\xdf\xd5\x4a\x06\xd0" - b"\x72\x01\x00\x2e\x53\x5b\xb8\x00\x00\x00\x00\x00" - b"\x7e\x88\x00\x00\x00\x00\x00\x5c\xb6\x00\x00\x00" - b"\x00\x00\x07\xd3\x6d\x01\x00\x2f\x55\x00\x19\xaa" - b"\xea\xdf\xd3\x3c\x00\x00\x00\x05\x0b\x00\x00\xf4" - b"\xe1\xe0\xe2\xba\x35\x00\xf4\x0c\x00\x00\x47\xe5" - b"\x2c\xf4\x0c\x00\x00\x00\x6e\x9d\xf4\x0c\x00\x00" - b"\x00\x40\xc0\xf4\x0c\x00\x00\x00\x6e\x9e\xf4\x0c" - b"\x00\x00\x47\xe6\x2e\xf4\xe1\xe0\xe2\xba\x36\x00" - b"\xf4\xe1\xe0\xe0\xdc\x00\xf4\x0c\x00\x00\x00\x00" - b"\xf4\x0c\x00\x00\x00\x00\xfe\xe8\xdc\xdc\x7b\x00" - b"\xf4\x0c\x00\x00\x00\x00\xf4\x0c\x00\x00\x00\x00" - b"\xf4\xe1\xe0\xe0\xe0\x1c\xf4\xe1\xe0\xe0\xdc\xf4" - b"\x0c\x00\x00\x00\xf4\x0c\x00\x00\x00\xfc\xbc\xb0" - b"\xb0\x63\xf6\x38\x2c\x2c\x18\xf4\x0c\x00\x00\x00" - b"\xf4\x0c\x00\x00\x00\x00\x00\x00\x04\x0c\x00\x00" - b"\x00\x18\xa8\xec\xde\xdd\x59\x06\xd0\x76\x02\x00" - b"\x25\x59\x5b\xb8\x00\x00\x00\x00\x00\x7e\x88\x00" - b"\x00\x00\x0d\x78\x5c\xb8\x00\x00\x00\x18\xdc\x06" - b"\xd1\x71\x01\x00\x37\xff\x00\x18\xa8\xea\xda\xd9" - b"\x77\x00\x00\x00\x05\x08\x00\x00\xf4\x0c\x00\x00" - b"\x00\xf0\x10\xf4\x0c\x00\x00\x00\xf0\x10\xf4\x0c" - b"\x00\x00\x00\xf0\x10\xfe\xe8\xdc\xdc\xdc\xff\x10" - b"\xf4\x0c\x00\x00\x00\xf0\x10\xf4\x0c\x00\x00\x00" - b"\xf0\x10\xf4\x0c\x00\x00\x00\xf0\x10\xf4\x0c\xf4" - b"\x0c\xf4\x0c\xf4\x0c\xf4\x0c\xf4\x0c\xf4\x0c\x00" - b"\x54\xe0\xe0\xfb\x24\x00\x00\x00\x00\xd8\x24\x00" - b"\x00\x00\x00\xd8\x24\x00\x00\x00\x00\xd8\x24\x00" - b"\x00\x00\x00\xd8\x22\x00\x58\x01\x0d\xef\x0a\x02" - b"\x91\xdb\xe1\x77\x00\x00\x00\x04\x04\x00\x00\xf4" - b"\x0c\x00\x02\xa2\x82\x00\xf4\x0c\x01\x9b\x90\x00" - b"\x00\xf4\x0c\x95\x9c\x01\x00\x00\xf4\x98\xff\x4f" - b"\x00\x00\x00\xff\xc1\x36\xe5\x2b\x00\x00\xf9\x13" - b"\x00\x4a\xda\x13\x00\xf4\x0c\x00\x00\x6a\xc0\x05" - b"\xf4\x0c\x00\x00\x00\xf4\x0c\x00\x00\x00\xf4\x0c" - b"\x00\x00\x00\xf4\x0c\x00\x00\x00\xf4\x0c\x00\x00" - b"\x00\xf4\x0c\x00\x00\x00\xf4\xe1\xe0\xe0\xbd\xf0" - b"\x2e\x00\x00\x00\x00\xa0\x7b\xff\xc3\x00\x00\x00" - b"\x36\xff\x7c\xf4\xa9\x5b\x00\x01\xc6\xa8\x7d\xf4" - b"\x1d\xd8\x0d\x62\x94\x77\x7d\xf4\x04\x72\x99\xd4" - b"\x11\x76\x7e\xf4\x04\x04\xe9\x63\x00\x75\x7e\xf4" - b"\x04\x00\x14\x01\x00\x74\x7f\xf1\x48\x00\x00\x00" - b"\xf0\x10\xff\xf0\x24\x00\x00\xf0\x10\xf4\x6b\xd4" - b"\x0c\x00\xf0\x10\xf4\x0c\x91\xae\x01\xf0\x10\xf4" - b"\x0c\x04\xbe\x7e\xf0\x10\xf4\x0c\x00\x14\xde\xff" - b"\x10\xf4\x0c\x00\x00\x31\xf6\x10\x00\x00\x00\x05" - b"\x0c\x00\x00\x00\x00\x16\xa7\xeb\xe0\xd7\x4a\x00" - b"\x05\xce\x72\x01\x00\x2e\xde\x40\x5a\xb8\x00\x00" - b"\x00\x00\x51\xc3\x7e\x88\x00\x00\x00\x00\x20\xe5" - b"\x5b\xb6\x00\x00\x00\x00\x4f\xc4\x06\xd0\x6d\x01" - b"\x00\x2b\xdc\x41\x00\x18\xa8\xe9\xde\xd7\x4a\x00" - b"\x00\x00\x00\x05\x0c\x00\x00\x00\xff\xe7\xe0\xe2" - b"\x96\x07\xf4\x0c\x00\x08\xa9\x7d\xf4\x0c\x00\x00" - b"\x58\xab\xf4\x0c\x00\x1b\xbb\x70\xff\xe8\xdc\xcd" - b"\x7a\x02\xf4\x0c\x00\x00\x00\x00\xf4\x0c\x00\x00" - b"\x00\x00\x00\x00\x00\x05\x0c\x00\x00\x00\x00\x00" - b"\x16\xa7\xeb\xe0\xd7\x4a\x00\x00\x05\xce\x72\x01" - b"\x00\x2e\xde\x40\x00\x5a\xb8\x00\x00\x00\x00\x51" - b"\xc3\x00\x7e\x88\x00\x00\x00\x00\x20\xe5\x00\x5b" - b"\xb8\x00\x00\x00\x00\x51\xc4\x00\x06\xd0\x70\x01" - b"\x00\x2c\xde\x41\x00\x00\x18\xa8\xee\xff\xdd\x4a" - b"\x00\x00\x00\x00\x00\x06\x9e\xbe\x61\xa9\x12\x00" - b"\x00\x00\x00\x00\x3a\x6a\x2f\x00\xff\xe7\xe0\xe2" - b"\x96\x07\xf4\x0c\x00\x08\xa9\x7d\xf4\x0c\x00\x00" - b"\x58\xab\xf4\x0c\x00\x1b\xbb\x70\xff\xe8\xdc\xff" - b"\x96\x02\xf4\x0c\x00\x51\xbe\x02\xf4\x0c\x00\x00" - b"\x9e\x75\x00\x00\x04\x0a\x00\x00\x05\x9f\xdf\xcf" - b"\xd6\x40\x56\xb9\x02\x00\x12\x14\x43\xd9\x2b\x00" - b"\x00\x00\x00\x65\xcf\xdd\x8d\x0e\x00\x00\x00\x17" - b"\x9f\x9f\x31\x2e\x00\x00\x5c\xb5\x3c\xc9\xd8\xd3" - b"\xce\x2d\x00\x00\x03\x0b\x00\x00\xd5\xe0\xf2\xee" - b"\xe0\xb9\x00\x00\x90\x70\x00\x00\x00\x00\x90\x70" - b"\x00\x00\x00\x00\x90\x70\x00\x00\x00\x00\x90\x70" - b"\x00\x00\x00\x00\x90\x70\x00\x00\x00\x00\x90\x70" - b"\x00\x00\x04\xfc\x00\x00\x00\x10\xe8\x04\xfc\x00" - b"\x00\x00\x10\xe8\x04\xfc\x00\x00\x00\x10\xe8\x04" - b"\xfc\x00\x00\x00\x10\xe7\x00\xf0\x0e\x00\x00\x22" - b"\xd4\x00\xb0\x78\x00\x01\x8c\x94\x00\x1d\xbb\xdf" - b"\xe1\xae\x11\x00\x00\x00\x05\x04\x00\x00\xcc\x47" - b"\x00\x00\x00\x1c\xde\x04\x5b\xb8\x00\x00\x00\x89" - b"\x77\x00\x05\xe4\x2a\x00\x0b\xe4\x12\x00\x00\x7a" - b"\x9a\x00\x6b\x97\x00\x00\x00\x13\xed\x16\xd8\x28" - b"\x00\x00\x00\x00\x99\xc9\xb7\x00\x00\x00\x00\x00" - b"\x29\xff\x47\x00\x00\x00\x86\x82\x00\x00\x07\xf0" - b"\x4a\x00\x00\x30\xc4\x30\xd7\x00\x00\x51\xee\xa1" - b"\x00\x00\x86\x6f\x00\xd9\x2d\x00\xa9\x4e\xe3\x07" - b"\x00\xd9\x1b\x00\x84\x83\x0b\xdf\x03\xa4\x4f\x32" - b"\xc3\x00\x00\x2f\xd8\x59\x96\x00\x4d\xa6\x88\x6e" - b"\x00\x00\x00\xd8\xdf\x3f\x00\x06\xe3\xe4\x1a\x00" - b"\x00\x00\x83\xff\x02\x00\x00\xac\xdd\x00\x00\x58" - b"\xc7\x04\x00\x1b\xdc\x1e\x00\x9e\x87\x02\xbf\x53" - b"\x00\x00\x0b\xd7\xbe\x97\x00\x00\x00\x00\x7f\xff" - b"\x25\x00\x00\x00\x1c\xdd\x80\xc1\x03\x00\x03\xc2" - b"\x5a\x00\xa4\x7f\x00\x82\xa3\x00\x00\x0d\xd8\x3d" - b"\xbd\x55\x00\x00\x01\xc6\x37\x28\xdd\x0c\x00\x65" - b"\x9a\x00\x00\x85\x8b\x13\xd8\x12\x00\x00\x09\xda" - b"\xc8\x63\x00\x00\x00\x00\x63\xf0\x01\x00\x00\x00" - b"\x00\x44\xbc\x00\x00\x00\x00\x00\x44\xbc\x00\x00" - b"\x00\x70\xe0\xe0\xe0\xec\xff\x1f\x00\x00\x00\x01" - b"\xac\x7f\x00\x00\x00\x00\x7d\xb0\x01\x00\x00\x00" - b"\x4c\xd3\x0d\x00\x00\x00\x28\xe0\x24\x00\x00\x00" - b"\x0f\xd6\x48\x00\x00\x00\x00\x86\xff\xe0\xe0\xe0" - b"\xe0\x3f\x66\x6c\x0f\xf4\x62\x0d\xf4\x04\x00\xf4" - b"\x04\x00\xf4\x04\x00\xf4\x04\x00\xf4\x04\x00\xf4" - b"\x04\x00\xf4\x04\x00\xe4\xc8\x1c\x20\x3c\x00\x00" - b"\x00\x13\xc8\x00\x00\x00\x00\xb4\x28\x00\x00\x00" - b"\x5b\x82\x00\x00\x00\x0b\xd1\x00\x00\x00\x00\xa7" - b"\x36\x00\x00\x00\x4e\x90\x00\x00\x00\x06\xd5\x03" - b"\x00\x00\x00\x9b\x44\x00\x00\x00\x41\x9e\x57\x6c" - b"\x1e\x4e\xce\x48\x00\xb0\x48\x00\xb0\x48\x00\xb0" - b"\x48\x00\xb0\x48\x00\xb0\x48\x00\xb0\x48\x00\xb0" - b"\x48\xa2\xe3\x43\x00\x00\x6b\x44\x00\x00\x00\x07" - b"\xc0\xb3\x00\x00\x00\x5e\x61\x8e\x31\x00\x00\xb8" - b"\x09\x25\x9c\x00\x13\x5d\x00\x00\x6f\x01\xa0\xa0" - b"\xa0\xa0\xa0\x38\x38\x00\x0c\xa2\x45\x00\x1a\x4a" - b"\x3e\x02\x00\x23\xc9\x89\xb2\xb3\x00\x00\x00\x08" - b"\x0d\xe9\x0f\x24\xc9\xaa\xa4\xff\x1c\x6e\x8a\x00" - b"\x15\xf8\x1c\x20\xca\xc1\xbf\xe7\x1c\x00\x00\x07" - b"\x00\x00\x00\x0a\x5e\x00\x00\x00\x00\x00\x18\xe0" - b"\x00\x00\x00\x00\x00\x18\xe0\x11\x4e\x35\x00\x00" - b"\x18\xff\xcc\x8e\xc4\xa8\x01\x18\xff\x1f\x00\x04" - b"\xd0\x40\x18\xff\x00\x00\x00\xa2\x5f\x18\xff\x4a" - b"\x00\x1a\xe4\x27\x18\xe1\xb5\xd7\xe3\x62\x00\x00" - b"\x00\x00\x07\x01\x00\x00\x00\x02\x3c\x4e\x13\x00" - b"\x0a\xc6\xb1\x90\xdf\x23\x6c\xa5\x00\x00\x15\x00" - b"\x8b\x74\x00\x00\x00\x00\x4f\xc9\x09\x00\x50\x12" - b"\x00\x7f\xe3\xdb\xb0\x0a\x00\x00\x01\x07\x00\x00" - b"\x00\x00\x00\x00\x05\x61\x00\x00\x00\x00\x0c\xe8" - b"\x00\x03\x41\x48\x14\xe8\x0c\xcc\xb0\x93\xd0\xf4" - b"\x6e\xa5\x00\x00\x52\xff\x8c\x74\x00\x00\x1c\xff" - b"\x53\xc8\x09\x00\x7c\xff\x00\x87\xe5\xd9\x9d\xe9" - b"\x00\x00\x03\x05\x00\x00\x00\x04\x41\x49\x09\x00" - b"\x0c\xcb\x9e\x90\xd7\x1b\x6d\x94\x04\x04\x6d\x8d" - b"\x8c\xf7\xac\xac\xb5\x77\x51\xc2\x09\x00\x2d\x05" - b"\x00\x80\xe4\xd2\xbf\x1a\x00\x00\x01\x06\x00\x00" - b"\x00\x16\x6e\x4d\x00\xc5\x7f\x4f\x40\xff\x4d\x28" - b"\x68\xff\x7e\x41\x00\xf4\x04\x00\x00\xf4\x04\x00" - b"\x00\xf4\x04\x00\x00\xf4\x04\x00\x00\x04\x40\x4a" - b"\x0b\x46\x10\xd0\xac\x8f\xc9\xff\x75\x9c\x00\x00" - b"\x30\xff\x89\x7a\x00\x00\x0e\xff\x37\xdc\x40\x23" - b"\x9d\xff\x00\x3e\xb1\xbb\x61\xf0\x06\x4f\x00\x00" - b"\x57\xbf\x14\xa9\xd7\xd8\xc0\x27\x0a\x5e\x00\x00" - b"\x00\x00\x18\xe0\x00\x00\x00\x00\x18\xe0\x14\x4e" - b"\x2c\x00\x18\xff\xca\x91\xd3\x7d\x18\xfe\x14\x00" - b"\x21\xe2\x18\xe1\x00\x00\x04\xf0\x18\xe0\x00\x00" - b"\x04\xf0\x18\xe0\x00\x00\x04\xf0\x3f\x41\x54\x54" - b"\x07\x42\x18\xe0\x18\xe0\x18\xe0\x18\xe0\x18\xe0" - b"\x00\x3f\x41\x00\x54\x54\x00\x03\x46\x00\x0c\xec" - b"\x00\x0c\xec\x00\x0c\xec\x00\x0c\xec\x00\x0c\xec" - b"\x01\x21\xe0\xac\xe0\x6a\x0a\x5e\x00\x00\x00\x00" - b"\x00\x18\xe0\x00\x00\x00\x00\x00\x18\xe0\x00\x00" - b"\x1e\x3a\x00\x18\xe0\x00\x32\xd5\x30\x00\x18\xe0" - b"\x43\xf4\x2d\x00\x00\x18\xff\xdb\xff\x32\x00\x00" - b"\x18\xff\x19\x45\xdd\x13\x00\x18\xe0\x00\x00\x73" - b"\xbb\x03\x0a\x5e\x18\xe0\x18\xe0\x18\xe0\x18\xe0" - b"\x18\xe0\x18\xe0\x18\xe0\x07\x3e\x15\x4f\x2a\x00" - b"\x20\x4d\x1e\x00\x18\xf6\xc7\x91\xe0\xb3\xcb\x91" - b"\xe5\x47\x18\xfd\x12\x00\x47\xf7\x09\x00\x5b\xa6" - b"\x18\xe1\x00\x00\x2c\xcd\x00\x00\x40\xb4\x18\xe0" - b"\x00\x00\x2c\xcc\x00\x00\x40\xb4\x18\xe0\x00\x00" - b"\x2c\xcc\x00\x00\x40\xb4\x07\x3e\x14\x4e\x2c\x00" - b"\x18\xf6\xca\x91\xd3\x7d\x18\xfd\x14\x00\x21\xe2" - b"\x18\xe1\x00\x00\x04\xf0\x18\xe0\x00\x00\x04\xf0" - b"\x18\xe0\x00\x00\x04\xf0\x00\x03\x3d\x4d\x11\x00" - b"\x0a\xc7\xb0\x93\xdf\x3b\x6c\xa5\x00\x00\x4d\xc8" - b"\x8b\x74\x00\x00\x1c\xe7\x4d\xc8\x09\x00\x79\xaa" - b"\x00\x73\xe3\xdc\xb9\x16\x00\x00\x02\x06\x00\x00" - b"\x07\x3e\x12\x4e\x35\x00\x00\x18\xfb\xcd\x8e\xc4" - b"\xa8\x01\x18\xff\x1f\x00\x04\xd0\x40\x18\xff\x00" - b"\x00\x00\xa2\x5f\x18\xff\x4a\x00\x1a\xe4\x27\x18" - b"\xeb\xb3\xd7\xe3\x62\x00\x18\xe0\x00\x07\x01\x00" - b"\x00\x16\xd2\x00\x00\x00\x00\x00\x00\x03\x41\x49" - b"\x0a\x44\x0c\xcc\xb0\x93\xcc\xf5\x6e\xa5\x00\x00" - b"\x4e\xff\x8c\x74\x00\x00\x1c\xff\x53\xc8\x09\x00" - b"\x83\xff\x00\x87\xe5\xd9\x9f\xe8\x00\x00\x03\x05" - b"\x0c\xe8\x00\x00\x00\x00\x0b\xd9\x07\x3e\x14\x3b" - b"\x18\xf0\xce\x75\x18\xfb\x18\x00\x18\xe2\x00\x00" - b"\x18\xe0\x00\x00\x18\xe0\x00\x00\x00\x1e\x4d\x39" - b"\x02\x50\xcc\x7f\xa2\x4e\x85\x98\x0f\x00\x00\x12" - b"\x8f\xcc\xce\x3e\x1b\x05\x00\x5a\xaa\x6c\xdc\xca" - b"\xcd\x3c\x00\x00\x06\x00\x00\x00\x6e\x01\x00\x40" - b"\xff\x50\x28\x68\xff\x80\x41\x00\xf4\x04\x00\x00" - b"\xf4\x04\x00\x00\xed\x13\x00\x00\x88\xde\xa2\x00" - b"\x00\x06\x00\x0a\x3d\x00\x00\x08\x40\x24\xd0\x00" - b"\x00\x1c\xd8\x24\xd0\x00\x00\x1c\xd8\x23\xd2\x00" - b"\x00\x24\xd8\x0c\xef\x17\x00\x7d\xdb\x00\x70\xde" - b"\xda\x9c\xd8\x00\x00\x00\x06\x00\x00\x48\x03\x00" - b"\x00\x1f\x28\xab\x53\x00\x00\xb1\x3e\x3c\xc1\x00" - b"\x23\xcc\x00\x00\xcb\x2f\x91\x5f\x00\x00\x5c\xab" - b"\xda\x06\x00\x00\x05\xff\x80\x00\x00\x42\x02\x00" - b"\x00\x45\x00\x00\x00\x41\xa4\x42\x00\x31\xff\x37" - b"\x00\x3c\xa3\x45\x9f\x00\x92\x9c\x96\x00\x9b\x43" - b"\x02\xd6\x10\xd3\x08\xd6\x10\xd2\x01\x00\x87\xad" - b"\x87\x00\x8b\xae\x83\x00\x00\x28\xff\x25\x00\x29" - b"\xff\x24\x00\x31\x20\x00\x00\x43\x0b\x30\xd3\x0f" - b"\x64\xa5\x00\x00\x63\xce\xcb\x0b\x00\x00\x10\xff" - b"\x8a\x00\x00\x01\xb1\x61\xc9\x3a\x00\x79\x95\x00" - b"\x23\xd6\x17\x00\x48\x03\x00\x00\x1f\x28\x00\xaa" - b"\x55\x00\x00\xb0\x3e\x00\x38\xc5\x00\x21\xca\x00" - b"\x00\x00\xc7\x34\x8d\x5c\x00\x00\x00\x56\xaf\xd6" - b"\x05\x00\x00\x00\x03\xff\x7c\x00\x00\x00\x0a\x0b" - b"\xdc\x14\x00\x00\x0e\xcc\xda\x5d\x00\x00\x00\x29" - b"\x4c\x4c\x4c\x3a\x43\x7c\x7c\xe8\x95\x00\x00\x4a" - b"\xc7\x07\x00\x26\xd7\x1b\x00\x0d\xd0\x3a\x00\x00" - b"\x89\xff\xcc\xcc\xac\x00\x07\x5b\x21\x00\x81\xb8" - b"\x1e\x00\x9f\x54\x00\x00\xa0\x54\x00\x0f\xbb\x49" - b"\x00\x49\xe7\x22\x00\x00\xa1\x53\x00\x00\xa0\x54" - b"\x00\x00\x9a\x64\x00\x00\x40\xd8\x3e\x60\xe4\xe4" - b"\xe4\xe4\xe4\xe4\xe4\xe4\xd5\x55\x30\x00\x5c\xf0" - b"\x10\x00\xd1\x27\x00\xd0\x28\x00\xc6\x51\x00\x9d" - b"\xb8\x00\xcf\x28\x00\xd0\x28\x00\xe0\x21\xad\xac" - b"\x02\x00\x3c\x2c\x00\x21\x10\x3e\xad\xa7\x91\xc1" - b"\x15\x1c\x11\x00\x3b\x2a\x00\x00\x11\x4d\x6f\x6e" - b"\x74\x73\x65\x72\x72\x61\x74\x20\x4d\x65\x64\x69" - b"\x75\x6d\x00\x11\x4d\x6f\x6e\x74\x73\x65\x72\x72" - b"\x61\x74\x2d\x4d\x65\x64\x69\x75\x6d\x01" -) diff --git a/m5stack/fs/system/common/font/MontserratMedium14.py b/m5stack/fs/system/common/font/MontserratMedium14.py deleted file mode 100644 index 139cd954..00000000 --- a/m5stack/fs/system/common/font/MontserratMedium14.py +++ /dev/null @@ -1,840 +0,0 @@ -FONT = ( - b"\x00\x00\x00\x63\x00\x00\x00\x0b\x00\x00\x00\x0e" - b"\x00\x00\x00\x00\x00\x00\x00\x0b\x00\x00\x00\x03" - b"\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x01" - b"\x00\x00\x00\x00\x00\x00\x00\x1c\xff\xff\xff\xf2" - b"\x00\x00\x00\x00\x00\x00\x00\x09\x00\x00\x00\x01" - b"\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x1c" - b"\xff\xff\xff\xf2\x00\x00\x00\x00\x00\x00\x00\x0a" - b"\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x00" - b"\x00\x00\x00\x1c\xff\xff\xff\xf2\x00\x00\x00\x00" - b"\x00\x00\x00\x0d\x00\x00\x00\x01\x00\x00\x00\x01" - b"\x00\x00\x00\x00\x00\x00\x00\x1c\xff\xff\xff\xf2" - b"\x00\x00\x00\x00\x00\x00\x00\x20\x00\x00\x00\x01" - b"\x00\x00\x00\x01\x00\x00\x00\x04\x00\x00\x00\x1c" - b"\xff\xff\xff\xf2\x00\x00\x00\x00\x00\x00\x00\x21" - b"\x00\x00\x00\x0b\x00\x00\x00\x03\x00\x00\x00\x04" - b"\x00\x00\x00\x0a\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x22\x00\x00\x00\x04\x00\x00\x00\x06" - b"\x00\x00\x00\x05\x00\x00\x00\x0a\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x23\x00\x00\x00\x0a" - b"\x00\x00\x00\x0a\x00\x00\x00\x0a\x00\x00\x00\x0a" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x24" - b"\x00\x00\x00\x0e\x00\x00\x00\x09\x00\x00\x00\x09" - b"\x00\x00\x00\x0c\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x25\x00\x00\x00\x0b\x00\x00\x00\x0c" - b"\x00\x00\x00\x0c\x00\x00\x00\x0a\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x26\x00\x00\x00\x0b" - b"\x00\x00\x00\x0a\x00\x00\x00\x0a\x00\x00\x00\x0a" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x27" - b"\x00\x00\x00\x04\x00\x00\x00\x03\x00\x00\x00\x03" - b"\x00\x00\x00\x0a\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x28\x00\x00\x00\x0e\x00\x00\x00\x04" - b"\x00\x00\x00\x05\x00\x00\x00\x0b\x00\x00\x00\x01" - b"\x00\x00\x00\x00\x00\x00\x00\x29\x00\x00\x00\x0e" - b"\x00\x00\x00\x04\x00\x00\x00\x05\x00\x00\x00\x0b" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x2a" - b"\x00\x00\x00\x06\x00\x00\x00\x06\x00\x00\x00\x06" - b"\x00\x00\x00\x0b\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x2b\x00\x00\x00\x07\x00\x00\x00\x08" - b"\x00\x00\x00\x08\x00\x00\x00\x08\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x2c\x00\x00\x00\x05" - b"\x00\x00\x00\x03\x00\x00\x00\x03\x00\x00\x00\x02" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x2d" - b"\x00\x00\x00\x02\x00\x00\x00\x05\x00\x00\x00\x05" - b"\x00\x00\x00\x05\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x2e\x00\x00\x00\x03\x00\x00\x00\x03" - b"\x00\x00\x00\x03\x00\x00\x00\x02\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x2f\x00\x00\x00\x0e" - b"\x00\x00\x00\x07\x00\x00\x00\x05\x00\x00\x00\x0c" - b"\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x30" - b"\x00\x00\x00\x0b\x00\x00\x00\x09\x00\x00\x00\x09" - b"\x00\x00\x00\x0a\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x31\x00\x00\x00\x0a\x00\x00\x00\x04" - b"\x00\x00\x00\x05\x00\x00\x00\x0a\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x32\x00\x00\x00\x0a" - b"\x00\x00\x00\x08\x00\x00\x00\x08\x00\x00\x00\x0a" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x33" - b"\x00\x00\x00\x0b\x00\x00\x00\x08\x00\x00\x00\x08" - b"\x00\x00\x00\x0a\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x34\x00\x00\x00\x0a\x00\x00\x00\x0a" - b"\x00\x00\x00\x09\x00\x00\x00\x0a\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x35\x00\x00\x00\x0b" - b"\x00\x00\x00\x08\x00\x00\x00\x08\x00\x00\x00\x0a" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x36" - b"\x00\x00\x00\x0b\x00\x00\x00\x09\x00\x00\x00\x09" - b"\x00\x00\x00\x0a\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x37\x00\x00\x00\x0a\x00\x00\x00\x08" - b"\x00\x00\x00\x08\x00\x00\x00\x0a\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x38\x00\x00\x00\x0b" - b"\x00\x00\x00\x09\x00\x00\x00\x09\x00\x00\x00\x0a" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x39" - b"\x00\x00\x00\x0b\x00\x00\x00\x08\x00\x00\x00\x09" - b"\x00\x00\x00\x0a\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x3a\x00\x00\x00\x09\x00\x00\x00\x03" - b"\x00\x00\x00\x03\x00\x00\x00\x08\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x3b\x00\x00\x00\x0b" - b"\x00\x00\x00\x03\x00\x00\x00\x03\x00\x00\x00\x08" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x3c" - b"\x00\x00\x00\x07\x00\x00\x00\x08\x00\x00\x00\x08" - b"\x00\x00\x00\x08\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x3d\x00\x00\x00\x06\x00\x00\x00\x08" - b"\x00\x00\x00\x08\x00\x00\x00\x08\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x3e\x00\x00\x00\x07" - b"\x00\x00\x00\x08\x00\x00\x00\x08\x00\x00\x00\x08" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x3f" - b"\x00\x00\x00\x0b\x00\x00\x00\x08\x00\x00\x00\x08" - b"\x00\x00\x00\x0a\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x40\x00\x00\x00\x0d\x00\x00\x00\x0e" - b"\x00\x00\x00\x0e\x00\x00\x00\x0a\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x41\x00\x00\x00\x0a" - b"\x00\x00\x00\x0b\x00\x00\x00\x0a\x00\x00\x00\x0a" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x42" - b"\x00\x00\x00\x0a\x00\x00\x00\x09\x00\x00\x00\x0b" - b"\x00\x00\x00\x0a\x00\x00\x00\x01\x00\x00\x00\x00" - b"\x00\x00\x00\x43\x00\x00\x00\x0b\x00\x00\x00\x0a" - b"\x00\x00\x00\x0a\x00\x00\x00\x0a\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x44\x00\x00\x00\x0a" - b"\x00\x00\x00\x0a\x00\x00\x00\x0c\x00\x00\x00\x0a" - b"\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x45" - b"\x00\x00\x00\x0a\x00\x00\x00\x08\x00\x00\x00\x09" - b"\x00\x00\x00\x0a\x00\x00\x00\x01\x00\x00\x00\x00" - b"\x00\x00\x00\x46\x00\x00\x00\x0a\x00\x00\x00\x08" - b"\x00\x00\x00\x09\x00\x00\x00\x0a\x00\x00\x00\x01" - b"\x00\x00\x00\x00\x00\x00\x00\x47\x00\x00\x00\x0b" - b"\x00\x00\x00\x0a\x00\x00\x00\x0b\x00\x00\x00\x0a" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x48" - b"\x00\x00\x00\x0a\x00\x00\x00\x09\x00\x00\x00\x0b" - b"\x00\x00\x00\x0a\x00\x00\x00\x01\x00\x00\x00\x00" - b"\x00\x00\x00\x49\x00\x00\x00\x0a\x00\x00\x00\x02" - b"\x00\x00\x00\x04\x00\x00\x00\x0a\x00\x00\x00\x01" - b"\x00\x00\x00\x00\x00\x00\x00\x4a\x00\x00\x00\x0b" - b"\x00\x00\x00\x07\x00\x00\x00\x07\x00\x00\x00\x0a" - b"\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x4b" - b"\x00\x00\x00\x0a\x00\x00\x00\x09\x00\x00\x00\x0a" - b"\x00\x00\x00\x0a\x00\x00\x00\x01\x00\x00\x00\x00" - b"\x00\x00\x00\x4c\x00\x00\x00\x0a\x00\x00\x00\x08" - b"\x00\x00\x00\x08\x00\x00\x00\x0a\x00\x00\x00\x01" - b"\x00\x00\x00\x00\x00\x00\x00\x4d\x00\x00\x00\x0a" - b"\x00\x00\x00\x0b\x00\x00\x00\x0d\x00\x00\x00\x0a" - b"\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x4e" - b"\x00\x00\x00\x0a\x00\x00\x00\x09\x00\x00\x00\x0b" - b"\x00\x00\x00\x0a\x00\x00\x00\x01\x00\x00\x00\x00" - b"\x00\x00\x00\x4f\x00\x00\x00\x0b\x00\x00\x00\x0c" - b"\x00\x00\x00\x0c\x00\x00\x00\x0a\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x50\x00\x00\x00\x0a" - b"\x00\x00\x00\x09\x00\x00\x00\x0a\x00\x00\x00\x0a" - b"\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x51" - b"\x00\x00\x00\x0d\x00\x00\x00\x0c\x00\x00\x00\x0c" - b"\x00\x00\x00\x0a\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x52\x00\x00\x00\x0a\x00\x00\x00\x09" - b"\x00\x00\x00\x0a\x00\x00\x00\x0a\x00\x00\x00\x01" - b"\x00\x00\x00\x00\x00\x00\x00\x53\x00\x00\x00\x0b" - b"\x00\x00\x00\x09\x00\x00\x00\x09\x00\x00\x00\x0a" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x54" - b"\x00\x00\x00\x0a\x00\x00\x00\x09\x00\x00\x00\x08" - b"\x00\x00\x00\x0a\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x55\x00\x00\x00\x0b\x00\x00\x00\x09" - b"\x00\x00\x00\x0b\x00\x00\x00\x0a\x00\x00\x00\x01" - b"\x00\x00\x00\x00\x00\x00\x00\x56\x00\x00\x00\x0a" - b"\x00\x00\x00\x0a\x00\x00\x00\x0a\x00\x00\x00\x0a" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x57" - b"\x00\x00\x00\x0a\x00\x00\x00\x10\x00\x00\x00\x10" - b"\x00\x00\x00\x0a\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x58\x00\x00\x00\x0a\x00\x00\x00\x0a" - b"\x00\x00\x00\x09\x00\x00\x00\x0a\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x59\x00\x00\x00\x0a" - b"\x00\x00\x00\x0a\x00\x00\x00\x09\x00\x00\x00\x0a" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x5a" - b"\x00\x00\x00\x0a\x00\x00\x00\x09\x00\x00\x00\x09" - b"\x00\x00\x00\x0a\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x5b\x00\x00\x00\x0e\x00\x00\x00\x04" - b"\x00\x00\x00\x05\x00\x00\x00\x0b\x00\x00\x00\x01" - b"\x00\x00\x00\x00\x00\x00\x00\x5c\x00\x00\x00\x0e" - b"\x00\x00\x00\x07\x00\x00\x00\x05\x00\x00\x00\x0c" - b"\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x5d" - b"\x00\x00\x00\x0e\x00\x00\x00\x04\x00\x00\x00\x05" - b"\x00\x00\x00\x0b\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x5e\x00\x00\x00\x07\x00\x00\x00\x07" - b"\x00\x00\x00\x08\x00\x00\x00\x08\x00\x00\x00\x01" - b"\x00\x00\x00\x00\x00\x00\x00\x5f\x00\x00\x00\x01" - b"\x00\x00\x00\x07\x00\x00\x00\x07\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x60" - b"\x00\x00\x00\x03\x00\x00\x00\x05\x00\x00\x00\x08" - b"\x00\x00\x00\x0b\x00\x00\x00\x01\x00\x00\x00\x00" - b"\x00\x00\x00\x61\x00\x00\x00\x09\x00\x00\x00\x08" - b"\x00\x00\x00\x08\x00\x00\x00\x08\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x62\x00\x00\x00\x0c" - b"\x00\x00\x00\x08\x00\x00\x00\x0a\x00\x00\x00\x0b" - b"\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x63" - b"\x00\x00\x00\x09\x00\x00\x00\x08\x00\x00\x00\x08" - b"\x00\x00\x00\x08\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x64\x00\x00\x00\x0c\x00\x00\x00\x09" - b"\x00\x00\x00\x0a\x00\x00\x00\x0b\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x65\x00\x00\x00\x09" - b"\x00\x00\x00\x08\x00\x00\x00\x09\x00\x00\x00\x08" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x66" - b"\x00\x00\x00\x0b\x00\x00\x00\x06\x00\x00\x00\x05" - b"\x00\x00\x00\x0b\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x67\x00\x00\x00\x0b\x00\x00\x00\x09" - b"\x00\x00\x00\x0a\x00\x00\x00\x08\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x68\x00\x00\x00\x0b" - b"\x00\x00\x00\x08\x00\x00\x00\x0a\x00\x00\x00\x0b" - b"\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x69" - b"\x00\x00\x00\x0b\x00\x00\x00\x03\x00\x00\x00\x04" - b"\x00\x00\x00\x0b\x00\x00\x00\x01\x00\x00\x00\x00" - b"\x00\x00\x00\x6a\x00\x00\x00\x0e\x00\x00\x00\x06" - b"\x00\x00\x00\x04\x00\x00\x00\x0b\xff\xff\xff\xfe" - b"\x00\x00\x00\x00\x00\x00\x00\x6b\x00\x00\x00\x0b" - b"\x00\x00\x00\x08\x00\x00\x00\x09\x00\x00\x00\x0b" - b"\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x6c" - b"\x00\x00\x00\x0b\x00\x00\x00\x02\x00\x00\x00\x04" - b"\x00\x00\x00\x0b\x00\x00\x00\x01\x00\x00\x00\x00" - b"\x00\x00\x00\x6d\x00\x00\x00\x08\x00\x00\x00\x0d" - b"\x00\x00\x00\x0f\x00\x00\x00\x08\x00\x00\x00\x01" - b"\x00\x00\x00\x00\x00\x00\x00\x6e\x00\x00\x00\x08" - b"\x00\x00\x00\x08\x00\x00\x00\x0a\x00\x00\x00\x08" - b"\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x6f" - b"\x00\x00\x00\x09\x00\x00\x00\x09\x00\x00\x00\x09" - b"\x00\x00\x00\x08\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x70\x00\x00\x00\x0b\x00\x00\x00\x08" - b"\x00\x00\x00\x0a\x00\x00\x00\x08\x00\x00\x00\x01" - b"\x00\x00\x00\x00\x00\x00\x00\x71\x00\x00\x00\x0b" - b"\x00\x00\x00\x09\x00\x00\x00\x0a\x00\x00\x00\x08" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x72" - b"\x00\x00\x00\x08\x00\x00\x00\x05\x00\x00\x00\x06" - b"\x00\x00\x00\x08\x00\x00\x00\x01\x00\x00\x00\x00" - b"\x00\x00\x00\x73\x00\x00\x00\x09\x00\x00\x00\x07" - b"\x00\x00\x00\x07\x00\x00\x00\x08\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x74\x00\x00\x00\x0b" - b"\x00\x00\x00\x06\x00\x00\x00\x06\x00\x00\x00\x0a" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x75" - b"\x00\x00\x00\x09\x00\x00\x00\x08\x00\x00\x00\x09" - b"\x00\x00\x00\x08\x00\x00\x00\x01\x00\x00\x00\x00" - b"\x00\x00\x00\x76\x00\x00\x00\x08\x00\x00\x00\x08" - b"\x00\x00\x00\x08\x00\x00\x00\x08\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x77\x00\x00\x00\x08" - b"\x00\x00\x00\x0d\x00\x00\x00\x0d\x00\x00\x00\x08" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x78" - b"\x00\x00\x00\x08\x00\x00\x00\x08\x00\x00\x00\x08" - b"\x00\x00\x00\x08\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x79\x00\x00\x00\x0b\x00\x00\x00\x09" - b"\x00\x00\x00\x08\x00\x00\x00\x08\xff\xff\xff\xff" - b"\x00\x00\x00\x00\x00\x00\x00\x7a\x00\x00\x00\x08" - b"\x00\x00\x00\x07\x00\x00\x00\x07\x00\x00\x00\x08" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x7b" - b"\x00\x00\x00\x0e\x00\x00\x00\x05\x00\x00\x00\x05" - b"\x00\x00\x00\x0b\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x7c\x00\x00\x00\x0e\x00\x00\x00\x02" - b"\x00\x00\x00\x04\x00\x00\x00\x0b\x00\x00\x00\x01" - b"\x00\x00\x00\x00\x00\x00\x00\x7d\x00\x00\x00\x0e" - b"\x00\x00\x00\x05\x00\x00\x00\x05\x00\x00\x00\x0b" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x7e" - b"\x00\x00\x00\x04\x00\x00\x00\x08\x00\x00\x00\x08" - b"\x00\x00\x00\x07\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\xb6\x85\x00\xdc\x9e\x00" - b"\xd2\x94\x00\xc9\x8a\x00\xbf\x80\x00\xb6\x76\x00" - b"\x9c\x62\x00\x00\x00\x00\x84\x55\x01\xe3\x9f\x00" - b"\x06\x02\x17\xcc\x0a\x17\xcc\x0a\x18\xff\x06\x18" - b"\xff\x06\x12\xfe\x00\x12\xfe\x00\x0b\xf3\x00\x0b" - b"\xf3\x00\x00\x00\x00\xa9\x22\x00\x2a\xa1\x00\x00" - b"\x00\x00\x00\xf1\x0e\x00\x51\xae\x00\x00\x0c\x48" - b"\x5a\xff\x48\x48\xb8\xd7\x48\x21\x20\xbc\xed\xff" - b"\xbc\xbc\xff\xff\xbc\x58\x00\x00\x51\xaf\x00\x00" - b"\xaf\x50\x00\x00\x00\x00\x71\x8f\x00\x00\xcf\x31" - b"\x00\x00\x57\x8c\xff\xfb\x8c\x8c\xff\x9d\x8c\x02" - b"\x4d\x7c\xff\xcb\x7c\x89\xff\x7c\x7c\x01\x00\x00" - b"\xd1\x2f\x00\x2d\xd2\x00\x00\x00\x00\x00\xf0\x0f" - b"\x00\x4c\xb3\x00\x00\x00\x00\x00\x00\x03\x6c\x00" - b"\x00\x00\x00\x00\x00\x00\x08\xe0\x00\x00\x00\x00" - b"\x00\x0c\x81\xd4\xff\xc8\x91\x25\x00\x00\xbd\xe1" - b"\x75\xff\x69\xbc\x7c\x00\x21\xff\x4b\x08\xe0\x00" - b"\x00\x02\x00\x19\xff\x7c\x08\xe0\x00\x00\x00\x00" - b"\x00\x84\xff\xda\xff\x41\x03\x00\x00\x00\x00\x2e" - b"\x8f\xff\xfe\xe2\x3c\x00\x00\x00\x00\x08\xe0\x1f" - b"\xbb\xef\x06\x00\x0b\x00\x08\xe0\x00\x52\xff\x15" - b"\x3b\xe0\x6b\x31\xf8\x3a\xc9\xca\x00\x05\x76\xd7" - b"\xff\xff\xf8\xac\x1a\x00\x00\x00\x00\x13\xf6\x04" - b"\x00\x00\x00\x00\x00\x00\x05\x9a\x00\x00\x00\x00" - b"\x00\x6a\xce\xbd\x2a\x00\x00\x00\x74\x67\x00\x00" - b"\x37\xc6\x08\x39\xc9\x00\x00\x33\xd5\x0a\x00\x00" - b"\x74\x76\x00\x00\xde\x08\x07\xd2\x39\x00\x00\x00" - b"\x65\x89\x00\x04\xe4\x02\x8c\x87\x00\x00\x00\x00" - b"\x12\xce\x6b\x9f\x85\x3d\xd0\x06\x2c\x2d\x00\x00" - b"\x00\x19\x6b\x54\x0e\xd7\x30\x99\xb4\xb5\x98\x00" - b"\x00\x00\x00\x00\x99\x7b\x21\xcd\x00\x00\xce\x23" - b"\x00\x00\x00\x48\xc8\x03\x3e\xa7\x00\x00\xa7\x41" - b"\x00\x00\x11\xdb\x27\x00\x17\xd6\x06\x06\xd6\x18" - b"\x00\x00\xa5\x6f\x00\x00\x00\x63\xd4\xd4\x63\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x06\x06\x00\x00" - b"\x00\x00\x39\xbc\xda\xac\x1e\x00\x00\x00\x00\x0a" - b"\xf0\x7d\x21\x91\xc7\x00\x00\x00\x00\x20\xff\x24" - b"\x00\x56\xdc\x00\x00\x00\x00\x01\xcc\xa8\x4c\xe8" - b"\x64\x00\x00\x00\x00\x00\x48\xff\xff\x4d\x00\x00" - b"\x00\x00\x00\x5a\xee\x9a\xec\x9a\x01\x25\x67\x00" - b"\x22\xfa\x4b\x00\x25\xe4\x9b\x81\xb3\x00\x57\xfd" - b"\x04\x00\x00\x24\xfe\xff\x4b\x00\x23\xfa\x92\x1e" - b"\x0e\x4a\xfd\xff\x9d\x01\x00\x49\xd7\xff\xff\xe1" - b"\x70\x24\xda\x28\x00\x00\x00\x0f\x0f\x00\x00\x00" - b"\x05\x00\x17\xcc\x0a\x18\xff\x06\x12\xfe\x00\x0b" - b"\xf3\x00\x00\x0a\x64\x11\x00\x77\xd5\x01\x01\xe1" - b"\x6c\x00\x34\xff\x1b\x00\x6a\xe4\x00\x00\x91\xbd" - b"\x00\x00\xa2\xad\x00\x00\xa6\xa8\x00\x00\x98\xb6" - b"\x00\x00\x79\xd5\x00\x00\x47\xfb\x09\x00\x09\xf4" - b"\x50\x00\x00\x9d\xaf\x00\x00\x22\xb6\x12\x2d\x52" - b"\x00\x00\x22\xfa\x33\x00\x00\xb3\x9e\x00\x00\x63" - b"\xee\x01\x00\x2b\xff\x25\x00\x04\xfe\x4d\x00\x00" - b"\xf2\x5d\x00\x00\xed\x62\x00\x00\xfb\x53\x00\x1c" - b"\xff\x34\x00\x4c\xfb\x08\x00\x98\xb9\x00\x0a\xeb" - b"\x59\x00\x45\xa4\x02\x00\x00\x00\x3e\x15\x00\x00" - b"\x2f\x14\x9f\x36\x3d\x06\x57\xdb\xdd\xcc\xc2\x1a" - b"\x0e\x95\xff\xfb\x4f\x00\x76\x75\xa3\x58\xad\x20" - b"\x00\x00\x84\x2d\x00\x00\x00\x00\x00\x7f\xa2\x00" - b"\x00\x00\x00\x00\x00\x84\xa8\x00\x00\x00\x07\x74" - b"\x74\xf8\xff\x74\x74\x17\x0a\xa8\xa8\xff\xff\xa8" - b"\xa8\x22\x00\x00\x00\x84\xa8\x00\x00\x00\x00\x00" - b"\x00\x84\xa8\x00\x00\x00\x00\x00\x00\x16\x1c\x00" - b"\x00\x00\x1f\xc3\x39\x33\xff\x73\x00\xe1\x27\x1f" - b"\xd1\x00\x05\x0c\x00\x15\x6c\x6c\x6c\x3c\x26\xbc" - b"\xbc\xbc\x69\x23\xc4\x3e\x39\xfc\x5e\x00\x08\x00" - b"\x00\x00\x00\x00\x00\xb3\x3e\x00\x00\x00\x00\x37" - b"\xf5\x0b\x00\x00\x00\x00\x91\xa7\x00\x00\x00\x00" - b"\x03\xe7\x4d\x00\x00\x00\x00\x45\xed\x05\x00\x00" - b"\x00\x00\x9f\x99\x00\x00\x00\x00\x07\xf0\x3f\x00" - b"\x00\x00\x00\x52\xe4\x02\x00\x00\x00\x00\xac\x8c" - b"\x00\x00\x00\x00\x0e\xf7\x32\x00\x00\x00\x00\x5f" - b"\xd8\x00\x00\x00\x00\x00\xb9\x7f\x00\x00\x00\x00" - b"\x16\xfb\x25\x00\x00\x00\x00\x21\x5d\x00\x00\x00" - b"\x00\x00\x00\x00\x34\xb0\xdd\xc8\x65\x00\x00\x00" - b"\x3d\xf7\xae\x5e\x83\xf6\x8b\x00\x00\xd2\xb1\x00" - b"\x00\x00\x5a\xfd\x29\x25\xff\x47\x00\x00\x00\x02" - b"\xed\x7b\x47\xff\x1e\x00\x00\x00\x00\xc7\x9d\x4a" - b"\xff\x1b\x00\x00\x00\x00\xc3\xa2\x2f\xff\x3b\x00" - b"\x00\x00\x00\xe3\x86\x02\xe6\x93\x00\x00\x00\x3b" - b"\xff\x3d\x00\x60\xfd\x7b\x2d\x51\xe2\xb5\x00\x00" - b"\x00\x63\xe5\xff\xf9\x9c\x0b\x00\x00\x00\x00\x00" - b"\x12\x04\x00\x00\x00\xb5\xcc\xd2\x8f\x60\x6c\xfa" - b"\xb4\x00\x00\xac\xb4\x00\x00\xac\xb4\x00\x00\xac" - b"\xb4\x00\x00\xac\xb4\x00\x00\xac\xb4\x00\x00\xac" - b"\xb4\x00\x00\xac\xb4\x00\x00\xac\xb4\x04\x66\xbe" - b"\xe0\xd6\x8f\x11\x00\x86\xef\x86\x5a\x78\xeb\xbf" - b"\x00\x0a\x21\x00\x00\x00\x60\xff\x17\x00\x00\x00" - b"\x00\x00\x5f\xfd\x0c\x00\x00\x00\x00\x0b\xd3\xa3" - b"\x00\x00\x00\x00\x0c\xbf\xd3\x11\x00\x00\x00\x0f" - b"\xc6\xd0\x15\x00\x00\x00\x13\xcc\xcb\x11\x00\x00" - b"\x00\x17\xe3\xfd\x46\x38\x38\x38\x1f\x7b\xff\xff" - b"\xff\xff\xff\xff\x90\x62\xcc\xcc\xcc\xcc\xce\xc8" - b"\x00\x34\x6c\x6c\x6c\x75\xff\xb1\x00\x00\x00\x00" - b"\x00\xa6\xd6\x0e\x00\x00\x00\x00\x78\xef\x25\x00" - b"\x00\x00\x00\x2a\xff\xe7\x6a\x06\x00\x00\x00\x19" - b"\x6e\x8c\xea\xc3\x04\x00\x00\x00\x00\x00\x35\xff" - b"\x40\x08\x00\x00\x00\x00\x1b\xff\x52\xae\xaa\x4a" - b"\x2a\x43\xc1\xed\x10\x3a\xb7\xf8\xff\xff\xc8\x34" - b"\x00\x00\x00\x04\x16\x09\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x9b\x98\x00\x00\x00\x00\x00\x00\x00\x78" - b"\xed\x20\x00\x00\x00\x00\x00\x00\x43\xfa\x49\x00" - b"\x00\x00\x00\x00\x00\x1d\xeb\x81\x00\x00\x00\x00" - b"\x00\x00\x07\xca\xb8\x02\x00\xb9\x40\x00\x00\x00" - b"\x9a\xe0\x13\x00\x00\xfc\x58\x00\x00\x52\xff\xce" - b"\x9c\x9c\x9c\xff\xf4\x9c\x1f\x47\x98\x98\x98\x98" - b"\x9b\xff\xf0\x98\x1e\x00\x00\x00\x00\x00\x04\xff" - b"\x58\x00\x00\x00\x00\x00\x00\x00\x04\xff\x58\x00" - b"\x00\x00\x74\xd4\xcc\xcc\xcc\xcc\x00\x00\xaa\xf9" - b"\x6c\x6c\x6c\x6c\x00\x00\xc4\x8a\x00\x00\x00\x00" - b"\x00\x00\xdd\x70\x00\x00\x00\x00\x00\x00\xff\xff" - b"\xe1\xcf\x99\x25\x00\x02\x54\x54\x58\x74\xd3\xf0" - b"\x1f\x00\x00\x00\x00\x00\x0d\xf4\x77\x07\x01\x00" - b"\x00\x00\x03\xf0\x7d\x84\xc1\x55\x2b\x3a\xa9\xfa" - b"\x2b\x25\xa7\xf1\xff\xff\xd5\x49\x00\x00\x00\x02" - b"\x15\x0d\x00\x00\x00\x00\x00\x19\x8c\xcf\xdf\xc2" - b"\x53\x00\x00\x25\xe8\xd5\x71\x53\x79\x47\x00\x00" - b"\xbe\xcb\x07\x00\x00\x00\x00\x00\x1c\xfe\x4f\x00" - b"\x00\x00\x00\x00\x00\x43\xff\x4a\xab\xe3\xe3\x99" - b"\x15\x00\x4d\xff\xf7\x82\x3e\x56\xda\xcc\x01\x36" - b"\xff\x9c\x00\x00\x00\x3b\xff\x31\x05\xf0\x7d\x00" - b"\x00\x00\x2c\xff\x32\x00\x75\xf1\x51\x0c\x2e\xc7" - b"\xd5\x02\x00\x00\x73\xe8\xff\xfd\xb8\x20\x00\x00" - b"\x00\x00\x00\x12\x09\x00\x00\x00\x75\xd5\xcc\xcc" - b"\xcc\xcc\xd2\xa5\x94\xff\x6c\x6c\x6c\x6c\xff\xa7" - b"\x94\xc4\x00\x00\x00\x40\xff\x35\x19\x21\x00\x00" - b"\x00\xb3\xc2\x00\x00\x00\x00\x00\x28\xfe\x4f\x00" - b"\x00\x00\x00\x00\x9b\xda\x02\x00\x00\x00\x00\x16" - b"\xf7\x69\x00\x00\x00\x00\x00\x83\xec\x0a\x00\x00" - b"\x00\x00\x0a\xec\x83\x00\x00\x00\x00\x00\x6a\xf9" - b"\x18\x00\x00\x00\x00\x08\x7c\xcc\xe2\xcc\x7c\x08" - b"\x00\x00\xa7\xeb\x67\x40\x68\xea\xa8\x00\x01\xfa" - b"\x70\x00\x00\x00\x69\xfc\x03\x00\xe0\x9a\x00\x00" - b"\x00\x94\xe3\x00\x00\x3f\xff\xff\xff\xff\xff\x41" - b"\x00\x01\xa1\xf3\xc2\xb7\xc2\xf3\x9e\x00\x41\xff" - b"\x3d\x00\x00\x00\x3b\xff\x43\x58\xff\x18\x00\x00" - b"\x00\x14\xff\x5b\x17\xf2\xb3\x2a\x0b\x2a\xb3\xf2" - b"\x18\x00\x34\xc2\xfd\xff\xfc\xc1\x34\x00\x00\x00" - b"\x00\x08\x17\x07\x00\x00\x00\x00\x2b\xaa\xdd\xd4" - b"\x94\x18\x00\x21\xf1\xb6\x4f\x4b\xb5\xe0\x11\x81" - b"\xdf\x02\x00\x00\x04\xe1\x85\x90\xd3\x00\x00\x00" - b"\x00\xdd\xd0\x47\xff\x73\x0c\x18\x8a\xff\xed\x00" - b"\x6d\xef\xff\xfe\xb5\x94\xea\x00\x00\x05\x20\x0a" - b"\x00\x9f\xca\x00\x00\x00\x00\x00\x19\xf3\x78\x00" - b"\x52\x2e\x23\x54\xdb\xd6\x0a\x03\xc0\xfe\xff\xf4" - b"\x9d\x12\x00\x00\x00\x07\x14\x01\x00\x00\x00\x23" - b"\xc4\x3e\x39\xfc\x5e\x00\x08\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x23\xc4\x3e\x39\xfc\x5e\x00" - b"\x08\x00\x23\xc4\x3e\x39\xfc\x5e\x00\x08\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x1f\xc3\x39\x33" - b"\xff\x73\x00\xe1\x27\x1f\xd1\x00\x05\x0c\x00\x00" - b"\x00\x00\x00\x03\x4e\xb4\x31\x00\x00\x20\x83\xe5" - b"\xd7\x76\x0b\x08\xc8\xf1\x9e\x3b\x00\x00\x00\x0b" - b"\xff\xd8\x69\x0f\x00\x00\x00\x00\x05\x53\xb8\xf4" - b"\xa3\x41\x01\x00\x00\x00\x00\x20\x83\xe5\x33\x00" - b"\x00\x00\x00\x00\x00\x03\x07\x01\x1c\x1c\x1c\x1c" - b"\x1c\x1c\x05\x10\xff\xff\xff\xff\xff\xff\x34\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x0c\xcc\xcc\xcc\xcc\xcc\xcc\x29\x05" - b"\x50\x50\x50\x50\x50\x50\x10\x0f\xc2\x5d\x08\x00" - b"\x00\x00\x00\x03\x68\xca\xee\x92\x2c\x00\x00\x00" - b"\x00\x00\x2b\x8e\xea\xd7\x1c\x00\x00\x00\x08\x59" - b"\xc0\xff\x26\x00\x33\x95\xee\xc7\x61\x0a\x00\x0f" - b"\xee\x92\x2c\x00\x00\x00\x00\x02\x08\x00\x00\x00" - b"\x00\x00\x00\x03\x67\xc0\xe0\xd6\x93\x13\x00\x93" - b"\xeb\x79\x4e\x70\xec\xc3\x00\x0e\x1f\x00\x00\x00" - b"\x6d\xff\x06\x00\x00\x00\x00\x00\x93\xdb\x00\x00" - b"\x00\x00\x00\x6c\xf7\x45\x00\x00\x00\x00\x54\xfb" - b"\x49\x00\x00\x00\x00\x00\xa8\xa1\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x7b\x58" - b"\x00\x00\x00\x00\x00\x00\xd7\xa3\x00\x00\x00\x00" - b"\x00\x00\x05\x02\x00\x00\x00\x00\x00\x00\x04\x5b" - b"\xab\xd2\xdd\xc7\x8b\x29\x00\x00\x00\x00\x00\x28" - b"\xd5\xaf\x4c\x19\x0e\x27\x6a\xd9\x8a\x02\x00\x00" - b"\x1b\xe2\x57\x00\x00\x07\x06\x00\x03\x0f\xaf\x8d" - b"\x00\x00\xa4\x7f\x00\x22\xbc\xfe\xfd\xb5\x90\xc0" - b"\x09\xdd\x2e\x0d\xf2\x0c\x07\xdb\xa6\x1c\x0b\x67" - b"\xff\xc0\x00\x71\x8b\x3e\xc6\x00\x4f\xef\x07\x00" - b"\x00\x00\xff\xd9\x00\x36\xba\x4e\xb5\x00\x6d\xce" - b"\x00\x00\x00\x00\xd4\xf1\x00\x2c\xc6\x3b\xca\x00" - b"\x4d\xf1\x0b\x00\x00\x00\xff\xd9\x00\x47\xad\x09" - b"\xf2\x11\x06\xd8\xba\x21\x0f\x6c\xff\xdb\x10\xa6" - b"\x64\x00\x99\x8b\x00\x1f\xba\xfe\xfd\xb4\x2b\xd9" - b"\xfe\xaa\x05\x00\x13\xdd\x68\x00\x00\x06\x06\x00" - b"\x00\x00\x09\x00\x00\x00\x00\x1f\xc8\xbd\x5a\x2a" - b"\x24\x43\x8f\x0a\x00\x00\x00\x00\x00\x00\x01\x4e" - b"\x9e\xc5\xcb\xaf\x6b\x0b\x00\x00\x00\x00\x00\x00" - b"\x00\x9b\xc3\x08\x00\x00\x00\x00\x00\x00\x00\x2b" - b"\xff\xff\x66\x00\x00\x00\x00\x00\x00\x00\x9f\xb8" - b"\x81\xd9\x01\x00\x00\x00\x00\x00\x19\xf9\x48\x17" - b"\xf9\x4f\x00\x00\x00\x00\x00\x87\xd6\x01\x00\xa0" - b"\xc3\x00\x00\x00\x00\x0b\xee\x66\x00\x00\x2f\xff" - b"\x37\x00\x00\x00\x6f\xff\x99\x90\x90\x90\xff\xac" - b"\x00\x00\x03\xe4\xff\x8c\x8c\x8c\x8c\xda\xff\x23" - b"\x00\x57\xfa\x1a\x00\x00\x00\x00\x02\xdb\x95\x00" - b"\xca\xa4\x00\x00\x00\x00\x00\x00\x6c\xf5\x13\x6c" - b"\xcc\xcc\xcc\xcb\xb8\x89\x1b\x00\x88\xea\x54\x54" - b"\x55\x6d\xd5\xe2\x11\x88\xe0\x00\x00\x00\x00\x1f" - b"\xff\x4f\x88\xe0\x00\x00\x00\x00\x34\xff\x3c\x88" - b"\xff\x88\x88\x88\xa2\xff\x99\x00\x88\xff\x9c\x9c" - b"\x9c\xaf\xf6\xdc\x2c\x88\xe0\x00\x00\x00\x00\x02" - b"\xc0\xba\x88\xe0\x00\x00\x00\x00\x00\x95\xd6\x88" - b"\xe4\x24\x24\x24\x2d\x66\xf3\x8f\x88\xff\xff\xff" - b"\xff\xf6\xd9\x83\x06\x00\x00\x03\x60\xb9\xdf\xd8" - b"\xa5\x3a\x00\x00\x09\xbc\xfc\x9b\x5e\x64\xb0\xfd" - b"\x4e\x00\x98\xf4\x35\x00\x00\x00\x00\x41\x04\x12" - b"\xfb\x73\x00\x00\x00\x00\x00\x00\x00\x45\xff\x24" - b"\x00\x00\x00\x00\x00\x00\x00\x4a\xff\x1d\x00\x00" - b"\x00\x00\x00\x00\x00\x1f\xff\x5a\x00\x00\x00\x00" - b"\x00\x00\x00\x00\xb9\xe3\x13\x00\x00\x00\x00\x1a" - b"\x00\x00\x1e\xe4\xe7\x67\x2d\x33\x7c\xf3\x52\x00" - b"\x00\x17\x99\xef\xff\xff\xda\x6b\x01\x00\x00\x00" - b"\x00\x01\x14\x0d\x00\x00\x00\x6c\xcc\xcc\xcc\xc9" - b"\xb3\x7b\x1b\x00\x00\x88\xed\x6c\x6c\x6f\x8a\xda" - b"\xf2\x53\x00\x88\xe0\x00\x00\x00\x00\x04\x95\xf8" - b"\x2c\x88\xe0\x00\x00\x00\x00\x00\x03\xdc\x9c\x88" - b"\xe0\x00\x00\x00\x00\x00\x00\x96\xd1\x88\xe0\x00" - b"\x00\x00\x00\x00\x00\x90\xd6\x88\xe0\x00\x00\x00" - b"\x00\x00\x00\xca\xad\x88\xe0\x00\x00\x00\x00\x00" - b"\x63\xff\x45\x88\xe7\x38\x38\x3a\x54\xa7\xfe\x82" - b"\x00\x88\xff\xff\xff\xfd\xe8\xb2\x45\x00\x00\x6c" - b"\xcc\xcc\xcc\xcc\xcc\xcc\x4f\x88\xed\x6c\x6c\x6c" - b"\x6c\x6c\x2a\x88\xe0\x00\x00\x00\x00\x00\x00\x88" - b"\xe0\x00\x00\x00\x00\x00\x00\x88\xff\x94\x94\x94" - b"\x94\x73\x00\x88\xff\xa0\xa0\xa0\xa0\x7d\x00\x88" - b"\xe0\x00\x00\x00\x00\x00\x00\x88\xe0\x00\x00\x00" - b"\x00\x00\x00\x88\xe7\x38\x38\x38\x38\x38\x20\x88" - b"\xff\xff\xff\xff\xff\xff\x94\x6c\xcc\xcc\xcc\xcc" - b"\xcc\xcc\x4f\x88\xed\x6c\x6c\x6c\x6c\x6c\x2a\x88" - b"\xe0\x00\x00\x00\x00\x00\x00\x88\xe0\x00\x00\x00" - b"\x00\x00\x00\x88\xeb\x2c\x2c\x2c\x2c\x22\x00\x88" - b"\xff\xff\xff\xff\xff\xc8\x00\x88\xe2\x08\x08\x08" - b"\x08\x06\x00\x88\xe0\x00\x00\x00\x00\x00\x00\x88" - b"\xe0\x00\x00\x00\x00\x00\x00\x88\xe0\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x03\x5d\xb7\xde\xdb\xae\x4a" - b"\x00\x00\x09\xbc\xfd\x9e\x5f\x61\xa3\xfb\x67\x00" - b"\x98\xf5\x38\x00\x00\x00\x00\x34\x0a\x12\xfb\x74" - b"\x00\x00\x00\x00\x00\x00\x00\x45\xff\x24\x00\x00" - b"\x00\x00\x00\x00\x00\x4a\xff\x1e\x00\x00\x00\x00" - b"\x00\xb3\x94\x1f\xff\x5c\x00\x00\x00\x00\x00\xbc" - b"\x9c\x00\xb8\xe6\x16\x00\x00\x00\x00\xc6\x9c\x00" - b"\x1d\xe2\xea\x69\x2e\x39\x6c\xff\xd1\x00\x00\x15" - b"\x96\xed\xff\xff\xe4\x89\x0e\x00\x00\x00\x00\x01" - b"\x13\x10\x00\x00\x00\x6c\xb2\x00\x00\x00\x00\x00" - b"\x66\xb5\x88\xe0\x00\x00\x00\x00\x00\x80\xe4\x88" - b"\xe0\x00\x00\x00\x00\x00\x80\xe4\x88\xe0\x00\x00" - b"\x00\x00\x00\x80\xe4\x88\xff\x98\x98\x98\x98\x98" - b"\xdf\xe4\x88\xff\xa0\xa0\xa0\xa0\xa0\xe4\xe4\x88" - b"\xe0\x00\x00\x00\x00\x00\x80\xe4\x88\xe0\x00\x00" - b"\x00\x00\x00\x80\xe4\x88\xe0\x00\x00\x00\x00\x00" - b"\x80\xe4\x88\xe0\x00\x00\x00\x00\x00\x80\xe4\x6c" - b"\xb2\x88\xe0\x88\xe0\x88\xe0\x88\xe0\x88\xe0\x88" - b"\xe0\x88\xe0\x88\xe0\x88\xe0\x00\x19\xcc\xcc\xcc" - b"\xcc\xa2\x00\x0d\x6c\x6c\x6c\xc3\xcc\x00\x00\x00" - b"\x00\x00\x98\xcc\x00\x00\x00\x00\x00\x98\xcc\x00" - b"\x00\x00\x00\x00\x98\xcc\x00\x00\x00\x00\x00\x98" - b"\xcc\x00\x00\x00\x00\x00\x98\xcb\x00\x0d\x00\x00" - b"\x00\xac\xb4\x04\xda\x7e\x22\x4c\xf6\x74\x00\x55" - b"\xe1\xff\xfa\x9f\x07\x00\x00\x00\x13\x03\x00\x00" - b"\x6c\xb2\x00\x00\x00\x00\x2d\xc7\x52\x88\xe0\x00" - b"\x00\x00\x29\xe9\x8c\x00\x88\xe0\x00\x00\x25\xe6" - b"\x99\x00\x00\x88\xe0\x00\x21\xe3\xa5\x02\x00\x00" - b"\x88\xe0\x1e\xee\xff\x04\x00\x00\x00\x88\xf9\xdc" - b"\xff\xff\x56\x00\x00\x00\x88\xff\xc6\x0d\x9a\xf5" - b"\x32\x00\x00\x88\xff\x0e\x00\x04\xba\xe3\x18\x00" - b"\x88\xe0\x00\x00\x00\x0e\xd4\xc7\x07\x88\xe0\x00" - b"\x00\x00\x00\x1f\xe7\xa1\x6c\xb2\x00\x00\x00\x00" - b"\x00\x00\x88\xe0\x00\x00\x00\x00\x00\x00\x88\xe0" - b"\x00\x00\x00\x00\x00\x00\x88\xe0\x00\x00\x00\x00" - b"\x00\x00\x88\xe0\x00\x00\x00\x00\x00\x00\x88\xe0" - b"\x00\x00\x00\x00\x00\x00\x88\xe0\x00\x00\x00\x00" - b"\x00\x00\x88\xe0\x00\x00\x00\x00\x00\x00\x88\xe7" - b"\x38\x38\x38\x38\x38\x09\x88\xff\xff\xff\xff\xff" - b"\xff\x2c\x6c\xad\x01\x00\x00\x00\x00\x00\x00\x66" - b"\xb5\x88\xff\x64\x00\x00\x00\x00\x00\x16\xf0\xe4" - b"\x88\xff\xeb\x11\x00\x00\x00\x00\x9d\xff\xe4\x88" - b"\xd3\xcf\x94\x00\x00\x00\x35\xf9\x9f\xe5\x88\xd0" - b"\x3a\xfb\x2e\x00\x01\xc8\x8f\x73\xe5\x88\xd0\x00" - b"\xa0\xc2\x00\x60\xe8\x0e\x73\xe5\x88\xd0\x00\x16" - b"\xf0\x68\xe7\x5f\x00\x72\xe6\x88\xd0\x00\x00\x6d" - b"\xff\xc6\x01\x00\x72\xe6\x88\xd0\x00\x00\x03\xb4" - b"\x31\x00\x00\x71\xe7\x88\xd0\x00\x00\x00\x00\x00" - b"\x00\x00\x70\xe7\x6c\xb5\x0a\x00\x00\x00\x00\x66" - b"\xb5\x88\xff\xa9\x00\x00\x00\x00\x80\xe4\x88\xff" - b"\xfe\x78\x00\x00\x00\x80\xe4\x88\xe0\x7d\xfd\x48" - b"\x00\x00\x80\xe4\x88\xe0\x01\xad\xef\x24\x00\x80" - b"\xe4\x88\xe0\x00\x0c\xd4\xd4\x0c\x80\xe4\x88\xe0" - b"\x00\x00\x23\xee\xae\x81\xe4\x88\xe0\x00\x00\x00" - b"\x47\xfd\xfd\xe4\x88\xe0\x00\x00\x00\x00\x76\xff" - b"\xe5\x88\xe0\x00\x00\x00\x00\x00\xa6\xe4\x00\x00" - b"\x02\x5e\xb8\xde\xda\xa8\x41\x00\x00\x00\x00\x08" - b"\xb9\xfb\x99\x5d\x65\xb5\xff\x8a\x00\x00\x00\x97" - b"\xf5\x35\x00\x00\x00\x00\x6a\xff\x5c\x00\x12\xfb" - b"\x75\x00\x00\x00\x00\x00\x00\xb5\xd2\x00\x45\xff" - b"\x24\x00\x00\x00\x00\x00\x00\x61\xff\x09\x4a\xff" - b"\x1d\x00\x00\x00\x00\x00\x00\x5a\xff\x0d\x1e\xff" - b"\x5c\x00\x00\x00\x00\x00\x00\x9c\xe2\x00\x00\xb6" - b"\xe5\x14\x00\x00\x00\x00\x3d\xfd\x7c\x00\x00\x1b" - b"\xdf\xe7\x65\x2c\x33\x81\xf9\xbc\x06\x00\x00\x00" - b"\x13\x93\xeb\xff\xff\xdf\x78\x05\x00\x00\x00\x00" - b"\x00\x00\x01\x13\x0f\x00\x00\x00\x00\x00\x6c\xd5" - b"\xcc\xcc\xc6\xa9\x58\x01\x00\x88\xff\x6c\x6c\x75" - b"\xa3\xfa\xa0\x00\x88\xe0\x00\x00\x00\x00\x5d\xff" - b"\x2d\x88\xe0\x00\x00\x00\x00\x10\xff\x57\x88\xe0" - b"\x00\x00\x00\x00\x3d\xff\x3d\x88\xff\x30\x30\x38" - b"\x65\xe2\xca\x02\x88\xff\xff\xff\xfe\xe6\x94\x11" - b"\x00\x88\xe6\x08\x08\x03\x00\x00\x00\x00\x88\xe0" - b"\x00\x00\x00\x00\x00\x00\x00\x88\xe0\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x02\x5e\xb8\xde\xda\xa8" - b"\x41\x00\x00\x00\x00\x08\xb9\xfb\x99\x5d\x65\xb5" - b"\xff\x8a\x00\x00\x00\x97\xf5\x35\x00\x00\x00\x00" - b"\x6a\xff\x5c\x00\x12\xfb\x75\x00\x00\x00\x00\x00" - b"\x00\xb5\xd2\x00\x45\xff\x24\x00\x00\x00\x00\x00" - b"\x00\x61\xff\x09\x4a\xff\x1e\x00\x00\x00\x00\x00" - b"\x00\x5a\xff\x0d\x1e\xff\x5e\x00\x00\x00\x00\x00" - b"\x00\x9d\xe2\x00\x00\xb6\xe5\x14\x00\x00\x00\x00" - b"\x3d\xfd\x7c\x00\x00\x1b\xdf\xe7\x65\x2c\x33\x81" - b"\xf9\xbc\x06\x00\x00\x00\x13\x93\xeb\xff\xff\xeb" - b"\x78\x05\x00\x00\x00\x00\x00\x00\x01\x3a\xf4\xc8" - b"\x35\x26\x96\x3f\x00\x00\x00\x00\x00\x00\x17\xa4" - b"\xf7\xfc\xb5\x19\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x01\x03\x00\x00\x6c\xd5\xcc\xcc\xc6\xa9\x58\x01" - b"\x00\x88\xff\x6c\x6c\x75\xa3\xfa\xa0\x00\x88\xe0" - b"\x00\x00\x00\x00\x5d\xff\x2d\x88\xe0\x00\x00\x00" - b"\x00\x10\xff\x57\x88\xe0\x00\x00\x00\x00\x3d\xff" - b"\x3d\x88\xfe\x2c\x2c\x34\x63\xe2\xca\x02\x88\xff" - b"\xff\xff\xff\xff\xb1\x11\x00\x88\xe3\x04\x04\x02" - b"\xb9\xc4\x03\x00\x88\xe0\x00\x00\x00\x1a\xed\x7c" - b"\x00\x88\xe0\x00\x00\x00\x00\x55\xfb\x36\x00\x0c" - b"\x81\xcc\xe1\xc8\x91\x25\x00\x00\xbd\xe1\x6d\x4e" - b"\x69\xbc\x7c\x00\x21\xff\x4b\x00\x00\x00\x00\x02" - b"\x00\x19\xff\x7c\x00\x00\x00\x00\x00\x00\x00\x84" - b"\xff\xd2\x85\x41\x03\x00\x00\x00\x00\x2e\x87\xcc" - b"\xfe\xe2\x3c\x00\x00\x00\x00\x00\x00\x1f\xbb\xef" - b"\x06\x00\x0b\x00\x00\x00\x00\x52\xff\x15\x3b\xe0" - b"\x6b\x29\x18\x3a\xc9\xca\x00\x05\x76\xd7\xff\xff" - b"\xf8\xac\x1a\x00\x00\x00\x00\x0b\x16\x04\x00\x00" - b"\x00\xbf\xcc\xcc\xcc\xcc\xcc\xcc\xcc\x1f\x65\x6c" - b"\x6c\xc1\xe1\x6c\x6c\x6c\x10\x00\x00\x00\x94\xcc" - b"\x00\x00\x00\x00\x00\x00\x00\x94\xcc\x00\x00\x00" - b"\x00\x00\x00\x00\x94\xcc\x00\x00\x00\x00\x00\x00" - b"\x00\x94\xcc\x00\x00\x00\x00\x00\x00\x00\x94\xcc" - b"\x00\x00\x00\x00\x00\x00\x00\x94\xcc\x00\x00\x00" - b"\x00\x00\x00\x00\x94\xcc\x00\x00\x00\x00\x00\x00" - b"\x00\x94\xcc\x00\x00\x00\x00\x7c\x9f\x00\x00\x00" - b"\x00\x00\x8c\x89\x9c\xc8\x00\x00\x00\x00\x00\xb0" - b"\xac\x9c\xc8\x00\x00\x00\x00\x00\xb0\xac\x9c\xc8" - b"\x00\x00\x00\x00\x00\xb0\xac\x9c\xc8\x00\x00\x00" - b"\x00\x00\xb0\xac\x9b\xc9\x00\x00\x00\x00\x00\xb1" - b"\xab\x8e\xe1\x00\x00\x00\x00\x00\xca\x9d\x5b\xfd" - b"\x2c\x00\x00\x00\x1b\xf6\x6a\x08\xdb\xdc\x57\x2e" - b"\x4f\xd1\xe2\x0e\x00\x1d\xaf\xf9\xff\xfb\xb6\x22" - b"\x00\x00\x00\x00\x06\x17\x07\x00\x00\x00\xab\x89" - b"\x00\x00\x00\x00\x00\x00\x79\xa4\x72\xf9\x17\x00" - b"\x00\x00\x00\x0d\xf0\x6a\x0e\xf2\x80\x00\x00\x00" - b"\x00\x6f\xee\x0b\x00\x91\xe9\x07\x00\x00\x02\xdd" - b"\x89\x00\x00\x22\xfd\x60\x00\x00\x51\xfb\x1d\x00" - b"\x00\x00\xaf\xcf\x00\x00\xc1\xa9\x00\x00\x00\x00" - b"\x3f\xff\x40\x32\xff\x38\x00\x00\x00\x00\x00\xcd" - b"\xb0\xa3\xc8\x00\x00\x00\x00\x00\x00\x5d\xff\xff" - b"\x58\x00\x00\x00\x00\x00\x00\x06\xfc\xfd\x04\x00" - b"\x00\x00\x54\xc7\x09\x00\x00\x00\x00\xab\x91\x00" - b"\x00\x00\x00\x1e\xcc\x24\x1e\xff\x52\x00\x00\x00" - b"\x26\xff\xfa\x0c\x00\x00\x00\x74\xe0\x00\x00\xc8" - b"\xa7\x00\x00\x00\x7f\xd2\xf2\x5a\x00\x00\x00\xca" - b"\x8b\x00\x00\x73\xf3\x09\x00\x00\xd7\x72\xa4\xb1" - b"\x00\x00\x21\xff\x36\x00\x00\x1e\xff\x52\x00\x30" - b"\xfe\x1c\x4d\xf8\x0f\x00\x77\xe0\x00\x00\x00\x00" - b"\xc8\xa7\x00\x89\xc2\x00\x06\xf0\x5e\x00\xcd\x8b" - b"\x00\x00\x00\x00\x73\xf3\x0a\xdf\x6a\x00\x00\xa0" - b"\xb5\x23\xff\x36\x00\x00\x00\x00\x1e\xff\x8c\xfc" - b"\x16\x00\x00\x49\xfa\x8b\xe0\x00\x00\x00\x00\x00" - b"\x00\xc8\xff\xbb\x00\x00\x00\x05\xed\xff\x8b\x00" - b"\x00\x00\x00\x00\x00\x73\xff\x63\x00\x00\x00\x00" - b"\x9c\xff\x36\x00\x00\x00\x3e\xcc\x3a\x00\x00\x00" - b"\x02\xa5\x8d\x00\x00\xa8\xe0\x10\x00\x00\x79\xed" - b"\x1c\x00\x00\x0f\xdf\xab\x00\x37\xfa\x4f\x00\x00" - b"\x00\x00\x39\xfb\x73\xdd\x95\x00\x00\x00\x00\x00" - b"\x00\x81\xff\xf1\x08\x00\x00\x00\x00\x00\x00\x8a" - b"\xff\xf4\x11\x00\x00\x00\x00\x00\x43\xfd\x5c\xdd" - b"\xae\x00\x00\x00\x00\x15\xe7\x96\x00\x37\xfb\x69" - b"\x00\x00\x01\xb6\xd5\x09\x00\x00\x78\xf7\x2c\x00" - b"\x73\xf8\x2f\x00\x00\x00\x01\xbb\xd4\x09\xa6\x8a" - b"\x00\x00\x00\x00\x00\x67\xae\x02\x45\xfe\x3a\x00" - b"\x00\x00\x1b\xf3\x51\x00\x00\xa9\xd1\x03\x00\x00" - b"\xac\xb6\x00\x00\x00\x1a\xf2\x71\x00\x48\xf7\x22" - b"\x00\x00\x00\x00\x70\xf2\x22\xdc\x7e\x00\x00\x00" - b"\x00\x00\x03\xd0\xff\xda\x07\x00\x00\x00\x00\x00" - b"\x00\x4e\xff\x5f\x00\x00\x00\x00\x00\x00\x00\x28" - b"\xff\x38\x00\x00\x00\x00\x00\x00\x00\x28\xff\x38" - b"\x00\x00\x00\x00\x00\x00\x00\x28\xff\x38\x00\x00" - b"\x00\x00\x3c\xcc\xcc\xcc\xcc\xcc\xcc\xce\x7c\x20" - b"\x6c\x6c\x6c\x6c\x6c\xba\xff\x53\x00\x00\x00\x00" - b"\x00\x29\xf2\x87\x00\x00\x00\x00\x00\x10\xda\xb7" - b"\x02\x00\x00\x00\x00\x02\xb6\xdb\x10\x00\x00\x00" - b"\x00\x00\x88\xf3\x2b\x00\x00\x00\x00\x00\x56\xfe" - b"\x52\x00\x00\x00\x00\x00\x2f\xf5\x83\x00\x00\x00" - b"\x00\x00\x13\xe4\xec\x3a\x38\x38\x38\x38\x2c\x63" - b"\xff\xff\xff\xff\xff\xff\xff\xcc\x35\x64\x64\x27" - b"\x88\xf2\xb8\x47\x88\xd0\x00\x00\x88\xd0\x00\x00" - b"\x88\xd0\x00\x00\x88\xd0\x00\x00\x88\xd0\x00\x00" - b"\x88\xd0\x00\x00\x88\xd0\x00\x00\x88\xd0\x00\x00" - b"\x88\xd0\x00\x00\x88\xd0\x00\x00\x88\xe2\x64\x27" - b"\x61\xb8\xb8\x47\x4e\xa4\x00\x00\x00\x00\x00\x18" - b"\xfc\x23\x00\x00\x00\x00\x00\xbc\x7c\x00\x00\x00" - b"\x00\x00\x62\xd6\x00\x00\x00\x00\x00\x0f\xf8\x30" - b"\x00\x00\x00\x00\x00\xae\x8a\x00\x00\x00\x00\x00" - b"\x54\xe2\x01\x00\x00\x00\x00\x08\xf1\x3d\x00\x00" - b"\x00\x00\x00\xa1\x97\x00\x00\x00\x00\x00\x47\xec" - b"\x05\x00\x00\x00\x00\x03\xe9\x4a\x00\x00\x00\x00" - b"\x00\x94\xa4\x00\x00\x00\x00\x00\x3a\xf3\x0a\x00" - b"\x00\x00\x00\x00\x64\x18\x49\x64\x64\x12\x87\xc3" - b"\xff\x30\x00\x28\xff\x30\x00\x28\xff\x30\x00\x28" - b"\xff\x30\x00\x28\xff\x30\x00\x28\xff\x30\x00\x28" - b"\xff\x30\x00\x28\xff\x30\x00\x28\xff\x30\x00\x28" - b"\xff\x30\x00\x28\xff\x30\x49\x7c\xff\x30\x87\xb8" - b"\xb8\x22\x00\x00\x91\xc6\x00\x00\x00\x00\x11\xe9" - b"\xe3\x31\x00\x00\x00\x74\x96\x6c\x9e\x00\x00\x02" - b"\xde\x2c\x0e\xeb\x13\x00\x4f\xc1\x00\x00\x99\x77" - b"\x00\xbc\x56\x00\x00\x2f\xe0\x03\x03\x00\x00\x00" - b"\x00\x03\x00\xe0\xe0\xe0\xe0\xe0\xe0\xe0\x0c\x4f" - b"\x28\x00\x00\x00\x5a\xe7\x3f\x00\x00\x00\x2b\x76" - b"\x07\x00\x02\x40\x6a\x68\x2e\x00\x00\x02\xca\xeb" - b"\xbb\xc8\xfe\x73\x00\x00\x3c\x07\x00\x00\x7b\xf8" - b"\x04\x00\x00\x20\x3f\x44\x78\xff\x22\x02\xac\xee" - b"\xbb\xb4\xe4\xff\x28\x3b\xff\x26\x00\x00\x37\xff" - b"\x28\x2c\xff\x5b\x01\x1b\xd6\xff\x28\x00\x76\xf0" - b"\xfb\xeb\x84\xff\x28\x00\x00\x02\x0e\x00\x00\x00" - b"\x00\x47\x3e\x00\x00\x00\x00\x00\x00\xb8\xa0\x00" - b"\x00\x00\x00\x00\x00\xb8\xa0\x00\x00\x00\x00\x00" - b"\x00\xb8\xa0\x14\x62\x71\x3f\x00\x00\xb8\xd5\xec" - b"\xc9\xbe\xfa\xb8\x08\xb8\xff\x4d\x00\x00\x29\xed" - b"\x86\xcb\xff\x00\x00\x00\x00\x86\xda\xd8\xff\x00" - b"\x00\x00\x00\x6d\xeb\xc0\xff\x05\x00\x00\x00\xae" - b"\xc1\xb8\xff\xad\x2f\x25\x89\xfe\x4a\xb8\x9b\x9e" - b"\xf8\xff\xd9\x55\x00\x00\x00\x00\x03\x0a\x00\x00" - b"\x00\x00\x00\x0c\x58\x74\x57\x0b\x00\x00\x3d\xea" - b"\xe2\xb7\xe6\xe0\x22\x0a\xea\xa0\x04\x00\x08\x86" - b"\x23\x4d\xff\x14\x00\x00\x00\x00\x00\x5f\xfb\x00" - b"\x00\x00\x00\x00\x00\x32\xff\x3d\x00\x00\x00\x07" - b"\x00\x00\xb3\xea\x4d\x22\x5a\xe6\x42\x00\x0a\x8f" - b"\xef\xff\xee\x83\x03\x00\x00\x00\x00\x0d\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x06\x64\x1c\x00\x00" - b"\x00\x00\x00\x00\x10\xff\x48\x00\x00\x00\x00\x00" - b"\x00\x10\xff\x48\x00\x00\x11\x5e\x73\x43\x11\xff" - b"\x48\x00\x45\xf0\xe1\xb8\xe8\xbe\xff\x48\x0c\xee" - b"\x9f\x03\x00\x08\xc3\xff\x48\x4f\xff\x14\x00\x00" - b"\x00\x32\xff\x48\x5f\xfb\x01\x00\x00\x00\x18\xff" - b"\x48\x36\xff\x3c\x00\x00\x00\x58\xff\x48\x00\xbd" - b"\xe0\x4a\x1f\x56\xea\xff\x48\x00\x0f\x9d\xf6\xff" - b"\xde\x53\xff\x48\x00\x00\x00\x01\x0c\x00\x00\x00" - b"\x00\x00\x00\x12\x5f\x73\x46\x02\x00\x00\x47\xf1" - b"\xcd\xac\xea\xc3\x0c\x0c\xef\x8c\x00\x00\x16\xd8" - b"\x8f\x4f\xff\x43\x3c\x3c\x3c\x9d\xe7\x5f\xff\xbc" - b"\xbc\xbc\xbc\xbe\xb5\x34\xff\x31\x00\x00\x00\x00" - b"\x00\x00\xb6\xdf\x4f\x20\x43\xbe\x33\x00\x0a\x8f" - b"\xee\xff\xf5\xa2\x0f\x00\x00\x00\x00\x0d\x02\x00" - b"\x00\x00\x00\x1c\x66\x61\x0f\x00\x21\xf1\xc5\xc3" - b"\x39\x00\x76\xde\x01\x00\x00\x56\xf4\xff\x6c\x65" - b"\x00\x8c\xff\xff\xb0\xa5\x00\x00\x88\xd0\x00\x00" - b"\x00\x00\x88\xd0\x00\x00\x00\x00\x88\xd0\x00\x00" - b"\x00\x00\x88\xd0\x00\x00\x00\x00\x88\xd0\x00\x00" - b"\x00\x00\x88\xd0\x00\x00\x00\x00\x00\x14\x5f\x74" - b"\x4b\x04\x61\x28\x00\x4f\xf3\xdf\xb7\xdf\xc9\xf4" - b"\x60\x13\xf4\xa1\x03\x00\x03\xa1\xff\x60\x55\xfe" - b"\x0e\x00\x00\x00\x0c\xff\x60\x5b\xfc\x06\x00\x00" - b"\x00\x04\xff\x60\x20\xfd\x78\x00\x00\x00\x78\xff" - b"\x60\x00\x77\xfe\xaf\x82\xae\xe6\xff\x5f\x00\x00" - b"\x3c\x94\xa8\x7f\x1f\xfe\x56\x00\x20\x03\x00\x00" - b"\x00\x4c\xff\x29\x00\xcc\xd5\x87\x68\x8a\xf1\xab" - b"\x00\x00\x18\x7b\xb5\xc5\xa9\x60\x02\x00\x47\x3e" - b"\x00\x00\x00\x00\x00\x00\xb8\xa0\x00\x00\x00\x00" - b"\x00\x00\xb8\xa0\x00\x00\x00\x00\x00\x00\xb8\xa0" - b"\x18\x64\x73\x3f\x00\x00\xb8\xdf\xef\xc7\xd0\xff" - b"\x92\x00\xb8\xff\x36\x00\x00\x75\xff\x1f\xb8\xb6" - b"\x00\x00\x00\x16\xff\x49\xb8\xa0\x00\x00\x00\x08" - b"\xff\x50\xb8\xa0\x00\x00\x00\x08\xff\x50\xb8\xa0" - b"\x00\x00\x00\x08\xff\x50\xb8\xa0\x00\x00\x00\x08" - b"\xff\x50\x38\x73\x01\x93\xf3\x09\x02\x0e\x00\x4d" - b"\x43\x00\xb8\xa0\x00\xb8\xa0\x00\xb8\xa0\x00\xb8" - b"\xa0\x00\xb8\xa0\x00\xb8\xa0\x00\xb8\xa0\x00\x00" - b"\x00\x00\x38\x73\x01\x00\x00\x00\x93\xf3\x09\x00" - b"\x00\x00\x02\x0e\x00\x00\x00\x00\x46\x4a\x00\x00" - b"\x00\x00\xa8\xb0\x00\x00\x00\x00\xa8\xb0\x00\x00" - b"\x00\x00\xa8\xb0\x00\x00\x00\x00\xa8\xb0\x00\x00" - b"\x00\x00\xa8\xb0\x00\x00\x00\x00\xa8\xb0\x00\x00" - b"\x00\x00\xa8\xb0\x00\x00\x00\x00\xaf\xa7\x00\x05" - b"\x7c\x68\xf3\x6c\x00\x18\xaa\xc1\x7f\x04\x00\x47" - b"\x3e\x00\x00\x00\x00\x00\x00\xb8\xa0\x00\x00\x00" - b"\x00\x00\x00\xb8\xa0\x00\x00\x00\x00\x00\x00\xb8" - b"\xa0\x00\x00\x00\x3e\x69\x09\xb8\xa0\x00\x00\x5e" - b"\xf8\x5b\x00\xb8\xa0\x00\x72\xfa\x58\x00\x00\xb8" - b"\xa0\x87\xff\x75\x00\x00\x00\xb8\xff\xf2\xff\xce" - b"\x08\x00\x00\xb8\xff\x33\x24\xef\x9f\x00\x00\xb8" - b"\xa5\x00\x00\x48\xfd\x69\x00\xb8\xa0\x00\x00\x00" - b"\x77\xf9\x39\x47\x3e\xb8\xa0\xb8\xa0\xb8\xa0\xb8" - b"\xa0\xb8\xa0\xb8\xa0\xb8\xa0\xb8\xa0\xb8\xa0\xb8" - b"\xa0\x4d\x3c\x1c\x68\x6e\x2b\x00\x05\x50\x74\x57" - b"\x09\x00\xb8\xd1\xf0\xc4\xda\xfe\x77\xd4\xdf\xc3" - b"\xf6\xd2\x0b\xb8\xff\x31\x00\x01\xa2\xff\xa2\x02" - b"\x00\x2f\xfb\x66\xb8\xb4\x00\x00\x00\x49\xff\x30" - b"\x00\x00\x00\xce\x91\xb8\xa0\x00\x00\x00\x3c\xff" - b"\x1c\x00\x00\x00\xc0\x98\xb8\xa0\x00\x00\x00\x3c" - b"\xff\x1c\x00\x00\x00\xc0\x98\xb8\xa0\x00\x00\x00" - b"\x3c\xff\x1c\x00\x00\x00\xc0\x98\xb8\xa0\x00\x00" - b"\x00\x3c\xff\x1c\x00\x00\x00\xc0\x98\x4d\x3c\x18" - b"\x64\x73\x3f\x00\x00\xb8\xcf\xef\xc7\xd0\xff\x92" - b"\x00\xb8\xff\x36\x00\x00\x75\xff\x1f\xb8\xb6\x00" - b"\x00\x00\x16\xff\x49\xb8\xa0\x00\x00\x00\x08\xff" - b"\x50\xb8\xa0\x00\x00\x00\x08\xff\x50\xb8\xa0\x00" - b"\x00\x00\x08\xff\x50\xb8\xa0\x00\x00\x00\x08\xff" - b"\x50\x00\x00\x0e\x5a\x74\x54\x09\x00\x00\x00\x3f" - b"\xec\xe1\xb8\xe7\xe2\x2e\x00\x0a\xea\x9f\x03\x00" - b"\x08\xb4\xd7\x01\x4d\xff\x14\x00\x00\x00\x30\xff" - b"\x31\x5f\xfb\x01\x00\x00\x00\x18\xff\x42\x32\xff" - b"\x3c\x00\x00\x00\x57\xfc\x19\x00\xb4\xe0\x4a\x1f" - b"\x54\xea\x98\x00\x00\x0a\x93\xf2\xff\xec\x83\x05" - b"\x00\x00\x00\x00\x00\x0c\x00\x00\x00\x00\x4d\x3c" - b"\x16\x63\x71\x3f\x00\x00\xb8\xc9\xef\xc9\xbe\xfa" - b"\xb8\x08\xb8\xff\x4d\x00\x00\x29\xed\x86\xcc\xff" - b"\x00\x00\x00\x00\x86\xda\xd8\xff\x00\x00\x00\x00" - b"\x6d\xeb\xc0\xff\x05\x00\x00\x00\xae\xc1\xb8\xff" - b"\xad\x2f\x25\x89\xfe\x4a\xb8\xa9\x99\xf7\xff\xd9" - b"\x55\x00\xb8\xa0\x00\x03\x0a\x00\x00\x00\xb8\xa0" - b"\x00\x00\x00\x00\x00\x00\x84\x73\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x11\x5e\x73\x46\x03\x6c\x1e\x00" - b"\x45\xf0\xe1\xb8\xe8\xb5\xff\x48\x0c\xee\x9f\x03" - b"\x00\x08\xb8\xff\x48\x4f\xff\x14\x00\x00\x00\x30" - b"\xff\x48\x5f\xfb\x01\x00\x00\x00\x18\xff\x48\x36" - b"\xff\x3c\x00\x00\x00\x5e\xff\x48\x00\xbd\xe0\x4a" - b"\x1f\x56\xf5\xff\x48\x00\x0f\x9d\xf6\xff\xdc\x5b" - b"\xff\x48\x00\x00\x00\x01\x0c\x00\x10\xff\x48\x00" - b"\x00\x00\x00\x00\x00\x10\xff\x48\x00\x00\x00\x00" - b"\x00\x00\x0b\xb8\x33\x4d\x3c\x18\x64\x1f\xb8\xc3" - b"\xf1\xe0\x36\xb8\xff\x51\x00\x00\xb8\xc1\x00\x00" - b"\x00\xb8\xa1\x00\x00\x00\xb8\xa0\x00\x00\x00\xb8" - b"\xa0\x00\x00\x00\xb8\xa0\x00\x00\x00\x00\x08\x54" - b"\x75\x63\x32\x00\x12\xdb\xe0\xaf\xc3\xfb\x2a\x5f" - b"\xf9\x0a\x00\x00\x16\x00\x37\xff\x99\x48\x14\x00" - b"\x00\x00\x4f\xb4\xeb\xfe\xbc\x18\x00\x00\x00\x00" - b"\x29\xe6\x8b\x46\x8e\x3a\x1b\x3c\xeb\x77\x42\xc2" - b"\xf3\xff\xf3\x98\x08\x00\x00\x00\x0e\x02\x00\x00" - b"\x00\x06\x09\x00\x00\x00\x00\x88\xd0\x00\x00\x00" - b"\x56\xf4\xff\x6c\x65\x00\x8c\xff\xff\xb0\xa5\x00" - b"\x00\x88\xd0\x00\x00\x00\x00\x88\xd0\x00\x00\x00" - b"\x00\x88\xd0\x00\x00\x00\x00\x86\xd1\x00\x00\x00" - b"\x00\x67\xf4\x2e\x2b\x0c\x00\x0a\xb9\xfe\xf4\x3d" - b"\x00\x00\x00\x08\x02\x00\x56\x3b\x00\x00\x00\x10" - b"\x6c\x14\xcc\x8c\x00\x00\x00\x28\xff\x30\xcc\x8c" - b"\x00\x00\x00\x28\xff\x30\xcc\x8c\x00\x00\x00\x28" - b"\xff\x30\xcb\x8d\x00\x00\x00\x2c\xff\x30\xbb\xae" - b"\x00\x00\x00\x5c\xff\x30\x72\xfc\x69\x28\x52\xf2" - b"\xff\x30\x03\x91\xf4\xff\xdf\x62\xff\x30\x00\x00" - b"\x02\x0c\x00\x00\x00\x00\x65\x32\x00\x00\x00\x00" - b"\x3c\x51\xa1\xc4\x00\x00\x00\x02\xdd\x70\x31\xff" - b"\x32\x00\x00\x50\xf2\x0e\x00\xc1\x9f\x00\x00\xc1" - b"\x90\x00\x00\x51\xf8\x15\x31\xfd\x22\x00\x00\x02" - b"\xde\x7a\xa2\xb0\x00\x00\x00\x00\x71\xfc\xfe\x40" - b"\x00\x00\x00\x00\x0e\xff\xdd\x00\x00\x00\x5b\x2f" - b"\x00\x00\x00\x23\x67\x00\x00\x00\x00\x54\x2d\x94" - b"\xb1\x00\x00\x00\x99\xff\x3a\x00\x00\x12\xf9\x28" - b"\x35\xfa\x14\x00\x09\xf0\xe9\x99\x00\x00\x6b\xc8" - b"\x00\x00\xd5\x6c\x00\x5c\xdd\x47\xef\x08\x00\xca" - b"\x68\x00\x00\x76\xca\x00\xbd\x7b\x02\xe2\x56\x2a" - b"\xf8\x11\x00\x00\x1a\xfc\x47\xfa\x1b\x00\x83\xb5" - b"\x89\xaa\x00\x00\x00\x00\xb7\xff\xb4\x00\x00\x23" - b"\xfe\xfd\x4a\x00\x00\x00\x00\x58\xff\x51\x00\x00" - b"\x00\xcc\xff\x03\x00\x00\x35\x67\x03\x00\x00\x1b" - b"\x6c\x16\x12\xe1\x88\x00\x05\xc6\xa9\x00\x00\x36" - b"\xf8\x4c\x90\xd7\x0c\x00\x00\x00\x6d\xff\xf9\x2a" - b"\x00\x00\x00\x00\x27\xff\xe9\x06\x00\x00\x00\x0a" - b"\xd4\xa5\xd5\x95\x00\x00\x00\xa3\xce\x08\x2b\xf5" - b"\x5d\x00\x69\xef\x22\x00\x00\x60\xf5\x2e\x00\x65" - b"\x32\x00\x00\x00\x00\x3c\x51\x00\xa0\xc6\x00\x00" - b"\x00\x02\xdc\x6f\x00\x2e\xff\x35\x00\x00\x4e\xf0" - b"\x0d\x00\x00\xbd\xa5\x00\x00\xbd\x8c\x00\x00\x00" - b"\x4c\xfa\x19\x2c\xfb\x1f\x00\x00\x00\x01\xd8\x83" - b"\x9b\xaa\x00\x00\x00\x00\x00\x69\xfe\xfe\x38\x00" - b"\x00\x00\x00\x00\x0a\xff\xdb\x00\x00\x00\x00\x00" - b"\x00\x0f\xf8\x56\x00\x00\x00\x0d\xa7\x66\xbc\xd2" - b"\x03\x00\x00\x00\x0f\x8c\xc0\x99\x16\x00\x00\x00" - b"\x00\x26\x6c\x6c\x6c\x6c\x6c\x46\x3f\xb0\xb0\xb0" - b"\xbf\xff\x8a\x00\x00\x00\x02\xb5\xca\x08\x00\x00" - b"\x00\x86\xe8\x1c\x00\x00\x00\x54\xf9\x3c\x00\x00" - b"\x00\x2d\xf4\x67\x00\x00\x00\x12\xe7\xb5\x1c\x1c" - b"\x1c\x15\x6e\xff\xff\xff\xff\xff\xc8\x00\x00\x08" - b"\x4f\x40\x00\x00\xc1\xee\x77\x00\x0b\xff\x5d\x00" - b"\x00\x14\xff\x44\x00\x00\x14\xff\x44\x00\x00\x14" - b"\xff\x43\x00\x14\x8d\xfb\x22\x00\x25\xd3\xe2\x0f" - b"\x00\x00\x1b\xff\x3f\x00\x00\x14\xff\x44\x00\x00" - b"\x14\xff\x44\x00\x00\x11\xff\x4d\x00\x00\x00\xe7" - b"\xc1\x41\x00\x00\x36\xa4\x75\x35\x47\x88\xb8\x88" - b"\xb8\x88\xb8\x88\xb8\x88\xb8\x88\xb8\x88\xb8\x88" - b"\xb8\x88\xb8\x88\xb8\x88\xb8\x88\xb8\x61\x84\x49" - b"\x4b\x04\x00\x00\x8a\xf2\xaf\x00\x00\x00\x66\xf9" - b"\x01\x00\x00\x54\xff\x04\x00\x00\x54\xff\x04\x00" - b"\x00\x53\xff\x04\x00\x00\x32\xfe\x81\x0a\x00\x19" - b"\xec\xcd\x14\x00\x50\xff\x0a\x00\x00\x54\xff\x04" - b"\x00\x00\x54\xff\x04\x00\x00\x58\xfe\x02\x00\x4c" - b"\xc8\xd6\x00\x00\x86\x9f\x2c\x00\x00\x00\x00\x0d" - b"\x00\x00\x00\x02\x01\x00\x94\xfb\xd9\x3a\x00\xb5" - b"\x44\x15\xe6\x0c\x4f\xe8\xd1\xdb\x09\x08\x22\x00" - b"\x00\x0f\x3e\x0d\x00\x00\x11\x4d\x6f\x6e\x74\x73" - b"\x65\x72\x72\x61\x74\x20\x4d\x65\x64\x69\x75\x6d" - b"\x00\x11\x4d\x6f\x6e\x74\x73\x65\x72\x72\x61\x74" - b"\x2d\x4d\x65\x64\x69\x75\x6d\x01" -) diff --git a/m5stack/fs/system/common/font/MontserratMedium16.py b/m5stack/fs/system/common/font/MontserratMedium16.py deleted file mode 100644 index 7160a6d6..00000000 --- a/m5stack/fs/system/common/font/MontserratMedium16.py +++ /dev/null @@ -1,1031 +0,0 @@ -FONT = ( - b"\x00\x00\x00\x63\x00\x00\x00\x0b\x00\x00\x00\x10" - b"\x00\x00\x00\x00\x00\x00\x00\x0c\x00\x00\x00\x04" - b"\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x01" - b"\x00\x00\x00\x00\x00\x00\x00\x20\xff\xff\xff\xf0" - b"\x00\x00\x00\x00\x00\x00\x00\x09\x00\x00\x00\x01" - b"\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x20" - b"\xff\xff\xff\xf0\x00\x00\x00\x00\x00\x00\x00\x0a" - b"\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x00" - b"\x00\x00\x00\x20\xff\xff\xff\xf0\x00\x00\x00\x00" - b"\x00\x00\x00\x0d\x00\x00\x00\x01\x00\x00\x00\x01" - b"\x00\x00\x00\x00\x00\x00\x00\x20\xff\xff\xff\xf0" - b"\x00\x00\x00\x00\x00\x00\x00\x20\x00\x00\x00\x01" - b"\x00\x00\x00\x01\x00\x00\x00\x04\x00\x00\x00\x20" - b"\xff\xff\xff\xf0\x00\x00\x00\x00\x00\x00\x00\x21" - b"\x00\x00\x00\x0d\x00\x00\x00\x03\x00\x00\x00\x04" - b"\x00\x00\x00\x0c\x00\x00\x00\x01\x00\x00\x00\x00" - b"\x00\x00\x00\x22\x00\x00\x00\x06\x00\x00\x00\x05" - b"\x00\x00\x00\x06\x00\x00\x00\x0c\x00\x00\x00\x01" - b"\x00\x00\x00\x00\x00\x00\x00\x23\x00\x00\x00\x0c" - b"\x00\x00\x00\x0b\x00\x00\x00\x0b\x00\x00\x00\x0c" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x24" - b"\x00\x00\x00\x10\x00\x00\x00\x0a\x00\x00\x00\x0a" - b"\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x25\x00\x00\x00\x0d\x00\x00\x00\x0d" - b"\x00\x00\x00\x0d\x00\x00\x00\x0c\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x26\x00\x00\x00\x0d" - b"\x00\x00\x00\x0b\x00\x00\x00\x0b\x00\x00\x00\x0c" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x27" - b"\x00\x00\x00\x06\x00\x00\x00\x02\x00\x00\x00\x03" - b"\x00\x00\x00\x0c\x00\x00\x00\x01\x00\x00\x00\x00" - b"\x00\x00\x00\x28\x00\x00\x00\x10\x00\x00\x00\x04" - b"\x00\x00\x00\x05\x00\x00\x00\x0c\x00\x00\x00\x01" - b"\x00\x00\x00\x00\x00\x00\x00\x29\x00\x00\x00\x10" - b"\x00\x00\x00\x04\x00\x00\x00\x05\x00\x00\x00\x0c" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x2a" - b"\x00\x00\x00\x07\x00\x00\x00\x07\x00\x00\x00\x06" - b"\x00\x00\x00\x0c\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x2b\x00\x00\x00\x08\x00\x00\x00\x08" - b"\x00\x00\x00\x09\x00\x00\x00\x0a\x00\x00\x00\x01" - b"\x00\x00\x00\x00\x00\x00\x00\x2c\x00\x00\x00\x06" - b"\x00\x00\x00\x03\x00\x00\x00\x04\x00\x00\x00\x03" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x2d" - b"\x00\x00\x00\x03\x00\x00\x00\x06\x00\x00\x00\x06" - b"\x00\x00\x00\x06\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x2e\x00\x00\x00\x04\x00\x00\x00\x03" - b"\x00\x00\x00\x04\x00\x00\x00\x03\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x2f\x00\x00\x00\x10" - b"\x00\x00\x00\x08\x00\x00\x00\x06\x00\x00\x00\x0e" - b"\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x30" - b"\x00\x00\x00\x0d\x00\x00\x00\x0a\x00\x00\x00\x0b" - b"\x00\x00\x00\x0c\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x31\x00\x00\x00\x0c\x00\x00\x00\x05" - b"\x00\x00\x00\x06\x00\x00\x00\x0c\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x32\x00\x00\x00\x0c" - b"\x00\x00\x00\x09\x00\x00\x00\x09\x00\x00\x00\x0c" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x33" - b"\x00\x00\x00\x0d\x00\x00\x00\x09\x00\x00\x00\x09" - b"\x00\x00\x00\x0c\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x34\x00\x00\x00\x0c\x00\x00\x00\x0b" - b"\x00\x00\x00\x0b\x00\x00\x00\x0c\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x35\x00\x00\x00\x0d" - b"\x00\x00\x00\x09\x00\x00\x00\x09\x00\x00\x00\x0c" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x36" - b"\x00\x00\x00\x0d\x00\x00\x00\x0a\x00\x00\x00\x0a" - b"\x00\x00\x00\x0c\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x37\x00\x00\x00\x0c\x00\x00\x00\x09" - b"\x00\x00\x00\x0a\x00\x00\x00\x0c\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x38\x00\x00\x00\x0d" - b"\x00\x00\x00\x0a\x00\x00\x00\x0a\x00\x00\x00\x0c" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x39" - b"\x00\x00\x00\x0d\x00\x00\x00\x0a\x00\x00\x00\x0a" - b"\x00\x00\x00\x0c\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x3a\x00\x00\x00\x0a\x00\x00\x00\x03" - b"\x00\x00\x00\x04\x00\x00\x00\x09\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x3b\x00\x00\x00\x0c" - b"\x00\x00\x00\x03\x00\x00\x00\x04\x00\x00\x00\x09" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x3c" - b"\x00\x00\x00\x08\x00\x00\x00\x08\x00\x00\x00\x09" - b"\x00\x00\x00\x0a\x00\x00\x00\x01\x00\x00\x00\x00" - b"\x00\x00\x00\x3d\x00\x00\x00\x06\x00\x00\x00\x08" - b"\x00\x00\x00\x09\x00\x00\x00\x09\x00\x00\x00\x01" - b"\x00\x00\x00\x00\x00\x00\x00\x3e\x00\x00\x00\x08" - b"\x00\x00\x00\x08\x00\x00\x00\x09\x00\x00\x00\x0a" - b"\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x3f" - b"\x00\x00\x00\x0d\x00\x00\x00\x09\x00\x00\x00\x09" - b"\x00\x00\x00\x0c\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x40\x00\x00\x00\x10\x00\x00\x00\x10" - b"\x00\x00\x00\x11\x00\x00\x00\x0c\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x41\x00\x00\x00\x0c" - b"\x00\x00\x00\x0c\x00\x00\x00\x0c\x00\x00\x00\x0c" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x42" - b"\x00\x00\x00\x0c\x00\x00\x00\x0b\x00\x00\x00\x0c" - b"\x00\x00\x00\x0c\x00\x00\x00\x01\x00\x00\x00\x00" - b"\x00\x00\x00\x43\x00\x00\x00\x0d\x00\x00\x00\x0b" - b"\x00\x00\x00\x0c\x00\x00\x00\x0c\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x44\x00\x00\x00\x0c" - b"\x00\x00\x00\x0c\x00\x00\x00\x0d\x00\x00\x00\x0c" - b"\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x45" - b"\x00\x00\x00\x0c\x00\x00\x00\x09\x00\x00\x00\x0b" - b"\x00\x00\x00\x0c\x00\x00\x00\x01\x00\x00\x00\x00" - b"\x00\x00\x00\x46\x00\x00\x00\x0c\x00\x00\x00\x09" - b"\x00\x00\x00\x0a\x00\x00\x00\x0c\x00\x00\x00\x01" - b"\x00\x00\x00\x00\x00\x00\x00\x47\x00\x00\x00\x0d" - b"\x00\x00\x00\x0c\x00\x00\x00\x0c\x00\x00\x00\x0c" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x48" - b"\x00\x00\x00\x0c\x00\x00\x00\x0b\x00\x00\x00\x0d" - b"\x00\x00\x00\x0c\x00\x00\x00\x01\x00\x00\x00\x00" - b"\x00\x00\x00\x49\x00\x00\x00\x0c\x00\x00\x00\x03" - b"\x00\x00\x00\x05\x00\x00\x00\x0c\x00\x00\x00\x01" - b"\x00\x00\x00\x00\x00\x00\x00\x4a\x00\x00\x00\x0d" - b"\x00\x00\x00\x08\x00\x00\x00\x08\x00\x00\x00\x0c" - b"\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x4b" - b"\x00\x00\x00\x0c\x00\x00\x00\x0b\x00\x00\x00\x0c" - b"\x00\x00\x00\x0c\x00\x00\x00\x01\x00\x00\x00\x00" - b"\x00\x00\x00\x4c\x00\x00\x00\x0c\x00\x00\x00\x09" - b"\x00\x00\x00\x0a\x00\x00\x00\x0c\x00\x00\x00\x01" - b"\x00\x00\x00\x00\x00\x00\x00\x4d\x00\x00\x00\x0c" - b"\x00\x00\x00\x0d\x00\x00\x00\x0f\x00\x00\x00\x0c" - b"\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x4e" - b"\x00\x00\x00\x0c\x00\x00\x00\x0b\x00\x00\x00\x0d" - b"\x00\x00\x00\x0c\x00\x00\x00\x01\x00\x00\x00\x00" - b"\x00\x00\x00\x4f\x00\x00\x00\x0d\x00\x00\x00\x0d" - b"\x00\x00\x00\x0d\x00\x00\x00\x0c\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x50\x00\x00\x00\x0c" - b"\x00\x00\x00\x0a\x00\x00\x00\x0c\x00\x00\x00\x0c" - b"\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x51" - b"\x00\x00\x00\x0f\x00\x00\x00\x0e\x00\x00\x00\x0d" - b"\x00\x00\x00\x0c\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x52\x00\x00\x00\x0c\x00\x00\x00\x0a" - b"\x00\x00\x00\x0c\x00\x00\x00\x0c\x00\x00\x00\x01" - b"\x00\x00\x00\x00\x00\x00\x00\x53\x00\x00\x00\x0d" - b"\x00\x00\x00\x0a\x00\x00\x00\x0a\x00\x00\x00\x0c" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x54" - b"\x00\x00\x00\x0c\x00\x00\x00\x0a\x00\x00\x00\x09" - b"\x00\x00\x00\x0c\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x55\x00\x00\x00\x0d\x00\x00\x00\x0b" - b"\x00\x00\x00\x0d\x00\x00\x00\x0c\x00\x00\x00\x01" - b"\x00\x00\x00\x00\x00\x00\x00\x56\x00\x00\x00\x0c" - b"\x00\x00\x00\x0c\x00\x00\x00\x0b\x00\x00\x00\x0c" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x57" - b"\x00\x00\x00\x0c\x00\x00\x00\x12\x00\x00\x00\x12" - b"\x00\x00\x00\x0c\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x58\x00\x00\x00\x0c\x00\x00\x00\x0b" - b"\x00\x00\x00\x0b\x00\x00\x00\x0c\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x59\x00\x00\x00\x0c" - b"\x00\x00\x00\x0b\x00\x00\x00\x0a\x00\x00\x00\x0c" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x5a" - b"\x00\x00\x00\x0c\x00\x00\x00\x0b\x00\x00\x00\x0b" - b"\x00\x00\x00\x0c\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x5b\x00\x00\x00\x10\x00\x00\x00\x05" - b"\x00\x00\x00\x05\x00\x00\x00\x0c\x00\x00\x00\x01" - b"\x00\x00\x00\x00\x00\x00\x00\x5c\x00\x00\x00\x10" - b"\x00\x00\x00\x08\x00\x00\x00\x06\x00\x00\x00\x0e" - b"\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x5d" - b"\x00\x00\x00\x10\x00\x00\x00\x04\x00\x00\x00\x05" - b"\x00\x00\x00\x0c\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x5e\x00\x00\x00\x07\x00\x00\x00\x08" - b"\x00\x00\x00\x09\x00\x00\x00\x09\x00\x00\x00\x01" - b"\x00\x00\x00\x00\x00\x00\x00\x5f\x00\x00\x00\x01" - b"\x00\x00\x00\x08\x00\x00\x00\x08\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x60" - b"\x00\x00\x00\x03\x00\x00\x00\x05\x00\x00\x00\x0a" - b"\x00\x00\x00\x0c\x00\x00\x00\x01\x00\x00\x00\x00" - b"\x00\x00\x00\x61\x00\x00\x00\x0a\x00\x00\x00\x09" - b"\x00\x00\x00\x0a\x00\x00\x00\x09\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x62\x00\x00\x00\x0d" - b"\x00\x00\x00\x0a\x00\x00\x00\x0b\x00\x00\x00\x0c" - b"\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x63" - b"\x00\x00\x00\x0a\x00\x00\x00\x09\x00\x00\x00\x09" - b"\x00\x00\x00\x09\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x64\x00\x00\x00\x0d\x00\x00\x00\x0a" - b"\x00\x00\x00\x0b\x00\x00\x00\x0c\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x65\x00\x00\x00\x0a" - b"\x00\x00\x00\x0a\x00\x00\x00\x0a\x00\x00\x00\x09" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x66" - b"\x00\x00\x00\x0c\x00\x00\x00\x07\x00\x00\x00\x06" - b"\x00\x00\x00\x0c\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x67\x00\x00\x00\x0d\x00\x00\x00\x0a" - b"\x00\x00\x00\x0b\x00\x00\x00\x09\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x68\x00\x00\x00\x0c" - b"\x00\x00\x00\x09\x00\x00\x00\x0b\x00\x00\x00\x0c" - b"\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x69" - b"\x00\x00\x00\x0d\x00\x00\x00\x03\x00\x00\x00\x04" - b"\x00\x00\x00\x0d\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x6a\x00\x00\x00\x11\x00\x00\x00\x06" - b"\x00\x00\x00\x05\x00\x00\x00\x0d\xff\xff\xff\xfe" - b"\x00\x00\x00\x00\x00\x00\x00\x6b\x00\x00\x00\x0c" - b"\x00\x00\x00\x09\x00\x00\x00\x0a\x00\x00\x00\x0c" - b"\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x6c" - b"\x00\x00\x00\x0c\x00\x00\x00\x02\x00\x00\x00\x04" - b"\x00\x00\x00\x0c\x00\x00\x00\x01\x00\x00\x00\x00" - b"\x00\x00\x00\x6d\x00\x00\x00\x09\x00\x00\x00\x0f" - b"\x00\x00\x00\x11\x00\x00\x00\x09\x00\x00\x00\x01" - b"\x00\x00\x00\x00\x00\x00\x00\x6e\x00\x00\x00\x09" - b"\x00\x00\x00\x09\x00\x00\x00\x0b\x00\x00\x00\x09" - b"\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x6f" - b"\x00\x00\x00\x0a\x00\x00\x00\x0a\x00\x00\x00\x0a" - b"\x00\x00\x00\x09\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x70\x00\x00\x00\x0d\x00\x00\x00\x0a" - b"\x00\x00\x00\x0b\x00\x00\x00\x09\x00\x00\x00\x01" - b"\x00\x00\x00\x00\x00\x00\x00\x71\x00\x00\x00\x0d" - b"\x00\x00\x00\x0a\x00\x00\x00\x0b\x00\x00\x00\x09" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x72" - b"\x00\x00\x00\x09\x00\x00\x00\x06\x00\x00\x00\x07" - b"\x00\x00\x00\x09\x00\x00\x00\x01\x00\x00\x00\x00" - b"\x00\x00\x00\x73\x00\x00\x00\x0a\x00\x00\x00\x08" - b"\x00\x00\x00\x08\x00\x00\x00\x09\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x74\x00\x00\x00\x0c" - b"\x00\x00\x00\x07\x00\x00\x00\x07\x00\x00\x00\x0b" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x75" - b"\x00\x00\x00\x0a\x00\x00\x00\x09\x00\x00\x00\x0b" - b"\x00\x00\x00\x09\x00\x00\x00\x01\x00\x00\x00\x00" - b"\x00\x00\x00\x76\x00\x00\x00\x09\x00\x00\x00\x09" - b"\x00\x00\x00\x09\x00\x00\x00\x09\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x77\x00\x00\x00\x09" - b"\x00\x00\x00\x0f\x00\x00\x00\x0e\x00\x00\x00\x09" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x78" - b"\x00\x00\x00\x09\x00\x00\x00\x09\x00\x00\x00\x09" - b"\x00\x00\x00\x09\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x79\x00\x00\x00\x0d\x00\x00\x00\x0a" - b"\x00\x00\x00\x09\x00\x00\x00\x09\xff\xff\xff\xff" - b"\x00\x00\x00\x00\x00\x00\x00\x7a\x00\x00\x00\x09" - b"\x00\x00\x00\x08\x00\x00\x00\x08\x00\x00\x00\x09" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x7b" - b"\x00\x00\x00\x10\x00\x00\x00\x06\x00\x00\x00\x06" - b"\x00\x00\x00\x0c\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x7c\x00\x00\x00\x10\x00\x00\x00\x03" - b"\x00\x00\x00\x05\x00\x00\x00\x0c\x00\x00\x00\x01" - b"\x00\x00\x00\x00\x00\x00\x00\x7d\x00\x00\x00\x10" - b"\x00\x00\x00\x05\x00\x00\x00\x06\x00\x00\x00\x0c" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x7e" - b"\x00\x00\x00\x03\x00\x00\x00\x09\x00\x00\x00\x09" - b"\x00\x00\x00\x07\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x26\x34\x02\xba\xff\x08\xb0" - b"\xfd\x00\xa7\xf3\x00\x9d\xe9\x00\x94\xde\x00\x8b" - b"\xd4\x00\x81\xc9\x00\x39\x5a\x00\x00\x00\x00\x9b" - b"\xd5\x0e\xb8\xf0\x14\x02\x09\x00\x33\x12\x00\x33" - b"\x12\xf8\x56\x00\xf8\x56\xf3\x4f\x00\xf3\x4f\xed" - b"\x47\x00\xed\x47\xe8\x40\x00\xe8\x40\x1c\x07\x00" - b"\x1c\x07\x00\x00\x00\x10\x29\x00\x00\x09\x30\x00" - b"\x00\x00\x00\x00\x67\xb9\x00\x00\x42\xdd\x00\x00" - b"\x00\x00\x00\x87\x99\x00\x00\x62\xbe\x00\x00\x05" - b"\x50\x50\xf7\xc9\x50\x50\xd1\xee\x50\x41\x0d\xd8" - b"\xd8\xff\xff\xd8\xd8\xff\xff\xd8\xaf\x00\x00\x00" - b"\xe8\x3a\x00\x00\xc0\x5f\x00\x00\x00\x00\x08\xfe" - b"\x1a\x00\x00\xdf\x40\x00\x00\x06\x0c\x34\xff\x0d" - b"\x0c\x0f\xff\x2d\x0c\x03\x90\xff\xff\xff\xff\xff" - b"\xff\xff\xff\xff\x4c\x12\x20\x88\xdb\x20\x20\x5e" - b"\xff\x20\x20\x09\x00\x00\x88\x9b\x00\x00\x5d\xc2" - b"\x00\x00\x00\x00\x00\xa8\x7b\x00\x00\x7c\xa3\x00" - b"\x00\x00\x00\x00\x00\x00\x0e\x12\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x74\x90\x00\x00\x00\x00\x00\x00" - b"\x00\x1b\xb8\xdd\x31\x04\x00\x00\x00\x13\xb3\xfe" - b"\xff\xff\xff\xec\x6e\x00\x00\xb4\xf5\x62\x8c\xa3" - b"\x38\x96\x6d\x00\x08\xfe\x92\x00\x74\x90\x00\x00" - b"\x00\x00\x04\xf9\xbb\x05\x74\x90\x00\x00\x00\x00" - b"\x00\x83\xff\xdc\xf4\xc9\x04\x00\x00\x00\x00\x00" - b"\x56\xc0\xff\xff\xef\x91\x0f\x00\x00\x00\x00\x00" - b"\x81\xde\xa6\xfe\xce\x04\x00\x00\x00\x00\x74\x90" - b"\x00\x77\xff\x39\x00\x44\x00\x00\x74\x90\x00\x66" - b"\xff\x31\x2a\xff\xc5\x6d\xb9\xd5\x7a\xf1\xc6\x00" - b"\x00\x47\xb7\xf6\xff\xff\xef\x99\x12\x00\x00\x00" - b"\x00\x03\x8b\xa5\x02\x00\x00\x00\x00\x00\x00\x00" - b"\x6a\x84\x00\x00\x00\x00\x00\x00\x29\x40\x07\x00" - b"\x00\x00\x00\x06\x34\x06\x00\x00\x92\xd5\xb1\xdb" - b"\x1b\x00\x00\x00\x87\xb4\x00\x00\x2f\xe2\x08\x00" - b"\x86\x93\x00\x00\x39\xe9\x18\x00\x00\x60\xa9\x00" - b"\x00\x45\xc4\x00\x0a\xda\x57\x00\x00\x00\x51\xbb" - b"\x00\x00\x57\xb5\x00\x94\xa7\x00\x00\x00\x00\x0e" - b"\xe6\x59\x2c\xcd\x5c\x44\xe5\x12\x00\x00\x00\x00" - b"\x00\x2e\xaf\xc4\x6c\x0f\xe2\x4a\x43\xda\xec\xac" - b"\x0d\x00\x00\x00\x00\x00\xa1\x9a\x0a\xeb\x36\x01" - b"\x9e\x8d\x00\x00\x00\x00\x50\xde\x0d\x3e\xc9\x00" - b"\x00\x35\xd5\x00\x00\x00\x15\xe7\x3f\x00\x41\xc5" - b"\x00\x00\x31\xd8\x00\x00\x00\xad\x8d\x00\x00\x0e" - b"\xed\x27\x00\x8e\x96\x00\x00\x5d\xd6\x08\x00\x00" - b"\x00\x54\xe5\xe0\xc2\x13\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x03\x0e\x00\x00\x00\x00\x00\x07\x3b" - b"\x42\x10\x00\x00\x00\x00\x00\x00\x36\xe8\xee\xe4" - b"\xf4\x4d\x00\x00\x00\x00\x00\xc4\xc0\x06\x00\x9a" - b"\xd1\x00\x00\x00\x00\x00\xda\x97\x00\x00\x8b\xd5" - b"\x00\x00\x00\x00\x00\x86\xf3\x2d\x6c\xf9\x5f\x00" - b"\x00\x00\x00\x00\x07\xff\xff\xff\x54\x00\x00\x00" - b"\x00\x00\x08\x9f\xff\xff\xf3\x27\x00\x05\x23\x00" - b"\x00\xb1\xe3\x2b\x0e\xc7\xe6\x27\x41\xfe\x14\x2b" - b"\xff\x5e\x00\x00\x0d\xc5\xe7\xce\xc6\x00\x3b\xff" - b"\x53\x00\x00\x00\x0d\xff\xff\x70\x00\x06\xd9\xeb" - b"\x67\x33\x3f\x90\xff\xff\xe8\x2a\x00\x1c\xad\xf8" - b"\xff\xff\xcb\x51\x0c\xc1\x6b\x00\x00\x00\x06\x1a" - b"\x08\x00\x00\x00\x07\x00\x33\x12\xf8\x56\xf3\x4f" - b"\xed\x47\xe8\x40\x1c\x07\x00\x00\xb3\x95\x00\x48" - b"\xff\x38\x00\xa8\xd7\x00\x04\xf3\x87\x00\x2d\xff" - b"\x53\x00\x58\xff\x29\x00\x6b\xff\x17\x00\x78\xff" - b"\x0b\x00\x6f\xff\x14\x00\x5f\xff\x23\x00\x38\xff" - b"\x49\x00\x0a\xfb\x78\x00\x00\xbb\xc4\x00\x00\x5f" - b"\xfd\x20\x00\x05\xde\x95\x00\x00\x11\x17\x3d\xdf" - b"\x2d\x00\x01\xd1\xb0\x00\x00\x71\xfc\x14\x00\x20" - b"\xff\x60\x00\x00\xea\x94\x00\x00\xbf\xc0\x00\x00" - b"\xac\xd2\x00\x00\x9f\xdf\x00\x00\xa9\xd6\x00\x00" - b"\xb8\xc6\x00\x00\xe0\x9f\x00\x11\xff\x6d\x00\x5e" - b"\xff\x23\x00\xb7\xc8\x00\x31\xff\x49\x00\x0c\x1b" - b"\x00\x00\x00\x00\x3e\x98\x00\x00\x00\x5c\x6d\x49" - b"\xab\x34\x97\x00\x2b\xb7\xf1\xf3\xe3\x62\x01\x0a" - b"\x7e\xff\xff\xc4\x2c\x00\x7b\xa7\x62\xad\x6d\xcb" - b"\x02\x01\x00\x47\xad\x00\x01\x00\x00\x00\x06\x10" - b"\x00\x00\x00\x00\x00\x00\x1c\x09\x00\x00\x00\x00" - b"\x00\x04\xff\x54\x00\x00\x00\x00\x00\x04\xff\x54" - b"\x00\x00\x00\x33\x38\x3c\xff\x8c\x38\x38\x0d\xec" - b"\xff\xff\xff\xff\xff\xff\x3c\x0b\x0c\x10\xff\x60" - b"\x0c\x0c\x02\x00\x00\x04\xff\x54\x00\x00\x00\x00" - b"\x00\x03\xe8\x4c\x00\x00\x00\x00\x09\x02\x1a\xf1" - b"\xb2\x1a\xff\xc9\x00\xb5\x7c\x02\xee\x27\x0a\x58" - b"\x00\x01\x10\x10\x10\x10\x03\x18\xff\xff\xff\xff" - b"\x38\x06\x44\x44\x44\x44\x0e\x00\x09\x02\x20\xf3" - b"\xb0\x1f\xf3\xaf\x00\x09\x02\x00\x00\x00\x00\x00" - b"\x1e\x78\x10\x00\x00\x00\x00\x00\x83\xdf\x01\x00" - b"\x00\x00\x00\x00\xdc\x87\x00\x00\x00\x00\x00\x37" - b"\xff\x2d\x00\x00\x00\x00\x00\x91\xd3\x00\x00\x00" - b"\x00\x00\x02\xe7\x79\x00\x00\x00\x00\x00\x44\xff" - b"\x21\x00\x00\x00\x00\x00\x9e\xc6\x00\x00\x00\x00" - b"\x00\x07\xf0\x6c\x00\x00\x00\x00\x00\x51\xfc\x16" - b"\x00\x00\x00\x00\x00\xab\xb9\x00\x00\x00\x00\x00" - b"\x0d\xf6\x5f\x00\x00\x00\x00\x00\x5f\xf7\x0e\x00" - b"\x00\x00\x00\x00\xb8\xab\x00\x00\x00\x00\x00\x15" - b"\xfb\x52\x00\x00\x00\x00\x00\x35\x96\x07\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x03\x36\x4a\x24\x00\x00" - b"\x00\x00\x00\x3d\xdf\xff\xff\xff\xb5\x13\x00\x00" - b"\x29\xf7\xd0\x44\x22\x65\xf5\xca\x03\x00\xb0\xf2" - b"\x16\x00\x00\x00\x5d\xff\x5c\x0a\xf8\x9f\x00\x00" - b"\x00\x00\x03\xf0\xae\x28\xff\x70\x00\x00\x00\x00" - b"\x00\xc5\xd3\x36\xff\x64\x00\x00\x00\x00\x00\xb8" - b"\xe1\x23\xff\x75\x00\x00\x00\x00\x00\xca\xce\x04" - b"\xef\xad\x00\x00\x00\x00\x09\xf7\xa0\x00\x9a\xfb" - b"\x2d\x00\x00\x00\x7c\xff\x46\x00\x15\xe4\xea\x76" - b"\x55\x96\xfe\xa6\x00\x00\x00\x1d\xaf\xfc\xff\xee" - b"\x82\x03\x00\x00\x00\x00\x00\x06\x16\x01\x00\x00" - b"\x00\x2d\x34\x34\x34\x0c\xe0\xff\xff\xff\x3c\x2a" - b"\x30\x88\xff\x3c\x00\x00\x58\xff\x3c\x00\x00\x58" - b"\xff\x3c\x00\x00\x58\xff\x3c\x00\x00\x58\xff\x3c" - b"\x00\x00\x58\xff\x3c\x00\x00\x58\xff\x3c\x00\x00" - b"\x58\xff\x3c\x00\x00\x58\xff\x3c\x00\x00\x58\xff" - b"\x3c\x00\x00\x11\x40\x4d\x2f\x01\x00\x00\x13\xa5" - b"\xfc\xff\xff\xff\xd6\x2c\x00\x7b\xf1\x74\x29\x23" - b"\x60\xf4\xd8\x01\x00\x1b\x00\x00\x00\x00\x89\xff" - b"\x1c\x00\x00\x00\x00\x00\x00\x89\xff\x10\x00\x00" - b"\x00\x00\x00\x11\xe8\xbc\x00\x00\x00\x00\x00\x0e" - b"\xc7\xeb\x22\x00\x00\x00\x00\x12\xcb\xec\x31\x00" - b"\x00\x00\x00\x16\xd1\xe8\x2c\x00\x00\x00\x00\x1a" - b"\xd7\xe4\x27\x00\x00\x00\x00\x1e\xe0\xff\x86\x64" - b"\x64\x64\x64\x40\x68\xff\xff\xff\xff\xff\xff\xff" - b"\xa4\x15\x34\x34\x34\x34\x34\x34\x33\x00\x68\xff" - b"\xff\xff\xff\xff\xff\xfa\x00\x13\x30\x30\x30\x30" - b"\x79\xff\x7d\x00\x00\x00\x00\x00\x26\xf0\xac\x01" - b"\x00\x00\x00\x00\x0e\xd7\xd1\x0b\x00\x00\x00\x00" - b"\x00\xab\xff\x8e\x28\x00\x00\x00\x00\x00\xa2\xd0" - b"\xf4\xfe\x7c\x00\x00\x00\x00\x00\x00\x0f\xb2\xff" - b"\x2f\x00\x00\x00\x00\x00\x00\x3c\xff\x68\x27\x15" - b"\x00\x00\x00\x00\x68\xff\x4f\xb6\xef\x91\x5b\x56" - b"\x8a\xf6\xd9\x07\x1e\x98\xe8\xff\xff\xf7\xa9\x1b" - b"\x00\x00\x00\x00\x12\x18\x04\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x26\x32\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x3a\xfa\x84\x00\x00\x00\x00\x00\x00\x00" - b"\x17\xe5\xbb\x02\x00\x00\x00\x00\x00\x00\x04\xc1" - b"\xe3\x15\x00\x00\x00\x00\x00\x00\x00\x8e\xf9\x38" - b"\x00\x00\x00\x00\x00\x00\x00\x56\xff\x6c\x00\x12" - b"\x90\x38\x00\x00\x00\x2a\xf3\xa5\x00\x00\x20\xff" - b"\x64\x00\x00\x0d\xe0\xf4\x2b\x20\x20\x40\xff\x84" - b"\x20\x10\x62\xff\xff\xff\xff\xff\xff\xff\xff\xff" - b"\x84\x19\x40\x40\x40\x40\x40\x6b\xff\xa4\x40\x21" - b"\x00\x00\x00\x00\x00\x00\x2c\xff\x64\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x2c\xff\x64\x00\x00\x00\x10" - b"\x34\x34\x34\x34\x34\x34\x00\x00\x63\xff\xff\xff" - b"\xff\xff\xff\x00\x00\x7d\xff\x32\x30\x30\x30\x30" - b"\x00\x00\x97\xe5\x00\x00\x00\x00\x00\x00\x00\xb1" - b"\xcb\x00\x00\x00\x00\x00\x00\x00\xce\xff\xbb\xb1" - b"\x98\x53\x02\x00\x00\x92\xa8\xa9\xb5\xe1\xff\xc1" - b"\x08\x00\x00\x00\x00\x00\x01\x76\xff\x67\x00\x00" - b"\x00\x00\x00\x00\x05\xfe\x97\x11\x2b\x00\x00\x00" - b"\x00\x36\xff\x7c\x85\xf9\xa2\x61\x53\x7a\xe9\xee" - b"\x1b\x0e\x83\xdd\xff\xff\xfc\xbc\x2e\x00\x00\x00" - b"\x00\x0d\x1a\x07\x00\x00\x00\x00\x00\x00\x00\x1a" - b"\x43\x48\x27\x00\x00\x00\x00\x1c\xb5\xfe\xff\xff" - b"\xff\x98\x00\x00\x14\xe5\xe5\x5e\x1a\x16\x46\x34" - b"\x00\x00\x9a\xf5\x25\x00\x00\x00\x00\x00\x00\x05" - b"\xf0\xa0\x00\x00\x00\x00\x00\x00\x00\x23\xff\x6d" - b"\x3d\x9f\xc2\xb0\x5f\x02\x00\x36\xff\xd5\xf8\xa2" - b"\x89\xbc\xff\xaa\x00\x28\xff\xff\x3c\x00\x00\x00" - b"\x7f\xff\x3b\x09\xf7\xca\x00\x00\x00\x00\x28\xff" - b"\x67\x00\xae\xeb\x0b\x00\x00\x00\x53\xff\x4a\x00" - b"\x24\xf0\xc6\x4d\x31\x66\xf3\xd1\x03\x00\x00\x2a" - b"\xb7\xfd\xff\xf9\xa8\x17\x00\x00\x00\x00\x00\x05" - b"\x19\x06\x00\x00\x00\x1a\x34\x34\x34\x34\x34\x34" - b"\x34\x2f\x84\xff\xff\xff\xff\xff\xff\xff\xeb\x84" - b"\xff\x34\x30\x30\x30\x3f\xff\xa6\x84\xff\x04\x00" - b"\x00\x00\x77\xff\x34\x18\x30\x00\x00\x00\x05\xe4" - b"\xc1\x00\x00\x00\x00\x00\x00\x5e\xff\x4f\x00\x00" - b"\x00\x00\x00\x00\xd0\xda\x02\x00\x00\x00\x00\x00" - b"\x45\xff\x6a\x00\x00\x00\x00\x00\x00\xb8\xed\x0a" - b"\x00\x00\x00\x00\x00\x2c\xff\x85\x00\x00\x00\x00" - b"\x00\x00\x9f\xf9\x19\x00\x00\x00\x00\x00\x18\xf9" - b"\xa1\x00\x00\x00\x00\x00\x00\x00\x18\x46\x4b\x29" - b"\x00\x00\x00\x00\x0b\xa8\xfe\xff\xfe\xff\xd0\x2b" - b"\x00\x00\x96\xfd\x6c\x0c\x05\x41\xe5\xdf\x04\x00" - b"\xd9\xc2\x00\x00\x00\x00\x73\xff\x28\x00\xbe\xe0" - b"\x09\x00\x00\x00\x9c\xfa\x12\x00\x37\xee\xec\xbb" - b"\xb7\xda\xfe\x74\x00\x00\x2d\xd8\xff\xff\xff\xff" - b"\xff\x59\x00\x0a\xe7\xd4\x22\x00\x00\x0d\x9f\xfe" - b"\x3c\x3f\xff\x5e\x00\x00\x00\x00\x13\xff\x8a\x31" - b"\xff\x7f\x00\x00\x00\x00\x35\xff\x7c\x00\xc1\xfa" - b"\x83\x3e\x37\x66\xe2\xef\x1d\x00\x0e\x92\xec\xff" - b"\xff\xf7\xb3\x2b\x00\x00\x00\x00\x01\x14\x18\x05" - b"\x00\x00\x00\x00\x00\x03\x38\x4c\x31\x01\x00\x00" - b"\x00\x00\x46\xe4\xff\xfd\xff\xdc\x3c\x00\x00\x1f" - b"\xf6\xcc\x27\x01\x23\xb3\xf7\x24\x00\x73\xff\x1f" - b"\x00\x00\x00\x0b\xf4\xa1\x00\x81\xff\x12\x00\x00" - b"\x00\x01\xef\xec\x00\x48\xff\x8d\x03\x00\x00\x77" - b"\xff\xff\x0b\x00\x99\xff\xe2\xbb\xdc\xe9\xba\xff" - b"\x14\x00\x00\x3d\x83\x8d\x68\x10\x95\xfb\x01\x00" - b"\x00\x00\x00\x00\x00\x00\xd3\xc5\x00\x00\x00\x00" - b"\x00\x00\x00\x66\xff\x60\x00\x00\x68\x6f\x47\x52" - b"\xa0\xfe\xad\x00\x00\x00\x96\xf7\xff\xff\xdd\x73" - b"\x02\x00\x00\x00\x00\x02\x17\x0c\x00\x00\x00\x00" - b"\x00\x00\x09\x02\x20\xf3\xb0\x1f\xf3\xaf\x00\x09" - b"\x02\x00\x00\x00\x00\x00\x00\x00\x09\x02\x20\xf3" - b"\xb0\x1f\xf3\xaf\x00\x09\x02\x00\x09\x02\x20\xf3" - b"\xb0\x1f\xf3\xaf\x00\x09\x02\x00\x00\x00\x00\x00" - b"\x00\x00\x09\x02\x1a\xf1\xb2\x1a\xff\xc9\x00\xb5" - b"\x7c\x02\xee\x27\x0a\x58\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x04\x00\x00\x00\x00\x11\x6f\xd4\x3c\x00" - b"\x00\x3e\xa3\xf7\xdd\x7d\x0f\x6d\xd7\xf7\xa5\x42" - b"\x01\x00\x00\xff\xff\x4c\x00\x00\x00\x00\x00\x41" - b"\xa8\xf9\xd8\x75\x17\x00\x00\x00\x00\x14\x72\xd7" - b"\xfb\xb0\x1b\x00\x00\x00\x00\x00\x3d\xa2\x34\x1d" - b"\x20\x20\x20\x20\x20\x20\x07\xec\xff\xff\xff\xff" - b"\xff\xff\x3c\x21\x24\x24\x24\x24\x24\x24\x08\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x51\x58\x58\x58\x58" - b"\x58\x58\x14\xd9\xec\xec\xec\xec\xec\xec\x37\x05" - b"\x00\x00\x00\x00\x00\x00\x00\xd9\x8f\x29\x00\x00" - b"\x00\x00\x00\x59\xc0\xfe\xc3\x5e\x09\x00\x00\x00" - b"\x00\x23\x85\xe4\xee\x92\x13\x00\x00\x00\x00\x21" - b"\xc9\xff\x3c\x00\x07\x56\xb8\xfd\xc8\x62\x08\x89" - b"\xec\xee\x92\x2d\x00\x00\x00\xae\x5c\x08\x00\x00" - b"\x00\x00\x00\x00\x00\x13\x41\x4d\x30\x01\x00\x00" - b"\x13\xa6\xfc\xff\xff\xff\xd8\x2f\x00\x8d\xed\x67" - b"\x1a\x13\x58\xf5\xd8\x00\x01\x1a\x00\x00\x00\x00" - b"\x9a\xff\x09\x00\x00\x00\x00\x00\x00\xc2\xde\x00" - b"\x00\x00\x00\x00\x01\x8f\xfe\x53\x00\x00\x00\x00" - b"\x00\x8b\xfc\x5e\x00\x00\x00\x00\x00\x27\xfe\x8b" - b"\x00\x00\x00\x00\x00\x00\x25\x78\x24\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x54\xe6\x41\x00\x00\x00\x00\x00\x00\x68\xff\x52" - b"\x00\x00\x00\x00\x00\x00\x00\x0b\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x1d\x3e\x49\x36\x0b\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x04\x68\xd7\xf5\xd2" - b"\xc5\xda\xf7\xb3\x36\x00\x00\x00\x00\x00\x13\xc1" - b"\xd4\x52\x07\x00\x00\x00\x13\x74\xed\x77\x00\x00" - b"\x00\x04\xc1\xb0\x09\x00\x00\x0a\x09\x00\x00\x08" - b"\x24\xe2\x68\x00\x00\x68\xdc\x0a\x00\x3d\xcc\xff" - b"\xff\xc8\x4d\xff\x48\x36\xf0\x14\x00\xd3\x65\x00" - b"\x31\xf9\xb2\x3e\x2e\x7b\xff\xff\x48\x00\xb7\x6c" - b"\x16\xff\x19\x00\xaf\xd2\x04\x00\x00\x00\x88\xff" - b"\x48\x00\x71\xa9\x32\xf9\x00\x00\xe6\x85\x00\x00" - b"\x00\x00\x24\xff\x48\x00\x56\xc1\x2f\xfa\x00\x00" - b"\xe5\x87\x00\x00\x00\x00\x28\xff\x48\x00\x5b\xba" - b"\x12\xfe\x1e\x00\xac\xd7\x05\x00\x00\x00\x85\xff" - b"\x49\x00\x85\x92\x00\xca\x6c\x00\x2e\xf7\xba\x43" - b"\x32\x7f\xe9\xff\x95\x3b\xe9\x3a\x00\x59\xe3\x10" - b"\x00\x39\xcb\xff\xff\xc7\x30\x78\xfc\xf8\x77\x00" - b"\x00\x01\xb2\xbe\x0f\x00\x00\x09\x09\x00\x00\x00" - b"\x08\x05\x00\x00\x00\x00\x0c\xaf\xe1\x65\x13\x00" - b"\x00\x01\x3f\x37\x00\x00\x00\x00\x00\x00\x00\x01" - b"\x53\xc3\xfa\xe7\xdf\xf7\xe2\x61\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x0e\x2c\x33\x1a\x01\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x32\x23\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x42\xff\xf2\x09" - b"\x00\x00\x00\x00\x00\x00\x00\x00\xb6\xf2\xfe\x6a" - b"\x00\x00\x00\x00\x00\x00\x00\x2a\xfe\x5d\xb2\xdb" - b"\x02\x00\x00\x00\x00\x00\x00\x9e\xe6\x06\x41\xff" - b"\x52\x00\x00\x00\x00\x00\x18\xf9\x7b\x00\x00\xd0" - b"\xc7\x00\x00\x00\x00\x00\x86\xf7\x14\x00\x00\x60" - b"\xff\x3b\x00\x00\x00\x0b\xf6\xae\x14\x14\x14\x1b" - b"\xfa\xb0\x00\x00\x00\x6e\xff\xff\xff\xff\xff\xff" - b"\xff\xff\x26\x00\x03\xde\xe3\x34\x34\x34\x34\x34" - b"\x49\xff\x98\x00\x56\xff\x48\x00\x00\x00\x00\x00" - b"\x00\x9d\xf7\x15\xc9\xd7\x01\x00\x00\x00\x00\x00" - b"\x00\x2d\xff\x81\x10\x34\x34\x34\x34\x32\x1c\x03" - b"\x00\x00\x00\x50\xff\xff\xff\xff\xff\xff\xec\x79" - b"\x01\x00\x50\xff\x59\x18\x18\x1a\x36\xa8\xff\x6a" - b"\x00\x50\xff\x48\x00\x00\x00\x00\x03\xf3\xaf\x00" - b"\x50\xff\x48\x00\x00\x00\x00\x11\xfa\x96\x00\x50" - b"\xff\x8c\x50\x50\x52\x6d\xd3\xea\x24\x00\x50\xff" - b"\xff\xf8\xf8\xf8\xff\xff\xbe\x22\x00\x50\xff\x48" - b"\x00\x00\x00\x06\x3b\xda\xdf\x07\x50\xff\x48\x00" - b"\x00\x00\x00\x00\x60\xff\x40\x50\xff\x48\x00\x00" - b"\x00\x00\x00\x79\xff\x3e\x50\xff\x7e\x4c\x4c\x4c" - b"\x59\x8d\xf8\xd7\x05\x50\xff\xff\xff\xff\xff\xfa" - b"\xdf\x97\x17\x00\x00\x00\x00\x00\x03\x33\x4d\x41" - b"\x12\x00\x00\x00\x00\x02\x76\xec\xff\xff\xff\xfc" - b"\xab\x1a\x00\x02\xaf\xff\xb0\x48\x1f\x2c\x76\xed" - b"\xae\x00\x73\xff\x83\x00\x00\x00\x00\x00\x22\x0f" - b"\x01\xe6\xd3\x04\x00\x00\x00\x00\x00\x00\x00\x25" - b"\xff\x7b\x00\x00\x00\x00\x00\x00\x00\x00\x36\xff" - b"\x65\x00\x00\x00\x00\x00\x00\x00\x00\x1e\xff\x86" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\xd7\xe4\x0f" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x55\xff\xae\x0d" - b"\x00\x00\x00\x00\x47\x29\x00\x00\x82\xff\xe2\x79" - b"\x52\x60\xa9\xfe\xa9\x00\x00\x00\x46\xbd\xfa\xff" - b"\xff\xdb\x76\x06\x00\x00\x00\x00\x00\x06\x19\x0e" - b"\x00\x00\x00\x10\x34\x34\x34\x34\x2f\x16\x00\x00" - b"\x00\x00\x00\x50\xff\xff\xff\xff\xff\xff\xdf\x7b" - b"\x06\x00\x00\x50\xff\x6a\x30\x30\x36\x54\xa7\xfe" - b"\xcd\x0e\x00\x50\xff\x48\x00\x00\x00\x00\x00\x51" - b"\xfd\xa8\x00\x50\xff\x48\x00\x00\x00\x00\x00\x00" - b"\x9a\xfd\x20\x50\xff\x48\x00\x00\x00\x00\x00\x00" - b"\x45\xff\x57\x50\xff\x48\x00\x00\x00\x00\x00\x00" - b"\x30\xff\x6b\x50\xff\x48\x00\x00\x00\x00\x00\x00" - b"\x51\xff\x51\x50\xff\x48\x00\x00\x00\x00\x00\x00" - b"\xb5\xf6\x12\x50\xff\x48\x00\x00\x00\x00\x02\x84" - b"\xff\x86\x00\x50\xff\x8f\x64\x64\x6a\x88\xd8\xff" - b"\xa0\x03\x00\x50\xff\xff\xff\xff\xfb\xe3\xac\x47" - b"\x00\x00\x00\x10\x34\x34\x34\x34\x34\x34\x34\x1e" - b"\x50\xff\xff\xff\xff\xff\xff\xff\x94\x50\xff\x6a" - b"\x30\x30\x30\x30\x30\x1b\x50\xff\x48\x00\x00\x00" - b"\x00\x00\x00\x50\xff\x48\x00\x00\x00\x00\x00\x00" - b"\x50\xff\x9a\x60\x60\x60\x60\x55\x00\x50\xff\xff" - b"\xfc\xfc\xfc\xfc\xe0\x00\x50\xff\x48\x00\x00\x00" - b"\x00\x00\x00\x50\xff\x48\x00\x00\x00\x00\x00\x00" - b"\x50\xff\x48\x00\x00\x00\x00\x00\x00\x50\xff\x8f" - b"\x64\x64\x64\x64\x64\x51\x50\xff\xff\xff\xff\xff" - b"\xff\xff\xd0\x10\x34\x34\x34\x34\x34\x34\x34\x1e" - b"\x50\xff\xff\xff\xff\xff\xff\xff\x94\x50\xff\x6a" - b"\x30\x30\x30\x30\x30\x1b\x50\xff\x48\x00\x00\x00" - b"\x00\x00\x00\x50\xff\x48\x00\x00\x00\x00\x00\x00" - b"\x50\xff\x48\x00\x00\x00\x00\x00\x00\x50\xff\xff" - b"\xe8\xe8\xe8\xe8\xce\x00\x50\xff\xb1\x78\x78\x78" - b"\x78\x6a\x00\x50\xff\x48\x00\x00\x00\x00\x00\x00" - b"\x50\xff\x48\x00\x00\x00\x00\x00\x00\x50\xff\x48" - b"\x00\x00\x00\x00\x00\x00\x50\xff\x48\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x03\x32\x4c\x43\x17" - b"\x00\x00\x00\x00\x00\x02\x74\xea\xff\xff\xff\xfe" - b"\xb8\x28\x00\x00\x02\xad\xff\xb3\x4b\x20\x28\x67" - b"\xe0\xcc\x01\x00\x72\xff\x7d\x00\x00\x00\x00\x00" - b"\x11\x1a\x00\x01\xe6\xcf\x01\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x25\xff\x7b\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x36\xff\x65\x00\x00\x00\x00\x00\x00" - b"\x5e\xa2\x00\x1e\xff\x87\x00\x00\x00\x00\x00\x00" - b"\x90\xf8\x00\x00\xd7\xe2\x08\x00\x00\x00\x00\x00" - b"\x90\xf8\x00\x00\x55\xff\xac\x0e\x00\x00\x00\x00" - b"\xb4\xff\x00\x00\x00\x82\xff\xe4\x7b\x53\x5b\x97" - b"\xff\xff\x00\x00\x00\x00\x44\xbb\xf9\xff\xff\xe4" - b"\x90\x19\x00\x00\x00\x00\x00\x00\x05\x19\x10\x00" - b"\x00\x00\x00\x10\x34\x0e\x00\x00\x00\x00\x00\x0e" - b"\x34\x10\x50\xff\x48\x00\x00\x00\x00\x00\x48\xff" - b"\x50\x50\xff\x48\x00\x00\x00\x00\x00\x48\xff\x50" - b"\x50\xff\x48\x00\x00\x00\x00\x00\x48\xff\x50\x50" - b"\xff\x48\x00\x00\x00\x00\x00\x48\xff\x50\x50\xff" - b"\x9f\x64\x64\x64\x64\x64\x9d\xff\x50\x50\xff\xff" - b"\xff\xff\xff\xff\xff\xff\xff\x50\x50\xff\x4b\x04" - b"\x04\x04\x04\x04\x4b\xff\x50\x50\xff\x48\x00\x00" - b"\x00\x00\x00\x48\xff\x50\x50\xff\x48\x00\x00\x00" - b"\x00\x00\x48\xff\x50\x50\xff\x48\x00\x00\x00\x00" - b"\x00\x48\xff\x50\x50\xff\x48\x00\x00\x00\x00\x00" - b"\x48\xff\x50\x10\x34\x0e\x50\xff\x48\x50\xff\x48" - b"\x50\xff\x48\x50\xff\x48\x50\xff\x48\x50\xff\x48" - b"\x50\xff\x48\x50\xff\x48\x50\xff\x48\x50\xff\x48" - b"\x50\xff\x48\x00\x00\x34\x34\x34\x34\x34\x20\x00" - b"\x04\xff\xff\xff\xff\xff\xa0\x00\x00\x30\x30\x30" - b"\x30\xf6\xa0\x00\x00\x00\x00\x00\x00\xf4\xa0\x00" - b"\x00\x00\x00\x00\x00\xf4\xa0\x00\x00\x00\x00\x00" - b"\x00\xf4\xa0\x00\x00\x00\x00\x00\x00\xf4\xa0\x00" - b"\x00\x00\x00\x00\x00\xf4\xa0\x00\x00\x00\x00\x00" - b"\x00\xf7\x99\x00\x3d\x0c\x00\x00\x21\xff\x79\x05" - b"\xe6\xd0\x5b\x50\xc6\xfa\x28\x00\x36\xca\xff\xff" - b"\xe2\x54\x00\x00\x00\x00\x0e\x10\x00\x00\x00\x10" - b"\x34\x0e\x00\x00\x00\x00\x00\x26\x33\x04\x50\xff" - b"\x48\x00\x00\x00\x00\x56\xfc\x84\x00\x50\xff\x48" - b"\x00\x00\x00\x4f\xfb\x91\x00\x00\x50\xff\x48\x00" - b"\x00\x49\xf9\x9e\x01\x00\x00\x50\xff\x48\x00\x43" - b"\xf7\xa9\x02\x00\x00\x00\x50\xff\x48\x3d\xff\xdd" - b"\x05\x00\x00\x00\x00\x50\xff\x80\xf2\xff\xe6\x13" - b"\x00\x00\x00\x00\x50\xff\xff\xcb\x53\xfb\xbf\x05" - b"\x00\x00\x00\x50\xff\xff\x11\x00\x64\xff\x97\x00" - b"\x00\x00\x50\xff\x53\x00\x00\x00\x88\xff\x6b\x00" - b"\x00\x50\xff\x48\x00\x00\x00\x01\xac\xfb\x42\x00" - b"\x50\xff\x48\x00\x00\x00\x00\x09\xc8\xed\x24\x10" - b"\x34\x0e\x00\x00\x00\x00\x00\x00\x50\xff\x48\x00" - b"\x00\x00\x00\x00\x00\x50\xff\x48\x00\x00\x00\x00" - b"\x00\x00\x50\xff\x48\x00\x00\x00\x00\x00\x00\x50" - b"\xff\x48\x00\x00\x00\x00\x00\x00\x50\xff\x48\x00" - b"\x00\x00\x00\x00\x00\x50\xff\x48\x00\x00\x00\x00" - b"\x00\x00\x50\xff\x48\x00\x00\x00\x00\x00\x00\x50" - b"\xff\x48\x00\x00\x00\x00\x00\x00\x50\xff\x48\x00" - b"\x00\x00\x00\x00\x00\x50\xff\x8f\x64\x64\x64\x64" - b"\x64\x22\x50\xff\xff\xff\xff\xff\xff\xff\x58\x10" - b"\x33\x02\x00\x00\x00\x00\x00\x00\x00\x00\x29\x1e" - b"\x50\xff\x66\x00\x00\x00\x00\x00\x00\x00\x2a\xfb" - b"\x94\x50\xff\xec\x12\x00\x00\x00\x00\x00\x00\xbb" - b"\xff\x94\x50\xff\xff\x97\x00\x00\x00\x00\x00\x51" - b"\xff\xff\x94\x50\xff\xa4\xfd\x31\x00\x00\x00\x08" - b"\xde\xa8\xf4\x95\x50\xff\x3b\xce\xc5\x01\x00\x00" - b"\x7d\xf3\x1b\xf3\x95\x50\xff\x38\x39\xfe\x5f\x00" - b"\x1c\xf5\x77\x00\xf3\x95\x50\xff\x38\x00\x9f\xe8" - b"\x0e\xa8\xd8\x06\x00\xf2\x96\x50\xff\x38\x00\x15" - b"\xef\xcd\xff\x46\x00\x00\xf2\x96\x50\xff\x38\x00" - b"\x00\x6c\xff\xad\x00\x00\x00\xf1\x96\x50\xff\x38" - b"\x00\x00\x03\x94\x1f\x00\x00\x00\xf1\x97\x50\xff" - b"\x38\x00\x00\x00\x00\x00\x00\x00\x00\xf0\x97\x10" - b"\x33\x03\x00\x00\x00\x00\x00\x0e\x34\x10\x50\xff" - b"\x8c\x00\x00\x00\x00\x00\x48\xff\x50\x50\xff\xff" - b"\x5b\x00\x00\x00\x00\x48\xff\x50\x50\xff\xff\xf6" - b"\x32\x00\x00\x00\x48\xff\x50\x50\xff\x66\xea\xe2" - b"\x15\x00\x00\x48\xff\x50\x50\xff\x48\x40\xfb\xc1" - b"\x05\x00\x48\xff\x50\x50\xff\x48\x00\x6e\xff\x95" - b"\x00\x48\xff\x50\x50\xff\x48\x00\x00\x9f\xff\x63" - b"\x48\xff\x50\x50\xff\x48\x00\x00\x07\xc9\xf9\x80" - b"\xff\x50\x50\xff\x48\x00\x00\x00\x1b\xe8\xff\xff" - b"\x50\x50\xff\x48\x00\x00\x00\x00\x3b\xfa\xff\x50" - b"\x50\xff\x48\x00\x00\x00\x00\x00\x67\xff\x50\x00" - b"\x00\x00\x00\x02\x32\x4c\x43\x14\x00\x00\x00\x00" - b"\x00\x00\x02\x72\xe9\xff\xff\xff\xfd\xb2\x24\x00" - b"\x00\x00\x02\xac\xff\xb0\x46\x1f\x2e\x78\xed\xf0" - b"\x32\x00\x00\x71\xff\x86\x00\x00\x00\x00\x00\x23" - b"\xe8\xdd\x06\x01\xe5\xd3\x05\x00\x00\x00\x00\x00" - b"\x00\x5d\xff\x58\x25\xff\x7b\x00\x00\x00\x00\x00" - b"\x00\x00\x0a\xfd\x95\x36\xff\x65\x00\x00\x00\x00" - b"\x00\x00\x00\x00\xf2\xa5\x1d\xff\x87\x00\x00\x00" - b"\x00\x00\x00\x00\x14\xff\x8d\x00\xd5\xe5\x10\x00" - b"\x00\x00\x00\x00\x00\x77\xff\x47\x00\x53\xff\xae" - b"\x0d\x00\x00\x00\x00\x4f\xf7\xc5\x00\x00\x00\x7e" - b"\xff\xe1\x78\x52\x60\xad\xff\xd7\x1a\x00\x00\x00" - b"\x00\x41\xba\xf9\xff\xff\xde\x7f\x0c\x00\x00\x00" - b"\x00\x00\x00\x00\x05\x19\x0f\x00\x00\x00\x00\x00" - b"\x10\x34\x34\x34\x34\x29\x0d\x00\x00\x00\x50\xff" - b"\xff\xff\xff\xff\xfc\xb7\x28\x00\x50\xff\x78\x30" - b"\x30\x40\x73\xe9\xea\x1b\x50\xff\x48\x00\x00\x00" - b"\x00\x37\xff\x84\x50\xff\x48\x00\x00\x00\x00\x00" - b"\xee\xae\x50\xff\x48\x00\x00\x00\x00\x0d\xfc\x9d" - b"\x50\xff\x48\x00\x00\x00\x12\xab\xff\x46\x50\xff" - b"\xff\xc8\xc8\xd7\xf8\xfb\x7a\x00\x50\xff\xe1\x9c" - b"\x9c\x91\x72\x23\x00\x00\x50\xff\x48\x00\x00\x00" - b"\x00\x00\x00\x00\x50\xff\x48\x00\x00\x00\x00\x00" - b"\x00\x00\x50\xff\x48\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x02\x32\x4c\x43\x14\x00\x00\x00" - b"\x00\x00\x00\x00\x02\x72\xe9\xff\xff\xff\xfd\xb2" - b"\x24\x00\x00\x00\x00\x02\xac\xff\xb0\x46\x1f\x2e" - b"\x78\xed\xf0\x32\x00\x00\x00\x71\xff\x86\x00\x00" - b"\x00\x00\x00\x23\xe8\xdd\x06\x00\x01\xe5\xd3\x05" - b"\x00\x00\x00\x00\x00\x00\x5d\xff\x58\x00\x25\xff" - b"\x7b\x00\x00\x00\x00\x00\x00\x00\x0a\xfd\x95\x00" - b"\x36\xff\x65\x00\x00\x00\x00\x00\x00\x00\x00\xf2" - b"\xa5\x00\x1d\xff\x87\x00\x00\x00\x00\x00\x00\x00" - b"\x14\xff\x8d\x00\x00\xd5\xe7\x11\x00\x00\x00\x00" - b"\x00\x00\x79\xff\x47\x00\x00\x53\xff\xae\x06\x00" - b"\x00\x00\x00\x47\xf8\xc5\x00\x00\x00\x00\x7e\xff" - b"\xda\x78\x52\x60\xa8\xfd\xd7\x1a\x00\x00\x00\x00" - b"\x00\x41\xba\xf9\xff\xff\xef\x7f\x0c\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x05\x64\xff\xcb\x2c\x01\x38" - b"\xa1\x05\x00\x00\x00\x00\x00\x00\x00\x35\xd5\xff" - b"\xf8\xff\xaa\x07\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x01\x35\x50\x2a\x00\x00\x10\x34\x34\x34\x34\x29" - b"\x0d\x00\x00\x00\x50\xff\xff\xff\xff\xff\xfc\xb7" - b"\x28\x00\x50\xff\x78\x30\x30\x40\x73\xe9\xea\x1b" - b"\x50\xff\x48\x00\x00\x00\x00\x37\xff\x84\x50\xff" - b"\x48\x00\x00\x00\x00\x00\xee\xae\x50\xff\x48\x00" - b"\x00\x00\x00\x0d\xfc\x9d\x50\xff\x48\x00\x00\x00" - b"\x10\xa8\xff\x46\x50\xff\xff\xc4\xc4\xde\xff\xfc" - b"\x79\x00\x50\xff\xdd\x98\x98\xd9\xff\x8e\x00\x00" - b"\x50\xff\x48\x00\x00\x00\x97\xf7\x2b\x00\x50\xff" - b"\x48\x00\x00\x00\x0a\xd8\xd0\x06\x50\xff\x48\x00" - b"\x00\x00\x00\x33\xfa\x8d\x00\x00\x00\x1b\x44\x4d" - b"\x31\x04\x00\x00\x00\x13\xb3\xfe\xff\xff\xff\xec" - b"\x6e\x00\x00\xb4\xf5\x62\x18\x13\x38\x96\x6d\x00" - b"\x08\xfe\x92\x00\x00\x00\x00\x00\x00\x00\x04\xf9" - b"\xbb\x05\x00\x00\x00\x00\x00\x00\x00\x83\xff\xdc" - b"\x80\x39\x04\x00\x00\x00\x00\x00\x56\xc0\xfb\xff" - b"\xef\x91\x0f\x00\x00\x00\x00\x00\x0d\x4e\xa6\xfe" - b"\xce\x04\x00\x00\x00\x00\x00\x00\x00\x77\xff\x39" - b"\x00\x44\x00\x00\x00\x00\x00\x66\xff\x31\x2a\xff" - b"\xc5\x6d\x45\x45\x7a\xf1\xc6\x00\x00\x47\xb7\xf6" - b"\xff\xff\xef\x99\x12\x00\x00\x00\x00\x03\x17\x15" - b"\x02\x00\x00\x00\x30\x34\x34\x34\x34\x34\x34\x34" - b"\x34\x11\xf0\xff\xff\xff\xff\xff\xff\xff\xff\x54" - b"\x2d\x30\x30\x43\xff\x94\x30\x30\x30\x0f\x00\x00" - b"\x00\x18\xff\x7c\x00\x00\x00\x00\x00\x00\x00\x18" - b"\xff\x7c\x00\x00\x00\x00\x00\x00\x00\x18\xff\x7c" - b"\x00\x00\x00\x00\x00\x00\x00\x18\xff\x7c\x00\x00" - b"\x00\x00\x00\x00\x00\x18\xff\x7c\x00\x00\x00\x00" - b"\x00\x00\x00\x18\xff\x7c\x00\x00\x00\x00\x00\x00" - b"\x00\x18\xff\x7c\x00\x00\x00\x00\x00\x00\x00\x18" - b"\xff\x7c\x00\x00\x00\x00\x00\x00\x00\x18\xff\x7c" - b"\x00\x00\x00\x00\x15\x34\x09\x00\x00\x00\x00\x00" - b"\x1a\x34\x03\x6c\xff\x30\x00\x00\x00\x00\x00\x80" - b"\xff\x10\x6c\xff\x30\x00\x00\x00\x00\x00\x80\xff" - b"\x10\x6c\xff\x30\x00\x00\x00\x00\x00\x80\xff\x10" - b"\x6c\xff\x30\x00\x00\x00\x00\x00\x80\xff\x10\x6c" - b"\xff\x30\x00\x00\x00\x00\x00\x80\xff\x10\x6c\xff" - b"\x30\x00\x00\x00\x00\x00\x80\xff\x10\x66\xff\x3c" - b"\x00\x00\x00\x00\x00\x8d\xff\x09\x4a\xff\x60\x00" - b"\x00\x00\x00\x00\xb2\xed\x00\x0e\xf3\xcd\x04\x00" - b"\x00\x00\x2a\xfb\xa4\x00\x00\x6e\xff\xd0\x6e\x56" - b"\x81\xf0\xee\x23\x00\x00\x00\x5a\xd6\xff\xff\xf9" - b"\xb3\x27\x00\x00\x00\x00\x00\x00\x0f\x1a\x06\x00" - b"\x00\x00\x00\x32\x27\x00\x00\x00\x00\x00\x00\x00" - b"\x0c\x34\x12\xb5\xf4\x11\x00\x00\x00\x00\x00\x00" - b"\x7f\xfb\x1d\x45\xff\x76\x00\x00\x00\x00\x00\x07" - b"\xe8\xa9\x00\x00\xd3\xe1\x03\x00\x00\x00\x00\x61" - b"\xff\x38\x00\x00\x64\xff\x55\x00\x00\x00\x00\xd1" - b"\xc8\x00\x00\x00\x08\xea\xc5\x00\x00\x00\x43\xff" - b"\x58\x00\x00\x00\x00\x83\xff\x35\x00\x00\xb4\xe3" - b"\x04\x00\x00\x00\x00\x18\xf9\xa5\x00\x26\xfe\x78" - b"\x00\x00\x00\x00\x00\x00\xa2\xfa\x19\x96\xf5\x12" - b"\x00\x00\x00\x00\x00\x00\x31\xff\x95\xf5\x98\x00" - b"\x00\x00\x00\x00\x00\x00\x00\xc1\xff\xff\x29\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x50\xff\xbf\x00\x00" - b"\x00\x00\x00\x17\x34\x09\x00\x00\x00\x00\x00\x23" - b"\x2b\x00\x00\x00\x00\x00\x02\x34\x18\x40\xff\x63" - b"\x00\x00\x00\x00\x01\xe4\xfa\x11\x00\x00\x00\x00" - b"\x41\xff\x43\x02\xe8\xb8\x00\x00\x00\x00\x3f\xff" - b"\xff\x62\x00\x00\x00\x00\x97\xea\x03\x00\x96\xfb" - b"\x12\x00\x00\x00\x98\xe0\xd3\xb8\x00\x00\x00\x03" - b"\xe9\x98\x00\x00\x40\xff\x62\x00\x00\x04\xeb\x89" - b"\x7b\xfb\x13\x00\x00\x44\xff\x43\x00\x00\x02\xe8" - b"\xb7\x00\x00\x49\xff\x31\x23\xff\x65\x00\x00\x9a" - b"\xea\x03\x00\x00\x00\x96\xfa\x11\x00\xa1\xda\x00" - b"\x00\xcc\xbb\x00\x04\xeb\x98\x00\x00\x00\x00\x40" - b"\xff\x61\x08\xf1\x82\x00\x00\x75\xfc\x15\x46\xff" - b"\x43\x00\x00\x00\x00\x02\xe8\xb6\x52\xff\x2a\x00" - b"\x00\x1f\xfe\x68\x9d\xea\x03\x00\x00\x00\x00\x00" - b"\x96\xfa\xbc\xd3\x00\x00\x00\x00\xc6\xc3\xed\x98" - b"\x00\x00\x00\x00\x00\x00\x40\xff\xff\x7b\x00\x00" - b"\x00\x00\x6f\xff\xff\x43\x00\x00\x00\x00\x00\x00" - b"\x02\xef\xff\x23\x00\x00\x00\x00\x1a\xff\xf4\x03" - b"\x00\x00\x00\x17\x34\x12\x00\x00\x00\x00\x00\x1c" - b"\x34\x09\x1a\xec\xc6\x04\x00\x00\x00\x14\xe5\xbe" - b"\x02\x00\x4e\xff\x86\x00\x00\x00\xb2\xeb\x1a\x00" - b"\x00\x00\x94\xfd\x44\x00\x6c\xff\x4c\x00\x00\x00" - b"\x00\x08\xd3\xe7\x44\xf7\x91\x00\x00\x00\x00\x00" - b"\x00\x2b\xff\xff\xd5\x07\x00\x00\x00\x00\x00\x00" - b"\x03\xff\xff\xb3\x00\x00\x00\x00\x00\x00\x00\x81" - b"\xfe\xd4\xfe\x47\x00\x00\x00\x00\x00\x3e\xfc\x8f" - b"\x05\xcb\xe9\x18\x00\x00\x00\x13\xe4\xd0\x07\x00" - b"\x24\xf3\xba\x01\x00\x00\xb2\xf6\x29\x00\x00\x00" - b"\x5f\xff\x77\x00\x6e\xff\x67\x00\x00\x00\x00\x00" - b"\xa5\xfb\x37\x33\x24\x00\x00\x00\x00\x00\x00\x0c" - b"\x34\x12\xa3\xf4\x1c\x00\x00\x00\x00\x00\x9a\xeb" - b"\x12\x17\xef\xad\x00\x00\x00\x00\x37\xfe\x62\x00" - b"\x00\x6b\xff\x49\x00\x00\x03\xce\xc5\x01\x00\x00" - b"\x02\xcc\xdc\x08\x00\x6e\xfc\x2e\x00\x00\x00\x00" - b"\x36\xfd\x81\x18\xf0\x8f\x00\x00\x00\x00\x00\x00" - b"\x98\xf8\xc9\xe5\x0d\x00\x00\x00\x00\x00\x00\x11" - b"\xf9\xff\x57\x00\x00\x00\x00\x00\x00\x00\x00\xab" - b"\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x9c\xf8" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x9c\xf8\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x9c\xf8\x00\x00" - b"\x00\x00\x00\x0a\x34\x34\x34\x34\x34\x34\x34\x34" - b"\x2b\x00\x34\xff\xff\xff\xff\xff\xff\xff\xff\xd6" - b"\x00\x09\x30\x30\x30\x30\x30\x30\xba\xff\x5b\x00" - b"\x00\x00\x00\x00\x00\x00\x58\xff\x8d\x00\x00\x00" - b"\x00\x00\x00\x00\x30\xf5\xbc\x03\x00\x00\x00\x00" - b"\x00\x00\x14\xe0\xde\x13\x00\x00\x00\x00\x00\x00" - b"\x04\xbe\xf5\x2f\x00\x00\x00\x00\x00\x00\x00\x91" - b"\xff\x57\x00\x00\x00\x00\x00\x00\x00\x5f\xff\x89" - b"\x00\x00\x00\x00\x00\x00\x00\x35\xf7\xb9\x03\x00" - b"\x00\x00\x00\x00\x00\x16\xe4\xff\x75\x64\x64\x64" - b"\x64\x64\x64\x04\x50\xff\xff\xff\xff\xff\xff\xff" - b"\xff\xff\x0c\x46\xe0\xe0\xe0\x07\x50\xff\x86\x64" - b"\x03\x50\xff\x38\x00\x00\x50\xff\x38\x00\x00\x50" - b"\xff\x38\x00\x00\x50\xff\x38\x00\x00\x50\xff\x38" - b"\x00\x00\x50\xff\x38\x00\x00\x50\xff\x38\x00\x00" - b"\x50\xff\x38\x00\x00\x50\xff\x38\x00\x00\x50\xff" - b"\x38\x00\x00\x50\xff\x38\x00\x00\x50\xff\x57\x28" - b"\x01\x50\xff\xff\xff\x08\x08\x1c\x1c\x1c\x00\x3f" - b"\x67\x00\x00\x00\x00\x00\x00\x45\xfe\x1f\x00\x00" - b"\x00\x00\x00\x03\xe8\x78\x00\x00\x00\x00\x00\x00" - b"\x92\xd2\x00\x00\x00\x00\x00\x00\x38\xff\x2c\x00" - b"\x00\x00\x00\x00\x00\xdd\x85\x00\x00\x00\x00\x00" - b"\x00\x85\xde\x00\x00\x00\x00\x00\x00\x2a\xff\x39" - b"\x00\x00\x00\x00\x00\x00\xd1\x93\x00\x00\x00\x00" - b"\x00\x00\x77\xe9\x03\x00\x00\x00\x00\x00\x1f\xfe" - b"\x46\x00\x00\x00\x00\x00\x00\xc4\xa0\x00\x00\x00" - b"\x00\x00\x00\x6a\xf1\x08\x00\x00\x00\x00\x00\x14" - b"\xfb\x53\x00\x00\x00\x00\x00\x00\xb7\xad\x00\x00" - b"\x00\x00\x00\x00\x41\x90\x01\x9d\xe0\xe0\x8f\x46" - b"\x64\xee\xa4\x00\x00\xe4\xa4\x00\x00\xe4\xa4\x00" - b"\x00\xe4\xa4\x00\x00\xe4\xa4\x00\x00\xe4\xa4\x00" - b"\x00\xe4\xa4\x00\x00\xe4\xa4\x00\x00\xe4\xa4\x00" - b"\x00\xe4\xa4\x00\x00\xe4\xa4\x00\x00\xe4\xa4\x1c" - b"\x28\xe8\xa4\xb4\xff\xff\xa4\x13\x1c\x1c\x11\x00" - b"\x00\x27\xff\x77\x00\x00\x00\x00\x00\x93\xdb\xe4" - b"\x04\x00\x00\x00\x0d\xf2\x2f\xd9\x55\x00\x00\x00" - b"\x6c\xc4\x00\x71\xc2\x00\x00\x01\xd8\x5b\x00\x11" - b"\xf6\x2e\x00\x46\xea\x07\x00\x00\x9e\x9a\x00\x78" - b"\x6e\x00\x00\x00\x31\xb1\x04\xfc\xfc\xfc\xfc\xfc" - b"\xfc\xfc\xfc\x01\x7b\xc8\x35\x00\x00\x00\x76\xed" - b"\x3c\x00\x00\x00\x2c\x3d\x00\x00\x33\x76\x89\x6d" - b"\x22\x00\x00\x00\x9e\xff\xea\xce\xf2\xf9\x54\x00" - b"\x00\x51\x3c\x00\x00\x10\xcb\xea\x00\x00\x00\x00" - b"\x00\x00\x00\x6a\xff\x1c\x00\x22\x9d\xd2\xdf\xe0" - b"\xff\xff\x2c\x06\xe1\xd0\x50\x38\x38\x8d\xff\x2c" - b"\x2b\xff\x5d\x00\x00\x00\x82\xff\x2c\x0c\xf1\xc6" - b"\x34\x21\x68\xff\xff\x2c\x00\x41\xd7\xff\xff\xcb" - b"\x78\xff\x2c\x00\x00\x00\x0e\x09\x00\x00\x00\x00" - b"\x7a\xdc\x00\x00\x00\x00\x00\x00\x00\x00\x8c\xfc" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x8c\xfc\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x8c\xfc\x00\x44\x81\x7f" - b"\x45\x01\x00\x00\x8c\xfe\xa8\xfc\xd3\xe2\xff\xbe" - b"\x0e\x00\x8c\xff\xd6\x24\x00\x00\x56\xfb\xa0\x00" - b"\x8c\xff\x3a\x00\x00\x00\x00\x92\xfa\x10\x8c\xff" - b"\x03\x00\x00\x00\x00\x59\xff\x32\x8c\xff\x12\x00" - b"\x00\x00\x00\x6a\xff\x26\x8c\xff\x7c\x00\x00\x00" - b"\x05\xcd\xe0\x02\x8c\xff\xf6\x96\x4a\x59\xc6\xff" - b"\x54\x00\x8c\xec\x3f\xcf\xff\xff\xd0\x4d\x00\x00" - b"\x00\x00\x00\x00\x0a\x08\x00\x00\x00\x00\x00\x00" - b"\x01\x40\x7c\x85\x57\x08\x00\x00\x11\xbe\xff\xdf" - b"\xd4\xfe\xd8\x1c\x00\xaf\xf4\x48\x00\x00\x2a\xc9" - b"\x3f\x21\xff\x7a\x00\x00\x00\x00\x00\x00\x4a\xff" - b"\x40\x00\x00\x00\x00\x00\x00\x3d\xff\x51\x00\x00" - b"\x00\x00\x00\x00\x08\xec\xba\x01\x00\x00\x00\x41" - b"\x02\x00\x5a\xfe\xbb\x56\x4c\xa0\xff\x58\x00\x00" - b"\x47\xc9\xfe\xff\xdf\x68\x00\x00\x00\x00\x00\x06" - b"\x0d\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11" - b"\xe0\x65\x00\x00\x00\x00\x00\x00\x00\x14\xff\x74" - b"\x00\x00\x00\x00\x00\x00\x00\x14\xff\x74\x00\x00" - b"\x03\x4c\x82\x7f\x3a\x14\xff\x74\x00\x17\xca\xff" - b"\xdd\xd5\xfe\xa7\xff\x74\x00\xb8\xf4\x46\x00\x00" - b"\x2f\xf6\xff\x74\x23\xff\x7a\x00\x00\x00\x00\x59" - b"\xff\x74\x4a\xff\x40\x00\x00\x00\x00\x19\xff\x74" - b"\x3e\xff\x52\x00\x00\x00\x00\x2a\xff\x74\x0a\xf1" - b"\xba\x01\x00\x00\x00\x95\xff\x74\x00\x6a\xff\xba" - b"\x54\x4d\xa2\xf4\xff\x74\x00\x00\x5a\xd7\xff\xff" - b"\xc6\x36\xff\x74\x00\x00\x00\x00\x0a\x08\x00\x00" - b"\x00\x00\x00\x00\x03\x4c\x82\x7e\x41\x00\x00\x00" - b"\x00\x17\xc9\xfc\xca\xd0\xff\xb1\x07\x00\x00\xb7" - b"\xe3\x27\x00\x00\x3c\xf4\x86\x00\x23\xff\x60\x00" - b"\x00\x00\x00\x86\xed\x01\x4a\xff\xff\xd4\xd4\xd4" - b"\xd4\xfe\xff\x15\x3d\xff\x88\x44\x44\x44\x44\x44" - b"\x44\x06\x09\xee\xb2\x01\x00\x00\x00\x14\x00\x00" - b"\x00\x5e\xfe\xbc\x5a\x4c\x81\xf4\x50\x00\x00\x00" - b"\x48\xc8\xfe\xff\xec\x8c\x08\x00\x00\x00\x00\x00" - b"\x05\x11\x01\x00\x00\x00\x00\x00\x41\xca\xee\xc6" - b"\x0f\x00\x12\xf1\xbe\x56\x7b\x00\x00\x44\xff\x3b" - b"\x00\x00\x00\x5e\xcc\xff\xac\x7c\x4f\x00\x99\xff" - b"\xff\xfd\xc8\x80\x00\x00\x50\xff\x38\x00\x00\x00" - b"\x00\x50\xff\x38\x00\x00\x00\x00\x50\xff\x38\x00" - b"\x00\x00\x00\x50\xff\x38\x00\x00\x00\x00\x50\xff" - b"\x38\x00\x00\x00\x00\x50\xff\x38\x00\x00\x00\x00" - b"\x50\xff\x38\x00\x00\x00\x00\x00\x05\x4f\x82\x81" - b"\x48\x01\x6e\x45\x00\x1e\xd1\xff\xdd\xd2\xfb\xb9" - b"\xeb\x90\x00\xc8\xee\x3d\x00\x00\x1b\xc8\xff\x90" - b"\x2e\xff\x6e\x00\x00\x00\x00\x27\xff\x90\x4c\xff" - b"\x3f\x00\x00\x00\x00\x00\xff\x90\x32\xff\x67\x00" - b"\x00\x00\x00\x20\xff\x90\x00\xd2\xe7\x2c\x00\x00" - b"\x0f\xbc\xff\x90\x00\x27\xdf\xfd\xc9\xbe\xf2\xc5" - b"\xff\x8f\x00\x00\x0c\x64\x96\x96\x59\x0e\xfe\x84" - b"\x00\x0c\x04\x00\x00\x00\x00\x4c\xff\x58\x00\x99" - b"\xd0\x66\x31\x31\x66\xe8\xe1\x09\x00\x35\xb6\xf9" - b"\xff\xff\xfe\xbf\x26\x00\x00\x00\x00\x07\x27\x2e" - b"\x12\x00\x00\x00\x7a\xdc\x00\x00\x00\x00\x00\x00" - b"\x00\x8c\xfc\x00\x00\x00\x00\x00\x00\x00\x8c\xfc" - b"\x00\x00\x00\x00\x00\x00\x00\x8c\xfc\x02\x4a\x82" - b"\x81\x42\x00\x00\x8c\xff\xb8\xf9\xd8\xf2\xff\x97" - b"\x00\x8c\xff\xc1\x15\x00\x08\xa9\xff\x35\x8c\xff" - b"\x2b\x00\x00\x00\x27\xff\x6f\x8c\xfe\x02\x00\x00" - b"\x00\x09\xff\x7f\x8c\xfc\x00\x00\x00\x00\x08\xff" - b"\x80\x8c\xfc\x00\x00\x00\x00\x08\xff\x80\x8c\xfc" - b"\x00\x00\x00\x00\x08\xff\x80\x8c\xfc\x00\x00\x00" - b"\x00\x08\xff\x80\x00\x0c\x03\x1e\xf5\xb1\x12\xcb" - b"\x83\x00\x00\x00\x00\x43\x7a\x00\x8c\xfc\x00\x8c" - b"\xfc\x00\x8c\xfc\x00\x8c\xfc\x00\x8c\xfc\x00\x8c" - b"\xfc\x00\x8c\xfc\x00\x8c\xfc\x00\x00\x00\x00\x0c" - b"\x03\x00\x00\x00\x1e\xf5\xb1\x00\x00\x00\x12\xcb" - b"\x83\x00\x00\x00\x00\x00\x00\x00\x00\x00\x3a\x7c" - b"\x09\x00\x00\x00\x78\xff\x14\x00\x00\x00\x78\xff" - b"\x14\x00\x00\x00\x78\xff\x14\x00\x00\x00\x78\xff" - b"\x14\x00\x00\x00\x78\xff\x14\x00\x00\x00\x78\xff" - b"\x14\x00\x00\x00\x78\xff\x14\x00\x00\x00\x78\xff" - b"\x14\x00\x00\x00\x7d\xff\x0d\x09\x45\x27\xcd\xdd" - b"\x00\x46\xfe\xff\xef\x47\x00\x00\x15\x2a\x08\x00" - b"\x00\x7a\xdc\x00\x00\x00\x00\x00\x00\x00\x8c\xfc" - b"\x00\x00\x00\x00\x00\x00\x00\x8c\xfc\x00\x00\x00" - b"\x00\x00\x00\x00\x8c\xfc\x00\x00\x00\x00\x51\x7b" - b"\x1b\x8c\xfc\x00\x00\x00\x77\xff\x79\x00\x8c\xfc" - b"\x00\x01\x8c\xff\x76\x00\x00\x8c\xfc\x04\xa0\xff" - b"\x72\x00\x00\x00\x8c\xff\xb3\xff\xff\x47\x00\x00" - b"\x00\x8c\xff\xf6\x58\xd5\xed\x21\x00\x00\x8c\xff" - b"\x3e\x00\x25\xef\xd0\x09\x00\x8c\xfc\x00\x00\x00" - b"\x49\xfd\xa4\x00\x8c\xfc\x00\x00\x00\x00\x77\xff" - b"\x6e\x7a\xdc\x8c\xfc\x8c\xfc\x8c\xfc\x8c\xfc\x8c" - b"\xfc\x8c\xfc\x8c\xfc\x8c\xfc\x8c\xfc\x8c\xfc\x8c" - b"\xfc\x43\x72\x03\x50\x84\x79\x2c\x00\x00\x41\x7e" - b"\x83\x49\x00\x00\x8c\xf1\xbd\xf7\xd9\xf7\xfe\x68" - b"\xb1\xfe\xda\xee\xff\xa4\x00\x8c\xff\xbc\x11\x00" - b"\x14\xd0\xff\xea\x2c\x00\x04\x9d\xff\x3f\x8c\xff" - b"\x29\x00\x00\x00\x62\xff\x71\x00\x00\x00\x1e\xff" - b"\x78\x8c\xfe\x02\x00\x00\x00\x45\xff\x48\x00\x00" - b"\x00\x01\xff\x87\x8c\xfc\x00\x00\x00\x00\x44\xff" - b"\x44\x00\x00\x00\x00\xff\x88\x8c\xfc\x00\x00\x00" - b"\x00\x44\xff\x44\x00\x00\x00\x00\xff\x88\x8c\xfc" - b"\x00\x00\x00\x00\x44\xff\x44\x00\x00\x00\x00\xff" - b"\x88\x8c\xfc\x00\x00\x00\x00\x44\xff\x44\x00\x00" - b"\x00\x00\xff\x88\x43\x72\x02\x4a\x82\x81\x42\x00" - b"\x00\x8c\xf0\xb8\xf9\xd8\xf2\xff\x97\x00\x8c\xff" - b"\xc1\x15\x00\x08\xa9\xff\x35\x8c\xff\x2b\x00\x00" - b"\x00\x27\xff\x6f\x8c\xfe\x02\x00\x00\x00\x09\xff" - b"\x7f\x8c\xfc\x00\x00\x00\x00\x08\xff\x80\x8c\xfc" - b"\x00\x00\x00\x00\x08\xff\x80\x8c\xfc\x00\x00\x00" - b"\x00\x08\xff\x80\x8c\xfc\x00\x00\x00\x00\x08\xff" - b"\x80\x00\x00\x01\x44\x7e\x83\x53\x06\x00\x00\x00" - b"\x12\xc1\xff\xdd\xd5\xfe\xd9\x27\x00\x00\xaf\xf4" - b"\x46\x00\x00\x2d\xe1\xd9\x04\x21\xff\x7a\x00\x00" - b"\x00\x00\x52\xff\x4a\x4a\xff\x40\x00\x00\x00\x00" - b"\x19\xff\x72\x3d\xff\x52\x00\x00\x00\x00\x2a\xff" - b"\x65\x08\xec\xba\x01\x00\x00\x00\x92\xfe\x21\x00" - b"\x5b\xfe\xba\x54\x4c\xa0\xff\x87\x00\x00\x00\x4b" - b"\xcc\xfe\xff\xdc\x67\x00\x00\x00\x00\x00\x00\x07" - b"\x0b\x00\x00\x00\x00\x43\x72\x01\x47\x82\x7f\x45" - b"\x01\x00\x00\x8c\xef\xae\xfc\xd3\xe2\xff\xbe\x0e" - b"\x00\x8c\xff\xd6\x24\x00\x00\x56\xfb\xa0\x00\x8c" - b"\xff\x3a\x00\x00\x00\x00\x92\xfa\x10\x8c\xff\x03" - b"\x00\x00\x00\x00\x59\xff\x32\x8c\xff\x12\x00\x00" - b"\x00\x00\x6a\xff\x26\x8c\xff\x7c\x00\x00\x00\x05" - b"\xcd\xe0\x02\x8c\xff\xf4\x96\x4a\x59\xc6\xff\x54" - b"\x00\x8c\xfc\x3b\xcc\xff\xff\xd0\x4d\x00\x00\x8c" - b"\xfc\x00\x00\x0a\x08\x00\x00\x00\x00\x8c\xfc\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x8c\xfc\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x0f\x1b\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x03\x4c\x82\x7f\x3e\x01\x7c" - b"\x38\x00\x17\xca\xff\xdd\xd5\xfe\x9e\xff\x74\x00" - b"\xb8\xf4\x46\x00\x00\x2f\xe6\xff\x74\x23\xff\x7a" - b"\x00\x00\x00\x00\x53\xff\x74\x4a\xff\x40\x00\x00" - b"\x00\x00\x19\xff\x74\x3e\xff\x52\x00\x00\x00\x00" - b"\x2b\xff\x74\x0a\xf1\xba\x01\x00\x00\x00\xa2\xff" - b"\x74\x00\x6a\xff\xba\x54\x4d\xa2\xff\xff\x74\x00" - b"\x00\x5a\xd7\xff\xfe\xc3\x42\xff\x74\x00\x00\x00" - b"\x00\x0a\x08\x00\x14\xff\x74\x00\x00\x00\x00\x00" - b"\x00\x00\x14\xff\x74\x00\x00\x00\x00\x00\x00\x00" - b"\x14\xff\x74\x00\x00\x00\x00\x00\x00\x00\x02\x1c" - b"\x0c\x43\x72\x02\x4b\x81\x02\x8c\xec\xae\xff\xf1" - b"\x03\x8c\xff\xd5\x2e\x00\x00\x8c\xff\x3d\x00\x00" - b"\x00\x8c\xfe\x08\x00\x00\x00\x8c\xfc\x00\x00\x00" - b"\x00\x8c\xfc\x00\x00\x00\x00\x8c\xfc\x00\x00\x00" - b"\x00\x8c\xfc\x00\x00\x00\x00\x00\x01\x49\x81\x85" - b"\x66\x28\x00\x02\xb9\xff\xd3\xcb\xf0\xf9\x17\x42" - b"\xff\x5d\x00\x00\x07\x3a\x00\x45\xff\x82\x07\x00" - b"\x00\x00\x00\x04\xa3\xff\xf8\xc9\x91\x2f\x00\x00" - b"\x00\x22\x62\x92\xe1\xf9\x3c\x00\x03\x00\x00\x00" - b"\x11\xff\x83\x4a\xd7\x7a\x4f\x46\x96\xff\x4e\x2c" - b"\xaa\xf0\xff\xff\xde\x67\x00\x00\x00\x01\x11\x0c" - b"\x00\x00\x00\x00\x1b\x58\x13\x00\x00\x00\x00\x50" - b"\xff\x38\x00\x00\x00\x5e\xcc\xff\xb4\x7c\x4f\x00" - b"\x99\xff\xff\xff\xc8\x80\x00\x00\x50\xff\x38\x00" - b"\x00\x00\x00\x50\xff\x38\x00\x00\x00\x00\x50\xff" - b"\x38\x00\x00\x00\x00\x50\xff\x38\x00\x00\x00\x00" - b"\x49\xff\x3f\x00\x00\x00\x00\x1e\xfc\xb1\x3d\x60" - b"\x00\x00\x00\x69\xf1\xff\xdb\x11\x00\x00\x00\x01" - b"\x0e\x00\x00\x4d\x70\x00\x00\x00\x00\x15\x7c\x2c" - b"\xa0\xe8\x00\x00\x00\x00\x2c\xff\x5c\xa0\xe8\x00" - b"\x00\x00\x00\x2c\xff\x5c\xa0\xe8\x00\x00\x00\x00" - b"\x2c\xff\x5c\xa0\xe8\x00\x00\x00\x00\x2c\xff\x5c" - b"\x9b\xf2\x00\x00\x00\x00\x3d\xff\x5c\x7c\xff\x2e" - b"\x00\x00\x00\x97\xff\x5c\x24\xf7\xde\x67\x51\x9a" - b"\xff\xff\x5c\x00\x45\xd4\xff\xff\xca\x4b\xff\x5c" - b"\x00\x00\x00\x0b\x0a\x00\x00\x00\x00\x72\x52\x00" - b"\x00\x00\x00\x00\x51\x68\x9a\xf0\x0b\x00\x00\x00" - b"\x0b\xef\x85\x2a\xff\x69\x00\x00\x00\x6a\xfa\x1b" - b"\x00\xba\xd6\x00\x00\x01\xd8\xa6\x00\x00\x49\xff" - b"\x45\x00\x4a\xff\x36\x00\x00\x01\xd7\xb3\x00\xba" - b"\xc6\x00\x00\x00\x00\x69\xfd\x4d\xff\x57\x00\x00" - b"\x00\x00\x0a\xee\xff\xe3\x04\x00\x00\x00\x00\x00" - b"\x88\xff\x77\x00\x00\x00\x65\x4e\x00\x00\x00\x00" - b"\x3f\x76\x00\x00\x00\x00\x17\x7c\x17\x8a\xe5\x02" - b"\x00\x00\x00\xcc\xff\x3c\x00\x00\x00\x76\xe6\x03" - b"\x2b\xff\x45\x00\x00\x2d\xff\xff\x9b\x00\x00\x00" - b"\xd5\x8a\x00\x00\xcc\xa3\x00\x00\x8f\xd6\x74\xf1" - b"\x09\x00\x34\xff\x2b\x00\x00\x6d\xf4\x0c\x05\xea" - b"\x73\x17\xfb\x58\x00\x94\xcc\x00\x00\x00\x13\xf9" - b"\x5e\x51\xfa\x16\x00\xb1\xb7\x06\xec\x6c\x00\x00" - b"\x00\x00\xae\xbc\xb2\xae\x00\x00\x50\xfc\x6c\xfa" - b"\x13\x00\x00\x00\x00\x4f\xff\xff\x4b\x00\x00\x04" - b"\xe9\xff\xae\x00\x00\x00\x00\x00\x05\xfa\xf6\x03" - b"\x00\x00\x00\x8d\xff\x4f\x00\x00\x00\x34\x7c\x22" - b"\x00\x00\x00\x36\x7c\x1f\x09\xd1\xce\x08\x00\x18" - b"\xe7\xac\x00\x00\x25\xf1\x99\x03\xbe\xd8\x0d\x00" - b"\x00\x00\x54\xff\xdf\xf4\x2b\x00\x00\x00\x00\x00" - b"\xc4\xff\x82\x00\x00\x00\x00\x00\x23\xf0\xff\xd5" - b"\x0b\x00\x00\x00\x09\xd0\xcf\x23\xea\xa8\x00\x00" - b"\x00\x9f\xf0\x23\x00\x47\xfe\x70\x00\x65\xff\x52" - b"\x00\x00\x00\x84\xfb\x3c\x00\x72\x52\x00\x00\x00" - b"\x00\x00\x50\x68\x00\x99\xf0\x0c\x00\x00\x00\x0a" - b"\xee\x84\x00\x28\xfe\x6d\x00\x00\x00\x68\xfa\x19" - b"\x00\x00\xb6\xda\x01\x00\x00\xd5\xa3\x00\x00\x00" - b"\x44\xff\x4c\x00\x45\xff\x32\x00\x00\x00\x00\xd2" - b"\xbc\x00\xb3\xc1\x00\x00\x00\x00\x00\x62\xff\x50" - b"\xfd\x50\x00\x00\x00\x00\x00\x07\xe9\xff\xdd\x02" - b"\x00\x00\x00\x00\x00\x00\x98\xff\x6f\x00\x00\x00" - b"\x00\x00\x00\x00\x93\xf1\x0d\x00\x00\x00\x03\x7d" - b"\x2d\x4d\xf5\x83\x00\x00\x00\x00\x23\xe1\xff\xff" - b"\xae\x07\x00\x00\x00\x00\x00\x02\x24\x14\x00\x00" - b"\x00\x00\x00\x00\x20\x7c\x7c\x7c\x7c\x7c\x7c\x4b" - b"\x35\xc8\xc8\xc8\xc8\xf4\xff\x85\x00\x00\x00\x00" - b"\x11\xdc\xcb\x08\x00\x00\x00\x02\xb8\xe8\x1c\x00" - b"\x00\x00\x00\x89\xfa\x3c\x00\x00\x00\x00\x57\xff" - b"\x67\x00\x00\x00\x00\x2f\xf5\x98\x00\x00\x00\x00" - b"\x13\xe4\xff\x49\x44\x44\x44\x33\x5b\xff\xff\xff" - b"\xff\xff\xff\xc0\x00\x00\x18\xa6\xd9\x46\x00\x00" - b"\x9e\xfb\x7e\x1f\x00\x00\xcb\xc3\x00\x00\x00\x00" - b"\xd0\xbc\x00\x00\x00\x00\xd0\xbc\x00\x00\x00\x00" - b"\xd0\xbc\x00\x00\x00\x14\xe9\xaa\x00\x00\x18\xff" - b"\xff\x41\x00\x00\x06\x4e\xf4\x9c\x00\x00\x00\x00" - b"\xd0\xbb\x00\x00\x00\x00\xd0\xbc\x00\x00\x00\x00" - b"\xd0\xbc\x00\x00\x00\x00\xce\xbe\x00\x00\x00\x00" - b"\xaf\xef\x41\x0c\x00\x00\x32\xe2\xff\x50\x00\x00" - b"\x00\x00\x15\x08\x46\xe0\x18\x50\xff\x1c\x50\xff" - b"\x1c\x50\xff\x1c\x50\xff\x1c\x50\xff\x1c\x50\xff" - b"\x1c\x50\xff\x1c\x50\xff\x1c\x50\xff\x1c\x50\xff" - b"\x1c\x50\xff\x1c\x50\xff\x1c\x50\xff\x1c\x50\xff" - b"\x1c\x08\x1c\x03\x9d\xce\x72\x00\x00\x48\xb2\xff" - b"\x3c\x00\x00\x1f\xff\x6b\x00\x00\x18\xff\x70\x00" - b"\x00\x18\xff\x70\x00\x00\x18\xff\x70\x00\x00\x0b" - b"\xff\x97\x06\x00\x00\xa1\xff\xb0\x00\x05\xf7\xb8" - b"\x2d\x00\x17\xff\x70\x00\x00\x18\xff\x70\x00\x00" - b"\x18\xff\x70\x00\x00\x1b\xff\x6e\x00\x1d\x7f\xff" - b"\x4e\x00\xb4\xff\xae\x03\x00\x13\x0b\x00\x00\x00" - b"\x00\x40\xd9\xdc\x6b\x00\x00\x9e\x49\x00\xdd\x7b" - b"\x5a\xe7\xb7\x85\xf4\x1d\x05\xa0\x01\x00\x17\x92" - b"\xb4\x4f\x00\x00\x11\x4d\x6f\x6e\x74\x73\x65\x72" - b"\x72\x61\x74\x20\x4d\x65\x64\x69\x75\x6d\x00\x11" - b"\x4d\x6f\x6e\x74\x73\x65\x72\x72\x61\x74\x2d\x4d" - b"\x65\x64\x69\x75\x6d\x01" -) diff --git a/m5stack/fs/system/common/font/MontserratMedium18.py b/m5stack/fs/system/common/font/MontserratMedium18.py deleted file mode 100644 index aa81b87d..00000000 --- a/m5stack/fs/system/common/font/MontserratMedium18.py +++ /dev/null @@ -1,1205 +0,0 @@ -FONT = ( - b"\x00\x00\x00\x63\x00\x00\x00\x0b\x00\x00\x00\x12" - b"\x00\x00\x00\x00\x00\x00\x00\x0e\x00\x00\x00\x04" - b"\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x01" - b"\x00\x00\x00\x00\x00\x00\x00\x24\xff\xff\xff\xee" - b"\x00\x00\x00\x00\x00\x00\x00\x09\x00\x00\x00\x01" - b"\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x24" - b"\xff\xff\xff\xee\x00\x00\x00\x00\x00\x00\x00\x0a" - b"\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x00" - b"\x00\x00\x00\x24\xff\xff\xff\xee\x00\x00\x00\x00" - b"\x00\x00\x00\x0d\x00\x00\x00\x01\x00\x00\x00\x01" - b"\x00\x00\x00\x00\x00\x00\x00\x24\xff\xff\xff\xee" - b"\x00\x00\x00\x00\x00\x00\x00\x20\x00\x00\x00\x01" - b"\x00\x00\x00\x01\x00\x00\x00\x05\x00\x00\x00\x24" - b"\xff\xff\xff\xee\x00\x00\x00\x00\x00\x00\x00\x21" - b"\x00\x00\x00\x0e\x00\x00\x00\x03\x00\x00\x00\x05" - b"\x00\x00\x00\x0d\x00\x00\x00\x01\x00\x00\x00\x00" - b"\x00\x00\x00\x22\x00\x00\x00\x06\x00\x00\x00\x05" - b"\x00\x00\x00\x07\x00\x00\x00\x0d\x00\x00\x00\x01" - b"\x00\x00\x00\x00\x00\x00\x00\x23\x00\x00\x00\x0d" - b"\x00\x00\x00\x0d\x00\x00\x00\x0d\x00\x00\x00\x0d" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x24" - b"\x00\x00\x00\x12\x00\x00\x00\x0b\x00\x00\x00\x0b" - b"\x00\x00\x00\x0f\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x25\x00\x00\x00\x0e\x00\x00\x00\x0f" - b"\x00\x00\x00\x0f\x00\x00\x00\x0d\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x26\x00\x00\x00\x0e" - b"\x00\x00\x00\x0d\x00\x00\x00\x0c\x00\x00\x00\x0d" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x27" - b"\x00\x00\x00\x06\x00\x00\x00\x02\x00\x00\x00\x04" - b"\x00\x00\x00\x0d\x00\x00\x00\x01\x00\x00\x00\x00" - b"\x00\x00\x00\x28\x00\x00\x00\x12\x00\x00\x00\x05" - b"\x00\x00\x00\x06\x00\x00\x00\x0e\x00\x00\x00\x01" - b"\x00\x00\x00\x00\x00\x00\x00\x29\x00\x00\x00\x12" - b"\x00\x00\x00\x05\x00\x00\x00\x06\x00\x00\x00\x0e" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x2a" - b"\x00\x00\x00\x08\x00\x00\x00\x07\x00\x00\x00\x07" - b"\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x2b\x00\x00\x00\x09\x00\x00\x00\x09" - b"\x00\x00\x00\x0a\x00\x00\x00\x0b\x00\x00\x00\x01" - b"\x00\x00\x00\x00\x00\x00\x00\x2c\x00\x00\x00\x06" - b"\x00\x00\x00\x04\x00\x00\x00\x04\x00\x00\x00\x03" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x2d" - b"\x00\x00\x00\x02\x00\x00\x00\x05\x00\x00\x00\x07" - b"\x00\x00\x00\x06\x00\x00\x00\x01\x00\x00\x00\x00" - b"\x00\x00\x00\x2e\x00\x00\x00\x04\x00\x00\x00\x04" - b"\x00\x00\x00\x04\x00\x00\x00\x03\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x2f\x00\x00\x00\x12" - b"\x00\x00\x00\x08\x00\x00\x00\x06\x00\x00\x00\x10" - b"\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x30" - b"\x00\x00\x00\x0e\x00\x00\x00\x0c\x00\x00\x00\x0c" - b"\x00\x00\x00\x0d\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x31\x00\x00\x00\x0d\x00\x00\x00\x05" - b"\x00\x00\x00\x07\x00\x00\x00\x0d\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x32\x00\x00\x00\x0d" - b"\x00\x00\x00\x0a\x00\x00\x00\x0a\x00\x00\x00\x0d" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x33" - b"\x00\x00\x00\x0e\x00\x00\x00\x0a\x00\x00\x00\x0a" - b"\x00\x00\x00\x0d\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x34\x00\x00\x00\x0d\x00\x00\x00\x0c" - b"\x00\x00\x00\x0c\x00\x00\x00\x0d\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x35\x00\x00\x00\x0e" - b"\x00\x00\x00\x0a\x00\x00\x00\x0a\x00\x00\x00\x0d" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x36" - b"\x00\x00\x00\x0e\x00\x00\x00\x0b\x00\x00\x00\x0b" - b"\x00\x00\x00\x0d\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x37\x00\x00\x00\x0d\x00\x00\x00\x0b" - b"\x00\x00\x00\x0b\x00\x00\x00\x0d\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x38\x00\x00\x00\x0e" - b"\x00\x00\x00\x0b\x00\x00\x00\x0c\x00\x00\x00\x0d" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x39" - b"\x00\x00\x00\x0e\x00\x00\x00\x0b\x00\x00\x00\x0b" - b"\x00\x00\x00\x0d\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x3a\x00\x00\x00\x0b\x00\x00\x00\x04" - b"\x00\x00\x00\x04\x00\x00\x00\x0a\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x3b\x00\x00\x00\x0d" - b"\x00\x00\x00\x04\x00\x00\x00\x04\x00\x00\x00\x0a" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x3c" - b"\x00\x00\x00\x09\x00\x00\x00\x09\x00\x00\x00\x0a" - b"\x00\x00\x00\x0b\x00\x00\x00\x01\x00\x00\x00\x00" - b"\x00\x00\x00\x3d\x00\x00\x00\x07\x00\x00\x00\x09" - b"\x00\x00\x00\x0a\x00\x00\x00\x0a\x00\x00\x00\x01" - b"\x00\x00\x00\x00\x00\x00\x00\x3e\x00\x00\x00\x09" - b"\x00\x00\x00\x09\x00\x00\x00\x0a\x00\x00\x00\x0b" - b"\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x3f" - b"\x00\x00\x00\x0e\x00\x00\x00\x0a\x00\x00\x00\x0a" - b"\x00\x00\x00\x0d\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x40\x00\x00\x00\x11\x00\x00\x00\x12" - b"\x00\x00\x00\x13\x00\x00\x00\x0d\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x41\x00\x00\x00\x0d" - b"\x00\x00\x00\x0e\x00\x00\x00\x0d\x00\x00\x00\x0d" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x42" - b"\x00\x00\x00\x0d\x00\x00\x00\x0c\x00\x00\x00\x0e" - b"\x00\x00\x00\x0d\x00\x00\x00\x01\x00\x00\x00\x00" - b"\x00\x00\x00\x43\x00\x00\x00\x0e\x00\x00\x00\x0d" - b"\x00\x00\x00\x0d\x00\x00\x00\x0d\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x44\x00\x00\x00\x0d" - b"\x00\x00\x00\x0d\x00\x00\x00\x0f\x00\x00\x00\x0d" - b"\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x45" - b"\x00\x00\x00\x0d\x00\x00\x00\x0b\x00\x00\x00\x0c" - b"\x00\x00\x00\x0d\x00\x00\x00\x01\x00\x00\x00\x00" - b"\x00\x00\x00\x46\x00\x00\x00\x0d\x00\x00\x00\x0a" - b"\x00\x00\x00\x0b\x00\x00\x00\x0d\x00\x00\x00\x01" - b"\x00\x00\x00\x00\x00\x00\x00\x47\x00\x00\x00\x0e" - b"\x00\x00\x00\x0d\x00\x00\x00\x0e\x00\x00\x00\x0d" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x48" - b"\x00\x00\x00\x0d\x00\x00\x00\x0c\x00\x00\x00\x0f" - b"\x00\x00\x00\x0d\x00\x00\x00\x01\x00\x00\x00\x00" - b"\x00\x00\x00\x49\x00\x00\x00\x0d\x00\x00\x00\x03" - b"\x00\x00\x00\x06\x00\x00\x00\x0d\x00\x00\x00\x01" - b"\x00\x00\x00\x00\x00\x00\x00\x4a\x00\x00\x00\x0e" - b"\x00\x00\x00\x09\x00\x00\x00\x09\x00\x00\x00\x0d" - b"\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x4b" - b"\x00\x00\x00\x0d\x00\x00\x00\x0c\x00\x00\x00\x0d" - b"\x00\x00\x00\x0d\x00\x00\x00\x01\x00\x00\x00\x00" - b"\x00\x00\x00\x4c\x00\x00\x00\x0d\x00\x00\x00\x0a" - b"\x00\x00\x00\x0b\x00\x00\x00\x0d\x00\x00\x00\x01" - b"\x00\x00\x00\x00\x00\x00\x00\x4d\x00\x00\x00\x0d" - b"\x00\x00\x00\x0f\x00\x00\x00\x11\x00\x00\x00\x0d" - b"\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x4e" - b"\x00\x00\x00\x0d\x00\x00\x00\x0c\x00\x00\x00\x0f" - b"\x00\x00\x00\x0d\x00\x00\x00\x01\x00\x00\x00\x00" - b"\x00\x00\x00\x4f\x00\x00\x00\x0e\x00\x00\x00\x0f" - b"\x00\x00\x00\x0f\x00\x00\x00\x0d\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x50\x00\x00\x00\x0d" - b"\x00\x00\x00\x0c\x00\x00\x00\x0d\x00\x00\x00\x0d" - b"\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x51" - b"\x00\x00\x00\x10\x00\x00\x00\x0f\x00\x00\x00\x0f" - b"\x00\x00\x00\x0d\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x52\x00\x00\x00\x0d\x00\x00\x00\x0c" - b"\x00\x00\x00\x0d\x00\x00\x00\x0d\x00\x00\x00\x01" - b"\x00\x00\x00\x00\x00\x00\x00\x53\x00\x00\x00\x0e" - b"\x00\x00\x00\x0b\x00\x00\x00\x0b\x00\x00\x00\x0d" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x54" - b"\x00\x00\x00\x0d\x00\x00\x00\x0b\x00\x00\x00\x0b" - b"\x00\x00\x00\x0d\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x55\x00\x00\x00\x0e\x00\x00\x00\x0c" - b"\x00\x00\x00\x0e\x00\x00\x00\x0d\x00\x00\x00\x01" - b"\x00\x00\x00\x00\x00\x00\x00\x56\x00\x00\x00\x0d" - b"\x00\x00\x00\x0d\x00\x00\x00\x0d\x00\x00\x00\x0d" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x57" - b"\x00\x00\x00\x0d\x00\x00\x00\x14\x00\x00\x00\x14" - b"\x00\x00\x00\x0d\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x58\x00\x00\x00\x0d\x00\x00\x00\x0c" - b"\x00\x00\x00\x0c\x00\x00\x00\x0d\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x59\x00\x00\x00\x0d" - b"\x00\x00\x00\x0d\x00\x00\x00\x0c\x00\x00\x00\x0d" - b"\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x5a" - b"\x00\x00\x00\x0d\x00\x00\x00\x0c\x00\x00\x00\x0c" - b"\x00\x00\x00\x0d\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x5b\x00\x00\x00\x12\x00\x00\x00\x05" - b"\x00\x00\x00\x06\x00\x00\x00\x0e\x00\x00\x00\x01" - b"\x00\x00\x00\x00\x00\x00\x00\x5c\x00\x00\x00\x12" - b"\x00\x00\x00\x08\x00\x00\x00\x06\x00\x00\x00\x10" - b"\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x5d" - b"\x00\x00\x00\x12\x00\x00\x00\x05\x00\x00\x00\x06" - b"\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x5e\x00\x00\x00\x09\x00\x00\x00\x09" - b"\x00\x00\x00\x0b\x00\x00\x00\x0b\x00\x00\x00\x01" - b"\x00\x00\x00\x00\x00\x00\x00\x5f\x00\x00\x00\x02" - b"\x00\x00\x00\x09\x00\x00\x00\x09\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x60" - b"\x00\x00\x00\x04\x00\x00\x00\x05\x00\x00\x00\x0b" - b"\x00\x00\x00\x0e\x00\x00\x00\x02\x00\x00\x00\x00" - b"\x00\x00\x00\x61\x00\x00\x00\x0b\x00\x00\x00\x0a" - b"\x00\x00\x00\x0b\x00\x00\x00\x0a\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x62\x00\x00\x00\x0f" - b"\x00\x00\x00\x0b\x00\x00\x00\x0c\x00\x00\x00\x0e" - b"\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x63" - b"\x00\x00\x00\x0b\x00\x00\x00\x0a\x00\x00\x00\x0a" - b"\x00\x00\x00\x0a\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x64\x00\x00\x00\x0f\x00\x00\x00\x0b" - b"\x00\x00\x00\x0c\x00\x00\x00\x0e\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x65\x00\x00\x00\x0b" - b"\x00\x00\x00\x0b\x00\x00\x00\x0b\x00\x00\x00\x0a" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x66" - b"\x00\x00\x00\x0e\x00\x00\x00\x07\x00\x00\x00\x06" - b"\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x67\x00\x00\x00\x0e\x00\x00\x00\x0b" - b"\x00\x00\x00\x0c\x00\x00\x00\x0a\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x68\x00\x00\x00\x0e" - b"\x00\x00\x00\x0a\x00\x00\x00\x0c\x00\x00\x00\x0e" - b"\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x69" - b"\x00\x00\x00\x0e\x00\x00\x00\x03\x00\x00\x00\x05" - b"\x00\x00\x00\x0e\x00\x00\x00\x01\x00\x00\x00\x00" - b"\x00\x00\x00\x6a\x00\x00\x00\x12\x00\x00\x00\x06" - b"\x00\x00\x00\x05\x00\x00\x00\x0e\xff\xff\xff\xfe" - b"\x00\x00\x00\x00\x00\x00\x00\x6b\x00\x00\x00\x0e" - b"\x00\x00\x00\x0a\x00\x00\x00\x0b\x00\x00\x00\x0e" - b"\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x6c" - b"\x00\x00\x00\x0e\x00\x00\x00\x03\x00\x00\x00\x05" - b"\x00\x00\x00\x0e\x00\x00\x00\x01\x00\x00\x00\x00" - b"\x00\x00\x00\x6d\x00\x00\x00\x0a\x00\x00\x00\x11" - b"\x00\x00\x00\x13\x00\x00\x00\x0a\x00\x00\x00\x01" - b"\x00\x00\x00\x00\x00\x00\x00\x6e\x00\x00\x00\x0a" - b"\x00\x00\x00\x0a\x00\x00\x00\x0c\x00\x00\x00\x0a" - b"\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x6f" - b"\x00\x00\x00\x0b\x00\x00\x00\x0b\x00\x00\x00\x0b" - b"\x00\x00\x00\x0a\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x70\x00\x00\x00\x0e\x00\x00\x00\x0b" - b"\x00\x00\x00\x0c\x00\x00\x00\x0a\x00\x00\x00\x01" - b"\x00\x00\x00\x00\x00\x00\x00\x71\x00\x00\x00\x0e" - b"\x00\x00\x00\x0b\x00\x00\x00\x0c\x00\x00\x00\x0a" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x72" - b"\x00\x00\x00\x0a\x00\x00\x00\x06\x00\x00\x00\x07" - b"\x00\x00\x00\x0a\x00\x00\x00\x01\x00\x00\x00\x00" - b"\x00\x00\x00\x73\x00\x00\x00\x0b\x00\x00\x00\x09" - b"\x00\x00\x00\x09\x00\x00\x00\x0a\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x74\x00\x00\x00\x0d" - b"\x00\x00\x00\x07\x00\x00\x00\x07\x00\x00\x00\x0c" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x75" - b"\x00\x00\x00\x0b\x00\x00\x00\x0a\x00\x00\x00\x0c" - b"\x00\x00\x00\x0a\x00\x00\x00\x01\x00\x00\x00\x00" - b"\x00\x00\x00\x76\x00\x00\x00\x0a\x00\x00\x00\x0b" - b"\x00\x00\x00\x0a\x00\x00\x00\x0a\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x77\x00\x00\x00\x0a" - b"\x00\x00\x00\x11\x00\x00\x00\x10\x00\x00\x00\x0a" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x78" - b"\x00\x00\x00\x0a\x00\x00\x00\x0a\x00\x00\x00\x0a" - b"\x00\x00\x00\x0a\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x79\x00\x00\x00\x0e\x00\x00\x00\x0c" - b"\x00\x00\x00\x0a\x00\x00\x00\x0a\xff\xff\xff\xff" - b"\x00\x00\x00\x00\x00\x00\x00\x7a\x00\x00\x00\x0a" - b"\x00\x00\x00\x09\x00\x00\x00\x09\x00\x00\x00\x0a" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x7b" - b"\x00\x00\x00\x12\x00\x00\x00\x05\x00\x00\x00\x06" - b"\x00\x00\x00\x0e\x00\x00\x00\x01\x00\x00\x00\x00" - b"\x00\x00\x00\x7c\x00\x00\x00\x12\x00\x00\x00\x03" - b"\x00\x00\x00\x05\x00\x00\x00\x0e\x00\x00\x00\x01" - b"\x00\x00\x00\x00\x00\x00\x00\x7d\x00\x00\x00\x12" - b"\x00\x00\x00\x06\x00\x00\x00\x06\x00\x00\x00\x0e" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x7e" - b"\x00\x00\x00\x04\x00\x00\x00\x09\x00\x00\x00\x0a" - b"\x00\x00\x00\x08\x00\x00\x00\x01\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x58\x98\x40\x8e\xff\x64\x84" - b"\xff\x5a\x7a\xff\x50\x70\xff\x46\x67\xff\x3b\x5d" - b"\xff\x31\x53\xff\x27\x49\xff\x1d\x01\x04\x00\x04" - b"\x27\x00\x9c\xff\x6e\x8b\xff\x5e\x00\x0f\x00\x82" - b"\x60\x00\x82\x60\xd7\x9c\x00\xd7\x9c\xd2\x95\x00" - b"\xd2\x95\xcd\x8f\x00\xcd\x8f\xc8\x88\x00\xc8\x88" - b"\x34\x23\x00\x34\x23\x00\x00\x00\x00\x88\x3a\x00" - b"\x00\x24\x98\x06\x00\x00\x00\x00\x00\x03\xfb\x49" - b"\x00\x00\x57\xf1\x00\x00\x00\x00\x00\x00\x1f\xff" - b"\x29\x00\x00\x76\xd1\x00\x00\x00\x00\x56\x5c\x9b" - b"\xff\x66\x5c\x5c\xf2\xff\x5c\x5c\x0f\x00\xe4\xf4" - b"\xff\xff\xf4\xf4\xf4\xff\xff\xf4\xf4\x29\x00\x00" - b"\x00\x7e\xca\x00\x00\x00\xd4\x73\x00\x00\x00\x00" - b"\x00\x00\x9e\xaa\x00\x00\x00\xf3\x54\x00\x00\x00" - b"\x00\x00\x00\xbe\x8a\x00\x00\x13\xff\x34\x00\x00" - b"\x00\x48\x8c\x8c\xff\xf6\x8c\x8c\xbf\xff\xa1\x8c" - b"\x53\x00\x65\xc4\xc6\xff\xff\xc4\xc4\xff\xff\xc4" - b"\xc4\x74\x00\x00\x00\x1d\xff\x2b\x00\x00\x72\xd6" - b"\x00\x00\x00\x00\x00\x00\x3d\xff\x0b\x00\x00\x91" - b"\xb6\x00\x00\x00\x00\x00\x00\x5c\xeb\x00\x00\x00" - b"\xb0\x97\x00\x00\x00\x00\x00\x00\x00\x00\x00\xae" - b"\x34\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe4\x44" - b"\x00\x00\x00\x00\x00\x00\x05\x5b\x9e\xff\xf8\x90" - b"\x4b\x04\x00\x00\x15\xd2\xff\xf1\xff\xff\xfd\xff" - b"\xb6\x00\x00\xa3\xff\x77\x04\xe4\x44\x0b\x5f\x5f" - b"\x00\x00\xe7\xdf\x00\x00\xe4\x44\x00\x00\x00\x00" - b"\x00\xdf\xf3\x18\x00\xe4\x44\x00\x00\x00\x00\x00" - b"\x7e\xff\xed\x7e\xff\x44\x00\x00\x00\x00\x00\x02" - b"\x73\xe5\xff\xff\xff\xa1\x3d\x00\x00\x00\x00\x00" - b"\x05\x44\xff\xff\xff\xff\x99\x00\x00\x00\x00\x00" - b"\x00\xe4\x44\x34\xd7\xff\x3d\x00\x00\x00\x00\x00" - b"\xe4\x44\x00\x60\xff\x67\x00\x8c\x27\x00\x00\xe4" - b"\x44\x00\x9a\xff\x42\x19\xf4\xfd\xba\x7e\xff\xbb" - b"\xbc\xff\xbb\x01\x00\x21\x90\xdf\xff\xff\xff\xe3" - b"\x85\x0a\x00\x00\x00\x00\x00\x0e\xff\x55\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\xe4\x44\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x23\x0a\x00\x00\x00\x00" - b"\x00\x06\x74\xaa\x87\x14\x00\x00\x00\x00\x07\x90" - b"\x39\x00\x00\x00\xab\xc5\x5e\xa7\xd5\x07\x00\x00" - b"\x00\x8b\xcf\x05\x00\x00\x25\xfb\x17\x00\x02\xdd" - b"\x54\x00\x00\x3c\xf6\x2d\x00\x00\x00\x4c\xe0\x00" - b"\x00\x00\xad\x7c\x00\x0b\xdd\x77\x00\x00\x00\x00" - b"\x3f\xef\x01\x00\x00\xbd\x6e\x00\x98\xc5\x03\x00" - b"\x00\x00\x00\x07\xe9\x66\x00\x36\xf7\x25\x48\xf4" - b"\x24\x00\x00\x00\x00\x00\x00\x3f\xe7\xea\xf3\x66" - b"\x11\xe5\x6b\x0d\x87\xbb\x94\x19\x00\x00\x00\x05" - b"\x23\x0b\x00\xa5\xbb\x01\xbc\xb8\x52\xa0\xda\x08" - b"\x00\x00\x00\x00\x00\x54\xf0\x1d\x2d\xf6\x0f\x00" - b"\x02\xde\x57\x00\x00\x00\x00\x17\xeb\x5f\x00\x51" - b"\xd7\x00\x00\x00\xb0\x7c\x00\x00\x00\x00\xb2\xb0" - b"\x00\x00\x40\xeb\x01\x00\x00\xc6\x6b\x00\x00\x00" - b"\x61\xeb\x16\x00\x00\x07\xe8\x6d\x01\x4a\xf7\x1f" - b"\x00\x00\x1e\xf1\x53\x00\x00\x00\x00\x3c\xe0\xf8" - b"\xec\x56\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x01\x14\x03\x00\x00\x00\x00\x00\x1b\x82\xac" - b"\x9f\x50\x00\x00\x00\x00\x00\x00\x00\x22\xed\xe4" - b"\x97\xb5\xff\x6f\x00\x00\x00\x00\x00\x00\x8a\xff" - b"\x1e\x00\x00\xb0\xd4\x00\x00\x00\x00\x00\x00\x94" - b"\xfd\x0c\x00\x00\xb9\xcb\x00\x00\x00\x00\x00\x00" - b"\x40\xff\x95\x09\x96\xff\x55\x00\x00\x00\x00\x00" - b"\x00\x00\x92\xff\xff\xef\x57\x00\x00\x00\x00\x00" - b"\x00\x00\x26\xc8\xff\xff\x97\x00\x00\x00\x00\x00" - b"\x00\x00\x36\xf0\xcf\x2d\x9c\xff\x81\x00\x0d\xd2" - b"\x40\x00\x01\xda\xdb\x0c\x00\x01\x9b\xff\x83\x56" - b"\xff\x2f\x00\x24\xff\x8a\x00\x00\x00\x01\x9a\xff" - b"\xff\xce\x00\x00\x15\xfe\xbc\x01\x00\x00\x00\x07" - b"\xff\xff\xc7\x00\x00\x00\x9c\xff\xbd\x6f\x54\x76" - b"\xd5\xfd\xff\xff\x86\x00\x00\x03\x79\xe4\xff\xff" - b"\xfa\xb3\x37\x01\x98\xb0\x01\x00\x00\x00\x00\x14" - b"\x1b\x03\x00\x00\x00\x01\x08\x00\x82\x60\xd7\x9c" - b"\xd2\x95\xcd\x8f\xc8\x88\x34\x23\x00\x00\x15\x5c" - b"\x24\x00\x00\x97\xf8\x19\x00\x19\xfa\x9b\x00\x00" - b"\x6d\xff\x42\x00\x00\xbc\xf0\x04\x00\x00\xef\xc0" - b"\x00\x00\x1e\xff\x94\x00\x00\x35\xff\x7d\x00\x00" - b"\x44\xff\x6f\x00\x00\x45\xff\x6e\x00\x00\x36\xff" - b"\x7c\x00\x00\x22\xff\x90\x00\x00\x02\xf4\xba\x00" - b"\x00\x00\xc4\xea\x01\x00\x00\x78\xff\x38\x00\x00" - b"\x21\xfe\x8e\x00\x00\x00\xa7\xf1\x10\x00\x00\x21" - b"\x7c\x2d\x1e\x5c\x1c\x00\x00\x10\xf1\xad\x00\x00" - b"\x00\x89\xff\x2c\x00\x00\x30\xff\x85\x00\x00\x00" - b"\xe0\xd4\x00\x00\x00\xac\xfc\x09\x00\x00\x7e\xff" - b"\x33\x00\x00\x67\xff\x48\x00\x00\x58\xff\x57\x00" - b"\x00\x56\xff\x59\x00\x00\x65\xff\x4a\x00\x00\x79" - b"\xff\x37\x00\x00\xa6\xfe\x0d\x00\x00\xd8\xdb\x00" - b"\x00\x25\xff\x8f\x00\x00\x7b\xff\x38\x00\x09\xe8" - b"\xbd\x00\x00\x25\x7c\x2a\x00\x00\x00\x00\x00\x56" - b"\x0c\x00\x00\x07\x04\x00\xef\x22\x00\x0b\x71\xd5" - b"\x42\xed\x49\xba\xa4\x06\x71\xf4\xff\xff\x8f\x11" - b"\x06\x70\xf4\xff\xff\x8e\x11\x70\xd3\x41\xed\x48" - b"\xb8\xa3\x06\x03\x00\xef\x22\x00\x0a\x00\x00\x00" - b"\x56\x0c\x00\x00\x00\x00\x00\x1e\x3c\x00\x00\x00" - b"\x00\x00\x00\x00\x84\xff\x00\x00\x00\x00\x00\x00" - b"\x00\x84\xff\x00\x00\x00\x00\x00\x00\x00\x84\xff" - b"\x00\x00\x00\x00\xcc\xff\xff\xff\xff\xff\xff\xff" - b"\x44\x56\x6c\x6c\xf0\xff\x6c\x6c\x6c\x1c\x00\x00" - b"\x00\x84\xff\x00\x00\x00\x00\x00\x00\x00\x84\xff" - b"\x00\x00\x00\x00\x00\x00\x00\x54\xa4\x00\x00\x00" - b"\x00\x00\x2e\x38\x00\x11\xf8\xfe\x24\x08\xff\xff" - b"\x22\x00\x86\xd3\x00\x00\xc3\x7b\x00\x01\xa1\x22" - b"\x00\xaa\xb0\xb0\xb0\x9a\xc9\xd0\xd0\xd0\xb6\x00" - b"\x2f\x36\x00\x16\xfa\xfd\x23\x0b\xe3\xeb\x14\x00" - b"\x06\x08\x00\x00\x00\x00\x00\x00\x00\x17\x26\x00" - b"\x00\x00\x00\x00\x00\xcb\xc4\x00\x00\x00\x00\x00" - b"\x25\xff\x6b\x00\x00\x00\x00\x00\x7e\xfb\x15\x00" - b"\x00\x00\x00\x00\xd7\xb8\x00\x00\x00\x00\x00\x32" - b"\xff\x5e\x00\x00\x00\x00\x00\x8b\xf7\x0d\x00\x00" - b"\x00\x00\x01\xe3\xab\x00\x00\x00\x00\x00\x3f\xff" - b"\x51\x00\x00\x00\x00\x00\x98\xf0\x07\x00\x00\x00" - b"\x00\x05\xec\x9e\x00\x00\x00\x00\x00\x4c\xff\x44" - b"\x00\x00\x00\x00\x00\xa5\xe7\x03\x00\x00\x00\x00" - b"\x0a\xf4\x91\x00\x00\x00\x00\x00\x59\xff\x37\x00" - b"\x00\x00\x00\x00\xb2\xdd\x00\x00\x00\x00\x00\x11" - b"\xf9\x84\x00\x00\x00\x00\x00\x49\xcc\x29\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x14\x7b\xad\xae\x7c\x15" - b"\x00\x00\x00\x00\x00\x3c\xee\xff\xef\xee\xff\xef" - b"\x3f\x00\x00\x00\x17\xee\xf2\x4e\x00\x00\x4b\xf1" - b"\xef\x17\x00\x00\x8e\xff\x5c\x00\x00\x00\x00\x5b" - b"\xff\x8e\x00\x00\xe0\xf0\x03\x00\x00\x00\x00\x03" - b"\xf0\xdf\x00\x09\xff\xbe\x00\x00\x00\x00\x00\x00" - b"\xbf\xff\x08\x1d\xff\xac\x00\x00\x00\x00\x00\x00" - b"\xad\xff\x1d\x12\xff\xb6\x00\x00\x00\x00\x00\x00" - b"\xb7\xff\x11\x01\xf5\xd9\x00\x00\x00\x00\x00\x00" - b"\xd9\xf5\x00\x00\xb0\xff\x29\x00\x00\x00\x00\x28" - b"\xff\xaf\x00\x00\x44\xff\xbf\x08\x00\x00\x07\xbd" - b"\xff\x45\x00\x00\x00\x91\xff\xda\x86\x85\xd9\xff" - b"\x95\x00\x00\x00\x00\x00\x6a\xe0\xff\xff\xe1\x6d" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x13\x13\x00\x00" - b"\x00\x00\x00\x82\x98\x98\x98\x74\xd5\xf8\xfb\xff" - b"\xc4\x00\x00\x04\xff\xc4\x00\x00\x04\xff\xc4\x00" - b"\x00\x04\xff\xc4\x00\x00\x04\xff\xc4\x00\x00\x04" - b"\xff\xc4\x00\x00\x04\xff\xc4\x00\x00\x04\xff\xc4" - b"\x00\x00\x04\xff\xc4\x00\x00\x04\xff\xc4\x00\x00" - b"\x04\xff\xc4\x00\x00\x04\xff\xc4\x00\x04\x56\x9b" - b"\xb7\xb0\x82\x21\x00\x00\x28\xd9\xff\xfa\xe3\xf4" - b"\xff\xf7\x4c\x00\x5b\xf0\x6a\x0b\x00\x01\x5d\xfc" - b"\xe7\x05\x00\x0d\x00\x00\x00\x00\x00\xb7\xff\x24" - b"\x00\x00\x00\x00\x00\x00\x00\xb7\xff\x14\x00\x00" - b"\x00\x00\x00\x00\x22\xfa\xcc\x00\x00\x00\x00\x00" - b"\x00\x12\xd1\xf9\x37\x00\x00\x00\x00\x00\x15\xd0" - b"\xfb\x54\x00\x00\x00\x00\x00\x19\xd5\xfa\x52\x00" - b"\x00\x00\x00\x00\x1d\xda\xf8\x4b\x00\x00\x00\x00" - b"\x00\x21\xdf\xf5\x45\x00\x00\x00\x00\x00\x1f\xe3" - b"\xff\xce\x90\x90\x90\x90\x90\x67\x54\xff\xff\xff" - b"\xff\xff\xff\xff\xff\xb8\x31\x98\x98\x98\x98\x98" - b"\x98\x98\x95\x00\x51\xf8\xf8\xf8\xf8\xf8\xff\xff" - b"\xed\x00\x00\x00\x00\x00\x00\x01\xb2\xfd\x48\x00" - b"\x00\x00\x00\x00\x00\x85\xff\x75\x00\x00\x00\x00" - b"\x00\x00\x56\xff\xa4\x00\x00\x00\x00\x00\x00\x2d" - b"\xff\xff\x34\x00\x00\x00\x00\x00\x00\x70\xff\xff" - b"\xff\xd3\x31\x00\x00\x00\x00\x0c\x1e\x35\x83\xfb" - b"\xea\x11\x00\x00\x00\x00\x00\x00\x00\x88\xff\x5f" - b"\x00\x00\x00\x00\x00\x00\x00\x5f\xff\x72\x41\x64" - b"\x02\x00\x00\x00\x03\xba\xff\x41\xb1\xff\xdc\x96" - b"\x7a\x8b\xd8\xff\xba\x01\x0b\x77\xd2\xfe\xff\xff" - b"\xe9\x8a\x0a\x00\x00\x00\x00\x0a\x1e\x13\x01\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x12\x95\x7f\x01" - b"\x00\x00\x00\x00\x00\x00\x00\x02\xb8\xfb\x3e\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x83\xff\x73\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x4d\xfe\xac\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x23\xef\xd8\x0d\x00\x00\x00" - b"\x00\x00\x00\x00\x0a\xd1\xf4\x2b\x00\x19\x60\x2a" - b"\x00\x00\x00\x00\xa3\xff\x59\x00\x00\x44\xff\x70" - b"\x00\x00\x00\x6c\xff\x92\x00\x00\x00\x44\xff\x70" - b"\x00\x00\x2c\xfc\xff\xa9\xa4\xa4\xa4\xe9\xff\xff" - b"\xa4\x87\x48\xe8\xe8\xe8\xe8\xe8\xe8\xff\xff\xff" - b"\xe8\xc0\x00\x00\x00\x00\x00\x00\x00\x50\xff\x70" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x50\xff\x70" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x50\xff\x70" - b"\x00\x00\x00\x15\x98\x98\x98\x98\x98\x98\x98\x00" - b"\x00\x39\xff\xff\xf8\xf8\xf8\xf8\xf8\x00\x00\x53" - b"\xff\x57\x00\x00\x00\x00\x00\x00\x00\x6d\xff\x3e" - b"\x00\x00\x00\x00\x00\x00\x00\x87\xff\x24\x00\x00" - b"\x00\x00\x00\x00\x00\xa2\xff\x9f\x91\x81\x5d\x16" - b"\x00\x00\x00\xb8\xff\xfc\xff\xff\xff\xf7\x71\x00" - b"\x00\x00\x00\x00\x02\x17\x58\xe2\xff\x43\x00\x00" - b"\x00\x00\x00\x00\x00\x44\xff\x9b\x00\x00\x00\x00" - b"\x00\x00\x00\x27\xff\xa8\x18\x84\x0a\x00\x00\x00" - b"\x00\x89\xff\x74\x79\xff\xeb\xa1\x7c\x85\xc3\xff" - b"\xda\x0e\x01\x5c\xc2\xf9\xff\xff\xf2\x9d\x15\x00" - b"\x00\x00\x00\x06\x1b\x17\x02\x00\x00\x00\x00\x00" - b"\x00\x02\x4a\x97\xb5\xac\x88\x2d\x00\x00\x00\x1d" - b"\xc9\xff\xf6\xd4\xde\xfb\xb4\x00\x00\x08\xd5\xfb" - b"\x75\x08\x00\x00\x19\x20\x00\x00\x77\xff\x70\x00" - b"\x00\x00\x00\x00\x00\x00\x00\xd1\xf0\x07\x00\x00" - b"\x00\x00\x00\x00\x00\x04\xfe\xbd\x04\x57\x95\xa0" - b"\x81\x2c\x00\x00\x1b\xff\xb9\xc9\xff\xde\xe2\xfe" - b"\xfd\x77\x00\x16\xff\xff\xd6\x2a\x00\x00\x29\xd7" - b"\xff\x38\x03\xfb\xff\x3f\x00\x00\x00\x00\x49\xff" - b"\x89\x00\xc0\xff\x23\x00\x00\x00\x00\x2b\xff\x93" - b"\x00\x5e\xff\x84\x00\x00\x00\x00\x84\xff\x5e\x00" - b"\x00\xaf\xff\xaa\x5e\x5b\xa2\xfe\xca\x06\x00\x00" - b"\x03\x79\xe3\xff\xff\xf0\x94\x0f\x00\x00\x00\x00" - b"\x00\x00\x10\x1a\x03\x00\x00\x00\x44\x98\x98\x98" - b"\x98\x98\x98\x98\x98\x98\x07\x74\xff\xff\xf8\xf8" - b"\xf8\xf8\xf8\xff\xfb\x08\x74\xff\x44\x00\x00\x00" - b"\x00\x37\xff\xa7\x00\x74\xff\x44\x00\x00\x00\x00" - b"\xab\xff\x35\x00\x19\x38\x0e\x00\x00\x00\x21\xfc" - b"\xc2\x00\x00\x00\x00\x00\x00\x00\x00\x92\xff\x4f" - b"\x00\x00\x00\x00\x00\x00\x00\x11\xf4\xda\x02\x00" - b"\x00\x00\x00\x00\x00\x00\x79\xff\x6a\x00\x00\x00" - b"\x00\x00\x00\x00\x06\xe6\xed\x0a\x00\x00\x00\x00" - b"\x00\x00\x00\x60\xff\x84\x00\x00\x00\x00\x00\x00" - b"\x00\x00\xd2\xf9\x18\x00\x00\x00\x00\x00\x00\x00" - b"\x47\xff\x9f\x00\x00\x00\x00\x00\x00\x00\x00\xba" - b"\xff\x2d\x00\x00\x00\x00\x00\x00\x00\x03\x56\x9e" - b"\xb8\xb0\x84\x28\x00\x00\x00\x0e\xc7\xff\xeb\xc2" - b"\xce\xfd\xfc\x6a\x00\x00\x84\xff\x95\x06\x00\x00" - b"\x25\xdf\xf9\x1b\x00\xb9\xff\x19\x00\x00\x00\x00" - b"\x7a\xff\x4d\x00\x9e\xff\x3e\x00\x00\x00\x00\xa1" - b"\xff\x32\x00\x29\xee\xe7\x72\x4f\x54\x9e\xff\xab" - b"\x00\x00\x00\x79\xff\xff\xff\xff\xff\xff\x28\x00" - b"\x00\x88\xff\xbf\x4e\x29\x35\x72\xed\xef\x2a\x10" - b"\xfb\xd5\x05\x00\x00\x00\x00\x45\xff\xa0\x2b\xff" - b"\xa6\x00\x00\x00\x00\x00\x0f\xff\xc2\x0b\xf5\xe6" - b"\x15\x00\x00\x00\x00\x66\xff\x98\x00\x76\xff\xe3" - b"\x7f\x59\x65\xa4\xfc\xeb\x22\x00\x00\x59\xcd\xfe" - b"\xff\xff\xf0\xa4\x22\x00\x00\x00\x00\x00\x09\x1d" - b"\x16\x02\x00\x00\x00\x00\x00\x24\x86\xb3\xae\x87" - b"\x25\x00\x00\x00\x00\x5b\xf8\xfb\xc7\xc3\xf5\xfc" - b"\x6b\x00\x00\x1b\xf9\xda\x20\x00\x00\x0f\xb3\xff" - b"\x3e\x00\x67\xff\x5c\x00\x00\x00\x00\x19\xff\xbb" - b"\x00\x75\xff\x51\x00\x00\x00\x00\x09\xfe\xfa\x0a" - b"\x43\xff\xb1\x01\x00\x00\x00\x7a\xff\xff\x25\x00" - b"\xb6\xff\xcc\x7d\x72\xb5\xfe\xe7\xff\x36\x00\x07" - b"\x7e\xde\xfe\xfb\xc5\x4a\x9b\xff\x24\x00\x00\x00" - b"\x00\x04\x01\x00\x00\xc2\xfa\x0a\x00\x00\x00\x00" - b"\x00\x00\x00\x21\xfc\xb5\x00\x00\x00\x00\x00\x00" - b"\x00\x12\xc8\xfe\x3f\x00\x00\x66\xb9\x7a\x6e\x93" - b"\xec\xfe\x77\x00\x00\x00\x71\xe9\xff\xff\xfc\xbd" - b"\x47\x00\x00\x00\x00\x00\x00\x10\x1b\x04\x00\x00" - b"\x00\x00\x00\x00\x2f\x36\x00\x16\xfa\xfd\x23\x0b" - b"\xe3\xeb\x14\x00\x06\x08\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x2f\x36\x00\x16" - b"\xfa\xfd\x23\x0b\xe3\xeb\x14\x00\x06\x08\x00\x00" - b"\x2f\x36\x00\x16\xfa\xfd\x23\x0b\xe3\xeb\x14\x00" - b"\x06\x08\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x2e\x38\x00\x11\xf8\xfe\x24\x08" - b"\xff\xff\x22\x00\x86\xd3\x00\x00\xc3\x7b\x00\x01" - b"\xa1\x22\x00\x00\x00\x00\x00\x00\x00\x00\x08\x0e" - b"\x00\x00\x00\x00\x00\x2c\x90\xee\x44\x00\x00\x0b" - b"\x61\xc6\xff\xe6\x88\x13\x2f\x96\xf1\xfa\xaf\x4d" - b"\x03\x00\x00\xef\xff\x89\x16\x00\x00\x00\x00\x00" - b"\x83\xf1\xf9\xab\x48\x02\x00\x00\x00\x00\x0a\x61" - b"\xc6\xff\xe2\x82\x21\x00\x00\x00\x00\x00\x2b\x90" - b"\xed\xfe\x3b\x00\x00\x00\x00\x00\x00\x07\x5b\x29" - b"\x1c\x24\x24\x24\x24\x24\x24\x24\x09\xcc\xff\xff" - b"\xff\xff\xff\xff\xff\x44\x39\x48\x48\x48\x48\x48" - b"\x48\x48\x13\x00\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\xb2\xe0\xe0" - b"\xe0\xe0\xe0\xe0\xe0\x3b\x6f\x8c\x8c\x8c\x8c\x8c" - b"\x8c\x8c\x25\x16\x00\x00\x00\x00\x00\x00\x00\x00" - b"\xcb\xc0\x5b\x08\x00\x00\x00\x00\x00\x4f\xbc\xfe" - b"\xed\x90\x2b\x00\x00\x00\x00\x00\x20\x81\xe0\xff" - b"\xc6\x61\x08\x00\x00\x00\x00\x02\x46\xea\xff\x44" - b"\x00\x00\x00\x1c\x7c\xde\xff\xc9\x23\x06\x53\xb6" - b"\xfd\xed\x90\x2b\x00\x00\xc2\xff\xc0\x5a\x07\x00" - b"\x00\x00\x00\x66\x25\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x05\x57\x9d\xb7\xb1\x85\x24\x00\x00\x29\xd8" - b"\xff\xee\xcf\xe4\xff\xfa\x50\x00\x71\xef\x58\x03" - b"\x00\x00\x59\xfd\xe6\x00\x00\x0c\x00\x00\x00\x00" - b"\x00\xcd\xff\x0d\x00\x00\x00\x00\x00\x00\x07\xea" - b"\xe4\x00\x00\x00\x00\x00\x00\x04\xa5\xff\x63\x00" - b"\x00\x00\x00\x00\x04\xae\xff\x7a\x00\x00\x00\x00" - b"\x00\x00\x76\xff\x85\x00\x00\x00\x00\x00\x00\x00" - b"\xcb\xfb\x0a\x00\x00\x00\x00\x00\x00\x00\x03\x03" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x14\x16\x00\x00" - b"\x00\x00\x00\x00\x00\x0d\xf0\xf3\x12\x00\x00\x00" - b"\x00\x00\x00\x0a\xe3\xe7\x0e\x00\x00\x00\x00\x00" - b"\x00\x00\x07\x08\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x07\x48\x89\xa5\xb2\x9f\x79\x33\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x66\xe4\xf2\xb1\x8b\x7d" - b"\x8f\xc1\xf9\xc7\x39\x00\x00\x00\x00\x00\x03\xa5" - b"\xf6\x74\x0a\x00\x00\x00\x00\x00\x18\x93\xf9\x63" - b"\x00\x00\x00\x00\x8e\xef\x31\x00\x00\x00\x0d\x0c" - b"\x00\x00\x05\x06\x59\xfa\x46\x00\x00\x2f\xfe\x4e" - b"\x00\x00\x59\xd8\xff\xff\xd3\x4c\xbc\xd4\x00\x85" - b"\xda\x05\x00\xa1\xc5\x00\x00\x6d\xff\xc7\x62\x51" - b"\x8e\xf8\xff\xd4\x00\x0c\xf2\x4b\x00\xea\x6e\x00" - b"\x11\xf7\xbe\x03\x00\x00\x00\x50\xff\xd4\x00\x00" - b"\xae\x91\x0f\xff\x40\x00\x56\xff\x4c\x00\x00\x00" - b"\x00\x00\xff\xdf\x00\x00\x88\xaf\x1e\xff\x31\x00" - b"\x6c\xff\x2e\x00\x00\x00\x00\x00\xff\xf1\x00\x00" - b"\x7c\xb9\x0c\xff\x43\x00\x53\xff\x50\x00\x00\x00" - b"\x00\x00\xff\xde\x00\x00\x92\xa4\x00\xe2\x75\x00" - b"\x0e\xf4\xc6\x06\x00\x00\x00\x57\xff\xd8\x00\x00" - b"\xcf\x6e\x00\x96\xcc\x00\x00\x66\xff\xcd\x66\x55" - b"\x92\xf9\xcf\xfc\x63\x8a\xf2\x13\x00\x23\xfa\x5a" - b"\x00\x00\x58\xd8\xff\xff\xd3\x4a\x1d\xd6\xff\xe6" - b"\x45\x00\x00\x00\x77\xf4\x3d\x00\x00\x00\x0d\x0c" - b"\x00\x00\x00\x00\x11\x01\x00\x00\x00\x00\x00\x8b" - b"\xfa\x86\x13\x00\x00\x00\x00\x02\x31\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x4e\xd6\xfb\xc4\xa0\x97" - b"\xb1\xe7\xf2\x0a\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x02\x36\x77\x93\x9b\x82\x57\x0c\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x43\x98\x5b\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\xce\xff\xec" - b"\x0a\x00\x00\x00\x00\x00\x00\x00\x00\x00\x43\xff" - b"\xc6\xff\x6b\x00\x00\x00\x00\x00\x00\x00\x00\x00" - b"\xb7\xf4\x13\xde\xdc\x02\x00\x00\x00\x00\x00\x00" - b"\x00\x2b\xff\x93\x00\x70\xff\x53\x00\x00\x00\x00" - b"\x00\x00\x00\x9f\xfe\x25\x00\x0d\xf1\xc7\x00\x00" - b"\x00\x00\x00\x00\x19\xf9\xb2\x00\x00\x00\x8f\xff" - b"\x3c\x00\x00\x00\x00\x00\x87\xff\x42\x00\x00\x00" - b"\x21\xfd\xb0\x00\x00\x00\x00\x0b\xff\xff\x98\x98" - b"\x98\x98\x98\xff\xff\x26\x00\x00\x00\x6f\xff\xff" - b"\xd8\xd8\xd8\xd8\xd8\xff\xff\x99\x00\x00\x03\xdf" - b"\xe9\x07\x00\x00\x00\x00\x00\x00\xcd\xf7\x15\x00" - b"\x57\xff\x80\x00\x00\x00\x00\x00\x00\x00\x5d\xff" - b"\x81\x00\xca\xf8\x17\x00\x00\x00\x00\x00\x00\x00" - b"\x06\xe6\xeb\x09\x10\x98\x98\x98\x98\x98\x94\x7b" - b"\x52\x04\x00\x00\x1c\xff\xf4\xdc\xdc\xdc\xe1\xf9" - b"\xff\xe0\x31\x00\x1c\xff\xb0\x00\x00\x00\x00\x07" - b"\x72\xff\xd2\x00\x1c\xff\xb0\x00\x00\x00\x00\x00" - b"\x00\xd1\xff\x08\x1c\xff\xb0\x00\x00\x00\x00\x00" - b"\x03\xe1\xee\x00\x1c\xff\xbd\x1c\x1c\x1c\x21\x3f" - b"\xaf\xff\x77\x00\x1c\xff\xff\xff\xff\xff\xff\xff" - b"\xff\xce\x0e\x00\x1c\xff\xd9\x58\x58\x58\x58\x69" - b"\x9e\xf9\xdd\x16\x1c\xff\xb0\x00\x00\x00\x00\x00" - b"\x00\x63\xff\x86\x1c\xff\xb0\x00\x00\x00\x00\x00" - b"\x00\x25\xff\xb0\x1c\xff\xb0\x00\x00\x00\x00\x00" - b"\x00\x72\xff\x94\x1c\xff\xd4\x74\x74\x74\x74\x84" - b"\xb7\xfd\xee\x25\x1c\xff\xff\xff\xff\xff\xff\xf9" - b"\xdd\x9a\x22\x00\x00\x00\x00\x00\x1a\x70\xa7\xb9" - b"\xa9\x76\x1f\x00\x00\x00\x00\x01\x7d\xf7\xff\xf7" - b"\xe2\xf3\xff\xf9\x7e\x00\x00\x00\x96\xff\xd7\x4e" - b"\x06\x00\x02\x40\xc9\xe3\x18\x00\x4b\xff\xcd\x0d" - b"\x00\x00\x00\x00\x00\x07\x1c\x00\x00\xc1\xff\x2e" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x07\xfc\xd1" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1d\xff" - b"\xae\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11" - b"\xff\xbd\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\xe0\xf4\x0c\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x82\xff\x87\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x0c\xda\xfe\x7a\x04\x00\x00\x00\x01" - b"\x6b\x9d\x03\x00\x00\x23\xd8\xff\xe0\x95\x79\x8d" - b"\xd6\xff\xd5\x16\x00\x00\x00\x0c\x7c\xd9\xff\xff" - b"\xff\xdb\x7d\x0b\x00\x00\x00\x00\x00\x00\x00\x0d" - b"\x1e\x0e\x00\x00\x00\x00\x10\x98\x98\x98\x98\x98" - b"\x8f\x74\x3f\x01\x00\x00\x00\x1c\xff\xfd\xf8\xf8" - b"\xf8\xfe\xff\xff\xde\x48\x00\x00\x1c\xff\xb0\x00" - b"\x00\x00\x04\x23\x77\xf0\xff\x67\x00\x1c\xff\xb0" - b"\x00\x00\x00\x00\x00\x00\x21\xe8\xfa\x2b\x1c\xff" - b"\xb0\x00\x00\x00\x00\x00\x00\x00\x54\xff\x99\x1c" - b"\xff\xb0\x00\x00\x00\x00\x00\x00\x00\x03\xf2\xda" - b"\x1c\xff\xb0\x00\x00\x00\x00\x00\x00\x00\x00\xd5" - b"\xf6\x1c\xff\xb0\x00\x00\x00\x00\x00\x00\x00\x00" - b"\xe5\xe6\x1c\xff\xb0\x00\x00\x00\x00\x00\x00\x00" - b"\x28\xff\xbc\x1c\xff\xb0\x00\x00\x00\x00\x00\x00" - b"\x01\xb0\xff\x59\x1c\xff\xb0\x00\x00\x00\x00\x00" - b"\x14\xa7\xff\xb9\x03\x1c\xff\xdd\x90\x90\x90\x9a" - b"\xbb\xfa\xff\xb2\x10\x00\x1c\xff\xff\xff\xff\xff" - b"\xf7\xde\xa9\x4c\x00\x00\x00\x10\x98\x98\x98\x98" - b"\x98\x98\x98\x98\x76\x00\x1c\xff\xfd\xf8\xf8\xf8" - b"\xf8\xf8\xf8\xc1\x00\x1c\xff\xb0\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x1c\xff\xb0\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x1c\xff\xb0\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x1c\xff\xc2\x28\x28\x28\x28\x28\x28\x00" - b"\x00\x1c\xff\xff\xff\xff\xff\xff\xff\xff\x04\x00" - b"\x1c\xff\xdb\x5c\x5c\x5c\x5c\x5c\x5c\x01\x00\x1c" - b"\xff\xb0\x00\x00\x00\x00\x00\x00\x00\x00\x1c\xff" - b"\xb0\x00\x00\x00\x00\x00\x00\x00\x00\x1c\xff\xb0" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x1c\xff\xdd\x90" - b"\x90\x90\x90\x90\x90\x90\x04\x1c\xff\xff\xff\xff" - b"\xff\xff\xff\xff\xff\x08\x10\x98\x98\x98\x98\x98" - b"\x98\x98\x98\x76\x1c\xff\xfd\xf8\xf8\xf8\xf8\xf8" - b"\xf8\xc1\x1c\xff\xb0\x00\x00\x00\x00\x00\x00\x00" - b"\x1c\xff\xb0\x00\x00\x00\x00\x00\x00\x00\x1c\xff" - b"\xb0\x00\x00\x00\x00\x00\x00\x00\x1c\xff\xb0\x00" - b"\x00\x00\x00\x00\x00\x00\x1c\xff\xff\xa4\xa4\xa4" - b"\xa4\xa4\xa4\x02\x1c\xff\xff\xe8\xe8\xe8\xe8\xe8" - b"\xe8\x03\x1c\xff\xb0\x00\x00\x00\x00\x00\x00\x00" - b"\x1c\xff\xb0\x00\x00\x00\x00\x00\x00\x00\x1c\xff" - b"\xb0\x00\x00\x00\x00\x00\x00\x00\x1c\xff\xb0\x00" - b"\x00\x00\x00\x00\x00\x00\x1c\xff\xb0\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x18\x6e\xa6\xb9" - b"\xab\x7e\x29\x00\x00\x00\x00\x01\x7a\xf6\xff\xf8" - b"\xe2\xf0\xff\xfd\x98\x05\x00\x00\x93\xff\xd9\x51" - b"\x07\x00\x00\x30\xae\xf0\x2c\x00\x4b\xff\xce\x0e" - b"\x00\x00\x00\x00\x00\x00\x1b\x00\x00\xc1\xff\x2f" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x07\xfc\xd1" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1d\xff" - b"\xae\x00\x00\x00\x00\x00\x00\x00\x24\x60\x21\x11" - b"\xff\xbd\x00\x00\x00\x00\x00\x00\x00\x60\xff\x58" - b"\x00\xe0\xf5\x0c\x00\x00\x00\x00\x00\x00\x60\xff" - b"\x58\x00\x82\xff\x8b\x00\x00\x00\x00\x00\x00\x60" - b"\xff\x58\x00\x0c\xda\xff\x7f\x05\x00\x00\x00\x00" - b"\x9c\xff\x58\x00\x00\x23\xd7\xff\xe3\x97\x79\x87" - b"\xc2\xff\xff\x4d\x00\x00\x00\x0b\x78\xd7\xff\xff" - b"\xff\xe4\x95\x23\x00\x00\x00\x00\x00\x00\x00\x0c" - b"\x1e\x11\x00\x00\x00\x00\x10\x98\x68\x00\x00\x00" - b"\x00\x00\x00\x0b\x98\x6d\x1c\xff\xb0\x00\x00\x00" - b"\x00\x00\x00\x14\xff\xb8\x1c\xff\xb0\x00\x00\x00" - b"\x00\x00\x00\x14\xff\xb8\x1c\xff\xb0\x00\x00\x00" - b"\x00\x00\x00\x14\xff\xb8\x1c\xff\xb0\x00\x00\x00" - b"\x00\x00\x00\x14\xff\xb8\x1c\xff\xc7\x30\x30\x30" - b"\x30\x30\x30\x44\xff\xb8\x1c\xff\xff\xff\xff\xff" - b"\xff\xff\xff\xff\xff\xb8\x1c\xff\xe0\x64\x64\x64" - b"\x64\x64\x64\x78\xff\xb8\x1c\xff\xb0\x00\x00\x00" - b"\x00\x00\x00\x14\xff\xb8\x1c\xff\xb0\x00\x00\x00" - b"\x00\x00\x00\x14\xff\xb8\x1c\xff\xb0\x00\x00\x00" - b"\x00\x00\x00\x14\xff\xb8\x1c\xff\xb0\x00\x00\x00" - b"\x00\x00\x00\x14\xff\xb8\x1c\xff\xb0\x00\x00\x00" - b"\x00\x00\x00\x14\xff\xb8\x10\x98\x68\x1c\xff\xb0" - b"\x1c\xff\xb0\x1c\xff\xb0\x1c\xff\xb0\x1c\xff\xb0" - b"\x1c\xff\xb0\x1c\xff\xb0\x1c\xff\xb0\x1c\xff\xb0" - b"\x1c\xff\xb0\x1c\xff\xb0\x1c\xff\xb0\x00\x00\x87" - b"\x98\x98\x98\x98\x98\x44\x00\x00\xdc\xf8\xf8\xf8" - b"\xfa\xff\x74\x00\x00\x00\x00\x00\x00\x54\xff\x74" - b"\x00\x00\x00\x00\x00\x00\x54\xff\x74\x00\x00\x00" - b"\x00\x00\x00\x54\xff\x74\x00\x00\x00\x00\x00\x00" - b"\x54\xff\x74\x00\x00\x00\x00\x00\x00\x54\xff\x74" - b"\x00\x00\x00\x00\x00\x00\x54\xff\x74\x00\x00\x00" - b"\x00\x00\x00\x54\xff\x73\x00\x00\x00\x00\x00\x00" - b"\x62\xff\x6a\x00\x70\x42\x00\x00\x00\xa4\xff\x43" - b"\x07\xe1\xfa\x9c\x70\xa6\xff\xd8\x03\x00\x1e\xac" - b"\xfa\xff\xfd\xbe\x23\x00\x00\x00\x00\x08\x1e\x0d" - b"\x00\x00\x00\x10\x98\x68\x00\x00\x00\x00\x00\x00" - b"\x72\x98\x2d\x1c\xff\xb0\x00\x00\x00\x00\x00\x87" - b"\xff\x8a\x00\x1c\xff\xb0\x00\x00\x00\x00\x80\xff" - b"\x97\x00\x00\x1c\xff\xb0\x00\x00\x00\x78\xff\xa4" - b"\x02\x00\x00\x1c\xff\xb0\x00\x00\x71\xff\xaf\x04" - b"\x00\x00\x00\x1c\xff\xb0\x00\x6a\xff\xbd\x07\x00" - b"\x00\x00\x00\x1c\xff\xb0\x62\xff\xff\x98\x00\x00" - b"\x00\x00\x00\x1c\xff\xff\xfd\xe1\xe9\xff\x61\x00" - b"\x00\x00\x00\x1c\xff\xff\xd3\x14\x21\xea\xf9\x3b" - b"\x00\x00\x00\x1c\xff\xff\x15\x00\x00\x39\xf7\xe9" - b"\x1e\x00\x00\x1c\xff\xb0\x00\x00\x00\x00\x56\xfe" - b"\xcf\x0b\x00\x1c\xff\xb0\x00\x00\x00\x00\x00\x79" - b"\xff\xad\x01\x1c\xff\xb0\x00\x00\x00\x00\x00\x00" - b"\x9d\xff\x82\x10\x98\x68\x00\x00\x00\x00\x00\x00" - b"\x00\x1c\xff\xb0\x00\x00\x00\x00\x00\x00\x00\x1c" - b"\xff\xb0\x00\x00\x00\x00\x00\x00\x00\x1c\xff\xb0" - b"\x00\x00\x00\x00\x00\x00\x00\x1c\xff\xb0\x00\x00" - b"\x00\x00\x00\x00\x00\x1c\xff\xb0\x00\x00\x00\x00" - b"\x00\x00\x00\x1c\xff\xb0\x00\x00\x00\x00\x00\x00" - b"\x00\x1c\xff\xb0\x00\x00\x00\x00\x00\x00\x00\x1c" - b"\xff\xb0\x00\x00\x00\x00\x00\x00\x00\x1c\xff\xb0" - b"\x00\x00\x00\x00\x00\x00\x00\x1c\xff\xb0\x00\x00" - b"\x00\x00\x00\x00\x00\x1c\xff\xdd\x90\x90\x90\x90" - b"\x90\x90\x4a\x1c\xff\xff\xff\xff\xff\xff\xff\xff" - b"\x84\x10\x98\x51\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x37\x98\x2a\x1c\xff\xee\x14\x00\x00\x00\x00" - b"\x00\x00\x00\x03\xd1\xff\x48\x1c\xff\xff\x9a\x00" - b"\x00\x00\x00\x00\x00\x00\x6b\xff\xff\x48\x1c\xff" - b"\xff\xfd\x34\x00\x00\x00\x00\x00\x13\xee\xff\xff" - b"\x48\x1c\xff\xb3\xed\xc8\x01\x00\x00\x00\x00\x98" - b"\xfb\x9e\xff\x49\x1c\xff\xa0\x68\xff\x63\x00\x00" - b"\x00\x31\xfd\x8d\x72\xff\x49\x1c\xff\xa0\x02\xcc" - b"\xea\x10\x00\x00\xc4\xe7\x0e\x71\xff\x49\x1c\xff" - b"\xa0\x00\x37\xfe\x93\x00\x5c\xff\x5c\x00\x71\xff" - b"\x4a\x1c\xff\xa0\x00\x00\x9c\xfc\x3b\xe5\xc3\x00" - b"\x00\x70\xff\x4a\x1c\xff\xa0\x00\x00\x14\xee\xff" - b"\xfc\x2f\x00\x00\x6f\xff\x4a\x1c\xff\xa0\x00\x00" - b"\x00\x69\xff\x93\x00\x00\x00\x6e\xff\x4b\x1c\xff" - b"\xa0\x00\x00\x00\x02\x71\x10\x00\x00\x00\x6d\xff" - b"\x4b\x1c\xff\xa0\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x6c\xff\x4b\x10\x98\x5a\x00\x00\x00\x00\x00" - b"\x00\x0b\x98\x6d\x1c\xff\xfb\x41\x00\x00\x00\x00" - b"\x00\x14\xff\xb8\x1c\xff\xff\xeb\x20\x00\x00\x00" - b"\x00\x14\xff\xb8\x1c\xff\xff\xff\xcf\x0a\x00\x00" - b"\x00\x14\xff\xb8\x1c\xff\xb0\x98\xff\xa8\x00\x00" - b"\x00\x14\xff\xb8\x1c\xff\xb0\x05\xc4\xff\x77\x00" - b"\x00\x14\xff\xb8\x1c\xff\xb0\x00\x17\xe4\xfd\x48" - b"\x00\x14\xff\xb8\x1c\xff\xb0\x00\x00\x35\xf8\xef" - b"\x25\x14\xff\xb8\x1c\xff\xb0\x00\x00\x00\x60\xff" - b"\xd5\x21\xff\xb8\x1c\xff\xb0\x00\x00\x00\x00\x92" - b"\xff\xc4\xff\xb8\x1c\xff\xb0\x00\x00\x00\x00\x04" - b"\xbf\xff\xff\xb8\x1c\xff\xb0\x00\x00\x00\x00\x00" - b"\x14\xe0\xff\xb8\x1c\xff\xb0\x00\x00\x00\x00\x00" - b"\x00\x31\xf6\xb8\x00\x00\x00\x00\x17\x6d\xa6\xb9" - b"\xab\x79\x23\x00\x00\x00\x00\x00\x00\x00\x76\xf5" - b"\xff\xf7\xe2\xf3\xff\xfc\x93\x06\x00\x00\x00\x00" - b"\x90\xff\xd6\x4c\x05\x00\x02\x3d\xc6\xff\xb3\x02" - b"\x00\x00\x48\xff\xce\x0d\x00\x00\x00\x00\x00\x05" - b"\xb6\xff\x69\x00\x00\xbf\xff\x30\x00\x00\x00\x00" - b"\x00\x00\x00\x1a\xf8\xdc\x00\x07\xfc\xd1\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\xb6\xff\x1f\x1d\xff" - b"\xae\x00\x00\x00\x00\x00\x00\x00\x00\x00\x93\xff" - b"\x38\x11\xff\xbd\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\xa1\xff\x2c\x00\xdf\xf4\x0c\x00\x00\x00\x00" - b"\x00\x00\x00\x03\xe2\xf4\x08\x00\x80\xff\x89\x00" - b"\x00\x00\x00\x00\x00\x00\x6b\xff\x9f\x00\x00\x0b" - b"\xd8\xfe\x7b\x04\x00\x00\x00\x01\x62\xf9\xea\x1c" - b"\x00\x00\x00\x21\xd4\xff\xe0\x94\x79\x8d\xd4\xff" - b"\xe5\x34\x00\x00\x00\x00\x00\x09\x73\xd3\xff\xff" - b"\xff\xdf\x87\x13\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x0b\x1e\x10\x00\x00\x00\x00\x00\x00\x10\x98" - b"\x98\x98\x98\x97\x88\x66\x1c\x00\x00\x00\x1c\xff" - b"\xff\xf8\xf8\xf9\xff\xff\xfb\x81\x00\x00\x1c\xff" - b"\xb0\x00\x00\x00\x0f\x48\xd2\xff\x68\x00\x1c\xff" - b"\xb0\x00\x00\x00\x00\x00\x1c\xf7\xdb\x00\x1c\xff" - b"\xb0\x00\x00\x00\x00\x00\x00\xcc\xfe\x06\x1c\xff" - b"\xb0\x00\x00\x00\x00\x00\x00\xdd\xf7\x01\x1c\xff" - b"\xb0\x00\x00\x00\x00\x00\x57\xff\xb4\x00\x1c\xff" - b"\xff\x60\x60\x60\x76\xaf\xfe\xf0\x2a\x00\x1c\xff" - b"\xff\xff\xff\xff\xff\xf6\xac\x24\x00\x00\x1c\xff" - b"\xdf\x30\x30\x2f\x21\x07\x00\x00\x00\x00\x1c\xff" - b"\xb0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1c\xff" - b"\xb0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1c\xff" - b"\xb0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x17\x6d\xa6\xb9\xab\x79\x23\x00\x00\x00" - b"\x00\x00\x00\x00\x76\xf5\xff\xf7\xe2\xf3\xff\xfc" - b"\x93\x06\x00\x00\x00\x00\x90\xff\xd6\x4c\x05\x00" - b"\x02\x3d\xc6\xff\xb3\x02\x00\x00\x48\xff\xce\x0d" - b"\x00\x00\x00\x00\x00\x05\xb6\xff\x69\x00\x00\xbf" - b"\xff\x30\x00\x00\x00\x00\x00\x00\x00\x1a\xf8\xdc" - b"\x00\x07\xfc\xd1\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\xb6\xff\x1f\x1d\xff\xae\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x93\xff\x38\x11\xff\xbd\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\xa2\xff\x2c\x00\xdf" - b"\xf5\x0e\x00\x00\x00\x00\x00\x00\x00\x03\xe3\xf4" - b"\x08\x00\x80\xff\x8e\x00\x00\x00\x00\x00\x00\x00" - b"\x6f\xff\x9f\x00\x00\x0b\xd8\xff\x7e\x04\x00\x00" - b"\x00\x01\x64\xfa\xea\x1c\x00\x00\x00\x21\xd4\xff" - b"\xe0\x94\x79\x8d\xd4\xff\xe5\x34\x00\x00\x00\x00" - b"\x00\x09\x73\xd3\xff\xff\xff\xf5\x87\x13\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x0b\x99\xff\xd1\x2b" - b"\x00\x02\x6e\x54\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x60\xf5\xfd\xd9\xea\xff\x7d\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x17\x73\x97\x88\x35\x00\x10\x98" - b"\x98\x98\x98\x97\x88\x66\x1c\x00\x00\x00\x1c\xff" - b"\xff\xf8\xf8\xf9\xff\xff\xfb\x81\x00\x00\x1c\xff" - b"\xb0\x00\x00\x00\x0f\x48\xd2\xff\x68\x00\x1c\xff" - b"\xb0\x00\x00\x00\x00\x00\x1c\xf7\xdb\x00\x1c\xff" - b"\xb0\x00\x00\x00\x00\x00\x00\xcc\xfe\x06\x1c\xff" - b"\xb0\x00\x00\x00\x00\x00\x00\xdd\xf7\x01\x1c\xff" - b"\xb0\x00\x00\x00\x00\x00\x57\xff\xb4\x00\x1c\xff" - b"\xff\x5c\x5c\x5c\x73\xac\xfe\xf0\x2a\x00\x1c\xff" - b"\xff\xff\xff\xff\xff\xff\xce\x24\x00\x00\x1c\xff" - b"\xdb\x2c\x2c\x2b\x48\xfd\xc7\x04\x00\x00\x1c\xff" - b"\xb0\x00\x00\x00\x00\x70\xff\x81\x00\x00\x1c\xff" - b"\xb0\x00\x00\x00\x00\x01\xbb\xfc\x3a\x00\x1c\xff" - b"\xb0\x00\x00\x00\x00\x00\x1b\xee\xde\x0e\x00\x00" - b"\x05\x5b\x9e\xb7\xb4\x90\x4b\x04\x00\x00\x15\xd2" - b"\xff\xf1\xcf\xdf\xfd\xff\xb6\x00\x00\xa3\xff\x77" - b"\x04\x00\x00\x0b\x5f\x5f\x00\x00\xe7\xdf\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\xdf\xf3\x18\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x7e\xff\xed\x7e\x2f\x00" - b"\x00\x00\x00\x00\x00\x02\x73\xe5\xff\xff\xe9\xa1" - b"\x3d\x00\x00\x00\x00\x00\x05\x44\x88\xd0\xff\xff" - b"\x99\x00\x00\x00\x00\x00\x00\x00\x00\x34\xd7\xff" - b"\x3d\x00\x00\x00\x00\x00\x00\x00\x00\x60\xff\x67" - b"\x00\x8c\x27\x00\x00\x00\x00\x00\x9a\xff\x42\x19" - b"\xf4\xfd\xba\x7e\x68\x77\xbc\xff\xbb\x01\x00\x21" - b"\x90\xdf\xff\xff\xff\xe3\x85\x0a\x00\x00\x00\x00" - b"\x00\x0e\x1f\x11\x00\x00\x00\x00\x8c\x98\x98\x98" - b"\x98\x98\x98\x98\x98\x98\x4c\xe4\xf8\xf8\xf8\xfc" - b"\xff\xf9\xf8\xf8\xf8\x7c\x00\x00\x00\x00\x9c\xff" - b"\x2c\x00\x00\x00\x00\x00\x00\x00\x00\x9c\xff\x2c" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x9c\xff\x2c\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x9c\xff\x2c\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x9c\xff\x2c\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x9c\xff\x2c\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x9c\xff\x2c\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x9c\xff\x2c\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x9c\xff\x2c\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x9c\xff\x2c\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x9c\xff\x2c\x00\x00\x00\x00\x21\x98\x57\x00\x00" - b"\x00\x00\x00\x00\x2f\x98\x42\x38\xff\x94\x00\x00" - b"\x00\x00\x00\x00\x50\xff\x70\x38\xff\x94\x00\x00" - b"\x00\x00\x00\x00\x50\xff\x70\x38\xff\x94\x00\x00" - b"\x00\x00\x00\x00\x50\xff\x70\x38\xff\x94\x00\x00" - b"\x00\x00\x00\x00\x50\xff\x70\x38\xff\x94\x00\x00" - b"\x00\x00\x00\x00\x50\xff\x70\x38\xff\x94\x00\x00" - b"\x00\x00\x00\x00\x50\xff\x70\x36\xff\x95\x00\x00" - b"\x00\x00\x00\x00\x52\xff\x6e\x27\xff\xa7\x00\x00" - b"\x00\x00\x00\x00\x64\xff\x5e\x05\xf4\xe2\x02\x00" - b"\x00\x00\x00\x00\xa1\xff\x31\x00\x9e\xff\x82\x01" - b"\x00\x00\x00\x46\xfa\xd4\x01\x00\x15\xde\xff\xcd" - b"\x8d\x83\xb3\xfe\xf4\x36\x00\x00\x00\x14\x98\xef" - b"\xff\xff\xf8\xb1\x2b\x00\x00\x00\x00\x00\x00\x02" - b"\x19\x1c\x06\x00\x00\x00\x00\x86\x96\x09\x00\x00" - b"\x00\x00\x00\x00\x00\x0f\x97\x69\x89\xff\x66\x00" - b"\x00\x00\x00\x00\x00\x00\x74\xff\x59\x1c\xfb\xd5" - b"\x00\x00\x00\x00\x00\x00\x03\xe0\xe4\x04\x00\xa7" - b"\xff\x46\x00\x00\x00\x00\x00\x55\xff\x79\x00\x00" - b"\x36\xff\xb7\x00\x00\x00\x00\x00\xc6\xf5\x12\x00" - b"\x00\x00\xc6\xfe\x28\x00\x00\x00\x37\xff\x98\x00" - b"\x00\x00\x00\x55\xff\x97\x00\x00\x00\xa8\xfe\x29" - b"\x00\x00\x00\x00\x03\xe0\xf5\x11\x00\x1d\xfb\xb8" - b"\x00\x00\x00\x00\x00\x00\x74\xff\x77\x00\x8a\xff" - b"\x48\x00\x00\x00\x00\x00\x00\x0f\xf3\xe3\x10\xee" - b"\xd7\x01\x00\x00\x00\x00\x00\x00\x00\x92\xff\xc3" - b"\xff\x68\x00\x00\x00\x00\x00\x00\x00\x00\x23\xff" - b"\xff\xed\x0a\x00\x00\x00\x00\x00\x00\x00\x00\x00" - b"\xb1\xff\x88\x00\x00\x00\x00\x00\x31\x98\x51\x00" - b"\x00\x00\x00\x00\x00\x6e\x98\x13\x00\x00\x00\x00" - b"\x00\x11\x98\x59\x13\xfb\xcc\x00\x00\x00\x00\x00" - b"\x0b\xf5\xff\x66\x00\x00\x00\x00\x00\x63\xff\x52" - b"\x00\xb9\xff\x22\x00\x00\x00\x00\x59\xff\xff\xbc" - b"\x00\x00\x00\x00\x00\xb9\xf3\x09\x00\x64\xff\x76" - b"\x00\x00\x00\x00\xb2\xf0\xab\xfc\x16\x00\x00\x00" - b"\x13\xfb\xa7\x00\x00\x13\xfb\xcb\x00\x00\x00\x10" - b"\xf9\xa0\x4d\xff\x69\x00\x00\x00\x66\xff\x51\x00" - b"\x00\x00\xb9\xff\x21\x00\x00\x63\xff\x48\x06\xef" - b"\xbf\x00\x00\x00\xbc\xf3\x09\x00\x00\x00\x64\xff" - b"\x75\x00\x00\xbb\xec\x04\x00\x9f\xfd\x18\x00\x16" - b"\xfc\xa7\x00\x00\x00\x00\x12\xfb\xca\x00\x16\xfc" - b"\x99\x00\x00\x48\xff\x6c\x00\x69\xff\x51\x00\x00" - b"\x00\x00\x00\xb9\xff\x20\x6c\xff\x41\x00\x00\x04" - b"\xec\xc2\x00\xbf\xf3\x08\x00\x00\x00\x00\x00\x63" - b"\xff\x74\xc4\xe7\x02\x00\x00\x00\x99\xfe\x33\xfd" - b"\xa6\x00\x00\x00\x00\x00\x00\x12\xfb\xe7\xfe\x92" - b"\x00\x00\x00\x00\x42\xff\xdb\xff\x51\x00\x00\x00" - b"\x00\x00\x00\x00\xb8\xff\xff\x3b\x00\x00\x00\x00" - b"\x02\xe8\xff\xf3\x08\x00\x00\x00\x00\x00\x00\x00" - b"\x63\xff\xe4\x01\x00\x00\x00\x00\x00\x94\xff\xa6" - b"\x00\x00\x00\x00\x28\x98\x78\x00\x00\x00\x00\x00" - b"\x00\x5e\x98\x34\x00\xb0\xff\x5f\x00\x00\x00\x00" - b"\x37\xfb\xc0\x02\x00\x13\xe4\xf4\x26\x00\x00\x0e" - b"\xdd\xec\x1b\x00\x00\x00\x40\xfd\xce\x06\x00\xa6" - b"\xff\x4c\x00\x00\x00\x00\x00\x84\xff\x90\x60\xff" - b"\x90\x00\x00\x00\x00\x00\x00\x04\xc6\xff\xff\xcd" - b"\x06\x00\x00\x00\x00\x00\x00\x00\x38\xff\xff\x4e" - b"\x00\x00\x00\x00\x00\x00\x00\x01\xba\xff\xff\xd1" - b"\x08\x00\x00\x00\x00\x00\x00\x78\xff\x88\x76\xff" - b"\x95\x00\x00\x00\x00\x00\x38\xfb\xcb\x05\x01\xba" - b"\xff\x50\x00\x00\x00\x0f\xdf\xf3\x24\x00\x00\x18" - b"\xe9\xee\x1d\x00\x00\xaa\xff\x60\x00\x00\x00\x00" - b"\x49\xfe\xc3\x03\x66\xff\xa8\x00\x00\x00\x00\x00" - b"\x00\x8d\xff\x82\x01\x87\x93\x0a\x00\x00\x00\x00" - b"\x00\x00\x25\x98\x51\x00\x6a\xff\x85\x00\x00\x00" - b"\x00\x00\x00\xba\xf2\x1a\x00\x02\xcb\xf9\x27\x00" - b"\x00\x00\x00\x58\xff\x71\x00\x00\x00\x34\xfd\xbb" - b"\x00\x00\x00\x0d\xe5\xd2\x04\x00\x00\x00\x00\x95" - b"\xff\x56\x00\x00\x90\xfe\x3b\x00\x00\x00\x00\x00" - b"\x10\xe8\xe4\x0d\x30\xfc\x9e\x00\x00\x00\x00\x00" - b"\x00\x00\x5d\xff\x8f\xc6\xee\x15\x00\x00\x00\x00" - b"\x00\x00\x00\x00\xc0\xff\xff\x67\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x36\xff\xe9\x02\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x10\xff\xba\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x10\xff\xb8\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x10\xff\xb8\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x10\xff\xb8" - b"\x00\x00\x00\x00\x00\x0e\x98\x98\x98\x98\x98\x98" - b"\x98\x98\x98\x98\x09\x17\xf8\xf8\xf8\xf8\xf8\xf8" - b"\xf8\xff\xff\xf6\x0a\x00\x00\x00\x00\x00\x00\x00" - b"\x03\xbb\xff\x5c\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x8d\xff\x8e\x00\x00\x00\x00\x00\x00\x00\x00\x5a" - b"\xff\xbd\x03\x00\x00\x00\x00\x00\x00\x00\x32\xf6" - b"\xe0\x14\x00\x00\x00\x00\x00\x00\x00\x15\xe1\xf5" - b"\x30\x00\x00\x00\x00\x00\x00\x00\x04\xc0\xff\x59" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x94\xff\x8b\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x61\xff\xbb\x03\x00" - b"\x00\x00\x00\x00\x00\x00\x37\xf8\xde\x12\x00\x00" - b"\x00\x00\x00\x00\x00\x13\xe5\xff\xbe\x90\x90\x90" - b"\x90\x90\x90\x90\x2a\x38\xff\xff\xff\xff\xff\xff" - b"\xff\xff\xff\xff\x4c\x0a\x5c\x5c\x5c\x3c\x1c\xff" - b"\xff\xff\xa8\x1c\xff\xa6\x10\x0a\x1c\xff\xa0\x00" - b"\x00\x1c\xff\xa0\x00\x00\x1c\xff\xa0\x00\x00\x1c" - b"\xff\xa0\x00\x00\x1c\xff\xa0\x00\x00\x1c\xff\xa0" - b"\x00\x00\x1c\xff\xa0\x00\x00\x1c\xff\xa0\x00\x00" - b"\x1c\xff\xa0\x00\x00\x1c\xff\xa0\x00\x00\x1c\xff" - b"\xa0\x00\x00\x1c\xff\xa0\x00\x00\x1c\xff\xa0\x00" - b"\x00\x1c\xff\xfa\xf0\x9d\x0d\x7c\x7c\x7c\x51\x1a" - b"\x24\x00\x00\x00\x00\x00\x00\x75\xfe\x1c\x00\x00" - b"\x00\x00\x00\x1d\xfe\x74\x00\x00\x00\x00\x00\x00" - b"\xc2\xce\x00\x00\x00\x00\x00\x00\x69\xff\x28\x00" - b"\x00\x00\x00\x00\x13\xfa\x82\x00\x00\x00\x00\x00" - b"\x00\xb5\xdb\x00\x00\x00\x00\x00\x00\x5c\xff\x36" - b"\x00\x00\x00\x00\x00\x0c\xf5\x90\x00\x00\x00\x00" - b"\x00\x00\xa8\xe7\x02\x00\x00\x00\x00\x00\x4f\xff" - b"\x43\x00\x00\x00\x00\x00\x06\xee\x9d\x00\x00\x00" - b"\x00\x00\x00\x9b\xef\x07\x00\x00\x00\x00\x00\x42" - b"\xff\x51\x00\x00\x00\x00\x00\x02\xe5\xab\x00\x00" - b"\x00\x00\x00\x00\x8e\xf7\x0d\x00\x00\x00\x00\x00" - b"\x35\xff\x5f\x00\x00\x00\x00\x00\x00\xb5\x8c\x3c" - b"\x5c\x5c\x5c\x0a\xa8\xff\xff\xff\x1c\x0a\x10\xa6" - b"\xff\x1c\x00\x00\xa0\xff\x1c\x00\x00\xa0\xff\x1c" - b"\x00\x00\xa0\xff\x1c\x00\x00\xa0\xff\x1c\x00\x00" - b"\xa0\xff\x1c\x00\x00\xa0\xff\x1c\x00\x00\xa0\xff" - b"\x1c\x00\x00\xa0\xff\x1c\x00\x00\xa0\xff\x1c\x00" - b"\x00\xa0\xff\x1c\x00\x00\xa0\xff\x1c\x00\x00\xa0" - b"\xff\x1c\x00\x00\xa0\xff\x1c\x9d\xf0\xfa\xff\x1c" - b"\x51\x7c\x7c\x7c\x0d\x00\x00\x00\x05\x0b\x00\x00" - b"\x00\x00\x00\x00\x00\xb9\xff\x33\x00\x00\x00\x00" - b"\x00\x23\xfd\xe7\x9f\x00\x00\x00\x00\x00\x8e\xc7" - b"\x4b\xf7\x14\x00\x00\x00\x0b\xef\x5d\x02\xde\x78" - b"\x00\x00\x00\x68\xeb\x07\x00\x76\xe1\x03\x00\x00" - b"\xd4\x89\x00\x00\x13\xf7\x51\x00\x42\xfd\x21\x00" - b"\x00\x00\xa0\xbe\x00\x3f\x5c\x00\x00\x00\x00\x24" - b"\x6f\x05\xff\xff\xff\xff\xff\xff\xff\xff\xff\x1c" - b"\x1c\x1c\x1c\x1c\x1c\x1c\x1c\x1c\x31\x44\x1d\x00" - b"\x00\x1e\xca\xe9\x2f\x00\x00\x07\x98\xee\x36\x00" - b"\x00\x00\x12\x0e\x00\x00\x24\x6f\x94\x91\x6d\x15" - b"\x00\x00\x00\x83\xfe\xfe\xe5\xf1\xff\xf1\x4a\x00" - b"\x00\x50\x8e\x17\x00\x00\x4b\xf9\xe3\x01\x00\x00" - b"\x00\x00\x00\x00\x00\xa6\xff\x23\x00\x00\x1b\x5e" - b"\x76\x7c\x7c\xe7\xff\x33\x00\x5e\xf9\xf0\xc8\xc0" - b"\xc0\xff\xff\x34\x03\xef\xd7\x0c\x00\x00\x00\x88" - b"\xff\x34\x0e\xff\xae\x00\x00\x00\x03\xe1\xff\x34" - b"\x00\xc5\xfc\x7e\x40\x55\xc1\xff\xff\x34\x00\x19" - b"\xaf\xfa\xff\xf7\xa0\x7e\xff\x34\x00\x00\x00\x07" - b"\x16\x03\x00\x00\x00\x00\x21\x5c\x21\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x5c\xff\x5c\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x5c\xff\x5c\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x5c\xff\x5c\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x5c\xff\x5c\x15\x6f\x96\x88\x47\x02\x00" - b"\x00\x5c\xff\x9d\xee\xfc\xe8\xfb\xff\xc1\x14\x00" - b"\x5c\xff\xff\xa0\x15\x00\x0f\x8b\xff\xba\x00\x5c" - b"\xff\xff\x01\x00\x00\x00\x00\xac\xff\x35\x5c\xff" - b"\xac\x00\x00\x00\x00\x00\x50\xff\x6e\x5c\xff\x94" - b"\x00\x00\x00\x00\x00\x41\xff\x7a\x5c\xff\xd3\x00" - b"\x00\x00\x00\x00\x76\xff\x58\x5c\xff\xff\x32\x00" - b"\x00\x00\x22\xeb\xf2\x0d\x5c\xff\xf4\xf6\x98\x6f" - b"\x90\xef\xfd\x59\x00\x5c\xff\x51\x86\xed\xff\xfe" - b"\xc4\x42\x00\x00\x00\x00\x00\x00\x00\x13\x06\x00" - b"\x00\x00\x00\x00\x00\x00\x20\x74\x95\x8c\x53\x05" - b"\x00\x00\x00\x7f\xfc\xff\xea\xf6\xff\xcf\x17\x00" - b"\x6c\xff\xbe\x24\x00\x06\x6b\xf7\x5e\x05\xe8\xe5" - b"\x0c\x00\x00\x00\x00\x13\x00\x2a\xff\x96\x00\x00" - b"\x00\x00\x00\x00\x00\x36\xff\x88\x00\x00\x00\x00" - b"\x00\x00\x00\x14\xfd\xba\x00\x00\x00\x00\x00\x00" - b"\x00\x00\xb3\xfe\x53\x00\x00\x00\x0d\x8f\x1b\x00" - b"\x1b\xe3\xfd\xa9\x71\x83\xe1\xff\x58\x00\x00\x14" - b"\x99\xf0\xff\xff\xcd\x4c\x00\x00\x00\x00\x00\x00" - b"\x12\x09\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x08\x5c\x3a\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x18\xff\xa4\x00\x00\x00\x00\x00\x00\x00\x00\x18" - b"\xff\xa4\x00\x00\x00\x00\x00\x00\x00\x00\x18\xff" - b"\xa4\x00\x00\x00\x2c\x7d\x98\x80\x2c\x18\xff\xa4" - b"\x00\x01\x8f\xff\xfe\xe9\xf7\xfd\x90\xff\xa4\x00" - b"\x76\xff\xbd\x22\x00\x07\x6f\xff\xff\xa4\x06\xeb" - b"\xe5\x0b\x00\x00\x00\x00\x93\xff\xa4\x2b\xff\x96" - b"\x00\x00\x00\x00\x00\x2b\xff\xa4\x36\xff\x88\x00" - b"\x00\x00\x00\x00\x1d\xff\xa4\x16\xfe\xbb\x00\x00" - b"\x00\x00\x00\x4f\xff\xa4\x00\xbc\xfe\x53\x00\x00" - b"\x00\x10\xd7\xff\xa4\x00\x24\xed\xfd\xa6\x70\x85" - b"\xe3\xe3\xff\xa4\x00\x00\x1f\xa8\xf7\xff\xfa\xac" - b"\x1e\xff\xa4\x00\x00\x00\x00\x02\x14\x04\x00\x00" - b"\x00\x00\x00\x00\x00\x2b\x7d\x98\x82\x35\x00\x00" - b"\x00\x00\x01\x8e\xff\xf7\xdb\xf4\xff\x9d\x03\x00" - b"\x00\x72\xff\x98\x0d\x00\x09\x86\xff\x7d\x00\x05" - b"\xe9\xcf\x02\x00\x00\x00\x00\xbb\xed\x07\x2b\xff" - b"\xe7\x70\x70\x70\x70\x70\xd2\xff\x2d\x36\xff\xff" - b"\xd0\xd0\xd0\xd0\xd0\xd0\xd0\x33\x14\xfd\xae\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x00\xb3\xfd\x4d\x00" - b"\x00\x00\x00\x4b\x03\x00\x00\x1b\xe4\xfd\xac\x73" - b"\x76\xbb\xff\x66\x00\x00\x00\x15\x98\xee\xff\xff" - b"\xdf\x75\x03\x00\x00\x00\x00\x00\x00\x11\x0f\x00" - b"\x00\x00\x00\x00\x00\x00\x22\x61\x68\x25\x00\x00" - b"\x56\xf8\xff\xfd\xcf\x00\x00\xe7\xdf\x1b\x09\x2a" - b"\x00\x14\xff\x9a\x00\x00\x00\x66\xa8\xff\xff\x8c" - b"\x8c\x32\xa4\xfc\xff\xff\xe0\xe0\x50\x00\x1c\xff" - b"\xa0\x00\x00\x00\x00\x1c\xff\xa0\x00\x00\x00\x00" - b"\x1c\xff\xa0\x00\x00\x00\x00\x1c\xff\xa0\x00\x00" - b"\x00\x00\x1c\xff\xa0\x00\x00\x00\x00\x1c\xff\xa0" - b"\x00\x00\x00\x00\x1c\xff\xa0\x00\x00\x00\x00\x1c" - b"\xff\xa0\x00\x00\x00\x00\x00\x00\x2f\x7e\x98\x85" - b"\x3c\x00\x7a\x6b\x00\x03\x9a\xff\xfe\xe9\xf4\xff" - b"\xa5\xe2\xc4\x00\x85\xff\xb4\x1f\x00\x02\x51\xed" - b"\xff\xc4\x0b\xf3\xdc\x07\x00\x00\x00\x00\x52\xff" - b"\xc4\x30\xff\x8f\x00\x00\x00\x00\x00\x02\xff\xc6" - b"\x30\xff\x91\x00\x00\x00\x00\x00\x03\xff\xc6\x09" - b"\xf1\xe0\x09\x00\x00\x00\x00\x59\xff\xc4\x00\x7e" - b"\xff\xbb\x26\x00\x04\x59\xf1\xff\xc4\x00\x02\x93" - b"\xff\xff\xf1\xf9\xff\x9a\xf9\xc3\x00\x00\x00\x2b" - b"\x7a\x95\x81\x35\x0e\xff\xb5\x00\x00\x06\x00\x00" - b"\x00\x00\x00\x4f\xff\x89\x00\x51\xd6\x57\x0e\x00" - b"\x05\x47\xe3\xfb\x2a\x00\x4d\xe6\xff\xfe\xee\xfc" - b"\xff\xf6\x5d\x00\x00\x00\x05\x47\x7b\x92\x8c\x66" - b"\x16\x00\x00\x21\x5c\x21\x00\x00\x00\x00\x00\x00" - b"\x00\x5c\xff\x5c\x00\x00\x00\x00\x00\x00\x00\x5c" - b"\xff\x5c\x00\x00\x00\x00\x00\x00\x00\x5c\xff\x5c" - b"\x00\x00\x00\x00\x00\x00\x00\x5c\xff\x5c\x1f\x75" - b"\x97\x8a\x44\x00\x00\x5c\xff\xaf\xf7\xfe\xf5\xff" - b"\xff\x9f\x00\x5c\xff\xff\x82\x0e\x00\x2b\xd4\xff" - b"\x4a\x5c\xff\xb3\x00\x00\x00\x00\x42\xff\x94\x5c" - b"\xff\x6b\x00\x00\x00\x00\x11\xff\xac\x5c\xff\x5c" - b"\x00\x00\x00\x00\x08\xff\xb0\x5c\xff\x5c\x00\x00" - b"\x00\x00\x08\xff\xb0\x5c\xff\x5c\x00\x00\x00\x00" - b"\x08\xff\xb0\x5c\xff\x5c\x00\x00\x00\x00\x08\xff" - b"\xb0\x5c\xff\x5c\x00\x00\x00\x00\x08\xff\xb0\x38" - b"\x93\x1f\xb7\xff\x83\x38\x93\x1d\x00\x00\x00\x32" - b"\x8c\x32\x5c\xff\x5c\x5c\xff\x5c\x5c\xff\x5c\x5c" - b"\xff\x5c\x5c\xff\x5c\x5c\xff\x5c\x5c\xff\x5c\x5c" - b"\xff\x5c\x5c\xff\x5c\x00\x00\x00\x38\x93\x1f\x00" - b"\x00\x00\xb7\xff\x83\x00\x00\x00\x38\x93\x1d\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x25\x8c\x3f\x00" - b"\x00\x00\x44\xff\x74\x00\x00\x00\x44\xff\x74\x00" - b"\x00\x00\x44\xff\x74\x00\x00\x00\x44\xff\x74\x00" - b"\x00\x00\x44\xff\x74\x00\x00\x00\x44\xff\x74\x00" - b"\x00\x00\x44\xff\x74\x00\x00\x00\x44\xff\x74\x00" - b"\x00\x00\x44\xff\x74\x00\x00\x00\x48\xff\x6d\x0c" - b"\x0f\x00\x90\xff\x47\x6f\xf8\xe8\xff\xc7\x02\x1e" - b"\x78\x8e\x65\x07\x00\x21\x5c\x21\x00\x00\x00\x00" - b"\x00\x00\x00\x5c\xff\x5c\x00\x00\x00\x00\x00\x00" - b"\x00\x5c\xff\x5c\x00\x00\x00\x00\x00\x00\x00\x5c" - b"\xff\x5c\x00\x00\x00\x00\x00\x00\x00\x5c\xff\x5c" - b"\x00\x00\x00\x00\x66\x8c\x34\x5c\xff\x5c\x00\x00" - b"\x01\x92\xff\x95\x01\x5c\xff\x5c\x00\x05\xa5\xff" - b"\x91\x00\x00\x5c\xff\x5c\x0a\xb7\xff\x8c\x00\x00" - b"\x00\x5c\xff\x6e\xc6\xff\xff\x02\x00\x00\x00\x5c" - b"\xff\xff\xfd\xff\xff\x80\x00\x00\x00\x5c\xff\xff" - b"\x59\x0e\xd7\xfe\x4b\x00\x00\x5c\xff\x9e\x00\x00" - b"\x26\xf0\xef\x23\x00\x5c\xff\x5c\x00\x00\x00\x4b" - b"\xfd\xd2\x0a\x5c\xff\x5c\x00\x00\x00\x00\x7b\xff" - b"\xa6\x21\x5c\x21\x5c\xff\x5c\x5c\xff\x5c\x5c\xff" - b"\x5c\x5c\xff\x5c\x5c\xff\x5c\x5c\xff\x5c\x5c\xff" - b"\x5c\x5c\xff\x5c\x5c\xff\x5c\x5c\xff\x5c\x5c\xff" - b"\x5c\x5c\xff\x5c\x5c\xff\x5c\x32\x8c\x29\x23\x7a" - b"\x98\x80\x29\x00\x00\x26\x7b\x97\x82\x2f\x00\x00" - b"\x5c\xff\xa3\xf9\xfc\xf7\xff\xfe\x61\x7c\xfe\xfd" - b"\xf7\xff\xfd\x6d\x00\x5c\xff\xff\x7c\x0a\x01\x44" - b"\xf1\xff\xff\x83\x0c\x01\x3f\xec\xfb\x1a\x5c\xff" - b"\xaf\x00\x00\x00\x00\x86\xff\xbe\x00\x00\x00\x00" - b"\x77\xff\x60\x5c\xff\x6a\x00\x00\x00\x00\x58\xff" - b"\x7a\x00\x00\x00\x00\x48\xff\x78\x5c\xff\x5c\x00" - b"\x00\x00\x00\x50\xff\x6c\x00\x00\x00\x00\x40\xff" - b"\x7c\x5c\xff\x5c\x00\x00\x00\x00\x50\xff\x6c\x00" - b"\x00\x00\x00\x40\xff\x7c\x5c\xff\x5c\x00\x00\x00" - b"\x00\x50\xff\x6c\x00\x00\x00\x00\x40\xff\x7c\x5c" - b"\xff\x5c\x00\x00\x00\x00\x50\xff\x6c\x00\x00\x00" - b"\x00\x40\xff\x7c\x5c\xff\x5c\x00\x00\x00\x00\x50" - b"\xff\x6c\x00\x00\x00\x00\x40\xff\x7c\x32\x8c\x29" - b"\x1f\x75\x97\x8a\x44\x00\x00\x5c\xff\x9f\xf7\xfe" - b"\xf5\xff\xff\x9f\x00\x5c\xff\xff\x82\x0e\x00\x2b" - b"\xd4\xff\x4a\x5c\xff\xb3\x00\x00\x00\x00\x42\xff" - b"\x94\x5c\xff\x6b\x00\x00\x00\x00\x11\xff\xac\x5c" - b"\xff\x5c\x00\x00\x00\x00\x08\xff\xb0\x5c\xff\x5c" - b"\x00\x00\x00\x00\x08\xff\xb0\x5c\xff\x5c\x00\x00" - b"\x00\x00\x08\xff\xb0\x5c\xff\x5c\x00\x00\x00\x00" - b"\x08\xff\xb0\x5c\xff\x5c\x00\x00\x00\x00\x08\xff" - b"\xb0\x00\x00\x00\x23\x77\x96\x88\x4c\x03\x00\x00" - b"\x00\x00\x82\xfd\xfe\xe9\xf7\xff\xcd\x20\x00\x00" - b"\x6c\xff\xbd\x22\x00\x07\x6d\xfb\xd3\x06\x05\xe8" - b"\xe5\x0b\x00\x00\x00\x00\x84\xff\x5a\x2a\xff\x96" - b"\x00\x00\x00\x00\x00\x2b\xff\x95\x36\xff\x88\x00" - b"\x00\x00\x00\x00\x1d\xff\xa1\x14\xfd\xbb\x00\x00" - b"\x00\x00\x00\x4f\xff\x7e\x00\xb3\xfe\x53\x00\x00" - b"\x00\x0f\xd3\xfb\x23\x00\x1b\xe4\xfd\xa6\x70\x85" - b"\xe2\xff\x6e\x00\x00\x00\x16\x9d\xf2\xff\xfe\xc8" - b"\x4c\x00\x00\x00\x00\x00\x00\x00\x13\x06\x00\x00" - b"\x00\x00\x32\x8c\x29\x18\x71\x96\x88\x47\x02\x00" - b"\x00\x5c\xff\x92\xf1\xfc\xe8\xfb\xff\xc1\x14\x00" - b"\x5c\xff\xff\xa0\x15\x00\x0f\x8b\xff\xba\x00\x5c" - b"\xff\xff\x01\x00\x00\x00\x00\xac\xff\x35\x5c\xff" - b"\xa7\x00\x00\x00\x00\x00\x50\xff\x6e\x5c\xff\x96" - b"\x00\x00\x00\x00\x00\x41\xff\x7a\x5c\xff\xdb\x00" - b"\x00\x00\x00\x00\x76\xff\x58\x5c\xff\xff\x32\x00" - b"\x00\x00\x22\xeb\xf2\x0d\x5c\xff\xfd\xf6\x98\x6f" - b"\x90\xef\xfd\x59\x00\x5c\xff\x60\x7f\xeb\xff\xfe" - b"\xc4\x42\x00\x00\x5c\xff\x5c\x00\x00\x13\x06\x00" - b"\x00\x00\x00\x5c\xff\x5c\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x5c\xff\x5c\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x2c\x7c\x2c\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x2c\x7d\x98\x82\x30\x02\x8c\x59\x00" - b"\x01\x8f\xff\xfe\xe9\xf7\xfe\x84\xff\xa4\x00\x76" - b"\xff\xbd\x22\x00\x07\x6f\xff\xff\xa4\x06\xeb\xe5" - b"\x0b\x00\x00\x00\x00\x86\xff\xa4\x2b\xff\x96\x00" - b"\x00\x00\x00\x00\x2b\xff\xa4\x36\xff\x88\x00\x00" - b"\x00\x00\x00\x1d\xff\xa4\x16\xfe\xbb\x00\x00\x00" - b"\x00\x00\x54\xff\xa4\x00\xbc\xfe\x53\x00\x00\x00" - b"\x10\xea\xff\xa4\x00\x24\xed\xfd\xa6\x70\x85\xe3" - b"\xf2\xff\xa4\x00\x00\x1f\xa8\xf7\xff\xf9\xa6\x2e" - b"\xff\xa4\x00\x00\x00\x00\x02\x14\x03\x00\x18\xff" - b"\xa4\x00\x00\x00\x00\x00\x00\x00\x00\x18\xff\xa4" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x18\xff\xa4\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x0b\x7c\x4f\x32\x8c" - b"\x29\x1e\x75\x72\x5c\xff\x91\xf5\xff\xc4\x5c\xff" - b"\xff\xa7\x2e\x09\x5c\xff\xc8\x01\x00\x00\x5c\xff" - b"\x78\x00\x00\x00\x5c\xff\x5d\x00\x00\x00\x5c\xff" - b"\x5c\x00\x00\x00\x5c\xff\x5c\x00\x00\x00\x5c\xff" - b"\x5c\x00\x00\x00\x5c\xff\x5c\x00\x00\x00\x00\x00" - b"\x32\x7f\x99\x88\x67\x1e\x00\x00\x84\xff\xf9\xdf" - b"\xf1\xff\xef\x0a\x20\xfe\xbf\x0f\x00\x00\x27\x51" - b"\x00\x3a\xff\x94\x00\x00\x00\x00\x00\x00\x09\xdc" - b"\xff\xc5\x80\x4d\x12\x00\x00\x00\x16\x8b\xd7\xfd" - b"\xff\xf9\x90\x03\x00\x00\x00\x00\x0b\x3d\xbc\xff" - b"\x5b\x00\x29\x00\x00\x00\x00\x4e\xff\x73\x4e\xfe" - b"\xbe\x7d\x68\x7f\xe2\xf8\x28\x16\x8b\xde\xff\xff" - b"\xfc\xc2\x3a\x00\x00\x00\x00\x0b\x17\x06\x00\x00" - b"\x00\x00\x11\xa0\x64\x00\x00\x00\x00\x1c\xff\xa0" - b"\x00\x00\x00\x66\xa8\xff\xff\x8c\x8c\x32\xa4\xfc" - b"\xff\xff\xe0\xe0\x50\x00\x1c\xff\xa0\x00\x00\x00" - b"\x00\x1c\xff\xa0\x00\x00\x00\x00\x1c\xff\xa0\x00" - b"\x00\x00\x00\x1c\xff\xa0\x00\x00\x00\x00\x1c\xff" - b"\xa0\x00\x00\x00\x00\x0d\xff\xb3\x00\x00\x00\x00" - b"\x00\xd0\xfb\x7e\x68\x72\x00\x00\x2e\xcf\xff\xfe" - b"\xab\x00\x00\x00\x00\x0c\x08\x00\x3f\x8c\x27\x00" - b"\x00\x00\x00\x1c\x8c\x4a\x74\xff\x48\x00\x00\x00" - b"\x00\x34\xff\x88\x74\xff\x48\x00\x00\x00\x00\x34" - b"\xff\x88\x74\xff\x48\x00\x00\x00\x00\x34\xff\x88" - b"\x74\xff\x48\x00\x00\x00\x00\x34\xff\x88\x73\xff" - b"\x49\x00\x00\x00\x00\x38\xff\x88\x66\xff\x61\x00" - b"\x00\x00\x00\x5d\xff\x88\x38\xff\xbe\x02\x00\x00" - b"\x0a\xda\xff\x88\x00\xc0\xff\xc9\x7f\x85\xdb\xf4" - b"\xff\x88\x00\x11\x9d\xf4\xff\xfb\xae\x36\xff\x88" - b"\x00\x00\x00\x02\x15\x05\x00\x00\x00\x00\x7f\x7b" - b"\x00\x00\x00\x00\x00\x00\x66\x84\x01\x93\xff\x36" - b"\x00\x00\x00\x00\x18\xf9\x9e\x00\x25\xfe\xa4\x00" - b"\x00\x00\x00\x82\xff\x2f\x00\x00\xb4\xf9\x18\x00" - b"\x00\x07\xe9\xbf\x00\x00\x00\x44\xff\x7f\x00\x00" - b"\x62\xff\x4f\x00\x00\x00\x00\xd3\xe7\x05\x00\xd1" - b"\xdc\x02\x00\x00\x00\x00\x64\xff\x5b\x42\xff\x6f" - b"\x00\x00\x00\x00\x00\x08\xeb\xc9\xb2\xf1\x0d\x00" - b"\x00\x00\x00\x00\x00\x84\xff\xff\x8f\x00\x00\x00" - b"\x00\x00\x00\x00\x19\xff\xff\x22\x00\x00\x00\x00" - b"\x6e\x76\x00\x00\x00\x00\x00\x63\x85\x01\x00\x00" - b"\x00\x00\x50\x86\x02\x80\xfe\x21\x00\x00\x00\x0c" - b"\xf4\xff\x3e\x00\x00\x00\x00\xdb\xaf\x00\x22\xfe" - b"\x7d\x00\x00\x00\x62\xff\xff\x9d\x00\x00\x00\x3d" - b"\xff\x50\x00\x00\xc2\xda\x00\x00\x00\xc3\xd2\xa0" - b"\xf2\x0a\x00\x00\x9c\xeb\x05\x00\x00\x63\xff\x38" - b"\x00\x24\xff\x6f\x3f\xff\x5b\x00\x09\xf1\x91\x00" - b"\x00\x00\x0e\xf6\x95\x00\x84\xf9\x13\x00\xdb\xba" - b"\x00\x5c\xff\x31\x00\x00\x00\x00\xa5\xec\x08\xe2" - b"\xa9\x00\x00\x7b\xfd\x1c\xbc\xd2\x00\x00\x00\x00" - b"\x00\x46\xff\x96\xff\x46\x00\x00\x1b\xfd\x96\xfd" - b"\x72\x00\x00\x00\x00\x00\x02\xe4\xff\xe1\x02\x00" - b"\x00\x00\xb7\xff\xfb\x17\x00\x00\x00\x00\x00\x00" - b"\x88\xff\x81\x00\x00\x00\x00\x55\xff\xb4\x00\x00" - b"\x00\x00\x31\x8c\x4f\x00\x00\x00\x00\x56\x8c\x26" - b"\x03\xbf\xf7\x2f\x00\x00\x38\xfa\xad\x00\x00\x17" - b"\xe6\xda\x0e\x13\xe2\xda\x0e\x00\x00\x00\x3e\xfb" - b"\xac\xb6\xf6\x2e\x00\x00\x00\x00\x00\x76\xff\xff" - b"\x5e\x00\x00\x00\x00\x00\x00\x4a\xff\xff\x36\x00" - b"\x00\x00\x00\x00\x1f\xed\xdf\xe5\xe2\x14\x00\x00" - b"\x00\x07\xcb\xf1\x24\x31\xf8\xbb\x02\x00\x00\x98" - b"\xff\x52\x00\x00\x69\xff\x85\x00\x5d\xff\x8d\x00" - b"\x00\x00\x00\xa8\xfe\x4d\x00\x7f\x7b\x00\x00\x00" - b"\x00\x00\x00\x65\x84\x01\x00\x92\xff\x38\x00\x00" - b"\x00\x00\x17\xf8\x9d\x00\x00\x23\xfd\xa8\x00\x00" - b"\x00\x00\x7f\xff\x2d\x00\x00\x00\xb0\xfb\x1c\x00" - b"\x00\x06\xe7\xbc\x00\x00\x00\x00\x40\xff\x87\x00" - b"\x00\x5c\xff\x4b\x00\x00\x00\x00\x00\xce\xed\x09" - b"\x00\xca\xd9\x01\x00\x00\x00\x00\x00\x5e\xff\x66" - b"\x3a\xff\x69\x00\x00\x00\x00\x00\x00\x06\xe7\xd4" - b"\xa9\xee\x0a\x00\x00\x00\x00\x00\x00\x00\x7d\xff" - b"\xff\x88\x00\x00\x00\x00\x00\x00\x00\x00\x14\xff" - b"\xff\x1c\x00\x00\x00\x00\x00\x00\x00\x00\x18\xff" - b"\xa6\x00\x00\x00\x00\x00\x00\x53\x04\x05\xa9\xfe" - b"\x31\x00\x00\x00\x00\x00\x2e\xff\xf6\xf1\xff\x85" - b"\x00\x00\x00\x00\x00\x00\x00\x3b\x81\x83\x46\x00" - b"\x00\x00\x00\x00\x00\x00\x18\x8c\x8c\x8c\x8c\x8c" - b"\x8c\x8c\x4e\x26\xe0\xe0\xe0\xe0\xe0\xff\xff\x7e" - b"\x00\x00\x00\x00\x00\x2e\xf4\xcc\x08\x00\x00\x00" - b"\x00\x12\xde\xe9\x1d\x00\x00\x00\x00\x03\xbb\xfa" - b"\x3d\x00\x00\x00\x00\x00\x8e\xff\x69\x00\x00\x00" - b"\x00\x00\x5b\xff\x9b\x00\x00\x00\x00\x00\x32\xf6" - b"\xc6\x06\x00\x00\x00\x00\x14\xe1\xff\x85\x6c\x6c" - b"\x6c\x6c\x4d\x48\xff\xff\xff\xff\xff\xff\xff\xb8" - b"\x00\x00\x08\x43\x58\x00\x10\xdb\xff\xf8\x00\x69" - b"\xff\x8e\x15\x00\x86\xff\x33\x00\x00\x88\xff\x30" - b"\x00\x00\x88\xff\x30\x00\x00\x88\xff\x30\x00\x00" - b"\x94\xff\x2c\x00\xa7\xf4\xd8\x09\x00\xc7\xff\xba" - b"\x04\x00\x00\x9b\xff\x2a\x00\x00\x88\xff\x30\x00" - b"\x00\x88\xff\x30\x00\x00\x88\xff\x30\x00\x00\x87" - b"\xff\x31\x00\x00\x6f\xff\x76\x00\x00\x1a\xed\xff" - b"\xed\x00\x00\x16\x64\x77\x0a\x5c\x2c\x1c\xff\x7c" - b"\x1c\xff\x7c\x1c\xff\x7c\x1c\xff\x7c\x1c\xff\x7c" - b"\x1c\xff\x7c\x1c\xff\x7c\x1c\xff\x7c\x1c\xff\x7c" - b"\x1c\xff\x7c\x1c\xff\x7c\x1c\xff\x7c\x1c\xff\x7c" - b"\x1c\xff\x7c\x1c\xff\x7c\x1c\xff\x7c\x0d\x7c\x3c" - b"\x3c\x4e\x19\x00\x00\x00\xa8\xff\xf6\x46\x00\x00" - b"\x0b\x49\xfd\xbf\x00\x00\x00\x00\xe0\xdd\x00\x00" - b"\x00\x00\xdc\xe0\x00\x00\x00\x00\xdc\xe0\x00\x00" - b"\x00\x00\xdc\xe0\x00\x00\x00\x00\xd8\xea\x00\x00" - b"\x00\x00\x8f\xff\xbd\x2f\x00\x00\x71\xff\xdc\x38" - b"\x00\x00\xd6\xef\x02\x00\x00\x00\xdc\xe0\x00\x00" - b"\x00\x00\xdc\xe0\x00\x00\x00\x00\xdc\xe0\x00\x00" - b"\x00\x00\xde\xdf\x00\x00\x00\x2a\xfa\xc5\x00\x00" - b"\x9e\xff\xfe\x5b\x00\x00\x51\x6e\x31\x00\x00\x00" - b"\x10\x9a\xce\x90\x0d\x00\x00\x8e\x47\x99\xe1\x88" - b"\xe0\xda\x46\x47\xf9\x36\xde\x4e\x00\x10\xab\xff" - b"\xff\xa2\x00\x1a\x05\x00\x00\x00\x25\x25\x00\x00" - b"\x00\x11\x4d\x6f\x6e\x74\x73\x65\x72\x72\x61\x74" - b"\x20\x4d\x65\x64\x69\x75\x6d\x00\x11\x4d\x6f\x6e" - b"\x74\x73\x65\x72\x72\x61\x74\x2d\x4d\x65\x64\x69" - b"\x75\x6d\x01" -) diff --git a/m5stack/modules/startup/dial/apps/dev.py b/m5stack/modules/startup/dial/apps/dev.py index bfa71de6..5415b12a 100644 --- a/m5stack/modules/startup/dial/apps/dev.py +++ b/m5stack/modules/startup/dial/apps/dev.py @@ -56,13 +56,13 @@ def on_view(self): self._mac_label = widgets.Label( "aabbcc112233", - 20, + 12, 140, w=120, - h=15, + h=20, fg_color=0x000000, bg_color=0xFEFEFE, - font="/system/common/font/Montserrat-Medium-18.vlw", + font="/system/common/font/Montserrat-Medium-16.vlw", ) self._mac_label.set_text(self._mac_text) @@ -74,7 +74,7 @@ def on_view(self): h=40, fg_color=0x000000, bg_color=0xFEFEFE, - font="/system/common/font/Montserrat-Medium-18.vlw", + font="/system/common/font/Montserrat-Medium-16.vlw", ) self._account_label.set_text(self._account_text) From c443d412b0e042f915d82b9860b393d1a2017dea Mon Sep 17 00:00:00 2001 From: lbuque Date: Thu, 3 Jul 2025 11:19:00 +0800 Subject: [PATCH 156/322] libs/m5ui: Add m5ui.M5Bar support. Signed-off-by: lbuque --- docs/en/m5ui/bar.rst | 419 ++++++++++++ docs/en/m5ui/index.rst | 1 + docs/en/refs/m5ui.bar.ref | 36 ++ docs/locales/zh_CN/LC_MESSAGES/m5ui/bar.po | 600 ++++++++++++++++++ .../bar/cores3_temperature_meter_example.m5f2 | 1 + .../bar/cores3_temperature_meter_example.py | 81 +++ m5stack/libs/m5ui/__init__.py | 1 + m5stack/libs/m5ui/bar.py | 112 ++++ m5stack/libs/m5ui/base.py | 32 + m5stack/libs/m5ui/manifest.py | 12 +- 10 files changed, 1294 insertions(+), 1 deletion(-) create mode 100644 docs/en/m5ui/bar.rst create mode 100644 docs/en/refs/m5ui.bar.ref create mode 100644 docs/locales/zh_CN/LC_MESSAGES/m5ui/bar.po create mode 100644 examples/m5ui/bar/cores3_temperature_meter_example.m5f2 create mode 100644 examples/m5ui/bar/cores3_temperature_meter_example.py create mode 100644 m5stack/libs/m5ui/bar.py diff --git a/docs/en/m5ui/bar.rst b/docs/en/m5ui/bar.rst new file mode 100644 index 00000000..befc6c19 --- /dev/null +++ b/docs/en/m5ui/bar.rst @@ -0,0 +1,419 @@ +.. currentmodule:: m5ui + +M5Bar +===== + +.. include:: ../refs/m5ui.bar.ref + +M5Bar is a widget that can be used to create progress bars in the user interface. It displays values within a specified range using a visual bar indicator. The bar can be customized with different colors, gradients, and can optionally display the current value as text. + + +UiFlow2 Example +--------------- + +Temperature meter +^^^^^^^^^^^^^^^^^ + +Open the |cores3_temperature_meter_example.m5f2| project in UiFlow2. + +This example demonstrates how to create a temperature meter that shows the current temperature. + +UiFlow2 Code Block: + + |cores3_temperature_meter_example.png| + +Example output: + + None + + +MicroPython Example +------------------- + +Temperature meter +^^^^^^^^^^^^^^^^^ + +This example demonstrates how to create a temperature meter that shows the current temperature. + +MicroPython Code Block: + + .. literalinclude:: ../../../examples/m5ui/bar/cores3_temperature_meter_example.py + :language: python + :linenos: + +Example output: + + None + + +**API** +------- + +M5Bar +^^^^^ + +.. autoclass:: m5ui.bar.M5Bar + :members: + + .. py:method:: set_value(value, anim_enable) + + Set the current value of the bar. + + :param int value: The value to set. + :param bool anim_enable: Whether to enable animation when changing the value. + :return: None + + UiFlow2 Code Block: + + |set_value.png| + + MicroPython Code Block: + + .. code-block:: python + + bar.set_value(75, True) + + .. py:method:: get_value() + + Get the current value of the bar. + + :return: The current value of the bar. + :rtype: int + + UiFlow2 Code Block: + + |get_value.png| + + MicroPython Code Block: + + .. code-block:: python + + current_value = bar.get_value() + + .. py:method:: set_range(min_value, max_value) + + Set the value range of the bar. + + :param int min_value: The minimum value. + :param int max_value: The maximum value. + :return: None + + UiFlow2 Code Block: + + |set_range.png| + + MicroPython Code Block: + + .. code-block:: python + + bar.set_range(0, 200) + + .. py:method:: get_min_value() + + Get the minimum value of the bar range. + + :return: The minimum value. + :rtype: int + + UiFlow2 Code Block: + + |get_min_value.png| + + MicroPython Code Block: + + .. code-block:: python + + min_val = bar.get_min_value() + + .. py:method:: get_max_value() + + Get the maximum value of the bar range. + + :return: The maximum value. + :rtype: int + + UiFlow2 Code Block: + + |get_max_value.png| + + MicroPython Code Block: + + .. code-block:: python + + max_val = bar.get_max_value() + + .. py:method:: set_flag(flag, value) + + Set a flag on the object. If ``value`` is True, the flag is added; if False, the flag is removed. + + :param int flag: The flag to set. + :param bool value: If True, the flag is added; if False, the flag is removed. + :return: None + + UiFlow2 Code Block: + + |set_flag.png| + + MicroPython Code Block: + + .. code-block:: python + + bar.set_flag(lv.obj.FLAG.HIDDEN, True) + + .. py:method:: toggle_flag(flag) + + Toggle a flag on the object. If the flag is set, it is removed; if not set, it is added. + + :param int flag: The flag to toggle. + :return: None + + UiFlow2 Code Block: + + |toggle_flag.png| + + MicroPython Code Block: + + .. code-block:: python + + bar.toggle_flag(lv.obj.FLAG.HIDDEN) + + .. py:method:: set_state(state, value) + + Set the state of the bar. If ``value`` is True, the state is set; if False, the state is unset. + + :param int state: The state to set. + :param bool value: If True, the state is set; if False, the state is unset. + :return: None + + UiFlow2 Code Block: + + |set_state.png| + + MicroPython Code Block: + + .. code-block:: python + + bar.set_state(lv.STATE.PRESSED, True) + + .. py:method:: toggle_state(state) + + Toggle the state of the bar. If the state is set, it is unset; if not set, it is set. + + :param int state: The state to toggle. + :return: None + + UiFlow2 Code Block: + + |toggle_state.png| + + MicroPython Code Block: + + .. code-block:: python + + bar.toggle_state(lv.STATE.PRESSED) + + .. py:method:: set_bg_color(color, opa, part) + + Set the background color of the bar. + + :param int color: The color to set. + :param int opa: The opacity of the color. + :param int part: The part of the object to apply the style to (e.g., lv.PART.MAIN). + :return: None + + UiFlow2 Code Block: + + |set_bg_color.png| + + |set_indicator_color.png| + + MicroPython Code Block: + + .. code-block:: python + + bar.set_bg_color(lv.color_hex(0x000000), 255, lv.PART.MAIN | lv.STATE.DEFAULT) + + bar.set_bg_color(lv.color_hex(0x000000), 255, lv.PART.INDICATOR | lv.STATE.DEFAULT) + + .. py:method:: set_bg_grad_color(color, opa, grad_color, grad_opd, grad_dir, part) + + Set the background gradient color of the bar. + + :param int color: The start color of the gradient, can be an integer (RGB). + :param int opa: The opacity of the start color (0-255). + :param int grad_color: The end color of the gradient, can be an integer (RGB). + :param int grad_opd: The opacity of the end color (0-255). + :param int grad_dir: The direction of the gradient (e.g., lv.GRAD_DIR.VER). + :param int part: The part of the object to apply the style to (e.g., lv.PART.MAIN). + :return: None + + UiFlow2 Code Block: + + |set_bg_grad_color.png| + + |set_indicator_grad_color.png| + + MicroPython Code Block: + + .. code-block:: python + + bar.set_bg_grad_color(0x00FF00, 255, 0xFF0000, 255, lv.GRAD_DIR.HOR, lv.PART.MAIN | lv.STATE.DEFAULT) + bar.set_bg_grad_color(0x00FF00, 255, 0xFF0000, 255, lv.GRAD_DIR.HOR, lv.PART.INDICATOR | lv.STATE.DEFAULT) + + .. py:method:: set_pos(x, y) + + Set the position of the bar. + + :param int x: The x-coordinate of the bar. + :param int y: The y-coordinate of the bar. + :return: None + + UiFlow2 Code Block: + + |set_pos.png| + + MicroPython Code Block: + + .. code-block:: python + + bar.set_pos(100, 100) + + .. py:method:: set_x(x) + + Set the x-coordinate of the bar. + + :param int x: The x-coordinate of the bar. + :return: None + + UiFlow2 Code Block: + + |set_x.png| + + MicroPython Code Block: + + .. code-block:: python + + bar.set_x(100) + + .. py:method:: set_y(y) + + Set the y-coordinate of the bar. + + :param int y: The y-coordinate of the bar. + :return: None + + UiFlow2 Code Block: + + |set_y.png| + + MicroPython Code Block: + + .. code-block:: python + + bar.set_y(100) + + .. py:method:: set_size(width, height) + + Set the size of the bar. + + :param int width: The width of the bar. + :param int height: The height of the bar. + :return: None + + UiFlow2 Code Block: + + |set_size.png| + + MicroPython Code Block: + + .. code-block:: python + + bar.set_size(200, 30) + + .. py:method:: set_width(width) + + Set the width of the bar. + + :param int width: The width of the bar. + :return: None + + UiFlow2 Code Block: + + |set_width.png| + + MicroPython Code Block: + + .. code-block:: python + + bar.set_width(200) + + .. py:method:: get_width() + + Get the width of the bar. + + :return: The width of the bar. + :rtype: int + + UiFlow2 Code Block: + + |get_width.png| + + MicroPython Code Block: + + .. code-block:: python + + width = bar.get_width() + + .. py:method:: set_height(height) + + Set the height of the bar. + + :param int height: The height of the bar. + :return: None + + UiFlow2 Code Block: + + |set_height.png| + + MicroPython Code Block: + + .. code-block:: python + + bar.set_height(30) + + .. py:method:: get_height() + + Get the height of the bar. + + :return: The height of the bar. + :rtype: int + + UiFlow2 Code Block: + + |get_height.png| + + MicroPython Code Block: + + .. code-block:: python + + height = bar.get_height() + + .. py:method:: align_to(obj, align, x, y) + + Align the bar to another object. + + :param lv.obj obj: The object to align to. + :param int align: The alignment type. + :param int x: The x-offset from the aligned object. + :param int y: The y-offset from the aligned object. + :return: None + + UiFlow2 Code Block: + + |align_to.png| + + MicroPython Code Block: + + .. code-block:: python + + bar.align_to(page_0, lv.ALIGN.CENTER, 0, 0) diff --git a/docs/en/m5ui/index.rst b/docs/en/m5ui/index.rst index 3f41ab34..88a0e2ce 100644 --- a/docs/en/m5ui/index.rst +++ b/docs/en/m5ui/index.rst @@ -37,3 +37,4 @@ Classes button.rst switch.rst image.rst + bar.rst diff --git a/docs/en/refs/m5ui.bar.ref b/docs/en/refs/m5ui.bar.ref new file mode 100644 index 00000000..b4639c34 --- /dev/null +++ b/docs/en/refs/m5ui.bar.ref @@ -0,0 +1,36 @@ + +.. |cores3_temperature_meter_example.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/bar/cores3_temperature_meter_example.png + +.. |align_to.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/bar/align_to.png +.. |get_height.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/bar/get_height.png +.. |get_max_value.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/bar/get_max_value.png +.. |get_min_value.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/bar/get_min_value.png +.. |get_value.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/bar/get_value.png +.. |get_width.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/bar/get_width.png +.. |set_bg_color.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/bar/set_bg_color.png +.. |set_bg_grad_color.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/bar/set_bg_grad_color.png +.. |set_flag.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/bar/set_flag.png +.. |set_height.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/bar/set_height.png +.. |set_indicator_color.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/bar/set_indicator_color.png +.. |set_indicator_grad_color.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/bar/set_indicator_grad_color.png +.. |set_indicator_radius.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/bar/set_indicator_radius.png +.. |set_pos.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/bar/set_pos.png +.. |set_range.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/bar/set_range.png +.. |set_size.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/bar/set_size.png +.. |set_state.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/bar/set_state.png +.. |set_style_radius.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/bar/set_style_radius.png +.. |set_value.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/bar/set_value.png +.. |set_width.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/bar/set_width.png +.. |set_x.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/bar/set_x.png +.. |set_y.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/bar/set_y.png +.. |toggle_flag.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/bar/toggle_flag.png +.. |toggle_state.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/bar/toggle_state.png + +.. |cores3_temperature_meter_example.m5f2| raw:: html + + + cores3_temperature_meter_example.m5f2 + diff --git a/docs/locales/zh_CN/LC_MESSAGES/m5ui/bar.po b/docs/locales/zh_CN/LC_MESSAGES/m5ui/bar.po new file mode 100644 index 00000000..ad723316 --- /dev/null +++ b/docs/locales/zh_CN/LC_MESSAGES/m5ui/bar.po @@ -0,0 +1,600 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2016 - 2025 M5Stack Technology Co., Ltd +# This file is distributed under the same license as the UIFlow2 Programming +# Guide package. +# FIRST AUTHOR , 2025. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: UIFlow2 Programming Guide \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-07-10 15:18+0800\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: zh_CN\n" +"Language-Team: zh_CN \n" +"Plural-Forms: nplurals=1; plural=0;\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.16.0\n" + +#: ../../en/m5ui/bar.rst:4 ../../en/m5ui/bar.rst:56 +#: 7f771fcb87084bac9a6eea3b4b0fc21a c8c828b36f94489a9af2e5c2c8e9e5ff +msgid "M5Bar" +msgstr "" + +#: ../../en/m5ui/bar.rst:8 b75806c51a5a43f786305a9f04cf6df9 +msgid "" +"M5Bar is a widget that can be used to create progress bars in the user " +"interface. It displays values within a specified range using a visual bar" +" indicator. The bar can be customized with different colors, gradients, " +"and can optionally display the current value as text." +msgstr "M5Bar 是一个可用于在用户界面中创建进度条的小部件。它使用可视的进度条指示器显示指定范围内的值。进度条可以自定义颜色和渐变,并可选择将当前值显示为文本。" + +#: ../../en/m5ui/bar.rst:15 b89c5c05f8454f98a7371da10b8ec8f7 +msgid "UiFlow2 Example" +msgstr "UiFlow2 应用示例" + +#: ../../en/m5ui/bar.rst:18 ../../en/m5ui/bar.rst:37 +#: 174807f6e07d4172915c78450159023f 3fb0fb22c3004f1fa9a6dd826df6219c +msgid "Temperature meter" +msgstr "温度计" + +#: ../../en/m5ui/bar.rst:20 c819a01a2f2742d1bbf8789e8afc7f40 +msgid "Open the |cores3_temperature_meter_example.m5f2| project in UiFlow2." +msgstr "在 UiFlow2 中打开 |cores3_temperature_meter_example.m5f2| 项目。" + +#: ../../en/m5ui/bar.rst:22 ../../en/m5ui/bar.rst:39 +#: 77f74d45d33f41068a01de256cf8f488 80e2845f0534430195a48a6ca98790c1 +msgid "" +"This example demonstrates how to create a temperature meter that shows " +"the current temperature." +msgstr "此示例演示如何创建显示当前温度的温度计。" + +#: ../../en/m5ui/bar.rst:24 ../../en/m5ui/bar.rst:69 ../../en/m5ui/bar.rst:86 +#: ../../en/m5ui/bar.rst:104 ../../en/m5ui/bar.rst:121 +#: ../../en/m5ui/bar.rst:138 ../../en/m5ui/bar.rst:156 +#: ../../en/m5ui/bar.rst:173 ../../en/m5ui/bar.rst:191 +#: ../../en/m5ui/bar.rst:208 ../../en/m5ui/bar.rst:227 +#: ../../en/m5ui/bar.rst:253 ../../en/m5ui/bar.rst:274 +#: ../../en/m5ui/bar.rst:291 ../../en/m5ui/bar.rst:308 +#: ../../en/m5ui/bar.rst:326 ../../en/m5ui/bar.rst:343 +#: ../../en/m5ui/bar.rst:360 ../../en/m5ui/bar.rst:377 +#: ../../en/m5ui/bar.rst:394 ../../en/m5ui/bar.rst:414 +#: 14ac9fa1e91e488383a31ae1d34dae20 1f0de37df98542e28caef97aa28e6364 +#: 213e1a83fc9b44bd8cd6bf20bb68c5f2 2144611aa8dc443bb695a40009f91493 +#: 216f886babc64ef896a1a56769a1242c 244f4dfdcc014bca88c507eb801bcc00 +#: 3931f0577c7446c3bb6b6377e3966b40 3a07c1d5e22c4878baae1cc01105b6a7 +#: 437b6bc5eb5344449ecf04d10910960e 478cd6a60e8e488998da3f11fd3ddd1d +#: 4f37b4e566854e0f8a8de9c9d9270ba5 648b9ce044d249ab855ef27a31e99b56 +#: 78ee2b2c82534de7b649b00826c38c02 90710ded42aa4ec2970a669b17d77326 +#: 93321cbed4194be48e35cb45156526ea a10d1928441f46f0adb55613d78b3cd7 +#: ad91355943ef4ac09682f8c0e9e42bad adfa1c7167d04a369d3c336d48a887cc +#: b97b34aff371428fb3a7cc0bc89197cf c9cc0df6c9204bcabada5cc0e745053e +#: d429dac87cc9441381efd739bc4ac6c8 ec83dbd0bb684dd28e03556fee46d5bc +#: m5ui.bar.M5Bar:16 of +msgid "UiFlow2 Code Block:" +msgstr "UiFlow2 代码块:" + +#: ../../en/m5ui/bar.rst:26 31520ecab446475a924e1d91f225ec17 +msgid "|cores3_temperature_meter_example.png|" +msgstr "" + +#: ../../en/refs/m5ui.bar.ref:2 7e5b345f6b0d44809c14ee408d03aae4 +msgid "cores3_temperature_meter_example.png" +msgstr "" + +#: ../../en/m5ui/bar.rst:28 ../../en/m5ui/bar.rst:47 +#: 94f4454f5ce748909df2ed88e3941d38 ed9641031be844109a8c06a565822756 +msgid "Example output:" +msgstr "示例输出:" + +#: ../../en/m5ui/bar.rst:30 ../../en/m5ui/bar.rst:49 ../../en/m5ui/bar.rst:67 +#: ../../en/m5ui/bar.rst:102 ../../en/m5ui/bar.rst:154 +#: ../../en/m5ui/bar.rst:171 ../../en/m5ui/bar.rst:189 +#: ../../en/m5ui/bar.rst:206 ../../en/m5ui/bar.rst:225 +#: ../../en/m5ui/bar.rst:251 ../../en/m5ui/bar.rst:272 +#: ../../en/m5ui/bar.rst:289 ../../en/m5ui/bar.rst:306 +#: ../../en/m5ui/bar.rst:324 ../../en/m5ui/bar.rst:341 +#: ../../en/m5ui/bar.rst:375 ../../en/m5ui/bar.rst:412 +#: 0ca8eba8a17c4207878e579f25ef0700 204d8f6f61b64bcc80bbe7ae7aee9631 +#: 27cc25d97d004a41a43955719fc75ffb 297be05ae3964366ae770d1ee26faecb +#: 3214bc11b81a43f38cf9df7d8284b7b1 433de50f69e9492e871b02485bb8411b +#: 4f00519b771e48ada81d06dcf6e23bc2 6c7a87b03375443bbdff1d6281e75672 +#: 7f3f3541d8134354b3c607fb0f861257 8cb523c0edb543a391c99b5c499b24ee +#: b8be6ba4db5c43f69bae787d04835a02 bb8ec1a30fe14e9b85b3bc99dd6a541f +#: c393bb592fed40b19e1e2a1bff4b3a55 d3c511eb0cd54a4081abe72da8a5234e +#: d89ca01810a140beac528898e85f03b9 df788af995914b43baa7b74348d1ebc3 +#: e9ac69efd9ab4343a8e34ef7ab74cb3d f1216f832303499ea6083cd6bfe1cf3d +#: f8aea44dd3a44f45ab70e583e2ac1740 m5ui.bar.M5Bar:14 m5ui.bar.M5Bar:18 of +msgid "None" +msgstr "无" + +#: ../../en/m5ui/bar.rst:34 c27dad8b2e684f8da26aed08ac6d228b +msgid "MicroPython Example" +msgstr "MicroPython 应用示例" + +#: ../../en/m5ui/bar.rst:41 ../../en/m5ui/bar.rst:73 ../../en/m5ui/bar.rst:90 +#: ../../en/m5ui/bar.rst:108 ../../en/m5ui/bar.rst:125 +#: ../../en/m5ui/bar.rst:142 ../../en/m5ui/bar.rst:160 +#: ../../en/m5ui/bar.rst:177 ../../en/m5ui/bar.rst:195 +#: ../../en/m5ui/bar.rst:212 ../../en/m5ui/bar.rst:233 +#: ../../en/m5ui/bar.rst:259 ../../en/m5ui/bar.rst:278 +#: ../../en/m5ui/bar.rst:295 ../../en/m5ui/bar.rst:312 +#: ../../en/m5ui/bar.rst:330 ../../en/m5ui/bar.rst:347 +#: ../../en/m5ui/bar.rst:364 ../../en/m5ui/bar.rst:381 +#: ../../en/m5ui/bar.rst:398 ../../en/m5ui/bar.rst:418 +#: 08784b5e7069434580c9bbf118474958 0eddefb61c80440d80fe011b40b1605e +#: 164b02e035df47febc9e55fd15a3e69c 540654f5955744fc95607b8e4bf8da33 +#: 5a4b78c2068743e5af033b9f3b533505 5ac86e81d0514f4586890612e777fb0f +#: 6305bc868d614659b0362a24686da030 6ea40e4c51f441dba52e973acc4a38ea +#: 73e56632f8e54f06aeedbdf64766ffcd 7fb393b8eb194ff6b6143bad8e16a25f +#: 8399f2c72b4445b5b92545967e5d0070 8c6a27fe8bfb45bca58f558d3de76e81 +#: 8dee58fb46774200a7ed2685742a93a9 8ecd12417431453b917cc9d83b953173 +#: 9ebd85f5d1354d9697eeafc8d903a174 a5ca9b8b04bc4fb4ba4ef7010741871d +#: aa46d40fbbf84b2f956b2e0f52878857 ad5c50a8df8746d69da713442ab14c92 +#: e6e680531f704d8682de0412cef9a13f ea4be4d4a84c47d4986b03230987eb29 +#: ee22d3e0fb2944638b83ce13a414707d eee6e0676cd6412099d0f1d05eacbf27 +#: m5ui.bar.M5Bar:20 of +msgid "MicroPython Code Block:" +msgstr "MicroPython 代码块:" + +#: ../../en/m5ui/bar.rst:53 1d8c4aabec064dc694caa6bcb636895c +msgid "**API**" +msgstr "API参考" + +#: 8d9307f940fa45e5ac0dad32f2366b8a m5ui.bar.M5Bar:1 of +msgid "Bases: :py:class:`~lvgl.bar`" +msgstr "" + +#: be3f0ba04f0d449f940029b2b538c3e8 m5ui.bar.M5Bar:1 of +msgid "Initialize a new M5Bar widget." +msgstr "初始化一个新的 M5Bar 控件。" + +#: ../../en/m5ui/bar.rst 145ca7d74d924234b21129b90d62e79e +#: 26aa351b5319488293f0eee287396238 33bac1292aee41379f98a07d43b5a60a +#: 4b64e689f3f8442a9f71ffec20a75db9 59cd565babf3489d8e3f7c0bbc607326 +#: 5f0d8bf067924020a0636f1f365e4b9d 8ade1f7f708a4c3c8899830acf609a5c +#: 9cbda461fb0a400ca4e28cc8af3f6b0c c4c4eb32b8044aa3a6089737bffc4a21 +#: ccdadc171bb449338519f8c69d729263 cf5d3dabf008488c8e4bd84d34963a89 +#: d2e778f8c7cd450abf4a19d0d1354d9c eb548c0ce102431d8401ff5833e0f918 +#: ee70107f933e408cb863adb2f855acc1 f1504ee437a842b79fcdf9cb0d7cd976 +#: f2011de4cdf747a0a133bcf66c055802 +msgid "Parameters" +msgstr "" + +#: ../../en/m5ui/bar.rst:270 ../../en/m5ui/bar.rst:288 +#: 164dd15eed5f4385bb9c693e237e37fb 7cd8f9f8029748c6933663addd422a4f +#: d1f23ea8b8eb46b7a468365bf8c16b02 m5ui.bar.M5Bar:3 of +msgid "The x-coordinate of the bar." +msgstr "进度条的 x 坐标。" + +#: ../../en/m5ui/bar.rst:271 ../../en/m5ui/bar.rst:305 +#: 00f2a11d23514f068eb8ab14180d6856 fb7aec40157541bc9ea96cf42c53a17e +#: fd34d9295d714870a6da89f9e30000bf m5ui.bar.M5Bar:4 of +msgid "The y-coordinate of the bar." +msgstr "进度条的 y 坐标。" + +#: ../../en/m5ui/bar.rst:322 ../../en/m5ui/bar.rst:340 +#: ../../en/m5ui/bar.rst:357 3666c46a9d5943e99cd99cd942e393e1 +#: 8118cb7186ac4b388265a65ebf62f604 ba930568309542e581e194d6324fea12 +#: d67f117acc384521aea36ddf5d6eca0e m5ui.bar.M5Bar:5 of +msgid "The width of the bar." +msgstr "进度条的宽度。" + +#: ../../en/m5ui/bar.rst:323 ../../en/m5ui/bar.rst:374 +#: ../../en/m5ui/bar.rst:391 11dd1ed3125e41a6a6932dfd18d5912b +#: 37ee989d281546c2a7b6d4571d382c79 46835a835f754d12b90a53bb3af20a56 +#: ad5c944449c04d4f81ce458123d52e5b m5ui.bar.M5Bar:6 of +msgid "The height of the bar." +msgstr "进度条的高度。" + +#: 64d7be847f4d4fadb01a7277ac71f15d m5ui.bar.M5Bar:7 of +msgid "The minimum value of the bar range." +msgstr "进度条的最小值。" + +#: ff3244b1145c4088bb57b8172c4aa0f0 m5ui.bar.M5Bar:8 of +msgid "The maximum value of the bar range." +msgstr "进度条的最大值。" + +#: 7a1a8d5c270542fda7d500d0719b6500 m5ui.bar.M5Bar:9 of +msgid "The initial value of the bar." +msgstr "进度条的初始值。" + +#: 533639e76a5d489a98d7b3c6b72ff1f4 m5ui.bar.M5Bar:10 of +msgid "Whether to display the current value as text." +msgstr "是否显示当前值为文本。" + +#: b080c0ef91264098a7993de26ed6148b m5ui.bar.M5Bar:11 of +msgid "The background color of the bar." +msgstr "进度条的背景颜色。" + +#: b2435ed2028942199c155f717e237a32 m5ui.bar.M5Bar:12 of +msgid "The indicator color of the bar." +msgstr "进度条的指示颜色。" + +#: 8cadb4469dea4d47815f584eb80f8700 m5ui.bar.M5Bar:13 of +msgid "The parent object. If None, uses the active screen." +msgstr "父对象。如果为 None,则使用当前活动屏幕。" + +#: ../../en/m5ui/bar.rst 055a258a435645d7a4282c6f0985084e +#: 09fe6b888b5c4fd9a7aa8a6a02c703a6 10423ca070f64b13b988c72d4838433c +#: 3a8dd1e3e0a84a15aa777ff2a1b9d4b4 72590f94dda147909351b27001257bb4 +#: 7594099ca9804698a134ca63edf5191a 7b3e886d18bc4bb0ab255087f9b43c3d +#: 7d0cfcadc35f4e7d8f63001eb8b7d48d 87b1aef05cac41f7802f550b7b3d2ddc +#: 8bb8217766694b56995a8051f844b563 a22d0335fd9343829e585e18ed58c077 +#: a6638eed66b841cc83e83b6de63aecc7 ad9d373d167b422f8c6c66a1fb1a937a +#: b0b72a2dbb9e44e595f8dc601ec17cba c04f315892b74a189ee4fd06f2a9465e +#: c6f95a608e884a51b4198b885bf4d5cf cb03530ab2754748bc48a49505cd73fc +#: dcab1cd5edcd4b699f7770d1db292762 e4731f3bb6244eff861ed3db9274de3b +#: e76927a053524a099a08e6c472c6102c ec63bb6f0a0b4dfbb527b3d34bd71425 +msgid "Returns" +msgstr "" + +#: ../../en/m5ui/bar.rst:63 a26c0f00a5384c1fa7ba1316c05888b8 +msgid "Set the current value of the bar." +msgstr "设置进度条的当前值。" + +#: ../../en/m5ui/bar.rst:65 026cc487113c4692b9b6072fd262ec06 +msgid "The value to set." +msgstr "要设置的值。" + +#: ../../en/m5ui/bar.rst:66 0d10ef56bbbf4724a6ac737b317e0201 +msgid "Whether to enable animation when changing the value." +msgstr "更改值时是否启用动画。" + +#: ../../en/m5ui/bar.rst:71 99b6f2b6a4d94073aee3275082eaf0cd +msgid "|set_value.png|" +msgstr "" + +#: ../../en/refs/m5ui.bar.ref:22 8f2dc4bf9dc9483cb6a4477434b15328 +msgid "set_value.png" +msgstr "" + +#: ../../en/m5ui/bar.rst:81 5f3fd4d9a8b8462eb10a85af98364029 +msgid "Get the current value of the bar." +msgstr "获取进度条的当前值。" + +#: ../../en/m5ui/bar.rst:83 93ef3cce9d71450e9104776491ccf84b +msgid "The current value of the bar." +msgstr "进度条的当前值。" + +#: ../../en/m5ui/bar.rst 3272eac0258447f6adb55a8685f24b43 +#: 4a81aa357fbc4253aa456c415ff666b1 af319c1c979c4badb2ac71532f5a20e3 +#: f695c4b514e14589af6068e6dbe65106 fd79c73f09de4847bdbdc2746fe5388b +msgid "Return type" +msgstr "" + +#: ../../en/m5ui/bar.rst:88 9cbe4e71f9784cb59144b09777bc4488 +msgid "|get_value.png|" +msgstr "" + +#: ../../en/refs/m5ui.bar.ref:8 a4dcc407ee844a4f9a78df8a9906fc24 +msgid "get_value.png" +msgstr "" + +#: ../../en/m5ui/bar.rst:98 69ff705e9fa140b48e435d19c555d513 +msgid "Set the value range of the bar." +msgstr "设置进度条的取值范围。" + +#: ../../en/m5ui/bar.rst:100 ../../en/m5ui/bar.rst:118 +#: 7336fc209ae6491688de6e7beb5b874a d6e7763d3efe4df7aab75d325a1f0dcc +msgid "The minimum value." +msgstr "最小值。" + +#: ../../en/m5ui/bar.rst:101 ../../en/m5ui/bar.rst:135 +#: 15d143df95974311974fad7e0e471e26 311c11e8fa1b4fe29bd64d372ce4275a +msgid "The maximum value." +msgstr "最大值。" + +#: ../../en/m5ui/bar.rst:106 56f1eefd3acf405ea6da8e75249b6d85 +msgid "|set_range.png|" +msgstr "" + +#: ../../en/refs/m5ui.bar.ref:18 7c0decce764a4563bfc7216cdee38377 +msgid "set_range.png" +msgstr "" + +#: ../../en/m5ui/bar.rst:116 f98412f8b8ed4ddebbcbb72887a099b9 +msgid "Get the minimum value of the bar range." +msgstr "获取进度条范围的最小值。" + +#: ../../en/m5ui/bar.rst:123 e50ef2ee7a89494688db13892af74dd1 +msgid "|get_min_value.png|" +msgstr "" + +#: ../../en/refs/m5ui.bar.ref:7 84d0f6e77ae04fcc828ec0560958a8f6 +msgid "get_min_value.png" +msgstr "" + +#: ../../en/m5ui/bar.rst:133 78c29afb767c4511b4ce57d772ad19c1 +msgid "Get the maximum value of the bar range." +msgstr "获取进度条范围的最大值。" + +#: ../../en/m5ui/bar.rst:140 2a526261edb341d59f8f5611e289e886 +msgid "|get_max_value.png|" +msgstr "" + +#: ../../en/refs/m5ui.bar.ref:6 dee0777e465d45198a46c6eec20dbc12 +msgid "get_max_value.png" +msgstr "" + +#: ../../en/m5ui/bar.rst:150 2ab744b2691b4ab9bcf40cbc5462d8d1 +msgid "" +"Set a flag on the object. If ``value`` is True, the flag is added; if " +"False, the flag is removed." +msgstr "在对象上设置一个标志。如果 `value` 为 True,则添加该标志;如果 `value` 为 False,则移除该标志。" + +#: ../../en/m5ui/bar.rst:152 49ba704b1bfd4909838d3f6ac1af5e52 +msgid "The flag to set." +msgstr "要设置的标志。" + +#: ../../en/m5ui/bar.rst:153 8287b052e9f8443a95217eedce87ad7b +msgid "If True, the flag is added; if False, the flag is removed." +msgstr "为 True 时添加标志,为 False 时移除标志。" + +#: ../../en/m5ui/bar.rst:158 d9b3d58861604f728e4bd525a592c6e8 +msgid "|set_flag.png|" +msgstr "" + +#: ../../en/refs/m5ui.bar.ref:12 82f522b7d12f45b3b7437f5c49a7c1fc +msgid "set_flag.png" +msgstr "" + +#: ../../en/m5ui/bar.rst:168 98ccb53404c6446eb92c21fdc6b14eea +msgid "" +"Toggle a flag on the object. If the flag is set, it is removed; if not " +"set, it is added." +msgstr "切换对象上的标志。如果设置了标志,则移除该标志;如果未设置,则添加该标志。" + +#: ../../en/m5ui/bar.rst:170 f44afeaeb7e04ee2b610e01f455b6ecc +msgid "The flag to toggle." +msgstr "要切换的标志。" + +#: ../../en/m5ui/bar.rst:175 5293c96d15bb498fbc3ef204a3db5267 +msgid "|toggle_flag.png|" +msgstr "" + +#: ../../en/refs/m5ui.bar.ref:26 1927f8fa11fc4b0e9f11e89e327d0bb0 +msgid "toggle_flag.png" +msgstr "" + +#: ../../en/m5ui/bar.rst:185 c64af2fa67094727a8ef7b3f3f7d6a71 +msgid "" +"Set the state of the bar. If ``value`` is True, the state is set; if " +"False, the state is unset." +msgstr "设置栏的状态。如果 ``value`` 为 True,则设置状态;如果为 False,则取消设置状态。" + +#: ../../en/m5ui/bar.rst:187 bbc928f4c42146c29600d1d6c5da7978 +msgid "The state to set." +msgstr "要设置的状态。" + +#: ../../en/m5ui/bar.rst:188 ba275bba9319467ab6bd1a6f5907c2e1 +msgid "If True, the state is set; if False, the state is unset." +msgstr "为 True 时设置状态,为 False 时取消状态。" + +#: ../../en/m5ui/bar.rst:193 9ef39f71537343399e45c2216eec3e6c +msgid "|set_state.png|" +msgstr "" + +#: ../../en/refs/m5ui.bar.ref:20 9dc42f7fdde94da18c4639cd6b24197a +msgid "set_state.png" +msgstr "" + +#: ../../en/m5ui/bar.rst:203 fae83d9fbf0b48f0aa43974c76cc52ab +msgid "" +"Toggle the state of the bar. If the state is set, it is unset; if not " +"set, it is set." +msgstr "切换栏的状态。如果状态已设置,则取消设置;如果未设置,则设置状态。" + +#: ../../en/m5ui/bar.rst:205 107792307de246f2ac359375c8e53a8a +msgid "The state to toggle." +msgstr "要切换的状态。" + +#: ../../en/m5ui/bar.rst:210 af8884b3fb85437bb5bc7a7a3002d7fb +msgid "|toggle_state.png|" +msgstr "" + +#: ../../en/refs/m5ui.bar.ref:27 ab5588f78861434c9762357a7dc5da05 +msgid "toggle_state.png" +msgstr "" + +#: ../../en/m5ui/bar.rst:220 dbe8dfd00c8a479cb5068e7b272e7b02 +msgid "Set the background color of the bar." +msgstr "设置进度条的背景颜色。" + +#: ../../en/m5ui/bar.rst:222 d83dccd4f842456eaadb0ec5c4d6b954 +msgid "The color to set." +msgstr "要设置的颜色。" + +#: ../../en/m5ui/bar.rst:223 4cd6bccc5ee144c9b4e22f8961397951 +msgid "The opacity of the color." +msgstr "颜色的不透明度。" + +#: ../../en/m5ui/bar.rst:224 ../../en/m5ui/bar.rst:250 +#: 4165dc6969d84335a5d9e2d4bdfa7d21 ecf0b902ae8347649ad85c47aca43341 +msgid "The part of the object to apply the style to (e.g., lv.PART.MAIN)." +msgstr "应用样式的对象部分(如 lv.PART.MAIN)。" + +#: ../../en/m5ui/bar.rst:229 22fd1e59f1284c619e68dc242dff9a4f +msgid "|set_bg_color.png|" +msgstr "" + +#: ../../en/refs/m5ui.bar.ref:10 c8d814f30f014d3c812f564cf142a5b3 +msgid "set_bg_color.png" +msgstr "" + +#: ../../en/m5ui/bar.rst:231 9399030983334b3abe8464494e5f1a2c +msgid "|set_indicator_color.png|" +msgstr "" + +#: ../../en/refs/m5ui.bar.ref:14 fcf14bf155334566850217f79003b9e3 +msgid "set_indicator_color.png" +msgstr "" + +#: ../../en/m5ui/bar.rst:243 d9607fa6dabe4718a0b55ff0c42ee68e +msgid "Set the background gradient color of the bar." +msgstr "设置条形的背景渐变颜色。" + +#: ../../en/m5ui/bar.rst:245 51ebffc391114bbcb7b182f6fd127735 +msgid "The start color of the gradient, can be an integer (RGB)." +msgstr "渐变起始颜色,可为整数(RGB)。" + +#: ../../en/m5ui/bar.rst:246 11f496c2becb435aae103f5b3f2cc66d +msgid "The opacity of the start color (0-255)." +msgstr "起始颜色的不透明度(0-255)。" + +#: ../../en/m5ui/bar.rst:247 0dcb72bc434c47d2861e8a567eb35178 +msgid "The end color of the gradient, can be an integer (RGB)." +msgstr "渐变结束颜色,可为整数(RGB)。" + +#: ../../en/m5ui/bar.rst:248 7a44867769c946eba66f53338d30876e +msgid "The opacity of the end color (0-255)." +msgstr "结束颜色的不透明度(0-255)。" + +#: ../../en/m5ui/bar.rst:249 bbf11cac35804e50b8f8d017b846d42a +msgid "The direction of the gradient (e.g., lv.GRAD_DIR.VER)." +msgstr "渐变方向(如 lv.GRAD_DIR.VER)。" + +#: ../../en/m5ui/bar.rst:255 b30dda6dafc5450192400f6a8a3b311c +msgid "|set_bg_grad_color.png|" +msgstr "" + +#: ../../en/refs/m5ui.bar.ref:11 585b7013e6cb4d269a1405ea9beae8c3 +msgid "set_bg_grad_color.png" +msgstr "" + +#: ../../en/m5ui/bar.rst:257 08f4be66631b442da0d3fc1a367fa16a +msgid "|set_indicator_grad_color.png|" +msgstr "" + +#: ../../en/refs/m5ui.bar.ref:15 39862841462d4845be5e9770b2f2132d +msgid "set_indicator_grad_color.png" +msgstr "" + +#: ../../en/m5ui/bar.rst:268 68a8b368c16841cb9bdf25acc8e949aa +msgid "Set the position of the bar." +msgstr "设置进度条的位置。" + +#: ../../en/m5ui/bar.rst:276 8e8838cbbc4345f09bd043637f6c830f +msgid "|set_pos.png|" +msgstr "" + +#: ../../en/refs/m5ui.bar.ref:17 5fce0bd13f4048f2a89f4fe8f5d1c7dc +msgid "set_pos.png" +msgstr "" + +#: ../../en/m5ui/bar.rst:286 2f935295985141269c4c64a9676769c2 +msgid "Set the x-coordinate of the bar." +msgstr "设置进度条的 x 坐标。" + +#: ../../en/m5ui/bar.rst:293 f1772cc014194baf80a6911aba133392 +msgid "|set_x.png|" +msgstr "" + +#: ../../en/refs/m5ui.bar.ref:24 3fa11891cc954ac6974d3f191ef20013 +msgid "set_x.png" +msgstr "" + +#: ../../en/m5ui/bar.rst:303 c2f5080babea4946aa462ef3014040fd +msgid "Set the y-coordinate of the bar." +msgstr "设置进度条的 y 坐标。" + +#: ../../en/m5ui/bar.rst:310 89c7facd984a438b8278a46c2adeaca8 +msgid "|set_y.png|" +msgstr "" + +#: ../../en/refs/m5ui.bar.ref:25 4cb505a6103c4a8cb99f9b687e193173 +msgid "set_y.png" +msgstr "" + +#: ../../en/m5ui/bar.rst:320 9f5f88c6ae064fc4ab63a3f589ce37c5 +msgid "Set the size of the bar." +msgstr "设置进度条的尺寸。" + +#: ../../en/m5ui/bar.rst:328 37ae60e7cf1a4010bf506c908ae7b301 +msgid "|set_size.png|" +msgstr "" + +#: ../../en/refs/m5ui.bar.ref:19 95b90447734644f3827446d8b9b89cb7 +msgid "set_size.png" +msgstr "" + +#: ../../en/m5ui/bar.rst:338 9a8094dc4c024f6d95b30cad6a984ad7 +msgid "Set the width of the bar." +msgstr "设置进度条的宽度。" + +#: ../../en/m5ui/bar.rst:345 d592f13be05c4240aef740077520b1b7 +msgid "|set_width.png|" +msgstr "" + +#: ../../en/refs/m5ui.bar.ref:23 2f3f2bcf5c4545dea91299e46da76541 +msgid "set_width.png" +msgstr "" + +#: ../../en/m5ui/bar.rst:355 3d8150fa907d445c89f0f3f81bdaaf4a +msgid "Get the width of the bar." +msgstr "获取进度条的宽度。" + +#: ../../en/m5ui/bar.rst:362 e61bb573a2034f5fb3934759b688e02c +msgid "|get_width.png|" +msgstr "" + +#: ../../en/refs/m5ui.bar.ref:9 0a10f9112dc64edaabccf24251bcbc12 +msgid "get_width.png" +msgstr "" + +#: ../../en/m5ui/bar.rst:372 295ebb87c8bc4e6cbe66097f936cafdb +msgid "Set the height of the bar." +msgstr "设置进度条的高度。" + +#: ../../en/m5ui/bar.rst:379 a3cb26fcbcd54aa1a9c32c2ff8c11a2d +msgid "|set_height.png|" +msgstr "" + +#: ../../en/refs/m5ui.bar.ref:13 6c7fd9568c8b415d9a275f000a3366da +msgid "set_height.png" +msgstr "" + +#: ../../en/m5ui/bar.rst:389 0f98b40f4f874dd6b010e2b145053be3 +msgid "Get the height of the bar." +msgstr "获取进度条的高度。" + +#: ../../en/m5ui/bar.rst:396 4feeacdbc6f748b2b5fa8a43b458d06e +msgid "|get_height.png|" +msgstr "" + +#: ../../en/refs/m5ui.bar.ref:5 0ac3da9d56bb491da8c11cd3ab7bd606 +msgid "get_height.png" +msgstr "" + +#: ../../en/m5ui/bar.rst:406 54a31861257a42e08a7d911c753c3714 +msgid "Align the bar to another object." +msgstr "将进度条对齐到另一个对象。" + +#: ../../en/m5ui/bar.rst:408 7737f638857d435aacc870b89a635aaf +msgid "The object to align to." +msgstr "要对齐的对象。" + +#: ../../en/m5ui/bar.rst:409 75c8bcde02d0468e91785e1220bf52ea +msgid "The alignment type." +msgstr "对齐类型。" + +#: ../../en/m5ui/bar.rst:410 f8d27f510bbc4e25929465b5bf256cc7 +msgid "The x-offset from the aligned object." +msgstr "相对于对齐对象的 x 偏移量。" + +#: ../../en/m5ui/bar.rst:411 b66efc2f013747a5b7a9a95a232006cb +msgid "The y-offset from the aligned object." +msgstr "相对于对齐对象的 y 偏移量。" + +#: ../../en/m5ui/bar.rst:416 5911ff92b57443fe948865668ac95f81 +msgid "|align_to.png|" +msgstr "" + +#: ../../en/refs/m5ui.bar.ref:4 b79ded96728041f5bbc5d461f25a7a09 +msgid "align_to.png" +msgstr "" + diff --git a/examples/m5ui/bar/cores3_temperature_meter_example.m5f2 b/examples/m5ui/bar/cores3_temperature_meter_example.m5f2 new file mode 100644 index 00000000..8eb84cdf --- /dev/null +++ b/examples/m5ui/bar/cores3_temperature_meter_example.m5f2 @@ -0,0 +1 @@ +{"version":"V2.0","versionNumber":"V2.3.0","type":"cores3","components":[{"name":"page0","type":"lvgl_page","layer":0,"screenId":"builtin","screenName":"","id":"l5f2SpBNAdiIZj$l","createTime":1752128633108,"backgroundColor":"#ffffff","isLVGL":true,"isSelected":true},{"name":"bar0","type":"lvgl_bar","layer":1,"screenId":"builtin","screenName":"","id":"s@nB`rh96#zn@OmT","createTime":1752128637953,"x":148,"y":21,"width":20,"height":200,"minValue":0,"maxValue":50,"currentValue":25,"color":"#2193f3","backgroundColor":"#2193f3","pageId":"l5f2SpBNAdiIZj$l","isLVGL":true,"isSelected":false},{"name":"label0","type":"lvgl_label","layer":2,"screenId":"builtin","screenName":"","id":"mw_Vbs91pAC#_zr*","createTime":1752130082941,"x":181,"y":112,"color":"#000000","backgroundColor":"#ffffff","bg_opacity":0,"text":"label0","font":"lv.font_montserrat_14","pageId":"l5f2SpBNAdiIZj$l","isLVGL":true,"isSelected":false,"width":43,"height":15}],"resources":[{"hardware":["hardware_button","hardware_pin_button","imu","speaker","touch","als","mic","sdcard","i2c"]},{"unit":["unit_envpro"]}],"units":[{"type":"unit_envpro","name":"envpro_0","portList":["A","PAHUB","Custom"],"portType":"A","userPort":[22,21],"id":"m=BK#Y%jdcViME7`","createTime":1752128714183,"bus":"i2c0","pahubPortList":[0,1,2,3,4,5],"pahubPort":0,"initBlockId":"jrzLghfRd(EBRC;SuDuh"}],"hats":[],"bases":[],"i2cs":[{"id":"i2c0","portType":"A","userPort":[22,21],"freq":"100000","blockId":"tE^WXf3T.9OwK.97~%5v"}],"blockly":"true010000012envpro_0page0bar0VERrgb25500255rgb00255255truebar00envpro_0label0hello M5envpro_01","screen":[{"simulationName":"Built-in","type":"builtin","width":320,"height":240,"scale":0.77,"screenName":"","blockId":"","screenColorType":0,"rotation":1,"id":"builtin","createTime":1752128633106}],"logicWhenNum":0,"customList":[]} \ No newline at end of file diff --git a/examples/m5ui/bar/cores3_temperature_meter_example.py b/examples/m5ui/bar/cores3_temperature_meter_example.py new file mode 100644 index 00000000..834786f8 --- /dev/null +++ b/examples/m5ui/bar/cores3_temperature_meter_example.py @@ -0,0 +1,81 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import os, sys, io +import M5 +from M5 import * +import m5ui +import lvgl as lv +from hardware import I2C +from hardware import Pin +from unit import ENVPROUnit +import time + + +page0 = None +bar0 = None +label0 = None +i2c0 = None +envpro_0 = None + + +def setup(): + global page0, bar0, label0, i2c0, envpro_0 + + M5.begin() + Widgets.setRotation(1) + m5ui.init() + page0 = m5ui.M5Page(bg_c=0xFFFFFF) + bar0 = m5ui.M5Bar( + x=148, + y=21, + w=20, + h=200, + min_value=0, + max_value=50, + value=25, + bg_c=0x2193F3, + color=0x2193F3, + parent=page0, + ) + label0 = m5ui.M5Label( + "label0", + x=181, + y=112, + text_c=0x000000, + bg_c=0xFFFFFF, + bg_opa=0, + font=lv.font_montserrat_14, + parent=page0, + ) + + i2c0 = I2C(0, scl=Pin(1), sda=Pin(2), freq=100000) + envpro_0 = ENVPROUnit(i2c0) + page0.screen_load() + bar0.set_bg_grad_color( + 0xFF0000, 255, 0x0000FF, 255, lv.GRAD_DIR.VER, lv.PART.INDICATOR | lv.STATE.DEFAULT + ) + + +def loop(): + global page0, bar0, label0, i2c0, envpro_0 + M5.update() + bar0.set_value(int(envpro_0.get_temperature()), True) + label0.set_text(str(envpro_0.get_temperature())) + time.sleep(1) + + +if __name__ == "__main__": + try: + setup() + while True: + loop() + except (Exception, KeyboardInterrupt) as e: + try: + m5ui.deinit() + from utility import print_error_msg + + print_error_msg(e) + except ImportError: + print("please update to latest firmware") diff --git a/m5stack/libs/m5ui/__init__.py b/m5stack/libs/m5ui/__init__.py index 5ea146af..0cf48520 100644 --- a/m5stack/libs/m5ui/__init__.py +++ b/m5stack/libs/m5ui/__init__.py @@ -5,6 +5,7 @@ _attrs = { "init": "port", "deinit": "port", + "M5Bar": "bar", "M5Button": "button", "M5Image": "image", "M5Label": "label", diff --git a/m5stack/libs/m5ui/bar.py b/m5stack/libs/m5ui/bar.py new file mode 100644 index 00000000..a347fb93 --- /dev/null +++ b/m5stack/libs/m5ui/bar.py @@ -0,0 +1,112 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +from .base import M5Base +import lvgl as lv +import time + + +class M5Bar(lv.bar): + """Initialize a new M5Bar widget. + + :param int x: The x-coordinate of the bar. + :param int y: The y-coordinate of the bar. + :param int w: The width of the bar. + :param int h: The height of the bar. + :param int min_value: The minimum value of the bar range. + :param int max_value: The maximum value of the bar range. + :param int value: The initial value of the bar. + :param bool is_show_value: Whether to display the current value as text. + :param int bg_c: The background color of the bar. + :param int color: The indicator color of the bar. + :param lv.obj parent: The parent object. If None, uses the active screen. + :return: None + + UiFlow2 Code Block: + + None + + MicroPython Code Block: + + .. code-block:: python + + bar = M5Bar(x=50, y=50, w=200, h=30, min_value=0, max_value=100, value=50) + """ + + def __init__( + self, + x=0, + y=0, + w=100, + h=20, + min_value=0, + max_value=100, + value=25, + is_show_value=False, + bg_c=0x2193F3, + color=0x2193F3, + parent=None, + ): + if parent is None: + parent = lv.screen_active() + super().__init__(parent) + + self.set_pos(x, y) + self.set_size(w, h) + self.set_range(min_value, max_value) + self.set_bg_color( + bg_c, 51, lv.PART.MAIN | lv.STATE.DEFAULT + ) # default opacity is 51 (20% opacity) + self.set_bg_color(color, lv.OPA.COVER, lv.PART.INDICATOR | lv.STATE.DEFAULT) + self.set_value(value, True) + + if is_show_value: + self.add_event_cb(self._draw_cb, lv.EVENT.DRAW_MAIN_END, None) + + def _draw_cb(self, event_struct): + label_dsc = lv.draw_label_dsc_t() + label_dsc.init() + label_dsc.font = lv.font_get_default() + + txt_size = lv.point_t() + lv.text_get_size( + txt_size, + f"{self.get_value()}", + label_dsc.font, + label_dsc.letter_space, + label_dsc.line_space, + lv.COORD.MAX, + label_dsc.flag, + ) + + txt_area = lv.area_t() + txt_area.x1 = 0 + txt_area.x2 = txt_size.x - 1 + txt_area.y1 = 0 + txt_area.y2 = txt_size.y - 1 + + indic_area = lv.area_t() + self.get_coords(indic_area) + indic_area.set_width(indic_area.get_width() * self.get_value() // self.get_max_value()) + + if indic_area.get_width() > (txt_size.x + 20): + print("Indicator area is large enough to display text") + indic_area.align(txt_area, lv.ALIGN.RIGHT_MID, -10, 0) + label_dsc.color = lv.color_hex(0xFFFFFF) + else: + print("Indicator area is too small to display text") + indic_area.align(txt_area, lv.ALIGN.OUT_RIGHT_MID, 10, 0) + label_dsc.color = lv.color_hex(0x000000) + + label_dsc.text = f"{self.get_value()}" + label_dsc.text_local = True + lv.draw_label(event_struct.get_layer(), label_dsc, txt_area) + + def __getattr__(self, name): + if hasattr(M5Base, name): + method = getattr(M5Base, name) + bound_method = lambda *args, **kwargs: method(self, *args, **kwargs) + setattr(self, name, bound_method) + return bound_method + raise AttributeError(f"'{self.__class__.__name__}' object has no attribute '{name}'") diff --git a/m5stack/libs/m5ui/base.py b/m5stack/libs/m5ui/base.py index 8530e9e3..b715493b 100644 --- a/m5stack/libs/m5ui/base.py +++ b/m5stack/libs/m5ui/base.py @@ -88,3 +88,35 @@ def set_bg_color(self, color: int, opa: int, part: int) -> None: self.set_style_bg_color(color, part) time.sleep(0.01) self.set_style_bg_opa(opa, part) + + @staticmethod + def set_bg_grad_color(self, color, opa, grad_color, grad_opd, grad_dir, part: int) -> None: + """Set the background gradient color of the bar. + + :param int color: The start color of the gradient, can be an integer (RGB). + :param int opa: The opacity of the start color (0-255). + :param int grad_color: The end color of the gradient, can be an integer (RGB). + :param int grad_opd: The opacity of the end color (0-255). + :param int grad_dir: The direction of the gradient (e.g., lv.GRAD_DIR.VER). + :param int part: The part of the object to apply the style to (e.g., lv.PART.MAIN). + :return: None + """ + if isinstance(color, int): + color = lv.color_hex(color) + + if isinstance(grad_color, int): + grad_color = lv.color_hex(grad_color) + + # start color and opacity + self.set_style_bg_color(color, part) + time.sleep(0.01) + self.set_style_bg_opa(opa, part) + time.sleep(0.01) + + # end color and opacity + self.set_style_bg_grad_color(grad_color, part) + time.sleep(0.01) + self.set_style_bg_grad_opa(grad_opd, part) + time.sleep(0.01) + self.set_style_bg_grad_dir(grad_dir, part) + time.sleep(0.01) diff --git a/m5stack/libs/m5ui/manifest.py b/m5stack/libs/m5ui/manifest.py index 68069c04..1758b6ae 100644 --- a/m5stack/libs/m5ui/manifest.py +++ b/m5stack/libs/m5ui/manifest.py @@ -4,7 +4,17 @@ package( "m5ui", - ("__init__.py", "base.py", "button.py", "image.py", "label.py", "page.py", "switch.py", "port.py"), + ( + "__init__.py", + "bar.py", + "base.py", + "button.py", + "image.py", + "label.py", + "page.py", + "port.py", + "switch.py", + ), base_path="..", opt=0, ) From edbef67452804814aec05d4f139f0a350d6d89df Mon Sep 17 00:00:00 2001 From: hlym123 Date: Wed, 9 Jul 2025 16:56:02 +0800 Subject: [PATCH 157/322] libs/unit: Add support for Step16Unit. Signed-off-by: hlym123 --- docs/en/refs/unit.step16.ref | 37 ++ docs/en/units/index.rst | 1 + docs/en/units/step16.rst | 59 ++ .../locales/zh_CN/LC_MESSAGES/units/step16.po | 575 ++++++++++++++++++ .../step16/cores3_step16_unit_example.m5f2 | 1 + .../unit/step16/cores3_step16_unit_example.py | 66 ++ m5stack/libs/unit/__init__.py | 1 + m5stack/libs/unit/manifest.py | 1 + m5stack/libs/unit/step16.py | 404 ++++++++++++ 9 files changed, 1145 insertions(+) create mode 100644 docs/en/refs/unit.step16.ref create mode 100644 docs/en/units/step16.rst create mode 100644 docs/locales/zh_CN/LC_MESSAGES/units/step16.po create mode 100644 examples/unit/step16/cores3_step16_unit_example.m5f2 create mode 100644 examples/unit/step16/cores3_step16_unit_example.py create mode 100644 m5stack/libs/unit/step16.py diff --git a/docs/en/refs/unit.step16.ref b/docs/en/refs/unit.step16.ref new file mode 100644 index 00000000..bc9ff001 --- /dev/null +++ b/docs/en/refs/unit.step16.ref @@ -0,0 +1,37 @@ +.. |Unit step16| image:: https://static-cdn.m5stack.com/resource/docs/products/unit/step16.webp + :target: https://docs.m5stack.com/zh_CN/unit/step16 + :height: 200px + :width: 200px + +.. |init.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/step16/init.png +.. |get_led_mode.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/step16/get_led_mode.png +.. |get_led_brightness.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/step16/get_led_brightness.png +.. |set_led_mode.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/step16/set_led_mode.png +.. |set_led_brightness.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/step16/set_led_brightness.png +.. |save_led_config.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/step16/save_led_config.png +.. |get_rgb_power.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/step16/get_rgb_power.png +.. |get_rgb_brightness.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/step16/get_rgb_brightness.png +.. |get_rgb_value.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/step16/get_rgb_value.png +.. |set_rgb_power.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/step16/set_rgb_power.png +.. |set_rgb_brightness.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/step16/set_rgb_brightness.png +.. |set_rgb_value.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/step16/set_rgb_value.png +.. |save_rgb_config.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/step16/save_rgb_config.png +.. |get_encoder_value.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/step16/get_encoder_value.png +.. |get_encoder_cw_increase.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/step16/get_encoder_cw_increase.png +.. |set_encoder_cw_increase.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/step16/set_encoder_cw_increase.png +.. |get_firmware_version.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/step16/get_firmware_version.png +.. |get_addr.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/step16/get_addr.png +.. |set_addr.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/step16/set_addr.png +.. |example.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/step16/example.png" + + +.. |cores3_step16_unit_example.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/step16/example.png + +.. |cores3_step16_unit_example.m5f2| raw:: html + + + cores3_step16_unit_example.m5f2 + diff --git a/docs/en/units/index.rst b/docs/en/units/index.rst index 52ba37ca..827306df 100644 --- a/docs/en/units/index.rst +++ b/docs/en/units/index.rst @@ -103,6 +103,7 @@ Unit scroll.rst servos8.rst ssr.rst + step16.rst synth.rst thermal.rst timerpwr.rst diff --git a/docs/en/units/step16.rst b/docs/en/units/step16.rst new file mode 100644 index 00000000..5b49c310 --- /dev/null +++ b/docs/en/units/step16.rst @@ -0,0 +1,59 @@ +Step16 Unit +=========== + +.. sku: xxx + +.. include:: ../refs/unit.step16.ref + +This library is the driver for Unit Step16. + +Support the following products: + + |Unit Step16| + +UiFlow2 Example +--------------- + +Read Encoder +^^^^^^^^^^^^ + +Open the |cores3_step16_unit_example.m5f2| project in UiFlow2. + +This example shows how to read and display encoder readings. + +UiFlow2 Code Block: + + |cores3_step16_unit_example.png| + +Example output: + + None + +MicroPython Example +------------------- + + +Read Encoder +^^^^^^^^^^^^ + +This example shows how to read and display encoder readings. + +MicroPython Code Block: + + .. literalinclude:: ../../../examples/unit/step16/cores3_step16_unit_example.py + :language: python + :linenos: + +Example output: + + None + +**API** +------- + +Step16Unit +^^^^^^^^^^ + +.. autoclass:: unit.step16.Step16Unit + :members: + diff --git a/docs/locales/zh_CN/LC_MESSAGES/units/step16.po b/docs/locales/zh_CN/LC_MESSAGES/units/step16.po new file mode 100644 index 00000000..12e5ad14 --- /dev/null +++ b/docs/locales/zh_CN/LC_MESSAGES/units/step16.po @@ -0,0 +1,575 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2016 - 2025 M5Stack Technology Co., Ltd +# This file is distributed under the same license as the UIFlow2 Programming +# Guide package. +# FIRST AUTHOR , 2025. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: UIFlow2 Programming Guide \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-07-09 15:19+0800\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: zh_CN\n" +"Language-Team: zh_CN \n" +"Plural-Forms: nplurals=1; plural=0;\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.16.0\n" + +#: ../../en/units/step16.rst:2 2d4b2889c738435196dcb6cf73e4f3fe +msgid "Step16 Unit" +msgstr "" + +#: ../../en/units/step16.rst:8 e6ca65b5547647b69c9a1a3b6bb0bcc0 +msgid "This library is the driver for Unit Step16." +msgstr "这个库是 Unit Step16 的驱动。" + +#: ../../en/units/step16.rst:10 f7c27496e7b0491c8ad5612b294954d1 +msgid "Support the following products:" +msgstr "支持以下产品:" + +#: ../../en/units/step16.rst:12 985036bcaa3742bf81ef40a88b29bd95 +msgid "|Unit Step16|" +msgstr "" + +#: ../../en/refs/unit.step16.ref 559de2b62a6a4fb19e8a7aa265016164 +msgid "Unit step16" +msgstr "" + +#: ../../en/units/step16.rst:15 f08ffb0e86a94e4e837d9ec179a867cf +msgid "UiFlow2 Example" +msgstr "" + +#: ../../en/units/step16.rst:18 ../../en/units/step16.rst:37 +#: 8493cc27f49d4579b43a38a16ebf5acb deddb9dea92140d1b0db1953902ddd10 +msgid "Read Encoder" +msgstr "读编码器" + +#: ../../en/units/step16.rst:20 b15c44d0e5ae45f4be14b44077737cfe +msgid "Open the |cores3_step16_unit_example.m5f2| project in UiFlow2." +msgstr "在 UiFlow2 上打开 |cores3_step16_unit_example.m5f2| 项目。" + +#: ../../en/units/step16.rst:22 ../../en/units/step16.rst:39 +#: 7a115264d4c94f83957dc13db27880ba ea549e044cee4f21a60d32a925c3ea8c +msgid "This example shows how to read and display encoder readings." +msgstr "示例展示了如何读取并显示编码器读数。" + +#: ../../docstring ../../en/units/step16.rst:24 +#: 01124cab78434693aafa02d6a1e98c7b 02966a798d31472eaa1e7048af1e25e9 +#: 02ab1d3088234d1ebdaaaa0a46b45385 1066a37f89ef43918307b5b7dc1bd49f +#: 1458a36934d04579b95d60cab28a66cc 16d422ecb4bc470284125873bd32557f +#: 27ce728238334d44a329c2752e794e60 2de0d5d028c2449194e535ab2b8a2f40 +#: 3712f6d4f7594efcad5f2c49e2ef0dd1 4855f2fdef78466e8835d88e78d91f81 +#: 5824fc2508f743d3924725c9302591ac 61fd2328dad948408f2795e69e7ec64c +#: 6f78eb307be949a4b0d1b99993d87ca7 a39efd893a8947dc94fe8e1b61590e11 +#: b04807673e34452fa3a10f39f4056bc9 bad29fe933994e568a6ed178b8489737 +#: d524594d036542bc8dc3be9ffd6e2c1c d573af93e608454aa1625fb8822733c8 +#: e6555ad48909431c8b1a145d8db2a44a f01e5f86b43a47f3a21b3eed66e33bdd of +#: unit.step16.Step16Unit.AUTO_OFF:6 unit.step16.Step16Unit.get_addr:6 +#: unit.step16.Step16Unit.get_encoder_cw_increase:6 +#: unit.step16.Step16Unit.get_encoder_value:6 +#: unit.step16.Step16Unit.get_firmware_version:6 +#: unit.step16.Step16Unit.get_led_brightness:6 +#: unit.step16.Step16Unit.get_led_mode:12 +#: unit.step16.Step16Unit.get_rgb_brightness:6 +#: unit.step16.Step16Unit.get_rgb_power:6 +#: unit.step16.Step16Unit.get_rgb_value:6 +#: unit.step16.Step16Unit.save_led_config:3 +#: unit.step16.Step16Unit.save_rgb_config:3 unit.step16.Step16Unit.set_addr:6 +#: unit.step16.Step16Unit.set_encoder_cw_increase:8 +#: unit.step16.Step16Unit.set_led_brightness:6 +#: unit.step16.Step16Unit.set_led_mode:11 +#: unit.step16.Step16Unit.set_rgb_brightness:6 +#: unit.step16.Step16Unit.set_rgb_power:6 +#: unit.step16.Step16Unit.set_rgb_value:6 +msgid "UiFlow2 Code Block:" +msgstr "UiFlow2 代码块:" + +#: ../../en/units/step16.rst:26 195b884d7858490992dd0111597941e9 +msgid "|cores3_step16_unit_example.png|" +msgstr "" + +#: ../../en/refs/unit.step16.ref:28 d4d94314af6c4ba39d4484c80c1e57b0 +msgid "cores3_step16_unit_example.png" +msgstr "" + +#: ../../en/units/step16.rst:28 ../../en/units/step16.rst:47 +#: 334e749d161f487c95490baca017f12e f829cb5f466c4abc9d8cd3697d67dab6 +msgid "Example output:" +msgstr "示例输出:" + +#: ../../en/units/step16.rst:30 ../../en/units/step16.rst:49 +#: 23fbf4d6ccbf4d77865529e71f0b1244 fd8ec3db6be2497b8e9abfbe2b35e854 +msgid "None" +msgstr "无" + +#: ../../en/units/step16.rst:33 c3c34930f5754fa0a730102f00bd4d13 +msgid "MicroPython Example" +msgstr "MicroPython 应用示例" + +#: ../../docstring ../../en/units/step16.rst:41 +#: 04c3f93c8be0445a95813cdf574f85ef 07cd34cd5b1e4bbd9582756e0a006946 +#: 0c5b804f0402455c88667458bb7ad1dc 163dff4fd84f44ea90a2b0b3278cfdb3 +#: 275184d260184c2eaaa64c6637798bd2 2e06b32a687347e094a243d7e4c4f674 +#: 4019b4c41b4b4556b76ae8eeab97f8de 42da7661bee5416fbebbd1b98266fa67 +#: 458577bfb59a4791b9ea0423d802f83c 4eeefc0e11e141218a5c70d9d77e1d8d +#: 6f5c524d6fc84b01a6b7e7de5d855536 b183e3555d414e7490ea1d2b04492144 +#: bed6517916f24ba5ab2a64ad07bbabcf cc199c67714e48768216d0f536085a2e +#: ccf9616f248140ce993423bb79d5f234 d6e3b30683584f97a3ade0c542625410 +#: db0c2c1a35e54bd797c380ae07c89c5e e9ad4bfb139441f3a8299b97647b367f +#: eb55634d50ec4115b2265c536a2bde48 ec0a5b154e0e47f3bf10c7fe8812c46a of +#: unit.step16.Step16Unit.AUTO_OFF:10 unit.step16.Step16Unit.get_addr:10 +#: unit.step16.Step16Unit.get_encoder_cw_increase:10 +#: unit.step16.Step16Unit.get_encoder_value:10 +#: unit.step16.Step16Unit.get_firmware_version:10 +#: unit.step16.Step16Unit.get_led_brightness:10 +#: unit.step16.Step16Unit.get_led_mode:16 +#: unit.step16.Step16Unit.get_rgb_brightness:10 +#: unit.step16.Step16Unit.get_rgb_power:10 +#: unit.step16.Step16Unit.get_rgb_value:10 +#: unit.step16.Step16Unit.save_led_config:7 +#: unit.step16.Step16Unit.save_rgb_config:7 unit.step16.Step16Unit.set_addr:10 +#: unit.step16.Step16Unit.set_encoder_cw_increase:12 +#: unit.step16.Step16Unit.set_led_brightness:10 +#: unit.step16.Step16Unit.set_led_mode:15 +#: unit.step16.Step16Unit.set_rgb_brightness:10 +#: unit.step16.Step16Unit.set_rgb_power:10 +#: unit.step16.Step16Unit.set_rgb_value:10 +msgid "MicroPython Code Block:" +msgstr "MicroPython 代码块:" + +#: ../../en/units/step16.rst:52 506eaa99139b4a8dad867fa259d38c82 +msgid "**API**" +msgstr "API应用" + +#: ../../en/units/step16.rst:55 7a7d39f902c44086933c30eec79eaee2 +msgid "Step16Unit" +msgstr "" + +#: 6c2f9896f9ea4eb28ecf28f9073c647e of unit.step16.Step16Unit:1 +msgid "Bases: :py:class:`object`" +msgstr "" + +#: ../../docstring ../../en/units/step16.rst 1998009dddfb46a4977fafca33ff4306 +#: 76d54cc6214d4b12ac8ff0edf7323950 7fed49c4be1343a4911b8506cd2e98b6 +#: 83440d6bee564318a9c4ba7fc4f98c94 860ea3a6340c42b7835dff6f03e64a3b +#: b21f9b229d304414a9aa49b2e561ac79 c2200ae3a89c4d33b84d658c840aa35c +#: c8839e177f5b40568e6d7e9f5ace5ea0 e1ae642b15104d5c9942998e68d969df of +#: unit.step16.Step16Unit.AUTO_OFF unit.step16.Step16Unit.set_addr +#: unit.step16.Step16Unit.set_encoder_cw_increase +#: unit.step16.Step16Unit.set_led_brightness +#: unit.step16.Step16Unit.set_led_mode +#: unit.step16.Step16Unit.set_rgb_brightness +#: unit.step16.Step16Unit.set_rgb_power unit.step16.Step16Unit.set_rgb_value +msgid "Parameters" +msgstr "" + +#: ../../docstring cb01c75110a84b76be86a41e89108f84 of +#: unit.step16.Step16Unit.AUTO_OFF:1 +msgid "Create an Step16Unit object." +msgstr "创建一个 Step16Unit 对象。" + +#: ../../docstring ebfc6748e57d407b82ec7790cb757cb7 of +#: unit.step16.Step16Unit.AUTO_OFF:3 +msgid "I2C port," +msgstr "I2C 端口" + +#: ../../docstring 052b4c4e4b62442daef9b11d620a43b6 of +#: unit.step16.Step16Unit.AUTO_OFF:4 +msgid "Step16Unit Slave Address" +msgstr "Step16Unit 从机地址" + +#: ../../docstring 8fe1f548c72546e2b9503ba7ec763b83 of +#: unit.step16.Step16Unit.AUTO_OFF:8 +msgid "|init.png|" +msgstr "" + +#: ../../en/refs/unit.step16.ref:6 5582856e802142fa918b2c2c90612dcc +msgid "init.png" +msgstr "" + +#: 8a88491acad94390ab38238b6fb07552 of unit.step16.Step16Unit.get_addr:1 +msgid "Get the current I2C device address." +msgstr "获取当前设备地址。" + +#: 1b20cec8630646b48bac3bdf336c6744 305c182335244be5a9353ac0324dedfe +#: 3ff0eb91fdf04734ac779d490db74573 4592426ef84b44db8c6d767afdeaf6df +#: 6bd6473a19ec4318852cc44e941155f3 b1807b56f9b246169e05fdb2f22dea36 +#: c26589cefd42456dba56137122763d08 d77a3c50090643aa9e446483803c1b01 +#: e6aa875b811d44a597e25a10ee1b2a33 of unit.step16.Step16Unit.get_addr +#: unit.step16.Step16Unit.get_encoder_cw_increase +#: unit.step16.Step16Unit.get_encoder_value +#: unit.step16.Step16Unit.get_firmware_version +#: unit.step16.Step16Unit.get_led_brightness +#: unit.step16.Step16Unit.get_led_mode +#: unit.step16.Step16Unit.get_rgb_brightness +#: unit.step16.Step16Unit.get_rgb_power unit.step16.Step16Unit.get_rgb_value +msgid "Returns" +msgstr "" + +#: 0a13809ef9ca47f698818feda8e23e52 of unit.step16.Step16Unit.get_addr:3 +msgid "I2C address." +msgstr "I2C 地址" + +#: 1aad9e42e77f4985bd3844be5997096f 2e3e37f9e1c947a1bee31f1f9ed52ae7 +#: 2e5783605d924e75856f803213d7c694 32a053dd799844f2bd3270e74e341d65 +#: 5d02dd04c64c4484a0f61444442b24d3 5d86c4e51070405897daa965202210e9 +#: 686167d12e854ec6b710607d2b0ec2b8 77c337e5b4794152bc58e8362dff5d73 +#: 870cbac2ed6e447a98d4827207463df6 a51b8b487df94d34bf23bbb1819cff70 +#: abd2a48a07104cd18918606ffe7e0600 af0a8228ab604453b07e12728a9cad39 +#: b15738dfdce54d02b785a60a0901bde6 bf7168422c844ef08aceaa8223b57cd1 +#: cade3bd494b346f2ab1e8e84774fb08a da923da1126f44d896a2df9a68719286 +#: e987c6aeb0ad42eda6af9f426ac876d6 fa571610a36e4e0e8699379ccdb5c4ad of +#: unit.step16.Step16Unit.get_addr +#: unit.step16.Step16Unit.get_encoder_cw_increase +#: unit.step16.Step16Unit.get_encoder_value +#: unit.step16.Step16Unit.get_firmware_version +#: unit.step16.Step16Unit.get_led_brightness +#: unit.step16.Step16Unit.get_led_mode +#: unit.step16.Step16Unit.get_rgb_brightness +#: unit.step16.Step16Unit.get_rgb_power unit.step16.Step16Unit.get_rgb_value +#: unit.step16.Step16Unit.save_led_config +#: unit.step16.Step16Unit.save_rgb_config unit.step16.Step16Unit.set_addr +#: unit.step16.Step16Unit.set_encoder_cw_increase +#: unit.step16.Step16Unit.set_led_brightness +#: unit.step16.Step16Unit.set_led_mode +#: unit.step16.Step16Unit.set_rgb_brightness +#: unit.step16.Step16Unit.set_rgb_power unit.step16.Step16Unit.set_rgb_value +msgid "Return type" +msgstr "返回类型" + +#: e5d0e86a560b4fa7aee6e6f0c564ecb5 of unit.step16.Step16Unit.get_addr:8 +msgid "|get_addr.png|" +msgstr "" + +#: ../../en/refs/unit.step16.ref:23 46cb09cb8b894235a4a34ced9fd96806 +msgid "get_addr.png" +msgstr "" + +#: 9c121d7adfe44a73b03912524d8705f3 of +#: unit.step16.Step16Unit.get_encoder_cw_increase:1 +msgid "Get current encoder direction mode." +msgstr "获取当前编码器方向。" + +#: 14a571f80c884c1d981d3a99a6936e14 of +#: unit.step16.Step16Unit.get_encoder_cw_increase:3 +msgid "1 for increasing clockwise, 0 for decreasing." +msgstr "1 为顺时针增加,0 为顺时针减少。" + +#: ba43d64f7619499984ed7a0be6ecebd3 of +#: unit.step16.Step16Unit.get_encoder_cw_increase:8 +msgid "|get_encoder_cw_increase.png|" +msgstr "" + +#: ../../en/refs/unit.step16.ref:20 20def756877b4578968ef0c67c35ea6e +msgid "get_encoder_cw_increase.png" +msgstr "" + +#: b0c9a13433e741f2a0fab1d511a5f2a6 of +#: unit.step16.Step16Unit.get_encoder_value:1 +msgid "Get the current encoder value (0~15)." +msgstr "获取当前编码器数值(0~15)。" + +#: 0e722addd787443796fbfeca36edea6b of +#: unit.step16.Step16Unit.get_encoder_value:3 +msgid "Encoder value." +msgstr "编码器数值。" + +#: f03e976c37384b33845b3c8933b09023 of +#: unit.step16.Step16Unit.get_encoder_value:8 +msgid "|get_encoder_value.png|" +msgstr "" + +#: ../../en/refs/unit.step16.ref:19 fc489b02faf440ad8f16c27d7cfe9352 +msgid "get_encoder_value.png" +msgstr "" + +#: 019473726560485ca859ec6f81ae434f of +#: unit.step16.Step16Unit.get_firmware_version:1 +msgid "Get the firmware version." +msgstr "获取固件版本。" + +#: 5d17a67874a54445a7b55afb0e2ddad8 of +#: unit.step16.Step16Unit.get_firmware_version:3 +msgid "firmware version." +msgstr "固件版本。" + +#: a2e7731b497242f08b1da59869967173 of +#: unit.step16.Step16Unit.get_firmware_version:8 +msgid "|get_firmware_version.png|" +msgstr "" + +#: ../../en/refs/unit.step16.ref:22 85c854e366d24cc38e64d1c6520541f0 +msgid "get_firmware_version.png" +msgstr "" + +#: f0c9e1baf12d45b3b3021218ef7bd1f8 of +#: unit.step16.Step16Unit.get_led_brightness:1 +msgid "Get current LED brightness." +msgstr "获取当前 LED 亮度。" + +#: 12f86ed4c40f4160bace740e5197d88b d3296bd920d34f558a09803fcd42f333 of +#: unit.step16.Step16Unit.get_led_brightness:3 +#: unit.step16.Step16Unit.set_led_brightness:3 +msgid "Brightness level." +msgstr "亮度等级。" + +#: 0e76ada13bc846b79c57e1bf9be6110f of +#: unit.step16.Step16Unit.get_led_brightness:8 +msgid "|get_led_brightness.png|" +msgstr "" + +#: ../../en/refs/unit.step16.ref:8 98ceddbb04324654bfc06bc20129fb33 +msgid "get_led_brightness.png" +msgstr "" + +#: e40e05d1a1b24ee4885e5e24959a1037 of unit.step16.Step16Unit.get_led_mode:1 +#, fuzzy +msgid "Get LED display mode." +msgstr "设置 LED 显示模式。" + +#: 537e4a833e314001af12acd1c0a73d90 of unit.step16.Step16Unit.get_led_mode:3 +msgid "The LED mode values:" +msgstr "" + +#: 7d4aea9e9aa8456dae652d8a83a81757 of unit.step16.Step16Unit.get_led_mode:5 +msgid "`0x00` : Always Off." +msgstr "`0x00` : 常灭." + +#: f3f9785365dd45c2aed9ed7f71724c75 of unit.step16.Step16Unit.get_led_mode:6 +msgid "`0xFE` : Always On." +msgstr "`0xFE` : 常亮." + +#: 59a8937fa9ef4b11a8e8e5995b4c735b of unit.step16.Step16Unit.get_led_mode:7 +msgid "`0x00` ~ `0xFD` : Auto off times in seconds." +msgstr "`0x00` ~ `0xFD` :自动关闭模式,值为自动关闭的时间(秒)。" + +#: 55a096c369594a85bac97d6219ffed65 of unit.step16.Step16Unit.get_led_mode:9 +#, fuzzy +msgid "LED display mode." +msgstr "设置 LED 显示模式。" + +#: c6b387a6347449c5a79fcc6eed9012a2 of unit.step16.Step16Unit.get_led_mode:14 +msgid "|get_led_mode.png|" +msgstr "" + +#: ../../en/refs/unit.step16.ref:7 adc66081b02f4321912484d33aec9531 +msgid "get_led_mode.png" +msgstr "" + +#: f26f1ebaa7dc49e7bad1a2a740212d4a of +#: unit.step16.Step16Unit.get_rgb_brightness:1 +msgid "Get the current RGB brightness level (0~100%)." +msgstr "获取当前 RGB 亮度等级(0~100%)。" + +#: 57b8830985e14f3ca7c57b714e896b90 of +#: unit.step16.Step16Unit.get_rgb_brightness:3 +msgid "Current RGB brightness percentage (0~100)." +msgstr " 当前 RGB 亮度百分比(0~100)。" + +#: f50eef010d6c458c96e1852c5ebc1cd5 of +#: unit.step16.Step16Unit.get_rgb_brightness:8 +msgid "|get_rgb_brightness.png|" +msgstr "" + +#: ../../en/refs/unit.step16.ref:13 b0416ac194e546658b05d59335fac3b1 +msgid "get_rgb_brightness.png" +msgstr "" + +#: ca918aa4db0a4e60a279279933f39a5f of unit.step16.Step16Unit.get_rgb_power:1 +msgid "Get the current power status of the RGB light." +msgstr "获取当前 RGB 灯电源状态。" + +#: 853fb42bf980406a910df96f1e7cb000 of unit.step16.Step16Unit.get_rgb_power:3 +msgid "True if the RGB light is ON, False if OFF." +msgstr "如果 RGB 灯已开启,则为 True;如果关闭,则为 False。" + +#: 4cbaac651a7141da81a7287750eb5dd6 of unit.step16.Step16Unit.get_rgb_power:8 +msgid "|get_rgb_power.png|" +msgstr "" + +#: ../../en/refs/unit.step16.ref:12 25a377c7d36a4f138795b15f0d08cd6c +msgid "get_rgb_power.png" +msgstr "" + +#: 7361f7b5bffd4b67bfd150080c7ca326 of unit.step16.Step16Unit.get_rgb_value:1 +msgid "Get current RGB LED color." +msgstr "获取当前 RGB 灯颜色。" + +#: 4b62f72839a74450acaf30dc51ba4fd4 of unit.step16.Step16Unit.get_rgb_value:3 +msgid "Tuple of (r, g, b)" +msgstr " (r, g, b) 元组" + +#: 4afc0a39b19f42bfb9ea8b3389869116 of unit.step16.Step16Unit.get_rgb_value:8 +msgid "|get_rgb_value.png|" +msgstr "" + +#: ../../en/refs/unit.step16.ref:14 9ff829a828884e3b88a5499c5e548ebd +msgid "get_rgb_value.png" +msgstr "" + +#: 58ca823e48e84777bfac44870fc7a6fb of unit.step16.Step16Unit.save_led_config:1 +msgid "Save current LED mode and brightness settings." +msgstr "保存当前的 LED 模式和亮度设置。" + +#: 4a87256c02a942c7b10cd55201e68d35 of unit.step16.Step16Unit.save_led_config:5 +msgid "|save_led_config.png|" +msgstr "" + +#: ../../en/refs/unit.step16.ref:11 1219376f655048d9848b507a2d4da493 +msgid "save_led_config.png" +msgstr "" + +#: 3c7b9d84527f4fcdb7cefab75956becc of unit.step16.Step16Unit.save_rgb_config:1 +msgid "Save current RGB color settings." +msgstr "保存当前的 RGB 灯颜色设置。" + +#: e687b6d2b55a4cfcab3d73d0ae8e5c76 of unit.step16.Step16Unit.save_rgb_config:5 +msgid "|save_rgb_config.png|" +msgstr "" + +#: ../../en/refs/unit.step16.ref:18 d98fb6464cfc4bbb9a338c741cd8e8ee +msgid "save_rgb_config.png" +msgstr "" + +#: 48f08f3c2b9044b48bf331ef4d50333a of unit.step16.Step16Unit.set_addr:1 +msgid "Set the device's I2C address." +msgstr "设置 I2C 设备地址。" + +#: 3236f625c3c54d61b9e29d90a5bbc55e of unit.step16.Step16Unit.set_addr:3 +msgid "New I2C address (0x08~0x77)." +msgstr "新 I2C 地址(0x08~0x77)。" + +#: 42b2ed63f39e4217ae78356793117b3f of unit.step16.Step16Unit.set_addr:8 +msgid "|set_addr.png|" +msgstr "" + +#: ../../en/refs/unit.step16.ref:24 5beb68cadd5e49bfb0ec858c857ab819 +msgid "set_addr.png" +msgstr "" + +#: 9e27c4de29c84933a7e56da3a4b9bef0 of +#: unit.step16.Step16Unit.set_encoder_cw_increase:1 +msgid "Configure whether clockwise rotation increases encoder value." +msgstr "配置顺时针旋转是否增加编码器数值。" + +#: 19e40aaa3b0b48f794d8b47470321de2 of +#: unit.step16.Step16Unit.set_encoder_cw_increase:3 +msgid "" +"- True: Clockwise rotation increases the encoder value. - False: " +"Clockwise rotation decreases the encoder value." +msgstr "- True:顺时针旋转时编码器值递增。 - False:顺时针旋转时编码器值递减。" + +#: c48dc3c41545480692d9b7511f808147 of +#: unit.step16.Step16Unit.set_encoder_cw_increase:4 +msgid "True: Clockwise rotation increases the encoder value." +msgstr "" + +#: 940fe9fc3c6947c8b24cfad133edd20d of +#: unit.step16.Step16Unit.set_encoder_cw_increase:5 +msgid "False: Clockwise rotation decreases the encoder value." +msgstr "True:顺时针旋转增加编码器数值。" + +#: e4d54ebed25d4f1eb85ebcc0d6d8bb1a of +#: unit.step16.Step16Unit.set_encoder_cw_increase:10 +msgid "|set_encoder_cw_increase.png|" +msgstr "" + +#: ../../en/refs/unit.step16.ref:21 ad3f16f70ccb428a838a16e98d38ecb9 +msgid "set_encoder_cw_increase.png" +msgstr "" + +#: c901b2b2da884ed4810059cf48f56799 of +#: unit.step16.Step16Unit.set_led_brightness:1 +msgid "Set LED brightness (0~100)." +msgstr "设置 LED 亮度 (0~100)。" + +#: f0678933047544d291f5cb994e32fa7e of +#: unit.step16.Step16Unit.set_led_brightness:8 +msgid "|set_led_brightness.png|" +msgstr "" + +#: ../../en/refs/unit.step16.ref:10 5ff44194a331493da4b210559aed3bef +msgid "set_led_brightness.png" +msgstr "" + +#: 496315c8ae4e4a699613e48b5cadff5d of unit.step16.Step16Unit.set_led_mode:1 +msgid "Set LED display mode." +msgstr "设置 LED 显示模式。" + +#: 08d23e16fd4b417ca5c30ac9544fe904 of unit.step16.Step16Unit.set_led_mode:3 +msgid "" +"LED mode type. 0 = always off, 1 = always on, 2 = auto-off mode with " +"`seconds` as timeout." +msgstr "LED 模式类型。0 = 常灭,1 = 常亮,2 = 自动关闭模式,超时时间为 seconds 秒。" + +#: 693368f8ad4842188174b7369671ab74 of unit.step16.Step16Unit.set_led_mode:8 +msgid "Timeout in seconds if `mode` is 2 (auto-off)." +msgstr "当 mode 为 2(自动关闭)时的超时时间,单位为秒。" + +#: 9ef2d9f5dcd845178082c53ad8976574 of unit.step16.Step16Unit.set_led_mode:13 +msgid "|set_led_mode.png|" +msgstr "" + +#: ../../en/refs/unit.step16.ref:9 b43a95bd2b5f4c0a869ecf7f07675ddc +msgid "set_led_mode.png" +msgstr "" + +#: a7f5f9bb9dfa4f29abfaedd0315680dc of +#: unit.step16.Step16Unit.set_rgb_brightness:1 +msgid "Set the brightness of the RGB light (0~100%)." +msgstr "设置 RGB 灯亮度 (0~100)%。" + +#: f323fdf798ad4188b5d4403fa1b746ac of +#: unit.step16.Step16Unit.set_rgb_brightness:3 +msgid "Brightness percentage (0~100)." +msgstr "亮度百分比(0~100)。" + +#: 9397adff003242919707a51abf0c1f69 of +#: unit.step16.Step16Unit.set_rgb_brightness:8 +msgid "|set_rgb_brightness.png|" +msgstr "" + +#: ../../en/refs/unit.step16.ref:16 68042f759cc9419a840809afd36db6e4 +msgid "set_rgb_brightness.png" +msgstr "" + +#: 2abc3074e28e4e4e83ea9c939ef3dd28 of unit.step16.Step16Unit.set_rgb_power:1 +msgid "Turn the RGB light power ON or OFF." +msgstr "打开或关闭 RGB 灯。" + +#: a51e4f634528473e89d4d23fb41790b2 of unit.step16.Step16Unit.set_rgb_power:3 +msgid "True to turn on the RGB light, False to turn it off." +msgstr "True 为打开 RGB 灯,False 为关闭 RGB 灯。" + +#: 5a5f700a38294f35b9b86ff45910fb69 of unit.step16.Step16Unit.set_rgb_power:8 +msgid "|set_rgb_power.png|" +msgstr "" + +#: ../../en/refs/unit.step16.ref:15 4f37352cf8024917b599e2c055112649 +msgid "set_rgb_power.png" +msgstr "" + +#: 27dcdb4ed48a49cabcd065956f95e32e of unit.step16.Step16Unit.set_rgb_value:1 +msgid "Set RGB LED color using a 24-bit integer." +msgstr "设置 RGB LED 颜色使用 24 位整数。" + +#: 13e3533319fa4f179bdbb8c9d8113652 of unit.step16.Step16Unit.set_rgb_value:3 +msgid "" +"A 24-bit integer representing the RGB color (e.g., 0xFF8040 for R=255, " +"G=128, B=64). Format is (R << 16) | (G << 8) | B." +msgstr "" +"使用一个 24 位整数表示 RGB 颜色(例如 0xFF8040,表示红色 255,绿色 128,蓝色 64)。格式为 (R << 16) | " +"(G << 8) | B。" + +#: 42b1661f26814fb09f2518c41b25b1d8 of unit.step16.Step16Unit.set_rgb_value:8 +msgid "|set_rgb_value.png|" +msgstr "" + +#: ../../en/refs/unit.step16.ref:17 02d1c67b1fd44728a7e93752590e7493 +msgid "set_rgb_value.png" +msgstr "" \ No newline at end of file diff --git a/examples/unit/step16/cores3_step16_unit_example.m5f2 b/examples/unit/step16/cores3_step16_unit_example.m5f2 new file mode 100644 index 00000000..b27f12c8 --- /dev/null +++ b/examples/unit/step16/cores3_step16_unit_example.m5f2 @@ -0,0 +1 @@ +{"version":"V2.0","versionNumber":"V2.3.0","type":"cores3","components":[{"name":"screen","type":"screen","layer":0,"screenId":"builtin","screenName":"","id":"__cores3_screen","createTime":1751505050187,"x":0,"y":0,"width":320,"height":240,"backgroundColor":"#222222","size":0,"isSelected":true},{"name":"title0","type":"title","layer":1,"screenId":"builtin","screenName":"","id":"r0dGbnIeTUG`ht%G","createTime":1751505055817,"x":0,"y":0,"color":"#ffffff","backgroundColor":"#0000FF","text":"UnitStep16 Example","textOffset":3,"font":"Widgets.FONTS.DejaVu24","isSelected":false},{"name":"label1","type":"label","layer":2,"screenId":"builtin","screenName":"","id":"v5JN%$gy8kJh7Atf","createTime":1751507686563,"x":10,"y":55,"color":"#ffffff","backgroundColor":"#222222","text":"Encoder Value:","font":"Widgets.FONTS.DejaVu24","rotation":0,"isSelected":false,"width":187,"height":28},{"name":"label_val","type":"label","layer":3,"screenId":"builtin","screenName":"","id":"nSQ&PB#=`xN8R54`","createTime":1751507720338,"x":205,"y":55,"color":"#ffffff","backgroundColor":"#222222","text":"0","font":"Widgets.FONTS.DejaVu24","rotation":0,"isSelected":false,"width":58,"height":21}],"resources":[{"hardware":["hardware_button","hardware_pin_button","imu","speaker","touch","als","mic","sdcard","i2c"]},{"unit":["unit_step16"]}],"units":[{"type":"unit_step16","name":"step16_0","portList":["A","PAHUB","Custom"],"portType":"A","userPort":[22,21],"id":"mlIigel947MG9=zV","createTime":1751505025189,"bus":"i2c0","pahubPortList":[0,1,2,3,4,5],"pahubPort":0,"initBlockId":",mSjYhV8|u04s*mxosc~"}],"hats":[],"bases":[],"i2cs":[{"id":"i2c0","portType":"A","userPort":[22,21],"freq":"100000","blockId":"n(1Gao`KI2;6?~uuo_c2"}],"blockly":"valtrue010000012step16_00x48anti-clockwisei2c addr: step16_0anti-clockwiseversion: step16_0step16_0AUTO_OFF5step16_080hello M5rgb brightness: step16_0hello M5rgb value: step16_0step16_0RGB power onRG B power offstep16_0Truestep16_0palette#3333fftruevalstep16_0label_valLabelval","screen":[{"simulationName":"Built-in","type":"builtin","width":320,"height":240,"scale":0.77,"screenName":"","blockId":"","screenColorType":0,"rotation":1,"id":"builtin","createTime":1751504440363}],"logicWhenNum":0,"customList":[]} \ No newline at end of file diff --git a/examples/unit/step16/cores3_step16_unit_example.py b/examples/unit/step16/cores3_step16_unit_example.py new file mode 100644 index 00000000..f19aa2e5 --- /dev/null +++ b/examples/unit/step16/cores3_step16_unit_example.py @@ -0,0 +1,66 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import os, sys, io +import M5 +from M5 import * +from hardware import I2C +from hardware import Pin +from unit import Step16Unit + + +title0 = None +label1 = None +label_val = None +i2c0 = None +step16_0 = None +val = None + + +def setup(): + global title0, label1, label_val, i2c0, step16_0, val + M5.begin() + Widgets.setRotation(1) + Widgets.fillScreen(0x222222) + title0 = Widgets.Title("UnitStep16 Example", 3, 0xFFFFFF, 0x0000FF, Widgets.FONTS.DejaVu24) + label1 = Widgets.Label( + "Encoder Value:", 10, 55, 1.0, 0xFFFFFF, 0x222222, Widgets.FONTS.DejaVu24 + ) + label_val = Widgets.Label("0", 205, 55, 1.0, 0xFFFFFF, 0x222222, Widgets.FONTS.DejaVu24) + + i2c0 = I2C(0, scl=Pin(1), sda=Pin(2), freq=100000) + step16_0 = Step16Unit(i2c0, 0x48) + print((str("i2c addr: ") + str((step16_0.get_addr())))) + print((str("version: ") + str((step16_0.get_firmware_version())))) + step16_0.set_led_mode(Step16Unit.AUTO_OFF, 5) + step16_0.set_led_brightness(80) + print((str("rgb brightness: ") + str((step16_0.get_rgb_brightness())))) + print((str("rgb value: ") + str((step16_0.get_rgb_value())))) + if step16_0.get_rgb_power(): + print("RGB power on") + else: + print("RG B power off") + step16_0.set_rgb_power(True) + step16_0.set_rgb_value(0x3333FF) + + +def loop(): + global title0, label1, label_val, i2c0, step16_0, val + M5.update() + val = step16_0.get_encoder_value() + label_val.setText(str(val)) + + +if __name__ == "__main__": + try: + setup() + while True: + loop() + except (Exception, KeyboardInterrupt) as e: + try: + from utility import print_error_msg + + print_error_msg(e) + except ImportError: + print("please update to latest firmware") diff --git a/m5stack/libs/unit/__init__.py b/m5stack/libs/unit/__init__.py index 672ce52f..31e10ce4 100644 --- a/m5stack/libs/unit/__init__.py +++ b/m5stack/libs/unit/__init__.py @@ -113,6 +113,7 @@ "ScrollUnit": "scroll", "Servos8Unit": "servos8", "SSRUnit": "ssr", + "Step16Unit": "step16", "SynthUnit": "synth", "ThermalUnit": "thermal", "Thermal2Unit": "thermal2", diff --git a/m5stack/libs/unit/manifest.py b/m5stack/libs/unit/manifest.py index 1bc08eba..1fcf2468 100644 --- a/m5stack/libs/unit/manifest.py +++ b/m5stack/libs/unit/manifest.py @@ -112,6 +112,7 @@ "scroll.py", "servos8.py", "ssr.py", + "step16.py", "synth.py", "thermal.py", "thermal2.py", diff --git a/m5stack/libs/unit/step16.py b/m5stack/libs/unit/step16.py new file mode 100644 index 00000000..b724c58d --- /dev/null +++ b/m5stack/libs/unit/step16.py @@ -0,0 +1,404 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import machine +from micropython import const +from .pahub import PAHUBUnit +import time + + + +class Step16Unit: + """Create an Step16Unit object. + + :param I2C i2c: I2C port, + :param int | list | tuple addr: Step16Unit Slave Address + + UiFlow2 Code Block: + + |init.png| + + MicroPython Code Block: + + .. code-block:: python + + from unit import Step16Unit + + unit_step16_0 = Step16Unit(i2c0, 0x48) + """ + REG_ENCODER_VALUE = const(0x00) # Step16 编码器当前数值(有符号增量) + REG_LED_WORK_MODE = const(0x10) # LED 工作模式 + REG_LED_BRIGHTNESS = const(0x20) # LED 全局亮度(0~100) + REG_ENCODER_DIR_CFG = const(0x30) # 编码器方向配置(顺时针为加/减) + REG_RGB_POWER = const(0x40) # RGB 灯电源控制 + REG_RGB_BRIGHTNESS = const(0x41) # RGB 灯亮度(0~255,用作 r/g/b 的亮度乘权) + REG_RGB_R = const(0x50) # RGB 红色通道值(0~255) + REG_RGB_G = const(0x51) # RGB 绿色通道值(0~255) + REG_RGB_B = const(0x52) # RGB 蓝色通道值(0~255) + REG_SAVE_CFG = const(0xF0) # 保存 LED 设置(模式、亮度等)/ RGB 设置(颜色、电源、亮度) + REG_DEVICE_ADDR = const(0xFF) # 修改设备 I2C 地址 + REG_DEVICE_VERSION = const(0xFE) # 固件版本 + ALWAYS_OFF = const(0) # 常灭 + ALWAYS_ON = const(1) # 常亮 + AUTO_OFF = const(2) # 自动关闭 + def __init__(self, i2c: machine.I2C, addr: int | list | tuple = 0x48): + self.i2c = i2c + self.dev_addr = addr + if self.dev_addr not in self.i2c.scan(): + raise OSError("Step16Unit not found on I2C bus") + + def get_encoder_value(self) -> int: + """Get the current encoder value (0~15). + + :returns: Encoder value. + :rtype: int + + UiFlow2 Code Block: + + |get_encoder_value.png| + + MicroPython Code Block: + + .. code-block:: python + + value = unit_step16_0.get_encoder_value() + """ + return self.i2c.readfrom_mem(self.dev_addr, REG_ENCODER_VALUE, 1)[0] + + def set_encoder_cw_increase(self, enable: bool) -> None: + """Configure whether clockwise rotation increases encoder value. + + :param enable: + - True: Clockwise rotation increases the encoder value. + - False: Clockwise rotation decreases the encoder value. + :type enable: bool + + UiFlow2 Code Block: + + |set_encoder_cw_increase.png| + + MicroPython Code Block: + + .. code-block:: python + + unit_step16_0.set_encoder_cw_increase(True) + unit_step16_0.set_encoder_cw_increase(False) + """ + self.i2c.writeto_mem( + self.dev_addr, REG_ENCODER_DIR_CFG, bytearray([1 if enable else 0]) + ) + + def get_encoder_cw_increase(self) -> int: + """Get current encoder direction mode. + + :returns: 1 for increasing clockwise, 0 for decreasing. + :rtype: int + + UiFlow2 Code Block: + + |get_encoder_cw_increase.png| + + MicroPython Code Block: + + .. code-block:: python + + direction = unit_step16_0.get_encoder_cw_increase() + """ + return self.i2c.readfrom_mem(self.dev_addr, REG_ENCODER_DIR_CFG, 1)[0] + + def set_led_mode(self, mode: int, seconds: int = 5) -> None: + """Set LED display mode. + + :param mode: LED mode type. + 0 = always off, + 1 = always on, + 2 = auto-off mode with `seconds` as timeout. + :type mode: int + :param seconds: Timeout in seconds if `mode` is 2 (auto-off). + :type seconds: int + + UiFlow2 Code Block: + + |set_led_mode.png| + + MicroPython Code Block: + + .. code-block:: python + + unit_step16_0.set_led_mode(0) # Always off + unit_step16_0.set_led_mode(1) # Always on + unit_step16_0.set_led_mode(2, 10) # Auto-off after 10 seconds + """ + if mode == 0: + value = 0 + elif mode == 1: + value = 0xFE + elif mode == 2: + value = max(1, min(0xFD, seconds)) # Clamp to valid range + else: + raise ValueError("Invalid mode: must be 0 (on), 1 (off), or 2 (auto-off)") + self.i2c.writeto_mem(self.dev_addr, REG_LED_WORK_MODE, bytearray([value])) + + def get_led_mode(self) -> int: + """Get LED display mode. + + The LED mode values: + + - `0x00` : Always Off. + - `0xFE` : Always On. + - `0x00` ~ `0xFD` : Auto off times in seconds. + + :returns: LED display mode. + :rtype: int + + UiFlow2 Code Block: + + |get_led_mode.png| + + MicroPython Code Block: + + .. code-block:: python + + unit_step16_0.get_led_mode() + """ + return self.i2c.readfrom_mem(self.dev_addr, REG_LED_WORK_MODE, 1)[0] + + def set_led_brightness(self, brightness: int) -> None: + """Set LED brightness (0~100). + + :param brightness int: Brightness level. + :type brightness: int + + UiFlow2 Code Block: + + |set_led_brightness.png| + + MicroPython Code Block: + + .. code-block:: python + + unit_step16_0.set_led_brightness(80) + """ + brightness = max(0, min(100, brightness)) + self.i2c.writeto_mem(self.dev_addr, REG_LED_BRIGHTNESS, bytearray([brightness])) + + def get_led_brightness(self) -> int: + """Get current LED brightness. + + :returns: Brightness level. + :rtype: int + + UiFlow2 Code Block: + + |get_led_brightness.png| + + MicroPython Code Block: + + .. code-block:: python + + brightness = unit_step16_0.get_led_brightness() + print("Brightness:", brightness) + """ + return self.i2c.readfrom_mem(self.dev_addr, REG_LED_BRIGHTNESS, 1)[0] + + def set_rgb_power(self, enable: bool) -> None: + """Turn the RGB light power ON or OFF. + + :param enable: True to turn on the RGB light, False to turn it off. + :type enable: bool + + UiFlow2 Code Block: + + |set_rgb_power.png| + + MicroPython Code Block: + + .. code-block:: python + + unit_step16_0.set_rgb_power(True) # Turn ON RGB light + unit_step16_0.set_rgb_power(False) # Turn OFF RGB light + """ + val = 1 if enable else 0 + self.i2c.writeto_mem(self.dev_addr, REG_RGB_POWER, bytearray([val])) + + def get_rgb_power(self) -> bool: + """Get the current power status of the RGB light. + + :returns: True if the RGB light is ON, False if OFF. + :rtype: bool + + UiFlow2 Code Block: + + |get_rgb_power.png| + + MicroPython Code Block: + + .. code-block:: python + + power_on = unit_step16_0.get_rgb_power() + """ + return self.i2c.readfrom_mem(self.dev_addr, REG_RGB_POWER, 1)[0] == 1 + + def set_rgb_brightness(self, brightness) -> None: + """Set the brightness of the RGB light (0~100%). + + :param brightness: Brightness percentage (0~100). + :type brightness: int + + UiFlow2 Code Block: + + |set_rgb_brightness.png| + + MicroPython Code Block: + + .. code-block:: python + + unit_step16_0.set_rgb_brightness(80) # Set RGB brightness to 80% + """ + brightness = max(0, min(100, brightness)) + self.i2c.writeto_mem(self.dev_addr, REG_RGB_BRIGHTNESS, bytearray([brightness])) + + def get_rgb_brightness(self) -> int: + """Get the current RGB brightness level (0~100%). + + :returns: Current RGB brightness percentage (0~100). + :rtype: int + + UiFlow2 Code Block: + + |get_rgb_brightness.png| + + MicroPython Code Block: + + .. code-block:: python + + brightness = unit_step16_0.get_rgb_brightness() + print("RGB Brightness:", brightness) + """ + return self.i2c.readfrom_mem(self.dev_addr, REG_RGB_BRIGHTNESS, 1)[0] + + def set_rgb_value(self, color: int = 0) -> None: + """Set RGB LED color using a 24-bit integer. + + :param color: A 24-bit integer representing the RGB color (e.g., 0xFF8040 for R=255, G=128, B=64). + Format is (R << 16) | (G << 8) | B. + + UiFlow2 Code Block: + + |set_rgb_value.png| + + MicroPython Code Block: + + .. code-block:: python + + unit_step16_0.set_rgb_value() + """ + self.i2c.writeto_mem(self.dev_addr, REG_RGB_R, color.to_bytes(3, "big")) + + def get_rgb_value(self) -> tuple: + """Get current RGB LED color. + + :returns: Tuple of (r, g, b) + :rtype: tuple + + UiFlow2 Code Block: + + |get_rgb_value.png| + + MicroPython Code Block: + + .. code-block:: python + + r, g, b = unit_step16_0.get_rgb_value() + """ + data = self.i2c.readfrom_mem(self.dev_addr, REG_RGB_R, 3) + return tuple(data) + + def save_led_config(self) -> None: + """Save current LED mode and brightness settings. + + UiFlow2 Code Block: + + |save_led_config.png| + + MicroPython Code Block: + + .. code-block:: python + + unit_step16_0.save_led_config() + """ + self.i2c.writeto_mem(self.dev_addr, REG_SAVE_CFG, bytearray([1])) + + def save_rgb_config(self) -> None: + """Save current RGB color settings. + + UiFlow2 Code Block: + + |save_rgb_config.png| + + MicroPython Code Block: + + .. code-block:: python + + unit_step16_0.save_rgb_config() + """ + self.i2c.writeto_mem(self.dev_addr, REG_SAVE_CFG, bytearray([2])) + + def set_addr(self, new_addr: int) -> None: + """Set the device's I2C address. + + :param new_addr: New I2C address (0x08~0x77). + :type new_addr: int + + UiFlow2 Code Block: + + |set_addr.png| + + MicroPython Code Block: + + .. code-block:: python + + unit_step16_0.set_addr(0x49) + """ + new_addr = max(0x08, min(0x77, new_addr)) + self.i2c.writeto_mem(self.dev_addr, REG_DEVICE_ADDR, bytearray([new_addr])) + self.dev_addr = new_addr + time.sleep_ms(30) + + def get_addr(self) -> int: + """Get the current I2C device address. + + :returns: I2C address. + :rtype: int + + UiFlow2 Code Block: + + |get_addr.png| + + MicroPython Code Block: + + .. code-block:: python + + addr = unit_step16_0.get_addr() + """ + return self.i2c.readfrom_mem(self.dev_addr, REG_DEVICE_ADDR, 1)[0] + + def get_firmware_version(self) -> int: + """Get the firmware version. + + :returns: firmware version. + :rtype: int + + UiFlow2 Code Block: + + |get_firmware_version.png| + + MicroPython Code Block: + + .. code-block:: python + + addr = unit_step16_0.get_firmware_version() + """ + return self.i2c.readfrom_mem(self.dev_addr, REG_DEVICE_VERSION, 1)[0] + From 7ab59238b8829c50e4c32ca823f7ab76c3148dcd Mon Sep 17 00:00:00 2001 From: LittleMouse Date: Thu, 10 Jul 2025 15:29:21 +0800 Subject: [PATCH 158/322] libs/module/llm.py: Fix MeloTTS setup timeout. Signed-off-by: LittleMouse --- m5stack/libs/module/llm.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/m5stack/libs/module/llm.py b/m5stack/libs/module/llm.py index 1a54e489..4cb65137 100644 --- a/m5stack/libs/module/llm.py +++ b/m5stack/libs/module/llm.py @@ -603,7 +603,7 @@ def setup( } success = self._module_msg.send_cmd_and_wait_to_take_msg( - ujson.dumps(cmd), request_id, self._set_llm_work_id, 15000 + ujson.dumps(cmd), request_id, self._set_llm_work_id, 30000 ) ret_work_id = self._llm_work_id if success else "" From d754e47d15cde1e5cb6a2fa78879c1ea46596a60 Mon Sep 17 00:00:00 2001 From: lbuque Date: Thu, 10 Jul 2025 15:58:13 +0800 Subject: [PATCH 159/322] components/M5Unified: UserDisplay adds sip_mode parameter. Signed-off-by: lbuque --- m5stack/components/M5Unified/mpy_user_lcd.txt | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/m5stack/components/M5Unified/mpy_user_lcd.txt b/m5stack/components/M5Unified/mpy_user_lcd.txt index 553f6010..f2ecb3c3 100644 --- a/m5stack/components/M5Unified/mpy_user_lcd.txt +++ b/m5stack/components/M5Unified/mpy_user_lcd.txt @@ -45,7 +45,7 @@ public: void user_spi_panel_setup(user_panel_t panel_type, int16_t width, int16_t height, int16_t offset_x, int16_t offset_y, bool invert, bool rgb_order, - uint8_t spi_host, uint32_t spi_freq = 40, int16_t pin_sclk = -1, + uint8_t spi_host, uint32_t spi_freq = 40, int spi_mode = 0, int16_t pin_sclk = -1, int16_t pin_mosi = -1, int16_t pin_miso = -1, int16_t pin_dc = -1, int16_t pin_cs = -1, int16_t pin_rst = -1, int16_t pin_busy = -1) { @@ -53,7 +53,7 @@ public: auto cfg = _spi_bus_instance.config(); cfg.spi_host = (spi_host_device_t)spi_host; - cfg.spi_mode = 0; + cfg.spi_mode = spi_mode; cfg.freq_write = spi_freq * 1000000; cfg.freq_read = spi_freq * 1000000; cfg.spi_3wire = true; @@ -198,7 +198,7 @@ mp_obj_t user_panel_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_ // panel commom setting ARG_w, ARG_h, ARG_ox, ARG_oy, ARG_invert, ARG_rgb, // spi display panel setting - ARG_spi_host, ARG_spi_freq, ARG_sclk, ARG_mosi, ARG_miso, + ARG_spi_host, ARG_spi_freq, ARG_spi_mode, ARG_sclk, ARG_mosi, ARG_miso, ARG_dc, ARG_cs, ARG_rst, ARG_busy, // i2c display panel setting ARG_i2c_host, ARG_i2c_addr, ARG_i2c_freq, ARG_sda, ARG_scl, @@ -223,6 +223,7 @@ mp_obj_t user_panel_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_ // spi display panel setting { MP_QSTR_spi_host, MP_ARG_INT , {.u_int = 2 } }, { MP_QSTR_spi_freq, MP_ARG_INT , {.u_int = 40 } }, + { MP_QSTR_spi_mode, MP_ARG_INT , {.u_int = 0 } }, { MP_QSTR_sclk, MP_ARG_INT , {.u_int = -1 } }, { MP_QSTR_mosi, MP_ARG_INT , {.u_int = -1 } }, { MP_QSTR_miso, MP_ARG_INT , {.u_int = -1 } }, @@ -277,7 +278,7 @@ mp_obj_t user_panel_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_ (user_panel_t)panel_type, args[ARG_w].u_int, args[ARG_h].u_int, args[ARG_ox].u_int, args[ARG_oy].u_int, args[ARG_invert].u_bool, args[ARG_rgb].u_bool, - args[ARG_spi_host].u_int, args[ARG_spi_freq].u_int, + args[ARG_spi_host].u_int, args[ARG_spi_freq].u_int, args[ARG_spi_mode].u_int, args[ARG_sclk].u_int, args[ARG_mosi].u_int, args[ARG_miso].u_int, args[ARG_dc].u_int, args[ARG_cs].u_int, args[ARG_rst].u_int, From bce5718eeefa3b86263d09fb695431bd36293ee4 Mon Sep 17 00:00:00 2001 From: lbuque Date: Thu, 10 Jul 2025 15:51:49 +0800 Subject: [PATCH 160/322] pathches: Modify the parameter check of time.mktime. Signed-off-by: lbuque --- m5stack/patches/0006-modtime-add-timezone-method.patch | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/m5stack/patches/0006-modtime-add-timezone-method.patch b/m5stack/patches/0006-modtime-add-timezone-method.patch index 3600e06f..17ff9c23 100644 --- a/m5stack/patches/0006-modtime-add-timezone-method.patch +++ b/m5stack/patches/0006-modtime-add-timezone-method.patch @@ -56,6 +56,15 @@ Index: micropython/extmod/modtime.c MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mp_time_localtime_obj, 0, 1, time_localtime); // mktime() +@@ -86,7 +111,7 @@ static mp_obj_t time_mktime(mp_obj_t tup + mp_obj_get_array(tuple, &len, &elem); + + // localtime generates a tuple of len 8. CPython uses 9, so we accept both. +- if (len < 8 || len > 9) { ++ if (len < 6) { + mp_raise_TypeError(MP_ERROR_TEXT("mktime needs a tuple of length 8 or 9")); + } + @@ -200,7 +225,7 @@ static const mp_rom_map_elem_t mp_module { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_time) }, From de1333a53be4d7e983bcc6170c7e83dcd26eebda Mon Sep 17 00:00:00 2001 From: lbuque Date: Wed, 9 Jul 2025 14:54:49 +0800 Subject: [PATCH 161/322] .github: Compile using action's server. Signed-off-by: lbuque --- .github/workflows/build-release.yml | 190 +++++++++++----- .github/workflows/code_formatting.yml | 2 +- .github/workflows/nightly-build.yml | 307 +++++++++++--------------- .github/workflows/ports_m5stack.yml | 27 ++- tools/ci.sh | 23 +- 5 files changed, 312 insertions(+), 237 deletions(-) diff --git a/.github/workflows/build-release.yml b/.github/workflows/build-release.yml index 6463c808..6d1f3be7 100644 --- a/.github/workflows/build-release.yml +++ b/.github/workflows/build-release.yml @@ -11,60 +11,140 @@ permissions: packages: write jobs: + setup: + runs-on: ubuntu-latest + outputs: + cache-hit: ${{ steps.cache-esp-idf.outputs.cache-hit }} + steps: + - uses: actions/checkout@v4 + + - name: Install dependencies + run: | + sudo apt-get update + sudo apt-get install -y git wget flex bison gperf quilt python3 python3-pip \ + python3-venv cmake ninja-build ccache libffi-dev libssl-dev dfu-util libusb-1.0-0 + + - name: Cache esp-idf + uses: actions/cache@v4 + id: cache-esp-idf + with: + path: | + ~/.espressif + ${{ github.workspace }}/esp-idf + key: ${{ runner.os }}-idf-v5.4.1 + + - name: Install ESP-IDF + if: steps.cache-esp-idf.outputs.cache-hit != 'true' + run: | + git clone --depth=1 -b $IDF_VERSION https://github.com/espressif/esp-idf.git + ./esp-idf/install.sh + env: + IDF_VERSION: "v5.4.1" + + - name: Setup environment + run: | + source tools/ci.sh && ci_esp32_idf541_setup + source esp-idf/export.sh + pip install future + make -C m5stack submodules + make -C m5stack patch + make -C m5stack littlefs + + build: - runs-on: [self-hosted, Linux, X64] + needs: setup + runs-on: ubuntu-latest + strategy: + matrix: + board: + - M5STACK_AirQ + - M5STACK_Atom_Echo + - M5STACK_Atom_Lite + - M5STACK_Atom_Matrix + - M5STACK_AtomS3 + - M5STACK_AtomS3_Lite + - M5STACK_AtomS3R + - M5STACK_AtomS3R_CAM + - M5STACK_AtomS3U + - M5STACK_AtomU + - M5STACK_Basic + - M5STACK_Basic_4MB + - M5STACK_Capsule + - M5STACK_Cardputer + - M5STACK_Core2 + - M5STACK_CoreInk + - M5STACK_CoreS3 + - M5STACK_Dial + - M5STACK_DinMeter + - M5STACK_Fire + - M5STACK_NanoC6 + - M5STACK_Paper + - M5STACK_PaperS3 + - M5STACK_Stamp_PICO + - M5STACK_StamPLC + - M5STACK_StampS3 + - M5STACK_Station + - M5STACK_StickC + - M5STACK_StickC_PLUS + - M5STACK_StickC_PLUS2 + - M5STACK_Tab5 + - M5STACK_Tough + - ESPRESSIF_ESP32_S3_BOX_3 + - SEEED_STUDIO_XIAO_ESP32S3 + max-parallel: 4 steps: - - uses: actions/checkout@v3.3.0 - - - name: Install dependencies with apt - run: | - sudo apt-get update - sudo apt-get install git wget flex bison gperf quilt python3 python3-pip python3-venv cmake ninja-build ccache libffi-dev libssl-dev dfu-util libusb-1.0-0 -y - - - name: Install packages - run: source tools/ci.sh && ci_esp32_idf541_setup - - - name: Build - run: source tools/ci.sh && ci_esp32_nightly_build - - - name: Upload firmware - uses: softprops/action-gh-release@v2 - if: startsWith(github.ref, 'refs/tags/') - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - files: | - $GITHUB_WORKSPACE/m5stack/build-M5STACK_AirQ/uiflow-*-*.bin - $GITHUB_WORKSPACE/m5stack/build-M5STACK_Atom_Echo/uiflow-*-*.bin - $GITHUB_WORKSPACE/m5stack/build-M5STACK_Atom_Lite/uiflow-*-*.bin - $GITHUB_WORKSPACE/m5stack/build-M5STACK_Atom_Matrix/uiflow-*-*.bin - $GITHUB_WORKSPACE/m5stack/build-M5STACK_AtomS3/uiflow-*-*.bin - $GITHUB_WORKSPACE/m5stack/build-M5STACK_AtomS3_Lite/uiflow-*-*.bin - $GITHUB_WORKSPACE/m5stack/build-M5STACK_AtomS3R/uiflow-*-*.bin - $GITHUB_WORKSPACE/m5stack/build-M5STACK_AtomS3R_CAM/uiflow-*-*.bin - $GITHUB_WORKSPACE/m5stack/build-M5STACK_AtomS3U/uiflow-*-*.bin - $GITHUB_WORKSPACE/m5stack/build-M5STACK_AtomU/uiflow-*-*.bin - $GITHUB_WORKSPACE/m5stack/build-M5STACK_Basic/uiflow-*-*.bin - $GITHUB_WORKSPACE/m5stack/build-M5STACK_Basic_4MB/uiflow-*-*.bin - $GITHUB_WORKSPACE/m5stack/build-M5STACK_Capsule/uiflow-*-*.bin - $GITHUB_WORKSPACE/m5stack/build-M5STACK_Cardputer/uiflow-*-*.bin - $GITHUB_WORKSPACE/m5stack/build-M5STACK_Core2/uiflow-*-*.bin - $GITHUB_WORKSPACE/m5stack/build-M5STACK_CoreInk/uiflow-*-*.bin - $GITHUB_WORKSPACE/m5stack/build-M5STACK_CoreS3/uiflow-*-*.bin - $GITHUB_WORKSPACE/m5stack/build-M5STACK_Dial/uiflow-*-*.bin - $GITHUB_WORKSPACE/m5stack/build-M5STACK_DinMeter/uiflow-*-*.bin - $GITHUB_WORKSPACE/m5stack/build-M5STACK_Fire/uiflow-*-*.bin - $GITHUB_WORKSPACE/m5stack/build-M5STACK_NanoC6/uiflow-*-*.bin - $GITHUB_WORKSPACE/m5stack/build-M5STACK_Paper/uiflow-*-*.bin - $GITHUB_WORKSPACE/m5stack/build-M5STACK_PaperS3/uiflow-*-*.bin - $GITHUB_WORKSPACE/m5stack/build-M5STACK_Stamp_PICO/uiflow-*-*.bin - $GITHUB_WORKSPACE/m5stack/build-M5STACK_StamPLC/uiflow-*-*.bin - $GITHUB_WORKSPACE/m5stack/build-M5STACK_StampS3/uiflow-*-*.bin - $GITHUB_WORKSPACE/m5stack/build-M5STACK_Station/uiflow-*-*.bin - $GITHUB_WORKSPACE/m5stack/build-M5STACK_StickC/uiflow-*-*.bin - $GITHUB_WORKSPACE/m5stack/build-M5STACK_StickC_PLUS/uiflow-*-*.bin - $GITHUB_WORKSPACE/m5stack/build-M5STACK_StickC_PLUS2/uiflow-*-*.bin - $GITHUB_WORKSPACE/m5stack/build-M5STACK_Tab5/uiflow-*-*.bin - $GITHUB_WORKSPACE/m5stack/build-M5STACK_Tough/uiflow-*-*.bin - $GITHUB_WORKSPACE/third-party/build-SEEED_STUDIO_XIAO_ESP32S3/uiflow-*-*.bin - $GITHUB_WORKSPACE/third-party/build-ESPRESSIF_ESP32_S3_BOX_3/uiflow-*-*.bin + - uses: actions/checkout@v4 + + - name: Install dependencies + run: | + sudo apt-get update + sudo apt-get install -y git wget flex bison gperf quilt python3 python3-pip \ + python3-venv cmake ninja-build ccache libffi-dev libssl-dev dfu-util libusb-1.0-0 + + - name: Restore ESP-IDF cache + uses: actions/cache@v4 + with: + path: | + ~/.espressif + ${{ github.workspace }}/esp-idf + key: ${{ runner.os }}-idf-v5.4.1 + + - name: Prepare environment + run: | + source esp-idf/export.sh + + - name: Build M5Stack ${{ matrix.board }} + if: startsWith(matrix.board, 'M5STACK') + run: | + source esp-idf/export.sh + pip install future + make -C m5stack submodules + make -C m5stack patch + make -C m5stack littlefs + make -C m5stack BOARD=${{ matrix.board }} pack_all + + - name: Build third-party ${{ matrix.board }} + if: "!startsWith(matrix.board, 'M5STACK')" + run: | + source esp-idf/export.sh + pip install future + make -C m5stack submodules + make -C m5stack patch + make -C m5stack littlefs + make -C third-party BOARD=${{ matrix.board }} pack_all + + - name: Upload firmware artifact + uses: actions/upload-artifact@v4 + with: + name: firmware-${{ matrix.board }} + path: | + ${{ startsWith(matrix.board, 'M5STACK') && 'm5stack' || 'third-party' }}/build-${{ matrix.board }}/uiflow-*-*.bin + + - name: Upload firmware to release + uses: softprops/action-gh-release@v2 + if: startsWith(github.ref, 'refs/tags/') + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + files: | + ${{ startsWith(matrix.board, 'M5STACK') && 'm5stack' || 'third-party' }}/build-${{ matrix.board }}/uiflow-*-*.bin diff --git a/.github/workflows/code_formatting.yml b/.github/workflows/code_formatting.yml index 3b445976..41ca52cb 100644 --- a/.github/workflows/code_formatting.yml +++ b/.github/workflows/code_formatting.yml @@ -11,7 +11,7 @@ permissions: jobs: build: - runs-on: [self-hosted, Linux, X64] + runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 diff --git a/.github/workflows/nightly-build.yml b/.github/workflows/nightly-build.yml index 90b3de91..e4d11a3e 100644 --- a/.github/workflows/nightly-build.yml +++ b/.github/workflows/nightly-build.yml @@ -10,180 +10,137 @@ permissions: packages: write jobs: + setup: + runs-on: ubuntu-latest + outputs: + cache-hit: ${{ steps.cache-esp-idf.outputs.cache-hit }} + steps: + - uses: actions/checkout@v4 + + - name: Install dependencies + run: | + sudo apt-get update + sudo apt-get install -y git wget flex bison gperf quilt python3 python3-pip \ + python3-venv cmake ninja-build ccache libffi-dev libssl-dev dfu-util libusb-1.0-0 + + - name: Cache esp-idf + uses: actions/cache@v4 + id: cache-esp-idf + with: + path: | + ~/.espressif + ${{ github.workspace }}/esp-idf + key: ${{ runner.os }}-idf-v5.4.1 + + - name: Install ESP-IDF + if: steps.cache-esp-idf.outputs.cache-hit != 'true' + run: | + git clone --depth=1 -b $IDF_VERSION https://github.com/espressif/esp-idf.git + ./esp-idf/install.sh + env: + IDF_VERSION: "v5.4.1" + + - name: Setup environment + run: | + source tools/ci.sh && ci_esp32_idf541_setup + source esp-idf/export.sh + pip install future + make -C m5stack submodules + make -C m5stack patch + make -C m5stack littlefs + build: - runs-on: [self-hosted, Linux, X64] + needs: setup + runs-on: ubuntu-latest + strategy: + matrix: + board: + - M5STACK_AirQ + - M5STACK_Atom_Echo + - M5STACK_Atom_Lite + - M5STACK_Atom_Matrix + - M5STACK_AtomS3 + - M5STACK_AtomS3_Lite + - M5STACK_AtomS3R + - M5STACK_AtomS3R_CAM + - M5STACK_AtomS3U + - M5STACK_AtomU + - M5STACK_Basic + - M5STACK_Basic_4MB + - M5STACK_Capsule + - M5STACK_Cardputer + - M5STACK_Core2 + - M5STACK_CoreInk + - M5STACK_CoreS3 + - M5STACK_Dial + - M5STACK_DinMeter + - M5STACK_Fire + - M5STACK_NanoC6 + - M5STACK_Paper + - M5STACK_PaperS3 + - M5STACK_Stamp_PICO + - M5STACK_StamPLC + - M5STACK_StampS3 + - M5STACK_Station + - M5STACK_StickC + - M5STACK_StickC_PLUS + - M5STACK_StickC_PLUS2 + - M5STACK_Tab5 + - M5STACK_Tough + - ESPRESSIF_ESP32_S3_BOX_3 + - SEEED_STUDIO_XIAO_ESP32S3 + max-parallel: 4 steps: - - uses: actions/checkout@v3.3.0 - - name: Install dependencies with apt - run: | - sudo apt-get update - sudo apt-get install git wget flex bison gperf quilt python3 python3-pip python3-venv cmake ninja-build ccache libffi-dev libssl-dev dfu-util libusb-1.0-0 -y - - name: Install packages - run: source tools/ci.sh && ci_esp32_idf541_setup - - name: Build - run: source tools/ci.sh && ci_esp32_nightly_build - - name: Deliver AirQ firmware - uses: actions/upload-artifact@v4 - with: - name: M5STACK_AirQ_firmware - path: ./m5stack/build-M5STACK_AirQ/uiflow-*-*.bin - - name: Deliver Atom Echo firmware - uses: actions/upload-artifact@v4 - with: - name: M5STACK_Atom_Echo_firmware - path: ./m5stack/build-M5STACK_Atom_Echo/uiflow-*-*.bin - - name: Deliver Atom Lite firmware - uses: actions/upload-artifact@v4 - with: - name: M5STACK_Atom_Lite_firmware - path: ./m5stack/build-M5STACK_Atom_Lite/uiflow-*-*.bin - - name: Deliver Atom Matrix firmware - uses: actions/upload-artifact@v4 - with: - name: M5STACK_Atom_Matrix_firmware - path: ./m5stack/build-M5STACK_Atom_Matrix/uiflow-*-*.bin - - name: Deliver AtomS3 firmware - uses: actions/upload-artifact@v4 - with: - name: M5STACK_AtomS3_firmware - path: ./m5stack/build-M5STACK_AtomS3/uiflow-*-*.bin - - name: Deliver AtomS3-Lite firmware - uses: actions/upload-artifact@v4 - with: - name: M5STACK_AtomS3-Lite_firmware - path: ./m5stack/build-M5STACK_AtomS3_Lite/uiflow-*-*.bin - - name: Deliver AtomS3R firmware - uses: actions/upload-artifact@v4 - with: - name: M5STACK_AtomS3R_firmware - path: ./m5stack/build-M5STACK_AtomS3R/uiflow-*-*.bin - - name: Deliver AtomS3R-CAM firmware - uses: actions/upload-artifact@v4 - with: - name: M5STACK_AtomS3R_CAM_firmware - path: ./m5stack/build-M5STACK_AtomS3R_CAM/uiflow-*-*.bin - - name: Deliver AtomS3U firmware - uses: actions/upload-artifact@v4 - with: - name: M5STACK_AtomS3U_firmware - path: ./m5stack/build-M5STACK_AtomS3U/uiflow-*-*.bin - - name: Deliver AtomU firmware - uses: actions/upload-artifact@v4 - with: - name: M5STACK_AtomU_firmware - path: ./m5stack/build-M5STACK_AtomU/uiflow-*-*.bin - - name: Deliver Basic firmware - uses: actions/upload-artifact@v4 - with: - name: M5STACK_Basic_firmware - path: ./m5stack/build-M5STACK_Basic/uiflow-*-*.bin - - name: Deliver Basic(4MB Flash) firmware - uses: actions/upload-artifact@v4 - with: - name: M5STACK_Basic_4MB_Flash_firmware - path: ./m5stack/build-M5STACK_Basic_4MB/uiflow-*-*.bin - - name: Deliver Capsule firmware - uses: actions/upload-artifact@v4 - with: - name: M5STACK_Capsule_firmware - path: ./m5stack/build-M5STACK_Capsule/uiflow-*-*.bin - - name: Deliver Cardputer firmware - uses: actions/upload-artifact@v4 - with: - name: M5STACK_Cardputer_firmware - path: ./m5stack/build-M5STACK_Cardputer/uiflow-*-*.bin - - name: Deliver Core2 firmware - uses: actions/upload-artifact@v4 - with: - name: M5STACK_Core2_firmware - path: ./m5stack/build-M5STACK_Core2/uiflow-*-*.bin - - name: Deliver CoreInk firmware - uses: actions/upload-artifact@v4 - with: - name: M5STACK_CoreInk_firmware - path: ./m5stack/build-M5STACK_CoreInk/uiflow-*-*.bin - - name: Deliver CoreS3 firmware - uses: actions/upload-artifact@v4 - with: - name: M5STACK_CoreS3_firmware - path: ./m5stack/build-M5STACK_CoreS3/uiflow-*-*.bin - - name: Deliver Dial firmware - uses: actions/upload-artifact@v4 - with: - name: M5STACK_Dial_firmware - path: ./m5stack/build-M5STACK_Dial/uiflow-*-*.bin - - name: Deliver DinMeter firmware - uses: actions/upload-artifact@v4 - with: - name: M5STACK_DinMeter_firmware - path: ./m5stack/build-M5STACK_DinMeter/uiflow-*-*.bin - - name: Deliver Fire firmware - uses: actions/upload-artifact@v4 - with: - name: M5STACK_Fire_firmware - path: ./m5stack/build-M5STACK_Fire/uiflow-*-*.bin - - name: Deliver NanoC6 firmware - uses: actions/upload-artifact@v4 - with: - name: M5STACK_NanoC6_firmware - path: ./m5stack/build-M5STACK_NanoC6/uiflow-*-*.bin - - name: Deliver Paper firmware - uses: actions/upload-artifact@v4 - with: - name: M5STACK_Paper_firmware - path: ./m5stack/build-M5STACK_Paper/uiflow-*-*.bin - - name: Deliver PaperS3 firmware - uses: actions/upload-artifact@v4 - with: - name: M5STACK_Paper_firmware - path: ./m5stack/build-M5STACK_PaperS3/uiflow-*-*.bin - - name: Deliver Stamp PICO firmware - uses: actions/upload-artifact@v4 - with: - name: M5STACK_Stamp_PICO_firmware - path: ./m5stack/build-M5STACK_Stamp_PICO/uiflow-*-*.bin - - name: Deliver StamPLC firmware - uses: actions/upload-artifact@v4 - with: - name: M5STACK_StamPLC_firmware - path: ./m5stack/build-M5STACK_StamPLC/uiflow-*-*.bin - - name: Deliver StampS3 firmware - uses: actions/upload-artifact@v4 - with: - name: M5STACK_StampS3_firmware - path: ./m5stack/build-M5STACK_StampS3/uiflow-*-*.bin - - name: Deliver Station firmware - uses: actions/upload-artifact@v4 - with: - name: M5STACK_Station_firmware - path: ./m5stack/build-M5STACK_Station/uiflow-*-*.bin - - name: Deliver StickC firmware - uses: actions/upload-artifact@v4 - with: - name: M5STACK_StickC_firmware - path: ./m5stack/build-M5STACK_StickC/uiflow-*-*.bin - - name: Deliver StickC_PLUS firmware - uses: actions/upload-artifact@v4 - with: - name: M5STACK_StickC_PLUS_firmware - path: ./m5stack/build-M5STACK_StickC_PLUS/uiflow-*-*.bin - - name: Deliver StickC_PLUS2 firmware - uses: actions/upload-artifact@v4 - with: - name: M5STACK_StickC_PLUS2_firmware - path: ./m5stack/build-M5STACK_StickC_PLUS2/uiflow-*-*.bin - - name: Deliver Tough firmware - uses: actions/upload-artifact@v4 - with: - name: M5STACK_Tough_firmware - path: ./m5stack/build-M5STACK_Tough/uiflow-*-*.bin - - name: Deliver XIAOS3 firmware - uses: actions/upload-artifact@v4 - with: - name: M5STACK_XIAOS3_firmware - path: ./third-party/build-SEEED_STUDIO_XIAO_ESP32S3/uiflow-*-*.bin - - name: Deliver BOX-3 firmware - uses: actions/upload-artifact@v4 - with: - name: M5STACK_BOX_3_firmware - path: ./third-party/build-ESPRESSIF_ESP32_S3_BOX_3/uiflow-*-*.bin + - uses: actions/checkout@v4 + + - name: Install dependencies + run: | + sudo apt-get update + sudo apt-get install -y git wget flex bison gperf quilt python3 python3-pip \ + python3-venv cmake ninja-build ccache libffi-dev libssl-dev dfu-util libusb-1.0-0 + + - name: Restore ESP-IDF cache + uses: actions/cache@v4 + with: + path: | + ~/.espressif + ${{ github.workspace }}/esp-idf + key: ${{ runner.os }}-idf-v5.4.1 + + - name: Prepare environment + run: | + source esp-idf/export.sh + + - name: Build M5Stack ${{ matrix.board }} + if: startsWith(matrix.board, 'M5STACK') + run: | + source esp-idf/export.sh + pip install future + make -C m5stack submodules + make -C m5stack patch + make -C m5stack littlefs + make -C m5stack BOARD=${{ matrix.board }} pack_all + + - name: Build third-party ${{ matrix.board }} + if: "!startsWith(matrix.board, 'M5STACK')" + run: | + source esp-idf/export.sh + pip install future + make -C m5stack submodules + make -C m5stack patch + make -C m5stack littlefs + make -C third-party BOARD=${{ matrix.board }} pack_all + + - name: Upload M5Stack firmware artifact + if: startsWith(matrix.board, 'M5STACK') + uses: actions/upload-artifact@v4 + with: + name: firmware-${{ matrix.board }} + path: m5stack/build-${{ matrix.board }}/uiflow-*-*.bin + + - name: Upload third-party firmware artifact + if: "!startsWith(matrix.board, 'M5STACK')" + uses: actions/upload-artifact@v4 + with: + name: firmware-${{ matrix.board }} + path: third-party/build-${{ matrix.board }}/uiflow-*-*.bin diff --git a/.github/workflows/ports_m5stack.yml b/.github/workflows/ports_m5stack.yml index 577d4848..012428cf 100644 --- a/.github/workflows/ports_m5stack.yml +++ b/.github/workflows/ports_m5stack.yml @@ -92,7 +92,9 @@ jobs: - M5STACK_StickC_PLUS2 - M5STACK_Tab5 - M5STACK_Tough - max-parallel: 3 + - ESPRESSIF_ESP32_S3_BOX_3 + - SEEED_STUDIO_XIAO_ESP32S3 + max-parallel: 4 steps: - uses: actions/checkout@v4 @@ -114,7 +116,8 @@ jobs: run: | source esp-idf/export.sh - - name: Build ${{ matrix.board }} + - name: Build M5Stack ${{ matrix.board }} + if: startsWith(matrix.board, 'M5STACK') run: | source esp-idf/export.sh pip install future @@ -123,8 +126,26 @@ jobs: make -C m5stack littlefs make -C m5stack BOARD=${{ matrix.board }} pack_all - - name: Upload firmware artifact + - name: Build third-party ${{ matrix.board }} + if: "!startsWith(matrix.board, 'M5STACK')" + run: | + source esp-idf/export.sh + pip install future + make -C m5stack submodules + make -C m5stack patch + make -C m5stack littlefs + make -C third-party BOARD=${{ matrix.board }} pack_all + + - name: Upload M5Stack firmware artifact + if: startsWith(matrix.board, 'M5STACK') uses: actions/upload-artifact@v4 with: name: firmware-${{ matrix.board }} path: m5stack/build-${{ matrix.board }}/uiflow-*-*.bin + + - name: Upload third-party firmware artifact + if: "!startsWith(matrix.board, 'M5STACK')" + uses: actions/upload-artifact@v4 + with: + name: firmware-${{ matrix.board }} + path: third-party/build-${{ matrix.board }}/uiflow-*-*.bin diff --git a/tools/ci.sh b/tools/ci.sh index 41df994a..29053711 100644 --- a/tools/ci.sh +++ b/tools/ci.sh @@ -13,10 +13,27 @@ fi ######################################################################################## # code formatting +function uncrustify_setup { + if [ uncrustify --version | grep -q "Uncrustify-0.72.0_f" ]; then + echo "uncrustify 0.72.0 is already installed." + return 0 + fi + + wget https://github.com/uncrustify/uncrustify/archive/refs/tags/uncrustify-0.72.0.tar.gz + tar -xvf uncrustify-0.72.0.tar.gz + cd uncrustify-uncrustify-0.72.0 + mkdir build + cd build + cmake -DCMAKE_BUILD_TYPE=Release .. + make -j$(nproc) + sudo make install + cd ../.. + rm -rf uncrustify-0.72.0.tar.gz + rm -rf uncrustify-uncrustify-0.72.0 +} + function ci_code_formatting_setup { - sudo apt-add-repository --yes ppa:pybricks/ppa - sudo apt update - sudo apt-get install uncrustify -y + uncrustify_setup sudo apt install pipx -y pipx install uv uv venv From add4ff04b31c4aef2aa72f6b335dd11267083ac9 Mon Sep 17 00:00:00 2001 From: lbuque Date: Thu, 10 Jul 2025 17:52:01 +0800 Subject: [PATCH 162/322] version.text: Update to V2.3.1. Signed-off-by: lbuque --- m5stack/version.txt | 2 +- third-party/version.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/m5stack/version.txt b/m5stack/version.txt index 0d52044a..d8be0998 100644 --- a/m5stack/version.txt +++ b/m5stack/version.txt @@ -1 +1 @@ -V2.3.0 \ No newline at end of file +V2.3.1 \ No newline at end of file diff --git a/third-party/version.txt b/third-party/version.txt index 0d52044a..d8be0998 100644 --- a/third-party/version.txt +++ b/third-party/version.txt @@ -1 +1 @@ -V2.3.0 \ No newline at end of file +V2.3.1 \ No newline at end of file From 4b33eeaebad148ede387ee53abfa244b09a3a623 Mon Sep 17 00:00:00 2001 From: mikeluyten Date: Sat, 12 Jul 2025 11:26:07 +1000 Subject: [PATCH 163/322] libs/unit: Adding missing LoRaWANUnit_RUI3. Signed-off-by: lbuque <1102390310@qq.com> --- m5stack/libs/unit/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/m5stack/libs/unit/__init__.py b/m5stack/libs/unit/__init__.py index 31e10ce4..4cfa9b6e 100644 --- a/m5stack/libs/unit/__init__.py +++ b/m5stack/libs/unit/__init__.py @@ -77,6 +77,7 @@ "LoRaE220433Unit": "lora_e220_433", "LoRaE220JPUnit": "lora_e220_jp", "LoRaWANUnit": "lorawan", + "LoRaWANUnit_RUI3": "lorawan_rui3", "MIDIUnit": "midi", "MiniOLEDUnit": "minioled", "MiniScaleUnit": "miniscale", From 10942753ed36db37988438c88649ea869ac8aa2d Mon Sep 17 00:00:00 2001 From: FuNK3Y Date: Mon, 14 Jul 2025 10:18:08 +0200 Subject: [PATCH 164/322] boards/M5STACK_AtomS3R: Enable OPI PSRAM. Signed-off-by: lbuque <1102390310@qq.com> --- m5stack/boards/M5STACK_AtomS3R/mpconfigboard.cmake | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/m5stack/boards/M5STACK_AtomS3R/mpconfigboard.cmake b/m5stack/boards/M5STACK_AtomS3R/mpconfigboard.cmake index 4b44c727..9cb4f202 100644 --- a/m5stack/boards/M5STACK_AtomS3R/mpconfigboard.cmake +++ b/m5stack/boards/M5STACK_AtomS3R/mpconfigboard.cmake @@ -17,6 +17,7 @@ set(SDKCONFIG_DEFAULTS ./boards/sdkconfig.usb ./boards/sdkconfig.usb_cdc ./boards/sdkconfig.flash_8mb + ./boards/sdkconfig.spiram ./boards/sdkconfig.spiram_oct ./boards/sdkconfig.freertos ./boards/M5STACK_AtomS3R/sdkconfig.board @@ -39,4 +40,4 @@ list(APPEND EXTRA_COMPONENT_DIRS $ENV{ADF_PATH}/components/esp-adf-libs $ENV{ADF_PATH}/components/esp-sr ${CMAKE_SOURCE_DIR}/boards -) \ No newline at end of file +) From c9da391a5b4cb1792978a3b78de62ed2dfd87eac Mon Sep 17 00:00:00 2001 From: lbuque <1102390310@qq.com> Date: Fri, 25 Jul 2025 14:41:33 +0800 Subject: [PATCH 165/322] tools/ci.sh: Support for building with esp-idf v5.4.2. Signed-off-by: lbuque <1102390310@qq.com> --- .github/workflows/build-release.yml | 2 +- .gitlab-ci.yml | 2 +- m5stack/Makefile | 3 ++- ...-micropython-1.25.0-machine-adc-v5.x.patch | 12 +++++++--- ...python-1.25.0-support-esp-idf-v5.4.2.patch | 24 +++++++++++++++++++ tools/ci.sh | 8 +++---- 6 files changed, 41 insertions(+), 10 deletions(-) create mode 100644 m5stack/patches/0018-micropython-1.25.0-support-esp-idf-v5.4.2.patch diff --git a/.github/workflows/build-release.yml b/.github/workflows/build-release.yml index 6d1f3be7..13af8868 100644 --- a/.github/workflows/build-release.yml +++ b/.github/workflows/build-release.yml @@ -7,7 +7,7 @@ on: workflow_dispatch: permissions: - contents: read + contents: write packages: write jobs: diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 9be21e3c..7b980af7 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -3,7 +3,7 @@ variables: ESP_IDF_SRC_DIR: $CI_PROJECT_DIR/esp-idf cache: - key: "$CI_PROJECT_ID-esp-idf-v541" + key: "$CI_PROJECT_ID-esp-idf-v542" paths: - $ESP_IDF_SRC_DIR policy: pull-push diff --git a/m5stack/Makefile b/m5stack/Makefile index eeaa97e4..be665d70 100644 --- a/m5stack/Makefile +++ b/m5stack/Makefile @@ -312,7 +312,8 @@ MICROPYTHON_PATCH_SERIES = \ 0014-micropython-1.25.0-fix-timer.patch \ 0015-micropython-1.25.0-fix-hostname.patch \ 0016-micropython-1.25.0-fix-mpnimbleport.patch \ - 0017-micropython-1.25.0-add-uart-mode.patch + 0017-micropython-1.25.0-add-uart-mode.patch \ + 0018-micropython-1.25.0-support-esp-idf-v5.4.2.patch IDF_PATH_PATCH_SERIES = \ 1004-idf_v5.4_freertos.patch diff --git a/m5stack/patches/0010-micropython-1.25.0-machine-adc-v5.x.patch b/m5stack/patches/0010-micropython-1.25.0-machine-adc-v5.x.patch index a1924d35..0c338cdd 100644 --- a/m5stack/patches/0010-micropython-1.25.0-machine-adc-v5.x.patch +++ b/m5stack/patches/0010-micropython-1.25.0-machine-adc-v5.x.patch @@ -11,7 +11,7 @@ Index: micropython/ports/esp32/adc.c #define DEFAULT_VREF 1100 -@@ -57,51 +55,52 @@ void madcblock_bits_helper(machine_adc_b +@@ -57,52 +55,57 @@ void madcblock_bits_helper(machine_adc_b mp_raise_ValueError(MP_ERROR_TEXT("invalid bits")); } self->bits = bits; @@ -78,10 +78,16 @@ Index: micropython/ports/esp32/adc.c int uv; - check_esp_err(ensure_adc_calibration(self, atten)); -+ check_esp_err(ensure_adc_calibration(self, channel_id, atten)); - check_esp_err(adc_cali_raw_to_voltage(self->handle[atten], raw, &uv)); +- check_esp_err(adc_cali_raw_to_voltage(self->handle[atten], raw, &uv)); ++ esp_err_t ret = ensure_adc_calibration(self, channel_id, atten); ++ if (ret == ESP_OK) { ++ check_esp_err(adc_cali_raw_to_voltage(self->handle[atten], raw, &uv)); ++ } else { ++ uv = raw / (2 << (self->width)) * 3300; ++ } return (mp_int_t)uv * 1000; + } Index: micropython/ports/esp32/adc.h =================================================================== --- micropython.orig/ports/esp32/adc.h diff --git a/m5stack/patches/0018-micropython-1.25.0-support-esp-idf-v5.4.2.patch b/m5stack/patches/0018-micropython-1.25.0-support-esp-idf-v5.4.2.patch new file mode 100644 index 00000000..fb626b23 --- /dev/null +++ b/m5stack/patches/0018-micropython-1.25.0-support-esp-idf-v5.4.2.patch @@ -0,0 +1,24 @@ +Index: micropython/ports/esp32/panichandler.c +=================================================================== +--- micropython.orig/ports/esp32/panichandler.c ++++ micropython/ports/esp32/panichandler.c +@@ -42,11 +42,19 @@ + #endif + + void __real_esp_panic_handler(void *); ++#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 4, 2) ++void esp_panic_handler_enable_rtc_wdt(uint32_t timeout_ms); ++#else + void esp_panic_handler_reconfigure_wdts(uint32_t timeout_ms); ++#endif + void panic_print_str(const char *str); + + void MICROPY_WRAP_PANICHANDLER_FUN(__wrap_esp_panic_handler)(void *info) { ++#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 4, 2) ++ esp_panic_handler_enable_rtc_wdt(1000); ++#else + esp_panic_handler_reconfigure_wdts(1000); ++#endif + + const static char *msg = MICROPY_WRAP_PANICHANDLER_STR( + "\r\n" diff --git a/tools/ci.sh b/tools/ci.sh index 29053711..0d7e8a34 100644 --- a/tools/ci.sh +++ b/tools/ci.sh @@ -181,16 +181,16 @@ function ci_esp32_idf522_setup { function ci_esp32_idf541_setup { if [ -d esp-idf ]; then echo "esp-idf is already cloned." - if [ "$(git -C esp-idf describe --tags)" == "v5.4.1" ]; then - echo "esp-idf is on v5.4.1 branch." + if [ "$(git -C esp-idf describe --tags)" == "v5.4.2" ]; then + echo "esp-idf is on v5.4.2 branch." return 0 else - echo "esp-idf is not on v5.4.1 branch." + echo "esp-idf is not on v5.4.2 branch." rm -rf esp-idf fi fi - git clone --depth 1 --branch v5.4.1 https://github.com/espressif/esp-idf.git + git clone --depth 1 --branch v5.4.2 https://github.com/espressif/esp-idf.git git -C esp-idf submodule update --init ./esp-idf/install.sh } From 8ffbb9ffd65cc7f18e5af334e3261e7ba7031bf2 Mon Sep 17 00:00:00 2001 From: tinyu Date: Tue, 15 Jul 2025 14:09:11 +0800 Subject: [PATCH 166/322] lib/unit: Fixed the abnormal reading of weight_i2c. Signed-off-by: tinyu --- m5stack/libs/unit/weight_i2c.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/m5stack/libs/unit/weight_i2c.py b/m5stack/libs/unit/weight_i2c.py index cf57786f..6a6ccff1 100644 --- a/m5stack/libs/unit/weight_i2c.py +++ b/m5stack/libs/unit/weight_i2c.py @@ -3,8 +3,8 @@ # SPDX-License-Identifier: MIT from machine import I2C -from .pahub import PAHUBUnit -from .unit_helper import UnitError +from unit.pahub import PAHUBUnit +from unit.unit_helper import UnitError import time import struct @@ -81,7 +81,7 @@ def set_reset_offset(self) -> None: def get_weight_int(self) -> int: """! Get the weight in grams(int).""" data = self.i2c.readfrom_mem(self.unit_addr, WEIGHT_X100_REG, 4) - return int(struct.unpack(" str: From b12470a1fa28b0adfa123aa2e83b29c3952402eb Mon Sep 17 00:00:00 2001 From: tinyu Date: Fri, 18 Jul 2025 10:40:23 +0800 Subject: [PATCH 167/322] lib/m5ui: Add LVGL Line widget and docs. Signed-off-by: tinyu --- docs/en/m5ui/index.rst | 5 +- docs/en/m5ui/line.rst | 124 +++++++++ docs/en/refs/m5ui.line.ref | 19 ++ docs/locales/zh_CN/LC_MESSAGES/m5ui/line.po | 263 ++++++++++++++++++++ examples/m5ui/line/cores3_line_example.m5f2 | 1 + examples/m5ui/line/cores3_line_example.py | 53 ++++ m5stack/libs/m5ui/__init__.py | 1 + m5stack/libs/m5ui/line.py | 118 +++++++++ m5stack/libs/m5ui/manifest.py | 1 + 9 files changed, 583 insertions(+), 2 deletions(-) create mode 100644 docs/en/m5ui/line.rst create mode 100644 docs/en/refs/m5ui.line.ref create mode 100644 docs/locales/zh_CN/LC_MESSAGES/m5ui/line.po create mode 100644 examples/m5ui/line/cores3_line_example.m5f2 create mode 100644 examples/m5ui/line/cores3_line_example.py create mode 100644 m5stack/libs/m5ui/line.py diff --git a/docs/en/m5ui/index.rst b/docs/en/m5ui/index.rst index 88a0e2ce..c1d60ceb 100644 --- a/docs/en/m5ui/index.rst +++ b/docs/en/m5ui/index.rst @@ -33,8 +33,9 @@ Classes :maxdepth: 1 page.rst - label.rst + bar.rst button.rst + label.rst + line.rst switch.rst image.rst - bar.rst diff --git a/docs/en/m5ui/line.rst b/docs/en/m5ui/line.rst new file mode 100644 index 00000000..2f45043f --- /dev/null +++ b/docs/en/m5ui/line.rst @@ -0,0 +1,124 @@ +.. currentmodule:: m5ui + +M5Line +======== + +.. include:: ../refs/m5ui.line.ref + +M5Line is a widget that can be used to create lines in the user interface. It can be used to draw shapes and connect points. + +UiFlow2 Example +--------------- + +points connect +^^^^^^^^^^^^^^ + +Open the |cores3_line_example.m5f2| project in UiFlow2. + +This example creates a line that connects multiple points. + +UiFlow2 Code Block: + + |cores3_line_example.png| + +Example output: + + None + + +MicroPython Example +------------------- + +points connect +^^^^^^^^^^^^^^ + +This example creates a line that connects multiple points. + +MicroPython Code Block: + + .. literalinclude:: ../../../examples/m5ui/line/cores3_line_example.py + :language: python + :linenos: + +Example output: + + None + + +**API** +------- + +M5Line +^^^^^^^ + +.. autoclass:: m5ui.line.M5Line + :members: + + .. py:method:: set_pos(x, y) + + Set the position of the switch. + + :param int x: The x-coordinate of the switch. + :param int y: The y-coordinate of the switch. + + UiFlow2 Code Block: + + |set_pos.png| + + MicroPython Code Block: + + .. code-block:: python + + switch_0.set_pos(100, 100) + + .. py:method:: set_x(x) + + Set the x-coordinate of the switch. + + :param int x: The x-coordinate of the switch. + + UiFlow2 Code Block: + + |set_x.png| + + MicroPython Code Block: + + .. code-block:: python + + switch_0.set_x(100) + + .. py:method:: set_y(y) + + Set the y-coordinate of the switch. + + :param int y: The y-coordinate of the switch. + + UiFlow2 Code Block: + + |set_y.png| + + MicroPython Code Block: + + .. code-block:: python + + switch_0.set_y(100) + + + .. py:method:: align_to(obj, align, x, y) + + Align the switch to another object. + + :param lv.obj obj: The object to align to. + :param int align: The alignment type. + :param int x: The x-offset from the aligned object. + :param int y: The y-offset from the aligned object. + + UiFlow2 Code Block: + + |align_to.png| + + MicroPython Code Block: + + .. code-block:: python + + switch_0.align_to(page_0, lv.ALIGN.CENTER, 0, 0) diff --git a/docs/en/refs/m5ui.line.ref b/docs/en/refs/m5ui.line.ref new file mode 100644 index 00000000..8c5e595e --- /dev/null +++ b/docs/en/refs/m5ui.line.ref @@ -0,0 +1,19 @@ +.. |align_to.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/line/align_to.png +.. |set_pos.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/line/set_pos.png +.. |set_size.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/line/set_size.png +.. |set_x.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/line/set_x.png +.. |set_y.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/line/set_y.png +.. |set_line_color.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/line/set_line_color.png +.. |set_points.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/line/set_points.png +.. |add_point.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/line/add_point.png + +.. |cores3_line_example.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/line/example.png + +.. |cores3_line_example.m5f2| raw:: html + + + cores3_line_example.m5f2 + diff --git a/docs/locales/zh_CN/LC_MESSAGES/m5ui/line.po b/docs/locales/zh_CN/LC_MESSAGES/m5ui/line.po new file mode 100644 index 00000000..517d348d --- /dev/null +++ b/docs/locales/zh_CN/LC_MESSAGES/m5ui/line.po @@ -0,0 +1,263 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2016 - 2025 M5Stack Technology Co., Ltd +# This file is distributed under the same license as the UIFlow2 Programming +# Guide package. +# FIRST AUTHOR , 2025. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: UIFlow2 Programming Guide \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-07-17 18:23+0800\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: zh_CN\n" +"Language-Team: zh_CN \n" +"Plural-Forms: nplurals=1; plural=0;\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.16.0\n" + +#: ../../en/m5ui/line.rst:4 ../../en/m5ui/line.rst:52 +#: 7607a76255bf4f5084aa92fc7f432964 a1755e319ea140728fe24d582a58b032 +msgid "M5Line" +msgstr "" + +#: ../../en/m5ui/line.rst:8 a9ea1d92b912466ca601cb5aa70648cb +msgid "" +"M5Line is a widget that can be used to create lines in the user " +"interface. It can be used to draw shapes and connect points." +msgstr "M5Line 是一个可在用户界面中绘制直线的控件,可用于绘制形状或连接多个点。" + +#: ../../en/m5ui/line.rst:11 fc3a9e27bc314b02bff6ac512205b6d2 +msgid "UiFlow2 Example" +msgstr "UiFlow2 示例" + +#: ../../en/m5ui/line.rst:14 ../../en/m5ui/line.rst:33 +#: 1d2a560c616746d5b2936155ca5a3954 907b9b416c994605845579f92952f4e1 +msgid "points connect" +msgstr "连接点" + +#: ../../en/m5ui/line.rst:16 5164e857c3d546c4b695f5ac43ef5cca +msgid "Open the |cores3_line_example.m5f2| project in UiFlow2." +msgstr "在 UiFlow2 中打开 |cores3_line_example.m5f2| 项目。" + +#: ../../en/m5ui/line.rst:18 ../../en/m5ui/line.rst:35 +#: 0bd252e4601c4dd4a9f8623cb8820463 b76ac01627a640cd8fe9edc40b43afb2 +msgid "This example creates a line that connects multiple points." +msgstr "本示例将创建一条用于连接多个点的线段。" + +#: ../../en/m5ui/line.rst:20 ../../en/m5ui/line.rst:64 +#: ../../en/m5ui/line.rst:80 ../../en/m5ui/line.rst:96 +#: ../../en/m5ui/line.rst:116 09ca2b2f35524a06ab3e0a8264cce1ab +#: 0ad9ac7ef74741b9a4361eff53543fb5 5efc8723908440bca65868d21ed7bd8b +#: 87410a7252ba43a8bc7981f2ad6e9589 9f6341a7c9204ea6adc3527db5d55e61 +#: f56df05d50624069bc0c9fe2810b11c0 m5ui.line.M5Line.add_point:6 +#: m5ui.line.M5Line.set_line_color:6 m5ui.line.M5Line.set_points:5 of +msgid "UiFlow2 Code Block:" +msgstr "UiFlow2 代码块:" + +#: ../../en/m5ui/line.rst:22 83e9c6d9436e4a03b7d1fb648dfd6429 +msgid "|cores3_line_example.png|" +msgstr "" + +#: ../../en/refs/m5ui.line.ref:10 e71269eec30747fcbec3d8aa7fb78f2f +msgid "cores3_line_example.png" +msgstr "" + +#: ../../en/m5ui/line.rst:24 ../../en/m5ui/line.rst:43 +#: 1ef75e3b7ba04d119e01d73e305a323b 72291626b96140dd812f89b8985d41ee +msgid "Example output:" +msgstr "示例输出:" + +#: ../../en/m5ui/line.rst:26 ../../en/m5ui/line.rst:45 +#: 1abad8f0df3e4de1b98d395d2f5d85bc 9d69fc41092946ec90e2d1a85e5ede23 +msgid "None" +msgstr "无" + +#: ../../en/m5ui/line.rst:30 68cee7a357784554853809fdf4956e13 +msgid "MicroPython Example" +msgstr "MicroPython 示例" + +#: ../../en/m5ui/line.rst:37 ../../en/m5ui/line.rst:68 +#: ../../en/m5ui/line.rst:84 ../../en/m5ui/line.rst:100 +#: ../../en/m5ui/line.rst:120 208b8a61597641078d4443fb979e4903 +#: 52cb12f2e3c146af9d58c3298aab84a3 66ccd86bc0e14f8791b6f6ef92f26078 +#: a26704f13b9644bea7d60d55f84d9790 bac912dd327c41b68ad5dbbd4802fe3d +#: c02c5011d7714725951364d245bd77d5 fcc36b517e4a486fb43efdc2eff089c3 +#: m5ui.line.M5Line:9 m5ui.line.M5Line.add_point:10 +#: m5ui.line.M5Line.set_line_color:10 m5ui.line.M5Line.set_points:9 of +msgid "MicroPython Code Block:" +msgstr "MicroPython 代码块:" + +#: ../../en/m5ui/line.rst:49 20e87b9990a34545b806020127bdae26 +msgid "**API**" +msgstr "" + +#: 362e2a3f3c5740edb0a5ca34203206ca m5ui.line.M5Line:1 of +msgid "Bases: :py:class:`~lvgl.line`" +msgstr "" + +#: 0e6fe28e921640f9b09e532f50d9c159 m5ui.line.M5Line:1 of +msgid "Create a line object." +msgstr "创建一个直线对象。" + +#: ../../en/m5ui/line.rst 39484576315448cf910a1157010d99e8 +#: 689ee15ad47b40e4b2e2243292354427 71e2bed486824601ab789ac81e13f07b +#: 7ddb8d73439d41f7bac5060bb2b05159 8e7815c2c538419ba3bd8e67c364afa3 +#: 9c5fa2a8615d47198b97ecb82394cd85 d8415e5cf32f4ad0867098bc4f086a80 +#: m5ui.line.M5Line.add_point m5ui.line.M5Line.set_line_color +#: m5ui.line.M5Line.set_points of +msgid "Parameters" +msgstr "参数" + +#: 40f2dded77c244d58160b845904f0004 b172130b74c241b4bd3ec328bd64c554 +#: m5ui.line.M5Line:3 m5ui.line.M5Line.set_points:3 of +msgid "A list of points where each point is a pair of x and y coordinates." +msgstr "点列表,每个点由一对 x、y 坐标组成。" + +#: e48e83b0951445bc93c73d9eeeb52894 m5ui.line.M5Line:4 of +msgid "The width of the line." +msgstr "线宽。" + +#: 0512a5721ed64f17be2b4d9dbb9fa550 cfb5abbde77641b39ce23b7a3172cbef +#: m5ui.line.M5Line:5 m5ui.line.M5Line.set_line_color:3 of +msgid "The color of the line in hexadecimal format." +msgstr "线的颜色(十六进制)。" + +#: db2eea24545c492ca034387b472dc1c1 m5ui.line.M5Line:6 of +msgid "" +"If True, the line will have rounded ends; otherwise, it will have square " +"ends." +msgstr "若为 True,则线段端点将为圆形;若为 False,则端点将为方形。" + +#: 94a781e671414a5bac194647f6604896 m5ui.line.M5Line:7 of +msgid "" +"The parent object to attach the line to. If not specified, the line will " +"be attached to the default screen." +msgstr "要将线附加到的父对象;若未指定,则附加到默认屏幕。" + +#: ../../en/m5ui/line.rst:59 b7a8b689ac224e1db379cd4f0938b0e3 +msgid "Set the position of the switch." +msgstr "设置线的位置。" + +#: ../../en/m5ui/line.rst:61 ../../en/m5ui/line.rst:78 +#: 5ece9e038b5043f0b3a7399735cdd65d f03cbc9b871f496f958cf37e1183b670 +msgid "The x-coordinate of the switch." +msgstr "线的 x 坐标。" + +#: ../../en/m5ui/line.rst:62 ../../en/m5ui/line.rst:94 +#: 07677161ec2e4df498b4648b50705fc7 adb67a83a2ab4f8b8f2b5fd59bcdf997 +msgid "The y-coordinate of the switch." +msgstr "线的 y 坐标。" + +#: ../../en/m5ui/line.rst:66 55d7901faf8443efa5f7c4bbb30760dc +msgid "|set_pos.png|" +msgstr "" + +#: ../../en/refs/m5ui.line.ref:2 3df262e766934c0cb46466bf356c0360 +msgid "set_pos.png" +msgstr "" + +#: ../../en/m5ui/line.rst:76 1d978d84c9354e7b912edf583dac8449 +msgid "Set the x-coordinate of the switch." +msgstr "设置线的 x 坐标。" + +#: ../../en/m5ui/line.rst:82 e2412c8fb4424cd3a2a7fe943f9dcf5d +msgid "|set_x.png|" +msgstr "" + +#: ../../en/refs/m5ui.line.ref:4 d90a3a6380e5431d9da0a8f53da997b9 +msgid "set_x.png" +msgstr "" + +#: ../../en/m5ui/line.rst:92 4c4b0f36863849908e62f817ddc06580 +msgid "Set the y-coordinate of the switch." +msgstr "设置线的 y 坐标。" + +#: ../../en/m5ui/line.rst:98 928fd9955e8643b997f14fa16c980615 +msgid "|set_y.png|" +msgstr "" + +#: ../../en/refs/m5ui.line.ref:5 92892a022bb54e919bbf1670edfd2d78 +msgid "set_y.png" +msgstr "" + +#: ../../en/m5ui/line.rst:109 6f85e913c7434d6d9453a87596cedc21 +msgid "Align the switch to another object." +msgstr "将线对齐到另一个对象。" + +#: ../../en/m5ui/line.rst:111 279d42ce36f0435f99c4a2603782e803 +msgid "The object to align to." +msgstr "要对齐到的对象。" + +#: ../../en/m5ui/line.rst:112 94e2c0d394dd4365a3a7a78fbecde0fe +msgid "The alignment type." +msgstr "对齐类型。" + +#: ../../en/m5ui/line.rst:113 8a98f6d71a1a4c169bddab971125c416 +msgid "The x-offset from the aligned object." +msgstr "相对于对齐对象的 x 偏移量。" + +#: ../../en/m5ui/line.rst:114 4e113ffa4f9f4db8973ca145e8f95e8f +msgid "The y-offset from the aligned object." +msgstr "相对于对齐对象的 y 偏移量。" + +#: ../../en/m5ui/line.rst:118 4459151a9b5741f2884bafe3c6db590e +msgid "|align_to.png|" +msgstr "" + +#: ../../en/refs/m5ui.line.ref:1 4b5144583e7945b5a0bb1e9a86073396 +msgid "align_to.png" +msgstr "" + +#: 2b04578ad9cf42e0896df31dc289fb3a m5ui.line.M5Line.set_line_color:1 of +msgid "Set the color and opacity of the line." +msgstr "设置线的颜色和透明度。" + +#: a5256fe5bd35485f85b393462604d055 m5ui.line.M5Line.set_line_color:4 of +msgid "The opacity level (0-255)." +msgstr "透明度级别(0-255)。" + +#: fcfc77041bc040fdbfb19cca69c2bea2 m5ui.line.M5Line.set_line_color:8 of +msgid "|set_line_color.png|" +msgstr "" + +#: ../../en/refs/m5ui.line.ref:6 d8ca570fe2d94623bd9132adecfca6c1 +msgid "set_line_color.png" +msgstr "" + +#: 9c33f7b983c3410e8a296322d7cf190f m5ui.line.M5Line.set_points:1 of +msgid "Set the points of the line." +msgstr "设置线的点列表。" + +#: 6c551b61882446159b3e5514222991a0 m5ui.line.M5Line.set_points:7 of +msgid "|set_points.png|" +msgstr "" + +#: ../../en/refs/m5ui.line.ref:7 23a61fac549c465886e2aa463e506c9f +msgid "set_points.png" +msgstr "" + +#: dca9c6c2c02b4469be62cfaef7802c51 m5ui.line.M5Line.add_point:1 of +msgid "Add a point to the line end." +msgstr "在线的末端添加一个点。" + +#: ef299bd152f645528e35cc91cdeb095e m5ui.line.M5Line.add_point:3 of +msgid "The x position of the point." +msgstr "点的 x 坐标。" + +#: b8fe74156e1f4321a4ea92d0a1a8c62b m5ui.line.M5Line.add_point:4 of +msgid "The y position of the point." +msgstr "点的 y 坐标。" + +#: 4f184d5f0f674393986530dc779bc378 m5ui.line.M5Line.add_point:8 of +msgid "|add_point.png|" +msgstr "" + +#: ../../en/refs/m5ui.line.ref:8 fbb99756546947f3ab74b9f2fe3f038a +msgid "add_point.png" +msgstr "" + diff --git a/examples/m5ui/line/cores3_line_example.m5f2 b/examples/m5ui/line/cores3_line_example.m5f2 new file mode 100644 index 00000000..d0d1e38a --- /dev/null +++ b/examples/m5ui/line/cores3_line_example.m5f2 @@ -0,0 +1 @@ +{"version":"V2.0","versionNumber":"V2.3.1","type":"cores3","components":[{"name":"page0","type":"lvgl_page","layer":0,"screenId":"builtin","screenName":"","id":"uD&V9olaIlrQlDpv","createTime":1752747224218,"backgroundColor":"#ffffff","isLVGL":true,"isSelected":true},{"name":"line0","type":"lvgl_line","layer":1,"screenId":"builtin","screenName":"","id":"mr84Yg+wRm!Ewlm!","createTime":1752747228607,"color":"#2196f3","width":7,"points":[[5,5],[70,70],[120,10],[180,60],[190,70],[200,80],[210,90],[220,100]],"pageId":"uD&V9olaIlrQlDpv","isLVGL":true,"isSelected":false}],"resources":[{"hardware":["hardware_button","hardware_pin_button","imu","speaker","touch","als","mic","sdcard"]}],"units":[],"hats":[],"bases":[],"i2cs":[],"blockly":"truepage0trueline0100100","screen":[{"simulationName":"Built-in","type":"builtin","width":320,"height":240,"scale":0.77,"screenName":"","blockId":"","screenColorType":0,"rotation":1,"id":"builtin","createTime":1752747224217}],"logicWhenNum":0,"customList":[]} \ No newline at end of file diff --git a/examples/m5ui/line/cores3_line_example.py b/examples/m5ui/line/cores3_line_example.py new file mode 100644 index 00000000..421cd5ba --- /dev/null +++ b/examples/m5ui/line/cores3_line_example.py @@ -0,0 +1,53 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import os, sys, io +import M5 +from M5 import * +import m5ui +import lvgl as lv + + +page0 = None +line0 = None + + +def setup(): + global page0, line0 + + M5.begin() + Widgets.setRotation(1) + m5ui.init() + page0 = m5ui.M5Page(bg_c=0xFFFFFF) + line0 = m5ui.M5Line( + points=[5, 5, 70, 70, 120, 10, 180, 60, 190, 70, 200, 80, 210, 90, 220, 100], + width=7, + color=0x2196F3, + rounded=True, + parent=page0, + ) + + page0.screen_load() + + +def loop(): + global page0, line0 + M5.update() + if M5.Touch.getCount(): + line0.add_point(M5.Touch.getX(), M5.Touch.getY()) + + +if __name__ == "__main__": + try: + setup() + while True: + loop() + except (Exception, KeyboardInterrupt) as e: + try: + m5ui.deinit() + from utility import print_error_msg + + print_error_msg(e) + except ImportError: + print("please update to latest firmware") diff --git a/m5stack/libs/m5ui/__init__.py b/m5stack/libs/m5ui/__init__.py index 0cf48520..99485944 100644 --- a/m5stack/libs/m5ui/__init__.py +++ b/m5stack/libs/m5ui/__init__.py @@ -9,6 +9,7 @@ "M5Button": "button", "M5Image": "image", "M5Label": "label", + "M5Line": "line", "M5Page": "page", "M5Switch": "switch", } diff --git a/m5stack/libs/m5ui/line.py b/m5stack/libs/m5ui/line.py new file mode 100644 index 00000000..6b1eecfb --- /dev/null +++ b/m5stack/libs/m5ui/line.py @@ -0,0 +1,118 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +from m5ui.base import M5Base +import lvgl as lv + + +class M5Line(lv.line): + """Create a line object. + + :param list points: A list of points where each point is a pair of x and y coordinates. + :param int width: The width of the line. + :param int color: The color of the line in hexadecimal format. + :param bool rounded: If True, the line will have rounded ends; otherwise, it will have square ends. + :param lv.obj parent: The parent object to attach the line to. If not specified, the line will be attached to the default screen. + + MicroPython Code Block: + + .. code-block:: python + + from m5ui import M5Image + import lvgl as lv + + m5ui.init() + line_0 = M5Line( + points=[5, 5, 70, 70, 120, 10, 180, 60, 240, 20], + width=2, + color=0x2196F3, + rounded=True, + parent=page0, + ) + """ + + def __init__( + self, + points=list([]), + width=1, + color=0x3F51B5, + rounded=True, + parent=None, + ): + if parent is None: + parent = lv.screen_active() + super().__init__(parent) + + self.set_points(points) + self.set_line_color(color, 255) + self.set_style_line_width(width, lv.PART.MAIN) + self.set_style_line_rounded(rounded, lv.PART.MAIN) + + def set_line_color(self, color, opa: int): + """Set the color and opacity of the line. + + :param int color: The color of the line in hexadecimal format. + :param int opa: The opacity level (0-255). + + UiFlow2 Code Block: + + |set_line_color.png| + + MicroPython Code Block: + + .. code-block:: python + + label_0.set_line_color(0x2196F3) + """ + if isinstance(color, int): + color = lv.color_hex(color) + self.set_style_line_color(color, lv.PART.MAIN) + self.set_style_line_opa(opa, lv.PART.MAIN) + + def set_points(self, points: list): + """Set the points of the line. + + :param list points: A list of points where each point is a pair of x and y coordinates. + + UiFlow2 Code Block: + + |set_points.png| + + MicroPython Code Block: + + .. code-block:: python + + label_0.set_points([0, 0, 100, 100, 200, 50]) + """ + self.lv_points = [] + for i in range(0, len(points), 2): + self.lv_points.append({"x": points[i], "y": points[i + 1]}) + super().set_points(self.lv_points, len(self.lv_points)) + + def add_point(self, x, y): + """Add a point to the line end. + + :param int x: The x position of the point. + :param int y: The y position of the point. + + UiFlow2 Code Block: + + |add_point.png| + + MicroPython Code Block: + + .. code-block:: python + + label_0.add_point(100, 100) + """ + self.lv_points.append({"x": x, "y": y}) + super().set_points(self.lv_points, len(self.lv_points)) + + def __getattr__(self, name): + if hasattr(M5Base, name): + method = getattr(M5Base, name) + bound_method = lambda *args, **kwargs: method(self, *args, **kwargs) + setattr(self, name, bound_method) + return bound_method + raise AttributeError(f"'{self.__class__.__name__}' object has no attribute '{name}'") diff --git a/m5stack/libs/m5ui/manifest.py b/m5stack/libs/m5ui/manifest.py index 1758b6ae..64efd6b6 100644 --- a/m5stack/libs/m5ui/manifest.py +++ b/m5stack/libs/m5ui/manifest.py @@ -11,6 +11,7 @@ "button.py", "image.py", "label.py", + "line.py", "page.py", "port.py", "switch.py", From a270c783ce14bde2323821530cb10765e1fc1deb Mon Sep 17 00:00:00 2001 From: tinyu Date: Thu, 24 Jul 2025 14:43:52 +0800 Subject: [PATCH 168/322] lib/m5ui: Supplement the documentation and API of m5ui.line. Signed-off-by: tinyu --- docs/en/m5ui/line.rst | 79 +++++-- docs/en/refs/m5ui.line.ref | 2 + docs/locales/zh_CN/LC_MESSAGES/m5ui/line.po | 241 ++++++++++++-------- m5stack/libs/m5ui/line.py | 25 +- 4 files changed, 220 insertions(+), 127 deletions(-) diff --git a/docs/en/m5ui/line.rst b/docs/en/m5ui/line.rst index 2f45043f..ef5df299 100644 --- a/docs/en/m5ui/line.rst +++ b/docs/en/m5ui/line.rst @@ -53,13 +53,66 @@ M5Line .. autoclass:: m5ui.line.M5Line :members: - + + .. py:method:: set_line_color(color, opa, part) + + Set the color of the line. + + :param int color: The color to set. + :param int opa: The opacity of the color. + :param int part: The part of the object to apply the style to (e.g., lv.PART.MAIN). + + UiFlow2 Code Block: + + |set_line_color.png| + + MicroPython Code Block: + + .. code-block:: python + + line_0.set_line_color(0xFF0000, 255, lv.PART.MAIN) + + .. py:method:: set_style_line_width(width, part) + + Set the width of the line. + + :param int width: The width to set. + :param int part: The part of the object to apply the style to (e.g., lv.PART.MAIN). + + UiFlow2 Code Block: + + |set_style_line_width.png| + + MicroPython Code Block: + + .. code-block:: python + + line_0.set_style_line_width(2, lv.PART.MAIN) + + .. py:method:: set_flag(flag, value) + + Set a flag on the object. If ``value`` is True, the flag is added; if False, the flag is removed. + + :param int flag: The flag to set. + :param bool value: If True, the flag is added; if False, the flag is removed. + :return: None + + UiFlow2 Code Block: + + |set_flag.png| + + MicroPython Code Block: + + .. code-block:: python + + button_0.set_flag(lv.obj.FLAG.HIDDEN, True) + .. py:method:: set_pos(x, y) - Set the position of the switch. + Set the position of the line. - :param int x: The x-coordinate of the switch. - :param int y: The y-coordinate of the switch. + :param int x: The x-coordinate of the line. + :param int y: The y-coordinate of the line. UiFlow2 Code Block: @@ -69,13 +122,13 @@ M5Line .. code-block:: python - switch_0.set_pos(100, 100) + line_0.set_pos(100, 100) .. py:method:: set_x(x) - Set the x-coordinate of the switch. + Set the x-coordinate of the line. - :param int x: The x-coordinate of the switch. + :param int x: The x-coordinate of the line. UiFlow2 Code Block: @@ -85,13 +138,13 @@ M5Line .. code-block:: python - switch_0.set_x(100) + line_0.set_x(100) .. py:method:: set_y(y) - Set the y-coordinate of the switch. + Set the y-coordinate of the line. - :param int y: The y-coordinate of the switch. + :param int y: The y-coordinate of the line. UiFlow2 Code Block: @@ -101,12 +154,12 @@ M5Line .. code-block:: python - switch_0.set_y(100) + line_0.set_y(100) .. py:method:: align_to(obj, align, x, y) - Align the switch to another object. + Align the line to another object. :param lv.obj obj: The object to align to. :param int align: The alignment type. @@ -121,4 +174,4 @@ M5Line .. code-block:: python - switch_0.align_to(page_0, lv.ALIGN.CENTER, 0, 0) + line_0.align_to(page_0, lv.ALIGN.CENTER, 0, 0) diff --git a/docs/en/refs/m5ui.line.ref b/docs/en/refs/m5ui.line.ref index 8c5e595e..8a74f69d 100644 --- a/docs/en/refs/m5ui.line.ref +++ b/docs/en/refs/m5ui.line.ref @@ -6,6 +6,8 @@ .. |set_line_color.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/line/set_line_color.png .. |set_points.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/line/set_points.png .. |add_point.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/line/add_point.png +.. |set_style_line_width.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/line/set_style_line_width.png +.. |set_flag.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/line/set_flag.png .. |cores3_line_example.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/line/example.png diff --git a/docs/locales/zh_CN/LC_MESSAGES/m5ui/line.po b/docs/locales/zh_CN/LC_MESSAGES/m5ui/line.po index 517d348d..aa405e3c 100644 --- a/docs/locales/zh_CN/LC_MESSAGES/m5ui/line.po +++ b/docs/locales/zh_CN/LC_MESSAGES/m5ui/line.po @@ -9,7 +9,7 @@ msgid "" msgstr "" "Project-Id-Version: UIFlow2 Programming Guide \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-07-17 18:23+0800\n" +"POT-Creation-Date: 2025-07-22 18:31+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language: zh_CN\n" @@ -21,243 +21,302 @@ msgstr "" "Generated-By: Babel 2.16.0\n" #: ../../en/m5ui/line.rst:4 ../../en/m5ui/line.rst:52 -#: 7607a76255bf4f5084aa92fc7f432964 a1755e319ea140728fe24d582a58b032 +#: 4e9ac41402734476933ead366423899f f7e7e9b6ecfe4e9cb937fac63ba22e81 msgid "M5Line" msgstr "" -#: ../../en/m5ui/line.rst:8 a9ea1d92b912466ca601cb5aa70648cb +#: ../../en/m5ui/line.rst:8 17b13182ead34d36b1e057abe4310765 msgid "" "M5Line is a widget that can be used to create lines in the user " "interface. It can be used to draw shapes and connect points." msgstr "M5Line 是一个可在用户界面中绘制直线的控件,可用于绘制形状或连接多个点。" -#: ../../en/m5ui/line.rst:11 fc3a9e27bc314b02bff6ac512205b6d2 +#: ../../en/m5ui/line.rst:11 dc10a37f23884bf1be7df7fe1527886c msgid "UiFlow2 Example" msgstr "UiFlow2 示例" #: ../../en/m5ui/line.rst:14 ../../en/m5ui/line.rst:33 -#: 1d2a560c616746d5b2936155ca5a3954 907b9b416c994605845579f92952f4e1 +#: 23e7de1d2e534986b6616990b007be85 3fa71e599512462fa07fe4d2c5520c5c msgid "points connect" msgstr "连接点" -#: ../../en/m5ui/line.rst:16 5164e857c3d546c4b695f5ac43ef5cca +#: ../../en/m5ui/line.rst:16 ee8add85fa8a4d009602841b89385540 msgid "Open the |cores3_line_example.m5f2| project in UiFlow2." msgstr "在 UiFlow2 中打开 |cores3_line_example.m5f2| 项目。" #: ../../en/m5ui/line.rst:18 ../../en/m5ui/line.rst:35 -#: 0bd252e4601c4dd4a9f8623cb8820463 b76ac01627a640cd8fe9edc40b43afb2 +#: c5fa7d40073a4659bb4da1b127425fab e950ae1a262c4db78f0ae1fb8c35bdad msgid "This example creates a line that connects multiple points." msgstr "本示例将创建一条用于连接多个点的线段。" -#: ../../en/m5ui/line.rst:20 ../../en/m5ui/line.rst:64 -#: ../../en/m5ui/line.rst:80 ../../en/m5ui/line.rst:96 -#: ../../en/m5ui/line.rst:116 09ca2b2f35524a06ab3e0a8264cce1ab -#: 0ad9ac7ef74741b9a4361eff53543fb5 5efc8723908440bca65868d21ed7bd8b -#: 87410a7252ba43a8bc7981f2ad6e9589 9f6341a7c9204ea6adc3527db5d55e61 -#: f56df05d50624069bc0c9fe2810b11c0 m5ui.line.M5Line.add_point:6 -#: m5ui.line.M5Line.set_line_color:6 m5ui.line.M5Line.set_points:5 of +#: ../../en/m5ui/line.rst:20 ../../en/m5ui/line.rst:65 +#: ../../en/m5ui/line.rst:82 ../../en/m5ui/line.rst:100 +#: ../../en/m5ui/line.rst:117 ../../en/m5ui/line.rst:133 +#: ../../en/m5ui/line.rst:149 ../../en/m5ui/line.rst:169 +#: 2116594b2731451e9d5535e7fe82794c 427d4219ea7d4fa5ae11c910eae85399 +#: 48721f415cad4cb892fd9fd7f161aa07 5353352a70ab40f9a72376f3319aefdc +#: 5c0581ce516c46c3a3420bb8e9346c60 70f781549de9488a81777146bcbacd84 +#: dd6c557610db4f22919548731e5aee10 e147bbcc28ce4981859357515d481566 +#: e30ca10b326e49f881647aa72235bc62 e42af52893564a09ae32461622f23e9b +#: m5ui.line.M5Line.add_point:6 m5ui.line.M5Line.set_points:5 of msgid "UiFlow2 Code Block:" msgstr "UiFlow2 代码块:" -#: ../../en/m5ui/line.rst:22 83e9c6d9436e4a03b7d1fb648dfd6429 +#: ../../en/m5ui/line.rst:22 bb6021bdde514bfb8ce19688b96e04a0 msgid "|cores3_line_example.png|" msgstr "" -#: ../../en/refs/m5ui.line.ref:10 e71269eec30747fcbec3d8aa7fb78f2f +#: ../../en/refs/m5ui.line.ref:10 4c3c6452930a4394b8f5eacb7bf50d07 msgid "cores3_line_example.png" msgstr "" #: ../../en/m5ui/line.rst:24 ../../en/m5ui/line.rst:43 -#: 1ef75e3b7ba04d119e01d73e305a323b 72291626b96140dd812f89b8985d41ee +#: 21ff7439342d47fdbfa2fbf54eddddfb 3a4d14a1505843d89b1ce44803835988 msgid "Example output:" msgstr "示例输出:" #: ../../en/m5ui/line.rst:26 ../../en/m5ui/line.rst:45 -#: 1abad8f0df3e4de1b98d395d2f5d85bc 9d69fc41092946ec90e2d1a85e5ede23 +#: ../../en/m5ui/line.rst:98 3992417ae39a4637b4f2b8634a16155f +#: 4539490e7d55435ca1388643c02ce94b ecb111c73b9f42d1adfba19ce4b30b10 msgid "None" msgstr "无" -#: ../../en/m5ui/line.rst:30 68cee7a357784554853809fdf4956e13 +#: ../../en/m5ui/line.rst:30 061d068357ff487d915f4201cf1898fb msgid "MicroPython Example" msgstr "MicroPython 示例" -#: ../../en/m5ui/line.rst:37 ../../en/m5ui/line.rst:68 -#: ../../en/m5ui/line.rst:84 ../../en/m5ui/line.rst:100 -#: ../../en/m5ui/line.rst:120 208b8a61597641078d4443fb979e4903 -#: 52cb12f2e3c146af9d58c3298aab84a3 66ccd86bc0e14f8791b6f6ef92f26078 -#: a26704f13b9644bea7d60d55f84d9790 bac912dd327c41b68ad5dbbd4802fe3d -#: c02c5011d7714725951364d245bd77d5 fcc36b517e4a486fb43efdc2eff089c3 -#: m5ui.line.M5Line:9 m5ui.line.M5Line.add_point:10 -#: m5ui.line.M5Line.set_line_color:10 m5ui.line.M5Line.set_points:9 of +#: ../../en/m5ui/line.rst:37 ../../en/m5ui/line.rst:69 +#: ../../en/m5ui/line.rst:86 ../../en/m5ui/line.rst:104 +#: ../../en/m5ui/line.rst:121 ../../en/m5ui/line.rst:137 +#: ../../en/m5ui/line.rst:153 ../../en/m5ui/line.rst:173 +#: 5d9d2ee39e3c473fb9e7835798e6cb8f 65a1229eacaf49b680a75a93ff688c25 +#: 65ad44f0e969408b940c9e592fa76565 66477766c7834522a56d706ee2a8e913 +#: 6b21ba0e60124180ad9ed578b111e9bf a157fa0f6cb34bccaf02d55d093e1ba2 +#: adb439238eb74630ad2e54c924099f6f b26c3f777ba04e419d3897f29a04485e +#: b5510d23c2e0423e907304dbbb157de1 cc5ba6a47e284a8fba07451993e64710 +#: eae4203d2b754ee98a777d9c4fa262ec m5ui.line.M5Line:9 +#: m5ui.line.M5Line.add_point:10 m5ui.line.M5Line.set_points:9 of msgid "MicroPython Code Block:" msgstr "MicroPython 代码块:" -#: ../../en/m5ui/line.rst:49 20e87b9990a34545b806020127bdae26 +#: ../../en/m5ui/line.rst:49 3d3e160d908249bd96fd395f4b59a1f4 msgid "**API**" msgstr "" -#: 362e2a3f3c5740edb0a5ca34203206ca m5ui.line.M5Line:1 of +#: 863f597576c848c0a187eb3922a110ee m5ui.line.M5Line:1 of msgid "Bases: :py:class:`~lvgl.line`" msgstr "" -#: 0e6fe28e921640f9b09e532f50d9c159 m5ui.line.M5Line:1 of +#: ecd330e467464404bb18b036e88f9d45 m5ui.line.M5Line:1 of msgid "Create a line object." msgstr "创建一个直线对象。" -#: ../../en/m5ui/line.rst 39484576315448cf910a1157010d99e8 -#: 689ee15ad47b40e4b2e2243292354427 71e2bed486824601ab789ac81e13f07b -#: 7ddb8d73439d41f7bac5060bb2b05159 8e7815c2c538419ba3bd8e67c364afa3 -#: 9c5fa2a8615d47198b97ecb82394cd85 d8415e5cf32f4ad0867098bc4f086a80 -#: m5ui.line.M5Line.add_point m5ui.line.M5Line.set_line_color +#: ../../en/m5ui/line.rst 59ca44aeed424372bce14c45b80d2c24 +#: 781306246a364808913407eab0b547b1 7d42f2ca3e0c44e582886281ce8319e2 +#: 991e9cb847bd4216a620f68d88d4a35e a64a5914832a4a32a2a57331b752243a +#: b14a5de580b944a992e8f398572f006b bcd06f4864714e369a6e3c077b0fbc00 +#: c6b468ca5fe1420faa3464d34c11a2c8 d74b63258c944ff2876a71f533f90fc5 +#: f95ea6cf60834b6d9f1111c43a488cf9 m5ui.line.M5Line.add_point #: m5ui.line.M5Line.set_points of msgid "Parameters" msgstr "参数" -#: 40f2dded77c244d58160b845904f0004 b172130b74c241b4bd3ec328bd64c554 +#: 5d3a1bf55fb1490b9d46e279dc229b8c 75e25086eabf41d29e7c611d69811815 #: m5ui.line.M5Line:3 m5ui.line.M5Line.set_points:3 of msgid "A list of points where each point is a pair of x and y coordinates." msgstr "点列表,每个点由一对 x、y 坐标组成。" -#: e48e83b0951445bc93c73d9eeeb52894 m5ui.line.M5Line:4 of +#: 278292e11e7d45c4915cd2de8be03794 m5ui.line.M5Line:4 of msgid "The width of the line." msgstr "线宽。" -#: 0512a5721ed64f17be2b4d9dbb9fa550 cfb5abbde77641b39ce23b7a3172cbef -#: m5ui.line.M5Line:5 m5ui.line.M5Line.set_line_color:3 of +#: bb5566cb36e54bf4a8e717c10c1684bc m5ui.line.M5Line:5 of msgid "The color of the line in hexadecimal format." msgstr "线的颜色(十六进制)。" -#: db2eea24545c492ca034387b472dc1c1 m5ui.line.M5Line:6 of +#: 5bcce0bdc59f4912b1df2c1a343cfef0 m5ui.line.M5Line:6 of msgid "" "If True, the line will have rounded ends; otherwise, it will have square " "ends." msgstr "若为 True,则线段端点将为圆形;若为 False,则端点将为方形。" -#: 94a781e671414a5bac194647f6604896 m5ui.line.M5Line:7 of +#: 34da604f149f4c6aacfe023371db4118 m5ui.line.M5Line:7 of msgid "" "The parent object to attach the line to. If not specified, the line will " "be attached to the default screen." msgstr "要将线附加到的父对象;若未指定,则附加到默认屏幕。" -#: ../../en/m5ui/line.rst:59 b7a8b689ac224e1db379cd4f0938b0e3 -msgid "Set the position of the switch." +#: ../../en/m5ui/line.rst:59 dcf6bfb5ed4a4d2e92c9a04d5c7e048c +msgid "Set the color of the line." +msgstr "设置线的点列表。" + +#: ../../en/m5ui/line.rst:61 c43f1cd4f51b4b09a94a8397028ef262 +msgid "The color to set." +msgstr "要设置的颜色。" + +#: ../../en/m5ui/line.rst:62 cb3010f6ebe3402bb37b5e6083308463 +msgid "The opacity of the color." +msgstr "线宽。" + +#: ../../en/m5ui/line.rst:63 ../../en/m5ui/line.rst:80 +#: 28b83b198e214869bf0bed7c9d05ca82 5db0b21b0be547989ea5bbdf28cadae5 +msgid "The part of the object to apply the style to (e.g., lv.PART.MAIN)." +msgstr "要应用样式的对象部分(例如,lv.PART.MAIN)。" + +#: ../../en/m5ui/line.rst:67 b6a2e6fdbf494e5e9999f9ec8d01f0d1 +msgid "|set_line_color.png|" +msgstr "" + +#: ../../en/refs/m5ui.line.ref:6 474e432165164c0caaae76ff4dfd7b38 +msgid "set_line_color.png" +msgstr "" + +#: ../../en/m5ui/line.rst:77 022a8b78b59746c396b63afaa4eaf9c5 +msgid "Set the width of the line." +msgstr "设置线的宽度。" + +#: ../../en/m5ui/line.rst:79 f754a6e1b8ea4759a0e4a2e56025d564 +msgid "The width to set." +msgstr "线的宽度。" + +#: ../../en/m5ui/line.rst:84 89a6e4b9e1e4420680c2223425c0d0af +msgid "|set_style_line_width.png|" +msgstr "" + +#: ../../en/m5ui/line.rst:94 32a91100ae454fca8aae607109b4e132 +msgid "" +"Set a flag on the object. If ``value`` is True, the flag is added; if " +"False, the flag is removed." +msgstr "设置对象的标志。如果 ``value`` 为 True,则添加标志;如果为 False,则移除标志。" + +#: ../../en/m5ui/line.rst:96 c22d0ad3809448f2827dcc57b2bd853e +msgid "The flag to set." +msgstr "要设置的标志。" + +#: ../../en/m5ui/line.rst:97 6b56e8c1c53b44b4a83a044f527e20bd +msgid "If True, the flag is added; if False, the flag is removed." +msgstr "如果为 True,则添加标志;如果为 False,则移除标志。" + +#: ../../en/m5ui/line.rst 3a04c4e3a24e4bdd88a79dd6e19365b1 +msgid "Returns" +msgstr "" + +#: ../../en/m5ui/line.rst:102 c1640f47c9b542b088e2509ecc0ce4e3 +msgid "|set_flag.png|" +msgstr "" + +#: ../../en/m5ui/line.rst:112 ae4535fbbfd94cc9bf9030d609361c48 +msgid "Set the position of the line." msgstr "设置线的位置。" -#: ../../en/m5ui/line.rst:61 ../../en/m5ui/line.rst:78 -#: 5ece9e038b5043f0b3a7399735cdd65d f03cbc9b871f496f958cf37e1183b670 -msgid "The x-coordinate of the switch." +#: ../../en/m5ui/line.rst:114 ../../en/m5ui/line.rst:131 +#: 1f36aa56d8ef4a989836b8d606980eaa 33c34678190c4a2eba99c60ee51529de +msgid "The x-coordinate of the line." msgstr "线的 x 坐标。" -#: ../../en/m5ui/line.rst:62 ../../en/m5ui/line.rst:94 -#: 07677161ec2e4df498b4648b50705fc7 adb67a83a2ab4f8b8f2b5fd59bcdf997 -msgid "The y-coordinate of the switch." +#: ../../en/m5ui/line.rst:115 ../../en/m5ui/line.rst:147 +#: 5255f3ccf4224380890e2d828b799c24 90e040c1f89c4f9197167391cf1ec795 +msgid "The y-coordinate of the line." msgstr "线的 y 坐标。" -#: ../../en/m5ui/line.rst:66 55d7901faf8443efa5f7c4bbb30760dc +#: ../../en/m5ui/line.rst:119 8bbb810c476c4e978261f6bb05923aac msgid "|set_pos.png|" msgstr "" -#: ../../en/refs/m5ui.line.ref:2 3df262e766934c0cb46466bf356c0360 +#: ../../en/refs/m5ui.line.ref:2 030ba4c5dad84b97b7fe0a9a30205824 msgid "set_pos.png" msgstr "" -#: ../../en/m5ui/line.rst:76 1d978d84c9354e7b912edf583dac8449 -msgid "Set the x-coordinate of the switch." +#: ../../en/m5ui/line.rst:129 efd2db99fc1643f3861c8fe09ddc3aad +msgid "Set the x-coordinate of the line." msgstr "设置线的 x 坐标。" -#: ../../en/m5ui/line.rst:82 e2412c8fb4424cd3a2a7fe943f9dcf5d +#: ../../en/m5ui/line.rst:135 4dc6b0c507ce4b6daf5048220cb6249a msgid "|set_x.png|" msgstr "" -#: ../../en/refs/m5ui.line.ref:4 d90a3a6380e5431d9da0a8f53da997b9 +#: ../../en/refs/m5ui.line.ref:4 bb3dd529d52e4b768e81115eff4c2992 msgid "set_x.png" msgstr "" -#: ../../en/m5ui/line.rst:92 4c4b0f36863849908e62f817ddc06580 -msgid "Set the y-coordinate of the switch." +#: ../../en/m5ui/line.rst:145 202cab62cae24c3a93370069d5d83dd0 +msgid "Set the y-coordinate of the line." msgstr "设置线的 y 坐标。" -#: ../../en/m5ui/line.rst:98 928fd9955e8643b997f14fa16c980615 +#: ../../en/m5ui/line.rst:151 ad966691766b45989deec4b5ff0a7ed5 msgid "|set_y.png|" msgstr "" -#: ../../en/refs/m5ui.line.ref:5 92892a022bb54e919bbf1670edfd2d78 +#: ../../en/refs/m5ui.line.ref:5 9109209c04f649539a3fa9a83a3000cf msgid "set_y.png" msgstr "" -#: ../../en/m5ui/line.rst:109 6f85e913c7434d6d9453a87596cedc21 -msgid "Align the switch to another object." +#: ../../en/m5ui/line.rst:162 a8e258fc82fd43ae85bbfff2d357f98f +msgid "Align the line to another object." msgstr "将线对齐到另一个对象。" -#: ../../en/m5ui/line.rst:111 279d42ce36f0435f99c4a2603782e803 +#: ../../en/m5ui/line.rst:164 49f9e600ab7c4dea881c0327ba731484 msgid "The object to align to." msgstr "要对齐到的对象。" -#: ../../en/m5ui/line.rst:112 94e2c0d394dd4365a3a7a78fbecde0fe +#: ../../en/m5ui/line.rst:165 fd2655e2fee64b2cbf17d1b13e91abc0 msgid "The alignment type." msgstr "对齐类型。" -#: ../../en/m5ui/line.rst:113 8a98f6d71a1a4c169bddab971125c416 +#: ../../en/m5ui/line.rst:166 0a6e84da9e84451cb4bb7ad966291eaa msgid "The x-offset from the aligned object." msgstr "相对于对齐对象的 x 偏移量。" -#: ../../en/m5ui/line.rst:114 4e113ffa4f9f4db8973ca145e8f95e8f +#: ../../en/m5ui/line.rst:167 2118acbe72514bf2b3363d022a6049fc msgid "The y-offset from the aligned object." msgstr "相对于对齐对象的 y 偏移量。" -#: ../../en/m5ui/line.rst:118 4459151a9b5741f2884bafe3c6db590e +#: ../../en/m5ui/line.rst:171 6835e87b9d6f47c38ada12fc44c0e362 msgid "|align_to.png|" msgstr "" -#: ../../en/refs/m5ui.line.ref:1 4b5144583e7945b5a0bb1e9a86073396 +#: ../../en/refs/m5ui.line.ref:1 41b8e027d8124a1cbce07eb4abf4fabd msgid "align_to.png" msgstr "" -#: 2b04578ad9cf42e0896df31dc289fb3a m5ui.line.M5Line.set_line_color:1 of -msgid "Set the color and opacity of the line." -msgstr "设置线的颜色和透明度。" - -#: a5256fe5bd35485f85b393462604d055 m5ui.line.M5Line.set_line_color:4 of -msgid "The opacity level (0-255)." -msgstr "透明度级别(0-255)。" - -#: fcfc77041bc040fdbfb19cca69c2bea2 m5ui.line.M5Line.set_line_color:8 of -msgid "|set_line_color.png|" -msgstr "" - -#: ../../en/refs/m5ui.line.ref:6 d8ca570fe2d94623bd9132adecfca6c1 -msgid "set_line_color.png" -msgstr "" - -#: 9c33f7b983c3410e8a296322d7cf190f m5ui.line.M5Line.set_points:1 of +#: aa415037def5448fb9b56b61bf7e1e32 m5ui.line.M5Line.set_points:1 of msgid "Set the points of the line." msgstr "设置线的点列表。" -#: 6c551b61882446159b3e5514222991a0 m5ui.line.M5Line.set_points:7 of +#: a115b18b6eef473bb2415c488cfdd74e m5ui.line.M5Line.set_points:7 of msgid "|set_points.png|" msgstr "" -#: ../../en/refs/m5ui.line.ref:7 23a61fac549c465886e2aa463e506c9f +#: ../../en/refs/m5ui.line.ref:7 ae1f05f2ca3948298302ac68ca471717 msgid "set_points.png" msgstr "" -#: dca9c6c2c02b4469be62cfaef7802c51 m5ui.line.M5Line.add_point:1 of +#: c260ac75ae294c088b9c23318d31b3c4 m5ui.line.M5Line.add_point:1 of msgid "Add a point to the line end." msgstr "在线的末端添加一个点。" -#: ef299bd152f645528e35cc91cdeb095e m5ui.line.M5Line.add_point:3 of +#: 8350a49ed5334d6fb6b1eaeb94a03dfe m5ui.line.M5Line.add_point:3 of msgid "The x position of the point." msgstr "点的 x 坐标。" -#: b8fe74156e1f4321a4ea92d0a1a8c62b m5ui.line.M5Line.add_point:4 of +#: 70d3b571a1474ac8802a1d8cedd98981 m5ui.line.M5Line.add_point:4 of msgid "The y position of the point." msgstr "点的 y 坐标。" -#: 4f184d5f0f674393986530dc779bc378 m5ui.line.M5Line.add_point:8 of +#: 997fdc8807c7485180a0dd9a51120c42 m5ui.line.M5Line.add_point:8 of msgid "|add_point.png|" msgstr "" -#: ../../en/refs/m5ui.line.ref:8 fbb99756546947f3ab74b9f2fe3f038a +#: ../../en/refs/m5ui.line.ref:8 ed711d918456450fa6464fa2a0ae7517 msgid "add_point.png" msgstr "" +#~ msgid "Set the position of the switch." +#~ msgstr "设置线的位置。" + +#~ msgid "Set the color and opacity of the line." +#~ msgstr "设置线的颜色和透明度。" + +#~ msgid "The opacity level (0-255)." +#~ msgstr "透明度级别(0-255)。" + diff --git a/m5stack/libs/m5ui/line.py b/m5stack/libs/m5ui/line.py index 6b1eecfb..d93308bd 100644 --- a/m5stack/libs/m5ui/line.py +++ b/m5stack/libs/m5ui/line.py @@ -49,27 +49,6 @@ def __init__( self.set_style_line_width(width, lv.PART.MAIN) self.set_style_line_rounded(rounded, lv.PART.MAIN) - def set_line_color(self, color, opa: int): - """Set the color and opacity of the line. - - :param int color: The color of the line in hexadecimal format. - :param int opa: The opacity level (0-255). - - UiFlow2 Code Block: - - |set_line_color.png| - - MicroPython Code Block: - - .. code-block:: python - - label_0.set_line_color(0x2196F3) - """ - if isinstance(color, int): - color = lv.color_hex(color) - self.set_style_line_color(color, lv.PART.MAIN) - self.set_style_line_opa(opa, lv.PART.MAIN) - def set_points(self, points: list): """Set the points of the line. @@ -83,7 +62,7 @@ def set_points(self, points: list): .. code-block:: python - label_0.set_points([0, 0, 100, 100, 200, 50]) + line_0.set_points([0, 0, 100, 100, 200, 50]) """ self.lv_points = [] for i in range(0, len(points), 2): @@ -104,7 +83,7 @@ def add_point(self, x, y): .. code-block:: python - label_0.add_point(100, 100) + line_0.add_point(100, 100) """ self.lv_points.append({"x": x, "y": y}) super().set_points(self.lv_points, len(self.lv_points)) From a9389f3069f8174b72c8d01302250aef73430eac Mon Sep 17 00:00:00 2001 From: tinyu Date: Fri, 25 Jul 2025 12:35:18 +0800 Subject: [PATCH 169/322] cmodules: Compatible with the new TWAI configuration method. Signed-off-by: tinyu --- m5stack/cmodules/m5can/modcan.c | 296 +++++++++++++++++++++++++++----- m5stack/libs/hardware/can.py | 52 +++--- m5stack/libs/module/pwrcan.py | 56 +++--- m5stack/libs/unit/can.py | 71 ++++---- 4 files changed, 341 insertions(+), 134 deletions(-) diff --git a/m5stack/cmodules/m5can/modcan.c b/m5stack/cmodules/m5can/modcan.c index 585f1076..0ad4ec3a 100644 --- a/m5stack/cmodules/m5can/modcan.c +++ b/m5stack/cmodules/m5can/modcan.c @@ -16,8 +16,10 @@ #include "py/mperrno.h" #include "py/mphal.h" #include "driver/twai.h" +#include "esp_clk_tree.h" +#include "esp_private/esp_clk.h" -#define DEBUG 0 +#define DEBUG 1 #if DEBUG #define DEBUG_printf(...) mp_printf(&mp_plat_print, __VA_ARGS__) #else @@ -25,10 +27,12 @@ #endif // Default timings; 125Kbps -#define CAN_DEFAULT_PRESCALER (32) -#define CAN_DEFAULT_SJW (3) -#define CAN_DEFAULT_BS1 (15) -#define CAN_DEFAULT_BS2 (4) +#define CAN_DEFAULT_CLK_SRC TWAI_CLK_SRC_DEFAULT +#define CAN_DEFAULT_QUANTA_RESOLUTION_HZ (2500000) +#define CAN_DEFAULT_BRP (0) +#define CAN_DEFAULT_TSEG1 (15) +#define CAN_DEFAULT_TSEG2 (4) +#define CAN_DEFAULT_SJW (3) #define CAN_MAXIMUM_NBRP (512) #define CAN_MAXIMUM_NBS1 (256) @@ -58,6 +62,172 @@ #define CAN_STATE_RECOVERING 5 #define CAN_STATE_RUNNING 6 +typedef struct { + int xtal; // MHz + int bitrate; // kbit + twai_timing_config_t config; +} twai_timing_entry_t; + +static const twai_timing_entry_t timing_table[] = { + // XTAL = 32 MHz + {32, + 25, + {.clk_src = CAN_DEFAULT_CLK_SRC, + .quanta_resolution_hz = 400000, + .brp = 0, + .tseg_1 = 11, + .tseg_2 = 4, + .sjw = 3, + .triple_sampling = false}}, + {32, + 50, + {.clk_src = CAN_DEFAULT_CLK_SRC, + .quanta_resolution_hz = 1000000, + .brp = 0, + .tseg_1 = 15, + .tseg_2 = 4, + .sjw = 3, + .triple_sampling = false}}, + {32, + 100, + {.clk_src = CAN_DEFAULT_CLK_SRC, + .quanta_resolution_hz = 2000000, + .brp = 0, + .tseg_1 = 15, + .tseg_2 = 4, + .sjw = 3, + .triple_sampling = false}}, + {32, + 125, + {.clk_src = CAN_DEFAULT_CLK_SRC, + .quanta_resolution_hz = 4000000, + .brp = 0, + .tseg_1 = 23, + .tseg_2 = 8, + .sjw = 3, + .triple_sampling = false}}, + {32, + 250, + {.clk_src = CAN_DEFAULT_CLK_SRC, + .quanta_resolution_hz = 4000000, + .brp = 0, + .tseg_1 = 11, + .tseg_2 = 4, + .sjw = 3, + .triple_sampling = false}}, + {32, + 500, + {.clk_src = CAN_DEFAULT_CLK_SRC, + .quanta_resolution_hz = 8000000, + .brp = 0, + .tseg_1 = 11, + .tseg_2 = 4, + .sjw = 3, + .triple_sampling = false}}, + {32, + 800, + {.clk_src = CAN_DEFAULT_CLK_SRC, + .quanta_resolution_hz = 16000000, + .brp = 0, + .tseg_1 = 15, + .tseg_2 = 4, + .sjw = 3, + .triple_sampling = false}}, + {32, + 1000, + {.clk_src = CAN_DEFAULT_CLK_SRC, + .quanta_resolution_hz = 16000000, + .brp = 0, + .tseg_1 = 11, + .tseg_2 = 4, + .sjw = 3, + .triple_sampling = false}}, + + // XTAL = 40 MHz + {40, + 25, + {.clk_src = CAN_DEFAULT_CLK_SRC, + .quanta_resolution_hz = 625000, + .brp = 0, + .tseg_1 = 16, + .tseg_2 = 8, + .sjw = 3, + .triple_sampling = false}}, + {40, + 50, + {.clk_src = CAN_DEFAULT_CLK_SRC, + .quanta_resolution_hz = 1000000, + .brp = 0, + .tseg_1 = 15, + .tseg_2 = 4, + .sjw = 3, + .triple_sampling = false}}, + {40, + 100, + {.clk_src = CAN_DEFAULT_CLK_SRC, + .quanta_resolution_hz = 2000000, + .brp = 0, + .tseg_1 = 15, + .tseg_2 = 4, + .sjw = 3, + .triple_sampling = false}}, + {40, + 125, + {.clk_src = CAN_DEFAULT_CLK_SRC, + .quanta_resolution_hz = 2500000, + .brp = 0, + .tseg_1 = 15, + .tseg_2 = 4, + .sjw = 3, + .triple_sampling = false}}, + {40, + 250, + {.clk_src = CAN_DEFAULT_CLK_SRC, + .quanta_resolution_hz = 5000000, + .brp = 0, + .tseg_1 = 15, + .tseg_2 = 4, + .sjw = 3, + .triple_sampling = false}}, + {40, + 500, + {.clk_src = CAN_DEFAULT_CLK_SRC, + .quanta_resolution_hz = 10000000, + .brp = 0, + .tseg_1 = 15, + .tseg_2 = 4, + .sjw = 3, + .triple_sampling = false}}, + {40, + 800, + {.clk_src = CAN_DEFAULT_CLK_SRC, + .quanta_resolution_hz = 20000000, + .brp = 0, + .tseg_1 = 16, + .tseg_2 = 8, + .sjw = 3, + .triple_sampling = false}}, + {40, + 1000, + {.clk_src = CAN_DEFAULT_CLK_SRC, + .quanta_resolution_hz = 20000000, + .brp = 0, + .tseg_1 = 15, + .tseg_2 = 4, + .sjw = 3, + .triple_sampling = false}}, +}; + +static int find_timing_config(int xtal, int bitrate, twai_timing_config_t *out) { + for (size_t i = 0; i < sizeof(timing_table) / sizeof(timing_table[0]); ++i) { + if (timing_table[i].bitrate == bitrate && timing_table[i].xtal == xtal) { + *out = timing_table[i].config; + return 0; + } + } + return -1; +} + typedef struct _pyb_can_obj_t { mp_obj_base_t base; mp_uint_t can_id; @@ -96,36 +266,74 @@ static void pyb_can_print(const mp_print_t *print, mp_obj_t self_in, mp_print_ki // init(mode, prescaler=100, *, sjw=1, bs1=6, bs2=8) static mp_obj_t pyb_can_init_helper(pyb_can_obj_t *self, size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { - enum { ARG_mode, ARG_tx, ARG_rx, ARG_prescaler, ARG_sjw, ARG_bs1, ARG_bs2, ARG_triple_sampling }; + enum { + ARG_mode, + ARG_tx, + ARG_rx, + ARG_quanta_resolution_hz, + ARG_brp, + ARG_tseg_1, + ARG_tseg_2, + ARG_sjw, + ARG_triple_sampling, + ARG_baudrate, + }; static const mp_arg_t allowed_args[] = { {MP_QSTR_mode, MP_ARG_REQUIRED | MP_ARG_INT, {.u_int = CAN_MODE_NORMAL}}, {MP_QSTR_tx, MP_ARG_REQUIRED | MP_ARG_INT, {.u_int = 2}}, {MP_QSTR_rx, MP_ARG_REQUIRED | MP_ARG_INT, {.u_int = 1}}, - {MP_QSTR_prescaler, MP_ARG_INT, {.u_int = CAN_DEFAULT_PRESCALER}}, + {MP_QSTR_quanta_resolution_hz, MP_ARG_INT, {.u_int = CAN_DEFAULT_QUANTA_RESOLUTION_HZ}}, + {MP_QSTR_brp, MP_ARG_INT, {.u_int = CAN_DEFAULT_BRP}}, + {MP_QSTR_tseg_1, MP_ARG_INT, {.u_int = CAN_DEFAULT_TSEG1}}, + {MP_QSTR_tseg_2, MP_ARG_INT, {.u_int = CAN_DEFAULT_TSEG2}}, {MP_QSTR_sjw, MP_ARG_INT, {.u_int = CAN_DEFAULT_SJW}}, - {MP_QSTR_bs1, MP_ARG_INT, {.u_int = CAN_DEFAULT_BS1}}, - {MP_QSTR_bs2, MP_ARG_INT, {.u_int = CAN_DEFAULT_BS2}}, {MP_QSTR_triple_sampling, MP_ARG_BOOL, {.u_bool = false}}, + {MP_QSTR_baudrate, MP_ARG_INT, {.u_int = 0}}, }; // parse args mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); - self->t_config.brp = args[ARG_prescaler].u_int; - self->t_config.tseg_1 = args[ARG_bs1].u_int; - self->t_config.tseg_2 = args[ARG_bs2].u_int; - self->t_config.sjw = args[ARG_sjw].u_int; - self->t_config.triple_sampling = args[ARG_triple_sampling].u_bool; - self->g_config.tx_io = args[ARG_tx].u_int; self->g_config.rx_io = args[ARG_rx].u_int; self->g_config.mode = args[ARG_mode].u_int; - DEBUG_printf(&mp_plat_print, "prescaler=%u, sjw=%u, bs1=%u, bs2=%u, triple_sampling=%u\n", self->t_config.brp, - self->t_config.tseg_1, self->t_config.tseg_2, self->t_config.sjw, self->t_config.triple_sampling); - DEBUG_printf(&mp_plat_print, "mode=%u, tx=%u, rx=%u\n", self->g_config.mode, self->g_config.tx_io, - self->g_config.rx_io); + if (args[ARG_baudrate].u_int != 0) { + int xtal = esp_clk_xtal_freq() / 1000000; + DEBUG_printf("XTAL frequency: %d MHz\n", xtal); + DEBUG_printf("find_timing_config: xtal=%d MHz, baudrate=%d kbps\n", xtal, args[ARG_baudrate].u_int); + if (find_timing_config(xtal, args[ARG_baudrate].u_int, &self->t_config) != 0) { + DEBUG_printf("Timing config not found for baudrate %d kbps\n", args[ARG_baudrate].u_int); + mp_raise_msg_varg(&mp_type_ValueError, + MP_ERROR_TEXT("Unsupported baudrate %d kbps for %d MHz crystal. Supported: 25, 50, 100, " + "125, 250, 500, 800, 1000"), + args[ARG_baudrate].u_int, xtal); + } + } else { + self->t_config.clk_src = CAN_DEFAULT_CLK_SRC; + if (args[ARG_quanta_resolution_hz].u_int == 0 && args[ARG_brp].u_int != 0) { + uint32_t xtal_apb_freq_hz; + + esp_clk_tree_src_get_freq_hz(CAN_DEFAULT_CLK_SRC, ESP_CLK_TREE_SRC_FREQ_PRECISION_EXACT, &xtal_apb_freq_hz); + DEBUG_printf("XTAL APB frequency: %d MHz\n", xtal_apb_freq_hz / 1000000); + args[ARG_quanta_resolution_hz].u_int = xtal_apb_freq_hz / args[ARG_brp].u_int; + DEBUG_printf("Using quanta_resolution_hz=%u based on brp=%u\n", args[ARG_quanta_resolution_hz].u_int, + args[ARG_brp].u_int); + args[ARG_brp].u_int = 0; + } + self->t_config.quanta_resolution_hz = args[ARG_quanta_resolution_hz].u_int; + self->t_config.brp = args[ARG_brp].u_int; + self->t_config.tseg_1 = args[ARG_tseg_1].u_int; + self->t_config.tseg_2 = args[ARG_tseg_2].u_int; + self->t_config.sjw = args[ARG_sjw].u_int; + self->t_config.triple_sampling = args[ARG_triple_sampling].u_bool; + } + + DEBUG_printf("tx=%u, rx=%u, mode=%u\n", self->g_config.tx_io, self->g_config.rx_io, self->g_config.mode); + DEBUG_printf("quanta_resolution_hz=%u brp=%u, tseg_1=%u, tseg_2=%u, sjw=%u, triple_sampling=%u\n", + self->t_config.quanta_resolution_hz, self->t_config.brp, self->t_config.tseg_1, self->t_config.tseg_2, + self->t_config.sjw, self->t_config.triple_sampling); check_esp_err(twai_driver_install(&self->g_config, &self->t_config, &self->f_config)); check_esp_err(twai_start()); @@ -157,9 +365,11 @@ static mp_obj_t pyb_can_make_new(const mp_obj_type_t *type, size_t n_args, size_ } { - self->t_config.brp = CAN_DEFAULT_PRESCALER; - self->t_config.tseg_1 = CAN_DEFAULT_BS1; - self->t_config.tseg_2 = CAN_DEFAULT_BS2; + self->t_config.clk_src = CAN_DEFAULT_CLK_SRC; + self->t_config.quanta_resolution_hz = CAN_DEFAULT_QUANTA_RESOLUTION_HZ; + self->t_config.brp = CAN_DEFAULT_BRP; + self->t_config.tseg_1 = CAN_DEFAULT_TSEG1; + self->t_config.tseg_2 = CAN_DEFAULT_TSEG2; self->t_config.sjw = CAN_DEFAULT_SJW; self->t_config.triple_sampling = false; }; @@ -187,7 +397,7 @@ static mp_obj_t pyb_can_make_new(const mp_obj_type_t *type, size_t n_args, size_ twai_stop(); // check_esp_err(twai_driver_uninstall()); int err = twai_driver_uninstall(); - mp_printf(&mp_plat_print, "twai_driver_uninstall() returned %d\n", err); + DEBUG_printf("twai_driver_uninstall() returned %d\n", (int)err); vTaskDelay(10 / portTICK_PERIOD_MS); self->is_enabled = false; @@ -340,24 +550,24 @@ static mp_obj_t pyb_can_send(size_t n_args, const mp_obj_t *pos_args, mp_map_t * tx_msg.rtr = args[ARG_rtr].u_bool; tx_msg.extd = args[ARG_extframe].u_bool; #if DEBUG - DEBUG_printf(&mp_plat_print, "Income Data:"); + DEBUG_printf("Send Data:"); for (size_t i = 0; i < bufinfo.len; i++) { - DEBUG_printf(&mp_plat_print, "0x%02X ", ((uint8_t *)bufinfo.buf)[i]); + DEBUG_printf("0x%02X ", ((uint8_t *)bufinfo.buf)[i]); } - DEBUG_printf(&mp_plat_print, "\n - extd: %d\n", tx_msg.extd); - DEBUG_printf(&mp_plat_print, " - rtr: %d\n", tx_msg.rtr); - DEBUG_printf(&mp_plat_print, " - ss: %d\n", tx_msg.ss); - DEBUG_printf(&mp_plat_print, " - self: %d\n", tx_msg.self); - DEBUG_printf(&mp_plat_print, " - dlc_non_comp: %d\n", tx_msg.dlc_non_comp); - DEBUG_printf(&mp_plat_print, "Complete tx_msg:\n"); - DEBUG_printf(&mp_plat_print, " flags: 0x%08X\n", tx_msg.flags); - DEBUG_printf(&mp_plat_print, " identifier: 0x%08X\n", tx_msg.identifier); - DEBUG_printf(&mp_plat_print, " data_length_code: %d\n", tx_msg.data_length_code); - DEBUG_printf(&mp_plat_print, " data: "); + DEBUG_printf("\n - extd: %d\n", tx_msg.extd); + DEBUG_printf(" - rtr: %d\n", tx_msg.rtr); + DEBUG_printf(" - ss: %d\n", tx_msg.ss); + DEBUG_printf(" - self: %d\n", tx_msg.self); + DEBUG_printf(" - dlc_non_comp: %d\n", tx_msg.dlc_non_comp); + DEBUG_printf("Complete tx_msg:\n"); + DEBUG_printf(" flags: 0x%08X\n", tx_msg.flags); + DEBUG_printf(" identifier: 0x%08X\n", tx_msg.identifier); + DEBUG_printf(" data_length_code: %d\n", tx_msg.data_length_code); + DEBUG_printf(" data: "); for (int i = 0; i < bufinfo.len; i++) { - DEBUG_printf(&mp_plat_print, "0x%02X ", tx_msg.data[i]); + DEBUG_printf("0x%02X ", tx_msg.data[i]); } - DEBUG_printf(&mp_plat_print, "\n\n"); + DEBUG_printf("\n\n"); #endif check_esp_err(twai_transmit(&tx_msg, args[ARG_timeout].u_int)); return mp_const_none; @@ -382,12 +592,12 @@ static mp_obj_t pyb_can_recv(size_t n_args, const mp_obj_t *pos_args, mp_map_t * twai_message_t rx_msg; esp_err_t ret = twai_receive(&rx_msg, args[ARG_timeout].u_int); #if DEBUG - DEBUG_printf(&mp_plat_print, "Received identifier: 0x%08X\n", rx_msg.identifier); - DEBUG_printf(&mp_plat_print, "received data: "); + DEBUG_printf("Received identifier: 0x%08X\n", rx_msg.identifier); + DEBUG_printf("received data: "); for (int i = 0; i < rx_msg.data_length_code; i++) { - DEBUG_printf(&mp_plat_print, "0x%02X ", rx_msg.data[i]); + DEBUG_printf("0x%02X ", rx_msg.data[i]); } - DEBUG_printf(&mp_plat_print, "\r\n"); + DEBUG_printf("\r\n"); #endif if (ret != ESP_OK || rx_msg.data_length_code > 8) { return mp_const_none; @@ -540,8 +750,8 @@ MP_DEFINE_CONST_OBJ_TYPE(pyb_can_type, MP_QSTR_CAN, MP_TYPE_FLAG_NONE, make_new, MP_REGISTER_ROOT_POINTER(struct _pyb_can_obj_t *pyb_can_obj_all[1]); static const mp_rom_map_elem_t m5can_module_globals_table[] = { - { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_m5can) }, - { MP_ROM_QSTR(MP_QSTR_CAN), MP_ROM_PTR(&pyb_can_type) }, + {MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_m5can)}, + {MP_ROM_QSTR(MP_QSTR_CAN), MP_ROM_PTR(&pyb_can_type)}, }; static MP_DEFINE_CONST_DICT(m5can_module_globals, m5can_module_globals_table); diff --git a/m5stack/libs/hardware/can.py b/m5stack/libs/hardware/can.py index 795dac8a..ba3d511e 100644 --- a/m5stack/libs/hardware/can.py +++ b/m5stack/libs/hardware/can.py @@ -10,39 +10,33 @@ class CAN(m5can.CAN): - _timing_table = { - # prescaler, sjw, bs1, bs2, triple_sampling - 25000: (128, 3, 16, 8, False), - 50000: (80, 3, 15, 4, False), - 100000: (40, 3, 15, 4, False), - 125000: (32, 3, 15, 4, False), - 250000: (16, 3, 15, 4, False), - 500000: (8, 3, 15, 4, False), - 800000: (4, 3, 16, 8, False), - 1000000: (4, 3, 15, 4, False), - } - def __init__( self, - id: Literal[0, 1], + id: Literal[0, 1] = 0, + port: list | tuple = None, mode: int = m5can.CAN.NORMAL, - tx: int = 0, - rx: int = 0, - *args, + prescaler: int = 0, + sjw: int = 0, + bs1: int = 0, + bs2: int = 0, + triple_sampling: bool = False, + quantum_resolution_hz: int = 0, + baudrate: int = 0, + verbose: bool = False, ): - if len(args) == 1: - (prescaler, sjw, bs1, bs2, triple_sampling) = self._timing_table.get(args[0]) - elif len(args) == 5: - (prescaler, sjw, bs1, bs2, triple_sampling) = args - + verbose and print( + f"mode={mode}, tx={port[1]}, rx={port[0]}, quantum_resolution_hz={quantum_resolution_hz}, brp={prescaler}, sjw={sjw}, tseg_1={bs1}, tseg_2={bs2}, triple_sampling={triple_sampling}, baudrate={baudrate}" + ) super().__init__( - 0, + id, mode, - tx, - rx, - prescaler, - sjw, - bs1, - bs2, - triple_sampling, + port[1], + port[0], + quantum_resolution_hz, + brp=prescaler, + sjw=sjw, + tseg_1=bs1, + tseg_2=bs2, + triple_sampling=triple_sampling, + baudrate=baudrate // 1000, ) diff --git a/m5stack/libs/module/pwrcan.py b/m5stack/libs/module/pwrcan.py index 17ef247a..ef7c0360 100644 --- a/m5stack/libs/module/pwrcan.py +++ b/m5stack/libs/module/pwrcan.py @@ -11,45 +11,35 @@ class PwrCANModule(m5can.CAN): - _timing_table = { - # prescaler, sjw, bs1, bs2, triple_sampling - 25000: (128, 3, 16, 8, False), - 50000: (80, 3, 15, 4, False), - 100000: (40, 3, 15, 4, False), - 125000: (32, 3, 15, 4, False), - 250000: (16, 3, 15, 4, False), - 500000: (8, 3, 15, 4, False), - 800000: (4, 3, 16, 8, False), - 1000000: (4, 3, 15, 4, False), - } - def __init__( self, - id: Literal[0, 1], - tx, - rx, + id: Literal[0, 1] = 0, + port: list | tuple = None, mode: int = m5can.CAN.NORMAL, - *args, - **kwargs, + prescaler: int = 0, + sjw: int = 0, + bs1: int = 0, + bs2: int = 0, + triple_sampling: bool = False, + quantum_resolution_hz: int = 0, + baudrate: int = 0, + verbose: bool = False, ): - print("CAN") - if len(kwargs) == 1: - (prescaler, sjw, bs1, bs2, triple_sampling) = self._timing_table.get( - kwargs.get("baudrate") - ) - elif len(args) == 5: - (prescaler, sjw, bs1, bs2, triple_sampling) = args - + verbose and print( + f"mode={mode}, tx={port[1]}, rx={port[0]}, quantum_resolution_hz={quantum_resolution_hz}, brp={prescaler}, sjw={sjw}, tseg_1={bs1}, tseg_2={bs2}, triple_sampling={triple_sampling}, baudrate={baudrate}" + ) super().__init__( - 0, + id, mode, - tx, - rx, - prescaler, - sjw, - bs1, - bs2, - triple_sampling, + port[1], + port[0], + quantum_resolution_hz, + brp=prescaler, + sjw=sjw, + tseg_1=bs1, + tseg_2=bs2, + triple_sampling=triple_sampling, + baudrate=baudrate // 1000, ) diff --git a/m5stack/libs/unit/can.py b/m5stack/libs/unit/can.py index 4e7308ca..0161a546 100644 --- a/m5stack/libs/unit/can.py +++ b/m5stack/libs/unit/can.py @@ -10,46 +10,59 @@ class CANUnit(m5can.CAN): - _timing_table = { - # prescaler, sjw, bs1, bs2, triple_sampling - 25000: (128, 3, 16, 8, False), - 50000: (80, 3, 15, 4, False), - 100000: (40, 3, 15, 4, False), - 125000: (32, 3, 15, 4, False), - 250000: (16, 3, 15, 4, False), - 500000: (8, 3, 15, 4, False), - 800000: (4, 3, 16, 8, False), - 1000000: (4, 3, 15, 4, False), - } - def __init__( self, - id: Literal[0, 1], + id: Literal[0, 1] = 0, port: list | tuple = None, mode: int = m5can.CAN.NORMAL, - *args, - **kwargs, + prescaler: int = 0, + sjw: int = 0, + bs1: int = 0, + bs2: int = 0, + triple_sampling: bool = False, + quantum_resolution_hz: int = 0, + baudrate: int = 0, + verbose: bool = False, ): - if len(kwargs) == 1: - (prescaler, sjw, bs1, bs2, triple_sampling) = self._timing_table.get( - kwargs.get("baudrate") - ) - elif len(args) == 5: - (prescaler, sjw, bs1, bs2, triple_sampling) = args + verbose and print( + f"mode={mode}, tx={port[1]}, rx={port[0]}, quantum_resolution_hz={quantum_resolution_hz}, brp={prescaler}, sjw={sjw}, tseg_1={bs1}, tseg_2={bs2}, triple_sampling={triple_sampling}, baudrate={baudrate}" + ) super().__init__( - 0, + id, mode, port[1], port[0], - prescaler, - sjw, - bs1, - bs2, - triple_sampling, + quantum_resolution_hz, + brp=prescaler, + sjw=sjw, + tseg_1=bs1, + tseg_2=bs2, + triple_sampling=triple_sampling, + baudrate=baudrate // 1000, ) -# can_0 = CANUnit(0, (33, 32), CANUnit.NORMAL, baudrate=1000000) -# can_0 = CANUnit(0, (33, 32), CANUnit.NORMAL, 4, 3, 15, 4, False) +# can_0 = CANUnit(id=0, port=(33, 32), mode=CANUnit.NORMAL, baudrate=25000) +# can_0 = CANUnit( +# id=0, +# port=(33, 32), +# mode=CANUnit.NORMAL, +# prescaler=128, +# sjw=3, +# bs1=16, +# bs2=8, +# triple_sampling=False, +# ) +# can_0 = CANUnit( +# id=0, +# port=(33, 32), +# mode=CANUnit.NORMAL, +# quantum_resolution_hz=625000, +# sjw=3, +# bs1=16, +# bs2=8, +# triple_sampling=False, +# ) + MiniCANUnit = CANUnit From d61d0cb1f9917db2450ac4c9a71b4a051004f458 Mon Sep 17 00:00:00 2001 From: Forairaaaaa Date: Mon, 14 Jul 2025 11:20:45 +0800 Subject: [PATCH 170/322] /modules/startup/tab5: Change ezdata to websocket client. Signed-off-by: Forairaaaaa --- m5stack/fs/system/tab5/icons/settings.png | Bin 0 -> 221 bytes .../system/tab5/utils/ezdata_guide_bg_1.png | Bin 0 -> 1732 bytes .../system/tab5/utils/ezdata_guide_bg_2.png | Bin 0 -> 2297 bytes .../system/tab5/utils/ezdata_guide_bg_3.png | Bin 0 -> 1981 bytes m5stack/modules/startup/manifest_tab5.py | 6 + .../startup/tab5/launcher/apps/__init__.py | 3 +- .../tab5/launcher/apps/app_app_list.py | 5 +- .../startup/tab5/launcher/apps/app_ezdata.py | 360 +++++++---------- .../tab5/launcher/apps/app_ezdata_settings.py | 97 +++++ .../tab5/launcher/apps/app_i2c_scan.py | 30 +- .../tab5/launcher/apps/app_wifi_scan.py | 8 +- .../startup/tab5/launcher/common/__init__.py | 4 +- .../startup/tab5/launcher/common/debug.py | 12 + .../startup/tab5/launcher/common/ezdata.py | 363 ++++++++++-------- .../startup/tab5/launcher/common/signal.py | 29 ++ .../launcher/common/uwebsockets/__init__.py | 1 + .../launcher/common/uwebsockets/client.py | 57 +++ .../launcher/common/uwebsockets/protocol.py | 243 ++++++++++++ .../tab5/launcher/components/ezdata_dock.py | 141 +++---- .../tab5/launcher/components/status_bar.py | 18 +- .../modules/startup/tab5/launcher/launcher.py | 40 +- 21 files changed, 908 insertions(+), 509 deletions(-) create mode 100644 m5stack/fs/system/tab5/icons/settings.png create mode 100644 m5stack/fs/system/tab5/utils/ezdata_guide_bg_1.png create mode 100644 m5stack/fs/system/tab5/utils/ezdata_guide_bg_2.png create mode 100644 m5stack/fs/system/tab5/utils/ezdata_guide_bg_3.png create mode 100644 m5stack/modules/startup/tab5/launcher/apps/app_ezdata_settings.py create mode 100644 m5stack/modules/startup/tab5/launcher/common/debug.py create mode 100644 m5stack/modules/startup/tab5/launcher/common/signal.py create mode 100644 m5stack/modules/startup/tab5/launcher/common/uwebsockets/__init__.py create mode 100644 m5stack/modules/startup/tab5/launcher/common/uwebsockets/client.py create mode 100644 m5stack/modules/startup/tab5/launcher/common/uwebsockets/protocol.py diff --git a/m5stack/fs/system/tab5/icons/settings.png b/m5stack/fs/system/tab5/icons/settings.png new file mode 100644 index 0000000000000000000000000000000000000000..4ce59aa03ac8ffa549abaeec8286dce3ab6d004d GIT binary patch literal 221 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDB3?!H8JlO)I7>k44ofvPP)Tsw@_yc@GT!C~* zNJwB{U=SD-uJv676k;t2@(X5g*#E!b<-?hufdbi{E{-7;bCMGlm?wxFP+YjC!THdQ z1`SR}aSH~|h8+RTy@CfA4uu}jKiHHdGU0W{!z`s(*B*A(!j8#Y8Dd*_E?i*soS|@t zEoqyf%8dz!goL^*R2>#_bNQ<*4rneHVAEykSr*IVaF;=igJHuP<0_S>8X%`Jc)I$z JtaD0e0s!OJMs)xH literal 0 HcmV?d00001 diff --git a/m5stack/fs/system/tab5/utils/ezdata_guide_bg_1.png b/m5stack/fs/system/tab5/utils/ezdata_guide_bg_1.png new file mode 100644 index 0000000000000000000000000000000000000000..8aec095699482540fb323dc0b6ed4b7223a137e8 GIT binary patch literal 1732 zcmeAS@N?(olHy`uVBq!ia0y~yU}9rnV3g-z28mqw7Xzdii-X*q7;m1`sRwfA2l#}z z0_p$6fmfgY9l!f$<>_A&_x`Nk^rL9i_lzaqpMU(f>EiFEEkAC({5Rv^&)1*-%{}t# zz|B87%f4qX`@ZnluMgk;_3Zlj=JUVC%|EwX`rWtt=gk*?H(&hy@a^BjxBn2d`I(5} z9H0|TOM?7@8T=<4e)i|TVB4FNcT<<*(|T@h4A=L5i7y$`mQ}L^7&9=iPV#hd45^s& z_U_A~TNwgvftA_oZ%(zHEIXt2?BDp?H)Q>dI^TLM`tc%R{^#q`>q zy_*K}$!wZ6J+qKl^Ywrl%+iJC4 zhP%W6c=O^{vR6)hZmvylH^1}o=I7StSE3=-7H1EeIGkuuVRm9ukrXgeIN30P(UYx( z$HPb=#UVkOI`}@0li>sf`j@W%t(x&Fij{lb)yYoluB=@SNeRz^t zKXbYNtu@KMYN;RI{9GzHr}E#Lt4`Mvyx8>Xc5G_eSO0U-O}AOICmdXz?^5tAboW#f zGppw7`D|J*ew(k4)?2!8qvW1i^9aZL`>Su4XrJvl^Wf_YEtX6V^ZRE` z^`3ZeatYV3d%wA7r@OF;-}kMm&nwGh^S`}5?P!~EMa9RydGr6j{QI<>`Mq`ioQFJe zzULkUUOsAKFz4X)@R@UF&9zlJv;NG3!rwR7TuWfvuD^e^zrVn`GYiUNZ!MEN^WdD2 z8)(WJNCPsZ(sg!HCk|Y7 zmrh@qEy!+Ep!?`p5}0fPZgYYMj+KEA6uhNvyT@t7EF?hNcEz@*v!|Ef60wU7T^=GnD>b$Y<-72f|t z-Y>Z1S^m!XtI(^O7j~8_?>SAK{I0%l&$1e2S^3iV8}=4!<{jB3EBB^cJSX@4n|Go5 l$+q9h%C~K|yZ`+^Q){665`Ch|(Y;6_cFk zD4m!gj+)GrcVk3;L>D7&bE=V$84NS`>D({(cR!qazMTDK?X~v$|My<&xA$86lDn&u zvXYh(007D^&JL#lKn?)_FkMCXSIbMOA0%LLr=09z6MeW0NU3!{?&%0g*`FI9mZd#Y zoAm=5S6S;BjJ5Ok#o>3wf+Zue7fV zSEO0Y^};9X%_EYD-?pOa#LQvI#m8&23tM-4H(tEi{J;Cpme|xm2R(e@l&c5eR{Y}K zrcC9cko>8v_K%^wDnBK0k>;21@;VZVNdN$3hl_)qXJX~Tlpx`4zp8DJieGn7x653g zN+juXU(mE{c*m$7y4^6Vy&g3^^C+cgp(O&9QeRfgh^l|Ju<9m1^xUOme1ZHAS7g{& z>Fi4$;+~rcp`%e}8)`pq+@!5p2kE`vNBwCdT_GW$(KHj+iWH2+vCBW{Rx&7STz_t% z24c*EJxRI=6Ktomn7EI`At0ryA<|k-7|*1yDmIP}Y>a)w4zQWKsXuSPqyxqnxFXv2 zDl7-A?gi`-?qom@zRxVP7^a4{?FBA_7&739*kzVEX+yp4Gk&<0l0=`Gn)VB1frQ;V z&JBS9Qysk9$!RjIkW8p%nI*0qq9jSa{c?d&Y;Be(on;+f^K1+YlfEnsYqnVr3Oc+y#;;|J8 zY*F>ug#PMe$BIYWo9Y}5o{-&JC~eQv&kP8QIeC-n^A-hkoP`J^?9SLR&d+es?Cz$ z9#=3r$WLGKNGHnbA5bNVuk=gW(NWVOHR@_RU%YQWCKof$EsB7!AsKd1&o2ElVOeiF9%v#8-Int!U1Og<%3b zLxKEK=Y&}K2P|o(+FDD;_?jpCs(pd^k!W{sm_XV0?(pf(5hX&wYkf(e$ItgOBb7`U zV)gERrMBmgX*$~7A12<@?96;pbh+#y*e3beQS?kBc17c{89$&CH6h{658jUUW>@u{ zeJ)nRq(*MTj!-l^R#MoB(OQi8hTqHp%w!AR)#=>NG(_7}Ukd`CSq??oI+Xf7Eb&8KN^j`k+E2bcLG zYSmxb;Vy-^U-KyzG;BXEYRsPltE!qt=K70VlhbF(dD0VsTOUa7_+3$4d|R2|OAvLViS-+Ql73 zgMAp|I`r{gj0zcWIYRB};|6!78=&QjqY$MnyD!c$RC_U*4)}_wN<-|nEKtrF5AUgk zFP>C@T+Sehcq?7cl;xl~3Gqqk{EUT@nIeV-j+tn>KHLg4%Qs|!hxWVAEeIRS>x;bxHKtmiF`(9O7vKt<>`q4}V3S zL379Ddsq(BgI_U3MDR>EsmTIE-8Wgv>1ASz?SM-meE;3$tp`f%KqwGX>bg#iBw8w) z7|T_Fs2+l^wKVnM&NXO)%_}{0XDJb}TL$}S@qsc#UV?7>-g9w$3JSE-jF=ibCD!os z$p5AVOua>2w{Ym889`1F=tO$5&+;{Mi0xO| z%5S=Roikb0_hL4@St@i8Xn%mP$>=qD!aGSk1m%FKGd@e3hknlV-W(V`Jq! z_hdZwqL3}rjm3e1ygd!@LRBqhsuxAYAV*K#JZz&|^y4<#1TO=31#heMho%27ewF{z zZ_bza6N`Y9EuqFo71E>YTM@svu9*g>OqQ!iqC19XPo6Fh$?)9?i=_UD$k;k-tq|p3 z>5A`_HtE10^YE*WR`)huxG}iZOqWkc4?T8$jLGf~dHk+2u77BxD@oM(p3hA-*gHAF sH-9toUbJBZ_1S~28?19-s}nNhaYr)(Ku+2Y{RRLRM^}em>`B@G2H5X2fB*mh literal 0 HcmV?d00001 diff --git a/m5stack/fs/system/tab5/utils/ezdata_guide_bg_3.png b/m5stack/fs/system/tab5/utils/ezdata_guide_bg_3.png new file mode 100644 index 0000000000000000000000000000000000000000..3d4b215d602132936baf73951b5558908941ebc8 GIT binary patch literal 1981 zcmb7FeK^x=AODSxSm?+|UE!pjOma$MWj5Mq-p;lV(Y#bLMW<_ui5RskBbz#UBr~Jw zP=~zK$dJQAC&a-M^HTFNAu=zUZRT0m`TsoUx&QdS-QUmm`rO|?K6g4Ei_rt`1Oot| z=i%<^0|1+#>Ns0lQ*9lbu7{}|=&BC}1sd!)Kv5@Lytkj5+Hd^hQ{AJ;s$YwIEFGL) zf7~wr^kscUqG}sami{5nZdjvK$zsZ+Ekb45OPP34Rn?=oStA?ztjcX(n_W_|TIH_? zmC+^As1hmXjr{AXD)ZIa+>$D_dab5c@%I!a_rtW+L}Ax_u)Y9}*)pZvFi4vVpU>fz z{5`OXMF2|~Cd-iz?(#fv0HAft!}YjdeEye_zyP7Aamq>Chn1T9bbNnq&v$W`HXu|R z?12v^;n5u!<2ydb5}PJcxzEr8lI=N!dvsCrko?Wm01)>4MO|g1|GUA_!JiO2-XE-o z3mNcA9y?)gA!Sus0w*yOb``Sq-(rOL_fu~ZDmqi?Xz31hv(QrCOP z=J3)dfc&!90kVn%wtO)b1kLP7X*^>+;L8A(M-nQt6E+ntIM6%$_5ew|z}irVcC=Bc zB_PK^QKWq z+z~Q}y4;^uL`Ys%X{4zq%%BZQieTSbJ`~|yxks@GB~@KEPP2fzb#dYoV-n($NeVAQ zKATm*Pg;E##yy}fcm!Jr36K}#q8MECqDs=@y@Vt*E}T1j+TbMAm~|<9C!N9G-_1ZK z%|$q#1~mmkk(AP4&!n~Cek!wW%mxzLWK%mWS&t&xp$3wx@ygTjv2jP0JtMy`D>0m3 z>tX`Qwo8$GH@kJR`CT+A*7VxBqx{C17@zh&Kj`SXL2_mp7g-&-lg@_eB|m(`W_5uB z_oElfFYN36chOmBdEPbAOGuYhnEwv?^k|k_uS#DJZBPN9Cy?Pb;lJt%3>~xM84=ZZ z*ZkGAy<^qqcu^C6CBwqSLqEeFatp?`cg7i?9`=xL8t{}Y>s~u zV}`O-QEOydhRmdydDNn0=ZKtxtZv=Fx~+ovNm8FgX55B&qQ#Nb`dOx-rAe=A6nLkL zlRX<17~+DaV&ww?ii~x(51&9n#u=E5aiN(sUxeqg^_s`c1Ng(O!Qw*~w$bwg%=A#C zx@*uM;dw??M|9k}tQlII+$3ygU+?+l+nvhZ^D>U2a<}sY8 z^bw|8J;|K*gh>OM=rIz~X3sJy;I~dN)9dl!4eDM}kT5;;Ma4yAQhLX3Lo&yNawKTi z$ZD#dxlq3_d+^VqDW*B_Y~harRIP9i+Pk5{;g znratp(bcbmqI*OiU$!Bt4?*m0Ovc{&SK3ssG;MxXM#E3kOX7o|TCqeU)2({Pu4}tB zS~CVXxzW6`I&9C{2PUJ%^2U#H$@NQ#cSYsKZT(}+S0_MR1wNcFiQM?Dt>sDx$@9~h zta8muP|O4sF~$9|{K!Vb0u0Dk{|}s3 zhGt@=Byy4Zw8Q`D_?cp7>&E4;RO{TsIs(Z-)y4XApItJD5vn}-CsKd1b@w}}70t-} z-jolH@;JmXPj^`JRPQ1laCDQ`WKU)y`I5e&;T2d$8amW*n=Wjt}`EHG*S8fX%hW^8VED zIHD`)|EU-|Oo)ZsFkM^ly2%b!g3fL%@w;sochZ|r@#VE~NYK+#5>#ZJSAPEYvLFk{ zt%@<@aM7a$x3d>)4C&2cIU+A~uKH&G3qPpVEyfc0&fOa^l+Dko{)M(R;WyXQX z%a9FsgvNXFtbkpL0pvgbCYxoK64h$VgVHHnGmU8;ONdu0oWCh_G)3V65m0;?pyk03 z+tsGNtH63J-ka}<(0LzFLEpT&ln9c4F3hbWTEY$yDHZYIiA30nyGzsDeDwCK0|PJz no)1oIxp`pfjXWrjwxQYKKRzmYXYq^rM*}?Eu&(8(u$%t{ewX!9 literal 0 HcmV?d00001 diff --git a/m5stack/modules/startup/manifest_tab5.py b/m5stack/modules/startup/manifest_tab5.py index ad589269..21b0087c 100644 --- a/m5stack/modules/startup/manifest_tab5.py +++ b/m5stack/modules/startup/manifest_tab5.py @@ -10,6 +10,11 @@ "tab5/launcher/common/common.py", "tab5/launcher/common/__init__.py", "tab5/launcher/common/indicator.py", + "tab5/launcher/common/debug.py", + "tab5/launcher/common/signal.py", + "tab5/launcher/common/uwebsockets/__init__.py", + "tab5/launcher/common/uwebsockets/client.py", + "tab5/launcher/common/uwebsockets/protocol.py", "tab5/launcher/apps/app.py", "tab5/launcher/apps/app_i2c_scan.py", "tab5/launcher/apps/app_app_list.py", @@ -22,6 +27,7 @@ "tab5/launcher/apps/app_uart.py", "tab5/launcher/apps/app_gpio.py", "tab5/launcher/apps/app_adc.py", + "tab5/launcher/apps/app_ezdata_settings.py", "tab5/launcher/hal.py", "tab5/launcher/components/ezdata_dock.py", "tab5/launcher/components/status_bar.py", diff --git a/m5stack/modules/startup/tab5/launcher/apps/__init__.py b/m5stack/modules/startup/tab5/launcher/apps/__init__.py index 7b106295..f45a9679 100644 --- a/m5stack/modules/startup/tab5/launcher/apps/__init__.py +++ b/m5stack/modules/startup/tab5/launcher/apps/__init__.py @@ -11,4 +11,5 @@ from .app_uart import AppUart from .app_gpio import AppGpio from .app_adc import AppAdc -from .app_ezdata import AppEzdata, AppEzdataSettings +from .app_ezdata import AppEzdata +from .app_ezdata_settings import AppEzdataSettings diff --git a/m5stack/modules/startup/tab5/launcher/apps/app_app_list.py b/m5stack/modules/startup/tab5/launcher/apps/app_app_list.py index eb4da21d..5d4aaf3b 100644 --- a/m5stack/modules/startup/tab5/launcher/apps/app_app_list.py +++ b/m5stack/modules/startup/tab5/launcher/apps/app_app_list.py @@ -91,7 +91,10 @@ async def main(self): for i, app_name in enumerate(get_hal().get_py_app_list()): self._app_options.append( PyAppOption( - self._py_app_panel, app_name, 10 + i * 70, self._on_py_app_option_clicked + self._py_app_panel, + app_name, + 10 + i * 70, + self._on_py_app_option_clicked, ) ) diff --git a/m5stack/modules/startup/tab5/launcher/apps/app_ezdata.py b/m5stack/modules/startup/tab5/launcher/apps/app_ezdata.py index 523530a0..a323edb2 100644 --- a/m5stack/modules/startup/tab5/launcher/apps/app_ezdata.py +++ b/m5stack/modules/startup/tab5/launcher/apps/app_ezdata.py @@ -3,141 +3,34 @@ # SPDX-License-Identifier: MIT from .app import AppBase -from ..hal import * -from ..common import * +from ..common import Ezdata, debug_print +from ..hal import get_hal import lvgl as lv import asyncio import time class ViewBase: - def cleanup(self): - pass + def __init__(self, parent: lv.obj, data: dict): + self._parent = parent + self._data_update_time = data.get("updateTime") + self._data_update_time = float(self._data_update_time) / 1000 # Convert to seconds -class ViewInit(ViewBase): - """View for nothing when ezdata is initializing""" + self._label_last_update_time: lv.obj | None = None + self._create_last_update_time_label() + self._update_last_update_time() - def __init__(self, parent: lv.obj): - label = lv.label(parent) - label.set_text("EzData Initializing...") - label.align(lv.ALIGN.CENTER, 0, -0) - label.set_style_text_font(lv.font_montserrat_24, lv.PART.MAIN) - - -class ViewWaitUserToken(ViewBase): - """View for no user token, display qrcode and tips""" - - def __init__(self, parent: lv.obj, qrcode_data: str): - qr_add = lv.qrcode(parent) - qr_add.align(lv.ALIGN.CENTER, -167, -62) - qr_add.set_size(280) - qr_add.set_dark_color(lv.color_hex(0x0075B0)) - qr_add.update(qrcode_data, len(qrcode_data)) - - label_qr_add = lv.label(parent) - label_qr_add.set_text('Scan with the "Ez Data" app to begin.') - label_qr_add.align(lv.ALIGN.CENTER, -167, 127) - label_qr_add.set_style_text_font(lv.font_montserrat_24, lv.PART.MAIN) - - install_url = "https://apps.apple.com/us/app/ezdata/id6738713869" - qr_install = lv.qrcode(parent) - qr_install.align(lv.ALIGN.CENTER, 413, -46) - qr_install.set_size(170) - qr_install.set_dark_color(lv.color_hex(0x0075B0)) - qr_install.update(install_url, len(install_url)) - - label_qr_install = lv.label(parent) - label_qr_install.set_width(300) - label_qr_install.set_text('Install "Ez Data" from App Store') - label_qr_install.align(lv.ALIGN.CENTER, 413, 110) - label_qr_install.set_style_text_font(lv.font_montserrat_24, lv.PART.MAIN) - label_qr_install.set_style_text_align(lv.TEXT_ALIGN.CENTER, lv.PART.MAIN) - - divider = lv.obj(parent) - divider.set_size(6, 345) - divider.set_style_border_width(0, lv.PART.MAIN) - divider.align(lv.ALIGN.CENTER, 249, 0) - divider.set_style_radius(2, lv.PART.MAIN) - divider.set_style_bg_color(lv.color_hex(0xE5E5E5), lv.PART.MAIN) - - -class DataItem: - """Item class to render a data key-value pair""" - - def __init__(self, parent: lv.obj, data, pos_x: int, pos_y: int): - # Transparent container for easier position handle - container = lv.obj(parent) - container.set_size(140, 210) - container.set_style_bg_opa(lv.OPA.TRANSP, lv.PART.MAIN) - container.set_style_border_width(0, lv.PART.MAIN) - container.remove_flag(lv.obj.FLAG.SCROLLABLE) - container.align(lv.ALIGN.TOP_LEFT, pos_x, pos_y) - - panel = lv.obj(container) - panel.align(lv.ALIGN.CENTER, 0, -20) - panel.set_size(140, 170) - panel.set_style_radius(18, lv.PART.MAIN) - panel.set_style_bg_color(lv.color_hex(0xFFFFFF), lv.PART.MAIN) - panel.remove_flag(lv.obj.FLAG.SCROLLABLE) - - value_panel = lv.obj(panel) - value_panel.set_size(120, 110) - value_panel.align(lv.ALIGN.CENTER, 0, -12) - value_panel.set_style_bg_color(lv.color_hex(0xFFFFFF), lv.PART.MAIN) - value_panel.set_style_border_width(0, lv.PART.MAIN) - value_panel.set_scroll_dir(lv.DIR.VER) - - label_value = lv.label(value_panel) - label_value.set_width(100) - label_value.align(lv.ALIGN.CENTER, 0, 0) - label_value.set_style_text_font(lv.font_montserrat_20, lv.PART.MAIN) - label_value.set_style_text_align(lv.TEXT_ALIGN.CENTER, lv.PART.MAIN) - - if not self._check_data_valid(data): - label_value.set_text("Invalid data") - return - - self._label_last_update = lv.label(panel) - self._label_last_update.align(lv.ALIGN.CENTER, 0, 60) - self._label_last_update.set_style_text_font(lv.font_montserrat_20, lv.PART.MAIN) - self._label_last_update.set_style_text_color(lv.color_hex(0x6E6E6E), lv.PART.MAIN) - - label_name = lv.label(container) - label_name.align(lv.ALIGN.CENTER, 0, 88) - label_name.set_style_text_font(lv.font_montserrat_20, lv.PART.MAIN) - - # Apply texts - label_value.set_text(str(data.get("value"))) - label_name.set_text(data.get("alias")) - self._update_time = data.get("updateTime") - self.update_update_time() - - def _check_data_valid(self, data) -> bool: - if not data: - return False - keys = ["alias", "value", "updateTime", "valueType"] - for key in keys: - if key not in data: - return False - return True - - def _get_time_since_last_update(self, last_update_str): - try: - year, month, day = ( - int(last_update_str[0:4]), - int(last_update_str[5:7]), - int(last_update_str[8:10]), - ) - hour, minute, second = ( - int(last_update_str[11:13]), - int(last_update_str[14:16]), - int(last_update_str[17:19]), - ) - last_time = time.mktime((year, month, day, hour, minute, second, 0, 0)) + def cleanup(self): + self._label_last_update_time = None + def update(self): + self._update_last_update_time() + + def _get_time_since_last_update(self, data_update_time: float): + try: now = time.time() - diff = int(now - last_time) + diff = int(now - data_update_time) if diff >= 3600: return "{}H".format(min(diff // 3600, 99)) @@ -149,131 +42,136 @@ def _get_time_since_last_update(self, last_update_str): print(e) return "" - def update_update_time(self): - self._label_last_update.set_text(self._get_time_since_last_update(self._update_time)) + def _create_last_update_time_label(self): + self._label_last_update_time = lv.label(self._parent) + self._label_last_update_time.align(lv.ALIGN.CENTER, 520, 226) + self._label_last_update_time.set_style_text_color(lv.color_hex(0x9EA4B5), lv.PART.MAIN) + self._label_last_update_time.set_style_text_font(lv.font_montserrat_22, lv.PART.MAIN) + self._label_last_update_time.set_style_text_align(lv.TEXT_ALIGN.CENTER, lv.PART.MAIN) + + def _update_last_update_time(self): + if self._label_last_update_time: + self._label_last_update_time.set_text( + self._get_time_since_last_update(self._data_update_time) + ) - def cleanup(self): - self._label_last_update = None + def create_data_type_label(self, type_name: str): + pass + # panel = lv.obj(self._parent) + # panel.align(lv.ALIGN.TOP_LEFT, 30, 23) + # panel.set_size(len(type_name) * 22 + 20, 32) + # panel.set_style_pad_all(0, lv.PART.MAIN) + # panel.set_scrollbar_mode(lv.SCROLLBAR_MODE.OFF) + # panel.set_style_radius(12, lv.PART.MAIN) + # panel.set_style_bg_color(lv.color_hex(0xE0E7FF), lv.PART.MAIN) + # panel.set_style_border_color(lv.color_hex(0x9DC0FA), lv.PART.MAIN) -class ViewData(ViewBase): - """View for a data group, render key-value pairs by api json response""" + # label = lv.label(panel) + # label.set_text(type_name) + # label.align(lv.ALIGN.CENTER, 0, 0) + # label.set_style_text_color(lv.color_hex(0x2E3F71), lv.PART.MAIN) + # label.set_style_text_font(lv.font_montserrat_22, lv.PART.MAIN) - def __init__(self, parent: lv.obj): - self._parent = parent - self._last_data = None - self._data_items = [] - - def update(self, data): - if self._last_data == data: - return - - self._create_data_items(data) - self._last_data = data - - def update_data_update_time(self): - for item in self._data_items: - item.update_update_time() - - def _create_data_items(self, data): - # Clean up old items - for item in self._data_items: - item.cleanup() - self._data_items.clear() - self._parent.clean() - - # Create new items - start_x, start_y = 56, 30 - offset_x, offset_y = 220, 237 - items_per_row = 5 - for i, item in enumerate(data): - row = i // items_per_row - col = i % items_per_row - x = start_x + col * offset_x - y = start_y + row * offset_y - self._data_items.append(DataItem(self._parent, item, x, y)) - def cleanup(self): - self._parent = None +class ViewString(ViewBase): + def __init__(self, parent: lv.obj, data: dict): + super().__init__(parent, data) + + self.create_data_type_label("Str") + + label = lv.label(parent) + label.set_text(str(data.get("value"))) + label.align(lv.ALIGN.CENTER, 0, -10) + label.set_style_text_color(lv.color_hex(0x272F43), lv.PART.MAIN) + label.set_style_text_font(lv.font_montserrat_32, lv.PART.MAIN) + label.set_style_text_align(lv.TEXT_ALIGN.CENTER, lv.PART.MAIN) + + +class ViewNumber(ViewBase): + def __init__(self, parent: lv.obj, data: dict): + super().__init__(parent, data) + + self.create_data_type_label("Num") + + label = lv.label(parent) + label.set_text(str(data.get("value"))) + label.align(lv.ALIGN.CENTER, 0, -10) + label.set_style_text_color(lv.color_hex(0x272F43), lv.PART.MAIN) + label.set_style_text_font(lv.font_montserrat_32, lv.PART.MAIN) + label.set_style_text_align(lv.TEXT_ALIGN.CENTER, lv.PART.MAIN) + + +class ViewArray(ViewBase): + class _Item: + def __init__(self, parent: lv.obj, data: str, pos_y: int): + panel = lv.obj(parent) + panel.set_size(1044, 55) + panel.align(lv.ALIGN.TOP_LEFT, 0, pos_y) + panel.set_style_radius(16, lv.PART.MAIN) + panel.set_style_bg_color(lv.color_hex(0xE0EDFF), lv.PART.MAIN) + panel.set_style_border_width(0, lv.PART.MAIN) + panel.set_style_pad_all(0, lv.PART.MAIN) + + label = lv.label(panel) + label.set_text(data) + label.align(lv.ALIGN.LEFT_MID, 34, 0) + label.set_style_text_color(lv.color_hex(0x272F43), lv.PART.MAIN) + label.set_style_text_font(lv.font_montserrat_32, lv.PART.MAIN) + + def __init__(self, parent: lv.obj, data: list): + super().__init__(parent, data) + + panel = lv.obj(parent) + panel.set_size(1092, 429) + panel.align(lv.ALIGN.CENTER, 0, -20) + panel.set_style_bg_opa(lv.OPA.TRANSP, lv.PART.MAIN) + panel.set_style_border_width(0, lv.PART.MAIN) + panel.set_style_pad_all(0, lv.PART.MAIN) + panel.set_style_pad_left(24, lv.PART.MAIN) + panel.set_style_pad_top(28, lv.PART.MAIN) + + data_array = data.get("value") + for i, item in enumerate(data_array): + self._Item(panel, str(item), i * 84) class AppEzdata(AppBase): async def main(self): - self._last_ezdata_state = None self._view = None - self._last_update_time = 0 - self._last_data_update_time = 0 + + self._create_view() + Ezdata.on_selected_data_changed.connect(self._handle_data_changed) while True: - self._update_view() - await asyncio.sleep(0.5) + await asyncio.sleep(10) + if self._view: + self._view.update() + + def on_cleanup(self): + Ezdata.on_selected_data_changed.disconnect(self._handle_data_changed) + self._destroy_view() def _destroy_view(self): if self._view: self._view.cleanup() self._view = None - self.get_app_panel().clean() - - def _update_view(self): - # If state changed - state = Ezdata.get_state() - if state != self._last_ezdata_state: - # Destroy old - self._destroy_view() - # Create new - if state == Ezdata.State.INIT: - self._view = ViewInit(self.get_app_panel()) - elif state == Ezdata.State.WAIT_USER_TOKEN: - self._view = ViewWaitUserToken(self.get_app_panel(), Ezdata.get_add_user_qr_code()) - elif state == Ezdata.State.NORMAL: - self._view = ViewData(self.get_app_panel()) - - self._last_ezdata_state = state - self._last_update_time = 0 - - # If is data view, update data - if isinstance(self._view, ViewData): - go_refresh = False - - # If some thing changed, refresh data - if EzdataAppState.needs_refresh(): - EzdataAppState.clear_needs_refresh() - go_refresh = True - - # Auto refresh in every 10s - if time.time() - self._last_update_time > 10: - go_refresh = True - - if go_refresh: - new_data = Ezdata.get_user_data_list(EzdataAppState.get_current_group_id()) - self._view.update(new_data) - self._last_update_time = time.time() - - # Update data's last update time in every 10s - if time.time() - self._last_data_update_time > 10: - self._view.update_data_update_time() - self._last_data_update_time = time.time() + self.get_app_panel().clean() - def on_cleanup(self): + def _create_view(self): self._destroy_view() + data = Ezdata.get_selected_data() + value = data.get("value") -class AppEzdataSettings(AppBase): - async def main(self): - btn_reset_ezdata = lv.button(self.get_app_panel()) - btn_reset_ezdata.align(lv.ALIGN.TOP_MID, -350, 80) - btn_reset_ezdata.set_size(280, 42) - btn_reset_ezdata.set_style_bg_color(lv.color_hex(0xF1677B), lv.PART.MAIN) - btn_reset_ezdata.set_style_shadow_width(0, lv.PART.MAIN) - btn_reset_ezdata.add_event_cb( - self._handle_btn_reset_ezdata_clicked, lv.EVENT.CLICKED, None - ) - - label_reset_ezdata = lv.label(btn_reset_ezdata) - label_reset_ezdata.set_align(lv.ALIGN.CENTER) - label_reset_ezdata.set_style_text_font(lv.font_montserrat_20, lv.PART.MAIN) - label_reset_ezdata.set_text("Reset Ezdata") - - def _handle_btn_reset_ezdata_clicked(self, e: lv.event_t): - get_hal().play_click_sfx() - Ezdata.reset_user_token() + if isinstance(value, str): + self._view = ViewString(self.get_app_panel(), data) + elif isinstance(value, int) or isinstance(value, float): + self._view = ViewNumber(self.get_app_panel(), data) + elif isinstance(value, list): + self._view = ViewArray(self.get_app_panel(), data) + else: + print("unsupported data type:", type(value)) + + def _handle_data_changed(self): + self._create_view() diff --git a/m5stack/modules/startup/tab5/launcher/apps/app_ezdata_settings.py b/m5stack/modules/startup/tab5/launcher/apps/app_ezdata_settings.py new file mode 100644 index 00000000..fc021d14 --- /dev/null +++ b/m5stack/modules/startup/tab5/launcher/apps/app_ezdata_settings.py @@ -0,0 +1,97 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +from .app import AppBase +from ..common import Ezdata, debug_print +from ..hal import get_hal +import lvgl as lv + + +class AppEzdataSettings(AppBase): + async def main(self): + parent = self.get_app_panel() + # qrcode_data = Ezdata.get_add_user_qr_code() + + # qr_add = lv.qrcode(parent) + # qr_add.align(lv.ALIGN.CENTER, -167, -62) + # qr_add.set_size(280) + # qr_add.set_dark_color(lv.color_hex(0x0075B0)) + # qr_add.update(qrcode_data, len(qrcode_data)) + + # label_qr_add = lv.label(parent) + # label_qr_add.set_text('Scan with the "Ez Data" app to begin.') + # label_qr_add.align(lv.ALIGN.CENTER, -167, 127) + # label_qr_add.set_style_text_font(lv.font_montserrat_24, lv.PART.MAIN) + + # install_url = "https://apps.apple.com/us/app/ezdata/id6738713869" + # qr_install = lv.qrcode(parent) + # qr_install.align(lv.ALIGN.CENTER, 413, -46) + # qr_install.set_size(170) + # qr_install.set_dark_color(lv.color_hex(0x0075B0)) + # qr_install.update(install_url, len(install_url)) + + # label_qr_install = lv.label(parent) + # label_qr_install.set_width(300) + # label_qr_install.set_text('Install "Ez Data" from App Store') + # label_qr_install.align(lv.ALIGN.CENTER, 413, 110) + # label_qr_install.set_style_text_font(lv.font_montserrat_24, lv.PART.MAIN) + # label_qr_install.set_style_text_align(lv.TEXT_ALIGN.CENTER, lv.PART.MAIN) + + # divider = lv.obj(parent) + # divider.set_size(6, 345) + # divider.set_style_border_width(0, lv.PART.MAIN) + # divider.align(lv.ALIGN.CENTER, 249, 0) + # divider.set_style_radius(2, lv.PART.MAIN) + # divider.set_style_bg_color(lv.color_hex(0xE5E5E5), lv.PART.MAIN) + + # Step 1 + img_bg_1 = lv.image(parent) + img_bg_1.set_src(get_hal().get_asset_path("utils/ezdata_guide_bg_1.png")) + img_bg_1.align(lv.ALIGN.TOP_LEFT, 33, 20) + + label_1 = lv.label(parent) + label_1.set_text('Install "Ez Data" from App Store') + label_1.align(lv.ALIGN.LEFT_MID, 132, -171) + label_1.set_style_text_font(lv.font_montserrat_24, lv.PART.MAIN) + + install_url = "https://apps.apple.com/us/app/ezdata/id6738713869" + qr_install = lv.qrcode(parent) + qr_install.align(lv.ALIGN.CENTER, -190, -50) + qr_install.set_size(135) + qr_install.set_dark_color(lv.color_hex(0x3F3F3F)) + qr_install.update(install_url, len(install_url)) + + # Step 2 + img_bg_2 = lv.image(parent) + img_bg_2.set_src(get_hal().get_asset_path("utils/ezdata_guide_bg_2.png")) + img_bg_2.align(lv.ALIGN.TOP_LEFT, 579, 20) + + label_2 = lv.label(parent) + label_2.set_text('Scan with the "Ez Data" app\nto Add device') + label_2.align(lv.ALIGN.LEFT_MID, 688, -171) + label_2.set_style_text_font(lv.font_montserrat_24, lv.PART.MAIN) + + add_user_qr_code = Ezdata.get_add_user_qr_code() + qr_add = lv.qrcode(parent) + qr_add.align(lv.ALIGN.CENTER, 369, -50) + qr_add.set_size(135) + qr_add.set_dark_color(lv.color_hex(0x3F3F3F)) + qr_add.update(add_user_qr_code, len(add_user_qr_code)) + + # Step 3 + img_bg_3 = lv.image(parent) + img_bg_3.set_src(get_hal().get_asset_path("utils/ezdata_guide_bg_3.png")) + img_bg_3.align(lv.ALIGN.TOP_LEFT, 33, 334) + + mac = "".join(f"{byte:02x}" for byte in get_hal().get_mac()) + label_3 = lv.label(parent) + label_3.set_text(f"Add or modify data in the tab5-{mac} folder") + label_3.align(lv.ALIGN.LEFT_MID, 132, 157) + label_3.set_style_text_font(lv.font_montserrat_24, lv.PART.MAIN) + + label_folder_name = lv.label(parent) + label_folder_name.set_text(f"tab5-{mac}") + label_folder_name.align(lv.ALIGN.CENTER, 369, 195) + label_folder_name.set_style_text_color(lv.color_hex(0x3F3F3F), lv.PART.MAIN) + label_folder_name.set_style_text_font(lv.font_montserrat_16, lv.PART.MAIN) diff --git a/m5stack/modules/startup/tab5/launcher/apps/app_i2c_scan.py b/m5stack/modules/startup/tab5/launcher/apps/app_i2c_scan.py index bafa5c72..f6074f34 100644 --- a/m5stack/modules/startup/tab5/launcher/apps/app_i2c_scan.py +++ b/m5stack/modules/startup/tab5/launcher/apps/app_i2c_scan.py @@ -16,9 +16,9 @@ async def main(self): img_panel_port_a.set_src(get_hal().get_asset_path("utils/i2c_panel_port_a.png")) img_panel_port_a.align(lv.ALIGN.CENTER, -279, 34) - # img_panel_internal = lv.image(self.get_app_panel()) - # img_panel_internal.set_src(get_hal().get_asset_path("utils/i2c_panel_internal.png")) - # img_panel_internal.align(lv.ALIGN.CENTER, 279, 34) + img_panel_internal = lv.image(self.get_app_panel()) + img_panel_internal.set_src(get_hal().get_asset_path("utils/i2c_panel_internal.png")) + img_panel_internal.align(lv.ALIGN.CENTER, 279, 34) btn_scan_port_a = lv.button(self.get_app_panel()) btn_scan_port_a.set_size(260, 54) @@ -33,18 +33,18 @@ async def main(self): btn_label_port_a.set_style_text_font(lv.font_montserrat_24, lv.PART.MAIN) btn_label_port_a.set_style_text_color(lv.color_hex(0x373737), lv.PART.MAIN) - # btn_scan_internal = lv.button(self.get_app_panel()) - # btn_scan_internal.set_size(260, 54) - # btn_scan_internal.align(lv.ALIGN.CENTER, 279, -158) - # btn_scan_internal.set_style_bg_color(lv.color_hex(0xFFD733), lv.PART.MAIN) - # btn_scan_internal.set_style_shadow_width(0, lv.PART.MAIN) - # btn_scan_internal.add_event_cb(self._handle_scan_internal, lv.EVENT.CLICKED, None) - - # btn_label_internal = lv.label(btn_scan_internal) - # btn_label_internal.set_text("SCAN INTERNAL") - # btn_label_internal.align(lv.ALIGN.CENTER, 0, 0) - # btn_label_internal.set_style_text_font(lv.font_montserrat_24, lv.PART.MAIN) - # btn_label_internal.set_style_text_color(lv.color_hex(0x373737), lv.PART.MAIN) + btn_scan_internal = lv.button(self.get_app_panel()) + btn_scan_internal.set_size(260, 54) + btn_scan_internal.align(lv.ALIGN.CENTER, 279, -158) + btn_scan_internal.set_style_bg_color(lv.color_hex(0xFFD733), lv.PART.MAIN) + btn_scan_internal.set_style_shadow_width(0, lv.PART.MAIN) + btn_scan_internal.add_event_cb(self._handle_scan_internal, lv.EVENT.CLICKED, None) + + btn_label_internal = lv.label(btn_scan_internal) + btn_label_internal.set_text("SCAN INTERNAL") + btn_label_internal.align(lv.ALIGN.CENTER, 0, 0) + btn_label_internal.set_style_text_font(lv.font_montserrat_24, lv.PART.MAIN) + btn_label_internal.set_style_text_color(lv.color_hex(0x373737), lv.PART.MAIN) self._addr_labels_port_a: list[lv.label] = [] self._addr_labels_internal: list[lv.label] = [] diff --git a/m5stack/modules/startup/tab5/launcher/apps/app_wifi_scan.py b/m5stack/modules/startup/tab5/launcher/apps/app_wifi_scan.py index 49927046..bfcf512a 100644 --- a/m5stack/modules/startup/tab5/launcher/apps/app_wifi_scan.py +++ b/m5stack/modules/startup/tab5/launcher/apps/app_wifi_scan.py @@ -32,7 +32,13 @@ def _parse_network_info(self, network_info: tuple): auth = network_info[4] hidden = network_info[5] - auth_dict = {0: "OPEN", 1: "WEP", 2: "WPA-PSK", 3: "WPA2-PSK", 4: "WPA/WPA2-PSK"} + auth_dict = { + 0: "OPEN", + 1: "WEP", + 2: "WPA-PSK", + 3: "WPA2-PSK", + 4: "WPA/WPA2-PSK", + } auth_str = auth_dict.get(auth, "UNKNOWN") self._label.set_text( diff --git a/m5stack/modules/startup/tab5/launcher/common/__init__.py b/m5stack/modules/startup/tab5/launcher/common/__init__.py index 5e3a551e..42cc6391 100644 --- a/m5stack/modules/startup/tab5/launcher/common/__init__.py +++ b/m5stack/modules/startup/tab5/launcher/common/__init__.py @@ -1,3 +1,5 @@ from .common import * from .indicator import IconIndicator -from .ezdata import Ezdata, EzdataAppState +from .ezdata import Ezdata +from .signal import Signal +from .debug import debug_print diff --git a/m5stack/modules/startup/tab5/launcher/common/debug.py b/m5stack/modules/startup/tab5/launcher/common/debug.py new file mode 100644 index 00000000..a6eefe70 --- /dev/null +++ b/m5stack/modules/startup/tab5/launcher/common/debug.py @@ -0,0 +1,12 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + + +# DEBUG = True +DEBUG = False + + +def debug_print(*args, **kwargs): + if DEBUG: + print(*args, **kwargs) diff --git a/m5stack/modules/startup/tab5/launcher/common/ezdata.py b/m5stack/modules/startup/tab5/launcher/common/ezdata.py index f7360ad5..ccaa8719 100644 --- a/m5stack/modules/startup/tab5/launcher/common/ezdata.py +++ b/m5stack/modules/startup/tab5/launcher/common/ezdata.py @@ -1,26 +1,180 @@ # SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD # -# SPDX-License-Identifier: MI +# SPDX-License-Identifier: MIT -from ..hal import * +from ..hal import get_hal, NetworkStatus +from .uwebsockets import connect as ws_connect +from .uwebsockets import WebsocketClient +from .debug import debug_print +from .signal import Signal import asyncio import requests import json -from umqtt import MQTTClient +import time + + +class _EzdataClientWebsocket: + """ + Ezdata client via websocket + """ + + # Response type + DEVICE_ADD_DATA = 100 + DEVICE_UPDATE_DATA = 101 + DEVICE_DELETE_DATA = 102 + DEVICE_DATA_LIST = 103 + DEVICE_DATA = 104 + DEVICE_DATA_FILE = 105 + DEVICE_REQUEST_ERROR = 500 + + def __init__(self, device_token: str, on_data_changed: Signal): + self._device_token = device_token + self._on_data_changed = on_data_changed + self._ws: WebsocketClient | None = None + self._ws_last_heartbeat_time = 0 + self._data = {} + + async def init(self): + await self._connect() + await self.fetch_all_data() # Fetch actively to create initial data + + def get_all_data(self): + return self._data + + def get_data(self, data_id: str): + for data in self._data: + if data.get("id") == data_id: + return data + return None + + async def _connect(self): + try: + # Close if already connected + if self._ws: + self._ws.close() + self._ws = None + + # Connect and login + uri = "wss://ezdata2.m5stack.com/ws" + msg = f'{{"deviceToken": "{self._device_token}"}}' + + self._ws = ws_connect(uri) + + # Wait login + for i in range(10): + debug_print("ws send:", msg) + self._ws.send(msg) + + await asyncio.sleep(1) + resp = self._ws.recv(blocking=False) + debug_print("ws recv:", resp) + + if "Login successful" in resp: + self._ws_last_heartbeat_time = time.time() + debug_print("ws login successful") + return + + raise Exception("max login retry") + + except Exception as e: + raise Exception("connect ws failed:", e) + + async def fetch_all_data(self): + if self._ws is None: + return + + self._data.clear() + + for i in range(10): + try: + msg = json.dumps( + { + "deviceToken": self._device_token, + "body": {"requestType": "DEVICE_DATA_LIST"}, + } + ) + debug_print("ws send:", msg) + self._ws.send(msg) + + await asyncio.sleep(1) + resp = self._ws.recv(blocking=False) + + response = json.loads(resp) + if response.get("code") == 200: + self._data = response.get("body") + # debug_print("fetch first data success:", self._data) + return + + except Exception as e: + debug_print("fetch first data failed:", e) + await asyncio.sleep(1) + continue + + self._data.clear() + raise Exception("max fetch data retry") + + def update(self): + self._receive() + self._heartbeat() + + def _receive(self): + if self._ws is None: + return + + try: + msg = self._ws.recv(blocking=False) + if not msg: + return + + # debug_print("ws recv:", msg) + + # Skip heartbeat + if msg == "pong": + return + + # Handle data update msg + response = json.loads(msg) + if response.get("code") == 200: + cmd = response.get("cmd") + if cmd in [ + self.DEVICE_ADD_DATA, + self.DEVICE_UPDATE_DATA, + self.DEVICE_DELETE_DATA, + self.DEVICE_DATA_LIST, + ]: + self.fetch_all_data() + self._on_data_changed.emit() + else: + print("ws recv error msg:", msg) + + except Exception as e: + debug_print("ws recv failed:", e) + + def _heartbeat(self): + if self._ws is None: + return + + if time.time() - self._ws_last_heartbeat_time > 25: + msg = f'{{"deviceToken": "{self._device_token}", "body": "ping"}}' + debug_print("ws send:", msg) + self._ws.send(msg) + self._ws_last_heartbeat_time = time.time() class Ezdata: class State: INIT = 0 - WAIT_USER_TOKEN = 1 - NORMAL = 2 + NORMAL = 1 _state: State = State.INIT _device_token: str | None = None - _user_token: str | None = None - _mqtt_client: MQTTClient | None = None + _client: _EzdataClientWebsocket | None = None _task = None + on_data_list_changed: Signal = Signal() + on_selected_data_changed: Signal = Signal() + _selected_data_id: str + @staticmethod def start(): Ezdata._task = asyncio.create_task(Ezdata._ezdata_task()) @@ -33,18 +187,46 @@ def get_state() -> State: def _set_state(state: State): Ezdata._state = state + @staticmethod + def get_all_data(): + if Ezdata._client is None: + return {} + return Ezdata._client.get_all_data() + + @staticmethod + def get_data(data_id: str): + if Ezdata._client is None: + return None + return Ezdata._client.get_data(data_id) + + @staticmethod + def get_selected_data(): + return Ezdata.get_data(Ezdata._selected_data_id) + + @staticmethod + def set_selected_data_id(data_id: str): + Ezdata._selected_data_id = data_id + Ezdata.on_selected_data_changed.emit() + @staticmethod async def _ezdata_task(): try: Ezdata._set_state(Ezdata.State.INIT) + + # Wait network and get device token await Ezdata._wait_network_connected() await Ezdata._get_device_token() - await Ezdata._connect_mqtt() - Ezdata._get_user_token_from_storage() - # Keep mqtt update + # Create ezdata websocket client and init + Ezdata._client = _EzdataClientWebsocket( + Ezdata._device_token, Ezdata.on_selected_data_changed + ) + await Ezdata._client.init() + Ezdata._set_state(Ezdata.State.NORMAL) + + # Keep ezdata websocket client running while True: - Ezdata._mqtt_client.check_msg() + Ezdata._client.update() await asyncio.sleep(0.2) except Exception as e: @@ -54,7 +236,10 @@ async def _ezdata_task(): @staticmethod async def _wait_network_connected(): - while get_hal().get_network_status() in {NetworkStatus.DISCONNECTED, NetworkStatus.INIT}: + while get_hal().get_network_status() in { + NetworkStatus.DISCONNECTED, + NetworkStatus.INIT, + }: await asyncio.sleep(1) @staticmethod @@ -99,155 +284,11 @@ async def _get_device_token(): Ezdata._device_token = None await asyncio.sleep(2) - @staticmethod - async def _connect_mqtt(): - client_id = "".join(f"{byte:02x}" for byte in get_hal().get_mac()) - client_id = f"m5{client_id}m5" - - Ezdata._mqtt_client = MQTTClient( - client_id, - "uiflow2.m5stack.com", - port=1883, - user=Ezdata._device_token, - password="", - keepalive=60, - ) - - # Try connect and subscribe - while True: - try: - Ezdata._mqtt_client.connect(clean_session=True) - Ezdata._mqtt_client.subscribe( - f"$ezdata/{Ezdata._device_token}/down", Ezdata._on_ezdata_down_message - ) - break - except Exception as e: - print("connect mqtt failed:", e) - - await asyncio.sleep(2) - - @staticmethod - def _on_ezdata_down_message(data: tuple[bytes, bytes]): - try: - topic = data[0].decode("utf-8") - payload = data[1].decode("utf-8") - print("mqtt >>", topic, ":", payload) - - msg = json.loads(payload) - if msg.get("userToken"): - Ezdata._user_token = msg.get("userToken") - if Ezdata._check_token_valid(Ezdata._user_token): - get_hal().store_ezdata_user_token(Ezdata._user_token) - Ezdata._set_state(Ezdata.State.NORMAL) - else: - Ezdata._user_token = None - raise Exception("invalid user token") - - except Exception as e: - print("on ezdata down message failed:", e) - - @staticmethod - def _get_user_token_from_storage(): - Ezdata._user_token = get_hal().get_ezdata_user_token() - if Ezdata._check_token_valid(Ezdata._user_token): - Ezdata._set_state(Ezdata.State.NORMAL) - else: - Ezdata._user_token = "" - Ezdata._set_state(Ezdata.State.WAIT_USER_TOKEN) - @staticmethod def get_add_user_qr_code(): - data = {"deviceToken": Ezdata._device_token, "deviceType": "tab5", "type": "device"} + data = { + "deviceToken": Ezdata._device_token, + "deviceType": "tab5", + "type": "device", + } return json.dumps(data) - - @staticmethod - def _simplify_user_group_list_response_json(data: dict): - return [{k: item.get(k) for k in ["id", "domainName"]} for item in data.get("data", [])] - - @staticmethod - def get_user_group_list(): - result = [] - - try: - if not Ezdata._check_token_valid(Ezdata._user_token): - raise Exception("user token not valid") - - url = f"https://ezdata2.m5stack.com/api/v2/device/userGroupList/{Ezdata._user_token}" - - response = requests.get(url) - if response.status_code == 200: - result = Ezdata._simplify_user_group_list_response_json(response.json()) - - # Filter out invalid items - result = [item for item in result if "id" in item and "domainName" in item] - - else: - raise Exception( - f"get user group list failed, status code: {response.status_code} , text: {response.text}" - ) - - except Exception as e: - print("get user group list failed:", e) - - return result - - @staticmethod - def _simplify_user_data_list_response_json(data: dict): - return [ - {k: item.get(k) for k in ["alias", "value", "updateTime", "valueType"]} - for item in data.get("data", []) - ] - - @staticmethod - def get_user_data_list(group_id: str): - result = [] - - try: - if not group_id: - raise Exception("group id not valid") - - if not Ezdata._check_token_valid(Ezdata._user_token): - raise Exception("user token not valid") - - url = f"https://ezdata2.m5stack.com/api/v2/device/userDataList/{Ezdata._user_token}/{group_id}" - - response = requests.get(url) - if response.status_code == 200: - result = Ezdata._simplify_user_data_list_response_json(response.json()) - else: - raise Exception( - f"get user data list failed, status code: {response.status_code} , text: {response.text}" - ) - - except Exception as e: - print("get user data list failed:", e) - - return result - - @staticmethod - def reset_user_token(): - get_hal().reset_ezdata_user_token() - Ezdata._user_token = None - Ezdata._set_state(Ezdata.State.WAIT_USER_TOKEN) - - -class EzdataAppState: - _current_group_id: str = "" - _needs_refresh: bool = True - - @staticmethod - def set_new_group_id(group_id: str): - EzdataAppState._current_group_id = group_id - EzdataAppState._needs_refresh = True - - @staticmethod - def get_current_group_id(): - return EzdataAppState._current_group_id - - @staticmethod - def needs_refresh(): - return EzdataAppState._needs_refresh - - @staticmethod - def clear_needs_refresh(): - EzdataAppState._needs_refresh = False diff --git a/m5stack/modules/startup/tab5/launcher/common/signal.py b/m5stack/modules/startup/tab5/launcher/common/signal.py new file mode 100644 index 00000000..704a6da5 --- /dev/null +++ b/m5stack/modules/startup/tab5/launcher/common/signal.py @@ -0,0 +1,29 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + + +class Signal: + def __init__(self): + self._slots = [] + + def connect(self, func): + if callable(func) and func not in self._slots: + self._slots.append(func) + + def disconnect(self, func): + if func in self._slots: + self._slots.remove(func) + + def clear(self): + self._slots.clear() + + def emit(self, *args, **kwargs): + for func in self._slots: + func(*args, **kwargs) + + def __len__(self): + return len(self._slots) + + def __contains__(self, func): + return func in self._slots diff --git a/m5stack/modules/startup/tab5/launcher/common/uwebsockets/__init__.py b/m5stack/modules/startup/tab5/launcher/common/uwebsockets/__init__.py new file mode 100644 index 00000000..04257002 --- /dev/null +++ b/m5stack/modules/startup/tab5/launcher/common/uwebsockets/__init__.py @@ -0,0 +1 @@ +from .client import connect, WebsocketClient diff --git a/m5stack/modules/startup/tab5/launcher/common/uwebsockets/client.py b/m5stack/modules/startup/tab5/launcher/common/uwebsockets/client.py new file mode 100644 index 00000000..565e95fe --- /dev/null +++ b/m5stack/modules/startup/tab5/launcher/common/uwebsockets/client.py @@ -0,0 +1,57 @@ +""" +Websockets client for micropython + +Based very heavily off +https://github.com/aaugustin/websockets/blob/master/websockets/client.py +""" + +import socket +import binascii +import random +import ssl + +from .protocol import Websocket, urlparse + + +class WebsocketClient(Websocket): + is_client = True + + +def connect(uri): + """ + Connect a websocket. + """ + + uri = urlparse(uri) + assert uri + + sock = socket.socket() + addr = socket.getaddrinfo(uri.hostname, uri.port) + sock.connect(addr[0][4]) + if uri.protocol == "wss": + sock = ssl.wrap_socket(sock, server_hostname=uri.hostname) + + def send_header(header, *args): + sock.write(header % args + "\r\n") + + # Sec-WebSocket-Key is 16 bytes of random base64 encoded + key = binascii.b2a_base64(bytes(random.getrandbits(8) for _ in range(16)))[:-1] + + send_header(b"GET %s HTTP/1.1", uri.path or "/") + send_header(b"Host: %s:%s", uri.hostname, uri.port) + send_header(b"Connection: Upgrade") + send_header(b"Upgrade: websocket") + send_header(b"Sec-WebSocket-Key: %s", key) + send_header(b"Sec-WebSocket-Version: 13") + send_header(b"Origin: http://{hostname}:{port}".format(hostname=uri.hostname, port=uri.port)) + send_header(b"") + + header = sock.readline()[:-2] + assert header.startswith(b"HTTP/1.1 101 "), header + + # We don't (currently) need these headers + # FIXME: should we check the return key? + while header: + header = sock.readline()[:-2] + + return WebsocketClient(sock) diff --git a/m5stack/modules/startup/tab5/launcher/common/uwebsockets/protocol.py b/m5stack/modules/startup/tab5/launcher/common/uwebsockets/protocol.py new file mode 100644 index 00000000..8ef97ee5 --- /dev/null +++ b/m5stack/modules/startup/tab5/launcher/common/uwebsockets/protocol.py @@ -0,0 +1,243 @@ +""" +Websockets protocol +""" + +import re +import struct +import random +from collections import namedtuple + +# Opcodes +OP_CONT = 0x0 +OP_TEXT = 0x1 +OP_BYTES = 0x2 +OP_CLOSE = 0x8 +OP_PING = 0x9 +OP_PONG = 0xA + +# Close codes +CLOSE_OK = 1000 +CLOSE_GOING_AWAY = 1001 +CLOSE_PROTOCOL_ERROR = 1002 +CLOSE_DATA_NOT_SUPPORTED = 1003 +CLOSE_BAD_DATA = 1007 +CLOSE_POLICY_VIOLATION = 1008 +CLOSE_TOO_BIG = 1009 +CLOSE_MISSING_EXTN = 1010 +CLOSE_BAD_CONDITION = 1011 + +URL_RE = re.compile(r"(wss|ws)://([A-Za-z0-9-\.]+)(?:\:([0-9]+))?(/.+)?") +URI = namedtuple("URI", ("protocol", "hostname", "port", "path")) + + +class NoDataException(Exception): + pass + + +class ConnectionClosed(Exception): + pass + + +def urlparse(uri): + """Parse ws:// URLs""" + match = URL_RE.match(uri) + if match: + protocol = match.group(1) + host = match.group(2) + port = match.group(3) + path = match.group(4) + + if protocol == "wss": + if port is None: + port = 443 + elif protocol == "ws": + if port is None: + port = 80 + else: + raise ValueError("Scheme {} is invalid".format(protocol)) + + return URI(protocol, host, int(port), path) + + +class Websocket: + """ + Basis of the Websocket protocol. + + This can probably be replaced with the C-based websocket module, but + this one currently supports more options. + """ + + is_client = False + + def __init__(self, sock): + self.sock = sock + self.open = True + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc, tb): + self.close() + + def settimeout(self, timeout): + self.sock.settimeout(timeout) + + def read_frame(self, max_size=None): + """ + Read a frame from the socket. + See https://tools.ietf.org/html/rfc6455#section-5.2 for the details. + """ + + # Frame header + two_bytes = self.sock.read(2) + + if not two_bytes: + raise NoDataException + + byte1, byte2 = struct.unpack("!BB", two_bytes) + + # Byte 1: FIN(1) _(1) _(1) _(1) OPCODE(4) + fin = bool(byte1 & 0x80) + opcode = byte1 & 0x0F + + # Byte 2: MASK(1) LENGTH(7) + mask = bool(byte2 & (1 << 7)) + length = byte2 & 0x7F + + if length == 126: # Magic number, length header is 2 bytes + (length,) = struct.unpack("!H", self.sock.read(2)) + elif length == 127: # Magic number, length header is 8 bytes + (length,) = struct.unpack("!Q", self.sock.read(8)) + + if mask: # Mask is 4 bytes + mask_bits = self.sock.read(4) + + try: + data = self.sock.read(length) + except MemoryError: + # We can't receive this many bytes, close the socket + self.close(code=CLOSE_TOO_BIG) + return True, OP_CLOSE, None + + if mask: + data = bytes(b ^ mask_bits[i % 4] for i, b in enumerate(data)) + + return fin, opcode, data + + def write_frame(self, opcode, data=b""): + """ + Write a frame to the socket. + See https://tools.ietf.org/html/rfc6455#section-5.2 for the details. + """ + fin = True + mask = self.is_client # messages sent by client are masked + + length = len(data) + + # Frame header + # Byte 1: FIN(1) _(1) _(1) _(1) OPCODE(4) + byte1 = 0x80 if fin else 0 + byte1 |= opcode + + # Byte 2: MASK(1) LENGTH(7) + byte2 = 0x80 if mask else 0 + + if length < 126: # 126 is magic value to use 2-byte length header + byte2 |= length + self.sock.write(struct.pack("!BB", byte1, byte2)) + + elif length < (1 << 16): # Length fits in 2-bytes + byte2 |= 126 # Magic code + self.sock.write(struct.pack("!BBH", byte1, byte2, length)) + + elif length < (1 << 64): + byte2 |= 127 # Magic code + self.sock.write(struct.pack("!BBQ", byte1, byte2, length)) + + else: + raise ValueError() + + if mask: # Mask is 4 bytes + mask_bits = struct.pack("!I", random.getrandbits(32)) + self.sock.write(mask_bits) + + data = bytes(b ^ mask_bits[i % 4] for i, b in enumerate(data)) + + self.sock.write(data) + + def recv(self, blocking=True): + """ + Receive data from the websocket. + + This is slightly different from 'websockets' in that it doesn't + fire off a routine to process frames and put the data in a queue. + If you don't call recv() sufficiently often you won't process control + frames. + """ + assert self.open + + while self.open: + try: + if not blocking: + self.sock.setblocking(False) + fin, opcode, data = self.read_frame() + if not blocking: + self.sock.setblocking(True) + except NoDataException: + return "" + except ValueError: + self._close() + raise ConnectionClosed() + + if not fin: + raise NotImplementedError() + + if opcode == OP_TEXT: + return data.decode("utf-8") + elif opcode == OP_BYTES: + return data + elif opcode == OP_CLOSE: + self._close() + return + elif opcode == OP_PONG: + # Ignore this frame, keep waiting for a data frame + continue + elif opcode == OP_PING: + # We need to send a pong frame + self.write_frame(OP_PONG, data) + # And then wait to receive + continue + elif opcode == OP_CONT: + # This is a continuation of a previous frame + raise NotImplementedError(opcode) + else: + raise ValueError(opcode) + + def send(self, buf): + """Send data to the websocket.""" + + assert self.open + + if isinstance(buf, str): + opcode = OP_TEXT + buf = buf.encode("utf-8") + elif isinstance(buf, bytes): + opcode = OP_BYTES + else: + raise TypeError() + + self.write_frame(opcode, buf) + + def close(self, code=CLOSE_OK, reason=""): + """Close the websocket.""" + if not self.open: + return + + buf = struct.pack("!H", code) + reason.encode("utf-8") + + self.write_frame(OP_CLOSE, buf) + self._close() + + def _close(self): + self.open = False + self.sock.close() diff --git a/m5stack/modules/startup/tab5/launcher/components/ezdata_dock.py b/m5stack/modules/startup/tab5/launcher/components/ezdata_dock.py index 9bd433f3..d85841b0 100644 --- a/m5stack/modules/startup/tab5/launcher/components/ezdata_dock.py +++ b/m5stack/modules/startup/tab5/launcher/components/ezdata_dock.py @@ -4,15 +4,15 @@ from ..hal import get_hal from ..apps.app import AppManager -from ..common import IconIndicator, Ezdata, EzdataAppState +from ..common import IconIndicator, Ezdata +from ..common import debug_print import lvgl as lv import asyncio -import time class EzdataIcon: - def __init__(self, parent: lv.obj, pos_x: int, group_id: str, name: str): - self._group_id = group_id + def __init__(self, parent: lv.obj, pos_x: int, data_id: str, name: str, pin_color=None): + self._data_id = data_id self._icon_pos_x = pos_x btn = lv.obj(parent) @@ -22,43 +22,45 @@ def __init__(self, parent: lv.obj, pos_x: int, group_id: str, name: str): btn.add_flag(lv.obj.FLAG.CLICKABLE) btn.set_style_pad_all(0, lv.PART.MAIN) btn.add_event_cb(self._on_clicked, lv.EVENT.CLICKED, None) + btn.set_style_bg_color(lv.color_hex(0xFFFFFF), lv.PART.MAIN) - if not group_id: - btn.remove_flag(lv.obj.FLAG.CLICKABLE) - btn.set_style_bg_color(lv.color_hex(0x808080), lv.PART.MAIN) - elif group_id == "settings": - btn.set_style_bg_color(lv.color_hex(0x9E9E9E), lv.PART.MAIN) - else: - btn.set_style_bg_color(lv.color_hex(0xE6E6E6), lv.PART.MAIN) - - label = lv.label(btn) - label.set_text(name) - label.set_width(82) - label.align(lv.ALIGN.CENTER, 0, 0) - label.set_style_text_color(lv.color_hex(0x4A4949), lv.PART.MAIN) - label.set_style_text_align(lv.TEXT_ALIGN.CENTER, lv.PART.MAIN) - if group_id == "add": - label.set_style_text_font(lv.font_montserrat_24, lv.PART.MAIN) + if data_id == "settings": + btn.set_style_bg_color(lv.color_hex(0xE0EDFF), lv.PART.MAIN) + + img_icon = lv.image(btn) + img_icon.set_src(get_hal().get_asset_path("icons/settings.png")) + img_icon.align(lv.ALIGN.CENTER, 0, 0) else: + label = lv.label(btn) + label.set_text(name) + label.set_width(82) + label.align(lv.ALIGN.CENTER, 0, 0) + label.set_style_text_color(lv.color_hex(0x4A4949), lv.PART.MAIN) + label.set_style_text_align(lv.TEXT_ALIGN.CENTER, lv.PART.MAIN) label.set_style_text_font(lv.font_montserrat_14, lv.PART.MAIN) + if pin_color: + pin = lv.obj(btn) + pin.set_size(16, 16) + pin.align(lv.ALIGN.TOP_RIGHT, -6, 6) + pin.set_scrollbar_mode(lv.SCROLLBAR_MODE.OFF) + pin.set_style_border_width(0, lv.PART.MAIN) + pin.set_style_bg_color(lv.color_hex(pin_color), lv.PART.MAIN) + def _on_clicked(self, e: lv.event_t): get_hal().play_click_sfx() IconIndicator.create_indicator(e.get_target_obj()) - if self._group_id == "settings": + if self._data_id == "settings": AppManager.open_app("EzDataSettings") else: - EzdataAppState.set_new_group_id(self._group_id) + Ezdata.set_selected_data_id(self._data_id) AppManager.open_app("EzData") class EzdataDock: def __init__(self): - self._last_ezdata_state = Ezdata.State.INIT self._icons = [] - self._last_update_group_list_time = 0 - self._last_group_list = [] self._dock_panel = lv.obj(lv.screen_active()) self._dock_panel.set_size(445, 115) @@ -68,67 +70,52 @@ def __init__(self): self._dock_panel.set_style_pad_all(8, lv.PART.MAIN) self._dock_panel.set_scrollbar_mode(lv.SCROLLBAR_MODE.ACTIVE) - self._create_init_dock() + label_init = lv.label(self._dock_panel) + label_init.set_text("Connecting...") + label_init.align(lv.ALIGN.CENTER, 0, 0) + label_init.set_style_text_color(lv.color_hex(0xFFFFFF), lv.PART.MAIN) + label_init.set_style_text_font(lv.font_montserrat_24, lv.PART.MAIN) - self._task = asyncio.create_task(self._update_task()) + self._task = asyncio.create_task(self._task()) - async def _update_task(self): - while True: + async def _task(self): + # Wait ezdata ready + while Ezdata.get_state() == Ezdata.State.INIT: await asyncio.sleep(0.5) + continue - state = Ezdata.get_state() - - # If state changed - if not state == self._last_ezdata_state: - self._handle_state_changed(state) - self._last_ezdata_state = state - - # Check if group list is updated in every 10s - if state == Ezdata.State.NORMAL: - if time.time() - self._last_update_group_list_time > 10: - new_group_list = Ezdata.get_user_group_list() - if new_group_list != self._last_group_list: - self._create_data_dock(new_group_list) - self._last_group_list = new_group_list - self._last_update_group_list_time = time.time() - - def _handle_state_changed(self, new_state: Ezdata.State): - # State init and wait token share the same dock - if new_state == Ezdata.State.INIT: - if self._last_ezdata_state == Ezdata.State.WAIT_USER_TOKEN: - return - self._create_init_dock() - elif new_state == Ezdata.State.WAIT_USER_TOKEN: - if self._last_ezdata_state == Ezdata.State.INIT: - return - self._create_init_dock() - - # Create data dock when ezdata in normal state - elif new_state == Ezdata.State.NORMAL: - new_group_list = Ezdata.get_user_group_list() - self._create_data_dock(new_group_list) - self._last_update_group_list_time = time.time() - self._last_group_list = new_group_list - - def _create_init_dock(self): - self._icons.clear() - self._dock_panel.clean() + # Connect on change signal and create initial data dock + Ezdata.on_data_list_changed.connect(self._handle_data_list_changed) + self._create_data_dock() - for i in range(4): - if i == 0: - self._icons.append(EzdataIcon(self._dock_panel, 0, "add", "+")) - else: - self._icons.append(EzdataIcon(self._dock_panel, i * 110, "", "")) + def _create_data_dock(self): + all_data = Ezdata.get_all_data() + # debug_print("data:", all_data) - def _create_data_dock(self, group_list: list[dict]): self._icons.clear() self._dock_panel.clean() - for i, group in enumerate(group_list): + # Setting icon + self._icons.append(EzdataIcon(self._dock_panel, 0, "settings", "settings")) + + # If no data + if len(all_data) == 0: + label_no_data = lv.label(self._dock_panel) + label_no_data.set_text("No data.") + label_no_data.align(lv.ALIGN.CENTER, 50, 0) + label_no_data.set_style_text_color(lv.color_hex(0xFFFFFF), lv.PART.MAIN) + label_no_data.set_style_text_font(lv.font_montserrat_24, lv.PART.MAIN) + + for i, data in enumerate(all_data): self._icons.append( - EzdataIcon(self._dock_panel, i * 110, group.get("id"), group.get("domainName")) + EzdataIcon( + self._dock_panel, + (i + 1) * 110, + data.get("id"), + data.get("name"), + ) ) - self._icons.append( - EzdataIcon(self._dock_panel, len(self._icons) * 110, "settings", "settings") - ) + def _handle_data_list_changed(self): + debug_print("[EzdataDock] data list changed") + self._create_data_dock() diff --git a/m5stack/modules/startup/tab5/launcher/components/status_bar.py b/m5stack/modules/startup/tab5/launcher/components/status_bar.py index 189f7983..a933a74f 100644 --- a/m5stack/modules/startup/tab5/launcher/components/status_bar.py +++ b/m5stack/modules/startup/tab5/launcher/components/status_bar.py @@ -4,7 +4,7 @@ from ..hal import get_hal from ..apps.app import AppManager -from ..common import * +from ..common import IMAGE_SUFFIX, IconIndicator import lvgl as lv import time as t import asyncio @@ -104,11 +104,19 @@ def __init__(self, parent: lv.obj): self._label_output_current.set_style_text_font(lv.font_montserrat_20, lv.PART.MAIN) self._label_output_current.align(lv.ALIGN.CENTER, -9, 19) - self._update_img() - self._update_labels() + # self._update_img() + # self._update_labels() + + # # TODO: Update task + # self._img.set_style_image_recolor_opa(123, lv.PART.MAIN) + + self._task = asyncio.create_task(self.update_task()) - # TODO: Update task - self._img.set_style_image_recolor_opa(123, lv.PART.MAIN) + async def update_task(self): + while True: + self._update_img() + self._update_labels() + await asyncio.sleep_ms(1000) def _update_img(self): if get_hal().is_charging(): diff --git a/m5stack/modules/startup/tab5/launcher/launcher.py b/m5stack/modules/startup/tab5/launcher/launcher.py index 0582b11d..fbebd339 100644 --- a/m5stack/modules/startup/tab5/launcher/launcher.py +++ b/m5stack/modules/startup/tab5/launcher/launcher.py @@ -2,12 +2,22 @@ # # SPDX-License-Identifier: MIT -from .components import * -from .common import * -from .apps import * +from .apps import ( + AppManager, + AppWifi, + AppAppList, + AppI2cScan, + AppWifiScan, + AppUart, + AppGpio, + AppAdc, + AppEzdata, + AppEzdataSettings, +) +from .components import StatusBar, AppDock, EzdataDock +from .common import Ezdata import lvgl as lv import asyncio -import M5 class Launcher: @@ -28,6 +38,7 @@ def _init_background(self): self._bottom_bar.set_style_radius(0, lv.PART.MAIN) self._bottom_bar.set_style_border_width(0, lv.PART.MAIN) self._bottom_bar.align(lv.ALIGN.BOTTOM_MID, 0, 0) + self._bottom_bar.set_scrollbar_mode(lv.SCROLLBAR_MODE.OFF) self._bottom_bar.set_style_pad_left(12, lv.PART.MAIN) self._bottom_label = lv.label(self._bottom_bar) @@ -89,21 +100,18 @@ async def _main(self): AppManager.install_app("UART", AppUart) AppManager.install_app("GPIO", AppGpio) AppManager.install_app("ADC", AppAdc) - # AppManager.install_app("EzData", AppEzdata) - # AppManager.install_app("EzDataSettings", AppEzdataSettings) + AppManager.install_app("EzData", AppEzdata) + AppManager.install_app("EzDataSettings", AppEzdataSettings) # Create components self._status_bar = StatusBar() + self._ezdata_dock = EzdataDock() self._app_dock = AppDock() - # self._ezdata_dock = EzdataDock() # Start ezdata service - # Ezdata.start() - - try: - # Keep app manager running - while True: - await asyncio.sleep_ms(50) - await AppManager.update() - except KeyboardInterrupt: - M5.Lcd.lvgl_deinit() + Ezdata.start() + + # Keep app manager running + while True: + await asyncio.sleep_ms(50) + await AppManager.update() From c2ddfef889dda720a71cb9f0394a9797f92f75d7 Mon Sep 17 00:00:00 2001 From: Forairaaaaa Date: Mon, 14 Jul 2025 11:28:36 +0800 Subject: [PATCH 171/322] modules/startup/tab5: Remove debug code. Signed-off-by: Forairaaaaa --- .../startup/tab5/launcher/apps/app_ezdata.py | 22 ------------- .../tab5/launcher/apps/app_ezdata_settings.py | 33 ------------------- .../modules/startup/tab5/launcher/launcher.py | 12 ++++--- 3 files changed, 8 insertions(+), 59 deletions(-) diff --git a/m5stack/modules/startup/tab5/launcher/apps/app_ezdata.py b/m5stack/modules/startup/tab5/launcher/apps/app_ezdata.py index a323edb2..d73d43c2 100644 --- a/m5stack/modules/startup/tab5/launcher/apps/app_ezdata.py +++ b/m5stack/modules/startup/tab5/launcher/apps/app_ezdata.py @@ -55,31 +55,11 @@ def _update_last_update_time(self): self._get_time_since_last_update(self._data_update_time) ) - def create_data_type_label(self, type_name: str): - pass - - # panel = lv.obj(self._parent) - # panel.align(lv.ALIGN.TOP_LEFT, 30, 23) - # panel.set_size(len(type_name) * 22 + 20, 32) - # panel.set_style_pad_all(0, lv.PART.MAIN) - # panel.set_scrollbar_mode(lv.SCROLLBAR_MODE.OFF) - # panel.set_style_radius(12, lv.PART.MAIN) - # panel.set_style_bg_color(lv.color_hex(0xE0E7FF), lv.PART.MAIN) - # panel.set_style_border_color(lv.color_hex(0x9DC0FA), lv.PART.MAIN) - - # label = lv.label(panel) - # label.set_text(type_name) - # label.align(lv.ALIGN.CENTER, 0, 0) - # label.set_style_text_color(lv.color_hex(0x2E3F71), lv.PART.MAIN) - # label.set_style_text_font(lv.font_montserrat_22, lv.PART.MAIN) - class ViewString(ViewBase): def __init__(self, parent: lv.obj, data: dict): super().__init__(parent, data) - self.create_data_type_label("Str") - label = lv.label(parent) label.set_text(str(data.get("value"))) label.align(lv.ALIGN.CENTER, 0, -10) @@ -92,8 +72,6 @@ class ViewNumber(ViewBase): def __init__(self, parent: lv.obj, data: dict): super().__init__(parent, data) - self.create_data_type_label("Num") - label = lv.label(parent) label.set_text(str(data.get("value"))) label.align(lv.ALIGN.CENTER, 0, -10) diff --git a/m5stack/modules/startup/tab5/launcher/apps/app_ezdata_settings.py b/m5stack/modules/startup/tab5/launcher/apps/app_ezdata_settings.py index fc021d14..8511aeb4 100644 --- a/m5stack/modules/startup/tab5/launcher/apps/app_ezdata_settings.py +++ b/m5stack/modules/startup/tab5/launcher/apps/app_ezdata_settings.py @@ -11,39 +11,6 @@ class AppEzdataSettings(AppBase): async def main(self): parent = self.get_app_panel() - # qrcode_data = Ezdata.get_add_user_qr_code() - - # qr_add = lv.qrcode(parent) - # qr_add.align(lv.ALIGN.CENTER, -167, -62) - # qr_add.set_size(280) - # qr_add.set_dark_color(lv.color_hex(0x0075B0)) - # qr_add.update(qrcode_data, len(qrcode_data)) - - # label_qr_add = lv.label(parent) - # label_qr_add.set_text('Scan with the "Ez Data" app to begin.') - # label_qr_add.align(lv.ALIGN.CENTER, -167, 127) - # label_qr_add.set_style_text_font(lv.font_montserrat_24, lv.PART.MAIN) - - # install_url = "https://apps.apple.com/us/app/ezdata/id6738713869" - # qr_install = lv.qrcode(parent) - # qr_install.align(lv.ALIGN.CENTER, 413, -46) - # qr_install.set_size(170) - # qr_install.set_dark_color(lv.color_hex(0x0075B0)) - # qr_install.update(install_url, len(install_url)) - - # label_qr_install = lv.label(parent) - # label_qr_install.set_width(300) - # label_qr_install.set_text('Install "Ez Data" from App Store') - # label_qr_install.align(lv.ALIGN.CENTER, 413, 110) - # label_qr_install.set_style_text_font(lv.font_montserrat_24, lv.PART.MAIN) - # label_qr_install.set_style_text_align(lv.TEXT_ALIGN.CENTER, lv.PART.MAIN) - - # divider = lv.obj(parent) - # divider.set_size(6, 345) - # divider.set_style_border_width(0, lv.PART.MAIN) - # divider.align(lv.ALIGN.CENTER, 249, 0) - # divider.set_style_radius(2, lv.PART.MAIN) - # divider.set_style_bg_color(lv.color_hex(0xE5E5E5), lv.PART.MAIN) # Step 1 img_bg_1 = lv.image(parent) diff --git a/m5stack/modules/startup/tab5/launcher/launcher.py b/m5stack/modules/startup/tab5/launcher/launcher.py index fbebd339..0f73574f 100644 --- a/m5stack/modules/startup/tab5/launcher/launcher.py +++ b/m5stack/modules/startup/tab5/launcher/launcher.py @@ -18,6 +18,7 @@ from .common import Ezdata import lvgl as lv import asyncio +import M5 class Launcher: @@ -111,7 +112,10 @@ async def _main(self): # Start ezdata service Ezdata.start() - # Keep app manager running - while True: - await asyncio.sleep_ms(50) - await AppManager.update() + try: + # Keep app manager running + while True: + await asyncio.sleep_ms(50) + await AppManager.update() + except KeyboardInterrupt: + M5.Lcd.lvgl_deinit() From 329ad2f4a5fe8e8d9d78f0cf737750b57ef2e312 Mon Sep 17 00:00:00 2001 From: Forairaaaaa Date: Mon, 14 Jul 2025 15:16:07 +0800 Subject: [PATCH 172/322] m5stack/modules/startup/tab5: Improve ezdata guide page render speed. Signed-off-by: Forairaaaaa --- .../boards/M5STACK_Tab5/mpconfigboard.cmake | 1 + m5stack/fs/system/tab5/icons/guide_camera.bin | Bin 0 -> 11036 bytes m5stack/fs/system/tab5/icons/guide_folder.bin | Bin 0 -> 7436 bytes m5stack/fs/system/tab5/icons/guide_phone.bin | Bin 0 -> 11036 bytes .../system/tab5/utils/ezdata_guide_bg_1.png | Bin 1732 -> 0 bytes .../system/tab5/utils/ezdata_guide_bg_2.png | Bin 2297 -> 0 bytes .../system/tab5/utils/ezdata_guide_bg_3.png | Bin 1981 -> 0 bytes .../startup/tab5/launcher/apps/app_ezdata.py | 6 +- .../tab5/launcher/apps/app_ezdata_settings.py | 53 +++++++++++++++--- 9 files changed, 48 insertions(+), 12 deletions(-) create mode 100644 m5stack/fs/system/tab5/icons/guide_camera.bin create mode 100644 m5stack/fs/system/tab5/icons/guide_folder.bin create mode 100644 m5stack/fs/system/tab5/icons/guide_phone.bin delete mode 100644 m5stack/fs/system/tab5/utils/ezdata_guide_bg_1.png delete mode 100644 m5stack/fs/system/tab5/utils/ezdata_guide_bg_2.png delete mode 100644 m5stack/fs/system/tab5/utils/ezdata_guide_bg_3.png diff --git a/m5stack/boards/M5STACK_Tab5/mpconfigboard.cmake b/m5stack/boards/M5STACK_Tab5/mpconfigboard.cmake index d0b3fcb8..87c582f1 100644 --- a/m5stack/boards/M5STACK_Tab5/mpconfigboard.cmake +++ b/m5stack/boards/M5STACK_Tab5/mpconfigboard.cmake @@ -32,6 +32,7 @@ set(LV_CFLAGS -DLV_FONT_MONTSERRAT_20=1 -DLV_FONT_MONTSERRAT_18=1 -DLV_FONT_MONTSERRAT_14=1 + -DLV_FONT_MONTSERRAT_30=1 ) if(NOT MICROPY_FROZEN_MANIFEST) diff --git a/m5stack/fs/system/tab5/icons/guide_camera.bin b/m5stack/fs/system/tab5/icons/guide_camera.bin new file mode 100644 index 0000000000000000000000000000000000000000..6c99f6261148e068cc47af085d50fb913fbaf638 GIT binary patch literal 11036 zcmeI1O>*2I5QT;QD>v{ta)Io!$uUy#c$Xvi8eZEzM=p@-B;#EIq8nrJ8U|3Mk_ot` zq>%)l-fL)JeDkjBK5u_r_v88Q{im-VyYu7egZ|wAd^&wUKi)o^pHAOip6|Zs<1byh z_GuopK5%_~K(*2wXbv<7uE>FY5KDiM%wdg`;Sfj^v93oz`iO|_)&X|SDaKswR>CRs z0<~_jQzI-SvC=JaYV2T{ETqmYb_x>oMG}(lR__WU7KuSZR#&6Hz_$T)Bf%*HW(Wz| z4s+Byes8~qK9^LO>$^wkt8AWt5S=m%Bt(NGDFPzmR0M*R@KD&RHx0$g{`r_)DB9~f zMV&J2WrP&u7RyLC8WW|F93jw)iIIHmIKkW{2#AP{cp|`QfO*222N*USe}XYx_NMWt z5<30@#xj|*BsxZgj){Av9uH#-Q_PrbO*h^mlv0MLfiYqqEMqL(?wg||rHFwMnunuO z!tfEZE}j@TP)ZoKj&6CNJl8u6j7b55!|i8lM?r%&pTSP;avaZ^z^!}+IpwR^R=5NK zLh>0l!T~}l3VkRfn?Y8ASxpp@&9D`K3>FfabtHp?1ZHJ3tn0ukrDzH8PcSItct2(} z21)CG^@h)l+3}#P3xtTP*K7vwl)|1RS(RFghKWI3BGV`b zvsAtg7P51T5NJv-V4!(WF#^aa@XFh}@_gs?F*;9)M_4hUkO4QZ(o(dPq4%r40CO&gDE4Ygd;UgzlnJHr;MBTI`u8}! zlj86ASQc~KmUOi-Bxl0wSMQZg)-aETZp5#MUeSaQuYJ*7$_TqT&CJ!b0uYnXMa`Rs zI31uR@;)Eceh(KJ>UD0bZQRyJGL-VBw|Wd*^l52FLXcD2wZkBwPdTXD+EviBaoCZ)T literal 0 HcmV?d00001 diff --git a/m5stack/fs/system/tab5/icons/guide_folder.bin b/m5stack/fs/system/tab5/icons/guide_folder.bin new file mode 100644 index 0000000000000000000000000000000000000000..36245cc245eb355eeafe8d0ca82a4607760ffff4 GIT binary patch literal 7436 zcmeH~%~8WJ5QXKRV1^E;g9f;9;XoDO+$e!II47xt251M&4eYg7Ye`R%?dTFl$|SZ{ z+Ar^|B|EvjQR<0aN`0O7caN_RsynoA-QnfEJGLMF`Th5?{roxYpZorA-&B6*8ss(b z|JFc}TdqK^K(4^WRiLb|omQ8dZ2;PU-wG@pZ5-G{+;KKIZUOq`^JmWPq$+LJv;rVqDUK2_QkIjI><2yMSL@@IL>JIk(= zPwx*)Hj)BkcHX2fm0i}C$}aP4+0YvZI(ehtWOgTg$?Q%%M>epALT(=UrSNP~qEr;4 zWHV2Avqgz=QOvp*3{!-M5+X6zuJBCR5zsgc>=B{3`G~O<03c6IdXw?X>yEA20D(wT@ucbQ!%7VytY0CnX&X$xbS>2paDz# z)zhvE#M5Ib@6)4{%lYv9c6=?@&;85zJYB!`-#_QW$LN1YD|(87_P}=cz)mItD-)=- z4_fVVOREEA85L4u7<#&to+@GrW<}^Kno6ZA>G&#I2BiT~HDD^#mZ-d2(bbQ-;0&c2oYih622EJpAjO&3M70lRz4#{h!sfq zUaWjZh!87~@V!|1j1aLREBfa|W&mP!NCS;s@Ec_q zHGWm5LCi+e8bf?|H{TMdsW3*ueQcI`3t#B*|4aO>s%6CMZi-kLrA72_stTiILWJr@ dPK2$Zsn~Xm7Lt)@D)Ik*Bo!uNdJ%yXfj^#}LAU?_ literal 0 HcmV?d00001 diff --git a/m5stack/fs/system/tab5/utils/ezdata_guide_bg_1.png b/m5stack/fs/system/tab5/utils/ezdata_guide_bg_1.png deleted file mode 100644 index 8aec095699482540fb323dc0b6ed4b7223a137e8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1732 zcmeAS@N?(olHy`uVBq!ia0y~yU}9rnV3g-z28mqw7Xzdii-X*q7;m1`sRwfA2l#}z z0_p$6fmfgY9l!f$<>_A&_x`Nk^rL9i_lzaqpMU(f>EiFEEkAC({5Rv^&)1*-%{}t# zz|B87%f4qX`@ZnluMgk;_3Zlj=JUVC%|EwX`rWtt=gk*?H(&hy@a^BjxBn2d`I(5} z9H0|TOM?7@8T=<4e)i|TVB4FNcT<<*(|T@h4A=L5i7y$`mQ}L^7&9=iPV#hd45^s& z_U_A~TNwgvftA_oZ%(zHEIXt2?BDp?H)Q>dI^TLM`tc%R{^#q`>q zy_*K}$!wZ6J+qKl^Ywrl%+iJC4 zhP%W6c=O^{vR6)hZmvylH^1}o=I7StSE3=-7H1EeIGkuuVRm9ukrXgeIN30P(UYx( z$HPb=#UVkOI`}@0li>sf`j@W%t(x&Fij{lb)yYoluB=@SNeRz^t zKXbYNtu@KMYN;RI{9GzHr}E#Lt4`Mvyx8>Xc5G_eSO0U-O}AOICmdXz?^5tAboW#f zGppw7`D|J*ew(k4)?2!8qvW1i^9aZL`>Su4XrJvl^Wf_YEtX6V^ZRE` z^`3ZeatYV3d%wA7r@OF;-}kMm&nwGh^S`}5?P!~EMa9RydGr6j{QI<>`Mq`ioQFJe zzULkUUOsAKFz4X)@R@UF&9zlJv;NG3!rwR7TuWfvuD^e^zrVn`GYiUNZ!MEN^WdD2 z8)(WJNCPsZ(sg!HCk|Y7 zmrh@qEy!+Ep!?`p5}0fPZgYYMj+KEA6uhNvyT@t7EF?hNcEz@*v!|Ef60wU7T^=GnD>b$Y<-72f|t z-Y>Z1S^m!XtI(^O7j~8_?>SAK{I0%l&$1e2S^3iV8}=4!<{jB3EBB^cJSX@4n|Go5 l$+q9h%C~K|yZ`+^Q){665`Ch|(Y;6_cFk zD4m!gj+)GrcVk3;L>D7&bE=V$84NS`>D({(cR!qazMTDK?X~v$|My<&xA$86lDn&u zvXYh(007D^&JL#lKn?)_FkMCXSIbMOA0%LLr=09z6MeW0NU3!{?&%0g*`FI9mZd#Y zoAm=5S6S;BjJ5Ok#o>3wf+Zue7fV zSEO0Y^};9X%_EYD-?pOa#LQvI#m8&23tM-4H(tEi{J;Cpme|xm2R(e@l&c5eR{Y}K zrcC9cko>8v_K%^wDnBK0k>;21@;VZVNdN$3hl_)qXJX~Tlpx`4zp8DJieGn7x653g zN+juXU(mE{c*m$7y4^6Vy&g3^^C+cgp(O&9QeRfgh^l|Ju<9m1^xUOme1ZHAS7g{& z>Fi4$;+~rcp`%e}8)`pq+@!5p2kE`vNBwCdT_GW$(KHj+iWH2+vCBW{Rx&7STz_t% z24c*EJxRI=6Ktomn7EI`At0ryA<|k-7|*1yDmIP}Y>a)w4zQWKsXuSPqyxqnxFXv2 zDl7-A?gi`-?qom@zRxVP7^a4{?FBA_7&739*kzVEX+yp4Gk&<0l0=`Gn)VB1frQ;V z&JBS9Qysk9$!RjIkW8p%nI*0qq9jSa{c?d&Y;Be(on;+f^K1+YlfEnsYqnVr3Oc+y#;;|J8 zY*F>ug#PMe$BIYWo9Y}5o{-&JC~eQv&kP8QIeC-n^A-hkoP`J^?9SLR&d+es?Cz$ z9#=3r$WLGKNGHnbA5bNVuk=gW(NWVOHR@_RU%YQWCKof$EsB7!AsKd1&o2ElVOeiF9%v#8-Int!U1Og<%3b zLxKEK=Y&}K2P|o(+FDD;_?jpCs(pd^k!W{sm_XV0?(pf(5hX&wYkf(e$ItgOBb7`U zV)gERrMBmgX*$~7A12<@?96;pbh+#y*e3beQS?kBc17c{89$&CH6h{658jUUW>@u{ zeJ)nRq(*MTj!-l^R#MoB(OQi8hTqHp%w!AR)#=>NG(_7}Ukd`CSq??oI+Xf7Eb&8KN^j`k+E2bcLG zYSmxb;Vy-^U-KyzG;BXEYRsPltE!qt=K70VlhbF(dD0VsTOUa7_+3$4d|R2|OAvLViS-+Ql73 zgMAp|I`r{gj0zcWIYRB};|6!78=&QjqY$MnyD!c$RC_U*4)}_wN<-|nEKtrF5AUgk zFP>C@T+Sehcq?7cl;xl~3Gqqk{EUT@nIeV-j+tn>KHLg4%Qs|!hxWVAEeIRS>x;bxHKtmiF`(9O7vKt<>`q4}V3S zL379Ddsq(BgI_U3MDR>EsmTIE-8Wgv>1ASz?SM-meE;3$tp`f%KqwGX>bg#iBw8w) z7|T_Fs2+l^wKVnM&NXO)%_}{0XDJb}TL$}S@qsc#UV?7>-g9w$3JSE-jF=ibCD!os z$p5AVOua>2w{Ym889`1F=tO$5&+;{Mi0xO| z%5S=Roikb0_hL4@St@i8Xn%mP$>=qD!aGSk1m%FKGd@e3hknlV-W(V`Jq! z_hdZwqL3}rjm3e1ygd!@LRBqhsuxAYAV*K#JZz&|^y4<#1TO=31#heMho%27ewF{z zZ_bza6N`Y9EuqFo71E>YTM@svu9*g>OqQ!iqC19XPo6Fh$?)9?i=_UD$k;k-tq|p3 z>5A`_HtE10^YE*WR`)huxG}iZOqWkc4?T8$jLGf~dHk+2u77BxD@oM(p3hA-*gHAF sH-9toUbJBZ_1S~28?19-s}nNhaYr)(Ku+2Y{RRLRM^}em>`B@G2H5X2fB*mh diff --git a/m5stack/fs/system/tab5/utils/ezdata_guide_bg_3.png b/m5stack/fs/system/tab5/utils/ezdata_guide_bg_3.png deleted file mode 100644 index 3d4b215d602132936baf73951b5558908941ebc8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1981 zcmb7FeK^x=AODSxSm?+|UE!pjOma$MWj5Mq-p;lV(Y#bLMW<_ui5RskBbz#UBr~Jw zP=~zK$dJQAC&a-M^HTFNAu=zUZRT0m`TsoUx&QdS-QUmm`rO|?K6g4Ei_rt`1Oot| z=i%<^0|1+#>Ns0lQ*9lbu7{}|=&BC}1sd!)Kv5@Lytkj5+Hd^hQ{AJ;s$YwIEFGL) zf7~wr^kscUqG}sami{5nZdjvK$zsZ+Ekb45OPP34Rn?=oStA?ztjcX(n_W_|TIH_? zmC+^As1hmXjr{AXD)ZIa+>$D_dab5c@%I!a_rtW+L}Ax_u)Y9}*)pZvFi4vVpU>fz z{5`OXMF2|~Cd-iz?(#fv0HAft!}YjdeEye_zyP7Aamq>Chn1T9bbNnq&v$W`HXu|R z?12v^;n5u!<2ydb5}PJcxzEr8lI=N!dvsCrko?Wm01)>4MO|g1|GUA_!JiO2-XE-o z3mNcA9y?)gA!Sus0w*yOb``Sq-(rOL_fu~ZDmqi?Xz31hv(QrCOP z=J3)dfc&!90kVn%wtO)b1kLP7X*^>+;L8A(M-nQt6E+ntIM6%$_5ew|z}irVcC=Bc zB_PK^QKWq z+z~Q}y4;^uL`Ys%X{4zq%%BZQieTSbJ`~|yxks@GB~@KEPP2fzb#dYoV-n($NeVAQ zKATm*Pg;E##yy}fcm!Jr36K}#q8MECqDs=@y@Vt*E}T1j+TbMAm~|<9C!N9G-_1ZK z%|$q#1~mmkk(AP4&!n~Cek!wW%mxzLWK%mWS&t&xp$3wx@ygTjv2jP0JtMy`D>0m3 z>tX`Qwo8$GH@kJR`CT+A*7VxBqx{C17@zh&Kj`SXL2_mp7g-&-lg@_eB|m(`W_5uB z_oElfFYN36chOmBdEPbAOGuYhnEwv?^k|k_uS#DJZBPN9Cy?Pb;lJt%3>~xM84=ZZ z*ZkGAy<^qqcu^C6CBwqSLqEeFatp?`cg7i?9`=xL8t{}Y>s~u zV}`O-QEOydhRmdydDNn0=ZKtxtZv=Fx~+ovNm8FgX55B&qQ#Nb`dOx-rAe=A6nLkL zlRX<17~+DaV&ww?ii~x(51&9n#u=E5aiN(sUxeqg^_s`c1Ng(O!Qw*~w$bwg%=A#C zx@*uM;dw??M|9k}tQlII+$3ygU+?+l+nvhZ^D>U2a<}sY8 z^bw|8J;|K*gh>OM=rIz~X3sJy;I~dN)9dl!4eDM}kT5;;Ma4yAQhLX3Lo&yNawKTi z$ZD#dxlq3_d+^VqDW*B_Y~harRIP9i+Pk5{;g znratp(bcbmqI*OiU$!Bt4?*m0Ovc{&SK3ssG;MxXM#E3kOX7o|TCqeU)2({Pu4}tB zS~CVXxzW6`I&9C{2PUJ%^2U#H$@NQ#cSYsKZT(}+S0_MR1wNcFiQM?Dt>sDx$@9~h zta8muP|O4sF~$9|{K!Vb0u0Dk{|}s3 zhGt@=Byy4Zw8Q`D_?cp7>&E4;RO{TsIs(Z-)y4XApItJD5vn}-CsKd1b@w}}70t-} z-jolH@;JmXPj^`JRPQ1laCDQ`WKU)y`I5e&;T2d$8amW*n=Wjt}`EHG*S8fX%hW^8VED zIHD`)|EU-|Oo)ZsFkM^ly2%b!g3fL%@w;sochZ|r@#VE~NYK+#5>#ZJSAPEYvLFk{ zt%@<@aM7a$x3d>)4C&2cIU+A~uKH&G3qPpVEyfc0&fOa^l+Dko{)M(R;WyXQX z%a9FsgvNXFtbkpL0pvgbCYxoK64h$VgVHHnGmU8;ONdu0oWCh_G)3V65m0;?pyk03 z+tsGNtH63J-ka}<(0LzFLEpT&ln9c4F3hbWTEY$yDHZYIiA30nyGzsDeDwCK0|PJz no)1oIxp`pfjXWrjwxQYKKRzmYXYq^rM*}?Eu&(8(u$%t{ewX!9 diff --git a/m5stack/modules/startup/tab5/launcher/apps/app_ezdata.py b/m5stack/modules/startup/tab5/launcher/apps/app_ezdata.py index d73d43c2..7890373d 100644 --- a/m5stack/modules/startup/tab5/launcher/apps/app_ezdata.py +++ b/m5stack/modules/startup/tab5/launcher/apps/app_ezdata.py @@ -64,7 +64,7 @@ def __init__(self, parent: lv.obj, data: dict): label.set_text(str(data.get("value"))) label.align(lv.ALIGN.CENTER, 0, -10) label.set_style_text_color(lv.color_hex(0x272F43), lv.PART.MAIN) - label.set_style_text_font(lv.font_montserrat_32, lv.PART.MAIN) + label.set_style_text_font(lv.font_montserrat_30, lv.PART.MAIN) label.set_style_text_align(lv.TEXT_ALIGN.CENTER, lv.PART.MAIN) @@ -76,7 +76,7 @@ def __init__(self, parent: lv.obj, data: dict): label.set_text(str(data.get("value"))) label.align(lv.ALIGN.CENTER, 0, -10) label.set_style_text_color(lv.color_hex(0x272F43), lv.PART.MAIN) - label.set_style_text_font(lv.font_montserrat_32, lv.PART.MAIN) + label.set_style_text_font(lv.font_montserrat_30, lv.PART.MAIN) label.set_style_text_align(lv.TEXT_ALIGN.CENTER, lv.PART.MAIN) @@ -95,7 +95,7 @@ def __init__(self, parent: lv.obj, data: str, pos_y: int): label.set_text(data) label.align(lv.ALIGN.LEFT_MID, 34, 0) label.set_style_text_color(lv.color_hex(0x272F43), lv.PART.MAIN) - label.set_style_text_font(lv.font_montserrat_32, lv.PART.MAIN) + label.set_style_text_font(lv.font_montserrat_30, lv.PART.MAIN) def __init__(self, parent: lv.obj, data: list): super().__init__(parent, data) diff --git a/m5stack/modules/startup/tab5/launcher/apps/app_ezdata_settings.py b/m5stack/modules/startup/tab5/launcher/apps/app_ezdata_settings.py index 8511aeb4..8018c6f0 100644 --- a/m5stack/modules/startup/tab5/launcher/apps/app_ezdata_settings.py +++ b/m5stack/modules/startup/tab5/launcher/apps/app_ezdata_settings.py @@ -9,19 +9,48 @@ class AppEzdataSettings(AppBase): + def create_panel(self, parent: lv.obj, pos: tuple[int, int], size: tuple[int, int]): + panel = lv.obj(parent) + panel.set_size(size[0], size[1]) + panel.align(lv.ALIGN.TOP_LEFT, pos[0], pos[1]) + panel.set_style_bg_color(lv.color_hex(0xFFFFFF), lv.PART.MAIN) + panel.set_scrollbar_mode(lv.SCROLLBAR_MODE.OFF) + panel.set_style_border_width(0, lv.PART.MAIN) + panel.set_style_radius(15, lv.PART.MAIN) + panel.remove_flag(lv.obj.FLAG.SCROLLABLE) + + def create_badge(self, parent: lv.obj, text: str, pos: tuple[int, int]): + panel = lv.obj(parent) + panel.set_size(53, 53) + panel.align(lv.ALIGN.TOP_LEFT, pos[0], pos[1]) + panel.set_style_bg_color(lv.color_hex(0x68A4F7), lv.PART.MAIN) + panel.set_scrollbar_mode(lv.SCROLLBAR_MODE.OFF) + panel.set_style_border_width(0, lv.PART.MAIN) + panel.set_style_radius(233, lv.PART.MAIN) + panel.remove_flag(lv.obj.FLAG.SCROLLABLE) + + label = lv.label(panel) + label.set_text(text) + label.align(lv.ALIGN.CENTER, 0, 0) + label.set_style_text_font(lv.font_montserrat_30, lv.PART.MAIN) + label.set_style_text_color(lv.color_hex(0xFFFFFF), lv.PART.MAIN) + async def main(self): parent = self.get_app_panel() # Step 1 - img_bg_1 = lv.image(parent) - img_bg_1.set_src(get_hal().get_asset_path("utils/ezdata_guide_bg_1.png")) - img_bg_1.align(lv.ALIGN.TOP_LEFT, 33, 20) + self.create_panel(parent, (33, 20), (518, 287)) + self.create_badge(parent, "1", (65, 52)) label_1 = lv.label(parent) label_1.set_text('Install "Ez Data" from App Store') label_1.align(lv.ALIGN.LEFT_MID, 132, -171) label_1.set_style_text_font(lv.font_montserrat_24, lv.PART.MAIN) + img_icon_pohone = lv.image(parent) + img_icon_pohone.set_src(get_hal().get_asset_path("icons/guide_phone.bin")) + img_icon_pohone.align(lv.ALIGN.TOP_LEFT, 132, 150) + install_url = "https://apps.apple.com/us/app/ezdata/id6738713869" qr_install = lv.qrcode(parent) qr_install.align(lv.ALIGN.CENTER, -190, -50) @@ -30,9 +59,12 @@ async def main(self): qr_install.update(install_url, len(install_url)) # Step 2 - img_bg_2 = lv.image(parent) - img_bg_2.set_src(get_hal().get_asset_path("utils/ezdata_guide_bg_2.png")) - img_bg_2.align(lv.ALIGN.TOP_LEFT, 579, 20) + self.create_panel(parent, (579, 20), (518, 287)) + self.create_badge(parent, "2", (617, 52)) + + img_icon_camera = lv.image(parent) + img_icon_camera.set_src(get_hal().get_asset_path("icons/guide_camera.bin")) + img_icon_camera.align(lv.ALIGN.TOP_LEFT, 688, 150) label_2 = lv.label(parent) label_2.set_text('Scan with the "Ez Data" app\nto Add device') @@ -47,9 +79,12 @@ async def main(self): qr_add.update(add_user_qr_code, len(add_user_qr_code)) # Step 3 - img_bg_3 = lv.image(parent) - img_bg_3.set_src(get_hal().get_asset_path("utils/ezdata_guide_bg_3.png")) - img_bg_3.align(lv.ALIGN.TOP_LEFT, 33, 334) + self.create_panel(parent, (33, 334), (1064, 145)) + self.create_badge(parent, "3", (65, 380)) + + img_icon_folder = lv.image(parent) + img_icon_folder.set_src(get_hal().get_asset_path("icons/guide_folder.bin")) + img_icon_folder.align(lv.ALIGN.TOP_LEFT, 894, 353) mac = "".join(f"{byte:02x}" for byte in get_hal().get_mac()) label_3 = lv.label(parent) From 17be95664ddf89da19d2ed7fe2500ba7d2b09967 Mon Sep 17 00:00:00 2001 From: Forairaaaaa Date: Mon, 14 Jul 2025 15:34:26 +0800 Subject: [PATCH 173/322] m5stack/modules/startup/tab5: Disbale status bar power icon. Signed-off-by: Forairaaaaa --- m5stack/modules/startup/tab5/hal_tab5.py | 9 +++------ .../startup/tab5/launcher/components/status_bar.py | 10 +++++----- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/m5stack/modules/startup/tab5/hal_tab5.py b/m5stack/modules/startup/tab5/hal_tab5.py index 13c5083d..6008e0d0 100644 --- a/m5stack/modules/startup/tab5/hal_tab5.py +++ b/m5stack/modules/startup/tab5/hal_tab5.py @@ -85,16 +85,13 @@ def set_charge_mode(self, mode: ChargeMode): M5.Power.setChargeCurrent(1000) def get_battery_level(self) -> int: - """TODO""" - return 0 + return M5.Power.getBatteryLevel() def get_output_current(self) -> float: - """TODO""" - return 0.0 + return M5.Power.getBatteryCurrent() def is_charging(self) -> bool: - """TODO""" - return False + return M5.Power.isCharging() def get_network_status(self) -> NetworkStatus: status = self._wifi.connect_status() diff --git a/m5stack/modules/startup/tab5/launcher/components/status_bar.py b/m5stack/modules/startup/tab5/launcher/components/status_bar.py index a933a74f..5ea78e65 100644 --- a/m5stack/modules/startup/tab5/launcher/components/status_bar.py +++ b/m5stack/modules/startup/tab5/launcher/components/status_bar.py @@ -104,13 +104,13 @@ def __init__(self, parent: lv.obj): self._label_output_current.set_style_text_font(lv.font_montserrat_20, lv.PART.MAIN) self._label_output_current.align(lv.ALIGN.CENTER, -9, 19) - # self._update_img() - # self._update_labels() + self._update_img() + self._update_labels() - # # TODO: Update task - # self._img.set_style_image_recolor_opa(123, lv.PART.MAIN) + # TODO: Update task + self._img.set_style_image_recolor_opa(123, lv.PART.MAIN) - self._task = asyncio.create_task(self.update_task()) + # self._task = asyncio.create_task(self.update_task()) async def update_task(self): while True: From 0fb2acb1f9c64aff315a2fa9ec400de389cc348b Mon Sep 17 00:00:00 2001 From: Forairaaaaa Date: Wed, 16 Jul 2025 16:01:18 +0800 Subject: [PATCH 174/322] modules/startup/tab5: Adapte new ws api. Signed-off-by: Forairaaaaa --- .../startup/tab5/launcher/apps/app_ezdata.py | 38 ++++++++++++- .../startup/tab5/launcher/common/ezdata.py | 56 +++++++++++++------ 2 files changed, 75 insertions(+), 19 deletions(-) diff --git a/m5stack/modules/startup/tab5/launcher/apps/app_ezdata.py b/m5stack/modules/startup/tab5/launcher/apps/app_ezdata.py index 7890373d..25ec94af 100644 --- a/m5stack/modules/startup/tab5/launcher/apps/app_ezdata.py +++ b/m5stack/modules/startup/tab5/launcher/apps/app_ezdata.py @@ -4,7 +4,6 @@ from .app import AppBase from ..common import Ezdata, debug_print -from ..hal import get_hal import lvgl as lv import asyncio import time @@ -114,6 +113,40 @@ def __init__(self, parent: lv.obj, data: list): self._Item(panel, str(item), i * 84) +class ViewDict(ViewBase): + class _Item: + def __init__(self, parent: lv.obj, data: str, pos_y: int): + panel = lv.obj(parent) + panel.set_size(1044, 55) + panel.align(lv.ALIGN.TOP_LEFT, 0, pos_y) + panel.set_style_radius(16, lv.PART.MAIN) + panel.set_style_bg_color(lv.color_hex(0xE0EDFF), lv.PART.MAIN) + panel.set_style_border_width(0, lv.PART.MAIN) + panel.set_style_pad_all(0, lv.PART.MAIN) + + label = lv.label(panel) + label.set_text(data) + label.align(lv.ALIGN.LEFT_MID, 34, 0) + label.set_style_text_color(lv.color_hex(0x272F43), lv.PART.MAIN) + label.set_style_text_font(lv.font_montserrat_30, lv.PART.MAIN) + + def __init__(self, parent: lv.obj, data: dict): + super().__init__(parent, data) + + panel = lv.obj(parent) + panel.set_size(1092, 429) + panel.align(lv.ALIGN.CENTER, 0, -20) + panel.set_style_bg_opa(lv.OPA.TRANSP, lv.PART.MAIN) + panel.set_style_border_width(0, lv.PART.MAIN) + panel.set_style_pad_all(0, lv.PART.MAIN) + panel.set_style_pad_left(24, lv.PART.MAIN) + panel.set_style_pad_top(28, lv.PART.MAIN) + + data_dict = data.get("value") + for i, (key, value) in enumerate(data_dict.items()): + self._Item(panel, f"{str(key)}: {str(value)}", i * 84) + + class AppEzdata(AppBase): async def main(self): self._view = None @@ -148,8 +181,11 @@ def _create_view(self): self._view = ViewNumber(self.get_app_panel(), data) elif isinstance(value, list): self._view = ViewArray(self.get_app_panel(), data) + elif isinstance(value, dict): + self._view = ViewDict(self.get_app_panel(), data) else: print("unsupported data type:", type(value)) def _handle_data_changed(self): + debug_print("data changed") self._create_view() diff --git a/m5stack/modules/startup/tab5/launcher/common/ezdata.py b/m5stack/modules/startup/tab5/launcher/common/ezdata.py index ccaa8719..c903ba24 100644 --- a/m5stack/modules/startup/tab5/launcher/common/ezdata.py +++ b/m5stack/modules/startup/tab5/launcher/common/ezdata.py @@ -26,10 +26,25 @@ class _EzdataClientWebsocket: DEVICE_DATA = 104 DEVICE_DATA_FILE = 105 DEVICE_REQUEST_ERROR = 500 - - def __init__(self, device_token: str, on_data_changed: Signal): + DEVICE_USER_SCAN = 106 + DEVICE_USER_UPDATE_DATA = 107 + DEVICE_USER_DELETE_DATA = 108 + DEVICE_USER_ADD_DATA = 109 + + DATA_CHANGE_TYPE_LIST = [ + DEVICE_ADD_DATA, + DEVICE_UPDATE_DATA, + DEVICE_DELETE_DATA, + DEVICE_DATA_LIST, + DEVICE_USER_UPDATE_DATA, + DEVICE_USER_DELETE_DATA, + DEVICE_USER_ADD_DATA, + ] + + def __init__(self, device_token: str, on_data_changed: Signal, on_data_list_changed: Signal): self._device_token = device_token self._on_data_changed = on_data_changed + self._on_data_list_changed = on_data_list_changed self._ws: WebsocketClient | None = None self._ws_last_heartbeat_time = 0 self._data = {} @@ -83,6 +98,8 @@ async def fetch_all_data(self): if self._ws is None: return + debug_print("fetch all data") + self._data.clear() for i in range(10): @@ -102,22 +119,24 @@ async def fetch_all_data(self): response = json.loads(resp) if response.get("code") == 200: self._data = response.get("body") - # debug_print("fetch first data success:", self._data) + # debug_print("fetch all data success:", self._data) + # pretty print + return except Exception as e: - debug_print("fetch first data failed:", e) + debug_print("fetch all data failed:", e) await asyncio.sleep(1) continue self._data.clear() - raise Exception("max fetch data retry") + raise Exception("max fetch all data retry") - def update(self): - self._receive() + async def update(self): + await self._receive() self._heartbeat() - def _receive(self): + async def _receive(self): if self._ws is None: return @@ -126,7 +145,7 @@ def _receive(self): if not msg: return - # debug_print("ws recv:", msg) + debug_print("ws recv:", msg) # Skip heartbeat if msg == "pong": @@ -136,14 +155,13 @@ def _receive(self): response = json.loads(msg) if response.get("code") == 200: cmd = response.get("cmd") - if cmd in [ - self.DEVICE_ADD_DATA, - self.DEVICE_UPDATE_DATA, - self.DEVICE_DELETE_DATA, - self.DEVICE_DATA_LIST, - ]: - self.fetch_all_data() + if cmd in self.DATA_CHANGE_TYPE_LIST: + await self.fetch_all_data() self._on_data_changed.emit() + self._on_data_list_changed.emit() + else: + debug_print("unhandled cmd:", cmd) + else: print("ws recv error msg:", msg) @@ -219,14 +237,16 @@ async def _ezdata_task(): # Create ezdata websocket client and init Ezdata._client = _EzdataClientWebsocket( - Ezdata._device_token, Ezdata.on_selected_data_changed + Ezdata._device_token, + Ezdata.on_selected_data_changed, + Ezdata.on_data_list_changed, ) await Ezdata._client.init() Ezdata._set_state(Ezdata.State.NORMAL) # Keep ezdata websocket client running while True: - Ezdata._client.update() + await Ezdata._client.update() await asyncio.sleep(0.2) except Exception as e: From 4f57b7cc6cc36c9532f29b7e9bd48b27be07652a Mon Sep 17 00:00:00 2001 From: Forairaaaaa Date: Thu, 24 Jul 2025 15:09:54 +0800 Subject: [PATCH 175/322] modules/startup/tab5: Add data num badge, img file support. Signed-off-by: Forairaaaaa --- m5stack/boards/M5STACK_Tab5/mpconfigboard.h | 1 + m5stack/modules/startup/tab5/hal_tab5.py | 14 +- .../startup/tab5/launcher/apps/app_ezdata.py | 167 +++++++++++++++++- .../tab5/launcher/components/ezdata_dock.py | 35 ++++ m5stack/modules/startup/tab5/launcher/hal.py | 9 + 5 files changed, 224 insertions(+), 2 deletions(-) diff --git a/m5stack/boards/M5STACK_Tab5/mpconfigboard.h b/m5stack/boards/M5STACK_Tab5/mpconfigboard.h index cba13ce2..0eb6d6b2 100644 --- a/m5stack/boards/M5STACK_Tab5/mpconfigboard.h +++ b/m5stack/boards/M5STACK_Tab5/mpconfigboard.h @@ -17,3 +17,4 @@ // #endif #define MICROPY_GC_INITIAL_HEAP_SIZE (128 * 1024) +#define MICROPY_CACHE_SIZE (1024) diff --git a/m5stack/modules/startup/tab5/hal_tab5.py b/m5stack/modules/startup/tab5/hal_tab5.py index 6008e0d0..720d54f3 100644 --- a/m5stack/modules/startup/tab5/hal_tab5.py +++ b/m5stack/modules/startup/tab5/hal_tab5.py @@ -50,7 +50,19 @@ def _save_network_config_to_nvs(self): nvs.commit() def get_asset_path(self, asset_path: str) -> str: - return "S:" + "/system/tab5/" + asset_path + return "S:/system/tab5/" + asset_path + + def create_temp_dir(self): + try: + os.stat("tmp") + except OSError: + os.mkdir("tmp") + + def get_lvgl_temp_file_path(self, file_name: str) -> str: + return "S:/flash/tmp/" + file_name + + def get_os_temp_file_path(self, file_name: str) -> str: + return "tmp/" + file_name def play_click_sfx(self): M5.Speaker.playWavFile("/system/common/wav/click.wav") diff --git a/m5stack/modules/startup/tab5/launcher/apps/app_ezdata.py b/m5stack/modules/startup/tab5/launcher/apps/app_ezdata.py index 25ec94af..f91688c0 100644 --- a/m5stack/modules/startup/tab5/launcher/apps/app_ezdata.py +++ b/m5stack/modules/startup/tab5/launcher/apps/app_ezdata.py @@ -4,9 +4,12 @@ from .app import AppBase from ..common import Ezdata, debug_print +from ..hal import get_hal import lvgl as lv import asyncio +import requests import time +import os class ViewBase: @@ -147,6 +150,164 @@ def __init__(self, parent: lv.obj, data: dict): self._Item(panel, f"{str(key)}: {str(value)}", i * 84) +class WidgetImage: + _panel: lv.obj | None = None + _img: lv.image | None = None + _is_maximized: bool = False + + @staticmethod + def is_maximized(): + return WidgetImage._is_maximized + + @staticmethod + def create(img_src: str): + WidgetImage.destroy() + + WidgetImage._panel = lv.obj(lv.screen_active()) + WidgetImage._apply_size_style() + WidgetImage._panel.set_style_border_width(0, lv.PART.MAIN) + WidgetImage._panel.set_style_pad_all(0, lv.PART.MAIN) + WidgetImage._panel.set_style_radius(0, lv.PART.MAIN) + WidgetImage._panel.set_scrollbar_mode(lv.SCROLLBAR_MODE.ACTIVE) + WidgetImage._panel.add_event_cb(WidgetImage._handle_on_click, lv.EVENT.CLICKED, None) + + WidgetImage._img = lv.image(WidgetImage._panel) + WidgetImage._img.set_src(img_src) + WidgetImage._img.align(lv.ALIGN.CENTER, 0, 0) + + @staticmethod + def _apply_size_style(): + if WidgetImage._is_maximized: + WidgetImage._panel.set_size(1280, 720) + WidgetImage._panel.align(lv.ALIGN.CENTER, 0, 0) + WidgetImage._panel.set_style_bg_color(lv.color_hex(0x000000), lv.PART.MAIN) + else: + WidgetImage._panel.set_size(1100, 430) + WidgetImage._panel.align(lv.ALIGN.TOP_LEFT, 35, 181) + WidgetImage._panel.set_style_bg_color(lv.color_hex(0xF6F6F6), lv.PART.MAIN) + + @staticmethod + def destroy(): + if WidgetImage._img: + WidgetImage._img.delete() + WidgetImage._img = None + if WidgetImage._panel: + WidgetImage._panel.delete() + WidgetImage._panel = None + + @staticmethod + def _handle_on_click(e: lv.event_t): + get_hal().play_click_sfx() + WidgetImage._is_maximized = not WidgetImage._is_maximized + WidgetImage._apply_size_style() + + +class ViewFile(ViewBase): + class _FileType: + UNSUPPORTED = 0 + PNG = 1 + JPG = 2 + + def __init__(self, parent: lv.obj, data: dict): + super().__init__(parent, data) + + self._parent = parent + self._url = data.get("value") + self._temp_path = None + + label_link = lv.label(parent) + label_link.set_text("URL: " + str(data.get("value"))) + label_link.align(lv.ALIGN.LEFT_MID, 20, 226) + label_link.set_style_text_color(lv.color_hex(0x9EA4B5), lv.PART.MAIN) + label_link.set_style_text_font(lv.font_montserrat_14, lv.PART.MAIN) + label_link.set_style_text_align(lv.TEXT_ALIGN.CENTER, lv.PART.MAIN) + + file_type = self._get_file_type(self._url) + if file_type == self._FileType.UNSUPPORTED: + self._handle_type_unsupported() + elif file_type == self._FileType.PNG: + self._handle_type_png() + elif file_type == self._FileType.JPG: + self._handle_type_jpg() + + def _get_file_type(self, file_name: str) -> _FileType: + if file_name.endswith(".png"): + return self._FileType.PNG + elif file_name.endswith(".jpg"): + return self._FileType.JPG + elif file_name.endswith(".jpeg"): + return self._FileType.JPG + else: + return self._FileType.UNSUPPORTED + + def _handle_type_unsupported(self): + self._handle_error("Unsupported file type\nCurrent supported file types: PNG, JPG") + + def _handle_type_png(self): + self._handle_type_image() + + def _handle_type_jpg(self): + self._handle_type_image() + + def _handle_type_image(self): + url = self._url + try: + + def get_file_name(url: str) -> str: + if "." in url: + suffix = "." + url.split(".")[-1].split("?")[0] + else: + raise Exception("Invalid file name") + return "ezdata_img" + suffix + + def download_image(file_name: str): + get_hal().create_temp_dir() + + temp_path = get_hal().get_os_temp_file_path(file_name) + + debug_print(f"temp_path: {temp_path}") + + # Get image from url + resp = requests.get(url, timeout=10) + if resp.status_code != 200: + raise Exception(f"HTTP {resp.status_code}") + + # Write image to file + with open(temp_path, "wb") as f: + f.write(resp.content) + + self._temp_path = temp_path + + def create_image(file_name: str): + path = get_hal().get_lvgl_temp_file_path(file_name) + + # Due to the same image path will be cached, cache drop is needed + lv.image.cache_drop(path) + + WidgetImage.create(path) + + file_name = get_file_name(url) + download_image(file_name) + create_image(file_name) + + except Exception as e: + self._handle_error(f"Image loading failed:\n{e}") + + def _handle_error(self, error_msg: str): + label = lv.label(self._parent) + label.set_text(error_msg) + label.align(lv.ALIGN.CENTER, 0, -32) + label.set_style_text_color(lv.color_hex(0x272F43), lv.PART.MAIN) + label.set_style_text_font(lv.font_montserrat_30, lv.PART.MAIN) + label.set_style_text_align(lv.TEXT_ALIGN.CENTER, lv.PART.MAIN) + + def cleanup(self): + WidgetImage.destroy() + if self._temp_path: + os.remove(self._temp_path) + super().cleanup() + + class AppEzdata(AppBase): async def main(self): self._view = None @@ -174,9 +335,13 @@ def _create_view(self): data = Ezdata.get_selected_data() value = data.get("value") + # print("data:", data) if isinstance(value, str): - self._view = ViewString(self.get_app_panel(), data) + if data.get("dataType") == "file": + self._view = ViewFile(self.get_app_panel(), data) + else: + self._view = ViewString(self.get_app_panel(), data) elif isinstance(value, int) or isinstance(value, float): self._view = ViewNumber(self.get_app_panel(), data) elif isinstance(value, list): diff --git a/m5stack/modules/startup/tab5/launcher/components/ezdata_dock.py b/m5stack/modules/startup/tab5/launcher/components/ezdata_dock.py index d85841b0..83537987 100644 --- a/m5stack/modules/startup/tab5/launcher/components/ezdata_dock.py +++ b/m5stack/modules/startup/tab5/launcher/components/ezdata_dock.py @@ -58,6 +58,32 @@ def _on_clicked(self, e: lv.event_t): AppManager.open_app("EzData") +class EzdataNumBadge: + def __init__(self): + self._badge = lv.obj(lv.screen_active()) + self._badge.align(lv.ALIGN.TOP_RIGHT, -820, 9) + self._badge.set_size(22, 22) + self._badge.set_style_border_width(0, lv.PART.MAIN) + self._badge.set_style_bg_color(lv.color_hex(0xFF5656), lv.PART.MAIN) + self._badge.set_scrollbar_mode(lv.SCROLLBAR_MODE.OFF) + + self._label = lv.label(self._badge) + self._label.set_text("") + self._label.align(lv.ALIGN.CENTER, 0, 0) + self._label.set_style_text_color(lv.color_hex(0xFFFFFF), lv.PART.MAIN) + self._label.set_style_text_font(lv.font_montserrat_14, lv.PART.MAIN) + + def update(self, data_num: int): + label_text = str(data_num) + self._label.set_text(label_text) + + width = 22 + if len(label_text) > 1: + width = 22 + (len(label_text) - 1) * 14 + + self._badge.set_width(width) + + class EzdataDock: def __init__(self): self._icons = [] @@ -76,6 +102,8 @@ def __init__(self): label_init.set_style_text_color(lv.color_hex(0xFFFFFF), lv.PART.MAIN) label_init.set_style_text_font(lv.font_montserrat_24, lv.PART.MAIN) + self._data_num_badge = None + self._task = asyncio.create_task(self._task()) async def _task(self): @@ -98,6 +126,8 @@ def _create_data_dock(self): # Setting icon self._icons.append(EzdataIcon(self._dock_panel, 0, "settings", "settings")) + self._create_data_num_badge(len(all_data)) + # If no data if len(all_data) == 0: label_no_data = lv.label(self._dock_panel) @@ -116,6 +146,11 @@ def _create_data_dock(self): ) ) + def _create_data_num_badge(self, data_num: int): + if self._data_num_badge is None: + self._data_num_badge = EzdataNumBadge() + self._data_num_badge.update(data_num) + def _handle_data_list_changed(self): debug_print("[EzdataDock] data list changed") self._create_data_dock() diff --git a/m5stack/modules/startup/tab5/launcher/hal.py b/m5stack/modules/startup/tab5/launcher/hal.py index 5431d514..fc39e9d4 100644 --- a/m5stack/modules/startup/tab5/launcher/hal.py +++ b/m5stack/modules/startup/tab5/launcher/hal.py @@ -60,6 +60,15 @@ def get_charge_mode(self) -> ChargeMode: def get_asset_path(self, asset_path: str) -> str: raise NotImplementedError() + def create_temp_dir(self): + raise NotImplementedError() + + def get_os_temp_file_path(self, file_name: str) -> str: + raise NotImplementedError() + + def get_lvgl_temp_file_path(self, file_name: str) -> str: + raise NotImplementedError() + def get_volume(self) -> int: return self._volume From d7e9ebf6d412240d238ad0a9f70556c81fbc0644 Mon Sep 17 00:00:00 2001 From: Forairaaaaa Date: Thu, 24 Jul 2025 18:20:47 +0800 Subject: [PATCH 176/322] boards/M5STACK_Tab5: Remove cache define. Signed-off-by: Forairaaaaa --- m5stack/boards/M5STACK_Tab5/mpconfigboard.h | 1 - 1 file changed, 1 deletion(-) diff --git a/m5stack/boards/M5STACK_Tab5/mpconfigboard.h b/m5stack/boards/M5STACK_Tab5/mpconfigboard.h index 0eb6d6b2..cba13ce2 100644 --- a/m5stack/boards/M5STACK_Tab5/mpconfigboard.h +++ b/m5stack/boards/M5STACK_Tab5/mpconfigboard.h @@ -17,4 +17,3 @@ // #endif #define MICROPY_GC_INITIAL_HEAP_SIZE (128 * 1024) -#define MICROPY_CACHE_SIZE (1024) From 586ca4808cc48992bdad838cf63624bbb6a6eea7 Mon Sep 17 00:00:00 2001 From: Forairaaaaa Date: Fri, 25 Jul 2025 14:27:33 +0800 Subject: [PATCH 177/322] modules/startup/tab5/: Fix long string wrapping. Signed-off-by: Forairaaaaa --- .../modules/startup/tab5/launcher/apps/app_ezdata.py | 12 ++++++++---- .../startup/tab5/launcher/components/ezdata_dock.py | 1 + 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/m5stack/modules/startup/tab5/launcher/apps/app_ezdata.py b/m5stack/modules/startup/tab5/launcher/apps/app_ezdata.py index f91688c0..300524bf 100644 --- a/m5stack/modules/startup/tab5/launcher/apps/app_ezdata.py +++ b/m5stack/modules/startup/tab5/launcher/apps/app_ezdata.py @@ -68,6 +68,7 @@ def __init__(self, parent: lv.obj, data: dict): label.set_style_text_color(lv.color_hex(0x272F43), lv.PART.MAIN) label.set_style_text_font(lv.font_montserrat_30, lv.PART.MAIN) label.set_style_text_align(lv.TEXT_ALIGN.CENTER, lv.PART.MAIN) + label.set_width(1100) class ViewNumber(ViewBase): @@ -169,6 +170,7 @@ def create(img_src: str): WidgetImage._panel.set_style_pad_all(0, lv.PART.MAIN) WidgetImage._panel.set_style_radius(0, lv.PART.MAIN) WidgetImage._panel.set_scrollbar_mode(lv.SCROLLBAR_MODE.ACTIVE) + WidgetImage._panel.remove_flag(lv.obj.FLAG.SCROLLABLE) WidgetImage._panel.add_event_cb(WidgetImage._handle_on_click, lv.EVENT.CLICKED, None) WidgetImage._img = lv.image(WidgetImage._panel) @@ -337,11 +339,13 @@ def _create_view(self): value = data.get("value") # print("data:", data) + # if isinstance(value, str): + # if data.get("dataType") == "file": + # self._view = ViewFile(self.get_app_panel(), data) + # else: + # self._view = ViewString(self.get_app_panel(), data) if isinstance(value, str): - if data.get("dataType") == "file": - self._view = ViewFile(self.get_app_panel(), data) - else: - self._view = ViewString(self.get_app_panel(), data) + self._view = ViewString(self.get_app_panel(), data) elif isinstance(value, int) or isinstance(value, float): self._view = ViewNumber(self.get_app_panel(), data) elif isinstance(value, list): diff --git a/m5stack/modules/startup/tab5/launcher/components/ezdata_dock.py b/m5stack/modules/startup/tab5/launcher/components/ezdata_dock.py index 83537987..74f9f6c4 100644 --- a/m5stack/modules/startup/tab5/launcher/components/ezdata_dock.py +++ b/m5stack/modules/startup/tab5/launcher/components/ezdata_dock.py @@ -66,6 +66,7 @@ def __init__(self): self._badge.set_style_border_width(0, lv.PART.MAIN) self._badge.set_style_bg_color(lv.color_hex(0xFF5656), lv.PART.MAIN) self._badge.set_scrollbar_mode(lv.SCROLLBAR_MODE.OFF) + self._badge.remove_flag(lv.obj.FLAG.SCROLLABLE) self._label = lv.label(self._badge) self._label.set_text("") From 0f2e91da563c3146a9fbfd5b229ceed678756df6 Mon Sep 17 00:00:00 2001 From: tinyu Date: Tue, 22 Jul 2025 12:03:10 +0800 Subject: [PATCH 178/322] lib/m5ui: M5Image adds rotation angle and zoom control. Signed-off-by: tinyu --- docs/en/refs/m5ui.image.ref | 2 + docs/locales/zh_CN/LC_MESSAGES/m5ui/image.po | 65 ++++++++++++++++---- m5stack/libs/m5ui/image.py | 46 ++++++++++++++ 3 files changed, 101 insertions(+), 12 deletions(-) diff --git a/docs/en/refs/m5ui.image.ref b/docs/en/refs/m5ui.image.ref index 79354172..139c48b2 100644 --- a/docs/en/refs/m5ui.image.ref +++ b/docs/en/refs/m5ui.image.ref @@ -7,6 +7,8 @@ .. |set_image.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/image/set_image.png .. |set_x.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/image/set_x.png .. |set_y.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/image/set_y.png +.. |set_rotation.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/image/set_rotation.png +.. |set_scale.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/image/set_scale.png .. |cores3_show_image_example.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/image/example.png diff --git a/docs/locales/zh_CN/LC_MESSAGES/m5ui/image.po b/docs/locales/zh_CN/LC_MESSAGES/m5ui/image.po index b4728ea8..0bcc3ae0 100644 --- a/docs/locales/zh_CN/LC_MESSAGES/m5ui/image.po +++ b/docs/locales/zh_CN/LC_MESSAGES/m5ui/image.po @@ -9,7 +9,7 @@ msgid "" msgstr "" "Project-Id-Version: UIFlow2 Programming Guide \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-07-09 09:51+0800\n" +"POT-Creation-Date: 2025-07-22 11:52+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language: zh_CN\n" @@ -52,8 +52,8 @@ msgstr "该示例演示如何在屏幕上显示图像。" #: ../../en/m5ui/image.rst:20 ../../en/m5ui/image.rst:64 #: ../../en/m5ui/image.rst:81 ../../en/m5ui/image.rst:97 #: ../../en/m5ui/image.rst:113 ../../en/m5ui/image.rst:166 -#: 1a10b6b6eac849379defe1f60f01e63c 31cb071512d148b89648a79439bccdaa -#: c951f731ddfc4e2b8026d967f5d261f0 m5ui.image.M5Image.set_image:5 of +#: 31cb071512d148b89648a79439bccdaa m5ui.image.M5Image.set_image:5 +#: m5ui.image.M5Image.set_rotation:5 m5ui.image.M5Image.set_scale:6 of msgid "UiFlow2 Code Block:" msgstr "UiFlow2 代码块:" @@ -82,9 +82,9 @@ msgstr "MicroPython 示例" #: ../../en/m5ui/image.rst:37 ../../en/m5ui/image.rst:68 #: ../../en/m5ui/image.rst:85 ../../en/m5ui/image.rst:101 #: ../../en/m5ui/image.rst:117 ../../en/m5ui/image.rst:170 -#: 0a00eccad0b04f3aaa2c1d7a7394410f 1d7f94f68a834722a4358d61315754e8 #: 297b0cb0f5784bf597709765289779e5 m5ui.image.M5Image:8 -#: m5ui.image.M5Image.set_image:9 of +#: m5ui.image.M5Image.set_image:9 m5ui.image.M5Image.set_rotation:9 +#: m5ui.image.M5Image.set_scale:10 of msgid "MicroPython Code Block:" msgstr "MicroPython 代码块:" @@ -100,9 +100,9 @@ msgstr "" msgid "Create a image object." msgstr "创建一个图像对象。" -#: ../../en/m5ui/image.rst 16927f2d78b84d7995d7757586c95c3d -#: 86729ee12c674d32aea2dae2da7894e3 ec61dd436e75448f913e8b24464169a1 -#: m5ui.image.M5Image.set_image of +#: ../../en/m5ui/image.rst 86729ee12c674d32aea2dae2da7894e3 +#: m5ui.image.M5Image.set_image m5ui.image.M5Image.set_rotation +#: m5ui.image.M5Image.set_scale of msgid "Parameters" msgstr "参数" @@ -165,7 +165,7 @@ msgstr "图像的 y 坐标。" msgid "|set_pos.png|" msgstr "" -#: ../../en/refs/m5ui.image.ref:6 24a4191acef241d9a4969e04504367a9 +#: ../../en/refs/m5ui.image.ref:5 24a4191acef241d9a4969e04504367a9 msgid "set_pos.png" msgstr "" @@ -177,7 +177,7 @@ msgstr "设置图像的 x 坐标。" msgid "|set_x.png|" msgstr "" -#: ../../en/refs/m5ui.image.ref:10 7b8998924bc6499f93fc7be7de337fc0 +#: ../../en/refs/m5ui.image.ref:8 7b8998924bc6499f93fc7be7de337fc0 msgid "set_x.png" msgstr "" @@ -189,7 +189,7 @@ msgstr "设置图像的 y 坐标。" msgid "|set_y.png|" msgstr "" -#: ../../en/refs/m5ui.image.ref:11 3e6941e1a24c442c89a1d8f70ba3d8b5 +#: ../../en/refs/m5ui.image.ref:9 3e6941e1a24c442c89a1d8f70ba3d8b5 msgid "set_y.png" msgstr "" @@ -229,13 +229,54 @@ msgstr "设置要显示的图像。" msgid "|set_image.png|" msgstr "" -#: ../../en/refs/m5ui.image.ref:8 eff9728ed324422f82fa19876de13c62 +#: ../../en/refs/m5ui.image.ref:7 eff9728ed324422f82fa19876de13c62 msgid "set_image.png" msgstr "" +#: c05c9c1dbf1d4d5fb958d6716441feed m5ui.image.M5Image.set_rotation:1 of +msgid "Set the rotation of the image." +msgstr "设置图像的旋转角度。" + +#: 8814a9edc3b743428d94fa815fd38620 m5ui.image.M5Image.set_rotation:3 of +msgid "The rotation angle in degrees (0, 90, 180, 270)." +msgstr "旋转角度(0、90、180、270)。" + +#: 10f2c4f0cc5e4795a792784eae174ade m5ui.image.M5Image.set_rotation:7 of +msgid "|set_rotation.png|" +msgstr "" + +#: ../../en/refs/m5ui.image.ref:10 27d4b0e3420a47d59f4aca49083ca4a9 +msgid "set_rotation.png" +msgstr "" + +#: abfb0eef70b647ee8e3e5153e6d2814b m5ui.image.M5Image.set_scale:1 of +msgid "Set the scale of the image." +msgstr "设置图像的缩放比例。" + +#: 22d5912df41c4ce5a8beb790ca320d81 m5ui.image.M5Image.set_scale:3 of +msgid "" +"The horizontal scale factor. If `scale_y` is not provided, this value " +"will be used for both axes." +msgstr "水平缩放因子。如果未提供 scale_y,则此值将用于两个轴。" + +#: 5f49fa823b534d249125f3d0432b1332 m5ui.image.M5Image.set_scale:4 of +msgid "The vertical scale factor." +msgstr "图像的垂直缩放因子。" + +#: 3431e1edeff3409fb8997cb1a1f60bd5 m5ui.image.M5Image.set_scale:8 of +msgid "|set_scale.png|" +msgstr "" + +#: ../../en/refs/m5ui.image.ref:11 eff9728ed324422f82fa19876de13c62 +msgid "set_scale.png" +msgstr "" + #~ msgid "Returns" #~ msgstr "返回值" #~ msgid "|set_flag.png|" #~ msgstr "" +#~ msgid "The horizontal scale factor." +#~ msgstr "图像的水平缩放因子。" + diff --git a/m5stack/libs/m5ui/image.py b/m5stack/libs/m5ui/image.py index 77cf80b9..c948ba02 100644 --- a/m5stack/libs/m5ui/image.py +++ b/m5stack/libs/m5ui/image.py @@ -31,6 +31,9 @@ def __init__( path, x=0, y=0, + rotation=0, + scale_x=1.0, + scale_y=1.0, parent=None, ): if parent is None: @@ -39,6 +42,10 @@ def __init__( self.set_src("S:" + path) self.set_pos(x, y) + self.set_width(lv.SIZE_CONTENT) + self.set_height(lv.SIZE_CONTENT) + self.set_rotation(rotation) + self.set_scale(scale_x, scale_y) def set_image(self, path): """Set the image to be displayed. @@ -58,6 +65,45 @@ def set_image(self, path): """ self.set_src("S:" + path) + def set_rotation(self, rotation): + """Set the rotation of the image. + + :param int rotation: The rotation angle in degrees (0, 90, 180, 270). + + UiFlow2 Code Block: + + |set_rotation.png| + + MicroPython Code Block: + + .. code-block:: python + + image_0.set_rotation(90) + """ + super().set_rotation(rotation * 10) + + def set_scale(self, scale, scale_y=None): + """Set the scale of the image. + + :param float scale_x: The horizontal scale factor. + :param float scale_y: The vertical scale factor. + + UiFlow2 Code Block: + + |set_scale.png| + + MicroPython Code Block: + + .. code-block:: python + + image_0.set_scale(2.0, 2.0) + """ + if scale_y is not None: + super().set_scale_x(int(scale * 256)) + super().set_scale_y(int(scale_y * 256)) + else: + super().set_scale(scale * 256) + def __getattr__(self, name): if hasattr(M5Base, name): method = getattr(M5Base, name) From e3400120ee50b70d8627e20b8b8a40433d8cf3f0 Mon Sep 17 00:00:00 2001 From: tinyu Date: Thu, 17 Jul 2025 11:39:50 +0800 Subject: [PATCH 179/322] lib/m5ui: Add LVGL Arc and calendar widget. Signed-off-by: tinyu --- docs/en/m5ui/arc.rst | 302 ++++++++++++++++++ docs/en/m5ui/calendar.rst | 215 +++++++++++++ docs/en/m5ui/index.rst | 4 +- docs/en/refs/m5ui.arc.ref | 26 ++ docs/en/refs/m5ui.calendar.ref | 23 ++ .../m5ui/arc/cores3_arc_event_example.m5f2 | 1 + examples/m5ui/arc/cores3_arc_event_example.py | 82 +++++ .../cores3_calendar_event_example.m5f2 | 1 + .../calendar/cores3_calendar_event_example.py | 82 +++++ m5stack/libs/m5ui/__init__.py | 2 + m5stack/libs/m5ui/arc.py | 114 +++++++ m5stack/libs/m5ui/calendar.py | 111 +++++++ m5stack/libs/m5ui/manifest.py | 2 + 13 files changed, 964 insertions(+), 1 deletion(-) create mode 100644 docs/en/m5ui/arc.rst create mode 100644 docs/en/m5ui/calendar.rst create mode 100644 docs/en/refs/m5ui.arc.ref create mode 100644 docs/en/refs/m5ui.calendar.ref create mode 100644 examples/m5ui/arc/cores3_arc_event_example.m5f2 create mode 100644 examples/m5ui/arc/cores3_arc_event_example.py create mode 100644 examples/m5ui/calendar/cores3_calendar_event_example.m5f2 create mode 100644 examples/m5ui/calendar/cores3_calendar_event_example.py create mode 100644 m5stack/libs/m5ui/arc.py create mode 100644 m5stack/libs/m5ui/calendar.py diff --git a/docs/en/m5ui/arc.rst b/docs/en/m5ui/arc.rst new file mode 100644 index 00000000..96ce6606 --- /dev/null +++ b/docs/en/m5ui/arc.rst @@ -0,0 +1,302 @@ +.. currentmodule:: m5ui + +M5Arc +======== + +.. include:: ../refs/m5ui.arc.ref + +M5Arc is a widget that can be used to create arcs in the user interface. It can be used to display circular progress or other circular indicators. + +UiFlow2 Example +--------------- + +event arc +^^^^^^^^^^^^ + +Open the |cores3_arc_event_example.m5f2| project in UiFlow2. + +This example creates an arc that triggers an event when the value changes. + +UiFlow2 Code Block: + + |cores3_arc_event_example.png| + +Example output: + + None + + +MicroPython Example +------------------- + +event arc +^^^^^^^^^^^^ + +This example creates an arc that triggers an event when the value changes. + +MicroPython Code Block: + + .. literalinclude:: ../../../examples/m5ui/arc/cores3_arc_event_example.py + :language: python + :linenos: + +Example output: + + None + + +**API** +------- + +M5Arc +^^^^^^^^ + +.. autoclass:: m5ui.arc.M5Arc + :members: + :member-order: bysource + + .. py:method:: set_rotation(rotation) + + Set the rotation of the arc. + + :param int rotation: The rotation angle of the arc in degrees. + + UiFlow2 Code Block: + + |set_rotation.png| + + MicroPython Code Block: + + .. code-block:: python + + arc_0.set_rotation(90) + + .. py:method:: set_value(value) + + Set the value of the arc. + + :param int value: The value of the arc. + + UiFlow2 Code Block: + + |set_value.png| + + MicroPython Code Block: + + .. code-block:: python + + arc_0.set_value(90) + + .. py:method:: get_value() + + Get the value of the arc. + + :return: The value of the arc. + :rtype: int + + UiFlow2 Code Block: + + |get_value.png| + + MicroPython Code Block: + + .. code-block:: python + + arc_0.get_value() + + .. py:method:: set_range() + + Set the range of the arc. + + :param int min: The minimum value of the arc. + :param int max: The maximum value of the arc. + + UiFlow2 Code Block: + + |set_range.png| + + MicroPython Code Block: + + .. code-block:: python + + arc_0.set_range(0, 100) + + + .. py:method:: set_mode() + + Set the mode of the arc. + + :param int mode: The mode of the arc. + + Option: + - lv.arc.MODE.NORMAL: Normal mode. + - lv.arc.MODE.REVERSE: Reverse mode. + - lv.arc.MODE.SYMMETRICAL: Symmetrical mode. + + UiFlow2 Code Block: + + |set_mode.png| + + MicroPython Code Block: + + .. code-block:: python + + arc_0.set_mode(lv.ARC.MODE.NORMAL) + + .. py:method:: set_flag(flag, value) + + Set a flag on the object. If ``value`` is True, the flag is added; if False, the flag is removed. + + :param int flag: The flag to set. + :param bool value: If True, the flag is added; if False, the flag is removed. + :return: None + + UiFlow2 Code Block: + + |set_flag.png| + + MicroPython Code Block: + + .. code-block:: python + + button_0.set_flag(lv.obj.FLAG.HIDDEN, True) + + + .. py:method:: set_pos(x, y) + + Set the position of the arc. + + :param int x: The x-coordinate of the arc. + :param int y: The y-coordinate of the arc. + + UiFlow2 Code Block: + + |set_pos.png| + + MicroPython Code Block: + + .. code-block:: python + + arc_0.set_pos(100, 100) + + .. py:method:: set_x(x) + + Set the x-coordinate of the arc. + + :param int x: The x-coordinate of the arc. + + UiFlow2 Code Block: + + |set_x.png| + + MicroPython Code Block: + + .. code-block:: python + + arc_0.set_x(100) + + .. py:method:: set_y(y) + + Set the y-coordinate of the arc. + + :param int y: The y-coordinate of the arc. + + UiFlow2 Code Block: + + |set_y.png| + + MicroPython Code Block: + + .. code-block:: python + + arc_0.set_y(100) + + .. py:method:: set_size(width, height) + + Set the size of the arc. + + :param int width: The width of the arc. + :param int height: The height of the arc. + + UiFlow2 Code Block: + + |set_size.png| + + MicroPython Code Block: + + .. code-block:: python + + arc_0.set_size(100, 50) + + .. py:method:: set_width(width) + + Set the width of the arc. + + :param int width: The width of the arc. + + UiFlow2 Code Block: + + |set_width.png| + + MicroPython Code Block: + + .. code-block:: python + + arc_0.set_width(100) + + .. py:method:: set_height(height) + + Set the height of the arc. + + :param int height: The height of the arc. + + UiFlow2 Code Block: + + |set_height.png| + + MicroPython Code Block: + + .. code-block:: python + + arc_0.set_height(50) + + .. py:method:: align_to(obj, align, x, y) + + Align the arc to another object. + + :param lv.obj obj: The object to align to. + :param int align: The alignment type. + :param int x: The x-offset from the aligned object. + :param int y: The y-offset from the aligned object. + + UiFlow2 Code Block: + + |align_to.png| + + MicroPython Code Block: + + .. code-block:: python + + arc_0.align_to(page_0, lv.ALIGN.CENTER, 0, 0) + + .. py:method:: add_event_cb(handler, event, user_data) + + Add an event callback to the arc. The callback will be called when the specified event occurs. + + :param function handler: The callback function to call. + :param int event: The event to listen for. + :param Any user_data: Optional user data to pass to the callback. + + UiFlow2 Code Block: + + |event.png| + + MicroPython Code Block: + + .. code-block:: python + + def value_changed_event(event_struct): + global page0, arc_0 + print("value changed:", arc_0.get_value()) + + arc_0.add_event_cb(value_changed_event, lv.EVENT.VALUE_CHANGED, None) diff --git a/docs/en/m5ui/calendar.rst b/docs/en/m5ui/calendar.rst new file mode 100644 index 00000000..671dd0fe --- /dev/null +++ b/docs/en/m5ui/calendar.rst @@ -0,0 +1,215 @@ +.. currentmodule:: m5ui + +M5Calendar +========== + +.. include:: ../refs/m5ui.calendar.ref + +M5Calendar is a widget that can be used to create a calendar in the user interface. It can be used to display and select dates. + +UiFlow2 Example +--------------- + +event calendar +^^^^^^^^^^^^^^ + +Open the |cores3_calendar_event_example.m5f2| project in UiFlow2. + +This example creates a calendar that triggers an event when the date is changed. + +UiFlow2 Code Block: + + |cores3_calendar_event_example.png| + +Example output: + + None + + +MicroPython Example +------------------- + +event calendar +^^^^^^^^^^^^^^ + +This example creates a calendar that triggers an event when the date is changed. + +MicroPython Code Block: + + .. literalinclude:: ../../../examples/m5ui/calendar/cores3_calendar_event_example.py + :language: python + :linenos: + +Example output: + + None + + +**API** +------- + +M5Calendar +^^^^^^^^^^ + +.. autoclass:: m5ui.calendar.M5Calendar + :members: + :member-order: bysource + + .. py:method:: set_month_shown(year, month) + + Set the month and year shown in the calendar. + + :param int year: The year to show. + :param int month: The month to show. + + UiFlow2 Code Block: + + |set_month_shown.png| + + MicroPython Code Block: + + .. code-block:: python + + calendar_0.set_month_shown(2023, 3) + + .. py:method:: set_pos(x, y) + + Set the position of the calendar. + + :param int x: The x-coordinate of the calendar. + :param int y: The y-coordinate of the calendar. + + UiFlow2 Code Block: + + |set_pos.png| + + MicroPython Code Block: + + .. code-block:: python + + calendar_0.set_pos(100, 100) + + .. py:method:: set_x(x) + + Set the x-coordinate of the calendar. + + :param int x: The x-coordinate of the calendar. + + UiFlow2 Code Block: + + |set_x.png| + + MicroPython Code Block: + + .. code-block:: python + + calendar_0.set_x(100) + + .. py:method:: set_y(y) + + Set the y-coordinate of the calendar. + + :param int y: The y-coordinate of the calendar. + + UiFlow2 Code Block: + + |set_y.png| + + MicroPython Code Block: + + .. code-block:: python + + calendar_0.set_y(100) + + .. py:method:: set_size(width, height) + + Set the size of the calendar. + + :param int width: The width of the calendar. + :param int height: The height of the calendar. + + UiFlow2 Code Block: + + |set_size.png| + + MicroPython Code Block: + + .. code-block:: python + + calendar_0.set_size(100, 50) + + .. py:method:: set_width(width) + + Set the width of the calendar. + + :param int width: The width of the calendar. + + UiFlow2 Code Block: + + |set_width.png| + + MicroPython Code Block: + + .. code-block:: python + + calendar_0.set_width(100) + + .. py:method:: set_height(height) + + Set the height of the calendar. + + :param int height: The height of the calendar. + + UiFlow2 Code Block: + + |set_height.png| + + MicroPython Code Block: + + .. code-block:: python + + calendar_0.set_height(50) + + .. py:method:: align_to(obj, align, x, y) + + Align the calendar to another object. + + :param lv.obj obj: The object to align to. + :param int align: The alignment type. + :param int x: The x-offset from the aligned object. + :param int y: The y-offset from the aligned object. + + UiFlow2 Code Block: + + |align_to.png| + + MicroPython Code Block: + + .. code-block:: python + + calendar_0.align_to(page_0, lv.ALIGN.CENTER, 0, 0) + + .. py:method:: add_event_cb(handler, event, user_data) + + Add an event callback to the calendar. The callback will be called when the specified event occurs. + + :param function handler: The callback function to call. + :param int event: The event to listen for. + :param Any user_data: Optional user data to pass to the callback. + + UiFlow2 Code Block: + + |event.png| + + MicroPython Code Block: + + .. code-block:: python + + def calendar_event_handler(event_struct): + if event_struct.get_code() == lv.EVENT.VALUE_CHANGED: + date = lv.calendar_date_t() + if calendar_0.get_pressed_date(date) == lv.RESULT.OK: + calendar_0.set_today_date(date.year, date.month, date.day) + print("Clicked date: %02d.%02d.%02d" % (date.year, date.month, date.day)) + + calendar_0.add_event_cb(calendar_event_handler, lv.EVENT.ALL, None) diff --git a/docs/en/m5ui/index.rst b/docs/en/m5ui/index.rst index c1d60ceb..d99c5113 100644 --- a/docs/en/m5ui/index.rst +++ b/docs/en/m5ui/index.rst @@ -33,9 +33,11 @@ Classes :maxdepth: 1 page.rst + arc.rst bar.rst button.rst + calendar.rst + image.rst label.rst line.rst switch.rst - image.rst diff --git a/docs/en/refs/m5ui.arc.ref b/docs/en/refs/m5ui.arc.ref new file mode 100644 index 00000000..2c0010db --- /dev/null +++ b/docs/en/refs/m5ui.arc.ref @@ -0,0 +1,26 @@ +.. |align_to.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/switch/align_to.png +.. |event.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/switch/event.png +.. |set_arc_color.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/switch/set_arc_color.png +.. |set_rotation.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/switch/set_rotation.png +.. |set_value.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/switch/set_value.png +.. |get_value.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/switch/get_value.png +.. |set_range.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/switch/set_range.png +.. |set_mode.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/switch/set_mode.png +.. |set_height.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/switch/set_height.png +.. |set_pos.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/switch/set_pos.png +.. |set_size.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/switch/set_size.png +.. |set_width.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/switch/set_width.png +.. |set_x.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/switch/set_x.png +.. |set_y.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/switch/set_y.png +.. |set_flag.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/switch/set_flag.png + +.. |cores3_arc_event_example.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/switch/example.png + +.. |cores3_arc_event_example.m5f2| raw:: html + + + cores3_arc_event_example.m5f2 + diff --git a/docs/en/refs/m5ui.calendar.ref b/docs/en/refs/m5ui.calendar.ref new file mode 100644 index 00000000..8cdb36ff --- /dev/null +++ b/docs/en/refs/m5ui.calendar.ref @@ -0,0 +1,23 @@ +.. |align_to.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/switch/align_to.png +.. |event.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/switch/event.png +.. |set_calendar_style.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/switch/set_calendar_style.png +.. |set_today_date.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/switch/set_today_date.png +.. |set_month_shown.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/switch/set_month_shown.png +.. |set_highlighted_dates.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/switch/set_highlighted_dates.png +.. |set_height.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/switch/set_height.png +.. |set_pos.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/switch/set_pos.png +.. |set_size.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/switch/set_size.png +.. |set_width.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/switch/set_width.png +.. |set_x.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/switch/set_x.png +.. |set_y.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/switch/set_y.png + +.. |cores3_calendar_event_example.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/switch/example.png + +.. |cores3_calendar_event_example.m5f2| raw:: html + + + cores3_calendar_event_example.m5f2 + diff --git a/examples/m5ui/arc/cores3_arc_event_example.m5f2 b/examples/m5ui/arc/cores3_arc_event_example.m5f2 new file mode 100644 index 00000000..29e726ee --- /dev/null +++ b/examples/m5ui/arc/cores3_arc_event_example.m5f2 @@ -0,0 +1 @@ +{"version":"V2.0","versionNumber":"V2.3.0","type":"cores3","components":[{"name":"page0","type":"lvgl_page","layer":0,"screenId":"builtin","screenName":"","id":"m9@lpi+_=W%+Mg*k","createTime":1751869561848,"backgroundColor":"#ffffff","isLVGL":true,"isSelected":true},{"name":"switch0","type":"lvgl_switch","layer":1,"screenId":"builtin","screenName":"","id":"s-ESKHmEhR7aT8SQ","createTime":1751869566947,"x":128,"y":91,"width":60,"height":30,"knobColor":"#ffffff","backgroundColor":"#e7e3e7","checkedBackgroundColor":"#2196f3","pageId":"m9@lpi+_=W%+Mg*k","isLVGL":true,"isSelected":false}],"resources":[{"hardware":["hardware_button","hardware_pin_button","imu","speaker","touch","als","mic","sdcard"]}],"units":[],"hats":[],"bases":[],"i2cs":[],"blockly":"truepage0switch0palette#666666255switch0palette#33ff33255trueswitch0CHECKEDswitch0 checkedswitch0UNCHECKEDswitch0 unchecked","screen":[{"simulationName":"Built-in","type":"builtin","width":320,"height":240,"scale":0.77,"screenName":"","blockId":"","screenColorType":0,"rotation":1,"id":"builtin","createTime":1751869561847}],"logicWhenNum":0,"customList":[]} \ No newline at end of file diff --git a/examples/m5ui/arc/cores3_arc_event_example.py b/examples/m5ui/arc/cores3_arc_event_example.py new file mode 100644 index 00000000..b23895cb --- /dev/null +++ b/examples/m5ui/arc/cores3_arc_event_example.py @@ -0,0 +1,82 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import os, sys, io +import M5 +from M5 import * +import m5ui +import lvgl as lv + + +page0 = None +switch0 = None + + +def switch0_checked_event(event_struct): + global page0, switch0 + + print("switch0 checked") + + +def switch0_unchecked_event(event_struct): + global page0, switch0 + + print("switch0 unchecked") + + +def switch0_event_handler(event_struct): + global page0, switch0 + event = event_struct.code + obj = event_struct.get_target_obj() + if event == lv.EVENT.VALUE_CHANGED: + if obj.has_state(lv.STATE.CHECKED): + switch0_checked_event(event_struct) + else: + switch0_unchecked_event(event_struct) + return + + +def setup(): + global page0, switch0 + + M5.begin() + Widgets.setRotation(1) + m5ui.init() + page0 = m5ui.M5Page(bg_c=0xFFFFFF) + switch0 = m5ui.M5Switch( + x=128, + y=91, + w=60, + h=30, + bg_c=0xE7E3E7, + bg_c_checked=0x2196F3, + circle_c=0xFFFFFF, + parent=page0, + ) + + switch0.add_event_cb(switch0_event_handler, lv.EVENT.ALL, None) + + page0.screen_load() + switch0.set_bg_color(0x666666, 255, lv.PART.MAIN | lv.STATE.DEFAULT) + switch0.set_bg_color(0x33FF33, 255, lv.PART.INDICATOR | lv.STATE.CHECKED) + + +def loop(): + global page0, switch0 + M5.update() + + +if __name__ == "__main__": + try: + setup() + while True: + loop() + except (Exception, KeyboardInterrupt) as e: + try: + m5ui.deinit() + from utility import print_error_msg + + print_error_msg(e) + except ImportError: + print("please update to latest firmware") diff --git a/examples/m5ui/calendar/cores3_calendar_event_example.m5f2 b/examples/m5ui/calendar/cores3_calendar_event_example.m5f2 new file mode 100644 index 00000000..29e726ee --- /dev/null +++ b/examples/m5ui/calendar/cores3_calendar_event_example.m5f2 @@ -0,0 +1 @@ +{"version":"V2.0","versionNumber":"V2.3.0","type":"cores3","components":[{"name":"page0","type":"lvgl_page","layer":0,"screenId":"builtin","screenName":"","id":"m9@lpi+_=W%+Mg*k","createTime":1751869561848,"backgroundColor":"#ffffff","isLVGL":true,"isSelected":true},{"name":"switch0","type":"lvgl_switch","layer":1,"screenId":"builtin","screenName":"","id":"s-ESKHmEhR7aT8SQ","createTime":1751869566947,"x":128,"y":91,"width":60,"height":30,"knobColor":"#ffffff","backgroundColor":"#e7e3e7","checkedBackgroundColor":"#2196f3","pageId":"m9@lpi+_=W%+Mg*k","isLVGL":true,"isSelected":false}],"resources":[{"hardware":["hardware_button","hardware_pin_button","imu","speaker","touch","als","mic","sdcard"]}],"units":[],"hats":[],"bases":[],"i2cs":[],"blockly":"truepage0switch0palette#666666255switch0palette#33ff33255trueswitch0CHECKEDswitch0 checkedswitch0UNCHECKEDswitch0 unchecked","screen":[{"simulationName":"Built-in","type":"builtin","width":320,"height":240,"scale":0.77,"screenName":"","blockId":"","screenColorType":0,"rotation":1,"id":"builtin","createTime":1751869561847}],"logicWhenNum":0,"customList":[]} \ No newline at end of file diff --git a/examples/m5ui/calendar/cores3_calendar_event_example.py b/examples/m5ui/calendar/cores3_calendar_event_example.py new file mode 100644 index 00000000..b23895cb --- /dev/null +++ b/examples/m5ui/calendar/cores3_calendar_event_example.py @@ -0,0 +1,82 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import os, sys, io +import M5 +from M5 import * +import m5ui +import lvgl as lv + + +page0 = None +switch0 = None + + +def switch0_checked_event(event_struct): + global page0, switch0 + + print("switch0 checked") + + +def switch0_unchecked_event(event_struct): + global page0, switch0 + + print("switch0 unchecked") + + +def switch0_event_handler(event_struct): + global page0, switch0 + event = event_struct.code + obj = event_struct.get_target_obj() + if event == lv.EVENT.VALUE_CHANGED: + if obj.has_state(lv.STATE.CHECKED): + switch0_checked_event(event_struct) + else: + switch0_unchecked_event(event_struct) + return + + +def setup(): + global page0, switch0 + + M5.begin() + Widgets.setRotation(1) + m5ui.init() + page0 = m5ui.M5Page(bg_c=0xFFFFFF) + switch0 = m5ui.M5Switch( + x=128, + y=91, + w=60, + h=30, + bg_c=0xE7E3E7, + bg_c_checked=0x2196F3, + circle_c=0xFFFFFF, + parent=page0, + ) + + switch0.add_event_cb(switch0_event_handler, lv.EVENT.ALL, None) + + page0.screen_load() + switch0.set_bg_color(0x666666, 255, lv.PART.MAIN | lv.STATE.DEFAULT) + switch0.set_bg_color(0x33FF33, 255, lv.PART.INDICATOR | lv.STATE.CHECKED) + + +def loop(): + global page0, switch0 + M5.update() + + +if __name__ == "__main__": + try: + setup() + while True: + loop() + except (Exception, KeyboardInterrupt) as e: + try: + m5ui.deinit() + from utility import print_error_msg + + print_error_msg(e) + except ImportError: + print("please update to latest firmware") diff --git a/m5stack/libs/m5ui/__init__.py b/m5stack/libs/m5ui/__init__.py index 99485944..35e3eba8 100644 --- a/m5stack/libs/m5ui/__init__.py +++ b/m5stack/libs/m5ui/__init__.py @@ -5,8 +5,10 @@ _attrs = { "init": "port", "deinit": "port", + "M5Arc": "arc", "M5Bar": "bar", "M5Button": "button", + "M5Calendar": "calendar", "M5Image": "image", "M5Label": "label", "M5Line": "line", diff --git a/m5stack/libs/m5ui/arc.py b/m5stack/libs/m5ui/arc.py new file mode 100644 index 00000000..e3894f57 --- /dev/null +++ b/m5stack/libs/m5ui/arc.py @@ -0,0 +1,114 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +from m5ui.base import M5Base +import lvgl as lv + + +class M5Arc(lv.arc): + """Create a arc object. + + :param int x: The x position of the arc. + :param int y: The y position of the arc. + :param int w: The width of the arc. + :param int h: The height of the arc. + :param int value: The initial value of the arc. + :param int min_value: The minimum value of the arc. + :param int max_value: The maximum value of the arc. + :param int rotation: The rotation of the arc in degrees. + :param int bg_c: The color of the arc in the off state in hexadecimal format. + :param int bg_c_indicator: The color of the arc in the on state in hexadecimal format. + :param int bg_c_knob: The color of the knob on the arc in hexadecimal format. + :param lv.obj parent: The parent object to attach the arc to. If not specified, the arc will be attached to the default screen. + + UiFlow2 Code Block: + + None + + MicroPython Code Block: + + .. code-block:: python + + from m5ui import M5Arc + import lvgl as lv + + m5ui.init() + arc_0 = M5Arc( + x=0, + y=0, + w=100, + h=100, + value=10, + min_value=0, + max_value=100, + rotation=0, + mode=lv.arc.MODE.REVERSE, + bg_c=0xE7E3E7, + bg_c_indicator=0x0288FB, + bg_c_knob=0xE7E3E7, + parent=page0, + ) + """ + + def __init__( + self, + x=0, + y=0, + w=0, + h=0, + value=0, + min_value=0, + max_value=100, + rotation=0, + mode=lv.arc.MODE.NORMAL, + bg_c=0xE7E3E7, + bg_c_indicator=0x0288FB, + bg_c_knob=0xE7E3E7, + parent=None, + ): + if parent is None: + parent = lv.screen_active() + super().__init__(parent) + self.set_pos(x, y) + self.set_size(w, h) + self.set_value(value) + self.set_range(min_value, max_value) + self.set_rotation(rotation) + self.set_mode(mode) + self.set_arc_color(bg_c, 255, lv.PART.MAIN | lv.STATE.DEFAULT) + self.set_arc_color(bg_c_indicator, 255, lv.PART.INDICATOR | lv.STATE.DEFAULT) + self.set_arc_color(bg_c_knob, 255, lv.PART.KNOB | lv.STATE.DEFAULT) + + def set_arc_color(self, color, opa: int, part: int): + """Set the color of the arc. + + :param int color: The color of the arc in hexadecimal format. + :param int opa: The opacity level (0-255). + :param int part: The part of the arc to apply the style to (e.g., lv.PART.MAIN | lv.STATE.DEFAULT). + + UiFlow2 Code Block: + + |set_arc_color.png| + + MicroPython Code Block: + + .. code-block:: python + + label_0.set_arc_color(0x2196F3, lv.PART.MAIN | lv.STATE.DEFAULT) + """ + if isinstance(color, int): + color = lv.color_hex(color) + if part == lv.PART.KNOB | lv.STATE.DEFAULT: + self.set_bg_color(color, opa, lv.PART.KNOB | lv.STATE.DEFAULT) + else: + self.set_style_arc_color(color, part) + self.set_style_arc_opa(opa, part) + + def __getattr__(self, name): + if hasattr(M5Base, name): + method = getattr(M5Base, name) + bound_method = lambda *args, **kwargs: method(self, *args, **kwargs) + setattr(self, name, bound_method) + return bound_method + raise AttributeError(f"'{self.__class__.__name__}' object has no attribute '{name}'") diff --git a/m5stack/libs/m5ui/calendar.py b/m5stack/libs/m5ui/calendar.py new file mode 100644 index 00000000..3251308d --- /dev/null +++ b/m5stack/libs/m5ui/calendar.py @@ -0,0 +1,111 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +from m5ui.base import M5Base +import lvgl as lv + + +class M5Calendar(lv.calendar): + """Create a calendar object. + + :param int x: The x position of the calendar. + :param int y: The y position of the calendar. + :param int w: The width of the calendar. + :param int h: The height of the calendar. + :param str style: The style of the calendar, can be "arrow" or "dropdown" and None. + :param list today_date: The date to highlight as today in the format [year, month, day]. + :param list show_month: The month to show in the format [year, month]. + :param lv.obj parent: The parent object to attach the calendar to. If not specified, the calendar will be attached to the default screen. + + UiFlow2 Code Block: + + None + + MicroPython Code Block: + + .. code-block:: python + + from m5ui import M5Calendar + import lvgl as lv + + m5ui.init() + calendar_0 = M5Calendar(x=0, y=0, w=200, h=200, style=None, today_date=[2024, 1, 1], show_month=[2024, 1], parent=page0) + """ + + def __init__( + self, + x=0, + y=0, + w=200, + h=200, + style="arrow", + today_date=[2024, 1, 1], + show_month=[2024, 1], + parent=None, + ): + if parent is None: + parent = lv.screen_active() + super().__init__(parent) + self.header = None + self.set_pos(x, y) + self.set_size(w, h) + self.set_calendar_style(style) + self.set_today_date(*today_date) + self.set_month_shown(*show_month) + + def set_calendar_style(self, style): + """Set the style of the calendar header. + + :param str style: The style of the calendar header, can be "arrow", "dropdown", or None. + + UiFlow2 Code Block: + + |set_calendar_style.png| + + MicroPython Code Block: + + .. code-block:: python + + calendar_0.set_calendar_style("arrow") + calendar_0.set_calendar_style("dropdown") + calendar_0.set_calendar_style(None) + """ + if self.header: + self.header.delete() + self.header = None + + if style == "arrow": + self.header = lv.calendar_header_arrow(self) + elif style == "dropdown": + self.header = lv.calendar_header_dropdown(self) + + def set_highlighted_dates(self, dates): + """Set the highlighted dates in the calendar. + + :param list dates: A list of dates to highlight in the format [year, month, day, year, month, day, ...] + + UiFlow2 Code Block: + + |set_highlighted_dates.png| + + MicroPython Code Block: + + .. code-block:: python + + calendar_0.set_highlighted_dates([2024, 1, 1, 2024, 1, 2, 2024, 1, 3]) + """ + self.highlighted_days = [] + for i in range(0, len(dates), 3): + self.highlighted_days.append( + {"year": dates[i], "month": dates[i + 1], "day": dates[i + 2]} + ) + super().set_highlighted_dates(self.highlighted_days, len(self.highlighted_days)) + + def __getattr__(self, name): + if hasattr(M5Base, name): + method = getattr(M5Base, name) + bound_method = lambda *args, **kwargs: method(self, *args, **kwargs) + setattr(self, name, bound_method) + return bound_method + raise AttributeError(f"'{self.__class__.__name__}' object has no attribute '{name}'") diff --git a/m5stack/libs/m5ui/manifest.py b/m5stack/libs/m5ui/manifest.py index 64efd6b6..2e333fa8 100644 --- a/m5stack/libs/m5ui/manifest.py +++ b/m5stack/libs/m5ui/manifest.py @@ -6,9 +6,11 @@ "m5ui", ( "__init__.py", + "arc.py", "bar.py", "base.py", "button.py", + "calendar.py", "image.py", "label.py", "line.py", From 59b3dd6f22237e8548274016390fd15533734bcc Mon Sep 17 00:00:00 2001 From: lbuque <1102390310@qq.com> Date: Tue, 22 Jul 2025 09:29:49 +0800 Subject: [PATCH 180/322] libs/modbus: Preventing data blocks from going out of range. Signed-off-by: lbuque <1102390310@qq.com> --- .../modbus/cores3_rtu_master_example.m5f2 | 2 +- .../modbus/cores3_rtu_master_example.py | 3 +- .../modbus/cores3_rtu_slave_example.m5f2 | 2 +- .../modbus/cores3_rtu_slave_example.py | 99 +++++++++++-------- m5stack/libs/modbus/modbus/slave.py | 10 +- 5 files changed, 67 insertions(+), 49 deletions(-) diff --git a/examples/softwave/modbus/cores3_rtu_master_example.m5f2 b/examples/softwave/modbus/cores3_rtu_master_example.m5f2 index 5db11852..efe7bf53 100644 --- a/examples/softwave/modbus/cores3_rtu_master_example.m5f2 +++ b/examples/softwave/modbus/cores3_rtu_master_example.m5f2 @@ -1 +1 @@ -{"version":"V2.0","versionNumber":"V2.1.4","type":"cores3","components":[{"name":"screen","type":"screen","layer":0,"screenId":"builtin","screenName":"","id":"__cores3_screen","createTime":1727336118236,"x":0,"y":0,"width":320,"height":240,"backgroundColor":"#222222","size":0,"isSelected":true},{"name":"label0","type":"label","layer":1,"screenId":"builtin","screenName":"","id":"dzZnRJhL$T-nMUDX","createTime":1727337322581,"x":65,"y":8,"color":"#ffffff","backgroundColor":"#222222","text":"co","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false},{"name":"label1","type":"label","layer":2,"screenId":"builtin","screenName":"","id":"xAEEi$qkH-^`mN6*","createTime":1727337322581,"x":135,"y":8,"color":"#ffffff","backgroundColor":"#222222","text":"di","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false},{"name":"label2","type":"label","layer":3,"screenId":"builtin","screenName":"","id":"uobZ-6s%cS@BuZDu","createTime":1727337322581,"x":205,"y":8,"color":"#ffffff","backgroundColor":"#222222","text":"hr","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false},{"name":"label3","type":"label","layer":4,"screenId":"builtin","screenName":"","id":"j6uwPW2zTZ5w_M_&","createTime":1727337322581,"x":275,"y":8,"color":"#ffffff","backgroundColor":"#222222","text":"ir","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false},{"name":"label4","type":"label","layer":5,"screenId":"builtin","screenName":"","id":"q4welMDf1R#csbyl","createTime":1727337394881,"x":4,"y":45,"color":"#ffffff","backgroundColor":"#222222","text":"1000","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false},{"name":"label5","type":"label","layer":6,"screenId":"builtin","screenName":"","id":"k8#RLW4mVwc!iqO3","createTime":1727337394881,"x":4,"y":85,"color":"#ffffff","backgroundColor":"#222222","text":"1001","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false},{"name":"label6","type":"label","layer":7,"screenId":"builtin","screenName":"","id":"sCqtJrwOpS+Mnt0`","createTime":1727337394881,"x":4,"y":125,"color":"#ffffff","backgroundColor":"#222222","text":"1002","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false},{"name":"label7","type":"label","layer":8,"screenId":"builtin","screenName":"","id":"vLmpS!KBrS6$kpV@","createTime":1727337394881,"x":4,"y":165,"color":"#ffffff","backgroundColor":"#222222","text":"1003","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false},{"name":"label8","type":"label","layer":9,"screenId":"builtin","screenName":"","id":"zYvx3_&Bxu^f$dsI","createTime":1727337394881,"x":4,"y":205,"color":"#ffffff","backgroundColor":"#222222","text":"1004","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false},{"name":"label9","type":"label","layer":10,"screenId":"builtin","screenName":"","id":"rxeWJeubjBcD*CL@","createTime":1727337482956,"x":65,"y":45,"color":"#ffffff","backgroundColor":"#222222","text":"a00","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false},{"name":"label14","type":"label","layer":11,"screenId":"builtin","screenName":"","id":"bC`kWaw=mRLdl9#t","createTime":1727337482956,"x":135,"y":45,"color":"#ffffff","backgroundColor":"#222222","text":"a01","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false},{"name":"label19","type":"label","layer":12,"screenId":"builtin","screenName":"","id":"t^TP$dbkypQY89@G","createTime":1727337482956,"x":205,"y":45,"color":"#ffffff","backgroundColor":"#222222","text":"a02","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false},{"name":"label24","type":"label","layer":13,"screenId":"builtin","screenName":"","id":"oht9qboi6d!C7PLM","createTime":1727337482956,"x":275,"y":45,"color":"#ffffff","backgroundColor":"#222222","text":"a03","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false},{"name":"label10","type":"label","layer":14,"screenId":"builtin","screenName":"","id":"inX@Hy@rEXPe6H`l","createTime":1727337482956,"x":65,"y":85,"color":"#ffffff","backgroundColor":"#222222","text":"a10","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false},{"name":"label15","type":"label","layer":15,"screenId":"builtin","screenName":"","id":"ggyl%AeCcT4wtK%2","createTime":1727337482956,"x":135,"y":85,"color":"#ffffff","backgroundColor":"#222222","text":"a11","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false},{"name":"label20","type":"label","layer":16,"screenId":"builtin","screenName":"","id":"x5i6%-zdXzTgYfhx","createTime":1727337482956,"x":205,"y":85,"color":"#ffffff","backgroundColor":"#222222","text":"a12","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false},{"name":"label25","type":"label","layer":17,"screenId":"builtin","screenName":"","id":"pQq`0rU375!JO4t9","createTime":1727337482956,"x":275,"y":85,"color":"#ffffff","backgroundColor":"#222222","text":"a13","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false},{"name":"label11","type":"label","layer":18,"screenId":"builtin","screenName":"","id":"w4VINK^mv&GtQo^n","createTime":1727337482956,"x":65,"y":125,"color":"#ffffff","backgroundColor":"#222222","text":"a20","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false},{"name":"label16","type":"label","layer":19,"screenId":"builtin","screenName":"","id":"hnEITM*p2-s@uDwS","createTime":1727337482956,"x":135,"y":125,"color":"#ffffff","backgroundColor":"#222222","text":"a21","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false},{"name":"label21","type":"label","layer":20,"screenId":"builtin","screenName":"","id":"a1gRk6#^TY-4oEwQ","createTime":1727337482956,"x":205,"y":125,"color":"#ffffff","backgroundColor":"#222222","text":"a22","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false},{"name":"label26","type":"label","layer":21,"screenId":"builtin","screenName":"","id":"i9Nw`1db_TxtmEsp","createTime":1727337482956,"x":275,"y":125,"color":"#ffffff","backgroundColor":"#222222","text":"a23","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false},{"name":"label12","type":"label","layer":22,"screenId":"builtin","screenName":"","id":"zLdJflicv3ayyPRj","createTime":1727337482956,"x":65,"y":165,"color":"#ffffff","backgroundColor":"#222222","text":"a30","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false},{"name":"label17","type":"label","layer":23,"screenId":"builtin","screenName":"","id":"haiN=xIwjI-KvvLD","createTime":1727337482956,"x":135,"y":165,"color":"#ffffff","backgroundColor":"#222222","text":"a31","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false},{"name":"label22","type":"label","layer":24,"screenId":"builtin","screenName":"","id":"k!aTlrR!6S`_`vU4","createTime":1727337482956,"x":205,"y":165,"color":"#ffffff","backgroundColor":"#222222","text":"a32","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false},{"name":"label27","type":"label","layer":25,"screenId":"builtin","screenName":"","id":"meV$iV&NsnV170Zm","createTime":1727337482956,"x":275,"y":165,"color":"#ffffff","backgroundColor":"#222222","text":"a33","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false},{"name":"label13","type":"label","layer":26,"screenId":"builtin","screenName":"","id":"ucAAW1M#SilbW75g","createTime":1727337482956,"x":65,"y":205,"color":"#ffffff","backgroundColor":"#222222","text":"a40","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false},{"name":"label18","type":"label","layer":27,"screenId":"builtin","screenName":"","id":"bQNoAo^8oOG!8tBz","createTime":1727337482956,"x":135,"y":205,"color":"#ffffff","backgroundColor":"#222222","text":"a41","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false},{"name":"label23","type":"label","layer":28,"screenId":"builtin","screenName":"","id":"mvwz12846%Y57UvC","createTime":1727337482956,"x":205,"y":205,"color":"#ffffff","backgroundColor":"#222222","text":"a42","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false},{"name":"label28","type":"label","layer":29,"screenId":"builtin","screenName":"","id":"qr@rN`qs^MY%4m4H","createTime":1727337482956,"x":275,"y":205,"color":"#ffffff","backgroundColor":"#222222","text":"a43","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false}],"resources":[{"software":["modbus"]},{"hardware":["hardware_button","hardware_pin_button","imu","speaker","touch","als","mic"]},{"unit":["unit_rs485"]}],"units":[{"type":"unit_rs485","name":"rs485_0","portList":["A","B","C","Custom"],"portType":"C","userPort":[22,21],"id":"u#UNPFYq#=J743`i","createTime":1727432206658,"initBlockId":"0H*Shc;h;^{XEzLD}5r7"}],"hats":[],"bases":[],"i2cs":[],"blockly":"reshrcoiltruers485_02rs485_0False115200modbusrtumaster_0rs485_0Falsehr0resmodbusrtumaster_01100052000label9LabelGETFROM_STARTres1label10LabelGETFROM_STARTres2label11LabelGETFROM_STARTres3label12LabelGETFROM_STARTres4label13LabelGETFROM_STARTres5resmodbusrtumaster_01100052000label14LabelGETFROM_STARTres1label15LabelGETFROM_STARTres2label16LabelGETFROM_STARTres3label17LabelGETFROM_STARTres4label18LabelGETFROM_STARTres5resmodbusrtumaster_01100052000label19LabelGETFROM_STARTres1label20LabelGETFROM_STARTres2label21LabelGETFROM_STARTres3label22LabelGETFROM_STARTres4label23LabelGETFROM_STARTres5resmodbusrtumaster_01100052000label24LabelGETFROM_STARTres1label25LabelGETFROM_STARTres2label26LabelGETFROM_STARTres3label27LabelGETFROM_STARTres4label28LabelGETFROM_STARTres5restruehrREMAINDER1ADD1hr165535coilcoilmodbusrtumaster_011000Truecoil2000modbusrtumaster_0110000hr2000modbusrtumaster_011001coilcoilcoil2000label9Labelcoillabel10Labelcoillabel11Labelcoillabel12Labelcoilmodbusrtumaster_0110010hr0hr0hr2000label19Labelhrlabel20Labelhrlabel21Labelhrlabel22Labelhr1","screen":[{"simulationName":"Built-in","type":"builtin","width":320,"height":240,"scale":0.78,"screenName":"","blockId":"","screenColorType":0,"id":"builtin","createTime":1727336118230}],"logicWhenNum":1,"customList":[]} \ No newline at end of file +{"version":"V2.0","versionNumber":"V2.3.1","type":"cores3","components":[{"name":"screen","type":"screen","layer":0,"screenId":"builtin","screenName":"","id":"__cores3_screen","createTime":1727336118236,"x":0,"y":0,"width":320,"height":240,"backgroundColor":"#222222","size":0,"isSelected":true},{"name":"label0","type":"label","layer":1,"screenId":"builtin","screenName":"","id":"dzZnRJhL$T-nMUDX","createTime":1727337322581,"x":65,"y":8,"color":"#ffffff","backgroundColor":"#222222","text":"co","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false},{"name":"label1","type":"label","layer":2,"screenId":"builtin","screenName":"","id":"xAEEi$qkH-^`mN6*","createTime":1727337322581,"x":135,"y":8,"color":"#ffffff","backgroundColor":"#222222","text":"di","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false},{"name":"label2","type":"label","layer":3,"screenId":"builtin","screenName":"","id":"uobZ-6s%cS@BuZDu","createTime":1727337322581,"x":205,"y":8,"color":"#ffffff","backgroundColor":"#222222","text":"hr","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false},{"name":"label3","type":"label","layer":4,"screenId":"builtin","screenName":"","id":"j6uwPW2zTZ5w_M_&","createTime":1727337322581,"x":275,"y":8,"color":"#ffffff","backgroundColor":"#222222","text":"ir","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false},{"name":"label4","type":"label","layer":5,"screenId":"builtin","screenName":"","id":"q4welMDf1R#csbyl","createTime":1727337394881,"x":4,"y":45,"color":"#ffffff","backgroundColor":"#222222","text":"1000","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false},{"name":"label5","type":"label","layer":6,"screenId":"builtin","screenName":"","id":"k8#RLW4mVwc!iqO3","createTime":1727337394881,"x":4,"y":85,"color":"#ffffff","backgroundColor":"#222222","text":"1001","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false},{"name":"label6","type":"label","layer":7,"screenId":"builtin","screenName":"","id":"sCqtJrwOpS+Mnt0`","createTime":1727337394881,"x":4,"y":125,"color":"#ffffff","backgroundColor":"#222222","text":"1002","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false},{"name":"label7","type":"label","layer":8,"screenId":"builtin","screenName":"","id":"vLmpS!KBrS6$kpV@","createTime":1727337394881,"x":4,"y":165,"color":"#ffffff","backgroundColor":"#222222","text":"1003","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false},{"name":"label8","type":"label","layer":9,"screenId":"builtin","screenName":"","id":"zYvx3_&Bxu^f$dsI","createTime":1727337394881,"x":4,"y":205,"color":"#ffffff","backgroundColor":"#222222","text":"1004","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false},{"name":"label9","type":"label","layer":10,"screenId":"builtin","screenName":"","id":"rxeWJeubjBcD*CL@","createTime":1727337482956,"x":65,"y":45,"color":"#ffffff","backgroundColor":"#222222","text":"a00","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false},{"name":"label14","type":"label","layer":11,"screenId":"builtin","screenName":"","id":"bC`kWaw=mRLdl9#t","createTime":1727337482956,"x":135,"y":45,"color":"#ffffff","backgroundColor":"#222222","text":"a01","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false},{"name":"label19","type":"label","layer":12,"screenId":"builtin","screenName":"","id":"t^TP$dbkypQY89@G","createTime":1727337482956,"x":205,"y":45,"color":"#ffffff","backgroundColor":"#222222","text":"a02","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false},{"name":"label24","type":"label","layer":13,"screenId":"builtin","screenName":"","id":"oht9qboi6d!C7PLM","createTime":1727337482956,"x":275,"y":45,"color":"#ffffff","backgroundColor":"#222222","text":"a03","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false},{"name":"label10","type":"label","layer":14,"screenId":"builtin","screenName":"","id":"inX@Hy@rEXPe6H`l","createTime":1727337482956,"x":65,"y":85,"color":"#ffffff","backgroundColor":"#222222","text":"a10","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false},{"name":"label15","type":"label","layer":15,"screenId":"builtin","screenName":"","id":"ggyl%AeCcT4wtK%2","createTime":1727337482956,"x":135,"y":85,"color":"#ffffff","backgroundColor":"#222222","text":"a11","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false},{"name":"label20","type":"label","layer":16,"screenId":"builtin","screenName":"","id":"x5i6%-zdXzTgYfhx","createTime":1727337482956,"x":205,"y":85,"color":"#ffffff","backgroundColor":"#222222","text":"a12","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false},{"name":"label25","type":"label","layer":17,"screenId":"builtin","screenName":"","id":"pQq`0rU375!JO4t9","createTime":1727337482956,"x":275,"y":85,"color":"#ffffff","backgroundColor":"#222222","text":"a13","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false},{"name":"label11","type":"label","layer":18,"screenId":"builtin","screenName":"","id":"w4VINK^mv&GtQo^n","createTime":1727337482956,"x":65,"y":125,"color":"#ffffff","backgroundColor":"#222222","text":"a20","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false},{"name":"label16","type":"label","layer":19,"screenId":"builtin","screenName":"","id":"hnEITM*p2-s@uDwS","createTime":1727337482956,"x":135,"y":125,"color":"#ffffff","backgroundColor":"#222222","text":"a21","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false},{"name":"label21","type":"label","layer":20,"screenId":"builtin","screenName":"","id":"a1gRk6#^TY-4oEwQ","createTime":1727337482956,"x":205,"y":125,"color":"#ffffff","backgroundColor":"#222222","text":"a22","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false},{"name":"label26","type":"label","layer":21,"screenId":"builtin","screenName":"","id":"i9Nw`1db_TxtmEsp","createTime":1727337482956,"x":275,"y":125,"color":"#ffffff","backgroundColor":"#222222","text":"a23","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false},{"name":"label12","type":"label","layer":22,"screenId":"builtin","screenName":"","id":"zLdJflicv3ayyPRj","createTime":1727337482956,"x":65,"y":165,"color":"#ffffff","backgroundColor":"#222222","text":"a30","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false},{"name":"label17","type":"label","layer":23,"screenId":"builtin","screenName":"","id":"haiN=xIwjI-KvvLD","createTime":1727337482956,"x":135,"y":165,"color":"#ffffff","backgroundColor":"#222222","text":"a31","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false},{"name":"label22","type":"label","layer":24,"screenId":"builtin","screenName":"","id":"k!aTlrR!6S`_`vU4","createTime":1727337482956,"x":205,"y":165,"color":"#ffffff","backgroundColor":"#222222","text":"a32","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false},{"name":"label27","type":"label","layer":25,"screenId":"builtin","screenName":"","id":"meV$iV&NsnV170Zm","createTime":1727337482956,"x":275,"y":165,"color":"#ffffff","backgroundColor":"#222222","text":"a33","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false},{"name":"label13","type":"label","layer":26,"screenId":"builtin","screenName":"","id":"ucAAW1M#SilbW75g","createTime":1727337482956,"x":65,"y":205,"color":"#ffffff","backgroundColor":"#222222","text":"a40","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false},{"name":"label18","type":"label","layer":27,"screenId":"builtin","screenName":"","id":"bQNoAo^8oOG!8tBz","createTime":1727337482956,"x":135,"y":205,"color":"#ffffff","backgroundColor":"#222222","text":"a41","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false},{"name":"label23","type":"label","layer":28,"screenId":"builtin","screenName":"","id":"mvwz12846%Y57UvC","createTime":1727337482956,"x":205,"y":205,"color":"#ffffff","backgroundColor":"#222222","text":"a42","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false},{"name":"label28","type":"label","layer":29,"screenId":"builtin","screenName":"","id":"qr@rN`qs^MY%4m4H","createTime":1727337482956,"x":275,"y":205,"color":"#ffffff","backgroundColor":"#222222","text":"a43","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false}],"resources":[{"software":["modbus"]},{"hardware":["hardware_button","hardware_pin_button","imu","speaker","touch","als","mic"]},{"unit":["unit_rs485"]}],"units":[{"type":"unit_rs485","name":"rs485_0","portList":["A","B","C","Custom"],"portType":"C","userPort":[22,21],"id":"u#UNPFYq#=J743`i","createTime":1752828015752,"initBlockId":"0H*Shc;h;^{XEzLD}5r7"}],"hats":[],"bases":[],"i2cs":[],"blockly":"reshrcoiltruers485_02rs485_0False115200modbusrtumaster_0rs485_0Truehr0resmodbusrtumaster_01100052000label9LabelGETFROM_STARTres1label10LabelGETFROM_STARTres2label11LabelGETFROM_STARTres3label12LabelGETFROM_STARTres4label13LabelGETFROM_STARTres5resmodbusrtumaster_01100052000label14LabelGETFROM_STARTres1label15LabelGETFROM_STARTres2label16LabelGETFROM_STARTres3label17LabelGETFROM_STARTres4label18LabelGETFROM_STARTres5resmodbusrtumaster_01100052000label19LabelGETFROM_STARTres1label20LabelGETFROM_STARTres2label21LabelGETFROM_STARTres3label22LabelGETFROM_STARTres4label23LabelGETFROM_STARTres5resmodbusrtumaster_01100052000label24LabelGETFROM_STARTres1label25LabelGETFROM_STARTres2label26LabelGETFROM_STARTres3label27LabelGETFROM_STARTres4label28LabelGETFROM_STARTres5restruehrREMAINDER1ADD1hr165535coilcoilmodbusrtumaster_011000Truecoil2000modbusrtumaster_0110000hr2000modbusrtumaster_011001coilcoilcoil2000label9Labelcoillabel10Labelcoillabel11Labelcoillabel12Labelcoilmodbusrtumaster_0110010hr0hr0hr2000label19Labelhrlabel20Labelhrlabel21Labelhrlabel22Labelhr1","screen":[{"simulationName":"Built-in","type":"builtin","width":320,"height":240,"scale":0.77,"screenName":"","blockId":"","screenColorType":0,"rotation":1,"id":"builtin","createTime":1727336118230}],"logicWhenNum":1,"customList":[]} \ No newline at end of file diff --git a/examples/softwave/modbus/cores3_rtu_master_example.py b/examples/softwave/modbus/cores3_rtu_master_example.py index 5fa1be65..8285d2c3 100644 --- a/examples/softwave/modbus/cores3_rtu_master_example.py +++ b/examples/softwave/modbus/cores3_rtu_master_example.py @@ -86,6 +86,7 @@ def setup(): coil M5.begin() + Widgets.setRotation(1) Widgets.fillScreen(0x222222) label0 = Widgets.Label("co", 65, 8, 1.0, 0xFFFFFF, 0x222222, Widgets.FONTS.DejaVu18) label1 = Widgets.Label("di", 135, 8, 1.0, 0xFFFFFF, 0x222222, Widgets.FONTS.DejaVu18) @@ -127,7 +128,7 @@ def setup(): parity=None, ctrl_pin=None, ) - modbusrtumaster_0 = modbus.ModbusRTUMaster(uart=rs485_0, verbose=False) + modbusrtumaster_0 = modbus.ModbusRTUMaster(uart=rs485_0, verbose=True) hr = 0 res = modbusrtumaster_0.read_coils(1, 1000, 5, timeout=2000) label9.setText(str(res[0])) diff --git a/examples/softwave/modbus/cores3_rtu_slave_example.m5f2 b/examples/softwave/modbus/cores3_rtu_slave_example.m5f2 index c202938b..a467c8ba 100644 --- a/examples/softwave/modbus/cores3_rtu_slave_example.m5f2 +++ b/examples/softwave/modbus/cores3_rtu_slave_example.m5f2 @@ -1 +1 @@ -{"version":"V2.0","versionNumber":"V2.1.5","type":"cores3","components":[{"name":"screen","type":"screen","layer":0,"screenId":"builtin","screenName":"","id":"__cores3_screen","createTime":1728358882080,"x":0,"y":0,"width":320,"height":240,"backgroundColor":"#222222","size":0,"isSelected":true},{"name":"label0","type":"label","layer":1,"screenId":"builtin","screenName":"","id":"umk70hLJ`oWyu7$E","createTime":1728440117096,"x":79,"y":60,"color":"#ffffff","backgroundColor":"#222222","text":"label0","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false},{"name":"label1","type":"label","layer":1,"screenId":"builtin","screenName":"","id":"e9oWnM#ZVND&2*YG","createTime":1728461615964,"x":73,"y":100,"color":"#ffffff","backgroundColor":"#222222","text":"label1","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false},{"name":"label2","type":"label","layer":2,"screenId":"builtin","screenName":"","id":"e9c=#SNGqzgI`dUy","createTime":1728461618328,"x":67,"y":135,"color":"#ffffff","backgroundColor":"#222222","text":"label2","engine":"gfx","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false}],"resources":[{"software":["mqtt","modbus"]},{"hardware":["hardware_button","hardware_pin_button","imu","speaker","touch","als","mic"]},{"unit":["unit_iso485"]}],"units":[{"type":"unit_iso485","name":"iso485_0","portList":["A","B","C","Custom"],"portType":"C","userPort":[22,21],"id":"lTUZgUM$IM%4m4i0","createTime":1728461603623,"initBlockId":"q#sIQm[e,wUj@M:]8UD!"}],"hats":[],"bases":[],"i2cs":[],"blockly":"register1slave_value1starting_register1slave_list1starting_register4slave_list4register2slave_value2starting_register2slave_list2starting_register6slave_list6starting_register5slave_list5starting_register3slave_list3trueiso485_02iso485_0False115200modbusrtuslave_0iso485_01Falsemodbusrtuslave_00Truemodbusrtuslave_01Falsemodbusrtuslave_00Truemodbusrtuslave_01Falsemodbusrtuslave_000x0102modbusrtuslave_010x0304modbusrtuslave_000x0102modbusrtuslave_010x0304truemodbusrtuslave_0modbusrtuslave_0starting_register1slave_list1label0read coilslabel1read coilsstarting_register1label2read coilsslave_list1modbusrtuslave_0register1slave_value1label0write single coillabel1write single coilregister1label2write single coilslave_value1modbusrtuslave_0starting_register4slave_list4label0read input registerlabel1read input registerstarting_register4label2read input registerslave_list4modbusrtuslave_0register2slave_value2label0write single registerslabel1write single registersregister2label2write single registersslave_value2modbusrtuslave_0starting_register2slave_list2label0read discrete inputlabel1read coilsstarting_register2label2read coilsslave_list2modbusrtuslave_0starting_register6slave_list6label0write multiple registerslabel1write multiple registersslave_list6label2write multiple registersslave_list6modbusrtuslave_0starting_register5slave_list5label0write multiple coilslabel1write multiple coilsslave_list5label2write multiple coilsslave_list5modbusrtuslave_0starting_register3slave_list3label0read holding registerlabel1read holding registerstarting_register3label2read holding registerslave_list3","screen":[{"simulationName":"Built-in","type":"builtin","width":320,"height":240,"scale":0.78,"screenName":"","blockId":"","screenColorType":0,"id":"builtin","createTime":1728358882080}],"logicWhenNum":0,"customList":[]} \ No newline at end of file +{"version":"V2.0","versionNumber":"V2.3.1","type":"cores3","components":[{"name":"screen","type":"screen","layer":0,"screenId":"builtin","screenName":"","id":"__cores3_screen","createTime":1728358882080,"x":0,"y":0,"width":320,"height":240,"backgroundColor":"#222222","size":0,"isSelected":true},{"name":"label0","type":"label","layer":1,"screenId":"builtin","screenName":"","id":"umk70hLJ`oWyu7$E","createTime":1728440117096,"x":79,"y":60,"color":"#ffffff","backgroundColor":"#222222","text":"label0","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false},{"name":"label1","type":"label","layer":1,"screenId":"builtin","screenName":"","id":"e9oWnM#ZVND&2*YG","createTime":1728461615964,"x":73,"y":100,"color":"#ffffff","backgroundColor":"#222222","text":"label1","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false},{"name":"label2","type":"label","layer":2,"screenId":"builtin","screenName":"","id":"e9c=#SNGqzgI`dUy","createTime":1728461618328,"x":67,"y":135,"color":"#ffffff","backgroundColor":"#222222","text":"label2","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false}],"resources":[{"software":["mqtt","modbus"]},{"hardware":["hardware_button","hardware_pin_button","imu","speaker","touch","als","mic"]},{"unit":["unit_iso485"]}],"units":[{"type":"unit_iso485","name":"iso485_0","portList":["A","B","C","Custom"],"portType":"C","userPort":[22,21],"id":"lTUZgUM$IM%4m4i0","createTime":1752827994690,"initBlockId":"q#sIQm[e,wUj@M:]8UD!"}],"hats":[],"bases":[],"i2cs":[],"blockly":"starting_register1slave_list1register1slave_value1starting_register4slave_list4register2slave_value2starting_register2slave_list2starting_register6slave_list6starting_register5slave_list5starting_register3slave_list3trueiso485_02iso485_0False115200modbusrtuslave_0iso485_01Truemodbusrtuslave_01000Truemodbusrtuslave_01001Falsemodbusrtuslave_01002Truemodbusrtuslave_01003Falsemodbusrtuslave_01004Truemodbusrtuslave_01000Truemodbusrtuslave_01001Falsemodbusrtuslave_01002Truemodbusrtuslave_01003Falsemodbusrtuslave_01004Truemodbusrtuslave_010000x0102modbusrtuslave_010010x0304modbusrtuslave_010020x0304modbusrtuslave_010030x0304modbusrtuslave_010040x0304modbusrtuslave_010000x0102modbusrtuslave_010010x0304modbusrtuslave_010020x0304modbusrtuslave_010030x0304modbusrtuslave_010040x0304truemodbusrtuslave_0100modbusrtuslave_0starting_register1slave_list1label0read coilslabel1read coilsstarting_register1label2read coilsslave_list1modbusrtuslave_0register1slave_value1label0write single coillabel1write single coilregister1label2write single coilslave_value1modbusrtuslave_0starting_register4slave_list4label0read input registerlabel1read input registerstarting_register4label2read input registerslave_list4modbusrtuslave_0register2slave_value2label0write single registerslabel1write single registersregister2label2write single registersslave_value2modbusrtuslave_0starting_register2slave_list2label0read discrete inputlabel1read coilsstarting_register2label2read coilsslave_list2modbusrtuslave_0starting_register6slave_list6label0write multiple registerslabel1write multiple registersslave_list6label2write multiple registersslave_list6modbusrtuslave_0starting_register5slave_list5label0write multiple coilslabel1write multiple coilsslave_list5label2write multiple coilsslave_list5modbusrtuslave_0starting_register3slave_list3label0read holding registerlabel1read holding registerstarting_register3label2read holding registerslave_list3","screen":[{"simulationName":"Built-in","type":"builtin","width":320,"height":240,"scale":0.77,"screenName":"","blockId":"","screenColorType":0,"rotation":1,"id":"builtin","createTime":1728358882080}],"logicWhenNum":0,"customList":[]} \ No newline at end of file diff --git a/examples/softwave/modbus/cores3_rtu_slave_example.py b/examples/softwave/modbus/cores3_rtu_slave_example.py index e2ed9f80..010ac962 100644 --- a/examples/softwave/modbus/cores3_rtu_slave_example.py +++ b/examples/softwave/modbus/cores3_rtu_slave_example.py @@ -16,10 +16,10 @@ iso485_0 = None -register1 = None -slave_value1 = None starting_register1 = None slave_list1 = None +register1 = None +slave_value1 = None starting_register4 = None slave_list4 = None register2 = None @@ -34,17 +34,17 @@ slave_list3 = None -def modbus_write_single_coil_cb(args): +def modbus_read_coils_cb(args): global \ label0, \ label1, \ label2, \ modbusrtuslave_0, \ iso485_0, \ - register1, \ - slave_value1, \ starting_register1, \ slave_list1, \ + register1, \ + slave_value1, \ starting_register4, \ slave_list4, \ register2, \ @@ -57,23 +57,23 @@ def modbus_write_single_coil_cb(args): slave_list5, \ starting_register3, \ slave_list3 - _, register1, slave_value1 = args - label0.setText(str("write single coil")) - label1.setText(str(register1)) - label2.setText(str(slave_value1)) + _, starting_register1, slave_list1 = args + label0.setText(str("read coils")) + label1.setText(str(starting_register1)) + label2.setText(str(slave_list1)) -def modbus_read_coils_cb(args): +def modbus_write_single_coil_cb(args): global \ label0, \ label1, \ label2, \ modbusrtuslave_0, \ iso485_0, \ - register1, \ - slave_value1, \ starting_register1, \ slave_list1, \ + register1, \ + slave_value1, \ starting_register4, \ slave_list4, \ register2, \ @@ -86,10 +86,10 @@ def modbus_read_coils_cb(args): slave_list5, \ starting_register3, \ slave_list3 - _, starting_register1, slave_list1 = args - label0.setText(str("read coils")) - label1.setText(str(starting_register1)) - label2.setText(str(slave_list1)) + _, register1, slave_value1 = args + label0.setText(str("write single coil")) + label1.setText(str(register1)) + label2.setText(str(slave_value1)) def modbus_read_input_registers_cb(args): @@ -99,10 +99,10 @@ def modbus_read_input_registers_cb(args): label2, \ modbusrtuslave_0, \ iso485_0, \ - register1, \ - slave_value1, \ starting_register1, \ slave_list1, \ + register1, \ + slave_value1, \ starting_register4, \ slave_list4, \ register2, \ @@ -128,10 +128,10 @@ def modbus_write_single_registers_cb(args): label2, \ modbusrtuslave_0, \ iso485_0, \ - register1, \ - slave_value1, \ starting_register1, \ slave_list1, \ + register1, \ + slave_value1, \ starting_register4, \ slave_list4, \ register2, \ @@ -157,10 +157,10 @@ def modbus_read_discrete_inputs_cb(args): label2, \ modbusrtuslave_0, \ iso485_0, \ - register1, \ - slave_value1, \ starting_register1, \ slave_list1, \ + register1, \ + slave_value1, \ starting_register4, \ slave_list4, \ register2, \ @@ -186,10 +186,10 @@ def modbus_write_multiple_registers_cb(args): label2, \ modbusrtuslave_0, \ iso485_0, \ - register1, \ - slave_value1, \ starting_register1, \ slave_list1, \ + register1, \ + slave_value1, \ starting_register4, \ slave_list4, \ register2, \ @@ -215,10 +215,10 @@ def modbus_write_multiple_coils_cb(args): label2, \ modbusrtuslave_0, \ iso485_0, \ - register1, \ - slave_value1, \ starting_register1, \ slave_list1, \ + register1, \ + slave_value1, \ starting_register4, \ slave_list4, \ register2, \ @@ -244,10 +244,10 @@ def modbus_read_holding_registers_cb(args): label2, \ modbusrtuslave_0, \ iso485_0, \ - register1, \ - slave_value1, \ starting_register1, \ slave_list1, \ + register1, \ + slave_value1, \ starting_register4, \ slave_list4, \ register2, \ @@ -273,10 +273,10 @@ def setup(): label2, \ modbusrtuslave_0, \ iso485_0, \ - register1, \ - slave_value1, \ starting_register1, \ slave_list1, \ + register1, \ + slave_value1, \ starting_register4, \ slave_list4, \ register2, \ @@ -291,12 +291,13 @@ def setup(): slave_list3 M5.begin() + Widgets.setRotation(1) Widgets.fillScreen(0x222222) label0 = Widgets.Label("label0", 79, 60, 1.0, 0xFFFFFF, 0x222222, Widgets.FONTS.DejaVu18) label1 = Widgets.Label("label1", 73, 100, 1.0, 0xFFFFFF, 0x222222, Widgets.FONTS.DejaVu18) label2 = Widgets.Label("label2", 67, 135, 1.0, 0xFFFFFF, 0x222222, Widgets.FONTS.DejaVu18) - iso485_0 = ISO485Unit(2, port=(18, 17)) + iso485_0 = ISO485Unit(2, port=(1, 2)) iso485_0.init( tx_pin=None, rx_pin=None, @@ -306,11 +307,11 @@ def setup(): parity=None, ctrl_pin=None, ) - modbusrtuslave_0 = modbus.ModbusRTUSlave(iso485_0, device_address=1, verbose=False) + modbusrtuslave_0 = modbus.ModbusRTUSlave(iso485_0, device_address=1, verbose=True) + modbusrtuslave_0.set_callback(modbusrtuslave_0.READ_COILS_EVENT, modbus_read_coils_cb) modbusrtuslave_0.set_callback( modbusrtuslave_0.WRITE_SINGLE_COIL_EVENT, modbus_write_single_coil_cb ) - modbusrtuslave_0.set_callback(modbusrtuslave_0.READ_COILS_EVENT, modbus_read_coils_cb) modbusrtuslave_0.set_callback( modbusrtuslave_0.READ_INPUT_REGISTERS_EVENT, modbus_read_input_registers_cb ) @@ -329,14 +330,26 @@ def setup(): modbusrtuslave_0.set_callback( modbusrtuslave_0.READ_HOLDING_REGISTERS_EVENT, modbus_read_holding_registers_cb ) - modbusrtuslave_0.add_coil(0, True) - modbusrtuslave_0.add_coil(1, False) - modbusrtuslave_0.add_discrete_input(0, True) - modbusrtuslave_0.add_discrete_input(1, False) - modbusrtuslave_0.add_holding_register(0, 0x0102) - modbusrtuslave_0.add_holding_register(1, 0x0304) - modbusrtuslave_0.add_input_register(0, 0x0102) - modbusrtuslave_0.add_input_register(1, 0x0304) + modbusrtuslave_0.add_coil(1000, True) + modbusrtuslave_0.add_coil(1001, False) + modbusrtuslave_0.add_coil(1002, True) + modbusrtuslave_0.add_coil(1003, False) + modbusrtuslave_0.add_coil(1004, True) + modbusrtuslave_0.add_discrete_input(1000, True) + modbusrtuslave_0.add_discrete_input(1001, False) + modbusrtuslave_0.add_discrete_input(1002, True) + modbusrtuslave_0.add_discrete_input(1003, False) + modbusrtuslave_0.add_discrete_input(1004, True) + modbusrtuslave_0.add_holding_register(1000, 0x0102) + modbusrtuslave_0.add_holding_register(1001, 0x0304) + modbusrtuslave_0.add_holding_register(1002, 0x0304) + modbusrtuslave_0.add_holding_register(1003, 0x0304) + modbusrtuslave_0.add_holding_register(1004, 0x0304) + modbusrtuslave_0.add_input_register(1000, 0x0102) + modbusrtuslave_0.add_input_register(1001, 0x0304) + modbusrtuslave_0.add_input_register(1002, 0x0304) + modbusrtuslave_0.add_input_register(1003, 0x0304) + modbusrtuslave_0.add_input_register(1004, 0x0304) def loop(): @@ -346,10 +359,10 @@ def loop(): label2, \ modbusrtuslave_0, \ iso485_0, \ - register1, \ - slave_value1, \ starting_register1, \ slave_list1, \ + register1, \ + slave_value1, \ starting_register4, \ slave_list4, \ register2, \ diff --git a/m5stack/libs/modbus/modbus/slave.py b/m5stack/libs/modbus/modbus/slave.py index e3dde660..713dac89 100644 --- a/m5stack/libs/modbus/modbus/slave.py +++ b/m5stack/libs/modbus/modbus/slave.py @@ -277,8 +277,8 @@ def _set_reg_datablock(self, reg_type: str, register: int, block: list): for reg in self.context[reg_type]: if reg["register"] <= register < reg["register"] + len(reg["value"]): idx = register - reg["register"] - for i in range(idx, len(reg["value"])): - reg["value"][i] = block[i] + for i in range(idx, idx + len(block)): + reg["value"][i] = block[i - idx] # for new_value in block: # reg["value"][idx] = new_value # idx += 1 @@ -747,6 +747,7 @@ def __init__(self, uart, verbose=False, *args, **kwargs): ignore_unit_id=kwargs.get("ignore_unit_id", False), device_address=kwargs.get("device_address", 1), ) + self.rsp = b"" async def run_async(self): self._verbose and print("starting async rtu slave") @@ -767,12 +768,15 @@ def run(self): def tick(self): if not self.stopped and self.uart.any(): - rsp = self.uart.read() + rsp = self.rsp + self.uart.read() frame = ModbusRTUFrame.parse_frame(rsp, verbose=self._verbose) if frame is None or ( self.ignore_unit_id is not True and frame.device_addr != self._device_address ): + if frame is None: + self.rsp = rsp # Save response for next tick return + self.rsp = b"" # Reset response resp = self.handle_message(frame).get_frame() self.uart.write(resp) cb = self.cb[frame.func_code] From b4502cff6e7a745f56d56c7bbb8b1bb75d50a883 Mon Sep 17 00:00:00 2001 From: lbuque Date: Wed, 9 Jul 2025 14:41:35 +0800 Subject: [PATCH 181/322] libs/m5ui: Add m5ui.M5Checkbox support. Signed-off-by: lbuque --- docs/en/m5ui/checkbox.rst | 454 +++++++++++++ docs/en/m5ui/index.rst | 1 + docs/en/m5ui/label.rst | 2 +- docs/en/refs/m5ui.checkbox.ref | 35 + .../zh_CN/LC_MESSAGES/m5ui/checkbox.po | 619 ++++++++++++++++++ .../cores3_checkbox_basic_example.m5f2 | 1 + .../checkbox/cores3_checkbox_basic_example.py | 104 +++ m5stack/libs/m5ui/__init__.py | 1 + m5stack/libs/m5ui/bar.py | 1 - m5stack/libs/m5ui/base.py | 15 + m5stack/libs/m5ui/checkbox.py | 71 ++ m5stack/libs/m5ui/manifest.py | 1 + 12 files changed, 1303 insertions(+), 2 deletions(-) create mode 100644 docs/en/m5ui/checkbox.rst create mode 100644 docs/en/refs/m5ui.checkbox.ref create mode 100644 docs/locales/zh_CN/LC_MESSAGES/m5ui/checkbox.po create mode 100644 examples/m5ui/checkbox/cores3_checkbox_basic_example.m5f2 create mode 100644 examples/m5ui/checkbox/cores3_checkbox_basic_example.py create mode 100644 m5stack/libs/m5ui/checkbox.py diff --git a/docs/en/m5ui/checkbox.rst b/docs/en/m5ui/checkbox.rst new file mode 100644 index 00000000..a8d2128c --- /dev/null +++ b/docs/en/m5ui/checkbox.rst @@ -0,0 +1,454 @@ +.. currentmodule:: m5ui + +M5Checkbox +========== + +.. include:: ../refs/m5ui.checkbox.ref + +M5Checkbox is a widget that can be used to create checkboxes in the user interface. It can be used to allow users to select or deselect options with a visual indicator. + +UiFlow2 Example +--------------- + +basic checkbox +^^^^^^^^^^^^^^ + +Open the |cores3_checkbox_basic_example.m5f2| project in UiFlow2. + +This example creates a basic checkbox that can be checked and unchecked. + +UiFlow2 Code Block: + + |cores3_checkbox_basic_example.png| + +Example output: + + None + + +MicroPython Example +------------------- + +basic checkbox +^^^^^^^^^^^^^^ + +This example creates a basic checkbox that can be checked and unchecked. + +MicroPython Code Block: + + .. literalinclude:: ../../../examples/m5ui/checkbox/cores3_checkbox_basic_example.py + :language: python + :linenos: + +Example output: + + None + + +**API** +------- + +M5Checkbox +^^^^^^^^^^ + +.. autoclass:: m5ui.checkbox.M5Checkbox + :members: + + .. py:method:: set_flag(flag, value) + + Set a flag on the object. If ``value`` is True, the flag is added; if False, the flag is removed. + + :param int flag: The flag to set. + :param bool value: If True, the flag is added; if False, the flag is removed. + :return: None + + UiFlow2 Code Block: + + |set_flag.png| + + MicroPython Code Block: + + .. code-block:: python + + checkbox_0.set_flag(lv.obj.FLAG.HIDDEN, True) + + + .. py:method:: toggle_flag(flag) + + Toggle a flag on the object. If the flag is set, it is removed; if not set, it is added. + + :param int flag: The flag to toggle. + :return: None + + UiFlow2 Code Block: + + |toggle_flag.png| + + MicroPython Code Block: + + .. code-block:: python + + checkbox_0.toggle_flag(lv.obj.FLAG.HIDDEN) + + + .. py:method:: set_state(state, value) + + Set the state of the checkbox. If ``value`` is True, the state is set; if False, the state is unset. + + :param int state: The state to set. + :param bool value: If True, the state is set; if False, the state is unset. + :return: None + + UiFlow2 Code Block: + + |set_state.png| + + MicroPython Code Block: + + .. code-block:: python + + checkbox_0.set_state(lv.STATE.CHECKED, True) + + + .. py:method:: toggle_state(state) + + Toggle the state of the checkbox. If the state is set, it is unset; if not set, it is set. + + :param int state: The state to toggle. + :return: None + + UiFlow2 Code Block: + + |toggle_state.png| + + MicroPython Code Block: + + .. code-block:: python + + checkbox_0.toggle_state(lv.STATE.CHECKED) + + .. py:method:: set_style_text_font(font, part) + + Set the font of the checkbox text. + + :param lv.lv_font_t font: The font to set. + :param int part: The part of the object to apply the style to (e.g., lv.PART.MAIN). + :return: None + + UiFlow2 Code Block: + + |set_style_text_font.png| + + MicroPython Code Block: + + .. code-block:: python + + checkbox_0.set_style_text_font(lv.font_montserrat_14, lv.PART.MAIN | lv.STATE.DEFAULT) + + + .. py:method:: set_text_color(color, opa, part) + + Set the color of the checkbox text. + + :param int color: The color to set. + :param int opa: The opacity of the color. The value should be between 0 (transparent) and 255 (opaque). + :param int part: The part of the object to apply the style to (e.g., lv.PART.MAIN). + :return: None + + UiFlow2 Code Block: + + |set_text_color.png| + + MicroPython Code Block: + + .. code-block:: python + + checkbox_0.set_text_color(lv.color_hex(0x000000), 255, lv.PART.MAIN | lv.STATE.DEFAULT) + checkbox_0.set_text_color(lv.color_hex(0x000000), 255, lv.PART.MAIN | lv.STATE.CHECKED) + + .. py:method:: set_bg_color(color, opa, part) + + Set the background color of the checkbox indicator. + + :param int color: The color to set. + :param int opa: The opacity of the color. The value should be between 0 (transparent) and 255 (opaque). + :param int part: The part of the object to apply the style to (e.g., lv.PART.INDICATOR). + :return: None + + UiFlow2 Code Block: + + |set_bg_color.png| + + MicroPython Code Block: + + .. code-block:: python + + checkbox_0.set_bg_color(lv.color_hex(0xFFFFFF), 255, lv.PART.INDICATOR | lv.STATE.DEFAULT) + checkbox_0.set_bg_color(lv.color_hex(0x2196F3), 255, lv.PART.INDICATOR | lv.STATE.CHECKED) + + .. py:method:: set_border_color(color, opa, part) + + Set the border color of the checkbox indicator. + + :param int color: The color to set. + :param int opa: The opacity of the color. The value should be between 0 (transparent) and 255 (opaque). + :param int part: The part of the object to apply the style to (e.g., lv.PART.INDICATOR). + :return: None + + UiFlow2 Code Block: + + |set_border_color.png| + + MicroPython Code Block: + + .. code-block:: python + + checkbox_0.set_border_color(lv.color_hex(0x2196F3), 255, lv.PART.INDICATOR | lv.STATE.DEFAULT) + + .. py:method:: set_pos(x, y) + + Set the position of the checkbox. + + :param int x: The x-coordinate of the checkbox. + :param int y: The y-coordinate of the checkbox. + :return: None + + UiFlow2 Code Block: + + |set_pos.png| + + MicroPython Code Block: + + .. code-block:: python + + checkbox_0.set_pos(100, 100) + + .. py:method:: set_x(x) + + Set the x-coordinate of the checkbox. + + :param int x: The x-coordinate of the checkbox. + :return: None + + UiFlow2 Code Block: + + |set_x.png| + + MicroPython Code Block: + + .. code-block:: python + + checkbox_0.set_x(100) + + .. py:method:: set_y(y) + + Set the y-coordinate of the checkbox. + + :param int y: The y-coordinate of the checkbox. + :return: None + + UiFlow2 Code Block: + + |set_y.png| + + MicroPython Code Block: + + .. code-block:: python + + checkbox_0.set_y(100) + + .. py:method:: set_size(width, height) + + Set the size of the checkbox. + + :param int width: The width of the checkbox. + :param int height: The height of the checkbox. + :return: None + + UiFlow2 Code Block: + + |set_size.png| + + MicroPython Code Block: + + .. code-block:: python + + checkbox_0.set_size(100, 50) + + .. py:method:: set_width(width) + + Set the width of the checkbox. + + :param int width: The width of the checkbox. + :return: None + + UiFlow2 Code Block: + + |set_width.png| + + MicroPython Code Block: + + .. code-block:: python + + checkbox_0.set_width(100) + + .. py:method:: get_width() + + Get the width of the checkbox. + + :return: The width of the checkbox. + :rtype: int + + UiFlow2 Code Block: + + |get_width.png| + + MicroPython Code Block: + + .. code-block:: python + + checkbox_0.get_width() + + .. py:method:: set_height(height) + + Set the height of the checkbox. + + :param int height: The height of the checkbox. + :return: None + + UiFlow2 Code Block: + + |set_height.png| + + MicroPython Code Block: + + .. code-block:: python + + checkbox_0.set_height(50) + + .. py:method:: get_height() + + Get the height of the checkbox. + + :return: The height of the checkbox. + :rtype: int + + UiFlow2 Code Block: + + |get_height.png| + + MicroPython Code Block: + + .. code-block:: python + + checkbox_0.get_height() + + .. py:method:: align_to(obj, align, x, y) + + Align the checkbox to another object. + + :param lv.obj obj: The object to align to. + :param int align: The alignment type. + :param int x: The x-offset from the aligned object. + :param int y: The y-offset from the aligned object. + :return: None + + UiFlow2 Code Block: + + |align_to.png| + + MicroPython Code Block: + + .. code-block:: python + + checkbox_0.align_to(page_0, lv.ALIGN.CENTER, 0, 0) + + .. py:method:: set_style_radius(radius, part) + + Set the corner radius of the checkbox indicator. + + :param int radius: The radius to set. + :param int part: The part of the object to apply the style to (e.g., lv.PART.INDICATOR). + :return: None + + UiFlow2 Code Block: + + |set_style_radius.png| + + MicroPython Code Block: + + .. code-block:: python + + checkbox_0.set_style_radius(10, lv.PART.INDICATOR | lv.STATE.DEFAULT) + + .. py:method:: set_text(text) + + Set the text of the checkbox. + + :param str text: The text to set. + :return: None + + UiFlow2 Code Block: + + |set_text.png| + + MicroPython Code Block: + + .. code-block:: python + + checkbox_0.set_text("Checkbox") + + .. py:method:: get_text() + + Get the text of the checkbox. + + :return: The text of the checkbox. + :rtype: str + + UiFlow2 Code Block: + + |get_text.png| + + MicroPython Code Block: + + .. code-block:: python + + checkbox_0.get_text() + + + .. py:method:: add_event_cb(handler, event, user_data) + + Add an event callback to the checkbox. The callback will be called when the specified event occurs. + + :param function handler: The callback function to call. + :param int event: The event to listen for. + :param Any user_data: Optional user data to pass to the callback. + :return: None + + UiFlow2 Code Block: + + |event.png| + + MicroPython Code Block: + + .. code-block:: python + + def checkbox_0_checked_event(event_struct): + global checkbox_0 + print("Checkbox checked!") + + def checkbox_0_unchecked_event(event_struct): + global checkbox_0 + print("Checkbox unchecked!") + + def checkbox_0_event_handler(event_struct): + global checkbox_0 + event = event_struct.code + if event == lv.EVENT.VALUE_CHANGED and checkbox_0.has_state(lv.STATE.CHECKED): + checkbox_0_checked_event(event_struct) + elif event == lv.EVENT.VALUE_CHANGED and not checkbox_0.has_state(lv.STATE.CHECKED): + checkbox_0_unchecked_event(event_struct) + return + + checkbox_0.add_event_cb(checkbox_0_event_handler, lv.EVENT.ALL, None) + diff --git a/docs/en/m5ui/index.rst b/docs/en/m5ui/index.rst index d99c5113..2ab4572f 100644 --- a/docs/en/m5ui/index.rst +++ b/docs/en/m5ui/index.rst @@ -37,6 +37,7 @@ Classes bar.rst button.rst calendar.rst + checkbox.rst image.rst label.rst line.rst diff --git a/docs/en/m5ui/label.rst b/docs/en/m5ui/label.rst index f39f146c..64cb2560 100644 --- a/docs/en/m5ui/label.rst +++ b/docs/en/m5ui/label.rst @@ -1,6 +1,6 @@ .. currentmodule:: m5ui -N5Label +M5Label ======= .. include:: ../refs/m5ui.label.ref diff --git a/docs/en/refs/m5ui.checkbox.ref b/docs/en/refs/m5ui.checkbox.ref new file mode 100644 index 00000000..8d5edf45 --- /dev/null +++ b/docs/en/refs/m5ui.checkbox.ref @@ -0,0 +1,35 @@ + +.. |align_to.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/checkbox/align_to.png +.. |event.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/checkbox/event.png +.. |get_height.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/checkbox/get_height.png +.. |get_text.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/checkbox/get_text.png +.. |get_width.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/checkbox/get_width.png +.. |has_state.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/checkbox/has_state.png +.. |set_bg_color.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/checkbox/set_bg_color.png +.. |set_border_color.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/checkbox/set_border_color.png +.. |set_default_size.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/checkbox/set_default_size.png +.. |set_flag.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/checkbox/set_flag.png +.. |set_height.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/checkbox/set_height.png +.. |set_pos.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/checkbox/set_pos.png +.. |set_size.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/checkbox/set_size.png +.. |set_state.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/checkbox/set_state.png +.. |set_style_radius.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/checkbox/set_style_radius.png +.. |set_style_text_font.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/checkbox/set_style_text_font.png +.. |set_text.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/checkbox/set_text.png +.. |set_text_color.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/checkbox/set_text_color.png +.. |set_width.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/checkbox/set_width.png +.. |set_x.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/checkbox/set_x.png +.. |set_y.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/checkbox/set_y.png +.. |toggle_flag.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/checkbox/toggle_flag.png +.. |toggle_state.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/checkbox/toggle_state.png + +.. |cores3_checkbox_basic_example.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/checkbox/cores3_checkbox_basic_example.png + +.. |cores3_checkbox_basic_example.m5f2| raw:: html + + + cores3_checkbox_basic_example.m5f2 + diff --git a/docs/locales/zh_CN/LC_MESSAGES/m5ui/checkbox.po b/docs/locales/zh_CN/LC_MESSAGES/m5ui/checkbox.po new file mode 100644 index 00000000..68cbf8e3 --- /dev/null +++ b/docs/locales/zh_CN/LC_MESSAGES/m5ui/checkbox.po @@ -0,0 +1,619 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2016 - 2025 M5Stack Technology Co., Ltd +# This file is distributed under the same license as the UIFlow2 Programming +# Guide package. +# FIRST AUTHOR , 2025. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: UIFlow2 Programming Guide \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-07-27 11:48+0800\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: zh_CN\n" +"Language-Team: zh_CN \n" +"Plural-Forms: nplurals=1; plural=0;\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.17.0\n" + +#: ../../en/m5ui/checkbox.rst:4 ../../en/m5ui/checkbox.rst:52 +#: b569382d86f2482aad9259cc59c0ba5a c77ef40216de4a7db2d8ba6770921bea +msgid "M5Checkbox" +msgstr "" + +#: ../../en/m5ui/checkbox.rst:8 4e102ce876694f449ceab21b9ba42867 +msgid "" +"M5Checkbox is a widget that can be used to create checkboxes in the user " +"interface. It can be used to allow users to select or deselect options " +"with a visual indicator." +msgstr "" +"M5Checkbox是一个可以在用户界面中创建复选框的控件。它可以用来允许用户通过可视化指示器选择或取消选择选项。" + +#: ../../en/m5ui/checkbox.rst:11 dc9719ff45b94a0d9ada8d773074d970 +msgid "UiFlow2 Example" +msgstr "UiFlow2 应用示例" + +#: ../../en/m5ui/checkbox.rst:14 ../../en/m5ui/checkbox.rst:33 +#: 2808d32e80744a68a11cff2eb2d6f416 5e4964b0d16e49fa8acae6caf915e86b +msgid "basic checkbox" +msgstr "" + +#: ../../en/m5ui/checkbox.rst:16 ff909c6af17a4d71a21d3ade2e3b99b1 +msgid "Open the |cores3_checkbox_basic_example.m5f2| project in UiFlow2." +msgstr "在UiFlow2中打开|cores3_checkbox_basic_example.m5f2|项目。" + +#: ../../en/m5ui/checkbox.rst:18 ../../en/m5ui/checkbox.rst:35 +#: 06b9d4d2d8da4af099ca46bf7e4bdf19 ac7dd833bdd440fe9955c068a6e14936 +msgid "This example creates a basic checkbox that can be checked and unchecked." +msgstr "此示例创建一个可以选中和取消选中的基础复选框。" + +#: ../../en/m5ui/checkbox.rst:20 ../../en/m5ui/checkbox.rst:65 +#: ../../en/m5ui/checkbox.rst:83 ../../en/m5ui/checkbox.rst:102 +#: ../../en/m5ui/checkbox.rst:120 ../../en/m5ui/checkbox.rst:138 +#: ../../en/m5ui/checkbox.rst:158 ../../en/m5ui/checkbox.rst:178 +#: ../../en/m5ui/checkbox.rst:198 ../../en/m5ui/checkbox.rst:216 +#: ../../en/m5ui/checkbox.rst:233 ../../en/m5ui/checkbox.rst:250 +#: ../../en/m5ui/checkbox.rst:268 ../../en/m5ui/checkbox.rst:285 +#: ../../en/m5ui/checkbox.rst:302 ../../en/m5ui/checkbox.rst:319 +#: ../../en/m5ui/checkbox.rst:336 ../../en/m5ui/checkbox.rst:356 +#: ../../en/m5ui/checkbox.rst:374 ../../en/m5ui/checkbox.rst:391 +#: ../../en/m5ui/checkbox.rst:408 ../../en/m5ui/checkbox.rst:428 +#: 0765111ddd434818bb6adc8a498b27ed 083bd4b52d9f44c4b5a96a20c1940076 +#: 0c2064f223a7456ebad8fcaceb34a095 1bbbfe873b534a4fb7da34a938c340f2 +#: 20f92579c02340b499fb4c3e9b7b6e3f 5dc56428d8ea4d6d9f2e3ead88774031 +#: 626a09ec181043109286b0028cfd6685 66fa55889c2747c48a66ad719e793cc0 +#: 6a9ec05e83fc4a0d85e5cdaa77a3853c 6d967554f02148f9aa03e175ac02077f +#: 932862a08b1446fe85ebc90baa476fbe 93470fa7c65b48a188ed3b3403eafa13 +#: 97bbbbd5e9554b41ad6fcd205a675d6f a722477d45934843bc63a5b133c4e1d3 +#: c3558b8e7f2f45b68503c044afe1195f c3a802969cf64522b72d43b093bf4c16 +#: d06491045f734120ae458bdb328cd3fa e71c215353b14a99a054f4a9e88e335a +#: e8a6a946f322482e9bd0c2c51882bfd5 e93b3512a55542d7858198bd9e151768 +#: ef915609734c400889f3853831893a6b faa7745a2cdb49ab8cb167133f003612 +#: fe28f98abd0f4860bd99dd3f319d041e m5ui.checkbox.M5Checkbox:13 of +msgid "UiFlow2 Code Block:" +msgstr "UiFlow2 代码块:" + +#: ../../en/m5ui/checkbox.rst:22 1cdb9d1ef9434720bc9ce02fcec20ba1 +msgid "|cores3_checkbox_basic_example.png|" +msgstr "" + +#: ../../en/refs/m5ui.checkbox.ref:26 9a4a793fe8324a4aad75ba1dae82f3f8 +msgid "cores3_checkbox_basic_example.png" +msgstr "" + +#: ../../en/m5ui/checkbox.rst:24 ../../en/m5ui/checkbox.rst:43 +#: 2ffcd115ce2e40e2bb2879edaa81c510 925a0b9a982041adbdacf8457c854ec6 +msgid "Example output:" +msgstr "示例输出:" + +#: ../../en/m5ui/checkbox.rst:26 ../../en/m5ui/checkbox.rst:45 +#: ../../en/m5ui/checkbox.rst:63 ../../en/m5ui/checkbox.rst:81 +#: ../../en/m5ui/checkbox.rst:100 ../../en/m5ui/checkbox.rst:118 +#: ../../en/m5ui/checkbox.rst:136 ../../en/m5ui/checkbox.rst:156 +#: ../../en/m5ui/checkbox.rst:176 ../../en/m5ui/checkbox.rst:196 +#: ../../en/m5ui/checkbox.rst:214 ../../en/m5ui/checkbox.rst:231 +#: ../../en/m5ui/checkbox.rst:248 ../../en/m5ui/checkbox.rst:266 +#: ../../en/m5ui/checkbox.rst:283 ../../en/m5ui/checkbox.rst:317 +#: ../../en/m5ui/checkbox.rst:354 ../../en/m5ui/checkbox.rst:372 +#: ../../en/m5ui/checkbox.rst:389 ../../en/m5ui/checkbox.rst:426 +#: 00f8638f44b64b67934bad9a08dd1848 079475235cc84330a3de346154998a83 +#: 07c72b9a8b814483899068d7a74bd6dd 2357d9d03cc84a108adf2ec551b301e5 +#: 24a32ffc7c76451daf76f706d4089dd0 36fd45ad321542f99ea832ce3344591b +#: 43df75cd090a4766bcf72876cf76a7bf 4641c857ad8a45efae05264ecac97ef7 +#: 4713b3b91849478fab5206c73d3ca5d3 687830b1174246909c9e9093ed425720 +#: 821fbdc0c170431cafe4f9061cf54461 8503e0ee8e5e406989667091790f9378 +#: 8a5e94db5a7041378080659bf0f61188 95d571a04fc9459a85793c10497cf0ce +#: aee23707608843a99cc635eb0c4212c7 af515f006ca34952a35bbf395a3d96b9 +#: c9a90bffee114796ad49bc8a70be6196 d0b56ad7c87c458bbaf0312a8c8d5d74 +#: dd6e250ab20f4cdca79c182dc59962df f2eb4b37640d421694c81d41feda44f6 +#: fdf94589907045daa0e2feff7c139d4f m5ui.checkbox.M5Checkbox:15 of +msgid "None" +msgstr "" + +#: ../../en/m5ui/checkbox.rst:30 c48689da2d9147949440c0be556c4495 +msgid "MicroPython Example" +msgstr "MicroPython 应用示例" + +#: ../../en/m5ui/checkbox.rst:37 ../../en/m5ui/checkbox.rst:69 +#: ../../en/m5ui/checkbox.rst:87 ../../en/m5ui/checkbox.rst:106 +#: ../../en/m5ui/checkbox.rst:124 ../../en/m5ui/checkbox.rst:142 +#: ../../en/m5ui/checkbox.rst:162 ../../en/m5ui/checkbox.rst:182 +#: ../../en/m5ui/checkbox.rst:202 ../../en/m5ui/checkbox.rst:220 +#: ../../en/m5ui/checkbox.rst:237 ../../en/m5ui/checkbox.rst:254 +#: ../../en/m5ui/checkbox.rst:272 ../../en/m5ui/checkbox.rst:289 +#: ../../en/m5ui/checkbox.rst:306 ../../en/m5ui/checkbox.rst:323 +#: ../../en/m5ui/checkbox.rst:340 ../../en/m5ui/checkbox.rst:360 +#: ../../en/m5ui/checkbox.rst:378 ../../en/m5ui/checkbox.rst:395 +#: ../../en/m5ui/checkbox.rst:412 ../../en/m5ui/checkbox.rst:432 +#: 041d85d89a6d4e5d94b910570eea9745 0b48a6c58a8c47769f4dd22e6126bd78 +#: 15e2d9e593d746da89bfd092224711ad 32119bcd07a146b0960a2ede042d5474 +#: 33928fa71ec94ab094e106a544009426 46f508de33394dd59be359f3901f7d64 +#: 5dffe3da72824b72b17f05f3f32450fa 6fdeb36108fa43e2a14810f3a4bebf42 +#: 8569928b2fa1496a9acaf4db4500f861 8a3412b4f5264ce4bd5668f62fb16ee6 +#: 903b4522aa434e998fb5e07167534eb1 ae919b01e1b5445a9e710e76fce8bfd1 +#: b14f72a814cb486ba042b3a10d38adc8 b29f0cc43c52450cb491e940b3c52b88 +#: b3eeffe7cb2246e4b115d0daa1dcf38d bec347f427834b19ac5b78107c4bacb4 +#: bf6e05375e9e45d6a4cf3e96b2d65fd7 bf8445d5706d48789fb83bdd3fe49663 +#: ceabf603990f43b494612fcaf2ce3b0f df4a15eebabf467fb4824630473f5c19 +#: e030ec59363b40eea47fbd46a4285c61 fe7b98d3f83246dd86cb7b9f778127a2 +#: feba7dee3be545838d3ecfae6abd5304 m5ui.checkbox.M5Checkbox:17 of +msgid "MicroPython Code Block:" +msgstr "MicroPython 代码块:" + +#: ../../en/m5ui/checkbox.rst:49 06be8ed0f8fe48b09e884b5193e03fee +msgid "**API**" +msgstr "API参考" + +#: 2c7ab26586ba48b7ae5791a70b3336b9 m5ui.checkbox.M5Checkbox:1 of +msgid "Bases: :py:class:`~lvgl.checkbox`" +msgstr "" + +#: 9b851b66f64c4605929de7b810884666 m5ui.checkbox.M5Checkbox:1 of +msgid "Create a checkbox object." +msgstr "创建一个复选框对象。" + +#: ../../en/m5ui/checkbox.rst 05a091b5ef294c43b7192d4b9f288628 +#: 09de210547f04d83ab8f96ae83d391a8 18656b644e3541638a02c14b41df4007 +#: 268c0b34570c4d1b9a1d3d16387cdf97 281d6debcb8549439e3f15ae9db55105 +#: 37f6505fb85e4d039408277bcbda8479 3d02bd789ff74090a95d72ea9ed57e9e +#: 4f7142c850a345308a4302a4f6a43a6d 72003f1f738a440b8d8f72da4fefe6c9 +#: 817fe6bef933457b9b4a8bdd65670d5b 829e3c740e3744f89e696b3c577afa42 +#: 8f16b72cafed468dbf55149f0efdca6a 9532949d776a4c1b966e261e6ea8155f +#: b45ec9d580c546a1859783006828380f c90020a029574b409ae701c4875c4e17 +#: d4c58a2bab6c4eada6b3a3eba065036c e63bd8dad0984094a8ba128d89bb6999 +#: ee7f2afd038c461a8d55cbe18d63bd29 f6ab192d5ab6416ab9574bd2d4243ce7 +msgid "Parameters" +msgstr "" + +#: 2ccc234c29af43c9a617778f0d888702 m5ui.checkbox.M5Checkbox:3 of +msgid "The title text of the checkbox." +msgstr "复选框的标题文本。" + +#: c24cf4a666a94b16ba2caab6c8ccb571 m5ui.checkbox.M5Checkbox:4 of +msgid "The initial checked state of the checkbox." +msgstr "复选框的初始选中状态。" + +#: 84e728533d6b4bf9953b603c6f51f5e7 m5ui.checkbox.M5Checkbox:5 of +msgid "The x position of the checkbox." +msgstr "复选框的x坐标位置。" + +#: 411492de7c1c45a8b79ee290fb05e991 m5ui.checkbox.M5Checkbox:6 of +msgid "The y position of the checkbox." +msgstr "复选框的y坐标位置。" + +#: 34e64d256f7648beb8349c9e049c3bed m5ui.checkbox.M5Checkbox:7 of +msgid "The color of the title text in hexadecimal format." +msgstr "标题文本的十六进制颜色格式。" + +#: f60900d003b3447ca53eeeb29f71eded m5ui.checkbox.M5Checkbox:8 of +msgid "The font to use for the title text." +msgstr "标题文本使用的字体。" + +#: a2e9008b8e89429b987679aa124f1c18 m5ui.checkbox.M5Checkbox:9 of +msgid "The border color of the checkbox bullet in hexadecimal format." +msgstr "复选框方块的边框颜色(十六进制格式)。" + +#: a254f225648642a0883d27553571eb51 m5ui.checkbox.M5Checkbox:10 of +msgid "The background color of the checkbox bullet in hexadecimal format." +msgstr "复选框方块的背景颜色(十六进制格式)。" + +#: bd78b08133a84190b7434d573592ee3a m5ui.checkbox.M5Checkbox:11 of +msgid "" +"The parent object to attach the checkbox to. If not specified, the " +"checkbox will be attached to the default screen." +msgstr "" +"要附加复选框的父对象。如果未指定," +"复选框将被附加到默认屏幕。" + +#: ../../en/m5ui/checkbox.rst:59 235e5199149c4dc7a32e488ad9d71088 +msgid "" +"Set a flag on the object. If ``value`` is True, the flag is added; if " +"False, the flag is removed." +msgstr "" +"设置对象上的标志。如果 ``value`` 为True,则添加标志;如果为" +"False,则移除标志。" + +#: ../../en/m5ui/checkbox.rst:61 5322959247214ea78f9e39d4d50b61a9 +msgid "The flag to set." +msgstr "要设置的标志。" + +#: ../../en/m5ui/checkbox.rst:62 493734ff50494ae9b0a8a0621d6a636d +msgid "If True, the flag is added; if False, the flag is removed." +msgstr "如果为True,则添加标志;如果为False,则移除标志。" + +#: ../../en/m5ui/checkbox.rst 040506618fc341e8bcbbe91727a4b25c +#: 1722afc5d5704f6eaace0ff77ecec7e4 1fa1f2daa0ad4ce7a0c2465c74cd4538 +#: 2f41e27e14914697ad1ff9d3717b87cf 302bc82bac8f4dcaa939f6087a5a2768 +#: 5c1c4469e8b442f59dba0d5c493a1d7e 605e3338ea204919bce662a50e5e365b +#: 60a5052d5edf4121a95673ca208e8eeb 62c89e7de43c4370add008e2e57e7674 +#: 71e479be16e4465592edbd57ccb00b13 8bd33f9477554f93944dad1edc249b92 +#: 9aac1a54716a481da547f4055053bb43 9ce1f8dbc4ad42e094367a1f268ea9ee +#: 9e6e6963fa694a578faf1e32ac956049 af87be7224ae4449b38757996cb76e97 +#: d61d1d28d5cc43fa95f74764e30d7a98 d7c6c5cd37c84923bb908bd21d5df40a +#: e2f4d728451e408db26f29e9ff5a9f44 e75790b8ca554e2c864ec91f11765af3 +#: e81d8dd0144448f99ba85527c775c7ea ef07b25d3ce646f9afac88686d7bf42d +msgid "Returns" +msgstr "" + +#: ../../en/m5ui/checkbox.rst:67 d3fd078549154241ac798edf7df77b99 +msgid "|set_flag.png|" +msgstr "" + +#: ../../en/refs/m5ui.checkbox.ref:11 bc4ff84a6e614f0f81bbb66b2a7a78ee +msgid "set_flag.png" +msgstr "" + +#: ../../en/m5ui/checkbox.rst:78 a802aefc065d4a90b3521d5268ba3cda +msgid "" +"Toggle a flag on the object. If the flag is set, it is removed; if not " +"set, it is added." +msgstr "" +"切换对象上的标志。如果标志已设置,则移除它;如果未设置," +"则添加它。" + +#: ../../en/m5ui/checkbox.rst:80 f3243db50802473bb88d5900153eec15 +msgid "The flag to toggle." +msgstr "要切换的标志。" + +#: ../../en/m5ui/checkbox.rst:85 c4bdf1528ec144fca8dfa37ecfae66ab +msgid "|toggle_flag.png|" +msgstr "" + +#: ../../en/refs/m5ui.checkbox.ref:23 5b15dcd3211f4f2da4a27744adff2fa6 +msgid "toggle_flag.png" +msgstr "" + +#: ../../en/m5ui/checkbox.rst:96 612620ce8f964a5c8dfc5dd5c87f62bd +msgid "" +"Set the state of the checkbox. If ``value`` is True, the state is set; if" +" False, the state is unset." +msgstr "" +"设置复选框的状态。如果 ``value`` 为True,则设置状态;如果为" +"False,则取消设置状态。" + +#: ../../en/m5ui/checkbox.rst:98 e18bd2de9622468ba850de56303c3f7d +msgid "The state to set." +msgstr "要设置的状态。" + +#: ../../en/m5ui/checkbox.rst:99 cd7f19ba89774fd68ea5f4d9ef3f91e2 +msgid "If True, the state is set; if False, the state is unset." +msgstr "如果为True,则设置状态;如果为False,则取消设置状态。" + +#: ../../en/m5ui/checkbox.rst:104 7862a01b704740dc8d4309a748c0162f +msgid "|set_state.png|" +msgstr "" + +#: ../../en/refs/m5ui.checkbox.ref:15 b87993cce71b4f40bf860c8c148fe1d5 +msgid "set_state.png" +msgstr "" + +#: ../../en/m5ui/checkbox.rst:115 cfe2ceb17a46434ebbd9f9f9dd3cd71c +msgid "" +"Toggle the state of the checkbox. If the state is set, it is unset; if " +"not set, it is set." +msgstr "" +"切换复选框的状态。如果状态已设置,则取消设置;如果未设置," +"则设置。" + +#: ../../en/m5ui/checkbox.rst:117 3f43158056af42f39c0b3bc155cb78a6 +msgid "The state to toggle." +msgstr "要切换的状态。" + +#: ../../en/m5ui/checkbox.rst:122 4dc09d20b7f94c5a81c3e4c2cf3e484a +msgid "|toggle_state.png|" +msgstr "" + +#: ../../en/refs/m5ui.checkbox.ref:24 13778a6fd54e4f58971387c6ba3e45b3 +msgid "toggle_state.png" +msgstr "" + +#: ../../en/m5ui/checkbox.rst:132 dcc8e3c20dfc4bfba988e27999fb4635 +msgid "Set the font of the checkbox text." +msgstr "设置复选框文本的字体。" + +#: ../../en/m5ui/checkbox.rst:134 e014454efc14495b8b6b1bd6d254fc3a +msgid "The font to set." +msgstr "要设置的字体。" + +#: ../../en/m5ui/checkbox.rst:135 ../../en/m5ui/checkbox.rst:155 +#: 6a7c5dcfa3574ac9bf92a71a33dba420 8316e571d6bf4ba59af137c366481d69 +msgid "The part of the object to apply the style to (e.g., lv.PART.MAIN)." +msgstr "要应用样式的对象部分(例如,lv.PART.MAIN)。" + +#: ../../en/m5ui/checkbox.rst:140 1cdfab4c43674a61b3b5dcdaed6dec8b +msgid "|set_style_text_font.png|" +msgstr "" + +#: ../../en/refs/m5ui.checkbox.ref:17 161f20511d164a5cb50477acab1fb6eb +msgid "set_style_text_font.png" +msgstr "" + +#: ../../en/m5ui/checkbox.rst:151 074766fa9b32403bb36cbb541a668f78 +msgid "Set the color of the checkbox text." +msgstr "设置复选框文本的颜色。" + +#: ../../en/m5ui/checkbox.rst:153 ../../en/m5ui/checkbox.rst:173 +#: ../../en/m5ui/checkbox.rst:193 088f1f17ccce4456a645b7ae2cac39ad +#: 98563f7a5bf54a21a97caa86a0f63a72 990cb6fe981b407987e661ca0223cdc1 +msgid "The color to set." +msgstr "要设置的颜色。" + +#: ../../en/m5ui/checkbox.rst:154 ../../en/m5ui/checkbox.rst:174 +#: ../../en/m5ui/checkbox.rst:194 1a970870178643a0ba709f7bc32a2bd4 +#: 6159c58e9b7d4155bc9600ed2251b4f0 b6f345f3179544c8a13512d38dabbb34 +msgid "" +"The opacity of the color. The value should be between 0 (transparent) and" +" 255 (opaque)." +msgstr "" +"颜色的不透明度。值应在0(透明)和" +"255(不透明)之间。" + +#: ../../en/m5ui/checkbox.rst:160 b9bcc113c2bc45c0baa7d7f1157ecc0a +msgid "|set_text_color.png|" +msgstr "" + +#: ../../en/refs/m5ui.checkbox.ref:19 bda8d46a5275447aa58759c0b6ecada5 +msgid "set_text_color.png" +msgstr "" + +#: ../../en/m5ui/checkbox.rst:171 af9b765f1e3241f89588d75c181985c0 +msgid "Set the background color of the checkbox indicator." +msgstr "设置复选框指示器的背景颜色。" + +#: ../../en/m5ui/checkbox.rst:175 ../../en/m5ui/checkbox.rst:195 +#: ../../en/m5ui/checkbox.rst:371 1854f185c9e54e9ba385c45c441c220a +#: 264b10aa17814e45815034a7fbd2e06d d34835b83c84475d807cd8bd8af6fc3c +msgid "The part of the object to apply the style to (e.g., lv.PART.INDICATOR)." +msgstr "要应用样式的对象部分(例如,lv.PART.INDICATOR)。" + +#: ../../en/m5ui/checkbox.rst:180 799beddaee1f4a49882124bd86c843d5 +msgid "|set_bg_color.png|" +msgstr "" + +#: ../../en/refs/m5ui.checkbox.ref:8 f1e55ba76073496486c4dfde7a44e65a +msgid "set_bg_color.png" +msgstr "" + +#: ../../en/m5ui/checkbox.rst:191 689fd9c6eb9842fe935e83bedea52b6c +msgid "Set the border color of the checkbox indicator." +msgstr "设置复选框指示器的边框颜色。" + +#: ../../en/m5ui/checkbox.rst:200 6dac135bb9ce4cc18e2dcb5211f932c5 +msgid "|set_border_color.png|" +msgstr "" + +#: ../../en/refs/m5ui.checkbox.ref:9 a9c3c7a75bd8425eaf12c34bebd227a8 +msgid "set_border_color.png" +msgstr "" + +#: ../../en/m5ui/checkbox.rst:210 133f95cfd3ea4f4c8b039cd20f48618e +msgid "Set the position of the checkbox." +msgstr "设置复选框的位置。" + +#: ../../en/m5ui/checkbox.rst:212 ../../en/m5ui/checkbox.rst:230 +#: 14f58e0fb9f6466b856050fa7ac4741a 18945e097ea946108f1a4a8e3199a177 +msgid "The x-coordinate of the checkbox." +msgstr "复选框的x坐标。" + +#: ../../en/m5ui/checkbox.rst:213 ../../en/m5ui/checkbox.rst:247 +#: 4e67f664dc7f441fb8f66d9a546f0b98 fd843260c3c34eff9c25a9f410973472 +msgid "The y-coordinate of the checkbox." +msgstr "复选框的y坐标。" + +#: ../../en/m5ui/checkbox.rst:218 f9b771c1b7fb4f40bf08c738fea1378a +msgid "|set_pos.png|" +msgstr "" + +#: ../../en/refs/m5ui.checkbox.ref:13 62995bd214e14674998f1971ce347873 +msgid "set_pos.png" +msgstr "" + +#: ../../en/m5ui/checkbox.rst:228 bd76844da5224ba1a50f2e81add7a78f +msgid "Set the x-coordinate of the checkbox." +msgstr "设置复选框的x坐标。" + +#: ../../en/m5ui/checkbox.rst:235 a2e09399c281456e93721a1a9757b7b4 +msgid "|set_x.png|" +msgstr "" + +#: ../../en/refs/m5ui.checkbox.ref:21 5ac8df2318c94b5ea88058a5a868d107 +msgid "set_x.png" +msgstr "" + +#: ../../en/m5ui/checkbox.rst:245 538b4e03a04c40199dc4ca353d7db869 +msgid "Set the y-coordinate of the checkbox." +msgstr "设置复选框的y坐标。" + +#: ../../en/m5ui/checkbox.rst:252 227cd4638d0b40609931156d2ca05298 +msgid "|set_y.png|" +msgstr "" + +#: ../../en/refs/m5ui.checkbox.ref:22 dfa4c67193ea4914aefc06ba0f17c8a5 +msgid "set_y.png" +msgstr "" + +#: ../../en/m5ui/checkbox.rst:262 63d1e0c1c422405bb127f0f1adcdd2ca +msgid "Set the size of the checkbox." +msgstr "设置复选框的大小。" + +#: ../../en/m5ui/checkbox.rst:264 ../../en/m5ui/checkbox.rst:282 +#: ../../en/m5ui/checkbox.rst:299 1e9c3e3e0d8b42ee9abf1db00dab1da7 +#: 2ee492c546ff42a8b2f27c9d394ce867 f82b340c82f240b09cad93f87613958d +msgid "The width of the checkbox." +msgstr "复选框的宽度。" + +#: ../../en/m5ui/checkbox.rst:265 ../../en/m5ui/checkbox.rst:316 +#: ../../en/m5ui/checkbox.rst:333 4f952a62fc484865b5972ee4bcdd803c +#: ad3a9e684a54452d8478f28b7d403422 ae54aa09ee724842b0f3c04592737b53 +msgid "The height of the checkbox." +msgstr "复选框的高度。" + +#: ../../en/m5ui/checkbox.rst:270 9a9d88b670ad4024bad9ba5f88012c79 +msgid "|set_size.png|" +msgstr "" + +#: ../../en/refs/m5ui.checkbox.ref:14 6a45b6a9a6b54c2dbf1d355f279b2e7a +msgid "set_size.png" +msgstr "" + +#: ../../en/m5ui/checkbox.rst:280 5fc46536c3034704ac4135aebdc26183 +msgid "Set the width of the checkbox." +msgstr "设置复选框的宽度。" + +#: ../../en/m5ui/checkbox.rst:287 98a688e7d53c430ea9f9d91c8218e21f +msgid "|set_width.png|" +msgstr "" + +#: ../../en/refs/m5ui.checkbox.ref:20 f50ef4baf89045d7865603ad7ecd9830 +msgid "set_width.png" +msgstr "" + +#: ../../en/m5ui/checkbox.rst:297 3a72bc13353848b4878b34464d96e473 +msgid "Get the width of the checkbox." +msgstr "获取复选框的宽度。" + +#: ../../en/m5ui/checkbox.rst 28b316445c1b4673a748006d1f2ac1ef +#: 7313be711b8c4befa9af362898a3e629 9aaf15a286484231aeb225b50bdd7c2a +msgid "Return type" +msgstr "" + +#: ../../en/m5ui/checkbox.rst:304 7c2ad3106e194a229fda44c017be37cc +msgid "|get_width.png|" +msgstr "" + +#: ../../en/refs/m5ui.checkbox.ref:6 871d317523844cf69d29a7ff5f714d1b +msgid "get_width.png" +msgstr "" + +#: ../../en/m5ui/checkbox.rst:314 9758421abb754eaf8065175b579528e7 +msgid "Set the height of the checkbox." +msgstr "设置复选框的高度。" + +#: ../../en/m5ui/checkbox.rst:321 9be351361b8f452fa775cf74403057e3 +msgid "|set_height.png|" +msgstr "" + +#: ../../en/refs/m5ui.checkbox.ref:12 57e17af98a95469dab56d8d20e9e6fdc +msgid "set_height.png" +msgstr "" + +#: ../../en/m5ui/checkbox.rst:331 b5c3d90e6a1f41f18ed3e1d10e38e2b4 +msgid "Get the height of the checkbox." +msgstr "获取复选框的高度。" + +#: ../../en/m5ui/checkbox.rst:338 009537ad7b7b41549d6aeec6528f7a8e +msgid "|get_height.png|" +msgstr "" + +#: ../../en/refs/m5ui.checkbox.ref:4 534563ba25994c3ea55ac586b7952085 +msgid "get_height.png" +msgstr "" + +#: ../../en/m5ui/checkbox.rst:348 34c0566c52b5465ca6c7451036f831d5 +msgid "Align the checkbox to another object." +msgstr "将复选框对齐到另一个对象。" + +#: ../../en/m5ui/checkbox.rst:350 070d02d62f1546bdbdc359119a09b91c +msgid "The object to align to." +msgstr "要对齐到的对象。" + +#: ../../en/m5ui/checkbox.rst:351 59bfce79d82e484aa767451ecc566dd8 +msgid "The alignment type." +msgstr "对齐类型。" + +#: ../../en/m5ui/checkbox.rst:352 44ab31c7e34a4251a684680fc3fb7bda +msgid "The x-offset from the aligned object." +msgstr "与对齐对象的x偏移量。" + +#: ../../en/m5ui/checkbox.rst:353 805f117200fa4fa8bbba78e56591a6ea +msgid "The y-offset from the aligned object." +msgstr "与对齐对象的y偏移量。" + +#: ../../en/m5ui/checkbox.rst:358 fb0ae0c669e04f68947c53ab9abeb037 +msgid "|align_to.png|" +msgstr "" + +#: ../../en/refs/m5ui.checkbox.ref:2 111f62e090ce4041bc66497027db1814 +msgid "align_to.png" +msgstr "" + +#: ../../en/m5ui/checkbox.rst:368 a601c1d958c44291845b09662c1ed759 +msgid "Set the corner radius of the checkbox indicator." +msgstr "设置复选框指示器的圆角半径。" + +#: ../../en/m5ui/checkbox.rst:370 a41ce3b1ef034952adceaf5def62fd30 +msgid "The radius to set." +msgstr "要设置的半径。" + +#: ../../en/m5ui/checkbox.rst:376 e04f01010b434bec835945811f523a5e +msgid "|set_style_radius.png|" +msgstr "" + +#: ../../en/refs/m5ui.checkbox.ref:16 04237c87a66b49d4887f8e5198f2d0ec +msgid "set_style_radius.png" +msgstr "" + +#: ../../en/m5ui/checkbox.rst:386 e919df338dbe4d07910d8c72c31898d7 +msgid "Set the text of the checkbox." +msgstr "设置复选框的文本。" + +#: ../../en/m5ui/checkbox.rst:388 8f70bd4ef213426f851d01bc135f80e6 +msgid "The text to set." +msgstr "要设置的文本。" + +#: ../../en/m5ui/checkbox.rst:393 72af5f3c7d91407e8abef87629e7b16b +msgid "|set_text.png|" +msgstr "" + +#: ../../en/refs/m5ui.checkbox.ref:18 ebf0e33835c843188eccdfbdd8536bd6 +msgid "set_text.png" +msgstr "" + +#: ../../en/m5ui/checkbox.rst:403 57b422436faf44b0a1db806562bebb99 +msgid "Get the text of the checkbox." +msgstr "获取复选框的文本。" + +#: ../../en/m5ui/checkbox.rst:405 7074d2c8e452433ca7fc1252d15047c8 +msgid "The text of the checkbox." +msgstr "复选框的文本。" + +#: ../../en/m5ui/checkbox.rst:410 e73811d38cdc4e5b851f677dfdef3c4e +msgid "|get_text.png|" +msgstr "" + +#: ../../en/refs/m5ui.checkbox.ref:5 c4719bb1d97546ef91cc0df14d57c16b +msgid "get_text.png" +msgstr "" + +#: ../../en/m5ui/checkbox.rst:421 4131984538864106a49436d9605091a2 +msgid "" +"Add an event callback to the checkbox. The callback will be called when " +"the specified event occurs." +msgstr "" +"为复选框添加事件回调。当指定事件发生时," +"将调用回调函数。" + +#: ../../en/m5ui/checkbox.rst:423 184ea050158b474c9a4ea752db2c7fa5 +msgid "The callback function to call." +msgstr "要调用的回调函数。" + +#: ../../en/m5ui/checkbox.rst:424 44530fbb7c4440f284a5b178dc8a0eec +msgid "The event to listen for." +msgstr "要监听的事件。" + +#: ../../en/m5ui/checkbox.rst:425 6c2cb458da4c40e9b40120c42e88cc89 +msgid "Optional user data to pass to the callback." +msgstr "传递给回调函数的可选用户数据。" + +#: ../../en/m5ui/checkbox.rst:430 50dfb856d37f476395719015e055b8b3 +msgid "|event.png|" +msgstr "" + +#: ../../en/refs/m5ui.checkbox.ref:3 3b926738ff58454585b114131d93fd5b +msgid "event.png" +msgstr "" + diff --git a/examples/m5ui/checkbox/cores3_checkbox_basic_example.m5f2 b/examples/m5ui/checkbox/cores3_checkbox_basic_example.m5f2 new file mode 100644 index 00000000..bce85299 --- /dev/null +++ b/examples/m5ui/checkbox/cores3_checkbox_basic_example.m5f2 @@ -0,0 +1 @@ +{"version":"V2.0","versionNumber":"V2.3.2","type":"cores3","components":[{"name":"page0","type":"lvgl_page","layer":0,"screenId":"builtin","screenName":"","id":"qkcBp9zGTa6oSd$^","createTime":1753587204985,"backgroundColor":"#ffffff","isLVGL":true,"isSelected":true},{"name":"checkbox0","type":"lvgl_checkbox","layer":1,"screenId":"builtin","screenName":"","id":"nX=Er9Y$&I1UzFm3","createTime":1753587211401,"x":77,"y":106,"defaultColor":"#2193f3","color":"#212121","bulletBorderColor":"#2193f3","bulletBackgroundColor":"#ffffff","checked":false,"text":"checkbox0","font":"lv.font_montserrat_24","pageId":"qkcBp9zGTa6oSd$^","isLVGL":true,"isSelected":false,"width":166,"height":27},{"name":"checkbox1","type":"lvgl_checkbox","layer":2,"screenId":"builtin","screenName":"","id":"eQKhU$zYuoA6VbjC","createTime":1753587642901,"x":80,"y":54,"defaultColor":"#2193f3","color":"#212121","bulletBorderColor":"#2193f3","bulletBackgroundColor":"#ffffff","checked":false,"text":"checkbox1","font":"lv.font_montserrat_24","pageId":"qkcBp9zGTa6oSd$^","isLVGL":true,"isSelected":false,"width":159,"height":27},{"name":"label0","type":"lvgl_label","layer":3,"screenId":"builtin","screenName":"","id":"sxo6%rqU5Oa5@+9p","createTime":1753587764301,"x":124,"y":153,"color":"#000000","backgroundColor":"#ffffff","bg_opacity":0,"text":"label0","font":"lv.font_montserrat_24","pageId":"qkcBp9zGTa6oSd$^","isLVGL":true,"isSelected":false,"width":74,"height":27}],"resources":[{"hardware":["hardware_button","hardware_pin_button","imu","speaker","touch","als","mic","sdcard"]}],"units":[],"hats":[],"bases":[],"i2cs":[],"blockly":"truepage0checkbox1DISABLEDTruecheckbox1CHECKEDTruetruecheckbox0CHECKEDlabel0checkedcheckbox0UNCHECKEDlabel0unchecked","screen":[{"simulationName":"Built-in","type":"builtin","width":320,"height":240,"scale":0.77,"screenName":"","blockId":"","screenColorType":0,"rotation":1,"id":"builtin","createTime":1753587204982}],"logicWhenNum":0,"customList":[]} \ No newline at end of file diff --git a/examples/m5ui/checkbox/cores3_checkbox_basic_example.py b/examples/m5ui/checkbox/cores3_checkbox_basic_example.py new file mode 100644 index 00000000..1afb5456 --- /dev/null +++ b/examples/m5ui/checkbox/cores3_checkbox_basic_example.py @@ -0,0 +1,104 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import os, sys, io +import M5 +from M5 import * +import m5ui +import lvgl as lv + + +page0 = None +checkbox0 = None +checkbox1 = None +label0 = None + + +def checkbox0_checked_event(event_struct): + global page0, checkbox0, checkbox1, label0 + label0.set_text(str("checked")) + + +def checkbox0_unchecked_event(event_struct): + global page0, checkbox0, checkbox1, label0 + label0.set_text(str("unchecked")) + + +def checkbox0_event_handler(event_struct): + global page0, checkbox0, checkbox1, label0 + event = event_struct.code + obj = event_struct.get_target_obj() + if event == lv.EVENT.VALUE_CHANGED: + if obj.has_state(lv.STATE.CHECKED): + checkbox0_checked_event(event_struct) + else: + checkbox0_unchecked_event(event_struct) + return + + +def setup(): + global page0, checkbox0, checkbox1, label0 + + M5.begin() + Widgets.setRotation(1) + m5ui.init() + page0 = m5ui.M5Page(bg_c=0xFFFFFF) + checkbox0 = m5ui.M5Checkbox( + title="checkbox0", + value=False, + x=77, + y=106, + title_c=0x212121, + title_font=lv.font_montserrat_24, + bullet_border_c=0x2193F3, + bullet_bg_c=0xFFFFFF, + parent=page0, + ) + checkbox1 = m5ui.M5Checkbox( + title="checkbox1", + value=False, + x=80, + y=54, + title_c=0x212121, + title_font=lv.font_montserrat_24, + bullet_border_c=0x2193F3, + bullet_bg_c=0xFFFFFF, + parent=page0, + ) + label0 = m5ui.M5Label( + "label0", + x=124, + y=153, + text_c=0x000000, + bg_c=0xFFFFFF, + bg_opa=0, + font=lv.font_montserrat_24, + parent=page0, + ) + + checkbox0.add_event_cb(checkbox0_event_handler, lv.EVENT.ALL, None) + + page0.screen_load() + checkbox1.set_state(lv.STATE.DISABLED, True) + checkbox1.set_state(lv.STATE.CHECKED, True) + + +def loop(): + global page0, checkbox0, checkbox1, label0 + M5.update() + + +if __name__ == "__main__": + try: + setup() + while True: + loop() + except (Exception, KeyboardInterrupt) as e: + try: + m5ui.deinit() + from utility import print_error_msg + + print_error_msg(e) + except ImportError: + print("please update to latest firmware") diff --git a/m5stack/libs/m5ui/__init__.py b/m5stack/libs/m5ui/__init__.py index 35e3eba8..38e52e53 100644 --- a/m5stack/libs/m5ui/__init__.py +++ b/m5stack/libs/m5ui/__init__.py @@ -9,6 +9,7 @@ "M5Bar": "bar", "M5Button": "button", "M5Calendar": "calendar", + "M5Checkbox": "checkbox", "M5Image": "image", "M5Label": "label", "M5Line": "line", diff --git a/m5stack/libs/m5ui/bar.py b/m5stack/libs/m5ui/bar.py index a347fb93..8df19e7a 100644 --- a/m5stack/libs/m5ui/bar.py +++ b/m5stack/libs/m5ui/bar.py @@ -4,7 +4,6 @@ from .base import M5Base import lvgl as lv -import time class M5Bar(lv.bar): diff --git a/m5stack/libs/m5ui/base.py b/m5stack/libs/m5ui/base.py index b715493b..0883bdee 100644 --- a/m5stack/libs/m5ui/base.py +++ b/m5stack/libs/m5ui/base.py @@ -120,3 +120,18 @@ def set_bg_grad_color(self, color, opa, grad_color, grad_opd, grad_dir, part: in time.sleep(0.01) self.set_style_bg_grad_dir(grad_dir, part) time.sleep(0.01) + + def set_border_color(self, color: int, opa: int, part: int): + """Set the border color and opacity for a given part of the object. + + :param int color: The color to set, can be an integer (hex) or a lv.color object. + :param int opa: The opacity level (0-255). + :param int part: The part of the object to apply the style to (e.g., lv.PART.MAIN). + :return: None + """ + if isinstance(color, int): + color = lv.color_hex(color) + + self.set_style_border_color(color, part) + time.sleep(0.01) + self.set_style_border_opa(opa, part) diff --git a/m5stack/libs/m5ui/checkbox.py b/m5stack/libs/m5ui/checkbox.py new file mode 100644 index 00000000..232d7acb --- /dev/null +++ b/m5stack/libs/m5ui/checkbox.py @@ -0,0 +1,71 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +from .base import M5Base +import lvgl as lv + + +class M5Checkbox(lv.checkbox): + """Create a checkbox object. + + :param str title: The title text of the checkbox. + :param bool value: The initial checked state of the checkbox. + :param int x: The x position of the checkbox. + :param int y: The y position of the checkbox. + :param int title_c: The color of the title text in hexadecimal format. + :param lv.lv_font_t title_font: The font to use for the title text. + :param int bullet_border_c: The border color of the checkbox bullet in hexadecimal format. + :param int bullet_bg_c: The background color of the checkbox bullet in hexadecimal format. + :param lv.obj parent: The parent object to attach the checkbox to. If not specified, the checkbox will be attached to the default screen. + + UiFlow2 Code Block: + + None + + MicroPython Code Block: + + .. code-block:: python + + from m5ui import M5Checkbox + import lvgl as lv + m5ui.init() + checkbox_0 = M5Checkbox(title="Check Me", value=True, x=10, y=10, title_c=0x2121, title_font=lv.font_montserrat_14, bullet_border_c=0x2196F3, bullet_bg_c=0xFFFFFF, parent=page0) + + """ + + def __init__( + self, + title="Checkbox", + value=False, + x=0, + y=0, + title_c=0x2121, + title_font=lv.font_montserrat_14, + bullet_border_c=0x2196F3, + bullet_bg_c=0xFFFFFF, + parent=None, + ): + if parent is None: + parent = lv.screen_active() + super().__init__(parent) + + self.set_pos(x, y) + self.set_text(title) + self.set_state(lv.STATE.CHECKED, value) + + part = lv.STATE.CHECKED if value else lv.STATE.DEFAULT + + self.set_text_color(title_c, lv.OPA.COVER, lv.PART.MAIN | part) + self.set_style_text_font(title_font, lv.PART.MAIN | lv.STATE.DEFAULT) + + self.set_bg_color(bullet_bg_c, lv.OPA.COVER, lv.PART.INDICATOR | part) + self.set_border_color(bullet_border_c, lv.OPA.COVER, lv.PART.INDICATOR | part) + + def __getattr__(self, name): + if hasattr(M5Base, name): + method = getattr(M5Base, name) + bound_method = lambda *args, **kwargs: method(self, *args, **kwargs) + setattr(self, name, bound_method) + return bound_method + raise AttributeError(f"'{self.__class__.__name__}' object has no attribute '{name}'") diff --git a/m5stack/libs/m5ui/manifest.py b/m5stack/libs/m5ui/manifest.py index 2e333fa8..e1a618fc 100644 --- a/m5stack/libs/m5ui/manifest.py +++ b/m5stack/libs/m5ui/manifest.py @@ -11,6 +11,7 @@ "base.py", "button.py", "calendar.py", + "checkbox.py", "image.py", "label.py", "line.py", From 38bb2f5f6e4d1fbb7050b0b4ea695581298a9f9d Mon Sep 17 00:00:00 2001 From: lbuque Date: Wed, 9 Jul 2025 14:41:35 +0800 Subject: [PATCH 182/322] libs/m5ui: Add m5ui.M5Slider support. Signed-off-by: lbuque --- docs/en/m5ui/index.rst | 1 + docs/en/m5ui/slider.rst | 473 +++++++++++++ docs/en/refs/m5ui.slider.ref | 34 + docs/locales/zh_CN/LC_MESSAGES/m5ui/slider.po | 641 ++++++++++++++++++ .../slider/cores3_slider_basic_example.m5f2 | 1 + .../slider/cores3_slider_basic_example.py | 83 +++ m5stack/libs/m5ui/__init__.py | 1 + m5stack/libs/m5ui/manifest.py | 1 + m5stack/libs/m5ui/slider.py | 58 ++ 9 files changed, 1293 insertions(+) create mode 100644 docs/en/m5ui/slider.rst create mode 100644 docs/en/refs/m5ui.slider.ref create mode 100644 docs/locales/zh_CN/LC_MESSAGES/m5ui/slider.po create mode 100644 examples/m5ui/slider/cores3_slider_basic_example.m5f2 create mode 100644 examples/m5ui/slider/cores3_slider_basic_example.py create mode 100644 m5stack/libs/m5ui/slider.py diff --git a/docs/en/m5ui/index.rst b/docs/en/m5ui/index.rst index 2ab4572f..e9c79b80 100644 --- a/docs/en/m5ui/index.rst +++ b/docs/en/m5ui/index.rst @@ -41,4 +41,5 @@ Classes image.rst label.rst line.rst + slider.rst switch.rst diff --git a/docs/en/m5ui/slider.rst b/docs/en/m5ui/slider.rst new file mode 100644 index 00000000..579aa5b5 --- /dev/null +++ b/docs/en/m5ui/slider.rst @@ -0,0 +1,473 @@ +.. currentmodule:: m5ui + +M5Slider +======== + +.. include:: ../refs/m5ui.slider.ref + +M5Slider is a widget that can be used to create sliders in the user interface. It allows users to select a value from a range by dragging a handle along a track. + +UiFlow2 Example +--------------- + +basic slider +^^^^^^^^^^^^ + +Open the |cores3_slider_basic_example.m5f2| project in UiFlow2. + +This example creates a basic slider that can be used to select values from 0 to 100. + +UiFlow2 Code Block: + + |cores3_slider_basic_example.png| + +Example output: + + None + + +MicroPython Example +------------------- + +basic slider +^^^^^^^^^^^^ + +This example creates a basic slider that can be used to select values from 0 to 100. + +MicroPython Code Block: + + .. literalinclude:: ../../../examples/m5ui/slider/cores3_slider_basic_example.py + :language: python + :linenos: + +Example output: + + None + + +**API** +------- + +M5Slider +^^^^^^^^ + +.. autoclass:: m5ui.slider.M5Slider + :members: + + .. py:method:: set_flag(flag, value) + + Set a flag on the object. If ``value`` is True, the flag is added; if False, the flag is removed. + + :param int flag: The flag to set. + :param bool value: If True, the flag is added; if False, the flag is removed. + :return: None + + UiFlow2 Code Block: + + |set_flag.png| + + MicroPython Code Block: + + .. code-block:: python + + slider_0.set_flag(lv.obj.FLAG.HIDDEN, True) + + .. py:method:: toggle_flag(flag) + + Toggle a flag on the object. If the flag is set, it is removed; if not set, it is added. + + :param int flag: The flag to toggle. + :return: None + + UiFlow2 Code Block: + + |toggle_flag.png| + + MicroPython Code Block: + + .. code-block:: python + + slider_0.toggle_flag(lv.obj.FLAG.HIDDEN) + + .. py:method:: set_state(state, value) + + Set the state of the slider. If ``value`` is True, the state is set; if False, the state is unset. + + :param int state: The state to set. + :param bool value: If True, the state is set; if False, the state is unset. + :return: None + + UiFlow2 Code Block: + + |set_state.png| + + MicroPython Code Block: + + .. code-block:: python + + slider_0.set_state(lv.STATE.PRESSED, True) + + .. py:method:: toggle_state(state) + + Toggle the state of the slider. If the state is set, it is unset; if not set, it is set. + + :param int state: The state to toggle. + :return: None + + UiFlow2 Code Block: + + |toggle_state.png| + + MicroPython Code Block: + + .. code-block:: python + + slider_0.toggle_state(lv.STATE.PRESSED) + + .. py:method:: add_event_cb(handler, event, user_data) + + Add an event callback to the slider. The callback will be called when the specified event occurs. + + :param function handler: The callback function to call. + :param int event: The event to listen for. + :param Any user_data: Optional user data to pass to the callback. + :return: None + + UiFlow2 Code Block: + + |event.png| + + MicroPython Code Block: + + .. code-block:: python + + def slider_0_value_changed_event(event_struct): + global slider_0 + value = slider_0.get_value() + print(f"Slider value changed to: {value}") + + def slider_0_event_handler(event_struct): + global slider_0 + event = event_struct.code + if event == lv.EVENT.VALUE_CHANGED: + slider_0_value_changed_event(event_struct) + return + + slider_0.add_event_cb(slider_0_event_handler, lv.EVENT.ALL, None) + + .. py:method:: set_bg_color(color, opa, part) + + Set the background color of the slider. + + :param int color: The color to set. + :param int opa: The opacity of the color. The value should be between 0 (transparent) and 255 (opaque). + :param int part: The part of the object to apply the style to (e.g., lv.PART.MAIN, lv.PART.INDICATOR). + :return: None + + UiFlow2 Code Block: + + |set_bg_color.png| + + MicroPython Code Block: + + .. code-block:: python + + slider_0.set_bg_color(lv.color_hex(0x2196F3), 255, lv.PART.INDICATOR | lv.STATE.DEFAULT) + + .. py:method:: set_style_radius(radius, part) + + Set the corner radius of the slider components. + + :param int radius: The radius to set. + :param int part: The part of the object to apply the style to (e.g., lv.PART.MAIN, lv.PART.KNOB). + :return: None + + UiFlow2 Code Block: + + |set_bg_radius.png| + + |set_indicator_radius.png| + + |set_knob_radius.png| + + MicroPython Code Block: + + .. code-block:: python + + slider_0.set_style_radius(10, lv.PART.MAIN | lv.STATE.DEFAULT) + + .. py:method:: set_bg_grad_color(color, opa, grad_color, grad_opd, grad_dir, part) + + Set the background gradient color of the bar. + + :param int color: The start color of the gradient, can be an integer (RGB). + :param int opa: The opacity of the start color (0-255). + :param int grad_color: The end color of the gradient, can be an integer (RGB). + :param int grad_opd: The opacity of the end color (0-255). + :param int grad_dir: The direction of the gradient (e.g., lv.GRAD_DIR.VER). + :param int part: The part of the object to apply the style to (e.g., lv.PART.MAIN). + :return: None + + UiFlow2 Code Block: + + |set_bg_grad_color.png| + + |set_indicator_grad_color.png| + + |set_knob_grad_color.png| + + MicroPython Code Block: + + .. code-block:: python + + bar.set_bg_grad_color(0x00FF00, 255, 0xFF0000, 255, lv.GRAD_DIR.HOR, lv.PART.MAIN | lv.STATE.DEFAULT) + bar.set_bg_grad_color(0x00FF00, 255, 0xFF0000, 255, lv.GRAD_DIR.HOR, lv.PART.INDICATOR | lv.STATE.DEFAULT) + + .. py:method:: set_value(value, anim) + + Set the value of the slider. + + :param int value: The value to set. + :param bool anim: Whether to animate the change. + :return: None + + UiFlow2 Code Block: + + |set_value.png| + + MicroPython Code Block: + + .. code-block:: python + + slider_0.set_value(50, True) + + .. py:method:: get_value() + + Get the current value of the slider. + + :return: The current value of the slider. + :rtype: int + + UiFlow2 Code Block: + + |get_value.png| + + MicroPython Code Block: + + .. code-block:: python + + value = slider_0.get_value() + + .. py:method:: set_range(min_value, max_value) + + Set the range of the slider. + + :param int min_value: The minimum value of the range. + :param int max_value: The maximum value of the range. + :return: None + + UiFlow2 Code Block: + + |set_range.png| + + MicroPython Code Block: + + .. code-block:: python + + slider_0.set_range(0, 200) + + .. py:method:: set_mode(mode) + + Set the mode of the slider. + + .. py:method:: get_min_value() + + Get the minimum value of the slider range. + + :return: The minimum value of the slider range. + :rtype: int + + UiFlow2 Code Block: + + |get_min_value.png| + + MicroPython Code Block: + + .. code-block:: python + + min_value = slider_0.get_min_value() + + .. py:method:: get_max_value() + + Get the maximum value of the slider range. + + :return: The maximum value of the slider range. + :rtype: int + + UiFlow2 Code Block: + + |get_max_value.png| + + MicroPython Code Block: + + .. code-block:: python + + max_value = slider_0.get_max_value() + + .. py:method:: set_pos(x, y) + + Set the position of the slider. + + :param int x: The x-coordinate of the slider. + :param int y: The y-coordinate of the slider. + :return: None + + UiFlow2 Code Block: + + |set_pos.png| + + MicroPython Code Block: + + .. code-block:: python + + slider_0.set_pos(100, 100) + + .. py:method:: set_x(x) + + Set the x-coordinate of the slider. + + :param int x: The x-coordinate of the slider. + :return: None + + UiFlow2 Code Block: + + |set_x.png| + + MicroPython Code Block: + + .. code-block:: python + + slider_0.set_x(100) + + .. py:method:: set_y(y) + + Set the y-coordinate of the slider. + + :param int y: The y-coordinate of the slider. + :return: None + + UiFlow2 Code Block: + + |set_y.png| + + MicroPython Code Block: + + .. code-block:: python + + slider_0.set_y(100) + + .. py:method:: align_to(obj, align, x, y) + + Align the slider to another object. + + :param lv.obj obj: The object to align to. + :param int align: The alignment type. + :param int x: The x-offset from the aligned object. + :param int y: The y-offset from the aligned object. + :return: None + + UiFlow2 Code Block: + + |align_to.png| + + MicroPython Code Block: + + .. code-block:: python + + slider_0.align_to(page_0, lv.ALIGN.CENTER, 0, 0) + + .. py:method:: set_size(width, height) + + Set the size of the slider. + + :param int width: The width of the slider. + :param int height: The height of the slider. + :return: None + + UiFlow2 Code Block: + + |set_size.png| + + MicroPython Code Block: + + .. code-block:: python + + slider_0.set_size(200, 20) + + .. py:method:: set_width(width) + + Set the width of the slider. + + :param int width: The width of the slider. + :return: None + + UiFlow2 Code Block: + + |set_width.png| + + MicroPython Code Block: + + .. code-block:: python + + slider_0.set_width(200) + + .. py:method:: get_width() + + Get the width of the slider. + + :return: The width of the slider. + :rtype: int + + UiFlow2 Code Block: + + |get_width.png| + + MicroPython Code Block: + + .. code-block:: python + + slider_0.get_width() + + .. py:method:: set_height(height) + + Set the height of the slider. + + :param int height: The height of the slider. + :return: None + + UiFlow2 Code Block: + + |set_height.png| + + MicroPython Code Block: + + .. code-block:: python + + slider_0.set_height(20) + + .. py:method:: get_height() + + Get the height of the slider. + + :return: The height of the slider. + :rtype: int + + UiFlow2 Code Block: + + |get_height.png| + + MicroPython Code Block: + + .. code-block:: python + + slider_0.get_height() diff --git a/docs/en/refs/m5ui.slider.ref b/docs/en/refs/m5ui.slider.ref new file mode 100644 index 00000000..7acb9d49 --- /dev/null +++ b/docs/en/refs/m5ui.slider.ref @@ -0,0 +1,34 @@ +.. |cores3_slider_basic_example.m5f2| raw:: html + + cores3_slider_basic_example.m5f2 + +.. |cores3_slider_basic_example.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/slider/cores3_slider_basic_example.png + +.. |align_to.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/slider/align_to.png +.. |event.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/slider/event.png +.. |get_height.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/slider/get_height.png +.. |get_max_value.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/slider/get_max_value.png +.. |get_min_value.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/slider/get_min_value.png +.. |get_value.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/slider/get_value.png +.. |get_width.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/slider/get_width.png +.. |set_bg_color.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/slider/set_bg_color.png +.. |set_bg_grad_color.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/slider/set_bg_grad_color.png +.. |set_bg_radius.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/slider/set_bg_radius.png +.. |set_flag.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/slider/set_flag.png +.. |set_height.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/slider/set_height.png +.. |set_indicator_color.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/slider/set_indicator_color.png +.. |set_indicator_grad_color.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/slider/set_indicator_grad_color.png +.. |set_indicator_radius.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/slider/set_indicator_radius.png +.. |set_knob_color.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/slider/set_knob_color.png +.. |set_knob_grad_color.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/slider/set_knob_grad_color.png +.. |set_knob_radius.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/slider/set_knob_radius.png +.. |set_pos.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/slider/set_pos.png +.. |set_range.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/slider/set_range.png +.. |set_size.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/slider/set_size.png +.. |set_state.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/slider/set_state.png +.. |set_value.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/slider/set_value.png +.. |set_width.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/slider/set_width.png +.. |set_x.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/slider/set_x.png +.. |set_y.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/slider/set_y.png +.. |toggle_flag.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/slider/toggle_flag.png +.. |toggle_state.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/slider/toggle_state.png diff --git a/docs/locales/zh_CN/LC_MESSAGES/m5ui/slider.po b/docs/locales/zh_CN/LC_MESSAGES/m5ui/slider.po new file mode 100644 index 00000000..c4afe3fe --- /dev/null +++ b/docs/locales/zh_CN/LC_MESSAGES/m5ui/slider.po @@ -0,0 +1,641 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2016 - 2025 M5Stack Technology Co., Ltd +# This file is distributed under the same license as the UIFlow2 Programming +# Guide package. +# FIRST AUTHOR , 2025. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: UIFlow2 Programming Guide \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-07-27 15:10+0800\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: zh_CN\n" +"Language-Team: zh_CN \n" +"Plural-Forms: nplurals=1; plural=0;\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.17.0\n" + +#: ../../en/m5ui/slider.rst:4 ../../en/m5ui/slider.rst:52 +#: 1397854c521d487690d3b990e9efa6c1 19be2e4ea65b4c36948da65f7ca2742f +msgid "M5Slider" +msgstr "" + +#: ../../en/m5ui/slider.rst:8 8b35ebbe69da40938fd001afb28305ce +msgid "" +"M5Slider is a widget that can be used to create sliders in the user " +"interface. It allows users to select a value from a range by dragging a " +"handle along a track." +msgstr "M5Slider是一个可以在用户界面中创建滑块的控件。它允许用户通过沿着轨道拖动手柄来从范围内选择一个值。" + +#: ../../en/m5ui/slider.rst:11 7aa95fb8de1743878179930220d7fb29 +msgid "UiFlow2 Example" +msgstr "UiFlow2 应用示例" + +#: ../../en/m5ui/slider.rst:14 ../../en/m5ui/slider.rst:33 +#: 017b653fa2e943b897a0ee9183086543 04a3cea74a864e858effd9c0a145530d +msgid "basic slider" +msgstr "" + +#: ../../en/m5ui/slider.rst:16 d05430de994d4f269c407e13fb9724e2 +msgid "Open the |cores3_slider_basic_example.m5f2| project in UiFlow2." +msgstr "在UiFlow2中打开|cores3_slider_basic_example.m5f2|项目。" + +#: ../../en/m5ui/slider.rst:18 ../../en/m5ui/slider.rst:35 +#: a090ff570bd1406cab842bb4aabbfc96 +msgid "" +"This example creates a basic slider that can be used to select values " +"from 0 to 100." +msgstr "此示例创建一个基础滑块,可以用来选择0到100之间的值。" + +#: ../../en/m5ui/slider.rst:20 ../../en/m5ui/slider.rst:65 +#: ../../en/m5ui/slider.rst:82 ../../en/m5ui/slider.rst:100 +#: ../../en/m5ui/slider.rst:117 ../../en/m5ui/slider.rst:136 +#: ../../en/m5ui/slider.rst:167 ../../en/m5ui/slider.rst:185 +#: ../../en/m5ui/slider.rst:211 ../../en/m5ui/slider.rst:234 +#: ../../en/m5ui/slider.rst:251 ../../en/m5ui/slider.rst:269 +#: ../../en/m5ui/slider.rst:290 ../../en/m5ui/slider.rst:307 +#: ../../en/m5ui/slider.rst:325 ../../en/m5ui/slider.rst:342 +#: ../../en/m5ui/slider.rst:359 ../../en/m5ui/slider.rst:379 +#: ../../en/m5ui/slider.rst:397 ../../en/m5ui/slider.rst:414 +#: ../../en/m5ui/slider.rst:431 ../../en/m5ui/slider.rst:448 +#: ../../en/m5ui/slider.rst:465 014091ce899f43faa4a224c219a4a518 +#: 4ef61c35f92240c6b6f818a875685cb1 +msgid "UiFlow2 Code Block:" +msgstr "UiFlow2 代码块:" + +#: ../../en/m5ui/slider.rst:22 9b9f46dad577481385b5d0f3117fd9a3 +msgid "|cores3_slider_basic_example.png|" +msgstr "" + +#: ../../en/refs/m5ui.slider.ref:5 53a4405c344a4b6d8571ce3a0bb1ffde +msgid "cores3_slider_basic_example.png" +msgstr "" + +#: ../../en/m5ui/slider.rst:24 ../../en/m5ui/slider.rst:43 +#: a491fc9033ed4847b1abafcc854368a5 +msgid "Example output:" +msgstr "示例输出:" + +#: ../../en/m5ui/slider.rst:26 ../../en/m5ui/slider.rst:45 +#: ../../en/m5ui/slider.rst:63 ../../en/m5ui/slider.rst:80 +#: ../../en/m5ui/slider.rst:98 ../../en/m5ui/slider.rst:115 +#: ../../en/m5ui/slider.rst:134 ../../en/m5ui/slider.rst:165 +#: ../../en/m5ui/slider.rst:183 ../../en/m5ui/slider.rst:209 +#: ../../en/m5ui/slider.rst:232 ../../en/m5ui/slider.rst:267 +#: ../../en/m5ui/slider.rst:323 ../../en/m5ui/slider.rst:340 +#: ../../en/m5ui/slider.rst:357 ../../en/m5ui/slider.rst:377 +#: ../../en/m5ui/slider.rst:395 ../../en/m5ui/slider.rst:412 +#: ../../en/m5ui/slider.rst:446 aa01d2aec7794b3280cf4080b1c0b19e +#: cec5e0f5625b49fc879bb7a308105f44 eec338e83a3e4153bed7ce72e557098f +msgid "None" +msgstr "" + +#: ../../en/m5ui/slider.rst:30 956901da43d1449ebfdf3644f899db94 +msgid "MicroPython Example" +msgstr "MicroPython 应用示例" + +#: ../../en/m5ui/slider.rst:37 ../../en/m5ui/slider.rst:69 +#: ../../en/m5ui/slider.rst:86 ../../en/m5ui/slider.rst:104 +#: ../../en/m5ui/slider.rst:121 ../../en/m5ui/slider.rst:140 +#: ../../en/m5ui/slider.rst:171 ../../en/m5ui/slider.rst:193 +#: ../../en/m5ui/slider.rst:219 ../../en/m5ui/slider.rst:238 +#: ../../en/m5ui/slider.rst:255 ../../en/m5ui/slider.rst:273 +#: ../../en/m5ui/slider.rst:294 ../../en/m5ui/slider.rst:311 +#: ../../en/m5ui/slider.rst:329 ../../en/m5ui/slider.rst:346 +#: ../../en/m5ui/slider.rst:363 ../../en/m5ui/slider.rst:383 +#: ../../en/m5ui/slider.rst:401 ../../en/m5ui/slider.rst:418 +#: ../../en/m5ui/slider.rst:435 ../../en/m5ui/slider.rst:452 +#: ../../en/m5ui/slider.rst:469 17124aac9cda4384b47343acd7439794 +#: bd1c7933189d4e7294eda9915c177864 +msgid "MicroPython Code Block:" +msgstr "MicroPython 代码块:" + +#: ../../en/m5ui/slider.rst:49 7c3240cb66d3434da8b1e7fece47e122 +msgid "**API**" +msgstr "API参考" + +#: 16a861e9c0ab42acbf2cda652c59b7fe m5ui.slider.M5Slider:1 of +msgid "Bases: :py:class:`~lvgl.slider`" +msgstr "" + +#: 23299e21f5734e0a8b2b353b929268b3 m5ui.slider.M5Slider:1 of +msgid "Create a slider widget." +msgstr "创建一个滑块小部件。" + +#: ../../en/m5ui/slider.rst 1732d89b6033416baced4cae5d2f2d7d +msgid "Parameters" +msgstr "" + +#: 5f84dbd4555d474db2193a2944060527 m5ui.slider.M5Slider:3 of +msgid "The x position of the slider." +msgstr "滑块的 x 位置。" + +#: 5f84dbd4555d474db2193a2944060527 m5ui.slider.M5Slider:4 of +msgid "The y position of the slider." +msgstr "滑块的 y 位置。" + +#: ../../en/m5ui/slider.rst:393 ../../en/m5ui/slider.rst:411 +#: ../../en/m5ui/slider.rst:428 4d2b6cc9402e42d582c5547c3a940565 +#: m5ui.slider.M5Slider:5 of +msgid "The width of the slider." +msgstr "滑块的宽度。" + +#: ../../en/m5ui/slider.rst:394 ../../en/m5ui/slider.rst:445 +#: ../../en/m5ui/slider.rst:462 a261bfe607e84533be1badbb50013666 +#: m5ui.slider.M5Slider:6 of +msgid "The height of the slider." +msgstr "滑块的高度。" + +#: 24f3c6d9b3c24f9f8e912d0a57cdb6e1 m5ui.slider.M5Slider:7 of +msgid "only `lv.slider.MODE.NORMAL` is supported." +msgstr "仅支持“lv.slider.MODE.NORMAL”。" + +#: 82f947a439cf4cafb10dc1d477a2fcd8 m5ui.slider.M5Slider:8 of +msgid "The minimum value of the slider." +msgstr "范围的最小值。" + +#: 076ab011d4fd4dae9f3fe7bf8eca7be1 m5ui.slider.M5Slider:9 of +msgid "The maximum value of the slider." +msgstr "范围的最大值。" + +#: 711e38aa18ba44caa4ce28e3db5e7263 m5ui.slider.M5Slider:10 of +msgid "The initial value of the slider." +msgstr "滑块的当前值。" + +#: f9e7553f221b46e79723c62330d18047 m5ui.slider.M5Slider:11 of +msgid "The background color of the slider." +msgstr "设置滑块的背景颜色。" + +#: 8fc7d234738b424f8521510485ba7d98 m5ui.slider.M5Slider:12 of +msgid "The color of the slider indicator." +msgstr "滑块指示器的颜色。" + +#: 4a54696e4090478f90822a3526b5850e m5ui.slider.M5Slider:13 of +msgid "" +"The parent object of the slider. If not specified, it will be set to the " +"active screen." +msgstr "滑块的父对象。如果未指定,则将被设置为活动屏幕。" + +#: ../../en/m5ui/slider.rst:59 aab92ea678f842229ae9c9f47398050a +msgid "" +"Set a flag on the object. If ``value`` is True, the flag is added; if " +"False, the flag is removed." +msgstr "设置对象上的标志。如果 ``value`` 为True,则添加标志;如果为False,则移除标志。" + +#: ../../en/m5ui/slider.rst:61 f94db829a86f44739fd888c5d571e14a +msgid "The flag to set." +msgstr "要设置的标志。" + +#: ../../en/m5ui/slider.rst:62 dac5509718a8483190f6090e5dbc46a7 +msgid "If True, the flag is added; if False, the flag is removed." +msgstr "如果为True,则添加标志;如果为False,则移除标志。" + +#: ../../en/m5ui/slider.rst 00feeabbb9d44514835dd2f77943a5a5 +#: a01772ee12844d51851760cfbd879156 +msgid "Returns" +msgstr "" + +#: ../../en/m5ui/slider.rst:67 a5ac599efdda4b63a21ab2d202176431 +msgid "|set_flag.png|" +msgstr "" + +#: ../../en/refs/m5ui.slider.ref:17 6f0cd885338a4712a38be890d1e0e548 +msgid "set_flag.png" +msgstr "" + +#: ../../en/m5ui/slider.rst:77 66abdee964af46b2a75a3ab6c17013ee +msgid "" +"Toggle a flag on the object. If the flag is set, it is removed; if not " +"set, it is added." +msgstr "切换对象上的标志。如果标志已设置,则移除它;如果未设置,则添加它。" + +#: ../../en/m5ui/slider.rst:79 5d79e9c32c2d46c283f1d48535458249 +msgid "The flag to toggle." +msgstr "要切换的标志。" + +#: ../../en/m5ui/slider.rst:84 0c36fb8275394cfebbb72ac5ebfd6e70 +msgid "|toggle_flag.png|" +msgstr "" + +#: ../../en/refs/m5ui.slider.ref:33 2eb397982a5342bc97ca6d4e4f719eb2 +msgid "toggle_flag.png" +msgstr "" + +#: ../../en/m5ui/slider.rst:94 aab92ea678f842229ae9c9f47398050a +msgid "" +"Set the state of the slider. If ``value`` is True, the state is set; if " +"False, the state is unset." +msgstr "设置滑块的状态。如果 ``value`` 为 True,则设置状态;如果为 False,则取消设置状态。" + +#: ../../en/m5ui/slider.rst:96 d17b68f15b6a4972971fcaf1c92c50fa +msgid "The state to set." +msgstr "要设置的状态。" + +#: ../../en/m5ui/slider.rst:97 dac5509718a8483190f6090e5dbc46a7 +msgid "If True, the state is set; if False, the state is unset." +msgstr "如果为 True,则状态已设置;如果为 False,则状态未设置。" + +#: ../../en/m5ui/slider.rst:102 7330e7761ca54bdc8ab9fbbf11701d44 +msgid "|set_state.png|" +msgstr "" + +#: ../../en/refs/m5ui.slider.ref:28 3e9e20aadfe942c1a791c8c287653302 +msgid "set_state.png" +msgstr "" + +#: ../../en/m5ui/slider.rst:112 66abdee964af46b2a75a3ab6c17013ee +msgid "" +"Toggle the state of the slider. If the state is set, it is unset; if not " +"set, it is set." +msgstr "切换滑块的状态。如果滑块状态已设置,则表示取消设置;如果滑块状态未设置,则表示设置。" + +#: ../../en/m5ui/slider.rst:114 5d79e9c32c2d46c283f1d48535458249 +msgid "The state to toggle." +msgstr "要切换的标志。" + +#: ../../en/m5ui/slider.rst:119 0c36fb8275394cfebbb72ac5ebfd6e70 +msgid "|toggle_state.png|" +msgstr "" + +#: ../../en/refs/m5ui.slider.ref:34 2eb397982a5342bc97ca6d4e4f719eb2 +msgid "toggle_state.png" +msgstr "" + +#: ../../en/m5ui/slider.rst:129 4a54696e4090478f90822a3526b5850e +msgid "" +"Add an event callback to the slider. The callback will be called when the" +" specified event occurs." +msgstr "为滑块添加事件回调。当指定的事件发生时,将调用回调函数。" + +#: ../../en/m5ui/slider.rst:131 65bb7055160a42908280b70e4604661d +msgid "The callback function to call." +msgstr "要调用的回调函数。" + +#: ../../en/m5ui/slider.rst:132 8355c08e88924947ad1eba716d194d67 +msgid "The event to listen for." +msgstr "要监听的事件。" + +#: ../../en/m5ui/slider.rst:133 e32472679fad4e4e8b9a924b744adb3d +msgid "Optional user data to pass to the callback." +msgstr "传递给回调函数的可选用户数据。" + +#: ../../en/m5ui/slider.rst:138 806a741155a443598ca8be98263cbb24 +msgid "|event.png|" +msgstr "" + +#: ../../en/refs/m5ui.slider.ref:8 c0723d4277ec4626bd1ca22afc0f351e +msgid "event.png" +msgstr "" + +#: ../../en/m5ui/slider.rst:160 f9e7553f221b46e79723c62330d18047 +msgid "Set the background color of the slider." +msgstr "设置滑块的背景颜色。" + +#: ../../en/m5ui/slider.rst:162 20c7b4f4339d4c51aa999e2cccf2d03d +msgid "The color to set." +msgstr "要设置的颜色。" + +#: ../../en/m5ui/slider.rst:163 cb73b9f926504ae0b75af4bee33df099 +msgid "" +"The opacity of the color. The value should be between 0 (transparent) and" +" 255 (opaque)." +msgstr "颜色的不透明度。值应在0(透明)和255(不透明)之间。" + +#: ../../en/m5ui/slider.rst:164 256992bdae0d44068f79b7494da70c8d +msgid "" +"The part of the object to apply the style to (e.g., lv.PART.MAIN, " +"lv.PART.INDICATOR)." +msgstr "要应用样式的对象部分(例如,lv.PART.MAIN,lv.PART.INDICATOR)。" + +#: ../../en/m5ui/slider.rst:169 d33af1890ff24042bac51bbfa6f50728 +msgid "|set_bg_color.png|" +msgstr "" + +#: ../../en/refs/m5ui.slider.ref:14 5d94c27365984efebd076f33551d38b1 +msgid "set_bg_color.png" +msgstr "" + +#: ../../en/m5ui/slider.rst:179 8fc7d234738b424f8521510485ba7d98 +msgid "Set the corner radius of the slider components." +msgstr "设置滑块组件的角半径。" + +#: ../../en/m5ui/slider.rst:181 6374ed0fac604faca29b41be4eb707fb +msgid "The radius to set." +msgstr "要设置的半径。" + +#: ../../en/m5ui/slider.rst:182 7b6469178e5142fe87eb08d67f7df969 +msgid "" +"The part of the object to apply the style to (e.g., lv.PART.MAIN, " +"lv.PART.KNOB)." +msgstr "要应用样式的对象部分(例如,lv.PART.MAIN,lv.PART.KNOB)。" + +#: ../../en/m5ui/slider.rst:187 daf4d4ff51f94035940927cce87bd3a3 +msgid "|set_bg_radius.png|" +msgstr "" + +#: ../../en/refs/m5ui.slider.ref:16 f69130b4e74f4068be25ac8912ef5d02 +msgid "set_bg_radius.png" +msgstr "" + +#: ../../en/m5ui/slider.rst:189 daf4d4ff51f94035940927cce87bd3a3 +msgid "|set_indicator_radius.png|" +msgstr "" + +#: ../../en/refs/m5ui.slider.ref:21 f69130b4e74f4068be25ac8912ef5d02 +msgid "set_indicator_radius.png" +msgstr "" + +#: ../../en/m5ui/slider.rst:191 daf4d4ff51f94035940927cce87bd3a3 +msgid "|set_knob_radius.png|" +msgstr "" + +#: ../../en/refs/m5ui.slider.ref:24 f69130b4e74f4068be25ac8912ef5d02 +msgid "set_knob_radius.png" +msgstr "" + +#: ../../en/m5ui/slider.rst:201 f9e7553f221b46e79723c62330d18047 +msgid "Set the background gradient color of the bar." +msgstr "设置滑块的背景颜色。" + +#: ../../en/m5ui/slider.rst:203 454cc3e88dfa455286fc3acbd73dddaa +msgid "The start color of the gradient, can be an integer (RGB)." +msgstr "渐变的起始颜色,可以是整数(RGB)。" + +#: ../../en/m5ui/slider.rst:204 e9d11671b32447329d23ac350a84e8e1 +msgid "The opacity of the start color (0-255)." +msgstr "起始颜色的不透明度(0-255)。" + +#: ../../en/m5ui/slider.rst:205 dbee46023b0340668007aff7e26170dc +msgid "The end color of the gradient, can be an integer (RGB)." +msgstr "渐变的结束颜色,可以是整数(RGB)。" + +#: ../../en/m5ui/slider.rst:206 89c071e1827947cfbb6508595e1dd17b +msgid "The opacity of the end color (0-255)." +msgstr "结束颜色的不透明度(0-255)。" + +#: ../../en/m5ui/slider.rst:207 7b6469178e5142fe87eb08d67f7df969 +msgid "The direction of the gradient (e.g., lv.GRAD_DIR.VER)." +msgstr "渐变的方向(例如,lv.GRAD_DIR.VER)。" + +#: ../../en/m5ui/slider.rst:208 7b6469178e5142fe87eb08d67f7df969 +msgid "The part of the object to apply the style to (e.g., lv.PART.MAIN)." +msgstr "要应用样式的对象部分(例如,lv.PART.MAIN,lv.PART.KNOB)。" + +#: ../../en/m5ui/slider.rst:213 257ddd7e18464322865311d44412e04e +msgid "|set_bg_grad_color.png|" +msgstr "" + +#: ../../en/refs/m5ui.slider.ref:15 5d94c27365984efebd076f33551d38b1 +msgid "set_bg_grad_color.png" +msgstr "" + +#: ../../en/m5ui/slider.rst:215 257ddd7e18464322865311d44412e04e +msgid "|set_indicator_grad_color.png|" +msgstr "" + +#: ../../en/refs/m5ui.slider.ref:20 e7a5a6460d7347e28cc84bf2c45b6bfd +msgid "set_indicator_grad_color.png" +msgstr "" + +#: ../../en/m5ui/slider.rst:217 257ddd7e18464322865311d44412e04e +msgid "|set_knob_grad_color.png|" +msgstr "" + +#: ../../en/refs/m5ui.slider.ref:23 e7a5a6460d7347e28cc84bf2c45b6bfd +msgid "set_knob_grad_color.png" +msgstr "" + +#: ../../en/m5ui/slider.rst:228 d8d62a2d582c4e8c9705e7e372e9efc9 +msgid "Set the value of the slider." +msgstr "设置滑块的值。" + +#: ../../en/m5ui/slider.rst:230 d17b68f15b6a4972971fcaf1c92c50fa +msgid "The value to set." +msgstr "要设置的值。" + +#: ../../en/m5ui/slider.rst:231 ca68dc5764954674beac911360a828af +msgid "Whether to animate the change." +msgstr "是否使用动画效果进行更改。" + +#: ../../en/m5ui/slider.rst:236 2f81a90297a8498392adc9bf4052ac2c +msgid "|set_value.png|" +msgstr "" + +#: ../../en/refs/m5ui.slider.ref:29 617e47f1971f41caba6c8e5e51ff98a4 +msgid "set_value.png" +msgstr "" + +#: ../../en/m5ui/slider.rst:246 61eb97c5795d48a0a38b81e2c3c454d9 +msgid "Get the current value of the slider." +msgstr "获取滑块的当前值。" + +#: ../../en/m5ui/slider.rst:248 711e38aa18ba44caa4ce28e3db5e7263 +msgid "The current value of the slider." +msgstr "滑块的当前值。" + +#: ../../en/m5ui/slider.rst 8edf7a0801aa4ce4915e5eeabb892ac5 +msgid "Return type" +msgstr "" + +#: ../../en/m5ui/slider.rst:253 9d1c2f12ffc44b2ca77438696471e66c +msgid "|get_value.png|" +msgstr "" + +#: ../../en/refs/m5ui.slider.ref:12 f5105cbbf6ec46d7bde721288037fa81 +msgid "get_value.png" +msgstr "" + +#: ../../en/m5ui/slider.rst:263 f4486db28a3447b0a62907d445cc998f +msgid "Set the range of the slider." +msgstr "设置滑块的范围。" + +#: ../../en/m5ui/slider.rst:265 82f947a439cf4cafb10dc1d477a2fcd8 +msgid "The minimum value of the range." +msgstr "范围的最小值。" + +#: ../../en/m5ui/slider.rst:266 076ab011d4fd4dae9f3fe7bf8eca7be1 +msgid "The maximum value of the range." +msgstr "范围的最大值。" + +#: ../../en/m5ui/slider.rst:271 35428c8a140c456eb3324b75cd661d46 +msgid "|set_range.png|" +msgstr "" + +#: ../../en/refs/m5ui.slider.ref:26 ab58fb66243a4b7c80baa9cc0bc1bf91 +msgid "set_range.png" +msgstr "" + +#: ../../en/m5ui/slider.rst:281 ddfb383783fe41809f04330f144f5975 +msgid "Set the mode of the slider." +msgstr "设置滑块的模式。" + +#: ../../en/m5ui/slider.rst:285 61eb97c5795d48a0a38b81e2c3c454d9 +msgid "Get the minimum value of the slider range." +msgstr "范围的最小值。" + +#: ../../en/m5ui/slider.rst:287 82f947a439cf4cafb10dc1d477a2fcd8 +msgid "The minimum value of the slider range." +msgstr "范围的最小值。" + +#: ../../en/m5ui/slider.rst:292 9d1c2f12ffc44b2ca77438696471e66c +msgid "|get_min_value.png|" +msgstr "" + +#: ../../en/refs/m5ui.slider.ref:11 f5105cbbf6ec46d7bde721288037fa81 +msgid "get_min_value.png" +msgstr "" + +#: ../../en/m5ui/slider.rst:302 61eb97c5795d48a0a38b81e2c3c454d9 +msgid "Get the maximum value of the slider range." +msgstr "范围的最大值。" + +#: ../../en/m5ui/slider.rst:304 076ab011d4fd4dae9f3fe7bf8eca7be1 +msgid "The maximum value of the slider range." +msgstr "范围的最大值。" + +#: ../../en/m5ui/slider.rst:309 9d1c2f12ffc44b2ca77438696471e66c +msgid "|get_max_value.png|" +msgstr "" + +#: ../../en/refs/m5ui.slider.ref:10 f5105cbbf6ec46d7bde721288037fa81 +msgid "get_max_value.png" +msgstr "" + +#: ../../en/m5ui/slider.rst:319 5f84dbd4555d474db2193a2944060527 +msgid "Set the position of the slider." +msgstr "设置滑块的位置。" + +#: ../../en/m5ui/slider.rst:321 ../../en/m5ui/slider.rst:339 +#: 1432d2c3dc3b4eb1b1e91cb56cf09fd6 +msgid "The x-coordinate of the slider." +msgstr "滑块的x坐标。" + +#: ../../en/m5ui/slider.rst:322 ../../en/m5ui/slider.rst:356 +#: f95f24aab6f74f9987278ef180b5b977 +msgid "The y-coordinate of the slider." +msgstr "滑块的y坐标。" + +#: ../../en/m5ui/slider.rst:327 0594893fa6f54a0eb96a924c4a27768f +msgid "|set_pos.png|" +msgstr "" + +#: ../../en/refs/m5ui.slider.ref:25 82ea1f43cab84e93acf975a643475159 +msgid "set_pos.png" +msgstr "" + +#: ../../en/m5ui/slider.rst:337 5962ac94367346f78ed7aaf94241c226 +msgid "Set the x-coordinate of the slider." +msgstr "设置滑块的x坐标。" + +#: ../../en/m5ui/slider.rst:344 dd0bfec12f534b309faa0e335f85273a +msgid "|set_x.png|" +msgstr "" + +#: ../../en/refs/m5ui.slider.ref:31 c9a030ceedca4553b78a96c02bf99d97 +msgid "set_x.png" +msgstr "" + +#: ../../en/m5ui/slider.rst:354 c0d8380d483a44fe92032de62875eca1 +msgid "Set the y-coordinate of the slider." +msgstr "设置滑块的y坐标。" + +#: ../../en/m5ui/slider.rst:361 6bfd7731be23448ab7d79aca4d0ba9fc +msgid "|set_y.png|" +msgstr "" + +#: ../../en/refs/m5ui.slider.ref:32 72dd2520f9e5482fa4db1835e17541a3 +msgid "set_y.png" +msgstr "" + +#: ../../en/m5ui/slider.rst:371 be06bed795104d9d882dc08766695848 +msgid "Align the slider to another object." +msgstr "将滑块与另一个对象对齐。" + +#: ../../en/m5ui/slider.rst:373 50a6726f6fc341c9ba844e6c9e996b4c +msgid "The object to align to." +msgstr "要对齐的对象。" + +#: ../../en/m5ui/slider.rst:374 d1577135905d4ca0a242156f1c19c8b5 +msgid "The alignment type." +msgstr "对齐类型。" + +#: ../../en/m5ui/slider.rst:375 f39a4c0ee9f746fa93ad2025bbe205ab +msgid "The x-offset from the aligned object." +msgstr "与对齐对象的 x 偏移量。" + +#: ../../en/m5ui/slider.rst:376 48d9a4569fcf41d1830e7db817367163 +msgid "The y-offset from the aligned object." +msgstr "与对齐对象的 y 偏移。" + +#: ../../en/m5ui/slider.rst:381 0b53122efd844fdb852e8aa3f1629f2f +msgid "|align_to.png|" +msgstr "" + +#: ../../en/refs/m5ui.slider.ref:7 f5ec2cdf36774bf4b81e9d238c468691 +msgid "align_to.png" +msgstr "" + +#: ../../en/m5ui/slider.rst:391 ea8b0a23633c4d48b90f6397ca58f083 +msgid "Set the size of the slider." +msgstr "设置滑块的尺寸。" + +#: ../../en/m5ui/slider.rst:399 7330e7761ca54bdc8ab9fbbf11701d44 +msgid "|set_size.png|" +msgstr "" + +#: ../../en/refs/m5ui.slider.ref:27 3e9e20aadfe942c1a791c8c287653302 +msgid "set_size.png" +msgstr "" + +#: ../../en/m5ui/slider.rst:409 0a86d2786bc34cbba97d8bd3f68a0077 +msgid "Set the width of the slider." +msgstr "设置滑块的宽度。" + +#: ../../en/m5ui/slider.rst:416 11a78c97c58d493bb0edfc939e4fac9a +msgid "|set_width.png|" +msgstr "" + +#: ../../en/refs/m5ui.slider.ref:30 7aea67faa5224156a0b2a66a47453fa3 +msgid "set_width.png" +msgstr "" + +#: ../../en/m5ui/slider.rst:426 edf0dfaf03a547fe927d811b80ffee98 +msgid "Get the width of the slider." +msgstr "获取滑块的宽度。" + +#: ../../en/m5ui/slider.rst:433 9badf6bc6a574f8397f7e1564ed9fe73 +msgid "|get_width.png|" +msgstr "" + +#: ../../en/refs/m5ui.slider.ref:13 2f59fe60049c4f009c5538bd0d9398d6 +msgid "get_width.png" +msgstr "" + +#: ../../en/m5ui/slider.rst:443 dbc70e98ed454c89b643f613dcab96ac +msgid "Set the height of the slider." +msgstr "设置滑块的高度。" + +#: ../../en/m5ui/slider.rst:450 7e8b33f2474f48fdaf33e07b76acd9a3 +msgid "|set_height.png|" +msgstr "" + +#: ../../en/refs/m5ui.slider.ref:18 5a3ba49167904c789214c8c491b43484 +msgid "set_height.png" +msgstr "" + +#: ../../en/m5ui/slider.rst:460 257e3d2c7ce24590bcc341c408eb3c5e +msgid "Get the height of the slider." +msgstr "获取滑块的高度。" + +#: ../../en/m5ui/slider.rst:467 1a9c29de406b4522b910cd6698607f7e +msgid "|get_height.png|" +msgstr "" + +#: ../../en/refs/m5ui.slider.ref:9 1b02b5696ba14e5cad528f61709d783a +msgid "get_height.png" +msgstr "" diff --git a/examples/m5ui/slider/cores3_slider_basic_example.m5f2 b/examples/m5ui/slider/cores3_slider_basic_example.m5f2 new file mode 100644 index 00000000..1de766a5 --- /dev/null +++ b/examples/m5ui/slider/cores3_slider_basic_example.m5f2 @@ -0,0 +1 @@ +{"version":"V2.0","versionNumber":"V2.3.2","type":"cores3","components":[{"name":"page0","type":"lvgl_page","layer":0,"screenId":"builtin","screenName":"","id":"qJ!shIcWvF&E=L*+","createTime":1753600881709,"backgroundColor":"#ffffff","isLVGL":true,"isSelected":true},{"name":"slider0","type":"lvgl_slider","layer":1,"screenId":"builtin","screenName":"","id":"nEPsiLvRtXBWeZAx","createTime":1753600888553,"x":60,"y":110,"width":200,"height":19,"minValue":0,"maxValue":100,"currentValue":25,"color":"#2193f3","backgroundColor":"#2193f3","pageId":"qJ!shIcWvF&E=L*+","isLVGL":true,"isSelected":false},{"name":"label0","type":"lvgl_label","layer":2,"screenId":"builtin","screenName":"","id":"vJtLIeImUBgSHhDi","createTime":1753600899631,"x":151,"y":142,"color":"#000000","backgroundColor":"#ffffff","bg_opacity":0,"text":"25","font":"lv.font_montserrat_14","pageId":"qJ!shIcWvF&E=L*+","isLVGL":true,"isSelected":false,"width":43,"height":15}],"resources":[{"hardware":["hardware_button","hardware_pin_button","imu","speaker","touch","als","mic","sdcard"]}],"units":[],"hats":[],"bases":[],"i2cs":[],"blockly":"truepage0trueslider0VALUE_CHANGEDlabel0hello M5slider0","screen":[{"simulationName":"Built-in","type":"builtin","width":320,"height":240,"scale":0.77,"screenName":"","blockId":"","screenColorType":0,"rotation":1,"id":"builtin","createTime":1753600881707}],"logicWhenNum":0,"customList":[]} \ No newline at end of file diff --git a/examples/m5ui/slider/cores3_slider_basic_example.py b/examples/m5ui/slider/cores3_slider_basic_example.py new file mode 100644 index 00000000..ad96d764 --- /dev/null +++ b/examples/m5ui/slider/cores3_slider_basic_example.py @@ -0,0 +1,83 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import os, sys, io +import M5 +from M5 import * +import m5ui +import lvgl as lv + + +page0 = None +slider0 = None +label0 = None + + +def slider0_value_changed_event(event_struct): + global page0, slider0, label0 + label0.set_text(str(slider0.get_value())) + + +def slider0_event_handler(event_struct): + global page0, slider0, label0 + event = event_struct.code + if event == lv.EVENT.VALUE_CHANGED and True: + slider0_value_changed_event(event_struct) + return + + +def setup(): + global page0, slider0, label0 + + M5.begin() + Widgets.setRotation(1) + m5ui.init() + page0 = m5ui.M5Page(bg_c=0xFFFFFF) + slider0 = m5ui.M5Slider( + x=60, + y=110, + w=200, + h=19, + mode=lv.slider.MODE.NORMAL, + min_value=0, + max_value=100, + value=25, + bg_c=0x2193F3, + color=0x2193F3, + parent=page0, + ) + label0 = m5ui.M5Label( + "25", + x=151, + y=142, + text_c=0x000000, + bg_c=0xFFFFFF, + bg_opa=0, + font=lv.font_montserrat_14, + parent=page0, + ) + + slider0.add_event_cb(slider0_event_handler, lv.EVENT.ALL, None) + + page0.screen_load() + + +def loop(): + global page0, slider0, label0 + M5.update() + + +if __name__ == "__main__": + try: + setup() + while True: + loop() + except (Exception, KeyboardInterrupt) as e: + try: + m5ui.deinit() + from utility import print_error_msg + + print_error_msg(e) + except ImportError: + print("please update to latest firmware") diff --git a/m5stack/libs/m5ui/__init__.py b/m5stack/libs/m5ui/__init__.py index 38e52e53..69fa8716 100644 --- a/m5stack/libs/m5ui/__init__.py +++ b/m5stack/libs/m5ui/__init__.py @@ -14,6 +14,7 @@ "M5Label": "label", "M5Line": "line", "M5Page": "page", + "M5Slider": "slider", "M5Switch": "switch", } diff --git a/m5stack/libs/m5ui/manifest.py b/m5stack/libs/m5ui/manifest.py index e1a618fc..369571b7 100644 --- a/m5stack/libs/m5ui/manifest.py +++ b/m5stack/libs/m5ui/manifest.py @@ -17,6 +17,7 @@ "line.py", "page.py", "port.py", + "slider.py", "switch.py", ), base_path="..", diff --git a/m5stack/libs/m5ui/slider.py b/m5stack/libs/m5ui/slider.py new file mode 100644 index 00000000..96a33dd6 --- /dev/null +++ b/m5stack/libs/m5ui/slider.py @@ -0,0 +1,58 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +from .base import M5Base +import lvgl as lv + + +class M5Slider(lv.slider): + + """Create a slider widget. + + :param x: The x position of the slider. + :param y: The y position of the slider. + :param w: The width of the slider. + :param h: The height of the slider. + :param mode: only `lv.slider.MODE.NORMAL` is supported. + :param min_value: The minimum value of the slider. + :param max_value: The maximum value of the slider. + :param value: The initial value of the slider. + :param bg_c: The background color of the slider. + :param color: The color of the slider indicator. + :param parent: The parent object of the slider. If not specified, it will be set to the active screen. + """ + + def __init__( + self, + x=0, + y=0, + w=100, + h=20, + mode=lv.slider.MODE.NORMAL, + min_value=0, + max_value=100, + value=25, + bg_c=0x2193F3, + color=0x2193F3, + parent=None, + ): + if parent is None: + parent = lv.screen_active() + super().__init__(parent) + + self.set_size(w, h) + self.set_pos(x, y) + self.set_mode(mode) + self.set_range(min_value, max_value) + self.set_value(value, False) + self.set_bg_color(bg_c, 51, lv.PART.MAIN | lv.STATE.DEFAULT) + self.set_bg_color(color, lv.OPA.COVER, lv.PART.INDICATOR | lv.STATE.DEFAULT) + + def __getattr__(self, name): + if hasattr(M5Base, name): + method = getattr(M5Base, name) + bound_method = lambda *args, **kwargs: method(self, *args, **kwargs) + setattr(self, name, bound_method) + return bound_method + raise AttributeError(f"'{self.__class__.__name__}' object has no attribute '{name}'") From 29909cc1686c2b559041ca86046fbeebbd9c8cf1 Mon Sep 17 00:00:00 2001 From: lbuque Date: Wed, 9 Jul 2025 14:41:35 +0800 Subject: [PATCH 183/322] libs/m5ui: Add m5ui.M5TextArea support. Signed-off-by: lbuque --- docs/en/m5ui/index.rst | 1 + docs/en/m5ui/textarea.rst | 602 ++++++++++++++ docs/en/refs/m5ui.textarea.ref | 36 + .../zh_CN/LC_MESSAGES/m5ui/textarea.po | 787 ++++++++++++++++++ .../cores3_textarea_basic_example.m5f2 | 1 + .../textarea/cores3_textarea_basic_example.py | 92 ++ m5stack/libs/m5ui/__init__.py | 1 + m5stack/libs/m5ui/manifest.py | 1 + m5stack/libs/m5ui/textarea.py | 58 ++ 9 files changed, 1579 insertions(+) create mode 100644 docs/en/m5ui/textarea.rst create mode 100644 docs/en/refs/m5ui.textarea.ref create mode 100644 docs/locales/zh_CN/LC_MESSAGES/m5ui/textarea.po create mode 100644 examples/m5ui/textarea/cores3_textarea_basic_example.m5f2 create mode 100644 examples/m5ui/textarea/cores3_textarea_basic_example.py create mode 100644 m5stack/libs/m5ui/textarea.py diff --git a/docs/en/m5ui/index.rst b/docs/en/m5ui/index.rst index e9c79b80..9f578d9a 100644 --- a/docs/en/m5ui/index.rst +++ b/docs/en/m5ui/index.rst @@ -43,3 +43,4 @@ Classes line.rst slider.rst switch.rst + textarea.rst diff --git a/docs/en/m5ui/textarea.rst b/docs/en/m5ui/textarea.rst new file mode 100644 index 00000000..d17b55db --- /dev/null +++ b/docs/en/m5ui/textarea.rst @@ -0,0 +1,602 @@ +.. currentmodule:: m5ui + +M5TextArea +========== + +.. include:: ../refs/m5ui.textarea.ref + +M5TextArea is a widget that can be used to create text input areas in the user interface. It allows users to input and edit multi-line text with support for placeholders, scrolling, and various styling options. + + +UiFlow2 Example +--------------- + +basic textarea +^^^^^^^^^^^^^^ + +Open the |cores3_textarea_basic_example.m5f2| project in UiFlow2. + +This example demonstrates how to add text content to a text box and clear the content of the text box using a button. + +UiFlow2 Code Block: + + |cores3_textarea_basic_example.png| + +Example output: + + None + + +MicroPython Example +------------------- + +basic textarea +^^^^^^^^^^^^^^ + +This example demonstrates how to add text content to a text box and clear the content of the text box using a button. + +MicroPython Code Block: + + .. literalinclude:: ../../../examples/m5ui/textarea/cores3_textarea_basic_example.py + :language: python + :linenos: + +Example output: + + None + + +**API** +------- + +M5TextArea +^^^^^^^^^^ + +.. autoclass:: m5ui.textarea.M5TextArea + :members: + + .. py:method:: set_flag(flag, value) + + Set a flag on the object. If ``value`` is True, the flag is added; if False, the flag is removed. + + :param int flag: The flag to set. + :param bool value: If True, the flag is added; if False, the flag is removed. + :return: None + + UiFlow2 Code Block: + + |set_flag.png| + + MicroPython Code Block: + + .. code-block:: python + + textarea_0.set_flag(lv.obj.FLAG.HIDDEN, True) + + .. py:method:: toggle_flag(flag) + + Toggle a flag on the object. If the flag is set, it is removed; if not set, it is added. + + :param int flag: The flag to toggle. + :return: None + + UiFlow2 Code Block: + + |toggle_flag.png| + + MicroPython Code Block: + + .. code-block:: python + + textarea_0.toggle_flag(lv.obj.FLAG.HIDDEN) + + .. py:method:: set_state(state, value) + + Set the state of the textarea. If ``value`` is True, the state is set; if False, the state is unset. + + :param int state: The state to set. + :param bool value: If True, the state is set; if False, the state is unset. + :return: None + + UiFlow2 Code Block: + + |set_state.png| + + MicroPython Code Block: + + .. code-block:: python + + textarea_0.set_state(lv.STATE.PRESSED, True) + + .. py:method:: toggle_state(state) + + Toggle the state of the textarea. If the state is set, it is unset; if not set, it is set. + + :param int state: The state to toggle. + :return: None + + UiFlow2 Code Block: + + |toggle_state.png| + + MicroPython Code Block: + + .. code-block:: python + + textarea_0.toggle_state(lv.STATE.PRESSED) + + .. py:method:: add_event_cb(handler, event, user_data) + + Add an event callback to the slider. The callback will be called when the specified event occurs. + + :param function handler: The callback function to call. + :param int event: The event to listen for. + :param Any user_data: Optional user data to pass to the callback. + :return: None + + UiFlow2 Code Block: + + |event.png| + + MicroPython Code Block: + + .. code-block:: python + + def textarea_0_ready_event(event_struct): + global page0, button0 + print("released") + + def textarea_0_value_changed_event(event_struct): + global page0, button0 + print("value changed") + + def textarea_0_focused_event(event_struct): + global page0, button0 + print("focused") + + def textarea_0_defocused_event(event_struct): + global page0, button0 + print("focused") + + def textarea_0_event_handler(event_struct): + event = event_struct.code + if event == lv.EVENT.VALUE_CHANGED and True: + textarea_0_value_changed_event(event_struct) + elif event == lv.EVENT.READY and True: + textarea_0_ready_event(event_struct) # 单行模式下才会触发 + elif event == lv.EVENT.FOCUSED: + textarea_0_focused_event(event_struct) + elif event == lv.EVENT.DEFOCUSED: + textarea_0_defocused_event(event_struct) + return + + textarea_0.add_event_cb(textarea_0_event_handler, lv.EVENT.ALL, None) + + .. py:method:: set_bg_color(color, opa, part) + + Set the background color of the textarea. + + :param int color: The color to set. + :param int opa: The opacity of the color. + :param int part: The part of the object to apply the style to (e.g., lv.PART.MAIN). + :return: None + + UiFlow2 Code Block: + + |set_bg_color.png| + + MicroPython Code Block: + + .. code-block:: python + + textarea_0.set_bg_color(lv.color_hex(0xFFFFFF), 255, lv.PART.MAIN | lv.STATE.DEFAULT) + + .. py:method:: set_style_radius(radius, part) + + Set the corner radius of the slider components. + + :param int radius: The radius to set. + :param int part: The part of the object to apply the style to (e.g., lv.PART.MAIN). + :return: None + + UiFlow2 Code Block: + + |set_style_radius.png| + + MicroPython Code Block: + + .. code-block:: python + + slider_0.set_style_radius(10, lv.PART.MAIN | lv.STATE.DEFAULT) + + .. py:method:: set_border_color(color, opa, part) + + Set the border color of the textarea. + + :param int color: The color to set. + :param int opa: The opacity of the color. + :param int part: The part of the object to apply the style to (e.g., lv.PART.MAIN). + :return: None + + UiFlow2 Code Block: + + |set_border_color.png| + + MicroPython Code Block: + + .. code-block:: python + + textarea_0.set_border_color(lv.color_hex(0xE0E0E0), 255, lv.PART.MAIN | lv.STATE.DEFAULT) + + .. py:method:: set_style_border_width(width, part) + + Set the border width of the textarea. + + :param int width: The width to set. + :param int part: The part of the object to apply the style to (e.g., lv.PART.MAIN). + :return: None + + UiFlow2 Code Block: + + |set_style_border_width.png| + + MicroPython Code Block: + + .. code-block:: python + + textarea_0.set_style_border_width(2, lv.PART.MAIN | lv.STATE.DEFAULT) + + .. py:method:: set_placeholder_text(text) + + Set the placeholder text that appears when the textarea is empty. + + :param str text: The placeholder text to set. + :return: None + + UiFlow2 Code Block: + + |set_placeholder_text.png| + + MicroPython Code Block: + + .. code-block:: python + + textarea_0.set_placeholder_text("Enter text here...") + + .. py:method:: set_text_color(color, opa, part) + + Set the color of the text. + + :param int color: The color to set. + :param int opa: The opacity of the color. + :param int part: The part of the object to apply the style to (e.g., lv.PART.MAIN). + :return: None + + UiFlow2 Code Block: + + |set_text_color.png| + + MicroPython Code Block: + + .. code-block:: python + + textarea_0.set_text_color(lv.color_hex(0x000000), 255, lv.PART.MAIN | lv.STATE.DEFAULT) + + .. py:method:: set_style_text_font(font, part) + + Set the font of the textarea text. + + :param lv.font_t font: The font to set. + :param int part: The part of the object to apply the style to (e.g., lv.PART.MAIN). + :return: None + + UiFlow2 Code Block: + + |set_placeholder_font.png| + + |set_text_font.png| + + MicroPython Code Block: + + .. code-block:: python + + textarea_0.set_style_text_font(lv.font_montserrat_14, lv.PART.MAIN | lv.STATE.DEFAULT) + + + .. py:method:: set_style_text_align(align, part) + + Set the text alignment of the textarea. + + :param int align: The alignment to set (e.g., lv.TEXT_ALIGN.LEFT, lv.TEXT_ALIGN.CENTER). + :param int part: The part of the object to apply the style to (e.g., lv.PART.MAIN). + :return: None + + UiFlow2 Code Block: + + |set_placeholder_align.png| + + |set_text_align.png| + + MicroPython Code Block: + + .. code-block:: python + + textarea_0.set_style_text_align(lv.TEXT_ALIGN.LEFT, lv.PART.MAIN | lv.STATE.DEFAULT) + + .. py:method:: set_text(text) + + Set the text content of the textarea. + + :param str text: The text to set. + :return: None + + UiFlow2 Code Block: + + |set_text.png| + + MicroPython Code Block: + + .. code-block:: python + + textarea_0.set_text("Hello World") + textarea_0.set_text("") # Clear the text content + + .. py:method:: get_text() + + Get the current text content of the textarea. + + :return: The current text content. + :rtype: str + + UiFlow2 Code Block: + + |get_text.png| + + MicroPython Code Block: + + .. code-block:: python + + text = textarea_0.get_text() + + .. py:method:: add_text(text) + + Add text to the current content of the textarea. + + :param str text: The text to add. + :return: None + + UiFlow2 Code Block: + + |add_text.png| + + MicroPython Code Block: + + .. code-block:: python + + textarea_0.add_text(" Additional text") + + .. py:method:: set_max_length(length) + + Set the maximum length of text that can be entered in the textarea. + + :param int length: The maximum length of text. + :return: None + + UiFlow2 Code Block: + + |set_max_length.png| + + MicroPython Code Block: + + .. code-block:: python + + textarea_0.set_max_length(256) + + .. py:method:: set_password_mode(en) + + Set whether the textarea should be in password mode (i.e., characters are hidden). + + :param bool en: True to enable password mode, False to disable. + :return: None + + UiFlow2 Code Block: + + |set_password_mode.png| + + MicroPython Code Block: + + .. code-block:: python + + textarea_0.set_password_mode(True) + + .. py:method:: set_one_line(text) + + Set whether the textarea should be single line or multi-line. + + :param bool text: True for single line, False for multi-line. + :return: None + + UiFlow2 Code Block: + + |set_one_line.png| + + MicroPython Code Block: + + .. code-block:: python + + textarea_0.set_one_line(True) + + .. py:method:: set_accepted_chars(chars) + + Set the characters that are accepted in the textarea. Only these characters can be entered. + + :param str chars: The string of accepted characters. + :return: None + + UiFlow2 Code Block: + + |set_accepted_chars.png| + + MicroPython Code Block: + + .. code-block:: python + + textarea_0.set_accepted_chars("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789") + + .. py:method:: set_pos(x, y) + + Set the position of the textarea. + + :param int x: The x-coordinate of the textarea. + :param int y: The y-coordinate of the textarea. + :return: None + + UiFlow2 Code Block: + + |set_pos.png| + + MicroPython Code Block: + + .. code-block:: python + + textarea_0.set_pos(100, 100) + + .. py:method:: set_x(x) + + Set the x-coordinate of the textarea. + + :param int x: The x-coordinate of the textarea. + :return: None + + UiFlow2 Code Block: + + |set_x.png| + + MicroPython Code Block: + + .. code-block:: python + + textarea_0.set_x(100) + + .. py:method:: set_y(y) + + Set the y-coordinate of the textarea. + + :param int y: The y-coordinate of the textarea. + :return: None + + UiFlow2 Code Block: + + |set_y.png| + + MicroPython Code Block: + + .. code-block:: python + + textarea_0.set_y(100) + + .. py:method:: align_to(obj, align, x, y) + + Align the textarea to another object. + + :param lv.obj obj: The object to align to. + :param int align: The alignment type. + :param int x: The x-offset from the aligned object. + :param int y: The y-offset from the aligned object. + :return: None + + UiFlow2 Code Block: + + |align_to.png| + + MicroPython Code Block: + + .. code-block:: python + + textarea_0.align_to(page_0, lv.ALIGN.CENTER, 0, 0) + + .. py:method:: set_size(width, height) + + Set the size of the textarea. + + :param int width: The width of the textarea. + :param int height: The height of the textarea. + :return: None + + UiFlow2 Code Block: + + |set_size.png| + + MicroPython Code Block: + + .. code-block:: python + + textarea_0.set_size(200, 100) + + .. py:method:: set_width(width) + + Set the width of the textarea. + + :param int width: The width of the textarea. + :return: None + + UiFlow2 Code Block: + + |set_width.png| + + MicroPython Code Block: + + .. code-block:: python + + textarea_0.set_width(200) + + .. py:method:: set_height(height) + + Set the height of the textarea. + + :param int height: The height of the textarea. + :return: None + + UiFlow2 Code Block: + + |set_height.png| + + MicroPython Code Block: + + .. code-block:: python + + textarea_0.set_height(100) + + .. py:method:: get_width() + + Get the width of the textarea. + + :return: The width of the textarea. + :rtype: int + + UiFlow2 Code Block: + + |get_width.png| + + MicroPython Code Block: + + .. code-block:: python + + width = textarea_0.get_width() + + .. py:method:: get_height() + + Get the height of the textarea. + + :return: The height of the textarea. + :rtype: int + + UiFlow2 Code Block: + + |get_height.png| + + MicroPython Code Block: + + .. code-block:: python + + height = textarea_0.get_height() diff --git a/docs/en/refs/m5ui.textarea.ref b/docs/en/refs/m5ui.textarea.ref new file mode 100644 index 00000000..663c4a2e --- /dev/null +++ b/docs/en/refs/m5ui.textarea.ref @@ -0,0 +1,36 @@ +.. |cores3_textarea_basic_example.m5f2| replace:: :download:`cores3_textarea_basic_example.m5f2 <../../../examples/m5ui/textarea/cores3_textarea_basic_example.m5f2>` + +.. |cores3_textarea_basic_example.png| image:: https://static-cdn.m5stack.com/resource/docs/products/core/CoreS3/software/uiflow2/api/m5ui/textarea/cores3_textarea_basic_example.png +.. |add_text.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/textarea/add_text.png +.. |align_to.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/textarea/align_to.png +.. |clear_text.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/textarea/clear_text.png +.. |event.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/textarea/event.png +.. |get_height.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/textarea/get_height.png +.. |get_text.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/textarea/get_text.png +.. |get_width.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/textarea/get_width.png +.. |set_accepted_chars.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/textarea/set_accepted_chars.png +.. |set_bg_color.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/textarea/set_bg_color.png +.. |set_border_color.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/textarea/set_border_color.png +.. |set_flag.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/textarea/set_flag.png +.. |set_height.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/textarea/set_height.png +.. |set_max_length.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/textarea/set_max_length.png +.. |set_one_line.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/textarea/set_one_line.png +.. |set_password_mode.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/textarea/set_password_mode.png +.. |set_placeholder_align.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/textarea/set_placeholder_align.png +.. |set_placeholder_color.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/textarea/set_placeholder_color.png +.. |set_placeholder_font.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/textarea/set_placeholder_font.png +.. |set_placeholder_text.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/textarea/set_placeholder_text.png +.. |set_pos.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/textarea/set_pos.png +.. |set_size.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/textarea/set_size.png +.. |set_state.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/textarea/set_state.png +.. |set_style_border_width.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/textarea/set_style_border_width.png +.. |set_style_radius.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/textarea/set_style_radius.png +.. |set_text.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/textarea/set_text.png +.. |set_text_align.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/textarea/set_text_align.png +.. |set_text_color.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/textarea/set_text_color.png +.. |set_text_font.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/textarea/set_text_font.png +.. |set_width.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/textarea/set_width.png +.. |set_x.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/textarea/set_x.png +.. |set_y.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/textarea/set_y.png +.. |toggle_flag.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/textarea/toggle_flag.png +.. |toggle_state.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/textarea/toggle_state.png diff --git a/docs/locales/zh_CN/LC_MESSAGES/m5ui/textarea.po b/docs/locales/zh_CN/LC_MESSAGES/m5ui/textarea.po new file mode 100644 index 00000000..cc546562 --- /dev/null +++ b/docs/locales/zh_CN/LC_MESSAGES/m5ui/textarea.po @@ -0,0 +1,787 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2016 - 2025 M5Stack Technology Co., Ltd +# This file is distributed under the same license as the UIFlow2 Programming +# Guide package. +# FIRST AUTHOR , 2025. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: UIFlow2 Programming Guide \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-07-27 16:42+0800\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: zh_CN\n" +"Language-Team: zh_CN \n" +"Plural-Forms: nplurals=1; plural=0;\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.17.0\n" + +#: ../../en/m5ui/textarea.rst:4 ../../en/m5ui/textarea.rst:53 +#: 7380ff9976a540fcaf17d4c27c3c2a05 9b8f71d6573846afb89b2d939b2fe19d +msgid "M5TextArea" +msgstr "" + +#: ../../en/m5ui/textarea.rst:8 9b051f6a671649e3b2f45d7b45f79414 +msgid "" +"M5TextArea is a widget that can be used to create text input areas in the" +" user interface. It allows users to input and edit multi-line text with " +"support for placeholders, scrolling, and various styling options." +msgstr "M5TextArea是一个可以在用户界面中创建文本输入区域的控件。它允许用户输入和编辑多行文本,支持占位符、滚动和各种样式选项。" + +#: ../../en/m5ui/textarea.rst:12 dfdeab4daaaa49c3b3f72a5c82017a21 +msgid "UiFlow2 Example" +msgstr "UiFlow2 应用示例" + +#: ../../en/m5ui/textarea.rst:15 ../../en/m5ui/textarea.rst:34 +#: 4764b19f5dc549eea34131ccc1c063a7 afa2d5d5481a42e8be1024b1eaedc76f +msgid "basic textarea" +msgstr "" + +#: ../../en/m5ui/textarea.rst:17 38f25dfcdd3b44838b608a6b2c5d71a3 +msgid "Open the |cores3_textarea_basic_example.m5f2| project in UiFlow2." +msgstr "在UiFlow2中打开|cores3_textarea_basic_example.m5f2|项目。" + +#: ../../en/m5ui/textarea.rst:19 ../../en/m5ui/textarea.rst:36 +#: 4bc23b841c53456f8dd399be19548e90 f217f699fae5436386aa6f12d963ff2a +msgid "" +"This example demonstrates how to add text content to a text box and clear" +" the content of the text box using a button." +msgstr "此示例演示如何向文本框添加文本内容以及使用按钮清除文本框的内容。" + +#: ../../en/m5ui/textarea.rst:21 ../../en/m5ui/textarea.rst:66 +#: ../../en/m5ui/textarea.rst:83 ../../en/m5ui/textarea.rst:101 +#: ../../en/m5ui/textarea.rst:118 ../../en/m5ui/textarea.rst:137 +#: ../../en/m5ui/textarea.rst:184 ../../en/m5ui/textarea.rst:202 +#: ../../en/m5ui/textarea.rst:221 ../../en/m5ui/textarea.rst:239 +#: ../../en/m5ui/textarea.rst:256 ../../en/m5ui/textarea.rst:275 +#: ../../en/m5ui/textarea.rst:293 ../../en/m5ui/textarea.rst:314 +#: ../../en/m5ui/textarea.rst:333 ../../en/m5ui/textarea.rst:351 +#: ../../en/m5ui/textarea.rst:368 ../../en/m5ui/textarea.rst:385 +#: ../../en/m5ui/textarea.rst:402 ../../en/m5ui/textarea.rst:419 +#: ../../en/m5ui/textarea.rst:436 ../../en/m5ui/textarea.rst:454 +#: ../../en/m5ui/textarea.rst:471 ../../en/m5ui/textarea.rst:488 +#: ../../en/m5ui/textarea.rst:508 ../../en/m5ui/textarea.rst:526 +#: ../../en/m5ui/textarea.rst:543 ../../en/m5ui/textarea.rst:560 +#: ../../en/m5ui/textarea.rst:577 ../../en/m5ui/textarea.rst:594 +#: 02736aa12554469d8deb6b4b358373ab 0af4d72c5025422db041226838f60dc5 +#: 13b2500144474197b6c3a01ea9e90862 2030ce385c284dcfad0f424f28011dd7 +#: 24cc17d94dfc4d699828be9109a456af 2532e7c100854003b70bc414f3520f4a +#: 337d72a48a1f4f518691682b3ff4ad32 4666211fd9fc46d1b74124f21b33c46e +#: 4767a0256c614f56b5c8100781beb4a0 5d372778dedb4d0189e54388862e3537 +#: 5f941679578c42efb753c53e9cb683ce 632733dff0774b9ba944353d28571232 +#: 650ed94e0d124d46918860e12ebf1342 66df5e1fb31f4bc08c637bad90dc0b21 +#: 6852dd0b0ba145a4bb6e71313c8fb3c9 7466c3579fe74c86bae708f7295fadcd +#: 762edb9edf21420481f7376c2c4c145e 77cd6fe3a47d497ea5a406464dccfd49 +#: 7bc96bc51c044b13ba6102caf3c759c4 825bbb9d7f1a41b687d6fe0ca46ec7d9 +#: 89440c37f56540deb63640278d8e90f2 8ad6942d4d3d43eda25b198f86c6e481 +#: 990a1b2bef514451a6c93ad9bf97bade 9c8ba628351b475c9532d3cdb2ff7564 +#: a89bae180aaf442ca0d8b40df011663a a9644c56d52b4c11811799001e2da82b +#: c6b449442d584c6f9ba197e23c5a1514 d6427cc7bc574fb9a7e29ab67494dad3 +#: d957a03039a844b5804e10992418d35b e03a9748ddc74920911d1e501a4b9623 +msgid "UiFlow2 Code Block:" +msgstr "UiFlow2 代码块:" + +#: ../../en/m5ui/textarea.rst:23 17b3ade14bbc435987f3636cc659bcde +msgid "|cores3_textarea_basic_example.png|" +msgstr "" + +#: ../../en/refs/m5ui.textarea.ref:3 fc9ed23a2de240c1a003d82ce9fd8fa4 +msgid "cores3_textarea_basic_example.png" +msgstr "" + +#: ../../en/m5ui/textarea.rst:25 ../../en/m5ui/textarea.rst:44 +#: 92d573498e7d40988c9ca26b54614ab5 dc653c35e263495a82606e5eca7894f7 +msgid "Example output:" +msgstr "示例输出:" + +#: ../../en/m5ui/textarea.rst:27 ../../en/m5ui/textarea.rst:46 +#: ../../en/m5ui/textarea.rst:64 ../../en/m5ui/textarea.rst:81 +#: ../../en/m5ui/textarea.rst:99 ../../en/m5ui/textarea.rst:116 +#: ../../en/m5ui/textarea.rst:135 ../../en/m5ui/textarea.rst:182 +#: ../../en/m5ui/textarea.rst:200 ../../en/m5ui/textarea.rst:219 +#: ../../en/m5ui/textarea.rst:237 ../../en/m5ui/textarea.rst:254 +#: ../../en/m5ui/textarea.rst:273 ../../en/m5ui/textarea.rst:291 +#: ../../en/m5ui/textarea.rst:312 ../../en/m5ui/textarea.rst:331 +#: ../../en/m5ui/textarea.rst:366 ../../en/m5ui/textarea.rst:383 +#: ../../en/m5ui/textarea.rst:400 ../../en/m5ui/textarea.rst:417 +#: ../../en/m5ui/textarea.rst:434 ../../en/m5ui/textarea.rst:452 +#: ../../en/m5ui/textarea.rst:469 ../../en/m5ui/textarea.rst:486 +#: ../../en/m5ui/textarea.rst:506 ../../en/m5ui/textarea.rst:524 +#: ../../en/m5ui/textarea.rst:541 ../../en/m5ui/textarea.rst:558 +#: 0563769c8b964c70b162a565ad15acf8 08874644aae943c3a969456d37160dbd +#: 0c1551b5278341888a299479208c967a 14109a9561804d85ade23bcdf0e3bdea +#: 2962f36df32b470d9c2bc1d0a70d7631 3a61a6f2e8ce4740bee4da236ecc2302 +#: 41e8afa587054f438d5c1df0978e61eb 43041742fff5437ba38ca7cb3ed67fe1 +#: 4c5295382ee24710936afc5234c66d12 5f990c777f0f47c0928c94ab0cb2d5d3 +#: 6c1410242a2245538ab71b87026f6d41 77fbda8de6fe419380e733d753ce933a +#: 78d74c8062074efab39aee6d538c5b80 89a0bb303f1546c2a6d4026e188735a1 +#: 95b4397bc5f54f07b5ff11c32946c2d0 9fe574a3dab749ff8ea2ca060fe622d9 +#: a1205c9095da4ae78ee19e080e0234ae c6fa1684f2ce4cd19e677f4ec70af3bf +#: d4a78c6455764bb386d5816cc8b19e7b d68eb214f958412bab45c8105aa0c839 +#: d6f8de7a913c4619b34a07ac9008cd1e d93950eb637745698944ab03d7460f44 +#: d96b57d686ac45ef8a7865829b5a04ef dcaafe7ea46540b7bd1cef6b814e8034 +#: df8e7267bde04458af352964a3ff7fb5 f03866ece6a344e69ea5470c22fc38b8 +#: f23ac21d562b48828862b2acbe66508d fb3184ae6a1b4d27a9200b92571be564 +msgid "None" +msgstr "" + +#: ../../en/m5ui/textarea.rst:31 537aabe054dc47dc978f67118fc35b88 +msgid "MicroPython Example" +msgstr "MicroPython 应用示例" + +#: ../../en/m5ui/textarea.rst:38 ../../en/m5ui/textarea.rst:70 +#: ../../en/m5ui/textarea.rst:87 ../../en/m5ui/textarea.rst:105 +#: ../../en/m5ui/textarea.rst:122 ../../en/m5ui/textarea.rst:141 +#: ../../en/m5ui/textarea.rst:188 ../../en/m5ui/textarea.rst:206 +#: ../../en/m5ui/textarea.rst:225 ../../en/m5ui/textarea.rst:243 +#: ../../en/m5ui/textarea.rst:260 ../../en/m5ui/textarea.rst:279 +#: ../../en/m5ui/textarea.rst:299 ../../en/m5ui/textarea.rst:320 +#: ../../en/m5ui/textarea.rst:337 ../../en/m5ui/textarea.rst:355 +#: ../../en/m5ui/textarea.rst:372 ../../en/m5ui/textarea.rst:389 +#: ../../en/m5ui/textarea.rst:406 ../../en/m5ui/textarea.rst:423 +#: ../../en/m5ui/textarea.rst:440 ../../en/m5ui/textarea.rst:458 +#: ../../en/m5ui/textarea.rst:475 ../../en/m5ui/textarea.rst:492 +#: ../../en/m5ui/textarea.rst:512 ../../en/m5ui/textarea.rst:530 +#: ../../en/m5ui/textarea.rst:547 ../../en/m5ui/textarea.rst:564 +#: ../../en/m5ui/textarea.rst:581 ../../en/m5ui/textarea.rst:598 +#: 1291a40bd1d14107a5228825499f9a28 13e79ed285d34076a57bf4086d9fe57e +#: 1d5b5a756d5649c6bb49c96c4923ed13 22b6541af09848caa97f86e1388683bf +#: 259efee647a3485583bcf1c1a21fa63b 3b29cc9872974375965d5282584a39f6 +#: 3c6650f96cf4492f94fd889e352e1aff 438bf3709bfe4482989060b2cf1aebf1 +#: 4549e225d1374004b5f970267c631b4c 49adeac5cf3f47b8b137290125b3273a +#: 4bed6860ee76406b98c69c0bef297438 4e798aa3e3ec4bc491b62bf5f37086ac +#: 579eeabd6658498d8c776797e6b4232e 6a0162b046f94e1185c2d69bde1776c0 +#: 6a6570bb7b754ae98340af7a61d73e1a 71b35c42c3b74fb88b24d75c38fd1f81 +#: 7927379218064b6ab6496c2552f4bfb6 891143f9c7fe449b87ddc12ed30a9cfb +#: 8ae152bcf6d44cc28dc78977e1782745 915ccb67a5794b95a8931220a3c01b29 +#: 9a6922d6508e42f4b5be206c0b3ac50d adf270b52d4e45beb8d25151a5e144d5 +#: ba22cc663cf841dd86a041197b22ccdc d33094e7ba354dce8d17cef9846a133d +#: ea3ccabfc32941ec9551865915f1cb75 eaa6737a4c8047ce853de3cb88215f16 +#: f0a3278ff7a248d382c2de7101fe1eab f3c0345899f345e3a9d0e31ad7a4754f +#: f40ff3683c5942179eed5e05bca35d49 f69bf302f1a644c586cd92a5c5e12c5b +msgid "MicroPython Code Block:" +msgstr "MicroPython 代码块:" + +#: ../../en/m5ui/textarea.rst:50 9a2c7ef251574fd588f4f4d2bc528eda +msgid "**API**" +msgstr "API参考" + +#: a5b97698450d4f6d8a7a3faa6ba5a672 m5ui.textarea.M5TextArea:1 of +msgid "Bases: :py:class:`~lvgl.textarea`" +msgstr "" + +#: 7e67257605834333b4944c6d38f505a2 m5ui.textarea.M5TextArea:1 of +msgid "Create a text area widget." +msgstr "创建一个文本框控件。" + +#: ../../en/m5ui/textarea.rst 023ffb971d5e4172a8892271d6de71fc +#: 0526f19da6134efabb1b0b0871e56af8 05f1be04865b4125bbbc1b0a6902d9cc +#: 1223534d61454475935227c365959562 1430b0c30db0437c8b0e711f0fd067e4 +#: 1653b7006fc14fdcb57fb12f79dafe4c 20bf477b3c1c486b859dd49dc63cfbde +#: 26dad7089b304da9a3e0fe5a9b646bec 2d11698fe8a84edfb7d8848efeb514b3 +#: 301803dea7bd4aff95f8e8bfa6048fcf 32a6264dfb1c4317a53b5eb2ef911096 +#: 395e0146197b4325baaa7f67bdfd3cb1 4fde6c9b1f874f51bfeb6536cdeff123 +#: 5664e0b8f3d1442a86520e5363bcb087 566d13a1d08f40abaf3418bc410c84df +#: 6006a52570734037959b000d0b56ff16 77d4c5ba39404d2da9a51e0bb05522c9 +#: 8544dd2ef94542398e345492c1c4298f 87abe4e45c044628b95958bdb5eaccc3 +#: 9bd9aab061d44df0833446cc303767d3 a2b9c369bb8d4ea6b28cce2d49b53fb0 +#: a36befac42034cd086011490ba8eaaf2 aa47212dbf4945588c0502afdf3cacef +#: c0f95fc94539480aa2f0bbd9df5b11bf c88fbef5416642c194e8e5a063da93d5 +#: d2ce365c54614d3a9822156420db2136 fbf2076894a54721bd50df76439c4691 +msgid "Parameters" +msgstr "" + +#: 6ca4d36958ad45ccb9ea247fd097636e m5ui.textarea.M5TextArea:3 of +msgid "Initial text content of the text area." +msgstr "文本框的初始文本内容。" + +#: f5acfb4f7ec24d6bb6fa50b79b27360f m5ui.textarea.M5TextArea:4 of +msgid "Placeholder text when the text area is empty." +msgstr "文本框为空时的占位符文本。" + +#: bade8d93defa42af83f7186c83bbbff2 m5ui.textarea.M5TextArea:5 of +msgid "X position of the text area." +msgstr "文本框的X坐标位置。" + +#: 3d63217d360c48bab7d89d64e51bcaf0 m5ui.textarea.M5TextArea:6 of +msgid "Y position of the text area." +msgstr "文本框的Y坐标位置。" + +#: 9b2783dd145b426e956bee0ced946134 m5ui.textarea.M5TextArea:7 of +msgid "Width of the text area." +msgstr "文本框的宽度。" + +#: c96b3c29edca42d6ba5786dfbaca3a5c m5ui.textarea.M5TextArea:8 of +msgid "Height of the text area." +msgstr "文本框的高度。" + +#: 52359bd4167f42318794d00c00c736f5 m5ui.textarea.M5TextArea:9 of +msgid "Font used for the text." +msgstr "用于文本的字体。" + +#: dd1041090054409ca8b5a50b3cbcd131 m5ui.textarea.M5TextArea:10 of +msgid "Background color of the text area." +msgstr "文本框的背景颜色。" + +#: 443b0a759963406c94d299f249274577 m5ui.textarea.M5TextArea:11 of +msgid "Border color of the text area." +msgstr "文本框的边框颜色。" + +#: 42ef0b38b6c147d3b77353b45c058086 m5ui.textarea.M5TextArea:12 of +msgid "Text color of the text area." +msgstr "文本框的文本颜色。" + +#: 5a3cc70937a44b6691ad28ae7f73585b m5ui.textarea.M5TextArea:13 of +msgid "" +"Parent object of the text area. If not specified, it will be set to the " +"active screen." +msgstr "文本框的父对象。如果未指定,将设置为活动屏幕。" + +#: ../../en/m5ui/textarea.rst:60 c3ae77941f9b4470b6ac299eeb0ede31 +msgid "" +"Set a flag on the object. If ``value`` is True, the flag is added; if " +"False, the flag is removed." +msgstr "在对象上设置标志。如果 ``value`` 为True,则添加标志;如果为False,则移除标志。" + +#: ../../en/m5ui/textarea.rst:62 32e37b943f6c4d15a45688648defed3c +msgid "The flag to set." +msgstr "要设置的标志。" + +#: ../../en/m5ui/textarea.rst:63 19a11523b5bf46a9b60ad72ad1130c19 +msgid "If True, the flag is added; if False, the flag is removed." +msgstr "如果为True,则添加标志;如果为False,则移除标志。" + +#: ../../en/m5ui/textarea.rst 04c4b1e88314400baea6d28298bd217d +#: 0e5fadc7c4b74c108c523617709b6843 32c029fe5d32408c97f50bd98b05559d +#: 3360375a97b343ec94f9c540e77a2cea 33b19d16cc49489d8f46a3a42dc9bb1c +#: 37772ff140b34bfcbb42136668f9d53c 38242bd8fa1542c2bab99db4af1a7988 +#: 3a8005f8557346f78c1cfef12ab3cf09 3b6ff2ff7dc243de8339913b926870e6 +#: 3bf7c12ebefe4de18626ab223e658240 3c7c93fa08c049d4b16463b4e2911e11 +#: 45d822c8ccf1497ca6b55f2b70f11dc1 476689ae03a54751915b3035e6bae74f +#: 4780a2806c1c4c06a1c933b043584258 4914afd3d24e43e68889d8ef0f9cdf31 +#: 4ff38f56e2474ef493e7d3d7c3a124b5 68f125c4288941288264f66a59a1d8aa +#: a9adc63beea0469a89d879689cfe59b7 ae7931c18247439c906069491dd47911 +#: b2d51643772248f592dd22f748949318 b7f907fe54bd41da85398e45e5a1f767 +#: b902d736cc894b529e03d4ab4c0b4a10 d81362684a004a4ab295de15bbf8169b +#: e1b55382e1574d74949f5ae305965450 e964051f206245efbec5670ec05a4b74 +#: e9f1b8ef26b74da281c97b3b78475a18 f143651bfe494e71a67adc2e6b456c80 +#: fdfba699f9d9479983ea43a3291188a3 fe6b3b5949fa4152866fcc6038d72819 +msgid "Returns" +msgstr "" + +#: ../../en/m5ui/textarea.rst:68 0e0f6bad868d423dbc50642c75137c08 +msgid "|set_flag.png|" +msgstr "" + +#: ../../en/refs/m5ui.textarea.ref:14 eb91f1819fb74506821bad8039cdf424 +msgid "set_flag.png" +msgstr "" + +#: ../../en/m5ui/textarea.rst:78 543ff656431d48e998d3018b2cce505b +msgid "" +"Toggle a flag on the object. If the flag is set, it is removed; if not " +"set, it is added." +msgstr "切换对象上的标志。如果标志已设置,则移除它;如果未设置,则添加它。" + +#: ../../en/m5ui/textarea.rst:80 a11d77971d334ea487360d88540dd97d +msgid "The flag to toggle." +msgstr "要切换的标志。" + +#: ../../en/m5ui/textarea.rst:85 88fd306722a5495e8fa784a5e480e46c +msgid "|toggle_flag.png|" +msgstr "" + +#: ../../en/refs/m5ui.textarea.ref:35 9275b6288d1a45248cb79e5fafd013fd +msgid "toggle_flag.png" +msgstr "" + +#: ../../en/m5ui/textarea.rst:95 6d55eb8a95b34e718413bc9b4b7d11f3 +msgid "" +"Set the state of the textarea. If ``value`` is True, the state is set; if" +" False, the state is unset." +msgstr "设置文本框的状态。如果 ``value`` 为True,则设置状态;如果为False,则取消设置状态。" + +#: ../../en/m5ui/textarea.rst:97 a601f5d8df0d445687a358f4b700b07d +msgid "The state to set." +msgstr "要设置的状态。" + +#: ../../en/m5ui/textarea.rst:98 30b6e48479934bb8aeee0ca21b799e08 +msgid "If True, the state is set; if False, the state is unset." +msgstr "如果为True,则设置状态;如果为False,则取消设置状态。" + +#: ../../en/m5ui/textarea.rst:103 0b4c0a0788a44094962f49345bb0a1cf +msgid "|set_state.png|" +msgstr "" + +#: ../../en/refs/m5ui.textarea.ref:25 8eac1b4041064138ba5516fc88cdf40d +msgid "set_state.png" +msgstr "" + +#: ../../en/m5ui/textarea.rst:113 8efbf04dc06942edbd0f43fc15a3767c +msgid "" +"Toggle the state of the textarea. If the state is set, it is unset; if " +"not set, it is set." +msgstr "切换文本框的状态。如果状态已设置,则取消设置;如果未设置,则设置它。" + +#: ../../en/m5ui/textarea.rst:115 6859159b4db94f908b0a77b2635b9da1 +msgid "The state to toggle." +msgstr "要切换的状态。" + +#: ../../en/m5ui/textarea.rst:120 2ec7630acdad41f3bfaf2e81057c1013 +msgid "|toggle_state.png|" +msgstr "" + +#: ../../en/refs/m5ui.textarea.ref:36 95b6d16b28e24bedabf2505eec802bc8 +msgid "toggle_state.png" +msgstr "" + +#: ../../en/m5ui/textarea.rst:130 fe381bb2bbb44317a90bbba6194eb0a1 +msgid "" +"Add an event callback to the slider. The callback will be called when the" +" specified event occurs." +msgstr "为文本框添加事件回调。当指定的事件发生时,将调用回调函数。" + +#: ../../en/m5ui/textarea.rst:132 7287de61906643acbf0f469621265e2b +msgid "The callback function to call." +msgstr "要调用的回调函数。" + +#: ../../en/m5ui/textarea.rst:133 2fa2f09ee2ba4d3aa0d7c7af35bd345b +msgid "The event to listen for." +msgstr "要监听的事件。" + +#: ../../en/m5ui/textarea.rst:134 8c4a837951394812b789807a0cd23c65 +msgid "Optional user data to pass to the callback." +msgstr "传递给回调函数的可选用户数据。" + +#: ../../en/m5ui/textarea.rst:139 edf9d4c1331a4bb2b21093341d1f6928 +msgid "|event.png|" +msgstr "" + +#: ../../en/refs/m5ui.textarea.ref:7 2b69e998a061464aa77d4ac3acc4ced7 +msgid "event.png" +msgstr "" + +#: ../../en/m5ui/textarea.rst:177 45d84d5749a74b64815af6df7d45e9c3 +msgid "Set the background color of the textarea." +msgstr "设置文本框的背景颜色。" + +#: ../../en/m5ui/textarea.rst:179 ../../en/m5ui/textarea.rst:216 +#: ../../en/m5ui/textarea.rst:270 0eee01ed6f964c09abff102c01537683 +#: 80af6dd7eea94456a1f7dbe8ec02ff9e 98fa56505d62449890715a9b555a4287 +msgid "The color to set." +msgstr "要设置的颜色。" + +#: ../../en/m5ui/textarea.rst:180 ../../en/m5ui/textarea.rst:217 +#: ../../en/m5ui/textarea.rst:271 53601b79f26a4a379279720ca23d73a3 +#: bb642507024f43999e09ee6a4782ea77 eda0116e05674a7d835b431825e61587 +msgid "The opacity of the color." +msgstr "颜色的不透明度。" + +#: ../../en/m5ui/textarea.rst:181 ../../en/m5ui/textarea.rst:199 +#: ../../en/m5ui/textarea.rst:218 ../../en/m5ui/textarea.rst:236 +#: ../../en/m5ui/textarea.rst:272 ../../en/m5ui/textarea.rst:290 +#: ../../en/m5ui/textarea.rst:311 13afed9af23c4c5bb5f764fe171a3f7e +#: 196c5f6baa78404481b24ceded0c45ee 300c22ae809b4d0f83bc6d415bfd5f85 +#: 7381c2083dda4fadb72af722edce591d 8aed369311a34d9d917f4d5bd09cc3cc +#: df1b8f768eda41098bcbff1478794280 f6c920d88244494d84cad3de4c32f8d9 +msgid "The part of the object to apply the style to (e.g., lv.PART.MAIN)." +msgstr "要应用样式的对象部分(例如,lv.PART.MAIN)。" + +#: ../../en/m5ui/textarea.rst:186 bed32b395ab64ad291bd6c61d5b06538 +msgid "|set_bg_color.png|" +msgstr "" + +#: ../../en/refs/m5ui.textarea.ref:12 cfc161fcc5314b11b5d536155b7b5d14 +msgid "set_bg_color.png" +msgstr "" + +#: ../../en/m5ui/textarea.rst:196 17c41f5a87e04a218c1cd4ba340257bf +msgid "Set the corner radius of the slider components." +msgstr "设置滑块组件的角半径。" + +#: ../../en/m5ui/textarea.rst:198 06ecfdd9ac084a40b18f0c466a874bfe +msgid "The radius to set." +msgstr "要设置的半径。" + +#: ../../en/m5ui/textarea.rst:204 38e57414964a4d34b5ef2b6b8ddd1e95 +msgid "|set_style_radius.png|" +msgstr "" + +#: ../../en/refs/m5ui.textarea.ref:27 3e1abd26a075449dbd041ada9d3885bf +msgid "set_style_radius.png" +msgstr "" + +#: ../../en/m5ui/textarea.rst:214 2f6ddc5bd1e1427ba065d329eef8ee2d +msgid "Set the border color of the textarea." +msgstr "设置文本框的边框颜色。" + +#: ../../en/m5ui/textarea.rst:223 dc7cfba485dd4596922a7c3639249581 +msgid "|set_border_color.png|" +msgstr "" + +#: ../../en/refs/m5ui.textarea.ref:13 e5faa8d121c947cba96c86d07c78e168 +msgid "set_border_color.png" +msgstr "" + +#: ../../en/m5ui/textarea.rst:233 0a73dd7f153f459abebde4054df187fa +msgid "Set the border width of the textarea." +msgstr "设置文本框的边框宽度。" + +#: ../../en/m5ui/textarea.rst:235 d48b2c069fe24d0d80653e2faf74393d +msgid "The width to set." +msgstr "要设置的宽度。" + +#: ../../en/m5ui/textarea.rst:241 d96980bdb528473a8032f3e266359b3a +msgid "|set_style_border_width.png|" +msgstr "" + +#: ../../en/refs/m5ui.textarea.ref:26 8c41b112d77e4f478dd091240587c7ad +msgid "set_style_border_width.png" +msgstr "" + +#: ../../en/m5ui/textarea.rst:251 a7055977473a4f6b92c7402e9d8e0208 +msgid "Set the placeholder text that appears when the textarea is empty." +msgstr "设置当文本框为空时显示的占位符文本。" + +#: ../../en/m5ui/textarea.rst:253 fc8e3939824c4daab6a01411e843a415 +msgid "The placeholder text to set." +msgstr "要设置的占位符文本。" + +#: ../../en/m5ui/textarea.rst:258 fe212171751c4db0ae74d22776295938 +msgid "|set_placeholder_text.png|" +msgstr "" + +#: ../../en/refs/m5ui.textarea.ref:22 56c6f6520210423980c14692f33cc91c +msgid "set_placeholder_text.png" +msgstr "" + +#: ../../en/m5ui/textarea.rst:268 95976a5b8f794489b79487c85e8fd22c +msgid "Set the color of the text." +msgstr "" + +#: ../../en/m5ui/textarea.rst:277 5968289dcee24a04b343bf38a58534e9 +msgid "|set_text_color.png|" +msgstr "" + +#: ../../en/refs/m5ui.textarea.ref:30 604538ae4a514d8381c7a657a71333f2 +msgid "set_text_color.png" +msgstr "" + +#: ../../en/m5ui/textarea.rst:287 35835826521d49fa8aaa85e1c0d64c06 +msgid "Set the font of the textarea text." +msgstr "设置textarea文本的字体。" + +#: ../../en/m5ui/textarea.rst:289 4a6f18e4230849febfa63055d1d2d27c +msgid "The font to set." +msgstr "要设置的字体。" + +#: ../../en/m5ui/textarea.rst:295 2f3a7fb5419c43a6b62b5a9755347e79 +msgid "|set_placeholder_font.png|" +msgstr "" + +#: ../../en/refs/m5ui.textarea.ref:21 28c3a17e052849079ee809f0863ca4d4 +msgid "set_placeholder_font.png" +msgstr "" + +#: ../../en/m5ui/textarea.rst:297 9a3be70b70c7468986a23d2514caf992 +msgid "|set_text_font.png|" +msgstr "" + +#: ../../en/refs/m5ui.textarea.ref:31 4c60225cafd541f5958d73fb0887cb5b +msgid "set_text_font.png" +msgstr "" + +#: ../../en/m5ui/textarea.rst:308 346a598a987141618afd574e624b2733 +msgid "Set the text alignment of the textarea." +msgstr "" + +#: ../../en/m5ui/textarea.rst:310 404666e3b5b84f0dbf96cb215cbeddb7 +msgid "The alignment to set (e.g., lv.TEXT_ALIGN.LEFT, lv.TEXT_ALIGN.CENTER)." +msgstr "" + +#: ../../en/m5ui/textarea.rst:316 1372db4a715a4a6a9e7402a3627a710b +msgid "|set_placeholder_align.png|" +msgstr "" + +#: ../../en/refs/m5ui.textarea.ref:19 d9dc44c2c370470f96076772d63964bb +msgid "set_placeholder_align.png" +msgstr "" + +#: ../../en/m5ui/textarea.rst:318 f447b03d0e674a4d880184b810e73b43 +msgid "|set_text_align.png|" +msgstr "" + +#: ../../en/refs/m5ui.textarea.ref:29 cb7db211cba547a2832f95d97ffa84a1 +msgid "set_text_align.png" +msgstr "" + +#: ../../en/m5ui/textarea.rst:328 ac0e26256563489fbf5bdf1b9f02eb6f +msgid "Set the text content of the textarea." +msgstr "设置textarea的文本内容。" + +#: ../../en/m5ui/textarea.rst:330 3ce5b4f418f7463a81a8749508e214ce +msgid "The text to set." +msgstr "要设置的文本。" + +#: ../../en/m5ui/textarea.rst:335 a63a2ed8520240e89a014f6162d57883 +msgid "|set_text.png|" +msgstr "" + +#: ../../en/refs/m5ui.textarea.ref:28 4bd175ce46494ca9bad98e795d918b9e +msgid "set_text.png" +msgstr "" + +#: ../../en/m5ui/textarea.rst:346 045536dbb2bf4349a67221ff6c5e406f +msgid "Get the current text content of the textarea." +msgstr "获取textarea当前的文本内容。" + +#: ../../en/m5ui/textarea.rst:348 3c8d462a919741428207bdb308520cdf +msgid "The current text content." +msgstr "当前文本内容。" + +#: ../../en/m5ui/textarea.rst 2db2c1ff24db42299f76cf49c0c70538 +#: 5371c7681ab0432e8d5d1c72e8b87c49 ae50d411e14f445db98fa7583199ca6e +msgid "Return type" +msgstr "" + +#: ../../en/m5ui/textarea.rst:353 bb0710d85fb947ce9dd9149dfdc41cb4 +msgid "|get_text.png|" +msgstr "" + +#: ../../en/refs/m5ui.textarea.ref:9 0a5ef1bebd4c4b98b46169f1002b09e7 +msgid "get_text.png" +msgstr "" + +#: ../../en/m5ui/textarea.rst:363 e2320a81b9bf4a7a841b9b0324769842 +msgid "Add text to the current content of the textarea." +msgstr "将文本添加到文本框的当前内容。" + +#: ../../en/m5ui/textarea.rst:365 3a971c884e9e4c82b603b71ae0921a42 +msgid "The text to add." +msgstr "要添加的文本。" + +#: ../../en/m5ui/textarea.rst:370 ee071ff836774627949feaba07d37c36 +msgid "|add_text.png|" +msgstr "" + +#: ../../en/refs/m5ui.textarea.ref:4 431c131cad8d4a4391756687219bfa2b +msgid "add_text.png" +msgstr "" + +#: ../../en/m5ui/textarea.rst:380 86952056d06f4145b4533c551fa85e86 +msgid "Set the maximum length of text that can be entered in the textarea." +msgstr "设置可在文本框中输入的文本的最大长度。" + +#: ../../en/m5ui/textarea.rst:382 905b890c3db6434fb48c50f4963b83c3 +msgid "The maximum length of text." +msgstr "文本的最大长度。" + +#: ../../en/m5ui/textarea.rst:387 d188c139a5434c89a780e913d68da37b +msgid "|set_max_length.png|" +msgstr "" + +#: ../../en/refs/m5ui.textarea.ref:16 f8153cf39d9b474eaffeb41e5905242d +msgid "set_max_length.png" +msgstr "" + +#: ../../en/m5ui/textarea.rst:397 fc2e0f707df744b19cbe33527a5cd5ea +msgid "" +"Set whether the textarea should be in password mode (i.e., characters are" +" hidden)." +msgstr "设置文本框是否应处于密码模式(即字符被隐藏)。" + +#: ../../en/m5ui/textarea.rst:399 2a3324817af445a9b455f8d0a3874159 +msgid "True to enable password mode, False to disable." +msgstr "True 表示启用密码模式,False 表示禁用。" + +#: ../../en/m5ui/textarea.rst:404 aa6a825f3a454429960d62cfc80902d4 +msgid "|set_password_mode.png|" +msgstr "" + +#: ../../en/refs/m5ui.textarea.ref:18 3a8776dad1c944eb99ae9e2f80c21af6 +msgid "set_password_mode.png" +msgstr "" + +#: ../../en/m5ui/textarea.rst:414 d99d8e28d6a44d59848a7d58d1db59e4 +msgid "Set whether the textarea should be single line or multi-line." +msgstr "设置文本框是单行还是多行。" + +#: ../../en/m5ui/textarea.rst:416 23a2787a335f4579b2d448f400c4b18f +msgid "True for single line, False for multi-line." +msgstr "表示单行,表示多行。" + +#: ../../en/m5ui/textarea.rst:421 0b8299717cb4462090c20a35dbc0e18a +msgid "|set_one_line.png|" +msgstr "" + +#: ../../en/refs/m5ui.textarea.ref:17 2b7fa5bc3640412f9140f17794025cf7 +msgid "set_one_line.png" +msgstr "" + +#: ../../en/m5ui/textarea.rst:431 3e7e721fc22a42ec9c35c8ca2aa18950 +msgid "" +"Set the characters that are accepted in the textarea. Only these " +"characters can be entered." +msgstr "设置文本框可接受的字符。只有这些字符才允许输入。" + +#: ../../en/m5ui/textarea.rst:433 9ec4c47d3032435f9c21678cfe1f6d7a +msgid "The string of accepted characters." +msgstr "可接受的字符串。" + +#: ../../en/m5ui/textarea.rst:438 38a732734d46460fbd193cdaa6dd4dc8 +msgid "|set_accepted_chars.png|" +msgstr "" + +#: ../../en/refs/m5ui.textarea.ref:11 5f4aeb65a3504696a4e73f65c6eec294 +msgid "set_accepted_chars.png" +msgstr "" + +#: ../../en/m5ui/textarea.rst:448 d0f2a484add04f7d96746a7a1e3d1803 +msgid "Set the position of the textarea." +msgstr "设置文本框的位置。" + +#: ../../en/m5ui/textarea.rst:450 ../../en/m5ui/textarea.rst:468 +#: 24c1ba3b59f04021b76441d9c0ea6885 f7f4e4a1637c4860be442c0c5aa8db07 +msgid "The x-coordinate of the textarea." +msgstr "文本框的 x 坐标。" + +#: ../../en/m5ui/textarea.rst:451 ../../en/m5ui/textarea.rst:485 +#: 0a357decf3f64609b940e082c38b8200 dc3179fac29b4cf8877c0cb73df23782 +msgid "The y-coordinate of the textarea." +msgstr "文本框的 y 坐标。" + +#: ../../en/m5ui/textarea.rst:456 7128c448175748e081e503741d89c668 +msgid "|set_pos.png|" +msgstr "" + +#: ../../en/refs/m5ui.textarea.ref:23 18702d1e47ad417d9af15d4f266ab3cf +msgid "set_pos.png" +msgstr "" + +#: ../../en/m5ui/textarea.rst:466 540b41bcebce4d19b2e0d5e7de7fe391 +msgid "Set the x-coordinate of the textarea." +msgstr "设置文本框的 x 坐标。" + +#: ../../en/m5ui/textarea.rst:473 f29ef2497f1342c5b67ae8c163d4867b +msgid "|set_x.png|" +msgstr "" + +#: ../../en/refs/m5ui.textarea.ref:33 9b145e6742074a7287f0296695f6717a +msgid "set_x.png" +msgstr "" + +#: ../../en/m5ui/textarea.rst:483 4722d40eae0e4423bf18917095f233d0 +msgid "Set the y-coordinate of the textarea." +msgstr "设置文本框的 y 坐标。" + +#: ../../en/m5ui/textarea.rst:490 99cdb4e8277d4c53975413aa8dbcd4e4 +msgid "|set_y.png|" +msgstr "" + +#: ../../en/refs/m5ui.textarea.ref:34 21b10e3a181442f1b9155c2c9546baf9 +msgid "set_y.png" +msgstr "" + +#: ../../en/m5ui/textarea.rst:500 4d7e6f7f72f54d2c8ecfb002ff986e23 +msgid "Align the textarea to another object." +msgstr "将文本框与另一个对象对齐。" + +#: ../../en/m5ui/textarea.rst:502 e518d106c1f94a70a9f79b4ff9ca2da2 +msgid "The object to align to." +msgstr "要对齐的对象。" + +#: ../../en/m5ui/textarea.rst:503 54062efa014d4488aa70ce60426cadd9 +msgid "The alignment type." +msgstr "对齐类型。" + +#: ../../en/m5ui/textarea.rst:504 654d91466a084125afa51ffa222b2ec1 +msgid "The x-offset from the aligned object." +msgstr "与对齐对象的 x 偏移量。" + +#: ../../en/m5ui/textarea.rst:505 4a5058160ef94145924d924e332af7a9 +msgid "The y-offset from the aligned object." +msgstr "与对齐对象的 y 偏移。" + +#: ../../en/m5ui/textarea.rst:510 a6bb7ce697fe4df5a17e9de756d240a4 +msgid "|align_to.png|" +msgstr "" + +#: ../../en/refs/m5ui.textarea.ref:5 52f350d8431b4c3f88e9bfab5490c1a4 +msgid "align_to.png" +msgstr "" + +#: ../../en/m5ui/textarea.rst:520 98c576f6d547413092df5a983033e491 +msgid "Set the size of the textarea." +msgstr "设置文本框的大小。" + +#: ../../en/m5ui/textarea.rst:522 ../../en/m5ui/textarea.rst:540 +#: ../../en/m5ui/textarea.rst:574 21ab1c6c1a344c4b84472ba619ce61f3 +#: aad85542d882486c9eae3b12372db8d3 fd4008023fbe4015a31f6041530849a1 +msgid "The width of the textarea." +msgstr "文本框的宽度。" + +#: ../../en/m5ui/textarea.rst:523 ../../en/m5ui/textarea.rst:557 +#: ../../en/m5ui/textarea.rst:591 7f7deae8e90a4d7ebcd3753690b46cf1 +#: 9844abe4d5f4449494451ff0f4de85d6 ce1cbde7dc7a470f91eb5d4010cbb409 +msgid "The height of the textarea." +msgstr "文本框的高度。" + +#: ../../en/m5ui/textarea.rst:528 d4c5c3184f1e46b68ee1e7e5e98f345b +msgid "|set_size.png|" +msgstr "" + +#: ../../en/refs/m5ui.textarea.ref:24 d0a726f94f104bc5a97df4262d637020 +msgid "set_size.png" +msgstr "" + +#: ../../en/m5ui/textarea.rst:538 21f4f2e51b4b4192b01495a488c214e7 +msgid "Set the width of the textarea." +msgstr "设置文本框的宽度。" + +#: ../../en/m5ui/textarea.rst:545 9d4714678728406380bd0aca24073f9b +msgid "|set_width.png|" +msgstr "" + +#: ../../en/refs/m5ui.textarea.ref:32 a95f99f994e148c4befcc6bfdb74ddf8 +msgid "set_width.png" +msgstr "" + +#: ../../en/m5ui/textarea.rst:555 f3fa5ab4d1514f1b8dcaf52c40adbe31 +msgid "Set the height of the textarea." +msgstr "设置文本框的高度。" + +#: ../../en/m5ui/textarea.rst:562 512db86df3054773a29e6adedc156c21 +msgid "|set_height.png|" +msgstr "" + +#: ../../en/refs/m5ui.textarea.ref:15 60bbf58b36f348b98c026d522c45c697 +msgid "set_height.png" +msgstr "" + +#: ../../en/m5ui/textarea.rst:572 b04893ce965a4dc0bdee76a33863b0e4 +msgid "Get the width of the textarea." +msgstr "获取文本框的宽度。" + +#: ../../en/m5ui/textarea.rst:579 dca6003e24a241a9b7975d3cbb826838 +msgid "|get_width.png|" +msgstr "" + +#: ../../en/refs/m5ui.textarea.ref:10 6b752e2d957a411d96a725a2fe9ce106 +msgid "get_width.png" +msgstr "" + +#: ../../en/m5ui/textarea.rst:589 b3784c885a084eadb08e0576194647c4 +msgid "Get the height of the textarea." +msgstr "获取文本框的高度。" + +#: ../../en/m5ui/textarea.rst:596 edddc9a6010b480ab4c71d171d0820f1 +msgid "|get_height.png|" +msgstr "" + +#: ../../en/refs/m5ui.textarea.ref:8 6f954704af704ac7877d440dc6fbcae6 +msgid "get_height.png" +msgstr "" diff --git a/examples/m5ui/textarea/cores3_textarea_basic_example.m5f2 b/examples/m5ui/textarea/cores3_textarea_basic_example.m5f2 new file mode 100644 index 00000000..b22625ab --- /dev/null +++ b/examples/m5ui/textarea/cores3_textarea_basic_example.m5f2 @@ -0,0 +1 @@ +{"version":"V2.0","versionNumber":"V2.3.2","type":"cores3","components":[{"name":"page0","type":"lvgl_page","layer":0,"screenId":"builtin","screenName":"","id":"qJ!shIcWvF&E=L*+","createTime":1753600881709,"backgroundColor":"#ffffff","isLVGL":true,"isSelected":true},{"name":"textarea0","type":"lvgl_textarea","layer":1,"screenId":"builtin","screenName":"","id":"ySpz!H7P^sTT1n&B","createTime":1753603138676,"x":19,"y":26,"width":266,"height":124,"color":"#212121","borderColor":"#e0e0e0","backgroundColor":"#ffffff","text":"","placeholder":"Placeholder...","font":"lv.font_montserrat_14","pageId":"qJ!shIcWvF&E=L*+","isLVGL":true,"isSelected":false},{"name":"button0","type":"lvgl_button","layer":2,"screenId":"builtin","screenName":"","id":"mBUAWtD^XQ^@Jo3I","createTime":1753603156152,"x":22,"y":172,"width":0,"height":0,"color":"#ffffff","backgroundColor":"#2196f3","text":"clear text","font":"lv.font_montserrat_14","pageId":"qJ!shIcWvF&E=L*+","isLVGL":true,"isSelected":false}],"resources":[{"hardware":["hardware_button","hardware_pin_button","imu","speaker","touch","als","mic","sdcard"]}],"units":[],"hats":[],"bases":[],"i2cs":[],"blockly":"linetruepage0line1truetextarea0hello M5lineline1line1button0CLICKEDtextarea0line1","screen":[{"simulationName":"Built-in","type":"builtin","width":320,"height":240,"scale":0.77,"screenName":"","blockId":"","screenColorType":0,"rotation":1,"id":"builtin","createTime":1753600881707}],"logicWhenNum":0,"customList":[]} \ No newline at end of file diff --git a/examples/m5ui/textarea/cores3_textarea_basic_example.py b/examples/m5ui/textarea/cores3_textarea_basic_example.py new file mode 100644 index 00000000..6993f46c --- /dev/null +++ b/examples/m5ui/textarea/cores3_textarea_basic_example.py @@ -0,0 +1,92 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import os, sys, io +import M5 +from M5 import * +import m5ui +import lvgl as lv +import time + + +page0 = None +textarea0 = None +button0 = None + + +line = None + + +def button0_clicked_event(event_struct): + global page0, textarea0, button0, line + + textarea0.set_text("") + line = 1 + + +def button0_event_handler(event_struct): + global page0, textarea0, button0, line + event = event_struct.code + if event == lv.EVENT.CLICKED and True: + button0_clicked_event(event_struct) + return + + +def setup(): + global page0, textarea0, button0, line + + M5.begin() + Widgets.setRotation(1) + m5ui.init() + page0 = m5ui.M5Page(bg_c=0xFFFFFF) + textarea0 = m5ui.M5TextArea( + text="", + placeholder="Placeholder...", + x=19, + y=26, + w=266, + h=124, + font=lv.font_montserrat_14, + bg_c=0xFFFFFF, + border_c=0xE0E0E0, + text_c=0x212121, + parent=page0, + ) + button0 = m5ui.M5Button( + text="clear text", + x=22, + y=172, + bg_c=0x2196F3, + text_c=0xFFFFFF, + font=lv.font_montserrat_14, + parent=page0, + ) + + button0.add_event_cb(button0_event_handler, lv.EVENT.ALL, None) + + page0.screen_load() + line = 1 + + +def loop(): + global page0, textarea0, button0, line + M5.update() + textarea0.add_text(str((str("line") + str(line)))) + time.sleep(1) + line = (line if isinstance(line, (int, float)) else 0) + 1 + + +if __name__ == "__main__": + try: + setup() + while True: + loop() + except (Exception, KeyboardInterrupt) as e: + try: + m5ui.deinit() + from utility import print_error_msg + + print_error_msg(e) + except ImportError: + print("please update to latest firmware") diff --git a/m5stack/libs/m5ui/__init__.py b/m5stack/libs/m5ui/__init__.py index 69fa8716..3e2f0baf 100644 --- a/m5stack/libs/m5ui/__init__.py +++ b/m5stack/libs/m5ui/__init__.py @@ -16,6 +16,7 @@ "M5Page": "page", "M5Slider": "slider", "M5Switch": "switch", + "M5TextArea": "textarea", } diff --git a/m5stack/libs/m5ui/manifest.py b/m5stack/libs/m5ui/manifest.py index 369571b7..305a980e 100644 --- a/m5stack/libs/m5ui/manifest.py +++ b/m5stack/libs/m5ui/manifest.py @@ -19,6 +19,7 @@ "port.py", "slider.py", "switch.py", + "textarea.py", ), base_path="..", opt=0, diff --git a/m5stack/libs/m5ui/textarea.py b/m5stack/libs/m5ui/textarea.py new file mode 100644 index 00000000..31d3b783 --- /dev/null +++ b/m5stack/libs/m5ui/textarea.py @@ -0,0 +1,58 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +from .base import M5Base +import lvgl as lv + + +class M5TextArea(lv.textarea): + """Create a text area widget. + + :param str text: Initial text content of the text area. + :param str placeholder: Placeholder text when the text area is empty. + :param int x: X position of the text area. + :param int y: Y position of the text area. + :param int w: Width of the text area. + :param int h: Height of the text area. + :param lv.font_t font: Font used for the text. + :param int bg_c: Background color of the text area. + :param int border_c: Border color of the text area. + :param int text_c: Text color of the text area. + :param lv.obj parent: Parent object of the text area. If not specified, it will be set to the active screen. + """ + + def __init__( + self, + text="", + placeholder="", + x=0, + y=0, + w=200, + h=100, + font=lv.font_montserrat_14, + bg_c=0xFFFFFF, + border_c=0xE0E0E0, + text_c=0x212121, + parent=None, + ): + if parent is None: + parent = lv.screen_active() + super().__init__(parent) + + self.set_size(w, h) + self.set_pos(x, y) + self.set_text(text) + self.set_placeholder_text(placeholder) + self.set_style_text_font(font, lv.PART.MAIN | lv.STATE.DEFAULT) + self.set_bg_color(bg_c, lv.OPA.COVER, lv.PART.MAIN | lv.STATE.DEFAULT) + self.set_border_color(border_c, lv.OPA.COVER, lv.PART.MAIN | lv.STATE.DEFAULT) + self.set_text_color(text_c, lv.OPA.COVER, lv.PART.MAIN | lv.STATE.DEFAULT) + + def __getattr__(self, name): + if hasattr(M5Base, name): + method = getattr(M5Base, name) + bound_method = lambda *args, **kwargs: method(self, *args, **kwargs) + setattr(self, name, bound_method) + return bound_method + raise AttributeError(f"'{self.__class__.__name__}' object has no attribute '{name}'") From bad154bdbf850d8ac2f27a002d8a67132d7a80b2 Mon Sep 17 00:00:00 2001 From: lbuque Date: Wed, 9 Jul 2025 14:41:35 +0800 Subject: [PATCH 184/322] libs/m5ui: Add m5ui.M5Canvas support. Signed-off-by: lbuque --- docs/en/m5ui/canvas.rst | 251 ++++++ docs/en/m5ui/index.rst | 1 + docs/en/refs/m5ui.canvas.ref | 30 + docs/locales/zh_CN/LC_MESSAGES/m5ui/canvas.po | 741 ++++++++++++++++++ .../canvas/cores3_canvas_basic_example.m5f2 | 1 + .../canvas/cores3_canvas_basic_example.py | 79 ++ m5stack/libs/m5ui/__init__.py | 1 + m5stack/libs/m5ui/canvas.py | 475 +++++++++++ m5stack/libs/m5ui/manifest.py | 1 + 9 files changed, 1580 insertions(+) create mode 100644 docs/en/m5ui/canvas.rst create mode 100644 docs/en/refs/m5ui.canvas.ref create mode 100644 docs/locales/zh_CN/LC_MESSAGES/m5ui/canvas.po create mode 100644 examples/m5ui/canvas/cores3_canvas_basic_example.m5f2 create mode 100644 examples/m5ui/canvas/cores3_canvas_basic_example.py create mode 100644 m5stack/libs/m5ui/canvas.py diff --git a/docs/en/m5ui/canvas.rst b/docs/en/m5ui/canvas.rst new file mode 100644 index 00000000..9d856f4c --- /dev/null +++ b/docs/en/m5ui/canvas.rst @@ -0,0 +1,251 @@ +.. currentmodule:: m5ui + +M5Canvas +======== + +.. include:: ../refs/m5ui.canvas.ref + +M5Canvas is a powerful graphics widget that provides a drawable surface for creating custom graphics, animations, and visual effects in the user interface. It supports drawing operations, sprite management, and advanced graphics rendering. + + +UiFlow2 Example +--------------- + +draw basic shapes +^^^^^^^^^^^^^^^^^ + +Open the |cores3_canvas_basic_example.m5f2| project in UiFlow2. + +This example demonstrates how to create a canvas and draw basic shapes like rectangles, circles, and lines. + +UiFlow2 Code Block: + + |cores3_canvas_basic_example.png| + +Example output: + + A canvas with various colored shapes including rectangles, circles, and lines. + + +MicroPython Example +------------------- + +draw basic shapes +^^^^^^^^^^^^^^^^^ + +This example demonstrates how to create a canvas and draw basic shapes programmatically. + +MicroPython Code Block: + + .. literalinclude:: ../../../examples/m5ui/canvas/cores3_canvas_basic_example.py + :language: python + :linenos: + +Example output: + + A canvas displaying various geometric shapes with different colors. + + +**API** +------- + +M5Canvas +^^^^^^^^ + +.. autoclass:: m5ui.canvas.M5Canvas + :members: + :member-order: bysource + + .. py:method:: set_flag(flag, value) + + Set a flag on the object. If ``value`` is True, the flag is added; if False, the flag is removed. + + :param int flag: The flag to set. + :param bool value: If True, the flag is added; if False, the flag is removed. + :return: None + + UiFlow2 Code Block: + + |set_flag.png| + + MicroPython Code Block: + + .. code-block:: python + + canvas_0.set_flag(lv.obj.FLAG.HIDDEN, True) + + .. py:method:: toggle_flag(flag) + + Toggle a flag on the object. If the flag is set, it is removed; if not set, it is added. + + :param int flag: The flag to toggle. + :return: None + + UiFlow2 Code Block: + + |toggle_flag.png| + + MicroPython Code Block: + + .. code-block:: python + + canvas_0.toggle_flag(lv.obj.FLAG.HIDDEN) + + + .. py:method:: set_pos(x, y) + + Set the position of the canvas. + + :param int x: The x-coordinate of the canvas. + :param int y: The y-coordinate of the canvas. + :return: None + + UiFlow2 Code Block: + + |set_pos.png| + + MicroPython Code Block: + + .. code-block:: python + + canvas_0.set_pos(100, 100) + + .. py:method:: set_x(x) + + Set the x-coordinate of the canvas. + + :param int x: The x-coordinate of the canvas. + :return: None + + UiFlow2 Code Block: + + |set_x.png| + + MicroPython Code Block: + + .. code-block:: python + + canvas_0.set_x(100) + + .. py:method:: set_y(y) + + Set the y-coordinate of the canvas. + + :param int y: The y-coordinate of the canvas. + :return: None + + UiFlow2 Code Block: + + |set_y.png| + + MicroPython Code Block: + + .. code-block:: python + + canvas_0.set_y(100) + + .. py:method:: align_to(obj, align, x, y) + + Align the canvas to another object. + + :param lv.obj obj: The object to align to. + :param int align: The alignment type. + :param int x: The x-offset from the aligned object. + :param int y: The y-offset from the aligned object. + :return: None + + UiFlow2 Code Block: + + |align_to.png| + + MicroPython Code Block: + + .. code-block:: python + + canvas_0.align_to(page_0, lv.ALIGN.CENTER, 0, 0) + + .. py:method:: set_size(width, height) + + Set the size of the canvas. + + :param int width: The width of the canvas. + :param int height: The height of the canvas. + :return: None + + UiFlow2 Code Block: + + |set_size.png| + + MicroPython Code Block: + + .. code-block:: python + + canvas_0.set_size(200, 100) + + .. py:method:: set_width(width) + + Set the width of the canvas. + + :param int width: The width of the canvas. + :return: None + + UiFlow2 Code Block: + + |set_width.png| + + MicroPython Code Block: + + .. code-block:: python + + canvas_0.set_width(200) + + .. py:method:: set_height(height) + + Set the height of the canvas. + + :param int height: The height of the canvas. + :return: None + + UiFlow2 Code Block: + + |set_height.png| + + MicroPython Code Block: + + .. code-block:: python + + canvas_0.set_height(100) + + .. py:method:: get_width() + + Get the width of the canvas. + + :return: The width of the canvas. + :rtype: int + + UiFlow2 Code Block: + + |get_width.png| + + MicroPython Code Block: + + .. code-block:: python + + width = canvas_0.get_width() + + .. py:method:: get_height() + + Get the height of the canvas. + + :return: The height of the canvas. + :rtype: int + + UiFlow2 Code Block: + + |get_height.png| + + MicroPython Code Block: + + .. code-block:: python + + height = canvas_0.get_height() diff --git a/docs/en/m5ui/index.rst b/docs/en/m5ui/index.rst index 9f578d9a..b302ed99 100644 --- a/docs/en/m5ui/index.rst +++ b/docs/en/m5ui/index.rst @@ -37,6 +37,7 @@ Classes bar.rst button.rst calendar.rst + canvas.rst checkbox.rst image.rst label.rst diff --git a/docs/en/refs/m5ui.canvas.ref b/docs/en/refs/m5ui.canvas.ref new file mode 100644 index 00000000..1af7b86f --- /dev/null +++ b/docs/en/refs/m5ui.canvas.ref @@ -0,0 +1,30 @@ +.. |cores3_canvas_basic_example.m5f2| raw:: html + + cores3_canvas_basic_example.m5f2 + +.. |cores3_canvas_basic_example.png| image:: https://static-cdn.m5stack.com/image/m5-docs/docs-en/tutorial/m5ui/canvas/cores3_canvas_basic_example.png + +.. |align_to.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/canvas/align_to.png +.. |draw_arc.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/canvas/draw_arc.png +.. |draw_image.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/canvas/draw_image.png +.. |draw_label.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/canvas/draw_label.png +.. |draw_line.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/canvas/draw_line.png +.. |draw_rect.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/canvas/draw_rect.png +.. |draw_triangle.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/canvas/draw_triangle.png +.. |get_height.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/canvas/get_height.png +.. |get_px_color.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/canvas/get_px_color.png +.. |get_px_opa.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/canvas/get_px_opa.png +.. |get_width.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/canvas/get_width.png +.. |set_bg_color.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/canvas/set_bg_color.png +.. |set_border_color.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/canvas/set_border_color.png +.. |set_flag.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/canvas/set_flag.png +.. |set_height.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/canvas/set_height.png +.. |set_pos.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/canvas/set_pos.png +.. |set_px.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/canvas/set_px.png +.. |set_size.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/canvas/set_size.png +.. |set_style_border_width.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/canvas/set_style_border_width.png +.. |set_style_radius.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/canvas/set_style_radius.png +.. |set_width.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/canvas/set_width.png +.. |set_x.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/canvas/set_x.png +.. |set_y.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/canvas/set_y.png +.. |toggle_flag.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/canvas/toggle_flag.png diff --git a/docs/locales/zh_CN/LC_MESSAGES/m5ui/canvas.po b/docs/locales/zh_CN/LC_MESSAGES/m5ui/canvas.po new file mode 100644 index 00000000..7f8e98c9 --- /dev/null +++ b/docs/locales/zh_CN/LC_MESSAGES/m5ui/canvas.po @@ -0,0 +1,741 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2016 - 2025 M5Stack Technology Co., Ltd +# This file is distributed under the same license as the UIFlow2 Programming +# Guide package. +# FIRST AUTHOR , 2025. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: UIFlow2 Programming Guide \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-07-28 14:31+0800\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: zh_CN\n" +"Language-Team: zh_CN \n" +"Plural-Forms: nplurals=1; plural=0;\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.17.0\n" + +#: ../../en/m5ui/canvas.rst:4 ../../en/m5ui/canvas.rst:53 +#: 8768b891c7a545dc912c9a8472b6df12 ac10d329627449579d9b3ab8edafe40f +msgid "M5Canvas" +msgstr "" + +#: ../../en/m5ui/canvas.rst:8 614b73a13b384069b9439be204d73234 +msgid "" +"M5Canvas is a powerful graphics widget that provides a drawable surface " +"for creating custom graphics, animations, and visual effects in the user " +"interface. It supports drawing operations, sprite management, and " +"advanced graphics rendering." +msgstr "M5Canvas是一个强大的图形控件,提供可绘制的表面,用于在用户界面中创建自定义图形、动画和视觉效果。它支持绘图操作、图像管理和高级图形渲染。" + +#: ../../en/m5ui/canvas.rst:12 c158267309e94384b6668aa9a6ac11f0 +msgid "UiFlow2 Example" +msgstr "UiFlow2 应用示例" + +#: ../../en/m5ui/canvas.rst:15 ../../en/m5ui/canvas.rst:34 +#: ba95f12783044027bf4488985231b60b c0f6e1e6bb73487ab83465fa7b5d01f8 +msgid "draw basic shapes" +msgstr "" + +#: ../../en/m5ui/canvas.rst:17 6b880d58607249f1b48bea4ad2a64712 +msgid "Open the |cores3_canvas_basic_example.m5f2| project in UiFlow2." +msgstr "在UiFlow2中打开 |cores3_canvas_basic_example.m5f2| 项目。" + +#: ../../en/m5ui/canvas.rst:19 e06c13f9b8ff47439f06a7104f0fe1bc +msgid "" +"This example demonstrates how to create a canvas and draw basic shapes " +"like rectangles, circles, and lines." +msgstr "此示例演示如何创建画布并绘制矩形、圆形和线条等基本图形。" + +#: ../../en/m5ui/canvas.rst:21 ../../en/m5ui/canvas.rst:67 +#: ../../en/m5ui/canvas.rst:84 ../../en/m5ui/canvas.rst:103 +#: ../../en/m5ui/canvas.rst:120 ../../en/m5ui/canvas.rst:137 +#: ../../en/m5ui/canvas.rst:157 ../../en/m5ui/canvas.rst:175 +#: ../../en/m5ui/canvas.rst:192 ../../en/m5ui/canvas.rst:209 +#: ../../en/m5ui/canvas.rst:226 ../../en/m5ui/canvas.rst:243 +#: 0b5e2fc9f69c4a11ba1c4dad76c225e6 660dfa6ab4c0486298c66f3471e0a3c0 +#: m5ui.canvas.M5Canvas.draw_arc:12 m5ui.canvas.M5Canvas.draw_image:12 +#: m5ui.canvas.M5Canvas.draw_label:10 m5ui.canvas.M5Canvas.draw_line:11 +#: m5ui.canvas.M5Canvas.draw_rect:25 m5ui.canvas.M5Canvas.draw_triangle:12 +#: m5ui.canvas.M5Canvas.fill_bg:6 m5ui.canvas.M5Canvas.get_px_color:8 +#: m5ui.canvas.M5Canvas.get_px_opa:8 m5ui.canvas.M5Canvas.set_px:8 of +msgid "UiFlow2 Code Block:" +msgstr "UiFlow2 代码块:" + +#: ../../en/m5ui/canvas.rst:23 a0761c40ff6d4ac088906cafca84b757 +msgid "|cores3_canvas_basic_example.png|" +msgstr "" + +#: ../../en/refs/m5ui.canvas.ref:5 edc07b776b924202a0abab893e6e7e43 +msgid "cores3_canvas_basic_example.png" +msgstr "" + +#: ../../en/m5ui/canvas.rst:25 ../../en/m5ui/canvas.rst:44 +#: 9cd432bf5e7b45d098a8772930c5aeac ec1f283a132046e4b450aff01a2a033f +msgid "Example output:" +msgstr "示例输出:" + +#: ../../en/m5ui/canvas.rst:27 f54427e7d5d345d989b80418c650daa9 +msgid "" +"A canvas with various colored shapes including rectangles, circles, and " +"lines." +msgstr "包含各种彩色图形的画布,包括矩形、圆形和线条。" + +#: ../../en/m5ui/canvas.rst:31 498f42e4bb2945b0aaf04597b77567e0 +msgid "MicroPython Example" +msgstr "MicroPython示例" + +#: ../../en/m5ui/canvas.rst:36 f1cd20ad33ce41a1980bb0e1b3f5587e +msgid "" +"This example demonstrates how to create a canvas and draw basic shapes " +"programmatically." +msgstr "此示例演示如何以编程方式创建画布并绘制基本图形。" + +#: ../../en/m5ui/canvas.rst:38 ../../en/m5ui/canvas.rst:71 +#: ../../en/m5ui/canvas.rst:88 ../../en/m5ui/canvas.rst:107 +#: ../../en/m5ui/canvas.rst:124 ../../en/m5ui/canvas.rst:141 +#: ../../en/m5ui/canvas.rst:161 ../../en/m5ui/canvas.rst:179 +#: ../../en/m5ui/canvas.rst:196 ../../en/m5ui/canvas.rst:213 +#: ../../en/m5ui/canvas.rst:230 ../../en/m5ui/canvas.rst:247 +#: 410c18aebd624832bdfc5531cffdd0a2 8c45e7a7b93b4a2cadf70e91c19d6741 +#: m5ui.canvas.M5Canvas.draw_arc:16 m5ui.canvas.M5Canvas.draw_image:16 +#: m5ui.canvas.M5Canvas.draw_label:14 m5ui.canvas.M5Canvas.draw_line:15 +#: m5ui.canvas.M5Canvas.draw_rect:29 m5ui.canvas.M5Canvas.draw_triangle:16 +#: m5ui.canvas.M5Canvas.fill_bg:10 m5ui.canvas.M5Canvas.get_px_color:12 +#: m5ui.canvas.M5Canvas.get_px_opa:12 m5ui.canvas.M5Canvas.set_px:12 of +msgid "MicroPython Code Block:" +msgstr "MicroPython 代码块:" + +#: ../../en/m5ui/canvas.rst:46 3781abd637b844b6a6a1f04be0765c27 +msgid "A canvas displaying various geometric shapes with different colors." +msgstr "显示不同颜色几何图形的画布。" + +#: ../../en/m5ui/canvas.rst:50 c061b77eeca54945a1945404e8455e53 +msgid "**API**" +msgstr "API参考" + +#: 0214bfcf5a814324a02b8e74abc11fac m5ui.canvas.M5Canvas:1 of +msgid "Bases: :py:class:`~lvgl.canvas`" +msgstr "" + +#: 098fce33424f41ba940e661611b4b165 m5ui.canvas.M5Canvas:1 of +msgid "Create a canvas widget for drawing." +msgstr "创建用于绘图的画布控件。" + +#: ../../en/m5ui/canvas.rst 63b0c6d5c2374596bae32ff90a4e8a6a +#: e43504a998374d9ba6f335bd9192d756 m5ui.canvas.M5Canvas.draw_arc +#: m5ui.canvas.M5Canvas.draw_image m5ui.canvas.M5Canvas.draw_label +#: m5ui.canvas.M5Canvas.draw_line m5ui.canvas.M5Canvas.draw_rect +#: m5ui.canvas.M5Canvas.draw_triangle m5ui.canvas.M5Canvas.fill_bg +#: m5ui.canvas.M5Canvas.get_px_color m5ui.canvas.M5Canvas.get_px_opa +#: m5ui.canvas.M5Canvas.set_px of +msgid "Parameters" +msgstr "" + +#: ../../en/m5ui/canvas.rst:99 ../../en/m5ui/canvas.rst:117 +#: 17883dadd3bf4f52a48652f2ac4c8a1d cd80caf4ae0a4fc9bf45194a98116667 +#: m5ui.canvas.M5Canvas:3 of +msgid "The x-coordinate of the canvas." +msgstr "画布的x坐标。" + +#: ../../en/m5ui/canvas.rst:100 ../../en/m5ui/canvas.rst:134 +#: 58aec390c0bf4177bd0e503296f9b952 f024c68ac1234587bd4d3bad3235b81e +#: m5ui.canvas.M5Canvas:4 of +msgid "The y-coordinate of the canvas." +msgstr "画布的y坐标。" + +#: ../../en/m5ui/canvas.rst:171 ../../en/m5ui/canvas.rst:189 +#: ../../en/m5ui/canvas.rst:223 5528520b9cf944f2ac5683da242601f9 +#: 7e95f0079ff5449eba9e8a3dc24bcf77 m5ui.canvas.M5Canvas:5 of +msgid "The width of the canvas." +msgstr "画布的宽度。" + +#: ../../en/m5ui/canvas.rst:172 ../../en/m5ui/canvas.rst:206 +#: ../../en/m5ui/canvas.rst:240 2ac85d1237aa41828ee36fdd37c5db5e +#: 32b8f100b42d4bd8befb459ef0b5026c m5ui.canvas.M5Canvas:6 of +msgid "The height of the canvas." +msgstr "画布的高度。" + +#: c505edb7328a4c1da16a396b357aa6f3 m5ui.canvas.M5Canvas:7 of +msgid "The color format of the canvas (default is ARGB8888)." +msgstr "画布的颜色格式(默认为ARGB8888)。" + +#: 129361a7120147d58f620188d8a7160f m5ui.canvas.M5Canvas:8 of +msgid "The background color of the canvas in hexadecimal format (e.g., 0xRRGGBB)." +msgstr "十六进制格式的画布背景颜色(例如,0xRRGGBB)。" + +#: 141e7513f68b4fa9b6a35720b1427ccb d646de0763b64c689da73e267a11f7b7 +#: m5ui.canvas.M5Canvas:9 m5ui.canvas.M5Canvas.draw_rect:9 +#: m5ui.canvas.M5Canvas.fill_bg:4 of +msgid "The opacity of the background (0-255)." +msgstr "背景的不透明度(0-255)。" + +#: be57f7f86c1649d58f3e87ec59a12e4e m5ui.canvas.M5Canvas:10 of +msgid "" +"The parent object of the canvas. If not specified, it will be set to the " +"active screen." +msgstr "画布的父对象。如果未指定,将设置为活动屏幕。" + +#: ../../en/m5ui/canvas.rst:61 9680356c22734a7f99b42cdcbe7abf83 +msgid "" +"Set a flag on the object. If ``value`` is True, the flag is added; if " +"False, the flag is removed." +msgstr "为对象设置标志。如果 ``value`` 为True,则添加标志;如果为False,则移除标志。" + +#: ../../en/m5ui/canvas.rst:63 63676386b47d42308ff44856f3011d4d +msgid "The flag to set." +msgstr "要设置的标志。" + +#: ../../en/m5ui/canvas.rst:64 bfd5751f3b8942939e6441a36d443513 +msgid "If True, the flag is added; if False, the flag is removed." +msgstr "如果为True,则添加标志;如果为False,则移除标志。" + +#: ../../en/m5ui/canvas.rst db2248fe09d04164a3aed99407c92933 +#: m5ui.canvas.M5Canvas.get_px_color m5ui.canvas.M5Canvas.get_px_opa of +msgid "Returns" +msgstr "" + +#: ../../en/m5ui/canvas.rst:65 ../../en/m5ui/canvas.rst:82 +#: ../../en/m5ui/canvas.rst:101 ../../en/m5ui/canvas.rst:118 +#: ../../en/m5ui/canvas.rst:135 ../../en/m5ui/canvas.rst:155 +#: ../../en/m5ui/canvas.rst:173 ../../en/m5ui/canvas.rst:190 +#: ../../en/m5ui/canvas.rst:207 0c467f17a1924b2d9b479397691247ad +msgid "None" +msgstr "" + +#: ../../en/m5ui/canvas.rst:69 943e98eeef8741d6ac621827d3b662c6 +msgid "|set_flag.png|" +msgstr "" + +#: ../../en/refs/m5ui.canvas.ref:20 a9c245b40bef4d71a0b0e03c17e042f7 +msgid "set_flag.png" +msgstr "" + +#: ../../en/m5ui/canvas.rst:79 db931bd9626444d3ad9b71b1cabc9093 +msgid "" +"Toggle a flag on the object. If the flag is set, it is removed; if not " +"set, it is added." +msgstr "切换对象上的标志。如果标志已设置,则移除它;如果未设置,则添加它。" + +#: ../../en/m5ui/canvas.rst:81 3a3f68658b1e4c03b4460f80cbdf7355 +msgid "The flag to toggle." +msgstr "要切换的标志。" + +#: ../../en/m5ui/canvas.rst:86 16696c797628439cbc002ce61b1a769f +msgid "|toggle_flag.png|" +msgstr "" + +#: ../../en/refs/m5ui.canvas.ref:30 a7c38b28d4e64ce1b034f1620e964587 +msgid "toggle_flag.png" +msgstr "" + +#: ../../en/m5ui/canvas.rst:97 e1214171c1fb4e5bad1264c47d80a052 +msgid "Set the position of the canvas." +msgstr "设置画布的位置。" + +#: ../../en/m5ui/canvas.rst:105 24d0a0f269744db8ae1e1f9f35ca573e +msgid "|set_pos.png|" +msgstr "" + +#: ../../en/refs/m5ui.canvas.ref:22 f48a914f42dd4876a025e5d0ac4e7e4b +msgid "set_pos.png" +msgstr "" + +#: ../../en/m5ui/canvas.rst:115 99dacccb5c73455ca6e1dcc2c9eb0b7e +msgid "Set the x-coordinate of the canvas." +msgstr "设置画布的x坐标。" + +#: ../../en/m5ui/canvas.rst:122 bb23547ebfde4464b1aaa6467f3b219e +msgid "|set_x.png|" +msgstr "" + +#: ../../en/refs/m5ui.canvas.ref:28 f42acf29a88b4fc8b9c2a19f6057f05d +msgid "set_x.png" +msgstr "" + +#: ../../en/m5ui/canvas.rst:132 3fb22c2b72374abd97e5cbf59fee5641 +msgid "Set the y-coordinate of the canvas." +msgstr "设置画布的y坐标。" + +#: ../../en/m5ui/canvas.rst:139 420b53e3cfe74dbaad8936a31de12174 +msgid "|set_y.png|" +msgstr "" + +#: ../../en/refs/m5ui.canvas.ref:29 f9e88b57ffab4eec80c245e2da32779c +msgid "set_y.png" +msgstr "" + +#: ../../en/m5ui/canvas.rst:149 5132e96a62db46118740dbaf6709d176 +msgid "Align the canvas to another object." +msgstr "将画布对齐到另一个对象。" + +#: ../../en/m5ui/canvas.rst:151 6a6f8bf5d6354f0e9d71bb341d46f6ce +msgid "The object to align to." +msgstr "要对齐到的对象。" + +#: ../../en/m5ui/canvas.rst:152 46323fb938b14d6c8e0e034f5626a959 +msgid "The alignment type." +msgstr "对齐类型。" + +#: ../../en/m5ui/canvas.rst:153 796ccb88ee394c75b646500ef0ce196d +msgid "The x-offset from the aligned object." +msgstr "相对于对齐对象的x偏移量。" + +#: ../../en/m5ui/canvas.rst:154 7e378afcde86488cb8e8904fe267f05a +msgid "The y-offset from the aligned object." +msgstr "相对于对齐对象的y偏移量。" + +#: ../../en/m5ui/canvas.rst:159 534e20cd641f4da685a15cf14a7634f7 +msgid "|align_to.png|" +msgstr "" + +#: ../../en/refs/m5ui.canvas.ref:7 197ad269bf83476da5bcddd7d7924bbf +msgid "align_to.png" +msgstr "" + +#: ../../en/m5ui/canvas.rst:169 beec2221710b43c88a1a403857f79ed7 +msgid "Set the size of the canvas." +msgstr "设置画布的尺寸。" + +#: ../../en/m5ui/canvas.rst:177 fcd73a782043474ca45e1682a712a0b4 +msgid "|set_size.png|" +msgstr "" + +#: ../../en/refs/m5ui.canvas.ref:24 b32a1d49ed194ebca7a9e91da8693b63 +msgid "set_size.png" +msgstr "" + +#: ../../en/m5ui/canvas.rst:187 dbdb2cd038ac4a6baa775560ef305dfa +msgid "Set the width of the canvas." +msgstr "设置画布的宽度。" + +#: ../../en/m5ui/canvas.rst:194 fb08bb5104aa402ca07f588ea19c898c +msgid "|set_width.png|" +msgstr "" + +#: ../../en/refs/m5ui.canvas.ref:27 54a8833d057448c3a3a4b268e127f1f5 +msgid "set_width.png" +msgstr "" + +#: ../../en/m5ui/canvas.rst:204 be35e71aacc0430ab18cc865de106fca +msgid "Set the height of the canvas." +msgstr "设置画布的高度。" + +#: ../../en/m5ui/canvas.rst:211 406ff113bc34421983e50f02ae39bada +msgid "|set_height.png|" +msgstr "" + +#: ../../en/refs/m5ui.canvas.ref:21 4f4003a8a844477f8d4615d73d487875 +msgid "set_height.png" +msgstr "" + +#: ../../en/m5ui/canvas.rst:221 182279d114fe447c9bf301951f8b145e +msgid "Get the width of the canvas." +msgstr "获取画布的宽度。" + +#: ../../en/m5ui/canvas.rst 8f365ccaf9764d72822b57397fc3fdcf +#: m5ui.canvas.M5Canvas.get_px_color m5ui.canvas.M5Canvas.get_px_opa of +msgid "Return type" +msgstr "" + +#: ../../en/m5ui/canvas.rst:228 a7d4bfdef9204929b384fa7de0beda9f +msgid "|get_width.png|" +msgstr "" + +#: ../../en/refs/m5ui.canvas.ref:17 c59b04bc743242d49aa7d4e79622c13b +msgid "get_width.png" +msgstr "" + +#: ../../en/m5ui/canvas.rst:238 fc478b0f4e2348b998314c64633b5994 +msgid "Get the height of the canvas." +msgstr "获取画布的高度。" + +#: ../../en/m5ui/canvas.rst:245 d5a6813eafe5421a9a64a0fc6936758c +msgid "|get_height.png|" +msgstr "" + +#: ../../en/refs/m5ui.canvas.ref:14 9a3174d755704f588ac0223581784c20 +msgid "get_height.png" +msgstr "" + +#: 7fab5dc991e84b50a89a8e316e947d3d m5ui.canvas.M5Canvas.fill_bg:1 of +msgid "Fill the canvas background with the specified color and opacity." +msgstr "用指定的颜色和不透明度填充画布背景。" + +#: 40efd488316a46108a3ac849e25b79b5 m5ui.canvas.M5Canvas.fill_bg:3 of +msgid "The background color in hexadecimal format (e.g., 0xRRGGBB)." +msgstr "十六进制格式的背景颜色(例如,0xRRGGBB)。" + +#: ed0696cf8b3c4ec4b97dcb4dad61c745 m5ui.canvas.M5Canvas.fill_bg:8 of +msgid "|set_bg_color.png|" +msgstr "" + +#: ../../en/refs/m5ui.canvas.ref:18 f77bd34f3df347589742c724a1ead5d7 +msgid "set_bg_color.png" +msgstr "" + +#: d4a089ddd40f4bf4b75e1877e2c566e9 m5ui.canvas.M5Canvas.set_px:1 of +msgid "Set a pixel at (x, y) with the specified color and opacity." +msgstr "在(x, y)位置设置具有指定颜色和不透明度的像素。" + +#: 469791a26d94455ea523f452518a09e2 m5ui.canvas.M5Canvas.get_px_color:3 +#: m5ui.canvas.M5Canvas.get_px_opa:3 m5ui.canvas.M5Canvas.set_px:3 of +msgid "The x-coordinate of the pixel." +msgstr "像素的x坐标。" + +#: 62add6df366c460ba2dddca11b29a348 m5ui.canvas.M5Canvas.get_px_color:4 +#: m5ui.canvas.M5Canvas.get_px_opa:4 m5ui.canvas.M5Canvas.set_px:4 of +msgid "The y-coordinate of the pixel." +msgstr "像素的y坐标。" + +#: ea101cb59aff49a9ac58cfc8590b597e m5ui.canvas.M5Canvas.get_px_color:5 +#: m5ui.canvas.M5Canvas.set_px:5 of +msgid "The color of the pixel in hexadecimal format (e.g., 0xRRGGBB)." +msgstr "十六进制格式的像素颜色(例如,0xRRGGBB)。" + +#: 9401f158b632439c939930e470a3e4c9 d8e04c5e1c304d0c8ab63188ce4bcc14 +#: m5ui.canvas.M5Canvas.get_px_opa:5 m5ui.canvas.M5Canvas.set_px:6 of +msgid "The opacity of the pixel (0-255)." +msgstr "像素的不透明度(0-255)。" + +#: ef88dd7accff444cb7eecb7712131d6d m5ui.canvas.M5Canvas.set_px:10 of +msgid "|set_px.png|" +msgstr "" + +#: ../../en/refs/m5ui.canvas.ref:23 fd875fecc2b54c5aa8af61df2f681eb7 +msgid "set_px.png" +msgstr "" + +#: 9e543c9a46b04d8d9e71a8182ee06400 m5ui.canvas.M5Canvas.get_px_color:1 of +msgid "Get the color of the pixel at (x, y)." +msgstr "获取(x, y)位置像素的颜色。" + +#: 3473dd9e13964cce8c0f9162198145d8 m5ui.canvas.M5Canvas.get_px_color:10 of +msgid "|get_px_color.png|" +msgstr "" + +#: ../../en/refs/m5ui.canvas.ref:15 cb26c4ba210844788ed8354a959b2585 +msgid "get_px_color.png" +msgstr "" + +#: 0d8788b5db644200b9881fa3df31d8bf m5ui.canvas.M5Canvas.get_px_opa:1 of +msgid "Get the opacity of the pixel at (x, y)." +msgstr "获取(x, y)位置像素的不透明度。" + +#: 008f094d24264604a2ec0cde586b72a8 m5ui.canvas.M5Canvas.get_px_opa:10 of +msgid "|get_px_opa.png|" +msgstr "" + +#: ../../en/refs/m5ui.canvas.ref:16 11e6441313384dffb36a9c577ed65a7e +msgid "get_px_opa.png" +msgstr "" + +#: 98d44c2d00f24806a26c9841d1edd4f9 m5ui.canvas.M5Canvas.draw_arc:1 of +msgid "Draw an arc on the canvas." +msgstr "在画布上绘制弧形。" + +#: 870b39a4407d41ca8795139050031e3d m5ui.canvas.M5Canvas.draw_arc:3 of +msgid "The x-coordinate of the center of the arc." +msgstr "弧形中心的x坐标。" + +#: 54c112927eae4b0a944f5f186aa66905 m5ui.canvas.M5Canvas.draw_arc:4 of +msgid "The y-coordinate of the center of the arc." +msgstr "弧形中心的y坐标。" + +#: 1c35ae6014fc4ca3aa5b41afb4cd9ded m5ui.canvas.M5Canvas.draw_arc:5 of +msgid "The radius of the arc." +msgstr "弧形的半径。" + +#: 912f91c5ce354ecbaf3101a3605dfa9c m5ui.canvas.M5Canvas.draw_arc:6 of +msgid "The color of the arc in hexadecimal format (e.g., 0xRRGGBB)." +msgstr "十六进制格式的弧形颜色(例如,0xRRGGBB)。" + +#: 50de3a1078804bc5907f6804ceceed45 m5ui.canvas.M5Canvas.draw_arc:7 of +msgid "The opacity of the arc (0-255)." +msgstr "弧形的不透明度(0-255)。" + +#: 058731d820dd4f12a41feba250cb2a46 m5ui.canvas.M5Canvas.draw_arc:8 of +msgid "The width of the arc line." +msgstr "弧形线条的宽度。" + +#: 703bc537d3314bdaaadf1a8d94467f63 m5ui.canvas.M5Canvas.draw_arc:9 of +msgid "The starting angle of the arc in degrees." +msgstr "弧形的起始角度(以度为单位)。" + +#: 7acc802215eb40bebe5bfa4c9d5b7022 m5ui.canvas.M5Canvas.draw_arc:10 of +msgid "The ending angle of the arc in degrees." +msgstr "弧形的结束角度(以度为单位)。" + +#: dce97b716aee46a78a9df66edbe1a5db m5ui.canvas.M5Canvas.draw_arc:14 of +msgid "|draw_arc.png|" +msgstr "" + +#: ../../en/refs/m5ui.canvas.ref:8 aff4a838d3d64f67b9b6072a211af48b +msgid "draw_arc.png" +msgstr "" + +#: 979fd68c16dd4adcbaef0646c5894246 m5ui.canvas.M5Canvas.draw_rect:1 of +msgid "Draw a rectangle on the canvas." +msgstr "在画布上绘制矩形。" + +#: a275045d45004be99eba4d09e915a781 m5ui.canvas.M5Canvas.draw_rect:3 of +msgid "The x-coordinate of the rectangle." +msgstr "矩形的x坐标。" + +#: 36890900126143c5b13e0bce7a086590 m5ui.canvas.M5Canvas.draw_rect:4 of +msgid "The y-coordinate of the rectangle." +msgstr "矩形的y坐标。" + +#: f55d42564a8d4685933f45acbd1c3d78 m5ui.canvas.M5Canvas.draw_rect:5 of +msgid "The width of the rectangle." +msgstr "矩形的宽度。" + +#: 255ec456b1014a4d995e4372b60c80ea m5ui.canvas.M5Canvas.draw_rect:6 of +msgid "The height of the rectangle." +msgstr "矩形的高度。" + +#: 2e6cd63747e64ae9a3ebcefed1d9e790 m5ui.canvas.M5Canvas.draw_rect:7 of +msgid "The corner radius of the rectangle." +msgstr "矩形的圆角半径。" + +#: 129361a7120147d58f620188d8a7160f m5ui.canvas.M5Canvas.draw_rect:8 of +msgid "" +"The background color of the rectangle in hexadecimal format (e.g., " +"0xRRGGBB)." +msgstr "十六进制格式的矩形背景颜色(例如,0xRRGGBB)。" + +#: 8ef00d08ca5c401a85488d8042fa3a65 m5ui.canvas.M5Canvas.draw_rect:10 of +msgid "The border color of the rectangle in hexadecimal format (e.g., 0xRRGGBB)." +msgstr "十六进制格式的矩形边框颜色(例如,0xRRGGBB)。" + +#: 347148f4f0644ad3bdf83a3cce4755a3 m5ui.canvas.M5Canvas.draw_rect:11 of +msgid "The opacity of the border (0-255)." +msgstr "边框的不透明度(0-255)。" + +#: f6ffca0c3127491495a166e037ba56e3 m5ui.canvas.M5Canvas.draw_rect:12 of +msgid "The width of the border." +msgstr "边框的宽度。" + +#: 74a9d2a83c0141b28f46d68a880b7dae m5ui.canvas.M5Canvas.draw_rect:13 of +msgid "The side of the border to draw (e.g., lv.BORDER_SIDE.FULL)." +msgstr "要绘制的边框边(例如,lv.BORDER_SIDE.FULL)。" + +#: 7b840a66e7b148d0a9a23ffd528d21f2 m5ui.canvas.M5Canvas.draw_rect:14 of +msgid "The outline color of the rectangle in hexadecimal format (e.g., 0xRRGGBB)." +msgstr "" + +#: ece6de775d134b1c9469d0e41a45e0f6 m5ui.canvas.M5Canvas.draw_rect:15 of +msgid "The opacity of the outline (0-255)." +msgstr "" + +#: a4b7932be51546d0ae80060487ec142b m5ui.canvas.M5Canvas.draw_rect:16 of +msgid "The width of the outline." +msgstr "" + +#: ca396cceb3bf45c8aa743dd78c016723 m5ui.canvas.M5Canvas.draw_rect:17 of +msgid "The padding of the outline." +msgstr "" + +#: 37d345b70ccf4a8c9d67aec31886f62a m5ui.canvas.M5Canvas.draw_rect:18 of +msgid "The shadow color of the rectangle in hexadecimal format (e.g., 0xRRGGBB)." +msgstr "" + +#: 888026b534194555934d4b8c1b43ecdc m5ui.canvas.M5Canvas.draw_rect:19 of +msgid "The opacity of the shadow (0-255)." +msgstr "" + +#: a2eb74b4ecbe48d4880f115efcf74a9a m5ui.canvas.M5Canvas.draw_rect:20 of +msgid "The width of the shadow." +msgstr "" + +#: c9ab1a4171ab41fb808c2b8393b36bfd m5ui.canvas.M5Canvas.draw_rect:21 of +msgid "The horizontal offset of the shadow." +msgstr "" + +#: 111a4ad0857047f5957106ad819297b2 m5ui.canvas.M5Canvas.draw_rect:22 of +msgid "The vertical offset of the shadow." +msgstr "" + +#: 39bda1e17a724da0a83dc7a02ddf8693 m5ui.canvas.M5Canvas.draw_rect:23 of +msgid "The spread of the shadow." +msgstr "" + +#: 78f13e0eaed04248aea55db78018ba35 m5ui.canvas.M5Canvas.draw_rect:27 of +msgid "|draw_rect.png|" +msgstr "" + +#: ../../en/refs/m5ui.canvas.ref:12 2acbc9e499594c1ba62a2e71b5803a3d +msgid "draw_rect.png" +msgstr "" + +#: f8593441b2984e8ea9bb4a3d34799203 m5ui.canvas.M5Canvas.draw_image:1 of +msgid "Draw an image at the specified coordinates." +msgstr "在指定坐标绘制图像。" + +#: 5e14026c3142448d9a70b18951ecacef m5ui.canvas.M5Canvas.draw_image:3 of +msgid "The source of the image (e.g., a file path or an image object)." +msgstr "图像的源(例如,文件路径或图像对象)。" + +#: 4efe6237d94e48f9bf5713d32e290ecb m5ui.canvas.M5Canvas.draw_image:4 of +msgid "The x-coordinate where the image will be drawn." +msgstr "绘制图像的x坐标。" + +#: 61042c702b9048e286d92b9e6d6550d2 m5ui.canvas.M5Canvas.draw_image:5 of +msgid "The y-coordinate where the image will be drawn." +msgstr "绘制图像的y坐标。" + +#: 9d711cc54b0f4c9eb712f5dce27e2e93 m5ui.canvas.M5Canvas.draw_image:6 of +msgid "The rotation angle of the image in degrees." +msgstr "" + +#: a4401e020f644ac79f9de4531efa3229 m5ui.canvas.M5Canvas.draw_image:7 of +msgid "The horizontal scaling factor of the image." +msgstr "" + +#: 99421428198f476bb7ef585200fcb2ef m5ui.canvas.M5Canvas.draw_image:8 of +msgid "The vertical scaling factor of the image." +msgstr "" + +#: fa775224d70a4961b471625a7981c895 m5ui.canvas.M5Canvas.draw_image:9 of +msgid "The horizontal skew angle of the image in degrees." +msgstr "" + +#: 5c40b6ac8b6b42b58a138e488f459912 m5ui.canvas.M5Canvas.draw_image:10 of +msgid "The vertical skew angle of the image in degrees." +msgstr "" + +#: 88d82f7b7d19451fb87b8e283afffe61 m5ui.canvas.M5Canvas.draw_image:14 of +msgid "|draw_image.png|" +msgstr "" + +#: ../../en/refs/m5ui.canvas.ref:9 ac51c6519fd0469a812ee8a0a7c8a82f +msgid "draw_image.png" +msgstr "" + +#: bb180dc0fca14b59a761bdeabbaba2ec m5ui.canvas.M5Canvas.draw_line:1 of +msgid "Draw a line from (x1, y1) to (x2, y2)." +msgstr "从(x1, y1)到(x2, y2)绘制线条。" + +#: 489e98f54fb7465789da9e75dd2dc665 m5ui.canvas.M5Canvas.draw_line:3 of +msgid "The x-coordinate of the start point of the line." +msgstr "线条起点的x坐标。" + +#: 4208a77282634aa7802ff1f545e2ff92 m5ui.canvas.M5Canvas.draw_line:4 of +msgid "The y-coordinate of the start point of the line." +msgstr "线条起点的y坐标。" + +#: 247da3b0cb6f48599b4373e868b3fa7b m5ui.canvas.M5Canvas.draw_line:5 of +msgid "The x-coordinate of the end point of the line." +msgstr "线条终点的x坐标。" + +#: dc51d712da1540eb85523e027b058d52 m5ui.canvas.M5Canvas.draw_line:6 of +msgid "The y-coordinate of the end point of the line." +msgstr "线条终点的y坐标。" + +#: 6e2bc3884501457c8f8bc9730ccace6c m5ui.canvas.M5Canvas.draw_line:7 of +msgid "The color of the line in hexadecimal format (e.g., 0xRRGGBB)." +msgstr "" + +#: b53ee5d8ebf24651a22a1b62d30c3e37 m5ui.canvas.M5Canvas.draw_line:8 of +msgid "The opacity of the line (0-255)." +msgstr "" + +#: f755476911d04b6d9c187dcd691897d6 m5ui.canvas.M5Canvas.draw_line:9 of +msgid "The width of the line." +msgstr "" + +#: 9581bee11a4845218bf686076981f2b5 m5ui.canvas.M5Canvas.draw_line:13 of +msgid "|draw_line.png|" +msgstr "" + +#: ../../en/refs/m5ui.canvas.ref:11 1584906164fd458c8bb135a7b7c9be93 +msgid "draw_line.png" +msgstr "" + +#: 3adefe6b982b4cb3a826a4195efeafe3 m5ui.canvas.M5Canvas.draw_label:1 of +msgid "Draw a label with the specified text at the given coordinates." +msgstr "在给定坐标处绘制具有指定文本的标签。" + +#: e6b19c2b43dc489bbfb2c4795b8e6cc9 m5ui.canvas.M5Canvas.draw_label:3 of +msgid "The text to be displayed." +msgstr "要显示的文本。" + +#: 65e1771cc7d44e15a6f3f1bc57a1c33c m5ui.canvas.M5Canvas.draw_label:4 of +msgid "The x-coordinate where the label will be drawn." +msgstr "绘制标签的x坐标。" + +#: 08bf6ba7f17d486c9088f4452cd92a44 m5ui.canvas.M5Canvas.draw_label:5 of +msgid "The y-coordinate where the label will be drawn." +msgstr "绘制标签的y坐标。" + +#: f91be191a9ee4167b09c9de646a6af18 m5ui.canvas.M5Canvas.draw_label:6 of +msgid "The font to be used for the label (default is lv.font_montserrat_14)." +msgstr "用于标签的字体(默认为lv.font_montserrat_14)。" + +#: b7ccc0d8aeed46d2ac22895e13ea0e40 m5ui.canvas.M5Canvas.draw_label:7 of +msgid "The color of the text in hexadecimal format (e.g., 0xRRGGBB)." +msgstr "" + +#: ed7fa23dbfa243a28a83d5ad5981a037 m5ui.canvas.M5Canvas.draw_label:8 of +msgid "The opacity of the text (0-255)." +msgstr "" + +#: 356ad1e49bf144c386bda0b6c47677e1 m5ui.canvas.M5Canvas.draw_label:12 of +msgid "|draw_label.png|" +msgstr "" + +#: ../../en/refs/m5ui.canvas.ref:10 adc76eeec45a464dafb13dd7dd526bb0 +msgid "draw_label.png" +msgstr "" + +#: 453d62726ba445a99f7785bd9e568b48 m5ui.canvas.M5Canvas.draw_triangle:1 of +msgid "Draw a triangle with the specified vertices." +msgstr "绘制具有指定顶点的三角形。" + +#: 9a7a0ea1dacc46ee8876edc902ec64a5 m5ui.canvas.M5Canvas.draw_triangle:3 of +msgid "The x-coordinate of the first vertex." +msgstr "第一个顶点的x坐标。" + +#: 071c263246b246e58b05b1d3a0429b9e m5ui.canvas.M5Canvas.draw_triangle:4 of +msgid "The y-coordinate of the first vertex." +msgstr "第一个顶点的y坐标。" + +#: e11153f64d3d4d08b05e96660a987165 m5ui.canvas.M5Canvas.draw_triangle:5 of +msgid "The x-coordinate of the second vertex." +msgstr "第二个顶点的x坐标。" + +#: 831bb23cbb7a431cbbf4084022ff7102 m5ui.canvas.M5Canvas.draw_triangle:6 of +msgid "The y-coordinate of the second vertex." +msgstr "第二个顶点的y坐标。" + +#: 4339fc24bd864389a1c52f71889b04c5 m5ui.canvas.M5Canvas.draw_triangle:7 of +msgid "The x-coordinate of the third vertex." +msgstr "" + +#: 9c57bb80b39947d6be62dfc7654c26b6 m5ui.canvas.M5Canvas.draw_triangle:8 of +msgid "The y-coordinate of the third vertex." +msgstr "" + +#: 2ba259696c374fdb9ed4ffba2ae2c71c m5ui.canvas.M5Canvas.draw_triangle:9 of +msgid "" +"The background color of the triangle in hexadecimal format (e.g., " +"0xRRGGBB)." +msgstr "" + +#: 1d77c2eef7c34a96aff6a00e0b84c1e6 m5ui.canvas.M5Canvas.draw_triangle:10 of +msgid "The opacity of the triangle (0-255)." +msgstr "" + +#: 3a7940d0372244aa8cfc053bbac61866 m5ui.canvas.M5Canvas.draw_triangle:14 of +msgid "|draw_triangle.png|" +msgstr "" + +#: ../../en/refs/m5ui.canvas.ref:13 b8d6c5fc1c39434baddba7b10c23ae8d +msgid "draw_triangle.png" +msgstr "" + diff --git a/examples/m5ui/canvas/cores3_canvas_basic_example.m5f2 b/examples/m5ui/canvas/cores3_canvas_basic_example.m5f2 new file mode 100644 index 00000000..a332e684 --- /dev/null +++ b/examples/m5ui/canvas/cores3_canvas_basic_example.m5f2 @@ -0,0 +1 @@ +{"version":"V2.0","versionNumber":"V2.3.2","type":"cores3","components":[{"name":"page0","type":"lvgl_page","layer":0,"screenId":"builtin","screenName":"","id":"zw`B`!SVA8Sc-X!_","createTime":1753609197574,"backgroundColor":"#ffffff","isLVGL":true,"isSelected":true},{"name":"canvas0","type":"lvgl_canvas","layer":1,"screenId":"builtin","screenName":"","id":"ycsdE1FLDbXlHQZt","createTime":1753609227655,"x":83,"y":53,"width":150,"height":70,"backgroundColor":"#c9c9c9","pageId":"zw`B`!SVA8Sc-X!_","isLVGL":true,"isSelected":false}],"resources":[{"hardware":["hardware_button","hardware_pin_button","imu","speaker","touch","als","mic","sdcard"]}],"units":[],"hats":[],"bases":[],"i2cs":[],"blockly":"truepage0canvas00042palette#6600cc255090canvas0FULL0020200palette#ff0000255palette#ff00002550palette#6600cc25500palette#6600cc2550000true","screen":[{"simulationName":"Built-in","type":"builtin","width":320,"height":240,"scale":0.77,"screenName":"","blockId":"","screenColorType":0,"rotation":1,"id":"builtin","createTime":1753609197573}],"logicWhenNum":0,"customList":[]} \ No newline at end of file diff --git a/examples/m5ui/canvas/cores3_canvas_basic_example.py b/examples/m5ui/canvas/cores3_canvas_basic_example.py new file mode 100644 index 00000000..8f9e6d0c --- /dev/null +++ b/examples/m5ui/canvas/cores3_canvas_basic_example.py @@ -0,0 +1,79 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import os, sys, io +import M5 +from M5 import * +import m5ui +import lvgl as lv + + +page0 = None +canvas0 = None + + +def setup(): + global page0, canvas0 + + M5.begin() + Widgets.setRotation(1) + m5ui.init() + page0 = m5ui.M5Page(bg_c=0xFFFFFF) + canvas0 = m5ui.M5Canvas( + x=83, + y=53, + w=150, + h=70, + color_format=lv.COLOR_FORMAT.ARGB8888, + bg_c=0xC9C9C9, + bg_opa=255, + parent=page0, + ) + + page0.screen_load() + canvas0.draw_arc(0, 0, 10, color=0x6600CC, opa=255, width=2, start_angle=0, end_angle=90) + canvas0.draw_rect( + 0, + 0, + 20, + 20, + 0, + bg_c=0xFF0000, + bg_opa=255, + border_c=0xFF0000, + border_opa=255, + border_w=0, + border_side=lv.BORDER_SIDE.FULL, + outline_c=0x6600CC, + outline_opa=255, + outline_w=0, + outline_pad=0, + shadow_c=0x6600CC, + shadow_opa=255, + shadow_w=0, + shadow_offset_x=0, + shadow_offset_y=0, + shadow_spread=0, + ) + canvas0.draw_line(0, 0, 50, 50, color=0x6600CC, opa=255, width=1) + + +def loop(): + global page0, canvas0 + M5.update() + + +if __name__ == "__main__": + try: + setup() + while True: + loop() + except (Exception, KeyboardInterrupt) as e: + try: + m5ui.deinit() + from utility import print_error_msg + + print_error_msg(e) + except ImportError: + print("please update to latest firmware") diff --git a/m5stack/libs/m5ui/__init__.py b/m5stack/libs/m5ui/__init__.py index 3e2f0baf..28213cc1 100644 --- a/m5stack/libs/m5ui/__init__.py +++ b/m5stack/libs/m5ui/__init__.py @@ -9,6 +9,7 @@ "M5Bar": "bar", "M5Button": "button", "M5Calendar": "calendar", + "M5Canvas": "canvas", "M5Checkbox": "checkbox", "M5Image": "image", "M5Label": "label", diff --git a/m5stack/libs/m5ui/canvas.py b/m5stack/libs/m5ui/canvas.py new file mode 100644 index 00000000..baa5f335 --- /dev/null +++ b/m5stack/libs/m5ui/canvas.py @@ -0,0 +1,475 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +from .base import M5Base +import lvgl as lv + + +class M5Canvas(lv.canvas): + """Create a canvas widget for drawing. + + :param int x: The x-coordinate of the canvas. + :param int y: The y-coordinate of the canvas. + :param int w: The width of the canvas. + :param int h: The height of the canvas. + :param lv.COLOR_FORMAT color_format: The color format of the canvas (default is ARGB8888). + :param int bg_c: The background color of the canvas in hexadecimal format (e.g., 0xRRGGBB). + :param int bg_opa: The opacity of the background (0-255). + :param lv.obj parent: The parent object of the canvas. If not specified, it will be + set to the active screen. + """ + + def __init__( + self, + x=0, + y=0, + w=50, + h=50, + color_format=lv.COLOR_FORMAT.ARGB8888, + bg_c=0xC9C9C9, + bg_opa=255, + parent=None, + ): + if parent is None: + parent = lv.screen_active() + super().__init__(parent) + + self._cbuf = lv.draw_buf_create(w, h, color_format, 0) + self.set_draw_buf(self._cbuf) + self.set_pos(x, y) + + self.fill_bg(bg_c, bg_opa) + + self._layer = lv.layer_t() + self.init_layer(self._layer) + + self._area = lv.area_t() + + def fill_bg(self, color: int, opa: int): + """Fill the canvas background with the specified color and opacity. + + :param int color: The background color in hexadecimal format (e.g., 0xRRGGBB). + :param int opa: The opacity of the background (0-255). + + UiFlow2 Code Block: + + |set_bg_color.png| + + MicroPython Code Block: + + .. code-block:: python + + canvas_0.fill_bg(0x001122, 255) + + """ + if isinstance(color, int): + color = lv.color_hex(color) + super().fill_bg(color, opa) + + def set_px(self, x: int, y: int, color: lv.color_t, opa: int): + """Set a pixel at (x, y) with the specified color and opacity. + + :param int x: The x-coordinate of the pixel. + :param int y: The y-coordinate of the pixel. + :param int color: The color of the pixel in hexadecimal format (e.g., 0xRRGGBB). + :param int opa: The opacity of the pixel (0-255). + + UiFlow2 Code Block: + + |set_px.png| + + MicroPython Code Block: + + .. code-block:: python + + canvas_0.set_px(100, 100, 0xFF0000, 255) + """ + if isinstance(color, int): + color = lv.color_hex(color) + super().set_px(x, y, color, opa) + + def get_px_color(self, x: int, y: int) -> int: + """Get the color of the pixel at (x, y). + + :param int x: The x-coordinate of the pixel. + :param int y: The y-coordinate of the pixel. + :return: The color of the pixel in hexadecimal format (e.g., 0xRRGGBB). + :rtype: int + + UiFlow2 Code Block: + + |get_px_color.png| + + MicroPython Code Block: + + .. code-block:: python + + color = canvas_0.get_px_color(100, 100) + print(hex(color)) # Prints the color in hexadecimal format + """ + c = super().get_px(x, y) + return c.red << 16 | c.green << 8 | c.blue + + def get_px_opa(self, x: int, y: int) -> int: + """Get the opacity of the pixel at (x, y). + + :param int x: The x-coordinate of the pixel. + :param int y: The y-coordinate of the pixel. + :return: The opacity of the pixel (0-255). + :rtype: int + + UiFlow2 Code Block: + + |get_px_opa.png| + + MicroPython Code Block: + + .. code-block:: python + """ + c = super().get_px(x, y) + return c.alpha + + def draw_arc( + self, + x: int, + y: int, + r: int, + color: int = 0x000000, + opa: int = lv.OPA.COVER, + width: int = 1, + start_angle: int = 0, + end_angle: int = 90, + ): + """Draw an arc on the canvas. + + :param int x: The x-coordinate of the center of the arc. + :param int y: The y-coordinate of the center of the arc. + :param int r: The radius of the arc. + :param int color: The color of the arc in hexadecimal format (e.g., 0xRRGGBB). + :param int opa: The opacity of the arc (0-255). + :param int width: The width of the arc line. + :param int start_angle: The starting angle of the arc in degrees. + :param int end_angle: The ending angle of the arc in degrees. + + UiFlow2 Code Block: + + |draw_arc.png| + + MicroPython Code Block: + + .. code-block:: python + + canvas_0.draw_arc(100, 100, 50, 0xFF0000, 255, 2, 0, 180) + """ + if not hasattr(self, "_arc_dsc"): + self._arc_dsc = lv.draw_arc_dsc_t() + self._arc_dsc.init() + + self._arc_dsc.color = lv.color_hex(color) + self._arc_dsc.opa = opa + self._arc_dsc.width = width + self._arc_dsc.center.x = x + self._arc_dsc.center.y = y + self._arc_dsc.radius = r + self._arc_dsc.start_angle = start_angle + self._arc_dsc.end_angle = end_angle + + lv.draw_arc(self._layer, self._arc_dsc) + self.finish_layer(self._layer) + + def draw_rect( + self, + x: int, + y: int, + w: int, + h: int, + radius: int = 0, + bg_c: int = 0xFFFFFF, + bg_opa: int = lv.OPA.COVER, + border_c: int = 0xFFFFFF, + border_opa: int = lv.OPA.COVER, + border_w: int = 0, + border_side: int = lv.BORDER_SIDE.FULL, + outline_c: int = 0x000000, + outline_opa: int = lv.OPA.COVER, + outline_w: int = 0, + outline_pad: int = 0, + shadow_c: int = 0x000000, + shadow_opa: int = lv.OPA.COVER, + shadow_w: int = 0, + shadow_offset_x: int = 0, + shadow_offset_y: int = 0, + shadow_spread: int = 0, + ): + """Draw a rectangle on the canvas. + + :param int x: The x-coordinate of the rectangle. + :param int y: The y-coordinate of the rectangle. + :param int w: The width of the rectangle. + :param int h: The height of the rectangle. + :param int radius: The corner radius of the rectangle. + :param int bg_c: The background color of the rectangle in hexadecimal format (e.g., 0xRRGGBB). + :param int bg_opa: The opacity of the background (0-255). + :param int border_c: The border color of the rectangle in hexadecimal format (e.g., 0xRRGGBB). + :param int border_opa: The opacity of the border (0-255). + :param int border_w: The width of the border. + :param int border_side: The side of the border to draw (e.g., lv.BORDER_SIDE.FULL). + :param int outline_c: The outline color of the rectangle in hexadecimal format (e.g., 0xRRGGBB). + :param int outline_opa: The opacity of the outline (0-255). + :param int outline_w: The width of the outline. + :param int outline_pad: The padding of the outline. + :param int shadow_c: The shadow color of the rectangle in hexadecimal format (e.g., 0xRRGGBB). + :param int shadow_opa: The opacity of the shadow (0-255). + :param int shadow_w: The width of the shadow. + :param int shadow_offset_x: The horizontal offset of the shadow. + :param int shadow_offset_y: The vertical offset of the shadow. + :param int shadow_spread: The spread of the shadow. + + UiFlow2 Code Block: + + |draw_rect.png| + + MicroPython Code Block: + + .. code-block:: python + + canvas_0.draw_rect(10, 10, 100, 50, radius=5, bg_c=0xFF0000, + bg_opa=255, border_c=0x00FF00, border_opa=255, + border_w=2, outline_c=0x0000FF, outline_opa=255, + outline_w=1, shadow_c=0x000000, shadow_opa=128, + shadow_w=5, shadow_offset_x=2, shadow_offset_y= 2, + shadow_spread=0) + """ + if not hasattr(self, "_rect_dsc"): + self._rect_dsc = lv.draw_rect_dsc_t() + + self._rect_dsc.init() + self._rect_dsc.radius = radius + self._rect_dsc.bg_color = lv.color_hex(bg_c) + self._rect_dsc.bg_opa = bg_opa + self._rect_dsc.border_color = lv.color_hex(border_c) + self._rect_dsc.border_width = border_w + self._rect_dsc.border_opa = border_opa + self._rect_dsc.border_side = border_side + self._rect_dsc.outline_color = lv.color_hex(outline_c) + self._rect_dsc.outline_width = outline_w + self._rect_dsc.outline_pad = outline_pad + self._rect_dsc.outline_opa = outline_opa + self._rect_dsc.shadow_color = lv.color_hex(shadow_c) + self._rect_dsc.shadow_width = shadow_w + self._rect_dsc.shadow_offset_x = shadow_offset_x + self._rect_dsc.shadow_offset_y = shadow_offset_y + self._rect_dsc.shadow_spread = shadow_spread + self._rect_dsc.shadow_opa = shadow_opa + + self._area.x1 = x + self._area.y1 = y + self._area.x2 = x + w - 1 + self._area.y2 = y + h - 1 + lv.draw_rect(self._layer, self._rect_dsc, self._area) + self.finish_layer(self._layer) + + def draw_image( + self, + img_src: str, + x: int = 0, + y: int = 0, + rotation: int = 0, + scale_x: float = 1.0, + scale_y: float = 1.0, + skew_x: int = 0, + skew_y: int = 0, + ): + """Draw an image at the specified coordinates. + + :param str img_src: The source of the image (e.g., a file path or an image object). + :param int x: The x-coordinate where the image will be drawn. + :param int y: The y-coordinate where the image will be drawn. + :param int rotation: The rotation angle of the image in degrees. + :param float scale_x: The horizontal scaling factor of the image. + :param float scale_y: The vertical scaling factor of the image. + :param int skew_x: The horizontal skew angle of the image in degrees. + :param int skew_y: The vertical skew angle of the image in degrees. + + UiFlow2 Code Block: + + |draw_image.png| + + MicroPython Code Block: + + .. code-block:: python + + canvas_0.draw_image("path/to/image.png", x=10, y=20, rotation=0, + scale_x=1.0, scale_y=1.0, skew_x=0, skew_y=0) + """ + if not hasattr(self, "_image_dsc"): + self._image_dsc = lv.draw_image_dsc_t() + if not hasattr(self, "_image_header"): + self._image_header = lv.image_header_t() + + img_src = "S:" + img_src + self._image_dsc.init() + self._image_dsc.src = img_src + self._image_dsc.rotation = rotation + self._image_dsc.scale_x = int(scale_x * 256) + self._image_dsc.scale_y = int(scale_y * 256) + self._image_dsc.skew_x = skew_x + self._image_dsc.skew_y = skew_y + + lv.image.decoder_get_info(img_src, self._image_header) + self._area.x1 = x + self._area.y1 = y + self._area.x2 = x + self._image_header.w - 1 + self._area.y2 = y + self._image_header.h - 1 + lv.draw_image(self._layer, self._image_dsc, self._area) + self.finish_layer(self._layer) + + def draw_line( + self, + x1: int, + y1: int, + x2: int, + y2: int, + color: int = 0x000000, + opa: int = lv.OPA.COVER, + width: int = 1, + ): + """Draw a line from (x1, y1) to (x2, y2). + + :param int x1: The x-coordinate of the start point of the line. + :param int y1: The y-coordinate of the start point of the line. + :param int x2: The x-coordinate of the end point of the line. + :param int y2: The y-coordinate of the end point of the line. + :param int color: The color of the line in hexadecimal format (e.g., 0xRRGGBB). + :param int opa: The opacity of the line (0-255). + :param int width: The width of the line. + + UiFlow2 Code Block: + + |draw_line.png| + + MicroPython Code Block: + + .. code-block:: python + + canvas_0.draw_line(10, 10, 100, 100, color=0xFF0000, opa=255, width=2) + """ + if not hasattr(self, "_line_dsc"): + self._line_dsc = lv.draw_line_dsc_t() + self._line_dsc.init() + self._line_dsc.color = lv.color_hex(color) + self._line_dsc.opa = opa + self._line_dsc.width = width + self._line_dsc.p1.x = x1 + self._line_dsc.p1.y = y1 + self._line_dsc.p2.x = x2 + self._line_dsc.p2.y = y2 + self._line_dsc.round_start = 1 + self._line_dsc.round_end = 1 + + lv.draw_line(self._layer, self._line_dsc) + self.finish_layer(self._layer) + + def draw_label( + self, + txt: str, + x: int = 0, + y: int = 0, + font=lv.font_montserrat_14, + color=0x000000, + opa=lv.OPA.COVER, + ): + """Draw a label with the specified text at the given coordinates. + + :param str txt: The text to be displayed. + :param int x: The x-coordinate where the label will be drawn. + :param int y: The y-coordinate where the label will be drawn. + :param font: The font to be used for the label (default is lv.font_montserrat_14). + :param int color: The color of the text in hexadecimal format (e.g., 0xRRGGBB). + :param int opa: The opacity of the text (0-255). + + UiFlow2 Code Block: + + |draw_label.png| + + MicroPython Code Block: + + .. code-block:: python + + canvas_0.draw_label("Hello, World!", x=10, y=20, font=lv.font_montserrat_14, + color=0xFFFFFF, opa=255) + """ + if not hasattr(self, "_label_dsc"): + self._label_dsc = lv.draw_label_dsc_t() + self._label_dsc.init() + self._label_dsc.text = txt + self._label_dsc.font = font + self._label_dsc.color = lv.color_hex(color) + self._label_dsc.opa = opa + + text_size = lv.point_t() + lv.text_get_size(text_size, txt, font, 0, 0, lv.COORD.MAX, lv.TEXT_FLAG.NONE) + self._area.x1 = x + self._area.y1 = y + self._area.x2 = x + text_size.x - 1 + self._area.y2 = y + text_size.y - 1 + + lv.draw_label(self._layer, self._label_dsc, self._area) + self.finish_layer(self._layer) + + def draw_triangle( + self, + x1: int, + y1: int, + x2: int, + y2: int, + x3: int, + y3: int, + bg_c: int = 0xFFFFFF, + bg_opa: int = lv.OPA.COVER, + ): + """Draw a triangle with the specified vertices. + + :param int x1: The x-coordinate of the first vertex. + :param int y1: The y-coordinate of the first vertex. + :param int x2: The x-coordinate of the second vertex. + :param int y2: The y-coordinate of the second vertex. + :param int x3: The x-coordinate of the third vertex. + :param int y3: The y-coordinate of the third vertex. + :param int bg_c: The background color of the triangle in hexadecimal format (e.g., 0xRRGGBB). + :param int bg_opa: The opacity of the triangle (0-255). + + UiFlow2 Code Block: + + |draw_triangle.png| + + MicroPython Code Block: + + .. code-block:: python + + canvas_0.draw_triangle(10, 10, 50, 10, 30, 40, bg_c=0xFF0000, bg_opa=255) + """ + if not hasattr(self, "_triangle_dsc"): + self._triangle_dsc = lv.draw_triangle_dsc_t() + self._triangle_dsc.init() + + self._triangle_dsc.bg_color = lv.color_hex(bg_c) + self._triangle_dsc.bg_opa = bg_opa + self._triangle_dsc.p[0].x = x1 + self._triangle_dsc.p[0].y = y1 + self._triangle_dsc.p[1].x = x2 + self._triangle_dsc.p[1].y = y2 + self._triangle_dsc.p[2].x = x3 + self._triangle_dsc.p[2].y = y3 + + lv.draw_triangle(self._layer, self._triangle_dsc) + self.finish_layer(self._layer) + + def __getattr__(self, name): + if hasattr(M5Base, name): + method = getattr(M5Base, name) + bound_method = lambda *args, **kwargs: method(self, *args, **kwargs) + setattr(self, name, bound_method) + return bound_method + raise AttributeError(f"'{self.__class__.__name__}' object has no attribute '{name}'") diff --git a/m5stack/libs/m5ui/manifest.py b/m5stack/libs/m5ui/manifest.py index 305a980e..fe700491 100644 --- a/m5stack/libs/m5ui/manifest.py +++ b/m5stack/libs/m5ui/manifest.py @@ -11,6 +11,7 @@ "base.py", "button.py", "calendar.py", + "canvas.py", "checkbox.py", "image.py", "label.py", From 61149b99cca29e2a5523893379c8ac8da8409615 Mon Sep 17 00:00:00 2001 From: lbuque <1102390310@qq.com> Date: Tue, 22 Jul 2025 15:48:16 +0800 Subject: [PATCH 185/322] libs/iot_devices: Add SwitchC6 support. Signed-off-by: lbuque <1102390310@qq.com> --- docs/en/conf.py | 1 + docs/en/index.rst | 1 + docs/en/iot-devices/index.rst | 12 + docs/en/iot-devices/switchc6.rst | 57 +++ docs/en/refs/iot-devices.switchc6.ref | 19 + .../zh_CN/LC_MESSAGES/iot-devices/index.po | 37 ++ .../zh_CN/LC_MESSAGES/iot-devices/switchc6.po | 327 +++++++++++++ .../switchc6/cores3_switchc6_example.m5f2 | 1 + .../switchc6/cores3_switchc6_example.py | 223 +++++++++ m5stack/libs/iot_devices/manifest.py | 5 + m5stack/libs/iot_devices/switchc6.py | 433 ++++++++++++++++++ m5stack/libs/m5espnow/m5espnow.py | 10 +- m5stack/libs/manifest.py | 1 + 13 files changed, 1122 insertions(+), 5 deletions(-) create mode 100644 docs/en/iot-devices/index.rst create mode 100644 docs/en/iot-devices/switchc6.rst create mode 100644 docs/en/refs/iot-devices.switchc6.ref create mode 100644 docs/locales/zh_CN/LC_MESSAGES/iot-devices/index.po create mode 100644 docs/locales/zh_CN/LC_MESSAGES/iot-devices/switchc6.po create mode 100644 examples/iot-devices/switchc6/cores3_switchc6_example.m5f2 create mode 100644 examples/iot-devices/switchc6/cores3_switchc6_example.py create mode 100644 m5stack/libs/iot_devices/manifest.py create mode 100644 m5stack/libs/iot_devices/switchc6.py diff --git a/docs/en/conf.py b/docs/en/conf.py index e7a96041..88ba9d7e 100644 --- a/docs/en/conf.py +++ b/docs/en/conf.py @@ -60,6 +60,7 @@ "rui3", "m5audio2", "lvgl", + "_espnow", ] autodoc_default_options = { diff --git a/docs/en/index.rst b/docs/en/index.rst index 108f0e86..0ec0edc3 100644 --- a/docs/en/index.rst +++ b/docs/en/index.rst @@ -16,6 +16,7 @@ UiFlow2 documentation and references units/index.rst hats/index.rst base/index.rst + iot-devices/index.rst advanced/index.rst quick-reference/index.rst contribute/index.rst diff --git a/docs/en/iot-devices/index.rst b/docs/en/iot-devices/index.rst new file mode 100644 index 00000000..c6960b4f --- /dev/null +++ b/docs/en/iot-devices/index.rst @@ -0,0 +1,12 @@ +IoT Devices +============ + +There is some support for M5Stack IoT devices. You can use the following modules to control them. + +Modules +------- + +.. toctree:: + :maxdepth: 1 + + switchc6.rst diff --git a/docs/en/iot-devices/switchc6.rst b/docs/en/iot-devices/switchc6.rst new file mode 100644 index 00000000..61af9cef --- /dev/null +++ b/docs/en/iot-devices/switchc6.rst @@ -0,0 +1,57 @@ +SwitchC6 +======== + +.. module:: switchc6 + :synopsis: A module for controlling the SwitchC6 device + +.. include:: ../refs/iot-devices.switchc6.ref + +The SwitchC6 is a device that can be controlled using the M5Stack platform. This module provides functions to interact with the SwitchC6 device. + +UiFlow2 Example +--------------- + +SwitchC6 Control +^^^^^^^^^^^^^^^^ + +Open the |cores3_switchc6_example.m5f2| project in UiFlow2. + +This example demonstrates how to control the SwitchC6 device using UiFlow2. + +UiFlow2 Code Block: + + |cores3_switchc6_example.png| + +Example output: + + None + + +MicroPython Example +------------------- + +SwitchC6 Control +^^^^^^^^^^^^^^^^ + +This example demonstrates how to control the SwitchC6 device using MicroPython. + +MicroPython Code Block: + + .. literalinclude:: ../../../examples/iot-devices/switchc6/cores3_switchc6_example.py + :language: python + :linenos: + +Example output: + + None + + +**API** +------- + +SwitchC6Controller +^^^^^^^^^^^^^^^^^^^ + +.. autoclass:: iot_devices.switchc6.SwitchC6Controller + :members: + :member-order: bysource diff --git a/docs/en/refs/iot-devices.switchc6.ref b/docs/en/refs/iot-devices.switchc6.ref new file mode 100644 index 00000000..6dcaf30e --- /dev/null +++ b/docs/en/refs/iot-devices.switchc6.ref @@ -0,0 +1,19 @@ + +.. |event.png| image:: https://static-cdn.m5stack.com/mpy_docs/iot_devices/switchc6/event.png +.. |get_capacitor_voltage.png| image:: https://static-cdn.m5stack.com/mpy_docs/iot_devices/switchc6/get_capacitor_voltage.png +.. |get_firmware_version.png| image:: https://static-cdn.m5stack.com/mpy_docs/iot_devices/switchc6/get_firmware_version.png +.. |get_switch_status.png| image:: https://static-cdn.m5stack.com/mpy_docs/iot_devices/switchc6/get_switch_status.png +.. |init.png| image:: https://static-cdn.m5stack.com/mpy_docs/iot_devices/switchc6/init.png +.. |set_switch.png| image:: https://static-cdn.m5stack.com/mpy_docs/iot_devices/switchc6/set_switch.png +.. |toggle_switch.png| image:: https://static-cdn.m5stack.com/mpy_docs/iot_devices/switchc6/toggle.png + +.. |cores3_switchc6_example.png| image:: https://static-cdn.m5stack.com/mpy_docs/iot_devices/switchc6/cores3_switchc6_example.png + +.. |cores3_switchc6_example.m5f2| raw:: html + + + cores3_switchc6_example.m5f2 + diff --git a/docs/locales/zh_CN/LC_MESSAGES/iot-devices/index.po b/docs/locales/zh_CN/LC_MESSAGES/iot-devices/index.po new file mode 100644 index 00000000..f863e146 --- /dev/null +++ b/docs/locales/zh_CN/LC_MESSAGES/iot-devices/index.po @@ -0,0 +1,37 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2016 - 2025 M5Stack Technology Co., Ltd +# This file is distributed under the same license as the UIFlow2 Programming +# Guide package. +# FIRST AUTHOR , 2025. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: UIFlow2 Programming Guide \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-07-26 23:25+0800\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: zh_CN\n" +"Language-Team: zh_CN \n" +"Plural-Forms: nplurals=1; plural=0;\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.17.0\n" + +#: ../../en/iot-devices/index.rst:2 53d82994fd97409ca99db26bd0815b6b +msgid "IoT Devices" +msgstr "" + +#: ../../en/iot-devices/index.rst:4 65453bf4be6f40fe894241f4e09bc321 +msgid "" +"There is some support for M5Stack IoT devices. You can use the following " +"modules to control them." +msgstr "" +"支持一些M5Stack物联网设备。您可以使用以下模块来控制它们。" + +#: ../../en/iot-devices/index.rst:7 0393e339983241d5bb9915982043cb93 +msgid "Modules" +msgstr "" + diff --git a/docs/locales/zh_CN/LC_MESSAGES/iot-devices/switchc6.po b/docs/locales/zh_CN/LC_MESSAGES/iot-devices/switchc6.po new file mode 100644 index 00000000..d077b06a --- /dev/null +++ b/docs/locales/zh_CN/LC_MESSAGES/iot-devices/switchc6.po @@ -0,0 +1,327 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2016 - 2025 M5Stack Technology Co., Ltd +# This file is distributed under the same license as the UIFlow2 Programming +# Guide package. +# FIRST AUTHOR , 2025. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: UIFlow2 Programming Guide \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-07-26 23:25+0800\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: zh_CN\n" +"Language-Team: zh_CN \n" +"Plural-Forms: nplurals=1; plural=0;\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.17.0\n" + +#: ../../en/iot-devices/switchc6.rst:2 fe54764279e54b5489ca2aab566c4977 +msgid "SwitchC6" +msgstr "" + +#: ../../en/iot-devices/switchc6.rst:9 a8659eada64d4b11b883b13bef452276 +msgid "" +"The SwitchC6 is a device that can be controlled using the M5Stack " +"platform. This module provides functions to interact with the SwitchC6 " +"device." +msgstr "" +"SwitchC6是一个可以通过M5Stack平台控制的设备。此模块提供与SwitchC6设备交互的功能。" + +#: ../../en/iot-devices/switchc6.rst:12 7184ce970883404e90158a02e428488d +msgid "UiFlow2 Example" +msgstr "UIFLOW2 应用示例" + +#: ../../en/iot-devices/switchc6.rst:15 ../../en/iot-devices/switchc6.rst:34 +#: a2959859885243218e8032dbbb742718 a5fb5829e05b4a26bcd5b78d8506c81f +msgid "SwitchC6 Control" +msgstr "SwitchC6控制" + +#: ../../en/iot-devices/switchc6.rst:17 25028d872b6b4ad7a0caa77fca7a23c6 +msgid "Open the |cores3_switchc6_example.m5f2| project in UiFlow2." +msgstr "在UiFlow2中打开|cores3_switchc6_example.m5f2|项目。" + +#: ../../en/iot-devices/switchc6.rst:19 337a535f63704126991b7664ae76640c +msgid "" +"This example demonstrates how to control the SwitchC6 device using " +"UiFlow2." +msgstr "" +"此示例演示如何使用UiFlow2控制SwitchC6设备。" + +#: ../../en/iot-devices/switchc6.rst:21 1ff55eb867e34d97b4341059958210fe +#: 31f88b46e8b84d60bbcba28db73318f1 463d0752f06f4712aa5857a2557877de +#: 5b571c4f4339489f81ab3a2e567d11f6 888bc242b21242698ee97df24610c7e0 +#: 938899417a2741d1a7b117572663d130 b2dab64a8d3b4444ad5d8a9743298954 +#: bfada81bddb942a18a766a611b42096d iot_devices.switchc6.SwitchC6Controller:8 +#: iot_devices.switchc6.SwitchC6Controller.get_capacitor_voltage:8 +#: iot_devices.switchc6.SwitchC6Controller.get_firmware_version:9 +#: iot_devices.switchc6.SwitchC6Controller.get_switch_status:8 +#: iot_devices.switchc6.SwitchC6Controller.set_callback:6 +#: iot_devices.switchc6.SwitchC6Controller.set_switch:7 +#: iot_devices.switchc6.SwitchC6Controller.toggle_switch:6 of +msgid "UiFlow2 Code Block:" +msgstr "UiFlow2代码块:" + +#: ../../en/iot-devices/switchc6.rst:23 53019f9fd8b34a559527178087bc04ec +msgid "|cores3_switchc6_example.png|" +msgstr "" + +#: ../../en/iot-devices/switchc6.rst:25 ../../en/iot-devices/switchc6.rst:44 +#: c2ebda8f144143cc9459d8d9447f7c5f d4a3332ca71a4939b2796d7c0c3aa136 +msgid "Example output:" +msgstr "示例输出:" + +#: ../../en/iot-devices/switchc6.rst:27 ../../en/iot-devices/switchc6.rst:46 +#: 45e287f362864084928878a015db23b5 66af327bdf3c45aea0ab744ba0f8232f +msgid "None" +msgstr "" + +#: ../../en/iot-devices/switchc6.rst:31 65e53998c77f442ab007113ffcbb7c38 +msgid "MicroPython Example" +msgstr "MicroPython 应用示例" + +#: ../../en/iot-devices/switchc6.rst:36 5292b5cf231c41bc961328b036dcf0f6 +msgid "" +"This example demonstrates how to control the SwitchC6 device using " +"MicroPython." +msgstr "" +"此示例演示如何使用MicroPython控制SwitchC6设备。" + +#: ../../en/iot-devices/switchc6.rst:38 22e25d767f7642bb82cb217febbb1c76 +#: 378959de60fd442899507078b49741bd 3b11e92a2b754c37a4f9608082932727 +#: 6d75d2a28abd4a3d96ede4ee99bfbdab a02d9565f63549c6bf2bcddcc8157fa1 +#: c8dabaf613414934b13ee3cdece3c1ea dcb28eefca0b46f6918889ea5c1c1789 +#: f9566e47f37b43b4a2004a871f78d1b5 iot_devices.switchc6.SwitchC6Controller:12 +#: iot_devices.switchc6.SwitchC6Controller.get_capacitor_voltage:12 +#: iot_devices.switchc6.SwitchC6Controller.get_firmware_version:13 +#: iot_devices.switchc6.SwitchC6Controller.get_switch_status:12 +#: iot_devices.switchc6.SwitchC6Controller.set_callback:10 +#: iot_devices.switchc6.SwitchC6Controller.set_switch:11 +#: iot_devices.switchc6.SwitchC6Controller.toggle_switch:10 of +msgid "MicroPython Code Block:" +msgstr "MicroPython 代码块:" + +#: ../../en/iot-devices/switchc6.rst:50 11b687be04cd4b698e225fd8c8363c66 +msgid "**API**" +msgstr "API参考" + +#: ../../en/iot-devices/switchc6.rst:53 010aad280b5149969b740ef25f7698af +msgid "SwitchC6Controller" +msgstr "" + +#: df34f0659edc4beb8e67371dfd18bfe1 iot_devices.switchc6.SwitchC6Controller:1 +#: of +msgid "Bases: :py:class:`object`" +msgstr "" + +#: 86a4b3f3f7f74be79b4d9caf061c752e iot_devices.switchc6.SwitchC6Controller:1 +#: of +msgid "Create a SwitchC6Controller instance to control M5Stack SwitchC6 devices." +msgstr "创建SwitchC6Controller实例来控制M5Stack SwitchC6设备。" + +#: ../../en/iot-devices/switchc6.rst 0dc3346e4f1040ed8cfa565694d1e097 +#: 3ab6ad6e8189479b99ebd1ca4fba819f 7b6762da210943f6b083ffb5ae318d7f +#: a3671bd7a88c47e092cbd861dd317ff8 ab140eb323294dc7a6ce5f09130658a0 +#: dcd239cc9e2c47d5adfca83dcdc099be fd0bb8dbc84e4703b15159d1a0388fda +#: iot_devices.switchc6.SwitchC6Controller.get_capacitor_voltage +#: iot_devices.switchc6.SwitchC6Controller.get_firmware_version +#: iot_devices.switchc6.SwitchC6Controller.get_switch_status +#: iot_devices.switchc6.SwitchC6Controller.set_callback +#: iot_devices.switchc6.SwitchC6Controller.set_switch +#: iot_devices.switchc6.SwitchC6Controller.toggle_switch of +msgid "Parameters" +msgstr "" + +#: 1219045777e84db5b6b7508c4571c0a8 iot_devices.switchc6.SwitchC6Controller:3 +#: of +msgid "List of target MAC addresses in \"XXXX-XXXX-XXXX\" format." +msgstr "\"XXXX-XXXX-XXXX\"格式的目标MAC地址列表。" + +#: 8de7b3d162a84fffb1aaf970d2d12828 iot_devices.switchc6.SwitchC6Controller:4 +#: of +msgid "" +"WiFi channel to use for communication (default is 0, which uses the " +"current channel)." +msgstr "" +"用于通信的WiFi信道(默认为0,使用当前信道)。" + +#: 077fe70d56d54c2a8c04c9926e2a4aa6 iot_devices.switchc6.SwitchC6Controller:5 +#: of +msgid "If True, print debug information (default is False)." +msgstr "如果为True,打印调试信息(默认为False)。" + +#: ../../en/iot-devices/switchc6.rst dbd31c4247fb4dcf9dfe219af15a431a +msgid "Raises" +msgstr "" + +#: 030ce84c25a848f6aad2d3e6657ccb51 iot_devices.switchc6.SwitchC6Controller:6 +#: of +msgid "If any MAC address in target_mac is not in the \"XXXX-XXXX-XXXX\" format." +msgstr "如果target_mac中的任何MAC地址不符合\"XXXX-XXXX-XXXX\"格式。" + +#: c9e99e5c549d4ce8b446050ca2ca4628 iot_devices.switchc6.SwitchC6Controller:10 +#: of +msgid "|init.png|" +msgstr "" + +#: ../../en/refs/iot-devices.switchc6.ref:6 41409e8434544f81b3a60a29205eb119 +msgid "init.png" +msgstr "" + +#: 58b2f17e93ff444a92aff51e8c0c85f4 +#: iot_devices.switchc6.SwitchC6Controller.set_switch:1 of +msgid "Set the switch state of the target device." +msgstr "设置目标设备的开关状态。" + +#: 3273464f057f478da7e917a23f397089 34c94b856df447a5816f6bb11f81c969 +#: 8a93321452a94fe89c747a602da01529 bf81d3fe2d7a40b49866c83a6df4e5f1 +#: ea3f43a6faff496c8986b80be14b15d4 +#: iot_devices.switchc6.SwitchC6Controller.get_capacitor_voltage:3 +#: iot_devices.switchc6.SwitchC6Controller.get_firmware_version:3 +#: iot_devices.switchc6.SwitchC6Controller.get_switch_status:3 +#: iot_devices.switchc6.SwitchC6Controller.set_switch:3 +#: iot_devices.switchc6.SwitchC6Controller.toggle_switch:3 of +msgid "Target MAC address in \"XXXX-XXXX-XXXX\" format." +msgstr "\"XXXX-XXXX-XXXX\"格式的目标MAC地址。" + +#: 83178c7c38bd4f73b993aef331b393b3 +#: iot_devices.switchc6.SwitchC6Controller.set_switch:4 of +msgid "True to turn on, False to turn off." +msgstr "True表示开启,False表示关闭。" + +#: 4548df054c814090a0b8078652b5e11f 689831b7928742d29502b94728a580db +#: 9200b47b0ddf480b8ee0a1a51b4b1f74 9ab91feb95a843cd90263be2d03b0558 +#: 9bd17e36bc8a45cf9d625c5ec2ad568e +#: iot_devices.switchc6.SwitchC6Controller.get_capacitor_voltage:4 +#: iot_devices.switchc6.SwitchC6Controller.get_firmware_version:4 +#: iot_devices.switchc6.SwitchC6Controller.get_switch_status:4 +#: iot_devices.switchc6.SwitchC6Controller.set_switch:5 +#: iot_devices.switchc6.SwitchC6Controller.toggle_switch:4 of +msgid "Timeout in milliseconds for waiting for a response (default is 5000)." +msgstr "等待响应的超时时间(毫秒)(默认为5000)。" + +#: 184d9829507e4c57bdbf558b5f0ae138 +#: iot_devices.switchc6.SwitchC6Controller.set_switch:9 of +msgid "|set_switch.png|" +msgstr "" + +#: ../../en/refs/iot-devices.switchc6.ref:7 39a17b3cb0d24f5f98cab4039741c821 +msgid "set_switch.png" +msgstr "" + +#: b610415585bd484082c8e35cbe9746c3 +#: iot_devices.switchc6.SwitchC6Controller.toggle_switch:1 of +msgid "Toggle the switch status of the target device." +msgstr "切换目标设备的开关状态。" + +#: 52bddc708bc444a8a60fd8071fdfb8aa +#: iot_devices.switchc6.SwitchC6Controller.toggle_switch:8 of +msgid "|toggle_switch.png|" +msgstr "" + +#: ../../en/refs/iot-devices.switchc6.ref:8 e64d34b3fb1f4945a3367db9abb81641 +msgid "toggle_switch.png" +msgstr "" + +#: 8c5465595f0948ee95ee164640285421 +#: iot_devices.switchc6.SwitchC6Controller.get_capacitor_voltage:1 of +msgid "Get the capacitor voltage of the target device." +msgstr "获取目标设备的电容电压。" + +#: 10518091a8cc452db29bb403d6108046 b549692ac87246bca67cc13687a26942 +#: dc7f3ed269b6448aae1b69d9a1fc9c82 +#: iot_devices.switchc6.SwitchC6Controller.get_capacitor_voltage +#: iot_devices.switchc6.SwitchC6Controller.get_firmware_version +#: iot_devices.switchc6.SwitchC6Controller.get_switch_status of +msgid "Returns" +msgstr "" + +#: 6d1f019979d04659b58ffdb4489abbe9 +#: iot_devices.switchc6.SwitchC6Controller.get_capacitor_voltage:5 of +msgid "The capacitor voltage as a float." +msgstr "电容电压(浮点数)。" + +#: 54831efd1fa1448c9e3ee1605926b8e0 6855fbaeb09747ab873700c09b9106cd +#: 8449fa4386124438a5976c3bb15dba3a dc76cb1b99c04282974952b62579f37a +#: iot_devices.switchc6.SwitchC6Controller.get_capacitor_voltage +#: iot_devices.switchc6.SwitchC6Controller.get_firmware_version +#: iot_devices.switchc6.SwitchC6Controller.get_switch_status +#: iot_devices.switchc6.SwitchC6Controller.set_callback of +msgid "Return type" +msgstr "" + +#: 98aa6817f72f4bdb9d7b3093d63134d8 +#: iot_devices.switchc6.SwitchC6Controller.get_capacitor_voltage:10 of +msgid "|get_capacitor_voltage.png|" +msgstr "" + +#: ../../en/refs/iot-devices.switchc6.ref:3 f119861789eb4715acfd32e58f929257 +msgid "get_capacitor_voltage.png" +msgstr "" + +#: c75439dbc60c42c5af773b45205b4f56 +#: iot_devices.switchc6.SwitchC6Controller.get_switch_status:1 of +msgid "Get the switch status of the target device." +msgstr "获取目标设备的开关状态。" + +#: 650699a96a3e478ca87db249ae792ad5 +#: iot_devices.switchc6.SwitchC6Controller.get_switch_status:5 of +msgid "True if the switch is ON, False if it is OFF." +msgstr "如果开关为开启状态返回True,关闭状态返回False。" + +#: 1d468920fe0d4743a169fc4fe0cbef6f +#: iot_devices.switchc6.SwitchC6Controller.get_switch_status:10 of +msgid "|get_switch_status.png|" +msgstr "" + +#: ../../en/refs/iot-devices.switchc6.ref:5 48e501a4230541b1864e3758a7b317d1 +msgid "get_switch_status.png" +msgstr "" + +#: 6d07d692178b4b02bbe74a5580f3e704 +#: iot_devices.switchc6.SwitchC6Controller.set_callback:1 of +msgid "Set a callback function for the specified trigger." +msgstr "为指定触发器设置回调函数。" + +#: a7a6abd7958e48e19a19c1db5bc7ddb0 +#: iot_devices.switchc6.SwitchC6Controller.set_callback:3 of +msgid "The callback function to be called when the trigger occurs." +msgstr "触发器发生时要调用的回调函数。" + +#: e14600e1dcb34de191bde269e9dff4c7 +#: iot_devices.switchc6.SwitchC6Controller.set_callback:4 of +msgid "The trigger type (0 for OFF, 1 for ON)." +msgstr "触发器类型(0表示关闭,1表示开启)。" + +#: 2bb8726e0e5545a39bb9f1e594756b23 +#: iot_devices.switchc6.SwitchC6Controller.set_callback:8 of +msgid "|event.png|" +msgstr "" + +#: ../../en/refs/iot-devices.switchc6.ref:2 6a2aeb5985d749d89384772165a22352 +msgid "event.png" +msgstr "" + +#: 0620e0400d6a41d7bb127c423287e2cb +#: iot_devices.switchc6.SwitchC6Controller.get_firmware_version:1 of +msgid "Get the firmware version of the target device." +msgstr "获取目标设备的固件版本。" + +#: 866d59d651b3448a942067a574f5839f +#: iot_devices.switchc6.SwitchC6Controller.get_firmware_version:6 of +msgid "The firmware version as a string." +msgstr "固件版本(字符串)。" + +#: 7d594796e61146ed9adbf476f76a5e57 +#: iot_devices.switchc6.SwitchC6Controller.get_firmware_version:11 of +msgid "|get_firmware_version.png|" +msgstr "" + +#: ../../en/refs/iot-devices.switchc6.ref:4 0c8456f08e8c4c23a4be0e697c4c26af +msgid "get_firmware_version.png" +msgstr "" + diff --git a/examples/iot-devices/switchc6/cores3_switchc6_example.m5f2 b/examples/iot-devices/switchc6/cores3_switchc6_example.m5f2 new file mode 100644 index 00000000..1c7b3ef9 --- /dev/null +++ b/examples/iot-devices/switchc6/cores3_switchc6_example.m5f2 @@ -0,0 +1 @@ +{"version":"V2.0","versionNumber":"V2.3.1","type":"core2","components":[{"name":"page0","type":"lvgl_page","layer":0,"screenId":"builtin","screenName":"","id":"ld04A5yGY5firrge","createTime":1753666466793,"backgroundColor":"#ffffff","isLVGL":true,"isSelected":true},{"name":"switch0","type":"lvgl_switch","layer":1,"screenId":"builtin","screenName":"","id":"xx5y93+nak03kHZ4","createTime":1753666492137,"x":76,"y":49,"width":154,"height":77,"knobColor":"#ffffff","backgroundColor":"#e7e3e7","checkedBackgroundColor":"#2196f3","pageId":"ld04A5yGY5firrge","isLVGL":true,"isSelected":false},{"name":"label0","type":"lvgl_label","layer":2,"screenId":"builtin","screenName":"","id":"iH%G-hDhu!i5cvht","createTime":1753666598255,"x":19,"y":151,"color":"#000000","backgroundColor":"#ffffff","bg_opacity":0,"text":"label0","font":"lv.font_montserrat_14","pageId":"ld04A5yGY5firrge","isLVGL":true,"isSelected":false,"width":43,"height":15},{"name":"label1","type":"lvgl_label","layer":3,"screenId":"builtin","screenName":"","id":"aqu72ZV&h`+rC$`5","createTime":1753666598255,"x":17,"y":173,"color":"#000000","backgroundColor":"#ffffff","bg_opacity":0,"text":"label1","font":"lv.font_montserrat_14","pageId":"ld04A5yGY5firrge","isLVGL":true,"isSelected":false,"width":39,"height":15},{"name":"label2","type":"lvgl_label","layer":4,"screenId":"builtin","screenName":"","id":"aeMnXhz_gA6-Yw%N","createTime":1753666598255,"x":17,"y":195,"color":"#000000","backgroundColor":"#ffffff","bg_opacity":0,"text":"label2","font":"lv.font_montserrat_14","pageId":"ld04A5yGY5firrge","isLVGL":true,"isSelected":false,"width":42,"height":15},{"name":"label3","type":"lvgl_label","layer":5,"screenId":"builtin","screenName":"","id":"zRcpP7ZoaseJ_Z-j","createTime":1753666598255,"x":18,"y":217,"color":"#000000","backgroundColor":"#ffffff","bg_opacity":0,"text":"label3","font":"lv.font_montserrat_14","pageId":"ld04A5yGY5firrge","isLVGL":true,"isSelected":false,"width":42,"height":15}],"resources":[{"hardware":["hardware_button","hardware_pin_button","imu","speaker","touch","mic","sdcard"]}],"units":[],"hats":[],"bases":[],"i2cs":[],"blockly":"switchc6_target_macswitchc6_onoffswitchc6_voltagetruepage0E4B3-2386-18B80Falselabel3hello M5'E4B3-2386-18B8'5000label1hello M5'E4B3-2386-18B8'5000label2hello M5'E4B3-2386-18B8'5000trueswitch0CHECKED'E4B3-2386-18B8'20000switchc6_target_macswitchc6_onoffswitchc6_voltagelabel0hello M5switchc6_target_maclabel1hello M5switchc6_onofflabel2hello M5switchc6_voltageswitch0UNCHECKED'E4B3-2386-18B8'False20001switchc6_target_macswitchc6_onoffswitchc6_voltagelabel0hello M5switchc6_target_maclabel1hello M5switchc6_onofflabel2hello M5switchc6_voltage","screen":[{"simulationName":"Built-in","type":"builtin","width":320,"height":240,"scale":0.77,"screenName":"","blockId":"","screenColorType":0,"rotation":1,"id":"builtin","createTime":1753666466792}],"logicWhenNum":0,"customList":[]} \ No newline at end of file diff --git a/examples/iot-devices/switchc6/cores3_switchc6_example.py b/examples/iot-devices/switchc6/cores3_switchc6_example.py new file mode 100644 index 00000000..998210b9 --- /dev/null +++ b/examples/iot-devices/switchc6/cores3_switchc6_example.py @@ -0,0 +1,223 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import os, sys, io +import M5 +from M5 import * +import m5ui +import lvgl as lv +import switchc6 + + +page0 = None +switch0 = None +label0 = None +label1 = None +label2 = None +label3 = None +switchc6_controller = None + + +switchc6_target_mac = None +switchc6_onoff = None +switchc6_voltage = None + + +def switch0_checked_event(event_struct): + global \ + page0, \ + switch0, \ + label0, \ + label1, \ + label2, \ + label3, \ + switchc6_controller, \ + switchc6_target_mac, \ + switchc6_onoff, \ + switchc6_voltage + switchc6_controller.toggle_switch("E4B3-2386-18B8", timeout=2000) + + +def switchc6_controller_off_event(args): + global \ + page0, \ + switch0, \ + label0, \ + label1, \ + label2, \ + label3, \ + switchc6_controller, \ + switchc6_target_mac, \ + switchc6_onoff, \ + switchc6_voltage + _, switchc6_target_mac, switchc6_onoff, switchc6_voltage = args + label0.set_text(str(switchc6_target_mac)) + label1.set_text(str(switchc6_onoff)) + label2.set_text(str(switchc6_voltage)) + + +def switch0_unchecked_event(event_struct): + global \ + page0, \ + switch0, \ + label0, \ + label1, \ + label2, \ + label3, \ + switchc6_controller, \ + switchc6_target_mac, \ + switchc6_onoff, \ + switchc6_voltage + switchc6_controller.set_switch("E4B3-2386-18B8", False, timeout=2000) + + +def switchc6_controller_on_event(args): + global \ + page0, \ + switch0, \ + label0, \ + label1, \ + label2, \ + label3, \ + switchc6_controller, \ + switchc6_target_mac, \ + switchc6_onoff, \ + switchc6_voltage + _, switchc6_target_mac, switchc6_onoff, switchc6_voltage = args + label0.set_text(str(switchc6_target_mac)) + label1.set_text(str(switchc6_onoff)) + label2.set_text(str(switchc6_voltage)) + + +def switch0_event_handler(event_struct): + global \ + page0, \ + switch0, \ + label0, \ + label1, \ + label2, \ + label3, \ + switchc6_controller, \ + switchc6_target_mac, \ + switchc6_onoff, \ + switchc6_voltage + event = event_struct.code + obj = event_struct.get_target_obj() + if event == lv.EVENT.VALUE_CHANGED: + if obj.has_state(lv.STATE.CHECKED): + switch0_checked_event(event_struct) + else: + switch0_unchecked_event(event_struct) + return + + +def setup(): + global \ + page0, \ + switch0, \ + label0, \ + label1, \ + label2, \ + label3, \ + switchc6_controller, \ + switchc6_target_mac, \ + switchc6_onoff, \ + switchc6_voltage + + M5.begin() + Widgets.setRotation(1) + m5ui.init() + page0 = m5ui.M5Page(bg_c=0xFFFFFF) + switch0 = m5ui.M5Switch( + x=76, + y=49, + w=154, + h=77, + bg_c=0xE7E3E7, + bg_c_checked=0x2196F3, + circle_c=0xFFFFFF, + parent=page0, + ) + label0 = m5ui.M5Label( + "label0", + x=19, + y=151, + text_c=0x000000, + bg_c=0xFFFFFF, + bg_opa=0, + font=lv.font_montserrat_14, + parent=page0, + ) + label1 = m5ui.M5Label( + "label1", + x=17, + y=173, + text_c=0x000000, + bg_c=0xFFFFFF, + bg_opa=0, + font=lv.font_montserrat_14, + parent=page0, + ) + label2 = m5ui.M5Label( + "label2", + x=17, + y=195, + text_c=0x000000, + bg_c=0xFFFFFF, + bg_opa=0, + font=lv.font_montserrat_14, + parent=page0, + ) + label3 = m5ui.M5Label( + "label3", + x=18, + y=217, + text_c=0x000000, + bg_c=0xFFFFFF, + bg_opa=0, + font=lv.font_montserrat_14, + parent=page0, + ) + + switch0.add_event_cb(switch0_event_handler, lv.EVENT.ALL, None) + + page0.screen_load() + switchc6_controller = switchc6.SwitchC6Controller( + ["E4B3-2386-18B8"], wifi_channel=0, verbose=False + ) + switchc6_controller.set_callback(switchc6_controller_off_event, switchc6_controller.OFF) + switchc6_controller.set_callback(switchc6_controller_on_event, switchc6_controller.ON) + label3.set_text(str(switchc6_controller.get_firmware_version("E4B3-2386-18B8", timeout=5000))) + label1.set_text(str(switchc6_controller.get_capacitor_voltage("E4B3-2386-18B8", timeout=5000))) + label2.set_text(str(switchc6_controller.get_switch_status("E4B3-2386-18B8", timeout=5000))) + + +def loop(): + global \ + page0, \ + switch0, \ + label0, \ + label1, \ + label2, \ + label3, \ + switchc6_controller, \ + switchc6_target_mac, \ + switchc6_onoff, \ + switchc6_voltage + M5.update() + + +if __name__ == "__main__": + try: + setup() + while True: + loop() + except (Exception, KeyboardInterrupt) as e: + try: + m5ui.deinit() + from utility import print_error_msg + + print_error_msg(e) + except ImportError: + print("please update to latest firmware") diff --git a/m5stack/libs/iot_devices/manifest.py b/m5stack/libs/iot_devices/manifest.py new file mode 100644 index 00000000..f313681b --- /dev/null +++ b/m5stack/libs/iot_devices/manifest.py @@ -0,0 +1,5 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +module("switchc6.py") diff --git a/m5stack/libs/iot_devices/switchc6.py b/m5stack/libs/iot_devices/switchc6.py new file mode 100644 index 00000000..191c9381 --- /dev/null +++ b/m5stack/libs/iot_devices/switchc6.py @@ -0,0 +1,433 @@ +import m5espnow +import time +import sys +import micropython + +if sys.platform != "esp32": + from typing import Literal + + +class SwitchC6Event: + EVENT_ONOFF = 0 + EVENT_STATUS = 1 + EVENT_VERSION = 2 + + def __init__( + self, + source_mac=None, + target_mac=None, + onoff=False, + voltage=0.0, + event_mac: bytes = None, + event_data: bytes = None, + event_type=EVENT_ONOFF, + is_wait: bool = False, + ): + if event_mac is not None and event_data is not None: + self.unpack(event_mac, event_data) + else: + self.source_mac = source_mac + self.target_mac = target_mac + self.onoff = onoff + self.voltage = voltage + self.is_wait = is_wait + self.event_type = event_type + self.version = "" + + def unpack(self, event_mac: bytes, event_data: bytes): + self.source_mac = self.convert_mac(event_mac) + print(f"Unpacking event from MAC: {self.source_mac}") + + event_data = event_data.decode("utf-8") + if event_data.endswith("V"): + # b"1122-AABB-CCGG;0;3.30V" + print("Unpacking ON/OFF event") + parts = event_data.split(";") + self.target_mac = parts[0] + self.onoff = int(parts[1]) == 1 + voltage_str = parts[2].rstrip("V") + self.voltage = float(voltage_str) + self.event_type = self.EVENT_ONOFF + else: + # b"1122-AABB-CCGG;1.0.0" + print("Unpacking VERSION event") + parts = event_data.split(";") + self.target_mac = parts[0] + self.version = parts[1] + self.event_type = self.EVENT_VERSION + self.is_wait = False + + @staticmethod + def convert_mac(mac: bytes) -> str: + hex_str = mac.hex().upper() + formatted = "-".join(hex_str[i : i + 4] for i in range(0, len(hex_str), 4)) + return formatted + + @staticmethod + def _is_valid_data_format(event_data: bytes) -> bool: + """验证数据是否以"XXXX-XXXX-XXXX"格式的MAC地址开始""" + try: + # 解码为字符串 + data_str = event_data.decode("utf-8") + + # 检查数据长度是否足够包含MAC地址(至少14个字符) + if len(data_str) < 14: + return False + + # 提取前14个字符作为MAC地址部分 + mac_part = data_str[:14] + + # 使用已有的MAC验证方法 + return SwitchC6Event._validate_single_mac(mac_part) + + except (UnicodeDecodeError, ValueError, IndexError): + return False + + @staticmethod + def _validate_mac_list(mac_list: list) -> bool: + """验证MAC地址列表中的每个MAC是否符合'XXXX-XXXX-XXXX'格式""" + if not isinstance(mac_list, list): + print("target_mac must be a list") + return False + + if len(mac_list) == 0: + print("target_mac list cannot be empty") + return False + + for i, mac in enumerate(mac_list): + if not isinstance(mac, str): + print(f"MAC address at index {i} must be a string, got {type(mac)}") + return False + + # 验证MAC地址格式:XXXX-XXXX-XXXX + if not SwitchC6Event._validate_single_mac(mac): + print( + f"Invalid MAC address format at index {i}: '{mac}'. Expected format: 'XXXX-XXXX-XXXX'" + ) + return False + + print(f"All {len(mac_list)} MAC addresses are valid") + return True + + @staticmethod + def _validate_single_mac(mac: str) -> bool: + """验证单个MAC地址是否符合'XXXX-XXXX-XXXX'格式""" + # 检查长度 + if len(mac) != 14: + return False + + # 检查连字符位置 + if mac[4] != "-" or mac[9] != "-": + return False + + # 检查每个十六进制段 + segments = [mac[0:4], mac[5:9], mac[10:14]] + + for segment in segments: + if len(segment) != 4: + return False + + # 检查每个字符是否为十六进制 + for char in segment: + if not ( + (char >= "0" and char <= "9") + or (char >= "A" and char <= "F") + or (char >= "a" and char <= "f") + ): + return False + + return True + + def __str__(self): + if self.event_type in [SwitchC6Event.EVENT_ONOFF, SwitchC6Event.EVENT_STATUS]: + return f"SwitchC6Event(source_mac={self.source_mac}, target_mac={self.target_mac}, onoff={self.onoff}, voltage={self.voltage})" + elif self.event_type == SwitchC6Event.EVENT_VERSION: + return f"SwitchC6Event(source_mac={self.source_mac}, target_mac={self.target_mac}, version={self.version})" + else: + return f"SwitchC6Event(source_mac={self.source_mac}, target_mac={self.target_mac})" + + +class SwitchC6Controller: + + """Create a SwitchC6Controller instance to control M5Stack SwitchC6 devices. + + :param target_mac: List of target MAC addresses in "XXXX-XXXX-XXXX" format. + :param wifi_channel: WiFi channel to use for communication (default is 0, which uses the current channel). + :param verbose: If True, print debug information (default is False). + :raises ValueError: If any MAC address in target_mac is not in the "XXXX-XXXX-XXXX" format. + + UiFlow2 Code Block: + + |init.png| + + MicroPython Code Block: + + .. code-block:: python + + import switchc6 + + controller = switchc6.SwitchC6Controller( + target_mac=["1122-AABB-CCDD", "2233-BBEE-DDEE"], + wifi_channel=0, + verbose=True + ) + """ + + OFF = 0 + ON = 1 + MAX = 2 + + def __init__(self, target_mac: list, wifi_channel: int = 0, verbose: bool = False): + # 验证target_mac列表中的MAC地址是否符合"XXXX-XXXX-XXXX"格式 + if not SwitchC6Event._validate_mac_list(target_mac): + raise ValueError( + "Invalid MAC address format in target_mac list. Expected format: 'XXXX-XXXX-XXXX'" + ) + + self.espnow = m5espnow.M5ESPNow(wifi_ch=wifi_channel) + self.wifi_channel = self.espnow.wlan_sta.config("channel") + self.target_mac = target_mac + self.espnow.set_irq_callback(self.espnow_recv_callback) + self.queue = SwitchC6Event( + source_mac=SwitchC6Event.convert_mac(self.espnow.get_mac()), + target_mac=SwitchC6Event.convert_mac(self.espnow.get_mac()), + onoff=False, + event_type=SwitchC6Event.EVENT_ONOFF, + is_wait=True, + ) + self._verbose = verbose + self._callback = [None for _ in range(self.MAX)] + + def espnow_recv_callback(self, espnow_obj): + event_mac, event_data = espnow_obj.recv_data() + micropython.schedule(self._data_handler, (event_mac, event_data)) + + def _data_handler(self, args): + event_mac, event_data = args + # 验证数据格式是否符合"1122-AABB-CCDD;0;3.30V" + if not event_mac or not event_data or not SwitchC6Event._is_valid_data_format(event_data): + # self._verbose and print(f"Invalid data format received: {event_data}") + return + + self._verbose and print(f"Received data from MAC: {event_mac}, Data: {event_data}") + + event = SwitchC6Event(event_mac=event_mac, event_data=event_data) + self._verbose and print(f"Received event: {event}") + + if event_mac == b"\xff\xff\xff\xff\xff\xff": + return + + wait_queue = self.queue + self._verbose and print(f"Waiting for event: {wait_queue}") + if wait_queue.event_type == SwitchC6Event.EVENT_ONOFF: + if ( + event.target_mac == wait_queue.source_mac + and event.source_mac == wait_queue.target_mac + and event.onoff == wait_queue.onoff + ): + wait_queue.onoff = event.onoff + wait_queue.voltage = event.voltage + wait_queue.is_wait = False + return + elif wait_queue.event_type == SwitchC6Event.EVENT_STATUS: + if ( + event.target_mac == wait_queue.source_mac + and event.source_mac == wait_queue.target_mac + ): + wait_queue.onoff = event.onoff + wait_queue.voltage = event.voltage + wait_queue.is_wait = False + return + elif wait_queue.event_type == SwitchC6Event.EVENT_VERSION: + if ( + event.target_mac == wait_queue.source_mac + and event.source_mac == wait_queue.target_mac + ): + wait_queue.version = event.version + wait_queue.is_wait = False + return + + if event.target_mac == "FFFF-FFFF-FFFF": + if self._callback[int(event.onoff)]: + micropython.schedule( + self._callback[int(event.onoff)], + (self, event.source_mac, event.onoff, event.voltage), + ) + + def _communicate(self, payload, wait_queue, timeout: int = 5000) -> None: + self._verbose and print("set_switch:", payload) + self._verbose and print("set_switch:", wait_queue) + self.espnow.set_irq_callback(None) + + # send broadcast message + self.espnow.broadcast_data("".join(payload).encode("utf-8")) + self._verbose and print("payload:", "".join(payload).encode("utf-8")) + + cur_time = time.ticks_ms() + # last_send_time = cur_time + # resend_interval = 10 # milliseconds + while time.ticks_diff(time.ticks_ms(), cur_time) < timeout: + # wait for response + event_mac, event_data = self.espnow.recv_data() + if event_mac and event_data: + self._data_handler((event_mac, event_data)) + if not wait_queue.is_wait: + break + + # resend if no response received + # if time.ticks_diff(time.ticks_ms(), last_send_time) > resend_interval: + # self._verbose and print("resend") + # self.espnow.broadcast_data("".join(payload).encode("utf-8")) + # last_send_time = time.ticks_ms() + self.espnow.broadcast_data("".join(payload).encode("utf-8")) + # time.sleep_ms(10) + if wait_queue.is_wait: + self._verbose and print("Timeout waiting for response") + self.espnow.set_irq_callback(self.espnow_recv_callback) + + def set_switch(self, target_mac: str, onoff: bool, timeout: int = 5000): + """Set the switch state of the target device. + + :param target_mac: Target MAC address in "XXXX-XXXX-XXXX" format. + :param onoff: True to turn on, False to turn off. + :param timeout: Timeout in milliseconds for waiting for a response (default is 5000). + + UiFlow2 Code Block: + + |set_switch.png| + + MicroPython Code Block: + + .. code-block:: python + + switchc6.set_switch("1122-AABB-CCDD", True, timeout=5000) + """ + payload = [ + target_mac.upper(), + "=1" if onoff else "=0", + ";", + f"ch={self.wifi_channel}", + ";", + ] + + self.queue.target_mac = target_mac.upper() + self.queue.onoff = onoff + self.queue.event_type = SwitchC6Event.EVENT_ONOFF + self.queue.is_wait = True + + self._communicate(payload, self.queue, timeout=timeout) + + def toggle_switch(self, target_mac: str, timeout: int = 5000): + """Toggle the switch status of the target device. + + :param target_mac: Target MAC address in "XXXX-XXXX-XXXX" format. + :param timeout: Timeout in milliseconds for waiting for a response (default is 5000). + + UiFlow2 Code Block: + + |toggle_switch.png| + + MicroPython Code Block: + + .. code-block:: python + + switchc6.toggle_switch("1122-AABB-CCDD", timeout=5000) + """ + current_state = self.get_switch_status(target_mac, timeout=timeout) + self.set_switch(target_mac, not current_state, timeout=timeout) + + def get_capacitor_voltage(self, target_mac: str, timeout: int = 5000) -> float: + """Get the capacitor voltage of the target device. + + :param target_mac: Target MAC address in "XXXX-XXXX-XXXX" format. + :param timeout: Timeout in milliseconds for waiting for a response (default is 5000). + :returns: The capacitor voltage as a float. + :rtype: float + + UiFlow2 Code Block: + + |get_capacitor_voltage.png| + + MicroPython Code Block: + + .. code-block:: python + + switchc6.get_capacitor_voltage("1122-AABB-CCDD", timeout=5000) + """ + payload = [target_mac.upper(), "=?", ";", f"ch={self.wifi_channel}", ";"] + + self.queue.target_mac = target_mac.upper() + self.queue.event_type = SwitchC6Event.EVENT_STATUS + self.queue.is_wait = True + + self._communicate(payload, self.queue, timeout=timeout) + return self.queue.voltage + + def get_switch_status(self, target_mac: str, timeout: int = 5000) -> bool: + """Get the switch status of the target device. + + :param target_mac: Target MAC address in "XXXX-XXXX-XXXX" format. + :param timeout: Timeout in milliseconds for waiting for a response (default is 5000). + :returns: True if the switch is ON, False if it is OFF. + :rtype: bool + + UiFlow2 Code Block: + + |get_switch_status.png| + + MicroPython Code Block: + + .. code-block:: python + + switchc6.get_switch_status("1122-AABB-CCDD", timeout=5000) + """ + payload = [target_mac.upper(), "=?", ";", f"ch={self.wifi_channel}", ";"] + self.queue.target_mac = target_mac.upper() + self.queue.event_type = SwitchC6Event.EVENT_STATUS + self.queue.is_wait = True + self._communicate(payload, self.queue, timeout=timeout) + return self.queue.onoff + + def set_callback(self, handler, trigger: Literal[0, 1]) -> None: + """Set a callback function for the specified trigger. + + :param handler: The callback function to be called when the trigger occurs. + :param trigger: The trigger type (0 for OFF, 1 for ON). + + UiFlow2 Code Block: + + |event.png| + + MicroPython Code Block: + + .. code-block:: python + + switchc6.set_callback(handler, trigger) + """ + self._callback[trigger] = handler + + def get_firmware_version(self, target_mac: str, timeout: int = 5000) -> str: + """Get the firmware version of the target device. + + :param target_mac: Target MAC address in "XXXX-XXXX-XXXX" format. + :param timeout: Timeout in milliseconds for waiting for a response (default is 5000). + + :returns: The firmware version as a string. + :rtype: str + + UiFlow2 Code Block: + + |get_firmware_version.png| + + MicroPython Code Block: + + .. code-block:: python + + switchc6.get_firmware_version("1122-AABB-CCDD", timeout=5000) + """ + payload = [target_mac.upper(), "=V", ";", f"ch={self.wifi_channel}", ";"] + self.queue.target_mac = target_mac.upper() + self.queue.event_type = SwitchC6Event.EVENT_VERSION + self.queue.is_wait = True + self._communicate(payload, self.queue, timeout=timeout) + return self.queue.version diff --git a/m5stack/libs/m5espnow/m5espnow.py b/m5stack/libs/m5espnow/m5espnow.py index be7014a8..453bbd9a 100644 --- a/m5stack/libs/m5espnow/m5espnow.py +++ b/m5stack/libs/m5espnow/m5espnow.py @@ -5,16 +5,16 @@ # # SPDX-License-Identifier: MIT -from _espnow import * +import _espnow import network import binascii import struct import time -class M5ESPNow(ESPNowBase): +class M5ESPNow(_espnow.ESPNowBase): # Static buffers for alloc free receipt of messages with ESPNow.irecv(). - _data = [None, bytearray(MAX_DATA_LEN)] + _data = [None, bytearray(_espnow.MAX_DATA_LEN)] _none_tuple = (None, None) AP = 0 STA = 1 @@ -42,7 +42,7 @@ def __init__(self, wifi_ch=0) -> None: while not self.active(): self.active(True) time.sleep(0.5) - self.peer_list = [None] * 20 + self.peer_list = [None for _ in range(20)] self.broadcast = False def set_add_peer(self, peer_mac, peer_id=1, ifidx=0, encrypt=False, lmk=None): @@ -69,7 +69,7 @@ def broadcast_data(self, msg) -> None: #! All devices will also receive messages sent to the broadcast MAC address msg = self.convert_to_bytes(msg) peer = b"\xff\xff\xff\xff\xff\xff" - if self.broadcast is False: + if not self.broadcast: self.add_peer(peer) self.broadcast = True self.send(peer, msg, False) diff --git a/m5stack/libs/manifest.py b/m5stack/libs/manifest.py index e068bc61..db91e3cb 100644 --- a/m5stack/libs/manifest.py +++ b/m5stack/libs/manifest.py @@ -26,3 +26,4 @@ module("label_plus.py") module("m5camera.py") module("pid.py") +include("iot_devices/manifest.py") From 89a3b6b3344ec8382174156fae9690b1987f4c2a Mon Sep 17 00:00:00 2001 From: lbuque <1102390310@qq.com> Date: Mon, 28 Jul 2025 16:53:23 +0800 Subject: [PATCH 186/322] version.text: Update to V2.3.2. Signed-off-by: lbuque <1102390310@qq.com> --- m5stack/version.txt | 2 +- third-party/version.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/m5stack/version.txt b/m5stack/version.txt index d8be0998..dfe2ef9c 100644 --- a/m5stack/version.txt +++ b/m5stack/version.txt @@ -1 +1 @@ -V2.3.1 \ No newline at end of file +V2.3.2 \ No newline at end of file diff --git a/third-party/version.txt b/third-party/version.txt index d8be0998..dfe2ef9c 100644 --- a/third-party/version.txt +++ b/third-party/version.txt @@ -1 +1 @@ -V2.3.1 \ No newline at end of file +V2.3.2 \ No newline at end of file From 4f10ac47b03f4138b8d0123e9eb63eab8e22598e Mon Sep 17 00:00:00 2001 From: tinyu Date: Tue, 5 Aug 2025 14:03:00 +0800 Subject: [PATCH 187/322] m5stack: Specify the esp_codec_dev component version. Signed-off-by: tinyu --- m5stack/main/idf_component.yml | 2 +- m5stack/main_esp32/CMakeLists.txt | 11 -------- m5stack/main_esp32/idf_component.yml | 6 ---- m5stack/main_esp32/linker.lf | 39 -------------------------- m5stack/main_esp32c3/CMakeLists.txt | 13 --------- m5stack/main_esp32c3/idf_component.yml | 5 ---- m5stack/main_esp32c3/linker.lf | 1 - m5stack/main_esp32c6/CMakeLists.txt | 14 --------- m5stack/main_esp32c6/idf_component.yml | 5 ---- m5stack/main_esp32c6/linker.lf | 1 - m5stack/main_esp32s2/CMakeLists.txt | 11 -------- m5stack/main_esp32s2/idf_component.yml | 6 ---- m5stack/main_esp32s2/linker.lf | 1 - m5stack/main_esp32s3/CMakeLists.txt | 18 ------------ m5stack/main_esp32s3/idf_component.yml | 7 ----- m5stack/main_esp32s3/linker.lf | 1 - third-party/main/idf_component.yml | 2 +- 17 files changed, 2 insertions(+), 141 deletions(-) delete mode 100644 m5stack/main_esp32/CMakeLists.txt delete mode 100644 m5stack/main_esp32/idf_component.yml delete mode 100644 m5stack/main_esp32/linker.lf delete mode 100644 m5stack/main_esp32c3/CMakeLists.txt delete mode 100644 m5stack/main_esp32c3/idf_component.yml delete mode 100644 m5stack/main_esp32c3/linker.lf delete mode 100644 m5stack/main_esp32c6/CMakeLists.txt delete mode 100644 m5stack/main_esp32c6/idf_component.yml delete mode 100644 m5stack/main_esp32c6/linker.lf delete mode 100644 m5stack/main_esp32s2/CMakeLists.txt delete mode 100644 m5stack/main_esp32s2/idf_component.yml delete mode 100644 m5stack/main_esp32s2/linker.lf delete mode 100644 m5stack/main_esp32s3/CMakeLists.txt delete mode 100644 m5stack/main_esp32s3/idf_component.yml delete mode 100644 m5stack/main_esp32s3/linker.lf diff --git a/m5stack/main/idf_component.yml b/m5stack/main/idf_component.yml index c7fdf6b1..9a1007cb 100644 --- a/m5stack/main/idf_component.yml +++ b/m5stack/main/idf_component.yml @@ -7,7 +7,7 @@ dependencies: version: "~1.0.0" idf: version: ">=5.2.0" - espressif/esp_codec_dev: "^1.3.2" + espressif/esp_codec_dev: "1.3.2" espressif/esp_hosted: rules: - if: "target in [esp32p4]" diff --git a/m5stack/main_esp32/CMakeLists.txt b/m5stack/main_esp32/CMakeLists.txt deleted file mode 100644 index d3b9823a..00000000 --- a/m5stack/main_esp32/CMakeLists.txt +++ /dev/null @@ -1,11 +0,0 @@ -# SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD -# -# SPDX-License-Identifier: MIT - -# Set location of base MicroPython directory. -if(NOT MICROPY_DIR) - get_filename_component(MICROPY_DIR ${PROJECT_DIR}/../micropython/ ABSOLUTE) -endif() - -# include(${MICROPY_PORT_DIR}/esp32_common.cmake) -include(${CMAKE_CURRENT_LIST_DIR}/../esp32_common.cmake) diff --git a/m5stack/main_esp32/idf_component.yml b/m5stack/main_esp32/idf_component.yml deleted file mode 100644 index c53a71f1..00000000 --- a/m5stack/main_esp32/idf_component.yml +++ /dev/null @@ -1,6 +0,0 @@ -## IDF Component Manager Manifest File -dependencies: - espressif/mdns: "~1.1.0" - espressif/esp_codec_dev: "^1.3.2" - idf: - version: ">=5.0.4" diff --git a/m5stack/main_esp32/linker.lf b/m5stack/main_esp32/linker.lf deleted file mode 100644 index e00cd63f..00000000 --- a/m5stack/main_esp32/linker.lf +++ /dev/null @@ -1,39 +0,0 @@ -# This fixes components/esp_ringbuf/linker.lf to allow us to put non-ISR ringbuf functions in flash. - -# Requires that both RINGBUF_PLACE_FUNCTIONS_INTO_FLASH and RINGBUF_PLACE_ISR_FUNCTIONS_INTO_FLASH -# are set to "n" (which is the default), otherwise this would result in duplicate section config -# when resolving the linker fragments. - -# The effect of this file is to leave the ISR functions in RAM (which we require), but apply a fixed -# version of RINGBUF_PLACE_FUNCTIONS_INTO_FLASH=y (leaving out prvGetFreeSize and prvGetCurMaxSizeByteBuf) -# See https://github.com/espressif/esp-idf/issues/13378 - -[mapping:esp_ringbuf_fix] -archive: libesp_ringbuf.a -entries: - # This is exactly the list of functions from RINGBUF_PLACE_FUNCTIONS_INTO_FLASH=y, - # but with prvGetFreeSize and prvGetCurMaxSizeByteBuf removed. - ringbuf: prvGetCurMaxSizeNoSplit (default) - ringbuf: prvGetCurMaxSizeAllowSplit (default) - ringbuf: prvInitializeNewRingbuffer (default) - ringbuf: prvReceiveGeneric (default) - ringbuf: vRingbufferDelete (default) - ringbuf: vRingbufferGetInfo (default) - ringbuf: vRingbufferReturnItem (default) - ringbuf: xRingbufferAddToQueueSetRead (default) - ringbuf: xRingbufferCanRead (default) - ringbuf: xRingbufferCreate (default) - ringbuf: xRingbufferCreateStatic (default) - ringbuf: xRingbufferCreateNoSplit (default) - ringbuf: xRingbufferReceive (default) - ringbuf: xRingbufferReceiveSplit (default) - ringbuf: xRingbufferReceiveUpTo (default) - ringbuf: xRingbufferRemoveFromQueueSetRead (default) - ringbuf: xRingbufferSend (default) - ringbuf: xRingbufferSendAcquire (default) - ringbuf: xRingbufferSendComplete (default) - ringbuf: xRingbufferPrintInfo (default) - ringbuf: xRingbufferGetMaxItemSize (default) - ringbuf: xRingbufferGetCurFreeSize (default) - - # Everything else will have the default rule already applied (i.e. noflash_text). diff --git a/m5stack/main_esp32c3/CMakeLists.txt b/m5stack/main_esp32c3/CMakeLists.txt deleted file mode 100644 index d5c6c4a5..00000000 --- a/m5stack/main_esp32c3/CMakeLists.txt +++ /dev/null @@ -1,13 +0,0 @@ -# SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD -# -# SPDX-License-Identifier: MIT - -# Set location of base MicroPython directory. -if(NOT MICROPY_DIR) - get_filename_component(MICROPY_DIR ${PROJECT_DIR}/../micropython/ ABSOLUTE) -endif() - -list(APPEND MICROPY_SOURCE_LIB ${MICROPY_DIR}/shared/runtime/gchelper_generic.c) - -# include(${MICROPY_PORT_DIR}/esp32_common.cmake) -include(${CMAKE_CURRENT_SOURCE_DIR}/../esp32_common.cmake) \ No newline at end of file diff --git a/m5stack/main_esp32c3/idf_component.yml b/m5stack/main_esp32c3/idf_component.yml deleted file mode 100644 index 5dabbc00..00000000 --- a/m5stack/main_esp32c3/idf_component.yml +++ /dev/null @@ -1,5 +0,0 @@ -## IDF Component Manager Manifest File -dependencies: - espressif/mdns: "~1.1.0" - idf: - version: ">=5.0.4" diff --git a/m5stack/main_esp32c3/linker.lf b/m5stack/main_esp32c3/linker.lf deleted file mode 100644 index 31c5b456..00000000 --- a/m5stack/main_esp32c3/linker.lf +++ /dev/null @@ -1 +0,0 @@ -# Empty linker fragment (no workaround required for C3, see main_esp32/linker.lf). diff --git a/m5stack/main_esp32c6/CMakeLists.txt b/m5stack/main_esp32c6/CMakeLists.txt deleted file mode 100644 index edae6cb5..00000000 --- a/m5stack/main_esp32c6/CMakeLists.txt +++ /dev/null @@ -1,14 +0,0 @@ -# SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD -# -# SPDX-License-Identifier: MIT - -# Set location of base MicroPython directory. -if(NOT MICROPY_DIR) - get_filename_component(MICROPY_DIR ${PROJECT_DIR}/../micropython/ ABSOLUTE) -endif() - -list(APPEND MICROPY_SOURCE_LIB ${MICROPY_DIR}/shared/runtime/gchelper_generic.c) -list(APPEND IDF_COMPONENTS riscv) - -# include(${MICROPY_PORT_DIR}/esp32_common.cmake) -include(${CMAKE_CURRENT_SOURCE_DIR}/../esp32_common.cmake) diff --git a/m5stack/main_esp32c6/idf_component.yml b/m5stack/main_esp32c6/idf_component.yml deleted file mode 100644 index 5bbab6d8..00000000 --- a/m5stack/main_esp32c6/idf_component.yml +++ /dev/null @@ -1,5 +0,0 @@ -## IDF Component Manager Manifest File -dependencies: - espressif/mdns: "~1.1.0" - idf: - version: ">=5.1.0" diff --git a/m5stack/main_esp32c6/linker.lf b/m5stack/main_esp32c6/linker.lf deleted file mode 100644 index cedabcf9..00000000 --- a/m5stack/main_esp32c6/linker.lf +++ /dev/null @@ -1 +0,0 @@ -# Empty linker fragment (no workaround required for C6, see main_esp32/linker.lf). diff --git a/m5stack/main_esp32s2/CMakeLists.txt b/m5stack/main_esp32s2/CMakeLists.txt deleted file mode 100644 index 6d792652..00000000 --- a/m5stack/main_esp32s2/CMakeLists.txt +++ /dev/null @@ -1,11 +0,0 @@ -# SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD -# -# SPDX-License-Identifier: MIT - -# Set location of base MicroPython directory. -if(NOT MICROPY_DIR) - get_filename_component(MICROPY_DIR ${PROJECT_DIR}/../micropython/ ABSOLUTE) -endif() - -# include(${MICROPY_PORT_DIR}/esp32_common.cmake) -include(${CMAKE_CURRENT_SOURCE_DIR}/../esp32_common.cmake) \ No newline at end of file diff --git a/m5stack/main_esp32s2/idf_component.yml b/m5stack/main_esp32s2/idf_component.yml deleted file mode 100644 index 05ab2f29..00000000 --- a/m5stack/main_esp32s2/idf_component.yml +++ /dev/null @@ -1,6 +0,0 @@ -## IDF Component Manager Manifest File -dependencies: - espressif/mdns: "~1.1.0" - espressif/esp_tinyusb: "~1.0.0" - idf: - version: ">=5.0.4" diff --git a/m5stack/main_esp32s2/linker.lf b/m5stack/main_esp32s2/linker.lf deleted file mode 100644 index 3c496fa8..00000000 --- a/m5stack/main_esp32s2/linker.lf +++ /dev/null @@ -1 +0,0 @@ -# Empty linker fragment (no workaround required for S2, see main_esp32/linker.lf). diff --git a/m5stack/main_esp32s3/CMakeLists.txt b/m5stack/main_esp32s3/CMakeLists.txt deleted file mode 100644 index f3b9c7c1..00000000 --- a/m5stack/main_esp32s3/CMakeLists.txt +++ /dev/null @@ -1,18 +0,0 @@ -# SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD -# -# SPDX-License-Identifier: MIT - -# Set location of base MicroPython directory. -if(NOT MICROPY_DIR) - get_filename_component(MICROPY_DIR ${PROJECT_DIR}/../micropython/ ABSOLUTE) -endif() - -# Set location of the ESP32 port directory. -if(NOT MICROPY_PORT_DIR) - get_filename_component(MICROPY_PORT_DIR ${CMAKE_CURRENT_LIST_DIR}/.. ABSOLUTE) -endif() - -set(MICROPY_PY_TINYUSB ON) - -# include(${MICROPY_PORT_DIR}/esp32_common.cmake) -include(${CMAKE_CURRENT_LIST_DIR}/../esp32_common.cmake) diff --git a/m5stack/main_esp32s3/idf_component.yml b/m5stack/main_esp32s3/idf_component.yml deleted file mode 100644 index df1233d7..00000000 --- a/m5stack/main_esp32s3/idf_component.yml +++ /dev/null @@ -1,7 +0,0 @@ -## IDF Component Manager Manifest File -dependencies: - espressif/mdns: "~1.1.0" - espressif/esp_tinyusb: "~1.0.0" - espressif/esp_codec_dev: "^1.3.2" - idf: - version: ">=5.0.4" diff --git a/m5stack/main_esp32s3/linker.lf b/m5stack/main_esp32s3/linker.lf deleted file mode 100644 index 81d27906..00000000 --- a/m5stack/main_esp32s3/linker.lf +++ /dev/null @@ -1 +0,0 @@ -# Empty linker fragment (no workaround required for S3, see main_esp32/linker.lf). diff --git a/third-party/main/idf_component.yml b/third-party/main/idf_component.yml index df1233d7..2d9bd5f9 100644 --- a/third-party/main/idf_component.yml +++ b/third-party/main/idf_component.yml @@ -2,6 +2,6 @@ dependencies: espressif/mdns: "~1.1.0" espressif/esp_tinyusb: "~1.0.0" - espressif/esp_codec_dev: "^1.3.2" + espressif/esp_codec_dev: "1.3.2" idf: version: ">=5.0.4" From a038e9d262ed28f1eab3088c30ab1503715e3542 Mon Sep 17 00:00:00 2001 From: tinyu Date: Tue, 22 Jul 2025 12:51:07 +0800 Subject: [PATCH 188/322] lib/m5ui: Add LVGL List widget and docs. Signed-off-by: tinyu --- docs/en/m5ui/index.rst | 1 + docs/en/m5ui/list.rst | 315 ++++++++++++++++++++ docs/en/refs/m5ui.list.ref | 26 ++ examples/m5ui/list/cores3_list_example.m5f2 | 1 + examples/m5ui/list/cores3_list_example.py | 56 ++++ m5stack/libs/m5ui/__init__.py | 1 + m5stack/libs/m5ui/list.py | 53 ++++ m5stack/libs/m5ui/manifest.py | 1 + 8 files changed, 454 insertions(+) create mode 100644 docs/en/m5ui/list.rst create mode 100644 docs/en/refs/m5ui.list.ref create mode 100644 examples/m5ui/list/cores3_list_example.m5f2 create mode 100644 examples/m5ui/list/cores3_list_example.py create mode 100644 m5stack/libs/m5ui/list.py diff --git a/docs/en/m5ui/index.rst b/docs/en/m5ui/index.rst index b302ed99..ab31442f 100644 --- a/docs/en/m5ui/index.rst +++ b/docs/en/m5ui/index.rst @@ -34,6 +34,7 @@ Classes page.rst arc.rst + list.rst bar.rst button.rst calendar.rst diff --git a/docs/en/m5ui/list.rst b/docs/en/m5ui/list.rst new file mode 100644 index 00000000..5497a724 --- /dev/null +++ b/docs/en/m5ui/list.rst @@ -0,0 +1,315 @@ +.. currentmodule:: m5ui + +M5List +======= + +.. include:: ../refs/m5ui.list.ref + +M5List is a widget that can be used to create lists in user interfaces. It is basically a rectangle with vertical layout to which Buttons and Text can be added. + + +UiFlow2 Example +--------------- + +list example +^^^^^^^^^^^^ + +Open the |cores3_list_example.m5f2| project in UiFlow2. + +This example demonstrates how to create a list that displays a series of items. + +UiFlow2 Code Block: + + |cores3_list_example.png| + +Example output: + + None + + +MicroPython Example +------------------- + +list example +^^^^^^^^^^^^ + +This example demonstrates how to create a list that displays a series of items. + +MicroPython Code Block: + + .. literalinclude:: ../../../examples/m5ui/list/cores3_list_example.py + :language: python + :linenos: + +Example output: + + None + + +**API** +------- + +M5List +^^^^^^^ + +.. autoclass:: m5ui.list.M5List + :members: + + .. py:method:: add_text(text:str) + + Add text to the list end. + + :param str text: The text to add. + + UiFlow2 Code Block: + + |add_text.png| + + MicroPython Code Block: + + .. code-block:: python + + text_0 = list_0.add_text("Item 1") + + .. py:method:: add_button(icon, text) + + Add a button to the list end. + + :param int icon: The icon to display on the button, `refer icon list `_ . + :param str text: The text to display on the button. + + UiFlow2 Code Block: + + |add_button.png| + + MicroPython Code Block: + + .. code-block:: python + + button_0 = list_0.add_button(lv.SYMBOL.BULLET, "Button0") + + .. py:method:: move_background() + + Move the background of the list to the end. + + UiFlow2 Code Block: + + |move_background.png| + + MicroPython Code Block: + + .. code-block:: python + + button_0.move_background() + text_0.move_background() + + + .. py:method:: move_foreground() + + Move the foreground of the list to the end. + + UiFlow2 Code Block: + + |move_foreground.png| + + MicroPython Code Block: + + .. code-block:: python + + button_0.move_foreground() + text_0.move_foreground() + + .. py:method:: move_to_index(index) + + Move the item at the specified index to the end of the list. + + UiFlow2 Code Block: + + |move_to_index.png| + + MicroPython Code Block: + + .. code-block:: python + + button_0.move_to_index(0) + text_0.move_to_index(1) + + .. py:method:: delete() + + Delete the item from the list. + + UiFlow2 Code Block: + + |delete.png| + + MicroPython Code Block: + + .. code-block:: python + + button_0.delete() + text_0.delete() + list_0.delete() + + .. py:method:: add_event_cb(handler, event, user_data) + + Add an event callback to the button. The callback will be called when the specified event occurs. + + :param function handler: The callback function to call. + :param int event: The event to listen for. + :param Any user_data: Optional user data to pass to the callback. + :return: None + + UiFlow2 Code Block: + + |event.png| + + MicroPython Code Block: + + .. code-block:: python + + def button0_event_handler(event_struct): + code = event_struct.code + obj = event_struct.get_target_obj() + if code == lv.EVENT.CLICKED: + print("Clicked: list1" + list_0.get_button_text(obj)) + + + def button1_event_handler(event_struct): + code = event_struct.code + obj = event_struct.get_target_obj() + if code == lv.EVENT.CLICKED: + print("Clicked: list1" + list_0.get_button_text(obj)) + + + button_0.add_event_cb(button0_event_handler, lv.EVENT.CLICKED, None) + button_1.add_event_cb(button1_event_handler, lv.EVENT.CLICKED, None) + + .. py:method:: set_flag(flag, value) + + Set a flag on the object. If ``value`` is True, the flag is added; if False, the flag is removed. + + :param int flag: The flag to set. + :param bool value: If True, the flag is added; if False, the flag is removed. + :return: None + + UiFlow2 Code Block: + + |set_flag.png| + + MicroPython Code Block: + + .. code-block:: python + + label_0.set_flag(lv.obj.FLAG.HIDDEN, True) + + .. py:method:: set_pos(x, y) + + Set the position of the label. + + :param int x: The x-coordinate of the label. + :param int y: The y-coordinate of the label. + :return: None + + UiFlow2 Code Block: + + |set_pos.png| + + MicroPython Code Block: + + .. code-block:: python + + label_0.set_pos(100, 100) + + + .. py:method:: set_x(x) + + Set the x-coordinate of the label. + + :param int x: The x-coordinate of the label. + :return: None + + UiFlow2 Code Block: + + |set_x.png| + + MicroPython Code Block: + + .. code-block:: python + + label_0.set_x(100) + + + .. py:method:: set_y(y) + + Set the y-coordinate of the label. + + :param int y: The y-coordinate of the label. + :return: None + + UiFlow2 Code Block: + + |set_y.png| + + MicroPython Code Block: + + .. code-block:: python + + label_0.set_y(100) + + + .. py:method:: set_size(width, height) + + Set the size of the label. + + :param int width: The width of the label. + :param int height: The height of the label. + :return: None + + UiFlow2 Code Block: + + |set_size.png| + + MicroPython Code Block: + + .. code-block:: python + + label_0.set_size(100, 50) + + + .. py:method:: set_width(width) + + Set the width of the label. + + :param int width: The width of the label. + :return: None + + UiFlow2 Code Block: + + |set_width.png| + + MicroPython Code Block: + + .. code-block:: python + + label_0.set_width(100) + + + .. py:method:: align_to(obj, align, x, y) + + Align the label to another object. + + :param lv.obj obj: The object to align to. + :param int align: The alignment type. + :param int x: The x-offset from the aligned object. + :param int y: The y-offset from the aligned object. + :return: None + + UiFlow2 Code Block: + + |align_to.png| + + MicroPython Code Block: + + .. code-block:: python + + label_0.align_to(page_0, lv.ALIGN.CENTER, 0, 0) diff --git a/docs/en/refs/m5ui.list.ref b/docs/en/refs/m5ui.list.ref new file mode 100644 index 00000000..c1ce32b1 --- /dev/null +++ b/docs/en/refs/m5ui.list.ref @@ -0,0 +1,26 @@ + +.. |align_to.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/label/align_to.png +.. |event.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/label/event.png +.. |set_flag.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/label/set_flag.png +.. |set_pos.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/label/set_pos.png +.. |set_size.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/label/set_size.png +.. |set_width.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/label/set_width.png +.. |set_x.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/label/set_x.png +.. |set_y.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/label/set_y.png +.. |add_text.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/label/add_text.png +.. |add_button.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/label/add_button.png +.. |move_foreground.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/label/move_foreground.png +.. |move_to_index.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/label/move_to_index.png +.. |move_background.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/label/move_background.png +.. |delete.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/label/delete.png + +.. |cores3_list_example.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/label/cores3_list_example.png + +.. |cores3_list_example.m5f2| raw:: html + + + cores3_list_example.m5f2 + diff --git a/examples/m5ui/list/cores3_list_example.m5f2 b/examples/m5ui/list/cores3_list_example.m5f2 new file mode 100644 index 00000000..fa9668dc --- /dev/null +++ b/examples/m5ui/list/cores3_list_example.m5f2 @@ -0,0 +1 @@ +{"version":"V2.0","versionNumber":"V2.3.0","type":"cores3","components":[{"name":"page0","type":"lvgl_page","layer":0,"screenId":"builtin","screenName":"","id":"zGUSgLk%P9BF_bji","createTime":1750906906496,"backgroundColor":"#ffffff","isLVGL":true,"isSelected":true},{"name":"label0","type":"lvgl_label","layer":1,"screenId":"builtin","screenName":"","id":"ktg+xZQJ^lvI#N=M","createTime":1750907034945,"x":60,"y":110,"color":"#000000","backgroundColor":"#ffffff","bg_opacity":0,"text":"It is a circularly scrolling text. ","font":"lv.font_montserrat_14","pageId":"zGUSgLk%P9BF_bji","isLVGL":true,"isSelected":false,"width":207,"height":15}],"resources":[{"hardware":["hardware_button","hardware_pin_button","imu","speaker","touch","als","mic","sdcard"]}],"units":[],"hats":[],"bases":[],"i2cs":[],"blockly":"truepage0label0SCROLL_CIRCULARlabel0150label0CENTERpage000true","screen":[{"simulationName":"Built-in","type":"builtin","width":320,"height":240,"scale":0.77,"screenName":"","blockId":"","screenColorType":0,"rotation":1,"id":"builtin","createTime":1750906899526}],"logicWhenNum":0,"customList":[]} \ No newline at end of file diff --git a/examples/m5ui/list/cores3_list_example.py b/examples/m5ui/list/cores3_list_example.py new file mode 100644 index 00000000..859d648f --- /dev/null +++ b/examples/m5ui/list/cores3_list_example.py @@ -0,0 +1,56 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import os, sys, io +import M5 +from M5 import * +import m5ui +import lvgl as lv + + +page0 = None +label0 = None + + +def setup(): + global page0, label0 + + M5.begin() + m5ui.init() + page0 = m5ui.M5Screen(bg_c=0xFFFFFF) + label0 = m5ui.M5Label( + "It is a circularly scrolling text. ", + x=60, + y=110, + text_c=0x000000, + bg_c=0xFFFFFF, + bg_opa=0, + font=lv.font_montserrat_14, + parent=page0, + ) + + page0.screen_load() + label0.set_long_mode(lv.label.LONG_MODE.SCROLL_CIRCULAR) + label0.set_width(150) + label0.align_to(page0, lv.ALIGN.CENTER, 0, 0) + + +def loop(): + global page0, label0 + M5.update() + + +if __name__ == "__main__": + try: + setup() + while True: + loop() + except (Exception, KeyboardInterrupt) as e: + try: + m5ui.deinit() + from utility import print_error_msg + + print_error_msg(e) + except ImportError: + print("please update to latest firmware") diff --git a/m5stack/libs/m5ui/__init__.py b/m5stack/libs/m5ui/__init__.py index 28213cc1..3569b775 100644 --- a/m5stack/libs/m5ui/__init__.py +++ b/m5stack/libs/m5ui/__init__.py @@ -14,6 +14,7 @@ "M5Image": "image", "M5Label": "label", "M5Line": "line", + "M5List": "list", "M5Page": "page", "M5Slider": "slider", "M5Switch": "switch", diff --git a/m5stack/libs/m5ui/list.py b/m5stack/libs/m5ui/list.py new file mode 100644 index 00000000..7ceadcf2 --- /dev/null +++ b/m5stack/libs/m5ui/list.py @@ -0,0 +1,53 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +from m5ui.base import M5Base +import lvgl as lv + + +class M5List(lv.list): + """Create a list object. + + :param int x: The x position of the list. + :param int y: The y position of the list. + :param int w: The width of the list. + :param int h: The height of the list. + :param lv.obj parent: The parent object to attach the list to. If not specified, the list will be attached to the default screen. + + UiFlow2 Code Block: + + None + + MicroPython Code Block: + + .. code-block:: python + + from m5ui import M5List + import lvgl as lv + + m5ui.init() + list_0 = M5List(x=20, y=10, w=100, h=220, parent=page0) + """ + + def __init__( + self, + x=0, + y=0, + w=0, + h=0, + parent=None, + ): + if parent is None: + parent = lv.screen_active() + super().__init__(parent) + self.set_pos(x, y) + self.set_size(w, h) + + def __getattr__(self, name): + if hasattr(M5Base, name): + method = getattr(M5Base, name) + bound_method = lambda *args, **kwargs: method(self, *args, **kwargs) + setattr(self, name, bound_method) + return bound_method + raise AttributeError(f"'{self.__class__.__name__}' object has no attribute '{name}'") diff --git a/m5stack/libs/m5ui/manifest.py b/m5stack/libs/m5ui/manifest.py index fe700491..09fbe3a3 100644 --- a/m5stack/libs/m5ui/manifest.py +++ b/m5stack/libs/m5ui/manifest.py @@ -16,6 +16,7 @@ "image.py", "label.py", "line.py", + "list.py", "page.py", "port.py", "slider.py", From f56c8d5aa0da0b61b6d32d8b3921cd115a509e91 Mon Sep 17 00:00:00 2001 From: tinyu Date: Tue, 5 Aug 2025 10:48:13 +0800 Subject: [PATCH 189/322] lib/m5ui: M5List supports add_text and add_button return M5Label/Button. Signed-off-by: tinyu --- docs/en/m5ui/button.rst | 1 + docs/en/m5ui/index.rst | 2 +- docs/en/m5ui/label.rst | 1 + docs/en/m5ui/list.rst | 216 +-------------- docs/en/refs/m5ui.list.ref | 27 +- docs/locales/zh_CN/LC_MESSAGES/m5ui/list.po | 285 ++++++++++++++++++++ examples/m5ui/list/cores3_list_example.m5f2 | 2 +- examples/m5ui/list/cores3_list_example.py | 99 +++++-- m5stack/libs/m5ui/list.py | 102 ++++++- 9 files changed, 496 insertions(+), 239 deletions(-) create mode 100644 docs/locales/zh_CN/LC_MESSAGES/m5ui/list.po diff --git a/docs/en/m5ui/button.rst b/docs/en/m5ui/button.rst index 9b10e209..5fdfd99e 100644 --- a/docs/en/m5ui/button.rst +++ b/docs/en/m5ui/button.rst @@ -1,4 +1,5 @@ .. currentmodule:: m5ui +.. _m5ui.M5Button: M5Button ======== diff --git a/docs/en/m5ui/index.rst b/docs/en/m5ui/index.rst index ab31442f..d38383b6 100644 --- a/docs/en/m5ui/index.rst +++ b/docs/en/m5ui/index.rst @@ -34,7 +34,6 @@ Classes page.rst arc.rst - list.rst bar.rst button.rst calendar.rst @@ -43,6 +42,7 @@ Classes image.rst label.rst line.rst + list.rst slider.rst switch.rst textarea.rst diff --git a/docs/en/m5ui/label.rst b/docs/en/m5ui/label.rst index 64cb2560..6a8ddb7d 100644 --- a/docs/en/m5ui/label.rst +++ b/docs/en/m5ui/label.rst @@ -1,4 +1,5 @@ .. currentmodule:: m5ui +.. _m5ui.M5Label: M5Label ======= diff --git a/docs/en/m5ui/list.rst b/docs/en/m5ui/list.rst index 5497a724..d0b4ffc0 100644 --- a/docs/en/m5ui/list.rst +++ b/docs/en/m5ui/list.rst @@ -55,46 +55,15 @@ M5List .. autoclass:: m5ui.list.M5List :members: - .. py:method:: add_text(text:str) - - Add text to the list end. - - :param str text: The text to add. - - UiFlow2 Code Block: - - |add_text.png| - - MicroPython Code Block: - - .. code-block:: python - - text_0 = list_0.add_text("Item 1") - - .. py:method:: add_button(icon, text) - - Add a button to the list end. - - :param int icon: The icon to display on the button, `refer icon list `_ . - :param str text: The text to display on the button. - - UiFlow2 Code Block: - - |add_button.png| - - MicroPython Code Block: - - .. code-block:: python - - button_0 = list_0.add_button(lv.SYMBOL.BULLET, "Button0") - .. py:method:: move_background() Move the background of the list to the end. UiFlow2 Code Block: - |move_background.png| + |button_move_to_index.png| + + |label_move_to_index.png| MicroPython Code Block: @@ -110,7 +79,9 @@ M5List UiFlow2 Code Block: - |move_foreground.png| + |button_move_to_index.png| + + |label_move_to_index.png| MicroPython Code Block: @@ -125,7 +96,9 @@ M5List UiFlow2 Code Block: - |move_to_index.png| + |button_move_to_index.png| + + |label_move_to_index.png| MicroPython Code Block: @@ -140,176 +113,13 @@ M5List UiFlow2 Code Block: - |delete.png| - - MicroPython Code Block: - - .. code-block:: python - - button_0.delete() - text_0.delete() - list_0.delete() - - .. py:method:: add_event_cb(handler, event, user_data) - - Add an event callback to the button. The callback will be called when the specified event occurs. - - :param function handler: The callback function to call. - :param int event: The event to listen for. - :param Any user_data: Optional user data to pass to the callback. - :return: None - - UiFlow2 Code Block: - - |event.png| - - MicroPython Code Block: - - .. code-block:: python - - def button0_event_handler(event_struct): - code = event_struct.code - obj = event_struct.get_target_obj() - if code == lv.EVENT.CLICKED: - print("Clicked: list1" + list_0.get_button_text(obj)) - - - def button1_event_handler(event_struct): - code = event_struct.code - obj = event_struct.get_target_obj() - if code == lv.EVENT.CLICKED: - print("Clicked: list1" + list_0.get_button_text(obj)) - - - button_0.add_event_cb(button0_event_handler, lv.EVENT.CLICKED, None) - button_1.add_event_cb(button1_event_handler, lv.EVENT.CLICKED, None) - - .. py:method:: set_flag(flag, value) - - Set a flag on the object. If ``value`` is True, the flag is added; if False, the flag is removed. - - :param int flag: The flag to set. - :param bool value: If True, the flag is added; if False, the flag is removed. - :return: None - - UiFlow2 Code Block: - - |set_flag.png| - - MicroPython Code Block: - - .. code-block:: python - - label_0.set_flag(lv.obj.FLAG.HIDDEN, True) - - .. py:method:: set_pos(x, y) - - Set the position of the label. - - :param int x: The x-coordinate of the label. - :param int y: The y-coordinate of the label. - :return: None - - UiFlow2 Code Block: + |button_delete.png| - |set_pos.png| + |label_delete.png| MicroPython Code Block: .. code-block:: python - label_0.set_pos(100, 100) - - - .. py:method:: set_x(x) - - Set the x-coordinate of the label. - - :param int x: The x-coordinate of the label. - :return: None - - UiFlow2 Code Block: - - |set_x.png| - - MicroPython Code Block: - - .. code-block:: python - - label_0.set_x(100) - - - .. py:method:: set_y(y) - - Set the y-coordinate of the label. - - :param int y: The y-coordinate of the label. - :return: None - - UiFlow2 Code Block: - - |set_y.png| - - MicroPython Code Block: - - .. code-block:: python - - label_0.set_y(100) - - - .. py:method:: set_size(width, height) - - Set the size of the label. - - :param int width: The width of the label. - :param int height: The height of the label. - :return: None - - UiFlow2 Code Block: - - |set_size.png| - - MicroPython Code Block: - - .. code-block:: python - - label_0.set_size(100, 50) - - - .. py:method:: set_width(width) - - Set the width of the label. - - :param int width: The width of the label. - :return: None - - UiFlow2 Code Block: - - |set_width.png| - - MicroPython Code Block: - - .. code-block:: python - - label_0.set_width(100) - - - .. py:method:: align_to(obj, align, x, y) - - Align the label to another object. - - :param lv.obj obj: The object to align to. - :param int align: The alignment type. - :param int x: The x-offset from the aligned object. - :param int y: The y-offset from the aligned object. - :return: None - - UiFlow2 Code Block: - - |align_to.png| - - MicroPython Code Block: - - .. code-block:: python - - label_0.align_to(page_0, lv.ALIGN.CENTER, 0, 0) + button_0.delete() + text_0.delete() \ No newline at end of file diff --git a/docs/en/refs/m5ui.list.ref b/docs/en/refs/m5ui.list.ref index c1ce32b1..ae8c0315 100644 --- a/docs/en/refs/m5ui.list.ref +++ b/docs/en/refs/m5ui.list.ref @@ -1,25 +1,20 @@ -.. |align_to.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/label/align_to.png -.. |event.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/label/event.png -.. |set_flag.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/label/set_flag.png -.. |set_pos.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/label/set_pos.png -.. |set_size.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/label/set_size.png -.. |set_width.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/label/set_width.png -.. |set_x.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/label/set_x.png -.. |set_y.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/label/set_y.png -.. |add_text.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/label/add_text.png -.. |add_button.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/label/add_button.png -.. |move_foreground.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/label/move_foreground.png -.. |move_to_index.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/label/move_to_index.png -.. |move_background.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/label/move_background.png -.. |delete.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/label/delete.png +.. |add_text.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/list/add_text.png +.. |add_text2.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/list/add_text2.png +.. |add_button.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/list/add_button.png +.. |add_button2.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/list/add_button2.png +.. |button_move_to_index.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/list/button_move_to.png +.. |label_move_to_index.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/list/label_move_to.png +.. |button_delete.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/list/button_delete.png +.. |label_delete.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/list/label_delete.png -.. |cores3_list_example.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/label/cores3_list_example.png + +.. |cores3_list_example.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/list/example.png .. |cores3_list_example.m5f2| raw:: html cores3_list_example.m5f2 diff --git a/docs/locales/zh_CN/LC_MESSAGES/m5ui/list.po b/docs/locales/zh_CN/LC_MESSAGES/m5ui/list.po new file mode 100644 index 00000000..11a1472b --- /dev/null +++ b/docs/locales/zh_CN/LC_MESSAGES/m5ui/list.po @@ -0,0 +1,285 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2016 - 2025 M5Stack Technology Co., Ltd +# This file is distributed under the same license as the UIFlow2 Programming +# Guide package. +# FIRST AUTHOR , 2025. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: UIFlow2 Programming Guide \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-08-04 11:13+0800\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: zh_CN\n" +"Language-Team: zh_CN \n" +"Plural-Forms: nplurals=1; plural=0;\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.16.0\n" + +#: ../../en/m5ui/list.rst:4 ../../en/m5ui/list.rst:53 +#: b38d28b891054b83bf1be37113263177 dc9f681fa2904cec91fa9f5773f117dc +msgid "M5List" +msgstr "" + +#: ../../en/m5ui/list.rst:8 d2ae8de227454dbbacc39ef10e7cc07b +msgid "" +"M5List is a widget that can be used to create lists in user interfaces. " +"It is basically a rectangle with vertical layout to which Buttons and " +"Text can be added." +msgstr "M5List 是一个可在用户界面中创建列表的控件。本质上它是一个具有垂直布局的矩形,可向其中添加按钮和文本。" + +#: ../../en/m5ui/list.rst:12 9d93af2b969349bba5700381828bd26f +msgid "UiFlow2 Example" +msgstr "UiFlow2 示例" + +#: ../../en/m5ui/list.rst:15 ../../en/m5ui/list.rst:34 +#: 14e420f5ef6e458cbfd6648ec4eddff6 eab239fc5a144ae58df4569aeb881220 +msgid "list example" +msgstr "列表示例" + +#: ../../en/m5ui/list.rst:17 266309c33e7e481e81c60d3ff6290d34 +msgid "Open the |cores3_list_example.m5f2| project in UiFlow2." +msgstr "" + +#: ../../en/m5ui/list.rst:19 ../../en/m5ui/list.rst:36 +#: b4db489f0c64435ea02f9cb9fbdfe5ca ff89bc3611f440beaf2e997989c79412 +msgid "" +"This example demonstrates how to create a list that displays a series of " +"items." +msgstr "本示例演示如何创建一个用于显示一系列条目的列表。" + +#: ../../en/m5ui/list.rst:21 ../../en/m5ui/list.rst:62 +#: ../../en/m5ui/list.rst:78 ../../en/m5ui/list.rst:93 +#: ../../en/m5ui/list.rst:108 1d68dba86e5549d5bc92e5d233cdddca +#: 54fc028b960044ee9e701a262c82c34e 62e11d48e10c4909ac140c3495257886 +#: 73e4d6c120424c45abcdf1d069faa852 7ec14ed5d8c84bef8fb555ff991cd00b +#: 87910c8749e34c63ab6b7451e81ad9bd b113fa96139b4f16ac1d0c5adf9d54b9 +#: m5ui.list.M5List:9 m5ui.list.M5List.add_button:14 +#: m5ui.list.M5List.add_text:12 of +msgid "UiFlow2 Code Block:" +msgstr "UiFlow2 代码块:" + +#: ../../en/m5ui/list.rst:23 f62061f2457d49e18fc4b1595d852db2 +msgid "|cores3_list_example.png|" +msgstr "" + +#: ../../en/refs/m5ui.list.ref:9 a90a74f44ee34a119414ada249758947 +msgid "cores3_list_example.png" +msgstr "" + +#: ../../en/m5ui/list.rst:25 ../../en/m5ui/list.rst:44 +#: 1eb3444b1e6444ed9b1f019f02467e43 39c0d9bf26eb4e46af2605b686ad7277 +msgid "Example output:" +msgstr "示例输出:" + +#: ../../en/m5ui/list.rst:27 ../../en/m5ui/list.rst:46 +#: 484ff2e836254c6092eb63423e03b690 49102d2d24d444598daa0acde7d14438 +#: 52ee58dfbdf8401e86ab3f03e6930beb m5ui.list.M5List:11 of +msgid "None" +msgstr "" + +#: ../../en/m5ui/list.rst:31 f74b3fa1d5bd4118b1577464ff5b4398 +msgid "MicroPython Example" +msgstr "MicroPython 示例" + +#: ../../en/m5ui/list.rst:38 ../../en/m5ui/list.rst:66 +#: ../../en/m5ui/list.rst:82 ../../en/m5ui/list.rst:97 +#: ../../en/m5ui/list.rst:112 536363d47eb94abf81a9ad58c2afea1e +#: 56aa7187d1a24e89818a56ab41a64716 7e3d3a25ab094cb496c4efe917d09a36 +#: 8e6c128ea739472c8da8a15d0d22b1e1 b838777af5734bf28381a4945d19728d +#: c88f5d96b5fb426e8c9f0945812035e1 e35f8bb6249e456f808a095f1cd53c5b +#: m5ui.list.M5List:13 m5ui.list.M5List.add_button:18 +#: m5ui.list.M5List.add_text:16 of +msgid "MicroPython Code Block:" +msgstr "MicroPython 代码块:" + +#: ../../en/m5ui/list.rst:50 224526be4e8a42e883ddcf41f91eb7e8 +msgid "**API**" +msgstr "" + +#: 6d3b95eca8444ef9aa3154e837e4e2cc m5ui.list.M5List:1 of +msgid "Bases: :py:class:`~lvgl.list`" +msgstr "" + +#: b1ee101d1d2d4d33b42a6fd2df364a31 m5ui.list.M5List:1 of +msgid "Create a list object." +msgstr "创建一个列表对象。" + +#: ../../en/m5ui/list.rst 9de3302485ba4a59b6198c28b3f561d0 +#: c49e9ea3fd4b4cc98cbf7cba05084874 eb41495127f9476ba2f4da5763c74a1b +#: m5ui.list.M5List.add_button m5ui.list.M5List.add_text of +msgid "Parameters" +msgstr "参数" + +#: e0515e09e87f43ce9c7ff37c1d9e9004 m5ui.list.M5List:3 of +msgid "The x position of the list." +msgstr "列表的 x 坐标。" + +#: f6c49bb0507d48839befcffd36ef0edf m5ui.list.M5List:4 of +msgid "The y position of the list." +msgstr "列表的 y 坐标。" + +#: a71156b246ba46d4b50752731d8de5d5 m5ui.list.M5List:5 of +msgid "The width of the list." +msgstr "列表的宽度。" + +#: af904d7b2d8148168e85e3919cebc920 m5ui.list.M5List:6 of +msgid "The height of the list." +msgstr "列表的高度。" + +#: 69b97570c4fb4573b5ec087664add798 m5ui.list.M5List:7 of +msgid "" +"The parent object to attach the list to. If not specified, the list will " +"be attached to the default screen." +msgstr "要将列表附加到的父对象;若未指定,则附加到默认屏幕。" + +#: ../../en/m5ui/list.rst:60 90b79cb1cd4d46b3af94fa39d6c218e1 +msgid "Move the background of the list to the end." +msgstr "将列表的背景项移动到末尾。" + +#: ../../en/m5ui/list.rst:64 7072db51146a480fb4167fcb23dac431 +msgid "|move_background.png|" +msgstr "" + +#: ../../en/refs/m5ui.list.ref:6 0180e942ba4c4178b2efa6d748ac540b +msgid "move_background.png" +msgstr "" + +#: ../../en/m5ui/list.rst:76 a4ff761882484ad18482edb30dcdc904 +msgid "Move the foreground of the list to the end." +msgstr "将列表的前景项移动到末尾。" + +#: ../../en/m5ui/list.rst:80 dfb9eb2799f245eabb3c2ab635abdf1f +msgid "|move_foreground.png|" +msgstr "" + +#: ../../en/refs/m5ui.list.ref:4 bccaa8e488cc451b9b8bd5fb3db3f486 +msgid "move_foreground.png" +msgstr "" + +#: ../../en/m5ui/list.rst:91 e6b3d08f418547a798993c8901f0b933 +msgid "Move the item at the specified index to the end of the list." +msgstr "将指定索引的项目移动到列表末尾。" + +#: ../../en/m5ui/list.rst:95 6fb0c1be103f44ef8b5ea49d1e087771 +msgid "|move_to_index.png|" +msgstr "" + +#: ../../en/refs/m5ui.list.ref:5 a7df447f3064481a9995eaf197781491 +msgid "move_to_index.png" +msgstr "" + +#: ../../en/m5ui/list.rst:106 6a7abda68b6d4edbabb1274b457a6ba5 +msgid "Delete the item from the list." +msgstr "从列表中删除该项目。" + +#: ../../en/m5ui/list.rst:110 b0b4ada34c5e41c7a4e77d4202638616 +msgid "|delete.png|" +msgstr "" + +#: ../../en/refs/m5ui.list.ref:7 38e625981589407690ae69f136d099a7 +msgid "delete.png" +msgstr "" + +#: 1c44467a8ea84d129c70ef27dd1e78c2 m5ui.list.M5List.add_text:1 of +msgid "Add a text label to the list." +msgstr "向列表添加文本标签。" + +#: 64c95aed4740483b99063b3e4d452ce2 m5ui.list.M5List.add_text:3 of +msgid "The text to display on the label." +msgstr "要在标签上显示的文本。" + +#: 61f0e87e7a9947dcb0451f061aa22115 m5ui.list.M5List.add_text:4 of +msgid "The text color of the label in hexadecimal format." +msgstr "标签文字的颜色(十六进制)。" + +#: 967fff07cabb4b3b96fc97f8518ca2a2 m5ui.list.M5List.add_text:5 of +msgid "The text opacity of the label (0-255)." +msgstr "按钮文字的不透明度(0-255)。" + +#: b4ef09172c334a65a1cd432653096dfb m5ui.list.M5List.add_text:6 of +msgid "The background color of the label in hexadecimal format." +msgstr "标签背景颜色(十六进制)。" + +#: 77f8d297393a4204beb71411914a7efd m5ui.list.M5List.add_text:7 of +msgid "The background opacity of the label (0-255)." +msgstr "标签背景的不透明度(0-255)。" + +#: 11db5ee401d9457293753bdd43ddfa24 m5ui.list.M5List.add_text:8 of +msgid "The font to use for the label." +msgstr "标签使用的字体。" + +#: cb4f3f13431b4716af9e7d36c8e2f9b9 m5ui.list.M5List.add_button +#: m5ui.list.M5List.add_text of +msgid "Returns" +msgstr "返回值" + +#: 60ca2092b17f48d4a2d1771d8d368e94 m5ui.list.M5List.add_text:9 of +msgid "The created label object :ref:`m5ui.M5Label `." +msgstr "创建的标签对象 :ref:`m5ui.M5Label `。" + +#: be5a6ad7a5844507b7311e6846b2079b m5ui.list.M5List.add_button +#: m5ui.list.M5List.add_text of +msgid "Return type" +msgstr "返回类型" + +#: 6e1c4e95ec53476bb98fac251d2c7cf9 m5ui.list.M5List.add_text:14 of +msgid "|add_text.png|" +msgstr "" + +#: ../../en/refs/m5ui.list.ref:2 387f0da3b41f4f1eae4590184c88099b +msgid "add_text.png" +msgstr "" + +#: c102e0ea40e24ff08227144efa8316f8 m5ui.list.M5List.add_button:1 of +msgid "Add a button to the list." +msgstr "向列表添加按钮。" + +#: f378228c517e4e87aeca6281e2c69bfd m5ui.list.M5List.add_button:3 of +msgid "The icon to display on the button." +msgstr "按钮上要显示的图标。" + +#: f899cec7313a4f84bc5951c983675f3d m5ui.list.M5List.add_button:4 of +msgid "The text to display on the button." +msgstr "按钮上要显示的文本。" + +#: 52486ef6f47f421db5d60edd88240f6a m5ui.list.M5List.add_button:5 of +msgid "The height of the button." +msgstr "按钮的高度。" + +#: 12dda48295df4de7b812db36c73aec61 m5ui.list.M5List.add_button:6 of +msgid "The background color of the button in hexadecimal format." +msgstr "按钮背景颜色(十六进制)。" + +#: b4526e38b14a4ae0af5088e6246d3053 m5ui.list.M5List.add_button:7 of +msgid "The background opacity of the button (0-255)." +msgstr "按钮背景的不透明度(0-255)。" + +#: 7ef72bfc265f4f2f95bc0874c7787027 m5ui.list.M5List.add_button:8 of +msgid "The text color of the button in hexadecimal format." +msgstr "按钮文字颜色(十六进制)。" + +#: 967fff07cabb4b3b96fc97f8518ca2a2 m5ui.list.M5List.add_button:9 of +msgid "The text opacity of the button (0-255)." +msgstr "按钮文字的不透明度(0-255)。" + +#: ee5a7140c6ad4260b488b270a6a7bc5a m5ui.list.M5List.add_button:10 of +msgid "The font to use for the button text." +msgstr "按钮文字所使用的字体。" + +#: f061c4bdd4b14a1f95281b4ff948686b m5ui.list.M5List.add_button:11 of +msgid "The created button object :ref:`m5ui.M5Button `." +msgstr "创建的按钮对象 :ref:`m5ui.M5Button `。" + +#: f101b3db15ce4d60b0942126ddc8ca73 m5ui.list.M5List.add_button:16 of +msgid "|add_button.png|" +msgstr "" + +#: ../../en/refs/m5ui.list.ref:3 5a6614b568c043e585e1d47a10acb894 +msgid "add_button.png" +msgstr "" + diff --git a/examples/m5ui/list/cores3_list_example.m5f2 b/examples/m5ui/list/cores3_list_example.m5f2 index fa9668dc..414e16e5 100644 --- a/examples/m5ui/list/cores3_list_example.m5f2 +++ b/examples/m5ui/list/cores3_list_example.m5f2 @@ -1 +1 @@ -{"version":"V2.0","versionNumber":"V2.3.0","type":"cores3","components":[{"name":"page0","type":"lvgl_page","layer":0,"screenId":"builtin","screenName":"","id":"zGUSgLk%P9BF_bji","createTime":1750906906496,"backgroundColor":"#ffffff","isLVGL":true,"isSelected":true},{"name":"label0","type":"lvgl_label","layer":1,"screenId":"builtin","screenName":"","id":"ktg+xZQJ^lvI#N=M","createTime":1750907034945,"x":60,"y":110,"color":"#000000","backgroundColor":"#ffffff","bg_opacity":0,"text":"It is a circularly scrolling text. ","font":"lv.font_montserrat_14","pageId":"zGUSgLk%P9BF_bji","isLVGL":true,"isSelected":false,"width":207,"height":15}],"resources":[{"hardware":["hardware_button","hardware_pin_button","imu","speaker","touch","als","mic","sdcard"]}],"units":[],"hats":[],"bases":[],"i2cs":[],"blockly":"truepage0label0SCROLL_CIRCULARlabel0150label0CENTERpage000true","screen":[{"simulationName":"Built-in","type":"builtin","width":320,"height":240,"scale":0.77,"screenName":"","blockId":"","screenColorType":0,"rotation":1,"id":"builtin","createTime":1750906899526}],"logicWhenNum":0,"customList":[]} \ No newline at end of file +{"version":"V2.0","versionNumber":"V2.3.2","type":"cores3","components":[{"name":"page0","type":"lvgl_page","layer":0,"screenId":"builtin","screenName":"","id":"sv%9o*Gy8_O#f&sN","createTime":1754275151694,"backgroundColor":"#ffffff","isLVGL":true,"isSelected":true},{"name":"list0","type":"lvgl_list","layer":1,"screenId":"builtin","screenName":"","id":"l7g^oT9XD8P`*3ry","createTime":1754275162243,"x":-1,"y":2,"width":320,"height":240,"childList":[{"type":"label","name":"File","value":"File"},{"type":"button","name":"New","value":"New"}],"isInit":true,"pageId":"sv%9o*Gy8_O#f&sN","isLVGL":true,"isSelected":false}],"resources":[{"hardware":["hardware_button","hardware_pin_button","imu","speaker","touch","als","mic","sdcard"]}],"units":[],"hats":[],"bases":[],"i2cs":[],"blockly":"truepage0list0OpenDIRECTORYOpenlist0SaveSAVESavelist0DeleteCLOSEDeletelist0NewPRESSEDpalette#ffff00255list0OpenPRESSEDpalette#ffff00100list0SavePRESSEDpalette#ffff00255list0DeletePRESSEDpalette#ffff00255list0NewCLICKEDNewlist0OpenCLICKEDOpenlist0SaveCLICKEDSavelist0DeleteCLICKEDDeletetrue","screen":[{"simulationName":"Built-in","type":"builtin","width":320,"height":240,"scale":0.77,"screenName":"","blockId":"","screenColorType":0,"rotation":1,"id":"builtin","createTime":1754275151692}],"logicWhenNum":0,"customList":[]} \ No newline at end of file diff --git a/examples/m5ui/list/cores3_list_example.py b/examples/m5ui/list/cores3_list_example.py index 859d648f..39f795e9 100644 --- a/examples/m5ui/list/cores3_list_example.py +++ b/examples/m5ui/list/cores3_list_example.py @@ -10,34 +10,99 @@ page0 = None -label0 = None +list0 = None +File = None +New = None +Open = None +Save = None +Delete = None + + +def New_clicked_event(event_struct): # noqa: N802 + global page0, list0, File, New, Open, Save, Delete + + print("New") + + +def Open_clicked_event(event_struct): # noqa: N802 + global page0, list0, File, New, Open, Save, Delete + + print("Open") + + +def Save_clicked_event(event_struct): # noqa: N802 + global page0, list0, File, New, Open, Save, Delete + + print("Save") + + +def Delete_clicked_event(event_struct): # noqa: N802 + global page0, list0, File, New, Open, Save, Delete + + print("Delete") + + +def New_event_handler(event_struct): # noqa: N802 + global page0, list0, File, New, Open, Save, Delete + event = event_struct.code + if event == lv.EVENT.CLICKED and True: + New_clicked_event(event_struct) + return + + +def Open_event_handler(event_struct): # noqa: N802 + global page0, list0, File, New, Open, Save, Delete + event = event_struct.code + if event == lv.EVENT.CLICKED and True: + Open_clicked_event(event_struct) + return + + +def Save_event_handler(event_struct): # noqa: N802 + global page0, list0, File, New, Open, Save, Delete + event = event_struct.code + if event == lv.EVENT.CLICKED and True: + Save_clicked_event(event_struct) + return + + +def Delete_event_handler(event_struct): # noqa: N802 + global page0, list0, File, New, Open, Save, Delete + event = event_struct.code + if event == lv.EVENT.CLICKED and True: + Delete_clicked_event(event_struct) + return def setup(): - global page0, label0 + global page0, list0, File, New, Open, Save, Delete M5.begin() + Widgets.setRotation(1) m5ui.init() - page0 = m5ui.M5Screen(bg_c=0xFFFFFF) - label0 = m5ui.M5Label( - "It is a circularly scrolling text. ", - x=60, - y=110, - text_c=0x000000, - bg_c=0xFFFFFF, - bg_opa=0, - font=lv.font_montserrat_14, - parent=page0, - ) + page0 = m5ui.M5Page(bg_c=0xFFFFFF) + list0 = m5ui.M5List(x=-1, y=2, w=320, h=240, parent=page0) + File = list0.add_text("File") + New = list0.add_button(lv.SYMBOL.BULLET, "New") page0.screen_load() - label0.set_long_mode(lv.label.LONG_MODE.SCROLL_CIRCULAR) - label0.set_width(150) - label0.align_to(page0, lv.ALIGN.CENTER, 0, 0) + Open = list0.add_button(lv.SYMBOL.DIRECTORY, "Open") + Save = list0.add_button(lv.SYMBOL.SAVE, "Save") + Delete = list0.add_button(lv.SYMBOL.CLOSE, "Delete") + + New.add_event_cb(New_event_handler, lv.EVENT.ALL, None) + Open.add_event_cb(Open_event_handler, lv.EVENT.ALL, None) + Save.add_event_cb(Save_event_handler, lv.EVENT.ALL, None) + Delete.add_event_cb(Delete_event_handler, lv.EVENT.ALL, None) + + New.set_text_color(0xFFFF00, 255, lv.PART.MAIN | lv.STATE.PRESSED) + Open.set_text_color(0xFFFF00, 100, lv.PART.MAIN | lv.STATE.PRESSED) + Save.set_text_color(0xFFFF00, 255, lv.PART.MAIN | lv.STATE.PRESSED) + Delete.set_text_color(0xFFFF00, 255, lv.PART.MAIN | lv.STATE.PRESSED) def loop(): - global page0, label0 + global page0, list0, File, New, Open, Save, Delete M5.update() diff --git a/m5stack/libs/m5ui/list.py b/m5stack/libs/m5ui/list.py index 7ceadcf2..9cfa3402 100644 --- a/m5stack/libs/m5ui/list.py +++ b/m5stack/libs/m5ui/list.py @@ -4,6 +4,7 @@ from m5ui.base import M5Base import lvgl as lv +import m5ui class M5List(lv.list): @@ -27,7 +28,7 @@ class M5List(lv.list): import lvgl as lv m5ui.init() - list_0 = M5List(x=20, y=10, w=100, h=220, parent=page0) + list_0 = M5List(x=120, y=80, w=60, h=30, parent=page0) """ def __init__( @@ -44,6 +45,105 @@ def __init__( self.set_pos(x, y) self.set_size(w, h) + def add_text( + self, + text, + text_c=0x212121, + text_opa=255, + bg_c=0xE6E2E6, + bg_opa=255, + font=lv.font_montserrat_14, + ): + """Add a text label to the list. + + :param str text: The text to display on the label. + :param int text_c: The text color of the label in hexadecimal format. + :param int text_opa: The text opacity of the label (0-255). + :param int bg_c: The background color of the label in hexadecimal format. + :param int bg_opa: The background opacity of the label (0-255). + :param lv.font font: The font to use for the label. + :return: The created label object :ref:`m5ui.M5Label `. + :rtype: lv.obj + + UiFlow2 Code Block: + + |add_text.png| + |add_text2.png| + + MicroPython Code Block: + + .. code-block:: python + + list_0.add_text("Item 1", text_c=0x000000, text_opa=255, bg_c=0xFFFFFF, bg_opa=255, font=lv.font_montserrat_14) + """ + _label = m5ui.M5Label( + text=text, text_c=text_c, bg_c=bg_c, bg_opa=bg_opa, font=font, parent=self + ) + _label.set_style_text_opa(text_opa, lv.PART.MAIN | lv.STATE.DEFAULT) + _label.set_width(lv.pct(100)) + _label.set_style_margin_left(-14, lv.PART.MAIN | lv.STATE.DEFAULT) + _label.set_style_margin_right(-14, lv.PART.MAIN | lv.STATE.DEFAULT) + _label.set_style_pad_left(14, lv.PART.MAIN | lv.STATE.DEFAULT) + _label.set_style_pad_right(14, lv.PART.MAIN | lv.STATE.DEFAULT) + return _label + + def add_button( + self, + icon, + text="button0", + h=0, + bg_c=0xFFFFFF, + bg_opa=255, + text_c=0x000000, + text_opa=255, + font=lv.font_montserrat_14, + ): + """Add a button to the list. + + :param int icon: The icon to display on the button. + :param str text: The text to display on the button. + :param int h: The height of the button. + :param int bg_c: The background color of the button in hexadecimal format. + :param int bg_opa: The background opacity of the button (0-255). + :param int text_c: The text color of the button in hexadecimal format. + :param int text_opa: The text opacity of the button (0-255). + :param lv.font font: The font to use for the button text. + :return: The created button object :ref:`m5ui.M5Button `. + :rtype: lv.obj + + UiFlow2 Code Block: + + |add_button.png| + |add_button2.png| + + MicroPython Code Block: + + .. code-block:: python + + list_0.add_button(lv.SYMBOL.BULLET, text="Home", h=40, bg_c=0xFFFFFF, text_c=0x000000, font=lv.font_montserrat_14) + """ + _button = m5ui.M5Button( + text=text, w=1, h=h, bg_c=bg_c, text_c=text_c, font=font, parent=self + ) + if icon: + _icon = lv.image(_button) + _icon.set_src(icon) + _icon.move_to_index(0) + + _button.set_width(lv.pct(100)) + _button.set_flex_flow(lv.FLEX_FLOW.ROW) + _button.set_style_margin_left(-14, lv.PART.MAIN) + _button.set_style_margin_right(-14, lv.PART.MAIN) + _button.set_style_pad_left(14, lv.PART.MAIN) + _button.set_style_pad_right(14, lv.PART.MAIN) + _button.set_style_radius(0, lv.PART.MAIN | lv.STATE.DEFAULT) + _button.set_style_border_side(lv.BORDER_SIDE.BOTTOM, 0) + _button.set_style_border_width(1, 0) + _button.set_style_border_color(lv.color_hex(0xECE9EC), 0) + _button.set_style_bg_opa(bg_opa, lv.PART.MAIN | lv.STATE.DEFAULT) + _button.set_style_text_opa(text_opa, lv.PART.MAIN | lv.STATE.DEFAULT) + return _button + def __getattr__(self, name): if hasattr(M5Base, name): method = getattr(M5Base, name) From 0c9a2539129eff245e9e5adcfe40f6b7299b0abb Mon Sep 17 00:00:00 2001 From: tinyu Date: Mon, 4 Aug 2025 11:49:01 +0800 Subject: [PATCH 190/322] lib/m5ui: M5Base add API for setting line color and opa. Signed-off-by: tinyu --- m5stack/libs/m5ui/base.py | 16 ++++++++++++++++ m5stack/libs/m5ui/line.py | 2 +- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/m5stack/libs/m5ui/base.py b/m5stack/libs/m5ui/base.py index 0883bdee..87dce543 100644 --- a/m5stack/libs/m5ui/base.py +++ b/m5stack/libs/m5ui/base.py @@ -73,6 +73,22 @@ def set_text_color(self, color: int, opa: int, part: int) -> None: time.sleep(0.01) self.set_style_text_opa(opa, part) + @staticmethod + def set_line_color(self, color: int, opa: int, part: int) -> None: + """Set the line color and opacity for a given part of the object. + + :param int color: The color to set, can be an integer (hex) or a lv.color object. + :param int opa: The opacity level (0-255). + :param int part: The part of the object to apply the style to (e.g., lv.PART.MAIN). + :return: None + """ + if isinstance(color, int): + color = lv.color_hex(color) + + self.set_style_line_color(color, part) + time.sleep(0.01) + self.set_style_line_opa(opa, part) + @staticmethod def set_bg_color(self, color: int, opa: int, part: int) -> None: """Set the background color and opacity for a given part of the object. diff --git a/m5stack/libs/m5ui/line.py b/m5stack/libs/m5ui/line.py index d93308bd..952584be 100644 --- a/m5stack/libs/m5ui/line.py +++ b/m5stack/libs/m5ui/line.py @@ -45,7 +45,7 @@ def __init__( super().__init__(parent) self.set_points(points) - self.set_line_color(color, 255) + self.set_line_color(color, 255, lv.PART.MAIN) self.set_style_line_width(width, lv.PART.MAIN) self.set_style_line_rounded(rounded, lv.PART.MAIN) From 6c949953c0340ece53d54940769efe6c50b39716 Mon Sep 17 00:00:00 2001 From: tinyu Date: Mon, 4 Aug 2025 14:11:48 +0800 Subject: [PATCH 191/322] lib/m5ui: Add the direction of the setting switch. Signed-off-by: tinyu --- docs/en/refs/m5ui.switch.ref | 2 + docs/locales/zh_CN/LC_MESSAGES/m5ui/switch.po | 333 +++++++++--------- m5stack/libs/m5ui/switch.py | 38 +- 3 files changed, 206 insertions(+), 167 deletions(-) diff --git a/docs/en/refs/m5ui.switch.ref b/docs/en/refs/m5ui.switch.ref index 9566e61c..fab07e65 100644 --- a/docs/en/refs/m5ui.switch.ref +++ b/docs/en/refs/m5ui.switch.ref @@ -10,6 +10,8 @@ .. |set_width.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/switch/set_width.png .. |set_x.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/switch/set_x.png .. |set_y.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/switch/set_y.png +.. |set_direction.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/switch/set_direction.png + .. |cores3_switch_event_example.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/switch/example.png diff --git a/docs/locales/zh_CN/LC_MESSAGES/m5ui/switch.po b/docs/locales/zh_CN/LC_MESSAGES/m5ui/switch.po index f95459e9..24edb4bc 100644 --- a/docs/locales/zh_CN/LC_MESSAGES/m5ui/switch.po +++ b/docs/locales/zh_CN/LC_MESSAGES/m5ui/switch.po @@ -9,7 +9,7 @@ msgid "" msgstr "" "Project-Id-Version: UIFlow2 Programming Guide \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-07-07 15:19+0800\n" +"POT-Creation-Date: 2025-08-04 12:32+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language: zh_CN\n" @@ -21,407 +21,408 @@ msgstr "" "Generated-By: Babel 2.16.0\n" #: ../../en/m5ui/switch.rst:4 ../../en/m5ui/switch.rst:52 -#: 144bc5926f274852891e514d61248e3a ae69ec18e8ce453f82020a7bca0a60ae +#: 25f0b66840b34160be8dcf03c43b75a2 520f39669c5543608d3f974fdc87d94b msgid "M5Switch" msgstr "M5Switch" -#: ../../en/m5ui/switch.rst:8 e8120d01253541799205e70886cf651c +#: ../../en/m5ui/switch.rst:8 9f9f6bf423764a52856a549828603ac2 msgid "" "M5Switch is a widget that can be used to create switch in the user " "interface. It can be used to trigger actions when checked and uncheked." msgstr "M5Switch 是一个用于在用户界面中创建开关的控件,可在开关被选中或取消选中时触发操作。" -#: ../../en/m5ui/switch.rst:11 f843eaaab08f469c9ef7ddaadb0becbf +#: ../../en/m5ui/switch.rst:11 e624a52be78845b49e9f6da580b6b94f msgid "UiFlow2 Example" msgstr "UiFlow2 示例" #: ../../en/m5ui/switch.rst:14 ../../en/m5ui/switch.rst:33 -#: 2eca1caa159b40b9a53b95f440339b52 4414bd7ae42e4df3959743bda4f06854 +#: 18fe15eb451943b9b47df1fdfb7a3039 msgid "event switch" msgstr "开关事件" -#: ../../en/m5ui/switch.rst:16 5ba93c2b354a469dbee81beeb00233bd +#: ../../en/m5ui/switch.rst:16 eaf70f95f30d4199ba09197e4687b96c msgid "Open the |cores3_switch_event_example.m5f2| project in UiFlow2." msgstr "" #: ../../en/m5ui/switch.rst:18 ../../en/m5ui/switch.rst:35 -#: 3a42e2385da54548872aed34038cb4b3 3a6f5a1a1b5540e8bd4badc40fda7273 +#: 66eb2971a07445378ab9607197f0d9fe msgid "" "This example creates a switch that triggers an event when checked and " "uncheked." msgstr "本示例创建了一个开关,当其被选中或取消选中时会触发事件。" -#: ../../en/m5ui/switch.rst:20 ../../en/m5ui/switch.rst:67 -#: ../../en/m5ui/switch.rst:92 ../../en/m5ui/switch.rst:110 -#: ../../en/m5ui/switch.rst:128 ../../en/m5ui/switch.rst:145 -#: ../../en/m5ui/switch.rst:162 ../../en/m5ui/switch.rst:180 -#: ../../en/m5ui/switch.rst:197 ../../en/m5ui/switch.rst:231 -#: ../../en/m5ui/switch.rst:268 ../../en/m5ui/switch.rst:287 -#: 01853a4cbb4540d28d2e9c0116600706 0424f6afa3d94c2b9a16a657e5fbcd98 -#: 075e1d03e602404dab1c47d841573cc4 0c511725339b4c88956dbbf665ec8ed7 -#: 523a444188304789a4a2b7829047c253 560b835a4c2b457ca0200e7c32cb5299 -#: 57a5453538c049baa1c363abe2782504 5c3b65d3ea1442b89884c105a9976607 -#: 7bf3eb87bacc48a7af4256d85f13270a c86121c5cd78401f958e2972312c751a -#: e24286979f504e6cb2a9354f850c0150 eddf23c9ab414b77b52527029f3ee734 -#: f44dce660e114656b0a3aeca192ceb7b m5ui.switch.M5Switch:12 of +#: ../../en/m5ui/switch.rst:20 ../../en/m5ui/switch.rst:66 +#: ../../en/m5ui/switch.rst:86 ../../en/m5ui/switch.rst:104 +#: ../../en/m5ui/switch.rst:121 ../../en/m5ui/switch.rst:137 +#: ../../en/m5ui/switch.rst:153 ../../en/m5ui/switch.rst:170 +#: ../../en/m5ui/switch.rst:186 ../../en/m5ui/switch.rst:219 +#: ../../en/m5ui/switch.rst:255 ../../en/m5ui/switch.rst:273 +#: 54b7be5e25be4fd899038419f17df284 m5ui.switch.M5Switch:12 of msgid "UiFlow2 Code Block:" msgstr "UiFlow2 代码块:" -#: ../../en/m5ui/switch.rst:22 de5619fd24684366a0772f1d8984930a +#: ../../en/m5ui/switch.rst:22 4a96f8985b2242c0906036fcad570f3e msgid "|cores3_switch_event_example.png|" msgstr "" -#: ../../en/refs/m5ui.switch.ref:16 6302fd9454dc45528989cd6abc3cd7dc +#: ../../en/refs/m5ui.switch.ref:16 26e98d225b5942ddaaecbda5ab1d18bc msgid "cores3_switch_event_example.png" msgstr "" #: ../../en/m5ui/switch.rst:24 ../../en/m5ui/switch.rst:43 -#: 7e5a1fd5134f43afb64fcbbd603ca1a3 f6c6674aa1dc48559f37e66e5025f109 +#: 615ba168037a447cb155661467caa695 msgid "Example output:" msgstr "示例输出:" #: ../../en/m5ui/switch.rst:26 ../../en/m5ui/switch.rst:45 -#: ../../en/m5ui/switch.rst:65 ../../en/m5ui/switch.rst:90 -#: ../../en/m5ui/switch.rst:126 ../../en/m5ui/switch.rst:143 -#: ../../en/m5ui/switch.rst:160 ../../en/m5ui/switch.rst:178 -#: ../../en/m5ui/switch.rst:195 ../../en/m5ui/switch.rst:229 -#: ../../en/m5ui/switch.rst:266 ../../en/m5ui/switch.rst:285 -#: 01cc6ec217bc4efbb82226f78ac9bddb 03aeffdcaa344a47bf570030b3469427 -#: 309c4b1717d442389b488f246ef27ff9 3b2660d27b534e1c97e95b54a25c75ea -#: 4d15aba6ee1f46c7869b39a27fb8e4ae 804dcbd5cdee482c86d0092bdbc4cf25 -#: 865aea79a85743c5af0b9316bcc85f4d a40dd4c82038497ea28f7df84f405e71 -#: b8257793d81449af8eba30888d4a2515 d107e8b8bcf1443bb9ddff4461d723dc -#: e7a968d5a98f440d9d5ae784f4aa6464 e857ad3f16ab47179a71a2d61d8322bf -#: f9355921e11748d7a567d8c07712d6dd m5ui.switch.M5Switch:14 of +#: 2276db21581b484d8d77c1eb8004f343 m5ui.switch.M5Switch:14 of msgid "None" msgstr "无" -#: ../../en/m5ui/switch.rst:30 95cab63deb5f4d5f85b0c9fb9092e3bb +#: ../../en/m5ui/switch.rst:30 d0cb961be61b4d39a3c207f18e3a3c68 msgid "MicroPython Example" msgstr "MicroPython 示例" -#: ../../en/m5ui/switch.rst:37 ../../en/m5ui/switch.rst:77 -#: ../../en/m5ui/switch.rst:96 ../../en/m5ui/switch.rst:114 -#: ../../en/m5ui/switch.rst:132 ../../en/m5ui/switch.rst:149 -#: ../../en/m5ui/switch.rst:166 ../../en/m5ui/switch.rst:184 -#: ../../en/m5ui/switch.rst:201 ../../en/m5ui/switch.rst:235 -#: ../../en/m5ui/switch.rst:272 ../../en/m5ui/switch.rst:291 -#: 193f872f16f54b128e61bd26461c7ce4 5c7cc888c53e4c55804777194a38f2f4 -#: 5cdbf26f3a2743a6ba7e42b375cce3f2 5e4f7f1e39b44e99a55326cd5dabd386 -#: 760e8bab26be42e7b6f833be20c13321 7e9e3519808d41d9a7845f9a056c362c -#: 863f402a224a4f9cadabc85cfc1a763d a9edb27db62543fd8c0886e50d56cada -#: c136c379f3094a32b3f4eec45da6f436 daf2a6adb2624020886a5ac49dfd4076 -#: e233e2611a9546d69b6fd9aa0578b1db eef2013d30ff4b199587cfa0b41a4dae -#: f8053399e629420c897bebea835c5e22 m5ui.switch.M5Switch:16 of +#: ../../en/m5ui/switch.rst:37 ../../en/m5ui/switch.rst:72 +#: ../../en/m5ui/switch.rst:90 ../../en/m5ui/switch.rst:108 +#: ../../en/m5ui/switch.rst:125 ../../en/m5ui/switch.rst:141 +#: ../../en/m5ui/switch.rst:157 ../../en/m5ui/switch.rst:174 +#: ../../en/m5ui/switch.rst:190 ../../en/m5ui/switch.rst:223 +#: ../../en/m5ui/switch.rst:259 ../../en/m5ui/switch.rst:277 +#: 545ffc0d2da44a699ebbe2edac7cd6bf m5ui.switch.M5Switch:16 +#: m5ui.switch.M5Switch.set_direction:14 of msgid "MicroPython Code Block:" msgstr "MicroPython 代码块:" -#: ../../en/m5ui/switch.rst:49 7d8762e883e3422d9575ab3f2ec61129 +#: ../../en/m5ui/switch.rst:49 78a4b462721348eea2f31a2c26c907ec msgid "**API**" msgstr "**API**" -#: aebc09d502de4d40a690cf2d04baac75 m5ui.switch.M5Switch:1 of +#: a187599015e84f109ad8390e770b14ea m5ui.switch.M5Switch:1 of msgid "Bases: :py:class:`~lvgl.switch`" msgstr "" -#: 30d7b60cbe77476bbaf58d99a5949a6b m5ui.switch.M5Switch:1 of +#: 1a981cbd890b44e6b271778383fd8aa6 m5ui.switch.M5Switch:1 of msgid "Create a switch object." msgstr "创建一个开关对象。" -#: ../../en/m5ui/switch.rst 0f52ae4b17e8494d995e51a33599a2ce -#: 100a3aa80366401bb3bddf0ad09382ae 1a21f9655a154fda9db3a0036377cec9 -#: 1a7f2a1c872b450aa08751fbef8acf92 1d2c69b90c124fb4b6551cdcffbc35f3 -#: 2c9bfa3adc5b43a5adbaf636229f9f45 645c976a05334742ab998b08e334f0d8 -#: 762c53f7ee694f7cb2ece141c8acf0dd 7a2813f6b993491080a8823310b359e3 -#: c9e247f55ab04108beef8351981d3139 dab8ca298e634ef582025efc4d607dde -#: f68899b6b1fb47e7a55343891e95ee95 +#: ../../en/m5ui/switch.rst 790bc5aab7904e4dbffeb0ee2b0af48f +#: m5ui.switch.M5Switch.set_direction of msgid "Parameters" msgstr "参数" -#: 8084ba15851e449db0ba19f543177916 m5ui.switch.M5Switch:3 of +#: 789eeffb3eeb400d8589e5b831b1ced3 m5ui.switch.M5Switch:3 of msgid "The x position of the switch." msgstr "开关的 x 坐标。" -#: bed123eba1f341548411ead6edea76d5 m5ui.switch.M5Switch:4 of +#: e8512075d11e47958c6b2cb2f37152d4 m5ui.switch.M5Switch:4 of msgid "The y position of the switch." msgstr "开关的 y 坐标。" -#: ab4f2542216f44a59b7b0cf45deb2df0 m5ui.switch.M5Switch:5 of -msgid "" -"The width of the switch." -msgstr "开关的宽度。 +#: ../../en/m5ui/switch.rst:167 ../../en/m5ui/switch.rst:184 +#: 64f83132790a449992c034f3147ec7ca 8b1e458bedf74d6fb95c4e9f167a77f5 +#: m5ui.switch.M5Switch:5 of +msgid "The width of the switch." +msgstr "开关的宽度" -#: 50e48ea8e28d4a11a8ce8321ba3446d3 m5ui.switch.M5Switch:6 of -msgid "" -"The height of the switch." -msgstr "开关的高度。 +#: ../../en/m5ui/switch.rst:168 ../../en/m5ui/switch.rst:217 +#: 3625bc5f471b46ffbb34d9c893ce5e0a ba21dc3f2d0c4984aad58410dc24725f +#: m5ui.switch.M5Switch:6 of +msgid "The height of the switch." +msgstr "开关的高度" -#: da8c3de9de314a318834840bc5326860 m5ui.switch.M5Switch:7 of +#: 93111ed53c984c449ed3fe752db433d3 m5ui.switch.M5Switch:7 of msgid "The color of the switch in the off state in hexadecimal format." msgstr "开关在关闭状态下的颜色(十六进制)。" -#: 0e9653609e6149efa27a49b3f6eeca00 m5ui.switch.M5Switch:8 of +#: 7ddab65e479742d4ade08aeefab9f5a5 m5ui.switch.M5Switch:8 of msgid "The color of the switch in the on state in hexadecimal format." msgstr "开关在开启状态下的颜色(十六进制)。" -#: 559f5e1f78be4fcdab74d10f1c00f095 m5ui.switch.M5Switch:9 of +#: f3e608ad72dc43f4833c1b5c1cbc1db1 m5ui.switch.M5Switch:9 of msgid "" "This color refers to the color of the circle on the switch in hexadecimal" " format." msgstr "此颜色指开关圆钮的颜色(十六进制)。" -#: 1785451c0e804514a087578146f9944c m5ui.switch.M5Switch:10 of +#: dc627b82859c4903b4933c62e91fd44e m5ui.switch.M5Switch:10 of msgid "" "The parent object to attach the switch to. If not specified, the switch " "will be attached to the default screen." msgstr "开关要附加到的父对象;若未指定,则附加到默认屏幕。" -#: ../../en/m5ui/switch.rst:60 62dc326fb9f04e04875e9e6d62b52021 +#: ../../en/m5ui/switch.rst:60 153fc6672b4040839d7f02554ea3f41a msgid "Set the background color of the switch." msgstr "设置开关的背景颜色。" -#: ../../en/m5ui/switch.rst:62 342c4ec62d884a0a9af96464117c556e +#: ../../en/m5ui/switch.rst:62 a02694999fa34a95811d2250a793335c msgid "The color to set." msgstr "要设置的颜色。" -#: ../../en/m5ui/switch.rst:63 94bf8505647646beb0b0aad47d4ccf4c +#: ../../en/m5ui/switch.rst:63 24eaf5fb5c40489eb7cdd5cab588a3bf msgid "" "The opacity of the color. The value should be between 0 (transparent) and" " 255 (opaque)." msgstr "颜色的不透明度,取值范围 0(透明)至 255(不透明)。" -#: ../../en/m5ui/switch.rst:64 5cced61eb3464308a02b5e80c4ce9a5e +#: ../../en/m5ui/switch.rst:64 0e464178334947769b4be6efb3f6284d msgid "The part of the object to apply the style to (e.g., lv.PART.MAIN)." msgstr "应用样式的对象部分(如 lv.PART.MAIN)。" -#: ../../en/m5ui/switch.rst 0c8fe987f81d4cd5a0c8b89967ec1e1a -#: 1e2b8b8013254cdaa126d2cf73749319 1e73944b4ebe4add9d538b6c62930917 -#: 2ea508f721e9426280b422140c7b48fb 4eafedcec364431291d60f27c6e41dc9 -#: 7019e89729df42b0b027404d0e296dff 7e35a552ee69425aa017aa0ba976de27 -#: 9ab07ba0f7b54b4b92fba81507de973e b3602d5ddbac435d9ac87a8fe9ed3784 -#: cd28e03636884adfb87f9333a8f0e879 d7d8f3b14bc1497f8e7bb37262009ee8 -msgid "Returns" -msgstr "" - -#: ../../en/m5ui/switch.rst:69 e968e78e617342a4a9e52985a0de16d9 -msgid "|set_bg_color_default.png|" -msgstr "" - -#: ../../en/refs/m5ui.switch.ref:4 4a2b4dcae3ad491aa9448290d978c0e9 -msgid "set_bg_color_default.png" -msgstr "" - -#: ../../en/m5ui/switch.rst:71 095b90d472674bc89c60259aaf0082b2 -msgid "|set_bg_color_checked.png|" +#: ../../en/m5ui/switch.rst:68 10efc8e2f146418494dc2fac22943387 +msgid "|set_bg_color.png|" msgstr "" -#: ../../en/refs/m5ui.switch.ref:5 e79cf68104e24cf2a78a0da0103368fa -msgid "set_bg_color_checked.png" +#: ../../en/refs/m5ui.switch.ref:4 343654da75cd419db54c93b4b9388943 +msgid "set_bg_color.png" msgstr "" -#: ../../en/m5ui/switch.rst:73 869291a671fd4fcaac95f18c17f796fe -msgid "|set_knob_color_checked.png|" +#: ../../en/m5ui/switch.rst:70 6885bd76a44b4be0bc7ba6249ffa5628 +msgid "|set_knob_color.png|" msgstr "" -#: ../../en/refs/m5ui.switch.ref:10 ea0edb2c8e5f4e99b21c52cde5d320e7 -msgid "set_knob_color_checked.png" +#: ../../en/refs/m5ui.switch.ref:9 5a6b69fbc8d74c5b964cb164db4afb9e +msgid "set_knob_color.png" msgstr "" -#: ../../en/m5ui/switch.rst:75 95ac7defc8bc4247a1a34ba83b13b88b -msgid "|set_knob_color_default.png|" -msgstr "" - -#: ../../en/refs/m5ui.switch.ref:11 9f7dd38e187b41cc8b6989b516d5d969 -msgid "set_knob_color_default.png" -msgstr "" - -#: ../../en/m5ui/switch.rst:86 849a8d24abcd479c91aa3c5db5f9d39d +#: ../../en/m5ui/switch.rst:81 81a7e9b743684484bfb80f973d1f354f msgid "Set the state of the Switch." msgstr "设置开关的状态。" -#: ../../en/m5ui/switch.rst:88 a2e9d1ae64e44201bc8dd71e4c31eba4 +#: ../../en/m5ui/switch.rst:83 f3b57b6c6ae7462cbe8a0db97a8054b0 msgid "The state to set." msgstr "要设置的状态。" -#: ../../en/m5ui/switch.rst:89 2c4b99e1d007479988081ce5e55a59b7 +#: ../../en/m5ui/switch.rst:84 2fed46dd959a46e0a299c998a0ea6c62 msgid "If True, the state is set; if False, the state is unset." msgstr "若为 True 表示设置状态;为 False 则取消状态。" -#: ../../en/m5ui/switch.rst:94 22309eb864f44e79a24af878c3a6df67 +#: ../../en/m5ui/switch.rst:88 3a34490641de45feb0dd2a07d4eafded msgid "|set_state.png|" msgstr "" -#: ../../en/refs/m5ui.switch.ref:9 a9ad005e47cd4e69abcdf1fc92ba7bb6 +#: ../../en/refs/m5ui.switch.ref:8 7437e970e31f49a799a35ae4ac24735c msgid "set_state.png" msgstr "" -#: ../../en/m5ui/switch.rst:104 9fdd83843760449a9ebf23c0b9f39ab9 +#: ../../en/m5ui/switch.rst:98 96a015120a994dbf9214feea530a1c9a msgid "Get the state of the Switch." msgstr "获取开关的状态。" -#: ../../en/m5ui/switch.rst:106 a546270ad5ad4f1d8970c2024216ac2c +#: ../../en/m5ui/switch.rst:100 5fad4580ea9849d28f1bb41bc4a63a0f msgid "The state to get." msgstr "要获取的状态。" -#: ../../en/m5ui/switch.rst:107 a239a42ae833475480dc1ee8cafdd9ec +#: ../../en/m5ui/switch.rst d6c7424cd27f4d029e3d82a739911b89 +msgid "Returns" +msgstr "" + +#: ../../en/m5ui/switch.rst:101 c3c08073c58a468cb7abaef67fee59d6 msgid "True if the state is set, False otherwise." msgstr "若状态已设置则返回 True,否则返回 False。" -#: ../../en/m5ui/switch.rst d413beb2ddbc4709801204011f1af9ce +#: ../../en/m5ui/switch.rst e19f7b6cfa9044b797d56b47d26bf7d0 msgid "Return type" msgstr "返回类型" -#: ../../en/m5ui/switch.rst:112 be35675f7e704862b6a31ef04c966037 +#: ../../en/m5ui/switch.rst:106 07083f5a56594ec7a120745c5acb172e msgid "|has_state.png|" msgstr "" -#: ../../en/refs/m5ui.switch.ref:3 5768c2fa1d194646ba7d44efc3ef92a5 +#: ../../en/refs/m5ui.switch.ref:3 8e40ca32396d46df88a7281a95f0fb8f msgid "has_state.png" msgstr "" -#: ../../en/m5ui/switch.rst:122 c8c692c7dcde4f9d8c3ae04e388c20dc +#: ../../en/m5ui/switch.rst:116 f1f969fd45364266bc307653bb65e1b8 msgid "Set the position of the switch." msgstr "设置开关的位置。" -#: ../../en/m5ui/switch.rst:124 ../../en/m5ui/switch.rst:142 -#: 7e7d4f99e24f4abe88dc15233a92e2cf f9b03d88a0b949a8ab091946c8295688 +#: ../../en/m5ui/switch.rst:118 ../../en/m5ui/switch.rst:135 +#: 4513d281decd4ebdb178c447a4e02bc3 4b864c9fd5d74401b1a3bc83fb6fb086 msgid "The x-coordinate of the switch." msgstr "开关的 x 坐标。" -#: ../../en/m5ui/switch.rst:125 ../../en/m5ui/switch.rst:159 -#: c9913dab3f70421093a7a42d8d8dd406 e23e3ca7bbd04270813db1cdd11278ed +#: ../../en/m5ui/switch.rst:119 ../../en/m5ui/switch.rst:151 +#: c424314e3c7242c8a0f056e293cc1e1f msgid "The y-coordinate of the switch." msgstr "开关的 y 坐标。" -#: ../../en/m5ui/switch.rst:130 9354fb27fb4c47579053924ca3726022 +#: ../../en/m5ui/switch.rst:123 663d57d0a5b041c3ae6cf04c19d2cad6 msgid "|set_pos.png|" msgstr "" -#: ../../en/refs/m5ui.switch.ref:7 379764c9fce443479fbc3ac603f384a9 +#: ../../en/refs/m5ui.switch.ref:6 1f5a1a27acb94b49b82452009e464d7f msgid "set_pos.png" msgstr "" -#: ../../en/m5ui/switch.rst:140 f19c85b772144063b5649f922d8363ff +#: ../../en/m5ui/switch.rst:133 d6b7458bedb24817bb5f389f382317b1 msgid "Set the x-coordinate of the switch." msgstr "设置开关的 x 坐标。" -#: ../../en/m5ui/switch.rst:147 f5107c8219074e5baef497e022eee811 +#: ../../en/m5ui/switch.rst:139 655baf546cdf4d10985351e4ef47ac4a msgid "|set_x.png|" msgstr "" -#: ../../en/refs/m5ui.switch.ref:13 e07cc4171bda4dd38589694dd899803c +#: ../../en/refs/m5ui.switch.ref:11 c35e8a17d502467ca00812fcd51322c3 msgid "set_x.png" msgstr "" -#: ../../en/m5ui/switch.rst:157 4d99ecb7c44f4139bc528d54f60e0fe7 +#: ../../en/m5ui/switch.rst:149 8c03f620e524427983796f55acaea35f msgid "Set the y-coordinate of the switch." msgstr "设置开关的 y 坐标。" -#: ../../en/m5ui/switch.rst:164 ad968ee9f52049749994ed65a717d555 +#: ../../en/m5ui/switch.rst:155 3990e1451465487395f75da7c195e117 msgid "|set_y.png|" msgstr "" -#: ../../en/refs/m5ui.switch.ref:14 2461393db1e6476d9f8f80546192cd64 +#: ../../en/refs/m5ui.switch.ref:12 fd4d9af0eb58409396f3def9f3e199a9 msgid "set_y.png" msgstr "" -#: ../../en/m5ui/switch.rst:174 fd8d1cfd7d8940f984765fa1f82fa8eb +#: ../../en/m5ui/switch.rst:165 36a521e991f044e5bdb9d3e33fa7e852 msgid "Set the size of the switch." msgstr "设置开关的尺寸。" -#: ../../en/m5ui/switch.rst:176 ../../en/m5ui/switch.rst:194 -#: 79010dacfc20429cb530555c8924faf0 a012dd7a36ee44deafa9ea7c06ff1feb -msgid "The width of the switch." -msgstr "开关的宽度。" - -#: ../../en/m5ui/switch.rst:177 ../../en/m5ui/switch.rst:228 -#: 064b526b488843c6b99193d0d180da6b c9e4f5820c5842159b31f077b3643993 -msgid "The height of the switch." -msgstr "开关的高度。" - -#: ../../en/m5ui/switch.rst:182 d3b4a47d886a46f0b2368fbc33c86ea1 +#: ../../en/m5ui/switch.rst:172 771a26ec1b6b45b9904713b3ab91f914 msgid "|set_size.png|" msgstr "" -#: ../../en/refs/m5ui.switch.ref:8 5e8424792d59402eb45a692733b7f812 +#: ../../en/refs/m5ui.switch.ref:7 d4b5e63027514d6a91d72a0c0c6bbf32 msgid "set_size.png" msgstr "" -#: ../../en/m5ui/switch.rst:192 c64fd4a7fc294fdc923dd111de7fe485 +#: ../../en/m5ui/switch.rst:182 cc08c6044a2d47e28e7468e2f9b2c57c msgid "Set the width of the switch." msgstr "设置开关的宽度。" -#: ../../en/m5ui/switch.rst:199 b5ee9c5345354f7eb55ee65b7f5f3988 +#: ../../en/m5ui/switch.rst:188 c323104cca18496690817f8ad32124a3 msgid "|set_width.png|" msgstr "" -#: ../../en/refs/m5ui.switch.ref:12 5005fb7afa8b455688f290503a303699 +#: ../../en/refs/m5ui.switch.ref:10 dee314103b554ea0ad9f7f33c551879c msgid "set_width.png" msgstr "" -#: ../../en/m5ui/switch.rst:226 0c8d53f532a04212bec52a9218366cd4 +#: ../../en/m5ui/switch.rst:215 604933f50f1747c88bde9cbd12973abf msgid "Set the height of the switch." msgstr "设置开关的高度。" -#: ../../en/m5ui/switch.rst:233 18b0e98c1b1a4869a2497264483fa7e6 +#: ../../en/m5ui/switch.rst:221 324c0806684d46a4ab68e1a136a58729 msgid "|set_height.png|" msgstr "" -#: ../../en/refs/m5ui.switch.ref:6 c0bd8d06c83f4c36a6683d0e829ae241 +#: ../../en/refs/m5ui.switch.ref:5 1bcafaaf9ae04687bf439edc277af428 msgid "set_height.png" msgstr "" -#: ../../en/m5ui/switch.rst:260 9e52c48b59ea442f92020c5d3c0cfd8b +#: ../../en/m5ui/switch.rst:248 4a1aa3181d1f4f6b9bd06c76f777903a msgid "Align the switch to another object." msgstr "将开关对齐到另一个对象。" -#: ../../en/m5ui/switch.rst:262 23eda6e5670b41dfbf52d35fb46298bd +#: ../../en/m5ui/switch.rst:250 ecc04d1a1af347d0aab45118d57dae4c msgid "The object to align to." msgstr "要对齐到的对象。" -#: ../../en/m5ui/switch.rst:263 4c6816a83b0c4425aaeb356d801ab185 +#: ../../en/m5ui/switch.rst:251 4f9e52bc8359451e8f0a07d229379c53 msgid "The alignment type." msgstr "对齐类型。" -#: ../../en/m5ui/switch.rst:264 7ab11305d2844166827a925dbc613258 +#: ../../en/m5ui/switch.rst:252 56f140876d96404fa1525565a74411b9 msgid "The x-offset from the aligned object." msgstr "相对于对齐对象的 x 偏移量。" -#: ../../en/m5ui/switch.rst:265 20d0c0d3c9f142b78dc5b900177b10e5 +#: ../../en/m5ui/switch.rst:253 ef7f35f2f2f547ffb09b445a275eea4b msgid "The y-offset from the aligned object." msgstr "相对于对齐对象的 y 偏移量。" -#: ../../en/m5ui/switch.rst:270 b12d9fe7086a442e954be27ea8fa1d80 +#: ../../en/m5ui/switch.rst:257 92cc615922ac4e7ea49bd0f9ce35a071 msgid "|align_to.png|" msgstr "" -#: ../../en/refs/m5ui.switch.ref:1 afba3bd73dac48579a7fc40c12c722e2 +#: ../../en/refs/m5ui.switch.ref:1 5847cd1c39264c5da8d98e35319fcafb msgid "align_to.png" msgstr "" -#: ../../en/m5ui/switch.rst:280 b4ea4bec4f404c29b5bcd321510c3177 +#: ../../en/m5ui/switch.rst:267 443a8ce774d242d98bfa9fee91c15b27 msgid "" "Add an event callback to the switch. The callback will be called when the" " specified event occurs." msgstr "为开关添加事件回调;当指定事件发生时将调用该回调。" -#: ../../en/m5ui/switch.rst:282 715794ce79f2430eac35d80ae17137dc +#: ../../en/m5ui/switch.rst:269 04c5f3a4769841d8acc5c83b6463d345 msgid "The callback function to call." msgstr "要调用的回调函数。" -#: ../../en/m5ui/switch.rst:283 70b470e696224c95b5b768225c4591f4 +#: ../../en/m5ui/switch.rst:270 ce8945dc7b2e437da2d0629eac2992fa msgid "The event to listen for." msgstr "要监听的事件。" -#: ../../en/m5ui/switch.rst:284 a9e2c4f962fb4957969a51effe2e2389 +#: ../../en/m5ui/switch.rst:271 71d200d088b74629b8653c474d6dd116 msgid "Optional user data to pass to the callback." msgstr "可选的用户数据,将传递给回调函数。" -#: ../../en/m5ui/switch.rst:289 1b26a6816bb843c28fb11298e766a040 +#: ../../en/m5ui/switch.rst:275 e3c2554840124ecab01610b2133fed4f msgid "|event.png|" msgstr "" -#: ../../en/refs/m5ui.switch.ref:2 5a7859c39560401a952fe530d9385eb3 +#: ../../en/refs/m5ui.switch.ref:2 8a21181a990c445f8ddd545d3d98af3e msgid "event.png" msgstr "" +#: f1f969fd45364266bc307653bb65e1b8 m5ui.switch.M5Switch.set_direction:1 of +msgid "Set the direction of the switch." +msgstr "设置开关的方向。" + +#: 789eeffb3eeb400d8589e5b831b1ced3 m5ui.switch.M5Switch.set_direction:3 of +msgid "The direction of the switch." +msgstr "开关的方向。" + +#: 8f80aeb0c4da4e79a5874c4b54691d66 m5ui.switch.M5Switch.set_direction:5 of +msgid "Options:" +msgstr "选项:" + +#: b116f490368f433fa1828b1082ea427e m5ui.switch.M5Switch.set_direction:7 of +msgid "0: Horizontal" +msgstr "0:水平" + +#: 3f5c4a220b88413b9e0853cd81999a80 m5ui.switch.M5Switch.set_direction:8 of +msgid "1: Vertical" +msgstr "1:垂直" + +#: 54b7be5e25be4fd899038419f17df284 m5ui.switch.M5Switch.set_direction:10 of +#, fuzzy +msgid "UIFlow2 Code Block:" +msgstr "UiFlow2 代码块:" + +#: 10efc8e2f146418494dc2fac22943387 m5ui.switch.M5Switch.set_direction:12 of +msgid "|set_direction.png|" +msgstr "" + +#: ../../en/refs/m5ui.switch.ref:13 343654da75cd419db54c93b4b9388943 +msgid "set_direction.png" +msgstr "" + +#~ msgid "|set_bg_color_default.png|" +#~ msgstr "" + +#~ msgid "set_bg_color_default.png" +#~ msgstr "" + +#~ msgid "|set_bg_color_checked.png|" +#~ msgstr "" + +#~ msgid "set_bg_color_checked.png" +#~ msgstr "" + +#~ msgid "|set_knob_color_checked.png|" +#~ msgstr "" + +#~ msgid "set_knob_color_checked.png" +#~ msgstr "" + +#~ msgid "|set_knob_color_default.png|" +#~ msgstr "" + +#~ msgid "set_knob_color_default.png" +#~ msgstr "" + diff --git a/m5stack/libs/m5ui/switch.py b/m5stack/libs/m5ui/switch.py index 01f4bd81..6333b76a 100644 --- a/m5stack/libs/m5ui/switch.py +++ b/m5stack/libs/m5ui/switch.py @@ -2,7 +2,7 @@ # # SPDX-License-Identifier: MIT -from .base import M5Base +from m5ui.base import M5Base import lvgl as lv @@ -52,6 +52,42 @@ def __init__( self.set_bg_color(bg_c_checked, 255, lv.PART.INDICATOR | lv.STATE.CHECKED) self.set_bg_color(circle_c, 255, lv.PART.KNOB | lv.STATE.DEFAULT) self.set_size(w, h) + self.width = w + self.height = h + + def set_direction(self, direction): + """Set the direction of the switch. + + :param int direction: The direction of the switch. + + Options: + + - 0: Horizontal + - 1: Vertical + + UIFlow2 Code Block: + + |set_direction.png| + + MicroPython Code Block: + + .. code-block:: python + + switch_0.set_direction(0) # Set to horizontal + switch_0.set_direction(1) # Set to vertical + """ + + cur_w = self.width + cur_h = self.height + if direction == 0: + self.set_size(max(cur_w, cur_h), min(cur_w, cur_h)) + else: + self.set_size(min(cur_w, cur_h), max(cur_w, cur_h)) + + def set_size(self, w, h): + super().set_size(w, h) + self.width = w + self.height = h def __getattr__(self, name): if hasattr(M5Base, name): From 44d17a1a3cb7e149314ddac84204c43cabb6a5e1 Mon Sep 17 00:00:00 2001 From: lbuque <1102390310@qq.com> Date: Tue, 5 Aug 2025 10:40:21 +0800 Subject: [PATCH 192/322] libs/modbus: Unit ID mismatch problem. Signed-off-by: lbuque <1102390310@qq.com> --- m5stack/libs/modbus/modbus/frame.py | 206 +++++++------ m5stack/libs/modbus/modbus/master.py | 430 ++++++++++++--------------- m5stack/libs/modbus/modbus/slave.py | 288 ++++++++++++------ 3 files changed, 487 insertions(+), 437 deletions(-) diff --git a/m5stack/libs/modbus/modbus/frame.py b/m5stack/libs/modbus/modbus/frame.py index 162777db..17c82b00 100644 --- a/m5stack/libs/modbus/modbus/frame.py +++ b/m5stack/libs/modbus/modbus/frame.py @@ -7,23 +7,27 @@ class ModbusFrame: def __init__( self, - func_code=0, - register=None, - fr_type="request", - length=None, - data=None, - error_code=None, - ): + func_code: int = 0, + register: int = None, + fr_type: str = "request", + length: int = None, + data: bytearray = None, + error_code: int = None, + ) -> None: """Init a generic modbus frame. - Args: - func_code (int, optional): Function Code. Defaults to 0. - register (int, optional): Register. Defaults to None. - fr_type (str, optional): Type of Frame (request or response). - Defaults to "request". - length (int, optional): Number of registers. Defaults to None. - data (bytearray, optional): Data. Defaults to None. - error_code (int, optional): Error Code. Defaults to None. + :param int func_code: Function code (1-6,15,16). Defaults to 0. + :param register: Register. Defaults to None. + :type register: int or None + :param fr_type: Frame type (request/response). Defaults to "request". + :type fr_type: str or None + :param length: Length of requested data. Defaults to None. + :type length: int or None + :param data: Payload. Defaults to None. + :type data: bytearray or None + :param error_code: Error Code. Defaults to None. + :type error_code: int or None + """ self.type = fr_type @@ -108,7 +112,7 @@ def __init__( self.pdu = None self.frame = None - def _create_pdu(self): + def _create_pdu(self) -> None: self.pdu = bytearray([]) self.pdu += bytearray([self.func_code]) @@ -133,40 +137,40 @@ def _create_pdu(self): if self.func_code in [0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x8F, 0x90]: self.pdu += bytearray([self.error_code]) - def _create_frame(self): + def _create_frame(self) -> None: """Override in derived Class""" pass - def get_frame(self): + def get_frame(self) -> bytearray: if self.frame is None: self._create_frame() return self.frame class ModbusRTUFrame(ModbusFrame): - def __init__(self, device_addr=0, verbose: bool = False, *args, **kwargs): + def __init__(self, device_addr: int = 0, verbose: bool = False, *args, **kwargs) -> None: """Create modbus rtu frame. - Args: - device_addr (int, optional): Slave address. Defaults to 0. - func_code (int, optional): Function code (1-6,15,16). Defaults to 0. - register ([type], optional): Register. Defaults to None. - fr_type (str, optional): Frame type (request/response). Defaults to "request". - length ([type], optional): Length of reqested data. Defaults to None. - data ([type], optional): Payload. Defaults to None. + :param int device_addr: Slave address. Defaults to 0. + :param bool verbose: If True, print debug information. Defaults to False. + :param int func_code: Function code (1-6,15,16). Defaults to 0. + :param int register: Register. Defaults to None. + :param str fr_type: Frame type (request/response). Defaults to "request". + :param int length: Length of requested data. Defaults to None. + :param bytes data: Payload. Defaults to None. """ super(ModbusRTUFrame, self).__init__(*args, **kwargs) self._verbose = verbose self.device_addr = device_addr self.frame = None - def _create_frame(self): + def _create_frame(self) -> None: self._create_pdu() self.frame = bytearray([self.device_addr]) + self.pdu crc = ModbusRTUFrame._crc16(self.frame) self.frame += bytearray([crc & 0xFF, crc >> 8]) - def __str__(self): + def __str__(self) -> str: return "".format( self.type, self.device_addr, @@ -175,7 +179,7 @@ def __str__(self): ) @classmethod - def _crc16(cls, data): + def _crc16(cls, data: bytearray) -> int: offset = 0 length = len(data) if data is None or offset < 0 or offset > len(data) - 1 and offset + length > len(data): @@ -191,15 +195,18 @@ def _crc16(cls, data): return crc @classmethod - def parse_frame(cls, frame, fr_type=None, verbose=False): - """Factory Method: Create a ModbusRTUFrame from bytearray. - - Args: - frame (bytearray): frame to parse. - fr_type (str, optional): request, response, or None - - Returns: - ModbusRTUFrame: parsed frame + def parse_frame( + cls, frame: bytearray, fr_type: str = None, verbose: bool = False + ) -> "ModbusRTUFrame": + """Create a ModbusRTUFrame from bytearray. + + :param bytearray frame: The frame to parse. + :param str fr_type: Type of frame, either "request", "response", or + None. If None, it will try to determine the type + automatically. + :param bool verbose: If True, print debug information. + :return: ModbusRTUFrame object if parsing was successful, None otherwise. + :rtype: ModbusRTUFrame or None """ verbose and print("Parsing RTU frame: " + " ".join(["{:02x}".format(x) for x in frame])) @@ -278,15 +285,14 @@ def parse_frame(cls, frame, fr_type=None, verbose=False): # raise ValueError("Could not parse Frame " + " ".join(["{:02x}".format(x) for x in frame])) @classmethod - def _check_both(cls, frame): - """Parser-Helper: if func_code is 0x05 or 0x06, we can't decide if it is a - request or a response. This method checks, if is a valid 0x05- or 0x06-frame. + def _check_both(cls, frame: bytearray) -> bool: + """if func_code is 0x05 or 0x06, we can't decide if it is a request or a + response. This method checks, if is a valid 0x05- or 0x06-frame. - Args: - frame (bytearray): The frame to check. + :param bytearray frame: The frame to check. - Returns: - bool: True, if it is a valid 0x05- or 0x06-frame. + :return: True, if it is a valid 0x05- or 0x06-frame. + :rtype: bool """ try: func_code = frame[1] @@ -297,15 +303,13 @@ def _check_both(cls, frame): return False @classmethod - def _check_request(cls, frame): - """Parser-Helper: This method checks, if is a valid request. It returns False, + def _check_request(cls, frame: bytearray) -> bool: + """This method checks, if is a valid request. It returns False, if the frame could be a request or a response. - Args: - frame (bytearray): The frame to check. - - Returns: - bool: True, if it is a valid request. + :param bytearray frame: The frame to check. + :return: True, if it is a valid request. + :rtype: bool """ try: func_code = frame[1] @@ -321,15 +325,13 @@ def _check_request(cls, frame): return False @classmethod - def _check_response(cls, frame): - """Parser-Helper: This method checks, if is a valid response. It returns False, + def _check_response(cls, frame: bytearray) -> bool: + """This method checks, if is a valid response. It returns False, if the frame could be a request or a response. - Args: - frame (bytearray): The frame to check. - - Returns: - bool: True, if it is a valid response. + :param bytearray frame: The frame to check. + :return: True, if it is a valid response. + :rtype: bool """ try: func_code = frame[1] @@ -345,14 +347,12 @@ def _check_response(cls, frame): return False @classmethod - def transform_frame(cls, tcp_frame): + def transform_frame(cls, tcp_frame: "ModbusTCPFrame") -> "ModbusRTUFrame": """transform a tcp-frame to a rtu-frame and copy all data - Args: - tcp_frame (ModbusTCPFrame): tcp-frame - - Returns: - ModbusRTUFrame + :param ModbusTCPFrame tcp_frame: tcp-frame to transform + :return: ModbusRTUFrame object with data from tcp_frame + :rtype: ModbusRTUFrame """ frame = ModbusRTUFrame() frame.data = tcp_frame.data @@ -365,24 +365,27 @@ def transform_frame(cls, tcp_frame): class ModbusTCPFrame(ModbusFrame): - def __init__(self, transaction_id=0, unit_id=0, *args, **kwargs): + def __init__(self, transaction_id: int = 0, unit_id: int = 0, *args, **kwargs) -> None: """Create modbus tcp frame. - Args: - transaction_id (int, optional): Transaction ID. Defaults to 0. - unit_id (int, optional): Unit address. Defaults to 0. - func_code (int, optional): Function code (1-6,15,16). Defaults to 0. - register ([type], optional): Register. Defaults to None. - fr_type (str, optional): Frame type (request/response). Defaults to "request". - length ([type], optional): Length of reqested data. Defaults to None. - data ([type], optional): Payload. Defaults to None. + :param int transaction_id: Transaction ID. Defaults to 0. + :param int unit_id: Unit address. Defaults to 0. + :param int func_code: Function code (1-6,15,16). Defaults to 0. + :param register: Register. Defaults to None. + :type register: int or None + :param fr_type: Frame type (request/response). Defaults to "request". + :type fr_type: str or None + :param length: Length of requested data. Defaults to None. + :type length: int or None + :param data: Payload. Defaults to None. + :type data: bytearray or None """ super(ModbusTCPFrame, self).__init__(*args, **kwargs) self.transaction_id = transaction_id self.unit_id = unit_id self.frame = None - def _create_frame(self): + def _create_frame(self) -> None: self._create_pdu() self.frame = bytearray([self.transaction_id >> 8, self.transaction_id & 0xFF]) self.frame += bytearray([0x00, 0x00]) # Protocol ID @@ -391,20 +394,19 @@ def _create_frame(self): self.frame += bytearray([self.unit_id]) self.frame += self.pdu - def __str__(self): + def __str__(self) -> str: return "".format( self.type, self.func_code, " ".join(["{:02x}".format(x) for x in self.get_frame()]) ) @classmethod - def parse_frame(cls, frame, verbose=False): - """Factory Method: Create a ModbusTCPFrame from bytearray. + def parse_frame(cls, frame: bytearray, verbose: bool = False) -> "ModbusTCPFrame": + """Create a ModbusTCPFrame from bytearray. - Args: - frame (bytearray): frame to parse. - - Returns: - ModbusTCPFrame: parsed frame + :param bytearray frame: The frame to parse. + :param bool verbose: If True, print debug information. + :return: ModbusTCPFrame object if parsing was successful, None otherwise. + :rtype: ModbusTCPFrame or None """ verbose and print("Parsing TCP frame: " + " ".join(["{:02x}".format(x) for x in frame])) @@ -454,7 +456,7 @@ def parse_frame(cls, frame, verbose=False): f = None if cls._check_response(frame, length): - verbose and print("frame is request") + verbose and print("frame is response") if func_code in [0x01, 0x02, 0x03, 0x04]: bc = frame[8] data = frame[9 : 9 + bc] @@ -492,15 +494,13 @@ def parse_frame(cls, frame, verbose=False): # raise ValueError("Could not parse Frame " + " ".join(["{:02x}".format(x) for x in frame])) @classmethod - def _check_both(cls, frame, length): - """Parser-Helper: if func_code is 0x05 or 0x06, we can't decide if it is a + def _check_both(cls, frame: bytearray, length: int) -> bool: + """if func_code is 0x05 or 0x06, we can't decide if it is a request or a response. This method checks, if is a valid 0x05- or 0x06-frame. - Args: - frame (bytearray): The frame to check. - - Returns: - bool: True, if it is a valid 0x05- or 0x06-frame. + :param bytearray frame: The frame to check. + :returns: True, if it is a valid 0x05- or 0x06-frame. + :rtype: bool """ try: func_code = frame[7] @@ -511,15 +511,13 @@ def _check_both(cls, frame, length): return False @classmethod - def _check_request(cls, frame, length): - """Parser-Helper: This method checks, if is a valid request. It returns False, + def _check_request(cls, frame: bytearray, length: int) -> bool: + """This method checks, if is a valid request. It returns False, if the frame could be a request or a response. - Args: - frame (bytearray): The frame to check. - - Returns: - bool: True, if it is a valid request. + :param bytearray frame: The frame to check. + :returns: True, if it is a valid request. + :rtype: bool """ try: func_code = frame[7] @@ -535,15 +533,13 @@ def _check_request(cls, frame, length): return False @classmethod - def _check_response(cls, frame, length): - """Parser-Helper: This method checks, if is a valid response. It returns False, + def _check_response(cls, frame: bytearray, length: int) -> bool: + """This method checks, if is a valid response. It returns False, if the frame could be a request or a response. - Args: - frame (bytearray): The frame to check. - - Returns: - bool: True, if it is a valid response. + :param bytearray frame: The frame to check. + :returns: True, if it is a valid response. + :rtype: bool """ try: func_code = frame[7] diff --git a/m5stack/libs/modbus/modbus/master.py b/m5stack/libs/modbus/modbus/master.py index 4420c53a..0693afa2 100644 --- a/m5stack/libs/modbus/modbus/master.py +++ b/m5stack/libs/modbus/modbus/master.py @@ -23,47 +23,44 @@ class ModbusMaster: - def __init__(self, ms_type: str = "tcp"): - """ - Basic Modbus Master class + def __init__(self, ms_type: str = "tcp") -> None: + """Basic Modbus Master class :param str ms_type: Modbus type (tcp or rtu) """ self.ms_type = ms_type def _send(self, frame: ModbusFrame, timeout: int = 2000) -> ModbusFrame: - """ - MUST BE OVERRIDDEN - - Args: - frame (ModbusFrame): the frame to send + """MUST BE OVERRIDDEN - Returns: - ModbusFrame: response + :param ModbusFrame frame: the frame to send + :param int timeout: timeout in milliseconds + :returns: response + :rtype: ModbusFrame + :raises: NotImplementedError """ raise NotImplementedError async def _send_async(self, frame: ModbusFrame, timeout: int = 2000) -> ModbusFrame: """MUST BE OVERRIDDEN (async) - Args: - frame (ModbusFrame): the frame to send - - Returns: - ModbusFrame: response + :param ModbusFrame frame: the frame to send + :param int timeout: timeout in milliseconds + :returns: response + :rtype: ModbusFrame + :raises: NotImplementedError """ raise NotImplementedError def read_coils(self, address: int, register: int, quantity: int, timeout: int = 2000) -> list: """Read a number (quantity) of coils - Args: - address (int): slave address - register (int): start register - quantity (int): number of coils + :param int address: slave address + :param int register: start register + :param int quantity: number of coils - Returns: - list: response + :returns: response + :rtype: list """ data = self._read_registers(address, register, quantity, 1, timeout=timeout) if data is None: @@ -79,13 +76,12 @@ async def read_coils_async( ) -> list: """Read a number (quantity) of coils (async) - Args: - address (int): slave address - register (int): start register - quantity (int): number of coils + :param int address: slave address + :param int register: start register + :param int quantity: number of coils - Returns: - list: response + :returns: response + :rtype: list """ data = await self._read_registers_async(address, register, quantity, 1, timeout=timeout) if data is None: @@ -101,13 +97,12 @@ def read_discrete_inputs( ) -> list: """Read a number (quantity) of digital inputs - Args: - address (int): slave address - register (int): start register - quantity (int): number of inputs + :param int address: slave address + :param int register: start register + :param int quantity: number of inputs - Returns: - list: response + :returns: response + :rtype: list """ data = self._read_registers(address, register, quantity, 2, timeout=timeout) if data is None: @@ -123,13 +118,12 @@ async def read_discrete_inputs_async( ) -> list: """Read a number (quantity) of digital inputs (async) - Args: - address (int): slave address - register (int): start register - quantity (int): number of inputs + :param int address: slave address + :param int register: start register + :param int quantity: number of inputs - Returns: - list: response + :returns: response + :rtype: list """ data = await self._read_registers_async(address, register, quantity, 2, timeout=timeout) if data is None: @@ -145,13 +139,12 @@ def read_holding_registers( ) -> list: """Read a number (quantity) of holding registers - Args: - address (int): slave address - register (int): start register - quantity (int): number of holding registers + :param int address: slave address + :param int register: start register + :param int quantity: number of holding registers - Returns: - list: response + :returns: response + :rtype: list """ data = self._read_registers(address, register, quantity, 3, timeout=timeout) if data is None: @@ -167,13 +160,12 @@ async def read_holding_registers_async( ) -> list: """Read a number (quantity) of holding registers (async) - Args: - address (int): slave address - register (int): start register - quantity (int): number of holding registers - - Returns: - list: response + :param int address: slave address + :param int register: start register + :param int quantity: number of holding registers + :param int timeout: timeout in milliseconds + :returns: response + :rtype: list """ data = await self._read_registers_async(address, register, quantity, 3, timeout=timeout) if data is None: @@ -189,13 +181,11 @@ def read_input_registers( ) -> list: """Read a number (quantity) of input registers - Args: - address (int): slave address - register (int): start register - quantity (int): number of input registers - - Returns: - list: response + :param int address: slave address + :param int register: start register + :param int quantity: number of input registers + :returns: response + :rtype: list """ data = self._read_registers(address, register, quantity, 4, timeout=timeout) if data is None: @@ -210,13 +200,13 @@ async def read_input_registers_async( ) -> list: """Read a number (quantity) of input registers (async) - Args: - address (int): slave address - register (int): start register - quantity (int): number of input registers + :param int address: slave address + :param int register: start register + :param int quantity: number of input registers + :param int timeout: timeout in milliseconds - Returns: - list: response + :returns: response + :rtype: list """ data = await self._read_registers_async(address, register, quantity, 4, timeout=timeout) if data is None: @@ -231,17 +221,19 @@ def _read_registers( ) -> bytearray: """private helper function - Args: - register (int): start register - quantity (int): number of registers - code (int): function code + :param int address: slave address + :param int register: start register + :param int quantity: number of registers + :param int code: function code + :param int timeout: timeout in milliseconds - Returns: - bytearray: response + :returns: response + :rtype: bytearray """ if self.ms_type == "tcp": f = ModbusTCPFrame( transaction_id=self.ti, + unit_id=address, func_code=code, register=register, fr_type="request", @@ -274,14 +266,13 @@ async def _read_registers_async( ) -> bytearray: """private helper function (asnc) - Args: - address (int): slave address - register (int): start register - quantity (int): number of registers - code (int): function code - - Returns: - bytearray: response + :param int address: slave address + :param int register: start register + :param int quantity: number of registers + :param int code: function code + :param int timeout: timeout in milliseconds + :returns: response + :rtype: bytearray """ if self.ms_type == "tcp": f = ModbusTCPFrame( @@ -318,17 +309,14 @@ def write_single_coil( ) -> bool: """Write a coil - Args: - address (int): slave address - register (int): register of the coil - value (bool or int or str): True/1/"on"/"ON"/"On" or False/0/"off"/"OFF"/"Off" - - Raises: - ValueError: if value is not valid - ValueError: if coil can't be written - - Returns: - bool: The value of the coil + :param int address: slave address + :param int register: register of the coil + :param value: the value to write + :type value: bool or int or str + :param int timeout: timeout in milliseconds + :returns: response + :rtype: bool + :raises: ValueError if value is not valid or coil can't be written """ if value in [True, 1, "on", "ON", "On"]: data = bytearray([0xFF, 0x00]) @@ -346,16 +334,15 @@ async def write_single_coil_async( ) -> bool: """Write a coil (async) - Args: - register (int): register of the coil - value (bool or int or str): True/1/"on"/"ON"/"On" or False/0/"off"/"OFF"/"Off" - - Raises: - ValueError: if value is not valid - ValueError: if coil can't be written + :param int address: slave address + :param int register: register of the coil + :param value: the value to write + :type value: bool or int or str + :param int timeout: timeout in milliseconds + :returns: response + :rtype: bool - Returns: - bool: The value of the coil + :raises: ValueError if value is not valid or coil can't be written """ if value in [True, 1, "on", "ON", "On"]: data = bytearray([0xFF, 0x00]) @@ -371,16 +358,12 @@ def write_single_register( ) -> int: """Write a holding register - Args: - address (int): slave address - register (int): register of the holding register - value (int): value - - Raises: - ValueError: if register can't be written - - Returns: - int: the written value + :param int address: slave address + :param int register: register of the holding register + :param int value: value to write + :param int timeout: timeout in milliseconds + :returns: the written value + :rtype: int """ resp = self._write_registers( address, register, 1, value.to_bytes(2, "big"), 6, timeout=timeout @@ -394,15 +377,13 @@ async def write_single_register_async( ) -> int: """Write a holding register (async) - Args: - register (int): register of the holding register - value (int): value - - Raises: - ValueError: if register can't be written - - Returns: - int: the written value + :param int address: slave address + :param int register: register of the holding register + :param int value: value to write + :param int timeout: timeout in milliseconds + :returns: the written value + :rtype: int + :raises: ValueError if register can't be written """ data = await self._write_registers_async( address, register, 1, value.to_bytes(2, "big"), 6, timeout=timeout @@ -416,16 +397,13 @@ def write_multiple_coils( ) -> int: """Write multiple coils - Args: - address (int): slave address - register (int): start register of the coils - value (list): value - - Raises: - ValueError: if coils can't be written - - Returns: - list: the value of the coils + :param int address: slave address + :param int register: start register of the coils + :param list value: value + :param int timeout: timeout in milliseconds + :returns: the value of the coils + :rtype: int + :raises: ValueError if coils can't be written """ new_value = bytearray(math.ceil(len(value) / 8)) for i in range(len(value)): @@ -441,16 +419,12 @@ async def write_multiple_coils_async( ) -> list: """Write multiple coils (async) - Args: - address (int): slave address - register (int): start register of the coils - value (list): value - - Raises: - ValueError: if coils can't be written - - Returns: - int: the value of the coils + :param int address: slave address + :param int register: start register of the coils + :param list value: value + :param int timeout: timeout in milliseconds + :returns: the value of the coils + :raises: ValueError if coils can't be written """ new_value = bytearray(math.ceil(len(value) / 8)) for i in range(len(value)): @@ -465,16 +439,13 @@ def write_multiple_registers( ) -> list: """Write multiple registers - Args: - address (int): slave address - register (int): start register - value (list): value - - Raises: - ValueError: if registers can't be written - - Returns: - list: the written value + :param int address: slave address + :param int register: start register + :param list value: value + :param int timeout: timeout in milliseconds + :returns: the written value + :rtype: list + :raises: ValueError if registers can't be written """ new_value = bytearray(len(value) * 2) for i in range(len(value)): @@ -489,16 +460,13 @@ async def write_multiple_registers_async( ) -> list: """Write multiple registers (async) - Args: - address (int): slave address - register (int): start register - value (list): value - - Raises: - ValueError: if registers can't be written - - Returns: - list: the written value + :param int address: slave address + :param int register: start register + :param list value: value + :param int timeout: timeout in milliseconds + :returns: the written value + :rtype: list + :raises: ValueError if registers can't be written """ new_value = bytearray(len(value) * 2) for i in range(len(value)): @@ -517,20 +485,17 @@ def _write_registers( code: int, timeout: int = 2000, ) -> bytearray or int: - """Helper function - - Args: - address (int): slave address - register (int): register - quantity (int): quantity - value (bytearray): value - code (int): function code - - Raises: - ValueError: if value can't be written - - Returns: - bytearray or int: data or quantity (depending on function code) + """Helper function to write registers + + :param int address: slave address + :param int register: register + :param int quantity: quantity of registers + :param bytearray value: value to write + :param int code: function code + :param int timeout: timeout in milliseconds + :returns: data or quantity (depending on function code) + :rtype: bytearray or int + :raises: ValueError if value can't be written """ f = None if self.ms_type == "tcp": @@ -621,19 +586,17 @@ async def _write_registers_async( code: int, timeout: int = 2000, ) -> bytearray or int: - """Helper function (async) - - Args: - register (int): register - quantity (int): quantity - value (bytearray): value - code (int): function code - - Raises: - ValueError: if value can't be written - - Returns: - bytearray or int: data or quantity (depending on function code) + """Helper function to write registers + + :param int address: slave address + :param int register: register + :param int quantity: quantity of registers + :param bytearray value: value to write + :param int code: function code + :param int timeout: timeout in milliseconds + :returns: data or quantity (depending on function code) + :rtype: bytearray or int + :raises: ValueError if value can't be written """ f = None if self.ms_type == "tcp": @@ -712,12 +675,12 @@ async def _write_registers_async( class ModbusTCPClient(ModbusMaster): - def __init__(self, host: str, port: int = 502, verbose=False, *args, **kwargs): + def __init__(self, host: str, port: int = 502, verbose=False, *args, **kwargs) -> None: """Init a modbus tcp client - Args: - host (str): IP or Hostname - port (int): Port Defaults to 502 + :param str host: IP or Hostname of the modbus server + :param int port: Port of the modbus server, defaults to 502 + :param bool verbose: If True, print debug messages """ self.host = host @@ -762,11 +725,10 @@ def _exit_read(self, response: bytearray) -> bool: async def _send_async(self, frame: bytearray, timeout: int = 2000) -> bytearray: """Send a frame an return the resonse (async) - Args: - frame (bytearray): data to send - - Returns: - bytearray: response + :param bytearray frame: data to send + :param int timeout: timeout in milliseconds + :returns: response + :rtype: bytearray """ loop = asyncio.get_event_loop() self.sock.send(frame) @@ -807,17 +769,17 @@ def __init__( ): """Init a modbus RTU master. Pins or UART-Modul can be passed. If a second slave - Args: - uart_no (int, optional): [description]. Defaults to 1. - baudrate (int, optional): [description]. Defaults to 9600. - bits (int, optional): [description]. Defaults to 8. - stop (int, optional): [description]. Defaults to 1. - parity (int, optional): [description]. Defaults to None. - tx_pin (int, optional): [description]. Defaults to 14. - rx_pin (int, optional): [description]. Defaults to 13. - en_pin (int, optional): [description]. Defaults to 15. - uart (uart, optional): [description]. Defaults to None. - device_addr (int, optional): [description]. Defaults to 1. + :param uart_no: UART number + :param baudrate: Baudrate for the UART + :param bits: Number of data bits + :param stop: Number of stop bits + :param parity: Parity bit (None, 'N', 'E', 'O') + :param tx_pin: Pin for TX (default 14) + :param rx_pin: Pin for RX (default 13) + :param en_pin: Pin for enable (default 15, -1 for no pin) + :param uart: UART object to use (optional) + :param device_addr: Modbus device address (default 1) + :param verbose: If True, print debug messages """ self._verbose = verbose super(_CModbusRTUMaster, self).__init__(ms_type="rtu", *args, **kwargs) @@ -852,11 +814,10 @@ def _exit_read(self, response: bytearray) -> bool: def _send(self, frame: bytearray, timeout: int = 2000) -> bytearray: """Send a frame an return the resonse - Args: - frame (bytearray): data to send - - Returns: - bytearray: response + :param bytearray frame: data to send + :param int timeout: timeout in milliseconds + :returns: response + :rtype: bytearray """ self._verbose and print("[MODBUS] M->S: " + " ".join("{:02x}".format(x) for x in frame)) self.uart.write(frame) @@ -885,11 +846,10 @@ def _send(self, frame: bytearray, timeout: int = 2000) -> bytearray: async def _send_async(self, frame: bytearray, timeout: int = 2000) -> bytearray: """Send a frame an return the resonse (async) - Args: - frame (bytearray): data to send - - Returns: - bytearray: response + :param bytearray frame: data to send + :param int timeout: timeout in milliseconds + :returns: response + :rtype: bytearray """ # TODO: implement async self._verbose and print("[MODBUS] M->S: " + " ".join("{:02x}".format(x) for x in frame)) @@ -935,17 +895,17 @@ def __init__( ): """Init a modbus RTU master. Pins or UART-Modul can be passed. If a second slave - Args: - uart_no (int, optional): [description]. Defaults to 1. - baudrate (int, optional): [description]. Defaults to 9600. - bits (int, optional): [description]. Defaults to 8. - stop (int, optional): [description]. Defaults to 1. - parity (int, optional): [description]. Defaults to None. - tx_pin (int, optional): [description]. Defaults to 14. - rx_pin (int, optional): [description]. Defaults to 13. - en_pin (int, optional): [description]. Defaults to 15. - uart (uart, optional): [description]. Defaults to None. - device_addr (int, optional): [description]. Defaults to 1. + :param int uart_no: UART number + :param int baudrate: Baudrate for the UART + :param int bits: Number of data bits + :param int stop: Number of stop bits + :param int parity: Parity bit (None, 'N', 'E', 'O') + :param int tx_pin: Pin for TX (default 14) + :param int rx_pin: Pin for RX (default 13) + :param int en_pin: Pin for enable (default 15, -1 for no pin) + :param uart: UART object to use (optional) + :param int device_addr: Modbus device address (default 1) + :param bool verbose: If True, print debug messages """ self._verbose = verbose super(_MModbusRTUMaster, self).__init__(ms_type="rtu") @@ -1012,11 +972,10 @@ def _exit_read(self, response: bytearray) -> bool: def _send(self, frame: bytearray, timeout: int = 2000) -> bytearray: """Send a frame an return the resonse - Args: - frame (bytearray): data to send - - Returns: - bytearray: response + :param bytearray frame: data to send + :param int timeout: timeout in milliseconds + :returns: response + :rtype: bytearray """ self._verbose and print("[MODBUS] M->S: " + " ".join("{:02x}".format(x) for x in frame)) if self.uart.any() > 0: @@ -1048,11 +1007,10 @@ def _send(self, frame: bytearray, timeout: int = 2000) -> bytearray: async def _send_async(self, frame: bytearray, timeout: int = 2000) -> bytearray: """Send a frame an return the resonse (async) - Args: - frame (bytearray): data to send - - Returns: - bytearray: response + :param bytearray frame: data to send + :param int timeout: timeout in milliseconds + :returns: response + :rtype: bytearray """ self._verbose and print("[MODBUS] M->S: " + " ".join("{:02x}".format(x) for x in frame)) self.uart.write(frame) diff --git a/m5stack/libs/modbus/modbus/slave.py b/m5stack/libs/modbus/modbus/slave.py index 713dac89..9c693484 100644 --- a/m5stack/libs/modbus/modbus/slave.py +++ b/m5stack/libs/modbus/modbus/slave.py @@ -50,6 +50,13 @@ class ModbusSlave: } def __init__(self, sl_type="tcp", context=None, ignore_unit_id=False, device_address=1): + """Basic Modbus Slave class + + :param str sl_type: Modbus type (tcp or rtu) + :param dict context: Initial register context + :param bool ignore_unit_id: Whether to ignore unit id in requests + :param int device_address: Device address for RTU mode + """ self.sl_type = sl_type self.context = context self.ignore_unit_id = ignore_unit_id @@ -80,9 +87,21 @@ def __init__(self, sl_type="tcp", context=None, ignore_unit_id=False, device_add } def set_callback(self, event, callback): + """Set callback function for specific modbus event + + :param int event: Event code (0x01-0x2B) + :param function callback: Callback function to handle the event + """ self.cb[event] = callback def _add_register_in_context(self, reg_type, register, value): + """Add a register to the context + + :param str reg_type: Type of register (coils, discrete_inputs, holding_registers, input_registers) + :param int register: Register address + :param value: Value to add + :raises: KeyError if register type is invalid + """ if reg_type not in self._available_register_types: raise KeyError( "{} is Invalid register type of {}".format( @@ -120,6 +139,12 @@ def _add_register_in_context(self, reg_type, register, value): regs.pop(i) def _remove_register_from_context(self, reg_type, register): + """Remove a register from the context + + :param str reg_type: Type of register (coils, discrete_inputs, holding_registers, input_registers) + :param int register: Register address + :raises: KeyError if register type is invalid + """ if reg_type not in self._available_register_types: raise KeyError( "{} is Invalid register type of {}".format( @@ -160,82 +185,73 @@ def _remove_register_from_context(self, reg_type, register): regs[i], regs[j] = regs[j], regs[i] def add_coil(self, register: int, value: bool) -> None: - """Add a coil to the modbus register dictionary. - - Args: - register (int): address of the coils. The address is 0x0000 to 0xFFFF. - value (bool): Value to add. The value is True or False. + """Add a coil to the modbus register dictionary + :param int register: address of the coils. The address is 0x0000 to 0xFFFF + :param bool value: Value to add. The value is True or False """ self._add_register_in_context("coils", register, value) def add_discrete_input(self, register: int, value: bool) -> None: - """Add a discrete input to the modbus register dictionary. - - Args: - register (int): address of the discrete inputs. The address is 0x0000 to 0xFFFF. - value (bool): Value to add. The value is True or False. + """Add a discrete input to the modbus register dictionary + :param int register: address of the discrete inputs. The address is 0x0000 to 0xFFFF + :param bool value: Value to add. The value is True or False """ self._add_register_in_context("discrete_inputs", register, value) def add_holding_register(self, register: int, value: int) -> None: - """Add a holding register to the modbus register dictionary. - - Args: - register (int): address of the holding registers. The address is 0x0000 to 0xFFFF. - value (int): Value to add. The value is 0x0000 to 0xFFFF. + """Add a holding register to the modbus register dictionary + :param int register: address of the holding registers. The address is 0x0000 to 0xFFFF + :param int value: Value to add. The value is 0x0000 to 0xFFFF """ self._add_register_in_context("holding_registers", register, value) def add_input_register(self, register: int, value: int) -> None: - """Add an input register to the modbus register dictionary. - - Args: - register (int): address of the input registers. The address is 0x0000 to 0xFFFF. - value (int): Value to add. The value is 0x0000 to 0xFFFF. + """Add an input register to the modbus register dictionary + :param int register: address of the input registers. The address is 0x0000 to 0xFFFF + :param int value: Value to add. The value is 0x0000 to 0xFFFF """ self._add_register_in_context("input_registers", register, value) def remove_coil(self, register: int) -> None: - """Remove a coil from the modbus register dictionary. - - Args: - register (int): address of the coils. The address is 0x0000 to 0xFFFF. + """Remove a coil from the modbus register dictionary + :param int register: address of the coils. The address is 0x0000 to 0xFFFF """ self._remove_register_from_context("coils", register) def remove_discrete_input(self, register: int) -> None: - """Remove a discrete input from the modbus register dictionary. - - Args: - register (int): address of the discrete inputs. The address is 0x0000 to 0xFFFF. + """Remove a discrete input from the modbus register dictionary + :param int register: address of the discrete inputs. The address is 0x0000 to 0xFFFF """ self._remove_register_from_context("discrete_inputs", register) def remove_holding_register(self, register: int) -> None: - """Remove a holding register from the modbus register dictionary. - - Args: - register (int): address of the holding registers. The address is 0x0000 to 0xFFFF. + """Remove a holding register from the modbus register dictionary + :param int register: address of the holding registers. The address is 0x0000 to 0xFFFF """ self._remove_register_from_context("holding_registers", register) def remove_input_register(self, register) -> None: - """Remove an input register from the modbus register dictionary. - - Args: - register (int): address of the input registers. The address is 0x0000 to 0xFFFF. + """Remove an input register from the modbus register dictionary + :param int register: address of the input registers. The address is 0x0000 to 0xFFFF """ self._remove_register_from_context("input_registers", register) def _get_reg_data(self, reg_type: str, register: int): + """Get register data from context + + :param str reg_type: Type of register + :param int register: Register address + :returns: Register value + :raises: KeyError if register type is invalid or register not found + """ if reg_type not in self._available_register_types: raise KeyError( "{} is Invalid register type of {}".format( @@ -251,6 +267,13 @@ def _get_reg_data(self, reg_type: str, register: int): raise KeyError("Register {} not found in context".format(register)) def _set_reg_data(self, reg_type, register, value): + """Set register data in context + + :param str reg_type: Type of register + :param int register: Register address + :param value: Value to set + :raises: KeyError if register type is invalid or register not found + """ if reg_type not in self._available_register_types: raise KeyError( "{} is Invalid register type of {}".format( @@ -267,6 +290,13 @@ def _set_reg_data(self, reg_type, register, value): raise KeyError("Register {} not found in context".format(register)) def _set_reg_datablock(self, reg_type: str, register: int, block: list): + """Set multiple register data in context + + :param str reg_type: Type of register + :param int register: Starting register address + :param list block: List of values to set + :raises: KeyError if register type is invalid or register not found + """ if reg_type not in self._available_register_types: raise KeyError( "{} is Invalid register type of {}".format( @@ -287,125 +317,115 @@ def _set_reg_datablock(self, reg_type: str, register: int, block: list): raise KeyError("Register {} not found in context".format(register)) def get_coil(self, register: int) -> bool: - """Get the coil value. + """Get the coil value - Args: - register (int): address of the coils. The address is 0x0000 to 0xFFFF. - - Returns: - bool: Value of the coil. The value is True or False. + :param int register: address of the coils. The address is 0x0000 to 0xFFFF + :returns: Value of the coil. The value is True or False + :rtype: bool """ return self._get_reg_data("coils", register) def get_discrete_input(self, register: int) -> bool: - """Get the discrete input value. - - Args: - register (int): address of the discrete inputs. The address is 0x0000 to 0xFFFF. + """Get the discrete input value - Returns: - bool: Value of the discrete input. The value is True or False. + :param int register: address of the discrete inputs. The address is 0x0000 to 0xFFFF + :returns: Value of the discrete input. The value is True or False + :rtype: bool """ return self._get_reg_data("discrete_inputs", register) def get_holding_register(self, register: int) -> int: - """Get the holding register value. + """Get the holding register value - Args: - register (int): address of the holding registers. The address is 0x0000 to 0xFFFF. - - Returns: - int: Value of the holding register. The value is 0x0000 to 0xFFFF + :param int register: address of the holding registers. The address is 0x0000 to 0xFFFF + :returns: Value of the holding register. The value is 0x0000 to 0xFFFF + :rtype: int """ return self._get_reg_data("holding_registers", register) def get_input_register(self, register: int) -> int: - """Get the input register value. - - Args: - register (int): address of the input registers. The address is 0x0000 to 0xFFFF. + """Get the input register value - Returns: - int: Value of the input register. The value is 0x0000 to 0xFFFF + :param int register: address of the input registers. The address is 0x0000 to 0xFFFF + :returns: Value of the input register. The value is 0x0000 to 0xFFFF + :rtype: int """ return self._get_reg_data("input_registers", register) def set_coil(self, register: int, value: bool) -> None: - """Set the coil value. + """Set the coil value - Args: - register (int): address of the coils. The address is 0x0000 to 0xFFFF. - value (bool): Value to add. The value is True or False. + :param int register: address of the coils. The address is 0x0000 to 0xFFFF + :param bool value: Value to add. The value is True or False """ self._set_reg_data("coils", register, value) def set_multi_coils(self, register: int, value: list) -> None: - """Set the coil value. + """Set multiple coil values - Args: - register (int): Start address of the coils. The address is 0x0000 to 0xFFFF. - value (bool): Values to write. The item of the list is True or False. + :param int register: Start address of the coils. The address is 0x0000 to 0xFFFF + :param list value: Values to write. The item of the list is True or False """ self._set_reg_datablock("coils", register, value) def set_discrete_input(self, register: int, value: bool) -> None: - """Set the discrete input value. + """Set the discrete input value - Args: - register (int): address of the discrete inputs. The address is 0x0000 to 0xFFFF. - value (bool): Value to add. The value is True or False. + :param int register: address of the discrete inputs. The address is 0x0000 to 0xFFFF + :param bool value: Value to add. The value is True or False """ self._set_reg_data("discrete_inputs", register, value) def set_multi_discrete_input(self, register: int, value: list) -> None: - """Set the discrete input value. + """Set multiple discrete input values - Args: - register (int): Start address of the discrete inputs. The address is 0x0000 to 0xFFFF. - Values to write. The item of the list is True or False. + :param int register: Start address of the discrete inputs. The address is 0x0000 to 0xFFFF + :param list value: Values to write. The item of the list is True or False """ self._set_reg_datablock("discrete_inputs", register, value) def set_holding_register(self, register: int, value: int) -> None: - """Set the holding register value. + """Set the holding register value - Args: - register (int): address of the holding registers. The address is 0x0000 to 0xFFFF. - value (int): Value to add. The value is 0x0000 to 0xFFFF. + :param int register: address of the holding registers. The address is 0x0000 to 0xFFFF + :param int value: Value to add. The value is 0x0000 to 0xFFFF """ self._set_reg_data("holding_registers", register, value) def set_multi_holding_register(self, register: int, value: list) -> None: - """Set the holding register value. + """Set multiple holding register values - Args: - register (int): Start address of the holding registers. The address is 0x0000 to 0xFFFF. - value (int): Values to write. The item of the list is 0x0000 to 0xFFFF. + :param int register: Start address of the holding registers. The address is 0x0000 to 0xFFFF + :param list value: Values to write. The item of the list is 0x0000 to 0xFFFF """ self._set_reg_datablock("holding_registers", register, value) def set_input_register(self, register: int, value: int) -> None: - """Set the input register value. + """Set the input register value - Args: - register (int): address of the input registers. The address is 0x0000 to 0xFFFF. - value (int): Value to add. The value is 0x0000 to 0xFFFF. + :param int register: address of the input registers. The address is 0x0000 to 0xFFFF + :param int value: Value to add. The value is 0x0000 to 0xFFFF """ self._set_reg_data("input_registers", register, value) def set_multi_input_register(self, register: int, value: list) -> None: - """Set the discrete input value. + """Set multiple input register values - Args: - register (int): Start address of the input registers. The address is 0x0000 to 0xFFFF. - Values to write. The item of the list is 0x0000 to 0xFFFF. + :param int register: Start address of the input registers. The address is 0x0000 to 0xFFFF + :param list value: Values to write. The item of the list is 0x0000 to 0xFFFF """ self._set_reg_datablock("input_registers", register, value) def stop(self) -> None: + """Stop the modbus slave""" self.stopped = True def handle_message(self, frame): # noqa: C901 + """Handle incoming modbus message frame + + :param frame: Modbus frame to handle + :returns: Response frame or None + """ # TODO: Refactor this method if self.context is None: if self.forward_message is not None: @@ -680,6 +700,14 @@ def handle_message(self, frame): # noqa: C901 ) def _check_register(self, register, length, regs): + """Check if register range is valid in context + + :param int register: Starting register address + :param int length: Number of registers + :param list regs: Register context + :returns: True if valid, False otherwise + :rtype: bool + """ for reg in regs: quantity = len(reg["value"]) if reg["register"] <= register < reg["register"] + quantity: @@ -688,6 +716,14 @@ def _check_register(self, register, length, regs): return False def _get_data(self, register, length, regs): + """Get data from register context + + :param int register: Starting register address + :param int length: Number of registers + :param list regs: Register context + :returns: List of register values + :rtype: list + """ for reg in regs: if reg["register"] <= register < reg["register"] + len(reg["value"]): return reg["value"][ @@ -695,6 +731,13 @@ def _get_data(self, register, length, regs): ] def _set_data(self, register, length, data_Block, data): + """Set data in register block + + :param int register: Register address + :param int length: Data length + :param dict data_Block: Data block structure + :param data: Data to set + """ offset = register - data_Block["startAddr"] for i in range(length * 2): data_Block["registers"][2 * offset + i] = data[i] @@ -702,6 +745,11 @@ def _set_data(self, register, length, data_Block, data): class _CModbusRTUSlave(ModbusSlave): def __init__(self, uart, verbose=False, *args, **kwargs): + """Init a modbus RTU slave for CPython + + :param uart: UART object to use + :param bool verbose: If True, print debug messages + """ self._verbose = verbose self.uart = uart super(_CModbusRTUSlave, self).__init__(sl_type="rtu", *args, **kwargs) @@ -712,12 +760,17 @@ def start(self): self.uart.read_all() async def run_async(self): + """Run the RTU slave asynchronously + + :returns: None + """ self._verbose and print("starting async rtu slave") while not self.stopped: self.tick() await asyncio.sleep(0.1) def tick(self): + """Process incoming RTU messages""" if not self.stopped and self.uart.inWaiting(): rsp = self.uart.read_all() frame = ModbusRTUFrame.parse_frame(rsp, verbose=self._verbose) @@ -739,6 +792,11 @@ def tick(self): class _MModbusRTUSlave(ModbusSlave): def __init__(self, uart, verbose=False, *args, **kwargs): + """Init a modbus RTU slave for MicroPython + + :param uart: UART object to use + :param bool verbose: If True, print debug messages + """ self._verbose = verbose self.uart = uart super(_MModbusRTUSlave, self).__init__( @@ -750,23 +808,33 @@ def __init__(self, uart, verbose=False, *args, **kwargs): self.rsp = b"" async def run_async(self): + """Run the RTU slave asynchronously + + :returns: None + """ self._verbose and print("starting async rtu slave") while not self.stopped: self.tick() await asyncio.sleep(0.1) def start(self): + """Start the RTU slave and clear UART buffer""" self.stopped = False if self.uart.any() > 0: self.uart.read() def run(self): + """Run the RTU slave in blocking mode + + :returns: None + """ self._verbose and print("starting rtu slave") while not self.stopped: self.tick() time.sleep(0.1) def tick(self): + """Process incoming RTU messages""" if not self.stopped and self.uart.any(): rsp = self.rsp + self.uart.read() frame = ModbusRTUFrame.parse_frame(rsp, verbose=self._verbose) @@ -792,6 +860,11 @@ def tick(self): class ModbusRTUSlave: def __new__(cls, *args, **kwargs): + """Factory class for RTU Slave + + :returns: Platform-specific RTU slave instance + :rtype: _CModbusRTUSlave or _MModbusRTUSlave + """ if sys.implementation.name == "cpython": return _CModbusRTUSlave(*args, **kwargs) elif sys.implementation.name == "micropython": @@ -800,6 +873,12 @@ def __new__(cls, *args, **kwargs): class _CModbusTCPServer(ModbusSlave): def __init__(self, host, port, verbose=False, *args, **kwargs): + """Init a modbus TCP server for CPython + + :param str host: Server host address + :param int port: Server port number + :param bool verbose: If True, print debug messages + """ self.host = host self.port = port self._verbose = verbose @@ -811,6 +890,7 @@ def __init__(self, host, port, verbose=False, *args, **kwargs): ) def start(self): + """Start the TCP server""" self.poll = select.poll() self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.sock.bind((self.host, self.port)) @@ -819,6 +899,10 @@ def start(self): self.clients = {} def run(self): + """Run the TCP server in blocking mode + + :returns: None + """ while not self.stopped: conn, addr = self.sock.accept() self._verbose and print("new connection from {}".format(addr)) @@ -843,6 +927,10 @@ def run(self): self.sock.close() def tick(self): + """Process TCP connections in non-blocking mode + + :returns: None + """ if not self.stopped: fds = self.poll.poll(10) for fd, event in fds: @@ -919,6 +1007,12 @@ async def run_async(self): class _MModbusTCPServer(ModbusSlave): def __init__(self, host, port, verbose=False, *args, **kwargs): + """Init a modbus TCP server for MicroPython + + :param str host: Server host address + :param int port: Server port number + :param bool verbose: If True, print debug messages + """ self.host = host self.port = port self._verbose = verbose @@ -930,8 +1024,6 @@ def __init__(self, host, port, verbose=False, *args, **kwargs): ) def start(self): - import select - self.poll = select.poll() self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.sock.bind((self.host, self.port)) @@ -963,8 +1055,6 @@ def run(self): self.sock.close() def tick(self): - import select - if not self.stopped: fds = self.poll.poll(10) for fd, event in fds: @@ -981,6 +1071,7 @@ def tick(self): self.ignore_unit_id is not True and frame.unit_id != self._device_address ): + self._verbose and print("unit id not match") return res = self.handle_message(frame).get_frame() self._verbose and print(res) @@ -1035,6 +1126,11 @@ async def run_async(self): class ModbusTCPServer: def __new__(cls, *args, **kwargs): + """Factory class for TCP Server + + :returns: Platform-specific TCP server instance + :rtype: _CModbusTCPServer or _MModbusTCPServer + """ if sys.implementation.name == "cpython": return _CModbusTCPServer(*args, **kwargs) elif sys.implementation.name == "micropython": From 887fc71224c1652d2243603b308bf680fd70353c Mon Sep 17 00:00:00 2001 From: lbuque <1102390310@qq.com> Date: Tue, 5 Aug 2025 17:58:06 +0800 Subject: [PATCH 193/322] patches: Setting a fixed clock source for esp32-p4. Signed-off-by: lbuque <1102390310@qq.com> --- .../0011-micropython-1.25.0-fix-esp32-p4-pwm.patch | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/m5stack/patches/0011-micropython-1.25.0-fix-esp32-p4-pwm.patch b/m5stack/patches/0011-micropython-1.25.0-fix-esp32-p4-pwm.patch index beb52f62..878ae3b4 100644 --- a/m5stack/patches/0011-micropython-1.25.0-fix-esp32-p4-pwm.patch +++ b/m5stack/patches/0011-micropython-1.25.0-fix-esp32-p4-pwm.patch @@ -14,3 +14,13 @@ Index: micropython/ports/esp32/machine_pwm.c } else { #if LEDC_SPEED_MODE_MAX > 1 #if CONFIG_IDF_TARGET_ESP32 +@@ -232,6 +236,9 @@ static void set_freq(machine_pwm_obj_t * + #else + #error No supported PWM / LEDC clocks. + #endif ++#if CONFIG_IDF_TARGET_ESP32P4 ++ timer->clk_cfg = LEDC_USE_XTAL_CLK; ++#endif + #if SOC_LEDC_SUPPORT_REF_TICK + if (freq < EMPIRIC_FREQ) { + timer->clk_cfg = LEDC_USE_REF_TICK; From 24e9be26d9467d6714cffd93cf9d71932f05efa8 Mon Sep 17 00:00:00 2001 From: lbuque <1102390310@qq.com> Date: Wed, 6 Aug 2025 15:32:33 +0800 Subject: [PATCH 194/322] libs/m5ui: Negative values for corner radius are not allowed. A negative value will allocate a large amount of memory and cause the device to freeze. Signed-off-by: lbuque <1102390310@qq.com> --- m5stack/libs/m5ui/arc.py | 5 +++ m5stack/libs/m5ui/bar.py | 5 +++ m5stack/libs/m5ui/base.py | 1 + m5stack/libs/m5ui/button.py | 5 +++ m5stack/libs/m5ui/calendar.py | 5 +++ m5stack/libs/m5ui/canvas.py | 5 +++ m5stack/libs/m5ui/checkbox.py | 5 +++ m5stack/libs/m5ui/dropdown.py | 71 +++++++++++++++++++++++++++++++++++ m5stack/libs/m5ui/image.py | 5 +++ m5stack/libs/m5ui/label.py | 5 +++ m5stack/libs/m5ui/line.py | 5 +++ m5stack/libs/m5ui/page.py | 5 +++ m5stack/libs/m5ui/slider.py | 5 +++ m5stack/libs/m5ui/switch.py | 5 +++ m5stack/libs/m5ui/textarea.py | 5 +++ 15 files changed, 137 insertions(+) create mode 100644 m5stack/libs/m5ui/dropdown.py diff --git a/m5stack/libs/m5ui/arc.py b/m5stack/libs/m5ui/arc.py index e3894f57..46393ca0 100644 --- a/m5stack/libs/m5ui/arc.py +++ b/m5stack/libs/m5ui/arc.py @@ -105,6 +105,11 @@ def set_arc_color(self, color, opa: int, part: int): self.set_style_arc_color(color, part) self.set_style_arc_opa(opa, part) + def set_style_radius(self, radius: int, part: int) -> None: + if radius < 0: + raise ValueError("Radius must be a non-negative integer.") + super().set_style_radius(radius, part) + def __getattr__(self, name): if hasattr(M5Base, name): method = getattr(M5Base, name) diff --git a/m5stack/libs/m5ui/bar.py b/m5stack/libs/m5ui/bar.py index 8df19e7a..5311ea5f 100644 --- a/m5stack/libs/m5ui/bar.py +++ b/m5stack/libs/m5ui/bar.py @@ -102,6 +102,11 @@ def _draw_cb(self, event_struct): label_dsc.text_local = True lv.draw_label(event_struct.get_layer(), label_dsc, txt_area) + def set_style_radius(self, radius: int, part: int) -> None: + if radius < 0: + raise ValueError("Radius must be a non-negative integer.") + super().set_style_radius(radius, part) + def __getattr__(self, name): if hasattr(M5Base, name): method = getattr(M5Base, name) diff --git a/m5stack/libs/m5ui/base.py b/m5stack/libs/m5ui/base.py index 87dce543..7afeb1f8 100644 --- a/m5stack/libs/m5ui/base.py +++ b/m5stack/libs/m5ui/base.py @@ -137,6 +137,7 @@ def set_bg_grad_color(self, color, opa, grad_color, grad_opd, grad_dir, part: in self.set_style_bg_grad_dir(grad_dir, part) time.sleep(0.01) + @staticmethod def set_border_color(self, color: int, opa: int, part: int): """Set the border color and opacity for a given part of the object. diff --git a/m5stack/libs/m5ui/button.py b/m5stack/libs/m5ui/button.py index e1a5766d..d65c96ed 100644 --- a/m5stack/libs/m5ui/button.py +++ b/m5stack/libs/m5ui/button.py @@ -83,6 +83,11 @@ def set_btn_text(self, text: str) -> None: """ self.label.set_text(text) + def set_style_radius(self, radius: int, part: int) -> None: + if radius < 0: + raise ValueError("Radius must be a non-negative integer.") + super().set_style_radius(radius, part) + def get_btn_text(self) -> str: """Get the text of the button. diff --git a/m5stack/libs/m5ui/calendar.py b/m5stack/libs/m5ui/calendar.py index 3251308d..66d9ea2e 100644 --- a/m5stack/libs/m5ui/calendar.py +++ b/m5stack/libs/m5ui/calendar.py @@ -102,6 +102,11 @@ def set_highlighted_dates(self, dates): ) super().set_highlighted_dates(self.highlighted_days, len(self.highlighted_days)) + def set_style_radius(self, radius: int, part: int) -> None: + if radius < 0: + raise ValueError("Radius must be a non-negative integer.") + super().set_style_radius(radius, part) + def __getattr__(self, name): if hasattr(M5Base, name): method = getattr(M5Base, name) diff --git a/m5stack/libs/m5ui/canvas.py b/m5stack/libs/m5ui/canvas.py index baa5f335..926e473d 100644 --- a/m5stack/libs/m5ui/canvas.py +++ b/m5stack/libs/m5ui/canvas.py @@ -466,6 +466,11 @@ def draw_triangle( lv.draw_triangle(self._layer, self._triangle_dsc) self.finish_layer(self._layer) + def set_style_radius(self, radius: int, part: int) -> None: + if radius < 0: + raise ValueError("Radius must be a non-negative integer.") + super().set_style_radius(radius, part) + def __getattr__(self, name): if hasattr(M5Base, name): method = getattr(M5Base, name) diff --git a/m5stack/libs/m5ui/checkbox.py b/m5stack/libs/m5ui/checkbox.py index 232d7acb..72938d08 100644 --- a/m5stack/libs/m5ui/checkbox.py +++ b/m5stack/libs/m5ui/checkbox.py @@ -62,6 +62,11 @@ def __init__( self.set_bg_color(bullet_bg_c, lv.OPA.COVER, lv.PART.INDICATOR | part) self.set_border_color(bullet_border_c, lv.OPA.COVER, lv.PART.INDICATOR | part) + def set_style_radius(self, radius: int, part: int) -> None: + if radius < 0: + raise ValueError("Radius must be a non-negative integer.") + super().set_style_radius(radius, part) + def __getattr__(self, name): if hasattr(M5Base, name): method = getattr(M5Base, name) diff --git a/m5stack/libs/m5ui/dropdown.py b/m5stack/libs/m5ui/dropdown.py new file mode 100644 index 00000000..4a6ddd86 --- /dev/null +++ b/m5stack/libs/m5ui/dropdown.py @@ -0,0 +1,71 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +from .base import M5Base +import lvgl as lv + + +class M5Dropdown(lv.dropdown): + def __init__( + self, + x=0, + y=0, + w=100, + h=lv.SIZE_CONTENT, + options: list = [], + direction: int = lv.DIR.RIGHT, + show_selected: bool = True, + font: lv.font_t = lv.font_montserrat_14, + parent=None, + ): + if parent is None: + parent = lv.screen_active() + super().__init__(parent) + self.set_pos(x, y) + self.set_size(w, h) + + if options: + self.set_options(options) + + self.set_dir(direction) + self.set_selected_highlight(show_selected) + self.set_style_text_font(font, lv.PART.MAIN) + + def set_options(self, options: list): + if isinstance(options, list): + options = "\n".join(options) + super().set_options(options) + + def get_selected_str(self) -> str: + sel = bytearray(32) + super().get_selected_str(sel, len(sel)) + # 找到第一个空字节的位置,截断字符串 + null_index = sel.find(0) + if null_index != -1: + sel = sel[:null_index] + return sel.decode("utf-8") + + def set_dir(self, direction: int): + super().set_dir(direction) + if direction == lv.DIR.LEFT: + self.set_symbol(lv.SYMBOL.LEFT) + elif direction == lv.DIR.RIGHT: + self.set_symbol(lv.SYMBOL.RIGHT) + elif direction == lv.DIR.UP: + self.set_symbol(lv.SYMBOL.UP) + elif direction == lv.DIR.DOWN: + self.set_symbol(lv.SYMBOL.DOWN) + + def set_style_radius(self, radius: int, part: int) -> None: + if radius < 0: + raise ValueError("Radius must be a non-negative integer.") + super().set_style_radius(radius, part) + + def __getattr__(self, name): + if hasattr(M5Base, name): + method = getattr(M5Base, name) + bound_method = lambda *args, **kwargs: method(self, *args, **kwargs) + setattr(self, name, bound_method) + return bound_method + raise AttributeError(f"'{self.__class__.__name__}' object has no attribute '{name}'") diff --git a/m5stack/libs/m5ui/image.py b/m5stack/libs/m5ui/image.py index c948ba02..e0a301c3 100644 --- a/m5stack/libs/m5ui/image.py +++ b/m5stack/libs/m5ui/image.py @@ -104,6 +104,11 @@ def set_scale(self, scale, scale_y=None): else: super().set_scale(scale * 256) + def set_style_radius(self, radius: int, part: int) -> None: + if radius < 0: + raise ValueError("Radius must be a non-negative integer.") + super().set_style_radius(radius, part) + def __getattr__(self, name): if hasattr(M5Base, name): method = getattr(M5Base, name) diff --git a/m5stack/libs/m5ui/label.py b/m5stack/libs/m5ui/label.py index b1d1b1ab..6faeb362 100644 --- a/m5stack/libs/m5ui/label.py +++ b/m5stack/libs/m5ui/label.py @@ -100,6 +100,11 @@ def unset_shadow(self) -> None: """ self._shadow_label.add_flag(lv.obj.FLAG.HIDDEN) + def set_style_radius(self, radius: int, part: int) -> None: + if radius < 0: + raise ValueError("Radius must be a non-negative integer.") + super().set_style_radius(radius, part) + def __del__(self): self._shadow_label.delete() super().__delete__() diff --git a/m5stack/libs/m5ui/line.py b/m5stack/libs/m5ui/line.py index 952584be..9ac06e27 100644 --- a/m5stack/libs/m5ui/line.py +++ b/m5stack/libs/m5ui/line.py @@ -88,6 +88,11 @@ def add_point(self, x, y): self.lv_points.append({"x": x, "y": y}) super().set_points(self.lv_points, len(self.lv_points)) + def set_style_radius(self, radius: int, part: int) -> None: + if radius < 0: + raise ValueError("Radius must be a non-negative integer.") + super().set_style_radius(radius, part) + def __getattr__(self, name): if hasattr(M5Base, name): method = getattr(M5Base, name) diff --git a/m5stack/libs/m5ui/page.py b/m5stack/libs/m5ui/page.py index d7817ca7..917fd665 100644 --- a/m5stack/libs/m5ui/page.py +++ b/m5stack/libs/m5ui/page.py @@ -45,6 +45,11 @@ def screen_load(self): """ lv.screen_load(self) + def set_style_radius(self, radius: int, part: int) -> None: + if radius < 0: + raise ValueError("Radius must be a non-negative integer.") + super().set_style_radius(radius, part) + def __getattr__(self, name): if hasattr(M5Base, name): method = getattr(M5Base, name) diff --git a/m5stack/libs/m5ui/slider.py b/m5stack/libs/m5ui/slider.py index 96a33dd6..9a9f8741 100644 --- a/m5stack/libs/m5ui/slider.py +++ b/m5stack/libs/m5ui/slider.py @@ -49,6 +49,11 @@ def __init__( self.set_bg_color(bg_c, 51, lv.PART.MAIN | lv.STATE.DEFAULT) self.set_bg_color(color, lv.OPA.COVER, lv.PART.INDICATOR | lv.STATE.DEFAULT) + def set_style_radius(self, radius: int, part: int) -> None: + if radius < 0: + raise ValueError("Radius must be a non-negative integer.") + super().set_style_radius(radius, part) + def __getattr__(self, name): if hasattr(M5Base, name): method = getattr(M5Base, name) diff --git a/m5stack/libs/m5ui/switch.py b/m5stack/libs/m5ui/switch.py index 6333b76a..f25247cf 100644 --- a/m5stack/libs/m5ui/switch.py +++ b/m5stack/libs/m5ui/switch.py @@ -89,6 +89,11 @@ def set_size(self, w, h): self.width = w self.height = h + def set_style_radius(self, radius: int, part: int) -> None: + if radius < 0: + raise ValueError("Radius must be a non-negative integer.") + super().set_style_radius(radius, part) + def __getattr__(self, name): if hasattr(M5Base, name): method = getattr(M5Base, name) diff --git a/m5stack/libs/m5ui/textarea.py b/m5stack/libs/m5ui/textarea.py index 31d3b783..2473d282 100644 --- a/m5stack/libs/m5ui/textarea.py +++ b/m5stack/libs/m5ui/textarea.py @@ -49,6 +49,11 @@ def __init__( self.set_border_color(border_c, lv.OPA.COVER, lv.PART.MAIN | lv.STATE.DEFAULT) self.set_text_color(text_c, lv.OPA.COVER, lv.PART.MAIN | lv.STATE.DEFAULT) + def set_style_radius(self, radius: int, part: int) -> None: + if radius < 0: + raise ValueError("Radius must be a non-negative integer.") + super().set_style_radius(radius, part) + def __getattr__(self, name): if hasattr(M5Base, name): method = getattr(M5Base, name) From fec6e4cc383006f061e642aa23efe9d6e8882519 Mon Sep 17 00:00:00 2001 From: lbuque <1102390310@qq.com> Date: Wed, 6 Aug 2025 15:32:33 +0800 Subject: [PATCH 195/322] libs/m5ui: Negative values for corner radius are not allowed. A negative value will allocate a large amount of memory and cause the device to freeze. Signed-off-by: lbuque <1102390310@qq.com> --- m5stack/libs/m5ui/switch.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/m5stack/libs/m5ui/switch.py b/m5stack/libs/m5ui/switch.py index f25247cf..c9683e58 100644 --- a/m5stack/libs/m5ui/switch.py +++ b/m5stack/libs/m5ui/switch.py @@ -94,6 +94,11 @@ def set_style_radius(self, radius: int, part: int) -> None: raise ValueError("Radius must be a non-negative integer.") super().set_style_radius(radius, part) + def set_style_radius(self, radius: int, part: int) -> None: + if radius < 0: + raise ValueError("Radius must be a non-negative integer.") + super().set_style_radius(radius, part) + def __getattr__(self, name): if hasattr(M5Base, name): method = getattr(M5Base, name) From fe563e6834ea5877d4b170470c485cbcd86f0be9 Mon Sep 17 00:00:00 2001 From: lbuque <1102390310@qq.com> Date: Wed, 6 Aug 2025 16:01:31 +0800 Subject: [PATCH 196/322] libs/unit: Fix motion detection unable to be disabled. Signed-off-by: lbuque <1102390310@qq.com> --- m5stack/libs/unit/accel.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/m5stack/libs/unit/accel.py b/m5stack/libs/unit/accel.py index 856e9b60..06ad561a 100644 --- a/m5stack/libs/unit/accel.py +++ b/m5stack/libs/unit/accel.py @@ -87,6 +87,7 @@ def disable_motion_detection(self) -> None: accel_0.disable_motion_detection() """ + super().disable_motion_detection() def get_data_rate(self) -> int: """Get the data rate of the sensor. @@ -263,7 +264,7 @@ def disable_freefall_detection(self) -> None: super().disable_freefall_detection() def enable_tap_detection( - *, + self, tap_count: int = 1, threshold: int = 20, duration: int = 50, From e392f5036525c61871b86bf8837b83fd21172cec Mon Sep 17 00:00:00 2001 From: lbuque <1102390310@qq.com> Date: Wed, 6 Aug 2025 15:32:33 +0800 Subject: [PATCH 197/322] libs/m5ui: Negative values for corner radius are not allowed. A negative value will allocate a large amount of memory and cause the device to freeze. Signed-off-by: lbuque <1102390310@qq.com> --- m5stack/libs/m5ui/switch.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/m5stack/libs/m5ui/switch.py b/m5stack/libs/m5ui/switch.py index c9683e58..f65e05c1 100644 --- a/m5stack/libs/m5ui/switch.py +++ b/m5stack/libs/m5ui/switch.py @@ -99,6 +99,11 @@ def set_style_radius(self, radius: int, part: int) -> None: raise ValueError("Radius must be a non-negative integer.") super().set_style_radius(radius, part) + def set_style_radius(self, radius: int, part: int) -> None: + if radius < 0: + raise ValueError("Radius must be a non-negative integer.") + super().set_style_radius(radius, part) + def __getattr__(self, name): if hasattr(M5Base, name): method = getattr(M5Base, name) From cd0a5718b38e900225fafd106f31261ccfea003c Mon Sep 17 00:00:00 2001 From: lbuque <1102390310@qq.com> Date: Wed, 6 Aug 2025 18:47:00 +0800 Subject: [PATCH 198/322] modules/startup/tab5: Clean up LVGL-related code. Signed-off-by: lbuque <1102390310@qq.com> --- .../components/M5Unified/mpy_m5unified.cpp | 4 ---- m5stack/modules/startup/tab5/__init__.py | 1 - .../startup/tab5/launcher/apps/app_gpio.py | 1 - .../startup/tab5/launcher/apps/app_wifi.py | 1 - .../modules/startup/tab5/launcher/launcher.py | 19 ++++++++++--------- 5 files changed, 10 insertions(+), 16 deletions(-) diff --git a/m5stack/components/M5Unified/mpy_m5unified.cpp b/m5stack/components/M5Unified/mpy_m5unified.cpp index 5b07f8ab..5502a888 100644 --- a/m5stack/components/M5Unified/mpy_m5unified.cpp +++ b/m5stack/components/M5Unified/mpy_m5unified.cpp @@ -480,9 +480,6 @@ mp_obj_t m5_begin(size_t n_args, const mp_obj_t *args) { // } - if (M5.getBoard() == m5::board_t::board_M5Tab5) { - M5.Lcd.setRotation(3); - } M5.Display.clear(); // default display m5_display.gfx = (void *)(&(M5.Display)); @@ -560,7 +557,6 @@ mp_obj_t m5_Displays(mp_obj_t index) { } mp_obj_t m5_getDisplay(mp_obj_t index) { - mp_printf(&mp_plat_print, "display index: %d\n", mp_obj_get_int(index)); gfx_obj_t *o = mp_obj_malloc_with_finaliser(gfx_obj_t, &mp_gfxdevice_type); o->gfx = (void *)&(M5.getDisplay(mp_obj_get_int(index))); return MP_OBJ_FROM_PTR(o); diff --git a/m5stack/modules/startup/tab5/__init__.py b/m5stack/modules/startup/tab5/__init__.py index 1fd8eb55..d579376a 100644 --- a/m5stack/modules/startup/tab5/__init__.py +++ b/m5stack/modules/startup/tab5/__init__.py @@ -4,7 +4,6 @@ import startup import M5 -import time import lvgl as lv import lv_utils from .launcher import Launcher, set_hal diff --git a/m5stack/modules/startup/tab5/launcher/apps/app_gpio.py b/m5stack/modules/startup/tab5/launcher/apps/app_gpio.py index 8d586e3d..6fa0b9b5 100644 --- a/m5stack/modules/startup/tab5/launcher/apps/app_gpio.py +++ b/m5stack/modules/startup/tab5/launcher/apps/app_gpio.py @@ -5,7 +5,6 @@ from .app import AppBase from ..hal import * import lvgl as lv -import asyncio class OutputTestPanel: diff --git a/m5stack/modules/startup/tab5/launcher/apps/app_wifi.py b/m5stack/modules/startup/tab5/launcher/apps/app_wifi.py index 94bf1746..ac782e9a 100644 --- a/m5stack/modules/startup/tab5/launcher/apps/app_wifi.py +++ b/m5stack/modules/startup/tab5/launcher/apps/app_wifi.py @@ -5,7 +5,6 @@ from .app import AppBase from ..hal import * import lvgl as lv -import asyncio COLOR_TEXT_AREA_BG = 0xE6E2E6 diff --git a/m5stack/modules/startup/tab5/launcher/launcher.py b/m5stack/modules/startup/tab5/launcher/launcher.py index 0f73574f..7c9b77f7 100644 --- a/m5stack/modules/startup/tab5/launcher/launcher.py +++ b/m5stack/modules/startup/tab5/launcher/launcher.py @@ -26,8 +26,12 @@ def __init__(self): self._init_background() self._create_app_panel() - # Create main loop - asyncio.run(self._main()) + try: + # Create main loop + asyncio.run(self._main()) + finally: + print("Launcher cleanup complete") + M5.Lcd.lvgl_deinit() def _init_background(self): screen = lv.screen_active() @@ -112,10 +116,7 @@ async def _main(self): # Start ezdata service Ezdata.start() - try: - # Keep app manager running - while True: - await asyncio.sleep_ms(50) - await AppManager.update() - except KeyboardInterrupt: - M5.Lcd.lvgl_deinit() + # Keep app manager running + while True: + await asyncio.sleep_ms(50) + await AppManager.update() From 62ea2d5b2bd549c1a0cf8f5d8f7f4ecadd2088af Mon Sep 17 00:00:00 2001 From: lbuque <1102390310@qq.com> Date: Tue, 5 Aug 2025 11:45:30 +0800 Subject: [PATCH 199/322] boards/M5STACK_Tab5: Add some lvgl font. Signed-off-by: lbuque <1102390310@qq.com> --- m5stack/boards/M5STACK_Tab5/mpconfigboard.cmake | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/m5stack/boards/M5STACK_Tab5/mpconfigboard.cmake b/m5stack/boards/M5STACK_Tab5/mpconfigboard.cmake index 87c582f1..1c729418 100644 --- a/m5stack/boards/M5STACK_Tab5/mpconfigboard.cmake +++ b/m5stack/boards/M5STACK_Tab5/mpconfigboard.cmake @@ -24,15 +24,19 @@ set(SDKCONFIG_DEFAULTS ) # If not enable LVGL, ignore this... -set(LV_CFLAGS +set(LV_CFLAGS -DLV_COLOR_DEPTH=16 -DLV_COLOR_16_SWAP=0 - -DLV_FONT_MONTSERRAT_24=1 - -DLV_FONT_MONTSERRAT_22=1 - -DLV_FONT_MONTSERRAT_20=1 - -DLV_FONT_MONTSERRAT_18=1 + # startup ui need these fonts -DLV_FONT_MONTSERRAT_14=1 + -DLV_FONT_MONTSERRAT_18=1 + -DLV_FONT_MONTSERRAT_20=1 + -DLV_FONT_MONTSERRAT_22=1 + -DLV_FONT_MONTSERRAT_24=1 -DLV_FONT_MONTSERRAT_30=1 + # user need these fonts + -DLV_FONT_MONTSERRAT_36=1 + -DLV_FONT_MONTSERRAT_48=1 ) if(NOT MICROPY_FROZEN_MANIFEST) From 8947f29deb4ec3fd7e92ad1b3018910ab9e982fe Mon Sep 17 00:00:00 2001 From: lbuque Date: Wed, 9 Jul 2025 14:41:35 +0800 Subject: [PATCH 200/322] libs/m5ui: Add m5ui.M5Keyboard support. Signed-off-by: lbuque --- docs/en/m5ui/index.rst | 1 + docs/en/m5ui/keyboard.rst | 439 +++++++++++++ docs/en/refs/m5ui.keyboard.ref | 33 + .../zh_CN/LC_MESSAGES/m5ui/keyboard.po | 598 ++++++++++++++++++ .../cores3_keyboard_basic_example.m5f2 | 1 + .../keyboard/cores3_keyboard_basic_example.py | 89 +++ m5stack/libs/m5ui/__init__.py | 1 + m5stack/libs/m5ui/keyboard.py | 61 ++ m5stack/libs/m5ui/manifest.py | 1 + 9 files changed, 1224 insertions(+) create mode 100644 docs/en/m5ui/keyboard.rst create mode 100644 docs/en/refs/m5ui.keyboard.ref create mode 100644 docs/locales/zh_CN/LC_MESSAGES/m5ui/keyboard.po create mode 100644 examples/m5ui/keyboard/cores3_keyboard_basic_example.m5f2 create mode 100644 examples/m5ui/keyboard/cores3_keyboard_basic_example.py create mode 100644 m5stack/libs/m5ui/keyboard.py diff --git a/docs/en/m5ui/index.rst b/docs/en/m5ui/index.rst index d38383b6..86cbf046 100644 --- a/docs/en/m5ui/index.rst +++ b/docs/en/m5ui/index.rst @@ -40,6 +40,7 @@ Classes canvas.rst checkbox.rst image.rst + keyboard.rst label.rst line.rst list.rst diff --git a/docs/en/m5ui/keyboard.rst b/docs/en/m5ui/keyboard.rst new file mode 100644 index 00000000..2dd193fd --- /dev/null +++ b/docs/en/m5ui/keyboard.rst @@ -0,0 +1,439 @@ +.. currentmodule:: m5ui + +M5Keyboard +========== + +.. include:: ../refs/m5ui.keyboard.ref + +M5Keyboard is a widget that can be used to create virtual keyboards in the user interface. It provides an on-screen keyboard that can be used for text input with support for different keyboard modes and layouts. + + +UiFlow2 Example +--------------- + +basic keyboard +^^^^^^^^^^^^^^ + +Open the |cores3_keyboard_basic_example.m5f2| project in UiFlow2. + +This example demonstrates how to create a virtual keyboard and connect it to a text input area. + +UiFlow2 Code Block: + + |cores3_keyboard_basic_example.png| + +Example output: + + None + + +MicroPython Example +------------------- + +basic keyboard +^^^^^^^^^^^^^^ + +This example demonstrates how to create a virtual keyboard and connect it to a text input area. + +MicroPython Code Block: + + .. literalinclude:: ../../../examples/m5ui/keyboard/cores3_keyboard_basic_example.py + :language: python + :linenos: + +Example output: + + None + + +**API** +------- + +M5Keyboard +^^^^^^^^^^ + +.. autoclass:: m5ui.keyboard.M5Keyboard + :members: + + .. py:method:: set_flag(flag, value) + + Set a flag on the object. If ``value`` is True, the flag is added; if False, the flag is removed. + + :param int flag: The flag to set. + :param bool value: If True, the flag is added; if False, the flag is removed. + :return: None + + UiFlow2 Code Block: + + |set_flag.png| + + MicroPython Code Block: + + .. code-block:: python + + keyboard_0.set_flag(lv.obj.FLAG.HIDDEN, True) + + .. py:method:: toggle_flag(flag) + + Toggle a flag on the object. If the flag is set, it is removed; if not set, it is added. + + :param int flag: The flag to toggle. + :return: None + + UiFlow2 Code Block: + + |toggle_flag.png| + + MicroPython Code Block: + + .. code-block:: python + + keyboard_0.toggle_flag(lv.obj.FLAG.HIDDEN) + + .. py:method:: set_state(state, value) + + Set the state of the keyboard. If ``value`` is True, the state is set; if False, the state is unset. + + :param int state: The state to set. + :param bool value: If True, the state is set; if False, the state is unset. + :return: None + + UiFlow2 Code Block: + + |set_state.png| + + MicroPython Code Block: + + .. code-block:: python + + keyboard_0.set_state(lv.STATE.PRESSED, True) + + .. py:method:: toggle_state(state) + + Toggle the state of the keyboard. If the state is set, it is unset; if not set, it is set. + + :param int state: The state to toggle. + :return: None + + UiFlow2 Code Block: + + |toggle_state.png| + + MicroPython Code Block: + + .. code-block:: python + + keyboard_0.toggle_state(lv.STATE.PRESSED) + + .. py:method:: add_event_cb(handler, event, user_data) + + Add an event callback to the keyboard. The callback will be called when the specified event occurs. + + :param function handler: The callback function to call. + :param int event: The event to listen for. + :param Any user_data: Optional user data to pass to the callback. + :return: None + + UiFlow2 Code Block: + + |event.png| + + MicroPython Code Block: + + .. code-block:: python + + def keyboard_0_value_changed_event(event_struct): + global page0, textarea0 + print("Key pressed") + + def keyboard_0_ready_event(event_struct): + global page0, textarea0 + print("Ready") + + def keyboard_0_event_handler(event_struct): + event = event_struct.code + if event == lv.EVENT.VALUE_CHANGED: + keyboard_0_value_changed_event(event_struct) + elif event == lv.EVENT.READY: + keyboard_0_ready_event(event_struct) + return + + keyboard_0.add_event_cb(keyboard_0_event_handler, lv.EVENT.ALL, None) + + .. py:method:: set_textarea(textarea) + + Set the text area that this keyboard should control. When keys are pressed, the text will be entered into the specified text area. + + :param lv.textarea textarea: The text area object to connect to the keyboard. + :return: None + + UiFlow2 Code Block: + + |set_textarea.png| + + MicroPython Code Block: + + .. code-block:: python + + keyboard_0.set_textarea(textarea_0) + + .. py:method:: get_textarea() + + Get the text area that is currently connected to this keyboard. + + :return: The connected text area object, or None if no text area is connected. + :rtype: lv.textarea or None + + UiFlow2 Code Block: + + |get_textarea.png| + + MicroPython Code Block: + + .. code-block:: python + + ta = keyboard_0.get_textarea() + + .. py:method:: set_mode(mode) + + Set the keyboard mode to display different keyboard layouts. + + :param int mode: The keyboard mode to set (e.g., lv.keyboard.MODE.TEXT_LOWER, lv.keyboard.MODE.TEXT_UPPER, lv.keyboard.MODE.SPECIAL, lv.keyboard.MODE.NUMBER). + :return: None + + UiFlow2 Code Block: + + |set_mode.png| + + MicroPython Code Block: + + .. code-block:: python + + keyboard_0.set_mode(lv.keyboard.MODE.TEXT_LOWER) + + .. py:method:: get_mode() + + Get the current keyboard mode. + + :return: The current keyboard mode. + :rtype: int + + UiFlow2 Code Block: + + |get_mode.png| + + MicroPython Code Block: + + .. code-block:: python + + mode = keyboard_0.get_mode() + + .. py:method:: set_popovers(en) + + Enable or disable popovers for the keyboard. Popovers are additional UI elements that can be displayed when certain keys are pressed. + + :param bool en: If True, popovers are enabled; if False, they are disabled. + :return: None + + UiFlow2 Code Block: + + |set_popovers.png| + + MicroPython Code Block: + + .. code-block:: python + + keyboard_0.set_popovers(True) + + .. py:method:: get_selected_button() + + Get the index of the last released button. This can be useful to determine which key was last pressed. + + :return: index of the last released button. + :rtype: int + + UiFlow2 Code Block: + + |get_selected_button.png| + + MicroPython Code Block: + + .. code-block:: python + + btn = keyboard_0.get_selected_button() + + .. py:method:: get_button_text(btn_id) + + Get the text of a button by its index. + + :param int btn: The index of the button. + :return: The text of the button. + :rtype: str + + UiFlow2 Code Block: + + |get_button_text.png| + + MicroPython Code Block: + + .. code-block:: python + + keyboard_0.get_button_text(3) + + .. py:method:: set_pos(x, y) + + Set the position of the keyboard. + + :param int x: The x-coordinate of the keyboard. + :param int y: The y-coordinate of the keyboard. + :return: None + + UiFlow2 Code Block: + + |set_pos.png| + + MicroPython Code Block: + + .. code-block:: python + + keyboard_0.set_pos(100, 100) + + .. py:method:: set_x(x) + + Set the x-coordinate of the keyboard. + + :param int x: The x-coordinate of the keyboard. + :return: None + + UiFlow2 Code Block: + + |set_x.png| + + MicroPython Code Block: + + .. code-block:: python + + keyboard_0.set_x(100) + + .. py:method:: set_y(y) + + Set the y-coordinate of the keyboard. + + :param int y: The y-coordinate of the keyboard. + :return: None + + UiFlow2 Code Block: + + |set_y.png| + + MicroPython Code Block: + + .. code-block:: python + + keyboard_0.set_y(100) + + .. py:method:: set_size(width, height) + + Set the size of the keyboard. + + :param int width: The width of the keyboard. + :param int height: The height of the keyboard. + :return: None + + UiFlow2 Code Block: + + |set_size.png| + + MicroPython Code Block: + + .. code-block:: python + + keyboard_0.set_size(300, 200) + + .. py:method:: set_width(width) + + Set the width of the keyboard. + + :param int width: The width of the keyboard. + :return: None + + UiFlow2 Code Block: + + |set_width.png| + + MicroPython Code Block: + + .. code-block:: python + + keyboard_0.set_width(300) + + .. py:method:: get_width() + + Get the width of the keyboard. + + :return: The width of the keyboard. + :rtype: int + + UiFlow2 Code Block: + + |get_width.png| + + MicroPython Code Block: + + .. code-block:: python + + width = keyboard_0.get_width() + + .. py:method:: set_height(height) + + Set the height of the keyboard. + + :param int height: The height of the keyboard. + :return: None + + UiFlow2 Code Block: + + |set_height.png| + + MicroPython Code Block: + + .. code-block:: python + + keyboard_0.set_height(200) + + .. py:method:: get_height() + + Get the height of the keyboard. + + :return: The height of the keyboard. + :rtype: int + + UiFlow2 Code Block: + + |get_height.png| + + MicroPython Code Block: + + .. code-block:: python + + height = keyboard_0.get_height() + + .. py:method:: align_to(obj, align, x, y) + + Align the keyboard to another object. + + :param lv.obj obj: The object to align to. + :param int align: The alignment type. + :param int x: The x-offset from the aligned object. + :param int y: The y-offset from the aligned object. + :return: None + + UiFlow2 Code Block: + + |align_to.png| + + MicroPython Code Block: + + .. code-block:: python + + keyboard_0.align_to(page_0, lv.ALIGN.BOTTOM_MID, 0, -10) diff --git a/docs/en/refs/m5ui.keyboard.ref b/docs/en/refs/m5ui.keyboard.ref new file mode 100644 index 00000000..c104ff6a --- /dev/null +++ b/docs/en/refs/m5ui.keyboard.ref @@ -0,0 +1,33 @@ + +.. |cores3_keyboard_basic_example.m5f2| raw:: html + + + cores3_keyboard_basic_example.m5f2 + + +.. |cores3_keyboard_basic_example.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/keyboard/cores3_keyboard_basic_example.png + +.. |align_to.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/keyboard/align_to.png +.. |event.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/keyboard/event.png +.. |get_button_text.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/keyboard/get_button_text.png +.. |get_height.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/keyboard/get_height.png +.. |get_mode.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/keyboard/get_mode.png +.. |get_selected_button.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/keyboard/get_selected_button.png +.. |get_textarea.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/keyboard/get_textarea.png +.. |get_width.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/keyboard/get_width.png +.. |set_flag.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/keyboard/set_flag.png +.. |set_height.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/keyboard/set_height.png +.. |set_mode.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/keyboard/set_mode.png +.. |set_popovers.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/keyboard/set_popovers.png +.. |set_pos.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/keyboard/set_pos.png +.. |set_size.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/keyboard/set_size.png +.. |set_state.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/keyboard/set_state.png +.. |set_textarea.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/keyboard/set_textarea.png +.. |set_width.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/keyboard/set_width.png +.. |set_x.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/keyboard/set_x.png +.. |set_y.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/keyboard/set_y.png +.. |toggle_flag.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/keyboard/toggle_flag.png +.. |toggle_state.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/keyboard/toggle_state.png diff --git a/docs/locales/zh_CN/LC_MESSAGES/m5ui/keyboard.po b/docs/locales/zh_CN/LC_MESSAGES/m5ui/keyboard.po new file mode 100644 index 00000000..356c7fb3 --- /dev/null +++ b/docs/locales/zh_CN/LC_MESSAGES/m5ui/keyboard.po @@ -0,0 +1,598 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2016 - 2025 M5Stack Technology Co., Ltd +# This file is distributed under the same license as the UIFlow2 Programming +# Guide package. +# FIRST AUTHOR , 2025. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: UIFlow2 Programming Guide \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-07-30 15:09+0800\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: zh_CN\n" +"Language-Team: zh_CN \n" +"Plural-Forms: nplurals=1; plural=0;\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.17.0\n" + +#: ../../en/m5ui/keyboard.rst:4 ../../en/m5ui/keyboard.rst:53 +#: 43177d277a9a48b0857c08c4bc371ba3 5dbf8b33abb44518b0a78549470f1c8d +msgid "M5Keyboard" +msgstr "" + +#: ../../en/m5ui/keyboard.rst:8 503582fe3e9e45a183313f21ffc117db +msgid "" +"M5Keyboard is a widget that can be used to create virtual keyboards in " +"the user interface. It provides an on-screen keyboard that can be used " +"for text input with support for different keyboard modes and layouts." +msgstr "" +"M5Keyboard是一个用于在用户界面中创建虚拟键盘的小组件。它提供屏幕键盘,可用于文本输入," +"支持不同的键盘模式和布局。" + +#: ../../en/m5ui/keyboard.rst:12 be54afbe0dee49c191ffa3c64bbb5294 +msgid "UiFlow2 Example" +msgstr "" + +#: ../../en/m5ui/keyboard.rst:15 ../../en/m5ui/keyboard.rst:34 +#: 7bd0397abd57464b9c454726d1f94046 c570aeaf963b48d592a5330aef95a963 +msgid "basic keyboard" +msgstr "" + +#: ../../en/m5ui/keyboard.rst:17 8002b2a95ddc43368809578b748476c7 +msgid "Open the |cores3_keyboard_basic_example.m5f2| project in UiFlow2." +msgstr "在UiFlow2中打开|cores3_keyboard_basic_example.m5f2|项目。" + +#: ../../en/m5ui/keyboard.rst:19 ../../en/m5ui/keyboard.rst:36 +#: 2fe17932b3f24a7a8aa4b7825cdf77da f533e0b98fa945eb892a5531a1b68d3a +msgid "" +"This example demonstrates how to create a virtual keyboard and connect it" +" to a text input area." +msgstr "此示例演示如何创建虚拟键盘并将其连接到文本输入区域。" + +#: ../../en/m5ui/keyboard.rst:21 ../../en/m5ui/keyboard.rst:66 +#: ../../en/m5ui/keyboard.rst:83 ../../en/m5ui/keyboard.rst:101 +#: ../../en/m5ui/keyboard.rst:118 ../../en/m5ui/keyboard.rst:137 +#: ../../en/m5ui/keyboard.rst:170 ../../en/m5ui/keyboard.rst:187 +#: ../../en/m5ui/keyboard.rst:204 ../../en/m5ui/keyboard.rst:221 +#: ../../en/m5ui/keyboard.rst:238 ../../en/m5ui/keyboard.rst:255 +#: ../../en/m5ui/keyboard.rst:273 ../../en/m5ui/keyboard.rst:291 +#: ../../en/m5ui/keyboard.rst:308 ../../en/m5ui/keyboard.rst:325 +#: ../../en/m5ui/keyboard.rst:343 ../../en/m5ui/keyboard.rst:360 +#: ../../en/m5ui/keyboard.rst:377 ../../en/m5ui/keyboard.rst:394 +#: ../../en/m5ui/keyboard.rst:411 ../../en/m5ui/keyboard.rst:431 +#: 02d2f0b8d65d49fa88357c2d22528cfc 13c964a3e11a48ab82c2ec17bb2de288 +#: 1541c6c43d6f4a5bb4a18c0cbaf17063 22378e702a6947e4b29fd3369246a441 +#: 29e4ba02090a47c3b8cc9693a9937f41 31c56bb1a1ee460497aeeb251ec1dbce +#: 35be85a2168c4137b50416739e84aa70 3789bf0b00844d19bcd1562f3a867d9d +#: 390609e2f6f441f599ab438fcac558b6 3d6272c9656c49469445b5f4cbbf0a58 +#: 3f2fbf6132884e8fa5b664b8b7c81253 59ffc0f94c44418b80eaadeb5f634892 +#: 5ea9478909db445284c26ac3548fdf9a 7b96572af1f24317a11063e5abc83f44 +#: 845e807e451b43c2a2821227cbccedba 9aff1158fca342c8bfd00ec9d56a4e99 +#: 9e27dfb682e04f1a9b62e6bc070fdb53 b8714b1dd1a940d7bcdc99a355f7040d +#: c01b823d3b7c41a2b5f29c6fe650a5bc e3901e85ff4e4a78a0585262add820b4 +#: ea875b3adacc4dda8e25135fadb0cc3c f3ce0b1bb3e948f3a80701a8c2c953e0 +#: f72129804d67491ab10db98471715f62 m5ui.keyboard.M5Keyboard:11 of +msgid "UiFlow2 Code Block:" +msgstr "UiFlow2 代码块:" + +#: ../../en/m5ui/keyboard.rst:23 f9a1f510625e4c8f942691d3ad28f52b +msgid "|cores3_keyboard_basic_example.png|" +msgstr "" + +#: ../../en/refs/m5ui.keyboard.ref:11 090b89974ac242e5a9b5fada935ddea1 +msgid "cores3_keyboard_basic_example.png" +msgstr "" + +#: ../../en/m5ui/keyboard.rst:25 ../../en/m5ui/keyboard.rst:44 +#: 242dcc4809e24ce0818d2c003ba3dcb3 96858a43d618475baeb609db6a5139b5 +msgid "Example output:" +msgstr "示例输出:" + +#: ../../en/m5ui/keyboard.rst:27 ../../en/m5ui/keyboard.rst:46 +#: ../../en/m5ui/keyboard.rst:64 ../../en/m5ui/keyboard.rst:81 +#: ../../en/m5ui/keyboard.rst:99 ../../en/m5ui/keyboard.rst:116 +#: ../../en/m5ui/keyboard.rst:135 ../../en/m5ui/keyboard.rst:168 +#: ../../en/m5ui/keyboard.rst:202 ../../en/m5ui/keyboard.rst:236 +#: ../../en/m5ui/keyboard.rst:289 ../../en/m5ui/keyboard.rst:306 +#: ../../en/m5ui/keyboard.rst:323 ../../en/m5ui/keyboard.rst:341 +#: ../../en/m5ui/keyboard.rst:358 ../../en/m5ui/keyboard.rst:392 +#: ../../en/m5ui/keyboard.rst:429 01c2cb4b7cf845a1afc3390d9edc99f4 +#: 2b13cdc5bfb14d7a9bebe7aa7b1d31a1 2e2ab43e3caf42448d3ad6168a493178 +#: 42e62daf9105441494abd60f6dddce5a 6050dcff312c4cfe8d4ae5ade3975b2b +#: 61a9439bb3e34fc0919c93720a300e23 72d6d5cfec8c486497e6d7d4880e7b66 +#: 87fcf3b5111e4f1599d327d9ffee355a 8df9b3d03db1403d97e237c38d77cd1a +#: 9c9abf1a8ad9495187528def538037f2 a43a68f73f444d13b8f073d35a7f3236 +#: a9cb17e80f8544c39c2e97f048611d87 ace16a179dd14125a9b0c23f396396e1 +#: cf552a6e02624ec09b7627ebc8da3f46 d908063e7f0249e5955e86319e335e6f +#: f1bcf850523f4d729774e8047cb171f2 f4c6f8b8db9c4165967cd3cecfd093d5 +#: f789cf7553dc4ad394135572c4de3c4b m5ui.keyboard.M5Keyboard:13 of +msgid "None" +msgstr "" + +#: ../../en/m5ui/keyboard.rst:31 79081489d4104b7b860f8b739e7ef45b +msgid "MicroPython Example" +msgstr "MicroPython 应用示例" + +#: ../../en/m5ui/keyboard.rst:38 ../../en/m5ui/keyboard.rst:70 +#: ../../en/m5ui/keyboard.rst:87 ../../en/m5ui/keyboard.rst:105 +#: ../../en/m5ui/keyboard.rst:122 ../../en/m5ui/keyboard.rst:141 +#: ../../en/m5ui/keyboard.rst:174 ../../en/m5ui/keyboard.rst:191 +#: ../../en/m5ui/keyboard.rst:208 ../../en/m5ui/keyboard.rst:225 +#: ../../en/m5ui/keyboard.rst:242 ../../en/m5ui/keyboard.rst:259 +#: ../../en/m5ui/keyboard.rst:277 ../../en/m5ui/keyboard.rst:295 +#: ../../en/m5ui/keyboard.rst:312 ../../en/m5ui/keyboard.rst:329 +#: ../../en/m5ui/keyboard.rst:347 ../../en/m5ui/keyboard.rst:364 +#: ../../en/m5ui/keyboard.rst:381 ../../en/m5ui/keyboard.rst:398 +#: ../../en/m5ui/keyboard.rst:415 ../../en/m5ui/keyboard.rst:435 +#: 07f3ee95f17740ee86bf5bffbe51c68c 1e77bc3be784415abca379bae46c950e +#: 2b58b857018e47f69f0dcdcd98a2263a 2c0a7100033146049b4283dd60916c6d +#: 5237eb22d2434cb58e709b6611d4f37b 59d5171c4e844441a9892b7696cafe04 +#: 5b878d2fc15143d09c5c0d5f03e163a5 6188e21eec7240839beadd7276bb2c3d +#: 648fac9d4bc340ccbcd5e95e1c0847b2 7b401482e15140498dc54c5cf27a6ce1 +#: 7da8effdbb01418bb9e81bb12b136103 8aab1d799e92461fa180ed4141270f9f +#: 8aeaabd0e59b485db73fd11b52b2abad 9de0b025df2043ca94b0e2c8ccc82008 +#: b5d14a13906144b1adb45ca44bf1d130 b9c5dd64c7c24a73bb1f48654140537e +#: c135081d46ae4d278af10950b2f26d4a c47234df08064833b910e75c967faf79 +#: cfd22a06c401458e99d0b8d19db152d9 dc75e26f85da49428d84d2b54d636ff9 +#: e4a9b5ad6b51434198709146dcec2964 f3802b9adae4433da56ce0e1c047285f +#: fe4a0e1aa2f44191b9f620d23c1543f1 m5ui.keyboard.M5Keyboard:15 of +msgid "MicroPython Code Block:" +msgstr "MicroPython 代码块:" + +#: ../../en/m5ui/keyboard.rst:50 da7980ff84da490aa3602c0c1fb01629 +msgid "**API**" +msgstr "API参考" + +#: 90d50d7bdea1416c9796493e35b04985 m5ui.keyboard.M5Keyboard:1 of +msgid "Bases: :py:class:`~lvgl.keyboard`" +msgstr "" + +#: bf1dacee147243719739018a6f66a3ae m5ui.keyboard.M5Keyboard:1 of +msgid "Create a keyboard widget." +msgstr "Create a keyboard widget." + +#: ../../en/m5ui/keyboard.rst 0306d82c6d6145959e19007f2bab97ed +#: 2aba4219bd64438f8260935b74f81199 56fb5e16fbec4f1fb62837ea840fe361 +#: 58fb8cec6651450a8e23dc2766730449 6156f879c834468abd2c90a0bd0099b3 +#: 68428130d1ed4210bc3bd78999bb713e 76bab6711efb44cb9ca1f2e475338ef9 +#: 7daee2c2b39f41d3892d90b8fa1fd868 81884aad9bd14cc18c958026bf09d2a9 +#: 868f013794fa41f59226fb132ccb93ce 941ecc7f714a45d9bdfee0da951aa578 +#: ccc03d0cd2a2443da2a49476873e1352 ea275e6a0f9f4add917a8ad8e0c01cff +#: eba69320c96241648653bf96b53c2d8d f54eac3dfeb840e6806fd6c712bf8948 +#: f5e6f8fe57aa4a5ca3be4631237b49e1 fe663679b25f463083db6dadb585868d +msgid "Parameters" +msgstr "" + +#: b0cd2648e6864832bbde045619f176a4 m5ui.keyboard.M5Keyboard:3 of +msgid "The x position of the keyboard." +msgstr "键盘的 x 位置。" + +#: 9d61145cb40e4f34b2add6717f5ac345 m5ui.keyboard.M5Keyboard:4 of +msgid "The y position of the keyboard." +msgstr "键盘的 y 位置。" + +#: ../../en/m5ui/keyboard.rst:339 ../../en/m5ui/keyboard.rst:357 +#: ../../en/m5ui/keyboard.rst:374 2748339630c04767864fe6dbc4d1521c +#: 4fa2476c28ef408292603c23c95192b7 7fa07a33bc9948d8bd5f3e59389c0326 +#: d64b9919457745b198ed5ddfa771b616 m5ui.keyboard.M5Keyboard:5 of +msgid "The width of the keyboard." +msgstr "键盘的宽度。" + +#: ../../en/m5ui/keyboard.rst:340 ../../en/m5ui/keyboard.rst:391 +#: ../../en/m5ui/keyboard.rst:408 08968b3b30bc4d50bff42d289ea4afc7 +#: 67e3e81a51524dcaa2a944340e370d11 c500fc17c63341e6a5cc3dab16eaebb6 +#: cc8c8c38435b45449051d62403783b42 m5ui.keyboard.M5Keyboard:6 of +msgid "The height of the keyboard." +msgstr "键盘的高度。" + +#: 220260199b0242d38f0f6f2c7e5eee7a m5ui.keyboard.M5Keyboard:7 of +msgid "The keyboard mode, default is `lv.keyboard.MODE.TEXT_LOWER`." +msgstr "键盘模式,默认为“lv.keyboard.MODE.TEXT_LOWER”。" + +#: 8e6c2584aac34f40908c384e1922b213 m5ui.keyboard.M5Keyboard:8 of +msgid "The target textarea to link with the keyboard." +msgstr "与键盘链接的目标文本区域。" + +#: d1acaf43e6c2433a93e79646fb624648 m5ui.keyboard.M5Keyboard:9 of +msgid "The parent object, default is the active screen." +msgstr "父对象,默认为活动屏幕。" + +#: ../../en/m5ui/keyboard.rst:60 b158ebe191a0466dbef635ce9a48041e +msgid "" +"Set a flag on the object. If ``value`` is True, the flag is added; if " +"False, the flag is removed." +msgstr "" +"在对象上设置标志。如果``value``为True,则添加标志;如果为False,则移除标志。" + +#: ../../en/m5ui/keyboard.rst:62 1a8e32b8e3e1415caa983d14fc665002 +msgid "The flag to set." +msgstr "要设置的标志。" + +#: ../../en/m5ui/keyboard.rst:63 3ddd22aea47745caa50b054358d2dc69 +msgid "If True, the flag is added; if False, the flag is removed." +msgstr "如果为True,则添加标志;如果为False,则移除标志。" + +#: ../../en/m5ui/keyboard.rst 027778077202471e8300c84736cf2779 +#: 02b32e1ec47f4f7d9b3ac145b1e6dcf0 120f96e2b2bd4c43bea1d6d6d0dcc91c +#: 22df825669114a16882b5227a541b176 3ed44121ec3546e08d62b51eb05361cb +#: 3fd632ff176e453bb9153682bb2d03c1 527fc5761bcb4202bff699594a12bb34 +#: 649318b8d5554edf8178633dc577cc73 7be2340e2b9c4609a1bfbf4e936f1882 +#: 85994211835646d283d89999d52abd56 8635f59bbae143c78cedeada88c6993d +#: 9628fba40a654adba271038010f14eb2 a520cb9751d94a0dbac8f8aa6f6488dc +#: a962ebe24b8b41e394d2f79415601772 abb457b882e44dc0ab5d18d3c6ca6d04 +#: befb4518b71040c6bb4729c7e0805f4d d206b046f1514baf89586cda9107ea98 +#: df5a6b79c9a047a7be0dd627ef7285fa f56afe096683475f9c160a48b5dae0c5 +#: fa7683dd13a44bd8828cf8eaef851b7d fbed1c645a474e858969a485e7a956a2 +msgid "Returns" +msgstr "" + +#: ../../en/m5ui/keyboard.rst:68 1825244867334c4f990a851b24484a23 +msgid "|set_flag.png|" +msgstr "" + +#: ../../en/refs/m5ui.keyboard.ref:21 eb712d18371e42f9bfe4673f177ed10e +msgid "set_flag.png" +msgstr "" + +#: ../../en/m5ui/keyboard.rst:78 4381ac77801845e8adf20b88865b1e5d +msgid "" +"Toggle a flag on the object. If the flag is set, it is removed; if not " +"set, it is added." +msgstr "" +"切换对象上的标志。如果标志已设置,则移除;如果未设置,则添加。" + +#: ../../en/m5ui/keyboard.rst:80 1772b580ba764663bb32ad2011024087 +msgid "The flag to toggle." +msgstr "要切换的标志。" + +#: ../../en/m5ui/keyboard.rst:85 92bb3cfb25684c90972b46d457757d88 +msgid "|toggle_flag.png|" +msgstr "" + +#: ../../en/refs/m5ui.keyboard.ref:32 ce499b82b78344d2a540a4a70ab9aab6 +msgid "toggle_flag.png" +msgstr "" + +#: ../../en/m5ui/keyboard.rst:95 1ffe3e3d4901424eaa265d99b6246e27 +msgid "" +"Set the state of the keyboard. If ``value`` is True, the state is set; if" +" False, the state is unset." +msgstr "" +"设置键盘的状态。如果``value``为True,则设置状态;如果为False,则取消设置状态。" + +#: ../../en/m5ui/keyboard.rst:97 525af09ff3944df3a6a0547b76069ec6 +msgid "The state to set." +msgstr "要设置的状态。" + +#: ../../en/m5ui/keyboard.rst:98 4e2931e4ddcf455fb6bfe6f5329c39f7 +msgid "If True, the state is set; if False, the state is unset." +msgstr "如果为True,则设置状态;如果为False,则取消设置状态。" + +#: ../../en/m5ui/keyboard.rst:103 74c6f422a57c4e26a617f56f39ad617f +msgid "|set_state.png|" +msgstr "" + +#: ../../en/refs/m5ui.keyboard.ref:27 6696a99846b4471a96796c7f65b4ca9d +msgid "set_state.png" +msgstr "" + +#: ../../en/m5ui/keyboard.rst:113 18fbd369c8ef47159d60d28c1e4238cf +msgid "" +"Toggle the state of the keyboard. If the state is set, it is unset; if " +"not set, it is set." +msgstr "切换键盘状态。如果状态已设置,则取消设置;如果状态未设置,则设置状态。" + +#: ../../en/m5ui/keyboard.rst:115 e116db38119e4426ae97e3b5a4a61c47 +msgid "The state to toggle." +msgstr "要切换的状态。" + +#: ../../en/m5ui/keyboard.rst:120 3f3914a609404ac89c2139015c51a267 +msgid "|toggle_state.png|" +msgstr "" + +#: ../../en/refs/m5ui.keyboard.ref:33 fe5c99da4f984e10bec2df562021cc58 +msgid "toggle_state.png" +msgstr "" + +#: ../../en/m5ui/keyboard.rst:130 ea1fda3746b248aa804f92378d4f396b +msgid "" +"Add an event callback to the keyboard. The callback will be called when " +"the specified event occurs." +msgstr "" +"为键盘添加事件回调。当指定事件发生时,将调用回调函数。" + +#: ../../en/m5ui/keyboard.rst:132 cc647cbc95564a4f9082c0c68e355743 +msgid "The callback function to call." +msgstr "要调用的回调函数。" + +#: ../../en/m5ui/keyboard.rst:133 3e20e773ccfd416e94484de2c294b804 +msgid "The event to listen for." +msgstr "要监听的事件。" + +#: ../../en/m5ui/keyboard.rst:134 61471837508e419d8f7e0e4a8e203fc3 +msgid "Optional user data to pass to the callback." +msgstr "传递给回调函数的可选用户数据。" + +#: ../../en/m5ui/keyboard.rst:139 ec36cdce90cb4b7d9df29a9b20b9fc83 +msgid "|event.png|" +msgstr "" + +#: ../../en/refs/m5ui.keyboard.ref:14 285cce84a4354e30b0b44c5c60c2ec5f +msgid "event.png" +msgstr "" + +#: ../../en/m5ui/keyboard.rst:165 e8a379fc5b5344c38eda3871eb4647c4 +msgid "" +"Set the text area that this keyboard should control. When keys are " +"pressed, the text will be entered into the specified text area." +msgstr "" +"设置此键盘应控制的文本输入区域。当按下按键时,文本将输入到指定的文本区域中。" + +#: ../../en/m5ui/keyboard.rst:167 47864dcfa2d24ed8a4a0d8dd7e930527 +msgid "The text area object to connect to the keyboard." +msgstr "要连接到键盘的文本输入区域对象。" + +#: ../../en/m5ui/keyboard.rst:172 ea4c2e98c954470a806edb6f8ce5f1ac +msgid "|set_textarea.png|" +msgstr "" + +#: ../../en/refs/m5ui.keyboard.ref:28 45ddcd86fa8f4371b8cde14568d3c06e +msgid "set_textarea.png" +msgstr "" + +#: ../../en/m5ui/keyboard.rst:182 daf8ec07d9ae4b7cba39fc6613859013 +msgid "Get the text area that is currently connected to this keyboard." +msgstr "获取当前连接到此键盘的文本输入区域。" + +#: ../../en/m5ui/keyboard.rst:184 d3acacc3728e44b39a6179048372f500 +msgid "The connected text area object, or None if no text area is connected." +msgstr "已连接的文本输入区域对象,如果没有连接文本区域则为None。" + +#: ../../en/m5ui/keyboard.rst 00782975ec494004b2db834a0d911c67 +#: 0b9cb6f714544285ad88caacc68287dd 1e3ac0bd03ee4ab2b9d8b2d94f391aab +#: 69068045b3a14e4da87d0af1acfac54a 8032d6d10c884f64aa9e6fd1552b234f +#: 882cf8355a234547bcd95605e943bac5 +msgid "Return type" +msgstr "" + +#: ../../en/m5ui/keyboard.rst:189 93eccd8fac2f4bf99e7af113a7da3bf0 +msgid "|get_textarea.png|" +msgstr "" + +#: ../../en/refs/m5ui.keyboard.ref:19 04f4a493b7f04fbf9c8b48eded1aeb58 +msgid "get_textarea.png" +msgstr "" + +#: ../../en/m5ui/keyboard.rst:199 156dd31923cb4a7ca9b47e298ae75add +msgid "Set the keyboard mode to display different keyboard layouts." +msgstr "设置键盘模式以显示不同的键盘布局。" + +#: ../../en/m5ui/keyboard.rst:201 c7e23284ec414a15bf449fa6aa033757 +msgid "" +"The keyboard mode to set (e.g., lv.keyboard.MODE.TEXT_LOWER, " +"lv.keyboard.MODE.TEXT_UPPER, lv.keyboard.MODE.SPECIAL, " +"lv.keyboard.MODE.NUMBER)." +msgstr "" +"要设置的键盘模式(例如,lv.keyboard.MODE.TEXT_LOWER(小写文本)、" +"lv.keyboard.MODE.TEXT_UPPER(大写文本)、lv.keyboard.MODE.SPECIAL(特殊字符)、" +"lv.keyboard.MODE.NUMBER(数字))。" + +#: ../../en/m5ui/keyboard.rst:206 b13ca65331a248d4b51fbc39be683c39 +msgid "|set_mode.png|" +msgstr "" + +#: ../../en/refs/m5ui.keyboard.ref:23 4acf1a31547641af90866c1ad5896765 +msgid "set_mode.png" +msgstr "" + +#: ../../en/m5ui/keyboard.rst:216 2ce19c4ecfd74affbec1bf2b51971f88 +msgid "Get the current keyboard mode." +msgstr "获取当前的键盘模式。" + +#: ../../en/m5ui/keyboard.rst:218 0067a60b780d4e639b35ccd02e1895f2 +msgid "The current keyboard mode." +msgstr "当前的键盘模式。" + +#: ../../en/m5ui/keyboard.rst:223 246802fb3762481d9844464b5b8182be +msgid "|get_mode.png|" +msgstr "" + +#: ../../en/refs/m5ui.keyboard.ref:17 21dbd997195344cd8b96eb16159a54e6 +msgid "get_mode.png" +msgstr "" + +#: ../../en/m5ui/keyboard.rst:233 b1e6c1904a3f42fc998497a902c88217 +msgid "" +"Enable or disable popovers for the keyboard. Popovers are additional UI " +"elements that can be displayed when certain keys are pressed." +msgstr "启用或禁用键盘的弹出窗口。弹出窗口是按下某些按键时显示的附加 UI 元素。" + +#: ../../en/m5ui/keyboard.rst:235 8059a5dacb134b86b8a530896e6f11c8 +msgid "If True, popovers are enabled; if False, they are disabled." +msgstr "如果为 True,则启用弹出窗口;如果为 False,则禁用弹出窗口。" + +#: ../../en/m5ui/keyboard.rst:240 0ec28a1f08b540d2b7671370412db258 +msgid "|set_popovers.png|" +msgstr "" + +#: ../../en/refs/m5ui.keyboard.ref:24 a78f4fc9a323416ca7fba9ff05217c34 +msgid "set_popovers.png" +msgstr "" + +#: ../../en/m5ui/keyboard.rst:250 bafd4a3fe38a459db960be289956bdad +msgid "" +"Get the index of the last released button. This can be useful to " +"determine which key was last pressed." +msgstr "获取最后释放的按钮的索引。这有助于确定最后按下的键。" + +#: ../../en/m5ui/keyboard.rst:252 43c52f3b6e8946e684b9910d8c19696c +msgid "index of the last released button." +msgstr "最后释放的按钮的索引。" + +#: ../../en/m5ui/keyboard.rst:257 d3865fa8809a472389ef4f45623a9086 +msgid "|get_selected_button.png|" +msgstr "" + +#: ../../en/refs/m5ui.keyboard.ref:18 74dc044074a24d9f93f07ec1edb2b965 +msgid "get_selected_button.png" +msgstr "" + +#: ../../en/m5ui/keyboard.rst:267 4b3abad02a4b4a9789594aee05b8ae50 +msgid "Get the text of a button by its index." +msgstr "根据索引获取按钮的文本。" + +#: ../../en/m5ui/keyboard.rst:269 591f99b1b5464354810acfbad5cb8ff1 +msgid "The index of the button." +msgstr "按钮的索引。" + +#: ../../en/m5ui/keyboard.rst:270 c1cb0898752e41a0adbd8ea19f91c05d +msgid "The text of the button." +msgstr "按钮的文本。" + +#: ../../en/m5ui/keyboard.rst:275 2c81b248a0b1423f9023476bba15687f +msgid "|get_button_text.png|" +msgstr "" + +#: ../../en/refs/m5ui.keyboard.ref:15 d5ddcd04b751459db82a70b64a64a74b +msgid "get_button_text.png" +msgstr "" + +#: ../../en/m5ui/keyboard.rst:285 127eee969bda42d394bf4492c95c59ca +msgid "Set the position of the keyboard." +msgstr "设置键盘的位置。" + +#: ../../en/m5ui/keyboard.rst:287 ../../en/m5ui/keyboard.rst:305 +#: 00641b7881c9452ca1b038290626088b a5346e374f66489eb4290c77acecaa27 +msgid "The x-coordinate of the keyboard." +msgstr "键盘的x坐标。" + +#: ../../en/m5ui/keyboard.rst:288 ../../en/m5ui/keyboard.rst:322 +#: 96422a77ab314c8e9f59d8d3dd40e742 ee0b4405877b4eb3b42185b7c81db809 +msgid "The y-coordinate of the keyboard." +msgstr "键盘的y坐标。" + +#: ../../en/m5ui/keyboard.rst:293 d6e69bff24ea4a95b21b1dd744ce2e47 +msgid "|set_pos.png|" +msgstr "" + +#: ../../en/refs/m5ui.keyboard.ref:25 706923a20ad34044b47617748fba30e2 +msgid "set_pos.png" +msgstr "" + +#: ../../en/m5ui/keyboard.rst:303 3bb734bdace441fcbbd85802c854acbe +msgid "Set the x-coordinate of the keyboard." +msgstr "设置键盘的x坐标。" + +#: ../../en/m5ui/keyboard.rst:310 09d4bbd0c3b94a9fa99bcb853e1bd4a8 +msgid "|set_x.png|" +msgstr "" + +#: ../../en/refs/m5ui.keyboard.ref:30 c86198aed23b401ba67932739e5c7530 +msgid "set_x.png" +msgstr "" + +#: ../../en/m5ui/keyboard.rst:320 ea036f50804a4c7ca2ea0601dae68712 +msgid "Set the y-coordinate of the keyboard." +msgstr "设置键盘的y坐标。" + +#: ../../en/m5ui/keyboard.rst:327 69e0fb2a6a6141acb923850c3e2d26fa +msgid "|set_y.png|" +msgstr "" + +#: ../../en/refs/m5ui.keyboard.ref:31 23afbbe59ef74123a579e19c461d4e1d +msgid "set_y.png" +msgstr "" + +#: ../../en/m5ui/keyboard.rst:337 a4f55f6605004168973e852f5f05ff80 +msgid "Set the size of the keyboard." +msgstr "" + +#: ../../en/m5ui/keyboard.rst:345 a4041d323e91488aa3721b724ec8d44a +msgid "|set_size.png|" +msgstr "" + +#: ../../en/refs/m5ui.keyboard.ref:26 16910e230e1349b584254ea1889b9c27 +msgid "set_size.png" +msgstr "" + +#: ../../en/m5ui/keyboard.rst:355 a66db619308f427bba2df75bc7cec05b +msgid "Set the width of the keyboard." +msgstr "设置键盘的宽度。" + +#: ../../en/m5ui/keyboard.rst:362 821ea1202d7a41248f1d71c95552e7ed +msgid "|set_width.png|" +msgstr "" + +#: ../../en/refs/m5ui.keyboard.ref:29 e199bbab97084a0aa53fe9a09a51e8f5 +msgid "set_width.png" +msgstr "" + +#: ../../en/m5ui/keyboard.rst:372 cfc886272d694aae9a0fb7f7f70276af +msgid "Get the width of the keyboard." +msgstr "获取键盘的宽度。" + +#: ../../en/m5ui/keyboard.rst:379 0a1431d720c1426092186d5dcc0aea92 +msgid "|get_width.png|" +msgstr "" + +#: ../../en/refs/m5ui.keyboard.ref:20 d65cda304b6e4fbb929a2693dda99ec0 +msgid "get_width.png" +msgstr "" + +#: ../../en/m5ui/keyboard.rst:389 6cd9e2c7b25f4bf6a5ec6348a9227ce1 +msgid "Set the height of the keyboard." +msgstr "设置键盘的高度。" + +#: ../../en/m5ui/keyboard.rst:396 8c577b5917a74a3f88ac55de319cdd0a +msgid "|set_height.png|" +msgstr "" + +#: ../../en/refs/m5ui.keyboard.ref:22 13b74e9942d84033aea2326afd7938ad +msgid "set_height.png" +msgstr "" + +#: ../../en/m5ui/keyboard.rst:406 623d4f4703674c29844e4a3ac3e081cd +msgid "Get the height of the keyboard." +msgstr "获取键盘的高度。" + +#: ../../en/m5ui/keyboard.rst:413 6582654a92f443acadfcaed11fe6e6bd +msgid "|get_height.png|" +msgstr "" + +#: ../../en/refs/m5ui.keyboard.ref:16 faf10f98f1654b35843a4e63ad57c8d2 +msgid "get_height.png" +msgstr "" + +#: ../../en/m5ui/keyboard.rst:423 6cfe1fa64af848cc8090072975a15741 +msgid "Align the keyboard to another object." +msgstr "将键盘与另一个物体对齐。" + +#: ../../en/m5ui/keyboard.rst:425 5dfaa0096ce9426f9d847e2e5722969a +msgid "The object to align to." +msgstr "要对齐的对象。" + +#: ../../en/m5ui/keyboard.rst:426 cfbd6f3fedb64884918dd6d0489fe840 +msgid "The alignment type." +msgstr "对齐类型。" + +#: ../../en/m5ui/keyboard.rst:427 1e7a2ffbb03f4e72948bd871ed402627 +msgid "The x-offset from the aligned object." +msgstr "与对齐对象的 x 偏移量。" + +#: ../../en/m5ui/keyboard.rst:428 b0c074ff9b2b46f4be176940d0a9d98e +msgid "The y-offset from the aligned object." +msgstr "与对齐对象的 y 偏移。" + +#: ../../en/m5ui/keyboard.rst:433 256ce013909b440f978e3e00441f976c +msgid "|align_to.png|" +msgstr "" + +#: ../../en/refs/m5ui.keyboard.ref:13 62e05e46e14c46acb44cb3aed4af327c +msgid "align_to.png" +msgstr "" + diff --git a/examples/m5ui/keyboard/cores3_keyboard_basic_example.m5f2 b/examples/m5ui/keyboard/cores3_keyboard_basic_example.m5f2 new file mode 100644 index 00000000..547b3315 --- /dev/null +++ b/examples/m5ui/keyboard/cores3_keyboard_basic_example.m5f2 @@ -0,0 +1 @@ +{"version":"V2.0","versionNumber":"V2.3.2","type":"cores3","components":[{"name":"page0","type":"lvgl_page","layer":0,"screenId":"builtin","screenName":"","id":"nCJ8s3C99E_rym#j","createTime":1753848408584,"backgroundColor":"#ffffff","isLVGL":true,"isSelected":true},{"name":"keyboard0","type":"lvgl_keyboard","layer":2,"screenId":"builtin","screenName":"","id":"rYCs`5Mj@4&gLvn&","createTime":1753848417696,"x":0,"y":120,"width":320,"height":120,"mode":"TEXT_LOWER","modeOption":[{"label":"Text Lower","value":"TEXT_LOWER"},{"label":"Text Upper","value":"TEXT_UPPER"},{"label":"Special","value":"SPECIAL"},{"label":"Number","value":"NUMBER"}],"targetTextarea":"r-BGk=$$A+mDJ6bD","pageId":"nCJ8s3C99E_rym#j","isLVGL":true,"isSelected":false},{"name":"textarea0","type":"lvgl_textarea","layer":1,"screenId":"builtin","screenName":"","id":"r-BGk=$$A+mDJ6bD","createTime":1753848467487,"x":10,"y":30,"width":300,"height":70,"color":"#212121","borderColor":"#e0e0e0","backgroundColor":"#ffffff","text":"textarea0","placeholder":"Placeholder...","font":"lv.font_montserrat_14","pageId":"nCJ8s3C99E_rym#j","isLVGL":true,"isSelected":false}],"resources":[{"hardware":["hardware_button","hardware_pin_button","imu","speaker","touch","als","mic","sdcard"]}],"units":[],"hats":[],"bases":[],"i2cs":[],"blockly":"truepage0truetextarea0FOCUSEDkeyboard0HIDDENFalsetextarea0DEFOCUSEDkeyboard0HIDDENTrue","screen":[{"simulationName":"Built-in","type":"builtin","width":320,"height":240,"scale":0.77,"screenName":"","blockId":"","screenColorType":0,"rotation":1,"id":"builtin","createTime":1753848408583}],"logicWhenNum":0,"customList":[]} \ No newline at end of file diff --git a/examples/m5ui/keyboard/cores3_keyboard_basic_example.py b/examples/m5ui/keyboard/cores3_keyboard_basic_example.py new file mode 100644 index 00000000..8302bcb1 --- /dev/null +++ b/examples/m5ui/keyboard/cores3_keyboard_basic_example.py @@ -0,0 +1,89 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import os, sys, io +import M5 +from M5 import * +import m5ui +import lvgl as lv + + +page0 = None +textarea0 = None +keyboard0 = None + + +def textarea0_focused_event(event_struct): + global page0, textarea0, keyboard0 + keyboard0.set_flag(lv.obj.FLAG.HIDDEN, False) + + +def textarea0_defocused_event(event_struct): + global page0, textarea0, keyboard0 + keyboard0.set_flag(lv.obj.FLAG.HIDDEN, True) + + +def textarea0_event_handler(event_struct): + global page0, textarea0, keyboard0 + event = event_struct.code + if event == lv.EVENT.FOCUSED and True: + textarea0_focused_event(event_struct) + if event == lv.EVENT.DEFOCUSED and True: + textarea0_defocused_event(event_struct) + return + + +def setup(): + global page0, textarea0, keyboard0 + + M5.begin() + Widgets.setRotation(1) + m5ui.init() + page0 = m5ui.M5Page(bg_c=0xFFFFFF) + textarea0 = m5ui.M5TextArea( + text="textarea0", + placeholder="Placeholder...", + x=10, + y=30, + w=300, + h=70, + font=lv.font_montserrat_14, + bg_c=0xFFFFFF, + border_c=0xE0E0E0, + text_c=0x212121, + parent=page0, + ) + keyboard0 = m5ui.M5Keyboard( + x=0, + y=120, + w=320, + h=120, + mode=lv.keyboard.MODE.TEXT_LOWER, + target_textarea=textarea0, + parent=page0, + ) + + textarea0.add_event_cb(textarea0_event_handler, lv.EVENT.ALL, None) + + page0.screen_load() + + +def loop(): + global page0, textarea0, keyboard0 + M5.update() + + +if __name__ == "__main__": + try: + setup() + while True: + loop() + except (Exception, KeyboardInterrupt) as e: + try: + m5ui.deinit() + from utility import print_error_msg + + print_error_msg(e) + except ImportError: + print("please update to latest firmware") diff --git a/m5stack/libs/m5ui/__init__.py b/m5stack/libs/m5ui/__init__.py index 3569b775..54dc3070 100644 --- a/m5stack/libs/m5ui/__init__.py +++ b/m5stack/libs/m5ui/__init__.py @@ -12,6 +12,7 @@ "M5Canvas": "canvas", "M5Checkbox": "checkbox", "M5Image": "image", + "M5Keyboard": "keyboard", "M5Label": "label", "M5Line": "line", "M5List": "list", diff --git a/m5stack/libs/m5ui/keyboard.py b/m5stack/libs/m5ui/keyboard.py new file mode 100644 index 00000000..d2164c38 --- /dev/null +++ b/m5stack/libs/m5ui/keyboard.py @@ -0,0 +1,61 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +from .base import M5Base +import lvgl as lv + + +class M5Keyboard(lv.keyboard): + """Create a keyboard widget. + + :param int x: The x position of the keyboard. + :param int y: The y position of the keyboard. + :param int w: The width of the keyboard. + :param int h: The height of the keyboard. + :param int mode: The keyboard mode, default is `lv.keyboard.MODE.TEXT_LOWER`. + :param lv.obj target_textarea: The target textarea to link with the keyboard. + :param lv.obj parent: The parent object, default is the active screen. + + UiFlow2 Code Block: + + None + + MicroPython Code Block: + + .. code-block:: python + + import m5ui + import lvgl as lv + + m5ui.init() + keyboard = m5ui.M5Keyboard(x=0, y=120, w=320, h=100, target_textarea=None, parent=page0) + """ + + def __init__( + self, + x=0, + y=0, + w=200, + h=100, + mode=lv.keyboard.MODE.TEXT_LOWER, + target_textarea=None, + parent=None, + ): + if parent is None: + parent = lv.screen_active() + super().__init__(parent) + + self.set_size(w, h) + self.set_pos(x, y) + self.set_align(lv.ALIGN.TOP_LEFT) + self.set_mode(mode) + self.set_textarea(target_textarea) + + def __getattr__(self, name): + if hasattr(M5Base, name): + method = getattr(M5Base, name) + bound_method = lambda *args, **kwargs: method(self, *args, **kwargs) + setattr(self, name, bound_method) + return bound_method + raise AttributeError(f"'{self.__class__.__name__}' object has no attribute '{name}'") diff --git a/m5stack/libs/m5ui/manifest.py b/m5stack/libs/m5ui/manifest.py index 09fbe3a3..230a50fe 100644 --- a/m5stack/libs/m5ui/manifest.py +++ b/m5stack/libs/m5ui/manifest.py @@ -14,6 +14,7 @@ "canvas.py", "checkbox.py", "image.py", + "keyboard.py", "label.py", "line.py", "list.py", From 957350ebacf6b1c7e91fda139c34b273f711f6d9 Mon Sep 17 00:00:00 2001 From: hlym123 Date: Thu, 7 Aug 2025 17:13:21 +0800 Subject: [PATCH 201/322] libs/module: Add version check for Module13.2 QRCode. Signed-off-by: hlym123 --- m5stack/libs/driver/qrcode/qrcode_m14.py | 36 ++++++++++++++++++++++-- m5stack/libs/module/qrcode.py | 3 ++ 2 files changed, 36 insertions(+), 3 deletions(-) diff --git a/m5stack/libs/driver/qrcode/qrcode_m14.py b/m5stack/libs/driver/qrcode/qrcode_m14.py index b725afb7..15051432 100644 --- a/m5stack/libs/driver/qrcode/qrcode_m14.py +++ b/m5stack/libs/driver/qrcode/qrcode_m14.py @@ -34,17 +34,22 @@ class QRCodeM14: POS_LIGHT_ON_DECODE = const(2) # 解码时亮 POS_LIGHT_FLASH_ON_DECODE = const(1) # 解码时闪烁 - def __init__(self, id: int = 1, tx: int = 5, rx: int = 6, trig: int = 7, led=None) -> None: + def __init__(self, id: int = 1, tx: int = 5, rx: int = 6, trig: int = None, led=None) -> None: self.serial = machine.UART(id, baudrate=115200, tx=rx, rx=tx) # 交叉 self.serial_handler = serial_cmd_helper.SerialCmdHelper(self.serial, False) - self.trig = machine.Pin(trig, machine.Pin.OUT) + self.trig = None + if trig is not None: + self.trig = machine.Pin(trig, machine.Pin.OUT) self.led = led def set_trig(self, value: int) -> None: """Set trigger pin. 设置触发引脚。 :param int value: ``0`` - 低电平,``1`` - 高电平。 """ - self.trig.value(value) + if self.trig is not None: + self.trig.value(value) + else: + raise RuntimeError("Trigger pin is not initialized.") def start_decode(self) -> None: """Start decode. 开始解码。""" @@ -260,3 +265,28 @@ def set_protocol_format(self, mode: int) -> None: cmd = bytes([0x21, 0x51, 0x43, mode]) cmd_ack = bytes([0x22, 0x51, 0x43, 0x00]) self.serial_handler.cmd(cmd, cmd_ack, timeout_ms=200) + + def get_version(self, timeout_ms=500): + self.serial.read() + self.serial.write(b'\x43\x02\xc1') # 查询版本 + start = time.ticks_ms() + rx = b'' + while time.ticks_diff(time.ticks_ms(), start) < timeout_ms: + if self.serial.any(): + time.sleep_ms(5) + rx += self.serial.read() + data_len = (rx[3] << 8) + rx[4] + if len(rx) >= 4 + data_len: + if rx[0] == 0x44 and rx[1] == 0x02 and rx[2] == 0xC1: + total_len = 4 + data_len + if len(rx) >= total_len: + payload = rx[4:4 + data_len + 1] + try: + version_str = payload.decode('ascii') + return version_str[1:] + except: + return None + else: + return None + time.sleep_ms(1) + return None diff --git a/m5stack/libs/module/qrcode.py b/m5stack/libs/module/qrcode.py index 63ab795d..5f58166c 100644 --- a/m5stack/libs/module/qrcode.py +++ b/m5stack/libs/module/qrcode.py @@ -49,6 +49,9 @@ def __init__(self, id: int = 1, tx: int = 17, rx: int = 18): self.init_device() self.set_power(True) # 默认开启电源 time.sleep_ms(1000) # wait for module startup. + # Check Version + if self.get_version() != "1.0": + raise RuntimeError("Module13.2 QRCode not found!") def _write_reg(self, reg, value): self.i2c.writeto(self.address, bytes([reg, value])) From 9e69b613e56d6ed262e8dbbb9ba7fdfaa80ff20b Mon Sep 17 00:00:00 2001 From: "tinyu.zhao@gmail.com" Date: Thu, 7 Aug 2025 16:40:14 +0800 Subject: [PATCH 202/322] lib/m5ui: Add LVGL Calendar widget docs and example. Signed-off-by: tinyu.zhao@gmail.com --- docs/en/m5ui/calendar.rst | 38 +-- docs/en/refs/m5ui.calendar.ref | 30 +- .../zh_CN/LC_MESSAGES/m5ui/calendar.po | 317 ++++++++++++++++++ .../m5ui/calendar/calendar_core2_example.m5f2 | 1 + .../m5ui/calendar/calendar_core2_example.py | 80 +++++ .../cores3_calendar_event_example.m5f2 | 1 - .../calendar/cores3_calendar_event_example.py | 82 ----- m5stack/libs/m5ui/calendar.py | 4 - 8 files changed, 415 insertions(+), 138 deletions(-) create mode 100644 docs/locales/zh_CN/LC_MESSAGES/m5ui/calendar.po create mode 100644 examples/m5ui/calendar/calendar_core2_example.m5f2 create mode 100644 examples/m5ui/calendar/calendar_core2_example.py delete mode 100644 examples/m5ui/calendar/cores3_calendar_event_example.m5f2 delete mode 100644 examples/m5ui/calendar/cores3_calendar_event_example.py diff --git a/docs/en/m5ui/calendar.rst b/docs/en/m5ui/calendar.rst index 671dd0fe..d240a418 100644 --- a/docs/en/m5ui/calendar.rst +++ b/docs/en/m5ui/calendar.rst @@ -13,13 +13,13 @@ UiFlow2 Example event calendar ^^^^^^^^^^^^^^ -Open the |cores3_calendar_event_example.m5f2| project in UiFlow2. +Open the |calendar_core2_example.m5f2| project in UiFlow2. This example creates a calendar that triggers an event when the date is changed. UiFlow2 Code Block: - |cores3_calendar_event_example.png| + |calendar_core2_example.png| Example output: @@ -36,7 +36,7 @@ This example creates a calendar that triggers an event when the date is changed. MicroPython Code Block: - .. literalinclude:: ../../../examples/m5ui/calendar/cores3_calendar_event_example.py + .. literalinclude:: ../../../examples/m5ui/calendar/calendar_core2_example.py :language: python :linenos: @@ -138,38 +138,6 @@ M5Calendar calendar_0.set_size(100, 50) - .. py:method:: set_width(width) - - Set the width of the calendar. - - :param int width: The width of the calendar. - - UiFlow2 Code Block: - - |set_width.png| - - MicroPython Code Block: - - .. code-block:: python - - calendar_0.set_width(100) - - .. py:method:: set_height(height) - - Set the height of the calendar. - - :param int height: The height of the calendar. - - UiFlow2 Code Block: - - |set_height.png| - - MicroPython Code Block: - - .. code-block:: python - - calendar_0.set_height(50) - .. py:method:: align_to(obj, align, x, y) Align the calendar to another object. diff --git a/docs/en/refs/m5ui.calendar.ref b/docs/en/refs/m5ui.calendar.ref index 8cdb36ff..827aaafb 100644 --- a/docs/en/refs/m5ui.calendar.ref +++ b/docs/en/refs/m5ui.calendar.ref @@ -1,23 +1,21 @@ -.. |align_to.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/switch/align_to.png -.. |event.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/switch/event.png -.. |set_calendar_style.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/switch/set_calendar_style.png -.. |set_today_date.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/switch/set_today_date.png -.. |set_month_shown.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/switch/set_month_shown.png -.. |set_highlighted_dates.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/switch/set_highlighted_dates.png -.. |set_height.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/switch/set_height.png -.. |set_pos.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/switch/set_pos.png -.. |set_size.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/switch/set_size.png -.. |set_width.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/switch/set_width.png -.. |set_x.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/switch/set_x.png -.. |set_y.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/switch/set_y.png +.. |align_to.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/calendar/align_to.png +.. |event.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/calendar/event.png +.. |set_calendar_style.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/calendar/set_calendar_style.png +.. |set_today_date.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/calendar/set_today_date.png +.. |set_month_shown.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/calendar/set_month_shown.png +.. |set_highlighted_dates.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/calendar/set_highlighted_dates.png +.. |set_pos.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/calendar/set_pos.png +.. |set_size.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/calendar/set_size.png +.. |set_x.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/calendar/set_x.png +.. |set_y.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/calendar/set_y.png -.. |cores3_calendar_event_example.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/switch/example.png +.. |calendar_core2_example.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/calendar/example.png -.. |cores3_calendar_event_example.m5f2| raw:: html +.. |calendar_core2_example.m5f2| raw:: html - cores3_calendar_event_example.m5f2 + calendar_core2_example.m5f2 diff --git a/docs/locales/zh_CN/LC_MESSAGES/m5ui/calendar.po b/docs/locales/zh_CN/LC_MESSAGES/m5ui/calendar.po new file mode 100644 index 00000000..252d1a68 --- /dev/null +++ b/docs/locales/zh_CN/LC_MESSAGES/m5ui/calendar.po @@ -0,0 +1,317 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2016 - 2025 M5Stack Technology Co., Ltd +# This file is distributed under the same license as the UIFlow2 Programming +# Guide package. +# FIRST AUTHOR , 2025. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: UIFlow2 Programming Guide \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-08-07 16:26+0800\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: zh_CN\n" +"Language-Team: zh_CN \n" +"Plural-Forms: nplurals=1; plural=0;\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.16.0\n" + +#: ../../en/m5ui/calendar.rst:4 ../../en/m5ui/calendar.rst:52 +#: 217f50617093415fae7abf285574dab5 b6d947862be6415588b41c19a38223e2 +msgid "M5Calendar" +msgstr "" + +#: ../../en/m5ui/calendar.rst:8 c2e5003f06b444de9874b83d3fcc4acc +msgid "" +"M5Calendar is a widget that can be used to create a calendar in the user " +"interface. It can be used to display and select dates." +msgstr "M5Calendar 是一个可在用户界面中创建日历的控件,可用于显示并选择日期。" + +#: ../../en/m5ui/calendar.rst:11 3a42f19d5c4446aba40470ba7affa737 +msgid "UiFlow2 Example" +msgstr "UiFlow2 示例" + +#: ../../en/m5ui/calendar.rst:14 ../../en/m5ui/calendar.rst:33 +#: a606c5881071472cb448f67cec49b7dd +msgid "event calendar" +msgstr "事件日历" + +#: ../../en/m5ui/calendar.rst:16 c906ef0de21f4ac1b83b1690231083bf +msgid "Open the |calendar_core2_example.m5f2| project in UiFlow2." +msgstr "" + +#: ../../en/m5ui/calendar.rst:18 ../../en/m5ui/calendar.rst:35 +#: ee44763896324c1b8a064c54c6537062 +msgid "" +"This example creates a calendar that triggers an event when the date is " +"changed." +msgstr "本示例创建了一个日历,当日期更改时会触发事件。" + +#: ../../en/m5ui/calendar.rst:20 ../../en/m5ui/calendar.rst:65 +#: ../../en/m5ui/calendar.rst:82 ../../en/m5ui/calendar.rst:98 +#: ../../en/m5ui/calendar.rst:114 ../../en/m5ui/calendar.rst:131 +#: ../../en/m5ui/calendar.rst:150 ../../en/m5ui/calendar.rst:168 +#: 26eace74ec34442f84ad57404eb2f268 962058dfa346478aa7c20adc9596fcf8 +#: m5ui.calendar.M5Calendar:12 m5ui.calendar.M5Calendar.set_calendar_style:5 +#: m5ui.calendar.M5Calendar.set_highlighted_dates:5 of +msgid "UiFlow2 Code Block:" +msgstr "UiFlow2 代码块:" + +#: ../../en/m5ui/calendar.rst:22 d1976e80d1d84423a3af2543566b6f58 +msgid "|calendar_core2_example.png|" +msgstr "" + +#: ../../en/refs/m5ui.calendar.ref:12 fbace895446e4599a06d22144765611a +msgid "calendar_core2_example.png" +msgstr "" + +#: ../../en/m5ui/calendar.rst:24 ../../en/m5ui/calendar.rst:43 +msgid "Example output:" +msgstr "示例输出:" + +#: ../../en/m5ui/calendar.rst:26 ../../en/m5ui/calendar.rst:45 +msgid "None" +msgstr "" + +#: ../../en/m5ui/calendar.rst:30 9a90fa3230004c9ca091e2145aab2b7b +msgid "MicroPython Example" +msgstr "MicroPython 示例" + +#: ../../en/m5ui/calendar.rst:37 ../../en/m5ui/calendar.rst:69 +#: ../../en/m5ui/calendar.rst:86 ../../en/m5ui/calendar.rst:102 +#: ../../en/m5ui/calendar.rst:118 ../../en/m5ui/calendar.rst:135 +#: ../../en/m5ui/calendar.rst:154 ../../en/m5ui/calendar.rst:172 +#: 50577195f6b84bf8865333900eabc93e 56f2a4c8de2c486295e3059b8035ade5 +#: m5ui.calendar.M5Calendar:16 m5ui.calendar.M5Calendar.set_calendar_style:9 +#: m5ui.calendar.M5Calendar.set_highlighted_dates:9 of +msgid "MicroPython Code Block:" +msgstr "MicroPython 代码块:" + +#: ../../en/m5ui/calendar.rst:49 8d8a8b8cbd8844d4a77dff79fabb53df +msgid "**API**" +msgstr "" + +#: 365fff94cd484cfa940c50d770cccff1 m5ui.calendar.M5Calendar:1 of +msgid "Bases: :py:class:`~lvgl.calendar`" +msgstr "" + +#: ee47ae855f95467c9fcef4b9125a15d3 m5ui.calendar.M5Calendar:1 of +msgid "Create a calendar object." +msgstr "创建一个日历对象。" + +#: ../../en/m5ui/calendar.rst 7321058bd235427795e5c98da8936948 +#: b230a15c556343699923bf65fe70922e m5ui.calendar.M5Calendar.set_calendar_style +#: m5ui.calendar.M5Calendar.set_highlighted_dates of +msgid "Parameters" +msgstr "参数" + +#: dbeee83de64e463bba0857321f1121ec m5ui.calendar.M5Calendar:3 of +msgid "The x position of the calendar." +msgstr "日历的 x 坐标。" + +#: 8a652163d5d04e52af868368c8c2d834 m5ui.calendar.M5Calendar:4 of +msgid "The y position of the calendar." +msgstr "日历的 y 坐标。" + +#: ../../en/m5ui/calendar.rst:128 8f2313e750534f6688e145c6f33100d7 +#: m5ui.calendar.M5Calendar:5 of +msgid "The width of the calendar." +msgstr "日历的宽度。" + +#: ../../en/m5ui/calendar.rst:129 3897da666fcb4e889859e110496867a6 +#: 7d1f8e20d7fe4b99bc3e053d450c08f6 m5ui.calendar.M5Calendar:6 of +msgid "The height of the calendar." +msgstr "日历的高度。" + +#: 4d04d1d5a8104d91a3ebd0b769d87bab m5ui.calendar.M5Calendar:7 of +msgid "The style of the calendar, can be \"arrow\" or \"dropdown\" and None." +msgstr "日历的样式,可为 \"arrow\"、\"dropdown\" 或 None。" + +#: 9408b1ae3c644362b458de962b7cb57f m5ui.calendar.M5Calendar:8 of +msgid "The date to highlight as today in the format [year, month, day]." +msgstr "要高亮显示为今天的日期,格式为 [year, month, day]。" + +#: 469a9454b56e4c3fbac2b2c9e9bc62ad m5ui.calendar.M5Calendar:9 of +msgid "The month to show in the format [year, month]." +msgstr "要显示的月份,格式为 [year, month]。" + +#: a5e738e915d94263aafdd7e7b6e65d2e m5ui.calendar.M5Calendar:10 of +msgid "" +"The parent object to attach the calendar to. If not specified, the " +"calendar will be attached to the default screen." +msgstr "要将日历附加到的父对象;若未指定,则附加到默认屏幕。" + +#: ../../en/m5ui/calendar.rst:60 f9181e41720d42f99f67a692c7c4006e +msgid "Set the month and year shown in the calendar." +msgstr "设置日历显示的年份和月份。" + +#: ../../en/m5ui/calendar.rst:62 dbff5892b4da468285e130a1f1f5ccc6 +msgid "The year to show." +msgstr "要显示的年份。" + +#: ../../en/m5ui/calendar.rst:63 8616158910f84b6181c5af1c4bb108cb +msgid "The month to show." +msgstr "要显示的月份。" + +#: ../../en/m5ui/calendar.rst:67 9a93f2b0428b45f088beb584ae21c96a +msgid "|set_month_shown.png|" +msgstr "" + +#: ../../en/refs/m5ui.calendar.ref:5 54456098c7c342bbac67f9567ed1802b +msgid "set_month_shown.png" +msgstr "" + +#: ../../en/m5ui/calendar.rst:77 ea511d661a514094a4d34b8ec8889fdb +msgid "Set the position of the calendar." +msgstr "设置日历的位置。" + +#: ../../en/m5ui/calendar.rst:79 ../../en/m5ui/calendar.rst:96 +#: 043c2b6ea7aa4e73bd0230a4b81bb0f6 4b604af7ce884cac96c36eca178a4dd0 +msgid "The x-coordinate of the calendar." +msgstr "日历的 x 坐标。" + +#: ../../en/m5ui/calendar.rst:80 ../../en/m5ui/calendar.rst:112 +#: a26aa37256674d769064cf2b96e003ba +msgid "The y-coordinate of the calendar." +msgstr "日历的 y 坐标。" + +#: ../../en/m5ui/calendar.rst:84 bb13862738a74ddf90a81812b7ea56c4 +msgid "|set_pos.png|" +msgstr "" + +#: ../../en/refs/m5ui.calendar.ref:7 dd217a8cc6c2453f890b54aed1dcfbd3 +msgid "set_pos.png" +msgstr "" + +#: ../../en/m5ui/calendar.rst:94 6f45845359e743ab82fea16ede5cc378 +msgid "Set the x-coordinate of the calendar." +msgstr "设置日历的 x 坐标。" + +#: ../../en/m5ui/calendar.rst:100 9d31009177354958b92ceefc974ed044 +msgid "|set_x.png|" +msgstr "" + +#: ../../en/refs/m5ui.calendar.ref:9 b1fb4df9b0534139972614c7f240feaf +msgid "set_x.png" +msgstr "" + +#: ../../en/m5ui/calendar.rst:110 56b3706541504c2588d9a83be38ffefd +msgid "Set the y-coordinate of the calendar." +msgstr "设置日历的 y 坐标。" + +#: ../../en/m5ui/calendar.rst:116 73c0036afdfb486ea337ac5b573bb91c +msgid "|set_y.png|" +msgstr "" + +#: ../../en/refs/m5ui.calendar.ref:10 e6dd933379ed4a04b2a29d12cdebe25c +msgid "set_y.png" +msgstr "" + +#: ../../en/m5ui/calendar.rst:126 0e4eaa2ce50a4074ac04edf25ca22837 +msgid "Set the size of the calendar." +msgstr "设置日历的尺寸。" + +#: ../../en/m5ui/calendar.rst:133 +msgid "|set_size.png|" +msgstr "" + +#: ../../en/refs/m5ui.calendar.ref:8 1692716dfaaa4ff38951963b24a313b3 +msgid "set_size.png" +msgstr "" + +#: ../../en/m5ui/calendar.rst:143 3ed4a43ca6d04032b9e47f7ee6ed2d06 +msgid "Align the calendar to another object." +msgstr "将日历对齐到另一个对象。" + +#: ../../en/m5ui/calendar.rst:145 9d1fc49c8e5642539740aad91f6119f0 +msgid "The object to align to." +msgstr "要对齐到的对象。" + +#: ../../en/m5ui/calendar.rst:146 e1b91589524d4389a17501fe9ed5f5a8 +msgid "The alignment type." +msgstr "对齐类型。" + +#: ../../en/m5ui/calendar.rst:147 9b18e7c8d4c6422f86646e311848f390 +msgid "The x-offset from the aligned object." +msgstr "相对于对齐对象的 x 偏移量。" + +#: ../../en/m5ui/calendar.rst:148 919d1db90c84495fa597db246fc72621 +msgid "The y-offset from the aligned object." +msgstr "相对于对齐对象的 y 偏移量。" + +#: ../../en/m5ui/calendar.rst:152 +msgid "|align_to.png|" +msgstr "" + +#: ../../en/refs/m5ui.calendar.ref:1 30be346916b74c689b8d5ee956f91b71 +msgid "align_to.png" +msgstr "" + +#: ../../en/m5ui/calendar.rst:162 217923ee1b114d3ea5f143acb5f83c15 +msgid "" +"Add an event callback to the calendar. The callback will be called when " +"the specified event occurs." +msgstr "为日历添加事件回调。当指定事件发生时,将调用该回调函数。" + +#: ../../en/m5ui/calendar.rst:164 3a6444c6b1a947e0beb9c9ff3e7a1b74 +msgid "The callback function to call." +msgstr "要调用的回调函数。" + +#: ../../en/m5ui/calendar.rst:165 aac00baffb0c4be3b912b176fecc1a5f +msgid "The event to listen for." +msgstr "要监听的事件。" + +#: ../../en/m5ui/calendar.rst:166 7070063e6b1345a88f7a63a2a981c04c +msgid "Optional user data to pass to the callback." +msgstr "可选用户数据,将传递给回调函数。" + +#: ../../en/m5ui/calendar.rst:170 544890c4d33a4b6db3319696a7157174 +msgid "|event.png|" +msgstr "" + +#: ../../en/refs/m5ui.calendar.ref:2 888a9bf95c764d04b281c7ccaa141e74 +msgid "event.png" +msgstr "" + +#: 75c0d5a417b742f1bf4bc56e40340c96 +#: m5ui.calendar.M5Calendar.set_calendar_style:1 of +msgid "Set the style of the calendar header." +msgstr "设置日历标题的样式。" + +#: ee30bd52ad1b44a89cb57ab65bdc386a +msgid "The style of the calendar header, can be \"arrow\", \"dropdown\", or None." +msgstr "日历标题的样式,可为 \"arrow\"、\"dropdown\" 或 None。" + +#: a0d8d3b2ed5740629d83cbfac1eddf3f +msgid "|set_calendar_style.png|" +msgstr "" + +#: ../../en/refs/m5ui.calendar.ref:3 a812371f42104df2853248e9b3e4fc49 +msgid "set_calendar_style.png" +msgstr "" + +#: 231de4ee14454e9891c1c042919695da +#: m5ui.calendar.M5Calendar.set_highlighted_dates:1 of +msgid "Set the highlighted dates in the calendar." +msgstr "设置日历中的高亮日期。" + +#: 7a4547f507ae4e64b48dd030477b28c3 +#: m5ui.calendar.M5Calendar.set_highlighted_dates:3 of +msgid "" +"A list of dates to highlight in the format [year, month, day, year, " +"month, day, ...]" +msgstr "要高亮显示的日期列表,格式为 [year, month, day, year, month, day, ...]" + +#: d9f221fc6dd749bd9e629ed464324abf +#: m5ui.calendar.M5Calendar.set_highlighted_dates:7 of +msgid "|set_highlighted_dates.png|" +msgstr "" + +#: ../../en/refs/m5ui.calendar.ref:6 1f2605c2650b4ed8b59a746391428539 +msgid "set_highlighted_dates.png" +msgstr "" + diff --git a/examples/m5ui/calendar/calendar_core2_example.m5f2 b/examples/m5ui/calendar/calendar_core2_example.m5f2 new file mode 100644 index 00000000..55c2f8f7 --- /dev/null +++ b/examples/m5ui/calendar/calendar_core2_example.m5f2 @@ -0,0 +1 @@ +{"version":"V2.0","versionNumber":"V2.3.2","type":"core2","components":[{"name":"page0","type":"lvgl_page","layer":0,"screenId":"builtin","screenName":"","id":"eWWm1TAQ7stbDhg*","createTime":1754540957973,"backgroundColor":"#ffffff","isLVGL":true,"isSelected":true},{"name":"calendar0","type":"lvgl_calendar","layer":1,"screenId":"builtin","screenName":"","id":"p%i74%V&@rVcZTYh","createTime":1754540963877,"x":0,"y":0,"width":320,"height":240,"style":"arrow","todayDate":[2025,8,7],"pageId":"eWWm1TAQ7stbDhg*","isLVGL":true,"isSelected":false}],"resources":[{"hardware":["hardware_button","hardware_pin_button","imu","speaker","touch","mic","sdcard"]}],"units":[],"hats":[],"bases":[],"i2cs":[],"blockly":"yearmonthdaytruepage0truecalendar0yearmonthdaycalendar02025year8month7dayhello M5Today is:Today is yearToday is monthday","screen":[{"simulationName":"Built-in","type":"builtin","width":320,"height":240,"scale":0.77,"screenName":"","blockId":"","screenColorType":0,"rotation":1,"id":"builtin","createTime":1754540957970}],"logicWhenNum":0,"customList":[]} \ No newline at end of file diff --git a/examples/m5ui/calendar/calendar_core2_example.py b/examples/m5ui/calendar/calendar_core2_example.py new file mode 100644 index 00000000..59d56b2a --- /dev/null +++ b/examples/m5ui/calendar/calendar_core2_example.py @@ -0,0 +1,80 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import os, sys, io +import M5 +from M5 import * +import m5ui +import lvgl as lv + + +page0 = None +calendar0 = None + + +year = None +month = None +day = None + + +def calendar0_value_changed_event(date): + global page0, calendar0, year, month, day + year = date.year + month = date.month + day = date.day + calendar0.set_today_date(year, month, day) + print((str("Today is:") + str((str(year) + str((str(month) + str(day))))))) + + +def calendar0_event_handler(event_struct): + global page0, calendar0, year, month, day + event = event_struct.code + if event == lv.EVENT.VALUE_CHANGED: + date = lv.calendar_date_t() + if calendar0.get_pressed_date(date) == lv.RESULT.OK: + calendar0_value_changed_event(date) + return + + +def setup(): + global page0, calendar0, year, month, day + + M5.begin() + Widgets.setRotation(1) + m5ui.init() + page0 = m5ui.M5Page(bg_c=0xFFFFFF) + calendar0 = m5ui.M5Calendar( + x=0, + y=0, + w=320, + h=240, + style="arrow", + today_date=[2025, 8, 7], + show_month=[2025, 8], + parent=page0, + ) + + calendar0.add_event_cb(calendar0_event_handler, lv.EVENT.ALL, None) + + page0.screen_load() + + +def loop(): + global page0, calendar0, year, month, day + M5.update() + + +if __name__ == "__main__": + try: + setup() + while True: + loop() + except (Exception, KeyboardInterrupt) as e: + try: + m5ui.deinit() + from utility import print_error_msg + + print_error_msg(e) + except ImportError: + print("please update to latest firmware") diff --git a/examples/m5ui/calendar/cores3_calendar_event_example.m5f2 b/examples/m5ui/calendar/cores3_calendar_event_example.m5f2 deleted file mode 100644 index 29e726ee..00000000 --- a/examples/m5ui/calendar/cores3_calendar_event_example.m5f2 +++ /dev/null @@ -1 +0,0 @@ -{"version":"V2.0","versionNumber":"V2.3.0","type":"cores3","components":[{"name":"page0","type":"lvgl_page","layer":0,"screenId":"builtin","screenName":"","id":"m9@lpi+_=W%+Mg*k","createTime":1751869561848,"backgroundColor":"#ffffff","isLVGL":true,"isSelected":true},{"name":"switch0","type":"lvgl_switch","layer":1,"screenId":"builtin","screenName":"","id":"s-ESKHmEhR7aT8SQ","createTime":1751869566947,"x":128,"y":91,"width":60,"height":30,"knobColor":"#ffffff","backgroundColor":"#e7e3e7","checkedBackgroundColor":"#2196f3","pageId":"m9@lpi+_=W%+Mg*k","isLVGL":true,"isSelected":false}],"resources":[{"hardware":["hardware_button","hardware_pin_button","imu","speaker","touch","als","mic","sdcard"]}],"units":[],"hats":[],"bases":[],"i2cs":[],"blockly":"truepage0switch0palette#666666255switch0palette#33ff33255trueswitch0CHECKEDswitch0 checkedswitch0UNCHECKEDswitch0 unchecked","screen":[{"simulationName":"Built-in","type":"builtin","width":320,"height":240,"scale":0.77,"screenName":"","blockId":"","screenColorType":0,"rotation":1,"id":"builtin","createTime":1751869561847}],"logicWhenNum":0,"customList":[]} \ No newline at end of file diff --git a/examples/m5ui/calendar/cores3_calendar_event_example.py b/examples/m5ui/calendar/cores3_calendar_event_example.py deleted file mode 100644 index b23895cb..00000000 --- a/examples/m5ui/calendar/cores3_calendar_event_example.py +++ /dev/null @@ -1,82 +0,0 @@ -# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD -# -# SPDX-License-Identifier: MIT - -import os, sys, io -import M5 -from M5 import * -import m5ui -import lvgl as lv - - -page0 = None -switch0 = None - - -def switch0_checked_event(event_struct): - global page0, switch0 - - print("switch0 checked") - - -def switch0_unchecked_event(event_struct): - global page0, switch0 - - print("switch0 unchecked") - - -def switch0_event_handler(event_struct): - global page0, switch0 - event = event_struct.code - obj = event_struct.get_target_obj() - if event == lv.EVENT.VALUE_CHANGED: - if obj.has_state(lv.STATE.CHECKED): - switch0_checked_event(event_struct) - else: - switch0_unchecked_event(event_struct) - return - - -def setup(): - global page0, switch0 - - M5.begin() - Widgets.setRotation(1) - m5ui.init() - page0 = m5ui.M5Page(bg_c=0xFFFFFF) - switch0 = m5ui.M5Switch( - x=128, - y=91, - w=60, - h=30, - bg_c=0xE7E3E7, - bg_c_checked=0x2196F3, - circle_c=0xFFFFFF, - parent=page0, - ) - - switch0.add_event_cb(switch0_event_handler, lv.EVENT.ALL, None) - - page0.screen_load() - switch0.set_bg_color(0x666666, 255, lv.PART.MAIN | lv.STATE.DEFAULT) - switch0.set_bg_color(0x33FF33, 255, lv.PART.INDICATOR | lv.STATE.CHECKED) - - -def loop(): - global page0, switch0 - M5.update() - - -if __name__ == "__main__": - try: - setup() - while True: - loop() - except (Exception, KeyboardInterrupt) as e: - try: - m5ui.deinit() - from utility import print_error_msg - - print_error_msg(e) - except ImportError: - print("please update to latest firmware") diff --git a/m5stack/libs/m5ui/calendar.py b/m5stack/libs/m5ui/calendar.py index 66d9ea2e..9ee42a8e 100644 --- a/m5stack/libs/m5ui/calendar.py +++ b/m5stack/libs/m5ui/calendar.py @@ -18,10 +18,6 @@ class M5Calendar(lv.calendar): :param list show_month: The month to show in the format [year, month]. :param lv.obj parent: The parent object to attach the calendar to. If not specified, the calendar will be attached to the default screen. - UiFlow2 Code Block: - - None - MicroPython Code Block: .. code-block:: python From ac432b8455a3c9523a08826aaa5a9b3259c6e376 Mon Sep 17 00:00:00 2001 From: lbuque <1102390310@qq.com> Date: Thu, 7 Aug 2025 18:00:33 +0800 Subject: [PATCH 203/322] version.text: Update to V2.3.3. Signed-off-by: lbuque <1102390310@qq.com> --- m5stack/version.txt | 2 +- third-party/version.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/m5stack/version.txt b/m5stack/version.txt index dfe2ef9c..2e32f5aa 100644 --- a/m5stack/version.txt +++ b/m5stack/version.txt @@ -1 +1 @@ -V2.3.2 \ No newline at end of file +V2.3.3 \ No newline at end of file diff --git a/third-party/version.txt b/third-party/version.txt index dfe2ef9c..2e32f5aa 100644 --- a/third-party/version.txt +++ b/third-party/version.txt @@ -1 +1 @@ -V2.3.2 \ No newline at end of file +V2.3.3 \ No newline at end of file From 82847b83058bb621d6f09378e181cdba837c687e Mon Sep 17 00:00:00 2001 From: lbuque <1102390310@qq.com> Date: Thu, 14 Aug 2025 10:38:28 +0800 Subject: [PATCH 204/322] modules/startup: Fix image resource path error. Signed-off-by: lbuque <1102390310@qq.com> --- m5stack/libs/image_plus/__init__.py | 4 +- m5stack/modules/startup/basic/res.py | 100 +++++++++++++------------- m5stack/modules/startup/stickc.py | 44 ++++++------ m5stack/modules/startup/stickcplus.py | 34 ++++----- 4 files changed, 92 insertions(+), 90 deletions(-) diff --git a/m5stack/libs/image_plus/__init__.py b/m5stack/libs/image_plus/__init__.py index 592c31cd..535678a2 100644 --- a/m5stack/libs/image_plus/__init__.py +++ b/m5stack/libs/image_plus/__init__.py @@ -14,7 +14,9 @@ class ImagePlus(Widgets.Image): - def __init__(self, url, x, y, enable, period, default_img="res/img/default.jpg", parent=None): + def __init__( + self, url, x, y, enable, period, default_img="/flash/res/img/default.jpg", parent=None + ): self._url = url self._x = x self._y = y diff --git a/m5stack/modules/startup/basic/res.py b/m5stack/modules/startup/basic/res.py index e9173b90..001448e7 100644 --- a/m5stack/modules/startup/basic/res.py +++ b/m5stack/modules/startup/basic/res.py @@ -11,56 +11,56 @@ def is_4mb(): if is_4mb(): _attrs = { - "BATTERY_BLACK_CHARGE_IMG": "res/basic/Battery/battery_Black_Charge.jpg", - "BATTERY_BLACK_IMG": "res/basic/Battery/battery_Black.jpg", - "BATTERY_GRAY_IMG": "res/basic/Battery/battery_Gray.jpg", - "BATTERY_GREEN_CHARGE_IMG": "res/basic/Battery/battery_Green_Charge.jpg", - "BATTERY_GREEN_IMG": "res/basic/Battery/battery_Green.jpg", - "BATTERY_RED_CHARGE_IMG": "res/basic/Battery/battery_Red_Charge.jpg", - "BATTERY_RED_IMG": "res/basic/Battery/battery_Red.jpg", - "BATTERY_YELLOW_IMG": "res/basic/Battery/battery_Yellow.jpg", - "SERVER_BLUE_IMG": "res/basic/Server/server_blue.jpg", - "SERVER_EMPTY_IMG": "res/basic/Server/server_empty.jpg", - "SERVER_ERROR_IMG": "res/basic/Server/server_error.jpg", - "SERVER_GREEN_IMG": "res/basic/Server/Server_Green.jpg", - "WIFI_DISCONNECTED_IMG": "res/basic/WiFi/wifi_disconnected.jpg", - "WIFI_EMPTY_IMG": "res/basic/WiFi/wifi_empty.jpg", - "WIFI_GOOD_IMG": "res/basic/WiFi/wifi_good.jpg", - "WIFI_MID_IMG": "res/basic/WiFi/wifi_mid.jpg", - "WIFI_WORSE_IMG": "res/basic/WiFi/wifi_worse.jpg", - "APPLIST_SELECTED_IMG": "res/basic/appList_selected.jpg", - "APPLIST_UNSELECTED_IMG": "res/basic/appList_unselected.jpg", - "APPLIST_IMG": "res/basic/applist.jpg", - "APPLIST_LEFT_IMG": "res/basic/applistLeft.jpg", - "APPLIST_RIGHT_IMG": "res/basic/applistRight.jpg", - "APPRUN_SELECTED_IMG": "res/basic/appRun_selected.jpg", - "APPRUN_UNSELECTED_IMG": "res/basic/appRun_unselected.jpg", - "BAR1_IMG": "res/basic/bar1.jpg", - "BAR2_IMG": "res/basic/bar2.jpg", - "BAR3_IMG": "res/basic/bar3.jpg", - "BAR4_IMG": "res/basic/bar4.jpg", - "BAR5_IMG": "res/basic/bar5.jpg", - "BOOT_NO_IMG": "res/basic/boot_No.jpg", - "BOOT_YES_IMG": "res/basic/boot_Yes.jpg", - "DEVELOP_SELECTED_IMG": "res/basic/develop_selected.jpg", - "DEVELOP_UNSELECTED_IMG": "res/basic/develop_unselected.jpg", - "DEVELOP_PRIVATE_IMG": "res/basic/developPrivate.jpg", - "DEVELOP_PUBLIC_IMG": "res/basic/developPublic.jpg", - "EZDATA_SELECTED_IMG": "res/basic/ezdata_selected.jpg", - "EZDATA_UNSELECTED_IMG": "res/basic/ezdata_unselected.jpg", - "LOGO_IMG": "res/basic/logo.jpg", - "RUN_IMG": "res/basic/run.jpg", - "SCREEN25_IMG": "res/basic/screen25.jpg", - "SCREEN50_IMG": "res/basic/screen50.jpg", - "SCREEN75_IMG": "res/basic/screen75.jpg", - "SCREEN100_IMG": "res/basic/screen100.jpg", - "SETTING_SELECTED_IMG": "res/basic/setting_selected.jpg", - "SETTING_UNSELECTED_IMG": "res/basic/setting_unselected.jpg", - "SETTING_SELECT_IMG": "res/basic/settingSelect.jpg", - "SETTING_UNSELECT_IMG": "res/basic/settingUnselect.jpg", - "SETTING_WIFI_IMG": "res/basic/SettingWifi.jpg", - "AVATAR_IMG": "res/img/avatar.jpg", - "USER_AVATAR_PATH": "res/img/", + "BATTERY_BLACK_CHARGE_IMG": "/flash/res/basic/Battery/battery_Black_Charge.jpg", + "BATTERY_BLACK_IMG": "/flash/res/basic/Battery/battery_Black.jpg", + "BATTERY_GRAY_IMG": "/flash/res/basic/Battery/battery_Gray.jpg", + "BATTERY_GREEN_CHARGE_IMG": "/flash/res/basic/Battery/battery_Green_Charge.jpg", + "BATTERY_GREEN_IMG": "/flash/res/basic/Battery/battery_Green.jpg", + "BATTERY_RED_CHARGE_IMG": "/flash/res/basic/Battery/battery_Red_Charge.jpg", + "BATTERY_RED_IMG": "/flash/res/basic/Battery/battery_Red.jpg", + "BATTERY_YELLOW_IMG": "/flash/res/basic/Battery/battery_Yellow.jpg", + "SERVER_BLUE_IMG": "/flash/res/basic/Server/server_blue.jpg", + "SERVER_EMPTY_IMG": "/flash/res/basic/Server/server_empty.jpg", + "SERVER_ERROR_IMG": "/flash/res/basic/Server/server_error.jpg", + "SERVER_GREEN_IMG": "/flash/res/basic/Server/Server_Green.jpg", + "WIFI_DISCONNECTED_IMG": "/flash/res/basic/WiFi/wifi_disconnected.jpg", + "WIFI_EMPTY_IMG": "/flash/res/basic/WiFi/wifi_empty.jpg", + "WIFI_GOOD_IMG": "/flash/res/basic/WiFi/wifi_good.jpg", + "WIFI_MID_IMG": "/flash/res/basic/WiFi/wifi_mid.jpg", + "WIFI_WORSE_IMG": "/flash/res/basic/WiFi/wifi_worse.jpg", + "APPLIST_SELECTED_IMG": "/flash/res/basic/appList_selected.jpg", + "APPLIST_UNSELECTED_IMG": "/flash/res/basic/appList_unselected.jpg", + "APPLIST_IMG": "/flash/res/basic/applist.jpg", + "APPLIST_LEFT_IMG": "/flash/res/basic/applistLeft.jpg", + "APPLIST_RIGHT_IMG": "/flash/res/basic/applistRight.jpg", + "APPRUN_SELECTED_IMG": "/flash/res/basic/appRun_selected.jpg", + "APPRUN_UNSELECTED_IMG": "/flash/res/basic/appRun_unselected.jpg", + "BAR1_IMG": "/flash/res/basic/bar1.jpg", + "BAR2_IMG": "/flash/res/basic/bar2.jpg", + "BAR3_IMG": "/flash/res/basic/bar3.jpg", + "BAR4_IMG": "/flash/res/basic/bar4.jpg", + "BAR5_IMG": "/flash/res/basic/bar5.jpg", + "BOOT_NO_IMG": "/flash/res/basic/boot_No.jpg", + "BOOT_YES_IMG": "/flash/res/basic/boot_Yes.jpg", + "DEVELOP_SELECTED_IMG": "/flash/res/basic/develop_selected.jpg", + "DEVELOP_UNSELECTED_IMG": "/flash/res/basic/develop_unselected.jpg", + "DEVELOP_PRIVATE_IMG": "/flash/res/basic/developPrivate.jpg", + "DEVELOP_PUBLIC_IMG": "/flash/res/basic/developPublic.jpg", + "EZDATA_SELECTED_IMG": "/flash/res/basic/ezdata_selected.jpg", + "EZDATA_UNSELECTED_IMG": "/flash/res/basic/ezdata_unselected.jpg", + "LOGO_IMG": "/flash/res/basic/logo.jpg", + "RUN_IMG": "/flash/res/basic/run.jpg", + "SCREEN25_IMG": "/flash/res/basic/screen25.jpg", + "SCREEN50_IMG": "/flash/res/basic/screen50.jpg", + "SCREEN75_IMG": "/flash/res/basic/screen75.jpg", + "SCREEN100_IMG": "/flash/res/basic/screen100.jpg", + "SETTING_SELECTED_IMG": "/flash/res/basic/setting_selected.jpg", + "SETTING_UNSELECTED_IMG": "/flash/res/basic/setting_unselected.jpg", + "SETTING_SELECT_IMG": "/flash/res/basic/settingSelect.jpg", + "SETTING_UNSELECT_IMG": "/flash/res/basic/settingUnselect.jpg", + "SETTING_WIFI_IMG": "/flash/res/basic/SettingWifi.jpg", + "AVATAR_IMG": "/flash/res/img/avatar.jpg", + "USER_AVATAR_PATH": "/flash/res/img/", } else: _attrs = { diff --git a/m5stack/modules/startup/stickc.py b/m5stack/modules/startup/stickc.py index bb2089fb..03a60d71 100644 --- a/m5stack/modules/startup/stickc.py +++ b/m5stack/modules/startup/stickc.py @@ -26,41 +26,41 @@ DEBUG = True # launcher -LAUNCHER_IMG = "res/stickc/launcher.jpeg" +LAUNCHER_IMG = "/flash/res/stickc/launcher.jpeg" # menu -MENU_DEV_IMG = "res/stickc/menu_dev.jpeg" -MENU_APPRUN_IMG = "res/stickc/menu_apprun.jpeg" -MENU_APPLIST_IMG = "res/stickc/menu_applist.jpeg" +MENU_DEV_IMG = "/flash/res/stickc/menu_dev.jpeg" +MENU_APPRUN_IMG = "/flash/res/stickc/menu_apprun.jpeg" +MENU_APPLIST_IMG = "/flash/res/stickc/menu_applist.jpeg" # app.run -APPRUN_IMG = "res/stickc/apprun.jpeg" -RUN_ONCE_SELECT_IMG = "res/stickc/run_once_select.jpeg" -RUN_ONCE_UNSELECT_IMG = "res/stickc/run_once_unselect.jpeg" -RUN_ALWAYS_SELECT_IMG = "res/stickc/run_always_select.jpeg" -RUN_ALWAYS_UNSELECT_IMG = "res/stickc/run_always_unselect.jpeg" +APPRUN_IMG = "/flash/res/stickc/apprun.jpeg" +RUN_ONCE_SELECT_IMG = "/flash/res/stickc/run_once_select.jpeg" +RUN_ONCE_UNSELECT_IMG = "/flash/res/stickc/run_once_unselect.jpeg" +RUN_ALWAYS_SELECT_IMG = "/flash/res/stickc/run_always_select.jpeg" +RUN_ALWAYS_UNSELECT_IMG = "/flash/res/stickc/run_always_unselect.jpeg" # app.list -APPLIST_IMG = "res/stickc/applist.jpeg" +APPLIST_IMG = "/flash/res/stickc/applist.jpeg" # app.cloud -CLOUD_WAIT_IMG = "res/stickc/wifi_wait.jpeg" -CLOUD_CONNECTED_IMG = "res/stickc/wifi_connect.jpeg" -CLOUD_DISCONNECTED_IMG = "res/stickc/wifi_disconnected.jpeg" -CLOUD_ERROR_IMG = "res/stickc/wifi_error.jpeg" -CLOUD_READY_IMG = "res/stickc/wifi_ready.jpeg" +CLOUD_WAIT_IMG = "/flash/res/stickc/wifi_wait.jpeg" +CLOUD_CONNECTED_IMG = "/flash/res/stickc/wifi_connect.jpeg" +CLOUD_DISCONNECTED_IMG = "/flash/res/stickc/wifi_disconnected.jpeg" +CLOUD_ERROR_IMG = "/flash/res/stickc/wifi_error.jpeg" +CLOUD_READY_IMG = "/flash/res/stickc/wifi_ready.jpeg" # statusbar -BAT_IMG_PATH = "res/stickc/battery/" +BAT_IMG_PATH = "/flash/res/stickc/battery/" # develop -AVATAR_IMG = "res/img/avatar.jpeg" -DEVELOP_PRIVATE_IMG = "res/stickc/develop_private.jpeg" -DEVELOP_PUBLIC_IMG = "res/stickc/develop_public.jpeg" +AVATAR_IMG = "/flash/res/img/avatar.jpeg" +DEVELOP_PRIVATE_IMG = "/flash/res/stickc/develop_private.jpeg" +DEVELOP_PUBLIC_IMG = "/flash/res/stickc/develop_public.jpeg" # font -MontserratMedium10_VLW = "res/stickc/Montserrat-Medium-10.vlw" -MontserratMedium12_VLW = "res/stickc/Montserrat-Medium-12.vlw" +MontserratMedium10_VLW = "/flash/res/stickc/Montserrat-Medium-10.vlw" +MontserratMedium12_VLW = "/flash/res/stickc/Montserrat-Medium-12.vlw" class AppBase: @@ -254,7 +254,7 @@ def _get_avatar(): if len(infos[4]) == 0: return AVATAR_IMG else: - return "res/img/" + str(infos[4]).split("/")[-1] + return "/flash/res/img/" + str(infos[4]).split("/")[-1] else: return AVATAR_IMG diff --git a/m5stack/modules/startup/stickcplus.py b/m5stack/modules/startup/stickcplus.py index 9497b6bc..ca7c3341 100644 --- a/m5stack/modules/startup/stickcplus.py +++ b/m5stack/modules/startup/stickcplus.py @@ -21,23 +21,23 @@ DEBUG = True if M5.getBoard() == M5.BOARD.M5StickCPlus: - APPLIST_IMG = "res/stickcplus/APPLIST.jpg" - BK_IMG = "res/stickcplus/bk.jpg" - CLOUD1_IMG = "res/stickcplus/cloud1.jpg" - CLOUD2_IMG = "res/stickcplus/cloud2.jpg" - CLOUD3_IMG = "res/stickcplus/cloud3.jpg" - CLOUD4_IMG = "res/stickcplus/cloud4.jpg" - CLOUD5_IMG = "res/stickcplus/cloud5.jpg" - CLOUD6_IMG = "res/stickcplus/cloud6.jpg" - CLOUD7_IMG = "res/stickcplus/cloud7.jpg" - CLOUD8_IMG = "res/stickcplus/cloud8.jpg" - CLOUD9_IMG = "res/stickcplus/cloud9.jpg" - CLOUD10_IMG = "res/stickcplus/cloud10.jpg" - MODE1_IMG = "res/stickcplus/mode1.jpg" - MODE2_IMG = "res/stickcplus/mode2.jpg" - MODE3_IMG = "res/stickcplus/mode3.jpg" - MODE4_IMG = "res/stickcplus/mode4.jpg" - USB_IMG = "res/stickcplus/usb.jpg" + APPLIST_IMG = "/flash/res/stickcplus/APPLIST.jpg" + BK_IMG = "/flash/res/stickcplus/bk.jpg" + CLOUD1_IMG = "/flash/res/stickcplus/cloud1.jpg" + CLOUD2_IMG = "/flash/res/stickcplus/cloud2.jpg" + CLOUD3_IMG = "/flash/res/stickcplus/cloud3.jpg" + CLOUD4_IMG = "/flash/res/stickcplus/cloud4.jpg" + CLOUD5_IMG = "/flash/res/stickcplus/cloud5.jpg" + CLOUD6_IMG = "/flash/res/stickcplus/cloud6.jpg" + CLOUD7_IMG = "/flash/res/stickcplus/cloud7.jpg" + CLOUD8_IMG = "/flash/res/stickcplus/cloud8.jpg" + CLOUD9_IMG = "/flash/res/stickcplus/cloud9.jpg" + CLOUD10_IMG = "/flash/res/stickcplus/cloud10.jpg" + MODE1_IMG = "/flash/res/stickcplus/mode1.jpg" + MODE2_IMG = "/flash/res/stickcplus/mode2.jpg" + MODE3_IMG = "/flash/res/stickcplus/mode3.jpg" + MODE4_IMG = "/flash/res/stickcplus/mode4.jpg" + USB_IMG = "/flash/res/stickcplus/usb.jpg" elif M5.getBoard() == M5.BOARD.M5StickCPlus2: APPLIST_IMG = "/system/stickcplus2/APPLIST.png" BK_IMG = "/system/stickcplus2/bk.png" From d552ea0e6c8587aabfc9c56b0353838dcc0bec18 Mon Sep 17 00:00:00 2001 From: lbuque <1102390310@qq.com> Date: Wed, 20 Aug 2025 16:26:34 +0800 Subject: [PATCH 205/322] libs: Add warnings library. Signed-off-by: lbuque <1102390310@qq.com> --- m5stack/libs/manifest.py | 1 + m5stack/libs/warnings.py | 7 +++++++ 2 files changed, 8 insertions(+) create mode 100644 m5stack/libs/warnings.py diff --git a/m5stack/libs/manifest.py b/m5stack/libs/manifest.py index db91e3cb..78fa6450 100644 --- a/m5stack/libs/manifest.py +++ b/m5stack/libs/manifest.py @@ -27,3 +27,4 @@ module("m5camera.py") module("pid.py") include("iot_devices/manifest.py") +module("warnings.py") diff --git a/m5stack/libs/warnings.py b/m5stack/libs/warnings.py new file mode 100644 index 00000000..30959054 --- /dev/null +++ b/m5stack/libs/warnings.py @@ -0,0 +1,7 @@ +# SPDX-FileCopyrightText: Copyright (c) 2013-2014 micropython-lib contributors +# +# SPDX-License-Identifier: MIT + + +def warn(msg, cat=None, stacklevel=1): + print("%s: %s" % ("Warning" if cat is None else cat.__name__, msg)) From bd099e10a47077d34eb8a4ab8c37d12d097220fe Mon Sep 17 00:00:00 2001 From: "tinyu.zhao@gmail.com" Date: Thu, 21 Aug 2025 14:24:11 +0800 Subject: [PATCH 206/322] .gitlab-ci.yml: Fix GitLab CI build documentation error. Signed-off-by: tinyu.zhao@gmail.com --- .gitlab-ci.yml | 17 ++++++++++++----- tools/ci.sh | 23 +++++++++++++++++------ 2 files changed, 29 insertions(+), 11 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 7b980af7..7083d107 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,3 +1,7 @@ +workflow: + auto_cancel: + on_new_commit: conservative # the default behavior + variables: DEBIAN_FRONTEND: noninteractive ESP_IDF_SRC_DIR: $CI_PROJECT_DIR/esp-idf @@ -5,7 +9,7 @@ variables: cache: key: "$CI_PROJECT_ID-esp-idf-v542" paths: - - $ESP_IDF_SRC_DIR + - ${ESP_IDF_SRC_DIR} policy: pull-push when: on_success @@ -18,6 +22,7 @@ stages: code-format: stage: code_format + interruptible: true before_script: - export PATH="$HOME/.local/bin:$PATH" script: @@ -26,15 +31,16 @@ code-format: - source tools/ci.sh && ci_code_formatting_run - git diff --exit-code tags: - - uiflow-firmware + - uiflow-docs build-job: stage: build + interruptible: true script: - sudo apt-get update -qy - - sudo apt-get install git wget flex bison gperf quilt python3 python3-pip python3-venv cmake ninja-build ccache libffi-dev libssl-dev dfu-util libusb-1.0-0 -y - - source tools/ci.sh && ci_esp32_idf541_setup + - sudo apt-get install git wget flex bison gperf quilt python3 python3-pip python3-venv cmake ninja-build ccache libffi-dev libssl-dev dfu-util libusb-1.0-0 locales -y + - source tools/ci.sh && ci_esp32_idf542_setup - source tools/ci.sh && ci_esp32_nightly_build artifacts: paths: @@ -46,6 +52,7 @@ build-job: build-docs: stage: docs + interruptible: true script: - echo "Building the documentation..." - cd docs @@ -59,7 +66,7 @@ build-docs: paths: - docs/build/ tags: - - uiflow-firmware + - uiflow-docs release_job: diff --git a/tools/ci.sh b/tools/ci.sh index 0d7e8a34..b7840687 100644 --- a/tools/ci.sh +++ b/tools/ci.sh @@ -22,6 +22,10 @@ function uncrustify_setup { wget https://github.com/uncrustify/uncrustify/archive/refs/tags/uncrustify-0.72.0.tar.gz tar -xvf uncrustify-0.72.0.tar.gz cd uncrustify-uncrustify-0.72.0 + chmod +x ./scripts/make_option_enum.py + sed -i -e '1s@.*@cmake_minimum_required(VERSION 3.5...4.1)@' \ + -e '20s@.*@find_package(Python3 COMPONENTS Interpreter REQUIRED)@' ./CMakeLists.txt + sed -i '1s@.*@cmake_minimum_required(VERSION 3.5...4.1)@' ./tests/CMakeLists.txt mkdir build cd build cmake -DCMAKE_BUILD_TYPE=Release .. @@ -159,26 +163,26 @@ function ci_esp32_idf522_setup { rm -rf esp-idf fi fi - + echo "Cloning esp-idf v5.2.2..." git clone --depth 1 --branch v5.2.2 https://github.com/espressif/esp-idf.git - git -C esp-idf submodule update --init \ + git -C esp-idf submodule update --init --depth 1 \ components/bt/host/nimble/nimble \ components/esp_wifi \ components/esptool_py/esptool \ components/lwip/lwip \ components/mbedtls/mbedtls if [ -d esp-idf/components/bt/controller/esp32 ]; then - git -C esp-idf submodule update --init \ + git -C esp-idf submodule update --init --depth 1 \ components/bt/controller/lib_esp32 \ components/bt/controller/lib_esp32c3_family else - git -C esp-idf submodule update --init \ + git -C esp-idf submodule update --init --depth 1 \ components/bt/controller/lib fi ./esp-idf/install.sh } -function ci_esp32_idf541_setup { +function ci_esp32_idf542_setup { if [ -d esp-idf ]; then echo "esp-idf is already cloned." if [ "$(git -C esp-idf describe --tags)" == "v5.4.2" ]; then @@ -191,7 +195,7 @@ function ci_esp32_idf541_setup { fi git clone --depth 1 --branch v5.4.2 https://github.com/espressif/esp-idf.git - git -C esp-idf submodule update --init + git -C esp-idf submodule update --init --depth 1 ./esp-idf/install.sh } @@ -312,6 +316,13 @@ function ci_hat_build { function ci_esp32_nightly_build { + REQUIRED_VERSION="3.28.3" + CURRENT_VERSION=$(cmake --version | head -n1 | awk '{print $3}') + + if [ "$(printf '%s\n' "$REQUIRED_VERSION" "$CURRENT_VERSION" | sort -V | head -n1)" != "$REQUIRED_VERSION" ]; then + echo "❌ CMake version $CURRENT_VERSION is too old! Require >= $REQUIRED_VERSION" + exit 1 + fi source esp-idf/export.sh pip install future make ${MAKEOPTS} -C m5stack unpatch From cd4bdc31e2cfb19e5ad74fcc2d91d0d777758b90 Mon Sep 17 00:00:00 2001 From: "tinyu.zhao@gmail.com" Date: Thu, 21 Aug 2025 10:41:29 +0800 Subject: [PATCH 207/322] lib/unit: Add Unit INA226 support and docs. Signed-off-by: tinyu.zhao@gmail.com --- docs/en/refs/unit.ina226.ref | 22 ++ docs/en/units/ina226.rst | 65 +++++ docs/en/units/index.rst | 1 + .../locales/zh_CN/LC_MESSAGES/units/ina226.po | 256 ++++++++++++++++++ .../unit/ina226/ina226_core2_example.m5f2 | 1 + examples/unit/ina226/ina226_core2_example.py | 104 +++++++ m5stack/libs/driver/ina226.py | 144 ++++++++-- m5stack/libs/unit/__init__.py | 1 + m5stack/libs/unit/ina226.py | 44 +++ m5stack/libs/unit/manifest.py | 1 + 10 files changed, 610 insertions(+), 29 deletions(-) create mode 100644 docs/en/refs/unit.ina226.ref create mode 100644 docs/en/units/ina226.rst create mode 100644 docs/locales/zh_CN/LC_MESSAGES/units/ina226.po create mode 100644 examples/unit/ina226/ina226_core2_example.m5f2 create mode 100644 examples/unit/ina226/ina226_core2_example.py create mode 100644 m5stack/libs/unit/ina226.py diff --git a/docs/en/refs/unit.ina226.ref b/docs/en/refs/unit.ina226.ref new file mode 100644 index 00000000..f874d24f --- /dev/null +++ b/docs/en/refs/unit.ina226.ref @@ -0,0 +1,22 @@ +.. |INA226| image:: https://static-cdn.m5stack.com/resource/docs/products/unit/Unit%20INA226/4.webp + :target: https://docs.m5stack.com/en/unit/Unit%20INA226 + :height: 200px + :width: 200px + +.. |init.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/ina226/init.png + +.. |read_shunt_voltage.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/ina226/read_shunt_voltage.png +.. |read_bus_voltage.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/ina226/read_bus_voltage.png +.. |read_current.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/ina226/read_current.png +.. |read_power.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/ina226/read_power.png + +.. |example.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/ina226/example.png + +.. |ina226_core2_example.m5f2| raw:: html + + + ina226_core2_example.m5f2 + diff --git a/docs/en/units/ina226.rst b/docs/en/units/ina226.rst new file mode 100644 index 00000000..eeeae489 --- /dev/null +++ b/docs/en/units/ina226.rst @@ -0,0 +1,65 @@ +INA226 Unit +============ + +.. sku: U200 / U200-1A + +.. include:: ../refs/unit.ina226.ref + +This is the driver library of INA226 Unit, which is used to obtain current and power data from the INA226 sensor. + +Support the following products: + + |INA226| + + +UiFlow2 Example +--------------- + +get value +^^^^^^^^^^^^^^^ + +Open the |ina226_core2_example.m5f2| project in UiFlow2. + +This example gets the current, voltage, and power values from the INA226 Unit and displays them on the screen. + +UiFlow2 Code Block: + + |example.png| + +Example output: + + None + +MicroPython Example +------------------- + +get value +^^^^^^^^^^^^^^^ + +This example gets the current, voltage, and power values from the INA226 Unit and displays them on the screen. + +MicroPython Code Block: + + .. literalinclude:: ../../../examples/unit/ina226/ina226_core2_example.py + :language: python + :linenos: + +Example output: + + None + + +**API** +------- + +INA226Unit +^^^^^^^^^^ + +.. autoclass:: unit.ina226.INA226Unit + :members: + +INA226 +^^^^^^^ + +.. autoclass:: driver.ina226.INA226 + :members: diff --git a/docs/en/units/index.rst b/docs/en/units/index.rst index 827306df..fe24d9e0 100644 --- a/docs/en/units/index.rst +++ b/docs/en/units/index.rst @@ -56,6 +56,7 @@ Unit id.rst imu.rst imupro.rst + ina226.rst ir.rst joystick.rst joystick2.rst diff --git a/docs/locales/zh_CN/LC_MESSAGES/units/ina226.po b/docs/locales/zh_CN/LC_MESSAGES/units/ina226.po new file mode 100644 index 00000000..fb32a648 --- /dev/null +++ b/docs/locales/zh_CN/LC_MESSAGES/units/ina226.po @@ -0,0 +1,256 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2016 - 2025 M5Stack Technology Co., Ltd +# This file is distributed under the same license as the UIFlow2 Programming +# Guide package. +# FIRST AUTHOR , 2025. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: UIFlow2 Programming Guide \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-08-11 14:46+0800\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: zh_CN\n" +"Language-Team: zh_CN \n" +"Plural-Forms: nplurals=1; plural=0;\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.16.0\n" + +#: ../../en/units/ina226.rst:2 309e555f5e0e43c6ad018adbd741a106 +msgid "INA226 Unit" +msgstr "" + +#: ../../en/units/ina226.rst:8 441f48534fdd48c687a5d4b0ff520351 +msgid "" +"This is the driver library of INA226 Unit, which is used " +"to obtain current and power data from the INA226 sensor." +msgstr "这是 INA226 单元的驱动库,用于从 INA226 传感器获取电流与功率数据。" + +#: ../../en/units/ina226.rst:10 28f98c0c1bdd4980913345e62ed35161 +msgid "Support the following products:" +msgstr "支持以下产品:" + +#: ../../en/units/ina226.rst:12 a278cadedef14b689a7fcc99c3df0d02 +msgid "|INA226|" +msgstr "" + +#: ../../en/refs/unit.ina226.ref ../../en/units/ina226.rst:62 +#: 3f55be23a385402f9418cce6c31f1a20 40e273b9843a4f8ba323907a21841fb4 +msgid "INA226" +msgstr "" + +#: ../../en/units/ina226.rst:16 fefd38959ab94d7285b5b7aabd613662 +msgid "UiFlow2 Example" +msgstr "UiFlow2 示例" + +#: ../../en/units/ina226.rst:19 ../../en/units/ina226.rst:37 +#: 2f3f99063e5a47af8e8d6e975a2a79ff 9e69a492f01f48d696607cf0e2ef2ee2 +msgid "get value" +msgstr "获取数值" + +#: ../../en/units/ina226.rst:21 db3cfe3c82e248eb976a259af7ea6685 +msgid "Open the |ina226_core2_example.m5f2| project in UiFlow2." +msgstr "在 UiFlow2 中打开 |ina226_core2_example.m5f2| 项目。" + +#: ../../en/units/ina226.rst:23 ../../en/units/ina226.rst:39 +#: 07c953e04ff44549b39195f121b64e90 a2df234e337245bb8468698418af7276 +msgid "" +"This example gets the current, voltage, and power values from the INA226 " +"Unit and displays them on the screen." +msgstr "该示例从 INA226 单元读取电流、电压和功率值并显示在屏幕上。" + +#: ../../en/units/ina226.rst:25 1dbbce64cab64cb4af4c519e82e4cf87 +#: 2eaccd5ab2fe45a09dfb1428517f0250 5c34fd58e62747c584383850378b9be4 +#: 88266ce0f2134fc092f3805214e64e4b b9088dcede4244a7b8a17a25b1ee46fb +#: ba60a36b1f7c4d35a6fb36a31960cdb6 driver.ina226.INA226:7 +#: driver.ina226.INA226.read_bus_voltage:6 driver.ina226.INA226.read_current:6 +#: driver.ina226.INA226.read_power:6 driver.ina226.INA226.read_shunt_voltage:6 +#: fc485d6f0dbb487ab85eef26901a8d35 of unit.ina226.INA226Unit:7 +msgid "UiFlow2 Code Block:" +msgstr "UiFlow2 代码块:" + +#: ../../en/units/ina226.rst:27 +msgid "|example.png|" +msgstr "" + +#: ../../en/refs/unit.ina226.ref:13 58703b60996c4f6e803134e038cb3e4a +msgid "example.png" +msgstr "" + +#: ../../en/units/ina226.rst:29 ../../en/units/ina226.rst:47 +msgid "Example output:" +msgstr "示例输出:" + +#: ../../en/units/ina226.rst:31 ../../en/units/ina226.rst:49 +#: 5d411285103746879e67eac6ea748260 eedb4da36c4c4d3f81449a79460b8fb5 +msgid "None" +msgstr "" + +#: ../../en/units/ina226.rst:34 385e4c482ee748bba376c0e020eecb76 +msgid "MicroPython Example" +msgstr "MicroPython 示例" + +#: ../../en/units/ina226.rst:41 3c2cd89eceb146bda9ddfd713f893aae +#: 4174176606434c7784e799a57499c01b 4e3b93ec92ee405ca919a56fb61222eb +#: 625c6fad5a954b6aba4b8f059021baf9 9a88d3b331cd4157bac1d351865d42c7 +#: c6486c07c92b45a4bd64544fac0e092e driver.ina226.INA226:11 +#: driver.ina226.INA226.calibrate:6 driver.ina226.INA226.read_bus_voltage:10 +#: driver.ina226.INA226.read_current:10 driver.ina226.INA226.read_power:10 +#: driver.ina226.INA226.read_shunt_voltage:10 f825a485f5a844d4b26fc1d6a8d1ddf8 +#: f9f2da003eec4d8da2cf0cd0af53b79d of unit.ina226.INA226Unit:11 +msgid "MicroPython Code Block:" +msgstr "MicroPython 代码块:" + +#: ../../en/units/ina226.rst:53 712e6414b4a94c858e480ec132e69961 +msgid "**API**" +msgstr "" + +#: ../../en/units/ina226.rst:56 d86f5601afbb49e2b8cc47aadf26b4ed +msgid "INA226Unit" +msgstr "" + +#: 28c3ee6a71044f4ba9a1454dbeed58b3 of unit.ina226.INA226Unit:1 +msgid "Bases: :py:class:`~driver.ina226.INA226`" +msgstr "" + +#: db3c95f1bb564e1089fed63dfdd169da of unit.ina226.INA226Unit:1 +msgid "Create an INA226Unit object." +msgstr "创建 INA226Unit 对象。" + +#: ../../en/units/ina226.rst 408124a742a34effb69f50c1bbf21e63 +#: c915ff596cdf4ae78741a3e3607d71f7 cb6f9a076caa4fde9f91005681914a00 +#: driver.ina226.INA226.calibrate of unit.ina226.INA226Unit +msgid "Parameters" +msgstr "参数" + +#: 52fbd03c38114fa9999e87aa9e8f3126 of unit.ina226.INA226Unit:3 +msgid "The I2C bus the Accel Unit is connected to." +msgstr "INA226 单元连接的 I2C 总线。" + +#: ba2d0a5f2cf04fc39832ce111969704d of unit.ina226.INA226Unit:4 +msgid "The I2C address of the device. Default is 0x41." +msgstr "设备的 I2C 地址,默认 0x41。" + +#: e7e21cab9a034839931760eec5577b4a of unit.ina226.INA226Unit:5 +msgid "The type of INA226. Default is \"10A\". Options are \"1A\" and \"10A\"." +msgstr "INA226 的规格类型,默认 \"10A\",可选 \"1A\" 或 \"10A\"。" + +#: driver.ina226.INA226:1 e7dbb5585a094365a6ee5d587740a304 of +msgid "Bases: :py:class:`object`" +msgstr "" + +#: driver.ina226.INA226:1 e94afddef1714697a24c8ed006cf742e of +msgid "Create an INA226 object." +msgstr "创建 INA226 对象。" + +#: d46a8cd285ee46a498f5b8145daf5e54 driver.ina226.INA226:3 of +msgid "The I2C bus the INA226 is connected to." +msgstr "INA226 所连接的 I2C 总线。" + +#: 5da9b3dcd783404c9d574c60e79f93cf driver.ina226.INA226:4 of +msgid "The I2C address of the device." +msgstr "设备的 I2C 地址。" + +#: driver.ina226.INA226:5 f404624bcb004bc49033d097c29cbc9e of +msgid "The value of the shunt resistor in ohms. Default is 0.2." +msgstr "分流电阻值(欧姆),默认 0.2。" + +#: 7efd1dbb024444a48ad2b3184f496da3 driver.ina226.INA226.calibrate:1 of +msgid "Calibrate the INA226." +msgstr "对 INA226 进行校准。" + +#: 8bb9550806dc4350897a0138e35333a1 driver.ina226.INA226.calibrate:3 of +msgid "The maximum expected current in Amperes." +msgstr "预期的最大电流(安培)。" + +#: 17eb1274907044b4b323feead89cc16d driver.ina226.INA226.calibrate:4 of +msgid "" +"The calibration value to use. If None, it will be calculated based on the" +" maximum expected current and the shunt resistor value." +msgstr "要使用的校准值;若为 None,则根据最大预期电流和分流电阻自动计算。" + +#: c7fbee1fb0354a48ba2134a0baa71817 driver.ina226.INA226.read_shunt_voltage:1 +#: of +msgid "Read the shunt voltage in Volts." +msgstr "读取分流电压(伏特)。" + +#: 862d74165b264fe3a68695703ef82471 d5d2ddc22f784ce0bd4471e6acf78d76 +#: driver.ina226.INA226.read_bus_voltage driver.ina226.INA226.read_current +#: driver.ina226.INA226.read_power driver.ina226.INA226.read_shunt_voltage +#: f24bb361c58b471bacca2a4777f87acb f5e8834d181447adb07652d501311875 of +msgid "Returns" +msgstr "返回值" + +#: b8bbed26ecc64f2f976497663844a08e driver.ina226.INA226.read_shunt_voltage:3 +#: of +msgid "The shunt voltage in Volts." +msgstr "分流电压(伏特)。" + +#: 32a8767c184545e9babda2662e830bce a159e961d1c94cfe9654e050adfe44b1 +#: aacbdda54aed456d8cd7b23810877853 c3c5bdb659224661af68d59a2f5b674d +#: driver.ina226.INA226.read_bus_voltage driver.ina226.INA226.read_current +#: driver.ina226.INA226.read_power driver.ina226.INA226.read_shunt_voltage of +msgid "Return type" +msgstr "" + +#: db6a415a030a433fa007bddccda58f34 driver.ina226.INA226.read_shunt_voltage:8 +#: of +msgid "|read_shunt_voltage.png|" +msgstr "" + +#: ../../en/refs/unit.ina226.ref:8 527fc0188a79408a8f81350ec555cc67 +msgid "read_shunt_voltage.png" +msgstr "" + +#: ac5031c03e2a4d45be00cc6aff930d2e driver.ina226.INA226.read_bus_voltage:1 +msgid "Read the bus voltage in Volts." +msgstr "读取总线电压(伏特)。" + +#: 61d04711378745e2b0407949f5667b5b driver.ina226.INA226.read_bus_voltage:3 of +msgid "The bus voltage in Volts." +msgstr "总线电压(伏特)。" + +#: 68e90b711ff9425abf6ddbfaf30b5e50 driver.ina226.INA226.read_bus_voltage:8 of +msgid "|read_bus_voltage.png|" +msgstr "" + +#: ../../en/refs/unit.ina226.ref:9 049210999b604381b6b921701dc4c400 +msgid "read_bus_voltage.png" +msgstr "" + +#: driver.ina226.INA226.read_current:1 +msgid "Read the current in Amperes." +msgstr "读取电流(安培)。" + +#: 3d1d513659c946bb9f815a1702646594 driver.ina226.INA226.read_current:3 of +msgid "The current in Amperes." +msgstr "电流(安培)。" + +#: 55298385f12845d99a3d574e992dbf12 driver.ina226.INA226.read_current:8 of +msgid "|read_current.png|" +msgstr "" + +#: ../../en/refs/unit.ina226.ref:10 80d1529cf24d4360a99aad53db7b8e54 +msgid "read_current.png" +msgstr "" + +#: d7f07cb4b2344a989bc37974e238e066 driver.ina226.INA226.read_power:1 of +msgid "Read the power in Watts." +msgstr "读取功率(瓦)。" + +#: 3eed67a019574fe98787d98131d8261b driver.ina226.INA226.read_power:3 of +msgid "The power in Watts." +msgstr "功率(瓦)。" + +#: 2313238bcafa424c9354e35e858ba5e8 driver.ina226.INA226.read_power:8 of +msgid "|read_power.png|" +msgstr "" + +#: ../../en/refs/unit.ina226.ref:11 d030faceb19f4ea6a18d1e2c097d0793 +msgid "read_power.png" +msgstr "" + diff --git a/examples/unit/ina226/ina226_core2_example.m5f2 b/examples/unit/ina226/ina226_core2_example.m5f2 new file mode 100644 index 00000000..b1c0bb20 --- /dev/null +++ b/examples/unit/ina226/ina226_core2_example.m5f2 @@ -0,0 +1 @@ +{"version":"V2.0","versionNumber":"V2.3.3","type":"core2","components":[{"name":"page0","type":"lvgl_page","layer":0,"screenId":"builtin","screenName":"","id":"o%kIIonEVlENSZAX","createTime":1754883745053,"backgroundColor":"#ffffff","isLVGL":true,"isSelected":true},{"name":"label0","type":"lvgl_label","layer":1,"screenId":"builtin","screenName":"","id":"cia6h3x$05EUDCR!","createTime":1754884715619,"x":0,"y":56,"color":"#000000","backgroundColor":"#ffffff","bg_opacity":0,"text":"Bus Voltage:","font":"lv.font_montserrat_16","pageId":"o%kIIonEVlENSZAX","isLVGL":true,"isSelected":false},{"name":"label1","type":"lvgl_label","layer":2,"screenId":"builtin","screenName":"","id":"kodpuidT#Dg#uQ0M","createTime":1754884769505,"x":0,"y":88,"color":"#000000","backgroundColor":"#ffffff","bg_opacity":0,"text":"Current:","font":"lv.font_montserrat_16","pageId":"o%kIIonEVlENSZAX","isLVGL":true,"isSelected":false},{"name":"label2","type":"lvgl_label","layer":3,"screenId":"builtin","screenName":"","id":"r7QMfaT0dc=0yZyM","createTime":1754884773480,"x":0,"y":124,"color":"#000000","backgroundColor":"#ffffff","bg_opacity":0,"text":"Power:","font":"lv.font_montserrat_16","pageId":"o%kIIonEVlENSZAX","isLVGL":true,"isSelected":false},{"name":"label3","type":"lvgl_label","layer":4,"screenId":"builtin","screenName":"","id":"xxl6O!O%jC^E2Mrp","createTime":1754884776705,"x":0,"y":161,"color":"#000000","backgroundColor":"#ffffff","bg_opacity":0,"text":"Shunt Voltage:","font":"lv.font_montserrat_16","pageId":"o%kIIonEVlENSZAX","isLVGL":true,"isSelected":false}],"resources":[{"hardware":["hardware_button","hardware_pin_button","imu","speaker","touch","mic","sdcard","i2c"]},{"unit":["unit_ina226"]}],"units":[{"type":"unit_ina226","name":"ina226_0","portList":["A","PAHUB","Custom"],"portType":"A","userPort":[22,21],"id":"nb1aBRydE3fX%Z#9","createTime":1754893250320,"bus":"i2c0","pahubPortList":[0,1,2,3,4,5],"pahubPort":0,"initBlockId":",MSovA[wdch,fv:V{%_r"}],"hats":[],"bases":[],"i2cs":[{"id":"i2c0","portType":"A","userPort":[22,21],"freq":"100000","blockId":"1j(`V,v}|q|iukQaL!n^"}],"blockly":"true01000003332ina226_010A0x41page0true500label0hello M5Bus Voltage:Bus Voltage:ina226_0Vlabel1hello M5Current:Bus Voltage:ina226_0Alabel2hello M5Power:Bus Voltage:ina226_0Wlabel3hello M5Shunt Voltage:Bus Voltage:ina226_0V","screen":[{"simulationName":"Built-in","type":"builtin","width":320,"height":240,"scale":0.77,"screenName":"","blockId":"","screenColorType":0,"rotation":1,"id":"builtin","createTime":1754883745052}],"logicWhenNum":0,"customList":[]} \ No newline at end of file diff --git a/examples/unit/ina226/ina226_core2_example.py b/examples/unit/ina226/ina226_core2_example.py new file mode 100644 index 00000000..1145a026 --- /dev/null +++ b/examples/unit/ina226/ina226_core2_example.py @@ -0,0 +1,104 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import os, sys, io +import M5 +from M5 import * +import m5ui +import lvgl as lv +from hardware import I2C +from hardware import Pin +import time +from unit import INA226Unit + + +page0 = None +label0 = None +label1 = None +label2 = None +label3 = None +i2c0 = None +ina226_0 = None + + +def setup(): + global page0, label0, label1, label2, label3, i2c0, ina226_0 + + M5.begin() + Widgets.setRotation(1) + m5ui.init() + page0 = m5ui.M5Page(bg_c=0xFFFFFF) + label0 = m5ui.M5Label( + "Bus Voltage:", + x=0, + y=56, + text_c=0x000000, + bg_c=0xFFFFFF, + bg_opa=0, + font=lv.font_montserrat_16, + parent=page0, + ) + label1 = m5ui.M5Label( + "Current:", + x=0, + y=88, + text_c=0x000000, + bg_c=0xFFFFFF, + bg_opa=0, + font=lv.font_montserrat_16, + parent=page0, + ) + label2 = m5ui.M5Label( + "Power:", + x=0, + y=124, + text_c=0x000000, + bg_c=0xFFFFFF, + bg_opa=0, + font=lv.font_montserrat_16, + parent=page0, + ) + label3 = m5ui.M5Label( + "Shunt Voltage:", + x=0, + y=161, + text_c=0x000000, + bg_c=0xFFFFFF, + bg_opa=0, + font=lv.font_montserrat_16, + parent=page0, + ) + + i2c0 = I2C(0, scl=Pin(33), sda=Pin(32), freq=100000) + ina226_0 = INA226Unit(i2c0, 0x41, type="1A") + page0.screen_load() + + +def loop(): + global page0, label0, label1, label2, label3, i2c0, ina226_0 + M5.update() + time.sleep_ms(500) + label0.set_text( + str((str("Bus Voltage:") + str((str((ina226_0.read_bus_voltage())) + str("V"))))) + ) + label1.set_text(str((str("Current:") + str((str((ina226_0.read_current())) + str("A")))))) + label2.set_text(str((str("Power:") + str((str((ina226_0.read_power())) + str("W")))))) + label3.set_text( + str((str("Shunt Voltage:") + str((str((ina226_0.read_shunt_voltage())) + str("V"))))) + ) + + +if __name__ == "__main__": + try: + setup() + while True: + loop() + except (Exception, KeyboardInterrupt) as e: + try: + m5ui.deinit() + from utility import print_error_msg + + print_error_msg(e) + except ImportError: + print("please update to latest firmware") diff --git a/m5stack/libs/driver/ina226.py b/m5stack/libs/driver/ina226.py index 1384df42..7c91c701 100644 --- a/m5stack/libs/driver/ina226.py +++ b/m5stack/libs/driver/ina226.py @@ -8,6 +8,39 @@ class INA226: + """Create an INA226 object. + + :param I2C i2c: The I2C bus the INA226 is connected to. + :param int address: The I2C address of the device. + :param float shunt_resistor: The value of the shunt resistor in ohms. Default is 0.2. + + UiFlow2 Code Block: + + |init.png| + + MicroPython Code Block: + + .. code-block:: python + + from hardware import I2C + from driver import INA226 + + i2c0 = I2C(0, scl=Pin(1), sda=Pin(2), freq=100000) + ina226 = INA226(i2c0, 0x40, shunt_resistor=0.02) + """ + + # Register Constants + REG_CONFIG = const(0x00) # Configuration + REG_SHUNT_VOLTAGE = const(0x01) # Shunt Voltage + REG_BUS_VOLTAGE = const(0x02) # Bus Voltage + REG_POWER = const(0x03) # Power + REG_CURRENT = const(0x04) # Current + REG_CALIBRATION = const(0x05) # Calibration + REG_MASK = const(0x06) # Mask/Enable (R/W),报警设置和转换准备标志 + REG_ALERTL = const(0x07) # Alert Limit (R/W),报警阈值 + REG_MANUF_ID = const(0xFE) # Manufacturer ID (R),0x5449 + REG_DIE_ID = const(0xFF) # Die ID (R),0x2260 + # Averaging mode # Configuration Register: Bit[11:9] CFG_AVGMODE_MASK = const(0x0E00) @@ -30,7 +63,7 @@ class INA226: CFG_VBUSCT_1100us = const(0x0100) CFG_VBUSCT_21116us = const(0x0140) CFG_VBUSCT_4156us = const(0x0180) - CFG_AVGMODE_8244us = const(0x01C0) + CFG_VBUSCT_8244us = const(0x01C0) # Shunt voltage conversion time # Configuration Register: Bit[5:3] @@ -56,27 +89,19 @@ class INA226: CFG_MODE_BVOLT_CONTINUOUS = const(0x0006) CFG_MODE_SANDBVOLT_CONTINUOUS = const(0x0007) - # Register Constants - REG_CONFIG = const(0x00) # Configuration - REG_SHUNT_VOLTAGE = const(0x01) # Shunt Voltage - REG_BUS_VOLTAGE = const(0x02) # Bus Voltage - REG_POWER = const(0x03) # Power - REG_CURRENT = const(0x04) # Current - REG_CALIBRATION = const(0x05) # Calibration - def __init__(self, i2c: I2C, addr=0x40, shunt_resistor=0.02): - """ - init - :param i2c: I2C object - :param addr: INA226 slave address - :param shunt_resistor: shunt resistor (unit: Ω) - """ self.i2c = i2c self.addr = addr + self._available() self.shunt_resistor = shunt_resistor self.current_lsb = None self.power_lsb = None + def _available(self): + buf = self._read_register(self.REG_MANUF_ID) + if buf != b"\x54\x49": # Manufacturer ID for Texas Instruments (0x5449) + raise ValueError("UnitByteSwitch not found in I2C bus.") + def _write_register(self, reg, value): data = value.to_bytes(2, "big") self.i2c.writeto_mem(self.addr, reg, data) @@ -89,37 +114,86 @@ def configure(self, avg, vbus_conv_time, vshunt_conv_time, mode): config = avg | vbus_conv_time | vshunt_conv_time | mode self._write_register(self.REG_CONFIG, config) - def calibrate(self, max_expected_current): - """ - calibrate INA226 - :param max_expected_current: unit: A + def calibrate(self, max_expected_current=0, cal_value=None): + """Calibrate the INA226. + + :param float max_expected_current: The maximum expected current in Amperes. + :param int cal_value: The calibration value to use. If None, it will be calculated based on the maximum expected current and the shunt resistor value. + + MicroPython Code Block: + + .. code-block:: python + + ina226_0.calibrate(10) + + ina226_0.calibrate(cal_value=0xC80) """ - self.current_lsb = max_expected_current / 32768 - calibration = int(0.00512 / (self.current_lsb * self.shunt_resistor)) + if cal_value is None: + self.current_lsb = max_expected_current / 32768 + calibration = int(0.00512 / (self.current_lsb * self.shunt_resistor)) + else: + calibration = cal_value + self.current_lsb = 0.00512 / (calibration * self.shunt_resistor) self.power_lsb = ( self.current_lsb * 25 ) # The power LSB has a fixed ratio to the Current_LSB of 25 self._write_register(self.REG_CALIBRATION, calibration) def read_shunt_voltage(self): - """ - unit: V + """Read the shunt voltage in Volts. + + :returns: The shunt voltage in Volts. + :rtype: float + + UiFlow2 Code Block: + + |read_shunt_voltage.png| + + MicroPython Code Block: + + .. code-block:: python + + ina226_0.read_shunt_voltage() """ raw = self._read_register(self.REG_SHUNT_VOLTAGE) raw_value = struct.unpack(">h", raw)[0] return raw_value * 2.5e-6 # Full-scale range = 81.92 mV (decimal = 7FFF); LSB: 2.5 μV def read_bus_voltage(self): - """ - unit: V + """Read the bus voltage in Volts. + + :returns: The bus voltage in Volts. + :rtype: float + + UiFlow2 Code Block: + + |read_bus_voltage.png| + + MicroPython Code Block: + + .. code-block:: python + + ina226_0.read_bus_voltage() """ raw = self._read_register(self.REG_BUS_VOLTAGE) raw_value = struct.unpack(">H", raw)[0] return raw_value * 1.25e-3 # Full-scale range = 40.96 V (decimal = 7FFF); LSB = 1.25 mV. def read_current(self): - """ - unit: A + """Read the current in Amperes. + + :returns: The current in Amperes. + :rtype: float + + UiFlow2 Code Block: + + |read_current.png| + + MicroPython Code Block: + + .. code-block:: python + + ina226_0.read_current() """ if self.current_lsb is None: raise ValueError("Please call the calibrate() method for calibration first.") @@ -129,8 +203,20 @@ def read_current(self): return raw_value * self.current_lsb # convert to unit: A def read_power(self): - """ - unit: W + """Read the power in Watts. + + :returns: The power in Watts. + :rtype: float + + UiFlow2 Code Block: + + |read_power.png| + + MicroPython Code Block: + + .. code-block:: python + + ina226_0.read_power() """ if self.current_lsb is None: raise ValueError("Please call the calibrate() method for calibration first.") diff --git a/m5stack/libs/unit/__init__.py b/m5stack/libs/unit/__init__.py index 4cfa9b6e..a883e649 100644 --- a/m5stack/libs/unit/__init__.py +++ b/m5stack/libs/unit/__init__.py @@ -108,6 +108,7 @@ "Roller485Unit": "roller485", "RollerCANUnit": "rollercan", "ISO485Unit": "rs485_iso", + "INA226Unit": "ina226", "RS485Unit": "rs485", "RTC8563Unit": "rtc8563", "ScalesUnit": "scales", diff --git a/m5stack/libs/unit/ina226.py b/m5stack/libs/unit/ina226.py new file mode 100644 index 00000000..d3ea0e07 --- /dev/null +++ b/m5stack/libs/unit/ina226.py @@ -0,0 +1,44 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +from driver.ina226 import INA226 + + +class INA226Unit(INA226): + """Create an INA226Unit object. + + :param I2C i2c: The I2C bus the Accel Unit is connected to. + :param int address: The I2C address of the device. Default is 0x41. + :param str type: The type of INA226. Default is "10A". Options are "1A" and "10A". + + UiFlow2 Code Block: + + |init.png| + + MicroPython Code Block: + + .. code-block:: python + + from hardware import I2C + from unit import INA226Unit + + i2c0 = I2C(0, scl=Pin(1), sda=Pin(2), freq=100000) + ina226_0 = INA226Unit(i2c0, address=0x41, type="10A") + """ + + def __init__(self, i2c, address=0x41, type="10A"): + if type == "1A": + self.shunt_resistor = 0.08 + self._cal_value = 0x0800 + elif type == "10A": + self.shunt_resistor = 0.005 + self._cal_value = 0x0C80 + super().__init__(i2c, address, shunt_resistor=self.shunt_resistor) + self.configure( + avg=self.CFG_AVGMODE_16SAMPLES, + vbus_conv_time=self.CFG_VBUSCT_8244us, + vshunt_conv_time=self.CFG_VSHUNTCT_8244us, + mode=self.CFG_MODE_SANDBVOLT_CONTINUOUS, + ) # 10A: 0x0C80, 1A:0x811 + self.calibrate(cal_value=self._cal_value) diff --git a/m5stack/libs/unit/manifest.py b/m5stack/libs/unit/manifest.py index 1fcf2468..7af26543 100644 --- a/m5stack/libs/unit/manifest.py +++ b/m5stack/libs/unit/manifest.py @@ -61,6 +61,7 @@ "id.py", "imu_pro.py", "imu.py", + "ina226.py", "ir.py", "joystick.py", "joystick2.py", From bd29e3c6c1c004c050e71688ae72cff3901f1f40 Mon Sep 17 00:00:00 2001 From: "tinyu.zhao@gmail.com" Date: Thu, 21 Aug 2025 10:47:28 +0800 Subject: [PATCH 208/322] lib/unit: Add Unit MQ support. Signed-off-by: tinyu.zhao@gmail.com --- docs/en/refs/unit.mq.ref | 33 ++ docs/en/units/index.rst | 3 +- docs/en/units/mq.rst | 60 +++ docs/locales/zh_CN/LC_MESSAGES/units/mq.po | 431 +++++++++++++++++++++ examples/unit/mq/mq_core2_example.m5f2 | 1 + examples/unit/mq/mq_core2_example.py | 94 +++++ m5stack/libs/unit/__init__.py | 1 + m5stack/libs/unit/manifest.py | 1 + m5stack/libs/unit/mq.py | 306 +++++++++++++++ 9 files changed, 929 insertions(+), 1 deletion(-) create mode 100644 docs/en/refs/unit.mq.ref create mode 100644 docs/en/units/mq.rst create mode 100644 docs/locales/zh_CN/LC_MESSAGES/units/mq.po create mode 100644 examples/unit/mq/mq_core2_example.m5f2 create mode 100644 examples/unit/mq/mq_core2_example.py create mode 100644 m5stack/libs/unit/mq.py diff --git a/docs/en/refs/unit.mq.ref b/docs/en/refs/unit.mq.ref new file mode 100644 index 00000000..a31086b7 --- /dev/null +++ b/docs/en/refs/unit.mq.ref @@ -0,0 +1,33 @@ + +.. |MQ| image:: https://static-cdn.m5stack.com/resource/docs/products/unit/co2/co2_01.webp + :target: https://docs.m5stack.com/en/unit/co2 + :height: 200px + :width: 200px + +.. |init.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/mq/init.png + +.. |set_mq_mode.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/mq/set_mq_mode.png +.. |get_mq_mode.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/mq/get_mq_mode.png +.. |set_led_status.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/mq/set_led_status.png +.. |get_led_status.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/mq/get_led_status.png +.. |set_heat_time.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/mq/set_heat_time.png +.. |get_heat_time.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/mq/get_heat_time.png +.. |get_adc_value.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/mq/get_adc_value.png +.. |get_valid_tags.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/mq/get_valid_tags.png +.. |get_ntc_adc_value.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/mq/get_ntc_adc_value.png +.. |get_ntc_res_value.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/mq/get_ntc_res_value.png +.. |get_voltage.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/mq/get_voltage.png +.. |get_firmware_version.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/mq/get_firmware_version.png +.. |get_i2c_address.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/mq/get_i2c_address.png +.. |set_i2c_address.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/mq/set_i2c_address.png + +.. |example.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/mq/example.png + +.. |mq_core2_example.m5f2| raw:: html + + + mq_core2_example.m5f2 + \ No newline at end of file diff --git a/docs/en/units/index.rst b/docs/en/units/index.rst index fe24d9e0..0a7c6e8f 100644 --- a/docs/en/units/index.rst +++ b/docs/en/units/index.rst @@ -74,9 +74,10 @@ Unit midi.rst minioled.rst miniscale.rst - tof90.rst + mq.rst mqtt.rst mqttpoe.rst + tof90.rst nbiot.rst nbiot2.rst ncir.rst diff --git a/docs/en/units/mq.rst b/docs/en/units/mq.rst new file mode 100644 index 00000000..b3a9ab0e --- /dev/null +++ b/docs/en/units/mq.rst @@ -0,0 +1,60 @@ +MQ Unit +========== + +.. sku: U199 + +.. include:: ../refs/unit.mq.ref + +This is the driver library of MQ Unit, which is used to obtain data from the +MQ sensor. + +Support the following products: + + |MQ| + + +UiFlow2 Example +--------------- + +get MQ value +^^^^^^^^^^^^^^^ + +Open the |mq_core2_example.m5f2| project in UiFlow2. + +This example gets the MQ value of the MQ Unit and displays it on the screen. + +UiFlow2 Code Block: + + |example.png| + +Example output: + + None + +MicroPython Example +------------------- + +get MQ value +^^^^^^^^^^^^^^^ + +This example gets the MQ value of the MQ Unit and displays it on the screen. + +MicroPython Code Block: + + .. literalinclude:: ../../../examples/unit/mq/mq_core2_example.py + :language: python + :linenos: + +Example output: + + None + + +**API** +------- + +MQUnit +^^^^^^^^^ + +.. autoclass:: unit.mq.MQUnit + :members: \ No newline at end of file diff --git a/docs/locales/zh_CN/LC_MESSAGES/units/mq.po b/docs/locales/zh_CN/LC_MESSAGES/units/mq.po new file mode 100644 index 00000000..cdadad8f --- /dev/null +++ b/docs/locales/zh_CN/LC_MESSAGES/units/mq.po @@ -0,0 +1,431 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2016 - 2025 M5Stack Technology Co., Ltd +# This file is distributed under the same license as the UIFlow2 Programming +# Guide package. +# FIRST AUTHOR , 2025. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: UIFlow2 Programming Guide \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-08-12 14:08+0800\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: zh_CN\n" +"Language-Team: zh_CN \n" +"Plural-Forms: nplurals=1; plural=0;\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.16.0\n" + +#: ../../en/units/mq.rst:2 d31c8307d2454bff8b6af7eab11e48e2 +msgid "MQ Unit" +msgstr "MQ Unit" + +#: ../../en/units/mq.rst:8 12569e03fd9b41bc8db5f6c5fd714ff4 +msgid "" +"This is the driver library of MQ Unit, which is used to obtain data from " +"the MQ sensor." +msgstr "这是MQ Unit的驱动库, 用于从MQ传感器获取ADC数据." + +#: ../../en/units/mq.rst:11 39fba555f8854206b3c15aafc02b1487 +msgid "Support the following products:" +msgstr "支持以下产品:" + +#: ../../en/units/mq.rst:13 4dbb2cf4ad5c4f22a408e49c4c13e628 +msgid "|MQ|" +msgstr "" + +#: ../../en/refs/unit.mq.ref 54df6f53961143db916e97def62101e3 +msgid "MQ" +msgstr "" + +#: ../../en/units/mq.rst:17 50be7e2123f14b77b92154cd268b0415 +msgid "UiFlow2 Example" +msgstr "UiFlow2应用示例" + +#: ../../en/units/mq.rst:20 ../../en/units/mq.rst:38 +#: 722377f56e7d48b694cf8999a00c8980 f889e9672de8478e96aa3a1ba9f0be55 +msgid "get MQ value" +msgstr "获取MQ值" + +#: ../../en/units/mq.rst:22 143de4bc84ce427aaf1f02ad7f87054c +msgid "Open the |mq_core2_example.m5f2| project in UiFlow2." +msgstr "在UiFlow2中打开 |mq_core2_example.m5f2| 项目." + +#: ../../en/units/mq.rst:24 ../../en/units/mq.rst:40 +#: 0b4ecd28addc42e6b614ebd3e6657254 c9fc139499cf4da98571b55e7b219191 +msgid "" +"This example gets the MQ value of the MQ Unit and displays it on the " +"screen." +msgstr "此示例获取MQ Unit的MQ值并在屏幕上显示." + +#: ../../en/units/mq.rst:26 1f19952e46d840c082bb850e1f681d33 +#: 31670619470e4c9480d4feea09a26d6e 31f63e94115f423698ccc0579bc84d53 +#: 35005d5388ee4688b09d3cb30bf95871 4dfd4d9a7431420196373e96bfd77789 +#: 6712762daf43420dbcd625992af25824 6ba5270f1e624865a889aada3d72edae +#: 7f330554cc114a92959e0ab332a81fc9 a1f1aa18056849b58f69cf10cc4a2093 +#: aebd21d6a89f40ecb74e15fce408405a b064886d3e37468e85dacf355236b6e0 +#: ba9d6a89f7c0466ead1744b408765d19 cff5bbd45ed644a5bef7e3c169504812 +#: d4379243ed4446d7a37d345b20ebe936 of unit.mq.MQUnit:6 +#: unit.mq.MQUnit.get_adc_value:7 unit.mq.MQUnit.get_firmware_version:6 +#: unit.mq.MQUnit.get_heat_time:6 unit.mq.MQUnit.get_i2c_address:6 +#: unit.mq.MQUnit.get_mq_mode:6 unit.mq.MQUnit.get_ntc_adc_value:7 +#: unit.mq.MQUnit.get_ntc_res_value:6 unit.mq.MQUnit.get_valid_tags:6 +#: unit.mq.MQUnit.get_voltage:7 unit.mq.MQUnit.set_heat_time:6 +#: unit.mq.MQUnit.set_i2c_address:5 unit.mq.MQUnit.set_mq_mode:10 +msgid "UiFlow2 Code Block:" +msgstr "UiFlow2 代码块:" + +#: ../../en/units/mq.rst:28 46b9c43e518342d2a126f4fe8179710a +msgid "|example.png|" +msgstr "" + +#: ../../en/refs/unit.mq.ref:24 dd9c57304ae84b47b4e5e56191ad4af2 +msgid "example.png" +msgstr "" + +#: ../../en/units/mq.rst:30 ../../en/units/mq.rst:48 +#: 77e6de2cd61c41ff9f55a24186273587 804ecd93edd548f492f6bd11a1ca7ffb +msgid "Example output:" +msgstr "示例输出:" + +#: ../../en/units/mq.rst:32 ../../en/units/mq.rst:50 +#: 07e0abb2eb814a8ca80b3acf1413bf7d c9349c8be3fe41f69067ab82ab57b90a +msgid "None" +msgstr "" + +#: ../../en/units/mq.rst:35 3193d9dfceeb4d79a839456bff4044d0 +msgid "MicroPython Example" +msgstr "MicroPython 示例" + +#: ../../en/units/mq.rst:42 046780955ad4496e9cf8b2525e9ab4fa +#: 121ac930d713418083b370078575f0f4 23e0741b1d9c4d9086b7b847771ffc36 +#: 2743ab8bf8ca4e00b3c2c07e0082e5b0 2af8ec066aae414cacb109c71b09a690 +#: 4bbfbdebab3345448032714990218c5d 678b5df007be47fd80b6a396ca8e4696 +#: 7409a311d273473bae9eb2bf0e9e35d4 77afaaeec2f44f8a9cf427a8bca1c8f6 +#: 7bcc319a9f1a40aaaa884c7facd072eb 7ce03691dff54837a4da07fdcc12e122 +#: c48050d2f6c947ffb8ce27d895013973 cbbcab2e21e2415a8fd078d07fd1016e +#: d6a9bc36138b494e84c8ab99aeda5831 db280f313f0142818fa54b90e68ab82b +#: f464b940cf36407083cc02aab5262df8 of unit.mq.MQUnit:10 +#: unit.mq.MQUnit.get_adc_value:11 unit.mq.MQUnit.get_firmware_version:10 +#: unit.mq.MQUnit.get_heat_time:10 unit.mq.MQUnit.get_i2c_address:10 +#: unit.mq.MQUnit.get_led_status:6 unit.mq.MQUnit.get_mq_mode:10 +#: unit.mq.MQUnit.get_ntc_adc_value:11 unit.mq.MQUnit.get_ntc_res_value:10 +#: unit.mq.MQUnit.get_valid_tags:10 unit.mq.MQUnit.get_voltage:11 +#: unit.mq.MQUnit.set_heat_time:10 unit.mq.MQUnit.set_i2c_address:9 +#: unit.mq.MQUnit.set_led_status:5 unit.mq.MQUnit.set_mq_mode:14 +msgid "MicroPython Code Block:" +msgstr "MicroPython 代码块:" + +#: ../../en/units/mq.rst:54 f977036ca53943108d85bcb4213ad6f3 +msgid "**API**" +msgstr "" + +#: ../../en/units/mq.rst:57 65440c247c8c4659b779fc4a922e93dd +msgid "MQUnit" +msgstr "" + +#: 4ee2817b40574b7f9a2ea344134370c2 of unit.mq.MQUnit:1 +msgid "Bases: :py:class:`object`" +msgstr "" + +#: 9b440433ef8541a8aba0da7a118f5f13 of unit.mq.MQUnit:1 +msgid "Create a MQUnit object." +msgstr "创建一个MQUnit对象." + +#: ../../en/units/mq.rst 160e2dff03164c90bc2acb781f77239f +#: 255dfe3a1f22409c8feee3f4d5616fa9 2bde080fec5e48f8b8da6e5957efe9e5 +#: 5ba457cec2fa41b588e611390e38455d ad531f618e644b968e86a294f1ddf3fa +#: bffa37ef49394bab8aee3ccce04119e1 d4a9cec4a0294ce0808d475dc4733df0 +#: d949aa001f3746a9b9585a774eb89fd5 of unit.mq.MQUnit.get_adc_value +#: unit.mq.MQUnit.get_ntc_adc_value unit.mq.MQUnit.get_voltage +#: unit.mq.MQUnit.set_heat_time unit.mq.MQUnit.set_i2c_address +#: unit.mq.MQUnit.set_led_status unit.mq.MQUnit.set_mq_mode +msgid "Parameters" +msgstr "" + +#: 3e7f966cff2c4f458da7fcd5a6288043 of unit.mq.MQUnit:3 +msgid "The I2C bus the MQ Unit is connected to." +msgstr "MQ Unit 连接的I2C总线." + +#: 2afa4db4a619465188bc820a00eda99d of unit.mq.MQUnit:4 +msgid "The I2C address of the device. Default is 0x11." +msgstr "设备的I2C地址, 默认为0x11." + +#: ccf5ca060d5341c2af8a5b72a2f6a327 of unit.mq.MQUnit:8 +msgid "|init.png|" +msgstr "" + +#: ../../en/refs/unit.mq.ref:7 9fff8e822b984db4b788cda4645f2b44 +msgid "init.png" +msgstr "" + +#: 88dcb92df8c24ff8856cffcf83bb3c00 of unit.mq.MQUnit.set_mq_mode:1 +msgid "Set the working mode of the MQ sensor." +msgstr "设置MQ传感器的工作模式." + +#: 9d655b093611419ea0268530d1e4bda7 of unit.mq.MQUnit.set_mq_mode:3 +msgid "Working mode value." +msgstr "工作模式." + +#: f437203d2b49498bb321d14f2b38e938 of unit.mq.MQUnit.set_mq_mode:8 +msgid "Option:" +msgstr "选项:" + +#: 3bb69904531b4aa9990e0e2bcefd1739 of unit.mq.MQUnit.set_mq_mode:6 +msgid "0 : Measurement off" +msgstr "0 : 关闭测量" + +#: 055b2ca6bac04dec87373ef4373b63c7 of unit.mq.MQUnit.set_mq_mode:7 +msgid "1 : Continuous heating mode" +msgstr "1 : 持续加热模式" + +#: 6730bb09ee784078bb9418c8abfd8006 of unit.mq.MQUnit.set_mq_mode:8 +msgid "2 : Pin Level Switching Mode" +msgstr "2 : 引脚电平切换模式" + +#: 0ebe2dd2d1a743cca6af5090cdea2901 of unit.mq.MQUnit.set_mq_mode:12 +msgid "|set_mq_mode.png|" +msgstr "" + +#: ../../en/refs/unit.mq.ref:9 1ef85a8fa63f4f80ba2d4cc24b795e4e +msgid "set_mq_mode.png" +msgstr "" + +#: a3f6011772d240f8a7bb9264040776d2 of unit.mq.MQUnit.get_mq_mode:1 +msgid "Get the current working mode of the MQ sensor." +msgstr "获取当前MQ传感器的工作模式." + +#: 23445d077f914813a528053dffed824b 35ad4d147da340d19afe1c7587315cbc +#: 3e4d2e4ce2ce41888e5bff84b3d27f49 59a8ded95de44d048e2e0b88b587388d +#: 770274fa41024c2195989e0c5f527e1e 78205eb7d20748258b8b31bd8a50906f +#: 82d1e9666af340e88707eb40af956e04 90ce39c21353451988e0a36577159a1a +#: a75ed69996e64072b146b20ef722429f c04e45028fe74982a11dad5d32f4375b of +#: unit.mq.MQUnit.get_adc_value unit.mq.MQUnit.get_firmware_version +#: unit.mq.MQUnit.get_heat_time unit.mq.MQUnit.get_i2c_address +#: unit.mq.MQUnit.get_led_status unit.mq.MQUnit.get_mq_mode +#: unit.mq.MQUnit.get_ntc_adc_value unit.mq.MQUnit.get_ntc_res_value +#: unit.mq.MQUnit.get_valid_tags unit.mq.MQUnit.get_voltage +msgid "Returns" +msgstr "返回值" + +#: 5d173c98749044b78be7a732cd904c91 of unit.mq.MQUnit.get_mq_mode:3 +msgid "Current working mode value." +msgstr "当前工作模式值." + +#: 1fa657eb13504ce688a421e00f4f825e 2135076354dd41ee95b0104eca171f3b +#: 2d2bcd8c12b54fd7ae4c3308534c9cd3 2e72c2824a254518b818b1e0e1a02b17 +#: b1c791375eb04f04bb68c6bb67bd89a1 b5f6cc420eb5466eb0972c615d6c8f0c +#: d248d30308b1410dacf198d0e30e784b ddd74f18307f4fd6ac2ee5895cf8e758 +#: e606858e4d19450b8e6c91386f64c235 f9f7b07108ff441baa3b45564749aeb2 of +#: unit.mq.MQUnit.get_adc_value unit.mq.MQUnit.get_firmware_version +#: unit.mq.MQUnit.get_heat_time unit.mq.MQUnit.get_i2c_address +#: unit.mq.MQUnit.get_led_status unit.mq.MQUnit.get_mq_mode +#: unit.mq.MQUnit.get_ntc_adc_value unit.mq.MQUnit.get_ntc_res_value +#: unit.mq.MQUnit.get_valid_tags unit.mq.MQUnit.get_voltage +msgid "Return type" +msgstr "返回类型" + +#: cdd93d3a40df4797ba01601717cfc81d of unit.mq.MQUnit.get_mq_mode:8 +msgid "|get_mq_mode.png|" +msgstr "" + +#: ../../en/refs/unit.mq.ref:10 8cb3ad733e8f48b4be94afea7675e967 +msgid "get_mq_mode.png" +msgstr "" + +#: 3ca4dd01091544dfaa70496fc624ad4f of unit.mq.MQUnit.set_led_status:1 +msgid "Set the LED status." +msgstr "设置LED状态." + +#: 6201bbd12686485a8b6e1c9687d2d506 of unit.mq.MQUnit.set_led_status:3 +msgid "" +"When the LED is set to on, it lights up when a valid tag is detected, and" +" the brightness is proportional to the ADC value, and it turns off when " +"no tag is detected." +msgstr "当 LED 设置为开启时,读数有效时 LED 亮起,亮度与 ADC 值成正比,否则 LED 熄灭。" + +#: f847575af2f24a1895ea44b2bfa47c70 of unit.mq.MQUnit.get_led_status:1 +msgid "Get the LED status." +msgstr "获取LED状态." + +#: 4c76f0894aa54aeebd4e7c08d4f03c5b of unit.mq.MQUnit.get_led_status:3 +msgid "True if LED status is on, False otherwise." +msgstr "如果 LED 状态为开启,则返回 True,否则返回 False。" + +#: 1d7ccd81759742cb82086a196faa3115 of unit.mq.MQUnit.set_heat_time:1 +msgid "Set heater high and low level time." +msgstr "设置加热的高低电平时间." + +#: 9661e4c8d324498284dd4ea02141fbbc of unit.mq.MQUnit.set_heat_time:3 +msgid "Time for high heating level." +msgstr "高电平加热时间." + +#: 7fa4c417f9f945e388988905b86036a5 of unit.mq.MQUnit.set_heat_time:4 +msgid "Time for low heating level." +msgstr "低电平时间." + +#: 4b22ac0170574cfdb0dd6fa445f664bf of unit.mq.MQUnit.set_heat_time:8 +msgid "|set_heat_time.png|" +msgstr "" + +#: ../../en/refs/unit.mq.ref:13 da1f6451a7c54987846a0d558329b48c +msgid "set_heat_time.png" +msgstr "" + +#: bad976ecfb2042fdbde88d76736bdc47 of unit.mq.MQUnit.get_heat_time:1 +msgid "Get heater high and low level time." +msgstr "获取加热器高低电平时间." + +#: 03b91aa289cc43ccb6ec2f7c06e84cf6 of unit.mq.MQUnit.get_heat_time:3 +msgid "[high_level_time, low_level_time]" +msgstr "" + +#: 5d0ef8f280ba4fc2b74ba984ca70ecb1 of unit.mq.MQUnit.get_heat_time:8 +msgid "|get_heat_time.png|" +msgstr "" + +#: ../../en/refs/unit.mq.ref:14 f186c978d32b44c4a85b4648e8a7878e +msgid "get_heat_time.png" +msgstr "" + +#: 5435ab51fbd94af685ad9d73904196b5 of unit.mq.MQUnit.get_adc_value:1 +msgid "Get ADC value." +msgstr "获取ADC值." + +#: 2e129dedba904fc8aa50e3816ec15626 e55bc86273ee42009ab4ff3c30a78e3d of +#: unit.mq.MQUnit.get_adc_value:3 unit.mq.MQUnit.get_ntc_adc_value:3 +msgid "0 for 8-bit, 1 for 12-bit." +msgstr "0 代表8位ADC数值,1 代表12位ADC数值." + +#: b6406cabc851458caa470999368e9592 of unit.mq.MQUnit.get_adc_value:4 +msgid "ADC value." +msgstr "ADC值." + +#: 38bbb4de43174d52b0533b58af6307de of unit.mq.MQUnit.get_adc_value:9 +msgid "|get_adc_value.png|" +msgstr "" + +#: ../../en/refs/unit.mq.ref:15 58c8d9d89aa14aaf8b9c321b3d709deb +msgid "get_adc_value.png" +msgstr "" + +#: 2baf3e2e7f30444aa079a397d5a2d6b5 of unit.mq.MQUnit.get_valid_tags:1 +msgid "Check if valid tags are detected." +msgstr "检查ADC读数是否有效." + +#: 83e0a074ce7144f68e4eae7868977744 of unit.mq.MQUnit.get_valid_tags:3 +msgid "True if valid tags detected, False otherwise." +msgstr "如果ADC读数有效,则返回 True,否则返回 False。" + +#: a5ee03ac19ea4fa9b166e557817a75a6 of unit.mq.MQUnit.get_valid_tags:8 +msgid "|get_valid_tags.png|" +msgstr "" + +#: ../../en/refs/unit.mq.ref:16 1b98f24754c44ee8a5b9c6c93f8be11d +msgid "get_valid_tags.png" +msgstr "" + +#: 11c499b1291540c69efa9d02a11bae56 of unit.mq.MQUnit.get_ntc_adc_value:1 +msgid "Get internal NTC ADC value." +msgstr "获取内部NTC ADC值." + +#: a184be2c4cd247e987a39e855637aee4 of unit.mq.MQUnit.get_ntc_adc_value:4 +msgid "NTC ADC value." +msgstr "NTC ADC值." + +#: 6374e9ad0716452b8f766a6e0bab0164 of unit.mq.MQUnit.get_ntc_adc_value:9 +msgid "|get_ntc_adc_value.png|" +msgstr "" + +#: ../../en/refs/unit.mq.ref:17 9a13d0fe4cbf44beb20dd5a3f5ed49c1 +msgid "get_ntc_adc_value.png" +msgstr "" + +#: 0db410b26286408199244b84ee67877e of unit.mq.MQUnit.get_ntc_res_value:1 +msgid "Get internal NTC resistance value." +msgstr "获取内部NTC电阻值." + +#: e3ec69fd587f498998a011e6b2ae17de of unit.mq.MQUnit.get_ntc_res_value:3 +msgid "Resistance value." +msgstr "电阻值." + +#: c1f6484b034d48e2a1e1bc56df39542d of unit.mq.MQUnit.get_ntc_res_value:8 +msgid "|get_ntc_res_value.png|" +msgstr "" + +#: ../../en/refs/unit.mq.ref:18 28194a1ec5fe4cc78574b41e56d52603 +msgid "get_ntc_res_value.png" +msgstr "" + +#: 15e786c7357c44f0a327d8e1eb99302e of unit.mq.MQUnit.get_voltage:1 +msgid "Get voltage value from a specific channel." +msgstr "获取指定通道的电压值." + +#: aa675b1bdce34373b12d858b68c30cbe of unit.mq.MQUnit.get_voltage:3 +msgid "Channel number." +msgstr "通道号." + +#: ef336db1f583414aac6a2a76ca0ea98c of unit.mq.MQUnit.get_voltage:4 +msgid "Voltage value." +msgstr "电压值." + +#: adee21bb66de41d6adca194d733b446f of unit.mq.MQUnit.get_voltage:9 +msgid "|get_voltage.png|" +msgstr "" + +#: ../../en/refs/unit.mq.ref:19 45331917ff4b4e809731bca71cab93ef +msgid "get_voltage.png" +msgstr "" + +#: e287e9ace673452f956c46c3d73cb850 of unit.mq.MQUnit.get_firmware_version:1 +msgid "Get firmware version." +msgstr "获取固件版本." + +#: ca2343f4d7cc4849a016fe32860b49ee of unit.mq.MQUnit.get_firmware_version:3 +msgid "Firmware version." +msgstr "固件版本." + +#: a2b3f5e983b1491eb401d4cb0b02fd62 of unit.mq.MQUnit.get_firmware_version:8 +msgid "|get_firmware_version.png|" +msgstr "" + +#: ../../en/refs/unit.mq.ref:20 1e4148cde53d49738571eab6af6693e1 +msgid "get_firmware_version.png" +msgstr "" + +#: 3eef56b15bec4663ac7dec252fb75620 of unit.mq.MQUnit.get_i2c_address:1 +msgid "Get current I2C address." +msgstr "获取当前I2C地址." + +#: 4abb574f0be34877861da34ca83c6906 of unit.mq.MQUnit.get_i2c_address:3 +msgid "MQ Unit I2C address, Default is 0x11." +msgstr "MQ Unit 连接的I2C地址, 默认为0x11." + +#: 2067ece2247e4efeb2b2b055a6b73d64 of unit.mq.MQUnit.get_i2c_address:8 +msgid "|get_i2c_address.png|" +msgstr "" + +#: ../../en/refs/unit.mq.ref:21 316b3e2e52dc48a09116476267a9c70a +msgid "get_i2c_address.png" +msgstr "" + +#: d0c8cd4a240d4f1ea0254af843381fc3 of unit.mq.MQUnit.set_i2c_address:1 +msgid "Set new I2C address." +msgstr "设置新的I2C地址." + +#: 350ca7b625b441308a5b049ced2f00fb of unit.mq.MQUnit.set_i2c_address:3 +msgid "New I2C address (0x08~0x77)." +msgstr "新I2C地址 (0x08~0x77)." + +#: eee8b02513414f9593a73e9a3aba840d of unit.mq.MQUnit.set_i2c_address:7 +msgid "|set_i2c_address.png|" +msgstr "" + +#: ../../en/refs/unit.mq.ref:22 8b9554f7874c40eaa20c4c5cc64b0fd5 +msgid "set_i2c_address.png" +msgstr "" + diff --git a/examples/unit/mq/mq_core2_example.m5f2 b/examples/unit/mq/mq_core2_example.m5f2 new file mode 100644 index 00000000..dcae6008 --- /dev/null +++ b/examples/unit/mq/mq_core2_example.m5f2 @@ -0,0 +1 @@ +{"version":"V2.0","versionNumber":"V2.3.3","type":"core2","components":[{"name":"page0","type":"lvgl_page","layer":0,"screenId":"builtin","screenName":"","id":"j2kEB=+DIENrav4m","createTime":1754972713434,"backgroundColor":"#ffffff","isLVGL":true,"isSelected":true},{"name":"label0","type":"lvgl_label","layer":1,"screenId":"builtin","screenName":"","id":"g*1GA$858eDQoo1K","createTime":1754972785615,"x":1,"y":76,"color":"#000000","backgroundColor":"#ffffff","bg_opacity":0,"text":"Valid Flag:","font":"lv.font_montserrat_16","pageId":"j2kEB=+DIENrav4m","isLVGL":true,"isSelected":false,"width":72,"height":15},{"name":"label1","type":"lvgl_label","layer":2,"screenId":"builtin","screenName":"","id":"w5Gp*WO_H^-FHQO0","createTime":1754972790891,"x":1,"y":111,"color":"#000000","backgroundColor":"#ffffff","bg_opacity":0,"text":"ADC 8bits:0","font":"lv.font_montserrat_16","pageId":"j2kEB=+DIENrav4m","isLVGL":true,"isSelected":false,"width":95,"height":18},{"name":"label2","type":"lvgl_label","layer":3,"screenId":"builtin","screenName":"","id":"vkd3UbY3giJK+gYM","createTime":1754972798529,"x":1,"y":145,"color":"#000000","backgroundColor":"#ffffff","bg_opacity":0,"text":"ADC 12bits:0","font":"lv.font_montserrat_16","pageId":"j2kEB=+DIENrav4m","isLVGL":true,"isSelected":false,"width":87,"height":15}],"resources":[{"hardware":["hardware_button","hardware_pin_button","imu","speaker","touch","mic","sdcard","i2c"]},{"unit":["unit_mq"]}],"units":[{"type":"unit_mq","name":"mq_0","portList":["A","PAHUB","Custom"],"portType":"A","userPort":[22,21],"id":"l*bLQGjfy8VESx4@","createTime":1754972720886,"bus":"i2c0","pahubPortList":[0,1,2,3,4,5],"pahubPort":0,"initBlockId":"2.7E?FTi1X4Ix|L%xNWy"}],"hats":[],"bases":[],"i2cs":[{"id":"i2c0","portType":"A","userPort":[22,21],"freq":"100000","blockId":"d|QUY1B.7slj?[K[C%oo"}],"blockly":"validtrue01000003332mq_00x11mq_01page0truevalidmq_0validlabel0Valid Flag: Wait heatingValid Flag:validlabel1Valid Flag: Wait heatingADC 8bits:mq_00label2Valid Flag: Wait heatingADC 12bits:mq_01label0Valid Flag: Wait heating","screen":[{"simulationName":"Built-in","type":"builtin","width":320,"height":240,"scale":0.77,"screenName":"","blockId":"","screenColorType":0,"rotation":1,"id":"builtin","createTime":1754972713432}],"logicWhenNum":0,"customList":[]} \ No newline at end of file diff --git a/examples/unit/mq/mq_core2_example.py b/examples/unit/mq/mq_core2_example.py new file mode 100644 index 00000000..2529e0b2 --- /dev/null +++ b/examples/unit/mq/mq_core2_example.py @@ -0,0 +1,94 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import os, sys, io +import M5 +from M5 import * +import m5ui +import lvgl as lv +from hardware import I2C +from hardware import Pin +from unit import MQUnit + + +page0 = None +label0 = None +label1 = None +label2 = None +i2c0 = None +mq_0 = None + + +valid = None + + +def setup(): + global page0, label0, label1, label2, i2c0, mq_0, valid + + M5.begin() + Widgets.setRotation(1) + m5ui.init() + page0 = m5ui.M5Page(bg_c=0xFFFFFF) + label0 = m5ui.M5Label( + "Valid Flag:", + x=1, + y=76, + text_c=0x000000, + bg_c=0xFFFFFF, + bg_opa=0, + font=lv.font_montserrat_16, + parent=page0, + ) + label1 = m5ui.M5Label( + "ADC 8bits:0", + x=1, + y=111, + text_c=0x000000, + bg_c=0xFFFFFF, + bg_opa=0, + font=lv.font_montserrat_16, + parent=page0, + ) + label2 = m5ui.M5Label( + "ADC 12bits:0", + x=1, + y=145, + text_c=0x000000, + bg_c=0xFFFFFF, + bg_opa=0, + font=lv.font_montserrat_16, + parent=page0, + ) + + i2c0 = I2C(0, scl=Pin(33), sda=Pin(32), freq=100000) + mq_0 = MQUnit(i2c0, 0x11) + mq_0.set_mq_mode(1) + page0.screen_load() + + +def loop(): + global page0, label0, label1, label2, i2c0, mq_0, valid + M5.update() + valid = mq_0.get_valid_tags() + if valid: + label0.set_text(str((str("Valid Flag:") + str(valid)))) + label1.set_text(str((str("ADC 8bits:") + str((mq_0.get_adc_value(0)))))) + label2.set_text(str((str("ADC 12bits:") + str((mq_0.get_adc_value(1)))))) + else: + label0.set_text(str("Valid Flag: Wait heating")) + + +if __name__ == "__main__": + try: + setup() + while True: + loop() + except (Exception, KeyboardInterrupt) as e: + try: + m5ui.deinit() + from utility import print_error_msg + + print_error_msg(e) + except ImportError: + print("please update to latest firmware") diff --git a/m5stack/libs/unit/__init__.py b/m5stack/libs/unit/__init__.py index a883e649..3c6e5020 100644 --- a/m5stack/libs/unit/__init__.py +++ b/m5stack/libs/unit/__init__.py @@ -81,6 +81,7 @@ "MIDIUnit": "midi", "MiniOLEDUnit": "minioled", "MiniScaleUnit": "miniscale", + "MQUnit": "mq", "MQTTUnit": "mqtt", "MQTTPoEUnit": "mqttpoe", "NBIOTUnit": "nbiot", diff --git a/m5stack/libs/unit/manifest.py b/m5stack/libs/unit/manifest.py index 7af26543..e61e200b 100644 --- a/m5stack/libs/unit/manifest.py +++ b/m5stack/libs/unit/manifest.py @@ -80,6 +80,7 @@ "midi.py", "minioled.py", "miniscale.py", + "mq.py", "mqtt.py", "mqttpoe.py", "nbiot.py", diff --git a/m5stack/libs/unit/mq.py b/m5stack/libs/unit/mq.py new file mode 100644 index 00000000..1527caf9 --- /dev/null +++ b/m5stack/libs/unit/mq.py @@ -0,0 +1,306 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +from unit.unit_helper import UnitError +import machine +import time + +MQ_ADDR = 0x11 + + +class MQUnit: + """Create a MQUnit object. + + :param I2C i2c: The I2C bus the MQ Unit is connected to. + :param int address: The I2C address of the device. Default is 0x11. + + UiFlow2 Code Block: + + |init.png| + + MicroPython Code Block: + + .. code-block:: python + + from hardware import I2C + from unit import MQUnit + + i2c0 = I2C(0, scl=Pin(1), sda=Pin(2), freq=100000) + tof_0 = MQUnit(i2c0) + """ + + MQ_CFG_REG = 0x0 + MQ_LED_CFG_REG = 0x01 + MQ_HEAT_CFG_REG = 0x10 + MQ_ADC_8BIT_REG = 0x20 + MQ_ADC_12BIT_REG = 0x30 + MQ_VALID_REG = 0x40 + MQ_INTERNAL_NTC_8BIT_ADC_REG = 0x50 + MQ_INTERNAL_NTC_12BIT_ADC_REG = 0x60 + MQ_NTC_RES_REG = 0x70 + MQ_VOLTAGE_REG = 0x80 + MQ_FW_VER_REG = 0xFE + MQ_I2C_ADDR_REG = 0xFF + + def __init__(self, i2c: machine.I2C, address: int = MQ_ADDR): + self._i2c = i2c + self._i2c_addr = address + self._available() + self.set_led_status(1) + + def _available(self): + if self._i2c_addr not in self._i2c.scan(): + raise UnitError("MQ unit maybe not connect") + + def set_mq_mode(self, mode: int = 1): + """Set the working mode of the MQ sensor. + + :param int mode: Working mode value. + + Option: + - 0 : Measurement off + - 1 : Continuous heating mode + - 2 : Pin Level Switching Mode + + UiFlow2 Code Block: + + |set_mq_mode.png| + + MicroPython Code Block: + + .. code-block:: python + + mq_0.set_mq_mode(1) + """ + self._i2c.writeto_mem(self._i2c_addr, self.MQ_CFG_REG, bytes([mode])) + + def get_mq_mode(self): + """Get the current working mode of the MQ sensor. + + :returns: Current working mode value. + :rtype: int + + UiFlow2 Code Block: + + |get_mq_mode.png| + + MicroPython Code Block: + + .. code-block:: python + + mode = mq_0.get_mq_mode() + """ + return self._i2c.readfrom_mem(self._i2c_addr, self.MQ_CFG_REG, 1)[0] + + def set_led_status(self, status: int): + """Set the LED status. + + :param int status: When the LED is set to on, it lights up when a valid tag is detected, and the brightness is proportional to the ADC value, and it turns off when no tag is detected. + + MicroPython Code Block: + + .. code-block:: python + + mq_0.set_led_status(1) + """ + self._i2c.writeto_mem(self._i2c_addr, self.MQ_LED_CFG_REG, bytes([status])) + + def get_led_status(self): + """Get the LED status. + + :returns: True if LED status is on, False otherwise. + :rtype: bool + + MicroPython Code Block: + + .. code-block:: python + + led_on = mq_0.get_led_status() + """ + return self._i2c.readfrom_mem(self._i2c_addr, self.MQ_LED_CFG_REG, 1)[0] == 1 + + def set_heat_time(self, high_level_time: int = 30, low_level_time: int = 5): + """Set heater high and low level time. + + :param int high_level_time: Time for high heating level. + :param int low_level_time: Time for low heating level. + + UiFlow2 Code Block: + + |set_heat_time.png| + + MicroPython Code Block: + + .. code-block:: python + + mq_0.set_heat_time(30, 5) + """ + self._i2c.writeto_mem( + self._i2c_addr, self.MQ_HEAT_CFG_REG, bytes([high_level_time, low_level_time]) + ) + + def get_heat_time(self): + """Get heater high and low level time. + + :returns: [high_level_time, low_level_time] + :rtype: [int, int] + + MicroPython Code Block: + + .. code-block:: python + + times = mq_0.get_heat_time() + """ + return list(self._i2c.readfrom_mem(self._i2c_addr, self.MQ_HEAT_CFG_REG, 2)) + + def get_adc_value(self, precision): + """Get ADC value. + + :param int precision: 0 for 8-bit, 1 for 12-bit. + :returns: ADC value. + :rtype: int + + UiFlow2 Code Block: + + |get_adc_value.png| + + MicroPython Code Block: + + .. code-block:: python + + value = mq_0.get_adc_value(1) + """ + if precision == 0: + return self._i2c.readfrom_mem(self._i2c_addr, self.MQ_ADC_8BIT_REG, 1)[0] + elif precision == 1: + buf = self._i2c.readfrom_mem(self._i2c_addr, self.MQ_ADC_12BIT_REG, 2) + return buf[1] << 8 | buf[0] + + def get_valid_tags(self): + """Check if valid tags are detected. + + :returns: True if valid tags detected, False otherwise. + :rtype: bool + + UiFlow2 Code Block: + + |get_valid_tags.png| + + MicroPython Code Block: + + .. code-block:: python + + valid = mq_0.get_valid_tags() + """ + return self._i2c.readfrom_mem(self._i2c_addr, self.MQ_VALID_REG, 1)[0] == 1 + + def get_ntc_adc_value(self, precision): + """Get internal NTC ADC value. + + :param int precision: 0 for 8-bit, 1 for 12-bit. + :returns: NTC ADC value. + :rtype: int + + MicroPython Code Block: + + .. code-block:: python + + ntc = mq_0.get_ntc_adc_value(1) + """ + if precision == 0: + return self._i2c.readfrom_mem(self._i2c_addr, self.MQ_INTERNAL_NTC_8BIT_ADC_REG, 1)[0] + elif precision == 1: + buf = self._i2c.readfrom_mem(self._i2c_addr, self.MQ_INTERNAL_NTC_12BIT_ADC_REG, 2) + return buf[1] << 8 | buf[0] + + def get_ntc_res_value(self): + """Get internal NTC resistance value. + + :returns: Resistance value. + :rtype: int + + MicroPython Code Block: + + .. code-block:: python + + res = mq_0.get_ntc_res_value() + """ + buf = self._i2c.readfrom_mem(self._i2c_addr, self.MQ_NTC_RES_REG, 2) + return buf[1] << 8 | buf[0] + + def get_voltage(self, channle: int): + """Get voltage value from a specific channel. + + :param int channle: Channel number. + :returns: Voltage value. + :rtype: int + + MicroPython Code Block: + + .. code-block:: python + + voltage = mq_0.get_voltage(0) + """ + buf = self._i2c.readfrom_mem(self._i2c_addr, self.MQ_VOLTAGE_REG + channle * 2, 2) + return buf[1] << 8 | buf[0] + + def get_firmware_version(self) -> int: + """Get firmware version. + + :returns: Firmware version. + :rtype: int + + UiFlow2 Code Block: + + |get_firmware_version.png| + + MicroPython Code Block: + + .. code-block:: python + + ver = mq_0.get_firmware_version() + """ + return self._i2c.readfrom_mem(self._i2c_addr, self.MQ_FW_VER_REG, 1)[0] + + def get_i2c_address(self) -> int: + """Get current I2C address. + + :returns: MQ Unit I2C address, Default is 0x11. + :rtype: int + + UiFlow2 Code Block: + + |get_i2c_address.png| + + MicroPython Code Block: + + .. code-block:: python + + addr = mq_0.get_i2c_address() + """ + return self._i2c.readfrom_mem(self._i2c_addr, self.MQ_I2C_ADDR_REG, 1)[0] + + def set_i2c_address(self, addr: int): + """Set new I2C address. + + :param int addr: New I2C address (0x08~0x77). + + UiFlow2 Code Block: + + |set_i2c_address.png| + + MicroPython Code Block: + + .. code-block:: python + + mq_0.set_i2c_address(0x3A) + """ + if addr >= 0x08 and addr <= 0x77: + if addr != self._i2c_addr: + time.sleep_ms(2) + self._i2c.writeto_mem(self._i2c_addr, self.MQ_I2C_ADDR_REG, bytearray([addr])) + self._i2c_addr = addr + time.sleep_ms(200) + else: + raise ValueError("I2C address error, range:0x08~0x77") From bf137d362f3c7f6553e6cbeb5fa9ea004eeea17c Mon Sep 17 00:00:00 2001 From: tinyu Date: Tue, 22 Jul 2025 17:59:55 +0800 Subject: [PATCH 209/322] lib/m5ui: Add LVGL Scale widget and docs. Signed-off-by: tinyu --- docs/en/m5ui/index.rst | 1 + docs/en/m5ui/scale.rst | 378 ++++++++++++++++++ docs/en/refs/m5ui.scale.ref | 31 ++ .../m5ui/scale/cores3_scroll_example.m5f2 | 1 + examples/m5ui/scale/cores3_scroll_example.py | 81 ++++ m5stack/libs/m5ui/__init__.py | 1 + m5stack/libs/m5ui/base.py | 16 + m5stack/libs/m5ui/manifest.py | 1 + m5stack/libs/m5ui/scale.py | 88 ++++ 9 files changed, 598 insertions(+) create mode 100644 docs/en/m5ui/scale.rst create mode 100644 docs/en/refs/m5ui.scale.ref create mode 100644 examples/m5ui/scale/cores3_scroll_example.m5f2 create mode 100644 examples/m5ui/scale/cores3_scroll_example.py create mode 100644 m5stack/libs/m5ui/scale.py diff --git a/docs/en/m5ui/index.rst b/docs/en/m5ui/index.rst index 86cbf046..c4e3b91e 100644 --- a/docs/en/m5ui/index.rst +++ b/docs/en/m5ui/index.rst @@ -44,6 +44,7 @@ Classes label.rst line.rst list.rst + scale.rst slider.rst switch.rst textarea.rst diff --git a/docs/en/m5ui/scale.rst b/docs/en/m5ui/scale.rst new file mode 100644 index 00000000..0599f7a5 --- /dev/null +++ b/docs/en/m5ui/scale.rst @@ -0,0 +1,378 @@ +.. currentmodule:: m5ui + +M5Scale +======== + +.. include:: ../refs/m5ui.scale.ref + +M5Scale is a widget that can be used to create scales in the user interface. Scale Widgets show linear or circular scales with configurable ranges, tick counts, placement, labeling, and subsections (Sections) with custom styling. + + +UiFlow2 Example +--------------- + +scale example +^^^^^^^^^^^^^^ + +Open the |cores3_scroll_example.m5f2| project in UiFlow2. + +This example demonstrates how to create a scale widget with a range of values and custom styling. + +UiFlow2 Code Block: + + |cores3_scroll_example.png| + +Example output: + + None + + +MicroPython Example +------------------- + +scroll example +^^^^^^^^^^^^^^^ + +This example demonstrates how to create a scale widget with a range of values and custom styling. + +MicroPython Code Block: + + .. literalinclude:: ../../../examples/m5ui/scale/cores3_scroll_example.py + :language: python + :linenos: + +Example output: + + None + + +**API** +------- + +M5Scale +^^^^^^^^ + +.. autoclass:: m5ui.scale.M5Scale + :members: + + .. py:method:: set_flag(flag, value) + + Set a flag on the object. If ``value`` is True, the flag is added; if False, the flag is removed. + + :param int flag: The flag to set. + :param bool value: If True, the flag is added; if False, the flag is removed. + + UiFlow2 Code Block: + + |set_flag.png| + + MicroPython Code Block: + + .. code-block:: python + + scale_0.set_flag(lv.obj.FLAG.HIDDEN, True) + + .. py:method:: set_range(start_pos, end_pos) + + Set the range of the scale. + + :param int start_pos: The start position of the scale. + :param int end_pos: The end position of the scale. + + UiFlow2 Code Block: + + |set_range.png| + + MicroPython Code Block: + + .. code-block:: python + + scale_0.set_range(0, 100) + + .. py:method:: set_major_tick_every(tick_every) + + Set the interval for major ticks on the scale. + + :param int tick_every: The interval for major ticks. + + UiFlow2 Code Block: + + |set_major_tick_every.png| + + MicroPython Code Block: + + .. code-block:: python + + scale_0.set_major_tick_every(10) + + .. py:method:: set_total_tick_count(tick_count) + + Set the total tick count of the scale. + + :param int tick_count: The total tick count. + + UiFlow2 Code Block: + + |set_total_tick_count.png| + + MicroPython Code Block: + + .. code-block:: python + + scale_0.set_total_tick_count(11) + + .. py:method:: set_label_show(label_show) + + Set the visibility of the scale labels. + + :param bool label_show: If True, the labels are shown; if False, they are hidden. + + UiFlow2 Code Block: + + |set_label_show.png| + + MicroPython Code Block: + + .. code-block:: python + + scale_0.set_label_show(True) + + + .. py:method:: set_mode(show_mode) + + Set the display mode of the scale. + + :param int show_mode: The display mode. + + Optional: + + - `lv.SCALE.MODE.HORIZONTAL_TOP`: Horizontal top scale. + - `lv.SCALE.MODE.HORIZONTAL_BOTTOM`: Horizontal bottom scale. + - `lv.SCALE.MODE.VERTICAL_LEFT`: Vertical left scale. + - `lv.SCALE.MODE.VERTICAL_RIGHT`: Vertical right scale. + - `lv.SCALE.MODE.ROUND_INNER`: Round inner scale. + - `lv.SCALE.MODE.ROUND_OUTER`: Round outer scale. + + UiFlow2 Code Block: + + |set_mode.png| + + MicroPython Code Block: + + .. code-block:: python + + scale_0.set_mode(lv.SCALE.MODE.HORIZONTAL_TOP) + + .. py:method:: set_text_src(text_src) + + Set the source of the scale label text. + + :param list text_src: The source of the scale label text. + + UiFlow2 Code Block: + + |set_text_src.png| + + MicroPython Code Block: + + .. code-block:: python + + scale_0.set_text_src(["0", "10", "20", "30", "40", "50", "60", "70", "80", "90", "100", None]) + + .. py:method:: set_line_color(color, opa, part) + + Set the color of the line. + + :param int color: The color to set. + :param int opa: The opacity of the color. + :param int part: The part of the object to apply the style to (e.g., lv.PART.MAIN). + + UiFlow2 Code Block: + + |set_line_color.png| + + MicroPython Code Block: + + .. code-block:: python + + scale_0.set_line_color(0xFF0000, 255, lv.PART.MAIN) + scale_0.set_line_color(0x00FF00, 255, lv.PART.ITEMS) + scale_0.set_line_color(0x0000FF, 255, lv.PART.INDICATOR) + + .. py:method:: set_style_line_width(width, part) + + Set the line width of the scale. + + :param int width: The line width to set. + :param int part: The part of the object to apply the style to (e.g., lv.PART.MAIN). + + UiFlow2 Code Block: + + |set_style_line_width.png| + + MicroPython Code Block: + + .. code-block:: python + + scale_0.set_style_line_width(2, lv.PART.MAIN) + scale_0.set_style_line_width(2, lv.PART.ITEMS) + scale_0.set_style_line_width(2, lv.PART.INDICATOR) + + .. py:method:: set_text_color(color, opa, part) + + Set the color of the text. + + :param int color: The color to set. + :param int opa: The opacity of the color. + :param int part: The part of the object to apply the style to lv.PART.INDICATOR. + + UiFlow2 Code Block: + + |set_text_color.png| + + MicroPython Code Block: + + .. code-block:: python + + scale_0.set_text_color(0xFF0000, 255, lv.PART.INDICATOR) + + + .. py:method:: set_style_text_font(font, part) + + Set the font of the scale label text. + + :param lv.lv_font_t font: The font to set. + :param int part: The part of the object to apply the style to lv.PART.INDICATOR. + + UiFlow2 Code Block: + + |set_style_text_font.png| + + MicroPython Code Block: + + .. code-block:: python + + scale_0.set_style_text_font(lv.font_montserrat_14, lv.PART.INDICATOR) + + .. py:method:: set_pos(x, y) + + Set the position of the scale. + + :param int x: The x-coordinate of the scale. + :param int y: The y-coordinate of the scale. + + UiFlow2 Code Block: + + |set_pos.png| + + MicroPython Code Block: + + .. code-block:: python + + scale_0.set_pos(100, 100) + + + .. py:method:: set_x(x) + + Set the x-coordinate of the scale. + + :param int x: The x-coordinate of the scale. + + UiFlow2 Code Block: + + |set_x.png| + + MicroPython Code Block: + + .. code-block:: python + + scale_0.set_x(100) + + + .. py:method:: set_y(y) + + Set the y-coordinate of the scale. + + :param int y: The y-coordinate of the scale. + + UiFlow2 Code Block: + + |set_y.png| + + MicroPython Code Block: + + .. code-block:: python + + scale_0.set_y(100) + + + .. py:method:: set_size(width, height) + + Set the size of the scale. + + :param int width: The width of the scale. + :param int height: The height of the scale. + + UiFlow2 Code Block: + + |set_size.png| + + MicroPython Code Block: + + .. code-block:: python + + scale_0.set_size(100, 50) + + + .. py:method:: set_width(width) + + Set the width of the scale. + + :param int width: The width of the scale. + + UiFlow2 Code Block: + + |set_width.png| + + |set_width1.png| + + MicroPython Code Block: + + .. code-block:: python + + scale_0.set_width(100) + + .. py:method:: set_height(height) + + Set the height of the scale. + + :param int height: The height of the scale. + + UiFlow2 Code Block: + + |set_height.png| + + MicroPython Code Block: + + .. code-block:: python + + scale_0.set_height(50) + + .. py:method:: align_to(obj, align, x, y) + + Align the scale to another object. + + :param lv.obj obj: The object to align to. + :param int align: The alignment type. + :param int x: The x-offset from the aligned object. + :param int y: The y-offset from the aligned object. + + UiFlow2 Code Block: + + |align_to.png| + + MicroPython Code Block: + + .. code-block:: python + + scale_0.align_to(page_0, lv.ALIGN.CENTER, 0, 0) diff --git a/docs/en/refs/m5ui.scale.ref b/docs/en/refs/m5ui.scale.ref new file mode 100644 index 00000000..05e55cd0 --- /dev/null +++ b/docs/en/refs/m5ui.scale.ref @@ -0,0 +1,31 @@ +.. |set_flag.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/image/set_flag.png +.. |set_range.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/image/set_range.png +.. |set_major_tick_every.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/image/set_major_tick_every.png +.. |set_total_tick_count.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/image/set_total_tick_count.png +.. |set_label_show.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/image/set_label_show.png +.. |set_mode.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/image/set_mode.png +.. |set_line_color.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/image/set_line_color.png +.. |set_style_line_width.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/image/set_style_line_width.png +.. |set_style_text_font.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/image/set_style_text_font.png +.. |set_pos.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/image/set_pos.png +.. |set_size.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/image/set_size.png +.. |set_x.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/image/set_x.png +.. |set_y.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/image/set_y.png +.. |align_to.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/image/align_to.png +.. |set_hidden.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/image/set_hidden.png +.. |set_text_src.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/image/set_text_src.png +.. |set_width.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/image/set_width.png +.. |set_width1.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/image/set_width1.png +.. |set_height.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/image/set_height.png +.. |set_text_color.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/image/set_text_color.png + +.. |cores3_scroll_example.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/image/example.png + +.. |cores3_scroll_example.m5f2| raw:: html + + + cores3_scroll_example.m5f2 + diff --git a/examples/m5ui/scale/cores3_scroll_example.m5f2 b/examples/m5ui/scale/cores3_scroll_example.m5f2 new file mode 100644 index 00000000..8eb84cdf --- /dev/null +++ b/examples/m5ui/scale/cores3_scroll_example.m5f2 @@ -0,0 +1 @@ +{"version":"V2.0","versionNumber":"V2.3.0","type":"cores3","components":[{"name":"page0","type":"lvgl_page","layer":0,"screenId":"builtin","screenName":"","id":"l5f2SpBNAdiIZj$l","createTime":1752128633108,"backgroundColor":"#ffffff","isLVGL":true,"isSelected":true},{"name":"bar0","type":"lvgl_bar","layer":1,"screenId":"builtin","screenName":"","id":"s@nB`rh96#zn@OmT","createTime":1752128637953,"x":148,"y":21,"width":20,"height":200,"minValue":0,"maxValue":50,"currentValue":25,"color":"#2193f3","backgroundColor":"#2193f3","pageId":"l5f2SpBNAdiIZj$l","isLVGL":true,"isSelected":false},{"name":"label0","type":"lvgl_label","layer":2,"screenId":"builtin","screenName":"","id":"mw_Vbs91pAC#_zr*","createTime":1752130082941,"x":181,"y":112,"color":"#000000","backgroundColor":"#ffffff","bg_opacity":0,"text":"label0","font":"lv.font_montserrat_14","pageId":"l5f2SpBNAdiIZj$l","isLVGL":true,"isSelected":false,"width":43,"height":15}],"resources":[{"hardware":["hardware_button","hardware_pin_button","imu","speaker","touch","als","mic","sdcard","i2c"]},{"unit":["unit_envpro"]}],"units":[{"type":"unit_envpro","name":"envpro_0","portList":["A","PAHUB","Custom"],"portType":"A","userPort":[22,21],"id":"m=BK#Y%jdcViME7`","createTime":1752128714183,"bus":"i2c0","pahubPortList":[0,1,2,3,4,5],"pahubPort":0,"initBlockId":"jrzLghfRd(EBRC;SuDuh"}],"hats":[],"bases":[],"i2cs":[{"id":"i2c0","portType":"A","userPort":[22,21],"freq":"100000","blockId":"tE^WXf3T.9OwK.97~%5v"}],"blockly":"true010000012envpro_0page0bar0VERrgb25500255rgb00255255truebar00envpro_0label0hello M5envpro_01","screen":[{"simulationName":"Built-in","type":"builtin","width":320,"height":240,"scale":0.77,"screenName":"","blockId":"","screenColorType":0,"rotation":1,"id":"builtin","createTime":1752128633106}],"logicWhenNum":0,"customList":[]} \ No newline at end of file diff --git a/examples/m5ui/scale/cores3_scroll_example.py b/examples/m5ui/scale/cores3_scroll_example.py new file mode 100644 index 00000000..834786f8 --- /dev/null +++ b/examples/m5ui/scale/cores3_scroll_example.py @@ -0,0 +1,81 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import os, sys, io +import M5 +from M5 import * +import m5ui +import lvgl as lv +from hardware import I2C +from hardware import Pin +from unit import ENVPROUnit +import time + + +page0 = None +bar0 = None +label0 = None +i2c0 = None +envpro_0 = None + + +def setup(): + global page0, bar0, label0, i2c0, envpro_0 + + M5.begin() + Widgets.setRotation(1) + m5ui.init() + page0 = m5ui.M5Page(bg_c=0xFFFFFF) + bar0 = m5ui.M5Bar( + x=148, + y=21, + w=20, + h=200, + min_value=0, + max_value=50, + value=25, + bg_c=0x2193F3, + color=0x2193F3, + parent=page0, + ) + label0 = m5ui.M5Label( + "label0", + x=181, + y=112, + text_c=0x000000, + bg_c=0xFFFFFF, + bg_opa=0, + font=lv.font_montserrat_14, + parent=page0, + ) + + i2c0 = I2C(0, scl=Pin(1), sda=Pin(2), freq=100000) + envpro_0 = ENVPROUnit(i2c0) + page0.screen_load() + bar0.set_bg_grad_color( + 0xFF0000, 255, 0x0000FF, 255, lv.GRAD_DIR.VER, lv.PART.INDICATOR | lv.STATE.DEFAULT + ) + + +def loop(): + global page0, bar0, label0, i2c0, envpro_0 + M5.update() + bar0.set_value(int(envpro_0.get_temperature()), True) + label0.set_text(str(envpro_0.get_temperature())) + time.sleep(1) + + +if __name__ == "__main__": + try: + setup() + while True: + loop() + except (Exception, KeyboardInterrupt) as e: + try: + m5ui.deinit() + from utility import print_error_msg + + print_error_msg(e) + except ImportError: + print("please update to latest firmware") diff --git a/m5stack/libs/m5ui/__init__.py b/m5stack/libs/m5ui/__init__.py index 54dc3070..63ce84c6 100644 --- a/m5stack/libs/m5ui/__init__.py +++ b/m5stack/libs/m5ui/__init__.py @@ -18,6 +18,7 @@ "M5List": "list", "M5Page": "page", "M5Slider": "slider", + "M5Scale": "scale", "M5Switch": "switch", "M5TextArea": "textarea", } diff --git a/m5stack/libs/m5ui/base.py b/m5stack/libs/m5ui/base.py index 7afeb1f8..3aa02068 100644 --- a/m5stack/libs/m5ui/base.py +++ b/m5stack/libs/m5ui/base.py @@ -105,6 +105,22 @@ def set_bg_color(self, color: int, opa: int, part: int) -> None: time.sleep(0.01) self.set_style_bg_opa(opa, part) + @staticmethod + def set_line_color(self, color: int, opa: int, part: int = lv.PART.MAIN) -> None: + """Set the line color and opacity for a given part of the object. + + :param int color: The color to set, can be an integer (hex) or a lv.color object. + :param int opa: The opacity level (0-255). + :param int part: The part of the object to apply the style to (e.g., lv.PART.MAIN). + :return: None + """ + if isinstance(color, int): + color = lv.color_hex(color) + + self.set_style_line_color(color, part) + time.sleep(0.01) + self.set_style_line_opa(opa, part) + @staticmethod def set_bg_grad_color(self, color, opa, grad_color, grad_opd, grad_dir, part: int) -> None: """Set the background gradient color of the bar. diff --git a/m5stack/libs/m5ui/manifest.py b/m5stack/libs/m5ui/manifest.py index 230a50fe..54da65fa 100644 --- a/m5stack/libs/m5ui/manifest.py +++ b/m5stack/libs/m5ui/manifest.py @@ -21,6 +21,7 @@ "page.py", "port.py", "slider.py", + "scale.py", "switch.py", "textarea.py", ), diff --git a/m5stack/libs/m5ui/scale.py b/m5stack/libs/m5ui/scale.py new file mode 100644 index 00000000..61c006d0 --- /dev/null +++ b/m5stack/libs/m5ui/scale.py @@ -0,0 +1,88 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +from m5ui.base import M5Base +import lvgl as lv + + +class M5Scale(lv.scale): + """Create a scale object. + + :param int x: The x position of the scale. + :param int y: The y position of the scale. + :param int w: The width of the scale. If not specified, it will be set based on the mode. + :param int h: The height of the scale. If not specified, it will be set based on the mode. + :param int start_pos: The starting position of the scale. + :param int end_pos: The ending position of the scale. + :param int tick_count: The total number of ticks on the scale. + :param int tick_every: The interval between major ticks on the scale. + :param int mode: The mode of the scale. It can be one of the following: + + Options: + + - `lv.scale.MODE.HORIZONTAL_TOP` + - `lv.scale.MODE.HORIZONTAL_BOTTOM` + - `lv.scale.MODE.VERTICAL_LEFT` + - `lv.scale.MODE.VERTICAL_RIGHT` + - `lv.scale.MODE.CIRCULAR` + + :param lv.obj parent: The parent object to attach the scale to. If not specified, the scale will be attached to the default screen. + + UiFlow2 Code Block: + + None + + MicroPython Code Block: + + .. code-block:: python + + from m5ui import M5Scale + import lvgl as lv + + m5ui.init() + scale_0 = M5Scale(x=10, y=10, w=200, h=20, start_pos=0, end_pos=100, tick_count=11, tick_every=2, mode=lv.scale.MODE.HORIZONTAL_TOP, parent=page0) + """ + + def __init__( + self, + x=0, + y=0, + w=0, + h=0, + start_pos=0, + end_pos=100, + tick_count=11, + tick_every=2, + show_mode=lv.scale.MODE.HORIZONTAL_BOTTOM, + parent=None, + ): + if parent is None: + parent = lv.screen_active() + super().__init__(parent) + self.set_pos(x, y) + self.set_range(start_pos, end_pos) + self.set_total_tick_count(tick_count) + self.set_major_tick_every(tick_every) + self.set_mode(show_mode) + if show_mode in [ + lv.scale.MODE.HORIZONTAL_TOP, + lv.scale.MODE.HORIZONTAL_BOTTOM, + ]: + self.set_size(w, 20) + elif show_mode in [ + lv.scale.MODE.VERTICAL_LEFT, + lv.scale.MODE.VERTICAL_RIGHT, + ]: + self.set_size(20, h) + else: + r = max(w, h) + self.set_size(r, r) + + def __getattr__(self, name): + if hasattr(M5Base, name): + method = getattr(M5Base, name) + bound_method = lambda *args, **kwargs: method(self, *args, **kwargs) + setattr(self, name, bound_method) + return bound_method + raise AttributeError(f"'{self.__class__.__name__}' object has no attribute '{name}'") From 8c495481841fae3fd1aa9bc00d9346e3d3e53323 Mon Sep 17 00:00:00 2001 From: "tinyu.zhao@gmail.com" Date: Wed, 13 Aug 2025 10:21:52 +0800 Subject: [PATCH 210/322] lib/m5ui: Add LVGL Scale widget docs and example. Signed-off-by: tinyu.zhao@gmail.com --- docs/en/m5ui/scale.rst | 32 +- docs/en/refs/m5ui.scale.ref | 45 +- docs/locales/zh_CN/LC_MESSAGES/m5ui/scale.po | 593 ++++++++++++++++++ .../m5ui/scale/cores3_scroll_example.m5f2 | 1 - examples/m5ui/scale/cores3_scroll_example.py | 81 --- examples/m5ui/scale/scale_core2_example.m5f2 | 1 + examples/m5ui/scale/scale_core2_example.py | 62 ++ m5stack/libs/m5ui/scale.py | 15 +- 8 files changed, 691 insertions(+), 139 deletions(-) create mode 100644 docs/locales/zh_CN/LC_MESSAGES/m5ui/scale.po delete mode 100644 examples/m5ui/scale/cores3_scroll_example.m5f2 delete mode 100644 examples/m5ui/scale/cores3_scroll_example.py create mode 100644 examples/m5ui/scale/scale_core2_example.m5f2 create mode 100644 examples/m5ui/scale/scale_core2_example.py diff --git a/docs/en/m5ui/scale.rst b/docs/en/m5ui/scale.rst index 0599f7a5..e4ba9196 100644 --- a/docs/en/m5ui/scale.rst +++ b/docs/en/m5ui/scale.rst @@ -14,13 +14,13 @@ UiFlow2 Example scale example ^^^^^^^^^^^^^^ -Open the |cores3_scroll_example.m5f2| project in UiFlow2. +Open the |scale_core2_example.m5f2| project in UiFlow2. This example demonstrates how to create a scale widget with a range of values and custom styling. UiFlow2 Code Block: - |cores3_scroll_example.png| + |scale_core2_example.png| Example output: @@ -37,7 +37,7 @@ This example demonstrates how to create a scale widget with a range of values an MicroPython Code Block: - .. literalinclude:: ../../../examples/m5ui/scale/cores3_scroll_example.py + .. literalinclude:: ../../../examples/m5ui/scale/scale_core2_example.py :language: python :linenos: @@ -146,12 +146,12 @@ M5Scale Optional: - - `lv.SCALE.MODE.HORIZONTAL_TOP`: Horizontal top scale. - - `lv.SCALE.MODE.HORIZONTAL_BOTTOM`: Horizontal bottom scale. - - `lv.SCALE.MODE.VERTICAL_LEFT`: Vertical left scale. - - `lv.SCALE.MODE.VERTICAL_RIGHT`: Vertical right scale. - - `lv.SCALE.MODE.ROUND_INNER`: Round inner scale. - - `lv.SCALE.MODE.ROUND_OUTER`: Round outer scale. + - `lv.scale.MODE.HORIZONTAL_TOP`: Horizontal top scale. + - `lv.scale.MODE.HORIZONTAL_BOTTOM`: Horizontal bottom scale. + - `lv.scale.MODE.VERTICAL_LEFT`: Vertical left scale. + - `lv.scale.MODE.VERTICAL_RIGHT`: Vertical right scale. + - `lv.scale.MODE.ROUND_INNER`: Round inner scale. + - `lv.scale.MODE.ROUND_OUTER`: Round outer scale. UiFlow2 Code Block: @@ -313,10 +313,6 @@ M5Scale :param int width: The width of the scale. :param int height: The height of the scale. - UiFlow2 Code Block: - - |set_size.png| - MicroPython Code Block: .. code-block:: python @@ -330,12 +326,6 @@ M5Scale :param int width: The width of the scale. - UiFlow2 Code Block: - - |set_width.png| - - |set_width1.png| - MicroPython Code Block: .. code-block:: python @@ -348,10 +338,6 @@ M5Scale :param int height: The height of the scale. - UiFlow2 Code Block: - - |set_height.png| - MicroPython Code Block: .. code-block:: python diff --git a/docs/en/refs/m5ui.scale.ref b/docs/en/refs/m5ui.scale.ref index 05e55cd0..913284b3 100644 --- a/docs/en/refs/m5ui.scale.ref +++ b/docs/en/refs/m5ui.scale.ref @@ -1,31 +1,26 @@ -.. |set_flag.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/image/set_flag.png -.. |set_range.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/image/set_range.png -.. |set_major_tick_every.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/image/set_major_tick_every.png -.. |set_total_tick_count.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/image/set_total_tick_count.png -.. |set_label_show.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/image/set_label_show.png -.. |set_mode.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/image/set_mode.png -.. |set_line_color.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/image/set_line_color.png -.. |set_style_line_width.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/image/set_style_line_width.png -.. |set_style_text_font.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/image/set_style_text_font.png -.. |set_pos.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/image/set_pos.png -.. |set_size.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/image/set_size.png -.. |set_x.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/image/set_x.png -.. |set_y.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/image/set_y.png -.. |align_to.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/image/align_to.png -.. |set_hidden.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/image/set_hidden.png -.. |set_text_src.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/image/set_text_src.png -.. |set_width.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/image/set_width.png -.. |set_width1.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/image/set_width1.png -.. |set_height.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/image/set_height.png -.. |set_text_color.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/image/set_text_color.png +.. |set_flag.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/scale/set_flag.png +.. |set_range.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/scale/set_range.png +.. |set_major_tick_every.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/scale/set_major_tick_every.png +.. |set_total_tick_count.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/scale/set_total_tick_count.png +.. |set_label_show.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/scale/set_label_show.png +.. |set_mode.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/scale/set_mode.png +.. |set_text_src.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/scale/set_text_src.png +.. |set_line_color.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/scale/set_line_color.png +.. |set_style_line_width.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/scale/set_style_line_width.png +.. |set_text_color.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/scale/set_text_color.png +.. |set_style_text_font.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/scale/set_style_text_font.png +.. |set_pos.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/scale/set_pos.png +.. |set_x.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/scale/set_x.png +.. |set_y.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/scale/set_y.png +.. |align_to.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/scale/align_to.png -.. |cores3_scroll_example.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/image/example.png +.. |scale_core2_example.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/scale/example.png -.. |cores3_scroll_example.m5f2| raw:: html +.. |scale_core2_example.m5f2| raw:: html - cores3_scroll_example.m5f2 - + scale_core2_example.m5f2 + \ No newline at end of file diff --git a/docs/locales/zh_CN/LC_MESSAGES/m5ui/scale.po b/docs/locales/zh_CN/LC_MESSAGES/m5ui/scale.po new file mode 100644 index 00000000..50eb274f --- /dev/null +++ b/docs/locales/zh_CN/LC_MESSAGES/m5ui/scale.po @@ -0,0 +1,593 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2016 - 2025 M5Stack Technology Co., Ltd +# This file is distributed under the same license as the UIFlow2 Programming +# Guide package. +# FIRST AUTHOR , 2025. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: UIFlow2 Programming Guide \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-08-13 10:20+0800\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: zh_CN\n" +"Language-Team: zh_CN \n" +"Plural-Forms: nplurals=1; plural=0;\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.16.0\n" + +#: ../../en/m5ui/scale.rst:4 ../../en/m5ui/scale.rst:53 +#: 3ff6b5f236d04ce2a6449f4da6387c40 7052ace6d4214bdcb2899784eff8e522 +msgid "M5Scale" +msgstr "" + +#: ../../en/m5ui/scale.rst:8 575ce3b73df842c2849925b4c9216672 +msgid "" +"M5Scale is a widget that can be used to create scales in the user " +"interface. Scale Widgets show linear or circular scales with configurable" +" ranges, tick counts, placement, labeling, and subsections (Sections) " +"with custom styling." +msgstr "" +"M5Scale " +"是一个可在用户界面中创建刻度的控件。它可显示线性或圆形刻度,并支持配置范围、刻度数量、位置、标签以及分段(Sections)等自定义样式。" + +#: ../../en/m5ui/scale.rst:12 a23ee1a02e3a47fda03b3b380ef4e19e +msgid "UiFlow2 Example" +msgstr "UiFlow2 示例" + +#: ../../en/m5ui/scale.rst:15 d6bd12a6f95a4ffeacc06517113c88d8 +msgid "scale example" +msgstr "刻度示例" + +#: ../../en/m5ui/scale.rst:17 5744f4d14a7b4d1083c074d58b68cf31 +msgid "Open the |scale_core2_example.m5f2| project in UiFlow2." +msgstr "" + +#: ../../en/m5ui/scale.rst:19 ../../en/m5ui/scale.rst:36 +#: 84c9c86cb2a64e518b7d9526b5d98525 +msgid "" +"This example demonstrates how to create a scale widget with a range of " +"values and custom styling." +msgstr "本示例演示如何创建具有数值范围和自定义样式的刻度控件。" + +#: ../../en/m5ui/scale.rst:21 ../../en/m5ui/scale.rst:65 +#: ../../en/m5ui/scale.rst:82 ../../en/m5ui/scale.rst:98 +#: ../../en/m5ui/scale.rst:114 ../../en/m5ui/scale.rst:130 +#: ../../en/m5ui/scale.rst:156 ../../en/m5ui/scale.rst:172 +#: ../../en/m5ui/scale.rst:190 ../../en/m5ui/scale.rst:209 +#: ../../en/m5ui/scale.rst:229 ../../en/m5ui/scale.rst:247 +#: ../../en/m5ui/scale.rst:264 ../../en/m5ui/scale.rst:281 +#: ../../en/m5ui/scale.rst:298 ../../en/m5ui/scale.rst:356 +#: efc695bf70814a60bb45598f77e3bac2 m5ui.scale.M5Scale:24 of +msgid "UiFlow2 Code Block:" +msgstr "UiFlow2 代码块:" + +#: ../../en/m5ui/scale.rst:23 fa7f48123abf48809ee063312dc72489 +msgid "|scale_core2_example.png|" +msgstr "" + +#: ../../en/refs/m5ui.scale.ref:17 ed2da5f42487467c94cca9a1214f98f3 +msgid "scale_core2_example.png" +msgstr "" + +#: ../../en/m5ui/scale.rst:25 ../../en/m5ui/scale.rst:44 +#: f9348cce69124dcaa13296ed7967b27f +msgid "Example output:" +msgstr "示例输出:" + +#: ../../en/m5ui/scale.rst:27 ../../en/m5ui/scale.rst:46 +#: 0a16407cfd654350a473a7a3d83b4f23 m5ui.scale.M5Scale:26 of +msgid "None" +msgstr "" + +#: ../../en/m5ui/scale.rst:31 dc3b1f988c8742fe8e016f628791faf2 +msgid "MicroPython Example" +msgstr "MicroPython 示例" + +#: ../../en/m5ui/scale.rst:34 cc27e449d0104e588cc343816d26b71d +msgid "scroll example" +msgstr "刻度示例" + +#: ../../en/m5ui/scale.rst:38 ../../en/m5ui/scale.rst:69 +#: ../../en/m5ui/scale.rst:86 ../../en/m5ui/scale.rst:102 +#: ../../en/m5ui/scale.rst:118 ../../en/m5ui/scale.rst:134 +#: ../../en/m5ui/scale.rst:160 ../../en/m5ui/scale.rst:176 +#: ../../en/m5ui/scale.rst:194 ../../en/m5ui/scale.rst:213 +#: ../../en/m5ui/scale.rst:233 ../../en/m5ui/scale.rst:251 +#: ../../en/m5ui/scale.rst:268 ../../en/m5ui/scale.rst:285 +#: ../../en/m5ui/scale.rst:302 ../../en/m5ui/scale.rst:316 +#: ../../en/m5ui/scale.rst:329 ../../en/m5ui/scale.rst:341 +#: ../../en/m5ui/scale.rst:360 1959bba6ead14443b8aeee6cbcf19356 +#: m5ui.scale.M5Scale:28 of +msgid "MicroPython Code Block:" +msgstr "MicroPython 代码块:" + +#: ../../en/m5ui/scale.rst:50 51cccb56a82348af89037a7e613f0d4b +msgid "**API**" +msgstr "" + +#: c389d08038954e6d94b953f102a0bb2b m5ui.scale.M5Scale:1 of +msgid "Bases: :py:class:`~lvgl.scale`" +msgstr "" + +#: 6ec3f9868bda42d6ac8729e8ea0a651a m5ui.scale.M5Scale:1 of +msgid "Create a scale object." +msgstr "创建一个刻度对象。" + +#: ../../en/m5ui/scale.rst 915381198a9b4bb68f44c91effd05bef +msgid "Parameters" +msgstr "" + +#: fb8a63fe738c47cf97125b41b659d1fc m5ui.scale.M5Scale:3 of +msgid "The x position of the scale." +msgstr "刻度的 x 坐标。" + +#: 6e175401cbdc4fcf81e8b09df31cafda m5ui.scale.M5Scale:4 of +msgid "The y position of the scale." +msgstr "刻度的 y 坐标。" + +#: a2a97166de0b4313b9e38c2a254c6437 m5ui.scale.M5Scale:5 of +msgid "" +"The width of the scale. If not specified, it will be set based on the " +"mode." +msgstr "刻度的宽度,若未指定则根据模式自动设置。" + +#: b2784083d4634b5baecf798457f378b5 m5ui.scale.M5Scale:6 of +msgid "" +"The height of the scale. If not specified, it will be set based on the " +"mode." +msgstr "刻度的高度,若未指定则根据模式自动设置。" + +#: dc6a3aa744b54ad4ac6162d4f5cbe7be m5ui.scale.M5Scale:7 of +msgid "The starting position of the scale." +msgstr "刻度的起始位置。" + +#: 4e7bf2c6d273454ab86a4a99d3b32a6e m5ui.scale.M5Scale:8 of +msgid "The ending position of the scale." +msgstr "刻度的结束位置。" + +#: 4fd843d262c6438c9ae8ed6ccad66466 m5ui.scale.M5Scale:9 of +msgid "The total number of ticks on the scale." +msgstr "刻度上的总刻度数。" + +#: e1fcf6086c8746dcb653748cbd2db62c m5ui.scale.M5Scale:10 of +msgid "The interval between major ticks on the scale." +msgstr "主刻度之间的间隔。" + +#: 05a95b86ddaf4b6e8eae56bce3adbe45 m5ui.scale.M5Scale:11 of +msgid "" +"The mode of the scale. It can be one of the following: Options: - " +"`lv.scale.MODE.HORIZONTAL_TOP`: Horizontal top scale. - " +"`lv.scale.MODE.HORIZONTAL_BOTTOM`: Horizontal bottom scale. - " +"`lv.scale.MODE.VERTICAL_LEFT`: Vertical left scale. - " +"`lv.scale.MODE.VERTICAL_RIGHT`: Vertical right scale. - " +"`lv.scale.MODE.ROUND_INNER`: Round inner scale. - " +"`lv.scale.MODE.ROUND_OUTER`: Round outer scale." +msgstr "" + +#: 513c463e0be74568b4b48913f7ceea28 m5ui.scale.M5Scale:11 of +msgid "The mode of the scale. It can be one of the following:" +msgstr "刻度的显示模式,可为以下之一:" + +#: 9bb58e453394430390b433ec50fb859a m5ui.scale.M5Scale:13 of +msgid "Options:" +msgstr "选项:" + +#: ../../en/m5ui/scale.rst:149 f61646f90aa34b9891ecb72c73889fce +#: m5ui.scale.M5Scale:15 of +msgid "`lv.scale.MODE.HORIZONTAL_TOP`: Horizontal top scale." +msgstr "`lv.scale.MODE.HORIZONTAL_TOP`:水平顶部刻度" + +#: ../../en/m5ui/scale.rst:150 ff14584f4c314060abd29f551b8fec65 +#: m5ui.scale.M5Scale:16 of +msgid "`lv.scale.MODE.HORIZONTAL_BOTTOM`: Horizontal bottom scale." +msgstr "`lv.scale.MODE.HORIZONTAL_BOTTOM`:水平底部刻度" + +#: ../../en/m5ui/scale.rst:151 6b4380fb6d434e20b00fea956b5cec02 +#: m5ui.scale.M5Scale:17 of +msgid "`lv.scale.MODE.VERTICAL_LEFT`: Vertical left scale." +msgstr "`lv.scale.MODE.VERTICAL_LEFT`:垂直左侧刻度" + +#: ../../en/m5ui/scale.rst:152 1471e4428e254d9ca2039fc562cdf3af +#: m5ui.scale.M5Scale:18 of +msgid "`lv.scale.MODE.VERTICAL_RIGHT`: Vertical right scale." +msgstr "`lv.scale.MODE.VERTICAL_RIGHT`:垂直右侧刻度" + +#: ../../en/m5ui/scale.rst:153 53e924113fba4bcd94d58195c83552a8 +#: m5ui.scale.M5Scale:19 of +msgid "`lv.scale.MODE.ROUND_INNER`: Round inner scale." +msgstr "`lv.scale.MODE.ROUND_INNER`:圆形内侧刻度" + +#: ../../en/m5ui/scale.rst:154 e77e023ba73e46c3b5435775c261e6ee +#: m5ui.scale.M5Scale:20 of +msgid "`lv.scale.MODE.ROUND_OUTER`: Round outer scale." +msgstr "`lv.scale.MODE.ROUND_OUTER`:圆形外侧刻度" + +#: 81e35befcf744b8b85b1cadba23d39c9 m5ui.scale.M5Scale:22 of +msgid "" +"The parent object to attach the scale to. If not specified, the scale " +"will be attached to the default screen." +msgstr "要将刻度附加到的父对象;若未指定,则附加到默认屏幕。" + +#: ../../en/m5ui/scale.rst:60 51465a83caf745fa86bbb738d0e7cbae +msgid "" +"Set a flag on the object. If ``value`` is True, the flag is added; if " +"False, the flag is removed." +msgstr "在对象上设置标志;若 ``value`` 为 True 则添加标志,为 False 则移除。" + +#: ../../en/m5ui/scale.rst:62 f9ee83a65a91483a9ea42e16ec6abef2 +msgid "The flag to set." +msgstr "要设置的标志。" + +#: ../../en/m5ui/scale.rst:63 e914610ee99b4bc29d9bcb0030d0586e +msgid "If True, the flag is added; if False, the flag is removed." +msgstr "若为 True 则添加标志;若为 False 则移除。" + +#: ../../en/m5ui/scale.rst:67 12a727b2fa98472e9434223c8137adbb +msgid "|set_flag.png|" +msgstr "" + +#: ../../en/refs/m5ui.scale.ref:1 c7616aff63994ffd8b7b9f35d49b98bd +msgid "set_flag.png" +msgstr "" + +#: ../../en/m5ui/scale.rst:77 e450c3a709034fc3ab9b52a7d8ac6d9e +msgid "Set the range of the scale." +msgstr "设置刻度的范围。" + +#: ../../en/m5ui/scale.rst:79 0f6e4425b8344e3f9dc225f675e72b0a +msgid "The start position of the scale." +msgstr "刻度的起始位置。" + +#: ../../en/m5ui/scale.rst:80 467c4eec0b5d40b09ad17c53d15e6d10 +msgid "The end position of the scale." +msgstr "刻度的结束位置。" + +#: ../../en/m5ui/scale.rst:84 d332b5def1e54216ace26e2b603e2e43 +msgid "|set_range.png|" +msgstr "" + +#: ../../en/refs/m5ui.scale.ref:2 4dddddf8b97e428485154c3e731331d0 +msgid "set_range.png" +msgstr "" + +#: ../../en/m5ui/scale.rst:94 6630a4cd64034587b7f4fa685c09246b +msgid "Set the interval for major ticks on the scale." +msgstr "设置刻度主刻度的间隔。" + +#: ../../en/m5ui/scale.rst:96 808c81d217c542a7beda50a797028a02 +msgid "The interval for major ticks." +msgstr "主刻度间隔值。" + +#: ../../en/m5ui/scale.rst:100 dffd04bc665f4ac1ba4ad53dfb98e51d +msgid "|set_major_tick_every.png|" +msgstr "" + +#: ../../en/refs/m5ui.scale.ref:3 54b431e2425545d38eaa68f7341af4d3 +msgid "set_major_tick_every.png" +msgstr "" + +#: ../../en/m5ui/scale.rst:110 6047e332d4bc44c79d6bf45652d51097 +msgid "Set the total tick count of the scale." +msgstr "设置刻度的总刻度数。" + +#: ../../en/m5ui/scale.rst:112 ca33b234c4314c9dba9b6b602cb10029 +msgid "The total tick count." +msgstr "刻度的总刻度数。" + +#: ../../en/m5ui/scale.rst:116 6533a50313f94c6cbe9c4e93faf7659d +msgid "|set_total_tick_count.png|" +msgstr "" + +#: ../../en/refs/m5ui.scale.ref:4 d9d990dfe35141a5984d82a2e71d78ba +msgid "set_total_tick_count.png" +msgstr "" + +#: ../../en/m5ui/scale.rst:126 a7320439d19a400ea11f41d290361acf +msgid "Set the visibility of the scale labels." +msgstr "设置刻度标签的可见性。" + +#: ../../en/m5ui/scale.rst:128 e7a38c6567bf4ee3b850ea6ddb1fc8d2 +msgid "If True, the labels are shown; if False, they are hidden." +msgstr "若为 True 显示标签;若为 False 隐藏标签。" + +#: ../../en/m5ui/scale.rst:132 fa6aca939bf541cd8f226b03a81f96e6 +msgid "|set_label_show.png|" +msgstr "" + +#: ../../en/refs/m5ui.scale.ref:5 3681c405cf464e5391e59c99daf6cb45 +msgid "set_label_show.png" +msgstr "" + +#: ../../en/m5ui/scale.rst:143 e3ea18f499c5491790ecc3378ff96ab2 +msgid "Set the display mode of the scale." +msgstr "设置刻度的显示模式。" + +#: ../../en/m5ui/scale.rst:145 05a95b86ddaf4b6e8eae56bce3adbe45 +msgid "" +"The display mode. Optional: - `lv.scale.MODE.HORIZONTAL_TOP`: " +"Horizontal top scale. - `lv.scale.MODE.HORIZONTAL_BOTTOM`: Horizontal" +" bottom scale. - `lv.scale.MODE.VERTICAL_LEFT`: Vertical left scale." +" - `lv.scale.MODE.VERTICAL_RIGHT`: Vertical right scale. - " +"`lv.scale.MODE.ROUND_INNER`: Round inner scale. - " +"`lv.scale.MODE.ROUND_OUTER`: Round outer scale." +msgstr "" + +#: ../../en/m5ui/scale.rst:145 8b7fc8b1af3f4638af3c64e6ee13440c +msgid "The display mode." +msgstr "显示模式。" + +#: ../../en/m5ui/scale.rst:147 c538c400fb1a42cb86aa75e62a065f84 +msgid "Optional:" +msgstr "可选值:" + +#: ../../en/m5ui/scale.rst:158 1c08b4b53e8649b5af4b1952a423873d +msgid "|set_mode.png|" +msgstr "" + +#: ../../en/refs/m5ui.scale.ref:6 ed2da5f42487467c94cca9a1214f98f3 +msgid "set_mode.png" +msgstr "" + +#: ../../en/m5ui/scale.rst:168 4302f5827cef4d8eb4e9daff493c6f6e +msgid "Set the source of the scale label text." +msgstr "设置刻度标签文本的来源。" + +#: ../../en/m5ui/scale.rst:170 218d596162b44273a5c93dab036b91ab +msgid "The source of the scale label text." +msgstr "标签文本来源。" + +#: ../../en/m5ui/scale.rst:174 7549b9d9a8634d10a7acbe6ba4478c46 +msgid "|set_text_src.png|" +msgstr "" + +#: ../../en/refs/m5ui.scale.ref:7 5739aec84dee4d938a0ae16c9a30b354 +msgid "set_text_src.png" +msgstr "" + +#: ../../en/m5ui/scale.rst:184 526f1f349faa430ab63966e2f0414fdf +msgid "Set the color of the line." +msgstr "设置线条颜色。" + +#: ../../en/m5ui/scale.rst:186 ../../en/m5ui/scale.rst:225 +#: a720005a64064620acaf382dc4d2ced3 +msgid "The color to set." +msgstr "要设置的颜色。" + +#: ../../en/m5ui/scale.rst:187 ../../en/m5ui/scale.rst:226 +#: a837e468ffb74c3485ab42c3f37f0d32 +msgid "The opacity of the color." +msgstr "颜色不透明度。" + +#: ../../en/m5ui/scale.rst:188 ../../en/m5ui/scale.rst:207 +#: e2f92be82660446089099e3e84f07a52 +msgid "The part of the object to apply the style to (e.g., lv.PART.MAIN)." +msgstr "要应用样式的对象部分(如 lv.PART.MAIN)。" + +#: ../../en/m5ui/scale.rst:192 3a0e3793395a433b8e8c996abf98bbd7 +msgid "|set_line_color.png|" +msgstr "" + +#: ../../en/refs/m5ui.scale.ref:8 14361d7d20104742ba759d4fa8e9b883 +msgid "set_line_color.png" +msgstr "" + +#: ../../en/m5ui/scale.rst:204 1258ebaaa66d4484aa65aef69ea427fa +msgid "Set the line width of the scale." +msgstr "设置刻度线宽度。" + +#: ../../en/m5ui/scale.rst:206 974cdf0730b242239412f33d677b496e +msgid "The line width to set." +msgstr "要设置的线宽。" + +#: ../../en/m5ui/scale.rst:211 f95095630683458abb5cb35ac2121a5d +msgid "|set_style_line_width.png|" +msgstr "" + +#: ../../en/refs/m5ui.scale.ref:9 937abbbf2484414da2bd63424d526f03 +msgid "set_style_line_width.png" +msgstr "" + +#: ../../en/m5ui/scale.rst:223 10804ce8f0d147f8a6b0640f7b36a6cc +msgid "Set the color of the text." +msgstr "设置文本颜色。" + +#: ../../en/m5ui/scale.rst:227 ../../en/m5ui/scale.rst:245 +#: dfb41703a76e435480bcf280cecd28b2 +msgid "The part of the object to apply the style to lv.PART.INDICATOR." +msgstr "要应用样式的对象部分 lv.PART.INDICATOR。" + +#: ../../en/m5ui/scale.rst:231 4b06619418b04546904fac72cfd1a86a +msgid "|set_text_color.png|" +msgstr "" + +#: ../../en/refs/m5ui.scale.ref:10 b69f793ad3cf4d258aa570039b453afe +msgid "set_text_color.png" +msgstr "" + +#: ../../en/m5ui/scale.rst:242 4d6d047536e7402392586128b8ba1b78 +msgid "Set the font of the scale label text." +msgstr "设置刻度标签文本字体。" + +#: ../../en/m5ui/scale.rst:244 717b019d59e146f9a9edfa3b4cab0212 +msgid "The font to set." +msgstr "要设置的字体。" + +#: ../../en/m5ui/scale.rst:249 3a60b3034dc7469a898c32aace69cbd4 +msgid "|set_style_text_font.png|" +msgstr "" + +#: ../../en/refs/m5ui.scale.ref:11 8e99bdf1622e4a19a023e80323f456ae +msgid "set_style_text_font.png" +msgstr "" + +#: ../../en/m5ui/scale.rst:259 f42d0af5aa334ee3af44b8675324da61 +msgid "Set the position of the scale." +msgstr "设置刻度的位置。" + +#: ../../en/m5ui/scale.rst:261 ../../en/m5ui/scale.rst:279 +#: 9d141ef96c444b98978865d06c07e53c d4ddb7ba1c39489bbfd49d09a0931fa9 +msgid "The x-coordinate of the scale." +msgstr "刻度的 x 坐标。" + +#: ../../en/m5ui/scale.rst:262 ../../en/m5ui/scale.rst:296 +#: 572fac9116784297901a8033ebf73825 +msgid "The y-coordinate of the scale." +msgstr "刻度的 y 坐标。" + +#: ../../en/m5ui/scale.rst:266 afdc03efd3fe444fa4fbb72d654e840a +msgid "|set_pos.png|" +msgstr "" + +#: ../../en/refs/m5ui.scale.ref:12 9fe9cfcf26aa4ea69998ef9e1bd40386 +msgid "set_pos.png" +msgstr "" + +#: ../../en/m5ui/scale.rst:277 f72270701d3a47f1b37dbad635d5ab58 +msgid "Set the x-coordinate of the scale." +msgstr "设置刻度的 x 坐标。" + +#: ../../en/m5ui/scale.rst:283 ba28ad45d68d43f3a8db5413e9ceafcd +msgid "|set_x.png|" +msgstr "" + +#: ../../en/refs/m5ui.scale.ref:13 d872f137e9a846209bb7b8e27f1cc993 +msgid "set_x.png" +msgstr "" + +#: ../../en/m5ui/scale.rst:294 76058868dcef43ffa1dd3dd2462c80b2 +msgid "Set the y-coordinate of the scale." +msgstr "设置刻度的 y 坐标。" + +#: ../../en/m5ui/scale.rst:300 50cfa38ddea14f9b8a341c3aa14b2069 +msgid "|set_y.png|" +msgstr "" + +#: ../../en/refs/m5ui.scale.ref:14 e21501d856e54fe3a2d4f81f19a62761 +msgid "set_y.png" +msgstr "" + +#: ../../en/m5ui/scale.rst:311 c794988987fd4065ab8161e769e16c7b +msgid "Set the size of the scale." +msgstr "设置刻度尺寸。" + +#: ../../en/m5ui/scale.rst:313 ../../en/m5ui/scale.rst:327 +#: 05d50e7f9e394cf7b11c22f37db195be c0c69121d3454c13a9428ce6432cc744 +msgid "The width of the scale." +msgstr "刻度宽度。" + +#: ../../en/m5ui/scale.rst:314 ../../en/m5ui/scale.rst:339 +#: 3d6fdd2aad5444e6bf9742a01d2bc1e3 +msgid "The height of the scale." +msgstr "刻度高度。" + +#: ../../en/m5ui/scale.rst:325 93d4917e61f64fa195ae8053d75827e5 +msgid "Set the width of the scale." +msgstr "设置刻度宽度。" + +#: ../../en/m5ui/scale.rst:337 be6d1a74a1694c0a9c45c19fa76eba39 +msgid "Set the height of the scale." +msgstr "设置刻度高度。" + +#: ../../en/m5ui/scale.rst:349 0bfcb5612a8b429bac1ca248e1c7d36c +msgid "Align the scale to another object." +msgstr "将刻度对齐到另一个对象。" + +#: ../../en/m5ui/scale.rst:351 62aa0845662a4baba1743fae1e9d6fc6 +msgid "The object to align to." +msgstr "要对齐到的对象。" + +#: ../../en/m5ui/scale.rst:352 5f87b2d64477463f9973b9669eda1c83 +msgid "The alignment type." +msgstr "对齐类型。" + +#: ../../en/m5ui/scale.rst:353 d16f318b9c8547a4b56df887335181b2 +msgid "The x-offset from the aligned object." +msgstr "相对于对齐对象的 x 偏移量。" + +#: ../../en/m5ui/scale.rst:354 a84b480ffd0641a2b8c3f99ebbbfb1ad +msgid "The y-offset from the aligned object." +msgstr "相对于对齐对象的 y 偏移量。" + +#: ../../en/m5ui/scale.rst:358 de033291ad46422e88b75f2d3128a970 +msgid "|align_to.png|" +msgstr "" + +#: ../../en/refs/m5ui.scale.ref:15 e1b359f0f2664755a993ea42c78b8281 +msgid "align_to.png" +msgstr "" + +#~ msgid "Open the |cores3_scale_example.m5f2| project in UiFlow2." +#~ msgstr "" + +#~ msgid "|cores3_scale_example.png|" +#~ msgstr "" + +#~ msgid "cores3_scale_example.png" +#~ msgstr "" + +#~ msgid "" +#~ "The mode of the scale. It can " +#~ "be one of the following: Options:" +#~ " - `lv.scale.MODE.HORIZONTAL_TOP` - " +#~ "`lv.scale.MODE.HORIZONTAL_BOTTOM` - " +#~ "`lv.scale.MODE.VERTICAL_LEFT` - " +#~ "`lv.scale.MODE.VERTICAL_RIGHT` - " +#~ "`lv.scale.MODE.CIRCULAR`" +#~ msgstr "" + +#~ msgid "`lv.scale.MODE.HORIZONTAL_TOP`" +#~ msgstr "" + +#~ msgid "`lv.scale.MODE.HORIZONTAL_BOTTOM`" +#~ msgstr "" + +#~ msgid "`lv.scale.MODE.VERTICAL_LEFT`" +#~ msgstr "" + +#~ msgid "`lv.scale.MODE.VERTICAL_RIGHT`" +#~ msgstr "" + +#~ msgid "`lv.scale.MODE.CIRCULAR`" +#~ msgstr "" + +#~ msgid "" +#~ "The display mode. Optional: - " +#~ "`lv.SCALE.MODE.HORIZONTAL_TOP`: Horizontal top " +#~ "scale. - `lv.SCALE.MODE.HORIZONTAL_BOTTOM`: " +#~ "Horizontal bottom scale. - " +#~ "`lv.SCALE.MODE.VERTICAL_LEFT`: Vertical left scale." +#~ " - `lv.SCALE.MODE.VERTICAL_RIGHT`: Vertical " +#~ "right scale. - `lv.SCALE.MODE.ROUND_INNER`: " +#~ "Round inner scale. - " +#~ "`lv.SCALE.MODE.ROUND_OUTER`: Round outer scale." +#~ msgstr "" + +#~ msgid "|set_size.png|" +#~ msgstr "" + +#~ msgid "set_size.png" +#~ msgstr "" + +#~ msgid "|set_width.png|" +#~ msgstr "" + +#~ msgid "set_width.png" +#~ msgstr "" + +#~ msgid "|set_width1.png|" +#~ msgstr "" + +#~ msgid "set_width1.png" +#~ msgstr "" + +#~ msgid "|set_height.png|" +#~ msgstr "" + +#~ msgid "set_height.png" +#~ msgstr "" + diff --git a/examples/m5ui/scale/cores3_scroll_example.m5f2 b/examples/m5ui/scale/cores3_scroll_example.m5f2 deleted file mode 100644 index 8eb84cdf..00000000 --- a/examples/m5ui/scale/cores3_scroll_example.m5f2 +++ /dev/null @@ -1 +0,0 @@ -{"version":"V2.0","versionNumber":"V2.3.0","type":"cores3","components":[{"name":"page0","type":"lvgl_page","layer":0,"screenId":"builtin","screenName":"","id":"l5f2SpBNAdiIZj$l","createTime":1752128633108,"backgroundColor":"#ffffff","isLVGL":true,"isSelected":true},{"name":"bar0","type":"lvgl_bar","layer":1,"screenId":"builtin","screenName":"","id":"s@nB`rh96#zn@OmT","createTime":1752128637953,"x":148,"y":21,"width":20,"height":200,"minValue":0,"maxValue":50,"currentValue":25,"color":"#2193f3","backgroundColor":"#2193f3","pageId":"l5f2SpBNAdiIZj$l","isLVGL":true,"isSelected":false},{"name":"label0","type":"lvgl_label","layer":2,"screenId":"builtin","screenName":"","id":"mw_Vbs91pAC#_zr*","createTime":1752130082941,"x":181,"y":112,"color":"#000000","backgroundColor":"#ffffff","bg_opacity":0,"text":"label0","font":"lv.font_montserrat_14","pageId":"l5f2SpBNAdiIZj$l","isLVGL":true,"isSelected":false,"width":43,"height":15}],"resources":[{"hardware":["hardware_button","hardware_pin_button","imu","speaker","touch","als","mic","sdcard","i2c"]},{"unit":["unit_envpro"]}],"units":[{"type":"unit_envpro","name":"envpro_0","portList":["A","PAHUB","Custom"],"portType":"A","userPort":[22,21],"id":"m=BK#Y%jdcViME7`","createTime":1752128714183,"bus":"i2c0","pahubPortList":[0,1,2,3,4,5],"pahubPort":0,"initBlockId":"jrzLghfRd(EBRC;SuDuh"}],"hats":[],"bases":[],"i2cs":[{"id":"i2c0","portType":"A","userPort":[22,21],"freq":"100000","blockId":"tE^WXf3T.9OwK.97~%5v"}],"blockly":"true010000012envpro_0page0bar0VERrgb25500255rgb00255255truebar00envpro_0label0hello M5envpro_01","screen":[{"simulationName":"Built-in","type":"builtin","width":320,"height":240,"scale":0.77,"screenName":"","blockId":"","screenColorType":0,"rotation":1,"id":"builtin","createTime":1752128633106}],"logicWhenNum":0,"customList":[]} \ No newline at end of file diff --git a/examples/m5ui/scale/cores3_scroll_example.py b/examples/m5ui/scale/cores3_scroll_example.py deleted file mode 100644 index 834786f8..00000000 --- a/examples/m5ui/scale/cores3_scroll_example.py +++ /dev/null @@ -1,81 +0,0 @@ -# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD -# -# SPDX-License-Identifier: MIT - -import os, sys, io -import M5 -from M5 import * -import m5ui -import lvgl as lv -from hardware import I2C -from hardware import Pin -from unit import ENVPROUnit -import time - - -page0 = None -bar0 = None -label0 = None -i2c0 = None -envpro_0 = None - - -def setup(): - global page0, bar0, label0, i2c0, envpro_0 - - M5.begin() - Widgets.setRotation(1) - m5ui.init() - page0 = m5ui.M5Page(bg_c=0xFFFFFF) - bar0 = m5ui.M5Bar( - x=148, - y=21, - w=20, - h=200, - min_value=0, - max_value=50, - value=25, - bg_c=0x2193F3, - color=0x2193F3, - parent=page0, - ) - label0 = m5ui.M5Label( - "label0", - x=181, - y=112, - text_c=0x000000, - bg_c=0xFFFFFF, - bg_opa=0, - font=lv.font_montserrat_14, - parent=page0, - ) - - i2c0 = I2C(0, scl=Pin(1), sda=Pin(2), freq=100000) - envpro_0 = ENVPROUnit(i2c0) - page0.screen_load() - bar0.set_bg_grad_color( - 0xFF0000, 255, 0x0000FF, 255, lv.GRAD_DIR.VER, lv.PART.INDICATOR | lv.STATE.DEFAULT - ) - - -def loop(): - global page0, bar0, label0, i2c0, envpro_0 - M5.update() - bar0.set_value(int(envpro_0.get_temperature()), True) - label0.set_text(str(envpro_0.get_temperature())) - time.sleep(1) - - -if __name__ == "__main__": - try: - setup() - while True: - loop() - except (Exception, KeyboardInterrupt) as e: - try: - m5ui.deinit() - from utility import print_error_msg - - print_error_msg(e) - except ImportError: - print("please update to latest firmware") diff --git a/examples/m5ui/scale/scale_core2_example.m5f2 b/examples/m5ui/scale/scale_core2_example.m5f2 new file mode 100644 index 00000000..3a1493a3 --- /dev/null +++ b/examples/m5ui/scale/scale_core2_example.m5f2 @@ -0,0 +1 @@ +{"version":"V2.0","versionNumber":"V2.3.3","type":"core2","components":[{"name":"page0","type":"lvgl_page","layer":0,"screenId":"builtin","screenName":"","id":"wBTu^*&oackQUfmy","createTime":1754985498393,"backgroundColor":"#ffffff","isLVGL":true,"isSelected":true},{"name":"scale0","type":"lvgl_scale","layer":1,"screenId":"builtin","screenName":"","id":"sx^oY=0Qw+YG&J_T","createTime":1754985875786,"x":7,"y":92,"width":300,"height":0,"startPos":0,"endPos":100,"tickCount":11,"tickEvery":2,"mode":"HORIZONTAL_TOP","modeOption":[{"label":"Horizontal Top","value":"HORIZONTAL_TOP"},{"label":"Horizontal Bottom","value":"HORIZONTAL_BOTTOM"},{"label":"Vertical Left","value":"VERTICAL_LEFT"},{"label":"Vertical Right","value":"VERTICAL_RIGHT"},{"label":"Round Inner","value":"ROUND_INNER"},{"label":"Round Outer","value":"ROUND_OUTER"}],"pageId":"wBTu^*&oackQUfmy","isLVGL":true,"isSelected":false}],"resources":[{"hardware":["hardware_button","hardware_pin_button","imu","speaker","touch","mic","sdcard"]}],"units":[],"hats":[],"bases":[],"i2cs":[],"blockly":"truepage0scale0MAIN2scale0MAINpalette#6600cc255scale0INDICATOR4scale0INDICATORpalette#ff9900255scale0ITEMS6scale0ITEMSpalette#66ff99255true","screen":[{"simulationName":"Built-in","type":"builtin","width":320,"height":240,"scale":0.77,"screenName":"","blockId":"","screenColorType":0,"rotation":1,"id":"builtin","createTime":1754985498391}],"logicWhenNum":0,"customList":[]} \ No newline at end of file diff --git a/examples/m5ui/scale/scale_core2_example.py b/examples/m5ui/scale/scale_core2_example.py new file mode 100644 index 00000000..0805b606 --- /dev/null +++ b/examples/m5ui/scale/scale_core2_example.py @@ -0,0 +1,62 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import os, sys, io +import M5 +from M5 import * +import m5ui +import lvgl as lv + + +page0 = None +scale0 = None + + +def setup(): + global page0, scale0 + + M5.begin() + Widgets.setRotation(1) + m5ui.init() + page0 = m5ui.M5Page(bg_c=0xFFFFFF) + scale0 = m5ui.M5Scale( + x=7, + y=92, + w=300, + h=0, + start_pos=0, + end_pos=100, + tick_count=11, + tick_every=2, + show_mode=lv.scale.MODE.HORIZONTAL_TOP, + parent=page0, + ) + + page0.screen_load() + scale0.set_style_line_width(2, lv.PART.MAIN) + scale0.set_line_color(0x6600CC, 255, lv.PART.MAIN) + scale0.set_style_line_width(4, lv.PART.INDICATOR) + scale0.set_line_color(0xFF9900, 255, lv.PART.INDICATOR) + scale0.set_style_line_width(6, lv.PART.ITEMS) + scale0.set_line_color(0x66FF99, 255, lv.PART.ITEMS) + + +def loop(): + global page0, scale0 + M5.update() + + +if __name__ == "__main__": + try: + setup() + while True: + loop() + except (Exception, KeyboardInterrupt) as e: + try: + m5ui.deinit() + from utility import print_error_msg + + print_error_msg(e) + except ImportError: + print("please update to latest firmware") diff --git a/m5stack/libs/m5ui/scale.py b/m5stack/libs/m5ui/scale.py index 61c006d0..7a5cf28b 100644 --- a/m5stack/libs/m5ui/scale.py +++ b/m5stack/libs/m5ui/scale.py @@ -21,18 +21,15 @@ class M5Scale(lv.scale): Options: - - `lv.scale.MODE.HORIZONTAL_TOP` - - `lv.scale.MODE.HORIZONTAL_BOTTOM` - - `lv.scale.MODE.VERTICAL_LEFT` - - `lv.scale.MODE.VERTICAL_RIGHT` - - `lv.scale.MODE.CIRCULAR` + - `lv.scale.MODE.HORIZONTAL_TOP`: Horizontal top scale. + - `lv.scale.MODE.HORIZONTAL_BOTTOM`: Horizontal bottom scale. + - `lv.scale.MODE.VERTICAL_LEFT`: Vertical left scale. + - `lv.scale.MODE.VERTICAL_RIGHT`: Vertical right scale. + - `lv.scale.MODE.ROUND_INNER`: Round inner scale. + - `lv.scale.MODE.ROUND_OUTER`: Round outer scale. :param lv.obj parent: The parent object to attach the scale to. If not specified, the scale will be attached to the default screen. - UiFlow2 Code Block: - - None - MicroPython Code Block: .. code-block:: python From 1524dd2ec1884c0724f078b8e35e3f0245215cf3 Mon Sep 17 00:00:00 2001 From: "tinyu.zhao@gmail.com" Date: Mon, 18 Aug 2025 16:20:45 +0800 Subject: [PATCH 211/322] libs/unit: Fix crash after switching UART port in audioplayer. Signed-off-by: tinyu.zhao@gmail.com --- m5stack/libs/unit/audioplayer.py | 46 +++++++++++--------------------- 1 file changed, 15 insertions(+), 31 deletions(-) diff --git a/m5stack/libs/unit/audioplayer.py b/m5stack/libs/unit/audioplayer.py index e323a072..0a36ddc6 100644 --- a/m5stack/libs/unit/audioplayer.py +++ b/m5stack/libs/unit/audioplayer.py @@ -4,6 +4,7 @@ import sys import time import machine +from micropython import schedule if sys.platform != "esp32": from typing import Literal @@ -30,51 +31,34 @@ class AudioPlayerUnit: audio_player_0 = AudioPlayerUnit(2, port=(33, 32)) """ + _uart_instance = None + def __init__(self, id: Literal[0, 1, 2] = 2, port: list | tuple = None, verbose=False): + if AudioPlayerUnit._uart_instance is not None: + AudioPlayerUnit._uart_instance.deinit() + verbose and print("UART instance exit, deinit uart obj") + self.uart = machine.UART(id, tx=port[1], rx=port[0]) self.uart.init(9600, bits=8, parity=None, stop=1) self.uart.irq(handler=self._handler, trigger=machine.UART.IRQ_RXIDLE) - self.uart.read() + AudioPlayerUnit._uart_instance = self.uart self.verbose = verbose self.raw_message = "" self.command_num = 0 self.is_recieved = False self.received_data = [False] self.play_status = None + self.uart.read() def _handler(self, uart) -> None: data = uart.read() + schedule(self._handler_data, data) + + def _handler_data(self, data): if data is not None and len(data) > 5: self.verbose and print( "Received message:", " ".join(f"0x{byte:02X}" for byte in data).split() ) - # self.verbose and print(data[0] == self.command) - # self.verbose and print(data[1] == (~self.command) & 0xFF) - # if self.retun_value is not None: - # self.verbose and print(data[2:4] == bytes(self.retun_value)) - # else: - # self.verbose and print("True") - # self.verbose and print( - # data[-1] - # == ( - # self.command + ~self.command - # & 0xFF - # + sum(data[4:-1]) - # + (0 if self.retun_value is None else sum(self.retun_value)) - # ) - # & 0xFF - # ) - # self.verbose and print( - # ( - # hex( - # self.command - # + (~self.command & 0xFF) - # + sum(data[4:-1]) - # + (0 if self.retun_value is None else sum(self.retun_value)) - # & 0xFF - # ) - # ) - # ) if (data[0] == 0x0A and self.command == 0x05) or ( data[0] == self.command and data[1] == (~self.command & 0xFF) ): @@ -99,7 +83,7 @@ def _handler(self, uart) -> None: # self.check_tick_callback() else: self.verbose and print("Invalid frame received: header/footer mismatch") - uart.read() + self.uart.read() def _wait_for_message(self, time_out: int = 500): self.is_recieved = False @@ -146,7 +130,7 @@ def check_play_status(self): audio_player_0.check_play_status() """ self._send_message(0x04, [0x00], [0x02, 0x00]) - return self._wait_for_message(500)[0] + return self._wait_for_message(600)[0] def play_audio(self) -> int: # 可从暂停处开始播放 """Play the audio. @@ -165,7 +149,7 @@ def play_audio(self) -> int: # 可从暂停处开始播放 audio_player_0.play_audio() """ self._send_message(0x04, [0x01], [0x02, 0x00]) - return self._wait_for_message(500)[0] + time.sleep(0.6) def pause_audio(self) -> int: # 暂停播放 """Pause the audio. From 618da72ae015e404b0fa6478cb3eda3b62cfc9ca Mon Sep 17 00:00:00 2001 From: lbuque <1102390310@qq.com> Date: Mon, 4 Aug 2025 15:53:28 +0800 Subject: [PATCH 212/322] libs/m5ui: Add M5Dropdown support. Signed-off-by: lbuque <1102390310@qq.com> --- docs/en/m5ui/dropdown.rst | 441 ++++++++++++ docs/en/m5ui/index.rst | 1 + docs/en/refs/m5ui.dropdown.ref | 38 + .../zh_CN/LC_MESSAGES/m5ui/dropdown.po | 679 ++++++++++++++++++ .../cores3_dropdown_directions_example.m5f2 | 1 + .../cores3_dropdown_directions_example.py | 95 +++ m5stack/libs/m5ui/__init__.py | 1 + m5stack/libs/m5ui/dropdown.py | 138 +++- m5stack/libs/m5ui/manifest.py | 1 + tests/m5ui/test_dropdown.py | 56 ++ 10 files changed, 1441 insertions(+), 10 deletions(-) create mode 100644 docs/en/m5ui/dropdown.rst create mode 100644 docs/en/refs/m5ui.dropdown.ref create mode 100644 docs/locales/zh_CN/LC_MESSAGES/m5ui/dropdown.po create mode 100644 examples/m5ui/dropdown/cores3_dropdown_directions_example.m5f2 create mode 100644 examples/m5ui/dropdown/cores3_dropdown_directions_example.py create mode 100644 tests/m5ui/test_dropdown.py diff --git a/docs/en/m5ui/dropdown.rst b/docs/en/m5ui/dropdown.rst new file mode 100644 index 00000000..4071bf34 --- /dev/null +++ b/docs/en/m5ui/dropdown.rst @@ -0,0 +1,441 @@ +.. currentmodule:: m5ui + +M5Dropdown +========== + +.. include:: ../refs/m5ui.dropdown.ref + +M5Dropdown is a widget that can be used to create dropdown menus in the user +interface. It allows users to select one option from a list of available options +with a compact dropdown interface. + +UiFlow2 Example +--------------- + +Drop down in four directions +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Open the |cores3_dropdown_directions_example.m5f2| project in UiFlow2. + +This example creates a drop down, up, left and right menus. + +UiFlow2 Code Block: + + |cores3_dropdown_directions_example.png| + +Example output: + + None + + +MicroPython Example +------------------- + +Drop down in four directions +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +This example creates a drop down, up, left and right menus. + +MicroPython Code Block: + + .. literalinclude:: ../../../examples/m5ui/dropdown/cores3_dropdown_directions_example.py + :language: python + :linenos: + +Example output: + + None + + +**API** +------- + +M5Dropdown +^^^^^^^^^^ + +.. autoclass:: m5ui.dropdown.M5Dropdown + :members: + + .. py:method:: set_flag(flag, value) + + Set a flag on the object. If ``value`` is True, the flag is added; if False, the flag is removed. + + :param int flag: The flag to set. + :param bool value: If True, the flag is added; if False, the flag is removed. + :return: None + + UiFlow2 Code Block: + + |set_flag.png| + + MicroPython Code Block: + + .. code-block:: python + + dropdown_0.set_flag(lv.obj.FLAG.HIDDEN, True) + + + .. py:method:: toggle_flag(flag) + + Toggle a flag on the object. If the flag is set, it is removed; if not set, it is added. + + :param int flag: The flag to toggle. + :return: None + + UiFlow2 Code Block: + + |toggle_flag.png| + + MicroPython Code Block: + + .. code-block:: python + + dropdown_0.toggle_flag(lv.obj.FLAG.HIDDEN) + + + .. py:method:: set_state(state, value) + + Set the state of the dropdown. If ``value`` is True, the state is set; if False, the state is unset. + + :param int state: The state to set. + :param bool value: If True, the state is set; if False, the state is unset. + :return: None + + UiFlow2 Code Block: + + |set_state.png| + + MicroPython Code Block: + + .. code-block:: python + + dropdown_0.set_state(lv.STATE.CHECKED, True) + + + .. py:method:: toggle_state(state) + + Toggle the state of the dropdown. If the state is set, it is unset; if not set, it is set. + + :param int state: The state to toggle. + :return: None + + UiFlow2 Code Block: + + |toggle_state.png| + + MicroPython Code Block: + + .. code-block:: python + + dropdown_0.toggle_state(lv.STATE.CHECKED) + + + .. py:method:: set_pos(x, y) + + Set the position of the dropdown. + + :param int x: The x-coordinate of the dropdown. + :param int y: The y-coordinate of the dropdown. + :return: None + + UiFlow2 Code Block: + + |set_pos.png| + + MicroPython Code Block: + + .. code-block:: python + + dropdown_0.set_pos(100, 100) + + + .. py:method:: set_x(x) + + Set the x-coordinate of the dropdown. + + :param int x: The x-coordinate of the dropdown. + :return: None + + UiFlow2 Code Block: + + |set_x.png| + + MicroPython Code Block: + + .. code-block:: python + + dropdown_0.set_x(100) + + + .. py:method:: set_y(y) + + Set the y-coordinate of the dropdown. + + :param int y: The y-coordinate of the dropdown. + :return: None + + UiFlow2 Code Block: + + |set_y.png| + + MicroPython Code Block: + + .. code-block:: python + + dropdown_0.set_y(100) + + + .. py:method:: set_size(width, height) + + Set the size of the dropdown. + + :param int width: The width of the dropdown. + :param int height: The height of the dropdown. + :return: None + + UiFlow2 Code Block: + + |set_size.png| + + MicroPython Code Block: + + .. code-block:: python + + dropdown_0.set_size(100, 50) + + + .. py:method:: set_width(width) + + Set the width of the dropdown. + + :param int width: The width of the dropdown. + :return: None + + UiFlow2 Code Block: + + |set_width.png| + + MicroPython Code Block: + + .. code-block:: python + + dropdown_0.set_width(100) + + + .. py:method:: get_width() + + Get the width of the dropdown. + + :return: The width of the dropdown. + :rtype: int + + UiFlow2 Code Block: + + |get_width.png| + + MicroPython Code Block: + + .. code-block:: python + + dropdown_0.get_width() + + + .. py:method:: set_height(height) + + Set the height of the dropdown. + + :param int height: The height of the dropdown. + :return: None + + UiFlow2 Code Block: + + |set_height.png| + + MicroPython Code Block: + + .. code-block:: python + + dropdown_0.set_height(50) + + + .. py:method:: get_height() + + Get the height of the dropdown. + + :return: The height of the dropdown. + :rtype: int + + UiFlow2 Code Block: + + |get_height.png| + + MicroPython Code Block: + + .. code-block:: python + + dropdown_0.get_height() + + .. py:method:: align_to(obj, align, x, y) + + Align the dropdown to another object. + + :param lv.obj obj: The object to align to. + :param int align: The alignment type. + :param int x: The x-offset from the aligned object. + :param int y: The y-offset from the aligned object. + :return: None + + UiFlow2 Code Block: + + |align_to.png| + + MicroPython Code Block: + + .. code-block:: python + + dropdown_0.align_to(page_0, lv.ALIGN.CENTER, 0, 0) + + + .. py:method:: get_selected() + + Get the index of the currently selected option. + + :return: The index of the selected option. + :rtype: int + + UiFlow2 Code Block: + + |get_selected.png| + + MicroPython Code Block: + + .. code-block:: python + + selected_index = dropdown_0.get_selected() + + + .. py:method:: set_selected_highlight(enable) + + Enable or disable highlighting of the selected option. + + :param bool enable: True to enable highlighting, False to disable. + :return: None + + UiFlow2 Code Block: + + |set_selected_highlight.png| + + MicroPython Code Block: + + .. code-block:: python + + dropdown_0.set_selected_highlight(True) + + + .. py:method:: get_option_count() + + Clear all options in a drop-down list. + + :return: The number of options in the dropdown. + :rtype: int + + UiFlow2 Code Block: + + |get_option_count.png| + + MicroPython Code Block: + + .. code-block:: python + + option_count = dropdown_0.get_option_count() + + .. py:method:: get_option_index(option) + + Get the index of an option. + + :param str option: The option to find. + :return: The index of the option, or -1 if not found. + :rtype: int + + UiFlow2 Code Block: + + |get_option_index.png| + + MicroPython Code Block: + + .. code-block:: python + + index = dropdown_0.get_option_index("Option 1") + if index != -1: + print(f"Option found at index: {index}") + else: + print("Option not found") + + + .. py:method:: get_text() + + Get text of the drop-down list's button. + + :return: The text of the dropdown button. + :rtype: str + + UiFlow2 Code Block: + + |get_text.png| + + MicroPython Code Block: + + .. code-block:: python + + text = dropdown_0.get_text() + print(f"Dropdown button text: {text}") + + + .. py:method:: set_text(txt) + + Set text of the drop-down list's button. + + :param str txt: The text to set for the dropdown button. + :return: None + + UiFlow2 Code Block: + + |set_text.png| + + MicroPython Code Block: + + .. code-block:: python + + dropdown_0.set_text("Select an option") + + + .. py:method:: add_event_cb(handler, event, user_data) + + Add an event callback to the dropdown. The callback will be called when the specified event occurs. + + :param function handler: The callback function to call. + :param int event: The event to listen for. + :param Any user_data: Optional user data to pass to the callback. + :return: None + + UiFlow2 Code Block: + + |event.png| + + MicroPython Code Block: + + .. code-block:: python + + def dropdown_0_value_changed_event(event_struct): + global dropdown_0 + selected = dropdown_0.get_selected_str() + print(f"Selected: {selected}") + + def dropdown_0_event_handler(event_struct): + global dropdown_0 + event = event_struct.code + if event == lv.EVENT.VALUE_CHANGED: + dropdown_0_value_changed_event(event_struct) + return + + dropdown_0.add_event_cb(dropdown_0_event_handler, lv.EVENT.ALL, None) diff --git a/docs/en/m5ui/index.rst b/docs/en/m5ui/index.rst index c4e3b91e..604e0153 100644 --- a/docs/en/m5ui/index.rst +++ b/docs/en/m5ui/index.rst @@ -39,6 +39,7 @@ Classes calendar.rst canvas.rst checkbox.rst + dropdown.rst image.rst keyboard.rst label.rst diff --git a/docs/en/refs/m5ui.dropdown.ref b/docs/en/refs/m5ui.dropdown.ref new file mode 100644 index 00000000..e3f524ad --- /dev/null +++ b/docs/en/refs/m5ui.dropdown.ref @@ -0,0 +1,38 @@ +.. |cores3_dropdown_directions_example.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/dropdown/cores3_dropdown_directions_example.png + +.. |add_option.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/dropdown/add_option.png +.. |align_to.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/dropdown/align_to.png +.. |clear_options.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/dropdown/clear_options.png +.. |event.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/dropdown/event.png +.. |get_height.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/dropdown/get_height.png +.. |get_option_count.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/dropdown/get_option_count.png +.. |get_option_index.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/dropdown/get_option_index.png +.. |get_options.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/dropdown/get_options.png +.. |get_selected.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/dropdown/get_selected.png +.. |get_selected_str.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/dropdown/get_selected_str.png +.. |get_text.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/dropdown/get_text.png +.. |get_width.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/dropdown/get_width.png +.. |set_default_size.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/dropdown/set_default_size.png +.. |set_dir.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/dropdown/set_dir.png +.. |set_flag.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/dropdown/set_flag.png +.. |set_height.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/dropdown/set_height.png +.. |set_options.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/dropdown/set_options.png +.. |set_pos.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/dropdown/set_pos.png +.. |set_selected_highlight.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/dropdown/set_selected_highlight.png +.. |set_size.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/dropdown/set_size.png +.. |set_state.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/dropdown/set_state.png +.. |set_text.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/dropdown/set_text.png +.. |set_width.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/dropdown/set_width.png +.. |set_x.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/dropdown/set_x.png +.. |set_y.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/dropdown/set_y.png +.. |toggle_flag.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/dropdown/toggle_flag.png +.. |toggle_state.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/dropdown/toggle_state.png + +.. |cores3_dropdown_directions_example.m5f2| raw:: html + + + cores3_dropdown_directions_example.m5f2 + \ No newline at end of file diff --git a/docs/locales/zh_CN/LC_MESSAGES/m5ui/dropdown.po b/docs/locales/zh_CN/LC_MESSAGES/m5ui/dropdown.po new file mode 100644 index 00000000..0ee817fe --- /dev/null +++ b/docs/locales/zh_CN/LC_MESSAGES/m5ui/dropdown.po @@ -0,0 +1,679 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2016 - 2025 M5Stack Technology Co., Ltd +# This file is distributed under the same license as the UIFlow2 Programming +# Guide package. +# FIRST AUTHOR , 2025. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: UIFlow2 Programming Guide \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-08-20 17:56+0800\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: zh_CN\n" +"Language-Team: zh_CN \n" +"Plural-Forms: nplurals=1; plural=0;\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.17.0\n" + +#: ../../en/m5ui/dropdown.rst:4 ../../en/m5ui/dropdown.rst:54 +#: 700ed03fb57b4fb0a2b0d3d29810d5e8 b2cded9f3953424980de56f390e0f348 +msgid "M5Dropdown" +msgstr "" + +#: ../../en/m5ui/dropdown.rst:8 fffe11498bd04125ade4d5cccf8967bd +msgid "" +"M5Dropdown is a widget that can be used to create dropdown menus in the " +"user interface. It allows users to select one option from a list of " +"available options with a compact dropdown interface." +msgstr "M5Dropdown是一个可以在用户界面中创建下拉菜单的控件。它允许用户通过紧凑的下拉界面从可用选项列表中选择一个选项。" + +#: ../../en/m5ui/dropdown.rst:13 cf3cfa01c87044809cbe1dc448adbdbb +msgid "UiFlow2 Example" +msgstr "UiFlow2 示例" + +#: ../../en/m5ui/dropdown.rst:16 ../../en/m5ui/dropdown.rst:35 +#: 3886b03cdafd443a85d233f81f10a3df 5459b6138efb487683e964b6acf94012 +msgid "Drop down in four directions" +msgstr "四个方向的下拉菜单" + +#: ../../en/m5ui/dropdown.rst:18 8222f27a4c6b47d7ad8e6474e9ce51ac +msgid "Open the |cores3_dropdown_directions_example.m5f2| project in UiFlow2." +msgstr "在UiFlow2中打开 |cores3_dropdown_directions_example.m5f2| 项目。" + +#: ../../en/m5ui/dropdown.rst:20 ../../en/m5ui/dropdown.rst:37 +#: 35f181f8a60a43ebb227a4f3d39e162a cd794b6a001041549c55a14f3fd331b9 +msgid "This example creates a drop down, up, left and right menus." +msgstr "此示例创建了向下、向上、向左和向右的下拉菜单。" + +#: ../../en/m5ui/dropdown.rst:22 ../../en/m5ui/dropdown.rst:67 +#: ../../en/m5ui/dropdown.rst:85 ../../en/m5ui/dropdown.rst:104 +#: ../../en/m5ui/dropdown.rst:122 ../../en/m5ui/dropdown.rst:141 +#: ../../en/m5ui/dropdown.rst:159 ../../en/m5ui/dropdown.rst:177 +#: ../../en/m5ui/dropdown.rst:196 ../../en/m5ui/dropdown.rst:214 +#: ../../en/m5ui/dropdown.rst:232 ../../en/m5ui/dropdown.rst:250 +#: ../../en/m5ui/dropdown.rst:268 ../../en/m5ui/dropdown.rst:288 +#: ../../en/m5ui/dropdown.rst:306 ../../en/m5ui/dropdown.rst:324 +#: ../../en/m5ui/dropdown.rst:342 ../../en/m5ui/dropdown.rst:360 +#: ../../en/m5ui/dropdown.rst:382 ../../en/m5ui/dropdown.rst:401 +#: ../../en/m5ui/dropdown.rst:421 0d73842b685346d6a8e10b24661b356c +#: 16e4277f547f4a65a66717d0e743963c 29acf5d45c1d4d46975a7b40a41f6072 +#: 48b9752f06fe42078d41e3497338b65c 52f7b56b4152489c82dfbcbb520ce6a3 +#: 5cf80b8063154cea938062b64312e87c 5fdca7c5887c4e0d8d72fba7a0a73607 +#: 6cca309ef29145e3856f1841c2a6bd4c 8277c647c370475abeb1b646dad0f18d +#: 858b367bac5a4513b60cfda537143805 c0185dd3753745e180b2681bb495eca4 +#: c848de98228f42cfa0a4713a9e9db7bc db9585667ba24503b20ccb666d896bdf +#: dbfc4a55285f487588be828ac4d1ad12 dd6415cd5166413d814be82f6cf515cd +#: de805c719fb64fe3bd49459edf0bbedf e164dca07eb24c5bb8b88c9e4408c4db +#: ebc9ba334e634262be14fe565c1dc5fb m5ui.dropdown.M5Dropdown.add_option:6 +#: m5ui.dropdown.M5Dropdown.clear_options:3 +#: m5ui.dropdown.M5Dropdown.get_selected_str:5 +#: m5ui.dropdown.M5Dropdown.set_dir:5 m5ui.dropdown.M5Dropdown.set_options:5 +#: m5ui.dropdown.M5Dropdown.set_style_radius:6 of +msgid "UiFlow2 Code Block:" +msgstr "UiFlow2 代码块:" + +#: ../../en/m5ui/dropdown.rst:24 5a10b62ec7f94c1f86f303bf696810e4 +msgid "|cores3_dropdown_directions_example.png|" +msgstr "" + +#: ../../en/refs/m5ui.dropdown.ref:1 10cee414ff374e399b73ed64ee93b863 +msgid "cores3_dropdown_directions_example.png" +msgstr "" + +#: ../../en/m5ui/dropdown.rst:26 ../../en/m5ui/dropdown.rst:45 +#: 7807d83367b6492aa4e77ea91f25541f e55620a123fb4eeb9110a4af8914ddee +msgid "Example output:" +msgstr "示例输出:" + +#: ../../en/m5ui/dropdown.rst:28 ../../en/m5ui/dropdown.rst:47 +#: ../../en/m5ui/dropdown.rst:65 ../../en/m5ui/dropdown.rst:83 +#: ../../en/m5ui/dropdown.rst:102 ../../en/m5ui/dropdown.rst:120 +#: ../../en/m5ui/dropdown.rst:139 ../../en/m5ui/dropdown.rst:157 +#: ../../en/m5ui/dropdown.rst:175 ../../en/m5ui/dropdown.rst:194 +#: ../../en/m5ui/dropdown.rst:212 ../../en/m5ui/dropdown.rst:248 +#: ../../en/m5ui/dropdown.rst:286 ../../en/m5ui/dropdown.rst:322 +#: ../../en/m5ui/dropdown.rst:399 ../../en/m5ui/dropdown.rst:419 +#: 129fba3f0c91499e84aa9ccc4d06e768 3202bac59af04324a52b954692ab32d4 +#: 3cafbaa29c1a4847ba6572cc056a3569 5434ab5ace5b4de3977fc2a8debf83c2 +#: 54e31901493143adb4ec8308051c562d 5fe1f197f157421aa9bfaba561c1e964 +#: 67d65ad26d084ff0b300ea5f5feefa80 6887f4306e834f9884425ae99d50a2b3 +#: 8b828bfb0e084dd3b19a3284574223e4 8f7caceab66b4ce78878a303c85f5c20 +#: 96391d4f8dfc4d81b14f8b363e6cc73e 966ea41c1743464ab00bc19017240230 +#: bd6d94c99f6d4692a20c8dc31b57557b d8542bd7d41142e8a69fbe49e150e364 +#: e12de8d516de4e3d8ed9033396eacdbc ee6dd49ec10646c18777962be81c987f +#: m5ui.dropdown.M5Dropdown.set_style_radius:8 of +msgid "None" +msgstr "" + +#: ../../en/m5ui/dropdown.rst:32 1c3eff7b49e9417f8b097dee3c32b53a +msgid "MicroPython Example" +msgstr "MicroPython 示例" + +#: ../../en/m5ui/dropdown.rst:39 ../../en/m5ui/dropdown.rst:71 +#: ../../en/m5ui/dropdown.rst:89 ../../en/m5ui/dropdown.rst:108 +#: ../../en/m5ui/dropdown.rst:126 ../../en/m5ui/dropdown.rst:145 +#: ../../en/m5ui/dropdown.rst:163 ../../en/m5ui/dropdown.rst:181 +#: ../../en/m5ui/dropdown.rst:200 ../../en/m5ui/dropdown.rst:218 +#: ../../en/m5ui/dropdown.rst:236 ../../en/m5ui/dropdown.rst:254 +#: ../../en/m5ui/dropdown.rst:272 ../../en/m5ui/dropdown.rst:292 +#: ../../en/m5ui/dropdown.rst:310 ../../en/m5ui/dropdown.rst:328 +#: ../../en/m5ui/dropdown.rst:346 ../../en/m5ui/dropdown.rst:364 +#: ../../en/m5ui/dropdown.rst:386 ../../en/m5ui/dropdown.rst:405 +#: ../../en/m5ui/dropdown.rst:425 0ae30fe16f664d28a744ca80bdd345ea +#: 11a7d99ac06d409eb152327e23b96243 21c20c90b7d648af9bbdccd72ccaf7ec +#: 369614e2d7674d6390b30f98d3cefbb8 408d5e78b0fd4c7a9e1b082afd895d65 +#: 4355c555bd4f46d3983c51697fcaeadf 44212750369945d89ed5df2bb2fcb416 +#: 4f852af2dda942729907111218d9d663 536b00697b9444d4859c23923657c4ba +#: 5c5f486a6dfe4faf9b36872cd1a9d36a 746ed8f36b33424788a7dfe977eaceb1 +#: 838ea220a25c4dc993dbfb706bfb245a 9fc8887ebb874326a10e837824355dd0 +#: a641aac94b084d588e84c178ac809809 b6eb7865c5ee48a69680efa4889ebe6a +#: cdd17db52c8346dd923c261018bf3da2 da2eb3e720dc4d68b17200c40707e118 +#: f99e22e4aa814e69b1a9b056f7e0f330 m5ui.dropdown.M5Dropdown.add_option:10 +#: m5ui.dropdown.M5Dropdown.clear_options:7 +#: m5ui.dropdown.M5Dropdown.get_selected_str:9 +#: m5ui.dropdown.M5Dropdown.set_dir:9 m5ui.dropdown.M5Dropdown.set_options:9 +#: m5ui.dropdown.M5Dropdown.set_style_radius:10 of +msgid "MicroPython Code Block:" +msgstr "MicroPython 代码块:" + +#: ../../en/m5ui/dropdown.rst:51 831d62cfc1e44026bcce175bc47967ac +msgid "**API**" +msgstr "API参考" + +#: c5973e17fd3f40ffa60a28a5e13932e1 m5ui.dropdown.M5Dropdown:1 of +msgid "Bases: :py:class:`~lvgl.dropdown`" +msgstr "" + +#: ea2d79e26bd145c9a80f9c172754fb58 m5ui.dropdown.M5Dropdown:1 of +msgid "Create a dropdown object." +msgstr "创建一个下拉菜单对象。" + +#: ../../en/m5ui/dropdown.rst 17648017208941ee9eeec8e73df9dca9 +#: 1e6306b1c0bd452ab60e8ca8129d20fb 1ff356355ced4aaf834204d2d4c79b7f +#: 2a923c817bb74a2c94b2c45aae83d11f 43e2393a79f349b897d0487733c411a9 +#: 561675051db745bbb9feebdaa40df724 6ed168a09d884a38b312da1b99b83a89 +#: 7fd5e6f9a6dd42d4829f84138ee5134b a1e4e23501a04df3a5896e5bf2a6eb87 +#: d213d5a0a34c4d9887588d9ef8c1fbf2 d5bb6c804e6e4236ac3c1f9e20d6ee02 +#: d5f3876c58694005b64569e13f55f418 dc5fee1c5a3c432e84ee8738f5ab72aa +#: e9aed0fc5de2423c9e424c070202768d m5ui.dropdown.M5Dropdown.add_option +#: m5ui.dropdown.M5Dropdown.set_dir m5ui.dropdown.M5Dropdown.set_options +#: m5ui.dropdown.M5Dropdown.set_style_radius of +msgid "Parameters" +msgstr "" + +#: 2256b30bf131468483ae405dd8d3d0fd m5ui.dropdown.M5Dropdown:3 of +msgid "The x position of the dropdown." +msgstr "下拉菜单的X坐标位置。" + +#: 7a036026c0e04c71acf86c929b1ce0bf m5ui.dropdown.M5Dropdown:4 of +msgid "The y position of the dropdown." +msgstr "下拉菜单的Y坐标位置。" + +#: ../../en/m5ui/dropdown.rst:192 ../../en/m5ui/dropdown.rst:211 +#: ../../en/m5ui/dropdown.rst:229 2c758c4ba24d4d029c96e68e0924bb6a +#: 4fcca57abdc846329cdf8fe2f7d242eb 670ffd0f57874feb9d281e70d4ee9712 +#: b4bbdcfa5cc2493a95e746c58c613681 m5ui.dropdown.M5Dropdown:5 of +msgid "The width of the dropdown." +msgstr "下拉菜单的宽度。" + +#: 64a7ee94ba954f95b17c4e22d24e8dcf m5ui.dropdown.M5Dropdown:6 of +msgid "The height of the dropdown, default is `lv.SIZE_CONTENT`." +msgstr "下拉菜单的高度,默认为 `lv.SIZE_CONTENT`。" + +#: 2a3c6cda04474856b29366e2a2efca24 c37130b4d7c84ff38a22261a1aceade5 +#: m5ui.dropdown.M5Dropdown:7 m5ui.dropdown.M5Dropdown.set_options:3 of +msgid "A list of options to display in the dropdown." +msgstr "在下拉菜单中显示的选项列表。" + +#: 2496650eab27426795554178efb26347 985e2f280f204d6cba4dc7ac407de5c5 +#: m5ui.dropdown.M5Dropdown:8 m5ui.dropdown.M5Dropdown.set_dir:3 of +msgid "" +"The direction of the dropdown, can be `lv.DIR.LEFT`, `lv.DIR.RIGHT`, " +"`lv.DIR.TOP`, or `lv.DIR.BOTTOM`." +msgstr "下拉菜单的方向,可以是 `lv.DIR.LEFT`、`lv.DIR.RIGHT`、`lv.DIR.TOP` 或 `lv.DIR.BOTTOM`。" + +#: 377492f603494f009b0da772ba47b661 m5ui.dropdown.M5Dropdown:9 of +msgid "Whether to highlight the selected option, default is `True`." +msgstr "是否高亮显示选中的选项,默认为 `True`。" + +#: f5f4bc4e3647482c95a540489688d844 m5ui.dropdown.M5Dropdown:10 of +msgid "" +"The font used for the text in the dropdown, default is " +"`lv.font_montserrat_14`." +msgstr "下拉菜单中文本使用的字体,默认为 `lv.font_montserrat_14`。" + +#: 7376aa9aab8b4eeb920d4c535b34e5f0 m5ui.dropdown.M5Dropdown:11 of +msgid "The parent object for this dropdown, default is the active screen." +msgstr "此下拉菜单的父对象,默认为当前活动屏幕。" + +#: ../../en/m5ui/dropdown.rst:61 e0d3d184d75247118e8b6cd179e31c0b +msgid "" +"Set a flag on the object. If ``value`` is True, the flag is added; if " +"False, the flag is removed." +msgstr "为对象设置标志。如果 ``value`` 为True,则添加标志;如果为False,则移除标志。" + +#: ../../en/m5ui/dropdown.rst:63 275ee2eaaaa043ea8a35162df2c39558 +msgid "The flag to set." +msgstr "要设置的标志。" + +#: ../../en/m5ui/dropdown.rst:64 a88ca972572847cf9f5a1b99d83b2aaf +msgid "If True, the flag is added; if False, the flag is removed." +msgstr "如果为True,则添加标志;如果为False,则移除标志。" + +#: ../../en/m5ui/dropdown.rst 125938408c7b4fdbb8cf287ad0d4108d +#: 2f0a96fc95824359a81266371c240e6f 35c91f8b81c448bb9d16b6ce5220107b +#: 3858a3c7bab24bc7a9ddde13f4b9dfc4 3e34a3b4758749a28c7a5e64e9eaf01d +#: 4070e1242bc14538b8120c9b6a783be6 665eef8bf3f546a186af3e9bd7de189f +#: 72ae8463de7d4bcba62636d6982f1135 74a1ddaf2ffb41ccabbfd66ea0ef3294 +#: 9ddcb459d01a4488a6a7f8e08b6b65b3 b3cba1bce3204ff78d0cacfd92d83b45 +#: cae790d2044d491e80072891e27a5f89 ced2cef411144241b2e59238edd81a8c +#: d065be4a997c4cff9077196fbc69b53d d6b87f8abbd84e249c8d196103badbb2 +#: eccb37e031f34e299aa0aeb79d9f2f1f f9d45a23f563465b964209f90e866a10 +#: m5ui.dropdown.M5Dropdown.get_selected_str of +msgid "Returns" +msgstr "" + +#: ../../en/m5ui/dropdown.rst:69 1fda9c63a269413a9fd4f17fb4ee329e +msgid "|set_flag.png|" +msgstr "" + +#: ../../en/refs/m5ui.dropdown.ref:17 ff2149c1905b44c187991e0b704c57c1 +msgid "set_flag.png" +msgstr "" + +#: ../../en/m5ui/dropdown.rst:80 6fa69730b37343a99d1dd2ece537d7ff +msgid "" +"Toggle a flag on the object. If the flag is set, it is removed; if not " +"set, it is added." +msgstr "切换对象的标志。如果标志已设置,则将其移除;如果未设置,则将其添加。" + +#: ../../en/m5ui/dropdown.rst:82 a8436bbb3098486ca83c4f18e9c995c7 +msgid "The flag to toggle." +msgstr "要切换的标志。" + +#: ../../en/m5ui/dropdown.rst:87 b76eefa76bc245659a53d1ca638b8266 +msgid "|toggle_flag.png|" +msgstr "" + +#: ../../en/refs/m5ui.dropdown.ref:28 966f6ec7c2594051b12f175962d94a80 +msgid "toggle_flag.png" +msgstr "" + +#: ../../en/m5ui/dropdown.rst:98 c5e7354831554a46a51a1e15a95f2d47 +msgid "" +"Set the state of the dropdown. If ``value`` is True, the state is set; if" +" False, the state is unset." +msgstr "设置下拉菜单的状态。如果 ``value`` 为True,则设置状态;如果为False,则取消设置状态。" + +#: ../../en/m5ui/dropdown.rst:100 282e37a407ab48f8adaef3de3a878b80 +msgid "The state to set." +msgstr "要设置的状态。" + +#: ../../en/m5ui/dropdown.rst:101 a6edef7c051d4587b2eb07f775a9ed4c +msgid "If True, the state is set; if False, the state is unset." +msgstr "如果为True,则设置状态;如果为False,则取消设置状态。" + +#: ../../en/m5ui/dropdown.rst:106 7fb46e4f06f549f3a8642c3975a1b722 +msgid "|set_state.png|" +msgstr "" + +#: ../../en/refs/m5ui.dropdown.ref:23 45cd12e4ef994143ab2639f14a8db0dd +msgid "set_state.png" +msgstr "" + +#: ../../en/m5ui/dropdown.rst:117 071381a0a8a444ffbd590ea21aa1b5c8 +msgid "" +"Toggle the state of the dropdown. If the state is set, it is unset; if " +"not set, it is set." +msgstr "" + +#: ../../en/m5ui/dropdown.rst:119 c65dda359783473a88578741d675a96c +msgid "The state to toggle." +msgstr "" + +#: ../../en/m5ui/dropdown.rst:124 5b007f4ea9984926a0285f3c3964295b +msgid "|toggle_state.png|" +msgstr "" + +#: ../../en/refs/m5ui.dropdown.ref:29 dde8f685922b4e53847d2d51627cb9d5 +msgid "toggle_state.png" +msgstr "" + +#: ../../en/m5ui/dropdown.rst:135 6322b879264c4593848384e7abe008cf +msgid "Set the position of the dropdown." +msgstr "设置下拉菜单的位置。" + +#: ../../en/m5ui/dropdown.rst:137 ../../en/m5ui/dropdown.rst:156 +#: 3986879235db4f91a6e84c3d02b9179f f763f120562f49ff818562acce0c7e11 +msgid "The x-coordinate of the dropdown." +msgstr "下拉菜单的X坐标。" + +#: ../../en/m5ui/dropdown.rst:138 ../../en/m5ui/dropdown.rst:174 +#: 1df54d03bf3e451ba54e7f083ecb3576 d902eb9eec8f4c1ab2448904eeb30f74 +msgid "The y-coordinate of the dropdown." +msgstr "下拉菜单的Y坐标。" + +#: ../../en/m5ui/dropdown.rst:143 7c9b3907113e40a48b78f195e7134b16 +msgid "|set_pos.png|" +msgstr "" + +#: ../../en/refs/m5ui.dropdown.ref:20 2eb381d050ea4d728cd1db90026749c8 +msgid "set_pos.png" +msgstr "" + +#: ../../en/m5ui/dropdown.rst:154 3a91cd2b7ae44d54984ba0a3ef23dc92 +msgid "Set the x-coordinate of the dropdown." +msgstr "设置下拉菜单的X坐标。" + +#: ../../en/m5ui/dropdown.rst:161 a4fa801d99864e5ab27111d311e5f134 +msgid "|set_x.png|" +msgstr "" + +#: ../../en/refs/m5ui.dropdown.ref:26 33f88bd0fbfa4a3ca001412c2e279822 +msgid "set_x.png" +msgstr "" + +#: ../../en/m5ui/dropdown.rst:172 e758f80ab6c544f4a7481a463c23497f +msgid "Set the y-coordinate of the dropdown." +msgstr "设置下拉菜单的Y坐标。" + +#: ../../en/m5ui/dropdown.rst:179 323298fe855e426bb999c5adc700599b +msgid "|set_y.png|" +msgstr "" + +#: ../../en/refs/m5ui.dropdown.ref:27 39bbeb192a4440d0b919f1c50088b37a +msgid "set_y.png" +msgstr "" + +#: ../../en/m5ui/dropdown.rst:190 5c5878b62d474e719a05b16c8c76e0ae +msgid "Set the size of the dropdown." +msgstr "设置下拉菜单的尺寸。" + +#: ../../en/m5ui/dropdown.rst:193 ../../en/m5ui/dropdown.rst:247 +#: ../../en/m5ui/dropdown.rst:265 439b62cb69fa4f2c826eaa9431219643 +#: 44932e7f3c594aaaa34115cd83df4536 fd9d62cec308482a9013e094a2e5d1bb +msgid "The height of the dropdown." +msgstr "下拉菜单的高度。" + +#: ../../en/m5ui/dropdown.rst:198 6cb114d806524e84827477d5593b82af +msgid "|set_size.png|" +msgstr "" + +#: ../../en/refs/m5ui.dropdown.ref:22 68bdada9a8f94a958e3b1ba625cdaeed +msgid "set_size.png" +msgstr "" + +#: ../../en/m5ui/dropdown.rst:209 9f038de1affc4ed487f9fa0137764d47 +msgid "Set the width of the dropdown." +msgstr "设置下拉菜单的宽度。" + +#: ../../en/m5ui/dropdown.rst:216 91f97c5882dc44dda50ec28b5bd003bb +msgid "|set_width.png|" +msgstr "" + +#: ../../en/refs/m5ui.dropdown.ref:25 942ed7884ccb49fbab786abaec103585 +msgid "set_width.png" +msgstr "" + +#: ../../en/m5ui/dropdown.rst:227 1ff1cbbed2754663b43f0afb72a513ef +msgid "Get the width of the dropdown." +msgstr "获取下拉菜单的宽度。" + +#: ../../en/m5ui/dropdown.rst 474ef770dea841d28199b6e57b09fbce +#: 564d7365240e419d82857c7ba2d73d9a 81097c1f5105480db967b27e6f0e22a8 +#: 9a081cbc8da7439198671a614e30d126 m5ui.dropdown.M5Dropdown.add_option +#: m5ui.dropdown.M5Dropdown.clear_options +#: m5ui.dropdown.M5Dropdown.get_selected_str m5ui.dropdown.M5Dropdown.set_dir +#: m5ui.dropdown.M5Dropdown.set_style_radius of +msgid "Return type" +msgstr "" + +#: ../../en/m5ui/dropdown.rst:234 1afac0eacba14e5e86cb7e2813c94298 +msgid "|get_width.png|" +msgstr "" + +#: ../../en/refs/m5ui.dropdown.ref:14 7293cfab2eec47c1b3079ef5abf027f7 +msgid "get_width.png" +msgstr "" + +#: ../../en/m5ui/dropdown.rst:245 f93a63472b034507a60842c4941bb852 +msgid "Set the height of the dropdown." +msgstr "设置下拉菜单的高度。" + +#: ../../en/m5ui/dropdown.rst:252 c371bc57641447be8d5e5a3b543da392 +msgid "|set_height.png|" +msgstr "" + +#: ../../en/refs/m5ui.dropdown.ref:18 35e35198f0754bbb8d87bbb0c8c08621 +msgid "set_height.png" +msgstr "" + +#: ../../en/m5ui/dropdown.rst:263 ac0ee39436ec4c42965a6fefc3700319 +msgid "Get the height of the dropdown." +msgstr "获取下拉菜单的高度。" + +#: ../../en/m5ui/dropdown.rst:270 110ac075851f463f9589f9ecb4dcc0c8 +msgid "|get_height.png|" +msgstr "" + +#: ../../en/refs/m5ui.dropdown.ref:7 5db0e658df6e458095e1b2b1db1b1bce +msgid "get_height.png" +msgstr "" + +#: ../../en/m5ui/dropdown.rst:280 bccf1adf1fa5494a97d0a185a20cda17 +msgid "Align the dropdown to another object." +msgstr "将下拉菜单对齐到另一个对象。" + +#: ../../en/m5ui/dropdown.rst:282 32303bb61a2f4296a565e570ccf33d24 +msgid "The object to align to." +msgstr "要对齐到的对象。" + +#: ../../en/m5ui/dropdown.rst:283 7e9b3c4df5854849b7767d46123f193a +msgid "The alignment type." +msgstr "对齐类型。" + +#: ../../en/m5ui/dropdown.rst:284 99a62c8d664a4442a05f795663074592 +msgid "The x-offset from the aligned object." +msgstr "相对于对齐对象的X偏移量。" + +#: ../../en/m5ui/dropdown.rst:285 a3a790f545cb4fd29900b77040af278b +msgid "The y-offset from the aligned object." +msgstr "相对于对齐对象的Y偏移量。" + +#: ../../en/m5ui/dropdown.rst:290 50e3ff970fc84123ac44279b1cdafab6 +msgid "|align_to.png|" +msgstr "" + +#: ../../en/refs/m5ui.dropdown.ref:4 8e07ae2843ba4470966fcfbf22def0df +msgid "align_to.png" +msgstr "" + +#: ../../en/m5ui/dropdown.rst:301 e4996e3bd3d44a48a0be6befe3bd33ab +msgid "Get the index of the currently selected option." +msgstr "获取当前选中选项的索引。" + +#: ../../en/m5ui/dropdown.rst:303 2461d21316da4c2bb3b38b98990521b4 +msgid "The index of the selected option." +msgstr "选中选项的索引。" + +#: ../../en/m5ui/dropdown.rst:308 3d83ac96417f443cbf8d9d3aa6726602 +msgid "|get_selected.png|" +msgstr "" + +#: ../../en/refs/m5ui.dropdown.ref:11 9a75ff836c884b439d60b2414f4402ff +msgid "get_selected.png" +msgstr "" + +#: ../../en/m5ui/dropdown.rst:319 a7c9dd03bfdc4fa0b7bbe635222a0f0c +msgid "Enable or disable highlighting of the selected option." +msgstr "启用或禁用选中选项的高亮显示。" + +#: ../../en/m5ui/dropdown.rst:321 e80f1c415cd5456f9e3306527123cf4b +msgid "True to enable highlighting, False to disable." +msgstr "True表示启用高亮显示,False表示禁用。" + +#: ../../en/m5ui/dropdown.rst:326 a5766e79c51e48bea6bfd4c77b69e975 +msgid "|set_selected_highlight.png|" +msgstr "" + +#: ../../en/refs/m5ui.dropdown.ref:21 8ec98d6e1da344d28155b4d75ffddec6 +msgid "set_selected_highlight.png" +msgstr "" + +#: ../../en/m5ui/dropdown.rst:337 36858c591926468c8810f262e43128fa +msgid "Clear all options in a drop-down list." +msgstr "" + +#: ../../en/m5ui/dropdown.rst:339 d41f7ef1b01e43f49dacebeb1c7b16ce +msgid "The number of options in the dropdown." +msgstr "下拉菜单中的选项数量。" + +#: ../../en/m5ui/dropdown.rst:344 4f8446661ea54e8da7b634ecc47eb6b1 +msgid "|get_option_count.png|" +msgstr "" + +#: ../../en/refs/m5ui.dropdown.ref:8 5acf63a3011140b1bffda0b916450328 +msgid "get_option_count.png" +msgstr "" + +#: ../../en/m5ui/dropdown.rst:354 338c769b472f4ddfa4aca737da0bbb47 +msgid "Get the index of an option." +msgstr "获取选项的索引。" + +#: ../../en/m5ui/dropdown.rst:356 61ce09b747cf42c1811919f494919ff1 +msgid "The option to find." +msgstr "要查找的选项。" + +#: ../../en/m5ui/dropdown.rst:357 60f10232137c4298aa19eaeb0cdc3a48 +msgid "The index of the option, or -1 if not found." +msgstr "选项的索引,如果未找到则返回-1。" + +#: ../../en/m5ui/dropdown.rst:362 82f7c1cb31ae4259ac1ba03d9348583b +msgid "|get_option_index.png|" +msgstr "" + +#: ../../en/refs/m5ui.dropdown.ref:9 11035d5f45954fb490cb79b828d3b47c +msgid "get_option_index.png" +msgstr "" + +#: ../../en/m5ui/dropdown.rst:377 224d7ab3416f4ee7b457e0e402dea9f7 +msgid "Get text of the drop-down list's button." +msgstr "获取下拉列表按钮的文本。" + +#: ../../en/m5ui/dropdown.rst:379 330781e93c684698ad7763f058302280 +msgid "The text of the dropdown button." +msgstr "下拉菜单按钮的文本。" + +#: ../../en/m5ui/dropdown.rst:384 14d3bfd80124446f9e9a2bd9821fa2f9 +msgid "|get_text.png|" +msgstr "" + +#: ../../en/refs/m5ui.dropdown.ref:13 f2aad20fc8884ebf8d69a3b42faa7861 +msgid "get_text.png" +msgstr "" + +#: ../../en/m5ui/dropdown.rst:396 73922f8b9df64159b6f8eab98983bb34 +msgid "Set text of the drop-down list's button." +msgstr "设置下拉列表按钮的文本。" + +#: ../../en/m5ui/dropdown.rst:398 22c3546a1424404e943fe50c983e5984 +msgid "The text to set for the dropdown button." +msgstr "为下拉按钮设置的文本。" + +#: ../../en/m5ui/dropdown.rst:403 f73929b35e9e4aa997ef451585055b9a +msgid "|set_text.png|" +msgstr "" + +#: ../../en/refs/m5ui.dropdown.ref:24 6e54502bcd424a00bce19eaa78a3ab85 +msgid "set_text.png" +msgstr "" + +#: ../../en/m5ui/dropdown.rst:414 3d6a88cfc35847a994a4d00e843c4c8d +msgid "" +"Add an event callback to the dropdown. The callback will be called when " +"the specified event occurs." +msgstr "为下拉菜单添加事件回调。当指定事件发生时将调用回调函数。" + +#: ../../en/m5ui/dropdown.rst:416 f98d9f64457b4de8a30ca7779da852dc +msgid "The callback function to call." +msgstr "要调用的回调函数。" + +#: ../../en/m5ui/dropdown.rst:417 d1b40fbc8e824a44a81a9d28359a7cac +msgid "The event to listen for." +msgstr "要监听的事件。" + +#: ../../en/m5ui/dropdown.rst:418 d5bcaa98c0c34fd1bb1f11f21c62ee17 +msgid "Optional user data to pass to the callback." +msgstr "要传递给回调函数的可选用户数据。" + +#: ../../en/m5ui/dropdown.rst:423 ad5e46b00c8f45b9a148e66db7ce66a8 +msgid "|event.png|" +msgstr "" + +#: ../../en/refs/m5ui.dropdown.ref:6 6044d1d1d2f040d9b290d630362d88e2 +msgid "event.png" +msgstr "" + +#: 22c3546a1424404e943fe50c983e5984 m5ui.dropdown.M5Dropdown.set_options:1 of +msgid "Set the options for the dropdown." +msgstr "设置下拉菜单的选项。" + +#: 5e5f40db66b94996b58f78217dd3f8c5 m5ui.dropdown.M5Dropdown.set_options:7 of +msgid "|set_options.png|" +msgstr "" + +#: ../../en/refs/m5ui.dropdown.ref:19 87a40a190ef5409497a3b867f4461d65 +msgid "set_options.png" +msgstr "" + +#: e2a1c59745bf4a72a981db82fd07b3d5 m5ui.dropdown.M5Dropdown.get_options:1 of +msgid "Get the list of options in the dropdown." +msgstr "获取下拉菜单中的选项列表。" + +#: 8b77727f0f4043a6bcfe4352cae1f682 m5ui.dropdown.M5Dropdown.get_options:3 of +msgid "The list of options." +msgstr "选项列表。" + +#: 51377ae99f594f29b525f95074e2bdd1 m5ui.dropdown.M5Dropdown.add_option:1 of +msgid "Add an option to the dropdown at a specific position." +msgstr "在特定位置的下拉菜单中添加一个选项。" + +#: 8abf1947fc8c457c90b8b4a7e9c5a6f1 m5ui.dropdown.M5Dropdown.add_option:3 of +msgid "The option to add." +msgstr "要添加的选项。" + +#: 692d0db9d3ac444f93a788ad0431b582 m5ui.dropdown.M5Dropdown.add_option:4 of +msgid "The position to insert the option at." +msgstr "要插入选项的位置。" + +#: 328943f02eb44498bd93d68813a681e4 m5ui.dropdown.M5Dropdown.add_option:8 of +msgid "|add_option.png|" +msgstr "" + +#: ../../en/refs/m5ui.dropdown.ref:3 8ec5dbbb8b2041a1b1309cc8b518fea5 +msgid "add_option.png" +msgstr "" + +#: 6a4323ba9c8043dc99944d15b791174a m5ui.dropdown.M5Dropdown.clear_options:1 of +msgid "Clear all options in the dropdown." +msgstr "清空下拉菜单中的所有选项。" + +#: b5395903259b41f197506f07f21e3bc4 m5ui.dropdown.M5Dropdown.clear_options:5 of +msgid "|clear_options.png|" +msgstr "" + +#: ../../en/refs/m5ui.dropdown.ref:5 ac4daea94abc431fb62d371e2527583e +msgid "clear_options.png" +msgstr "" + +#: 0d59a966cbd342579c74ca55f0987e1b m5ui.dropdown.M5Dropdown.get_selected_str:1 +#: of +msgid "Get the currently selected option as a string." +msgstr "以字符串形式获取当前选中的选项。" + +#: 59d26dd07e884368b07e5fd80cd669b3 m5ui.dropdown.M5Dropdown.get_selected_str:3 +#: of +msgid "The selected option as a string." +msgstr "选中的选项(字符串形式)。" + +#: 706b90f72ea34ea69b5351e11e65ab02 m5ui.dropdown.M5Dropdown.get_selected_str:7 +#: of +msgid "|get_selected_str.png|" +msgstr "" + +#: ../../en/refs/m5ui.dropdown.ref:12 6d26f1a5a28743548af9c44aee1c2ec9 +msgid "get_selected_str.png" +msgstr "" + +#: 4349f6022c194b9eb659bc584d8a8a59 m5ui.dropdown.M5Dropdown.set_dir:1 of +msgid "Set the direction of the dropdown." +msgstr "设置下拉菜单的方向。" + +#: f73929b35e9e4aa997ef451585055b9a m5ui.dropdown.M5Dropdown.set_dir:7 of +msgid "|set_dir.png|" +msgstr "" + +#: ../../en/refs/m5ui.dropdown.ref:16 6e54502bcd424a00bce19eaa78a3ab85 +msgid "set_dir.png" +msgstr "" + +#: 7b2c09f862be4674aa37a2075a76815f m5ui.dropdown.M5Dropdown.set_style_radius:1 +#: of +msgid "Set the radius of the dropdown's corners." +msgstr "设置下拉菜单角的半径。" + +#: 07b9a99ba6d5465c81b82abe60d58ab4 m5ui.dropdown.M5Dropdown.set_style_radius:3 +#: of +msgid "The radius of the corners in pixels." +msgstr "角的半径(以像素为单位)。" + +#: 549549766913468f816dc935b87a2794 m5ui.dropdown.M5Dropdown.set_style_radius:4 +#: of +msgid "The part of the dropdown to apply the radius to, e.g., `lv.PART.MAIN`." +msgstr "下拉菜单中应用半径的部分,例如“lv.PART.MAIN”。" + diff --git a/examples/m5ui/dropdown/cores3_dropdown_directions_example.m5f2 b/examples/m5ui/dropdown/cores3_dropdown_directions_example.m5f2 new file mode 100644 index 00000000..5b62fd82 --- /dev/null +++ b/examples/m5ui/dropdown/cores3_dropdown_directions_example.m5f2 @@ -0,0 +1 @@ +{"version":"V2.0","versionNumber":"V2.3.3","type":"cores3","components":[{"name":"page0","type":"lvgl_page","layer":0,"screenId":"builtin","screenName":"","id":"iU8PZP8Fv3%xRxYn","createTime":1755588261142,"backgroundColor":"#ffffff","isLVGL":true,"isSelected":true},{"name":"dropdown0","type":"lvgl_dropdown","layer":1,"screenId":"builtin","screenName":"","id":"lj2Db3zrFHVUPsFM","createTime":1755588273598,"x":110,"y":0,"width":100,"options":["option1","option2"],"direction":"BOTTOM","font":"lv.font_montserrat_14","showSelected":true,"pageId":"iU8PZP8Fv3%xRxYn","isLVGL":true,"isSelected":false,"height":27},{"name":"dropdown1","type":"lvgl_dropdown","layer":2,"screenId":"builtin","screenName":"","id":"xF^ThmMPWFH+Wgy!","createTime":1755588281645,"x":111,"y":212,"width":100,"options":["option1","option2"],"direction":"TOP","font":"lv.font_montserrat_14","showSelected":true,"pageId":"iU8PZP8Fv3%xRxYn","isLVGL":true,"isSelected":false,"height":27},{"name":"dropdown2","type":"lvgl_dropdown","layer":3,"screenId":"builtin","screenName":"","id":"fyqh&3D4Uq8VJPEi","createTime":1755588289755,"x":220,"y":106,"width":100,"options":["option1","option2"],"direction":"LEFT","font":"lv.font_montserrat_14","showSelected":true,"pageId":"iU8PZP8Fv3%xRxYn","isLVGL":true,"isSelected":false,"height":27},{"name":"dropdown3","type":"lvgl_dropdown","layer":4,"screenId":"builtin","screenName":"","id":"l_iM-Uiv8_*YWo=&","createTime":1755588316854,"x":0,"y":106,"width":100,"options":["option1","option2"],"direction":"RIGHT","font":"lv.font_montserrat_14","showSelected":true,"pageId":"iU8PZP8Fv3%xRxYn","isLVGL":true,"isSelected":false,"height":27}],"resources":[{"hardware":["hardware_button","hardware_pin_button","imu","speaker","touch","als","mic","sdcard"]}],"units":[],"hats":[],"bases":[],"i2cs":[],"blockly":"truepage0dropdown0Truedropdown1Truedropdown2Truedropdown3Truetrue","screen":[{"simulationName":"Built-in","type":"builtin","width":320,"height":240,"scale":0.77,"screenName":"","blockId":"","screenColorType":0,"rotation":1,"id":"builtin","createTime":1755588261141}],"logicWhenNum":0,"customList":[]} \ No newline at end of file diff --git a/examples/m5ui/dropdown/cores3_dropdown_directions_example.py b/examples/m5ui/dropdown/cores3_dropdown_directions_example.py new file mode 100644 index 00000000..945c6ac7 --- /dev/null +++ b/examples/m5ui/dropdown/cores3_dropdown_directions_example.py @@ -0,0 +1,95 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import os, sys, io +import M5 +from M5 import * +import m5ui +import lvgl as lv + + +page0 = None +dropdown0 = None +dropdown1 = None +dropdown2 = None +dropdown3 = None + + +def setup(): + global page0, dropdown0, dropdown1, dropdown2, dropdown3 + + M5.begin() + Widgets.setRotation(1) + m5ui.init() + page0 = m5ui.M5Page(bg_c=0xFFFFFF) + dropdown0 = m5ui.M5Dropdown( + x=110, + y=0, + w=100, + h=lv.SIZE_CONTENT, + options=["option1", "option2"], + direction=lv.DIR.BOTTOM, + show_selected=True, + font=lv.font_montserrat_14, + parent=page0, + ) + dropdown1 = m5ui.M5Dropdown( + x=111, + y=212, + w=100, + h=lv.SIZE_CONTENT, + options=["option1", "option2"], + direction=lv.DIR.TOP, + show_selected=True, + font=lv.font_montserrat_14, + parent=page0, + ) + dropdown2 = m5ui.M5Dropdown( + x=220, + y=106, + w=100, + h=lv.SIZE_CONTENT, + options=["option1", "option2"], + direction=lv.DIR.LEFT, + show_selected=True, + font=lv.font_montserrat_14, + parent=page0, + ) + dropdown3 = m5ui.M5Dropdown( + x=0, + y=106, + w=100, + h=lv.SIZE_CONTENT, + options=["option1", "option2"], + direction=lv.DIR.RIGHT, + show_selected=True, + font=lv.font_montserrat_14, + parent=page0, + ) + + page0.screen_load() + dropdown0.set_selected_highlight(True) + dropdown1.set_selected_highlight(True) + dropdown2.set_selected_highlight(True) + dropdown3.set_selected_highlight(True) + + +def loop(): + global page0, dropdown0, dropdown1, dropdown2, dropdown3 + M5.update() + + +if __name__ == "__main__": + try: + setup() + while True: + loop() + except (Exception, KeyboardInterrupt) as e: + try: + m5ui.deinit() + from utility import print_error_msg + + print_error_msg(e) + except ImportError: + print("please update to latest firmware") diff --git a/m5stack/libs/m5ui/__init__.py b/m5stack/libs/m5ui/__init__.py index 63ce84c6..d4c24b37 100644 --- a/m5stack/libs/m5ui/__init__.py +++ b/m5stack/libs/m5ui/__init__.py @@ -11,6 +11,7 @@ "M5Calendar": "calendar", "M5Canvas": "canvas", "M5Checkbox": "checkbox", + "M5Dropdown": "dropdown", "M5Image": "image", "M5Keyboard": "keyboard", "M5Label": "label", diff --git a/m5stack/libs/m5ui/dropdown.py b/m5stack/libs/m5ui/dropdown.py index 4a6ddd86..95a58246 100644 --- a/m5stack/libs/m5ui/dropdown.py +++ b/m5stack/libs/m5ui/dropdown.py @@ -4,9 +4,23 @@ from .base import M5Base import lvgl as lv +import warnings class M5Dropdown(lv.dropdown): + """Create a dropdown object. + + :param x: The x position of the dropdown. + :param y: The y position of the dropdown. + :param w: The width of the dropdown. + :param h: The height of the dropdown, default is `lv.SIZE_CONTENT`. + :param options: A list of options to display in the dropdown. + :param direction: The direction of the dropdown, can be `lv.DIR.LEFT`, `lv.DIR.RIGHT`, `lv.DIR.TOP`, or `lv.DIR.BOTTOM`. + :param show_selected: Whether to highlight the selected option, default is `True`. + :param font: The font used for the text in the dropdown, default is `lv.font_montserrat_14`. + :param parent: The parent object for this dropdown, default is the active screen. + """ + def __init__( self, x=0, @@ -25,6 +39,7 @@ def __init__( self.set_pos(x, y) self.set_size(w, h) + self.options = [] if options: self.set_options(options) @@ -33,33 +48,136 @@ def __init__( self.set_style_text_font(font, lv.PART.MAIN) def set_options(self, options: list): + """Set the options for the dropdown. + + :param options: A list of options to display in the dropdown. + + UiFlow2 Code Block: + + |set_options.png| + + MicroPython Code Block: + + .. code-block:: python + + dropdown_0.set_options(["option1", "option2", "option3"]) + """ if isinstance(options, list): + self.options = options options = "\n".join(options) - super().set_options(options) + super().set_options(options) + else: + warnings.warn("Options must be a list.") + + def get_options(self) -> list: + """Get the list of options in the dropdown. + + :return: The list of options. + :rtype: list + """ + return self.options + + def add_option(self, option: str, pos: int) -> None: + """Add an option to the dropdown at a specific position. + + :param option: The option to add. + :param pos: The position to insert the option at. + + UiFlow2 Code Block: + + |add_option.png| + + MicroPython Code Block: + + .. code-block:: python + + dropdown_0.add_option("New Option", 1) + """ + if pos < 0 or pos > len(self.options): + warnings.warn("Position out of range, appending to the end.") + pos = len(self.options) + self.options.insert(pos, option) + super().add_option(option, pos) + + def clear_options(self) -> None: + """Clear all options in the dropdown. + + UiFlow2 Code Block: + + |clear_options.png| + + MicroPython Code Block: + + .. code-block:: python + + dropdown_0.clear_options() + """ + self.options = [] + super().set_options("") def get_selected_str(self) -> str: + """Get the currently selected option as a string. + + :return: The selected option as a string. + + UiFlow2 Code Block: + + |get_selected_str.png| + + MicroPython Code Block: + + .. code-block:: python + + selected_option = dropdown_0.get_selected_str() + """ sel = bytearray(32) super().get_selected_str(sel, len(sel)) - # 找到第一个空字节的位置,截断字符串 - null_index = sel.find(0) - if null_index != -1: - sel = sel[:null_index] - return sel.decode("utf-8") + return sel.decode("utf-8").rstrip("\x00") + + def set_dir(self, direction: int) -> None: + """Set the direction of the dropdown. + + :param direction: The direction of the dropdown, can be `lv.DIR.LEFT`, `lv.DIR.RIGHT`, `lv.DIR.TOP`, or `lv.DIR.BOTTOM`. - def set_dir(self, direction: int): + UiFlow2 Code Block: + + |set_dir.png| + + MicroPython Code Block: + + .. code-block:: python + + dropdown_0.set_dir(lv.DIR.LEFT) + """ super().set_dir(direction) if direction == lv.DIR.LEFT: self.set_symbol(lv.SYMBOL.LEFT) elif direction == lv.DIR.RIGHT: self.set_symbol(lv.SYMBOL.RIGHT) - elif direction == lv.DIR.UP: + elif direction == lv.DIR.TOP: self.set_symbol(lv.SYMBOL.UP) - elif direction == lv.DIR.DOWN: + elif direction == lv.DIR.BOTTOM: self.set_symbol(lv.SYMBOL.DOWN) def set_style_radius(self, radius: int, part: int) -> None: + """Set the radius of the dropdown's corners. + + :param radius: The radius of the corners in pixels. + :param part: The part of the dropdown to apply the radius to, e.g., `lv.PART.MAIN`. + + UiFlow2 Code Block: + + None + + MicroPython Code Block: + + .. code-block:: python + + dropdown_0.set_style_radius(10, lv.PART.MAIN | lv.STATE.DEFAULT) + """ if radius < 0: - raise ValueError("Radius must be a non-negative integer.") + warnings.warn("Radius must be a non-negative integer.") + return super().set_style_radius(radius, part) def __getattr__(self, name): diff --git a/m5stack/libs/m5ui/manifest.py b/m5stack/libs/m5ui/manifest.py index 54da65fa..83665356 100644 --- a/m5stack/libs/m5ui/manifest.py +++ b/m5stack/libs/m5ui/manifest.py @@ -13,6 +13,7 @@ "calendar.py", "canvas.py", "checkbox.py", + "dropdown.py", "image.py", "keyboard.py", "label.py", diff --git a/tests/m5ui/test_dropdown.py b/tests/m5ui/test_dropdown.py new file mode 100644 index 00000000..4a735ca5 --- /dev/null +++ b/tests/m5ui/test_dropdown.py @@ -0,0 +1,56 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import lvgl as lv +import sys + +sys.path.append("../../m5stack/libs") +import m5ui +import unittest + + +class Test(unittest.TestCase): + def __init__(self) -> None: + super().__init__() + m5ui.init() + page0 = m5ui.M5Page() + self.dropdown0 = m5ui.M5Dropdown(options=["a", "b", "c"], x=81, y=93, w=200, parent=page0) + page0.screen_load() + + def test_get_options(self): + self.assertEqual(self.dropdown0.get_options(), ["a", "b", "c"]) + + def test_get_selected_str(self): + self.dropdown0.set_selected(0, True) + self.assertEqual(self.dropdown0.get_selected_str(), "a") + self.dropdown0.set_selected(1, True) + self.assertEqual(self.dropdown0.get_selected_str(), "b") + + def test_clear_options(self): + self.dropdown0.clear_options() + self.assertEqual(self.dropdown0.get_options(), []) + + def test_set_options(self): + self.dropdown0.set_options(["a", "b", "c"]) + self.assertEqual(self.dropdown0.get_options(), ["a", "b", "c"]) + + def test_get_option_index(self): + self.dropdown0.set_options(["a", "b", "c"]) + self.assertEqual(self.dropdown0.get_option_index("a"), 0) + self.assertEqual(self.dropdown0.get_option_index("b"), 1) + self.assertEqual(self.dropdown0.get_option_index("c"), 2) + self.assertEqual(self.dropdown0.get_option_index("d"), -1) + + def test_get_text(self): + self.dropdown0.set_text("abc") + self.assertEqual(self.dropdown0.get_text(), "abc") + + def test_add_option(self): + self.dropdown0.add_option("d", 0) + self.assertEqual(self.dropdown0.get_options(), ["d", "a", "b", "c"]) + self.assertEqual(self.dropdown0.get_option_index("d"), 0) + + +if __name__ == "__main__": + unittest.main() From 68c255121a892706bb3e5ad97a8e1ec74c35bc8e Mon Sep 17 00:00:00 2001 From: lbuque <1102390310@qq.com> Date: Wed, 30 Jul 2025 11:24:27 +0800 Subject: [PATCH 213/322] libs/m5ui: Add M5ButtonMatrix support. Signed-off-by: lbuque <1102390310@qq.com> --- docs/en/m5ui/buttonmatrix.rst | 472 ++++++++++++ docs/en/m5ui/index.rst | 1 + docs/en/refs/m5ui.buttonmatrix.ref | 36 + .../zh_CN/LC_MESSAGES/m5ui/buttonmatrix.po | 708 ++++++++++++++++++ .../cores3_buttonmatrix_basic_example.m5f2 | 1 + .../cores3_buttonmatrix_basic_example.py | 104 +++ m5stack/libs/m5ui/__init__.py | 1 + m5stack/libs/m5ui/buttonmatrix.py | 129 ++++ m5stack/libs/m5ui/manifest.py | 1 + tests/m5ui/test_buttonmatrix.py | 56 ++ 10 files changed, 1509 insertions(+) create mode 100644 docs/en/m5ui/buttonmatrix.rst create mode 100644 docs/en/refs/m5ui.buttonmatrix.ref create mode 100644 docs/locales/zh_CN/LC_MESSAGES/m5ui/buttonmatrix.po create mode 100644 examples/m5ui/buttonmatrix/cores3_buttonmatrix_basic_example.m5f2 create mode 100644 examples/m5ui/buttonmatrix/cores3_buttonmatrix_basic_example.py create mode 100644 m5stack/libs/m5ui/buttonmatrix.py create mode 100644 tests/m5ui/test_buttonmatrix.py diff --git a/docs/en/m5ui/buttonmatrix.rst b/docs/en/m5ui/buttonmatrix.rst new file mode 100644 index 00000000..deb81f8c --- /dev/null +++ b/docs/en/m5ui/buttonmatrix.rst @@ -0,0 +1,472 @@ +.. currentmodule:: m5ui + +M5ButtonMatrix +============== + +.. include:: ../refs/m5ui.buttonmatrix.ref + +M5ButtonMatrix is a widget that can be used to create a matrix of buttons in the +user interface. It provides a flexible layout for displaying multiple buttons in +a grid format with support for different button configurations and text labels. + + +UiFlow2 Example +--------------- + +basic buttonmatrix +^^^^^^^^^^^^^^^^^^ + +Open the |cores3_buttonmatrix_basic_example.m5f2| project in UiFlow2. + +This example demonstrates how to create a button matrix with custom labels and handle button press events. + +UiFlow2 Code Block: + + |cores3_buttonmatrix_basic_example.png| + +Example output: + + None + + +MicroPython Example +------------------- + +basic buttonmatrix +^^^^^^^^^^^^^^^^^^ + +This example demonstrates how to create a button matrix with custom labels and handle button press events. + +MicroPython Code Block: + + .. literalinclude:: ../../../examples/m5ui/buttonmatrix/cores3_buttonmatrix_basic_example.py + :language: python + :linenos: + +Example output: + + None + + +**API** +------- + +M5ButtonMatrix +^^^^^^^^^^^^^^ + +.. autoclass:: m5ui.buttonmatrix.M5ButtonMatrix + :members: + + .. py:method:: set_flag(flag, value) + + Set a flag on the object. If ``value`` is True, the flag is added; if False, the flag is removed. + + :param int flag: The flag to set. + :param bool value: If True, the flag is added; if False, the flag is removed. + :return: None + + UiFlow2 Code Block: + + |set_flag.png| + + MicroPython Code Block: + + .. code-block:: python + + buttonmatrix_0.set_flag(lv.obj.FLAG.HIDDEN, True) + + + .. py:method:: toggle_flag(flag) + + Toggle a flag on the object. If the flag is set, it is removed; if not set, it is added. + + :param int flag: The flag to toggle. + :return: None + + UiFlow2 Code Block: + + |toggle_flag.png| + + MicroPython Code Block: + + .. code-block:: python + + buttonmatrix_0.toggle_flag(lv.obj.FLAG.HIDDEN) + + + .. py:method:: set_state(state, value) + + Set the state of the buttonmatrix. If ``value`` is True, the state is set; if False, the state is unset. + + :param int state: The state to set. + :param bool value: If True, the state is set; if False, the state is unset. + :return: None + + UiFlow2 Code Block: + + |set_state.png| + + MicroPython Code Block: + + .. code-block:: python + + buttonmatrix_0.set_state(lv.STATE.PRESSED, True) + + + .. py:method:: toggle_state(state) + + Toggle the state of the buttonmatrix. If the state is set, it is unset; if not set, it is set. + + :param int state: The state to toggle. + :return: None + + UiFlow2 Code Block: + + |toggle_state.png| + + MicroPython Code Block: + + .. code-block:: python + + buttonmatrix_0.toggle_state(lv.STATE.PRESSED) + + + .. py:method:: add_event_cb(handler, event, user_data) + + Add an event callback to the buttonmatrix. The callback will be called when the specified event occurs. + + :param function handler: The callback function to call. + :param int event: The event to listen for. + :param Any user_data: Optional user data to pass to the callback. + :return: None + + UiFlow2 Code Block: + + |event.png| + + MicroPython Code Block: + + .. code-block:: python + + def buttonmatrix_0_pressed_event(event_struct): + global page0 + btn_id = buttonmatrix_0.get_selected_button() + print(f"Button {btn_id} pressed") + + def buttonmatrix_0_event_handler(event_struct): + event = event_struct.code + if event == lv.EVENT.VALUE_CHANGED: + buttonmatrix_0_pressed_event(event_struct) + return + + buttonmatrix_0.add_event_cb(buttonmatrix_0_event_handler, lv.EVENT.ALL, None) + + + .. py:method:: set_button_width(btn_id, width) + + Set the relative width of a specific button. + + :param int btn_id: The index of the button. + :param int width: The relative width (1-7, where 1 is normal width). + :return: None + + UiFlow2 Code Block: + + |set_button_width.png| + + MicroPython Code Block: + + .. code-block:: python + + buttonmatrix_0.set_button_width(0, 2) # Make first button twice as wide + + + .. py:method:: get_selected_button() + + Get the index of the last pressed button. + + :return: The index of the last pressed button, or lv.buttonmatrix.BUTTON.NONE if no button is pressed. + :rtype: int + + UiFlow2 Code Block: + + |get_selected_button.png| + + MicroPython Code Block: + + .. code-block:: python + + btn_id = buttonmatrix_0.get_selected_button() + + + .. py:method:: get_button_text(btn_id) + + Get the text of a specific button. + + :param int btn_id: The index of the button. + :return: The text of the button. + :rtype: str + + UiFlow2 Code Block: + + |get_button_text.png| + + MicroPython Code Block: + + .. code-block:: python + + text = buttonmatrix_0.get_button_text(0) + + + .. py:method:: clear_button_ctrl(btn_id, ctrl) + + Clear control flags for a specific button. + + :param int btn_id: The button ID to clear control flags for. + :param int ctrl: The control flags to clear. + + UiFlow2 Code Block: + + |clear_button_ctrl.png| + + MicroPython Code Block: + + .. code-block:: python + + buttonmatrix_0.clear_button_ctrl(0, lv.buttonmatrix.CTRL.HIDDEN) + + + .. py:method:: set_button_ctrl(btn_id, ctrl) + + Set control flags for a specific button. + + :param int btn_id: The button ID to set control flags for. + :param int ctrl: The control flags to set. + + UiFlow2 Code Block: + + |set_button_ctrl.png| + + MicroPython Code Block: + + .. code-block:: python + + buttonmatrix_0.set_button_ctrl(0, lv.buttonmatrix.CTRL.HIDDEN) + + + .. py:method:: set_button_ctrl_all(ctrl) + + Set control flags for all buttons. + + :param int ctrl: The control flags to set for all buttons. + + UiFlow2 Code Block: + + |set_button_ctrl_all.png| + + MicroPython Code Block: + + .. code-block:: python + + buttonmatrix_0.set_button_ctrl_all(lv.buttonmatrix.CTRL.HIDDEN) + + + .. py:method:: clear_button_ctrl_all(ctrl) + + Clear control flags for all buttons. + + :param int ctrl: The control flags to clear for all buttons. + + UiFlow2 Code Block: + + |clear_button_ctrl_all.png| + + MicroPython Code Block: + + .. code-block:: python + + buttonmatrix_0.clear_button_ctrl_all(lv.buttonmatrix.CTRL.HIDDEN) + + + .. py:method:: set_one_checked(btn_id) + + Set a specific button as checked. + + :param int btn_id: The button ID to set as checked. + + UiFlow2 Code Block: + + |set_one_checked.png| + + MicroPython Code Block: + + .. code-block:: python + + buttonmatrix_0.set_one_checked(0) + + + .. py:method:: set_pos(x, y) + + Set the position of the buttonmatrix. + + :param int x: The x-coordinate of the buttonmatrix. + :param int y: The y-coordinate of the buttonmatrix. + :return: None + + UiFlow2 Code Block: + + |set_pos.png| + + MicroPython Code Block: + + .. code-block:: python + + buttonmatrix_0.set_pos(100, 100) + + + .. py:method:: set_x(x) + + Set the x-coordinate of the buttonmatrix. + + :param int x: The x-coordinate of the buttonmatrix. + :return: None + + UiFlow2 Code Block: + + |set_x.png| + + MicroPython Code Block: + + .. code-block:: python + + buttonmatrix_0.set_x(100) + + + .. py:method:: set_y(y) + + Set the y-coordinate of the buttonmatrix. + + :param int y: The y-coordinate of the buttonmatrix. + :return: None + + UiFlow2 Code Block: + + |set_y.png| + + MicroPython Code Block: + + .. code-block:: python + + buttonmatrix_0.set_y(100) + + + .. py:method:: set_size(width, height) + + Set the size of the buttonmatrix. + + :param int width: The width of the buttonmatrix. + :param int height: The height of the buttonmatrix. + :return: None + + UiFlow2 Code Block: + + |set_size.png| + + MicroPython Code Block: + + .. code-block:: python + + buttonmatrix_0.set_size(300, 200) + + + .. py:method:: set_width(width) + + Set the width of the buttonmatrix. + + :param int width: The width of the buttonmatrix. + :return: None + + UiFlow2 Code Block: + + |set_width.png| + + MicroPython Code Block: + + .. code-block:: python + + buttonmatrix_0.set_width(300) + + + .. py:method:: get_width() + + Get the width of the buttonmatrix. + + :return: The width of the buttonmatrix. + :rtype: int + + UiFlow2 Code Block: + + |get_width.png| + + MicroPython Code Block: + + .. code-block:: python + + width = buttonmatrix_0.get_width() + + + .. py:method:: set_height(height) + + Set the height of the buttonmatrix. + + :param int height: The height of the buttonmatrix. + :return: None + + UiFlow2 Code Block: + + |set_height.png| + + MicroPython Code Block: + + .. code-block:: python + + buttonmatrix_0.set_height(200) + + + .. py:method:: get_height() + + Get the height of the buttonmatrix. + + :return: The height of the buttonmatrix. + :rtype: int + + UiFlow2 Code Block: + + |get_height.png| + + MicroPython Code Block: + + .. code-block:: python + + height = buttonmatrix_0.get_height() + + + .. py:method:: align_to(obj, align, x, y) + + Align the buttonmatrix to another object. + + :param lv.obj obj: The object to align to. + :param int align: The alignment type. + :param int x: The x-offset from the aligned object. + :param int y: The y-offset from the aligned object. + :return: None + + UiFlow2 Code Block: + + |align_to.png| + + MicroPython Code Block: + + .. code-block:: python + + buttonmatrix_0.align_to(page_0, lv.ALIGN.CENTER, 0, 0) diff --git a/docs/en/m5ui/index.rst b/docs/en/m5ui/index.rst index 604e0153..5c0a0e6d 100644 --- a/docs/en/m5ui/index.rst +++ b/docs/en/m5ui/index.rst @@ -36,6 +36,7 @@ Classes arc.rst bar.rst button.rst + buttonmatrix.rst calendar.rst canvas.rst checkbox.rst diff --git a/docs/en/refs/m5ui.buttonmatrix.ref b/docs/en/refs/m5ui.buttonmatrix.ref new file mode 100644 index 00000000..8c948792 --- /dev/null +++ b/docs/en/refs/m5ui.buttonmatrix.ref @@ -0,0 +1,36 @@ +.. |cores3_buttonmatrix_basic_example.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/button_matrix/cores3_buttonmatrix_basic_example.png + +.. |align_to.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/button_matrix/align_to.png +.. |clear_button_ctrl.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/button_matrix/clear_button_ctrl.png +.. |clear_button_ctrl_all.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/button_matrix/clear_button_ctrl_all.png +.. |event.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/button_matrix/event.png +.. |get_button_text.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/button_matrix/get_button_text.png +.. |get_height.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/button_matrix/get_height.png +.. |get_selected_button.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/button_matrix/get_selected_button.png +.. |get_textarea.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/button_matrix/get_textarea.png +.. |get_width.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/button_matrix/get_width.png +.. |set_button_ctrl.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/button_matrix/set_button_ctrl.png +.. |set_button_ctrl_all.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/button_matrix/set_button_ctrl_all.png +.. |set_button_width.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/button_matrix/set_button_width.png +.. |set_flag.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/button_matrix/set_flag.png +.. |set_height.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/button_matrix/set_height.png +.. |set_one_checked.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/button_matrix/set_one_checked.png +.. |set_pos.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/button_matrix/set_pos.png +.. |set_size.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/button_matrix/set_size.png +.. |set_state.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/button_matrix/set_state.png +.. |set_textarea.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/button_matrix/set_textarea.png +.. |set_width.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/button_matrix/set_width.png +.. |set_x.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/button_matrix/set_x.png +.. |set_y.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/button_matrix/set_y.png +.. |toggle_button_ctrl.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/button_matrix/toggle_button_ctrl.png +.. |toggle_flag.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/button_matrix/toggle_flag.png +.. |toggle_state.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/button_matrix/toggle_state.png + +.. |cores3_buttonmatrix_basic_example.m5f2| raw:: html + + + cores3_buttonmatrix_basic_example.m5f2 + diff --git a/docs/locales/zh_CN/LC_MESSAGES/m5ui/buttonmatrix.po b/docs/locales/zh_CN/LC_MESSAGES/m5ui/buttonmatrix.po new file mode 100644 index 00000000..5e982655 --- /dev/null +++ b/docs/locales/zh_CN/LC_MESSAGES/m5ui/buttonmatrix.po @@ -0,0 +1,708 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2016 - 2025 M5Stack Technology Co., Ltd +# This file is distributed under the same license as the UIFlow2 Programming +# Guide package. +# FIRST AUTHOR , 2025. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: UIFlow2 Programming Guide \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-08-20 10:29+0800\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: zh_CN\n" +"Language-Team: zh_CN \n" +"Plural-Forms: nplurals=1; plural=0;\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.17.0\n" + +#: ../../en/m5ui/buttonmatrix.rst:4 ../../en/m5ui/buttonmatrix.rst:55 +#: 5540d17ce9bf49a49657e76df5326c98 c1a370daed7a42bab6901e65abc485dc +msgid "M5ButtonMatrix" +msgstr "" + +#: ../../en/m5ui/buttonmatrix.rst:8 56aba2e590c143b3a4ed6079d42f344f +msgid "" +"M5ButtonMatrix is a widget that can be used to create a matrix of buttons" +" in the user interface. It provides a flexible layout for displaying " +"multiple buttons in a grid format with support for different button " +"configurations and text labels." +msgstr "" +"M5ButtonMatrix是一个可以在用户界面中创建按钮矩阵的控件。它提供了一个灵活的布局来" +"以网格格式显示多个按钮,支持不同的按钮配置和文本标签。" + +#: ../../en/m5ui/buttonmatrix.rst:14 484db3bd4a304115b7b1fc3b78542fd0 +msgid "UiFlow2 Example" +msgstr "UiFlow2 示例" + +#: ../../en/m5ui/buttonmatrix.rst:17 ../../en/m5ui/buttonmatrix.rst:36 +#: 26f4e3e2ea4e414a8b5431520b4c4e80 3fe97a02cef1487a86909a5dcedda56c +msgid "basic buttonmatrix" +msgstr "基础按钮矩阵" + +#: ../../en/m5ui/buttonmatrix.rst:19 6063f9f18efb4521984fd55828216f48 +msgid "Open the |cores3_buttonmatrix_basic_example.m5f2| project in UiFlow2." +msgstr "在UiFlow2中打开 |cores3_buttonmatrix_basic_example.m5f2| 项目。" + +#: ../../en/m5ui/buttonmatrix.rst:21 ../../en/m5ui/buttonmatrix.rst:38 +#: 8b93ad2cd7e44ed4ac53089dd635f8d6 aa278b5e1af44ae7a0b19790f72dbb8c +msgid "" +"This example demonstrates how to create a button matrix with custom " +"labels and handle button press events." +msgstr "" +"此示例演示如何创建带有自定义标签的按钮矩阵并处理按钮按下事件。" + +#: ../../en/m5ui/buttonmatrix.rst:23 ../../en/m5ui/buttonmatrix.rst:68 +#: ../../en/m5ui/buttonmatrix.rst:86 ../../en/m5ui/buttonmatrix.rst:105 +#: ../../en/m5ui/buttonmatrix.rst:123 ../../en/m5ui/buttonmatrix.rst:143 +#: ../../en/m5ui/buttonmatrix.rst:173 ../../en/m5ui/buttonmatrix.rst:191 +#: ../../en/m5ui/buttonmatrix.rst:210 ../../en/m5ui/buttonmatrix.rst:228 +#: ../../en/m5ui/buttonmatrix.rst:245 ../../en/m5ui/buttonmatrix.rst:262 +#: ../../en/m5ui/buttonmatrix.rst:279 ../../en/m5ui/buttonmatrix.rst:298 +#: ../../en/m5ui/buttonmatrix.rst:316 ../../en/m5ui/buttonmatrix.rst:334 +#: ../../en/m5ui/buttonmatrix.rst:353 ../../en/m5ui/buttonmatrix.rst:371 +#: ../../en/m5ui/buttonmatrix.rst:389 ../../en/m5ui/buttonmatrix.rst:407 +#: ../../en/m5ui/buttonmatrix.rst:425 ../../en/m5ui/buttonmatrix.rst:446 +#: 02a02d7e6bc94d84bfef99865c78f3d7 127f7507c27b43648e74332e979cb131 +#: 17609aee372a463eae2178c6cac4802d 1920c33f359643fd97dee33f1b3a1b8d +#: 27f04587fc7c4389abf2a271ab36c078 28e57792daac47acb064a5c3a3e1a381 +#: 29613927d51a47a4b56c3f05bc2ac866 29a271aeb10a4132902656b69f9aca60 +#: 3ffbabe73081426dad8b1c19d51b1c4a 5a0fbae37b7f4c1ea4073390a919562e +#: 5e42eec4715343a3ab2204b2aa003120 6b7e3a6e2e2b40a2938a8536a18ff339 +#: 7510c92bfb374c228c198ba9cac837d5 751d5726debb4b51a7adf75d6482e705 +#: 91d925c1d8464f93aa6ab3394e0937f0 94d6431f27964204980dfbd0917c7e7a +#: a4d9774ae5b14fa08654e698ca3fae58 a96f52a3c5994b9daab1677546289c65 +#: b86c5952aeed4208963794cf7cd54b82 bea3c0f4332b48e09f355e44c97d2b70 +#: bf40d9b6ebfa4f82af275854a9bca5eb bff22d5a6d474cf9b6fe5a90fb9fac61 +#: c58393615e2d474c98a3eb8061d112fc e2714ee6dbfd41ab82a97166efa19b38 +#: e7ca0f07fbeb45caad3fe7401552eba0 e8b21e987e4644a39c9f2ea46ddaf5db +#: f49523acb9264116949665cebc3969f2 m5ui.buttonmatrix.M5ButtonMatrix:11 +#: m5ui.buttonmatrix.M5ButtonMatrix.get_textarea:6 +#: m5ui.buttonmatrix.M5ButtonMatrix.set_button_ctrl:6 +#: m5ui.buttonmatrix.M5ButtonMatrix.set_textarea:5 +#: m5ui.buttonmatrix.M5ButtonMatrix.toggle_button_ctrl:6 of +msgid "UiFlow2 Code Block:" +msgstr "UiFlow2 代码块:" + +#: ../../en/m5ui/buttonmatrix.rst:25 b8d0995f15764f8d9957c2b2a75bc8df +msgid "|cores3_buttonmatrix_basic_example.png|" +msgstr "" + +#: ../../en/refs/m5ui.buttonmatrix.ref:1 e0c79052be26427b931cb88991bcf002 +msgid "cores3_buttonmatrix_basic_example.png" +msgstr "" + +#: ../../en/m5ui/buttonmatrix.rst:27 ../../en/m5ui/buttonmatrix.rst:46 +#: 5b6b20a8dfd34cb3bcb6072dde7dcc50 d107297ae68846a98657d1569b6e500a +msgid "Example output:" +msgstr "示例输出:" + +#: ../../en/m5ui/buttonmatrix.rst:29 ../../en/m5ui/buttonmatrix.rst:48 +#: ../../en/m5ui/buttonmatrix.rst:66 ../../en/m5ui/buttonmatrix.rst:84 +#: ../../en/m5ui/buttonmatrix.rst:103 ../../en/m5ui/buttonmatrix.rst:121 +#: ../../en/m5ui/buttonmatrix.rst:141 ../../en/m5ui/buttonmatrix.rst:171 +#: ../../en/m5ui/buttonmatrix.rst:296 ../../en/m5ui/buttonmatrix.rst:314 +#: ../../en/m5ui/buttonmatrix.rst:332 ../../en/m5ui/buttonmatrix.rst:351 +#: ../../en/m5ui/buttonmatrix.rst:369 ../../en/m5ui/buttonmatrix.rst:405 +#: ../../en/m5ui/buttonmatrix.rst:444 2968b41bc2c042a09568a4583d88be5c +#: 2d60a0129e71478394c4cea95891e57b 3a1f458c21ce4fe48aeb6a823096f526 +#: 4118a1df1eac4febb56ec6f2064b4a69 428dd7ecacc0472288f0b83ca1da2513 +#: 4459f6152868472c8c5057ee9dd54e28 4568bc03133c45739344a7a39f6c6b31 +#: 5411898078b044c2a009a480e4e0b9a5 5aca548655b440f98081707e63fe1026 +#: 8fddca55027443bfb99e005f9b262f10 98d6d05300d3478bb563aabe34486153 +#: b40ebadcb0b24887b69c7ded9747dcd2 ce896fbb3ae14a36a0ec76f0a3c8acca +#: d9130a4557ff4dcfa5c38be003ebb871 f63d4929161a4b64b7e5b59abfc543ad +#: fdd8ddcc9b754500b8c5f62db787bf50 m5ui.buttonmatrix.M5ButtonMatrix:13 of +msgid "None" +msgstr "" + +#: ../../en/m5ui/buttonmatrix.rst:33 32f3171d95a44daabff6bf7f5d7b99b7 +msgid "MicroPython Example" +msgstr "MicroPython 示例" + +#: ../../en/m5ui/buttonmatrix.rst:40 ../../en/m5ui/buttonmatrix.rst:72 +#: ../../en/m5ui/buttonmatrix.rst:90 ../../en/m5ui/buttonmatrix.rst:109 +#: ../../en/m5ui/buttonmatrix.rst:127 ../../en/m5ui/buttonmatrix.rst:147 +#: ../../en/m5ui/buttonmatrix.rst:177 ../../en/m5ui/buttonmatrix.rst:195 +#: ../../en/m5ui/buttonmatrix.rst:214 ../../en/m5ui/buttonmatrix.rst:232 +#: ../../en/m5ui/buttonmatrix.rst:249 ../../en/m5ui/buttonmatrix.rst:266 +#: ../../en/m5ui/buttonmatrix.rst:283 ../../en/m5ui/buttonmatrix.rst:302 +#: ../../en/m5ui/buttonmatrix.rst:320 ../../en/m5ui/buttonmatrix.rst:338 +#: ../../en/m5ui/buttonmatrix.rst:357 ../../en/m5ui/buttonmatrix.rst:375 +#: ../../en/m5ui/buttonmatrix.rst:393 ../../en/m5ui/buttonmatrix.rst:411 +#: ../../en/m5ui/buttonmatrix.rst:429 ../../en/m5ui/buttonmatrix.rst:450 +#: 0667645069c34cf49b6f5b252c7df01e 0dbef0068b3d4ae5a24075264e53d3b2 +#: 102f061e3bd94c70b576019eaae66dd3 330f863643e74eea9a65f10cad391432 +#: 40c141d36b884b09a33946c0848ea4c3 447bc046119e45b3acbf4eebcc98b3ac +#: 4a1263344bf74e89a2ce00fc026dcaee 4e322aac4b60439b9c3321654dde2c76 +#: 5953f6208ad747bea7d161add8599003 6332ee192504424f842fbe4e9fd7f4c9 +#: 65d3a7dc714249d4b4b19367be4bdc4d 810e57004062417f916a2242a708a024 +#: 926ceb223ed84a3a802f658ea348fe8b 94f73b4b71fc4a43a17a64ec7a985d63 +#: a4d427efa51449cf904fb39532d9f928 a77faff5ccc749bfa9c0e7114aa76a01 +#: abc4ab1baf1544cabd4ebacf1dde6d3e b4c3e87876c544c5bb6a9971986e8140 +#: b85fa40004f04b888eed7101ce1781f2 c2b9bdf7f38a4016be9b0db264314356 +#: c45ea1134a3a423994f6c34f5cad7eca c66e4daa769d4215802d17c30efc5fe0 +#: d35b2638e7e4493dae19687223610223 eb2ce9bbfddd4e25ae2ac5025dddd02d +#: f1c8c600baf14275b02c46948cae66e1 f6ed04b0b6934a94a501c61532478745 +#: f8c2b975b4b24a578acee5547a8602d4 m5ui.buttonmatrix.M5ButtonMatrix:15 +#: m5ui.buttonmatrix.M5ButtonMatrix.get_textarea:10 +#: m5ui.buttonmatrix.M5ButtonMatrix.set_button_ctrl:10 +#: m5ui.buttonmatrix.M5ButtonMatrix.set_textarea:9 +#: m5ui.buttonmatrix.M5ButtonMatrix.toggle_button_ctrl:10 of +msgid "MicroPython Code Block:" +msgstr "MicroPython 代码块:" + +#: ../../en/m5ui/buttonmatrix.rst:52 d5afc4903c5f4a88b65d2693704b2459 +msgid "**API**" +msgstr "API参考" + +#: fd8ec279238c4c4abc7231d5232e0125 m5ui.buttonmatrix.M5ButtonMatrix:1 of +msgid "Bases: :py:class:`~lvgl.buttonmatrix`" +msgstr "" + +#: eff27f8a9faf4363a7a43575df8b5ba6 m5ui.buttonmatrix.M5ButtonMatrix:1 of +msgid "Create a button matrix object." +msgstr "创建一个按钮矩阵对象。" + +#: ../../en/m5ui/buttonmatrix.rst 042ce52ee71543cd8bfc82a49bcfcf09 +#: 0c61091ff89f4337861d71931d14a523 0f1767473e294ad2a3e71b121ad1bbf0 +#: 14fe256407e645789be12cd953a25f30 15927dc4edde4d448ee2ec342eafc78e +#: 39a159aa8778419db86186ae4d57f2eb 4030e73dddac46c38d51d888840e0a84 +#: 4a63810e00474900badd2f779c27ee12 5a7d308730044e71a0a73c91c6b81d32 +#: 6e1c7a231f274fbcb4206b97b639e2e1 7ffd799c661044f997aa86f076eb1e20 +#: 837f894983c646daa56d3f199c4c1d00 861a43962e534edd91d24c87b074dcd1 +#: 90033b52a2694ae5b2c886199ab87f4d 90d01ba093dc478c9d9c951bf9f359fe +#: a812a39711e74989a984211d5c77eba0 bbbcc20636ec4be1957a319c2373d768 +#: bc8e3863b38143d194c4cc53c900a49e cec0a79cd2e74897ac572b0b0a121ad2 +#: d9d8f6e7e1164c4ea1d0748a8a1b4289 f0c8723682d44dbaacc919a9bfdf3dea +#: f49815423e2844c583b2d5df93623655 +#: m5ui.buttonmatrix.M5ButtonMatrix.set_button_ctrl +#: m5ui.buttonmatrix.M5ButtonMatrix.set_textarea +#: m5ui.buttonmatrix.M5ButtonMatrix.toggle_button_ctrl of +msgid "Parameters" +msgstr "" + +#: 5e7815bd5c7d4f7e868a88162dcdee89 m5ui.buttonmatrix.M5ButtonMatrix:3 of +msgid "A list of button labels. Use \"\\\\n\" to create a new row." +msgstr "按钮标签的列表。使用 \"\\\\n\" 来创建新行。" + +#: d4afa28525474cb481dd1e9b0ea82703 m5ui.buttonmatrix.M5ButtonMatrix:4 of +msgid "The x position of the button matrix." +msgstr "按钮矩阵的X坐标位置。" + +#: 6e249107cffd41b491c88bf271345ed1 m5ui.buttonmatrix.M5ButtonMatrix:5 of +msgid "The y position of the button matrix." +msgstr "按钮矩阵的Y坐标位置。" + +#: 5bcdb5c3e6744f05a6bb980fe8402897 m5ui.buttonmatrix.M5ButtonMatrix:6 of +msgid "The width of the button matrix." +msgstr "按钮矩阵的宽度。" + +#: 7cff769227934455a2afd0c1c0c26002 m5ui.buttonmatrix.M5ButtonMatrix:7 of +msgid "The height of the button matrix." +msgstr "按钮矩阵的高度。" + +#: 3949957319424622b0a4334deff57bdd m5ui.buttonmatrix.M5ButtonMatrix:8 of +msgid "A M5TextArea to display the button text when a button is pressed." +msgstr "按下按钮时显示按钮文本的M5TextArea。" + +#: b370113d5e9b4d03b2825a1e651297fd m5ui.buttonmatrix.M5ButtonMatrix:9 of +msgid "The parent object to attach the button matrix to." +msgstr "要附加按钮矩阵的父对象。" + +#: ../../en/m5ui/buttonmatrix.rst:62 ed569c16ef7941e88ce99de07ca9b246 +msgid "" +"Set a flag on the object. If ``value`` is True, the flag is added; if " +"False, the flag is removed." +msgstr "" +"为对象设置标志。如果 ``value`` 为True,则添加标志;" +"如果为False,则移除标志。" + +#: ../../en/m5ui/buttonmatrix.rst:64 36df2029433a46bb92d84913fdfd5d3a +msgid "The flag to set." +msgstr "要设置的标志。" + +#: ../../en/m5ui/buttonmatrix.rst:65 1493a7be63ca4db0be812d3b013a8bc3 +msgid "If True, the flag is added; if False, the flag is removed." +msgstr "如果为True,则添加标志;如果为False,则移除标志。" + +#: ../../en/m5ui/buttonmatrix.rst 13eb4fc85c6842fa8ef55a522a6fb3f5 +#: 17b6dd91bee34629a4aa199846e1da2e 1e609d4eaaf94fe3a40546d12731aab1 +#: 2394118fb0714e8393d9aeabf2029389 4083b5fb5c854ed5a0ea763c20db20ec +#: 4e470cd453c34b359488d5118d979a51 584d4b55fa144076921a467ce69e5e3b +#: 6d4a5654b4114f6d978b2985cc8bdafb 717e7e25aef8475693f1b0ced893bcf7 +#: 71cea7df68ee4c83afa5cb87c881fde1 84042f93a07b4f75b0e3029f034ba112 +#: a4a519ea04944c66b635e038e7bc17b1 b47772aabe9743efb8ed843c3477c7f1 +#: cdbeb9aa83274acea1e3de82fad96233 cff7467562b746f7b6d420fc777a0cdc +#: dd546278eb254121918b3b1572409301 ea4ca98941674be299935fb00e19b3af +#: f5cd5d74ba864874bef5a0fb3f959082 +#: m5ui.buttonmatrix.M5ButtonMatrix.get_textarea of +msgid "Returns" +msgstr "" + +#: ../../en/m5ui/buttonmatrix.rst:70 cd41a618fbdf40bca11e2a447610d5a5 +msgid "|set_flag.png|" +msgstr "" + +#: ../../en/refs/m5ui.buttonmatrix.ref:15 bcf3870ebd1242808b34d0dc30cfe8d0 +msgid "set_flag.png" +msgstr "" + +#: ../../en/m5ui/buttonmatrix.rst:81 bb0411c537304bbb86298911c713fa51 +msgid "" +"Toggle a flag on the object. If the flag is set, it is removed; if not " +"set, it is added." +msgstr "" +"切换对象的标志。如果标志已设置,则将其移除;" +"如果未设置,则将其添加。" + +#: ../../en/m5ui/buttonmatrix.rst:83 2f2db537d78d40638be2f68dd80366f0 +msgid "The flag to toggle." +msgstr "要切换的标志。" + +#: ../../en/m5ui/buttonmatrix.rst:88 96dbf1c24fc645bd8a8805fe3f469be8 +msgid "|toggle_flag.png|" +msgstr "" + +#: ../../en/refs/m5ui.buttonmatrix.ref:26 c3eb7348ea13490a8b44a470254bd3f5 +msgid "toggle_flag.png" +msgstr "" + +#: ../../en/m5ui/buttonmatrix.rst:99 f4a1dc354870488cb58ad545c6648820 +msgid "" +"Set the state of the buttonmatrix. If ``value`` is True, the state is " +"set; if False, the state is unset." +msgstr "" +"设置按钮矩阵的状态。如果 ``value`` 为True,则设置状态;" +"如果为False,则取消设置状态。" + +#: ../../en/m5ui/buttonmatrix.rst:101 914b414d54514771a92594c984b0f563 +msgid "The state to set." +msgstr "要设置的状态。" + +#: ../../en/m5ui/buttonmatrix.rst:102 ff3d27ae23ca42968cb35ad9b17df9c1 +msgid "If True, the state is set; if False, the state is unset." +msgstr "如果为True,则设置状态;如果为False,则取消设置状态。" + +#: ../../en/m5ui/buttonmatrix.rst:107 d7d39541e10d4193ade2a6d71807cab9 +msgid "|set_state.png|" +msgstr "" + +#: ../../en/refs/m5ui.buttonmatrix.ref:20 1d7b8d308b5e47948c85ff40c80d8196 +msgid "set_state.png" +msgstr "" + +#: ../../en/m5ui/buttonmatrix.rst:118 b67650dc628545149c83c321a4b47cab +msgid "" +"Toggle the state of the buttonmatrix. If the state is set, it is unset; " +"if not set, it is set." +msgstr "" +"切换按钮矩阵的状态。如果状态已设置,则取消设置;" +"如果未设置,则将其设置。" + +#: ../../en/m5ui/buttonmatrix.rst:120 5fbf1d5f84db46fab1d62e4c6bb83b76 +msgid "The state to toggle." +msgstr "要切换的状态。" + +#: ../../en/m5ui/buttonmatrix.rst:125 25d347cfdbb14c748e2d4a1d7c10a194 +msgid "|toggle_state.png|" +msgstr "" + +#: ../../en/refs/m5ui.buttonmatrix.ref:27 3d3e51742d3042f18abdd31127d27846 +msgid "toggle_state.png" +msgstr "" + +#: ../../en/m5ui/buttonmatrix.rst:136 3ab536245ed34698b17230883a81ceb7 +msgid "" +"Add an event callback to the buttonmatrix. The callback will be called " +"when the specified event occurs." +msgstr "" +"为按钮矩阵添加事件回调。当指定事件发生时将调用回调函数。" + +#: ../../en/m5ui/buttonmatrix.rst:138 3666759557b747589a7597e8b8403ee7 +msgid "The callback function to call." +msgstr "要调用的回调函数。" + +#: ../../en/m5ui/buttonmatrix.rst:139 e319252ac13f4e6aa51a650c061a6a7c +msgid "The event to listen for." +msgstr "要监听的事件。" + +#: ../../en/m5ui/buttonmatrix.rst:140 eac92d3024a74df08fa76e32f3c05333 +msgid "Optional user data to pass to the callback." +msgstr "传递给回调函数的可选用户数据。" + +#: ../../en/m5ui/buttonmatrix.rst:145 0ebf81a0a90a4e6db175c35db6043a83 +msgid "|event.png|" +msgstr "" + +#: ../../en/refs/m5ui.buttonmatrix.ref:6 e7abf0112d9a45828fc20915a47b3a37 +msgid "event.png" +msgstr "" + +#: ../../en/m5ui/buttonmatrix.rst:167 9fe7ce3f8d104e9da7ea91813963b90a +msgid "Set the relative width of a specific button." +msgstr "设置特定按钮的相对宽度。" + +#: ../../en/m5ui/buttonmatrix.rst:169 ../../en/m5ui/buttonmatrix.rst:206 +#: 0866fdba42b6496691f63093cdcf1983 7c3248bf15f2433abdbf7c714e8af038 +msgid "The index of the button." +msgstr "按钮的索引。" + +#: ../../en/m5ui/buttonmatrix.rst:170 9d4d94e899ed48c3b197b9fd059b6b73 +msgid "The relative width (1-7, where 1 is normal width)." +msgstr "相对宽度(1-7,其中1为正常宽度)。" + +#: ../../en/m5ui/buttonmatrix.rst:175 4244126fa9b14a688ceff6698e58d5fb +msgid "|set_button_width.png|" +msgstr "" + +#: ../../en/refs/m5ui.buttonmatrix.ref:14 1ca3c4dea4974e39b6ba5ed1d15798c2 +msgid "set_button_width.png" +msgstr "" + +#: ../../en/m5ui/buttonmatrix.rst:186 928d93d38ed545e19bff7a8bdf3e4640 +msgid "Get the index of the last pressed button." +msgstr "获取最后按下按钮的索引。" + +#: ../../en/m5ui/buttonmatrix.rst:188 c74310fa070d40beaa731fa6c499053a +msgid "" +"The index of the last pressed button, or lv.buttonmatrix.BUTTON.NONE if " +"no button is pressed." +msgstr "" +"最后按下按钮的索引,如果没有按钮被按下,则返回 lv.buttonmatrix.BUTTON.NONE。" + +#: ../../en/m5ui/buttonmatrix.rst 05bdf3628bf948b0ac2ed630c31ee226 +#: 2168f531f7d64bc58588e41cbcc68aa0 6822aae96eaa4fc3a73b418b446a3c20 +#: e2329f520947424f8514ed2febe6f625 e5d5cb002e4240b7992ac32532a59c17 +#: m5ui.buttonmatrix.M5ButtonMatrix.get_textarea of +msgid "Return type" +msgstr "" + +#: ../../en/m5ui/buttonmatrix.rst:193 71fe635b95864a8d88fcad53fd3877e9 +msgid "|get_selected_button.png|" +msgstr "" + +#: ../../en/refs/m5ui.buttonmatrix.ref:9 9b652c56af2f447f897fe5dea3f26e56 +msgid "get_selected_button.png" +msgstr "" + +#: ../../en/m5ui/buttonmatrix.rst:204 ef72caabcdbe4dccb347598f01bbb563 +msgid "Get the text of a specific button." +msgstr "获取特定按钮的文本。" + +#: ../../en/m5ui/buttonmatrix.rst:207 74011edd0fa84f0aaa3d5f2f96cd5446 +msgid "The text of the button." +msgstr "按钮的文本。" + +#: ../../en/m5ui/buttonmatrix.rst:212 d2fdb15431ff4701b3d6dca396d31b49 +msgid "|get_button_text.png|" +msgstr "" + +#: ../../en/refs/m5ui.buttonmatrix.ref:7 f66bebff11a9460eb8f3518c7ad5c718 +msgid "get_button_text.png" +msgstr "" + +#: ../../en/m5ui/buttonmatrix.rst:223 1dc9387b9d7f4dc0afc1bf679499cd17 +msgid "Clear control flags for a specific button." +msgstr "清除特定按钮的控制标志。" + +#: ../../en/m5ui/buttonmatrix.rst:225 265da30b55f74a949195ad50214d343c +msgid "The button ID to clear control flags for." +msgstr "要清除控制标志的按钮ID。" + +#: ../../en/m5ui/buttonmatrix.rst:226 dbb968dbe1784c9ea7eede5ab16984f8 +msgid "The control flags to clear." +msgstr "要清除的控制标志。" + +#: ../../en/m5ui/buttonmatrix.rst:230 94961f589edd4177b44e5b96231a82de +msgid "|clear_button_ctrl.png|" +msgstr "" + +#: ../../en/refs/m5ui.buttonmatrix.ref:4 5ce0470b6c334024aa8a7e1d2b1f6a6f +msgid "clear_button_ctrl.png" +msgstr "" + +#: ../../en/m5ui/buttonmatrix.rst:241 8d12baf25b7a4ce2b38aa4a1e437b1e4 +msgid "Set control flags for all buttons." +msgstr "设置所有按钮的控制标志。" + +#: ../../en/m5ui/buttonmatrix.rst:243 6a52f9802f9c4b59bfd1c1f26029608d +msgid "The control flags to set for all buttons." +msgstr "要为所有按钮设置的控制标志。" + +#: ../../en/m5ui/buttonmatrix.rst:247 84556ea9ef114fc188c44072f1ea7f7d +msgid "|set_button_ctrl_all.png|" +msgstr "" + +#: ../../en/refs/m5ui.buttonmatrix.ref:13 8696de0788d94fcba671750c191a138c +msgid "set_button_ctrl_all.png" +msgstr "" + +#: ../../en/m5ui/buttonmatrix.rst:258 93a54d78aa4a4e8fb412ac1f82250566 +msgid "Clear control flags for all buttons." +msgstr "清除所有按钮的控制标志。" + +#: ../../en/m5ui/buttonmatrix.rst:260 819df99dcd934240ba61afd78c76d6f4 +msgid "The control flags to clear for all buttons." +msgstr "要为所有按钮清除的控制标志。" + +#: ../../en/m5ui/buttonmatrix.rst:264 fefabb691498400c89104748c98eafe6 +msgid "|clear_button_ctrl_all.png|" +msgstr "" + +#: ../../en/refs/m5ui.buttonmatrix.ref:5 aad19dd39782480287548491f6cdaa87 +msgid "clear_button_ctrl_all.png" +msgstr "" + +#: ../../en/m5ui/buttonmatrix.rst:275 0958c73f77d443b689bfabba4a56fb0b +msgid "Set a specific button as checked." +msgstr "将特定按钮设置为选中状态。" + +#: ../../en/m5ui/buttonmatrix.rst:277 094f082f90c4465697fb46460fae8d7d +msgid "The button ID to set as checked." +msgstr "要设置为选中状态的按钮ID。" + +#: ../../en/m5ui/buttonmatrix.rst:281 d8a0314aead84a86bf91524ba213fa7d +msgid "|set_one_checked.png|" +msgstr "" + +#: ../../en/refs/m5ui.buttonmatrix.ref:17 9ee66e48a1b34aeaaae0c5ca58c4e40d +msgid "set_one_checked.png" +msgstr "" + +#: ../../en/m5ui/buttonmatrix.rst:292 ec36f17f24f3442897b6178998a7a902 +msgid "Set the position of the buttonmatrix." +msgstr "设置按钮矩阵的位置。" + +#: ../../en/m5ui/buttonmatrix.rst:294 ../../en/m5ui/buttonmatrix.rst:313 +#: 3f6dbd60d590487e9516ecf5a3fd2d01 5cc7717548c74df787eb0b11c1bfcb4e +msgid "The x-coordinate of the buttonmatrix." +msgstr "按钮矩阵的x坐标。" + +#: ../../en/m5ui/buttonmatrix.rst:295 ../../en/m5ui/buttonmatrix.rst:331 +#: 1c9bdaccb7e24735b13efe525a6ef64a 32f44ed032864480861a9966e288de4d +msgid "The y-coordinate of the buttonmatrix." +msgstr "按钮矩阵的y坐标。" + +#: ../../en/m5ui/buttonmatrix.rst:300 8f76b714dd384eb0aad06145ec8f1bc3 +msgid "|set_pos.png|" +msgstr "" + +#: ../../en/refs/m5ui.buttonmatrix.ref:18 7a9c9e30f4e04a5c9d5fdaf271f5180b +msgid "set_pos.png" +msgstr "" + +#: ../../en/m5ui/buttonmatrix.rst:311 b22a7fb2485443579993355fa813ddf0 +msgid "Set the x-coordinate of the buttonmatrix." +msgstr "设置按钮矩阵的x坐标。" + +#: ../../en/m5ui/buttonmatrix.rst:318 9fb0c8cdae0a4d45879a805b8a25d1ef +msgid "|set_x.png|" +msgstr "" + +#: ../../en/refs/m5ui.buttonmatrix.ref:23 22448ccc65ac4eb49ef0123032d9dbc3 +msgid "set_x.png" +msgstr "" + +#: ../../en/m5ui/buttonmatrix.rst:329 3746cb3fe11640d6910bf1f43746602c +msgid "Set the y-coordinate of the buttonmatrix." +msgstr "设置按钮矩阵的y坐标。" + +#: ../../en/m5ui/buttonmatrix.rst:336 6080a7b1b44947a189ddd6ec86b06fac +msgid "|set_y.png|" +msgstr "" + +#: ../../en/refs/m5ui.buttonmatrix.ref:24 d21b5dc3ee17459ebc54e0ea2626b878 +msgid "set_y.png" +msgstr "" + +#: ../../en/m5ui/buttonmatrix.rst:347 b4d3a1043b0c4e3d87b20330b0bff182 +msgid "Set the size of the buttonmatrix." +msgstr "设置按钮矩阵的大小。" + +#: ../../en/m5ui/buttonmatrix.rst:349 ../../en/m5ui/buttonmatrix.rst:368 +#: ../../en/m5ui/buttonmatrix.rst:386 42799b1058cb4f6da4b7fdb06457296b +#: d22c132bee814267985b02d4ad513cf8 ea8e6a2afc9447818576b76d22606db1 +msgid "The width of the buttonmatrix." +msgstr "按钮矩阵的宽度。" + +#: ../../en/m5ui/buttonmatrix.rst:350 ../../en/m5ui/buttonmatrix.rst:404 +#: ../../en/m5ui/buttonmatrix.rst:422 392a019e40d3476e9852f57b216d5a78 +#: 93b95db8851a4ba19b859d2f5e454380 ac85ac03ad014915b9d390650858d825 +msgid "The height of the buttonmatrix." +msgstr "按钮矩阵的高度。" + +#: ../../en/m5ui/buttonmatrix.rst:355 72e5253b380d4ca7b9f0fd50400d752a +msgid "|set_size.png|" +msgstr "" + +#: ../../en/refs/m5ui.buttonmatrix.ref:19 5d8c8c3b195f426c8ffa3fea13e915e5 +msgid "set_size.png" +msgstr "" + +#: ../../en/m5ui/buttonmatrix.rst:366 55ab12a4e97b471aa4e3fc90c043b38c +msgid "Set the width of the buttonmatrix." +msgstr "设置按钮矩阵的宽度。" + +#: ../../en/m5ui/buttonmatrix.rst:373 ae911a4cf0344098a52a85a93922c7a9 +msgid "|set_width.png|" +msgstr "" + +#: ../../en/refs/m5ui.buttonmatrix.ref:22 a8c1d896eda5453386445d46827d10a9 +msgid "set_width.png" +msgstr "" + +#: ../../en/m5ui/buttonmatrix.rst:384 a8b64cab0adf4c0eb62ee3f40d8a75b9 +msgid "Get the width of the buttonmatrix." +msgstr "获取按钮矩阵的宽度。" + +#: ../../en/m5ui/buttonmatrix.rst:391 28319137d94b45f98b6ae448c7fe3d45 +msgid "|get_width.png|" +msgstr "" + +#: ../../en/refs/m5ui.buttonmatrix.ref:11 cbcf296acf1a4be9a56f986e57a6c3ef +msgid "get_width.png" +msgstr "" + +#: ../../en/m5ui/buttonmatrix.rst:402 af7284046bf64d11b23ecb07b07fb137 +msgid "Set the height of the buttonmatrix." +msgstr "设置按钮矩阵的高度。" + +#: ../../en/m5ui/buttonmatrix.rst:409 41a5a210c11a4a6a8f765afdbf8756a0 +msgid "|set_height.png|" +msgstr "" + +#: ../../en/refs/m5ui.buttonmatrix.ref:16 77f4ce850d3e4988a3c7432340cb1a0d +msgid "set_height.png" +msgstr "" + +#: ../../en/m5ui/buttonmatrix.rst:420 a8b3c9d998a3490899029f37a3113eba +msgid "Get the height of the buttonmatrix." +msgstr "获取按钮矩阵的高度。" + +#: ../../en/m5ui/buttonmatrix.rst:427 79abb9feb0c74bb38d1e87fc7963ee6b +msgid "|get_height.png|" +msgstr "" + +#: ../../en/refs/m5ui.buttonmatrix.ref:8 36a6c21feb8c41e08de6531c907227f7 +msgid "get_height.png" +msgstr "" + +#: ../../en/m5ui/buttonmatrix.rst:438 09dc01b29ba94648a767407122216007 +msgid "Align the buttonmatrix to another object." +msgstr "将按钮矩阵对齐到另一个对象。" + +#: ../../en/m5ui/buttonmatrix.rst:440 b6dbc5ad976d4b6787a6ffc2b3fb3005 +msgid "The object to align to." +msgstr "要对齐到的对象。" + +#: ../../en/m5ui/buttonmatrix.rst:441 ad98b6216f1140eba9fea3367507b85e +msgid "The alignment type." +msgstr "对齐类型。" + +#: ../../en/m5ui/buttonmatrix.rst:442 6df57c3bfa75495c90e9b59bdad81934 +msgid "The x-offset from the aligned object." +msgstr "与对齐对象的x偏移量。" + +#: ../../en/m5ui/buttonmatrix.rst:443 0a9351caf6964c1786f6fb335c6e5a13 +msgid "The y-offset from the aligned object." +msgstr "与对齐对象的y偏移量。" + +#: ../../en/m5ui/buttonmatrix.rst:448 81ccc58f7e4e48a7a51d155e1d0d7263 +msgid "|align_to.png|" +msgstr "" + +#: ../../en/refs/m5ui.buttonmatrix.ref:3 18a38fbf6b4040efb0ec4ab99f7176ed +msgid "align_to.png" +msgstr "" + +#: 2a5c4c9acf2842e98c587d6cb44e23d1 +#: m5ui.buttonmatrix.M5ButtonMatrix.set_button_ctrl:1 of +msgid "Set control flags for a specific button." +msgstr "设置特定按钮的控制标志。" + +#: 573451ee7cde41b18f8a5554111ed3b6 +#: m5ui.buttonmatrix.M5ButtonMatrix.set_button_ctrl:3 of +msgid "The button ID to set control flags for." +msgstr "要设置控制标志的按钮ID。" + +#: 52da1f38c2284129b3d26849cb7dcdc0 +#: m5ui.buttonmatrix.M5ButtonMatrix.set_button_ctrl:4 of +msgid "The control flags to set." +msgstr "要设置的控制标志。" + +#: fa7c38942b1843149b4eef9212eaa5b5 +#: m5ui.buttonmatrix.M5ButtonMatrix.set_button_ctrl:8 of +msgid "|set_button_ctrl.png|" +msgstr "" + +#: ../../en/refs/m5ui.buttonmatrix.ref:12 c463c2a21a9b44e9af94c3bb58ea0b7a +msgid "set_button_ctrl.png" +msgstr "" + +#: 2ade3b61acf34022a87f4d5a10ce7445 +#: m5ui.buttonmatrix.M5ButtonMatrix.toggle_button_ctrl:1 of +msgid "Toggle control flags for a specific button." +msgstr "切换特定按钮的控制标志。" + +#: fcb4fe6c55744da0884785e0f2dce691 +#: m5ui.buttonmatrix.M5ButtonMatrix.toggle_button_ctrl:3 of +msgid "The button ID to toggle control flags for." +msgstr "要切换控制标志的按钮ID。" + +#: c56d38fbcdbd46509a42c75153ef7ad8 +#: m5ui.buttonmatrix.M5ButtonMatrix.toggle_button_ctrl:4 of +msgid "The control flags to toggle." +msgstr "要切换的控制标志。" + +#: 8a184cd855b34277ad7327e49aca8b7a +#: m5ui.buttonmatrix.M5ButtonMatrix.toggle_button_ctrl:8 of +msgid "|toggle_button_ctrl.png|" +msgstr "" + +#: ../../en/refs/m5ui.buttonmatrix.ref:25 5612193223184918a9e0af0d7ccbb901 +msgid "toggle_button_ctrl.png" +msgstr "" + +#: e8f727ee3a774960abfaf3e476b1f29f +#: m5ui.buttonmatrix.M5ButtonMatrix.set_textarea:1 of +msgid "Set a M5TextArea to display button text." +msgstr "设置M5TextArea以显示按钮文本。" + +#: 0d3f3197228243968e3796eb9052d808 +#: m5ui.buttonmatrix.M5ButtonMatrix.set_textarea:3 of +msgid "The M5TextArea to set." +msgstr "要设置的M5TextArea。" + +#: 1197b3d677ae456bafe004025c9021fe +#: m5ui.buttonmatrix.M5ButtonMatrix.set_textarea:7 of +msgid "|set_textarea.png|" +msgstr "" + +#: ../../en/refs/m5ui.buttonmatrix.ref:21 1384e3ff95e2497baeef20489ebd81e9 +msgid "set_textarea.png" +msgstr "" + +#: 2e71884ccb414638bd34087d55dba57a +#: m5ui.buttonmatrix.M5ButtonMatrix.get_textarea:1 of +msgid "Get the currently set M5TextArea." +msgstr "获取当前设置的M5TextArea。" + +#: 5b304a9de35d4e02adb75fe6d74aabf4 +#: m5ui.buttonmatrix.M5ButtonMatrix.get_textarea:3 of +msgid "The M5TextArea currently set for the button matrix." +msgstr "当前为按钮矩阵设置的M5TextArea。" + +#: 8f05b93ccadb4717810f4b4e7a931c38 +#: m5ui.buttonmatrix.M5ButtonMatrix.get_textarea:8 of +msgid "|get_textarea.png|" +msgstr "" + +#: ../../en/refs/m5ui.buttonmatrix.ref:10 372e9c2854ed429db5133d3a7ae264bd +msgid "get_textarea.png" +msgstr "" + diff --git a/examples/m5ui/buttonmatrix/cores3_buttonmatrix_basic_example.m5f2 b/examples/m5ui/buttonmatrix/cores3_buttonmatrix_basic_example.m5f2 new file mode 100644 index 00000000..724a3bc7 --- /dev/null +++ b/examples/m5ui/buttonmatrix/cores3_buttonmatrix_basic_example.m5f2 @@ -0,0 +1 @@ +{"version":"V2.0","versionNumber":"V2.3.3","type":"core2","components":[{"name":"page0","type":"lvgl_page","layer":0,"screenId":"builtin","screenName":"","id":"w#OUTx5g2=WUes=i","createTime":1755596357827,"backgroundColor":"#ffffff","isLVGL":true,"isSelected":true},{"name":"textarea0","type":"lvgl_textarea","layer":1,"screenId":"builtin","screenName":"","id":"h*==WkzQoUKv*z$1","createTime":1755596420739,"x":24,"y":15,"width":150,"height":70,"color":"#212121","borderColor":"#e0e0e0","backgroundColor":"#ffffff","text":"","placeholder":"Placeholder...","font":"lv.font_montserrat_14","pageId":"w#OUTx5g2=WUes=i","isLVGL":true,"isSelected":false},{"name":"buttonmatrix0","type":"lvgl_buttonmatrix","layer":2,"screenId":"builtin","screenName":"","id":"n+@+k6w_5RL-ZEJs","createTime":1755596423752,"x":25,"y":100,"width":260,"height":130,"keyList":[["0","1","2","4"],["5","6","7","8","9"]],"targetTextarea":"h*==WkzQoUKv*z$1","pageId":"w#OUTx5g2=WUes=i","isLVGL":true,"isSelected":false},{"name":"label0","type":"lvgl_label","layer":3,"screenId":"builtin","screenName":"","id":"vWXox&Pi7i#P@4az","createTime":1755656081220,"x":189,"y":15,"color":"#c9c9c9","backgroundColor":"#ffffff","bg_opacity":0,"text":"last key:","font":"lv.font_montserrat_14","pageId":"w#OUTx5g2=WUes=i","isLVGL":true,"isSelected":false,"width":56,"height":15},{"name":"label1","type":"lvgl_label","layer":4,"screenId":"builtin","screenName":"","id":"w`*DcO^huctVu6Lc","createTime":1755656142705,"x":203,"y":42,"color":"#000000","backgroundColor":"#ffffff","bg_opacity":0,"text":"label1","font":"lv.font_montserrat_24","pageId":"w#OUTx5g2=WUes=i","isLVGL":true,"isSelected":false,"width":67,"height":27}],"resources":[{"hardware":["hardware_button","hardware_pin_button","imu","speaker","touch","mic","sdcard"]}],"units":[],"hats":[],"bases":[],"i2cs":[],"blockly":"truepage0truebuttonmatrix0label1hello M5buttonmatrix00buttonmatrix0","screen":[{"simulationName":"Built-in","type":"builtin","width":320,"height":240,"scale":0.77,"screenName":"","blockId":"","screenColorType":0,"rotation":1,"id":"builtin","createTime":1755596357824}],"logicWhenNum":0,"customList":[]} \ No newline at end of file diff --git a/examples/m5ui/buttonmatrix/cores3_buttonmatrix_basic_example.py b/examples/m5ui/buttonmatrix/cores3_buttonmatrix_basic_example.py new file mode 100644 index 00000000..306636e2 --- /dev/null +++ b/examples/m5ui/buttonmatrix/cores3_buttonmatrix_basic_example.py @@ -0,0 +1,104 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import os, sys, io +import M5 +from M5 import * +import m5ui +import lvgl as lv + + +page0 = None +textarea0 = None +buttonmatrix0 = None +label0 = None +label1 = None + + +def buttonmatrix0_value_changed_event(event_struct): + global page0, textarea0, buttonmatrix0, label0, label1 + label1.set_text(str(buttonmatrix0.get_button_text(buttonmatrix0.get_selected_button()))) + + +def buttonmatrix0_event_handler(event_struct): + global page0, textarea0, buttonmatrix0, label0, label1 + event = event_struct.code + if event == lv.EVENT.VALUE_CHANGED and True: + buttonmatrix0_value_changed_event(event_struct) + return + + +def setup(): + global page0, textarea0, buttonmatrix0, label0, label1 + + M5.begin() + Widgets.setRotation(1) + m5ui.init() + page0 = m5ui.M5Page(bg_c=0xFFFFFF) + textarea0 = m5ui.M5TextArea( + text="", + placeholder="Placeholder...", + x=24, + y=15, + w=150, + h=70, + font=lv.font_montserrat_14, + bg_c=0xFFFFFF, + border_c=0xE0E0E0, + text_c=0x212121, + parent=page0, + ) + buttonmatrix0 = m5ui.M5ButtonMatrix( + ["0", "1", "2", "4", "\n", "5", "6", "7", "8", "9"], + x=25, + y=100, + w=260, + h=130, + target_textarea=textarea0, + parent=page0, + ) + label0 = m5ui.M5Label( + "last key:", + x=189, + y=15, + text_c=0xC9C9C9, + bg_c=0xFFFFFF, + bg_opa=0, + font=lv.font_montserrat_14, + parent=page0, + ) + label1 = m5ui.M5Label( + "label1", + x=203, + y=42, + text_c=0x000000, + bg_c=0xFFFFFF, + bg_opa=0, + font=lv.font_montserrat_24, + parent=page0, + ) + + buttonmatrix0.add_event_cb(buttonmatrix0_event_handler, lv.EVENT.ALL, None) + + page0.screen_load() + + +def loop(): + global page0, textarea0, buttonmatrix0, label0, label1 + M5.update() + + +if __name__ == "__main__": + try: + setup() + while True: + loop() + except (Exception, KeyboardInterrupt) as e: + try: + m5ui.deinit() + from utility import print_error_msg + + print_error_msg(e) + except ImportError: + print("please update to latest firmware") diff --git a/m5stack/libs/m5ui/__init__.py b/m5stack/libs/m5ui/__init__.py index d4c24b37..63027bc7 100644 --- a/m5stack/libs/m5ui/__init__.py +++ b/m5stack/libs/m5ui/__init__.py @@ -8,6 +8,7 @@ "M5Arc": "arc", "M5Bar": "bar", "M5Button": "button", + "M5ButtonMatrix": "buttonmatrix", "M5Calendar": "calendar", "M5Canvas": "canvas", "M5Checkbox": "checkbox", diff --git a/m5stack/libs/m5ui/buttonmatrix.py b/m5stack/libs/m5ui/buttonmatrix.py new file mode 100644 index 00000000..a77d63ab --- /dev/null +++ b/m5stack/libs/m5ui/buttonmatrix.py @@ -0,0 +1,129 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +from .base import M5Base +import lvgl as lv + + +class M5ButtonMatrix(lv.buttonmatrix): + """Create a button matrix object. + + :param list map: A list of button labels. Use "\\\\n" to create a new row. + :param int x: The x position of the button matrix. + :param int y: The y position of the button matrix. + :param int w: The width of the button matrix. + :param int h: The height of the button matrix. + :param m5ui.M5TextArea target_textarea: A M5TextArea to display the button text when a button is pressed. + :param lv.obj parent: The parent object to attach the button matrix to. + + UiFlow2 Code Block: + + None + + MicroPython Code Block: + + .. code-block:: python + + import m5ui + import lvgl as lv + + m5ui.init() + page0 = m5ui.M5Page() + page0.screen_load() + textarea0 = m5ui.M5TextArea(x=10, y=10, w=200, h=60, parent=page0) + buttonmatrix_0 = m5ui.M5ButtonMatrix( + ["0", "1", "2", "3", "4","\\n", "5", "6", "7", "8", "9",], + x=10, y=80, w=260, h=130, + target_textarea=textarea0, + parent=page0 + ) + + """ + + def __init__( + self, + map, + x=0, + y=0, + w=260, + h=130, + target_textarea=None, + parent=None, + ): + super().__init__(parent) + self.set_map(map) + self.set_pos(x, y) + self.set_size(w, h) + + self.add_event_cb(self.value_changed_event, lv.EVENT.VALUE_CHANGED, None) + self.textarea = target_textarea # To hold a reference to a M5TextArea if set + + def value_changed_event(self, event_struct): + btn_id = self.get_selected_button() + if self.textarea: + self.textarea.add_text(self.get_button_text(btn_id)) + + def toggle_button_ctrl(self, btn_id, ctrl): + """Toggle control flags for a specific button. + + :param int btn_id: The button ID to toggle control flags for. + :param int ctrl: The control flags to toggle. + + UiFlow2 Code Block: + + |toggle_button_ctrl.png| + + MicroPython Code Block: + + .. code-block:: python + + buttonmatrix_0.toggle_button_ctrl(0, lv.buttonmatrix.CTRL.HIDDEN) + """ + if self.has_button_ctrl(btn_id, ctrl): + self.clear_button_ctrl(btn_id, ctrl) + else: + self.set_button_ctrl(btn_id, ctrl) + + def set_textarea(self, textarea): + """Set a M5TextArea to display button text. + + :param m5ui.M5TextArea textarea: The M5TextArea to set. + + UiFlow2 Code Block: + + |set_textarea.png| + + MicroPython Code Block: + + .. code-block:: python + + buttonmatrix_0.set_textarea(textarea0) + """ + self.textarea = textarea + + def get_textarea(self): + """Get the currently set M5TextArea. + + :return: The M5TextArea currently set for the button matrix. + :rtype: m5ui.M5TextArea + + UiFlow2 Code Block: + + |get_textarea.png| + + MicroPython Code Block: + + .. code-block:: python + + textarea = buttonmatrix_0.get_textarea() + """ + return self.textarea + + def __getattr__(self, name): + if hasattr(M5Base, name): + method = getattr(M5Base, name) + bound_method = lambda *args, **kwargs: method(self, *args, **kwargs) + setattr(self, name, bound_method) + return bound_method + raise AttributeError(f"'{self.__class__.__name__}' object has no attribute '{name}'") diff --git a/m5stack/libs/m5ui/manifest.py b/m5stack/libs/m5ui/manifest.py index 83665356..3fc4abdd 100644 --- a/m5stack/libs/m5ui/manifest.py +++ b/m5stack/libs/m5ui/manifest.py @@ -10,6 +10,7 @@ "bar.py", "base.py", "button.py", + "buttonmatrix.py", "calendar.py", "canvas.py", "checkbox.py", diff --git a/tests/m5ui/test_buttonmatrix.py b/tests/m5ui/test_buttonmatrix.py new file mode 100644 index 00000000..46232bb4 --- /dev/null +++ b/tests/m5ui/test_buttonmatrix.py @@ -0,0 +1,56 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import lvgl as lv +import sys + +sys.path.append("../../m5stack/libs") +import m5ui +import unittest + + +class Test(unittest.TestCase): + def __init__(self) -> None: + super().__init__() + m5ui.init() + page0 = m5ui.M5Page() + self.textarea0 = m5ui.M5TextArea(x=10, y=10, w=200, h=60, parent=page0) + self.buttonmatrix0 = m5ui.M5ButtonMatrix( + ["0", "1", "2", "4", "\n", "5", "6", "7", "8", "9"], + x=25, + y=100, + w=260, + h=130, + target_textarea=self.textarea0, + parent=page0, + ) + page0.screen_load() + + def test_get_textarea(self): + self.buttonmatrix0.set_textarea(None) + self.assertIsNone(self.buttonmatrix0.textarea) + self.buttonmatrix0.set_textarea(self.textarea0) + self.assertIsInstance(self.buttonmatrix0.textarea, m5ui.M5TextArea) + + def test_toggle_button_ctrl(self): + self.buttonmatrix0.toggle_button_ctrl(0, lv.buttonmatrix.CTRL.NO_REPEAT) + self.assertTrue(self.buttonmatrix0.has_button_ctrl(0, lv.buttonmatrix.CTRL.NO_REPEAT)) + + def test_text_area_update(self): + self.textarea0.set_text("") + self.buttonmatrix0.set_selected_button(0) + self.buttonmatrix0.send_event(lv.EVENT.VALUE_CHANGED, None) + self.assertEqual(self.textarea0.get_text(), "0") + + self.buttonmatrix0.set_selected_button(1) + self.buttonmatrix0.send_event(lv.EVENT.VALUE_CHANGED, None) + self.assertEqual(self.textarea0.get_text(), "01") + + self.buttonmatrix0.set_selected_button(2) + self.buttonmatrix0.send_event(lv.EVENT.VALUE_CHANGED, None) + self.assertEqual(self.textarea0.get_text(), "012") + + +if __name__ == "__main__": + unittest.main() From 5c354dc9e49c70e5f878874d2167dc19d8b609ad Mon Sep 17 00:00:00 2001 From: lbuque <1102390310@qq.com> Date: Mon, 11 Aug 2025 11:48:34 +0800 Subject: [PATCH 214/322] libs/m5ui: Add M5Roller support. Signed-off-by: lbuque <1102390310@qq.com> --- docs/en/m5ui/index.rst | 1 + docs/en/m5ui/roller.rst | 438 ++++++++++++ docs/en/refs/m5ui.roller.ref | 36 + docs/locales/zh_CN/LC_MESSAGES/m5ui/roller.po | 652 ++++++++++++++++++ .../roller/cores3_roller_basic_example.m5f2 | 1 + .../roller/cores3_roller_basic_example.py | 66 ++ m5stack/libs/m5ui/__init__.py | 3 +- m5stack/libs/m5ui/manifest.py | 3 +- m5stack/libs/m5ui/roller.py | 155 +++++ tests/m5ui/test_roller.py | 39 ++ 10 files changed, 1392 insertions(+), 2 deletions(-) create mode 100644 docs/en/m5ui/roller.rst create mode 100644 docs/en/refs/m5ui.roller.ref create mode 100644 docs/locales/zh_CN/LC_MESSAGES/m5ui/roller.po create mode 100644 examples/m5ui/roller/cores3_roller_basic_example.m5f2 create mode 100644 examples/m5ui/roller/cores3_roller_basic_example.py create mode 100644 m5stack/libs/m5ui/roller.py create mode 100644 tests/m5ui/test_roller.py diff --git a/docs/en/m5ui/index.rst b/docs/en/m5ui/index.rst index 5c0a0e6d..6ddd937f 100644 --- a/docs/en/m5ui/index.rst +++ b/docs/en/m5ui/index.rst @@ -46,6 +46,7 @@ Classes label.rst line.rst list.rst + roller.rst scale.rst slider.rst switch.rst diff --git a/docs/en/m5ui/roller.rst b/docs/en/m5ui/roller.rst new file mode 100644 index 00000000..24e45fed --- /dev/null +++ b/docs/en/m5ui/roller.rst @@ -0,0 +1,438 @@ +.. currentmodule:: m5ui + +M5Roller +======== + +.. include:: ../refs/m5ui.roller.ref + +M5Roller is a widget that can be used to create a roller (spinner/wheel picker) in the +user interface. It provides a scrollable list of options that users can select from by +scrolling up or down, similar to iOS-style picker wheels. + + +UiFlow2 Example +--------------- + +basic roller +^^^^^^^^^^^^ + +Open the |cores3_roller_basic_example.m5f2| project in UiFlow2. + +This example demonstrates how to create a roller with multiple options and handle selection events. + +UiFlow2 Code Block: + + |cores3_roller_basic_example.png| + +Example output: + + None + + +MicroPython Example +------------------- + +basic roller +^^^^^^^^^^^^ + +This example demonstrates how to create a roller with multiple options and handle selection events. + +MicroPython Code Block: + + .. literalinclude:: ../../../examples/m5ui/roller/cores3_roller_basic_example.py + :language: python + :linenos: + +Example output: + + None + + +**API** +------- + +M5Roller +^^^^^^^^ + +.. autoclass:: m5ui.roller.M5Roller + :members: + + .. py:method:: set_flag(flag, value) + + Set a flag on the object. If ``value`` is True, the flag is added; if False, the flag is removed. + + :param int flag: The flag to set. + :param bool value: If True, the flag is added; if False, the flag is removed. + :return: None + + UiFlow2 Code Block: + + |set_flag.png| + + MicroPython Code Block: + + .. code-block:: python + + roller_0.set_flag(lv.obj.FLAG.HIDDEN, True) + + + .. py:method:: toggle_flag(flag) + + Toggle a flag on the object. If the flag is set, it is removed; if not set, it is added. + + :param int flag: The flag to toggle. + :return: None + + UiFlow2 Code Block: + + |toggle_flag.png| + + MicroPython Code Block: + + .. code-block:: python + + roller_0.toggle_flag(lv.obj.FLAG.HIDDEN) + + + .. py:method:: set_state(state, value) + + Set the state of the roller. If ``value`` is True, the state is set; if False, the state is unset. + + :param int state: The state to set. + :param bool value: If True, the state is set; if False, the state is unset. + :return: None + + UiFlow2 Code Block: + + |set_state.png| + + MicroPython Code Block: + + .. code-block:: python + + roller_0.set_state(lv.STATE.CHECKED, True) + + + .. py:method:: toggle_state(state) + + Toggle the state of the roller. If the state is set, it is unset; if not set, it is set. + + :param int state: The state to toggle. + :return: None + + UiFlow2 Code Block: + + |toggle_state.png| + + MicroPython Code Block: + + .. code-block:: python + + roller_0.toggle_state(lv.STATE.CHECKED) + + + .. py:method:: event(callback, event, user_data=None) + + Add an event callback to the roller. The callback will be called when the specified event occurs. + + :param callback: The callback function to call. + :param int event: The event to listen for. + :param user_data: Optional user data to pass to the callback. + :return: None + + UiFlow2 Code Block: + + |event.png| + + MicroPython Code Block: + + .. code-block:: python + + def roller_callback(event_obj): + print("Roller value changed") + + roller_0.event(roller_callback, lv.EVENT.VALUE_CHANGED) + + + .. py:method:: set_bg_color(color, opa, part) + + Set the background color of the roller. + + :param int color: The color to set. + :param int opa: The opacity of the color. + :param int part: The part of the object to apply the style to (e.g., lv.PART.MAIN). + :return: None + + UiFlow2 Code Block: + + |set_bg_color.png| + + MicroPython Code Block: + + .. code-block:: python + + roller_0.set_bg_color(lv.color_hex(0x000000), 255, lv.PART.MAIN | lv.STATE.DEFAULT) + + roller_0.set_bg_color(lv.color_hex(0x000000), 255, lv.PART.SELECTED | lv.STATE.DEFAULT) + + + .. py:method:: set_border_color(color, opa, part) + + Set the border color of the roller. + + :param int color: The color to set. + :param int opa: The opacity of the color. The value should be between 0 (transparent) and 255 (opaque). + :param int part: The part of the object to apply the style to (e.g., lv.PART.MAIN). + :return: None + + UiFlow2 Code Block: + + |set_border_color.png| + + MicroPython Code Block: + + .. code-block:: python + + roller_0.set_border_color(lv.color_hex(0x2196F3), 255, lv.PART.MAIN | lv.STATE.DEFAULT) + roller_0.set_border_color(lv.color_hex(0x2196F3), 255, lv.PART.SELECTED | lv.STATE.DEFAULT) + + + .. py:method:: set_style_border_width(width, part) + + Set the border width of the roller. + + :param int width: The width to set. + :param int part: The part of the object to apply the style to (e.g., lv.PART.MAIN). + :return: None + + UiFlow2 Code Block: + + |set_style_border_width.png| + + MicroPython Code Block: + + .. code-block:: python + + roller_0.set_style_border_width(2, lv.PART.MAIN | lv.STATE.DEFAULT) + roller_0.set_style_border_width(2, lv.PART.SELECTED | lv.STATE.DEFAULT) + + + .. py:method:: get_option_count() + + Get the total number of options in the roller. + + :return: The number of options. + :rtype: int + + UiFlow2 Code Block: + + |get_option_count.png| + + MicroPython Code Block: + + .. code-block:: python + + option_count = roller_0.get_option_count() + + + .. py:method:: get_selected() + + Get the index of the currently selected option. + + :return: The index of the selected option. + :rtype: int + + UiFlow2 Code Block: + + |get_selected.png| + + MicroPython Code Block: + + .. code-block:: python + + selected_index = roller_0.get_selected() + + + .. py:method:: set_visible_row_count(count) + + Set the number of visible rows in the roller. + + :param int count: The number of visible rows. + :return: None + + UiFlow2 Code Block: + + |set_visible_row_count.png| + + MicroPython Code Block: + + .. code-block:: python + + roller_0.set_visible_row_count(3) + + + .. py:method:: set_pos(x, y) + + Set the position of the roller. + + :param int x: The x-coordinate of the roller. + :param int y: The y-coordinate of the roller. + :return: None + + UiFlow2 Code Block: + + |set_pos.png| + + MicroPython Code Block: + + .. code-block:: python + + roller_0.set_pos(100, 50) + + + .. py:method:: set_x(x) + + Set the x-coordinate of the roller. + + :param int x: The x-coordinate of the roller. + :return: None + + UiFlow2 Code Block: + + |set_x.png| + + MicroPython Code Block: + + .. code-block:: python + + roller_0.set_x(100) + + + .. py:method:: set_y(y) + + Set the y-coordinate of the roller. + + :param int y: The y-coordinate of the roller. + :return: None + + UiFlow2 Code Block: + + |set_y.png| + + MicroPython Code Block: + + .. code-block:: python + + roller_0.set_y(50) + + + .. py:method:: align_to(obj, align, x_ofs=0, y_ofs=0) + + Align the roller to another object. + + :param lv.obj obj: The object to align to. + :param int align: The alignment type. + :param int x_ofs: The x-offset from the aligned object. + :param int y_ofs: The y-offset from the aligned object. + :return: None + + UiFlow2 Code Block: + + |align_to.png| + + MicroPython Code Block: + + .. code-block:: python + + roller_0.align_to(other_obj, lv.ALIGN.CENTER, 0, 0) + + + .. py:method:: set_size(w, h) + + Set the size of the roller. + + :param int w: The width of the roller. + :param int h: The height of the roller. + :return: None + + UiFlow2 Code Block: + + |set_size.png| + + MicroPython Code Block: + + .. code-block:: python + + roller_0.set_size(150, 120) + + + .. py:method:: set_width(w) + + Set the width of the roller. + + :param int w: The width of the roller. + :return: None + + UiFlow2 Code Block: + + |set_width.png| + + MicroPython Code Block: + + .. code-block:: python + + roller_0.set_width(150) + + + .. py:method:: get_width() + + Get the width of the roller. + + :return: The width of the roller. + :rtype: int + + UiFlow2 Code Block: + + |get_width.png| + + MicroPython Code Block: + + .. code-block:: python + + width = roller_0.get_width() + + + .. py:method:: set_height(h) + + Set the height of the roller. + + :param int h: The height of the roller. + :return: None + + UiFlow2 Code Block: + + |set_height.png| + + MicroPython Code Block: + + .. code-block:: python + + roller_0.set_height(120) + + + .. py:method:: get_height() + + Get the height of the roller. + + :return: The height of the roller. + :rtype: int + + UiFlow2 Code Block: + + |get_height.png| + + MicroPython Code Block: + + .. code-block:: python + + height = roller_0.get_height() diff --git a/docs/en/refs/m5ui.roller.ref b/docs/en/refs/m5ui.roller.ref new file mode 100644 index 00000000..ff121ad7 --- /dev/null +++ b/docs/en/refs/m5ui.roller.ref @@ -0,0 +1,36 @@ +.. |align_to.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/roller/align_to.png +.. |event.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/roller/event.png +.. |get_height.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/roller/get_height.png +.. |get_option_count.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/roller/get_option_count.png +.. |get_options.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/roller/get_options.png +.. |get_selected.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/roller/get_selected.png +.. |get_selected_str.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/roller/get_selected_str.png +.. |get_width.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/roller/get_width.png +.. |set_bg_color.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/roller/set_bg_color.png +.. |set_border_color.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/roller/set_border_color.png +.. |set_default_size.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/roller/set_default_size.png +.. |set_flag.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/roller/set_flag.png +.. |set_height.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/roller/set_height.png +.. |set_options.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/roller/set_options.png +.. |set_pos.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/roller/set_pos.png +.. |set_size.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/roller/set_size.png +.. |set_state.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/roller/set_state.png +.. |set_style_border_width.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/roller/set_style_border_width.png +.. |set_style_radius.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/roller/set_style_radius.png +.. |set_visible_row_count.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/roller/set_visible_row_count.png +.. |set_width.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/roller/set_width.png +.. |set_x.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/roller/set_x.png +.. |set_y.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/roller/set_y.png +.. |toggle_flag.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/roller/toggle_flag.png +.. |toggle_state.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/roller/toggle_state.png + +.. |cores3_roller_basic_example.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/roller/cores3_roller_basic_example.png + +.. |cores3_roller_basic_example.m5f2| raw:: html + + + cores3_roller_basic_example.m5f2 + diff --git a/docs/locales/zh_CN/LC_MESSAGES/m5ui/roller.po b/docs/locales/zh_CN/LC_MESSAGES/m5ui/roller.po new file mode 100644 index 00000000..2962d1b9 --- /dev/null +++ b/docs/locales/zh_CN/LC_MESSAGES/m5ui/roller.po @@ -0,0 +1,652 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2016 - 2025 M5Stack Technology Co., Ltd +# This file is distributed under the same license as the UIFlow2 Programming +# Guide package. +# FIRST AUTHOR , 2025. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: UIFlow2 Programming Guide \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-08-20 16:20+0800\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: zh_CN\n" +"Language-Team: zh_CN \n" +"Plural-Forms: nplurals=1; plural=0;\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.17.0\n" + +#: ../../en/m5ui/roller.rst:4 ../../en/m5ui/roller.rst:55 +#: 03aefaf61d89499baf7f73bc451f9489 3c6adfc5e58f4324b856c0bec5337562 +msgid "M5Roller" +msgstr "" + +#: ../../en/m5ui/roller.rst:8 f8de2edb47414ea18db9c77a3e8b0f56 +msgid "" +"M5Roller is a widget that can be used to create a roller (spinner/wheel " +"picker) in the user interface. It provides a scrollable list of options " +"that users can select from by scrolling up or down, similar to iOS-style " +"picker wheels." +msgstr "M5Roller是一个可用于在用户界面中创建滚轮(旋转器/滚轮选择器)的控件。它提供了一个可滚动的选项列表,用户可以通过向上或向下滚动来选择,类似于iOS风格的选择器滚轮。" + +#: ../../en/m5ui/roller.rst:14 00e3bd9b460d442cbdaf611045d338cc +msgid "UiFlow2 Example" +msgstr "UiFlow2 示例" + +#: ../../en/m5ui/roller.rst:17 ../../en/m5ui/roller.rst:36 +#: 66d4206f0546410c8a838c96010a3972 983ecbc60b2b46939afb847cee060f16 +msgid "basic roller" +msgstr "基础滚轮" + +#: ../../en/m5ui/roller.rst:19 6308a7a1e1d441f5966889d01a52d786 +msgid "Open the |cores3_roller_basic_example.m5f2| project in UiFlow2." +msgstr "在UiFlow2中打开 |cores3_roller_basic_example.m5f2| 项目。" + +#: ../../en/m5ui/roller.rst:21 ../../en/m5ui/roller.rst:38 +#: 65d26675a441467facc29d964699367e 843af05dee4647e6bb5eb51ded7208e8 +msgid "" +"This example demonstrates how to create a roller with multiple options " +"and handle selection events." +msgstr "此示例演示如何创建具有多个选项的滚轮并处理选择事件。" + +#: ../../en/m5ui/roller.rst:23 ../../en/m5ui/roller.rst:68 +#: ../../en/m5ui/roller.rst:86 ../../en/m5ui/roller.rst:105 +#: ../../en/m5ui/roller.rst:123 ../../en/m5ui/roller.rst:143 +#: ../../en/m5ui/roller.rst:166 ../../en/m5ui/roller.rst:188 +#: ../../en/m5ui/roller.rst:208 ../../en/m5ui/roller.rst:227 +#: ../../en/m5ui/roller.rst:245 ../../en/m5ui/roller.rst:263 +#: ../../en/m5ui/roller.rst:282 ../../en/m5ui/roller.rst:300 +#: ../../en/m5ui/roller.rst:318 ../../en/m5ui/roller.rst:339 +#: ../../en/m5ui/roller.rst:358 ../../en/m5ui/roller.rst:376 +#: ../../en/m5ui/roller.rst:394 ../../en/m5ui/roller.rst:412 +#: ../../en/m5ui/roller.rst:430 00d1cc73792549d7b600de5480ac41ca +#: 150d4ae6f15c49b6b83c0b8442648b1e 1cd06a22831b4a17bdd08f67f85dc250 +#: 2fa6c1c63b554af1950db23cc26d5291 364f596f547a4dfb92643687faa98be9 +#: 445f3627fe39467aa2fbd0d4f4788118 460f565e83d64fd3af95ad0d2bf65e4a +#: 48e01fdf8cb9424b8776000f13e32703 54271383760e4fe5a0e93ce021e5c77b +#: 59fa6f1ad4b241eea2d30f9c08df804f 7317fb14e9064ea1b058c94c1f53ffd8 +#: 922fe8cf729644afa91b6bb15c8663c7 97b8d82de0e24543b1e2ecd86f9e6a97 +#: 9ea387244f284575972cee1c87cda4c4 c5474666859549a0afeee27e24d2b332 +#: cd318783e3134535929cebc498f2d677 d58dbdb189894d9d903bbc317416c06b +#: fa4f095e542c40ceb9165795a30ac610 m5ui.roller.M5Roller:15 +#: m5ui.roller.M5Roller.get_options:6 m5ui.roller.M5Roller.get_selected_str:5 +#: m5ui.roller.M5Roller.set_options:6 m5ui.roller.M5Roller.set_style_radius:7 +#: of +msgid "UiFlow2 Code Block:" +msgstr "UiFlow2 代码块:" + +#: ../../en/m5ui/roller.rst:25 9b96c48b3cfa47cfa0f6a924c2523626 +msgid "|cores3_roller_basic_example.png|" +msgstr "" + +#: ../../en/refs/m5ui.roller.ref:27 4db04f298f0b48519b52d820e59415e1 +msgid "cores3_roller_basic_example.png" +msgstr "" + +#: ../../en/m5ui/roller.rst:27 ../../en/m5ui/roller.rst:46 +#: 2fedc330b1ad48d782a86cb96fe13c58 a1cf058dc9f640309f83c5aad07dbbaa +msgid "Example output:" +msgstr "示例输出:" + +#: ../../en/m5ui/roller.rst:29 ../../en/m5ui/roller.rst:48 +#: ../../en/m5ui/roller.rst:66 ../../en/m5ui/roller.rst:84 +#: ../../en/m5ui/roller.rst:103 ../../en/m5ui/roller.rst:121 +#: ../../en/m5ui/roller.rst:141 ../../en/m5ui/roller.rst:164 +#: ../../en/m5ui/roller.rst:186 ../../en/m5ui/roller.rst:206 +#: ../../en/m5ui/roller.rst:261 ../../en/m5ui/roller.rst:280 +#: ../../en/m5ui/roller.rst:298 ../../en/m5ui/roller.rst:316 +#: ../../en/m5ui/roller.rst:337 ../../en/m5ui/roller.rst:356 +#: ../../en/m5ui/roller.rst:374 ../../en/m5ui/roller.rst:410 +#: 1e233d688cd64517aaec5c9b73fda8cb 3cde07dc91bc41dd8f3ddf1405ef9d23 +#: 48c68a56f3fd4936af73dcb93254971b 4c20bcb3b3cf407e9d198b003c8800be +#: 5de223d03ec644a5a89ab38dc77c8769 6cc45b6993fd4dd89446b566acec6694 +#: 8b6291073ce64d46b7da19c9cd7452e4 9d5040c5e8bf4e5992dfe2f517fae646 +#: c80f955e3aac4f2d9b4ddc569569aaf8 e056e23663bd45c38fd12ed9975e0199 +#: fdbb31fc824d47c5b83b699cd46e8aa4 ff69cdc6e1764141968cb7aa644fc97d +#: m5ui.roller.M5Roller:17 m5ui.roller.M5Roller.set_style_radius:5 of +msgid "None" +msgstr "" + +#: ../../en/m5ui/roller.rst:33 790fa83906e249449dbd10dce3817e6a +msgid "MicroPython Example" +msgstr "MicroPython 示例" + +#: ../../en/m5ui/roller.rst:40 ../../en/m5ui/roller.rst:72 +#: ../../en/m5ui/roller.rst:90 ../../en/m5ui/roller.rst:109 +#: ../../en/m5ui/roller.rst:127 ../../en/m5ui/roller.rst:147 +#: ../../en/m5ui/roller.rst:170 ../../en/m5ui/roller.rst:192 +#: ../../en/m5ui/roller.rst:212 ../../en/m5ui/roller.rst:231 +#: ../../en/m5ui/roller.rst:249 ../../en/m5ui/roller.rst:267 +#: ../../en/m5ui/roller.rst:286 ../../en/m5ui/roller.rst:304 +#: ../../en/m5ui/roller.rst:322 ../../en/m5ui/roller.rst:343 +#: ../../en/m5ui/roller.rst:362 ../../en/m5ui/roller.rst:380 +#: ../../en/m5ui/roller.rst:398 ../../en/m5ui/roller.rst:416 +#: ../../en/m5ui/roller.rst:434 122f63549d6349e2b982449666f03f0a +#: 15184d078fad4c3bad724d7a91d5f752 17f7381012b344faa61d7c2a6fadd34b +#: 18c11a799ece4b9793769af94e5bb841 2016aa51e15149ef9261ba1b12899b50 +#: 23b2d5f5ea3b487dba711db84419fa54 23bf4f192f0d4313a9f6252fb4759b12 +#: 399cd361aaea4837854bf0073645950f 4591144c269d4804915591cc44f97fab +#: 50349280d9f247c1a79b2234b4cdb08a 6953f30c075147e29787a2ec2fb34aa3 +#: 85cc4d43275449fbb3cfdc9f359c5382 92875f78278744aeb636a14c9a824841 +#: ae424a7b8b11424792c2f37a9b444a2b c94bed96f58b4177bd5ada70bee785c7 +#: e7fb84f0fb6f446d92028daa01a0a963 e9e5e7e603d740e3beba550a5a8d52e3 +#: ef644d5b7a744f69acf17d273c940637 m5ui.roller.M5Roller:19 +#: m5ui.roller.M5Roller.get_options:10 m5ui.roller.M5Roller.get_selected_str:9 +#: m5ui.roller.M5Roller.set_options:10 m5ui.roller.M5Roller.set_style_radius:11 +#: of +msgid "MicroPython Code Block:" +msgstr "MicroPython 代码块:" + +#: ../../en/m5ui/roller.rst:52 1abc33be93484fe695e55162474ec757 +msgid "**API**" +msgstr "API参考" + +#: e93f7a2dc7b3425a9247526e9cafc3a0 m5ui.roller.M5Roller:1 of +msgid "Bases: :py:class:`~lvgl.roller`" +msgstr "" + +#: a2b59b2069494f249fdb0a4a60676bb4 m5ui.roller.M5Roller:1 of +msgid "Create a roller widget." +msgstr "创建一个滚轮控件。" + +#: ../../en/m5ui/roller.rst 60b9e97567f5479f8296bb04da4bc269 +#: 705a46e829fa4881a2c823d5be61fd68 79a3d47545944513afe48855a7da91e1 +#: 8b7845143c7c4f219de00ed86272b1cc 941ca056cee14b8085d2d361fcb6ceaa +#: b74b125d19ac4f49b1e346dee3c1170f de13638003aa4584a5105da66118616a +#: f3c77a6c4c914939a473af65c9f3aa90 f8d74ea25bc344aab29259cae8ab904a +#: fd1b258ae55f414bb9e10d9ba625e1fb m5ui.roller.M5Roller.set_options +#: m5ui.roller.M5Roller.set_style_radius of +msgid "Parameters" +msgstr "" + +#: b8c4a4137d084d1384fda80699b0b3a7 m5ui.roller.M5Roller:3 of +msgid "X position of the widget." +msgstr "控件的X位置。" + +#: 208ce15b2a9f4dbc81249fc7eccfbb01 m5ui.roller.M5Roller:4 of +msgid "Y position of the widget." +msgstr "控件的Y位置。" + +#: 3eb9238dd9f746ce8d56694963f7dd39 m5ui.roller.M5Roller:5 of +msgid "Width of the widget." +msgstr "控件的宽度。" + +#: f1f01dab795240b1a17b72b6222c2f51 m5ui.roller.M5Roller:6 of +msgid "Height of the widget." +msgstr "控件的高度。" + +#: 8201d10c644747c9a22aad41021a10e5 d4fc84ab03c740dfb594927c1e0d29e9 +#: m5ui.roller.M5Roller:7 m5ui.roller.M5Roller.set_options:3 of +msgid "List of options to display in the roller." +msgstr "要在滚轮中显示的选项列表。" + +#: 2eaf953632bc4496bcd124aebbcf41ce a6a28a169e384402bbab065faa44993b +#: m5ui.roller.M5Roller:8 m5ui.roller.M5Roller.set_options:4 of +msgid "Roller mode (default is NORMAL)." +msgstr "滚轮模式(默认为NORMAL)。" + +#: a1b10c8232fe4c02a058a6efdf428538 m5ui.roller.M5Roller:9 of +msgid "Index of the initially selected option." +msgstr "初始选中选项的索引。" + +#: 2bd7a18253a9431588a9d4150c825e1c m5ui.roller.M5Roller:10 of +msgid "Number of visible rows in the roller." +msgstr "滚轮中可见行数。" + +#: 54acc259d1624984837714d2a7bce3fa m5ui.roller.M5Roller:11 of +msgid "Font to use for the text in the roller." +msgstr "滚轮中文本使用的字体。" + +#: f6311311e8b34d5cb1d6cd43c584adca m5ui.roller.M5Roller:12 of +msgid "Parent widget to attach this roller to (default is the active screen)." +msgstr "要附加此滚轮的父控件(默认为活动屏幕)。" + +#: ../../en/m5ui/roller.rst:62 ba6689c80ec346e0961ddbe4d40c5ad7 +msgid "" +"Set a flag on the object. If ``value`` is True, the flag is added; if " +"False, the flag is removed." +msgstr "为对象设置标志。如果 ``value`` 为True,则添加标志;如果为False,则移除标志。" + +#: ../../en/m5ui/roller.rst:64 749a3b0aff7f4bb4b7d0fb44a9a2ae4a +msgid "The flag to set." +msgstr "要设置的标志。" + +#: ../../en/m5ui/roller.rst:65 da878c64c811402b8258b71d5f7b8def +msgid "If True, the flag is added; if False, the flag is removed." +msgstr "如果为True,则添加标志;如果为False,则移除标志。" + +#: ../../en/m5ui/roller.rst 1721e80c55fd48edaea8748da7c6cf9e +#: 2cfd2eab9ded49a7bcfd1809823b438d 36a6521260c149e9b930c69797826cdf +#: 3eb19df571db4804a8bd08435dfd82c5 57348c97a50d421db1e5b0334cc043ef +#: 59b902c2e401491595deeec51a41f045 674a89b323c847ebad35c599e1841a20 +#: 68bdd897b67141149d5a2fbb6107aa5e 8dc140070ef347c28c3673bc9b7afb56 +#: 915ac9452d954b95be2caa9609ca183a e010b23ae74a4c17bf291a4012774279 +#: e08b759b6b604f9fb82a4fa7a7fb23c1 m5ui.roller.M5Roller.get_options +#: m5ui.roller.M5Roller.get_selected_str m5ui.roller.M5Roller.set_style_radius +#: of +msgid "Returns" +msgstr "" + +#: ../../en/m5ui/roller.rst:70 ea50da8e345f4a61b3f6a7cecacd41fe +msgid "|set_flag.png|" +msgstr "" + +#: ../../en/refs/m5ui.roller.ref:12 e8a7eaf3bad04eb8bd8d0d35afd1460e +msgid "set_flag.png" +msgstr "" + +#: ../../en/m5ui/roller.rst:81 c9cf79181311405f940907261e015a6e +msgid "" +"Toggle a flag on the object. If the flag is set, it is removed; if not " +"set, it is added." +msgstr "切换对象的标志。如果标志已设置,则将其移除;如果未设置,则将其添加。" + +#: ../../en/m5ui/roller.rst:83 5dd46c570cd84f5cae7c0a50cbbf3c3b +msgid "The flag to toggle." +msgstr "要切换的标志。" + +#: ../../en/m5ui/roller.rst:88 e8acb0f76df644589753609dfb2c8222 +msgid "|toggle_flag.png|" +msgstr "" + +#: ../../en/refs/m5ui.roller.ref:24 96ba18576580402b9ae7121eb7f5fc4a +msgid "toggle_flag.png" +msgstr "" + +#: ../../en/m5ui/roller.rst:99 6d023bd86f3848538291a3ed26a182c4 +msgid "" +"Set the state of the roller. If ``value`` is True, the state is set; if " +"False, the state is unset." +msgstr "设置滚轮的状态。如果 ``value`` 为True,则设置状态;如果为False,则取消设置状态。" + +#: ../../en/m5ui/roller.rst:101 37bc3bc31bc1443a9d28601283480b01 +msgid "The state to set." +msgstr "要设置的状态。" + +#: ../../en/m5ui/roller.rst:102 22570de35e1f4ed9b3c1b80b51ae0ebb +msgid "If True, the state is set; if False, the state is unset." +msgstr "如果为True,则设置状态;如果为False,则取消设置状态。" + +#: ../../en/m5ui/roller.rst:107 2968bb55e5944dbca8634c2289ce9da3 +msgid "|set_state.png|" +msgstr "" + +#: ../../en/refs/m5ui.roller.ref:17 33adda7784464040b51104b535d3867a +msgid "set_state.png" +msgstr "" + +#: ../../en/m5ui/roller.rst:118 95e75435b6da4576abd5f1535d156587 +msgid "" +"Toggle the state of the roller. If the state is set, it is unset; if not " +"set, it is set." +msgstr "切换滚轮的状态。如果状态已设置,则取消设置;如果未设置,则将其设置。" + +#: ../../en/m5ui/roller.rst:120 913c2b56f48c499295ca2aa1665123fc +msgid "The state to toggle." +msgstr "要切换的状态。" + +#: ../../en/m5ui/roller.rst:125 29c00c9e797745808381b661f8f47ff4 +msgid "|toggle_state.png|" +msgstr "" + +#: ../../en/refs/m5ui.roller.ref:25 81a7f05f6f1a4ad481cad2dd914d9ad6 +msgid "toggle_state.png" +msgstr "" + +#: ../../en/m5ui/roller.rst:136 c05d566c009d43a8bdc902f5bc85f9ef +msgid "" +"Add an event callback to the roller. The callback will be called when the" +" specified event occurs." +msgstr "为滚轮添加事件回调。当指定事件发生时将调用回调函数。" + +#: ../../en/m5ui/roller.rst:138 c2c6bf8bcd2a45b991102441ca07b410 +msgid "The callback function to call." +msgstr "要调用的回调函数。" + +#: ../../en/m5ui/roller.rst:139 2e5955501b6f4700a02a470a44fa8dee +msgid "The event to listen for." +msgstr "要监听的事件。" + +#: ../../en/m5ui/roller.rst:140 bb7be24006fc43569e57422b893f454e +msgid "Optional user data to pass to the callback." +msgstr "传递给回调函数的可选用户数据。" + +#: ../../en/m5ui/roller.rst:145 9c2d88e3f9ad4fccab9d86adbdd5f9e2 +msgid "|event.png|" +msgstr "" + +#: ../../en/refs/m5ui.roller.ref:2 1946019a72ad43c4bd1079be50747e13 +msgid "event.png" +msgstr "" + +#: ../../en/m5ui/roller.rst:159 885368c6163a4dc8bc1b4ab1aa88eba1 +msgid "Set the background color of the roller." +msgstr "设置滚轮的背景颜色。" + +#: ../../en/m5ui/roller.rst:161 ../../en/m5ui/roller.rst:183 +#: 8c5cf9f5d8e74299bc187622d336b904 f070b8f19a784584b86ac98539e2a43b +msgid "The color to set." +msgstr "要设置的颜色。" + +#: ../../en/m5ui/roller.rst:162 655c136d4b184c40b2e09e85c7a99016 +msgid "The opacity of the color." +msgstr "颜色的不透明度。" + +#: ../../en/m5ui/roller.rst:163 ../../en/m5ui/roller.rst:185 +#: ../../en/m5ui/roller.rst:205 2a5f8351301643bfa5f9ee0c7d0c3b71 +#: 756ffa83fd014b0ebc30e39798b27acf a6f470c8a7da4d1ba821dc259ed67f68 +msgid "The part of the object to apply the style to (e.g., lv.PART.MAIN)." +msgstr "要应用样式的对象部分(例如:lv.PART.MAIN)。" + +#: ../../en/m5ui/roller.rst:168 75389e48a43847ffb63ea63fed49cb36 +msgid "|set_bg_color.png|" +msgstr "" + +#: ../../en/refs/m5ui.roller.ref:9 4d44a84b702f409f87c0a42fdf7761f3 +msgid "set_bg_color.png" +msgstr "" + +#: ../../en/m5ui/roller.rst:181 d8f10aa78dec4b41a938d161bd830eff +msgid "Set the border color of the roller." +msgstr "设置滚轮的边框颜色。" + +#: ../../en/m5ui/roller.rst:184 e1df2129867d4c578c6c664e163d59e4 +msgid "" +"The opacity of the color. The value should be between 0 (transparent) and" +" 255 (opaque)." +msgstr "颜色的不透明度。值应在0(透明)到255(不透明)之间。" + +#: ../../en/m5ui/roller.rst:190 dffa8a087d9743a69df4bf801079c5e8 +msgid "|set_border_color.png|" +msgstr "" + +#: ../../en/refs/m5ui.roller.ref:10 f219b370aae24d879a0a21eb720e6195 +msgid "set_border_color.png" +msgstr "" + +#: ../../en/m5ui/roller.rst:202 8055958c699c4a49ac64529af17dcd2d +msgid "Set the border width of the roller." +msgstr "设置滚轮的边框宽度。" + +#: ../../en/m5ui/roller.rst:204 bc8a491b8de74a69ac76df46251028e4 +msgid "The width to set." +msgstr "要设置的宽度。" + +#: ../../en/m5ui/roller.rst:210 de8807ff1fe6438a814b06a283ee85c7 +msgid "|set_style_border_width.png|" +msgstr "" + +#: ../../en/refs/m5ui.roller.ref:18 46ee39f78df94e148ff3dd877a59758f +msgid "set_style_border_width.png" +msgstr "" + +#: ../../en/m5ui/roller.rst:222 4c5fc73d3962444fb52e52eee8ef6da6 +msgid "Get the total number of options in the roller." +msgstr "获取滚轮中选项的总数。" + +#: ../../en/m5ui/roller.rst:224 cd413ae846b246e1a63de5666281c3e6 +msgid "The number of options." +msgstr "选项的数量。" + +#: ../../en/m5ui/roller.rst 3c71709ae0674c27bb94db8b1e0a4a9d +#: 594d2693cb304632863c89145dfea6a4 81467515d3be4d4eba54989e4836c8e3 +#: 9e1e23ea38f34856a7ae711de65ccd0c m5ui.roller.M5Roller.get_options +#: m5ui.roller.M5Roller.get_selected_str m5ui.roller.M5Roller.set_style_radius +#: of +msgid "Return type" +msgstr "" + +#: ../../en/m5ui/roller.rst:229 5729fe3f6fde4b92ab7c4a0e183c4f61 +msgid "|get_option_count.png|" +msgstr "" + +#: ../../en/refs/m5ui.roller.ref:4 dae8c20852e64e5b843eadb1ebf16743 +msgid "get_option_count.png" +msgstr "" + +#: ../../en/m5ui/roller.rst:240 a4986c031a5a4bdfbf1833e852b33577 +msgid "Get the index of the currently selected option." +msgstr "获取当前选中选项的索引。" + +#: ../../en/m5ui/roller.rst:242 21c2d3cfd0d74eb8b090e9aae9dcacc7 +msgid "The index of the selected option." +msgstr "选中选项的索引。" + +#: ../../en/m5ui/roller.rst:247 5cc5b2ebb048492fa48f117a4edac327 +msgid "|get_selected.png|" +msgstr "" + +#: ../../en/refs/m5ui.roller.ref:6 2238fad457534e35901a7f7683268258 +msgid "get_selected.png" +msgstr "" + +#: ../../en/m5ui/roller.rst:258 351b7914a1be4d45ad1b88752e4d13a8 +msgid "Set the number of visible rows in the roller." +msgstr "设置滚轮中可见行数。" + +#: ../../en/m5ui/roller.rst:260 93c125fa336f419d9c2570d41e6bd7b3 +msgid "The number of visible rows." +msgstr "可见行数。" + +#: ../../en/m5ui/roller.rst:265 55b7364719d54745afb8725823d950d4 +msgid "|set_visible_row_count.png|" +msgstr "" + +#: ../../en/refs/m5ui.roller.ref:20 41a61cde0bc04b07847de927350ea685 +msgid "set_visible_row_count.png" +msgstr "" + +#: ../../en/m5ui/roller.rst:276 f8a8120886574013a6c0944fb7421200 +msgid "Set the position of the roller." +msgstr "设置滚轮的位置。" + +#: ../../en/m5ui/roller.rst:278 ../../en/m5ui/roller.rst:297 +#: d0bc1ae6ebc3440e849c5202cc1b3cc1 +msgid "The x-coordinate of the roller." +msgstr "滚轮的x坐标。" + +#: ../../en/m5ui/roller.rst:279 ../../en/m5ui/roller.rst:315 +#: c08c7b47237f43999c4c3736b81f227e +msgid "The y-coordinate of the roller." +msgstr "滚轮的y坐标。" + +#: ../../en/m5ui/roller.rst:284 002f5a3723394ce2904e4fc068793f4e +msgid "|set_pos.png|" +msgstr "" + +#: ../../en/refs/m5ui.roller.ref:15 0de4992913dc4ec89b06a0a5fc5984ef +msgid "set_pos.png" +msgstr "" + +#: ../../en/m5ui/roller.rst:295 106873ef2fe94b3c861b08713dd72b1c +msgid "Set the x-coordinate of the roller." +msgstr "设置滚轮的x坐标。" + +#: ../../en/m5ui/roller.rst:302 bb252726e9bb4c729cd86096b7641400 +msgid "|set_x.png|" +msgstr "" + +#: ../../en/refs/m5ui.roller.ref:22 ef79f30f3e454eeca83e61f9b4c6e35f +msgid "set_x.png" +msgstr "" + +#: ../../en/m5ui/roller.rst:313 d1fb85bec44d4275801af365170427b6 +msgid "Set the y-coordinate of the roller." +msgstr "设置滚轮的y坐标。" + +#: ../../en/m5ui/roller.rst:320 15806ede2556412195559af3d1d3e33e +msgid "|set_y.png|" +msgstr "" + +#: ../../en/refs/m5ui.roller.ref:23 242445e2c73542c2878b001984321a4d +msgid "set_y.png" +msgstr "" + +#: ../../en/m5ui/roller.rst:331 7ecb3e7fb9c94c7b95726a1c6a57d1b7 +msgid "Align the roller to another object." +msgstr "将滚轮对齐到另一个对象。" + +#: ../../en/m5ui/roller.rst:333 3f84b9ceb957474caa708e6165901a11 +msgid "The object to align to." +msgstr "要对齐的对象。" + +#: ../../en/m5ui/roller.rst:334 7f905cd94d7549b39d03db355510d2c4 +msgid "The alignment type." +msgstr "对齐类型。" + +#: ../../en/m5ui/roller.rst:335 f6c42898c6944e6f8438725180692049 +msgid "The x-offset from the aligned object." +msgstr "与对齐对象的x偏移量。" + +#: ../../en/m5ui/roller.rst:336 337e4465b8684ddb85f3f3f5543e4c98 +msgid "The y-offset from the aligned object." +msgstr "与对齐对象的y偏移量。" + +#: ../../en/m5ui/roller.rst:341 3bffd6a71d6748e38c99762a0837b941 +msgid "|align_to.png|" +msgstr "" + +#: ../../en/refs/m5ui.roller.ref:1 c24a368034b04a68b66da367628ec6cf +msgid "align_to.png" +msgstr "" + +#: ../../en/m5ui/roller.rst:352 7ea6bd3ff39b4428b5dc71cb42d88ea9 +msgid "Set the size of the roller." +msgstr "设置滚轮的尺寸。" + +#: ../../en/m5ui/roller.rst:354 ../../en/m5ui/roller.rst:373 +#: ../../en/m5ui/roller.rst:391 a83c72a7a6ce478ab4e080c7465c0c48 +msgid "The width of the roller." +msgstr "滚轮的宽度。" + +#: ../../en/m5ui/roller.rst:355 ../../en/m5ui/roller.rst:409 +#: ../../en/m5ui/roller.rst:427 f520cb0c374845acab6b5624959afcb0 +msgid "The height of the roller." +msgstr "滚轮的高度。" + +#: ../../en/m5ui/roller.rst:360 84a5d84fce164012a259950a2e118d82 +msgid "|set_size.png|" +msgstr "" + +#: ../../en/refs/m5ui.roller.ref:16 1c2c16bb2a4946969805e06d4b5be3cd +msgid "set_size.png" +msgstr "" + +#: ../../en/m5ui/roller.rst:371 6cd5324f9d064d0390cbe0626a82bcad +msgid "Set the width of the roller." +msgstr "设置滚轮的宽度。" + +#: ../../en/m5ui/roller.rst:378 fd00c50bb4894d5c93c1098007f7c9dc +msgid "|set_width.png|" +msgstr "" + +#: ../../en/refs/m5ui.roller.ref:21 63efe9d60480417285e37d9a5685b468 +msgid "set_width.png" +msgstr "" + +#: ../../en/m5ui/roller.rst:389 8c4030ce4105480a9bd012ffa13089c0 +msgid "Get the width of the roller." +msgstr "获取滚轮的宽度。" + +#: ../../en/m5ui/roller.rst:396 2dccff5831ec45de886a4731272d2d0b +msgid "|get_width.png|" +msgstr "" + +#: ../../en/refs/m5ui.roller.ref:8 b76dccc96d4543ec875e5fe3ec39d2cb +msgid "get_width.png" +msgstr "" + +#: ../../en/m5ui/roller.rst:407 d48d966d24ae4de1a48e622c0e676af9 +msgid "Set the height of the roller." +msgstr "设置滚轮的高度。" + +#: ../../en/m5ui/roller.rst:414 02cc628f7b5445c39516ffc381bbed0f +msgid "|set_height.png|" +msgstr "" + +#: ../../en/refs/m5ui.roller.ref:13 7cbd0908c5ff467683bad1f6b02159b7 +msgid "set_height.png" +msgstr "" + +#: ../../en/m5ui/roller.rst:425 060bea8edfc04473badd0322d00cba3e +msgid "Get the height of the roller." +msgstr "获取滚轮的高度。" + +#: ../../en/m5ui/roller.rst:432 1f0fad375f75427a86c71598906506a5 +msgid "|get_height.png|" +msgstr "" + +#: ../../en/refs/m5ui.roller.ref:3 2d92694d0a22402c8907b4cd682aacd0 +msgid "get_height.png" +msgstr "" + +#: f8a3c23138894109bd1db6f07386ed85 m5ui.roller.M5Roller.set_options:1 of +msgid "Set the options for the roller." +msgstr "设置滚轮的选项。" + +#: 9a4e9c3115d4461a90340d7ff22dcb99 m5ui.roller.M5Roller.set_options:8 of +msgid "|set_options.png|" +msgstr "" + +#: ../../en/refs/m5ui.roller.ref:14 217d6c3e60e74f088fa3fe2e399b81bf +msgid "set_options.png" +msgstr "" + +#: 0c78f21c6ca34956a2cfebae7460ce80 m5ui.roller.M5Roller.get_options:1 of +msgid "Get the list of options in the dropdown." +msgstr "获取下拉菜单中的选项列表。" + +#: 568828dc38034a838d05b8350be89fa7 m5ui.roller.M5Roller.get_options:3 of +msgid "The list of options." +msgstr "选项列表。" + +#: c9d886d11858439d8407c3012ca4e264 m5ui.roller.M5Roller.get_options:8 of +msgid "|get_options.png|" +msgstr "" + +#: ../../en/refs/m5ui.roller.ref:5 fc47b0fe8af44ba7b5ffd3d9c27c0c86 +msgid "get_options.png" +msgstr "" + +#: b812bc4c90124088ac18e51938c50696 m5ui.roller.M5Roller.get_selected_str:1 of +msgid "Get the currently selected option as a string." +msgstr "获取当前选中选项的文本。" + +#: 82adc507d46645b3bf6bb213b1082e40 m5ui.roller.M5Roller.get_selected_str:3 of +msgid "The selected option as a string." +msgstr "选中选项的文本。" + +#: 288d04a2a7354ac08af9660ccbeaf0fe m5ui.roller.M5Roller.get_selected_str:7 of +msgid "|get_selected_str.png|" +msgstr "" + +#: ../../en/refs/m5ui.roller.ref:7 be1564e559a5408eaf6a058cc02c8d7f +msgid "get_selected_str.png" +msgstr "" + +#: 737042603d1c46118c0b7b63a9bab803 m5ui.roller.M5Roller.set_style_radius:1 of +msgid "Set the corner radius of the slider components." +msgstr "设置滑块组件的圆角半径。" + +#: ab485dc557d64e2baf1b4383eabf9a27 m5ui.roller.M5Roller.set_style_radius:3 of +msgid "The radius to set." +msgstr "要设置的半径。" + +#: 653fa0736d2a479db72b5db36d213a55 m5ui.roller.M5Roller.set_style_radius:4 of +msgid "" +"The part of the object to apply the style to (e.g., lv.PART.MAIN, " +"lv.PART.SELECTED)." +msgstr "要应用样式的对象部分(例如,lv.PART.MAIN、lv.PART.SELECTED)。" + +#: 7d98abf0d01c4e1ea4272ce5ebe0cc65 m5ui.roller.M5Roller.set_style_radius:9 of +msgid "|set_style_radius.png|" +msgstr "" + +#: ../../en/refs/m5ui.roller.ref:19 a2e56529da964f5aa08ec959f9ed8b90 +msgid "set_style_radius.png" +msgstr "" + diff --git a/examples/m5ui/roller/cores3_roller_basic_example.m5f2 b/examples/m5ui/roller/cores3_roller_basic_example.m5f2 new file mode 100644 index 00000000..45f19217 --- /dev/null +++ b/examples/m5ui/roller/cores3_roller_basic_example.m5f2 @@ -0,0 +1 @@ +{"version":"V2.0","versionNumber":"V2.3.3","type":"core2","components":[{"name":"page0","type":"lvgl_page","layer":0,"screenId":"builtin","screenName":"","id":"w#OUTx5g2=WUes=i","createTime":1755596357827,"backgroundColor":"#ffffff","isLVGL":true,"isSelected":true},{"name":"roller0","type":"lvgl_roller","layer":5,"screenId":"builtin","screenName":"","id":"pbFFtnEPbtH=w=sx","createTime":1755659649751,"x":110,"y":71,"width":100,"height":0,"options":["January","February","March","April","May","June","July","August","September","October","November","December"],"mode":"INFINITE","modeOption":[{"label":"Normal","value":"NORMAL"},{"label":"Infinite","value":"INFINITE"}],"selectedIndex":0,"visibleRowCount":3,"font":"lv.font_montserrat_14","pageId":"w#OUTx5g2=WUes=i","isLVGL":true,"isSelected":false}],"resources":[{"hardware":["hardware_button","hardware_pin_button","imu","speaker","touch","mic","sdcard"]}],"units":[],"hats":[],"bases":[],"i2cs":[],"blockly":"truepage0hello M5roller0true","screen":[{"simulationName":"Built-in","type":"builtin","width":320,"height":240,"scale":0.77,"screenName":"","blockId":"","screenColorType":0,"rotation":1,"id":"builtin","createTime":1755596357824}],"logicWhenNum":0,"customList":[]} \ No newline at end of file diff --git a/examples/m5ui/roller/cores3_roller_basic_example.py b/examples/m5ui/roller/cores3_roller_basic_example.py new file mode 100644 index 00000000..e91dec67 --- /dev/null +++ b/examples/m5ui/roller/cores3_roller_basic_example.py @@ -0,0 +1,66 @@ +import os, sys, io +import M5 +from M5 import * +import m5ui +import lvgl as lv + + +page0 = None +roller0 = None + + +def setup(): + global page0, roller0 + + M5.begin() + Widgets.setRotation(1) + m5ui.init() + page0 = m5ui.M5Page(bg_c=0xFFFFFF) + roller0 = m5ui.M5Roller( + x=110, + y=71, + w=100, + h=0, + options=[ + "January", + "February", + "March", + "April", + "May", + "June", + "July", + "August", + "September", + "October", + "November", + "December", + ], + mode=lv.roller.MODE.INFINITE, + selected=0, + visible_row_count=3, + font=lv.font_montserrat_14, + parent=page0, + ) + + page0.screen_load() + print(roller0.get_options()) + + +def loop(): + global page0, roller0 + M5.update() + + +if __name__ == "__main__": + try: + setup() + while True: + loop() + except (Exception, KeyboardInterrupt) as e: + try: + m5ui.deinit() + from utility import print_error_msg + + print_error_msg(e) + except ImportError: + print("please update to latest firmware") diff --git a/m5stack/libs/m5ui/__init__.py b/m5stack/libs/m5ui/__init__.py index 63027bc7..3c1dc3cc 100644 --- a/m5stack/libs/m5ui/__init__.py +++ b/m5stack/libs/m5ui/__init__.py @@ -19,8 +19,9 @@ "M5Line": "line", "M5List": "list", "M5Page": "page", - "M5Slider": "slider", + "M5Roller": "roller", "M5Scale": "scale", + "M5Slider": "slider", "M5Switch": "switch", "M5TextArea": "textarea", } diff --git a/m5stack/libs/m5ui/manifest.py b/m5stack/libs/m5ui/manifest.py index 3fc4abdd..4259d5bb 100644 --- a/m5stack/libs/m5ui/manifest.py +++ b/m5stack/libs/m5ui/manifest.py @@ -22,8 +22,9 @@ "list.py", "page.py", "port.py", - "slider.py", + "roller.py", "scale.py", + "slider.py", "switch.py", "textarea.py", ), diff --git a/m5stack/libs/m5ui/roller.py b/m5stack/libs/m5ui/roller.py new file mode 100644 index 00000000..3af8eb44 --- /dev/null +++ b/m5stack/libs/m5ui/roller.py @@ -0,0 +1,155 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +from .base import M5Base +import lvgl as lv +import warnings + + +class M5Roller(lv.roller): + """Create a roller widget. + + :param int x: X position of the widget. + :param int y: Y position of the widget. + :param int w: Width of the widget. + :param int h: Height of the widget. + :param list options: List of options to display in the roller. + :param lv.roller.MODE mode: Roller mode (default is NORMAL). + :param int selected: Index of the initially selected option. + :param int visible_row_count: Number of visible rows in the roller. + :param lv.font_t font: Font to use for the text in the roller. + :param parent: Parent widget to attach this roller to (default is the active screen). + :type parent: lv.obj or None + + UiFlow2 Code Block: + + None + + MicroPython Code Block: + + .. code-block:: python + + from m5ui import M5Roller + import lvgl as lv + m5ui.init() + roller_0 = M5Roller(x=10, y=10, w=100, h=100, options=["Option 1", "Option 2"], mode=lv.roller.MODE.NORMAL, selected=0, visible_row_count=2, font=lv.font_montserrat_14, parent=page0) + """ + + def __init__( + self, + x=0, + y=0, + w=100, + h=100, + options=[], + mode=lv.roller.MODE.NORMAL, + selected=0, + visible_row_count=2, + font: lv.font_t = lv.font_montserrat_14, + parent=None, + ): + if parent is None: + parent = lv.screen_active() + super().__init__(parent) + self.set_pos(x, y) + self.set_size(w, h) + + self.options = [] + if options: + self.set_options(options, mode) + + self.set_selected(selected, True) + self.set_visible_row_count(visible_row_count) + self.set_style_text_font(font, lv.PART.MAIN | lv.STATE.DEFAULT) + + def set_options(self, options: list, mode=lv.roller.MODE.NORMAL): + """Set the options for the roller. + + :param list options: List of options to display in the roller. + :param lv.roller.MODE mode: Roller mode (default is NORMAL). + + UiFlow2 Code Block: + + |set_options.png| + + MicroPython Code Block: + + .. code-block:: python + + roller_0.set_options(["Option 1", "Option 2"], mode=lv.roller.MODE.NORMAL) + """ + if isinstance(options, list): + self.options = options + options = "\n".join(options) + super().set_options(options, mode) + else: + warnings.warn("Options must be a list.") + + def get_options(self) -> list: + """Get the list of options in the dropdown. + + :return: The list of options. + :rtype: list + + UiFlow2 Code Block: + + |get_options.png| + + MicroPython Code Block: + + .. code-block:: python + + options = roller_0.get_options() + """ + return self.options + + def get_selected_str(self) -> str: + """Get the currently selected option as a string. + + :return: The selected option as a string. + + UiFlow2 Code Block: + + |get_selected_str.png| + + MicroPython Code Block: + + .. code-block:: python + + selected_option = roller_0.get_selected_str() + """ + sel = bytearray(32) + super().get_selected_str(sel, len(sel)) + return sel.decode("utf-8").rstrip("\x00") + + def set_style_radius(self, radius: int, part: int) -> None: + """Set the corner radius of the slider components. + + :param int radius: The radius to set. + :param int part: The part of the object to apply the style to (e.g., lv.PART.MAIN, lv.PART.SELECTED). + :return: None + + UiFlow2 Code Block: + + |set_style_radius.png| + + MicroPython Code Block: + + .. code-block:: python + + roller_0.set_style_radius(10, lv.PART.MAIN | lv.STATE.DEFAULT) + roller_0.set_style_radius(10, lv.PART.SELECTED | lv.STATE.DEFAULT) + """ + if radius < 0: + warnings.warn("Radius must be a non-negative integer.") + return + super().set_style_radius(radius, part) + + def __getattr__(self, name): + if hasattr(M5Base, name): + method = getattr(M5Base, name) + bound_method = lambda *args, **kwargs: method(self, *args, **kwargs) + setattr(self, name, bound_method) + return bound_method + raise AttributeError(f"'{self.__class__.__name__}' object has no attribute '{name}'") diff --git a/tests/m5ui/test_roller.py b/tests/m5ui/test_roller.py new file mode 100644 index 00000000..39211bf2 --- /dev/null +++ b/tests/m5ui/test_roller.py @@ -0,0 +1,39 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import lvgl as lv +import sys + +sys.path.append("../../m5stack/libs") +import m5ui +import unittest + + +class Test(unittest.TestCase): + def __init__(self) -> None: + super().__init__() + m5ui.init() + page0 = m5ui.M5Page() + self.roller0 = m5ui.M5Roller(options=["a", "b", "c"], x=81, y=93, w=200, parent=page0) + page0.screen_load() + + def test_get_options(self): + self.roller0.set_options(["a", "b", "c"], lv.roller.MODE.NORMAL) + self.assertEqual(self.roller0.get_options(), ["a", "b", "c"]) + + def test_get_selected_str(self): + self.roller0.set_selected(0, True) + self.assertEqual(self.roller0.get_selected_str(), "a") + self.roller0.set_selected(1, True) + self.assertEqual(self.roller0.get_selected_str(), "b") + + def test_get_selected(self): + self.roller0.set_selected(0, True) + self.assertEqual(self.roller0.get_selected(), 0) + self.roller0.set_selected(1, True) + self.assertEqual(self.roller0.get_selected(), 1) + + +if __name__ == "__main__": + unittest.main() From 67397075d2dd2346afdc7f760c8bee97b37f597c Mon Sep 17 00:00:00 2001 From: luoweiyuan Date: Thu, 21 Aug 2025 09:16:28 +0800 Subject: [PATCH 215/322] libs/usb: fix usb keyboard send_report() error. Signed-off-by: luoweiyuan --- m5stack/libs/usb/device/keyboard.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/m5stack/libs/usb/device/keyboard.py b/m5stack/libs/usb/device/keyboard.py index 79a733ad..0732c8eb 100644 --- a/m5stack/libs/usb/device/keyboard.py +++ b/m5stack/libs/usb/device/keyboard.py @@ -196,7 +196,7 @@ def send_keys(self, down_keys, timeout_ms=100): r[i] = 0 i += 1 - if self.send_report(r, timeout_ms): + if super().send_report(r, timeout_ms): # Swap buffers if the previous one is newly queued to send, so # any subsequent call can't modify that buffer mid-send self._key_reports[0] = s @@ -204,7 +204,7 @@ def send_keys(self, down_keys, timeout_ms=100): return True return False - def _send_report(self, timeout_ms=100): + def send_report(self, timeout_ms=100): struct.pack_into( "8B", self._buf, @@ -218,7 +218,7 @@ def _send_report(self, timeout_ms=100): self._keypresses[4], self._keypresses[5], ) - if self.send_report(self._buf, timeout_ms): + if super().send_report(self._buf, timeout_ms): return True return False @@ -249,9 +249,9 @@ def set_keys(self, k0=0x00, k1=0x00, k2=0x00, k3=0x00, k4=0x00, k5=0x00): def send_key(self, key): self.set_keys(k0=key) - self._send_report() + self.send_report() self.set_keys() - self._send_report() + self.send_report() def input(self, key): if isinstance(key, str): @@ -264,10 +264,10 @@ def input(self, key): key_cache.clear() self.set_modifiers(left_shift=True) self.set_keys(k0=hid_key) - self._send_report() + self.send_report() self.set_modifiers() self.set_keys() - self._send_report() + self.send_report() else: key_cache.append(hid_key) if len(key_cache) == 6: @@ -277,7 +277,7 @@ def input(self, key): self.send_keypresses(key_cache) key_cache.clear() self.set_keys() - self._send_report() + self.send_report() else: self.send_key(key) @@ -287,13 +287,13 @@ def send_keypresses(self, key): for i, k in enumerate(key): if k == last_k: self._keypresses[cnt - 1] = 0x00 - self._send_report() + self.send_report() self._keypresses[cnt] = k cnt += 1 last_k = k - self._send_report() + self.send_report() self.set_keys() - self._send_report() + self.send_report() time.sleep_ms(10) From 6266a29fd451b7dd29578dd6fb15514c58d4ace0 Mon Sep 17 00:00:00 2001 From: lbuque <1102390310@qq.com> Date: Thu, 21 Aug 2025 09:56:01 +0800 Subject: [PATCH 216/322] libs/iot_devices/switchc6.py: Remove event trigger source settings. Signed-off-by: lbuque <1102390310@qq.com> --- .../zh_CN/LC_MESSAGES/iot-devices/switchc6.po | 159 +++++++++--------- m5stack/libs/iot_devices/switchc6.py | 14 +- 2 files changed, 89 insertions(+), 84 deletions(-) diff --git a/docs/locales/zh_CN/LC_MESSAGES/iot-devices/switchc6.po b/docs/locales/zh_CN/LC_MESSAGES/iot-devices/switchc6.po index d077b06a..caf1f98d 100644 --- a/docs/locales/zh_CN/LC_MESSAGES/iot-devices/switchc6.po +++ b/docs/locales/zh_CN/LC_MESSAGES/iot-devices/switchc6.po @@ -9,7 +9,7 @@ msgid "" msgstr "" "Project-Id-Version: UIFlow2 Programming Guide \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-07-26 23:25+0800\n" +"POT-Creation-Date: 2025-08-20 18:56+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language: zh_CN\n" @@ -20,43 +20,41 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.17.0\n" -#: ../../en/iot-devices/switchc6.rst:2 fe54764279e54b5489ca2aab566c4977 +#: ../../en/iot-devices/switchc6.rst:2 838a6af22b9443a9ba4d67e56109a5c9 msgid "SwitchC6" msgstr "" -#: ../../en/iot-devices/switchc6.rst:9 a8659eada64d4b11b883b13bef452276 +#: ../../en/iot-devices/switchc6.rst:9 acf935bd3a014735ba840d431c9e683b msgid "" "The SwitchC6 is a device that can be controlled using the M5Stack " "platform. This module provides functions to interact with the SwitchC6 " "device." -msgstr "" -"SwitchC6是一个可以通过M5Stack平台控制的设备。此模块提供与SwitchC6设备交互的功能。" +msgstr "SwitchC6是一个可以通过M5Stack平台控制的设备。此模块提供与SwitchC6设备交互的功能。" -#: ../../en/iot-devices/switchc6.rst:12 7184ce970883404e90158a02e428488d +#: ../../en/iot-devices/switchc6.rst:12 5049fdb2312e4491999bc16aefdf94e1 msgid "UiFlow2 Example" msgstr "UIFLOW2 应用示例" #: ../../en/iot-devices/switchc6.rst:15 ../../en/iot-devices/switchc6.rst:34 -#: a2959859885243218e8032dbbb742718 a5fb5829e05b4a26bcd5b78d8506c81f +#: 70f66a41bd0c43e5a96bfeda3200d266 a529d886fd6a4aa68086ea8444f1a2ef msgid "SwitchC6 Control" msgstr "SwitchC6控制" -#: ../../en/iot-devices/switchc6.rst:17 25028d872b6b4ad7a0caa77fca7a23c6 +#: ../../en/iot-devices/switchc6.rst:17 cecd47fc737e42bfa94911463d162864 msgid "Open the |cores3_switchc6_example.m5f2| project in UiFlow2." msgstr "在UiFlow2中打开|cores3_switchc6_example.m5f2|项目。" -#: ../../en/iot-devices/switchc6.rst:19 337a535f63704126991b7664ae76640c +#: ../../en/iot-devices/switchc6.rst:19 efb9136415d84f62bfd9e4bf7e4e31ee msgid "" "This example demonstrates how to control the SwitchC6 device using " "UiFlow2." -msgstr "" -"此示例演示如何使用UiFlow2控制SwitchC6设备。" +msgstr "此示例演示如何使用UiFlow2控制SwitchC6设备。" -#: ../../en/iot-devices/switchc6.rst:21 1ff55eb867e34d97b4341059958210fe -#: 31f88b46e8b84d60bbcba28db73318f1 463d0752f06f4712aa5857a2557877de -#: 5b571c4f4339489f81ab3a2e567d11f6 888bc242b21242698ee97df24610c7e0 -#: 938899417a2741d1a7b117572663d130 b2dab64a8d3b4444ad5d8a9743298954 -#: bfada81bddb942a18a766a611b42096d iot_devices.switchc6.SwitchC6Controller:8 +#: ../../en/iot-devices/switchc6.rst:21 15f4e3edc6c34885991dfd1bb1556e8a +#: 4582b8005c724f0e88286956f95cb81f 4e8cd899b3d9462daed90442a5107c54 +#: 535232eb40ff4d448860a8d0e2332453 83c42565ffa444c2beac8c72c7035547 +#: cd55f9caa68c45669cd57adf5e8a176a e73c41661bee491dab1a5b9d59a54d38 +#: edf97b2c7e1f4d2ba6412cae064ffbe9 iot_devices.switchc6.SwitchC6Controller:8 #: iot_devices.switchc6.SwitchC6Controller.get_capacitor_voltage:8 #: iot_devices.switchc6.SwitchC6Controller.get_firmware_version:9 #: iot_devices.switchc6.SwitchC6Controller.get_switch_status:8 @@ -66,36 +64,40 @@ msgstr "" msgid "UiFlow2 Code Block:" msgstr "UiFlow2代码块:" -#: ../../en/iot-devices/switchc6.rst:23 53019f9fd8b34a559527178087bc04ec +#: ../../en/iot-devices/switchc6.rst:23 d8568018ecd840969f318d175e08cbd0 msgid "|cores3_switchc6_example.png|" msgstr "" +#: ../../en/refs/iot-devices.switchc6.ref:10 65bacd84bf3a4dbb9a70888c3c4edbd4 +#, fuzzy +msgid "cores3_switchc6_example.png" +msgstr "在UiFlow2中打开|cores3_switchc6_example.m5f2|项目。" + #: ../../en/iot-devices/switchc6.rst:25 ../../en/iot-devices/switchc6.rst:44 -#: c2ebda8f144143cc9459d8d9447f7c5f d4a3332ca71a4939b2796d7c0c3aa136 +#: 2bf8794fdc514b72bb083503dc2b5f87 9bd3937e1bbc45d2b8e2ac2139bc52f5 msgid "Example output:" msgstr "示例输出:" #: ../../en/iot-devices/switchc6.rst:27 ../../en/iot-devices/switchc6.rst:46 -#: 45e287f362864084928878a015db23b5 66af327bdf3c45aea0ab744ba0f8232f +#: 36223b1294904f9eac08695d6eb771c3 a91efc6f59154a2b9a99331618f25144 msgid "None" msgstr "" -#: ../../en/iot-devices/switchc6.rst:31 65e53998c77f442ab007113ffcbb7c38 +#: ../../en/iot-devices/switchc6.rst:31 70f773789b8f4f30bc64e8d9736a397f msgid "MicroPython Example" msgstr "MicroPython 应用示例" -#: ../../en/iot-devices/switchc6.rst:36 5292b5cf231c41bc961328b036dcf0f6 +#: ../../en/iot-devices/switchc6.rst:36 3d6a2318ddfd4fe1850b9990619897ea msgid "" "This example demonstrates how to control the SwitchC6 device using " "MicroPython." -msgstr "" -"此示例演示如何使用MicroPython控制SwitchC6设备。" +msgstr "此示例演示如何使用MicroPython控制SwitchC6设备。" -#: ../../en/iot-devices/switchc6.rst:38 22e25d767f7642bb82cb217febbb1c76 -#: 378959de60fd442899507078b49741bd 3b11e92a2b754c37a4f9608082932727 -#: 6d75d2a28abd4a3d96ede4ee99bfbdab a02d9565f63549c6bf2bcddcc8157fa1 -#: c8dabaf613414934b13ee3cdece3c1ea dcb28eefca0b46f6918889ea5c1c1789 -#: f9566e47f37b43b4a2004a871f78d1b5 iot_devices.switchc6.SwitchC6Controller:12 +#: ../../en/iot-devices/switchc6.rst:38 11876878cb8d4b179519b6d65a3aae54 +#: 280d70fc51a04a1290d6220c41e2fe84 7e685d2611c44284ad0553f564a96824 +#: 8e527d9c4d774387a90f21f69581cfef bf62cdb336e6431e8960d333715d2df3 +#: c833580f97ee45f7bab96cc39618c897 d7a0d4bfec45414199b84824fe943c8b +#: f27c4e8b00914fa1bb304202fc5f3ff2 iot_devices.switchc6.SwitchC6Controller:12 #: iot_devices.switchc6.SwitchC6Controller.get_capacitor_voltage:12 #: iot_devices.switchc6.SwitchC6Controller.get_firmware_version:13 #: iot_devices.switchc6.SwitchC6Controller.get_switch_status:12 @@ -105,28 +107,28 @@ msgstr "" msgid "MicroPython Code Block:" msgstr "MicroPython 代码块:" -#: ../../en/iot-devices/switchc6.rst:50 11b687be04cd4b698e225fd8c8363c66 +#: ../../en/iot-devices/switchc6.rst:50 f2f1bbe1717d47b59c16ec706b71db70 msgid "**API**" msgstr "API参考" -#: ../../en/iot-devices/switchc6.rst:53 010aad280b5149969b740ef25f7698af +#: ../../en/iot-devices/switchc6.rst:53 c37f8997bf58478a9e0e34812384e2d4 msgid "SwitchC6Controller" msgstr "" -#: df34f0659edc4beb8e67371dfd18bfe1 iot_devices.switchc6.SwitchC6Controller:1 +#: 433fe9fa0c94478cae58d7d72f5092d1 iot_devices.switchc6.SwitchC6Controller:1 #: of msgid "Bases: :py:class:`object`" msgstr "" -#: 86a4b3f3f7f74be79b4d9caf061c752e iot_devices.switchc6.SwitchC6Controller:1 +#: bd5a1ba6e13445c194d8d8d2aa585be1 iot_devices.switchc6.SwitchC6Controller:1 #: of msgid "Create a SwitchC6Controller instance to control M5Stack SwitchC6 devices." msgstr "创建SwitchC6Controller实例来控制M5Stack SwitchC6设备。" -#: ../../en/iot-devices/switchc6.rst 0dc3346e4f1040ed8cfa565694d1e097 -#: 3ab6ad6e8189479b99ebd1ca4fba819f 7b6762da210943f6b083ffb5ae318d7f -#: a3671bd7a88c47e092cbd861dd317ff8 ab140eb323294dc7a6ce5f09130658a0 -#: dcd239cc9e2c47d5adfca83dcdc099be fd0bb8dbc84e4703b15159d1a0388fda +#: ../../en/iot-devices/switchc6.rst 03cd2ace6ec84516acf72e0617e0a535 +#: 50024437995c4092b702dcf6ef4c2bf1 53e63db4f2844bed9432851f1296e62d +#: 5cb95c5399094f2ab7d2d7da0d802710 6d922674ddc447d48ec529d0d5f1c606 +#: b276d1145ff1477a8468596a82669593 c4143286a92f4414ac4912479916f4fe #: iot_devices.switchc6.SwitchC6Controller.get_capacitor_voltage #: iot_devices.switchc6.SwitchC6Controller.get_firmware_version #: iot_devices.switchc6.SwitchC6Controller.get_switch_status @@ -136,50 +138,49 @@ msgstr "创建SwitchC6Controller实例来控制M5Stack SwitchC6设备。" msgid "Parameters" msgstr "" -#: 1219045777e84db5b6b7508c4571c0a8 iot_devices.switchc6.SwitchC6Controller:3 +#: fcecbb3e1fd5445b9b3e4e79243db60b iot_devices.switchc6.SwitchC6Controller:3 #: of msgid "List of target MAC addresses in \"XXXX-XXXX-XXXX\" format." msgstr "\"XXXX-XXXX-XXXX\"格式的目标MAC地址列表。" -#: 8de7b3d162a84fffb1aaf970d2d12828 iot_devices.switchc6.SwitchC6Controller:4 +#: a98a595feb1f4c17bbf1635a1e02d54e iot_devices.switchc6.SwitchC6Controller:4 #: of msgid "" "WiFi channel to use for communication (default is 0, which uses the " "current channel)." -msgstr "" -"用于通信的WiFi信道(默认为0,使用当前信道)。" +msgstr "用于通信的WiFi信道(默认为0,使用当前信道)。" -#: 077fe70d56d54c2a8c04c9926e2a4aa6 iot_devices.switchc6.SwitchC6Controller:5 +#: 7e06dfcc693d4793b51624fcd8169763 iot_devices.switchc6.SwitchC6Controller:5 #: of msgid "If True, print debug information (default is False)." msgstr "如果为True,打印调试信息(默认为False)。" -#: ../../en/iot-devices/switchc6.rst dbd31c4247fb4dcf9dfe219af15a431a +#: ../../en/iot-devices/switchc6.rst 3d9fed57ae784cefa2f834d5ae25c82c msgid "Raises" msgstr "" -#: 030ce84c25a848f6aad2d3e6657ccb51 iot_devices.switchc6.SwitchC6Controller:6 +#: 63ab8843d443460b8059be02175acc2d iot_devices.switchc6.SwitchC6Controller:6 #: of msgid "If any MAC address in target_mac is not in the \"XXXX-XXXX-XXXX\" format." msgstr "如果target_mac中的任何MAC地址不符合\"XXXX-XXXX-XXXX\"格式。" -#: c9e99e5c549d4ce8b446050ca2ca4628 iot_devices.switchc6.SwitchC6Controller:10 +#: 3954e4340a6c4b4ca58cc63c1859ee0f iot_devices.switchc6.SwitchC6Controller:10 #: of msgid "|init.png|" msgstr "" -#: ../../en/refs/iot-devices.switchc6.ref:6 41409e8434544f81b3a60a29205eb119 +#: ../../en/refs/iot-devices.switchc6.ref:6 f23ca791ab074955a9d75c472ab26e9a msgid "init.png" msgstr "" -#: 58b2f17e93ff444a92aff51e8c0c85f4 +#: 6c20d7924e3f470082d1c8fcc1b83000 #: iot_devices.switchc6.SwitchC6Controller.set_switch:1 of msgid "Set the switch state of the target device." msgstr "设置目标设备的开关状态。" -#: 3273464f057f478da7e917a23f397089 34c94b856df447a5816f6bb11f81c969 -#: 8a93321452a94fe89c747a602da01529 bf81d3fe2d7a40b49866c83a6df4e5f1 -#: ea3f43a6faff496c8986b80be14b15d4 +#: 481b09f5ad274ae7972167431385184c 5f8b61faae2e47de89a23dbc6f6eff03 +#: af24e3ae15e44f6e93dc27b7609390a9 f2ceef52067e40b09b9e52885a5710c4 +#: f84f17d00c89452a852eb128f024f95b #: iot_devices.switchc6.SwitchC6Controller.get_capacitor_voltage:3 #: iot_devices.switchc6.SwitchC6Controller.get_firmware_version:3 #: iot_devices.switchc6.SwitchC6Controller.get_switch_status:3 @@ -188,14 +189,14 @@ msgstr "设置目标设备的开关状态。" msgid "Target MAC address in \"XXXX-XXXX-XXXX\" format." msgstr "\"XXXX-XXXX-XXXX\"格式的目标MAC地址。" -#: 83178c7c38bd4f73b993aef331b393b3 +#: ebae2096383d41c1948922fb00cb141d #: iot_devices.switchc6.SwitchC6Controller.set_switch:4 of msgid "True to turn on, False to turn off." msgstr "True表示开启,False表示关闭。" -#: 4548df054c814090a0b8078652b5e11f 689831b7928742d29502b94728a580db -#: 9200b47b0ddf480b8ee0a1a51b4b1f74 9ab91feb95a843cd90263be2d03b0558 -#: 9bd17e36bc8a45cf9d625c5ec2ad568e +#: 078b836848c4435f91126ecbe56d7daf 3410a6e82a454e38af3de750556f5837 +#: 4a935b945891488fabb334269c78df2e 7b8a98f3038644b9819dc7ee09f5b8ea +#: c50c6866077c480c822ed5d4dbffc7d1 #: iot_devices.switchc6.SwitchC6Controller.get_capacitor_voltage:4 #: iot_devices.switchc6.SwitchC6Controller.get_firmware_version:4 #: iot_devices.switchc6.SwitchC6Controller.get_switch_status:4 @@ -204,49 +205,49 @@ msgstr "True表示开启,False表示关闭。" msgid "Timeout in milliseconds for waiting for a response (default is 5000)." msgstr "等待响应的超时时间(毫秒)(默认为5000)。" -#: 184d9829507e4c57bdbf558b5f0ae138 +#: 18aebacb5273458790a069f4d5367ff9 #: iot_devices.switchc6.SwitchC6Controller.set_switch:9 of msgid "|set_switch.png|" msgstr "" -#: ../../en/refs/iot-devices.switchc6.ref:7 39a17b3cb0d24f5f98cab4039741c821 +#: ../../en/refs/iot-devices.switchc6.ref:7 13e573293e1947fd9dbd55690ea1d54d msgid "set_switch.png" msgstr "" -#: b610415585bd484082c8e35cbe9746c3 +#: b7d589fb60484f26967c3358a4a30f40 #: iot_devices.switchc6.SwitchC6Controller.toggle_switch:1 of msgid "Toggle the switch status of the target device." msgstr "切换目标设备的开关状态。" -#: 52bddc708bc444a8a60fd8071fdfb8aa +#: eb73f4272714493b83d4fc73fbc6f512 #: iot_devices.switchc6.SwitchC6Controller.toggle_switch:8 of msgid "|toggle_switch.png|" msgstr "" -#: ../../en/refs/iot-devices.switchc6.ref:8 e64d34b3fb1f4945a3367db9abb81641 +#: ../../en/refs/iot-devices.switchc6.ref:8 bf7074600efc46739ef8ab9a80d1d76b msgid "toggle_switch.png" msgstr "" -#: 8c5465595f0948ee95ee164640285421 +#: e851468a7566470bafe9a96ccea56bfe #: iot_devices.switchc6.SwitchC6Controller.get_capacitor_voltage:1 of msgid "Get the capacitor voltage of the target device." msgstr "获取目标设备的电容电压。" -#: 10518091a8cc452db29bb403d6108046 b549692ac87246bca67cc13687a26942 -#: dc7f3ed269b6448aae1b69d9a1fc9c82 +#: 0b4cc2111298420db9e714bd92b502a6 342ab40f969b47e4a901c14b3f1c08bd +#: ca5b85354b6745aea621a714dbae4717 #: iot_devices.switchc6.SwitchC6Controller.get_capacitor_voltage #: iot_devices.switchc6.SwitchC6Controller.get_firmware_version #: iot_devices.switchc6.SwitchC6Controller.get_switch_status of msgid "Returns" msgstr "" -#: 6d1f019979d04659b58ffdb4489abbe9 +#: c23db0f037d9421bb0485d7e45f82a4e #: iot_devices.switchc6.SwitchC6Controller.get_capacitor_voltage:5 of msgid "The capacitor voltage as a float." msgstr "电容电压(浮点数)。" -#: 54831efd1fa1448c9e3ee1605926b8e0 6855fbaeb09747ab873700c09b9106cd -#: 8449fa4386124438a5976c3bb15dba3a dc76cb1b99c04282974952b62579f37a +#: 1d278d9c83ee4cc8a3185888f3476fc2 326956a2bc574f2b9bbf10409d208ada +#: 646592c215084da9bee75acd3ffe7067 f997397929414c3790c41ae2ed3e0890 #: iot_devices.switchc6.SwitchC6Controller.get_capacitor_voltage #: iot_devices.switchc6.SwitchC6Controller.get_firmware_version #: iot_devices.switchc6.SwitchC6Controller.get_switch_status @@ -254,74 +255,74 @@ msgstr "电容电压(浮点数)。" msgid "Return type" msgstr "" -#: 98aa6817f72f4bdb9d7b3093d63134d8 +#: e14c54792bcc455abcc40af03d61b4fa #: iot_devices.switchc6.SwitchC6Controller.get_capacitor_voltage:10 of msgid "|get_capacitor_voltage.png|" msgstr "" -#: ../../en/refs/iot-devices.switchc6.ref:3 f119861789eb4715acfd32e58f929257 +#: ../../en/refs/iot-devices.switchc6.ref:3 49fae596257e423d97498558de89e6df msgid "get_capacitor_voltage.png" msgstr "" -#: c75439dbc60c42c5af773b45205b4f56 +#: e1c40e18d0a149d2aa1b95792dc9234f #: iot_devices.switchc6.SwitchC6Controller.get_switch_status:1 of msgid "Get the switch status of the target device." msgstr "获取目标设备的开关状态。" -#: 650699a96a3e478ca87db249ae792ad5 +#: dae956cd3fde45ecb9fb872ff9dcbe89 #: iot_devices.switchc6.SwitchC6Controller.get_switch_status:5 of msgid "True if the switch is ON, False if it is OFF." msgstr "如果开关为开启状态返回True,关闭状态返回False。" -#: 1d468920fe0d4743a169fc4fe0cbef6f +#: eb23c9b6f7804b1f9c8437aab24438b5 #: iot_devices.switchc6.SwitchC6Controller.get_switch_status:10 of msgid "|get_switch_status.png|" msgstr "" -#: ../../en/refs/iot-devices.switchc6.ref:5 48e501a4230541b1864e3758a7b317d1 +#: ../../en/refs/iot-devices.switchc6.ref:5 149ef8f5d4184b55af9112aeb52da919 msgid "get_switch_status.png" msgstr "" -#: 6d07d692178b4b02bbe74a5580f3e704 +#: 3fa494873f894ec393f1367a31baeec2 #: iot_devices.switchc6.SwitchC6Controller.set_callback:1 of msgid "Set a callback function for the specified trigger." msgstr "为指定触发器设置回调函数。" -#: a7a6abd7958e48e19a19c1db5bc7ddb0 +#: 5b03b50cf6f14212b55d9a42447d6f40 #: iot_devices.switchc6.SwitchC6Controller.set_callback:3 of msgid "The callback function to be called when the trigger occurs." msgstr "触发器发生时要调用的回调函数。" -#: e14600e1dcb34de191bde269e9dff4c7 +#: 9f2aa39c76024f3a87a887119b222a44 #: iot_devices.switchc6.SwitchC6Controller.set_callback:4 of msgid "The trigger type (0 for OFF, 1 for ON)." msgstr "触发器类型(0表示关闭,1表示开启)。" -#: 2bb8726e0e5545a39bb9f1e594756b23 +#: 8f69152cbd1547b28dc0e7381edf5924 #: iot_devices.switchc6.SwitchC6Controller.set_callback:8 of msgid "|event.png|" msgstr "" -#: ../../en/refs/iot-devices.switchc6.ref:2 6a2aeb5985d749d89384772165a22352 +#: ../../en/refs/iot-devices.switchc6.ref:2 7e782079feb441ab87299eea85a3d77f msgid "event.png" msgstr "" -#: 0620e0400d6a41d7bb127c423287e2cb +#: 72d2104371274385ad48ea384271c934 #: iot_devices.switchc6.SwitchC6Controller.get_firmware_version:1 of msgid "Get the firmware version of the target device." msgstr "获取目标设备的固件版本。" -#: 866d59d651b3448a942067a574f5839f +#: 46317e3f85f54ed89dc4596e5da3e204 #: iot_devices.switchc6.SwitchC6Controller.get_firmware_version:6 of msgid "The firmware version as a string." msgstr "固件版本(字符串)。" -#: 7d594796e61146ed9adbf476f76a5e57 +#: 574c041f20a244ce9c738d802d03d68d #: iot_devices.switchc6.SwitchC6Controller.get_firmware_version:11 of msgid "|get_firmware_version.png|" msgstr "" -#: ../../en/refs/iot-devices.switchc6.ref:4 0c8456f08e8c4c23a4be0e697c4c26af +#: ../../en/refs/iot-devices.switchc6.ref:4 74cb811bc38a4538a94dc45cfd2b607e msgid "get_firmware_version.png" msgstr "" diff --git a/m5stack/libs/iot_devices/switchc6.py b/m5stack/libs/iot_devices/switchc6.py index 191c9381..d486bd6a 100644 --- a/m5stack/libs/iot_devices/switchc6.py +++ b/m5stack/libs/iot_devices/switchc6.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + import m5espnow import time import sys @@ -196,7 +200,7 @@ def __init__(self, target_mac: list, wifi_channel: int = 0, verbose: bool = Fals is_wait=True, ) self._verbose = verbose - self._callback = [None for _ in range(self.MAX)] + self._callback = None def espnow_recv_callback(self, espnow_obj): event_mac, event_data = espnow_obj.recv_data() @@ -248,9 +252,9 @@ def _data_handler(self, args): return if event.target_mac == "FFFF-FFFF-FFFF": - if self._callback[int(event.onoff)]: + if self._callback: micropython.schedule( - self._callback[int(event.onoff)], + self._callback, (self, event.source_mac, event.onoff, event.voltage), ) @@ -388,7 +392,7 @@ def get_switch_status(self, target_mac: str, timeout: int = 5000) -> bool: self._communicate(payload, self.queue, timeout=timeout) return self.queue.onoff - def set_callback(self, handler, trigger: Literal[0, 1]) -> None: + def set_callback(self, handler) -> None: """Set a callback function for the specified trigger. :param handler: The callback function to be called when the trigger occurs. @@ -404,7 +408,7 @@ def set_callback(self, handler, trigger: Literal[0, 1]) -> None: switchc6.set_callback(handler, trigger) """ - self._callback[trigger] = handler + self._callback = handler def get_firmware_version(self, target_mac: str, timeout: int = 5000) -> str: """Get the firmware version of the target device. From 3654a9181c6ed396f8eeab9f67aba618254c02fd Mon Sep 17 00:00:00 2001 From: lbuque <1102390310@qq.com> Date: Tue, 12 Aug 2025 11:00:12 +0800 Subject: [PATCH 217/322] _vfs_stream.c: Fix cannot open files with relative paths. Signed-off-by: lbuque <1102390310@qq.com> --- m5stack/_vfs_stream.c | 23 +++++++++++++++-------- m5stack/_vfs_stream.h | 2 +- m5stack/cmodules/lv_utils/modlv_utils.c | 2 +- 3 files changed, 17 insertions(+), 10 deletions(-) diff --git a/m5stack/_vfs_stream.c b/m5stack/_vfs_stream.c index dcc1fde1..f697c164 100644 --- a/m5stack/_vfs_stream.c +++ b/m5stack/_vfs_stream.c @@ -46,7 +46,7 @@ static int lfs2_get_mode(int flags) { ret |= flags & VFS_READ ? LFS2_O_RDONLY : 0; ret = flags & VFS_WRITE ? LFS2_O_WRONLY : 0; ret |= flags & VFS_APPEND ? LFS2_O_APPEND : 0; - ret |= flags & VFS_CREATE ? LFS2_O_CREAT : 0; + ret |= flags & VFS_CREATE ? LFS2_O_CREAT | LFS2_O_TRUNC: 0; return ret; } @@ -65,26 +65,29 @@ static int fatfs_get_mode(int flags) { void *vfs_stream_open(const char *path, int flags) { - ESP_LOGI(TAG, "vfs_stream_open: path=%s, mode=%d\n", path, flags); + ESP_LOGI(TAG, "vfs_stream_open: path=%s, mode=%d", path, flags); vfs_stream_t *vfs = calloc(1, sizeof(vfs_stream_t)); - const char *path_out; + const char *path_out = {'\0'}; mp_vfs_mount_t *existing_mount = mp_vfs_lookup_path(path, &path_out); if (existing_mount == MP_VFS_NONE || existing_mount == MP_VFS_ROOT) { ESP_LOGE(TAG, "No vfs mount"); goto _vfs_init_exit; } - if (strstr(path, "flash")) { + if (strstr(path_out, "flash")) { ESP_LOGD(TAG, "in flash"); vfs->lfs2 = &((mp_obj_vfs_lfs2_t *)MP_OBJ_TO_PTR(existing_mount->obj))->lfs; - } else if (strstr(path, "system")) { + } else if (strstr(path_out, "system")) { ESP_LOGD(TAG, "in system"); vfs->lfs2 = &((mp_obj_vfs_lfs2_t *)MP_OBJ_TO_PTR(existing_mount->obj))->lfs; - } else if (strstr(path, "sd")) { + } else if (strstr(path_out, "sd")) { ESP_LOGD(TAG, "in sd"); vfs->fatfs = &((fs_user_mount_t *)MP_OBJ_TO_PTR(existing_mount->obj))->fatfs; + } else { + ESP_LOGI(TAG, "default in flash"); + vfs->lfs2 = &((mp_obj_vfs_lfs2_t *)MP_OBJ_TO_PTR(existing_mount->obj))->lfs; } if (vfs->lfs2) { @@ -128,9 +131,9 @@ void *vfs_stream_open(const char *path, int flags) { } -uint32_t vfs_stream_read(void *file_p, void *buf, uint32_t btr) { +int32_t vfs_stream_read(void *file_p, void *buf, uint32_t btr) { vfs_stream_t *vfs = file_p; - uint32_t br = 0; + int32_t br = 0; if (vfs->lfs2) { br = lfs2_file_read(vfs->lfs2, vfs->file.lfs2_file, (uint8_t *)buf, btr); @@ -214,6 +217,10 @@ int32_t vfs_stream_tell(void *file_p) { void vfs_stream_close(void *file_p) { vfs_stream_t *vfs = file_p; + if (vfs == NULL) { + return; + } + if (vfs->lfs2) { lfs2_file_close(vfs->lfs2, vfs->file.lfs2_file); free(vfs->file.lfs2_file); diff --git a/m5stack/_vfs_stream.h b/m5stack/_vfs_stream.h index 2a643da6..f6a6bde4 100644 --- a/m5stack/_vfs_stream.h +++ b/m5stack/_vfs_stream.h @@ -28,7 +28,7 @@ #define VFS_CREATE 0x08 void *vfs_stream_open(const char *path, int flags); -uint32_t vfs_stream_read(void *file_p, void *buf, uint32_t btr); +int32_t vfs_stream_read(void *file_p, void *buf, uint32_t btr); ssize_t vfs_stream_write(void *file_p, const void *buf, size_t len); int32_t vfs_stream_seek(void *file_p, uint32_t pos, int whence); int32_t vfs_stream_tell(void *file_p); diff --git a/m5stack/cmodules/lv_utils/modlv_utils.c b/m5stack/cmodules/lv_utils/modlv_utils.c index 17566d44..5a1b5801 100644 --- a/m5stack/cmodules/lv_utils/modlv_utils.c +++ b/m5stack/cmodules/lv_utils/modlv_utils.c @@ -46,7 +46,7 @@ static void *lv_utils_fs_open_cb(lv_fs_drv_t *drv, const char *path, lv_fs_mode_ LV_UNUSED(drv); lv_fs_res_t res = LV_FS_RES_NOT_IMP; - ESP_LOGI("lv_utils", "fs_open_cb: path=%s, mode=%d\n", path, mode); + ESP_LOGI("lv_utils", "fs_open_cb: path=%s, mode=%d", path, mode); vfs_stream_t *vfs = lv_malloc(sizeof(vfs_stream_t)); From ffc0d92e028dc0a0c35c2f230f862dafaf9add6b Mon Sep 17 00:00:00 2001 From: lbuque <1102390310@qq.com> Date: Tue, 12 Aug 2025 11:14:34 +0800 Subject: [PATCH 218/322] modules/startup/tab5: Update EZData WebSocket protocol. Signed-off-by: lbuque <1102390310@qq.com> --- m5stack/modules/startup/tab5/launcher/common/ezdata.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/m5stack/modules/startup/tab5/launcher/common/ezdata.py b/m5stack/modules/startup/tab5/launcher/common/ezdata.py index c903ba24..a74faeac 100644 --- a/m5stack/modules/startup/tab5/launcher/common/ezdata.py +++ b/m5stack/modules/startup/tab5/launcher/common/ezdata.py @@ -107,7 +107,7 @@ async def fetch_all_data(self): msg = json.dumps( { "deviceToken": self._device_token, - "body": {"requestType": "DEVICE_DATA_LIST"}, + "body": {"requestType": 103}, } ) debug_print("ws send:", msg) From f3b54b231eed90d47e770313020b09b73eabec49 Mon Sep 17 00:00:00 2001 From: lbuque <1102390310@qq.com> Date: Fri, 22 Aug 2025 14:55:23 +0800 Subject: [PATCH 219/322] version.text: Update to V2.3.4. Signed-off-by: lbuque <1102390310@qq.com> --- m5stack/version.txt | 2 +- third-party/version.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/m5stack/version.txt b/m5stack/version.txt index 2e32f5aa..82dd2275 100644 --- a/m5stack/version.txt +++ b/m5stack/version.txt @@ -1 +1 @@ -V2.3.3 \ No newline at end of file +V2.3.4 \ No newline at end of file diff --git a/third-party/version.txt b/third-party/version.txt index 2e32f5aa..82dd2275 100644 --- a/third-party/version.txt +++ b/third-party/version.txt @@ -1 +1 @@ -V2.3.3 \ No newline at end of file +V2.3.4 \ No newline at end of file From 8bad19ff1235e5c08f65c5a476a0c2b794dee2e2 Mon Sep 17 00:00:00 2001 From: lbuque <1102390310@qq.com> Date: Mon, 25 Aug 2025 17:42:25 +0800 Subject: [PATCH 220/322] machine_hw_spi.c: Fix SPI deinit errors for LCD. Signed-off-by: lbuque <1102390310@qq.com> --- m5stack/libs/module/lan.py | 4 +- m5stack/libs/module/lora.py | 6 ++- m5stack/libs/module/lora868_v12.py | 2 +- m5stack/libs/module/mbus.py | 59 ++++++++++++++++++++++++------ m5stack/libs/module/usb.py | 2 +- m5stack/machine_hw_spi.c | 22 +++++------ 6 files changed, 67 insertions(+), 28 deletions(-) diff --git a/m5stack/libs/module/lan.py b/m5stack/libs/module/lan.py index c8248713..d53a56ab 100644 --- a/m5stack/libs/module/lan.py +++ b/m5stack/libs/module/lan.py @@ -2,7 +2,7 @@ # # SPDX-License-Identifier: MIT -from .mbus import spi2 +from . import mbus import network import machine import builtins @@ -14,7 +14,7 @@ def __new__(cls, cs=-1, rst=-1, int=-1): 0, phy_addr=0, phy_type=network.PHY_W5500, - spi=spi2, + spi=mbus.spi, cs=machine.Pin(cs), int=machine.Pin(int), power=machine.Pin(rst), diff --git a/m5stack/libs/module/lora.py b/m5stack/libs/module/lora.py index 1b3030c0..fc09fff9 100644 --- a/m5stack/libs/module/lora.py +++ b/m5stack/libs/module/lora.py @@ -9,6 +9,7 @@ from lora import SX1278 from lora import SX1276 from lora import RxPacket +import M5 class LoraModule: @@ -108,8 +109,11 @@ def __init__( "output_power": output_power, # dBm } + if M5.getBoard() in (M5.BOARD.M5Stack, M5.BOARD.M5StackCore2, M5.BOARD.M5Tough): + mbus.spi.init(baudrate=1000000, polarity=0, phase=0) + print(mbus.spi) self.modem = sx_instance( - spi=mbus.spi2, + spi=mbus.spi, cs=machine.Pin(pin_cs), dio0=machine.Pin(pin_irq), # dio1=machine.Pin(35), diff --git a/m5stack/libs/module/lora868_v12.py b/m5stack/libs/module/lora868_v12.py index 5637b402..41d03db8 100644 --- a/m5stack/libs/module/lora868_v12.py +++ b/m5stack/libs/module/lora868_v12.py @@ -91,7 +91,7 @@ def __init__( } self.modem = SX1262( - spi=mbus.spi2, + spi=mbus.spi, reset=machine.Pin(pin_rst), cs=machine.Pin(pin_cs), busy=machine.Pin(pin_busy), diff --git a/m5stack/libs/module/mbus.py b/m5stack/libs/module/mbus.py index dabf82a6..8dc97c08 100644 --- a/m5stack/libs/module/mbus.py +++ b/m5stack/libs/module/mbus.py @@ -7,24 +7,59 @@ from collections import namedtuple MBusIO = namedtuple( - "MBusIO", ["sda0", "scl0", "sda1", "scl1", "spi2_sck", "spi2_mosi", "spi2_miso"] + "MBusIO", ["sda0", "scl0", "sda1", "scl1", "spi_host", "spi_sck", "spi_mosi", "spi_miso"] ) iomap = { M5.BOARD.M5Stack: MBusIO( - sda0=21, scl0=22, sda1=21, scl1=22, spi2_sck=18, spi2_mosi=23, spi2_miso=19 + sda0=21, + scl0=22, + sda1=21, + scl1=22, + spi_host=2, + spi_sck=18, + spi_mosi=23, + spi_miso=19, # SPI3_HOST ), M5.BOARD.M5StackCore2: MBusIO( - sda0=32, scl0=33, sda1=21, scl1=22, spi2_sck=18, spi2_mosi=23, spi2_miso=38 + sda0=32, + scl0=33, + sda1=21, + scl1=22, + spi_host=2, + spi_sck=18, + spi_mosi=23, + spi_miso=38, # SPI3_HOST ), M5.BOARD.M5StackCoreS3: MBusIO( - sda0=2, scl0=1, sda1=12, scl1=11, spi2_sck=36, spi2_mosi=37, spi2_miso=35 + sda0=2, + scl0=1, + sda1=12, + scl1=11, + spi_host=1, + spi_sck=36, + spi_mosi=37, + spi_miso=35, # SPI2_HOST ), M5.BOARD.M5Tough: MBusIO( - sda0=32, scl0=33, sda1=21, scl1=22, spi2_sck=18, spi2_mosi=23, spi2_miso=38 + sda0=32, + scl0=33, + sda1=21, + scl1=22, + spi_host=2, + spi_sck=18, + spi_mosi=23, + spi_miso=38, # SPI3_HOST ), M5.BOARD.M5Tab5: MBusIO( - sda0=53, scl0=54, sda1=31, scl1=32, spi2_sck=5, spi2_mosi=18, spi2_miso=19 + sda0=53, + scl0=54, + sda1=31, + scl1=32, + spi_host=1, + spi_sck=5, + spi_mosi=18, + spi_miso=19, # SPI2_HOST ), }.get(M5.getBoard()) @@ -39,19 +74,19 @@ def _i2c1_init(): return machine.I2C(1, scl=machine.Pin(iomap.scl1), sda=machine.Pin(iomap.sda1), freq=100000) -def _spi2_init(): +def _spi_init(): return machine.SPI( - 1, - sck=machine.Pin(iomap.spi2_sck), - mosi=machine.Pin(iomap.spi2_mosi), - miso=machine.Pin(iomap.spi2_miso), + iomap.spi_host, + sck=machine.Pin(iomap.spi_sck), + mosi=machine.Pin(iomap.spi_mosi), + miso=machine.Pin(iomap.spi_miso), ) _attrs = { "i2c0": _i2c0_init, "i2c1": _i2c1_init, - "spi2": _spi2_init, + "spi": _spi_init, } diff --git a/m5stack/libs/module/usb.py b/m5stack/libs/module/usb.py index 763c4ad0..deec7ddc 100644 --- a/m5stack/libs/module/usb.py +++ b/m5stack/libs/module/usb.py @@ -91,7 +91,7 @@ def keycode_to_ascii(keycode, shift=False): class USBModule(UsbHID): def __init__(self, pin_cs=0, pin_int=14): super().__init__( - spi=mbus.spi2, cs=machine.Pin(pin_cs, machine.Pin.OUT), irq=machine.Pin(pin_int) + spi=mbus.spi, cs=machine.Pin(pin_cs, machine.Pin.OUT), irq=machine.Pin(pin_int) ) self.event = 0 self.event_callback = None diff --git a/m5stack/machine_hw_spi.c b/m5stack/machine_hw_spi.c index 2f08c56b..8ce8d3df 100644 --- a/m5stack/machine_hw_spi.c +++ b/m5stack/machine_hw_spi.c @@ -148,7 +148,7 @@ static void machine_hw_spi_deinit_internal(machine_hw_spi_obj_t *self) { case ESP_ERR_INVALID_STATE: // NOTE: // core2和cores3的屏幕和sd卡复用一个spi, - // 所以这里不需要对VSPI_HOST和SPI2_HOST进行初始化。 + // 所以这里不需要对 SPI 进行初始化。 if (!self->is_shared) { mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("SPI device already freed")); return; @@ -157,8 +157,8 @@ static void machine_hw_spi_deinit_internal(machine_hw_spi_obj_t *self) { // NOTE: // core2和cores3的屏幕和sd卡复用一个spi, - // 所以这里不需要对VSPI_HOST和SPI2_HOST进行初始化。 - if (self->is_shared) { + // 所以这里不需要对 SPI 进行初始化。 + if (!self->is_shared) { switch (spi_bus_free(self->host)) { case ESP_ERR_INVALID_ARG: mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("invalid configuration")); @@ -168,15 +168,15 @@ static void machine_hw_spi_deinit_internal(machine_hw_spi_obj_t *self) { mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("SPI bus already freed")); return; } - } - int8_t pins[3] = {self->miso, self->mosi, self->sck}; + int8_t pins[3] = {self->miso, self->mosi, self->sck}; - for (int i = 0; i < 3; i++) { - if (pins[i] != -1) { - esp_rom_gpio_pad_select_gpio(pins[i]); - esp_rom_gpio_connect_out_signal(pins[i], SIG_GPIO_OUT_IDX, false, false); - gpio_set_direction(pins[i], GPIO_MODE_INPUT); + for (int i = 0; i < 3; i++) { + if (pins[i] != -1) { + esp_rom_gpio_pad_select_gpio(pins[i]); + esp_rom_gpio_connect_out_signal(pins[i], SIG_GPIO_OUT_IDX, false, false); + gpio_set_direction(pins[i], GPIO_MODE_INPUT); + } } } } @@ -284,7 +284,7 @@ static void machine_hw_spi_init_internal(machine_hw_spi_obj_t *self, mp_arg_val_ case ESP_ERR_INVALID_STATE: // NOTE: // core2和cores3的屏幕和sd卡复用一个spi, - // 所以这里不需要对VSPI_HOST和SPI2_HOST进行初始化。 + // 所以这里不需要对 SPI 进行初始化。 self->is_shared = true; mp_warning("hw_spi", "SPI bus already initialized"); } From 873740acfec44453c08a73bfc2617d69782a8d3b Mon Sep 17 00:00:00 2001 From: lbuque <1102390310@qq.com> Date: Fri, 5 Sep 2025 10:21:38 +0800 Subject: [PATCH 221/322] _vfs_stream.c: Fix memory leak issues. Signed-off-by: lbuque <1102390310@qq.com> --- m5stack/_vfs_stream.c | 2 ++ m5stack/cmodules/m5unified/m5unified_widgets.c | 4 +++- m5stack/components/M5Unified/mpy_m5gfx.cpp | 6 ++++++ m5stack/components/M5Unified/mpy_m5widgets.cpp | 17 +++++++++++++++++ 4 files changed, 28 insertions(+), 1 deletion(-) diff --git a/m5stack/_vfs_stream.c b/m5stack/_vfs_stream.c index f697c164..cdf1454c 100644 --- a/m5stack/_vfs_stream.c +++ b/m5stack/_vfs_stream.c @@ -228,4 +228,6 @@ void vfs_stream_close(void *file_p) { } else if (vfs->fatfs) { f_close(&vfs->file.fat_file); } + free(vfs); + vfs = NULL; } diff --git a/m5stack/cmodules/m5unified/m5unified_widgets.c b/m5stack/cmodules/m5unified/m5unified_widgets.c index 2dd047ff..b8b740ea 100644 --- a/m5stack/cmodules/m5unified/m5unified_widgets.c +++ b/m5stack/cmodules/m5unified/m5unified_widgets.c @@ -13,13 +13,15 @@ MAKE_METHOD_KW(m5widgets_label, setCursor, 1); MAKE_METHOD_KW(m5widgets_label, setSize, 1); MAKE_METHOD_KW(m5widgets_label, setFont, 1); MAKE_METHOD_KW(m5widgets_label, setVisible, 1); +MAKE_METHOD_KW(m5widgets_label, __del__, 1); static const mp_rom_map_elem_t m5widgets_label_member_table[] = { MAKE_TABLE(m5widgets_label, setText), MAKE_TABLE(m5widgets_label, setColor), MAKE_TABLE(m5widgets_label, setCursor), MAKE_TABLE(m5widgets_label, setSize), MAKE_TABLE(m5widgets_label, setFont), - MAKE_TABLE(m5widgets_label, setVisible) + MAKE_TABLE(m5widgets_label, setVisible), + MAKE_TABLE(m5widgets_label, __del__) }; static MP_DEFINE_CONST_DICT(m5widgets_label_member, m5widgets_label_member_table); extern mp_obj_t m5widgets_label_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args); diff --git a/m5stack/components/M5Unified/mpy_m5gfx.cpp b/m5stack/components/M5Unified/mpy_m5gfx.cpp index c36c28c8..528ca082 100644 --- a/m5stack/components/M5Unified/mpy_m5gfx.cpp +++ b/m5stack/components/M5Unified/mpy_m5gfx.cpp @@ -183,6 +183,8 @@ mp_obj_t gfx_setFont(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) if (fontWrapper == NULL) { fontWrapper = new LFS2Wrapper(); ((gfx_obj_t *)MP_OBJ_TO_PTR(pos_args[0]))->font_wrapper = fontWrapper; + } else { + fontWrapper->close(); } fontWrapper->open(mp_obj_str_get_str(args[ARG_font].u_obj), VFS_READ); gfx->loadFont((lgfx::DataWrapper *)fontWrapper); @@ -829,6 +831,7 @@ mp_obj_t gfx_drawJpg(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) , args[ARG_maxW].u_int, args[ARG_maxH].u_int , args[ARG_offX].u_int, args[ARG_offY].u_int , mp_obj_get_float(args[ARG_scaleX].u_obj), mp_obj_get_float(args[ARG_scaleY].u_obj)); + wrapper.close(); } else { // buffer mp_buffer_info_t bufinfo; mp_get_buffer_raise(args[ARG_img].u_obj, &bufinfo, MP_BUFFER_READ); @@ -870,6 +873,7 @@ mp_obj_t gfx_drawPng(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) , args[ARG_maxW].u_int, args[ARG_maxH].u_int , args[ARG_offX].u_int, args[ARG_offY].u_int , mp_obj_get_float(args[ARG_scaleX].u_obj), mp_obj_get_float(args[ARG_scaleY].u_obj)); + wrapper.close(); } else { // buffer mp_buffer_info_t bufinfo; mp_get_buffer_raise(args[ARG_img].u_obj, &bufinfo, MP_BUFFER_READ); @@ -911,6 +915,7 @@ mp_obj_t gfx_drawBmp(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) , args[ARG_maxW].u_int, args[ARG_maxH].u_int , args[ARG_offX].u_int, args[ARG_offY].u_int , mp_obj_get_float(args[ARG_scaleX].u_obj), mp_obj_get_float(args[ARG_scaleY].u_obj)); + wrapper.close(); } else { // buffer mp_buffer_info_t bufinfo; mp_get_buffer_raise(args[ARG_img].u_obj, &bufinfo, MP_BUFFER_READ); @@ -977,6 +982,7 @@ mp_obj_t gfx_drawImage(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_arg , args[ARG_offX].u_int, args[ARG_offY].u_int , mp_obj_get_float(args[ARG_scaleX].u_obj), mp_obj_get_float(args[ARG_scaleY].u_obj)); } + wrapper.close(); } else { // buffer mp_buffer_info_t bufinfo; mp_get_buffer_raise(args[ARG_img].u_obj, &bufinfo, MP_BUFFER_READ); diff --git a/m5stack/components/M5Unified/mpy_m5widgets.cpp b/m5stack/components/M5Unified/mpy_m5widgets.cpp index 0456a52c..f99e1576 100644 --- a/m5stack/components/M5Unified/mpy_m5widgets.cpp +++ b/m5stack/components/M5Unified/mpy_m5widgets.cpp @@ -297,6 +297,14 @@ mp_obj_t m5widgets_label_setVisible(size_t n_args, const mp_obj_t *pos_args, mp_ return mp_const_none; } +mp_obj_t m5widgets_label___del__(mp_obj_t self_in) { + widgets_label_obj_t *self = (widgets_label_obj_t *)MP_OBJ_TO_PTR(self_in); + + self->rtfont->unloadFont(); + self->fontWrapper->close(); + return mp_const_none; +} + extern gfx_obj_t m5_display; mp_obj_t m5widgets_label_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { enum {ARG_text, ARG_x, ARG_y, ARG_text_sz, ARG_text_c, ARG_bg_c, ARG_font, ARG_parent}; @@ -508,6 +516,14 @@ mp_obj_t m5widgets_title_setVisible(size_t n_args, const mp_obj_t *pos_args, mp_ return mp_const_none; } +mp_obj_t m5widgets_title___del__(mp_obj_t self_in) { + widgets_title_obj_t *self = (widgets_title_obj_t *)MP_OBJ_TO_PTR(self_in); + + self->rtfont->unloadFont(); + self->fontWrapper->close(); + return mp_const_none; +} + mp_obj_t m5widgets_title_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { enum {ARG_text, ARG_text_x, ARG_text_c, ARG_bg_c, ARG_font, ARG_parent}; /* *FORMAT-OFF* */ @@ -723,6 +739,7 @@ static bool m5widgets_image_draw_helper(widgets_image_obj_t *self) { } else { printf("Image format was not bmp, jpg, png\r\n"); } + wrapper.close(); if (!ret) { printf("Get %s width and height failed\r\n", self->img); From 2e590bfff46ef35f0ff678242b706dbd19660d13 Mon Sep 17 00:00:00 2001 From: lbuque <1102390310@qq.com> Date: Mon, 11 Aug 2025 09:46:33 +0800 Subject: [PATCH 222/322] libs/m5ui: Add M5Spinbox support. Signed-off-by: lbuque <1102390310@qq.com> --- docs/en/m5ui/index.rst | 1 + docs/en/m5ui/spinbox.rst | 287 +++++++++ docs/en/refs/m5ui.spinbox.ref | 37 ++ .../locales/zh_CN/LC_MESSAGES/m5ui/spinbox.po | 606 ++++++++++++++++++ .../spinbox/cores3_spinbox_basic_example.m5f2 | 1 + .../spinbox/cores3_spinbox_basic_example.py | 84 +++ m5stack/libs/m5ui/__init__.py | 1 + m5stack/libs/m5ui/manifest.py | 1 + m5stack/libs/m5ui/spinbox.py | 390 +++++++++++ tests/m5ui/test_spinbox.py | 74 +++ 10 files changed, 1482 insertions(+) create mode 100644 docs/en/m5ui/spinbox.rst create mode 100644 docs/en/refs/m5ui.spinbox.ref create mode 100644 docs/locales/zh_CN/LC_MESSAGES/m5ui/spinbox.po create mode 100644 examples/m5ui/spinbox/cores3_spinbox_basic_example.m5f2 create mode 100644 examples/m5ui/spinbox/cores3_spinbox_basic_example.py create mode 100644 m5stack/libs/m5ui/spinbox.py create mode 100644 tests/m5ui/test_spinbox.py diff --git a/docs/en/m5ui/index.rst b/docs/en/m5ui/index.rst index 6ddd937f..b575b573 100644 --- a/docs/en/m5ui/index.rst +++ b/docs/en/m5ui/index.rst @@ -49,5 +49,6 @@ Classes roller.rst scale.rst slider.rst + spinbox.rst switch.rst textarea.rst diff --git a/docs/en/m5ui/spinbox.rst b/docs/en/m5ui/spinbox.rst new file mode 100644 index 00000000..7efc617f --- /dev/null +++ b/docs/en/m5ui/spinbox.rst @@ -0,0 +1,287 @@ +.. currentmodule:: m5ui + +M5Spinbox +========= + +.. include:: ../refs/m5ui.spinbox.ref + +M5Spinbox is a widget that provides a numeric input interface with increment and decrement buttons. +It displays a numeric value that can be adjusted by clicking the + and - buttons or by typing directly. +The spinbox supports both integer and floating-point numbers with customizable digit count and decimal precision. + + +UiFlow2 Example +--------------- + +basic spinbox +^^^^^^^^^^^^^ + +Open the |cores3_spinbox_basic_example.m5f2| project in UiFlow2. + +This example demonstrates how to create a spinbox with customizable range and precision settings. + +UiFlow2 Code Block: + + |cores3_spinbox_basic_example.png| + +Example output: + + None + + +MicroPython Example +------------------- + +basic spinbox +^^^^^^^^^^^^^ + +This example demonstrates how to create a spinbox with customizable range and precision settings. + +MicroPython Code Block: + + .. literalinclude:: ../../../examples/m5ui/spinbox/cores3_spinbox_basic_example.py + :language: python + :linenos: + +Example output: + + None + + +**API** +------- + +M5Spinbox +^^^^^^^^^ + +.. autoclass:: m5ui.spinbox.M5Spinbox + :members: + :exclude-members: set_digit_format, value2raw, raw2value + + .. py:method:: set_flag(flag, value) + + Set a flag on the object. If ``value`` is True, the flag is added; if False, the flag is removed. + + :param int flag: The flag to set. + :param bool value: If True, the flag is added; if False, the flag is removed. + :return: None + + UiFlow2 Code Block: + + |set_flag.png| + + MicroPython Code Block: + + .. code-block:: python + + spinbox_0.set_flag(lv.obj.FLAG.HIDDEN, True) + + .. py:method:: toggle_flag(flag) + + Toggle a flag on the object. If the flag is set, it is removed; if not set, it is added. + + :param int flag: The flag to toggle. + :return: None + + UiFlow2 Code Block: + + |toggle_flag.png| + + MicroPython Code Block: + + .. code-block:: python + + spinbox_0.toggle_flag(lv.obj.FLAG.HIDDEN) + + .. py:method:: set_state(state, value) + + Set the state of the spinbox. If ``value`` is True, the state is set; if False, the state is unset. + + :param int state: The state to set. + :param bool value: If True, the state is set; if False, the state is unset. + :return: None + + UiFlow2 Code Block: + + |set_state.png| + + MicroPython Code Block: + + .. code-block:: python + + spinbox_0.set_state(lv.STATE.DISABLED, True) + + .. py:method:: toggle_state(state) + + Toggle the state of the spinbox. If the state is set, it is unset; if not set, it is set. + + :param int state: The state to toggle. + :return: None + + UiFlow2 Code Block: + + |toggle_state.png| + + MicroPython Code Block: + + .. code-block:: python + + spinbox_0.toggle_state(lv.STATE.CHECKED) + + .. py:method:: set_pos(x, y) + + Set the position of the spinbox. + + :param int x: The x-coordinate of the spinbox. + :param int y: The y-coordinate of the spinbox. + :return: None + + UiFlow2 Code Block: + + |set_pos.png| + + MicroPython Code Block: + + .. code-block:: python + + spinbox_0.set_pos(100, 200) + + .. py:method:: set_x(x) + + Set the x-coordinate of the spinbox. + + :param int x: The x-coordinate of the spinbox. + :return: None + + UiFlow2 Code Block: + + |set_x.png| + + MicroPython Code Block: + + .. code-block:: python + + spinbox_0.set_x(150) + + .. py:method:: set_y(y) + + Set the y-coordinate of the spinbox. + + :param int y: The y-coordinate of the spinbox. + :return: None + + UiFlow2 Code Block: + + |set_y.png| + + MicroPython Code Block: + + .. code-block:: python + + spinbox_0.set_y(250) + + .. py:method:: set_size(width, height) + + Set the size of the spinbox. + + :param int width: The width of the spinbox. + :param int height: The height of the spinbox. + :return: None + + UiFlow2 Code Block: + + |set_size.png| + + MicroPython Code Block: + + .. code-block:: python + + spinbox_0.set_size(150, 40) + + .. py:method:: set_width(width) + + Set the width of the spinbox. + + :param int width: The width of the spinbox. + :return: None + + UiFlow2 Code Block: + + |set_width.png| + + MicroPython Code Block: + + .. code-block:: python + + spinbox_0.set_width(180) + + .. py:method:: get_width() + + Get the width of the spinbox. + + :return: The width of the spinbox. + :rtype: int + + UiFlow2 Code Block: + + |get_width.png| + + MicroPython Code Block: + + .. code-block:: python + + width = spinbox_0.get_width() + + .. py:method:: set_height(height) + + Set the height of the spinbox. + + :param int height: The height of the spinbox. + :return: None + + UiFlow2 Code Block: + + |set_height.png| + + MicroPython Code Block: + + .. code-block:: python + + spinbox_0.set_height(50) + + .. py:method:: get_height() + + Get the height of the spinbox. + + :return: The height of the spinbox. + :rtype: int + + UiFlow2 Code Block: + + |get_height.png| + + MicroPython Code Block: + + .. code-block:: python + + height = spinbox_0.get_height() + + .. py:method:: align_to(obj, align, x, y) + + Align the spinbox to another object. + + :param lv.obj obj: The object to align to. + :param int align: The alignment type. + :param int x: The x-offset from the aligned object. + :param int y: The y-offset from the aligned object. + :return: None + + UiFlow2 Code Block: + + |align_to.png| + + MicroPython Code Block: + + .. code-block:: python + + spinbox_0.align_to(label_0, lv.ALIGN.OUT_BOTTOM_MID, 0, 10) diff --git a/docs/en/refs/m5ui.spinbox.ref b/docs/en/refs/m5ui.spinbox.ref new file mode 100644 index 00000000..9a3d70fc --- /dev/null +++ b/docs/en/refs/m5ui.spinbox.ref @@ -0,0 +1,37 @@ +.. |align_to.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/spinbox/align_to.png +.. |decrement.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/spinbox/decrement.png +.. |event.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/spinbox/event.png +.. |get_height.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/spinbox/get_height.png +.. |get_value.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/spinbox/get_value.png +.. |get_width.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/spinbox/get_width.png +.. |increment.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/spinbox/increment.png +.. |set_bg_color.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/spinbox/set_bg_color.png +.. |set_border_color.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/spinbox/set_border_color.png +.. |set_default_size.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/spinbox/set_default_size.png +.. |set_flag.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/spinbox/set_flag.png +.. |set_height.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/spinbox/set_height.png +.. |set_pos.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/spinbox/set_pos.png +.. |set_range.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/spinbox/set_range.png +.. |set_rollover.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/spinbox/set_rollover.png +.. |set_size.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/spinbox/set_size.png +.. |set_state.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/spinbox/set_state.png +.. |set_step.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/spinbox/set_step.png +.. |set_style_border_width.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/spinbox/set_style_border_width.png +.. |set_style_radius.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/spinbox/set_style_radius.png +.. |set_value.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/spinbox/set_value.png +.. |set_width.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/spinbox/set_width.png +.. |set_x.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/spinbox/set_x.png +.. |set_y.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/spinbox/set_y.png +.. |toggle_flag.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/spinbox/toggle_flag.png +.. |toggle_state.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/spinbox/toggle_state.png + +.. |cores3_spinbox_basic_example.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/spinbox/cores3_spinbox_basic_example.png + +.. |cores3_spinbox_basic_example.m5f2| raw:: html + + + cores3_spinbox_basic_example.m5f2 + diff --git a/docs/locales/zh_CN/LC_MESSAGES/m5ui/spinbox.po b/docs/locales/zh_CN/LC_MESSAGES/m5ui/spinbox.po new file mode 100644 index 00000000..cdcd56ae --- /dev/null +++ b/docs/locales/zh_CN/LC_MESSAGES/m5ui/spinbox.po @@ -0,0 +1,606 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2016 - 2025 M5Stack Technology Co., Ltd +# This file is distributed under the same license as the UIFlow2 Programming +# Guide package. +# FIRST AUTHOR , 2025. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: UIFlow2 Programming Guide \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-08-27 17:46+0800\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: zh_CN\n" +"Language-Team: zh_CN \n" +"Plural-Forms: nplurals=1; plural=0;\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.17.0\n" + +#: ../../en/m5ui/spinbox.rst:4 ../../en/m5ui/spinbox.rst:55 +#: 4c4d49a533e54005a93490d3a75ecdf0 b40fec91acbf431baf03013474563f9f +msgid "M5Spinbox" +msgstr "" + +#: ../../en/m5ui/spinbox.rst:8 f95a12e4dfe04f5888cbfd5f83dd134a +msgid "" +"M5Spinbox is a widget that provides a numeric input interface with " +"increment and decrement buttons. It displays a numeric value that can be " +"adjusted by clicking the + and - buttons or by typing directly. The " +"spinbox supports both integer and floating-point numbers with " +"customizable digit count and decimal precision." +msgstr "" +"M5Spinbox是一个提供数字输入界面的控件,具有递增和递减按钮。" +"它显示一个数字值,可以通过单击+和-按钮或直接输入来调整。" +"数字输入框支持整数和浮点数,具有可自定义的数字位数和小数精度。" + +#: ../../en/m5ui/spinbox.rst:14 9073c63fbadd4280b15aabd5e8528969 +msgid "UiFlow2 Example" +msgstr "UiFlow2 应用示例" + +#: ../../en/m5ui/spinbox.rst:17 ../../en/m5ui/spinbox.rst:36 +#: 286e13309ae54e55be1e4c1561c4ae35 +msgid "basic spinbox" +msgstr "基础数字输入框" + +#: ../../en/m5ui/spinbox.rst:19 57413b9cb5ef4d988e9d18c7d532cb26 +msgid "Open the |cores3_spinbox_basic_example.m5f2| project in UiFlow2." +msgstr "在UiFlow2中打开 |cores3_spinbox_basic_example.m5f2| 项目。" + +#: ../../en/m5ui/spinbox.rst:21 ../../en/m5ui/spinbox.rst:38 +#: f727620d42254dc2b4e8feef4cecbef3 +msgid "" +"This example demonstrates how to create a spinbox with customizable range" +" and precision settings." +msgstr "此示例演示如何创建具有可自定义范围和精度设置的数字输入框。" + +#: ../../en/m5ui/spinbox.rst:23 ../../en/m5ui/spinbox.rst:69 +#: ../../en/m5ui/spinbox.rst:86 ../../en/m5ui/spinbox.rst:104 +#: ../../en/m5ui/spinbox.rst:121 ../../en/m5ui/spinbox.rst:139 +#: ../../en/m5ui/spinbox.rst:156 ../../en/m5ui/spinbox.rst:173 +#: ../../en/m5ui/spinbox.rst:191 ../../en/m5ui/spinbox.rst:208 +#: ../../en/m5ui/spinbox.rst:225 ../../en/m5ui/spinbox.rst:242 +#: ../../en/m5ui/spinbox.rst:259 ../../en/m5ui/spinbox.rst:279 +#: 0ed679adc9ee474cbc62aaa4ad2d7869 bf23a489e6bb49819e3b07ccf807e74b +#: m5ui.spinbox.M5Spinbox.add_event_cb:7 m5ui.spinbox.M5Spinbox.get_value:6 +#: m5ui.spinbox.M5Spinbox.set_bg_color:8 +#: m5ui.spinbox.M5Spinbox.set_border_color:8 m5ui.spinbox.M5Spinbox.set_range:8 +#: m5ui.spinbox.M5Spinbox.set_step:6 +#: m5ui.spinbox.M5Spinbox.set_style_border_width:7 +#: m5ui.spinbox.M5Spinbox.set_style_radius:6 m5ui.spinbox.M5Spinbox.set_value:6 +#: of +msgid "UiFlow2 Code Block:" +msgstr "UiFlow2 代码块:" + +#: ../../en/m5ui/spinbox.rst:25 a45738ac5353412fb3e87a67b0b9aa00 +msgid "|cores3_spinbox_basic_example.png|" +msgstr "" + +#: ../../en/refs/m5ui.spinbox.ref:28 0d0a89f04d8047068995bffd177ca5f7 +msgid "cores3_spinbox_basic_example.png" +msgstr "" + +#: ../../en/m5ui/spinbox.rst:27 ../../en/m5ui/spinbox.rst:46 +#: 8f4714fa1574423181e5e19bc1b3f441 +msgid "Example output:" +msgstr "示例输出:" + +#: ../../en/m5ui/spinbox.rst:29 ../../en/m5ui/spinbox.rst:48 +#: ../../en/m5ui/spinbox.rst:67 ../../en/m5ui/spinbox.rst:84 +#: ../../en/m5ui/spinbox.rst:102 ../../en/m5ui/spinbox.rst:119 +#: ../../en/m5ui/spinbox.rst:137 ../../en/m5ui/spinbox.rst:154 +#: ../../en/m5ui/spinbox.rst:171 ../../en/m5ui/spinbox.rst:189 +#: ../../en/m5ui/spinbox.rst:206 ../../en/m5ui/spinbox.rst:240 +#: ../../en/m5ui/spinbox.rst:277 b7d2c0b061464af787260c9b866f8b80 +#: cb4f44d15d7d40c283113052735f5f4d m5ui.spinbox.M5Spinbox.set_bg_color:6 +#: m5ui.spinbox.M5Spinbox.set_border_color:6 +#: m5ui.spinbox.M5Spinbox.set_style_border_width:5 of +msgid "None" +msgstr "" + +#: ../../en/m5ui/spinbox.rst:33 9afa53e059734a37aa9b0b96105d23c8 +msgid "MicroPython Example" +msgstr "MicroPython 应用示例" + +#: ../../en/m5ui/spinbox.rst:40 ../../en/m5ui/spinbox.rst:73 +#: ../../en/m5ui/spinbox.rst:90 ../../en/m5ui/spinbox.rst:108 +#: ../../en/m5ui/spinbox.rst:125 ../../en/m5ui/spinbox.rst:143 +#: ../../en/m5ui/spinbox.rst:160 ../../en/m5ui/spinbox.rst:177 +#: ../../en/m5ui/spinbox.rst:195 ../../en/m5ui/spinbox.rst:212 +#: ../../en/m5ui/spinbox.rst:229 ../../en/m5ui/spinbox.rst:246 +#: ../../en/m5ui/spinbox.rst:263 ../../en/m5ui/spinbox.rst:283 +#: 0f71e03582db4a32869dc85227b04ecd 88e80a55ec3041a5944caed12440e1dc +#: m5ui.spinbox.M5Spinbox.add_event_cb:11 m5ui.spinbox.M5Spinbox.get_value:10 +#: m5ui.spinbox.M5Spinbox.set_bg_color:12 +#: m5ui.spinbox.M5Spinbox.set_border_color:12 +#: m5ui.spinbox.M5Spinbox.set_range:12 m5ui.spinbox.M5Spinbox.set_step:10 +#: m5ui.spinbox.M5Spinbox.set_style_border_width:11 +#: m5ui.spinbox.M5Spinbox.set_style_radius:10 +#: m5ui.spinbox.M5Spinbox.set_value:10 of +msgid "MicroPython Code Block:" +msgstr "MicroPython 代码块:" + +#: ../../en/m5ui/spinbox.rst:52 56b1837487674fe99c6576327ee772a7 +msgid "**API**" +msgstr "API参考" + +#: d1bf6b9387a44250ad5ebaaaf395defb m5ui.spinbox.M5Spinbox:1 of +msgid "Bases: :py:class:`~lvgl.obj`" +msgstr "" + +#: f1c87c53952f4cff905de9f6077d3d1a m5ui.spinbox.M5Spinbox:1 of +msgid "Create a spinbox widget." +msgstr "创建一个数字输入框控件。" + +#: ../../en/m5ui/spinbox.rst 4f02a1e3f53140af8979eb24067cac33 +#: 8f45717930a840ac832596eea966333a m5ui.spinbox.M5Spinbox.add_event_cb +#: m5ui.spinbox.M5Spinbox.set_bg_color m5ui.spinbox.M5Spinbox.set_border_color +#: m5ui.spinbox.M5Spinbox.set_range m5ui.spinbox.M5Spinbox.set_step +#: m5ui.spinbox.M5Spinbox.set_style_border_width +#: m5ui.spinbox.M5Spinbox.set_style_radius m5ui.spinbox.M5Spinbox.set_value of +msgid "Parameters" +msgstr "" + +#: e108057c01be42df976b1bec935e4706 m5ui.spinbox.M5Spinbox:3 of +msgid "The x position of the spinbox." +msgstr "数字输入框的x位置。" + +#: e10878074c4d42ad804a472b2d59cae1 m5ui.spinbox.M5Spinbox:4 of +msgid "The y position of the spinbox." +msgstr "数字输入框的y位置。" + +#: ../../en/m5ui/spinbox.rst:187 ../../en/m5ui/spinbox.rst:205 +#: ../../en/m5ui/spinbox.rst:222 5021d44cd4f5473f9e8ebcb12845c14b +#: m5ui.spinbox.M5Spinbox:5 of +msgid "The width of the spinbox." +msgstr "数字输入框的宽度。" + +#: ../../en/m5ui/spinbox.rst:188 ../../en/m5ui/spinbox.rst:239 +#: ../../en/m5ui/spinbox.rst:256 17087b6462e14c278ee73c0ab0429943 +#: m5ui.spinbox.M5Spinbox:6 of +msgid "The height of the spinbox." +msgstr "数字输入框的高度。" + +#: c8ba2294c0724deeabc323fa84984960 m5ui.spinbox.M5Spinbox:7 of +msgid "The initial value of the spinbox." +msgstr "数字输入框的初始值。" + +#: 06fdfab8762e48deaa969cef206be805 m5ui.spinbox.M5Spinbox:8 +#: m5ui.spinbox.M5Spinbox.set_range:3 of +msgid "The minimum value of the spinbox." +msgstr "数字输入框的最小值。" + +#: 7fe5cae89b824d5a96593fb08c1523c6 m5ui.spinbox.M5Spinbox:9 +#: m5ui.spinbox.M5Spinbox.set_range:5 of +msgid "The maximum value of the spinbox." +msgstr "数字输入框的最大值。" + +#: 6468ecda8d8d4a4082f281ce2b7f8faa m5ui.spinbox.M5Spinbox:10 of +msgid "The number of digits to display." +msgstr "显示的数字位数。" + +#: 26e6bee0eba54bef858527536b62cbcc m5ui.spinbox.M5Spinbox:11 of +msgid "The number of decimal places." +msgstr "小数位数。" + +#: 259ddf549eb1412d8c8995908035587f m5ui.spinbox.M5Spinbox:12 of +msgid "The font to use for the spinbox." +msgstr "数字输入框使用的字体。" + +#: c137d5a58dba4de39adc8432957d20b5 m5ui.spinbox.M5Spinbox:13 of +msgid "The parent object of the spinbox." +msgstr "数字输入框的父对象。" + +#: ../../en/m5ui/spinbox.rst:63 c4b4f50425a04b9c9a97add82e04c740 +msgid "" +"Set a flag on the object. If ``value`` is True, the flag is added; if " +"False, the flag is removed." +msgstr "" +"在对象上设置标志。如果 ``value`` 为True,则添加标志;" +"如果为False,则移除标志。" + +#: ../../en/m5ui/spinbox.rst:65 b9681eb7a2144d9092be78b9e09511f6 +msgid "The flag to set." +msgstr "要设置的标志。" + +#: ../../en/m5ui/spinbox.rst:66 0d46cb119aa1473ba685244458bcd281 +msgid "If True, the flag is added; if False, the flag is removed." +msgstr "如果为True,则添加标志;如果为False,则移除标志。" + +#: ../../en/m5ui/spinbox.rst 8e3a60ccf9da485985c7163f39254b83 +#: m5ui.spinbox.M5Spinbox.get_value m5ui.spinbox.M5Spinbox.set_bg_color +#: m5ui.spinbox.M5Spinbox.set_border_color +#: m5ui.spinbox.M5Spinbox.set_style_border_width of +msgid "Returns" +msgstr "" + +#: ../../en/m5ui/spinbox.rst:71 f5a9cb7141a640ab9747e3d9ec6f5156 +msgid "|set_flag.png|" +msgstr "" + +#: ../../en/refs/m5ui.spinbox.ref:11 ef8a8a9950f1439d896e2bb6973e8d39 +msgid "set_flag.png" +msgstr "" + +#: ../../en/m5ui/spinbox.rst:81 a9d35daa9f6f42d9b61b0134b0b3afcf +msgid "" +"Toggle a flag on the object. If the flag is set, it is removed; if not " +"set, it is added." +msgstr "" +"切换对象上的标志。如果标志已设置,则将其移除;" +"如果未设置,则将其添加。" + +#: ../../en/m5ui/spinbox.rst:83 a5a9fa6d48e94afea397c1ea982bccd3 +msgid "The flag to toggle." +msgstr "要切换的标志。" + +#: ../../en/m5ui/spinbox.rst:88 139fa0735bc24cba833163ab7cc5ba1b +msgid "|toggle_flag.png|" +msgstr "" + +#: ../../en/refs/m5ui.spinbox.ref:25 4413db6a673a4600af503d63dea4b330 +msgid "toggle_flag.png" +msgstr "" + +#: ../../en/m5ui/spinbox.rst:98 723ece88774b48b3b48591e43d7d4e34 +msgid "" +"Set the state of the spinbox. If ``value`` is True, the state is set; if " +"False, the state is unset." +msgstr "" +"设置数字输入框的状态。如果 ``value`` 为True,则设置状态;" +"如果为False,则取消设置状态。" + +#: ../../en/m5ui/spinbox.rst:100 b91ea714f42847d598feddb260623a67 +msgid "The state to set." +msgstr "要设置的状态。" + +#: ../../en/m5ui/spinbox.rst:101 c2ae5fb749ee49969c73a3e93e1f7330 +msgid "If True, the state is set; if False, the state is unset." +msgstr "如果为True,则设置状态;如果为False,则取消设置状态。" + +#: ../../en/m5ui/spinbox.rst:106 4b37653a89404111b5a373ad022b969d +msgid "|set_state.png|" +msgstr "" + +#: ../../en/refs/m5ui.spinbox.ref:17 aac3d075c1f6477fa6ec0698c2382ec1 +msgid "set_state.png" +msgstr "" + +#: ../../en/m5ui/spinbox.rst:116 614d5a7bbb604bafaaca50371096ab00 +msgid "" +"Toggle the state of the spinbox. If the state is set, it is unset; if not" +" set, it is set." +msgstr "" +"切换数字输入框的状态。如果状态已设置,则取消设置;" +"如果未设置,则将其设置。" + +#: ../../en/m5ui/spinbox.rst:118 0a9d2ed0258049718e0049a2a0564e94 +msgid "The state to toggle." +msgstr "要切换的状态。" + +#: ../../en/m5ui/spinbox.rst:123 4cbb5d34347e4e8a8b862b73f6bcc334 +msgid "|toggle_state.png|" +msgstr "" + +#: ../../en/refs/m5ui.spinbox.ref:26 f36796c7b9214e099b0143ba00654e6b +msgid "toggle_state.png" +msgstr "" + +#: ../../en/m5ui/spinbox.rst:133 6f15b9e9326e4a1e9d2f45c32ec61b36 +msgid "Set the position of the spinbox." +msgstr "设置数字输入框的位置。" + +#: ../../en/m5ui/spinbox.rst:135 ../../en/m5ui/spinbox.rst:153 +#: 4e33222147b54366b81b228a0911d401 +msgid "The x-coordinate of the spinbox." +msgstr "数字输入框的x坐标。" + +#: ../../en/m5ui/spinbox.rst:136 ../../en/m5ui/spinbox.rst:170 +#: a538226647ba455db17165639a76e847 +msgid "The y-coordinate of the spinbox." +msgstr "数字输入框的y坐标。" + +#: ../../en/m5ui/spinbox.rst:141 921f0f798a944a0c96431e12680002e6 +msgid "|set_pos.png|" +msgstr "" + +#: ../../en/refs/m5ui.spinbox.ref:13 77cd935cfdc74e179bca00ff79105f28 +msgid "set_pos.png" +msgstr "" + +#: ../../en/m5ui/spinbox.rst:151 ab037fff9e5541148495e88773ac5ed1 +msgid "Set the x-coordinate of the spinbox." +msgstr "设置数字输入框的x坐标。" + +#: ../../en/m5ui/spinbox.rst:158 6f59f831f6c7498eba643dcfe96400d8 +msgid "|set_x.png|" +msgstr "" + +#: ../../en/refs/m5ui.spinbox.ref:23 d061e7b30c9947b4b5b543805538a023 +msgid "set_x.png" +msgstr "" + +#: ../../en/m5ui/spinbox.rst:168 e9d1b07c9cf145548b096d31414efa35 +msgid "Set the y-coordinate of the spinbox." +msgstr "设置数字输入框的y坐标。" + +#: ../../en/m5ui/spinbox.rst:175 1c3f5a03fc6f44f097d564f0e21fc5a7 +msgid "|set_y.png|" +msgstr "" + +#: ../../en/refs/m5ui.spinbox.ref:24 9a6454bc19774bd5910ef917a78ab8f9 +msgid "set_y.png" +msgstr "" + +#: ../../en/m5ui/spinbox.rst:185 6cb15a91b90d44dcbad9db06aa259c60 +msgid "Set the size of the spinbox." +msgstr "设置数字输入框的尺寸。" + +#: ../../en/m5ui/spinbox.rst:193 f2da6b0817c74096aa9bf9eb791f37ec +msgid "|set_size.png|" +msgstr "" + +#: ../../en/refs/m5ui.spinbox.ref:16 cfa8f9f02a934c26bd491b7bc950a22e +msgid "set_size.png" +msgstr "" + +#: ../../en/m5ui/spinbox.rst:203 73267d2c59404321b94df54db78a363f +msgid "Set the width of the spinbox." +msgstr "设置数字输入框的宽度。" + +#: ../../en/m5ui/spinbox.rst:210 5566e6b6b1b84cb1b847e8782381f8cb +msgid "|set_width.png|" +msgstr "" + +#: ../../en/refs/m5ui.spinbox.ref:22 f1980ded92fe4b178705d4b11773d1d3 +msgid "set_width.png" +msgstr "" + +#: ../../en/m5ui/spinbox.rst:220 6daa334eb1c74f41a14d3a4a7771936e +msgid "Get the width of the spinbox." +msgstr "获取数字输入框的宽度。" + +#: ../../en/m5ui/spinbox.rst 8f739b14d160416faf4df9585e9951eb +#: m5ui.spinbox.M5Spinbox.get_value m5ui.spinbox.M5Spinbox.set_bg_color +#: m5ui.spinbox.M5Spinbox.set_range m5ui.spinbox.M5Spinbox.set_step +#: m5ui.spinbox.M5Spinbox.set_style_radius m5ui.spinbox.M5Spinbox.set_value of +msgid "Return type" +msgstr "" + +#: ../../en/m5ui/spinbox.rst:227 24030b115de243fcb425dd5006646515 +msgid "|get_width.png|" +msgstr "" + +#: ../../en/refs/m5ui.spinbox.ref:6 476730e6ae2e491598acd262d75daba5 +msgid "get_width.png" +msgstr "" + +#: ../../en/m5ui/spinbox.rst:237 10bba5072558494eb09215aab70cb2ab +msgid "Set the height of the spinbox." +msgstr "设置数字输入框的高度。" + +#: ../../en/m5ui/spinbox.rst:244 17e8a53859534aed987fb1e9a01c80ea +msgid "|set_height.png|" +msgstr "" + +#: ../../en/refs/m5ui.spinbox.ref:12 a2b92e759d10430694b6fec9d92e671a +msgid "set_height.png" +msgstr "" + +#: ../../en/m5ui/spinbox.rst:254 e72771b86367496cbecc81490fc75931 +msgid "Get the height of the spinbox." +msgstr "获取数字输入框的高度。" + +#: ../../en/m5ui/spinbox.rst:261 a27b278f98a9449fbee38928c12ff2ee +msgid "|get_height.png|" +msgstr "" + +#: ../../en/refs/m5ui.spinbox.ref:4 aac42a157805491593bf9331cca1ff35 +msgid "get_height.png" +msgstr "" + +#: ../../en/m5ui/spinbox.rst:271 b9946c5ba2094dadbfce8bd7eee7e519 +msgid "Align the spinbox to another object." +msgstr "将数字输入框对齐到另一个对象。" + +#: ../../en/m5ui/spinbox.rst:273 af4db98763e6479ca32edad975fddd3b +msgid "The object to align to." +msgstr "要对齐的对象。" + +#: ../../en/m5ui/spinbox.rst:274 8eb6b3ef7a054a6cbbf91d9688311e87 +msgid "The alignment type." +msgstr "对齐类型。" + +#: ../../en/m5ui/spinbox.rst:275 f1b4a2efc54648cbbec77b136cb3e45b +msgid "The x-offset from the aligned object." +msgstr "相对于对齐对象的x偏移量。" + +#: ../../en/m5ui/spinbox.rst:276 43e436dbd77642d1b1359cdd084cc4bb +msgid "The y-offset from the aligned object." +msgstr "相对于对齐对象的y偏移量。" + +#: ../../en/m5ui/spinbox.rst:281 45389d26db0646f586d5f1b425ae4bbe +msgid "|align_to.png|" +msgstr "" + +#: ../../en/refs/m5ui.spinbox.ref:1 55d5ebef3c81406a88fc38c16542f560 +msgid "align_to.png" +msgstr "" + +#: f21f9e9917264c4799cd03027261fdd1 m5ui.spinbox.M5Spinbox.add_event_cb:1 of +msgid "Add an event callback to the spinbox." +msgstr "为数字输入框添加事件回调。" + +#: 6468ecda8d8d4a4082f281ce2b7f8faa m5ui.spinbox.M5Spinbox.add_event_cb:3 of +msgid "The callback function to call." +msgstr "要调用的回调函数。" + +#: af4db98763e6479ca32edad975fddd3b m5ui.spinbox.M5Spinbox.add_event_cb:4 of +msgid "The event to listen for." +msgstr "要监听的事件。" + +#: af5783f07b5e41afb03cbb4640f0cfce m5ui.spinbox.M5Spinbox.add_event_cb:5 of +msgid "Optional user data to pass to the callback." +msgstr "传递给回调的可选用户数据。" + +#: 45389d26db0646f586d5f1b425ae4bbe m5ui.spinbox.M5Spinbox.add_event_cb:9 of +msgid "|event.png|" +msgstr "" + +#: ../../en/refs/m5ui.spinbox.ref:3 d061e7b30c9947b4b5b543805538a023 +msgid "event.png" +msgstr "" + +#: 142fdbd8ea534cf5af80e6d0dd8bff10 m5ui.spinbox.M5Spinbox.set_bg_color:1 of +msgid "Set the background color and opacity for a given part of the object." +msgstr "为对象的指定部分设置背景颜色和透明度。" + +#: 2b24f377f9444e3289eaf426bf298838 m5ui.spinbox.M5Spinbox.set_bg_color:3 +#: m5ui.spinbox.M5Spinbox.set_border_color:3 of +msgid "The color to set, can be an integer (hex) or a lv.color object." +msgstr "要设置的颜色,可以是整数(十六进制)或lv.color对象。" + +#: 6cb6ccb385f24776b6977062f9855417 m5ui.spinbox.M5Spinbox.set_bg_color:4 +#: m5ui.spinbox.M5Spinbox.set_border_color:4 of +msgid "The opacity level (0-255)." +msgstr "透明度级别(0-255)。" + +#: 231baf2051394aeba89d78ee7e6b9727 m5ui.spinbox.M5Spinbox.set_bg_color:5 +#: m5ui.spinbox.M5Spinbox.set_border_color:5 of +msgid "The part of the object to apply the style to (e.g., lv.PART.MAIN)." +msgstr "应用样式的对象部分(例如,lv.PART.MAIN)。" + +#: bb9d0ba82fb946bc8ab47dd9525b851e m5ui.spinbox.M5Spinbox.set_bg_color:10 of +msgid "|set_bg_color.png|" +msgstr "" + +#: ../../en/refs/m5ui.spinbox.ref:8 61ec1239e6634a829d1914368ae1f5bc +msgid "set_bg_color.png" +msgstr "" + +#: 01448c74dd474c13ae20b66c196c060c m5ui.spinbox.M5Spinbox.set_border_color:1 +#: of +msgid "Set the border color and opacity for a given part of the object." +msgstr "为对象的指定部分设置边框颜色和透明度。" + +#: 5a48184f927446cd8fa452982809ac73 m5ui.spinbox.M5Spinbox.set_border_color:10 +#: of +msgid "|set_border_color.png|" +msgstr "" + +#: ../../en/refs/m5ui.spinbox.ref:9 374268e93e7e4ae7ba1247566ee453af +msgid "set_border_color.png" +msgstr "" + +#: dddf82abc7f447259b7db5b6b188aef0 +#: m5ui.spinbox.M5Spinbox.set_style_border_width:1 of +msgid "Set the border width of the spinbox." +msgstr "设置数字输入框的边框宽度。" + +#: b924cc2fb60241bcabf64cec734d0dc8 +#: m5ui.spinbox.M5Spinbox.set_style_border_width:3 of +msgid "The border width in pixels." +msgstr "边框宽度(以像素为单位)。" + +#: 78f1993421174ebc84e5d14cbe9d9023 +#: m5ui.spinbox.M5Spinbox.set_style_border_width:4 of +msgid "" +"The part of the spinbox to apply the border width to, e.g., " +"`lv.PART.MAIN`." +msgstr "要应用边框宽度的数字输入框部分,例如 `lv.PART.MAIN`。" + +#: 54c4ab5ba674430990aac94d1d1e7c25 +#: m5ui.spinbox.M5Spinbox.set_style_border_width:9 of +msgid "|set_style_border_width.png|" +msgstr "" + +#: ../../en/refs/m5ui.spinbox.ref:19 0d0a89f04d8047068995bffd177ca5f7 +msgid "set_style_border_width.png" +msgstr "" + +#: a9972278940847c79ced9abd6caffff9 m5ui.spinbox.M5Spinbox.set_style_radius:1 +#: of +msgid "Set the radius of the spinbox's corners." +msgstr "设置数字输入框的圆角半径。" + +#: 8371a42685564013b5638ae3925b72a8 m5ui.spinbox.M5Spinbox.set_style_radius:3 +#: of +msgid "The radius of the corners in pixels." +msgstr "圆角半径(以像素为单位)。" + +#: 5cbf797b2aa14afda02b01459ce636c4 m5ui.spinbox.M5Spinbox.set_style_radius:4 +#: of +msgid "The part of the spinbox to apply the radius to, e.g., `lv.PART.MAIN`." +msgstr "要应用圆角半径的数字输入框部分,例如 `lv.PART.MAIN`。" + +#: a2bf1b3491e3425fa210a11605b41d18 m5ui.spinbox.M5Spinbox.set_style_radius:8 +#: of +msgid "|set_style_radius.png|" +msgstr "" + +#: ../../en/refs/m5ui.spinbox.ref:20 da5cba0345d7418aa07777459258c41a +msgid "set_style_radius.png" +msgstr "" + +#: 635904f9761740a0bcbb52e525b403e2 m5ui.spinbox.M5Spinbox.set_range:1 of +msgid "Set the range of the spinbox." +msgstr "设置数字输入框的取值范围。" + +#: b299bc7e82104a5eaa11c198c8f459f7 m5ui.spinbox.M5Spinbox.set_range:10 of +msgid "|set_range.png|" +msgstr "" + +#: ../../en/refs/m5ui.spinbox.ref:14 4585541403c041f897fd0e5bcddf6ea3 +msgid "set_range.png" +msgstr "" + +#: 07bf55e5c64c48c0b387e34ef120eb4d m5ui.spinbox.M5Spinbox.set_value:1 of +msgid "Set the value of the spinbox." +msgstr "设置数字输入框的值。" + +#: 22bdbc77bcd94d5784fa6bab889b9722 m5ui.spinbox.M5Spinbox.set_value:3 of +msgid "The value to set." +msgstr "要设置的值。" + +#: 8a6b7bcdb25342b99c03e19d9a67bead m5ui.spinbox.M5Spinbox.set_value:8 of +msgid "|set_value.png|" +msgstr "" + +#: ../../en/refs/m5ui.spinbox.ref:21 d436e63b852a484d83f994a810d5d047 +msgid "set_value.png" +msgstr "" + +#: f21f9e9917264c4799cd03027261fdd1 m5ui.spinbox.M5Spinbox.get_value:1 of +msgid "Get the current value of the spinbox." +msgstr "获取数字输入框的当前值。" + +#: af1975ff5c0a4cf4bb5fd2dc41f939c2 m5ui.spinbox.M5Spinbox.get_value:3 of +msgid "The current value." +msgstr "当前值。" + +#: 77c54b4e662b4dc7a81ed9120ef0b323 m5ui.spinbox.M5Spinbox.get_value:8 of +msgid "|get_value.png|" +msgstr "" + +#: ../../en/refs/m5ui.spinbox.ref:5 867b4f02921f45688668dfab8069bb6e +msgid "get_value.png" +msgstr "" + +#: 20203e03b3b64912916f75b68d46cfdc m5ui.spinbox.M5Spinbox.set_step:1 of +msgid "Set the step value for the spinbox." +msgstr "设置数字输入框的步长值。" + +#: d43c2e53f80647338e91a642a0073410 m5ui.spinbox.M5Spinbox.set_step:3 of +msgid "The step value to set." +msgstr "要设置的步长值。" + +#: 6e0fbb2734f84903a930f9a3d96fe51a m5ui.spinbox.M5Spinbox.set_step:8 of +msgid "|set_step.png|" +msgstr "" + +#: ../../en/refs/m5ui.spinbox.ref:18 8f3f6093d4b84a2394a17a454e98d965 +msgid "set_step.png" +msgstr "" diff --git a/examples/m5ui/spinbox/cores3_spinbox_basic_example.m5f2 b/examples/m5ui/spinbox/cores3_spinbox_basic_example.m5f2 new file mode 100644 index 00000000..154288a6 --- /dev/null +++ b/examples/m5ui/spinbox/cores3_spinbox_basic_example.m5f2 @@ -0,0 +1 @@ +{"version":"V2.0","versionNumber":"V2.3.4","type":"cores3","components":[{"name":"page0","type":"lvgl_page","layer":0,"screenId":"builtin","screenName":"","id":"xCp7xAOuwO%6Dsxo","createTime":1756282483195,"backgroundColor":"#ffffff","isLVGL":true,"isSelected":true},{"name":"spinbox0","type":"lvgl_spinbox","layer":1,"screenId":"builtin","screenName":"","id":"zyrn4vf+B7x8yZk8","createTime":1756282489992,"x":60,"y":100,"width":200,"height":40,"minValue":0,"maxValue":100,"currentValue":50,"digitCount":5,"sepPos":2,"font":"lv.font_montserrat_14","pageId":"xCp7xAOuwO%6Dsxo","isLVGL":true,"isSelected":false},{"name":"label0","type":"lvgl_label","layer":2,"screenId":"builtin","screenName":"","id":"ohW#ZHPUfvmTM8!A","createTime":1756287490314,"x":138,"y":166,"color":"#000000","backgroundColor":"#ffffff","bg_opacity":0,"text":"label0","font":"lv.font_montserrat_14","pageId":"xCp7xAOuwO%6Dsxo","isLVGL":true,"isSelected":false}],"resources":[{"hardware":["hardware_button","hardware_pin_button","imu","speaker","touch","als","mic","sdcard"]}],"units":[],"hats":[],"bases":[],"i2cs":[],"blockly":"truepage0spinbox0DEFAULTpalette#ff0000255truespinbox0label0hello M5spinbox0","screen":[{"simulationName":"Built-in","type":"builtin","width":320,"height":240,"scale":0.77,"screenName":"","blockId":"","screenColorType":0,"rotation":1,"id":"builtin","createTime":1756282483194}],"logicWhenNum":0,"customList":[]} \ No newline at end of file diff --git a/examples/m5ui/spinbox/cores3_spinbox_basic_example.py b/examples/m5ui/spinbox/cores3_spinbox_basic_example.py new file mode 100644 index 00000000..a409cd2f --- /dev/null +++ b/examples/m5ui/spinbox/cores3_spinbox_basic_example.py @@ -0,0 +1,84 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import os, sys, io +import M5 +from M5 import * +import m5ui +import lvgl as lv + + +page0 = None +spinbox0 = None +label0 = None + + +def spinbox0_value_changed_event(event_struct): + global page0, spinbox0, label0 + label0.set_text(str(spinbox0.get_value())) + + +def spinbox0_event_handler(event_struct): + global page0, spinbox0, label0 + event = event_struct.code + if event == lv.EVENT.VALUE_CHANGED and True: + spinbox0_value_changed_event(event_struct) + return + + +def setup(): + global page0, spinbox0, label0 + + M5.begin() + Widgets.setRotation(1) + m5ui.init() + page0 = m5ui.M5Page(bg_c=0xFFFFFF) + spinbox0 = m5ui.M5Spinbox( + x=60, + y=100, + w=200, + h=40, + value=50, + min_value=0, + max_value=100, + digit_count=5, + prec=2, + font=lv.font_montserrat_14, + parent=page0, + ) + label0 = m5ui.M5Label( + "label0", + x=138, + y=166, + text_c=0x000000, + bg_c=0xFFFFFF, + bg_opa=0, + font=lv.font_montserrat_14, + parent=page0, + ) + + spinbox0.add_event_cb(spinbox0_event_handler, lv.EVENT.ALL, None) + + page0.screen_load() + spinbox0.set_border_color(0xFF0000, 255, lv.PART.MAIN | lv.STATE.DEFAULT) + + +def loop(): + global page0, spinbox0, label0 + M5.update() + + +if __name__ == "__main__": + try: + setup() + while True: + loop() + except (Exception, KeyboardInterrupt) as e: + try: + m5ui.deinit() + from utility import print_error_msg + + print_error_msg(e) + except ImportError: + print("please update to latest firmware") diff --git a/m5stack/libs/m5ui/__init__.py b/m5stack/libs/m5ui/__init__.py index 3c1dc3cc..aa3dcd05 100644 --- a/m5stack/libs/m5ui/__init__.py +++ b/m5stack/libs/m5ui/__init__.py @@ -22,6 +22,7 @@ "M5Roller": "roller", "M5Scale": "scale", "M5Slider": "slider", + "M5Spinbox": "spinbox", "M5Switch": "switch", "M5TextArea": "textarea", } diff --git a/m5stack/libs/m5ui/manifest.py b/m5stack/libs/m5ui/manifest.py index 4259d5bb..f4760420 100644 --- a/m5stack/libs/m5ui/manifest.py +++ b/m5stack/libs/m5ui/manifest.py @@ -25,6 +25,7 @@ "roller.py", "scale.py", "slider.py", + "spinbox.py", "switch.py", "textarea.py", ), diff --git a/m5stack/libs/m5ui/spinbox.py b/m5stack/libs/m5ui/spinbox.py new file mode 100644 index 00000000..63c99d57 --- /dev/null +++ b/m5stack/libs/m5ui/spinbox.py @@ -0,0 +1,390 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +from .base import M5Base +import lvgl as lv +import warnings +import time + + +class M5Spinbox(lv.obj): + """Create a spinbox widget. + + :param int x: The x position of the spinbox. + :param int y: The y position of the spinbox. + :param int w: The width of the spinbox. + :param int h: The height of the spinbox. + :param int value: The initial value of the spinbox. + :param int min_value: The minimum value of the spinbox. + :param int max_value: The maximum value of the spinbox. + :param int digit_count: The number of digits to display. + :param int prec: The number of decimal places. + :param lv.font_t font: The font to use for the spinbox. + :param lv.obj parent: The parent object of the spinbox. + """ + + def __init__( + self, + x=0, + y=0, + w=100, + h=20, + value=50, + min_value=0, + max_value=100, + digit_count=5, + prec=0, + font=lv.font_montserrat_14, + parent=None, + ): + if parent is None: + parent = lv.screen_active() + super().__init__(parent) + + self.remove_style_all() + self.set_flex_flow(lv.FLEX_FLOW.ROW) + self.set_flex_align(lv.FLEX_ALIGN.SPACE_AROUND, lv.FLEX_ALIGN.CENTER, lv.FLEX_ALIGN.CENTER) + self.set_size(w, h) + self.set_style_pad_gap(5, 0) + self.set_pos(x, y) + self.set_style_text_font(font, lv.PART.MAIN | lv.STATE.DEFAULT) + + self._btn_dec = lv.button(self) + self._spinbox = lv.spinbox(self) + self._btn_inc = lv.button(self) + + # left button + self._btn_dec.set_size(h, h) + self._btn_dec.set_style_bg_image_src(lv.SYMBOL.MINUS, 0) + self._btn_dec.add_event_cb(self._decrement_event_cb, lv.EVENT.CLICKED, None) + self._btn_dec.add_event_cb(self._decrement_event_cb, lv.EVENT.SHORT_CLICKED, None) + self._btn_dec.add_event_cb(self._decrement_event_cb, lv.EVENT.LONG_PRESSED_REPEAT, None) + + # spinbox + self._spinbox.set_height(h) + self._spinbox.set_flex_grow(1) + self._digit_count = digit_count + self._sep_pos = digit_count - prec + self._min_value = min_value + self._max_value = max_value + self._check_value(value, min_value, max_value, digit_count, prec) + self.set_range(min_value, max_value) + self.set_digit_format(digit_count, self._sep_pos) + self.set_value(value) + + # right button + self._btn_inc.set_size(h, h) + self._btn_inc.set_style_bg_image_src(lv.SYMBOL.PLUS, 0) + self._btn_inc.add_event_cb(self._increment_event_cb, lv.EVENT.CLICKED, None) + self._btn_inc.add_event_cb(self._increment_event_cb, lv.EVENT.SHORT_CLICKED, None) + self._btn_inc.add_event_cb(self._increment_event_cb, lv.EVENT.LONG_PRESSED_REPEAT, None) + + def _check_value(self, value, min_value, max_value, digit_count, prec): + if value < min_value or value > max_value: + warnings.warn(f"Value must be between {min_value} and {max_value}.") + + if prec < 0 or prec > digit_count: + warnings.warn("prec must be between 0 and digit_count.") + + new_max = 10 ** (digit_count - prec) - 1 + new_min = -new_max if min_value < 0 else 0 + if max_value > new_max or min_value < new_min: + warnings.warn( + f"max_value must be less than {new_max}, min_value must be greater than {new_min}." + ) + + def add_event_cb(self, event_cb, filters, user_data): + """Add an event callback to the spinbox. + + :param function handler: The callback function to call. + :param int event: The event to listen for. + :param Any user_data: Optional user data to pass to the callback. + + UiFlow2 Code Block: + + |event.png| + + MicroPython Code Block: + + .. code-block:: python + + def spinbox0_value_changed_event(event_struct): + global page0, spinbox0 + print("value changed:", spinbox0.get_value()) + + def spinbox0_event_handler(event_struct): + global page0, spinbox0 + event = event_struct.code + if event == lv.EVENT.VALUE_CHANGED and True: + spinbox0_value_changed_event(event_struct) + return + + spinbox_0.add_event_cb(spinbox0_event_handler, lv.EVENT.ALL, None) + """ + self._spinbox.add_event_cb(event_cb, filters, user_data) + + def set_bg_color(self, color: int, opa: int, part: int) -> None: + """Set the background color and opacity for a given part of the object. + + :param int color: The color to set, can be an integer (hex) or a lv.color object. + :param int opa: The opacity level (0-255). + :param int part: The part of the object to apply the style to (e.g., lv.PART.MAIN). + :return: None + + UiFlow2 Code Block: + + |set_bg_color.png| + + MicroPython Code Block: + + .. code-block:: python + + spinbox0.set_bg_color(0xFF0000, 255, lv.PART.MAIN | lv.STATE.DEFAULT) + """ + if isinstance(color, int): + color = lv.color_hex(color) + + self._spinbox.set_style_bg_color(color, part) + time.sleep(0.01) + self._spinbox.set_style_bg_opa(opa, part) + + def set_border_color(self, color: int, opa: int, part: int): + """Set the border color and opacity for a given part of the object. + + :param int color: The color to set, can be an integer (hex) or a lv.color object. + :param int opa: The opacity level (0-255). + :param int part: The part of the object to apply the style to (e.g., lv.PART.MAIN). + :return: None + + UiFlow2 Code Block: + + |set_border_color.png| + + MicroPython Code Block: + + .. code-block:: python + + spinbox0.set_border_color(0xFF0000, 255, lv.PART.MAIN | lv.STATE.DEFAULT) + """ + if isinstance(color, int): + color = lv.color_hex(color) + + self._spinbox.set_style_border_color(color, part) + time.sleep(0.01) + self._spinbox.set_style_border_opa(opa, part) + + def set_style_border_width(self, w, part): + """Set the border width of the spinbox. + + :param int w: The border width in pixels. + :param int part: The part of the spinbox to apply the border width to, e.g., `lv.PART.MAIN`. + :return: None + + UiFlow2 Code Block: + + |set_style_border_width.png| + + MicroPython Code Block: + + .. code-block:: python + + spinbox0.set_style_border_width(10, lv.PART.MAIN | lv.STATE.DEFAULT) + """ + self._spinbox.set_style_border_width(w, part) + + def set_style_radius(self, radius: int, part: int) -> None: + """Set the radius of the spinbox's corners. + + :param radius: The radius of the corners in pixels. + :param part: The part of the spinbox to apply the radius to, e.g., `lv.PART.MAIN`. + + UiFlow2 Code Block: + + |set_style_radius.png| + + MicroPython Code Block: + + .. code-block:: python + + dropdown_0.set_style_radius(10, lv.PART.MAIN | lv.STATE.DEFAULT) + """ + if radius < 0: + warnings.warn("Radius must be a non-negative integer.") + return + self._spinbox.set_style_radius(radius, part) + + def _increment_event_cb(self, event_struct): + event = event_struct.code + if event in (lv.EVENT.CLICKED, lv.EVENT.SHORT_CLICKED, lv.EVENT.LONG_PRESSED_REPEAT): + self._spinbox.increment() + + def _decrement_event_cb(self, event_struct): + event = event_struct.code + if event in (lv.EVENT.CLICKED, lv.EVENT.SHORT_CLICKED, lv.EVENT.LONG_PRESSED_REPEAT): + self._spinbox.decrement() + + def set_digit_format(self, digit_count: int, sep_pos: int) -> None: + """Set the digit format of the spinbox. + + :param digit_count: The total number of digits in the float representation. + :type digit_count: int + :param sep_pos: The position of the separator. + :type sep_pos: int + """ + if digit_count < 0 or sep_pos < 0 or sep_pos > digit_count: + raise ValueError( + "digit_count and sep_pos must be non-negative and sep_pos must be less than or equal to digit_count." + ) + + self._digit_count = digit_count + self._sep_pos = sep_pos + self._spinbox.set_digit_format(digit_count, sep_pos) + + def set_range(self, min_value: float | int, max_value: float | int) -> None: + """Set the range of the spinbox. + + :param min_value: The minimum value of the spinbox. + :type min_value: float | int + :param max_value: The maximum value of the spinbox. + :type max_value: float | int + + UiFlow2 Code Block: + + |set_range.png| + + MicroPython Code Block: + + .. code-block:: python + + spinbox0.set_range(0, 100) + """ + if min_value >= max_value: + raise ValueError("min_value must be less than max_value.") + + self._min_value = min_value + self._max_value = max_value + self._spinbox.set_range( + self.value2raw(min_value, self._digit_count, self._sep_pos), + self.value2raw(max_value, self._digit_count, self._sep_pos), + ) + + def set_value(self, value: float | int) -> None: + """Set the value of the spinbox. + + :param value: The value to set. + :type value: float | int + + UiFlow2 Code Block: + + |set_value.png| + + MicroPython Code Block: + + .. code-block:: python + + spinbox0.set_value(50) + """ + if value < self._min_value or value > self._max_value: + raise ValueError(f"Value must be between {self._min_value} and {self._max_value}.") + + self._spinbox.set_value(self.value2raw(value, self._digit_count, self._sep_pos)) + + def get_value(self) -> float | int: + """Get the current value of the spinbox. + + :return: The current value. + :rtype: float | int + + UiFlow2 Code Block: + + |get_value.png| + + MicroPython Code Block: + + .. code-block:: python + + spinbox0.get_value() + """ + return self.raw2value(self._spinbox.get_value(), self._digit_count, self._sep_pos) + + def set_step(self, step: float | int) -> None: + """Set the step value for the spinbox. + + :param step: The step value to set. + :type step: float | int + + UiFlow2 Code Block: + + |set_step.png| + + MicroPython Code Block: + + .. code-block:: python + + spinbox0.set_step(1) + spinbox0.set_step(0.1) + """ + self._spinbox.set_step(self.value2raw(step, self._digit_count, self._sep_pos)) + + @staticmethod + def value2raw(value: float | int, digit_count: int, sep_pos: int) -> int: + """Convert a float to an integer by removing the decimal point. + + :param float value: The float value to convert. + :return: The converted integer value. + :rtype: int + """ + if sep_pos < 0 or sep_pos >= digit_count: + raise ValueError("sep_pos must be between 0 and digit_count.") + + dec_pos = digit_count - sep_pos + return int(value * (10**dec_pos)) + + @staticmethod + def raw2value(raw: int, digit_count: int, sep_pos: int) -> float | int: + """Convert an integer to a float with a specified decimal point position. + + :param int value: The integer value to convert. + :param int digit_count: The total number of digits in the float representation. + :param int sep_pos: The position of the decimal point. + :return: The converted float value. + :rtype: float + """ + if sep_pos < 0 or sep_pos >= digit_count: + raise ValueError("sep_pos must be between 0 and digit_count.") + + if sep_pos == 0: + return raw + + dot_pos = sep_pos + s = str(raw) + if len(s) < digit_count: + if s.find("-") == 0: + dot_pos += 1 + s = "-" + "0" * (digit_count - len(s) + 1) + s[1:] + else: + s = "0" * (digit_count - len(s)) + s + return float(".".join([s[:dot_pos], s[dot_pos:]])) + + def __getattr__(self, name): + # First check if it's a method from M5Base + if hasattr(M5Base, name): + method = getattr(M5Base, name) + bound_method = lambda *args, **kwargs: method(self, *args, **kwargs) + setattr(self, name, bound_method) + return bound_method + + # Then check if it's a method from the spinbox widget + if hasattr(self._spinbox, name): + attr = getattr(self._spinbox, name) + if callable(attr): + # Create a bound method that delegates to the spinbox + bound_method = lambda *args, **kwargs: attr(*args, **kwargs) + setattr(self, name, bound_method) + return bound_method + else: + # For non-callable attributes, return them directly + return attr + + raise AttributeError(f"'{self.__class__.__name__}' object has no attribute '{name}'") diff --git a/tests/m5ui/test_spinbox.py b/tests/m5ui/test_spinbox.py new file mode 100644 index 00000000..0ece76f5 --- /dev/null +++ b/tests/m5ui/test_spinbox.py @@ -0,0 +1,74 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import lvgl as lv +import sys + +sys.path.append("../../m5stack/libs") +import m5ui +import unittest + + +class Test(unittest.TestCase): + def __init__(self) -> None: + super().__init__() + m5ui.init() + page0 = m5ui.M5Page() + self.spinbox1 = m5ui.M5Spinbox( + x=0, + y=0, + w=200, + h=40, + value=50, + min_value=0, + max_value=100, + digit_count=5, + prec=2, # 小数点位数 000.00 + parent=page0, + ) + page0.screen_load() + + def test_set_get_value(self): + self.spinbox1.set_value(75) + self.assertEqual(self.spinbox1.get_value(), 75) + + def test_set_range(self): + self.spinbox1.set_value(100) + self.spinbox1.set_range(10, 90) + self.assertEqual(self.spinbox1.get_value(), 90) + + def test_set_rollover(self): + self.spinbox1.set_rollover(True) + self.assertTrue(self.spinbox1.get_rollover()) + + def test_step(self): + self.spinbox1.set_value(50) + self.spinbox1.set_range(0, 100) + self.spinbox1.set_step(10) + self.spinbox1.increment() + self.assertEqual(self.spinbox1.get_value(), 60) + self.spinbox1.decrement() + self.assertEqual(self.spinbox1.get_value(), 50) + + self.spinbox1.set_step(1) + self.spinbox1.increment() + self.assertEqual(self.spinbox1.get_value(), 51) + self.spinbox1.decrement() + self.assertEqual(self.spinbox1.get_value(), 50) + + self.spinbox1.set_step(0.1) + self.spinbox1.increment() + self.assertEqual(self.spinbox1.get_value(), 50.1) + self.spinbox1.decrement() + self.assertEqual(self.spinbox1.get_value(), 50) + + self.spinbox1.set_step(0.01) + self.spinbox1.increment() + self.assertEqual(self.spinbox1.get_value(), 50.01) + self.spinbox1.decrement() + self.assertEqual(self.spinbox1.get_value(), 50) + + +if __name__ == "__main__": + unittest.main() From af5803f333440b8650c59370c9f6b277592fca99 Mon Sep 17 00:00:00 2001 From: lbuque <1102390310@qq.com> Date: Tue, 26 Aug 2025 10:53:28 +0800 Subject: [PATCH 223/322] docs: Fix some documentation link errors. Signed-off-by: lbuque <1102390310@qq.com> --- docs/en/refs/m5ui.canvas.ref | 9 +++- docs/en/refs/m5ui.textarea.ref | 13 ++++- .../canvas/cores3_canvas_basic_example.m5f2 | 2 +- .../canvas/cores3_canvas_basic_example.py | 53 ++++++++----------- 4 files changed, 40 insertions(+), 37 deletions(-) diff --git a/docs/en/refs/m5ui.canvas.ref b/docs/en/refs/m5ui.canvas.ref index 1af7b86f..caf90729 100644 --- a/docs/en/refs/m5ui.canvas.ref +++ b/docs/en/refs/m5ui.canvas.ref @@ -1,8 +1,13 @@ .. |cores3_canvas_basic_example.m5f2| raw:: html - cores3_canvas_basic_example.m5f2 + + cores3_canvas_basic_example.m5f2 + -.. |cores3_canvas_basic_example.png| image:: https://static-cdn.m5stack.com/image/m5-docs/docs-en/tutorial/m5ui/canvas/cores3_canvas_basic_example.png +.. |cores3_canvas_basic_example.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/canvas/cores3_canvas_basic_example.png .. |align_to.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/canvas/align_to.png .. |draw_arc.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/canvas/draw_arc.png diff --git a/docs/en/refs/m5ui.textarea.ref b/docs/en/refs/m5ui.textarea.ref index 663c4a2e..4ab19cba 100644 --- a/docs/en/refs/m5ui.textarea.ref +++ b/docs/en/refs/m5ui.textarea.ref @@ -1,6 +1,4 @@ -.. |cores3_textarea_basic_example.m5f2| replace:: :download:`cores3_textarea_basic_example.m5f2 <../../../examples/m5ui/textarea/cores3_textarea_basic_example.m5f2>` -.. |cores3_textarea_basic_example.png| image:: https://static-cdn.m5stack.com/resource/docs/products/core/CoreS3/software/uiflow2/api/m5ui/textarea/cores3_textarea_basic_example.png .. |add_text.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/textarea/add_text.png .. |align_to.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/textarea/align_to.png .. |clear_text.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/textarea/clear_text.png @@ -34,3 +32,14 @@ .. |set_y.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/textarea/set_y.png .. |toggle_flag.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/textarea/toggle_flag.png .. |toggle_state.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/textarea/toggle_state.png + +.. |cores3_textarea_basic_example.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/textarea/cores3_textarea_basic_example.png + +.. |cores3_textarea_basic_example.m5f2| raw:: html + + + cores3_textarea_basic_example.m5f2 + diff --git a/examples/m5ui/canvas/cores3_canvas_basic_example.m5f2 b/examples/m5ui/canvas/cores3_canvas_basic_example.m5f2 index a332e684..8cbc9675 100644 --- a/examples/m5ui/canvas/cores3_canvas_basic_example.m5f2 +++ b/examples/m5ui/canvas/cores3_canvas_basic_example.m5f2 @@ -1 +1 @@ -{"version":"V2.0","versionNumber":"V2.3.2","type":"cores3","components":[{"name":"page0","type":"lvgl_page","layer":0,"screenId":"builtin","screenName":"","id":"zw`B`!SVA8Sc-X!_","createTime":1753609197574,"backgroundColor":"#ffffff","isLVGL":true,"isSelected":true},{"name":"canvas0","type":"lvgl_canvas","layer":1,"screenId":"builtin","screenName":"","id":"ycsdE1FLDbXlHQZt","createTime":1753609227655,"x":83,"y":53,"width":150,"height":70,"backgroundColor":"#c9c9c9","pageId":"zw`B`!SVA8Sc-X!_","isLVGL":true,"isSelected":false}],"resources":[{"hardware":["hardware_button","hardware_pin_button","imu","speaker","touch","als","mic","sdcard"]}],"units":[],"hats":[],"bases":[],"i2cs":[],"blockly":"truepage0canvas00042palette#6600cc255090canvas0FULL0020200palette#ff0000255palette#ff00002550palette#6600cc25500palette#6600cc2550000true","screen":[{"simulationName":"Built-in","type":"builtin","width":320,"height":240,"scale":0.77,"screenName":"","blockId":"","screenColorType":0,"rotation":1,"id":"builtin","createTime":1753609197573}],"logicWhenNum":0,"customList":[]} \ No newline at end of file +{"version":"V2.0","versionNumber":"V2.3.4","type":"cores3","components":[{"name":"page0","type":"lvgl_page","layer":0,"screenId":"builtin","screenName":"","id":"zw`B`!SVA8Sc-X!_","createTime":1753609197574,"backgroundColor":"#ffffff","isLVGL":true,"isSelected":true},{"name":"canvas0","type":"lvgl_canvas","layer":1,"screenId":"builtin","screenName":"","id":"ycsdE1FLDbXlHQZt","createTime":1753609227655,"x":120,"y":100,"width":80,"height":40,"backgroundColor":"#4994ec","pageId":"zw`B`!SVA8Sc-X!_","isLVGL":true,"isSelected":false}],"resources":[{"hardware":["hardware_button","hardware_pin_button","imu","speaker","touch","als","mic","sdcard"]}],"units":[],"hats":[],"bases":[],"i2cs":[],"blockly":"yxtruepage0palette#ffcccc255page0y10201x5751canvas00x0yhex0x4994ec50y20301x5751canvas00x0yhex0x4994ec20y30401x5751canvas00x0yhex0x4994ec0true","screen":[{"simulationName":"Built-in","type":"builtin","width":320,"height":240,"scale":0.77,"screenName":"","blockId":"","screenColorType":0,"rotation":1,"id":"builtin","createTime":1753609197573}],"logicWhenNum":0,"customList":[]} \ No newline at end of file diff --git a/examples/m5ui/canvas/cores3_canvas_basic_example.py b/examples/m5ui/canvas/cores3_canvas_basic_example.py index 8f9e6d0c..b132f4d1 100644 --- a/examples/m5ui/canvas/cores3_canvas_basic_example.py +++ b/examples/m5ui/canvas/cores3_canvas_basic_example.py @@ -13,54 +13,43 @@ canvas0 = None +y = None +x = None + + def setup(): - global page0, canvas0 + global page0, canvas0, x, y M5.begin() Widgets.setRotation(1) m5ui.init() page0 = m5ui.M5Page(bg_c=0xFFFFFF) canvas0 = m5ui.M5Canvas( - x=83, - y=53, - w=150, - h=70, + x=120, + y=100, + w=80, + h=40, color_format=lv.COLOR_FORMAT.ARGB8888, - bg_c=0xC9C9C9, + bg_c=0x4994EC, bg_opa=255, parent=page0, ) + page0.set_bg_color(0xFFCCCC, 255, 0) page0.screen_load() - canvas0.draw_arc(0, 0, 10, color=0x6600CC, opa=255, width=2, start_angle=0, end_angle=90) - canvas0.draw_rect( - 0, - 0, - 20, - 20, - 0, - bg_c=0xFF0000, - bg_opa=255, - border_c=0xFF0000, - border_opa=255, - border_w=0, - border_side=lv.BORDER_SIDE.FULL, - outline_c=0x6600CC, - outline_opa=255, - outline_w=0, - outline_pad=0, - shadow_c=0x6600CC, - shadow_opa=255, - shadow_w=0, - shadow_offset_x=0, - shadow_offset_y=0, - shadow_spread=0, - ) - canvas0.draw_line(0, 0, 50, 50, color=0x6600CC, opa=255, width=1) + for y in range(10, 21): + for x in range(5, 76): + canvas0.set_px(x, y, 0x4994EC, 50) + for y in range(20, 31): + for x in range(5, 76): + canvas0.set_px(x, y, 0x4994EC, 20) + for y in range(30, 41): + for x in range(5, 76): + canvas0.set_px(x, y, 0x4994EC, 0) def loop(): - global page0, canvas0 + global page0, canvas0, x, y M5.update() From 96df214edc04b7d15f8358b2e445dba8d76c18a0 Mon Sep 17 00:00:00 2001 From: lbuque <1102390310@qq.com> Date: Fri, 5 Sep 2025 14:23:59 +0800 Subject: [PATCH 224/322] docs: Add product images. Signed-off-by: lbuque <1102390310@qq.com> --- docs/en/iot-devices/switchc6.rst | 2 + docs/en/refs/iot-devices.switchc6.ref | 5 + .../zh_CN/LC_MESSAGES/iot-devices/switchc6.po | 143 ++++++++---------- 3 files changed, 73 insertions(+), 77 deletions(-) diff --git a/docs/en/iot-devices/switchc6.rst b/docs/en/iot-devices/switchc6.rst index 61af9cef..928ddb00 100644 --- a/docs/en/iot-devices/switchc6.rst +++ b/docs/en/iot-devices/switchc6.rst @@ -8,6 +8,8 @@ SwitchC6 The SwitchC6 is a device that can be controlled using the M5Stack platform. This module provides functions to interact with the SwitchC6 device. +|SwitchC6| + UiFlow2 Example --------------- diff --git a/docs/en/refs/iot-devices.switchc6.ref b/docs/en/refs/iot-devices.switchc6.ref index 6dcaf30e..51da11c2 100644 --- a/docs/en/refs/iot-devices.switchc6.ref +++ b/docs/en/refs/iot-devices.switchc6.ref @@ -1,4 +1,9 @@ +.. |SwitchC6| image:: https://m5stack-doc.oss-cn-shenzhen.aliyuncs.com/1159/k140_switchc6_01.webp + :target: https://docs.m5stack.com/en/iot/SwitchC6 + :height: 200px + :width: 200px + .. |event.png| image:: https://static-cdn.m5stack.com/mpy_docs/iot_devices/switchc6/event.png .. |get_capacitor_voltage.png| image:: https://static-cdn.m5stack.com/mpy_docs/iot_devices/switchc6/get_capacitor_voltage.png .. |get_firmware_version.png| image:: https://static-cdn.m5stack.com/mpy_docs/iot_devices/switchc6/get_firmware_version.png diff --git a/docs/locales/zh_CN/LC_MESSAGES/iot-devices/switchc6.po b/docs/locales/zh_CN/LC_MESSAGES/iot-devices/switchc6.po index caf1f98d..c07a26b5 100644 --- a/docs/locales/zh_CN/LC_MESSAGES/iot-devices/switchc6.po +++ b/docs/locales/zh_CN/LC_MESSAGES/iot-devices/switchc6.po @@ -9,7 +9,7 @@ msgid "" msgstr "" "Project-Id-Version: UIFlow2 Programming Guide \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-08-20 18:56+0800\n" +"POT-Creation-Date: 2025-09-05 14:20+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language: zh_CN\n" @@ -20,41 +20,43 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.17.0\n" -#: ../../en/iot-devices/switchc6.rst:2 838a6af22b9443a9ba4d67e56109a5c9 +#: ../../en/iot-devices/switchc6.rst:2 ../../en/refs/iot-devices.switchc6.ref +#: a4e5f6a7107f4b9cb9ab879227854c2d de4b64e571654a28921bebe6ad4f6d91 msgid "SwitchC6" msgstr "" -#: ../../en/iot-devices/switchc6.rst:9 acf935bd3a014735ba840d431c9e683b +#: ../../en/iot-devices/switchc6.rst:9 58e45674e0c84d8e8ab6623998f585c6 msgid "" "The SwitchC6 is a device that can be controlled using the M5Stack " "platform. This module provides functions to interact with the SwitchC6 " "device." msgstr "SwitchC6是一个可以通过M5Stack平台控制的设备。此模块提供与SwitchC6设备交互的功能。" -#: ../../en/iot-devices/switchc6.rst:12 5049fdb2312e4491999bc16aefdf94e1 +#: ../../en/iot-devices/switchc6.rst:11 d5f4aa985b27453a90154a455e607020 +msgid "|SwitchC6|" +msgstr "" + +#: ../../en/iot-devices/switchc6.rst:14 b177bac51c7642b7af00814f2b38ad86 msgid "UiFlow2 Example" msgstr "UIFLOW2 应用示例" -#: ../../en/iot-devices/switchc6.rst:15 ../../en/iot-devices/switchc6.rst:34 -#: 70f66a41bd0c43e5a96bfeda3200d266 a529d886fd6a4aa68086ea8444f1a2ef +#: ../../en/iot-devices/switchc6.rst:17 ../../en/iot-devices/switchc6.rst:36 +#: 45e7d1a1b0874ea3979314773b981a1c msgid "SwitchC6 Control" msgstr "SwitchC6控制" -#: ../../en/iot-devices/switchc6.rst:17 cecd47fc737e42bfa94911463d162864 +#: ../../en/iot-devices/switchc6.rst:19 43673ef5c09e479a8570eaf02164f6cb msgid "Open the |cores3_switchc6_example.m5f2| project in UiFlow2." msgstr "在UiFlow2中打开|cores3_switchc6_example.m5f2|项目。" -#: ../../en/iot-devices/switchc6.rst:19 efb9136415d84f62bfd9e4bf7e4e31ee +#: ../../en/iot-devices/switchc6.rst:21 2d22869846a04bef81db7b272a632de3 msgid "" "This example demonstrates how to control the SwitchC6 device using " "UiFlow2." msgstr "此示例演示如何使用UiFlow2控制SwitchC6设备。" -#: ../../en/iot-devices/switchc6.rst:21 15f4e3edc6c34885991dfd1bb1556e8a -#: 4582b8005c724f0e88286956f95cb81f 4e8cd899b3d9462daed90442a5107c54 -#: 535232eb40ff4d448860a8d0e2332453 83c42565ffa444c2beac8c72c7035547 -#: cd55f9caa68c45669cd57adf5e8a176a e73c41661bee491dab1a5b9d59a54d38 -#: edf97b2c7e1f4d2ba6412cae064ffbe9 iot_devices.switchc6.SwitchC6Controller:8 +#: ../../en/iot-devices/switchc6.rst:23 b28bd55894c74142a243954c5089dda4 +#: iot_devices.switchc6.SwitchC6Controller:8 #: iot_devices.switchc6.SwitchC6Controller.get_capacitor_voltage:8 #: iot_devices.switchc6.SwitchC6Controller.get_firmware_version:9 #: iot_devices.switchc6.SwitchC6Controller.get_switch_status:8 @@ -64,40 +66,36 @@ msgstr "此示例演示如何使用UiFlow2控制SwitchC6设备。" msgid "UiFlow2 Code Block:" msgstr "UiFlow2代码块:" -#: ../../en/iot-devices/switchc6.rst:23 d8568018ecd840969f318d175e08cbd0 +#: ../../en/iot-devices/switchc6.rst:25 28e009f083fc4e1797fae639278a625e msgid "|cores3_switchc6_example.png|" msgstr "" -#: ../../en/refs/iot-devices.switchc6.ref:10 65bacd84bf3a4dbb9a70888c3c4edbd4 -#, fuzzy +#: ../../en/refs/iot-devices.switchc6.ref:15 7976e5b48f9c424b85605a0b03c2d30c msgid "cores3_switchc6_example.png" -msgstr "在UiFlow2中打开|cores3_switchc6_example.m5f2|项目。" +msgstr "" -#: ../../en/iot-devices/switchc6.rst:25 ../../en/iot-devices/switchc6.rst:44 -#: 2bf8794fdc514b72bb083503dc2b5f87 9bd3937e1bbc45d2b8e2ac2139bc52f5 +#: ../../en/iot-devices/switchc6.rst:27 ../../en/iot-devices/switchc6.rst:46 +#: 0e4aa9461f964ab5af518bda38843033 msgid "Example output:" msgstr "示例输出:" -#: ../../en/iot-devices/switchc6.rst:27 ../../en/iot-devices/switchc6.rst:46 -#: 36223b1294904f9eac08695d6eb771c3 a91efc6f59154a2b9a99331618f25144 +#: ../../en/iot-devices/switchc6.rst:29 ../../en/iot-devices/switchc6.rst:48 +#: 2a2c29143dcb48beb17fa2faad510538 msgid "None" msgstr "" -#: ../../en/iot-devices/switchc6.rst:31 70f773789b8f4f30bc64e8d9736a397f +#: ../../en/iot-devices/switchc6.rst:33 c25f620242064e4180047838de5d5a2f msgid "MicroPython Example" msgstr "MicroPython 应用示例" -#: ../../en/iot-devices/switchc6.rst:36 3d6a2318ddfd4fe1850b9990619897ea +#: ../../en/iot-devices/switchc6.rst:38 27593e2c5aba4dce8e29bde617c04842 msgid "" "This example demonstrates how to control the SwitchC6 device using " "MicroPython." msgstr "此示例演示如何使用MicroPython控制SwitchC6设备。" -#: ../../en/iot-devices/switchc6.rst:38 11876878cb8d4b179519b6d65a3aae54 -#: 280d70fc51a04a1290d6220c41e2fe84 7e685d2611c44284ad0553f564a96824 -#: 8e527d9c4d774387a90f21f69581cfef bf62cdb336e6431e8960d333715d2df3 -#: c833580f97ee45f7bab96cc39618c897 d7a0d4bfec45414199b84824fe943c8b -#: f27c4e8b00914fa1bb304202fc5f3ff2 iot_devices.switchc6.SwitchC6Controller:12 +#: ../../en/iot-devices/switchc6.rst:40 d924c11f46b743b19dd55ece0e4bb768 +#: iot_devices.switchc6.SwitchC6Controller:12 #: iot_devices.switchc6.SwitchC6Controller.get_capacitor_voltage:12 #: iot_devices.switchc6.SwitchC6Controller.get_firmware_version:13 #: iot_devices.switchc6.SwitchC6Controller.get_switch_status:12 @@ -107,28 +105,25 @@ msgstr "此示例演示如何使用MicroPython控制SwitchC6设备。" msgid "MicroPython Code Block:" msgstr "MicroPython 代码块:" -#: ../../en/iot-devices/switchc6.rst:50 f2f1bbe1717d47b59c16ec706b71db70 +#: ../../en/iot-devices/switchc6.rst:52 966d47a07086423e8ffc0112ae35e3a8 msgid "**API**" msgstr "API参考" -#: ../../en/iot-devices/switchc6.rst:53 c37f8997bf58478a9e0e34812384e2d4 +#: ../../en/iot-devices/switchc6.rst:55 9f37e05d51c24740b4dd364a0f49d974 msgid "SwitchC6Controller" msgstr "" -#: 433fe9fa0c94478cae58d7d72f5092d1 iot_devices.switchc6.SwitchC6Controller:1 +#: 87cbefa4ea854dd8b0c89a88922777ec iot_devices.switchc6.SwitchC6Controller:1 #: of msgid "Bases: :py:class:`object`" msgstr "" -#: bd5a1ba6e13445c194d8d8d2aa585be1 iot_devices.switchc6.SwitchC6Controller:1 +#: 3d744caf307e43f491a5d7c5e8b63c3a iot_devices.switchc6.SwitchC6Controller:1 #: of msgid "Create a SwitchC6Controller instance to control M5Stack SwitchC6 devices." msgstr "创建SwitchC6Controller实例来控制M5Stack SwitchC6设备。" -#: ../../en/iot-devices/switchc6.rst 03cd2ace6ec84516acf72e0617e0a535 -#: 50024437995c4092b702dcf6ef4c2bf1 53e63db4f2844bed9432851f1296e62d -#: 5cb95c5399094f2ab7d2d7da0d802710 6d922674ddc447d48ec529d0d5f1c606 -#: b276d1145ff1477a8468596a82669593 c4143286a92f4414ac4912479916f4fe +#: ../../en/iot-devices/switchc6.rst fd1a1529fd74401a99ccf3b23262b51e #: iot_devices.switchc6.SwitchC6Controller.get_capacitor_voltage #: iot_devices.switchc6.SwitchC6Controller.get_firmware_version #: iot_devices.switchc6.SwitchC6Controller.get_switch_status @@ -138,49 +133,47 @@ msgstr "创建SwitchC6Controller实例来控制M5Stack SwitchC6设备。" msgid "Parameters" msgstr "" -#: fcecbb3e1fd5445b9b3e4e79243db60b iot_devices.switchc6.SwitchC6Controller:3 +#: 14578ac7ee824144b51ce97c0fcdfab1 iot_devices.switchc6.SwitchC6Controller:3 #: of msgid "List of target MAC addresses in \"XXXX-XXXX-XXXX\" format." msgstr "\"XXXX-XXXX-XXXX\"格式的目标MAC地址列表。" -#: a98a595feb1f4c17bbf1635a1e02d54e iot_devices.switchc6.SwitchC6Controller:4 +#: 17f4af6b1df24425937382567381114c iot_devices.switchc6.SwitchC6Controller:4 #: of msgid "" "WiFi channel to use for communication (default is 0, which uses the " "current channel)." msgstr "用于通信的WiFi信道(默认为0,使用当前信道)。" -#: 7e06dfcc693d4793b51624fcd8169763 iot_devices.switchc6.SwitchC6Controller:5 +#: 6a25776254c64646887015cdd17cdedb iot_devices.switchc6.SwitchC6Controller:5 #: of msgid "If True, print debug information (default is False)." msgstr "如果为True,打印调试信息(默认为False)。" -#: ../../en/iot-devices/switchc6.rst 3d9fed57ae784cefa2f834d5ae25c82c +#: ../../en/iot-devices/switchc6.rst 2aa0366d0bfe4389944bf88bcc43b3e8 msgid "Raises" msgstr "" -#: 63ab8843d443460b8059be02175acc2d iot_devices.switchc6.SwitchC6Controller:6 +#: f14e6528e0624b598bce28f135e77db4 iot_devices.switchc6.SwitchC6Controller:6 #: of msgid "If any MAC address in target_mac is not in the \"XXXX-XXXX-XXXX\" format." msgstr "如果target_mac中的任何MAC地址不符合\"XXXX-XXXX-XXXX\"格式。" -#: 3954e4340a6c4b4ca58cc63c1859ee0f iot_devices.switchc6.SwitchC6Controller:10 +#: e3cd3e47bf6149e69ace558bae4ce55c iot_devices.switchc6.SwitchC6Controller:10 #: of msgid "|init.png|" msgstr "" -#: ../../en/refs/iot-devices.switchc6.ref:6 f23ca791ab074955a9d75c472ab26e9a +#: ../../en/refs/iot-devices.switchc6.ref:11 9ccd7aedd1bd41ff910d92029d718338 msgid "init.png" msgstr "" -#: 6c20d7924e3f470082d1c8fcc1b83000 +#: 107e690f22424f6697abadd312e13ee3 #: iot_devices.switchc6.SwitchC6Controller.set_switch:1 of msgid "Set the switch state of the target device." msgstr "设置目标设备的开关状态。" -#: 481b09f5ad274ae7972167431385184c 5f8b61faae2e47de89a23dbc6f6eff03 -#: af24e3ae15e44f6e93dc27b7609390a9 f2ceef52067e40b09b9e52885a5710c4 -#: f84f17d00c89452a852eb128f024f95b +#: b9fe90ef3777495781842f7d3e02a9e9 #: iot_devices.switchc6.SwitchC6Controller.get_capacitor_voltage:3 #: iot_devices.switchc6.SwitchC6Controller.get_firmware_version:3 #: iot_devices.switchc6.SwitchC6Controller.get_switch_status:3 @@ -189,14 +182,12 @@ msgstr "设置目标设备的开关状态。" msgid "Target MAC address in \"XXXX-XXXX-XXXX\" format." msgstr "\"XXXX-XXXX-XXXX\"格式的目标MAC地址。" -#: ebae2096383d41c1948922fb00cb141d +#: 1babfed06a1842ea881457b44e024cc7 #: iot_devices.switchc6.SwitchC6Controller.set_switch:4 of msgid "True to turn on, False to turn off." msgstr "True表示开启,False表示关闭。" -#: 078b836848c4435f91126ecbe56d7daf 3410a6e82a454e38af3de750556f5837 -#: 4a935b945891488fabb334269c78df2e 7b8a98f3038644b9819dc7ee09f5b8ea -#: c50c6866077c480c822ed5d4dbffc7d1 +#: 0dc6f77e9f3947098834e45f7877642f 392a238416374e37bb9106823f622dbf #: iot_devices.switchc6.SwitchC6Controller.get_capacitor_voltage:4 #: iot_devices.switchc6.SwitchC6Controller.get_firmware_version:4 #: iot_devices.switchc6.SwitchC6Controller.get_switch_status:4 @@ -205,49 +196,47 @@ msgstr "True表示开启,False表示关闭。" msgid "Timeout in milliseconds for waiting for a response (default is 5000)." msgstr "等待响应的超时时间(毫秒)(默认为5000)。" -#: 18aebacb5273458790a069f4d5367ff9 +#: d5f4aa985b27453a90154a455e607020 #: iot_devices.switchc6.SwitchC6Controller.set_switch:9 of msgid "|set_switch.png|" msgstr "" -#: ../../en/refs/iot-devices.switchc6.ref:7 13e573293e1947fd9dbd55690ea1d54d +#: ../../en/refs/iot-devices.switchc6.ref:12 e004ce19b0ee4da791e316273f829cb0 msgid "set_switch.png" msgstr "" -#: b7d589fb60484f26967c3358a4a30f40 +#: a94c7e41e29e4ee0a372cee683f9e61f #: iot_devices.switchc6.SwitchC6Controller.toggle_switch:1 of msgid "Toggle the switch status of the target device." msgstr "切换目标设备的开关状态。" -#: eb73f4272714493b83d4fc73fbc6f512 +#: 553ab002283641a0980251c345669765 #: iot_devices.switchc6.SwitchC6Controller.toggle_switch:8 of msgid "|toggle_switch.png|" msgstr "" -#: ../../en/refs/iot-devices.switchc6.ref:8 bf7074600efc46739ef8ab9a80d1d76b +#: ../../en/refs/iot-devices.switchc6.ref:13 678664a8c92e4de8ba5b838a4c7c8b1a msgid "toggle_switch.png" msgstr "" -#: e851468a7566470bafe9a96ccea56bfe +#: 34a2581418544f818b4b8c3200b45da7 #: iot_devices.switchc6.SwitchC6Controller.get_capacitor_voltage:1 of msgid "Get the capacitor voltage of the target device." msgstr "获取目标设备的电容电压。" -#: 0b4cc2111298420db9e714bd92b502a6 342ab40f969b47e4a901c14b3f1c08bd -#: ca5b85354b6745aea621a714dbae4717 +#: d52658c65ead4a3fb0562358629600f4 #: iot_devices.switchc6.SwitchC6Controller.get_capacitor_voltage #: iot_devices.switchc6.SwitchC6Controller.get_firmware_version #: iot_devices.switchc6.SwitchC6Controller.get_switch_status of msgid "Returns" msgstr "" -#: c23db0f037d9421bb0485d7e45f82a4e +#: 4dbd1faa9c0540bf91b022ed0fd6a09b #: iot_devices.switchc6.SwitchC6Controller.get_capacitor_voltage:5 of msgid "The capacitor voltage as a float." msgstr "电容电压(浮点数)。" -#: 1d278d9c83ee4cc8a3185888f3476fc2 326956a2bc574f2b9bbf10409d208ada -#: 646592c215084da9bee75acd3ffe7067 f997397929414c3790c41ae2ed3e0890 +#: 2acae55bf8564edf9a7711febf6b73cd #: iot_devices.switchc6.SwitchC6Controller.get_capacitor_voltage #: iot_devices.switchc6.SwitchC6Controller.get_firmware_version #: iot_devices.switchc6.SwitchC6Controller.get_switch_status @@ -255,74 +244,74 @@ msgstr "电容电压(浮点数)。" msgid "Return type" msgstr "" -#: e14c54792bcc455abcc40af03d61b4fa +#: e2e74df66eb24206a223a72474251dbf #: iot_devices.switchc6.SwitchC6Controller.get_capacitor_voltage:10 of msgid "|get_capacitor_voltage.png|" msgstr "" -#: ../../en/refs/iot-devices.switchc6.ref:3 49fae596257e423d97498558de89e6df +#: ../../en/refs/iot-devices.switchc6.ref:8 c57f8bffe869405288057f2d6136fe3f msgid "get_capacitor_voltage.png" msgstr "" -#: e1c40e18d0a149d2aa1b95792dc9234f +#: fdca7cd4c2a3457e8dccd9d6a9fdcb80 #: iot_devices.switchc6.SwitchC6Controller.get_switch_status:1 of msgid "Get the switch status of the target device." msgstr "获取目标设备的开关状态。" -#: dae956cd3fde45ecb9fb872ff9dcbe89 +#: a5518557416a44a6871330396e86262d #: iot_devices.switchc6.SwitchC6Controller.get_switch_status:5 of msgid "True if the switch is ON, False if it is OFF." msgstr "如果开关为开启状态返回True,关闭状态返回False。" -#: eb23c9b6f7804b1f9c8437aab24438b5 +#: 05433884e7984b8f93c3ff0d73712817 #: iot_devices.switchc6.SwitchC6Controller.get_switch_status:10 of msgid "|get_switch_status.png|" msgstr "" -#: ../../en/refs/iot-devices.switchc6.ref:5 149ef8f5d4184b55af9112aeb52da919 +#: ../../en/refs/iot-devices.switchc6.ref:10 b5b48953a1d041de991b2084a719d4ce msgid "get_switch_status.png" msgstr "" -#: 3fa494873f894ec393f1367a31baeec2 +#: baf41bab9ca3410086b0f3de5cc74520 #: iot_devices.switchc6.SwitchC6Controller.set_callback:1 of msgid "Set a callback function for the specified trigger." msgstr "为指定触发器设置回调函数。" -#: 5b03b50cf6f14212b55d9a42447d6f40 +#: 320c8c0491a941f29e09bee51b852d22 #: iot_devices.switchc6.SwitchC6Controller.set_callback:3 of msgid "The callback function to be called when the trigger occurs." msgstr "触发器发生时要调用的回调函数。" -#: 9f2aa39c76024f3a87a887119b222a44 +#: 0f56140b37644dc3bf11301654331dfc #: iot_devices.switchc6.SwitchC6Controller.set_callback:4 of msgid "The trigger type (0 for OFF, 1 for ON)." msgstr "触发器类型(0表示关闭,1表示开启)。" -#: 8f69152cbd1547b28dc0e7381edf5924 +#: 92bb59249f8e48f5949f17eeeff8c724 #: iot_devices.switchc6.SwitchC6Controller.set_callback:8 of msgid "|event.png|" msgstr "" -#: ../../en/refs/iot-devices.switchc6.ref:2 7e782079feb441ab87299eea85a3d77f +#: ../../en/refs/iot-devices.switchc6.ref:7 7ca468529fd247eca739d4de046c029c msgid "event.png" msgstr "" -#: 72d2104371274385ad48ea384271c934 +#: fa0ce81e3f764fd9a4a95fae9db5f616 #: iot_devices.switchc6.SwitchC6Controller.get_firmware_version:1 of msgid "Get the firmware version of the target device." msgstr "获取目标设备的固件版本。" -#: 46317e3f85f54ed89dc4596e5da3e204 +#: 947dd3bbdec54064afc73c216950ee2e #: iot_devices.switchc6.SwitchC6Controller.get_firmware_version:6 of msgid "The firmware version as a string." msgstr "固件版本(字符串)。" -#: 574c041f20a244ce9c738d802d03d68d +#: 0a33d0d907a044bd8340b94903320443 #: iot_devices.switchc6.SwitchC6Controller.get_firmware_version:11 of msgid "|get_firmware_version.png|" msgstr "" -#: ../../en/refs/iot-devices.switchc6.ref:4 74cb811bc38a4538a94dc45cfd2b607e +#: ../../en/refs/iot-devices.switchc6.ref:9 7aa1bb10f8e34971a04db0c9b5567467 msgid "get_firmware_version.png" msgstr "" From bdb27dcda4d5cb6ce5be68900805f5195de37d0c Mon Sep 17 00:00:00 2001 From: lbuque <1102390310@qq.com> Date: Mon, 18 Aug 2025 09:28:09 +0800 Subject: [PATCH 225/322] boards: Add Cardputer-ADV support. Signed-off-by: lbuque <1102390310@qq.com> --- m5stack/Makefile | 8 +- .../boards/M5STACK_CardputerADV/board.json | 18 + .../boards/M5STACK_CardputerADV/manifest.py | 5 + .../M5STACK_CardputerADV/mpconfigboard.cmake | 41 ++ .../M5STACK_CardputerADV/mpconfigboard.h | 21 + .../M5STACK_CardputerADV/sdkconfig.board | 21 + m5stack/cmodules/m5unified/m5unified.c | 1 + m5stack/components/M5Unified/CMakeLists.txt | 1 + m5stack/components/M5Unified/M5GFX | 2 +- m5stack/components/M5Unified/M5Unified | 2 +- m5stack/fs/system/cardputeradv/applist.jpeg | Bin 0 -> 3718 bytes m5stack/fs/system/cardputeradv/apprun.jpeg | Bin 0 -> 3879 bytes .../apprun/run_always_select.jpeg | Bin 0 -> 2747 bytes .../apprun/run_always_unselect.jpeg | Bin 0 -> 1582 bytes .../system/cardputeradv/apprun/run_info.jpeg | Bin 0 -> 753 bytes .../cardputeradv/apprun/run_once_select.jpeg | Bin 0 -> 2446 bytes .../apprun/run_once_unselect.jpeg | Bin 0 -> 1433 bytes .../common/card_228x32_select.jpeg | Bin 0 -> 2321 bytes .../common/card_228x32_unselect.jpeg | Bin 0 -> 508 bytes m5stack/fs/system/cardputeradv/develop.jpeg | Bin 0 -> 4002 bytes .../system/cardputeradv/develop/private.jpeg | Bin 0 -> 16897 bytes .../system/cardputeradv/develop/public.jpeg | Bin 0 -> 15756 bytes m5stack/fs/system/cardputeradv/ezdata.jpeg | Bin 0 -> 3329 bytes m5stack/fs/system/cardputeradv/ico/a.jpeg | Bin 0 -> 1344 bytes m5stack/fs/system/cardputeradv/ico/b.jpeg | Bin 0 -> 1357 bytes m5stack/fs/system/cardputeradv/ico/c.jpeg | Bin 0 -> 1259 bytes m5stack/fs/system/cardputeradv/ico/d.jpeg | Bin 0 -> 1167 bytes m5stack/fs/system/cardputeradv/ico/e.jpeg | Bin 0 -> 1287 bytes m5stack/fs/system/cardputeradv/ico/f.jpeg | Bin 0 -> 1279 bytes m5stack/fs/system/cardputeradv/ico/g.jpeg | Bin 0 -> 1266 bytes m5stack/fs/system/cardputeradv/ico/h.jpeg | Bin 0 -> 1166 bytes m5stack/fs/system/cardputeradv/ico/i.jpeg | Bin 0 -> 1119 bytes m5stack/fs/system/cardputeradv/ico/j.jpeg | Bin 0 -> 1286 bytes m5stack/fs/system/cardputeradv/ico/k.jpeg | Bin 0 -> 1236 bytes m5stack/fs/system/cardputeradv/ico/l.jpeg | Bin 0 -> 1054 bytes m5stack/fs/system/cardputeradv/ico/m.jpeg | Bin 0 -> 1341 bytes m5stack/fs/system/cardputeradv/ico/n.jpeg | Bin 0 -> 1369 bytes m5stack/fs/system/cardputeradv/ico/o.jpeg | Bin 0 -> 1253 bytes m5stack/fs/system/cardputeradv/ico/p.jpeg | Bin 0 -> 1121 bytes m5stack/fs/system/cardputeradv/ico/q.jpeg | Bin 0 -> 1365 bytes m5stack/fs/system/cardputeradv/ico/r.jpeg | Bin 0 -> 1373 bytes m5stack/fs/system/cardputeradv/ico/s.jpeg | Bin 0 -> 1244 bytes m5stack/fs/system/cardputeradv/ico/t.jpeg | Bin 0 -> 1108 bytes m5stack/fs/system/cardputeradv/ico/u.jpeg | Bin 0 -> 1333 bytes m5stack/fs/system/cardputeradv/ico/v.jpeg | Bin 0 -> 1333 bytes m5stack/fs/system/cardputeradv/ico/w.jpeg | Bin 0 -> 1295 bytes m5stack/fs/system/cardputeradv/ico/x.jpeg | Bin 0 -> 1185 bytes m5stack/fs/system/cardputeradv/ico/y.jpeg | Bin 0 -> 1246 bytes m5stack/fs/system/cardputeradv/ico/z.jpeg | Bin 0 -> 1348 bytes m5stack/fs/system/cardputeradv/left.jpeg | Bin 0 -> 733 bytes m5stack/fs/system/cardputeradv/right.jpeg | Bin 0 -> 685 bytes m5stack/fs/system/cardputeradv/setting.jpeg | Bin 0 -> 4074 bytes .../cardputeradv/setting/caret_right.jpeg | Bin 0 -> 584 bytes .../system/cardputeradv/setting/general.jpeg | Bin 0 -> 1112 bytes .../cardputeradv/setting/general/disable.jpeg | Bin 0 -> 546 bytes .../cardputeradv/setting/general/enable.jpeg | Bin 0 -> 839 bytes .../fs/system/cardputeradv/setting/wlan.jpeg | Bin 0 -> 1423 bytes .../setting/wlan/input_default.jpeg | Bin 0 -> 6673 bytes .../cardputeradv/setting/wlan/input_psk.jpeg | Bin 0 -> 6725 bytes .../setting/wlan/input_server.jpeg | Bin 0 -> 6546 bytes .../cardputeradv/setting/wlan/input_ssid.jpeg | Bin 0 -> 6751 bytes .../setting/wlan/submit_select.jpeg | Bin 0 -> 2367 bytes .../setting/wlan/submit_unselect.jpeg | Bin 0 -> 1263 bytes .../fs/system/cardputeradv/sidebar/Aa.jpeg | Bin 0 -> 883 bytes .../fs/system/cardputeradv/sidebar/Aa0.jpeg | Bin 0 -> 665 bytes .../fs/system/cardputeradv/sidebar/alt.jpeg | Bin 0 -> 897 bytes .../fs/system/cardputeradv/sidebar/alt0.jpeg | Bin 0 -> 641 bytes .../fs/system/cardputeradv/sidebar/ctrl.jpeg | Bin 0 -> 838 bytes .../fs/system/cardputeradv/sidebar/ctrl0.jpeg | Bin 0 -> 653 bytes .../fs/system/cardputeradv/sidebar/fn.jpeg | Bin 0 -> 867 bytes .../fs/system/cardputeradv/sidebar/fn0.jpeg | Bin 0 -> 654 bytes .../fs/system/cardputeradv/sidebar/opt.jpeg | Bin 0 -> 782 bytes .../fs/system/cardputeradv/sidebar/opt0.jpeg | Bin 0 -> 656 bytes .../cardputeradv/statusbar/battery/black.jpeg | Bin 0 -> 1108 bytes .../statusbar/battery/black_charge.jpeg | Bin 0 -> 1240 bytes .../cardputeradv/statusbar/battery/green.jpeg | Bin 0 -> 932 bytes .../statusbar/battery/green_charge.jpeg | Bin 0 -> 1044 bytes .../cardputeradv/statusbar/battery/red.jpeg | Bin 0 -> 945 bytes .../statusbar/battery/red_charge.jpeg | Bin 0 -> 1051 bytes .../cardputeradv/statusbar/cloud/empty.jpeg | Bin 0 -> 621 bytes .../cardputeradv/statusbar/cloud/error.jpeg | Bin 0 -> 585 bytes .../cardputeradv/statusbar/cloud/green.jpeg | Bin 0 -> 640 bytes .../cardputeradv/statusbar/title_blue.jpeg | Bin 0 -> 1956 bytes .../statusbar/wifi/disconnected.jpeg | Bin 0 -> 582 bytes .../cardputeradv/statusbar/wifi/empty.jpeg | Bin 0 -> 620 bytes .../cardputeradv/statusbar/wifi/good.jpeg | Bin 0 -> 583 bytes .../cardputeradv/statusbar/wifi/mid.jpeg | Bin 0 -> 572 bytes .../cardputeradv/statusbar/wifi/worse.jpeg | Bin 0 -> 547 bytes m5stack/modules/startup/__init__.py | 5 + .../modules/startup/cardputeradv/__init__.py | 50 ++ .../modules/startup/cardputeradv/app_base.py | 93 +++ .../startup/cardputeradv/apps/app_list.py | 284 ++++++++ .../startup/cardputeradv/apps/app_run.py | 160 +++++ .../modules/startup/cardputeradv/apps/dev.py | 189 ++++++ .../startup/cardputeradv/apps/ezdata.py | 40 ++ .../startup/cardputeradv/apps/launcher.py | 125 ++++ .../startup/cardputeradv/apps/settings.py | 633 ++++++++++++++++++ .../startup/cardputeradv/apps/sidebar.py | 55 ++ .../startup/cardputeradv/apps/statusbar.py | 199 ++++++ .../modules/startup/cardputeradv/framework.py | 104 +++ m5stack/modules/startup/cardputeradv/res.py | 85 +++ .../modules/startup/manifest_cardputeradv.py | 23 + m5stack/patches/2006-Support-LTR553.patch | 319 +++++++++ 103 files changed, 2479 insertions(+), 5 deletions(-) create mode 100644 m5stack/boards/M5STACK_CardputerADV/board.json create mode 100644 m5stack/boards/M5STACK_CardputerADV/manifest.py create mode 100644 m5stack/boards/M5STACK_CardputerADV/mpconfigboard.cmake create mode 100644 m5stack/boards/M5STACK_CardputerADV/mpconfigboard.h create mode 100644 m5stack/boards/M5STACK_CardputerADV/sdkconfig.board create mode 100644 m5stack/fs/system/cardputeradv/applist.jpeg create mode 100644 m5stack/fs/system/cardputeradv/apprun.jpeg create mode 100644 m5stack/fs/system/cardputeradv/apprun/run_always_select.jpeg create mode 100644 m5stack/fs/system/cardputeradv/apprun/run_always_unselect.jpeg create mode 100644 m5stack/fs/system/cardputeradv/apprun/run_info.jpeg create mode 100644 m5stack/fs/system/cardputeradv/apprun/run_once_select.jpeg create mode 100644 m5stack/fs/system/cardputeradv/apprun/run_once_unselect.jpeg create mode 100644 m5stack/fs/system/cardputeradv/common/card_228x32_select.jpeg create mode 100644 m5stack/fs/system/cardputeradv/common/card_228x32_unselect.jpeg create mode 100644 m5stack/fs/system/cardputeradv/develop.jpeg create mode 100644 m5stack/fs/system/cardputeradv/develop/private.jpeg create mode 100644 m5stack/fs/system/cardputeradv/develop/public.jpeg create mode 100644 m5stack/fs/system/cardputeradv/ezdata.jpeg create mode 100644 m5stack/fs/system/cardputeradv/ico/a.jpeg create mode 100644 m5stack/fs/system/cardputeradv/ico/b.jpeg create mode 100644 m5stack/fs/system/cardputeradv/ico/c.jpeg create mode 100644 m5stack/fs/system/cardputeradv/ico/d.jpeg create mode 100644 m5stack/fs/system/cardputeradv/ico/e.jpeg create mode 100644 m5stack/fs/system/cardputeradv/ico/f.jpeg create mode 100644 m5stack/fs/system/cardputeradv/ico/g.jpeg create mode 100644 m5stack/fs/system/cardputeradv/ico/h.jpeg create mode 100644 m5stack/fs/system/cardputeradv/ico/i.jpeg create mode 100644 m5stack/fs/system/cardputeradv/ico/j.jpeg create mode 100644 m5stack/fs/system/cardputeradv/ico/k.jpeg create mode 100644 m5stack/fs/system/cardputeradv/ico/l.jpeg create mode 100644 m5stack/fs/system/cardputeradv/ico/m.jpeg create mode 100644 m5stack/fs/system/cardputeradv/ico/n.jpeg create mode 100644 m5stack/fs/system/cardputeradv/ico/o.jpeg create mode 100644 m5stack/fs/system/cardputeradv/ico/p.jpeg create mode 100644 m5stack/fs/system/cardputeradv/ico/q.jpeg create mode 100644 m5stack/fs/system/cardputeradv/ico/r.jpeg create mode 100644 m5stack/fs/system/cardputeradv/ico/s.jpeg create mode 100644 m5stack/fs/system/cardputeradv/ico/t.jpeg create mode 100644 m5stack/fs/system/cardputeradv/ico/u.jpeg create mode 100644 m5stack/fs/system/cardputeradv/ico/v.jpeg create mode 100644 m5stack/fs/system/cardputeradv/ico/w.jpeg create mode 100644 m5stack/fs/system/cardputeradv/ico/x.jpeg create mode 100644 m5stack/fs/system/cardputeradv/ico/y.jpeg create mode 100644 m5stack/fs/system/cardputeradv/ico/z.jpeg create mode 100644 m5stack/fs/system/cardputeradv/left.jpeg create mode 100644 m5stack/fs/system/cardputeradv/right.jpeg create mode 100644 m5stack/fs/system/cardputeradv/setting.jpeg create mode 100644 m5stack/fs/system/cardputeradv/setting/caret_right.jpeg create mode 100644 m5stack/fs/system/cardputeradv/setting/general.jpeg create mode 100644 m5stack/fs/system/cardputeradv/setting/general/disable.jpeg create mode 100644 m5stack/fs/system/cardputeradv/setting/general/enable.jpeg create mode 100644 m5stack/fs/system/cardputeradv/setting/wlan.jpeg create mode 100644 m5stack/fs/system/cardputeradv/setting/wlan/input_default.jpeg create mode 100644 m5stack/fs/system/cardputeradv/setting/wlan/input_psk.jpeg create mode 100644 m5stack/fs/system/cardputeradv/setting/wlan/input_server.jpeg create mode 100644 m5stack/fs/system/cardputeradv/setting/wlan/input_ssid.jpeg create mode 100644 m5stack/fs/system/cardputeradv/setting/wlan/submit_select.jpeg create mode 100644 m5stack/fs/system/cardputeradv/setting/wlan/submit_unselect.jpeg create mode 100644 m5stack/fs/system/cardputeradv/sidebar/Aa.jpeg create mode 100644 m5stack/fs/system/cardputeradv/sidebar/Aa0.jpeg create mode 100644 m5stack/fs/system/cardputeradv/sidebar/alt.jpeg create mode 100644 m5stack/fs/system/cardputeradv/sidebar/alt0.jpeg create mode 100644 m5stack/fs/system/cardputeradv/sidebar/ctrl.jpeg create mode 100644 m5stack/fs/system/cardputeradv/sidebar/ctrl0.jpeg create mode 100644 m5stack/fs/system/cardputeradv/sidebar/fn.jpeg create mode 100644 m5stack/fs/system/cardputeradv/sidebar/fn0.jpeg create mode 100644 m5stack/fs/system/cardputeradv/sidebar/opt.jpeg create mode 100644 m5stack/fs/system/cardputeradv/sidebar/opt0.jpeg create mode 100644 m5stack/fs/system/cardputeradv/statusbar/battery/black.jpeg create mode 100644 m5stack/fs/system/cardputeradv/statusbar/battery/black_charge.jpeg create mode 100644 m5stack/fs/system/cardputeradv/statusbar/battery/green.jpeg create mode 100644 m5stack/fs/system/cardputeradv/statusbar/battery/green_charge.jpeg create mode 100644 m5stack/fs/system/cardputeradv/statusbar/battery/red.jpeg create mode 100644 m5stack/fs/system/cardputeradv/statusbar/battery/red_charge.jpeg create mode 100644 m5stack/fs/system/cardputeradv/statusbar/cloud/empty.jpeg create mode 100644 m5stack/fs/system/cardputeradv/statusbar/cloud/error.jpeg create mode 100644 m5stack/fs/system/cardputeradv/statusbar/cloud/green.jpeg create mode 100644 m5stack/fs/system/cardputeradv/statusbar/title_blue.jpeg create mode 100644 m5stack/fs/system/cardputeradv/statusbar/wifi/disconnected.jpeg create mode 100644 m5stack/fs/system/cardputeradv/statusbar/wifi/empty.jpeg create mode 100644 m5stack/fs/system/cardputeradv/statusbar/wifi/good.jpeg create mode 100644 m5stack/fs/system/cardputeradv/statusbar/wifi/mid.jpeg create mode 100644 m5stack/fs/system/cardputeradv/statusbar/wifi/worse.jpeg create mode 100644 m5stack/modules/startup/cardputeradv/__init__.py create mode 100644 m5stack/modules/startup/cardputeradv/app_base.py create mode 100644 m5stack/modules/startup/cardputeradv/apps/app_list.py create mode 100644 m5stack/modules/startup/cardputeradv/apps/app_run.py create mode 100644 m5stack/modules/startup/cardputeradv/apps/dev.py create mode 100644 m5stack/modules/startup/cardputeradv/apps/ezdata.py create mode 100644 m5stack/modules/startup/cardputeradv/apps/launcher.py create mode 100644 m5stack/modules/startup/cardputeradv/apps/settings.py create mode 100644 m5stack/modules/startup/cardputeradv/apps/sidebar.py create mode 100644 m5stack/modules/startup/cardputeradv/apps/statusbar.py create mode 100644 m5stack/modules/startup/cardputeradv/framework.py create mode 100644 m5stack/modules/startup/cardputeradv/res.py create mode 100644 m5stack/modules/startup/manifest_cardputeradv.py create mode 100644 m5stack/patches/2006-Support-LTR553.patch diff --git a/m5stack/Makefile b/m5stack/Makefile index be665d70..9805ef6b 100644 --- a/m5stack/Makefile +++ b/m5stack/Makefile @@ -42,7 +42,8 @@ boards := \ M5STACK_AtomS3R:atoms3r \ M5STACK_AtomS3R_CAM:atoms3r_cam \ M5STACK_StamPLC:stamplc \ - M5STACK_Tab5:tab5 + M5STACK_Tab5:tab5 \ + M5STACK_CardputerADV:cardputeradv define find_board $(if $(filter $(1):%,$(boards)),$(word 2,$(subst :, ,$(filter $(1):%,$(boards)))),none) @@ -81,7 +82,8 @@ BOARD_TYPE_DEF := \ atoms3r \ atoms3r_cam \ stamplc \ - tab5 + tab5 \ + cardputeradv # Select the board type to build, default is None # This value affects which folder in the "./fs/system/" directory is pack into "fs-system.bin" @@ -319,7 +321,7 @@ IDF_PATH_PATCH_SERIES = \ 1004-idf_v5.4_freertos.patch M5UNIFIED_PATCH_SERIES = \ - 2005-Support-LTR553.patch + 2006-Support-LTR553.patch ADF_PATCH_SERIES = \ 3002-Modify-i2s_stream_idf5.patch diff --git a/m5stack/boards/M5STACK_CardputerADV/board.json b/m5stack/boards/M5STACK_CardputerADV/board.json new file mode 100644 index 00000000..5a3db152 --- /dev/null +++ b/m5stack/boards/M5STACK_CardputerADV/board.json @@ -0,0 +1,18 @@ +{ + "deploy": [ + "../deploy_s3.md" + ], + "docs": "", + "features": [ + "BLE", + "WiFi" + ], + "images": [ + "generic_s3.jpg" + ], + "mcu": "esp32s3", + "product": "M5Dial", + "thumbnail": "", + "url": "https://docs.m5stack.com/en/core/M5Dial", + "vendor": "M5Stack" +} diff --git a/m5stack/boards/M5STACK_CardputerADV/manifest.py b/m5stack/boards/M5STACK_CardputerADV/manifest.py new file mode 100644 index 00000000..75cb6720 --- /dev/null +++ b/m5stack/boards/M5STACK_CardputerADV/manifest.py @@ -0,0 +1,5 @@ +# SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +include("$(MPY_DIR)/../m5stack/modules/startup/manifest_cardputeradv.py") diff --git a/m5stack/boards/M5STACK_CardputerADV/mpconfigboard.cmake b/m5stack/boards/M5STACK_CardputerADV/mpconfigboard.cmake new file mode 100644 index 00000000..065aa11d --- /dev/null +++ b/m5stack/boards/M5STACK_CardputerADV/mpconfigboard.cmake @@ -0,0 +1,41 @@ +# SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +set(IDF_TARGET esp32s3) + +# cardputer https://github.com/m5stack/m5stack-board-id/blob/main/board.csv#L16 +set(BOARD_ID 24) +set(MICROPY_PY_LVGL 0) + +set(SDKCONFIG_DEFAULTS + ./boards/sdkconfig.base + ${SDKCONFIG_IDF_VERSION_SPECIFIC} + ./boards/sdkconfig.240mhz + ./boards/sdkconfig.disable_iram + ./boards/sdkconfig.ble + ./boards/sdkconfig.usb + ./boards/sdkconfig.usb_cdc + ./boards/sdkconfig.flash_8mb + ./boards/sdkconfig.freertos + ./boards/M5STACK_CardputerADV/sdkconfig.board +) + +# If not enable LVGL, ignore this... +set(LV_CFLAGS -DLV_COLOR_DEPTH=16 -DLV_COLOR_16_SWAP=0) + +if(NOT MICROPY_FROZEN_MANIFEST) + set(MICROPY_FROZEN_MANIFEST ${CMAKE_SOURCE_DIR}/boards/manifest.py) +endif() + +# NOTE: 这里的配置是无效的,仅为了兼容ADF,保证编译通过 +set(ADF_COMPS "$ENV{ADF_PATH}/components") +set(ADF_BOARD_DIR "$ENV{ADF_PATH}/components/audio_board/esp32_s3_box_3") + +list(APPEND EXTRA_COMPONENT_DIRS + $ENV{ADF_PATH}/components/audio_pipeline + $ENV{ADF_PATH}/components/audio_sal + $ENV{ADF_PATH}/components/esp-adf-libs + $ENV{ADF_PATH}/components/esp-sr + ${CMAKE_SOURCE_DIR}/boards +) \ No newline at end of file diff --git a/m5stack/boards/M5STACK_CardputerADV/mpconfigboard.h b/m5stack/boards/M5STACK_CardputerADV/mpconfigboard.h new file mode 100644 index 00000000..041da5a4 --- /dev/null +++ b/m5stack/boards/M5STACK_CardputerADV/mpconfigboard.h @@ -0,0 +1,21 @@ +/* +* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD +* +* SPDX-License-Identifier: MIT +*/ + +#define MICROPY_HW_BOARD_NAME "M5STACK Cardputer" +#define MICROPY_HW_MCU_NAME "ESP32-S3-FN8" + +#define MICROPY_PY_MACHINE_DAC (0) + +// Enable UART REPL for modules that have an external USB-UART and don't use native USB. +#define MICROPY_HW_ENABLE_UART_REPL (0) + +#define MICROPY_HW_I2C0_SCL (9) +#define MICROPY_HW_I2C0_SDA (8) + +#define MICROPY_HW_USB_CDC_INTERFACE_STRING "M5Stack Cardputer-ADV(UiFlow2)" + +// If not enable LVGL, ignore this... +#include "./../mpconfiglvgl.h" diff --git a/m5stack/boards/M5STACK_CardputerADV/sdkconfig.board b/m5stack/boards/M5STACK_CardputerADV/sdkconfig.board new file mode 100644 index 00000000..ced57b6e --- /dev/null +++ b/m5stack/boards/M5STACK_CardputerADV/sdkconfig.board @@ -0,0 +1,21 @@ +# SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +CONFIG_FLASHMODE_DIO=y # QIO mode has some problem when mount fs +CONFIG_ESPTOOLPY_FLASHMODE_DIO=y +CONFIG_ESPTOOLPY_FLASHMODE="dio" +CONFIG_ESPTOOLPY_FLASHFREQ_80M=y +CONFIG_ESPTOOLPY_FLASHSIZE_DETECT=y +CONFIG_ESPTOOLPY_AFTER_NORESET=y + +CONFIG_SPIRAM_MEMTEST= +# CONFIG_FREERTOS_UNICORE=y + +# M5STACK UiFlow USB description +CONFIG_TINYUSB_DESC_CDC_STRING="M5Stack Cardputer-ADV(UiFlow2)" + +# SSL +CONFIG_MBEDTLS_AES_USE_INTERRUPT=n + +CONFIG_FREERTOS_ENABLE_BACKWARD_COMPATIBILITY=y diff --git a/m5stack/cmodules/m5unified/m5unified.c b/m5stack/cmodules/m5unified/m5unified.c index 5e4a4573..b8121647 100644 --- a/m5stack/cmodules/m5unified/m5unified.c +++ b/m5stack/cmodules/m5unified/m5unified.c @@ -30,6 +30,7 @@ static const mp_rom_map_elem_t m5_board_member_table[] = { { MP_ROM_QSTR(MP_QSTR_M5PaperS3), MP_ROM_INT(19) }, { MP_ROM_QSTR(MP_QSTR_M5StamPLC), MP_ROM_INT(21) }, { MP_ROM_QSTR(MP_QSTR_M5Tab5), MP_ROM_INT(22) }, + { MP_ROM_QSTR(MP_QSTR_M5CardputerADV), MP_ROM_INT(24) }, // non display boards { MP_ROM_QSTR(MP_QSTR_M5Atom), MP_ROM_INT(128) }, { MP_ROM_QSTR(MP_QSTR_M5AtomPsram), MP_ROM_INT(129) }, diff --git a/m5stack/components/M5Unified/CMakeLists.txt b/m5stack/components/M5Unified/CMakeLists.txt index 9ede28c6..6841ffb2 100644 --- a/m5stack/components/M5Unified/CMakeLists.txt +++ b/m5stack/components/M5Unified/CMakeLists.txt @@ -24,6 +24,7 @@ file(GLOB SRCS M5Unified/src/*.cpp M5Unified/src/utility/*.cpp M5Unified/src/utility/imu/*.cpp + M5Unified/src/utility/power/*.cpp *.cpp ) diff --git a/m5stack/components/M5Unified/M5GFX b/m5stack/components/M5Unified/M5GFX index c7b2726f..5581745d 160000 --- a/m5stack/components/M5Unified/M5GFX +++ b/m5stack/components/M5Unified/M5GFX @@ -1 +1 @@ -Subproject commit c7b2726fc043d5720bcbbd4f2036f41649749078 +Subproject commit 5581745d7d0cf00f918d2f427b3c2923e9ce2528 diff --git a/m5stack/components/M5Unified/M5Unified b/m5stack/components/M5Unified/M5Unified index 2d84e624..a4c9f6dc 160000 --- a/m5stack/components/M5Unified/M5Unified +++ b/m5stack/components/M5Unified/M5Unified @@ -1 +1 @@ -Subproject commit 2d84e624e56d002451e1002e26817b07088428ba +Subproject commit a4c9f6dc6a3ded7369c4e42b008d579756cff6f0 diff --git a/m5stack/fs/system/cardputeradv/applist.jpeg b/m5stack/fs/system/cardputeradv/applist.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..77e8bcb12096901b933a3bb67c2b6dbbd9c71be9 GIT binary patch literal 3718 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 Zd7c>IVV@;f5S0Wk%AMp%Y^siYW& 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 Fe*@0z!(9LX literal 0 HcmV?d00001 diff --git a/m5stack/fs/system/cardputeradv/apprun/run_always_unselect.jpeg b/m5stack/fs/system/cardputeradv/apprun/run_always_unselect.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..675dd6e8f31b76abdbcbd89b4e59dff629a549ac GIT binary patch literal 1582 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 L&j|!)@z{R=$5Syy literal 0 HcmV?d00001 diff --git a/m5stack/fs/system/cardputeradv/apprun/run_once_unselect.jpeg b/m5stack/fs/system/cardputeradv/apprun/run_once_unselect.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..0b273a46b1a017bf69e5ae499cae8521e2d052ee GIT binary patch literal 1433 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 literal 0 HcmV?d00001 diff --git a/m5stack/fs/system/cardputeradv/develop/private.jpeg b/m5stack/fs/system/cardputeradv/develop/private.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..822b74c6585aaa254e7e90d29a373cca5f1d3e4a GIT binary patch literal 16897 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 literal 0 HcmV?d00001 diff --git a/m5stack/fs/system/cardputeradv/develop/public.jpeg b/m5stack/fs/system/cardputeradv/develop/public.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..f9079cf54d801e75387a509de340f4a70d61891f GIT binary patch literal 15756 zcmbumb8sb36fb^*O*Yt=8{4)wwr!g?wzY9?>||rxwr$(?#&5r`-h1`KpYKdf*EHr# zbx)ns2cQ00`Pu;>Nr_2{0l>fj0I+`-;A=FH=o5$pg>b-Qy*$ee=rgt>A6($T=fv?wIN{p;+3*ULt|)v2<7$I0*WqQiuzG zS71m5MhF&83XT>CEO(m_h3}U2*(r9Z*kKIE+c@!Y5jQ8H6G7@ zKC(pSJShB}BVGBISLeQsosN{`@6#}Zxpl)&*xcWJi>V#wqc@3nShve3X}DX=G{urC z?&O*3#4{FH!?e4S6_X34DB$WgR$@DfaFoFwciIgQ;%mB4dl=lMvg*-}^N0Fg5JMUi zGde~=#E$ZdbaQI)onW^!N!`M+^2#z%d*_6U+wQW`jwipJO>d__ zxLuE&18Aw3*34#OyGnFq(wq^(97ur zsmVP;uiwZamGdo*3D_j|)4xG>VN4}Xp`QKw67zd7j7fO>;kZ(OyZZN@fy!O1z{lpA zO0tGiT_^8~9-F3J_daO@QCo6T!gHe$In2sET4m*yJjTh6A~J1Kt=Y-JsvcSWtMBft z=>8enSFup9QZ;>JuM@{={vf0jYvGF$j|28QUnXW*TkC;80~}maDB~^B@qml>)xQRg z>3U94hZ<7rdl+?oByS7N{!Nn=tJQdRW$!EH(zEmMa8>L?Rp_|N9?E19d1fSAHlY|z z$$`j;<^0YCH|5g#3b#WWZRn+(NBt=2;Gc5S!BVp%S&(#i&7afiP{=)&Zw zj-(FBs3Gile;+y6pH)&3fOqMB#tv1ZQFQ!(F)T;)`hB={$L%kTh!MG?^M*?N8$^(}_HQEW>hxrzYUESp zmE>5vE@%9;Ik%TopA+S}+xeL#>xagx5|(7Cg-iv-T_dz)>ngEGtN_N6qrLd3L@)UU z>#YQjofNo(EpzP(TS5joV21dKSA{ixLxLZOwed|=dOpGQOhO&!N9j80LDvFSe8HNz zE|%<&ajm{7r7()>be$k6;vx`+Q_V+}CNUu~3O|dP*%!TxPP)$EnY>~d$FYXA2E8*! ztnA`Dz&sURvuC&R#$b9VnvoTFmy1Z=V_r8q5kf&oB2u`3U`a>DK#%5sL1$`we%_4^ z0q1USHRBS_V`Sz~GD3bLzjT0K&d6CiSE;F&<7-T>ZG2N3ULO9_fi~3l3&3e3-|^`X zc1e1`@Wyg-{RK#0de>ytAN0F%2k4k=_@}afpq$sz0BH2n@F|*BAUae$QXPQ#dH$pwT9z3FBED%a~qk z+C-(ml4ETpMBu3s%~`OOP)W3x%f>t+B0e6=5#Kay({@=QstQqXrI1GQx|p0l#?w|8 z!gJ_RHSF+%be}*+W_jM=t^2(^e7o)4nr?T)FlX5BXWti~pMn579)FXK-RtLGyyoDf zk>IDDj;B$rxdxTZhT0c^sl>*YI*)ORlnMH8w&N5|ZwtiRcJ<*i8tWGTv4Lz}$>9?} zeNpKVKV3#*y9mUF=9;Hdg&o|Q0#TGuykVQg#a4=tL1}emM%7Fx<+<~!uMJ-QT~g_n zBGqtZe4y*-5MS05$F%J0vh42e?s3S1vxi};_*0*?!0Vd$OQ8PP{bA@K!8%OEKf$mh zID+xx`2xUv0kY_SegUdqIbKC=OZUD2&cj~-x#>@l`Lp|-GnJ+dA zIrb1{)W5E9PMV_lk~J|*q=p~8yKj+*XU_izinxAp`JrE?sVpp*QvOV;vCN_Ohb7z(?g z1RORP@;*s$2^5_9R^fiMzL|4rIbT?z+8hB1w)hgc#jBu-#)Gb|MbM%0F@tx?+_Tmf zpl)|Yi=cl|?*SQz9 zTbI`KLj+RC7}+wCG&c@KN(LN=z{9tH1yH zGoH&BnmsWnRoNQ}o-qim8w)Y4K@BhQ=Cm|5^{va0(#)${5?S2#jfG{I<0Lr2pmR!o z0j!a91cRH{RfAS3exR(=oJJ>|kk{BX9`T2+wUNiO4I8$~FSosCMKUO5E#!~&fO9dC z@E~*HkSeCQLQ^U$UtECbZ_D#FY>eM zE*+Y!BIsEn8Ko|+tfo$uS% z45vK1EbG$s^uZxDy+HFYhQHHNwr#YeFo0@Aod{%dHOXQ^vT5b)KTVkkn>dx--QZof zZODKa#{5*cxFpg(D8J_DzxP@Gg{)eK*?<28IkLvOzy{@#!p4?6v~PSa&qhGJ5}$C> ztpb_1v~5dPu~E6$R!Gm{b=-Zn@v)4zcJ#TNkp2ZoZI89Hcz2?$%8N9~c&c4)PSpV0 zDR;ZEF`9_2Im|zDn1H>9Bb}`?W@QQF0^RK^iE}*XXxLq?oL>4+HM;&MJo5oyb*XU_@=v^ zc`E(LWDy4;^emq5*%s;j zBqo&4xC}I$XG8u**HqJJNbU2i^Vj^bnxC0Lg?}de2`G1hy=n(+bfPQHdZtO&+7@cj z>2IAd>!SsrinqM&sh+JwIbcTVSnqIC(e(FMZnq4fht3tU>(Fq1=s*AUxV^cRny6ln zf9u~KWq9K!uoJ&V^ds%QcqwKeY^jP|a;!HLB6}0@C8L{Hp-g+Rk}c!TqjC($PL z-+9%8i+a;It#my&;nj^lkLju;(7FZhfWw5yLs21U{(iquTh;5JpX^^()Oc^39IQGg zK3Bg0LF3pT_U^BPCfAvq>$;8~?Rj=uuTpPhBy$;R>JECrO?ZeTc<2^s@fit>TKPcL z7NOpd{`*4qnYyDXj8H6OEs;LVt7R59liOVly; z5b$P7K+W4{dy}WU_Pa~8MOQ2oV>}8#3a7&Uwr3TP-7ql!N4gxHL=XM<0Uj40{LCd_ zKez)e6;#RCvg~_~7vJt%j#_=m!|cstYE&(2Fn63{UNXIWr~$>RC`13t!tz&WJSWBq zl>n7s&AmKtuQ5x?cl4u#gbweV1eYJZ=Z(iP)f-g|_2`Hb@wp1cnP!k;gd%WCE@k30 zNPEnE2rR1&nIJ*(BI4hpnxq%ZF)aW9pFLPWz*Pl@b|=+c#pRz>0qKT^n7GicQA`@Q zWo~VoB!#6XM-zhalYYaBf+G?J5A=S5k&MZka^FbHy)vqiGzDo5IV@V9hD7U<{i(fv z*|cDBtRdR!Wrlu2qp}Db90f|!NStUqxj8+1W`O}7*6bb5fe&l)Uh+Hv_nO=HBN9&* z1!+~fXN60BKF@ed*EDuHqarlxWL#RPUhtoIxjLyiI%fqv@6D+OKi>O5D>`Cu!oStL zRJUiQ_4NO&=WczF+dp$Qr2#4WjO=2yM@8P8Bt@vIlg6|3b!%xW zB~`tP-Qn9ZzVDBc8@Ldc_ANcn2WnX5zVVH8{B>mHl}lTFG!3~}gbJrOx)7*8G_i!X z-9dx+C@fl$_?(AGf{1bR@a!qsKF8`&EqjX@RM6*$>D1o&b$?$o5M9cwvpUY;S1`g? z^QKpmsZ4T^Jc|$-Rt(KTybsIJ1mo6cro=-@LfCM>OZ%M_ndds` zCb0Ha12gDkoS{LQbs5_>_rmqB?)9hDp~Yno2g5{0#X^Y6C`HE4uj9-F-)fF>yfJCJ^Z@+QM&5Asx!%>><2?{4pKq zKP?aAI-m8hFGgKkLr6T?6RS(3(w$cWgz`S);MkPPLg0)o6o*8;`y>F%;|$AT&|bQj zasvY*o#`vu`Rc0+x14=Jc3x7k@=sod^qkqLRy+ry%4TBiO{Kj{IQ&=`~U0!hA+R2SZ31a1wEIemU7%pITA%*}FTZz?ma=>lBw7tM3?!WL&2 zX4RqoE1DQu%aG`X!D6wIe6 zoC}*526Hd8>?e}iYPZOX!@AALtRM+(V#;~XWW(#xpoNTx_-O8$i+JE=6XTtPAd0k> zyd~VrrwAFA)>~|x56CXYfA7*|HS6*0jv89nMJ@J((nt6gBz`xeyTGTa&|ww#0U~O<0ooP&Je?<&-p5^YRAPI^Gl1fi|N)x(u802WB>y`t1W~m zQ#G+M=@ZWf3j-&b*HkS}*d3Yb+RjpSfJt6G|0tJGT_KSQko@kk(&FTWKXrM*m*?0g za@ppZm8irJ}K_YbkpZYHKm-fxcmJj+sdXFQ;-626jHlC#RjHSf^s$nk61V7?}#e zvtGQ=zI!-NROf~0F#6oJB=bJ%aT07dW}&>xh3B`zsnQz{vjCbp{O_0JwY z1B>OR;Nt4eoYl5=TO0ZXx0vdi#{r2etwTHZ8S8lrtl!upk@1AG)M~tvtM%qE4kWS; z9^XnIdD52M;g|%UH|v7~>trRTRrqi;(tbCYRJ0m0EBzd9WW+s^!GuvN+$0N~7^O9& z8TEKA`Af5#!xD&Cq5KR@;zfPdW*2tp>DxEo?xn{x;Jg`C8IhHWQQf8^g<^tgI3p7D z#3k{1_HCdQNcNMnsu`r?V@q)~mCs>c;b-2|)jg+sJ>V6a=@NCaK~~|>HV!evq|Q|u zh6gJ)DPS_H(5rNTylN3VEgqGWtOqaMpEXhN`%AU&WqTz5om3 z27Y4u9o0MpT5sdW#e~OA#WtR<^VL$RuQUmmuo#vMxbET72YaNablUj)hw$s+F58VR zat%p^)-Mhf=Ctaw)iBPO*w$NxEf0F@b8qRp%Rb=8qLB`>rm`tAw9@Emj6&IXtKpQS z-k~GRg8qAKb-g19wTY;i$A^81_wT&Cyh^!e=CYQ2L8j3qDt1!IHRB)0ySFb@TK>HR z24G62Ux0l_1;gOwIY?stF95u>pYrJk`=?v-y4CIXDFoOm^3?lrbACXHQP%jpvK{OlFuEpF}yjFOIaE#q0Hpkfd2%8Ca-a3djWYG;vp>D`}9sM6xkDrw_jV54=RoH7|1 zO9F_iSYXUs0(k zA7fB%4DqH?|CyCbOkcs|VOlO+1f#Bl9n|+J9pm!bDAEBF-@ut-`!fKML1e3;wH}RA zWPKn#7FJsC{5NbABxBt^YzX(ElGqo(%2P;?tVxj+Z83rklKN1`-#VQ}H(HI>6h20sRK}zdVtn(qAKE3`jf$nrfD~yLHnL-2GGgFR?;mqB z6X*=+5<|-i@<#xuLHz>kV-LL)Gl#{t_xr`>%q!vPg@3D%&c?@=N(>H!a^vDsN_6t) zE=ia{{JEd`1-L=1i$z34{V^jjic$8th*qHM(1$Y^8E&*d!K7q~Rbxo_a_$6xf=I5R@RJbpr2`Z!}@0Wxe^S zr~9X;mN$SMU0(nqySHf(2OnYo&Xfz;Mx{>cvr={D>t>%cdH!Ay14pl^p`)z{2AUci zTJ8j?V^XrpKKvn#81J@xO{aD%ha0l@9+mH1=sz3?re4nx4;S_&tu}}#1^W0)xp75EQCmJV~ZPWK${OT zGDFNqi+Ke*rH7@PYFH)-6DJNBqjrS+0y6d3T_N?TBmQ3Cr7v613LCx~ba&_EuTc$! zjq!xD;!8Rhxk{*b)}lka7iZylvv`rlJ=|~$*Wb<{N$gCl?b-ga2`a(auF2-%`fc4; z>n%L;bF(Ekub9!8{6+SOXx_MjUW{a&=s2LNObXc%O3=3|UNIR57b=<9rNnfCS;&Lz?nX(`(4`^l zq{Dk?pD#dBL8->w;{!CyfZJ95p>GL`_rn82L)UUt^Xdb8bGdE-G1tDt7WFU(w0SJu zpf8EV0#dz-yM8Agc$*|D^E77)Za>ZEqg1^Q#98(7RQt0_#Y2<;gYwqQ*e+d43wdL| zoU{>wIyTL1^5k!Lua{MfqHWhWaPwBSgzvWQ*NX0_l&7_F`5ab7{iEofMDgm-37uHm&n z{s%qEzD5f7w$Zr{SLdr4D(LCQy#9HIDi+mTMtB~P%Y@sY~ntOip!R+_OKMlP)T*o(hUKCkTa27Zuxo)|T6-ox; zAJd+Vh_hu~g;7rZi?jK)@@?_}lkj8mw0p1*|! z&h9LFwlA}^%$|F`0I;Dlp!ItdMNADWbXmFibym&soMUxCChRwb$*~GgpG5I3U^b93 z7Bupv?N-I-V-=0HOynwB z#-Ev5>Jw2irUfikX7M+#lr`|Dv>CzgqaOMMO!i&^EBc+=hPt+XkJ_{C?B&H)vuk7S zR*P@IP3L{4`007;Mdq7tt{tK=d|^2af|E%1vUDBXDIlZ86bUWVyx5f#E7{?KhU=P3 zkFIVc3JY}}nbb(<*`bkf@?HV->fB#xDJZV80pnS?r5N9}Om*zx7^ug~|I$efp;g+s z|D!_UDfW*#t?j?I+cVPQvG!xdG{p_jT*C6#SP@|T8I?52G#m@jwVEa-Xjn60^`t$5 za;vz%y+QfsuN>^Q^M2fI<~IShW$NdkP7%>J!+jHx?qc`TVAw zWcPrphQmc(XTxG)w6-vu`Ai7uBK!wbOIaM*_@J5**`-LLML8_D<1!brR+L61*4rF8 z-5`R05NqBCi}nQz*)b|=^|jK2XCp|h+-$YaSIMpvnL=ODR#Wnggicu7#IW2nd`Fpi zHmPseq9JbOHw#!|Q7fi4WI>4u()V6c6Nm(V*WOLNk!@0g`NS8mFMxoxVD)lsK;hZ}VFmJ$QHLpd-GTV`Lf*A1NqZ$&6@;e(XF zzQ6ct-(wyUWn_Yq-jbm1C7Q^!wSx!ktnW&vM?^2yJJh;=*A9)VUDU^s4T-EAYGd>W z?Ac0#g{m~=N7(o;$?~{%TjQ9MQoS5uCmHn<5Te8kHo}Mtd^lJ4m2jEp(u~6ZsGvlzh7{Frx>fO4{>wSK7Ijxyh^GxcJ%<)zGGalAhK4fGnyWg7~RRuH%8(0 z)k$KKl!)eRR*ITloWeTSSjPj&2l@m#q5E}{5+Gljvy1c%*!WFys9~$I@tiD`#&bMy zJgIWXSR-EsA^v?L7>%^)Kv)Y+Ys*AwgUVTCS(eN~thjh>+oVF&i#(#F zc3$8O;5o`?m8rL1sc_zi=?H*iEOU>li>ljClS8>O=cxL7UB&&(bMaQ^5en}NCPAG>?SvE9z0R=K|-%ywJR z>uHs@exz?ry^8T!=0?b$&?Ttn8nRS${`st1w$S47H`~VjQf4 zBCKUGpid!(i(yr8_V0LN%Q&bdBZ;n&(&;gDrdEgiVVnoc+HAadNZ%klM{1GLu zWly6z)NB)V3A;^xRp#t0V<|K;zX#JakoK3i_gl-JBnM139+x{W`;cWm`` zy66IrVo4{t^SSkQ^>YG)f1zrNF2ly6RbW7Ja>T)AAX${hI@oKKm7r{hmXSfLx@0#w zRk6s32<%2VmFZGFi2pJAnP|Y%UK?0#alw})JL^WB8=w9rgYHwNMP;(^cpCRN8Z5xt zRFY6(2Tn&Q(BhA_9>%(~`1C_krl$0EiGbUi!x^Hgb4)_YR->%BaJek2>b~{4_H<^3dJnJhEkRSwVzeOcPWxy)Q4F-Tev-)mo<;s z<>gLxx3P_?M~$C-D@xZ~#!8ex$Vt@5StLjqYIZepqE*awR%RSv;-LUNd;j^c(sNOM zuKwQJb#CLe@b=-A@#tKI=Upfp91}>uJpf3 z--a+Lh2nKXi1cvctHO>pp zZu_$0Y=pt&GLYOje}|Y;7^@>33Yql(bGXXPf6Q}h7r-!xRki!{<4_}v>vG&O8X5au8t>Q2?t>x3o>Sa71{FT(OB0EF3}9=l4j>6 zFth3XQK_9(O*Tds9u!GUiOEc0)@F9eKOh?+qEGjVFwM-<05)HOmj1yf>^=jvc)fhB zy8pTl`!*G>B=pAzfd^_}hfX!B5Rr9_@bvBVcz_rs768zoQ?*UG3JkOhz2k93is9r^Nk7 z)a8&3awW1n8{RY4kx%F#{3Ty9F_@h;IY7Zxy?^o#@+#IN)Bq9;DR{hpE({?h*d;sx z?okz2(vc_f1AhLl>e0(Y+qUWWW^onbx_ol0@Nh4yLUNjf%3?mTpiB7d`CeZuK28g) zM*hXV+0bCnseHEdW7pm=KAN~vcOi4zeiZZ1iObkP#p>g8|_JWHNS8YW>8Sy2;(x%GKZ(f zpN($_aP3tu@a;?34V32VpW1_8+1Xu94mQ))?hmw%P^-urHD;CLZx>fa$j=QI%(l?n zQ#DEw4+2s>p0lMxS=Hw&9IrZx7aYP{Z!Ov5iF963~*4|KqkrI==r2IV%cUWErROLmDJ9V) zt75tSoI`D|jq^vn97cCZlZn0)0mh1!;$1^`#9gNOp`2rd3L?*k?x#ecl$jGSsETQx0{BF*HMX_VsmzvoO?e5Q^v9t~QXWB1`!1HgR(wb9_ z>-!z+CDZ3pw>eBK4HjFq?#sf!%9JWkxYdfH=~Fr9B0DFiayQ_Bx|pYfN|9jbEDa?S z2?aFKMa}Sp9Lkj9y+D#T@Mw{5%N*vB=mK4Hx*z7qCn)py3lPePsyXHNW;~(cd=>mS zd6ARZ=_$49Q{{BcrTrjJs=%h(^xG^br&cCN*TEf!$o0InyRjF8HXw(W@@!>Er2D_*bcW;$< z1r;fZFl6ZdL>EWz#6Dt>r%SimseqO9B#uYLZFEZb>(5&Tyn0%fwGNxXtq{$!nc##- zx55jA)`&|zF}aa*_{HqD{nK=Q#u<(NQhORvSJB%p|Kn-}T=D77`ZCX>Ytg64f+yD1 z*9S?7;=!O={c}39JK_SQBTWOPuGCh)Z>?cEK#lNrH%Dj}w*eZ$wZ|~F zXP>2p?)IdBYV;qiL@tAB=}nmdDXhkay=L9aub|hx@Ccw?_Z@P|x)xIijWvIF*iP7K z=O4R@(g_U)^vmJ)D}`D-ZDjfES`1O2Qc1xgt~-7kw?oWn4MO0uBz@f$%}MMyHP`_O z9nFB~0ROsAmX~H`^C{h;z|{Nk=o9it#YJbSfzD@IN_t)fc?l1tmb}5_L=m7f0E->h z!F7;mQJi~w!KcFzR*o_S)7kg#Ky&lQI{#qRw6?2MqbNb*%W)hn6XOC-<8TtYjR>u* zw$*ZlY7Yu>BUY}2Lb$ICvw5r@+CIp_iR*>z+Id5~dU?Jh`aB`4Z7xlEz8%74!LZCb z2uGjZY8hdD7Zf=F6k#Up$U=~c^~ZWsIB z1;2{neVwV4{?#ef#k5_2<^{A#kiSvCRvPb;?0WvFbpjDmYkfbn7lqLini&1-)NE&Y zQ6%uDI^JEpAKw@?%g4qdy&$FTbN-S*1qV^pDYR-4?d{2b_E|O!7@=oswE8RMU$hvh zNzD||)j2>nQ>k!`s^rN;8!wbGIU$yv>U1rsA(x9A_7u2EyY}e2iBMR})vwhIfVA#- zDLgzwma06RGas^q-omeJ&Sc9_ng53~Hx%H4`+K~*m$>5npGOvNj^n#ZXaCNAeL|K$ z+`LD+Z@*4f`A@rSen2+3KfTX<0TQNPk{7-JmqL#pXtS!em~UF$(*jRL{{@jb`l>8$ zZvNqq{vY1m{|qbNy8RvC)%~RA;N{P0`)?#V9=_Xa`}>>!7eG(=rQ{z1_W!M1%RFPl z->ehPK6Yg)whS7?HWs4oe$X|tUd?B%u<6WyTY*UcFQpOvHgeilQG^anqTRsRje7Zdqm@3{oyKAy4IyB@p}sy3D` z2X0l)6*kqMruGZuV{HkIkzcfsDpJ_{L!!dT61B znPK`xT4lc0;h+Z1ON}%i0X0ul)BK<@eZ(0s*nH`t(qT)qYNmaH>ojwKTr0c9jT4xW zLa4V)EF5WjEFImJ!R(bmN(KX5Rw{-NtlH!!E6Lfr&|ZA}>AP#K+9W&pY-_tgdwysnt&W%lCUIG^Mc)+Q7Vy&MNJ&3X8_zIL?YOu2wW8MxDZI=w{m^xN?~^Hopk9 z&)2Rcn=>M@ykQC{xn_!(WNuu@dfGx_uv!2Kgc0HtaaobUAgt}J2w9?dw~B)oiw5i%$1`@4(I(NIgcZQ zxzQhba@n%iF|PB_jYwC}@8M=uWq;KU_~9cS5uwBGe53|UyH`$wFh%(($(9!z@t+u-?gvl z#=}xWSzjy7wQ@V&ys>P!jN^!GqLCNRuXRZqSd@|Vsbx9=w{u1+7jhD!#`37!j#Y)R zS#b0N+%bgXY#V+&jbhSO7p#ACQ2D5;oddhej%spdLgQm-qh7arE+`S%HZE%#bx-~2 zVXM!CO=c5YCm8%@E7m~OYDaJq6F>Rp`l%zM>Z{ioViD7@&Lq_&5_7ecksABHNQR$Q z9y*)q>}o?cV$`DTJ}4LX!b(}2%rbSrQD=mJxwB&@ik7XrRI%oYQ61|z$j2vGW(~V= z!XCZIL_8^%xq7+&H~|S0Y7I4jPpOvI6$JJz1*zw^Hk`CQcM21!wLSvIIM@IW&fZ0`juH3Lf~JjAA*b(?;r&wp5&PY>x0djo%6yded`V7AUz2EC zGZ{18)pr&K_NB{FGt$rkX<>P}5Sn_e)_luZhaXQ;N+~E8k<*+mMawaMMO+LS?3MT# z9rUke;G-REXl$>eYE_H7Nue*Q4N z$_lEo!x2wlley}}@^J$zW56-bYTRuivNYF#fzy$mr$xOTmL5{{)RexsJI zDBmeWxnfE@j*v%I_oLUeW{#^FcZP9BgKU#ulbm+RW{&$0qiR zu_~8RJ(d8J&Zh{)Zo^Gk`brO>+oqeKRwMDK0{GXeOn%chJv3q#(KLg?&kT>1c5UGf zl_kjb)a-Ic_H8R^B-3o#OP7zvwW?gl>tIz3hg^9fL+MVhW--Z_Q!Q;>tJo$|GUbgR zjD88^Im=6*mvaAb(}Uv#}z^GSi?5XE~3HFlr^XHY z*+`{!W}mt8mD>iftGG<%m=z3Kb2pViNUp3d?y0%F7-dxr;FJI4%-cUTfkxEFZdnnG zG{a4OA_}mg-5CKZT3Dx2GaCDcKpI$tTR}l5q0;y4nDiamTFa4xBF z{1Mbh>MXy`3SxuW-dbD5x_en4NwhwJY z5OxJfGh?WpTo zgLYwF!xP)+EpU+2W?|+Y3jrlvijp^i5R}1{1*vUIiI&z}oJHU@vyM441MMQT&lgq|C#dm@0=Rdf6Wn!|2(R2_x5)cJ9i%em;c(Sxp}W2-Cmx- zK0Cth6+a#RKRY(M$|C{t({krU*1vp~^F2DDn8Q%c%FjIh0~43_P@O9q0+|6 z8zYEg%m{YV3l<0<#p}7GNNBD|$9nVA*K-c}4M&;(_0!KGh$m>3zXg8ixqS!>&@^S& zwHrkx(Qm!gniuP^K;yGcy^ zGGmQ7o1YeYX?#8y(s4^{XqlD1Lpy~bbd7H4wgdz-tA%fh&e$%Hs$B_L2gJ)xlBl3N z`g$Zz3u>B@7ZlaT!{-Z+d4%rEfexvlw{oETSRBZ- z?M;dfp==s(G11b$&A*}RNkhwoKAbP))Xsq~GQ29|+RlWRylUm03S6;G9126qV@!*> z6W)ba^|-bZS0~RertBG+)7WVI2;>mRXSaA&&2E2XK0&+CIWFD^^Q?tu*&Db7qZGZoPepz>8zk&>}rqYb6``%pwEXhu^8 zV+o*19xUUD@*}(sb}INzf&cvn35W$8bN2RMJL0{~I_VrYUYdS-U9hbm5Ycz9l+lz0 zH`nw&BSk zHeLZ)30mybX(~DXIykn1L7HSUHTwtY2FB#Ps-ks$4#&Ow@8KU~evz?fH!sLnm<=4sb^aGNJwv`TGC{#^iRrW#Ik*?no%aj zH&V;a3B)FE3!^BelcIexAwq#^^rOFwX;g}4ehZ3@Oj0!#KD7Ucd+gde;89tfAtf4( zVKE@Lg;p*d6q9Q%bJ02_Z(>_aA#M(F9m>CadfLz^mqrm2V}#>M2hy7iG&OY*?Bcyf zIVH?9(Yh|LaA1u?%Jv(qZ#W?*)UY7ai>j1D)QVRz&VvMFwv-tMq%Jj0i_r>Od2b11 zxs+37hyTyqj-RoH1O}sw|1w4SZ61c^?#a$8X>B)cXew86YfF*0QrSybF^jfz!eeTy z8B6#sf-C=)6nYznbD$-AR&exG4`#O|@r-qCln|z4#siL9Git>mpGI(q)bfVv$&n34 sd$slc6W-rfpr1DR=`1wz&;8TPOdnSQFJFMzFTiwIHc+os|7-Ps0BqXOo&W#< literal 0 HcmV?d00001 diff --git a/m5stack/fs/system/cardputeradv/ezdata.jpeg b/m5stack/fs/system/cardputeradv/ezdata.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..4e30db55031e4054a4857ac7b40fac7408a88032 GIT binary patch literal 3329 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 D;%_Fs literal 0 HcmV?d00001 diff --git a/m5stack/fs/system/cardputeradv/ico/c.jpeg b/m5stack/fs/system/cardputeradv/ico/c.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..919fbd2be270f3ca2d7679e5667fb338929cd5b9 GIT binary patch literal 1259 zcmex=}(87ESy|GNmT&`W}r`)K!6#ngprAvg_TW^L5N*ZScyYK z)X)*AOcJPs8R%C8U}a-r=Kz_YEC`ljWP$1j+Q!TXGJ#!4ScE}|LsaqqEe0N-W+p*q zK?ZwrxF@j&z-`KW&khybqU+$xs z*7E*8&dR<^*{16uwB&xsfvKHeSaSX|L`FL^_V0XU8}U`_ee~u? z*}8N+%RNW${Q4dC&FsaCoXFd^ZrohFVbvB!ld~C}=c~V(M0xxcb#=RR_f}Pv={Doa zy4uK)TW3~F1t)vzTV<{E{rXC8uGLiefLlM+9p&2_!W<6%$(_{LV(vZj>VuN(=H-*) zwU_r^xop=`^DE(B^VtLTVR8q<=ZgGNXbV%m_~YWqpN~KJ*~~0FzvIfhn2%@rEB(Hg zv|RK0w$(Z4YN|tKl(DVM=G|f{f1)e-gLmI}oq7Aotn#ea`#+xdwPL*N)wyWRv&*H< zsv%#(5(ORX6ZYSjB>u>k@A86&-^`USpPRk*+2*w|Md5Guotc|0q8*Wck#}mEO}6iq zHG6Et+?MW{%{7Db$@GA!WI``wrcGghUubC8-z}HPN|y|$I--o z_gV6%Z&7Zo5jLyUwGRCD^6R+Qq5S34i^CWHGb~zC|4?3b&-+iz=L{d7?yifM{iK!P z>SL?-pFwlmmrqmVrgg5W+WenE_4rb??_O6=X$RijcXY#ko0OCNpZtp|K0QBeWwP$& zEaN8I_lD7t*Sxx}Zk1daB@owl=AE}tow}W{T>Hi^?Q33rtoXM%++bDaog;hGI?uYi z*4BDGHS)Ug$!Xb=Yn%1ge){v=cbQ9D=b1UjXGz+|#ij1e*|jZk>*PZ1io<>@qdbHe z{Tri|IMNvB-A^%bQ#h7<#HIPg!J2usgdFW4)9k?q?2YMynkY|DvGcYTwa0v*UyBk^NH( z=YNs7|0TQP%HoTNNzfnVD zerN8)r;$lb@Kg~!?s}A zvMYvaN;R<=TG#v9yk)jND%s^&H2KGo?Aww*4{cpKbK`N7+w)g@{WIIj)_*(dR;<_4 zw4Q@&9{4dffBv;mm;cH!*DfPTp~<20#d7POUtI1O(-pNUH2X?)u7b|Zq}M7!JR1}5 z-cEd`$a3AJvo~cj&yub*PrFwPZyr<`Z{5*){n@QKp`TA2UZ8jJZ)joE&Mg}SCMfJo zcVgpUUpRRx!;0|hoX01N_ubJ{*{CeL;WEQ3$%xe=D%D}y8|q%ih_nBxTU2yUTYvt6 zM{m9F*Xgr%B<+urHhCX$JaSskW%oN*b58o4Wqu*vy6Byb+u6U`VTNC|VnunPPw$Vr zu;_#gPsHK`X|Adx(@scaEszq(w(mR4!uqo<>H2QF%)C1Gf z=51@!G7C4JW%5;7m+d~GkMsFDmEHxPj-8p<{@y|U*lq{gLak>u*C%~`yFCBXT(g%i zwj?^ZC@cwG$KtYUtG0%t>Ff1dzB0%i3G&G_T#}>X^s!88L)NA*63ZAi=5X~Z7#KD& zGqs)(m~cj5iq8=s_~SpdYwPx8U0;TznF|9XwsFjm5WddB#ww(uDs12sXlQ6GEG(C= ztgNiI%`dg;XXVw`*Uv4PqG+;OQ`2j;W|r5KtsJ`Ei~>;yTo~9`l9ZHecvmY;IwGW+ zn!6&PnyK|IvxBD8-dV7{i5wG-@_J6* Q9F(Ee!XH^v2bt#B@_gKni)WPVUiHdf7^` z^sD8x=URKKBKsoFi%nNNdmwiCn|oWjdG@bfKYiW)%Uc^)*&CQ`=}_tNNh$OfKYH$- z+rMddXB&m6+ioac?XM$qms>B`@9gU@>)EF#s0cn#IXqKRbCP6N!uvn9cPpwlZ9iTv z>Ho~?kH^BiqpB~S-!1ijvt}+Azh9f~?JXx}#-6N@%}&dj)M&qUfs>|9ecj)gM^BzU zzW&bKuAi1yHVY>Gb&1>a<-(d(t4eQgbDfqKouT1(b~R7(8;jnv#)%U>XP@}?VcmCG z?aJJHlb1d{9CWI3@4CHDlb7smn>^uc-p)zc2k!jK;K;hxvNBbOi-V*8jQWEspJem& zSFW+~PLGyaoV!!eWb4zO(h%E;QSxP7FT?jM$Q?ViTG8**G+iy`56!Qy?(Hr5lK3L- zyyM<($@NtpH>R5WXOPJ4S{NIa;(o_MXhV8Y!Umy_z3aSJTW)=$*;yrZWUnq0+sadx zY?=*$EIg5&jEyb=Q+#|}jvxQ>pJ7p*$L87Xvo}AIkN@TIeB+A5^q3bvdiQppUO2D8 zDcnr=>vF~44z+7ox~BLU8%F%Q-#=l0-N#RbwKLykKYsc=<@$zF{TptnT33XAU!P!> zceiNON{8F!Ivc0yMjEO|RP(L_hKa}Fr;9hAsM~+@iKK;em{!NVwAy1UH3j!rPKz8|g(vSU+eTmrYmCX+h{1EtPM89oYfGZeNn z3Vv7B`SaqR^-osci+a1Ix>l}y@>87`*UznqsPXeQv^>=~*VDvdWxCnjXR6a!?JwqZ zo&39P*0UolG7QIrPnCQNS}pqX?{vji(G#AgN1K}JN|``SaH&dR578p0ix>wAj*B)>*>QVFeCHXPoJ5?CIn=3D8MKv(UJaj`gQhi_xhE# zF5dU%b!ex#+{g2=n|AzuS7>q~>8wHc7ok{&6KAFeG7DZ=`0$R$&Z~RFqpw)?dFmaG f7IivkyKPp{A&vHMf$u``KnI*((b2cB{{Kw?*Letp literal 0 HcmV?d00001 diff --git a/m5stack/fs/system/cardputeradv/ico/f.jpeg b/m5stack/fs/system/cardputeradv/ico/f.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..235c23f87042a5e490b9a785a639d157f0b1c932 GIT binary patch literal 1279 zcmex=<&=<@sKoSg?Ss0mE+1MF41UZF-MMTBK zoq)>385v;mP*a%MIN5;`DuQ7BOiXYafX1-^^$RfwDgw0|Iy(Ko#lQp9%p}Mx$Y9T) z`up7H)t}4RgXzZ~-Vsbq{v5MDe5dbK#ZtXmwOh~M|9Z6P*&Z#? z^Io$yvZzE|(M_5be6n`lb?*A6=R2crPR-6c_BOfmiIv@!m*I!bFHKrhyrI(i=l7*4 zx~3K_QLA1GHIt$g(w{J*K;LH#x~pj(TSL&imFS?VnmV>+k(4Nca{t&+&kF8w2*%MM3eomk`J?z-`GRyMo(#*OWv+vd!Ws#dygwzYhdHtX!4 z&0+I8b?R4_UfT73=Cp+g+Z_IsJTN+DbcUlLv1kH2V`kzMHPI)jg6xayt{qJ^oRDh$e4ZcKkuvY*^BKtRkh05a=}Dj|D}JHojcm|c0aF@ z$CefR{qq}K&Yy{q50r_iu`l`i>&8sW8Ly38O(NoTS&I+UvLEEN`Pj0c$!N!GRb}1H zRX5JenQpkgDDK*X>Wd!_o3C7Y@|)Oa0jccM|C|j@oIHDa?k$e)SI&Dw*?Ij}Nu(^C z%+T4$IkUg%sxDvNec#ie7Ym% z>5yp;IpK8b&$FA?6PzvhbdGZ}%ubOJ8T*+H9CHV`hJ}#ZybaNWSRVj(pjSDnSR1 z2bx?telF5gB-%1z-KNv6SuETQCJUMbgPH|iMg?tMo1|?1Ief<4M^QiY{Ft_@XI>X^ zx~qJ?>tjGbprNAIlWl&nKYxGu`t?7<`u&H$cl-&NuWp^{cRS4_Wz}M(aEE;FXP2df ah2Q3_e|f;^-{aT!Z+ZXu_2)ms|C<1PB>KPr literal 0 HcmV?d00001 diff --git a/m5stack/fs/system/cardputeradv/ico/g.jpeg b/m5stack/fs/system/cardputeradv/ico/g.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..e271e388c415f09129f084e63e41ef09a7454da0 GIT binary patch literal 1266 zcmex=#P|76T7RfgrOWgFVB` z?^|!Z{K@a19>0Fm@ABNe`q^>)PQ0>nyc|yowKV=w7WtE|x$0cjss-HBrC;f9{Qc+4 zt51KzYd?f}XT)7S+Wu20AyRWz^w+d)QwnSsA1VoF^_u*Xzu+G8?3vro?g>^;{_vkc za`E0X(~hoW3Yq+w+}*pW{~PQ3{Wkq)Hr4j8Xa8xpUVmm=&9?{c zd}}Q}*UNrNPruGMZ&Fm`)_-4?+P^oO(V&>N-}2qlO26#ju2(kepM+c8UD|m4S<#m> z&t%WXXP%wAW&Vxi?ELgp-iBqZE=ybZE8oZmyIkVm8`bq>+q=t`FGeSu&p03!l(H|_ zDlFMWU8%`enbAvaQkJaul+Q^=*S~nY==3%H4?4HB8S3V1uYbH(tN7QBZADd;l9Hca z&VGJb=s-|Nd|m&tp!He{wN5!J3%>ZH{^a-LH*+eD9?vgaX|wb3Ou0{U-@W8qGxcp& z+tO918ZK=~tFlZl)A9Uq_?;Yo`SCq_Qa|Y{ZJ$!I|G= z$;9`!Hmx?^Eb^z@y6d08i|lXn6yH?c;fpR__n2eZV#%|seG68uR`7W;<=}=GcHSAM z6C{=2?#hvk(Ct*%f9T$Rv32X*!zWMqHY>8~tJdKJ*Qt4-Nl%y7-Ch~q?B=F@YyTRt zDJs{rKX=af{&TjjveO#5(8*I1YL}|J%oxlK)`4L(rcl?NKdU*Houd{Ye3--O^KEv(P zg3{J0FI#rbIload>bvagD7pCUu5B46 z8#RQFx_KR4ZRq4{ClGZ_>FbP+j?I<%)5~pcZ}fXUA#+~udC7GD%bd@9id}Y2QP$L8 z6xh8%FRmwMdYRo`7j^xmE?)j@+ucJ;O3#OdyD?6k+B6}wkwx&YfTO&)uFzZ^;YHeu zU!Pmow{YUsRSTz1ZRA-}?*f#&C~tQ4%(LRev(Jy&{4wk)crsvONoSESWFHaTpSIf+w9 za?8DICin6Me;rLyGrsxt(8phu+zb15UNaFaSZDm7!9%Mvt4q`ILiV)%^5I_v1tZq4 K|9=1Z|C<1?`24s4 literal 0 HcmV?d00001 diff --git a/m5stack/fs/system/cardputeradv/ico/h.jpeg b/m5stack/fs/system/cardputeradv/ico/h.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..ab1d1945e28d433143b49e0de0535eea2678db40 GIT binary patch literal 1166 zcmex=;GEUat{?1DnV93qOM ziDKf0MrLJOfodffL28i!BO5adCs0O3kbwyV7+Hbg0S1gr%q*;eY(fl*j_krBhDL$^ zZ!z#N0}T*l7G$tzc+npnKcRS@+g)DkGShPN5(SqxH$J~jdfOio>UkqYYimNXtLfEu zyRJ@UV*a?op0DVg^hpc7(-!|3w9d`hTdFALx8&B+;zPPTvn2T!rd_T4E-~|K+sU5e zO@i}}`xNSbT4((4VDEnhx9#DxUol&66sw+?x%a^R=R5Cof7vrJHHT~F*|WbrJZo33 zxj#E|zhckyvn)416xuCPetbG-@{H6jg`N`^EniEC6$C6=!63lY;Pgff2 zvA5THRo+={@zGQ#*l(+8>o2jJrV`&(%^ZJDd#mgE`C388WXHqW-@Z0Z&{VtlYsR~G zwNfRbCzh^S8n)MU+a;#6)ATkuDx4OReRrGRkV9?f5}$098L2A4#&*BXeEpaFalgOa z`~y4p?W5@MLI_qW=q zTcN-(skb3V^R-a4LgK21497Kps(A#&pQ+A0_D<}4ai#Iju(vt4X7Q~InzLNfEI1(4 zLq)_%?Cst169K_GGi!Rzf9p8@c#-^-qf(ZC?>_Nb=(kqYEHHER%2?BBr_G$7ZA@&K zz-YdAnQyU|YHy<8OhKPB4OLUEPAJb_UQ<71MxC9tQf-2f&4dGyJy{V)mV3XcaC?_tyuEt$R`>D=!6%pI+bc?( zjI+{H+8MS!>FR=Hx2Lb)Oh2t>#?iV!Zh|0pXQxt|zIftlw$=KMT?yP`2UdJyW|mMe z(1P6saivvXKd)5f@YOoy6}0M9)TE`lPTWBUT*NjAGB7qubaeEsy18LXkZPYo z$XlmxS|UvQ*Zb~&eT1*{Y<8{Iu?H)Y3QXTzn*T}r`l{`6b3z`i|8_?wA$-$0$z|#{ M<97YLzyAMC07RIz`Tzg` literal 0 HcmV?d00001 diff --git a/m5stack/fs/system/cardputeradv/ico/i.jpeg b/m5stack/fs/system/cardputeradv/ico/i.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..81c5e84075c269072aeb3a782241910ed77d445c GIT binary patch literal 1119 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 literal 0 HcmV?d00001 diff --git a/m5stack/fs/system/cardputeradv/ico/n.jpeg b/m5stack/fs/system/cardputeradv/ico/n.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..5f4388163e4229b7bb1de5eaf1a3370dab30aaa0 GIT binary patch literal 1369 zcmbu5eKZq#7{`Bm(dMm}n%xMCxz z?xfq@6j746$7*?PN9JX2befmK?Pa@KSa#>$`$zxxeg68M=llJf^E~Pa^(%n#IqZEH zKp+4>Gy~LAz)R!4O`DK!BWT*{9)LyyJix;smH>)|z|au2P~!m<3IksWfz;9gaBaMi8LMeJxFBlVE}Vm!P3+K4eq> z6VTf*7KT`YS_Y5lNI?Sbu^3l5B1$FO z{PE>20~ee!1`(y~geRUT-|_mrJdUjf(;a4OdTJ1|OBp_x@%&|E%w#5Q?4ij_-GeaK z>4n}ax)|(6dVT%AaB1)NeE+s;3yGs+swndqrZN5XN{9WQX8cRHMGmQgZEVsD140-2BTVJhdHY>UZ3&8b z*_xYP{#xmIUAmF)rvaMC(EpHMD)%8RL_VLI*6^%{YMQrB2ynDR6+$`;L#;SA4 z*;!grme77YhUDSikP`Qs6+Es!QD;D4NaIf?SD==;<95wIMT}ME&OJE0_3EPx%g{y7 z4F5V~YXxSdYyabs_U56Jbaz~F8Fx^q1{s{PrKPG3mK-LMzlS_adwJh!c*%JUmjlA2 z_7#P87cF!3&2}~FYkRPhaZ#7=34Nu})+mRj-OVNIr*72GIw@PR-qJn0cYF$OKV+dd zx-q~b!Kv)IZjl&)EndMEb2zo4k=$S2$XA};kp*$5$VU?1AjRM18h>;oUC~E&qMT{* z7|UxO@y*j!=FJ}yYqR+8&i2)ZD(oXNk~+eQvN^un2k0yVHj^{AK*v6hWh5pnw?<22 z=D3^|f$&&Slt(gcqIDJhMAWPFpWzo*a@eheAX0%`9O{Y?XQ5s^-|mClWzB90w4ZX~ zm-h9g;+0l^C6K#*oV6{N+{hbuWB>Z5Yxqc!l6fj*ct$5VeqPn=qqk(2VjJWMK^@o6 z9=q#6p^P55Nt_@5q5k$hR;HNxB~szF^>r1mT>N=t+}uy6MsAyTbE#rV(im-nPyg>**6W&T^{;FzT5p07~!`z*jQ1&s8AyNwP^ zN3mh-0_}Vhu!j-n##n*7kLY?Pa{3r(0T2BKygb>&x3LuV(hAAXN zs!NohWuPDtu6v%?Z~-0oGwSV$z&3ugWzd|v74KD2M5q(2GGOp2PdMtqG#ZL@*2yNY zwzM6a>^gli+1;2+a?8n7uKIlJE~{1e-S@tB{{>NSK@Fb0@Oz1mHt<8 literal 0 HcmV?d00001 diff --git a/m5stack/fs/system/cardputeradv/ico/o.jpeg b/m5stack/fs/system/cardputeradv/ico/o.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..ce955089801ff6c69c28a9c30bb3c611a961871c GIT binary patch literal 1253 zcmex==1^2p zF>-WH1geu{WMBd+L;^rV*f~H(C<%h4n1J$71tkUn%Q`1j(w(WlR#5``=+FbY5)uE?#maK_>-SfBQA?J@d%fyO9Lz!2e z%Wty_xZY3{@aCV#(-e`=wT8~o3mKc$T^mbZ^z_wzQ>zAcb&f6>X1--|fq@--RXc({e%pL_R{j9uU}u1|Ze#Rc;h#On(- zo2~T}IkoFl;mpk`UYyZ(d!Ffpzt-FmId9?F2)R1Td+w96+xAcSGwFWrZd>-6>vF4N zCPy}3l55y3a_i#F+m~*-$E}$=>qXzrog%SmB^_+1YX$f=X57_(^@=;oa$QBl%epp+ ztsS|JhN*A%&d_S`HN9fB$%-$0%Z4ixn&Y;MCRX37Q?aked0ZB5HZ$Y!ZrNLY-qz&L$H{8;lPXU>K0ROO#iiZLGR|>)-*YrK zJyJ_M>`L0w496;K0WlK!!;i(l@mFySN#;6 zWWcaPKRPx|JA2cTNf+3%KO3u@OEV9jKYiiaNsG<3k`h8~6Sl{#en0^Za0=@@(-TFCzzai8DG+O*y(4 zUL7i|nJTDhBXw6c^x}2f$XE%surMyiFcty#7fl?mc5Lw~icoh6cfUS2R@Pa0b*Qtp zHj|{sUjav!x{lYmG3k=W4);EOEN9P?EMuvv>8+x!=~XbxuXvV^lCJ=(OMqwyYXAfD zYWV~A`1k+f4qlfWzp+~J%*nuSbM;EznXkCE^44bdj5nET{<}Q11zPv^I15b9zNMyq z%l>v*=tHqnQZp4ZbpAF7WYu31IuzElR>O+7==vq?inZAhOEaU6xcJoD|GxViPinHX6ZnPDa{GBLBT3JNi>DLS$Xizq1@B~4uT{}uxe zGtdk{WV{6hq+j)MkqJ7pouue-d;+(qL$Yh#o48xs* zFX9uKYxeQYT66A$P2jO)SHqn$kIQd5H3qt*2yQ6dD`3BFi}T*y+ZAtB>ZnQc-LNUO z{4g`SLqExAr@*D8CC8nErM>paJ&jZl6S}-;|Ah_8hASe@{g}EjZ&{mJ^UFJ~d+yr2 zR#|?%!ffrSvI%uZ*jO2^>~yoAAtc!`E>l?Qq?)9s$eAnU{~4w~_}us6{riuLer}o}qN(-#sFp0-^UI;( zu@NGie|$}UEV-eO6To=l_>>Zf7Z1fM9%U{HIx5;_)%M^Fqw*&?4m9DSN86U6<>+%JGy}$1^`pTYJ6B+w}My_p?&wf6aMT{AXwo(%Ln__+`|_{*^&5 zRwUhv{q?O?%;7=OQE7%7!Piu_IUEjUxX>K=yOKvh{7&xNWAFIR7grkZ411e%Yx>=w zrAmH5Im;HTn&EX-N$l<2@)H5UIx}l}&c77wU)NE$OylDDckh)#xk@uNH+B2Qdc9n; zEpn5*X*Ul`1LxkiM%l9hb+S2nPIRTEFkhM6(`4Dz@XT=W-l=?|UGI+_JMi`O>KzNc zjrqiPE?F!RDOsre_K8l)q@^{p!ZxR^@?65xuVNsUEH%wg^U_aQzctspH(YHFoxb3x z0c&?cNF`%qkAQ@TUP4P-Ld&8wH8nN$TU9ewN0%90xxl75OO&xYp|i2kw!k4FP{}FK zm_=1sSlH0e@bZZhCr+-NJTq=zTxr$U&ns0qe6>z_1+6+2HEF4?6L-)77qJb342+Eu z9UXm*MK@O|D>8ppsm!ywF8}EE-}}!!=d1qd)<6C+X-T)RT)>kh8(KUIS8=#7DQ`Ts SwJ&ww`h8Z9-`CmyzX<^M%AE=T literal 0 HcmV?d00001 diff --git a/m5stack/fs/system/cardputeradv/ico/q.jpeg b/m5stack/fs/system/cardputeradv/ico/q.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..d38cbd96c5330db2416cdc85e8dcd5045934d6d5 GIT binary patch literal 1365 zcmex=yFT_riR>b1AmBkyzfY8FFDoOS+!%y(rNnHA5lE z%B9EW^;gX1`_E8z(Wp)5kbdx$uQ9%Jm)*$Vb=32Ju|{tVPfF4i-6dZ-IzJ@*lD0W_ zIC7WQi^O-2_PEXtoH&=)<}Ax;Q-v<;12aOWp7#Hyrr{O0(eR_a@XPZ4_wVnV)ANh7 z`g`_9yt<{%0fS!+m5*ClTUPYEUUQ{q$IT3_yVGVU@*P}TEcRXXZRYmYljgng9H9#-A6bF5J+vZ^Z$(ip7kx;~0)e1ZH;3ocd*XaL=8q zr%qolkZ*jYJ=a+<+CXEikNmyn{XccJJXX3J=bzc&<#K+#>gSSeJ7bT0ExKLJzu>?n z;Y^FICziFvEvptR6e;gt$#7~7m-?eR-RMi%8UGnJzFsqrK{?>&f(Si!xzcZE0oRxIWa@GC&_^Gh=%y;d_PoJl( zJ9?$IAx&iyqxE^FHM?H6tY~%G_E<%k*SPA&8JX#t>x<&9O>AF$>a|sUw^eM&&$rn( zG+xQ3O-U)+mbYY4QiEHjgvn8E(cd2Dn0vQAyLaHiTfuxz*|YQXV=tuIX7i?d2{UY& z>3cI`x+q8Ke$TG^*LN;t@scsNJCb##`lIK&^$R|h$KKJsQVf4J;vV>30_H)e`>XlS@&!MaVSTdO#@8$uQ| z2?i||cpVkAb#0Q;-Gc>%pWUAOE_v3m^HAHVZ2`|09e$uWd-jEG-5FUE_wRipY1nnx zSK_!PXHoUc@78CE1wV;RJDi?VDK69SU23)D!aF@tSN9y*_O<`pmP?-1Q=TbuR>xiY JZdd>RCIFhbIK2P> literal 0 HcmV?d00001 diff --git a/m5stack/fs/system/cardputeradv/ico/r.jpeg b/m5stack/fs/system/cardputeradv/ico/r.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..3e57362f7a7f5b3499f133c39e17283d534673d6 GIT binary patch literal 1373 zcmbu4dpy&77{|Z6xQ5JauXe~;$O#_as^x*_P z0)-hufAxN1q7;! z5YDvaLwTe+G6{P+Z^uap#CJhpa?7$;SdaAO65|auX`obsSFJ60ohMkis-|LmMB3w@ zlIC|++d3+=uH)8CBMGb(NAhFPlrA`;Tvu zD}xmlR6&NVGcttSzm?S3q2(g*u_SpY@)&cNdAGByymNn?BAaxI9IX=w2T+R|>dDuC zBPYqPaa&)LaW4x8@p{7ASls-&_cRt=H64bRabnAMDYvDU#jLkCU21-4DgLR9Wyiix zWM&WuCc?Q~0{Su8wlF?EaUY|+_l-j>sHET^jd@nxhFbj}0<3yT`ftxlO&w zpdU~8J#G`yeH^6>amCp|+1TtNJAe3?2Rp?3FzaomEH3`7*Yobnwq8X36$%0ZajYf- zQk9^qtDpC7+Q(r@72R>8Pdr%^s%zeB{q$jK{zQ2DdD~}!fAR&K8b-ghjnGX1fmUkK zz2bRxO8S$e?>tLnEm6@|*mU3ROczIz(U51|T=G^A7Z1Pj*qxjNvo_xaQ3vmI&JFyr z#M}0Iw=QNCeC%!DwPznU^=9gmwP)UY|6eTC(@O=iE(ThF&-mtG{o!o9t7KjC?& z*n5wWuYCaTPES?@dPN%HS_a1bjK0FhonIS>kCaI^)IIc0;k(|Ca%g;ira^fqwBnX7 zuH`(pag$CpH7MKU0pioeuADKV%fwXsKW8tndWH_0)viu&X{s)c9!Lz^Fz8o*ecq5A XWJ1fxas8!davgf_mMM}s2O9hrawRv3 literal 0 HcmV?d00001 diff --git a/m5stack/fs/system/cardputeradv/ico/s.jpeg b/m5stack/fs/system/cardputeradv/ico/s.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..76974e1192b8add4e236f76d16a5cfd592f06f14 GIT binary patch literal 1244 zcmex=`VOWt9aOm>HOun3x%ufqnr2MkZz!RzV>KHg;h} z4kg1xpdv}2M^Q{*U}a%r=K$$f5(JtAlxBfxXJBSwWMXAw7Zef}VGvbR`hSao2PnrR z$SlZU&+zhl>8_i<`OX{XZ$GmyuPpavb<~$Cfs8kgg7`e9sPKCnwK^9ZI_GMr1AlG$ z;rXiXOfTPhP&KdAV6Ez&%l_LM6>Y1`Fio!9R2*Y@_w9vQ2WA2qA* zrnQ~+JJz{)<_W^rzI?U1wdcoX_nxLN=Ne7F&z5_L8&dh8#vf05_9G2fx;bGEmV_IeXT$9^}M_LF25@dPu_g=eEI1c%GNx$QWvH5EXrVR-<6`O(RC+f-Sy&gMpJU;1p`js3s+&z!n; z;;kRqy{~=VQy#x|nrT_&*@74GuY%ovtlhFJU(h>tBI|u&Y1k;7xnv^cXZ?T}q({W~)#EJ-6i zHSNnk7pG~D^Eckl`CKktvnuV)Hu0REJ+r>@dTnR^7@*~s(U`938&)()<=Lu^RXV7qX}AJF)iB)-|r$Ue}bAl7GtozX<@dclr7N literal 0 HcmV?d00001 diff --git a/m5stack/fs/system/cardputeradv/ico/t.jpeg b/m5stack/fs/system/cardputeradv/ico/t.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..48be6f4684581233335e98dfbe961201cf4ef81f GIT binary patch literal 1108 zcmex=lP2pE}}*nzc2= z6)xQP{}uxeGtd-4WUBjidiIKNVmgI_io!V43H`OKK zq~}GBl#&EVS&!5N4uytVpLnr1UvIy(zaxA0>WghJf9~`2PtLt`Yq`=r{m&*Bu9~_0 zo&7bO`CI<+C%sz*5vM~ z+p1kzW8e2rT(Nt1`h%RgJ6;|%Eq2%K?X&utY@KyXJ=28ATi}=LezlMF${+l`{)lt! zn^X7RCT(x=vOT*r`JXAU-pt+^dVz+PK!%hArzh zTEMk%by&G#2*ZMn3=HfCzWTp7E99|C&ErX`?4Iljevm*{++hwDi`IYigwdt3I7LeP-X}%D|@~nb-0z zFS+Zr%gQj^J%}+Zj5X4OMZi&B`6Bn+clqTr-=5FEE%D@OaO=I--WylPE?)CBah2}* zgqiDYH=o#;|8fJ*wxeY~s%zJAE6ywaYS_+ifA_x9G@i;ruVqrJZDu~6RdRW$eeYsd zA&su@dt+B;Z7^9YIrrv_Ne*_4T4!0zy&~K3l;Qi)Es`F#Wy@dwXRwWR^gp@bM@;Lh zzztIiXYTr7J=4{DBgflo6IGau&PN4tS-P_HPPuq}>8s~<`bnF;PEWtEWJR~ z7q1vu8O-Eav&3kFihv{ggI_U`=l1r+tm^a(UN}+HN^6&u=`%@L3DML9VCr#XY2x_0 zr_Wt^^6bfH&d;BI?)-dvYmr2bM`tyT8J*QUWi;iiIde>%aQo z(sNthio8p^G-I+b+j>{?IoGW?Qyvvs?ch59R&b_r6SJnIz~Pf|TA%D&tWqlPD?E~( z^3r?8be9I!^^+IH|9oq3A*e?GL1xA(>zgf+YqNO+Cq~#?^G)6GwuoQ;{{5YEV&?2A z{XKibKEI;KhQzN7pO15Ka|TLYk6bBPxamUZZe0T>+2*Kcy6?PiU7l>O!NW4|k-?RP z+G)l%p(mGocx`C3+j!+AbEZAV-`NFi_MT-QY-pmZrvCTqI)Aq>`B#2i-~6helK1bP zS5r#-T6S8o%nl2WN^;4QRaiA`qmLTT8Px*gw*t(@hCkdk>lMlJT}$Iz*6R|sEwt|F zt*Ltt%{!^V#JhOdR6*~=;1@a^uHs&!T?n`g?F81>i&q3o<%>W>RW zT-F>p@+aT>TJoP6UrIjjpYWf-`cYESv&?4|_qSeuc4bk`_sp9`E?3_exfxz@)-Vmf z>f)NG>G+=^Tr%LSd6r|0HrqzdivJAz_teaZFSb4P<7eef?Tw}K36h;U4St^wtk&C= zsHGUNcE?GM!#?-68MCXN%Dyvw>k@;nLRNqIkN@7i^Uk-gzg)W4pWEoQodr@c1g{wd(+pP&5i8roH5^f z3PhX^fBi==Qv>SDqu=@PUwzP)<;)3-rH=tS3h#?YGYwx;n(Zs(q}%q`0VGG zz~>(>d)nAc4fc(hA|4tV?pUyH)9KbK4(^7K1x`T1#k&lBz)n^&BUTDR)e kr{!CZ^xw&yb}R93+u_t!@jX*K1$|~pikvw=|NH-&0N?x*K>z>% literal 0 HcmV?d00001 diff --git a/m5stack/fs/system/cardputeradv/ico/v.jpeg b/m5stack/fs/system/cardputeradv/ico/v.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..dee6e6e6465dfd32b400a6c9ffdfb7b3d3f58031 GIT binary patch literal 1333 zcmex=}L{tceu!yLlqtpLe3_KtOg3N*p_6$Yt{^7qZU&o&_-hTU6eEq7Q zliB7y;y9giBlqQPOZ6&Qm12Rw(3XT}Y5y6L9tgCs2=Lr;;ASq{6Yu}LHtjwC%_r0O zS3cXqJ$?J6WUZ6JC5Dk39!~MQP6wgic$z4|7fWc`lwUoD>7Z+B7n z_S0{l%{R@IUA9g5rXTCoXKD8JU#tZTo%$|jiG=Mq8GY&7n)mtfUq8?L?fCwFRNd9b zOZM*Bz3FktTidWG-K-|57^c-5`m&!c&Hioe>tT6TQ0mK)_PKSJ>#ur8MZUXyg85_F zI*XD?=fa!QXWU)I8e$&1waTwF{-$Q9ckPEB-HknBj+0hgS@_&hn~TRwB5BF&Cp(%d zrOg9&zTI~|W4~*Ey4%SWrIF9SNBm(ceH+QMQztcR3ftwRw-E;21)ich($j8CetFOI z)jr{s=T^RydHLh)+B*wMZfN^m)2^z2cH^?^eT5BQf0rLVe)354er4|DXNuEyOKCCwWp1_&)KIqh91s>;u$VXRbk2nKlxsVCt0#Ti z@+p7oyc6-|-~0YETv$;gF1j~5mJLox=0G9WSL__J7H+c1hL0N2=Rr?g%*>FuBTUCK3#b+V2#FtFI*pwr#^Dq_+BUay@5_d zSI`w%gQ)w-lU|rNKK|EUZ7==VYth<%H>Yb|ozo68``i^=Ar$hhdy|wy_E(OpGH2gL z$9e~ZHtt7T4N z;m^&^?PiiZP?%=nD51JUz*SI7a_up%sO?K%8%KvFT{IS!S65)ze6_fHlc2%QZ=YET zWJ)xAlq{qsxC$y=QFtFY>)!M=*RIa&b~$pU=3C*ZqB)ynwwpD{Tx0TB+Ss=!b7lXW zOiVnu6PUpJ9AS-{NERXuHHDRpor4o7qa?_{1k}jL0@V$)ikT6ppIuN$ScF4VQR)9J1|ESEfDBEK{}pUzJ6dUzwTku_q z_4+UKF5fCF`qR&RTN&CdRAHc{(lDLunxOfwUKL{S%zI@3_r!}$)5e6 zp>NL_#0-7Z^S`TK%T6)*Jql=~OXmgnX*`{$ePmKN5xTb^f)Q^DtB_XM8qnLqv1 z_qqJR7lMvltdE@i~ZfkNrG zc;}m$#nVa+HPQl0Pd%KTJbM{uh2A-r%iEKKBDUJp`ZYX_i+XA_^~kn4LfwBIFWc?C zS}mXK=O545xy8TnrQ!ndxHnzmYt+3VuW7fla_Gv)?`E@PSvm*5z-z`P@GL z-jy>qtVCX!9Xq$^=bNiqvtHf&=`6H0Ry(-#T1tOleVovaiN=-wXWlhCy?t}$EKk)e z`EcHCSmUA_B)m`7F3zrS2I4qCUY-9EQt;gVN}j-8sf zGr)u;u7FKzrn1VKmbY3ze=|N{apr{nz{wqr z%^K7Ecf`H?=3i7l#qwt1&gDn+CeE{oDRl~6wto5BeXW-_M^?2q>70^Wa^->+|7;hG#TEW2h z>gFSU(KmI0UuyfFC*FG*e*3(ebMC9ivvFtlY9@6xu`UV!d{jAXEz8mZr9uT|x#qX? zi^{_t=c_Zv>&>!nQLJIHO)FM?WYbIOagtZpO+IrfE&0N-^xv=l{J#kRw>J<; literal 0 HcmV?d00001 diff --git a/m5stack/fs/system/cardputeradv/ico/x.jpeg b/m5stack/fs/system/cardputeradv/ico/x.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..8f23f004afa1309b21bde410ab46eecc9c022a5b GIT binary patch literal 1185 zcmex=m16lu;K18qCDV%)-bDm0@IJW?>Z+VpCKC8ek|Qs-o&x`2Q9I56}uG zL1sY)dxjU)#pT%>?rz<NLviiqBpi>_84lN$nrMZ*@cvDszD z|G85txASeqS)DU|-E3#Ae|z}$Oj&r<oUx+ww%eymF`6H-nNA?ki#2bx-AI%rbf_n)uhJmQ(bhPHb6=&9kMZQ7wlx zt`%;(HRq~JX{cfELjPrQ>Wy)~9_{)h^=7y7>uVFfJ`eSO`%hnd;!6>T&9xK7RMb<~=z{~0E>u0C<`LZEBWk>T=)GN z7B(;Uv`65SKQcE@@6OBI(4lfgUu9d8VuO+WqL1_4KPBJZ`6cs0xT|ic*49_L+a`$R zY%HC`$riEU{o8GFNdmsb9y6|a8l3W2mKuBa-pei3d|lU*zxPe~n%lQ&jU&(XExZb| zPDc9td39YvG&sU%MK;&MVC{ctE_*b3Q~QjqT$Nc_=2<%&Ve%@vc|CFQ-nq=TUl`PN zIwc1G!qf>c0Q=^rZDL!IX)+$)LGRI{KXXC#vDcka0)^^*Ir{9>o@ceX%u9MF` z|2$&Xmlt_eQDH;Z`K@_%&$NU5JSXjRn${Sh$Kf&gQ&O+Ye}=p-UJL!!s+t96u3i~y zI_-#bEd;jt(8VcwHK%?SKu$$uhi#|zww5ZdhGGv zFQ%-1T-7&E*uGji&Rf67=$hw$hN7*?r$c*U+6-Q8+h zFMad&=$B|q3CmggpTdjupz^b%Uy5?U6esi~={f3@=oUAtQ{D}!0&ELX$P4K4=`%-f-0 zpfOQVV`h_3XJ_Y(88be8GBPsCPI(r-e)`bm!{AX$0=`}K4~9e+I2nc#;0HZwyyWsTYl|Ab$IWAOGh6p-0qiX j8})U+mDk40O)EN<6u#s3Il;0g&|B+Rm!P{W>;IboP4l{K literal 0 HcmV?d00001 diff --git a/m5stack/fs/system/cardputeradv/ico/y.jpeg b/m5stack/fs/system/cardputeradv/ico/y.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..89472d0723d6bb8b2657a2e65e3e200281f88b8b GIT binary patch literal 1246 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 literal 0 HcmV?d00001 diff --git a/m5stack/fs/system/cardputeradv/left.jpeg b/m5stack/fs/system/cardputeradv/left.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..8ba2494dbc0f68f173f7b71b742156339af2fbde GIT binary patch literal 733 zcmex=AfY;L=Pi%&d%T4D1|&Lc$`VhR%hJKxLvp zlaVzuFmP}Jg%t!Dn3#bEz%?^4voJETvI((sFbFCO|G&k+1C(MCWENzwXSmiL80#AP zscFqiP2Oy~gvhm%9GOm>+AH;`uR!eE!=C>PUiVDqj-OY8H?Y#Qe>BVJ#{_VP6&BAklVSi8X(O0V09hz^?Pp#Q`vS#J#)-%^G zKlJ!m#mTOW@oOM2I}e_4!9#!h4BS>AU7rvB^B{=2DK=;+eLF4slARP+`>ZvYo#Ct=j(N8v?#}`&0#YZ}pAX zS6VY~>9yL9g#MelfhXz&8T#k6rN!>)>DWBy$%BUv-}~tq@7!lsd1lVBd#i6}g&qqC zc^m4%#n!s8sq0zmgKZlV4C|k>Y+ZkS%bAn@S8Hum=RSS4e!;6(r*8Uq9n5*eGSBbC bl23w@7Ektg#I7>y`z)8>?GYV6|K9`v1*G!h literal 0 HcmV?d00001 diff --git a/m5stack/fs/system/cardputeradv/right.jpeg b/m5stack/fs/system/cardputeradv/right.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..712600f7ed52030ab988e57b52575401dac1af94 GIT binary patch literal 685 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 MHvKjD*Zlt`0E@GxXaE2J literal 0 HcmV?d00001 diff --git a/m5stack/fs/system/cardputeradv/setting/general.jpeg b/m5stack/fs/system/cardputeradv/setting/general.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..888f58289a6926e558ff8a8d5ed0925e0032284b GIT binary patch literal 1112 zcmex=*j)zs0}< zlwuNO7G$tz*qGKVDJVu#&p^9!?+PVqbqbXg)g)!?Mb8@@dnD!prE7#5XG z+9~le=0wW8(CVx2A}2jwdTWi#!SqVwRWkGJ%Syu7_~-vD3v)Ob=g?GV;QXF#^@oJQ zZ@Js}ZmtjQTc@yS>SS@1O|H!LUXyC)n_oG0Y32sc9VZL-F070cnlW?D%4wI*L~(b& zTK(cwifJpSXKt!^&!Q79XJ`60Rn)LoZ<`Zl&wlEzN#Kjhc6U?3pSApo7X$Otws0xG zdgaKhSsv+c%Gi9O?oE*HyLECJQ`^F&Lls?4YEC-fVsLn4V6#|9!nBBzSv|?d_8v?5 zZTdT&#m)Wl)%E=HjX&<)I?Z$Lv0+tZ?u30JXpT(Of@i0sCN0lxBY zg}#D|X7{8S7SeG2Q1#>iRbAe!!b)fp;2g zS8uj_y2T=F$8FW6YlF`eOZ6mo8M=Mq2s2!p92P0JHal?TO+n$S*>(2Le}xwR$%y~> z)$F&}{fYkb_kPtW)zvIcH+xnxbIo_*kE)zI<1|n0ZD%>uYq)(^w6)c(n|a(f93_Lo zA|m+91y^tu9=)Y-p@RKKTbD%gyu!I=MN7BYYi8;B-F^0_92TElV3sx;T`)0Ku(}|-Y|Gd`z zt$JCI=+;xA3`BhvNy!o?lCvd_ZCslzac!&AwXG{Ut}RjmgR13|{>eDZ-L$^)QqcbY FHv#o`nce^Z literal 0 HcmV?d00001 diff --git a/m5stack/fs/system/cardputeradv/setting/general/disable.jpeg b/m5stack/fs/system/cardputeradv/setting/general/disable.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..39d266e5d15fb546568f3915e16a8a5c51d4a246 GIT binary patch literal 546 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 a3YkfkS^#V4TKNMMx#Uc>TdT{^D*gsFIZ&wp literal 0 HcmV?d00001 diff --git a/m5stack/fs/system/cardputeradv/setting/wlan/input_default.jpeg b/m5stack/fs/system/cardputeradv/setting/wlan/input_default.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..d1e197a52d96e3d80585e232963e12f52d148c7e GIT binary patch literal 6673 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 literal 0 HcmV?d00001 diff --git a/m5stack/fs/system/cardputeradv/setting/wlan/input_psk.jpeg b/m5stack/fs/system/cardputeradv/setting/wlan/input_psk.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..00b91a712ea53b5c457771a59f3e3ea48f946184 GIT binary patch literal 6725 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 M4Idjirk&0F8#~dD1poj5 literal 0 HcmV?d00001 diff --git a/m5stack/fs/system/cardputeradv/setting/wlan/input_server.jpeg b/m5stack/fs/system/cardputeradv/setting/wlan/input_server.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..9908380d6c8270a96dc628fd4d68cf5b1394dd23 GIT binary patch literal 6546 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 literal 0 HcmV?d00001 diff --git a/m5stack/fs/system/cardputeradv/setting/wlan/input_ssid.jpeg b/m5stack/fs/system/cardputeradv/setting/wlan/input_ssid.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..b15802c5c8151104e5d45bfa7d0647591cae4d4d GIT binary patch literal 6751 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 JMJ(r2e*?vV1-AeI literal 0 HcmV?d00001 diff --git a/m5stack/fs/system/cardputeradv/setting/wlan/submit_select.jpeg b/m5stack/fs/system/cardputeradv/setting/wlan/submit_select.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..19bf1b0e4afc29de4b11cd5c7d65890093b539c0 GIT binary patch literal 2367 zcmbtVXI#@;68$HkqhcUZR0vW;jDRRa#Ra8E3q^{QARxrh!-^7+t|%%JKuRFOf(n9Y z=q(fpzGnzX2azuE(E@}bfzZpwEpPYxJM%jq?#!7p_uenFKe+z`IBaTYVhDgh0080! zus;Imb5DN_zfAs`1Gjts695$eB!Ni?NDcr)K@cctp9UP~UJ?ueAi)0s769=J^6>I; ztICG~5Dyp(2JwJ+|1AK4Aw0YQA3s#|gt`Ds*Gf!W&e}bIE0y6snXCPOBYqH=hmThf zA|S-ABcQ+LfPbYR+<)N$3;}VipnUwIVkgwC1Yk$xbd%(JelG710|&SgFcbm>t^h2i zBHN|o%ZO(D?k<5c6zMcqSZK3I(!7o$tLGRH?9g5sJ*GDUlvnVPD0Hms!C-=Co6Fc3 zCE3@@e{lxgH_%tfN?#fcpf+WSTtZ5fPJ0$JUkl8LAC?8n%BhcadU!-4Bg_gvJj;D` zO}NN95Boehwcr+D0G8898iMUjsJ7&rdSD5di&W2L0_J$YpRD_cC@>n@_)<`&LDKBE zM9-tjCL_`lXM-D%=ArJ*+YST4>_P|1gq!KSiLzPC^(-GRk|MS0&56p*?iz|iY_qzC z?K`6=66vjPN|LxulHx+b{27h-m@KfF(vY=+^_6DaOUoO@j@}C|89#!AMisp?yh0Tl zbBV+tZ^BG4@`)1H2}e=VkdBsgc}h>72Oi-vWp8pp)$x56I>I~nhX zG*;E7>nc1tBg%bKGs2YyKS_*tk7D#M~!rfu(+#L5V>ixuiVJ?)bzPE^Ztu# zZ-)1*Vy=8iYrzL>pWw|(62~%c<7eYLoLiO!A@2z-lr^O)>${<6 zwY(45hOQ1H1kccWi!{IE<2(0Y%DwlG*^bYZ@VTUA6u|0(8c&)v(8Wrfl2?Sw4CHpa zlICgnI##rHW_J9{kB!B#ZB9~n!1G6fQh3AP+pf5+3yWASMGPlg(=L3@37%-Mcm%V^*|)QqXjf6-*owYI8;sQVp%UJU2r{0PRx4eK8_2# zFlJzQJoJ0(biUS6oP~p`magq1Qav>x1gSV8walA~bCI`c6LX0{*uinQf%ayKUO7y~ zSNhtP^;(Ugy(YPoU_IjlWwvtQVix7N_W0BDY>AC+Y5!K(hiR#SK=MeKqim^jl}!wI z^7S8!0$`sh94?R^$QPMME1L_EQytQ`F>ylH&qn4ndKZxs`Z`X2^&c1Z4XHnNu*sVu z^Hgh3!Lj}}vc;K(ZGFMadK8dtRGUfv8_HfOw2)y_Y~9Ohk2p3G!o!XH6dNLHL~K#@CqyikF4lMc)Hjc(De zUd4AwP4LfJR9jftN$mos&f%x#kDrbuoftS$QZCZ}Is~0@iLk*C)8f=AZ9Xr`!+gIn zuZw!$xWjQeC#K~5T~l#*=rBh83rc5@k!$)h{0g?UYC+_;(0TGO2jVW_<~n}g0IoN+ zM>jHrS7Qhmspb_m0!DJ;UrZLS?oU64&I}L|((BG4zPY*vBPv7_A@hfI`6uKFg=RDw zt=)_ZOB*rxMG|R~M%z`2eMTaYVq+8Xm6&2;oY=%^rF01bwS;mIZs+Mt@tSXYs>moS ztq5UGYq~flq@7UV)Y!2xHBZ}2?+~AiqHe!<5Rl4}nm1G%ZyaF2zjV>sIU?pEn|K@3 z|nv$vvS%4W0e(>H^{sal&$jft)+ zlFj$lv$YZ_>XRk8GWYfi9*(x_DBrU1ef+at?nK z^&wq-beFln+0z-ti*cU4V$SaDZi?o~Y+Gq_7`L~VrIi{P`rW%C`ZA`n(YZY{(4m>f zTgLLcMsK!uFj5xGr7P=Ei&3>xp^24Ij7|6e4^gJiL>oV(z#X@~A@%3B`OoG_))OD^ z9r3bBP|-c5Cu&<&kR5HE8f}{s0e}>^exdY(ae_;^XQf!I9+&}#5x}rvZ(6Fj89z+L z4?ctiuP(}YT;2+=y|d9a%YSD~(<2BGY*AjD?7V$+8|mVXsrrL7=k7D;j KHVqHmANnVdgg7n$ literal 0 HcmV?d00001 diff --git a/m5stack/fs/system/cardputeradv/setting/wlan/submit_unselect.jpeg b/m5stack/fs/system/cardputeradv/setting/wlan/submit_unselect.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..5ec14b0165c61155c4c38b4a02174f3aa1ec3974 GIT binary patch literal 1263 zcmex=PKf)jbbR;ta6Egz>FtTwlv9qvp0%bJ?7#LZY7@63Zn1Ox)0Y)Zf7FGr}L3TwB zAz?$uK#@k#!inNQr6M3NqXLkqAg1Uo1|FbkOoGgU4E7Aa&+f~ge*5c}*WI0$WHWE_ zYq7A}JnW1W*s>6NW53aq#V431_aqo1qglt7{5mYn?ti;qI=G#! zcw2Z|-Ou`^A@8I%UzjZ0EZd#EQux;93$4Ku=W|XKQtmw^v|sh=N=**A5Wl&vKW>S* zRTaEq?^U)e=~rCJCu1tDmAB_?n|bhg}tP9)5F6=n6Cw`$s~Z)HbjO_sIhU+DE_w&Kb+ zI!;FBYB~QICc3O%XqGkWdP<;0_Ji$yPrBKxmhPK>_sw4U%etkuM(4gA-Fv(~>D#fk zCf}ZI4>rawwG2DEWQSpFso>1$ZF!D)!Hr8ai>Dl5^`FDneJodV)_vc|b@!FuOu4$% zK-KZX*J$>n#+buJTeW4UFB2;(S|90>cH!~Gv{<)({nV}|(Tqb+_HEjEBJs-a%3AxE zORImk);-f$m6a*^;n|Vys*0SI`lq&CS|^?}!|a0Mo;Ol|C*54}`5X7N;8)ghXS(_Q zQo9S4_eexbmc26Z_OPD&CcDaOZdG(qQ(&mGWXF{rXKxQ5ajl-hm)^}vQjrsm_RqYo z)7+N*-s<-|ol6B0#|$7y@}yRf$kc^iPM4e}ZJ9Jx)!~!KR3;9E?<@k2-_BVaxI9a| zvvzjy^;=6e<=kJT{jN^s=DS^Aracs!=^ZHcY0l>z)56-lvm&)Pq5?cQCC(i0;okOh z;r$Hr%PZe0oXA|Z#Ab%@mD5eCJr0i!3nkkHNEcY#V9j7Zck9o|Aa{mYeufRl5U5dM z0tZV&?Ew|$zy0&?*_?WQ_T=;Tlh6Nt-;-czyL;Z<weYxl^8w|_2bpiw?X%lw*u$vuds21bozFL_`TjFVN*rT`BTH4Ss7WoV-ccQ1 z0TV)&YRqE_n!u?t?*N0R-Hl^xvo7p0$+?}j@9N#jZH+E> literal 0 HcmV?d00001 diff --git a/m5stack/fs/system/cardputeradv/sidebar/Aa.jpeg b/m5stack/fs/system/cardputeradv/sidebar/Aa.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..b5a7440d9eb7dd0158e5d8f8b7ac8db0800d891a GIT binary patch literal 883 zcmex=P#F!bC7?=bZ znFSgDA7Ky!IgSAZuyX*#6$KcWn3$LufCvg$Seedf8do zIJtmQ3W5;b2x(>(CRRo^26iD1K_%h;w-|VUQcQx(f(-Tyv#Qr$E3Y@X{=&=TEo0ub zJG*&Xw|prHQ#sFKzcAaY*?ZlI&Cf5iGlcA15+-5Drc+$IT&O;PU2u6K)2Fx1zrrTf z^t$Nl6qBc0&0+aqLifk(InE30=V{LV@$0MD@)wiK**5!maWlU7b**(?bz}+K zC0^HWYd-z;G+Z2(zQv5&Na==Hx~q?U3Wv&sH;lL5x~~7$etl*0mOPDDv#!SJXni=u z8}igq;b&WpmIZ&}ly}i{mZ#h~B5|?d#FD_>e=Q@tc0UgOIS*1KF6s`g529bGlQWbHFM{j9Q8uPfMXjh|LblUd4YC;PoewO7sZY0um`ncg3} zPpMSut={_DB_{Na_08%(FSFMExqeRPN>s7}%MmWeUm`7K?oSr?@IGT#Nt~1>x8FX( zt;{GYwCw#x_3L5h;xd)0C9mhaVJX;gNHs?7OoX&%xcsf-ic`LYOrMyP#F!bC7?=bZ znFSgDA7PLHI+7XaP9(s>&cO1#6R&3cSY_aJCg!x~rTNRgb;d4PELy9X#g@qtBVBWl z>zIqN_J0O_DbHVWyz&AXf7Wspoecb>Ys#&B&Wg!HerddaSZbsrkNL!VM;Q!!3-$JW zVpH1}cVv5wWbv|MF};w2gye5blNx?qFgI;uT{KZ5E-fT^?bb%MBX=)*s9)1G>$)nv z_++q5ie;7x^DGIQrQD^8Rp);i-(&l}xaLU1(q{~!md6$2p3bhPB`&*KDF0Yf*Sp95SvCQmDwOwu>()QbYKJfXxjr{+c07(DGVE_OC literal 0 HcmV?d00001 diff --git a/m5stack/fs/system/cardputeradv/sidebar/alt.jpeg b/m5stack/fs/system/cardputeradv/sidebar/alt.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..23d1c5a2b7400561a1ff1f725a2cd2ebd9f22802 GIT binary patch literal 897 zcmex=P#F!bC7?=bZ znFSgDA7PLHI+7XaP9(s_&cO+kkr!ZKVusoZk!4|JW@BgKU|{4F76U4fWCSTd(#XKT z#=^nL1(Z?|WME`vU}OOjP{6{-#LUVj%pfQv%FZDors(+p76T7ZHOX@_ z1QYX@FqM@(#?7;;lrH{NjGmpn?_YG?`ld47$1>7bUs!(r>wqVH<2G`V@Rh zIcfc1@ipz3#`({=CN*>0&5pdoFu^&hGyeB~hM(o--{$^%pAoq)OgnV;J0UU4tIl4h z-bhWcT&y}RSm*Ekjq4h(Wn8`0I`8(K?nzO%ELPl-Dq2`Ff0chzGV7nfH1&T4NB=Xp zzZH6OtEV;WZ|jpY%TMb#xkf*&-W6e@({^==)i#!s&294O8kIMd2Ru?k(&!DFHwfI*59O3xo(sEI zoO$S{?_7^1&Z=K0S!GL|N?uacd8EHGG~vv8ckKsR|E^wI`|M;C^P9zwHWW0ky!hkD rd8TPG5Ao=6P>;E?aZg)yx literal 0 HcmV?d00001 diff --git a/m5stack/fs/system/cardputeradv/sidebar/alt0.jpeg b/m5stack/fs/system/cardputeradv/sidebar/alt0.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..5b03c2a8b6f21c61444f7deb140ea8909bc0c5e3 GIT binary patch literal 641 zcmex=P#F!bC7?=bZ znFSgDA7PLLI+BT*0U5AzFtf1(rQ`(|n3$Ns_ClpuSee-vnb;XPLUh#`Bed*$(Ynh=wQP4rJ&rD3~YO(sZ?3rIfdhM=-bD35q zWNx_pYTLfq$#V^E>24L^-1aZo_Wqnd8}6?TwOA6`cKY&{y1if2BOgUDeR{&RHc)TR zulYxE7{oG{@&7V3*7&#Jj&b;5X*QOBQ4c0BS6y=ZfD1p%+bMm)Y|2|cA2Ty{dr>m` z!1~vGt4(j~yn1yraau@F%#9V>D-Sw-OKM8osPQ%0Ms4l&FKoqYx|ewFTlnimiCJHG z%B5Dr8PDb8O6`}QV9r)adV1;KP#F!bC7?=bZ znFSgDA7Ky!IgSAZu(JWhmMV*|>l* z>Vgc6ER4*|%&ah3CPro!RyGDfAz=|kb`B*`6;(sy|F;-;m>Gc%VHRYtXISI*Iqd%A zwa!~Vx%G*9B^5_pS(5gy(=lX&~j1@kXT zZ#A9XQY*7PojGvQrpekhb8F|FlXu^`$Zz_pDH++%OlGB*S!RYGKdYb9D!NQv;}B=U z%!a-~7sHLGBW(qiyUJ}I!|m&5T?4uklM|z7uGYHk>~-zH zgx=-r15U-o>K(0_#JkGu*^Bf;?dG?H)a{E4Pe*959=iIhTYUE0-sGFJ{^%|+Tdcfb zcu`1War&-lZ}Re7?fe}|ma6gYJ=;9~r0j zyti~OnRL2y2AhWL8pD4K3Bn$~*xTRvv`>qlre`lH@7AfZ?p^8AoR!CateHN|yz#^Q z{7b&mi~KLHny-4*ojYG6YnnqEU-x&;$!SkcFEBX3Eo*b>&xe1Cte(>@6bdbto849% ceY>^0FM?;W#(J5;W2S2^_gOq#9RL3&0Hl5rr2qf` literal 0 HcmV?d00001 diff --git a/m5stack/fs/system/cardputeradv/sidebar/ctrl0.jpeg b/m5stack/fs/system/cardputeradv/sidebar/ctrl0.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..1e02d87e1e723e82797e789798d175b1ccba9809 GIT binary patch literal 653 zcmex=P#F!bC7?=bZ znFSgDA7Ky&JB|Svu(PoOCFBJdn3$Nswn8OYSee-vnb;XPL`B4a3PeDDL<9pBe+VJxMHqXCpd;YP+_QGMt-Ql6?o?e+}vhVs% z^pmzt+FMbm?wD{rj-uBp{EU~3qDZs$$22=j! zpEfbC<{#0ywXgTg-J;H%9aAhW9GoER8~Nt1^W-d{ZvkR{ea%*SrP4QMCK;NZfBnSc z;UvWpo#{Kv*Uj;LH2rAi<`s=KHfik%i`q(DPq?gKP=EE8dhoP#F!bC7?=bZ znFSgDA7PLHIhC0K8L+c*Z~|o%1sIr^n1QlPFliQ6W;RA9b_Na+Q9)rPkY=D(kWY~S z8z(C}P)0@&s14*~xD+!BBNHnd1G~`wTMRtRKvM;o1sUuaUSB^ZZyEZ_SA6r=-+LwO zW^A8(pjX$gAwDSl48z9A82>+;XTI&%3*E4uX=2Xl(^1Jvp(|<?n$2W$FI8MYre0^`O;(%W_vdln_asXqB9#5%ixSO@lFqQ77`G-Q)=o z%*`yvvwI%@UA6lB5A(}+cAompXvTMC`;7K5!-Un#{8+YZe;>8=)&WOfCzFU1vNL|^ zpL+VXY+A0`mAS5$V^t>jXRSK>@zsBZ`K9p*N6+~E+#ft~-l26zle7L^nVen0?_6+Q zx9Iuj&c2xbO|edYeDc-b)bEyP&aB$`!zroQu{1H}=b!n`OP47cu}WQdx&ESw%$2AL z$q9dsehQ0N6w@nPs8$#nxa!W1$J_rNt=q-7epj~UH@9WyFaB6#qiobY%kst^fgfw0 zm79M46n%eA$A(?v^JCi`n({U-Tg-C7tgR$1E!CrHzQK;d-EZ4x-xoi&?P8>w1b-8g zvX18>9S;e88=*)48CaSQRyxhnnR%q>k>uB&4tI6W1)Z6-+`ipqw=C4voznM<` zUb=O{QND9p59YXQrEp&r-N?Ku>xHiJgvOLRHOCIcZg0;un;fzI+{IU)#ND< literal 0 HcmV?d00001 diff --git a/m5stack/fs/system/cardputeradv/sidebar/fn0.jpeg b/m5stack/fs/system/cardputeradv/sidebar/fn0.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..772f5c5b40189ac4322f6cc880abc8216a21bf8e GIT binary patch literal 654 zcmex=P#F!bC7?=bZ znFSgDA7Ky&JB|Svu(7fOCFBGcn3x%uff`|QEUe6I>#!vcPSbY8U5 zn(d*YMuD6=e!k0OTOBb^==9P<6<%6Jk~gL#hyE*2J2{*lLPHIL0x*W4&#JsuZ% z->dM|(RkN@%@g81Rz~h!`0_-xXE9T~pR@0Mr&Ej{b}-r;Rct&a7Q8}XaoT*Z%h7BP z+GeZ8hAg?J9%B*Az#I~t7rkVf^GnYJMW*!=WfeCxd+iBp`JLnRLsdxNsme!QQx>*A z$K8)E;ypgwH@*C7Df9UW1-l%LxX;!0UKMFmjEksFR&KoUyl+{O&8c=Sfv#|cGu-ov zj&UfMta*2S^1U@X@17_)zVT{A8RLJ3vkB6rsTGqrd>Ci7oqjq)tn>C-6}MHAocbHj zMnO;bi8t^& j%Q(6D^}$J=8bZCAf<5j#U0V-4yi<6b@wiNZ{r{T)08q&@ literal 0 HcmV?d00001 diff --git a/m5stack/fs/system/cardputeradv/sidebar/opt.jpeg b/m5stack/fs/system/cardputeradv/sidebar/opt.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..9eefe359256114458d6f97faee18ad4528962a37 GIT binary patch literal 782 zcmex=P#F!bC7?=bZ znFSgDA7Ky&JB|Svu(NRhB@_i1n3$ND8Gr~1SXi0anAjN@IXFdxMa4mSfxd*xLKLyF za{?vg1c7>)8JL0ohXQ66CRPSUHg;hVq5roScz`lYg3N*p_6##`?{iwUHMTcved(%q zXSdevz5Au#I*0d|a?~2_fL!h?Nq;?@R!q5ME#0-XV4JqXcKhQ3-5iq*|8iNY7(UlG zTw&+sY;>{Vnl5jV=X(CHy7P~pFcZ3+n)olzTx(9pu^pRt&DpUugKKwA(;1C-9@?s> zc;4AaOujHfI$vW1d2G+eu1;^Cs>j9lI!7kR9WG}M<=QB^Po-~Fx_5HxM!wLJ_w&E@ zwZ6K~wm#}3*Y33qyREH$mTOo4I<@VL#MZpyR#Wp1yqy)5uqMhnrI;b>pr*jhXYFqe zt0XcvoHJVedgk-kmwD1pCfcT(9iC)rTWqC&p{u1wRg_!VKEugJ_|Eaoo{w|>Gt4hr XB+|K9`vU*-i6 literal 0 HcmV?d00001 diff --git a/m5stack/fs/system/cardputeradv/sidebar/opt0.jpeg b/m5stack/fs/system/cardputeradv/sidebar/opt0.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..21b1eb09172a7b49c978ff56217c3ee06bf49674 GIT binary patch literal 656 zcmex=P#F!bC7?=bZ znFSgDA7Ky&JB|Svu(7iOC1eE{n3%zq!sJ-ln3!4E85lW4fYKr$AEE+~s34~3Ee0ND zMxcevf(-Ty)1&^JR_@NYeCut*y!NY>(;Z*`xvBl?-rPk!OFy6O-(h;-acOGCq~Fqd z=i`unl!6Aadc literal 0 HcmV?d00001 diff --git a/m5stack/fs/system/cardputeradv/statusbar/battery/black.jpeg b/m5stack/fs/system/cardputeradv/statusbar/battery/black.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..1872066ef42bf3d5695ae8f5cc5883d4b4a3d31f GIT binary patch literal 1108 zcmex=ObeS2I7?=bZ znFSgDA7Ky&JB|Sfu(NV-0VUJ~7#P?Y8JL(DS%KaF0Y)Zf7FITP4nZLXPDNp5BS)t| zkO7Pg%uodoWem(LY^*>D9YK%|R(2L{zCl0J!FW`$A{uaK+BUQtnT)B1I@zGXXXj8igyzp+;FYU!#Xt}B{< zwN;i*^RhMPWSKSLZQMpjOZGL%M-M+q7PQzs$+#fp!m@&0bsQgiZyi2r%X)WWmAzTX z)qBbXFJ9bTZn-nLGV7OaSlo)=vS%~3wiW#|@)cd2w)ok~BG*c}kDHP>^e%>kgx?JC zEe`)Jwmvh2TU1V&@wfQdyAiXN%dOov|7?)w&E(`O1rts#=X?43>E-^aUp6v^ZoWdy zJ1$$WujL8nP=6BbDxIl)WKLI)ka)XEb4%UYtp_Ep{jd$)tG4j)D!;gGiPlBmYV}uo zO*Bj9D-iq7P^2T#FAXnV!CWO+;dwm1(P#X|hUUu|hz;`?iY4<0hZVzV3hexB5263rn_gJpJrmbYbG|xA6tf=d8|Oeri&dy7$t<=iK&bn?C-}5c_HE!7V4c#a)!%Humf; ze>=s~>4#QFBUkd2BZgbJ%Q<%Xh&ZN9Q~xNuq-f5*1B`FtRR#GKpMR38V|Zwx8_)fG z&1U}Phs`^>&b<^U-KNWW>Jro0ODVorE-kHl^UAV7FZGVc9;;>c?_`UsrY!lY7ObeS2I7?=bZ znFSgDA7PMSU|GD-prj4aFyKnRm&WnyMwV`S&x6cl0*7EuQ2W@KPu zMrZ}Hm|5A_Ie;?Sf%TyKH zoxBON8y}r*I-H_*P=2QHOo>yUbtWx-B;vBl`R%X3m%VQjAI)oecd%N1wpG?U<%*Jm zyO-DHMdgR>-rE|JJ+v!R*Qrk-<`kwbD-Lr#qK*5*G$f_S-E+BPR-53ACrM6Th{&{$L(s8rrr*1zg;(Igo?AxuaN%`52eoxgB z&5Ec#;dcMC=OJsi;%Qwkt*30X3Sa)GJ7(*Ls@1G-s;gziDqI~*H#D`|SVupy3=H%Z z+Gb|vbMu;*!m()6b|u#)&&WxR7Jcs9Dw^C)K9&8t|7-vH>cqLtD@PhIRqH>);(M#CIk?$hPHPBz7+-(lW7J2B)CJ3`bxl?VhN^L| zH<-!u^hoNpx((a@UH&qs`uG#=tcPAYc_s^sw+eMfwkIsSsAW>sIVr~|rq}zMxUAhx zJH|^A1=FO9lAn2bdJ9E`T(#OZx#i&zRkLMFu3n8bT(9-(+Dv{y#xS<6oJ-epo>?UH z`QPNz0;QV%?-l%#?#FVY5SASr|qg-@xFQ8!4>x#t69Px`BZPn zlvVO6Xc04c6tp};!gc$to~barVHY-Y@s|^Gm|1Ux nsn$-}`-8Q!NziMq)73ObeS2I7?=bZ znFSgDA7Ky&I+B?I8L%^OZ~-MW1Q;0D85o&an3#dy00BlOW)@a9b`AzXA&H3TSA`Xg z9GwDzDkT^hm{5&iWMyXO0Lo|z0yVL)vM{njOkiLT77_tkEXu&ds;JDuCMK?AXl&-x z`2Q9I56~7SL1sY)dxp#JLMM6^)ki-~X}z6wM_ce`*@ffA%fdcq-=XrBp_w>GSi!P6CzDDYg7R>#Af!F_zx{h7WlwbcEHmxlS?vc8$t^DrB zx`*lgSGKi9r5c@_eNkkmgU9u|XR9u3@|P*8YUlhmeOVW|)1iq|=g!Ny{>qK= zwe=x?_kKG3(9(ayOP3X2I?p__IXZ8XXnb8#eMg_m<&@)EWy_4eb-zBgH+_1YbN##a z@7j;I{M1`>GJZvbX@;Fx13#CcEyI<+hl&nAJL$jvh47S`5E=Xas%Lv$p17+A?MOE{ zX`Ne2lcl4k#q^8q*$O8UHI_Wxbo67O@(k-K)f~3J{;m97{j~j1(fq^< z>KZ@8tn1~ro@whmUgx*E;Nr_krCJ|+p1fPA#Hnh)xy>=sGJR7@w%k!cgH=y29sB61 zByqQ;nxXUK3r35;ry>UzoK{k5xm>Dc8SML7B1!N0*ZAJ=tW(8jtF6vRtgkIjykd~1 z7w&iDPQB4W>&s&DW;YZ{*5tLWxSqnPcIsnf-{gcB(yPvYwhk0<@;P+%pw}vfbB^K} zbB(8O^qV2TlQd(QnAtnQq8S=ZCw2TEse3%NtZ86AvG*Asx z%Q_hr*uOaZuP^c6nc{N8_J92ee;L?)RRw}hP3l(^4w+~WmAxx8D!i(;up%Se+iYSL f>!Yj7V`oj>dUnb3w`WhWy9rjhHPx){|9=wzt!PVS literal 0 HcmV?d00001 diff --git a/m5stack/fs/system/cardputeradv/statusbar/battery/green_charge.jpeg b/m5stack/fs/system/cardputeradv/statusbar/battery/green_charge.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..f0f50e9bdeb69c23e8c5d7347538b66147fe4c25 GIT binary patch literal 1044 zcmex=ObeS2I7?=bZ znFSgDA7PLHIhC0K39xf8uyO%qR0S9q*clj^SwVgQ14brh7FIT(yr7VXM8x!~!pa~6 z7#WyQ^fNHBGP834Wi$mD7+9H^*ce%1rh^O+Vigu)6J=l*6jM|dS28wpYW#nTfrlAr ziXgKfgFVB`?^-98TH3Guw8`o1);!UUKf5!MXI|E>-1^olho!W3UB2h$6`S5l8icxU z%2#=F-u3ix?K!S0(P}e=KL+^ye#5r>lkhb8GOxONhsdzqn#Xwar%wEy6JD_S_^W7V zU89*PXJ2&1EKtdQx4FtU)+Ma$hx^6``60^ zKVR-;yMNAVu5xF|*=fs7uK8VF7j{!|?xM73d5b=4n=1)_&N^dS1)G}XE;Psz){mPxBlh#pc}$Qn}9 z@bc&M6ASNH^?bFH2vM(C8+}wOWY?*(S$q992CVY;+?ubh9HO+@@O;ks8NPO!PlP=} z!@^t-3KUG7$9ZM~15>MJcFttIRgWc~#+xoPW}6V&ab>1Taii;%=8gXu+&^97nzHoY zC!u`4&BwF8F^Gl=sfAor%A9n){HF40Su@?EFH3)#vZicZ)Mq(sy}z}V=fo%nrd_(y za~fJLnR$=i)s84y*)c0aNq0;B8O1ZZR%C5hG~apt^eC0H=S~-B-g@<=KTNf7v(t{w d@X*^+Wvo{02(a3^Y~iw5f~GQB9}E891OQ~Vcn1Ig literal 0 HcmV?d00001 diff --git a/m5stack/fs/system/cardputeradv/statusbar/battery/red.jpeg b/m5stack/fs/system/cardputeradv/statusbar/battery/red.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..f07f9a2494f4a67dc8a495617f0c7c64bdd5a831 GIT binary patch literal 945 zcmex=ObeS2I7?=bZ znFSgDA7Ky&JB|SfuyZhS0VT8q7#P@?7@3)wS)r1QOw25-Z0sC@3__9-)2|9EI*BV8 zIR=6ZVPs$i`V|QF&O$|~Zt;ILpH%)6_R`l`Ib3s?JlgnIw`>8LeskyS(C3pKiq5ZN~{&s%)vu_4@0>$HHSHw%}tMlY>%+w)oK;;T~%yN~H#3Y@Z7(`)yoooP>*IFvT03R}%R z#i*gLu+`r0cK(}^?I*r$*WKW=|JJ&~zJ>d4p8lhgs!Zr1K-jb0~N|GR_(1@zv1uc`H4R1{j;X8=-u}HQ266r47Ymy|MFa~**ha6T6#-n zOyO2Nzq_%LYqq^AlKE8dVD=QD$sM&4Wt(`ygu48q*2u0}QY=0}jzxnX$sLSar4uYq z$8P#2xA66}iz^FHitM!NEm8X@y~Co&^9)n&gs`C4FEgc%Z*=j|+F7!3caX<2b0!;h zXDeGJ%av|i>Q*TM$8|gco*e60Aiv|Vbk6!Y$$PdTw7iGnnoS)#laBmTD(MTJzJtNK`=pEWgy-*MzF2l|_`{}q*hD8Z zy4%UJ*J!OU+gZU#_ok~wj51yt;e9@(SATudwk=BT{LfH1RqldjwHFW1%tZ%R=}J%E zYU07l_@~Ey;u-s!eR)2K{~6pHelxK97@eEb#ndw8snDDa2K)+%j8E(>W}2HyJ-uW0 jcy`(LRTtuxeCqknuwv=%{MS2^npObeS2I7?=bZ znFSgDA7PLHI+7XaP6Xg!WM$_9%BTr2FtRW)GP45pLS&hlSXkK@**O>lg+wGHre75n zR|c8D$iT#m&<ZKqG{NMFd5~7}&)Xm6TPCIUF1R z-(uhaI)X`%S&+e=;pOq*SDbM{Z~sM}xM!aCWwraOwYN@(Us@e?>6ce*oz0S~H_+xxUQY$`ejEk{$kpPhC*EFDqWjGWUDS`sXEeSAN%pcSqMxE4*H- zyjR!ocX-*RTOzylwk(+TGJfKEn`>LDHZqj=#CP;8mGla|xAe5Rf?I*GnZh-Hr9=+> zf~fh&^P>OC>i)6~Ti&~H{on0jt9DPVUB3LM$F#XND}`P@UG7_R`q8}-`-aRTKh{j# znsXRo{O(W5pO*GY&ja6n4*Ds0I)9I9z4aRAsWSfm8F(J=yLZP# z&+SuBxNNTXHgwtjHmh{(klyZ=V!i>m)Ys&-FIT?$t&4AW?#vB@bIF#VdFN&RMq>Q3)fT& zJdMQ(Ibya|m&B~v<F(n> zvzYh4n-iXM(>!eUy2sTw4<6V%z5Mp^b^HDHhUD!lzOr(T$I`r6Q!YnZyWLxB#J5Xm z_S&8NMrkit{@hHCRaF(KjlCwb@)}qF^Vdd4=TF)j@oBlN+}FSx0Sfb%MJcSk7*{vp z($<$|MQ4KkC<)Cq4W6cIAoRde$td7wV)AGINrm@idCcm$SAMn$TJw^tRd18FYuM88 z?ro}$CM?!D23YW?i+%b&W<`kE4p-VDcyZ!%70NIFz Af&c&j literal 0 HcmV?d00001 diff --git a/m5stack/fs/system/cardputeradv/statusbar/cloud/empty.jpeg b/m5stack/fs/system/cardputeradv/statusbar/cloud/empty.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..2647f691e2759ccc43b7f7943fab170f05f1a784 GIT binary patch literal 621 zcmex=O1eh6>7?=bZ znFSgDA7Ky!IgSAtaIkX$#T5h?n3$Lu8JL)1axAPsJ{vm+r?8NSC{Tqcx<&?8cA&7F zAW#poJ_aThMrKwvAz^k=rT@1WctDbZ%z_N|442~92!7LBz3a8N$^M|n=WJX<)3op2 zUE^!8Eq!)r*lZo$Yul_P`v1nSYTW2~B{-=uD6Knx>bj17PCOwW%=2G6R!v|3@v!y0 zlP~S9Up>6AH$LVDgUZ6|@7$-b2(DY7bawe;u}yR4=={n_bI?g+g@D68y+6Cy8Cd8!G)WL(srjty$U~&eL~~?wWL*x`Qp3RENz%>aALxm zigT;}GjJFAu6E4SE4^8oDwr(pzj2Z$!hRPi59rjV+fht^a=$ E0PjV!1^@s6 literal 0 HcmV?d00001 diff --git a/m5stack/fs/system/cardputeradv/statusbar/cloud/error.jpeg b/m5stack/fs/system/cardputeradv/statusbar/cloud/error.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..1850f45ead00958056653b9de7889f350dc0f1fc GIT binary patch literal 585 zcmex=O1eh6>7?=bZ znFSgDA7Kz@U|>R?PLO^*@WGS#3Rt})B zrXT|Y3o{EhTo(hQppXcIm^ibrqNAv?in^h*TVhIT`u|%DJV1+?1epaH>=`cRyM8sh z{`~JX-LB(0%yT+Z>x8E37Imyry{)!#58qA~Ij>Egc}W*pihGv}PikffO89d#alx&U zaJw1Lw820& zj$z53OGi%Md%Dae_2J_UYo6);44-br?VMxjTP4xFeDc~!mfLoUCeH3xdh5LLz!E0s z_Kp&pIXAafeimt)C8+ni?N0UU;06B*XF6}2uH3rW`+?%KIDztDQ#r42UYAhmbxZGZ YN2$bgg-!jS9~kq+@duCHw8H;40e|hHCIA2c literal 0 HcmV?d00001 diff --git a/m5stack/fs/system/cardputeradv/statusbar/cloud/green.jpeg b/m5stack/fs/system/cardputeradv/statusbar/cloud/green.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..223732358c768b2fc40363949954a2a88882710d GIT binary patch literal 640 zcmex=O1eh6>7?=bZ znFSgDA7Kz@U|aRXq z@LcYJcV6;6CDAXvmrGtIFAkYnCAG7a`$X3@mp{joS1sMXcwO}#jpSv@l02@q{k%#F zQitLWUQ)`M`?-3P(O=_-aTBsVmNpmftS+P1jNYyBxLPLB%M1}MN-~L(u(5&| zWtAmF+Zc=>qJ@Z2A|aRw6K3Upxc7eF_x*I9_dUP!9OfJ5J3zn^Wq|@95CDLj8(=N~ z#^=*N(|5=}Bb;wD`v8yw5CCkU5Cp&iLZBdoIS7aY0QB7dCpH)dJ15t9q$B`9*v=n; zutK10|0%FSSfFg|Fb)vl6ciE`5xszrQ`0a!uU>!v(C;t*PwX&GHjeWI0Rk-FBbNVO zfu2*oLxe#g5kL^Zd<*bE&vOt80tNv1Awm|txjWk>>GZInJ3j9hN&<#Yv<2>5sshtB z{TO>W#W5vJ%~h9G+KA#Rjr3?~jV}gsY%lkPw0apVC#1)w^8{$M`FF~>iq7<33>>Jg zk(i%z@aUjfY7A)SPfp(9#1zAiJMPWkgo%2&+k*%EfsI?R4cX-Y-iZ)ZMpEKOqP;Th za=4AP=q=UX;hdIismAhwteJ*LfvkI@U_-*la4D7+$KidA_rnVE*V8lEip3+tt73*z zN1oPBnZC(Y4fSpz2@?IA@BIB^d81a+C?ZNGl1cAj2OL2!mXo9Ax4XCwi=5RnD>KSJ zkhNe|ofaC?kw&LR;})O!4Ieng40iJ?sqG8gXhc!ZQ^f_MkmL3;gSw>X{hGgYwNgGY z*q!SV(Eb+LjJSM3@9_P4CIv;f53euVYjBDIucTyWssCV56sjM;!LN&Lobn~BEg_qi zre^kTl3q91OvgmWqra9*ihxdsq$cOZg8IC%oPM9kBNhzaNZ-H@I>AFY9noF+G)&B# z)vr1nLjKV8m)L^dmVwL*)x-ps&&|3kE2$TiT_2<9*+1SGEs!72GI;fDr1=bfmJKR+ zDbo2Dsa;3Wv|s z;TL3YK~gb~O>&pQ%Sv*jXeNKkUHf8VF8?^r>@2m-gaa$Qsy~$;{G5-Cc!Gal^6%lP z|I~M9!y2SbBj4(wnGKdf3}H>sn*bSnKM~ z1kk&*%Z<;|ODs z4Qg$9Xn{|jtZX*9Y#O$T!(w!7wLTxsY1c>S&Qim>x4vDCce1PE9y$?oscrr1@M=?L z>3WIk;%Nh(UA16r4JDxt-f2IuZo9f-n;Uu>=JDcYsh>`&GP{JZS)jcpCy%qoY`FhG zm@+{$qAmL2m70`oFk5C$%6U{T;dNgIz0LGepgkdmwdTf}?zV65W?1cB|8@7E{i#*| zI)x)BhHO)4o9%K*qj)BoOiUl&B{6sy!9krX&V{)l9u|*KSWi}$u$<=VXS@@x`N>fv zQ_E=M=C$nOtiq8Q71q1-gCX3I7oOC%er%&P9a3GpL6b=AosVzLK-4Qg4fVZ~60Y>p z@9;J|gHt|BiwPinN3k+|bwRm$lPIq>`jKLTl8&hYhSgaog^>2~gi?P46s40^rUGLA zxIej%U;lIAs9*Nb|z1@DnA zNsyAc6+yRncPtpIaP9t~w_*RmX;H;t=C8$e2mO2HdlQ;HPa4OTIHo6D#{n(9z*`4N z($lpSLGiN_aAHX=%$=g%z`yC^@%lk=q%~o`P~drt-x#cI3A-Zi5)%>#;iJ zZk4+nP_%>n9L&+tP0~8e4*7H#Z#O8dfY2;3WVIq>5x=e!qtWP-n@P86B`?RH3}QN# zuyI$e8yLmpZ|gVu-a@|M1)?qM&Wfx2k9ORWV}JX3WQ{=a$zOXmjOC_yQ{pDs4m#;p nDxSgL!~PT7s_OrKR-^{wB~g`8;o?vG3WmVnAT9% literal 0 HcmV?d00001 diff --git a/m5stack/fs/system/cardputeradv/statusbar/wifi/disconnected.jpeg b/m5stack/fs/system/cardputeradv/statusbar/wifi/disconnected.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..833b16b7f982b94fe9203adc42212b7183ab62bc GIT binary patch literal 582 zcmex=O1eh6>7?=bZ znFSgDA7Ky!IgSAtFtTw0#pMMU7+IJ>mcry%n3!4F7}+^QgvEq_3Ph2lAWB#{fWn%B zKn^ztGfWpS7zBlcMMTBKl?($J6jjunUEC7WTU!6$V&GwB1iFJ+kinke(tejO^R7SL zdyRE{LXYY4DY*rXtSVofM4#u~N-7Wv(SNmOhWkRJE5A9@j5OI+IzLhW^Ty~hH*dxC z%IizM9S_Re$(^@PqH4QF$U|NxlkO7bv$5MvJ}yzozjEzA!+xWrEYU|TC5dOfI!^XK z{T7$`bw-biUzy*Omww$l7fpH4z<%m#;_A!ASG&&qXE^tH_c9O7qtiJRr~Z)cY*_QO zt*c5kb+OQ?(|%@wc@u6a$(dc6t9@>}>805M%#~4BOxyCX8EP-TCfDOgJ@Tkw$fOsASCR9ojDvGuR@h S3D`M*38~`x&!Bqx|4jg14W3T` literal 0 HcmV?d00001 diff --git a/m5stack/fs/system/cardputeradv/statusbar/wifi/empty.jpeg b/m5stack/fs/system/cardputeradv/statusbar/wifi/empty.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..02a2729382f1437b4608355753462a85b019dec2 GIT binary patch literal 620 zcmex=O1eh6>7?=bZ znFSgDA7Kz@U|?@OWS&^*dDC1Man1Q#^G)wp{mNbc z8KmN`ebaaUP^(y$p7(wCqaB<4Zu5wD`YPBo`f3~8h^#hz&o`}YeTLD?4eZMGTF0{7 zS0B0C`!M>dmD-NmMVV8cC)_nq`b~18{x**kMW^yUoDDrA>_IoSlN3RSn3!5Yq zX-uB8deQ!tTidrydKx=DERgN!(Jv`22NJSGkEEprtm)X-W>@%=qwJqKA>xusi%iJAKd4|UxyBq)i FCIA7=wb%dv literal 0 HcmV?d00001 diff --git a/m5stack/fs/system/cardputeradv/statusbar/wifi/good.jpeg b/m5stack/fs/system/cardputeradv/statusbar/wifi/good.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..2c5b10c1b3cfa3f127e85d370047857065a4e5d4 GIT binary patch literal 583 zcmex=O1eh6>7?=bZ znFSgDA7Ky!IgSAtFtTz1#bpH;n3%zK!sM7)m{{2u*+n^ofYKspniv?^fud@H42(>y z4BRj+3=AT|qKvG9LW)4cSj5DYRaDiC{@-HYVP*unfLV~ip5a>G`_T3A-d5eYY+cj* zPF{LiQ0MEgZgzI2SGP=9)LhxOvwA}>Uf)uA#CxSec#H9jgW7I)9ySScuDr2vX>X`o zvuC*o*Zk5M@e$|sXWqOTViVV7RrB;~QC8@^>#OqW*@GB2a8?U3iDg?}WV!aA!6&#% z_4%A$d#iU1z0U4l$ENL8E&N~_cGFPce%QXI`+ebp?rEMHNd;mWMu!qJQ*TLj^LhR` z@B3m(U4g5u^!7)2Ro2UlOK;t--@j_*$tjC|n*QRh+nMAp*Qs>z_~9Eir(VuYjYwI_ zv-?Q*(>r3XADq%$yU6PH#I2i_Em3@_I#parI&MqaPbQUVXLq)oeACGC<#)=#?eBU| U&$+ehhDTz++ZhEfm;b*B010QKCjbBd literal 0 HcmV?d00001 diff --git a/m5stack/fs/system/cardputeradv/statusbar/wifi/mid.jpeg b/m5stack/fs/system/cardputeradv/statusbar/wifi/mid.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..1a5293e3821b43a08b4215b57c539083de647d9a GIT binary patch literal 572 zcmex=O1eh6>7?=bZ znFSgDA7Kz@U|}*A& zt9s7Np5M3hUe(B0=Y86p$1?oNb)U~2?B*ODR+%mW8E;&gCA;p($DOT%zB;elV#L@>R0g&wZ}Vm$C0aO6RYn*%$R~o@D;KUc!@zO1eh6>7?=bZ znFSgDA7Kz@U|KKmaofBNHndyO1b{AW&WeApwvcOArU5FK}7~-MrM}(w-|VUMluO93o_U<+-!HeIN9`L`kYxUrMo0{{;0WD zf9a}Kf(q9yy$w-bD^@2(EnYM0bpO4CQbX_eUUR;?`Kq7ldU91d^ML2#&dce3X)8oc zmrcFk+7+~F&sI;l=@TLwtB>^gDV^2KkiTT{&+*R3r@|lKZqK`GB~xalVi}v?ekH5u z@XmdUEUY%%VhOxG>C}t~;+GO$u}(d3;#AashWKZH>J=XScz3FjuiWkFFWENBH~*GL zl~>j5i&!Fl+PysL>^`@kI1^|Gx None: cardputer = Cardputer_Startup() cardputer.startup(ssid, pswd, timeout) + elif board_id == M5.BOARD.M5CardputerADV: + from .cardputeradv import CardputerADV_Startup + + cardputeradv = CardputerADV_Startup() + cardputeradv.startup(ssid, pswd, timeout) elif board_id == M5.BOARD.M5NanoC6: from .nanoc6 import NanoC6_Startup diff --git a/m5stack/modules/startup/cardputeradv/__init__.py b/m5stack/modules/startup/cardputeradv/__init__.py new file mode 100644 index 00000000..44247133 --- /dev/null +++ b/m5stack/modules/startup/cardputeradv/__init__.py @@ -0,0 +1,50 @@ +# SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +from . import framework +from .apps.settings import SettingsApp +from .apps.dev import DevApp +from .apps.launcher import LauncherApp +from .apps.app_run import RunApp +from .apps.app_list import ListApp +from .apps.statusbar import StatusBarApp +from .apps.ezdata import EzDataApp +from startup import Startup +import M5 +import time + +# from .res import LOGO_IMG + + +class CardputerADV_Startup: + def __init__(self) -> None: + self._wlan = Startup() + + def startup(self, ssid: str, pswd: str, timeout: int = 60) -> None: + self._wlan.connect_network(ssid, pswd) + M5.Speaker.setVolume(60) + M5.Speaker.tone(4000, 50) + M5.Lcd.setRotation(1) + # M5.Lcd.drawImage(LOGO_IMG) + time.sleep_ms(200) + + M5.Lcd.clear(0x333333) + fw = framework.Framework() + # sidebar = SidebarApp() + setting_app = SettingsApp(None, data=self._wlan) + dev_app = DevApp(None, data=self._wlan) + launcher = LauncherApp() + run_app = RunApp(None, data=self._wlan) + list_app = ListApp(None, data=self._wlan) + ezdata_app = EzDataApp(None, data=self._wlan) + fw.install_bar(StatusBarApp(None, self._wlan)) + # fw.install_sidebar(sidebar) + fw.install_launcher(launcher) + fw.install(launcher) + fw.install(setting_app) + fw.install(dev_app) + fw.install(run_app) + fw.install(list_app) + fw.install(ezdata_app) + fw.start() diff --git a/m5stack/modules/startup/cardputeradv/app_base.py b/m5stack/modules/startup/cardputeradv/app_base.py new file mode 100644 index 00000000..51b9b6c3 --- /dev/null +++ b/m5stack/modules/startup/cardputeradv/app_base.py @@ -0,0 +1,93 @@ +# SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import asyncio + + +def generator(d): + try: + len(d) + except TypeError: + cache = [] + for i in d: + yield i + cache.append(i) + d = cache + while d: + yield from d + + +class AppSelector: + def __init__(self, apps: list) -> None: + self._apps = apps + self._id = 0 + + def prev(self): + self._id = (self._id - 1) % len(self._apps) + return self._apps[self._id] + + def next(self): + self._id = (self._id + 1) % len(self._apps) + return self._apps[self._id] + + def current(self): + return self._apps[self._id] + + def select(self, app): + self._id = self._apps.index(app) + + def index(self, id): + self._id = id % len(self._apps) + return self._apps[self._id] + + +class AppBase: + def __init__(self) -> None: + self._task = None + + def on_install(self): + pass + + def on_launch(self): + pass + + def on_view(self): + pass + + def on_ready(self): + self._task = asyncio.create_task(self.on_run()) + + async def on_run(self): + while True: + await asyncio.sleep_ms(500) + + def on_hide(self): + self._task.cancel() + + def on_exit(self): + pass + + def on_uninstall(self): + pass + + def install(self): + self.on_install() + + def start(self): + self.on_launch() + self.on_view() + self.on_ready() + + def pause(self): + self.on_hide() + + def resume(self): + self.on_ready() + + def stop(self): + self.on_hide() + self.on_exit() + + def uninstall(self): + self.on_uninstall() diff --git a/m5stack/modules/startup/cardputeradv/apps/app_list.py b/m5stack/modules/startup/cardputeradv/apps/app_list.py new file mode 100644 index 00000000..7a93b235 --- /dev/null +++ b/m5stack/modules/startup/cardputeradv/apps/app_list.py @@ -0,0 +1,284 @@ +# SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +from .. import app_base +from .. import res +import widgets +import M5 +import esp32 +import machine +import os +import sys + + +class Rectangle: + def __init__(self, x, y, w, h, color, fill_c, parent=M5.Lcd) -> None: + self._x = x + self._y = y + self._w = w + self._h = h + self._color = color + self._fill_c = fill_c + self._parent = parent + self.set_pos(self._x, self._y) + + def set_pos(self, x, y): + self._x = x + self._y = y + self._parent.fillRect(self._x, self._y, self._w, self._h, self._fill_c) + self._parent.drawRect(self._x, self._y, self._w, self._h, self._color) + + def set_color(self, color, fill_c): + self._color = color + self._fill_c = fill_c + self._parent.fillRect(self._x, self._y, self._w, self._h, self._fill_c) + self._parent.drawRect(self._x, self._y, self._w, self._h, self._color) + + +class FileList: + def __init__(self, dir, suffix=".py") -> None: + self.files = [] + for file in os.listdir(dir): + if file.endswith(suffix): + self.files.append(file) + self.files_len = len(self.files) + self.file_pos = 0 + + def __contains__(self, item): + return item in self.files + + def __getitem__(self, item): + return self.files[item] + + # def __iter__(self): + # return iter(self.files) + + # def __next__(self): + # if self.file_pos < self.files_len: + # val = self.files[self.file_pos] + # self.file_pos += 1 + # return val + # else: + # raise StopIteration() + + def __len__(self): + return self.files_len + + +class ListApp(app_base.AppBase): + # log control + DEBUG = False + + def __init__(self, icos: dict, data=None) -> None: + self._wlan = data + super().__init__() + + def on_install(self): + pass + + def on_launch(self): + self._files = FileList("apps") + self._imgs = [] + self._icos = [] + self._labels = [] + self._max_file_num = 3 if len(self._files) > 3 else len(self._files) + self._cursor_pos = 0 + self._file_pos = 0 + + def on_view(self): + M5.Lcd.fillRect(0, 16, 240, 119, 0xEEEEEF) + + if self._max_file_num > 0: + self._img0 = widgets.Image(use_sprite=False) + self._img0.set_pos(6, 22) + self._img0.set_size(228, 32) + self._img0.set_src(res.CARD_228x32_SELECT_IMG) + self._imgs.append(self._img0) + + self._ico0 = widgets.Image(use_sprite=False) + self._ico0.set_pos(9, 25) + self._ico0.set_size(26, 26) + self._icos.append(self._ico0) + + self._label0 = widgets.Label( + "", + 40, + 27, + w=182, + h=22, + fg_color=0x000000, + bg_color=0xFFFFFF, + font=res.MontserratMedium18_VLW, + ) + self._labels.append(self._label0) + + if self._max_file_num > 1: + self._img1 = widgets.Image(use_sprite=False) + self._img1.set_pos(6, 60) + self._img1.set_size(228, 32) + self._img1.set_src(res.CARD_228x32_UNSELECT_IMG) + self._imgs.append(self._img1) + + self._ico1 = widgets.Image(use_sprite=False) + self._ico1.set_pos(9, 63) + self._ico1.set_size(26, 26) + self._icos.append(self._ico1) + + self._label1 = widgets.Label( + "", + 40, + 65, + w=182, + h=22, + fg_color=0x000000, + bg_color=0xFFFFFF, + font=res.MontserratMedium18_VLW, + ) + self._labels.append(self._label1) + + if self._max_file_num > 2: + self._img2 = widgets.Image(use_sprite=False) + self._img2.set_pos(6, 98) + self._img2.set_size(228, 32) + self._img2.set_src(res.CARD_228x32_UNSELECT_IMG) + self._imgs.append(self._img2) + + self._ico2 = widgets.Image(use_sprite=False) + self._ico2.set_pos(9, 101) + self._ico2.set_size(26, 26) + self._icos.append(self._ico2) + + self._label2 = widgets.Label( + "", + 40, + 103, + w=182, + h=22, + fg_color=0x000000, + bg_color=0xFFFFFF, + font=res.MontserratMedium18_VLW, + ) + self._labels.append(self._label2) + + for label, icos, file in zip(self._labels, self._icos, self._files): + ico_name = file[0].lower() + icos.set_src(f"/system/cardputer/ico/{ico_name}.jpeg") + label.set_text(file) + + def on_ready(self): + pass + + def on_hide(self): + M5.Lcd.fillRect(32, 26, 206, 103, 0x333333) + + def on_exit(self): + del ( + self._imgs, + self._icos, + self._labels, + self._files, + ) + + def _btn_up_event_handler(self, event): + self.DEBUG and print("_btn_up_event_handler") + if self._file_pos == 0 and self._cursor_pos == 0: + M5.Speaker.tone(4500, 60) + return + + # Clear selection cursor + self._imgs[self._cursor_pos].set_src(res.CARD_228x32_UNSELECT_IMG) + + # Calculate cursor and file positions + self._file_pos -= 1 + if self._file_pos < 0: + self._file_pos = 0 + + self._cursor_pos -= 1 + if self._cursor_pos < 0: + self._cursor_pos = 0 + + # cursor img + self._imgs[self._cursor_pos].set_src(res.CARD_228x32_SELECT_IMG) + + if self._file_pos < self._cursor_pos: + for label, icos, file in zip(self._labels, self._icos, self._files): + ico_name = file[0].lower() + icos.set_src(f"/system/cardputer/ico/{ico_name}.jpeg") + label.set_text(file) + else: + for label, icos, file in zip( + self._labels, + self._icos, + self._files[ + self._file_pos - self._cursor_pos : self._file_pos + (3 - self._cursor_pos) + ], + ): + ico_name = file[0].lower() + icos.set_src(f"/system/cardputer/ico/{ico_name}.jpeg") + label.set_text(file) + + def _btn_down_event_handler(self, fw): + # Clear selection cursor + self.DEBUG and print("_btn_down_event_handler") + self.DEBUG and print("_cursor_pos:", self._cursor_pos) + self._imgs[self._cursor_pos].set_src(res.CARD_228x32_UNSELECT_IMG) + + # Calculate cursor and file positions + self._file_pos += 1 + self._cursor_pos += 1 + + max_cursor_pos = len(self._files) - 1 if len(self._files) < 3 else 2 + if self._cursor_pos > max_cursor_pos: + self._cursor_pos = max_cursor_pos + + # cursor img + self.DEBUG and print("_cursor_pos:", self._cursor_pos) + self._imgs[self._cursor_pos].set_src(res.CARD_228x32_SELECT_IMG) + + if self._file_pos >= len(self._files): + self._file_pos = len(self._files) - 1 + M5.Speaker.tone(4500, 60) + + # Show File + if self._file_pos < 3: + for label, icos, file in zip(self._labels, self._icos, self._files): + ico_name = file[0].lower() + icos.set_src(f"/system/cardputer/ico/{ico_name}.jpeg") + label.set_text(file) + else: + for label, icos, file in zip( + self._labels, self._icos, self._files[self._file_pos - 2 : self._file_pos + 1] + ): + ico_name = file[0].lower() + icos.set_src(f"/system/cardputer/ico/{ico_name}.jpeg") + label.set_text(file) + + def _btn_once_event_handler(self, event): + execfile("/".join(["apps/", self._files[self._file_pos]]), {"__name__": "__main__"}) # noqa: F821 + raise KeyboardInterrupt + + def _btn_always_event_handler(self, event): + nvs = esp32.NVS("uiflow") + nvs.set_u8("boot_option", 2) + nvs.commit() + with open("apps/" + self._files[self._file_pos], "rb") as f_src, open( + "main.py", "wb" + ) as f_dst: + while True: + chunk = f_src.read(1024) + if not chunk: + break + f_dst.write(chunk) + machine.reset() + + async def _kb_event_handler(self, event, fw): + if event.key == 182: # down key + self._btn_down_event_handler(None) + if event.key == 181: # up key + self._btn_up_event_handler(None) + + if event.key == 0x0D: # Enter key + self._btn_once_event_handler(event) + elif event.key in (ord("a"), ord("A")): + self._btn_always_event_handler(event) diff --git a/m5stack/modules/startup/cardputeradv/apps/app_run.py b/m5stack/modules/startup/cardputeradv/apps/app_run.py new file mode 100644 index 00000000..29505c03 --- /dev/null +++ b/m5stack/modules/startup/cardputeradv/apps/app_run.py @@ -0,0 +1,160 @@ +# SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +from .. import app_base +from .. import res +import widgets +import M5 +import esp32 +import machine +import sys +import os +import time + +try: + import M5Things + + _HAS_SERVER = True +except ImportError: + _HAS_SERVER = False + + +class RunApp(app_base.AppBase): + def __init__(self, icos: dict, data=None) -> None: + super().__init__() + self._enter_handler = self._handle_run_once + + def on_install(self): + pass + + def on_launch(self): + self._mtime_text, self._account_text, self._ver_text = self._get_file_info("main.py") + + def on_view(self): + M5.Lcd.fillRect(0, 16, 240, 119, 0xEEEEEF) + M5.Lcd.drawImage(res.RUN_INFO_IMG, 6, 22) + + self._name_label = widgets.Label( + "name", + 16, + 23, + w=208, + font_align=widgets.Label.LEFT_ALIGNED, + fg_color=0x000000, + bg_color=0xCDCDCD, + font=res.MontserratMedium16_VLW, + ) + self._name_label.set_text("main.py") + + self._mtime_label = widgets.Label( + "Time: 2023/5/14 12:23:43", + 16, + 46, + w=208, + font_align=widgets.Label.LEFT_ALIGNED, + fg_color=0x000000, + bg_color=0xFFFFFF, + font=res.MontserratMedium10_VLW, + ) + self._mtime_label.set_text(self._mtime_text) + + self._account_label = widgets.Label( + "Account: XXABC", + 16, + 60, + w=208, + font_align=widgets.Label.LEFT_ALIGNED, + fg_color=0x000000, + bg_color=0xFFFFFF, + font=res.MontserratMedium10_VLW, + ) + self._account_label.set_text(self._account_text) + + self._ver_label = widgets.Label( + "Ver: UIFLOW2.0 a18", + 16, + 74, + w=208, + font_align=widgets.Label.LEFT_ALIGNED, + fg_color=0x000000, + bg_color=0xFFFFFF, + font=res.MontserratMedium10_VLW, + ) + self._ver_label.set_text(self._ver_text) + + M5.Lcd.drawImage(res.RUN_ONCE_SELECT_IMG, 6, 100) + M5.Lcd.drawImage(res.RUN_ALWAYS_UNSELECT_IMG, 123, 100) + + def on_ready(self): + pass + + def on_hide(self): + self._enter_handler = self._handle_run_once + M5.Lcd.fillRect(32, 26, 206, 103, 0xEEEEEF) + + def on_exit(self): + del ( + self._name_label, + self._mtime_label, + self._account_label, + self._ver_label, + ) + + def _handle_run_once(self, fw): + execfile("main.py", {"__name__": "__main__"}) # noqa: F821 + raise KeyboardInterrupt + + def _handle_run_always(self, fw): + nvs = esp32.NVS("uiflow") + nvs.set_u8("boot_option", 2) + nvs.commit() + machine.reset() + + @staticmethod + def _get_file_info(path) -> tuple(str, str, str): + mtime = None + account = None + ver = f"Ver: UIFLOW2 {esp32.firmware_info()[3]}" + + try: + stat = os.stat(path) + mtime = time.localtime(stat[8]) + except OSError: + pass + + if mtime is None or mtime[0] < 2023 and mtime[1] < 9: + mtime = "Time: ----/--/-- --:--:--" + else: + mtime = "Time: {:04d}/{:d}/{:d} {:02d}:{:02d}:{:02d}".format( + mtime[0], mtime[1], mtime[2], mtime[3], mtime[4], mtime[5] + ) + + with open(path, "r") as f: + for line in f: + if line.find("Account") != -1: + account = line.split(":")[1].strip() + if line.find("Ver") != -1: + ver = line.split(":")[1].strip() + if account is not None and ver is not None: + break + + if account is None and _HAS_SERVER and M5Things.status() == 2: + infos = M5Things.info() + account = "Account: None" if len(infos[1]) == 0 else "Account: {:s}".format(infos[1]) + else: + account = "Account: None" + + return (mtime, account, ver) + + async def _kb_event_handler(self, event, fw): + if event.key == 183: # Right key + M5.Lcd.drawImage(res.RUN_ONCE_UNSELECT_IMG, 6, 100) + M5.Lcd.drawImage(res.RUN_ALWAYS_SELECT_IMG, 123, 100) + self._enter_handler = self._handle_run_always + elif event.key == 180: # Left key + M5.Lcd.drawImage(res.RUN_ONCE_SELECT_IMG, 6, 100) + M5.Lcd.drawImage(res.RUN_ALWAYS_UNSELECT_IMG, 123, 100) + self._enter_handler = self._handle_run_once + elif event.key == 0x0D: # Enter key + self._enter_handler(fw) diff --git a/m5stack/modules/startup/cardputeradv/apps/dev.py b/m5stack/modules/startup/cardputeradv/apps/dev.py new file mode 100644 index 00000000..6bccfeb2 --- /dev/null +++ b/m5stack/modules/startup/cardputeradv/apps/dev.py @@ -0,0 +1,189 @@ +# SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +from .. import app_base +from .. import res +import widgets +import M5 +import requests +import machine +import asyncio +import binascii +import os + +try: + import M5Things + + _HAS_SERVER = True +except ImportError: + _HAS_SERVER = False + + +class NetworkStatus: + INIT = 0 + RSSI_GOOD = 1 + RSSI_MID = 2 + RSSI_WORSE = 3 + DISCONNECTED = 4 + + +class CloudStatus: + INIT = 0 + CONNECTED = 1 + DISCONNECTED = 2 + + +class DevApp(app_base.AppBase): + def __init__(self, icos: dict, data=None) -> None: + super().__init__() + + def on_install(self): + pass + + def on_launch(self): + self._mac_text = self._get_mac() + self._account_text = self._get_account() + self._bg_src = self._get_bg_src() + + def on_view(self): + self._origin_x = 0 + self._origin_y = 0 + + M5.Lcd.fillRect(0, 16, 240, 119, 0xEEEEEF) + + self._bg_img = widgets.Image(use_sprite=False) + self._bg_img.set_pos(6, 22) + self._bg_img.set_size(228, 107) + self._bg_img.set_src(self._bg_src) + self._avatar_src = self._get_avatar() + + self._mac_label = widgets.Label( + "aabbcc112233", + 15, + 63, + w=116, + h=15, + fg_color=0x000000, + bg_color=0xFEFEFE, + font=res.MontserratMedium12_VLW, + ) + self._mac_label.set_text(self._mac_text) + + self._account_label = widgets.Label( + "XXABC", + 15, + 92, + w=90, + h=34, + fg_color=0x000000, + bg_color=0xFEFEFE, + font=res.MontserratMedium12_VLW, + ) + self._account_label.set_text(self._account_text) + + self._avatar_img = widgets.Image(use_sprite=False) + self._avatar_img.set_pos(110, 91) + self._avatar_img.set_size(38, 38) + self._avatar_img.set_scale(0.19, 0.19) + self._avatar_img.set_src(self._avatar_src) + + def on_ready(self): + super().on_ready() + + async def on_run(self): + refresh = False + while True: + t = self._get_bg_src() + if t != self._bg_src: + self._bg_src = t + self._bg_img.set_src(self._bg_src) + refresh = True + + refresh and self._mac_label.set_text(self._mac_text) + + t = self._get_account() + if t != self._account_text or refresh: + self._account_text = t + self._account_label.set_text(self._account_text) + + t = self._get_avatar() + if t != self._avatar_src: + self._avatar_src = t + try: + os.stat(self._avatar_src) + self._avatar_img.set_src(self._avatar_src) + except OSError: + self._dl_task = asyncio.create_task(self._dl_avatar(self._avatar_src)) + elif refresh: + self._avatar_img._draw(False) + + refresh = False + await asyncio.sleep_ms(1500) + + def on_hide(self): + self._task.cancel() + + def on_exit(self): + M5.Lcd.fillRect(30, 19, 210, 116, 0x333333) + del self._bg_img, self._mac_label, self._account_label + + async def _dl_avatar(self, dst): + if _HAS_SERVER is True and M5Things.status() == 2: + infos = M5Things.info() + if len(infos[4]) == 0: + self._avatar_img.set_src(res.AVATAR_IMG) + else: + try: + rsp = requests.get("https://community.m5stack.com" + str(infos[4])) + length = int(rsp.headers["Content-Length"]) + block_len = 1024 + source = rsp.raw + read = 0 + with open(dst, "wb") as f: + # 逐块读取数据 + while read < length: + to_read = block_len if (length - read) >= block_len else length - read + buf = source.read(to_read) + read += len(buf) + f.write(buf) + self._avatar_img.set_src(dst) + except: + self._avatar_img.set_src(res.AVATAR_IMG) + else: + self._avatar_img.set_src(res.AVATAR_IMG) + + @staticmethod + def _get_mac(): + return binascii.hexlify(machine.unique_id()).upper() + + @staticmethod + def _get_account(): + if _HAS_SERVER is True and M5Things.status() == 2: + infos = M5Things.info() + return "None" if len(infos[1]) == 0 else infos[1] + else: + return "None" + + @staticmethod + def _get_avatar(): + if _HAS_SERVER is True and M5Things.status() == 2: + infos = M5Things.info() + print(infos) + if len(infos[4]) == 0: + return res.AVATAR_IMG + else: + return "/system/common/img/" + str(infos[4]).split("/")[-1] + else: + return res.AVATAR_IMG + + @staticmethod + def _get_bg_src(): + if _HAS_SERVER is True and M5Things.status() == 2: + infos = M5Things.info() + if infos[0] == 0: + return res.DEVELOP_PRIVATE_IMG + elif infos[0] in (1, 2): + return res.DEVELOP_PUBLIC_IMG + else: + return res.DEVELOP_PRIVATE_IMG diff --git a/m5stack/modules/startup/cardputeradv/apps/ezdata.py b/m5stack/modules/startup/cardputeradv/apps/ezdata.py new file mode 100644 index 00000000..75dad3dc --- /dev/null +++ b/m5stack/modules/startup/cardputeradv/apps/ezdata.py @@ -0,0 +1,40 @@ +# SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +from .. import app_base +from .. import res +import widgets +import M5 + + +class EzDataApp(app_base.AppBase): + def __init__(self, icos, data=None) -> None: + super().__init__() + + def on_install(self): + pass + + def on_view(self): + M5.Lcd.fillRect(0, 16, 240, 119, 0xEEEEEF) + self._text_label = widgets.Label( + "aabbcc112233", + 120, + 69, + w=240, + h=22, + font_align=widgets.Label.CENTER_ALIGNED, + fg_color=0x000000, + bg_color=0xEEEEEF, + font=res.MontserratMedium18_VLW, + ) + self._text_label.set_text("Please stay tuned") + + def on_ready(self): + pass + + def on_hide(self): + pass + + def on_exit(self): + pass diff --git a/m5stack/modules/startup/cardputeradv/apps/launcher.py b/m5stack/modules/startup/cardputeradv/apps/launcher.py new file mode 100644 index 00000000..43dd98c8 --- /dev/null +++ b/m5stack/modules/startup/cardputeradv/apps/launcher.py @@ -0,0 +1,125 @@ +# SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +from .. import app_base +from .. import res +import widgets +import M5 +from collections import namedtuple + +Icon = namedtuple("Icon", ["name", "src"]) + + +class LauncherApp(app_base.AppBase): + def __init__(self) -> None: + super().__init__() + self._icos = ( + Icon("SETTING", res.SETTING_ICO), + Icon("DEVELOP", res.DEVELOP_ICO), + Icon("APP.RUN", res.APPRUN_ICO), + Icon("APP.LIST", res.APPLIST_ICO), + Icon("EZDATA", res.EZDATA_ICO), + ) + self._id = 1 + + def on_view(self): + M5.Lcd.fillRect(0, 16, 240, 119, 0xEEEEEF) + + left = (len(self._icos) - 1) if self._id - 1 < 0 else self._id - 1 + self._left_img = widgets.Image(use_sprite=False) + self._left_img.set_pos(23, 42) + self._left_img.set_size(48, 48) + self._left_img.set_scale(0.75, 0.75) + self._left_img.set_src(self._icos[left].src) + + self._left_label = widgets.Label( + "SETTING", + 47, + 92, + w=48 + 4, + font_align=widgets.Label.CENTER_ALIGNED, + fg_color=0x000000, + bg_color=0xEEEEEF, + font=res.MontserratMedium10_VLW, + ) + self._left_label.set_text(self._icos[left].name) + + self._center_img = widgets.Image(use_sprite=False) + self._center_img.set_pos(88, 36) + self._center_img.set_size(64, 64) + self._center_img.set_src(self._icos[self._id].src) + + self._center_label = widgets.Label( + "DEVELOP", + 120, + 101, + w=64, + font_align=widgets.Label.CENTER_ALIGNED, + fg_color=0x000000, + bg_color=0xEEEEEF, + font=res.MontserratMedium12_VLW, + ) + self._center_label.set_text(self._icos[self._id].name) + + right = 0 if self._id + 1 > (len(self._icos) - 1) else self._id + 1 + self._right_img = widgets.Image(use_sprite=False) + self._right_img.set_pos(169, 42) + self._right_img.set_size(48, 48) + self._right_img.set_scale(0.75, 0.75) + self._right_img.set_src(self._icos[right].src) + + self._right_label = widgets.Label( + "APP.RUN", + 193, + 92, + w=48 + 4, + font_align=widgets.Label.CENTER_ALIGNED, + fg_color=0x000000, + bg_color=0xEEEEEF, + font=res.MontserratMedium10_VLW, + ) + self._right_label.set_text(self._icos[right].name) + + M5.Lcd.drawImage(res.LEFT_ICO, 3, 56) + M5.Lcd.drawImage(res.RIGHT_ICO, 227, 56) + + def on_ready(self): + pass + + def on_hide(self): + pass + + def on_exit(self): + pass + + def on_uninstall(self): + pass + + async def _kb_event_handler(self, event, fw): + left = 0 + right = 0 + refresh = False + if event.key == 183: # right key + self._id = self._id + 1 if self._id + 1 < len(self._icos) else 0 + left = (len(self._icos) - 1) if self._id - 1 < 0 else self._id - 1 + right = 0 if self._id + 1 > (len(self._icos) - 1) else self._id + 1 + refresh = True + + if event.key == 180: # left key + self._id = self._id - 1 if self._id - 1 >= 0 else (len(self._icos) - 1) + left = (len(self._icos) - 1) if self._id - 1 < 0 else self._id - 1 + right = 0 if self._id + 1 > (len(self._icos) - 1) else self._id + 1 + refresh = True + + if refresh: + self._left_img.set_src(self._icos[left].src) + self._left_label.set_text(self._icos[left].name) + self._center_img.set_src(self._icos[self._id].src) + self._center_label.set_text(self._icos[self._id].name) + self._right_img.set_src(self._icos[right].src) + self._right_label.set_text(self._icos[right].name) + + if event.key == 0x0D: + app = fw._app_selector.index(self._id + 1) + app.start() diff --git a/m5stack/modules/startup/cardputeradv/apps/settings.py b/m5stack/modules/startup/cardputeradv/apps/settings.py new file mode 100644 index 00000000..f86ae903 --- /dev/null +++ b/m5stack/modules/startup/cardputeradv/apps/settings.py @@ -0,0 +1,633 @@ +# SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +from .. import app_base +from .. import res +import widgets +import M5 +import esp32 + + +class WiFiSettingApp(app_base.AppBase): + def __init__(self, icos: dict, data=None) -> None: + self._wifi = data + super().__init__() + + def on_launch(self): + self.get_data() + self._option = 0 + + def on_view(self): + M5.Lcd.fillRect(0, 16, 240, 119, 0xEEEEEF) + + self._ssid_label = widgets.Label( + "ssid", + 70, + 32, + w=152, + h=16, + font_align=widgets.Label.LEFT_ALIGNED, + fg_color=0x000000, + bg_color=0xFFFFFF, + font=res.MontserratMedium10_VLW, + ) + self._ssid_label.set_long_mode(widgets.Label.LONG_DOT) + self._ssid_label.set_text(self.ssid) + + self._psk_label = widgets.Label( + "psk", + 70, + 54, + w=152, + h=16, + font_align=widgets.Label.LEFT_ALIGNED, + fg_color=0x000000, + bg_color=0xFFFFFF, + font=res.MontserratMedium10_VLW, + ) + self._psk_label.set_long_mode(widgets.Label.LONG_DOT) + if len(self.psk): + self._psk_label.set_text("*" * 20) + else: + self._psk_label.set_text("") + + self._server_label = widgets.Label( + "server", + 70, + 76, + w=152, + h=16, + font_align=widgets.Label.LEFT_ALIGNED, + fg_color=0x000000, + bg_color=0xFFFFFF, + font=res.MontserratMedium10_VLW, + ) + self._server_label.set_long_mode(widgets.Label.LONG_DOT) + self._server_label.set_text(self.server) + + self._submit_button = widgets.Image(use_sprite=False) + self._submit_button.set_pos(6, 105) + self._submit_button.set_size(228, 24) + self._submit_button.set_src(res.SUBMIT_UNSELECT_BUTTON_IMG) + + self._menu_selector = app_base.AppSelector( + ( + (0, self._select_default_option), + (1, self._select_ssid_option), + (2, self._select_psk_option), + (3, self._select_server_option), + (4, self._select_submit_button_option), + ) + ) + self._option, view_fn = self._menu_selector.index(0) + view_fn() + + def on_ready(self): + pass + + def on_hide(self): + pass + + def on_exit(self): + del ( + self._ssid_label, + self._psk_label, + self._server_label, + self.nvs, + self.ssid, + self.psk, + self.server, + self.ssid_tmp, + self.psk_tmp, + self.server_tmp, + self._option, + ) + + async def _kb_event_handler(self, event, fw): + if event.key == 182: # down key + self._option, view_fn = self._menu_selector.next() + view_fn() + event.status = True + elif event.key == 181: # up key + self._option, view_fn = self._menu_selector.prev() + view_fn() + event.status = True + + if event.key == 0x0D and self._option == 4: # Enter key + self._option, view_fn = self._menu_selector.current() + view_fn() + event.status = True + + if event.key == 0x1B: # ESC key + self.ssid_tmp = self.ssid + self.psk_tmp = self.psk + self.server_tmp = self.server + self._option, view_fn = self._menu_selector.index(0) + view_fn() + event.status = True + + if event.key == 0x08 and self._option in (1, 2, 3): + print("backspace") + if self._option == 1: + self.ssid_tmp = self.ssid_tmp[:-1] + self._ssid_label.set_text(self.ssid_tmp) + elif self._option == 2: + self.psk_tmp = self.psk_tmp[:-1] + self._psk_label.set_text(self.psk_tmp) + elif self._option == 3: + self.server_tmp = self.server_tmp[:-1] + self._server_label.set_text(self.server_tmp) + event.status = True + elif event.key >= 0x20 and event.key <= 126: + if self._option == 1: + self.ssid_tmp += chr(event.key) + self._ssid_label.set_text(self.ssid_tmp) + elif self._option == 2: + self.psk_tmp += chr(event.key) + self._psk_label.set_text(self.psk_tmp) + elif self._option == 3: + self.server_tmp += chr(event.key) + self._server_label.set_text(self.server_tmp) + event.status = True + + def _select_default_option(self): + M5.Lcd.drawImage(res.WIFI_DEFAULT_IMG, 6, 22) + self._ssid_label.set_text(self.ssid_tmp) + if len(self.psk_tmp) == 0: + self._psk_label.set_text("") + else: + self._psk_label.set_text("*" * 20) + self._server_label.set_text(self.server_tmp) + self._submit_button.set_src(res.SUBMIT_UNSELECT_BUTTON_IMG) + + def _select_ssid_option(self): + M5.Lcd.drawImage(res.WIFI_SSID_IMG, 6, 22) + self._ssid_label.set_text(self.ssid_tmp) + if len(self.psk_tmp) == 0: + self._psk_label.set_text("") + else: + self._psk_label.set_text("*" * 20) + self._server_label.set_text(self.server_tmp) + self._submit_button.set_src(res.SUBMIT_UNSELECT_BUTTON_IMG) + + def _select_psk_option(self): + M5.Lcd.drawImage(res.WIFI_PSK_IMG, 6, 22) + self._ssid_label.set_text(self.ssid_tmp) + if len(self.psk_tmp) == 0: + self._psk_label.set_text("") + else: + self._psk_label.set_text("*" * 20) + self._server_label.set_text(self.server_tmp) + self._submit_button.set_src(res.SUBMIT_UNSELECT_BUTTON_IMG) + + def _select_server_option(self): + M5.Lcd.drawImage(res.WIFI_SERVER_IMG, 6, 22) + self._ssid_label.set_text(self.ssid_tmp) + if len(self.psk_tmp) == 0: + self._psk_label.set_text("") + else: + self._psk_label.set_text("*" * 20) + self._server_label.set_text(self.server_tmp) + self._submit_button.set_src(res.SUBMIT_UNSELECT_BUTTON_IMG) + + def _select_submit_button_option(self): + M5.Lcd.drawImage(res.WIFI_DEFAULT_IMG, 6, 22) + self._ssid_label.set_text(self.ssid_tmp) + if len(self.psk_tmp) == 0: + self._psk_label.set_text("") + else: + self._psk_label.set_text("*" * 20) + self._server_label.set_text(self.server_tmp) + self._submit_button.set_src(res.SUBMIT_SELECT_BUTTON_IMG) + + def get_data(self): + self.nvs = esp32.NVS("uiflow") + self.ssid = self.nvs.get_str("ssid0") + self.psk = self.nvs.get_str("pswd0") + self.server = self.nvs.get_str("server") + self.ssid_tmp = self.ssid + self.psk_tmp = self.psk + self.server_tmp = self.server + + def set_data(self): + is_save = False + if self.ssid != self.ssid_tmp: + self.ssid = self.ssid_tmp + self.nvs.set_str("ssid0", self.ssid) + print("set new ssid: ", self.ssid) + is_save = True + if self.psk != self.psk_tmp: + self.psk = self.psk_tmp + self.nvs.set_str("pswd0", self.psk) + print("set new psk: ", self.psk) + is_save = True + if self.server != self.server_tmp: + self.server = self.server_tmp + self.nvs.set_str("server", self.server) + print("set new server: ", self.server) + is_save = True + + if is_save is True: + self.nvs.commit() + self._wifi.wlan.disconnect() + self._wifi.wlan.active(False) + self._wifi.wlan.active(True) + self._wifi.connect_network(self.ssid, self.psk) + + +class BootScreenSetting(app_base.AppBase): + _boot_options = { + 1: res.ENABLE_IMG, + 2: res.DISABLE_IMG, + } + + def __init__(self, icos: dict, data=None) -> None: + super().__init__() + + def on_install(self): + self.on_launch() + self.on_view() + self.on_hide() + + def on_launch(self): + self._option = self._get_boot_option() + self._option = 1 if self._option == 1 else 2 + self._options = app_base.generator(self._boot_options) + while True: + t = next(self._options) + if t == self._option: + break + + def on_view(self): + self._menu_label = widgets.Label( + "Boot Screen", + 14, + 65, + w=155, + h=22, + font_align=widgets.Label.LEFT_ALIGNED, + fg_color=0x000000, + bg_color=0xFFFFFF, + font=res.MontserratMedium18_VLW, + ) + + self._option_img = widgets.Image(use_sprite=False) + self._option_img.set_pos(193, 69) + self._option_img.set_size(30, 14) + self._option_img.set_src(self._boot_options.get(self._option)) + + def on_ready(self): + M5.Lcd.drawImage(res.CARD_228x32_SELECT_IMG, 6, 60) + self._menu_label.set_text("Boot Screen") + self._option_img.set_src(self._boot_options.get(self._option)) + + def on_hide(self): + M5.Lcd.drawImage(res.CARD_228x32_UNSELECT_IMG, 6, 60) + self._menu_label.set_text("Boot Screen") + self._option_img.set_src(self._boot_options.get(self._option)) + + def on_exit(self): + del (self._menu_label, self._option_img) + + @staticmethod + def _get_boot_option(): + nvs = esp32.NVS("uiflow") + return nvs.get_u8("boot_option") + + @staticmethod + def _set_boot_option(boot_option): + nvs = esp32.NVS("uiflow") + nvs.set_u8("boot_option", boot_option) + nvs.commit() + + def _handle_boot_option(self, fw): + self._option = next(self._options) + self._set_boot_option(self._option) + self._option_img.set_src(self._boot_options.get(self._option)) + + async def _kb_event_handler(self, event, fw): + if event.key == 0x0D: # Enter key + self._handle_boot_option(fw) + event.status = True + + +class ComLinkSetting(app_base.AppBase): + _comlink_options = { + False: res.DISABLE_IMG, + True: res.ENABLE_IMG, + } + + def __init__(self, icos: dict) -> None: + super().__init__() + + def on_install(self): + self.on_launch() + self.on_view() + self.on_hide() + + def on_launch(self): + self._option = False + self._options = app_base.generator(self._comlink_options) + while True: + t = next(self._options) + if t == self._option: + break + + def on_view(self): + self._menu_label = widgets.Label( + "COM.X Link", + 14, + 103, + w=155, + h=22, + font_align=widgets.Label.LEFT_ALIGNED, + fg_color=0x000000, + bg_color=0xFFFFFF, + font=res.MontserratMedium18_VLW, + ) + + self._option_img = widgets.Image(use_sprite=False) + self._option_img.set_pos(193, 106) + self._option_img.set_size(30, 14) + self._option_img.set_src(self._comlink_options.get(self._option)) + + def on_ready(self): + M5.Lcd.drawImage(res.CARD_228x32_SELECT_IMG, 6, 98) + self._menu_label.set_text("COM.X Link") + self._option_img.set_src(self._comlink_options.get(self._option)) + + def on_hide(self): + M5.Lcd.drawImage(res.CARD_228x32_UNSELECT_IMG, 6, 98) + self._menu_label.set_text("COM.X Link") + self._option_img.set_src(self._comlink_options.get(self._option)) + + def on_exit(self): + del self._option_img + + async def _btnb_event_handler(self, fw): + pass + + def _handle_option(self, fw): + self._option = next(self._options) + self._option_img.set_src(self._comlink_options.get(self._option)) + + async def _kb_event_handler(self, event, fw): + if event.key == 0x0D: # Enter key + self._handle_option(fw) + event.status = True + + +class BrightnessSettingApp(app_base.AppBase): + _brightness_options = {64: "25%", 128: "50%", 192: "75%", 255: "100%"} + + def __init__(self, icos: dict) -> None: + super().__init__() + + def on_install(self): + self.on_launch() + self.on_view() + self.on_hide() + + def on_launch(self): + self._brightness = M5.Lcd.getBrightness() + self._brightness = self.approximate(self._brightness) + self._options = app_base.generator(self._brightness_options) + while True: + t = next(self._options) + if t == self._brightness: + break + + def on_view(self): + self._menu_label = widgets.Label( + "Brightness", + 14, + 27, + w=155, + h=22, + font_align=widgets.Label.LEFT_ALIGNED, + fg_color=0x000000, + bg_color=0xFFFFFF, + font=res.MontserratMedium18_VLW, + ) + + self._brightness_label = widgets.Label( + "server", + 223, + 30, + w=40, + h=15, + font_align=widgets.Label.RIGHT_ALIGNED, + fg_color=0x000000, + bg_color=0xFFFFFF, + font=res.MontserratMedium12_VLW, + ) + + def on_ready(self): + M5.Lcd.drawImage(res.CARD_228x32_SELECT_IMG, 6, 22) + self._menu_label.set_text("Brightness") + self._brightness_label.set_text(self._brightness_options.get(self._brightness)) + + def on_hide(self): + M5.Lcd.drawImage(res.CARD_228x32_UNSELECT_IMG, 6, 22) + self._menu_label.set_text("Brightness") + self._brightness_label.set_text(self._brightness_options.get(self._brightness)) + + def on_exit(self): + del (self._menu_label, self._brightness_label) + + def _handle_brightness(self, fw): + self._brightness = next(self._options) + M5.Lcd.setBrightness(self._brightness) + self._brightness_label.set_text(self._brightness_options.get(self._brightness)) + + @staticmethod + def approximate(number): + tolerance = 32 + for v in (64, 128, 192, 255): + if number < 64: + return 64 + if abs(number - v) < tolerance: + return v + + async def _kb_event_handler(self, event, fw): + if event.key == 0x0D: # Enter key + self._handle_brightness(fw) + event.status = True + + +class GeneralSettingApp(app_base.AppBase): + def __init__(self, icos: dict, data=None) -> None: + self._menus = ( + BrightnessSettingApp(None), + BootScreenSetting(None), + ComLinkSetting(None), + ) + self._menu_selector = app_base.AppSelector(self._menus) + super().__init__() + + def on_install(self): + pass + + def on_launch(self): + pass + + def on_view(self): + M5.Lcd.fillRect(0, 16, 240, 119, 0xEEEEEF) + + def on_ready(self): + pass + + def on_hide(self): + pass + + def start(self): + super().start() + for menu in self._menus: + menu.install() + self._menu_selector.current().resume() + + def stop(self): + for menu in self._menus: + menu.uninstall() + super().stop() + + async def _kb_event_handler(self, event, fw): + if event.key == 182: # down key + self._menu_selector.current().pause() + app = self._menu_selector.next().resume() + event.status = True + elif event.key == 181: # up key + self._menu_selector.current().pause() + self._menu_selector.prev().resume() + event.status = True + elif event.key == 0x0D: # Enter key + app = self._menu_selector.current() + await app._kb_event_handler(event, fw) + + +class SettingsApp(app_base.AppBase): + def __init__(self, icos: dict, data=None) -> None: + self._wlan = data + self._menus = ( + WiFiSettingApp(None, data=self._wlan), + GeneralSettingApp(None), + ) + self._menu_selector = app_base.AppSelector(self._menus) + super().__init__() + + def on_install(self): + pass + + def on_launch(self): + self._imgs = [] + self._icos = [] + self._labels = [] + self._app = None + + def on_view(self): + M5.Lcd.fillRect(0, 16, 240, 119, 0xEEEEEF) + + self._img0 = widgets.Image(use_sprite=False) + self._img0.set_pos(6, 22) + self._img0.set_size(228, 32) + self._img0.set_src(res.CARD_228x32_SELECT_IMG) + self._imgs.append(self._img0) + + self._ico0 = widgets.Image(use_sprite=False) + self._ico0.set_pos(9, 25) + self._ico0.set_size(26, 26) + self._ico0.set_src(res.WLAN_ICO_IMG) + self._icos.append(self._ico0) + + self._label0 = widgets.Label( + "", + 40, + 27, + w=182, + h=22, + fg_color=0x000000, + bg_color=0xFFFFFF, + font=res.MontserratMedium18_VLW, + ) + self._label0.set_text("WLAN") + self._labels.append(self._label0) + + M5.Lcd.drawImage(res.CARET_RIGHT, 213, 25) + + self._img1 = widgets.Image(use_sprite=False) + self._img1.set_pos(6, 60) + self._img1.set_size(228, 32) + self._img1.set_src(res.CARD_228x32_UNSELECT_IMG) + self._imgs.append(self._img1) + + self._ico1 = widgets.Image(use_sprite=False) + self._ico1.set_pos(9, 63) + self._ico1.set_size(26, 26) + self._ico1.set_src(res.GENERAL_ICO_IMG) + self._icos.append(self._ico1) + + self._label1 = widgets.Label( + "", + 40, + 65, + w=182, + h=22, + fg_color=0x000000, + bg_color=0xFFFFFF, + font=res.MontserratMedium18_VLW, + ) + self._label1.set_text("General") + self._labels.append(self._label1) + + M5.Lcd.drawImage(res.CARET_RIGHT, 213, 63) + + def on_ready(self): + pass + + def on_hide(self): + self._app = None + + async def _kb_event_handler(self, event, fw): + if self._app: + await self._app._kb_event_handler(event, fw) + return + + if event.key == 0x0D: # Enter key + self._app = self._menu_selector.current() + print("current app:", self._app) + await fw.load(self._app) + event.status = True + elif event.key == 182: # down key + self._menu_selector.index(1) + self._imgs[0].set_src(res.CARD_228x32_UNSELECT_IMG) + self._icos[0].refresh() + # self._labels[0].refresh() + self._labels[0].set_text("WLAN") + M5.Lcd.drawImage(res.CARET_RIGHT, 213, 25) + + self._imgs[1].set_src(res.CARD_228x32_SELECT_IMG) + self._icos[1].refresh() + # self._labels[1].refresh() + self._labels[1].set_text("General") + M5.Lcd.drawImage(res.CARET_RIGHT, 213, 63) + + event.status = True + elif event.key == 181: # up key + self._menu_selector.index(0) + self._imgs[0].set_src(res.CARD_228x32_SELECT_IMG) + self._icos[0].refresh() + # self._labels[0].refresh() + self._labels[0].set_text("WLAN") + M5.Lcd.drawImage(res.CARET_RIGHT, 213, 25) + + self._imgs[1].set_src(res.CARD_228x32_UNSELECT_IMG) + self._icos[1].refresh() + # self._labels[1].refresh() + self._labels[1].set_text("General") + M5.Lcd.drawImage(res.CARET_RIGHT, 213, 63) + event.status = True + + async def _btna_event_handler(self, fw): + self._menu_selector.index(0) + self._app = None diff --git a/m5stack/modules/startup/cardputeradv/apps/sidebar.py b/m5stack/modules/startup/cardputeradv/apps/sidebar.py new file mode 100644 index 00000000..7e2b27b0 --- /dev/null +++ b/m5stack/modules/startup/cardputeradv/apps/sidebar.py @@ -0,0 +1,55 @@ +# SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +from .. import app_base +from .. import res +import M5 + + +class SidebarApp(app_base.AppBase): + def __init__(self) -> None: + super().__init__() + + def on_launch(self): + return super().on_launch() + + def on_view(self): + M5.Lcd.fillRoundRect(2, 4, 28, 127, 15, 0xC4C4C4) + M5.Lcd.drawImage(res.Aa0_IMG, 5, 20) + M5.Lcd.drawImage(res.FN0_IMG, 5, 39) + M5.Lcd.drawImage(res.CTRL0_IMG, 5, 58) + M5.Lcd.drawImage(res.OPT0_IMG, 5, 77) + M5.Lcd.drawImage(res.ALT0_IMG, 5, 96) + + def on_ready(self): + pass + + def on_hide(self): + pass + + def on_key(self, state): + if state.shift: + M5.Lcd.drawImage(res.Aa_IMG, 5, 20) + else: + M5.Lcd.drawImage(res.Aa0_IMG, 5, 20) + + if state.fn: + M5.Lcd.drawImage(res.FN_IMG, 5, 39) + else: + M5.Lcd.drawImage(res.FN0_IMG, 5, 39) + + if state.ctrl: + M5.Lcd.drawImage(res.CTRL_IMG, 5, 58) + else: + M5.Lcd.drawImage(res.CTRL0_IMG, 5, 58) + + if state.opt: + M5.Lcd.drawImage(res.OPT_IMG, 5, 77) + else: + M5.Lcd.drawImage(res.OPT0_IMG, 5, 77) + + if state.alt: + M5.Lcd.drawImage(res.ALT_IMG, 5, 96) + else: + M5.Lcd.drawImage(res.ALT0_IMG, 5, 96) diff --git a/m5stack/modules/startup/cardputeradv/apps/statusbar.py b/m5stack/modules/startup/cardputeradv/apps/statusbar.py new file mode 100644 index 00000000..19d7d19e --- /dev/null +++ b/m5stack/modules/startup/cardputeradv/apps/statusbar.py @@ -0,0 +1,199 @@ +# SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +from .. import app_base +from .. import res +import widgets +import M5 +import network +import asyncio +import time + +try: + import M5Things + + _HAS_SERVER = True +except ImportError: + _HAS_SERVER = False + + +class NetworkStatus: + INIT = 0 + RSSI_GOOD = 1 + RSSI_MID = 2 + RSSI_WORSE = 3 + DISCONNECTED = 4 + + +class CloudStatus: + INIT = 0 + CONNECTED = 1 + DISCONNECTED = 2 + + +_WIFI_STATUS_ICO = { + NetworkStatus.INIT: res.WIFI_EMPTY_IMG, + NetworkStatus.RSSI_GOOD: res.WIFI_GOOD_IMG, + NetworkStatus.RSSI_MID: res.WIFI_MID_IMG, + NetworkStatus.RSSI_WORSE: res.WIFI_WORSE_IMG, + NetworkStatus.DISCONNECTED: res.WIFI_DISCONNECTED_IMG, +} + +_CLOUD_STATUS_ICOS = { + CloudStatus.INIT: res.SERVER_EMPTY_IMG, + CloudStatus.CONNECTED: res.SERVER_GREEN_IMG, + CloudStatus.DISCONNECTED: res.SERVER_ERROR_IMG, +} + + +class StatusBarApp(app_base.AppBase): + def __init__(self, icos: dict, wifi) -> None: + self._wifi = wifi + + def on_launch(self): + self._time_text = self._get_local_time_text() + self._network_status = self._get_network_status() + self._cloud_status = self._get_cloud_status() + self._battery_src = self._get_battery_src( + M5.Power.getBatteryLevel(), M5.Power.isCharging() + ) + self._battery_text = self._get_battery_text(M5.Power.getBatteryLevel()) + + def on_view(self): + M5.Lcd.fillRect(0, 0, 240, 16, 0xEEEEEF) + M5.Lcd.drawImage(res.BLUE_TITLE_IMG, 0, 0) + self._time_label = widgets.Label( + "12:23", + 120, + 1, + w=48, + font_align=widgets.Label.CENTER_ALIGNED, + fg_color=0x534D4C, + bg_color=0xEEEEEF, + font=res.MontserratMedium12_VLW, + ) + self._time_label.set_text(self._time_text) + + self._network_img = widgets.Image(use_sprite=False) + self._network_img.set_pos(163, 0) + self._network_img.set_size(16, 16) + self._network_img.set_src(_WIFI_STATUS_ICO[self._network_status]) + + self._cloud_img = widgets.Image(use_sprite=False) + self._cloud_img.set_pos(179, 0) + self._cloud_img.set_size(16, 16) + self._cloud_img.set_src(_CLOUD_STATUS_ICOS[self._cloud_status]) + + self._battery_img = widgets.Image(use_sprite=False) + self._battery_img.set_pos(195, 0) + self._battery_img.set_size(45, 16) + self._battery_img.set_src(self._battery_src) + + self._battery_label = widgets.Label( + "78%", + 212, + 2, + w=26 + 4, + font_align=widgets.Label.CENTER_ALIGNED, + fg_color=0x534D4C, + bg_color=0xFEFEFE, + font=res.MontserratMedium10_VLW, + ) + self._battery_label.set_text(self._battery_text) + + async def on_run(self): + refresh = False + while True: + t = self._get_local_time_text() + if t != self._time_text: + self._time_label.set_text(t) + self._time_text = t + + t = self._get_network_status() + if t != self._network_status: + self._network_img.set_src(_WIFI_STATUS_ICO[t]) + self._network_status = t + + t = self._get_cloud_status() + if t != self._cloud_status: + self._cloud_img.set_src(_CLOUD_STATUS_ICOS[t]) + + t = self._get_battery_src(M5.Power.getBatteryLevel(), M5.Power.isCharging()) + if t != self._battery_src: + self._battery_img.set_src(t) + self._battery_src = t + refresh = True + + t = self._get_battery_text(M5.Power.getBatteryLevel()) + if t != self._battery_text or refresh: + self._battery_label.set_text(t) + self._battery_text = t + refresh = False + + await asyncio.sleep_ms(5000) + + def _update_time(self, struct_time): + self._time_label.set_text("{:02d}:{:02d}".format(struct_time[3], struct_time[4])) + + def _update_wifi(self, status): + self._wifi_status = status + src = _WIFI_STATUS_ICO.get(self._wifi_status) + M5.Lcd.drawImage(src.src, src.x, src.y) + + def _update_server(self, status): + self._server_status = status + src = _CLOUD_STATUS_ICOS.get(self._server_status) + M5.Lcd.drawImage(src.src, src.x, src.y) + + @staticmethod + def _get_local_time_text(): + struct_time = time.localtime() + return "{:02d}:{:02d}".format(struct_time[3], struct_time[4]) + + def _get_network_status(self): + status = self._wifi.connect_status() + if status is network.STAT_GOT_IP: + rssi = self._wifi.get_rssi() + if rssi <= -80: + return NetworkStatus.RSSI_WORSE + elif rssi <= -60: + return NetworkStatus.RSSI_MID + else: + return NetworkStatus.RSSI_GOOD + else: + return NetworkStatus.DISCONNECTED + + @staticmethod + def _get_cloud_status(): + if _HAS_SERVER is True: + status = M5Things.status() + return { + -2: CloudStatus.DISCONNECTED, + -1: CloudStatus.DISCONNECTED, + 0: CloudStatus.INIT, + 1: CloudStatus.INIT, + 2: CloudStatus.CONNECTED, + 3: CloudStatus.DISCONNECTED, + }[status] + else: + return CloudStatus.DISCONNECTED + + @staticmethod + def _get_battery_src(battery, charging): + src = "" + if battery > 0 and battery <= 100: + if battery < 20: + src = res.BATTERY_RED_CHARGE_IMG if charging else res.BATTERY_RED_IMG + elif battery <= 100: + src = res.BATTERY_GREEN_CHARGE_IMG if charging else res.BATTERY_GREEN_IMG + else: + src = res.BATTERY_BLACK_CHARGE_IMG if charging else res.BATTERY_BLACK_IMG + return src + + @staticmethod + def _get_battery_text(battery): + if battery > 0 and battery <= 100: + return "{:d}%".format(battery) + else: + return "" diff --git a/m5stack/modules/startup/cardputeradv/framework.py b/m5stack/modules/startup/cardputeradv/framework.py new file mode 100644 index 00000000..50f86070 --- /dev/null +++ b/m5stack/modules/startup/cardputeradv/framework.py @@ -0,0 +1,104 @@ +# SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +from . import app_base +from hardware import MatrixKeyboard +import M5 +import gc +import asyncio + + +class KeyEvent: + key = 0 + status = False + + +class Framework: + def __init__(self) -> None: + self._apps = [] + self._app_selector = app_base.AppSelector(self._apps) + self._launcher = None + self._bar = None + self._sidebar = None + + def install_bar(self, bar: app_base.AppBase): + self._bar = bar + + def install_sidebar(self, bar: app_base.AppBase): + self._sidebar = bar + + def install_launcher(self, launcher: app_base.AppBase): + self._launcher = launcher + + def install(self, app: app_base.AppBase): + app.install() + self._apps.append(app) + + def start(self): + # asyncio.create_task(self.gc_task()) + asyncio.run(self.run()) + + async def unload(self, app: app_base.AppBase): + # app = self._apps.pop() + app.stop() + + async def load(self, app: app_base.AppBase): + app.start() + + async def reload(self, app: app_base.AppBase): + app.stop() + app.start() + + async def run(self): + kb = MatrixKeyboard() + event = KeyEvent() + + self._bar and self._bar.start() + self._sidebar and self._sidebar.start() + if self._launcher: + self._app_selector.select(self._launcher) + self._launcher.start() + + while True: + M5.update() + kb.tick() + if kb.is_pressed(): + M5.Speaker.tone(3500, 50) + event.key = kb.get_key() + event.status = False + await self.handle_input(event) + + if M5.BtnA.wasClicked(): + app = self._app_selector.current() + if hasattr(app, "_btna_event_handler"): + await app._btna_event_handler(self) + app.stop() + self._app_selector.select(self._launcher) + self._launcher.start() + + await asyncio.sleep_ms(10) + + async def handle_input(self, event: KeyEvent): + print("key:", event.key) + + app = self._app_selector.current() + if hasattr(app, "_kb_event_handler"): + await app._kb_event_handler(event, self) + + if event.status is False: + if event.key == 96: # ESC key + app = self._app_selector.current() + if hasattr(app, "_btna_event_handler"): + await app._btna_event_handler(self) + app.stop() + self._app_selector.select(self._launcher) + self._launcher.start() + return + + async def gc_task(self): + while True: + gc.collect() + print("heap RAM free:", gc.mem_free()) + print("heap RAM alloc:", gc.mem_alloc()) + await asyncio.sleep_ms(5000) diff --git a/m5stack/modules/startup/cardputeradv/res.py b/m5stack/modules/startup/cardputeradv/res.py new file mode 100644 index 00000000..8bd13aa6 --- /dev/null +++ b/m5stack/modules/startup/cardputeradv/res.py @@ -0,0 +1,85 @@ +# SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +_attrs = { + "LOGO_IMG": "/system/cardputeradv/boot/boot_logo_1.jpeg", + # apprun + "RUN_INFO_IMG": "/system/cardputeradv/apprun/run_info.jpeg", + "RUN_ONCE_SELECT_IMG": "/system/cardputeradv/apprun/run_once_select.jpeg", + "RUN_ONCE_UNSELECT_IMG": "/system/cardputeradv/apprun/run_once_unselect.jpeg", + "RUN_ALWAYS_SELECT_IMG": "/system/cardputeradv/apprun/run_always_select.jpeg", + "RUN_ALWAYS_UNSELECT_IMG": "/system/cardputeradv/apprun/run_always_unselect.jpeg", + # develop + "DEVELOP_PRIVATE_IMG": "/system/cardputeradv/develop/private.jpeg", + "DEVELOP_PUBLIC_IMG": "/system/cardputeradv/develop/public.jpeg", + "AVATAR_IMG": "/system/common/img/avatar.jpeg", + # ezdata + # launcher + "APPLIST_ICO": "/system/cardputeradv/applist.jpeg", + "DEVELOP_ICO": "/system/cardputeradv/develop.jpeg", + "APPRUN_ICO": "/system/cardputeradv/apprun.jpeg", + "EZDATA_ICO": "/system/cardputeradv/ezdata.jpeg", + "SETTING_ICO": "/system/cardputeradv/setting.jpeg", + "RIGHT_ICO": "/system/cardputeradv/right.jpeg", + "LEFT_ICO": "/system/cardputeradv/left.jpeg", + # setting + "WLAN_ICO_IMG": "/system/cardputeradv/setting/wlan.jpeg", + "GENERAL_ICO_IMG": "/system/cardputeradv/setting/general.jpeg", + "CARET_RIGHT": "/system/cardputeradv/setting/caret_right.jpeg", + # wlan + "WIFI_DEFAULT_IMG": "/system/cardputeradv/setting/wlan/input_default.jpeg", + "WIFI_SSID_IMG": "/system/cardputeradv/setting/wlan/input_ssid.jpeg", + "WIFI_PSK_IMG": "/system/cardputeradv/setting/wlan/input_psk.jpeg", + "WIFI_SERVER_IMG": "/system/cardputeradv/setting/wlan/input_server.jpeg", + "SUBMIT_SELECT_BUTTON_IMG": "/system/cardputeradv/setting/wlan/submit_select.jpeg", + "SUBMIT_UNSELECT_BUTTON_IMG": "/system/cardputeradv/setting/wlan/submit_unselect.jpeg", + # general + "DISABLE_IMG": "/system/cardputeradv/setting/general/disable.jpeg", + "ENABLE_IMG": "/system/cardputeradv/setting/general/enable.jpeg", + # sidebar + "Aa_IMG": "/system/cardputeradv/sidebar/Aa.jpeg", + "Aa0_IMG": "/system/cardputeradv/sidebar/Aa0.jpeg", + "ALT_IMG": "/system/cardputeradv/sidebar/alt.jpeg", + "ALT0_IMG": "/system/cardputeradv/sidebar/alt0.jpeg", + "CTRL_IMG": "/system/cardputeradv/sidebar/ctrl.jpeg", + "CTRL0_IMG": "/system/cardputeradv/sidebar/ctrl0.jpeg", + "FN_IMG": "/system/cardputeradv/sidebar/fn.jpeg", + "FN0_IMG": "/system/cardputeradv/sidebar/fn0.jpeg", + "OPT_IMG": "/system/cardputeradv/sidebar/opt.jpeg", + "OPT0_IMG": "/system/cardputeradv/sidebar/opt0.jpeg", + # statusbar + "BATTERY_BLACK_CHARGE_IMG": "/system/cardputeradv/statusbar/battery/black_charge.jpeg", + "BATTERY_BLACK_IMG": "/system/cardputeradv/statusbar/battery/black.jpeg", + "BATTERY_GREEN_CHARGE_IMG": "/system/cardputeradv/statusbar/battery/green_charge.jpeg", + "BATTERY_GREEN_IMG": "/system/cardputeradv/statusbar/battery/green.jpeg", + "BATTERY_RED_CHARGE_IMG": "/system/cardputeradv/statusbar/battery/red_charge.jpeg", + "BATTERY_RED_IMG": "/system/cardputeradv/statusbar/battery/red.jpeg", + "SERVER_EMPTY_IMG": "/system/cardputeradv/statusbar/cloud/empty.jpeg", + "SERVER_ERROR_IMG": "/system/cardputeradv/statusbar/cloud/error.jpeg", + "SERVER_GREEN_IMG": "/system/cardputeradv/statusbar/cloud/green.jpeg", + "WIFI_DISCONNECTED_IMG": "/system/cardputeradv/statusbar/wifi/disconnected.jpeg", + "WIFI_EMPTY_IMG": "/system/cardputeradv/statusbar/wifi/empty.jpeg", + "WIFI_GOOD_IMG": "/system/cardputeradv/statusbar/wifi/good.jpeg", + "WIFI_MID_IMG": "/system/cardputeradv/statusbar/wifi/mid.jpeg", + "WIFI_WORSE_IMG": "/system/cardputeradv/statusbar/wifi/worse.jpeg", + "BLUE_TITLE_IMG": "/system/cardputeradv/statusbar/title_blue.jpeg", + # common + "CARD_228x32_SELECT_IMG": "/system/cardputeradv/common/card_228x32_select.jpeg", + "CARD_228x32_UNSELECT_IMG": "/system/cardputeradv/common/card_228x32_unselect.jpeg", + # font + "MontserratMedium10_VLW": "/system/common/font/Montserrat-Medium-10.vlw", + "MontserratMedium12_VLW": "/system/common/font/Montserrat-Medium-12.vlw", + "MontserratMedium16_VLW": "/system/common/font/Montserrat-Medium-16.vlw", + "MontserratMedium18_VLW": "/system/common/font/Montserrat-Medium-18.vlw", + # path + "USER_AVATAR_PATH": "/system/common/img/", +} + + +def __getattr__(attr): + value = _attrs.get(attr, None) + if value is None: + raise AttributeError(attr) + globals()[attr] = value + return value diff --git a/m5stack/modules/startup/manifest_cardputeradv.py b/m5stack/modules/startup/manifest_cardputeradv.py new file mode 100644 index 00000000..7eb051d3 --- /dev/null +++ b/m5stack/modules/startup/manifest_cardputeradv.py @@ -0,0 +1,23 @@ +# SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +package( + "startup", + ( + "__init__.py", + "cardputeradv/__init__.py", + "cardputeradv/app_base.py", + "cardputeradv/framework.py", + "cardputeradv/res.py", + "cardputeradv/apps/app_list.py", + "cardputeradv/apps/app_run.py", + "cardputeradv/apps/dev.py", + "cardputeradv/apps/ezdata.py", + "cardputeradv/apps/launcher.py", + "cardputeradv/apps/settings.py", + "cardputeradv/apps/statusbar.py", + ), + base_path="..", + opt=3, +) diff --git a/m5stack/patches/2006-Support-LTR553.patch b/m5stack/patches/2006-Support-LTR553.patch new file mode 100644 index 00000000..28a97d75 --- /dev/null +++ b/m5stack/patches/2006-Support-LTR553.patch @@ -0,0 +1,319 @@ +Index: M5Unified/src/M5Unified.cpp +=================================================================== +--- M5Unified.orig/src/M5Unified.cpp ++++ M5Unified/src/M5Unified.cpp +@@ -1408,6 +1408,7 @@ static constexpr const uint8_t _pin_tabl + #elif defined (CONFIG_IDF_TARGET_ESP32S3) + case board_t::board_M5StackCoreS3: + case board_t::board_M5StackCoreS3SE: ++ cfg.internal_als = true; + if (cfg.internal_mic) + { + mic_cfg.magnification = 2; +@@ -1914,6 +1915,9 @@ static constexpr const uint8_t _pin_tabl + { + port_a_used = M5.Imu.begin(&M5.Ex_I2C) || port_a_used; + } ++ if (cfg.internal_als && In_I2C.isEnabled()) { ++ M5.Als.begin(); ++ } + return port_a_used; + } + +Index: M5Unified/src/M5Unified.hpp +=================================================================== +--- M5Unified.orig/src/M5Unified.hpp ++++ M5Unified/src/M5Unified.hpp +@@ -62,6 +62,7 @@ namespace m5 + #include "utility/Touch_Class.hpp" + #include "utility/Log_Class.hpp" + #include "utility/IMU_Class.hpp" ++#include "utility/LTR553_Class.hpp" + #include "utility/IOExpander_Base.hpp" + + #include +@@ -138,6 +139,9 @@ namespace m5 + /// use the speaker. + bool internal_spk = true; + ++ /// use als ++ bool internal_als = false; ++ + /// use Unit Accel & Gyro. + bool external_imu = false; + +@@ -214,6 +218,7 @@ namespace m5 + Power_Class Power; + RTC8563_Class Rtc; + Touch_Class Touch; ++ LTR553_Class Als; + + /* + /// List of available buttons: +Index: M5Unified/src/utility/LTR553_Class.cpp +=================================================================== +--- /dev/null ++++ M5Unified/src/utility/LTR553_Class.cpp +@@ -0,0 +1,135 @@ ++#include "LTR553_Class.hpp" ++ ++namespace m5 ++{ ++ bool LTR553_Class::begin(I2C_Class* i2c) { ++ if (i2c != nullptr) { ++ _i2c = i2c; ++ i2c->begin(); ++ } ++ ++ uint8_t partID = readRegister8(0x86); ++ uint8_t mfrID = readRegister8(0x87); ++ if (partID != 0x92 || mfrID != 0x05) { ++ return false; ++ } ++ ++ // software reset ++ // bool ret = writeRegister8(0x80, 0x02); ++ ++ // active als ++ // bool ret = bitOn(0x80, 0); ++ bool ret = writeRegister8(0x80, 0x01); ++ // active ps ++ ret = ret && writeRegister8(0x81, 0x03); ++ return ret; ++ } ++ ++ bool LTR553_Class::sleep() { ++ // deactivate als ++ bool ret = writeRegister8(0x80, 0x00); ++ // deactivate ps ++ ret = ret && writeRegister8(0x81, 0x00); ++ return ret; ++ } ++ ++ bool LTR553_Class::softwareReset() { ++ return writeRegister8(0x80, 0x02); ++ } ++ ++ void LTR553_Class::setLightSensorMode(uint8_t mode) ++ { ++ uint8_t value = readRegister8(0x80); ++ value &= (~0x01); ++ value |= mode; ++ writeRegister8(0x80, value); ++ } ++ ++ void LTR553_Class::setProximitySensorMode(uint8_t mode) ++ { ++ uint8_t value = readRegister8(0x81); ++ value &= (~0x03); ++ value |= mode; ++ writeRegister8(0x81, value); ++ } ++ ++ void LTR553_Class::setLightSensorGain(uint8_t gain) ++ { ++ uint8_t value = readRegister8(0x80); ++ value &= (~0x1C); ++ value |= gain; ++ writeRegister8(0x80, value); ++ } ++ ++ void LTR553_Class::setProximitySensorGain(uint8_t gain) ++ { ++ uint8_t value = readRegister8(0x81); ++ value &= ~(0x0C); ++ value |= gain; ++ writeRegister8(0x81, value); ++ } ++ ++ void LTR553_Class::setProximitySensorLEDPulsePeriod(uint8_t period) ++ { ++ uint8_t value = readRegister8(0x82); ++ value &= ~(0xE0); ++ value |= period; ++ writeRegister8(0x82, value); ++ } ++ ++ void LTR553_Class::setProximitySensorLEDPulseDuty(uint8_t duty) ++ { ++ uint8_t value = readRegister8(0x82); ++ value &= ~(0x18); ++ value |= duty; ++ writeRegister8(0x82, value); ++ } ++ ++ void LTR553_Class::setProximitySensorLEDCurrent(uint8_t current) ++ { ++ uint8_t value = readRegister8(0x82); ++ value &= ~(0x07); ++ value |= current; ++ writeRegister8(0x82, value); ++ } ++ ++ void LTR553_Class::setLightSensorMeasurementRate(uint8_t rate) ++ { ++ uint8_t value = readRegister8(0x85); ++ value &= ~(0x07); ++ value |= rate; ++ writeRegister8(0x85, value); ++ } ++ ++ void LTR553_Class::setProximitySensorMeasurementRate(uint8_t rate) ++ { ++ writeRegister8(0x84, rate); ++ } ++ ++ uint16_t LTR553_Class::getLightSensorData() ++ { ++ uint8_t buffer[4] = {0}; ++ uint16_t result; ++ ++ readRegister(0x88, &buffer[0], 4); ++ ++ uint16_t ch1_value, ch0_value; ++ ch1_value = (buffer[1] << 8) | buffer[0]; ++ ch0_value = (buffer[3] << 8) | buffer[2]; ++ result = (ch1_value + ch0_value) >> 1; ++ return result; ++ } ++ ++ uint16_t LTR553_Class::getProximitySensorData() ++ { ++ uint8_t buffer[2]; ++ uint16_t result; ++ ++ readRegister(0x8D, &buffer[0], 2); ++ buffer[0] &= 0xFF; ++ buffer[1] &= 0x07; ++ result = (buffer[1] << 8) | buffer[0]; ++ return result; ++ } ++ ++} +Index: M5Unified/src/utility/LTR553_Class.hpp +=================================================================== +--- /dev/null ++++ M5Unified/src/utility/LTR553_Class.hpp +@@ -0,0 +1,122 @@ ++#ifndef __M5_LTR553_CLASS_H__ ++#define __M5_LTR553_CLASS_H__ ++ ++#include "I2C_Class.hpp" ++ ++namespace m5 ++{ ++ class LTR553_Class : public I2C_Device ++ { ++ public: ++ enum als_mode_t : std::uint8_t ++ { ++ E_ALS_MODE_STANDBY, ++ E_ALS_MODE_ACTIVE, ++ }; ++ enum ps_mode_t : std::uint8_t ++ { ++ E_PS_MODE_STANDBY = 0x00, ++ E_PS_MODE_ACTIVE = 0x10, ++ }; ++ ++ enum als_agin_t : std::uint8_t ++ { ++ E_ALS_MODE_X1 = 0x00, ++ E_ALS_MODE_X2 = 0x04, ++ E_ALS_MODE_X4 = 0x08, ++ E_ALS_MODE_X8 = 0x0C, ++ E_ALS_MODE_X48 = 0x18, ++ E_ALS_MODE_X96 = 0x1C, ++ }; ++ ++ enum ps_agin_t : std::uint8_t ++ { ++ E_ALS_MODE_X16 = 0x00, ++ E_ALS_MODE_X32 = 0x08, ++ E_ALS_MODE_X64 = 0x0C, ++ }; ++ ++ enum led_period_t : std::uint8_t ++ { ++ E_LED_PERIOD_30KHZ = 0x00, ++ E_LED_PERIOD_40KHZ = 0x20, ++ E_LED_PERIOD_50KHZ = 0x40, ++ E_LED_PERIOD_60KHZ = 0x60, ++ E_LED_PERIOD_70KHZ = 0x80, ++ E_LED_PERIOD_80KHZ = 0xA0, ++ E_LED_PERIOD_90KHZ = 0xC0, ++ E_LED_PERIOD_100KHZ = 0xE0, ++ }; ++ ++ enum led_duty_t : std::uint8_t ++ { ++ E_LED_DUTY_25 = 0x00, ++ E_LED_DUTY_50 = 0x08, ++ E_LED_DUTY_75 = 0x10, ++ E_LED_DUTY_100 = 0x18, ++ }; ++ ++ enum led_current_t : std::uint8_t ++ { ++ E_LED_CURRENT_5MA = 0x00, ++ E_LED_CURRENT_10MA = 0x01, ++ E_LED_CURRENT_20MA = 0x02, ++ E_LED_CURRENT_50MA = 0x03, ++ E_LED_CURRENT_100MA = 0x04, ++ }; ++ ++ enum als_rate_t : std::uint8_t ++ { ++ E_ALS_RATE_50MS = 0x00, ++ E_ALS_RATE_100MS = 0x01, ++ E_ALS_RATE_200MS = 0x02, ++ E_ALS_RATE_500MS = 0x03, ++ E_ALS_RATE_1000MS = 0x04, ++ E_ALS_RATE_2000MS = 0x05, ++ }; ++ ++ enum ps_rate_t : std::uint8_t ++ { ++ E_PS_RATE_50MS = 0x00, ++ E_PS_RATE_70MS = 0x01, ++ E_PS_RATE_100MS = 0x02, ++ E_PS_RATE_200MS = 0x03, ++ E_PS_RATE_500MS = 0x04, ++ E_PS_RATE_1000MS = 0x05, ++ E_PS_RATE_2000MS = 0x06, ++ E_PS_RATE_10MS = 0x08, ++ }; ++ ++ static constexpr std::uint8_t DEFAULT_ADDRESS = 0x23; ++ ++ LTR553_Class( ++ std::uint8_t i2c_addr = DEFAULT_ADDRESS, ++ std::uint32_t freq = 400000, ++ I2C_Class *i2c = &In_I2C) : I2C_Device(i2c_addr, freq, i2c) ++ { ++ } ++ ++ ~LTR553_Class() {} ++ ++ bool begin(I2C_Class *i2c = nullptr); ++ bool sleep(); ++ bool softwareReset(); ++ void setLightSensorMode(uint8_t mode); ++ void setProximitySensorMode(uint8_t mode); ++ void setLightSensorGain(uint8_t gain); ++ void setProximitySensorGain(uint8_t gain); ++ void setProximitySensorLEDPulsePeriod(uint8_t period); ++ void setProximitySensorLEDPulseDuty(uint8_t duty); ++ void setProximitySensorLEDCurrent(uint8_t current); ++ ++ void setLightSensorMeasurementRate(uint8_t rate); ++ void setProximitySensorMeasurementRate(uint8_t rate); ++ ++ uint16_t getLightSensorData(); ++ uint16_t getProximitySensorData(); ++ ++ bool isEnabled(void) const { return true; } ++ }; ++} ++ ++#endif From 33ed7ab5b5472b6f28865ada58dd272a2204405d Mon Sep 17 00:00:00 2001 From: lbuque <1102390310@qq.com> Date: Tue, 19 Aug 2025 14:19:20 +0800 Subject: [PATCH 226/322] libs/hardware: Add I2C Keyboard support. Signed-off-by: lbuque <1102390310@qq.com> --- m5stack/libs/driver/manifest.py | 1 + m5stack/libs/driver/tca8418.py | 369 +++++++++++++++++++ m5stack/libs/hardware/keyboard/__init__.py | 404 +++++++++++++++++++-- m5stack/libs/hardware/keyboard/asciimap.py | 21 +- m5stack/libs/hardware/matrix_keyboard.py | 18 +- 5 files changed, 776 insertions(+), 37 deletions(-) create mode 100644 m5stack/libs/driver/tca8418.py diff --git a/m5stack/libs/driver/manifest.py b/m5stack/libs/driver/manifest.py index d046c3d0..f20000fd 100644 --- a/m5stack/libs/driver/manifest.py +++ b/m5stack/libs/driver/manifest.py @@ -92,6 +92,7 @@ "sht30.py", "soft_timer.py", "sths34pf80.py", + "tca8418.py", "tcs3472.py", "timer_thread.py", "vl53l0x.py", diff --git a/m5stack/libs/driver/tca8418.py b/m5stack/libs/driver/tca8418.py new file mode 100644 index 00000000..19ed3a4a --- /dev/null +++ b/m5stack/libs/driver/tca8418.py @@ -0,0 +1,369 @@ +# SPDX-FileCopyrightText: 2017 Scott Shawcroft, written for Adafruit Industries +# SPDX-FileCopyrightText: Copyright (c) 2022 ladyada for Adafruit Industries +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import sys +import micropython + +if sys.platform != "esp32": + from typing import Optional + + +class RWBit: + """ + Single bit register that is readable and writeable. + + Values are `bool` + + :param int register_address: The register address to read the bit from + :param int bit: The bit index within the byte at ``register_address`` + :param int register_width: The number of bytes in the register. Defaults to 1. + :param bool lsb_first: Is the first byte we read from I2C the LSB? Defaults to true + + """ + + def __init__( + self, + register_address: int, + bit: int, + register_width: int = 1, + lsb_first: bool = True, + ) -> None: + self.bit_mask = 1 << (bit % 8) # the bitmask *within* the byte! + self.buffer = bytearray(register_width) + self.register_address = register_address + if lsb_first: + self.byte = bit // 8 # the byte number within the buffer + else: + self.byte = register_width - (bit // 8) # the byte number within the buffer + + def __get__( + self, + obj, + objtype=None, + ) -> bool: + i2c = obj.i2c if hasattr(obj, "i2c") else obj._i2c + address = obj._address if hasattr(obj, "_address") else obj.address + if i2c is None or address is None: + raise RuntimeError("I2C device not initialized or address not set") + i2c.readfrom_mem_into(address, self.register_address, self.buffer) + return bool(self.buffer[self.byte] & self.bit_mask) + + def __set__(self, obj, value: bool) -> None: + i2c = obj.i2c if hasattr(obj, "i2c") else obj._i2c + address = obj._address if hasattr(obj, "_address") else obj.address + if i2c and address: + i2c.readfrom_mem_into(address, self.register_address, self.buffer) + if value: + self.buffer[self.byte] |= self.bit_mask + else: + self.buffer[self.byte] &= ~self.bit_mask + i2c.writeto_mem(address, self.register_address, self.buffer) + + +class ROBit(RWBit): + """Single bit register that is read only. Subclass of `RWBit`. + + Values are `bool` + + :param int register_address: The register address to read the bit from + :param type bit: The bit index within the byte at ``register_address`` + :param int register_width: The number of bytes in the register. Defaults to 1. + + """ + + def __set__(self, obj, value: bool) -> None: + raise AttributeError() + + +class RWBits: + """ + Multibit register (less than a full byte) that is readable and writeable. + This must be within a byte register. + + Values are `int` between 0 and 2 ** ``num_bits`` - 1. + + :param int num_bits: The number of bits in the field. + :param int register_address: The register address to read the bit from + :param int lowest_bit: The lowest bits index within the byte at ``register_address`` + :param int register_width: The number of bytes in the register. Defaults to 1. + :param bool lsb_first: Is the first byte we read from I2C the LSB? Defaults to true + :param bool signed: If True, the value is a "two's complement" signed value. + If False, it is unsigned. + """ + + def __init__( + self, + num_bits: int, + register_address: int, + lowest_bit: int, + register_width: int = 1, + lsb_first: bool = True, + signed: bool = False, + ) -> None: + self.bit_mask = ((1 << num_bits) - 1) << lowest_bit + # print("bitmask: ",hex(self.bit_mask)) + if self.bit_mask >= 1 << (register_width * 8): + raise ValueError("Cannot have more bits than register size") + self.lowest_bit = lowest_bit + self.register_address = register_address + self.buffer = bytearray(register_width) + self.lsb_first = lsb_first + self.sign_bit = (1 << (num_bits - 1)) if signed else 0 + + def __get__(self, obj, objtype=None) -> int: + i2c = obj.i2c if hasattr(obj, "i2c") else obj._i2c + address = obj._address if hasattr(obj, "_address") else obj.address + if i2c is None or address is None: + raise RuntimeError("I2C device not initialized or address not set") + i2c.readfrom_mem_into(address, self.register_address, self.buffer) + # read the number of bytes into a single variable + reg = 0 + order = None + if not self.lsb_first: + order = range(len(self.buffer)) + else: + order = range(len(self.buffer) - 1, -1, -1) + for i in order: + reg = (reg << 8) | self.buffer[i] + reg = (reg & self.bit_mask) >> self.lowest_bit + # If the value is signed and negative, convert it + if reg & self.sign_bit: + reg -= 2 * self.sign_bit + return reg + + def __set__(self, obj, value: int) -> None: + value <<= self.lowest_bit # shift the value over to the right spot + i2c = obj.i2c if hasattr(obj, "i2c") else obj._i2c + address = obj._address if hasattr(obj, "_address") else obj.address + if i2c and address: + reg = 0 + order = range(len(self.buffer), 0, -1) + if not self.lsb_first: + order = range(1, len(self.buffer)) + for i in order: + reg = (reg << 8) | self.buffer[i] + # print("old reg: ", hex(reg)) + reg &= ~self.bit_mask # mask off the bits we're about to change + reg |= value # then or in our new value + # print("new reg: ", hex(reg)) + for i in reversed(order): + self.buffer[i] = reg & 0xFF + reg >>= 8 + i2c.writeto_mem(address, self.register_address, self.buffer) + + +class ROBits(RWBits): + """ + Multibit register (less than a full byte) that is read-only. This must be + within a byte register. + + Values are `int` between 0 and 2 ** ``num_bits`` - 1. + + :param int num_bits: The number of bits in the field. + :param int register_address: The register address to read the bit from + :param type lowest_bit: The lowest bits index within the byte at ``register_address`` + :param int register_width: The number of bytes in the register. Defaults to 1. + """ + + def __set__(self, obj, value: int) -> None: + raise AttributeError() + + +class TCA8418_register: + """A class for interacting with the TCA8418 registers + + :param TCA8418 tca: The associated TCA8418 object + :param int base_addr: The base address for this register + :param bool invert_value: Whether the value given should be interpreted as inverted + (True -> False), default is False (inputs are as-is, not inverted) + :param bool read_only: Whether the register is read-only or read/write, default + is False (register is read/write) + :param int|None initial_value: An initial value to provide to the register, default + is ``None`` (no default is provided) + """ + + def __init__( + self, + tca: "TCA8418", + base_addr: int, + invert_value: bool = False, + read_only: bool = False, + initial_value: Optional[int] = None, + ) -> None: + self._tca = tca + self._baseaddr = base_addr + self._invert = invert_value + self._ro = read_only + + # theres 3 registers in a row for each setting + if not read_only and initial_value is not None: + self._tca._write_reg(base_addr, initial_value) + self._tca._write_reg(base_addr + 1, initial_value) + self._tca._write_reg(base_addr + 2, initial_value) + + def __index__(self) -> int: + """Read all 18 bits of register data and return as one integer""" + val = self._tca._read_reg(self._baseaddr + 2) + val <<= 8 + val |= self._tca._read_reg(self._baseaddr + 1) + val <<= 8 + val |= self._tca._read_reg(self._baseaddr) + val &= 0x3FFFF + return val + + def __getitem__(self, pin_number: int) -> bool: + """Read the single bit at 'pin_number' offset""" + value = self._tca._get_gpio_register(self._baseaddr, pin_number) + if self._invert: + value = not value + return value + + def __setitem__(self, pin_number: int, value: bool) -> None: + """Set a single bit at 'pin_number' offset to 'value'""" + if self._ro: + raise NotImplementedError("Read only register") + if self._invert: + value = not value + self._tca._set_gpio_register(self._baseaddr, pin_number, value) + + +class TCA8418: + _TCA8418_REG_CONFIG = micropython.const(0x01) + _TCA8418_REG_INTSTAT = micropython.const(0x02) + _TCA8418_REG_KEYLCKEC = micropython.const(0x03) + _TCA8418_REG_KEYEVENT = micropython.const(0x04) + + _TCA8418_REG_GPIODATSTAT1 = micropython.const(0x14) + _TCA8418_REG_GPIODATOUT1 = micropython.const(0x17) + _TCA8418_REG_INTEN1 = micropython.const(0x1A) + _TCA8418_REG_KPGPIO1 = micropython.const(0x1D) + _TCA8418_REG_GPIOINTSTAT1 = micropython.const(0x11) + + _TCA8418_REG_EVTMODE1 = micropython.const(0x20) + _TCA8418_REG_GPIODIR1 = micropython.const(0x23) + _TCA8418_REG_INTLVL1 = micropython.const(0x26) + _TCA8418_REG_DEBOUNCEDIS1 = micropython.const(0x29) + _TCA8418_REG_GPIOPULL1 = micropython.const(0x2C) + + R0 = 0 + R1 = 1 + R2 = 2 + R3 = 3 + R4 = 4 + R5 = 5 + R6 = 6 + R7 = 7 + C0 = 8 + C1 = 9 + C2 = 10 + C3 = 11 + C4 = 12 + C5 = 13 + C6 = 14 + C7 = 15 + C8 = 16 + C9 = 17 + + events_count = ROBits(4, _TCA8418_REG_KEYLCKEC, 0) + cad_int = RWBit(_TCA8418_REG_INTSTAT, 4) + overflow_int = RWBit(_TCA8418_REG_INTSTAT, 3) + keylock_int = RWBit(_TCA8418_REG_INTSTAT, 2) + gpi_int = RWBit(_TCA8418_REG_INTSTAT, 1) + key_int = RWBit(_TCA8418_REG_INTSTAT, 0) + + gpi_event_while_locked = RWBit(_TCA8418_REG_CONFIG, 6) + overflow_mode = RWBit(_TCA8418_REG_CONFIG, 5) + int_retrigger = RWBit(_TCA8418_REG_CONFIG, 4) + overflow_intenable = RWBit(_TCA8418_REG_CONFIG, 3) + keylock_intenable = RWBit(_TCA8418_REG_CONFIG, 2) + GPI_intenable = RWBit(_TCA8418_REG_CONFIG, 1) + key_intenable = RWBit(_TCA8418_REG_CONFIG, 0) + + def __init__(self, i2c, address=0x34): + self._i2c = i2c + self._address = address + self._buf = bytearray(1) + + # disable all interrupt + self.enable_int = TCA8418_register(self, self._TCA8418_REG_INTEN1, initial_value=0) + self.gpio_int_status = TCA8418_register( + self, self._TCA8418_REG_GPIOINTSTAT1, read_only=True + ) + _ = self.gpio_int_status # read to clear + + # set all pins to inputs + self.gpio_direction = TCA8418_register(self, self._TCA8418_REG_GPIODIR1, initial_value=0) + # set all pins to GPIO + self.gpio_mode = TCA8418_register( + self, self._TCA8418_REG_KPGPIO1, invert_value=True, initial_value=0 + ) + self.keypad_mode = TCA8418_register(self, self._TCA8418_REG_KPGPIO1) + # set all pins low output + self.output_value = TCA8418_register(self, self._TCA8418_REG_GPIODATOUT1, initial_value=0) + self.input_value = TCA8418_register(self, self._TCA8418_REG_GPIODATSTAT1, read_only=True) + # enable all pullups + self.pullup = TCA8418_register( + self, self._TCA8418_REG_GPIOPULL1, invert_value=True, initial_value=0 + ) + # enable all debounce + self.debounce = TCA8418_register( + self, self._TCA8418_REG_DEBOUNCEDIS1, invert_value=True, initial_value=0 + ) + # default int on falling + self.int_on_rising = TCA8418_register(self, self._TCA8418_REG_INTLVL1, initial_value=0) + + # default no gpio in event queue + self.event_mode_fifo = TCA8418_register(self, self._TCA8418_REG_EVTMODE1, initial_value=0) + + # read in event queue + # print(self.events_count, "events") + while self.events_count: + _ = self.next_event # read and toss + + # reset interrutps + self._write_reg(self._TCA8418_REG_INTSTAT, 0x1F) + self.gpi_int = False + + @property + def next_event(self) -> int: + """The next key event""" + return self._read_reg(self._TCA8418_REG_KEYEVENT) + + def _set_gpio_register(self, reg_base_addr: int, pin_number: int, value: bool) -> None: + if not 0 <= pin_number <= 17: + raise ValueError("Pin number must be between 0 & 17") + reg_base_addr += pin_number // 8 + self._set_reg_bit(reg_base_addr, pin_number % 8, value) + + def _get_gpio_register(self, reg_base_addr: int, pin_number: int) -> bool: + if not 0 <= pin_number <= 17: + raise ValueError("Pin number must be between 0 & 17") + reg_base_addr += pin_number // 8 + return self._get_reg_bit(reg_base_addr, pin_number % 8) + + # register helpers + + def _set_reg_bit(self, addr: int, bitoffset: int, value: bool) -> None: + temp = self._read_reg(addr) + if value: + temp |= 1 << bitoffset + else: + temp &= ~(1 << bitoffset) + self._write_reg(addr, temp) + + def _get_reg_bit(self, addr: int, bitoffset: int) -> bool: + temp = self._read_reg(addr) + return bool(temp & (1 << bitoffset)) + + def _read_reg(self, addr: int) -> int: + self._i2c.readfrom_mem_into(self._address, addr, self._buf) + # print("readfrom_mem_into reg:", hex(addr), "val:", hex(self._buf[0])) + return self._buf[0] + + def _write_reg(self, addr: int, val: int) -> None: + # print("writeto_mem reg:", hex(addr), "val:", hex(val)) + self._buf[0] = val & 0xFF + self._i2c.writeto_mem(self._address, addr, self._buf) diff --git a/m5stack/libs/hardware/keyboard/__init__.py b/m5stack/libs/hardware/keyboard/__init__.py index 58f59da4..667c1ce4 100644 --- a/m5stack/libs/hardware/keyboard/__init__.py +++ b/m5stack/libs/hardware/keyboard/__init__.py @@ -2,19 +2,11 @@ # # SPDX-License-Identifier: MIT -from machine import Pin +import machine +import micropython from collections import namedtuple -from .asciimap import ( - KEY_BACKSPACE, - KEY_TAB, - KEY_FN, - KEY_LEFT_SHIFT, - KEY_ENTER, - KEY_LEFT_CTRL, - KEY_OPT, - KEY_LEFT_ALT, - kb_asciimap, -) +from . import asciimap +from driver import tca8418 Point2D = namedtuple("Point2D", ["x", "y"]) Chart = namedtuple("Chart", ["value", "x_1", "x_2"]) @@ -45,10 +37,10 @@ KeyValue(ord("0"), ord(")")), KeyValue(ord("-"), ord("_")), KeyValue(ord("="), ord("+")), - KeyValue(KEY_BACKSPACE, KEY_BACKSPACE), + KeyValue(asciimap.KEY_BACKSPACE, asciimap.KEY_BACKSPACE), ), ( - KeyValue(KEY_TAB, KEY_TAB), + KeyValue(asciimap.KEY_TAB, asciimap.KEY_TAB), KeyValue(ord("q"), ord("Q")), KeyValue(ord("w"), ord("W")), KeyValue(ord("e"), ord("E")), @@ -64,8 +56,8 @@ KeyValue(ord("\\"), ord("|")), ), ( - KeyValue(KEY_FN, KEY_FN), - KeyValue(KEY_LEFT_SHIFT, KEY_LEFT_SHIFT), + KeyValue(asciimap.KEY_FN, asciimap.KEY_FN), + KeyValue(asciimap.KEY_LEFT_SHIFT, asciimap.KEY_LEFT_SHIFT), KeyValue(ord("a"), ord("A")), KeyValue(ord("s"), ord("S")), KeyValue(ord("d"), ord("D")), @@ -77,12 +69,12 @@ KeyValue(ord("l"), ord("L")), KeyValue(ord(";"), ord(":")), KeyValue(ord("'"), ord('"')), - KeyValue(KEY_ENTER, KEY_ENTER), + KeyValue(asciimap.KEY_ENTER, asciimap.KEY_ENTER), ), ( - KeyValue(KEY_LEFT_CTRL, KEY_LEFT_CTRL), - KeyValue(KEY_OPT, KEY_OPT), - KeyValue(KEY_LEFT_ALT, KEY_LEFT_ALT), + KeyValue(asciimap.KEY_LEFT_CTRL, asciimap.KEY_LEFT_CTRL), + KeyValue(asciimap.KEY_LEFT_OPT, asciimap.KEY_LEFT_OPT), + KeyValue(asciimap.KEY_LEFT_ALT, asciimap.KEY_LEFT_ALT), KeyValue(ord("z"), ord("Z")), KeyValue(ord("x"), ord("X")), KeyValue(ord("c"), ord("C")), @@ -132,9 +124,15 @@ def reset(self): class Keyboard: - def __init__(self): - self.output_list = [Pin(id, Pin.OUT, Pin.PULL_DOWN) for id in (8, 9, 11)] - self.input_list = [Pin(id, Pin.IN, Pin.PULL_UP) for id in (13, 15, 3, 4, 5, 6, 7)] + def __init__( + self, row_pins: tuple = (8, 9, 11), col_pins: tuple = (13, 15, 3, 4, 5, 6, 7) + ) -> None: + self.output_list = [ + machine.Pin(pin, machine.Pin.OUT, machine.Pin.PULL_DOWN) for pin in row_pins + ] + self.input_list = [ + machine.Pin(pin, machine.Pin.IN, machine.Pin.PULL_UP) for pin in col_pins + ] for pin in self.output_list: pin.value(0) @@ -233,40 +231,40 @@ def update_keys_state(self): # Get special keys for i in self._key_list_buffer: # modifier - if self.get_key_value(i).first == KEY_FN: + if self.get_key_value(i).first == asciimap.KEY_FN: self._keys_state_buffer.fn = True continue - if self.get_key_value(i).first == KEY_OPT: + if self.get_key_value(i).first == asciimap.KEY_LEFT_OPT: self._keys_state_buffer.opt = True continue - if self.get_key_value(i).first == KEY_LEFT_CTRL: + if self.get_key_value(i).first == asciimap.KEY_LEFT_CTRL: self._keys_state_buffer.ctrl = True self._key_pos_modifier_keys.append(i) continue - if self.get_key_value(i).first == KEY_LEFT_SHIFT: + if self.get_key_value(i).first == asciimap.KEY_LEFT_SHIFT: self._keys_state_buffer.shift = True self._key_pos_modifier_keys.append(i) continue - if self.get_key_value(i).first == KEY_LEFT_ALT: + if self.get_key_value(i).first == asciimap.KEY_LEFT_ALT: self._keys_state_buffer.alt = True self._key_pos_modifier_keys.append(i) continue # function - if self.get_key_value(i).first == KEY_TAB: + if self.get_key_value(i).first == asciimap.KEY_TAB: self._keys_state_buffer.tab = True self._key_pos_hid_keys.append(i) continue - if self.get_key_value(i).first == KEY_BACKSPACE: + if self.get_key_value(i).first == asciimap.KEY_BACKSPACE: self._keys_state_buffer.delete = True self._key_pos_hid_keys.append(i) continue - if self.get_key_value(i).first == KEY_ENTER: + if self.get_key_value(i).first == asciimap.KEY_ENTER: self._keys_state_buffer.enter = True self._key_pos_hid_keys.append(i) continue @@ -286,10 +284,10 @@ def update_keys_state(self): for i in self._key_pos_hid_keys: k = self.get_key_value(i).first - if k in (KEY_TAB, KEY_BACKSPACE, KEY_ENTER): + if k in (asciimap.KEY_TAB, asciimap.KEY_BACKSPACE, asciimap.KEY_ENTER): self._keys_state_buffer.hid_keys.append(k) continue - key = kb_asciimap[k] + key = asciimap.kb_asciimap[k] if key: self._keys_state_buffer.hid_keys.append(key) @@ -312,3 +310,343 @@ def capslocked(self) -> bool: def set_caps_locked(self, isLocked: bool): self._is_caps_locked = isLocked + + +class KeyEventRaw: + state: bool = False + row: int = 0 + col: int = 0 + + def __init__(self, value) -> None: + self.decode(value) + + def decode(self, value: int) -> str: + # Decode the key event from a byte value + self.state = bool(value & 0x80) + value &= 0x7F + value -= 1 + self.row = value // 10 + self.col = value % 10 + + # Convert 7 * 8 matrix to 14 * 4 + col = self.row * 2 + if self.col > 3: + col += 1 + + row = (self.col + 4) % 4 + + self.row = row + self.col = col + + def __str__(self) -> str: + return f"KeyEventRaw(({self.row}, {self.col}), {'pressed' if self.state else 'released'})" + + +class KeyEvent: + state: bool = False + row: int = 0 + col: int = 0 + keycode: int = 0 + modifier_mask: int = 0 + + def __init__( + self, + raw_event: KeyEventRaw, + modifier_mask, + fn_state: bool = False, + is_capslock_locked=False, + ) -> None: + self.convert(raw_event, modifier_mask, fn_state, is_capslock_locked) + + def convert( + self, raw_event: KeyEventRaw, modifier_mask, fn_state: bool, is_capslock_locked + ) -> None: + self.state = raw_event.state + self.row = raw_event.row + self.col = raw_event.col + self.modifier_mask = modifier_mask + + if fn_state and (self.row, self.col) != (0, 2): # FN key + if (self.row, self.col) == (0, 0): # ESC key + self.keycode = asciimap.KEY_ESC + elif (self.row, self.col) == (0, 13): # delete key + self.keycode = asciimap.KEY_BACKSPACE + elif (self.row, self.col) == (2, 11): # up key + self.keycode = asciimap.KEY_UP + elif (self.row, self.col) == (3, 10): # left key + self.keycode = asciimap.KEY_LEFT + elif (self.row, self.col) == (3, 11): # down key + self.keycode = asciimap.KEY_DOWN + elif (self.row, self.col) == (3, 12): # right key + self.keycode = asciimap.KEY_RIGHT + return + + # self.toggle_state = (modifier_mask & asciimap.KEY_MOD_LCTRL) or (modifier_mask & asciimap.KEY_MOD_LSHIFT) or capslock_state or is_capslock_locked + if ( + self.modifier_mask & (asciimap.KEY_MOD_LSHIFT | asciimap.KEY_MOD_RSHIFT) + or is_capslock_locked + ): + self.keycode = _key_value_map[self.row][self.col].second + else: + self.keycode = _key_value_map[self.row][self.col].first + + def __str__(self) -> str: + return f"KeyEvent(({self.row}, {self.col}), {'pressed' if self.state else 'released'}, keycode: {self.keycode}, modifier_mask: {self.modifier_mask})" + + +class HIDInputReport: + + """HID Input Report for keyboard events. only supports cardputer keyboard layout.""" + + scancode = ( + b"\x35" # Keyboard ` and ~ + b"\x1e" # Keyboard 1 and ! + b"\x1f" # Keyboard 2 and @ + b"\x20" # Keyboard 3 and # + b"\x21" # Keyboard 4 and $ + b"\x22" # Keyboard 5 and % + b"\x23" # Keyboard 6 and ^ + b"\x24" # Keyboard 7 and & + b"\x25" # Keyboard 8 and * + b"\x26" # Keyboard 9 and ( + b"\x27" # Keyboard 0 and ) + b"\x2d" # Keyboard - and _ + b"\x2e" # Keyboard = and + + b"\x2a" # Keyboard DELETE (Backspace) + b"\x2b" # Keyboard TAB + b"\x14" # Keyboard q and Q + b"\x1a" # Keyboard w and W + b"\x08" # Keyboard e and E + b"\x15" # Keyboard r and R + b"\x17" # Keyboard t and T + b"\x1c" # Keyboard y and Y + b"\x18" # Keyboard u and U + b"\x0f" # Keyboard i and I + b"\x12" # Keyboard o and O + b"\x13" # Keyboard p and P + b"\x2f" # Keyboard [ and { + b"\x30" # Keyboard ] and } + b"\x31" # Keyboard \ and | + b"\x00" # No key pressed (FN POSITION) + b"\x00" # No key pressed (SHIFT POSITION) + b"\x04" # Keyboard a and A + b"\x16" # Keyboard s and S + b"\x07" # Keyboard d and D + b"\x09" # Keyboard f and F + b"\x0a" # Keyboard g and G + b"\x0b" # Keyboard h and H + b"\x0d" # Keyboard j and J + b"\x0e" # Keyboard k and K + b"\x0c" # Keyboard l and L + b"\x33" # Keyboard ; and : + b"\x34" # Keyboard ' and " + b"\x28" # Keyboard ENTER (Return) + b"\x00" # No key pressed (CTRL POSITION) + b"\x00" # No key pressed (META/OPT POSITION) + b"\x00" # No key pressed (ALT POSITION) + b"\x2c" # Keyboard z and Z + b"\x2d" # Keyboard x and X + b"\x2e" # Keyboard c and C + b"\x2f" # Keyboard v and V + b"\x30" # Keyboard b and B + b"\x31" # Keyboard n and N + b"\x32" # Keyboard m and M + b"\x33" # Keyboard , and < + b"\x34" # Keyboard . and > + b"\x35" # Keyboard / and ? + b"\x2c" # Keyboard SPACE (Spacebar) + ) + + def __init__(self): + self.modifiers = 0 + self.keypresses = bytearray(6) + self.done = False + + def set_modifier_mask(self, mask: int): + self.modifiers = mask + + def set_key(self, index: int, key: int): + if 0 <= index < 6: + self.keypresses[index] = key + + def append_key(self, row: int, col: int, fn_state: bool = False): + for i in range(6): + if self.keypresses[i] == 0: + keycode = self.scancode[row * 14 + col] + if fn_state: + if (row, col) == (0, 0): # ESC key + keycode = 0x29 + elif (row, col) == (0, 13): # delete key + keycode = 0x2A + elif (row, col) == (2, 11): # up key + keycode = 0x52 + elif (row, col) == (3, 10): # left key + keycode = 0x50 + elif (row, col) == (3, 11): # down key + keycode = 0x51 + elif (row, col) == (3, 12): # right key + keycode = 0x4F + + self.set_key(i, keycode) + if i == 5: + self.done = True + return + + def __str__(self) -> str: + return f"HIDInputReport(modifiers: {self.modifiers}, keys: {self.keypresses})" + + +class KeyboardI2C: + def __init__(self, i2c: machine.I2C, address: int = 0x34, intr_pin=None) -> None: + super().__init__() + self._is_caps_locked = False + self._last_key_size = 0 + + self._modifier_mask = 0 + self._shift_state = False + self._fn_state = False + self.keyevent_raw = KeyEventRaw(0) + self.keyevent = KeyEvent(self.keyevent_raw, 0, False, False) + + self._keyevents = [] + self._hid_reports = [] + + self._hid_report_callback = None + self._keyevent_callback = None + + self._i2c = i2c + self._address = address + self._intr_pin = intr_pin + self._tca = tca8418.TCA8418(i2c, address) + + keypad_pins = ( + tca8418.TCA8418.R0, + tca8418.TCA8418.R1, + tca8418.TCA8418.R2, + tca8418.TCA8418.R3, + tca8418.TCA8418.R4, + tca8418.TCA8418.R5, + tca8418.TCA8418.R6, + tca8418.TCA8418.C0, + tca8418.TCA8418.C1, + tca8418.TCA8418.C2, + tca8418.TCA8418.C3, + tca8418.TCA8418.C4, + tca8418.TCA8418.C5, + tca8418.TCA8418.C6, + tca8418.TCA8418.C7, + ) + + # make them inputs with pullups + for pin in keypad_pins: + self._tca.keypad_mode[pin] = True + # make sure the key pins generate FIFO events + self._tca.enable_int[pin] = True + # we will stick events into the FIFO queue + self._tca.event_mode_fifo[pin] = True + + if self._intr_pin is not None: + self._intr_pin.init(machine.Pin.IN, pull=None) + self._intr_pin.irq(self._irq_handler, machine.Pin.IRQ_FALLING) + + # turn on INT output pin + self._tca.key_intenable = True + + def set_hid_report_callback(self, callback): + """ + Set a callback function to be called when a HID report is ready. + The callback should accept one argument, which is the HIDInputReport object. + """ + self._hid_report_callback = callback + + def set_keyevent_callback(self, callback): + """ + Set a callback function to be called when a key event is ready. + The callback should accept one argument, which is the KeyEvent object. + """ + self._keyevent_callback = callback + + def _irq_handler(self, pin: machine.Pin): + if self._tca.key_int: + if self._tca.events_count: + events = bytearray() + while self._tca.events_count: + events.append(self._tca.next_event) + micropython.schedule(self.tick, events) + # clear interrupt + self._tca.key_int = True # clear the IRQ by writing 1 to it + + def tick(self, events): + count = len(events) + if count == 0: + return + + hid_report = HIDInputReport() + for i in range(count): + self.keyevent_raw.decode(events[i]) + self.update_modifier_mask(self.keyevent_raw) + + # hid report + hid_report.set_modifier_mask(self._modifier_mask) + hid_report.append_key(self.keyevent_raw.row, self.keyevent_raw.col, self._fn_state) + if hid_report.done: + # send the HID report + if self._hid_report_callback: + micropython.schedule( + self._hid_report_callback, + (self, hid_report.modifiers, hid_report.keypresses), + ) + else: + self._hid_reports.append(hid_report) + hid_report = HIDInputReport() + + # user key event + keyevent = KeyEvent( + self.keyevent_raw, self._modifier_mask, self._fn_state, self._is_caps_locked + ) + if self._keyevent_callback: + micropython.schedule( + self._keyevent_callback, (self, keyevent.keycode, keyevent.state) + ) + else: + # append to the key events list + self._keyevents.append(keyevent) + # print(keyevent) + + if not hid_report.done: + # send the HID report + if self._hid_report_callback: + micropython.schedule( + self._hid_report_callback, (self, hid_report.modifiers, hid_report.keypresses) + ) + else: + self._hid_reports.append(hid_report) + + def update_modifier_mask(self, keyevent_raw: KeyEventRaw): + if (keyevent_raw.row, keyevent_raw.col) == (2, 0): # FN + self._fn_state = keyevent_raw.state + + if (keyevent_raw.row, keyevent_raw.col) == (2, 1): # left shift + self._shift_state = keyevent_raw.state + if keyevent_raw.state: + self._modifier_mask |= asciimap.KEY_MOD_LSHIFT + else: + self._modifier_mask &= ~asciimap.KEY_MOD_LSHIFT + + if (keyevent_raw.row, keyevent_raw.col) == (3, 0): # left ctrl + if keyevent_raw.state: + self._modifier_mask |= asciimap.KEY_MOD_LCTRL + else: + self._modifier_mask &= ~asciimap.KEY_MOD_LCTRL + + if (keyevent_raw.row, keyevent_raw.col) == (3, 1): # left opt + if keyevent_raw.state: + self._modifier_mask |= asciimap.KEY_MOD_LMETA + else: + self._modifier_mask &= ~asciimap.KEY_MOD_LMETA + + if (keyevent_raw.row, keyevent_raw.col) == (3, 2): # left alt + if keyevent_raw.state: + self._modifier_mask |= asciimap.KEY_MOD_LALT + else: + self._modifier_mask &= ~asciimap.KEY_MOD_LALT diff --git a/m5stack/libs/hardware/keyboard/asciimap.py b/m5stack/libs/hardware/keyboard/asciimap.py index f5d7f55e..706a3a49 100644 --- a/m5stack/libs/hardware/keyboard/asciimap.py +++ b/m5stack/libs/hardware/keyboard/asciimap.py @@ -2,6 +2,18 @@ # # SPDX-License-Identifier: MIT +import micropython + +# Modifier masks +KEY_MOD_LCTRL = micropython.const(0x01) +KEY_MOD_LSHIFT = micropython.const(0x02) +KEY_MOD_LALT = micropython.const(0x04) +KEY_MOD_LMETA = micropython.const(0x08) # windows key or command key on macOS +KEY_MOD_RCTRL = micropython.const(0x10) +KEY_MOD_RSHIFT = micropython.const(0x20) +KEY_MOD_RALT = micropython.const(0x40) +KEY_MOD_RMETA = micropython.const(0x80) # right windows key or command key on macOS + SHIFT = 0x80 KEY_LEFT_CTRL = 0x80 @@ -9,12 +21,19 @@ KEY_LEFT_ALT = 0x82 KEY_FN = 0xFF -KEY_OPT = 0x00 +KEY_LEFT_OPT = 0xFE +KEY_ESC = 0x29 KEY_BACKSPACE = 0x2A KEY_TAB = 0x2B KEY_ENTER = 0x28 +# user defined keys +KEY_LEFT = 180 +KEY_UP = 181 +KEY_DOWN = 182 +KEY_RIGHT = 183 + # +-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+ # | NUL | SOH | STX | ETX | EOT | ENQ | ACK | BEL | BS | TAB | LF | VT | FF | CR | SO | SI | # +-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+ diff --git a/m5stack/libs/hardware/matrix_keyboard.py b/m5stack/libs/hardware/matrix_keyboard.py index b7105bc7..77f23988 100644 --- a/m5stack/libs/hardware/matrix_keyboard.py +++ b/m5stack/libs/hardware/matrix_keyboard.py @@ -2,15 +2,27 @@ # # SPDX-License-Identifier: MIT -from .keyboard import Keyboard -from micropython import schedule +from . import Keyboard +import micropython class MatrixKeyboard(Keyboard): + _instance = None + _initialized = False + + def __new__(cls): + if cls._instance is None: + cls._instance = super().__new__(cls) + return cls._instance + def __init__(self) -> None: + if self._initialized: + return + super().__init__() self._keys = [] self._handler = None + self._initialized = True def get_key(self) -> int: if self._keys: @@ -61,4 +73,4 @@ def tick(self) -> None: for word in status.word: self._keys.append(word) if self.is_pressed() and self._handler: - schedule(self._handler, self) + micropython.schedule(self._handler, self) From 9a4a08c0bbf699690cf930d7fc0d62c5d73a4abb Mon Sep 17 00:00:00 2001 From: lbuque <1102390310@qq.com> Date: Tue, 26 Aug 2025 18:21:38 +0800 Subject: [PATCH 227/322] libs/hardware/matrix_keyboard.py: KeyboardI2C compatible. Signed-off-by: lbuque <1102390310@qq.com> --- m5stack/libs/hardware/__init__.py | 1 + m5stack/libs/hardware/keyboard/__init__.py | 125 +++++++++++++-------- m5stack/libs/hardware/matrix_keyboard.py | 51 +++++++-- m5stack/libs/unit/cardkb.py | 2 +- 4 files changed, 122 insertions(+), 57 deletions(-) diff --git a/m5stack/libs/hardware/__init__.py b/m5stack/libs/hardware/__init__.py index 119d2203..0ac524ff 100644 --- a/m5stack/libs/hardware/__init__.py +++ b/m5stack/libs/hardware/__init__.py @@ -4,6 +4,7 @@ _attrs = { "Keyboard": "keyboard", + "KeyboardI2C": "keyboard", "Button": "button", "CAN": "can", "IR": "ir", diff --git a/m5stack/libs/hardware/keyboard/__init__.py b/m5stack/libs/hardware/keyboard/__init__.py index 667c1ce4..b6721778 100644 --- a/m5stack/libs/hardware/keyboard/__init__.py +++ b/m5stack/libs/hardware/keyboard/__init__.py @@ -37,7 +37,7 @@ KeyValue(ord("0"), ord(")")), KeyValue(ord("-"), ord("_")), KeyValue(ord("="), ord("+")), - KeyValue(asciimap.KEY_BACKSPACE, asciimap.KEY_BACKSPACE), + KeyValue(0x08, 0x08), ), ( KeyValue(asciimap.KEY_TAB, asciimap.KEY_TAB), @@ -69,7 +69,7 @@ KeyValue(ord("l"), ord("L")), KeyValue(ord(";"), ord(":")), KeyValue(ord("'"), ord('"')), - KeyValue(asciimap.KEY_ENTER, asciimap.KEY_ENTER), + KeyValue(0x0A, 0x0A), ), ( KeyValue(asciimap.KEY_LEFT_CTRL, asciimap.KEY_LEFT_CTRL), @@ -149,17 +149,19 @@ def __init__( @staticmethod def _set_output(pin_list, output): - output = output & 0b00000111 - pin_list[0].value(output & 0b00000001) - pin_list[1].value(output & 0b00000010) - pin_list[2].value(output & 0b00000100) + n = len(pin_list) + mask = (1 << n) - 1 + output = output & mask + for i in range(n): + pin_list[i].value(output & (1 << i)) @staticmethod def _get_input(pin_list): buffer = 0x00 pin_value = 0x00 + n = len(pin_list) - for i in range(7): + for i in range(n): pin_value = 0x00 if pin_list[i].value() == 1 else 0x01 pin_value = pin_value << i buffer = buffer | pin_value @@ -312,7 +314,7 @@ def set_caps_locked(self, isLocked: bool): self._is_caps_locked = isLocked -class KeyEventRaw: +class _KeyEventConverter: state: bool = False row: int = 0 col: int = 0 @@ -339,7 +341,7 @@ def decode(self, value: int) -> str: self.col = col def __str__(self) -> str: - return f"KeyEventRaw(({self.row}, {self.col}), {'pressed' if self.state else 'released'})" + return f"_KeyEventConverter(({self.row}, {self.col}), {'pressed' if self.state else 'released'})" class KeyEvent: @@ -351,7 +353,7 @@ class KeyEvent: def __init__( self, - raw_event: KeyEventRaw, + raw_event: _KeyEventConverter, modifier_mask, fn_state: bool = False, is_capslock_locked=False, @@ -359,7 +361,7 @@ def __init__( self.convert(raw_event, modifier_mask, fn_state, is_capslock_locked) def convert( - self, raw_event: KeyEventRaw, modifier_mask, fn_state: bool, is_capslock_locked + self, raw_event: _KeyEventConverter, modifier_mask, fn_state: bool, is_capslock_locked ) -> None: self.state = raw_event.state self.row = raw_event.row @@ -368,9 +370,9 @@ def convert( if fn_state and (self.row, self.col) != (0, 2): # FN key if (self.row, self.col) == (0, 0): # ESC key - self.keycode = asciimap.KEY_ESC + self.keycode = 0x1B # ascii ESC elif (self.row, self.col) == (0, 13): # delete key - self.keycode = asciimap.KEY_BACKSPACE + self.keycode = 0x7F # ascii DEL elif (self.row, self.col) == (2, 11): # up key self.keycode = asciimap.KEY_UP elif (self.row, self.col) == (3, 10): # left key @@ -399,6 +401,7 @@ class HIDInputReport: """HID Input Report for keyboard events. only supports cardputer keyboard layout.""" scancode = ( + # Row 0 b"\x35" # Keyboard ` and ~ b"\x1e" # Keyboard 1 and ! b"\x1f" # Keyboard 2 and @ @@ -413,6 +416,7 @@ class HIDInputReport: b"\x2d" # Keyboard - and _ b"\x2e" # Keyboard = and + b"\x2a" # Keyboard DELETE (Backspace) + # Row 1 b"\x2b" # Keyboard TAB b"\x14" # Keyboard q and Q b"\x1a" # Keyboard w and W @@ -427,6 +431,7 @@ class HIDInputReport: b"\x2f" # Keyboard [ and { b"\x30" # Keyboard ] and } b"\x31" # Keyboard \ and | + # Row 2 b"\x00" # No key pressed (FN POSITION) b"\x00" # No key pressed (SHIFT POSITION) b"\x04" # Keyboard a and A @@ -441,6 +446,7 @@ class HIDInputReport: b"\x33" # Keyboard ; and : b"\x34" # Keyboard ' and " b"\x28" # Keyboard ENTER (Return) + # Row 3 b"\x00" # No key pressed (CTRL POSITION) b"\x00" # No key pressed (META/OPT POSITION) b"\x00" # No key pressed (ALT POSITION) @@ -497,17 +503,22 @@ def __str__(self) -> str: class KeyboardI2C: - def __init__(self, i2c: machine.I2C, address: int = 0x34, intr_pin=None) -> None: + ASCII_MODE = 0 + HID_MODE = 1 + + def __init__( + self, i2c: machine.I2C, address: int = 0x34, intr_pin=None, mode=ASCII_MODE + ) -> None: super().__init__() self._is_caps_locked = False - self._last_key_size = 0 self._modifier_mask = 0 self._shift_state = False self._fn_state = False - self.keyevent_raw = KeyEventRaw(0) - self.keyevent = KeyEvent(self.keyevent_raw, 0, False, False) + self._keyevent_converter = _KeyEventConverter(0) + self._mode = mode + self._tick_handler = self._ascii_handler if mode == self.ASCII_MODE else self._hid_handler self._keyevents = [] self._hid_reports = [] @@ -552,6 +563,11 @@ def __init__(self, i2c: machine.I2C, address: int = 0x34, intr_pin=None) -> None # turn on INT output pin self._tca.key_intenable = True + def deinit(self): + if self._intr_pin is not None: + self._intr_pin.irq(None) + self._tca.key_int = True # clear the IRQ by writing 1 to it + def set_hid_report_callback(self, callback): """ Set a callback function to be called when a HID report is ready. @@ -572,23 +588,51 @@ def _irq_handler(self, pin: machine.Pin): events = bytearray() while self._tca.events_count: events.append(self._tca.next_event) - micropython.schedule(self.tick, events) + micropython.schedule(self._tick_handler, events) # clear interrupt self._tca.key_int = True # clear the IRQ by writing 1 to it - def tick(self, events): + def _ascii_handler(self, events): + count = len(events) + if count == 0: + return + + for i in range(count): + self._keyevent_converter.decode(events[i]) + self._update_modifier_mask(self._keyevent_converter) + + # ascii key event + # !!! release event is ignored + keyevent = KeyEvent( + self._keyevent_converter, self._modifier_mask, self._fn_state, self._is_caps_locked + ) + if self._keyevent_callback and keyevent.state: + micropython.schedule( + self._keyevent_callback, + self, # (self, keyevent.keycode, keyevent.state) + ) + else: + # append to the key events list + keyevent.state and self._keyevents.append(keyevent) + # print(keyevent) + + def _hid_handler(self, events): count = len(events) if count == 0: return hid_report = HIDInputReport() for i in range(count): - self.keyevent_raw.decode(events[i]) - self.update_modifier_mask(self.keyevent_raw) + self._keyevent_converter.decode(events[i]) + self._update_modifier_mask(self._keyevent_converter) # hid report hid_report.set_modifier_mask(self._modifier_mask) - hid_report.append_key(self.keyevent_raw.row, self.keyevent_raw.col, self._fn_state) + # !!! only append key if it's a press event + if self._keyevent_converter.state: + hid_report.append_key( + self._keyevent_converter.row, self._keyevent_converter.col, self._fn_state + ) if hid_report.done: # send the HID report if self._hid_report_callback: @@ -600,19 +644,6 @@ def tick(self, events): self._hid_reports.append(hid_report) hid_report = HIDInputReport() - # user key event - keyevent = KeyEvent( - self.keyevent_raw, self._modifier_mask, self._fn_state, self._is_caps_locked - ) - if self._keyevent_callback: - micropython.schedule( - self._keyevent_callback, (self, keyevent.keycode, keyevent.state) - ) - else: - # append to the key events list - self._keyevents.append(keyevent) - # print(keyevent) - if not hid_report.done: # send the HID report if self._hid_report_callback: @@ -622,31 +653,31 @@ def tick(self, events): else: self._hid_reports.append(hid_report) - def update_modifier_mask(self, keyevent_raw: KeyEventRaw): - if (keyevent_raw.row, keyevent_raw.col) == (2, 0): # FN - self._fn_state = keyevent_raw.state + def _update_modifier_mask(self, keyevent_converter: _KeyEventConverter): + if (keyevent_converter.row, keyevent_converter.col) == (2, 0): # FN + self._fn_state = keyevent_converter.state - if (keyevent_raw.row, keyevent_raw.col) == (2, 1): # left shift - self._shift_state = keyevent_raw.state - if keyevent_raw.state: + if (keyevent_converter.row, keyevent_converter.col) == (2, 1): # left shift + self._shift_state = keyevent_converter.state + if keyevent_converter.state: self._modifier_mask |= asciimap.KEY_MOD_LSHIFT else: self._modifier_mask &= ~asciimap.KEY_MOD_LSHIFT - if (keyevent_raw.row, keyevent_raw.col) == (3, 0): # left ctrl - if keyevent_raw.state: + if (keyevent_converter.row, keyevent_converter.col) == (3, 0): # left ctrl + if keyevent_converter.state: self._modifier_mask |= asciimap.KEY_MOD_LCTRL else: self._modifier_mask &= ~asciimap.KEY_MOD_LCTRL - if (keyevent_raw.row, keyevent_raw.col) == (3, 1): # left opt - if keyevent_raw.state: + if (keyevent_converter.row, keyevent_converter.col) == (3, 1): # left opt + if keyevent_converter.state: self._modifier_mask |= asciimap.KEY_MOD_LMETA else: self._modifier_mask &= ~asciimap.KEY_MOD_LMETA - if (keyevent_raw.row, keyevent_raw.col) == (3, 2): # left alt - if keyevent_raw.state: + if (keyevent_converter.row, keyevent_converter.col) == (3, 2): # left alt + if keyevent_converter.state: self._modifier_mask |= asciimap.KEY_MOD_LALT else: self._modifier_mask &= ~asciimap.KEY_MOD_LALT diff --git a/m5stack/libs/hardware/matrix_keyboard.py b/m5stack/libs/hardware/matrix_keyboard.py index 77f23988..88df42e8 100644 --- a/m5stack/libs/hardware/matrix_keyboard.py +++ b/m5stack/libs/hardware/matrix_keyboard.py @@ -3,51 +3,84 @@ # SPDX-License-Identifier: MIT from . import Keyboard +from . import KeyboardI2C import micropython +import machine +import M5 -class MatrixKeyboard(Keyboard): +class MatrixKeyboard: _instance = None _initialized = False def __new__(cls): if cls._instance is None: - cls._instance = super().__new__(cls) + cls._instance = super(MatrixKeyboard, cls).__new__(cls) return cls._instance def __init__(self) -> None: if self._initialized: return - super().__init__() + board_id = M5.getBoard() + if M5.BOARD.M5Cardputer == board_id: + self._keyboard = Keyboard() + elif M5.BOARD.M5CardputerADV == board_id: + i2c1 = machine.I2C(1, scl=machine.Pin(9), sda=machine.Pin(8), freq=100000) + self._keyboard = KeyboardI2C( + i2c1, intr_pin=machine.Pin(11), mode=KeyboardI2C.ASCII_MODE + ) + else: + self._keyboard = None + self._keys = [] self._handler = None self._initialized = True def get_key(self) -> int: + if self._keyboard and hasattr(self._keyboard, "_keyevents"): + if self._keyboard._keyevents: + keyevent = self._keyboard._keyevents.pop(0) + return keyevent.keycode + else: + return None + if self._keys: return self._keys.pop(0) else: return None def get_string(self) -> str: - return chr(self.get_key()) + key = self.get_key() + return chr(key) if key is not None else None def is_pressed(self) -> bool: + if self._keyboard and hasattr(self._keyboard, "_keyevents"): + if self._keyboard._keyevents: + return True + else: + return False + if self._keys: return True else: return False def set_callback(self, handler) -> None: + if isinstance(self._keyboard, KeyboardI2C): + self._keyboard.set_keyevent_callback(handler) + return self._handler = handler def tick(self) -> None: - self.update_key_list() - self.update_keys_state() - if self.is_change(): - if super().is_pressed(): - status = self.keys_state() + if not self._keyboard or not hasattr(self._keyboard, "update_key_list"): + return + + self._keyboard.update_key_list() + self._keyboard.update_keys_state() + if self._keyboard.is_change(): + if self._keyboard.is_pressed(): + status = self._keyboard.keys_state() if status.tab: self._keys.append(0x09) elif status.enter: diff --git a/m5stack/libs/unit/cardkb.py b/m5stack/libs/unit/cardkb.py index 74b6af22..5c66fc1b 100644 --- a/m5stack/libs/unit/cardkb.py +++ b/m5stack/libs/unit/cardkb.py @@ -10,7 +10,7 @@ class KeyCode: KEYCODE_UNKNOWN = 0x00 KEYCODE_BACKSPACE = 0x08 KEYCODE_TAB = 0x09 - KEYCODE_ENTER = 0x0D + KEYCODE_ENTER = 0x0A KEYCODE_ESC = 0x1B KEYCODE_SPACE = 0x20 KEYCODE_DEL = 0x7F From eee1c368432afa88974760194ded0af6d8eec2ac Mon Sep 17 00:00:00 2001 From: lbuque <1102390310@qq.com> Date: Tue, 26 Aug 2025 18:23:17 +0800 Subject: [PATCH 228/322] modules/startup/cardputeradv: Fix keyboard input errors. Signed-off-by: lbuque <1102390310@qq.com> --- .../startup/cardputer/apps/app_list.py | 8 ++--- .../modules/startup/cardputer/apps/app_run.py | 8 ++--- .../startup/cardputer/apps/launcher.py | 7 +++-- .../startup/cardputer/apps/settings.py | 30 ++++++++++--------- .../modules/startup/cardputer/framework.py | 3 +- .../startup/cardputeradv/apps/app_list.py | 8 ++--- .../startup/cardputeradv/apps/app_run.py | 8 ++--- .../startup/cardputeradv/apps/launcher.py | 7 +++-- .../startup/cardputeradv/apps/settings.py | 30 ++++++++++--------- .../modules/startup/cardputeradv/framework.py | 5 ++-- 10 files changed, 61 insertions(+), 53 deletions(-) diff --git a/m5stack/modules/startup/cardputer/apps/app_list.py b/m5stack/modules/startup/cardputer/apps/app_list.py index 7a93b235..baa7e9cf 100644 --- a/m5stack/modules/startup/cardputer/apps/app_list.py +++ b/m5stack/modules/startup/cardputer/apps/app_list.py @@ -9,7 +9,7 @@ import esp32 import machine import os -import sys +from unit import KeyCode class Rectangle: @@ -273,12 +273,12 @@ def _btn_always_event_handler(self, event): machine.reset() async def _kb_event_handler(self, event, fw): - if event.key == 182: # down key + if event.key == KeyCode.KEYCODE_DOWN: # down key self._btn_down_event_handler(None) - if event.key == 181: # up key + if event.key == KeyCode.KEYCODE_UP: # up key self._btn_up_event_handler(None) - if event.key == 0x0D: # Enter key + if event.key == KeyCode.KEYCODE_ENTER: # Enter key self._btn_once_event_handler(event) elif event.key in (ord("a"), ord("A")): self._btn_always_event_handler(event) diff --git a/m5stack/modules/startup/cardputer/apps/app_run.py b/m5stack/modules/startup/cardputer/apps/app_run.py index 29505c03..e7cb1cd2 100644 --- a/m5stack/modules/startup/cardputer/apps/app_run.py +++ b/m5stack/modules/startup/cardputer/apps/app_run.py @@ -8,9 +8,9 @@ import M5 import esp32 import machine -import sys import os import time +from unit import KeyCode try: import M5Things @@ -148,13 +148,13 @@ def _get_file_info(path) -> tuple(str, str, str): return (mtime, account, ver) async def _kb_event_handler(self, event, fw): - if event.key == 183: # Right key + if event.key == KeyCode.KEYCODE_RIGHT: # Right key M5.Lcd.drawImage(res.RUN_ONCE_UNSELECT_IMG, 6, 100) M5.Lcd.drawImage(res.RUN_ALWAYS_SELECT_IMG, 123, 100) self._enter_handler = self._handle_run_always - elif event.key == 180: # Left key + elif event.key == KeyCode.KEYCODE_LEFT: # Left key M5.Lcd.drawImage(res.RUN_ONCE_SELECT_IMG, 6, 100) M5.Lcd.drawImage(res.RUN_ALWAYS_UNSELECT_IMG, 123, 100) self._enter_handler = self._handle_run_once - elif event.key == 0x0D: # Enter key + elif event.key == KeyCode.KEYCODE_ENTER: # Enter key self._enter_handler(fw) diff --git a/m5stack/modules/startup/cardputer/apps/launcher.py b/m5stack/modules/startup/cardputer/apps/launcher.py index 43dd98c8..d51fd2ae 100644 --- a/m5stack/modules/startup/cardputer/apps/launcher.py +++ b/m5stack/modules/startup/cardputer/apps/launcher.py @@ -7,6 +7,7 @@ import widgets import M5 from collections import namedtuple +from unit import KeyCode Icon = namedtuple("Icon", ["name", "src"]) @@ -100,13 +101,13 @@ async def _kb_event_handler(self, event, fw): left = 0 right = 0 refresh = False - if event.key == 183: # right key + if event.key == KeyCode.KEYCODE_RIGHT: # right key self._id = self._id + 1 if self._id + 1 < len(self._icos) else 0 left = (len(self._icos) - 1) if self._id - 1 < 0 else self._id - 1 right = 0 if self._id + 1 > (len(self._icos) - 1) else self._id + 1 refresh = True - if event.key == 180: # left key + if event.key == KeyCode.KEYCODE_LEFT: # left key self._id = self._id - 1 if self._id - 1 >= 0 else (len(self._icos) - 1) left = (len(self._icos) - 1) if self._id - 1 < 0 else self._id - 1 right = 0 if self._id + 1 > (len(self._icos) - 1) else self._id + 1 @@ -120,6 +121,6 @@ async def _kb_event_handler(self, event, fw): self._right_img.set_src(self._icos[right].src) self._right_label.set_text(self._icos[right].name) - if event.key == 0x0D: + if event.key == KeyCode.KEYCODE_ENTER: app = fw._app_selector.index(self._id + 1) app.start() diff --git a/m5stack/modules/startup/cardputer/apps/settings.py b/m5stack/modules/startup/cardputer/apps/settings.py index f86ae903..36658935 100644 --- a/m5stack/modules/startup/cardputer/apps/settings.py +++ b/m5stack/modules/startup/cardputer/apps/settings.py @@ -7,6 +7,7 @@ import widgets import M5 import esp32 +from unit import KeyCode class WiFiSettingApp(app_base.AppBase): @@ -105,21 +106,22 @@ def on_exit(self): ) async def _kb_event_handler(self, event, fw): - if event.key == 182: # down key + if event.key == KeyCode.KEYCODE_DOWN: # down key self._option, view_fn = self._menu_selector.next() view_fn() event.status = True - elif event.key == 181: # up key + elif event.key == KeyCode.KEYCODE_UP: # up key self._option, view_fn = self._menu_selector.prev() view_fn() event.status = True - if event.key == 0x0D and self._option == 4: # Enter key + if event.key == KeyCode.KEYCODE_ENTER and self._option == 4: # Enter key self._option, view_fn = self._menu_selector.current() view_fn() + self.set_data() event.status = True - if event.key == 0x1B: # ESC key + if event.key == KeyCode.KEYCODE_ESC: # ESC key self.ssid_tmp = self.ssid self.psk_tmp = self.psk self.server_tmp = self.server @@ -127,7 +129,7 @@ async def _kb_event_handler(self, event, fw): view_fn() event.status = True - if event.key == 0x08 and self._option in (1, 2, 3): + if event.key == KeyCode.KEYCODE_BACKSPACE and self._option in (1, 2, 3): print("backspace") if self._option == 1: self.ssid_tmp = self.ssid_tmp[:-1] @@ -307,7 +309,7 @@ def _handle_boot_option(self, fw): self._option_img.set_src(self._boot_options.get(self._option)) async def _kb_event_handler(self, event, fw): - if event.key == 0x0D: # Enter key + if event.key == KeyCode.KEYCODE_ENTER: # Enter key self._handle_boot_option(fw) event.status = True @@ -373,7 +375,7 @@ def _handle_option(self, fw): self._option_img.set_src(self._comlink_options.get(self._option)) async def _kb_event_handler(self, event, fw): - if event.key == 0x0D: # Enter key + if event.key == KeyCode.KEYCODE_ENTER: # Enter key self._handle_option(fw) event.status = True @@ -451,7 +453,7 @@ def approximate(number): return v async def _kb_event_handler(self, event, fw): - if event.key == 0x0D: # Enter key + if event.key == KeyCode.KEYCODE_ENTER: # Enter key self._handle_brightness(fw) event.status = True @@ -493,15 +495,15 @@ def stop(self): super().stop() async def _kb_event_handler(self, event, fw): - if event.key == 182: # down key + if event.key == KeyCode.KEYCODE_DOWN: # down key self._menu_selector.current().pause() app = self._menu_selector.next().resume() event.status = True - elif event.key == 181: # up key + elif event.key == KeyCode.KEYCODE_UP: # up key self._menu_selector.current().pause() self._menu_selector.prev().resume() event.status = True - elif event.key == 0x0D: # Enter key + elif event.key == KeyCode.KEYCODE_ENTER: # Enter key app = self._menu_selector.current() await app._kb_event_handler(event, fw) @@ -593,12 +595,12 @@ async def _kb_event_handler(self, event, fw): await self._app._kb_event_handler(event, fw) return - if event.key == 0x0D: # Enter key + if event.key == KeyCode.KEYCODE_ENTER: # Enter key self._app = self._menu_selector.current() print("current app:", self._app) await fw.load(self._app) event.status = True - elif event.key == 182: # down key + elif event.key == KeyCode.KEYCODE_DOWN: # down key self._menu_selector.index(1) self._imgs[0].set_src(res.CARD_228x32_UNSELECT_IMG) self._icos[0].refresh() @@ -613,7 +615,7 @@ async def _kb_event_handler(self, event, fw): M5.Lcd.drawImage(res.CARET_RIGHT, 213, 63) event.status = True - elif event.key == 181: # up key + elif event.key == KeyCode.KEYCODE_UP: # up key self._menu_selector.index(0) self._imgs[0].set_src(res.CARD_228x32_SELECT_IMG) self._icos[0].refresh() diff --git a/m5stack/modules/startup/cardputer/framework.py b/m5stack/modules/startup/cardputer/framework.py index 50f86070..1fb2f24f 100644 --- a/m5stack/modules/startup/cardputer/framework.py +++ b/m5stack/modules/startup/cardputer/framework.py @@ -7,6 +7,7 @@ import M5 import gc import asyncio +from unit import KeyCode class KeyEvent: @@ -87,7 +88,7 @@ async def handle_input(self, event: KeyEvent): await app._kb_event_handler(event, self) if event.status is False: - if event.key == 96: # ESC key + if event.key == KeyCode.KEYCODE_ESC: # ESC key app = self._app_selector.current() if hasattr(app, "_btna_event_handler"): await app._btna_event_handler(self) diff --git a/m5stack/modules/startup/cardputeradv/apps/app_list.py b/m5stack/modules/startup/cardputeradv/apps/app_list.py index 7a93b235..baa7e9cf 100644 --- a/m5stack/modules/startup/cardputeradv/apps/app_list.py +++ b/m5stack/modules/startup/cardputeradv/apps/app_list.py @@ -9,7 +9,7 @@ import esp32 import machine import os -import sys +from unit import KeyCode class Rectangle: @@ -273,12 +273,12 @@ def _btn_always_event_handler(self, event): machine.reset() async def _kb_event_handler(self, event, fw): - if event.key == 182: # down key + if event.key == KeyCode.KEYCODE_DOWN: # down key self._btn_down_event_handler(None) - if event.key == 181: # up key + if event.key == KeyCode.KEYCODE_UP: # up key self._btn_up_event_handler(None) - if event.key == 0x0D: # Enter key + if event.key == KeyCode.KEYCODE_ENTER: # Enter key self._btn_once_event_handler(event) elif event.key in (ord("a"), ord("A")): self._btn_always_event_handler(event) diff --git a/m5stack/modules/startup/cardputeradv/apps/app_run.py b/m5stack/modules/startup/cardputeradv/apps/app_run.py index 29505c03..e7cb1cd2 100644 --- a/m5stack/modules/startup/cardputeradv/apps/app_run.py +++ b/m5stack/modules/startup/cardputeradv/apps/app_run.py @@ -8,9 +8,9 @@ import M5 import esp32 import machine -import sys import os import time +from unit import KeyCode try: import M5Things @@ -148,13 +148,13 @@ def _get_file_info(path) -> tuple(str, str, str): return (mtime, account, ver) async def _kb_event_handler(self, event, fw): - if event.key == 183: # Right key + if event.key == KeyCode.KEYCODE_RIGHT: # Right key M5.Lcd.drawImage(res.RUN_ONCE_UNSELECT_IMG, 6, 100) M5.Lcd.drawImage(res.RUN_ALWAYS_SELECT_IMG, 123, 100) self._enter_handler = self._handle_run_always - elif event.key == 180: # Left key + elif event.key == KeyCode.KEYCODE_LEFT: # Left key M5.Lcd.drawImage(res.RUN_ONCE_SELECT_IMG, 6, 100) M5.Lcd.drawImage(res.RUN_ALWAYS_UNSELECT_IMG, 123, 100) self._enter_handler = self._handle_run_once - elif event.key == 0x0D: # Enter key + elif event.key == KeyCode.KEYCODE_ENTER: # Enter key self._enter_handler(fw) diff --git a/m5stack/modules/startup/cardputeradv/apps/launcher.py b/m5stack/modules/startup/cardputeradv/apps/launcher.py index 43dd98c8..d51fd2ae 100644 --- a/m5stack/modules/startup/cardputeradv/apps/launcher.py +++ b/m5stack/modules/startup/cardputeradv/apps/launcher.py @@ -7,6 +7,7 @@ import widgets import M5 from collections import namedtuple +from unit import KeyCode Icon = namedtuple("Icon", ["name", "src"]) @@ -100,13 +101,13 @@ async def _kb_event_handler(self, event, fw): left = 0 right = 0 refresh = False - if event.key == 183: # right key + if event.key == KeyCode.KEYCODE_RIGHT: # right key self._id = self._id + 1 if self._id + 1 < len(self._icos) else 0 left = (len(self._icos) - 1) if self._id - 1 < 0 else self._id - 1 right = 0 if self._id + 1 > (len(self._icos) - 1) else self._id + 1 refresh = True - if event.key == 180: # left key + if event.key == KeyCode.KEYCODE_LEFT: # left key self._id = self._id - 1 if self._id - 1 >= 0 else (len(self._icos) - 1) left = (len(self._icos) - 1) if self._id - 1 < 0 else self._id - 1 right = 0 if self._id + 1 > (len(self._icos) - 1) else self._id + 1 @@ -120,6 +121,6 @@ async def _kb_event_handler(self, event, fw): self._right_img.set_src(self._icos[right].src) self._right_label.set_text(self._icos[right].name) - if event.key == 0x0D: + if event.key == KeyCode.KEYCODE_ENTER: app = fw._app_selector.index(self._id + 1) app.start() diff --git a/m5stack/modules/startup/cardputeradv/apps/settings.py b/m5stack/modules/startup/cardputeradv/apps/settings.py index f86ae903..36658935 100644 --- a/m5stack/modules/startup/cardputeradv/apps/settings.py +++ b/m5stack/modules/startup/cardputeradv/apps/settings.py @@ -7,6 +7,7 @@ import widgets import M5 import esp32 +from unit import KeyCode class WiFiSettingApp(app_base.AppBase): @@ -105,21 +106,22 @@ def on_exit(self): ) async def _kb_event_handler(self, event, fw): - if event.key == 182: # down key + if event.key == KeyCode.KEYCODE_DOWN: # down key self._option, view_fn = self._menu_selector.next() view_fn() event.status = True - elif event.key == 181: # up key + elif event.key == KeyCode.KEYCODE_UP: # up key self._option, view_fn = self._menu_selector.prev() view_fn() event.status = True - if event.key == 0x0D and self._option == 4: # Enter key + if event.key == KeyCode.KEYCODE_ENTER and self._option == 4: # Enter key self._option, view_fn = self._menu_selector.current() view_fn() + self.set_data() event.status = True - if event.key == 0x1B: # ESC key + if event.key == KeyCode.KEYCODE_ESC: # ESC key self.ssid_tmp = self.ssid self.psk_tmp = self.psk self.server_tmp = self.server @@ -127,7 +129,7 @@ async def _kb_event_handler(self, event, fw): view_fn() event.status = True - if event.key == 0x08 and self._option in (1, 2, 3): + if event.key == KeyCode.KEYCODE_BACKSPACE and self._option in (1, 2, 3): print("backspace") if self._option == 1: self.ssid_tmp = self.ssid_tmp[:-1] @@ -307,7 +309,7 @@ def _handle_boot_option(self, fw): self._option_img.set_src(self._boot_options.get(self._option)) async def _kb_event_handler(self, event, fw): - if event.key == 0x0D: # Enter key + if event.key == KeyCode.KEYCODE_ENTER: # Enter key self._handle_boot_option(fw) event.status = True @@ -373,7 +375,7 @@ def _handle_option(self, fw): self._option_img.set_src(self._comlink_options.get(self._option)) async def _kb_event_handler(self, event, fw): - if event.key == 0x0D: # Enter key + if event.key == KeyCode.KEYCODE_ENTER: # Enter key self._handle_option(fw) event.status = True @@ -451,7 +453,7 @@ def approximate(number): return v async def _kb_event_handler(self, event, fw): - if event.key == 0x0D: # Enter key + if event.key == KeyCode.KEYCODE_ENTER: # Enter key self._handle_brightness(fw) event.status = True @@ -493,15 +495,15 @@ def stop(self): super().stop() async def _kb_event_handler(self, event, fw): - if event.key == 182: # down key + if event.key == KeyCode.KEYCODE_DOWN: # down key self._menu_selector.current().pause() app = self._menu_selector.next().resume() event.status = True - elif event.key == 181: # up key + elif event.key == KeyCode.KEYCODE_UP: # up key self._menu_selector.current().pause() self._menu_selector.prev().resume() event.status = True - elif event.key == 0x0D: # Enter key + elif event.key == KeyCode.KEYCODE_ENTER: # Enter key app = self._menu_selector.current() await app._kb_event_handler(event, fw) @@ -593,12 +595,12 @@ async def _kb_event_handler(self, event, fw): await self._app._kb_event_handler(event, fw) return - if event.key == 0x0D: # Enter key + if event.key == KeyCode.KEYCODE_ENTER: # Enter key self._app = self._menu_selector.current() print("current app:", self._app) await fw.load(self._app) event.status = True - elif event.key == 182: # down key + elif event.key == KeyCode.KEYCODE_DOWN: # down key self._menu_selector.index(1) self._imgs[0].set_src(res.CARD_228x32_UNSELECT_IMG) self._icos[0].refresh() @@ -613,7 +615,7 @@ async def _kb_event_handler(self, event, fw): M5.Lcd.drawImage(res.CARET_RIGHT, 213, 63) event.status = True - elif event.key == 181: # up key + elif event.key == KeyCode.KEYCODE_UP: # up key self._menu_selector.index(0) self._imgs[0].set_src(res.CARD_228x32_SELECT_IMG) self._icos[0].refresh() diff --git a/m5stack/modules/startup/cardputeradv/framework.py b/m5stack/modules/startup/cardputeradv/framework.py index 50f86070..cbc8c5bc 100644 --- a/m5stack/modules/startup/cardputeradv/framework.py +++ b/m5stack/modules/startup/cardputeradv/framework.py @@ -7,6 +7,7 @@ import M5 import gc import asyncio +from unit import KeyCode class KeyEvent: @@ -62,7 +63,7 @@ async def run(self): while True: M5.update() - kb.tick() + # kb.tick() if kb.is_pressed(): M5.Speaker.tone(3500, 50) event.key = kb.get_key() @@ -87,7 +88,7 @@ async def handle_input(self, event: KeyEvent): await app._kb_event_handler(event, self) if event.status is False: - if event.key == 96: # ESC key + if event.key == KeyCode.KEYCODE_ESC: # ESC key app = self._app_selector.current() if hasattr(app, "_btna_event_handler"): await app._btna_event_handler(self) From 47981d794193dfa72ec6674fd5174f7b44d828af Mon Sep 17 00:00:00 2001 From: lbuque <1102390310@qq.com> Date: Tue, 26 Aug 2025 18:24:35 +0800 Subject: [PATCH 229/322] tools/littlefs/littlefs2.c: Fix board name matching errors. Signed-off-by: lbuque <1102390310@qq.com> --- tools/littlefs/littlefs2.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/tools/littlefs/littlefs2.c b/tools/littlefs/littlefs2.c index fec0becb..438ed150 100644 --- a/tools/littlefs/littlefs2.c +++ b/tools/littlefs/littlefs2.c @@ -146,7 +146,14 @@ int cpt_scan_files(const char *board, const char *basePath, char (*file_path)[25 sprintf(path_new, "%s/%s", basePath, ptr->d_name); if (strstr(path_new, "system/") != NULL) { if (strstr(path_new, "system/common") == NULL) { - if (strstr(path_new, board) == NULL) { + // Check for exact board match - ensure board name is either at end or followed by '/' + char *board_pos = strstr(path_new, board); + if (board_pos == NULL) { + continue; + } + // Check if it's an exact match (not a substring) + char next_char = board_pos[strlen(board)]; + if (next_char != '\0' && next_char != '/') { continue; } } From d11408d2081aacddd42f0a491a165860f603b55a Mon Sep 17 00:00:00 2001 From: lbuque <1102390310@qq.com> Date: Wed, 27 Aug 2025 18:24:01 +0800 Subject: [PATCH 230/322] libs/hardware/keyboard: Fix issue where interrupts may not trigger. Signed-off-by: lbuque <1102390310@qq.com> --- m5stack/libs/hardware/keyboard/__init__.py | 5 ++++- m5stack/libs/hardware/matrix_keyboard.py | 4 +++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/m5stack/libs/hardware/keyboard/__init__.py b/m5stack/libs/hardware/keyboard/__init__.py index b6721778..5b8a2ade 100644 --- a/m5stack/libs/hardware/keyboard/__init__.py +++ b/m5stack/libs/hardware/keyboard/__init__.py @@ -557,12 +557,15 @@ def __init__( self._tca.event_mode_fifo[pin] = True if self._intr_pin is not None: - self._intr_pin.init(machine.Pin.IN, pull=None) + # self._intr_pin.init(machine.Pin.IN, pull=None) self._intr_pin.irq(self._irq_handler, machine.Pin.IRQ_FALLING) # turn on INT output pin self._tca.key_intenable = True + # Magic Code + self._irq_handler(self._intr_pin) + def deinit(self): if self._intr_pin is not None: self._intr_pin.irq(None) diff --git a/m5stack/libs/hardware/matrix_keyboard.py b/m5stack/libs/hardware/matrix_keyboard.py index 88df42e8..9195abe7 100644 --- a/m5stack/libs/hardware/matrix_keyboard.py +++ b/m5stack/libs/hardware/matrix_keyboard.py @@ -28,7 +28,9 @@ def __init__(self) -> None: elif M5.BOARD.M5CardputerADV == board_id: i2c1 = machine.I2C(1, scl=machine.Pin(9), sda=machine.Pin(8), freq=100000) self._keyboard = KeyboardI2C( - i2c1, intr_pin=machine.Pin(11), mode=KeyboardI2C.ASCII_MODE + i2c1, + intr_pin=machine.Pin(11, mode=machine.Pin.IN, pull=None), + mode=KeyboardI2C.ASCII_MODE, ) else: self._keyboard = None From cec6531917b82d8a4ce8d3cd539a7b8790bbc883 Mon Sep 17 00:00:00 2001 From: lbuque <1102390310@qq.com> Date: Thu, 4 Sep 2025 15:05:36 +0800 Subject: [PATCH 231/322] libs/hardware/ir.py: Fix CardputerADV initialization IR failure. Signed-off-by: lbuque <1102390310@qq.com> --- m5stack/libs/hardware/ir.py | 1 + 1 file changed, 1 insertion(+) diff --git a/m5stack/libs/hardware/ir.py b/m5stack/libs/hardware/ir.py index 11319644..6fb45aef 100644 --- a/m5stack/libs/hardware/ir.py +++ b/m5stack/libs/hardware/ir.py @@ -15,6 +15,7 @@ def __init__(self) -> None: M5.BOARD.M5AtomS3U: (None, 12), M5.BOARD.M5Capsule: (None, 4), M5.BOARD.M5Cardputer: (None, 44), + M5.BOARD.M5CardputerADV: (None, 44), M5.BOARD.M5StickCPlus: (None, 9), M5.BOARD.M5StickC: (None, 9), M5.BOARD.M5StickCPlus2: (None, 19), From 793b9d84c0adcfc676aadd76fbd3d2d603414845 Mon Sep 17 00:00:00 2001 From: lbuque <1102390310@qq.com> Date: Thu, 4 Sep 2025 16:44:13 +0800 Subject: [PATCH 232/322] machine_rtc.c: Fix timezone and time reading errors. Signed-off-by: lbuque <1102390310@qq.com> --- m5stack/machine_rtc.c | 35 ++++++++++++++++++----------------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/m5stack/machine_rtc.c b/m5stack/machine_rtc.c index c83bb55d..7bae4598 100644 --- a/m5stack/machine_rtc.c +++ b/m5stack/machine_rtc.c @@ -5,7 +5,6 @@ * * Copyright (c) 2017 "Eric Poulsen" * Copyright (c) 2017 "Tom Manning" - * Copyright (c) 2024 M5Stack Technology CO LTD * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -37,11 +36,10 @@ #include "py/obj.h" #include "py/runtime.h" #include "py/mphal.h" +#include "extmod/modmachine.h" #include "shared/timeutils/timeutils.h" -#include "modmachine.h" #include "machine_rtc.h" #include "uiflow_utility.h" -#include "extmod/modmachine.h" typedef struct _machine_rtc_obj_t { mp_obj_base_t base; @@ -109,15 +107,14 @@ static mp_obj_t machine_rtc_datetime_helper(mp_uint_t n_args, const mp_obj_t *ar // Get time struct timeval tv; - gettimeofday(&tv, NULL); - timeutils_struct_time_t tm; - timeutils_seconds_since_epoch_to_struct_time(tv.tv_sec, &tm); + struct tm tm; + gmtime_r(&tv.tv_sec, &tm); mp_obj_t tuple[8] = { - mp_obj_new_int(tm.tm_year), - mp_obj_new_int(tm.tm_mon), + mp_obj_new_int(tm.tm_year + 1900), + mp_obj_new_int(tm.tm_mon + 1), mp_obj_new_int(tm.tm_mday), mp_obj_new_int(tm.tm_wday), mp_obj_new_int(tm.tm_hour), @@ -204,39 +201,43 @@ static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(machine_rtc_local_datetime_obj, 1, 2, static mp_obj_t machine_rtc_timezone(size_t n_args, const mp_obj_t *args) { if (n_args == 1) { + // Get timezone char *tz = getenv("TZ"); if (tz == NULL) { return mp_const_none; } else { - char *ptr = strchr(tz, '+'); + char timezone[64] = { 0 }; + memcpy(timezone, tz, strlen(tz)); + char *ptr = strchr(timezone, '+'); if (ptr != NULL) { *ptr = '-'; } else { - ptr = strchr(tz, '-'); + ptr = strchr(timezone, '-'); if (ptr != NULL) { *ptr = '+'; } } - return mp_obj_new_str(tz, strlen(tz)); + return mp_obj_new_str(timezone, strlen(timezone)); } } else { - char tz[64] = { 0 }; - snprintf(tz, sizeof(tz), "%s", mp_obj_str_get_str(args[0])); + // Set timezone + char timezone[64] = { 0 }; + snprintf(timezone, sizeof(timezone) - 1, "%s", mp_obj_str_get_str(args[1])); - char *ptr = strchr(tz, '-'); + char *ptr = strchr(timezone, '-'); if (ptr != NULL) { *ptr = '+'; } else { - ptr = strchr(tz, '+'); + ptr = strchr(timezone, '+'); if (ptr != NULL) { *ptr = '-'; } } - setenv("TZ", tz, 1); + setenv("TZ", timezone, 1); tzset(); - nvs_write_str_helper(UIFLOW_NVS_NAMESPACE, "tz", tz); + nvs_write_str_helper(UIFLOW_NVS_NAMESPACE, "tz", timezone); return mp_const_none; } } From 8006ac33a4a0870a1c7d4ad47721339aac8a00d8 Mon Sep 17 00:00:00 2001 From: lbuque <1102390310@qq.com> Date: Thu, 4 Sep 2025 16:56:32 +0800 Subject: [PATCH 233/322] pathces: Fix timezone reading error. Signed-off-by: lbuque <1102390310@qq.com> --- .../0006-modtime-add-timezone-method.patch | 24 +++++++++++-------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/m5stack/patches/0006-modtime-add-timezone-method.patch b/m5stack/patches/0006-modtime-add-timezone-method.patch index 17ff9c23..f06843f6 100644 --- a/m5stack/patches/0006-modtime-add-timezone-method.patch +++ b/m5stack/patches/0006-modtime-add-timezone-method.patch @@ -127,46 +127,50 @@ Index: micropython/ports/esp32/modtime.c struct timeval tv; gettimeofday(&tv, NULL); timeutils_struct_time_t tm; -@@ -56,3 +95,46 @@ static mp_obj_t mp_time_time_get(void) { +@@ -56,3 +95,50 @@ static mp_obj_t mp_time_time_get(void) { gettimeofday(&tv, NULL); return mp_obj_new_int(tv.tv_sec); } + +static mp_obj_t time_timezone(size_t n_args, const mp_obj_t *args) { + if (n_args == 0 || args[0] == mp_const_none) { ++ // Get timezone + char *tz = getenv("TZ"); + if (tz == NULL) { + return mp_const_none; + } else { -+ char *ptr = strchr(tz, '+'); ++ char timezone[64] = { 0 }; ++ memcpy(timezone, tz, strlen(tz)); ++ char *ptr = strchr(timezone, '+'); + if (ptr != NULL) { + *ptr = '-'; + } else { -+ ptr = strchr(tz, '-'); ++ ptr = strchr(timezone, '-'); + if (ptr != NULL) { + *ptr = '+'; + } + } -+ return mp_obj_new_str(tz, strlen(tz)); ++ return mp_obj_new_str(timezone, strlen(timezone)); + } + } else { -+ char tz[64] = { 0 }; -+ snprintf(tz, sizeof(tz), "%s", mp_obj_str_get_str(args[0])); ++ // Set timezone ++ char timezone[64] = { 0 }; ++ snprintf(timezone, sizeof(timezone), "%s", mp_obj_str_get_str(args[0])); + -+ char *ptr = strchr(tz, '-'); ++ char *ptr = strchr(timezone, '-'); + if (ptr != NULL) { + *ptr = '+'; + } else { -+ ptr = strchr(tz, '+'); ++ ptr = strchr(timezone, '+'); + if (ptr != NULL) { + *ptr = '-'; + } + } + -+ setenv("TZ", tz, 1); ++ setenv("TZ", timezone, 1); + tzset(); + -+ nvs_write_str_helper(UIFLOW_NVS_NAMESPACE, "tz", tz); ++ nvs_write_str_helper(UIFLOW_NVS_NAMESPACE, "tz", timezone); + return mp_const_none; + } +} From 1e6800d9e1fae3028162df30c36f35d9de715197 Mon Sep 17 00:00:00 2001 From: lbuque <1102390310@qq.com> Date: Fri, 5 Sep 2025 09:30:35 +0800 Subject: [PATCH 234/322] components/M5Unified: Update versions of M5Unified and M5GFX. Signed-off-by: lbuque <1102390310@qq.com> --- m5stack/components/M5Unified/M5GFX | 2 +- m5stack/components/M5Unified/M5Unified | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/m5stack/components/M5Unified/M5GFX b/m5stack/components/M5Unified/M5GFX index 5581745d..65f2622c 160000 --- a/m5stack/components/M5Unified/M5GFX +++ b/m5stack/components/M5Unified/M5GFX @@ -1 +1 @@ -Subproject commit 5581745d7d0cf00f918d2f427b3c2923e9ce2528 +Subproject commit 65f2622cdc5cf7c39208ebe322447e4867555311 diff --git a/m5stack/components/M5Unified/M5Unified b/m5stack/components/M5Unified/M5Unified index a4c9f6dc..d48e8921 160000 --- a/m5stack/components/M5Unified/M5Unified +++ b/m5stack/components/M5Unified/M5Unified @@ -1 +1 @@ -Subproject commit a4c9f6dc6a3ded7369c4e42b008d579756cff6f0 +Subproject commit d48e8921b73ef4437ad0de87852c79678da789d1 From e14a2a0b3e652568d7329e6f90fee650c3364369 Mon Sep 17 00:00:00 2001 From: "tinyu.zhao@gmail.com" Date: Tue, 19 Aug 2025 10:51:40 +0800 Subject: [PATCH 235/322] tools: Add stub file generation script. Signed-off-by: tinyu.zhao@gmail.com --- pyrightconfig.json | 19 +++++++++++++++++++ tools/gen_stubs.py | 18 ++++++++++++++++++ 2 files changed, 37 insertions(+) create mode 100644 pyrightconfig.json create mode 100644 tools/gen_stubs.py diff --git a/pyrightconfig.json b/pyrightconfig.json new file mode 100644 index 00000000..fefcc0b7 --- /dev/null +++ b/pyrightconfig.json @@ -0,0 +1,19 @@ +{ + "include": [ + "m5stack/**" + ], + "exclude": [], + "ignore": [], + "defineConstant": { + "DEBUG": true + }, + "stubPath": "stubs", + "reportMissingImports": "error", + "reportMissingTypeStubs": false, + "executionEnvironments": [ + { + "root": "./", + "reportMissingImports": "warning" + }, + ] +} \ No newline at end of file diff --git a/tools/gen_stubs.py b/tools/gen_stubs.py new file mode 100644 index 00000000..cbb95bca --- /dev/null +++ b/tools/gen_stubs.py @@ -0,0 +1,18 @@ +# SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +from pathlib import Path +import sys, shutil, subprocess + +r = Path(sys.argv[1]) if len(sys.argv) > 1 else Path(__file__).resolve().parent.parent +s = r / "stubs" +l = r / "m5stack/libs" + +if s.exists(): + shutil.rmtree(s) +s.mkdir() + +for p in sorted(l.iterdir()): + if p.is_dir() and not p.name.startswith("__"): + subprocess.run(["pyright", "--createstub", f"m5stack.libs.{p.name}"], cwd=r) \ No newline at end of file From 10d4cfef448bd9e61d4140bb79b275a4b1d29b3e Mon Sep 17 00:00:00 2001 From: "tinyu.zhao@gmail.com" Date: Mon, 25 Aug 2025 11:09:49 +0800 Subject: [PATCH 236/322] .github/workflows: Set IDF version to 5.4.2. Signed-off-by: tinyu.zhao@gmail.com --- .github/workflows/build-release.yml | 8 ++++---- .github/workflows/nightly-build.yml | 8 ++++---- .github/workflows/ports_m5stack.yml | 8 ++++---- README.md | 2 +- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/.github/workflows/build-release.yml b/.github/workflows/build-release.yml index 13af8868..f1258f9c 100644 --- a/.github/workflows/build-release.yml +++ b/.github/workflows/build-release.yml @@ -31,7 +31,7 @@ jobs: path: | ~/.espressif ${{ github.workspace }}/esp-idf - key: ${{ runner.os }}-idf-v5.4.1 + key: ${{ runner.os }}-idf-v5.4.2 - name: Install ESP-IDF if: steps.cache-esp-idf.outputs.cache-hit != 'true' @@ -39,11 +39,11 @@ jobs: git clone --depth=1 -b $IDF_VERSION https://github.com/espressif/esp-idf.git ./esp-idf/install.sh env: - IDF_VERSION: "v5.4.1" + IDF_VERSION: "v5.4.2" - name: Setup environment run: | - source tools/ci.sh && ci_esp32_idf541_setup + source tools/ci.sh && ci_esp32_idf542_setup source esp-idf/export.sh pip install future make -C m5stack submodules @@ -107,7 +107,7 @@ jobs: path: | ~/.espressif ${{ github.workspace }}/esp-idf - key: ${{ runner.os }}-idf-v5.4.1 + key: ${{ runner.os }}-idf-v5.4.2 - name: Prepare environment run: | diff --git a/.github/workflows/nightly-build.yml b/.github/workflows/nightly-build.yml index e4d11a3e..8b5699ee 100644 --- a/.github/workflows/nightly-build.yml +++ b/.github/workflows/nightly-build.yml @@ -30,7 +30,7 @@ jobs: path: | ~/.espressif ${{ github.workspace }}/esp-idf - key: ${{ runner.os }}-idf-v5.4.1 + key: ${{ runner.os }}-idf-v5.4.2 - name: Install ESP-IDF if: steps.cache-esp-idf.outputs.cache-hit != 'true' @@ -38,11 +38,11 @@ jobs: git clone --depth=1 -b $IDF_VERSION https://github.com/espressif/esp-idf.git ./esp-idf/install.sh env: - IDF_VERSION: "v5.4.1" + IDF_VERSION: "v5.4.2" - name: Setup environment run: | - source tools/ci.sh && ci_esp32_idf541_setup + source tools/ci.sh && ci_esp32_idf542_setup source esp-idf/export.sh pip install future make -C m5stack submodules @@ -105,7 +105,7 @@ jobs: path: | ~/.espressif ${{ github.workspace }}/esp-idf - key: ${{ runner.os }}-idf-v5.4.1 + key: ${{ runner.os }}-idf-v5.4.2 - name: Prepare environment run: | diff --git a/.github/workflows/ports_m5stack.yml b/.github/workflows/ports_m5stack.yml index 012428cf..bfe9f3b2 100644 --- a/.github/workflows/ports_m5stack.yml +++ b/.github/workflows/ports_m5stack.yml @@ -35,7 +35,7 @@ jobs: path: | ~/.espressif ${{ github.workspace }}/esp-idf - key: ${{ runner.os }}-idf-v5.4.1 + key: ${{ runner.os }}-idf-v5.4.2 - name: Install ESP-IDF if: steps.cache-esp-idf.outputs.cache-hit != 'true' @@ -43,11 +43,11 @@ jobs: git clone --depth=1 -b $IDF_VERSION https://github.com/espressif/esp-idf.git ./esp-idf/install.sh env: - IDF_VERSION: "v5.4.1" + IDF_VERSION: "v5.4.2" - name: Setup environment run: | - source tools/ci.sh && ci_esp32_idf541_setup + source tools/ci.sh && ci_esp32_idf542_setup source esp-idf/export.sh pip install future make -C m5stack submodules @@ -110,7 +110,7 @@ jobs: path: | ~/.espressif ${{ github.workspace }}/esp-idf - key: ${{ runner.os }}-idf-v5.4.1 + key: ${{ runner.os }}-idf-v5.4.2 - name: Prepare environment run: | diff --git a/README.md b/README.md index 7f4e8d36..00a3e3b0 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ ```shell mkdir uiflow_workspace && cd uiflow_workspace -git clone --depth 1 --branch v5.4.1 https://github.com/espressif/esp-idf.git +git clone --depth 1 --branch v5.4.2 https://github.com/espressif/esp-idf.git git -C esp-idf submodule update --init --recursive ./esp-idf/install.sh . ./esp-idf/export.sh From 7c0c0f2e8de663194c360b913959a3c0319bbeec Mon Sep 17 00:00:00 2001 From: "tinyu.zhao@gmail.com" Date: Wed, 20 Aug 2025 15:22:12 +0800 Subject: [PATCH 237/322] docs: Update m5ui.arc documentation. Signed-off-by: tinyu.zhao@gmail.com --- docs/en/m5ui/arc.rst | 32 -- docs/en/refs/m5ui.arc.ref | 34 +- docs/locales/zh_CN/LC_MESSAGES/m5ui/arc.po | 483 +++++++++++++++++++++ m5stack/libs/m5ui/arc.py | 4 +- 4 files changed, 503 insertions(+), 50 deletions(-) create mode 100644 docs/locales/zh_CN/LC_MESSAGES/m5ui/arc.po diff --git a/docs/en/m5ui/arc.rst b/docs/en/m5ui/arc.rst index 96ce6606..5e7ab50b 100644 --- a/docs/en/m5ui/arc.rst +++ b/docs/en/m5ui/arc.rst @@ -228,38 +228,6 @@ M5Arc arc_0.set_size(100, 50) - .. py:method:: set_width(width) - - Set the width of the arc. - - :param int width: The width of the arc. - - UiFlow2 Code Block: - - |set_width.png| - - MicroPython Code Block: - - .. code-block:: python - - arc_0.set_width(100) - - .. py:method:: set_height(height) - - Set the height of the arc. - - :param int height: The height of the arc. - - UiFlow2 Code Block: - - |set_height.png| - - MicroPython Code Block: - - .. code-block:: python - - arc_0.set_height(50) - .. py:method:: align_to(obj, align, x, y) Align the arc to another object. diff --git a/docs/en/refs/m5ui.arc.ref b/docs/en/refs/m5ui.arc.ref index 2c0010db..05994ef0 100644 --- a/docs/en/refs/m5ui.arc.ref +++ b/docs/en/refs/m5ui.arc.ref @@ -1,25 +1,25 @@ -.. |align_to.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/switch/align_to.png -.. |event.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/switch/event.png -.. |set_arc_color.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/switch/set_arc_color.png -.. |set_rotation.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/switch/set_rotation.png -.. |set_value.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/switch/set_value.png -.. |get_value.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/switch/get_value.png -.. |set_range.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/switch/set_range.png -.. |set_mode.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/switch/set_mode.png -.. |set_height.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/switch/set_height.png -.. |set_pos.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/switch/set_pos.png -.. |set_size.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/switch/set_size.png -.. |set_width.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/switch/set_width.png -.. |set_x.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/switch/set_x.png -.. |set_y.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/switch/set_y.png -.. |set_flag.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/switch/set_flag.png +.. |align_to.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/arc/align_to.png +.. |event.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/arc/event.png +.. |set_bg_color.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/arc/set_bg_color.png +.. |set_indicator_color.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/arc/set_indicator_color.png +.. |set_knob_color.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/arc/set_knob_color.png +.. |set_rotation.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/arc/set_rotation.png +.. |set_value.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/arc/set_value.png +.. |get_value.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/arc/get_value.png +.. |set_range.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/arc/set_range.png +.. |set_mode.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/arc/set_mode.png +.. |set_pos.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/arc/set_pos.png +.. |set_size.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/arc/set_size.png +.. |set_x.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/arc/set_x.png +.. |set_y.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/arc/set_y.png +.. |set_flag.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/arc/set_flag.png -.. |cores3_arc_event_example.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/switch/example.png +.. |cores3_arc_event_example.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/arc/example.png .. |cores3_arc_event_example.m5f2| raw:: html cores3_arc_event_example.m5f2 diff --git a/docs/locales/zh_CN/LC_MESSAGES/m5ui/arc.po b/docs/locales/zh_CN/LC_MESSAGES/m5ui/arc.po new file mode 100644 index 00000000..03cae0f2 --- /dev/null +++ b/docs/locales/zh_CN/LC_MESSAGES/m5ui/arc.po @@ -0,0 +1,483 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2016 - 2025 M5Stack Technology Co., Ltd +# This file is distributed under the same license as the UIFlow2 Programming +# Guide package. +# FIRST AUTHOR , 2025. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: UIFlow2 Programming Guide \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-08-04 10:27+0800\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: zh_CN\n" +"Language-Team: zh_CN \n" +"Plural-Forms: nplurals=1; plural=0;\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.16.0\n" + +#: ../../en/m5ui/arc.rst:4 ../../en/m5ui/arc.rst:52 +#: 593821ab4623459d895d5e5171c3f3ae ff9885fe3890467aa6691e6544615792 +msgid "M5Arc" +msgstr "" + +#: ../../en/m5ui/arc.rst:8 ea499dbfe4644d8eaa8d62ca016d98c2 +msgid "" +"M5Arc is a widget that can be used to create arcs in the user interface. " +"It can be used to display circular progress or other circular indicators." +msgstr "" + +#: ../../en/m5ui/arc.rst:11 403dabb03a1243449ce18d64a75c1717 +msgid "UiFlow2 Example" +msgstr "" + +#: ../../en/m5ui/arc.rst:14 ../../en/m5ui/arc.rst:33 +#: 4985e0fd79b24299af1366970297be55 ff105dda36e74e3eb901886f9d88c7c9 +msgid "event arc" +msgstr "" + +#: ../../en/m5ui/arc.rst:16 b5fccef3ab0444ceb1138bc17f8f38a5 +msgid "Open the |cores3_arc_event_example.m5f2| project in UiFlow2." +msgstr "" + +#: ../../en/m5ui/arc.rst:18 ../../en/m5ui/arc.rst:35 +#: 588852d970bc4f9c9093cf6a135edae1 7914cec46f124c90b0e3237e55847e1f +msgid "This example creates an arc that triggers an event when the value changes." +msgstr "" + +#: ../../en/m5ui/arc.rst:20 ../../en/m5ui/arc.rst:64 ../../en/m5ui/arc.rst:80 +#: ../../en/m5ui/arc.rst:97 ../../en/m5ui/arc.rst:114 ../../en/m5ui/arc.rst:136 +#: ../../en/m5ui/arc.rst:154 ../../en/m5ui/arc.rst:172 +#: ../../en/m5ui/arc.rst:188 ../../en/m5ui/arc.rst:204 +#: ../../en/m5ui/arc.rst:221 ../../en/m5ui/arc.rst:237 +#: ../../en/m5ui/arc.rst:253 ../../en/m5ui/arc.rst:272 +#: ../../en/m5ui/arc.rst:290 0a8e7c374ecc4a239431750ecf5afcec +#: 0af850d9d9d84583b0d03c01b76b63cd 0cc9241b7245407bbd3ebab63487c884 +#: 0dc0a22fe4704a60bf856ea0d1528fc4 0fa33ea1d8b34975aebea8dcfcb10af0 +#: 123e77a661ee474fa283cab139f512d8 257e1b710a3d4e8bb03b2910000deb6c +#: 2cdd76530e3f453783ee7c07fc7e81aa 2ed2a0747ca147ae919d152eaf3e7a1c +#: 4fe4008a4eee48fa83057008f1f99cca 64a12f1ceb1c4934b7a0416db2380e3f +#: 6aba9ec21ae74fcc8fcec5422efada25 75f0ca1ca65b4f97bf1f21c22b14c50a +#: a203d11dece547039fa7e1089d2a19bd bbaad1f677604cd5a715555cb38ddc75 +#: dc340b2d19a7475bb2d60c8100cf9972 f93e3a088a77494fa5db63a90b5e87e1 +#: m5ui.arc.M5Arc:16 m5ui.arc.M5Arc.set_arc_color:7 of +msgid "UiFlow2 Code Block:" +msgstr "" + +#: ../../en/m5ui/arc.rst:22 902d83403e884e71b9aa24a1971da53c +msgid "|cores3_arc_event_example.png|" +msgstr "" + +#: ../../en/refs/m5ui.arc.ref:17 26adc3495cb34fe385de80e6fdecaa08 +msgid "cores3_arc_event_example.png" +msgstr "" + +#: ../../en/m5ui/arc.rst:24 ../../en/m5ui/arc.rst:43 +#: 282ca485745c454daa17294585ebaf14 b663444d5e4e4a6d8ab55f39f21471a6 +msgid "Example output:" +msgstr "" + +#: ../../en/m5ui/arc.rst:26 ../../en/m5ui/arc.rst:45 ../../en/m5ui/arc.rst:152 +#: 1d0410ba2e5c4afb87d7f832fbf2e525 306bc39dd58e4eeca0078104fb590dcf +#: 5ad1ca7fec0e4b9ca6104f5a9aaab319 bd1657dafd574f339d6e0166a383d289 +#: m5ui.arc.M5Arc:18 of +msgid "None" +msgstr "" + +#: ../../en/m5ui/arc.rst:30 f7a2fde0095141aa97c19b00569a6be4 +msgid "MicroPython Example" +msgstr "" + +#: ../../en/m5ui/arc.rst:37 ../../en/m5ui/arc.rst:68 ../../en/m5ui/arc.rst:84 +#: ../../en/m5ui/arc.rst:101 ../../en/m5ui/arc.rst:118 +#: ../../en/m5ui/arc.rst:140 ../../en/m5ui/arc.rst:158 +#: ../../en/m5ui/arc.rst:176 ../../en/m5ui/arc.rst:192 +#: ../../en/m5ui/arc.rst:208 ../../en/m5ui/arc.rst:225 +#: ../../en/m5ui/arc.rst:241 ../../en/m5ui/arc.rst:257 +#: ../../en/m5ui/arc.rst:276 ../../en/m5ui/arc.rst:294 +#: 063841cd208c41b3b17d30fd7161d93f 0f102faad19b406caa4e61fd4ea91e98 +#: 3ad3a966354047d39dd54ff7d808fba7 6b23534eb4a744cfabd1b302f84da597 +#: 80be8c378bd74714ad88d6eb208db8e0 83d35c384e30483caf10657af23ff3f7 +#: 970ed5b434cd461798c19b8e54efd2b4 98fca75648a24a97a636faf7c4cb8abc +#: a547ce6a56da446c8a3d6ce0c2fc3d29 ac26b303f8a24f35977e90e994ca8f9e +#: b1d2dde3eb7a415eae12539ec9150e53 b28277eb52a74f9680b72f55cbf1d10b +#: c20e7525386b434abac03e8f03c0da02 cc3bf681e89848dfa8766e56c8db6959 +#: def108ae2c35419abced43bf47ca2560 e790e8fa4509462ebd7da809de03cb1e +#: f56f1dd118474413848d3d807e5efa24 m5ui.arc.M5Arc:20 +#: m5ui.arc.M5Arc.set_arc_color:11 of +msgid "MicroPython Code Block:" +msgstr "" + +#: ../../en/m5ui/arc.rst:49 dc5e373ef93b46f4b51f793d9e293f88 +msgid "**API**" +msgstr "" + +#: da50986c7f68480da02c290a5c14aefa m5ui.arc.M5Arc:1 of +msgid "Bases: :py:class:`~lvgl.arc`" +msgstr "" + +#: 9cea13e9b0514801b7f7b13c9369ff71 m5ui.arc.M5Arc:1 of +msgid "Create a arc object." +msgstr "" + +#: ../../en/m5ui/arc.rst 012452aa982d4c378578746684b36d81 +#: 37bbb4105e2c47449e6b4847a477efb6 3ba8c1bf46bb4aaebb340838efff6098 +#: 4e1247d569734a5097c7cfb9c266f9de 85b564eff9b144ef998c1d8f919185ae +#: 8f2a3bd1ae864228926f67c14e02f73b bc3bccdc8a1c4a17ab7e03c524dfc092 +#: c0079a98e4ba4c3fbf7d27fe7b441f0b c9c25ed179c04e3ab1a0421a7cb460eb +#: e095ba7315a04046bc7401de21a9cc2a e29985bf7cbd409a86c120798a805630 +#: f8853ba1242b4949bec8e792c5eb7afc fdfe134d5c4b4b0fb676c31b1961937d +#: feeef60ba7814a2fb9203bf0ca7ec222 ff4783db43e54360a199737efb8ebe32 +#: m5ui.arc.M5Arc.set_arc_color of +msgid "Parameters" +msgstr "" + +#: 61a87cfc71c148819ef7bbace828f3f9 m5ui.arc.M5Arc:3 of +msgid "The x position of the arc." +msgstr "" + +#: 8bc4a72ba38e419794003fbbc39f9995 m5ui.arc.M5Arc:4 of +msgid "The y position of the arc." +msgstr "" + +#: ../../en/m5ui/arc.rst:218 ../../en/m5ui/arc.rst:235 +#: 3bb7eb5bcd874fcebeb069d169a091a0 b93a5ab46943482d953721f8f62c43b2 +#: d8a26d044b0841c8a13ded8a5f465283 m5ui.arc.M5Arc:5 of +msgid "The width of the arc." +msgstr "" + +#: ../../en/m5ui/arc.rst:219 ../../en/m5ui/arc.rst:251 +#: 6966ab01fdc24f29a736ce8f62434d9f 9fa2f5f13bc14826a2434f803fd4bbb5 +#: f072167b5dbc44518e8bb73cfa4909ea m5ui.arc.M5Arc:6 of +msgid "The height of the arc." +msgstr "" + +#: 1c93c552bfe845e7bea673621a14ac72 m5ui.arc.M5Arc:7 of +msgid "The initial value of the arc." +msgstr "" + +#: ../../en/m5ui/arc.rst:111 31862ba40e6e49918b4306ad736b1895 +#: 328f91e959c04a469019b0e5fc76452f m5ui.arc.M5Arc:8 of +msgid "The minimum value of the arc." +msgstr "" + +#: ../../en/m5ui/arc.rst:112 2a8a29ff3d6944eea808d74419347efa +#: 7ca2fd642d7d4980a7d285eb20027180 m5ui.arc.M5Arc:9 of +msgid "The maximum value of the arc." +msgstr "" + +#: 2c1411a2bc8f467eab473cd3f7119fc0 m5ui.arc.M5Arc:10 of +msgid "The rotation of the arc in degrees." +msgstr "" + +#: 31467c972da24a649f204b6b2525e6f3 m5ui.arc.M5Arc:11 of +msgid "The color of the arc in the off state in hexadecimal format." +msgstr "" + +#: deffbed7b42c46da81389af40fc649a0 m5ui.arc.M5Arc:12 of +msgid "The color of the arc in the on state in hexadecimal format." +msgstr "" + +#: ff1b090e19344693b24a78dda46d682b m5ui.arc.M5Arc:13 of +msgid "The color of the knob on the arc in hexadecimal format." +msgstr "" + +#: dfb87872ba66465cbc880792a4bb88a7 m5ui.arc.M5Arc:14 of +msgid "" +"The parent object to attach the arc to. If not specified, the arc will be" +" attached to the default screen." +msgstr "" + +#: ../../en/m5ui/arc.rst:60 df4555c8d28349a0b8d09b6c7a2a8419 +msgid "Set the rotation of the arc." +msgstr "" + +#: ../../en/m5ui/arc.rst:62 53a6249138a54990bde70244e42b1548 +msgid "The rotation angle of the arc in degrees." +msgstr "" + +#: ../../en/m5ui/arc.rst:66 8313ad8d3e04498ea3e271afd5d72631 +msgid "|set_rotation.png|" +msgstr "" + +#: ../../en/refs/m5ui.arc.ref:4 619c8c6912d64b39a05804e931adacfd +msgid "set_rotation.png" +msgstr "" + +#: ../../en/m5ui/arc.rst:76 7c18884b99ea408a8ee5cd18c6b60d14 +msgid "Set the value of the arc." +msgstr "" + +#: ../../en/m5ui/arc.rst:78 ../../en/m5ui/arc.rst:94 +#: ae688c321be04923b06216621074766d bb4d4021fa74495aaf9fcaeecd8f5310 +msgid "The value of the arc." +msgstr "" + +#: ../../en/m5ui/arc.rst:82 ab7fb1281b5d41528f8623366cb25f80 +msgid "|set_value.png|" +msgstr "" + +#: ../../en/refs/m5ui.arc.ref:5 6131869528744a9d9a0ab397e9b0b167 +msgid "set_value.png" +msgstr "" + +#: ../../en/m5ui/arc.rst:92 17936446d6e340a88487ea2f5ae51c7d +msgid "Get the value of the arc." +msgstr "" + +#: ../../en/m5ui/arc.rst 22aa5acf4bc5485e9fa3bd3214479d03 +#: f3fd703c47d242da8eb3d30364734908 +msgid "Returns" +msgstr "" + +#: ../../en/m5ui/arc.rst 5f34b04fc98748e2a177d2098a98a5a9 +msgid "Return type" +msgstr "" + +#: ../../en/m5ui/arc.rst:99 6cf6924f7e0a48988f288d0fd868759a +msgid "|get_value.png|" +msgstr "" + +#: ../../en/refs/m5ui.arc.ref:6 4a4b483223364fe2ae35d6f0d405b4ad +msgid "get_value.png" +msgstr "" + +#: ../../en/m5ui/arc.rst:109 1c7bde8a9e6144a0b42f3071086dc631 +msgid "Set the range of the arc." +msgstr "" + +#: ../../en/m5ui/arc.rst:116 9ec86360cc9c4022bbaea3f9c6f1ceb8 +msgid "|set_range.png|" +msgstr "" + +#: ../../en/refs/m5ui.arc.ref:7 efbe2ea3d0f64cff8983b5048872f8b9 +msgid "set_range.png" +msgstr "" + +#: ../../en/m5ui/arc.rst:127 dd416f6914c940e2baabaa258d10e97b +msgid "Set the mode of the arc." +msgstr "" + +#: ../../en/m5ui/arc.rst:129 97a5655ab2d84a6ba50a0db778922a08 +msgid "" +"The mode of the arc. Option: - lv.arc.MODE.NORMAL: Normal mode. " +"- lv.arc.MODE.REVERSE: Reverse mode. - lv.arc.MODE.SYMMETRICAL: " +"Symmetrical mode." +msgstr "" + +#: ../../en/m5ui/arc.rst:129 a6c71908897049358ce139ffb35a1beb +msgid "The mode of the arc." +msgstr "" + +#: ../../en/m5ui/arc.rst:134 a1d2464025f046b789c4cafd3f462064 +msgid "Option:" +msgstr "" + +#: ../../en/m5ui/arc.rst:132 9d1e0a057c104ff183aabd283fdf0861 +msgid "lv.arc.MODE.NORMAL: Normal mode." +msgstr "" + +#: ../../en/m5ui/arc.rst:133 d215bf9bc4624dc785712732fc424de2 +msgid "lv.arc.MODE.REVERSE: Reverse mode." +msgstr "" + +#: ../../en/m5ui/arc.rst:134 086285fe1c1a4fe49c8cedd032ad12c0 +msgid "lv.arc.MODE.SYMMETRICAL: Symmetrical mode." +msgstr "" + +#: ../../en/m5ui/arc.rst:138 ecc1e786202f4031a6bda3422e45869f +msgid "|set_mode.png|" +msgstr "" + +#: ../../en/refs/m5ui.arc.ref:8 e7c3c4ea65e44f6784f579bc6bd3447d +msgid "set_mode.png" +msgstr "" + +#: ../../en/m5ui/arc.rst:148 8afc088838294e608bd3af3b391b1b0c +msgid "" +"Set a flag on the object. If ``value`` is True, the flag is added; if " +"False, the flag is removed." +msgstr "" + +#: ../../en/m5ui/arc.rst:150 d781e6f70aa54113ad8d0cc5e5dbc518 +msgid "The flag to set." +msgstr "" + +#: ../../en/m5ui/arc.rst:151 7a675549d2064b97a97e5cdc028c5d49 +msgid "If True, the flag is added; if False, the flag is removed." +msgstr "" + +#: ../../en/m5ui/arc.rst:156 869a35221e854b649e7399d9888e1629 +msgid "|set_flag.png|" +msgstr "" + +#: ../../en/refs/m5ui.arc.ref:15 17f33ce7e5d74ab2ab4e8561f4898255 +msgid "set_flag.png" +msgstr "" + +#: ../../en/m5ui/arc.rst:167 babd0012fbab4e469755761e9ccd06af +msgid "Set the position of the arc." +msgstr "" + +#: ../../en/m5ui/arc.rst:169 ../../en/m5ui/arc.rst:186 +#: 35a7e9125d0d4ae3a8a446dea9335ad3 f030bf96e0154cb5887ad6ee42c37342 +msgid "The x-coordinate of the arc." +msgstr "" + +#: ../../en/m5ui/arc.rst:170 ../../en/m5ui/arc.rst:202 +#: 48cc12bde64744509efa3b23e59d9687 e521445e9c414178a16f88bc4aae59b3 +msgid "The y-coordinate of the arc." +msgstr "" + +#: ../../en/m5ui/arc.rst:174 a415ccc84d2445728d0e41ec3ec84f33 +msgid "|set_pos.png|" +msgstr "" + +#: ../../en/refs/m5ui.arc.ref:10 b00a11e71d084ac3a018ed123ebb3f47 +msgid "set_pos.png" +msgstr "" + +#: ../../en/m5ui/arc.rst:184 ee7d8e5a3dab4363b7b9ed1585bec910 +msgid "Set the x-coordinate of the arc." +msgstr "" + +#: ../../en/m5ui/arc.rst:190 b5be812c9619474caf311862811e802b +msgid "|set_x.png|" +msgstr "" + +#: ../../en/refs/m5ui.arc.ref:13 43ee9d93b9564a62aae98bf8b0e5978c +msgid "set_x.png" +msgstr "" + +#: ../../en/m5ui/arc.rst:200 07e903771d7c4887a2553f003977b8c5 +msgid "Set the y-coordinate of the arc." +msgstr "" + +#: ../../en/m5ui/arc.rst:206 e6501e980e654501ac9ca6c8ae6e907b +msgid "|set_y.png|" +msgstr "" + +#: ../../en/refs/m5ui.arc.ref:14 440ed13b349d4bc8ad9aefb2abaff495 +msgid "set_y.png" +msgstr "" + +#: ../../en/m5ui/arc.rst:216 ae2a11f92e604caaa7b3100c919dc569 +msgid "Set the size of the arc." +msgstr "" + +#: ../../en/m5ui/arc.rst:223 34f95be0eaf84610a7d4736cacb8daf1 +msgid "|set_size.png|" +msgstr "" + +#: ../../en/refs/m5ui.arc.ref:11 ca6fcf889f8b465ea527ad23945b27ec +msgid "set_size.png" +msgstr "" + +#: ../../en/m5ui/arc.rst:233 eca7bf4796154018b6ecc20ff10799c1 +msgid "Set the width of the arc." +msgstr "" + +#: ../../en/m5ui/arc.rst:239 45b4d6baef334e9da51ace8acd2914af +msgid "|set_width.png|" +msgstr "" + +#: ../../en/refs/m5ui.arc.ref:12 6f96b4ac0df54a7487b200da89021035 +msgid "set_width.png" +msgstr "" + +#: ../../en/m5ui/arc.rst:249 8692a72419ab454ea6316fa7f5125588 +msgid "Set the height of the arc." +msgstr "" + +#: ../../en/m5ui/arc.rst:255 ff16c88b6fbf445fa8e66e24001b0c8d +msgid "|set_height.png|" +msgstr "" + +#: ../../en/refs/m5ui.arc.ref:9 b4d9d811b4cd4ffdb678247a95bc10fb +msgid "set_height.png" +msgstr "" + +#: ../../en/m5ui/arc.rst:265 49135d2421d142828e47bcb7321a6fed +msgid "Align the arc to another object." +msgstr "" + +#: ../../en/m5ui/arc.rst:267 e10aa63deb8a472f96065eb2349d9786 +msgid "The object to align to." +msgstr "" + +#: ../../en/m5ui/arc.rst:268 d25e445265664dfa9da033beec440ed9 +msgid "The alignment type." +msgstr "" + +#: ../../en/m5ui/arc.rst:269 a766b01b17b144f69c1c8d5f6a4871c7 +msgid "The x-offset from the aligned object." +msgstr "" + +#: ../../en/m5ui/arc.rst:270 2ba16dada0464a228e166e7cc4edf640 +msgid "The y-offset from the aligned object." +msgstr "" + +#: ../../en/m5ui/arc.rst:274 56fef355b414464a99eada969c439d4b +msgid "|align_to.png|" +msgstr "" + +#: ../../en/refs/m5ui.arc.ref:1 a0ec3097c44b4a36a1439b0b5068a6b9 +msgid "align_to.png" +msgstr "" + +#: ../../en/m5ui/arc.rst:284 a188fb22a954481b94a3d280f9a60fc5 +msgid "" +"Add an event callback to the arc. The callback will be called when the " +"specified event occurs." +msgstr "" + +#: ../../en/m5ui/arc.rst:286 386b44195d6c4067aeaf87c62e3a2658 +msgid "The callback function to call." +msgstr "" + +#: ../../en/m5ui/arc.rst:287 801a63958e8147f1a192aaa36b9b4d95 +msgid "The event to listen for." +msgstr "" + +#: ../../en/m5ui/arc.rst:288 1b542ee486d1486bb72b76757873b071 +msgid "Optional user data to pass to the callback." +msgstr "" + +#: ../../en/m5ui/arc.rst:292 29bcd2aac110422f9d111beb34d44a95 +msgid "|event.png|" +msgstr "" + +#: ../../en/refs/m5ui.arc.ref:2 e5aa300ed91e4826aa97487796748668 +msgid "event.png" +msgstr "" + +#: a7b25b9620474a38bf26e46b3647b49b m5ui.arc.M5Arc.set_arc_color:1 of +msgid "Set the color of the arc." +msgstr "" + +#: e1490ec8262f401187c814f684f51e74 m5ui.arc.M5Arc.set_arc_color:3 of +msgid "The color of the arc in hexadecimal format." +msgstr "" + +#: 53e8338028a7458b88b773bdaa03e113 m5ui.arc.M5Arc.set_arc_color:4 of +msgid "The opacity level (0-255)." +msgstr "" + +#: d943bd2c35ef451d8117743620ae87d5 m5ui.arc.M5Arc.set_arc_color:5 of +msgid "" +"The part of the arc to apply the style to (e.g., lv.PART.MAIN | " +"lv.STATE.DEFAULT)." +msgstr "" + +#: 114f27d771064397bfbfbf5a0c188a98 m5ui.arc.M5Arc.set_arc_color:9 of +msgid "|set_arc_color.png|" +msgstr "" + +#: ../../en/refs/m5ui.arc.ref:3 b41b439184344485bbc5a8deae3ccdb8 +msgid "set_arc_color.png" +msgstr "" + diff --git a/m5stack/libs/m5ui/arc.py b/m5stack/libs/m5ui/arc.py index 46393ca0..ee97f51e 100644 --- a/m5stack/libs/m5ui/arc.py +++ b/m5stack/libs/m5ui/arc.py @@ -89,7 +89,9 @@ def set_arc_color(self, color, opa: int, part: int): UiFlow2 Code Block: - |set_arc_color.png| + |set_bg_color.png| + |set_indicator_color.png| + |set_knob_color.png| MicroPython Code Block: From adb7bc5d6a3ce47b497c48ce29a40000c010027d Mon Sep 17 00:00:00 2001 From: "tinyu.zhao@gmail.com" Date: Mon, 1 Sep 2025 10:28:04 +0800 Subject: [PATCH 238/322] docs: Fix errors in MQ documentation. Signed-off-by: tinyu.zhao@gmail.com --- docs/en/refs/unit.mq.ref | 4 +- docs/en/units/mq.rst | 12 +- docs/locales/zh_CN/LC_MESSAGES/units/mq.po | 137 +++++++++------------ 3 files changed, 66 insertions(+), 87 deletions(-) diff --git a/docs/en/refs/unit.mq.ref b/docs/en/refs/unit.mq.ref index a31086b7..40703d72 100644 --- a/docs/en/refs/unit.mq.ref +++ b/docs/en/refs/unit.mq.ref @@ -1,6 +1,6 @@ -.. |MQ| image:: https://static-cdn.m5stack.com/resource/docs/products/unit/co2/co2_01.webp - :target: https://docs.m5stack.com/en/unit/co2 +.. |MQ| image:: https://m5stack-doc.oss-cn-shenzhen.aliyuncs.com/1168/U199_Unit_MQ_01.webp + :target: https://docs.m5stack.com/en/unit/Unit_MQ :height: 200px :width: 200px diff --git a/docs/en/units/mq.rst b/docs/en/units/mq.rst index b3a9ab0e..121ea79f 100644 --- a/docs/en/units/mq.rst +++ b/docs/en/units/mq.rst @@ -16,12 +16,12 @@ Support the following products: UiFlow2 Example --------------- -get MQ value -^^^^^^^^^^^^^^^ +get MQ ADC value +^^^^^^^^^^^^^^^^^ Open the |mq_core2_example.m5f2| project in UiFlow2. -This example gets the MQ value of the MQ Unit and displays it on the screen. +This example gets the ADC value of the MQ Unit and displays it on the screen. UiFlow2 Code Block: @@ -34,10 +34,10 @@ Example output: MicroPython Example ------------------- -get MQ value -^^^^^^^^^^^^^^^ +get MQ ADC value +^^^^^^^^^^^^^^^^^ -This example gets the MQ value of the MQ Unit and displays it on the screen. +This example gets the ADC value of the MQ Unit and displays it on the screen. MicroPython Code Block: diff --git a/docs/locales/zh_CN/LC_MESSAGES/units/mq.po b/docs/locales/zh_CN/LC_MESSAGES/units/mq.po index cdadad8f..382b7546 100644 --- a/docs/locales/zh_CN/LC_MESSAGES/units/mq.po +++ b/docs/locales/zh_CN/LC_MESSAGES/units/mq.po @@ -9,7 +9,7 @@ msgid "" msgstr "" "Project-Id-Version: UIFlow2 Programming Guide \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-08-12 14:08+0800\n" +"POT-Creation-Date: 2025-09-01 10:26+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language: zh_CN\n" @@ -48,8 +48,9 @@ msgstr "UiFlow2应用示例" #: ../../en/units/mq.rst:20 ../../en/units/mq.rst:38 #: 722377f56e7d48b694cf8999a00c8980 f889e9672de8478e96aa3a1ba9f0be55 -msgid "get MQ value" -msgstr "获取MQ值" +#, fuzzy +msgid "get MQ ADC value" +msgstr "获取ADC值." #: ../../en/units/mq.rst:22 143de4bc84ce427aaf1f02ad7f87054c msgid "Open the |mq_core2_example.m5f2| project in UiFlow2." @@ -58,23 +59,17 @@ msgstr "在UiFlow2中打开 |mq_core2_example.m5f2| 项目." #: ../../en/units/mq.rst:24 ../../en/units/mq.rst:40 #: 0b4ecd28addc42e6b614ebd3e6657254 c9fc139499cf4da98571b55e7b219191 msgid "" -"This example gets the MQ value of the MQ Unit and displays it on the " +"This example gets the ADC value of the MQ Unit and displays it on the " "screen." -msgstr "此示例获取MQ Unit的MQ值并在屏幕上显示." - -#: ../../en/units/mq.rst:26 1f19952e46d840c082bb850e1f681d33 -#: 31670619470e4c9480d4feea09a26d6e 31f63e94115f423698ccc0579bc84d53 -#: 35005d5388ee4688b09d3cb30bf95871 4dfd4d9a7431420196373e96bfd77789 -#: 6712762daf43420dbcd625992af25824 6ba5270f1e624865a889aada3d72edae -#: 7f330554cc114a92959e0ab332a81fc9 a1f1aa18056849b58f69cf10cc4a2093 -#: aebd21d6a89f40ecb74e15fce408405a b064886d3e37468e85dacf355236b6e0 -#: ba9d6a89f7c0466ead1744b408765d19 cff5bbd45ed644a5bef7e3c169504812 +msgstr "此示例获取MQ Unit的 ADC 值并在屏幕上显示." + +#: ../../en/units/mq.rst:26 31670619470e4c9480d4feea09a26d6e +#: 35005d5388ee4688b09d3cb30bf95871 6ba5270f1e624865a889aada3d72edae +#: a1f1aa18056849b58f69cf10cc4a2093 b064886d3e37468e85dacf355236b6e0 #: d4379243ed4446d7a37d345b20ebe936 of unit.mq.MQUnit:6 #: unit.mq.MQUnit.get_adc_value:7 unit.mq.MQUnit.get_firmware_version:6 -#: unit.mq.MQUnit.get_heat_time:6 unit.mq.MQUnit.get_i2c_address:6 -#: unit.mq.MQUnit.get_mq_mode:6 unit.mq.MQUnit.get_ntc_adc_value:7 -#: unit.mq.MQUnit.get_ntc_res_value:6 unit.mq.MQUnit.get_valid_tags:6 -#: unit.mq.MQUnit.get_voltage:7 unit.mq.MQUnit.set_heat_time:6 +#: unit.mq.MQUnit.get_i2c_address:6 unit.mq.MQUnit.get_mq_mode:6 +#: unit.mq.MQUnit.get_valid_tags:6 unit.mq.MQUnit.set_heat_time:6 #: unit.mq.MQUnit.set_i2c_address:5 unit.mq.MQUnit.set_mq_mode:10 msgid "UiFlow2 Code Block:" msgstr "UiFlow2 代码块:" @@ -101,20 +96,16 @@ msgstr "" msgid "MicroPython Example" msgstr "MicroPython 示例" -#: ../../en/units/mq.rst:42 046780955ad4496e9cf8b2525e9ab4fa -#: 121ac930d713418083b370078575f0f4 23e0741b1d9c4d9086b7b847771ffc36 -#: 2743ab8bf8ca4e00b3c2c07e0082e5b0 2af8ec066aae414cacb109c71b09a690 -#: 4bbfbdebab3345448032714990218c5d 678b5df007be47fd80b6a396ca8e4696 -#: 7409a311d273473bae9eb2bf0e9e35d4 77afaaeec2f44f8a9cf427a8bca1c8f6 -#: 7bcc319a9f1a40aaaa884c7facd072eb 7ce03691dff54837a4da07fdcc12e122 +#: ../../en/units/mq.rst:42 121ac930d713418083b370078575f0f4 +#: 2af8ec066aae414cacb109c71b09a690 4bbfbdebab3345448032714990218c5d +#: 7409a311d273473bae9eb2bf0e9e35d4 7ce03691dff54837a4da07fdcc12e122 #: c48050d2f6c947ffb8ce27d895013973 cbbcab2e21e2415a8fd078d07fd1016e -#: d6a9bc36138b494e84c8ab99aeda5831 db280f313f0142818fa54b90e68ab82b -#: f464b940cf36407083cc02aab5262df8 of unit.mq.MQUnit:10 +#: d6a9bc36138b494e84c8ab99aeda5831 of unit.mq.MQUnit:10 #: unit.mq.MQUnit.get_adc_value:11 unit.mq.MQUnit.get_firmware_version:10 -#: unit.mq.MQUnit.get_heat_time:10 unit.mq.MQUnit.get_i2c_address:10 +#: unit.mq.MQUnit.get_heat_time:6 unit.mq.MQUnit.get_i2c_address:10 #: unit.mq.MQUnit.get_led_status:6 unit.mq.MQUnit.get_mq_mode:10 -#: unit.mq.MQUnit.get_ntc_adc_value:11 unit.mq.MQUnit.get_ntc_res_value:10 -#: unit.mq.MQUnit.get_valid_tags:10 unit.mq.MQUnit.get_voltage:11 +#: unit.mq.MQUnit.get_ntc_adc_value:7 unit.mq.MQUnit.get_ntc_res_value:6 +#: unit.mq.MQUnit.get_valid_tags:10 unit.mq.MQUnit.get_voltage:7 #: unit.mq.MQUnit.set_heat_time:10 unit.mq.MQUnit.set_i2c_address:9 #: unit.mq.MQUnit.set_led_status:5 unit.mq.MQUnit.set_mq_mode:14 msgid "MicroPython Code Block:" @@ -137,13 +128,12 @@ msgid "Create a MQUnit object." msgstr "创建一个MQUnit对象." #: ../../en/units/mq.rst 160e2dff03164c90bc2acb781f77239f -#: 255dfe3a1f22409c8feee3f4d5616fa9 2bde080fec5e48f8b8da6e5957efe9e5 -#: 5ba457cec2fa41b588e611390e38455d ad531f618e644b968e86a294f1ddf3fa -#: bffa37ef49394bab8aee3ccce04119e1 d4a9cec4a0294ce0808d475dc4733df0 -#: d949aa001f3746a9b9585a774eb89fd5 of unit.mq.MQUnit.get_adc_value -#: unit.mq.MQUnit.get_ntc_adc_value unit.mq.MQUnit.get_voltage -#: unit.mq.MQUnit.set_heat_time unit.mq.MQUnit.set_i2c_address -#: unit.mq.MQUnit.set_led_status unit.mq.MQUnit.set_mq_mode +#: 2bde080fec5e48f8b8da6e5957efe9e5 ad531f618e644b968e86a294f1ddf3fa +#: bffa37ef49394bab8aee3ccce04119e1 d949aa001f3746a9b9585a774eb89fd5 of +#: unit.mq.MQUnit.get_adc_value unit.mq.MQUnit.get_ntc_adc_value +#: unit.mq.MQUnit.get_voltage unit.mq.MQUnit.set_heat_time +#: unit.mq.MQUnit.set_i2c_address unit.mq.MQUnit.set_led_status +#: unit.mq.MQUnit.set_mq_mode msgid "Parameters" msgstr "" @@ -199,11 +189,8 @@ msgstr "" msgid "Get the current working mode of the MQ sensor." msgstr "获取当前MQ传感器的工作模式." -#: 23445d077f914813a528053dffed824b 35ad4d147da340d19afe1c7587315cbc -#: 3e4d2e4ce2ce41888e5bff84b3d27f49 59a8ded95de44d048e2e0b88b587388d -#: 770274fa41024c2195989e0c5f527e1e 78205eb7d20748258b8b31bd8a50906f -#: 82d1e9666af340e88707eb40af956e04 90ce39c21353451988e0a36577159a1a -#: a75ed69996e64072b146b20ef722429f c04e45028fe74982a11dad5d32f4375b of +#: 23445d077f914813a528053dffed824b 3e4d2e4ce2ce41888e5bff84b3d27f49 +#: 82d1e9666af340e88707eb40af956e04 a75ed69996e64072b146b20ef722429f of #: unit.mq.MQUnit.get_adc_value unit.mq.MQUnit.get_firmware_version #: unit.mq.MQUnit.get_heat_time unit.mq.MQUnit.get_i2c_address #: unit.mq.MQUnit.get_led_status unit.mq.MQUnit.get_mq_mode @@ -216,11 +203,8 @@ msgstr "返回值" msgid "Current working mode value." msgstr "当前工作模式值." -#: 1fa657eb13504ce688a421e00f4f825e 2135076354dd41ee95b0104eca171f3b -#: 2d2bcd8c12b54fd7ae4c3308534c9cd3 2e72c2824a254518b818b1e0e1a02b17 -#: b1c791375eb04f04bb68c6bb67bd89a1 b5f6cc420eb5466eb0972c615d6c8f0c -#: d248d30308b1410dacf198d0e30e784b ddd74f18307f4fd6ac2ee5895cf8e758 -#: e606858e4d19450b8e6c91386f64c235 f9f7b07108ff441baa3b45564749aeb2 of +#: 2135076354dd41ee95b0104eca171f3b 2d2bcd8c12b54fd7ae4c3308534c9cd3 +#: b1c791375eb04f04bb68c6bb67bd89a1 f9f7b07108ff441baa3b45564749aeb2 of #: unit.mq.MQUnit.get_adc_value unit.mq.MQUnit.get_firmware_version #: unit.mq.MQUnit.get_heat_time unit.mq.MQUnit.get_i2c_address #: unit.mq.MQUnit.get_led_status unit.mq.MQUnit.get_mq_mode @@ -284,20 +268,12 @@ msgstr "获取加热器高低电平时间." msgid "[high_level_time, low_level_time]" msgstr "" -#: 5d0ef8f280ba4fc2b74ba984ca70ecb1 of unit.mq.MQUnit.get_heat_time:8 -msgid "|get_heat_time.png|" -msgstr "" - -#: ../../en/refs/unit.mq.ref:14 f186c978d32b44c4a85b4648e8a7878e -msgid "get_heat_time.png" -msgstr "" - #: 5435ab51fbd94af685ad9d73904196b5 of unit.mq.MQUnit.get_adc_value:1 msgid "Get ADC value." msgstr "获取ADC值." -#: 2e129dedba904fc8aa50e3816ec15626 e55bc86273ee42009ab4ff3c30a78e3d of -#: unit.mq.MQUnit.get_adc_value:3 unit.mq.MQUnit.get_ntc_adc_value:3 +#: 2e129dedba904fc8aa50e3816ec15626 of unit.mq.MQUnit.get_adc_value:3 +#: unit.mq.MQUnit.get_ntc_adc_value:3 msgid "0 for 8-bit, 1 for 12-bit." msgstr "0 代表8位ADC数值,1 代表12位ADC数值." @@ -337,14 +313,6 @@ msgstr "获取内部NTC ADC值." msgid "NTC ADC value." msgstr "NTC ADC值." -#: 6374e9ad0716452b8f766a6e0bab0164 of unit.mq.MQUnit.get_ntc_adc_value:9 -msgid "|get_ntc_adc_value.png|" -msgstr "" - -#: ../../en/refs/unit.mq.ref:17 9a13d0fe4cbf44beb20dd5a3f5ed49c1 -msgid "get_ntc_adc_value.png" -msgstr "" - #: 0db410b26286408199244b84ee67877e of unit.mq.MQUnit.get_ntc_res_value:1 msgid "Get internal NTC resistance value." msgstr "获取内部NTC电阻值." @@ -353,14 +321,6 @@ msgstr "获取内部NTC电阻值." msgid "Resistance value." msgstr "电阻值." -#: c1f6484b034d48e2a1e1bc56df39542d of unit.mq.MQUnit.get_ntc_res_value:8 -msgid "|get_ntc_res_value.png|" -msgstr "" - -#: ../../en/refs/unit.mq.ref:18 28194a1ec5fe4cc78574b41e56d52603 -msgid "get_ntc_res_value.png" -msgstr "" - #: 15e786c7357c44f0a327d8e1eb99302e of unit.mq.MQUnit.get_voltage:1 msgid "Get voltage value from a specific channel." msgstr "获取指定通道的电压值." @@ -373,14 +333,6 @@ msgstr "通道号." msgid "Voltage value." msgstr "电压值." -#: adee21bb66de41d6adca194d733b446f of unit.mq.MQUnit.get_voltage:9 -msgid "|get_voltage.png|" -msgstr "" - -#: ../../en/refs/unit.mq.ref:19 45331917ff4b4e809731bca71cab93ef -msgid "get_voltage.png" -msgstr "" - #: e287e9ace673452f956c46c3d73cb850 of unit.mq.MQUnit.get_firmware_version:1 msgid "Get firmware version." msgstr "获取固件版本." @@ -429,3 +381,30 @@ msgstr "" msgid "set_i2c_address.png" msgstr "" +#~ msgid "|get_heat_time.png|" +#~ msgstr "" + +#~ msgid "get_heat_time.png" +#~ msgstr "" + +#~ msgid "|get_ntc_adc_value.png|" +#~ msgstr "" + +#~ msgid "get_ntc_adc_value.png" +#~ msgstr "" + +#~ msgid "|get_ntc_res_value.png|" +#~ msgstr "" + +#~ msgid "get_ntc_res_value.png" +#~ msgstr "" + +#~ msgid "|get_voltage.png|" +#~ msgstr "" + +#~ msgid "get_voltage.png" +#~ msgstr "" + +#~ msgid "get MQ value" +#~ msgstr "获取MQ值" + From 4f6c9e77cfa6112213bbfb23be5cc76e3edfdda6 Mon Sep 17 00:00:00 2001 From: LittleMouse Date: Tue, 26 Aug 2025 10:07:54 +0800 Subject: [PATCH 239/322] libs/module/llm.py: Add dynamic parameter list. Signed-off-by: LittleMouse --- m5stack/libs/module/llm.py | 78 ++++++++++++++++++++++++++++---------- 1 file changed, 59 insertions(+), 19 deletions(-) diff --git a/m5stack/libs/module/llm.py b/m5stack/libs/module/llm.py index 4cb65137..c64007bf 100644 --- a/m5stack/libs/module/llm.py +++ b/m5stack/libs/module/llm.py @@ -230,6 +230,7 @@ def setup( enkws=True, max_token_len=127, request_id="llm_setup", + **kwargs, ) -> str: cmd = { "request_id": request_id, @@ -246,6 +247,7 @@ def setup( "prompt": prompt, }, } + cmd["data"].update(kwargs) success = self._module_msg.send_cmd_and_wait_to_take_msg( ujson.dumps(cmd), request_id, self._set_llm_work_id, 30000 ) @@ -321,6 +323,7 @@ def setup( enoutput=True, max_token_len=256, request_id="vlm_setup", + **kwargs, ) -> str: cmd = { "request_id": request_id, @@ -336,6 +339,7 @@ def setup( "prompt": prompt, }, } + cmd["data"].update(kwargs) success = self._module_msg.send_cmd_and_wait_to_take_msg( ujson.dumps(cmd), request_id, self._set_llm_work_id, 30000 ) @@ -420,6 +424,7 @@ def setup( playdevice=1, play_volume=0.15, request_id="audio_setup", + **kwargs, ) -> str: cmd = { "request_id": request_id, @@ -435,7 +440,7 @@ def setup( "playVolume": play_volume, }, } - + cmd["data"].update(kwargs) success = self._module_msg.send_cmd_and_wait_to_take_msg( ujson.dumps(cmd), request_id, self._set_audio_work_id, 5000 ) @@ -468,6 +473,7 @@ def setup( enoutput=False, response_format="camera.raw", request_id="camera_setup", + **kwargs, ) -> str: cmd = { "request_id": request_id, @@ -482,7 +488,7 @@ def setup( "enoutput": enoutput, }, } - + cmd["data"].update(kwargs) success = self._module_msg.send_cmd_and_wait_to_take_msg( ujson.dumps(cmd), request_id, self._set_camera_work_id, 10000 ) @@ -517,6 +523,7 @@ def setup( enoutput=False, enkws=False, request_id="tts_setup", + **kwargs, ) -> str: cmd = { "request_id": request_id, @@ -531,7 +538,7 @@ def setup( "enkws": enkws, }, } - + cmd["data"].update(kwargs) success = self._module_msg.send_cmd_and_wait_to_take_msg( ujson.dumps(cmd), request_id, self._set_llm_work_id, 10000 ) @@ -588,6 +595,7 @@ def setup( input="tts.utf-8.stream", enoutput=False, request_id="melotts_setup", + **kwargs, ) -> str: cmd = { "request_id": request_id, @@ -601,7 +609,7 @@ def setup( "enoutput": enoutput, }, } - + cmd["data"].update(kwargs) success = self._module_msg.send_cmd_and_wait_to_take_msg( ujson.dumps(cmd), request_id, self._set_llm_work_id, 30000 ) @@ -658,6 +666,7 @@ def setup( enoutput=True, enaudio=True, request_id="kws_setup", + **kwargs, ) -> str: cmd = { "request_id": request_id, @@ -673,7 +682,7 @@ def setup( "kws": kws, }, } - + cmd["data"].update(kwargs) success = self._module_msg.send_cmd_and_wait_to_take_msg( ujson.dumps(cmd), request_id, self._set_llm_work_id, 30000 ) @@ -708,6 +717,7 @@ def setup( rule2=1.2, rule3=30.0, request_id="asr_setup", + **kwargs, ) -> str: cmd = { "request_id": request_id, @@ -725,7 +735,7 @@ def setup( "rule3": rule3, }, } - + cmd["data"].update(kwargs) success = self._module_msg.send_cmd_and_wait_to_take_msg( ujson.dumps(cmd), request_id, self._set_llm_work_id, 10000 ) @@ -756,6 +766,7 @@ def setup( input="sys.pcm", enoutput=True, request_id="vad_setup", + **kwargs, ) -> str: cmd = { "request_id": request_id, @@ -769,7 +780,7 @@ def setup( "enoutput": enoutput, }, } - + cmd["data"].update(kwargs) success = self._module_msg.send_cmd_and_wait_to_take_msg( ujson.dumps(cmd), request_id, self._set_llm_work_id, 30000 ) @@ -801,6 +812,7 @@ def setup( enoutput=True, language="en", request_id="whisper_setup", + **kwargs, ) -> str: cmd = { "request_id": request_id, @@ -815,7 +827,7 @@ def setup( "enoutput": enoutput, }, } - + cmd["data"].update(kwargs) success = self._module_msg.send_cmd_and_wait_to_take_msg( ujson.dumps(cmd), request_id, self._set_llm_work_id, 30000 ) @@ -847,6 +859,7 @@ def setup( input="yolo.jpg.base64", enoutput=True, request_id="yolo_setup", + **kwargs, ) -> str: cmd = { "request_id": request_id, @@ -860,7 +873,7 @@ def setup( "enoutput": enoutput, }, } - + cmd["data"].update(kwargs) success = self._module_msg.send_cmd_and_wait_to_take_msg( ujson.dumps(cmd), request_id, self._set_yolo_work_id, 10000 ) @@ -1030,6 +1043,7 @@ def llm_setup( enkws=None, max_token_len=127, request_id="llm_setup", + **kwargs, ) -> str: if self.version == "v1.0": model = "qwen2.5-0.5b" @@ -1048,7 +1062,15 @@ def llm_setup( enkws = bool(enkws) self.latest_llm_work_id = self.llm.setup( - prompt, model, response_format, input, enoutput, enkws, max_token_len, request_id + prompt, + model, + response_format, + input, + enoutput, + enkws, + max_token_len, + request_id, + **kwargs, ) return self.latest_llm_work_id @@ -1066,6 +1088,7 @@ def vlm_setup( enkws=None, max_token_len=256, request_id="vlm_setup", + **kwargs, ) -> str: if input is None: input = ["vlm.utf-8"] @@ -1076,7 +1099,7 @@ def vlm_setup( input.append(enkws) self.latest_vlm_work_id = self.vlm.setup( - prompt, model, response_format, input, enoutput, max_token_len, request_id + prompt, model, response_format, input, enoutput, max_token_len, request_id, **kwargs ) return self.latest_vlm_work_id @@ -1098,9 +1121,10 @@ def audio_setup( playdevice=1, play_volume=0.15, request_id="audio_setup", + **kwargs, ) -> str: self.latest_audio_work_id = self.audio.setup( - capcard, capdevice, cap_volume, playcard, playdevice, play_volume, request_id + capcard, capdevice, cap_volume, playcard, playdevice, play_volume, request_id, **kwargs ) return self.latest_audio_work_id @@ -1112,9 +1136,10 @@ def camera_setup( frame_height=320, enoutput=False, response_format="camera.raw", + **kwargs, ) -> str: self.latest_camera_work_id = self.camera.setup( - input, frame_width, frame_height, enoutput, response_format, request_id + input, frame_width, frame_height, enoutput, response_format, request_id, **kwargs ) return self.latest_camera_work_id @@ -1145,6 +1170,7 @@ def tts_setup( enoutput=False, enkws=None, request_id="tts_setup", + **kwargs, ) -> str: if self.version == "v1.0": response_format = "tts.base64.wav" @@ -1165,7 +1191,7 @@ def tts_setup( enkws = bool(enkws) self.latest_tts_work_id = self.tts.setup( - model, response_format, input, enoutput, enkws, request_id + model, response_format, input, enoutput, enkws, request_id, **kwargs ) return self.latest_tts_work_id @@ -1182,6 +1208,7 @@ def melotts_setup( enoutput=False, enkws=None, request_id="melotts_setup", + **kwargs, ) -> str: if float(self.version.lstrip("v")) >= 1.6 and model == "melotts_zh-cn": model = "melotts-zh-cn" @@ -1201,7 +1228,7 @@ def melotts_setup( input.append(enkws) self.latest_melotts_work_id = self.melotts.setup( - model, response_format, input, enoutput, request_id + model, response_format, input, enoutput, request_id, **kwargs ) return self.latest_melotts_work_id @@ -1219,11 +1246,12 @@ def kws_setup( enoutput=True, enaudio=True, request_id="kws_setup", + **kwargs, ) -> str: if language == "zh_CN": model = "sherpa-onnx-kws-zipformer-wenetspeech-3.3M-2024-01-01" self.latest_kws_work_id = self.kws.setup( - kws, model, response_format, input, enoutput, enaudio, request_id + kws, model, response_format, input, enoutput, enaudio, request_id, **kwargs ) return self.latest_kws_work_id @@ -1239,6 +1267,7 @@ def asr_setup( rule2=1.2, rule3=30.0, request_id="asr_setup", + **kwargs, ) -> str: if input is None: input = "sys.pcm" if self.version == "v1.0" else ["sys.pcm"] @@ -1255,7 +1284,16 @@ def asr_setup( enkws = bool(enkws) self.latest_asr_work_id = self.asr.setup( - model, response_format, input, enoutput, enkws, rule1, rule2, rule3, request_id + model, + response_format, + input, + enoutput, + enkws, + rule1, + rule2, + rule3, + request_id, + **kwargs, ) return self.latest_asr_work_id @@ -1267,6 +1305,7 @@ def vad_setup( enoutput=True, enkws=None, request_id="kws_setup", + **kwargs, ) -> str: if input is None: input = ["sys.pcm"] @@ -1275,7 +1314,7 @@ def vad_setup( input = [input] input.append(enkws) self.latest_vad_work_id = self.vad.setup( - model, response_format, input, enoutput, request_id + model, response_format, input, enoutput, request_id, **kwargs ) return self.latest_vad_work_id @@ -1289,6 +1328,7 @@ def whisper_setup( enkws=None, envad=None, request_id="asr_setup", + **kwargs, ) -> str: if input is None: input = ["sys.pcm"] @@ -1301,7 +1341,7 @@ def whisper_setup( input = [input] input.append(envad) self.latest_whisper_work_id = self.whisper.setup( - model, response_format, input, enoutput, language, request_id + model, response_format, input, enoutput, language, request_id, **kwargs ) return self.latest_whisper_work_id From 45d53b067c010e597e81b2a96cc5c62e59207234 Mon Sep 17 00:00:00 2001 From: hlym123 Date: Tue, 2 Sep 2025 15:40:26 +0800 Subject: [PATCH 240/322] boards: Fix I2C SCL/SDA reversed. Signed-off-by: hlym123 --- m5stack/boards/M5STACK_CoreS3/board_init.c | 65 +++++++++++----------- 1 file changed, 33 insertions(+), 32 deletions(-) diff --git a/m5stack/boards/M5STACK_CoreS3/board_init.c b/m5stack/boards/M5STACK_CoreS3/board_init.c index 3f2320fa..3845cea0 100644 --- a/m5stack/boards/M5STACK_CoreS3/board_init.c +++ b/m5stack/boards/M5STACK_CoreS3/board_init.c @@ -33,6 +33,15 @@ static char *TAG = "cores3"; static void *audio_hal = NULL; +#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_0 +#define AUDIO_I2S_GPIO_WS GPIO_NUM_33 +#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_34 +#define AUDIO_I2S_GPIO_DIN GPIO_NUM_14 +#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_13 +#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_11 +#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_12 +#define AUDIO_CODEC_GPIO_PA GPIO_NUM_NC + #if USE_IDF5 @@ -63,11 +72,11 @@ esp_err_t get_i2s_pins(int port, board_i2s_pin_t *i2s_config) ESP_LOGI(TAG, "get_i2s_pins !!!"); AUDIO_NULL_CHECK(TAG, i2s_config, return ESP_FAIL); if (port == 1) { - i2s_config->bck_io_num = GPIO_NUM_34; - i2s_config->ws_io_num = GPIO_NUM_33; - i2s_config->data_out_num = GPIO_NUM_13; - i2s_config->data_in_num = GPIO_NUM_14; - i2s_config->mck_io_num = GPIO_NUM_0; + i2s_config->bck_io_num = AUDIO_I2S_GPIO_BCLK; + i2s_config->ws_io_num = AUDIO_I2S_GPIO_WS; + i2s_config->data_out_num = AUDIO_I2S_GPIO_DOUT; + i2s_config->data_in_num = AUDIO_I2S_GPIO_DIN; + i2s_config->mck_io_num = AUDIO_I2S_GPIO_MCLK; } else { memset(i2s_config, -1, sizeof(board_i2s_pin_t)); ESP_LOGE(TAG, "I2S PORT %d is not supported, please use I2S PORT 0", port); @@ -127,8 +136,8 @@ void * board_codec_init(void) const audio_codec_if_t *in_codec_if = es7210_codec_new(&es7210_cfg); esp_codec_dev_cfg_t dev_cfg = { - .codec_if = out_codec_if, // aw88298_codec_new 获取到的接口实现 - .data_if = data_if, // 这里不实例化 i2s; 后续的 i2s_stream_init 会实例化 i2s。 + .codec_if = out_codec_if, // aw88298_codec_new 获取到的接口实现 + .data_if = data_if, // 这里不实例化 i2s; 后续的 i2s_stream_init 会实例化 i2s。 .dev_type = ESP_CODEC_DEV_TYPE_OUT, // 设备只播放 }; audio_hal = esp_codec_dev_new(&dev_cfg); @@ -156,29 +165,26 @@ void * board_codec_init(void) return audio_hal; } - // NOTE: 使用内联函数??? int board_codec_volume_set(void *hd, int vol) { return esp_codec_dev_set_out_vol(hd, vol); } - // NOTE: 使用内联函数??? int board_codec_volume_get(void *hd, int *vol) { return esp_codec_dev_get_out_vol(hd, vol); } - static int ut_i2c_init(uint8_t port) { #ifdef USE_IDF_I2C_MASTER i2c_master_bus_config_t i2c_bus_config = {0}; i2c_bus_config.clk_source = I2C_CLK_SRC_DEFAULT; i2c_bus_config.i2c_port = port; - i2c_bus_config.scl_io_num = 12; - i2c_bus_config.sda_io_num = 11; + i2c_bus_config.scl_io_num = AUDIO_CODEC_I2C_SCL_PIN; + i2c_bus_config.sda_io_num = AUDIO_CODEC_I2C_SDA_PIN; i2c_bus_config.glitch_ignore_cnt = 7; i2c_bus_config.flags.enable_internal_pullup = true; return i2c_new_master_bus(&i2c_bus_config, &i2c_bus_handle); @@ -189,8 +195,8 @@ static int ut_i2c_init(uint8_t port) .scl_pullup_en = GPIO_PULLUP_ENABLE, .master.clk_speed = 100000, }; - i2c_cfg.sda_io_num = 12; - i2c_cfg.scl_io_num = 11; + i2c_cfg.scl_io_num = AUDIO_CODEC_I2C_SCL_PIN; + i2c_cfg.sda_io_num = AUDIO_CODEC_I2C_SDA_PIN; esp_err_t ret = i2c_param_config(port, &i2c_cfg); if (ret != ESP_OK) { return -1; @@ -199,7 +205,6 @@ static int ut_i2c_init(uint8_t port) #endif } - static int ut_i2c_deinit(uint8_t port) { #ifdef USE_IDF_I2C_MASTER @@ -213,7 +218,6 @@ static int ut_i2c_deinit(uint8_t port) #endif } - #if USE_IDF5 static void ut_set_i2s_mode(i2s_comm_mode_t out_mode, i2s_comm_mode_t in_mode) { @@ -221,7 +225,6 @@ static void ut_set_i2s_mode(i2s_comm_mode_t out_mode, i2s_comm_mode_t in_mode) i2s_out_mode = out_mode; } - static void ut_clr_i2s_mode(void) { i2s_in_mode = I2S_COMM_MODE_STD; @@ -229,7 +232,6 @@ static void ut_clr_i2s_mode(void) } #endif - static int ut_i2s_init(uint8_t port) { #if USE_IDF5 @@ -245,10 +247,10 @@ static int ut_i2s_init(uint8_t port) .clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(16000), .slot_cfg = I2S_STD_PHILIPS_SLOT_DEFAULT_CONFIG(16, I2S_SLOT_MODE_STEREO), .gpio_cfg ={ - .mclk = 0, - .bclk = 34, - .ws = 33, - .dout = 13, + .mclk = AUDIO_I2S_GPIO_MCLK, + .bclk = AUDIO_I2S_GPIO_BCLK, + .ws = AUDIO_I2S_GPIO_WS, + .dout = AUDIO_I2S_GPIO_DOUT, .din = -1, }, }; @@ -262,10 +264,10 @@ static int ut_i2s_init(uint8_t port) .slot_cfg = I2S_TDM_PHILIPS_SLOT_DEFAULT_CONFIG(16, I2S_SLOT_MODE_STEREO, slot_mask), .clk_cfg = I2S_TDM_CLK_DEFAULT_CONFIG(16000), .gpio_cfg = { - .mclk = 0, - .bclk = 34, - .ws = 33, - .dout = 13, + .mclk = AUDIO_I2S_GPIO_MCLK, + .bclk = AUDIO_I2S_GPIO_BCLK, + .ws = AUDIO_I2S_GPIO_WS, + .dout = AUDIO_I2S_GPIO_DOUT, .din = -1, }, }; @@ -305,18 +307,17 @@ static int ut_i2s_init(uint8_t port) }; int ret = i2s_driver_install(port, &i2s_config, 0, NULL); i2s_pin_config_t i2s_pin_cfg = { - .mck_io_num = 0, - .bck_io_num = 34, - .ws_io_num = 33, - .data_out_num = 13, - .data_in_num = 14, + .mck_io_num = AUDIO_I2S_GPIO_MCLK, + .bck_io_num = AUDIO_I2S_GPIO_BCLK, + .ws_io_num = AUDIO_I2S_GPIO_WS, + .data_out_num = AUDIO_I2S_GPIO_DOUT, + .data_in_num = AUDIO_I2S_GPIO_DIN, }; i2s_set_pin(port, &i2s_pin_cfg); #endif return ret; } - static int ut_i2s_deinit(uint8_t port) { #if USE_IDF5 From 1affbc90e1e263d39053de8ab58b43037691099d Mon Sep 17 00:00:00 2001 From: hlym123 Date: Fri, 5 Sep 2025 10:52:35 +0800 Subject: [PATCH 241/322] docs: Add Display API description. Signed-off-by: hlym123 --- docs/en/hardware/display.rst | 1101 ++++++++++---- docs/en/m5ui/index.rst | 26 + docs/en/refs/hardware.display.ref | 54 + docs/en/widgets/index.rst | 26 + .../zh_CN/LC_MESSAGES/hardware/display.po | 1322 ++++++++++++----- docs/locales/zh_CN/LC_MESSAGES/m5ui/index.po | 139 +- .../zh_CN/LC_MESSAGES/widgets/index.po | 147 +- .../display/cores3_draw_test_example.m5f2 | 1 + .../display/cores3_draw_test_example.py | 52 + 9 files changed, 2158 insertions(+), 710 deletions(-) create mode 100644 docs/en/refs/hardware.display.ref create mode 100644 examples/hardware/display/cores3_draw_test_example.m5f2 create mode 100644 examples/hardware/display/cores3_draw_test_example.py diff --git a/docs/en/hardware/display.rst b/docs/en/hardware/display.rst index 33ad51d0..7bc3b63f 100644 --- a/docs/en/hardware/display.rst +++ b/docs/en/hardware/display.rst @@ -3,479 +3,988 @@ Display ======= +.. include:: ../refs/hardware.display.ref + A lcd display library .. module:: Display :synopsis: A lcd display library -Micropython Example +M5 Series Display Libraries +--------------------------- + +1. Display +^^^^^^^^^^^ +- A low-level graphics library providing basic screen drawing, text, lines, and color management. +- Can be used independently, suitable for scenarios that only require drawing graphics or text. + +2. M5Widgets +^^^^^^^^^^^^^ +- A basic UI widget library providing labels, image displays, and other UI controls. +- Built on top of M5GFX. +- Suitable for simple interactive UI elements. + +3. M5UI +^^^^^^^^ +- A high-level UI framework based on LVGL. +- Provides page management, multi-widget layouts, and unified event handling. + +Usage Tips +^^^^^^^^^^ +- ⚠️ Do not mix M5GFX, M5Widgets, and M5UI simultaneously, as it may cause rendering issues or event conflicts. +- For graphics-only drawing → use M5GFX. +- For simple interactive widgets → use M5Widgets. +- For multi-page UI → use M5UI. + + +UiFlow2 Example +--------------- + +draw test +^^^^^^^^^ + +Open the |cores3_draw_test_example.m5f2| project in UiFlow2. + +This example demonstrates basic drawing functions of Display, including text, images, QR code, and various shapes. + +UiFlow2 Code Block: + + |cores3_draw_test_example.png| + +Example output: + + None + +MicroPython Example ------------------- draw test -+++++++++ +^^^^^^^^^ + +This example demonstrates basic drawing functions of Display, including text, images, QR code, and various shapes. + +MicroPython Code Block: + + .. literalinclude:: ../../../examples/hardware/display/cores3_draw_test_example.py + :language: python + :linenos: + +Example output: + + None + +**API** +------- + +.. class:: M5.Display + + .. method:: width() + + Get the horizontal resolution of the display. + + :returns width: horizontal resolution in pixels. + :return type: int + + UiFlow2 Code Block: + + |width.png| + + MicroPython Code Block: + + .. code-block:: python + + Display.width() + + .. method:: height() + + Get the vertical resolution of the display. + + :returns height: vertical resolution in pixels. + :return type: int + + UiFlow2 Code Block: + + |height.png| + + MicroPython Code Block: + + .. code-block:: python + + Display.height() + + .. method:: getRotation() + + Get the current rotation of the display. + + :returns rotation: display rotation value. + :return type: int + + Rotation values: + + - 1: 0° rotation + - 2: 90° rotation + - 3: 180° rotation + - 4: 270° rotation + + UiFlow2 Code Block: + + |getrotation.png| + + MicroPython Code Block: + + .. code-block:: python + + Display.getRotation() + + .. method:: getColorDepth() + + Get the color depth of the display. + + :returns depth: color depth in bits per pixel. + :return type: int + + UiFlow2 Code Block: + + |getcolordepth.png| + + MicroPython Code Block: + + .. code-block:: python + + Display.getColorDepth() + + .. method:: getCursor() + + Get the current cursor position on the display. + + :returns pos: tuple (x, y) of cursor position. + :return type: tuple + + UiFlow2 Code Block: + + |getcursor.png| + + MicroPython Code Block: + + .. code-block:: python + + Display.getCursor() + + .. method:: setRotation(r) + + Set the rotation of the display. + + :param int r: rotation value (1~4) + - 1: 0° rotation + - 2: 90° rotation + - 3: 180° rotation + - 4: 270° rotation + + UiFlow2 Code Block: + + |setrotation.png| + + MicroPython Code Block: + + .. code-block:: python + + Display.setRotation(2) + + .. method:: setColorDepth(bpp) + + Set the color depth of the display. + + :param int bpp: desired color depth in bits per pixel. + + Notes: For CoreS3 devices, color depth is fixed at 16 bits and this method has no effect. + + UiFlow2 Code Block: + + |setcolordepth.png| + + MicroPython Code Block: + + .. code-block:: python + + Display.setColorDepth(16) + + .. method:: setEpdMode(epd_mode) + + Set the EPD mode for the display. + + :param int epd_mode: desired EPD mode + - 0: M5.Lcd.EPDMode.EPD_QUALITY + - 1: M5.Lcd.EPDMode.EPD_TEXT + - 2: M5.Lcd.EPDMode.EPD_FAST + - 3: M5.Lcd.EPDMode.EPD_FASTEST + + Notes: Only applicable to devices with EPD capabilities. + + UiFlow2 Code Block: + + |setepdmode.png| + + MicroPython Code Block: + + .. code-block:: python + + Display.setEpdMode(2) + + .. method:: isEPD() + + Check if the display is an EPD (Electronic Paper Display). + + :returns is_epd: True if the display is EPD, False otherwise. + :return type: bool + + UiFlow2 Code Block: + + |isepd.png| + + MicroPython Code Block: + + .. code-block:: python + + Display.isEPD() + + .. method:: setFont(font) + + Set the font for the display. + + :param font: font type. Available options: + + - M5.Lcd.FONTS.ASCII7 + - M5.Lcd.FONTS.DejaVu9 + - M5.Lcd.FONTS.DejaVu12 + - M5.Lcd.FONTS.DejaVu18 + - M5.Lcd.FONTS.DejaVu24 + - M5.Lcd.FONTS.DejaVu40 + - M5.Lcd.FONTS.DejaVu56 + - M5.Lcd.FONTS.DejaVu72 + - M5.Lcd.FONTS.EFontCN24 + - M5.Lcd.FONTS.EFontJA24 + - M5.Lcd.FONTS.EFontKR24 + + UiFlow2 Code Block: + + |setfont.png| + + MicroPython Code Block: + + .. code-block:: python + + Display.setFont(M5.Lcd.FONTS.DejaVu18) + + .. method:: setTextColor(fgcolor, bgcolor) + + Set the text color and background color. + + :param int fgcolor: text color in RGB888 format (default 0, black) + :param int bgcolor: background color in RGB888 format (default 0, black) + + UiFlow2 Code Block: + + |settextcolor.png| + + MicroPython Code Block: + + .. code-block:: python + + Display.setTextColor(0xFF0000, 0x000000) + + .. method:: setTextScroll(scroll) + + Enable or disable text scrolling. + + :param bool scroll: True to enable text scrolling, False to disable (default False) + + UiFlow2 Code Block: + + |settextscroll.png| + + MicroPython Code Block: + + .. code-block:: python + + Display.setTextScroll(True) + + .. method:: setTextSize(size) + + Set the size of the text. + + :param int size: desired text size + + UiFlow2 Code Block: + + |settextsize.png| + + MicroPython Code Block: + + .. code-block:: python + + Display.setTextSize(2) + + .. method:: setCursor(x, y) + + Set the cursor position. + + :param int x: horizontal position of the cursor (default 0) + :param int y: vertical position of the cursor (default 0) + + UiFlow2 Code Block: + + |setcursor.png| + + MicroPython Code Block: + + .. code-block:: python + + Display.setCursor(10, 20) + + .. method:: clear(color) + + Clear the display with a specific color. + + :param int color: fill color in RGB888 format (default 0) + + UiFlow2 Code Block: + + |clear.png| + + MicroPython Code Block: + + .. code-block:: python + + Display.clear(0xFFFFFF) + + .. method:: fillScreen(color) + + Fill the entire screen with a specified color. + + :param int color: fill color in RGB888 format (default 0) + + UiFlow2 Code Block: + + |fillscreen.png| + + MicroPython Code Block: + + .. code-block:: python + + Display.fillScreen(0xFF0000) + + .. method:: drawPixel(x, y, color) + + Draw a single pixel on the screen. + + :param int x: horizontal coordinate of the pixel (default -1) + :param int y: vertical coordinate of the pixel (default -1) + :param int color: pixel color in RGB888 format (default 0) + + UiFlow2 Code Block: + + |drawpixel.png| + + MicroPython Code Block: + + .. code-block:: python + + Display.drawPixel(50, 50, 0x00FF00) + + .. method:: drawCircle(x, y, r, color) + + Draw an outline of a circle. + + :param int x: x-coordinate of circle center (default -1) + :param int y: y-coordinate of circle center (default -1) + :param int r: radius of the circle (default -1) + :param int color: circle color in RGB888 format (default 0) + + UiFlow2 Code Block: + + |drawcircle.png| + + MicroPython Code Block: + + .. code-block:: python + + Display.drawCircle(60, 60, 20, 0x0000FF) + + .. method:: fillCircle(x, y, r, color) + + Draw a filled circle. + + :param int x: x-coordinate of circle center (default -1) + :param int y: y-coordinate of circle center (default -1) + :param int r: radius of the circle (default -1) + :param int color: fill color in RGB888 format (default 0) + + UiFlow2 Code Block: + + |fillcircle.png| + + MicroPython Code Block: + + .. code-block:: python + + Display.fillCircle(60, 60, 20, 0x00FFFF) + + .. method:: drawEllipse(x, y, rx, ry, color) + + Draw an outline of an ellipse. + + :param int x: x-coordinate of ellipse center (default -1) + :param int y: y-coordinate of ellipse center (default -1) + :param int rx: horizontal radius (default -1) + :param int ry: vertical radius (default -1) + :param int color: ellipse color in RGB888 format (default 0) + + UiFlow2 Code Block: + + |drawellipse.png| + + MicroPython Code Block: + + .. code-block:: python + + Display.drawEllipse(80, 40, 30, 20, 0xFF00FF) + + .. method:: fillEllipse(x, y, rx, ry, color) + + Draw a filled ellipse. + + :param int x: x-coordinate of ellipse center (default -1) + :param int y: y-coordinate of ellipse center (default -1) + :param int rx: horizontal radius (default -1) + :param int ry: vertical radius (default -1) + :param int color: fill color in RGB888 format (default 0) + + UiFlow2 Code Block: + + |fillellipse.png| + + MicroPython Code Block: + + .. code-block:: python + + Display.fillEllipse(80, 40, 30, 20, 0x00FF00) + + .. method:: drawLine(x0, y0, x1, y1, color) + + Draw a line. + + :param int x0: starting x-coordinate (default -1) + :param int y0: starting y-coordinate (default -1) + :param int x1: ending x-coordinate (default -1) + :param int y1: ending y-coordinate (default -1) + :param int color: line color in RGB888 format (default 0) + + UiFlow2 Code Block: + + |drawline.png| + + MicroPython Code Block: + + .. code-block:: python + + Display.drawLine(10, 10, 100, 100, 0xFF0000) + + .. method:: drawRect(x, y, w, h, color) + + Draw a rectangle. + + :param int x: top-left x-coordinate (default -1) + :param int y: top-left y-coordinate (default -1) + :param int w: width of rectangle (default -1) + :param int h: height of rectangle (default -1) + :param int color: rectangle color in RGB888 format (default 0) + + UiFlow2 Code Block: + + |drawrect.png| + + MicroPython Code Block: + + .. code-block:: python + + display.drawRect(20, 20, 80, 50, 0x00FF00) + + .. method:: fillRect(x, y, w, h, color) + + Draw a filled rectangle. + + :param int x: top-left x-coordinate (default -1) + :param int y: top-left y-coordinate (default -1) + :param int w: width of rectangle (default -1) + :param int h: height of rectangle (default -1) + :param int color: fill color in RGB888 format (default 0) + + UiFlow2 Code Block: + + |fillrect.png| + + MicroPython Code Block: + + .. code-block:: python + + Display.fillRect(20, 20, 80, 50, 0x0000FF) + + .. method:: drawRoundRect(x, y, w, h, r, color) -:: + Draw a rounded rectangle. - import M5 - from M5 import Display - import random - import time + :param int x: top-left x-coordinate (default -1) + :param int y: top-left y-coordinate (default -1) + :param int w: width of rectangle (default -1) + :param int h: height of rectangle (default -1) + :param int r: corner radius (default -1) + :param int color: rectangle color in RGB888 format (default 0) - M5.begin() + UiFlow2 Code Block: - print("rotation: ", Display.getRotation()) - print("color depth: ", Display.getColorDepth()) - print("w: %d, h: %d"%(Display.width(), Display.height())) + |drawroundrect.png| - Display.setRotation(1) - Display.clear(0) - Display.setTextColor(fgcolor=0x0000FF, bgcolor=0) - Display.setFont(M5.Lcd.FONTS.EFontCN24) - Display.setCursor(220, 3) - Display.print("你好",color=0xFF0000) + MicroPython Code Block: - Display.drawImage("res/img/uiflow.jpg", 0, 0) - Display.drawJpg("res/img/default.jpg", 60, 0) + .. code-block:: python - Display.drawQR("Hello", 220, 40, 100) + Display.drawRoundRect(30, 30, 60, 40, 10, 0xFF00FF) - Display.drawCircle(30, 80, 20, 0x0000FF) - Display.fillCircle(80, 80, 20, 0x0000FF) + .. method:: fillRoundRect(x, y, w, h, r, color) - Display.drawEllipse(60, 140, 50, 30, 0x00FF00) - Display.fillEllipse(60, 140, 30, 20, 0xFFFF00) + Draw a filled rounded rectangle. - Display.drawLine(x0=115, y0=10, x1=115, y1=60, color=0xFF0000) + :param int x: top-left x-coordinate (default -1) + :param int y: top-left y-coordinate (default -1) + :param int w: width of rectangle (default -1) + :param int h: height of rectangle (default -1) + :param int r: corner radius (default -1) + :param int color: fill color in RGB888 format (default 0) - Display.drawRect(125, 10, 40, 30, 0xFF0000) - Display.fillRect(125, 50, 40, 30, 0x00FF00) + UiFlow2 Code Block: - Display.drawRoundRect(120, 90, 50, 40, 10, 0xFF0000) - Display.fillRoundRect(125, 95, 40, 30, 10, 0x00FF00) + |fillroundrect.png| - Display.drawTriangle(135, 150, 110, 190, 160, 190, 0x00FF00) - Display.fillTriangle(145, 150, 170, 190, 190, 150, 0x0000FF) + MicroPython Code Block: - Display.drawArc(10, 180, 40, 45, 0, 90, 0xFFFF00) - Display.fillArc(20, 190, 40, 45, 0, 90, 0x00FFFF) + .. code-block:: python - Display.drawEllipseArc(200, 150, 30, 35, 20, 25, 0, 90, 0x00FF0F) - Display.fillEllipseArc(200, 170, 30, 35, 20, 25, 0, 90, 0x00FFF0) + Display.fillRoundRect(30, 30, 60, 40, 10, 0x00FFFF) + .. method:: drawTriangle(x0, y0, x1, y1, x2, y2, color) -Functions ---------- + Draw a triangle. -.. method:: Display.width() -> int + :param int x0: first vertex x-coordinate (default -1) + :param int y0: first vertex y-coordinate (default -1) + :param int x1: second vertex x-coordinate (default -1) + :param int y1: second vertex y-coordinate (default -1) + :param int x2: third vertex x-coordinate (default -1) + :param int y2: third vertex y-coordinate (default -1) + :param int color: triangle color in RGB888 format (default 0) - Get the horizontal resolution of the display. + UiFlow2 Code Block: - Returns An integer representing the horizontal resolution (width) in pixels. + |drawtriangle.png| -.. method:: Display.height() -> int + MicroPython Code Block: - Get the vertical resolution of the display. + .. code-block:: python - Returns An integer representing the vertical resolution (height) in pixels. + Display.drawTriangle(10, 10, 50, 80, 90, 10, 0xFF0000) -.. method:: Display.getRotation() -> int + .. method:: fillTriangle(x0, y0, x1, y1, x2, y2, color) - Get the current rotation of the display. + Draw a filled triangle. - Returns An integer representing the display's rotation: + :param int x0: first vertex x-coordinate (default -1) + :param int y0: first vertex y-coordinate (default -1) + :param int x1: second vertex x-coordinate (default -1) + :param int y1: second vertex y-coordinate (default -1) + :param int x2: third vertex x-coordinate (default -1) + :param int y2: third vertex y-coordinate (default -1) + :param int color: fill color in RGB888 format (default 0) - - ``1``: 0° rotation - - ``2``: 90° rotation - - ``3``: 180° rotation - - ``4``: 270° rotation + UiFlow2 Code Block: -.. method:: Display.getColorDepth() -> int + |filltriangle.png| - Get the color depth of the display. + MicroPython Code Block: - Returns An integer representing the display's color depth in bits. + .. code-block:: python -.. method::Display.getCursor() -> Tuple[int, int] + Display.fillTriangle(10, 10, 50, 80, 90, 10, 0x00FF00) - Get the current cursor position on the display. + .. method:: drawArc(x, y, r0, r1, angle0, angle1, color) - Returns A tuple (x, y) where: + Draw an arc. - - ``x`` is the horizontal position of the cursor. - - ``y`` is the vertical position of the cursor. + :param int x: center x-coordinate (default -1) + :param int y: center y-coordinate (default -1) + :param int r0: first radius (default -1) + :param int r1: second radius (default -1) + :param int angle0: starting angle in degrees (default -1) + :param int angle1: ending angle in degrees (default -1) + :param int color: arc color in RGB888 format (default 0) -.. method:: Display.setRotation(r: int = -1) + UiFlow2 Code Block: - Set the rotation of the display. + |drawarc.png| - The ``r`` parameter only accepts the following values: + MicroPython Code Block: - - ``1``: 0° rotation - - ``2``: 90° rotation - - ``3``: 180° rotation - - ``4``: 270° rotation + .. code-block:: python -.. method:: Display.setColorDepth(bpp: int = 1) + Display.drawArc(50, 50, 20, 30, 0, 180, 0xFF0000) - Set the color depth of the display. + .. method:: fillArc(x, y, r0, r1, angle0, angle1, color) - - ``bpp`` The desired color depth in bits per pixel. + Draw a filled arc. - Notes: For CoreS3 devices, the color depth is fixed at 16 bits, and this method has no effect. + :param int x: center x-coordinate (default -1) + :param int y: center y-coordinate (default -1) + :param int r0: first radius (default -1) + :param int r1: second radius (default -1) + :param int angle0: starting angle in degrees (default -1) + :param int angle1: ending angle in degrees (default -1) + :param int color: fill color in RGB888 format (default 0) -.. method:: Display.setEpdMode(epd_mode) + UiFlow2 Code Block: - Set the EPD mode for the display. + |fillarc.png| - - ``epd_mode`` The desired EPD mode. - - 0: M5.Lcd.EPDMode.EPD_QUALITY - - 1: M5.Lcd.EPDMode.EPD_TEXT - - 2: M5.Lcd.EPDMode.EPD_FAST - - 3: M5.Lcd.EPDMode.EPD_FASTEST + MicroPython Code Block: - Notes: This method is only applicable to devices with EPD (Electronic Paper Display) capabilities. + .. code-block:: python -.. method:: Display.isEPD() -> bool + Display.fillArc(50, 50, 20, 30, 0, 180, 0x00FF00) - Check if the display is an EPD (Electronic Paper Display). + .. method:: drawEllipseArc(x, y, r0x, r1x, r0y, r1y, angle0, angle1, color) - Returns A boolean indicating whether the display is an EPD. + Draw an elliptical arc. -.. method:: Display.setFont(font) + :param int x: center x-coordinate (default -1) + :param int y: center y-coordinate (default -1) + :param int r0x: first horizontal radius (default -1) + :param int r1x: second horizontal radius (default -1) + :param int r0y: first vertical radius (default -1) + :param int r1y: second vertical radius (default -1) + :param int angle0: starting angle in degrees (default -1) + :param int angle1: ending angle in degrees (default 0) + :param int color: arc color in RGB888 format (default 0) - Set the font for the display. + UiFlow2 Code Block: - The ``font`` parameter only accepts the following values: + |drawellipsearc.png| - - M5.Lcd.FONTS.ASCII7 - - M5.Lcd.FONTS.DejaVu9 - - M5.Lcd.FONTS.DejaVu12 - - M5.Lcd.FONTS.DejaVu18 - - M5.Lcd.FONTS.DejaVu24 - - M5.Lcd.FONTS.DejaVu40 - - M5.Lcd.FONTS.DejaVu56 - - M5.Lcd.FONTS.DejaVu72 - - M5.Lcd.FONTS.EFontCN24 - - M5.Lcd.FONTS.EFontJA24 - - M5.Lcd.FONTS.EFontKR24 + MicroPython Code Block: -.. method:: Display.setTextColor(fgcolor: int = 0, bgcolor: int = 0) + .. code-block:: python - Set the text color and background color. + Display.drawEllipseArc(50, 50, 20, 40, 10, 30, 0, 180, 0xFF00FF) - - ``fgcolor`` The text color in RGB888 format. Default is 0 (black). - - ``bgcolor`` The background color in RGB888 format. Default is 0 (black). + .. method:: fillEllipseArc(x, y, r0x, r1x, r0y, r1y, angle0, angle1, color) -.. method:: Display.setTextScroll(scroll: bool = False) + Draw a filled elliptical arc. - Enable or disable text scrolling. + :param int x: center x-coordinate (default -1) + :param int y: center y-coordinate (default -1) + :param int r0x: first horizontal radius (default -1) + :param int r1x: second horizontal radius (default -1) + :param int r0y: first vertical radius (default -1) + :param int r1y: second vertical radius (default -1) + :param int angle0: starting angle in degrees (default -1) + :param int angle1: ending angle in degrees (default 0) + :param int color: fill color in RGB888 format (default 0) - - ``scroll`` Set to True to enable text scrolling, or False to disable it. Default is False.\ + UiFlow2 Code Block: -.. method:: Display.setTextSize(size) + |fillellipsearc.png| - Set the size of the text. + MicroPython Code Block: - - ``size`` The desired text size. + .. code-block:: python -.. method:: Display.setCursor(x: int = 0, y: int = 0) + Display.fillEllipseArc(50, 50, 20, 40, 10, 30, 0, 180, 0x00FFFF) - Set the cursor position. + .. method:: drawQR(text, x, y, w, version) - - ``x`` The horizontal position of the cursor. Default is 0. - - ``y`` The vertical position of the cursor. Default is 0. + Draw a QR code. -.. method:: Display.clear(color: int = 0) + :param str text: QR code content + :param int x: x-coordinate to display (default 0) + :param int y: y-coordinate to display (default 0) + :param int w: QR code width (default 0) + :param int version: QR code version (default 1) - Clear the display with a specific color. + UiFlow2 Code Block: - - ``color`` The fill color in RGB888 format. Default is 0. + |drawqr.png| -.. method:: Display.fillScreen(color: int = 0) + MicroPython Code Block: - Fill the entire screen with a specified color. + .. code-block:: python - - ``color`` The fill color in RGB888 format. Default is 0. + Display.drawQR("Hello", 0, 0, 200) + .. method:: drawPng(img, x, y, maxW, maxH, offX, offY, scaleX, scaleY) -.. method:: Display.drawPixel(x: int = -1, y: int = -1, color: int = 0) + Draw a PNG image. - Draw a single pixel on the screen. + :param str img: image path or data + :param int x: display x-coordinate (default 0) + :param int y: display y-coordinate (default 0) + :param int maxW: max width to draw (default 0) + :param int maxH: max height to draw (default 0) + :param int offX: x-offset in image (default 0) + :param int offY: y-offset in image (default 0) + :param bool scaleX: scale horizontally (default True) + :param bool scaleY: scale vertically (default False) - - ``x`` The horizontal coordinate of the pixel. Default is -1. - - ``y`` The vertical coordinate of the pixel. Default is -1. - - ``color`` The color of the pixel in RGB888 format. Default is 0. + UiFlow2 Code Block: -.. method:: Display.drawCircle(x: int = -1, y: int = -1, r: int = -1, color: int = 0) + |drawpng.png| - Draw an outline of a circle. + MicroPython Code Block: - - ``x`` The x-coordinate of the circle center. Default is -1. - - ``y`` The y-coordinate of the circle center. Default is -1. - - ``r`` The radius of the circle. Default is -1. - - ``color`` The color of the circle in RGB888 format. Default is 0. + .. code-block:: python -.. method:: Display.fillCircle(x: int = -1, y: int = -1, r: int = -1, color: int = 0) + Display.drawPng("res/img/uiflow.png", 0, 0) - Draw a filled circle. + Example: - - ``x`` The x-coordinate of the circle center. Default is -1. - - ``y`` The y-coordinate of the circle center. Default is -1. - - ``r`` The radius of the circle. Default is -1. - - ``color`` The fill color in RGB888 format. Default is 0. + .. code-block:: python -.. method:: Display.drawEllipse(x: int = -1, y: int = -1, rx: int = -1, ry: int = -1, color: int = 0) + Display.drawPng("res/img/uiflow.png", 0, 0) + img = open("res/img/uiflow.png", "b") + img.seek(0) + Display.drawPng(img.read(), 0, 100) + img.close() - Draw an outline of an ellipse. + .. method:: drawJpg(img, x, y, maxW, maxH, offX, offY) - - ``x`` The x-coordinate of the ellipse center. Default is -1. - - ``y`` The y-coordinate of the ellipse center. Default is -1. - - ``rx`` The horizontal radius of the ellipse. Default is -1. - - ``ry`` The vertical radius of the ellipse. Default is -1. - - ``color`` The color of the ellipse in RGB888 format. Default is 0. + Draw a JPG image. -.. method:: Display.fillEllipse(x: int = -1, y: int = -1, rx: int = -1, ry: int = -1, color: int = 0) + :param img: image path or data + :param int x: display x-coordinate (default 0) + :param int y: display y-coordinate (default 0) + :param int maxW: max width to draw (default 0) + :param int maxH: max height to draw (default 0) + :param int offX: x-offset in image (default 0) + :param int offY: y-offset in image (default 0) - Draw a filled ellipse. + UiFlow2 Code Block: - - ``x`` The x-coordinate of the ellipse center. Default is -1. - - ``y`` The y-coordinate of the ellipse center. Default is -1. - - ``rx`` The horizontal radius of the ellipse. Default is -1. - - ``ry`` The vertical radius of the ellipse. Default is -1. - - ``color`` The fill color in RGB888 format. Default is 0. + |drawjpg.png| -.. method:: Display.drawLine(x0: int = -1, y0: int = -1, x1: int = -1, y1: int = -1, color: int = 0) + MicroPython Code Block: - Draw a line. + .. code-block:: python - - ``x0, y0`` Starting point coordinates of the line. Default is -1. - - ``x1, y1`` Ending point coordinates of the line. Default is -1. - - ``color`` Color in RGB888 format. Default is 0. + Display.drawJpg("res/img/uiflow.jpg", 0, 0) -.. method:: Display.drawRect(x: int = -1, y: int = -1, w: int = -1, h: int = -1, color: int = 0) + Example: - Draw a rectangle. + .. code-block:: python - - ``x, y`` Top-left corner coordinates of the rectangle. Default is -1. - - ``w, h`` Width and height of the rectangle. Default is -1. - - ``color`` Color in RGB888 format. Default is 0. + Display.drawJpg("res/img/uiflow.jpg", 0, 0) + img = open("res/img/uiflow.jpg", "b") + img.seek(0) + Display.drawJpg(img.read(), 0, 100) + img.close() -.. method:: Display.fillRect(x: int = -1, y: int = -1, w: int = -1, h: int = -1, color: int = 0) + .. method:: drawBmp(img, x, y, maxW, maxH, offX, offY) - Draw a filled rectangle. + Draw a BMP image. - - ``x, y`` Top-left corner coordinates of the rectangle. Default is -1. - - ``w, h`` Width and height of the rectangle. Default is -1. - - ``color`` Color in RGB888 format. Default is 0. + :param img: image path or data + :param int x: display x-coordinate (default 0) + :param int y: display y-coordinate (default 0) + :param int maxW: max width to draw (default 0) + :param int maxH: max height to draw (default 0) + :param int offX: x-offset in image (default 0) + :param int offY: y-offset in image (default 0) -.. method:: Display.drawRoundRect(x: int = -1, y: int = -1, w: int = -1, h: int = -1, r: int = -1, color: int = 0) + UiFlow2 Code Block: - Draw a rounded rectangle. + |drawbmp.png| - - ``x, y`` Top-left corner coordinates of the rectangle. Default is -1. - - ``w, h`` Width and height of the rectangle. Default is -1. - - ``r`` Radius of the corners. Default is -1. - - ``color`` Color in RGB888 format. Default is 0. + MicroPython Code Block: -.. method:: Display.fillRoundRect(x: int = -1, y: int = -1, w: int = -1, h: int = -1, r: int = -1, color: int = 0) + .. code-block:: python - Draw a filled rounded rectangle. + Display.drawBmp("res/img/uiflow.bmp", 0, 0) - - ``x, y`` Top-left corner coordinates of the rectangle. Default is -1. - - ``w, h`` Width and height of the rectangle. Default is -1. - - ``r`` Radius of the corners. Default is -1. - - ``color`` Color in RGB888 format. Default is 0. + Example: + .. code-block:: python -.. method:: Display.drawTriangle(x0: int = -1, y0: int = -1, x1: int = -1, y1: int = -1, x2: int = -1, y2: int = -1, color: int = 0) + Display.drawBmp("res/img/uiflow.bmp", 0, 0) + img = open("res/img/uiflow.bmp", "b") + img.seek(0) + Display.drawBmp(img.read(), 0, 100) + img.close() - Draw a triangle. + .. method:: drawImage(img, x, y, maxW, maxH, offX, offY) - - ``x0, y0`` Coordinates of the first vertex. Default is -1. - - ``x1, y1`` Coordinates of the second vertex. Default is -1. - - ``x2, y2`` Coordinates of the third vertex. Default is -1. - - ``color`` Color in RGB888 format. Default is 0. + Draw an image. -.. method:: Display.fillTriangle(x0: int = -1, y0: int = -1, x1: int = -1, y1: int = -1, x2: int = -1, y2: int = -1, color: int = 0) + :param img: image path or data + :param int x: display x-coordinate (default 0) + :param int y: display y-coordinate (default 0) + :param int maxW: max width to draw (default 0) + :param int maxH: max height to draw (default 0) + :param int offX: x-offset in image (default 0) + :param int offY: y-offset in image (default 0) - Draw a filled triangle. + UiFlow2 Code Block: - - ``x0, y0`` Coordinates of the first vertex. Default is -1. - - ``x1, y1`` Coordinates of the second vertex. Default is -1. - - ``x2, y2`` Coordinates of the third vertex. Default is -1. - - ``color:`` Color in RGB888 format. Default is 0. + |drawimage.png| -.. method:: Display.drawArc(x: int = -1, y: int = -1, r0: int = -1, r1: int = -1, angle0: int = -1, angle1: int = -1, color: int = 0) + MicroPython Code Block: - Draw an arc. + .. code-block:: python - - ``x, y`` Center coordinates of the arc. Default is -1. - - ``r0`` Inner radius of the arc. Default is -1. - - ``r1`` Outer radius of the arc. Default is -1. - - ``angle0`` Starting angle of the arc (in degrees). Default is -1. - - ``angle1`` Ending angle of the arc (in degrees). Default is -1. - - ``color`` Color in RGB888 format. Default is 0. + img = open("res/img/uiflow.jpg", "b") -.. method:: Display.fillArc(x: int = -1, y: int = -1, r0: int = -1, r1: int = -1, angle0: int = -1, angle1: int = -1, color: int = 0) + Example: - Draw a filled arc. + .. code-block:: python - - ``x, y`` Center coordinates of the arc. Default is -1. - - ``r0`` Inner radius of the arc. Default is -1. - - ``r1`` Outer radius of the arc. Default is -1. - - ``angle0`` Starting angle of the arc (in degrees). Default is -1. - - ``angle1`` Ending angle of the arc (in degrees). Default is -1. - - ``color`` Color in RGB888 format. Default is 0. + img = open("res/img/uiflow.jpg", "b") + img.seek(0) + Display.drawImage(img.read(), 0, 0) + img.close() -.. method:: Display.drawEllipseArc(x: int = -1, y: int = -1, r0x: int = -1, r0y: int = -1, r1x: int = -1, r1y: int = -1, angle0: int = -1, angle1: int = -1, color: int = 0) + .. method:: drawRawBuf(buf, x, y, w, h, len, swap) - Draw an elliptical arc. + Draw an image from raw buffer data. - - ``x, y`` Center coordinates of the elliptical arc. Default is -1. - - ``r0x, r0y`` Radii of the inner ellipse (horizontal and vertical). Default is -1. - - ``r1x, r1y`` Radii of the outer ellipse (horizontal and vertical). Default is -1. - - ``angle0`` Starting angle of the arc (in degrees). Default is -1. - - ``angle1`` Ending angle of the arc (in degrees). Default is 0. - - ``color`` Color in RGB888 format. Default is 0. + :param buf: image buffer + :param int x: display x-coordinate (default 0) + :param int y: display y-coordinate (default 0) + :param int w: image width (default 0) + :param int h: image height (default 0) + :param int len: length of image data (default 0) + :param bool swap: inverted display (default False) -.. method:: Display.fillEllipseArc(x: int = -1, y: int = -1, r0x: int = -1, r0y: int = -1, r1x: int = -1, r1y: int = -1, angle0: int = -1, angle1: int = -1, color: int = 0) + UiFlow2 Code Block: - Draw a filled elliptical arc. + |drawrawbuf.png| - - ``x, y`` Center coordinates of the elliptical arc. Default is -1. - - ``r0x, r0y`` Radii of the inner ellipse (horizontal and vertical). Default is -1. - - ``r1x, r1y`` Radii of the outer ellipse (horizontal and vertical). Default is -1. - - ``angle0`` Starting angle of the arc (in degrees). Default is -1. - - ``angle1`` Ending angle of the arc (in degrees). Default is -1. - - ``color:`` Color in RGB888 format. Default is 0. + MicroPython Code Block: -.. method:: Display.drawQR(text: str = None, x: int = 0, y: int = 0, w: int = 0, version: int = 1) + .. code-block:: python - Draw a QR code. + Display.drawRawBuf(raw_buf, 0, 0, 100, 100, len(raw_buf), swap=False) - - ``text`` QR code content. - - ``x, y`` Starting coordinates for displaying the QR code. - - ``w:`` Width of the QR code. Default is 0. - - ``version`` QR code version. Default is 1. + Example: - **Example**: + .. code-block:: python - Generate and display a QR code with the content "hello": + width, height = 40, 30 + green565 = 0x07E0 + raw_buf = bytearray(width * height * 2) + for i in range(width * height): + raw_buf[2*i] = (green565 >> 8) & 0xFF + raw_buf[2*i+1] = green565 & 0xFF + Display.drawRawBuf(raw_buf, 100, 100, width, height, len(raw_buf), swap=False) - .. code-block:: python + .. method:: print(text, color) - Display.drawQR("Hello", 0, 0, 200) + Display a string (no formatting support). -.. method:: Display.drawPng(img: str, x: int = 0, y: int = 0, maxW: int = 0, maxH: int = 0, offX: int = 0, offY: int = 0, scaleX=True, scaleY=False) + :param str text: text to display + :param int color: color in RGB888 format (default 0) - Draw a PNG image. + UiFlow2 Code Block: - - ``img`` Image file path or opened image data. - - ``x, y`` Starting coordinates on the display screen. - - ``maxW, maxH`` Width and height to be drawn. Draws the full image if ≤0. - - ``offX, offY`` Offset in the image to start from. - - ``scaleX, scaleY`` Whether to scale the image horizontally or vertically. + |print.png| - **Examples**: + MicroPython Code Block: - Display a PNG image from a specified path: + .. code-block:: python - .. code-block:: python + Display.print("Hello World", color=0xFF0000) - Display.drawPng("res/img/uiflow.png", 0, 0) + .. method:: printf(text) - Display a PNG image from read data: + Display a formatted string. - .. code-block:: python + :param str text: text to display with formatting - img = open("res/img/uiflow.png", "b") - img.seek(0) - Display.drawPng(img.read(), 0, 100) - img.close() + UiFlow2 Code Block: -.. method:: Display.drawJpg(img, x: int = 0, y: int = 0, maxW: int = 0, maxH: int = 0, offX: int = 0, offY: int = 0) + |printf.png| - Draw a JPG image. + MicroPython Code Block: - - ``img`` Image file path or opened image data. - - ``x, y`` Starting coordinates on the display screen. - - ``maxW, maxH`` Width and height to be drawn. Draws the full image if ≤0. - - ``offX, offY`` Offset in the image to start from. + .. code-block:: python -.. method:: Display.drawBmp(img: str, x: int = 0, y: int = 0, maxW: int = 0, maxH: int = 0, offX: int = 0, offY: int = 0) + Display.printf("Value: %d" % 100) - Draw a BMP image. + .. method:: newCanvas(w, h, bpp, psram) - - ``img`` Image file path or opened image data. - - ``x, y`` Starting coordinates on the display screen. - - ``maxW, maxH`` Width and height to be drawn. Draws the full image if ≤0. - - ``offX, offY`` Offset in the image to start from. + Create a canvas. -.. method:: Display.drawImage(img: str, x: int = 0, y: int = 0, maxW: int = 0, maxH: int = 0, offX: int = 0, offY: int = 0) + :param int w: canvas width + :param int h: canvas height + :param int bpp: color depth (default -1) + :param bool psram: use PSRAM (default False) + :returns: created canvas object - Draw an image. + UiFlow2 Code Block: - - ``img`` Image file path or opened image data. - - ``x, y`` Starting coordinates on the display screen. - - ``maxW, maxH`` Width and height to be drawn. Draws the full image if ≤0. - - ``offX, offY`` Offset in the image to start from. + |newcanvas.png| - **Example**: + MicroPython Code Block: - Draw an image from the buffer: + .. code-block:: python - .. code-block:: python + w1 = Display.newCanvas(w=100, h=100, bpp=16) - img = open(img_path) - img.seek(0) - drawImage(img.read()) + Example: -.. method:: Display.drawRawBuf(buf, x: int = 0, y: int = 0, w: int = 0, h: int = 0, len: int = 0, swap: bool = False) + .. code-block:: python - Draw an image from raw buffer data. + w1 = Display.newCanvas(w=100, h=100, bpp=16) + w1.drawImage("res/img/uiflow.jpg", 80, 0) + w1.push(30, 0) - - ``buf`` Image buffer. - - ``x, y`` Starting coordinates on the display screen. - - ``w, h`` Width and height of the image. - - ``len`` Length of the image data. - - ``swap`` Whether to enable inverted display. + .. method:: startWrite() -.. method:: Display.print(text: str = None, color: int = 0) + Start writing to the display. - Display a string (no formatting support). + UiFlow2 Code Block: - - ``text`` Text to display. - - ``color`` Color in RGB888 format. Default is 0. + |startwrite.png| -.. method:: Display.printf(text: str = None) + MicroPython Code Block: - Display a formatted string. + .. code-block:: python - - ``text`` Text to display with formatting. + Display.startWrite() -.. method:: Display.newCanvas(w: int = 0, h: int = 0, bpp: int = -1, psram: bool = False) + Example: - Create a canvas. + .. code-block:: python - - ``w, h`` Width and height of the canvas. - - ``bpp`` Color depth. Default is -1. - - ``psram`` Whether to use PSRAM. Default is False. + Display.startWrite() + Display.drawPixel(10, 10, 0xFF0000) + Display.endWrite() - Returns Created canvas object. + .. method:: endWrite() - **Example**: + End writing to the display. - .. code-block:: python + UiFlow2 Code Block: - w1 = Display.newCanvas(w=100, h=100, bpp=16) - w1.drawImage("res/img/uiflow.jpg", 80, 0) - w1.push(30, 0) + |endwrite.png| -.. method:: Display.startWrite() + MicroPython Code Block: - Start writing to the display. + .. code-block:: python -.. method:: Display.endWrite() + Display.endWrite() - End writing to the display. diff --git a/docs/en/m5ui/index.rst b/docs/en/m5ui/index.rst index b575b573..d3bc4561 100644 --- a/docs/en/m5ui/index.rst +++ b/docs/en/m5ui/index.rst @@ -8,6 +8,32 @@ M5UI is a UI library based on LVGL v9.3. It provides a set of widgets and functi It has been adapted for M5Stack devices and you only need to call ``m5ui.init()`` to start using it. +M5 Series Display Libraries +--------------------------- + +1. Display +^^^^^^^^^^^ +- A low-level graphics library providing basic screen drawing, text, lines, and color management. +- Can be used independently, suitable for scenarios that only require drawing graphics or text. + +2. Widgets +^^^^^^^^^^^ +- A basic UI widget library providing labels, image displays, and other UI controls. +- Built on top of M5GFX. +- Suitable for simple interactive UI elements. + +3. M5UI +^^^^^^^^ +- A high-level UI framework based on LVGL. +- Provides page management, multi-widget layouts, and unified event handling. + +Usage Tips +^^^^^^^^^^ +- ⚠️ Do not mix M5GFX, M5Widgets, and M5UI simultaneously, as it may cause rendering issues or event conflicts. +- For graphics-only drawing → use M5GFX. +- For simple interactive widgets → use M5Widgets. +- For multi-page UI → use M5UI. + Functions --------- diff --git a/docs/en/refs/hardware.display.ref b/docs/en/refs/hardware.display.ref new file mode 100644 index 00000000..5f7c758c --- /dev/null +++ b/docs/en/refs/hardware.display.ref @@ -0,0 +1,54 @@ +.. |cores3_draw_test_example.png| image:: https://static-cdn.m5stack.com/mpy_docs/lcd/example.png + +.. |width.png| image:: https://static-cdn.m5stack.com/mpy_docs/lcd/width.png +.. |height.png| image:: https://static-cdn.m5stack.com/mpy_docs/lcd/height.png +.. |getRotation.png| image:: https://static-cdn.m5stack.com/mpy_docs/lcd/getRotation.png +.. |getColorDepth.png| image:: https://static-cdn.m5stack.com/mpy_docs/lcd/getColorDepth.png +.. |getCursor.png| image:: https://static-cdn.m5stack.com/mpy_docs/lcd/getCursor.png +.. |setRotation.png| image:: https://static-cdn.m5stack.com/mpy_docs/lcd/setRotation.png +.. |setColorDepth.png| image:: https://static-cdn.m5stack.com/mpy_docs/lcd/setColorDepth.png +.. |setEpdMode.png| image:: https://static-cdn.m5stack.com/mpy_docs/lcd/setEpdMode.png +.. |isEPD.png| image:: https://static-cdn.m5stack.com/mpy_docs/lcd/isEPD.png +.. |setFont.png| image:: https://static-cdn.m5stack.com/mpy_docs/lcd/setFont.png +.. |setTextColor.png| image:: https://static-cdn.m5stack.com/mpy_docs/lcd/setTextColor.png +.. |setTextScroll.png| image:: https://static-cdn.m5stack.com/mpy_docs/lcd/setTextScroll.png +.. |setTextSize.png| image:: https://static-cdn.m5stack.com/mpy_docs/lcd/setTextSize.png +.. |setCursor.png| image:: https://static-cdn.m5stack.com/mpy_docs/lcd/setCursor.png +.. |clear.png| image:: https://static-cdn.m5stack.com/mpy_docs/lcd/clear.png +.. |fillScreen.png| image:: https://static-cdn.m5stack.com/mpy_docs/lcd/fillScreen.png +.. |drawPixel.png| image:: https://static-cdn.m5stack.com/mpy_docs/lcd/drawPixel.png +.. |drawCircle.png| image:: https://static-cdn.m5stack.com/mpy_docs/lcd/drawCircle.png +.. |fillCircle.png| image:: https://static-cdn.m5stack.com/mpy_docs/lcd/fillCircle.png +.. |drawEllipse.png| image:: https://static-cdn.m5stack.com/mpy_docs/lcd/drawEllipse.png +.. |fillEllipse.png| image:: https://static-cdn.m5stack.com/mpy_docs/lcd/fillEllipse.png +.. |drawLine.png| image:: https://static-cdn.m5stack.com/mpy_docs/lcd/drawLine.png +.. |drawRect.png| image:: https://static-cdn.m5stack.com/mpy_docs/lcd/drawRect.png +.. |fillRect.png| image:: https://static-cdn.m5stack.com/mpy_docs/lcd/fillRect.png +.. |drawRoundRect.png| image:: https://static-cdn.m5stack.com/mpy_docs/lcd/drawRoundRect.png +.. |fillRoundRect.png| image:: https://static-cdn.m5stack.com/mpy_docs/lcd/fillRoundRect.png +.. |drawTriangle.png| image:: https://static-cdn.m5stack.com/mpy_docs/lcd/drawTriangle.png +.. |fillTriangle.png| image:: https://static-cdn.m5stack.com/mpy_docs/lcd/fillTriangle.png +.. |drawArc.png| image:: https://static-cdn.m5stack.com/mpy_docs/lcd/drawArc.png +.. |fillArc.png| image:: https://static-cdn.m5stack.com/mpy_docs/lcd/fillArc.png +.. |drawEllipseArc.png| image:: https://static-cdn.m5stack.com/mpy_docs/lcd/drawEllipseArc.png +.. |fillEllipseArc.png| image:: https://static-cdn.m5stack.com/mpy_docs/lcd/fillEllipseArc.png +.. |drawQR.png| image:: https://static-cdn.m5stack.com/mpy_docs/lcd/drawQR.png +.. |drawPng.png| image:: https://static-cdn.m5stack.com/mpy_docs/lcd/drawPng.png +.. |drawJpg.png| image:: https://static-cdn.m5stack.com/mpy_docs/lcd/drawJpg.png +.. |drawBmp.png| image:: https://static-cdn.m5stack.com/mpy_docs/lcd/drawBmp.png +.. |drawImage.png| image:: https://static-cdn.m5stack.com/mpy_docs/lcd/drawImage.png +.. |drawRawBuf.png| image:: https://static-cdn.m5stack.com/mpy_docs/lcd/drawRawBuf.png +.. |print.png| image:: https://static-cdn.m5stack.com/mpy_docs/lcd/print.png +.. |printf.png| image:: https://static-cdn.m5stack.com/mpy_docs/lcd/printf.png +.. |newCanvas.png| image:: https://static-cdn.m5stack.com/mpy_docs/lcd/newCanvas.png +.. |startWrite.png| image:: https://static-cdn.m5stack.com/mpy_docs/lcd/startWrite.png +.. |endWrite.png| image:: https://static-cdn.m5stack.com/mpy_docs/lcd/endWrite.png + +.. |cores3_draw_test_example.m5f2| raw:: html + + + cores3_draw_test_example.m5f2 + diff --git a/docs/en/widgets/index.rst b/docs/en/widgets/index.rst index cc271046..b5b3395a 100644 --- a/docs/en/widgets/index.rst +++ b/docs/en/widgets/index.rst @@ -6,6 +6,32 @@ .. include:: ../refs/widgets.ref +M5 Series Display Libraries +--------------------------- + +1. Display +^^^^^^^^^^^ +- A low-level graphics library providing basic screen drawing, text, lines, and color management. +- Can be used independently, suitable for scenarios that only require drawing graphics or text. + +2. M5Widgets +^^^^^^^^^^^^^ +- A basic UI widget library providing labels, image displays, and other UI controls. +- Built on top of M5GFX. +- Suitable for simple interactive UI elements. + +3. M5UI +^^^^^^^^ +- A high-level UI framework based on LVGL. +- Provides page management, multi-widget layouts, and unified event handling. + +Usage Tips +^^^^^^^^^^ +- ⚠️ Do not mix M5GFX, M5Widgets, and M5UI simultaneously, as it may cause rendering issues or event conflicts. +- For graphics-only drawing → use M5GFX. +- For simple interactive widgets → use M5Widgets. +- For multi-page UI → use M5UI. + Micropython Example: .. literalinclude:: ../../../examples/widgets/screen/cores3_widgets_example.py diff --git a/docs/locales/zh_CN/LC_MESSAGES/hardware/display.po b/docs/locales/zh_CN/LC_MESSAGES/hardware/display.po index f163a778..24815f28 100644 --- a/docs/locales/zh_CN/LC_MESSAGES/hardware/display.po +++ b/docs/locales/zh_CN/LC_MESSAGES/hardware/display.po @@ -9,7 +9,7 @@ msgid "" msgstr "" "Project-Id-Version: UIFlow2 Programming Guide \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-05-15 15:04+0800\n" +"POT-Creation-Date: 2025-09-05 01:05+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language: zh_CN\n" @@ -20,659 +20,1251 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.16.0\n" -#: ../../en/hardware/display.rst:4 1144f0b75e6d44dd9182da08d5c66242 +#: ../../en/hardware/display.rst:4 320f6c3301e9466383d70854c8571565 msgid "Display" msgstr "" -#: ../../en/hardware/display.rst:6 292206cb76dd4b86b47b585788b38497 +#: ../../en/hardware/display.rst:8 aabe2a468551495390ec3ad98851669d msgid "A lcd display library" +msgstr "LCD 屏幕显示库" + +#: ../../en/hardware/display.rst:15 1a86dcd18afc4dacb59ac2a89b6ec11d +msgid "M5 Series Display Libraries" +msgstr "M5 系列显示库说明" + +#: ../../en/hardware/display.rst:18 fb41e2d8095540aab10449315e84bf84 +msgid "1. Display" msgstr "" -#: ../../en/hardware/display.rst:13 4f7e88f934584be1b92aa6bca6bf86a8 -msgid "Micropython Example" +#: ../../en/hardware/display.rst:19 8fd8b80ad6234027968326fa0a6162eb +msgid "" +"A low-level graphics library providing basic screen drawing, text, lines," +" and color management." +msgstr "底层图形库,提供屏幕绘制、文字、线条、颜色管理等基础功能。" + +#: ../../en/hardware/display.rst:20 446ad13c87924839bbc55db8cc391f30 +msgid "" +"Can be used independently, suitable for scenarios that only require " +"drawing graphics or text." +msgstr "可独立使用,适合只需要绘制图形或文字的场景。" + +#: ../../en/hardware/display.rst:23 7ad8885c7b314f1db803f9b16d89b9fa +msgid "2. M5Widgets" msgstr "" -#: ../../en/hardware/display.rst:16 3e1179ec25b74f0bb50c6aa687c1a261 +#: ../../en/hardware/display.rst:24 7a2262fd362b4be989d1cfcfe658d13e +msgid "" +"A basic UI widget library providing labels, image displays, and other UI " +"controls." +msgstr "基础控件库,提供标签、图片显示等 UI 控件。" + +#: ../../en/hardware/display.rst:25 41d5f1e230c4464b9c1b8828ccdd9a6d +msgid "Built on top of M5GFX." +msgstr "底层依赖 M5GFX。" + +#: ../../en/hardware/display.rst:26 5756086635fc4c09b12f20d0ce70ca3b +msgid "Suitable for simple interactive UI elements." +msgstr "适合需要简单交互控件的界面。" + +#: ../../en/hardware/display.rst:29 6a5109fa6f574c9fb8ae416cccbd091e +msgid "3. M5UI" +msgstr "" + +#: ../../en/hardware/display.rst:30 013c8177e366468c8c1f16f1a9e1c64e +msgid "A high-level UI framework based on LVGL." +msgstr "高层 UI 框架,基于 LVGL 封装。" + +#: ../../en/hardware/display.rst:31 bfd5ae3cdc5c4c63b0d9c0907e156991 +msgid "" +"Provides page management, multi-widget layouts, and unified event " +"handling." +msgstr "提供页面管理、多控件布局和统一事件处理。" + +#: ../../en/hardware/display.rst:34 422d1949d6024346b79e7383c7e26a97 +msgid "Usage Tips" +msgstr "使用提示" + +#: ../../en/hardware/display.rst:35 b7be0027f3674840a758ae96702630bf +msgid "" +"⚠️ Do not mix M5GFX, M5Widgets, and M5UI simultaneously, as it may cause " +"rendering issues or event conflicts." +msgstr "⚠️ 不建议同时混用 M5GFX、M5Widgets、M5UI,可能导致渲染异常或事件冲突。" + +#: ../../en/hardware/display.rst:36 a0e0aeba0e3d450babad162463a5d61b +msgid "For graphics-only drawing → use M5GFX." +msgstr "单独绘图 → 使用 M5GFX。" + +#: ../../en/hardware/display.rst:37 bf64cb1ffbab41beaf42a751c12bc5be +msgid "For simple interactive widgets → use M5Widgets." +msgstr "简单控件交互 → 使用 M5Widgets。" + +#: ../../en/hardware/display.rst:38 b7de8274cfed4466bba03c8384c4014a +msgid "For multi-page UI → use M5UI." +msgstr "多页面 UI → 使用 M5UI。" + +#: ../../en/hardware/display.rst:42 87701e9fc234487d886be4719762eee1 +msgid "UiFlow2 Example" +msgstr "UiFlow2 应用示例" + +#: ../../en/hardware/display.rst:45 ../../en/hardware/display.rst:63 +#: e779ff613d3e4a45a7a282bec77a5019 msgid "draw test" +msgstr "绘图测试" + +#: ../../en/hardware/display.rst:47 926664d6d0ae43c0bdf8f18b62e91521 +msgid "Open the |cores3_draw_test_example.m5f2| project in UiFlow2." +msgstr "在 UiFlow2 上打开 |cores3_draw_test_example.m5f2| 项目。" + +#: ../../en/hardware/display.rst:49 ../../en/hardware/display.rst:65 +#: 17c3cfc65fa34f1bb9a1ad07809009d4 783259c6ecba480fbbb1916d462ca372 +msgid "" +"This example demonstrates basic drawing functions of Display, including " +"text, images, QR code, and various shapes." +msgstr "本示例演示了 M5.Lcd 的基本绘图功能,包括文字、图片、二维码以及各种图形。" + +#: ../../en/hardware/display.rst:51 ../../en/hardware/display.rst:89 +#: ../../en/hardware/display.rst:106 ../../en/hardware/display.rst:130 +#: ../../en/hardware/display.rst:147 ../../en/hardware/display.rst:164 +#: ../../en/hardware/display.rst:184 ../../en/hardware/display.rst:202 +#: ../../en/hardware/display.rst:224 ../../en/hardware/display.rst:241 +#: ../../en/hardware/display.rst:269 ../../en/hardware/display.rst:286 +#: ../../en/hardware/display.rst:302 ../../en/hardware/display.rst:318 +#: ../../en/hardware/display.rst:335 ../../en/hardware/display.rst:351 +#: ../../en/hardware/display.rst:367 ../../en/hardware/display.rst:385 +#: ../../en/hardware/display.rst:404 ../../en/hardware/display.rst:423 +#: ../../en/hardware/display.rst:443 ../../en/hardware/display.rst:463 +#: ../../en/hardware/display.rst:483 ../../en/hardware/display.rst:503 +#: ../../en/hardware/display.rst:523 ../../en/hardware/display.rst:544 +#: ../../en/hardware/display.rst:565 ../../en/hardware/display.rst:587 +#: ../../en/hardware/display.rst:609 ../../en/hardware/display.rst:631 +#: ../../en/hardware/display.rst:653 ../../en/hardware/display.rst:677 +#: ../../en/hardware/display.rst:701 ../../en/hardware/display.rst:721 +#: ../../en/hardware/display.rst:745 ../../en/hardware/display.rst:777 +#: ../../en/hardware/display.rst:809 ../../en/hardware/display.rst:841 +#: ../../en/hardware/display.rst:872 ../../en/hardware/display.rst:901 +#: ../../en/hardware/display.rst:917 ../../en/hardware/display.rst:937 +#: ../../en/hardware/display.rst:959 ../../en/hardware/display.rst:981 +#: 2db3ac00d78a4612a3d0b6edbc9a6dda 7088c3a89b214e2e8fcf8d547fdc070b +#: 7c44bb3d09eb4d8db38abdcbdf52a4de 87206c7138a1463a87adbca44e529a2b +#: d1043f0316d042258c38417a28fd3b85 +msgid "UiFlow2 Code Block:" +msgstr "UiFlow2 代码块:" + +#: ../../en/hardware/display.rst:53 aa8c4e6c72924263a5bbbd63fa5c2613 +msgid "|cores3_draw_test_example.png|" +msgstr "" + +#: ../../en/refs/hardware.display.ref:1 ff2aa4c6570045e5897602c48e95fd31 +msgid "cores3_draw_test_example.png" +msgstr "" + +#: ../../en/hardware/display.rst:55 ../../en/hardware/display.rst:73 +#: 20e517109db64f80af0066eb150fed42 +msgid "Example output:" +msgstr "示例输出:" + +#: ../../en/hardware/display.rst:57 ../../en/hardware/display.rst:75 +#: 079ef8c8e5d6425b8a1716f42200ad8e +msgid "None" +msgstr "无" + +#: ../../en/hardware/display.rst:60 87701e9fc234487d886be4719762eee1 +msgid "MicroPython Example" +msgstr "MicroPython 应用示例" + +#: ../../en/hardware/display.rst:67 ../../en/hardware/display.rst:93 +#: ../../en/hardware/display.rst:110 ../../en/hardware/display.rst:134 +#: ../../en/hardware/display.rst:151 ../../en/hardware/display.rst:168 +#: ../../en/hardware/display.rst:188 ../../en/hardware/display.rst:206 +#: ../../en/hardware/display.rst:228 ../../en/hardware/display.rst:245 +#: ../../en/hardware/display.rst:273 ../../en/hardware/display.rst:290 +#: ../../en/hardware/display.rst:306 ../../en/hardware/display.rst:322 +#: ../../en/hardware/display.rst:339 ../../en/hardware/display.rst:355 +#: ../../en/hardware/display.rst:371 ../../en/hardware/display.rst:389 +#: ../../en/hardware/display.rst:408 ../../en/hardware/display.rst:427 +#: ../../en/hardware/display.rst:447 ../../en/hardware/display.rst:467 +#: ../../en/hardware/display.rst:487 ../../en/hardware/display.rst:507 +#: ../../en/hardware/display.rst:527 ../../en/hardware/display.rst:548 +#: ../../en/hardware/display.rst:569 ../../en/hardware/display.rst:591 +#: ../../en/hardware/display.rst:613 ../../en/hardware/display.rst:635 +#: ../../en/hardware/display.rst:657 ../../en/hardware/display.rst:681 +#: ../../en/hardware/display.rst:705 ../../en/hardware/display.rst:725 +#: ../../en/hardware/display.rst:749 ../../en/hardware/display.rst:781 +#: ../../en/hardware/display.rst:813 ../../en/hardware/display.rst:845 +#: ../../en/hardware/display.rst:876 ../../en/hardware/display.rst:905 +#: ../../en/hardware/display.rst:921 ../../en/hardware/display.rst:941 +#: ../../en/hardware/display.rst:963 ../../en/hardware/display.rst:985 +#: 87701e9fc234487d886be4719762eee1 +msgid "MicroPython Code Block:" +msgstr "MicroPython 代码块:" + +#: ../../en/hardware/display.rst:78 0feedf81b67745a3b05baa06d9f93465 +msgid "**API**" +msgstr "API 应用" + +#: ../../en/hardware/display.rst:84 81bda3ba5be04d8b9a406d69d15457a7 +msgid "Get the horizontal resolution of the display." +msgstr "获取显示屏水平分辨率。" + +#: ../../en/hardware/display.rst 273e68e983b2430188619faf645c5b84 +msgid "returns width" msgstr "" -#: ../../en/hardware/display.rst:68 ed7db7582ed24d7f8e4b8c9109f4fc45 -msgid "Functions" +#: ../../en/hardware/display.rst:86 81bda3ba5be04d8b9a406d69d15457a7 +msgid "horizontal resolution in pixels." +msgstr "水平分辨率。" + +#: ../../en/hardware/display.rst be2900f665784c6b93e4d3e60b8615fc +msgid "return type" msgstr "" -#: ../../en/hardware/display.rst:72 5d4da6f86dc34c5f99e324fb82bfbc60 -msgid "Get the horizontal resolution of the display." +#: ../../en/hardware/display.rst:87 ../../en/hardware/display.rst:104 +#: ../../en/hardware/display.rst:121 ../../en/hardware/display.rst:145 +#: 624d9819e31f4d96ba1e2362e635b8ea +msgid "int" msgstr "" -#: ../../en/hardware/display.rst:74 964bc36b5e8843ec8e6a9f1469b684da -msgid "" -"Returns An integer representing the horizontal resolution (width) in " -"pixels." +#: ../../en/hardware/display.rst:91 5ad1811bcbb74418a843bcf8fc8b804b +msgid "|width.png|" +msgstr "" + +#: ../../en/refs/hardware.display.ref:3 8a9bdad16ee944b5961e110da941af16 +msgid "width.png" msgstr "" -#: ../../en/hardware/display.rst:78 adccf65031e44f1292db39cb10e98f04 +#: ../../en/hardware/display.rst:101 888ecf06b9a24d7ea6551a01856926d7 msgid "Get the vertical resolution of the display." +msgstr "获取显示屏垂直分辨率。" + +#: ../../en/hardware/display.rst 365526b4c4ac48999c516edb1da09249 +msgid "returns height" msgstr "" -#: ../../en/hardware/display.rst:80 4a8b307f94b9409c8dbd3f5997adc881 -msgid "" -"Returns An integer representing the vertical resolution (height) in " -"pixels." +#: ../../en/hardware/display.rst:103 888ecf06b9a24d7ea6551a01856926d7 +msgid "vertical resolution in pixels." +msgstr "垂直分辨率。" + +#: ../../en/hardware/display.rst:108 6c1193c8148947fabf973c3c2f2664d0 +msgid "|height.png|" +msgstr "" + +#: ../../en/refs/hardware.display.ref:4 2323bca87aec4c44a324e09925bc7265 +msgid "height.png" msgstr "" -#: ../../en/hardware/display.rst:84 0268c3e6c54547ac91b0b9fc57ac8b58 +#: ../../en/hardware/display.rst:118 05a69311881a483cbcaaf6c0b5ff04eb msgid "Get the current rotation of the display." +msgstr "获取显示屏旋转方向。" + +#: ../../en/hardware/display.rst 771e82b8e2d241c4933ef50fd38b43a8 +msgid "returns rotation" +msgstr "" + +#: ../../en/hardware/display.rst:120 23f81682ec0c4b7fba344f4f9db9c6d3 +msgid "display rotation value." +msgstr "旋转方向值" + +#: ../../en/hardware/display.rst:123 0013573cae9f47b6b7626604356231a0 +msgid "Rotation values:" +msgstr "" + +#: ../../en/hardware/display.rst:125 771e82b8e2d241c4933ef50fd38b43a8 +msgid "1: 0° rotation" msgstr "" -#: ../../en/hardware/display.rst:86 f9866b7669114b0abb7ff77d4f0155fe -msgid "Returns An integer representing the display's rotation:" +#: ../../en/hardware/display.rst:126 15993cd5eba84629aad32fb38b93cc54 +msgid "2: 90° rotation" msgstr "" -#: ../../en/hardware/display.rst:88 ../../en/hardware/display.rst:114 -#: 33efa20d8dac4b28b612bec7fb31efed 3dd7de9babf541a38a01b18b33830b98 -msgid "``1``: 0° rotation" +#: ../../en/hardware/display.rst:127 d5d824077e214b10a49b02cd179af652 +msgid "3: 180° rotation" msgstr "" -#: ../../en/hardware/display.rst:89 ../../en/hardware/display.rst:115 -#: 3f16b99bfc85490986676b259f65714f 66d53eee62a34a9db1f037123a4e0f05 -msgid "``2``: 90° rotation" +#: ../../en/hardware/display.rst:128 01ce3e006be94a54a1c44825fca065e4 +msgid "4: 270° rotation" msgstr "" -#: ../../en/hardware/display.rst:90 ../../en/hardware/display.rst:116 -#: 0e8f3509025f474e897895a034724b2d fb3f6b1b88d8480e8a7b2a5fe196e4b9 -msgid "``3``: 180° rotation" +#: ../../en/hardware/display.rst:132 0f8d49506429412c844320f23f5e823f +msgid "|getrotation.png|" msgstr "" -#: ../../en/hardware/display.rst:91 ../../en/hardware/display.rst:117 -#: 70b952cf1148452bbc4d2303bff4dff9 afe0828ae5014383be6b479330e42c8e -msgid "``4``: 270° rotation" +#: ../../en/refs/hardware.display.ref:5 3314fcb26d8540c9b697ac75a3be4851 +msgid "getRotation.png" msgstr "" -#: ../../en/hardware/display.rst:95 faa759de24dd4eb7ba0f1a8add37d354 +#: ../../en/hardware/display.rst:142 8df5a94dd4b6498287f400ccbc7b73e2 msgid "Get the color depth of the display." +msgstr "获取显示屏颜色深度。" + +#: ../../en/hardware/display.rst a0c974f7947b45d6b71ac8c13b6b2de3 +msgid "returns depth" msgstr "" -#: ../../en/hardware/display.rst:97 3e12f142582c4b9eadebdd86955535f4 -msgid "Returns An integer representing the display's color depth in bits." +#: ../../en/hardware/display.rst:144 6abe3bb09711432da3065b686288fa07 +msgid "color depth in bits per pixel." +msgstr "以每像素位数表示的颜色深度。" + +#: ../../en/hardware/display.rst:149 402a3bed4cec4dd2b9708fba84332162 +msgid "|getcolordepth.png|" msgstr "" -#: ../../en/hardware/display.rst:110 2029e6fa89cf4097ab82a722ee6309c9 -msgid "Set the rotation of the display." +#: ../../en/refs/hardware.display.ref:6 79a864ea866a4254823b47d08126f9aa +msgid "getColorDepth.png" msgstr "" -#: ../../en/hardware/display.rst:112 4ffd7df483ff48e7899cfc2756b1a52c -msgid "The ``r`` parameter only accepts the following values:" +#: ../../en/hardware/display.rst:159 05a69311881a483cbcaaf6c0b5ff04eb +msgid "Get the current cursor position on the display." +msgstr "获取显示屏绘图光标位置。" + +#: ../../en/hardware/display.rst f11a0bf64ba54834b5b31f47664620b0 +msgid "returns pos" msgstr "" -#: ../../en/hardware/display.rst:121 84fa0e39d9e742158d5dd2c2889a44ea -msgid "Set the color depth of the display." +#: ../../en/hardware/display.rst:161 4b046990b80548eb9bca421c86c3902d +msgid "tuple (x, y) of cursor position." +msgstr "tuple (x, y) 光标位置。" + +#: ../../en/hardware/display.rst:162 f1c59d3bf7ba484ab2d45a05219a0faa +msgid "tuple" msgstr "" -#: ../../en/hardware/display.rst:123 d8544534a76a4bd78c9f243ba430c6d6 -msgid "``bpp`` The desired color depth in bits per pixel." +#: ../../en/hardware/display.rst:166 ec314d03032748e284d57a447175d6e8 +msgid "|getcursor.png|" msgstr "" -#: ../../en/hardware/display.rst:125 187162d692de4e54a8dfcbdb1f2eec9c -msgid "" -"Notes: For CoreS3 devices, the color depth is fixed at 16 bits, and this " -"method has no effect." +#: ../../en/refs/hardware.display.ref:7 9b4dd130a8b14960947ee43d2551d264 +msgid "getCursor.png" msgstr "" -#: ../../en/hardware/display.rst:129 0f81544fd03048eaa0472b7ae184a76b -msgid "Set the EPD mode for the display." +#: ../../en/hardware/display.rst:176 41a8dfba301d429ab267d4b5e83cfd74 +msgid "Set the rotation of the display." +msgstr "设置显示屏旋转。" + +#: ../../en/hardware/display.rst 02a89f80d63d42b18bb6329895fda7e7 +#: 2faefb33c24b4ba0b8846e175af4b4cb 40c6d6f79ea04f8787149117c755b08f +#: f0385575a7af469c828d47fbb3b92b06 fa9970ae3d154193aacb5ead92b195d0 +msgid "Parameters" msgstr "" -#: ../../en/hardware/display.rst:135 a8b963a4b4664b5397a7747f5a050ea0 -msgid "``epd_mode`` The desired EPD mode." +#: ../../en/hardware/display.rst:178 59dd68a126ba4c3e8572a928f68dd8f2 +msgid "" +"rotation value (1~4) - 1: 0° rotation - 2: 90° rotation - 3: 180° " +"rotation - 4: 270° rotation" msgstr "" -#: ../../en/hardware/display.rst:132 ba6ca73de649468c80bd836c4b046fa9 -msgid "0: M5.Lcd.EPDMode.EPD_QUALITY" +#: ../../en/hardware/display.rst:186 b9b2135813884b6b98a9439c11323e44 +msgid "|setrotation.png|" msgstr "" -#: ../../en/hardware/display.rst:133 0deec3adbca749ebb35279fc63ab679e -msgid "1: M5.Lcd.EPDMode.EPD_TEXT" +#: ../../en/refs/hardware.display.ref:8 5e5dfb633e2c4772b114f72f0ceac5eb +msgid "setRotation.png" msgstr "" -#: ../../en/hardware/display.rst:134 e696e419d89c470e9357e1dc02c938bf -msgid "2: M5.Lcd.EPDMode.EPD_FAST" +#: ../../en/hardware/display.rst:196 b54b5f5a5dcf48488ce48cd2ffd7acb9 +msgid "Set the color depth of the display." +msgstr "设置显示屏的颜色深度。" + +#: ../../en/hardware/display.rst:198 6abe3bb09711432da3065b686288fa07 +msgid "desired color depth in bits per pixel." +msgstr "期望的颜色深度(以每像素位数表示)。" + +#: ../../en/hardware/display.rst:200 314597b63fca47929eed02ab9a1fbb3e +msgid "" +"Notes: For CoreS3 devices, color depth is fixed at 16 bits and this " +"method has no effect." +msgstr "注意:对于 CoreS3 设备,颜色深度固定为 16 位,此方法无效。" + +#: ../../en/hardware/display.rst:204 041b75e955694464b45fda72a419ae40 +msgid "|setcolordepth.png|" msgstr "" -#: ../../en/hardware/display.rst:135 c90f9e2f71684befa54d9d6da3e02159 -msgid "3: M5.Lcd.EPDMode.EPD_FASTEST" +#: ../../en/refs/hardware.display.ref:9 707ca9ebf80a4f13904ef1215efc381e +msgid "setColorDepth.png" msgstr "" -#: ../../en/hardware/display.rst:137 f96cfa699bfc411a9edd95b86309e7d3 +#: ../../en/hardware/display.rst:214 d442aad848c14f539a43a8add5a2157a +msgid "Set the EPD mode for the display." +msgstr "设置显示屏的 EPD 模式。" + +#: ../../en/hardware/display.rst:216 ae4109cbaa574613904d4e328ca9b39b msgid "" -"Notes: This method is only applicable to devices with EPD (Electronic " -"Paper Display) capabilities." +"desired EPD mode - 0: M5.Lcd.EPDMode.EPD_QUALITY - 1: " +"M5.Lcd.EPDMode.EPD_TEXT - 2: M5.Lcd.EPDMode.EPD_FAST - 3: " +"M5.Lcd.EPDMode.EPD_FASTEST" msgstr "" -#: ../../en/hardware/display.rst:141 006618c93ed7484f9607db44272aab83 +#: ../../en/hardware/display.rst:222 39494dcbb24249c791de0e9a31a77f8a +msgid "Notes: Only applicable to devices with EPD capabilities." +msgstr "注意:仅适用于具有 EPD 功能的设备。" + +#: ../../en/hardware/display.rst:226 19e3eef19c07403a8b413e0438e0a811 +msgid "|setepdmode.png|" +msgstr "" + +#: ../../en/refs/hardware.display.ref:10 d22ecae101b34646b20cb4ae2e50b47b +msgid "setEpdMode.png" +msgstr "" + +#: ../../en/hardware/display.rst:236 99382d57c66d4eafb52a942ecd526de7 msgid "Check if the display is an EPD (Electronic Paper Display)." +msgstr "检查显示屏是否为电子墨水屏(EPD)。" + +#: ../../en/hardware/display.rst fb35e1cef072455bb2ff30d24f2f1fbf +msgid "returns is_epd" msgstr "" -#: ../../en/hardware/display.rst:143 d94f0da60e934f63b29fa80c71b07673 -msgid "Returns A boolean indicating whether the display is an EPD." +#: ../../en/hardware/display.rst:238 99382d57c66d4eafb52a942ecd526de7 +msgid "True if the display is EPD, False otherwise." +msgstr "如果显示屏为 EPD,则返回 True,否则返回 False。" + +#: ../../en/hardware/display.rst:239 1840f052787c449bb852570fab18f626 +msgid "bool" msgstr "" -#: ../../en/hardware/display.rst:147 0f81544fd03048eaa0472b7ae184a76b -msgid "Set the font for the display." +#: ../../en/hardware/display.rst:243 63027857b78c4b8893aee312c0a4fb0f +msgid "|isepd.png|" msgstr "" -#: ../../en/hardware/display.rst:149 0ac8b90657b34f5a81178b6b28ec7006 -msgid "The ``font`` parameter only accepts the following values:" +#: ../../en/refs/hardware.display.ref:11 5eb43b7f2f3e4b45bea18bf978ea89e0 +msgid "isEPD.png" msgstr "" -#: ../../en/hardware/display.rst:151 70ecf03e47e9413e970c5fd8d9f464da +#: ../../en/hardware/display.rst:253 1122916c27ba49dcb9b6967597d0d008 +msgid "Set the font for the display." +msgstr 设置显示字体。" + +#: ../../en/hardware/display.rst:255 5285475bd8494ae4bfe70469967e3e71 +msgid "" +"font type. Available options: - M5.Lcd.FONTS.ASCII7 - " +"M5.Lcd.FONTS.DejaVu9 - M5.Lcd.FONTS.DejaVu12 - M5.Lcd.FONTS.DejaVu18 - " +"M5.Lcd.FONTS.DejaVu24 - M5.Lcd.FONTS.DejaVu40 - M5.Lcd.FONTS.DejaVu56 - " +"M5.Lcd.FONTS.DejaVu72 - M5.Lcd.FONTS.EFontCN24 - M5.Lcd.FONTS.EFontJA24 -" +" M5.Lcd.FONTS.EFontKR24" +msgstr "" + +#: ../../en/hardware/display.rst:255 ae3bc8a0498c492c886f16bd40832443 +msgid "font type. Available options:" +msgstr "字体类型,可选:" + +#: ../../en/hardware/display.rst:257 1765bce8c8a04eebb104b4580b677d4f msgid "M5.Lcd.FONTS.ASCII7" msgstr "" -#: ../../en/hardware/display.rst:152 55c76b14f10c4f38a676ecbf9eef7684 +#: ../../en/hardware/display.rst:258 a3b156a28d1c4939be136f2aea20dd34 msgid "M5.Lcd.FONTS.DejaVu9" msgstr "" -#: ../../en/hardware/display.rst:153 6003f869a00d4a72b842fe8f67b93ad5 +#: ../../en/hardware/display.rst:259 228ed5df834042dc82615f02d8a23a34 msgid "M5.Lcd.FONTS.DejaVu12" msgstr "" -#: ../../en/hardware/display.rst:154 5f4d68fcee714f4ea6e43caeaa67d8c5 +#: ../../en/hardware/display.rst:260 8e25ff78ae064d96be72fa09e10c79e6 msgid "M5.Lcd.FONTS.DejaVu18" msgstr "" -#: ../../en/hardware/display.rst:155 25ba25b358454e95afeab920787f75d1 +#: ../../en/hardware/display.rst:261 740eac43be3442c9a3a304efb569740a msgid "M5.Lcd.FONTS.DejaVu24" msgstr "" -#: ../../en/hardware/display.rst:156 42875ff7686d42e09e27c4392984a15d +#: ../../en/hardware/display.rst:262 7714188f98d940b6b2dd266cdf2d6520 msgid "M5.Lcd.FONTS.DejaVu40" msgstr "" -#: ../../en/hardware/display.rst:157 730c79be54874468babdb22af5d2120a +#: ../../en/hardware/display.rst:263 fd6beb22a9304989a94d33870e86adcc msgid "M5.Lcd.FONTS.DejaVu56" msgstr "" -#: ../../en/hardware/display.rst:158 0c2287e86f80436f8ccf800f0a5c9c21 +#: ../../en/hardware/display.rst:264 14ac36dcd4194f3088e1448d70b6a753 msgid "M5.Lcd.FONTS.DejaVu72" msgstr "" -#: ../../en/hardware/display.rst:159 81c4b6f55192451ca643b15d81bd0667 +#: ../../en/hardware/display.rst:265 a534c9a6f70a46d8b884f10cc8c3c1d2 msgid "M5.Lcd.FONTS.EFontCN24" msgstr "" -#: ../../en/hardware/display.rst:160 14f75c4e5a334be6bd48f8c462fc02ee +#: ../../en/hardware/display.rst:266 5f8bc1d7feed4925901a2228f5c89174 msgid "M5.Lcd.FONTS.EFontJA24" msgstr "" -#: ../../en/hardware/display.rst:161 5c6ff79e48be41899aa2ff7f90fb0988 +#: ../../en/hardware/display.rst:267 4779de869c3d49d392ba50c2d91ae4f8 msgid "M5.Lcd.FONTS.EFontKR24" msgstr "" -#: ../../en/hardware/display.rst:165 cb5cb858ab004c379656a012bb4196e7 -msgid "Set the text color and background color." +#: ../../en/hardware/display.rst:271 f0094d94163e4186b6e27d345c76fb6b +msgid "|setfont.png|" +msgstr "" + +#: ../../en/refs/hardware.display.ref:12 a8ca6a35120741c9a5cbc5486521cd3f +msgid "setFont.png" msgstr "" -#: ../../en/hardware/display.rst:167 073f682993e64b4d94935306ea75dba7 -msgid "``fgcolor`` The text color in RGB888 format. Default is 0 (black)." +#: ../../en/hardware/display.rst:281 217ecff01fd34171a6713a8a6d517c97 +msgid "Set the text color and background color." +msgstr "设置文本颜色和背景颜色。" + +#: ../../en/hardware/display.rst:283 53a5737130c141319590acfef68145bd +msgid "text color in RGB888 format (default 0, black)" +msgstr "文本颜色,RGB888 格式。默认值为 0(黑色)。" + +#: ../../en/hardware/display.rst:284 2bce215d09a74963a053b350a4b0e7b5 +msgid "background color in RGB888 format (default 0, black)" +msgstr "背景颜色,RGB888 格式。默认值为 0(黑色)" + +#: ../../en/hardware/display.rst:288 a62056f757ca4d5bbadc82fbde025865 +msgid "|settextcolor.png|" msgstr "" -#: ../../en/hardware/display.rst:168 9e331907256c4f72873cb730285745a6 -msgid "``bgcolor`` The background color in RGB888 format. Default is 0 (black)." +#: ../../en/refs/hardware.display.ref:13 77fbf250a11d4ce2a22cc04b04d49e2a +msgid "setTextColor.png" msgstr "" -#: ../../en/hardware/display.rst:172 2eb30dfd0ba44c9ba485d63dbe08f319 +#: ../../en/hardware/display.rst:298 bd6fd0cf8bc44a96bb31af0dbf8a354e msgid "Enable or disable text scrolling." +msgstr "启用或禁用文本滚动。" + +#: ../../en/hardware/display.rst:300 eb76728072774aacb0362388b8cec790 +msgid "True to enable text scrolling, False to disable (default False)" +msgstr "设置为 True 启用文本滚动,设置为 False 禁用文本滚动。默认值为 False。" + +#: ../../en/hardware/display.rst:304 bd6fd0cf8bc44a96bb31af0dbf8a354e +msgid "|settextscroll.png|" msgstr "" -#: ../../en/hardware/display.rst:174 ebe4596068a1488e83049188301a408b -msgid "" -"``scroll`` Set to True to enable text scrolling, or False to disable it. " -"Default is False.\\" +#: ../../en/refs/hardware.display.ref:14 5facdd3fe9f14475885d006298ac2d71 +msgid "setTextScroll.png" msgstr "" -#: ../../en/hardware/display.rst:178 f5b4f369bfa34bb799785cd6b43faa12 +#: ../../en/hardware/display.rst:314 b7de3ad229d04a44a17266ad7c9662a7 msgid "Set the size of the text." +msgstr "设置文本的大小。" + +#: ../../en/hardware/display.rst:316 cfbfa8ac02614842a21808d5761f3c17 +msgid "desired text size" +msgstr "期望的文本大小。" + +#: ../../en/hardware/display.rst:320 792b2e03c7dd4a7c83327e03f180496c +msgid "|settextsize.png|" msgstr "" -#: ../../en/hardware/display.rst:180 a8b963a4b4664b5397a7747f5a050ea0 -msgid "``size`` The desired text size." +#: ../../en/refs/hardware.display.ref:15 aa932b75bc02420da8524956880cfbf2 +msgid "setTextSize.png" msgstr "" -#: ../../en/hardware/display.rst:184 98e181ccd619448297da49ed1700b3d0 +#: ../../en/hardware/display.rst:330 4b046990b80548eb9bca421c86c3902d msgid "Set the cursor position." -msgstr "" +msgstr "设置光标位置。" -#: ../../en/hardware/display.rst:186 771bde5a833740c29ecfd8d7aee8622c -msgid "``x`` The horizontal position of the cursor. Default is 0." +#: ../../en/hardware/display.rst:332 5d793ff68f2842d2a800ab718273d71c +msgid "horizontal position of the cursor (default 0)" +msgstr "光标的水平位置。默认值为 0。 " + +#: ../../en/hardware/display.rst:333 2dc3041f71d24d4aa6ed91b684a358bb +msgid "vertical position of the cursor (default 0)" +msgstr "光标的垂直位置。默认值为 0。" + +#: ../../en/hardware/display.rst:337 7a695d3c23804d678281e46ab6c5c0b3 +msgid "|setcursor.png|" msgstr "" -#: ../../en/hardware/display.rst:187 e157a26ce0ac410d924ac5eeddf40640 -msgid "``y`` The vertical position of the cursor. Default is 0." +#: ../../en/refs/hardware.display.ref:16 317473be3dc3465583415f0073d07459 +msgid "setCursor.png" msgstr "" -#: ../../en/hardware/display.rst:191 83c1a89ee02f45e8915a963ba30c3550 +#: ../../en/hardware/display.rst:347 c1797e30a96342a8929e7eda20877849 msgid "Clear the display with a specific color." +msgstr "使用指定颜色清空显示屏" + +#: ../../en/hardware/display.rst:349 ../../en/hardware/display.rst:365 +#: ../../en/hardware/display.rst:421 ../../en/hardware/display.rst:461 +#: ../../en/hardware/display.rst:521 ../../en/hardware/display.rst:563 +#: ../../en/hardware/display.rst:607 ../../en/hardware/display.rst:651 +#: ../../en/hardware/display.rst:699 1053eb3d56a24ab29cb9cd5f0dd71eba +msgid "fill color in RGB888 format (default 0)" +msgstr "填充颜色,使用 RGB888 格式(默认值为 0)。" + +#: ../../en/hardware/display.rst:353 a5f6832acdb04470aabac3c1acb07a91 +msgid "|clear.png|" msgstr "" -#: ../../en/hardware/display.rst:193 ../../en/hardware/display.rst:199 -#: ../../en/hardware/display.rst:226 ../../en/hardware/display.rst:246 -#: 660360a923c04931867e1eeac0178bf5 -msgid "``color`` The fill color in RGB888 format. Default is 0." +#: ../../en/refs/hardware.display.ref:17 5cb675258a9f4cc1a027b097ba96e4c4 +msgid "clear.png" msgstr "" -#: ../../en/hardware/display.rst:197 ad36a6ff28054197a23b24047899fea3 +#: ../../en/hardware/display.rst:363 76fad14a34b34e4e917e804cb5d6bb39 msgid "Fill the entire screen with a specified color." -msgstr "" +msgstr "用指定颜色填充整个屏幕。" -#: ../../en/hardware/display.rst:204 2418963bed0d48e9a5746a9cbdd66e81 -msgid "Draw a single pixel on the screen." +#: ../../en/hardware/display.rst:369 91c84d4da49a46dc9df72cdb8bb68c6f +msgid "|fillscreen.png|" msgstr "" -#: ../../en/hardware/display.rst:206 337ede9ed7534f1bbd55ae51ee52f79f -msgid "``x`` The horizontal coordinate of the pixel. Default is -1." +#: ../../en/refs/hardware.display.ref:18 af1f7695296347efae034f9f5a1f2072 +msgid "fillScreen.png" msgstr "" -#: ../../en/hardware/display.rst:207 e74c3aec349c4a228afeed6dacb11a76 -msgid "``y`` The vertical coordinate of the pixel. Default is -1." +#: ../../en/hardware/display.rst:379 4bf91ab9b3494e9eabacd530adcf3e0e +msgid "Draw a single pixel on the screen." +msgstr "在屏幕上绘制单个像素。" + +#: ../../en/hardware/display.rst:381 8ee59db1f6cb425da4ef612f8bee5d41 +msgid "horizontal coordinate of the pixel (default -1)" +msgstr "像素的水平坐标。默认值为 -1" + +#: ../../en/hardware/display.rst:382 ce29c038a4d14bc3a0cfab0dac653685 +msgid "vertical coordinate of the pixel (default -1)" +msgstr "像素的垂直坐标。默认值为 -1" + +#: ../../en/hardware/display.rst:383 1053eb3d56a24ab29cb9cd5f0dd71eba +msgid "pixel color in RGB888 format (default 0)" +msgstr 像素的颜色,RGB888 格式。默认值为 0" + +#: ../../en/hardware/display.rst:387 d58ddf6cd39a47688a1675b9cd2bfced +msgid "|drawpixel.png|" msgstr "" -#: ../../en/hardware/display.rst:208 d6c1bd4334564b6d9aa1d220e51020cd -msgid "``color`` The color of the pixel in RGB888 format. Default is 0." +#: ../../en/refs/hardware.display.ref:19 c1bfe42aa624499e8fe12128f39de560 +msgid "drawPixel.png" msgstr "" -#: ../../en/hardware/display.rst:212 015370504d89468bb1929b4536fb4fa2 +#: ../../en/hardware/display.rst:397 95d9adcee2a541688e382e903d1a124d msgid "Draw an outline of a circle." -msgstr "" +msgstr "绘制一个圆。" -#: ../../en/hardware/display.rst:214 ../../en/hardware/display.rst:223 -#: 77d5adb379934dfa8ff74bda63295f9c -msgid "``x`` The x-coordinate of the circle center. Default is -1." -msgstr "" +#: ../../en/hardware/display.rst:399 ../../en/hardware/display.rst:418 +#: e912e17f212e4e73997160730ce8716e +msgid "x-coordinate of circle center (default -1)" +msgstr "圆心的 x 坐标。默认值为 -1。" -#: ../../en/hardware/display.rst:215 ../../en/hardware/display.rst:224 -#: 3b87435835a14267863f424d4794080f -msgid "``y`` The y-coordinate of the circle center. Default is -1." -msgstr "" +#: ../../en/hardware/display.rst:400 ../../en/hardware/display.rst:419 +#: 33ead5ad749c43b0a8363db4d4255a38 +msgid "y-coordinate of circle center (default -1)" +msgstr "圆心的 y 坐标。默认值为 -1。" -#: ../../en/hardware/display.rst:216 ../../en/hardware/display.rst:225 -#: 1009c0a41f5b48abb15e8a4f11dc215b -msgid "``r`` The radius of the circle. Default is -1." +#: ../../en/hardware/display.rst:401 ../../en/hardware/display.rst:420 +#: 7e64e196d68b4225b1f1694d013ac559 +msgid "radius of the circle (default -1)" +msgstr "圆的半径。默认值为 -1。" + +#: ../../en/hardware/display.rst:402 14b64745dff04ebcad4896bf02e64b43 +msgid "circle color in RGB888 format (default 0)" +msgstr "圆的颜色,RGB888 格式。默认值为 0。" + +#: ../../en/hardware/display.rst:406 f3e4f41b8e864a07849f3772e6eab8c6 +msgid "|drawcircle.png|" msgstr "" -#: ../../en/hardware/display.rst:217 1c23e3a4cb474ca4a7bcd0449faa8192 -msgid "``color`` The color of the circle in RGB888 format. Default is 0." +#: ../../en/refs/hardware.display.ref:20 71158a8309d74b8cb64b695b1baecdc0 +msgid "drawCircle.png" msgstr "" -#: ../../en/hardware/display.rst:221 07bc7ab86d564853906e0740823c4e98 +#: ../../en/hardware/display.rst:416 c3ef5d6b8f3b4e4e8dff2511ebaef60f msgid "Draw a filled circle." -msgstr "" +msgstr "绘制一个实心圆。 " -#: ../../en/hardware/display.rst:230 1d42c9f07f2b44ecab79ca8bf55d644a -msgid "Draw an outline of an ellipse." +#: ../../en/hardware/display.rst:425 f029d9aa297a4b889a9b48ef39f9d145 +msgid "|fillcircle.png|" msgstr "" -#: ../../en/hardware/display.rst:232 ../../en/hardware/display.rst:242 -#: 24d77e38257545f2965cf9a073ed023c -msgid "``x`` The x-coordinate of the ellipse center. Default is -1." +#: ../../en/refs/hardware.display.ref:21 dc36b71e06234cbdbd581b403920e594 +msgid "fillCircle.png" msgstr "" -#: ../../en/hardware/display.rst:233 ../../en/hardware/display.rst:243 -#: 81ae0a1839594f7483626452d9572e62 -msgid "``y`` The y-coordinate of the ellipse center. Default is -1." -msgstr "" +#: ../../en/hardware/display.rst:435 bfba436aa7d1418c81d3cc078cff1696 +msgid "Draw an outline of an ellipse." +msgstr "绘制一个椭圆。" -#: ../../en/hardware/display.rst:234 ../../en/hardware/display.rst:244 -#: 07f7db561b86411b97ac4781ee35c2f2 -msgid "``rx`` The horizontal radius of the ellipse. Default is -1." -msgstr "" +#: ../../en/hardware/display.rst:437 ../../en/hardware/display.rst:457 +#: d374ff5fe5564382adfc84277aeec332 +msgid "x-coordinate of ellipse center (default -1)" +msgstr "椭圆中心的 x 坐标。默认值为 -1。 " + +#: ../../en/hardware/display.rst:438 ../../en/hardware/display.rst:458 +#: fc11af7ea32348a18313da33cf8951d5 +msgid "y-coordinate of ellipse center (default -1)" +msgstr "椭圆中心的 y 坐标。默认值为 -1。" + +#: ../../en/hardware/display.rst:439 ../../en/hardware/display.rst:459 +#: 44ec027e0f5e47608b5ba7cf20eb1e58 +msgid "horizontal radius (default -1)" +msgstr "椭圆的水平半径。默认值为 -1。" + +#: ../../en/hardware/display.rst:440 ../../en/hardware/display.rst:460 +#: d1cb9fe8cda84b9dab6e2cd3ee75160c +msgid "vertical radius (default -1)" +msgstr "椭圆的垂直半径。默认值为 -1。" -#: ../../en/hardware/display.rst:235 ../../en/hardware/display.rst:245 -#: 330704d9440847e18716fa06f2bb9081 -msgid "``ry`` The vertical radius of the ellipse. Default is -1." +#: ../../en/hardware/display.rst:441 1053eb3d56a24ab29cb9cd5f0dd71eba +msgid "ellipse color in RGB888 format (default 0)" +msgstr "椭圆颜色,RGB888 格式。默认值为 0。" + +#: ../../en/hardware/display.rst:445 d48c237e1db143859ee9d5e7a0e8aab0 +msgid "|drawellipse.png|" msgstr "" -#: ../../en/hardware/display.rst:236 af5d142b59c747f9b7fcc9d1e980a2b3 -msgid "``color`` The color of the ellipse in RGB888 format. Default is 0." +#: ../../en/refs/hardware.display.ref:22 4fcf56598a774e12a981509d3c7bda1a +msgid "drawEllipse.png" msgstr "" -#: ../../en/hardware/display.rst:240 f9fdc102118b4bb98b24f6a83795ec93 +#: ../../en/hardware/display.rst:455 391ee9f9325546cf9200a59293ff0030 msgid "Draw a filled ellipse." -msgstr "" +msgstr "绘制一个实心椭圆。" -#: ../../en/hardware/display.rst:250 48fd5a39d6a34732aa85dc90bbd64fd7 -msgid "Draw a line." +#: ../../en/hardware/display.rst:465 78f4b0a6dd434fd1b7bcb523de3e9253 +msgid "|fillellipse.png|" msgstr "" -#: ../../en/hardware/display.rst:252 c0fe69d7f5724e1aae9b2e306402fba5 -msgid "``x0, y0`` Starting point coordinates of the line. Default is -1." +#: ../../en/refs/hardware.display.ref:23 0fe170b98f6e4040bc1c21cd57dd64bc +msgid "fillEllipse.png" msgstr "" -#: ../../en/hardware/display.rst:253 79e8969ca13c432d8e0b540bb3014841 -msgid "``x1, y1`` Ending point coordinates of the line. Default is -1." +#: ../../en/hardware/display.rst:475 1951e7367a6346339a1e326f254cf498 +msgid "Draw a line." +msgstr "绘制一条直线。" + +#: ../../en/hardware/display.rst:477 a0e6b86a7a1742079d5deee1326991cc +msgid "starting x-coordinate (default -1)" +msgstr "直线起点坐标 x,默认值为 -1。" + +#: ../../en/hardware/display.rst:478 a0e6b86a7a1742079d5deee1326991cc +msgid "starting y-coordinate (default -1)" +msgstr "直线起点坐标 y,默认值为 -1。" + +#: ../../en/hardware/display.rst:479 d685b9c9e92d4d008c5e53d575c76037 +msgid "ending x-coordinate (default -1)" +msgstr "直线终点坐标 x,默认值为 -1。" + +#: ../../en/hardware/display.rst:480 d685b9c9e92d4d008c5e53d575c76037 +msgid "ending y-coordinate (default -1)" +msgstr "直线终点坐标 y,默认值为 -1。" + +#: ../../en/hardware/display.rst:481 1053eb3d56a24ab29cb9cd5f0dd71eba +msgid "line color in RGB888 format (default 0)" +msgstr "线颜色,RGB888 格式,默认值为 0。" + +#: ../../en/hardware/display.rst:485 cc505916ed92475c823f744ce9f14100 +msgid "|drawline.png|" msgstr "" -#: ../../en/hardware/display.rst:254 ../../en/hardware/display.rst:262 -#: ../../en/hardware/display.rst:270 ../../en/hardware/display.rst:279 -#: ../../en/hardware/display.rst:288 ../../en/hardware/display.rst:298 -#: ../../en/hardware/display.rst:318 ../../en/hardware/display.rst:329 -#: ../../en/hardware/display.rst:340 ../../en/hardware/display.rst:449 -#: c4cac61a47ca4c5b911500c98a5cc756 d54dd490e6554e55b6bd35123a6a2550 -msgid "``color`` Color in RGB888 format. Default is 0." +#: ../../en/refs/hardware.display.ref:24 eac86bba16da4fbf86212419670536a9 +msgid "drawLine.png" msgstr "" -#: ../../en/hardware/display.rst:258 0a29cdb2f0f64aa7b6d9366107d5e8bc +#: ../../en/hardware/display.rst:495 1ec912129ee84a2a8619e8f52d7f5fa7 msgid "Draw a rectangle." -msgstr "" +msgstr "绘制一个矩形。" + +#: ../../en/hardware/display.rst:497 ../../en/hardware/display.rst:517 +#: ../../en/hardware/display.rst:537 ../../en/hardware/display.rst:558 +#: 4284dad8d5d843579b1c55ea6ee8bd82 +msgid "top-left x-coordinate (default -1)" +msgstr 矩形左上角坐标 x,默认值为 -1。"" + +#: ../../en/hardware/display.rst:498 ../../en/hardware/display.rst:518 +#: ../../en/hardware/display.rst:538 ../../en/hardware/display.rst:559 +#: 4284dad8d5d843579b1c55ea6ee8bd82 +msgid "top-left y-coordinate (default -1)" +msgstr "矩形左上角坐标 y,默认值为 -1。" + +#: ../../en/hardware/display.rst:499 ../../en/hardware/display.rst:519 +#: ../../en/hardware/display.rst:539 ../../en/hardware/display.rst:560 +#: 87753b18a9e84ea988b0b89da58a73ba +msgid "width of rectangle (default -1)" +msgstr "矩形的宽度,默认值为 -1。 " + +#: ../../en/hardware/display.rst:500 ../../en/hardware/display.rst:520 +#: ../../en/hardware/display.rst:540 ../../en/hardware/display.rst:561 +#: 87753b18a9e84ea988b0b89da58a73ba +msgid "height of rectangle (default -1)" +msgstr "矩形的高度,默认值为 -1。 " -#: ../../en/hardware/display.rst:260 ../../en/hardware/display.rst:268 -#: ../../en/hardware/display.rst:276 ../../en/hardware/display.rst:285 -#: 42eb9ae3cb1149f6a226479725d40ec1 6ac97cb9143f4f11aa512938ba83d13a -msgid "``x, y`` Top-left corner coordinates of the rectangle. Default is -1." +#: ../../en/hardware/display.rst:501 ../../en/hardware/display.rst:542 +#: 1053eb3d56a24ab29cb9cd5f0dd71eba +msgid "rectangle color in RGB888 format (default 0)" +msgstr "矩形颜色,RGB888 格式,默认值为 0。" + +#: ../../en/hardware/display.rst:505 23f81682ec0c4b7fba344f4f9db9c6d3 +msgid "|drawrect.png|" msgstr "" -#: ../../en/hardware/display.rst:261 ../../en/hardware/display.rst:269 -#: ../../en/hardware/display.rst:277 ../../en/hardware/display.rst:286 -#: 04a95103cf414d22b04b244d9e91f188 b333928906f0460bbda516b8ae35531f -msgid "``w, h`` Width and height of the rectangle. Default is -1." +#: ../../en/refs/hardware.display.ref:25 35f3ee5e17ac429aaf858c274c7571cb +msgid "drawRect.png" msgstr "" -#: ../../en/hardware/display.rst:266 8f29ed2e67f148abb837e252f69d4e4c +#: ../../en/hardware/display.rst:515 9bab821100214ea58ce84e321e5b4637 msgid "Draw a filled rectangle." +msgstr "绘制一个填充矩形。" + +#: ../../en/hardware/display.rst:525 23f81682ec0c4b7fba344f4f9db9c6d3 +msgid "|fillrect.png|" +msgstr "" + +#: ../../en/refs/hardware.display.ref:26 c0dac482d8ca463f80e3948e573eee2b +msgid "fillRect.png" msgstr "" -#: ../../en/hardware/display.rst:274 85acb75a70574c3a8d76199c7cc964dc +#: ../../en/hardware/display.rst:535 7586bf5a682945be9b2a0e2e4621381f msgid "Draw a rounded rectangle." +msgstr "绘制一个圆角矩形。" + +#: ../../en/hardware/display.rst:541 ../../en/hardware/display.rst:562 +#: b8118b7a72b94009a0e534aab80aa184 +msgid "corner radius (default -1)" +msgstr "圆角半径,默认值为 -1。" + +#: ../../en/hardware/display.rst:546 2f0e46eb70fe4183b03b878ae7d6e176 +msgid "|drawroundrect.png|" msgstr "" -#: ../../en/hardware/display.rst:278 ../../en/hardware/display.rst:287 -#: c1dcd70992ab41199f8324e91583bda5 -msgid "``r`` Radius of the corners. Default is -1." +#: ../../en/refs/hardware.display.ref:27 7d138dab284a4f469d56fbd426ac3876 +msgid "drawRoundRect.png" msgstr "" -#: ../../en/hardware/display.rst:283 8ed5f96f0d664a51857a29d010eb4ea4 +#: ../../en/hardware/display.rst:556 d5ea65a32d0a4dd887b32e3d10603e8e msgid "Draw a filled rounded rectangle." -msgstr "" +msgstr "绘制一个填充圆角矩形。" -#: ../../en/hardware/display.rst:293 4816740775324eff9f5e4d8ef6b6937a -msgid "Draw a triangle." +#: ../../en/hardware/display.rst:567 d5ea65a32d0a4dd887b32e3d10603e8e +msgid "|fillroundrect.png|" msgstr "" -#: ../../en/hardware/display.rst:295 ../../en/hardware/display.rst:304 -#: 484283251a0b4e598e8545244bd3264d -msgid "``x0, y0`` Coordinates of the first vertex. Default is -1." +#: ../../en/refs/hardware.display.ref:28 ded350ea33ef4f088114ee27d7c46a47 +msgid "fillRoundRect.png" msgstr "" -#: ../../en/hardware/display.rst:296 ../../en/hardware/display.rst:305 -#: cc94adf56a2e499b9641097a6df2d8f0 -msgid "``x1, y1`` Coordinates of the second vertex. Default is -1." +#: ../../en/hardware/display.rst:577 e7329aaf1e554e5fba4c7b60a97e8e20 +msgid "Draw a triangle." +msgstr "绘制一个三角形。" + +#: ../../en/hardware/display.rst:579 ../../en/hardware/display.rst:601 +#: ce29c038a4d14bc3a0cfab0dac653685 +msgid "first vertex x-coordinate (default -1)" +msgstr "第一个顶点的坐标 x,默认值为 -1。" + +#: ../../en/hardware/display.rst:580 ../../en/hardware/display.rst:602 +#: ce29c038a4d14bc3a0cfab0dac653685 +msgid "first vertex y-coordinate (default -1)" +msgstr "第一个顶点的坐标 y,默认值为 -1。" + +#: ../../en/hardware/display.rst:581 ../../en/hardware/display.rst:603 +#: ce29c038a4d14bc3a0cfab0dac653685 +msgid "second vertex x-coordinate (default -1)" +msgstr "第二个顶点的坐标 x,默认值为 -1。" + +#: ../../en/hardware/display.rst:582 ../../en/hardware/display.rst:604 +#: ce29c038a4d14bc3a0cfab0dac653685 +msgid "second vertex y-coordinate (default -1)" +msgstr "第二个顶点的坐标 y,默认值为 -1。 + +#: ../../en/hardware/display.rst:583 ../../en/hardware/display.rst:605 +#: ce29c038a4d14bc3a0cfab0dac653685 +msgid "third vertex x-coordinate (default -1)" +msgstr "第三个顶点的坐标 x,默认值为 -1。" + +#: ../../en/hardware/display.rst:584 ../../en/hardware/display.rst:606 +#: ce29c038a4d14bc3a0cfab0dac653685 +msgid "third vertex y-coordinate (default -1)" +msgstr "第三个顶点的坐标 y,默认值为 -1。" + +#: ../../en/hardware/display.rst:585 1053eb3d56a24ab29cb9cd5f0dd71eba +msgid "triangle color in RGB888 format (default 0)" +msgstr "三角形颜色,RGB888 格式,默认值为 0。" + +#: ../../en/hardware/display.rst:589 47b63a6dfdd444f7aa6d9c8593860cc0 +msgid "|drawtriangle.png|" msgstr "" -#: ../../en/hardware/display.rst:297 ../../en/hardware/display.rst:306 -#: 3af3ce7444b445abb9da82f32777248c -msgid "``x2, y2`` Coordinates of the third vertex. Default is -1." +#: ../../en/refs/hardware.display.ref:29 955d4ae7980c4a389efec7313b8b52b6 +msgid "drawTriangle.png" msgstr "" -#: ../../en/hardware/display.rst:302 4508fb8c0c854ac3b7dfffc3eb25efea +#: ../../en/hardware/display.rst:599 9bd29948e7da4663827d01ea2ce36b71 msgid "Draw a filled triangle." +msgstr "绘制一个填充三角形。" + +#: ../../en/hardware/display.rst:611 9ccab09ac7a748ff923e094de34356b1 +msgid "|filltriangle.png|" msgstr "" -#: ../../en/hardware/display.rst:307 ../../en/hardware/display.rst:351 -#: ffc99121de0d42e2b351a5d2677aaf91 -msgid "``color:`` Color in RGB888 format. Default is 0." +#: ../../en/refs/hardware.display.ref:30 219840d1abe14ea382ef5795224b9d49 +msgid "fillTriangle.png" msgstr "" -#: ../../en/hardware/display.rst:311 74c911debb5b4a588ad51a12579f2ea7 +#: ../../en/hardware/display.rst:621 3fb91a0d94c548b1bd6a02ec3f517f91 msgid "Draw an arc." -msgstr "" +msgstr "绘制一个弧线。" -#: ../../en/hardware/display.rst:313 ../../en/hardware/display.rst:324 -#: 72f177a7ab0f4b43ac3f2cfdad4c6feb -msgid "``x, y`` Center coordinates of the arc. Default is -1." -msgstr "" +#: ../../en/hardware/display.rst:623 ../../en/hardware/display.rst:645 +#: ../../en/hardware/display.rst:667 ../../en/hardware/display.rst:691 +#: 4284dad8d5d843579b1c55ea6ee8bd82 +msgid "center x-coordinate (default -1)" +msgstr "弧线的中心坐标 x,默认值为 -1。" -#: ../../en/hardware/display.rst:314 ../../en/hardware/display.rst:325 -#: 0db0325821c8428ab37ed79e14bb5b5f -msgid "``r0`` Inner radius of the arc. Default is -1." -msgstr "" +#: ../../en/hardware/display.rst:624 ../../en/hardware/display.rst:646 +#: ../../en/hardware/display.rst:668 ../../en/hardware/display.rst:692 +#: 4284dad8d5d843579b1c55ea6ee8bd82 +msgid "center y-coordinate (default -1)" +msgstr "弧线的中心坐标 y,默认值为 -1。" -#: ../../en/hardware/display.rst:315 ../../en/hardware/display.rst:326 -#: 4bb6720c490d4aa58a843f4fc1808579 -msgid "``r1`` Outer radius of the arc. Default is -1." -msgstr "" +#: ../../en/hardware/display.rst:625 ../../en/hardware/display.rst:647 +#: 3f139b071f874fe29dbf0ec74795c28a +msgid "first radius (default -1)" +msgstr "第一个半径,默认值为 -1。" + +#: ../../en/hardware/display.rst:626 ../../en/hardware/display.rst:648 +#: b8118b7a72b94009a0e534aab80aa184 +msgid "second radius (default -1)" +msgstr "第二个半价,默认值为 -1。" + +#: ../../en/hardware/display.rst:627 ../../en/hardware/display.rst:649 +#: ../../en/hardware/display.rst:673 ../../en/hardware/display.rst:697 +#: 66f93f2e4d5e48d7ab23a61a3f86af49 +msgid "starting angle in degrees (default -1)" +msgstr "弧线的起始角度(单位:度),默认值为 -1。" + +#: ../../en/hardware/display.rst:628 ../../en/hardware/display.rst:650 +#: 639356a81fbe4110902efdb8c770f7bb +msgid "ending angle in degrees (default -1)" +msgstr "弧线的终止角度(单位:度),默认值为 -1。" -#: ../../en/hardware/display.rst:316 ../../en/hardware/display.rst:327 -#: ../../en/hardware/display.rst:338 ../../en/hardware/display.rst:349 -#: a8f5218579934eb8a5389cbcaecdf202 -msgid "``angle0`` Starting angle of the arc (in degrees). Default is -1." +#: ../../en/hardware/display.rst:629 ../../en/hardware/display.rst:675 +#: 1053eb3d56a24ab29cb9cd5f0dd71eba +msgid "arc color in RGB888 format (default 0)" +msgstr "弧线颜色,RGB888 格式,默认值为 0。" + +#: ../../en/hardware/display.rst:633 1ca64ea992e9452abde49e8bce106c5f +msgid "|drawarc.png|" msgstr "" -#: ../../en/hardware/display.rst:317 ../../en/hardware/display.rst:328 -#: ../../en/hardware/display.rst:350 b16c8fca83e944918433ee5c36c7eb1a -msgid "``angle1`` Ending angle of the arc (in degrees). Default is -1." +#: ../../en/refs/hardware.display.ref:31 a7e72bb7b9d94c66bc7fa7857abef865 +msgid "drawArc.png" msgstr "" -#: ../../en/hardware/display.rst:322 e41b306671c7438ca1f58318f470d733 +#: ../../en/hardware/display.rst:643 7bf48f536796466fa6a1a9677368cff9 msgid "Draw a filled arc." -msgstr "" +msgstr "绘制一个填充弧线。" -#: ../../en/hardware/display.rst:333 1f2b686832704358aa5ed7f0c6330d50 -msgid "Draw an elliptical arc." +#: ../../en/hardware/display.rst:655 a24cfa7b5b424996a79dec7fa1d47beb +msgid "|fillarc.png|" msgstr "" -#: ../../en/hardware/display.rst:335 ../../en/hardware/display.rst:346 -#: 2c800cef0f274ff481799f2ae3e8a3e4 -msgid "``x, y`` Center coordinates of the elliptical arc. Default is -1." +#: ../../en/refs/hardware.display.ref:32 fcc50a71d4e74fe187a0e728f8bd62ab +msgid "fillArc.png" msgstr "" -#: ../../en/hardware/display.rst:336 ../../en/hardware/display.rst:347 -#: fba42ecaffbe4b9686a5483957ecf91f -msgid "" -"``r0x, r0y`` Radii of the inner ellipse (horizontal and vertical). " -"Default is -1." -msgstr "" +#: ../../en/hardware/display.rst:665 6d492da623ed4914800730a6c90de6d3 +msgid "Draw an elliptical arc." +msgstr "绘制一个椭圆弧线。" -#: ../../en/hardware/display.rst:337 ../../en/hardware/display.rst:348 -#: 5fcd2eb12ecf4aa09840ab6ddd362e70 -msgid "" -"``r1x, r1y`` Radii of the outer ellipse (horizontal and vertical). " -"Default is -1." -msgstr "" +#: ../../en/hardware/display.rst:669 ../../en/hardware/display.rst:693 +#: 44ec027e0f5e47608b5ba7cf20eb1e58 +msgid "first horizontal radius (default -1)" +msgstr "第一个水平半径,默认值为 -1。" -#: ../../en/hardware/display.rst:339 58c1ccb2b1354a08a54c9cf871275df0 -msgid "``angle1`` Ending angle of the arc (in degrees). Default is 0." -msgstr "" +#: ../../en/hardware/display.rst:670 ../../en/hardware/display.rst:694 +#: d1cb9fe8cda84b9dab6e2cd3ee75160c +msgid "first vertical radius (default -1)" +msgstr "第一个垂直半径,默认值为 -1。" -#: ../../en/hardware/display.rst:344 2faaa85a742d42178cd966a211e133d1 -msgid "Draw a filled elliptical arc." -msgstr "" +#: ../../en/hardware/display.rst:671 ../../en/hardware/display.rst:695 +#: 44ec027e0f5e47608b5ba7cf20eb1e58 +msgid "second horizontal radius (default -1)" +msgstr "第二个水平半径,默认值为 -1。" -#: ../../en/hardware/display.rst:355 ba2e249576d04aadb7cbadbc59a09e5a -msgid "Draw a QR code." -msgstr "" +#: ../../en/hardware/display.rst:672 ../../en/hardware/display.rst:696 +#: d1cb9fe8cda84b9dab6e2cd3ee75160c +msgid "second vertical radius (default -1)" +msgstr 第二个垂直半径,默认值为 -1。"" -#: ../../en/hardware/display.rst:357 79210a88c6ee4810b7f041725ea5b38f -msgid "``text`` QR code content." -msgstr "" +#: ../../en/hardware/display.rst:674 ../../en/hardware/display.rst:698 +#: a340a7fd3fd94f14945d7702dbe34547 +msgid "ending angle in degrees (default 0)" +msgstr "椭圆弧线的终止角度(单位:度),默认值为 0。" -#: ../../en/hardware/display.rst:358 a3cef6d055244ec286e010e5e05779d8 -msgid "``x, y`` Starting coordinates for displaying the QR code." +#: ../../en/hardware/display.rst:679 28461b360f93460ebb167524425cec2a +msgid "|drawellipsearc.png|" msgstr "" -#: ../../en/hardware/display.rst:359 c1d3be9e7dc545f3a76e5d982c9c50e5 -msgid "``w:`` Width of the QR code. Default is 0." +#: ../../en/refs/hardware.display.ref:33 e50e635166724479b566986c48b58618 +msgid "drawEllipseArc.png" msgstr "" -#: ../../en/hardware/display.rst:360 a5f85a36391c45dd8733951eb36c89c2 -msgid "``version`` QR code version. Default is 1." -msgstr "" +#: ../../en/hardware/display.rst:689 671e1de97cb0427d9a8a1809f49b9cad +msgid "Draw a filled elliptical arc." +msgstr "绘制一个填充椭圆弧线。" -#: ../../en/hardware/display.rst:362 ../../en/hardware/display.rst:424 -#: ../../en/hardware/display.rst:467 8d9af6d08eca41939bd66fcdbbf1ff30 -msgid "**Example**:" +#: ../../en/hardware/display.rst:703 f1140bf9ccd24a578ba5736daf8c94b9 +msgid "|fillellipsearc.png|" msgstr "" -#: ../../en/hardware/display.rst:364 b47c7735d57a492194c428c4976273ad -msgid "Generate and display a QR code with the content \"hello\":" +#: ../../en/refs/hardware.display.ref:34 9326ad0418904093bbbb497f753b3c5a +msgid "fillEllipseArc.png" msgstr "" -#: ../../en/hardware/display.rst:372 b975d4c7cd3c49ed9fa4dcf8b05eda2f -msgid "Draw a PNG image." -msgstr "" +#: ../../en/hardware/display.rst:713 5c19e5bc336e4b3ba3bcd5efeb139950 +msgid "Draw a QR code." +msgstr "绘制二维码。" -#: ../../en/hardware/display.rst:374 ../../en/hardware/display.rst:401 -#: ../../en/hardware/display.rst:410 ../../en/hardware/display.rst:419 -#: 79be809b16c842e5a12e3dfa65d07fba f090d98913de40ef953695d1c25d66d4 -msgid "``img`` Image file path or opened image data." -msgstr "" +#: ../../en/hardware/display.rst:715 aa954d954a9b49a5b3ba8408da81631d +msgid "QR code content" +msgstr "二维码内容" -#: ../../en/hardware/display.rst:375 ../../en/hardware/display.rst:402 -#: ../../en/hardware/display.rst:411 ../../en/hardware/display.rst:420 -#: ../../en/hardware/display.rst:439 4af54c7322d3459c9b7515c00999e1dd -#: 6bb64bba96ee4295aba5c02f1ca322ea -msgid "``x, y`` Starting coordinates on the display screen." -msgstr "" +#: ../../en/hardware/display.rst:716 e912e17f212e4e73997160730ce8716e +msgid "x-coordinate to display (default 0)" +msgstr "二维码显示的起始坐标 x,默认值为 0。" -#: ../../en/hardware/display.rst:376 ../../en/hardware/display.rst:403 -#: ../../en/hardware/display.rst:412 ../../en/hardware/display.rst:421 -#: 12180cc8126a43b3bb7e118687d46ffc 97c1255d66704a1e9affc7ec29b37412 -msgid "``maxW, maxH`` Width and height to be drawn. Draws the full image if ≤0." -msgstr "" +#: ../../en/hardware/display.rst:717 33ead5ad749c43b0a8363db4d4255a38 +msgid "y-coordinate to display (default 0)" +msgstr "二维码显示的起始坐标 y,默认值为 0。" -#: ../../en/hardware/display.rst:377 ../../en/hardware/display.rst:404 -#: ../../en/hardware/display.rst:413 ../../en/hardware/display.rst:422 -#: 03fd294c6cc44f33ae0da0205d7848c8 39058bccc2c844fc93df956b4ea8dbd6 -msgid "``offX, offY`` Offset in the image to start from." -msgstr "" +#: ../../en/hardware/display.rst:718 a41a1c7fa9494d17b7fc6e01c7cf5a38 +msgid "QR code width (default 0)" +msgstr "二维码的宽度,默认值为 0。" -#: ../../en/hardware/display.rst:378 d64fa358355e46edaeda95a3a787fa6e -msgid "``scaleX, scaleY`` Whether to scale the image horizontally or vertically." -msgstr "" +#: ../../en/hardware/display.rst:719 a41a1c7fa9494d17b7fc6e01c7cf5a38 +msgid "QR code version (default 1)" +msgstr "二维码版本,默认值为 1。" -#: ../../en/hardware/display.rst:380 28809e09f07441e8a9c910aee3e6768b -msgid "**Examples**:" +#: ../../en/hardware/display.rst:723 eef5d9dc766a4014a8a23e37f310925d +msgid "|drawqr.png|" msgstr "" -#: ../../en/hardware/display.rst:382 24e73e088a0f442fbe33bc81c0735e50 -msgid "Display a PNG image from a specified path:" +#: ../../en/refs/hardware.display.ref:35 2d8c02a33464488f8a34c81521724bd6 +msgid "drawQR.png" msgstr "" -#: ../../en/hardware/display.rst:388 eb3d314b190c4bc7accc561e07b0f258 -msgid "Display a PNG image from read data:" +#: ../../en/hardware/display.rst:733 ac84db764545449f83b4c48902968ff0 +msgid "Draw a PNG image." +msgstr "绘制 PNG 图片。" + +#: ../../en/hardware/display.rst:735 ../../en/hardware/display.rst:769 +#: ../../en/hardware/display.rst:801 ../../en/hardware/display.rst:833 +#: 44dbc9a23015460a92589f45bc24f5bd +msgid "image path or data" +msgstr "图片文件路径或已打开的图片数据。" + +#: ../../en/hardware/display.rst:736 ../../en/hardware/display.rst:770 +#: ../../en/hardware/display.rst:802 ../../en/hardware/display.rst:834 +#: ../../en/hardware/display.rst:865 e912e17f212e4e73997160730ce8716e +msgid "display x-coordinate (default 0)" +msgstr "图片显示的起始坐标 x,默认值为 0。" + +#: ../../en/hardware/display.rst:737 ../../en/hardware/display.rst:771 +#: ../../en/hardware/display.rst:803 ../../en/hardware/display.rst:835 +#: ../../en/hardware/display.rst:866 33ead5ad749c43b0a8363db4d4255a38 +msgid "display y-coordinate (default 0)" +msgstr "图片显示的起始坐标 y,默认值为 0。" + +#: ../../en/hardware/display.rst:738 ../../en/hardware/display.rst:772 +#: ../../en/hardware/display.rst:804 ../../en/hardware/display.rst:836 +#: f4a2ba9ed88a48e299ea27649cebac2e +msgid "max width to draw (default 0)" +msgstr "要绘制的宽度" + +#: ../../en/hardware/display.rst:739 ../../en/hardware/display.rst:773 +#: ../../en/hardware/display.rst:805 ../../en/hardware/display.rst:837 +#: 5a4c9eca12eb439a9a06de76728659b1 +msgid "max height to draw (default 0)" +msgstr "要绘制的高度" + +#: ../../en/hardware/display.rst:740 ../../en/hardware/display.rst:774 +#: ../../en/hardware/display.rst:806 ../../en/hardware/display.rst:838 +#: 368f705038a34f959cacb1e03899298f +msgid "x-offset in image (default 0)" +msgstr "图片中起始的偏移量 x,默认值为 0。" + +#: ../../en/hardware/display.rst:741 ../../en/hardware/display.rst:775 +#: ../../en/hardware/display.rst:807 ../../en/hardware/display.rst:839 +#: 368f705038a34f959cacb1e03899298f +msgid "y-offset in image (default 0)" +msgstr "图片中起始的偏移量 y,默认值为 0。" + +#: ../../en/hardware/display.rst:742 45647fec51574936803a8d77e8e8751f +msgid "scale horizontally (default True)" +msgstr "是否水平缩放图片,默认值分别为 True。" + +#: ../../en/hardware/display.rst:743 a9046833e9a24c4c952aaa51a6c37341 +msgid "scale vertically (default False)" +msgstr "是否垂直缩放图片,默认值分别为 False。" + +#: ../../en/hardware/display.rst:747 60d3901fe72f4d729045ea9450f2185e +msgid "|drawpng.png|" +msgstr "" + +#: ../../en/refs/hardware.display.ref:36 2eac840158024b899e0ee3a80b94c752 +msgid "drawPng.png" +msgstr "" + +#: ../../en/hardware/display.rst:755 ../../en/hardware/display.rst:787 +#: ../../en/hardware/display.rst:819 ../../en/hardware/display.rst:851 +#: ../../en/hardware/display.rst:882 ../../en/hardware/display.rst:947 +#: ../../en/hardware/display.rst:969 66c9e1eb48004e5eb0037b3ce5fd5d2b +msgid "Example:" +msgstr "示例:" + +#: ../../en/hardware/display.rst:767 31497a79ab7a4ee78a868996bd2e20ca +msgid "Draw a JPG image." +msgstr "绘制 JPG 图片。" + +#: ../../en/hardware/display.rst:779 f962bfa1e81d48df9fc5289522386c7a +msgid "|drawjpg.png|" msgstr "" -#: ../../en/hardware/display.rst:399 1e0735b2a7cf461cab5478faf3230470 -msgid "Draw a JPG image." +#: ../../en/refs/hardware.display.ref:37 d81a3bc9230247d297a8ba09103c5cfd +msgid "drawJpg.png" msgstr "" -#: ../../en/hardware/display.rst:408 db765317835c4e5dbbab843ff7ad012d +#: ../../en/hardware/display.rst:799 05b27ca3359240468fc9ab6fae8eae9d msgid "Draw a BMP image." +msgstr "绘制 BMP 图片。" + +#: ../../en/hardware/display.rst:811 80aee5fdb99849538d5b9bf46347cddc +msgid "|drawbmp.png|" +msgstr "" + +#: ../../en/refs/hardware.display.ref:38 89239652cddf492781581a83934b3509 +msgid "drawBmp.png" msgstr "" -#: ../../en/hardware/display.rst:417 53f9191c8b814e5bb1701c8cba9e2601 +#: ../../en/hardware/display.rst:831 91472a1549d346ee8132e6440bbb83d2 msgid "Draw an image." +msgstr "绘制图片。" + +#: ../../en/hardware/display.rst:843 23f81682ec0c4b7fba344f4f9db9c6d3 +msgid "|drawimage.png|" msgstr "" -#: ../../en/hardware/display.rst:426 a0194c06cdbd486db9198e2f053f2741 -msgid "Draw an image from the buffer:" +#: ../../en/refs/hardware.display.ref:39 7cec0363c4324af1ac16f44a07ba3c0d +msgid "drawImage.png" msgstr "" -#: ../../en/hardware/display.rst:436 b39f7270fcba47b3a22e9c464ff5f4da +#: ../../en/hardware/display.rst:862 ec2c52eca7264f7b809e4aa7327eda40 msgid "Draw an image from raw buffer data." -msgstr "" +msgstr "绘制缓冲区中的图片。" -#: ../../en/hardware/display.rst:438 c19d5aaa9bd4435894441c67dbc787ed -msgid "``buf`` Image buffer." -msgstr "" +#: ../../en/hardware/display.rst:864 4fa52c47c4294a7188bcbcc2ccaaff33 +msgid "image buffer" +msgstr "图像数据" -#: ../../en/hardware/display.rst:440 143de434b9be404c9e7180f316f7bfbc -msgid "``w, h`` Width and height of the image." -msgstr "" +#: ../../en/hardware/display.rst:867 f4a2ba9ed88a48e299ea27649cebac2e +msgid "image width (default 0)" +msgstr "图像宽" + +#: ../../en/hardware/display.rst:868 93c0e5cee256497f967ac52543d1cc8b +msgid "image height (default 0)" +msgstr "图像高" + +#: ../../en/hardware/display.rst:869 f4a2ba9ed88a48e299ea27649cebac2e +msgid "length of image data (default 0)" +msgstr "图像大小" + +#: ../../en/hardware/display.rst:870 3080fc5052534ae581c78ae5917b0fae +msgid "inverted display (default False)" +msgstr "是否反色显示(默认值为 False)。" -#: ../../en/hardware/display.rst:441 a362c0fbd93549a2a798b68040814f7b -msgid "``len`` Length of the image data." +#: ../../en/hardware/display.rst:874 db8ea69f5de94169a1b07a29358b6e2a +msgid "|drawrawbuf.png|" msgstr "" -#: ../../en/hardware/display.rst:442 c8a93adb99de4c0ab03be719b94bdc01 -msgid "``swap`` Whether to enable inverted display." +#: ../../en/refs/hardware.display.ref:40 0e376fd69c1f42a0b1524de0c369d2de +msgid "drawRawBuf.png" msgstr "" -#: ../../en/hardware/display.rst:446 4aa65d2a875c483b80361de5066ca48c +#: ../../en/hardware/display.rst:896 da6461317bed48b7af2b30b7d86f2011 msgid "Display a string (no formatting support)." -msgstr "" +msgstr "显示字符串(不支持格式化)。" -#: ../../en/hardware/display.rst:448 b151e79c0092448cb791bc9fd677b7f9 -msgid "``text`` Text to display." -msgstr "" +#: ../../en/hardware/display.rst:898 330c11fc086940f6bfc55608f5abeecb +msgid "text to display" +msgstr "要显示的字符串。" -#: ../../en/hardware/display.rst:453 6d89be9b5d624e4db14ed4c3dafbe561 -msgid "Display a formatted string." -msgstr "" +#: ../../en/hardware/display.rst:899 1053eb3d56a24ab29cb9cd5f0dd71eba +msgid "color in RGB888 format (default 0)" +msgstr "颜色值,RGB888 格式。" -#: ../../en/hardware/display.rst:455 a734cf86fbd5442ebb437d18d8bf1093 -msgid "``text`` Text to display with formatting." +#: ../../en/hardware/display.rst:903 59c3937a5ef647089ca583845fee2c25 +msgid "|print.png|" msgstr "" -#: ../../en/hardware/display.rst:459 07f9beaeb59240bc9c71894c8925be64 -msgid "Create a canvas." +#: ../../en/refs/hardware.display.ref:41 a01fce1c563f48a385913ff5ffc5569c +msgid "print.png" msgstr "" -#: ../../en/hardware/display.rst:461 ef728efc93ab4200b3115d0602a53053 -msgid "``w, h`` Width and height of the canvas." -msgstr "" +#: ../../en/hardware/display.rst:913 23f81682ec0c4b7fba344f4f9db9c6d3 +msgid "Display a formatted string." +msgstr "显示格式化字符串。" -#: ../../en/hardware/display.rst:462 495468b16ce54d23839cddadec74b5eb -msgid "``bpp`` Color depth. Default is -1." -msgstr "" +#: ../../en/hardware/display.rst:915 c1b397690cd04e348bb8b539a5c8cc0e +msgid "text to display with formatting" +msgstr "要显示的格式化字符串。" -#: ../../en/hardware/display.rst:463 68c065ae6f58410e87d8154ac17685da -msgid "``psram`` Whether to use PSRAM. Default is False." +#: ../../en/hardware/display.rst:919 945c13c7d239426cbcbbe9be26a8cbbf +msgid "|printf.png|" msgstr "" -#: ../../en/hardware/display.rst:465 0b5d6b29cce9420da7e1922708fc4d71 -msgid "Returns Created canvas object." +#: ../../en/refs/hardware.display.ref:42 55c723d797b742d8a67d05156fea7d15 +msgid "printf.png" msgstr "" -#: ../../en/hardware/display.rst:477 b0f6baf4c8e74b5f81460c1d46c5a660 -msgid "Start writing to the display." -msgstr "" +#: ../../en/hardware/display.rst:929 aad4a5440dc34aeab1f73366ad3aa602 +msgid "Create a canvas." +msgstr "创建一个画布。" -#: ../../en/hardware/display.rst:481 4ddd560d23a94a1fb468c36211b42145 -msgid "End writing to the display." -msgstr "" +#: ../../en/hardware/display.rst:931 a7177f73556e41039ed39fbd9369b60b +msgid "canvas width" +msgstr "画布宽" -#~ msgid "K5.Lcd.FONTS.ASCII7" -#~ msgstr "" +#: ../../en/hardware/display.rst:932 d81d44b88ee543dcbaf54d85d722d9c8 +msgid "canvas height" +msgstr "画布高" -#~ msgid "K5.Lcd.FONTS.DejaVu9" -#~ msgstr "" +#: ../../en/hardware/display.rst:933 c24560527d6a4858983518a8cdaba006 +msgid "color depth (default -1)" +msgstr "颜色深度" -#~ msgid "K5.Lcd.FONTS.DejaVu12" -#~ msgstr "" +#: ../../en/hardware/display.rst:934 882a81ffb06f4ffab26314d270fe0253 +msgid "use PSRAM (default False)" +msgstr "使用 PSRAM" -#~ msgid "K5.Lcd.FONTS.DejaVu18" -#~ msgstr "" +#: ../../en/hardware/display.rst 72c3095b146646e68ce17650b953cac5 +msgid "Returns" +msgstr "" -#~ msgid "K5.Lcd.FONTS.DejaVu24" -#~ msgstr "" +#: ../../en/hardware/display.rst:935 6dde2d148d6742f79bb35c0ca05189d4 +msgid "created canvas object" +msgstr "创建画布对象。" -#~ msgid "K5.Lcd.FONTS.DejaVu40" -#~ msgstr "" +#: ../../en/hardware/display.rst:939 0cfafa1a6e4843b891b0a4f1530e9933 +msgid "|newcanvas.png|" +msgstr "" -#~ msgid "K5.Lcd.FONTS.DejaVu56" -#~ msgstr "" +#: ../../en/refs/hardware.display.ref:43 30c24844685044778c2ec2b8fa3af1fb +msgid "newCanvas.png" +msgstr "" -#~ msgid "K5.Lcd.FONTS.DejaVu72" -#~ msgstr "" +#: ../../en/hardware/display.rst:957 5f692055f8314d02b8548192ab21d060 +msgid "Start writing to the display." +msgstr "开始向显示屏写入内容。" -#~ msgid "K5.Lcd.FONTS.EFontCN24" -#~ msgstr "" +#: ../../en/hardware/display.rst:961 41204217b44b44779ba15a0cd966cfdf +msgid "|startwrite.png|" +msgstr "" -#~ msgid "K5.Lcd.FONTS.EFontJA24" -#~ msgstr "" +#: ../../en/refs/hardware.display.ref:44 a0804da3243e44908b07a20fa4d84fee +msgid "startWrite.png" +msgstr "" -#~ msgid "K5.Lcd.FONTS.EFontKR24" -#~ msgstr "" +#: ../../en/hardware/display.rst:979 a40c68b06a9a41e688a071b2bc1aaf6a +msgid "End writing to the display." +msgstr "结束显示屏写入。" + +#: ../../en/hardware/display.rst:983 23f81682ec0c4b7fba344f4f9db9c6d3 +msgid "|endwrite.png|" +msgstr "" + +#: ../../en/refs/hardware.display.ref:45 30e75656bcc44d1c9c625090414c6d19 +msgid "endWrite.png" +msgstr "" -#~ msgid "`` y`` The y-coordinate of the circle center. Default is -1." +#~ msgid "This example shows how to display an image on the screen." #~ msgstr "" diff --git a/docs/locales/zh_CN/LC_MESSAGES/m5ui/index.po b/docs/locales/zh_CN/LC_MESSAGES/m5ui/index.po index f7271690..2726b485 100644 --- a/docs/locales/zh_CN/LC_MESSAGES/m5ui/index.po +++ b/docs/locales/zh_CN/LC_MESSAGES/m5ui/index.po @@ -9,7 +9,7 @@ msgid "" msgstr "" "Project-Id-Version: UIFlow2 Programming Guide \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-06-26 15:06+0800\n" +"POT-Creation-Date: 2025-09-05 00:55+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language: zh_CN\n" @@ -20,49 +20,164 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.16.0\n" -#: ../../en/m5ui/index.rst:2 7cf56d7cd22e4f0d9fc5892bc9653652 +#: ../../en/m5ui/index.rst:2 7d7435bc07204952982bc8d17c08447f msgid "M5UI" msgstr "" -#: ../../en/m5ui/index.rst:7 1f54d93f6547485685ad7c4ee3f758e9 +#: ../../en/m5ui/index.rst:7 8f5899301791406d86e42521e8b7556f msgid "" "M5UI is a UI library based on LVGL v9.3. It provides a set of widgets and" " functions to create user interfaces for M5Stack devices." msgstr "M5UI 是一个基于 LVGL v9.3 的 UI 库。它提供了一组用于创建 M5Stack 设备用户界面的小部件和函数。" -#: ../../en/m5ui/index.rst:9 f0ff465507ef443dbcc943023f445901 +#: ../../en/m5ui/index.rst:9 c60047038d4a4476ba97dd7705c0a410 msgid "" "It has been adapted for M5Stack devices and you only need to call " "``m5ui.init()`` to start using it." msgstr "它已适用于 M5Stack 设备,您只需调用 ``m5ui.init()`` 即可开始使用它。" -#: ../../en/m5ui/index.rst:13 9f83572a079f4c05bb00b4f02644e266 +#: ../../en/m5ui/index.rst:12 484602ca23dd42f1944e40cc327269ca +msgid "M5 Series Display Libraries" +msgstr "M5 系列显示库说明" + +#: ../../en/m5ui/index.rst:15 36c7d6ef76a84cb69ca0064067e2aae9 +msgid "1. Display" +msgstr "" + +#: ../../en/m5ui/index.rst:16 deaa108368b64b269c5da36b7a772f03 +msgid "" +"A low-level graphics library providing basic screen drawing, text, lines," +" and color management." +msgstr "底层图形库,提供屏幕绘制、文字、线条、颜色管理等基础功能。" + +#: ../../en/m5ui/index.rst:17 382922b24d004cae8946153a9f3c7fe3 +msgid "" +"Can be used independently, suitable for scenarios that only require " +"drawing graphics or text." +msgstr "可独立使用,适合只需要绘制图形或文字的场景。" + +#: ../../en/m5ui/index.rst:20 014a99bdfe9b4377876de176daf5acf6 +msgid "2. Widgets" +msgstr "" + +#: ../../en/m5ui/index.rst:21 db2dbdcdddb9456ea4232801d9c6dc3c +msgid "" +"A basic UI widget library providing labels, image displays, and other UI " +"controls." +msgstr "基础控件库,提供标签、图片显示等 UI 控件。" + +#: ../../en/m5ui/index.rst:22 d6141fbdadd747fb804746fad082b3f7 +msgid "Built on top of M5GFX." +msgstr "底层依赖 M5GFX。" + +#: ../../en/m5ui/index.rst:23 c3d73de7f57b45a6b6bdbdc1c1fe91e1 +msgid "Suitable for simple interactive UI elements." +msgstr "适合需要简单交互控件的界面。" + +#: ../../en/m5ui/index.rst:26 2a26706a264d466580297e52f600e91e +msgid "3. M5UI" +msgstr "" + +#: ../../en/m5ui/index.rst:27 bfa4f9b122494e6691ae282085e7a728 +msgid "A high-level UI framework based on LVGL." +msgstr "高层 UI 框架,基于 LVGL 封装。" + +#: ../../en/m5ui/index.rst:28 105acb23326e4ff99fe0d463d47abd73 +msgid "" +"Provides page management, multi-widget layouts, and unified event " +"handling." +msgstr "提供页面管理、多控件布局和统一事件处理。" + +#: ../../en/m5ui/index.rst:31 5462ecccf0a743cbbe52fe1b0edc3541 +msgid "Usage Tips" +msgstr "使用提示" + +#: ../../en/m5ui/index.rst:32 66c7248770694fa7baeba86c0bdddf9e +msgid "" +"⚠️ Do not mix M5GFX, M5Widgets, and M5UI simultaneously, as it may cause " +"rendering issues or event conflicts." +msgstr "⚠️ 不建议同时混用 M5GFX、M5Widgets、M5UI,可能导致渲染异常或事件冲突。" + +#: ../../en/m5ui/index.rst:33 1cabe9cb1184401dbb7dd28cbbbe8b59 +msgid "For graphics-only drawing → use M5GFX." +msgstr "单独绘图 → 使用 M5GFX。" + +#: ../../en/m5ui/index.rst:34 c6490d5c3681467c9f858ed733f35708 +msgid "For simple interactive widgets → use M5Widgets." +msgstr "简单控件交互 → 使用 M5Widgets。" + +#: ../../en/m5ui/index.rst:35 8073036d0a314ceebfaa9c5633b1022d +msgid "For multi-page UI → use M5UI." +msgstr "多页面 UI → 使用 M5UI。" + +#: ../../en/m5ui/index.rst:39 96f377de8991490fb90f6671a92dbccb msgid "Functions" msgstr "" -#: ../../en/m5ui/index.rst:17 c4264d75fb94489e9097c8ffba561275 +#: ../../en/m5ui/index.rst:43 9812232ebd594d2aa3ec65fdf611a159 msgid "" "Initialize the M5UI library. This function must be called before using " "any other M5UI functions." msgstr "初始化 M5UI 库。使用任何其他 M5UI 函数之前必须调用此函数" -#: ../../en/m5ui/index.rst 1d8bffeebb3847e3890345a8514b8880 -#: 5997afce9da645fbb58bd18819ff4269 +#: ../../en/m5ui/index.rst e4264090847b4654a40083ad31895c3e msgid "Returns" msgstr "" -#: ../../en/m5ui/index.rst:19 ../../en/m5ui/index.rst:26 -#: 3efafbfbb7e04b799b65b12c8861fb5a 422bba168c324167b20ff975239d9c1a +#: ../../en/m5ui/index.rst:45 ../../en/m5ui/index.rst:52 +#: c57ce1f22ea84475b48836b193f29569 msgid "None" msgstr "" -#: ../../en/m5ui/index.rst:24 9fca89b6e5bf47c1966b794858aa4abd +#: ../../en/m5ui/index.rst:50 94cec5f5c077430bac992df8c3396179 msgid "" "Deinitialize the M5UI library. This function should be called when you no" " longer need to use M5UI." msgstr "取消初始化 M5UI 库。当您不再需要使用 M5UI 时,应调用此函数。" -#: ../../en/m5ui/index.rst:30 2101ab814a3146218b046b976e577481 +#: ../../en/m5ui/index.rst:56 a62583117fe2465c90f3174667c96fa2 msgid "Classes" msgstr "" +#~ msgid "M5 系列显示库说明" +#~ msgstr "" + +#~ msgid "底层图形库,提供屏幕绘制、文字、线条、颜色管理等基础功能。" +#~ msgstr "" + +#~ msgid "可独立使用,适合只需要绘制图形或文字的场景。" +#~ msgstr "" + +#~ msgid "2. M5Widgets" +#~ msgstr "" + +#~ msgid "基础控件库,提供标签、图片显示等 UI 控件。" +#~ msgstr "" + +#~ msgid "底层依赖 M5GFX。" +#~ msgstr "" + +#~ msgid "适合需要简单交互控件的界面。" +#~ msgstr "" + +#~ msgid "高层 UI 框架,基于 LVGL 封装。" +#~ msgstr "" + +#~ msgid "提供页面管理、多控件布局和统一事件处理。" +#~ msgstr "" + +#~ msgid "使用提示" +#~ msgstr "" + +#~ msgid "⚠️ 不建议同时混用 M5GFX、M5Widgets、M5UI,可能导致渲染异常或事件冲突。" +#~ msgstr "" + +#~ msgid "单独绘图 → 使用 M5GFX。" +#~ msgstr "" + +#~ msgid "简单控件交互 → 使用 M5Widgets。" +#~ msgstr "" + +#~ msgid "多页面 UI → 使用 M5UI。" +#~ msgstr "" + diff --git a/docs/locales/zh_CN/LC_MESSAGES/widgets/index.po b/docs/locales/zh_CN/LC_MESSAGES/widgets/index.po index 46748ba9..41850b3c 100644 --- a/docs/locales/zh_CN/LC_MESSAGES/widgets/index.po +++ b/docs/locales/zh_CN/LC_MESSAGES/widgets/index.po @@ -9,7 +9,7 @@ msgid "" msgstr "" "Project-Id-Version: UIFlow2 Programming Guide \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-01-16 15:35+0800\n" +"POT-Creation-Date: 2025-09-05 00:55+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language: zh_CN\n" @@ -20,99 +20,172 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.16.0\n" -#: ../../en/widgets/index.rst:2 4186b000a4f44b309bd23218f94b7ef4 +#: ../../en/widgets/index.rst:2 c422be4b225a42dbb622bc4af828960b msgid ":mod:`Widgets` --- A basic UI library" +msgstr ":mod:`Widgets` --- 基础 UI 库" + +#: ../../en/widgets/index.rst:10 5b69130a1c1641e1a2f1888d2d2f7406 +msgid "M5 Series Display Libraries" +msgstr "M5 系列显示库说明" + +#: ../../en/widgets/index.rst:13 1194bbb7dbc24b6ea1a4803519f3147c +msgid "1. Display" msgstr "" -#: ../../en/widgets/index.rst:9 021edf90ab7a4781bac939e2e4fc840f -msgid "Micropython Example:" +#: ../../en/widgets/index.rst:14 c366ea5653cb4448ba7712c0a343ae73 +msgid "" +"A low-level graphics library providing basic screen drawing, text, lines," +" and color management." +msgstr "底层图形库,提供屏幕绘制、文字、线条、颜色管理等基础功能。" + +#: ../../en/widgets/index.rst:15 5af9fddf85384b36ac624349dc511b3e +msgid "" +"Can be used independently, suitable for scenarios that only require " +"drawing graphics or text." +msgstr "可独立使用,适合只需要绘制图形或文字的场景。" + +#: ../../en/widgets/index.rst:18 ef160a506adc4a20bc8044179b754f84 +msgid "2. M5Widgets" msgstr "" -#: ../../en/widgets/index.rst:16 ec9dcaa263904387b15600f0e96a58d8 -msgid "UIFLOW2 Example:" +#: ../../en/widgets/index.rst:19 08e6ae4be55c447da6cb5d6fbb372523 +msgid "" +"A basic UI widget library providing labels, image displays, and other UI " +"controls." +msgstr "基础控件库,提供标签、图片显示等 UI 控件。" + +#: ../../en/widgets/index.rst:20 1c976a3fff654a039fe1daf2908ba9b6 +msgid "Built on top of M5GFX." +msgstr "底层依赖 M5GFX。" + +#: ../../en/widgets/index.rst:21 51b52ac4af694944a20c02e3e3cad738 +msgid "Suitable for simple interactive UI elements." +msgstr "适合需要简单交互控件的界面。" + +#: ../../en/widgets/index.rst:24 e5171bb5996145b8a7e9daeeecb4260d +msgid "3. M5UI" msgstr "" -#: ../../en/widgets/index.rst:18 492cd82d4940403b9b008ac88eb7246f +#: ../../en/widgets/index.rst:25 11b4f35350154c17a0a2c9034b8b2d4d +msgid "A high-level UI framework based on LVGL." +msgstr "高层 UI 框架,基于 LVGL 封装。" + +#: ../../en/widgets/index.rst:26 6cd2fe0e169a401e81a1ed0d0aeaae43 +msgid "" +"Provides page management, multi-widget layouts, and unified event " +"handling." +msgstr "提供页面管理、多控件布局和统一事件处理。" + +#: ../../en/widgets/index.rst:29 c431e86416664e668b1806d7dc9419ca +msgid "Usage Tips" +msgstr "使用提示" + +#: ../../en/widgets/index.rst:30 e51f41baf9054c2686a2d693c6d10458 +msgid "" +"⚠️ Do not mix M5GFX, M5Widgets, and M5UI simultaneously, as it may cause " +"rendering issues or event conflicts." +msgstr "⚠️ 不建议同时混用 M5GFX、M5Widgets、M5UI,可能导致渲染异常或事件冲突。" + +#: ../../en/widgets/index.rst:31 d0f69ebd374d40db9f8ffe40ba08b8fe +msgid "For graphics-only drawing → use M5GFX." +msgstr "单独绘图 → 使用 M5GFX。" + +#: ../../en/widgets/index.rst:32 a5feddda10094b1ca1e8a088fffc5b77 +msgid "For simple interactive widgets → use M5Widgets." +msgstr "简单控件交互 → 使用 M5Widgets。" + +#: ../../en/widgets/index.rst:33 e36398314fcc4adb8b416919192db2cb +msgid "For multi-page UI → use M5UI." +msgstr "多页面 UI → 使用 M5UI。" + +#: ../../en/widgets/index.rst:35 d97969d699ff4bc286645ebf12c5deeb +msgid "Micropython Example:" +msgstr "MicroPython 应用示例" + +#: ../../en/widgets/index.rst:42 1a912f61f7d04c9bb73372b5d45fa33a +msgid "UIFLOW2 Example:" +msgstr "UiFlow2 应用示例" + +#: ../../en/widgets/index.rst:44 c6e84d4c10cd4818bf39db9ccb2ee08e msgid "|example.png|" msgstr "" -#: ../../en/refs/widgets.ref:1 0fbe71cc6dfb454fa9d25a0c28c260b4 +#: ../../en/refs/widgets.ref:1 34a71c42501b4bfda8f32304750d949e msgid "example.png" msgstr "" -#: ../../en/widgets/index.rst:22 6fe55f1fec8f45b690962e91c3c858a2 +#: ../../en/widgets/index.rst:48 3d9578eb767a4aeda9b09d20590abfbc msgid "|cores3_widgets_example.m5f2|" msgstr "" -#: ../../en/widgets/index.rst:25 210dbcbad77b48bb825e69b35b411c7d +#: ../../en/widgets/index.rst:51 b857983e661c4217aab0932bc47982a6 msgid "Screen functions" msgstr "" -#: ../../en/widgets/index.rst:29 a8d55305f85a4dddb88db3a9b3721685 +#: ../../en/widgets/index.rst:55 af0ca7284ee142e183750c679052e2d7 msgid "Set the backlight of the monitor。``brightness`` ranges from 0 to 255." -msgstr "" +msgstr "设置显示器的背光亮度。参数 brightness 的取值范围是 0 到 255。" -#: ../../en/widgets/index.rst:31 ../../en/widgets/index.rst:39 -#: ../../en/widgets/index.rst:54 3eb16b5d7aef458d91130492d9be395f -#: 6b45ec589c3040b4bcb8d42801dd0936 b40f6b449b464ce9a9a710fe7b8d23d8 +#: ../../en/widgets/index.rst:57 ../../en/widgets/index.rst:65 +#: ../../en/widgets/index.rst:80 e0efabe1cb674e608d123da9097c8e12 msgid "UIFLOW2:" -msgstr "" +msgstr "UiFlow2:" -#: ../../en/widgets/index.rst:33 6ff62e7c40854516813d99d8e4f3ad53 +#: ../../en/widgets/index.rst:59 cf8db21a3be84fe6a9c69e2e921ad0ac msgid "|setBrightness.png|" msgstr "" -#: ../../en/refs/widgets.ref:3 02e52c35c9944b65bfbd13dc79dc996b +#: ../../en/refs/widgets.ref:3 664d1dc186af46998978d41a8275e736 msgid "setBrightness.png" msgstr "" -#: ../../en/widgets/index.rst:37 adc355e448234646ae8d715fb5b0c6dc +#: ../../en/widgets/index.rst:63 2c6b5e2a4896415e8ef4087f85a11593 msgid "" "Set the background color of the monitor. ``color`` accepts the color code" " of RGB888." -msgstr "" +msgstr "设置显示器的背景颜色。参数 color 接受 RGB888 的颜色代码。" -#: ../../en/widgets/index.rst:41 f7b65a43b979464780a572494ffdd62e +#: ../../en/widgets/index.rst:67 0de2efc385ea4fca870189ed31d65a6f msgid "|fillScreen.png|" msgstr "" -#: ../../en/refs/widgets.ref:5 e221699e1d344aa29c6e7a5b5726188b +#: ../../en/refs/widgets.ref:5 632b61e227134f81b67d267b72f35abc msgid "fillScreen.png" msgstr "" -#: ../../en/widgets/index.rst:45 81d74f3ccd304672b4e31d8cdd16fb79 +#: ../../en/widgets/index.rst:71 87f39a6801524da7abe378e377bf7601 msgid "Set the rotation Angle of the display." -msgstr "" +msgstr "设置显示器的旋转角度。" -#: ../../en/widgets/index.rst:47 a42a6c26a42b4cb282f786606794159d +#: ../../en/widgets/index.rst:73 638b01ea821b4c729b73683754f86714 msgid "The ``rotation`` parameter only accepts the following values:" -msgstr "" +msgstr "``rotation`` 参数只接受以下值:" -#: ../../en/widgets/index.rst:49 40feb84e30b94e1194fd5921002c3658 +#: ../../en/widgets/index.rst:75 2b250874e87e4b3c852aff7920bcbd07 msgid "``0``: Portrait (0°C)" -msgstr "" +msgstr "``0``: 竖屏 (0°C)" -#: ../../en/widgets/index.rst:50 a74a64296a804d28b319be0b9347a4af +#: ../../en/widgets/index.rst:76 d1707a4865ca4b8d92dfca0da1f552ee msgid "``1``: Landscape (90°C)" -msgstr "" +msgstr "``1``: 横屏 (90°C)" -#: ../../en/widgets/index.rst:51 fe910c0dbada43038353e54bc3694837 +#: ../../en/widgets/index.rst:77 22545305c9424d4cb7d7b62ed62387c1 msgid "``2``: Inverse Portrait (180°C)" -msgstr "" +msgstr "``2``: 反向竖屏 (180°C)" -#: ../../en/widgets/index.rst:52 217d7b8beb244bb3a50b3f56b53c9b6a +#: ../../en/widgets/index.rst:78 77ffffa9ad1241bda84d274eef07311e msgid "``3``: Inverse Landscape (270°C)" -msgstr "" +msgstr "``3``: 反向横屏 (270°C)" -#: ../../en/widgets/index.rst:56 18b36fe2be0e4e8aa05a9c89a217f2ae +#: ../../en/widgets/index.rst:82 34d0f17d8bc34bc7a666eea18aac942f msgid "|setRotation.png|" msgstr "" -#: ../../en/refs/widgets.ref:7 7023b5bb83d6422cb0f73acc8352d685 +#: ../../en/refs/widgets.ref:7 6c473ab5b5bd41078b138d1b54b0b3b3 msgid "setRotation.png" msgstr "" -#: ../../en/widgets/index.rst:59 2c5b56d6bf644eb8915db483d76cdca6 +#: ../../en/widgets/index.rst:85 8c672047b28140f0943dc3e7335ce3bd msgid "Classes" msgstr "" diff --git a/examples/hardware/display/cores3_draw_test_example.m5f2 b/examples/hardware/display/cores3_draw_test_example.m5f2 new file mode 100644 index 00000000..c86dc757 --- /dev/null +++ b/examples/hardware/display/cores3_draw_test_example.m5f2 @@ -0,0 +1 @@ +{"version":"V2.0","versionNumber":"V2.3.5","type":"cores3","components":[{"name":"screen","type":"screen","layer":0,"screenId":"builtin","screenName":"","id":"__cores3_screen","createTime":1756981852224,"x":0,"y":0,"width":320,"height":240,"backgroundColor":"#222222","size":0,"isSelected":true}],"resources":[{"hardware":["hardware_button","hardware_pin_button","imu","speaker","touch","als","mic","sdcard"]}],"units":[],"hats":[],"bases":[],"i2cs":[],"blockly":"truehello M5rotation: hello M5color depth: hello M5hello M5w: h:1rgb000rgb00255rgb0002003hello M5hello M5palette#6600ccdefault.png00Hello220401001draw308020palette#3333fffill808020palette#009900draw601405030rgb02550fill601403020rgb25525501151011560rgb25500draw125104030rgb25500fill125504030rgb02550draw135150110190160190rgb02550fill145150170190190150rgb00255draw101804045090rgb2552550fill201904045090rgb0255255true","screen":[{"simulationName":"Built-in","type":"builtin","width":320,"height":240,"scale":0.77,"screenName":"","blockId":"","screenColorType":0,"rotation":1,"id":"builtin","createTime":1756980148672}],"logicWhenNum":0,"customList":[]} \ No newline at end of file diff --git a/examples/hardware/display/cores3_draw_test_example.py b/examples/hardware/display/cores3_draw_test_example.py new file mode 100644 index 00000000..afa27660 --- /dev/null +++ b/examples/hardware/display/cores3_draw_test_example.py @@ -0,0 +1,52 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT +import os, sys, io +import M5 +from M5 import * + + +def setup(): + M5.begin() + Widgets.setRotation(1) + Widgets.fillScreen(0x222222) + print((str("rotation: ") + str((M5.Lcd.getRotation())))) + print((str("color depth: ") + str((M5.Lcd.getColorDepth())))) + print((str((str("w: ") + str((M5.Lcd.width())))) + str((str("h:") + str((M5.Lcd.height())))))) + M5.Lcd.setRotation(1) + M5.Lcd.clear(0x000000) + M5.Lcd.setTextColor(0x0000FF, 0x000000) + M5.Lcd.setCursor(200, 3) + M5.Lcd.printf("hello M5") + M5.Lcd.print("hello M5", 0x6600CC) + M5.Lcd.drawImage("/flash/res/img/default.png", 0, 0) + M5.Lcd.drawQR("Hello", 220, 40, 100, 1) + M5.Lcd.drawCircle(30, 80, 20, 0x3333FF) + M5.Lcd.fillCircle(80, 80, 20, 0x009900) + M5.Lcd.drawEllipse(60, 140, 50, 30, 0x00FF00) + M5.Lcd.fillEllipse(60, 140, 30, 20, 0xFFFF00) + M5.Lcd.drawLine(115, 10, 115, 60, 0xFF0000) + M5.Lcd.drawRect(125, 10, 40, 30, 0xFF0000) + M5.Lcd.fillRect(125, 50, 40, 30, 0x00FF00) + M5.Lcd.drawTriangle(135, 150, 110, 190, 160, 190, 0x00FF00) + M5.Lcd.fillTriangle(145, 150, 170, 190, 190, 150, 0x0000FF) + M5.Lcd.drawArc(10, 180, 40, 45, 0, 90, 0xFFFF00) + M5.Lcd.fillArc(20, 190, 40, 45, 0, 90, 0x00FFFF) + + +def loop(): + M5.update() + + +if __name__ == "__main__": + try: + setup() + while True: + loop() + except (Exception, KeyboardInterrupt) as e: + try: + from utility import print_error_msg + + print_error_msg(e) + except ImportError: + print("please update to latest firmware") From 12b9c6261e30e27e60d1c24953ba6cd69e320c5f Mon Sep 17 00:00:00 2001 From: hlym123 Date: Fri, 5 Sep 2025 15:50:06 +0800 Subject: [PATCH 242/322] cmodules: Add omv/image find_qrcode support. Signed-off-by: hlym123 --- docs/en/advanced/image.rst | 520 ++++++++++--- docs/en/refs/advanced.image.ref | 23 + .../zh_CN/LC_MESSAGES/advanced/image.po | 702 +++++++++++++++--- .../cores3_image_find_qrcode_example.m5f2 | 1 + .../image/cores3_image_find_qrcode_example.py | 65 ++ m5stack/CMakeListsDefault.cmake | 2 + m5stack/CMakeListsLvgl.cmake | 2 +- m5stack/cmodules/omv/micropython.cmake | 2 + m5stack/cmodules/omv/modules/py_image.c | 330 +++++++- m5stack/cmodules/omv/omv_atoms3r_cam.cmake | 2 + m5stack/main/idf_component.yml | 1 + 11 files changed, 1401 insertions(+), 249 deletions(-) create mode 100644 examples/advanced/image/cores3_image_find_qrcode_example.m5f2 create mode 100644 examples/advanced/image/cores3_image_find_qrcode_example.py diff --git a/docs/en/advanced/image.rst b/docs/en/advanced/image.rst index 4547c920..353c4a99 100644 --- a/docs/en/advanced/image.rst +++ b/docs/en/advanced/image.rst @@ -1,192 +1,502 @@ image -=============================== +===== .. include:: ../refs/advanced.image.ref .. note:: This module is only applicable to the CoreS3 Controller -.. module:: image - :synopsis: machine vision -Micropython Example --------------------------------- +UiFlow2 Example +--------------- draw test -++++++++++++++++++++++++++++ ++++++++++ + +Open the |cores3_example_draw_test.m5f2| project in UiFlow2. + +This example captures an image from the camera and demonstrates how to draw text and basic shapes on it. + +UiFlow2 Code Block: + + |image_draw_example.png| + +Example output: + + None + +find qrcode ++++++++++++ + +Open the |cores3_image_find_qrcode_example.m5f2| project in UiFlow2. + +This example captures images from the camera, detects QR codes, and draws their bounding boxes and decoded payload text on the LCD. + +UiFlow2 Code Block: + + |cores3_image_find_qrcode_example.png| + +Example output: + + None + +MicroPython Example +------------------- + +draw test ++++++++++ .. literalinclude:: ../../../examples/advanced/image/cores3_example_draw_test.py :language: python :linenos: -UIFlow2.0 Example --------------------------------- +find qrcode ++++++++++++ -draw test -++++++++++++++++++++++++++++ + .. literalinclude:: ../../../examples/advanced/image/cores3_image_find_qrcode_example.py + :language: python + :linenos: - |image_draw_example.png| +**API** +------- -.. only:: builder_html +.. class:: image.Image + + The image.Image object is returned by `camera.snapshot()`. - |cores3_example_draw_test.m5f2| + .. method:: width() -class image.Image --------------------------------- -The line object is returned by `camera.snapshot()`. + Returns the image width in pixels. -Basic Methods --------------------------------- + :returns width: image width. + :return type: int -.. method:: width() -> int + UiFlow2 Code Block: - Returns the image width in pixels. - - UIFlow2.0 + |width.png| - |width.png| + MicroPython Code Block: -.. method:: height() -> int + .. code-block:: python - Returns the image height in pixels. + width() - UIFlow2.0 + .. method:: height() - |height.png| + Returns the image height in pixels. -.. method:: format() -> int + :returns height: image height. + :return type: int - Returns the image format + UiFlow2 Code Block: - UIFlow2.0 + |height.png| - |format.png| + MicroPython Code Block: -.. method:: size() -> int + .. code-block:: python - Returns the image size in bytes. + height() - UIFlow2.0 + .. method:: format() - |size.png| + Returns the image format. -.. method:: bytearray() -> bytearray + Returns image.GRAYSCALE for grayscale images, image.RGB565 for RGB565 images, image.BAYER for bayer pattern images, and image.JPEG for JPEG images. - Returns a bytearray object that points to the image data for byte-level read/write access. + :returns format: image format. + :return type: int - UIFlow2.0 + UiFlow2 Code Block: - |bytearray.png| + |format.png| + MicroPython Code Block: -Drawing Methods --------------------------------- - -.. method:: draw_line(x0:int, y0:int, x1:int, y1:int, color:Optional[int,Tuple[int,int,int]]=None, thickness=1) -> Image + .. code-block:: python + + format() + + .. method:: size() + + Returns the image size in bytes. + + :returns size: image size in bytes. + :return type: int + + UiFlow2 Code Block: + + |size.png| + + MicroPython Code Block: + + .. code-block:: python + + size() + + .. method:: bytearray() + + Returns a bytearray object that points to the image data for byte-level read/write access. + + :returns data: image data buffer. + :return type: bytearray + + UiFlow2 Code Block: + + |bytearray.png| + + MicroPython Code Block: + + .. code-block:: python + + bytearray() + + .. method:: draw_line(x0, y0, x1, y1, color, thickness) + + Draws a line from (x0, y0) to (x1, y1) on the image. You may either + pass x0, y0, x1, y1 separately or as a tuple (x0, y0, x1, y1). + + :param int x0: start x coordinate. + :param int y0: start y coordinate. + :param int x1: end x coordinate. + :param int y1: end y coordinate. + :param color: RGB888 tuple, grayscale value (0-255), or RGB565 value. Defaults to white. + :param int thickness: line thickness in pixels. Defaults to 1. + + :returns self: the image object for method chaining. + :return type: Image + + UiFlow2 Code Block: + + |draw_line.png| + + MicroPython Code Block: + + .. code-block:: python + + img.draw_line(10, 10, 100, 100, color=(255,0,0), thickness=2) + + .. method:: draw_rectangle(x, y, w, h, color, thickness, fill) + + Draws a rectangle on the image. You may either pass x, y, w, h separately + or as a tuple (x, y, w, h). + + :param int x: top-left x coordinate. + :param int y: top-left y coordinate. + :param int w: rectangle width. + :param int h: rectangle height. + :param color: RGB888 tuple, grayscale value (0-255), or RGB565 value. Defaults to white. + :param int thickness: border thickness in pixels. Defaults to 1. + :param bool fill: set True to fill the rectangle. Defaults to False. + + :returns self: the image object for method chaining. + :return type: Image + + UiFlow2 Code Block: + + |draw_rectangle.png| + + MicroPython Code Block: + + .. code-block:: python + + img.draw_rectangle(20, 20, 80, 60, color=(0,255,0), thickness=2, fill=True) + + .. method:: draw_circle(x, y, radius, color, thickness, fill) + + Draws a circle on the image. You may either pass x, y, radius separately or + as a tuple (x, y, radius). + + :param int x: circle center x coordinate. + :param int y: circle center y coordinate. + :param int radius: circle radius. + :param color: RGB888 tuple, grayscale value (0-255), or RGB565 value. Defaults to white. + :param int thickness: border thickness in pixels. Defaults to 1. + :param bool fill: set True to fill the circle. Defaults to False. + + :returns self: the image object for method chaining. + :return type: Image + + UiFlow2 Code Block: + + |draw_circle.png| + + MicroPython Code Block: + + .. code-block:: python + + img.draw_circle(50, 50, 30, color=(0,0,255), thickness=3, fill=False) + + .. method:: draw_string(x, y, text, color, scale) + + Draws 8x16 text starting at location (x, y) in the image. You may either pass + x, y separately or as a tuple (x, y). + + :param int x: text start x coordinate. + :param int y: text start y coordinate. + :param str text: text to draw. Supports ``\n``, ``\r``, ``\r\n`` line breaks. + :param color: RGB888 tuple, grayscale value (0-255), or RGB565 value. Defaults to white. + :param scale: scale factor to resize text. Integer or float > 0. Defaults to 1. + + :returns self: the image object for method chaining. + :return type: Image + + UiFlow2 Code Block: + + |draw_string.png| + + MicroPython Code Block: + + .. code-block:: python + + img.draw_string(10, 10, "Hello", color=(255,255,0), scale=2) + + .. method:: find_qrcodes() - Draws a line from (x0, y0) to (x1, y1) on the image. You may either - pass x0, y0, x1, y1 separately or as a tuple (x0, y0, x1, y1). + Finds all QR codes returns a list of ``image.qrcode`` objects. + Please see the image.qrcode object for more details. - - ``color`` is an RGB888 tuple for Grayscale or RGB565 images. Defaults to - white. However, you may also pass the underlying pixel value (0-255) for - grayscale images or a RGB565 value for RGB565 images. + :returns qrcodes: list of detected QR codes. + :return type: List[image.qrcode] - - ``thickness`` controls how thick the line is in pixels. + UiFlow2 Code Block: - Returns the image object so you can call another method using ``.`` notation. + |find_qrcodes.png| - UIFlow2.0 + MicroPython Code Block: - |draw_line.png| + .. code-block:: python -.. method:: draw_rectangle(x:int, y:int, w:int, h:int, color:Optional[int,Tuple[int,int,int]]=None, thickness=1, fill=False) -> Image + qrcodes = img.find_qrcodes() - Draws a rectangle on the image. You may either pass x, y, w, h separately - or as a tuple (x, y, w, h). +.. class:: image.qrcode - - ``color`` is an RGB888 tuple for Grayscale or RGB565 images. Defaults to - white. However, you may also pass the underlying pixel value (0-255) for - grayscale images or a RGB565 value for RGB565 images. + Please call ``Image.find_qrcodes()`` to create this object. - - ``thickness`` controls how thick the lines are in pixels. + .. method:: corners() - - ``fill`` set to True to fill the rectangle. + Get the 4 corners of the QR code in clockwise order starting from the top-left. - Returns the image object so you can call another method using ``.`` notation. + :returns corners: list of 4 (x, y) tuples. + :return type: List[Tuple[int, int]] - UIFlow2.0 + UiFlow2 Code Block: - |draw_rectangle.png| + |corners.png| -.. method:: draw_circle(x:int, y:int, radius:int, color:Optional[int,Tuple[int,int,int]]=None, thickness=1, fill=False) -> Image + MicroPython Code Block: - Draws a circle on the image. You may either pass x, y, radius separately or - as a tuple (x, y, radius). + .. code-block:: python - - ``color`` is an RGB888 tuple for Grayscale or RGB565 images. Defaults to - white. However, you may also pass the underlying pixel value (0-255) for - grayscale images or a RGB565 value for RGB565 images. + q.corners() - - ``thickness`` controls how thick the edges are in pixels. + .. method:: rect() - - ``fill`` set to True to fill the circle. + Get the bounding box of the QR code. - Returns the image object so you can call another method using ``.`` notation. + :returns rect: (x, y, w, h) tuple. + :return type: Tuple[int, int, int, int] - UIFlow2.0 + UiFlow2 Code Block: - |draw_circle.png| - - -.. method:: draw_string(x:int, y:int, text:str, color:Optional[int,Tuple[int,int,int]]=None, scale=1) -> Image + |rect.png| - Draws 8x16 text starting at location (x, y) in the image. You may either pass - x, y separately or as a tuple (x, y). + MicroPython Code Block: - - ``text`` is a string to write to the image. ``\n``, ``\r``, and ``\r\n`` - line endings move the cursor to the next line. + .. code-block:: python - - ``color`` is an RGB888 tuple for Grayscale or RGB565 images. Defaults to - white. However, you may also pass the underlying pixel value (0-255) for - grayscale images or a RGB565 value for RGB565 images. + q.rect() - - ``scale`` may be increased to increase/decrease the size of the text on the - image. You can pass greater than 0 integer or floating point values. + .. method:: x() - Returns the image object so you can call another method using ``.`` notation. + Get the bounding box x coordinate. + :returns x: the x coordinate. + :return type: int - UIFlow2.0 + UiFlow2 Code Block: - |draw_string.png| + |x.png| + + MicroPython Code Block: + + .. code-block:: python + + q.x() + + .. method:: y() + + Get the bounding box y coordinate. + + :returns y: the y coordinate. + :return type: int + + UiFlow2 Code Block: + + |y.png| + + MicroPython Code Block: + + .. code-block:: python + + q.y() + + .. method:: w() + + Get the bounding box width. + + :returns w: the width of the QR code. + :return type: int + + UiFlow2 Code Block: + + |w.png| + + MicroPython Code Block: + + .. code-block:: python + + q.w() + + .. method:: h() + + Get the bounding box height. + + :returns h: the height of the QR code. + :return type: int + + UiFlow2 Code Block: + + |h.png| + + MicroPython Code Block: + + .. code-block:: python + + q.h() + + .. method:: payload() + + Get the decoded payload string (e.g. URL) from the QR code. + + :returns payload: decoded string. + :return type: str + + UiFlow2 Code Block: + + |payload.png| + + MicroPython Code Block: + + .. code-block:: python + + q.payload() + + .. method:: version() + + Get the QR code version number. + + :returns version: QR code version. + :return type: int + + UiFlow2 Code Block: + + |version.png| + + MicroPython Code Block: + + .. code-block:: python + + q.version() + + .. method:: ecc_level() + + Get the QR code ECC (error correction) level. + + ECC levels: L, M, Q, H. Higher levels allow more damage tolerance but reduce data capacity. + + :returns ecc: ECC level (0~3). + :return type: int + + UiFlow2 Code Block: + + |ecc_level.png| + + MicroPython Code Block: + + .. code-block:: python + + q.ecc_level() + + .. method:: mask() + + Get the QR code mask pattern (0~7). + + Mask is used to improve QR readability. + + :returns mask: mask pattern ID. + :return type: int + + UiFlow2 Code Block: + + |mask.png| + + MicroPython Code Block: + + .. code-block:: python + + q.mask() + + .. method:: eci() + + Get the QR code ECI (Extended Channel Interpretation) value. + + ECI indicates the text encoding (e.g. UTF-8, Shift-JIS). ``0`` means ECI not used. + + :returns eci: ECI value. + :return type: int + + UiFlow2 Code Block: + + |eci.png| + + MicroPython Code Block: + + .. code-block:: python + + q.eci() Constants --------------------------------- +--------- .. data:: RGB565 - :type: int - RGB565 pixel format. Each pixel is 16-bits, 2-bytes. 5-bits are used for red, - 6-bits are used for green, and 5-bits are used for blue. + :type: int + + RGB565 pixel format. Each pixel is 16-bits, 2-bytes. 5-bits are used for red, + 6-bits are used for green, and 5-bits are used for blue. .. data:: GRAYSCALE - :type: int - GRAYSCALE pixel format. Each pixel is 8-bits, 1-byte. + :type: int + + GRAYSCALE pixel format. Each pixel is 8-bits, 1-byte. .. data:: JPEG - :type: int - A JPEG image. + :type: int + + A JPEG image. .. data:: YUV422 - :type: int - - A pixel format that is very easy to jpeg compress. Each pixel is stored as a grayscale - 8-bit Y value followed by alternating 8-bit U/V color values that are shared between two - Y values (8-bits Y1, 8-bits U, 8-bits Y2, 8-bits V, etc.). Only some image processing - methods work with YUV422. - UIFlow2.0 + :type: int + + A pixel format that is very easy to jpeg compress. Each pixel is stored as a grayscale + 8-bit Y value followed by alternating 8-bit U/V color values that are shared between two + Y values (8-bits Y1, 8-bits U, 8-bits Y2, 8-bits V, etc.). Only some image processing + methods work with YUV422. + + UiFlow2 Code Block: |format_option.png| - + diff --git a/docs/en/refs/advanced.image.ref b/docs/en/refs/advanced.image.ref index b29438b0..7bd4d2f5 100644 --- a/docs/en/refs/advanced.image.ref +++ b/docs/en/refs/advanced.image.ref @@ -12,6 +12,20 @@ .. |snapshot.png| image:: https://static-cdn.m5stack.com/mpy_docs/advanced/openmv/image/snapshot.png .. |show.png| image:: https://static-cdn.m5stack.com/mpy_docs/advanced/openmv/image/show.png +.. |find_qrcodes.png| image:: https://static-cdn.m5stack.com/mpy_docs/advanced/openmv/qrcode/find_qrcodes.png +.. |corners.png| image:: https://static-cdn.m5stack.com/mpy_docs/advanced/openmv/qrcode/corners.png +.. |rect.png| image:: https://static-cdn.m5stack.com/mpy_docs/advanced/openmv/qrcode/rect.png +.. |x.png| image:: https://static-cdn.m5stack.com/mpy_docs/advanced/openmv/qrcode/x.png +.. |y.png| image:: https://static-cdn.m5stack.com/mpy_docs/advanced/openmv/qrcode/y.png +.. |w.png| image:: https://static-cdn.m5stack.com/mpy_docs/advanced/openmv/qrcode/w.png +.. |h.png| image:: https://static-cdn.m5stack.com/mpy_docs/advanced/openmv/qrcode/h.png +.. |payload.png| image:: https://static-cdn.m5stack.com/mpy_docs/advanced/openmv/qrcode/payload.png +.. |version.png| image:: https://static-cdn.m5stack.com/mpy_docs/advanced/openmv/qrcode/version.png +.. |ecc_level.png| image:: https://static-cdn.m5stack.com/mpy_docs/advanced/openmv/qrcode/ecc_level.png +.. |mask.png| image:: https://static-cdn.m5stack.com/mpy_docs/advanced/openmv/qrcode/mask.png +.. |eci.png| image:: https://static-cdn.m5stack.com/mpy_docs/advanced/openmv/qrcode/eci.png +.. |cores3_image_find_qrcode_example.png| image:: https://static-cdn.m5stack.com/mpy_docs/advanced/openmv/image_find_qrcode_example.png + .. |cores3_example_draw_test.m5f2| raw:: html @@ -21,3 +35,12 @@ > cores3_example_draw_test.m5f2 + +.. |cores3_image_find_qrcode_example.m5f2| raw:: html + + + cores3_image_find_qrcode_example.m5f2 + diff --git a/docs/locales/zh_CN/LC_MESSAGES/advanced/image.po b/docs/locales/zh_CN/LC_MESSAGES/advanced/image.po index 8d202b63..9f7b76c5 100644 --- a/docs/locales/zh_CN/LC_MESSAGES/advanced/image.po +++ b/docs/locales/zh_CN/LC_MESSAGES/advanced/image.po @@ -9,7 +9,7 @@ msgid "" msgstr "" "Project-Id-Version: UIFlow2 Programming Guide \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-01-16 17:26+0800\n" +"POT-Creation-Date: 2025-09-02 09:46+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language: zh_CN\n" @@ -20,268 +20,726 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.16.0\n" -#: ../../en/advanced/image.rst:2 ee4bf22e05eb47b786085e7077ef7f7d +#: ../../en/advanced/image.rst:2 ee9e434e946c430c84047af3256f3a54 msgid "image" msgstr "" -#: ../../en/advanced/image.rst:6 ac9cc9cbcd654cfd97c4dfff79a3d6ec +#: ../../en/advanced/image.rst:6 d37210f151d145d1939185bd0dd468ca msgid "This module is only applicable to the CoreS3 Controller" msgstr "当前模块只适用于 CoreS3 主机" -#: ../../en/advanced/image.rst:12 e3dab7ec026f4655bfa578e4e8ef7144 -msgid "Micropython Example" -msgstr "Micropython 案例" +#: ../../en/advanced/image.rst:10 4f83b87722d140ce8a27ae5c2d534e1b +msgid "UiFlow2 Example" +msgstr "UiFlow2 应用示例" -#: ../../en/advanced/image.rst:15 ../../en/advanced/image.rst:25 -#: 393726024e9043cfa0ae22c9f086f3b2 885040fac4cc4b43b544250aef319906 +#: ../../en/advanced/image.rst:13 ../../en/advanced/image.rst:46 +#: bf25eb8c81a24ab7949da15771169f38 msgid "draw test" msgstr "绘图测试" -#: ../../en/advanced/image.rst:22 2de7e679b9f947868c69ee79cf030c9f -msgid "UIFlow2.0 Example" -msgstr "" +#: ../../en/advanced/image.rst:15 110b7ad814ba4291bb88764cb183d5a6 +msgid "Open the |cores3_example_draw_test.m5f2| project in UiFlow2." +msgstr "在 UiFlow2 上打开 |cores3_example_draw_test.m5f2| 项目。" -#: ../../en/advanced/image.rst:27 16c38ffcb50644508a39eae531a32942 +#: ../../en/advanced/image.rst:17 4958410910f54d3ab1174c6d86360e4c +msgid "" +"This example captures an image from the camera and demonstrates how to " +"draw text and basic shapes on it." +msgstr "此示例从摄像头拍摄图像,并演示如何在其上绘制文本和基本图形。" + +#: ../../en/advanced/image.rst:19 ../../en/advanced/image.rst:34 +#: ../../en/advanced/image.rst:73 ../../en/advanced/image.rst:90 +#: ../../en/advanced/image.rst:109 ../../en/advanced/image.rst:126 +#: ../../en/advanced/image.rst:143 ../../en/advanced/image.rst:168 +#: ../../en/advanced/image.rst:194 ../../en/advanced/image.rst:219 +#: ../../en/advanced/image.rst:243 ../../en/advanced/image.rst:261 +#: ../../en/advanced/image.rst:282 ../../en/advanced/image.rst:299 +#: ../../en/advanced/image.rst:316 ../../en/advanced/image.rst:333 +#: ../../en/advanced/image.rst:350 ../../en/advanced/image.rst:367 +#: ../../en/advanced/image.rst:384 ../../en/advanced/image.rst:401 +#: ../../en/advanced/image.rst:420 ../../en/advanced/image.rst:439 +#: ../../en/advanced/image.rst:458 ../../en/advanced/image.rst:499 +#: 2656e05a96f148918070cb719c316ad8 710bbaaf4f484348a682277f1e527a47 +msgid "UiFlow2 Code Block:" +msgstr "UiFlow2 代码块:" + +#: ../../en/advanced/image.rst:21 4e9270d214aa49cca575bb0dfaf27b93 msgid "|image_draw_example.png|" msgstr "" -#: ../../en/refs/advanced.image.ref:7 70a3fb541c8a4f979fce702018f0e617 +#: ../../en/refs/advanced.image.ref:7 c5e4cf7d1dcc419e845767d710bf7796 msgid "image_draw_example.png" msgstr "" -#: ../../en/advanced/image.rst:31 6f7448cdece94e16a4f4cd64d8d8ad18 -msgid "|cores3_example_draw_test.m5f2|" -msgstr "" +#: ../../en/advanced/image.rst:23 ../../en/advanced/image.rst:38 +#: 0fbca08cafea4466a3efa81b00735ee6 511c7016e4514a198d882adc1f519794 +msgid "Example output:" +msgstr "示例输出:" -#: ../../en/advanced/image.rst:34 2b540bcb5bbf43efa57f2ae993419a22 -msgid "class image.Image" +#: ../../en/advanced/image.rst:25 ../../en/advanced/image.rst:40 +#: b7197fc9687a4f5790e227fb3f7a68e4 e8fe1dd0aea346e3a83354bd239969af +msgid "None" msgstr "" -#: ../../en/advanced/image.rst:35 2fe3ddfd25d64a6093f304f63d852d28 -msgid "The line object is returned by `camera.snapshot()`." -msgstr "``image.Image`` 对象由 `camera.snapshot()` 返回." +#: ../../en/advanced/image.rst:28 ../../en/advanced/image.rst:53 +#: edcd4964df974cd29cb8366e675408ff +msgid "find qrcode" +msgstr "查找二维码" + +#: ../../en/advanced/image.rst:30 2a260c5648e445fdaaf84fcf5d988866 +msgid "Open the |cores3_image_find_qrcode_example.m5f2| project in UiFlow2." +msgstr "在 UiFlow2 上打开 |cores3_image_find_qrcode_example.m5f2| 项目。" + +#: ../../en/advanced/image.rst:32 9df40b5efde14c2687662807c056bb92 +msgid "" +"This example captures images from the camera, detects QR codes, and draws" +" their bounding boxes and decoded payload text on the LCD." +msgstr "此示例从摄像头拍摄图像,检测二维码,并在 LCD 上绘制其边界框和解码后的文本。" + +#: ../../en/advanced/image.rst:36 752391eef0ff482cae40f1d83d53e141 +msgid "|cores3_image_find_qrcode_example.png|" +msgstr "" -#: ../../en/advanced/image.rst:38 d265f707bb8d4be4928f48c7608180f8 -msgid "Basic Methods" +#: ../../en/refs/advanced.image.ref:28 194468293f69448692285c301c78dd2c +msgid "cores3_image_find_qrcode_example.png" msgstr "" -#: ../../en/advanced/image.rst:42 f8bbfb9f358f495ba4a5ded17b2dde06 +#: ../../en/advanced/image.rst:43 9d15155a50be4487b20e1b85062dccba +msgid "MicroPython Example" +msgstr "MicroPython 应用示例" + +#: ../../en/advanced/image.rst:60 ec0bf8f00592459abf3b56e5f5a08e14 +msgid "**API**" +msgstr "API应用" + +#: ../../en/advanced/image.rst:64 5b94bc825b8b44878d86a21c0fe2d258 +msgid "The image.Image object is returned by `camera.snapshot()`." +msgstr "``image.Image`` 对象由 `camera.snapshot()` 返回." + +#: ../../en/advanced/image.rst:68 0278e0c555434a69bdbf363b3ce3c3bb msgid "Returns the image width in pixels." msgstr "返回图像的宽度(以像素为单位)。" -#: ../../en/advanced/image.rst:44 ../../en/advanced/image.rst:52 -#: ../../en/advanced/image.rst:60 ../../en/advanced/image.rst:68 -#: ../../en/advanced/image.rst:76 ../../en/advanced/image.rst:97 -#: ../../en/advanced/image.rst:116 ../../en/advanced/image.rst:135 -#: ../../en/advanced/image.rst:158 ../../en/advanced/image.rst:189 -#: 1ba2b755d2984b4fbe7b70469a4957d4 33013c866cd94672a9e92de291019465 -#: 3bd044c0d20749408235cba0081ab5d5 3d9fedfde9a94e819ab6c4fcb9de6271 -#: 5907d01216aa4276b2e6b4ebad37f82c 66480c95cea04b3fb914650f032ede38 -#: 7868b23b76e44da58a82afc78aab7f16 7be962d6a8e2460a81716adbec2d2381 -#: c0c9b2445f744e9f844b98cf8d98844b c43764dca7614bfba7ec67ec0a881764 -msgid "UIFlow2.0" +#: ../../en/advanced/image.rst 589332a44e9a48dcbffeb253bb16d711 +msgid "returns width" +msgstr "" + +#: ../../en/advanced/image.rst:70 6a9549b3ad5d4095bde359511e7733a6 +msgid "image width." msgstr "" -#: ../../en/advanced/image.rst:46 a0a2778d4acf463780ad6332a970e37d +#: ../../en/advanced/image.rst fc6baf1a54f54f1a8d1da81400327402 +msgid "return type" +msgstr "" + +#: ../../en/advanced/image.rst:71 ../../en/advanced/image.rst:88 +#: ../../en/advanced/image.rst:107 ../../en/advanced/image.rst:124 +#: ../../en/advanced/image.rst:314 ../../en/advanced/image.rst:331 +#: ../../en/advanced/image.rst:348 ../../en/advanced/image.rst:365 +#: ../../en/advanced/image.rst:399 ../../en/advanced/image.rst:418 +#: ../../en/advanced/image.rst:437 ../../en/advanced/image.rst:456 +#: ../../en/advanced/image.rst:473 ../../en/advanced/image.rst:480 +#: ../../en/advanced/image.rst:486 ../../en/advanced/image.rst:492 +#: 32508641492e42a39fd9a081d162cc99 4938587c1aca460b9394bc273c2f4888 +#: 86adb6ec02504e5b84b0d3de26024b39 9ce97c68c18745609412d230d87d1a5f +#: fe1a31bc5dac4054b64c11cf12920e2a +msgid "int" +msgstr "" + +#: ../../en/advanced/image.rst:75 c3e0b3d8cc77475b9d7f4419398dd5a3 msgid "|width.png|" msgstr "" -#: ../../en/refs/advanced.image.ref:1 fbc07c7112de4bb2b1038e494c3aa60d +#: ../../en/refs/advanced.image.ref:1 0d3f52dd61914798ba7be499eb16a727 msgid "width.png" msgstr "" -#: ../../en/advanced/image.rst:50 25304f03dc8147e8b5134e822b26d9ab +#: ../../en/advanced/image.rst:77 ../../en/advanced/image.rst:94 +#: ../../en/advanced/image.rst:113 ../../en/advanced/image.rst:130 +#: ../../en/advanced/image.rst:147 ../../en/advanced/image.rst:172 +#: ../../en/advanced/image.rst:198 ../../en/advanced/image.rst:223 +#: ../../en/advanced/image.rst:247 ../../en/advanced/image.rst:265 +#: ../../en/advanced/image.rst:286 ../../en/advanced/image.rst:303 +#: ../../en/advanced/image.rst:320 ../../en/advanced/image.rst:337 +#: ../../en/advanced/image.rst:354 ../../en/advanced/image.rst:371 +#: ../../en/advanced/image.rst:388 ../../en/advanced/image.rst:405 +#: ../../en/advanced/image.rst:424 ../../en/advanced/image.rst:443 +#: ../../en/advanced/image.rst:462 821b8793be07424b995eb4fa659851c4 +msgid "MicroPython Code Block:" +msgstr "MicroPython 代码块:" + +#: ../../en/advanced/image.rst:85 a5cc488a444a49d5be4fabfc59f93506 msgid "Returns the image height in pixels." msgstr "返回图像的高度(以像素为单位)。" -#: ../../en/advanced/image.rst:54 c7899c7acd7f4e86930369e30529fb19 +#: ../../en/advanced/image.rst 1e9c3c0faaeb4f49b869d78b6492a082 +msgid "returns height" +msgstr "" + +#: ../../en/advanced/image.rst:87 968141dd80b44945b8c2a3a92a6125ff +msgid "image height." +msgstr "" + +#: ../../en/advanced/image.rst:92 7d9366955bb640a3a441a6aac7088cba msgid "|height.png|" msgstr "" -#: ../../en/refs/advanced.image.ref:2 ada8c480d57344188a420417694284b8 +#: ../../en/refs/advanced.image.ref:2 6367a5535d364fc1a1ee7fc4ab2cf99c msgid "height.png" msgstr "" -#: ../../en/advanced/image.rst:58 f712ef47acea4ab68e3818d062d95de0 -msgid "Returns the image format" +#: ../../en/advanced/image.rst:102 b722b7ed4b814452b8a2aeb25bb1ae8d +msgid "Returns the image format." msgstr "返回图像的格式" -#: ../../en/advanced/image.rst:62 4d28d37ac42248c295f24e8cb26da936 +#: ../../en/advanced/image.rst:104 68eb35d1f5804d82a9b30ae5b2650044 +msgid "" +"Returns image.GRAYSCALE for grayscale images, image.RGB565 for RGB565 " +"images, image.BAYER for bayer pattern images, and image.JPEG for JPEG " +"images." +msgstr "返回值为 image.GRAYSCALE(灰度图)、image.RGB565(RGB565 图)、image.BAYER(Bayer 图像)、或 " +"image.JPEG(JPEG 图像)。" + +#: ../../en/advanced/image.rst 61294bf986c441079aa97f799c9b7252 +msgid "returns format" +msgstr "" + +#: ../../en/advanced/image.rst:106 1512263a59e142d28520bf73e3b0e249 +msgid "image format." +msgstr "" + +#: ../../en/advanced/image.rst:111 d1155712ca4f4fd1b8b42b5df24dfabb msgid "|format.png|" msgstr "" -#: ../../en/refs/advanced.image.ref:3 23c15a3da6ec40619b4e7d40077bd8b0 +#: ../../en/refs/advanced.image.ref:3 57c65c5bf9ee429989d202e6cb134b3e msgid "format.png" msgstr "" -#: ../../en/advanced/image.rst:66 a71cb5a4f0c047cc9f662d1882def710 +#: ../../en/advanced/image.rst:121 083884928c4b4542ad1db5690dbde955 msgid "Returns the image size in bytes." msgstr "返回图像的大小(以字节为单位)。" -#: ../../en/advanced/image.rst:70 67fceb72b26742518a7300bb76439e93 +#: ../../en/advanced/image.rst 2525b6f7bb80474ba54f5063efc40abc +msgid "returns size" +msgstr "" + +#: ../../en/advanced/image.rst:123 8582a5ff32ac4e618da1423df545e597 +msgid "image size in bytes." +msgstr "图像大小,单位:字节" + +#: ../../en/advanced/image.rst:128 0eb70006a55145b896b835498a3bd0bb msgid "|size.png|" msgstr "" -#: ../../en/refs/advanced.image.ref:5 12d6f83317e541dd9d601ebfc32a4b23 +#: ../../en/refs/advanced.image.ref:5 04c2319fdba344808a75d098101254f3 msgid "size.png" msgstr "" -#: ../../en/advanced/image.rst:74 c5b734979560409da5ba55074e29f67b +#: ../../en/advanced/image.rst:138 18e34b54e689444da7f03beed0c01d6c msgid "" "Returns a bytearray object that points to the image data for byte-level " "read/write access." msgstr "返回一个 bytearray 对象,该对象指向图像数据,允许进行字节级别的读写访问。" -#: ../../en/advanced/image.rst:78 5fb0d6cf079e42e98a1ecac81fc736b6 -msgid "|bytearray.png|" +#: ../../en/advanced/image.rst 00e6ef5a3b624f1e9d4dbda20df9e3b1 +msgid "returns data" msgstr "" -#: ../../en/refs/advanced.image.ref:6 f87985c4bb9e4fa48a273e3aeb8aa7c9 -msgid "bytearray.png" +#: ../../en/advanced/image.rst:140 595d720c96334b20b58d3faa90953bc6 +msgid "image data buffer." msgstr "" -#: ../../en/advanced/image.rst:82 5fda9afdf9c94752bbceabb5b8c234ee -msgid "Drawing Methods" +#: ../../en/advanced/image.rst:141 84dda8f8b6214a8ba4603dede3d7de64 +msgid "bytearray" msgstr "" -#: ../../en/advanced/image.rst:86 c551e18cbb044d2388a65589670550df +#: ../../en/advanced/image.rst:145 c8f7d4945ca142ab970bbd40dca523c6 +msgid "|bytearray.png|" +msgstr "" + +#: ../../en/refs/advanced.image.ref:6 994c303fcbec457da2b58a7a32e6ee36 +msgid "bytearray.png" +msgstr "" + +#: ../../en/advanced/image.rst:155 bb8f6d9bb7454e148fad0030938a34d9 msgid "" "Draws a line from (x0, y0) to (x1, y1) on the image. You may either pass " "x0, y0, x1, y1 separately or as a tuple (x0, y0, x1, y1)." -msgstr "在图像上绘制一条从 (x0, y0) 到 (x1, y1) 的线段。你可以分别传递 x0, y0, x1, y1,或者将它们作为元组 (x0, y0, x1, y1) 一起传递。" +msgstr "" +"在图像上绘制一条从 (x0, y0) 到 (x1, y1) 的线段。你可以分别传递 x0, y0, x1, y1,或者将它们作为元组 (x0, " +"y0, x1, y1) 一起传递。" -#: ../../en/advanced/image.rst:89 ../../en/advanced/image.rst:106 -#: ../../en/advanced/image.rst:125 ../../en/advanced/image.rst:148 -#: 7597fe3727d74e6686fc38912af2ef2a 8e7fff87b4dd4ddea7c6236685ccc6e2 -#: b32fdbcfbaf74f44abc256e3a63fdb0b cf5b7b6cbba24caaa8913c1dc49bea68 -msgid "" -"``color`` is an RGB888 tuple for Grayscale or RGB565 images. Defaults to " -"white. However, you may also pass the underlying pixel value (0-255) for " -"grayscale images or a RGB565 value for RGB565 images." -msgstr "``color`` 颜色值 RGB888 格式,可以是 (r, g, b) 元组或整数。" - -#: ../../en/advanced/image.rst:93 5afde101ae0c480a9e25ac2b4f75337f -msgid "``thickness`` controls how thick the line is in pixels." -msgstr "``thickness`` 控制线条的粗细(以像素为单位)。" - -#: ../../en/advanced/image.rst:95 ../../en/advanced/image.rst:114 -#: ../../en/advanced/image.rst:133 ../../en/advanced/image.rst:155 -#: 2e0b49bfb9d74b39875bd80621bcc006 3d049b022f2d48d596d1fddf547861db -#: 91a904127e1846a994fd349fac389c78 f7c67126a0c14d3c870d85a0adcb8bb0 -msgid "" -"Returns the image object so you can call another method using ``.`` " -"notation." -msgstr "返回 ``image.Image`` 对象,以便你可以使用 . 表示法调用其他方法。" +#: ../../en/advanced/image.rst 58d43195e29942b8ba090d8b9d5a08d1 +msgid "Parameters" +msgstr "" + +#: ../../en/advanced/image.rst:158 8586cd7790a1454faa5f427d70947cfa +msgid "start x coordinate." +msgstr "起始坐标 x" + +#: ../../en/advanced/image.rst:159 c90835e4e1874fa8b9ab9eaa3c442929 +msgid "start y coordinate." +msgstr "起始坐标 y" -#: ../../en/advanced/image.rst:99 1a45057613904a83acc52f57a4be9fa9 +#: ../../en/advanced/image.rst:160 faab8f16427d468dbae45ed8431c0beb +msgid "end x coordinate." +msgstr "结束坐标 x" + +#: ../../en/advanced/image.rst:161 64a8f36b867b490fbed17e639a19557c +msgid "end y coordinate." +msgstr "结束坐标 y" + +#: ../../en/advanced/image.rst:162 ../../en/advanced/image.rst:187 +#: ../../en/advanced/image.rst:212 ../../en/advanced/image.rst:237 +#: 2817b8961b09416e95c72917aaf4eac5 +msgid "RGB888 tuple, grayscale value (0-255), or RGB565 value. Defaults to white." +msgstr "RGB888 元组、灰度值 (0–255),或 RGB565 值。默认为白色。" + +#: ../../en/advanced/image.rst:163 1a10a3c96e7642f796cc81dd54611610 +msgid "line thickness in pixels. Defaults to 1." +msgstr "线条厚度,单位为像素。默认值为 1。" + +#: ../../en/advanced/image.rst 7827bfae4b8244909ce80eb2ba1927e0 +msgid "returns self" +msgstr "" + +#: ../../en/advanced/image.rst:165 ../../en/advanced/image.rst:191 +#: ../../en/advanced/image.rst:216 ../../en/advanced/image.rst:240 +#: cfbf48a03762439d9f1d3b2d46cc68d2 +msgid "the image object for method chaining." +msgstr "图像对象,可用于链式调用方法。" + +#: ../../en/advanced/image.rst:166 ../../en/advanced/image.rst:192 +#: ../../en/advanced/image.rst:217 ../../en/advanced/image.rst:241 +#: bf79bdb73e9a47aa81adc18c84f1fae1 +msgid "Image" +msgstr "" + +#: ../../en/advanced/image.rst:170 3d80adbfe63a4169bb1d3a4a79f26d90 msgid "|draw_line.png|" msgstr "" -#: ../../en/refs/advanced.image.ref:10 d237c67d052541cf977e0f8ead03dc67 +#: ../../en/refs/advanced.image.ref:10 9dc8c86f5f1847539e517446d08ec76e msgid "draw_line.png" msgstr "" -#: ../../en/advanced/image.rst:103 128ab18b172449db868c24c4270ff803 +#: ../../en/advanced/image.rst:180 9dab6eebf9464ba2aea3de8f491c2b7d msgid "" "Draws a rectangle on the image. You may either pass x, y, w, h separately" " or as a tuple (x, y, w, h)." msgstr "在图像上绘制一个矩形。你可以分别传递 x, y, w, h,或者将它们作为元组 (x, y, w, h) 一起传递。" -#: ../../en/advanced/image.rst:110 c06db4eebf6c4e7f980a49639d8f1171 -msgid "``thickness`` controls how thick the lines are in pixels." -msgstr "``thickness`` 控制线条的粗细(以像素为单位)。" +#: ../../en/advanced/image.rst:183 a19222e41f8748c39109e967fe199249 +msgid "top-left x coordinate." +msgstr "左上角坐标 x" + +#: ../../en/advanced/image.rst:184 d9d65954796341de98efe8d5533ecbe8 +msgid "top-left y coordinate." +msgstr "左上角坐标 y" + +#: ../../en/advanced/image.rst:185 40f18d06c3d64234a973b8f1392f2b6c +msgid "rectangle width." +msgstr "矩形宽" + +#: ../../en/advanced/image.rst:186 5c42cc21597d4231a082432a1eb7e222 +msgid "rectangle height." +msgstr "矩形高" -#: ../../en/advanced/image.rst:112 56b2897d03a6491fa7a29da28574d58b -msgid "``fill`` set to True to fill the rectangle." -msgstr "``fill`` 是否填充。" +#: ../../en/advanced/image.rst:188 ../../en/advanced/image.rst:213 +#: d961529f4c4148bdb6bbdf7a9db0b2bf +msgid "border thickness in pixels. Defaults to 1." +msgstr "边框厚度,单位为像素。默认值为 1。" -#: ../../en/advanced/image.rst:118 a6aa19b86bef482894c887e6f6b4d137 +#: ../../en/advanced/image.rst:189 81ff9f7efa364a2e8aec7156d327a800 +msgid "set True to fill the rectangle. Defaults to False." +msgstr "设置为 True 以填充矩形。默认值为 False。" + +#: ../../en/advanced/image.rst:196 da7dcbf493dd4f77a70e32221bd9e3da msgid "|draw_rectangle.png|" msgstr "" -#: ../../en/refs/advanced.image.ref:9 ac018a76bd5541c9915e7387c80c89a4 +#: ../../en/refs/advanced.image.ref:9 194468293f69448692285c301c78dd2c msgid "draw_rectangle.png" msgstr "" -#: ../../en/advanced/image.rst:122 ecb49d9d4c1043c2996f5e49189b00bd +#: ../../en/advanced/image.rst:206 33197f0565a34a76bf14fb5fa9252ecb msgid "" "Draws a circle on the image. You may either pass x, y, radius separately " "or as a tuple (x, y, radius)." msgstr "在图像上绘制一个圆形。你可以分别传递 x, y, radius,或者将它们作为元组 (x, y, radius) 一起传递。" -#: ../../en/advanced/image.rst:129 2f6a30e3deeb46c4a6da3b4797255d53 -msgid "``thickness`` controls how thick the edges are in pixels." -msgstr "``thickness`` 厚度”控制边缘的像素厚度。 " +#: ../../en/advanced/image.rst:209 526a17f01e804964ab1ba35928e1b253 +msgid "circle center x coordinate." +msgstr "圆心坐标 x" + +#: ../../en/advanced/image.rst:210 eced48f92cea4108be8259c39e01ed84 +msgid "circle center y coordinate." +msgstr "圆心坐标 y" + +#: ../../en/advanced/image.rst:211 acff0df186ac47dd9215539d718491e3 +msgid "circle radius." +msgstr "圆半径" -#: ../../en/advanced/image.rst:131 105a1cec0c9a490e8432b4d5fc5510d5 -msgid "``fill`` set to True to fill the circle." -msgstr "- ``fill`` 是否填充。" +#: ../../en/advanced/image.rst:214 b4cab23f06054ccb90c064ff44e6f7db +msgid "set True to fill the circle. Defaults to False." +msgstr "设置为 True 以填充圆形。默认值为 False。" -#: ../../en/advanced/image.rst:137 37076ba5b66a4cdb9e4528e33c781611 +#: ../../en/advanced/image.rst:221 a0f77cfe0ad34f0194d28752cc6e269d msgid "|draw_circle.png|" msgstr "" -#: ../../en/refs/advanced.image.ref:11 d0459a44daa346929aff6b338453fafb +#: ../../en/refs/advanced.image.ref:11 acd6058addf442fea84a864e4f03b5a6 msgid "draw_circle.png" msgstr "" -#: ../../en/advanced/image.rst:142 4b7bf3c7bb6e480bb9dffb0587ce95fa +#: ../../en/advanced/image.rst:231 009d7aee129b4c99a67a95f6087419d0 msgid "" "Draws 8x16 text starting at location (x, y) in the image. You may either " "pass x, y separately or as a tuple (x, y)." msgstr "在图像上绘制从位置 (x, y) 开始的 8x16 文本。你可以分别传递 x, y,或者将它们作为元组 (x, y) 一起传递。" -#: ../../en/advanced/image.rst:145 2a937d934552420aa366eb29661ce3de -msgid "" -"``text`` is a string to write to the image. ``\\n``, ``\\r``, and " -"``\\r\\n`` line endings move the cursor to the next line." -msgstr "``text`` 是要写入图像的字符串。``\n``、``\r`` 和 ``\r\n`` 换行符将光标移动到下一行。" +#: ../../en/advanced/image.rst:234 ee9b4548b6794eb283b2e03a3dbeea70 +msgid "text start x coordinate." +msgstr "文本起始坐标 x" -#: ../../en/advanced/image.rst:152 462401bf084740a2a2fef098e148359c -msgid "" -"``scale`` may be increased to increase/decrease the size of the text on " -"the image. You can pass greater than 0 integer or floating point values." -msgstr "``scale`` 可增加或减少图像中文本的大小。你可以传递大于 0 的整数或浮动值。" +#: ../../en/advanced/image.rst:235 bcc5e28ccde34188b18850a408acdd06 +msgid "text start y coordinate." +msgstr "文本起始坐标 y" + +#: ../../en/advanced/image.rst:236 54cde5f4c87046f9ade7de965d2b7e4c +msgid "text to draw. Supports ``\\n``, ``\\r``, ``\\r\\n`` line breaks." +msgstr "要绘制的文本。支持换行符 ``\\n``, ``\\r``, ``\\r\\n``。" -#: ../../en/advanced/image.rst:160 1f2443a1311d40ef8def4c7f791c3a03 +#: ../../en/advanced/image.rst:238 9b25c0f2f1d449feafa4b10a60f5d0db +msgid "scale factor to resize text. Integer or float > 0. Defaults to 1." +msgstr "文本缩放因子,可用于调整文字大小。接受大于 0 的整数或浮点数。默认值为 1。" + +#: ../../en/advanced/image.rst:245 4cab100098c04749898b4b1aef50c676 msgid "|draw_string.png|" msgstr "" -#: ../../en/refs/advanced.image.ref:8 c1eda54bd4c242de968f12a94e3ab041 +#: ../../en/refs/advanced.image.ref:8 d6febf6a9e0c46ed8cac99784cb749f9 msgid "draw_string.png" msgstr "" -#: ../../en/advanced/image.rst:163 3ec51ba93f5641cfaf8e5c3f3cd286c8 +#: ../../en/advanced/image.rst:255 da9442f4b3014fb394b1cf67272e4a85 +msgid "" +"Finds all QR codes returns a list of ``image.qrcode`` objects. Please see" +" the image.qrcode object for more details." +msgstr "查找所有二维码,并返回一个包含 ``image.qrcode`` 对象的列表。更多详情请参见 image.qrcode 对象。" + +#: ../../en/advanced/image.rst 589332a44e9a48dcbffeb253bb16d711 +msgid "returns qrcodes" +msgstr "" + +#: ../../en/advanced/image.rst:258 af8f20fd84d84dd1b26621906a5384a0 +msgid "list of detected QR codes." +msgstr "检测到的二维码列表。" + +#: ../../en/advanced/image.rst:259 b099bf33fef44883a4d55f3c272cf00d +msgid "List[image.qrcode]" +msgstr "" + +#: ../../en/advanced/image.rst:263 752391eef0ff482cae40f1d83d53e141 +msgid "|find_qrcodes.png|" +msgstr "" + +#: ../../en/refs/advanced.image.ref:16 acd6058addf442fea84a864e4f03b5a6 +msgid "find_qrcodes.png" +msgstr "" + +#: ../../en/advanced/image.rst:273 fd3f6b1060814bf98c6ae7d332dec9d7 +msgid "Please call ``Image.find_qrcodes()`` to create this object." +msgstr "请调用 Image.find_qrcodes() 来创建该对象。" + +#: ../../en/advanced/image.rst:277 45499c7fb4e141b1968a03e67b8e8eec +msgid "" +"Get the 4 corners of the QR code in clockwise order starting from the " +"top-left." +msgstr "按顺时针顺序获取二维码的四个角点,从左上角开始。" + +#: ../../en/advanced/image.rst 61294bf986c441079aa97f799c9b7252 +msgid "returns corners" +msgstr "" + +#: ../../en/advanced/image.rst:279 98cc911926ef44298fdd75859c0d274f +msgid "list of 4 (x, y) tuples." +msgstr "包含 4 个 (x, y) 元组的列表。" + +#: ../../en/advanced/image.rst:280 60ba9068176d43389ea1f2714da0e7f9 +msgid "List[Tuple[int, int]]" +msgstr "" + +#: ../../en/advanced/image.rst:284 d1155712ca4f4fd1b8b42b5df24dfabb +msgid "|corners.png|" +msgstr "" + +#: ../../en/refs/advanced.image.ref:17 194468293f69448692285c301c78dd2c +msgid "corners.png" +msgstr "" + +#: ../../en/advanced/image.rst:294 bcd20d2cfc3043ca8f785745890241c7 +msgid "Get the bounding box of the QR code." +msgstr "获取二维码的边界框。" + +#: ../../en/advanced/image.rst 7827bfae4b8244909ce80eb2ba1927e0 +msgid "returns rect" +msgstr "" + +#: ../../en/advanced/image.rst:296 6271f81c58734f6598d6410ffdf77b30 +msgid "(x, y, w, h) tuple." +msgstr "(x, y, w, h) 元组" + +#: ../../en/advanced/image.rst:297 747b7a648b8b4cf4b6c73175f7c7cc95 +msgid "Tuple[int, int, int, int]" +msgstr "" + +#: ../../en/advanced/image.rst:301 7d9366955bb640a3a441a6aac7088cba +msgid "|rect.png|" +msgstr "" + +#: ../../en/refs/advanced.image.ref:18 57c65c5bf9ee429989d202e6cb134b3e +msgid "rect.png" +msgstr "" + +#: ../../en/advanced/image.rst:311 526a17f01e804964ab1ba35928e1b253 +msgid "Get the bounding box x coordinate." +msgstr "获取边界框的 x 坐标。" + +#: ../../en/advanced/image.rst 2525b6f7bb80474ba54f5063efc40abc +msgid "returns x" +msgstr "" + +#: ../../en/advanced/image.rst:313 faab8f16427d468dbae45ed8431c0beb +msgid "the x coordinate." +msgstr "坐标 x" + +#: ../../en/advanced/image.rst:318 0eb70006a55145b896b835498a3bd0bb +msgid "|x.png|" +msgstr "" + +#: ../../en/refs/advanced.image.ref:19 57c65c5bf9ee429989d202e6cb134b3e +msgid "x.png" +msgstr "" + +#: ../../en/advanced/image.rst:328 526a17f01e804964ab1ba35928e1b253 +msgid "Get the bounding box y coordinate." +msgstr "获取边界框的 y 坐标。" + +#: ../../en/advanced/image.rst 2525b6f7bb80474ba54f5063efc40abc +msgid "returns y" +msgstr "" + +#: ../../en/advanced/image.rst:330 64a8f36b867b490fbed17e639a19557c +msgid "the y coordinate." +msgstr "坐标 y" + +#: ../../en/advanced/image.rst:335 0eb70006a55145b896b835498a3bd0bb +msgid "|y.png|" +msgstr "" + +#: ../../en/refs/advanced.image.ref:20 57c65c5bf9ee429989d202e6cb134b3e +msgid "y.png" +msgstr "" + +#: ../../en/advanced/image.rst:345 f1e252a4990a4e14a4339728405dbe42 +msgid "Get the bounding box width." +msgstr "获取边界框的宽。" + +#: ../../en/advanced/image.rst 589332a44e9a48dcbffeb253bb16d711 +msgid "returns w" +msgstr "" + +#: ../../en/advanced/image.rst:347 8732d7ff9e4245c3b7227a356348eb3c +msgid "the width of the QR code." +msgstr "二维码宽。" + +#: ../../en/advanced/image.rst:352 c3e0b3d8cc77475b9d7f4419398dd5a3 +msgid "|w.png|" +msgstr "" + +#: ../../en/refs/advanced.image.ref:21 57c65c5bf9ee429989d202e6cb134b3e +msgid "w.png" +msgstr "" + +#: ../../en/advanced/image.rst:362 27d19b63e6df48ef91a8aec5ee847e52 +msgid "Get the bounding box height." +msgstr "获取边界框的高。" + +#: ../../en/advanced/image.rst 589332a44e9a48dcbffeb253bb16d711 +msgid "returns h" +msgstr "" + +#: ../../en/advanced/image.rst:364 cfbf48a03762439d9f1d3b2d46cc68d2 +msgid "the height of the QR code." +msgstr "二维码高。" + +#: ../../en/advanced/image.rst:369 c3e0b3d8cc77475b9d7f4419398dd5a3 +msgid "|h.png|" +msgstr "" + +#: ../../en/refs/advanced.image.ref:22 0d3f52dd61914798ba7be499eb16a727 +msgid "h.png" +msgstr "" + +#: ../../en/advanced/image.rst:379 78c5ac370647434e881f378c3f4a17e5 +msgid "Get the decoded payload string (e.g. URL) from the QR code." +msgstr "获取二维码解码后的数据字符串(例如 URL)。" + +#: ../../en/advanced/image.rst 00e6ef5a3b624f1e9d4dbda20df9e3b1 +msgid "returns payload" +msgstr "" + +#: ../../en/advanced/image.rst:381 db2f784d3fc74ad9af160edf6c7729b6 +msgid "decoded string." +msgstr "" + +#: ../../en/advanced/image.rst:382 cd14bff54b9c494895325082a1e1842c +msgid "str" +msgstr "" + +#: ../../en/advanced/image.rst:386 3d80adbfe63a4169bb1d3a4a79f26d90 +msgid "|payload.png|" +msgstr "" + +#: ../../en/refs/advanced.image.ref:23 994c303fcbec457da2b58a7a32e6ee36 +msgid "payload.png" +msgstr "" + +#: ../../en/advanced/image.rst:396 6736227f9aeb4b05bd0c3c6f599a71fc +msgid "Get the QR code version number." +msgstr "获取二维码版本号。" + +#: ../../en/advanced/image.rst 2525b6f7bb80474ba54f5063efc40abc +msgid "returns version" +msgstr "" + +#: ../../en/advanced/image.rst:398 ecf13c132bcd4f658c33e588b9f7757c +msgid "QR code version." +msgstr "二维码版本。" + +#: ../../en/advanced/image.rst:403 44fc5752a4a34bfda06739794d9beb85 +msgid "|version.png|" +msgstr "" + +#: ../../en/refs/advanced.image.ref:24 44eba0823c99432c89764caf1b702a90 +msgid "version.png" +msgstr "" + +#: ../../en/advanced/image.rst:413 fee9edd3170843e5990804c73aaf350c +msgid "Get the QR code ECC (error correction) level." +msgstr "获取二维码的 ECC(纠错)等级。" + +#: ../../en/advanced/image.rst:415 6feaa2d2915f43298338390e1cfea321 +msgid "" +"ECC levels: L, M, Q, H. Higher levels allow more damage tolerance but " +"reduce data capacity." +msgstr "ECC 等级:L、M、Q、H。等级越高,二维码对损坏的容忍度越高,但可存储的数据量会减少。" + +#: ../../en/advanced/image.rst 7827bfae4b8244909ce80eb2ba1927e0 +msgid "returns ecc" +msgstr "" + +#: ../../en/advanced/image.rst:417 647cf102869c4c378bf41d6a340503b5 +msgid "ECC level (0~3)." +msgstr "ECC 等级(0~3)。" + +#: ../../en/advanced/image.rst:422 3d80adbfe63a4169bb1d3a4a79f26d90 +msgid "|ecc_level.png|" +msgstr "" + +#: ../../en/refs/advanced.image.ref:25 194468293f69448692285c301c78dd2c +msgid "ecc_level.png" +msgstr "" + +#: ../../en/advanced/image.rst:432 001dcc6fa72246b69fbde7a44c25cf0d +msgid "Get the QR code mask pattern (0~7)." +msgstr "获取二维码的掩码模式(0~7)。" + +#: ../../en/advanced/image.rst:434 cd8fa847cb1244a1b1d1c59da365e8d0 +msgid "Mask is used to improve QR readability." +msgstr "掩码用于提高二维码的可读性。" + +#: ../../en/advanced/image.rst 00e6ef5a3b624f1e9d4dbda20df9e3b1 +msgid "returns mask" +msgstr "" + +#: ../../en/advanced/image.rst:436 91dc3c1c66e44c509e12187fe87a0aa3 +msgid "mask pattern ID." +msgstr "掩码模式 ID。" + +#: ../../en/advanced/image.rst:441 0eb70006a55145b896b835498a3bd0bb +msgid "|mask.png|" +msgstr "" + +#: ../../en/refs/advanced.image.ref:26 57c65c5bf9ee429989d202e6cb134b3e +msgid "mask.png" +msgstr "" + +#: ../../en/advanced/image.rst:451 d33ccd4bdd3b450c957183454b904c30 +msgid "Get the QR code ECI (Extended Channel Interpretation) value." +msgstr "获取二维码的 ECI(扩展通道解释)值。" + +#: ../../en/advanced/image.rst:453 7e467f93191746e29b1c0e6cc1e31478 +msgid "" +"ECI indicates the text encoding (e.g. UTF-8, Shift-JIS). ``0`` means ECI " +"not used." +msgstr "ECI 表示文本编码(例如 UTF-8、Shift-JIS)。0 表示未使用 ECI。" + +#: ../../en/advanced/image.rst 7827bfae4b8244909ce80eb2ba1927e0 +msgid "returns eci" +msgstr "" + +#: ../../en/advanced/image.rst:455 2a14def4e98a4491b49d40d4dfb1df5a +msgid "ECI value." +msgstr "" + +#: ../../en/advanced/image.rst:460 0eb70006a55145b896b835498a3bd0bb +msgid "|eci.png|" +msgstr "" + +#: ../../en/refs/advanced.image.ref:27 acd6058addf442fea84a864e4f03b5a6 +msgid "eci.png" +msgstr "" + +#: ../../en/advanced/image.rst:469 7214528470814e45817349ecfded2e21 msgid "Constants" msgstr "" -#: ../../en/advanced/image.rst:168 5d1a7434ab314023aea591bfa1704ce1 +#: ../../en/advanced/image.rst 92236ea2eb51472cbedfe0c9d54a895e +#: d2d186c696ec4aee846b9d0b48d5d9ab f58744f436074981946af935880c17db +#: f69c4586430c4e499e801017fe8d5592 +msgid "type" +msgstr "" + +#: ../../en/advanced/image.rst:475 4e50a7ac9b534b57aa2e429c5474a114 msgid "" "RGB565 pixel format. Each pixel is 16-bits, 2-bytes. 5-bits are used for " "red, 6-bits are used for green, and 5-bits are used for blue." -msgstr "RGB565 像素格式。每个像素为 16 位,2 字节。5 位用于红色,6 位用于绿色,5 位用于蓝色。" +msgstr "RGB565 像素格式。每个像素占 16 位(2 字节)。其中红色占 5 位,绿色占 6 位,蓝色占 5 位。" -#: ../../en/advanced/image.rst:174 77b2a84e2364448c9d1e4ac21b122a92 +#: ../../en/advanced/image.rst:482 7bf04212ffac44a2af1c4e9ab62d5203 msgid "GRAYSCALE pixel format. Each pixel is 8-bits, 1-byte." -msgstr "灰度图像像素格式。每个像素为 8 位,1 字节。" +msgstr "GRAYSCALE 像素格式。每个像素占 8 位(1 字节)。" -#: ../../en/advanced/image.rst:179 18ce7865508d4540ba3f899ebba03219 +#: ../../en/advanced/image.rst:488 52a28e9f99294fd5a377ca4e501eede5 msgid "A JPEG image." -msgstr "" +msgstr "JPEG 图像" -#: ../../en/advanced/image.rst:184 6213c2259ac2432ea2e506da09e3c439 +#: ../../en/advanced/image.rst:494 d0faecbff62144e0910ff24289847eb8 msgid "" "A pixel format that is very easy to jpeg compress. Each pixel is stored " "as a grayscale 8-bit Y value followed by alternating 8-bit U/V color " "values that are shared between two Y values (8-bits Y1, 8-bits U, 8-bits " "Y2, 8-bits V, etc.). Only some image processing methods work with YUV422." -msgstr "一种非常适合 JPEG 压缩的像素格式。每个像素存储为一个灰度 8 位 Y 值,后跟交替的 8 位 U/V 色彩值,这些值在两个 Y 值之间共享(8 位 Y1,8 位 U,8 位 Y2,8 位 V,依此类推)。只有一些图像处理方法可以与 YUV422 格式兼容。" +msgstr "一种非常容易进行 JPEG 压缩的像素格式。每个像素存储为 8 位灰度 Y 值,后跟在两个 Y 值之间共享的交替 8 位 U/V 色度值(即 8 位 Y1、8 位 U、8 位 Y2、8 位 V,依此类推)。只有部分图像处理方法支持 YUV422。" -#: ../../en/advanced/image.rst:191 597faf9e0ff440f290a3e053855bf9f2 +#: ../../en/advanced/image.rst:501 d9e7b9b4e7084cf4b7ec3bddac6b68a4 msgid "|format_option.png|" msgstr "" -#: ../../en/refs/advanced.image.ref:4 fdbf90a128bc487abee7636677122783 +#: ../../en/refs/advanced.image.ref:4 396d84d1f80342529f33d0e10b86342e msgid "format_option.png" msgstr "" diff --git a/examples/advanced/image/cores3_image_find_qrcode_example.m5f2 b/examples/advanced/image/cores3_image_find_qrcode_example.m5f2 new file mode 100644 index 00000000..b80d8e69 --- /dev/null +++ b/examples/advanced/image/cores3_image_find_qrcode_example.m5f2 @@ -0,0 +1 @@ +{"version":"V2.0","versionNumber":"V2.3.4","type":"cores3","components":[{"name":"screen","type":"screen","layer":0,"screenId":"builtin","screenName":"","id":"__cores3_screen","createTime":1756721781900,"x":0,"y":0,"width":320,"height":240,"backgroundColor":"#222222","size":0,"isSelected":true}],"resources":[{"hardware":["hardware_button","hardware_pin_button","imu","speaker","touch","als","mic","sdcard"]}],"units":[],"hats":[],"bases":[],"i2cs":[],"blockly":"imgqrcode_listqrcode_rescornersipointcoordx0y0x1y1trueRGB565QVGAtrueimgqrcode_listimgqrcode_listqrcode_resqrcode_listcornersqrcode_resicornerspointicoordGETFROM_STARTcornersADD1point1x0GETFROM_STARTcoord1y0GETFROM_STARTcoord2pointREMAINDER1ADD1i11cornerscoordGETFROM_STARTcornersADD1point1x1GETFROM_STARTcoord1y1GETFROM_STARTcoord2img0x00y00x10y1palette#3333ff3img00qrcode_respalette#3333ff1.5img00320240","screen":[{"simulationName":"Built-in","type":"builtin","width":320,"height":240,"scale":0.77,"screenName":"","blockId":"","screenColorType":0,"rotation":1,"id":"builtin","createTime":1756709754898}],"logicWhenNum":0,"customList":[]} \ No newline at end of file diff --git a/examples/advanced/image/cores3_image_find_qrcode_example.py b/examples/advanced/image/cores3_image_find_qrcode_example.py new file mode 100644 index 00000000..c1e743a1 --- /dev/null +++ b/examples/advanced/image/cores3_image_find_qrcode_example.py @@ -0,0 +1,65 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT +import os, sys, io +import M5 +from M5 import * +import camera +import image + + +img = None +qrcode_list = None +qrcode_res = None +corners = None +i = None +point = None +coord = None +x0 = None +y0 = None +x1 = None +y1 = None + + +def setup(): + global img, qrcode_list, corners, point, i, coord, x0, y0, x1, y1 + M5.begin() + Widgets.setRotation(1) + Widgets.fillScreen(0x222222) + camera.init(pixformat=camera.RGB565, framesize=camera.QVGA) + + +def loop(): + global img, qrcode_list, corners, point, i, coord, x0, y0, x1, y1 + M5.update() + img = camera.snapshot() + qrcode_list = img.find_qrcodes() + if qrcode_list: + for qrcode_res in qrcode_list: + corners = qrcode_res.corners() + for i in range(len(corners)): + point = i + coord = corners[int((point + 1) - 1)] + x0 = coord[0] + y0 = coord[1] + point = (i + 1) % len(corners) + coord = corners[int((point + 1) - 1)] + x1 = coord[0] + y1 = coord[1] + img.draw_line(x0, y0, x1, y1, color=0x3333FF, thickness=3) + img.draw_string(0, 0, str(qrcode_res.payload()), color=0x3333FF, scale=1.5) + M5.Lcd.show(img, 0, 0, 320, 240) + + +if __name__ == "__main__": + try: + setup() + while True: + loop() + except (Exception, KeyboardInterrupt) as e: + try: + from utility import print_error_msg + + print_error_msg(e) + except ImportError: + print("please update to latest firmware") diff --git a/m5stack/CMakeListsDefault.cmake b/m5stack/CMakeListsDefault.cmake index f8d5e35d..3e8d1a3a 100644 --- a/m5stack/CMakeListsDefault.cmake +++ b/m5stack/CMakeListsDefault.cmake @@ -250,12 +250,14 @@ list(APPEND IDF_COMPONENTS pedestrian_detect human_face_recognition esp-code-scanner + quirc ) endif() if (M5_CAMERA_MODULE_ENABLE AND BOARD_TYPE STREQUAL "atoms3r_cam") list(APPEND IDF_COMPONENTS esp-code-scanner + quirc ) endif() diff --git a/m5stack/CMakeListsLvgl.cmake b/m5stack/CMakeListsLvgl.cmake index ad7b5281..5df17d5d 100644 --- a/m5stack/CMakeListsLvgl.cmake +++ b/m5stack/CMakeListsLvgl.cmake @@ -238,6 +238,7 @@ list(APPEND IDF_COMPONENTS pedestrian_detect human_face_recognition esp-code-scanner + quirc ) endif() @@ -250,7 +251,6 @@ if(IDF_TARGET STREQUAL "esp32" OR IDF_TARGET STREQUAL "esp32s3") list(APPEND IDF_COMPONENTS esp_codec_dev) endif() - # Register the main IDF component. idf_component_register( SRCS diff --git a/m5stack/cmodules/omv/micropython.cmake b/m5stack/cmodules/omv/micropython.cmake index de7f331d..04cc85dd 100755 --- a/m5stack/cmodules/omv/micropython.cmake +++ b/m5stack/cmodules/omv/micropython.cmake @@ -54,6 +54,8 @@ target_include_directories(moudle_omv INTERFACE ${CMAKE_CURRENT_LIST_DIR}/../../components/esp_dl/models/human_face_recognition # esp-code-scanner ${CMAKE_CURRENT_LIST_DIR}/../../components/esp-code-scanner/include + # quirc + ${CMAKE_CURRENT_LIST_DIR}/../../managed_components/espressif__quirc/quirc/lib ) # Link our INTERFACE library to the usermod target. diff --git a/m5stack/cmodules/omv/modules/py_image.c b/m5stack/cmodules/omv/modules/py_image.c index eed2d353..c69cbecd 100644 --- a/m5stack/cmodules/omv/modules/py_image.c +++ b/m5stack/cmodules/omv/modules/py_image.c @@ -1,8 +1,8 @@ /* * SPDX-License-Identifier: MIT * - * Copyright (C) 2013-2024 OpenMV, LLC. - * Copyright (c) 2024 M5Stack Technology CO LTD + * Copyright (C) 2013-2025 OpenMV, LLC. + * Copyright (c) 2025 M5Stack Technology CO LTD * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -39,7 +39,8 @@ #include "imlib.h" #include "py_helper.h" #include "py_assert.h" - +#include "quirc.h" +#include #define PY_ASSERT_TYPE(obj, type) \ @@ -358,8 +359,297 @@ static mp_obj_t py_image_draw_edges(uint n_args, const mp_obj_t *args, mp_map_t } static MP_DEFINE_CONST_FUN_OBJ_KW(py_image_draw_edges_obj, 2, py_image_draw_edges); +// ==================================================================================== +// Find Methods +// ==================================================================================== + +// QRCode Object +#define py_qrcode2_obj_size 10 +typedef struct py_qrcode2_obj { + mp_obj_base_t base; + mp_obj_t corners; + mp_obj_t x, y, w, h, payload, version, ecc_level, mask, data_type, eci; +} py_qrcode_obj_t; + +static void py_qrcode_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { + py_qrcode_obj_t *self = self_in; + mp_printf(print, + "{\"x\":%d, \"y\":%d, \"w\":%d, \"h\":%d, \"payload\":\"%s\"," + " \"version\":%d, \"ecc_level\":%d, \"mask\":%d, \"data_type\":%d, \"eci\":%d}", + mp_obj_get_int(self->x), + mp_obj_get_int(self->y), + mp_obj_get_int(self->w), + mp_obj_get_int(self->h), + mp_obj_str_get_str(self->payload), + mp_obj_get_int(self->version), + mp_obj_get_int(self->ecc_level), + mp_obj_get_int(self->mask), + mp_obj_get_int(self->data_type), + mp_obj_get_int(self->eci)); +} + +static mp_obj_t py_qrcode_subscr(mp_obj_t self_in, mp_obj_t index, mp_obj_t value) { + if (value == MP_OBJ_SENTINEL) { + py_qrcode_obj_t *self = self_in; + if (MP_OBJ_IS_TYPE(index, &mp_type_slice)) { + mp_bound_slice_t slice; + if (!mp_seq_get_fast_slice_indexes(py_qrcode2_obj_size, index, &slice)) { + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("only slices with step=1 (aka None) are supported")); + } + mp_obj_tuple_t *result = mp_obj_new_tuple(slice.stop - slice.start, NULL); + mp_seq_copy(result->items, &(self->x) + slice.start, result->len, mp_obj_t); + return result; + } + switch (mp_get_index(self->base.type, py_qrcode2_obj_size, index, false)) { + case 0: + return self->x; + case 1: + return self->y; + case 2: + return self->w; + case 3: + return self->h; + case 4: + return self->payload; + case 5: + return self->version; + case 6: + return self->ecc_level; + case 7: + return self->mask; + case 8: + return self->data_type; + case 9: + return self->eci; + } + } + return MP_OBJ_NULL; // op not supported +} + +mp_obj_t py_qrcode_corners(mp_obj_t self_in) { + return ((py_qrcode_obj_t *)self_in)->corners; +} +static MP_DEFINE_CONST_FUN_OBJ_1(py_qrcode_corners_obj, py_qrcode_corners); + +mp_obj_t py_qrcode_rect(mp_obj_t self_in) { + return mp_obj_new_tuple(4, (mp_obj_t []) {((py_qrcode_obj_t *)self_in)->x, + ((py_qrcode_obj_t *)self_in)->y, + ((py_qrcode_obj_t *)self_in)->w, + ((py_qrcode_obj_t *)self_in)->h}); +} +static MP_DEFINE_CONST_FUN_OBJ_1(py_qrcode_rect_obj, py_qrcode_rect); + +mp_obj_t py_qrcode_x(mp_obj_t self_in) { + return ((py_qrcode_obj_t *)self_in)->x; +} +static MP_DEFINE_CONST_FUN_OBJ_1(py_qrcode_x_obj, py_qrcode_x); + +mp_obj_t py_qrcode_y(mp_obj_t self_in) { + return ((py_qrcode_obj_t *)self_in)->y; +} +static MP_DEFINE_CONST_FUN_OBJ_1(py_qrcode_y_obj, py_qrcode_y); + +mp_obj_t py_qrcode_w(mp_obj_t self_in) { + return ((py_qrcode_obj_t *)self_in)->w; +} +static MP_DEFINE_CONST_FUN_OBJ_1(py_qrcode_w_obj, py_qrcode_w); + +mp_obj_t py_qrcode_h(mp_obj_t self_in) { + return ((py_qrcode_obj_t *)self_in)->h; +} +static MP_DEFINE_CONST_FUN_OBJ_1(py_qrcode_h_obj, py_qrcode_h); + +mp_obj_t py_qrcode_payload(mp_obj_t self_in) { + return ((py_qrcode_obj_t *)self_in)->payload; +} +static MP_DEFINE_CONST_FUN_OBJ_1(py_qrcode_payload_obj, py_qrcode_payload); + +mp_obj_t py_qrcode_version(mp_obj_t self_in) { + return ((py_qrcode_obj_t *)self_in)->version; +} +static MP_DEFINE_CONST_FUN_OBJ_1(py_qrcode_version_obj, py_qrcode_version); + +mp_obj_t py_qrcode_ecc_level(mp_obj_t self_in) { + return ((py_qrcode_obj_t *)self_in)->ecc_level; +} +static MP_DEFINE_CONST_FUN_OBJ_1(py_qrcode_ecc_level_obj, py_qrcode_ecc_level); + +mp_obj_t py_qrcode_mask(mp_obj_t self_in) { + return ((py_qrcode_obj_t *)self_in)->mask; +} +static MP_DEFINE_CONST_FUN_OBJ_1(py_qrcode_mask_obj, py_qrcode_mask); + +mp_obj_t py_qrcode_data_type(mp_obj_t self_in) { + return ((py_qrcode_obj_t *)self_in)->data_type; +} +static MP_DEFINE_CONST_FUN_OBJ_1(py_qrcode_data_type_obj, py_qrcode_data_type); +mp_obj_t py_qrcode_eci(mp_obj_t self_in) { + return ((py_qrcode_obj_t *)self_in)->eci; +} +static MP_DEFINE_CONST_FUN_OBJ_1(py_qrcode_eci_obj, py_qrcode_eci); +mp_obj_t py_qrcode_is_numeric(mp_obj_t self_in) { + return mp_obj_new_bool(mp_obj_get_int(((py_qrcode_obj_t *)self_in)->data_type) == 1); +} +static MP_DEFINE_CONST_FUN_OBJ_1(py_qrcode_is_numeric_obj, py_qrcode_is_numeric); + +mp_obj_t py_qrcode_is_alphanumeric(mp_obj_t self_in) { + return mp_obj_new_bool(mp_obj_get_int(((py_qrcode_obj_t *)self_in)->data_type) == 2); +} +static MP_DEFINE_CONST_FUN_OBJ_1(py_qrcode_is_alphanumeric_obj, py_qrcode_is_alphanumeric); + +mp_obj_t py_qrcode_is_binary(mp_obj_t self_in) { + return mp_obj_new_bool(mp_obj_get_int(((py_qrcode_obj_t *)self_in)->data_type) == 4); +} +static MP_DEFINE_CONST_FUN_OBJ_1(py_qrcode_is_binary_obj, py_qrcode_is_binary); + +mp_obj_t py_qrcode_is_kanji(mp_obj_t self_in) { + return mp_obj_new_bool(mp_obj_get_int(((py_qrcode_obj_t *)self_in)->data_type) == 8); +} +static MP_DEFINE_CONST_FUN_OBJ_1(py_qrcode_is_kanji_obj, py_qrcode_is_kanji); + +static const mp_rom_map_elem_t py_qrcode_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_corners), MP_ROM_PTR(&py_qrcode_corners_obj) }, + { MP_ROM_QSTR(MP_QSTR_rect), MP_ROM_PTR(&py_qrcode_rect_obj) }, + { MP_ROM_QSTR(MP_QSTR_x), MP_ROM_PTR(&py_qrcode_x_obj) }, + { MP_ROM_QSTR(MP_QSTR_y), MP_ROM_PTR(&py_qrcode_y_obj) }, + { MP_ROM_QSTR(MP_QSTR_w), MP_ROM_PTR(&py_qrcode_w_obj) }, + { MP_ROM_QSTR(MP_QSTR_h), MP_ROM_PTR(&py_qrcode_h_obj) }, + { MP_ROM_QSTR(MP_QSTR_payload), MP_ROM_PTR(&py_qrcode_payload_obj) }, + { MP_ROM_QSTR(MP_QSTR_version), MP_ROM_PTR(&py_qrcode_version_obj) }, + { MP_ROM_QSTR(MP_QSTR_ecc_level), MP_ROM_PTR(&py_qrcode_ecc_level_obj) }, + { MP_ROM_QSTR(MP_QSTR_mask), MP_ROM_PTR(&py_qrcode_mask_obj) }, + { MP_ROM_QSTR(MP_QSTR_data_type), MP_ROM_PTR(&py_qrcode_data_type_obj) }, + { MP_ROM_QSTR(MP_QSTR_eci), MP_ROM_PTR(&py_qrcode_eci_obj) }, + { MP_ROM_QSTR(MP_QSTR_is_numeric), MP_ROM_PTR(&py_qrcode_is_numeric_obj) }, + { MP_ROM_QSTR(MP_QSTR_is_alphanumeric), MP_ROM_PTR(&py_qrcode_is_alphanumeric_obj) }, + { MP_ROM_QSTR(MP_QSTR_is_binary), MP_ROM_PTR(&py_qrcode_is_binary_obj) }, + { MP_ROM_QSTR(MP_QSTR_is_kanji), MP_ROM_PTR(&py_qrcode_is_kanji_obj) } +}; + +static MP_DEFINE_CONST_DICT(py_qrcode_locals_dict, py_qrcode_locals_dict_table); + +static MP_DEFINE_CONST_OBJ_TYPE( + py_qrcode_type, + MP_QSTR_qrcode, + MP_TYPE_FLAG_NONE, + print, py_qrcode_print, + subscr, py_qrcode_subscr, + locals_dict, &py_qrcode_locals_dict + ); + + +typedef union { + uint16_t val; + struct { + uint16_t b : 5; + uint16_t g : 6; + uint16_t r : 5; + }; +} rgb565_t; + +static uint8_t rgb565_to_grayscale(const uint8_t *img) { + uint16_t *img_16 = (uint16_t *)img; + rgb565_t rgb = {.val = __builtin_bswap16(*img_16)}; + uint16_t val = (rgb.r * 8 + rgb.g * 4 + rgb.b * 8) / 3; + return (uint8_t)MIN(255, val); +} + +static void rgb565_to_grayscale_buf(const uint8_t *src, uint8_t *dst, int qr_width, int qr_height) { + for (size_t y = 0; y < qr_height; y++) { + for (size_t x = 0; x < qr_width; x++) { + dst[y * qr_width + x] = rgb565_to_grayscale(&src[(y * qr_width + x) * 2]); + } + } +} + +static struct quirc *qr_decoder; +struct quirc_code code = {}; +struct quirc_data qr_data = {}; +static mp_obj_t py_image_find_qrcodes(size_t n_args, const mp_obj_t *args, mp_map_t *kw_args) { + image_t *img = py_image_cobj(args[0]); + + qr_decoder = quirc_new(); + if (!qr_decoder) { + return mp_const_none; + } + + if (quirc_resize(qr_decoder, img->w, img->h) < 0) { + return mp_const_none; + } + + uint8_t *qr_buf = quirc_begin(qr_decoder, NULL, NULL); + + // Convert the frame to grayscale. We could have asked the camera for a grayscale frame, + // but then the image on the display would be grayscale too. + rgb565_to_grayscale_buf(img->data, qr_buf, img->w, img->h); + + // Process the frame. This step find the corners of the QR code (capstones) + quirc_end(qr_decoder); + + int count = quirc_count(qr_decoder); + quirc_decode_error_t err = QUIRC_ERROR_DATA_UNDERFLOW; + + mp_obj_list_t *objects_list = mp_obj_new_list(count, NULL); + for (size_t i = 0; i < count; i++) { + // Extract raw QR code binary data (values of black/white modules) + quirc_extract(qr_decoder, i, &code); + + // Decode the raw data. This step also performs error correction. + err = quirc_decode(&code, &qr_data); + if (err == QUIRC_ERROR_DATA_ECC) { + quirc_flip(&code); + err = quirc_decode(&code, &qr_data); + } + if (err != 0) { + continue; + } + + py_qrcode_obj_t *o = m_new_obj(py_qrcode_obj_t); + o->base.type = &py_qrcode_type; + o->corners = mp_obj_new_tuple(4, (mp_obj_t []) { + mp_obj_new_tuple(2, (mp_obj_t []) { mp_obj_new_int(code.corners[0].x), mp_obj_new_int(code.corners[0].y) }), + mp_obj_new_tuple(2, (mp_obj_t []) { mp_obj_new_int(code.corners[1].x), mp_obj_new_int(code.corners[1].y) }), + mp_obj_new_tuple(2, (mp_obj_t []) { mp_obj_new_int(code.corners[2].x), mp_obj_new_int(code.corners[2].y) }), + mp_obj_new_tuple(2, (mp_obj_t []) { mp_obj_new_int(code.corners[3].x), mp_obj_new_int(code.corners[3].y) }) + }); + int min_x = code.corners[0].x; + int min_y = code.corners[0].y; + int max_x = code.corners[0].x; + int max_y = code.corners[0].y; + for (int i = 1; i < 4; i++) { + if (code.corners[i].x < min_x) { + min_x = code.corners[i].x; + } + if (code.corners[i].y < min_y) { + min_y = code.corners[i].y; + } + if (code.corners[i].x > max_x) { + max_x = code.corners[i].x; + } + if (code.corners[i].y > max_y) { + max_y = code.corners[i].y; + } + } + o->x = mp_obj_new_int(min_x); + o->y = mp_obj_new_int(min_y); + o->w = mp_obj_new_int(max_x - min_x + 1); + o->h = mp_obj_new_int(max_y - min_y + 1); + o->payload = mp_obj_new_str((const char *)qr_data.payload, strlen((const char *)qr_data.payload)); + o->version = mp_obj_new_int(qr_data.version); + o->ecc_level = mp_obj_new_int(qr_data.ecc_level); + o->mask = mp_obj_new_int(qr_data.mask); + o->data_type = mp_obj_new_int(qr_data.data_type); + o->eci = mp_obj_new_int(qr_data.eci); + objects_list->items[i] = o; + } + + quirc_destroy(qr_decoder); + + return objects_list; +} +static MP_DEFINE_CONST_FUN_OBJ_KW(py_image_find_qrcodes_obj, 1, py_image_find_qrcodes); mp_obj_t py_image(int w, int h, omv_pixformat_t pixfmt, uint32_t size, void *pixels) { py_image_obj_t *o = m_new_obj(py_image_obj_t); @@ -379,24 +669,25 @@ mp_obj_t py_image_from_struct(image_t *img) { return o; } - static const mp_rom_map_elem_t locals_dict_table[] = { /* Basic Methods */ - {MP_ROM_QSTR(MP_QSTR_width), MP_ROM_PTR(&py_image_width_obj)}, - {MP_ROM_QSTR(MP_QSTR_height), MP_ROM_PTR(&py_image_height_obj)}, - {MP_ROM_QSTR(MP_QSTR_format), MP_ROM_PTR(&py_image_format_obj)}, - {MP_ROM_QSTR(MP_QSTR_size), MP_ROM_PTR(&py_image_size_obj)}, - {MP_ROM_QSTR(MP_QSTR_bytearray), MP_ROM_PTR(&py_image_bytearray_obj)}, + {MP_ROM_QSTR(MP_QSTR_width), MP_ROM_PTR(&py_image_width_obj)}, + {MP_ROM_QSTR(MP_QSTR_height), MP_ROM_PTR(&py_image_height_obj)}, + {MP_ROM_QSTR(MP_QSTR_format), MP_ROM_PTR(&py_image_format_obj)}, + {MP_ROM_QSTR(MP_QSTR_size), MP_ROM_PTR(&py_image_size_obj)}, + {MP_ROM_QSTR(MP_QSTR_bytearray), MP_ROM_PTR(&py_image_bytearray_obj)}, /* Drawing Methods */ - {MP_ROM_QSTR(MP_QSTR_clear), MP_ROM_PTR(&py_image_clear_obj)}, - {MP_ROM_QSTR(MP_QSTR_draw_line), MP_ROM_PTR(&py_image_draw_line_obj)}, - {MP_ROM_QSTR(MP_QSTR_draw_rectangle), MP_ROM_PTR(&py_image_draw_rectangle_obj)}, - {MP_ROM_QSTR(MP_QSTR_draw_circle), MP_ROM_PTR(&py_image_draw_circle_obj)}, - {MP_ROM_QSTR(MP_QSTR_draw_ellipse), MP_ROM_PTR(&py_image_draw_ellipse_obj)}, - {MP_ROM_QSTR(MP_QSTR_draw_string), MP_ROM_PTR(&py_image_draw_string_obj)}, - {MP_ROM_QSTR(MP_QSTR_draw_cross), MP_ROM_PTR(&py_image_draw_cross_obj)}, - {MP_ROM_QSTR(MP_QSTR_draw_arrow), MP_ROM_PTR(&py_image_draw_arrow_obj)}, - {MP_ROM_QSTR(MP_QSTR_draw_edges), MP_ROM_PTR(&py_image_draw_edges_obj)}, + {MP_ROM_QSTR(MP_QSTR_clear), MP_ROM_PTR(&py_image_clear_obj)}, + {MP_ROM_QSTR(MP_QSTR_draw_line), MP_ROM_PTR(&py_image_draw_line_obj)}, + {MP_ROM_QSTR(MP_QSTR_draw_rectangle), MP_ROM_PTR(&py_image_draw_rectangle_obj)}, + {MP_ROM_QSTR(MP_QSTR_draw_circle), MP_ROM_PTR(&py_image_draw_circle_obj)}, + {MP_ROM_QSTR(MP_QSTR_draw_ellipse), MP_ROM_PTR(&py_image_draw_ellipse_obj)}, + {MP_ROM_QSTR(MP_QSTR_draw_string), MP_ROM_PTR(&py_image_draw_string_obj)}, + {MP_ROM_QSTR(MP_QSTR_draw_cross), MP_ROM_PTR(&py_image_draw_cross_obj)}, + {MP_ROM_QSTR(MP_QSTR_draw_arrow), MP_ROM_PTR(&py_image_draw_arrow_obj)}, + {MP_ROM_QSTR(MP_QSTR_draw_edges), MP_ROM_PTR(&py_image_draw_edges_obj)}, + /* Find Methods */ + {MP_ROM_QSTR(MP_QSTR_find_qrcodes), MP_ROM_PTR(&py_image_find_qrcodes_obj)}, }; static MP_DEFINE_CONST_DICT(py_image_locals_dict, locals_dict_table); @@ -408,12 +699,9 @@ MP_DEFINE_CONST_OBJ_TYPE( locals_dict, &py_image_locals_dict ); - - // ================================================================================== // module: image // ================================================================================== - static const mp_rom_map_elem_t globals_dict_table[] = { {MP_ROM_QSTR(MP_QSTR___name__), MP_OBJ_NEW_QSTR(MP_QSTR_image)}, // Pixel formats diff --git a/m5stack/cmodules/omv/omv_atoms3r_cam.cmake b/m5stack/cmodules/omv/omv_atoms3r_cam.cmake index 9ea9ac74..ac334bdc 100644 --- a/m5stack/cmodules/omv/omv_atoms3r_cam.cmake +++ b/m5stack/cmodules/omv/omv_atoms3r_cam.cmake @@ -35,6 +35,8 @@ target_include_directories(moudle_omv INTERFACE ${CMAKE_CURRENT_LIST_DIR}/../../components/esp32-camera/sensors/private_include # esp-code-scanner ${CMAKE_CURRENT_LIST_DIR}/../../components/esp-code-scanner/include + # quirc + ${CMAKE_CURRENT_LIST_DIR}/../../managed_components/espressif__quirc/quirc/lib ) # Link our INTERFACE library to the usermod target. diff --git a/m5stack/main/idf_component.yml b/m5stack/main/idf_component.yml index 9a1007cb..5b58b9d5 100644 --- a/m5stack/main/idf_component.yml +++ b/m5stack/main/idf_component.yml @@ -16,3 +16,4 @@ dependencies: rules: - if: "target in [esp32p4]" version: 0.8.5 + espressif/quirc: "1.2.0" From 01e186d14f386918ebaba8c34ad454902163b000 Mon Sep 17 00:00:00 2001 From: tinyu Date: Wed, 3 Sep 2025 12:13:14 +0800 Subject: [PATCH 243/322] lib/m5ui: Add LVGL Win widget. Signed-off-by: tinyu --- m5stack/libs/m5ui/__init__.py | 1 + m5stack/libs/m5ui/manifest.py | 1 + m5stack/libs/m5ui/win.py | 199 ++++++++++++++++++++++++++++++++++ 3 files changed, 201 insertions(+) create mode 100644 m5stack/libs/m5ui/win.py diff --git a/m5stack/libs/m5ui/__init__.py b/m5stack/libs/m5ui/__init__.py index aa3dcd05..d7b38c6a 100644 --- a/m5stack/libs/m5ui/__init__.py +++ b/m5stack/libs/m5ui/__init__.py @@ -25,6 +25,7 @@ "M5Spinbox": "spinbox", "M5Switch": "switch", "M5TextArea": "textarea", + "M5Win": "win", } diff --git a/m5stack/libs/m5ui/manifest.py b/m5stack/libs/m5ui/manifest.py index f4760420..cdde9d26 100644 --- a/m5stack/libs/m5ui/manifest.py +++ b/m5stack/libs/m5ui/manifest.py @@ -28,6 +28,7 @@ "spinbox.py", "switch.py", "textarea.py", + "win.py", ), base_path="..", opt=0, diff --git a/m5stack/libs/m5ui/win.py b/m5stack/libs/m5ui/win.py new file mode 100644 index 00000000..a4df488f --- /dev/null +++ b/m5stack/libs/m5ui/win.py @@ -0,0 +1,199 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +from m5ui.base import M5Base +import lvgl as lv +import m5ui + + +class M5Win(lv.win): + """Create a window object. + + :param int x: The x position of the window. + :param int y: The y position of the window. + :param int w: The width of the window. + :param int h: The height of the window. + :param lv.obj parent: The parent object to attach the window to. If not specified, the window will be attached to the default screen. + + UiFlow2 Code Block: + + None + + MicroPython Code Block: + + .. code-block:: python + + from m5ui import M5Win + import lvgl as lv + + m5ui.init() + win0 = M5Win(x=120, y=80, w=60, h=30, parent=page0) + """ + + def __init__( + self, + x=0, + y=0, + w=0, + h=0, + parent=None, + ): + if parent is None: + parent = lv.screen_active() + super().__init__(parent) + self.set_pos(x, y) + self.set_size(w, h) + + def add_title( + self, + text, + text_c=0x212121, + text_opa=255, + bg_c=0xE0E0E0, + bg_opa=255, + font=lv.font_montserrat_14, + ): + """Add a title label to the window. + + :param str text: The text to display on the window. + :param int text_c: The text color of the label in hexadecimal format. + :param int text_opa: The text opacity of the label (0-255). + :param int bg_c: The background color of the label in hexadecimal format. + :param int bg_opa: The background opacity of the label (0-255). + :param lv.font font: The font to use for the label. + :return: The created label object :ref:`m5ui.M5Label `. + :rtype: lv.obj + + UiFlow2 Code Block: + + |add_title.png| + + MicroPython Code Block: + + .. code-block:: python + + win0.add_title("A title", text_c=0x212121, text_opa=255, bg_c=0xE0E0E0, bg_opa=255, font=lv.font_montserrat_14) + """ + _label = m5ui.M5Label( + text=text, + text_c=text_c, + bg_c=bg_c, + bg_opa=bg_opa, + font=font, + parent=self.get_header(), + ) + _label.set_long_mode(lv.label.LONG_MODE.DOTS) + _label.set_flex_grow(1) + _label.set_style_text_opa(text_opa, lv.PART.MAIN | lv.STATE.DEFAULT) + return _label + + def add_text( + self, + text, + x=0, + y=0, + text_c=0x212121, + text_opa=255, + bg_c=0xF6F6F6, + bg_opa=255, + font=lv.font_montserrat_14, + ): + """Add a text label to the window. + + :param str text: The text to display on the window. + :param int x: The x position of the label. + :param int y: The y position of the label. + :param int text_c: The text color of the label in hexadecimal format. + :param int text_opa: The text opacity of the label (0-255). + :param int bg_c: The background color of the label in hexadecimal format. + :param int bg_opa: The background opacity of the label (0-255). + :param lv.font font: The font to use for the label. + :return: The created label object :ref:`m5ui.M5Label `. + :rtype: lv.obj + + UiFlow2 Code Block: + + |add_text.png| + + MicroPython Code Block: + + .. code-block:: python + + win0.add_text("A title", text_c=0x212121, text_opa=255, bg_c=0xF6F6F6, bg_opa=255, font=lv.font_montserrat_14) + """ + _label = m5ui.M5Label( + text=text, + x=x, + y=y, + text_c=text_c, + bg_c=bg_c, + bg_opa=bg_opa, + font=font, + parent=self.get_content(), + ) + _label.set_style_text_opa(text_opa, lv.PART.MAIN | lv.STATE.DEFAULT) + return _label + + def add_button( + self, + icon=None, + text="", + w=lv.DPI_DEF // 3, + bg_c=0x2196F3, + bg_opa=255, + text_c=0xFFFFFF, + text_opa=255, + font=lv.font_montserrat_14, + ): + """Add a button to the window. + + :param int icon: The icon to display on the button. + :param str text: The text to display on the button. + :param int h: The height of the button. + :param int bg_c: The background color of the button in hexadecimal format. + :param int bg_opa: The background opacity of the button (0-255). + :param int text_c: The text color of the button in hexadecimal format. + :param int text_opa: The text opacity of the button (0-255). + :param lv.font font: The font to use for the button text. + :return: The created button object :ref:`m5ui.M5Button `. + :rtype: lv.obj + + UiFlow2 Code Block: + + |add_button.png| + |add_button2.png| + + MicroPython Code Block: + + .. code-block:: python + + win0.add_button(lv.SYMBOL.BULLET, text="", w=40, bg_c=0x2196F3) + win0.add_button(text="btn", w=40, bg_c=0x2196F3, bg_opa=255, text_c=0xFFFFFF, text_opa=255, font=lv.font_montserrat_14) + """ + _h = lv.pct(100) + _button = m5ui.M5Button( + text=text, + w=w, + h=_h, + bg_c=bg_c, + text_c=text_c, + font=font, + parent=self.get_header(), + ) + _button.set_style_bg_opa(bg_opa, lv.PART.MAIN | lv.STATE.DEFAULT) + _button.set_style_text_opa(text_opa, lv.PART.MAIN | lv.STATE.DEFAULT) + + if icon is not None: + _icon = lv.image(_button) + _icon.set_src(icon) + _icon.set_align(lv.ALIGN.CENTER) + return _button + + def __getattr__(self, name): + if hasattr(M5Base, name): + method = getattr(M5Base, name) + bound_method = lambda *args, **kwargs: method(self, *args, **kwargs) + setattr(self, name, bound_method) + return bound_method + raise AttributeError(f"'{self.__class__.__name__}' object has no attribute '{name}'") From 872d1e53e848490e19cc397c29a55787986465b7 Mon Sep 17 00:00:00 2001 From: tinyu Date: Fri, 5 Sep 2025 16:54:47 +0800 Subject: [PATCH 244/322] lib/m5ui: Add LVGL Win widget docs and example. Signed-off-by: tinyu --- docs/en/m5ui/index.rst | 1 + docs/en/m5ui/win.rst | 469 ++++++++++ docs/en/refs/m5ui.win.ref | 52 ++ docs/locales/zh_CN/LC_MESSAGES/m5ui/win.po | 876 ++++++++++++++++++ .../m5ui/window/window_core2_example.m5f2 | 1 + examples/m5ui/window/window_core2_example.py | 101 ++ m5stack/libs/m5ui/win.py | 17 +- 7 files changed, 1514 insertions(+), 3 deletions(-) create mode 100644 docs/en/m5ui/win.rst create mode 100644 docs/en/refs/m5ui.win.ref create mode 100644 docs/locales/zh_CN/LC_MESSAGES/m5ui/win.po create mode 100644 examples/m5ui/window/window_core2_example.m5f2 create mode 100644 examples/m5ui/window/window_core2_example.py diff --git a/docs/en/m5ui/index.rst b/docs/en/m5ui/index.rst index d3bc4561..65bde043 100644 --- a/docs/en/m5ui/index.rst +++ b/docs/en/m5ui/index.rst @@ -78,3 +78,4 @@ Classes spinbox.rst switch.rst textarea.rst + win.rst diff --git a/docs/en/m5ui/win.rst b/docs/en/m5ui/win.rst new file mode 100644 index 00000000..5211927e --- /dev/null +++ b/docs/en/m5ui/win.rst @@ -0,0 +1,469 @@ +.. currentmodule:: m5ui + +M5Window +======== + +.. include:: ../refs/m5ui.win.ref + +M5Window is a widget that can be used to create windows in the user interface. + +UiFlow2 Example +--------------- + +window event +^^^^^^^^^^^^^^ + +Open the |window_core2_example.m5f2| project in UiFlow2. + +This example creates a window and associates it with events. + +UiFlow2 Code Block: + + |example.png| + +Example output: + + None + + +MicroPython Example +------------------- + +window event +^^^^^^^^^^^^^^ + +This example creates a window and associates it with events. + +MicroPython Code Block: + + .. literalinclude:: ../../../examples/m5ui/window/window_core2_example.py + :language: python + :linenos: + +Example output: + + None + + +**API** +------- +M5Win +^^^^^^^^ + +.. autoclass:: m5ui.win.M5Win + :members: + + .. py:method:: delete() + + Delete the item from the window. + + UiFlow2 Code Block: + + |label_delete.png| + + |button_delete.png| + + |title_delete.png| + + MicroPython Code Block: + + .. code-block:: python + + label_0.delete() + + button_0.delete() + + text_0.delete() + + + .. py:method:: set_text(txt) + + Set text of the window button/label/title. + + :param str txt: The text to set for the window button/label/title. + :return: None + + UiFlow2 Code Block: + + |button_set_text.png| + + |label_set_text.png| + + |title_set_text.png| + + MicroPython Code Block: + + .. code-block:: python + + button_0.set_text("Select an option") + + label_0.set_text("M5Stack") + + title_0.set_text("Hello M5Stack") + + .. py:method:: set_style_text_font(font, part) + + Set the font of the window button text. + + :param lv.lv_font_t font: The font to set. + :param int part: The part of the object to apply the style to (e.g., lv.PART.MAIN). + :return: None + + UiFlow2 Code Block: + + |button_set_font.png| + + MicroPython Code Block: + + .. code-block:: python + + button_0.set_style_text_font(lv.font_montserrat_14, lv.PART.MAIN | lv.STATE.DEFAULT) + + .. py:method:: set_text_color(color, opa, part) + + Set the color of the window button/label/title. + + :param int color: The color to set. + :param int opa: The opacity of the color. + :param int part: The part of the object to apply the style to (e.g., lv.PART.MAIN). + :return: None + + UiFlow2 Code Block: + + |button_set_text_color.png| + + |label_set_text_color.png| + + |title_set_text_color.png| + + MicroPython Code Block: + + .. code-block:: python + + button_0.set_text_color(lv.color_hex(0x000000), 255, lv.PART.MAIN | lv.STATE.DEFAULT) + + label_0.set_text_color(lv.color_hex(0x000000), 255, lv.PART.MAIN | lv.STATE.DEFAULT) + + title_0.set_text_color(lv.color_hex(0x000000), 255, lv.PART.MAIN | lv.STATE.DEFAULT) + + .. py:method:: set_bg_color(color, opa, part) + + Set the background color of the window button/label/title. + + :param int color: The color to set. + :param int opa: The opacity of the color. + :param int part: The part of the object to apply the style to (e.g., lv.PART.MAIN). + :return: None + + UiFlow2 Code Block: + + |button_set_bg_color.png| + + |label_set_bg_color.png| + + |title_set_bg_color.png| + + MicroPython Code Block: + + .. code-block:: python + + button_0.set_bg_color(lv.color_hex(0x000000), 255, lv.PART.MAIN | lv.STATE.DEFAULT) + + label_0.set_bg_color(lv.color_hex(0x000000), 255, lv.PART.MAIN | lv.STATE.DEFAULT) + + title_0.set_bg_color(lv.color_hex(0x000000), 255, lv.PART.MAIN | lv.STATE.DEFAULT) + + .. py:method:: set_long_mode(mode) + + Set the long mode of the window label/title. + + :param int mode: The long mode to set. + + UiFlow2 Code Block: + + |label_set_long_mode.png| + + |title_set_long_mode.png| + + MicroPython Code Block: + + .. code-block:: python + + label_0.set_long_mode(lv.label.LONG_MODE.WRAP) + + .. py:method:: set_flag(flag, value) + + Set a flag on the object. If ``value`` is True, the flag is added; if False, the flag is removed. + + :param int flag: The flag to set. + :param bool value: If True, the flag is added; if False, the flag is removed. + + UiFlow2 Code Block: + + |button_set_flag.png| + + |label_set_flag.png| + + |title_set_flag.png| + + MicroPython Code Block: + + .. code-block:: python + + window_0.set_flag(lv.obj.FLAG.HIDDEN, True) + + .. py:method:: set_pos(x, y) + + Set the position of the window. + + :param int x: The x-coordinate of the window. + :param int y: The y-coordinate of the window. + + UiFlow2 Code Block: + + |set_pos.png| + + MicroPython Code Block: + + .. code-block:: python + + window_0.set_pos(100, 100) + + .. py:method:: set_x(x) + + Set the x-coordinate of the window. + + :param int x: The x-coordinate of the window. + + UiFlow2 Code Block: + + |set_x.png| + + MicroPython Code Block: + + .. code-block:: python + + window_0.set_x(100) + + .. py:method:: set_y(y) + + Set the y-coordinate of the window. + + :param int y: The y-coordinate of the window. + + UiFlow2 Code Block: + + |set_y.png| + + MicroPython Code Block: + + .. code-block:: python + + window_0.set_y(100) + + .. py:method:: set_size(width, height) + + Set the size of the window. + + :param int width: The width of the window. + :param int height: The height of the window. + :return: None + + UiFlow2 Code Block: + + |set_size.png| + + MicroPython Code Block: + + .. code-block:: python + + window_0.set_size(100, 50) + + + .. py:method:: align_to(obj, align, x, y) + + Align the windowto another object. + + :param lv.obj obj: The object to align to. + :param int align: The alignment type. + :param int x: The x-offset from the aligned object. + :param int y: The y-offset from the aligned object. + + UiFlow2 Code Block: + + |align_to.png| + + MicroPython Code Block: + + .. code-block:: python + + window_0.align_to(page_0, lv.ALIGN.CENTER, 0, 0) + + .. py:method:: set_state(state, value) + + Set the state of the bar. If ``value`` is True, the state is set; if False, the state is unset. + + :param int state: The state to set. + :param bool value: If True, the state is set; if False, the state is unset. + :return: None + + UiFlow2 Code Block: + + |button_set_state.png| + + MicroPython Code Block: + + .. code-block:: python + + window_0.set_state(lv.STATE.PRESSED, True) + + .. py:method:: toggle_flag(flag) + + Toggle a flag on the object. If the flag is set, it is removed; if not set, it is added. + + :param int flag: The flag to toggle. + :return: None + + UiFlow2 Code Block: + + |button_toggle_flag.png| + + MicroPython Code Block: + + .. code-block:: python + + window_0.toggle_flag(lv.obj.FLAG.HIDDEN) + + + .. py:method:: set_style_radius(radius, part) + + Set the corner radius of the window button. + + :param int radius: The radius to set. + :param int part: The part of the object to apply the style to (e.g., lv.PART.MAIN). + :return: None + + UiFlow2 Code Block: + + |button_set_radius.png| + + MicroPython Code Block: + + .. code-block:: python + + button_0.set_style_radius(10, lv.PART.MAIN | lv.STATE.DEFAULT) + + .. py:method:: set_shadow(color, opa, align, offset_x, offset_y) + + Set a shadow for the label/title. + + :param int color: The color of the shadow in hexadecimal format or an integer. + :param int opa: The opacity of the shadow (0-255). + :param int align: The alignment of the shadow relative to the label/title. + :param int offset_x: The horizontal offset of the shadow. + :param int offset_y: The vertical offset of the shadow. + :return: None + + UiFlow2 Code Block: + + |label_set_shadow.png| + + |title_set_shadow.png| + + MicroPython Code Block: + + .. code-block:: python + + label_0.set_shadow(color=0x000000, opa=128, align=lv.ALIGN.BOTTOM_RIGHT, offset_x=5, offset_y=5) + + .. py:method:: unset_shadow() + + Remove the shadow from the label/title. + + UiFlow2 Code Block: + + |label_unset_shadow.png| + + |title_unset_shadow.png| + + MicroPython Code Block: + + .. code-block:: python + + label_0.unset_shadow() + + .. py:method:: get_text() + + Get the text of the button/label/title. + + :return: The text of the button/label/title. + :rtype: str + + UiFlow2 Code Block: + + |button_get_text.png| + + |label_get_text.png| + + |title_get_text.png| + + MicroPython Code Block: + + .. code-block:: python + + button_0.get_text() + + label_0.get_text() + + text_0.get_text() + + + .. py:method:: toggle_state(state) + + Toggle the state of the button. If the state is set, it is unset; if not set, it is set. + + :param int state: The state to toggle. + :return: None + + UiFlow2 Code Block: + + |button_toggle_state.png| + + MicroPython Code Block: + + .. code-block:: python + + button_0.toggle_state(lv.STATE.PRESSED) + + .. py:method:: add_event_cb(handler, event, user_data) + + Add an event callback to the button. The callback will be called when the specified event occurs. + + :param function handler: The callback function to call. + :param int event: The event to listen for. + :param Any user_data: Optional user data to pass to the callback. + :return: None + + UiFlow2 Code Block: + + |button_event.png| + + MicroPython Code Block: + + .. code-block:: python + + def btn_ono_clicked_event(event_struct): + global page0, window_0, label_lkg, btn_ono, btn_pjm, label0 + + print('hello M5') + + + def btn_ono_event_handler(event_struct): + global page0, window_0, label_lkg, btn_ono, btn_pjm, label0 + event = event_struct.code + if event == lv.EVENT.CLICKED and True: + btn_ono_clicked_event(event_struct) + return + + btn_ono.add_event_cb(btn_ono_event_handler, lv.EVENT.ALL, None) + diff --git a/docs/en/refs/m5ui.win.ref b/docs/en/refs/m5ui.win.ref new file mode 100644 index 00000000..e2a8da0c --- /dev/null +++ b/docs/en/refs/m5ui.win.ref @@ -0,0 +1,52 @@ +.. |add_button.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/window/add_button.png +.. |add_button2.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/window/add_button2.png +.. |add_title.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/window/add_title.png +.. |add_title2.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/window/add_title2.png +.. |add_text.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/window/add_text.png +.. |add_text2.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/window/add_text2.png +.. |label_delete.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/window/label_delete.png +.. |button_delete.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/window/button_delete.png +.. |title_delete.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/window/title_delete.png +.. |button_set_text.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/window/button_set_text.png +.. |label_set_text.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/window/label_set_text.png +.. |title_set_text.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/window/title_set_text.png +.. |button_set_font.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/window/button_set_font.png +.. |button_set_text_color.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/window/button_set_text_color.png +.. |label_set_text_color.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/window/label_set_text_color.png +.. |title_set_text_color.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/window/title_set_text_color.png +.. |button_set_bg_color.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/window/button_set_bg_color.png +.. |label_set_bg_color.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/window/label_set_bg_color.png +.. |title_set_bg_color.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/window/title_set_bg_color.png +.. |label_set_long_mode.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/window/label_set_long_mode.png +.. |title_set_long_mode.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/window/title_set_long_mode.png +.. |button_set_flag.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/window/button_set_flag.png +.. |label_set_flag.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/window/label_set_flag.png +.. |title_set_flag.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/window/title_set_flag.png +.. |set_pos.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/window/set_pos.png +.. |set_x.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/window/set_x.png +.. |set_y.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/window/set_y.png +.. |set_size.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/window/set_size.png +.. |align_to.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/window/align_to.png +.. |button_set_state.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/window/button_set_state.png +.. |button_toggle_flag.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/window/button_toggle_flag.png +.. |button_set_radius.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/window/button_set_radius.png +.. |label_set_shadow.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/window/label_set_shadow.png +.. |title_set_shadow.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/window/title_set_shadow.png +.. |label_unset_shadow.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/window/label_unset_shadow.png +.. |title_unset_shadow.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/window/title_unset_shadow.png +.. |button_get_text.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/window/button_get_text.png +.. |label_get_text.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/window/label_get_text.png +.. |title_get_text.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/window/title_get_text.png +.. |button_toggle_state.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/window/button_toggle_state.png +.. |button_event.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/window/button_event.png + +.. |example.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/window/example.png + +.. |window_core2_example.m5f2| raw:: html + + + window_core2_example.m5f2 + \ No newline at end of file diff --git a/docs/locales/zh_CN/LC_MESSAGES/m5ui/win.po b/docs/locales/zh_CN/LC_MESSAGES/m5ui/win.po new file mode 100644 index 00000000..d4c5a3a7 --- /dev/null +++ b/docs/locales/zh_CN/LC_MESSAGES/m5ui/win.po @@ -0,0 +1,876 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2016 - 2025 M5Stack Technology Co., Ltd +# This file is distributed under the same license as the UIFlow2 Programming +# Guide package. +# FIRST AUTHOR , 2025. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: UIFlow2 Programming Guide \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-09-05 16:23+0800\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: zh_CN\n" +"Language-Team: zh_CN \n" +"Plural-Forms: nplurals=1; plural=0;\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.16.0\n" + +#: ../../en/m5ui/win.rst:4 5620992a311d4cd798d37f5884821191 +msgid "M5Window" +msgstr "" + +#: ../../en/m5ui/win.rst:8 b8ccbd2a8c0c4a3dac3a86a426372fe0 +msgid "" +"M5Window is a widget that can be used to create windows in the user " +"interface." +msgstr "M5Window 是一个可用于在用户界面中创建 window 的组件。" + +#: ../../en/m5ui/win.rst:11 4e8b73665c7148a4a0cd8bdf6fb77396 +msgid "UiFlow2 Example" +msgstr "UiFlow2 示例" + +#: ../../en/m5ui/win.rst:14 ../../en/m5ui/win.rst:33 +#: 08135837d63d458881799d17bb0e815f 50de28be4c1e47c4a79c784a6db2d2ff +msgid "window event" +msgstr "window 事件" + +#: ../../en/m5ui/win.rst:16 ce32af2725cf4dda89fcbc523d27fc7c +msgid "Open the |window_core2_example.m5f2| project in UiFlow2." +msgstr "在 UiFlow2 中打开 |window_core2_example.m5f2| 项目。" + +#: ../../en/m5ui/win.rst:18 ../../en/m5ui/win.rst:35 +#: 22447cf9be55460685f01d6f0342b647 809aca67853547ab80350babb782b570 +msgid "This example creates a window and associates it with events." +msgstr "此示例创建一个 window 并将其与事件关联。" + +#: ../../en/m5ui/win.rst:20 ../../en/m5ui/win.rst:71 ../../en/m5ui/win.rst:89 +#: ../../en/m5ui/win.rst:115 ../../en/m5ui/win.rst:141 +#: ../../en/m5ui/win.rst:160 ../../en/m5ui/win.rst:187 +#: ../../en/m5ui/win.rst:211 ../../en/m5ui/win.rst:230 +#: ../../en/m5ui/win.rst:251 ../../en/m5ui/win.rst:267 +#: ../../en/m5ui/win.rst:283 ../../en/m5ui/win.rst:301 +#: ../../en/m5ui/win.rst:321 ../../en/m5ui/win.rst:339 +#: ../../en/m5ui/win.rst:356 ../../en/m5ui/win.rst:375 +#: ../../en/m5ui/win.rst:396 ../../en/m5ui/win.rst:412 +#: ../../en/m5ui/win.rst:431 ../../en/m5ui/win.rst:457 +#: ../../en/m5ui/win.rst:476 0bea274951e94e298dd230b54be366af +#: 146116ab0de34e22a8ae3464d817e2d0 1dc627c178604f689b098b2036181812 +#: 2e9f1f32b70348aaa013cc375f020744 3118c5f78b1042e084c01af42323ebee +#: 3c21384bd9064a4fbf9c6ebf272cd8af 3ed05e66e0a8483ca2eae78be59cf552 +#: 44aaf5f179044dde92393c4e67546390 4862c4a1e2644ac1b44b05f396a8697a +#: 48db3a17f37c4d44b2491d558e4c2aab 4a5cdde192464335a388070bd815f371 +#: 4c09e32898f243e0a735cde335a11ceb 5353af56b1da40d797c3fa3b70d7658f +#: 705b6e04ec344fddbdc0c38cb403b52b 706f61d1965748a292e9471ce4b381a8 +#: 824fa447eb644b6c8f2b001ae7f01a58 87e1b4e1e3de46a2acc95dacd3260ccf +#: 98b4ee35143f4291b8be3843ae391535 9cec1bf47a5740a9bd00e5e38f12181a +#: a5df161859bd4fe6a5375f8de3212110 b82f8c2548bf4771898cc33547ff59dd +#: c6e64b8beb6443579f2f15b3be71cd02 d2fb70102f554864a18c34cf08309929 +#: d8b8703f7cf1403bb22590f049764189 e107b8760b4b44c59d5fce26c0f86d7d +#: f6ec6d16898e44e3b8a3d4bff3849a0a m5ui.win.M5Win:9 +#: m5ui.win.M5Win.add_button:14 m5ui.win.M5Win.add_text:14 +#: m5ui.win.M5Win.add_title:12 of +msgid "UiFlow2 Code Block:" +msgstr "UiFlow2 代码块:" + +#: ../../en/m5ui/win.rst:22 e2f36e6fec0e4bb0b7bbe8e414bff490 +msgid "|example.png|" +msgstr "" + +#: ../../en/refs/m5ui.win.ref:43 28fa7b13abad429ba74faf42f92806b3 +msgid "example.png" +msgstr "" + +#: ../../en/m5ui/win.rst:24 ../../en/m5ui/win.rst:43 +#: 538948fab98445ff8404449f71018331 5aad7c8b41f9425f82f3a8298d8afa82 +msgid "Example output:" +msgstr "示例输出:" + +#: ../../en/m5ui/win.rst:26 ../../en/m5ui/win.rst:45 ../../en/m5ui/win.rst:113 +#: ../../en/m5ui/win.rst:139 ../../en/m5ui/win.rst:158 +#: ../../en/m5ui/win.rst:185 ../../en/m5ui/win.rst:299 +#: ../../en/m5ui/win.rst:337 ../../en/m5ui/win.rst:354 +#: ../../en/m5ui/win.rst:373 ../../en/m5ui/win.rst:394 +#: ../../en/m5ui/win.rst:455 ../../en/m5ui/win.rst:474 +#: 08d2b32464134b68ab6602c5002c9f57 0a4cdc9d6c1c42b888b07ba6fa391053 +#: 0c036f2446f441f39efcf7619e12dd25 12bc0ccb3ede461a9bc1c4220a0da2b4 +#: 977607a3a03146d5aa9be377a97c4bb2 a5d57fb9c3724287a068c0804b6cbcae +#: b0a63f4b235f4583b43bd54a883a4417 b0e7adfb2b3449cd911d4e9fc972a2da +#: c048bc65a55f4ee5816cc2a5c4a9e4c7 c699ad4392974748a55a3764ba084ac0 +#: ce9edc79352842adbfa5df089209e512 dfed5562031246a4afc2068da01de943 +#: e42cc35419dc4f7395021bef04a2e504 f712f10e3046443ba45b25a0099eba4d +#: m5ui.win.M5Win:11 of +msgid "None" +msgstr "" + +#: ../../en/m5ui/win.rst:30 0363679c629845998badccdb395afc81 +msgid "MicroPython Example" +msgstr "MicroPython 示例" + +#: ../../en/m5ui/win.rst:37 ../../en/m5ui/win.rst:77 ../../en/m5ui/win.rst:97 +#: ../../en/m5ui/win.rst:123 ../../en/m5ui/win.rst:145 +#: ../../en/m5ui/win.rst:168 ../../en/m5ui/win.rst:195 +#: ../../en/m5ui/win.rst:217 ../../en/m5ui/win.rst:238 +#: ../../en/m5ui/win.rst:255 ../../en/m5ui/win.rst:271 +#: ../../en/m5ui/win.rst:287 ../../en/m5ui/win.rst:305 +#: ../../en/m5ui/win.rst:325 ../../en/m5ui/win.rst:343 +#: ../../en/m5ui/win.rst:360 ../../en/m5ui/win.rst:379 +#: ../../en/m5ui/win.rst:402 ../../en/m5ui/win.rst:418 +#: ../../en/m5ui/win.rst:439 ../../en/m5ui/win.rst:461 +#: ../../en/m5ui/win.rst:480 264cb5a349b9438c9a971a17f67ea3dc +#: 3713e1312393415393645037455c4983 44df0f1c2b8a4b719c8048a2393667c3 +#: 50a7aa25a6784a8aaf4c08fb2dc65ce1 528b00db47ee4d15a0ed8b62bf7dd608 +#: 59004e3271144107aac179344cc123c3 5d4483c6efca4b5c9dfb4ebfd93367c8 +#: 604d9c34651d4e158930cae7437806b6 715f2b3306f9405894183c52737bd81f +#: 77f4f928dcfd4bf88ac2a26cf36c0063 833de2527c84406da734546b2ea1ce49 +#: 873c1cda2a0d415e8cc959cb7f884614 8ae0a3680b6540fabd10ad9e4c67c4a7 +#: 949a9581b4ff489eb67d1e636d7ade71 95182a21d67446338e03f45e8a2fa4cd +#: 9912d8d64b454c72975e3f3d0b84203e 9d1b72925e35455cb3b2f3a397ae3380 +#: 9ff689d3364b40f89a5b3a7e05350d60 a3bd171a300d49e58fc9863f22e27f2b +#: a5592c12d52b4c57bd77e33d88081cbc a8097d5439354f4fa5f9ad2063dd40b4 +#: ad4315eb8ef6420f894f3fde24a11a89 cc62f29c900440d3bbe0c042498bc144 +#: d9913f36d6c0442cb1f19fcf0549c97a ec1f7ebfdd6b433abf31de2980cf5b0e +#: f5bfd210b81743168defc6d4b77952b2 m5ui.win.M5Win:13 +#: m5ui.win.M5Win.add_button:19 m5ui.win.M5Win.add_text:20 +#: m5ui.win.M5Win.add_title:18 of +msgid "MicroPython Code Block:" +msgstr "MicroPython 代码块:" + +#: ../../en/m5ui/win.rst:49 b1ee14c8140c4b3ca92c9d2459852be3 +msgid "**API**" +msgstr "" + +#: ../../en/m5ui/win.rst:51 1c933015b3d54231a3b3b8d140112292 +msgid "M5Win" +msgstr "M5Win" + +#: feabfdc3713a46e3b04e8d90875f9e44 m5ui.win.M5Win:1 of +msgid "Bases: :py:class:`~lvgl.win`" +msgstr "" + +#: 2d2aedf337314784a679e84d753543a8 m5ui.win.M5Win:1 of +msgid "Create a window object." +msgstr "创建一个 window 对象。" + +#: ../../en/m5ui/win.rst 0125e8d97e5c41afae34707fe4417fd7 +#: 0674a017abae4dbaa77d0d172c4a1e3b 0875199a74f34a7a8aeb30faafa75c4b +#: 0db5adba1a824e518b6d5bf0df4d09fd 188783d3c1e24879bf2e9f1f8ead48ef +#: 2cc7121b76b9431396305a694a035106 33b263f317e64703890c8533542e04d4 +#: 36c3581212984c839f1d04f1049f8577 3fa0b1fca6064acf969f4a607629d331 +#: 4c747ab2e88e4c4e8f5c639b61fab9fa 554b43c66f1a450cad74f064ad9f002f +#: 604ec054a2214ec99c77ecadb5700412 72206541b207447f950cc82470dbf55d +#: 72221673c39e4228a58a6095666fb488 80190723491844d4b018fdef5b1c52c2 +#: 83d6e9d0da58497786e31de51a04f060 964b3fbf26da4fa99da4b938ff8c8558 +#: 97444c6d5f2a4b23b43134a033980dd2 a4c7b6526e524640b13b2f192236eee6 +#: ac8cde4c00f4472d8bce8b5986d2a435 d6928d7f2e3348e5bd126e30d5f2de9f +#: ed67afb04b0b4384b894dd1a8a11137d m5ui.win.M5Win.add_button +#: m5ui.win.M5Win.add_text m5ui.win.M5Win.add_title of +msgid "Parameters" +msgstr "参数" + +#: 70d259da5684470cb9d9e2fa6f6f675a m5ui.win.M5Win:3 of +msgid "The x position of the window." +msgstr "window 的 x 坐标位置。" + +#: b0a4e5f4592643bbb96ba68f4486f712 m5ui.win.M5Win:4 of +msgid "The y position of the window." +msgstr "window 的 y 坐标位置。" + +#: ../../en/m5ui/win.rst:297 617edf0328774b8da642db8136a57076 +#: bbf5ad69e78548e48368ce4043481820 m5ui.win.M5Win:5 of +msgid "The width of the window." +msgstr "window 的宽度。" + +#: ../../en/m5ui/win.rst:298 49fcf44ba54d4469ae56ec37b544a624 +#: bdac5fcd866d4326960dd50e8ef58b36 m5ui.win.M5Win:6 of +msgid "The height of the window." +msgstr "window 的高度。" + +#: 54c1ad4f628048c29edeed9a376b26a0 m5ui.win.M5Win:7 of +msgid "" +"The parent object to attach the window to. If not specified, the window " +"will be attached to the default screen." +msgstr "window 要附加到的父对象。如果未指定,则 window 会附加到默认屏幕。" + +#: ../../en/m5ui/win.rst:68 4fe526d38c81437fa4f701e87abeaec2 +#: 5d9414d2e98c4f5db1f2272f525021a6 m5ui.win.M5Win.add_button:11 of +msgid "The created button object :ref:`m5ui.M5Button `." +msgstr "创建的按钮对象 :ref:`m5ui.M5Button `。" + +#: ../../en/m5ui/win.rst 6b58733f23c64e03bab3e9f23a8165a4 +#: 87eb70039a9e4711ba46472613126bf4 c473ad5c77c1455fa06f926fc7a46770 +#: eda35e820c4d4e7c9bbdb4e1f46f75c5 f7ee5a4f650841debd6aa2efe6ce47ec +#: m5ui.win.M5Win.add_button m5ui.win.M5Win.add_text m5ui.win.M5Win.add_title +#: of +msgid "Return type" +msgstr "返回类型" + +#: ../../en/m5ui/win.rst:73 d4c3ff034a9948fdaa8efaf04bd36938 +msgid "|add_button.png|" +msgstr "" + +#: ../../en/refs/m5ui.win.ref:1 4cb2926d3d894626b556c65fe7ab0418 +#: 63a37297a40941c4a8eeccddbf4ec93d +msgid "add_button.png" +msgstr "" + +#: ../../en/m5ui/win.rst:75 0c127bccaebd4074bb307465be5047d7 +msgid "|add_button2.png|" +msgstr "" + +#: ../../en/refs/m5ui.win.ref:2 5c6144762bd34df697633cda9a61828f +#: dc6bc7a032ca4fe983babee7cb54074f +msgid "add_button2.png" +msgstr "" + +#: ../../en/m5ui/win.rst:87 e90cc944293c4f26adfbeb2b9d969bc2 +msgid "Delete the item from the window." +msgstr "从 window 中删除项目。" + +#: ../../en/m5ui/win.rst:91 3d0cde916b7248c89adcc9f7ea970812 +msgid "|label_delete.png|" +msgstr "" + +#: ../../en/refs/m5ui.win.ref:7 4b706aa569c54260b29b45bf8472aae8 +msgid "label_delete.png" +msgstr "" + +#: ../../en/m5ui/win.rst:93 a9db2a94c95a45078740c0a88b4cbc55 +msgid "|button_delete.png|" +msgstr "" + +#: ../../en/refs/m5ui.win.ref:8 202dc7f99a624035a73edbdc7906dcf2 +msgid "button_delete.png" +msgstr "" + +#: ../../en/m5ui/win.rst:95 bce98e7595274dbca36e0f3183301c23 +msgid "|title_delete.png|" +msgstr "" + +#: ../../en/refs/m5ui.win.ref:9 76ec128a76dc49559ab5adcbc59aeb79 +msgid "title_delete.png" +msgstr "" + +#: ../../en/m5ui/win.rst:110 6281f7c489c840d384c0b1fdd593bd5c +msgid "Set text of the window button/label/title." +msgstr "设置 window 按钮/标签/标题的文本。" + +#: ../../en/m5ui/win.rst:112 849539a129224b839e9eaeb3d03fde2b +msgid "The text to set for the window button/label/title." +msgstr "要为 window 按钮/标签/标题设置的文本。" + +#: ../../en/m5ui/win.rst 0234dbd8c7b3464aaaa7432d92a4eeb9 +#: 14c714892bbc41ef931d001f4d782d3f 2460785ae329424eb01686c55d312673 +#: 3063d265cbaa4139b1e3ad5521864d6e 314ffeb6fb8e4123948baabfdda08a38 +#: 4d7cd9bf1cee40bf8d45b6d8575ff0af 635e8bd9849d468783be198539b96953 +#: 961ec95b27cb4d19ae1339f4c275358c abd0b4cdb3f94f218ac0eebfa42b8f77 +#: b0acb44d7a074cc38105aff59f2cc452 c18bef160da94b14a224aee971db46a6 +#: c1b59eaa841c4cf4a257b5b81962a231 cafeb95d41184b25b922fec56663b9a3 +#: e72f19834a514bdf97660a0840721192 faa2a1a6c2a3419484977364fcc25382 +#: m5ui.win.M5Win.add_button m5ui.win.M5Win.add_text m5ui.win.M5Win.add_title +#: of +msgid "Returns" +msgstr "" + +#: ../../en/m5ui/win.rst:88 2efb053197484082a533f90f72db972c +msgid "|button_set_text.png|" +msgstr "" + +#: ../../en/refs/m5ui.win.ref:10 26efb6eb016648a3ab2b14301e3dcb0b +msgid "button_set_text.png" +msgstr "" + +#: ../../en/m5ui/win.rst:119 ce5d67acb5b34343b76ccc86cbd63d04 +msgid "|label_set_text.png|" +msgstr "" + +#: ../../en/refs/m5ui.win.ref:11 902a1aa0695141b98b77cfcd46a0ef20 +msgid "label_set_text.png" +msgstr "" + +#: ../../en/m5ui/win.rst:121 3a035f838d814ecca412ac907dd6ac00 +msgid "|title_set_text.png|" +msgstr "" + +#: ../../en/refs/m5ui.win.ref:12 7a650ba761654691ad5aa55fb1932c41 +msgid "title_set_text.png" +msgstr "" + +#: ../../en/m5ui/win.rst:135 3e3bb7c61fe840d881c3deede98ab208 +msgid "Set the font of the window button text." +msgstr "设置 window 按钮文本的字体。" + +#: ../../en/m5ui/win.rst:137 1674d27ce6cf4513b97037c0c4267e23 +msgid "The font to set." +msgstr "要设置的字体。" + +#: ../../en/m5ui/win.rst:138 ../../en/m5ui/win.rst:157 +#: ../../en/m5ui/win.rst:184 ../../en/m5ui/win.rst:372 +#: 43aa77c12e354968a2edc1864f156695 4ffb3bee80864ad7bd20ffe8f02b723f +#: 981103fdc949429aae1ba8cc23c95186 ab289701290a46f38e50f5282b518e4e +msgid "The part of the object to apply the style to (e.g., lv.PART.MAIN)." +msgstr "要应用样式的对象部分(例如 lv.PART.MAIN)。" + +#: ../../en/m5ui/win.rst:143 aa35fbbff88345a4972a4df44daac35a +msgid "|button_set_font.png|" +msgstr "" + +#: ../../en/refs/m5ui.win.ref:13 e22f173c4ed84b17ac2d4d8e79a1ba1b +msgid "button_set_font.png" +msgstr "" + +#: ../../en/m5ui/win.rst:153 5e4b37eed08f4295a4d358ab99d93c59 +msgid "Set the color of the window button/label/title." +msgstr "设置 window 按钮/标签/标题的颜色。" + +#: ../../en/m5ui/win.rst:155 ../../en/m5ui/win.rst:182 +#: 066ce98275ed4ec08cab69205a0655df 6118e14fd15c4a86b7e3f59c1a30409d +msgid "The color to set." +msgstr "要设置的颜色。" + +#: ../../en/m5ui/win.rst:156 ../../en/m5ui/win.rst:183 +#: 2053cec248934f4fa32c72e66cc42e9f 43e89cb273244be1ad0c6f546f442d34 +msgid "The opacity of the color." +msgstr "颜色的不透明度。" + +#: ../../en/m5ui/win.rst:162 9af9565ec5b6410eba60b19e285fc950 +msgid "|button_set_text_color.png|" +msgstr "" + +#: ../../en/refs/m5ui.win.ref:14 df7c02e499e44661b315b50b9e6ba8b3 +msgid "button_set_text_color.png" +msgstr "" + +#: ../../en/m5ui/win.rst:164 9bd306ff80ff45a8bc7df1aa99eb7246 +msgid "|label_set_text_color.png|" +msgstr "" + +#: ../../en/refs/m5ui.win.ref:15 09dfcf2df4f74d1d80c7b5e4dba776fb +msgid "label_set_text_color.png" +msgstr "" + +#: ../../en/m5ui/win.rst:166 b2629a0dfe404dc4ad8ef8fb73822e14 +msgid "|title_set_text_color.png|" +msgstr "" + +#: ../../en/refs/m5ui.win.ref:16 0e9df25ed06441f5ac9f2b4e3cde51cf +msgid "title_set_text_color.png" +msgstr "" + +#: ../../en/m5ui/win.rst:180 2b684c42beaa454283537e10bdfaaa1c +msgid "Set the background color of the window button/label/title." +msgstr "设置 window 按钮/标签/标题的背景颜色。" + +#: ../../en/m5ui/win.rst:189 7f5fc2306fac4f20a004c9fb18289c92 +msgid "|button_set_bg_color.png|" +msgstr "" + +#: ../../en/refs/m5ui.win.ref:17 71cf7be0ccb94bd692987e9562871e07 +msgid "button_set_bg_color.png" +msgstr "" + +#: ../../en/m5ui/win.rst:191 b7be83e4b666458795c7085d88132c36 +msgid "|label_set_bg_color.png|" +msgstr "" + +#: ../../en/refs/m5ui.win.ref:18 0a0aef3beebb485b8de5dc6cfbaae16d +msgid "label_set_bg_color.png" +msgstr "" + +#: ../../en/m5ui/win.rst:193 91aad32d16124c55a5ef20d1f72e09ce +msgid "|title_set_bg_color.png|" +msgstr "" + +#: ../../en/refs/m5ui.win.ref:19 c041d4812a7942c2bad0c9b2c4da9d5c +msgid "title_set_bg_color.png" +msgstr "" + +#: ../../en/m5ui/win.rst:207 ed0041f0a8344a9097c9733a4408f2a5 +msgid "Set the long mode of the window label/title." +msgstr "设置 window 标签/标题的长文本模式。" + +#: ../../en/m5ui/win.rst:209 2beff9e6ba1f4565a24318d3286f198b +msgid "The long mode to set." +msgstr "要设置的长文本模式。" + +#: ../../en/m5ui/win.rst:213 c0105eb503304333a0092d10faf3cdd4 +msgid "|label_set_long_mode.png|" +msgstr "" + +#: ../../en/refs/m5ui.win.ref:20 c2413036a11d4b338204849e03776a4e +msgid "label_set_long_mode.png" +msgstr "" + +#: ../../en/m5ui/win.rst:215 3aa421f0a4a64340bf5cb68b1f9597eb +msgid "|title_set_long_mode.png|" +msgstr "" + +#: ../../en/refs/m5ui.win.ref:21 9bb21c5fb6c54ad7816a2c4c0f02a282 +msgid "title_set_long_mode.png" +msgstr "" + +#: ../../en/m5ui/win.rst:225 d5102af2c04e45aea1bf911a89430ae7 +msgid "" +"Set a flag on the object. If ``value`` is True, the flag is added; if " +"False, the flag is removed." +msgstr "在对象上设置标志。如果 ``value`` 为 True,则添加标志;为 False 时移除标志。" + +#: ../../en/m5ui/win.rst:227 b4822e8a06fb4efdbf67c69b6865b630 +msgid "The flag to set." +msgstr "要设置的标志。" + +#: ../../en/m5ui/win.rst:228 dbe78995b09844b08166cfc5e1b8a236 +msgid "If True, the flag is added; if False, the flag is removed." +msgstr "为 True 时添加标志,为 False 时移除标志。" + +#: ../../en/m5ui/win.rst:232 06162249a3ca4003aef49ce0b266dd96 +msgid "|button_set_flag.png|" +msgstr "" + +#: ../../en/refs/m5ui.win.ref:22 9409bd7abc6745518b3efec7e0cdc4a2 +msgid "button_set_flag.png" +msgstr "" + +#: ../../en/m5ui/win.rst:234 b1267bc2df924c05a008c458cf800a35 +msgid "|label_set_flag.png|" +msgstr "" + +#: ../../en/refs/m5ui.win.ref:23 460d743f3bd84f9f948d63d24a62f05a +msgid "label_set_flag.png" +msgstr "" + +#: ../../en/m5ui/win.rst:236 7f68694ac8354b8e9f879c4c9c03c0fc +msgid "|title_set_flag.png|" +msgstr "" + +#: ../../en/refs/m5ui.win.ref:24 e4e8a287cc4a438ca352b387ac9bb283 +msgid "title_set_flag.png" +msgstr "" + +#: ../../en/m5ui/win.rst:246 0f3958dae36b46a6801ff3064d99c1e8 +msgid "Set the position of the window." +msgstr "设置 window 的位置。" + +#: ../../en/m5ui/win.rst:248 ../../en/m5ui/win.rst:265 +#: 046273bb5b4b46dc8d42efe021d0f271 81fe3724aad34f64a8791ee3bbd90044 +msgid "The x-coordinate of the window." +msgstr "window 的 x 坐标。" + +#: ../../en/m5ui/win.rst:249 ../../en/m5ui/win.rst:281 +#: 1e5f2c97dc374dba8ab22c0d852fcdb3 9a89f366b181457a9fcd816e37b517c9 +msgid "The y-coordinate of the window." +msgstr "window 的 y 坐标。" + +#: ../../en/m5ui/win.rst:253 72c9e40670de4b508006b60a24fed838 +msgid "|set_pos.png|" +msgstr "" + +#: ../../en/refs/m5ui.win.ref:25 a9385b33902f4f9ca29a486617779fbd +msgid "set_pos.png" +msgstr "" + +#: ../../en/m5ui/win.rst:263 732c83f16a454bc990dfdf7e1b25d28e +msgid "Set the x-coordinate of the window." +msgstr "设置 window 的 x 坐标。" + +#: ../../en/m5ui/win.rst:269 e94e54580a8f456b9941d86e465e55ad +msgid "|set_x.png|" +msgstr "" + +#: ../../en/refs/m5ui.win.ref:26 5d1e2c1d134b4220a080e50da9b348e9 +msgid "set_x.png" +msgstr "" + +#: ../../en/m5ui/win.rst:279 11b49011688d454fb3e13035d0d6f8e6 +msgid "Set the y-coordinate of the window." +msgstr "设置 window 的 y 坐标。" + +#: ../../en/m5ui/win.rst:285 84f6f877a3b64baca86b6e0ce7cce497 +msgid "|set_y.png|" +msgstr "" + +#: ../../en/refs/m5ui.win.ref:27 de50551a9ed24d1cb73426df0a12b464 +msgid "set_y.png" +msgstr "" + +#: ../../en/m5ui/win.rst:295 cc18c62b1d5e48f99a8ed6b596038d13 +msgid "Set the size of the window." +msgstr "设置 window 的尺寸。" + +#: ../../en/m5ui/win.rst:303 93ac24232cfa47a9b5e8aa83221ec6ba +msgid "|set_size.png|" +msgstr "" + +#: ../../en/refs/m5ui.win.ref:28 f569a5194ce046fb8213d62d6448c740 +msgid "set_size.png" +msgstr "" + +#: ../../en/m5ui/win.rst:314 07c66ffdd7aa4334b801997c15e5996a +msgid "Align the windowto another object." +msgstr "将 window 对齐到其他对象。" + +#: ../../en/m5ui/win.rst:316 05745fef730e499fbf1d63e5e97fcb79 +msgid "The object to align to." +msgstr "要对齐的对象。" + +#: ../../en/m5ui/win.rst:317 87c11d5e870b4c019ed61f21afda9880 +msgid "The alignment type." +msgstr "对齐方式类型。" + +#: ../../en/m5ui/win.rst:318 567ee52a9cb84dfcb0e9eee559a55b22 +msgid "The x-offset from the aligned object." +msgstr "相对于对齐对象的 x 偏移量。" + +#: ../../en/m5ui/win.rst:319 59d91d078fa243abb5d4bc196887a933 +msgid "The y-offset from the aligned object." +msgstr "相对于对齐对象的 y 偏移量。" + +#: ../../en/m5ui/win.rst:323 023bd7e2a15f4a33b84c6c9b2b9b74c9 +msgid "|align_to.png|" +msgstr "" + +#: ../../en/refs/m5ui.win.ref:29 e47b248a728b471a9ac51998afcc377d +msgid "align_to.png" +msgstr "" + +#: ../../en/m5ui/win.rst:333 4976fb9bc6a745b8bb970656f633f8d1 +msgid "" +"Set the state of the bar. If ``value`` is True, the state is set; if " +"False, the state is unset." +msgstr "设置进度条的状态。如果 ``value`` 为 True,则设置状态;为 False 时取消状态。" + +#: ../../en/m5ui/win.rst:335 5752db18c9bd48289743cdb4b84d478e +msgid "The state to set." +msgstr "要设置的状态。" + +#: ../../en/m5ui/win.rst:336 c52ab56438424ddfa1ffb6842f64771b +msgid "If True, the state is set; if False, the state is unset." +msgstr "为 True 时设置状态;为 False 时取消状态。" + +#: ../../en/m5ui/win.rst:341 e038548d20a9455bb84e1f1343629a6b +msgid "|button_set_state.png|" +msgstr "" + +#: ../../en/refs/m5ui.win.ref:30 88e103ccc2584f56abde4692e94b9456 +msgid "button_set_state.png" +msgstr "" + +#: ../../en/m5ui/win.rst:351 3a1e893cc6204cfd812239dd3fbf19d2 +msgid "" +"Toggle a flag on the object. If the flag is set, it is removed; if not " +"set, it is added." +msgstr "切换对象上的标志。如果该标志已设置,则移除;否则添加。" + +#: ../../en/m5ui/win.rst:353 e504feeac8d9406b8696d69139b58689 +msgid "The flag to toggle." +msgstr "要切换的标志。" + +#: ../../en/m5ui/win.rst:358 bcb895c3192e4c23adfd76ba0d1505bd +msgid "|button_toggle_flag.png|" +msgstr "" + +#: ../../en/refs/m5ui.win.ref:31 ab7a75dcbc6c4111a82d54550bd944b3 +msgid "button_toggle_flag.png" +msgstr "" + +#: ../../en/m5ui/win.rst:369 7d8ee367f8234847a344c8ec76cd8d4e +msgid "Set the corner radius of the window button." +msgstr "设置 window 按钮的圆角半径。" + +#: ../../en/m5ui/win.rst:371 067d5129afa54fe6806e4cb9b835530e +msgid "The radius to set." +msgstr "要设置的半径。" + +#: ../../en/m5ui/win.rst:377 1cc4937e46144016a29d03a279a3ec20 +msgid "|button_set_radius.png|" +msgstr "" + +#: ../../en/refs/m5ui.win.ref:32 cc4b2d8ffd254a0a8b88ea8582f74ede +msgid "button_set_radius.png" +msgstr "" + +#: ../../en/m5ui/win.rst:387 2e4d7731c8df4e29ba9ba48270150cdf +msgid "Set a shadow for the label/title." +msgstr "为标签/标题设置阴影。" + +#: ../../en/m5ui/win.rst:389 313e382842694d7a950fe781c479f81a +msgid "The color of the shadow in hexadecimal format or an integer." +msgstr "阴影的颜色,十六进制格式或整数。" +#: ../../en/m5ui/win.rst:390 444b28adaa614086ba1f6bb68af4f789 +msgid "The opacity of the shadow (0-255)." +msgstr "阴影的不透明度(0-255)。" + +#: ../../en/m5ui/win.rst:391 ab7b305f97944b1fb53597206eb68ad8 +msgid "The alignment of the shadow relative to the label/title." +msgstr "阴影相对于标签/标题的对齐方式。" + +#: ../../en/m5ui/win.rst:392 9f4def86587c493fb22a82bb4b7e1eee +msgid "The horizontal offset of the shadow." +msgstr "阴影的水平偏移量。" + +#: ../../en/m5ui/win.rst:393 46d299538066423eb9d2d3a5dd1ef863 +msgid "The vertical offset of the shadow." +msgstr "阴影的垂直偏移量。" + +#: ../../en/m5ui/win.rst:398 d467cd3968df417a8fd8b24d4b3ccbb5 +msgid "|label_set_shadow.png|" +msgstr "" + +#: ../../en/refs/m5ui.win.ref:33 c0a5e8c8ffeb4bc9b917c1fed331e8f2 +msgid "label_set_shadow.png" +msgstr "" + +#: ../../en/m5ui/win.rst:400 61ef08f67f494287a6d0b6e7bfd52d30 +msgid "|title_set_shadow.png|" +msgstr "" + +#: ../../en/refs/m5ui.win.ref:34 782b477b5c5c46c298e8a51151a121da +msgid "title_set_shadow.png" +msgstr "" + +#: ../../en/m5ui/win.rst:410 1450d8e1e9c14df3b0036c501a243dbb +msgid "Remove the shadow from the label/title." +msgstr "移除标签/标题的阴影。" + +#: ../../en/m5ui/win.rst:414 24f824ba2ae74bba957ef7b0475c6980 +msgid "|label_unset_shadow.png|" +msgstr "" + +#: ../../en/refs/m5ui.win.ref:35 5c29cad7261e441491234333ad069f77 +msgid "label_unset_shadow.png" +msgstr "" + +#: ../../en/m5ui/win.rst:416 eac3a08c444545c9a23178553e3c797b +msgid "|title_unset_shadow.png|" +msgstr "" + +#: ../../en/refs/m5ui.win.ref:36 6e6d071b22d24bb7ac6b2aec3d603a86 +msgid "title_unset_shadow.png" +msgstr "" + +#: ../../en/m5ui/win.rst:426 b0913c0acae645509af2ae01f7175220 +msgid "Get the text of the button/label/title." +msgstr "获取按钮/标签/标题的文本。" + +#: ../../en/m5ui/win.rst:428 c5f7ae5d75524409afb5238c8c7ab3e4 +msgid "The text of the button/label/title." +msgstr "" + +#: ../../en/m5ui/win.rst 20a9c7c04f1a484e9e77ad99abfe8024 +#: 286bd797005b4d0cbef9d3b5144e7170 645820f773bd4c13b13b1335fcf9c46b +#: d1a7cd822445480ca330642fcfc12ec9 m5ui.win.M5Win.add_button +#: m5ui.win.M5Win.add_text m5ui.win.M5Win.add_title of +msgid "Return type" +msgstr "" + +#: ../../en/m5ui/win.rst:433 006b1f123f0b4da889b72dc6e063a5b4 +msgid "|button_get_text.png|" +msgstr "" + +#: ../../en/refs/m5ui.win.ref:37 1b89c1e468f04c549c919bbe4e6b0e1d +msgid "button_get_text.png" +msgstr "" + +#: ../../en/m5ui/win.rst:435 0cf4229af3a84552a59c494c6a24c0cc +msgid "|label_get_text.png|" +msgstr "" + +#: ../../en/refs/m5ui.win.ref:38 2289ec017100403994b223bf1ac46fa1 +msgid "label_get_text.png" +msgstr "" + +#: ../../en/m5ui/win.rst:437 cd1d6f1a53c14a14ae0dcc58109b19f2 +msgid "|title_get_text.png|" +msgstr "" + +#: ../../en/refs/m5ui.win.ref:39 2ad0676c57434710be1632fefbe19b5e +msgid "title_get_text.png" +msgstr "" + +#: ../../en/m5ui/win.rst:452 33580215d6864929847c79fe31a38e15 +msgid "" +"Toggle the state of the button. If the state is set, it is unset; if not " +"set, it is set." +msgstr "切换按钮的状态。如果该状态已设置则取消设置,未设置则添加设置。" + +#: ../../en/m5ui/win.rst:454 f478140116754151b634afaf6acad2dd +msgid "The state to toggle." +msgstr "要切换的状态。" + +#: ../../en/m5ui/win.rst:459 1b9c198dd6534128954323f424bc89c1 +msgid "|button_toggle_state.png|" +msgstr "" + +#: ../../en/refs/m5ui.win.ref:40 ac0931a7107041cd92ea3ecb15d6a47c +msgid "button_toggle_state.png" +msgstr "" + +#: ../../en/m5ui/win.rst:469 b7ebbe97932a4031a98c2f292054d0e5 +msgid "" +"Add an event callback to the button. The callback will be called when the" +" specified event occurs." +msgstr "为按钮添加事件回调。当指定事件发生时将调用该回调函数。" + +#: ../../en/m5ui/win.rst:471 68abdc5dc21646b484352aea8654387a +msgid "The callback function to call." +msgstr "要调用的回调函数。" + +#: ../../en/m5ui/win.rst:472 2f6fc86273444462b04094fcd54d5b02 +msgid "The event to listen for." +msgstr "要监听的事件。" + +#: ../../en/m5ui/win.rst:473 3f854db5c72a4aab9cff9fea7d85d7dd +msgid "Optional user data to pass to the callback." +msgstr "可选的用户数据,将传递给回调函数。" + +#: ../../en/m5ui/win.rst:478 7db342e3fa814647b9204262e1cada68 +msgid "|button_event.png|" +msgstr "" + +#: ../../en/refs/m5ui.win.ref:41 b3827b0583de4b029dabce33f0a1b414 +msgid "button_event.png" +msgstr "" + +#: 1a4c0eb42a9f4aa9b66095e0bc4584f9 m5ui.win.M5Win.add_title:1 of +msgid "Add a title label to the window." +msgstr "向 window 添加标题标签。" + +#: 2e918c05ab824e96bd205d1b3de34111 a9f040d7ea7f47baaae5535e1f6a0080 +#: m5ui.win.M5Win.add_text:3 m5ui.win.M5Win.add_title:3 of +msgid "The text to display on the window." +msgstr "要在 window 上显示的文本。" + +#: 4b5de88a8911415fa86f1febab21d1a0 c649c4f64d404b8ba3687d9aedbc254a +#: m5ui.win.M5Win.add_text:6 m5ui.win.M5Win.add_title:4 of +msgid "The text color of the label in hexadecimal format." +msgstr "标签的文本颜色(十六进制格式)。" + +#: af020be1bbab4647ad5f824006be6c8d e62c57ff24cf4d6b85dde437fe8c4dcb +#: m5ui.win.M5Win.add_text:7 m5ui.win.M5Win.add_title:5 of +msgid "The text opacity of the label (0-255)." +msgstr "标签文本的不透明度(0-255)。" + +#: 38eaf543c41a4af492afdd663a5efd54 ae351b687b964f47992f5d18f9abfe3f +#: m5ui.win.M5Win.add_text:8 m5ui.win.M5Win.add_title:6 of +msgid "The background color of the label in hexadecimal format." +msgstr "标签的背景颜色(十六进制格式)。" + +#: a254f911d6f44f1cab4c0a0fa8c98878 bd885d632dc849888bf71eec5d76317a +#: m5ui.win.M5Win.add_text:9 m5ui.win.M5Win.add_title:7 of +msgid "The background opacity of the label (0-255)." +msgstr "标签背景的不透明度(0-255)。" + +#: 1b3df7cb5fad4774a8ac107bcf003b50 d5870aaa38bc48dcaf3d9dd01da55f56 +#: m5ui.win.M5Win.add_text:10 m5ui.win.M5Win.add_title:8 of +msgid "The font to use for the label." +msgstr "用于标签的字体。" + +#: 3091c1498d7341c68e133a5b3bff41b2 ed86a9ce738a490dbc931a11b69a313a +#: m5ui.win.M5Win.add_text:11 m5ui.win.M5Win.add_title:9 of +msgid "The created label object :ref:`m5ui.M5Label `." +msgstr "创建的标签对象 :ref:`m5ui.M5Label `。" + +#: fc7f8b78b3534cab9185f7deb6036c31 m5ui.win.M5Win.add_title:14 of +msgid "|add_title.png|" +msgstr "" + +#: ../../en/refs/m5ui.win.ref:3 bcca65458aa640889b0aa5af36a1808e +msgid "add_title.png" +msgstr "" + +#: 6e6630ab97434e6c9a9b022384006491 m5ui.win.M5Win.add_title:16 of +msgid "|add_title2.png|" +msgstr "" + +#: ../../en/refs/m5ui.win.ref:4 be5232c3dc844f87825cf70494754e7f +msgid "add_title2.png" +msgstr "" + +#: 52eddef19de342409156ece51413e233 m5ui.win.M5Win.add_text:1 of +msgid "Add a text label to the window." +msgstr "向 window 添加文本标签。" + +#: 7210bc0baf9443e6ad1d974763543fd9 m5ui.win.M5Win.add_text:4 of +msgid "The x position of the label." +msgstr "标签的 x 坐标位置。" + +#: f51b6af81da44d728535705d4f468d37 m5ui.win.M5Win.add_text:5 of +msgid "The y position of the label." +msgstr "标签的 y 坐标位置。" + +#: cfbb36f087b84910a6a69aa2b2e85d6f m5ui.win.M5Win.add_text:16 of +msgid "|add_text.png|" +msgstr "" + +#: ../../en/refs/m5ui.win.ref:5 f2da1f5c408446aa849adb1d1b2ed962 +msgid "add_text.png" +msgstr "" + +#: cc746f6edefe44a795d7ec815d10369a m5ui.win.M5Win.add_text:18 of +msgid "|add_text2.png|" +msgstr "" + +#: ../../en/refs/m5ui.win.ref:6 777eb74d6e5a47a5b37dc105ca65ecff +msgid "add_text2.png" +msgstr "" + +#: 079d7a332c2c411ba3aaaf1e65b771e2 m5ui.win.M5Win.add_button:1 of +msgid "Add a button to the window." +msgstr "向 window 添加按钮。" + +#: 1c33fc2ef7ec4a74a78dd0867eb524ee m5ui.win.M5Win.add_button:3 of +msgid "The icon to display on the button." +msgstr "按钮上要显示的图标。" + +#: 9e45844f585f4eac978a252e414d06b9 m5ui.win.M5Win.add_button:4 of +msgid "The text to display on the button." +msgstr "按钮上要显示的文本。" + +#: 72c83ebd0d684b45b6bd0a9274fbc3d5 m5ui.win.M5Win.add_button:5 of +msgid "The height of the button." +msgstr "按钮的高度。" + +#: d7882fd07cba4eb6bd0d2b82147fb79a m5ui.win.M5Win.add_button:6 of +msgid "The background color of the button in hexadecimal format." +msgstr "按钮的背景颜色(十六进制格式)。" + +#: dfca510cf1f141cebe1c6b28f7127708 m5ui.win.M5Win.add_button:7 of +msgid "The background opacity of the button (0-255)." +msgstr "按钮背景的不透明度(0-255)。" + +#: 08d074435a4f4c7f96860a647a22d62d m5ui.win.M5Win.add_button:8 of +msgid "The text color of the button in hexadecimal format." +msgstr "按钮文本颜色(十六进制格式)。" + +#: 03c290d8e88a4634955d78f37751cd3d m5ui.win.M5Win.add_button:9 of +msgid "The text opacity of the button (0-255)." +msgstr "按钮文本的不透明度(0-255)。" + +#: 5d487fa1c17a4eaaa8d34bb7a9b16045 m5ui.win.M5Win.add_button:10 of +msgid "The font to use for the button text." +msgstr "按钮文本所使用的字体。" + +#: a0af2d491ef440689c14b2abb69322e5 m5ui.win.M5Win.add_button:11 of +msgid "The created button object :ref:`m5ui.M5Button `." +msgstr "创建的按钮对象 :ref:`m5ui.M5Button `。" + +#: 1399e94b513640d9a65cd16a8ef3f7be m5ui.win.M5Win.add_button:16 of +msgid "|add_button.png|" +msgstr "" + +#: ../../en/refs/m5ui.win.ref:1 8b451815050f4bb88cd48016d9f870ad +msgid "add_button.png" +msgstr "" + +#: 8ad225b3a1914410b2f38dea3957193d m5ui.win.M5Win.add_button:18 of +msgid "|add_button2.png|" +msgstr "" + +#: ../../en/refs/m5ui.win.ref:2 8e60b385a6b1402db028f83839270678 +msgid "add_button2.png" +msgstr "" + diff --git a/examples/m5ui/window/window_core2_example.m5f2 b/examples/m5ui/window/window_core2_example.m5f2 new file mode 100644 index 00000000..8eb84cdf --- /dev/null +++ b/examples/m5ui/window/window_core2_example.m5f2 @@ -0,0 +1 @@ +{"version":"V2.0","versionNumber":"V2.3.0","type":"cores3","components":[{"name":"page0","type":"lvgl_page","layer":0,"screenId":"builtin","screenName":"","id":"l5f2SpBNAdiIZj$l","createTime":1752128633108,"backgroundColor":"#ffffff","isLVGL":true,"isSelected":true},{"name":"bar0","type":"lvgl_bar","layer":1,"screenId":"builtin","screenName":"","id":"s@nB`rh96#zn@OmT","createTime":1752128637953,"x":148,"y":21,"width":20,"height":200,"minValue":0,"maxValue":50,"currentValue":25,"color":"#2193f3","backgroundColor":"#2193f3","pageId":"l5f2SpBNAdiIZj$l","isLVGL":true,"isSelected":false},{"name":"label0","type":"lvgl_label","layer":2,"screenId":"builtin","screenName":"","id":"mw_Vbs91pAC#_zr*","createTime":1752130082941,"x":181,"y":112,"color":"#000000","backgroundColor":"#ffffff","bg_opacity":0,"text":"label0","font":"lv.font_montserrat_14","pageId":"l5f2SpBNAdiIZj$l","isLVGL":true,"isSelected":false,"width":43,"height":15}],"resources":[{"hardware":["hardware_button","hardware_pin_button","imu","speaker","touch","als","mic","sdcard","i2c"]},{"unit":["unit_envpro"]}],"units":[{"type":"unit_envpro","name":"envpro_0","portList":["A","PAHUB","Custom"],"portType":"A","userPort":[22,21],"id":"m=BK#Y%jdcViME7`","createTime":1752128714183,"bus":"i2c0","pahubPortList":[0,1,2,3,4,5],"pahubPort":0,"initBlockId":"jrzLghfRd(EBRC;SuDuh"}],"hats":[],"bases":[],"i2cs":[{"id":"i2c0","portType":"A","userPort":[22,21],"freq":"100000","blockId":"tE^WXf3T.9OwK.97~%5v"}],"blockly":"true010000012envpro_0page0bar0VERrgb25500255rgb00255255truebar00envpro_0label0hello M5envpro_01","screen":[{"simulationName":"Built-in","type":"builtin","width":320,"height":240,"scale":0.77,"screenName":"","blockId":"","screenColorType":0,"rotation":1,"id":"builtin","createTime":1752128633106}],"logicWhenNum":0,"customList":[]} \ No newline at end of file diff --git a/examples/m5ui/window/window_core2_example.py b/examples/m5ui/window/window_core2_example.py new file mode 100644 index 00000000..03f4dcdd --- /dev/null +++ b/examples/m5ui/window/window_core2_example.py @@ -0,0 +1,101 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import os, sys, io +import M5 +from M5 import * +import m5ui +import lvgl as lv + + +page0 = None +window0 = None +btn_lav = None +title_fmk = None +btn_nzk = None +btn_dkc = None +label_wgy = None + + +def btn_lav_clicked_event(event_struct): + global page0, window0, btn_lav, title_fmk, btn_nzk, btn_dkc, label_wgy + + label_wgy.set_text(str("Left Btn was clicked")) + + +def btn_nzk_clicked_event(event_struct): + global page0, window0, btn_lav, title_fmk, btn_nzk, btn_dkc, label_wgy + + label_wgy.set_text(str("Right Btn was clicked")) + + +def btn_dkc_clicked_event(event_struct): + global page0, window0, btn_lav, title_fmk, btn_nzk, btn_dkc, label_wgy + + window0.set_flag(lv.obj.FLAG.HIDDEN, True) + + +def btn_lav_event_handler(event_struct): + global page0, window0, btn_lav, title_fmk, btn_nzk, btn_dkc, label_wgy + event = event_struct.code + if event == lv.EVENT.CLICKED and True: + btn_lav_clicked_event(event_struct) + return + + +def btn_nzk_event_handler(event_struct): + global page0, window0, btn_lav, title_fmk, btn_nzk, btn_dkc, label_wgy + event = event_struct.code + if event == lv.EVENT.CLICKED and True: + btn_nzk_clicked_event(event_struct) + return + + +def btn_dkc_event_handler(event_struct): + global page0, window0, btn_lav, title_fmk, btn_nzk, btn_dkc, label_wgy + event = event_struct.code + if event == lv.EVENT.CLICKED and True: + btn_dkc_clicked_event(event_struct) + return + + +def setup(): + global page0, window0, btn_lav, title_fmk, btn_nzk, btn_dkc, label_wgy + + M5.begin() + Widgets.setRotation(1) + m5ui.init() + page0 = m5ui.M5Page(bg_c=0xFFFFFF) + window0 = m5ui.M5Win(x=0, y=0, w=320, h=240, parent=page0) + btn_lav = window0.add_button(icon=lv.SYMBOL.LEFT, w=40) + title_fmk = window0.add_title("This is a window") + btn_nzk = window0.add_button(icon=lv.SYMBOL.RIGHT, w=40) + btn_dkc = window0.add_button(icon=lv.SYMBOL.CLOSE, w=60) + label_wgy = window0.add_text("This is label_wgy", x=0, y=0) + + btn_lav.add_event_cb(btn_lav_event_handler, lv.EVENT.ALL, None) + btn_nzk.add_event_cb(btn_nzk_event_handler, lv.EVENT.ALL, None) + btn_dkc.add_event_cb(btn_dkc_event_handler, lv.EVENT.ALL, None) + + page0.screen_load() + + +def loop(): + global page0, window0, btn_lav, title_fmk, btn_nzk, btn_dkc, label_wgy + M5.update() + + +if __name__ == "__main__": + try: + setup() + while True: + loop() + except (Exception, KeyboardInterrupt) as e: + try: + m5ui.deinit() + from utility import print_error_msg + + print_error_msg(e) + except ImportError: + print("please update to latest firmware") diff --git a/m5stack/libs/m5ui/win.py b/m5stack/libs/m5ui/win.py index a4df488f..c18fe9ca 100644 --- a/m5stack/libs/m5ui/win.py +++ b/m5stack/libs/m5ui/win.py @@ -6,6 +6,11 @@ import lvgl as lv import m5ui +try: + DPI = lv.DPI_DEF / 3 +except Exception: + DPI = 80 + class M5Win(lv.win): """Create a window object. @@ -69,6 +74,8 @@ def add_title( |add_title.png| + |add_title2.png| + MicroPython Code Block: .. code-block:: python @@ -116,6 +123,8 @@ def add_text( |add_text.png| + |add_text2.png| + MicroPython Code Block: .. code-block:: python @@ -139,7 +148,7 @@ def add_button( self, icon=None, text="", - w=lv.DPI_DEF // 3, + w=DPI, bg_c=0x2196F3, bg_opa=255, text_c=0xFFFFFF, @@ -162,14 +171,16 @@ def add_button( UiFlow2 Code Block: |add_button.png| + |add_button2.png| MicroPython Code Block: .. code-block:: python - win0.add_button(lv.SYMBOL.BULLET, text="", w=40, bg_c=0x2196F3) - win0.add_button(text="btn", w=40, bg_c=0x2196F3, bg_opa=255, text_c=0xFFFFFF, text_opa=255, font=lv.font_montserrat_14) + win0.add_button(icon=lv.SYMBOL.BULLET, text_c=0xffffff, text_opa=255, bg_c=0x2196f3, bg_opa=255, font=lv.font_montserrat_14) + + win0.add_button(text='M5', text_c=0xffffff, text_opa=255, bg_c=0x2196f3, bg_opa=255, font=lv.font_montserrat_14) """ _h = lv.pct(100) _button = m5ui.M5Button( From 0b7cec438938e5810b373fa3183a6b7109bc68e5 Mon Sep 17 00:00:00 2001 From: "tinyu.zhao@gmail.com" Date: Thu, 7 Aug 2025 15:33:01 +0800 Subject: [PATCH 245/322] lib/m5ui: Add LVGL Message Box widget. Signed-off-by: tinyu.zhao@gmail.com --- m5stack/libs/m5ui/__init__.py | 1 + m5stack/libs/m5ui/manifest.py | 1 + m5stack/libs/m5ui/msgbox.py | 96 +++++++++++++++++++++++++++++++++++ 3 files changed, 98 insertions(+) create mode 100644 m5stack/libs/m5ui/msgbox.py diff --git a/m5stack/libs/m5ui/__init__.py b/m5stack/libs/m5ui/__init__.py index d7b38c6a..c3e15d6a 100644 --- a/m5stack/libs/m5ui/__init__.py +++ b/m5stack/libs/m5ui/__init__.py @@ -18,6 +18,7 @@ "M5Label": "label", "M5Line": "line", "M5List": "list", + "M5Msgbox": "msgbox", "M5Page": "page", "M5Roller": "roller", "M5Scale": "scale", diff --git a/m5stack/libs/m5ui/manifest.py b/m5stack/libs/m5ui/manifest.py index cdde9d26..59bfb47a 100644 --- a/m5stack/libs/m5ui/manifest.py +++ b/m5stack/libs/m5ui/manifest.py @@ -20,6 +20,7 @@ "label.py", "line.py", "list.py", + "msgbox.py", "page.py", "port.py", "roller.py", diff --git a/m5stack/libs/m5ui/msgbox.py b/m5stack/libs/m5ui/msgbox.py new file mode 100644 index 00000000..f3794620 --- /dev/null +++ b/m5stack/libs/m5ui/msgbox.py @@ -0,0 +1,96 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +from m5ui.base import M5Base +import lvgl as lv +import m5ui + + +class M5Msgbox(lv.msgbox): + def __init__( + self, + title="", + x=0, + y=0, + w=0, + h=0, + parent=None, + ): + if parent is None: + parent = lv.screen_active() + super().__init__(parent) + self.set_align(lv.ALIGN.DEFAULT) + self.add_title(title) + self.set_pos(x, y) + self.set_size(w, h) + + def add_text( + self, + text, + text_c=0x212121, + text_opa=255, + bg_c=0xFFFFFF, + bg_opa=255, + font=lv.font_montserrat_14, + ): + _label = m5ui.M5Label( + text=text, + text_c=text_c, + bg_c=bg_c, + bg_opa=bg_opa, + font=font, + parent=self.get_content(), + ) + _label.set_width(lv.pct(100)) + _label.set_style_text_opa(text_opa, lv.PART.MAIN | lv.STATE.DEFAULT) + return _label + + def add_button( + self, + icon=None, + text="", + bg_c=0x2196F3, + bg_opa=255, + text_c=0xFFFFFF, + text_opa=255, + font=lv.font_montserrat_14, + option="footer", + ): + _parent = None + _w = 0 + _h = 0 + if option == "header": + _parent = self.get_header() + if _parent is None: + self.add_title("") + _parent = self.get_header() + _w = lv.DPI_DEF // 3 + _h = lv.pct(100) + elif option == "footer": + _parent = self.get_footer() + if _parent is None: + _tmp_btn = self.add_footer_button("") + _parent = self.get_footer() + _tmp_btn.delete() + _w = lv.SIZE_CONTENT + _h = lv.pct(100) + _button = m5ui.M5Button( + text=text, w=_w, h=_h, bg_c=bg_c, text_c=text_c, font=font, parent=_parent + ) + _button.set_style_bg_opa(bg_opa, lv.PART.MAIN | lv.STATE.DEFAULT) + _button.set_style_text_opa(text_opa, lv.PART.MAIN | lv.STATE.DEFAULT) + + if icon is not None: + _icon = lv.image(_button) + _icon.set_src(icon) + _icon.set_align(lv.ALIGN.CENTER) + return _button + + def __getattr__(self, name): + if hasattr(M5Base, name): + method = getattr(M5Base, name) + bound_method = lambda *args, **kwargs: method(self, *args, **kwargs) + setattr(self, name, bound_method) + return bound_method + raise AttributeError(f"'{self.__class__.__name__}' object has no attribute '{name}'") From 2de530f5bc9034ed76d2fd7be9967a67cb85d2f8 Mon Sep 17 00:00:00 2001 From: tinyu Date: Tue, 2 Sep 2025 15:18:24 +0800 Subject: [PATCH 246/322] lib/m5ui: Add LVGL Msgbox widget docs and example. Signed-off-by: tinyu --- docs/en/m5ui/index.rst | 1 + docs/en/m5ui/msgbox.rst | 463 +++++++++++ docs/en/refs/m5ui.msgbox.ref | 46 ++ docs/locales/zh_CN/LC_MESSAGES/m5ui/msgbox.po | 747 ++++++++++++++++++ .../m5ui/msgbox/msgbox_core2_example.m5f2 | 1 + examples/m5ui/msgbox/msgbox_core2_example.py | 86 ++ m5stack/libs/m5ui/line.py | 2 +- m5stack/libs/m5ui/msgbox.py | 93 +++ 8 files changed, 1438 insertions(+), 1 deletion(-) create mode 100644 docs/en/m5ui/msgbox.rst create mode 100644 docs/en/refs/m5ui.msgbox.ref create mode 100644 docs/locales/zh_CN/LC_MESSAGES/m5ui/msgbox.po create mode 100644 examples/m5ui/msgbox/msgbox_core2_example.m5f2 create mode 100644 examples/m5ui/msgbox/msgbox_core2_example.py diff --git a/docs/en/m5ui/index.rst b/docs/en/m5ui/index.rst index 65bde043..51b51484 100644 --- a/docs/en/m5ui/index.rst +++ b/docs/en/m5ui/index.rst @@ -72,6 +72,7 @@ Classes label.rst line.rst list.rst + msgbox.rst roller.rst scale.rst slider.rst diff --git a/docs/en/m5ui/msgbox.rst b/docs/en/m5ui/msgbox.rst new file mode 100644 index 00000000..a8fa9bc5 --- /dev/null +++ b/docs/en/m5ui/msgbox.rst @@ -0,0 +1,463 @@ +.. currentmodule:: m5ui + +M5Msgbox +======== + +.. include:: ../refs/m5ui.msgbox.ref + +M5Msgbox is a widget that can be used to create msgboxes in the user interface. + +UiFlow2 Example +--------------- + +msgbox event +^^^^^^^^^^^^^^ + +Open the |msgbox_core2_example.m5f2| project in UiFlow2. + +This example creates msgbox and associated with events. + +UiFlow2 Code Block: + + |msgbox_core2_example.png| + +Example output: + + None + + +MicroPython Example +------------------- + +msgbox event +^^^^^^^^^^^^^^ + +This example creates msgbox and associated with events. + +MicroPython Code Block: + + .. literalinclude:: ../../../examples/m5ui/msgbox/msgbox_core2_example.py + :language: python + :linenos: + +Example output: + + None + + +**API** +------- +M5Msgbox +^^^^^^^^ + +.. autoclass:: m5ui.msgbox.M5Msgbox + :members: + + .. py:method:: add_close_button() + + Add a close button to the msgboxheader. + + :return: None + + UiFlow2 Code Block: + + |add_close_button.png| + + MicroPython Code Block: + + .. code-block:: python + + msgbox_0.add_close_button() + + .. py:method:: delete() + + Delete the item from the msgbox. + + UiFlow2 Code Block: + + |label_delete.png| + + |button_delete.png| + + MicroPython Code Block: + + .. code-block:: python + + button_0.delete() + text_0.delete() + + + .. py:method:: set_text(txt) + + Set text of the msgbox button/label. + + :param str txt: The text to set for the msgbox button/label. + :return: None + + UiFlow2 Code Block: + + |button_set_text.png| + + |label_set_text.png| + + + MicroPython Code Block: + + .. code-block:: python + + button_0.set_text("Select an option") + + label_0.set_text("M5Stack") + + .. py:method:: set_style_text_font(font, part) + + Set the font of the msgbox button/label text. + + :param lv.lv_font_t font: The font to set. + :param int part: The part of the object to apply the style to (e.g., lv.PART.MAIN). + :return: None + + UiFlow2 Code Block: + + |button_set_font.png| + + |label_set_font.png| + + MicroPython Code Block: + + .. code-block:: python + + label_0.set_style_text_font(lv.font_montserrat_14, lv.PART.MAIN | lv.STATE.DEFAULT) + + button_0.set_style_text_font(lv.font_montserrat_14, lv.PART.MAIN | lv.STATE.DEFAULT) + + .. py:method:: set_text_color(color, opa, part) + + Set the color of the msgbox button/label. + + :param int color: The color to set. + :param int opa: The opacity of the color. + :param int part: The part of the object to apply the style to (e.g., lv.PART.MAIN). + :return: None + + UiFlow2 Code Block: + + |button_set_text_color.png| + + |label_set_text_color.png| + + + MicroPython Code Block: + + .. code-block:: python + + button_0.set_text_color(lv.color_hex(0x000000), 255, lv.PART.MAIN | lv.STATE.DEFAULT) + + label_0.set_text_color(lv.color_hex(0x000000), 255, lv.PART.MAIN | lv.STATE.DEFAULT) + + .. py:method:: set_bg_color(color, opa, part) + + Set the background color of the msgbox label. + + :param int color: The color to set. + :param int opa: The opacity of the color. + :param int part: The part of the object to apply the style to (e.g., lv.PART.MAIN). + :return: None + + UiFlow2 Code Block: + + |button_set_bg_color.png| + + |label_set_bg_color.png| + + MicroPython Code Block: + + .. code-block:: python + + button_0.set_bg_color(lv.color_hex(0x000000), 255, lv.PART.MAIN | lv.STATE.DEFAULT) + + label_0.set_bg_color(lv.color_hex(0x000000), 255, lv.PART.MAIN | lv.STATE.DEFAULT) + + .. py:method:: set_long_mode(mode) + + Set the long mode of the msgbox label. + + :param int mode: The long mode to set. + + UiFlow2 Code Block: + + |label_set_long_mode.png| + + MicroPython Code Block: + + .. code-block:: python + + label_0.set_long_mode(lv.label.LONG_MODE.WRAP) + + .. py:method:: set_flag(flag, value) + + Set a flag on the object. If ``value`` is True, the flag is added; if False, the flag is removed. + + :param int flag: The flag to set. + :param bool value: If True, the flag is added; if False, the flag is removed. + + UiFlow2 Code Block: + + |set_flag.png| + + |label_set_flag.png| + + |button_set_flag.png| + + MicroPython Code Block: + + .. code-block:: python + + msgbox_0.set_flag(lv.obj.FLAG.HIDDEN, True) + + .. py:method:: set_pos(x, y) + + Set the position of the msgbox. + + :param int x: The x-coordinate of the msgbox. + :param int y: The y-coordinate of the msgbox. + + UiFlow2 Code Block: + + |set_pos.png| + + MicroPython Code Block: + + .. code-block:: python + + msgbox_0.set_pos(100, 100) + + .. py:method:: set_x(x) + + Set the x-coordinate of the msgbox. + + :param int x: The x-coordinate of the msgbox. + + UiFlow2 Code Block: + + |set_x.png| + + MicroPython Code Block: + + .. code-block:: python + + msgbox_0.set_x(100) + + .. py:method:: set_y(y) + + Set the y-coordinate of the msgbox. + + :param int y: The y-coordinate of the msgbox. + + UiFlow2 Code Block: + + |set_y.png| + + MicroPython Code Block: + + .. code-block:: python + + msgbox_0.set_y(100) + + .. py:method:: set_size(width, height) + + Set the size of the msgbox. + + :param int width: The width of the msgbox. + :param int height: The height of the msgbox. + :return: None + + UiFlow2 Code Block: + + |set_size.png| + + MicroPython Code Block: + + .. code-block:: python + + msgbox_0.set_size(100, 50) + + + .. py:method:: align_to(obj, align, x, y) + + Align the msgboxto another object. + + :param lv.obj obj: The object to align to. + :param int align: The alignment type. + :param int x: The x-offset from the aligned object. + :param int y: The y-offset from the aligned object. + + UiFlow2 Code Block: + + |align_to.png| + + MicroPython Code Block: + + .. code-block:: python + + msgbox_0.align_to(page_0, lv.ALIGN.CENTER, 0, 0) + + .. py:method:: set_state(state, value) + + Set the state of the bar. If ``value`` is True, the state is set; if False, the state is unset. + + :param int state: The state to set. + :param bool value: If True, the state is set; if False, the state is unset. + :return: None + + UiFlow2 Code Block: + + |button_set_state.png| + + MicroPython Code Block: + + .. code-block:: python + + msgbox_0.set_state(lv.STATE.PRESSED, True) + + .. py:method:: toggle_flag(flag) + + Toggle a flag on the object. If the flag is set, it is removed; if not set, it is added. + + :param int flag: The flag to toggle. + :return: None + + UiFlow2 Code Block: + + |button_toggle_flag.png| + + MicroPython Code Block: + + .. code-block:: python + + msgbox_0.toggle_flag(lv.obj.FLAG.HIDDEN) + + + .. py:method:: set_style_radius(radius, part) + + Set the corner radius of the msgbox button. + + :param int radius: The radius to set. + :param int part: The part of the object to apply the style to (e.g., lv.PART.MAIN). + :return: None + + UiFlow2 Code Block: + + |button_set_radius.png| + + MicroPython Code Block: + + .. code-block:: python + + button_0.set_style_radius(10, lv.PART.MAIN | lv.STATE.DEFAULT) + + .. py:method:: set_shadow(color, opa, align, offset_x, offset_y) + + Set a shadow for the label. + + :param int color: The color of the shadow in hexadecimal format or an integer. + :param int opa: The opacity of the shadow (0-255). + :param int align: The alignment of the shadow relative to the label. + :param int offset_x: The horizontal offset of the shadow. + :param int offset_y: The vertical offset of the shadow. + :return: None + + UiFlow2 Code Block: + + |label_set_shadow.png| + + MicroPython Code Block: + + .. code-block:: python + + label_0.set_shadow(color=0x000000, opa=128, align=lv.ALIGN.BOTTOM_RIGHT, offset_x=5, offset_y=5) + + .. py:method:: unset_shadow() + + Remove the shadow from the label. + + UiFlow2 Code Block: + + |label_unset_shadow.png| + + MicroPython Code Block: + + .. code-block:: python + + label_0.unset_shadow() + + .. py:method:: get_text() + + Get the text of the label. + + :return: The text of the label. + :rtype: str + + UiFlow2 Code Block: + + |button_get_text.png| + + |label_get_text.png| + + MicroPython Code Block: + + .. code-block:: python + + label_0.get_text() + button_0.get_text() + + + .. py:method:: toggle_state(state) + + Toggle the state of the button. If the state is set, it is unset; if not set, it is set. + + :param int state: The state to toggle. + :return: None + + UiFlow2 Code Block: + + |button_toggle_state.png| + + MicroPython Code Block: + + .. code-block:: python + + button_0.toggle_state(lv.STATE.PRESSED) + + .. py:method:: add_event_cb(handler, event, user_data) + + Add an event callback to the button. The callback will be called when the specified event occurs. + + :param function handler: The callback function to call. + :param int event: The event to listen for. + :param Any user_data: Optional user data to pass to the callback. + :return: None + + UiFlow2 Code Block: + + |button_event.png| + + MicroPython Code Block: + + .. code-block:: python + + def btn_ono_clicked_event(event_struct): + global page0, msgbox_0, label_lkg, btn_ono, btn_pjm, label0 + + print('hello M5') + + + def btn_ono_event_handler(event_struct): + global page0, msgbox_0, label_lkg, btn_ono, btn_pjm, label0 + event = event_struct.code + if event == lv.EVENT.CLICKED and True: + btn_ono_clicked_event(event_struct) + return + + btn_ono.add_event_cb(btn_ono_event_handler, lv.EVENT.ALL, None) + diff --git a/docs/en/refs/m5ui.msgbox.ref b/docs/en/refs/m5ui.msgbox.ref new file mode 100644 index 00000000..3d0980ac --- /dev/null +++ b/docs/en/refs/m5ui.msgbox.ref @@ -0,0 +1,46 @@ +.. |add_text.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/msgbox/add_text.png +.. |add_text2.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/msgbox/add_text2.png +.. |add_button.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/msgbox/add_button.png +.. |add_button2.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/msgbox/add_button2.png +.. |add_close_button.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/msgbox/add_close_button.png +.. |label_delete.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/msgbox/label_delete.png +.. |button_delete.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/msgbox/button_delete.png +.. |button_set_text.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/msgbox/button_set_text.png +.. |label_set_text.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/msgbox/label_set_text.png +.. |set_style_text_font.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/msgbox/set_style_text_font.png +.. |button_set_text_color.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/msgbox/button_set_text_color.png +.. |label_set_text_color.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/msgbox/label_set_text_color.png +.. |button_set_bg_color.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/msgbox/button_set_bg_color.png +.. |label_set_bg_color.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/msgbox/label_set_bg_color.png +.. |label_set_long_mode.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/msgbox/label_set_long_mode.png +.. |set_flag.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/msgbox/set_flag.png +.. |label_set_flag.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/msgbox/label_set_flag.png +.. |button_set_flag.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/msgbox/button_set_flag.png +.. |set_pos.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/msgbox/set_pos.png +.. |set_x.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/msgbox/set_x.png +.. |set_y.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/msgbox/set_y.png +.. |set_size.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/msgbox/set_size.png +.. |align_to.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/msgbox/align_to.png +.. |button_set_state.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/msgbox/button_set_state.png +.. |button_toggle_flag.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/msgbox/button_toggle_flag.png +.. |button_set_radius.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/msgbox/button_set_radius.png +.. |label_set_shadow.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/msgbox/label_set_shadow.png +.. |label_unset_shadow.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/msgbox/label_unset_shadow.png +.. |button_get_text.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/msgbox/button_get_text.png +.. |label_get_text.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/msgbox/label_get_text.png +.. |button_toggle_state.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/msgbox/button_toggle_state.png +.. |button_set_font.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/msgbox/button_set_font.png +.. |label_set_font.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/msgbox/label_set_font.png + +.. |button_event.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/msgbox/button_event.png + +.. |msgbox_core2_example.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/msgbox/example.png + +.. |msgbox_core2_example.m5f2| raw:: html + + + msgbox_core2_example.m5f2 + \ No newline at end of file diff --git a/docs/locales/zh_CN/LC_MESSAGES/m5ui/msgbox.po b/docs/locales/zh_CN/LC_MESSAGES/m5ui/msgbox.po new file mode 100644 index 00000000..a479c4a3 --- /dev/null +++ b/docs/locales/zh_CN/LC_MESSAGES/m5ui/msgbox.po @@ -0,0 +1,747 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2016 - 2025 M5Stack Technology Co., Ltd +# This file is distributed under the same license as the UIFlow2 Programming +# Guide package. +# FIRST AUTHOR , 2025. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: UIFlow2 Programming Guide \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-09-02 15:12+0800\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: zh_CN\n" +"Language-Team: zh_CN \n" +"Plural-Forms: nplurals=1; plural=0;\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.16.0\n" + +#: ../../en/m5ui/msgbox.rst:4 ../../en/m5ui/msgbox.rst:51 +#: b98d06f002be443aa3cdab2a0634bdc8 f734a60e9e7547dfa2c1d7e14db63eba +msgid "M5Msgbox" +msgstr "M5Msgbox" + +#: ../../en/m5ui/msgbox.rst:8 b25ffcab4ef448b8931970137fd64ba5 +msgid "" +"M5Msgbox is a widget that can be used to create msgboxes in the user " +"interface." +msgstr "M5Msgbox 是一个用于在用户界面中创建msgbox的组件。" + +#: ../../en/m5ui/msgbox.rst:11 94df4e26fa73437ebb02c78671d48cce +msgid "UiFlow2 Example" +msgstr "UiFlow2 示例" + +#: ../../en/m5ui/msgbox.rst:14 ../../en/m5ui/msgbox.rst:33 +#: 12a172334cfa4a5097452fb9ce3011da e44935d0d177460b986f8ab8dc262359 +msgid "msgbox event" +msgstr "msgbox事件" + +#: ../../en/m5ui/msgbox.rst:16 9106ef4fc3474e2a8dd2a86a43b17506 +msgid "Open the |msgbox_core2_example.m5f2| project in UiFlow2." +msgstr "在 UiFlow2 中打开 |msgbox_core2_example.m5f2| 项目。" + +#: ../../en/m5ui/msgbox.rst:18 ../../en/m5ui/msgbox.rst:35 +#: 94efb564b6d64e9692bb34702a6e9095 e3617d4b1113463eb795154685c85fbe +msgid "This example creates msgbox and associated with events." +msgstr "本示例创建msgbox并关联事件。" + +#: ../../en/m5ui/msgbox.rst:20 ../../en/m5ui/msgbox.rst:62 +#: ../../en/m5ui/msgbox.rst:76 ../../en/m5ui/msgbox.rst:97 +#: ../../en/m5ui/msgbox.rst:120 ../../en/m5ui/msgbox.rst:143 +#: ../../en/m5ui/msgbox.rst:167 ../../en/m5ui/msgbox.rst:187 +#: ../../en/m5ui/msgbox.rst:204 ../../en/m5ui/msgbox.rst:225 +#: ../../en/m5ui/msgbox.rst:241 ../../en/m5ui/msgbox.rst:257 +#: ../../en/m5ui/msgbox.rst:275 ../../en/m5ui/msgbox.rst:295 +#: ../../en/m5ui/msgbox.rst:313 ../../en/m5ui/msgbox.rst:330 +#: ../../en/m5ui/msgbox.rst:349 ../../en/m5ui/msgbox.rst:370 +#: ../../en/m5ui/msgbox.rst:384 ../../en/m5ui/msgbox.rst:401 +#: ../../en/m5ui/msgbox.rst:422 ../../en/m5ui/msgbox.rst:441 +#: 16f351a8bb80460da9129e4687f51411 2f53e10dfef34d39817cb3c49424bbe3 +#: 31cc0a1fdb0a428ca49d0274bea6c169 5a51c7805b744de0ace8fb7782c67c2e +#: 5c60031c9875426d806fdd1f08c27b6b 6dad63c22313458f86cd09ded2f07a84 +#: 7197bcc65a5d4262b2729290911d7e60 735699b5e4364597822a342c9117b734 +#: 73ee40c7643d4038a3f3d954e8fe1d32 8a31868c5d0e411f9ec7c85e124e49cd +#: 91c58fb8c56b4af59704e3aeb7de51ee 949132f1d94d4c3686c1e5ed1f40ce17 +#: adef8210908645229de08e8602398579 b1b83f89ea81497da936a364cc3fee5a +#: b1e3c74a523740d6934305e0fe02d778 c285a859f3da439a9f942466595653db +#: c34f1212d20d4b7184455dac2db35745 c9efcfdd4ec0456d8f45a4984eb18bb2 +#: ca6b037f99b24549a2b0eb5281826226 d0ac783584c84a5d81873824a0160dbd +#: d2207234ab7f4945b0550cac6e290d67 d8fd7b96b6e8483b928941e4989f62ea +#: e04c0cafaea74a2ca4dcfda6a156e3bf e647b90e494c4d1bb6e4aa7cfd3e000f +#: m5ui.msgbox.M5Msgbox.add_button:15 m5ui.msgbox.M5Msgbox.add_text:13 of +msgid "UiFlow2 Code Block:" +msgstr "UiFlow2 代码块:" + +#: ../../en/m5ui/msgbox.rst:22 70bf04edf7f0453c8467a7410afc7a84 +msgid "|msgbox_core2_example.png|" +msgstr "" + +#: ../../en/refs/m5ui.msgbox.ref:37 cb7e8638ec7c4954a724a896b4674021 +msgid "msgbox_core2_example.png" +msgstr "" + +#: ../../en/m5ui/msgbox.rst:24 ../../en/m5ui/msgbox.rst:43 +#: 811db614a6a5486c9756440d894e3114 bc9a9b998049438bba87256ebcfcea04 +msgid "Example output:" +msgstr "示例输出:" + +#: ../../en/m5ui/msgbox.rst:26 ../../en/m5ui/msgbox.rst:45 +#: ../../en/m5ui/msgbox.rst:60 ../../en/m5ui/msgbox.rst:95 +#: ../../en/m5ui/msgbox.rst:118 ../../en/m5ui/msgbox.rst:141 +#: ../../en/m5ui/msgbox.rst:165 ../../en/m5ui/msgbox.rst:273 +#: ../../en/m5ui/msgbox.rst:311 ../../en/m5ui/msgbox.rst:328 +#: ../../en/m5ui/msgbox.rst:347 ../../en/m5ui/msgbox.rst:368 +#: ../../en/m5ui/msgbox.rst:420 ../../en/m5ui/msgbox.rst:439 +#: 1535cdc5f47a48538603c8c15cad9044 26b0061beb6642f3a698834b2249f958 +#: 2f7cf0bdadb1494885ac064ee3ad25c0 378dbd63cc8a488dabe928c6bc1f955f +#: 424dc93985414176a9feb600ad768ea6 505f0b9ede0e4a9897ca8431d67d0654 +#: 5348cfc07cb146208b46a50f8dbfa4d9 7b32a293011542518dabe8250bbfb629 +#: 7da17916b8bd491387c486dfa3657257 7dd5a75c2bfd40dbb4348f12273560cb +#: a78d975e95a24b00bb7ee317e6d15dc8 da2f58f03de141b9b0bec1eabd1599fd +#: e3449784b9c545ebad4054fb891ee4ac f44912973a8c4d158bf34a9fb67e4514 +msgid "None" +msgstr "" + +#: ../../en/m5ui/msgbox.rst:30 7e28d37c4e3c45cbb2721796e24ea5d7 +msgid "MicroPython Example" +msgstr "" + +#: ../../en/m5ui/msgbox.rst:37 ../../en/m5ui/msgbox.rst:66 +#: ../../en/m5ui/msgbox.rst:82 ../../en/m5ui/msgbox.rst:104 +#: ../../en/m5ui/msgbox.rst:126 ../../en/m5ui/msgbox.rst:150 +#: ../../en/m5ui/msgbox.rst:173 ../../en/m5ui/msgbox.rst:191 +#: ../../en/m5ui/msgbox.rst:212 ../../en/m5ui/msgbox.rst:229 +#: ../../en/m5ui/msgbox.rst:245 ../../en/m5ui/msgbox.rst:261 +#: ../../en/m5ui/msgbox.rst:279 ../../en/m5ui/msgbox.rst:299 +#: ../../en/m5ui/msgbox.rst:317 ../../en/m5ui/msgbox.rst:334 +#: ../../en/m5ui/msgbox.rst:353 ../../en/m5ui/msgbox.rst:374 +#: ../../en/m5ui/msgbox.rst:388 ../../en/m5ui/msgbox.rst:407 +#: ../../en/m5ui/msgbox.rst:426 ../../en/m5ui/msgbox.rst:445 +#: 19bbf021db3b4200891e5595e6b639c3 1e234f899888408b86632e6f07fd4717 +#: 289e37cbbf9d426785704f022ccfc8db 2bec61d81ef54028b383133d59901cc8 +#: 2d1b25ce5fdf4c958ff093dad842c240 3443e868e2b746e388b0014259e8f265 +#: 3650beef8528486a91f08bd4c206c90b 42001e1487084d3f8e05845cb7176410 +#: 4cb0a50f7e1b4ce08df04356f3cc7cd0 504b1a25900d4b95b3d0a875a0c47c5e +#: 5c19d35bf85a4ca1b05ad108db0bac79 6151fdc3726e4ab0a08d97cd179c8b35 +#: 631d5a7b40194ff2b2a446eab23374c9 63b6819d4c3549ffb13f998f18c53ccb +#: 783ffe0573c1454f9b7fcee5ebf3ffaa 7926297dc026418685cffa7a1678fbdd +#: 8013fd55e44b485a829324ca9c373d9a 86a601631d4649c89e5e513cd71551d9 +#: 875a573f3f2445b4a58c26c20342be00 957f2f0a214b45b9a7237863aff2157b +#: ace8a3fa94714b5fbe15515632cc4574 cd9e6a44daf442518795d53d0116a94e +#: d1fe6b29f5e849cc80664618c2b57582 ef11300e9ae24a189db6b35b3bb65b0d +#: f77a9658044d4c2f93a0649520ff9a99 m5ui.msgbox.M5Msgbox:10 +#: m5ui.msgbox.M5Msgbox.add_button:21 m5ui.msgbox.M5Msgbox.add_text:19 of +msgid "MicroPython Code Block:" +msgstr "MicroPython 代码块:" + +#: ../../en/m5ui/msgbox.rst:49 e663be305c0b4440aaa9a6da3a69b99a +msgid "**API**" +msgstr "" + +#: 8f82413b826c42fb8ebb54b1dbc9f8b1 m5ui.msgbox.M5Msgbox:1 of +msgid "Bases: :py:class:`~lvgl.msgbox`" +msgstr "" + +#: db04dd5d578f41cda69c62a35e55caef m5ui.msgbox.M5Msgbox:1 of +msgid "Create a msgbox object." +msgstr "创建一个msgbox对象。" + +#: ../../en/m5ui/msgbox.rst 1317b17574344c69b9322cee2b847e93 +#: 1d167cf7dca44cafb260d4a0a6c2181e 331b4827c1c34a7fba545ce080a3225c +#: 373acf590461427892d8cfec87ca978d 58b037ee587748d5a63881978668befb +#: 643edb5aea3842efb17bbe82607eab3a 6d865c845999404685c7744772928dbb +#: 75787c8256fa45589747fb003f01eafc 7f59ed24d9ec443a92daeca09f0b2740 +#: 982aa912906547e0b757a54becd75c78 9f6053207e42443dbbebfc29fd3aaafb +#: a9ea2c5bebd14ca5ad97ed9cd220de46 b707d97c34a04fec847d209ee5a952bb +#: bbfede6f3f2f40eea0afbc9b58825838 cff07912de0a4d4b87adc6b01a867c9c +#: d4a93c1dbc4d455d9c0af6887674f832 dfe97bc8967b42f0815010060d2ef3b4 +#: ee2289c581b4464aac46bf489450e819 f0c87a347f1d4960ac604d94083681df +#: f20d222fe59c47499809c984bba28a84 m5ui.msgbox.M5Msgbox.add_button +#: m5ui.msgbox.M5Msgbox.add_text of +msgid "Parameters" +msgstr "参数" + +#: 79c5bcac2d98488ba10449d3434d7ae4 m5ui.msgbox.M5Msgbox:3 of +msgid "The title of the msgbox." +msgstr "msgbox的标题。" + +#: ../../en/m5ui/msgbox.rst:222 ../../en/m5ui/msgbox.rst:239 +#: 107a6e9cd9244a02b8f70df7edf92740 8ea007afe63744bbb5248f931add1d38 +#: ef7e5cc03d474768ab7bfbe5b55e27e0 m5ui.msgbox.M5Msgbox:4 of +msgid "The x-coordinate of the msgbox." +msgstr "msgbox的 x 坐标。" + +#: ../../en/m5ui/msgbox.rst:223 ../../en/m5ui/msgbox.rst:255 +#: 25818443728c470c9fa353076263924d 38e3e7393eae4d36993d762ec3b3edba +#: cd74d75a56854012b7eca26f18e08f4d m5ui.msgbox.M5Msgbox:5 of +msgid "The y-coordinate of the msgbox." +msgstr "msgbox的 y 坐标。" + +#: ../../en/m5ui/msgbox.rst:271 103c84e220074ae8a67f8e6d1fdb751e +#: d0791b6da6164e6eaf2f76d79278f1f2 m5ui.msgbox.M5Msgbox:6 of +msgid "The width of the msgbox." +msgstr "msgbox的宽度。" + +#: ../../en/m5ui/msgbox.rst:272 3051a88d8d2b49149a34b6ae71ffc0a8 +#: c64a3be890974410a4de91a177052f53 m5ui.msgbox.M5Msgbox:7 of +msgid "The height of the msgbox." +msgstr "msgbox的高度。" + +#: f7465520adf44693ab39db83ed053065 m5ui.msgbox.M5Msgbox:8 of +msgid "" +"The parent object to attach the msgbox to. If not specified, the msgbox " +"will be attached to the default screen." +msgstr "要附加msgbox的父对象。如果未指定,则msgbox会附加到默认屏幕。" + +#: ../../en/m5ui/msgbox.rst:58 efdee2e4fd654e918b0110d1b86377a4 +msgid "Add a close button to the msgboxheader." +msgstr "为msgbox头部添加关闭按钮。" + +#: ../../en/m5ui/msgbox.rst 08a437c0961742dba3a65ab5fcd6686f +#: 2d1d88aca78b433ca064ce4033fa2ddc 4414e1acef4a499b9bd80c0922dee1ae +#: 61480c4c26fa4605bfe788bb33ab732f 65f157e088f9481890b0362dc38c8358 +#: 6b4720f0a5944812820ae40d553f73a1 71547769b6584dda8b5230487e9f365c +#: 7b2d57b249264730974a3dc90de8ad8c 7dd0cb4c36ed4b5fa1d25e21a9b02f96 +#: 7e40ca77345b4c73bb2f1caf3e356f2f 83c1b7aadfbd416daac4dd7a4dea6b29 +#: b2a4fe24763b45f69b7c9262afdeec15 b82a8134c6774dc691a845fbe454fa5c +#: c3abea219a73478aa7b5b7756c1b5a16 fb2cd4a6e2b2492088f7f99793ba8281 +#: m5ui.msgbox.M5Msgbox.add_button m5ui.msgbox.M5Msgbox.add_text of +msgid "Returns" +msgstr "返回" + +#: ../../en/m5ui/msgbox.rst:64 509cf4a3749f45069db940f09d5ce0ae +msgid "|add_close_button.png|" +msgstr "" + +#: ../../en/refs/m5ui.msgbox.ref:5 867a8e64d9044aaa9b92a0ae6e4b4a7c +msgid "add_close_button.png" +msgstr "" + +#: ../../en/m5ui/msgbox.rst:74 e25122ba43db46c89cf62df452a42a2a +msgid "Delete the item from the msgbox." +msgstr "从msgbox中删除项目。" + +#: ../../en/m5ui/msgbox.rst:78 dbb6fb6ed6df4239a50c7653891987ad +msgid "|label_delete.png|" +msgstr "" + +#: ../../en/refs/m5ui.msgbox.ref:6 210a133e82904ec5b110dffa6e0b5661 +msgid "label_delete.png" +msgstr "" + +#: ../../en/m5ui/msgbox.rst:80 eac05698fdd34e5a8a96741875723eb5 +msgid "|button_delete.png|" +msgstr "" + +#: ../../en/refs/m5ui.msgbox.ref:7 ad7603fbc3c94871b761fc8b3fbfead7 +msgid "button_delete.png" +msgstr "" + +#: ../../en/m5ui/msgbox.rst:92 4d87c1c680f54ce2ac7787b6ed453583 +msgid "Set text of the msgbox button/label." +msgstr "设置msgbox按钮/标签的文本。" + +#: ../../en/m5ui/msgbox.rst:94 bbdfa550a23e4335b7f0785ab3858049 +msgid "The text to set for the msgbox button/label." +msgstr "要为msgbox按钮/标签设置的文本。" + +#: ../../en/m5ui/msgbox.rst:99 11debc6f98ec4e1ea6e2cdbf20cc728f +msgid "|button_set_text.png|" +msgstr "" + +#: ../../en/refs/m5ui.msgbox.ref:8 3d8a085f731f44de8b2dd3abbc5e9763 +msgid "button_set_text.png" +msgstr "" + +#: ../../en/m5ui/msgbox.rst:101 c59c19b2b9cc401abd181a31a3dd9787 +msgid "|label_set_text.png|" +msgstr "" + +#: ../../en/refs/m5ui.msgbox.ref:9 73e06a4c5ede4958b390bf50bfb98d6b +msgid "label_set_text.png" +msgstr "" + +#: ../../en/m5ui/msgbox.rst:114 aef1e628038a4d8c94ef959791c695fc +msgid "Set the font of the msgbox button/label text." +msgstr "设置msgbox按钮/标签文本的字体。" + +#: ../../en/m5ui/msgbox.rst:116 6844c4e5c8bd4709a5c82c8d58c0aae2 +msgid "The font to set." +msgstr "要设置的字体。" + +#: ../../en/m5ui/msgbox.rst:117 ../../en/m5ui/msgbox.rst:140 +#: ../../en/m5ui/msgbox.rst:164 ../../en/m5ui/msgbox.rst:346 +#: 29b474be855c4fa6b4a05ede5e9f2183 6545f55d34c743408f99c8698b2ef673 +#: 91435c973e9d42d992670caa73e1402d 9bcdeadb5e3241b997d2797b73143097 +msgid "The part of the object to apply the style to (e.g., lv.PART.MAIN)." +msgstr "要应用样式的对象部分(例如 lv.PART.MAIN)。" + +#: ../../en/m5ui/msgbox.rst:122 ef1be4dea3a94cd7966efdce4a54bb18 +msgid "|button_set_font.png|" +msgstr "" + +#: ../../en/refs/m5ui.msgbox.ref:32 03c108c02dbb49589cda835df7804c89 +msgid "button_set_font.png" +msgstr "" + +#: ../../en/m5ui/msgbox.rst:124 946fda450da4408491f1a188706fd6c6 +msgid "|label_set_font.png|" +msgstr "" + +#: ../../en/refs/m5ui.msgbox.ref:33 3b8d31ea18d74bb9b226d8e6706227fd +msgid "label_set_font.png" +msgstr "" + +#: ../../en/m5ui/msgbox.rst:136 d7b474ab9924498f893e975f538ae396 +msgid "Set the color of the msgbox button/label." +msgstr "设置msgbox按钮/标签的颜色。" + +#: ../../en/m5ui/msgbox.rst:138 ../../en/m5ui/msgbox.rst:162 +#: 282a2c6823a54d05a74d3ebec9b82223 dc45b25b171944f6a5bfe3632e3caa22 +msgid "The color to set." +msgstr "要设置的颜色。" + +#: ../../en/m5ui/msgbox.rst:139 ../../en/m5ui/msgbox.rst:163 +#: ced18a4de717429d8504ad116c2da720 dfdb491edfd545048a2eb0c4cc9ed9e5 +msgid "The opacity of the color." +msgstr "颜色的不透明度。" + +#: ../../en/m5ui/msgbox.rst:145 c68579db4b824e4a97542241762edd77 +msgid "|button_set_text_color.png|" +msgstr "" + +#: ../../en/refs/m5ui.msgbox.ref:11 b74067ce4f88486eb210ca8701258e12 +msgid "button_set_text_color.png" +msgstr "" + +#: ../../en/m5ui/msgbox.rst:147 e723bc9bf9f84ba18eea92de40a72a77 +msgid "|label_set_text_color.png|" +msgstr "" + +#: ../../en/refs/m5ui.msgbox.ref:12 2bfdfd38ed7d48b09b84f20a97a5a163 +msgid "label_set_text_color.png" +msgstr "" + +#: ../../en/m5ui/msgbox.rst:160 f4733a58047d45dea91a5c68f35c08e9 +msgid "Set the background color of the msgbox label." +msgstr "设置msgbox按钮/标签的背景颜色。" + +#: ../../en/m5ui/msgbox.rst:169 e7c14f7d94a947d89ff55ca0b6fb6f41 +msgid "|button_set_bg_color.png|" +msgstr "" + +#: ../../en/refs/m5ui.msgbox.ref:13 1fad6b359fc24ade85f4e4e6e717de86 +msgid "button_set_bg_color.png" +msgstr "" + +#: ../../en/m5ui/msgbox.rst:171 bb743b0ddf484e83b89dd7341df4dcae +msgid "|label_set_bg_color.png|" +msgstr "" + +#: ../../en/refs/m5ui.msgbox.ref:14 9fba92b1cba34977a760ce209e73b9f0 +msgid "label_set_bg_color.png" +msgstr "" + +#: ../../en/m5ui/msgbox.rst:183 547065cd01144a0fadb2be7e6e481cf8 +msgid "Set the long mode of the msgbox label." +msgstr "设置msgbox标签的长文本模式。" + +#: ../../en/m5ui/msgbox.rst:185 c6b719b3c27f47ddab4482d8e44a32b8 +msgid "The long mode to set." +msgstr "要设置的长文本模式。" + +#: ../../en/m5ui/msgbox.rst:189 3ef0fa5f93b545cfb97a05449448b08e +msgid "|label_set_long_mode.png|" +msgstr "" + +#: ../../en/refs/m5ui.msgbox.ref:15 708ff911cc084d75afbe3ea49b79e40d +msgid "label_set_long_mode.png" +msgstr "" + +#: ../../en/m5ui/msgbox.rst:199 95e28a2824a24fa98c45a9481a095ec2 +msgid "" +"Set a flag on the object. If ``value`` is True, the flag is added; if " +"False, the flag is removed." +msgstr "在对象上设置标志。如果 ``value`` 为 True,则添加该标志;为 False 时移除该标志。" + +#: ../../en/m5ui/msgbox.rst:201 397f1a15cc4c426dad99828568b9115d +msgid "The flag to set." +msgstr "要设置的标志。" + +#: ../../en/m5ui/msgbox.rst:202 bae22d70113e4a9da38ef5118a75605a +msgid "If True, the flag is added; if False, the flag is removed." +msgstr "为 True 时添加标志,为 False 时移除标志。" + +#: ../../en/m5ui/msgbox.rst:206 4476358802e9466d9e8cd0575fb5acf0 +msgid "|set_flag.png|" +msgstr "" + +#: ../../en/refs/m5ui.msgbox.ref:16 873f8cc7de0c4cc0bfc92c42c2924324 +msgid "set_flag.png" +msgstr "" + +#: ../../en/m5ui/msgbox.rst:208 804360b55cd24bdb81b752371696e7e5 +msgid "|label_set_flag.png|" +msgstr "" + +#: ../../en/refs/m5ui.msgbox.ref:17 1d10330247fd40b995b709f92a389339 +msgid "label_set_flag.png" +msgstr "" + +#: ../../en/m5ui/msgbox.rst:210 e3b816e2e42743a8a1a19f058c0e5c62 +msgid "|button_set_flag.png|" +msgstr "" + +#: ../../en/refs/m5ui.msgbox.ref:18 2bf3f15264674506baa163bfeea565f8 +msgid "button_set_flag.png" +msgstr "" + +#: ../../en/m5ui/msgbox.rst:220 ce7e68a66df14107932a6fe90766abb2 +msgid "Set the position of the msgbox." +msgstr "设置msgbox的位置。" + +#: ../../en/m5ui/msgbox.rst:227 e75c515a60654bb5beba83228bf3e18b +msgid "|set_pos.png|" +msgstr "" + +#: ../../en/refs/m5ui.msgbox.ref:19 723738aa41374df8ad43b34f13a7aebc +msgid "set_pos.png" +msgstr "" + +#: ../../en/m5ui/msgbox.rst:237 fcbaf4965b2c4ac9b9040ebd699fc240 +msgid "Set the x-coordinate of the msgbox." +msgstr "设置msgbox的 x 坐标。" + +#: ../../en/m5ui/msgbox.rst:243 beab43a7cd714156928d936b44595c14 +msgid "|set_x.png|" +msgstr "" + +#: ../../en/refs/m5ui.msgbox.ref:20 549442c99fe745cd90e1e4ca310ec999 +msgid "set_x.png" +msgstr "" + +#: ../../en/m5ui/msgbox.rst:253 535cfeb842144f349f02c53e4b87980e +msgid "Set the y-coordinate of the msgbox." +msgstr "设置msgbox的 y 坐标。" + +#: ../../en/m5ui/msgbox.rst:259 842dd0dedc5348529148c8c8231ffea1 +msgid "|set_y.png|" +msgstr "" + +#: ../../en/refs/m5ui.msgbox.ref:21 c2a9dfd2910843b4b34e41c96c64879e +msgid "set_y.png" +msgstr "" + +#: ../../en/m5ui/msgbox.rst:269 4015e66038584bfd9452bef4dd72978f +msgid "Set the size of the msgbox." +msgstr "设置msgbox的尺寸。" + +#: ../../en/m5ui/msgbox.rst:277 7ca1b2b7730d452b84e56f3ff371e59a +msgid "|set_size.png|" +msgstr "" + +#: ../../en/refs/m5ui.msgbox.ref:22 b5630dcf000d473fbec60f5fd84e49a4 +msgid "set_size.png" +msgstr "" + +#: ../../en/m5ui/msgbox.rst:288 8146e66a45a843aa883ef438c614c875 +msgid "Align the msgboxto another object." +msgstr "将msgbox对齐到其他对象。" + +#: ../../en/m5ui/msgbox.rst:290 0e425f24dfa5450eb49cb42552e2ca48 +msgid "The object to align to." +msgstr "要对齐的对象。" + +#: ../../en/m5ui/msgbox.rst:291 2dcb6cbab20a494aaa283a58a5323cd3 +msgid "The alignment type." +msgstr "对齐方式类型。" + +#: ../../en/m5ui/msgbox.rst:292 e753a37d893f442da4e519063549956a +msgid "The x-offset from the aligned object." +msgstr "相对于对齐对象的 x 偏移量。" + +#: ../../en/m5ui/msgbox.rst:293 3c55e984883f4af8bb42eb00294e907b +msgid "The y-offset from the aligned object." +msgstr "相对于对齐对象的 y 偏移量。" + +#: ../../en/m5ui/msgbox.rst:297 f6737bd7a4c94e6aaf6b130ee666aaab +msgid "|align_to.png|" +msgstr "" + +#: ../../en/refs/m5ui.msgbox.ref:23 5086bf5926b54bbb9b1d2fb4c7b6319d +msgid "align_to.png" +msgstr "" + +#: ../../en/m5ui/msgbox.rst:307 ce249d865cfd473f81117e057bb99dd8 +msgid "" +"Set the state of the bar. If ``value`` is True, the state is set; if " +"False, the state is unset." +msgstr "设置进度条的状态。如果 ``value`` 为 True,则设置状态;为 False 时取消状态。" + +#: ../../en/m5ui/msgbox.rst:309 8247a022ca6e4709b0b6ecd6a0af4e64 +msgid "The state to set." +msgstr "要设置的状态。" + +#: ../../en/m5ui/msgbox.rst:310 d55db59b65584ab9b8af2d27768b485c +msgid "If True, the state is set; if False, the state is unset." +msgstr "为 True 时设置状态;为 False 时取消状态。" + +#: ../../en/m5ui/msgbox.rst:315 8f1e6cc6c9684c84a6a3ac25adae07ea +msgid "|button_set_state.png|" +msgstr "" + +#: ../../en/refs/m5ui.msgbox.ref:24 825121590e2e415ca2ad2191f08ad949 +msgid "button_set_state.png" +msgstr "" + +#: ../../en/m5ui/msgbox.rst:325 5475a8e7bbd044b5af478e3097facfc1 +msgid "" +"Toggle a flag on the object. If the flag is set, it is removed; if not " +"set, it is added." +msgstr "切换对象上的标志。如果该标志已设置,则移除;否则添加。" + +#: ../../en/m5ui/msgbox.rst:327 b07d4602fe434aa7a42a7d84d678452b +msgid "The flag to toggle." +msgstr "要切换的标志。" + +#: ../../en/m5ui/msgbox.rst:332 ea1b7765bfb64de09d03511efdad29a3 +msgid "|button_toggle_flag.png|" +msgstr "" + +#: ../../en/refs/m5ui.msgbox.ref:25 b2c4748a6890439f9057ff136ef866de +msgid "button_toggle_flag.png" +msgstr "" + +#: ../../en/m5ui/msgbox.rst:343 b39b1e0ec1e442d0815e6426e0056280 +msgid "Set the corner radius of the msgbox button." +msgstr "设置msgbox按钮的圆角半径。" + +#: ../../en/m5ui/msgbox.rst:345 dc6bcbd7e66148eda6368d043aad5607 +msgid "The radius to set." +msgstr "要设置的半径。" + +#: ../../en/m5ui/msgbox.rst:351 4d392cb252014b349081c446b84bbc6f +msgid "|button_set_radius.png|" +msgstr "" + +#: ../../en/refs/m5ui.msgbox.ref:26 69cd2af0b1b64b25b2cf6ce6b9d9b6cc +msgid "button_set_radius.png" +msgstr "" + +#: ../../en/m5ui/msgbox.rst:361 b7fa38ed942040c3b33dfcf481fcad22 +msgid "Set a shadow for the label." +msgstr "为标签设置阴影。" + +#: ../../en/m5ui/msgbox.rst:363 8e7da18d822e4bc0ac7931b275acc2c3 +msgid "The color of the shadow in hexadecimal format or an integer." +msgstr "阴影的颜色,十六进制格式或整数。" + +#: ../../en/m5ui/msgbox.rst:364 a1099da4587a48d88cca6bad66e61cd8 +msgid "The opacity of the shadow (0-255)." +msgstr "阴影的不透明度(0-255)。" + +#: ../../en/m5ui/msgbox.rst:365 2a8cbed6196f4a3cb15c6f638c75495d +msgid "The alignment of the shadow relative to the label." +msgstr "阴影相对于标签的对齐方式。" + +#: ../../en/m5ui/msgbox.rst:366 9df8fcfe3379425082c4330fd7ceda31 +msgid "The horizontal offset of the shadow." +msgstr "阴影的水平偏移量。" + +#: ../../en/m5ui/msgbox.rst:367 1ce6bca14c70424d8f8ff069c7f31fbc +msgid "The vertical offset of the shadow." +msgstr "阴影的垂直偏移量。" + +#: ../../en/m5ui/msgbox.rst:372 6f336eaa93d845268a1dec38800e3db1 +msgid "|label_set_shadow.png|" +msgstr "" + +#: ../../en/refs/m5ui.msgbox.ref:27 d1348f12831b4e84b809aeba821e7f35 +msgid "label_set_shadow.png" +msgstr "" + +#: ../../en/m5ui/msgbox.rst:382 a6632e521a124990a6d4c81101963ebe +msgid "Remove the shadow from the label." +msgstr "移除标签的阴影。" + +#: ../../en/m5ui/msgbox.rst:386 de63b0ba157d4de2b95cd681e6126ec1 +msgid "|label_unset_shadow.png|" +msgstr "" + +#: ../../en/refs/m5ui.msgbox.ref:28 45c2e08dcc4e4046956f290eaf85ba17 +msgid "label_unset_shadow.png" +msgstr "" + +#: ../../en/m5ui/msgbox.rst:396 c3a2e0455f7e4bcb927d87f144905d7b +msgid "Get the text of the label." +msgstr "获取标签的文本。" + +#: ../../en/m5ui/msgbox.rst:398 c00696f37194401399aa02002a7f32d4 +msgid "The text of the label." +msgstr "标签的文本内容。" + +#: ../../en/m5ui/msgbox.rst 32c5a89519b14a469a30f71be7b27c1d +#: 7fa5a1c62453407e9f67a32c7d420c86 c562db8a0e3c4d67924c9f914ad1a5f2 +#: m5ui.msgbox.M5Msgbox.add_button m5ui.msgbox.M5Msgbox.add_text of +msgid "Return type" +msgstr "返回类型" + +#: ../../en/m5ui/msgbox.rst:403 120462a994a1467b8d9db25c16971362 +msgid "|button_get_text.png|" +msgstr "" + +#: ../../en/refs/m5ui.msgbox.ref:29 2541558cfc9f403eaa238387db763f95 +msgid "button_get_text.png" +msgstr "" + +#: ../../en/m5ui/msgbox.rst:405 b56ed59ccbf3471f888ec7a44961dace +msgid "|label_get_text.png|" +msgstr "" + +#: ../../en/refs/m5ui.msgbox.ref:30 941c5e7cafb642b986bbcb58be6bbcd2 +msgid "label_get_text.png" +msgstr "" + +#: ../../en/m5ui/msgbox.rst:417 75f9586c0bab41b7b7aeaa8548032b6d +msgid "" +"Toggle the state of the button. If the state is set, it is unset; if not " +"set, it is set." +msgstr "切换按钮的状态。如果该状态已设置则取消设置,未设置则添加设置。" + +#: ../../en/m5ui/msgbox.rst:419 ccbd951feb05454ba2a0ec48bea1c3ea +msgid "The state to toggle." +msgstr "要切换的状态。" + +#: ../../en/m5ui/msgbox.rst:424 1906a0273dcc426e9c672c18b0c0982d +msgid "|button_toggle_state.png|" +msgstr "" + +#: ../../en/refs/m5ui.msgbox.ref:31 bfbe058cf23d43e5a3e04329a55f904f +msgid "button_toggle_state.png" +msgstr "" + +#: ../../en/m5ui/msgbox.rst:434 15acf780e8f34f8b898de42efc87ac91 +msgid "" +"Add an event callback to the button. The callback will be called when the" +" specified event occurs." +msgstr "为按钮添加事件回调。当指定事件发生时将调用该回调函数。" + +#: ../../en/m5ui/msgbox.rst:436 b964809baad84c7780675a0f5f8a7e9d +msgid "The callback function to call." +msgstr "要调用的回调函数。" + +#: ../../en/m5ui/msgbox.rst:437 90fcea00fc7c4342b46c684dd0d211a0 +msgid "The event to listen for." +msgstr "要监听的事件。" + +#: ../../en/m5ui/msgbox.rst:438 3a4f7380ce3f4918a2d3958e0f3f4b06 +msgid "Optional user data to pass to the callback." +msgstr "可选的用户数据,将传递给回调函数。" + +#: ../../en/m5ui/msgbox.rst:443 3061b7e3dd3542949215fdf8f765871f +msgid "|button_event.png|" +msgstr "" + +#: ../../en/refs/m5ui.msgbox.ref:35 ad355bc6f0ed4ef5b0d07f34f41da5a0 +msgid "button_event.png" +msgstr "" + +#: e28174cb4367447ba89bbc6b8dcdb225 m5ui.msgbox.M5Msgbox.add_text:1 of +msgid "Add a text label to the msgbox." +msgstr "向msgbox添加文本标签。" + +#: a6f021657b0e40d591008a0214ca88ca m5ui.msgbox.M5Msgbox.add_text:3 of +msgid "The text to display." +msgstr "要显示的文本。" + +#: 5a1137e29d1946f7b334ac324acf024e d5b9da854ee84c7bbf96ab3b88b86ca8 +#: m5ui.msgbox.M5Msgbox.add_button:7 m5ui.msgbox.M5Msgbox.add_text:4 of +msgid "The text color in hexadecimal format." +msgstr "文本颜色(十六进制格式)。" + +#: 60017204fb96434e87aa0dd515398605 8f29483a94fa475fade5e0e3e574ebe1 +#: m5ui.msgbox.M5Msgbox.add_button:8 m5ui.msgbox.M5Msgbox.add_text:5 of +msgid "The text opacity (0-255)." +msgstr "文本不透明度(0-255)。" + +#: 25ddd74094744f41a37c59a2ef110e76 fcf806790cc7435b8b249ab667e14441 +#: m5ui.msgbox.M5Msgbox.add_button:5 m5ui.msgbox.M5Msgbox.add_text:6 of +msgid "The background color in hexadecimal format." +msgstr "背景颜色(十六进制格式)。" + +#: 456eb40ff53e4d239bdeff3f6595e87b ad6e2a5b4a2f4a8c80ae520b9d53670b +#: m5ui.msgbox.M5Msgbox.add_button:6 m5ui.msgbox.M5Msgbox.add_text:7 of +msgid "The background opacity (0-255)." +msgstr "背景不透明度(0-255)。" + +#: 47360c8e10834877a2fa4be62ffc416f m5ui.msgbox.M5Msgbox.add_text:8 of +msgid "The font to use for the text." +msgstr "用于文本的字体。" + +#: 2a551721b74c4280b1aff55a4126f091 m5ui.msgbox.M5Msgbox.add_text:10 of +msgid "The created label object." +msgstr "创建的标签对象。" + +#: 4dc557247ea44daaaf2add05089f1409 m5ui.msgbox.M5Msgbox.add_text:15 of +msgid "|add_text.png|" +msgstr "" + +#: ../../en/refs/m5ui.msgbox.ref:1 432528e330ab44cdad32c957920c8f52 +msgid "add_text.png" +msgstr "" + +#: f2c5fbbd65d04487a937b4e9abd9af95 m5ui.msgbox.M5Msgbox.add_text:17 of +msgid "|add_text2.png|" +msgstr "" + +#: ../../en/refs/m5ui.msgbox.ref:2 c341de6efec74c07ba4e953d06033354 +msgid "add_text2.png" +msgstr "" + +#: fc6dd6ec699b4de0a0981ee24d35102c m5ui.msgbox.M5Msgbox.add_button:1 of +msgid "Add a button to the msgbox." +msgstr "向msgbox添加按钮。" + +#: 236cf72ad71e4f2ba710ac36739b01be m5ui.msgbox.M5Msgbox.add_button:3 of +msgid "The icon to display on the button." +msgstr "按钮上要显示的图标。" + +#: 44f76786aa444c598a0df7d377470df3 m5ui.msgbox.M5Msgbox.add_button:4 of +msgid "The text to display on the button." +msgstr "按钮上要显示的文本。" + +#: 64a4a18b5d294dbcb7cacb46b94ad4ba m5ui.msgbox.M5Msgbox.add_button:9 of +msgid "The font to use for the button text." +msgstr "按钮文本所使用的字体。" + +#: 0a8fc32dcf0646799c48796cb0677118 m5ui.msgbox.M5Msgbox.add_button:10 of +msgid "The position of the button (\"header\" or \"footer\")." +msgstr "按钮的位置(“header” 或 “footer”)。" + +#: ce04d7bc474a4138972b10598c477073 m5ui.msgbox.M5Msgbox.add_button:12 of +msgid "The created button object." +msgstr "创建的按钮对象。" + +#: cdf7d987b76a4d34a3a6ef2b93e288f8 m5ui.msgbox.M5Msgbox.add_button:17 of +msgid "|add_button.png|" +msgstr "" + +#: ../../en/refs/m5ui.msgbox.ref:3 46a08c2815de4967b941174d4ce856bb +msgid "add_button.png" +msgstr "" + +#: bb2d71483e264bfc8acb4dc7705b67d7 m5ui.msgbox.M5Msgbox.add_button:19 of +msgid "|add_button2.png|" +msgstr "" + +#: ../../en/refs/m5ui.msgbox.ref:4 b25d6ac9d44d41d097304e7a05275cb0 +msgid "add_button2.png" +msgstr "" + +#~ msgid "points connect" +#~ msgstr "" + diff --git a/examples/m5ui/msgbox/msgbox_core2_example.m5f2 b/examples/m5ui/msgbox/msgbox_core2_example.m5f2 new file mode 100644 index 00000000..55eb623a --- /dev/null +++ b/examples/m5ui/msgbox/msgbox_core2_example.m5f2 @@ -0,0 +1 @@ +{"version":"V2.0","versionNumber":"V2.3.4","type":"core2","components":[{"name":"page0","type":"lvgl_page","layer":0,"screenId":"builtin","screenName":"","id":"nldXANNAKi5ql`E-","createTime":1756783560758,"backgroundColor":"#ffffff","isLVGL":true,"isSelected":true},{"name":"msgbox0","type":"lvgl_msgbox","layer":1,"screenId":"builtin","screenName":"","id":"t&wiwh@m9E-I`h!v","createTime":1756783565234,"x":0,"y":0,"width":320,"height":240,"title":"Message Box","labelList":[{"type":"label","name":"label_sfu","value":"This is label_sfu"}],"btnList":[{"type":"button","name":"btn_apply","value":"Apply","mode":"text","option":"footer"},{"type":"button","name":"btn_cancel","value":"Cancel","mode":"text","option":"footer"}],"isInit":true,"showCloseBtn":true,"pageId":"nldXANNAKi5ql`E-","isLVGL":true,"isSelected":false}],"resources":[{"hardware":["hardware_button","hardware_pin_button","imu","speaker","touch","mic","sdcard"]}],"units":[],"hats":[],"bases":[],"i2cs":[],"blockly":"truepage0truemsgbox0btn_applyCLICKEDmsgbox0label_sfuhello M5Hello 1100msgbox0btn_cancelCLICKEDmsgbox0btn_applyHIDDEN","screen":[{"simulationName":"Built-in","type":"builtin","width":320,"height":240,"scale":0.77,"screenName":"","blockId":"","screenColorType":0,"rotation":1,"id":"builtin","createTime":1756783560757}],"logicWhenNum":0,"customList":[]} \ No newline at end of file diff --git a/examples/m5ui/msgbox/msgbox_core2_example.py b/examples/m5ui/msgbox/msgbox_core2_example.py new file mode 100644 index 00000000..5329469b --- /dev/null +++ b/examples/m5ui/msgbox/msgbox_core2_example.py @@ -0,0 +1,86 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import os, sys, io +import M5 +from M5 import * +import m5ui +import lvgl as lv + + +page0 = None +msgbox0 = None +label_sfu = None +btn_apply = None +btn_cancel = None + + +import random + + +def btn_apply_clicked_event(event_struct): + global page0, msgbox0, label_sfu, btn_apply, btn_cancel + + label_sfu.set_text(str((str("Hello ") + str((random.randint(1, 100)))))) + + +def btn_cancel_clicked_event(event_struct): + global page0, msgbox0, label_sfu, btn_apply, btn_cancel + + btn_apply.toggle_flag(lv.obj.FLAG.HIDDEN) + + +def btn_apply_event_handler(event_struct): + global page0, msgbox0, label_sfu, btn_apply, btn_cancel + event = event_struct.code + if event == lv.EVENT.CLICKED and True: + btn_apply_clicked_event(event_struct) + return + + +def btn_cancel_event_handler(event_struct): + global page0, msgbox0, label_sfu, btn_apply, btn_cancel + event = event_struct.code + if event == lv.EVENT.CLICKED and True: + btn_cancel_clicked_event(event_struct) + return + + +def setup(): + global page0, msgbox0, label_sfu, btn_apply, btn_cancel + + M5.begin() + Widgets.setRotation(1) + m5ui.init() + page0 = m5ui.M5Page(bg_c=0xFFFFFF) + msgbox0 = m5ui.M5Msgbox(title="Message Box", x=0, y=0, w=320, h=240, parent=page0) + label_sfu = msgbox0.add_text("This is label_sfu") + btn_apply = msgbox0.add_button(text="Apply", option="footer") + btn_cancel = msgbox0.add_button(text="Cancel", option="footer") + msgbox0.add_close_button() + + btn_apply.add_event_cb(btn_apply_event_handler, lv.EVENT.ALL, None) + btn_cancel.add_event_cb(btn_cancel_event_handler, lv.EVENT.ALL, None) + + page0.screen_load() + + +def loop(): + global page0, msgbox0, label_sfu, btn_apply, btn_cancel + M5.update() + + +if __name__ == "__main__": + try: + setup() + while True: + loop() + except (Exception, KeyboardInterrupt) as e: + try: + m5ui.deinit() + from utility import print_error_msg + + print_error_msg(e) + except ImportError: + print("please update to latest firmware") diff --git a/m5stack/libs/m5ui/line.py b/m5stack/libs/m5ui/line.py index 9ac06e27..b6999544 100644 --- a/m5stack/libs/m5ui/line.py +++ b/m5stack/libs/m5ui/line.py @@ -19,7 +19,7 @@ class M5Line(lv.line): .. code-block:: python - from m5ui import M5Image + from m5ui import M5Line import lvgl as lv m5ui.init() diff --git a/m5stack/libs/m5ui/msgbox.py b/m5stack/libs/m5ui/msgbox.py index f3794620..681c2d91 100644 --- a/m5stack/libs/m5ui/msgbox.py +++ b/m5stack/libs/m5ui/msgbox.py @@ -8,6 +8,33 @@ class M5Msgbox(lv.msgbox): + """Create a msgbox object. + + :param str title: The title of the msgbox. + :param int x: The x-coordinate of the msgbox. + :param int y: The y-coordinate of the msgbox. + :param int w: The width of the msgbox. + :param int h: The height of the msgbox. + :param lv.obj parent: The parent object to attach the msgbox to. If not specified, the msgbox will be attached to the default screen. + + MicroPython Code Block: + + .. code-block:: python + + from m5ui import M5Msgbox + import lvgl as lv + + m5ui.init() + msgbox_0 = M5Msgbox( + title="msgbox", + x=0, + y=0, + w=320, + h=240, + parent=page0, + ) + """ + def __init__( self, title="", @@ -34,6 +61,37 @@ def add_text( bg_opa=255, font=lv.font_montserrat_14, ): + """Add a text label to the msgbox. + + :param str text: The text to display. + :param int text_c: The text color in hexadecimal format. + :param int text_opa: The text opacity (0-255). + :param int bg_c: The background color in hexadecimal format. + :param int bg_opa: The background opacity (0-255). + :param lv.font font: The font to use for the text. + + :return: The created label object. + :rtype: m5ui.M5Label + + UiFlow2 Code Block: + + |add_text.png| + + |add_text2.png| + + MicroPython Code Block: + + .. code-block:: python + + text0 = msgbox_0.add_text( + text="Hello World", + text_c=0x212121, + text_opa=255, + bg_c=0xFFFFFF, + bg_opa=255, + font=lv.font_montserrat_14, + ) + """ _label = m5ui.M5Label( text=text, text_c=text_c, @@ -57,6 +115,41 @@ def add_button( font=lv.font_montserrat_14, option="footer", ): + """Add a button to the msgbox. + + :param str icon: The icon to display on the button. + :param str text: The text to display on the button. + :param int bg_c: The background color in hexadecimal format. + :param int bg_opa: The background opacity (0-255). + :param int text_c: The text color in hexadecimal format. + :param int text_opa: The text opacity (0-255). + :param lv.font font: The font to use for the button text. + :param str option: The position of the button ("header" or "footer"). + + :return: The created button object. + :rtype: m5ui.M5Button + + UiFlow2 Code Block: + + |add_button.png| + + |add_button2.png| + + MicroPython Code Block: + + .. code-block:: python + + button0 = msgbox_0.add_button( + icon=None, + text="OK", + bg_c=0x2196F3, + bg_opa=255, + text_c=0xFFFFFF, + text_opa=255, + font=lv.font_montserrat_14, + option="footer", + ) + """ _parent = None _w = 0 _h = 0 From 2e2340a61beb76c1ea0f1596b03ea633145d856a Mon Sep 17 00:00:00 2001 From: tinyu Date: Fri, 5 Sep 2025 16:57:50 +0800 Subject: [PATCH 247/322] lib/unit: Fixed the abnormal reading of weight_i2c. Signed-off-by: tinyu --- m5stack/libs/unit/ultrasonic_i2c.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/m5stack/libs/unit/ultrasonic_i2c.py b/m5stack/libs/unit/ultrasonic_i2c.py index 61ab51b5..4dbafe35 100644 --- a/m5stack/libs/unit/ultrasonic_i2c.py +++ b/m5stack/libs/unit/ultrasonic_i2c.py @@ -26,7 +26,7 @@ def get_target_distance(self, mode=1): data = self.i2c.readfrom(self.i2c_addr, 3) self._distance = ((data[0] << 16) | (data[1] << 8) | data[2]) / 1000 except OSError: - pass + return -1 if mode == 2: self._distance = self._distance / 10 return round(self._distance, 2) From 22a5da856b373639f88384047a1676405b240f67 Mon Sep 17 00:00:00 2001 From: "tinyu.zhao@gmail.com" Date: Wed, 27 Aug 2025 16:35:01 +0800 Subject: [PATCH 248/322] lib/m5ui: Limit switch maximum size. Signed-off-by: tinyu.zhao@gmail.com --- m5stack/libs/m5ui/switch.py | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/m5stack/libs/m5ui/switch.py b/m5stack/libs/m5ui/switch.py index f65e05c1..3ce3ac45 100644 --- a/m5stack/libs/m5ui/switch.py +++ b/m5stack/libs/m5ui/switch.py @@ -4,6 +4,7 @@ from m5ui.base import M5Base import lvgl as lv +import M5 class M5Switch(lv.switch): @@ -85,19 +86,20 @@ def set_direction(self, direction): self.set_size(min(cur_w, cur_h), max(cur_w, cur_h)) def set_size(self, w, h): - super().set_size(w, h) - self.width = w - self.height = h + if w < 0 or h < 0: + raise ValueError("Width and height must be non-negative") - def set_style_radius(self, radius: int, part: int) -> None: - if radius < 0: - raise ValueError("Radius must be a non-negative integer.") - super().set_style_radius(radius, part) + _width = M5.Display.width() + _height = M5.Display.height() - def set_style_radius(self, radius: int, part: int) -> None: - if radius < 0: - raise ValueError("Radius must be a non-negative integer.") - super().set_style_radius(radius, part) + if w < _width and h < _height: + super().set_size(w, h) + else: + print("Warning: width or height exceed screen size, auto set to screen size") + super().set_size(_width, _height) + + self.width = w + self.height = h def set_style_radius(self, radius: int, part: int) -> None: if radius < 0: From b971ad8c8ef03a4202ffa38ece0acdb1507de1a9 Mon Sep 17 00:00:00 2001 From: lbuque <1102390310@qq.com> Date: Mon, 8 Sep 2025 14:20:57 +0800 Subject: [PATCH 249/322] components/M5Unified/mpy_lvgl.txt: Modify touch value reading logic. Signed-off-by: lbuque <1102390310@qq.com> --- m5stack/components/M5Unified/mpy_lvgl.txt | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/m5stack/components/M5Unified/mpy_lvgl.txt b/m5stack/components/M5Unified/mpy_lvgl.txt index 1d51e307..f2c49c4a 100644 --- a/m5stack/components/M5Unified/mpy_lvgl.txt +++ b/m5stack/components/M5Unified/mpy_lvgl.txt @@ -190,15 +190,15 @@ bool gfx_lvgl_touch_read(lv_disp_t *indev_drv, lv_indev_data_t *data) { M5.update(); - if (!M5.Touch.getCount()) { - data->point = (lv_point_t) { 0, 0 }; + if (M5.Touch.getCount()) { + auto tp = M5.Touch.getDetail(0); + data->point.x = tp.x; + data->point.y = tp.y; + data->state = LV_INDEV_STATE_PRESSED; + } else { data->state = LV_INDEV_STATE_RELEASED; - return false; } - auto tp = M5.Touch.getDetail(0); - data->point = (lv_point_t) { tp.x, tp.y }; - data->state = LV_INDEV_STATE_PRESSED; return true; } From ff73c85c2b0f7bc3af7cf05d907766874c1a10b5 Mon Sep 17 00:00:00 2001 From: lbuque <1102390310@qq.com> Date: Mon, 8 Sep 2025 17:15:13 +0800 Subject: [PATCH 250/322] tools/ci.sh: Add build of M5STACK_CardputerADV. Signed-off-by: lbuque <1102390310@qq.com> --- .github/workflows/build-release.yml | 1 + .github/workflows/nightly-build.yml | 1 + .github/workflows/ports_m5stack.yml | 1 + tools/ci.sh | 1 + 4 files changed, 4 insertions(+) diff --git a/.github/workflows/build-release.yml b/.github/workflows/build-release.yml index f1258f9c..ba689f7b 100644 --- a/.github/workflows/build-release.yml +++ b/.github/workflows/build-release.yml @@ -71,6 +71,7 @@ jobs: - M5STACK_Basic_4MB - M5STACK_Capsule - M5STACK_Cardputer + - M5STACK_CardputerADV - M5STACK_Core2 - M5STACK_CoreInk - M5STACK_CoreS3 diff --git a/.github/workflows/nightly-build.yml b/.github/workflows/nightly-build.yml index 8b5699ee..d780b191 100644 --- a/.github/workflows/nightly-build.yml +++ b/.github/workflows/nightly-build.yml @@ -69,6 +69,7 @@ jobs: - M5STACK_Basic_4MB - M5STACK_Capsule - M5STACK_Cardputer + - M5STACK_CardputerADV - M5STACK_Core2 - M5STACK_CoreInk - M5STACK_CoreS3 diff --git a/.github/workflows/ports_m5stack.yml b/.github/workflows/ports_m5stack.yml index bfe9f3b2..be3502cd 100644 --- a/.github/workflows/ports_m5stack.yml +++ b/.github/workflows/ports_m5stack.yml @@ -74,6 +74,7 @@ jobs: - M5STACK_Basic_4MB - M5STACK_Capsule - M5STACK_Cardputer + - M5STACK_CardputerADV - M5STACK_Core2 - M5STACK_CoreInk - M5STACK_CoreS3 diff --git a/tools/ci.sh b/tools/ci.sh index b7840687..b42ca9a1 100644 --- a/tools/ci.sh +++ b/tools/ci.sh @@ -344,6 +344,7 @@ function ci_esp32_nightly_build { make ${MAKEOPTS} -C m5stack BOARD=M5STACK_Basic_4MB pack_all make ${MAKEOPTS} -C m5stack BOARD=M5STACK_Capsule pack_all make ${MAKEOPTS} -C m5stack BOARD=M5STACK_Cardputer pack_all + make ${MAKEOPTS} -C m5stack BOARD=M5STACK_CardputerADV pack_all make ${MAKEOPTS} -C m5stack BOARD=M5STACK_Core2 pack_all make ${MAKEOPTS} -C m5stack BOARD=M5STACK_CoreInk pack_all make ${MAKEOPTS} -C m5stack BOARD=M5STACK_CoreS3 pack_all From a069909284ac5c6245080713b057fe17c2fe7b70 Mon Sep 17 00:00:00 2001 From: lbuque <1102390310@qq.com> Date: Mon, 8 Sep 2025 17:17:18 +0800 Subject: [PATCH 251/322] version.text: Update to V2.3.5. Signed-off-by: lbuque <1102390310@qq.com> --- m5stack/version.txt | 2 +- third-party/version.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/m5stack/version.txt b/m5stack/version.txt index 82dd2275..e272c87b 100644 --- a/m5stack/version.txt +++ b/m5stack/version.txt @@ -1 +1 @@ -V2.3.4 \ No newline at end of file +V2.3.5 \ No newline at end of file diff --git a/third-party/version.txt b/third-party/version.txt index 82dd2275..e272c87b 100644 --- a/third-party/version.txt +++ b/third-party/version.txt @@ -1 +1 @@ -V2.3.4 \ No newline at end of file +V2.3.5 \ No newline at end of file From 3250101a84923b4131d6df9bc5cde66411f71723 Mon Sep 17 00:00:00 2001 From: tinyu Date: Tue, 9 Sep 2025 12:06:50 +0800 Subject: [PATCH 252/322] lib/hardware: Add SHT30 support for Paper. Signed-off-by: tinyu --- m5stack/libs/driver/sht30.py | 7 +++++++ m5stack/libs/hardware/__init__.py | 1 + m5stack/libs/hardware/manifest.py | 1 + m5stack/libs/hardware/sht30.py | 12 ++++++++++++ 4 files changed, 21 insertions(+) create mode 100644 m5stack/libs/hardware/sht30.py diff --git a/m5stack/libs/driver/sht30.py b/m5stack/libs/driver/sht30.py index c0ac620e..cd8f9142 100644 --- a/m5stack/libs/driver/sht30.py +++ b/m5stack/libs/driver/sht30.py @@ -42,6 +42,7 @@ def __init__(self, i2c=None, delta_temp=0, delta_hum=0, i2c_address=DEFAULT_I2C_ raise ValueError("An I2C object is required.") self.i2c = i2c self.i2c_addr = i2c_address + self.is_present() self.set_delta(delta_temp, delta_hum) time.sleep_ms(50) @@ -158,6 +159,12 @@ def measure_int(self, raw=False): h_dec = (aux % 0xFFFF * 100) // 0xFFFF return t_int, t_dec, h_int, h_dec + def get_temperature(self): + return round(self.measure()[0], 2) + + def get_humidity(self): + return round(self.measure()[1], 2) + class SHT30Error(Exception): """ diff --git a/m5stack/libs/hardware/__init__.py b/m5stack/libs/hardware/__init__.py index 0ac524ff..dfcfec36 100644 --- a/m5stack/libs/hardware/__init__.py +++ b/m5stack/libs/hardware/__init__.py @@ -18,6 +18,7 @@ "SCD40": "scd40", "SDCard": "sdcard", "SEN55": "sen55", + "SHT30": "sht30", } diff --git a/m5stack/libs/hardware/manifest.py b/m5stack/libs/hardware/manifest.py index 4ecc3878..1a756812 100644 --- a/m5stack/libs/hardware/manifest.py +++ b/m5stack/libs/hardware/manifest.py @@ -20,6 +20,7 @@ "scd40.py", "sdcard.py", "sen55.py", + "sht30.py", ), base_path="..", # opt=0, diff --git a/m5stack/libs/hardware/sht30.py b/m5stack/libs/hardware/sht30.py new file mode 100644 index 00000000..73d0ab69 --- /dev/null +++ b/m5stack/libs/hardware/sht30.py @@ -0,0 +1,12 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +from driver.sht30 import SHT30 as sht30 +from machine import I2C, Pin + + +class SHT30(sht30): + def __init__(self): + in_i2c = I2C(1, scl=Pin(22), sda=Pin(21)) + super().__init__(i2c=in_i2c) From 007e50816616ed7f2ae6cf884e4ff4a9eac6222a Mon Sep 17 00:00:00 2001 From: tinyu Date: Fri, 12 Sep 2025 11:16:36 +0800 Subject: [PATCH 253/322] docs: Add hardware.sht30 docs and examples. Signed-off-by: tinyu --- docs/en/hardware/index.rst | 1 + docs/en/hardware/sht30.rst | 58 +++++++ docs/en/refs/hardware.sht30.ref | 14 ++ .../zh_CN/LC_MESSAGES/hardware/sht30.po | 155 ++++++++++++++++++ .../hardware/sht30/paper_sht30_example.m5f2 | 1 + .../hardware/sht30/paper_sht30_example.py | 41 +++++ m5stack/libs/driver/sht30.py | 113 ++++++++----- 7 files changed, 341 insertions(+), 42 deletions(-) create mode 100644 docs/en/hardware/sht30.rst create mode 100644 docs/en/refs/hardware.sht30.ref create mode 100644 docs/locales/zh_CN/LC_MESSAGES/hardware/sht30.po create mode 100644 examples/hardware/sht30/paper_sht30_example.m5f2 create mode 100644 examples/hardware/sht30/paper_sht30_example.py diff --git a/docs/en/hardware/index.rst b/docs/en/hardware/index.rst index ff9eb240..9a2a5eeb 100644 --- a/docs/en/hardware/index.rst +++ b/docs/en/hardware/index.rst @@ -18,6 +18,7 @@ Hardware rotary.rst scd40.rst sen55.rst + sht30.rst speaker.rst uart.rst wdt.rst diff --git a/docs/en/hardware/sht30.rst b/docs/en/hardware/sht30.rst new file mode 100644 index 00000000..cf5d1042 --- /dev/null +++ b/docs/en/hardware/sht30.rst @@ -0,0 +1,58 @@ +SHT30 +===== + +.. include:: ../refs/hardware.sht30.ref + +SHT30 is a sensor that can be used to measure temperature and humidity. + +UiFlow2 Example +--------------- + +get temperature and humidity +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Open the |paper_sht30_example.m5f2| project in UiFlow2. + +This example reads the temperature and humidity from the SHT30 sensor. + +UiFlow2 Code Block: + + |example.png| + +Example output: + + None + + +MicroPython Example +------------------- + +get temperature and humidity +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +This example reads the temperature and humidity from the SHT30 sensor. + +MicroPython Code Block: + + .. literalinclude:: ../../../examples/hardware/sht30/paper_sht30_example.py + :language: python + :linenos: + +Example output: + + None + + +**API** +------- + +SHT30 +^^^^^^^^ + +.. autoclass:: hardware.sht30.SHT30 + :members: + :member-order: bysource + +.. autoclass:: driver.sht30.SHT30 + :members: + :member-order: bysource \ No newline at end of file diff --git a/docs/en/refs/hardware.sht30.ref b/docs/en/refs/hardware.sht30.ref new file mode 100644 index 00000000..d2dc6738 --- /dev/null +++ b/docs/en/refs/hardware.sht30.ref @@ -0,0 +1,14 @@ + +.. |init.png| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/sht30/init.png +.. |get_humidity.png| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/sht30/get_humidity.png +.. |get_temperature.png| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/sht30/get_temperature.png +.. |example.png| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/sht30/example.png + +.. |paper_sht30_example.m5f2| raw:: html + + + paper_sht30_example.m5f2 + diff --git a/docs/locales/zh_CN/LC_MESSAGES/hardware/sht30.po b/docs/locales/zh_CN/LC_MESSAGES/hardware/sht30.po new file mode 100644 index 00000000..f78aeb30 --- /dev/null +++ b/docs/locales/zh_CN/LC_MESSAGES/hardware/sht30.po @@ -0,0 +1,155 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2016 - 2025 M5Stack Technology Co., Ltd +# This file is distributed under the same license as the UIFlow2 Programming +# Guide package. +# FIRST AUTHOR , 2025. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: UIFlow2 Programming Guide \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-09-12 11:12+0800\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: zh_CN\n" +"Language-Team: zh_CN \n" +"Plural-Forms: nplurals=1; plural=0;\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.16.0\n" + +#: ../../en/hardware/sht30.rst:2 1a658bcbc53c4897b6fa5cb29986a6d9 +msgid "SHT30" +msgstr "SHT30" + +#: ../../en/hardware/sht30.rst:6 2d3a8b048e1341859e5950c76736b753 +msgid "SHT30 is a sensor that can be used to measure temperature and humidity." +msgstr "SHT30 是一款用于测量温度和湿度的传感器。" + +#: ../../en/hardware/sht30.rst:9 64bc0199440345a1b186e93853110609 +msgid "UiFlow2 Example" +msgstr "UiFlow2 示例" + +#: ../../en/hardware/sht30.rst:12 ../../en/hardware/sht30.rst:31 +#: 048ff22e69fe457e982e85388d7e2084 0b8693284b8b4f989c807a24587c1a1a +msgid "get temperature and humidity" +msgstr "获取温度和湿度" + +#: ../../en/hardware/sht30.rst:14 d376db4a1f5742b181af75a303567e34 +msgid "Open the |paper_sht30_example.m5f2| project in UiFlow2." +msgstr "在 UiFlow2 中打开 |paper_sht30_example.m5f2| 项目。" + +#: ../../en/hardware/sht30.rst:16 ../../en/hardware/sht30.rst:33 +#: d052ec4c3e0e4c798346b6129c388e4e dfc0221dde784a918c8bbef56ef73140 +msgid "This example reads the temperature and humidity from the SHT30 sensor." +msgstr "本示例从 SHT30 传感器读取温度和湿度。" + +#: ../../en/hardware/sht30.rst:18 16d7b5912af745d890e31019ee44041f +#: 18c744c833d74a38b82d5a27f5cdb2cf 9507e089ac6743afb4af9c272e917094 +#: d7c94b089b46411190443c1d813951ff driver.sht30.SHT30:8 +#: driver.sht30.SHT30.get_humidity:3 driver.sht30.SHT30.get_temperature:3 of +msgid "UiFlow2 Code Block:" +msgstr "UiFlow2 代码块:" + +#: ../../en/hardware/sht30.rst:20 71051090651d423ebc44c7fb6836600d +msgid "|example.png|" +msgstr "" + +#: ../../en/refs/hardware.sht30.ref:5 6f254916dccd4c3c94c4772dc7bab3b7 +msgid "example.png" +msgstr "" + +#: ../../en/hardware/sht30.rst:22 ../../en/hardware/sht30.rst:41 +#: 1c3e1314595b4ff2b195ce0ec6d25efa fe6710e6217948b28fcb9144bf3fc0c7 +msgid "Example output:" +msgstr "示例输出:" + +#: ../../en/hardware/sht30.rst:24 ../../en/hardware/sht30.rst:43 +#: 8267aa325283457a9e85d976ec7ecf6f cf9f3d7ed1784fc198cfc8de685b6bf8 +msgid "None" +msgstr "" + +#: ../../en/hardware/sht30.rst:28 bf1c9d423b154111b63cc2e2f57cb245 +msgid "MicroPython Example" +msgstr "MicroPython 示例" + +#: ../../en/hardware/sht30.rst:35 1b14ba704c894b228597a11635c5393b +#: 895499913f3841cbb62ca1b3c1d89fd2 9ddb8a19ed4940d19a2eda9b97ee905a +#: driver.sht30.SHT30:12 driver.sht30.SHT30.get_humidity:7 +#: driver.sht30.SHT30.get_temperature:7 eb5b76f5ad34419c93b12db063217503 of +msgid "MicroPython Code Block:" +msgstr "MicroPython 代码块:" + +#: ../../en/hardware/sht30.rst:47 7b62471d405f44f28106b5b843e1b63a +msgid "**API**" +msgstr "" + +#: ../../en/hardware/sht30.rst:50 5bfca301ce0943b8be4f5658d7fd3aeb +msgid "SHT30" +msgstr "SHT30" + +#: d88349f9550a47f2b30d126f15e9164c hardware.sht30.SHT30:1 of +msgid "Bases: :py:class:`~driver.sht30.SHT30`" +msgstr "" + +#: 07fb9ec4bfd74e2987d2be5f70183ff5 driver.sht30.SHT30:1 of +msgid "Bases: :py:class:`object`" +msgstr "" + +#: c426a0803192475abf5b8e9b51501202 driver.sht30.SHT30:1 of +msgid "Create a SHT30 object." +msgstr "创建一个 SHT30 对象。" + +#: ../../en/hardware/sht30.rst edbb8047523e4fbb8353569303c4859e +msgid "Parameters" +msgstr "参数" + +#: 6e2766960697430eae4f1222de3f57a2 driver.sht30.SHT30:3 of +msgid "The I2C bus object." +msgstr "I2C 总线对象。" + +#: 92cddc7bfa88454c99eedb5ee5a5ed65 driver.sht30.SHT30:4 of +msgid "The temperature delta to apply to measurements." +msgstr "用于测量的温度修正值。" + +#: driver.sht30.SHT30:5 e69b1c8b5e2d424c99461f610f8461c2 of +msgid "The humidity delta to apply to measurements." +msgstr "用于测量的湿度修正值。" + +#: 241f3621f6da4367a6c7f9658f0a5fff driver.sht30.SHT30:6 of +msgid "The I2C address of the sensor." +msgstr "传感器的 I2C 地址。" + +#: dd64f46f32fc4ecd92ad01ce7a58e0c6 driver.sht30.SHT30:10 of +msgid "|init.png|" +msgstr "" + +#: ../../en/refs/hardware.sht30.ref:2 165b3ef7681c4fd0ae2f4604859c41e8 +msgid "init.png" +msgstr "" + +#: 8446b18821584ebabd34d0acf1e368c7 driver.sht30.SHT30.get_temperature:1 of +msgid "Get the temperature in Celsius." +msgstr "获取摄氏温度。" + +#: 9f7cab196d834b19a39e7b568981af11 driver.sht30.SHT30.get_temperature:5 of +msgid "|get_temperature.png|" +msgstr "" + +#: ../../en/refs/hardware.sht30.ref:4 fb2d147c10ac4fc98479fc847ffda065 +msgid "get_temperature.png" +msgstr "" + +#: c0201b1f38654ee5bd67ac21a4e2d917 driver.sht30.SHT30.get_humidity:1 of +msgid "Get the relative humidity in percent." +msgstr "获取相对湿度(百分比)。" + +#: ba20f5e9d7c442849b4f4ec6046d9e50 driver.sht30.SHT30.get_humidity:5 of +msgid "|get_humidity.png|" +msgstr "" + +#: ../../en/refs/hardware.sht30.ref:3 2b9a568f9db44e759f8127055a5a3a92 +msgid "get_humidity.png" +msgstr "" \ No newline at end of file diff --git a/examples/hardware/sht30/paper_sht30_example.m5f2 b/examples/hardware/sht30/paper_sht30_example.m5f2 new file mode 100644 index 00000000..4289b807 --- /dev/null +++ b/examples/hardware/sht30/paper_sht30_example.m5f2 @@ -0,0 +1 @@ +{"version":"V2.0","versionNumber":"V2.3.5","type":"paper","components":[{"name":"screen","type":"screen","layer":0,"screenId":"builtin","screenName":"","id":"__paper_screen","createTime":1757640340959,"x":0,"y":0,"width":540,"height":960,"backgroundColor":"#eeeeee","size":0,"isSelected":true}],"resources":[{"hardware":["hardware_button","hardware_pin_button","touch","hardware_sht30","sdcard"]}],"units":[],"hats":[],"bases":[],"i2cs":[],"blockly":"truetruehello M5Humidity:Temperature:","screen":[{"simulationName":"Built-in","type":"builtin","width":540,"height":960,"scale":0.28,"screenName":"","blockId":"","screenColorType":2,"id":"builtin","createTime":1757640340957}],"logicWhenNum":0,"customList":[]} \ No newline at end of file diff --git a/examples/hardware/sht30/paper_sht30_example.py b/examples/hardware/sht30/paper_sht30_example.py new file mode 100644 index 00000000..92c7144f --- /dev/null +++ b/examples/hardware/sht30/paper_sht30_example.py @@ -0,0 +1,41 @@ +# SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import os, sys, io +import M5 +from M5 import * +from hardware import SHT30 + + +sht30 = None + + +def setup(): + global sht30 + + M5.begin() + Widgets.fillScreen(0xEEEEEE) + + sht30 = SHT30() + + +def loop(): + global sht30 + M5.update() + print((str("Humidity:") + str((sht30.get_humidity())))) + print((str("Temperature:") + str((sht30.get_temperature())))) + + +if __name__ == "__main__": + try: + setup() + while True: + loop() + except (Exception, KeyboardInterrupt) as e: + try: + from utility import print_error_msg + + print_error_msg(e) + except ImportError: + print("please update to latest firmware") diff --git a/m5stack/libs/driver/sht30.py b/m5stack/libs/driver/sht30.py index cd8f9142..fc3ba80e 100644 --- a/m5stack/libs/driver/sht30.py +++ b/m5stack/libs/driver/sht30.py @@ -9,16 +9,37 @@ class SHT30: - """ - SHT30 sensor driver in pure python based on I2C bus + """Create a SHT30 object. + + :param I2C i2c: The I2C bus object. + :param int delta_temp: The temperature delta to apply to measurements. + :param int delta_hum: The humidity delta to apply to measurements. + :param int i2c_address: The I2C address of the sensor. + + UiFlow2 Code Block: + + |init.png| + + MicroPython Code Block: - References: - * https://www.sensirion.com/fileadmin/user_upload/customers/sensirion/Dokumente/2_Humidity_Sensors/Sensirion_Humidity_Sensors_SHT3x_Datasheet_digital.pdf # NOQA - * https://www.wemos.cc/sites/default/files/2016-11/SHT30-DIS_datasheet.pdf - * https://github.com/wemos/WEMOS_SHT3x_Arduino_Library - * https://www.sensirion.com/fileadmin/user_upload/customers/sensirion/Dokumente/11_Sample_Codes_Software/Humidity_Sensors/Sensirion_Humidity_Sensors_SHT3x_Sample_Code_V2.pdf + .. code-block:: python + + from hardware import Pin + from hardware import I2C + from hardware import SHT30 + + # Paper + i2c0 = I2C(0, scl=Pin(22), sda=Pin(21), freq=100000) + sht30 = SHT30(i2c0) """ + # SHT30 sensor driver in pure python based on I2C bus + + # References: + # * https://www.sensirion.com/fileadmin/user_upload/customers/sensirion/Dokumente/2_Humidity_Sensors/Sensirion_Humidity_Sensors_SHT3x_Datasheet_digital.pdf # NOQA + # * https://www.wemos.cc/sites/default/files/2016-11/SHT30-DIS_datasheet.pdf + # * https://github.com/wemos/WEMOS_SHT3x_Arduino_Library + # * https://www.sensirion.com/fileadmin/user_upload/customers/sensirion/Dokumente/11_Sample_Codes_Software/Humidity_Sensors/Sensirion_Humidity_Sensors_SHT3x_Sample_Code_V2.pdf POLYNOMIAL = 0x131 # P(x) = x^8 + x^5 + x^4 + 1 = 100110001 ALERT_PENDING_MASK = 0x8000 # 15 @@ -47,16 +68,12 @@ def __init__(self, i2c=None, delta_temp=0, delta_hum=0, i2c_address=DEFAULT_I2C_ time.sleep_ms(50) def is_present(self): - """ - Return true if the sensor is correctly conneced, False otherwise - """ + # Return true if the sensor is correctly connected, False otherwise return self.i2c_addr in self.i2c.scan() def set_delta(self, delta_temp=0, delta_hum=0): - """ - Apply a delta value on the future measurements of temperature and/or humidity - The units are Celsius for temperature and percent for humidity (can be negative values) - """ + # Apply a delta value on the future measurements of temperature and/or humidity + # The units are Celsius for temperature and percent for humidity (can be negative values) self.delta_temp = delta_temp self.delta_hum = delta_hum @@ -75,10 +92,8 @@ def _check_crc(self, data): return crc_to_check == crc def send_cmd(self, cmd_request, response_size=6, read_delay_ms=100): - """ - Send a command to the sensor and read (optionally) the response - The responsed data is validated by CRC - """ + # Send a command to the sensor and read (optionally) the response + # The responsed data is validated by CRC try: self.i2c.writeto(self.i2c_addr, cmd_request) if not response_size: @@ -98,22 +113,16 @@ def send_cmd(self, cmd_request, response_size=6, read_delay_ms=100): raise ex def clear_status(self): - """ - Clear the status register - """ + # Clear the status register return self.send_cmd(SHT30.CLEAR_STATUS_CMD, None) def reset(self): - """ - Send a soft-reset to the sensor - """ + # Send a soft-reset to the sensor return self.send_cmd(SHT30.RESET_CMD, None) def status(self, raw=False): - """ - Get the sensor status register. - It returns a int value or the bytearray(3) if raw==True - """ + # Get the sensor status register. + # It returns a int value or the bytearray(3) if raw==True data = self.send_cmd(SHT30.STATUS_CMD, 3, read_delay_ms=20) if raw: @@ -123,12 +132,10 @@ def status(self, raw=False): return status_register def measure(self, raw=False): - """ - If raw==True returns a bytearrya(6) with sensor direct measurement otherwise - It gets the temperature (T) and humidity (RH) measurement and return them. + # If raw==True returns a bytearrya(6) with sensor direct measurement otherwise + # It gets the temperature (T) and humidity (RH) measurement and return them. - The units are Celsius and percent - """ + # The units are Celsius and percent data = self.send_cmd(SHT30.MEASURE_CMD, 6) if raw: @@ -139,15 +146,13 @@ def measure(self, raw=False): return t_celsius, rh def measure_int(self, raw=False): - """ - Get the temperature (T) and humidity (RH) measurement using integers. - If raw==True returns a bytearrya(6) with sensor direct measurement otherwise - It returns a tuple with 4 values: T integer, T decimal, H integer, H decimal - For instance to return T=24.0512 and RH= 34.662 This method will return - (24, 5, 34, 66) Only 2 decimal digits are returned, .05 becomes 5 - Delta values are not applied in this method - The units are Celsius and percent. - """ + # Get the temperature (T) and humidity (RH) measurement using integers. + # If raw==True returns a bytearrya(6) with sensor direct measurement otherwise + # It returns a tuple with 4 values: T integer, T decimal, H integer, H decimal + # For instance to return T=24.0512 and RH= 34.662 This method will return + # (24, 5, 34, 66) Only 2 decimal digits are returned, .05 becomes 5 + # Delta values are not applied in this method + # The units are Celsius and percent. data = self.send_cmd(SHT30.MEASURE_CMD, 6) if raw: return data @@ -160,9 +165,33 @@ def measure_int(self, raw=False): return t_int, t_dec, h_int, h_dec def get_temperature(self): + """Get the temperature in Celsius. + + UiFlow2 Code Block: + + |get_temperature.png| + + MicroPython Code Block: + + .. code-block:: python + + sht30.get_temperature() + """ return round(self.measure()[0], 2) def get_humidity(self): + """Get the relative humidity in percent. + + UiFlow2 Code Block: + + |get_humidity.png| + + MicroPython Code Block: + + .. code-block:: python + + sht30.get_humidity() + """ return round(self.measure()[1], 2) From bb694a9a37263b4ff9761d607e51ce7808c520f7 Mon Sep 17 00:00:00 2001 From: tinyu Date: Fri, 12 Sep 2025 09:20:46 +0800 Subject: [PATCH 254/322] libs/unit: Fix image position after scaling. Signed-off-by: tinyu --- m5stack/libs/m5ui/image.py | 1 + 1 file changed, 1 insertion(+) diff --git a/m5stack/libs/m5ui/image.py b/m5stack/libs/m5ui/image.py index e0a301c3..3bdcfa09 100644 --- a/m5stack/libs/m5ui/image.py +++ b/m5stack/libs/m5ui/image.py @@ -46,6 +46,7 @@ def __init__( self.set_height(lv.SIZE_CONTENT) self.set_rotation(rotation) self.set_scale(scale_x, scale_y) + self.set_pivot(0, 0) def set_image(self, path): """Set the image to be displayed. From c6bcb71e2fa5fb4f34a79f6233a1fde016515014 Mon Sep 17 00:00:00 2001 From: tinyu Date: Tue, 9 Sep 2025 11:03:53 +0800 Subject: [PATCH 255/322] lib/m5ui: Add LVGL Spinner widget. Signed-off-by: tinyu --- m5stack/libs/m5ui/__init__.py | 1 + m5stack/libs/m5ui/manifest.py | 1 + m5stack/libs/m5ui/spinner.py | 81 +++++++++++++++++++++++++++++++++++ 3 files changed, 83 insertions(+) create mode 100644 m5stack/libs/m5ui/spinner.py diff --git a/m5stack/libs/m5ui/__init__.py b/m5stack/libs/m5ui/__init__.py index c3e15d6a..5fd71d73 100644 --- a/m5stack/libs/m5ui/__init__.py +++ b/m5stack/libs/m5ui/__init__.py @@ -24,6 +24,7 @@ "M5Scale": "scale", "M5Slider": "slider", "M5Spinbox": "spinbox", + "M5Spinner": "spinner", "M5Switch": "switch", "M5TextArea": "textarea", "M5Win": "win", diff --git a/m5stack/libs/m5ui/manifest.py b/m5stack/libs/m5ui/manifest.py index 59bfb47a..a7d60ea4 100644 --- a/m5stack/libs/m5ui/manifest.py +++ b/m5stack/libs/m5ui/manifest.py @@ -27,6 +27,7 @@ "scale.py", "slider.py", "spinbox.py", + "spinner.py", "switch.py", "textarea.py", "win.py", diff --git a/m5stack/libs/m5ui/spinner.py b/m5stack/libs/m5ui/spinner.py new file mode 100644 index 00000000..3c6d8666 --- /dev/null +++ b/m5stack/libs/m5ui/spinner.py @@ -0,0 +1,81 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +from m5ui.base import M5Base +import lvgl as lv + + +class M5Spinner(lv.spinner): + """Create a spinner object. + + :param int x: The x position of the spinner. + :param int y: The y position of the spinner. + :param int w: The width of the spinner. + :param int h: The height of the spinner. + :param int anim_t: The animation time in milliseconds. + :param int angle: The angle of the spinner in degrees. + :param lv.obj parent: The parent object to attach the spinner to. If not specified, the spinner will be attached to the default screen. + + UiFlow2 Code Block: + + None + + MicroPython Code Block: + + .. code-block:: python + + from m5ui import M5Spinner + import lvgl as lv + + m5ui.init() + spinner_0 = M5Spinner(x=120, y=80, w=60, h=30, anim_t=1000, angle=180, parent=page0) + """ + + def __init__( + self, + x=0, + y=0, + w=0, + h=0, + anim_t=10000, + angle=180, + parent=None, + ): + if parent is None: + parent = lv.screen_active() + super().__init__(parent) + self.set_pos(x, y) + self.set_size(w, h) + self.set_anim_params(anim_t, angle) + + def set_spinner_color(self, color, opa: int, part: int): + """Set the color of the spinner. + + :param int color: The color of the spinner in hexadecimal format. + + UiFlow2 Code Block: + + |set_spinner_color.png| + + MicroPython Code Block: + + .. code-block:: python + + label_0.set_spinner_color(0x2196F3, lv.PART.MAIN | lv.STATE.DEFAULT) + """ + if isinstance(color, int): + color = lv.color_hex(color) + if part == lv.PART.KNOB | lv.STATE.DEFAULT: + self.set_bg_color(color, opa, lv.PART.KNOB | lv.STATE.DEFAULT) + else: + self.set_style_arc_color(color, part) + self.set_style_arc_opa(opa, part) + + def __getattr__(self, name): + if hasattr(M5Base, name): + method = getattr(M5Base, name) + bound_method = lambda *args, **kwargs: method(self, *args, **kwargs) + setattr(self, name, bound_method) + return bound_method + raise AttributeError(f"'{self.__class__.__name__}' object has no attribute '{name}'") From f151100e87ab8dc24c7fb06d3e4902a680ca64b4 Mon Sep 17 00:00:00 2001 From: tinyu Date: Fri, 12 Sep 2025 10:49:14 +0800 Subject: [PATCH 256/322] lib/m5ui: Add LVGL Spinner widget docs and example. Signed-off-by: tinyu --- docs/en/m5ui/index.rst | 1 + docs/en/m5ui/spinner.rst | 176 ++++++++++ docs/en/refs/m5ui.spinner.ref | 20 ++ .../locales/zh_CN/LC_MESSAGES/m5ui/spinner.po | 327 ++++++++++++++++++ .../m5ui/spinner/core2_spinner_example.m5f2 | 1 + .../m5ui/spinner/core2_spinner_example.py | 55 +++ m5stack/libs/m5ui/spinner.py | 16 +- 7 files changed, 593 insertions(+), 3 deletions(-) create mode 100644 docs/en/m5ui/spinner.rst create mode 100644 docs/en/refs/m5ui.spinner.ref create mode 100644 docs/locales/zh_CN/LC_MESSAGES/m5ui/spinner.po create mode 100644 examples/m5ui/spinner/core2_spinner_example.m5f2 create mode 100644 examples/m5ui/spinner/core2_spinner_example.py diff --git a/docs/en/m5ui/index.rst b/docs/en/m5ui/index.rst index 51b51484..c164522e 100644 --- a/docs/en/m5ui/index.rst +++ b/docs/en/m5ui/index.rst @@ -77,6 +77,7 @@ Classes scale.rst slider.rst spinbox.rst + spinner.rst switch.rst textarea.rst win.rst diff --git a/docs/en/m5ui/spinner.rst b/docs/en/m5ui/spinner.rst new file mode 100644 index 00000000..1ac0e9c4 --- /dev/null +++ b/docs/en/m5ui/spinner.rst @@ -0,0 +1,176 @@ +.. currentmodule:: m5ui + +M5Spinner +========== + +.. include:: ../refs/m5ui.spinner.ref + +M5Spinner is a spinning arc over a ring, typically used to show some type of activity is in progress. + +UiFlow2 Example +--------------- + +spinner +^^^^^^^^^^^^^^ + +Open the |core2_spinner_example.m5f2| project in UiFlow2. + +This example shows a spinning arc over a ring. + +UiFlow2 Code Block: + + |example.png| + +Example output: + + None + + +MicroPython Example +------------------- + +spinner +^^^^^^^^^^^^^ + +This example shows a spinning arc over a ring. + +MicroPython Code Block: + + .. literalinclude:: ../../../examples/m5ui/spinner/core2_spinner_example.py + :language: python + :linenos: + +Example output: + + None + + +**API** +------- + +M5Spinner +^^^^^^^^^ + +.. autoclass:: m5ui.spinner.M5Spinner + :members: + :member-order: bysource + + .. py:method:: set_flag(flag, value) + + Set a flag on the object. If ``value`` is True, the flag is added; if False, the flag is removed. + + :param int flag: The flag to set. + :param bool value: If True, the flag is added; if False, the flag is removed. + :return: None + + UiFlow2 Code Block: + + |set_flag.png| + + MicroPython Code Block: + + .. code-block:: python + + spinner_0.set_flag(lv.obj.FLAG.HIDDEN, True) + + .. py:method:: set_pos(x, y) + + Set the position of the spinner. + + :param int x: The x-coordinate of the spinner. + :param int y: The y-coordinate of the spinner. + + UiFlow2 Code Block: + + |set_pos.png| + + MicroPython Code Block: + + .. code-block:: python + + spinner_0.set_pos(100, 100) + + .. py:method:: set_x(x) + + Set the x-coordinate of the spinner. + + :param int x: The x-coordinate of the spinner. + + UiFlow2 Code Block: + + |set_x.png| + + MicroPython Code Block: + + .. code-block:: python + + spinner_0.set_x(100) + + .. py:method:: set_y(y) + + Set the y-coordinate of the spinner. + + :param int y: The y-coordinate of the spinner. + + UiFlow2 Code Block: + + |set_y.png| + + MicroPython Code Block: + + .. code-block:: python + + spinner_0.set_y(100) + + .. py:method:: set_size(width, height) + + Set the size of the spinner. + + :param int width: The width of the spinner. + :param int height: The height of the spinner. + + UiFlow2 Code Block: + + |set_size.png| + + MicroPython Code Block: + + .. code-block:: python + + spinner_0.set_size(100, 50) + + .. py:method:: align_to(obj, align, x, y) + + Align the spinner to another object. + + :param lv.obj obj: The object to align to. + :param int align: The alignment type. + :param int x: The x-offset from the aligned object. + :param int y: The y-offset from the aligned object. + + UiFlow2 Code Block: + + |align_to.png| + + MicroPython Code Block: + + .. code-block:: python + + spinner_0.align_to(page_0, lv.ALIGN.CENTER, 0, 0) + + .. py:method:: set_anim_params(anim_t, angle) + + Set the animation parameters of the spinner. + + :param int anim_t: The animation time in milliseconds. + :param int angle: The angle of the spinner in degrees. + + UiFlow2 Code Block: + + |set_anim_params.png| + + MicroPython Code Block: + + .. code-block:: python + + spinner_0.set_anim_params(1000, 180) \ No newline at end of file diff --git a/docs/en/refs/m5ui.spinner.ref b/docs/en/refs/m5ui.spinner.ref new file mode 100644 index 00000000..978cda39 --- /dev/null +++ b/docs/en/refs/m5ui.spinner.ref @@ -0,0 +1,20 @@ +.. |align_to.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/spinner/align_to.png +.. |set_bg_color.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/spinner/set_bg_color.png +.. |set_indicator_color.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/spinner/set_indicator_color.png +.. |set_pos.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/spinner/set_pos.png +.. |set_size.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/spinner/set_size.png +.. |set_x.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/spinner/set_x.png +.. |set_y.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/spinner/set_y.png +.. |set_flag.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/spinner/set_flag.png +.. |set_anim_params.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/spinner/set_anim_params.png + +.. |example.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/spinner/example.png + +.. |core2_spinner_example.m5f2| raw:: html + + + core2_spinner_example.m5f2 + diff --git a/docs/locales/zh_CN/LC_MESSAGES/m5ui/spinner.po b/docs/locales/zh_CN/LC_MESSAGES/m5ui/spinner.po new file mode 100644 index 00000000..f66663da --- /dev/null +++ b/docs/locales/zh_CN/LC_MESSAGES/m5ui/spinner.po @@ -0,0 +1,327 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2016 - 2025 M5Stack Technology Co., Ltd +# This file is distributed under the same license as the UIFlow2 Programming +# Guide package. +# FIRST AUTHOR , 2025. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: UIFlow2 Programming Guide \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-09-12 10:47+0800\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: zh_CN\n" +"Language-Team: zh_CN \n" +"Plural-Forms: nplurals=1; plural=0;\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.16.0\n" + +#: ../../en/m5ui/spinner.rst:4 ../../en/m5ui/spinner.rst:52 +#: 5fe7caab91d14429ad412309292ddb49 d82fad7e493b4296b0c7e078fcbfa792 +msgid "M5Spinner" +msgstr "M5Spinner" + +#: ../../en/m5ui/spinner.rst:8 13d8479a91534fb1b975b508d0ce25a7 +msgid "" +"M5Spinner is a spinning arc over a ring, typically used to show some type" +" of activity is in progress." +msgstr "M5Spinner 是一个环形上旋转的弧形,通常用于显示有某些操作正在进行中。" + +#: ../../en/m5ui/spinner.rst:11 5ab7c5284f9b44b09039b55616a87b8c +msgid "UiFlow2 Example" +msgstr "UiFlow2 示例" + +#: ../../en/m5ui/spinner.rst:14 ../../en/m5ui/spinner.rst:33 +#: afb5bd511cbb46608719f85d4d946e20 ff2677ed658c400e95c34c94e55f7ff7 +msgid "spinner" +msgstr "spinner" + +#: ../../en/m5ui/spinner.rst:16 fed5ce0d70744d6e8ed3a98f7b16c92b +msgid "Open the |core2_spinner_example.m5f2| project in UiFlow2." +msgstr "在 UiFlow2 中打开 |core2_spinner_example.m5f2| 项目。" + +#: ../../en/m5ui/spinner.rst:18 ../../en/m5ui/spinner.rst:35 +#: 2b7d3c9873314fc4b49bad49a6508a2b 6130abd93b974459b1bc6984dbfeb8db +msgid "This example shows a spinning arc over a ring." +msgstr "本示例演示了一个在环形上旋转的弧形。" + +#: ../../en/m5ui/spinner.rst:20 ../../en/m5ui/spinner.rst:66 +#: ../../en/m5ui/spinner.rst:83 ../../en/m5ui/spinner.rst:99 +#: ../../en/m5ui/spinner.rst:115 ../../en/m5ui/spinner.rst:132 +#: ../../en/m5ui/spinner.rst:151 ../../en/m5ui/spinner.rst:168 +#: 14cef86ae6584315934840f8721948f8 1ba3d1d99dfb49dbac4399bfb4ea1a68 +#: 1d228f34add74653853aa14ddb29a568 4f0e175a323043dda20873faca63ca7a +#: 5fd1b48ed1444f6c9994ec505b92757a 71550b271d5e4dabac7e432e0f2792b6 +#: 8a5142a33f98431ba52d3db12d3b7156 9bc83632bcdf4150970312c721797b44 +#: c7606be360f346f8a30ffe6e080abfd0 ec29ad76e7204533815957e8dc2d73ad +#: m5ui.spinner.M5Spinner:13 m5ui.spinner.M5Spinner.set_spinner_color:7 of +msgid "UiFlow2 Code Block:" +msgstr "UiFlow2 代码块:" + +#: ../../en/m5ui/spinner.rst:22 852c6c2a88ef46a69ae88585140864ca +msgid "|example.png|" +msgstr "" + +#: ../../en/refs/m5ui.spinner.ref:11 ec86f6cbb8ef4a9bb7d94331684dc8ef +msgid "example.png" +msgstr "" + +#: ../../en/m5ui/spinner.rst:24 ../../en/m5ui/spinner.rst:43 +#: 626f309b02c14dd78f0106d0aa79527a aef66c9e71164e608d7b885c744ec084 +msgid "Example output:" +msgstr "示例输出:" + +#: ../../en/m5ui/spinner.rst:26 ../../en/m5ui/spinner.rst:45 +#: ../../en/m5ui/spinner.rst:64 4ff9fcc7e3374f10bf0c06fb1311a870 +#: bc88698626014b91a4cae305de69dfd6 cfae4546150748499dc6172d33d39c7f +#: d044d27835db4388ae8fc9f0184e8fb1 m5ui.spinner.M5Spinner:15 of +msgid "None" +msgstr "" + +#: ../../en/m5ui/spinner.rst:30 0c12ad3f157d4b1f87ffca08bb865229 +msgid "MicroPython Example" +msgstr "MicroPython 示例" + +#: ../../en/m5ui/spinner.rst:37 ../../en/m5ui/spinner.rst:70 +#: ../../en/m5ui/spinner.rst:87 ../../en/m5ui/spinner.rst:103 +#: ../../en/m5ui/spinner.rst:119 ../../en/m5ui/spinner.rst:136 +#: ../../en/m5ui/spinner.rst:155 ../../en/m5ui/spinner.rst:172 +#: 0c3b48eb24464fcc947d74376ebccf8a 17ec5f41d5134bc9992b3f959270b54c +#: 229a8f3c5f144b0aa2b431d23d442d43 41237149d69640b1b4e489ea10d237a6 +#: 7abadabce0504bbcb6a6cb17fbdf501d a2c6a773905e4b8d8b6a580c61170418 +#: b7a01b6e02e74ed88b2d1baf82bfac86 b7d8f6c792cc4100a457735c31d7e488 +#: bbd55ee9ae69480aa9d1a1f5d69ce2c8 f505c60c4b5c414c87018a1f20250e47 +#: m5ui.spinner.M5Spinner:17 m5ui.spinner.M5Spinner.set_spinner_color:13 of +msgid "MicroPython Code Block:" +msgstr "MicroPython 代码块:" + +#: ../../en/m5ui/spinner.rst:49 81ad4a560dd34af0bbde5c284072c408 +msgid "**API**" +msgstr "" + +#: 646aa81d6909490cbb34818f02af11b4 m5ui.spinner.M5Spinner:1 of +msgid "Bases: :py:class:`~lvgl.spinner`" +msgstr "" + +#: 07ac6cc7a5e147f7b19c52752c2217f5 m5ui.spinner.M5Spinner:1 of +msgid "Create a spinner object." +msgstr "创建一个spinner对象。" + +#: ../../en/m5ui/spinner.rst 3788bb7a048142e0972d385bc070afc9 +#: 6fff59dee0f046dcae1c4fded3931fe0 939c4ef3eca84b41bff7d9a5e3fa8552 +#: 9652112174024c54a80f8d2f5eae8a38 ad8add8ff5f5474b932eaef2cfc8cecf +#: c5be7e0f9c5444269e6d8cb238b26fd4 ef5d703c0c3a440ea0dc467b2bee4083 +#: f46afabb4cf94a0ea847f80123ad9c80 f843ed3578ab4e538efd28c5b405e045 +#: m5ui.spinner.M5Spinner.set_spinner_color of +msgid "Parameters" +msgstr "参数" + +#: 5235e4c4abc44804b24447ebf2b710ea m5ui.spinner.M5Spinner:3 of +msgid "The x position of the spinner." +msgstr "spinner的 x 坐标位置。" + +#: 2a78ad328f5d46aeafa94cddad3313b5 m5ui.spinner.M5Spinner:4 of +msgid "The y position of the spinner." +msgstr "spinner的 y 坐标位置。" + +#: ../../en/m5ui/spinner.rst:129 d0c3f9e210ea4de4b0a651ebf8cf16cc +#: ee9e30a11a1340ed9f3eee5288e1d785 m5ui.spinner.M5Spinner:5 of +msgid "The width of the spinner." +msgstr "spinner的宽度。" + +#: ../../en/m5ui/spinner.rst:130 0a8efc2c34ce45519382f156bafaef22 +#: e6477f6fb81a4ea0b16a91bb93591cea m5ui.spinner.M5Spinner:6 of +msgid "The height of the spinner." +msgstr "spinner的高度。" + +#: ../../en/m5ui/spinner.rst:165 770451d7739b4085b8ef24446865d346 +#: b5f14933c9314e9a89ed671445c5cde4 m5ui.spinner.M5Spinner:7 of +msgid "The animation time in milliseconds." +msgstr "动画时间(毫秒)。" + +#: ../../en/m5ui/spinner.rst:166 00b92a7fef024ee39514af004a0ba898 +#: eb4257cf1f7f4055b60583c133a1c1ba m5ui.spinner.M5Spinner:8 of +msgid "The angle of the spinner in degrees." +msgstr "spinner的角度(度)。" + +#: fb343d66ba524fb59a78caabcb1fc694 m5ui.spinner.M5Spinner:9 of +msgid "The background color of the spinner in hexadecimal format." +msgstr "spinner的背景颜色(十六进制格式)。" + +#: ad2c7c607b874c4aab37812768a9510b m5ui.spinner.M5Spinner:10 of +msgid "The indicator color of the spinner in hexadecimal format." +msgstr "spinner指示器的颜色(十六进制格式)。" + +#: 08944caf536c47ea935793965d11d888 m5ui.spinner.M5Spinner:11 of +msgid "" +"The parent object to attach the spinner to. If not specified, the spinner" +" will be attached to the default screen." +msgstr "要附加spinner的父对象。如果未指定,spinner将附加到默认屏幕。" + +#: ../../en/m5ui/spinner.rst:60 64f917b75b904bde9cf0e30917a3714a +msgid "" +"Set a flag on the object. If ``value`` is True, the flag is added; if " +"False, the flag is removed." +msgstr "在对象上设置标志。如果 ``value`` 为 True,则添加标志;为 False 时移除标志。" + +#: ../../en/m5ui/spinner.rst:62 108eb6f0dab9411687e0673a868debdf +msgid "The flag to set." +msgstr "要设置的标志。" + +#: ../../en/m5ui/spinner.rst:63 9d6a6339116846bc860f7624051d58c3 +msgid "If True, the flag is added; if False, the flag is removed." +msgstr "为 True 时添加标志,为 False 时移除标志。" + +#: ../../en/m5ui/spinner.rst fb4eec82b3944bb8a18184b1d4e915d6 +msgid "Returns" +msgstr "返回" + +#: ../../en/m5ui/spinner.rst:68 42595aefc4144ca89245f619d79c3acf +msgid "|set_flag.png|" +msgstr "" + +#: ../../en/refs/m5ui.spinner.ref:8 2b7ff25626dc4e698ee1bf6c4587a735 +msgid "set_flag.png" +msgstr "" + +#: ../../en/m5ui/spinner.rst:78 15d4f044e7ab42e597b2233b81b0e4d3 +msgid "Set the position of the spinner." +msgstr "设置spinner的位置。" + +#: ../../en/m5ui/spinner.rst:80 ../../en/m5ui/spinner.rst:97 +#: 95e4f4a7e8f044aabcf763dba7d2c935 a65ce0e060804707a9b60d55b3dddb3c +msgid "The x-coordinate of the spinner." +msgstr "spinner的 x 坐标。" + +#: ../../en/m5ui/spinner.rst:81 ../../en/m5ui/spinner.rst:113 +#: 7eb60bb4cc244a1f91d2c531c2b32a93 cca15f6896c547f78bd0535c83048dc2 +msgid "The y-coordinate of the spinner." +msgstr "spinner的 y 坐标。" + +#: ../../en/m5ui/spinner.rst:85 c72b99d391e54289baa94503cac7a7ae +msgid "|set_pos.png|" +msgstr "" + +#: ../../en/refs/m5ui.spinner.ref:4 081428b9409b4bf19583681d6c7b4d55 +msgid "set_pos.png" +msgstr "" + +#: ../../en/m5ui/spinner.rst:95 7311ab152238405593f843df9c14c887 +msgid "Set the x-coordinate of the spinner." +msgstr "设置spinner的 x 坐标。" + +#: ../../en/m5ui/spinner.rst:101 2de8b1a2648c492cbd83100ede378916 +msgid "|set_x.png|" +msgstr "" + +#: ../../en/refs/m5ui.spinner.ref:6 aa8de5f43694450e8063cc2adcdfac02 +msgid "set_x.png" +msgstr "" + +#: ../../en/m5ui/spinner.rst:111 af456f55778b453681ac4a1ac8baab4e +msgid "Set the y-coordinate of the spinner." +msgstr "设置spinner的 y 坐标。" + +#: ../../en/m5ui/spinner.rst:117 6d9b819a8aab4c7dacae72b83b3354b8 +msgid "|set_y.png|" +msgstr "" + +#: ../../en/refs/m5ui.spinner.ref:7 975e4e66499a42699ad0510265d696a8 +msgid "set_y.png" +msgstr "" + +#: ../../en/m5ui/spinner.rst:127 0bb386cba62545f7821319762012ff42 +msgid "Set the size of the spinner." +msgstr "设置spinner的尺寸。" + +#: ../../en/m5ui/spinner.rst:134 7717aa1ae5c24ee8b2a8bbcee9a14f78 +msgid "|set_size.png|" +msgstr "" + +#: ../../en/refs/m5ui.spinner.ref:5 11d1d4be555a4560817df87196604ef4 +msgid "set_size.png" +msgstr "" + +#: ../../en/m5ui/spinner.rst:144 445075ef291a44b3ad107e5230b4e19d +msgid "Align the spinner to another object." +msgstr "将spinner对齐到另一个对象。" + +#: ../../en/m5ui/spinner.rst:146 03f3122196c14969b9bd9e2e005a2f1d +msgid "The object to align to." +msgstr "要对齐的对象。" + +#: ../../en/m5ui/spinner.rst:147 878a44f925264832b857305b6a73d11e +msgid "The alignment type." +msgstr "对齐类型。" + +#: ../../en/m5ui/spinner.rst:148 e71c8208f235467895006872c88211ea +msgid "The x-offset from the aligned object." +msgstr "相对于对齐对象的 x 偏移量。" + +#: ../../en/m5ui/spinner.rst:149 a15dc2a98da34b3aaeea0b781d58c1de +msgid "The y-offset from the aligned object." +msgstr "相对于对齐对象的 y 偏移量。" + +#: ../../en/m5ui/spinner.rst:153 ddf88816e42947718155179116a46bd2 +msgid "|align_to.png|" +msgstr "" + +#: ../../en/refs/m5ui.spinner.ref:1 77e08ebae9b74a04a2571feab5c6b875 +msgid "align_to.png" +msgstr "" + +#: ../../en/m5ui/spinner.rst:163 4525615aa3a84615a3d62dcb088596dd +msgid "Set the animation parameters of the spinner." +msgstr "设置spinner的动画参数。" + +#: ../../en/m5ui/spinner.rst:170 976fb0b843ee40f09629c1897365c6b7 +msgid "|set_anim_params.png|" +msgstr "" + +#: ../../en/refs/m5ui.spinner.ref:9 08bd4c26a57749a7b6dfe43af984c07e +msgid "set_anim_params.png" +msgstr "" + +#: 961c683bc93048a98643e23cd24886f3 m5ui.spinner.M5Spinner.set_spinner_color:1 +#: of +msgid "Set the color of the spinner." +msgstr "设置spinner的颜色。" + +#: 9d5976f902df4b3f97b8ca1208a6f010 m5ui.spinner.M5Spinner.set_spinner_color:3 +#: of +msgid "The color of the spinner in hexadecimal format." +msgstr "spinner的颜色(十六进制格式)。" + +#: a06947314c1a4c82813fee6412b13ca3 m5ui.spinner.M5Spinner.set_spinner_color:4 +#: of +msgid "The opacity of the color (0-255)." +msgstr "颜色的不透明度(0-255)。" + +#: 9d5976f902df4b3f97b8ca1208a6f010 m5ui.spinner.M5Spinner.set_spinner_color:5 +#: of +#, fuzzy +msgid "The part of the spinner to set the color for." +msgstr "要设置颜色的spinner部分。" + +#: 64c3b2982c4c498994ba739d0ce100aa m5ui.spinner.M5Spinner.set_spinner_color:9 +#: of +msgid "|set_indicator_color.png|" +msgstr "" + +#: ../../en/refs/m5ui.spinner.ref:3 bc4d79b5ca644868ad43fc7b5946cefa +msgid "set_indicator_color.png" +msgstr "" + +#: a801f04b247042599fbbb2b37b5bd6da m5ui.spinner.M5Spinner.set_spinner_color:11 +#: of +msgid "|set_bg_color.png|" +msgstr "" + +#: ../../en/refs/m5ui.spinner.ref:2 92d9fb4bc9df4128841277eb9401911b +msgid "set_bg_color.png" +msgstr "" + diff --git a/examples/m5ui/spinner/core2_spinner_example.m5f2 b/examples/m5ui/spinner/core2_spinner_example.m5f2 new file mode 100644 index 00000000..04916fc5 --- /dev/null +++ b/examples/m5ui/spinner/core2_spinner_example.m5f2 @@ -0,0 +1 @@ +{"version":"V2.0","versionNumber":"V2.3.5","type":"core2","components":[{"name":"page0","type":"lvgl_page","layer":0,"screenId":"builtin","screenName":"","id":"zDOFVKXbjDIFbO!c","createTime":1757580300040,"backgroundColor":"#ffffff","isLVGL":true,"isSelected":true},{"name":"spinner0","type":"lvgl_spinner","layer":1,"screenId":"builtin","screenName":"","id":"s#7sms#h$B%d+fWc","createTime":1757580306199,"x":71,"y":81,"width":100,"height":100,"angle":180,"anim_time":10000,"indicatorColor":"#2193f3","backgroundColor":"#e7e3e7","pageId":"zDOFVKXbjDIFbO!c","isLVGL":true,"isSelected":false}],"resources":[{"hardware":["hardware_button","hardware_pin_button","imu","speaker","touch","mic","sdcard"]}],"units":[],"hats":[],"bases":[],"i2cs":[],"blockly":"truepage0true","screen":[{"simulationName":"Built-in","type":"builtin","width":320,"height":240,"scale":0.77,"screenName":"","blockId":"","screenColorType":0,"rotation":1,"id":"builtin","createTime":1757580300040}],"logicWhenNum":0,"customList":[]} \ No newline at end of file diff --git a/examples/m5ui/spinner/core2_spinner_example.py b/examples/m5ui/spinner/core2_spinner_example.py new file mode 100644 index 00000000..cc3fd442 --- /dev/null +++ b/examples/m5ui/spinner/core2_spinner_example.py @@ -0,0 +1,55 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import os, sys, io +import M5 +from M5 import * +import m5ui +import lvgl as lv + + +page0 = None +spinner0 = None + + +def setup(): + global page0, spinner0 + + M5.begin() + Widgets.setRotation(1) + m5ui.init() + page0 = m5ui.M5Page(bg_c=0xFFFFFF) + spinner0 = m5ui.M5Spinner( + x=71, + y=81, + w=100, + h=100, + anim_t=10000, + angle=180, + bg_c=0xE7E3E7, + bg_c_indicator=0x2193F3, + parent=page0, + ) + + page0.screen_load() + + +def loop(): + global page0, spinner0 + M5.update() + + +if __name__ == "__main__": + try: + setup() + while True: + loop() + except (Exception, KeyboardInterrupt) as e: + try: + m5ui.deinit() + from utility import print_error_msg + + print_error_msg(e) + except ImportError: + print("please update to latest firmware") diff --git a/m5stack/libs/m5ui/spinner.py b/m5stack/libs/m5ui/spinner.py index 3c6d8666..f025ab96 100644 --- a/m5stack/libs/m5ui/spinner.py +++ b/m5stack/libs/m5ui/spinner.py @@ -15,6 +15,8 @@ class M5Spinner(lv.spinner): :param int h: The height of the spinner. :param int anim_t: The animation time in milliseconds. :param int angle: The angle of the spinner in degrees. + :param int bg_c: The background color of the spinner in hexadecimal format. + :param int bg_c_indicator: The indicator color of the spinner in hexadecimal format. :param lv.obj parent: The parent object to attach the spinner to. If not specified, the spinner will be attached to the default screen. UiFlow2 Code Block: @@ -29,7 +31,7 @@ class M5Spinner(lv.spinner): import lvgl as lv m5ui.init() - spinner_0 = M5Spinner(x=120, y=80, w=60, h=30, anim_t=1000, angle=180, parent=page0) + spinner_0 = M5Spinner(x=120, y=80, w=60, h=30, anim_t=1000, angle=180, bg_c=0xE7E3E7, bg_c_indicator=0x0288FB, parent=page0) """ def __init__( @@ -40,6 +42,8 @@ def __init__( h=0, anim_t=10000, angle=180, + bg_c=0xE7E3E7, + bg_c_indicator=0x0288FB, parent=None, ): if parent is None: @@ -48,21 +52,27 @@ def __init__( self.set_pos(x, y) self.set_size(w, h) self.set_anim_params(anim_t, angle) + self.set_spinner_color(bg_c, 255, lv.PART.MAIN | lv.STATE.DEFAULT) + self.set_spinner_color(bg_c_indicator, 255, lv.PART.INDICATOR | lv.STATE.DEFAULT) def set_spinner_color(self, color, opa: int, part: int): """Set the color of the spinner. :param int color: The color of the spinner in hexadecimal format. + :param int opa: The opacity of the color (0-255). + :param int part: The part of the spinner to set the color for. UiFlow2 Code Block: - |set_spinner_color.png| + |set_indicator_color.png| + + |set_bg_color.png| MicroPython Code Block: .. code-block:: python - label_0.set_spinner_color(0x2196F3, lv.PART.MAIN | lv.STATE.DEFAULT) + spinner_0.set_spinner_color(0x2196F3, 255, lv.PART.MAIN | lv.STATE.DEFAULT) """ if isinstance(color, int): color = lv.color_hex(color) From ea6affae8836e671f9f29dc7a15d624885318b24 Mon Sep 17 00:00:00 2001 From: tinyu Date: Fri, 12 Sep 2025 11:29:05 +0800 Subject: [PATCH 257/322] libs/unit: Limit 8encoder I2C address range. Signed-off-by: tinyu --- m5stack/libs/unit/encoder8.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/m5stack/libs/unit/encoder8.py b/m5stack/libs/unit/encoder8.py index 5ef2e472..1ec5debe 100644 --- a/m5stack/libs/unit/encoder8.py +++ b/m5stack/libs/unit/encoder8.py @@ -255,7 +255,7 @@ def set_i2c_address(self, addr: int = 0x41) -> None: addr: note: The new I2C address. Default is 0x41. """ - if addr >= 1 and addr <= 127: + if addr >= 0x08 and addr <= 0x77: if addr != self.i2c_addr: self.write_reg_data(self._ENCODER_I2C_ADDR_REG, [addr]) self.i2c_addr = addr From a45490b832d3a6ba7f4cc523952a845e157b572e Mon Sep 17 00:00:00 2001 From: hlym123 Date: Fri, 12 Sep 2025 11:49:52 +0800 Subject: [PATCH 258/322] docs: Fix some link error. Signed-off-by: hlym123 --- docs/en/refs/module.lora868_v12.ref | 2 +- docs/en/refs/unit.step16.ref | 4 ++-- docs/en/units/step16.rst | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/en/refs/module.lora868_v12.ref b/docs/en/refs/module.lora868_v12.ref index e66c4450..09987eba 100644 --- a/docs/en/refs/module.lora868_v12.ref +++ b/docs/en/refs/module.lora868_v12.ref @@ -1,5 +1,5 @@ -.. |LoRa868Module v1.2| image:: https://static-cdn.m5stack.com/resource/docs/products/module/Module-LoRa868_V1.2/img-dac09b0a-7367-4ed9-9374-b604f646ec3b.webp +.. |LoRa868Module v1.2| image:: https://m5stack-doc.oss-cn-shenzhen.aliyuncs.com/639/M029-V12_01.webp :target: https://docs.m5stack.com/en/module/Module-LoRa868_V1.2 :height: 200px :width: 200px diff --git a/docs/en/refs/unit.step16.ref b/docs/en/refs/unit.step16.ref index bc9ff001..27b46c93 100644 --- a/docs/en/refs/unit.step16.ref +++ b/docs/en/refs/unit.step16.ref @@ -1,5 +1,5 @@ -.. |Unit step16| image:: https://static-cdn.m5stack.com/resource/docs/products/unit/step16.webp - :target: https://docs.m5stack.com/zh_CN/unit/step16 +.. |Unit step16| image:: https://m5stack-doc.oss-cn-shenzhen.aliyuncs.com/1160/Unit_Step16_01.webp + :target: https://docs.m5stack.com/en/unit/Unit_Step16 :height: 200px :width: 200px diff --git a/docs/en/units/step16.rst b/docs/en/units/step16.rst index 5b49c310..c84e517a 100644 --- a/docs/en/units/step16.rst +++ b/docs/en/units/step16.rst @@ -1,7 +1,7 @@ Step16 Unit =========== -.. sku: xxx +.. sku: U198 .. include:: ../refs/unit.step16.ref From f8753dc06b2a5392166ad9a79c7bb3b14ace9f9c Mon Sep 17 00:00:00 2001 From: tinyu Date: Mon, 15 Sep 2025 09:31:56 +0800 Subject: [PATCH 259/322] libs/m5ui: Fix screen size retrieval in emulator. Signed-off-by: tinyu --- m5stack/libs/m5ui/base.py | 26 ++++++++++---------------- m5stack/libs/m5ui/switch.py | 11 +++++------ 2 files changed, 15 insertions(+), 22 deletions(-) diff --git a/m5stack/libs/m5ui/base.py b/m5stack/libs/m5ui/base.py index 3aa02068..5df78dbf 100644 --- a/m5stack/libs/m5ui/base.py +++ b/m5stack/libs/m5ui/base.py @@ -105,22 +105,6 @@ def set_bg_color(self, color: int, opa: int, part: int) -> None: time.sleep(0.01) self.set_style_bg_opa(opa, part) - @staticmethod - def set_line_color(self, color: int, opa: int, part: int = lv.PART.MAIN) -> None: - """Set the line color and opacity for a given part of the object. - - :param int color: The color to set, can be an integer (hex) or a lv.color object. - :param int opa: The opacity level (0-255). - :param int part: The part of the object to apply the style to (e.g., lv.PART.MAIN). - :return: None - """ - if isinstance(color, int): - color = lv.color_hex(color) - - self.set_style_line_color(color, part) - time.sleep(0.01) - self.set_style_line_opa(opa, part) - @staticmethod def set_bg_grad_color(self, color, opa, grad_color, grad_opd, grad_dir, part: int) -> None: """Set the background gradient color of the bar. @@ -168,3 +152,13 @@ def set_border_color(self, color: int, opa: int, part: int): self.set_style_border_color(color, part) time.sleep(0.01) self.set_style_border_opa(opa, part) + + @staticmethod + def get_display_size(self): + """Get the display size. + + :return: A tuple containing the width and height of the display. + :rtype: tuple + """ + scr = lv.screen_active() + return scr.get_width(), scr.get_height() diff --git a/m5stack/libs/m5ui/switch.py b/m5stack/libs/m5ui/switch.py index 3ce3ac45..2db74244 100644 --- a/m5stack/libs/m5ui/switch.py +++ b/m5stack/libs/m5ui/switch.py @@ -4,7 +4,6 @@ from m5ui.base import M5Base import lvgl as lv -import M5 class M5Switch(lv.switch): @@ -49,12 +48,12 @@ def __init__( parent = lv.screen_active() super().__init__(parent) self.set_pos(x, y) - self.set_bg_color(bg_c, 255, lv.PART.MAIN | lv.STATE.DEFAULT) - self.set_bg_color(bg_c_checked, 255, lv.PART.INDICATOR | lv.STATE.CHECKED) - self.set_bg_color(circle_c, 255, lv.PART.KNOB | lv.STATE.DEFAULT) self.set_size(w, h) self.width = w self.height = h + self.set_bg_color(bg_c, 255, lv.PART.MAIN | lv.STATE.DEFAULT) + self.set_bg_color(bg_c_checked, 255, lv.PART.INDICATOR | lv.STATE.CHECKED) + self.set_bg_color(circle_c, 255, lv.PART.KNOB | lv.STATE.DEFAULT) def set_direction(self, direction): """Set the direction of the switch. @@ -89,13 +88,13 @@ def set_size(self, w, h): if w < 0 or h < 0: raise ValueError("Width and height must be non-negative") - _width = M5.Display.width() - _height = M5.Display.height() + _width, _height = self.get_display_size() if w < _width and h < _height: super().set_size(w, h) else: print("Warning: width or height exceed screen size, auto set to screen size") + self.set_pos(0, 0) super().set_size(_width, _height) self.width = w From 926bd1fa3047cb3f8b06c7ae7828b8badcd00c54 Mon Sep 17 00:00:00 2001 From: lbuque <1102390310@qq.com> Date: Fri, 12 Sep 2025 14:44:21 +0800 Subject: [PATCH 260/322] libs/cap: Add Cap LoRa868 support. Signed-off-by: lbuque <1102390310@qq.com> --- docs/en/cap/index.rst | 7 + docs/en/cap/lora868.rst | 126 ++++ docs/en/conf.py | 1 + docs/en/index.rst | 1 + docs/en/refs/cap.lora868.ref | 71 ++ docs/locales/zh_CN/LC_MESSAGES/cap/index.po | 26 + docs/locales/zh_CN/LC_MESSAGES/cap/lora868.po | 667 ++++++++++++++++++ ...cardputer_adv_lora868_cap_gps_example.m5f2 | 1 + .../cardputer_adv_lora868_cap_gps_example.py | 48 ++ ...uter_adv_lora868_cap_receiver_example.m5f2 | 1 + ...dputer_adv_lora868_cap_receiver_example.py | 77 ++ ...dputer_adv_lora868_cap_sender_example.m5f2 | 1 + ...ardputer_adv_lora868_cap_sender_example.py | 78 ++ m5stack/libs/cap/__init__.py | 17 + m5stack/libs/cap/lora868.py | 500 +++++++++++++ m5stack/libs/cap/manifest.py | 13 + m5stack/libs/hardware/keyboard/__init__.py | 2 + m5stack/libs/manifest.py | 1 + 18 files changed, 1638 insertions(+) create mode 100644 docs/en/cap/index.rst create mode 100644 docs/en/cap/lora868.rst create mode 100644 docs/en/refs/cap.lora868.ref create mode 100644 docs/locales/zh_CN/LC_MESSAGES/cap/index.po create mode 100644 docs/locales/zh_CN/LC_MESSAGES/cap/lora868.po create mode 100644 examples/cap/lora868/cardputer_adv_lora868_cap_gps_example.m5f2 create mode 100644 examples/cap/lora868/cardputer_adv_lora868_cap_gps_example.py create mode 100644 examples/cap/lora868/cardputer_adv_lora868_cap_receiver_example.m5f2 create mode 100644 examples/cap/lora868/cardputer_adv_lora868_cap_receiver_example.py create mode 100644 examples/cap/lora868/cardputer_adv_lora868_cap_sender_example.m5f2 create mode 100644 examples/cap/lora868/cardputer_adv_lora868_cap_sender_example.py create mode 100644 m5stack/libs/cap/__init__.py create mode 100644 m5stack/libs/cap/lora868.py create mode 100644 m5stack/libs/cap/manifest.py diff --git a/docs/en/cap/index.rst b/docs/en/cap/index.rst new file mode 100644 index 00000000..e7f348be --- /dev/null +++ b/docs/en/cap/index.rst @@ -0,0 +1,7 @@ +Cap +=== + +.. toctree:: + :maxdepth: 1 + + lora868.rst diff --git a/docs/en/cap/lora868.rst b/docs/en/cap/lora868.rst new file mode 100644 index 00000000..77bb12ea --- /dev/null +++ b/docs/en/cap/lora868.rst @@ -0,0 +1,126 @@ +LoRa868 Cap +=========== + +.. sku: U201 + +.. include:: ../refs/cap.lora868.ref + +Cap LoRa868 is a high-performance LoRa communication and GNSS global navigation expansion module designed for the Cardputer-Adv. + +Support the following products: + + |LoRa868Cap| + +UiFlow2 Example +--------------- + +Sender +^^^^^^ + +Open the |cardputer_adv_lora868_cap_sender_example.m5f2| project in UiFlow2. + +Use the keyboard to enter the text you want to send and press ENTER to send it. + +UiFlow2 Code Block: + + |cardputer_adv_lora868_cap_sender_example.png| + +Example output: + + None + +Receiver +^^^^^^^^ + +Open the |cardputer_adv_lora868_cap_receiver_example.m5f2| project in UiFlow2. + +This example receives and displays data. + +UiFlow2 Code Block: + + |cardputer_adv_lora868_cap_receiver_example.png| + +Example output: + + None + +GPS Usage +^^^^^^^^^ + +Open the |cardputer_adv_lora868_cap_gps_example.m5f2| project in UiFlow2. + +This example demonstrates how to use the GPS functionality of the LoRa868 Cap. + +UiFlow2 Code Block: + + |cardputer_adv_lora868_cap_gps_example.png| + +Example output: + + None + + +MicroPython Example +------------------- + +Sender +^^^^^^ + +Use the keyboard to enter the text you want to send and press ENTER to send it. + +MicroPython Code Block: + + .. literalinclude:: ../../../examples/cap/lora868/cardputer_adv_lora868_cap_sender_example.py + :language: python + :linenos: + +Example output: + + None + +Receiver +^^^^^^^^ + +This example receives and displays data. + +MicroPython Code Block: + + .. literalinclude:: ../../../examples/cap/lora868/cardputer_adv_lora868_cap_receiver_example.py + :language: python + :linenos: + +Example output: + + None + +GPS Usage +^^^^^^^^^ + +This example demonstrates how to use the GPS functionality of the LoRa868 Cap. + +MicroPython Code Block: + + .. literalinclude:: ../../../examples/cap/lora868/cardputer_adv_lora868_cap_gps_example.py + :language: python + :linenos: + +Example output: + + None + +**API** +------- + +class LoRa868Cap +^^^^^^^^^^^^^^^^ + +.. autoclass:: cap.lora868.LoRa868Cap + :members: + :member-order: bysource + +class GPSCap +^^^^^^^^^^^^ + +.. autoclass:: cap.lora868.GPSCap + :members: + :member-order: bysource diff --git a/docs/en/conf.py b/docs/en/conf.py index 88ba9d7e..4c1dca0f 100644 --- a/docs/en/conf.py +++ b/docs/en/conf.py @@ -61,6 +61,7 @@ "m5audio2", "lvgl", "_espnow", + "lora" ] autodoc_default_options = { diff --git a/docs/en/index.rst b/docs/en/index.rst index 0ec0edc3..5b339633 100644 --- a/docs/en/index.rst +++ b/docs/en/index.rst @@ -16,6 +16,7 @@ UiFlow2 documentation and references units/index.rst hats/index.rst base/index.rst + cap/index.rst iot-devices/index.rst advanced/index.rst quick-reference/index.rst diff --git a/docs/en/refs/cap.lora868.ref b/docs/en/refs/cap.lora868.ref new file mode 100644 index 00000000..a9164076 --- /dev/null +++ b/docs/en/refs/cap.lora868.ref @@ -0,0 +1,71 @@ +.. |LoRa868Cap| image:: https://m5stack-doc.oss-cn-shenzhen.aliyuncs.com/1177/Cap_LoRa868_01.webp + :target: https://docs.m5stack.com/en/cap/Cap_LoRa868 + :height: 200px + :width: 200px + +.. |cardputer_adv_lora868_cap_gps_example.png| image:: https://static-cdn.m5stack.com/mpy_docs/cap/lora868/cardputer_adv_lora868_cap_gps_example.png +.. |cardputer_adv_lora868_cap_receiver_example.png| image:: https://static-cdn.m5stack.com/mpy_docs/cap/lora868/cardputer_adv_lora868_cap_receiver_example.png +.. |cardputer_adv_lora868_cap_sender_example.png| image:: https://static-cdn.m5stack.com/mpy_docs/cap/lora868/cardputer_adv_lora868_cap_sender_example.png + +.. |deinit.png| image:: https://static-cdn.m5stack.com/mpy_docs/cap/lora868/deinit.png +.. |get_altitude.png| image:: https://static-cdn.m5stack.com/mpy_docs/cap/lora868/get_altitude.png +.. |get_corse_over_ground.png| image:: https://static-cdn.m5stack.com/mpy_docs/cap/lora868/get_corse_over_ground.png +.. |get_gps_date.png| image:: https://static-cdn.m5stack.com/mpy_docs/cap/lora868/get_gps_date.png +.. |get_gps_date_time.png| image:: https://static-cdn.m5stack.com/mpy_docs/cap/lora868/get_gps_date_time.png +.. |get_gps_time.png| image:: https://static-cdn.m5stack.com/mpy_docs/cap/lora868/get_gps_time.png +.. |get_latitude.png| image:: https://static-cdn.m5stack.com/mpy_docs/cap/lora868/get_latitude.png +.. |get_longitude.png| image:: https://static-cdn.m5stack.com/mpy_docs/cap/lora868/get_longitude.png +.. |get_pos_quality.png| image:: https://static-cdn.m5stack.com/mpy_docs/cap/lora868/get_pos_quality.png +.. |get_satellite_num.png| image:: https://static-cdn.m5stack.com/mpy_docs/cap/lora868/get_satellite_num.png +.. |get_speed_over_ground.png| image:: https://static-cdn.m5stack.com/mpy_docs/cap/lora868/get_speed_over_ground.png +.. |get_time_zone.png| image:: https://static-cdn.m5stack.com/mpy_docs/cap/lora868/get_time_zone.png +.. |get_timestamp.png| image:: https://static-cdn.m5stack.com/mpy_docs/cap/lora868/get_timestamp.png +.. |get_work_mode.png| image:: https://static-cdn.m5stack.com/mpy_docs/cap/lora868/get_work_mode.png +.. |gps_init.png| image:: https://static-cdn.m5stack.com/mpy_docs/cap/lora868/gps_init.png +.. |irq_triggered.png| image:: https://static-cdn.m5stack.com/mpy_docs/cap/lora868/irq_triggered.png +.. |lora_init.png| image:: https://static-cdn.m5stack.com/mpy_docs/cap/lora868/lora_init.png +.. |recv.png| image:: https://static-cdn.m5stack.com/mpy_docs/cap/lora868/recv.png +.. |recv_data_param.png| image:: https://static-cdn.m5stack.com/mpy_docs/cap/lora868/recv_data_param.png +.. |send.png| image:: https://static-cdn.m5stack.com/mpy_docs/cap/lora868/send.png +.. |send_return.png| image:: https://static-cdn.m5stack.com/mpy_docs/cap/lora868/send_return.png +.. |send_time.png| image:: https://static-cdn.m5stack.com/mpy_docs/cap/lora868/send_time.png +.. |set_bw.png| image:: https://static-cdn.m5stack.com/mpy_docs/cap/lora868/set_bw.png +.. |set_coding_rate.png| image:: https://static-cdn.m5stack.com/mpy_docs/cap/lora868/set_coding_rate.png +.. |set_freq.png| image:: https://static-cdn.m5stack.com/mpy_docs/cap/lora868/set_freq.png +.. |set_irq_callback.png| image:: https://static-cdn.m5stack.com/mpy_docs/cap/lora868/set_irq_callback.png +.. |set_output_power.png| image:: https://static-cdn.m5stack.com/mpy_docs/cap/lora868/set_output_power.png +.. |set_preamble_len.png| image:: https://static-cdn.m5stack.com/mpy_docs/cap/lora868/set_preamble_len.png +.. |set_sf.png| image:: https://static-cdn.m5stack.com/mpy_docs/cap/lora868/set_sf.png +.. |set_syncword.png| image:: https://static-cdn.m5stack.com/mpy_docs/cap/lora868/set_syncword.png +.. |set_time_zone.png| image:: https://static-cdn.m5stack.com/mpy_docs/cap/lora868/set_time_zone.png +.. |set_work_mode.png| image:: https://static-cdn.m5stack.com/mpy_docs/cap/lora868/set_work_mode.png +.. |sleep.png| image:: https://static-cdn.m5stack.com/mpy_docs/cap/lora868/sleep.png +.. |standby.png| image:: https://static-cdn.m5stack.com/mpy_docs/cap/lora868/standby.png +.. |start_recv.png| image:: https://static-cdn.m5stack.com/mpy_docs/cap/lora868/start_recv.png + +.. |cardputer_adv_lora868_cap_gps_example.m5f2| raw:: html + + + cardputer_adv_lora868_cap_gps_example.m5f2 + + +.. |cardputer_adv_lora868_cap_receiver_example.m5f2| raw:: html + + + cardputer_adv_lora868_cap_receiver_example.m5f2 + + +.. |cardputer_adv_lora868_cap_sender_example.m5f2| raw:: html + + + cardputer_adv_lora868_cap_sender_example.m5f2 + \ No newline at end of file diff --git a/docs/locales/zh_CN/LC_MESSAGES/cap/index.po b/docs/locales/zh_CN/LC_MESSAGES/cap/index.po new file mode 100644 index 00000000..dbaa3d1d --- /dev/null +++ b/docs/locales/zh_CN/LC_MESSAGES/cap/index.po @@ -0,0 +1,26 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2016 - 2025 M5Stack Technology Co., Ltd +# This file is distributed under the same license as the UIFlow2 Programming +# Guide package. +# FIRST AUTHOR , 2025. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: UIFlow2 Programming Guide \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-09-17 09:37+0800\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: zh_CN\n" +"Language-Team: zh_CN \n" +"Plural-Forms: nplurals=1; plural=0;\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.17.0\n" + +#: ../../en/cap/index.rst:2 9c11984cc8a34dfdaf5ca25bde76d827 +msgid "Cap" +msgstr "" + diff --git a/docs/locales/zh_CN/LC_MESSAGES/cap/lora868.po b/docs/locales/zh_CN/LC_MESSAGES/cap/lora868.po new file mode 100644 index 00000000..d7792c74 --- /dev/null +++ b/docs/locales/zh_CN/LC_MESSAGES/cap/lora868.po @@ -0,0 +1,667 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2016 - 2025 M5Stack Technology Co., Ltd +# This file is distributed under the same license as the UIFlow2 Programming +# Guide package. +# FIRST AUTHOR , 2025. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: UIFlow2 Programming Guide \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-09-17 10:26+0800\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: zh_CN\n" +"Language-Team: zh_CN \n" +"Plural-Forms: nplurals=1; plural=0;\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.17.0\n" + +#: ../../en/cap/lora868.rst:2 ee4b86c4701042faa563aa4b71e40ff4 +msgid "LoRa868 Cap" +msgstr "" + +#: ../../en/cap/lora868.rst:8 5a6af78baefb45cf855f903e8e2b8612 +msgid "" +"Cap LoRa868 is a high-performance LoRa communication and GNSS global " +"navigation expansion module designed for the Cardputer-Adv." +msgstr "Cap LoRa868 是一款专为 Cardputer-Adv 设计的高性能 LoRa 通信和 GNSS 全球导航扩展模块。" + +#: ../../en/cap/lora868.rst:10 5d115265a3e04364adbe1524e13dcbd4 +msgid "Support the following products:" +msgstr "支持以下产品:" + +#: ../../en/cap/lora868.rst:12 4d7a5cecd7fd47c18be025c4789ba0aa +msgid "|LoRa868Cap|" +msgstr "" + +#: ../../en/refs/cap.lora868.ref 380206b16c044a348e123593e6a2093b +msgid "LoRa868Cap" +msgstr "" + +#: ../../en/cap/lora868.rst:15 ba3ef3254b4d42178e1fe2a2a1873d12 +msgid "UiFlow2 Example" +msgstr "" + +#: ../../en/cap/lora868.rst:18 ../../en/cap/lora868.rst:67 +#: 48241198995441c085c1bf1d8236a3a1 6f98111c025f493f961b3c9dd9ae5418 +msgid "Sender" +msgstr "发送端" + +#: ../../en/cap/lora868.rst:20 13152480c1424fd08eda83f1e660804a +msgid "" +"Open the |cardputer_adv_lora868_cap_sender_example.m5f2| project in " +"UiFlow2." +msgstr "在 UiFlow2 中打开 |cardputer_adv_lora868_cap_sender_example.m5f2| 项目。" + +#: ../../en/cap/lora868.rst:22 ../../en/cap/lora868.rst:69 +#: 14cdcefa7f6e4199808bc173f62c44e7 576c0ae0ca6f485c8e87e5ff8b56efe8 +msgid "" +"Use the keyboard to enter the text you want to send and press ENTER to " +"send it." +msgstr "使用键盘输入要发送的文本,然后按 ENTER 键发送。" + +#: ../../en/cap/lora868.rst:24 ../../en/cap/lora868.rst:39 +#: ../../en/cap/lora868.rst:54 0c9d427bc6654e7892fb361e6ece832f +#: 0d7e7909e4dc44978c38e0ee867ac048 16883c6aa58e421f8845b9e1bd9a7314 +#: 19e43495d50d41f7a5fb145120662f51 288e3b64ac5245e2b5e93969393fe42f +#: 2fa32f35966542f4aedfab630e9976fd 4a982357a5744802a07db0ae146691a5 +#: 57d42eee614b484f9db6129910b01b1c 5db3de3db0614d8d92fc016fa08c33b6 +#: 61734d608c2e4c9ab8059a5d14259ab1 6336d10453b84891aaa40c19841ccfa3 +#: 91c913b285154c7c918727186ac059e3 942a1e133bef48b28a0720c93599b605 +#: ab339117549e49b7b64bc55421c684ea ac9bd2bb45c44c63842852a4cb2c9f05 +#: bfb8ec96827a4b8e92723696cb7f9459 cap.lora868.GPSCap:8 +#: cap.lora868.LoRa868Cap:26 cap.lora868.LoRa868Cap.irq_triggered:6 +#: cap.lora868.LoRa868Cap.recv:11 cap.lora868.LoRa868Cap.send:10 +#: cap.lora868.LoRa868Cap.set_bw:7 cap.lora868.LoRa868Cap.set_coding_rate:5 +#: cap.lora868.LoRa868Cap.set_freq:5 cap.lora868.LoRa868Cap.set_irq_callback:6 +#: cap.lora868.LoRa868Cap.set_output_power:5 +#: cap.lora868.LoRa868Cap.set_preamble_len:5 cap.lora868.LoRa868Cap.set_sf:5 +#: cap.lora868.LoRa868Cap.set_syncword:5 cap.lora868.LoRa868Cap.sleep:5 +#: cap.lora868.LoRa868Cap.standby:5 cap.lora868.LoRa868Cap.start_recv:5 +#: dd19a2ac2cd740d69e486d1839058ca9 f7a11575dd994a27bc1206626fd5d460 +#: ff3675644a324135953367d4250013d6 of +msgid "UiFlow2 Code Block:" +msgstr "UiFlow2 代码块:" + +#: ../../en/cap/lora868.rst:26 a6dcff6a9af94805b979e63121730956 +msgid "|cardputer_adv_lora868_cap_sender_example.png|" +msgstr "" + +#: ../../en/refs/cap.lora868.ref:8 101bf5cdef274edcbacd34f9bb711392 +msgid "cardputer_adv_lora868_cap_sender_example.png" +msgstr "" + +#: ../../en/cap/lora868.rst:28 ../../en/cap/lora868.rst:43 +#: ../../en/cap/lora868.rst:58 ../../en/cap/lora868.rst:77 +#: ../../en/cap/lora868.rst:92 ../../en/cap/lora868.rst:107 +#: 05f8edf1ef244ba6a82022c238676346 0a343e90445b4bd7a2d9795f6b8dbc48 +#: 25df90c7eed54760bf31fa9362557a6b 6683d1072726443fa4124f7378e2a8d5 +#: 796072cedc5048ac92ec7ae0fb7454e8 9384948938c64a629bdb2a4ea8a11ca9 +msgid "Example output:" +msgstr "示例输出:" + +#: ../../en/cap/lora868.rst:30 ../../en/cap/lora868.rst:45 +#: ../../en/cap/lora868.rst:60 ../../en/cap/lora868.rst:79 +#: ../../en/cap/lora868.rst:94 ../../en/cap/lora868.rst:109 +#: 02b96cd4ee8645a29217cf6809792022 18e2d54394ff46129cc1cada520ea433 +#: 2f2224bc3b034180ac857d3aace79e9f 5ae993ab55264e049e4a691e21ba05f7 +#: 653d40890b3944df863b7ad77052f90f e34f0aaf551b43e6a06a4df49ad563ec +msgid "None" +msgstr "" + +#: ../../en/cap/lora868.rst:33 ../../en/cap/lora868.rst:82 +#: 1155c1969fb24156a21705cdd23a2e77 be7da6e88c634202a7b8c288e7ed525b +msgid "Receiver" +msgstr "接收端" + +#: ../../en/cap/lora868.rst:35 ec19a5631cb14e85b31142ffc0e5cfe4 +msgid "" +"Open the |cardputer_adv_lora868_cap_receiver_example.m5f2| project in " +"UiFlow2." +msgstr "在 UiFlow2 中打开 |cardputer_adv_lora868_cap_receiver_example.m5f2| 项目。" + +#: ../../en/cap/lora868.rst:37 ../../en/cap/lora868.rst:84 +#: 763f2c9457e84162b615aeaaa231ea22 a0e948ed9d9148318a8ee26cd8d19d2d +msgid "This example receives and displays data." +msgstr "此示例接收并显示数据。" + +#: ../../en/cap/lora868.rst:41 cf190a933b424339a61aba19333cfb7e +msgid "|cardputer_adv_lora868_cap_receiver_example.png|" +msgstr "" + +#: ../../en/refs/cap.lora868.ref:7 e61600c45bcf41a58f68791935ac9d45 +msgid "cardputer_adv_lora868_cap_receiver_example.png" +msgstr "" + +#: ../../en/cap/lora868.rst:48 ../../en/cap/lora868.rst:97 +#: 35f894aaabdb41f7862e30f6247b58b4 7232309f4ad2415384213d0acb7eaa3e +msgid "GPS Usage" +msgstr "GPS 使用" + +#: ../../en/cap/lora868.rst:50 c8f2ce3b1d66419d8453aa9cbc6f3bbc +msgid "Open the |cardputer_adv_lora868_cap_gps_example.m5f2| project in UiFlow2." +msgstr "在 UiFlow2 中打开 |cardputer_adv_lora868_cap_gps_example.m5f2| 项目。" + +#: ../../en/cap/lora868.rst:52 ../../en/cap/lora868.rst:99 +#: 0f9202a213634a049d4a95f54a5145c2 c4fac037813e4fa9a22d2c4733a5fdda +msgid "" +"This example demonstrates how to use the GPS functionality of the LoRa868" +" Cap." +msgstr "此示例演示如何使用 LoRa868 Cap 的 GPS 功能。" + +#: ../../en/cap/lora868.rst:56 3e14df7285a84dd29f6ba76624550419 +msgid "|cardputer_adv_lora868_cap_gps_example.png|" +msgstr "" + +#: ../../en/refs/cap.lora868.ref:6 6688f773a58a45ce97d8a361624a4bed +msgid "cardputer_adv_lora868_cap_gps_example.png" +msgstr "" + +#: ../../en/cap/lora868.rst:64 78ff4e370b3c4e71bcbdf70386e16f08 +msgid "MicroPython Example" +msgstr "MicroPython 示例" + +#: ../../en/cap/lora868.rst:71 ../../en/cap/lora868.rst:86 +#: ../../en/cap/lora868.rst:101 0ad45b6dcac14a5e8460a0e65cde6f54 +#: 0d1706ef56dd41bc9d86788fa4f7ca25 2421f386c7104b3198c6e42ba40ded92 +#: 59166f8d6c944a799a6ba94b40777495 638f7791e1674662b8e202b3bf02267e +#: 7251fbf779d04563995bab4a233e7f6a 828081e514154a7d98558f41faeebe71 +#: 8e52605c9d0848c6b5a7db22c52cea8a 8ee291743856497f91ee56ee57983bce +#: 912dd5d9928c48778079d4ae4ea62753 ba5e531e9af5494daf384f0337c2c5d1 +#: bbf3f60d25fd42fe9e077e8360ca8181 c378e33cfa374f1596e0378462c76694 +#: c868b01047c443cea0ad875df719a810 cap.lora868.GPSCap:12 +#: cap.lora868.LoRa868Cap:30 cap.lora868.LoRa868Cap.irq_triggered:10 +#: cap.lora868.LoRa868Cap.recv:15 cap.lora868.LoRa868Cap.send:14 +#: cap.lora868.LoRa868Cap.set_bw:11 cap.lora868.LoRa868Cap.set_coding_rate:9 +#: cap.lora868.LoRa868Cap.set_freq:9 cap.lora868.LoRa868Cap.set_irq_callback:10 +#: cap.lora868.LoRa868Cap.set_output_power:9 +#: cap.lora868.LoRa868Cap.set_preamble_len:9 cap.lora868.LoRa868Cap.set_sf:9 +#: cap.lora868.LoRa868Cap.set_syncword:9 cap.lora868.LoRa868Cap.sleep:9 +#: cap.lora868.LoRa868Cap.standby:9 cap.lora868.LoRa868Cap.start_recv:9 +#: db21dfba71b04a7084b780ffd568ca17 e019f1baff514cd7bc6aa9e65bebfe29 +#: f243ee5bc1fc469f864f7245cf38a654 fa67a8539b394989b7308a00034e8907 +#: fcf69824d08f43e2a34991d753aed65b of +msgid "MicroPython Code Block:" +msgstr "MicroPython 代码块:" + +#: ../../en/cap/lora868.rst:112 57c073f7194e4804a27519972f25f01a +msgid "**API**" +msgstr "API参考" + +#: ../../en/cap/lora868.rst:115 9e1a1d56ac6b43609c5aee60bba6e942 +msgid "class LoRa868Cap" +msgstr "" + +#: 27c676b6e4434881aaa7abfb74c57704 cap.lora868.LoRa868Cap:1 of +msgid "Bases: :py:class:`object`" +msgstr "" + +#: cap.lora868.LoRa868Cap:1 d0d49940a3d34368a3759841c6db36f8 of +msgid "Create a LoRa868Cap object." +msgstr "创建一个 LoRa868Cap 对象。" + +#: ../../en/cap/lora868.rst 1cfdf4f76732490a874ec487727d7fde +#: 31385428448742a6bbaf07736e949f06 3faa9099346544d3857d2cdab1a46414 +#: 4b768ecab6bc4da0ad85dbbfc2d09691 5d9bea755e41423cb631ffd90e180078 +#: b135da038dc04f7fbe72a7de20c9ba13 b199247f6263406294d43c39d540027c +#: cap.lora868.GPSCap cap.lora868.LoRa868Cap.recv cap.lora868.LoRa868Cap.send +#: cap.lora868.LoRa868Cap.set_bw cap.lora868.LoRa868Cap.set_coding_rate +#: cap.lora868.LoRa868Cap.set_freq cap.lora868.LoRa868Cap.set_irq_callback +#: cap.lora868.LoRa868Cap.set_output_power +#: cap.lora868.LoRa868Cap.set_preamble_len cap.lora868.LoRa868Cap.set_sf +#: cap.lora868.LoRa868Cap.set_syncword d4c374a73041488192f1f155f7866664 +#: e101ca42078b478a9a43d9615e409d73 ea15794f5bbd4bf3a1440a7e0ffe077a +#: fb435c2058914fddb2a1fe635c818838 ff7ec168ba124565907b9eb62b75ed18 of +msgid "Parameters" +msgstr "" + +#: 73a74c09804c46a09b8caca0d0f916c8 cap.lora868.LoRa868Cap:3 of +msgid "(RST) Reset pin number." +msgstr "(RST) 复位引脚号。" + +#: cap.lora868.LoRa868Cap:4 f2d1493234ee4c5da2465d08d30c04e4 of +msgid "(NSS) Chip select pin number." +msgstr "(NSS) 片选引脚号。" + +#: 00c150ab376449b295087d5e3b029967 cap.lora868.LoRa868Cap:5 of +msgid "(IRQ) Interrupt pin number." +msgstr "(IRQ) 中断引脚号。" + +#: 0a81597ffbf04b1b8c989dea79eac7df cap.lora868.LoRa868Cap:6 of +msgid "(BUSY) Busy pin number." +msgstr "(BUSY) 忙碌引脚号。" + +#: cap.lora868.LoRa868Cap:7 fb0e9e62f45741af80062f12a0d7f1d5 of +msgid "LoRa RF frequency in KHz, with a range of 850000 KHz to 930000 KHz." +msgstr "LoRa 射频频率,单位为 KHz,范围为 850000 KHz 至 930000 KHz。" + +#: 45fa434999bb412b8ce1a56727071dee cap.lora868.LoRa868Cap:8 of +msgid "" +"Bandwidth, options include: - ``\"7.8\"``: 7.8 KHz - ``\"10.4\"``: 10.4 " +"KHz - ``\"15.6\"``: 15.6 KHz - ``\"20.8\"``: 20.8 KHz - ``\"31.25\"``: " +"31.25 KHz - ``\"41.7\"``: 41.7 KHz - ``\"62.5\"``: 62.5 KHz - " +"``\"125\"``: 125 KHz - ``\"250\"``: 250 KHz - ``\"500\"``: 500 KHz" +msgstr "" +"带宽,选项包括: - ``\"7.8\"``: 7.8 KHz - ``\"10.4\"``: 10.4 KHz - ``\"15.6\"``: " +"15.6 KHz - ``\"20.8\"``: 20.8 KHz - ``\"31.25\"``: 31.25 KHz - " +"``\"41.7\"``: 41.7 KHz - ``\"62.5\"``: 62.5 KHz - ``\"125\"``: 125 KHz - " +"``\"250\"``: 250 KHz - ``\"500\"``: 500 KHz" + +#: abb8c157005b47edbd4a292602eb94b3 cap.lora868.LoRa868Cap:8 of +msgid "Bandwidth, options include:" +msgstr "带宽,选项包括:" + +#: 9ebe8f675f494b60bb0d9feb06010f25 cap.lora868.LoRa868Cap:10 of +msgid "``\"7.8\"``: 7.8 KHz" +msgstr "``\"7.8\"``: 7.8 KHz" + +#: 74c9256a042b49ff8678da7b4419cdfa cap.lora868.LoRa868Cap:11 of +msgid "``\"10.4\"``: 10.4 KHz" +msgstr "``\"10.4\"``: 10.4 KHz" + +#: 98acef579216413fbca0d6addd0b2f03 cap.lora868.LoRa868Cap:12 of +msgid "``\"15.6\"``: 15.6 KHz" +msgstr "``\"15.6\"``: 15.6 KHz" + +#: cap.lora868.LoRa868Cap:13 d1634fcd7bb744a1947a71f41a1e0a52 of +msgid "``\"20.8\"``: 20.8 KHz" +msgstr "``\"20.8\"``: 20.8 KHz" + +#: 5e8fd609b7884a07b41a49bd85882554 cap.lora868.LoRa868Cap:14 of +msgid "``\"31.25\"``: 31.25 KHz" +msgstr "``\"31.25\"``: 31.25 KHz" + +#: cap.lora868.LoRa868Cap:15 d7c3b1efa6e143e5988a40acded94413 of +msgid "``\"41.7\"``: 41.7 KHz" +msgstr "``\"41.7\"``: 41.7 KHz" + +#: 53d604cd968a462685cbed99caf3d3ce cap.lora868.LoRa868Cap:16 of +msgid "``\"62.5\"``: 62.5 KHz" +msgstr "``\"62.5\"``: 62.5 KHz" + +#: cap.lora868.LoRa868Cap:17 f96b79efc4f749138b8dbb349d21b885 of +msgid "``\"125\"``: 125 KHz" +msgstr "``\"125\"``: 125 KHz" + +#: 874e7169788d43aebdd1230fc331e171 cap.lora868.LoRa868Cap:18 of +msgid "``\"250\"``: 250 KHz" +msgstr "``\"250\"``: 250 KHz" + +#: 7e82338269e84418a4aec843a1e218ed cap.lora868.LoRa868Cap:19 of +msgid "``\"500\"``: 500 KHz" +msgstr "``\"500\"``: 500 KHz" + +#: cap.lora868.LoRa868Cap:20 e56309a298d3447cac538ce9372c5f2d of +msgid "" +"Spreading factor, range from 7 to 12. Higher spreading factors allow " +"reception of weaker signals but with slower data rates." +msgstr "扩频因子,范围从 7 到 12。较高的扩频因子可以接收到更弱的信号,但数据速率较慢。" + +#: cap.lora868.LoRa868Cap:21 eb86c666ef8040758bb8f4cab3c0be7c of +msgid "" +"Forward Error Correction (FEC) coding rate expressed as 4/N, with a range" +" from 5 to 8." +msgstr "前向纠错 (FEC) 编码率,表示为 4/N,范围从 5 到 8。" + +#: 719699436418407cae224933917ded3a cap.lora868.LoRa868Cap:22 of +msgid "Length of the preamble sequence in symbols, range from 0 to 255." +msgstr "前导码序列的长度,以符号为单位,范围从 0 到 255。" + +#: cap.lora868.LoRa868Cap:23 ed23aaff00fe4a99894de69036911eae of +msgid "Sync word to mark the start of the data frame, default is 0x12." +msgstr "用于标记数据帧开始的同步字,默认为 0x12。" + +#: 2d7e68f94a454b87938b6f313e0cb01b cap.lora868.LoRa868Cap:24 of +msgid "Output power in dBm, range from -9 to 22." +msgstr "输出功率,单位为 dBm,范围从 -9 到 22。" + +#: a1dbafc270db436587f9f4b74a9879ad cap.lora868.LoRa868Cap:28 of +msgid "|lora_init.png|" +msgstr "" + +#: ../../en/refs/cap.lora868.ref:26 e53b959bf8f34553b1a4a374d7eadf7d +msgid "lora_init.png" +msgstr "" + +#: 921814c22b0d4ccc8bcfead07df868e8 cap.lora868.LoRa868Cap.set_freq:1 of +msgid "Set frequency in kHz." +msgstr "设置频率,单位为 kHz。" + +#: 3e7842d42c2e45d1b779cf6441939a9b cap.lora868.LoRa868Cap.set_freq:3 of +msgid "Frequency in kHz (850000 ~ 930000), default is 868000." +msgstr "频率,单位为 kHz (850000 ~ 930000),默认为 868000。" + +#: 1aa11c4e6e8d4c92bf03620f135316ef 2d8f9e5a68c546988028f736d4d4ad88 +#: 2fc664dcb571411ba6a78411be80c15e 4817e698c17548dd944e00448bf7ece5 +#: 6dc994a9058d4265bca9c8a2bcd2e5c4 6e40ea17ffa345eea04e4a9d109664d0 +#: 791eac0a6b7b46acbbc1e0a0da9046fd 841a183f54004f34acf96e3015e4303e +#: 95e3af9014b844e0a78a1ed91ab78ef9 99fa59682b4441eba27f2b024ac4b6b8 +#: ad495e8d0d4e4bc49e81a34e13875bed b6371af775914ed8bfe396757ef6e1cf +#: cap.lora868.LoRa868Cap.irq_triggered cap.lora868.LoRa868Cap.recv +#: cap.lora868.LoRa868Cap.send cap.lora868.LoRa868Cap.set_bw +#: cap.lora868.LoRa868Cap.set_coding_rate cap.lora868.LoRa868Cap.set_freq +#: cap.lora868.LoRa868Cap.set_irq_callback +#: cap.lora868.LoRa868Cap.set_output_power +#: cap.lora868.LoRa868Cap.set_preamble_len cap.lora868.LoRa868Cap.set_sf +#: cap.lora868.LoRa868Cap.set_syncword cap.lora868.LoRa868Cap.sleep +#: cap.lora868.LoRa868Cap.standby cap.lora868.LoRa868Cap.start_recv +#: f08c510810dd402ea1016bdb7495cffd ff0d6c1c69ea4ead900243eb26abccbb of +msgid "Return type" +msgstr "" + +#: 6f1e2f4b4ae74093bc48631252a83172 cap.lora868.LoRa868Cap.set_freq:7 of +msgid "|set_freq.png|" +msgstr "" + +#: ../../en/refs/cap.lora868.ref:34 42b1b3bf00084141b2e5dc794d5a6647 +msgid "set_freq.png" +msgstr "" + +#: 78f1d93fa24c46ec8c6166cd5f4fa669 cap.lora868.LoRa868Cap.set_sf:1 of +msgid "Set spreading factor (SF)." +msgstr "设置扩频因子 (SF)。" + +#: 42fa4621cf5b4fa7ab96d2a3c86981f9 cap.lora868.LoRa868Cap.set_sf:3 of +msgid "Spreading factor (7 ~ 12)" +msgstr "扩频因子 (7 ~ 12)" + +#: 0386488f93c34451a223f993d80076a1 cap.lora868.LoRa868Cap.set_sf:7 of +msgid "|set_sf.png|" +msgstr "" + +#: ../../en/refs/cap.lora868.ref:38 bcf10b739e98476195e8423d0d63d688 +msgid "set_sf.png" +msgstr "" + +#: 2ded87ab8c3444819bf884969dc0c545 cap.lora868.LoRa868Cap.set_bw:1 of +msgid "Set bandwidth." +msgstr "设置带宽。" + +#: 6f23bf1010194472a18d6c305f91981d cap.lora868.LoRa868Cap.set_bw:3 of +msgid "" +"Bandwidth in kHz as string. Must be one of: '7.8', '10.4', '15.6', " +"'20.8', '31.25', '41.7', '62.5', '125', '250', '500'." +msgstr "" +"带宽,单位 kHz,字符串格式。必须是 " +"'7.8'、'10.4'、'15.6'、'20.8'、'31.25'、'41.7'、'62.5'、'125'、'250'、'500' 中的一个。" + +#: cap.lora868.LoRa868Cap.set_bw:9 ed38dae6cf344b21ade6a3b307ca39a1 of +msgid "|set_bw.png|" +msgstr "" + +#: ../../en/refs/cap.lora868.ref:32 72a4072e9e03451b914012ed02603524 +msgid "set_bw.png" +msgstr "" + +#: 7bbec3ae4d794c4683b68f3a29f0c8b4 cap.lora868.LoRa868Cap.set_coding_rate:1 of +msgid "Set coding rate." +msgstr "设置编码率。" + +#: 7246f864b47041aeb50b0d62cd10de6d cap.lora868.LoRa868Cap.set_coding_rate:3 of +msgid "Coding rate (5 ~ 8)" +msgstr "编码率 (5 ~ 8)" + +#: 5c59c3ed26a74d029acea5e34b38ed80 cap.lora868.LoRa868Cap.set_coding_rate:7 of +msgid "|set_coding_rate.png|" +msgstr "" + +#: ../../en/refs/cap.lora868.ref:33 14df14987f4640da9fcc06e1612d4861 +msgid "set_coding_rate.png" +msgstr "" + +#: 8e0dc356efa64210b4bad3ac7c5041e3 cap.lora868.LoRa868Cap.set_syncword:1 of +msgid "Set syncword." +msgstr "设置同步字。" + +#: 9dcb300f081e462b800542f15869b3ec cap.lora868.LoRa868Cap.set_syncword:3 of +msgid "Sync word (0 ~ 0xFF)" +msgstr "同步字 (0 ~ 0xFF)" + +#: 23ee4ae3b2a74626a98a35f20134cb97 cap.lora868.LoRa868Cap.set_syncword:7 of +msgid "|set_syncword.png|" +msgstr "" + +#: ../../en/refs/cap.lora868.ref:39 ea3df27e1d2f4b238ab205321f913414 +msgid "set_syncword.png" +msgstr "" + +#: a1f444cb3ce94619b7ff626cf039976e cap.lora868.LoRa868Cap.set_preamble_len:1 +#: of +msgid "Set preamble length." +msgstr "设置前导码长度。" + +#: 951f157ecd8c449f96f521d7933b1e9b cap.lora868.LoRa868Cap.set_preamble_len:3 +#: of +msgid "Preamble length, range: 0~255." +msgstr "前导码长度,范围:0~255。" + +#: 3f430966d4b048b0811d49fc7731d95b cap.lora868.LoRa868Cap.set_preamble_len:7 +#: of +msgid "|set_preamble_len.png|" +msgstr "" + +#: ../../en/refs/cap.lora868.ref:37 078e6d2e0c414c1eafcdf6f22ab5e1fb +msgid "set_preamble_len.png" +msgstr "" + +#: 2bcd6eed9b9a4178bf4075f3342f5c3f cap.lora868.LoRa868Cap.set_output_power:1 +#: of +msgid "Set output power in dBm." +msgstr "设置输出功率,单位 dBm。" + +#: bc6708e95ec749cfa86c2ae274ac1d88 cap.lora868.LoRa868Cap.set_output_power:3 +#: of +msgid "Output power in dBm (-9 ~ 22)" +msgstr "输出功率,单位 dBm (-9 ~ 22)" + +#: cap.lora868.LoRa868Cap.set_output_power:7 e8f5242d237f48f5858bb0a308981f00 +#: of +msgid "|set_output_power.png|" +msgstr "" + +#: ../../en/refs/cap.lora868.ref:36 049cc82aac49490896427d5197a1762e +msgid "set_output_power.png" +msgstr "" + +#: 439ee794c43744dfb0d4a61ab3b89378 cap.lora868.LoRa868Cap.send:1 of +msgid "Send data" +msgstr "发送数据" + +#: 8e6cfa9265114fe691b5532455b50151 cap.lora868.LoRa868Cap.send:3 of +msgid "The data to be sent." +msgstr "要发送的数据。" + +#: cap.lora868.LoRa868Cap.send:4 f0d94f9574804085bce2c619200842a0 of +msgid "" +"The timestamp in milliseconds when to send the data (optional). Default " +"is None." +msgstr "发送数据的时间戳(毫秒,可选)。默认为 None。" + +#: 1da9c4f9f4dd42ac9249c8566a6a720a 5631b9c49a9245d69476f944be398713 +#: a6712b76719c48739e62a67b64b9690c cap.lora868.LoRa868Cap.irq_triggered +#: cap.lora868.LoRa868Cap.recv cap.lora868.LoRa868Cap.send of +msgid "Returns" +msgstr "" + +#: 6e430a7e95374f199b40e0782b84b847 cap.lora868.LoRa868Cap.send:5 of +msgid "timestamp" +msgstr "时间戳" + +#: 214808357eb04bcd804de8e4c50e0337 cap.lora868.LoRa868Cap.send:8 of +msgid "Send a data packet and return the timestamp after the packet is sent." +msgstr "发送一个数据包,并在数据包发送后返回时间戳。" + +#: cap.lora868.LoRa868Cap.send:12 e56e511a3f964120b32fb441b08c548b of +msgid "|send.png|" +msgstr "" + +#: ../../en/refs/cap.lora868.ref:29 05a080e73ff24813a85d7c095bdba251 +msgid "send.png" +msgstr "" + +#: 6c363932eacf42da90381c5a5b5f6f74 cap.lora868.LoRa868Cap.recv:1 of +msgid "Receive data" +msgstr "接收数据" + +#: 36f24c0755c846048d8d7348a2b31a9f cap.lora868.LoRa868Cap.recv:3 of +msgid "Timeout in milliseconds (optional). Default is None." +msgstr "超时时间(毫秒,可选)。默认为 None。" + +#: 5b6f92191e0b43dbb9063c322b29eee4 cap.lora868.LoRa868Cap.recv:4 of +msgid "Length of the data to be read. Default is 0xFF." +msgstr "要读取的数据长度。默认为 0xFF。" + +#: 6025904091c34ddfb474d4ffb2c87777 cap.lora868.LoRa868Cap.recv:5 of +msgid "An instance of `RxPacket` (optional) to reuse." +msgstr "一个 `RxPacket` 实例(可选),用于重用。" + +#: 66c40ce376554dbe86ee6597acac951c cap.lora868.LoRa868Cap.recv:6 of +msgid "Received packet instance" +msgstr "接收到的数据包实例" + +#: 20eb222fc969467b982ac2a094a963bf cap.lora868.LoRa868Cap.recv:9 of +msgid "" +"Attempt to receive a LoRa packet. Returns `None` if timeout occurs, or " +"returns the received packet instance." +msgstr "尝试接收一个 LoRa 数据包。如果超时则返回 `None`,否则返回接收到的数据包实例。" + +#: 10d604a7a9c54ba9ae22ef7f6fd5815d cap.lora868.LoRa868Cap.recv:13 of +msgid "|recv.png|" +msgstr "" + +#: ../../en/refs/cap.lora868.ref:27 8a59f7b7cd3640ed9ced4fb3be8b09e7 +msgid "recv.png" +msgstr "" + +#: b1bdde3217eb40689b66152a39e8f7df cap.lora868.LoRa868Cap.start_recv:1 of +msgid "Start receive data" +msgstr "开始接收数据" + +#: 032b5b144e2b4e439547d31187e70961 cap.lora868.LoRa868Cap.start_recv:3 of +msgid "This method initiates the process to begin receiving data." +msgstr "该方法启动开始接收数据的过程。" + +#: 61cd1b870d7045fcbe9c88b8f950cb4b cap.lora868.LoRa868Cap.start_recv:7 of +msgid "|start_recv.png|" +msgstr "" + +#: ../../en/refs/cap.lora868.ref:44 e9c85dd0a0bd4d798869268a08684907 +msgid "start_recv.png" +msgstr "" + +#: cap.lora868.LoRa868Cap.set_irq_callback:1 e700ccbf7941441ebca56bafc00dc296 +#: of +msgid "Set the interrupt callback function to be executed on IRQ." +msgstr "设置在 IRQ 上执行的中断回调函数。" + +#: be3da2e9b5c548b7b3f91e4e406cd1d5 cap.lora868.LoRa868Cap.set_irq_callback:3 +#: of +msgid "" +"The callback function to be invoked when the interrupt is triggered. The " +"callback should not take any arguments and should return nothing." +msgstr "中断触发时要调用的回调函数。该回调函数不应接受任何参数,也不应返回任何内容。" + +#: 7711d8fef52c4a5fac870a225c9d2b9a cap.lora868.LoRa868Cap.set_irq_callback:8 +#: of +msgid "|set_irq_callback.png|" +msgstr "" + +#: ../../en/refs/cap.lora868.ref:35 a08010005bdc406dbe8564cef4c3c88d +msgid "set_irq_callback.png" +msgstr "" + +#: 18b35ac277bb48ff8485701964538fc9 cap.lora868.LoRa868Cap.standby:1 of +msgid "Set module to standby mode." +msgstr "将模块设置为待机模式。" + +#: 041518ea68c8409c9b0df36f9d6f76e2 cap.lora868.LoRa868Cap.standby:3 of +msgid "Puts the LoRa module into standby mode, consuming less power." +msgstr "将 LoRa 模块置于待机模式,功耗更低。" + +#: a67683068d7644f298eb16ca69d9db17 cap.lora868.LoRa868Cap.standby:7 of +msgid "|standby.png|" +msgstr "" + +#: ../../en/refs/cap.lora868.ref:43 14be92a02ac940d7b8d92b2ceeb41074 +msgid "standby.png" +msgstr "" + +#: 1af41342b12747809c4c571491fb5299 cap.lora868.LoRa868Cap.sleep:1 of +msgid "Set module to sleep mode." +msgstr "将模块设置为睡眠模式。" + +#: 8b3e8fa5baf14725bde34bc6b4d3eddc cap.lora868.LoRa868Cap.sleep:3 of +msgid "Reduces the power consumption by putting the module into deep sleep mode." +msgstr "通过将模块置于深度睡眠模式来降低功耗。" + +#: b60f32fdcf914843a65318703d80b2e2 cap.lora868.LoRa868Cap.sleep:7 of +msgid "|sleep.png|" +msgstr "" + +#: ../../en/refs/cap.lora868.ref:42 82b54cf6d3f144689699ced17751ce39 +msgid "sleep.png" +msgstr "" + +#: 3ce3dcec79574da1988361a3c31b72b2 cap.lora868.LoRa868Cap.irq_triggered:1 of +msgid "Check IRQ trigger." +msgstr "检查 IRQ 触发。" + +#: 622ea276637947c4a389219a506e765a cap.lora868.LoRa868Cap.irq_triggered:3 of +msgid "" +"Returns `True` if an interrupt service routine (ISR) has been triggered " +"since the last send or receive started." +msgstr "如果自上次发送或接收开始以来已触发中断服务例程 (ISR),则返回 `True`。" + +#: 655d0eebbc954efcb59db7c1054cba0d cap.lora868.LoRa868Cap.irq_triggered:8 of +msgid "|irq_triggered.png|" +msgstr "" + +#: ../../en/refs/cap.lora868.ref:25 2838084d97214dbe8b50316643518402 +msgid "irq_triggered.png" +msgstr "" + +#: ../../en/cap/lora868.rst:122 c802ccfb38804e288c0889619837b618 +msgid "class GPSCap" +msgstr "class GPSCap" + +#: 08a6aee9a353426cba30963164efa950 cap.lora868.GPSCap:1 of +msgid "Bases: :py:class:`~driver.atgm336h.ATGM336H`" +msgstr "基类: :py:class:`~driver.atgm336h.ATGM336H`" + +#: 649166fe01994896add71d2c814979bf cap.lora868.GPSCap:1 of +msgid "Initialize the GPSCap with a specific UART id and port for communication." +msgstr "使用特定的 UART ID 和端口初始化 GPSCap 以进行通信。" + +#: 979e57b5adc34660a9acc83722d03fd7 cap.lora868.GPSCap:3 of +msgid "The UART ID for communication with the GPS module. It can be 0, 1, or 2." +msgstr "与 GPS 模块通信的 UART ID。可以是 0、1 或 2。" + +#: 919918580ee34d379664a8d435caca3b cap.lora868.GPSCap:4 of +msgid "" +"The RX pin for UART communication. If None, uses default pin from board " +"definition." +msgstr "用于 UART 通信的 RX 引脚。如果没有,则使用开发板定义的默认引脚。" + +#: 83dc779e33714511967b0dead3768d40 cap.lora868.GPSCap:5 of +msgid "" +"The TX pin for UART communication. If None, uses default pin from board " +"definition." +msgstr "用于 UART 通信的 TX 引脚。如果没有,则使用板定义的默认引脚。" + +#: a1f31f130a83455591a64811535d5bfe cap.lora868.GPSCap:6 of +msgid "" +"The PPS (Pulse Per Second) pin, used for high-precision time " +"synchronization. Default is -1 (not used)." +msgstr "PPS(每秒脉冲)引脚,用于高精度时间同步。默认值为 -1(未使用)。" + +#: 71b264510b6d4550b9be0f72f2e6b454 cap.lora868.GPSCap:10 of +msgid "|gps_init.png|" +msgstr "" + +#: ../../en/refs/cap.lora868.ref:24 664e7afaf1ed41d8a289876b8737b6d9 +msgid "gps_init.png" +msgstr "" diff --git a/examples/cap/lora868/cardputer_adv_lora868_cap_gps_example.m5f2 b/examples/cap/lora868/cardputer_adv_lora868_cap_gps_example.m5f2 new file mode 100644 index 00000000..375c2de3 --- /dev/null +++ b/examples/cap/lora868/cardputer_adv_lora868_cap_gps_example.m5f2 @@ -0,0 +1 @@ +{"version":"V2.0","versionNumber":"V2.3.5","type":"cardputer-adv","components":[{"name":"screen","type":"screen","layer":0,"screenId":"builtin","screenName":"","id":"__cardputer-adv_screen","createTime":1758016559724,"x":0,"y":0,"width":240,"height":135,"backgroundColor":"#000000","size":0,"isSelected":true},{"name":"label0","type":"label","layer":1,"screenId":"builtin","screenName":"","id":"a1xFHopOnphQR!-a","createTime":1758016565697,"x":11,"y":16,"color":"#ffffff","backgroundColor":"#222222","text":"label0","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false,"width":58,"height":21},{"name":"label1","type":"label","layer":2,"screenId":"builtin","screenName":"","id":"vEGv!B=1hN-yz7oG","createTime":1758016566992,"x":11,"y":46,"color":"#ffffff","backgroundColor":"#222222","text":"label1","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false,"width":58,"height":21},{"name":"label2","type":"label","layer":3,"screenId":"builtin","screenName":"","id":"qa-ZXm10es*Brjz3","createTime":1758016582679,"x":10,"y":77,"color":"#ffffff","backgroundColor":"#222222","text":"label2","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false,"width":58,"height":21}],"resources":[{"hardware":["hardware_button","hardware_pin_button","imu","speaker","ir","matrixkeyboard","mic","sdcard"]},{"cap":["cap_lora868"]}],"units":[],"hats":[],"caps":[{"type":"cap_lora868","name":"cap_lora868","id":"mlGh-OJ8vJkq=tN^","createTime":1758016616842}],"bases":[],"i2cs":[],"blockly":"true2truelabel0Labellabel1Labellabel2Label","screen":[{"simulationName":"Built-in","type":"builtin","width":240,"height":135,"scale":0.43,"screenName":"","blockId":"","screenColorType":0,"id":"builtin","createTime":1758016559724}],"logicWhenNum":0,"customList":[]} \ No newline at end of file diff --git a/examples/cap/lora868/cardputer_adv_lora868_cap_gps_example.py b/examples/cap/lora868/cardputer_adv_lora868_cap_gps_example.py new file mode 100644 index 00000000..434e3c1c --- /dev/null +++ b/examples/cap/lora868/cardputer_adv_lora868_cap_gps_example.py @@ -0,0 +1,48 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import os, sys, io +import M5 +from M5 import * +from cap import GPSCap + + +label0 = None +label1 = None +label2 = None +cap_lora868 = None + + +def setup(): + global label0, label1, label2, cap_lora868 + + M5.begin() + Widgets.fillScreen(0x000000) + label0 = Widgets.Label("label0", 11, 16, 1.0, 0xFFFFFF, 0x222222, Widgets.FONTS.DejaVu18) + label1 = Widgets.Label("label1", 11, 46, 1.0, 0xFFFFFF, 0x222222, Widgets.FONTS.DejaVu18) + label2 = Widgets.Label("label2", 10, 77, 1.0, 0xFFFFFF, 0x222222, Widgets.FONTS.DejaVu18) + + cap_lora868 = GPSCap(id=2) + + +def loop(): + global label0, label1, label2, cap_lora868 + M5.update() + label0.setText(str(cap_lora868.get_latitude())) + label1.setText(str(cap_lora868.get_longitude())) + label2.setText(str(cap_lora868.get_gps_time())) + + +if __name__ == "__main__": + try: + setup() + while True: + loop() + except (Exception, KeyboardInterrupt) as e: + try: + from utility import print_error_msg + + print_error_msg(e) + except ImportError: + print("please update to latest firmware") diff --git a/examples/cap/lora868/cardputer_adv_lora868_cap_receiver_example.m5f2 b/examples/cap/lora868/cardputer_adv_lora868_cap_receiver_example.m5f2 new file mode 100644 index 00000000..0b499c6f --- /dev/null +++ b/examples/cap/lora868/cardputer_adv_lora868_cap_receiver_example.m5f2 @@ -0,0 +1 @@ +{"version":"V2.0","versionNumber":"V2.3.5","type":"cardputer-adv","components":[{"name":"screen","type":"screen","layer":0,"screenId":"builtin","screenName":"","id":"__cardputer-adv_screen","createTime":1758015037491,"x":0,"y":0,"width":240,"height":135,"backgroundColor":"#000000","size":0,"isSelected":true}],"resources":[{"hardware":["hardware_button","hardware_pin_button","imu","speaker","ir","matrixkeyboard","mic","sdcard"]},{"cap":["cap_lora868"]}],"units":[],"hats":[],"caps":[{"type":"cap_lora868","name":"cap_lora868","id":"fYOPO*ZR#c`K^uVB","createTime":1758015267597}],"bases":[],"i2cs":[],"blockly":"lora_datacur_timetime_bufrx_texttrue86800025088120x1210Widgets.FONTS.DejaVu1200Truetruelora_datacur_timetime_buftime_bufhello M5time_buf[time_bufhello M5time_bufFROM_STARTcur_time4time_bufhello M5time_buf:time_bufhello M5time_bufFROM_STARTcur_time5time_bufhello M5time_buf:time_bufhello M5time_bufFROM_STARTcur_time6time_bufhello M5time_buf] -> [17:32:00]->time_bufpalette#33ff33rx_textlora_data[17:32:00]->rx_textpalette#ffffff\\n","screen":[{"simulationName":"Built-in","type":"builtin","width":240,"height":135,"scale":0.43,"screenName":"","blockId":"","screenColorType":0,"id":"builtin","createTime":1758015037491}],"logicWhenNum":0,"customList":[]} \ No newline at end of file diff --git a/examples/cap/lora868/cardputer_adv_lora868_cap_receiver_example.py b/examples/cap/lora868/cardputer_adv_lora868_cap_receiver_example.py new file mode 100644 index 00000000..2e562201 --- /dev/null +++ b/examples/cap/lora868/cardputer_adv_lora868_cap_receiver_example.py @@ -0,0 +1,77 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import os, sys, io +import M5 +from M5 import * +import time +from cap import LoRa868Cap + + +cap_lora868 = None + + +lora_data = None +cur_time = None +time_buf = None +rx_text = None + + +def cap_lora868_receive_event(received_data): + global cap_lora868, lora_data, cur_time, time_buf, rx_text + lora_data = received_data + cur_time = time.gmtime() + time_buf = "" + time_buf = str(time_buf) + str("[") + time_buf = str(time_buf) + str((cur_time[3])) + time_buf = str(time_buf) + str(":") + time_buf = str(time_buf) + str((cur_time[4])) + time_buf = str(time_buf) + str(":") + time_buf = str(time_buf) + str((cur_time[5])) + time_buf = str(time_buf) + str("] -> ") + M5.Lcd.print(time_buf, 0x33FF33) + rx_text = lora_data.decode() + M5.Lcd.print(rx_text, 0xFFFFFF) + M5.Lcd.printf("\n") + + +def setup(): + global cap_lora868, lora_data, cur_time, time_buf, rx_text + + M5.begin() + Widgets.fillScreen(0x000000) + + cap_lora868 = LoRa868Cap( + freq_khz=868000, + bw="250", + sf=8, + coding_rate=8, + preamble_len=12, + syncword=0x12, + output_power=10, + ) + cap_lora868.set_irq_callback(cap_lora868_receive_event) + M5.Lcd.setFont(Widgets.FONTS.DejaVu12) + M5.Lcd.setCursor(0, 0) + M5.Lcd.setTextScroll(True) + cap_lora868.start_recv() + + +def loop(): + global cap_lora868, lora_data, cur_time, time_buf, rx_text + M5.update() + + +if __name__ == "__main__": + try: + setup() + while True: + loop() + except (Exception, KeyboardInterrupt) as e: + try: + from utility import print_error_msg + + print_error_msg(e) + except ImportError: + print("please update to latest firmware") diff --git a/examples/cap/lora868/cardputer_adv_lora868_cap_sender_example.m5f2 b/examples/cap/lora868/cardputer_adv_lora868_cap_sender_example.m5f2 new file mode 100644 index 00000000..eccd144d --- /dev/null +++ b/examples/cap/lora868/cardputer_adv_lora868_cap_sender_example.m5f2 @@ -0,0 +1 @@ +{"version":"V2.0","versionNumber":"V2.3.5","type":"cardputer-adv","components":[{"name":"screen","type":"screen","layer":0,"screenId":"builtin","screenName":"","id":"__cardputer-adv_screen","createTime":1758010843982,"x":0,"y":0,"width":240,"height":135,"backgroundColor":"#000000","size":0,"isSelected":true}],"resources":[{"hardware":["hardware_button","hardware_pin_button","imu","speaker","ir","matrixkeyboard","mic","sdcard"]},{"cap":["cap_lora868"]}],"units":[],"hats":[],"caps":[{"type":"cap_lora868","name":"cap_lora868","id":"a+n!c4-VxB1rPc$r","createTime":1758010850492}],"bases":[],"i2cs":[],"blockly":"keycodekeybuftrue86800025088120x1210Widgets.FONTS.DejaVu12palette#ffffffpalette#00000000>>> buftruekeycodeANDGTEkeycode0x20LTEkeycode0x7ekey65keycodebufhello M5bufkeyhello M5keyEQkeycodeENTERbufbufpalette#00000000\\n>>> ","screen":[{"simulationName":"Built-in","type":"builtin","width":240,"height":135,"scale":0.43,"screenName":"","blockId":"","screenColorType":0,"id":"builtin","createTime":1758010843981}],"logicWhenNum":0,"customList":[]} \ No newline at end of file diff --git a/examples/cap/lora868/cardputer_adv_lora868_cap_sender_example.py b/examples/cap/lora868/cardputer_adv_lora868_cap_sender_example.py new file mode 100644 index 00000000..8dd35a92 --- /dev/null +++ b/examples/cap/lora868/cardputer_adv_lora868_cap_sender_example.py @@ -0,0 +1,78 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import os, sys, io +import M5 +from M5 import * +from hardware import MatrixKeyboard +from cap import LoRa868Cap +from unit import KeyCode + + +kb = None +cap_lora868 = None + + +keycode = None +key = None +buf = None + + +def kb_pressed_event(kb_0): + global kb, cap_lora868, keycode, key, buf + keycode = kb.get_key() + if keycode >= 0x20 and keycode <= 0x7E: + key = chr(keycode) + buf = str(buf) + str(key) + M5.Lcd.printf(key) + elif keycode == (KeyCode.KEYCODE_ENTER): + cap_lora868.send(buf, None) + buf = "" + M5.Lcd.fillScreen(0x000000) + M5.Lcd.setCursor(0, 0) + M5.Lcd.printf("\n") + M5.Lcd.printf(">>> ") + + +def setup(): + global kb, cap_lora868, keycode, key, buf + + M5.begin() + Widgets.fillScreen(0x000000) + + cap_lora868 = LoRa868Cap( + freq_khz=868000, + bw="250", + sf=8, + coding_rate=8, + preamble_len=12, + syncword=0x12, + output_power=10, + ) + kb = MatrixKeyboard() + kb.set_callback(kb_pressed_event) + M5.Lcd.setFont(Widgets.FONTS.DejaVu12) + M5.Lcd.setTextColor(0xFFFFFF, 0x000000) + M5.Lcd.setCursor(0, 0) + M5.Lcd.printf(">>> ") + buf = "" + + +def loop(): + global kb, cap_lora868, keycode, key, buf + M5.update() + + +if __name__ == "__main__": + try: + setup() + while True: + loop() + except (Exception, KeyboardInterrupt) as e: + try: + from utility import print_error_msg + + print_error_msg(e) + except ImportError: + print("please update to latest firmware") diff --git a/m5stack/libs/cap/__init__.py b/m5stack/libs/cap/__init__.py new file mode 100644 index 00000000..15cbf3a0 --- /dev/null +++ b/m5stack/libs/cap/__init__.py @@ -0,0 +1,17 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +_attrs = { + "LoRa868Cap": "lora868", + "GPSCap": "lora868", +} + + +def __getattr__(attr): + mod = _attrs.get(attr, None) + if mod is None: + raise AttributeError(attr) + value = getattr(__import__(mod, None, None, True, 1), attr) + globals()[attr] = value + return value diff --git a/m5stack/libs/cap/lora868.py b/m5stack/libs/cap/lora868.py new file mode 100644 index 00000000..c65d87b6 --- /dev/null +++ b/m5stack/libs/cap/lora868.py @@ -0,0 +1,500 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import M5 +import machine +from lora import SX1262 +from lora import RxPacket +from driver.atgm336h import ATGM336H +import sys +import micropython + +from collections import namedtuple + +if sys.platform != "esp32": + from typing import Literal + +""" +IODescriptor = namedtuple( + "IODescriptor", ["pin", "func"] +) + +schema = ( + IODescriptor(3, "GPIO"), IODescriptor(-1, "POWER"), + IODescriptor(4, "GPIO"), IODescriptor(-1, "POWER"), + IODescriptor(6, "GPIO"), IODescriptor(-1, "POWER"), + IODescriptor(40, "SPI_SCK"), IODescriptor(8, "I2C_SDA"), + IODescriptor(14, "SPI_MOSI"), IODescriptor(9, "I2C_SCL"), + IODescriptor(39, "SPI_MISO"), IODescriptor(13, "GPIO"), + IODescriptor(5, "GPIO"), IODescriptor(15, "GPIO"), +) + +""" + +PortDescriptor = namedtuple( + "PortDescriptor", + [ + "reset", + "intr", + "busy", + "spi_sck", + "spi_mosi", + "spi_miso", + "spi_cs", + "i2c_sda", + "i2c_scl", + "uart_rx", + "uart_tx", + ], +) + +_port_table = { + M5.BOARD.M5CardputerADV: PortDescriptor( + reset=3, + intr=4, + busy=6, + spi_sck=40, + spi_mosi=14, + spi_miso=39, + spi_cs=5, + i2c_sda=8, + i2c_scl=9, + uart_rx=15, + uart_tx=13, + ), +} + + +class LoRa868Cap: + """Create a LoRa868Cap object. + + :param int pin_rst: (RST) Reset pin number. + :param int pin_cs: (NSS) Chip select pin number. + :param int pin_irq: (IRQ) Interrupt pin number. + :param int pin_busy: (BUSY) Busy pin number. + :param int freq_khz: LoRa RF frequency in KHz, with a range of 850000 KHz to 930000 KHz. + :param str bw: Bandwidth, options include: + + - ``"7.8"``: 7.8 KHz + - ``"10.4"``: 10.4 KHz + - ``"15.6"``: 15.6 KHz + - ``"20.8"``: 20.8 KHz + - ``"31.25"``: 31.25 KHz + - ``"41.7"``: 41.7 KHz + - ``"62.5"``: 62.5 KHz + - ``"125"``: 125 KHz + - ``"250"``: 250 KHz + - ``"500"``: 500 KHz + :param int sf: Spreading factor, range from 7 to 12. Higher spreading factors allow reception of weaker signals but with slower data rates. + :param int coding_rate: Forward Error Correction (FEC) coding rate expressed as 4/N, with a range from 5 to 8. + :param int preamble_len: Length of the preamble sequence in symbols, range from 0 to 255. + :param int syncword: Sync word to mark the start of the data frame, default is 0x12. + :param int output_power: Output power in dBm, range from -9 to 22. + + UiFlow2 Code Block: + + |lora_init.png| + + MicroPython Code Block: + + .. code-block:: python + + from cap import LoRa868Cap + + cap_lora868_0 = LoRa868Cap(5, 1, 10, 2, 868000, '250', 8, 8, 12, 0x12, 10) + """ + + # Valid bandwidth + BANDWIDTHS = ( + "7.8", + "10.4", + "15.6", + "20.8", + "31.25", + "41.7", + "62.5", + "125", + "250", + "500", + ) + + def __init__( + self, + freq_khz: int = 868000, + bw: str = "250", + sf: int = 8, + coding_rate: int = 8, + preamble_len: int = 12, + syncword: int = 0x12, + output_power: int = 10, + ) -> None: + self._port = _port_table.get(M5.getBoard()) + if self._port is None: + raise NotImplementedError("LoRa CAP is not supported on this board") + + self._validate_range(sf, 6, 12) + self._validate_range(coding_rate, 5, 8) + if bw not in self.BANDWIDTHS: + raise ValueError(f"Invalid bandwidth {bw}") + + self._i2c = machine.I2C( + 0, + scl=machine.Pin(self._port.i2c_scl), + sda=machine.Pin(self._port.i2c_sda), + freq=100000, + ) + self._spi = machine.SPI( + 1, + baudrate=1000000, + polarity=0, + phase=0, + sck=machine.Pin(self._port.spi_sck), + mosi=machine.Pin(self._port.spi_mosi), + miso=machine.Pin(self._port.spi_miso), + ) + self._cs = machine.Pin(self._port.spi_cs, machine.Pin.OUT, value=1) + self._reset = machine.Pin(self._port.reset, machine.Pin.OUT, value=1) + self._intr = machine.Pin(self._port.intr, machine.Pin.IN) + self._busy = machine.Pin(self._port.busy, machine.Pin.IN) + + lora_cfg = { + "freq_khz": freq_khz, + "sf": sf, + "bw": bw, # kHz + "coding_rate": coding_rate, + "syncword": syncword, + "preamble_len": preamble_len, + "output_power": output_power, # -9dBm ~ 22dBm + } + + self.modem = SX1262( + spi=self._spi, + reset=self._reset, + cs=self._cs, + busy=self._busy, + dio1=self._intr, + dio3_tcxo_millivolts=3300, # 3300mV + lora_cfg=lora_cfg, + ) + + self.irq_callback = None + + def _validate_range(self, value, min, max): + if value < min or value > max: + raise ValueError(f"Value {value} out of range {min} to {max}") + + def set_freq(self, freq_khz: int = 868000) -> None: + """Set frequency in kHz. + + :param int freq_khz: Frequency in kHz (850000 ~ 930000), default is 868000. + + UiFlow2 Code Block: + + |set_freq.png| + + MicroPython Code Block: + + .. code-block:: python + + module_lora868v12_0.set_freq(868000) + """ + self._validate_range(freq_khz, 850000, 930000) + lora_cfg = {"freq_khz": freq_khz} + self.modem.configure(lora_cfg) + + def set_sf(self, sf: int) -> None: + """Set spreading factor (SF). + + :param int sf: Spreading factor (7 ~ 12) + + UiFlow2 Code Block: + + |set_sf.png| + + MicroPython Code Block: + + .. code-block:: python + + module_lora868v12_0.set_sf(7) + """ + self._validate_range(sf, 7, 12) + lora_cfg = {"sf": sf} + self.modem.configure(lora_cfg) + + def set_bw(self, bw: str) -> None: + """Set bandwidth. + + :param str bw: Bandwidth in kHz as string. Must be one of: + '7.8', '10.4', '15.6', '20.8', '31.25', '41.7', + '62.5', '125', '250', '500'. + + UiFlow2 Code Block: + + |set_bw.png| + + MicroPython Code Block: + + .. code-block:: python + + module_lora868v12_0.set_bw(bw) + """ + if bw not in self.BANDWIDTHS: + raise ValueError(f"Invalid bandwidth '{bw}', must be one of {self.BANDWIDTHS}") + lora_cfg = {"bw": bw} + self.modem.configure(lora_cfg) + + def set_coding_rate(self, coding_rate: int) -> None: + """Set coding rate. + + :param int coding_rate: Coding rate (5 ~ 8) + + UiFlow2 Code Block: + + |set_coding_rate.png| + + MicroPython Code Block: + + .. code-block:: python + + module_lora868v12_0.set_coding_rate(coding_rate) + """ + self._validate_range(coding_rate, 5, 8) + lora_cfg = {"coding_rate": coding_rate} + self.modem.configure(lora_cfg) + + def set_syncword(self, syncword: int) -> None: + """Set syncword. + + :param int syncword: Sync word (0 ~ 0xFF) + + UiFlow2 Code Block: + + |set_syncword.png| + + MicroPython Code Block: + + .. code-block:: python + + module_lora868v12_0.set_syncword(syncword) + """ + self._validate_range(syncword, 0, 0xFF) + lora_cfg = {"syncword": syncword} + self.modem.configure(lora_cfg) + + def set_preamble_len(self, preamble_len: int) -> None: + """Set preamble length. + + :param int preamble_len: Preamble length, range: 0~255. + + UiFlow2 Code Block: + + |set_preamble_len.png| + + MicroPython Code Block: + + .. code-block:: python + + module_lora868v12_0.set_preamble_len(preamble_len) + """ + self._validate_range(preamble_len, 6, 65535) + lora_cfg = {"preamble_len": preamble_len} + self.modem.configure(lora_cfg) + + def set_output_power(self, output_power: int) -> None: + """Set output power in dBm. + + :param int output_power: Output power in dBm (-9 ~ 22) + + UiFlow2 Code Block: + + |set_output_power.png| + + MicroPython Code Block: + + .. code-block:: python + + module_lora868v12_0.set_output_power(output_power) + """ + self._validate_range(output_power, -9, 22) + lora_cfg = {"output_power": output_power} + self.modem.configure(lora_cfg) + + def send(self, packet: str | list | tuple | int | bytearray, tx_at_ms: int = None) -> int: + """Send data + + :param str | list | tuple | int | bytearray packet: The data to be sent. + :param int tx_at_ms: The timestamp in milliseconds when to send the data (optional). Default is None. + :returns: timestamp + :rtype: int + + Send a data packet and return the timestamp after the packet is sent. + + UiFlow2 Code Block: + + |send.png| + + MicroPython Code Block: + + .. code-block:: python + + module_lora868v12_0.send() + """ + if isinstance(packet, str): + packet = bytes(packet, "utf-8") + elif isinstance(packet, list | tuple): + packet = bytes(packet) + elif isinstance(packet, int): + packet = bytes([packet]) + return self.modem.send(packet, tx_at_ms) + + def recv( + self, timeout_ms: int = None, rx_length: int = 0xFF, rx_packet: RxPacket = None + ) -> RxPacket: + """Receive data + + :param int timeout_ms: Timeout in milliseconds (optional). Default is None. + :param int rx_length: Length of the data to be read. Default is 0xFF. + :param RxPacket rx_packet: An instance of `RxPacket` (optional) to reuse. + :returns: Received packet instance + :rtype: RxPacket + + Attempt to receive a LoRa packet. Returns `None` if timeout occurs, or returns the received packet instance. + + UiFlow2 Code Block: + + |recv.png| + + MicroPython Code Block: + + .. code-block:: python + + data = module_lora868v12_0.recv() + """ + return self.modem.recv(timeout_ms, rx_length, rx_packet) + + def start_recv(self) -> None: + """Start receive data + + This method initiates the process to begin receiving data. + + UiFlow2 Code Block: + + |start_recv.png| + + MicroPython Code Block: + + .. code-block:: python + + module_lora868v12_0.start_recv() + """ + self.modem.start_recv(continuous=True) + + def set_irq_callback(self, callback) -> None: + """Set the interrupt callback function to be executed on IRQ. + + :param callback: The callback function to be invoked when the interrupt is triggered. + The callback should not take any arguments and should return nothing. + + UiFlow2 Code Block: + + |set_irq_callback.png| + + MicroPython Code Block: + + .. code-block:: python + + module_lora868v12_0.set_irq_callback() + """ + self.irq_callback = callback + + def _irq_callback(): + if self.irq_callback: + micropython.schedule(self.irq_callback, self.modem.poll_recv()) + + self.modem.set_irq_callback(_irq_callback) + + def standby(self) -> None: + """Set module to standby mode. + + Puts the LoRa module into standby mode, consuming less power. + + UiFlow2 Code Block: + + |standby.png| + + MicroPython Code Block: + + .. code-block:: python + + module_lora868v12_0.standby() + """ + self.modem.standby() + + def sleep(self) -> None: + """Set module to sleep mode. + + Reduces the power consumption by putting the module into deep sleep mode. + + UiFlow2 Code Block: + + |sleep.png| + + MicroPython Code Block: + + .. code-block:: python + + module_lora868v12_0.sleep() + """ + self.modem.sleep() + + def irq_triggered(self) -> bool: + """Check IRQ trigger. + + :returns: Returns `True` if an interrupt service routine (ISR) has been triggered since the last send or receive started. + :rtype: bool + + UiFlow2 Code Block: + + |irq_triggered.png| + + MicroPython Code Block: + + .. code-block:: python + + module_lora868v12_0.irq_triggered() + """ + return self.modem.irq_triggered() + + def deinit(self) -> None: + self.sleep() + + +class GPSCap(ATGM336H): + """Initialize the GPSCap with a specific UART id and port for communication. + + :param int id: The UART ID for communication with the GPS module. It can be 0, 1, or 2. + :param int rx: The RX pin for UART communication. If None, uses default pin from board definition. + :param int tx: The TX pin for UART communication. If None, uses default pin from board definition. + :param int pps: The PPS (Pulse Per Second) pin, used for high-precision time synchronization. Default is -1 (not used). + + UiFlow2 Code Block: + + |gps_init.png| + + MicroPython Code Block: + + .. code-block:: python + + from cap import GPSCap + gps_0 = GPSCap(id=2) + """ + + def __init__(self, id: Literal[0, 1, 2] = 1, rx: int = None, tx: int = None, pps: int = -1): + self._port = _port_table.get(M5.getBoard()) + if rx is None: + rx = self._port.uart_rx + if tx is None: + tx = self._port.uart_tx + + super().__init__(id, tx, rx, pps) diff --git a/m5stack/libs/cap/manifest.py b/m5stack/libs/cap/manifest.py new file mode 100644 index 00000000..f41dd4e1 --- /dev/null +++ b/m5stack/libs/cap/manifest.py @@ -0,0 +1,13 @@ +# SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +package( + "cap", + ( + "__init__.py", + "lora868.py", + ), + base_path="..", + opt=0, +) diff --git a/m5stack/libs/hardware/keyboard/__init__.py b/m5stack/libs/hardware/keyboard/__init__.py index 5b8a2ade..cfe9a764 100644 --- a/m5stack/libs/hardware/keyboard/__init__.py +++ b/m5stack/libs/hardware/keyboard/__init__.py @@ -610,6 +610,8 @@ def _ascii_handler(self, events): self._keyevent_converter, self._modifier_mask, self._fn_state, self._is_caps_locked ) if self._keyevent_callback and keyevent.state: + # append to the key events list + keyevent.state and self._keyevents.append(keyevent) micropython.schedule( self._keyevent_callback, self, # (self, keyevent.keycode, keyevent.state) diff --git a/m5stack/libs/manifest.py b/m5stack/libs/manifest.py index 78fa6450..b6de8e92 100644 --- a/m5stack/libs/manifest.py +++ b/m5stack/libs/manifest.py @@ -4,6 +4,7 @@ include("base/manifest.py") include("bleuart/manifest.py") +include("cap/manifest.py") include("driver/manifest.py") include("ezdata/manifest.py") include("hardware/manifest.py") From 4a684d7bcc53f001227aa9a61822f8c0af51e7a4 Mon Sep 17 00:00:00 2001 From: lbuque <1102390310@qq.com> Date: Wed, 17 Sep 2025 14:46:45 +0800 Subject: [PATCH 261/322] libs: Remove unnecessary libraries from firmware. Signed-off-by: lbuque <1102390310@qq.com> --- m5stack/boards/M5STACK_AirQ/manifest.py | 1 + m5stack/boards/M5STACK_AtomS3/manifest.py | 2 ++ m5stack/boards/M5STACK_AtomS3R/manifest.py | 2 ++ .../boards/M5STACK_AtomS3R_CAM/manifest.py | 2 ++ m5stack/boards/M5STACK_AtomS3U/manifest.py | 1 + .../boards/M5STACK_AtomS3_Lite/manifest.py | 2 ++ m5stack/boards/M5STACK_AtomU/manifest.py | 1 + m5stack/boards/M5STACK_Atom_Echo/manifest.py | 2 ++ m5stack/boards/M5STACK_Atom_Lite/manifest.py | 2 ++ .../boards/M5STACK_Atom_Matrix/manifest.py | 2 ++ m5stack/boards/M5STACK_Basic/manifest.py | 2 ++ m5stack/boards/M5STACK_Basic_4MB/manifest.py | 2 ++ m5stack/boards/M5STACK_Capsule/manifest.py | 1 + m5stack/boards/M5STACK_Cardputer/manifest.py | 1 + .../boards/M5STACK_CardputerADV/manifest.py | 2 ++ m5stack/boards/M5STACK_Core2/manifest.py | 2 ++ m5stack/boards/M5STACK_CoreInk/manifest.py | 2 ++ m5stack/boards/M5STACK_CoreS3/manifest.py | 3 +++ m5stack/boards/M5STACK_Dial/manifest.py | 1 + m5stack/boards/M5STACK_DinMeter/manifest.py | 1 + m5stack/boards/M5STACK_Fire/manifest.py | 2 ++ m5stack/boards/M5STACK_NanoC6/manifest.py | 1 + m5stack/boards/M5STACK_Paper/manifest.py | 1 + m5stack/boards/M5STACK_PaperS3/manifest.py | 1 + m5stack/boards/M5STACK_StamPLC/manifest.py | 1 + m5stack/boards/M5STACK_StampS3/manifest.py | 1 + m5stack/boards/M5STACK_Stamp_PICO/manifest.py | 1 + m5stack/boards/M5STACK_Station/manifest.py | 1 + m5stack/boards/M5STACK_StickC/manifest.py | 2 ++ .../boards/M5STACK_StickC_PLUS/manifest.py | 2 ++ .../boards/M5STACK_StickC_PLUS2/manifest.py | 2 ++ m5stack/boards/M5STACK_Tab5/manifest.py | 3 +++ m5stack/boards/M5STACK_Tough/manifest.py | 2 ++ m5stack/boards/manifest_m5stack.py | 2 +- m5stack/libs/manifest_basic.py | 25 +++++++++++++++++++ 35 files changed, 80 insertions(+), 1 deletion(-) create mode 100644 m5stack/libs/manifest_basic.py diff --git a/m5stack/boards/M5STACK_AirQ/manifest.py b/m5stack/boards/M5STACK_AirQ/manifest.py index ae9cbc65..f73a47c1 100644 --- a/m5stack/boards/M5STACK_AirQ/manifest.py +++ b/m5stack/boards/M5STACK_AirQ/manifest.py @@ -3,3 +3,4 @@ # SPDX-License-Identifier: MIT include("$(MPY_DIR)/../m5stack/modules/startup/manifest_airq.py") +include("$(MPY_DIR)/../m5stack/libs/unit/manifest.py") diff --git a/m5stack/boards/M5STACK_AtomS3/manifest.py b/m5stack/boards/M5STACK_AtomS3/manifest.py index f28377b9..2d18ef78 100644 --- a/m5stack/boards/M5STACK_AtomS3/manifest.py +++ b/m5stack/boards/M5STACK_AtomS3/manifest.py @@ -3,3 +3,5 @@ # SPDX-License-Identifier: MIT include("$(MPY_DIR)/../m5stack/modules/startup/manifest_atoms3.py") +include("$(MPY_DIR)/../m5stack/libs/base/manifest.py") +include("$(MPY_DIR)/../m5stack/libs/unit/manifest.py") diff --git a/m5stack/boards/M5STACK_AtomS3R/manifest.py b/m5stack/boards/M5STACK_AtomS3R/manifest.py index 16965ca0..d1096847 100644 --- a/m5stack/boards/M5STACK_AtomS3R/manifest.py +++ b/m5stack/boards/M5STACK_AtomS3R/manifest.py @@ -3,3 +3,5 @@ # SPDX-License-Identifier: MIT include("$(MPY_DIR)/../m5stack/modules/startup/manifest_atoms3r.py") +include("$(MPY_DIR)/../m5stack/libs/base/manifest.py") +include("$(MPY_DIR)/../m5stack/libs/unit/manifest.py") diff --git a/m5stack/boards/M5STACK_AtomS3R_CAM/manifest.py b/m5stack/boards/M5STACK_AtomS3R_CAM/manifest.py index bb721620..3e5351fc 100644 --- a/m5stack/boards/M5STACK_AtomS3R_CAM/manifest.py +++ b/m5stack/boards/M5STACK_AtomS3R_CAM/manifest.py @@ -3,3 +3,5 @@ # SPDX-License-Identifier: MIT include("$(MPY_DIR)/../m5stack/modules/startup/manifest_atoms3r_cam.py") +include("$(MPY_DIR)/../m5stack/libs/base/manifest.py") +include("$(MPY_DIR)/../m5stack/libs/unit/manifest.py") diff --git a/m5stack/boards/M5STACK_AtomS3U/manifest.py b/m5stack/boards/M5STACK_AtomS3U/manifest.py index 8ad46d81..5341fc6e 100644 --- a/m5stack/boards/M5STACK_AtomS3U/manifest.py +++ b/m5stack/boards/M5STACK_AtomS3U/manifest.py @@ -3,3 +3,4 @@ # SPDX-License-Identifier: MIT include("$(MPY_DIR)/../m5stack/modules/startup/manifest_atoms3u.py") +include("$(MPY_DIR)/../m5stack/libs/unit/manifest.py") diff --git a/m5stack/boards/M5STACK_AtomS3_Lite/manifest.py b/m5stack/boards/M5STACK_AtomS3_Lite/manifest.py index ad769216..98291c07 100644 --- a/m5stack/boards/M5STACK_AtomS3_Lite/manifest.py +++ b/m5stack/boards/M5STACK_AtomS3_Lite/manifest.py @@ -3,3 +3,5 @@ # SPDX-License-Identifier: MIT include("$(MPY_DIR)/../m5stack/modules/startup/manifest_atoms3lite.py") +include("$(MPY_DIR)/../m5stack/libs/base/manifest.py") +include("$(MPY_DIR)/../m5stack/libs/unit/manifest.py") diff --git a/m5stack/boards/M5STACK_AtomU/manifest.py b/m5stack/boards/M5STACK_AtomU/manifest.py index ad769216..738c6a3a 100644 --- a/m5stack/boards/M5STACK_AtomU/manifest.py +++ b/m5stack/boards/M5STACK_AtomU/manifest.py @@ -3,3 +3,4 @@ # SPDX-License-Identifier: MIT include("$(MPY_DIR)/../m5stack/modules/startup/manifest_atoms3lite.py") +include("$(MPY_DIR)/../m5stack/libs/unit/manifest.py") diff --git a/m5stack/boards/M5STACK_Atom_Echo/manifest.py b/m5stack/boards/M5STACK_Atom_Echo/manifest.py index ad769216..98291c07 100644 --- a/m5stack/boards/M5STACK_Atom_Echo/manifest.py +++ b/m5stack/boards/M5STACK_Atom_Echo/manifest.py @@ -3,3 +3,5 @@ # SPDX-License-Identifier: MIT include("$(MPY_DIR)/../m5stack/modules/startup/manifest_atoms3lite.py") +include("$(MPY_DIR)/../m5stack/libs/base/manifest.py") +include("$(MPY_DIR)/../m5stack/libs/unit/manifest.py") diff --git a/m5stack/boards/M5STACK_Atom_Lite/manifest.py b/m5stack/boards/M5STACK_Atom_Lite/manifest.py index ad769216..98291c07 100644 --- a/m5stack/boards/M5STACK_Atom_Lite/manifest.py +++ b/m5stack/boards/M5STACK_Atom_Lite/manifest.py @@ -3,3 +3,5 @@ # SPDX-License-Identifier: MIT include("$(MPY_DIR)/../m5stack/modules/startup/manifest_atoms3lite.py") +include("$(MPY_DIR)/../m5stack/libs/base/manifest.py") +include("$(MPY_DIR)/../m5stack/libs/unit/manifest.py") diff --git a/m5stack/boards/M5STACK_Atom_Matrix/manifest.py b/m5stack/boards/M5STACK_Atom_Matrix/manifest.py index fd6ce256..690f891a 100644 --- a/m5stack/boards/M5STACK_Atom_Matrix/manifest.py +++ b/m5stack/boards/M5STACK_Atom_Matrix/manifest.py @@ -3,3 +3,5 @@ # SPDX-License-Identifier: MIT include("$(MPY_DIR)/../m5stack/modules/startup/manifest_atommatrix.py") +include("$(MPY_DIR)/../m5stack/libs/base/manifest.py") +include("$(MPY_DIR)/../m5stack/libs/unit/manifest.py") diff --git a/m5stack/boards/M5STACK_Basic/manifest.py b/m5stack/boards/M5STACK_Basic/manifest.py index 581fd386..0a829fe6 100644 --- a/m5stack/boards/M5STACK_Basic/manifest.py +++ b/m5stack/boards/M5STACK_Basic/manifest.py @@ -3,3 +3,5 @@ # SPDX-License-Identifier: MIT include("$(MPY_DIR)/../m5stack/modules/startup/manifest_basic.py") +include("$(MPY_DIR)/../m5stack/libs/module/manifest.py") +include("$(MPY_DIR)/../m5stack/libs/unit/manifest.py") diff --git a/m5stack/boards/M5STACK_Basic_4MB/manifest.py b/m5stack/boards/M5STACK_Basic_4MB/manifest.py index 581fd386..0a829fe6 100644 --- a/m5stack/boards/M5STACK_Basic_4MB/manifest.py +++ b/m5stack/boards/M5STACK_Basic_4MB/manifest.py @@ -3,3 +3,5 @@ # SPDX-License-Identifier: MIT include("$(MPY_DIR)/../m5stack/modules/startup/manifest_basic.py") +include("$(MPY_DIR)/../m5stack/libs/module/manifest.py") +include("$(MPY_DIR)/../m5stack/libs/unit/manifest.py") diff --git a/m5stack/boards/M5STACK_Capsule/manifest.py b/m5stack/boards/M5STACK_Capsule/manifest.py index 079a6b3b..84bf697e 100644 --- a/m5stack/boards/M5STACK_Capsule/manifest.py +++ b/m5stack/boards/M5STACK_Capsule/manifest.py @@ -3,3 +3,4 @@ # SPDX-License-Identifier: MIT include("$(MPY_DIR)/../m5stack/modules/startup/manifest_capsule.py") +include("$(MPY_DIR)/../m5stack/libs/unit/manifest.py") diff --git a/m5stack/boards/M5STACK_Cardputer/manifest.py b/m5stack/boards/M5STACK_Cardputer/manifest.py index a17eacde..5561d797 100644 --- a/m5stack/boards/M5STACK_Cardputer/manifest.py +++ b/m5stack/boards/M5STACK_Cardputer/manifest.py @@ -3,3 +3,4 @@ # SPDX-License-Identifier: MIT include("$(MPY_DIR)/../m5stack/modules/startup/manifest_cardputer.py") +include("$(MPY_DIR)/../m5stack/libs/unit/manifest.py") diff --git a/m5stack/boards/M5STACK_CardputerADV/manifest.py b/m5stack/boards/M5STACK_CardputerADV/manifest.py index 75cb6720..a0cbc2d3 100644 --- a/m5stack/boards/M5STACK_CardputerADV/manifest.py +++ b/m5stack/boards/M5STACK_CardputerADV/manifest.py @@ -3,3 +3,5 @@ # SPDX-License-Identifier: MIT include("$(MPY_DIR)/../m5stack/modules/startup/manifest_cardputeradv.py") +include("$(MPY_DIR)/../m5stack/libs/cap/manifest.py") +include("$(MPY_DIR)/../m5stack/libs/unit/manifest.py") diff --git a/m5stack/boards/M5STACK_Core2/manifest.py b/m5stack/boards/M5STACK_Core2/manifest.py index cafeb383..82b3e47e 100644 --- a/m5stack/boards/M5STACK_Core2/manifest.py +++ b/m5stack/boards/M5STACK_Core2/manifest.py @@ -4,3 +4,5 @@ include("$(MPY_DIR)/../m5stack/modules/startup/manifest_core2.py") include("$(MPY_DIR)/../m5stack/libs/m5ui/manifest.py") +include("$(MPY_DIR)/../m5stack/libs/module/manifest.py") +include("$(MPY_DIR)/../m5stack/libs/unit/manifest.py") diff --git a/m5stack/boards/M5STACK_CoreInk/manifest.py b/m5stack/boards/M5STACK_CoreInk/manifest.py index c24bb384..f534a7d4 100644 --- a/m5stack/boards/M5STACK_CoreInk/manifest.py +++ b/m5stack/boards/M5STACK_CoreInk/manifest.py @@ -3,3 +3,5 @@ # SPDX-License-Identifier: MIT include("$(MPY_DIR)/../m5stack/modules/startup/manifest_coreink.py") +include("$(MPY_DIR)/../m5stack/libs/hat/manifest.py") +include("$(MPY_DIR)/../m5stack/libs/unit/manifest.py") diff --git a/m5stack/boards/M5STACK_CoreS3/manifest.py b/m5stack/boards/M5STACK_CoreS3/manifest.py index 11d2efb7..ba36cf26 100644 --- a/m5stack/boards/M5STACK_CoreS3/manifest.py +++ b/m5stack/boards/M5STACK_CoreS3/manifest.py @@ -4,3 +4,6 @@ include("$(MPY_DIR)/../m5stack/modules/startup/manifest_cores3.py") include("$(MPY_DIR)/../m5stack/libs/m5ui/manifest.py") +include("$(MPY_DIR)/../m5stack/libs/module/manifest.py") +include("$(MPY_DIR)/../m5stack/libs/unit/manifest.py") +include("$(MPY_DIR)/../m5stack/libs/usb/manifest.py") diff --git a/m5stack/boards/M5STACK_Dial/manifest.py b/m5stack/boards/M5STACK_Dial/manifest.py index 33d29bf5..bfd6d92a 100644 --- a/m5stack/boards/M5STACK_Dial/manifest.py +++ b/m5stack/boards/M5STACK_Dial/manifest.py @@ -4,3 +4,4 @@ include("$(MPY_DIR)/../m5stack/modules/startup/manifest_dial.py") include("$(MPY_DIR)/../m5stack/libs/m5ui/manifest.py") +include("$(MPY_DIR)/../m5stack/libs/unit/manifest.py") diff --git a/m5stack/boards/M5STACK_DinMeter/manifest.py b/m5stack/boards/M5STACK_DinMeter/manifest.py index b7689727..c929de7b 100644 --- a/m5stack/boards/M5STACK_DinMeter/manifest.py +++ b/m5stack/boards/M5STACK_DinMeter/manifest.py @@ -3,3 +3,4 @@ # SPDX-License-Identifier: MIT include("$(MPY_DIR)/../m5stack/modules/startup/manifest_dinmeter.py") +include("$(MPY_DIR)/../m5stack/libs/unit/manifest.py") diff --git a/m5stack/boards/M5STACK_Fire/manifest.py b/m5stack/boards/M5STACK_Fire/manifest.py index fc2851d8..61e6296a 100644 --- a/m5stack/boards/M5STACK_Fire/manifest.py +++ b/m5stack/boards/M5STACK_Fire/manifest.py @@ -3,3 +3,5 @@ # SPDX-License-Identifier: MIT include("$(MPY_DIR)/../m5stack/modules/startup/manifest_fire.py") +include("$(MPY_DIR)/../m5stack/libs/module/manifest.py") +include("$(MPY_DIR)/../m5stack/libs/unit/manifest.py") diff --git a/m5stack/boards/M5STACK_NanoC6/manifest.py b/m5stack/boards/M5STACK_NanoC6/manifest.py index 19c5ef7e..d6e66932 100644 --- a/m5stack/boards/M5STACK_NanoC6/manifest.py +++ b/m5stack/boards/M5STACK_NanoC6/manifest.py @@ -3,3 +3,4 @@ # SPDX-License-Identifier: MIT include("$(MPY_DIR)/../m5stack/modules/startup/manifest_nanoc6.py") +include("$(MPY_DIR)/../m5stack/libs/unit/manifest.py") diff --git a/m5stack/boards/M5STACK_Paper/manifest.py b/m5stack/boards/M5STACK_Paper/manifest.py index 5daff7f5..8e3a724b 100644 --- a/m5stack/boards/M5STACK_Paper/manifest.py +++ b/m5stack/boards/M5STACK_Paper/manifest.py @@ -3,3 +3,4 @@ # SPDX-License-Identifier: MIT include("$(MPY_DIR)/../m5stack/modules/startup/manifest_paper.py") +include("$(MPY_DIR)/../m5stack/libs/unit/manifest.py") diff --git a/m5stack/boards/M5STACK_PaperS3/manifest.py b/m5stack/boards/M5STACK_PaperS3/manifest.py index df2c9166..2374477e 100644 --- a/m5stack/boards/M5STACK_PaperS3/manifest.py +++ b/m5stack/boards/M5STACK_PaperS3/manifest.py @@ -3,3 +3,4 @@ # SPDX-License-Identifier: MIT include("$(MPY_DIR)/../m5stack/modules/startup/manifest_papers3.py") +include("$(MPY_DIR)/../m5stack/libs/unit/manifest.py") diff --git a/m5stack/boards/M5STACK_StamPLC/manifest.py b/m5stack/boards/M5STACK_StamPLC/manifest.py index 3f8a311b..e9117711 100644 --- a/m5stack/boards/M5STACK_StamPLC/manifest.py +++ b/m5stack/boards/M5STACK_StamPLC/manifest.py @@ -3,3 +3,4 @@ # SPDX-License-Identifier: MIT include("$(MPY_DIR)/../m5stack/modules/startup/manifest_stamplc.py") +include("$(MPY_DIR)/../m5stack/libs/unit/manifest.py") diff --git a/m5stack/boards/M5STACK_StampS3/manifest.py b/m5stack/boards/M5STACK_StampS3/manifest.py index f852e904..98fdf5c0 100644 --- a/m5stack/boards/M5STACK_StampS3/manifest.py +++ b/m5stack/boards/M5STACK_StampS3/manifest.py @@ -3,3 +3,4 @@ # SPDX-License-Identifier: MIT include("$(MPY_DIR)/../m5stack/modules/startup/manifest_stamps3.py") +include("$(MPY_DIR)/../m5stack/libs/unit/manifest.py") diff --git a/m5stack/boards/M5STACK_Stamp_PICO/manifest.py b/m5stack/boards/M5STACK_Stamp_PICO/manifest.py index ad769216..738c6a3a 100644 --- a/m5stack/boards/M5STACK_Stamp_PICO/manifest.py +++ b/m5stack/boards/M5STACK_Stamp_PICO/manifest.py @@ -3,3 +3,4 @@ # SPDX-License-Identifier: MIT include("$(MPY_DIR)/../m5stack/modules/startup/manifest_atoms3lite.py") +include("$(MPY_DIR)/../m5stack/libs/unit/manifest.py") diff --git a/m5stack/boards/M5STACK_Station/manifest.py b/m5stack/boards/M5STACK_Station/manifest.py index 54c67589..a11297f7 100644 --- a/m5stack/boards/M5STACK_Station/manifest.py +++ b/m5stack/boards/M5STACK_Station/manifest.py @@ -3,3 +3,4 @@ # SPDX-License-Identifier: MIT include("$(MPY_DIR)/../m5stack/modules/startup/manifest_station.py") +include("$(MPY_DIR)/../m5stack/libs/unit/manifest.py") diff --git a/m5stack/boards/M5STACK_StickC/manifest.py b/m5stack/boards/M5STACK_StickC/manifest.py index 324c0b7e..04c9435b 100644 --- a/m5stack/boards/M5STACK_StickC/manifest.py +++ b/m5stack/boards/M5STACK_StickC/manifest.py @@ -3,3 +3,5 @@ # SPDX-License-Identifier: MIT include("$(MPY_DIR)/../m5stack/modules/startup/manifest_stickc.py") +include("$(MPY_DIR)/../m5stack/libs/hat/manifest.py") +include("$(MPY_DIR)/../m5stack/libs/unit/manifest.py") diff --git a/m5stack/boards/M5STACK_StickC_PLUS/manifest.py b/m5stack/boards/M5STACK_StickC_PLUS/manifest.py index b193a2a0..abfd4d19 100644 --- a/m5stack/boards/M5STACK_StickC_PLUS/manifest.py +++ b/m5stack/boards/M5STACK_StickC_PLUS/manifest.py @@ -3,3 +3,5 @@ # SPDX-License-Identifier: MIT include("$(MPY_DIR)/../m5stack/modules/startup/manifest_stickcplus.py") +include("$(MPY_DIR)/../m5stack/libs/hat/manifest.py") +include("$(MPY_DIR)/../m5stack/libs/unit/manifest.py") diff --git a/m5stack/boards/M5STACK_StickC_PLUS2/manifest.py b/m5stack/boards/M5STACK_StickC_PLUS2/manifest.py index b193a2a0..abfd4d19 100644 --- a/m5stack/boards/M5STACK_StickC_PLUS2/manifest.py +++ b/m5stack/boards/M5STACK_StickC_PLUS2/manifest.py @@ -3,3 +3,5 @@ # SPDX-License-Identifier: MIT include("$(MPY_DIR)/../m5stack/modules/startup/manifest_stickcplus.py") +include("$(MPY_DIR)/../m5stack/libs/hat/manifest.py") +include("$(MPY_DIR)/../m5stack/libs/unit/manifest.py") diff --git a/m5stack/boards/M5STACK_Tab5/manifest.py b/m5stack/boards/M5STACK_Tab5/manifest.py index d9b00aae..0a67b0b2 100644 --- a/m5stack/boards/M5STACK_Tab5/manifest.py +++ b/m5stack/boards/M5STACK_Tab5/manifest.py @@ -4,3 +4,6 @@ include("$(MPY_DIR)/../m5stack/modules/startup/manifest_tab5.py") include("$(MPY_DIR)/../m5stack/libs/m5ui/manifest.py") +include("$(MPY_DIR)/../m5stack/libs/module/manifest.py") +include("$(MPY_DIR)/../m5stack/libs/unit/manifest.py") +include("$(MPY_DIR)/../m5stack/libs/usb/manifest.py") diff --git a/m5stack/boards/M5STACK_Tough/manifest.py b/m5stack/boards/M5STACK_Tough/manifest.py index 73062f3c..a92a065c 100644 --- a/m5stack/boards/M5STACK_Tough/manifest.py +++ b/m5stack/boards/M5STACK_Tough/manifest.py @@ -4,3 +4,5 @@ include("$(MPY_DIR)/../m5stack/modules/startup/manifest_tough.py") include("$(MPY_DIR)/../m5stack/libs/m5ui/manifest.py") +include("$(MPY_DIR)/../m5stack/libs/module/manifest.py") +include("$(MPY_DIR)/../m5stack/libs/unit/manifest.py") diff --git a/m5stack/boards/manifest_m5stack.py b/m5stack/boards/manifest_m5stack.py index d90533be..689b1708 100644 --- a/m5stack/boards/manifest_m5stack.py +++ b/m5stack/boards/manifest_m5stack.py @@ -4,4 +4,4 @@ include("$(BOARD_DIR)/manifest.py") include("$(MPY_DIR)/../m5stack/modules/manifest.py") -include("$(MPY_DIR)/../m5stack/libs/manifest.py") +include("$(MPY_DIR)/../m5stack/libs/manifest_basic.py") diff --git a/m5stack/libs/manifest_basic.py b/m5stack/libs/manifest_basic.py new file mode 100644 index 00000000..0f43adaf --- /dev/null +++ b/m5stack/libs/manifest_basic.py @@ -0,0 +1,25 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + + +include("bleuart/manifest.py") +include("driver/manifest.py") +include("ezdata/manifest.py") +include("hardware/manifest.py") +include("image_plus/manifest.py") +include("lv_utils/manifest.py") +include("m5ble/manifest.py") +include("m5espnow/manifest.py") +include("modbus/modbus/manifest.py") +include("requests2/manifest.py") +include("umqtt/manifest.py") +include("unit/manifest.py") +include("utility/manifest.py") +module("boot_option.py") +module("color_conv.py") +module("attitude_estimator.py") +module("label_plus.py") +module("pid.py") +include("iot_devices/manifest.py") +module("warnings.py") From 5a173d39a3b808b56600f493ffa4a52f81244e0e Mon Sep 17 00:00:00 2001 From: lbuque <1102390310@qq.com> Date: Thu, 18 Sep 2025 15:48:51 +0800 Subject: [PATCH 262/322] libs/label_plus.py: Fix timer exception issue. Signed-off-by: lbuque <1102390310@qq.com> --- docs/en/refs/widgets.label+.ref | 20 + docs/en/widgets/index.rst | 1 + docs/en/widgets/label+.rst | 137 +++++++ .../zh_CN/LC_MESSAGES/widgets/label+.po | 354 ++++++++++++++++++ .../labelplus/cores3_labelplus_example.m5f2 | 1 + .../labelplus/cores3_labelplus_example.py | 69 ++++ m5stack/libs/driver/soft_timer.py | 38 +- m5stack/libs/image_plus/__init__.py | 3 + m5stack/libs/label_plus.py | 124 +++++- 9 files changed, 723 insertions(+), 24 deletions(-) create mode 100644 docs/en/refs/widgets.label+.ref create mode 100644 docs/en/widgets/label+.rst create mode 100644 docs/locales/zh_CN/LC_MESSAGES/widgets/label+.po create mode 100644 examples/widgets/labelplus/cores3_labelplus_example.m5f2 create mode 100644 examples/widgets/labelplus/cores3_labelplus_example.py diff --git a/docs/en/refs/widgets.label+.ref b/docs/en/refs/widgets.label+.ref new file mode 100644 index 00000000..0ed8fb11 --- /dev/null +++ b/docs/en/refs/widgets.label+.ref @@ -0,0 +1,20 @@ +.. |cores3_labelplus_example.png| image:: https://static-cdn.m5stack.com/mpy_docs/widgets/label_plus/cores3_labelplus_example.png +.. |get_data.png| image:: https://static-cdn.m5stack.com/mpy_docs/widgets/label_plus/get_data.png +.. |is_valid_data.png| image:: https://static-cdn.m5stack.com/mpy_docs/widgets/label_plus/is_valid_data.png +.. |setColor.png| image:: https://static-cdn.m5stack.com/mpy_docs/widgets/label_plus/setColor.png +.. |setCursor.png| image:: https://static-cdn.m5stack.com/mpy_docs/widgets/label_plus/setCursor.png +.. |setFont.png| image:: https://static-cdn.m5stack.com/mpy_docs/widgets/label_plus/setFont.png +.. |setSize.png| image:: https://static-cdn.m5stack.com/mpy_docs/widgets/label_plus/setSize.png +.. |setText.png| image:: https://static-cdn.m5stack.com/mpy_docs/widgets/label_plus/setText.png +.. |setVisible.png| image:: https://static-cdn.m5stack.com/mpy_docs/widgets/label_plus/setVisible.png +.. |set_update_enable.png| image:: https://static-cdn.m5stack.com/mpy_docs/widgets/label_plus/set_update_enable.png +.. |set_update_period.png| image:: https://static-cdn.m5stack.com/mpy_docs/widgets/label_plus/set_update_period.png + +.. |cores3_labelplus_example.m5f2| raw:: html + + + cores3_labelplus_example.m5f2 + diff --git a/docs/en/widgets/index.rst b/docs/en/widgets/index.rst index b5b3395a..f536aad5 100644 --- a/docs/en/widgets/index.rst +++ b/docs/en/widgets/index.rst @@ -91,3 +91,4 @@ Classes image.rst image+.rst label.rst + label+.rst diff --git a/docs/en/widgets/label+.rst b/docs/en/widgets/label+.rst new file mode 100644 index 00000000..bd8dccc5 --- /dev/null +++ b/docs/en/widgets/label+.rst @@ -0,0 +1,137 @@ +.. currentmodule:: Widgets + +class LabelPlus -- display remote text +====================================== + +The `LabelPlus` class extends the `Widgets.Label` class to provide additional functionalities for handling text with dynamic updates. + +Currently only accepts strings in json format, and extracts data through ``json_key``. + +.. include:: ../refs/widgets.label+.ref + +UiFlow2 Example +--------------- + +Simple Usage +^^^^^^^^^^^^ + +Open the |cores3_labelplus_example.m5f2| project in UiFlow2. + +This example demonstrates how to create and manipulate a LabelPlus widget. + +UiFlow2 Code Block: + + |cores3_labelplus_example.png| + +Example output: + + None + + +MicroPython Example +------------------- + +Simple Usage +^^^^^^^^^^^^ + +This example demonstrates how to create and manipulate a LabelPlus widget. + +MicroPython Code Block: + + .. literalinclude:: ../../../examples/widgets/labelplus/cores3_labelplus_example.py + +Example output: + + None + + +**API** +------- + +LabelPlus +^^^^^^^^^ + +.. autoclass:: label_plus.LabelPlus + :members: + :member-order: bysource + + .. py:method:: setText(text) + + Set the text of the LabelPlus widget. + + :param str text: The text to set on the label. + + UiFlow2 Code Block: + + |setText.png| + + MicroPython Code Block: + + .. code-block:: python + + label_plus_0.setText("New Text") + + .. py:method:: setCursor(x=0, y=0) + + Sets the starting coordinates of the text cursor in the LabelPlus widget. + + :param int x: The x-coordinate of the cursor. + :param int y: The y-coordinate of the cursor. + + UiFlow2 Code Block: + + |setCursor.png| + + MicroPython Code Block: + + .. code-block:: python + + label_plus_0.setCursor(10, 20) + + .. py:method:: setSize(size) + + Sets the font size of the text in the LabelPlus widget. + + :param float size: The font size to set. + + UiFlow2 Code Block: + + |setSize.png| + + MicroPython Code Block: + + .. code-block:: python + + label_plus_0.setSize(1.5) + + .. py:method:: setFont(font) + + Sets the font of the text in the LabelPlus widget. + + :param str font: The font to set (e.g., Widgets.FONTS.DejaVu9). + + UiFlow2 Code Block: + + |setFont.png| + + MicroPython Code Block: + + .. code-block:: python + + label_plus_0.setFont(Widgets.FONTS.DejaVu9) + + .. py:method:: setVisible(visible) + + Set the visible property of the LabelPlus widget. + + :param bool visible: True to make the label visible, False to hide it. + + UiFlow2 Code Block: + + |setVisible.png| + + MicroPython Code Block: + + .. code-block:: python + + label_plus_0.setVisible(True) diff --git a/docs/locales/zh_CN/LC_MESSAGES/widgets/label+.po b/docs/locales/zh_CN/LC_MESSAGES/widgets/label+.po new file mode 100644 index 00000000..66a43c47 --- /dev/null +++ b/docs/locales/zh_CN/LC_MESSAGES/widgets/label+.po @@ -0,0 +1,354 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2016 - 2025 M5Stack Technology Co., Ltd +# This file is distributed under the same license as the UIFlow2 Programming +# Guide package. +# FIRST AUTHOR , 2025. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: UIFlow2 Programming Guide \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-09-18 15:17+0800\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: zh_CN\n" +"Language-Team: zh_CN \n" +"Plural-Forms: nplurals=1; plural=0;\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.17.0\n" + +#: ../../en/widgets/label+.rst:4 e49b3673babe4197b89484ac93bc9444 +msgid "class LabelPlus -- display remote text" +msgstr "class LabelPlus -- 显示远程文本" + +#: ../../en/widgets/label+.rst:6 d78d106629724159b83c2a00e3ba477a +msgid "" +"The `LabelPlus` class extends the `Widgets.Label` class to provide " +"additional functionalities for handling text with dynamic updates." +msgstr "`LabelPlus` 类扩展了 `Widgets.Label` 类,提供了处理动态更新文本的附加功能。" + +#: ../../en/widgets/label+.rst:8 feb5f64f978341e58f23087cd8a4af52 +msgid "" +"Currently only accepts strings in json format, and extracts data through " +"``json_key``." +msgstr "目前只接受json格式的字符串,并通过``json_key``提取数据。" + +#: ../../en/widgets/label+.rst:13 236322b79f8848d7a8e804a7afcd86c8 +msgid "UiFlow2 Example" +msgstr "UiFlow2 示例" + +#: ../../en/widgets/label+.rst:16 ../../en/widgets/label+.rst:35 +#: 5258cf1a47884f32873445ee864ca718 919d134b77d3495aab2695fb486a3f17 +msgid "Simple Usage" +msgstr "简单用法" + +#: ../../en/widgets/label+.rst:18 008e92b827b644f8841108bb8fbff161 +msgid "Open the |cores3_labelplus_example.m5f2| project in UiFlow2." +msgstr "在 UiFlow2 中打开 |cores3_labelplus_example.m5f2| 项目。" + +#: ../../en/widgets/label+.rst:20 ../../en/widgets/label+.rst:37 +#: 87645af84a584930af22e2de6f1a8137 dd696bedea6b468eaacc4924036e350d +msgid "This example demonstrates how to create and manipulate a LabelPlus widget." +msgstr "此示例演示如何创建和操作 LabelPlus 控件。" + +#: ../../en/widgets/label+.rst:22 ../../en/widgets/label+.rst:64 +#: ../../en/widgets/label+.rst:81 ../../en/widgets/label+.rst:97 +#: ../../en/widgets/label+.rst:113 ../../en/widgets/label+.rst:129 +#: ae3a23ad408a4e12aa012982743737db label_plus.LabelPlus:17 +#: label_plus.LabelPlus.get_data:6 label_plus.LabelPlus.is_valid_data:6 +#: label_plus.LabelPlus.setColor:6 label_plus.LabelPlus.set_update_enable:5 +#: label_plus.LabelPlus.set_update_period:5 of +msgid "UiFlow2 Code Block:" +msgstr "UiFlow2 代码块:" + +#: ../../en/widgets/label+.rst:24 6e80b80fa10642afb60f269348f35fcf +msgid "|cores3_labelplus_example.png|" +msgstr "" + +#: ../../en/refs/widgets.label+.ref:1 46c5612e650a4b06bd56076c6653a035 +msgid "cores3_labelplus_example.png" +msgstr "" + +#: ../../en/widgets/label+.rst:26 ../../en/widgets/label+.rst:43 +#: 4a50eec73fbe485eabde02feb47b1a1f e6158c4df4504cbfbd3e29e51df0d0d1 +msgid "Example output:" +msgstr "示例输出:" + +#: ../../en/widgets/label+.rst:28 ../../en/widgets/label+.rst:45 +#: 49f778b7c3c345be9b430e44367f8855 label_plus.LabelPlus:19 of +msgid "None" +msgstr "" + +#: ../../en/widgets/label+.rst:32 82e7dfc213dd48d4a40ab86a5779c368 +msgid "MicroPython Example" +msgstr "MicroPython 示例" + +#: ../../en/widgets/label+.rst:39 ../../en/widgets/label+.rst:68 +#: ../../en/widgets/label+.rst:85 ../../en/widgets/label+.rst:101 +#: ../../en/widgets/label+.rst:117 ../../en/widgets/label+.rst:133 +#: 7e750a5501c843fc81103ab4b8a2fa6b label_plus.LabelPlus:21 +#: label_plus.LabelPlus.get_data:10 label_plus.LabelPlus.is_valid_data:10 +#: label_plus.LabelPlus.setColor:10 label_plus.LabelPlus.set_update_enable:9 +#: label_plus.LabelPlus.set_update_period:9 of +msgid "MicroPython Code Block:" +msgstr "MicroPython 代码块:" + +#: ../../en/widgets/label+.rst:49 9bf2ee5d8e8a48d79b0e8f696c7abb44 +msgid "**API**" +msgstr "API参考" + +#: ../../en/widgets/label+.rst:52 4d801252417f49109111fa80cd8bf383 +msgid "LabelPlus" +msgstr "" + +#: 479bdf87c85a4fae9030078cc3977b0d label_plus.LabelPlus:1 of +msgid "Bases: :py:class:`~M5.Widgets.Label`" +msgstr "" + +#: 6d2a22cb2f2d45bdbb2d6b50999fcedc label_plus.LabelPlus:1 of +msgid "Create a LabelPlus object that can fetch and display text from a URL." +msgstr "创建一个可以从 URL 获取和显示文本的 LabelPlus 对象。" + +#: ../../en/widgets/label+.rst 8209e4a1697b40ce8a1aa4f4a76221e8 +#: label_plus.LabelPlus.setColor label_plus.LabelPlus.set_update_enable +#: label_plus.LabelPlus.set_update_period of +msgid "Parameters" +msgstr "" + +#: 83b1299f0b4848b2bb77e9a7ed3b4c1d label_plus.LabelPlus:3 of +msgid "The initial text to display on the label." +msgstr "标签上显示的初始文本。" + +#: 301b85b82c8b46159216a3fbd661b6c6 label_plus.LabelPlus:4 of +msgid "The x position of the label." +msgstr "标签的 x 坐标。" + +#: 620a50eecd74458695a04151fde65141 label_plus.LabelPlus:5 of +msgid "The y position of the label." +msgstr "标签的 y 坐标。" + +#: 8e140938cf374f40b78189a74cda3b48 label_plus.LabelPlus:6 of +msgid "The font size of the label text." +msgstr "标签文本的字体大小。" + +#: be5f685694684680bc81ba93754ecfa4 label_plus.LabelPlus:7 of +msgid "The text color of the label in hexadecimal format." +msgstr "标签的文本颜色,十六进制格式。" + +#: f8ff805ff114425dbfabe5021fdfc576 label_plus.LabelPlus:8 of +msgid "The background color of the label in hexadecimal format." +msgstr "标签的背景颜色,十六进制格式。" + +#: 0c41d3f695154d0ba2674f84b6ae86bb label_plus.LabelPlus:9 of +msgid "The font to use for the label text." +msgstr "用于标签文本的字体。" + +#: 4370d38cccb34a689d91d2226bb99ecc label_plus.LabelPlus:10 of +msgid "The URL to fetch data from." +msgstr "从中获取数据的 URL。" + +#: 0a5263ae5342442cbc74c564e7d4c497 label_plus.LabelPlus:11 of +msgid "" +"The update period in milliseconds. If set to 0, the label will not update" +" automatically." +msgstr "更新周期(以毫秒为单位)。如果设置为 0,标签将不会自动更新。" + +#: 4342321c36c144429a0a0a74f741471f label_plus.LabelPlus:12 of +msgid "Whether to enable automatic updates." +msgstr "是否启用自动更新。" + +#: 3d5230c01e374cd7b4228732e0476ab7 label_plus.LabelPlus:13 of +msgid "The JSON key to extract from the fetched data." +msgstr "要从获取的数据中提取的 JSON 键。" + +#: a9991153efb946009b231c0587e49c9f label_plus.LabelPlus:14 of +msgid "The message to display in case of an error." +msgstr "出现错误时显示的消息。" + +#: 756b91071ec94bddbe014917498fe9c2 label_plus.LabelPlus:15 of +msgid "" +"The text color to use when displaying an error message, in hexadecimal " +"format." +msgstr "显示错误消息时使用的文本颜色。" + +#: ../../en/widgets/label+.rst:60 0b34f9643eb94c90b65d6bad76f33c45 +msgid "Set the text of the LabelPlus widget." +msgstr "设置 LabelPlus 控件的文本。" + +#: ../../en/widgets/label+.rst:62 83b1299f0b4848b2bb77e9a7ed3b4c1d +msgid "The text to set on the label." +msgstr "要在标签上设置的文本。" + +#: ../../en/widgets/label+.rst:66 6a0cb3cd881842a8949752b79b88c27e +msgid "|setText.png|" +msgstr "" + +#: ../../en/refs/widgets.label+.ref:8 66d69d5e100e42b0a833696b5ff77fd9 +msgid "setText.png" +msgstr "" + +#: ../../en/widgets/label+.rst:76 091746525cbf4945bf374cb15bf7cca1 +msgid "Sets the starting coordinates of the text cursor in the LabelPlus widget." +msgstr "设置 LabelPlus 控件中文本光标的起始坐标。" + +#: ../../en/widgets/label+.rst:78 301b85b82c8b46159216a3fbd661b6c6 +msgid "The x-coordinate of the cursor." +msgstr "光标的 x 坐标。" + +#: ../../en/widgets/label+.rst:79 620a50eecd74458695a04151fde65141 +msgid "The y-coordinate of the cursor." +msgstr "光标的 y 坐标。" + +#: ../../en/widgets/label+.rst:83 36ebd0a4effe4419826c047c2ce2232e +msgid "|setCursor.png|" +msgstr "" + +#: ../../en/refs/widgets.label+.ref:5 966f62f181084c21b28f13514a1c7e82 +msgid "setCursor.png" +msgstr "" + +#: ../../en/widgets/label+.rst:93 0b34f9643eb94c90b65d6bad76f33c45 +msgid "Sets the font size of the text in the LabelPlus widget." +msgstr "设置 LabelPlus 控件中文本的字体大小。" + +#: ../../en/widgets/label+.rst:95 8e140938cf374f40b78189a74cda3b48 +msgid "The font size to set." +msgstr "要设置的字体大小。" + +#: ../../en/widgets/label+.rst:99 36ebd0a4effe4419826c047c2ce2232e +msgid "|setSize.png|" +msgstr "" + +#: ../../en/refs/widgets.label+.ref:7 dd8b86888a904dee80288805e0846c83 +msgid "setSize.png" +msgstr "" + +#: ../../en/widgets/label+.rst:109 0b34f9643eb94c90b65d6bad76f33c45 +msgid "Sets the font of the text in the LabelPlus widget." +msgstr "设置 LabelPlus 控件中文本的字体。" + +#: ../../en/widgets/label+.rst:111 645f96112019407f9079acf5fe6607fb +msgid "The font to set (e.g., Widgets.FONTS.DejaVu9)." +msgstr "要设置的字体(例如,Widgets.FONTS.DejaVu9)。" + +#: ../../en/widgets/label+.rst:115 36ebd0a4effe4419826c047c2ce2232e +msgid "|setFont.png|" +msgstr "" + +#: ../../en/refs/widgets.label+.ref:6 9e63a4f6b4cf46b2b0e6c55861215523 +msgid "setFont.png" +msgstr "" + +#: ../../en/widgets/label+.rst:125 0b34f9643eb94c90b65d6bad76f33c45 +msgid "Set the visible property of the LabelPlus widget." +msgstr "设置 LabelPlus 控件的可见属性。" + +#: ../../en/widgets/label+.rst:127 f238cd95e9ea4f9ea862214dd09537df +msgid "True to make the label visible, False to hide it." +msgstr "True 使标签可见,False 隐藏它。" + +#: ../../en/widgets/label+.rst:131 634c9234286c43919c568264df566ac6 +msgid "|setVisible.png|" +msgstr "" + +#: ../../en/refs/widgets.label+.ref:9 71947e39cfcd4c83aa82ddf1094b8f84 +msgid "setVisible.png" +msgstr "" + +#: 1d417870a52f4dfca5ae5f8a6210205c label_plus.LabelPlus.set_update_enable:1 of +msgid "Enable or disable automatic updates." +msgstr "启用或禁用自动更新。" + +#: f238cd95e9ea4f9ea862214dd09537df label_plus.LabelPlus.set_update_enable:3 of +msgid "True to enable automatic updates, False to disable." +msgstr "True 启用自动更新,False 禁用。" + +#: 634c9234286c43919c568264df566ac6 label_plus.LabelPlus.set_update_enable:7 of +msgid "|set_update_enable.png|" +msgstr "" + +#: ../../en/refs/widgets.label+.ref:10 0743c5c1ca5c4e829ce765bcca488d1e +msgid "set_update_enable.png" +msgstr "" + +#: cacd684db7864656a592acb8b16770dd label_plus.LabelPlus.set_update_period:1 of +msgid "Set the update period for automatic updates." +msgstr "设置自动更新的更新周期。" + +#: 4c8fc2d049b54145b8c67c6c0933a50d label_plus.LabelPlus.set_update_period:3 of +msgid "The update period in milliseconds." +msgstr "更新周期(以毫秒为单位)。" + +#: 182d18f31bcd4439a7d9c0b3c34bb1b3 label_plus.LabelPlus.set_update_period:7 of +msgid "|set_update_period.png|" +msgstr "" + +#: ../../en/refs/widgets.label+.ref:11 2267453aa0f4499ba7b182ba9cb81fff +msgid "set_update_period.png" +msgstr "" + +#: d013ac06f15740d79c27ba29e31a804b label_plus.LabelPlus.is_valid_data:1 of +msgid "Check if the current data is valid (i.e., not an error message)." +msgstr "检查当前数据是否有效(即不是错误消息)。" + +#: 7a342334b7ec47ea8eb9bcf9961531e9 label_plus.LabelPlus.get_data +#: label_plus.LabelPlus.is_valid_data of +msgid "Returns" +msgstr "" + +#: 0b25f286d39549878781c1d0d30eeecf label_plus.LabelPlus.is_valid_data:3 of +msgid "True if the current data is valid, False otherwise." +msgstr "如果当前数据有效则返回 True,否则返回 False。" + +#: 6df4c23661274bffa0ccc7d8ca52f88d label_plus.LabelPlus.get_data +#: label_plus.LabelPlus.is_valid_data of +msgid "Return type" +msgstr "" + +#: 43596db0a2a24b9484fe874d376cced5 label_plus.LabelPlus.is_valid_data:8 of +msgid "|is_valid_data.png|" +msgstr "" + +#: ../../en/refs/widgets.label+.ref:3 16feb3006e594f47849ff619b9f70ce0 +msgid "is_valid_data.png" +msgstr "" + +#: 44d519502b064268a3be68a5e09afe6d label_plus.LabelPlus.get_data:1 of +msgid "Get the current data displayed on the label." +msgstr "获取标签上显示的当前数据。" + +#: b17a74f980a84b11ae703f757b974d50 label_plus.LabelPlus.get_data:3 of +msgid "The current data." +msgstr "当前数据。" + +#: 6a0cb3cd881842a8949752b79b88c27e label_plus.LabelPlus.get_data:8 of +msgid "|get_data.png|" +msgstr "" + +#: ../../en/refs/widgets.label+.ref:2 88c85310e9d24ab8b526292f02c9856b +msgid "get_data.png" +msgstr "" + +#: 0b34f9643eb94c90b65d6bad76f33c45 label_plus.LabelPlus.setColor:1 of +msgid "Sets the text font color of the Label object." +msgstr "设置 Label 对象的文本字体颜色。" + +#: 1abe2962bfb24aaa83813b046e85d9dc label_plus.LabelPlus.setColor:3 of +msgid "The text color in hexadecimal format." +msgstr "文本颜色,十六进制格式。" + +#: 5c93e622efc44f50b3636aed02cd8fa9 label_plus.LabelPlus.setColor:4 of +msgid "The background color in hexadecimal format." +msgstr "背景颜色,十六进制格式。" + +#: 36ebd0a4effe4419826c047c2ce2232e label_plus.LabelPlus.setColor:8 of +msgid "|setColor.png|" +msgstr "" + +#: ../../en/refs/widgets.label+.ref:4 b9ecefbe5f2449ed8ad170120c3534d5 +msgid "setColor.png" +msgstr "" + diff --git a/examples/widgets/labelplus/cores3_labelplus_example.m5f2 b/examples/widgets/labelplus/cores3_labelplus_example.m5f2 new file mode 100644 index 00000000..0fb0cb9a --- /dev/null +++ b/examples/widgets/labelplus/cores3_labelplus_example.m5f2 @@ -0,0 +1 @@ +{"version":"V2.0","versionNumber":"V2.3.5","type":"cores3","components":[{"name":"screen","type":"screen","layer":0,"screenId":"builtin","screenName":"","id":"__cores3_screen","createTime":1758178861408,"x":0,"y":0,"width":320,"height":240,"backgroundColor":"#222222","size":0,"isSelected":true},{"name":"label_plus0","type":"label_plus","layer":1,"screenId":"builtin","screenName":"","id":"r-2`gO@+V+$X`SI8","createTime":1758178863555,"x":24,"y":31,"color":"#ffffff","backgroundColor":"#222222","text":"label_plus0","font":"Widgets.FONTS.DejaVu18","rotation":0,"dataSource":"http://192.168.8.200:8000/data","interval":3000,"is_update":true,"use_json":true,"json_key":"data","error_msg":"error","error_msg_color":"#FF0000","isSelected":false,"width":106,"height":21}],"resources":[{"hardware":["hardware_button","hardware_pin_button","imu","speaker","touch","als","mic","sdcard"]}],"units":[],"hats":[],"bases":[],"i2cs":[],"blockly":"entrueenTRUEtrueBtnPWRWAS_CLICKEDenenenlabel_plus0Truelabel_plus0False","screen":[{"simulationName":"Built-in","type":"builtin","width":320,"height":240,"scale":0.77,"screenName":"","blockId":"","screenColorType":0,"rotation":1,"id":"builtin","createTime":1758178852415}],"logicWhenNum":0,"customList":[]} \ No newline at end of file diff --git a/examples/widgets/labelplus/cores3_labelplus_example.py b/examples/widgets/labelplus/cores3_labelplus_example.py new file mode 100644 index 00000000..7d9fc240 --- /dev/null +++ b/examples/widgets/labelplus/cores3_labelplus_example.py @@ -0,0 +1,69 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import os, sys, io +import M5 +from M5 import * +from label_plus import LabelPlus + + +label_plus0 = None + + +en = None + + +def btnPWR_wasClicked_event(state): # noqa: N802 + global label_plus0, en + en = not en + if en: + label_plus0.set_update_enable(True) + else: + label_plus0.set_update_enable(False) + + +def setup(): + global label_plus0, en + + M5.begin() + Widgets.setRotation(1) + Widgets.fillScreen(0x222222) + label_plus0 = LabelPlus( + "label_plus0", + 24, + 31, + 1.0, + 0xFFFFFF, + 0x222222, + Widgets.FONTS.DejaVu18, + "http://192.168.8.200:8000/data", + 3000, + True, + "data", + "error", + 0xFF0000, + ) + + BtnPWR.setCallback(type=BtnPWR.CB_TYPE.WAS_CLICKED, cb=btnPWR_wasClicked_event) + + en = True + + +def loop(): + global label_plus0, en + M5.update() + + +if __name__ == "__main__": + try: + setup() + while True: + loop() + except (Exception, KeyboardInterrupt) as e: + try: + from utility import print_error_msg + + print_error_msg(e) + except ImportError: + print("please update to latest firmware") diff --git a/m5stack/libs/driver/soft_timer.py b/m5stack/libs/driver/soft_timer.py index 15ff076a..980e93d6 100644 --- a/m5stack/libs/driver/soft_timer.py +++ b/m5stack/libs/driver/soft_timer.py @@ -14,12 +14,21 @@ class SoftTimerScheduler: def __new__(cls, *args, **kw): if cls._instance is None: - cls._instance = object.__new__(cls) - cls._instance._tims = [] - cls._run = False + cls._instance = super().__new__(cls) + cls._instance._initialized = False return cls._instance + def __init__(self): + if self._initialized: + return + self._tims = [] + self._run = False + self._initialized = True + def add_timer(self, tim): + if tim in self._tims: + return + self._tims.append(tim) if not self._run: _thread.start_new_thread(self._cb, ()) @@ -31,29 +40,26 @@ def del_timer(self, tim): def _cb(self): while self._run: - to_delete = [] + # print("running...", self._tims) + for tim in self._tims: - if tim.dead: - to_delete.append(tim) - else: + if not tim.dead: self.update(tim) - for tim in to_delete: - self._tims.remove(tim) - to_delete = [] - # if not self._tims: - # break; + time.sleep_ms(10) + self._wait = False self._run = False - def update(self, tim): + def update(self, tim: "SoftTimer"): if time.ticks_ms() > tim.next_time: tim.next_time = time.ticks_ms() + tim.period - tim.callback and tim.callback() if tim.mode == ONE_SHOT: tim.dead = True + if tim.callback: + tim.callback(tim) - def deinit(self, tim): + def deinit(self, tim: "SoftTimer"): self._wait = True self._run = False while self._wait: @@ -70,7 +76,7 @@ def __init__(self, mode=PERIODIC, period=-1, callback=None): self.init(mode, period, callback) def init(self, mode=PERIODIC, period=-1, callback=None): - SoftTimerScheduler().del_timer(self) + # SoftTimerScheduler().del_timer(self) self.callback = callback self.next_time = time.ticks_ms() + period self.period = period diff --git a/m5stack/libs/image_plus/__init__.py b/m5stack/libs/image_plus/__init__.py index 535678a2..29d416b6 100644 --- a/m5stack/libs/image_plus/__init__.py +++ b/m5stack/libs/image_plus/__init__.py @@ -75,3 +75,6 @@ def is_valid_image(self): def __del__(self): self._enable = False + + def deinit(self): + self._enable = False diff --git a/m5stack/libs/label_plus.py b/m5stack/libs/label_plus.py index 046b07d5..a7d96b1d 100644 --- a/m5stack/libs/label_plus.py +++ b/m5stack/libs/label_plus.py @@ -1,18 +1,45 @@ # SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD # # SPDX-License-Identifier: MIT + from M5 import Widgets +import requests -try: - import urequests as requests -except ImportError: - import requests -from machine import Timer +from driver import soft_timer # from micropython import schedule class LabelPlus(Widgets.Label): + """Create a LabelPlus object that can fetch and display text from a URL. + + :param str text: The initial text to display on the label. + :param int x: The x position of the label. + :param int y: The y position of the label. + :param int size: The font size of the label text. + :param int text_color: The text color of the label in hexadecimal format. + :param int bg_color: The background color of the label in hexadecimal format. + :param font: The font to use for the label text. + :param str url: The URL to fetch data from. + :param int period: The update period in milliseconds. If set to 0, the label will not update automatically. + :param bool enable: Whether to enable automatic updates. + :param str json_key: The JSON key to extract from the fetched data. + :param str error_msg: The message to display in case of an error. + :param int error_msg_color: The text color to use when displaying an error message, in hexadecimal format. + + UiFlow2 Code Block: + + None + + MicroPython Code Block: + + .. code-block:: python + + from label_plus import LabelPlus + + label_plus0 = LabelPlus("label_plus0", 7, 10, 1.0, 0xffffff, 0x222222, Widgets.FONTS.DejaVu18, "http://example.com", 3000, True, "title", "error", 0xFF0000) + """ + def __init__( self, text, @@ -30,7 +57,7 @@ def __init__( error_msg_color=0xFF0000, ) -> None: self._url = url - self._tim = Timer(-1) + # self._tim = Timer(-1) self._period = period self._enable = enable self._key = json_key @@ -41,13 +68,35 @@ def __init__( super(LabelPlus, self).__init__(text, x, y, size, text_color, bg_color, font) self._data = error_msg + self._update() self._init_timer() def _init_timer(self): if self._enable and self._period > 0: - self._tim.init(period=self._period, mode=Timer.ONE_SHOT, callback=self._cb) + self._tim = soft_timer.SoftTimer( + mode=soft_timer.SoftTimer.ONE_SHOT, period=self._period, callback=self._cb + ) + + def deinit(self): + if self._enable is True: + self._tim.deinit() + self._enable = False def set_update_enable(self, enable): + """Enable or disable automatic updates. + + :param bool enable: True to enable automatic updates, False to disable. + + UiFlow2 Code Block: + + |set_update_enable.png| + + MicroPython Code Block: + + .. code-block:: python + + label_plus0.set_update_enable(True) + """ if self._enable is True and enable is True: return if self._enable is True: @@ -58,21 +107,80 @@ def set_update_enable(self, enable): def _cb(self, tim): # schedule(self._update(), self) self._update() - self._tim.init(mode=Timer.ONE_SHOT, period=self._period, callback=self._cb) + self._tim.init(mode=soft_timer.SoftTimer.ONE_SHOT, period=self._period, callback=self._cb) def set_update_period(self, period): + """Set the update period for automatic updates. + + :param int period: The update period in milliseconds. + + UiFlow2 Code Block: + + |set_update_period.png| + + MicroPython Code Block: + + .. code-block:: python + + label_plus0.set_update_period(5000) + """ if self._enable: self._tim.deinit() self._period = period self._init_timer() def is_valid_data(self) -> bool: + """Check if the current data is valid (i.e., not an error message). + + :return: True if the current data is valid, False otherwise. + :rtype: bool + + UiFlow2 Code Block: + + |is_valid_data.png| + + MicroPython Code Block: + + .. code-block:: python + + valid = label_plus0.is_valid_data() + """ return False if self._data is self._error_msg else True def get_data(self): + """Get the current data displayed on the label. + + :return: The current data. + :rtype: str + + UiFlow2 Code Block: + + |get_data.png| + + MicroPython Code Block: + + .. code-block:: python + + data = label_plus0.get_data() + """ return self._data def setColor(self, fg_color, bg_color=0x000000): # noqa: N802 + """Sets the text font color of the Label object. + + :param int fg_color: The text color in hexadecimal format. + :param int bg_color: The background color in hexadecimal format. + + UiFlow2 Code Block: + + |setColor.png| + + MicroPython Code Block: + + .. code-block:: python + + label_plus0.setColor(0xFFFFFF, 0x000000) + """ self._text_color = fg_color super().setColor(fg_color, bg_color) From 01c8b3e557b6386cce050e0457e19a1f63176367 Mon Sep 17 00:00:00 2001 From: lbuque <1102390310@qq.com> Date: Mon, 15 Sep 2025 10:50:36 +0800 Subject: [PATCH 263/322] libs/m5ui/bar.py: Add warning prompts. Signed-off-by: lbuque <1102390310@qq.com> --- docs/en/m5ui/bar.rst | 36 --------------------------- m5stack/libs/m5ui/bar.py | 54 ++++++++++++++++++++++++++++++++++++++-- tests/m5ui/test_bar.py | 37 +++++++++++++++++++++++++++ 3 files changed, 89 insertions(+), 38 deletions(-) create mode 100644 tests/m5ui/test_bar.py diff --git a/docs/en/m5ui/bar.rst b/docs/en/m5ui/bar.rst index befc6c19..2df6d24f 100644 --- a/docs/en/m5ui/bar.rst +++ b/docs/en/m5ui/bar.rst @@ -55,24 +55,6 @@ M5Bar .. autoclass:: m5ui.bar.M5Bar :members: - .. py:method:: set_value(value, anim_enable) - - Set the current value of the bar. - - :param int value: The value to set. - :param bool anim_enable: Whether to enable animation when changing the value. - :return: None - - UiFlow2 Code Block: - - |set_value.png| - - MicroPython Code Block: - - .. code-block:: python - - bar.set_value(75, True) - .. py:method:: get_value() Get the current value of the bar. @@ -90,24 +72,6 @@ M5Bar current_value = bar.get_value() - .. py:method:: set_range(min_value, max_value) - - Set the value range of the bar. - - :param int min_value: The minimum value. - :param int max_value: The maximum value. - :return: None - - UiFlow2 Code Block: - - |set_range.png| - - MicroPython Code Block: - - .. code-block:: python - - bar.set_range(0, 200) - .. py:method:: get_min_value() Get the minimum value of the bar range. diff --git a/m5stack/libs/m5ui/bar.py b/m5stack/libs/m5ui/bar.py index 5311ea5f..b4492060 100644 --- a/m5stack/libs/m5ui/bar.py +++ b/m5stack/libs/m5ui/bar.py @@ -4,6 +4,7 @@ from .base import M5Base import lvgl as lv +import warnings class M5Bar(lv.bar): @@ -53,16 +54,65 @@ def __init__( self.set_pos(x, y) self.set_size(w, h) - self.set_range(min_value, max_value) + super().set_range(min_value, max_value) self.set_bg_color( bg_c, 51, lv.PART.MAIN | lv.STATE.DEFAULT ) # default opacity is 51 (20% opacity) self.set_bg_color(color, lv.OPA.COVER, lv.PART.INDICATOR | lv.STATE.DEFAULT) - self.set_value(value, True) + super().set_value(value, True) if is_show_value: self.add_event_cb(self._draw_cb, lv.EVENT.DRAW_MAIN_END, None) + def set_value(self, value: int, anim: bool = False) -> None: + """Set the current value of the bar. + + :param int value: The value to set. + :param bool anim_enable: Whether to enable animation when changing the value. + :return: None + + UiFlow2 Code Block: + + |set_value.png| + + MicroPython Code Block: + + .. code-block:: python + + bar.set_value(75, True) + """ + if not isinstance(value, int): + raise ValueError("Value must be an integer.") + if value < self.get_min_value(): + warnings.warn(f"Value is less than min_value, setting to {self.get_min_value()}.") + value = self.get_min_value() + if value > self.get_max_value(): + warnings.warn(f"Value is greater than max_value, setting to {self.get_max_value()}.") + value = self.get_max_value() + super().set_value(value, anim) + + def set_range(self, min_value: int, max_value: int) -> None: + """Set the value range of the bar. + + :param int min_value: The minimum value. + :param int max_value: The maximum value. + :return: None + + UiFlow2 Code Block: + + |set_range.png| + + MicroPython Code Block: + + .. code-block:: python + + bar.set_range(0, 200) + """ + if not isinstance(min_value, int) or not isinstance(max_value, int): + raise ValueError("min_value and max_value must be integers.") + super().set_range(min_value, max_value) + self.set_value(self.get_value(), False) + def _draw_cb(self, event_struct): label_dsc = lv.draw_label_dsc_t() label_dsc.init() diff --git a/tests/m5ui/test_bar.py b/tests/m5ui/test_bar.py new file mode 100644 index 00000000..1f31a9b7 --- /dev/null +++ b/tests/m5ui/test_bar.py @@ -0,0 +1,37 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import lvgl as lv +import sys +import time + +sys.path.append("../../m5stack/libs") +import m5ui +import unittest + + +class Test(unittest.TestCase): + def __init__(self) -> None: + super().__init__() + m5ui.init() + page0 = m5ui.M5Page() + self.bar0 = m5ui.M5Bar(value=25, parent=page0) + page0.screen_load() + + def test_set_range(self): + self.bar0.set_range(40, 50) + self.assertEqual(self.bar0.get_value(), 40) + + def test_set_value(self): + self.bar0.set_value(60) + self.assertEqual(self.bar0.get_value(), 50) + self.bar0.set_value(30) + self.assertEqual(self.bar0.get_value(), 40) + + def tearDown(self): + time.sleep(3) + + +if __name__ == "__main__": + unittest.main() From d19acc6cbfe1af0cb30afa011cacad4252f57369 Mon Sep 17 00:00:00 2001 From: lbuque <1102390310@qq.com> Date: Tue, 16 Sep 2025 15:55:01 +0800 Subject: [PATCH 264/322] libs/m5ui: Fix some M5UI bugs. Signed-off-by: lbuque <1102390310@qq.com> --- docs/en/m5ui/slider.rst | 36 ------------------- docs/en/m5ui/textarea.rst | 17 --------- m5stack/libs/m5ui/port.py | 1 + m5stack/libs/m5ui/slider.py | 67 +++++++++++++++++++++++++++++++++-- m5stack/libs/m5ui/textarea.py | 24 +++++++++++++ tests/m5ui/test_slider.py | 38 ++++++++++++++++++++ 6 files changed, 128 insertions(+), 55 deletions(-) create mode 100644 tests/m5ui/test_slider.py diff --git a/docs/en/m5ui/slider.rst b/docs/en/m5ui/slider.rst index 579aa5b5..1f3c2890 100644 --- a/docs/en/m5ui/slider.rst +++ b/docs/en/m5ui/slider.rst @@ -223,24 +223,6 @@ M5Slider bar.set_bg_grad_color(0x00FF00, 255, 0xFF0000, 255, lv.GRAD_DIR.HOR, lv.PART.MAIN | lv.STATE.DEFAULT) bar.set_bg_grad_color(0x00FF00, 255, 0xFF0000, 255, lv.GRAD_DIR.HOR, lv.PART.INDICATOR | lv.STATE.DEFAULT) - .. py:method:: set_value(value, anim) - - Set the value of the slider. - - :param int value: The value to set. - :param bool anim: Whether to animate the change. - :return: None - - UiFlow2 Code Block: - - |set_value.png| - - MicroPython Code Block: - - .. code-block:: python - - slider_0.set_value(50, True) - .. py:method:: get_value() Get the current value of the slider. @@ -258,24 +240,6 @@ M5Slider value = slider_0.get_value() - .. py:method:: set_range(min_value, max_value) - - Set the range of the slider. - - :param int min_value: The minimum value of the range. - :param int max_value: The maximum value of the range. - :return: None - - UiFlow2 Code Block: - - |set_range.png| - - MicroPython Code Block: - - .. code-block:: python - - slider_0.set_range(0, 200) - .. py:method:: set_mode(mode) Set the mode of the slider. diff --git a/docs/en/m5ui/textarea.rst b/docs/en/m5ui/textarea.rst index d17b55db..d384757f 100644 --- a/docs/en/m5ui/textarea.rst +++ b/docs/en/m5ui/textarea.rst @@ -409,23 +409,6 @@ M5TextArea textarea_0.set_password_mode(True) - .. py:method:: set_one_line(text) - - Set whether the textarea should be single line or multi-line. - - :param bool text: True for single line, False for multi-line. - :return: None - - UiFlow2 Code Block: - - |set_one_line.png| - - MicroPython Code Block: - - .. code-block:: python - - textarea_0.set_one_line(True) - .. py:method:: set_accepted_chars(chars) Set the characters that are accepted in the textarea. Only these characters can be entered. diff --git a/m5stack/libs/m5ui/port.py b/m5stack/libs/m5ui/port.py index c99087ee..c2f9dee0 100644 --- a/m5stack/libs/m5ui/port.py +++ b/m5stack/libs/m5ui/port.py @@ -84,5 +84,6 @@ def deinit(): import M5 M5.Lcd.lvgl_deinit() + lv.mp_lv_deinit_gc() else: pass diff --git a/m5stack/libs/m5ui/slider.py b/m5stack/libs/m5ui/slider.py index 9a9f8741..8aaf22ef 100644 --- a/m5stack/libs/m5ui/slider.py +++ b/m5stack/libs/m5ui/slider.py @@ -4,6 +4,7 @@ from .base import M5Base import lvgl as lv +import warnings class M5Slider(lv.slider): @@ -21,6 +22,19 @@ class M5Slider(lv.slider): :param bg_c: The background color of the slider. :param color: The color of the slider indicator. :param parent: The parent object of the slider. If not specified, it will be set to the active screen. + + UiFlow2 Code Block: + + None + + MicroPython Code Block: + + .. code-block:: python + + from m5ui import M5Slider + import lvgl as lv + + slider_0 = M5Slider(x=50, y=50, w=200, h=20, min_value=0, max_value=100, value=25) """ def __init__( @@ -44,11 +58,60 @@ def __init__( self.set_size(w, h) self.set_pos(x, y) self.set_mode(mode) - self.set_range(min_value, max_value) - self.set_value(value, False) + super().set_range(min_value, max_value) + super().set_value(value, False) self.set_bg_color(bg_c, 51, lv.PART.MAIN | lv.STATE.DEFAULT) self.set_bg_color(color, lv.OPA.COVER, lv.PART.INDICATOR | lv.STATE.DEFAULT) + def set_value(self, value: int, anim: bool = False) -> None: + """Set the value of the slider. + + :param int value: The value to set. + :param bool anim: Whether to animate the change. + :return: None + + UiFlow2 Code Block: + + |set_value.png| + + MicroPython Code Block: + + .. code-block:: python + + slider_0.set_value(50, True) + """ + if not isinstance(value, int): + raise ValueError("Value must be an integer.") + if value < self.get_min_value(): + warnings.warn(f"Value is less than min_value, setting to {self.get_min_value()}.") + value = self.get_min_value() + if value > self.get_max_value(): + warnings.warn(f"Value is greater than max_value, setting to {self.get_max_value()}.") + value = self.get_max_value() + super().set_value(value, anim) + + def set_range(self, min_value: int, max_value: int) -> None: + """Set the range of the slider. + + :param int min_value: The minimum value of the range. + :param int max_value: The maximum value of the range. + :return: None + + UiFlow2 Code Block: + + |set_range.png| + + MicroPython Code Block: + + .. code-block:: python + + slider_0.set_range(0, 200) + """ + if not isinstance(min_value, int) or not isinstance(max_value, int): + raise ValueError("min_value and max_value must be integers.") + super().set_range(min_value, max_value) + self.set_value(self.get_value(), False) + def set_style_radius(self, radius: int, part: int) -> None: if radius < 0: raise ValueError("Radius must be a non-negative integer.") diff --git a/m5stack/libs/m5ui/textarea.py b/m5stack/libs/m5ui/textarea.py index 2473d282..84f02e0c 100644 --- a/m5stack/libs/m5ui/textarea.py +++ b/m5stack/libs/m5ui/textarea.py @@ -49,6 +49,30 @@ def __init__( self.set_border_color(border_c, lv.OPA.COVER, lv.PART.MAIN | lv.STATE.DEFAULT) self.set_text_color(text_c, lv.OPA.COVER, lv.PART.MAIN | lv.STATE.DEFAULT) + self._h = h + + def set_one_line(self, en: bool) -> None: + """Set whether the textarea should be single line or multi-line. + + :param bool text: True for single line, False for multi-line. + :return: None + + UiFlow2 Code Block: + + |set_one_line.png| + + MicroPython Code Block: + + .. code-block:: python + + textarea_0.set_one_line(True) + """ + if en: + self._h = self.get_height() + super().set_one_line(en) + if not en: + self.set_height(self._h) + def set_style_radius(self, radius: int, part: int) -> None: if radius < 0: raise ValueError("Radius must be a non-negative integer.") diff --git a/tests/m5ui/test_slider.py b/tests/m5ui/test_slider.py new file mode 100644 index 00000000..52f4eb5b --- /dev/null +++ b/tests/m5ui/test_slider.py @@ -0,0 +1,38 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import lvgl as lv +import sys +import time + +sys.path.append("../../m5stack/libs") +import m5ui +import unittest + + +class Test(unittest.TestCase): + def __init__(self) -> None: + super().__init__() + m5ui.init() + page0 = m5ui.M5Page() + self.slider0 = m5ui.M5Slider(value=25, parent=page0) + self.slider0.set_pos(50, 50) + page0.screen_load() + + def test_set_range(self): + self.slider0.set_range(40, 50) + self.assertEqual(self.slider0.get_value(), 40) + + def test_set_value(self): + self.slider0.set_value(60) + self.assertEqual(self.slider0.get_value(), 50) + self.slider0.set_value(30) + self.assertEqual(self.slider0.get_value(), 40) + + def tearDown(self): + time.sleep(3) + + +if __name__ == "__main__": + unittest.main() From 78fd98b6d627d97e1761f40027852b874fb5d7e7 Mon Sep 17 00:00:00 2001 From: hlym123 Date: Thu, 18 Sep 2025 15:43:20 +0800 Subject: [PATCH 265/322] boards: Add AtomEchoS3R Support. Signed-off-by: hlym123 --- m5stack/Makefile | 130 +++---- m5stack/boards/Kconfig | 6 + .../M5STACK_Atom_EchoS3R/audioconfigboard.h | 121 +++++++ .../boards/M5STACK_Atom_EchoS3R/board.json | 18 + .../boards/M5STACK_Atom_EchoS3R/board_init.c | 335 ++++++++++++++++++ .../boards/M5STACK_Atom_EchoS3R/manifest.py | 7 + .../M5STACK_Atom_EchoS3R/mpconfigboard.cmake | 49 +++ .../M5STACK_Atom_EchoS3R/mpconfigboard.h | 21 ++ .../M5STACK_Atom_EchoS3R/sdkconfig.board | 23 ++ m5stack/boards/include/board_init.h | 3 + m5stack/cmodules/adf_module/audio_recorder.c | 1 - m5stack/cmodules/m5unified/m5unified.c | 1 + m5stack/libs/hardware/ir.py | 1 + m5stack/modules/startup/__init__.py | 5 + m5stack/modules/startup/atom_echos3r.py | 76 ++++ .../modules/startup/manifest_atom_echos3r.py | 13 + tools/ci.sh | 2 + 17 files changed, 747 insertions(+), 65 deletions(-) create mode 100644 m5stack/boards/M5STACK_Atom_EchoS3R/audioconfigboard.h create mode 100644 m5stack/boards/M5STACK_Atom_EchoS3R/board.json create mode 100644 m5stack/boards/M5STACK_Atom_EchoS3R/board_init.c create mode 100644 m5stack/boards/M5STACK_Atom_EchoS3R/manifest.py create mode 100644 m5stack/boards/M5STACK_Atom_EchoS3R/mpconfigboard.cmake create mode 100644 m5stack/boards/M5STACK_Atom_EchoS3R/mpconfigboard.h create mode 100644 m5stack/boards/M5STACK_Atom_EchoS3R/sdkconfig.board create mode 100644 m5stack/modules/startup/atom_echos3r.py create mode 100644 m5stack/modules/startup/manifest_atom_echos3r.py diff --git a/m5stack/Makefile b/m5stack/Makefile index 9805ef6b..fa3aa145 100644 --- a/m5stack/Makefile +++ b/m5stack/Makefile @@ -11,38 +11,39 @@ BOARD ?= M5STACK_AtomS3 boards := \ - M5STACK_AtomS3:atoms3 \ - M5STACK_AtomS3_Lite:atoms3-lite \ - M5STACK_StampS3:stamps3 \ - M5STACK_CoreS3:cores3 \ - M5STACK_AtomS3U:atoms3u \ - M5STACK_Core2:core2 \ - M5STACK_Tough:tough \ - M5STACK_StickC_PLUS2:stickcplus2 \ - M5STACK_StickC_PLUS:stickcplus \ - M5STACK_Fire:fire \ - M5STACK_NanoC6:nanoc6 \ - M5STACK_Basic:basic \ - M5STACK_Basic_4MB:basic \ - M5STACK_Capsule:capsule \ - M5STACK_CoreInk:coreink \ - M5STACK_AirQ:airq \ - M5STACK_Dial:dial \ - M5STACK_Cardputer:cardputer \ - M5STACK_Paper:paper \ - M5STACK_PaperS3:papers3 \ - M5STACK_DinMeter:dinmeter \ - M5STACK_StickC:stickc \ - M5STACK_Station:station \ - M5STACK_Atom_Lite:atom-lite \ - M5STACK_Stamp_PICO:stamppico \ - M5STACK_Atom_Matrix:atommatrix \ - M5STACK_AtomU:atomu \ - M5STACK_Atom_Echo:atomecho \ - M5STACK_AtomS3R:atoms3r \ - M5STACK_AtomS3R_CAM:atoms3r_cam \ - M5STACK_StamPLC:stamplc \ - M5STACK_Tab5:tab5 \ + M5STACK_AtomS3:atoms3 \ + M5STACK_AtomS3_Lite:atoms3-lite \ + M5STACK_StampS3:stamps3 \ + M5STACK_CoreS3:cores3 \ + M5STACK_AtomS3U:atoms3u \ + M5STACK_Core2:core2 \ + M5STACK_Tough:tough \ + M5STACK_StickC_PLUS2:stickcplus2 \ + M5STACK_StickC_PLUS:stickcplus \ + M5STACK_Fire:fire \ + M5STACK_NanoC6:nanoc6 \ + M5STACK_Basic:basic \ + M5STACK_Basic_4MB:basic \ + M5STACK_Capsule:capsule \ + M5STACK_CoreInk:coreink \ + M5STACK_AirQ:airq \ + M5STACK_Dial:dial \ + M5STACK_Cardputer:cardputer \ + M5STACK_Paper:paper \ + M5STACK_PaperS3:papers3 \ + M5STACK_DinMeter:dinmeter \ + M5STACK_StickC:stickc \ + M5STACK_Station:station \ + M5STACK_Atom_Lite:atom-lite \ + M5STACK_Stamp_PICO:stamppico \ + M5STACK_Atom_Matrix:atommatrix \ + M5STACK_AtomU:atomu \ + M5STACK_Atom_Echo:atomecho \ + M5STACK_AtomS3R:atoms3r \ + M5STACK_AtomS3R_CAM:atoms3r_cam \ + M5STACK_Atom_EchoS3R:atom_echos3r \ + M5STACK_StamPLC:stamplc \ + M5STACK_Tab5:tab5 \ M5STACK_CardputerADV:cardputeradv define find_board @@ -51,38 +52,39 @@ endef # Board type list BOARD_TYPE_DEF := \ - none \ - atoms3 \ - atoms3-lite \ - stamps3 \ - cores3 \ - atoms3u \ - core2 \ - tough \ - stickcplus2 \ - stickcplus \ - fire \ - nanoc6 \ - basic \ - capsule \ - coreink \ - airq \ - dial \ - cardputer \ - paper \ - papers3 \ - dinmeter \ - stickc \ - station \ - atom-lite \ - stamppico \ - atommatrix \ - atomu \ - atomecho \ - atoms3r \ - atoms3r_cam \ - stamplc \ - tab5 \ + none \ + atoms3 \ + atoms3-lite \ + stamps3 \ + cores3 \ + atoms3u \ + core2 \ + tough \ + stickcplus2 \ + stickcplus \ + fire \ + nanoc6 \ + basic \ + capsule \ + coreink \ + airq \ + dial \ + cardputer \ + paper \ + papers3 \ + dinmeter \ + stickc \ + station \ + atom-lite \ + stamppico \ + atommatrix \ + atomu \ + atomecho \ + atoms3r \ + atoms3r_cam \ + atom_echos3r \ + stamplc \ + tab5 \ cardputeradv # Select the board type to build, default is None diff --git a/m5stack/boards/Kconfig b/m5stack/boards/Kconfig index dd22ec7c..ee03a5c2 100755 --- a/m5stack/boards/Kconfig +++ b/m5stack/boards/Kconfig @@ -11,4 +11,10 @@ menu "Audio Board Configuration" default n help Enable this option to support m5stack tab5. + + config ATOM_ECHOS3R + bool "select M5STACK ATOM ECHOS3R" + default n + help + Enable this option to support m5stack atom_echos3r. endmenu diff --git a/m5stack/boards/M5STACK_Atom_EchoS3R/audioconfigboard.h b/m5stack/boards/M5STACK_Atom_EchoS3R/audioconfigboard.h new file mode 100644 index 00000000..d26228c8 --- /dev/null +++ b/m5stack/boards/M5STACK_Atom_EchoS3R/audioconfigboard.h @@ -0,0 +1,121 @@ +/* +* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD +* +* SPDX-License-Identifier: MIT +*/ + +#if (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 2, 0) && ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0)) +#include "driver/i2s.h" +#else +#include "driver/i2s_pdm.h" +#include "driver/i2s_tdm.h" +#include "driver/i2s_std.h" +#endif + +#define BOARD_TASK_CORE (1) + +// mp3 decoder +#define BOARD_MP3_DECODER_CONFIG() { \ + .out_rb_size = MP3_DECODER_RINGBUFFER_SIZE, \ + .task_stack = MP3_DECODER_TASK_STACK_SIZE, \ + .task_core = BOARD_TASK_CORE, \ + .task_prio = MP3_DECODER_TASK_PRIO, \ + .stack_in_ext = true, \ +} + +// amr decoder +#define BOARD_AMR_DECODER_CONFIG() { \ + .out_rb_size = AMR_DECODER_RINGBUFFER_SIZE, \ + .task_stack = AMR_DECODER_TASK_STACK_SIZE, \ + .task_core = BOARD_TASK_CORE, \ + .task_prio = AMR_DECODER_TASK_PRIO, \ + .stack_in_ext = true, \ + } + +// wav decoder +#define BOARD_WAV_DECODER_CONFIG() { \ + .out_rb_size = WAV_DECODER_RINGBUFFER_SIZE, \ + .task_stack = WAV_DECODER_TASK_STACK, \ + .task_core = BOARD_TASK_CORE, \ + .task_prio = WAV_DECODER_TASK_PRIO, \ + .stack_in_ext = true, \ +} + +// pcm decoder +#define BOARD_PCM_DECODER_CONFIG() { \ + .out_rb_size = PCM_DECODER_RINGBUFFER_SIZE, \ + .task_stack = PCM_DECODER_TASK_STACK, \ + .task_core = BOARD_TASK_CORE, \ + .task_prio = PCM_DECODER_TASK_PRIO, \ + .stack_in_ext = true, \ + .rate = 8000, \ + .bits = 16, \ + .channels = 1, \ +} + +// i2s stream +#if (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 2, 0) && ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0)) +#define BOARD_I2S_STREAM_CFG_DEFAULT() { \ + .type = AUDIO_STREAM_WRITER, \ + .i2s_config = { \ + .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX | I2S_MODE_RX), \ + .sample_rate = 48000, \ + .bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT, \ + .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT, \ + .communication_format = I2S_COMM_FORMAT_STAND_I2S, \ + .intr_alloc_flags = ESP_INTR_FLAG_LEVEL2 | ESP_INTR_FLAG_IRAM, \ + .dma_buf_count = 3, \ + .dma_buf_len = 300, \ + .use_apll = true, \ + .tx_desc_auto_clear = true, \ + .fixed_mclk = 0 \ + }, \ + .i2s_port = I2S_NUM_1, \ + .use_alc = false, \ + .volume = 0, \ + .out_rb_size = I2S_STREAM_RINGBUFFER_SIZE, \ + .task_stack = I2S_STREAM_TASK_STACK, \ + .task_core = BOARD_TASK_CORE, \ + .task_prio = I2S_STREAM_TASK_PRIO, \ + .stack_in_ext = false, \ + .multi_out_num = 0, \ + .uninstall_drv = false, \ + .need_expand = false, \ + .expand_src_bits = I2S_BITS_PER_SAMPLE_16BIT, \ + .buffer_len = I2S_STREAM_BUF_SIZE, \ +} +#else +#define BOARD_I2S_STREAM_CFG_DEFAULT() { \ + .type = AUDIO_STREAM_WRITER, \ + .std_cfg = { \ + .clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(48000), \ + .slot_cfg = I2S_STD_PHILIPS_SLOT_DEFAULT_ADF_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_STEREO), \ + .gpio_cfg = { \ + .invert_flags = { \ + .mclk_inv = false, \ + .bclk_inv = false, \ + }, \ + }, \ + }, \ + .transmit_mode = I2S_COMM_MODE_STD, \ + .chan_cfg = { \ + .id = I2S_NUM_1, \ + .role = I2S_ROLE_MASTER, \ + .dma_desc_num = 3, \ + .dma_frame_num = 300, \ + .auto_clear = true, \ + }, \ + .use_alc = false, \ + .volume = 0, \ + .out_rb_size = I2S_STREAM_RINGBUFFER_SIZE, \ + .task_stack = I2S_STREAM_TASK_STACK, \ + .task_core = BOARD_TASK_CORE, \ + .task_prio = I2S_STREAM_TASK_PRIO, \ + .stack_in_ext = false, \ + .multi_out_num = 0, \ + .uninstall_drv = false, \ + .need_expand = false, \ + .expand_src_bits = I2S_DATA_BIT_WIDTH_16BIT, \ + .buffer_len = I2S_STREAM_BUF_SIZE, \ +} +#endif diff --git a/m5stack/boards/M5STACK_Atom_EchoS3R/board.json b/m5stack/boards/M5STACK_Atom_EchoS3R/board.json new file mode 100644 index 00000000..096f750e --- /dev/null +++ b/m5stack/boards/M5STACK_Atom_EchoS3R/board.json @@ -0,0 +1,18 @@ +{ + "deploy": [ + "../deploy_s3.md" + ], + "docs": "", + "features": [ + "BLE", + "WiFi" + ], + "images": [ + "generic_s3.jpg" + ], + "mcu": "esp32s3", + "product": "M5Stack S3 Serials", + "thumbnail": "", + "url": "https://www.espressif.com/en/products/modules", + "vendor": "M5Stack" +} diff --git a/m5stack/boards/M5STACK_Atom_EchoS3R/board_init.c b/m5stack/boards/M5STACK_Atom_EchoS3R/board_init.c new file mode 100644 index 00000000..aa7d1a8a --- /dev/null +++ b/m5stack/boards/M5STACK_Atom_EchoS3R/board_init.c @@ -0,0 +1,335 @@ +/* +* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD +* +* SPDX-License-Identifier: MIT +*/ + +// NOTE: 使用IDF5的 i2s 驱动 +#define USE_IDF5 (1) + +// #include "esp_idf_version.h" +#if USE_IDF5 +#include "driver/i2s_std.h" +#include "driver/i2s_tdm.h" +#include "soc/soc_caps.h" +#else +#include "driver/i2s.h" +#endif + +#include "string.h" +#include "board_init.h" +#include "esp_log.h" +#include "driver/gpio.h" +#include "esp_codec_dev.h" +#include "esp_codec_dev_defaults.h" + +#if 0 //ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0) +#include "driver/i2c_master.h" +#define USE_IDF_I2C_MASTER +#else +#include "driver/i2c.h" +#endif + +static char *TAG = "atom_echos3r"; +static void *audio_hal = NULL; + + +#if USE_IDF5 + +#define I2S_MAX_KEEP SOC_I2S_NUM + +typedef struct { + i2s_chan_handle_t tx_handle; + i2s_chan_handle_t rx_handle; +} i2s_keep_t; + +static i2s_comm_mode_t i2s_in_mode = I2S_COMM_MODE_STD; +static i2s_comm_mode_t i2s_out_mode = I2S_COMM_MODE_STD; +static i2s_keep_t *i2s_keep[I2S_MAX_KEEP]; +#endif + +#ifdef USE_IDF_I2C_MASTER +static i2c_master_bus_handle_t i2c_bus_handle; +#endif + +static int ut_i2c_init(uint8_t port); +static int ut_i2c_deinit(uint8_t port); + +static int ut_i2s_init(uint8_t port); +static int ut_i2s_deinit(uint8_t port); + +#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_11 +#define AUDIO_I2S_GPIO_WS GPIO_NUM_3 +#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_17 +#define AUDIO_I2S_GPIO_DIN GPIO_NUM_4 +#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_48 +#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_45 +#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_0 +#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR +#define AUDIO_CODEC_GPIO_PA GPIO_NUM_18 + + +esp_err_t get_i2s_pins(int port, board_i2s_pin_t *i2s_config) +{ + ESP_LOGI(TAG, "get_i2s_pins !!!"); + AUDIO_NULL_CHECK(TAG, i2s_config, return ESP_FAIL); + if (port == 1) { + i2s_config->bck_io_num = AUDIO_I2S_GPIO_BCLK; + i2s_config->ws_io_num = AUDIO_I2S_GPIO_WS; + i2s_config->data_out_num = AUDIO_I2S_GPIO_DOUT; + i2s_config->data_in_num = AUDIO_I2S_GPIO_DIN; + i2s_config->mck_io_num = AUDIO_I2S_GPIO_MCLK; + } else { + memset(i2s_config, -1, sizeof(board_i2s_pin_t)); + ESP_LOGE(TAG, "I2S PORT %d is not supported, please use I2S PORT 0", port); + return ESP_FAIL; + } + return ESP_OK; +} + +void * board_codec_init(void) +{ + if (audio_hal) { + return audio_hal; + } + ESP_LOGI(TAG, "init"); + + int ret = ut_i2c_init(1); + ret |= ut_i2s_init(1); + + audio_codec_i2s_cfg_t i2s_cfg = { + .port = 1, +#if USE_IDF5 + .rx_handle = i2s_keep[1]->rx_handle, + .tx_handle = i2s_keep[1]->tx_handle, +#endif + }; + const audio_codec_data_if_t *data_if = audio_codec_new_i2s_data(&i2s_cfg); + + audio_codec_i2c_cfg_t i2c_cfg = { + .port = 1, + .addr = AUDIO_CODEC_ES8311_ADDR, + }; +#ifdef USE_IDF_I2C_MASTER + i2c_cfg.bus_handle = i2c_bus_handle; +#endif + const audio_codec_ctrl_if_t *ctrl_if = audio_codec_new_i2c_ctrl(&i2c_cfg); + const audio_codec_gpio_if_t *gpio_if = audio_codec_new_gpio(); + + es8311_codec_cfg_t es8311_cfg = {}; + es8311_cfg.ctrl_if = ctrl_if; + es8311_cfg.gpio_if = gpio_if; + es8311_cfg.codec_mode = ESP_CODEC_DEV_WORK_MODE_BOTH; + es8311_cfg.pa_pin = AUDIO_CODEC_GPIO_PA; + es8311_cfg.use_mclk = true; + es8311_cfg.hw_gain.pa_voltage = 5.0; + es8311_cfg.hw_gain.codec_dac_voltage = 3.3; + es8311_cfg.pa_reverted = false; + const audio_codec_if_t *codec_if = es8311_codec_new(&es8311_cfg); + + esp_codec_dev_cfg_t dev_cfg = { + .dev_type = ESP_CODEC_DEV_TYPE_IN_OUT, + .codec_if = codec_if, + .data_if = data_if, + }; + audio_hal = esp_codec_dev_new(&dev_cfg); + + esp_codec_dev_sample_info_t fs = { + .bits_per_sample = 16, + .channel = 1, + .channel_mask = 0, + .sample_rate = (uint32_t)48000, + .mclk_multiple = 0, + }; + esp_codec_dev_open(audio_hal, &fs); + esp_codec_dev_set_in_gain(audio_hal, 30.0); + esp_codec_dev_set_out_vol(audio_hal, 60); + + // i2s_stream_init 会实例化 i2s。初始化 codec 之后,需要将 i2s 释放。 + ut_i2s_deinit(1); + + return audio_hal; +} + + +// NOTE: 使用内联函数??? +int board_codec_volume_set(void *hd, int vol) +{ + return esp_codec_dev_set_out_vol(hd, vol); +} + + +// NOTE: 使用内联函数??? +int board_codec_volume_get(void *hd, int *vol) +{ + return esp_codec_dev_get_out_vol(hd, vol); +} + + +static int ut_i2c_init(uint8_t port) +{ +#ifdef USE_IDF_I2C_MASTER + i2c_master_bus_config_t i2c_bus_config = {0}; + i2c_bus_config.clk_source = I2C_CLK_SRC_DEFAULT; + i2c_bus_config.i2c_port = port; + i2c_bus_config.scl_io_num = AUDIO_CODEC_I2C_SCL_PIN; + i2c_bus_config.sda_io_num = AUDIO_CODEC_I2C_SDA_PIN; + i2c_bus_config.glitch_ignore_cnt = 7; + i2c_bus_config.flags.enable_internal_pullup = true; + return i2c_new_master_bus(&i2c_bus_config, &i2c_bus_handle); +#else + i2c_config_t i2c_cfg = { + .mode = I2C_MODE_MASTER, + .sda_pullup_en = GPIO_PULLUP_ENABLE, + .scl_pullup_en = GPIO_PULLUP_ENABLE, + .master.clk_speed = 100000, + }; + i2c_cfg.scl_io_num = AUDIO_CODEC_I2C_SCL_PIN; + i2c_cfg.sda_io_num = AUDIO_CODEC_I2C_SDA_PIN; + esp_err_t ret = i2c_param_config(port, &i2c_cfg); + if (ret != ESP_OK) { + return -1; + } + return i2c_driver_install(port, i2c_cfg.mode, 0, 0, 0); +#endif +} + + +static int ut_i2c_deinit(uint8_t port) +{ +#ifdef USE_IDF_I2C_MASTER + if (i2c_bus_handle) { + i2c_del_master_bus(i2c_bus_handle); + } + i2c_bus_handle = NULL; + return 0; +#else + return i2c_driver_delete(port); +#endif +} + + +#if USE_IDF5 +static void ut_set_i2s_mode(i2s_comm_mode_t out_mode, i2s_comm_mode_t in_mode) +{ + i2s_in_mode = in_mode; + i2s_out_mode = out_mode; +} + + +static void ut_clr_i2s_mode(void) +{ + i2s_in_mode = I2S_COMM_MODE_STD; + i2s_out_mode = I2S_COMM_MODE_STD; +} +#endif + + +static int ut_i2s_init(uint8_t port) +{ +#if USE_IDF5 + if (port >= I2S_MAX_KEEP) { + return -1; + } + // Already installed + if (i2s_keep[port]) { + return 0; + } + i2s_chan_config_t chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(port, I2S_ROLE_MASTER); + i2s_std_config_t std_cfg = { + .clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(16000), + .slot_cfg = I2S_STD_PHILIPS_SLOT_DEFAULT_CONFIG(16, I2S_SLOT_MODE_STEREO), + .gpio_cfg ={ + .mclk = AUDIO_I2S_GPIO_MCLK, + .bclk = AUDIO_I2S_GPIO_BCLK, + .ws = AUDIO_I2S_GPIO_WS, + .dout = AUDIO_I2S_GPIO_DOUT, + .din = AUDIO_I2S_GPIO_DIN, + }, + }; + i2s_keep[port] = (i2s_keep_t *) calloc(1, sizeof(i2s_keep_t)); + if (i2s_keep[port] == NULL) { + return -1; + } +#if SOC_I2S_SUPPORTS_TDM + i2s_tdm_slot_mask_t slot_mask = I2S_TDM_SLOT0 | I2S_TDM_SLOT1 | I2S_TDM_SLOT2 | I2S_TDM_SLOT3; + i2s_tdm_config_t tdm_cfg = { + .slot_cfg = I2S_TDM_PHILIPS_SLOT_DEFAULT_CONFIG(16, I2S_SLOT_MODE_STEREO, slot_mask), + .clk_cfg = I2S_TDM_CLK_DEFAULT_CONFIG(16000), + .gpio_cfg ={ + .mclk = AUDIO_I2S_GPIO_MCLK, + .bclk = AUDIO_I2S_GPIO_BCLK, + .ws = AUDIO_I2S_GPIO_WS, + .dout = AUDIO_I2S_GPIO_DOUT, + .din = AUDIO_I2S_GPIO_DIN, + }, + }; + tdm_cfg.slot_cfg.total_slot = 4; +#endif + int ret = i2s_new_channel(&chan_cfg, &i2s_keep[port]->tx_handle, &i2s_keep[port]->rx_handle); + if (i2s_out_mode == I2S_COMM_MODE_STD) { + ret = i2s_channel_init_std_mode(i2s_keep[port]->tx_handle, &std_cfg); + } +#if SOC_I2S_SUPPORTS_TDM + else if (i2s_out_mode == I2S_COMM_MODE_TDM) { + ret = i2s_channel_init_tdm_mode(i2s_keep[port]->tx_handle, &tdm_cfg); + } +#endif + if (i2s_in_mode == I2S_COMM_MODE_STD) { + ret = i2s_channel_init_std_mode(i2s_keep[port]->rx_handle, &std_cfg); + } +#if SOC_I2S_SUPPORTS_TDM + else if (i2s_in_mode == I2S_COMM_MODE_TDM) { + ret = i2s_channel_init_tdm_mode(i2s_keep[port]->rx_handle, &tdm_cfg); + } +#endif + // For tx master using duplex mode + i2s_channel_enable(i2s_keep[port]->tx_handle); +#else + i2s_config_t i2s_config = { + .mode = (i2s_mode_t) (I2S_MODE_TX | I2S_MODE_RX | I2S_MODE_MASTER), + .sample_rate = 48000, + .bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT, + .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT, + .communication_format = I2S_COMM_FORMAT_STAND_I2S, + .intr_alloc_flags = ESP_INTR_FLAG_LEVEL2 | ESP_INTR_FLAG_IRAM, + .dma_buf_count = 2, + .dma_buf_len = 128, + .use_apll = true, + .tx_desc_auto_clear = true, + }; + int ret = i2s_driver_install(port, &i2s_config, 0, NULL); + i2s_pin_config_t i2s_pin_cfg = { + .mck_io_num = AUDIO_I2S_GPIO_MCLK, + .bck_io_num = AUDIO_I2S_GPIO_BCLK, + .ws_io_num = AUDIO_I2S_GPIO_WS, + .data_out_num = AUDIO_I2S_GPIO_DOUT, + .data_in_num = AUDIO_I2S_GPIO_DIN, + }; + i2s_set_pin(port, &i2s_pin_cfg); +#endif + return ret; +} + + +static int ut_i2s_deinit(uint8_t port) +{ +#if USE_IDF5 + if (port >= I2S_MAX_KEEP) { + return -1; + } + // already installed + if (i2s_keep[port] == NULL) { + return 0; + } + i2s_channel_disable(i2s_keep[port]->tx_handle); + i2s_channel_disable(i2s_keep[port]->rx_handle); + i2s_del_channel(i2s_keep[port]->tx_handle); + i2s_del_channel(i2s_keep[port]->rx_handle); + free(i2s_keep[port]); + i2s_keep[port] = NULL; +#else + i2s_driver_uninstall(port); +#endif + return 0; +} diff --git a/m5stack/boards/M5STACK_Atom_EchoS3R/manifest.py b/m5stack/boards/M5STACK_Atom_EchoS3R/manifest.py new file mode 100644 index 00000000..9f61255c --- /dev/null +++ b/m5stack/boards/M5STACK_Atom_EchoS3R/manifest.py @@ -0,0 +1,7 @@ +# SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +include("$(MPY_DIR)/../m5stack/modules/startup/manifest_atom_echos3r.py") +include("$(MPY_DIR)/../m5stack/libs/base/manifest.py") +include("$(MPY_DIR)/../m5stack/libs/unit/manifest.py") diff --git a/m5stack/boards/M5STACK_Atom_EchoS3R/mpconfigboard.cmake b/m5stack/boards/M5STACK_Atom_EchoS3R/mpconfigboard.cmake new file mode 100644 index 00000000..b1918fbe --- /dev/null +++ b/m5stack/boards/M5STACK_Atom_EchoS3R/mpconfigboard.cmake @@ -0,0 +1,49 @@ +# SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +set(IDF_TARGET esp32s3) + +# atom_echos3r https://github.com/m5stack/m5stack-board-id/blob/main/board.csv#L21 +set(BOARD_ID 145) + +set(MICROPY_PY_LVGL 0) + +set(SDKCONFIG_DEFAULTS + ./boards/sdkconfig.base + ${SDKCONFIG_IDF_VERSION_SPECIFIC} + ./boards/sdkconfig.240mhz + ./boards/sdkconfig.disable_iram + ./boards/sdkconfig.ble + ./boards/sdkconfig.usb + ./boards/sdkconfig.usb_cdc + ./boards/sdkconfig.flash_8mb + ./boards/sdkconfig.spiram + ./boards/sdkconfig.spiram_oct + ./boards/sdkconfig.freertos + ./boards/M5STACK_Atom_EchoS3R/sdkconfig.board +) + +# If not enable LVGL, ignore this... +set(LV_CFLAGS -DLV_COLOR_DEPTH=16 -DLV_COLOR_16_SWAP=0) + +if(NOT MICROPY_FROZEN_MANIFEST) + set(MICROPY_FROZEN_MANIFEST ${CMAKE_SOURCE_DIR}/boards/manifest.py) +endif() + +set(ADF_MODULE_ENABLE TRUE) + +set(ADF_COMPS "$ENV{ADF_PATH}/components") + +set(ADF_BOARD_INIT_SRC + $ENV{ADF_PATH}/components + M5STACK_Atom_EchoS3R/board_init.c +) + +list(APPEND EXTRA_COMPONENT_DIRS + $ENV{ADF_PATH}/components/audio_pipeline + $ENV{ADF_PATH}/components/audio_sal + $ENV{ADF_PATH}/components/esp-adf-libs + $ENV{ADF_PATH}/components/esp-sr + ${CMAKE_SOURCE_DIR}/boards +) diff --git a/m5stack/boards/M5STACK_Atom_EchoS3R/mpconfigboard.h b/m5stack/boards/M5STACK_Atom_EchoS3R/mpconfigboard.h new file mode 100644 index 00000000..d5ddd7cf --- /dev/null +++ b/m5stack/boards/M5STACK_Atom_EchoS3R/mpconfigboard.h @@ -0,0 +1,21 @@ +/* +* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD +* +* SPDX-License-Identifier: MIT +*/ + +#define MICROPY_HW_BOARD_NAME "M5STACK Atom EchoS3R" +#define MICROPY_HW_MCU_NAME "ESP32-S3-PICO-1" + +#define MICROPY_PY_MACHINE_DAC (0) + +// Enable UART REPL for modules that have an external USB-UART and don't use native USB. +#define MICROPY_HW_ENABLE_UART_REPL (1) + +#define MICROPY_HW_I2C0_SCL (0) +#define MICROPY_HW_I2C0_SDA (45) + +#define MICROPY_HW_USB_CDC_INTERFACE_STRING "M5Stack Atom EchoS3R(UiFlow2)" + +// If not enable LVGL, ignore this... +#include "./../mpconfiglvgl.h" diff --git a/m5stack/boards/M5STACK_Atom_EchoS3R/sdkconfig.board b/m5stack/boards/M5STACK_Atom_EchoS3R/sdkconfig.board new file mode 100644 index 00000000..db25fae5 --- /dev/null +++ b/m5stack/boards/M5STACK_Atom_EchoS3R/sdkconfig.board @@ -0,0 +1,23 @@ +# SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +CONFIG_FLASHMODE_DIO=y # QIO mode has some problem when mount fs +CONFIG_ESPTOOLPY_FLASHMODE_DIO=y +CONFIG_ESPTOOLPY_FLASHMODE="dio" +CONFIG_ESPTOOLPY_FLASHFREQ_80M=y +CONFIG_ESPTOOLPY_FLASHSIZE_DETECT=y +CONFIG_ESPTOOLPY_AFTER_NORESET=y + +CONFIG_SPIRAM_MEMTEST= +# CONFIG_FREERTOS_UNICORE=y + +# M5STACK UiFlow USB description +CONFIG_TINYUSB_DESC_CDC_STRING="M5Stack Atom EchoS3R(UiFlow2)" + +CONFIG_ATOM_ECHOS3R=y +CONFIG_CODEC_ES8311_SUPPORT=y +CONFIG_FREERTOS_ENABLE_BACKWARD_COMPATIBILITY=y + +# SSL +CONFIG_MBEDTLS_AES_USE_INTERRUPT=n diff --git a/m5stack/boards/include/board_init.h b/m5stack/boards/include/board_init.h index 0158f464..a52c1123 100644 --- a/m5stack/boards/include/board_init.h +++ b/m5stack/boards/include/board_init.h @@ -12,8 +12,11 @@ #include "../M5STACK_CoreS3/audioconfigboard.h" #elif CONFIG_TAB5 #include "../M5STACK_Tab5/audioconfigboard.h" +#elif CONFIG_ATOM_ECHOS3R +#include "../M5STACK_Atom_EchoS3R/audioconfigboard.h" #endif + /** * @brief Board i2s pin definition */ diff --git a/m5stack/cmodules/adf_module/audio_recorder.c b/m5stack/cmodules/adf_module/audio_recorder.c index fb21e1b3..0846d34f 100644 --- a/m5stack/cmodules/adf_module/audio_recorder.c +++ b/m5stack/cmodules/adf_module/audio_recorder.c @@ -135,7 +135,6 @@ static void audio_recorder_endtime_task(void *arg) { vTaskDelete(NULL); } - static mp_obj_t audio_recorder_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { enum { ARG_sample, ARG_bits, ARG_stereo, }; static const mp_arg_t allowed_args[] = { diff --git a/m5stack/cmodules/m5unified/m5unified.c b/m5stack/cmodules/m5unified/m5unified.c index b8121647..762bc482 100644 --- a/m5stack/cmodules/m5unified/m5unified.c +++ b/m5stack/cmodules/m5unified/m5unified.c @@ -48,6 +48,7 @@ static const mp_rom_map_elem_t m5_board_member_table[] = { { MP_ROM_QSTR(MP_QSTR_M5AtomMatrix), MP_ROM_INT(141) }, { MP_ROM_QSTR(MP_QSTR_M5AtomEcho), MP_ROM_INT(142) }, { MP_ROM_QSTR(MP_QSTR_M5AtomS3R_CAM), MP_ROM_INT(144) }, + { MP_ROM_QSTR(MP_QSTR_M5AtomEchoS3R), MP_ROM_INT(145) }, // external displays { MP_ROM_QSTR(MP_QSTR_M5ATOMDisplay), MP_ROM_INT(192) }, { MP_ROM_QSTR(MP_QSTR_M5UnitLCD), MP_ROM_INT(193) }, diff --git a/m5stack/libs/hardware/ir.py b/m5stack/libs/hardware/ir.py index 6fb45aef..a8e74a9b 100644 --- a/m5stack/libs/hardware/ir.py +++ b/m5stack/libs/hardware/ir.py @@ -22,6 +22,7 @@ def __init__(self) -> None: M5.BOARD.M5AtomU: (None, 12), M5.BOARD.M5Atom: (None, 12), M5.BOARD.M5AtomEcho: (None, 12), + M5.BOARD.M5AtomEchoS3R: (None, 47), M5.BOARD.M5NanoC6: (None, 3), } (self._rx_pin, self._tx_pin) = _pin_map.get(M5.getBoard()) diff --git a/m5stack/modules/startup/__init__.py b/m5stack/modules/startup/__init__.py index 38160da5..c43717d7 100644 --- a/m5stack/modules/startup/__init__.py +++ b/m5stack/modules/startup/__init__.py @@ -120,6 +120,11 @@ def startup(boot_opt, timeout: int = 60) -> None: atoms3r = AtomS3R_CAM_Startup() atoms3r.startup(ssid, pswd, timeout) + elif board_id == M5.BOARD.M5AtomEchoS3R: + from .atom_echos3r import AtomEchoS3R_Startup + + atom_echos3r = AtomEchoS3R_Startup() + atom_echos3r.startup(ssid, pswd, timeout) elif board_id == M5.BOARD.M5AtomMatrix: from .atommatrix import AtomMatrix_Startup diff --git a/m5stack/modules/startup/atom_echos3r.py b/m5stack/modules/startup/atom_echos3r.py new file mode 100644 index 00000000..68c0b077 --- /dev/null +++ b/m5stack/modules/startup/atom_echos3r.py @@ -0,0 +1,76 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT +# AtomS3R-CAM startup script +import M5 +import time +import network +import machine +import binascii +from . import Startup + + +# AtomEchoS3R startup menu + + +class AtomEchoS3R_Startup(Startup): + def __init__(self) -> None: + super().__init__() + + def show_hits(self, hits: str) -> None: + print(hits) + + def show_msg(self, msg: str) -> None: + print(msg) + + def show_ssid(self, ssid: str) -> None: + if len(ssid) > 9: + self.show_msg(ssid[:7] + "...") + else: + self.show_msg(ssid) + + def show_mac(self) -> None: + mac = binascii.hexlify(machine.unique_id()).decode("utf-8").upper() + print(mac[0:6] + "_" + mac[6:]) + + def show_error(self, ssid: str, error: str) -> None: + self.show_ssid(ssid) + self.show_hits(error) + self.show_mac() + print("SSID: " + ssid + "\r\nNotice: " + error) + + def startup(self, ssid: str, pswd: str, timeout: int = 60) -> None: + self.show_mac() + + if super().connect_network(ssid=ssid, pswd=pswd): + self.show_ssid(ssid) + count = 1 + status = super().connect_status() + start = time.time() + while status is not network.STAT_GOT_IP: + time.sleep_ms(300) + if status is network.STAT_NO_AP_FOUND: + self.show_error(ssid, "NO AP FOUND") + break + elif status is network.STAT_WRONG_PASSWORD: + self.show_error(ssid, "WRONG PASSWORD") + break + elif status is network.STAT_HANDSHAKE_TIMEOUT: + self.show_error(ssid, "HANDSHAKE ERR") + break + elif status is network.STAT_CONNECTING: + self.show_hits("." * count) + count = count + 1 + if count > 5: + count = 1 + status = super().connect_status() + # connect to network timeout + if (time.time() - start) > timeout: + self.show_error(ssid, "TIMEOUT") + break + + if status is network.STAT_GOT_IP: + self.show_hits(super().local_ip()) + print("Local IP: " + super().local_ip()) + else: + self.show_error("Not Found", "Use Burner setup") diff --git a/m5stack/modules/startup/manifest_atom_echos3r.py b/m5stack/modules/startup/manifest_atom_echos3r.py new file mode 100644 index 00000000..de2e8388 --- /dev/null +++ b/m5stack/modules/startup/manifest_atom_echos3r.py @@ -0,0 +1,13 @@ +# SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +package( + "startup", + ( + "__init__.py", + "atom_echos3r.py", + ), + base_path="..", + opt=3, +) diff --git a/tools/ci.sh b/tools/ci.sh index b42ca9a1..9c861e1e 100644 --- a/tools/ci.sh +++ b/tools/ci.sh @@ -298,6 +298,7 @@ function ci_base_build { make ${MAKEOPTS} -C m5stack mpy-cross make ${MAKEOPTS} -C m5stack BOARD=M5STACK_Atom_Lite pack_all make ${MAKEOPTS} -C m5stack BOARD=M5STACK_AtomS3 pack_all + make ${MAKEOPTS} -C m5stack BOARD=M5STACK_Atom_EchoS3R pack_all make ${MAKEOPTS} -C third-party BOARD=ESPRESSIF_ESP32_S3_BOX_3 pack_all } @@ -334,6 +335,7 @@ function ci_esp32_nightly_build { make ${MAKEOPTS} -C m5stack BOARD=M5STACK_Atom_Echo pack_all make ${MAKEOPTS} -C m5stack BOARD=M5STACK_Atom_Lite pack_all make ${MAKEOPTS} -C m5stack BOARD=M5STACK_Atom_Matrix pack_all + make ${MAKEOPTS} -C m5stack BOARD=M5STACK_Atom_EchoS3R pack_all make ${MAKEOPTS} -C m5stack BOARD=M5STACK_AtomS3 pack_all make ${MAKEOPTS} -C m5stack BOARD=M5STACK_AtomS3_Lite pack_all make ${MAKEOPTS} -C m5stack BOARD=M5STACK_AtomS3R pack_all From 54db02517f0a8a352e888323b486e2dcf29aec1c Mon Sep 17 00:00:00 2001 From: tinyu Date: Thu, 18 Sep 2025 16:46:50 +0800 Subject: [PATCH 266/322] libs/unit: Add command index search to ASR unit. Signed-off-by: tinyu --- m5stack/libs/unit/asr.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/m5stack/libs/unit/asr.py b/m5stack/libs/unit/asr.py index ace7e6df..cbb4bf9b 100644 --- a/m5stack/libs/unit/asr.py +++ b/m5stack/libs/unit/asr.py @@ -287,6 +287,9 @@ def search_command_num(self, command_word: str) -> int: asr.search_command_num("custom command") """ + for key, value in self._COMMAND_LIST.items(): + if value[0] == command_word: + return key return -1 def search_command_word(self, command_num: int) -> str: From 0b369add44e05720f900c94f14306cd87e185994 Mon Sep 17 00:00:00 2001 From: tinyu Date: Thu, 18 Sep 2025 11:17:41 +0800 Subject: [PATCH 267/322] libs/unit: Fix label shadow when created via parent component. Signed-off-by: tinyu --- m5stack/libs/m5ui/label.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/m5stack/libs/m5ui/label.py b/m5stack/libs/m5ui/label.py index 6faeb362..1fcb090b 100644 --- a/m5stack/libs/m5ui/label.py +++ b/m5stack/libs/m5ui/label.py @@ -2,7 +2,7 @@ # # SPDX-License-Identifier: MIT -from .base import M5Base +from m5ui.base import M5Base import lvgl as lv @@ -83,6 +83,19 @@ def set_shadow(self, color: int, opa: int, align: int, offset_x: int, offset_y: self._shadow_label.set_text(self.get_text()) self._shadow_label.set_style_text_color(color, lv.PART.MAIN | lv.STATE.DEFAULT) self._shadow_label.set_style_text_opa(opa, lv.PART.MAIN | lv.STATE.DEFAULT) + self._shadow_label.set_style_text_font( + self.get_style_text_font(lv.PART.MAIN), lv.PART.MAIN + ) + self._shadow_label.set_width(self.get_width()) + + for part in (lv.PART.MAIN,): + self._shadow_label.set_style_pad_left(self.get_style_pad_left(part), part) + self._shadow_label.set_style_pad_right(self.get_style_pad_right(part), part) + self._shadow_label.set_style_pad_top(self.get_style_pad_top(part), part) + self._shadow_label.set_style_pad_bottom(self.get_style_pad_bottom(part), part) + + if isinstance(self.get_parent(), lv.list): + offset_x -= self.get_style_pad_left(part) self._shadow_label.align_to(self, align, offset_x, offset_y) def unset_shadow(self) -> None: From 3c040688d9c4a79710d969f9b6817e58c98b8a6d Mon Sep 17 00:00:00 2001 From: tinyu Date: Thu, 18 Sep 2025 16:35:30 +0800 Subject: [PATCH 268/322] libs/unit: Fix LVGL win switch width float issue. Signed-off-by: tinyu --- m5stack/libs/m5ui/win.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/m5stack/libs/m5ui/win.py b/m5stack/libs/m5ui/win.py index c18fe9ca..c99406fc 100644 --- a/m5stack/libs/m5ui/win.py +++ b/m5stack/libs/m5ui/win.py @@ -7,7 +7,7 @@ import m5ui try: - DPI = lv.DPI_DEF / 3 + DPI = int(lv.DPI_DEF / 3) except Exception: DPI = 80 From 71f7756ab0d3b58729a895d15e7f60afafbf5fcd Mon Sep 17 00:00:00 2001 From: lbuque <1102390310@qq.com> Date: Thu, 18 Sep 2025 17:34:10 +0800 Subject: [PATCH 269/322] cmodules/m5unified: Add e-ink screen power APIs. Signed-off-by: lbuque <1102390310@qq.com> --- m5stack/cmodules/m5unified/m5unified_gfx.c | 4 ++++ m5stack/components/M5Unified/mpy_m5gfx.cpp | 12 ++++++++++++ 2 files changed, 16 insertions(+) diff --git a/m5stack/cmodules/m5unified/m5unified_gfx.c b/m5stack/cmodules/m5unified/m5unified_gfx.c index 22220465..785fa4cf 100644 --- a/m5stack/cmodules/m5unified/m5unified_gfx.c +++ b/m5stack/cmodules/m5unified/m5unified_gfx.c @@ -30,6 +30,8 @@ MAKE_METHOD_0(gfx, height); MAKE_METHOD_0(gfx, getRotation); MAKE_METHOD_0(gfx, getColorDepth); MAKE_METHOD_0(gfx, getEpdMode); +MAKE_METHOD_0(gfx, powerSaveOn); +MAKE_METHOD_0(gfx, powerSaveOff); MAKE_METHOD_0(gfx, getCursor); MAKE_METHOD_KW(gfx, setRotation, 1); MAKE_METHOD_KW(gfx, setColorDepth, 1); @@ -99,6 +101,8 @@ MAKE_METHOD_0(gfx, lvgl_benchmark); MAKE_TABLE(gfx, getRotation), \ MAKE_TABLE(gfx, getColorDepth), \ MAKE_TABLE(gfx, getEpdMode), \ + MAKE_TABLE(gfx, powerSaveOn), \ + MAKE_TABLE(gfx, powerSaveOff), \ MAKE_TABLE(gfx, getCursor), \ MAKE_TABLE(gfx, setRotation), \ MAKE_TABLE(gfx, setColorDepth), \ diff --git a/m5stack/components/M5Unified/mpy_m5gfx.cpp b/m5stack/components/M5Unified/mpy_m5gfx.cpp index 528ca082..994b38c0 100644 --- a/m5stack/components/M5Unified/mpy_m5gfx.cpp +++ b/m5stack/components/M5Unified/mpy_m5gfx.cpp @@ -65,6 +65,18 @@ mp_obj_t gfx_getEpdMode(mp_obj_t self) { return mp_obj_new_int((int)gfx->getEpdMode()); } +mp_obj_t gfx_powerSaveOn(mp_obj_t self) { + auto gfx = getGfx(&self); + gfx->powerSaveOn(); + return mp_const_none; +} + +mp_obj_t gfx_powerSaveOff(mp_obj_t self) { + auto gfx = getGfx(&self); + gfx->powerSaveOff(); + return mp_const_none; +} + mp_obj_t gfx_getCursor(mp_obj_t self) { auto gfx = getGfx(&self); mp_obj_t tuple[2] = { mp_obj_new_int(gfx->getCursorX()) From b3782c8977c01eb8e8963755022fc3f0464e4275 Mon Sep 17 00:00:00 2001 From: lbuque <1102390310@qq.com> Date: Fri, 19 Sep 2025 09:16:53 +0800 Subject: [PATCH 270/322] .github/workflows: Add build of M5STACK_Atom_EchoS3R. Signed-off-by: lbuque <1102390310@qq.com> --- .github/workflows/build-release.yml | 1 + .github/workflows/nightly-build.yml | 1 + .github/workflows/ports_m5stack.yml | 1 + 3 files changed, 3 insertions(+) diff --git a/.github/workflows/build-release.yml b/.github/workflows/build-release.yml index ba689f7b..15369ba2 100644 --- a/.github/workflows/build-release.yml +++ b/.github/workflows/build-release.yml @@ -59,6 +59,7 @@ jobs: board: - M5STACK_AirQ - M5STACK_Atom_Echo + - M5STACK_Atom_EchoS3R - M5STACK_Atom_Lite - M5STACK_Atom_Matrix - M5STACK_AtomS3 diff --git a/.github/workflows/nightly-build.yml b/.github/workflows/nightly-build.yml index d780b191..44103e59 100644 --- a/.github/workflows/nightly-build.yml +++ b/.github/workflows/nightly-build.yml @@ -57,6 +57,7 @@ jobs: board: - M5STACK_AirQ - M5STACK_Atom_Echo + - M5STACK_Atom_EchoS3R - M5STACK_Atom_Lite - M5STACK_Atom_Matrix - M5STACK_AtomS3 diff --git a/.github/workflows/ports_m5stack.yml b/.github/workflows/ports_m5stack.yml index be3502cd..edb1a2f1 100644 --- a/.github/workflows/ports_m5stack.yml +++ b/.github/workflows/ports_m5stack.yml @@ -62,6 +62,7 @@ jobs: board: - M5STACK_AirQ - M5STACK_Atom_Echo + - M5STACK_Atom_EchoS3R - M5STACK_Atom_Lite - M5STACK_Atom_Matrix - M5STACK_AtomS3 From 2f129587d49cc3060277a72ddda344e19e5444f0 Mon Sep 17 00:00:00 2001 From: lbuque <1102390310@qq.com> Date: Fri, 19 Sep 2025 18:01:51 +0800 Subject: [PATCH 271/322] components/M5Unified: Update M5GFX&M5Unfied version. Signed-off-by: lbuque <1102390310@qq.com> --- m5stack/components/M5Unified/M5GFX | 2 +- m5stack/components/M5Unified/M5Unified | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/m5stack/components/M5Unified/M5GFX b/m5stack/components/M5Unified/M5GFX index 65f2622c..bd0eaaf2 160000 --- a/m5stack/components/M5Unified/M5GFX +++ b/m5stack/components/M5Unified/M5GFX @@ -1 +1 @@ -Subproject commit 65f2622cdc5cf7c39208ebe322447e4867555311 +Subproject commit bd0eaaf2338f8a31a0563297fd8f878018dba450 diff --git a/m5stack/components/M5Unified/M5Unified b/m5stack/components/M5Unified/M5Unified index d48e8921..05377f09 160000 --- a/m5stack/components/M5Unified/M5Unified +++ b/m5stack/components/M5Unified/M5Unified @@ -1 +1 @@ -Subproject commit d48e8921b73ef4437ad0de87852c79678da789d1 +Subproject commit 05377f09d83d1c71212cc84d1c6380b90c1de655 From 7af4551a9794679f80f12ef65f2ab53878eb9559 Mon Sep 17 00:00:00 2001 From: lbuque <1102390310@qq.com> Date: Fri, 19 Sep 2025 18:02:24 +0800 Subject: [PATCH 272/322] version.text: Update to V2.3.6. Signed-off-by: lbuque <1102390310@qq.com> --- m5stack/version.txt | 2 +- third-party/version.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/m5stack/version.txt b/m5stack/version.txt index e272c87b..4fe5f1b2 100644 --- a/m5stack/version.txt +++ b/m5stack/version.txt @@ -1 +1 @@ -V2.3.5 \ No newline at end of file +V2.3.6 \ No newline at end of file diff --git a/third-party/version.txt b/third-party/version.txt index e272c87b..4fe5f1b2 100644 --- a/third-party/version.txt +++ b/third-party/version.txt @@ -1 +1 @@ -V2.3.5 \ No newline at end of file +V2.3.6 \ No newline at end of file From 69e64ce0e7f17968507f67e40b1e8a11bff28150 Mon Sep 17 00:00:00 2001 From: lbuque <1102390310@qq.com> Date: Mon, 22 Sep 2025 11:17:14 +0800 Subject: [PATCH 273/322] components/M5Unified: Enable external power by default. Signed-off-by: lbuque <1102390310@qq.com> --- m5stack/components/M5Unified/mpy_m5unified.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/m5stack/components/M5Unified/mpy_m5unified.cpp b/m5stack/components/M5Unified/mpy_m5unified.cpp index 5502a888..ee4e92b3 100644 --- a/m5stack/components/M5Unified/mpy_m5unified.cpp +++ b/m5stack/components/M5Unified/mpy_m5unified.cpp @@ -469,6 +469,7 @@ mp_obj_t m5_begin(size_t n_args, const mp_obj_t *args) { // initial M5.begin(cfg); + M5.Power.setExtOutput(true); // enable external power by default M5.In_I2C.release(); in_i2c_init(); // if (M5.getBoard() != m5::board_t::board_M5StackCoreS3 From 9fe77d177d6fabf4bfe6fdcc1661c5d4fef505d0 Mon Sep 17 00:00:00 2001 From: lbuque <1102390310@qq.com> Date: Fri, 26 Sep 2025 17:11:31 +0800 Subject: [PATCH 274/322] .gitlab-ci.yml: Sync submodules during code formatting. Signed-off-by: lbuque <1102390310@qq.com> --- .gitlab-ci.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 7083d107..40786883 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -27,6 +27,8 @@ code-format: - export PATH="$HOME/.local/bin:$PATH" script: - make -C m5stack submodules + - sudo apt-get update -qy + - sudo apt-get install build-essential python3 python3-pip python-is-python3 cmake -y - source tools/ci.sh && ci_code_formatting_setup - source tools/ci.sh && ci_code_formatting_run - git diff --exit-code @@ -73,6 +75,10 @@ release_job: stage: release script: - echo "Releasing the M5Burn..." + - sudo apt install pipx -y + - pipx install uv + - uv venv + - source .venv/bin/activate - python ./tools/release.py only: refs: From 0a01ab1e2c7a450505964f3b313218b1c4d3ef60 Mon Sep 17 00:00:00 2001 From: lbuque <1102390310@qq.com> Date: Fri, 26 Sep 2025 17:30:37 +0800 Subject: [PATCH 275/322] libs/m5ui: Fix some bugs. Signed-off-by: lbuque <1102390310@qq.com> --- docs/en/m5ui/buttonmatrix.rst | 18 - docs/en/m5ui/roller.rst | 18 - .../zh_CN/LC_MESSAGES/m5ui/buttonmatrix.po | 474 ++++++++---------- docs/locales/zh_CN/LC_MESSAGES/m5ui/label.po | 346 ++++++------- docs/locales/zh_CN/LC_MESSAGES/m5ui/roller.po | 411 +++++++-------- m5stack/libs/m5ui/buttonmatrix.py | 19 + m5stack/libs/m5ui/roller.py | 18 + 7 files changed, 636 insertions(+), 668 deletions(-) diff --git a/docs/en/m5ui/buttonmatrix.rst b/docs/en/m5ui/buttonmatrix.rst index deb81f8c..d93e88ac 100644 --- a/docs/en/m5ui/buttonmatrix.rst +++ b/docs/en/m5ui/buttonmatrix.rst @@ -181,24 +181,6 @@ M5ButtonMatrix buttonmatrix_0.set_button_width(0, 2) # Make first button twice as wide - .. py:method:: get_selected_button() - - Get the index of the last pressed button. - - :return: The index of the last pressed button, or lv.buttonmatrix.BUTTON.NONE if no button is pressed. - :rtype: int - - UiFlow2 Code Block: - - |get_selected_button.png| - - MicroPython Code Block: - - .. code-block:: python - - btn_id = buttonmatrix_0.get_selected_button() - - .. py:method:: get_button_text(btn_id) Get the text of a specific button. diff --git a/docs/en/m5ui/roller.rst b/docs/en/m5ui/roller.rst index 24e45fed..51cfee51 100644 --- a/docs/en/m5ui/roller.rst +++ b/docs/en/m5ui/roller.rst @@ -217,24 +217,6 @@ M5Roller roller_0.set_style_border_width(2, lv.PART.SELECTED | lv.STATE.DEFAULT) - .. py:method:: get_option_count() - - Get the total number of options in the roller. - - :return: The number of options. - :rtype: int - - UiFlow2 Code Block: - - |get_option_count.png| - - MicroPython Code Block: - - .. code-block:: python - - option_count = roller_0.get_option_count() - - .. py:method:: get_selected() Get the index of the currently selected option. diff --git a/docs/locales/zh_CN/LC_MESSAGES/m5ui/buttonmatrix.po b/docs/locales/zh_CN/LC_MESSAGES/m5ui/buttonmatrix.po index 5e982655..794c0788 100644 --- a/docs/locales/zh_CN/LC_MESSAGES/m5ui/buttonmatrix.po +++ b/docs/locales/zh_CN/LC_MESSAGES/m5ui/buttonmatrix.po @@ -9,7 +9,7 @@ msgid "" msgstr "" "Project-Id-Version: UIFlow2 Programming Guide \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-08-20 10:29+0800\n" +"POT-Creation-Date: 2025-09-26 17:20+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language: zh_CN\n" @@ -21,45 +21,42 @@ msgstr "" "Generated-By: Babel 2.17.0\n" #: ../../en/m5ui/buttonmatrix.rst:4 ../../en/m5ui/buttonmatrix.rst:55 -#: 5540d17ce9bf49a49657e76df5326c98 c1a370daed7a42bab6901e65abc485dc +#: 82e37b7d30594c8c8999d9db2da4df34 b24eea0e529b4ac7a8269c25b974bd6c msgid "M5ButtonMatrix" msgstr "" -#: ../../en/m5ui/buttonmatrix.rst:8 56aba2e590c143b3a4ed6079d42f344f +#: ../../en/m5ui/buttonmatrix.rst:8 19d08bf79ea84336ac04342e7effcd7f msgid "" "M5ButtonMatrix is a widget that can be used to create a matrix of buttons" " in the user interface. It provides a flexible layout for displaying " "multiple buttons in a grid format with support for different button " "configurations and text labels." -msgstr "" -"M5ButtonMatrix是一个可以在用户界面中创建按钮矩阵的控件。它提供了一个灵活的布局来" -"以网格格式显示多个按钮,支持不同的按钮配置和文本标签。" +msgstr "M5ButtonMatrix是一个可以在用户界面中创建按钮矩阵的控件。它提供了一个灵活的布局来以网格格式显示多个按钮,支持不同的按钮配置和文本标签。" -#: ../../en/m5ui/buttonmatrix.rst:14 484db3bd4a304115b7b1fc3b78542fd0 +#: ../../en/m5ui/buttonmatrix.rst:14 8f08d601ce96476fa0e4374ba6fcd95d msgid "UiFlow2 Example" msgstr "UiFlow2 示例" #: ../../en/m5ui/buttonmatrix.rst:17 ../../en/m5ui/buttonmatrix.rst:36 -#: 26f4e3e2ea4e414a8b5431520b4c4e80 3fe97a02cef1487a86909a5dcedda56c +#: 4dc85ee4125b454fb7050b7f64470dbc ed1dc1cb44a548c6ab95bf436e105cb1 msgid "basic buttonmatrix" msgstr "基础按钮矩阵" -#: ../../en/m5ui/buttonmatrix.rst:19 6063f9f18efb4521984fd55828216f48 +#: ../../en/m5ui/buttonmatrix.rst:19 a59b27def0a843f2b4be009ba459dc75 msgid "Open the |cores3_buttonmatrix_basic_example.m5f2| project in UiFlow2." msgstr "在UiFlow2中打开 |cores3_buttonmatrix_basic_example.m5f2| 项目。" #: ../../en/m5ui/buttonmatrix.rst:21 ../../en/m5ui/buttonmatrix.rst:38 -#: 8b93ad2cd7e44ed4ac53089dd635f8d6 aa278b5e1af44ae7a0b19790f72dbb8c +#: 8a7020988a2f46068b90fcc9d631d552 f973c5b90d5a4d209f3ed8f45163f486 msgid "" "This example demonstrates how to create a button matrix with custom " "labels and handle button press events." -msgstr "" -"此示例演示如何创建带有自定义标签的按钮矩阵并处理按钮按下事件。" +msgstr "此示例演示如何创建带有自定义标签的按钮矩阵并处理按钮按下事件。" #: ../../en/m5ui/buttonmatrix.rst:23 ../../en/m5ui/buttonmatrix.rst:68 #: ../../en/m5ui/buttonmatrix.rst:86 ../../en/m5ui/buttonmatrix.rst:105 #: ../../en/m5ui/buttonmatrix.rst:123 ../../en/m5ui/buttonmatrix.rst:143 -#: ../../en/m5ui/buttonmatrix.rst:173 ../../en/m5ui/buttonmatrix.rst:191 +#: ../../en/m5ui/buttonmatrix.rst:173 ../../en/m5ui/buttonmatrix.rst:192 #: ../../en/m5ui/buttonmatrix.rst:210 ../../en/m5ui/buttonmatrix.rst:228 #: ../../en/m5ui/buttonmatrix.rst:245 ../../en/m5ui/buttonmatrix.rst:262 #: ../../en/m5ui/buttonmatrix.rst:279 ../../en/m5ui/buttonmatrix.rst:298 @@ -67,37 +64,32 @@ msgstr "" #: ../../en/m5ui/buttonmatrix.rst:353 ../../en/m5ui/buttonmatrix.rst:371 #: ../../en/m5ui/buttonmatrix.rst:389 ../../en/m5ui/buttonmatrix.rst:407 #: ../../en/m5ui/buttonmatrix.rst:425 ../../en/m5ui/buttonmatrix.rst:446 -#: 02a02d7e6bc94d84bfef99865c78f3d7 127f7507c27b43648e74332e979cb131 -#: 17609aee372a463eae2178c6cac4802d 1920c33f359643fd97dee33f1b3a1b8d -#: 27f04587fc7c4389abf2a271ab36c078 28e57792daac47acb064a5c3a3e1a381 -#: 29613927d51a47a4b56c3f05bc2ac866 29a271aeb10a4132902656b69f9aca60 -#: 3ffbabe73081426dad8b1c19d51b1c4a 5a0fbae37b7f4c1ea4073390a919562e -#: 5e42eec4715343a3ab2204b2aa003120 6b7e3a6e2e2b40a2938a8536a18ff339 -#: 7510c92bfb374c228c198ba9cac837d5 751d5726debb4b51a7adf75d6482e705 -#: 91d925c1d8464f93aa6ab3394e0937f0 94d6431f27964204980dfbd0917c7e7a -#: a4d9774ae5b14fa08654e698ca3fae58 a96f52a3c5994b9daab1677546289c65 -#: b86c5952aeed4208963794cf7cd54b82 bea3c0f4332b48e09f355e44c97d2b70 -#: bf40d9b6ebfa4f82af275854a9bca5eb bff22d5a6d474cf9b6fe5a90fb9fac61 -#: c58393615e2d474c98a3eb8061d112fc e2714ee6dbfd41ab82a97166efa19b38 -#: e7ca0f07fbeb45caad3fe7401552eba0 e8b21e987e4644a39c9f2ea46ddaf5db -#: f49523acb9264116949665cebc3969f2 m5ui.buttonmatrix.M5ButtonMatrix:11 +#: 051a38d7763442408d95eb86214e76dc 280196a9608c47c38d6468623c4ad4da +#: 353f1c07ceb442078c93f376f4aee1af 52855fd14fbf4c14bcdf8d4a2128ad23 +#: 55e3e07e60fe460a8409e918b43861f1 701f07e19f7b4d3f97f2a34cd1de65d8 +#: 7b158603e8674d0c83eb6440d78897a8 8766881576294cfb98e2642b600e1f34 +#: 8e3634a4cccf41fbae93da1d91867cea a550f18ccfc048ec957cb6798ea26d56 +#: b03d0a71c34c4773a35a2cc993dfae8a b6069bee2ce5435b9144d094336768df +#: b917a52ca1da49fb943640d0303ed1bd d2a952149fcb4b1dafc2a0ca68eb4c29 +#: d97d4ab0f6c246f5a51a06d73a5fd5cf dfb647f7b1cb44bdae1f69b13e662ef6 +#: m5ui.buttonmatrix.M5ButtonMatrix:11 +#: m5ui.buttonmatrix.M5ButtonMatrix.get_selected_button:6 #: m5ui.buttonmatrix.M5ButtonMatrix.get_textarea:6 -#: m5ui.buttonmatrix.M5ButtonMatrix.set_button_ctrl:6 #: m5ui.buttonmatrix.M5ButtonMatrix.set_textarea:5 #: m5ui.buttonmatrix.M5ButtonMatrix.toggle_button_ctrl:6 of msgid "UiFlow2 Code Block:" msgstr "UiFlow2 代码块:" -#: ../../en/m5ui/buttonmatrix.rst:25 b8d0995f15764f8d9957c2b2a75bc8df +#: ../../en/m5ui/buttonmatrix.rst:25 0d0dfb5b08824056b933de498cdc22b8 msgid "|cores3_buttonmatrix_basic_example.png|" msgstr "" -#: ../../en/refs/m5ui.buttonmatrix.ref:1 e0c79052be26427b931cb88991bcf002 +#: ../../en/refs/m5ui.buttonmatrix.ref:1 58ff8ea5816541bb87d4b573f48cf53e msgid "cores3_buttonmatrix_basic_example.png" msgstr "" #: ../../en/m5ui/buttonmatrix.rst:27 ../../en/m5ui/buttonmatrix.rst:46 -#: 5b6b20a8dfd34cb3bcb6072dde7dcc50 d107297ae68846a98657d1569b6e500a +#: a4145fb97f5c46ed983075ebf564b263 d5b2281527d44e42a98cf9de548ae8a4 msgid "Example output:" msgstr "示例输出:" @@ -108,26 +100,23 @@ msgstr "示例输出:" #: ../../en/m5ui/buttonmatrix.rst:296 ../../en/m5ui/buttonmatrix.rst:314 #: ../../en/m5ui/buttonmatrix.rst:332 ../../en/m5ui/buttonmatrix.rst:351 #: ../../en/m5ui/buttonmatrix.rst:369 ../../en/m5ui/buttonmatrix.rst:405 -#: ../../en/m5ui/buttonmatrix.rst:444 2968b41bc2c042a09568a4583d88be5c -#: 2d60a0129e71478394c4cea95891e57b 3a1f458c21ce4fe48aeb6a823096f526 -#: 4118a1df1eac4febb56ec6f2064b4a69 428dd7ecacc0472288f0b83ca1da2513 -#: 4459f6152868472c8c5057ee9dd54e28 4568bc03133c45739344a7a39f6c6b31 -#: 5411898078b044c2a009a480e4e0b9a5 5aca548655b440f98081707e63fe1026 -#: 8fddca55027443bfb99e005f9b262f10 98d6d05300d3478bb563aabe34486153 -#: b40ebadcb0b24887b69c7ded9747dcd2 ce896fbb3ae14a36a0ec76f0a3c8acca -#: d9130a4557ff4dcfa5c38be003ebb871 f63d4929161a4b64b7e5b59abfc543ad -#: fdd8ddcc9b754500b8c5f62db787bf50 m5ui.buttonmatrix.M5ButtonMatrix:13 of +#: ../../en/m5ui/buttonmatrix.rst:444 16eefc01b0d141558e8e57ac51d3e844 +#: 2b4f63c5afe5484396e13b1380bfc2e6 4de42112e50c45b684a90f388e996ab1 +#: 6108cc904c9747e8b0904e41751dce68 9dcc693dc074452eb4a13f86cd6b4a00 +#: d0dbe00ab01d4ecfba92ebcb5b6f35b8 d365e857ff5f478887a28e440af53e50 +#: d411e0a78f4a4b23b8e954e0e996c61c d55ce19db63c4e31b0d71f1c267c6937 +#: fb98cb0a0a564b949efecfcba44fcdaf m5ui.buttonmatrix.M5ButtonMatrix:13 of msgid "None" msgstr "" -#: ../../en/m5ui/buttonmatrix.rst:33 32f3171d95a44daabff6bf7f5d7b99b7 +#: ../../en/m5ui/buttonmatrix.rst:33 fa27740135394e80a33bd0ecc7452c88 msgid "MicroPython Example" msgstr "MicroPython 示例" #: ../../en/m5ui/buttonmatrix.rst:40 ../../en/m5ui/buttonmatrix.rst:72 #: ../../en/m5ui/buttonmatrix.rst:90 ../../en/m5ui/buttonmatrix.rst:109 #: ../../en/m5ui/buttonmatrix.rst:127 ../../en/m5ui/buttonmatrix.rst:147 -#: ../../en/m5ui/buttonmatrix.rst:177 ../../en/m5ui/buttonmatrix.rst:195 +#: ../../en/m5ui/buttonmatrix.rst:177 ../../en/m5ui/buttonmatrix.rst:196 #: ../../en/m5ui/buttonmatrix.rst:214 ../../en/m5ui/buttonmatrix.rst:232 #: ../../en/m5ui/buttonmatrix.rst:249 ../../en/m5ui/buttonmatrix.rst:266 #: ../../en/m5ui/buttonmatrix.rst:283 ../../en/m5ui/buttonmatrix.rst:302 @@ -135,574 +124,549 @@ msgstr "MicroPython 示例" #: ../../en/m5ui/buttonmatrix.rst:357 ../../en/m5ui/buttonmatrix.rst:375 #: ../../en/m5ui/buttonmatrix.rst:393 ../../en/m5ui/buttonmatrix.rst:411 #: ../../en/m5ui/buttonmatrix.rst:429 ../../en/m5ui/buttonmatrix.rst:450 -#: 0667645069c34cf49b6f5b252c7df01e 0dbef0068b3d4ae5a24075264e53d3b2 -#: 102f061e3bd94c70b576019eaae66dd3 330f863643e74eea9a65f10cad391432 -#: 40c141d36b884b09a33946c0848ea4c3 447bc046119e45b3acbf4eebcc98b3ac -#: 4a1263344bf74e89a2ce00fc026dcaee 4e322aac4b60439b9c3321654dde2c76 -#: 5953f6208ad747bea7d161add8599003 6332ee192504424f842fbe4e9fd7f4c9 -#: 65d3a7dc714249d4b4b19367be4bdc4d 810e57004062417f916a2242a708a024 -#: 926ceb223ed84a3a802f658ea348fe8b 94f73b4b71fc4a43a17a64ec7a985d63 -#: a4d427efa51449cf904fb39532d9f928 a77faff5ccc749bfa9c0e7114aa76a01 -#: abc4ab1baf1544cabd4ebacf1dde6d3e b4c3e87876c544c5bb6a9971986e8140 -#: b85fa40004f04b888eed7101ce1781f2 c2b9bdf7f38a4016be9b0db264314356 -#: c45ea1134a3a423994f6c34f5cad7eca c66e4daa769d4215802d17c30efc5fe0 -#: d35b2638e7e4493dae19687223610223 eb2ce9bbfddd4e25ae2ac5025dddd02d -#: f1c8c600baf14275b02c46948cae66e1 f6ed04b0b6934a94a501c61532478745 -#: f8c2b975b4b24a578acee5547a8602d4 m5ui.buttonmatrix.M5ButtonMatrix:15 +#: 369127c63efd47e4917877ace3ed0a64 38769807186046f7954c825474fb4879 +#: 4acd1f7d11ea495c8d6f754d29382299 4bc26b4f2b464d48a4db8980952853ec +#: 4d1de191a30e41d48cc9029dfdf5a659 4d2668b50b984c1c920f15d46d0c9893 +#: 606cc4cee81c43f0b6e92f0653e51a0c 7df8fd03a38943eab267615ad5e8103b +#: 7ed1eafe3ce049e6b43de8fa2afaa370 837c521080174275820fdbe3cdf1bf92 +#: 936391616e564e1690822c1f41a70559 a7141a5bcaf14acfa9941136bf4603c5 +#: baf317b3422a42a2b4d999758d1b5903 de887bc15c604dd49c09a6736485274f +#: e0fe5edf98ad492eb6e3575dc1d69e59 e71d0438f65f452bb6193726872292e6 +#: m5ui.buttonmatrix.M5ButtonMatrix:15 +#: m5ui.buttonmatrix.M5ButtonMatrix.get_selected_button:10 #: m5ui.buttonmatrix.M5ButtonMatrix.get_textarea:10 -#: m5ui.buttonmatrix.M5ButtonMatrix.set_button_ctrl:10 #: m5ui.buttonmatrix.M5ButtonMatrix.set_textarea:9 #: m5ui.buttonmatrix.M5ButtonMatrix.toggle_button_ctrl:10 of msgid "MicroPython Code Block:" msgstr "MicroPython 代码块:" -#: ../../en/m5ui/buttonmatrix.rst:52 d5afc4903c5f4a88b65d2693704b2459 +#: ../../en/m5ui/buttonmatrix.rst:52 de8bce7a89304c85a5933967af998a05 msgid "**API**" msgstr "API参考" -#: fd8ec279238c4c4abc7231d5232e0125 m5ui.buttonmatrix.M5ButtonMatrix:1 of +#: 2ec9ca4946b442fb93a6a1cf98fc5d17 m5ui.buttonmatrix.M5ButtonMatrix:1 of msgid "Bases: :py:class:`~lvgl.buttonmatrix`" msgstr "" -#: eff27f8a9faf4363a7a43575df8b5ba6 m5ui.buttonmatrix.M5ButtonMatrix:1 of +#: f209024a98df4c39b70ee83307697861 m5ui.buttonmatrix.M5ButtonMatrix:1 of msgid "Create a button matrix object." msgstr "创建一个按钮矩阵对象。" -#: ../../en/m5ui/buttonmatrix.rst 042ce52ee71543cd8bfc82a49bcfcf09 -#: 0c61091ff89f4337861d71931d14a523 0f1767473e294ad2a3e71b121ad1bbf0 -#: 14fe256407e645789be12cd953a25f30 15927dc4edde4d448ee2ec342eafc78e -#: 39a159aa8778419db86186ae4d57f2eb 4030e73dddac46c38d51d888840e0a84 -#: 4a63810e00474900badd2f779c27ee12 5a7d308730044e71a0a73c91c6b81d32 -#: 6e1c7a231f274fbcb4206b97b639e2e1 7ffd799c661044f997aa86f076eb1e20 -#: 837f894983c646daa56d3f199c4c1d00 861a43962e534edd91d24c87b074dcd1 -#: 90033b52a2694ae5b2c886199ab87f4d 90d01ba093dc478c9d9c951bf9f359fe -#: a812a39711e74989a984211d5c77eba0 bbbcc20636ec4be1957a319c2373d768 -#: bc8e3863b38143d194c4cc53c900a49e cec0a79cd2e74897ac572b0b0a121ad2 -#: d9d8f6e7e1164c4ea1d0748a8a1b4289 f0c8723682d44dbaacc919a9bfdf3dea -#: f49815423e2844c583b2d5df93623655 -#: m5ui.buttonmatrix.M5ButtonMatrix.set_button_ctrl +#: ../../en/m5ui/buttonmatrix.rst 0a34415339584da29ace1cfca2cd37bc +#: 0f0d20cccd6349c79df30ddb74c7bdd1 2435a2cc4b224c239a60a7a6ac909e8c +#: 8421b5e9a7ea48c2842145f6c24bdabc 882d3cec8cf74b85a8f746f90a3af686 +#: 9c6a02eb199f47b8afb46bfc58be5154 a8ec0040092f400db0a7532c18e1ce50 +#: afbce974fa7447228357773d5b14162f b6acdd0dc4f24b9cbb074c9e90a03881 +#: d3eeea86721d4cae81906ce5f576c198 dc2e243eb9b64f57aac7e7ac5344c010 #: m5ui.buttonmatrix.M5ButtonMatrix.set_textarea #: m5ui.buttonmatrix.M5ButtonMatrix.toggle_button_ctrl of msgid "Parameters" msgstr "" -#: 5e7815bd5c7d4f7e868a88162dcdee89 m5ui.buttonmatrix.M5ButtonMatrix:3 of +#: 6cc0f8ac0d824c7b82c483b49de58b44 m5ui.buttonmatrix.M5ButtonMatrix:3 of msgid "A list of button labels. Use \"\\\\n\" to create a new row." msgstr "按钮标签的列表。使用 \"\\\\n\" 来创建新行。" -#: d4afa28525474cb481dd1e9b0ea82703 m5ui.buttonmatrix.M5ButtonMatrix:4 of +#: a131620bf2134099aebc6e31fed59793 m5ui.buttonmatrix.M5ButtonMatrix:4 of msgid "The x position of the button matrix." msgstr "按钮矩阵的X坐标位置。" -#: 6e249107cffd41b491c88bf271345ed1 m5ui.buttonmatrix.M5ButtonMatrix:5 of +#: 0722f0ae2d53421cb8879812c3f854aa m5ui.buttonmatrix.M5ButtonMatrix:5 of msgid "The y position of the button matrix." msgstr "按钮矩阵的Y坐标位置。" -#: 5bcdb5c3e6744f05a6bb980fe8402897 m5ui.buttonmatrix.M5ButtonMatrix:6 of +#: fc20c78db3ad4f1b898138f0b12a280e m5ui.buttonmatrix.M5ButtonMatrix:6 of msgid "The width of the button matrix." msgstr "按钮矩阵的宽度。" -#: 7cff769227934455a2afd0c1c0c26002 m5ui.buttonmatrix.M5ButtonMatrix:7 of +#: 17798ea59b81410ba67437c8e9f2f312 m5ui.buttonmatrix.M5ButtonMatrix:7 of msgid "The height of the button matrix." msgstr "按钮矩阵的高度。" -#: 3949957319424622b0a4334deff57bdd m5ui.buttonmatrix.M5ButtonMatrix:8 of +#: e78c36a46ea24d21bdd286e4edb389ca m5ui.buttonmatrix.M5ButtonMatrix:8 of msgid "A M5TextArea to display the button text when a button is pressed." msgstr "按下按钮时显示按钮文本的M5TextArea。" -#: b370113d5e9b4d03b2825a1e651297fd m5ui.buttonmatrix.M5ButtonMatrix:9 of +#: ab9faf77d98747cc9d1f773f28344d13 m5ui.buttonmatrix.M5ButtonMatrix:9 of msgid "The parent object to attach the button matrix to." msgstr "要附加按钮矩阵的父对象。" -#: ../../en/m5ui/buttonmatrix.rst:62 ed569c16ef7941e88ce99de07ca9b246 +#: ../../en/m5ui/buttonmatrix.rst:62 344e821f72a84ea5b4d69f7f2e636754 msgid "" "Set a flag on the object. If ``value`` is True, the flag is added; if " "False, the flag is removed." -msgstr "" -"为对象设置标志。如果 ``value`` 为True,则添加标志;" -"如果为False,则移除标志。" +msgstr "为对象设置标志。如果 ``value`` 为True,则添加标志;如果为False,则移除标志。" -#: ../../en/m5ui/buttonmatrix.rst:64 36df2029433a46bb92d84913fdfd5d3a +#: ../../en/m5ui/buttonmatrix.rst:64 f941942123e042d99949343254b7579f msgid "The flag to set." msgstr "要设置的标志。" -#: ../../en/m5ui/buttonmatrix.rst:65 1493a7be63ca4db0be812d3b013a8bc3 +#: ../../en/m5ui/buttonmatrix.rst:65 2751e470eb3c4fb080ce5f79678cf1c1 msgid "If True, the flag is added; if False, the flag is removed." msgstr "如果为True,则添加标志;如果为False,则移除标志。" -#: ../../en/m5ui/buttonmatrix.rst 13eb4fc85c6842fa8ef55a522a6fb3f5 -#: 17b6dd91bee34629a4aa199846e1da2e 1e609d4eaaf94fe3a40546d12731aab1 -#: 2394118fb0714e8393d9aeabf2029389 4083b5fb5c854ed5a0ea763c20db20ec -#: 4e470cd453c34b359488d5118d979a51 584d4b55fa144076921a467ce69e5e3b -#: 6d4a5654b4114f6d978b2985cc8bdafb 717e7e25aef8475693f1b0ced893bcf7 -#: 71cea7df68ee4c83afa5cb87c881fde1 84042f93a07b4f75b0e3029f034ba112 -#: a4a519ea04944c66b635e038e7bc17b1 b47772aabe9743efb8ed843c3477c7f1 -#: cdbeb9aa83274acea1e3de82fad96233 cff7467562b746f7b6d420fc777a0cdc -#: dd546278eb254121918b3b1572409301 ea4ca98941674be299935fb00e19b3af -#: f5cd5d74ba864874bef5a0fb3f959082 +#: ../../en/m5ui/buttonmatrix.rst 09149188d7ba4dd7af59a5d5740363d7 +#: 0b2639019dad4ab48c2026601b7cff08 329da0f493234801a19f2a2cd3cb3d4e +#: 8c449a54ce42441ba03c951dceb1d5f2 a801b90f9787480881614068e976bc9e +#: ab94939985c642a38ae82166484a327f d7cb7b5c0e4b4a8fb7ecf0dde5add086 +#: f895d9db7e3b405ba5b4799508a689ca +#: m5ui.buttonmatrix.M5ButtonMatrix.get_selected_button #: m5ui.buttonmatrix.M5ButtonMatrix.get_textarea of msgid "Returns" msgstr "" -#: ../../en/m5ui/buttonmatrix.rst:70 cd41a618fbdf40bca11e2a447610d5a5 +#: ../../en/m5ui/buttonmatrix.rst:70 4d46a61d475a40f69911de9c966ba7a7 msgid "|set_flag.png|" msgstr "" -#: ../../en/refs/m5ui.buttonmatrix.ref:15 bcf3870ebd1242808b34d0dc30cfe8d0 +#: ../../en/refs/m5ui.buttonmatrix.ref:15 957ff1c9741443a680e19e3eebfbaf12 msgid "set_flag.png" msgstr "" -#: ../../en/m5ui/buttonmatrix.rst:81 bb0411c537304bbb86298911c713fa51 +#: ../../en/m5ui/buttonmatrix.rst:81 d594c84a7e6a45fbbc342db94903a088 msgid "" "Toggle a flag on the object. If the flag is set, it is removed; if not " "set, it is added." -msgstr "" -"切换对象的标志。如果标志已设置,则将其移除;" -"如果未设置,则将其添加。" +msgstr "切换对象的标志。如果标志已设置,则将其移除;如果未设置,则将其添加。" -#: ../../en/m5ui/buttonmatrix.rst:83 2f2db537d78d40638be2f68dd80366f0 +#: ../../en/m5ui/buttonmatrix.rst:83 1cdcd410dfaa4603ae87a6e50311ac13 msgid "The flag to toggle." msgstr "要切换的标志。" -#: ../../en/m5ui/buttonmatrix.rst:88 96dbf1c24fc645bd8a8805fe3f469be8 +#: ../../en/m5ui/buttonmatrix.rst:88 2deea6ab0ea44df6b5cedb4bcc213141 msgid "|toggle_flag.png|" msgstr "" -#: ../../en/refs/m5ui.buttonmatrix.ref:26 c3eb7348ea13490a8b44a470254bd3f5 +#: ../../en/refs/m5ui.buttonmatrix.ref:26 fdade57a7e174771b3e9f74256d97eef msgid "toggle_flag.png" msgstr "" -#: ../../en/m5ui/buttonmatrix.rst:99 f4a1dc354870488cb58ad545c6648820 +#: ../../en/m5ui/buttonmatrix.rst:99 e7dd93b92cb7428a8768dff4b2c5e957 msgid "" "Set the state of the buttonmatrix. If ``value`` is True, the state is " "set; if False, the state is unset." -msgstr "" -"设置按钮矩阵的状态。如果 ``value`` 为True,则设置状态;" -"如果为False,则取消设置状态。" +msgstr "设置按钮矩阵的状态。如果 ``value`` 为True,则设置状态;如果为False,则取消设置状态。" -#: ../../en/m5ui/buttonmatrix.rst:101 914b414d54514771a92594c984b0f563 +#: ../../en/m5ui/buttonmatrix.rst:101 22ada3cc8ea843178f23715c8b49540e msgid "The state to set." msgstr "要设置的状态。" -#: ../../en/m5ui/buttonmatrix.rst:102 ff3d27ae23ca42968cb35ad9b17df9c1 +#: ../../en/m5ui/buttonmatrix.rst:102 6ec26dc81d2a4754814257fb2c5dbc43 msgid "If True, the state is set; if False, the state is unset." msgstr "如果为True,则设置状态;如果为False,则取消设置状态。" -#: ../../en/m5ui/buttonmatrix.rst:107 d7d39541e10d4193ade2a6d71807cab9 +#: ../../en/m5ui/buttonmatrix.rst:107 07810eb16a5147ad801c9565ddaeefd9 msgid "|set_state.png|" msgstr "" -#: ../../en/refs/m5ui.buttonmatrix.ref:20 1d7b8d308b5e47948c85ff40c80d8196 +#: ../../en/refs/m5ui.buttonmatrix.ref:20 da164366549448db9f40d64bbde6e7ec msgid "set_state.png" msgstr "" -#: ../../en/m5ui/buttonmatrix.rst:118 b67650dc628545149c83c321a4b47cab +#: ../../en/m5ui/buttonmatrix.rst:118 31ea256185574aa8b54835b8b623e7a1 msgid "" "Toggle the state of the buttonmatrix. If the state is set, it is unset; " "if not set, it is set." -msgstr "" -"切换按钮矩阵的状态。如果状态已设置,则取消设置;" -"如果未设置,则将其设置。" +msgstr "切换按钮矩阵的状态。如果状态已设置,则取消设置;如果未设置,则将其设置。" -#: ../../en/m5ui/buttonmatrix.rst:120 5fbf1d5f84db46fab1d62e4c6bb83b76 +#: ../../en/m5ui/buttonmatrix.rst:120 04102280beac48ac9fc74bbe69333d11 msgid "The state to toggle." msgstr "要切换的状态。" -#: ../../en/m5ui/buttonmatrix.rst:125 25d347cfdbb14c748e2d4a1d7c10a194 +#: ../../en/m5ui/buttonmatrix.rst:125 9ace6eab96064b0991d220b482ffe840 msgid "|toggle_state.png|" msgstr "" -#: ../../en/refs/m5ui.buttonmatrix.ref:27 3d3e51742d3042f18abdd31127d27846 +#: ../../en/refs/m5ui.buttonmatrix.ref:27 79f71ed4b79e424fa764a0671369f0b8 msgid "toggle_state.png" msgstr "" -#: ../../en/m5ui/buttonmatrix.rst:136 3ab536245ed34698b17230883a81ceb7 +#: ../../en/m5ui/buttonmatrix.rst:136 19cdd8d7cc174b36a9d345748495a5b6 msgid "" "Add an event callback to the buttonmatrix. The callback will be called " "when the specified event occurs." -msgstr "" -"为按钮矩阵添加事件回调。当指定事件发生时将调用回调函数。" +msgstr "为按钮矩阵添加事件回调。当指定事件发生时将调用回调函数。" -#: ../../en/m5ui/buttonmatrix.rst:138 3666759557b747589a7597e8b8403ee7 +#: ../../en/m5ui/buttonmatrix.rst:138 72bbd98d06e740a8ac61f90c395f37b9 msgid "The callback function to call." msgstr "要调用的回调函数。" -#: ../../en/m5ui/buttonmatrix.rst:139 e319252ac13f4e6aa51a650c061a6a7c +#: ../../en/m5ui/buttonmatrix.rst:139 f9b2adc1cf37472993f333a2ab3ede23 msgid "The event to listen for." msgstr "要监听的事件。" -#: ../../en/m5ui/buttonmatrix.rst:140 eac92d3024a74df08fa76e32f3c05333 +#: ../../en/m5ui/buttonmatrix.rst:140 a6a8897ddb784d79974c7274a684076e msgid "Optional user data to pass to the callback." msgstr "传递给回调函数的可选用户数据。" -#: ../../en/m5ui/buttonmatrix.rst:145 0ebf81a0a90a4e6db175c35db6043a83 +#: ../../en/m5ui/buttonmatrix.rst:145 a02ab07f1e7543e59ac4a27672d38ef0 msgid "|event.png|" msgstr "" -#: ../../en/refs/m5ui.buttonmatrix.ref:6 e7abf0112d9a45828fc20915a47b3a37 +#: ../../en/refs/m5ui.buttonmatrix.ref:6 3e9cc60957d74fb38c810bcfe906c67b msgid "event.png" msgstr "" -#: ../../en/m5ui/buttonmatrix.rst:167 9fe7ce3f8d104e9da7ea91813963b90a +#: ../../en/m5ui/buttonmatrix.rst:167 ba7b7093c8784a5da0c106d74986371a msgid "Set the relative width of a specific button." msgstr "设置特定按钮的相对宽度。" -#: ../../en/m5ui/buttonmatrix.rst:169 ../../en/m5ui/buttonmatrix.rst:206 -#: 0866fdba42b6496691f63093cdcf1983 7c3248bf15f2433abdbf7c714e8af038 +#: ../../en/m5ui/buttonmatrix.rst:169 ../../en/m5ui/buttonmatrix.rst:188 +#: 77f382e23e6d43f4a83ee86306659541 a8415e2f23b54b139b762efdbb5b7cb7 msgid "The index of the button." msgstr "按钮的索引。" -#: ../../en/m5ui/buttonmatrix.rst:170 9d4d94e899ed48c3b197b9fd059b6b73 +#: ../../en/m5ui/buttonmatrix.rst:170 9cf32cf2a2f745269427fbd3beb60843 msgid "The relative width (1-7, where 1 is normal width)." msgstr "相对宽度(1-7,其中1为正常宽度)。" -#: ../../en/m5ui/buttonmatrix.rst:175 4244126fa9b14a688ceff6698e58d5fb +#: ../../en/m5ui/buttonmatrix.rst:175 4299c6a31be049b1b5a6f5daddac56d5 msgid "|set_button_width.png|" msgstr "" -#: ../../en/refs/m5ui.buttonmatrix.ref:14 1ca3c4dea4974e39b6ba5ed1d15798c2 +#: ../../en/refs/m5ui.buttonmatrix.ref:14 262f870996144dbcab3ad2a8c5abc02b msgid "set_button_width.png" msgstr "" -#: ../../en/m5ui/buttonmatrix.rst:186 928d93d38ed545e19bff7a8bdf3e4640 -msgid "Get the index of the last pressed button." -msgstr "获取最后按下按钮的索引。" - -#: ../../en/m5ui/buttonmatrix.rst:188 c74310fa070d40beaa731fa6c499053a -msgid "" -"The index of the last pressed button, or lv.buttonmatrix.BUTTON.NONE if " -"no button is pressed." -msgstr "" -"最后按下按钮的索引,如果没有按钮被按下,则返回 lv.buttonmatrix.BUTTON.NONE。" - -#: ../../en/m5ui/buttonmatrix.rst 05bdf3628bf948b0ac2ed630c31ee226 -#: 2168f531f7d64bc58588e41cbcc68aa0 6822aae96eaa4fc3a73b418b446a3c20 -#: e2329f520947424f8514ed2febe6f625 e5d5cb002e4240b7992ac32532a59c17 -#: m5ui.buttonmatrix.M5ButtonMatrix.get_textarea of -msgid "Return type" -msgstr "" - -#: ../../en/m5ui/buttonmatrix.rst:193 71fe635b95864a8d88fcad53fd3877e9 -msgid "|get_selected_button.png|" -msgstr "" - -#: ../../en/refs/m5ui.buttonmatrix.ref:9 9b652c56af2f447f897fe5dea3f26e56 -msgid "get_selected_button.png" -msgstr "" - -#: ../../en/m5ui/buttonmatrix.rst:204 ef72caabcdbe4dccb347598f01bbb563 +#: ../../en/m5ui/buttonmatrix.rst:186 014d7fa535a54fb1814a371ae61af696 msgid "Get the text of a specific button." msgstr "获取特定按钮的文本。" -#: ../../en/m5ui/buttonmatrix.rst:207 74011edd0fa84f0aaa3d5f2f96cd5446 +#: ../../en/m5ui/buttonmatrix.rst:189 ab9d7b11006b456d8d5397ad1d056cbd msgid "The text of the button." msgstr "按钮的文本。" -#: ../../en/m5ui/buttonmatrix.rst:212 d2fdb15431ff4701b3d6dca396d31b49 +#: ../../en/m5ui/buttonmatrix.rst 9cca8ed38b554625866409865edbcb22 +#: ff1a84e38686411fa85fc5f530a4f020 +#: m5ui.buttonmatrix.M5ButtonMatrix.get_selected_button +#: m5ui.buttonmatrix.M5ButtonMatrix.get_textarea of +msgid "Return type" +msgstr "" + +#: ../../en/m5ui/buttonmatrix.rst:194 ef8d890ca5bc466ba65c35843be53882 msgid "|get_button_text.png|" msgstr "" -#: ../../en/refs/m5ui.buttonmatrix.ref:7 f66bebff11a9460eb8f3518c7ad5c718 +#: ../../en/refs/m5ui.buttonmatrix.ref:7 dfc6664bcf884d7e9a69d0fa70647897 msgid "get_button_text.png" msgstr "" -#: ../../en/m5ui/buttonmatrix.rst:223 1dc9387b9d7f4dc0afc1bf679499cd17 +#: ../../en/m5ui/buttonmatrix.rst:205 168a336b474544d8b76cf0b21a323cda msgid "Clear control flags for a specific button." msgstr "清除特定按钮的控制标志。" -#: ../../en/m5ui/buttonmatrix.rst:225 265da30b55f74a949195ad50214d343c +#: ../../en/m5ui/buttonmatrix.rst:207 c27f46ab6d7a4b71b83b9fa402a91ff6 msgid "The button ID to clear control flags for." msgstr "要清除控制标志的按钮ID。" -#: ../../en/m5ui/buttonmatrix.rst:226 dbb968dbe1784c9ea7eede5ab16984f8 +#: ../../en/m5ui/buttonmatrix.rst:208 09a507de4d0843d9bca6e1e752e912dd msgid "The control flags to clear." msgstr "要清除的控制标志。" -#: ../../en/m5ui/buttonmatrix.rst:230 94961f589edd4177b44e5b96231a82de +#: ../../en/m5ui/buttonmatrix.rst:212 f9d1dc4c4d99487880f236a82dd5307a msgid "|clear_button_ctrl.png|" msgstr "" -#: ../../en/refs/m5ui.buttonmatrix.ref:4 5ce0470b6c334024aa8a7e1d2b1f6a6f +#: ../../en/refs/m5ui.buttonmatrix.ref:4 e47a13612bf24561a8e9124bb56644f3 msgid "clear_button_ctrl.png" msgstr "" -#: ../../en/m5ui/buttonmatrix.rst:241 8d12baf25b7a4ce2b38aa4a1e437b1e4 +#: ../../en/m5ui/buttonmatrix.rst:223 f98960b1922c43d69dfb57a1e8ca172f +msgid "Set control flags for a specific button." +msgstr "设置特定按钮的控制标志。" + +#: ../../en/m5ui/buttonmatrix.rst:225 23b583fa8afc4293af472072c5056ab2 +msgid "The button ID to set control flags for." +msgstr "要设置控制标志的按钮ID。" + +#: ../../en/m5ui/buttonmatrix.rst:226 b1f2f9345718497ea1593adc713bdf35 +msgid "The control flags to set." +msgstr "要设置的控制标志。" + +#: ../../en/m5ui/buttonmatrix.rst:230 53f659bdf7834a96a8558d9cc86ce516 +msgid "|set_button_ctrl.png|" +msgstr "" + +#: ../../en/refs/m5ui.buttonmatrix.ref:12 7b6312df8edf46e29810b8f882ec3707 +msgid "set_button_ctrl.png" +msgstr "" + +#: ../../en/m5ui/buttonmatrix.rst:241 8087a446b11b4ed782fe73e1d02c87c2 msgid "Set control flags for all buttons." msgstr "设置所有按钮的控制标志。" -#: ../../en/m5ui/buttonmatrix.rst:243 6a52f9802f9c4b59bfd1c1f26029608d +#: ../../en/m5ui/buttonmatrix.rst:243 f8b5f005febf4a349aa3011b27b867b2 msgid "The control flags to set for all buttons." msgstr "要为所有按钮设置的控制标志。" -#: ../../en/m5ui/buttonmatrix.rst:247 84556ea9ef114fc188c44072f1ea7f7d +#: ../../en/m5ui/buttonmatrix.rst:247 c49f33247cbc467c88803248e728053b msgid "|set_button_ctrl_all.png|" msgstr "" -#: ../../en/refs/m5ui.buttonmatrix.ref:13 8696de0788d94fcba671750c191a138c +#: ../../en/refs/m5ui.buttonmatrix.ref:13 227ffee5e7a94ca68e9428ca0cfd20c2 msgid "set_button_ctrl_all.png" msgstr "" -#: ../../en/m5ui/buttonmatrix.rst:258 93a54d78aa4a4e8fb412ac1f82250566 +#: ../../en/m5ui/buttonmatrix.rst:258 cbb48b146c2740d0848e4f9f34d7bef1 msgid "Clear control flags for all buttons." msgstr "清除所有按钮的控制标志。" -#: ../../en/m5ui/buttonmatrix.rst:260 819df99dcd934240ba61afd78c76d6f4 +#: ../../en/m5ui/buttonmatrix.rst:260 8f4b418b2ac740a687c46b636e7bb6b9 msgid "The control flags to clear for all buttons." msgstr "要为所有按钮清除的控制标志。" -#: ../../en/m5ui/buttonmatrix.rst:264 fefabb691498400c89104748c98eafe6 +#: ../../en/m5ui/buttonmatrix.rst:264 5c5e6b64e4f142fda1ea6de98cb926c9 msgid "|clear_button_ctrl_all.png|" msgstr "" -#: ../../en/refs/m5ui.buttonmatrix.ref:5 aad19dd39782480287548491f6cdaa87 +#: ../../en/refs/m5ui.buttonmatrix.ref:5 39892f92c5d64c389dacf9685f8c4dc0 msgid "clear_button_ctrl_all.png" msgstr "" -#: ../../en/m5ui/buttonmatrix.rst:275 0958c73f77d443b689bfabba4a56fb0b +#: ../../en/m5ui/buttonmatrix.rst:275 d30043132ff64afba42651780f32d069 msgid "Set a specific button as checked." msgstr "将特定按钮设置为选中状态。" -#: ../../en/m5ui/buttonmatrix.rst:277 094f082f90c4465697fb46460fae8d7d +#: ../../en/m5ui/buttonmatrix.rst:277 90101522efd7418d893f857cf4f6f18c msgid "The button ID to set as checked." msgstr "要设置为选中状态的按钮ID。" -#: ../../en/m5ui/buttonmatrix.rst:281 d8a0314aead84a86bf91524ba213fa7d +#: ../../en/m5ui/buttonmatrix.rst:281 f658c0b5a052461d9093b2eae89a0bcc msgid "|set_one_checked.png|" msgstr "" -#: ../../en/refs/m5ui.buttonmatrix.ref:17 9ee66e48a1b34aeaaae0c5ca58c4e40d +#: ../../en/refs/m5ui.buttonmatrix.ref:17 5bcb9dbd0d7440219e8f6f2b0d756737 msgid "set_one_checked.png" msgstr "" -#: ../../en/m5ui/buttonmatrix.rst:292 ec36f17f24f3442897b6178998a7a902 +#: ../../en/m5ui/buttonmatrix.rst:292 93aade8f39344ef0b011e7648c4cdf06 msgid "Set the position of the buttonmatrix." msgstr "设置按钮矩阵的位置。" #: ../../en/m5ui/buttonmatrix.rst:294 ../../en/m5ui/buttonmatrix.rst:313 -#: 3f6dbd60d590487e9516ecf5a3fd2d01 5cc7717548c74df787eb0b11c1bfcb4e +#: 54936663cf1648c0add2f2b07624cd57 msgid "The x-coordinate of the buttonmatrix." msgstr "按钮矩阵的x坐标。" #: ../../en/m5ui/buttonmatrix.rst:295 ../../en/m5ui/buttonmatrix.rst:331 -#: 1c9bdaccb7e24735b13efe525a6ef64a 32f44ed032864480861a9966e288de4d +#: 6baa3e5d6fc84bc2a84b95bc5d655c77 msgid "The y-coordinate of the buttonmatrix." msgstr "按钮矩阵的y坐标。" -#: ../../en/m5ui/buttonmatrix.rst:300 8f76b714dd384eb0aad06145ec8f1bc3 +#: ../../en/m5ui/buttonmatrix.rst:300 a0caf5cd072c4334a990910eddb4a63c msgid "|set_pos.png|" msgstr "" -#: ../../en/refs/m5ui.buttonmatrix.ref:18 7a9c9e30f4e04a5c9d5fdaf271f5180b +#: ../../en/refs/m5ui.buttonmatrix.ref:18 8a093bf6ec1a4372a0eefb607872cf66 msgid "set_pos.png" msgstr "" -#: ../../en/m5ui/buttonmatrix.rst:311 b22a7fb2485443579993355fa813ddf0 +#: ../../en/m5ui/buttonmatrix.rst:311 761d9d698b8a4fa8bd673180d446bd1c msgid "Set the x-coordinate of the buttonmatrix." msgstr "设置按钮矩阵的x坐标。" -#: ../../en/m5ui/buttonmatrix.rst:318 9fb0c8cdae0a4d45879a805b8a25d1ef +#: ../../en/m5ui/buttonmatrix.rst:318 8c9a972d90d0475d8d75d903b5cbab78 msgid "|set_x.png|" msgstr "" -#: ../../en/refs/m5ui.buttonmatrix.ref:23 22448ccc65ac4eb49ef0123032d9dbc3 +#: ../../en/refs/m5ui.buttonmatrix.ref:23 627474758fae458fb131b226d3724fd7 msgid "set_x.png" msgstr "" -#: ../../en/m5ui/buttonmatrix.rst:329 3746cb3fe11640d6910bf1f43746602c +#: ../../en/m5ui/buttonmatrix.rst:329 f51399db99d145ce982ea0eb29517902 msgid "Set the y-coordinate of the buttonmatrix." msgstr "设置按钮矩阵的y坐标。" -#: ../../en/m5ui/buttonmatrix.rst:336 6080a7b1b44947a189ddd6ec86b06fac +#: ../../en/m5ui/buttonmatrix.rst:336 eed645483f5c44379b6c30d9b87c659c msgid "|set_y.png|" msgstr "" -#: ../../en/refs/m5ui.buttonmatrix.ref:24 d21b5dc3ee17459ebc54e0ea2626b878 +#: ../../en/refs/m5ui.buttonmatrix.ref:24 acd7855c3a04443b97fc7c8b10af7240 msgid "set_y.png" msgstr "" -#: ../../en/m5ui/buttonmatrix.rst:347 b4d3a1043b0c4e3d87b20330b0bff182 +#: ../../en/m5ui/buttonmatrix.rst:347 e97c99db2b1746328c84e5efb095c047 msgid "Set the size of the buttonmatrix." msgstr "设置按钮矩阵的大小。" #: ../../en/m5ui/buttonmatrix.rst:349 ../../en/m5ui/buttonmatrix.rst:368 -#: ../../en/m5ui/buttonmatrix.rst:386 42799b1058cb4f6da4b7fdb06457296b -#: d22c132bee814267985b02d4ad513cf8 ea8e6a2afc9447818576b76d22606db1 +#: ../../en/m5ui/buttonmatrix.rst:386 4295f9b4a372407895e2e3015ea3c3aa msgid "The width of the buttonmatrix." msgstr "按钮矩阵的宽度。" #: ../../en/m5ui/buttonmatrix.rst:350 ../../en/m5ui/buttonmatrix.rst:404 -#: ../../en/m5ui/buttonmatrix.rst:422 392a019e40d3476e9852f57b216d5a78 -#: 93b95db8851a4ba19b859d2f5e454380 ac85ac03ad014915b9d390650858d825 +#: ../../en/m5ui/buttonmatrix.rst:422 23e02a84adea455c8e9763d1f8552ccb msgid "The height of the buttonmatrix." msgstr "按钮矩阵的高度。" -#: ../../en/m5ui/buttonmatrix.rst:355 72e5253b380d4ca7b9f0fd50400d752a +#: ../../en/m5ui/buttonmatrix.rst:355 58d2ea1e333e4ff39e4fb3e41ffcd4df msgid "|set_size.png|" msgstr "" -#: ../../en/refs/m5ui.buttonmatrix.ref:19 5d8c8c3b195f426c8ffa3fea13e915e5 +#: ../../en/refs/m5ui.buttonmatrix.ref:19 8a7f519f98c74eaca64098b8cc35d87d msgid "set_size.png" msgstr "" -#: ../../en/m5ui/buttonmatrix.rst:366 55ab12a4e97b471aa4e3fc90c043b38c +#: ../../en/m5ui/buttonmatrix.rst:366 6d8cdbb66f32490284a88c11f0051b1f msgid "Set the width of the buttonmatrix." msgstr "设置按钮矩阵的宽度。" -#: ../../en/m5ui/buttonmatrix.rst:373 ae911a4cf0344098a52a85a93922c7a9 +#: ../../en/m5ui/buttonmatrix.rst:373 5cbe5536b73c439ea04b53b61a254a14 msgid "|set_width.png|" msgstr "" -#: ../../en/refs/m5ui.buttonmatrix.ref:22 a8c1d896eda5453386445d46827d10a9 +#: ../../en/refs/m5ui.buttonmatrix.ref:22 9eff67b3840c4a15923e78ded22254d3 msgid "set_width.png" msgstr "" -#: ../../en/m5ui/buttonmatrix.rst:384 a8b64cab0adf4c0eb62ee3f40d8a75b9 +#: ../../en/m5ui/buttonmatrix.rst:384 c11939882ea44300a1193dd02ff1deff msgid "Get the width of the buttonmatrix." msgstr "获取按钮矩阵的宽度。" -#: ../../en/m5ui/buttonmatrix.rst:391 28319137d94b45f98b6ae448c7fe3d45 +#: ../../en/m5ui/buttonmatrix.rst:391 af8078fd298c48c184b5d31f2874a5dd msgid "|get_width.png|" msgstr "" -#: ../../en/refs/m5ui.buttonmatrix.ref:11 cbcf296acf1a4be9a56f986e57a6c3ef +#: ../../en/refs/m5ui.buttonmatrix.ref:11 e8972ae2336149739515166a333f1f5e msgid "get_width.png" msgstr "" -#: ../../en/m5ui/buttonmatrix.rst:402 af7284046bf64d11b23ecb07b07fb137 +#: ../../en/m5ui/buttonmatrix.rst:402 2d0c66fccd7043989285649ce1b353e6 msgid "Set the height of the buttonmatrix." msgstr "设置按钮矩阵的高度。" -#: ../../en/m5ui/buttonmatrix.rst:409 41a5a210c11a4a6a8f765afdbf8756a0 +#: ../../en/m5ui/buttonmatrix.rst:409 dd6701e354b6462db514f74cbf58b20a msgid "|set_height.png|" msgstr "" -#: ../../en/refs/m5ui.buttonmatrix.ref:16 77f4ce850d3e4988a3c7432340cb1a0d +#: ../../en/refs/m5ui.buttonmatrix.ref:16 c2b555e7cc9a4042b228130dceeb5a61 msgid "set_height.png" msgstr "" -#: ../../en/m5ui/buttonmatrix.rst:420 a8b3c9d998a3490899029f37a3113eba +#: ../../en/m5ui/buttonmatrix.rst:420 b5e216316e5c43eeb674fb8f3d3126e9 msgid "Get the height of the buttonmatrix." msgstr "获取按钮矩阵的高度。" -#: ../../en/m5ui/buttonmatrix.rst:427 79abb9feb0c74bb38d1e87fc7963ee6b +#: ../../en/m5ui/buttonmatrix.rst:427 5dd7a8c13ff1455c8d5afc3e89fb84ad msgid "|get_height.png|" msgstr "" -#: ../../en/refs/m5ui.buttonmatrix.ref:8 36a6c21feb8c41e08de6531c907227f7 +#: ../../en/refs/m5ui.buttonmatrix.ref:8 139e3bd8ada44e3a8c6bb8bebfabe1ab msgid "get_height.png" msgstr "" -#: ../../en/m5ui/buttonmatrix.rst:438 09dc01b29ba94648a767407122216007 +#: ../../en/m5ui/buttonmatrix.rst:438 e6acf6bc45974422a2c34d047442645c msgid "Align the buttonmatrix to another object." msgstr "将按钮矩阵对齐到另一个对象。" -#: ../../en/m5ui/buttonmatrix.rst:440 b6dbc5ad976d4b6787a6ffc2b3fb3005 +#: ../../en/m5ui/buttonmatrix.rst:440 2de7ebf297324b55a591c7caacdf1518 msgid "The object to align to." msgstr "要对齐到的对象。" -#: ../../en/m5ui/buttonmatrix.rst:441 ad98b6216f1140eba9fea3367507b85e +#: ../../en/m5ui/buttonmatrix.rst:441 4db40c35c0504922ac0d0f6b2c1b0b6a msgid "The alignment type." msgstr "对齐类型。" -#: ../../en/m5ui/buttonmatrix.rst:442 6df57c3bfa75495c90e9b59bdad81934 +#: ../../en/m5ui/buttonmatrix.rst:442 7eac3d00fc18405bafa2ee62bf9f63f3 msgid "The x-offset from the aligned object." msgstr "与对齐对象的x偏移量。" -#: ../../en/m5ui/buttonmatrix.rst:443 0a9351caf6964c1786f6fb335c6e5a13 +#: ../../en/m5ui/buttonmatrix.rst:443 f3ce1f11c28143db9dbe4721084da27d msgid "The y-offset from the aligned object." msgstr "与对齐对象的y偏移量。" -#: ../../en/m5ui/buttonmatrix.rst:448 81ccc58f7e4e48a7a51d155e1d0d7263 +#: ../../en/m5ui/buttonmatrix.rst:448 06910263057e4d6db3147c2dbe2d60ef msgid "|align_to.png|" msgstr "" -#: ../../en/refs/m5ui.buttonmatrix.ref:3 18a38fbf6b4040efb0ec4ab99f7176ed +#: ../../en/refs/m5ui.buttonmatrix.ref:3 85eba71f4d454eefa77b81fe39fdc4bf msgid "align_to.png" msgstr "" -#: 2a5c4c9acf2842e98c587d6cb44e23d1 -#: m5ui.buttonmatrix.M5ButtonMatrix.set_button_ctrl:1 of -msgid "Set control flags for a specific button." -msgstr "设置特定按钮的控制标志。" - -#: 573451ee7cde41b18f8a5554111ed3b6 -#: m5ui.buttonmatrix.M5ButtonMatrix.set_button_ctrl:3 of -msgid "The button ID to set control flags for." -msgstr "要设置控制标志的按钮ID。" - -#: 52da1f38c2284129b3d26849cb7dcdc0 -#: m5ui.buttonmatrix.M5ButtonMatrix.set_button_ctrl:4 of -msgid "The control flags to set." -msgstr "要设置的控制标志。" - -#: fa7c38942b1843149b4eef9212eaa5b5 -#: m5ui.buttonmatrix.M5ButtonMatrix.set_button_ctrl:8 of -msgid "|set_button_ctrl.png|" -msgstr "" - -#: ../../en/refs/m5ui.buttonmatrix.ref:12 c463c2a21a9b44e9af94c3bb58ea0b7a -msgid "set_button_ctrl.png" -msgstr "" - -#: 2ade3b61acf34022a87f4d5a10ce7445 +#: 14627d142bf844f984a3951cb313231d #: m5ui.buttonmatrix.M5ButtonMatrix.toggle_button_ctrl:1 of msgid "Toggle control flags for a specific button." msgstr "切换特定按钮的控制标志。" -#: fcb4fe6c55744da0884785e0f2dce691 +#: 982997e0f587400087900a2815a81a7c #: m5ui.buttonmatrix.M5ButtonMatrix.toggle_button_ctrl:3 of msgid "The button ID to toggle control flags for." msgstr "要切换控制标志的按钮ID。" -#: c56d38fbcdbd46509a42c75153ef7ad8 +#: 95a8378325d440b3843a00db95f9634f #: m5ui.buttonmatrix.M5ButtonMatrix.toggle_button_ctrl:4 of msgid "The control flags to toggle." msgstr "要切换的控制标志。" -#: 8a184cd855b34277ad7327e49aca8b7a +#: 5a88c97c7fa042289e089fc29718324f #: m5ui.buttonmatrix.M5ButtonMatrix.toggle_button_ctrl:8 of msgid "|toggle_button_ctrl.png|" msgstr "" -#: ../../en/refs/m5ui.buttonmatrix.ref:25 5612193223184918a9e0af0d7ccbb901 +#: ../../en/refs/m5ui.buttonmatrix.ref:25 92772a8190944aa8b60ecfd9644f02f5 msgid "toggle_button_ctrl.png" msgstr "" -#: e8f727ee3a774960abfaf3e476b1f29f +#: 54989dd248474a289a129af8c0693255 #: m5ui.buttonmatrix.M5ButtonMatrix.set_textarea:1 of msgid "Set a M5TextArea to display button text." msgstr "设置M5TextArea以显示按钮文本。" -#: 0d3f3197228243968e3796eb9052d808 +#: a0084a4593c34258bc941954fc8e33c9 #: m5ui.buttonmatrix.M5ButtonMatrix.set_textarea:3 of msgid "The M5TextArea to set." msgstr "要设置的M5TextArea。" -#: 1197b3d677ae456bafe004025c9021fe +#: 5e4d7561b037442ca3c9d9daf2d4c21d #: m5ui.buttonmatrix.M5ButtonMatrix.set_textarea:7 of msgid "|set_textarea.png|" msgstr "" -#: ../../en/refs/m5ui.buttonmatrix.ref:21 1384e3ff95e2497baeef20489ebd81e9 +#: ../../en/refs/m5ui.buttonmatrix.ref:21 444685851a78469fadf4619102af0f63 msgid "set_textarea.png" msgstr "" -#: 2e71884ccb414638bd34087d55dba57a +#: 9b0be599f5a846a69abb97d8db9c4279 #: m5ui.buttonmatrix.M5ButtonMatrix.get_textarea:1 of msgid "Get the currently set M5TextArea." msgstr "获取当前设置的M5TextArea。" -#: 5b304a9de35d4e02adb75fe6d74aabf4 +#: 763909d96662424d9e3b88ca4f40ff3a #: m5ui.buttonmatrix.M5ButtonMatrix.get_textarea:3 of msgid "The M5TextArea currently set for the button matrix." msgstr "当前为按钮矩阵设置的M5TextArea。" -#: 8f05b93ccadb4717810f4b4e7a931c38 +#: 86aa65cede284701a8e9a79651479bb9 #: m5ui.buttonmatrix.M5ButtonMatrix.get_textarea:8 of msgid "|get_textarea.png|" msgstr "" -#: ../../en/refs/m5ui.buttonmatrix.ref:10 372e9c2854ed429db5133d3a7ae264bd +#: ../../en/refs/m5ui.buttonmatrix.ref:10 75f777bc234f4369834fa13a3d3d7ccd msgid "get_textarea.png" msgstr "" +#: 7f41c1ab482d41bea85eb02b7bf646c6 +#: m5ui.buttonmatrix.M5ButtonMatrix.get_selected_button:1 of +msgid "Get the ID of the currently selected button." +msgstr "获取最后按下按钮的索引。" + +#: 14b39acd790f41c5a2779add1c4538f9 +#: m5ui.buttonmatrix.M5ButtonMatrix.get_selected_button:3 of +msgid "The ID of the currently selected button, or -1 if none is selected." +msgstr "当前选定按钮的 ID,如果未选定则为 -1。" + +#: 130694dd1931421882e99123804b94b2 +#: m5ui.buttonmatrix.M5ButtonMatrix.get_selected_button:8 of +msgid "|get_selected_button.png|" +msgstr "" + +#: ../../en/refs/m5ui.buttonmatrix.ref:9 935cb3d2b293478c91082b1eba4a5489 +msgid "get_selected_button.png" +msgstr "" + +#~ msgid "" +#~ "The index of the last pressed " +#~ "button, or lv.buttonmatrix.BUTTON.NONE if no" +#~ " button is pressed." +#~ msgstr "最后按下按钮的索引,如果没有按钮被按下,则返回 lv.buttonmatrix.BUTTON.NONE。" + diff --git a/docs/locales/zh_CN/LC_MESSAGES/m5ui/label.po b/docs/locales/zh_CN/LC_MESSAGES/m5ui/label.po index 3fa8fdee..419f46c7 100644 --- a/docs/locales/zh_CN/LC_MESSAGES/m5ui/label.po +++ b/docs/locales/zh_CN/LC_MESSAGES/m5ui/label.po @@ -9,7 +9,7 @@ msgid "" msgstr "" "Project-Id-Version: UIFlow2 Programming Guide \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-06-26 16:19+0800\n" +"POT-Creation-Date: 2025-09-17 10:26+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language: zh_CN\n" @@ -18,480 +18,477 @@ msgstr "" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" -"Generated-By: Babel 2.16.0\n" +"Generated-By: Babel 2.17.0\n" -#: ../../en/m5ui/label.rst:4 8b659a8bce9246ef9187c22235ea8eff -msgid "N5Label" +#: ../../en/m5ui/label.rst:5 ../../en/m5ui/label.rst:54 +#: 376604be23a94a70b92e32ddd3f8bd3f ac8107500c424193a0a4fc4b4b7224b9 +msgid "M5Label" msgstr "" -#: ../../en/m5ui/label.rst:8 b31fb5aa3870484a9531fbbf243821dc +#: ../../en/m5ui/label.rst:9 4358ab0e70924e38aa29c2cd0160b60f msgid "" "M5Label is a widget that can be used to create labels in the user " "interface. It can display text and can be styled with different fonts, " "colors, and sizes." msgstr "M5Label 是一个可用于在用户界面中创建标签的小部件。它可以显示文本,并可以使用不同的字体、颜色和大小进行样式设置。" -#: ../../en/m5ui/label.rst:12 cadecff3920c41c0bc4d1a1e03a35f74 +#: ../../en/m5ui/label.rst:13 0435a6bbedeb4a41a1a24a2b45e951a3 msgid "UiFlow2 Example" msgstr "UiFlow2 应用示例" -#: ../../en/m5ui/label.rst:15 ../../en/m5ui/label.rst:34 -#: 3127ef8a8a874f6fbe713e36be5180b2 4a431834462140c095f3ea588ad39a74 +#: ../../en/m5ui/label.rst:16 ../../en/m5ui/label.rst:35 +#: 3f70cd8b4bd84a81a365491b09cd76f2 42515a0f37f94994aba87664f73525a9 msgid "scroll label" msgstr "滚动标签" -#: ../../en/m5ui/label.rst:17 396ade604fb14775a4b0f0f579b5661e +#: ../../en/m5ui/label.rst:18 fb8f8a367e074cb58873f924479799cb msgid "Open the |cores3_scroll_label_example.m5f2| project in UiFlow2." msgstr "在 UiFlow2 中打开 |cores3_scroll_label_example.m5f2| 项目。" -#: ../../en/m5ui/label.rst:19 ../../en/m5ui/label.rst:36 -#: 5a72173892474ff5badc285d332c658e 63298ada59c148e08583271c139a32bf +#: ../../en/m5ui/label.rst:20 ../../en/m5ui/label.rst:37 +#: 018c87c5b7c449f384af5c968e0ab6c9 13e1dbcdb9b644fa80cbddc046f50f77 msgid "" "This example demonstrates how to create a label that scrolls text in a " "circular manner." msgstr "此示例演示如何创建以循环方式滚动文本的标签。" -#: ../../en/m5ui/label.rst:21 ../../en/m5ui/label.rst:66 -#: ../../en/m5ui/label.rst:83 ../../en/m5ui/label.rst:101 -#: ../../en/m5ui/label.rst:118 ../../en/m5ui/label.rst:136 -#: ../../en/m5ui/label.rst:156 ../../en/m5ui/label.rst:176 -#: ../../en/m5ui/label.rst:195 ../../en/m5ui/label.rst:213 -#: ../../en/m5ui/label.rst:231 ../../en/m5ui/label.rst:250 -#: ../../en/m5ui/label.rst:268 ../../en/m5ui/label.rst:309 -#: 02a1b2cd91ce4ceb8f2440942be8c8fd 1e32f52d56a8427aa5fdbdf49ccdc24c -#: 282c1978f5d546ccb873b841206f8e2b 2b0805ac60524ee099ed606bbad12541 -#: 7b638c6168b2405688eba1ac2c1d234f 8487ef7504a04190b8ccee24b969adfb -#: 88cfaae41b4444a1aaa28a212caab13a 894511c55e8045ddb004bcb69655ca92 -#: 8eaa1b1963764294b38bc710ca6a817e 96c2f030b56d451c9c079676d28aa9d4 -#: a4c994b6eadb4ff6b179ce78711b6b04 a8dc89b8746146db8d0041f484ffdc43 -#: ad1d5e8f84b24e938d38a54e3bb54d00 b36047682400440f8a174afd65b3380d -#: b7c78f23f6ff4cd5a5c0a731f3f44db6 db84251bd3ae4ce5acacc073e8e34d48 -#: ec3e6c62f38e4f3eb0784d55c30f7799 m5ui.label.M5Label:12 +#: ../../en/m5ui/label.rst:22 ../../en/m5ui/label.rst:67 +#: ../../en/m5ui/label.rst:84 ../../en/m5ui/label.rst:102 +#: ../../en/m5ui/label.rst:119 ../../en/m5ui/label.rst:137 +#: ../../en/m5ui/label.rst:157 ../../en/m5ui/label.rst:177 +#: ../../en/m5ui/label.rst:196 ../../en/m5ui/label.rst:214 +#: ../../en/m5ui/label.rst:232 ../../en/m5ui/label.rst:251 +#: ../../en/m5ui/label.rst:269 ../../en/m5ui/label.rst:310 +#: 0b069956c08d48699cd861f2419cbbdd 0d939d30a2e4497f8fbe43f54d81741b +#: 124338016c334d8f9ec81346f6465c96 1b6f918df9484e07a81d7d2850ef42fb +#: 2fd29a2432a24d73b134ff5f9ee8db4e 496d00b140f046c2b685d7b0d3e87dc8 +#: 55b88905847949a59b0d0b9af4829c90 652ac41caa0b45dfabd8078d1ceed7b6 +#: 7fe44de071f94683b32be994d06dc4e2 8838b8c875ca46fea15ebb31dc0b96d7 +#: 9f64923ca7c24914adb6323d7790fab0 b3d54cdcd76c48d2a411e9d693643fe4 +#: c3de89accb524e83a2d2cc43a5bc05d6 cf6d8b1d37434ce987991478e791f948 +#: eb1c9ccc5131425eb6a2fdcec1d8fe4b f025007e24f344bca4b2ca4f39fdadb7 +#: f30fa1bac306420d8cbf7cc3dc47ccfb m5ui.label.M5Label:12 #: m5ui.label.M5Label.set_shadow:10 m5ui.label.M5Label.unset_shadow:3 of msgid "UiFlow2 Code Block:" msgstr "UiFlow2 代码块:" -#: ../../en/m5ui/label.rst:23 bef77983b8d0438387998eeb337a4d6f +#: ../../en/m5ui/label.rst:24 648ad234e28d412594a313546f6dfd2e msgid "|cores3_scroll_label_example.png|" msgstr "" -#: ../../en/refs/m5ui.label.ref:1 e3ec00a2a47149eeba1ae40bb9a6cf60 +#: ../../en/refs/m5ui.label.ref:1 0328bc203bc94066bd4164bc9cf6f14b msgid "cores3_scroll_label_example.png" msgstr "" -#: ../../en/m5ui/label.rst:25 ../../en/m5ui/label.rst:44 -#: 25f04a6b398046c1b9cb8509f933982f 63f2e30e5661454e9043085901c8b2a9 +#: ../../en/m5ui/label.rst:26 ../../en/m5ui/label.rst:45 +#: 46f13ee682894f0a9c7f5df93bd24d5f e93351a2ea85459b9e11e43f2f031ed6 msgid "Example output:" msgstr "示例输出:" -#: ../../en/m5ui/label.rst:27 ../../en/m5ui/label.rst:46 -#: ../../en/m5ui/label.rst:64 ../../en/m5ui/label.rst:81 -#: ../../en/m5ui/label.rst:99 ../../en/m5ui/label.rst:116 -#: ../../en/m5ui/label.rst:134 ../../en/m5ui/label.rst:154 -#: ../../en/m5ui/label.rst:174 ../../en/m5ui/label.rst:193 -#: ../../en/m5ui/label.rst:211 ../../en/m5ui/label.rst:229 -#: ../../en/m5ui/label.rst:248 ../../en/m5ui/label.rst:266 -#: ../../en/m5ui/label.rst:307 03e90f2fe8454860aafb13502d6a0355 -#: 0cce67cbad7941a5a9c5f92c8cd4308a 18ed67d824794195a118f95369501219 -#: 22ea1e2fb58e478f8c0b60dbc4189b4f 3fef67191abe4c30b6c202a4c8210634 -#: 537103503371452a9d34a1efdf620d5e 54d93b9dafee409d958099bb8ccf7ff5 -#: 6fcf90bb8541407a95834aac0dd92ecd 78e2bd1684d24598ae5a802c22851e9e -#: 80289f7b943940198fb83f98d31fc632 9119488df6234ac8a796f6dcfe6de136 -#: 9889f36d45614f7cbcd7017a325450b4 bd912e8f58e04672bdbed79064dbc13e -#: da6867096e584291ac66fe14d954ea32 dbd4da74d8da4d1bb31efba4de43d4c7 -#: dfb8eac9875849fcac152977e073f564 f42afb98c8364b7983f0877ae7f49807 +#: ../../en/m5ui/label.rst:28 ../../en/m5ui/label.rst:47 +#: ../../en/m5ui/label.rst:65 ../../en/m5ui/label.rst:82 +#: ../../en/m5ui/label.rst:100 ../../en/m5ui/label.rst:117 +#: ../../en/m5ui/label.rst:135 ../../en/m5ui/label.rst:155 +#: ../../en/m5ui/label.rst:175 ../../en/m5ui/label.rst:194 +#: ../../en/m5ui/label.rst:212 ../../en/m5ui/label.rst:230 +#: ../../en/m5ui/label.rst:249 ../../en/m5ui/label.rst:267 +#: ../../en/m5ui/label.rst:308 082435bebb164a9a82bce7e06a98016d +#: 0ab17acbad6146779c56590872e6bcfb 100639a41abe4cfca482685a663330f4 +#: 1274518871eb429c9137ddddd7d6a68e 2b60b52797fb484a92af365adb1ffd38 +#: 3ac05b3a22334f549fb10aeb744ec298 44df700dfece4401b99e64c7a8479a35 +#: 478491bdc4ef457cb2a6463bd3cc76a5 5d84a5287d1d47aeb7ca7b8e9dfe149c +#: 64429d893e5b496798707ec899c5682d 8c384d38fdac4ebb8c1c36ae319c1426 +#: 8cdaa4eb3ecc40509d1da909cd3465a4 944c83fbce264edf96745efb87a06199 +#: 9ae7d90f727e4cf0bcb0c292fca2de7b bf124c86985b4ebf819d745edd280faa +#: e3fac589206f4def85ec6e7f9f47e7a2 f91f748dc3294396b8219572f2fd6c2c #: m5ui.label.M5Label:14 m5ui.label.M5Label.set_shadow:8 of msgid "None" msgstr "" -#: ../../en/m5ui/label.rst:31 09e36183e5da48e2a6ccada0ed7dd0be +#: ../../en/m5ui/label.rst:32 d3d0f54b55ac466b8f139593854ade75 msgid "MicroPython Example" msgstr "MicroPython 应用示例" -#: ../../en/m5ui/label.rst:38 ../../en/m5ui/label.rst:70 -#: ../../en/m5ui/label.rst:87 ../../en/m5ui/label.rst:105 -#: ../../en/m5ui/label.rst:122 ../../en/m5ui/label.rst:140 -#: ../../en/m5ui/label.rst:160 ../../en/m5ui/label.rst:180 -#: ../../en/m5ui/label.rst:199 ../../en/m5ui/label.rst:217 -#: ../../en/m5ui/label.rst:235 ../../en/m5ui/label.rst:254 -#: ../../en/m5ui/label.rst:274 ../../en/m5ui/label.rst:313 -#: 00e42519be614cb8bb87130e087cb606 063873f4b18449b1ac613fcb9ffa2b95 -#: 08b0e650b4c147e6a664e3f126a52173 0a5c82ba67ef4e3da43a6b204a29edd1 -#: 24218454642a4b8e9d0736a69b58a5ce 347f6962b7a94b02b1c39e27023ba96d -#: 37d7638d7da441e69c735f335892448b 4940bfe7715e41b8aeba9674dc0863a5 -#: 55dac99c323f4394b060ec4167a91ac8 68a4982c22274dabbc1de1c36ba44d58 -#: 7b4c83fa524744ea9beb5de2fdea31cd 8a21ce73103043deb77e8e692ce7c555 -#: a1f2b32b22cc408eb8007dc2b1727e5e a5bbf49f033142c593a0c876da1455cb -#: bcac3191a88f4b659c663005fabdf472 c9c46dd0be364f60b1a011a34ff9cea1 -#: d11b2f30641b470bb0e91f08b5bc40e4 m5ui.label.M5Label:16 +#: ../../en/m5ui/label.rst:39 ../../en/m5ui/label.rst:71 +#: ../../en/m5ui/label.rst:88 ../../en/m5ui/label.rst:106 +#: ../../en/m5ui/label.rst:123 ../../en/m5ui/label.rst:141 +#: ../../en/m5ui/label.rst:161 ../../en/m5ui/label.rst:181 +#: ../../en/m5ui/label.rst:200 ../../en/m5ui/label.rst:218 +#: ../../en/m5ui/label.rst:236 ../../en/m5ui/label.rst:255 +#: ../../en/m5ui/label.rst:275 ../../en/m5ui/label.rst:314 +#: 06024133178a489aaf84df0e758e7778 3ec479ab7b394c71a7768fb72184d390 +#: 5b401a0bde814bf2a70cae10804d1c6b 62c790fac18546c7b5f13296ce035e93 +#: 63b34a4e13404b82997c5546aa7bde0a 63ef962b93b045f6bfd52448e5a1ddb3 +#: 6f287f162a694049a75ebf32403b2880 9500d65e0c3b4d50a7eebd0fce5d44e8 +#: 9a462ae30643429d821721de459ef7f1 9de03957790343bb98657f89c9a8f2f6 +#: 9fac3347f9624bae848b4af3fcfb06d0 c0ae02a01f43406d9777ce0d000ba5b7 +#: c25ec0fc4406488b9a73c0360d6c2ce5 e7341bee00f242dc8c7e9127f8255aef +#: e8264ecba56245ea90064254017c998f e9cfed5119dd4a3bbf96d0716f7f7a0a +#: f7608caa809d4de0858228abea5e8153 m5ui.label.M5Label:16 #: m5ui.label.M5Label.set_shadow:14 m5ui.label.M5Label.unset_shadow:7 of msgid "MicroPython Code Block:" msgstr "MicroPython 代码块:" -#: ../../en/m5ui/label.rst:50 3db2d1d60dff4ba1a14dde2cb7026442 +#: ../../en/m5ui/label.rst:51 60f1af137fe5443793dd11aa19dcd5dd msgid "**API**" msgstr "API参考" -#: ../../en/m5ui/label.rst:53 a7fc49a82ed94331a4a37b74bb845f5a -msgid "M5Label" -msgstr "" - -#: 4aed3d9ba67b4a45a89ba049a7a1f4de m5ui.label.M5Label:1 of +#: e0421b850705488a80dc9004af5c4bef m5ui.label.M5Label:1 of msgid "Bases: :py:class:`~lvgl.label`" msgstr "" -#: 1ecec18d6f0d4a9d8f5135e8ad3e3cfe m5ui.label.M5Label:1 of +#: 1a22803e0f8f433998555e1845c8460f m5ui.label.M5Label:1 of msgid "Create a label object." msgstr "创建标签对象。" -#: ../../en/m5ui/label.rst 0d51798b448d41dcb4c2d5c2341a7728 -#: 161189e4cc4c4153a7f52b41dde1f8fb 383590fe3ebe48b5b8c56c0992f71a6e -#: 6d82484925f84796a70919700158cc6a 6deedd0da1c243fa80ce84cfb8086342 -#: 71376a52fbd84ee8877d4151fd0a0b7d 96a641f7da164c5796837898a73bce59 -#: 99f070739d014ce4a5b7bb7746c1d922 b2f3b6902a994d2d9f1f031be48bd350 -#: b483a32b59ea4daeb3debd0b14bf6b2e b87eca9923634ddcb546cafb5a1fab05 -#: c5fe20fe83be43f28591f16f4651d965 d26de94acc5145078db742b39d12d1c6 -#: f5fd38bf153d4b1a8ee5bc4b434f2d85 f871049b38c84e05845ff027cec498cc +#: ../../en/m5ui/label.rst 27414db2b195418c98f50f0ce2c67bfb +#: 2988e120bb1f46c58750cb0e188ea72b 36be8a60b77a4bb8a9a1bb0a8f11af29 +#: 3f5c9d0315ff4292afb7b15b695ebce7 3fd9a9112cf54844853e4f1488115500 +#: 618dd160161349489d014757d11444d1 76af675d9b0a46b3935d1f80cc77aa22 +#: 92820eba5a634e0db154c73c0c906462 96fed367554142eabad8a7dfd51a7763 +#: 9a473e7cd7c44e8995ee963fe5dd6ca2 a158957ae08d4206bc5efd5680190c4f +#: ac512b386ba142e2936d2125614f7a21 bcc6bf59d88f4ffc9ad0fae155128417 +#: f504cffe91fb47b6abba5a93df739fbd f7c8018eb88741d9965c5df61379f12b #: m5ui.label.M5Label.set_shadow of msgid "Parameters" msgstr "" -#: 05c90064dad0456aaba45d8cde951631 m5ui.label.M5Label:3 of +#: c0eaacee0cd645f09246d199276bef8a m5ui.label.M5Label:3 of msgid "The text to display on the label." msgstr "标签上显示的文本。" -#: ace2ae1e1e92492abee189f57f39e8ee m5ui.label.M5Label:4 of +#: 39412d4366764080a4df1580264d154f m5ui.label.M5Label:4 of msgid "The x position of the label." msgstr "标签的 x 位置。" -#: be89188e73844634a40bf31b1000a11e m5ui.label.M5Label:5 of +#: 95740d560eb5458e9b0d953e37f625fb m5ui.label.M5Label:5 of msgid "The y position of the label." msgstr "标签的 y 位置。" -#: f96cdec781f444b9b122379cf9c9097f m5ui.label.M5Label:6 of +#: c66e3cd779f24c57b979f79b85137fc0 m5ui.label.M5Label:6 of msgid "The text color of the label in hexadecimal format." msgstr "标签的文本颜色(十六进制格式)。" -#: b8420b1ece624041bd10f85e16e80fd1 m5ui.label.M5Label:7 of +#: 15245b4e754e4780a9a867b04651dcd7 m5ui.label.M5Label:7 of msgid "The background color of the label in hexadecimal format." msgstr "标签的背景颜色(十六进制格式)。" -#: fb2b69cfce31499397c5aea1478751ff m5ui.label.M5Label:8 of +#: 15ce9eaffa334ac387d236adb6c629f2 m5ui.label.M5Label:8 of msgid "The background opacity of the label (0-255)." msgstr "标签的背景不透明度(0-255)。" -#: 51457fd0b0d64bdbae5ff2c67d0f8211 m5ui.label.M5Label:9 of +#: 0c5872ee4f5c46da8ad5604ebb7a103c m5ui.label.M5Label:9 of msgid "The font to use for the button text." msgstr "按钮文本使用的字体。" -#: 62a3724259804778b5218fb039c759e7 m5ui.label.M5Label:10 of +#: 6f878ec385dd4a1db9f0bb79cd92581f m5ui.label.M5Label:10 of msgid "" "The parent object to attach the button to. If not specified, the button " "will be attached to the default screen." msgstr "按钮要附加到的父对象。如果未指定,按钮将附加到默认屏幕。" -#: ../../en/m5ui/label.rst:60 5ab71ea553c74a73baa09b3147f62982 +#: ../../en/m5ui/label.rst:61 dba786db3f224345aab3336c7443d266 msgid "" "Set a flag on the object. If ``value`` is True, the flag is added; if " "False, the flag is removed." msgstr "在对象上设置一个标志。如果 ``value`` 为 True,则添加该标志;如果为 False,则移除该标志。" -#: ../../en/m5ui/label.rst:62 e078ef2c8d394bc8816b715b2d463fad +#: ../../en/m5ui/label.rst:63 ceb6442604fd4bdeba358b01f539d496 msgid "The flag to set." msgstr "要设置的标志。" -#: ../../en/m5ui/label.rst:63 039282c2bfc34b708eb95de89d001c58 +#: ../../en/m5ui/label.rst:64 32917838489542f1bfb05b79ce625a94 msgid "If True, the flag is added; if False, the flag is removed." msgstr "如果为 True,则添加标志;如果为 False,则删除标志。" -#: ../../en/m5ui/label.rst 167fded2d00f49758d5b036f401faa86 -#: 231b44a91b55431d8133cdff806fc354 252973756bb94138bcac50781648b388 -#: 5234ecefb0804d389f1eb5424f8a3996 5ffd014b312044e5a6bb183f900dedf5 -#: 6f4141e18382436f982f9228e6b5ae98 7aedd1da44fa418a9293ab6e5656cee3 -#: 859d49000ae34028b09ecee0d7577025 86475bda4e77466f8d2d4adec3b57928 -#: 8feb7c045dcd4fd093b0b73a72529089 95706eaa079b4063bd5ea472cf042c69 -#: d5eee0574deb46bd8a206a8af3b9872e e4a87265f65c4990b2dc84b230272747 -#: ec5d55a544184d72badeed7c5b37b403 m5ui.label.M5Label.set_shadow of +#: ../../en/m5ui/label.rst 0a3fde6ad1584a2f87c20ade8a397ceb +#: 2176cfe05dd44650b8940e0fc37338ee 2b2b96ca60854ffeaacf51750a4aed0c +#: 3150e7bdbcfb4f5caa618d3ae2cf7f8e 423c2f1ad5394cffa420103cae170448 +#: 7c7ef35aa87c497781765372ecd37d7b 7f050270f4824e50aa7777bbe1096982 +#: a2db8edf0a784adea825ac5ef642fb63 abc1d234bb9941d593cb9dc9c4d68f8e +#: b5e26fa7a46d4748aba406a799aeedc5 b7d30c03a49c4f1c89a103cf3ec5ee77 +#: d22bee9d4e6440aa881ac151c67dccbe ebd4dea953ba42deaa21d564b1c3b31b +#: ebdb8b10e7f6434f95cb47ed8d0a7f75 m5ui.label.M5Label.set_shadow of msgid "Returns" msgstr "" -#: ../../en/m5ui/label.rst:68 662715e5f32d43afa5bdb11b8051f073 +#: ../../en/m5ui/label.rst:69 13c6d36d9c8248809c696effcbe7f964 msgid "|set_flag.png|" msgstr "" -#: ../../en/refs/m5ui.label.ref:8 4d5731ec0e5a420cbc453a911e32d92e +#: ../../en/refs/m5ui.label.ref:8 26319a1a78174e589eb536e50a86c9e7 msgid "set_flag.png" msgstr "" -#: ../../en/m5ui/label.rst:78 f9693a46660d4b3aa655329ed954be79 +#: ../../en/m5ui/label.rst:79 2cda1d9276ea45148e7fcc3f87fa5482 msgid "" "Toggle a flag on the object. If the flag is set, it is removed; if not " "set, it is added." msgstr "切换对象上的标志。如果设置了标志,则移除该标志;如果未设置,则添加该标志。" -#: ../../en/m5ui/label.rst:80 f71821a639d540abb8c5a2e173b74a36 +#: ../../en/m5ui/label.rst:81 918a57eece2d4cbfb88d52787e7691ca msgid "The flag to toggle." msgstr "要切换的标志。" -#: ../../en/m5ui/label.rst:85 9a33637061a04c94928a59267813a250 +#: ../../en/m5ui/label.rst:86 74734572efe64f06baa1435f119697bf msgid "|toggle_flag.png|" msgstr "" -#: ../../en/refs/m5ui.label.ref:21 7df1b8f0f07a42be992e8402c493ecca +#: ../../en/refs/m5ui.label.ref:21 97b9823d33184385b07ba595017183c0 msgid "toggle_flag.png" msgstr "" -#: ../../en/m5ui/label.rst:95 00ab9e5b69a340d6a9c82cb8b0b9e580 +#: ../../en/m5ui/label.rst:96 0c9c63f8bb3545fc835053834987fb18 msgid "" "Set the state of the label. If ``value`` is True, the state is set; if " "False, the state is unset." msgstr "设置标签的状态。如果 ``value`` 为 True,则设置状态;如果为 False,则取消设置状态。" -#: ../../en/m5ui/label.rst:97 2ae16659d2a84435bb4ebf822ecf3945 +#: ../../en/m5ui/label.rst:98 677eb30c70fc45b186e7e1b6e2fdc056 msgid "The state to set." msgstr "要设置的状态。" -#: ../../en/m5ui/label.rst:98 1cd0a2c12f9c40d3bae37831fcf8daf9 +#: ../../en/m5ui/label.rst:99 012534341d3e4e57aaff89c5f5df6f74 msgid "If True, the state is set; if False, the state is unset." msgstr "如果为 True,则状态已设置;如果为 False,则状态未设置。" -#: ../../en/m5ui/label.rst:103 8304d41049e34904abecca8d1d731df2 +#: ../../en/m5ui/label.rst:104 bd043e9b91f74ceb99ebdb3a09ba4493 msgid "|set_state.png|" msgstr "" -#: ../../en/refs/m5ui.label.ref:13 02411e929ee849c88c217e5a3d2b284a +#: ../../en/refs/m5ui.label.ref:13 3880c54d24fe4f9eb3f84d3f042b4de2 msgid "set_state.png" msgstr "" -#: ../../en/m5ui/label.rst:113 564f1185d7104ce88d8663e26edadb54 +#: ../../en/m5ui/label.rst:114 60c3221daef942599a1791c761be8e24 msgid "" "Toggle the state of the label. If the state is set, it is unset; if not " "set, it is set." msgstr "切换标签的状态。如果标签状态已设置,则取消设置;如果标签状态未设置,则设置标签状态。" -#: ../../en/m5ui/label.rst:115 2dcb2d7b2bff400e9099d99ff2c9ee1b +#: ../../en/m5ui/label.rst:116 7337a094dd8f45beb8263b2c23039a40 msgid "The state to toggle." msgstr "要切换的状态。" -#: ../../en/m5ui/label.rst:120 aba56bf8a82c42cab7f2300e343a2475 +#: ../../en/m5ui/label.rst:121 7d025e237f0842e1a3972263eb64216b msgid "|toggle_state.png|" msgstr "" -#: ../../en/refs/m5ui.label.ref:22 70e28948deaf4416a99c5e4dad21b5a0 +#: ../../en/refs/m5ui.label.ref:22 de26a88a9a35460eb9a7f4de98b7b05f msgid "toggle_state.png" msgstr "" -#: ../../en/m5ui/label.rst:130 77c6b46672444e338283c051041a7378 +#: ../../en/m5ui/label.rst:131 4b78566efc5645759e5723dd1b1a3580 msgid "Set the font of the label text." msgstr "设置标签文本的字体。" -#: ../../en/m5ui/label.rst:132 15d32cb36319452a8287b92064a8970b +#: ../../en/m5ui/label.rst:133 752ec3ce2f724f9aa6da2b67727a7125 msgid "The font to set." msgstr "要设置的字体。" -#: ../../en/m5ui/label.rst:133 ../../en/m5ui/label.rst:153 -#: ../../en/m5ui/label.rst:173 4aafbcbcbce147408f4fd5aae289f267 -#: 57118c0e1e124496a40480f714105b46 93ef0bd7bdcd43d8832046b0211c79a2 +#: ../../en/m5ui/label.rst:134 ../../en/m5ui/label.rst:154 +#: ../../en/m5ui/label.rst:174 5aeec82d8eef48f7bddd62d62e9449f1 +#: 6299cb0821b44bdc901e11f3fd8e862b 7525fb974333418693c22b9ebce14209 msgid "The part of the object to apply the style to (e.g., lv.PART.MAIN)." msgstr "应用样式的对象部分(例如,lv.PART.MAIN)。" -#: ../../en/m5ui/label.rst:138 c8b8f53fa5c54ca4b2b0c64188ba5ed6 +#: ../../en/m5ui/label.rst:139 81b84cb21e964a4c84856c110602fe53 msgid "|set_style_text_font.png|" msgstr "" -#: ../../en/refs/m5ui.label.ref:14 94c5bbe42d714176b8ed780c7918b617 +#: ../../en/refs/m5ui.label.ref:14 6b8d343d52274ac4803334bc75ebfd41 msgid "set_style_text_font.png" msgstr "" -#: ../../en/m5ui/label.rst:149 a32f6cd1c9ab441589c5fcb4b193d5f7 +#: ../../en/m5ui/label.rst:150 da2ad69b5794482db6ef9cf7ede48f05 msgid "Set the color of the text." msgstr "设置文本的颜色。" -#: ../../en/m5ui/label.rst:151 ../../en/m5ui/label.rst:171 -#: 4661530fb4e841cbbbe236d0d4acef61 87a34b53a6164c7b8b85e8edc3ed916c +#: ../../en/m5ui/label.rst:152 ../../en/m5ui/label.rst:172 +#: 44a330aab78b48f7854a3438026cab78 6c9fc90b51ca4c67b2bcd8377253ba64 msgid "The color to set." msgstr "要设置的颜色。" -#: ../../en/m5ui/label.rst:152 ../../en/m5ui/label.rst:172 -#: 7bd4d9f477f64d4f80e71e55c8f4fd6c aa6d09e0a83449b2bada99837b115332 +#: ../../en/m5ui/label.rst:153 ../../en/m5ui/label.rst:173 +#: 022eec252f814d99b919ee0d67c3bc97 c24bdca6776b4f378a512af889bbd642 msgid "The opacity of the color." msgstr "颜色的不透明度。" -#: ../../en/m5ui/label.rst:158 ../../en/m5ui/label.rst:178 -#: 282e1d02ec3b45cfa97242c3518d2a1e ef450737aa784fe98b3b711c1ff209a3 +#: ../../en/m5ui/label.rst:159 ../../en/m5ui/label.rst:179 +#: 086c6ffc68a8452dba283a9032f70e34 8c9483f6922d40a6af2c87670a3cb5de msgid "|set_text_color.png|" msgstr "" -#: ../../en/refs/m5ui.label.ref:16 e80b742357f243a5bba5dd1a5346e1e3 -#: f7bd10d0b0da4b77a6b3a9c11ac82f10 +#: ../../en/refs/m5ui.label.ref:16 89ef8599143f436283c743b20197525a +#: a517129421ec42ad96937bb34112a88f msgid "set_text_color.png" msgstr "" -#: ../../en/m5ui/label.rst:169 7eab7e26b0e244f18eeb2dfe29075d08 +#: ../../en/m5ui/label.rst:170 cfa4074f6c644680ad85b30601f2e390 msgid "Set the background color of the label." msgstr "设置标签的背景颜色。" -#: ../../en/m5ui/label.rst:189 6d3c3aff77674949937924b83186987d +#: ../../en/m5ui/label.rst:190 2abd33ad502c40f38ff3e64f33933861 msgid "Set the position of the label." msgstr "设置标签的位置。" -#: ../../en/m5ui/label.rst:191 ../../en/m5ui/label.rst:210 -#: 191fcfbbc5e54885880480a59beafafa 7759551df81148adab2c99967a52320e +#: ../../en/m5ui/label.rst:192 ../../en/m5ui/label.rst:211 +#: 4ef0d57b743f417f8587d2b5617beefc 5e1849e9c18d4f3cb37b3715742c3b9f msgid "The x-coordinate of the label." msgstr "标签的 x 坐标。" -#: ../../en/m5ui/label.rst:192 ../../en/m5ui/label.rst:228 -#: 98ea68ba37dd4de58756d7687166ee6e 9c7ac128d7df4fbf8b5a8f2fc8152a8c +#: ../../en/m5ui/label.rst:193 ../../en/m5ui/label.rst:229 +#: 0713e77dfefa435d92a59cb7320a8e5a b8c0e89f51d047b69c1407513d46d5e1 msgid "The y-coordinate of the label." msgstr "标签的 y 坐标。" -#: ../../en/m5ui/label.rst:197 145fc9482f444e819d325f1f2456a83d +#: ../../en/m5ui/label.rst:198 2a50eec7cda54bd0ac6bd60f6049e9dd msgid "|set_pos.png|" msgstr "" -#: ../../en/refs/m5ui.label.ref:10 b7a35318e55f4f7eb8bec4c20afeecd7 +#: ../../en/refs/m5ui.label.ref:10 1cacbf2bc5184eb699784c76dc127684 msgid "set_pos.png" msgstr "" -#: ../../en/m5ui/label.rst:208 e33597de2bf04b61aad9918baeb22111 +#: ../../en/m5ui/label.rst:209 e9887628f26346c8a9ad96c9c3771ee8 msgid "Set the x-coordinate of the label." msgstr "设置标签的 x 坐标。" -#: ../../en/m5ui/label.rst:215 7fdb98fc9111462a9aea537b176b96bf +#: ../../en/m5ui/label.rst:216 0dc473420fa24a7bbe4e48e3a68b52ef msgid "|set_x.png|" msgstr "" -#: ../../en/refs/m5ui.label.ref:19 7597690bc7a94d5cbc1ca1a75378e896 +#: ../../en/refs/m5ui.label.ref:19 ab1140004f2943069fa693ffd36c9eb4 msgid "set_x.png" msgstr "" -#: ../../en/m5ui/label.rst:226 4fd9ddee60ac4848a6dfce87190e0595 +#: ../../en/m5ui/label.rst:227 8126b635737b496ebd70061daffcfbce msgid "Set the y-coordinate of the label." msgstr "设置标签的 y 坐标。" -#: ../../en/m5ui/label.rst:233 e3abf9e7ca8c4cde8038ce3fec0cd14b +#: ../../en/m5ui/label.rst:234 a685f574aac9429e935574a4620aa77f msgid "|set_y.png|" msgstr "" -#: ../../en/refs/m5ui.label.ref:20 2f97ae3175b14c08b4d4f523d990efa3 +#: ../../en/refs/m5ui.label.ref:20 de0fd93f806f4dea9eb0f9fa6540fcc5 msgid "set_y.png" msgstr "" -#: ../../en/m5ui/label.rst:244 4ccac23199774b519d37d798ea06a45b +#: ../../en/m5ui/label.rst:245 949126d7ddbc4be381c871a0df158785 msgid "Set the size of the label." msgstr "设置标签的大小。" -#: ../../en/m5ui/label.rst:246 ../../en/m5ui/label.rst:265 -#: 23a78ae6df1641368ccd43a0d8fa0947 36d2ce8395064a94a320da067fa6dc3e +#: ../../en/m5ui/label.rst:247 ../../en/m5ui/label.rst:266 +#: 2a7473c7f4d44b699463fc3fc0073ce1 b6a8d1eaeb3d40cfbcd71ea8aeda96d7 msgid "The width of the label." msgstr "标签的宽度。" -#: ../../en/m5ui/label.rst:247 ce0c42b6e712493db8b6cfe8903fb1d1 +#: ../../en/m5ui/label.rst:248 83d1aefea63a4f1cb6cedcb55ad47c4f msgid "The height of the label." msgstr "标签的高度。" -#: ../../en/m5ui/label.rst:252 727f9f8edcdd47b98c532bb675d0534e +#: ../../en/m5ui/label.rst:253 61f1eca6dce14644965e89f1f86daf8d msgid "|set_size.png|" msgstr "" -#: ../../en/refs/m5ui.label.ref:12 53358830cce54d2e83310e5ef0d6eb76 +#: ../../en/refs/m5ui.label.ref:12 7fc2f522083844c39057f7a36fa206e7 msgid "set_size.png" msgstr "" -#: ../../en/m5ui/label.rst:263 88fc065164584954974283b885da38d6 +#: ../../en/m5ui/label.rst:264 c24e972904324c8eb97bfda44f1fa9d9 msgid "Set the width of the label." msgstr "设置标签的宽度。" -#: ../../en/m5ui/label.rst:270 23d03de0d65f498083b061b135b0ea6b +#: ../../en/m5ui/label.rst:271 4be1cff486b84b829ffb9e134e8a8f78 msgid "|set_width.png|" msgstr "" -#: ../../en/refs/m5ui.label.ref:17 04f45c68211b44c0afa1759ba9fce86b +#: ../../en/refs/m5ui.label.ref:17 a570fc9a192543bfa8f8a386c3e8ab8e msgid "set_width.png" msgstr "" -#: ../../en/m5ui/label.rst:272 8804a6fbefe946cf9946366ce8b293ae +#: ../../en/m5ui/label.rst:273 3f4f131c89194d5a8e13be7c7232114e msgid "|set_width1.png|" msgstr "" -#: ../../en/refs/m5ui.label.ref:18 2b3bb5ea4eb34490942463a0f82feb97 +#: ../../en/refs/m5ui.label.ref:18 715d549cf9e84434ac6fe61794885e68 msgid "set_width1.png" msgstr "" -#: ../../en/m5ui/label.rst:301 919cb0c6c6ea4f1684ded3347a6814a2 +#: ../../en/m5ui/label.rst:302 2630483b9d99411bba4e46e310b823aa msgid "Align the label to another object." msgstr "将标签与另一个对象对齐。" -#: ../../en/m5ui/label.rst:303 d486ddef42a945e7ac23b32fd33cd944 +#: ../../en/m5ui/label.rst:304 48188b55753d4a9d817972384d261113 msgid "The object to align to." msgstr "要对齐的对象。" -#: ../../en/m5ui/label.rst:304 f18524d14d2f4a9b8e6399dfcfc79f3a +#: ../../en/m5ui/label.rst:305 09a6025b6cd6403fb589f4949a060ff3 msgid "The alignment type." msgstr "对齐类型。" -#: ../../en/m5ui/label.rst:305 0e0e0c887c2b47358ae59c19bcf794d3 +#: ../../en/m5ui/label.rst:306 a6d27d8f9ed941c5930bcc035a551abf msgid "The x-offset from the aligned object." msgstr "与对齐对象的 x 偏移量。" -#: ../../en/m5ui/label.rst:306 5aa44bb28c604aeabd412b6f92596f50 +#: ../../en/m5ui/label.rst:307 276208dee0184d0187fbd6f07956e0a1 msgid "The y-offset from the aligned object." msgstr "与对齐对象的 y 偏移。" -#: ../../en/m5ui/label.rst:311 0300ccb58adf4f64998b2ad3e974f387 +#: ../../en/m5ui/label.rst:312 f8c73fab27384682af5a3f165877bd24 msgid "|align_to.png|" msgstr "" -#: ../../en/refs/m5ui.label.ref:3 fea132afe3924d80b5bce4f0210db256 +#: ../../en/refs/m5ui.label.ref:3 a28da7d554d64a0887ecb1aa32ffa503 msgid "align_to.png" msgstr "" -#: 401a0c71f7514f19a5ef360f19f88f52 m5ui.label.M5Label.set_shadow:1 of +#: 104107c3533f41e09889f716984932c4 m5ui.label.M5Label.set_shadow:1 of msgid "Set a shadow for the label." msgstr "为标签设置阴影。" -#: bbf53b99e7264b04a996dcb3b8879809 m5ui.label.M5Label.set_shadow:3 of +#: 627e7545d8224e20a769ac345e83530a m5ui.label.M5Label.set_shadow:3 of msgid "The color of the shadow in hexadecimal format or an integer." msgstr "阴影的颜色(十六进制格式或整数)。" -#: d9f72b33334d4da68f5bee35d078eddb m5ui.label.M5Label.set_shadow:4 of +#: b5183b993ed1403d8a1371a0c6056426 m5ui.label.M5Label.set_shadow:4 of msgid "The opacity of the shadow (0-255)." msgstr "阴影的不透明度(0-255)。" -#: 8e6bd76adb6b4a088127eeb41e3ece91 m5ui.label.M5Label.set_shadow:5 of +#: fffd78b741754f7cbcdf386079633a3c m5ui.label.M5Label.set_shadow:5 of msgid "The alignment of the shadow relative to the label." msgstr "阴影相对于标签的对齐方式。" -#: 0a7c18a02f304b7b92675781a263289d m5ui.label.M5Label.set_shadow:6 of +#: 380f569df7fc4d8ca82d317fed7d257f m5ui.label.M5Label.set_shadow:6 of msgid "The horizontal offset of the shadow." msgstr "阴影的水平偏移。" -#: 3f80f9f4d5224a0c93ba5ec711c1631d m5ui.label.M5Label.set_shadow:7 of +#: 89948bba1dd540eea65529ae4b63bc18 m5ui.label.M5Label.set_shadow:7 of msgid "The vertical offset of the shadow." msgstr "阴影的垂直偏移。" -#: c96d18513f174f14a3642a32ced2a6a4 d575e562ac86428b8b25378f268ffd23 +#: 96c3ec41bee14520ac33b9e57da5e771 e182fd3604494f80b3bdeb2844c11a78 #: m5ui.label.M5Label.set_shadow m5ui.label.M5Label.unset_shadow of msgid "Return type" msgstr "" -#: 3894fbc143784dddb4b6bcc68b3163e2 m5ui.label.M5Label.set_shadow:12 of +#: 3ca7c041458c4dba95d90ec3f674ef8c m5ui.label.M5Label.set_shadow:12 of msgid "|set_shadow.png|" msgstr "" -#: ../../en/refs/m5ui.label.ref:11 8f275e1a751441a3a7dde09fae424239 +#: ../../en/refs/m5ui.label.ref:11 1aa4392b9dc349799b65dfa3f8e981bb msgid "set_shadow.png" msgstr "" -#: b0f0e960d255463d8dd3421cdb9fb360 m5ui.label.M5Label.unset_shadow:1 of +#: 8910810756074dfea6bfa3d63ae2470f m5ui.label.M5Label.unset_shadow:1 of msgid "Remove the shadow from the label." msgstr "删除标签上的阴影。" -#: 9cc3d025916240ba99702f152c94d1b5 m5ui.label.M5Label.unset_shadow:5 of +#: f763b62cca5e48cf88b8b629478b991b m5ui.label.M5Label.unset_shadow:5 of msgid "|unset_shadow.png|" msgstr "" -#: ../../en/refs/m5ui.label.ref:23 6c5673a8880a457dbd06c4a7209855a2 +#: ../../en/refs/m5ui.label.ref:23 6adcb7102e664350ae96f736f8a094a7 msgid "unset_shadow.png" msgstr "" @@ -501,3 +498,6 @@ msgstr "" #~ msgid "Set the position of the button." #~ msgstr "设置按钮的位置。" +#~ msgid "N5Label" +#~ msgstr "" + diff --git a/docs/locales/zh_CN/LC_MESSAGES/m5ui/roller.po b/docs/locales/zh_CN/LC_MESSAGES/m5ui/roller.po index 2962d1b9..02ff04ec 100644 --- a/docs/locales/zh_CN/LC_MESSAGES/m5ui/roller.po +++ b/docs/locales/zh_CN/LC_MESSAGES/m5ui/roller.po @@ -9,7 +9,7 @@ msgid "" msgstr "" "Project-Id-Version: UIFlow2 Programming Guide \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-08-20 16:20+0800\n" +"POT-Creation-Date: 2025-09-26 17:20+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language: zh_CN\n" @@ -21,11 +21,11 @@ msgstr "" "Generated-By: Babel 2.17.0\n" #: ../../en/m5ui/roller.rst:4 ../../en/m5ui/roller.rst:55 -#: 03aefaf61d89499baf7f73bc451f9489 3c6adfc5e58f4324b856c0bec5337562 +#: 3be29225c5304c408d83c83824cee275 ac95942236c54fea914fbc9416c6347b msgid "M5Roller" msgstr "" -#: ../../en/m5ui/roller.rst:8 f8de2edb47414ea18db9c77a3e8b0f56 +#: ../../en/m5ui/roller.rst:8 725f0d969c814243b72c61cee23268d9 msgid "" "M5Roller is a widget that can be used to create a roller (spinner/wheel " "picker) in the user interface. It provides a scrollable list of options " @@ -33,21 +33,21 @@ msgid "" "picker wheels." msgstr "M5Roller是一个可用于在用户界面中创建滚轮(旋转器/滚轮选择器)的控件。它提供了一个可滚动的选项列表,用户可以通过向上或向下滚动来选择,类似于iOS风格的选择器滚轮。" -#: ../../en/m5ui/roller.rst:14 00e3bd9b460d442cbdaf611045d338cc +#: ../../en/m5ui/roller.rst:14 a63f58117b2e448498110f70dafd356b msgid "UiFlow2 Example" msgstr "UiFlow2 示例" #: ../../en/m5ui/roller.rst:17 ../../en/m5ui/roller.rst:36 -#: 66d4206f0546410c8a838c96010a3972 983ecbc60b2b46939afb847cee060f16 +#: 770a8f6844174761a1af181d339daad4 c39d722711004ea9b616532d31259a99 msgid "basic roller" msgstr "基础滚轮" -#: ../../en/m5ui/roller.rst:19 6308a7a1e1d441f5966889d01a52d786 +#: ../../en/m5ui/roller.rst:19 99660fe997ef42f99eda66cf20fe246a msgid "Open the |cores3_roller_basic_example.m5f2| project in UiFlow2." msgstr "在UiFlow2中打开 |cores3_roller_basic_example.m5f2| 项目。" #: ../../en/m5ui/roller.rst:21 ../../en/m5ui/roller.rst:38 -#: 65d26675a441467facc29d964699367e 843af05dee4647e6bb5eb51ded7208e8 +#: 5757d34231f74d51ab34464846936c3e d40f3ee96ee04e93b2a6d72ccd7989e6 msgid "" "This example demonstrates how to create a roller with multiple options " "and handle selection events." @@ -58,37 +58,37 @@ msgstr "此示例演示如何创建具有多个选项的滚轮并处理选择事 #: ../../en/m5ui/roller.rst:123 ../../en/m5ui/roller.rst:143 #: ../../en/m5ui/roller.rst:166 ../../en/m5ui/roller.rst:188 #: ../../en/m5ui/roller.rst:208 ../../en/m5ui/roller.rst:227 -#: ../../en/m5ui/roller.rst:245 ../../en/m5ui/roller.rst:263 +#: ../../en/m5ui/roller.rst:245 ../../en/m5ui/roller.rst:264 #: ../../en/m5ui/roller.rst:282 ../../en/m5ui/roller.rst:300 -#: ../../en/m5ui/roller.rst:318 ../../en/m5ui/roller.rst:339 +#: ../../en/m5ui/roller.rst:321 ../../en/m5ui/roller.rst:340 #: ../../en/m5ui/roller.rst:358 ../../en/m5ui/roller.rst:376 #: ../../en/m5ui/roller.rst:394 ../../en/m5ui/roller.rst:412 -#: ../../en/m5ui/roller.rst:430 00d1cc73792549d7b600de5480ac41ca -#: 150d4ae6f15c49b6b83c0b8442648b1e 1cd06a22831b4a17bdd08f67f85dc250 -#: 2fa6c1c63b554af1950db23cc26d5291 364f596f547a4dfb92643687faa98be9 -#: 445f3627fe39467aa2fbd0d4f4788118 460f565e83d64fd3af95ad0d2bf65e4a -#: 48e01fdf8cb9424b8776000f13e32703 54271383760e4fe5a0e93ce021e5c77b -#: 59fa6f1ad4b241eea2d30f9c08df804f 7317fb14e9064ea1b058c94c1f53ffd8 -#: 922fe8cf729644afa91b6bb15c8663c7 97b8d82de0e24543b1e2ecd86f9e6a97 -#: 9ea387244f284575972cee1c87cda4c4 c5474666859549a0afeee27e24d2b332 -#: cd318783e3134535929cebc498f2d677 d58dbdb189894d9d903bbc317416c06b -#: fa4f095e542c40ceb9165795a30ac610 m5ui.roller.M5Roller:15 -#: m5ui.roller.M5Roller.get_options:6 m5ui.roller.M5Roller.get_selected_str:5 -#: m5ui.roller.M5Roller.set_options:6 m5ui.roller.M5Roller.set_style_radius:7 -#: of +#: 0867fd9eee7346dcbc7f626ee92044a6 14588c8c963b40b5ba7485f19203eb54 +#: 32ca65d9bd014041adeacf81b2b1d9ad 359769533eda4537b33c4ed80fcf4a6a +#: 361b8e3e491c4935a5f69097b9e77c1a 51a20a576ce84d2b917fd1ae964c1e9f +#: 6faa33fce9644140b71a8ddd467b4ef1 72b8f3c55ace4b3e9d9ce52fd3df7048 +#: 73e094d768334de59e213565b22e57dc 7ecb31effa3549e688cd436459229768 +#: 99f1a46896894d63bdb9d6652f1d6111 a15234e360394b8682ef78ca0a611d15 +#: d74e833a251c4c9f94f2998c5c93b198 d765d8d9f461423f8638243946fea6f9 +#: db4b69c197b5453f88152e714ec6f36a dfe37399af404571acae545482a0c346 +#: e87d2c992a9e463c92d2f68d0e9f414d e9af57beea82464aab3f3c33bc75eea8 +#: f8c2702341c04bf8aac59fea783610db m5ui.roller.M5Roller:15 +#: m5ui.roller.M5Roller.get_option_count:6 m5ui.roller.M5Roller.get_options:6 +#: m5ui.roller.M5Roller.get_selected_str:5 m5ui.roller.M5Roller.set_options:6 +#: m5ui.roller.M5Roller.set_style_radius:7 of msgid "UiFlow2 Code Block:" msgstr "UiFlow2 代码块:" -#: ../../en/m5ui/roller.rst:25 9b96c48b3cfa47cfa0f6a924c2523626 +#: ../../en/m5ui/roller.rst:25 db3bb70b2c23413588b2efc47cf30205 msgid "|cores3_roller_basic_example.png|" msgstr "" -#: ../../en/refs/m5ui.roller.ref:27 4db04f298f0b48519b52d820e59415e1 +#: ../../en/refs/m5ui.roller.ref:27 6a7c1ca400f147b6998f598e0a062e13 msgid "cores3_roller_basic_example.png" msgstr "" #: ../../en/m5ui/roller.rst:27 ../../en/m5ui/roller.rst:46 -#: 2fedc330b1ad48d782a86cb96fe13c58 a1cf058dc9f640309f83c5aad07dbbaa +#: 89390b18314e4735b7400f6c9107717f 9cae2e8b417d40cca3f1784991412ee4 msgid "Example output:" msgstr "示例输出:" @@ -97,21 +97,22 @@ msgstr "示例输出:" #: ../../en/m5ui/roller.rst:103 ../../en/m5ui/roller.rst:121 #: ../../en/m5ui/roller.rst:141 ../../en/m5ui/roller.rst:164 #: ../../en/m5ui/roller.rst:186 ../../en/m5ui/roller.rst:206 -#: ../../en/m5ui/roller.rst:261 ../../en/m5ui/roller.rst:280 -#: ../../en/m5ui/roller.rst:298 ../../en/m5ui/roller.rst:316 -#: ../../en/m5ui/roller.rst:337 ../../en/m5ui/roller.rst:356 -#: ../../en/m5ui/roller.rst:374 ../../en/m5ui/roller.rst:410 -#: 1e233d688cd64517aaec5c9b73fda8cb 3cde07dc91bc41dd8f3ddf1405ef9d23 -#: 48c68a56f3fd4936af73dcb93254971b 4c20bcb3b3cf407e9d198b003c8800be -#: 5de223d03ec644a5a89ab38dc77c8769 6cc45b6993fd4dd89446b566acec6694 -#: 8b6291073ce64d46b7da19c9cd7452e4 9d5040c5e8bf4e5992dfe2f517fae646 -#: c80f955e3aac4f2d9b4ddc569569aaf8 e056e23663bd45c38fd12ed9975e0199 -#: fdbb31fc824d47c5b83b699cd46e8aa4 ff69cdc6e1764141968cb7aa644fc97d -#: m5ui.roller.M5Roller:17 m5ui.roller.M5Roller.set_style_radius:5 of +#: ../../en/m5ui/roller.rst:243 ../../en/m5ui/roller.rst:262 +#: ../../en/m5ui/roller.rst:280 ../../en/m5ui/roller.rst:298 +#: ../../en/m5ui/roller.rst:319 ../../en/m5ui/roller.rst:338 +#: ../../en/m5ui/roller.rst:356 ../../en/m5ui/roller.rst:392 +#: 0ae8797324cc44b4ae54a13a444deab7 15116e418c47412faf6d32914bbed853 +#: 31fe09dc9b2d4ad9b13f1bce9b37d472 45b579b01e434f3b9b8ca54d7dfeb356 +#: 5151fd006e264a24a2d4f82fc3f08373 67a92f26a416447caf15001a842625a1 +#: 7a79293d0ab444a7b726bfb58e7db665 7bad50beb32a4fd59c084bc6cc18680c +#: 8ce93b144c9a4f42ab2aa6355b6e5bac a9e9b20d76b240c6a53bf879667e641b +#: d8d04a9b2ed24a5da108a9a9d1c9be41 eb1ee431fa6e4a1cb0818674f5f3a8ee +#: f843be8003cb495191aab325f0e5f1f7 m5ui.roller.M5Roller:17 +#: m5ui.roller.M5Roller.set_style_radius:5 of msgid "None" msgstr "" -#: ../../en/m5ui/roller.rst:33 790fa83906e249449dbd10dce3817e6a +#: ../../en/m5ui/roller.rst:33 dd3befe2ed604e728d81f9ec81526c48 msgid "MicroPython Example" msgstr "MicroPython 示例" @@ -120,533 +121,535 @@ msgstr "MicroPython 示例" #: ../../en/m5ui/roller.rst:127 ../../en/m5ui/roller.rst:147 #: ../../en/m5ui/roller.rst:170 ../../en/m5ui/roller.rst:192 #: ../../en/m5ui/roller.rst:212 ../../en/m5ui/roller.rst:231 -#: ../../en/m5ui/roller.rst:249 ../../en/m5ui/roller.rst:267 +#: ../../en/m5ui/roller.rst:249 ../../en/m5ui/roller.rst:268 #: ../../en/m5ui/roller.rst:286 ../../en/m5ui/roller.rst:304 -#: ../../en/m5ui/roller.rst:322 ../../en/m5ui/roller.rst:343 +#: ../../en/m5ui/roller.rst:325 ../../en/m5ui/roller.rst:344 #: ../../en/m5ui/roller.rst:362 ../../en/m5ui/roller.rst:380 #: ../../en/m5ui/roller.rst:398 ../../en/m5ui/roller.rst:416 -#: ../../en/m5ui/roller.rst:434 122f63549d6349e2b982449666f03f0a -#: 15184d078fad4c3bad724d7a91d5f752 17f7381012b344faa61d7c2a6fadd34b -#: 18c11a799ece4b9793769af94e5bb841 2016aa51e15149ef9261ba1b12899b50 -#: 23b2d5f5ea3b487dba711db84419fa54 23bf4f192f0d4313a9f6252fb4759b12 -#: 399cd361aaea4837854bf0073645950f 4591144c269d4804915591cc44f97fab -#: 50349280d9f247c1a79b2234b4cdb08a 6953f30c075147e29787a2ec2fb34aa3 -#: 85cc4d43275449fbb3cfdc9f359c5382 92875f78278744aeb636a14c9a824841 -#: ae424a7b8b11424792c2f37a9b444a2b c94bed96f58b4177bd5ada70bee785c7 -#: e7fb84f0fb6f446d92028daa01a0a963 e9e5e7e603d740e3beba550a5a8d52e3 -#: ef644d5b7a744f69acf17d273c940637 m5ui.roller.M5Roller:19 -#: m5ui.roller.M5Roller.get_options:10 m5ui.roller.M5Roller.get_selected_str:9 -#: m5ui.roller.M5Roller.set_options:10 m5ui.roller.M5Roller.set_style_radius:11 -#: of +#: 005bd3084d164d42bef8478415840705 18ff6251a7b44baca856d903f11f0a81 +#: 2224d1158f6f43759af5067713033dae 351e9dd1fde348fbbfa53f5bd7b2d61e +#: 4bf85d5627024028bb223a39a53d14e3 7116b65f29504f128218c6c4b8117705 +#: 81ef85fe0fa14e6a833bffc7040b6e62 8d7efad0fb5241d8a04f5f6313e19cb9 +#: 96adf39897da4eb08086c2aac3151565 a10647e5474148b185b58967273881db +#: a7a6774eca6a4771bebe9331115eb57a c3c3318d668942d69513a0061c4cc737 +#: c79db2eb57f247c98eb65e87b794a914 ca33b8bee1f344baa2ca3d97272202ca +#: d68ff138e5b7499a9e0147bee48f55f7 dc84e9354f904b8d988b77a86c6ff3ab +#: e1c8d9b146184feea6d4314d033d2594 e9641d70fc174b66a5fdfbb52eeb84cb +#: efc07c856fc9482eb0d01727f6e24910 m5ui.roller.M5Roller:19 +#: m5ui.roller.M5Roller.get_option_count:10 m5ui.roller.M5Roller.get_options:10 +#: m5ui.roller.M5Roller.get_selected_str:9 m5ui.roller.M5Roller.set_options:10 +#: m5ui.roller.M5Roller.set_style_radius:11 of msgid "MicroPython Code Block:" msgstr "MicroPython 代码块:" -#: ../../en/m5ui/roller.rst:52 1abc33be93484fe695e55162474ec757 +#: ../../en/m5ui/roller.rst:52 ec2227c01a6b468f8828adb0bbc0736a msgid "**API**" msgstr "API参考" -#: e93f7a2dc7b3425a9247526e9cafc3a0 m5ui.roller.M5Roller:1 of +#: 9ce4bf7d09274d6ea76573b62bffb335 m5ui.roller.M5Roller:1 of msgid "Bases: :py:class:`~lvgl.roller`" msgstr "" -#: a2b59b2069494f249fdb0a4a60676bb4 m5ui.roller.M5Roller:1 of +#: 977cc14125624adf97b893a77f151c59 m5ui.roller.M5Roller:1 of msgid "Create a roller widget." msgstr "创建一个滚轮控件。" -#: ../../en/m5ui/roller.rst 60b9e97567f5479f8296bb04da4bc269 -#: 705a46e829fa4881a2c823d5be61fd68 79a3d47545944513afe48855a7da91e1 -#: 8b7845143c7c4f219de00ed86272b1cc 941ca056cee14b8085d2d361fcb6ceaa -#: b74b125d19ac4f49b1e346dee3c1170f de13638003aa4584a5105da66118616a -#: f3c77a6c4c914939a473af65c9f3aa90 f8d74ea25bc344aab29259cae8ab904a -#: fd1b258ae55f414bb9e10d9ba625e1fb m5ui.roller.M5Roller.set_options -#: m5ui.roller.M5Roller.set_style_radius of +#: ../../en/m5ui/roller.rst 083027081a3645b2b1b0092459f95adc +#: 2e696b0cd3d349fe911ad99f6be5f95d 35d8ca5dc87845cc9e6b2d38fa1ea55f +#: 497122c5c4a240f687872a8e788d22af 4f0ed224074841a7ba1629585029e67a +#: 769553d617d740d49e9f56ea21573964 81f2c87a0632497488e2415f66986ba1 +#: 9408ffc593514c70b4b345f815b3ec3e adbcae610d984604a1dbe0ad78aeb0fc +#: dea2f2de3e65472795ff37a915c535ae ff4393b9d2e549cdba5c2a58969d2a77 +#: m5ui.roller.M5Roller.set_options m5ui.roller.M5Roller.set_style_radius of msgid "Parameters" msgstr "" -#: b8c4a4137d084d1384fda80699b0b3a7 m5ui.roller.M5Roller:3 of +#: c42af21103df468c90a4006af95db9e6 m5ui.roller.M5Roller:3 of msgid "X position of the widget." msgstr "控件的X位置。" -#: 208ce15b2a9f4dbc81249fc7eccfbb01 m5ui.roller.M5Roller:4 of +#: 469cafe28f4f4e3dbc3b2259dbfb77cf m5ui.roller.M5Roller:4 of msgid "Y position of the widget." msgstr "控件的Y位置。" -#: 3eb9238dd9f746ce8d56694963f7dd39 m5ui.roller.M5Roller:5 of +#: 3823f92cd3f24762ac3624a0163058fe m5ui.roller.M5Roller:5 of msgid "Width of the widget." msgstr "控件的宽度。" -#: f1f01dab795240b1a17b72b6222c2f51 m5ui.roller.M5Roller:6 of +#: c7e9584df79a4e789bc4d580a3626726 m5ui.roller.M5Roller:6 of msgid "Height of the widget." msgstr "控件的高度。" -#: 8201d10c644747c9a22aad41021a10e5 d4fc84ab03c740dfb594927c1e0d29e9 +#: 0ae3df878f8b4b13b972e520419d10e6 673e4be3a63344c59d0c64ee0770d778 #: m5ui.roller.M5Roller:7 m5ui.roller.M5Roller.set_options:3 of msgid "List of options to display in the roller." msgstr "要在滚轮中显示的选项列表。" -#: 2eaf953632bc4496bcd124aebbcf41ce a6a28a169e384402bbab065faa44993b +#: 0b42ab001c5a41458998fb8e892f67b1 12f471bc4d2d4a4d820d30368691d51d #: m5ui.roller.M5Roller:8 m5ui.roller.M5Roller.set_options:4 of msgid "Roller mode (default is NORMAL)." msgstr "滚轮模式(默认为NORMAL)。" -#: a1b10c8232fe4c02a058a6efdf428538 m5ui.roller.M5Roller:9 of +#: 728766de86814fd89106dd2717ecbb5f m5ui.roller.M5Roller:9 of msgid "Index of the initially selected option." msgstr "初始选中选项的索引。" -#: 2bd7a18253a9431588a9d4150c825e1c m5ui.roller.M5Roller:10 of +#: c514b5c54cc7442fb8b72e7f1c2283c9 m5ui.roller.M5Roller:10 of msgid "Number of visible rows in the roller." msgstr "滚轮中可见行数。" -#: 54acc259d1624984837714d2a7bce3fa m5ui.roller.M5Roller:11 of +#: a7836b821542461297b8f02bcff1e25f m5ui.roller.M5Roller:11 of msgid "Font to use for the text in the roller." msgstr "滚轮中文本使用的字体。" -#: f6311311e8b34d5cb1d6cd43c584adca m5ui.roller.M5Roller:12 of +#: 89ace5eb8e034a089c75130053a6bda2 m5ui.roller.M5Roller:12 of msgid "Parent widget to attach this roller to (default is the active screen)." msgstr "要附加此滚轮的父控件(默认为活动屏幕)。" -#: ../../en/m5ui/roller.rst:62 ba6689c80ec346e0961ddbe4d40c5ad7 +#: ../../en/m5ui/roller.rst:62 f03904f1395c413ba13a7a1ef2d761de msgid "" "Set a flag on the object. If ``value`` is True, the flag is added; if " "False, the flag is removed." msgstr "为对象设置标志。如果 ``value`` 为True,则添加标志;如果为False,则移除标志。" -#: ../../en/m5ui/roller.rst:64 749a3b0aff7f4bb4b7d0fb44a9a2ae4a +#: ../../en/m5ui/roller.rst:64 a30e5b7cfef1458a81e8e75126f303c8 msgid "The flag to set." msgstr "要设置的标志。" -#: ../../en/m5ui/roller.rst:65 da878c64c811402b8258b71d5f7b8def +#: ../../en/m5ui/roller.rst:65 59094b47b41242a58cb9d5cf501ac06f msgid "If True, the flag is added; if False, the flag is removed." msgstr "如果为True,则添加标志;如果为False,则移除标志。" -#: ../../en/m5ui/roller.rst 1721e80c55fd48edaea8748da7c6cf9e -#: 2cfd2eab9ded49a7bcfd1809823b438d 36a6521260c149e9b930c69797826cdf -#: 3eb19df571db4804a8bd08435dfd82c5 57348c97a50d421db1e5b0334cc043ef -#: 59b902c2e401491595deeec51a41f045 674a89b323c847ebad35c599e1841a20 -#: 68bdd897b67141149d5a2fbb6107aa5e 8dc140070ef347c28c3673bc9b7afb56 -#: 915ac9452d954b95be2caa9609ca183a e010b23ae74a4c17bf291a4012774279 -#: e08b759b6b604f9fb82a4fa7a7fb23c1 m5ui.roller.M5Roller.get_options +#: ../../en/m5ui/roller.rst 095b6d880ae1481185ab4def08def99d +#: 1fc7ac9c11aa464e83fce975b2052cb4 3c8a98acad2a4645a7254f3c82f9abde +#: 483786279aa64edfafb4019747908bbf 6fdefe3cde6a4f9ab42c7e288ea7a725 +#: 7160fd0c40a849b6b7e99c485e6481d9 75f176d024dd4c4296930b71f0575414 +#: 7c657d686dd54e00b2b7e33e338d9483 859b879b7aab42e0b3a085f2565277ae +#: 9e2aa91322eb455580b8c9e219199a3a c9a1f92bd6b34be2a682737f0cdf83e6 +#: ec25709bf96941cbb59e89b1b3e7f864 fbd8181cc11248aa9b55bfd1e8a62a25 +#: m5ui.roller.M5Roller.get_option_count m5ui.roller.M5Roller.get_options #: m5ui.roller.M5Roller.get_selected_str m5ui.roller.M5Roller.set_style_radius #: of msgid "Returns" msgstr "" -#: ../../en/m5ui/roller.rst:70 ea50da8e345f4a61b3f6a7cecacd41fe +#: ../../en/m5ui/roller.rst:70 5f94bc038a9f44c08e95c9dc6aaf0428 msgid "|set_flag.png|" msgstr "" -#: ../../en/refs/m5ui.roller.ref:12 e8a7eaf3bad04eb8bd8d0d35afd1460e +#: ../../en/refs/m5ui.roller.ref:12 b010d665ea6e46318ffeff63c5c9c67d msgid "set_flag.png" msgstr "" -#: ../../en/m5ui/roller.rst:81 c9cf79181311405f940907261e015a6e +#: ../../en/m5ui/roller.rst:81 05e58c9aa030420fafcca02c300d2763 msgid "" "Toggle a flag on the object. If the flag is set, it is removed; if not " "set, it is added." msgstr "切换对象的标志。如果标志已设置,则将其移除;如果未设置,则将其添加。" -#: ../../en/m5ui/roller.rst:83 5dd46c570cd84f5cae7c0a50cbbf3c3b +#: ../../en/m5ui/roller.rst:83 f50ba43c8bbd4a3f8be35ebd2e7468a7 msgid "The flag to toggle." msgstr "要切换的标志。" -#: ../../en/m5ui/roller.rst:88 e8acb0f76df644589753609dfb2c8222 +#: ../../en/m5ui/roller.rst:88 c735abd1bde2467b8eb6278ae94ef6a6 msgid "|toggle_flag.png|" msgstr "" -#: ../../en/refs/m5ui.roller.ref:24 96ba18576580402b9ae7121eb7f5fc4a +#: ../../en/refs/m5ui.roller.ref:24 475d345baafd46fca26a7374bf13c174 msgid "toggle_flag.png" msgstr "" -#: ../../en/m5ui/roller.rst:99 6d023bd86f3848538291a3ed26a182c4 +#: ../../en/m5ui/roller.rst:99 ab5b7e6b78c84a2b9d7dc1e8ac35d8c8 msgid "" "Set the state of the roller. If ``value`` is True, the state is set; if " "False, the state is unset." msgstr "设置滚轮的状态。如果 ``value`` 为True,则设置状态;如果为False,则取消设置状态。" -#: ../../en/m5ui/roller.rst:101 37bc3bc31bc1443a9d28601283480b01 +#: ../../en/m5ui/roller.rst:101 28ac4688609649918faf3d62cf397ad6 msgid "The state to set." msgstr "要设置的状态。" -#: ../../en/m5ui/roller.rst:102 22570de35e1f4ed9b3c1b80b51ae0ebb +#: ../../en/m5ui/roller.rst:102 d94f49dc7e394d1ea9629a3f49b09703 msgid "If True, the state is set; if False, the state is unset." msgstr "如果为True,则设置状态;如果为False,则取消设置状态。" -#: ../../en/m5ui/roller.rst:107 2968bb55e5944dbca8634c2289ce9da3 +#: ../../en/m5ui/roller.rst:107 75fdb9530b64401d89f7c1078b77012c msgid "|set_state.png|" msgstr "" -#: ../../en/refs/m5ui.roller.ref:17 33adda7784464040b51104b535d3867a +#: ../../en/refs/m5ui.roller.ref:17 ed67b43339d04a1581523a9b43610cb3 msgid "set_state.png" msgstr "" -#: ../../en/m5ui/roller.rst:118 95e75435b6da4576abd5f1535d156587 +#: ../../en/m5ui/roller.rst:118 8a3a34d3624344c0b04c691feaa0db3c msgid "" "Toggle the state of the roller. If the state is set, it is unset; if not " "set, it is set." msgstr "切换滚轮的状态。如果状态已设置,则取消设置;如果未设置,则将其设置。" -#: ../../en/m5ui/roller.rst:120 913c2b56f48c499295ca2aa1665123fc +#: ../../en/m5ui/roller.rst:120 6eebd21786ef44b7a7bcdaa8c4dd5100 msgid "The state to toggle." msgstr "要切换的状态。" -#: ../../en/m5ui/roller.rst:125 29c00c9e797745808381b661f8f47ff4 +#: ../../en/m5ui/roller.rst:125 01cba5f71f7840e3a917f63d56582482 msgid "|toggle_state.png|" msgstr "" -#: ../../en/refs/m5ui.roller.ref:25 81a7f05f6f1a4ad481cad2dd914d9ad6 +#: ../../en/refs/m5ui.roller.ref:25 38492f0e84224d22968b4a2757aedf1b msgid "toggle_state.png" msgstr "" -#: ../../en/m5ui/roller.rst:136 c05d566c009d43a8bdc902f5bc85f9ef +#: ../../en/m5ui/roller.rst:136 2f011186308444ce950a445ce926b17f msgid "" "Add an event callback to the roller. The callback will be called when the" " specified event occurs." msgstr "为滚轮添加事件回调。当指定事件发生时将调用回调函数。" -#: ../../en/m5ui/roller.rst:138 c2c6bf8bcd2a45b991102441ca07b410 +#: ../../en/m5ui/roller.rst:138 323e3388eef44da491f33ff287694fbb msgid "The callback function to call." msgstr "要调用的回调函数。" -#: ../../en/m5ui/roller.rst:139 2e5955501b6f4700a02a470a44fa8dee +#: ../../en/m5ui/roller.rst:139 82a1dd9a86174028bb639b6c83231c3c msgid "The event to listen for." msgstr "要监听的事件。" -#: ../../en/m5ui/roller.rst:140 bb7be24006fc43569e57422b893f454e +#: ../../en/m5ui/roller.rst:140 d945a9e09be24606b6b71bd38085cf62 msgid "Optional user data to pass to the callback." msgstr "传递给回调函数的可选用户数据。" -#: ../../en/m5ui/roller.rst:145 9c2d88e3f9ad4fccab9d86adbdd5f9e2 +#: ../../en/m5ui/roller.rst:145 b1755d8476044ee588ff07523bb67206 msgid "|event.png|" msgstr "" -#: ../../en/refs/m5ui.roller.ref:2 1946019a72ad43c4bd1079be50747e13 +#: ../../en/refs/m5ui.roller.ref:2 d4823fd32ea84cafa3bf604fbd8f527e msgid "event.png" msgstr "" -#: ../../en/m5ui/roller.rst:159 885368c6163a4dc8bc1b4ab1aa88eba1 +#: ../../en/m5ui/roller.rst:159 0cd123ae66a0469dae299d3b7fc1bc89 msgid "Set the background color of the roller." msgstr "设置滚轮的背景颜色。" #: ../../en/m5ui/roller.rst:161 ../../en/m5ui/roller.rst:183 -#: 8c5cf9f5d8e74299bc187622d336b904 f070b8f19a784584b86ac98539e2a43b +#: 41713e2e228a4b66b40394a2e615525e 4a567dc557584de88f0934b8bc53858a msgid "The color to set." msgstr "要设置的颜色。" -#: ../../en/m5ui/roller.rst:162 655c136d4b184c40b2e09e85c7a99016 +#: ../../en/m5ui/roller.rst:162 06a6df1fcbc441e8911b8b3a8aac264e msgid "The opacity of the color." msgstr "颜色的不透明度。" #: ../../en/m5ui/roller.rst:163 ../../en/m5ui/roller.rst:185 -#: ../../en/m5ui/roller.rst:205 2a5f8351301643bfa5f9ee0c7d0c3b71 -#: 756ffa83fd014b0ebc30e39798b27acf a6f470c8a7da4d1ba821dc259ed67f68 +#: ../../en/m5ui/roller.rst:205 0af3eb450b9b4042af43dd0f10bcad8c +#: 14d38eafdc4c43a5a56d4cc24808b4ed a42043d54537431a8c1c0cb6729226a9 msgid "The part of the object to apply the style to (e.g., lv.PART.MAIN)." msgstr "要应用样式的对象部分(例如:lv.PART.MAIN)。" -#: ../../en/m5ui/roller.rst:168 75389e48a43847ffb63ea63fed49cb36 +#: ../../en/m5ui/roller.rst:168 390494116b264223a14d34a028ce215c msgid "|set_bg_color.png|" msgstr "" -#: ../../en/refs/m5ui.roller.ref:9 4d44a84b702f409f87c0a42fdf7761f3 +#: ../../en/refs/m5ui.roller.ref:9 ed134e17ba164b97bc151439de0e2123 msgid "set_bg_color.png" msgstr "" -#: ../../en/m5ui/roller.rst:181 d8f10aa78dec4b41a938d161bd830eff +#: ../../en/m5ui/roller.rst:181 b0a56c7e88764e70b65b95d0e91221ef msgid "Set the border color of the roller." msgstr "设置滚轮的边框颜色。" -#: ../../en/m5ui/roller.rst:184 e1df2129867d4c578c6c664e163d59e4 +#: ../../en/m5ui/roller.rst:184 131b2774f26042199e50d82b82b8b2b3 msgid "" "The opacity of the color. The value should be between 0 (transparent) and" " 255 (opaque)." msgstr "颜色的不透明度。值应在0(透明)到255(不透明)之间。" -#: ../../en/m5ui/roller.rst:190 dffa8a087d9743a69df4bf801079c5e8 +#: ../../en/m5ui/roller.rst:190 dc11bcd6154d4fe39541ba7b3c44c555 msgid "|set_border_color.png|" msgstr "" -#: ../../en/refs/m5ui.roller.ref:10 f219b370aae24d879a0a21eb720e6195 +#: ../../en/refs/m5ui.roller.ref:10 4bf73af88a49456fae30d99f37623252 msgid "set_border_color.png" msgstr "" -#: ../../en/m5ui/roller.rst:202 8055958c699c4a49ac64529af17dcd2d +#: ../../en/m5ui/roller.rst:202 593350ee9f3149958d942e1a3b776087 msgid "Set the border width of the roller." msgstr "设置滚轮的边框宽度。" -#: ../../en/m5ui/roller.rst:204 bc8a491b8de74a69ac76df46251028e4 +#: ../../en/m5ui/roller.rst:204 40ba18751562404587aa3b276022894b msgid "The width to set." msgstr "要设置的宽度。" -#: ../../en/m5ui/roller.rst:210 de8807ff1fe6438a814b06a283ee85c7 +#: ../../en/m5ui/roller.rst:210 3cb3e9ca3beb4fce9c284b1ca6ed0faf msgid "|set_style_border_width.png|" msgstr "" -#: ../../en/refs/m5ui.roller.ref:18 46ee39f78df94e148ff3dd877a59758f +#: ../../en/refs/m5ui.roller.ref:18 71d26ca19aa948218a9143396f7f5d82 msgid "set_style_border_width.png" msgstr "" -#: ../../en/m5ui/roller.rst:222 4c5fc73d3962444fb52e52eee8ef6da6 -msgid "Get the total number of options in the roller." -msgstr "获取滚轮中选项的总数。" +#: ../../en/m5ui/roller.rst:222 345f51cac85a4e1db0edf45afa7e436e +msgid "Get the index of the currently selected option." +msgstr "获取当前选中选项的索引。" -#: ../../en/m5ui/roller.rst:224 cd413ae846b246e1a63de5666281c3e6 -msgid "The number of options." -msgstr "选项的数量。" +#: ../../en/m5ui/roller.rst:224 c11e4946dc1b4057afa63863bb95d216 +msgid "The index of the selected option." +msgstr "选中选项的索引。" -#: ../../en/m5ui/roller.rst 3c71709ae0674c27bb94db8b1e0a4a9d -#: 594d2693cb304632863c89145dfea6a4 81467515d3be4d4eba54989e4836c8e3 -#: 9e1e23ea38f34856a7ae711de65ccd0c m5ui.roller.M5Roller.get_options +#: ../../en/m5ui/roller.rst 297241525dea4017bf165b9c5d5d30e5 +#: aba6394d60304f1c9d73eff8f192087c b1000ed914e549b2b33201125492c46e +#: bd817d4dd3fa42d6a277387ab37234b9 dce91b5571e642b7949f1741cb043374 +#: m5ui.roller.M5Roller.get_option_count m5ui.roller.M5Roller.get_options #: m5ui.roller.M5Roller.get_selected_str m5ui.roller.M5Roller.set_style_radius #: of msgid "Return type" msgstr "" -#: ../../en/m5ui/roller.rst:229 5729fe3f6fde4b92ab7c4a0e183c4f61 -msgid "|get_option_count.png|" -msgstr "" - -#: ../../en/refs/m5ui.roller.ref:4 dae8c20852e64e5b843eadb1ebf16743 -msgid "get_option_count.png" -msgstr "" - -#: ../../en/m5ui/roller.rst:240 a4986c031a5a4bdfbf1833e852b33577 -msgid "Get the index of the currently selected option." -msgstr "获取当前选中选项的索引。" - -#: ../../en/m5ui/roller.rst:242 21c2d3cfd0d74eb8b090e9aae9dcacc7 -msgid "The index of the selected option." -msgstr "选中选项的索引。" - -#: ../../en/m5ui/roller.rst:247 5cc5b2ebb048492fa48f117a4edac327 +#: ../../en/m5ui/roller.rst:229 49dda85905a54689bf28c6ab637f6498 msgid "|get_selected.png|" msgstr "" -#: ../../en/refs/m5ui.roller.ref:6 2238fad457534e35901a7f7683268258 +#: ../../en/refs/m5ui.roller.ref:6 1dfb9324e60043b2b34d6ad63c6c4938 msgid "get_selected.png" msgstr "" -#: ../../en/m5ui/roller.rst:258 351b7914a1be4d45ad1b88752e4d13a8 +#: ../../en/m5ui/roller.rst:240 c9bb58a1ca2749f0aed4fac3cd2db419 msgid "Set the number of visible rows in the roller." msgstr "设置滚轮中可见行数。" -#: ../../en/m5ui/roller.rst:260 93c125fa336f419d9c2570d41e6bd7b3 +#: ../../en/m5ui/roller.rst:242 9ebb3ea0ba864bfdb052e6926954e252 msgid "The number of visible rows." msgstr "可见行数。" -#: ../../en/m5ui/roller.rst:265 55b7364719d54745afb8725823d950d4 +#: ../../en/m5ui/roller.rst:247 bc6518f73ec940438a85838290b1f559 msgid "|set_visible_row_count.png|" msgstr "" -#: ../../en/refs/m5ui.roller.ref:20 41a61cde0bc04b07847de927350ea685 +#: ../../en/refs/m5ui.roller.ref:20 a175329fd8884d50956c6d4cfd8c84e1 msgid "set_visible_row_count.png" msgstr "" -#: ../../en/m5ui/roller.rst:276 f8a8120886574013a6c0944fb7421200 +#: ../../en/m5ui/roller.rst:258 5e107276ad9142419b7165675da40c3c msgid "Set the position of the roller." msgstr "设置滚轮的位置。" -#: ../../en/m5ui/roller.rst:278 ../../en/m5ui/roller.rst:297 -#: d0bc1ae6ebc3440e849c5202cc1b3cc1 +#: ../../en/m5ui/roller.rst:260 ../../en/m5ui/roller.rst:279 +#: 1a631b56a1e94caa94337d398f478313 msgid "The x-coordinate of the roller." msgstr "滚轮的x坐标。" -#: ../../en/m5ui/roller.rst:279 ../../en/m5ui/roller.rst:315 -#: c08c7b47237f43999c4c3736b81f227e +#: ../../en/m5ui/roller.rst:261 ../../en/m5ui/roller.rst:297 +#: 92d7d334930044dbbce79c58d84e5e96 msgid "The y-coordinate of the roller." msgstr "滚轮的y坐标。" -#: ../../en/m5ui/roller.rst:284 002f5a3723394ce2904e4fc068793f4e +#: ../../en/m5ui/roller.rst:266 b5a75351fe604752b3285e7a4e17e3d4 msgid "|set_pos.png|" msgstr "" -#: ../../en/refs/m5ui.roller.ref:15 0de4992913dc4ec89b06a0a5fc5984ef +#: ../../en/refs/m5ui.roller.ref:15 60c9dfe2098c4d12b418422b6fb78e57 msgid "set_pos.png" msgstr "" -#: ../../en/m5ui/roller.rst:295 106873ef2fe94b3c861b08713dd72b1c +#: ../../en/m5ui/roller.rst:277 ad757872a70d404ea166f3e4f552c683 msgid "Set the x-coordinate of the roller." msgstr "设置滚轮的x坐标。" -#: ../../en/m5ui/roller.rst:302 bb252726e9bb4c729cd86096b7641400 +#: ../../en/m5ui/roller.rst:284 bc169154654d4d379c42bfe0e0500b04 msgid "|set_x.png|" msgstr "" -#: ../../en/refs/m5ui.roller.ref:22 ef79f30f3e454eeca83e61f9b4c6e35f +#: ../../en/refs/m5ui.roller.ref:22 68cd474e68cd40aaafd10dab9bc1053a msgid "set_x.png" msgstr "" -#: ../../en/m5ui/roller.rst:313 d1fb85bec44d4275801af365170427b6 +#: ../../en/m5ui/roller.rst:295 0e424b368a6945fa88f31da4b59d9037 msgid "Set the y-coordinate of the roller." msgstr "设置滚轮的y坐标。" -#: ../../en/m5ui/roller.rst:320 15806ede2556412195559af3d1d3e33e +#: ../../en/m5ui/roller.rst:302 e0bed5fc78764aea86bdcab4d925c7d1 msgid "|set_y.png|" msgstr "" -#: ../../en/refs/m5ui.roller.ref:23 242445e2c73542c2878b001984321a4d +#: ../../en/refs/m5ui.roller.ref:23 3f7931e7e5634dc2a7ad4a5277086347 msgid "set_y.png" msgstr "" -#: ../../en/m5ui/roller.rst:331 7ecb3e7fb9c94c7b95726a1c6a57d1b7 +#: ../../en/m5ui/roller.rst:313 51114f321c8b4190a452c46b01e3d1a5 msgid "Align the roller to another object." msgstr "将滚轮对齐到另一个对象。" -#: ../../en/m5ui/roller.rst:333 3f84b9ceb957474caa708e6165901a11 +#: ../../en/m5ui/roller.rst:315 264ddc309df14ed0938b2d45fc1aede4 msgid "The object to align to." msgstr "要对齐的对象。" -#: ../../en/m5ui/roller.rst:334 7f905cd94d7549b39d03db355510d2c4 +#: ../../en/m5ui/roller.rst:316 3d7c52c3593041ddb275d50ec1308a5e msgid "The alignment type." msgstr "对齐类型。" -#: ../../en/m5ui/roller.rst:335 f6c42898c6944e6f8438725180692049 +#: ../../en/m5ui/roller.rst:317 c642bfa66d8040c08f4d1e5cf9201e6f msgid "The x-offset from the aligned object." msgstr "与对齐对象的x偏移量。" -#: ../../en/m5ui/roller.rst:336 337e4465b8684ddb85f3f3f5543e4c98 +#: ../../en/m5ui/roller.rst:318 d8057a78eae74453a86eafa8d2610d49 msgid "The y-offset from the aligned object." msgstr "与对齐对象的y偏移量。" -#: ../../en/m5ui/roller.rst:341 3bffd6a71d6748e38c99762a0837b941 +#: ../../en/m5ui/roller.rst:323 d25cca412f614bcb997ede436c105bf7 msgid "|align_to.png|" msgstr "" -#: ../../en/refs/m5ui.roller.ref:1 c24a368034b04a68b66da367628ec6cf +#: ../../en/refs/m5ui.roller.ref:1 6ef84661c5cd4e0ea276902fd586a890 msgid "align_to.png" msgstr "" -#: ../../en/m5ui/roller.rst:352 7ea6bd3ff39b4428b5dc71cb42d88ea9 +#: ../../en/m5ui/roller.rst:334 d46e63d768cc468ab12348cdc2bd9133 msgid "Set the size of the roller." msgstr "设置滚轮的尺寸。" -#: ../../en/m5ui/roller.rst:354 ../../en/m5ui/roller.rst:373 -#: ../../en/m5ui/roller.rst:391 a83c72a7a6ce478ab4e080c7465c0c48 +#: ../../en/m5ui/roller.rst:336 ../../en/m5ui/roller.rst:355 +#: ../../en/m5ui/roller.rst:373 b77a6217f25d4c7ba91ebfe7533a0abb msgid "The width of the roller." msgstr "滚轮的宽度。" -#: ../../en/m5ui/roller.rst:355 ../../en/m5ui/roller.rst:409 -#: ../../en/m5ui/roller.rst:427 f520cb0c374845acab6b5624959afcb0 +#: ../../en/m5ui/roller.rst:337 ../../en/m5ui/roller.rst:391 +#: ../../en/m5ui/roller.rst:409 d77afb2a3edd48d0bcb3a556eaad052f msgid "The height of the roller." msgstr "滚轮的高度。" -#: ../../en/m5ui/roller.rst:360 84a5d84fce164012a259950a2e118d82 +#: ../../en/m5ui/roller.rst:342 68144ec2d7534a4eb0d8385065a5a205 msgid "|set_size.png|" msgstr "" -#: ../../en/refs/m5ui.roller.ref:16 1c2c16bb2a4946969805e06d4b5be3cd +#: ../../en/refs/m5ui.roller.ref:16 e6d8524a2980436d9fbee88198fc04e9 msgid "set_size.png" msgstr "" -#: ../../en/m5ui/roller.rst:371 6cd5324f9d064d0390cbe0626a82bcad +#: ../../en/m5ui/roller.rst:353 db5d3de136314152a463ff0e6d4c154f msgid "Set the width of the roller." msgstr "设置滚轮的宽度。" -#: ../../en/m5ui/roller.rst:378 fd00c50bb4894d5c93c1098007f7c9dc +#: ../../en/m5ui/roller.rst:360 db0985674e5346ae8d382aae7fa4b0ed msgid "|set_width.png|" msgstr "" -#: ../../en/refs/m5ui.roller.ref:21 63efe9d60480417285e37d9a5685b468 +#: ../../en/refs/m5ui.roller.ref:21 cbd07be36a704eb0a3fadd7245caf9fa msgid "set_width.png" msgstr "" -#: ../../en/m5ui/roller.rst:389 8c4030ce4105480a9bd012ffa13089c0 +#: ../../en/m5ui/roller.rst:371 c78ddd3223d54c51b99e99b042286246 msgid "Get the width of the roller." msgstr "获取滚轮的宽度。" -#: ../../en/m5ui/roller.rst:396 2dccff5831ec45de886a4731272d2d0b +#: ../../en/m5ui/roller.rst:378 77fee9036ebd4296a9c2af06103c1782 msgid "|get_width.png|" msgstr "" -#: ../../en/refs/m5ui.roller.ref:8 b76dccc96d4543ec875e5fe3ec39d2cb +#: ../../en/refs/m5ui.roller.ref:8 6661246d75b045aeb86a19950aa5d5bd msgid "get_width.png" msgstr "" -#: ../../en/m5ui/roller.rst:407 d48d966d24ae4de1a48e622c0e676af9 +#: ../../en/m5ui/roller.rst:389 13d82cacf688411ba37772ad01171aa6 msgid "Set the height of the roller." msgstr "设置滚轮的高度。" -#: ../../en/m5ui/roller.rst:414 02cc628f7b5445c39516ffc381bbed0f +#: ../../en/m5ui/roller.rst:396 6d406ca412814d9a9effcf2521582099 msgid "|set_height.png|" msgstr "" -#: ../../en/refs/m5ui.roller.ref:13 7cbd0908c5ff467683bad1f6b02159b7 +#: ../../en/refs/m5ui.roller.ref:13 117085ba4da640e185fa21e828dde744 msgid "set_height.png" msgstr "" -#: ../../en/m5ui/roller.rst:425 060bea8edfc04473badd0322d00cba3e +#: ../../en/m5ui/roller.rst:407 32cfacfeda014a8883749c639b9deb80 msgid "Get the height of the roller." msgstr "获取滚轮的高度。" -#: ../../en/m5ui/roller.rst:432 1f0fad375f75427a86c71598906506a5 +#: ../../en/m5ui/roller.rst:414 c857183bac924b96b87e439dfc9b48a6 msgid "|get_height.png|" msgstr "" -#: ../../en/refs/m5ui.roller.ref:3 2d92694d0a22402c8907b4cd682aacd0 +#: ../../en/refs/m5ui.roller.ref:3 fd5d726d182a4b988dc067072a39966b msgid "get_height.png" msgstr "" -#: f8a3c23138894109bd1db6f07386ed85 m5ui.roller.M5Roller.set_options:1 of +#: 4ecfa4f884dc4d5384801f6e152836f4 m5ui.roller.M5Roller.set_options:1 of msgid "Set the options for the roller." msgstr "设置滚轮的选项。" -#: 9a4e9c3115d4461a90340d7ff22dcb99 m5ui.roller.M5Roller.set_options:8 of +#: d24fda6c148e421db92fc6a1c6febd27 m5ui.roller.M5Roller.set_options:8 of msgid "|set_options.png|" msgstr "" -#: ../../en/refs/m5ui.roller.ref:14 217d6c3e60e74f088fa3fe2e399b81bf +#: ../../en/refs/m5ui.roller.ref:14 7ffbcee1715d4168916eda4be89827aa msgid "set_options.png" msgstr "" -#: 0c78f21c6ca34956a2cfebae7460ce80 m5ui.roller.M5Roller.get_options:1 of +#: f848cfa043214f87877d7ae19934b155 m5ui.roller.M5Roller.get_options:1 of msgid "Get the list of options in the dropdown." msgstr "获取下拉菜单中的选项列表。" -#: 568828dc38034a838d05b8350be89fa7 m5ui.roller.M5Roller.get_options:3 of +#: 5e8e27844eeb45a4b446b850e114b819 m5ui.roller.M5Roller.get_options:3 of msgid "The list of options." msgstr "选项列表。" -#: c9d886d11858439d8407c3012ca4e264 m5ui.roller.M5Roller.get_options:8 of +#: 9c54bbfe63004af6a52c6ef64bed9b9a m5ui.roller.M5Roller.get_options:8 of msgid "|get_options.png|" msgstr "" -#: ../../en/refs/m5ui.roller.ref:5 fc47b0fe8af44ba7b5ffd3d9c27c0c86 +#: ../../en/refs/m5ui.roller.ref:5 ef03aba5a9e046f5878a41ed3077380f msgid "get_options.png" msgstr "" -#: b812bc4c90124088ac18e51938c50696 m5ui.roller.M5Roller.get_selected_str:1 of +#: 6d8be90a39a74ca9999a1c0975f80920 m5ui.roller.M5Roller.get_option_count:1 of +msgid "Get the number of options in the roller." +msgstr "获取滚轮中选项的总数。" + +#: 33acdda6eef443509b8dc28caf18e47a m5ui.roller.M5Roller.get_option_count:3 of +msgid "The number of options." +msgstr "选项的数量。" + +#: 4b118c3f574a4976a3b235ca03288778 m5ui.roller.M5Roller.get_option_count:8 of +msgid "|get_option_count.png|" +msgstr "" + +#: ../../en/refs/m5ui.roller.ref:4 d88609c23bee4fec8c3dab0551e9ce79 +msgid "get_option_count.png" +msgstr "" + +#: 7606868789c7458c89722277f4008b32 m5ui.roller.M5Roller.get_selected_str:1 of msgid "Get the currently selected option as a string." msgstr "获取当前选中选项的文本。" -#: 82adc507d46645b3bf6bb213b1082e40 m5ui.roller.M5Roller.get_selected_str:3 of +#: 7333380fd8a44cd5885a4105b6f443e4 m5ui.roller.M5Roller.get_selected_str:3 of msgid "The selected option as a string." msgstr "选中选项的文本。" -#: 288d04a2a7354ac08af9660ccbeaf0fe m5ui.roller.M5Roller.get_selected_str:7 of +#: ff946e05e5744c4f8a1c64f945487259 m5ui.roller.M5Roller.get_selected_str:7 of msgid "|get_selected_str.png|" msgstr "" -#: ../../en/refs/m5ui.roller.ref:7 be1564e559a5408eaf6a058cc02c8d7f +#: ../../en/refs/m5ui.roller.ref:7 95f1f9eb86594e5b89b8441cd635c6b8 msgid "get_selected_str.png" msgstr "" -#: 737042603d1c46118c0b7b63a9bab803 m5ui.roller.M5Roller.set_style_radius:1 of +#: 234d0763545d481b987f1eb4ce3dd409 m5ui.roller.M5Roller.set_style_radius:1 of msgid "Set the corner radius of the slider components." msgstr "设置滑块组件的圆角半径。" -#: ab485dc557d64e2baf1b4383eabf9a27 m5ui.roller.M5Roller.set_style_radius:3 of +#: 173aca5345f24b048352f6d7c13084a6 m5ui.roller.M5Roller.set_style_radius:3 of msgid "The radius to set." msgstr "要设置的半径。" -#: 653fa0736d2a479db72b5db36d213a55 m5ui.roller.M5Roller.set_style_radius:4 of +#: 023cfc855d1b4f21bb7f25d795b1cdf4 m5ui.roller.M5Roller.set_style_radius:4 of msgid "" "The part of the object to apply the style to (e.g., lv.PART.MAIN, " "lv.PART.SELECTED)." msgstr "要应用样式的对象部分(例如,lv.PART.MAIN、lv.PART.SELECTED)。" -#: 7d98abf0d01c4e1ea4272ce5ebe0cc65 m5ui.roller.M5Roller.set_style_radius:9 of +#: 0eca6d08c4f749b6a6867776a78c8842 m5ui.roller.M5Roller.set_style_radius:9 of msgid "|set_style_radius.png|" msgstr "" -#: ../../en/refs/m5ui.roller.ref:19 a2e56529da964f5aa08ec959f9ed8b90 +#: ../../en/refs/m5ui.roller.ref:19 1031f158b5dc431aa33319950d756849 msgid "set_style_radius.png" msgstr "" diff --git a/m5stack/libs/m5ui/buttonmatrix.py b/m5stack/libs/m5ui/buttonmatrix.py index a77d63ab..f43aa6fe 100644 --- a/m5stack/libs/m5ui/buttonmatrix.py +++ b/m5stack/libs/m5ui/buttonmatrix.py @@ -120,6 +120,25 @@ def get_textarea(self): """ return self.textarea + def get_selected_button(self) -> int: + """Get the ID of the currently selected button. + + :return: The ID of the currently selected button, or -1 if none is selected. + :rtype: int + + UiFlow2 Code Block: + + |get_selected_button.png| + + MicroPython Code Block: + + .. code-block:: python + + selected_button = buttonmatrix_0.get_selected_button() + """ + index = super().get_selected_button() + return -1 if index == 65535 else index + def __getattr__(self, name): if hasattr(M5Base, name): method = getattr(M5Base, name) diff --git a/m5stack/libs/m5ui/roller.py b/m5stack/libs/m5ui/roller.py index 3af8eb44..88f501ed 100644 --- a/m5stack/libs/m5ui/roller.py +++ b/m5stack/libs/m5ui/roller.py @@ -104,6 +104,24 @@ def get_options(self) -> list: """ return self.options + def get_option_count(self) -> int: + """Get the number of options in the roller. + + :return: The number of options. + :rtype: int + + UiFlow2 Code Block: + + |get_option_count.png| + + MicroPython Code Block: + + .. code-block:: python + + option_count = roller_0.get_option_count() + """ + return len(self.options) + def get_selected_str(self) -> str: """Get the currently selected option as a string. From 127fcd63f9a57c2da5fd157bc1140f91cb6d5401 Mon Sep 17 00:00:00 2001 From: lbuque <1102390310@qq.com> Date: Fri, 26 Sep 2025 17:32:48 +0800 Subject: [PATCH 276/322] cmodules/m5utils: Add binding for FreeRTOS soft timer. Signed-off-by: lbuque <1102390310@qq.com> --- m5stack/cmodules/m5utils/m5utils.c | 2 + m5stack/cmodules/m5utils/m5utils.cmake | 2 + m5stack/cmodules/m5utils/timer.c | 143 +++++++++++++++++++++++++ 3 files changed, 147 insertions(+) create mode 100644 m5stack/cmodules/m5utils/timer.c diff --git a/m5stack/cmodules/m5utils/m5utils.c b/m5stack/cmodules/m5utils/m5utils.c index 94e53b54..acf1755e 100644 --- a/m5stack/cmodules/m5utils/m5utils.c +++ b/m5stack/cmodules/m5utils/m5utils.c @@ -26,11 +26,13 @@ static mp_obj_t m5_utils_remap(size_t n_args, const mp_obj_t *args) { } static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(m5_utils_remap_obj, 5, 5, m5_utils_remap); +extern const mp_obj_type_t mp_m5utils_timer_type; static const mp_rom_map_elem_t m5utils_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_m5utils) }, { MP_ROM_QSTR(MP_QSTR_remap), MP_ROM_PTR(&m5_utils_remap_obj) }, + { MP_ROM_QSTR(MP_QSTR_Timer), MP_ROM_PTR(&mp_m5utils_timer_type) }, }; static MP_DEFINE_CONST_DICT(m5utils_module_globals, m5utils_module_globals_table); diff --git a/m5stack/cmodules/m5utils/m5utils.cmake b/m5stack/cmodules/m5utils/m5utils.cmake index 73918fff..3309d051 100644 --- a/m5stack/cmodules/m5utils/m5utils.cmake +++ b/m5stack/cmodules/m5utils/m5utils.cmake @@ -6,6 +6,7 @@ add_library(usermod_M5UTILS INTERFACE) target_sources(usermod_M5UTILS INTERFACE ${CMAKE_CURRENT_LIST_DIR}/m5utils.c + ${CMAKE_CURRENT_LIST_DIR}/timer.c ) target_include_directories(usermod_M5UTILS INTERFACE @@ -16,6 +17,7 @@ target_link_libraries(usermod INTERFACE usermod_M5UTILS) set_source_files_properties( ${CMAKE_CURRENT_LIST_DIR}/m5utils.c + ${CMAKE_CURRENT_LIST_DIR}/timer.c PROPERTIES COMPILE_FLAGS "-Wno-discarded-qualifiers -Wno-implicit-int" ) diff --git a/m5stack/cmodules/m5utils/timer.c b/m5stack/cmodules/m5utils/timer.c new file mode 100644 index 00000000..4d97ebfe --- /dev/null +++ b/m5stack/cmodules/m5utils/timer.c @@ -0,0 +1,143 @@ +/* +* SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +* +* SPDX-License-Identifier: MIT +*/ +#include +#include +#include + +#include "freertos/FreeRTOS.h" +#include "freertos/timers.h" + +typedef struct _mp_m5utils_timer_t { + mp_obj_base_t base; + TimerHandle_t timer_handler; + mp_uint_t period_ms; + mp_uint_t repeat; + mp_obj_t callback; +} mp_m5utils_timer_t; + +extern const mp_obj_type_t mp_m5utils_timer_type; + +static void vTimerCallback(TimerHandle_t timer) { + mp_m5utils_timer_t *self = pvTimerGetTimerID(timer); + if (self->callback != mp_const_none) { + mp_sched_schedule(self->callback, self); + } +} + +static mp_obj_t mp_m5utils_timer_init_helper(mp_m5utils_timer_t *self, mp_uint_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { + ARG_mode, + ARG_period, + ARG_callback, + }; + static const mp_arg_t allowed_args[] = { + /* *FORMAT-OFF* */ + { MP_QSTR_mode, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 1} }, + { MP_QSTR_period, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0xffffffff} }, + { MP_QSTR_callback, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} }, + /* *FORMAT-ON* */ + }; + + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + self->repeat = args[ARG_mode].u_int; + self->period_ms = args[ARG_period].u_int; + self->callback = args[ARG_callback].u_obj; + + return mp_const_none; +} + + +static void mp_m5utils_timer_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { + mp_m5utils_timer_t *self = MP_OBJ_TO_PTR(self_in); + qstr mode = self->repeat ? MP_QSTR_PERIODIC : MP_QSTR_ONE_SHOT; + + mp_printf(print, "Timer(mode=%q, period=%lu)", mode, self->period_ms); +} + +static mp_obj_t mp_m5utils_timer_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { + mp_arg_check_num(n_args, n_kw, 1, MP_OBJ_FUN_ARGS_MAX, true); + + mp_m5utils_timer_t *self = m_new_obj(mp_m5utils_timer_t); + self->base.type = &mp_m5utils_timer_type; + + mp_map_t kw_args; + mp_map_init_fixed_table(&kw_args, n_kw, args + n_args); + mp_m5utils_timer_init_helper(self, n_args - 1, args + 1, &kw_args); + + self->timer_handler = xTimerCreate("freertos timer", pdMS_TO_TICKS(self->period_ms), self->repeat, self, vTimerCallback); + if (self->timer_handler == NULL) { + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("Failed creating FreeRTOS timer")); + } + if (xTimerStart(self->timer_handler, 0) != pdPASS) { + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("Failed starting FreeRTOS timer")); + } + return self; +} + + +static mp_obj_t mp_m5utils_timer_init(size_t n_args, const mp_obj_t *args, mp_map_t *kw_args) { + mp_m5utils_timer_t *self = args[0]; + mp_m5utils_timer_init_helper(self, n_args - 1, args + 1, kw_args); + if (self->timer_handler) { + xTimerDelete(self->timer_handler, portMAX_DELAY); + self->timer_handler = NULL; + } + self->timer_handler = xTimerCreate("freertos timer", pdMS_TO_TICKS(self->period_ms), self->repeat, self, vTimerCallback); + if (self->timer_handler == NULL) { + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("Failed creating FreeRTOS timer")); + } + if (xTimerStart(self->timer_handler, 0) != pdPASS) { + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("Failed starting FreeRTOS timer")); + } + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_KW(mp_m5utils_timer_init_obj, 1, mp_m5utils_timer_init); + +static mp_obj_t mp_m5utils_timer_deinit(mp_obj_t self_in) { + mp_m5utils_timer_t *self = MP_OBJ_TO_PTR(self_in); + if (self->timer_handler) { + xTimerDelete(self->timer_handler, portMAX_DELAY); + self->timer_handler = NULL; + } + + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_1(mp_m5utils_timer_deinit_obj, mp_m5utils_timer_deinit); + + + +static const mp_rom_map_elem_t mp_m5utils_timer_locals_dict_table[] = { + /* *FORMAT-OFF* */ + { MP_ROM_QSTR(MP_QSTR_init), MP_ROM_PTR(&mp_m5utils_timer_init_obj) }, + { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&mp_m5utils_timer_deinit_obj) }, + { MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&mp_m5utils_timer_deinit_obj) }, + { MP_ROM_QSTR(MP_QSTR_ONE_SHOT), MP_ROM_INT(false) }, + { MP_ROM_QSTR(MP_QSTR_PERIODIC), MP_ROM_INT(true) }, + /* *FORMAT-ON* */ +}; + +static MP_DEFINE_CONST_DICT(mp_m5utils_timer_locals_dict, mp_m5utils_timer_locals_dict_table); + +#ifdef MP_OBJ_TYPE_GET_SLOT +MP_DEFINE_CONST_OBJ_TYPE( + mp_m5utils_timer_type, + MP_QSTR_Timer, + MP_TYPE_FLAG_NONE, + make_new, mp_m5utils_timer_make_new, + print, mp_m5utils_timer_print, + locals_dict, &mp_m5utils_timer_locals_dict + ); +#else +const mp_obj_type_t mp_m5utils_timer_type = { + .base = { &mp_type_type }, + .name = MP_QSTR_Timer, + .make_new = mp_m5utils_timer_make_new, + .print = mp_m5utils_timer_print, + .locals_dict = &mp_m5utils_timer_locals_dict, +}; +#endif From 8736b3ad467cf7cb1ae79eb6173c9d34a0746292 Mon Sep 17 00:00:00 2001 From: lbuque <1102390310@qq.com> Date: Fri, 26 Sep 2025 17:39:51 +0800 Subject: [PATCH 277/322] libs/m5ui: Use software timer for LVGL rendering tasks. Signed-off-by: lbuque <1102390310@qq.com> --- m5stack/cmodules/m5unified/m5unified_lvgl.c | 8 +-- m5stack/libs/m5ui/__init__.py | 1 + m5stack/libs/m5ui/port.py | 57 +++++++++++++++++++ m5stack/modules/startup/tab5/__init__.py | 3 + .../modules/startup/tab5/launcher/launcher.py | 3 +- 5 files changed, 67 insertions(+), 5 deletions(-) diff --git a/m5stack/cmodules/m5unified/m5unified_lvgl.c b/m5stack/cmodules/m5unified/m5unified_lvgl.c index 34cb04e7..eb3a84fd 100644 --- a/m5stack/cmodules/m5unified/m5unified_lvgl.c +++ b/m5stack/cmodules/m5unified/m5unified_lvgl.c @@ -51,11 +51,11 @@ mp_obj_t gfx_lvgl_init(mp_obj_t self) { #endif lv_init(); - lvgl_timer = xTimerCreate("lvgl_timer", 10, pdTRUE, NULL, vTimerCallback); + // lvgl_timer = xTimerCreate("lvgl_timer", pdMS_TO_TICKS(10), pdTRUE, NULL, vTimerCallback); - if (lvgl_timer == NULL || xTimerStart(lvgl_timer, 0) != pdPASS) { - ESP_LOGE("LVGL", "Failed creating or starting LVGL timer!"); - } + // if (lvgl_timer == NULL || xTimerStart(lvgl_timer, 0) != pdPASS) { + // ESP_LOGE("LVGL", "Failed creating or starting LVGL timer!"); + // } return mp_const_none; } diff --git a/m5stack/libs/m5ui/__init__.py b/m5stack/libs/m5ui/__init__.py index 5fd71d73..bbc108f4 100644 --- a/m5stack/libs/m5ui/__init__.py +++ b/m5stack/libs/m5ui/__init__.py @@ -5,6 +5,7 @@ _attrs = { "init": "port", "deinit": "port", + "event_loop": "port", "M5Arc": "arc", "M5Bar": "bar", "M5Button": "button", diff --git a/m5stack/libs/m5ui/port.py b/m5stack/libs/m5ui/port.py index c2f9dee0..c7ce9e71 100644 --- a/m5stack/libs/m5ui/port.py +++ b/m5stack/libs/m5ui/port.py @@ -5,10 +5,64 @@ import lvgl as lv import sys import lv_utils +import m5utils +import micropython _event_loop_instance = None +class event_loop: + _instance = None + _initialized = False + + def __new__(cls, freq=33, max_scheduled=2): + if cls._instance is None: + cls._instance = super().__new__(cls) + return cls._instance + + def __init__(self, freq=33, max_scheduled=2): + # 防止重复初始化 + if self._initialized: + return + + self._initialized = True + self.delay = 1000 // freq + self.timer = m5utils.Timer( + 0, mode=m5utils.Timer.PERIODIC, period=self.delay, callback=self.timer_cb + ) + self.max_scheduled = max_scheduled + self.scheduled = 0 + + def timer_cb(self, t): + lv.tick_inc(self.delay) + if self.scheduled < self.max_scheduled: + micropython.schedule(self.task_handler, 0) + self.scheduled += 1 + + def task_handler(self, _): + if lv._nesting.value == 0: + lv.task_handler() + self.scheduled -= 1 + + def deinit(self): + if hasattr(self, "timer"): + self.timer.deinit() + event_loop._initialized = False + event_loop._instance = None + + @classmethod + def get_instance(cls): + """获取单例实例""" + if cls._instance is None: + cls._instance = cls() + return cls._instance + + @classmethod + def is_initialized(cls): + """检查是否已经初始化""" + return cls._instance is not None and cls._initialized + + def _sdl_init(width=320, height=240): global _event_loop_instance @@ -71,6 +125,8 @@ def _m5_init(): fs_drv = lv.fs_drv_t() lv_utils.fs_register(fs_drv, "S", 500) + event_loop() + def init(): if sys.platform == "esp32": @@ -83,6 +139,7 @@ def deinit(): if sys.platform == "esp32": import M5 + event_loop().deinit() M5.Lcd.lvgl_deinit() lv.mp_lv_deinit_gc() else: diff --git a/m5stack/modules/startup/tab5/__init__.py b/m5stack/modules/startup/tab5/__init__.py index d579376a..1b73e1e1 100644 --- a/m5stack/modules/startup/tab5/__init__.py +++ b/m5stack/modules/startup/tab5/__init__.py @@ -8,6 +8,7 @@ import lv_utils from .launcher import Launcher, set_hal from .hal_tab5 import HALTab5 +import m5ui class Tab5_Startup: @@ -50,3 +51,5 @@ def _init_lvgl(self): fs_drv = lv.fs_drv_t() lv_utils.fs_register(fs_drv, "S", 500) + + m5ui.event_loop() # start event loop diff --git a/m5stack/modules/startup/tab5/launcher/launcher.py b/m5stack/modules/startup/tab5/launcher/launcher.py index 7c9b77f7..b3539c7a 100644 --- a/m5stack/modules/startup/tab5/launcher/launcher.py +++ b/m5stack/modules/startup/tab5/launcher/launcher.py @@ -19,6 +19,7 @@ import lvgl as lv import asyncio import M5 +import m5ui class Launcher: @@ -31,7 +32,7 @@ def __init__(self): asyncio.run(self._main()) finally: print("Launcher cleanup complete") - M5.Lcd.lvgl_deinit() + m5ui.deinit() def _init_background(self): screen = lv.screen_active() From 32e771d8310e0ece1685cad034ca9d40b5e44345 Mon Sep 17 00:00:00 2001 From: hlym123 Date: Mon, 29 Sep 2025 16:24:27 +0800 Subject: [PATCH 278/322] boards: Add UnitC6L Support. Signed-off-by: hlym123 --- docs/en/controllers/index.rst | 1 + docs/en/controllers/unit_c6l.rst | 2 + docs/en/hardware/index.rst | 1 + docs/en/hardware/lora.rst | 374 ++++++++ docs/en/module/lora868_v12.rst | 22 +- docs/en/refs/hardware.lora.ref | 40 + .../zh_CN/LC_MESSAGES/hardware/lora.po | 563 ++++++++++++ .../lora/unit_c6l_lora_rx_example.m5f2 | 1 + .../lora/unit_c6l_lora_tx_example.m5f2 | 1 + examples/hardware/lora/unit_c6l_rx_example.py | 63 ++ examples/hardware/lora/unit_c6l_tx_example.py | 61 ++ m5stack/CMakeListsDefault.cmake | 1 + m5stack/CMakeListsLvgl.cmake | 1 + m5stack/Makefile | 139 +-- m5stack/boards/M5STACK_Unit_C6L/board.json | 22 + .../M5STACK_Unit_C6L/deploy_unit_c6l.md | 18 + m5stack/boards/M5STACK_Unit_C6L/manifest.py | 5 + .../M5STACK_Unit_C6L/mpconfigboard.cmake | 28 + .../boards/M5STACK_Unit_C6L/mpconfigboard.h | 22 + .../boards/M5STACK_Unit_C6L/sdkconfig.board | 28 + m5stack/cmodules/m5unified/m5unified.c | 1 + m5stack/components/M5Unified/M5GFX | 2 +- m5stack/libs/hardware/__init__.py | 1 + m5stack/libs/hardware/lora.py | 139 +++ m5stack/libs/hardware/manifest.py | 1 + m5stack/libs/hardware/rgb.py | 3 + m5stack/modules/startup/__init__.py | 6 + m5stack/modules/startup/manifest_unit_c6l.py | 13 + m5stack/modules/startup/unit_c6l.py | 476 ++++++++++ m5stack/patches/2007-Support-UnitC6L.patch | 855 ++++++++++++++++++ pyproject.toml | 3 +- tools/ci.sh | 2 + 32 files changed, 2814 insertions(+), 81 deletions(-) create mode 100644 docs/en/controllers/unit_c6l.rst create mode 100644 docs/en/hardware/lora.rst create mode 100644 docs/en/refs/hardware.lora.ref create mode 100644 docs/locales/zh_CN/LC_MESSAGES/hardware/lora.po create mode 100644 examples/hardware/lora/unit_c6l_lora_rx_example.m5f2 create mode 100644 examples/hardware/lora/unit_c6l_lora_tx_example.m5f2 create mode 100644 examples/hardware/lora/unit_c6l_rx_example.py create mode 100644 examples/hardware/lora/unit_c6l_tx_example.py create mode 100644 m5stack/boards/M5STACK_Unit_C6L/board.json create mode 100644 m5stack/boards/M5STACK_Unit_C6L/deploy_unit_c6l.md create mode 100644 m5stack/boards/M5STACK_Unit_C6L/manifest.py create mode 100644 m5stack/boards/M5STACK_Unit_C6L/mpconfigboard.cmake create mode 100644 m5stack/boards/M5STACK_Unit_C6L/mpconfigboard.h create mode 100644 m5stack/boards/M5STACK_Unit_C6L/sdkconfig.board create mode 100644 m5stack/libs/hardware/lora.py create mode 100644 m5stack/modules/startup/manifest_unit_c6l.py create mode 100644 m5stack/modules/startup/unit_c6l.py create mode 100644 m5stack/patches/2007-Support-UnitC6L.patch diff --git a/docs/en/controllers/index.rst b/docs/en/controllers/index.rst index 813eb091..2210af14 100644 --- a/docs/en/controllers/index.rst +++ b/docs/en/controllers/index.rst @@ -15,3 +15,4 @@ Controllers airq.rst paper.rst dinmeter.rst + unit_c6l.rst diff --git a/docs/en/controllers/unit_c6l.rst b/docs/en/controllers/unit_c6l.rst new file mode 100644 index 00000000..de03cf71 --- /dev/null +++ b/docs/en/controllers/unit_c6l.rst @@ -0,0 +1,2 @@ +UnitC6L +======= \ No newline at end of file diff --git a/docs/en/hardware/index.rst b/docs/en/hardware/index.rst index 9a2a5eeb..a96128dd 100644 --- a/docs/en/hardware/index.rst +++ b/docs/en/hardware/index.rst @@ -11,6 +11,7 @@ Hardware display.rst imu.rst ir.rst + lora.rst mic.rst pin.rst plcio.rst diff --git a/docs/en/hardware/lora.rst b/docs/en/hardware/lora.rst new file mode 100644 index 00000000..d3aeef00 --- /dev/null +++ b/docs/en/hardware/lora.rst @@ -0,0 +1,374 @@ +LoRa +==== + +.. include:: ../refs/hardware.lora.ref + +LoRa is used to control the built-in long-range wireless communication module inside the host device. Below is the detailed LoRa support for the host: + +.. table:: + :widths: auto + :align: center + + +-----------------+---------+ + | Controllers | LoRa | + +=================+=========+ + | UnitC6L | |S| | + +-----------------+---------+ + +.. |S| unicode:: U+2714 + +UiFlow2 Example +--------------- + +Sender +^^^^^^ + +Open the |unit_c6l_tx_example.m5f2| project in UiFlow2. + +This example sends data every second. + +UiFlow2 Code Block: + + |unit_c6l_tx_example.png| + +Example output: + + None + +Receiver +^^^^^^^^ + +Open the |unit_c6l_rx_example.m5f2| project in UiFlow2. + +This example receives and displays data. + +UiFlow2 Code Block: + + |unit_c6l_rx_example.png| + +Example output: + + None + +MicroPython Example +------------------- + +Sender +^^^^^^ + +This example sends data every second. + +MicroPython Code Block: + + .. literalinclude:: ../../../examples/hardware/lora/unit_c6l_tx_example.py + :language: python + :linenos: + +Example output: + + None + +Receiver +^^^^^^^^ + +This example receives and displays data. + +MicroPython Code Block: + + .. literalinclude:: ../../../examples/hardware/lora/unit_c6l_rx_example.py + :language: python + :linenos: + +Example output: + + None + +**API** +------- + +class LoRa +^^^^^^^^^^ + +.. class:: hardware.LoRa(freq_khz = 868000, \ + bw = "250", \ + sf = 8, \ + coding_rate = 8, \ + reamble_len = 12, \ + syncword = 0x12, \ + output_power = 10) + + Create an LoRa object. + + :param int freq_khz: LoRa RF frequency in KHz, with a range of 850000 KHz to 930000 KHz. + :param str bw: Bandwidth, options include: + + - ``"7.8"``: 7.8 KHz + - ``"10.4"``: 10.4 KHz + - ``"15.6"``: 15.6 KHz + - ``"20.8"``: 20.8 KHz + - ``"31.25"``: 31.25 KHz + - ``"41.7"``: 41.7 KHz + - ``"62.5"``: 62.5 KHz + - ``"125"``: 125 KHz + - ``"250"``: 250 KHz + - ``"500"``: 500 KHz + :param int sf: Spreading factor, range from 7 to 12. Higher spreading factors allow reception of weaker signals but with slower data rates. + :param int coding_rate: Forward Error Correction (FEC) coding rate expressed as 4/N, with a range from 5 to 8. + :param int preamble_len: Length of the preamble sequence in symbols, range from 0 to 255. + :param int syncword: Sync word to mark the start of the data frame, default is 0x12. + :param int output_power: Output power in dBm, range from -9 to 22. + + UiFlow2 Code Block: + + |init.png| + + MicroPython Code Block: + + .. code-block:: python + + from hardware import LoRa + + lora_0 = LoRa(868000, '250', 8, 8, 12, 0x12, 10) + + .. method:: set_freq(freq_khz) + + Set frequency in kHz. + + :param int freq_khz: Frequency in kHz (850000 ~ 930000), default is 868000. + + UiFlow2 Code Block: + + |set_freq.png| + + MicroPython Code Block: + + .. code-block:: python + + lora_0.set_freq(freq_khz) + + .. method:: set_sf(sf) + + Set spreading factor (SF). + + :param int sf: Spreading factor (7 ~ 12) + + UiFlow2 Code Block: + + |set_sf.png| + + MicroPython Code Block: + + .. code-block:: python + + lora_0.set_sf(sf) + + .. method:: set_bw(bw) + + Set bandwidth. + + :param str bw: Bandwidth in kHz as string. Must be one of: + '7.8', '10.4', '15.6', '20.8', '31.25', '41.7', + '62.5', '125', '250', '500'. + + UiFlow2 Code Block: + + |set_bw.png| + + MicroPython Code Block: + + .. code-block:: python + + lora_0.set_bw(bw) + + .. method:: set_coding_rate(coding_rate) + + Set coding rate. + + :param int coding_rate: Coding rate (5 ~ 8) + + UiFlow2 Code Block: + + |set_coding_rate.png| + + MicroPython Code Block: + + .. code-block:: python + + lora_0.set_coding_rate(coding_rate) + + .. method:: set_syncword(syncword) + + Set syncword. + + :param int syncword: Sync word (0 ~ 0xFF) + + UiFlow2 Code Block: + + |set_syncword.png| + + MicroPython Code Block: + + .. code-block:: python + + lora_0.set_syncword(syncword) + + .. method:: set_preamble_len(preamble_len) + + Set preamble length. + + :param int preamble_len: Preamble length, range: 0~255. + + UiFlow2 Code Block: + + |set_preamble_len.png| + + MicroPython Code Block: + + .. code-block:: python + + lora_0.set_preamble_len(preamble_len) + + .. method:: set_output_power(output_power) + + Set output power in dBm. + + :param int output_power: Output power in dBm (-9 ~ 22) + + UiFlow2 Code Block: + + |set_output_power.png| + + MicroPython Code Block: + + .. code-block:: python + + lora_0.set_output_power(output_power) + + .. method:: set_irq_callback(callback) + + Set the interrupt callback function to be executed on IRQ. + + :param callback: The callback function to be invoked when the interrupt is triggered. + The callback should not take any arguments and should return nothing. + + Call `start_recv()` to begin receiving data. + + UiFlow2 Code Block: + + |set_irq_callback.png| + + MicroPython Code Block: + + .. code-block:: python + + lora_0.set_irq_callback() + + .. method:: start_recv() + + Start receive data. + + This method initiates the process to begin receiving data. + + UiFlow2 Code Block: + + |start_recv.png| + + MicroPython Code Block: + + .. code-block:: python + + lora_0.start_recv() + + .. method:: recv(self, timeout_ms, rx_length, rx_packet) + + Receive data. + + :param int timeout_ms: Timeout in milliseconds (optional). Default is None. + :param int rx_length: Length of the data to be read. Default is 0xFF. + :param RxPacket rx_packet: An instance of `RxPacket` (optional) to reuse. + :returns: Received packet instance + :rtype: RxPacket + + Attempt to receive a LoRa packet. Returns `None` if timeout occurs, or returns the received packet instance. + + UiFlow2 Code Block: + + |recv.png| + + MicroPython Code Block: + + .. code-block:: python + + data = lora_0.recv() + + .. method:: send(buf, tx_at_ms=None) + + Send data. + + :param str | list | tuple | int | bytearray packet: The data to be sent. + :param int tx_at_ms: The timestamp in milliseconds when to send the data (optional). Default is None. + :returns: Returns a timestamp (result of `time.ticks_ms()`) indicating when the data packet was sent. + :rtype: int + + Send a data packet and return the timestamp after the packet is sent. + + UiFlow2 Code Block: + + |send.png| + + MicroPython Code Block: + + .. code-block:: python + + lora_0.send() + + .. method:: standby() + + Set module to standby mode. + + Puts the LoRa module into standby mode, consuming less power. + + UiFlow2 Code Block: + + |standby.png| + + MicroPython Code Block: + + .. code-block:: python + + lora_0.standby() + + .. method:: sleep() + + Put the module to sleep mode. + + Reduces the power consumption by putting the module into deep sleep mode. + + UiFlow2 Code Block: + + |sleep.png| + + MicroPython Code Block: + + .. code-block:: python + + lora_0.sleep() + + .. method:: irq_triggered() + + Check IRQ trigger. + + :returns: Returns `True` if an interrupt service routine (ISR) has been triggered since the last send or receive started. + :rtype: bool + + UiFlow2 Code Block: + + |irq_triggered.png| + + MicroPython Code Block: + + .. code-block:: python + + lora_0.irq_triggered() + +Refer to :ref:`lora_rxpacket` for more details about RxPacket. diff --git a/docs/en/module/lora868_v12.rst b/docs/en/module/lora868_v12.rst index fdf9ea3e..e04aecf1 100644 --- a/docs/en/module/lora868_v12.rst +++ b/docs/en/module/lora868_v12.rst @@ -1,5 +1,5 @@ LoRa868 v1.2 Module -============================ +=================== .. sku: M029-V12 @@ -12,12 +12,12 @@ Support the following products: |LoRa868Module v1.2| UiFlow2 Example --------------------------- +--------------- .. note:: Before using the following examples, please check the DIP switches on the module to ensure that the pins used in the example match the DIP switch positions. For specific configurations, please refer to the product manual page. The SPI configuration has been implemented internally, so users do not need to worry about it. Sender -^^^^^^^^^^^^^^^^^^^^^^^^ +^^^^^^ Open the |cores3_lora868_v12_tx_example.m5f2| project in UiFlow2. @@ -32,7 +32,7 @@ Example output: None Receiver -^^^^^^^^^^^^^^^^^^^^^^^^ +^^^^^^^^ Open the |cores3_lora868_v12_rx_example.m5f2| project in UiFlow2. @@ -47,12 +47,12 @@ Example output: None MicroPython Example --------------------------- +------------------- .. note:: Before using the following examples, please check the DIP switches on the module to ensure that the pins used in the example match the DIP switch positions. For specific configurations, please refer to the product manual page. The SPI configuration has been implemented internally, so users do not need to worry about it. Sender -^^^^^^^^^^^^^^^^^^^^^^^^ +^^^^^^ This example sends data every second. @@ -67,7 +67,7 @@ Example output: None Receiver -^^^^^^^^^^^^^^^^^^^^^^^^ +^^^^^^^^ This example receives and displays data. @@ -82,11 +82,11 @@ Example output: None **API** --------------------------- +------- class LoRa868V12Module -^^^^^^^^^^^^^^^^^^^^^^^^ +^^^^^^^^^^^^^^^^^^^^^^ .. class:: module.lora868_v12.LoRa868V12Module(pin_rst = 5, \ pin_cs = 1, \ @@ -378,8 +378,10 @@ class LoRa868V12Module module_lora868v12_0.irq_triggered() +.. _lora_rxpacket: + class RxPacket -^^^^^^^^^^^^^^^^^^^^^^^^ +^^^^^^^^^^^^^^ .. class:: lora.RxPacket() diff --git a/docs/en/refs/hardware.lora.ref b/docs/en/refs/hardware.lora.ref new file mode 100644 index 00000000..85784ef3 --- /dev/null +++ b/docs/en/refs/hardware.lora.ref @@ -0,0 +1,40 @@ + +.. |init.png| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/lora/init.png +.. |set_freq.png| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/lora/set_freq.png +.. |set_sf.png| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/lora/set_sf.png +.. |set_bw.png| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/lora/set_bw.png +.. |set_coding_rate.png| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/lora/set_coding_rate.png +.. |set_syncword.png| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/lora/set_syncword.png +.. |set_preamble_len.png| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/lora/set_preamble_len.png +.. |set_output_power.png| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/lora/set_output_power.png +.. |start_recv.png| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/lora/start_recv.png +.. |recv.png| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/lora/recv.png +.. |recv_data_param.png| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/lora/recv_data_param.png +.. |set_irq_callback.png| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/lora/set_irq_callback.png +.. |send_return.png| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/lora/send_return.png +.. |send_time.png| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/lora/send_time.png +.. |send.png| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/lora/send.png +.. |irq_triggered.png| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/lora/irq_triggered.png +.. |standby.png| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/lora/standby.png +.. |sleep.png| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/lora/sleep.png + +.. |unit_c6l_tx_example.png| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/lora/tx_example.png +.. |unit_c6l_rx_example.png| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/lora/rx_example.png + +.. |unit_c6l_tx_example.m5f2| raw:: html + + + unit_c6l_tx_example.m5f2 + + +.. |unit_c6l_rx_example.m5f2| raw:: html + + + unit_c6l_rx_example.m5f2 + diff --git a/docs/locales/zh_CN/LC_MESSAGES/hardware/lora.po b/docs/locales/zh_CN/LC_MESSAGES/hardware/lora.po new file mode 100644 index 00000000..66086469 --- /dev/null +++ b/docs/locales/zh_CN/LC_MESSAGES/hardware/lora.po @@ -0,0 +1,563 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2016 - 2025 M5Stack Technology Co., Ltd +# This file is distributed under the same license as the UIFlow2 Programming +# Guide package. +# FIRST AUTHOR , 2025. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: UIFlow2 Programming Guide \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-09-26 10:50+0800\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: zh_CN\n" +"Language-Team: zh_CN \n" +"Plural-Forms: nplurals=1; plural=0;\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.16.0\n" + +#: ../../en/hardware/lora.rst:2 ../../en/hardware/lora.rst:13 +#: c06e8d85a3a34ee79b2ccc5e28041bd8 fe1319a0c4e144419f14c6aaba7eee76 +msgid "LoRa" +msgstr "LoRa" + +#: ../../en/hardware/lora.rst:6 b0a08d0b402c4fb8851949dcced1af82 +msgid "" +"LoRa is used to control the built-in long-range wireless communication " +"module inside the host device. Below is the detailed LoRa support for the" +" host:" +msgstr "LoRa 用于控制主机设备内置的远程无线通信模块。以下是主机的详细 LoRa 支持:" + +#: ../../en/hardware/lora.rst:13 d2cfc7d198ef41eb8fa72a27e37832a7 +msgid "Controllers" +msgstr "" + +#: ../../en/hardware/lora.rst:15 756596419e4e4138902aa2a47c694695 +msgid "UnitC6L" +msgstr "UnitC6L" + +#: ../../en/hardware/lora.rst:15 da4a3c7bab9a40aca3c9b69547f380e1 +msgid "|S|" +msgstr "" + +#: ../../en/hardware/lora.rst:21 60f11e0527ea4ddab9db6d73592dcb4a +msgid "UiFlow2 Example" +msgstr "UiFlow2 应用示例" + +#: ../../en/hardware/lora.rst:24 ../../en/hardware/lora.rst:57 +#: 134f1698d39d4af3a22445d2cdb6d0dc b39800c60d28452c9558a87aa5ca30e7 +msgid "Sender" +msgstr "发送端" + +#: ../../en/hardware/lora.rst:26 07f15398cf8e46c4b927998cb98a1ae1 +#, fuzzy +msgid "Open the |unit_c6l_tx_example.m5f2| project in UiFlow2." +msgstr "在 UiFlow2 上打开 |unitc6l_tx_example.m5f2| 项目。" + +#: ../../en/hardware/lora.rst:28 ../../en/hardware/lora.rst:59 +#: c1ed525f46c144f6b06963e24e158083 fe49c2459fdc40b38df1d618b81d39dd +msgid "This example sends data every second." +msgstr "该示例每秒发送一次数据。" + +#: ../../en/hardware/lora.rst:30 ../../en/hardware/lora.rst:45 +#: ../../en/hardware/lora.rst:121 ../../en/hardware/lora.rst:139 +#: ../../en/hardware/lora.rst:155 ../../en/hardware/lora.rst:173 +#: ../../en/hardware/lora.rst:189 ../../en/hardware/lora.rst:205 +#: ../../en/hardware/lora.rst:221 ../../en/hardware/lora.rst:237 +#: ../../en/hardware/lora.rst:256 ../../en/hardware/lora.rst:272 +#: ../../en/hardware/lora.rst:294 ../../en/hardware/lora.rst:315 +#: ../../en/hardware/lora.rst:331 ../../en/hardware/lora.rst:347 +#: ../../en/hardware/lora.rst:364 5f3df6bf6ebd4ad48001639727ebc692 +#: 8c24ae9ac0144f2b891411c7ad6f006d efff837a0bbd4f58b95325f013ddac08 +msgid "UiFlow2 Code Block:" +msgstr "UiFlow2 代码块:" + +#: ../../en/hardware/lora.rst:32 32321f22c3314a32b43a59df1951c31f +msgid "|unit_c6l_tx_example.png|" +msgstr "" + +#: ../../en/refs/hardware.lora.ref:21 09e4f7d908d74172a517a393459ce8b8 +msgid "unit_c6l_tx_example.png" +msgstr "" + +#: ../../en/hardware/lora.rst:34 ../../en/hardware/lora.rst:49 +#: ../../en/hardware/lora.rst:67 ../../en/hardware/lora.rst:82 +#: 309bddc5217b44c4af29684cd2e62cf0 d6b09811e2894256ba58c1dd51f9b7af +#: ff76e67d20ff4fb086c1f047bed12600 +msgid "Example output:" +msgstr "示例输出:" + +#: ../../en/hardware/lora.rst:36 ../../en/hardware/lora.rst:51 +#: ../../en/hardware/lora.rst:69 ../../en/hardware/lora.rst:84 +#: 7f8369e2656e49deb41bee9465663332 87b214342af0434a9c2d96e6bea32d36 +#: e17113801eb34480ab298801b7b0ede0 +msgid "None" +msgstr "无" + +#: ../../en/hardware/lora.rst:39 ../../en/hardware/lora.rst:72 +#: 1e5c63e07c93415080edf0c04cb0de75 31153b10476545f091c8f7ee865b423f +msgid "Receiver" +msgstr "接收端" + +#: ../../en/hardware/lora.rst:41 9f38f03e15af4c019d66de32a4500516 +#, fuzzy +msgid "Open the |unit_c6l_rx_example.m5f2| project in UiFlow2." +msgstr "在 UiFlow2 上打开 |unitc6l_rx_example.m5f2| 项目。" + +#: ../../en/hardware/lora.rst:43 ../../en/hardware/lora.rst:74 +#: b73aae634eba4eea8bdc7ef9c793b5c7 cab7da0f396f4cbb842dda17d067fcbe +msgid "This example receives and displays data." +msgstr "示例接收并显示数据。" + +#: ../../en/hardware/lora.rst:47 2ceba1768c674022ae3d65ccd1b00b61 +msgid "|unit_c6l_rx_example.png|" +msgstr "" + +#: ../../en/refs/hardware.lora.ref:22 8bed3b068c8c4254bbce389c99b5e9dc +msgid "unit_c6l_rx_example.png" +msgstr "" + +#: ../../en/hardware/lora.rst:54 545d379a5385468d8c674a203240899d +msgid "MicroPython Example" +msgstr "MicroPython 应用示例" + +#: ../../en/hardware/lora.rst:61 ../../en/hardware/lora.rst:76 +#: ../../en/hardware/lora.rst:125 ../../en/hardware/lora.rst:143 +#: ../../en/hardware/lora.rst:159 ../../en/hardware/lora.rst:177 +#: ../../en/hardware/lora.rst:193 ../../en/hardware/lora.rst:209 +#: ../../en/hardware/lora.rst:225 ../../en/hardware/lora.rst:241 +#: ../../en/hardware/lora.rst:260 ../../en/hardware/lora.rst:276 +#: ../../en/hardware/lora.rst:298 ../../en/hardware/lora.rst:319 +#: ../../en/hardware/lora.rst:335 ../../en/hardware/lora.rst:351 +#: ../../en/hardware/lora.rst:368 cbebf6bdd22042ea8d9c80974f92b679 +msgid "MicroPython Code Block:" +msgstr "MicroPython 代码块:" + +#: ../../en/hardware/lora.rst:87 99d4a966bd684feeb9db55687ea9f2e1 +msgid "**API**" +msgstr "API应用" + +#: ../../en/hardware/lora.rst:90 3a1c9a3923e54c5bb7f4dd76b3045056 +msgid "class LoRa" +msgstr "" + +#: ../../en/hardware/lora.rst:100 25a3b4e6198b42d6bacc31ec77f59d0a +msgid "Create an LoRa object." +msgstr "创建一个 LoRa 对象" + +#: ../../en/hardware/lora.rst b4409e3d58764db4bff9c8358f7f2712 +msgid "Parameters" +msgstr "" + +#: ../../en/hardware/lora.rst:102 42772aaa52c04a9486c45efe70de999e +msgid "LoRa RF frequency in KHz, with a range of 850000 KHz to 930000 KHz." +msgstr "LoRa 通信频率,单位为 KHz,范围为 850000 KHz 到 930000 KHz。" + +#: ../../en/hardware/lora.rst:103 7131ab06e14b4ea28550f36e1a2e36ac +#, fuzzy +msgid "" +"Bandwidth, options include: - ``\"7.8\"``: 7.8 KHz - ``\"10.4\"``: 10.4 " +"KHz - ``\"15.6\"``: 15.6 KHz - ``\"20.8\"``: 20.8 KHz - ``\"31.25\"``: " +"31.25 KHz - ``\"41.7\"``: 41.7 KHz - ``\"62.5\"``: 62.5 KHz - " +"``\"125\"``: 125 KHz - ``\"250\"``: 250 KHz - ``\"500\"``: 500 KHz" +msgstr "" +"带宽,可选项:- ``\"7.8\"``: 7.8 KHz - ``\"10.4\"``: 10.4 KHz - ``\"15.6\"``: " +"15.6 KHz - ``\"20.8\"``: 20.8 KHz - ``\"31.25\"``: 31.25 KHz - " +"``\"41.7\"``: 41.7 KHz - ``\"62.5\"``: 62.5 KHz - ``\"125\"``: 125 KHz - " +"``\"250\"``: 250 KHz - ``\"500\"``: 500 KHz" + +#: ../../en/hardware/lora.rst:103 3c63776dc4af41b0960340043cf81635 +msgid "Bandwidth, options include:" +msgstr "" + +#: ../../en/hardware/lora.rst:105 3c837a6e86904bb89d820e4853e84999 +msgid "``\"7.8\"``: 7.8 KHz" +msgstr "" + +#: ../../en/hardware/lora.rst:106 7128011d7e9542b38fc63ef5ef56ddaa +msgid "``\"10.4\"``: 10.4 KHz" +msgstr "" + +#: ../../en/hardware/lora.rst:107 f90a34f2fe7a4185845efb343c51780b +msgid "``\"15.6\"``: 15.6 KHz" +msgstr "" + +#: ../../en/hardware/lora.rst:108 58d6b375d09f41348849444f0910be0b +msgid "``\"20.8\"``: 20.8 KHz" +msgstr "" + +#: ../../en/hardware/lora.rst:109 779eed6fbf784d8d9d10dab48879a71d +msgid "``\"31.25\"``: 31.25 KHz" +msgstr "" + +#: ../../en/hardware/lora.rst:110 a7ccb26655934228ac7259bf89024836 +msgid "``\"41.7\"``: 41.7 KHz" +msgstr "" + +#: ../../en/hardware/lora.rst:111 6f2d5c42dea24c868a83b72b62918516 +msgid "``\"62.5\"``: 62.5 KHz" +msgstr "" + +#: ../../en/hardware/lora.rst:112 cf414a5d779840de9cd2adb3f7557bca +msgid "``\"125\"``: 125 KHz" +msgstr "" + +#: ../../en/hardware/lora.rst:113 0d89fb08ae7d4d79979701f94363b5c2 +msgid "``\"250\"``: 250 KHz" +msgstr "" + +#: ../../en/hardware/lora.rst:114 5a787b77582e429a8439f2d7f7194caa +msgid "``\"500\"``: 500 KHz" +msgstr "" + +#: ../../en/hardware/lora.rst:115 9360d3ac93984ebfb4be75b2a878fd5a +msgid "" +"Spreading factor, range from 7 to 12. Higher spreading factors allow " +"reception of weaker signals but with slower data rates." +msgstr "扩频因子,范围为 7 到 12。较高的扩频因子可以接收较弱的信号,但数据传输速率会变慢。" + +#: ../../en/hardware/lora.rst:116 e8ad6769e8f047e799bd6232383c50f0 +msgid "" +"Forward Error Correction (FEC) coding rate expressed as 4/N, with a range" +" from 5 to 8." +msgstr "前向纠错(FEC)编码率表示为 4/N,范围为 5 到 8。" + +#: ../../en/hardware/lora.rst:117 e0e367f17dd84c40ba9a0a027299d9ca +msgid "Length of the preamble sequence in symbols, range from 0 to 255." +msgstr "前导码序列的长度(以符号为单位),范围为 0 到 255。" + +#: ../../en/hardware/lora.rst:118 1cccd7be09004234859216f887b3fa4b +msgid "Sync word to mark the start of the data frame, default is 0x12." +msgstr "用于标记数据帧起始的同步字,默认值为 0x12。" + +#: ../../en/hardware/lora.rst:119 30173a89d61542acb616380e02944363 +msgid "Output power in dBm, range from -9 to 22." +msgstr "输出功率(以 dBm 为单位),范围为 -9 到 22。" + +#: ../../en/hardware/lora.rst:123 52ead286a2284c36bb7ba8dfd4b887ae +msgid "|init.png|" +msgstr "" + +#: ../../en/refs/hardware.lora.ref:2 9e28814eae664a148b53a3ccab36787f +msgid "init.png" +msgstr "" + +#: ../../en/hardware/lora.rst:135 e64c8feebdc649f4906d19c4bb1eeaa7 +msgid "Set frequency in kHz." +msgstr "设置频率(单位:kHz)" + +#: ../../en/hardware/lora.rst:137 5ff7a11a41cc4a999a8e2547da5dd8fc +msgid "Frequency in kHz (850000 ~ 930000), default is 868000." +msgstr "频率(单位:kHz),默认值 868000 (kHz)。" + +#: ../../en/hardware/lora.rst:141 0b4d9c59e2684a5ba0c5294defc272cc +msgid "|set_freq.png|" +msgstr "" + +#: ../../en/refs/hardware.lora.ref:3 40ed1aa310fa46d4adc01e200b92473f +msgid "set_freq.png" +msgstr "" + +#: ../../en/hardware/lora.rst:151 21eabf6e61524c52ae5159f6b0be8c72 +msgid "Set spreading factor (SF)." +msgstr "设置扩频因子。" + +#: ../../en/hardware/lora.rst:153 4b020b79eac74916a775a3a6ad1c4657 +msgid "Spreading factor (7 ~ 12)" +msgstr "扩频因子(7~12)。" + +#: ../../en/hardware/lora.rst:157 71fa8aa118104b69848f1d8b2c971045 +msgid "|set_sf.png|" +msgstr "" + +#: ../../en/refs/hardware.lora.ref:4 e964b0fbf6f140f58108db7388d4a096 +msgid "set_sf.png" +msgstr "" + +#: ../../en/hardware/lora.rst:167 f6e52d66144647468bf8e2029466ded8 +msgid "Set bandwidth." +msgstr "设置带宽。" + +#: ../../en/hardware/lora.rst:169 f792a3e3e1a940b892f12ca5d4d2567f +msgid "" +"Bandwidth in kHz as string. Must be one of: '7.8', '10.4', '15.6', " +"'20.8', '31.25', '41.7', '62.5', '125', '250', '500'." +msgstr "" +"带宽(以 kHz " +"表示的字符串)。必须是以下值之一:'7.8'、'10.4'、'15.6'、'20.8'、'31.25'、'41.7'、'62.5'、'125'、'250'、'500'。" + +#: ../../en/hardware/lora.rst:175 01eb33a4922f4e51991169a9abba5ee7 +msgid "|set_bw.png|" +msgstr "" + +#: ../../en/refs/hardware.lora.ref:5 e2a21c10bdc44d6da973534fc021868e +msgid "set_bw.png" +msgstr "" + +#: ../../en/hardware/lora.rst:185 576c0a5ca483427b96ce8476b3846978 +msgid "Set coding rate." +msgstr "设置编码率。" + +#: ../../en/hardware/lora.rst:187 f2929a079672499c8c508ad403fe3123 +msgid "Coding rate (5 ~ 8)" +msgstr "编码率 (5 ~ 8)。" + +#: ../../en/hardware/lora.rst:191 b1f7d31fcf654917acbedac0b359068f +msgid "|set_coding_rate.png|" +msgstr "" + +#: ../../en/refs/hardware.lora.ref:6 b4d35331db4544d1bb747462bbe77918 +msgid "set_coding_rate.png" +msgstr "" + +#: ../../en/hardware/lora.rst:201 4533411d387e49a0971424aaf3f7d132 +msgid "Set syncword." +msgstr "设置同步字。" + +#: ../../en/hardware/lora.rst:203 f05be3c9e8f145ecb90e8162aec5330d +msgid "Sync word (0 ~ 0xFF)" +msgstr "同步字 (0 ~ 0xFF)" + +#: ../../en/hardware/lora.rst:207 ff27c34fc69e45b68ac72d1fec4d7bd3 +msgid "|set_syncword.png|" +msgstr "" + +#: ../../en/refs/hardware.lora.ref:7 e292cc14e5a54b9bb13f3fd99ac50d6c +msgid "set_syncword.png" +msgstr "" + +#: ../../en/hardware/lora.rst:217 2e4fe0d2effd47df90bee7a3f2c3bba4 +msgid "Set preamble length." +msgstr "设置前导符长度。" + +#: ../../en/hardware/lora.rst:219 614470ec25384a2fac03d3139bd61152 +msgid "Preamble length, range: 0~255." +msgstr "前导符长度,范围:0~255。" + +#: ../../en/hardware/lora.rst:223 2fda68cf2fae41a3be76bf67f8f99065 +msgid "|set_preamble_len.png|" +msgstr "" + +#: ../../en/refs/hardware.lora.ref:8 808cb1727da244a1ba2c1225cd49bfae +msgid "set_preamble_len.png" +msgstr "" + +#: ../../en/hardware/lora.rst:233 e41f5216b36a4ea6b0bf3c30e656d525 +msgid "Set output power in dBm." +msgstr "设置输出功率(单位:dBm) 。" + +#: ../../en/hardware/lora.rst:235 c7ebaec98a5749f2b5b0b8f4ddaa542a +msgid "Output power in dBm (-9 ~ 22)" +msgstr "输出功率(单位:dBm),范围:-9 ~ 22。" + +#: ../../en/hardware/lora.rst:239 e9ca90e1a41a46e8bfaeefd743664e7b +msgid "|set_output_power.png|" +msgstr "" + +#: ../../en/refs/hardware.lora.ref:9 cc02b052cf1e4e1d8bf3a6ec3bde4acb +msgid "set_output_power.png" +msgstr "" + +#: ../../en/hardware/lora.rst:249 cecc3ca7d7d04a4a8cadd0273c2af6fa +msgid "Set the interrupt callback function to be executed on IRQ." +msgstr "设置在中断请求(IRQ)发生时执行的中断回调函数。" + +#: ../../en/hardware/lora.rst:251 c9f43ddfe5d1411dbe997a2e67277e78 +msgid "" +"The callback function to be invoked when the interrupt is triggered. The " +"callback should not take any arguments and should return nothing." +msgstr "当中断触发时调用的回调函数。该回调函数不应接受任何参数,且不应有返回值。" + +#: ../../en/hardware/lora.rst:254 7c894b5fd2a44cd6a8b0a86366169fba +msgid "Call `start_recv()` to begin receiving data." +msgstr "调用 `start_recv()` 开始接收数据。" + +#: ../../en/hardware/lora.rst:258 c975519f080a4711bc23a6c063bdc115 +msgid "|set_irq_callback.png|" +msgstr "" + +#: ../../en/refs/hardware.lora.ref:13 81c0a2d3084b469baadd5280a5801a94 +msgid "set_irq_callback.png" +msgstr "" + +#: ../../en/hardware/lora.rst:268 53fbb3769b044ea9ae719e067f20b9bb +msgid "Start receive data." +msgstr "开始接收数据。" + +#: ../../en/hardware/lora.rst:270 8fa547847f8c48b282157573cc29456a +msgid "This method initiates the process to begin receiving data." +msgstr "此方法启动接收数据的过程。" + +#: ../../en/hardware/lora.rst:274 9416035105f54323a111b362e9b644b3 +msgid "|start_recv.png|" +msgstr "" + +#: ../../en/refs/hardware.lora.ref:10 5591f4a1d94648d982fc2c4d62d59ddc +msgid "start_recv.png" +msgstr "" + +#: ../../en/hardware/lora.rst:284 7444c7eb6fdf42b3aec3149613649c28 +msgid "Receive data." +msgstr "接收数据。" + +#: ../../en/hardware/lora.rst:286 1fadcc95aaeb47528719ec1b4dae3f9a +msgid "Timeout in milliseconds (optional). Default is None." +msgstr "超时时间(以毫秒为单位,可选)。默认为 None。" + +#: ../../en/hardware/lora.rst:287 3096cd82bd7342f5854b927e2094cdc7 +msgid "Length of the data to be read. Default is 0xFF." +msgstr "读取数据的长度。默认为 0xFF。" + +#: ../../en/hardware/lora.rst:288 edbdca7d69cc4714b38e6d1c37634b47 +msgid "An instance of `RxPacket` (optional) to reuse." +msgstr "一个可选的 RxPacket 实例,用于重用。" + +#: ../../en/hardware/lora.rst 234df300158341e49c9ee488e5ceb233 +msgid "Returns" +msgstr "" + +#: ../../en/hardware/lora.rst:289 c431d11e2b104f53a235e44f9b8b271a +msgid "Received packet instance" +msgstr "接收数据包实例。" + +#: ../../en/hardware/lora.rst fec628b0fb5341ba86b856e2dc296e69 +msgid "Return type" +msgstr "" + +#: ../../en/hardware/lora.rst:292 d47716f4af6c4e8dbacf7e278cd10490 +msgid "" +"Attempt to receive a LoRa packet. Returns `None` if timeout occurs, or " +"returns the received packet instance." +msgstr "尝试接收一个 LoRa 数据包。如果发生超时,返回 None;否则,返回接收到的数据包实例。" + +#: ../../en/hardware/lora.rst:296 653dc45c40104f6fa1cbdace8fdb165d +msgid "|recv.png|" +msgstr "" + +#: ../../en/refs/hardware.lora.ref:11 7b71266ccee041d6b73eb3cf770dc29a +msgid "recv.png" +msgstr "" + +#: ../../en/hardware/lora.rst:306 31e1ab68745546dfac84a4b7187e58d4 +msgid "Send data." +msgstr "发送数据" + +#: ../../en/hardware/lora.rst:308 94c1e8f4d7714d32948f3e179fb5d64d +msgid "The data to be sent." +msgstr "要发送的数据" + +#: ../../en/hardware/lora.rst:309 289a5a70788b45d6938e608b9dcf0835 +msgid "" +"The timestamp in milliseconds when to send the data (optional). Default " +"is None." +msgstr "发送数据的时间戳,单位为毫秒(可选)。默认为 None。" + +#: ../../en/hardware/lora.rst:310 4be0e879a04d446d87d98a4b41a319e7 +msgid "" +"Returns a timestamp (result of `time.ticks_ms()`) indicating when the " +"data packet was sent." +msgstr "返回一个时间戳(time.ticks_ms()的结果),表示数据包发送的时间。" + +#: ../../en/hardware/lora.rst:313 e18b5ef59cf24321be6c739781012a0d +msgid "Send a data packet and return the timestamp after the packet is sent." +msgstr "发送一个数据包并返回数据包发送后的时间戳。" + +#: ../../en/hardware/lora.rst:317 475cdffad2d0468595e003be78a641b3 +msgid "|send.png|" +msgstr "" + +#: ../../en/refs/hardware.lora.ref:16 e4d0383f3e78461e842a07c9775c2de6 +msgid "send.png" +msgstr "" + +#: ../../en/hardware/lora.rst:327 8c71fa94841a4f81a7ba7ba32db26f45 +msgid "Set module to standby mode." +msgstr "设置模块为待机模式。" + +#: ../../en/hardware/lora.rst:329 127560c09acd4c628e40e984d0e30a2e +msgid "Puts the LoRa module into standby mode, consuming less power." +msgstr "将 LoRa 模块置于待机模式,从而降低功耗。" + +#: ../../en/hardware/lora.rst:333 bc9cf07e96464f0195fda0cfe76d6fb9 +msgid "|standby.png|" +msgstr "" + +#: ../../en/refs/hardware.lora.ref:18 97283f5bf2fc4cab8a7e3902bac1f046 +msgid "standby.png" +msgstr "" + +#: ../../en/hardware/lora.rst:343 4e79912da77245aebd8bf2dc8d176756 +msgid "Put the module to sleep mode." +msgstr "将 LoRa 模块置于睡眠模式" + +#: ../../en/hardware/lora.rst:345 6b60652eaf0c44c0bea441aa29bb24a3 +msgid "Reduces the power consumption by putting the module into deep sleep mode." +msgstr "通过将模块置于深度睡眠模式来减少功耗。" + +#: ../../en/hardware/lora.rst:349 aded052e1ce347d390b7ddd17c8448b3 +msgid "|sleep.png|" +msgstr "" + +#: ../../en/refs/hardware.lora.ref:19 a92ae70f03c44a1b9b70eecd6750764b +msgid "sleep.png" +msgstr "" + +#: ../../en/hardware/lora.rst:359 91b67aa1fd284474882b1980a961073f +msgid "Check IRQ trigger." +msgstr "检查 IRQ 触发。" + +#: ../../en/hardware/lora.rst:361 b18180e9174f4e1d9ee999d8cfa51eb6 +msgid "" +"Returns `True` if an interrupt service routine (ISR) has been triggered " +"since the last send or receive started." +msgstr "如果自上次发送或接收开始以来,已经触发了中断服务例程(ISR),则返回 True。" + +#: ../../en/hardware/lora.rst:366 3cf9f1bbd41a40da88ca923db8618205 +msgid "|irq_triggered.png|" +msgstr "" + +#: ../../en/refs/hardware.lora.ref:17 f6d7a71e1a91409ca31f80e9789d35c8 +msgid "irq_triggered.png" +msgstr "" + +#: ../../en/hardware/lora.rst:374 599abc9ac89743718026139887ba50bf +msgid "Refer to :ref:`lora_rxpacket` for more details about RxPacket." +msgstr "" + +#~ msgid "|unitc6l_tx_example.png|" +#~ msgstr "" + +#~ msgid "unitc6l_tx_example.png" +#~ msgstr "" + +#~ msgid "|unitc6l_rx_example.png|" +#~ msgstr "" + +#~ msgid "unitc6l_rx_example.png" +#~ msgstr "" + +#~ msgid "class RxPacket" +#~ msgstr "" + +#~ msgid "Create an RxPacket object." +#~ msgstr "创建一个 RxPacket 对象。" + +#~ msgid "Decode the received data." +#~ msgstr "解码接收到的数据。" + +#~ msgid "Timestamp of when the data was received." +#~ msgstr "数据接收的时间戳。" + +#~ msgid "Received signal strength (units: dBm)." +#~ msgstr "接收信号强度(单位:dBm)。" + +#~ msgid "Signal-to-noise ratio (units: dB * 4)." +#~ msgstr "信噪比(单位:dB * 4)。" + +#~ msgid "CRC validity check." +#~ msgstr "CRC 校验有效性。" + diff --git a/examples/hardware/lora/unit_c6l_lora_rx_example.m5f2 b/examples/hardware/lora/unit_c6l_lora_rx_example.m5f2 new file mode 100644 index 00000000..b2f6358a --- /dev/null +++ b/examples/hardware/lora/unit_c6l_lora_rx_example.m5f2 @@ -0,0 +1 @@ +{"version":"V2.0","versionNumber":"V2.3.6","type":"unit-c6l","components":[{"name":"screen","type":"screen","layer":0,"screenId":"builtin","screenName":"","id":"__unit-c6l_screen","createTime":1758849987599,"x":0,"y":0,"width":64,"height":48,"backgroundColor":"#000000","size":0,"isSelected":true},{"name":"title0","type":"title","layer":1,"screenId":"builtin","screenName":"","id":"iS9T%CyJ#L4&L+sy","createTime":1758849996669,"x":0,"y":0,"color":"#000000","backgroundColor":"#ffffff","text":"Rx","textOffset":3,"font":"Widgets.FONTS.DejaVu12","isSelected":false},{"name":"label_rx","type":"label","layer":2,"screenId":"builtin","screenName":"","id":"w0aDjKpAOp#rO0TP","createTime":1758850012645,"x":2,"y":23,"color":"#ffffff","backgroundColor":"#000000","text":"label0","font":"Widgets.FONTS.DejaVu12","rotation":0,"isSelected":false,"width":42,"height":15}],"resources":[{"hardware":["hardware_button","hardware_pin_button","rgb","speaker","hardware_lora"]}],"units":[],"hats":[],"caps":[],"bases":[],"i2cs":[],"blockly":"lora_datasnrrssitrue86800025088120x1210truelora_datalabel_rxLabellora_datasnrDIVIDE1snrlora_data4rssirssilora_datahello M5SNR: SNR: snr RSSI: rssi","screen":[{"simulationName":"Built-in","type":"builtin","width":64,"height":48,"scale":1.2,"screenName":"","blockId":"","screenColorType":1,"id":"builtin","createTime":1758849987598}],"logicWhenNum":0,"customList":[]} \ No newline at end of file diff --git a/examples/hardware/lora/unit_c6l_lora_tx_example.m5f2 b/examples/hardware/lora/unit_c6l_lora_tx_example.m5f2 new file mode 100644 index 00000000..c7ba74f6 --- /dev/null +++ b/examples/hardware/lora/unit_c6l_lora_tx_example.m5f2 @@ -0,0 +1 @@ +{"version":"V2.0","versionNumber":"V2.3.6","type":"unit-c6l","components":[{"name":"screen","type":"screen","layer":0,"screenId":"builtin","screenName":"","id":"__unit-c6l_screen","createTime":1758850530127,"x":0,"y":0,"width":64,"height":48,"backgroundColor":"#000000","size":0,"isSelected":true},{"name":"title0","type":"title","layer":1,"screenId":"builtin","screenName":"","id":"hGg`oWBufzx*g30%","createTime":1758851734048,"x":0,"y":0,"color":"#000000","backgroundColor":"#ffffff","text":"Tx","textOffset":3,"font":"Widgets.FONTS.DejaVu12","isSelected":false},{"name":"label_tx","type":"label","layer":2,"screenId":"builtin","screenName":"","id":"rQH!WYCwCEVzHQaL","createTime":1758851746559,"x":2,"y":23,"color":"#ffffff","backgroundColor":"#000000","text":"label0","font":"Widgets.FONTS.DejaVu12","rotation":0,"isSelected":false,"width":42,"height":15}],"resources":[{"hardware":["hardware_button","hardware_pin_button","rgb","speaker","hardware_lora"]}],"units":[],"hats":[],"caps":[],"bases":[],"i2cs":[],"blockly":"last_timecounttxtrue86800025088120x1210last_timecount0trueGTE11last_time1000last_timetxM5 countcount1txlabel_txLabeltx","screen":[{"simulationName":"Built-in","type":"builtin","width":64,"height":48,"scale":1.2,"screenName":"","blockId":"","screenColorType":1,"id":"builtin","createTime":1758850530126}],"logicWhenNum":0,"customList":[]} \ No newline at end of file diff --git a/examples/hardware/lora/unit_c6l_rx_example.py b/examples/hardware/lora/unit_c6l_rx_example.py new file mode 100644 index 00000000..90d4d9be --- /dev/null +++ b/examples/hardware/lora/unit_c6l_rx_example.py @@ -0,0 +1,63 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import os, sys, io +import M5 +from M5 import * +from hardware import LoRa + + +title0 = None +label_rx = None +lora = None +lora_data = None +snr = None +rssi = None + + +def lora_receive_event(received_data): + global title0, label_rx, lora, lora_data, snr, rssi + lora_data = received_data + label_rx.setText(str(lora_data.decode())) + snr = (lora_data.snr) / 4 + rssi = lora_data.rssi + print((str((str("SNR: ") + str(snr))) + str((str(" RSSI: ") + str(rssi))))) + + +def setup(): + global title0, label_rx, lora, lora_data, snr, rssi + M5.begin() + Widgets.fillScreen(0x000000) + title0 = Widgets.Title("Rx", 3, 0x000000, 0xFFFFFF, Widgets.FONTS.DejaVu12) + label_rx = Widgets.Label("label0", 2, 23, 1.0, 0xFFFFFF, 0x000000, Widgets.FONTS.DejaVu12) + lora = LoRa( + freq_khz=868000, + bw="250", + sf=8, + coding_rate=8, + preamble_len=12, + syncword=0x12, + output_power=10, + ) + lora.set_irq_callback(lora_receive_event) + lora.start_recv() + + +def loop(): + global title0, label_rx, lora, lora_data, snr, rssi + M5.update() + + +if __name__ == "__main__": + try: + setup() + while True: + loop() + except (Exception, KeyboardInterrupt) as e: + try: + from utility import print_error_msg + + print_error_msg(e) + except ImportError: + print("please update to latest firmware") diff --git a/examples/hardware/lora/unit_c6l_tx_example.py b/examples/hardware/lora/unit_c6l_tx_example.py new file mode 100644 index 00000000..e3b65e62 --- /dev/null +++ b/examples/hardware/lora/unit_c6l_tx_example.py @@ -0,0 +1,61 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import os, sys, io +import M5 +from M5 import * +from hardware import LoRa +import time + + +title0 = None +label_tx = None +lora = None +last_time = None +count = None +tx = None + + +def setup(): + global title0, label_tx, lora, last_time, count, tx + M5.begin() + Widgets.fillScreen(0x000000) + title0 = Widgets.Title("Tx", 3, 0x000000, 0xFFFFFF, Widgets.FONTS.DejaVu12) + label_tx = Widgets.Label("label0", 2, 23, 1.0, 0xFFFFFF, 0x000000, Widgets.FONTS.DejaVu12) + lora = LoRa( + freq_khz=868000, + bw="250", + sf=8, + coding_rate=8, + preamble_len=12, + syncword=0x12, + output_power=10, + ) + last_time = time.ticks_ms() + count = 0 + + +def loop(): + global title0, label_tx, lora, last_time, count, tx + M5.update() + if (time.ticks_diff((time.ticks_ms()), last_time)) >= 1000: + last_time = time.ticks_ms() + tx = str("M5 ") + str(count) + count = (count if isinstance(count, (int, float)) else 0) + 1 + lora.send(tx, None) + label_tx.setText(str(tx)) + + +if __name__ == "__main__": + try: + setup() + while True: + loop() + except (Exception, KeyboardInterrupt) as e: + try: + from utility import print_error_msg + + print_error_msg(e) + except ImportError: + print("please update to latest firmware") diff --git a/m5stack/CMakeListsDefault.cmake b/m5stack/CMakeListsDefault.cmake index 3e8d1a3a..fa93961c 100644 --- a/m5stack/CMakeListsDefault.cmake +++ b/m5stack/CMakeListsDefault.cmake @@ -153,6 +153,7 @@ if ( OR BOARD_TYPE STREQUAL "capsule" OR BOARD_TYPE STREQUAL "tough" OR BOARD_TYPE STREQUAL "stamplc" + OR BOARD_TYPE STREQUAL "unit_c6l" ) LIST(APPEND MICROPY_SOURCE_PORT ${PROJECT_DIR}/machine_hw_spi.c) LIST(APPEND MICROPY_SOURCE_PORT ${PROJECT_DIR}/machine_sdcard.c) diff --git a/m5stack/CMakeListsLvgl.cmake b/m5stack/CMakeListsLvgl.cmake index 5df17d5d..6b39943d 100644 --- a/m5stack/CMakeListsLvgl.cmake +++ b/m5stack/CMakeListsLvgl.cmake @@ -138,6 +138,7 @@ if ( OR BOARD_TYPE STREQUAL "capsule" OR BOARD_TYPE STREQUAL "tough" OR BOARD_TYPE STREQUAL "stamplc" + OR BOARD_TYPE STREQUAL "unit_c6l" ) LIST(APPEND MICROPY_SOURCE_PORT ${PROJECT_DIR}/machine_hw_spi.c) LIST(APPEND MICROPY_SOURCE_PORT ${PROJECT_DIR}/machine_sdcard.c) diff --git a/m5stack/Makefile b/m5stack/Makefile index fa3aa145..3afe665f 100644 --- a/m5stack/Makefile +++ b/m5stack/Makefile @@ -11,40 +11,40 @@ BOARD ?= M5STACK_AtomS3 boards := \ - M5STACK_AtomS3:atoms3 \ - M5STACK_AtomS3_Lite:atoms3-lite \ - M5STACK_StampS3:stamps3 \ - M5STACK_CoreS3:cores3 \ - M5STACK_AtomS3U:atoms3u \ - M5STACK_Core2:core2 \ - M5STACK_Tough:tough \ - M5STACK_StickC_PLUS2:stickcplus2 \ - M5STACK_StickC_PLUS:stickcplus \ - M5STACK_Fire:fire \ - M5STACK_NanoC6:nanoc6 \ - M5STACK_Basic:basic \ - M5STACK_Basic_4MB:basic \ - M5STACK_Capsule:capsule \ - M5STACK_CoreInk:coreink \ - M5STACK_AirQ:airq \ - M5STACK_Dial:dial \ - M5STACK_Cardputer:cardputer \ - M5STACK_Paper:paper \ - M5STACK_PaperS3:papers3 \ - M5STACK_DinMeter:dinmeter \ - M5STACK_StickC:stickc \ - M5STACK_Station:station \ - M5STACK_Atom_Lite:atom-lite \ - M5STACK_Stamp_PICO:stamppico \ - M5STACK_Atom_Matrix:atommatrix \ - M5STACK_AtomU:atomu \ - M5STACK_Atom_Echo:atomecho \ - M5STACK_AtomS3R:atoms3r \ - M5STACK_AtomS3R_CAM:atoms3r_cam \ - M5STACK_Atom_EchoS3R:atom_echos3r \ - M5STACK_StamPLC:stamplc \ - M5STACK_Tab5:tab5 \ - M5STACK_CardputerADV:cardputeradv + M5STACK_AtomS3:atoms3 \ + M5STACK_AtomS3_Lite:atoms3-lite \ + M5STACK_StampS3:stamps3 \ + M5STACK_CoreS3:cores3 \ + M5STACK_AtomS3U:atoms3u \ + M5STACK_Core2:core2 \ + M5STACK_Tough:tough \ + M5STACK_StickC_PLUS2:stickcplus2 \ + M5STACK_StickC_PLUS:stickcplus \ + M5STACK_Fire:fire \ + M5STACK_NanoC6:nanoc6 \ + M5STACK_Basic:basic \ + M5STACK_Basic_4MB:basic \ + M5STACK_Capsule:capsule \ + M5STACK_CoreInk:coreink \ + M5STACK_AirQ:airq \ + M5STACK_Dial:dial \ + M5STACK_Cardputer:cardputer \ + M5STACK_Paper:paper \ + M5STACK_PaperS3:papers3 \ + M5STACK_DinMeter:dinmeter \ + M5STACK_StickC:stickc \ + M5STACK_Station:station \ + M5STACK_Atom_Lite:atom-lite \ + M5STACK_Stamp_PICO:stamppico \ + M5STACK_Atom_Matrix:atommatrix \ + M5STACK_AtomU:atomu \ + M5STACK_Atom_Echo:atomecho \ + M5STACK_AtomS3R:atoms3r \ + M5STACK_AtomS3R_CAM:atoms3r_cam \ + M5STACK_StamPLC:stamplc \ + M5STACK_Tab5:tab5 \ + M5STACK_CardputerADV:cardputeradv\ + M5STACK_Unit_C6L:unit_c6l define find_board $(if $(filter $(1):%,$(boards)),$(word 2,$(subst :, ,$(filter $(1):%,$(boards)))),none) @@ -52,40 +52,40 @@ endef # Board type list BOARD_TYPE_DEF := \ - none \ - atoms3 \ - atoms3-lite \ - stamps3 \ - cores3 \ - atoms3u \ - core2 \ - tough \ - stickcplus2 \ - stickcplus \ - fire \ - nanoc6 \ - basic \ - capsule \ - coreink \ - airq \ - dial \ - cardputer \ - paper \ - papers3 \ - dinmeter \ - stickc \ - station \ - atom-lite \ - stamppico \ - atommatrix \ - atomu \ - atomecho \ - atoms3r \ - atoms3r_cam \ - atom_echos3r \ - stamplc \ - tab5 \ - cardputeradv + none \ + atoms3 \ + atoms3-lite \ + stamps3 \ + cores3 \ + atoms3u \ + core2 \ + tough \ + stickcplus2 \ + stickcplus \ + fire \ + nanoc6 \ + basic \ + capsule \ + coreink \ + airq \ + dial \ + cardputer \ + paper \ + papers3 \ + dinmeter \ + stickc \ + station \ + atom-lite \ + stamppico \ + atommatrix \ + atomu \ + atomecho \ + atoms3r \ + atoms3r_cam \ + stamplc \ + tab5 \ + cardputeradv\ + unit_c6l # Select the board type to build, default is None # This value affects which folder in the "./fs/system/" directory is pack into "fs-system.bin" @@ -323,7 +323,8 @@ IDF_PATH_PATCH_SERIES = \ 1004-idf_v5.4_freertos.patch M5UNIFIED_PATCH_SERIES = \ - 2006-Support-LTR553.patch + 2006-Support-LTR553.patch \ + 2007-Support-UnitC6L.patch ADF_PATCH_SERIES = \ 3002-Modify-i2s_stream_idf5.patch diff --git a/m5stack/boards/M5STACK_Unit_C6L/board.json b/m5stack/boards/M5STACK_Unit_C6L/board.json new file mode 100644 index 00000000..9c912b8f --- /dev/null +++ b/m5stack/boards/M5STACK_Unit_C6L/board.json @@ -0,0 +1,22 @@ +{ + "deploy": [ + "deploy_unit_c6l.md" + ], + "docs": "", + "features": [ + "BLE", + "WiFi", + "LoRa", + "Buzzer", + "RGB LED", + "USB", + ], + "images": [ + "m5stack_unit_c6l.jpg" + ], + "mcu": "esp32c6", + "product": "M5Stack UnitC6L", + "url": "https://shop.m5stack.com/products/m5stack-unit-c6l", + "vendor": "M5Stack" +} + diff --git a/m5stack/boards/M5STACK_Unit_C6L/deploy_unit_c6l.md b/m5stack/boards/M5STACK_Unit_C6L/deploy_unit_c6l.md new file mode 100644 index 00000000..fc7596c7 --- /dev/null +++ b/m5stack/boards/M5STACK_Unit_C6L/deploy_unit_c6l.md @@ -0,0 +1,18 @@ +Program your board using the esptool.py program, found +[here](https://github.com/espressif/esptool). + +To put the NanoC6 into 'update mode', hold the button while connecting the USB +cable. It can be released after the connection is made. + +If you are putting MicroPython on your board for the first time then you should +first erase the entire flash using: + +```bash +esptool.py --chip esp32c6 --port /dev/ttyUSB0 erase_flash +``` + +From then on program the firmware starting at address 0x0: + +```bash +esptool.py --chip esp32c6 --port /dev/ttyUSB0 --baud 460800 write_flash -z 0x0 M5STACK_Unit_C6L-20250915-v1.25.0.bin +``` diff --git a/m5stack/boards/M5STACK_Unit_C6L/manifest.py b/m5stack/boards/M5STACK_Unit_C6L/manifest.py new file mode 100644 index 00000000..c9dbc017 --- /dev/null +++ b/m5stack/boards/M5STACK_Unit_C6L/manifest.py @@ -0,0 +1,5 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +include("$(MPY_DIR)/../m5stack/modules/startup/manifest_unit_c6l.py") diff --git a/m5stack/boards/M5STACK_Unit_C6L/mpconfigboard.cmake b/m5stack/boards/M5STACK_Unit_C6L/mpconfigboard.cmake new file mode 100644 index 00000000..593a7a9b --- /dev/null +++ b/m5stack/boards/M5STACK_Unit_C6L/mpconfigboard.cmake @@ -0,0 +1,28 @@ +# SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +set(IDF_TARGET esp32c6) + +# nanoc6 https://github.com/m5stack/m5stack-board-id/blob/main/board.csv#L33 +set(BOARD_ID 25) +set(MICROPY_PY_LVGL 0) + +set(SDKCONFIG_DEFAULTS + ./boards/sdkconfig.base + ${SDKCONFIG_IDF_VERSION_SPECIFIC} + ./boards/sdkconfig.flash_8mb + ./boards/sdkconfig.c6 + ./boards/sdkconfig.ble + ./boards/sdkconfig.usb + ./boards/sdkconfig.freertos + ./boards/M5STACK_Unit_C6L/sdkconfig.board +) + +# If not enable LVGL, ignore this... +set(LV_CFLAGS -DLV_COLOR_DEPTH=16 -DLV_COLOR_16_SWAP=0) + +if(NOT MICROPY_FROZEN_MANIFEST) + set(MICROPY_FROZEN_MANIFEST ${CMAKE_SOURCE_DIR}/boards/manifest.py) +endif() + diff --git a/m5stack/boards/M5STACK_Unit_C6L/mpconfigboard.h b/m5stack/boards/M5STACK_Unit_C6L/mpconfigboard.h new file mode 100644 index 00000000..b0713b4d --- /dev/null +++ b/m5stack/boards/M5STACK_Unit_C6L/mpconfigboard.h @@ -0,0 +1,22 @@ +#define MICROPY_HW_BOARD_NAME "M5Stack UnitC6L" +#define MICROPY_HW_MCU_NAME "ESP32C6" + +#define MICROPY_HW_ENABLE_SDCARD (0) +#define MICROPY_PY_MACHINE_I2S (0) + +#define MICROPY_HW_I2C0_SCL (4) +#define MICROPY_HW_I2C0_SDA (5) + +// ESP32-C6 SPI configuration +// ESP32-C6 only has SPI2 (HSPI), SPI3 is not available +// Default SPI2 pins for ESP32-C6 +#define MICROPY_HW_SPI1_SCK (6) // GPIO6 - SCLK +#define MICROPY_HW_SPI1_MOSI (7) // GPIO7 - MOSI +#define MICROPY_HW_SPI1_MISO (2) // GPIO2 - MISO + +// #define MICROPY_HW_USB_CDC_INTERFACE_STRING "M5Stack UnitC6L(UiFlow2)" + +// #ifndef MICROPY_HW_ENABLE_USB_RUNTIME_DEVICE +// #define MICROPY_HW_ENABLE_USB_RUNTIME_DEVICE (1) // Support machine.USBDevice +// #endif + diff --git a/m5stack/boards/M5STACK_Unit_C6L/sdkconfig.board b/m5stack/boards/M5STACK_Unit_C6L/sdkconfig.board new file mode 100644 index 00000000..36f2e157 --- /dev/null +++ b/m5stack/boards/M5STACK_Unit_C6L/sdkconfig.board @@ -0,0 +1,28 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +# +# Serial flasher config +# +CONFIG_ESPTOOLPY_FLASHMODE_DIO=y +CONFIG_ESPTOOLPY_FLASH_SAMPLE_MODE_STR=y +CONFIG_ESPTOOLPY_FLASHMODE="dio" +CONFIG_ESPTOOLPY_FLASHFREQ_80M=y +CONFIG_ESPTOOLPY_FLASHFREQ="80m" +CONFIG_ESPTOOLPY_FLASHSIZE_8MB=y +CONFIG_ESPTOOLPY_FLASHSIZE="8MB" +CONFIG_ESPTOOLPY_BEFORE_RESET=y +CONFIG_ESPTOOLPY_BEFORE="default_reset" +CONFIG_ESPTOOLPY_AFTER_RESET=y +CONFIG_ESPTOOLPY_AFTER="hard_reset" +CONFIG_ESPTOOLPY_MONITOR_BAUD=115200 +# end of Serial flasher config + +# M5STACK UiFlow USB description +CONFIG_TINYUSB_DESC_CDC_STRING="M5Stack UnitC6L(UiFlow2)" + +# SSL +CONFIG_MBEDTLS_AES_USE_INTERRUPT=n + +CONFIG_FREERTOS_ENABLE_BACKWARD_COMPATIBILITY=y diff --git a/m5stack/cmodules/m5unified/m5unified.c b/m5stack/cmodules/m5unified/m5unified.c index 762bc482..151e04de 100644 --- a/m5stack/cmodules/m5unified/m5unified.c +++ b/m5stack/cmodules/m5unified/m5unified.c @@ -31,6 +31,7 @@ static const mp_rom_map_elem_t m5_board_member_table[] = { { MP_ROM_QSTR(MP_QSTR_M5StamPLC), MP_ROM_INT(21) }, { MP_ROM_QSTR(MP_QSTR_M5Tab5), MP_ROM_INT(22) }, { MP_ROM_QSTR(MP_QSTR_M5CardputerADV), MP_ROM_INT(24) }, + { MP_ROM_QSTR(MP_QSTR_M5UnitC6L), MP_ROM_INT(25) }, // non display boards { MP_ROM_QSTR(MP_QSTR_M5Atom), MP_ROM_INT(128) }, { MP_ROM_QSTR(MP_QSTR_M5AtomPsram), MP_ROM_INT(129) }, diff --git a/m5stack/components/M5Unified/M5GFX b/m5stack/components/M5Unified/M5GFX index bd0eaaf2..bf16c3d2 160000 --- a/m5stack/components/M5Unified/M5GFX +++ b/m5stack/components/M5Unified/M5GFX @@ -1 +1 @@ -Subproject commit bd0eaaf2338f8a31a0563297fd8f878018dba450 +Subproject commit bf16c3d2b70bfbe721399e0990ef2a0c0dc5bd74 diff --git a/m5stack/libs/hardware/__init__.py b/m5stack/libs/hardware/__init__.py index dfcfec36..af1271ab 100644 --- a/m5stack/libs/hardware/__init__.py +++ b/m5stack/libs/hardware/__init__.py @@ -8,6 +8,7 @@ "Button": "button", "CAN": "can", "IR": "ir", + "LoRa": "lora", "MatrixKeyboard": "matrix_keyboard", "DigitalInput": "plcio", "Relay": "plcio", diff --git a/m5stack/libs/hardware/lora.py b/m5stack/libs/hardware/lora.py new file mode 100644 index 00000000..c056720b --- /dev/null +++ b/m5stack/libs/hardware/lora.py @@ -0,0 +1,139 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +from machine import Pin, SPI +from lora import SX1262 +from lora import RxPacket +from micropython import const, schedule +import machine + + +class LoRa: + def __init__( + self, + pin_rst: int = -1, + pin_cs: int = 23, + pin_irq: int = 7, + pin_busy: int = 19, + freq_khz: int = 868000, + bw: str = "250", + sf: int = 8, + coding_rate: int = 8, + preamble_len: int = 12, + syncword: int = 0x12, + output_power: int = 10, + ): + # Valid bandwidth + self.BANDWIDTHS = ( + "7.8", + "10.4", + "15.6", + "20.8", + "31.25", + "41.7", + "62.5", + "125", + "250", + "500", + ) + self._validate_range(sf, 6, 12) + self._validate_range(coding_rate, 5, 8) + if bw not in self.BANDWIDTHS: + raise ValueError(f"Invalid bandwidth {bw}") + + lora_cfg = { + "freq_khz": freq_khz, + "sf": sf, + "bw": bw, # kHz + "coding_rate": coding_rate, + "syncword": syncword, + "preamble_len": preamble_len, + "output_power": output_power, # -9dBm ~ 22dBm + } + + self.modem = SX1262( + spi=machine.SPI(1, sck=machine.Pin(20), mosi=machine.Pin(21), miso=machine.Pin(22)), + reset=None, + cs=Pin(pin_cs), + busy=Pin(pin_busy), + dio1=Pin(pin_irq), + dio3_tcxo_millivolts=3300, # 3300mV + lora_cfg=lora_cfg, + ) + self.irq_callback = None + + def _validate_range(self, value, min, max): + if value < min or value > max: + raise ValueError(f"Value {value} out of range {min} to {max}") + + def set_freq(self, freq_khz: int = 868000) -> None: + self._validate_range(freq_khz, 850000, 930000) + lora_cfg = {"freq_khz": freq_khz} + self.modem.configure(lora_cfg) + + def set_sf(self, sf: int) -> None: + self._validate_range(sf, 7, 12) + lora_cfg = {"sf": sf} + self.modem.configure(lora_cfg) + + def set_bw(self, bw: str) -> None: + if bw not in self.BANDWIDTHS: + raise ValueError(f"Invalid bandwidth '{bw}', must be one of {self.BANDWIDTHS}") + lora_cfg = {"bw": bw} + self.modem.configure(lora_cfg) + + def set_coding_rate(self, coding_rate: int) -> None: + self._validate_range(coding_rate, 5, 8) + lora_cfg = {"coding_rate": coding_rate} + self.modem.configure(lora_cfg) + + def set_syncword(self, syncword: int) -> None: + self._validate_range(syncword, 0, 0xFF) + lora_cfg = {"syncword": syncword} + self.modem.configure(lora_cfg) + + def set_preamble_len(self, preamble_len: int) -> None: + self._validate_range(preamble_len, 6, 65535) + lora_cfg = {"preamble_len": preamble_len} + self.modem.configure(lora_cfg) + + def set_output_power(self, output_power: int) -> None: + self._validate_range(output_power, -9, 22) + lora_cfg = {"output_power": output_power} + self.modem.configure(lora_cfg) + + def send(self, packet: str | list | tuple | int | bytearray, tx_at_ms: int = None) -> int: + if isinstance(packet, str): + packet = bytes(packet, "utf-8") + elif isinstance(packet, list | tuple): + packet = bytes(packet) + elif isinstance(packet, int): + packet = bytes([packet]) + return self.modem.send(packet, tx_at_ms) + + def recv( + self, timeout_ms: int = None, rx_length: int = 0xFF, rx_packet: RxPacket = None + ) -> RxPacket: + return self.modem.recv(timeout_ms, rx_length, rx_packet) + + def start_recv(self) -> None: + self.modem.start_recv(continuous=True) + + def set_irq_callback(self, callback) -> None: + self.irq_callback = callback + + def _irq_callback(): + if self.irq_callback: + schedule(self.irq_callback, self.modem.poll_recv()) + + self.modem.set_irq_callback(_irq_callback) + + def standby(self) -> None: + self.modem.standby() + + def sleep(self) -> None: + self.modem.sleep() + + def irq_triggered(self) -> bool: + return self.modem.irq_triggered() diff --git a/m5stack/libs/hardware/manifest.py b/m5stack/libs/hardware/manifest.py index 1a756812..1dca0587 100644 --- a/m5stack/libs/hardware/manifest.py +++ b/m5stack/libs/hardware/manifest.py @@ -11,6 +11,7 @@ "button.py", "can.py", "ir.py", + "lora.py", "matrix_keyboard.py", "plcio.py", "pwr485.py", diff --git a/m5stack/libs/hardware/rgb.py b/m5stack/libs/hardware/rgb.py index e666514a..d164c602 100644 --- a/m5stack/libs/hardware/rgb.py +++ b/m5stack/libs/hardware/rgb.py @@ -61,6 +61,9 @@ def __new__(cls, **kwargs) -> NeoPixel: en(1) cls._instance = WS2812(io=20, n=1) return cls._instance + elif board_id == M5.BOARD.M5UnitC6L: + cls._instance = WS2812(io=2, n=1) + return cls._instance else: pass else: diff --git a/m5stack/modules/startup/__init__.py b/m5stack/modules/startup/__init__.py index c43717d7..9ae5299f 100644 --- a/m5stack/modules/startup/__init__.py +++ b/m5stack/modules/startup/__init__.py @@ -258,6 +258,12 @@ def startup(boot_opt, timeout: int = 60) -> None: tab5 = Tab5_Startup() tab5.startup(ssid, pswd, timeout) + elif board_id == M5.BOARD.M5UnitC6L: + from .unit_c6l import UnitC6L_Startup + + unit_c6l = UnitC6L_Startup() + unit_c6l.startup(ssid, pswd, timeout) + # Only connect to network, not show any menu elif boot_opt is BOOT_OPT_NETWORK: startup = Startup() diff --git a/m5stack/modules/startup/manifest_unit_c6l.py b/m5stack/modules/startup/manifest_unit_c6l.py new file mode 100644 index 00000000..70da3340 --- /dev/null +++ b/m5stack/modules/startup/manifest_unit_c6l.py @@ -0,0 +1,13 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +package( + "startup", + ( + "__init__.py", + "unit_c6l.py", + ), + base_path="..", + opt=3, +) diff --git a/m5stack/modules/startup/unit_c6l.py b/m5stack/modules/startup/unit_c6l.py new file mode 100644 index 00000000..2da4fc01 --- /dev/null +++ b/m5stack/modules/startup/unit_c6l.py @@ -0,0 +1,476 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT +# UnitC6L startup script +import M5 +import time +import network +import machine +import binascii +import os +import sys +import gc +import asyncio +import esp32 +from . import Startup + +try: + import M5Things + + _HAS_SERVER = True +except ImportError: + _HAS_SERVER = False + + +def _draw_textbox(x, y, w, h, r, text, invert=False, is_title=False): + max_chars = 8 + if len(text) > max_chars: + if is_title: + text = text[:3] + "..." + text[-3:] + else: + text = text[:3] + ".." + text[-3:] + M5.Lcd.setFont(M5.Lcd.FONTS.DejaVu9) + text_w = M5.Lcd.textWidth(text) + cursor_x = x + (w - text_w) // 2 + cursor_y = y + (5 if is_title else 3) + M5.Lcd.setCursor(cursor_x, cursor_y) + if invert: + M5.Lcd.fillRoundRect(x, y, w, h, r, M5.Lcd.COLOR.WHITE) + M5.Lcd.setTextColor(M5.Lcd.COLOR.BLACK, M5.Lcd.COLOR.WHITE) + else: + M5.Lcd.fillRoundRect(x, y, w, h, r, M5.Lcd.COLOR.BLACK) + M5.Lcd.drawRoundRect(x, y, w, h, r, M5.Lcd.COLOR.WHITE) + M5.Lcd.setTextColor(M5.Lcd.COLOR.WHITE, M5.Lcd.COLOR.BLACK) + M5.Lcd.print(text) + + +def title_set_text(text, invert=False): + _draw_textbox(0, -3, 64, 18, 4, text, invert, is_title=True) + + +def label1_set_text(text, invert=False): + _draw_textbox(3, 16, 58, 15, 4, text, invert, is_title=False) + + +def label2_set_text(text, invert=False): + _draw_textbox(3, 32, 58, 15, 4, text, invert, is_title=False) + + +class AppBase: + def __init__(self) -> None: + self._task = None + + def on_launch(self): + pass + + def on_view(self): + pass + + def on_ready(self): + self._task = asyncio.create_task(self.on_run()) + + async def on_run(self): + while True: + await asyncio.sleep_ms(500) + + def on_hide(self): + if self._task: + self._task.cancel() + + def on_exit(self): + pass + + async def _keycode_enter_event_handler(self, fw): + pass + + async def _keycode_back_event_handler(self, fw): + pass + + async def _keycode_dpad_down_event_handler(self, fw): + pass + + def set_internal_mode(self, in_internal: bool): + pass + + +# CloudApp - 显示网络连接状态和云服务状态 +class CloudApp(AppBase): + def __init__(self, wifi, ssid) -> None: + super().__init__() + self._wifi = wifi + self._ssid = ssid + self._cloud_status = 0 + + def on_launch(self): + self._cloud_status = self._get_cloud_status() + self._internal = False + + def on_view(self): + M5.Lcd.fillRect(0, 0, 64, 48, M5.Lcd.COLOR.BLACK) + title_set_text("Cloud", False) + label1_set_text(str(self._ssid) if self._ssid else "None", self._internal) + status = self._wifi.connect_status() + status_text = { + network.STAT_GOT_IP: "CONNECTED", + network.STAT_CONNECTING: "CONNECTING", + network.STAT_NO_AP_FOUND: "NO AP", + network.STAT_WRONG_PASSWORD: "WRONG PSK", + network.STAT_HANDSHAKE_TIMEOUT: "HANDSHAKE ERR", + }.get(status, "DISCONNECTED") + label2_set_text(status_text, self._internal) + + async def on_run(self): + while True: + status = self._wifi.connect_status() + if status != self._cloud_status: + self._cloud_status = status + status_text = { + network.STAT_GOT_IP: "CONNECTED", + network.STAT_CONNECTING: "CONNECTING", + network.STAT_NO_AP_FOUND: "NO AP", + network.STAT_WRONG_PASSWORD: "WRONG PSK", + network.STAT_HANDSHAKE_TIMEOUT: "HANDSHAKE ERR", + }.get(status, "DISCONNECTED") + label2_set_text(status_text, self._internal) + await asyncio.sleep_ms(1000) + + def set_internal_mode(self, in_internal: bool): + self._internal = in_internal + label1_set_text(str(self._ssid) if self._ssid else "None", self._internal) + status = self._wifi.connect_status() + status_text = { + network.STAT_GOT_IP: "CONNECTED", + network.STAT_CONNECTING: "CONNECTING", + network.STAT_NO_AP_FOUND: "NO AP", + network.STAT_WRONG_PASSWORD: "WRONG PSK", + network.STAT_HANDSHAKE_TIMEOUT: "HANDSHAKE ERR", + }.get(status, "DISCONNECTED") + label2_set_text(status_text, self._internal) + + def _get_cloud_status(self): + if self._wifi.connect_status() != network.STAT_GOT_IP: + return 0 + if _HAS_SERVER and M5Things.status() == 2: + return 1 + return 0 + + +# RunApp - 显示 main.py 文件信息并提供运行选项 +class RunApp(AppBase): + def __init__(self) -> None: + super().__init__() + self._run_mode = 0 # 0: run once, 1: run always + + def on_launch(self): + self._mtime_text, self._account_text, self._ver_text = self._get_file_info("main.py") + + def on_view(self): + M5.Lcd.fillRect(0, 0, 64, 48, M5.Lcd.COLOR.BLACK) + title_set_text("main.py", False) + label1_set_text("RUN ONCE", self._run_mode == 0) + label2_set_text("ALWAYS", self._run_mode == 1) + + async def _keycode_dpad_down_event_handler(self, fw): + self._run_mode = (self._run_mode + 1) % 2 + self.on_view() + + async def _keycode_enter_event_handler(self, fw): + if self._run_mode == 0: + # Run once + M5.Lcd.clear(0xFFFFFF) + execfile("main.py", {"__name__": "__main__"}) + raise KeyboardInterrupt + else: + # Run always + M5.Lcd.clear(0xFFFFFF) + nvs = esp32.NVS("uiflow") + nvs.set_u8("boot_option", 2) + nvs.commit() + machine.reset() + + @staticmethod + def _get_file_info(path): + mtime = None + account = None + ver = f"UIFLOW2 {esp32.firmware_info()[3]}" + + try: + stat = os.stat(path) + mtime = time.localtime(stat[8]) + except OSError: + pass + + if mtime is None or mtime[0] < 2024: + mtime = "----/--/--" + else: + mtime = "{:04d}/{:d}/{:d}".format(mtime[0] - 2000, mtime[1], mtime[2]) + + if account is None and _HAS_SERVER and M5Things.status() == 2: + infos = M5Things.info() + account = "None" if len(infos[1]) == 0 else infos[1] + else: + account = "None" + + return (mtime, account, ver) + + +# ListApp - 显示 apps 目录下的应用列表 +class ListApp(AppBase): + def __init__(self) -> None: + super().__init__() + self._files = [] + self._file_pos = 0 + + def on_launch(self): + self._files = [] + try: + for file in os.listdir("apps"): + if file.endswith(".py"): + self._files.append(file) + except OSError: + pass + + def on_view(self): + M5.Lcd.fillRect(0, 0, 64, 48, M5.Lcd.COLOR.BLACK) + title_set_text("LIST.APP", False) + + if len(self._files) > 0: + current_file = self._files[self._file_pos] + if len(current_file) > 8: + current_file = current_file[:5] + "..." + label1_set_text(current_file, True) + + if len(self._files) > 1: + next_file = self._files[(self._file_pos + 1) % len(self._files)] + if len(next_file) > 8: + next_file = next_file[:5] + "..." + label2_set_text(next_file, False) + else: + label2_set_text("", False) + else: + label1_set_text("No Apps", False) + label2_set_text("", False) + + async def _keycode_dpad_down_event_handler(self, fw): + if len(self._files) > 0: + self._file_pos = (self._file_pos + 1) % len(self._files) + self.on_view() + + async def _keycode_enter_event_handler(self, fw): + if len(self._files) > 0: + M5.Lcd.clear(0xFFFFFF) + execfile("/".join(["apps/", self._files[self._file_pos]]), {"__name__": "__main__"}) # noqa: F821 + raise KeyboardInterrupt + + +# 框架类 - 直接管理3个app的切换 +class Framework: + def __init__(self, cloud_app, run_app, list_app) -> None: + self._apps = [cloud_app, run_app, list_app] + self._app_names = ["Cloud", "main.py", "LIST.APP"] + self._current_app_index = 0 + self._current_app = None + self._in_app_mode = False # False: 切换app模式, True: app内部模式 + self._last_click_time = 0 + self._click_count = 0 + # 单击/双击判定 + self._pending_single = False + self._last_click_ms = 0 + self._double_window_ms = 350 + # 长按判定 + self._pressing = False + self._press_start_ms = 0 + self._long_fired = False + + async def start(self): + """启动框架,显示第一个app""" + self._current_app = self._apps[self._current_app_index] + self._current_app.on_launch() + self._current_app.on_view() + self._current_app.on_ready() + # 开机默认处于 app 切换模式:单击在三个 app 之间切换 + self._in_app_mode = False + self._update_display() + await self.run() + + async def run(self): + while True: + M5.update() + now_ms = time.ticks_ms() + if M5.BtnA.wasClicked(): + try: + M5.Speaker.tone(666, 100) + except Exception: + pass + # 延迟触发单击:先记录第一次点击,窗口内再次点击视为双击 + if ( + self._pending_single + and time.ticks_diff(now_ms, self._last_click_ms) <= self._double_window_ms + ): + # 双击:取消单击,执行双击动作 + self._pending_single = False + if not self._in_app_mode: + self._in_app_mode = True + self._update_display() + if hasattr(self._current_app, "set_internal_mode"): + try: + self._current_app.set_internal_mode(True) + except Exception: + pass + else: + await self._current_app._keycode_enter_event_handler(self) + else: + # 记录首次点击,等待窗口超时来判定单击 + self._pending_single = True + self._last_click_ms = now_ms + + # 处理长按事件 - 使用持续按下时间判定,避免与单击冲突 + if M5.BtnA.isPressed(): + if not self._pressing: + self._pressing = True + self._press_start_ms = now_ms + self._long_fired = False + else: + if ( + not self._long_fired + and time.ticks_diff(now_ms, self._press_start_ms) > 700 + ): + self._long_fired = True + if self._in_app_mode: + try: + M5.Speaker.tone(666, 200) + except Exception: + pass + self._in_app_mode = False + self._pending_single = False + self._update_display() + if hasattr(self._current_app, "set_internal_mode"): + try: + self._current_app.set_internal_mode(False) + except Exception: + pass + else: + self._pressing = False + self._long_fired = False + + # 单击延迟触发:超过双击窗口且仍挂起,则执行单击动作 + if ( + self._pending_single + and time.ticks_diff(now_ms, self._last_click_ms) > self._double_window_ms + ): + if not self._in_app_mode: + await self._switch_to_next_app() + else: + await self._current_app._keycode_dpad_down_event_handler(self) + self._update_display() + self._pending_single = False + + await asyncio.sleep_ms(100) + + async def _switch_to_next_app(self): + """切换到下一个app""" + if self._current_app: + self._current_app.on_hide() + self._current_app_index = (self._current_app_index + 1) % len(self._apps) + self._current_app = self._apps[self._current_app_index] + self._in_app_mode = False + self._current_app.on_launch() + self._current_app.on_view() + self._current_app.on_ready() + + def _update_display(self): + """更新显示,根据模式显示不同的标题状态""" + if self._current_app: + if self._in_app_mode: + title_set_text(self._app_names[self._current_app_index], True) + else: + title_set_text(self._app_names[self._current_app_index], False) + + +# UnitC6L startup menu +class UnitC6L_Startup(Startup): + def __init__(self) -> None: + super().__init__() + + def show_hits(self, hits: str) -> None: + print(hits) + + def show_msg(self, msg: str) -> None: + print(msg) + + def show_ssid(self, ssid: str) -> None: + if len(ssid) > 9: + self.show_msg(ssid[:7] + "...") + else: + self.show_msg(ssid) + + def show_mac(self) -> None: + mac = binascii.hexlify(machine.unique_id()).decode("utf-8").upper() + print(mac[0:6] + "_" + mac[6:]) + + def show_error(self, ssid: str, error: str) -> None: + self.show_ssid(ssid) + self.show_hits(error) + self.show_mac() + print("SSID: " + ssid + "\r\nNotice: " + error) + + def startup(self, ssid: str, pswd: str, timeout: int = 60) -> None: + M5.Speaker.begin() + M5.Speaker.setVolumePercentage(1) + # 显示启动画面 + M5.Lcd.fillRect(0, 0, 64, 48, M5.Lcd.COLOR.BLACK) + M5.Lcd.setFont(M5.Lcd.FONTS.DejaVu12) + M5.Lcd.fillRect(0, 0, 64, 15, M5.Lcd.COLOR.WHITE) + M5.Lcd.setTextColor(M5.Lcd.COLOR.BLACK, M5.Lcd.COLOR.WHITE) + M5.Lcd.drawCenterString("UiFlow2", 32, 2) + M5.Lcd.setTextColor(M5.Lcd.COLOR.WHITE, M5.Lcd.COLOR.BLACK) + M5.Lcd.drawCenterString("Unit C6L", 32, 26) + M5.Speaker.tone(888, 200) + self.show_mac() + # 连接网络 + if super().connect_network(ssid=ssid, pswd=pswd): + self.show_ssid(ssid) + count = 1 + status = super().connect_status() + start = time.time() + while status is not network.STAT_GOT_IP: + time.sleep_ms(300) + if status is network.STAT_NO_AP_FOUND: + self.show_error(ssid, "NO AP FOUND") + break + elif status is network.STAT_WRONG_PASSWORD: + self.show_error(ssid, "WRONG PASSWORD") + break + elif status is network.STAT_HANDSHAKE_TIMEOUT: + self.show_error(ssid, "HANDSHAKE ERR") + break + elif status is network.STAT_CONNECTING: + self.show_hits("." * count) + count = count + 1 + if count > 5: + count = 1 + status = super().connect_status() + # connect to network timeout + if (time.time() - start) > timeout: + self.show_error(ssid, "TIMEOUT") + break + + if status is network.STAT_GOT_IP: + self.show_hits(super().local_ip()) + print("Local IP: " + super().local_ip()) + else: + self.show_error("Not Found", "Use Burner setup") + self._start_menu_system(ssid) + + def _start_menu_system(self, ssid): + """启动app切换系统""" + try: + cloud_app = CloudApp(self, ssid) + run_app = RunApp() + list_app = ListApp() + fw = Framework(cloud_app, run_app, list_app) + asyncio.run(fw.start()) + except KeyboardInterrupt: + pass + except Exception as e: + print(f"App system error: {e}") diff --git a/m5stack/patches/2007-Support-UnitC6L.patch b/m5stack/patches/2007-Support-UnitC6L.patch new file mode 100644 index 00000000..acdf7282 --- /dev/null +++ b/m5stack/patches/2007-Support-UnitC6L.patch @@ -0,0 +1,855 @@ +Index: M5Unified/src/M5Unified.cpp +=================================================================== +--- M5Unified.orig/src/M5Unified.cpp ++++ M5Unified/src/M5Unified.cpp +@@ -88,6 +88,7 @@ static constexpr const uint8_t _pin_tabl + { board_t::board_unknown , 255 ,255 , GPIO_NUM_0 ,GPIO_NUM_1 }, + #elif defined (CONFIG_IDF_TARGET_ESP32C6) + { board_t::board_ArduinoNessoN1,GPIO_NUM_8 ,GPIO_NUM_10 , GPIO_NUM_8 ,GPIO_NUM_10 }, ++{ board_t::board_M5UnitC6L , 255 , 255 , 255 ,255 }, + { board_t::board_unknown , 255 ,255 , GPIO_NUM_1 ,GPIO_NUM_2 }, // NanoC6 + #elif defined (CONFIG_IDF_TARGET_ESP32P4) + { board_t::board_M5Tab5 , GPIO_NUM_32,GPIO_NUM_31, GPIO_NUM_54,GPIO_NUM_53 }, // Tab5 +@@ -115,6 +116,7 @@ static constexpr const uint8_t _pin_tabl + #elif defined (CONFIG_IDF_TARGET_ESP32C3) + #elif defined (CONFIG_IDF_TARGET_ESP32C6) + { board_t::board_ArduinoNessoN1,GPIO_NUM_4 ,GPIO_NUM_5 , 255 ,255 }, ++{ board_t::board_M5UnitC6L ,GPIO_NUM_4 ,GPIO_NUM_5 , 255 ,255 }, + #elif defined (CONFIG_IDF_TARGET_ESP32P4) + { board_t::board_M5Tab5 , GPIO_NUM_17,GPIO_NUM_52, GPIO_NUM_7 ,GPIO_NUM_6 }, // Tab5 + #else +@@ -1320,6 +1322,36 @@ static constexpr const uint8_t _pin_tabl + _io_expander[i].reset(ioexp); + } + break; ++ case board_t::board_M5UnitC6L: ++ { ++ In_SoftI2C.begin(10, 8); ++ auto ioexp = new PI4IOE5V6408_Class(0x43); ++ ioexp->begin(); ++ _io_expander[0].reset(ioexp); ++ // user button(P0) input pullup ++ _io_expander[0]->setDirection(0, false); ++ _io_expander[0]->setPullMode(0, true); ++ _io_expander[0]->setHighImpedance(0, false); ++ // sx1262 reset(P7) ++ _io_expander[0]->setDirection(7, true); ++ _io_expander[0]->setPullMode(7, false); ++ _io_expander[0]->setHighImpedance(7, false); ++ _io_expander[0]->digitalWrite(7, false); ++ delay(10); ++ _io_expander[0]->digitalWrite(7, true); ++ delay(10); ++ // LAN EN ++ _io_expander[0]->setDirection(5, true); ++ _io_expander[0]->setPullMode(5, false); ++ _io_expander[0]->setHighImpedance(5, false); ++ _io_expander[0]->digitalWrite(5, true); ++ // SW EN ++ _io_expander[0]->setDirection(6, true); ++ _io_expander[0]->setPullMode(6, false); ++ _io_expander[0]->setHighImpedance(6, false); ++ _io_expander[0]->digitalWrite(6, true); ++ } ++ break; + #elif defined (CONFIG_IDF_TARGET_ESP32S3) + case board_t::board_M5StampPLC: + { +@@ -1679,7 +1711,14 @@ static constexpr const uint8_t _pin_tabl + spk_cfg.magnification = 48; + } + break; +- ++ case board_t::board_M5UnitC6L: ++ if (cfg.internal_spk) ++ { ++ spk_cfg.pin_data_out = GPIO_NUM_11; ++ spk_cfg.buzzer = true; ++ spk_cfg.magnification = 16; ++ } ++ break; + #elif defined (CONFIG_IDF_TARGET_ESP32P4) + case board_t::board_M5Tab5: + if (cfg.internal_spk) +@@ -2307,7 +2346,14 @@ static constexpr const uint8_t _pin_tabl + ; + break; + } +- ++ case board_t::board_M5UnitC6L: ++ { ++ use_rawstate_bits = 0b00001; ++ auto exp = static_cast(_io_expander[0].get()); ++ uint8_t value = exp->readRegister8(0x0F); ++ btn_rawstate_bits = (!(value & 0b00001) ? 0b00001 : 0); // BtnA ++ break; ++ } + default: + break; + } +Index: M5Unified/src/utility/IOExpander_Base.hpp +=================================================================== +--- M5Unified.orig/src/utility/IOExpander_Base.hpp ++++ M5Unified/src/utility/IOExpander_Base.hpp +@@ -6,15 +6,28 @@ + + #include + #include "I2C_Class.hpp" ++#if CONFIG_IDF_TARGET_ESP32C6 ++#include "SoftI2C_Class.hpp" ++#endif + + namespace m5 + { ++ ++#if CONFIG_IDF_TARGET_ESP32C6 ++ class IOExpander_Base : public SoftI2C_Device ++ { ++ public: ++ IOExpander_Base(std::uint8_t i2c_addr, std::uint32_t freq = 400000, m5::SoftI2C_Class* i2c = &m5::In_SoftI2C) ++ : SoftI2C_Device(i2c_addr, freq, i2c) ++ {} ++#else + class IOExpander_Base : public I2C_Device + { + public: + IOExpander_Base(std::uint8_t i2c_addr, std::uint32_t freq = 400000, m5::I2C_Class* i2c = &m5::In_I2C) + : I2C_Device(i2c_addr, freq, i2c) + {} ++#endif + IOExpander_Base(const IOExpander_Base&) = delete; + + // false input, true output +Index: M5Unified/src/utility/PI4IOE5V6408_Class.hpp +=================================================================== +--- M5Unified.orig/src/utility/PI4IOE5V6408_Class.hpp ++++ M5Unified/src/utility/PI4IOE5V6408_Class.hpp +@@ -15,6 +15,9 @@ + + #include "IOExpander_Base.hpp" + #include "I2C_Class.hpp" ++#if CONFIG_IDF_TARGET_ESP32C6 ++#include "SoftI2C_Class.hpp" ++#endif + + namespace m5 + { +@@ -24,9 +27,15 @@ namespace m5 + public: + static constexpr std::uint8_t DEFAULT_ADDRESS = 0x43; + ++#if CONFIG_IDF_TARGET_ESP32C6 ++ PI4IOE5V6408_Class(std::uint8_t i2c_addr = DEFAULT_ADDRESS, std::uint32_t freq = 400000, m5::SoftI2C_Class* i2c = &m5::In_SoftI2C) ++ : IOExpander_Base(i2c_addr, freq, i2c) ++ {} ++#else + PI4IOE5V6408_Class(std::uint8_t i2c_addr = DEFAULT_ADDRESS, std::uint32_t freq = 400000, m5::I2C_Class* i2c = &m5::In_I2C) + : IOExpander_Base(i2c_addr, freq, i2c) + {} ++#endif + + bool begin(); + +Index: M5Unified/src/utility/SoftI2C_Class.cpp +=================================================================== +--- /dev/null ++++ M5Unified/src/utility/SoftI2C_Class.cpp +@@ -0,0 +1,479 @@ ++// Copyright (c) M5Stack. All rights reserved. ++// Licensed under the MIT license. See LICENSE file in the project root for full license information. ++ ++#include "SoftI2C_Class.hpp" ++#include ++#include "driver/gpio.h" ++#include "esp_rom_sys.h" ++ ++ ++namespace m5 ++{ ++ SoftI2C_Class In_SoftI2C; ++ ++ SoftI2C_Class::SoftI2C_Class() ++ { ++ _pin_sda = -1; ++ _pin_scl = -1; ++ _freq = 100000; // default 100kHz ++ _delay_us = 5; ++ } ++ ++ SoftI2C_Class::~SoftI2C_Class() ++ { ++ release(); ++ } ++ ++ void SoftI2C_Class::setPins(int pin_sda, int pin_scl) ++ { ++ _pin_sda = pin_sda; ++ _pin_scl = pin_scl; ++ } ++ ++ bool SoftI2C_Class::begin(int pin_sda, int pin_scl) ++ { ++ setPins(pin_sda, pin_scl); ++ return begin(); ++ } ++ ++ bool SoftI2C_Class::begin(void) ++ { ++ if (_pin_sda < 0 || _pin_scl < 0) { ++ return false; ++ } ++ ++ gpio_config_t scl_conf = {}; ++ scl_conf.pin_bit_mask = (1ULL << _pin_scl); ++ scl_conf.mode = GPIO_MODE_OUTPUT_OD; ++ scl_conf.pull_up_en = GPIO_PULLUP_ENABLE; ++ scl_conf.pull_down_en = GPIO_PULLDOWN_DISABLE; ++ scl_conf.intr_type = GPIO_INTR_DISABLE; ++ esp_err_t ret = gpio_config(&scl_conf); ++ if (ret != ESP_OK) { ++ return false; ++ } ++ ++ gpio_config_t sda_conf = {}; ++ sda_conf.pin_bit_mask = (1ULL << _pin_sda); ++ sda_conf.mode = GPIO_MODE_INPUT_OUTPUT_OD; ++ sda_conf.pull_up_en = GPIO_PULLUP_ENABLE; ++ sda_conf.pull_down_en = GPIO_PULLDOWN_DISABLE; ++ sda_conf.intr_type = GPIO_INTR_DISABLE; ++ ret = gpio_config(&sda_conf); ++ if (ret != ESP_OK) { ++ return false; ++ } ++ ++ _delay_us = (uint32_t)((1e6f / _freq) / 2.0f + 0.5f); ++ if (_delay_us < 1) _delay_us = 1; ++ ++ return true; ++ } ++ ++ bool SoftI2C_Class::release(void) const ++ { ++ if (_pin_sda >= 0) { ++ gpio_set_direction((gpio_num_t)_pin_sda, GPIO_MODE_INPUT); ++ } ++ if (_pin_scl >= 0) { ++ gpio_set_direction((gpio_num_t)_pin_scl, GPIO_MODE_INPUT); ++ } ++ return true; ++ } ++ ++ void SoftI2C_Class::_delay() const ++ { ++ esp_rom_delay_us(_delay_us); ++ } ++ ++ void SoftI2C_Class::_scl_high() const ++ { ++ gpio_set_level((gpio_num_t)_pin_scl, 1); ++ } ++ ++ void SoftI2C_Class::_scl_low() const ++ { ++ gpio_set_level((gpio_num_t)_pin_scl, 0); ++ } ++ ++ void SoftI2C_Class::_sda_high() const ++ { ++ gpio_set_level((gpio_num_t)_pin_sda, 1); ++ } ++ ++ void SoftI2C_Class::_sda_low() const ++ { ++ gpio_set_level((gpio_num_t)_pin_sda, 0); ++ } ++ ++ bool SoftI2C_Class::_sda_read() const ++ { ++ return gpio_get_level((gpio_num_t)_pin_sda) == 1; ++ } ++ ++ bool SoftI2C_Class::_i2c_start() const ++ { ++ _sda_high(); ++ _scl_high(); ++ _delay(); ++ ++ _sda_low(); ++ _delay(); ++ ++ _scl_low(); ++ _delay(); ++ ++ return true; ++ } ++ ++ bool SoftI2C_Class::_i2c_stop() const ++ { ++ _sda_low(); ++ _delay(); ++ ++ _scl_high(); ++ _delay(); ++ ++ _sda_high(); ++ _delay(); ++ ++ return true; ++ } ++ ++ bool SoftI2C_Class::_i2c_write_byte(uint8_t data) const ++ { ++ for (int i = 7; i >= 0; i--) { ++ if (data & (1 << i)) { ++ _sda_high(); ++ } else { ++ _sda_low(); ++ } ++ _delay(); ++ ++ _scl_high(); ++ _delay(); ++ ++ _scl_low(); ++ _delay(); ++ } ++ ++ _sda_high(); ++ _delay(); ++ ++ _scl_high(); ++ _delay(); ++ ++ bool ack = !_sda_read(); ++ ++ _scl_low(); ++ _delay(); ++ ++ return ack; ++ } ++ ++ uint8_t SoftI2C_Class::_i2c_read_byte(bool ack) const ++ { ++ uint8_t data = 0; ++ ++ _sda_high(); ++ ++ for (int i = 7; i >= 0; i--) { ++ _scl_high(); ++ _delay(); ++ ++ if (_sda_read()) { ++ data |= (1 << i); ++ } ++ ++ _scl_low(); ++ _delay(); ++ } ++ ++ if (ack) { ++ _sda_low(); ++ } else { ++ _sda_high(); ++ } ++ _delay(); ++ ++ _scl_high(); ++ _delay(); ++ _scl_low(); ++ _delay(); ++ ++ return data; ++ } ++ ++ bool SoftI2C_Class::start(std::uint8_t address, bool read, std::uint32_t freq) const ++ { ++ if (_pin_sda < 0 || _pin_scl < 0) { ++ return false; ++ } ++ ++ if (freq > 0) { ++ _delay_us = (uint32_t)((1e6f / freq) / 2.0f + 0.5f); ++ if (_delay_us < 1) _delay_us = 1; ++ } ++ ++ if (!_i2c_start()) { ++ return false; ++ } ++ ++ uint8_t address_byte = (address << 1) | (read ? 1 : 0); ++ if (!_i2c_write_byte(address_byte)) { ++ _i2c_stop(); ++ return false; ++ } ++ ++ return true; ++ } ++ ++ bool SoftI2C_Class::restart(std::uint8_t address, bool read, std::uint32_t freq) const ++ { ++ return start(address, read, freq); ++ } ++ ++ bool SoftI2C_Class::stop(void) const ++ { ++ if (_pin_sda < 0 || _pin_scl < 0) { ++ return false; ++ } ++ ++ return _i2c_stop(); ++ } ++ ++ bool SoftI2C_Class::write(std::uint8_t data) const ++ { ++ if (_pin_sda < 0 || _pin_scl < 0) { ++ return false; ++ } ++ ++ return _i2c_write_byte(data); ++ } ++ ++ bool SoftI2C_Class::write(const std::uint8_t* data, std::size_t length) const ++ { ++ if (_pin_sda < 0 || _pin_scl < 0) { ++ return false; ++ } ++ ++ if (!data || length == 0) { ++ return true; ++ } ++ ++ for (std::size_t i = 0; i < length; i++) { ++ if (!_i2c_write_byte(data[i])) { ++ return false; ++ } ++ } ++ ++ return true; ++ } ++ ++ bool SoftI2C_Class::read(std::uint8_t* result, std::size_t length, bool last_nack) const ++ { ++ if (_pin_sda < 0 || _pin_scl < 0) { ++ return false; ++ } ++ ++ if (!result || length == 0) { ++ return true; ++ } ++ ++ for (std::size_t i = 0; i < length; i++) { ++ bool send_ack = (i != length - 1) || !last_nack; ++ result[i] = _i2c_read_byte(send_ack); ++ } ++ ++ return true; ++ } ++ ++ bool SoftI2C_Class::writeRegister(std::uint8_t address, std::uint8_t reg, const std::uint8_t* data, std::size_t length, std::uint32_t freq) const ++ { ++ if (_pin_sda < 0 || _pin_scl < 0) { ++ return false; ++ } ++ ++ if (freq > 0) { ++ _delay_us = (uint32_t)((1e6f / freq) / 2.0f + 0.5f); ++ if (_delay_us < 1) _delay_us = 1; ++ } ++ ++ if (!_i2c_start()) { ++ return false; ++ } ++ ++ uint8_t address_byte = (address << 1) | 0; ++ if (!_i2c_write_byte(address_byte)) { ++ _i2c_stop(); ++ return false; ++ } ++ ++ if (!_i2c_write_byte(reg)) { ++ _i2c_stop(); ++ return false; ++ } ++ ++ for (std::size_t i = 0; i < length; i++) { ++ if (!_i2c_write_byte(data[i])) { ++ _i2c_stop(); ++ return false; ++ } ++ } ++ ++ // 发送 STOP 信号 ++ _i2c_stop(); ++ return true; ++ } ++ ++ bool SoftI2C_Class::readRegister(std::uint8_t address, std::uint8_t reg, std::uint8_t* result, std::size_t length, std::uint32_t freq) const ++ { ++ if (_pin_sda < 0 || _pin_scl < 0) { ++ return false; ++ } ++ ++ if (freq > 0) { ++ _delay_us = (uint32_t)((1e6f / freq) / 2.0f + 0.5f); ++ if (_delay_us < 1) _delay_us = 1; ++ } ++ ++ if (!_i2c_start()) { ++ return false; ++ } ++ ++ uint8_t address_byte = (address << 1) | 0; ++ if (!_i2c_write_byte(address_byte)) { ++ _i2c_stop(); ++ return false; ++ } ++ ++ if (!_i2c_write_byte(reg)) { ++ _i2c_stop(); ++ return false; ++ } ++ ++ if (!_i2c_start()) { ++ return false; ++ } ++ ++ address_byte = (address << 1) | 1; ++ if (!_i2c_write_byte(address_byte)) { ++ _i2c_stop(); ++ return false; ++ } ++ ++ for (std::size_t i = 0; i < length; i++) { ++ bool send_ack = (i != length - 1); ++ result[i] = _i2c_read_byte(send_ack); ++ } ++ ++ _i2c_stop(); ++ return true; ++ } ++ ++ bool SoftI2C_Class::writeRegister8(std::uint8_t address, std::uint8_t reg, std::uint8_t data, std::uint32_t freq) const ++ { ++ return writeRegister(address, reg, &data, 1, freq); ++ } ++ ++ std::uint8_t SoftI2C_Class::readRegister8(std::uint8_t address, std::uint8_t reg, std::uint32_t freq) const ++ { ++ std::uint8_t result = 0; ++ if (!readRegister(address, reg, &result, 1, freq)) { ++ return 0; ++ } ++ return result; ++ } ++ ++ bool SoftI2C_Class::bitOn(std::uint8_t address, std::uint8_t reg, std::uint8_t data, std::uint32_t freq) const ++ { ++ std::uint8_t current = readRegister8(address, reg, freq); ++ if (current == 0 && !readRegister(address, reg, ¤t, 1, freq)) { ++ return false; ++ } ++ return writeRegister8(address, reg, current | data, freq); ++ } ++ ++ bool SoftI2C_Class::bitOff(std::uint8_t address, std::uint8_t reg, std::uint8_t data, std::uint32_t freq) const ++ { ++ std::uint8_t current = readRegister8(address, reg, freq); ++ if (current == 0 && !readRegister(address, reg, ¤t, 1, freq)) { ++ return false; ++ } ++ return writeRegister8(address, reg, current & ~data, freq); ++ } ++ ++ void SoftI2C_Class::scanID(bool* result, std::uint32_t freq) const ++ { ++ if (!result) { ++ return; ++ } ++ ++ std::memset(result, false, 120); ++ ++ if (_pin_sda < 0 || _pin_scl < 0) { ++ return; ++ } ++ ++ if (freq > 0) { ++ _delay_us = (uint32_t)((1e6f / freq) / 2.0f + 0.5f); ++ if (_delay_us < 1) _delay_us = 1; ++ } ++ ++ for (int addr = 0x03; addr < 0x78; addr++) { ++ if (scanID(addr, freq)) { ++ result[addr] = true; ++ } ++ } ++ } ++ ++ bool SoftI2C_Class::scanID(uint8_t addr, std::uint32_t freq) const ++ { ++ if (_pin_sda < 0 || _pin_scl < 0) { ++ return false; ++ } ++ ++ if (freq > 0) { ++ _delay_us = (uint32_t)((1e6f / freq) / 2.0f + 0.5f); ++ if (_delay_us < 1) _delay_us = 1; ++ } ++ ++ if (!_i2c_start()) { ++ return false; ++ } ++ ++ uint8_t address_byte = (addr << 1) | 0; ++ bool ack = _i2c_write_byte(address_byte); ++ ++ _i2c_stop(); ++ ++ return ack; ++ } ++ ++ bool SoftI2C_Device::writeRegister8Array(const std::uint8_t* reg_data_array, std::size_t length) const ++ { ++ if (!_i2c || !reg_data_array || length == 0) { ++ return false; ++ } ++ ++ if (!_i2c->start(_addr, false, _freq)) { ++ return false; ++ } ++ ++ for (std::size_t i = 0; i < length; i += 2) { ++ if (i + 1 < length) { ++ if (!_i2c->write(reg_data_array[i])) { ++ _i2c->stop(); ++ return false; ++ } ++ if (!_i2c->write(reg_data_array[i + 1])) { ++ _i2c->stop(); ++ return false; ++ } ++ } ++ } ++ ++ return _i2c->stop(); ++ } ++ ++} // namespace m5 +Index: M5Unified/src/utility/SoftI2C_Class.hpp +=================================================================== +--- /dev/null ++++ M5Unified/src/utility/SoftI2C_Class.hpp +@@ -0,0 +1,214 @@ ++// Copyright (c) M5Stack. All rights reserved. ++// Licensed under the MIT license. See LICENSE file in the project root for full license information. ++ ++#ifndef __M5_SOFT_I2C_CLASS_H__ ++#define __M5_SOFT_I2C_CLASS_H__ ++ ++#include ++#include ++#include ++ ++namespace m5 ++{ ++ class SoftI2C_Class ++ { ++ public: ++ /// Constructor ++ SoftI2C_Class(); ++ ++ /// Destructor ++ ~SoftI2C_Class(); ++ ++ /// setup I2C pin parameters. (No begin) ++ /// @param pin_sda SDA pin number. ++ /// @param pin_scl SCL pin number. ++ void setPins(int pin_sda, int pin_scl); ++ ++ /// setup and begin I2C peripheral. (No communication is performed.) ++ /// @param pin_sda SDA pin number. ++ /// @param pin_scl SCL pin number. ++ /// @return success(true) or failed(false). ++ bool begin(int pin_sda, int pin_scl); ++ ++ /// begin I2C peripheral. (No communication is performed.) ++ /// @return success(true) or failed(false). ++ bool begin(void); ++ ++ /// release I2C peripheral. ++ /// @return success(true) or failed(false). ++ bool release(void) const; ++ ++ /// Sends the I2C start condition and the address of the slave. ++ /// @param address slave addr. ++ /// @param read bit of read flag. true=read / false=write. ++ /// @return success(true) or failed(false). ++ bool start(std::uint8_t address, bool read, std::uint32_t freq) const; ++ ++ /// Sends the I2C repeated start condition and the address of the slave. ++ /// @param address slave addr. ++ /// @param read bit of read flag. true=read / false=write. ++ /// @return success(true) or failed(false). ++ bool restart(std::uint8_t address, bool read, std::uint32_t freq) const; ++ ++ /// Sends the I2C stop condition. ++ /// If an ACK error occurs, return false. ++ /// @return success(true) or failed(false). ++ bool stop(void) const; ++ ++ /// Send 1 byte of data. ++ /// @param data write data. ++ /// @return success(true) or failed(false). ++ bool write(std::uint8_t data) const; ++ ++ /// Send multiple bytes of data. ++ /// @param[in] data write data array. ++ /// @param length data array length. ++ /// @return success(true) or failed(false). ++ bool write(const std::uint8_t* data, std::size_t length) const; ++ ++ /// Receive multiple bytes of data. ++ /// @param[out] result read data array. ++ /// @param length data array length. ++ /// @return success(true) or failed(false). ++ bool read(std::uint8_t* result, std::size_t length, bool last_nack = false) const; ++ ++ //---------- ++ ++ /// Write multiple bytes value to the register. Performs a series of communications from START to STOP. ++ /// @param address slave addr. ++ /// @param reg register number. ++ /// @param[in] data write data array. ++ /// @param length data array length. ++ /// @return success(true) or failed(false). ++ bool writeRegister(std::uint8_t address, std::uint8_t reg, const std::uint8_t* data, std::size_t length, std::uint32_t freq) const; ++ ++ /// Read multiple bytes value from the register. Performs a series of communications from START to STOP. ++ /// @param address slave addr. ++ /// @param reg register number. ++ /// @param[out] result read data array. ++ /// @param length data array length. ++ /// @return success(true) or failed(false). ++ bool readRegister(std::uint8_t address, std::uint8_t reg, std::uint8_t* result, std::size_t length, std::uint32_t freq) const; ++ ++ /// Write a 1-byte value to the register. Performs a series of communications from START to STOP. ++ /// @param address slave addr. ++ /// @param reg register number. ++ /// @param data write data. ++ /// @return success(true) or failed(false). ++ bool writeRegister8(std::uint8_t address, std::uint8_t reg, std::uint8_t data, std::uint32_t freq) const; ++ ++ /// Read a 1-byte value from the register. Performs a series of communications from START to STOP. ++ /// @param address slave addr. ++ /// @param reg register number. ++ /// @return read value. ++ std::uint8_t readRegister8(std::uint8_t address, std::uint8_t reg, std::uint32_t freq) const; ++ ++ /// Write a 1-byte value to the register by bit add operation. Performs a series of communications from START to STOP. ++ /// @param address slave addr. ++ /// @param reg register number. ++ /// @param data add bit data. ++ /// @return success(true) or failed(false). ++ bool bitOn(std::uint8_t address, std::uint8_t reg, std::uint8_t data, std::uint32_t freq) const; ++ ++ /// Write a 1-byte value to the register by bit erase operation. Performs a series of communications from START to STOP. ++ /// @param address slave addr. ++ /// @param reg register number. ++ /// @param data erase bit data. ++ /// @return success(true) or failed(false). ++ bool bitOff(std::uint8_t address, std::uint8_t reg, std::uint8_t data, std::uint32_t freq) const; ++ ++ /// execute I2C scan. (for 7bit address) ++ /// @param[out] result data array needs 120 Bytes. ++ void scanID(bool* result, std::uint32_t freq = 100000) const; ++ ++ bool scanID(uint8_t addr, std::uint32_t freq = 100000) const; ++ ++ int8_t getSDA(void) const { return _pin_sda; } ++ int8_t getSCL(void) const { return _pin_scl; } ++ uint32_t getFrequency(void) const { return _freq; } ++ ++ bool isEnabled(void) const { return _pin_sda >= 0 && _pin_scl >= 0; } ++ ++ private: ++ int8_t _pin_sda = -1; ++ int8_t _pin_scl = -1; ++ uint32_t _freq = 100000; ++ mutable uint32_t _delay_us = 5; ++ ++ void _delay() const; ++ void _scl_high() const; ++ void _scl_low() const; ++ void _sda_high() const; ++ void _sda_low() const; ++ bool _sda_read() const; ++ bool _i2c_start() const; ++ bool _i2c_stop() const; ++ bool _i2c_write_byte(uint8_t data) const; ++ uint8_t _i2c_read_byte(bool ack) const; ++ }; ++ ++ /// for internal I2C device ++ extern SoftI2C_Class In_SoftI2C; ++ ++ class SoftI2C_Device ++ { ++ public: ++ SoftI2C_Device(std::uint8_t i2c_addr, std::uint32_t freq, SoftI2C_Class* i2c = &In_SoftI2C) ++ : _i2c { i2c } ++ , _freq { freq } ++ , _addr { i2c_addr } ++ , _init { false } ++ {} ++ ++ void setPort(SoftI2C_Class* i2c) { _i2c = i2c; } ++ ++ void setClock(std::uint32_t freq) { _freq = freq; } ++ ++ void setAddress(std::uint8_t i2c_addr) { _addr = i2c_addr; } ++ ++ std::uint8_t getAddress(void) const { return _addr; } ++ ++ bool writeRegister8(std::uint8_t reg, std::uint8_t data) const ++ { ++ return _i2c->writeRegister8(_addr, reg, data, _freq); ++ } ++ ++ std::uint8_t readRegister8(std::uint8_t reg) const ++ { ++ return _i2c->readRegister8(_addr, reg, _freq); ++ } ++ ++ bool writeRegister8Array(const std::uint8_t* reg_data_array, std::size_t length) const; ++ ++ bool writeRegister(std::uint8_t reg, const std::uint8_t* data, std::size_t length) const ++ { ++ return _i2c->writeRegister(_addr, reg, data, length, _freq); ++ } ++ ++ bool readRegister(std::uint8_t reg, std::uint8_t* result, std::size_t length) const ++ { ++ return _i2c->readRegister(_addr, reg, result, length, _freq); ++ } ++ ++ bool bitOn(std::uint8_t reg, std::uint8_t bit) const ++ { ++ return _i2c->bitOn(_addr, reg, bit, _freq); ++ } ++ ++ bool bitOff(std::uint8_t reg, std::uint8_t bit) const ++ { ++ return _i2c->bitOff(_addr, reg, bit, _freq); ++ } ++ ++ bool isEnabled(void) const { return _init; } ++ ++ protected: ++ SoftI2C_Class *_i2c; ++ std::uint32_t _freq; ++ std::uint8_t _addr; ++ bool _init; ++ }; ++ ++} ++ ++#endif diff --git a/pyproject.toml b/pyproject.toml index cc6da16e..1a1844e6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -26,11 +26,12 @@ ignore = [ "F401", "F403", "F405", + "F821", "PLC1901", ] [tool.ruff.lint.mccabe] -max-complexity = 40 +max-complexity = 50 [tool.ruff.lint.per-file-ignores] # manifest.py files are evaluated with some global names pre-defined diff --git a/tools/ci.sh b/tools/ci.sh index 9c861e1e..41b50106 100644 --- a/tools/ci.sh +++ b/tools/ci.sh @@ -252,6 +252,7 @@ function ci_esp32_quick_build { make ${MAKEOPTS} -C m5stack BOARD=M5STACK_NanoC6 pack_all make ${MAKEOPTS} -C m5stack BOARD=M5STACK_Stamp_PICO pack_all make ${MAKEOPTS} -C m5stack BOARD=M5STACK_StickC pack_all + make ${MAKEOPTS} -C m5stack BOARD=M5STACK_Unit_C6L pack_all make ${MAKEOPTS} -C third-party BOARD=SEEED_STUDIO_XIAO_ESP32S3 pack_all } @@ -365,6 +366,7 @@ function ci_esp32_nightly_build { make ${MAKEOPTS} -C m5stack BOARD=M5STACK_StickC_PLUS2 pack_all make ${MAKEOPTS} -C m5stack BOARD=M5STACK_Tab5 pack_all make ${MAKEOPTS} -C m5stack BOARD=M5STACK_Tough pack_all + make ${MAKEOPTS} -C m5stack BOARD=M5STACK_Unit_C6L pack_all make ${MAKEOPTS} -C third-party BOARD=ESPRESSIF_ESP32_S3_BOX_3 pack_all make ${MAKEOPTS} -C third-party BOARD=SEEED_STUDIO_XIAO_ESP32S3 pack_all } From b7a5f8d4aafc4620a9969f4f7dbc3d4f1adc1001 Mon Sep 17 00:00:00 2001 From: lbuque <1102390310@qq.com> Date: Tue, 30 Sep 2025 15:38:02 +0800 Subject: [PATCH 279/322] docs: Update Link. Signed-off-by: lbuque <1102390310@qq.com> --- docs/en/module/gateway_h2.rst | 2 +- docs/en/units/gateway_h2.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/en/module/gateway_h2.rst b/docs/en/module/gateway_h2.rst index 1368a302..a822f5a0 100644 --- a/docs/en/module/gateway_h2.rst +++ b/docs/en/module/gateway_h2.rst @@ -11,7 +11,7 @@ Support the following products: |Module Gateway H2| -.. note:: When using this module, you need to flash the NCP firmware to the module. For details, refer to the `ESP Zigbee NCP `_ documentation. +.. note:: When using this module, you need to flash the NCP firmware to the module. For details, refer to the `ESP Zigbee NCP `_ documentation. UiFlow2 Example --------------- diff --git a/docs/en/units/gateway_h2.rst b/docs/en/units/gateway_h2.rst index 86f69362..f5056234 100644 --- a/docs/en/units/gateway_h2.rst +++ b/docs/en/units/gateway_h2.rst @@ -11,7 +11,7 @@ Support the following products: |Unit Gateway H2| -.. note:: When using this unit, you need to flash the NCP firmware to the unit. For details, refer to the `ESP Zigbee NCP `_ documentation. +.. note:: When using this unit, you need to flash the NCP firmware to the unit. For details, refer to the `ESP Zigbee NCP `_ documentation. UiFlow2 Example --------------- From 22a999e64a850f2e675dbad5cb214c1425baf6cd Mon Sep 17 00:00:00 2001 From: lbuque <1102390310@qq.com> Date: Tue, 30 Sep 2025 17:08:40 +0800 Subject: [PATCH 280/322] boards/M5STACK_Unit_C6L: Add unit libs. Signed-off-by: lbuque <1102390310@qq.com> --- m5stack/boards/M5STACK_Unit_C6L/manifest.py | 1 + 1 file changed, 1 insertion(+) diff --git a/m5stack/boards/M5STACK_Unit_C6L/manifest.py b/m5stack/boards/M5STACK_Unit_C6L/manifest.py index c9dbc017..fb94c77c 100644 --- a/m5stack/boards/M5STACK_Unit_C6L/manifest.py +++ b/m5stack/boards/M5STACK_Unit_C6L/manifest.py @@ -3,3 +3,4 @@ # SPDX-License-Identifier: MIT include("$(MPY_DIR)/../m5stack/modules/startup/manifest_unit_c6l.py") +include("$(MPY_DIR)/../m5stack/libs/unit/manifest.py") From 5c13617ae6356dbca4a5e51fcec29fb42d8f2f8d Mon Sep 17 00:00:00 2001 From: lbuque <1102390310@qq.com> Date: Tue, 30 Sep 2025 18:04:32 +0800 Subject: [PATCH 281/322] .github/workflows: Add build of M5STACK_Unit_C6L. Signed-off-by: lbuque <1102390310@qq.com> --- .github/workflows/build-release.yml | 1 + .github/workflows/nightly-build.yml | 1 + .github/workflows/ports_m5stack.yml | 1 + 3 files changed, 3 insertions(+) diff --git a/.github/workflows/build-release.yml b/.github/workflows/build-release.yml index 15369ba2..1fbf18ab 100644 --- a/.github/workflows/build-release.yml +++ b/.github/workflows/build-release.yml @@ -91,6 +91,7 @@ jobs: - M5STACK_StickC_PLUS2 - M5STACK_Tab5 - M5STACK_Tough + - M5STACK_Unit_C6L - ESPRESSIF_ESP32_S3_BOX_3 - SEEED_STUDIO_XIAO_ESP32S3 max-parallel: 4 diff --git a/.github/workflows/nightly-build.yml b/.github/workflows/nightly-build.yml index 44103e59..4ac47575 100644 --- a/.github/workflows/nightly-build.yml +++ b/.github/workflows/nightly-build.yml @@ -89,6 +89,7 @@ jobs: - M5STACK_StickC_PLUS2 - M5STACK_Tab5 - M5STACK_Tough + - M5STACK_Unit_C6L - ESPRESSIF_ESP32_S3_BOX_3 - SEEED_STUDIO_XIAO_ESP32S3 max-parallel: 4 diff --git a/.github/workflows/ports_m5stack.yml b/.github/workflows/ports_m5stack.yml index edb1a2f1..d676a7df 100644 --- a/.github/workflows/ports_m5stack.yml +++ b/.github/workflows/ports_m5stack.yml @@ -94,6 +94,7 @@ jobs: - M5STACK_StickC_PLUS2 - M5STACK_Tab5 - M5STACK_Tough + - M5STACK_Unit_C6L - ESPRESSIF_ESP32_S3_BOX_3 - SEEED_STUDIO_XIAO_ESP32S3 max-parallel: 4 From 022ec6fc463534e0037498377c7feecbc9021309 Mon Sep 17 00:00:00 2001 From: hlym123 Date: Mon, 22 Sep 2025 12:01:22 +0800 Subject: [PATCH 282/322] lib/m5ui: Add LVGL LED. Signed-off-by: hlym123 --- docs/en/m5ui/index.rst | 1 + docs/en/m5ui/led.rst | 339 +++++++++++++ docs/en/refs/m5ui.led.ref | 27 + docs/locales/zh_CN/LC_MESSAGES/m5ui/led.po | 471 ++++++++++++++++++ .../m5ui/led/m5cores3_m5ui_led_example.m5f2 | 1 + .../m5ui/led/m5cores3_m5ui_led_example.py | 130 +++++ m5stack/libs/m5ui/__init__.py | 1 + m5stack/libs/m5ui/led.py | 55 ++ m5stack/libs/m5ui/manifest.py | 1 + 9 files changed, 1026 insertions(+) create mode 100644 docs/en/m5ui/led.rst create mode 100644 docs/en/refs/m5ui.led.ref create mode 100644 docs/locales/zh_CN/LC_MESSAGES/m5ui/led.po create mode 100644 examples/m5ui/led/m5cores3_m5ui_led_example.m5f2 create mode 100644 examples/m5ui/led/m5cores3_m5ui_led_example.py create mode 100644 m5stack/libs/m5ui/led.py diff --git a/docs/en/m5ui/index.rst b/docs/en/m5ui/index.rst index c164522e..523f8133 100644 --- a/docs/en/m5ui/index.rst +++ b/docs/en/m5ui/index.rst @@ -70,6 +70,7 @@ Classes image.rst keyboard.rst label.rst + led.rst line.rst list.rst msgbox.rst diff --git a/docs/en/m5ui/led.rst b/docs/en/m5ui/led.rst new file mode 100644 index 00000000..2f6faffe --- /dev/null +++ b/docs/en/m5ui/led.rst @@ -0,0 +1,339 @@ +.. currentmodule:: m5ui + +M5LED +===== + +.. include:: ../refs/m5ui.led.ref + +M5LED is a lightweight widget that simulates a light-emitting diode indicator in the user interface. + +UiFlow2 Example +--------------- + +LED Basic Usage Example +^^^^^^^^^^^^^^^^^^^^^^^ + +Open the |m5cores3_m5ui_led_example.m5f2| project in UiFlow2. + +This example demonstrates how to create and control an LED widget. +It shows how to turn the LED on and off, change its color, adjust brightness. + +UiFlow2 Code Block: + + |m5cores3_m5ui_led_example.png| + +Example output: + + None. + +MicroPython Example +------------------- + +LED Basic Usage Example +^^^^^^^^^^^^^^^^^^^^^^^ + +This example demonstrates how to create and control an LED widget. +It shows how to turn the LED on and off, change its color, adjust brightness. + +MicroPython Code Block: + + .. literalinclude:: ../../../examples/m5ui/led/m5cores3_m5ui_led_example.py + :language: python + :linenos: + +Example output: + + None. + +**API** +------- + +M5LED +^^^^^ + +.. autoclass:: m5ui.led.M5LED + :members: + + .. py:method:: on() + + Turn on the LED. + + :return: None + + UiFlow2 Code Block: + + |on.png| + + MicroPython Code Block: + + .. code-block:: python + + led_0.on() + + .. py:method:: off() + + Turn off the LED. + + :return: None + + UiFlow2 Code Block: + + |set_state.png| + + MicroPython Code Block: + + .. code-block:: python + + led_0.off() + + .. py:method:: toggle() + + Toggle the state of a LED. + + :return: None + + UiFlow2 Code Block: + + |toggle.png| + + MicroPython Code Block: + + .. code-block:: python + + led_0.toggle() + + .. py:method:: set_color(color) + + Set the color of the LED. + + :param int color: The color of the LED (RGB888 format). + :return: None + + UiFlow2 Code Block: + + |set_color.png| + + MicroPython Code Block: + + .. code-block:: python + + led_0.set_color(color) + + .. py:method:: set_brightness(brightness) + + Set the brightness of the LED. + + :param int brightness: Brightness value, range: 80 ~ 255 (80 = dark, 255 = light). + :return: None + + UiFlow2 Code Block: + + |set_brightness.png| + + MicroPython Code Block: + + .. code-block:: python + + led_0.set_brightness(brightness) + + .. py:method:: get_brightness() + + Get the brightness of the LED. + + :return: The brightness value of the LED. + :rtype: int + + UiFlow2 Code Block: + + |get_brightness.png| + + MicroPython Code Block: + + .. code-block:: python + + brightness = led_0.get_brightness() + + .. py:method:: set_pos(x, y) + + Set the position of the LED. + + :param int x: The x position of the LED. + :param int y: The y position of the LED. + :return: None + + UiFlow2 Code Block: + + |set_pos.png| + + MicroPython Code Block: + + .. code-block:: python + + led_0.set_pos(x, y) + + .. py:method:: set_x(x) + + Set the x position of the LED. + + :param int x: The x position of the LED. + :return: None + + UiFlow2 Code Block: + + |set_x.png| + + MicroPython Code Block: + + .. code-block:: python + + led_0.set_x(x) + + .. py:method:: set_y(y) + + Set the y position of the LED. + + :param int y: The y position of the LED. + :return: None + + UiFlow2 Code Block: + + |set_y.png| + + MicroPython Code Block: + + .. code-block:: python + + led_0.set_y(y) + + .. py:method:: get_x() + + Get the x position of the LED. + + :return: The x position of the LED. + :rtype: int + + UiFlow2 Code Block: + + |get_x.png| + + MicroPython Code Block: + + .. code-block:: python + + x = led_0.get_x() + + .. py:method:: get_y() + + Get the y position of the LED. + + :return: The y position of the LED. + :rtype: int + + UiFlow2 Code Block: + + |get_y.png| + + MicroPython Code Block: + + .. code-block:: python + + y = led_0.get_y() + + .. py:method:: set_size(width, height) + + Set the size of the LED. + + :param int width: The width of the LED. + :param int height: The height of the LED. + :return: None + + UiFlow2 Code Block: + + |set_size.png| + + MicroPython Code Block: + + .. code-block:: python + + led_0.set_size(width, height) + + .. py:method:: set_width(width) + + Set the width of the LED. + + :param int width: The width of the LED. + :return: None + + UiFlow2 Code Block: + + |set_width.png| + + MicroPython Code Block: + + .. code-block:: python + + led_0.set_width(width) + + .. py:method:: set_height(height) + + Set the height of the LED. + + :param int height: The height of the LED. + :return: None + + UiFlow2 Code Block: + + |set_height.png| + + MicroPython Code Block: + + .. code-block:: python + + led_0.set_height(height) + + .. py:method:: align_to(obj, align, x, y) + + Align the LED relative to another object. + + :param obj: The reference object (e.g. page0). + :param int align: Alignment option (see lv.ALIGN constants below). + :param int x: X offset after alignment. + :param int y: Y offset after alignment. + :return: None + + UiFlow2 Code Block: + + |align_to.png| + + MicroPython Code Block: + + .. code-block:: python + + led_0.align_to(page0, lv.ALIGN.CENTER, 0, 0) + + .. py:data:: lv.ALIGN + + Alignment options for positioning objects. + + - lv.ALIGN.DEFAULT + - lv.ALIGN.TOP_LEFT + - lv.ALIGN.TOP_MID + - lv.ALIGN.TOP_RIGHT + - lv.ALIGN.BOTTOM_LEFT + - lv.ALIGN.BOTTOM_MID + - lv.ALIGN.BOTTOM_RIGHT + - lv.ALIGN.LEFT_MID + - lv.ALIGN.RIGHT_MID + - lv.ALIGN.CENTER + - lv.ALIGN.OUT_TOP_LEFT + - lv.ALIGN.OUT_TOP_MID + - lv.ALIGN.OUT_TOP_RIGHT + - lv.ALIGN.OUT_BOTTOM_LEFT + - lv.ALIGN.OUT_BOTTOM_MID + - lv.ALIGN.OUT_BOTTOM_RIGHT + - lv.ALIGN.OUT_LEFT_TOP + - lv.ALIGN.OUT_LEFT_MID + - lv.ALIGN.OUT_LEFT_BOTTOM + - lv.ALIGN.OUT_RIGHT_TOP + - lv.ALIGN.OUT_RIGHT_MID + - lv.ALIGN.OUT_RIGHT_BOTTOM diff --git a/docs/en/refs/m5ui.led.ref b/docs/en/refs/m5ui.led.ref new file mode 100644 index 00000000..32ef2289 --- /dev/null +++ b/docs/en/refs/m5ui.led.ref @@ -0,0 +1,27 @@ +.. |on.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/led/on.png +.. |set_state.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/led/set_state.png +.. |toggle.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/led/toggle.png +.. |get_brightness.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/led/get_brightness.png +.. |set_brightness.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/led/set_brightness.png +.. |set_color.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/led/set_color.png +.. |get_x.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/led/get_x.png +.. |get_y.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/led/get_y.png +.. |set_pos.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/led/set_pos.png +.. |set_x.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/led/set_x.png +.. |set_y.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/led/set_y.png +.. |align_to.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/led/align_to.png +.. |get_width.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/led/get_width.png +.. |get_height.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/led/get_height.png +.. |set_size.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/led/set_size.png +.. |set_width.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/led/set_width.png +.. |set_height.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/led/set_height.png +.. |m5cores3_m5ui_led_example.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/led/example.png + +.. |m5cores3_m5ui_led_example.m5f2| raw:: html + + + m5cores3_m5ui_led_example.m5f2 + diff --git a/docs/locales/zh_CN/LC_MESSAGES/m5ui/led.po b/docs/locales/zh_CN/LC_MESSAGES/m5ui/led.po new file mode 100644 index 00000000..46625c98 --- /dev/null +++ b/docs/locales/zh_CN/LC_MESSAGES/m5ui/led.po @@ -0,0 +1,471 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2016 - 2025 M5Stack Technology Co., Ltd +# This file is distributed under the same license as the UIFlow2 Programming +# Guide package. +# FIRST AUTHOR , 2025. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: UIFlow2 Programming Guide \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-09-22 11:43+0800\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: zh_CN\n" +"Language-Team: zh_CN \n" +"Plural-Forms: nplurals=1; plural=0;\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.16.0\n" + +#: ../../en/m5ui/led.rst:4 ../../en/m5ui/led.rst:52 +#: a11337000bd6414a8a0290c2be8b2629 f61a7c7f3a9945dc86d39375dc438e6d +msgid "M5LED" +msgstr "" + +#: ../../en/m5ui/led.rst:8 6d25a5ba316b46809744d356077c57d8 +msgid "" +"M5LED is a lightweight widget that simulates a light-emitting diode " +"indicator in the user interface." +msgstr "M5LED 是一个轻量级控件,用于在用户界面中模拟发光二极管(LED)指示灯。" + +#: ../../en/m5ui/led.rst:11 59f079c806d7496797b560f805faa829 +msgid "UiFlow2 Example" +msgstr "UiFlow2 应用示例" + +#: ../../en/m5ui/led.rst:14 ../../en/m5ui/led.rst:33 +#: 7594bfeea96c41c6890fe6dd18030b4d +msgid "LED Basic Usage Example" +msgstr "LED 基本用法示例" + +#: ../../en/m5ui/led.rst:16 8c1c5001c1d64e40b4fc74bef62da27a +msgid "Open the |m5cores3_m5ui_led_example.m5f2| project in UiFlow2." +msgstr "在 UiFlow2 中打开 |m5cores3_m5ui_led_example.m5f2| 项目。" + +#: ../../en/m5ui/led.rst:18 ../../en/m5ui/led.rst:35 +#: 985dcd6af9234ec383a6aef19c6ae210 +msgid "" +"This example demonstrates how to create and control an LED widget. It " +"shows how to turn the LED on and off, change its color, adjust " +"brightness." +msgstr "该示例演示如何创建并控制 LED 控件,展示了如何打开/关闭 LED、改变颜色、调整亮度。" + +#: ../../en/m5ui/led.rst:21 ../../en/m5ui/led.rst:63 ../../en/m5ui/led.rst:79 +#: ../../en/m5ui/led.rst:95 ../../en/m5ui/led.rst:112 ../../en/m5ui/led.rst:129 +#: ../../en/m5ui/led.rst:146 ../../en/m5ui/led.rst:164 +#: ../../en/m5ui/led.rst:181 ../../en/m5ui/led.rst:198 +#: ../../en/m5ui/led.rst:215 ../../en/m5ui/led.rst:232 +#: ../../en/m5ui/led.rst:250 ../../en/m5ui/led.rst:267 +#: ../../en/m5ui/led.rst:284 ../../en/m5ui/led.rst:304 +#: ac594b5062d44751854b422f8ece5703 m5ui.led.M5LED:10 of +msgid "UiFlow2 Code Block:" +msgstr "UiFlow2 代码块:" + +#: ../../en/m5ui/led.rst:23 83fe5d94c59e462b92a27fbfde08554e +msgid "|m5cores3_m5ui_led_example.png|" +msgstr "" + +#: ../../en/refs/m5ui.led.ref:18 06843d737bac488bb96d08597998b590 +msgid "m5cores3_m5ui_led_example.png" +msgstr "" + +#: ../../en/m5ui/led.rst:25 ../../en/m5ui/led.rst:44 +#: a965150f258a4189a30db02faeb4fbe7 +msgid "Example output:" +msgstr "示例输出:" + +#: ../../en/m5ui/led.rst:27 ../../en/m5ui/led.rst:46 +#: d49f10f13bac42b2a87a472907ede0c4 +msgid "None." +msgstr "无。" + +#: ../../en/m5ui/led.rst:30 29d179a9781d4f3e9b99cda7003a3d0c +msgid "MicroPython Example" +msgstr "MicroPython 应用示例" + +#: ../../en/m5ui/led.rst:38 ../../en/m5ui/led.rst:67 ../../en/m5ui/led.rst:83 +#: ../../en/m5ui/led.rst:99 ../../en/m5ui/led.rst:116 ../../en/m5ui/led.rst:133 +#: ../../en/m5ui/led.rst:150 ../../en/m5ui/led.rst:168 +#: ../../en/m5ui/led.rst:185 ../../en/m5ui/led.rst:202 +#: ../../en/m5ui/led.rst:219 ../../en/m5ui/led.rst:236 +#: ../../en/m5ui/led.rst:254 ../../en/m5ui/led.rst:271 +#: ../../en/m5ui/led.rst:288 ../../en/m5ui/led.rst:308 +#: db3f0fa388784215a836805cfb7c883a m5ui.led.M5LED:14 of +msgid "MicroPython Code Block:" +msgstr "MicroPython 代码块:" + +#: ../../en/m5ui/led.rst:49 13a052fe8ee841ccae0656893167e489 +msgid "**API**" +msgstr "API 参考" + +#: b9de8f31bfbc47a8a2bfa4973871de37 m5ui.led.M5LED:1 of +msgid "Bases: :py:class:`~lvgl.led`" +msgstr "" + +#: 2b03cfcc3ade45ef842406d0a5932316 m5ui.led.M5LED:1 of +msgid "Create a LED object." +msgstr "创建一个 LED 对象。" + +#: ../../en/m5ui/led.rst 4b48887862424386b65b467ac7aed374 +msgid "Parameters" +msgstr "参数" + +#: ../../en/m5ui/led.rst:160 ../../en/m5ui/led.rst:178 +#: ../../en/m5ui/led.rst:212 06cc5237a3c540478cd68d20f0745322 m5ui.led.M5LED:3 +#: of +msgid "The x position of the LED." +msgstr "LED 的 x 坐标。" + +#: ../../en/m5ui/led.rst:161 ../../en/m5ui/led.rst:195 +#: ../../en/m5ui/led.rst:229 dd93e2eff66f4ee4984e6b507695220c m5ui.led.M5LED:4 +#: of +msgid "The y position of the LED." +msgstr "LED 的 y 坐标。" + +#: 37fa14ba63e24a91b1e5e7afbb2bb3b3 m5ui.led.M5LED:5 of +msgid "The size (width and height) of the LED." +msgstr "LED 的大小(宽度和高度)。" + +#: a1132247048a4454a4a58ec672b93a34 m5ui.led.M5LED:6 of +msgid "The color of the LED in RGB888 format." +msgstr "LED 的颜色(RGB888 格式)。" + +#: 0bb5559edfe14e8d8b6a7824dcb9331b m5ui.led.M5LED:7 of +msgid "Initial state of the LED (True for ON, False for OFF)." +msgstr "LED 的初始状态(True 表示打开,False 表示关闭)。" + +#: d78b7a1899f84e639378e795190355d5 m5ui.led.M5LED:8 of +msgid "" +"The parent object to attach the LED to. If not specified, the LED will be" +" attached to the default screen." +msgstr "要将 LED 附加到的父对象。如果未指定,LED 将附加到默认屏幕。" + +#: ../../en/m5ui/led.rst:61 ../../en/m5ui/led.rst:77 ../../en/m5ui/led.rst:93 +#: ../../en/m5ui/led.rst:110 ../../en/m5ui/led.rst:127 +#: ../../en/m5ui/led.rst:162 ../../en/m5ui/led.rst:179 +#: ../../en/m5ui/led.rst:196 ../../en/m5ui/led.rst:248 +#: ../../en/m5ui/led.rst:265 ../../en/m5ui/led.rst:282 +#: ../../en/m5ui/led.rst:302 7c4d22e7809b45a098159ffbebc804aa +#: 8fa35ff50ac24bf09ae3ddb4add40cea m5ui.led.M5LED:12 of +msgid "None" +msgstr "无" + +#: ../../en/m5ui/led.rst:59 95cdc85d96ab4200b08024083ece515e +msgid "Turn on the LED." +msgstr "打开 LED。" + +#: ../../en/m5ui/led.rst 81eb01b492e348d8ad91d72bd64f7e10 +msgid "Returns" +msgstr "返回" + +#: ../../en/m5ui/led.rst:65 13510135685e4268a2a5288d59b89051 +msgid "|on.png|" +msgstr "" + +#: ../../en/refs/m5ui.led.ref:1 06843d737bac488bb96d08597998b590 +msgid "on.png" +msgstr "" + +#: ../../en/m5ui/led.rst:75 c72f1526a1e442f48d108788d9b7df28 +msgid "Turn off the LED." +msgstr "关闭 LED。" + +#: ../../en/m5ui/led.rst:81 0a6eb0f4f55348698782f61ca5612f32 +msgid "|set_state.png|" +msgstr "" + +#: ../../en/refs/m5ui.led.ref:2 c68fbda5acd34fdab28f705b0b114c31 +msgid "set_state.png" +msgstr "" + +#: ../../en/m5ui/led.rst:91 da7762418e4c458f9d6d633de55fc699 +msgid "Toggle the state of a LED." +msgstr "切换 LED 的状态。" + +#: ../../en/m5ui/led.rst:97 13510135685e4268a2a5288d59b89051 +msgid "|toggle.png|" +msgstr "" + +#: ../../en/refs/m5ui.led.ref:3 06843d737bac488bb96d08597998b590 +msgid "toggle.png" +msgstr "" + +#: ../../en/m5ui/led.rst:107 d70f90480ef242aba3f4b6ae57f911fa +msgid "Set the color of the LED." +msgstr "设置 LED 的颜色。" + +#: ../../en/m5ui/led.rst:109 9536adb67aca4c3490e9d44e8d718e3f +msgid "The color of the LED (RGB888 format)." +msgstr "LED 的颜色(RGB888 格式)。" + +#: ../../en/m5ui/led.rst:114 0a6eb0f4f55348698782f61ca5612f32 +msgid "|set_color.png|" +msgstr "" + +#: ../../en/refs/m5ui.led.ref:6 82db32b366154a68912d1a484e906648 +msgid "set_color.png" +msgstr "" + +#: ../../en/m5ui/led.rst:124 5b57eeae1e664d4aa0c67f70570c487d +msgid "Set the brightness of the LED." +msgstr "设置 LED 的亮度。" + +#: ../../en/m5ui/led.rst:126 99e59fa79b4041d7bf651efc0af62cb0 +msgid "Brightness value, range: 80 ~ 255 (80 = dark, 255 = light)." +msgstr "亮度值,范围:80 ~ 255(80 = 暗,255 = 亮)。" + +#: ../../en/m5ui/led.rst:131 89b0a317e2e64ee1a71ddfe9e826399a +msgid "|set_brightness.png|" +msgstr "" + +#: ../../en/refs/m5ui.led.ref:5 c43a0b5fb19445ccb7e338b0e4673cca +msgid "set_brightness.png" +msgstr "" + +#: ../../en/m5ui/led.rst:141 363e1ccd635044d39a0d54d82a3eeb4d +msgid "Get the brightness of the LED." +msgstr "获取 LED 的亮度。" + +#: ../../en/m5ui/led.rst:143 7cf84ffea4b1408dac3a90ebbc904067 +msgid "The brightness value of the LED." +msgstr "LED 的亮度值。" + +#: ../../en/m5ui/led.rst b8d758dd39aa4a35a1a06aa72e99267a +msgid "Return type" +msgstr "返回类型" + +#: ../../en/m5ui/led.rst:148 bc5ad52e9e814bafa80921459188ab83 +msgid "|get_brightness.png|" +msgstr "" + +#: ../../en/refs/m5ui.led.ref:4 71822bc0863f4286af7fd0f8eb012bfa +msgid "get_brightness.png" +msgstr "" + +#: ../../en/m5ui/led.rst:158 06cc5237a3c540478cd68d20f0745322 +msgid "Set the position of the LED." +msgstr "设置 LED 的位置。" + +#: ../../en/m5ui/led.rst:166 0a6eb0f4f55348698782f61ca5612f32 +msgid "|set_pos.png|" +msgstr "" + +#: ../../en/refs/m5ui.led.ref:9 c68fbda5acd34fdab28f705b0b114c31 +msgid "set_pos.png" +msgstr "" + +#: ../../en/m5ui/led.rst:176 06cc5237a3c540478cd68d20f0745322 +msgid "Set the x position of the LED." +msgstr "设置 LED 的 x 坐标。" + +#: ../../en/m5ui/led.rst:183 0a6eb0f4f55348698782f61ca5612f32 +msgid "|set_x.png|" +msgstr "" + +#: ../../en/refs/m5ui.led.ref:10 c68fbda5acd34fdab28f705b0b114c31 +msgid "set_x.png" +msgstr "" + +#: ../../en/m5ui/led.rst:193 dd93e2eff66f4ee4984e6b507695220c +msgid "Set the y position of the LED." +msgstr "设置 LED 的 y 坐标。" + +#: ../../en/m5ui/led.rst:200 0a6eb0f4f55348698782f61ca5612f32 +msgid "|set_y.png|" +msgstr "" + +#: ../../en/refs/m5ui.led.ref:11 c68fbda5acd34fdab28f705b0b114c31 +msgid "set_y.png" +msgstr "" + +#: ../../en/m5ui/led.rst:210 06cc5237a3c540478cd68d20f0745322 +msgid "Get the x position of the LED." +msgstr "获取 LED 的 x 坐标。" + +#: ../../en/m5ui/led.rst:217 0a6eb0f4f55348698782f61ca5612f32 +msgid "|get_x.png|" +msgstr "" + +#: ../../en/refs/m5ui.led.ref:7 2612ef0e389f43449ef95aaad7207f46 +msgid "get_x.png" +msgstr "" + +#: ../../en/m5ui/led.rst:227 dd93e2eff66f4ee4984e6b507695220c +msgid "Get the y position of the LED." +msgstr "获取 LED 的 y 坐标。" + +#: ../../en/m5ui/led.rst:234 0a6eb0f4f55348698782f61ca5612f32 +msgid "|get_y.png|" +msgstr "" + +#: ../../en/refs/m5ui.led.ref:8 2612ef0e389f43449ef95aaad7207f46 +msgid "get_y.png" +msgstr "" + +#: ../../en/m5ui/led.rst:244 d70f90480ef242aba3f4b6ae57f911fa +msgid "Set the size of the LED." +msgstr "设置 LED 的大小。" + +#: ../../en/m5ui/led.rst:246 ../../en/m5ui/led.rst:264 +#: f6d45ebb803a4d8d9869f351a10b38a0 +msgid "The width of the LED." +msgstr "LED 的宽度。" + +#: ../../en/m5ui/led.rst:247 ../../en/m5ui/led.rst:281 +#: f6d45ebb803a4d8d9869f351a10b38a0 +msgid "The height of the LED." +msgstr "LED 的高度。" + +#: ../../en/m5ui/led.rst:252 0a6eb0f4f55348698782f61ca5612f32 +msgid "|set_size.png|" +msgstr "" + +#: ../../en/refs/m5ui.led.ref:15 c68fbda5acd34fdab28f705b0b114c31 +msgid "set_size.png" +msgstr "" + +#: ../../en/m5ui/led.rst:262 d70f90480ef242aba3f4b6ae57f911fa +msgid "Set the width of the LED." +msgstr "设置 LED 的宽度。" + +#: ../../en/m5ui/led.rst:269 0a6eb0f4f55348698782f61ca5612f32 +msgid "|set_width.png|" +msgstr "" + +#: ../../en/refs/m5ui.led.ref:16 c68fbda5acd34fdab28f705b0b114c31 +msgid "set_width.png" +msgstr "" + +#: ../../en/m5ui/led.rst:279 5b57eeae1e664d4aa0c67f70570c487d +msgid "Set the height of the LED." +msgstr "设置 LED 的高度。" + +#: ../../en/m5ui/led.rst:286 89b0a317e2e64ee1a71ddfe9e826399a +msgid "|set_height.png|" +msgstr "" + +#: ../../en/refs/m5ui.led.ref:17 c43a0b5fb19445ccb7e338b0e4673cca +msgid "set_height.png" +msgstr "" + +#: ../../en/m5ui/led.rst:296 8e0690f870eb403bbe6f4b3e099d13e0 +msgid "Align the LED relative to another object." +msgstr "将 LED 相对于另一个对象对齐。" + +#: ../../en/m5ui/led.rst:298 f9fc1cfa9d974a17bfc87b493ef13283 +msgid "The reference object (e.g. page0)." +msgstr "" + +#: ../../en/m5ui/led.rst:299 feb68f38f854494190ffac0c65e0caa2 +msgid "Alignment option (see lv.ALIGN constants below)." +msgstr "对齐选项(参见下方 lv.ALIGN 常量)。" + +#: ../../en/m5ui/led.rst:300 3fb3a67665d24542b09a9d3056205290 +msgid "X offset after alignment." +msgstr "对齐后的 X 偏移。" + +#: ../../en/m5ui/led.rst:301 7fc0124ed5e14afeb4bf03b03abcd839 +msgid "Y offset after alignment." +msgstr "对齐后的 Y 偏移。" + +#: ../../en/m5ui/led.rst:306 0a6eb0f4f55348698782f61ca5612f32 +msgid "|align_to.png|" +msgstr "" + +#: ../../en/refs/m5ui.led.ref:12 06843d737bac488bb96d08597998b590 +msgid "align_to.png" +msgstr "" + +#: ../../en/m5ui/led.rst:316 52e7b2822eea49d4af7649ffcf3cc08e +msgid "Alignment options for positioning objects." +msgstr "用于定位对象的对齐选项。" + +#: ../../en/m5ui/led.rst:318 eaeb667a06b34f2091ef9f2f7b542c63 +msgid "lv.ALIGN.DEFAULT" +msgstr "" + +#: ../../en/m5ui/led.rst:319 d12f1c9155134d86b7e15bdc430cd244 +msgid "lv.ALIGN.TOP_LEFT" +msgstr "" + +#: ../../en/m5ui/led.rst:320 eb24fb905a4a4e9bb4bc4fff07d0ffac +msgid "lv.ALIGN.TOP_MID" +msgstr "" + +#: ../../en/m5ui/led.rst:321 d10a2adc8f864426ac68ee863663e34d +msgid "lv.ALIGN.TOP_RIGHT" +msgstr "" + +#: ../../en/m5ui/led.rst:322 7c2a96f6b4f142ce9e8081f40333874a +msgid "lv.ALIGN.BOTTOM_LEFT" +msgstr "" + +#: ../../en/m5ui/led.rst:323 21f1737f125e4dc382203ad614cc3280 +msgid "lv.ALIGN.BOTTOM_MID" +msgstr "" + +#: ../../en/m5ui/led.rst:324 b7306e65637b43ab9134680d8a4fef62 +msgid "lv.ALIGN.BOTTOM_RIGHT" +msgstr "" + +#: ../../en/m5ui/led.rst:325 41cd3d19a86f4fd79c0660571fd42ef4 +msgid "lv.ALIGN.LEFT_MID" +msgstr "" + +#: ../../en/m5ui/led.rst:326 edec3bcd17094a189d2b7c8571b48342 +msgid "lv.ALIGN.RIGHT_MID" +msgstr "" + +#: ../../en/m5ui/led.rst:327 c698a185166e458ba821b04b5ab64360 +msgid "lv.ALIGN.CENTER" +msgstr "" + +#: ../../en/m5ui/led.rst:328 613c104d2f104a4aa88797cefdb7751f +msgid "lv.ALIGN.OUT_TOP_LEFT" +msgstr "" + +#: ../../en/m5ui/led.rst:329 d0758ba31b9b4a8ca47aac7d89bc063d +msgid "lv.ALIGN.OUT_TOP_MID" +msgstr "" + +#: ../../en/m5ui/led.rst:330 d4369f62c57542c2b983eafe8274ca6d +msgid "lv.ALIGN.OUT_TOP_RIGHT" +msgstr "" + +#: ../../en/m5ui/led.rst:331 d4a07186d69d4b1192bca641f3e80c40 +msgid "lv.ALIGN.OUT_BOTTOM_LEFT" +msgstr "" + +#: ../../en/m5ui/led.rst:332 9bd211e14df44e11b61df9176fb55e10 +msgid "lv.ALIGN.OUT_BOTTOM_MID" +msgstr "" + +#: ../../en/m5ui/led.rst:333 fa4ba1fd752a4829b8e6d5a7ce40b32f +msgid "lv.ALIGN.OUT_BOTTOM_RIGHT" +msgstr "" + +#: ../../en/m5ui/led.rst:334 ab668faee47d4d4f8bf4325dca8cd316 +msgid "lv.ALIGN.OUT_LEFT_TOP" +msgstr "" + +#: ../../en/m5ui/led.rst:335 ea14ca3e58f943d6850f3c753405aeee +msgid "lv.ALIGN.OUT_LEFT_MID" +msgstr "" + +#: ../../en/m5ui/led.rst:336 d425f9467fc24a2cb520eb443773f094 +msgid "lv.ALIGN.OUT_LEFT_BOTTOM" +msgstr "" + +#: ../../en/m5ui/led.rst:337 85fdff55da324e69b410271baf108cdc +msgid "lv.ALIGN.OUT_RIGHT_TOP" +msgstr "" + +#: ../../en/m5ui/led.rst:338 96c144a0876c493485e16c284baad05a +msgid "lv.ALIGN.OUT_RIGHT_MID" +msgstr "" + +#: ../../en/m5ui/led.rst:339 16ee12515d644eadabaa8e06b8c99f33 +msgid "lv.ALIGN.OUT_RIGHT_BOTTOM" +msgstr "" diff --git a/examples/m5ui/led/m5cores3_m5ui_led_example.m5f2 b/examples/m5ui/led/m5cores3_m5ui_led_example.m5f2 new file mode 100644 index 00000000..797ab2f0 --- /dev/null +++ b/examples/m5ui/led/m5cores3_m5ui_led_example.m5f2 @@ -0,0 +1 @@ +{"version":"V2.0","versionNumber":"V2.3.6","type":"cores3","components":[{"name":"page0","type":"lvgl_page","layer":0,"screenId":"builtin","screenName":"","id":"qeO8YG1DO63v*fHf","createTime":1758502322955,"backgroundColor":"#000000","isLVGL":true,"isSelected":true},{"name":"led0","type":"lvgl_led","layer":1,"screenId":"builtin","screenName":"","id":"lRU3QB#dWwYygmI+","createTime":1758502435204,"x":135,"y":14,"width":50,"color":"#00ff00","state":true,"pageId":"qeO8YG1DO63v*fHf","isLVGL":true,"isSelected":false},{"name":"switch0","type":"lvgl_switch","layer":2,"screenId":"builtin","screenName":"","id":"wd35lVzpmp%%gV&h","createTime":1758503641987,"x":110,"y":159,"width":100,"height":40,"knobColor":"#ffffff","backgroundColor":"#e7e3e7","checkedBackgroundColor":"#2196f3","pageId":"qeO8YG1DO63v*fHf","isLVGL":true,"isSelected":false},{"name":"slider0","type":"lvgl_slider","layer":3,"screenId":"builtin","screenName":"","id":"s6K%27i5Lyf-Q*Nd","createTime":1758503765908,"x":20,"y":118,"width":280,"height":16,"minValue":0,"maxValue":100,"currentValue":25,"color":"#2193f3","backgroundColor":"#2193f3","pageId":"qeO8YG1DO63v*fHf","isLVGL":true,"isSelected":false},{"name":"label0","type":"lvgl_label","layer":4,"screenId":"builtin","screenName":"","id":"s1c_drb&!`pIhYlF","createTime":1758503943980,"x":99,"y":85,"color":"#2193f3","backgroundColor":"#ffffff","bg_opacity":0,"text":"Brightness: 0%","font":"lv.font_montserrat_16","pageId":"qeO8YG1DO63v*fHf","isLVGL":true,"isSelected":false}],"resources":[{"hardware":["hardware_button","hardware_pin_button","imu","speaker","touch","als","mic","sdcard"]}],"units":[],"hats":[],"caps":[],"bases":[],"i2cs":[],"blockly":"brightnesstruepage0led0offbrightness0slider00led0TOP_MIDpage005label0CENTERslider00-25led080hello M5led0trueswitch0CHECKEDled0palette#3366ffled0onswitch0UNCHECKEDled0offled0palette#000000slider0VALUE_CHANGEDbrightnessslider0led02550brightness010080255label0hello M5Brightness: Brightness: brightness%hello M5led0","screen":[{"simulationName":"Built-in","type":"builtin","width":320,"height":240,"scale":0.77,"screenName":"","blockId":"","screenColorType":0,"rotation":1,"id":"builtin","createTime":1758502322948}],"logicWhenNum":0,"customList":[]} \ No newline at end of file diff --git a/examples/m5ui/led/m5cores3_m5ui_led_example.py b/examples/m5ui/led/m5cores3_m5ui_led_example.py new file mode 100644 index 00000000..a921dfbd --- /dev/null +++ b/examples/m5ui/led/m5cores3_m5ui_led_example.py @@ -0,0 +1,130 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import os, sys, io +import M5 +from M5 import * +import m5ui +import lvgl as lv +import m5utils + + +page0 = None +led0 = None +switch0 = None +slider0 = None +label0 = None +brightness = None + + +def switch0_checked_event(event_struct): + global page0, led0, switch0, slider0, label0, brightness + led0.set_color(0x3366FF) + led0.on() + + +def switch0_unchecked_event(event_struct): + global page0, led0, switch0, slider0, label0, brightness + led0.off() + led0.set_color(0x000000) + + +def slider0_value_changed_event(event_struct): + global page0, led0, switch0, slider0, label0, brightness + brightness = slider0.get_value() + led0.set_brightness(int(m5utils.remap(brightness, 0, 100, 80, 255))) + label0.set_text(str((str("Brightness: ") + str((str(brightness) + str("%")))))) + print(led0.get_brightness()) + + +def switch0_event_handler(event_struct): + global page0, led0, switch0, slider0, label0, brightness + event = event_struct.code + obj = event_struct.get_target_obj() + if event == lv.EVENT.VALUE_CHANGED: + if obj.has_state(lv.STATE.CHECKED): + switch0_checked_event(event_struct) + else: + switch0_unchecked_event(event_struct) + return + + +def slider0_event_handler(event_struct): + global page0, led0, switch0, slider0, label0, brightness + event = event_struct.code + if event == lv.EVENT.VALUE_CHANGED and True: + slider0_value_changed_event(event_struct) + return + + +def setup(): + global page0, led0, switch0, slider0, label0, brightness + M5.begin() + Widgets.setRotation(1) + m5ui.init() + page0 = m5ui.M5Page(bg_c=0x000000) + led0 = m5ui.M5LED(x=135, y=14, size=50, color=0x00FF00, on=True, parent=page0) + switch0 = m5ui.M5Switch( + x=110, + y=159, + w=100, + h=40, + bg_c=0xE7E3E7, + bg_c_checked=0x2196F3, + circle_c=0xFFFFFF, + parent=page0, + ) + slider0 = m5ui.M5Slider( + x=20, + y=118, + w=280, + h=16, + mode=lv.slider.MODE.NORMAL, + min_value=0, + max_value=100, + value=25, + bg_c=0x2193F3, + color=0x2193F3, + parent=page0, + ) + label0 = m5ui.M5Label( + "Brightness: 0%", + x=99, + y=85, + text_c=0x2193F3, + bg_c=0xFFFFFF, + bg_opa=0, + font=lv.font_montserrat_16, + parent=page0, + ) + switch0.add_event_cb(switch0_event_handler, lv.EVENT.ALL, None) + slider0.add_event_cb(slider0_event_handler, lv.EVENT.ALL, None) + page0.screen_load() + led0.off() + brightness = 0 + slider0.set_value(0, True) + led0.align_to(page0, lv.ALIGN.TOP_MID, 0, 5) + label0.align_to(slider0, lv.ALIGN.CENTER, 0, -25) + led0.set_brightness(80) + print(led0.get_brightness()) + + +def loop(): + global page0, led0, switch0, slider0, label0, brightness + M5.update() + + +if __name__ == "__main__": + try: + setup() + while True: + loop() + except (Exception, KeyboardInterrupt) as e: + try: + m5ui.deinit() + from utility import print_error_msg + + print_error_msg(e) + except ImportError: + print("please update to latest firmware") diff --git a/m5stack/libs/m5ui/__init__.py b/m5stack/libs/m5ui/__init__.py index bbc108f4..a237e276 100644 --- a/m5stack/libs/m5ui/__init__.py +++ b/m5stack/libs/m5ui/__init__.py @@ -17,6 +17,7 @@ "M5Image": "image", "M5Keyboard": "keyboard", "M5Label": "label", + "M5LED": "led", "M5Line": "line", "M5List": "list", "M5Msgbox": "msgbox", diff --git a/m5stack/libs/m5ui/led.py b/m5stack/libs/m5ui/led.py new file mode 100644 index 00000000..f85c0dfe --- /dev/null +++ b/m5stack/libs/m5ui/led.py @@ -0,0 +1,55 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +from .base import M5Base +import lvgl as lv + + +class M5LED(lv.led): + """Create a LED object. + + :param int x: The x position of the LED. + :param int y: The y position of the LED. + :param int size: The size (width and height) of the LED. + :param int color: The color of the LED in RGB888 format. + :param bool on: Initial state of the LED (True for ON, False for OFF). + :param lv.obj parent: The parent object to attach the LED to. If not specified, the LED will be attached to the default screen. + + UiFlow2 Code Block: + + None + + MicroPython Code Block: + + .. code-block:: python + + from m5ui import M5Led + import lvgl as lv + + m5ui.init() + led_0 = M5Led(x=50, y=50, size=50, color=0x00FF00, on=True, parent=page0) + """ + + def __init__(self, x=0, y=0, size=50, color=0x00FF00, on=True, parent=None): + if parent is None: + parent = lv.screen_active() + super().__init__(parent) + self.set_pos(x, y) + self.set_size(size, size) + self.set_color(color) + if on: + self.on() + else: + self.off() + + def set_color(self, color: int): + super().set_color(lv.color_hex(color)) + + def __getattr__(self, name): + if hasattr(M5Base, name): + method = getattr(M5Base, name) + bound_method = lambda *args, **kwargs: method(self, *args, **kwargs) + setattr(self, name, bound_method) + return bound_method + raise AttributeError(f"'{self.__class__.__name__}' object has no attribute '{name}'") diff --git a/m5stack/libs/m5ui/manifest.py b/m5stack/libs/m5ui/manifest.py index a7d60ea4..37439d92 100644 --- a/m5stack/libs/m5ui/manifest.py +++ b/m5stack/libs/m5ui/manifest.py @@ -18,6 +18,7 @@ "image.py", "keyboard.py", "label.py", + "led.py", "line.py", "list.py", "msgbox.py", From 45990176415968449fcd95eca558c81c8c733f3b Mon Sep 17 00:00:00 2001 From: lbuque <1102390310@qq.com> Date: Thu, 23 Oct 2025 12:26:23 +0800 Subject: [PATCH 283/322] libs/m5ui/led.py: Remap the brightness range. Signed-off-by: lbuque <1102390310@qq.com> --- docs/en/m5ui/led.rst | 34 -- docs/locales/zh_CN/LC_MESSAGES/m5ui/led.po | 354 ++++++++++++--------- m5stack/libs/m5ui/led.py | 55 ++++ tests/m5ui/test_led.py | 32 ++ 4 files changed, 286 insertions(+), 189 deletions(-) create mode 100644 tests/m5ui/test_led.py diff --git a/docs/en/m5ui/led.rst b/docs/en/m5ui/led.rst index 2f6faffe..00600719 100644 --- a/docs/en/m5ui/led.rst +++ b/docs/en/m5ui/led.rst @@ -119,40 +119,6 @@ M5LED led_0.set_color(color) - .. py:method:: set_brightness(brightness) - - Set the brightness of the LED. - - :param int brightness: Brightness value, range: 80 ~ 255 (80 = dark, 255 = light). - :return: None - - UiFlow2 Code Block: - - |set_brightness.png| - - MicroPython Code Block: - - .. code-block:: python - - led_0.set_brightness(brightness) - - .. py:method:: get_brightness() - - Get the brightness of the LED. - - :return: The brightness value of the LED. - :rtype: int - - UiFlow2 Code Block: - - |get_brightness.png| - - MicroPython Code Block: - - .. code-block:: python - - brightness = led_0.get_brightness() - .. py:method:: set_pos(x, y) Set the position of the LED. diff --git a/docs/locales/zh_CN/LC_MESSAGES/m5ui/led.po b/docs/locales/zh_CN/LC_MESSAGES/m5ui/led.po index 46625c98..42035307 100644 --- a/docs/locales/zh_CN/LC_MESSAGES/m5ui/led.po +++ b/docs/locales/zh_CN/LC_MESSAGES/m5ui/led.po @@ -9,7 +9,7 @@ msgid "" msgstr "" "Project-Id-Version: UIFlow2 Programming Guide \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-09-22 11:43+0800\n" +"POT-Creation-Date: 2025-10-23 12:14+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language: zh_CN\n" @@ -18,34 +18,34 @@ msgstr "" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" -"Generated-By: Babel 2.16.0\n" +"Generated-By: Babel 2.17.0\n" #: ../../en/m5ui/led.rst:4 ../../en/m5ui/led.rst:52 -#: a11337000bd6414a8a0290c2be8b2629 f61a7c7f3a9945dc86d39375dc438e6d +#: 0318c23acc9a4c63a172d879d41186f1 96f093565b5f42cf82d9ea8807aabb5b msgid "M5LED" msgstr "" -#: ../../en/m5ui/led.rst:8 6d25a5ba316b46809744d356077c57d8 +#: ../../en/m5ui/led.rst:8 8203482b8d89470da4994c7fb64822a5 msgid "" "M5LED is a lightweight widget that simulates a light-emitting diode " "indicator in the user interface." msgstr "M5LED 是一个轻量级控件,用于在用户界面中模拟发光二极管(LED)指示灯。" -#: ../../en/m5ui/led.rst:11 59f079c806d7496797b560f805faa829 +#: ../../en/m5ui/led.rst:11 6724b357394b4a23b94c87973068bc1f msgid "UiFlow2 Example" msgstr "UiFlow2 应用示例" #: ../../en/m5ui/led.rst:14 ../../en/m5ui/led.rst:33 -#: 7594bfeea96c41c6890fe6dd18030b4d +#: 69be6bc6d2b643c79614c5c9c438ebc4 b832c25c422d427681db2fbda7b93da7 msgid "LED Basic Usage Example" msgstr "LED 基本用法示例" -#: ../../en/m5ui/led.rst:16 8c1c5001c1d64e40b4fc74bef62da27a +#: ../../en/m5ui/led.rst:16 0fe0e8e40f3b4f05a795c4d7458769a0 msgid "Open the |m5cores3_m5ui_led_example.m5f2| project in UiFlow2." msgstr "在 UiFlow2 中打开 |m5cores3_m5ui_led_example.m5f2| 项目。" #: ../../en/m5ui/led.rst:18 ../../en/m5ui/led.rst:35 -#: 985dcd6af9234ec383a6aef19c6ae210 +#: 6f3c20b9d17d489783e19675469b994a 9f7c642079ad465f8372c9e841ac913b msgid "" "This example demonstrates how to create and control an LED widget. It " "shows how to turn the LED on and off, change its color, adjust " @@ -53,419 +53,463 @@ msgid "" msgstr "该示例演示如何创建并控制 LED 控件,展示了如何打开/关闭 LED、改变颜色、调整亮度。" #: ../../en/m5ui/led.rst:21 ../../en/m5ui/led.rst:63 ../../en/m5ui/led.rst:79 -#: ../../en/m5ui/led.rst:95 ../../en/m5ui/led.rst:112 ../../en/m5ui/led.rst:129 -#: ../../en/m5ui/led.rst:146 ../../en/m5ui/led.rst:164 +#: ../../en/m5ui/led.rst:95 ../../en/m5ui/led.rst:112 ../../en/m5ui/led.rst:130 +#: ../../en/m5ui/led.rst:147 ../../en/m5ui/led.rst:164 #: ../../en/m5ui/led.rst:181 ../../en/m5ui/led.rst:198 -#: ../../en/m5ui/led.rst:215 ../../en/m5ui/led.rst:232 -#: ../../en/m5ui/led.rst:250 ../../en/m5ui/led.rst:267 -#: ../../en/m5ui/led.rst:284 ../../en/m5ui/led.rst:304 -#: ac594b5062d44751854b422f8ece5703 m5ui.led.M5LED:10 of +#: ../../en/m5ui/led.rst:216 ../../en/m5ui/led.rst:233 +#: ../../en/m5ui/led.rst:250 ../../en/m5ui/led.rst:270 +#: 0cc4cf70a6554974ad854bd631e6afec 3b08f68c7edd4beca3a326c6f24dc0a7 +#: 3e937901ceb2402088f0cfc21b7bb328 3f40bc166ba440c5b7f472721edb2e8a +#: 44fe2a779aee48949e3d94e9d544ce35 4f75f43da4bd480f83e8d2f85c414f8c +#: 596e5ce34c244069a94c25c2e2fb2e23 59c8d43ed9e44845935cb95623e3ec10 +#: 6d4089f919794dafa5c8de5a8f60d541 8da62713c92348bd892f0029c7075795 +#: 9d934633801140b5ac266c32045aaf78 a40bb9a559d8468ba2235c859d1efb71 +#: a7f064d7812c40b5acb6b1ad5b382c7e bc56677951e240a39e6c1402b26dda14 +#: c625ff5a047041589b13d27f4884b6d0 ee252552f068419b8a2c82d7ddb1306e +#: ff1eaead43144c8d9822ec2701c54516 m5ui.led.M5LED:10 +#: m5ui.led.M5LED.get_brightness:6 m5ui.led.M5LED.set_brightness:5 of msgid "UiFlow2 Code Block:" msgstr "UiFlow2 代码块:" -#: ../../en/m5ui/led.rst:23 83fe5d94c59e462b92a27fbfde08554e +#: ../../en/m5ui/led.rst:23 a10d8d19e2be478ca09af5f6e9af3d0d msgid "|m5cores3_m5ui_led_example.png|" msgstr "" -#: ../../en/refs/m5ui.led.ref:18 06843d737bac488bb96d08597998b590 +#: ../../en/refs/m5ui.led.ref:18 b87efe7388b44ce0ae0f3bc6db51cbb5 msgid "m5cores3_m5ui_led_example.png" msgstr "" #: ../../en/m5ui/led.rst:25 ../../en/m5ui/led.rst:44 -#: a965150f258a4189a30db02faeb4fbe7 +#: 1433ac87df5e42d5a009676a41425249 e664e2fc8c3348caa1e23d2dfd504601 msgid "Example output:" msgstr "示例输出:" #: ../../en/m5ui/led.rst:27 ../../en/m5ui/led.rst:46 -#: d49f10f13bac42b2a87a472907ede0c4 +#: 3eaf66e4ed1e4f7f9664cee32ea2c88f 47e5c7bddc624fccb0d275c5e88c0a0e msgid "None." msgstr "无。" -#: ../../en/m5ui/led.rst:30 29d179a9781d4f3e9b99cda7003a3d0c +#: ../../en/m5ui/led.rst:30 5434cb59b017455bae4809ee39441005 msgid "MicroPython Example" msgstr "MicroPython 应用示例" #: ../../en/m5ui/led.rst:38 ../../en/m5ui/led.rst:67 ../../en/m5ui/led.rst:83 -#: ../../en/m5ui/led.rst:99 ../../en/m5ui/led.rst:116 ../../en/m5ui/led.rst:133 -#: ../../en/m5ui/led.rst:150 ../../en/m5ui/led.rst:168 +#: ../../en/m5ui/led.rst:99 ../../en/m5ui/led.rst:116 ../../en/m5ui/led.rst:134 +#: ../../en/m5ui/led.rst:151 ../../en/m5ui/led.rst:168 #: ../../en/m5ui/led.rst:185 ../../en/m5ui/led.rst:202 -#: ../../en/m5ui/led.rst:219 ../../en/m5ui/led.rst:236 -#: ../../en/m5ui/led.rst:254 ../../en/m5ui/led.rst:271 -#: ../../en/m5ui/led.rst:288 ../../en/m5ui/led.rst:308 -#: db3f0fa388784215a836805cfb7c883a m5ui.led.M5LED:14 of +#: ../../en/m5ui/led.rst:220 ../../en/m5ui/led.rst:237 +#: ../../en/m5ui/led.rst:254 ../../en/m5ui/led.rst:274 +#: 1fd3fbcf6c4b484ea2d99199cc4c333e 4d3b8948b8134413975cd11fa29c11be +#: 57b6395e98544a838a02c374a5a6ef4d 5970c498190a4997aa3215b2a03a8403 +#: 7659dc2f70f54a00ac16af47d6853596 77dc481c605d4664a67529ffbcc79837 +#: 94dd56b7ad98491c93f6bcabc770b0fd ad5f0920384243dba545d856bc12c845 +#: b5d8072449b8405ba392d34aedf4a3de ba20c564a27c45f2b951a59b69e3458d +#: bb35e29911bf4af08296f63ec8152f7f cc15a7e8310647caad40792e32b0a926 +#: e3af92b752734f268aa6e302cef29b78 e50d71c62a504a0ca51274c3166fd5e6 +#: f0e5047d8baa4d559243e2b42a86e167 f7dac53f008e454b85aabdfcd4f2839d +#: fe881ae429c8411387f341949c69a44f m5ui.led.M5LED:14 +#: m5ui.led.M5LED.get_brightness:10 m5ui.led.M5LED.set_brightness:9 of msgid "MicroPython Code Block:" msgstr "MicroPython 代码块:" -#: ../../en/m5ui/led.rst:49 13a052fe8ee841ccae0656893167e489 +#: ../../en/m5ui/led.rst:49 e08e377822364a07917220f90b7babfb msgid "**API**" msgstr "API 参考" -#: b9de8f31bfbc47a8a2bfa4973871de37 m5ui.led.M5LED:1 of +#: 45186bf85c1246ea9c87ae3553de1f4e m5ui.led.M5LED:1 of msgid "Bases: :py:class:`~lvgl.led`" msgstr "" -#: 2b03cfcc3ade45ef842406d0a5932316 m5ui.led.M5LED:1 of +#: 231140a70e184c26af006c8b59d5b832 m5ui.led.M5LED:1 of msgid "Create a LED object." msgstr "创建一个 LED 对象。" -#: ../../en/m5ui/led.rst 4b48887862424386b65b467ac7aed374 +#: ../../en/m5ui/led.rst 447cbb40de8a42789a490d0f920b9707 +#: 4879a0b0da4b42e4bced6b4fac0aee7d 5d3043b366284a658095c9fa9aabdd37 +#: 62eb36ab698f4ed4807976485ab1eb1f 6f324787e4de4543ae8e6aae96101f99 +#: a2c9f17a35464872bfb39d97dc138bb9 ad53cf4337a94fd0b45da94df2f23c57 +#: adf08ca1833b4328b3ae98dc27c7dc03 c45428266ee74c8b9cccd89a49464451 +#: e695c4f47ad741e9ab350b4eff5d9076 m5ui.led.M5LED.set_brightness of msgid "Parameters" msgstr "参数" -#: ../../en/m5ui/led.rst:160 ../../en/m5ui/led.rst:178 -#: ../../en/m5ui/led.rst:212 06cc5237a3c540478cd68d20f0745322 m5ui.led.M5LED:3 -#: of +#: ../../en/m5ui/led.rst:126 ../../en/m5ui/led.rst:144 +#: ../../en/m5ui/led.rst:178 0dac5425362b4fadbbefeda9eb01ce8d +#: 3de231db696f4eebb47e8d638547fb70 6f60663258c948e39aeca621f8d524a1 +#: f7344cdde58a4445a3d59823ae255a95 m5ui.led.M5LED:3 of msgid "The x position of the LED." msgstr "LED 的 x 坐标。" -#: ../../en/m5ui/led.rst:161 ../../en/m5ui/led.rst:195 -#: ../../en/m5ui/led.rst:229 dd93e2eff66f4ee4984e6b507695220c m5ui.led.M5LED:4 -#: of +#: ../../en/m5ui/led.rst:127 ../../en/m5ui/led.rst:161 +#: ../../en/m5ui/led.rst:195 4e12a0e0911d4e4a954cea23261183d5 +#: 9a9a07a4d51845ffb6ed7f63a18ffd8f 9bbd014c0631408ebeaf60d99fe7f723 +#: c86ba34560a54c1fb8864b35b0299487 m5ui.led.M5LED:4 of msgid "The y position of the LED." msgstr "LED 的 y 坐标。" -#: 37fa14ba63e24a91b1e5e7afbb2bb3b3 m5ui.led.M5LED:5 of +#: 34bf456080cf4ba697ffd861e8e7a562 m5ui.led.M5LED:5 of msgid "The size (width and height) of the LED." msgstr "LED 的大小(宽度和高度)。" -#: a1132247048a4454a4a58ec672b93a34 m5ui.led.M5LED:6 of +#: 60388bc41bab45008696b0afa562fb49 m5ui.led.M5LED:6 of msgid "The color of the LED in RGB888 format." msgstr "LED 的颜色(RGB888 格式)。" -#: 0bb5559edfe14e8d8b6a7824dcb9331b m5ui.led.M5LED:7 of +#: f99ef847d68d4dde800c3b8b6ca15542 m5ui.led.M5LED:7 of msgid "Initial state of the LED (True for ON, False for OFF)." msgstr "LED 的初始状态(True 表示打开,False 表示关闭)。" -#: d78b7a1899f84e639378e795190355d5 m5ui.led.M5LED:8 of +#: aa30ac3d8ab54be3a12bad57b8fa312a m5ui.led.M5LED:8 of msgid "" "The parent object to attach the LED to. If not specified, the LED will be" " attached to the default screen." msgstr "要将 LED 附加到的父对象。如果未指定,LED 将附加到默认屏幕。" #: ../../en/m5ui/led.rst:61 ../../en/m5ui/led.rst:77 ../../en/m5ui/led.rst:93 -#: ../../en/m5ui/led.rst:110 ../../en/m5ui/led.rst:127 -#: ../../en/m5ui/led.rst:162 ../../en/m5ui/led.rst:179 -#: ../../en/m5ui/led.rst:196 ../../en/m5ui/led.rst:248 -#: ../../en/m5ui/led.rst:265 ../../en/m5ui/led.rst:282 -#: ../../en/m5ui/led.rst:302 7c4d22e7809b45a098159ffbebc804aa -#: 8fa35ff50ac24bf09ae3ddb4add40cea m5ui.led.M5LED:12 of +#: ../../en/m5ui/led.rst:110 ../../en/m5ui/led.rst:128 +#: ../../en/m5ui/led.rst:145 ../../en/m5ui/led.rst:162 +#: ../../en/m5ui/led.rst:214 ../../en/m5ui/led.rst:231 +#: ../../en/m5ui/led.rst:248 ../../en/m5ui/led.rst:268 +#: 000e462d11ba4a6db273fac0a77e162d 0885b9c20ca44311884ce1762a103066 +#: 2012117538494d8f92b42b90f19792f8 3329da92826b47f18dcea7a34236317c +#: 3f665042a94f4ef7b0ed4b0cc5869191 45c32c33a19048d2999b5b8f95850227 +#: 4786d604f72042a09842385311af5b36 6f0cd51ecdbf473bb1a1cd575e321180 +#: 778f69e645f24e1b809d36cb88c919c8 8e6b1182cc3c45f48d919c1e54178710 +#: b1c4fa0c272c4bb29a01414c67a8d63c deddf11122fa46c08bdfecdb452ef819 +#: m5ui.led.M5LED:12 of msgid "None" -msgstr "无" +msgstr "" -#: ../../en/m5ui/led.rst:59 95cdc85d96ab4200b08024083ece515e +#: ../../en/m5ui/led.rst:59 ba97c8bb668b4087b04088e669ca41d7 msgid "Turn on the LED." msgstr "打开 LED。" -#: ../../en/m5ui/led.rst 81eb01b492e348d8ad91d72bd64f7e10 +#: ../../en/m5ui/led.rst 073c0c04685e4e2db1d7084c24573760 +#: 10eca4a218244180a3ecf7606cf52dd9 114cf0439cd44d7e81d954324f50ddb4 +#: 1770f0daf9b94a129a330bff9e2dd80c 1b2036221a9d4e61aea3c819203f48e0 +#: 2a0fd5e1e5cc48c6beb9c3c99ef4d6ef 48f519db2e5c4bf9ad60c31677c1dc98 +#: 51788c5ad5894f50a0a82e7b6294f929 5bb7f437b7bf4d53959489641fde2469 +#: 7267bd2b3879490e8a35b7838ddf7bb9 91801e1201454f839272acd1ad924d41 +#: ab9b924b68a742e8ba6b369b70f5c8cc e0437fff98b74c3dbb4c7cd0f21240bc +#: f72e1586f5354affaf44e11c97563404 m5ui.led.M5LED.get_brightness of msgid "Returns" msgstr "返回" -#: ../../en/m5ui/led.rst:65 13510135685e4268a2a5288d59b89051 +#: ../../en/m5ui/led.rst:65 0b203224f1ce4620a65a11ec7e31285e msgid "|on.png|" msgstr "" -#: ../../en/refs/m5ui.led.ref:1 06843d737bac488bb96d08597998b590 +#: ../../en/refs/m5ui.led.ref:1 38ad5df376714057a22f726ac861efdb msgid "on.png" msgstr "" -#: ../../en/m5ui/led.rst:75 c72f1526a1e442f48d108788d9b7df28 +#: ../../en/m5ui/led.rst:75 cffa342b3f77491291fcc26425941d3e msgid "Turn off the LED." msgstr "关闭 LED。" -#: ../../en/m5ui/led.rst:81 0a6eb0f4f55348698782f61ca5612f32 +#: ../../en/m5ui/led.rst:81 168ba90734bc49e49836d6259287dc59 msgid "|set_state.png|" msgstr "" -#: ../../en/refs/m5ui.led.ref:2 c68fbda5acd34fdab28f705b0b114c31 +#: ../../en/refs/m5ui.led.ref:2 6fedf21c358c46deaec72bd5fc0dfe20 msgid "set_state.png" msgstr "" -#: ../../en/m5ui/led.rst:91 da7762418e4c458f9d6d633de55fc699 +#: ../../en/m5ui/led.rst:91 518047b3087842b9b7065d9b633c76a6 msgid "Toggle the state of a LED." msgstr "切换 LED 的状态。" -#: ../../en/m5ui/led.rst:97 13510135685e4268a2a5288d59b89051 +#: ../../en/m5ui/led.rst:97 b58dd15b518341c3b0e9656b4cc5e1d0 msgid "|toggle.png|" msgstr "" -#: ../../en/refs/m5ui.led.ref:3 06843d737bac488bb96d08597998b590 +#: ../../en/refs/m5ui.led.ref:3 82a6ba5ad1de43328fc0f92872cf38fd msgid "toggle.png" msgstr "" -#: ../../en/m5ui/led.rst:107 d70f90480ef242aba3f4b6ae57f911fa +#: ../../en/m5ui/led.rst:107 7d5376e49314436a814d1504ffed4519 msgid "Set the color of the LED." msgstr "设置 LED 的颜色。" -#: ../../en/m5ui/led.rst:109 9536adb67aca4c3490e9d44e8d718e3f +#: ../../en/m5ui/led.rst:109 7480bd59eb4e4cfc8f04aff2f8b86fe8 msgid "The color of the LED (RGB888 format)." msgstr "LED 的颜色(RGB888 格式)。" -#: ../../en/m5ui/led.rst:114 0a6eb0f4f55348698782f61ca5612f32 +#: ../../en/m5ui/led.rst:114 5f0bd158011943799406fa5c8a46464b msgid "|set_color.png|" msgstr "" -#: ../../en/refs/m5ui.led.ref:6 82db32b366154a68912d1a484e906648 +#: ../../en/refs/m5ui.led.ref:6 341e96d2f72f4b779778d1ae2e8c194e msgid "set_color.png" msgstr "" -#: ../../en/m5ui/led.rst:124 5b57eeae1e664d4aa0c67f70570c487d -msgid "Set the brightness of the LED." -msgstr "设置 LED 的亮度。" - -#: ../../en/m5ui/led.rst:126 99e59fa79b4041d7bf651efc0af62cb0 -msgid "Brightness value, range: 80 ~ 255 (80 = dark, 255 = light)." -msgstr "亮度值,范围:80 ~ 255(80 = 暗,255 = 亮)。" - -#: ../../en/m5ui/led.rst:131 89b0a317e2e64ee1a71ddfe9e826399a -msgid "|set_brightness.png|" -msgstr "" - -#: ../../en/refs/m5ui.led.ref:5 c43a0b5fb19445ccb7e338b0e4673cca -msgid "set_brightness.png" -msgstr "" - -#: ../../en/m5ui/led.rst:141 363e1ccd635044d39a0d54d82a3eeb4d -msgid "Get the brightness of the LED." -msgstr "获取 LED 的亮度。" - -#: ../../en/m5ui/led.rst:143 7cf84ffea4b1408dac3a90ebbc904067 -msgid "The brightness value of the LED." -msgstr "LED 的亮度值。" - -#: ../../en/m5ui/led.rst b8d758dd39aa4a35a1a06aa72e99267a -msgid "Return type" -msgstr "返回类型" - -#: ../../en/m5ui/led.rst:148 bc5ad52e9e814bafa80921459188ab83 -msgid "|get_brightness.png|" -msgstr "" - -#: ../../en/refs/m5ui.led.ref:4 71822bc0863f4286af7fd0f8eb012bfa -msgid "get_brightness.png" -msgstr "" - -#: ../../en/m5ui/led.rst:158 06cc5237a3c540478cd68d20f0745322 +#: ../../en/m5ui/led.rst:124 a14c7206807a411f8057bcbbb6808ae8 msgid "Set the position of the LED." msgstr "设置 LED 的位置。" -#: ../../en/m5ui/led.rst:166 0a6eb0f4f55348698782f61ca5612f32 +#: ../../en/m5ui/led.rst:132 5db27a90adbd4811a29cc405f386c987 msgid "|set_pos.png|" msgstr "" -#: ../../en/refs/m5ui.led.ref:9 c68fbda5acd34fdab28f705b0b114c31 +#: ../../en/refs/m5ui.led.ref:9 8afcb18c86304e5bb67570dbfbf15f5d msgid "set_pos.png" msgstr "" -#: ../../en/m5ui/led.rst:176 06cc5237a3c540478cd68d20f0745322 +#: ../../en/m5ui/led.rst:142 5fdee37118d644c1baddc5cf28647063 msgid "Set the x position of the LED." msgstr "设置 LED 的 x 坐标。" -#: ../../en/m5ui/led.rst:183 0a6eb0f4f55348698782f61ca5612f32 +#: ../../en/m5ui/led.rst:149 aa858069bc944d558a80893b0854fde5 msgid "|set_x.png|" msgstr "" -#: ../../en/refs/m5ui.led.ref:10 c68fbda5acd34fdab28f705b0b114c31 +#: ../../en/refs/m5ui.led.ref:10 fadae12b3f3b45eea03d72d739acf40f msgid "set_x.png" msgstr "" -#: ../../en/m5ui/led.rst:193 dd93e2eff66f4ee4984e6b507695220c +#: ../../en/m5ui/led.rst:159 e59e7d81a3fb4e8d9c700f1003f08566 msgid "Set the y position of the LED." msgstr "设置 LED 的 y 坐标。" -#: ../../en/m5ui/led.rst:200 0a6eb0f4f55348698782f61ca5612f32 +#: ../../en/m5ui/led.rst:166 e65467b852c1482fb3b82e4898caf260 msgid "|set_y.png|" msgstr "" -#: ../../en/refs/m5ui.led.ref:11 c68fbda5acd34fdab28f705b0b114c31 +#: ../../en/refs/m5ui.led.ref:11 577610aa995f48eba05c4111ab66e75f msgid "set_y.png" msgstr "" -#: ../../en/m5ui/led.rst:210 06cc5237a3c540478cd68d20f0745322 +#: ../../en/m5ui/led.rst:176 1e091cf08fd24ef09193ce8d0a7e076c msgid "Get the x position of the LED." msgstr "获取 LED 的 x 坐标。" -#: ../../en/m5ui/led.rst:217 0a6eb0f4f55348698782f61ca5612f32 +#: ../../en/m5ui/led.rst 2877da7f2bad4b7996acf420c2407d10 +#: 3e3331767e1145efbc86ddadabd27968 724b517a9a9a47f4bce33bd33095d6e2 +#: m5ui.led.M5LED.get_brightness of +msgid "Return type" +msgstr "返回类型" + +#: ../../en/m5ui/led.rst:183 cd420f6e51aa4fdcab40f4dbc451cd11 msgid "|get_x.png|" msgstr "" -#: ../../en/refs/m5ui.led.ref:7 2612ef0e389f43449ef95aaad7207f46 +#: ../../en/refs/m5ui.led.ref:7 5f021229fe4e4630a2ac38e7a868cb92 msgid "get_x.png" msgstr "" -#: ../../en/m5ui/led.rst:227 dd93e2eff66f4ee4984e6b507695220c +#: ../../en/m5ui/led.rst:193 dbe6d45a3c114933a6fa02c0afae3411 msgid "Get the y position of the LED." msgstr "获取 LED 的 y 坐标。" -#: ../../en/m5ui/led.rst:234 0a6eb0f4f55348698782f61ca5612f32 +#: ../../en/m5ui/led.rst:200 fb186493e5f147a3a7c253d431a4ff13 msgid "|get_y.png|" msgstr "" -#: ../../en/refs/m5ui.led.ref:8 2612ef0e389f43449ef95aaad7207f46 +#: ../../en/refs/m5ui.led.ref:8 e22cf95fcf4b4e4c98d688848f7c5e9d msgid "get_y.png" msgstr "" -#: ../../en/m5ui/led.rst:244 d70f90480ef242aba3f4b6ae57f911fa +#: ../../en/m5ui/led.rst:210 936eaf01aed34e7aa4f0b4d854110e9f msgid "Set the size of the LED." msgstr "设置 LED 的大小。" -#: ../../en/m5ui/led.rst:246 ../../en/m5ui/led.rst:264 -#: f6d45ebb803a4d8d9869f351a10b38a0 +#: ../../en/m5ui/led.rst:212 ../../en/m5ui/led.rst:230 +#: 964b45d7177c4340904b4a3eb6832c11 d7115cb036214aeebf46d07ae622b3ee msgid "The width of the LED." msgstr "LED 的宽度。" -#: ../../en/m5ui/led.rst:247 ../../en/m5ui/led.rst:281 -#: f6d45ebb803a4d8d9869f351a10b38a0 +#: ../../en/m5ui/led.rst:213 ../../en/m5ui/led.rst:247 +#: 6b5f3e810e984cd08a14e98e606e6edf 755ceb2391374b94912b8287e1f50b88 msgid "The height of the LED." msgstr "LED 的高度。" -#: ../../en/m5ui/led.rst:252 0a6eb0f4f55348698782f61ca5612f32 +#: ../../en/m5ui/led.rst:218 c6af51b7340e4010ab32c3432974c238 msgid "|set_size.png|" msgstr "" -#: ../../en/refs/m5ui.led.ref:15 c68fbda5acd34fdab28f705b0b114c31 +#: ../../en/refs/m5ui.led.ref:15 94cde1407a944393b3d2e98d8c9f0e84 msgid "set_size.png" msgstr "" -#: ../../en/m5ui/led.rst:262 d70f90480ef242aba3f4b6ae57f911fa +#: ../../en/m5ui/led.rst:228 f27fe763c3de4c338f0d7a37f83d2dcc msgid "Set the width of the LED." msgstr "设置 LED 的宽度。" -#: ../../en/m5ui/led.rst:269 0a6eb0f4f55348698782f61ca5612f32 +#: ../../en/m5ui/led.rst:235 b5d4c52bd1844ddf92de30c77f4b013a msgid "|set_width.png|" msgstr "" -#: ../../en/refs/m5ui.led.ref:16 c68fbda5acd34fdab28f705b0b114c31 +#: ../../en/refs/m5ui.led.ref:16 16d53043eef84abbbb0ab23b0021f831 msgid "set_width.png" msgstr "" -#: ../../en/m5ui/led.rst:279 5b57eeae1e664d4aa0c67f70570c487d +#: ../../en/m5ui/led.rst:245 982e4c780085462cbb5f1c8da6e11a81 msgid "Set the height of the LED." msgstr "设置 LED 的高度。" -#: ../../en/m5ui/led.rst:286 89b0a317e2e64ee1a71ddfe9e826399a +#: ../../en/m5ui/led.rst:252 ff46dd6481b34ad7a135ea1c92874783 msgid "|set_height.png|" msgstr "" -#: ../../en/refs/m5ui.led.ref:17 c43a0b5fb19445ccb7e338b0e4673cca +#: ../../en/refs/m5ui.led.ref:17 a8f1bfcbddfc4307a5cfd23dad949da2 msgid "set_height.png" msgstr "" -#: ../../en/m5ui/led.rst:296 8e0690f870eb403bbe6f4b3e099d13e0 +#: ../../en/m5ui/led.rst:262 ee66654e8eb146cf99b1778e37a364c8 msgid "Align the LED relative to another object." msgstr "将 LED 相对于另一个对象对齐。" -#: ../../en/m5ui/led.rst:298 f9fc1cfa9d974a17bfc87b493ef13283 +#: ../../en/m5ui/led.rst:264 a11faadbb9ad43129cab32dda63c68c8 msgid "The reference object (e.g. page0)." -msgstr "" +msgstr "引用对象(例如 page0)。" -#: ../../en/m5ui/led.rst:299 feb68f38f854494190ffac0c65e0caa2 +#: ../../en/m5ui/led.rst:265 4c091af30c38461c9be103b84e094c01 msgid "Alignment option (see lv.ALIGN constants below)." msgstr "对齐选项(参见下方 lv.ALIGN 常量)。" -#: ../../en/m5ui/led.rst:300 3fb3a67665d24542b09a9d3056205290 +#: ../../en/m5ui/led.rst:266 88f9b6e027e546d68974e2138f6ec95e msgid "X offset after alignment." msgstr "对齐后的 X 偏移。" -#: ../../en/m5ui/led.rst:301 7fc0124ed5e14afeb4bf03b03abcd839 +#: ../../en/m5ui/led.rst:267 c31e780c61554df1a2096f70fabb33c5 msgid "Y offset after alignment." msgstr "对齐后的 Y 偏移。" -#: ../../en/m5ui/led.rst:306 0a6eb0f4f55348698782f61ca5612f32 +#: ../../en/m5ui/led.rst:272 3ce01b49976f4e089c482832220cdd58 msgid "|align_to.png|" msgstr "" -#: ../../en/refs/m5ui.led.ref:12 06843d737bac488bb96d08597998b590 +#: ../../en/refs/m5ui.led.ref:12 cbde9379eaa147bf96f3270d9d7c164c msgid "align_to.png" msgstr "" -#: ../../en/m5ui/led.rst:316 52e7b2822eea49d4af7649ffcf3cc08e +#: ../../en/m5ui/led.rst:282 25ecac70abb74a048c7111c6dc41691c msgid "Alignment options for positioning objects." msgstr "用于定位对象的对齐选项。" -#: ../../en/m5ui/led.rst:318 eaeb667a06b34f2091ef9f2f7b542c63 +#: ../../en/m5ui/led.rst:284 39663f57f2784b5099cb56534db794eb msgid "lv.ALIGN.DEFAULT" msgstr "" -#: ../../en/m5ui/led.rst:319 d12f1c9155134d86b7e15bdc430cd244 +#: ../../en/m5ui/led.rst:285 68ff5d51584b4950a17da064f32588b4 msgid "lv.ALIGN.TOP_LEFT" msgstr "" -#: ../../en/m5ui/led.rst:320 eb24fb905a4a4e9bb4bc4fff07d0ffac +#: ../../en/m5ui/led.rst:286 0af754fd0e9a45bf86215011c98e3fc9 msgid "lv.ALIGN.TOP_MID" msgstr "" -#: ../../en/m5ui/led.rst:321 d10a2adc8f864426ac68ee863663e34d +#: ../../en/m5ui/led.rst:287 c392cfde26924f99a42e908631899da5 msgid "lv.ALIGN.TOP_RIGHT" msgstr "" -#: ../../en/m5ui/led.rst:322 7c2a96f6b4f142ce9e8081f40333874a +#: ../../en/m5ui/led.rst:288 8255cf21446344e4959c28a69ac32579 msgid "lv.ALIGN.BOTTOM_LEFT" msgstr "" -#: ../../en/m5ui/led.rst:323 21f1737f125e4dc382203ad614cc3280 +#: ../../en/m5ui/led.rst:289 794a12886c6f4d998da064555bcd70ca msgid "lv.ALIGN.BOTTOM_MID" msgstr "" -#: ../../en/m5ui/led.rst:324 b7306e65637b43ab9134680d8a4fef62 +#: ../../en/m5ui/led.rst:290 69e91663b9194f7ab6ed2f1fdb5471f0 msgid "lv.ALIGN.BOTTOM_RIGHT" msgstr "" -#: ../../en/m5ui/led.rst:325 41cd3d19a86f4fd79c0660571fd42ef4 +#: ../../en/m5ui/led.rst:291 aa973c90f09c44919628063ec00136a2 msgid "lv.ALIGN.LEFT_MID" msgstr "" -#: ../../en/m5ui/led.rst:326 edec3bcd17094a189d2b7c8571b48342 +#: ../../en/m5ui/led.rst:292 217c65145a56430fb6cacf9dc45ac268 msgid "lv.ALIGN.RIGHT_MID" msgstr "" -#: ../../en/m5ui/led.rst:327 c698a185166e458ba821b04b5ab64360 +#: ../../en/m5ui/led.rst:293 946b11a5f41842b882f92d7b97d71329 msgid "lv.ALIGN.CENTER" msgstr "" -#: ../../en/m5ui/led.rst:328 613c104d2f104a4aa88797cefdb7751f +#: ../../en/m5ui/led.rst:294 a39141f6d53f401cbce9090004daa73b msgid "lv.ALIGN.OUT_TOP_LEFT" msgstr "" -#: ../../en/m5ui/led.rst:329 d0758ba31b9b4a8ca47aac7d89bc063d +#: ../../en/m5ui/led.rst:295 f45d0fe5c9414fbca4bafc39f45f1d94 msgid "lv.ALIGN.OUT_TOP_MID" msgstr "" -#: ../../en/m5ui/led.rst:330 d4369f62c57542c2b983eafe8274ca6d +#: ../../en/m5ui/led.rst:296 487bcd5553f442398ddc8396669d5b4c msgid "lv.ALIGN.OUT_TOP_RIGHT" msgstr "" -#: ../../en/m5ui/led.rst:331 d4a07186d69d4b1192bca641f3e80c40 +#: ../../en/m5ui/led.rst:297 b272d509d2324498857734c4a5dee612 msgid "lv.ALIGN.OUT_BOTTOM_LEFT" msgstr "" -#: ../../en/m5ui/led.rst:332 9bd211e14df44e11b61df9176fb55e10 +#: ../../en/m5ui/led.rst:298 06448a66b1484befa9841f3d9928bf84 msgid "lv.ALIGN.OUT_BOTTOM_MID" msgstr "" -#: ../../en/m5ui/led.rst:333 fa4ba1fd752a4829b8e6d5a7ce40b32f +#: ../../en/m5ui/led.rst:299 4da2fb4e1bb543a9beab3cfc7eb27bb9 msgid "lv.ALIGN.OUT_BOTTOM_RIGHT" msgstr "" -#: ../../en/m5ui/led.rst:334 ab668faee47d4d4f8bf4325dca8cd316 +#: ../../en/m5ui/led.rst:300 9e573d3fe73b49d98af8a85f70037c0a msgid "lv.ALIGN.OUT_LEFT_TOP" msgstr "" -#: ../../en/m5ui/led.rst:335 ea14ca3e58f943d6850f3c753405aeee +#: ../../en/m5ui/led.rst:301 a5ffb28a011e4f629b72199aa664e12d msgid "lv.ALIGN.OUT_LEFT_MID" msgstr "" -#: ../../en/m5ui/led.rst:336 d425f9467fc24a2cb520eb443773f094 +#: ../../en/m5ui/led.rst:302 56159053801d46dead584ca185656eb4 msgid "lv.ALIGN.OUT_LEFT_BOTTOM" msgstr "" -#: ../../en/m5ui/led.rst:337 85fdff55da324e69b410271baf108cdc +#: ../../en/m5ui/led.rst:303 dcfafe5ddc57421491413a044f9b0c82 msgid "lv.ALIGN.OUT_RIGHT_TOP" msgstr "" -#: ../../en/m5ui/led.rst:338 96c144a0876c493485e16c284baad05a +#: ../../en/m5ui/led.rst:304 622b40ce2dfa42e1b3eb227438a7fdda msgid "lv.ALIGN.OUT_RIGHT_MID" msgstr "" -#: ../../en/m5ui/led.rst:339 16ee12515d644eadabaa8e06b8c99f33 +#: ../../en/m5ui/led.rst:305 622b3277449246a29abd451a7933c3d0 msgid "lv.ALIGN.OUT_RIGHT_BOTTOM" msgstr "" + +#: b19ca9270759482e97c8c2dda5aa7704 m5ui.led.M5LED.set_brightness:1 of +msgid "Set the brightness of the LED." +msgstr "设置 LED 的亮度。" + +#: 01cb2b161c804a8b997435b77ce4feea m5ui.led.M5LED.set_brightness:3 of +msgid "Brightness level (0-100). Will be mapped to 80-255 internally." +msgstr "亮度等级 (0-100)。内部将映射到 80-255。" + +#: da88c931790d4416b769246bcc2ff7fc m5ui.led.M5LED.set_brightness:7 of +msgid "|set_brightness.png|" +msgstr "" + +#: ../../en/refs/m5ui.led.ref:5 7698d9db07a14b7db4d50472b6882458 +msgid "set_brightness.png" +msgstr "" + +#: 275518dab24440c59fc621c2f11800dc m5ui.led.M5LED.get_brightness:1 of +msgid "Get the brightness of the LED." +msgstr "获取 LED 的亮度。" + +#: e3755ed6d5254e80a3748fea124f22bd m5ui.led.M5LED.get_brightness:3 of +msgid "Brightness level (0-100)." +msgstr "亮度级别(0-100)。" + +#: 01ccd7d3f11142e29371e812fde109cb m5ui.led.M5LED.get_brightness:8 of +msgid "|get_brightness.png|" +msgstr "" + +#: ../../en/refs/m5ui.led.ref:4 8351631dd6f444c39acdb9914971b6df +msgid "get_brightness.png" +msgstr "" + +#~ msgid "Brightness value, range: 80 ~ 255 (80 = dark, 255 = light)." +#~ msgstr "亮度值,范围:80 ~ 255(80 = 暗,255 = 亮)。" + +#~ msgid "The brightness value of the LED." +#~ msgstr "LED 的亮度值。" + diff --git a/m5stack/libs/m5ui/led.py b/m5stack/libs/m5ui/led.py index f85c0dfe..60501909 100644 --- a/m5stack/libs/m5ui/led.py +++ b/m5stack/libs/m5ui/led.py @@ -4,6 +4,7 @@ from .base import M5Base import lvgl as lv +import warnings class M5LED(lv.led): @@ -46,6 +47,60 @@ def __init__(self, x=0, y=0, size=50, color=0x00FF00, on=True, parent=None): def set_color(self, color: int): super().set_color(lv.color_hex(color)) + def set_brightness(self, brightness: int): + """Set the brightness of the LED. + + :param int brightness: Brightness level (0-100). Will be mapped to 80-255 internally. + + UiFlow2 Code Block: + + |set_brightness.png| + + MicroPython Code Block: + + .. code-block:: python + + led_0.set_brightness(50) # Set brightness to 50% + """ + # Clamp input to [0, 100] + if brightness < 0: + brightness = 0 + warnings.warn("Brightness below 0, clamped to 0") + elif brightness > 100: + brightness = 100 + warnings.warn("Brightness above 100, clamped to 100") + + # Map 0..100 -> 80..255 linearly + # 0 -> 80, 100 -> 255 + mapped = 80 + (175 * brightness + 50) // 100 # integer rounding + super().set_brightness(int(mapped)) + + def get_brightness(self) -> int: + """Get the brightness of the LED. + + :return: Brightness level (0-100). + :rtype: int + + UiFlow2 Code Block: + + |get_brightness.png| + + MicroPython Code Block: + + .. code-block:: python + + brightness = led_0.get_brightness() + """ + raw_brightness = super().get_brightness() + # Map 80..255 -> 0..100 linearly with rounding to match set_brightness mapping + if raw_brightness <= 80: + return 0 + if raw_brightness >= 255: + return 100 + # round to nearest: add half of denominator (175//2 == 87) + mapped = ((raw_brightness - 80) * 100 + 87) // 175 + return int(mapped) + def __getattr__(self, name): if hasattr(M5Base, name): method = getattr(M5Base, name) diff --git a/tests/m5ui/test_led.py b/tests/m5ui/test_led.py new file mode 100644 index 00000000..30e335bb --- /dev/null +++ b/tests/m5ui/test_led.py @@ -0,0 +1,32 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import lvgl as lv +import sys +import time + +sys.path.append("../../m5stack/libs") +import m5ui +import unittest + + +class Test(unittest.TestCase): + def __init__(self) -> None: + super().__init__() + m5ui.init() + page0 = m5ui.M5Page() + self.led0 = m5ui.M5LED(x=123, y=109, size=20, color=0x00FF00, on=False, parent=page0) + page0.screen_load() + + def test_brightness(self): + for br in range(0, 101): + self.led0.set_brightness(br) + self.assertEqual(self.led0.get_brightness(), br) + + def tearDown(self): + time.sleep(3) + + +if __name__ == "__main__": + unittest.main() From 817d579e96c86607dac547758e2678e1309604fa Mon Sep 17 00:00:00 2001 From: tinyu Date: Tue, 21 Oct 2025 14:25:19 +0800 Subject: [PATCH 284/322] libs/m5ui: Add LVGL Menu widget. Signed-off-by: tinyu --- m5stack/libs/m5ui/__init__.py | 1 + m5stack/libs/m5ui/manifest.py | 1 + m5stack/libs/m5ui/menu.py | 157 ++++++++++++++++++++++++++++++++++ 3 files changed, 159 insertions(+) create mode 100644 m5stack/libs/m5ui/menu.py diff --git a/m5stack/libs/m5ui/__init__.py b/m5stack/libs/m5ui/__init__.py index a237e276..51541011 100644 --- a/m5stack/libs/m5ui/__init__.py +++ b/m5stack/libs/m5ui/__init__.py @@ -20,6 +20,7 @@ "M5LED": "led", "M5Line": "line", "M5List": "list", + "M5Menu": "menu", "M5Msgbox": "msgbox", "M5Page": "page", "M5Roller": "roller", diff --git a/m5stack/libs/m5ui/manifest.py b/m5stack/libs/m5ui/manifest.py index 37439d92..871197b6 100644 --- a/m5stack/libs/m5ui/manifest.py +++ b/m5stack/libs/m5ui/manifest.py @@ -21,6 +21,7 @@ "led.py", "line.py", "list.py", + "menu.py", "msgbox.py", "page.py", "port.py", diff --git a/m5stack/libs/m5ui/menu.py b/m5stack/libs/m5ui/menu.py new file mode 100644 index 00000000..b670568d --- /dev/null +++ b/m5stack/libs/m5ui/menu.py @@ -0,0 +1,157 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +from m5ui.base import M5Base +import lvgl as lv +import m5ui + + +class M5Menu(lv.menu): + """Create a list object. + + :param int x: The x position of the menu. + :param int y: The y position of the menu. + :param int w: The width of the menu. + :param int h: The height of the menu. + :param lv.obj parent: The parent object to attach the menu to. If not specified, the menu will be attached to the default screen. + + UiFlow2 Code Block: + + None + + MicroPython Code Block: + + .. code-block:: python + + from m5ui import M5Menu + import lvgl as lv + + m5ui.init() + menu0 = M5Menu(x=120, y=80, w=60, h=30, parent=page0) + """ + + def __init__( + self, + x=0, + y=0, + w=0, + h=0, + parent=None, + ): + if parent is None: + parent = lv.screen_active() + super().__init__(parent) + self.set_pos(x, y) + self.set_size(w, h) + self.main_page = lv.menu_page(self, None) # Create a main page + + def add_label( + self, + text, + text_c=0x212121, + text_opa=255, + bg_c=0xFFFFFF, + bg_opa=255, + font=lv.font_montserrat_14, + parent=None, + ): + """Add a label to the menu. + + :param str text: The text to display on the label. + :param int text_c: The text color of the label. + :param int bg_c: The background color of the label. + :param int bg_opa: The background opacity of the label. + :param lv.font_t font: The font of the label. + :param lv.obj parent: The parent object to attach the label to. If not specified, the label will be attached to the main page of the menu. + :return: The created label object. + :rtype: :ref:`m5ui.M5Label ` + + UiFlow2 Code Block: + + |add_label.png| + + |add_label2.png| + + MicroPython Code Block: + + .. code-block:: python + + label0 = menu0.add_label("Hello, World!", text_c=0x212121, bg_c=0xFFFFFF, bg_opa=255, font=lv.font_montserrat_14, parent=menu0.main_page) + """ + if parent is None: + parent = self.main_page + _cont = lv.menu_cont(parent) + _label = m5ui.M5Label( + text=text, text_c=text_c, bg_c=bg_c, bg_opa=bg_opa, font=font, parent=_cont + ) + _label.cont = _cont + _label.set_style_text_opa(text_opa, lv.PART.MAIN | lv.STATE.DEFAULT) + return _label + + def add_switch( + self, + text, + w=50, + h=20, + bg_c=0xE7E3E7, + bg_opa=255, + bg_c_checked=0x0288FB, + bg_c_checked_opa=255, + circle_c=0xFFFFFF, + circle_opa=255, + parent=None, + ): + """Add a switch to the menu. + + :param str text: The text to display next to the switch. + :param int w: The width of the switch. + + :param int h: The height of the switch. + :param int bg_c: The background color of the switch when unchecked. + :param int bg_c_checked: The background color of the switch when checked. + :param int circle_c: The color of the switch circle. + :param lv.obj parent: The parent object to attach the switch to. If not specified, the switch will be attached to the main page of the menu. + :return: The created switch object. + :rtype: :ref:`m5ui.M5Switch ` + + UiFlow2 Code Block: + + |add_switch.png| + + |add_switch2.png| + + MicroPython Code Block: + + .. code-block:: python + + switch_0 = menu0.add_switch("Switch 1", w=50, h=20, bg_c=0xE7E3E7, bg_c_checked=0x0288FB, circle_c=0xFFFFFF, parent=menu0.main_page) + """ + + if parent is None: + parent = self.main_page + _cont = lv.menu_cont(parent) + _label = lv.label(_cont) + _label.set_text(text) + _label.set_long_mode(lv.label.LONG_MODE.SCROLL_CIRCULAR) + _switch = m5ui.M5Switch( + w=w, + h=h, + bg_c=bg_c, + bg_c_checked=bg_c_checked, + circle_c=circle_c, + parent=_cont, + ) + _switch.set_style_bg_opa(bg_opa, lv.PART.MAIN | lv.STATE.DEFAULT) + _switch.set_style_bg_opa(bg_c_checked_opa, lv.PART.INDICATOR | lv.STATE.CHECKED) + _switch.set_style_bg_opa(circle_opa, lv.PART.KNOB | lv.STATE.DEFAULT) + + return _switch + + def __getattr__(self, name): + if hasattr(M5Base, name): + method = getattr(M5Base, name) + bound_method = lambda *args, **kwargs: method(self, *args, **kwargs) + setattr(self, name, bound_method) + return bound_method + raise AttributeError(f"'{self.__class__.__name__}' object has no attribute '{name}'") From b5014999670505ad15fba7a1b6a3097fd0880b3c Mon Sep 17 00:00:00 2001 From: tinyu Date: Tue, 21 Oct 2025 14:25:50 +0800 Subject: [PATCH 285/322] libs/m5ui: Add LVGL Menu widget docs and example. Signed-off-by: tinyu --- docs/en/m5ui/index.rst | 1 + docs/en/m5ui/menu.rst | 125 ++++++++ docs/en/m5ui/switch.rst | 1 + docs/en/refs/m5ui.menu.ref | 20 ++ docs/locales/zh_CN/LC_MESSAGES/m5ui/menu.po | 328 ++++++++++++++++++++ examples/m5ui/menu/menu_core2_example.m5f2 | 1 + examples/m5ui/menu/menu_core2_example.py | 166 ++++++++++ 7 files changed, 642 insertions(+) create mode 100644 docs/en/m5ui/menu.rst create mode 100644 docs/en/refs/m5ui.menu.ref create mode 100644 docs/locales/zh_CN/LC_MESSAGES/m5ui/menu.po create mode 100644 examples/m5ui/menu/menu_core2_example.m5f2 create mode 100644 examples/m5ui/menu/menu_core2_example.py diff --git a/docs/en/m5ui/index.rst b/docs/en/m5ui/index.rst index 523f8133..22ff7c0c 100644 --- a/docs/en/m5ui/index.rst +++ b/docs/en/m5ui/index.rst @@ -73,6 +73,7 @@ Classes led.rst line.rst list.rst + menu.rst msgbox.rst roller.rst scale.rst diff --git a/docs/en/m5ui/menu.rst b/docs/en/m5ui/menu.rst new file mode 100644 index 00000000..a0334061 --- /dev/null +++ b/docs/en/m5ui/menu.rst @@ -0,0 +1,125 @@ +.. currentmodule:: m5ui + +M5Menu +======== + +.. include:: ../refs/m5ui.menu.ref + +M5Menu is a widget that can be used to create multi-level menus in the user interface. + +UiFlow2 Example +--------------- + +menu event +^^^^^^^^^^^^^^ + +Open the |menu_core2_example.m5f2| project in UiFlow2. + +This example creates a multi-level menus. + +UiFlow2 Code Block: + + |menu_core2_example.png| + +Example output: + + None + +MicroPython Example +------------------- + +menu event +^^^^^^^^^^^^^^ + +This example creates a multi-level menus. + +MicroPython Code Block: + + .. literalinclude:: ../../../examples/m5ui/menu/menu_core2_example.py + :language: python + :linenos: + +Example output: + + None + + +**API** +------- +M5Menu +^^^^^^^^ + +.. autoclass:: m5ui.menu.M5Menu + :members: + + .. py:method:: set_page(page) + + Set main page for the menu. + + :param lv.obj page: The main page object. + + UiFlow2 Code Block: + + |set_page.png| + + MicroPython Code Block: + + .. code-block:: python + + menu0.set_page(menu0.main_page) + + .. py:method:: set_mode_header(mode) + + Set the mode header for the menu. + + :param int mode: The mode header text. + + Options: + + - ``lv.menu.HEADER.TOP_FIXED`` + - ``lv.menu.HEADER.TOP_UNFIXED`` + - ``lv.menu.HEADER.BOTTOM_FIXED`` + + UiFlow2 Code Block: + + |set_mode_header.png| + + MicroPython Code Block: + + .. code-block:: python + + menu0.set_mode_header(lv.menu.HEADER.TOP_FIXED) + + .. py:method:: set_pos(x, y) + + Set the position of the menu. + + :param int x: The x-coordinate of the menu. + :param int y: The y-coordinate of the menu. + + UiFlow2 Code Block: + + |set_pos.png| + + MicroPython Code Block: + + .. code-block:: python + + menu0.set_pos(100, 100) + + .. py:method:: set_size(width, height) + + Set the size of the menu. + + :param int width: The width of the menu. + :param int height: The height of the menu. + + UiFlow2 Code Block: + + |set_size.png| + + MicroPython Code Block: + + .. code-block:: python + + menu0.set_size(100, 50) \ No newline at end of file diff --git a/docs/en/m5ui/switch.rst b/docs/en/m5ui/switch.rst index 0cbea024..49f935a2 100644 --- a/docs/en/m5ui/switch.rst +++ b/docs/en/m5ui/switch.rst @@ -1,4 +1,5 @@ .. currentmodule:: m5ui +.. _m5ui.M5Switch: M5Switch ======== diff --git a/docs/en/refs/m5ui.menu.ref b/docs/en/refs/m5ui.menu.ref new file mode 100644 index 00000000..65556e8d --- /dev/null +++ b/docs/en/refs/m5ui.menu.ref @@ -0,0 +1,20 @@ +.. |set_page.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/menu/set_page.png +.. |set_mode_header.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/menu/set_mode_header.png +.. |set_pos.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/menu/set_pos.png +.. |set_size.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/menu/set_size.png +.. |add_label.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/menu/add_label.png +.. |add_label2.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/menu/add_label2.png +.. |add_switch.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/menu/add_switch.png +.. |add_switch2.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/menu/add_switch2.png + + +.. |menu_core2_example.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/menu/example.png + +.. |menu_core2_example.m5f2| raw:: html + + + menu_core2_example.m5f2 + \ No newline at end of file diff --git a/docs/locales/zh_CN/LC_MESSAGES/m5ui/menu.po b/docs/locales/zh_CN/LC_MESSAGES/m5ui/menu.po new file mode 100644 index 00000000..ce32ea35 --- /dev/null +++ b/docs/locales/zh_CN/LC_MESSAGES/m5ui/menu.po @@ -0,0 +1,328 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2016 - 2025 M5Stack Technology Co., Ltd +# This file is distributed under the same license as the UIFlow2 Programming +# Guide package. +# FIRST AUTHOR , 2025. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: UIFlow2 Programming Guide \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-09-27 17:17+0800\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: zh_CN\n" +"Language-Team: zh_CN \n" +"Plural-Forms: nplurals=1; plural=0;\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.16.0\n" + +#: ../../en/m5ui/menu.rst:4 ../../en/m5ui/menu.rst:50 +#: b002356162034699bac4296792fd317e c8cecd2bc8704d5eba21b3fc0fc21f7e +msgid "M5Menu" +msgstr "M5Menu" + +#: ../../en/m5ui/menu.rst:8 0848d8ae571d4a96ab3c468f4ade190b +msgid "" +"M5Menu is a widget that can be used to create multi-level menus in the " +"user interface." +msgstr "M5Menu 是一个用于在用户界面中创建多级菜单的组件。" + +#: ../../en/m5ui/menu.rst:11 7238a7b978f040cdb08a08c62c66dc45 +msgid "UiFlow2 Example" +msgstr "UiFlow2 示例" + +#: ../../en/m5ui/menu.rst:14 ../../en/m5ui/menu.rst:32 +#: 727a3973c7d94854aa803f269c97765c +msgid "menu event" +msgstr "菜单事件" + +#: ../../en/m5ui/menu.rst:16 298a332bd8f04bfb82efbff1151787f6 +msgid "Open the |menu_core2_example.m5f2| project in UiFlow2." +msgstr "在 UiFlow2 中打开 |menu_core2_example.m5f2| 项目。" + +#: ../../en/m5ui/menu.rst:18 ../../en/m5ui/menu.rst:34 +#: 505ce99474c54f62840974a64ec30277 b45bdb9b954c42abb47692beb707dee9 +msgid "This example creates a multi-level menus." +msgstr "本示例创建了一个多级菜单。" + +#: ../../en/m5ui/menu.rst:20 ../../en/m5ui/menu.rst:61 +#: ../../en/m5ui/menu.rst:83 ../../en/m5ui/menu.rst:100 +#: ../../en/m5ui/menu.rst:117 117fc01a664c4dfe89260fd3ca3e620d +#: m5ui.menu.M5Menu:9 m5ui.menu.M5Menu.add_label:12 +#: m5ui.menu.M5Menu.add_switch:14 of +msgid "UiFlow2 Code Block:" +msgstr "UiFlow2 代码块:" + +#: ../../en/m5ui/menu.rst:22 e8d630b7e5c04740b8d4db411d2005c7 +msgid "|menu_core2_example.png|" +msgstr "" + +#: ../../en/refs/m5ui.menu.ref:9 df4c0117f6c443a89ceb6436dc301457 +msgid "menu_core2_example.png" +msgstr "" + +#: ../../en/m5ui/menu.rst:24 ../../en/m5ui/menu.rst:42 +#: 71843c511f4542fea44ed89ad4b4e90d +msgid "Example output:" +msgstr "示例输出:" + +#: ../../en/m5ui/menu.rst:26 ../../en/m5ui/menu.rst:44 +#: d67807bfb92d438696e2099a54fa26ee m5ui.menu.M5Menu:11 of +msgid "None" +msgstr "" + +#: ../../en/m5ui/menu.rst:29 5a27301cab0246e285497857514efb00 +msgid "MicroPython Example" +msgstr "MicroPython 示例" + +#: ../../en/m5ui/menu.rst:36 ../../en/m5ui/menu.rst:65 +#: ../../en/m5ui/menu.rst:87 ../../en/m5ui/menu.rst:104 +#: ../../en/m5ui/menu.rst:121 8d9119d3e49f4526b695b6de472a2269 +#: m5ui.menu.M5Menu:13 m5ui.menu.M5Menu.add_label:16 +#: m5ui.menu.M5Menu.add_switch:18 of +msgid "MicroPython Code Block:" +msgstr "MicroPython 代码块:" + +#: ../../en/m5ui/menu.rst:48 b23b13ec056c4ab0a69250e712cbf646 +msgid "**API**" +msgstr "" + +#: 0a82de82acac41bf82579f8cab417087 m5ui.menu.M5Menu:1 of +msgid "Bases: :py:class:`~lvgl.menu`" +msgstr "" + +#: 68512e43e0b84fcb965cb5b45ca978de m5ui.menu.M5Menu:1 of +msgid "Create a list object." +msgstr "创建一个列表对象。" + +#: ../../en/m5ui/menu.rst 00c29291fbb641b6a7b3eed27fa361d9 +#: m5ui.menu.M5Menu.add_label m5ui.menu.M5Menu.add_switch of +msgid "Parameters" +msgstr "参数" + +#: bc0a4c8e8a1e4dbfb883d0e081ad4cc9 m5ui.menu.M5Menu:3 of +msgid "The x position of the menu." +msgstr "菜单的 x 坐标位置。" + +#: bc0a4c8e8a1e4dbfb883d0e081ad4cc9 m5ui.menu.M5Menu:4 of +msgid "The y position of the menu." +msgstr "菜单的 y 坐标位置。" + +#: ../../en/m5ui/menu.rst:114 ac7b991bbb014aa4ad9c35732ca72aaf +#: m5ui.menu.M5Menu:5 of +msgid "The width of the menu." +msgstr "菜单的宽度。" + +#: ../../en/m5ui/menu.rst:115 fd3b0c0e972543f4b83e21a5ee21789b +#: m5ui.menu.M5Menu:6 of +msgid "The height of the menu." +msgstr "菜单的高度。" + +#: 877b4455e5964b84a099cbea8cb8b6a0 m5ui.menu.M5Menu:7 of +msgid "" +"The parent object to attach the menu to. If not specified, the menu will " +"be attached to the default screen." +msgstr "要附加菜单的父对象。如果未指定,菜单会附加到默认屏幕。" + +#: ../../en/m5ui/menu.rst:57 dcd6ea52eb08465781eb8e6afbef5fc2 +msgid "Set main page for the menu." +msgstr "为菜单设置主页面。" + +#: ../../en/m5ui/menu.rst:59 306b64274a124f81afb402c68eeff115 +msgid "The main page object." +msgstr "主页面对象。" + +#: ../../en/m5ui/menu.rst:63 caa00bda651541daa530e566b9aa9aa4 +msgid "|set_page.png|" +msgstr "" + +#: ../../en/refs/m5ui.menu.ref:1 58d0b807152a4cb5948e18b515833e4e +msgid "set_page.png" +msgstr "" + +#: ../../en/m5ui/menu.rst:73 22a64323436f44389aa345d6dabefcc2 +msgid "Set the mode header for the menu." +msgstr "设置菜单的模式头部。" + +#: ../../en/m5ui/menu.rst:75 7f9664625b05453e91f220dc6941eb4e +msgid "" +"The mode header text. Options: - ``lv.menu.HEADER.TOP_FIXED`` -" +" ``lv.menu.HEADER.TOP_UNFIXED`` - ``lv.menu.HEADER.BOTTOM_FIXED``" +msgstr "模式头部文本。 选项:- ``lv.menu.HEADER.TOP_FIXED`` - ``lv.menu.HEADER.TOP_UNFIXED`` - ``lv.menu.HEADER.BOTTOM_FIXED``" + +#: ../../en/m5ui/menu.rst:75 67b5aa011561441ba66fde854eacdf4b +msgid "The mode header text." +msgstr "模式头部文本。" + +#: ../../en/m5ui/menu.rst:77 4de8a50126e04091ad30fe51045a9dbb +msgid "Options:" +msgstr "选项:" + +#: ../../en/m5ui/menu.rst:79 2a62218832094acaa3f14a8907f9c4b0 +msgid "``lv.menu.HEADER.TOP_FIXED``" +msgstr "``lv.menu.HEADER.TOP_FIXED``" + +#: ../../en/m5ui/menu.rst:80 5f4209a398224d9a988ad138ec20fa66 +msgid "``lv.menu.HEADER.TOP_UNFIXED``" +msgstr "``lv.menu.HEADER.TOP_UNFIXED``" + +#: ../../en/m5ui/menu.rst:81 71842d84ec3f48d98e83b742af0cc786 +msgid "``lv.menu.HEADER.BOTTOM_FIXED``" +msgstr "``lv.menu.HEADER.BOTTOM_FIXED``" + +#: ../../en/m5ui/menu.rst:85 32cd6f79be21441c9054887c893cae03 +msgid "|set_mode_header.png|" +msgstr "" + +#: ../../en/refs/m5ui.menu.ref:2 629bdd469fb946969b42a4688313dedf +msgid "set_mode_header.png" +msgstr "" + +#: ../../en/m5ui/menu.rst:95 bc0a4c8e8a1e4dbfb883d0e081ad4cc9 +msgid "Set the position of the menu." +msgstr "设置菜单的位置。" + +#: ../../en/m5ui/menu.rst:97 055bc45beaaf483f9f58db6beab64341 +msgid "The x-coordinate of the menu." +msgstr "菜单的 x 坐标。" + +#: ../../en/m5ui/menu.rst:98 700a8567d2d64ff5bebb9959e180bc9b +msgid "The y-coordinate of the menu." +msgstr "菜单的 y 坐标。" + +#: ../../en/m5ui/menu.rst:102 6c003355babe4b2ab98ff72f8c3d2edd +msgid "|set_pos.png|" +msgstr "" + +#: ../../en/refs/m5ui.menu.ref:3 041cae90a5bd464798af7e7f15cf79ff +msgid "set_pos.png" +msgstr "" + +#: ../../en/m5ui/menu.rst:112 9ab8973ffdc74ce294e790ba81cc0e23 +msgid "Set the size of the menu." +msgstr "设置菜单的尺寸。" + +#: ../../en/m5ui/menu.rst:119 2aafedacbdc64a52be865cd9af615ffc +msgid "|set_size.png|" +msgstr "" + +#: ../../en/refs/m5ui.menu.ref:4 cff27d7ea8c7476897930b91ef8b20cc +msgid "set_size.png" +msgstr "" + +#: 38f8844c6f0e403b8ca33760f48ea8d9 m5ui.menu.M5Menu.add_label:1 of +msgid "Add a label to the menu." +msgstr "向菜单添加一个标签。" + +#: 7295825c775b41db97d50616c985e858 m5ui.menu.M5Menu.add_label:3 of +msgid "The text to display on the label." +msgstr "标签上要显示的文本。" + +#: 36034266289844fb88f978abf317f0a6 m5ui.menu.M5Menu.add_label:4 of +msgid "The text color of the label." +msgstr "标签的文本颜色。" + +#: f100e5dae1f84367977d7cb88b6691b4 m5ui.menu.M5Menu.add_label:5 of +msgid "The background color of the label." +msgstr "标签的背景颜色。" + +#: 98da860f799c4ac9bd34cc2ebcfc8107 m5ui.menu.M5Menu.add_label:6 of +msgid "The background opacity of the label." +msgstr "标签背景的不透明度。" + +#: 36034266289844fb88f978abf317f0a6 m5ui.menu.M5Menu.add_label:7 of +msgid "The font of the label." +msgstr "标签的字体。" + +#: 877b4455e5964b84a099cbea8cb8b6a0 m5ui.menu.M5Menu.add_label:8 of +msgid "" +"The parent object to attach the label to. If not specified, the label " +"will be attached to the main page of the menu." +msgstr "要附加标签的父对象。如果未指定,标签将附加到菜单的主页面。" + +#: 4da60e9e1a674bc48535263a53c4ed92 m5ui.menu.M5Menu.add_label +#: m5ui.menu.M5Menu.add_switch of +msgid "Returns" +msgstr "返回" + +#: 306b64274a124f81afb402c68eeff115 m5ui.menu.M5Menu.add_label:9 of +msgid "The created label object." +msgstr "创建的标签对象。" + +#: 773ed29216b64489877153d004010a1a m5ui.menu.M5Menu.add_label +#: m5ui.menu.M5Menu.add_switch of +msgid "Return type" +msgstr "返回类型" + +#: bf9d7fb1b73841d0aed089dff44467db m5ui.menu.M5Menu.add_label:10 of +msgid "" +":ref:`m5ui.M5Label ` UiFlow2 Code Block: " +"|add_label.png|" +msgstr "" + +#: 22778bf038c249879d8c6db8ca7ff07d m5ui.menu.M5Menu.add_label:10 of +msgid ":ref:`m5ui.M5Label `" +msgstr ":ref:`m5ui.M5Label `" + +#: b3d85c37d5e0476ab8a98fa77f65095d m5ui.menu.M5Menu.add_label:14 of +msgid "|add_label.png|" +msgstr "" + +#: ../../en/refs/m5ui.menu.ref:5 66f2fa29a8214a1bac0adf720d9c70df +msgid "add_label.png" +msgstr "" + +#: 7a34b85aea20499cb15de7ba351d8090 m5ui.menu.M5Menu.add_switch:1 of +msgid "Add a switch to the menu." +msgstr "向菜单添加一个开关。" + +#: 7295825c775b41db97d50616c985e858 m5ui.menu.M5Menu.add_switch:3 of +msgid "The text to display next to the switch." +msgstr "开关旁显示的文本。" + +#: ac7b991bbb014aa4ad9c35732ca72aaf m5ui.menu.M5Menu.add_switch:4 of +msgid "The width of the switch." +msgstr "开关的宽度。" + +#: fd3b0c0e972543f4b83e21a5ee21789b m5ui.menu.M5Menu.add_switch:6 of +msgid "The height of the switch." +msgstr "开关的高度。" + +#: f100e5dae1f84367977d7cb88b6691b4 m5ui.menu.M5Menu.add_switch:7 of +msgid "The background color of the switch when unchecked." +msgstr "开关未选中时的背景颜色。" + +#: 58ba7043a53a4aca8c6a820f10fd5865 m5ui.menu.M5Menu.add_switch:8 of +msgid "The background color of the switch when checked." +msgstr "开关选中时的背景颜色。" + +#: 6791b866281243b59d8f6e76071525d4 m5ui.menu.M5Menu.add_switch:9 of +msgid "The color of the switch circle." +msgstr "开关圆圈的颜色。" + +#: 877b4455e5964b84a099cbea8cb8b6a0 m5ui.menu.M5Menu.add_switch:10 of +msgid "" +"The parent object to attach the switch to. If not specified, the switch " +"will be attached to the main page of the menu." +msgstr "要附加开关的父对象。如果未指定,开关将附加到菜单的主页面。" + +#: 379f16791f9b4fb6be440e36e3997bd0 m5ui.menu.M5Menu.add_switch:11 of +msgid "The created switch object." +msgstr "创建的开关对象。" + +#: b42613764d0f4569b7cbadcc2b6a77c9 m5ui.menu.M5Menu.add_switch:12 of +msgid ":ref:`m5ui.M5Switch `" +msgstr "" + +#: ca67896047824c9187502f11e5a47fd4 m5ui.menu.M5Menu.add_switch:16 of +msgid "|add_switch.png|" +msgstr "" + +#: ../../en/refs/m5ui.menu.ref:6 12e5e78c540b43c69fccc3f96078c4f9 +msgid "add_switch.png" +msgstr "" + diff --git a/examples/m5ui/menu/menu_core2_example.m5f2 b/examples/m5ui/menu/menu_core2_example.m5f2 new file mode 100644 index 00000000..d491108f --- /dev/null +++ b/examples/m5ui/menu/menu_core2_example.m5f2 @@ -0,0 +1 @@ +{"version":"V2.0","versionNumber":"V2.3.6","type":"core2","components":[{"name":"page0","type":"lvgl_page","layer":0,"screenId":"builtin","screenName":"","id":"eE-S8#gL0$DB!KNF","createTime":1758959299674,"backgroundColor":"#ffffff","isLVGL":true,"isSelected":true},{"name":"menu0","type":"lvgl_menu","layer":1,"screenId":"builtin","screenName":"","id":"kHrNxo#Sv1+xyYhX","createTime":1758959305184,"x":0,"y":0,"width":320,"height":240,"childList":[{"type":"label","name":"Label1","value":"Label1"},{"type":"label","name":"Label2","value":"Label2"}],"showBackBtn":true,"isInit":true,"mode":"TOP_FIXED","modeOption":[{"label":"Top Fixed","value":"TOP_FIXED"},{"label":"Top Unfixed","value":"TOP_UNFIXED"},{"label":"Bottom Fixed","value":"BOTTOM_FIXED"}],"pageId":"eE-S8#gL0$DB!KNF","isLVGL":true,"isSelected":false}],"resources":[{"hardware":["hardware_button","hardware_pin_button","imu","speaker","touch","mic","sdcard"]}],"units":[],"hats":[],"caps":[],"bases":[],"i2cs":[],"blockly":"truepage0menu0Label2lv.font_montserrat_24menu0Label2Click me!!!!!!!!menu0Page_2Page2menu0Label2Page_2menu0Page2_Label1Page_2Label1menu0Page2_Label2lv.font_montserrat_24Page_2Label2 (Click me)palette#ff0000255palette#ffffff255menu0Page_3Page3menu0Page2_Label2Page_3menu0Page3_LabelPage_3A Labelmenu0Page3_Labellv.font_montserrat_24menu0Page3_SwitchPage_3A Switch8040palette#e7e3e7255palette#2196f3255palette#ff9966255truemenu0Page3_SwitchCHECKEDmenu0Page3_Label0x0000000xFFFFFF255menu0Page3_SwitchUNCHECKEDmenu0Page3_Label0x0000000xFFFFFF255","screen":[{"simulationName":"Built-in","type":"builtin","width":320,"height":240,"scale":0.77,"screenName":"","blockId":"","screenColorType":0,"rotation":1,"id":"builtin","createTime":1758959299672}],"logicWhenNum":0,"customList":[]} \ No newline at end of file diff --git a/examples/m5ui/menu/menu_core2_example.py b/examples/m5ui/menu/menu_core2_example.py new file mode 100644 index 00000000..1a5cb6e8 --- /dev/null +++ b/examples/m5ui/menu/menu_core2_example.py @@ -0,0 +1,166 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import os, sys, io +import M5 +from M5 import * +import m5ui +import lvgl as lv + + +page0 = None +menu0 = None +Label1 = None +Label2 = None +Page_2 = None +Page2_Label1 = None +Page2_Label2 = None +Page_3 = None +Page3_Label = None +Page3_Switch = None + + +import random + + +def Page3_Switch_checked_event(event_struct): # noqa: N802 + global \ + page0, \ + menu0, \ + Label1, \ + Label2, \ + Page_2, \ + Page2_Label1, \ + Page2_Label2, \ + Page_3, \ + Page3_Label, \ + Page3_Switch + + Page3_Label.set_text_color(random.randint(0x000000, 0xFFFFFF), 255, 0) + + +def Page3_Switch_unchecked_event(event_struct): # noqa: N802 + global \ + page0, \ + menu0, \ + Label1, \ + Label2, \ + Page_2, \ + Page2_Label1, \ + Page2_Label2, \ + Page_3, \ + Page3_Label, \ + Page3_Switch + + Page3_Label.set_text_color(random.randint(0x000000, 0xFFFFFF), 255, 0) + + +def Page3_Switch_event_handler(event_struct): # noqa: N802 + global \ + page0, \ + menu0, \ + Label1, \ + Label2, \ + Page_2, \ + Page2_Label1, \ + Page2_Label2, \ + Page_3, \ + Page3_Label, \ + Page3_Switch + event = event_struct.code + obj = event_struct.get_target_obj() + if event == lv.EVENT.VALUE_CHANGED: + if obj.has_state(lv.STATE.CHECKED): + Page3_Switch_checked_event(event_struct) + else: + Page3_Switch_unchecked_event(event_struct) + return + + +def setup(): + global \ + page0, \ + menu0, \ + Label1, \ + Label2, \ + Page_2, \ + Page2_Label1, \ + Page2_Label2, \ + Page_3, \ + Page3_Label, \ + Page3_Switch + + M5.begin() + Widgets.setRotation(1) + m5ui.init() + page0 = m5ui.M5Page(bg_c=0xFFFFFF) + menu0 = m5ui.M5Menu(x=0, y=0, w=320, h=240, parent=page0) + menu0.set_page(menu0.main_page) + Label1 = menu0.add_label("Label1") + Label2 = menu0.add_label("Label2") + menu0.set_mode_root_back_button(lv.menu.ROOT_BACK_BUTTON.ENABLED) + menu0.set_mode_header(lv.menu.HEADER.TOP_FIXED) + + page0.screen_load() + Label2.set_style_text_font(lv.font_montserrat_24, lv.PART.MAIN | lv.STATE.DEFAULT) + Label2.set_text(str("Click me!!!!!!!!")) + Page_2 = lv.menu_page(menu0, "Page2") + menu0.set_load_page_event(Label2.cont, Page_2) + Page2_Label1 = menu0.add_label("Label1", parent=Page_2) + Page2_Label2 = menu0.add_label( + "Label2 (Click me)", + text_c=0xFF0000, + text_opa=255, + bg_c=0xFFFFFF, + bg_opa=255, + font=lv.font_montserrat_24, + parent=Page_2, + ) + Page_3 = lv.menu_page(menu0, "Page3") + menu0.set_load_page_event(Page2_Label2.cont, Page_3) + Page3_Label = menu0.add_label("A Label", parent=Page_3) + Page3_Label.set_style_text_font(lv.font_montserrat_24, lv.PART.MAIN | lv.STATE.DEFAULT) + Page3_Switch = menu0.add_switch( + "A Switch", + w=80, + h=40, + bg_c=0xE7E3E7, + bg_opa=255, + bg_c_checked=0x2196F3, + bg_c_checked_opa=255, + circle_c=0xFF9966, + circle_opa=255, + parent=Page_3, + ) + Page3_Switch.add_event_cb(Page3_Switch_event_handler, lv.EVENT.ALL, None) + + +def loop(): + global \ + page0, \ + menu0, \ + Label1, \ + Label2, \ + Page_2, \ + Page2_Label1, \ + Page2_Label2, \ + Page_3, \ + Page3_Label, \ + Page3_Switch + M5.update() + + +if __name__ == "__main__": + try: + setup() + while True: + loop() + except (Exception, KeyboardInterrupt) as e: + try: + m5ui.deinit() + from utility import print_error_msg + + print_error_msg(e) + except ImportError: + print("please update to latest firmware") From 191428938f76534659277e6e9045a2469f45809d Mon Sep 17 00:00:00 2001 From: hlym123 Date: Fri, 10 Oct 2025 16:51:54 +0800 Subject: [PATCH 286/322] libs/unit: Add support for Fingerprint2Unit. Signed-off-by: hlym123 --- docs/en/refs/unit.fingerprint2.ref | 66 ++ docs/en/units/fingerprint2.rst | 123 ++ docs/en/units/index.rst | 1 + .../zh_CN/LC_MESSAGES/units/fingerprint2.po | 1016 +++++++++++++++++ .../m5cores3_fp2_basic_example.m5f2 | 1 + .../m5cores3_fp2_basic_example.py | 429 +++++++ ..._fp2_template_upload_download_example.m5f2 | 1 + ...s3_fp2_template_upload_download_example.py | 651 +++++++++++ .../m5cores3_fp2_upload_image_example.m5f2 | 1 + .../m5cores3_fp2_upload_image_example.py | 121 ++ m5stack/libs/unit/__init__.py | 1 + m5stack/libs/unit/fingerprint2.py | 839 ++++++++++++++ m5stack/libs/unit/manifest.py | 1 + 13 files changed, 3251 insertions(+) create mode 100644 docs/en/refs/unit.fingerprint2.ref create mode 100644 docs/en/units/fingerprint2.rst create mode 100644 docs/locales/zh_CN/LC_MESSAGES/units/fingerprint2.po create mode 100644 examples/unit/fingerprint2/m5cores3_fp2_basic_example.m5f2 create mode 100644 examples/unit/fingerprint2/m5cores3_fp2_basic_example.py create mode 100644 examples/unit/fingerprint2/m5cores3_fp2_template_upload_download_example.m5f2 create mode 100644 examples/unit/fingerprint2/m5cores3_fp2_template_upload_download_example.py create mode 100644 examples/unit/fingerprint2/m5cores3_fp2_upload_image_example.m5f2 create mode 100644 examples/unit/fingerprint2/m5cores3_fp2_upload_image_example.py create mode 100644 m5stack/libs/unit/fingerprint2.py diff --git a/docs/en/refs/unit.fingerprint2.ref b/docs/en/refs/unit.fingerprint2.ref new file mode 100644 index 00000000..532f46a1 --- /dev/null +++ b/docs/en/refs/unit.fingerprint2.ref @@ -0,0 +1,66 @@ +.. |Unit Fingerprint2| image:: https://m5stack-doc.oss-cn-shenzhen.aliyuncs.com/1186/U203_01.webp + :target: https://docs.m5stack.com/en/products/sku/U203 + :height: 200px + :width: 200px + +.. |init.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/fingerprint2/init.png +.. |get_verify_image.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/fingerprint2/get_verify_image.png +.. |get_enroll_image.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/fingerprint2/get_enroll_image.png +.. |gen_feature.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/fingerprint2/gen_feature.png +.. |gen_template.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/fingerprint2/gen_template.png +.. |store_template.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/fingerprint2/store_template.png +.. |load_template.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/fingerprint2/load_template.png +.. |delete_template.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/fingerprint2/delete_template.png +.. |delete_all_template.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/fingerprint2/delete_all_template.png +.. |upload_template.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/fingerprint2/upload_template.png +.. |download_template.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/fingerprint2/download_template.png +.. |upload_image.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/fingerprint2/upload_image.png +.. |get_valid_template_num.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/fingerprint2/get_valid_template_num.png +.. |get_stored_template_id.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/fingerprint2/get_stored_template_id.png +.. |find_match.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/fingerprint2/find_match.png +.. |match.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/fingerprint2/match.png +.. |is_connected.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/fingerprint2/is_connected.png +.. |activate_module.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/fingerprint2/activate_module.png +.. |set_work_mode.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/fingerprint2/set_work_mode.png +.. |get_work_mode.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/fingerprint2/get_work_mode.png +.. |set_auto_sleep_time.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/fingerprint2/set_auto_sleep_time.png +.. |get_auto_sleep_time.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/fingerprint2/get_auto_sleep_time.png +.. |get_work_status.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/fingerprint2/get_work_status.png +.. |get_firmware_version.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/fingerprint2/get_firmware_version.png +.. |set_led_breath.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/fingerprint2/set_led_breath.png +.. |set_led_color.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/fingerprint2/set_led_color.png + + +.. |m5cores3_fp2_basic_example.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/fingerprint2/basic_example.png + +.. |m5cores3_fp2_basic_example.m5f2| raw:: html + + + m5cores3_fp2_basic_example.m5f2 + + +.. |m5cores3_fp2_template_upload_download_example.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/fingerprint2/template_upload_download_example.png + +.. |m5cores3_fp2_template_upload_download_example.m5f2| raw:: html + + + m5cores3_fp2_template_upload_download_example.m5f2 + + +.. |m5cores3_fp2_upload_image_example.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/fingerprint2/upload_image_example.png + +.. |m5cores3_fp2_upload_image_example.m5f2| raw:: html + + + m5cores3_fp2_upload_image_example.m5f2 + + \ No newline at end of file diff --git a/docs/en/units/fingerprint2.rst b/docs/en/units/fingerprint2.rst new file mode 100644 index 00000000..dc0532a1 --- /dev/null +++ b/docs/en/units/fingerprint2.rst @@ -0,0 +1,123 @@ +Fingerprint2 Unit +================= + +.. sku: U203 + +.. include:: ../refs/unit.fingerprint2.ref + +This library is the driver for Unit Fingerprint2. + +Support the following products: + + |Unit Fingerprint2| + +UiFlow2 Example +--------------- + +Enroll and recognize +^^^^^^^^^^^^^^^^^^^^ + +Open the |m5cores3_fp2_basic_example.m5f2| project in UiFlow2. + +This example demonstrates how to use a fingerprint recognition module to perform +the complete process of fingerprint enrollment, identification, and deletion. + +UiFlow2 Code Block: + + |m5cores3_fp2_basic_example.png| + +Example output: + + None + +Upload and download template +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Open the |m5cores3_fp2_template_upload_download_example.m5f2| project in UiFlow2. + +This example demonstrates how to use a fingerprint recognition module to perform the complete process of fingerprint enrollment, identification, deletion, +and template upload/download.(The upload and download functions enable cross-device fingerprint recognition — a fingerprint enrolled on one module can be verified on another. +The fingerprint template transfer method can be customized according to user requirements, such as via serial communication, network, or cloud synchronization.) + +UiFlow2 Code Block: + + |m5cores3_fp2_template_upload_download_example.png| + +Example output: + + None + +Upload adn display fingerprint image +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Open the |m5cores3_fp2_upload_image_example.m5f2| project in UiFlow2. + +This example demonstrates how to upload and display the fingerprint image. + +UiFlow2 Code Block: + + |m5cores3_fp2_upload_image_example.png| + +Example output: + + None + +MicroPython Example +------------------- + +Enroll and recognize +^^^^^^^^^^^^^^^^^^^^ + +This example demonstrates how to use a fingerprint recognition module to perform +the complete process of fingerprint enrollment, identification, and deletion. + +MicroPython Code Block: + + .. literalinclude:: ../../../examples/unit/fingerprint2/m5cores3_fp2_basic_example.py + :language: python + :linenos: + +Example output: + + None + +Upload and download template +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +This example demonstrates how to use a fingerprint recognition module to perform the complete process of fingerprint enrollment, identification, deletion, +and template upload/download.(The upload and download functions enable cross-device fingerprint recognition — a fingerprint enrolled on one module can be verified on another. +The fingerprint template transfer method can be customized according to user requirements, such as via serial communication, network, or cloud synchronization.) + +MicroPython Code Block: + + .. literalinclude:: ../../../examples/unit/fingerprint2/m5cores3_fp2_template_upload_download_example.py + :language: python + :linenos: + +Example output: + + None + +Upload adn display fingerprint image +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +This example demonstrates how to upload and display the fingerprint image. + +MicroPython Code Block: + + .. literalinclude:: ../../../examples/unit/fingerprint2/m5cores3_fp2_upload_image_example.py + :language: python + :linenos: + +Example output: + + None + +**API** +------- + +Fingerprint2Unit +^^^^^^^^^^^^^^^^ + +.. autoclass:: unit.fingerprint2.Fingerprint2Unit + :members: diff --git a/docs/en/units/index.rst b/docs/en/units/index.rst index 0a7c6e8f..c85e46fb 100644 --- a/docs/en/units/index.rst +++ b/docs/en/units/index.rst @@ -44,6 +44,7 @@ Unit extio2.rst fader.rst finger.rst + fingerprint2.rst flash_light.rst gateway_h2.rst glass.rst diff --git a/docs/locales/zh_CN/LC_MESSAGES/units/fingerprint2.po b/docs/locales/zh_CN/LC_MESSAGES/units/fingerprint2.po new file mode 100644 index 00000000..72609648 --- /dev/null +++ b/docs/locales/zh_CN/LC_MESSAGES/units/fingerprint2.po @@ -0,0 +1,1016 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2016 - 2025 M5Stack Technology Co., Ltd +# This file is distributed under the same license as the UIFlow2 Programming +# Guide package. +# FIRST AUTHOR , 2025. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: UIFlow2 Programming Guide \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-10-10 16:28+0800\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: zh_CN\n" +"Language-Team: zh_CN \n" +"Plural-Forms: nplurals=1; plural=0;\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.16.0\n" + +#: ../../en/units/fingerprint2.rst:2 c051023fc4ea43beb39a558732fd15f0 +msgid "Fingerprint2 Unit" +msgstr "" + +#: ../../en/units/fingerprint2.rst:8 57aa5122e038445da4829605eb917f2c +msgid "This library is the driver for Unit Fingerprint2." +msgstr "此库是 Unit Fingerprint2 的驱动程序。" + +#: ../../en/units/fingerprint2.rst:10 2f6e6781284c4455aa62d86bcef114a6 +msgid "Support the following products:" +msgstr "支持以下产品:" + +#: ../../en/units/fingerprint2.rst:12 dd56c40223f44e599ec631ae795748c5 +msgid "|Unit Fingerprint2|" +msgstr "|Unit Fingerprint2|" + +#: ../../en/refs/unit.fingerprint2.ref 8ef13580abd848a991d812cd7396f35b +msgid "Unit Fingerprint2" +msgstr "Unit Fingerprint2" + +#: ../../en/units/fingerprint2.rst:15 2d8131ded56b42858cec9349b411d3a8 +msgid "UiFlow2 Example" +msgstr "UiFlow2 应用示例" + +#: ../../en/units/fingerprint2.rst:18 ../../en/units/fingerprint2.rst:69 +#: 640658f9c5934ffa85988cba25167c32 +msgid "Enroll and recognize" +msgstr "录入与识别" + +#: ../../en/units/fingerprint2.rst:20 b0e43048825e49e4b56ffc58816f5e13 +msgid "Open the |m5cores3_fp2_basic_example.m5f2| project in UiFlow2." +msgstr "在 UiFlow2 上打开 |m5cores3_fp2_basic_example.m5f2| 项目。" + +#: ../../en/units/fingerprint2.rst:22 ../../en/units/fingerprint2.rst:71 +#: 64304efbb45c4b74afad7d1c96991e26 +msgid "" +"This example demonstrates how to use a fingerprint recognition module to " +"perform the complete process of fingerprint enrollment, identification, " +"and deletion." +msgstr "本案例演示了如何使用指纹识别模块实现指纹录入、识别与删除的完整流程。" + +#: ../../en/units/fingerprint2.rst:25 ../../en/units/fingerprint2.rst:42 +#: ../../en/units/fingerprint2.rst:57 092ad019634e470889da561f4de67bbb of +#: unit.fingerprint2.Fingerprint2Unit.activate_module:3 +#: unit.fingerprint2.Fingerprint2Unit.delete_all_template:8 +#: unit.fingerprint2.Fingerprint2Unit.delete_template:9 +#: unit.fingerprint2.Fingerprint2Unit.download_template:9 +#: unit.fingerprint2.Fingerprint2Unit.find_match:10 +#: unit.fingerprint2.Fingerprint2Unit.gen_feature:9 +#: unit.fingerprint2.Fingerprint2Unit.gen_template:8 +#: unit.fingerprint2.Fingerprint2Unit.get_auto_sleep_time:9 +#: unit.fingerprint2.Fingerprint2Unit.get_enroll_image:6 +#: unit.fingerprint2.Fingerprint2Unit.get_firmware_version:6 +#: unit.fingerprint2.Fingerprint2Unit.get_stored_template_id:10 +#: unit.fingerprint2.Fingerprint2Unit.get_valid_template_num:8 +#: unit.fingerprint2.Fingerprint2Unit.get_verify_image:6 +#: unit.fingerprint2.Fingerprint2Unit.get_work_mode:10 +#: unit.fingerprint2.Fingerprint2Unit.get_work_status:6 +#: unit.fingerprint2.Fingerprint2Unit.is_connected:6 +#: unit.fingerprint2.Fingerprint2Unit.load_template:11 +#: unit.fingerprint2.Fingerprint2Unit.match:8 +#: unit.fingerprint2.Fingerprint2Unit.set_auto_sleep_time:10 +#: unit.fingerprint2.Fingerprint2Unit.set_led_breath:19 +#: unit.fingerprint2.Fingerprint2Unit.set_led_color:17 +#: unit.fingerprint2.Fingerprint2Unit.set_work_mode:6 +#: unit.fingerprint2.Fingerprint2Unit.store_template:9 +#: unit.fingerprint2.Fingerprint2Unit.upload_image:11 +#: unit.fingerprint2.Fingerprint2Unit.upload_template:9 +msgid "UiFlow2 Code Block:" +msgstr "UiFlow2 代码块:" + +#: ../../en/units/fingerprint2.rst:27 57bb9a2b8b6c420198e4e223af49834e +msgid "|m5cores3_fp2_basic_example.png|" +msgstr "" + +#: ../../en/refs/unit.fingerprint2.ref:34 c87145f892884bf6a0baebf22bc083d6 +msgid "m5cores3_fp2_basic_example.png" +msgstr "" + +#: ../../en/units/fingerprint2.rst:29 ../../en/units/fingerprint2.rst:46 +#: ../../en/units/fingerprint2.rst:61 ../../en/units/fingerprint2.rst:80 +#: ../../en/units/fingerprint2.rst:97 ../../en/units/fingerprint2.rst:112 +#: 447e38e8b6e7489a892282f4044992dd +msgid "Example output:" +msgstr "示例输出:" + +#: ../../en/units/fingerprint2.rst:31 ../../en/units/fingerprint2.rst:48 +#: ../../en/units/fingerprint2.rst:63 ../../en/units/fingerprint2.rst:82 +#: ../../en/units/fingerprint2.rst:99 ../../en/units/fingerprint2.rst:114 +#: 90087bea2b1f44f8bd3e961a75848657 +msgid "None" +msgstr "无" + +#: ../../en/units/fingerprint2.rst:34 ../../en/units/fingerprint2.rst:85 +#: 13a751f621774f4c8161f5022660deea +msgid "Upload and download template" +msgstr "上传与下载指纹模板" + +#: ../../en/units/fingerprint2.rst:36 b0e43048825e49e4b56ffc58816f5e13 +msgid "" +"Open the |m5cores3_fp2_template_upload_download_example.m5f2| project in " +"UiFlow2." +msgstr "在 UiFlow2 上打开 |m5cores3_fp2_template_upload_download_example.m5f2| 项目。" + +#: ../../en/units/fingerprint2.rst:38 ../../en/units/fingerprint2.rst:87 +#: e14148889d254406b4caf0ea42f488bb +msgid "" +"This example demonstrates how to use a fingerprint recognition module to " +"perform the complete process of fingerprint enrollment, identification, " +"deletion, and template upload/download.(The upload and download functions" +" enable cross-device fingerprint recognition — a fingerprint enrolled on " +"one module can be verified on another. The fingerprint template transfer " +"method can be customized according to user requirements, such as via " +"serial communication, network, or cloud synchronization.)" +msgstr "本案例演示了如何使用指纹识别模块实现指纹录入、识别、删除以及指纹模板的上传、下载的完整流程。(指纹模板的上传与下载功能可实现跨设备的指纹识别,即可以在一个指纹模块上录入指纹,在另一个指纹模块上进行验证识别。指纹模板的传输方式可根据用户需求进行灵活设计,例如通过串口、网络或云端进行数据同步。)" + +#: ../../en/units/fingerprint2.rst:44 57bb9a2b8b6c420198e4e223af49834e +msgid "|m5cores3_fp2_template_upload_download_example.png|" +msgstr "" + +#: ../../en/refs/unit.fingerprint2.ref:45 7eddab8286454987a8ae5d35c572bd06 +msgid "m5cores3_fp2_template_upload_download_example.png" +msgstr "" + +#: ../../en/units/fingerprint2.rst:51 ../../en/units/fingerprint2.rst:102 +#: eed3c9687e104c72bdb5f3d7fa9f845a +msgid "Upload adn display fingerprint image" +msgstr "上传和显示指纹图像" + +#: ../../en/units/fingerprint2.rst:53 da3136b61ee94457acd7a284cbbb11e7 +msgid "Open the |m5cores3_fp2_upload_image_example.m5f2| project in UiFlow2." +msgstr "在 UiFlow2 上打开 |m5cores3_fp2_upload_image_example.m5f2| 项目。" + +#: ../../en/units/fingerprint2.rst:55 ../../en/units/fingerprint2.rst:104 +#: 5dafe4cf3356470495b64c5de1c2a784 +msgid "This example demonstrates how to upload and display the fingerprint image." +msgstr "本示例演示如何上传并显示指纹图像。" + +#: ../../en/units/fingerprint2.rst:59 14ab60403a2e4b42821910e8e0015fa6 +msgid "|m5cores3_fp2_upload_image_example.png|" +msgstr "" + +#: ../../en/refs/unit.fingerprint2.ref:56 b3e8e133e8784744b1e15e010e35c74a +msgid "m5cores3_fp2_upload_image_example.png" +msgstr "" + +#: ../../en/units/fingerprint2.rst:66 c2d63b76dd6d4359b265ea29e02ac3c8 +msgid "MicroPython Example" +msgstr "MicroPython 示例" + +#: ../../en/units/fingerprint2.rst:74 ../../en/units/fingerprint2.rst:91 +#: ../../en/units/fingerprint2.rst:106 4c10eb36764d404b84330ec812b62ce2 of +#: unit.fingerprint2.Fingerprint2Unit.activate_module:7 +#: unit.fingerprint2.Fingerprint2Unit.delete_all_template:12 +#: unit.fingerprint2.Fingerprint2Unit.delete_template:13 +#: unit.fingerprint2.Fingerprint2Unit.download_template:13 +#: unit.fingerprint2.Fingerprint2Unit.find_match:14 +#: unit.fingerprint2.Fingerprint2Unit.gen_feature:13 +#: unit.fingerprint2.Fingerprint2Unit.gen_template:12 +#: unit.fingerprint2.Fingerprint2Unit.get_auto_sleep_time:13 +#: unit.fingerprint2.Fingerprint2Unit.get_enroll_image:10 +#: unit.fingerprint2.Fingerprint2Unit.get_firmware_version:10 +#: unit.fingerprint2.Fingerprint2Unit.get_stored_template_id:14 +#: unit.fingerprint2.Fingerprint2Unit.get_valid_template_num:12 +#: unit.fingerprint2.Fingerprint2Unit.get_verify_image:10 +#: unit.fingerprint2.Fingerprint2Unit.get_work_mode:14 +#: unit.fingerprint2.Fingerprint2Unit.get_work_status:10 +#: unit.fingerprint2.Fingerprint2Unit.is_connected:10 +#: unit.fingerprint2.Fingerprint2Unit.load_template:15 +#: unit.fingerprint2.Fingerprint2Unit.match:12 +#: unit.fingerprint2.Fingerprint2Unit.set_auto_sleep_time:14 +#: unit.fingerprint2.Fingerprint2Unit.set_led_breath:23 +#: unit.fingerprint2.Fingerprint2Unit.set_led_color:21 +#: unit.fingerprint2.Fingerprint2Unit.set_work_mode:10 +#: unit.fingerprint2.Fingerprint2Unit.store_template:13 +#: unit.fingerprint2.Fingerprint2Unit.upload_image:15 +#: unit.fingerprint2.Fingerprint2Unit.upload_template:13 +msgid "MicroPython Code Block:" +msgstr "MicroPython 代码块:" + +#: ../../en/units/fingerprint2.rst:117 bee4514136c34c9785dec1dc14c720ec +msgid "**API**" +msgstr "**API 应用**" + +#: ../../en/units/fingerprint2.rst:120 b3858e1c19f14f3ebb39e651afcea629 +msgid "Fingerprint2Unit" +msgstr "" + +#: 1be8bdc6dbfb4e18bf00a84262c8b0c5 of unit.fingerprint2.Fingerprint2Unit:1 +msgid "Bases: :py:class:`object`" +msgstr "" + +#: ../../en/units/fingerprint2.rst 4c8a9fc225f24c1f99a53084610b8ec8 of +#: unit.fingerprint2.Fingerprint2Unit.delete_template +#: unit.fingerprint2.Fingerprint2Unit.download_template +#: unit.fingerprint2.Fingerprint2Unit.load_template +#: unit.fingerprint2.Fingerprint2Unit.set_auto_sleep_time +#: unit.fingerprint2.Fingerprint2Unit.set_led_breath +#: unit.fingerprint2.Fingerprint2Unit.set_led_color +#: unit.fingerprint2.Fingerprint2Unit.set_work_mode +#: unit.fingerprint2.Fingerprint2Unit.store_template +#: unit.fingerprint2.Fingerprint2Unit.upload_image +#: unit.fingerprint2.Fingerprint2Unit.upload_template +msgid "Parameters" +msgstr "参数" + +#: 9ca11dc921cb49f9a0cbb8faf366c06f of +#: unit.fingerprint2.Fingerprint2Unit.get_verify_image:1 +msgid "Capture fingerprint image for verification." +msgstr "获取用于验证的指纹图像获" + +#: 2a412df9939b4a9c8472e9568d78c499 of +#: unit.fingerprint2.Fingerprint2Unit.delete_all_template +#: unit.fingerprint2.Fingerprint2Unit.delete_template +#: unit.fingerprint2.Fingerprint2Unit.download_template +#: unit.fingerprint2.Fingerprint2Unit.find_match +#: unit.fingerprint2.Fingerprint2Unit.gen_feature +#: unit.fingerprint2.Fingerprint2Unit.gen_template +#: unit.fingerprint2.Fingerprint2Unit.get_auto_sleep_time +#: unit.fingerprint2.Fingerprint2Unit.get_enroll_image +#: unit.fingerprint2.Fingerprint2Unit.get_firmware_version +#: unit.fingerprint2.Fingerprint2Unit.get_stored_template_id +#: unit.fingerprint2.Fingerprint2Unit.get_valid_template_num +#: unit.fingerprint2.Fingerprint2Unit.get_verify_image +#: unit.fingerprint2.Fingerprint2Unit.get_work_mode +#: unit.fingerprint2.Fingerprint2Unit.get_work_status +#: unit.fingerprint2.Fingerprint2Unit.is_connected +#: unit.fingerprint2.Fingerprint2Unit.load_template +#: unit.fingerprint2.Fingerprint2Unit.match +#: unit.fingerprint2.Fingerprint2Unit.set_led_breath +#: unit.fingerprint2.Fingerprint2Unit.set_led_color +#: unit.fingerprint2.Fingerprint2Unit.store_template +#: unit.fingerprint2.Fingerprint2Unit.upload_image +#: unit.fingerprint2.Fingerprint2Unit.upload_template +msgid "Returns" +msgstr "返回值" + +#: 3c2668b112f849279b2be5692a587a53 of +#: unit.fingerprint2.Fingerprint2Unit.get_enroll_image:3 +#: unit.fingerprint2.Fingerprint2Unit.get_verify_image:3 +msgid "True if the fingerprint image was successfully captured, False otherwise." +msgstr "如果成功获取指纹图像则返回 True,否则返回 False。" + +#: c8642f93730a4e2a887c1c02b5192c44 of +#: unit.fingerprint2.Fingerprint2Unit.activate_module +#: unit.fingerprint2.Fingerprint2Unit.delete_all_template +#: unit.fingerprint2.Fingerprint2Unit.delete_template +#: unit.fingerprint2.Fingerprint2Unit.download_template +#: unit.fingerprint2.Fingerprint2Unit.gen_feature +#: unit.fingerprint2.Fingerprint2Unit.gen_template +#: unit.fingerprint2.Fingerprint2Unit.get_auto_sleep_time +#: unit.fingerprint2.Fingerprint2Unit.get_enroll_image +#: unit.fingerprint2.Fingerprint2Unit.get_firmware_version +#: unit.fingerprint2.Fingerprint2Unit.get_stored_template_id +#: unit.fingerprint2.Fingerprint2Unit.get_valid_template_num +#: unit.fingerprint2.Fingerprint2Unit.get_verify_image +#: unit.fingerprint2.Fingerprint2Unit.get_work_mode +#: unit.fingerprint2.Fingerprint2Unit.get_work_status +#: unit.fingerprint2.Fingerprint2Unit.is_connected +#: unit.fingerprint2.Fingerprint2Unit.load_template +#: unit.fingerprint2.Fingerprint2Unit.match +#: unit.fingerprint2.Fingerprint2Unit.set_auto_sleep_time +#: unit.fingerprint2.Fingerprint2Unit.set_led_breath +#: unit.fingerprint2.Fingerprint2Unit.set_led_color +#: unit.fingerprint2.Fingerprint2Unit.set_work_mode +#: unit.fingerprint2.Fingerprint2Unit.store_template +#: unit.fingerprint2.Fingerprint2Unit.upload_image +#: unit.fingerprint2.Fingerprint2Unit.upload_template +msgid "Return type" +msgstr "返回类型" + +#: 585d455170134f3baee787adbe4b16a6 of +#: unit.fingerprint2.Fingerprint2Unit.get_verify_image:8 +msgid "|get_verify_image.png|" +msgstr "" + +#: ../../en/refs/unit.fingerprint2.ref:7 53ccbc9dddf841d094b7789e69bef717 +msgid "get_verify_image.png" +msgstr "" + +#: cb9863d35f35477eac2f7a9e1711280e of +#: unit.fingerprint2.Fingerprint2Unit.get_enroll_image:1 +msgid "Capture fingerprint image for enrollment." +msgstr "获取用于注册的指纹图像。" + +#: d42ea5e78c7a4db3945f8fe8f0b3125c of +#: unit.fingerprint2.Fingerprint2Unit.get_enroll_image:8 +msgid "|get_enroll_image.png|" +msgstr "" + +#: ../../en/refs/unit.fingerprint2.ref:8 ee400b6cb03b40a7bf6e901487cd1aa5 +msgid "get_enroll_image.png" +msgstr "" + +#: 81508ba95bf94ec499a5c8c3c53f2cd6 of +#: unit.fingerprint2.Fingerprint2Unit.gen_feature:1 +msgid "Generate fingerprint feature." +msgstr "生成指纹特征。" + +#: a5f9184ccf5a41f9b53958f6a447560c of +#: unit.fingerprint2.Fingerprint2Unit.gen_feature:3 +msgid "" +"Converts the original fingerprint image stored in the image buffer into a" +" feature file, which is then stored in the template buffer." +msgstr "将存储在图像缓冲区中的原始指纹图像转换为特征文件,然后存储在模板缓冲区中。" + +#: 197bb08183be4884be3955be70de08ac of +#: unit.fingerprint2.Fingerprint2Unit.gen_feature:6 +msgid "" +"True if the fingerprint feature was successfully generate, False " +"otherwise." +msgstr "如果成功生成指纹特征则返回 True,否则返回 False。" + +#: 59bcc303be244db19505c3eb6dce83bc of +#: unit.fingerprint2.Fingerprint2Unit.gen_feature:11 +msgid "|gen_feature.png|" +msgstr "" + +#: ../../en/refs/unit.fingerprint2.ref:9 34baed69c99c4a5fb1728977633d8aa5 +msgid "gen_feature.png" +msgstr "" + +#: 0a752ebf37324ca295f10ec7354dc6e3 of +#: unit.fingerprint2.Fingerprint2Unit.gen_template:1 +msgid "Merge fingerprint features to generate a template." +msgstr "合并指纹特征以生成模板。" + +#: 9938641430fb4ee1be084f139b26a2bd of +#: unit.fingerprint2.Fingerprint2Unit.gen_template:3 +msgid "Combines two fingerprint feature files into one fingerprint template." +msgstr "将两个指纹特征文件合并为一个指纹模板。" + +#: 7aa15684f8d64f728cee64e663c064b0 of +#: unit.fingerprint2.Fingerprint2Unit.gen_template:5 +msgid "" +"True if the fingerprint template was successfully generate, False " +"otherwise." +msgstr "如果成功生成指纹模板则返回 True,否则返回 False。" + +#: 59bcc303be244db19505c3eb6dce83bc of +#: unit.fingerprint2.Fingerprint2Unit.gen_template:10 +msgid "|gen_template.png|" +msgstr "" + +#: ../../en/refs/unit.fingerprint2.ref:10 34baed69c99c4a5fb1728977633d8aa5 +msgid "gen_template.png" +msgstr "" + +#: 155c9fa551714456b4827720117133cf of +#: unit.fingerprint2.Fingerprint2Unit.store_template:1 +msgid "Store fingerprint template into flash memory." +msgstr "将指纹模板存储到 flash 中。" + +#: c0383e937a74422f884ff68e154ac1e4 of +#: unit.fingerprint2.Fingerprint2Unit.store_template:3 +msgid "" +"Stores the generated fingerprint template into flash memory at the " +"specified ID." +msgstr "将生成的指纹模板以指定的 ID 存储到 flash 中。" + +#: 69ad664fb4ae42cbadbe1eaa6996b171 of +#: unit.fingerprint2.Fingerprint2Unit.store_template:5 +msgid "Storage location ID (range: 0 ~ 99)" +msgstr "存储位置 ID(范围:0 ~ 99)" + +#: 8ed5c65af4ad44aab46b30ecfa3388df of +#: unit.fingerprint2.Fingerprint2Unit.store_template:6 +msgid "True if storage successful False otherwise." +msgstr "如果存储成功则返回 True,否则返回 False。" + +#: 5cd4d2b472c4405cbc402692e9d0b5c8 of +#: unit.fingerprint2.Fingerprint2Unit.store_template:11 +msgid "|store_template.png|" +msgstr "" + +#: ../../en/refs/unit.fingerprint2.ref:11 2fb6adb767cb40d5a54c5d5cde59e650 +msgid "store_template.png" +msgstr "" + +#: 66f28028b67e4250ad35f32c0b3602bb of +#: unit.fingerprint2.Fingerprint2Unit.load_template:1 +msgid "Load fingerprint template from flash memory." +msgstr "从 flash 加载指纹模板。" + +#: 0cb541194b63433a9ac459825b01429a of +#: unit.fingerprint2.Fingerprint2Unit.load_template:3 +msgid "" +"Loads the fingerprint template with the specified ID from flash memory " +"into the template buffer." +msgstr "将指定 ID 的指纹模板从 flash 加载到模板缓冲区中。" + +#: b4e47a0ef5d24dc28e1ef058473c8e9a of +#: unit.fingerprint2.Fingerprint2Unit.load_template:6 +msgid "ID of the fingerprint template to load" +msgstr "要加载的指纹模板 ID" + +#: 81d4c26f97e54f01b81f3c7423e80c22 of +#: unit.fingerprint2.Fingerprint2Unit.load_template:8 +msgid "True if load template succcessful." +msgstr "如果成功加载模板则返回 True。" + +#: 64a730c076ce4e4a8fe1ac744e3a3536 of +#: unit.fingerprint2.Fingerprint2Unit.load_template:13 +msgid "|load_template.png|" +msgstr "" + +#: ../../en/refs/unit.fingerprint2.ref:12 c6f040b5c3754c4599c176d06d846803 +msgid "load_template.png" +msgstr "" + +#: e5f643eec6b743b4bf9e3cfdbeca47f5 of +#: unit.fingerprint2.Fingerprint2Unit.delete_template:1 +msgid "Delete fingerprint template from flash memory." +msgstr "从 flash 删除指纹模板。" + +#: aae1355a73034753af0ccde5099b49e4 of +#: unit.fingerprint2.Fingerprint2Unit.delete_template:3 +msgid "" +"Deletes the fingerprint template with the specified ID from the flash " +"storage." +msgstr "从 flash 中删除指定 ID 的指纹模板。" + +#: c3e4f82b4ab14b278d8904b065c38211 of +#: unit.fingerprint2.Fingerprint2Unit.delete_template:5 +msgid "ID of the fingerprint template to delete" +msgstr "要删除的指纹模板 ID" + +#: 98221620c92146729e44bd5a6270f4b5 of +#: unit.fingerprint2.Fingerprint2Unit.delete_all_template:5 +#: unit.fingerprint2.Fingerprint2Unit.delete_template:6 +msgid "True if deletion successful, False otherwise" +msgstr "如果删除成功则返回 True,否则返回 False" + +#: bfb368b74c7e435dbccc8cae5df7418c of +#: unit.fingerprint2.Fingerprint2Unit.delete_template:11 +msgid "|delete_template.png|" +msgstr "" + +#: ../../en/refs/unit.fingerprint2.ref:13 a3d1b08d39244023a9035efefee4bd7e +msgid "delete_template.png" +msgstr "" + +#: 1d3cdc5f55324a6d823a0d17dbdd0ce4 of +#: unit.fingerprint2.Fingerprint2Unit.delete_all_template:1 +msgid "Clear the fingerprint database." +msgstr "清空指纹数据库。" + +#: 168ec3633fde45b1964fcda043d82760 of +#: unit.fingerprint2.Fingerprint2Unit.delete_all_template:3 +msgid "Deletes all fingerprint templates stored in the fingerprint database." +msgstr "删除存储在指纹数据库中的所有指纹模板。" + +#: bfb368b74c7e435dbccc8cae5df7418c of +#: unit.fingerprint2.Fingerprint2Unit.delete_all_template:10 +msgid "|delete_all_template.png|" +msgstr "" + +#: ../../en/refs/unit.fingerprint2.ref:14 a3d1b08d39244023a9035efefee4bd7e +msgid "delete_all_template.png" +msgstr "" + +#: 2b6370d9f6e041b19f898ca2acfdcf8b of +#: unit.fingerprint2.Fingerprint2Unit.upload_template:1 +msgid "Upload fingerprint template and save to specified path" +msgstr "上传指纹模板并保存到指定路径" + +#: 26c52da350704037975c9ca13c1e0a74 of +#: unit.fingerprint2.Fingerprint2Unit.upload_template:3 +msgid "Uploads the template stored in the template buffer to the host controller." +msgstr "将模板缓冲区中存储的模板上传到主机控制器。" + +#: 13a751f621774f4c8161f5022660deea of +#: unit.fingerprint2.Fingerprint2Unit.upload_template:5 +msgid "File path to save the uploaded template" +msgstr "保存上传模板的文件路径" + +#: bc6b35880f4b45d6b40dc866a0cd8107 of +#: unit.fingerprint2.Fingerprint2Unit.upload_template:6 +msgid "True if upload template successful." +msgstr "如果成功上传模板则返回 True。" + +#: 7213018da615497d823a084863183406 of +#: unit.fingerprint2.Fingerprint2Unit.upload_template:11 +msgid "|upload_template.png|" +msgstr "" + +#: ../../en/refs/unit.fingerprint2.ref:15 b3e8e133e8784744b1e15e010e35c74a +msgid "upload_template.png" +msgstr "" + +#: 4c44f478242f483e932586b2616c6c1f of +#: unit.fingerprint2.Fingerprint2Unit.download_template:1 +msgid "Download template." +msgstr "下载模板。" + +#: 5dafe4cf3356470495b64c5de1c2a784 of +#: unit.fingerprint2.Fingerprint2Unit.download_template:3 +msgid "" +"Reads the fingerprint template from the file system and downloads it to " +"the fingerprint module." +msgstr "从文件系统读取指纹模板并下载到指纹模块。" + +#: 2a4be5e1643a4b478be41ec618c08ee6 of +#: unit.fingerprint2.Fingerprint2Unit.download_template:5 +msgid "Path to the fingerprint template file" +msgstr "指纹模板文件的路径" + +#: 80612a947dee443a85953468de1040a1 of +#: unit.fingerprint2.Fingerprint2Unit.download_template:6 +msgid "True if download template successful." +msgstr "如果成功下载模板则返回 True。" + +#: 44548c1beff14b3d836c04de876ce966 of +#: unit.fingerprint2.Fingerprint2Unit.download_template:11 +msgid "|download_template.png|" +msgstr "" + +#: ../../en/refs/unit.fingerprint2.ref:16 1f0aac1074b444bb8883a9648d08cb79 +msgid "download_template.png" +msgstr "" + +#: cb9863d35f35477eac2f7a9e1711280e of +#: unit.fingerprint2.Fingerprint2Unit.upload_image:1 +msgid "Upload fingerprint image from module." +msgstr "上传指纹图像。" + +#: 6a88270b31304aa0830bd47fb5979382 of +#: unit.fingerprint2.Fingerprint2Unit.upload_image:3 +msgid "" +"Uploads the 4-bit grayscale fingerprint image from the module (size: " +"80x208). Optionally, converts the raw image to RGB565 format suitable for" +" display." +msgstr "从模块上传 4 位灰度指纹图像(尺寸:80x208)。可选地,将原始图像转换为适合显示的 RGB565 格式。" + +#: 06b00b1e8fb3494aadd938a3707896fd of +#: unit.fingerprint2.Fingerprint2Unit.upload_image:6 +msgid "Whether to convert raw image to RGB565 (default True)" +msgstr "是否将原始图像转换为 RGB565(默认 True)" + +#: fcd3150b0c6948e9ba3736e1586fc1dc of +#: unit.fingerprint2.Fingerprint2Unit.upload_image:7 +#, fuzzy +msgid "" +"If converting to RGB565, set True for little-endian byte order, or False " +"for big-endian. Default is True." +msgstr "如果转换为 RGB565,选择小端字节序(默认 True)" + +#: da88f1093bb3416e8f5a83c97cce9395 of +#: unit.fingerprint2.Fingerprint2Unit.upload_image:8 +msgid "Fingerprint image data as bytearray. Returns None on failure." +msgstr "指纹图像数据,类型为 bytearray。失败时返回 None。" + +#: 7213018da615497d823a084863183406 of +#: unit.fingerprint2.Fingerprint2Unit.upload_image:13 +msgid "|upload_image.png|" +msgstr "" + +#: ../../en/refs/unit.fingerprint2.ref:17 b3e8e133e8784744b1e15e010e35c74a +msgid "upload_image.png" +msgstr "" + +#: 98ed35f791ee4a13a5e64a293ebe0a7b of +#: unit.fingerprint2.Fingerprint2Unit.get_valid_template_num:1 +msgid "Get the number of valid fingerprint templates." +msgstr "获取有效指纹模板的数量。" + +#: 6be3bb471574412b9a4b391d2d79b23f of +#: unit.fingerprint2.Fingerprint2Unit.get_valid_template_num:3 +msgid "" +"Returns the count of fingerprint templates currently stored in the " +"fingerprint database." +msgstr "返回当前存储在指纹数据库中的指纹模板数量。" + +#: eed3c9687e104c72bdb5f3d7fa9f845a of +#: unit.fingerprint2.Fingerprint2Unit.get_valid_template_num:5 +msgid "Number of valid fingerprint templates" +msgstr "有效指纹模板的数量" + +#: aac19c32c733466b94243fed1dc7f0b0 of +#: unit.fingerprint2.Fingerprint2Unit.get_valid_template_num:10 +msgid "|get_valid_template_num.png|" +msgstr "" + +#: ../../en/refs/unit.fingerprint2.ref:18 9c980b774ac34a029f3c3fc81075b784 +msgid "get_valid_template_num.png" +msgstr "" + +#: cfeb3e996c4d49a991d1482d16ef3473 of +#: unit.fingerprint2.Fingerprint2Unit.get_stored_template_id:1 +msgid "" +"Get the list of stored fingerprint template IDs from the fingerprint " +"sensor." +msgstr "从指纹传感器获取已存储的指纹模板 ID 列表。" + +#: 963dd27821bb40d99fb962a51282f9bd of +#: unit.fingerprint2.Fingerprint2Unit.get_stored_template_id:3 +msgid "" +"This function queries the fingerprint sensor for its template index map, " +"which represents the occupied (used) template slots in the database, and " +"returns the list of those occupied IDs." +msgstr "此函数查询指纹传感器的模板索引映射,该映射表示数据库中已占用(使用)的模板槽位,并返回这些已占用ID的列表。" + +#: 4d6c86937bee4c3192f362ab93a02b48 of +#: unit.fingerprint2.Fingerprint2Unit.get_stored_template_id:7 +msgid "A list of occupied fingerprint template IDs, or None if retrieval fails." +msgstr "已占用的指纹模板 ID 列表,如果检索失败则返回 None。" + +#: 3bae7b521c7243e4a2d9ece93dfd8220 of +#: unit.fingerprint2.Fingerprint2Unit.get_stored_template_id:12 +msgid "|get_stored_template_id.png|" +msgstr "" + +#: ../../en/refs/unit.fingerprint2.ref:19 58216a801e57488180161f3680abdd72 +msgid "get_stored_template_id.png" +msgstr "" + +#: 380874e5d8274e7a90cf238afbea570f of +#: unit.fingerprint2.Fingerprint2Unit.find_match:1 +msgid "Search for a matching fingerprint in the database." +msgstr "在数据库中搜索匹配的指纹。" + +#: dd04ed9302b447bbbd8d641617ee957b of +#: unit.fingerprint2.Fingerprint2Unit.find_match:3 +msgid "" +"Compares the fingerprint features stored in the template buffer with the " +"stored templates in the database." +msgstr "将模板缓冲区中存储的指纹特征与数据库中存储的模板进行比较。" + +#: 53f99026476f41c89f35523d307be866 of +#: unit.fingerprint2.Fingerprint2Unit.find_match:6 +msgid "" +"- (id, score): A tuple of the matched fingerprint ID and match score. - " +"None: If no matching fingerprint is found." +msgstr "- (id, score):包含匹配的指纹ID和匹配分数的元组。 - None:如果未找到匹配的指纹。" + +#: 88f444fea50440ba8f5f8195febf6b89 of +#: unit.fingerprint2.Fingerprint2Unit.find_match:7 +msgid "(id, score): A tuple of the matched fingerprint ID and match score." +msgstr "(id, score):包含匹配的指纹ID和匹配分数的元组。" + +#: 25844d5c884a4fdabce1b91b07ae0c90 of +#: unit.fingerprint2.Fingerprint2Unit.find_match:8 +msgid "None: If no matching fingerprint is found." +msgstr "None:如果未找到匹配的指纹。" + +#: 44b2798383b44a5080c41434528530cf of +#: unit.fingerprint2.Fingerprint2Unit.find_match:12 +msgid "|find_match.png|" +msgstr "" + +#: ../../en/refs/unit.fingerprint2.ref:20 7ea8c26a341e49faab69cf0fb8d15b0e +msgid "find_match.png" +msgstr "" + +#: 6b64067daf984401a62eb201217306e4 of +#: unit.fingerprint2.Fingerprint2Unit.match:1 +msgid "Precisely match two fingerprint features." +msgstr "精确匹配两个指纹特征。" + +#: ed7bfa9388d047898cd8fdc5cca4a8ec of +#: unit.fingerprint2.Fingerprint2Unit.match:3 +msgid "Compares two fingerprint feature files and returns the result and score." +msgstr "比较两个指纹特征文件并返回结果和分数。" + +#: c7dd6f9257d44ec8b24280d94fdb6173 of +#: unit.fingerprint2.Fingerprint2Unit.match:5 +msgid "Similarity score of the match" +msgstr "匹配的相似度分数" + +#: 44b2798383b44a5080c41434528530cf of +#: unit.fingerprint2.Fingerprint2Unit.match:10 +msgid "|match.png|" +msgstr "" + +#: ../../en/refs/unit.fingerprint2.ref:21 18a4eb2417b24e8b97c4c4752776fd96 +msgid "match.png" +msgstr "" + +#: b4dcc89bccae4caba90d5fba2cf4fcd6 of +#: unit.fingerprint2.Fingerprint2Unit.is_connected:1 +msgid "Check whether the fingerprint module is connected." +msgstr "检查指纹模块是否已连接。" + +#: 7f143800116a415ca03b900aff8e52aa of +#: unit.fingerprint2.Fingerprint2Unit.is_connected:3 +msgid "True if the module is connected, False otherwise." +msgstr "如果模块已连接则返回 True,否则返回 False。" + +#: 2254126e406d49f9b42b29e1a684de3c of +#: unit.fingerprint2.Fingerprint2Unit.is_connected:8 +msgid "|is_connected.png|" +msgstr "" + +#: ../../en/refs/unit.fingerprint2.ref:22 b45d0c42e30046fa99a318e1ca0ea443 +msgid "is_connected.png" +msgstr "" + +#: 60743d2da02f48d4a3637e17db0cef23 of +#: unit.fingerprint2.Fingerprint2Unit.activate_module:1 +msgid "Activate the module." +msgstr "激活指纹模组。" + +#: 817ac323cf584957ae0588de3c15a4ff of +#: unit.fingerprint2.Fingerprint2Unit.activate_module:5 +msgid "|activate_module.png|" +msgstr "" + +#: ../../en/refs/unit.fingerprint2.ref:23 bd20362e883b418ab0aebbd84e37b52e +msgid "activate_module.png" +msgstr "" + +#: 827185a05863449591af5080c9ab1b5c of +#: unit.fingerprint2.Fingerprint2Unit.set_work_mode:1 +msgid "Set the working mode." +msgstr "设置工作模式。" + +#: 04c926446ee44868b397bb281d5a83ba of +#: unit.fingerprint2.Fingerprint2Unit.set_work_mode:3 +msgid "Working mode (0: Auto sleep, 1: Always-on)." +msgstr "工作模式(0:自动休眠,1:常开)" + +#: 06b00b1e8fb3494aadd938a3707896fd of +#: unit.fingerprint2.Fingerprint2Unit.set_work_mode:4 +msgid "Whether to save the setting to the device. Default is False." +msgstr "是否将设置保存到设备。默认为 False。" + +#: 785bae11782f46348c29e8f2dde58d31 of +#: unit.fingerprint2.Fingerprint2Unit.set_work_mode:8 +msgid "|set_work_mode.png|" +msgstr "" + +#: ../../en/refs/unit.fingerprint2.ref:24 89d44c18994d40afa6798cbefbe8918a +msgid "set_work_mode.png" +msgstr "" + +#: 9b649fe316e64dfabd218d943b7f8430 of +#: unit.fingerprint2.Fingerprint2Unit.get_work_mode:1 +msgid "Get the current working mode." +msgstr "获取当前工作模式。" + +#: 21e585ffad0845d4a8881eab9aed3f2a of +#: unit.fingerprint2.Fingerprint2Unit.get_work_mode:5 +msgid "Returns the module's current working mode:" +msgstr "返回模块的当前工作模式:" + +#: c88034cb3ff34929a9aa709f2929b7e9 of +#: unit.fingerprint2.Fingerprint2Unit.get_work_mode:4 +msgid "0: Auto sleep mode" +msgstr "0:自动休眠模式" + +#: e04a7db7dd8e4cf3aec08b20a6b99cff of +#: unit.fingerprint2.Fingerprint2Unit.get_work_mode:5 +msgid "1: Always-on mode" +msgstr "1:常开模式" + +#: 1bfd0524581c4634915dd3e282fa05da of +#: unit.fingerprint2.Fingerprint2Unit.get_work_mode:7 +msgid "Current working mode" +msgstr "当前工作模式" + +#: 42c9f0cbd1654e48b36514adc53eaa25 of +#: unit.fingerprint2.Fingerprint2Unit.get_work_mode:12 +msgid "|get_work_mode.png|" +msgstr "" + +#: ../../en/refs/unit.fingerprint2.ref:25 d1ec4662dc874425aa1f1ba931596b04 +msgid "get_work_mode.png" +msgstr "" + +#: 69c4cc8307334d858d3f71b5ea924852 of +#: unit.fingerprint2.Fingerprint2Unit.set_auto_sleep_time:1 +msgid "Set the sleep timeout." +msgstr "设置休眠超时时间。" + +#: 7f9f25f86f4b4d71a472972e943f5692 of +#: unit.fingerprint2.Fingerprint2Unit.set_auto_sleep_time:3 +msgid "" +"This parameter is only effective in \"Auto Sleep Mode\". It determines " +"how long the fingerprint module waits without receiving any command " +"before it enters sleep mode and starts monitoring for fingerprint press." +msgstr "此参数仅在\"自动休眠模式\"下有效。它决定指纹模块在进入休眠模式并开始监控指纹按压之前,在没有接收到任何命令的情况下等待多长时间。" + +#: b38a366dfc1741f6bc46cb2118a9a6c4 of +#: unit.fingerprint2.Fingerprint2Unit.set_auto_sleep_time:7 +msgid "Auto sleep timeout in seconds. Range: 10~254." +msgstr "自动休眠超时时间(秒)。范围:10~254。" + +#: 0de05b20e661417c8df755878cac36d6 of +#: unit.fingerprint2.Fingerprint2Unit.set_auto_sleep_time:8 +msgid "Whether to save this configuration to flash." +msgstr "是否将此配置保存到 flash 。" + +#: 43c638cdbe8f474e8ec42096b6c401db of +#: unit.fingerprint2.Fingerprint2Unit.set_auto_sleep_time:12 +msgid "|set_auto_sleep_time.png|" +msgstr "" + +#: ../../en/refs/unit.fingerprint2.ref:26 2d36cdcdfddd4cf29607a17757e45a41 +msgid "set_auto_sleep_time.png" +msgstr "" + +#: aef1a78cc9e945dfb927fa407cf7c981 of +#: unit.fingerprint2.Fingerprint2Unit.get_auto_sleep_time:1 +msgid "Get auto sleep time." +msgstr "获取自动休眠时间。" + +#: 1175fa946ca44807bcfdd5ea532cce47 of +#: unit.fingerprint2.Fingerprint2Unit.get_auto_sleep_time:3 +msgid "" +"This value is only valid in \"Timed Sleep Mode\". It indicates how long " +"the module will wait without receiving commands before entering sleep " +"state." +msgstr "此值仅在\"定时休眠模式\"下有效。它表示模块在进入休眠状态之前,在没有接收到命令的情况下等待多长时间。" + +#: 12b9487764214a19b5d5094745756266 of +#: unit.fingerprint2.Fingerprint2Unit.get_auto_sleep_time:6 +msgid "Auto sleep time in seconds" +msgstr "自动休眠时间(秒)" + +#: 12d290c29bcf4ce29d4244acba952536 of +#: unit.fingerprint2.Fingerprint2Unit.get_auto_sleep_time:11 +msgid "|get_auto_sleep_time.png|" +msgstr "" + +#: ../../en/refs/unit.fingerprint2.ref:27 4fadce4436ac48bbb3d7b228e25ec8ce +msgid "get_auto_sleep_time.png" +msgstr "" + +#: bd8d27308c1e46619ce04ae7f70c4888 of +#: unit.fingerprint2.Fingerprint2Unit.get_work_status:1 +msgid "Get fingerprint module work status." +msgstr "获取指纹模块工作状态。" + +#: c7b830b4e1214718975f8baee5adada1 of +#: unit.fingerprint2.Fingerprint2Unit.get_work_status:3 +msgid "True if active, False otherwise" +msgstr "如果已激活则返回 True,否则返回 False。" + +#: 1c5db1878188450380fb955dece64845 of +#: unit.fingerprint2.Fingerprint2Unit.get_work_status:8 +msgid "|get_work_status.png|" +msgstr "" + +#: ../../en/refs/unit.fingerprint2.ref:28 89f020ff49f842dea97f9ec505416ad4 +msgid "get_work_status.png" +msgstr "" + +#: dfd655b333e84f0a95b7798a61c27d4c of +#: unit.fingerprint2.Fingerprint2Unit.get_firmware_version:1 +msgid "Get firmware version." +msgstr "获取固件版本。" + +#: c1132940c57f42608f9b89b8baed1b24 of +#: unit.fingerprint2.Fingerprint2Unit.get_firmware_version:3 +msgid "Firmware version number" +msgstr "固件版本号" + +#: a521c0ea9e8f401c8599d4dcadcf548d of +#: unit.fingerprint2.Fingerprint2Unit.get_firmware_version:8 +msgid "|get_firmware_version.png|" +msgstr "" + +#: ../../en/refs/unit.fingerprint2.ref:29 2220a87fdabc49c98bb6a61d86f72e3d +msgid "get_firmware_version.png" +msgstr "" + +#: 62a2f87139104fb3a081a9991acc2511 of +#: unit.fingerprint2.Fingerprint2Unit.set_led_breath:1 +msgid "Set LED breathing mode." +msgstr "设置 LED 呼吸模式。" + +#: 6fef7f5f811949e7b545653df4f63c95 of +#: unit.fingerprint2.Fingerprint2Unit.set_led_breath:3 +msgid "Start color (bit0: blue, bit1: green, bit2: red)" +msgstr "起始颜色(bit0:蓝色,bit1:绿色,bit2:红色)" + +#: 36e199dc4fa648518c2bc837f2a31cd9 of +#: unit.fingerprint2.Fingerprint2Unit.set_led_breath:4 +msgid "End color (bit0: blue, bit1: green, bit2: red)" +msgstr "结束颜色(bit0:蓝色,bit1:绿色,bit2:红色)" + +#: 4d1df482d1a24dbeabcb556b08c37d58 of +#: unit.fingerprint2.Fingerprint2Unit.set_led_breath:5 +msgid "Number of cycles (0=infinite)" +msgstr "循环次数(0=无限)" + +#: d98e52cee2bd4a259d02496299f7fb71 of +#: unit.fingerprint2.Fingerprint2Unit.set_led_breath:6 +#: unit.fingerprint2.Fingerprint2Unit.set_led_color:4 +msgid "True if command successful, False otherwise" +msgstr "如果命令成功则返回 True,否则返回 False。" + +#: c324b12ee2fd4ded91a558c9bed9437c of +#: unit.fingerprint2.Fingerprint2Unit.set_led_breath:17 +#: unit.fingerprint2.Fingerprint2Unit.set_led_color:15 +msgid "Color codes:" +msgstr "颜色代码:" + +#: ad5ebfd5a9534ab9919870b435b41936 of +#: unit.fingerprint2.Fingerprint2Unit.set_led_breath:10 +msgid "0x00: All off" +msgstr "0x00:全部关闭" + +#: 95017fced1d74aafaae2001c51fc4ac9 of +#: unit.fingerprint2.Fingerprint2Unit.set_led_breath:11 +#: unit.fingerprint2.Fingerprint2Unit.set_led_color:9 +msgid "0x01: Blue" +msgstr "0x01:蓝色" + +#: f1f9b5ddc7724be7b55a3ee48882642d of +#: unit.fingerprint2.Fingerprint2Unit.set_led_breath:12 +#: unit.fingerprint2.Fingerprint2Unit.set_led_color:10 +msgid "0x02: Green" +msgstr "0x02:绿色" + +#: d12d953f39194d21b9055f1ca2a32bfb of +#: unit.fingerprint2.Fingerprint2Unit.set_led_breath:13 +#: unit.fingerprint2.Fingerprint2Unit.set_led_color:11 +msgid "0x03: Cyan (blue + green)" +msgstr "0x03:青色(蓝色 + 绿色)" + +#: 6a4626deeea54fe1b0e8ff34d4ed761b of +#: unit.fingerprint2.Fingerprint2Unit.set_led_breath:14 +#: unit.fingerprint2.Fingerprint2Unit.set_led_color:12 +msgid "0x04: Red" +msgstr "0x04:红色" + +#: a3416a513f50433eb4225b3137213045 of +#: unit.fingerprint2.Fingerprint2Unit.set_led_breath:15 +#: unit.fingerprint2.Fingerprint2Unit.set_led_color:13 +msgid "0x05: Magenta (red + blue)" +msgstr "0x05:品红色(红色 + 蓝色)" + +#: be47eea126e84d27a422973cb3c2573b of +#: unit.fingerprint2.Fingerprint2Unit.set_led_breath:16 +#: unit.fingerprint2.Fingerprint2Unit.set_led_color:14 +msgid "0x06: Yellow (red + green)" +msgstr "0x06:黄色(红色 + 绿色)" + +#: 7213dafad20a4d44907c188dde4e7c91 of +#: unit.fingerprint2.Fingerprint2Unit.set_led_breath:17 +#: unit.fingerprint2.Fingerprint2Unit.set_led_color:15 +msgid "0x07: White (red + green + blue)" +msgstr "0x07:白色(红色 + 绿色 + 蓝色)" + +#: 6af2837a72f6474e98e873fe75124201 of +#: unit.fingerprint2.Fingerprint2Unit.set_led_breath:21 +msgid "|set_led_breath.png|" +msgstr "" + +#: ../../en/refs/unit.fingerprint2.ref:30 3b16da51b92341f9929b6281d48c73cb +msgid "set_led_breath.png" +msgstr "" + +#: 1ff86e3b53204e35a63ce2039fb99c7e of +#: unit.fingerprint2.Fingerprint2Unit.set_led_color:1 +msgid "Set LED color." +msgstr "设置LED颜色。" + +#: ebf80624ed8a45a79468831759db8b17 of +#: unit.fingerprint2.Fingerprint2Unit.set_led_color:3 +msgid "LED color (0: always off, other values: always on with specified color)" +msgstr "LED颜色(0:始终关闭,其他值:使用指定颜色始终开启)" + +#: f066a29fb4a14f18a6052c2ebb1d02f9 of +#: unit.fingerprint2.Fingerprint2Unit.set_led_color:8 +msgid "0x00: Always off" +msgstr "0x00:始终关闭" + +#: 061c62e3d84049779b8aca9d0079b570 of +#: unit.fingerprint2.Fingerprint2Unit.set_led_color:19 +msgid "|set_led_color.png|" +msgstr "" + +#: ../../en/refs/unit.fingerprint2.ref:31 52575bf273bc47a18e15ec11883d4445 +msgid "set_led_color.png" +msgstr "" + +#~ msgid "|get_stored_templates_id.png|" +#~ msgstr "" + +#~ msgid "get_stored_templates_id.png" +#~ msgstr "" + +#~ msgid "" +#~ "Uploads the 4-bit grayscale fingerprint " +#~ "image from the module. Optionally, " +#~ "converts the raw image to RGB565 " +#~ "format suitable for display." +#~ msgstr "" + +#~ msgid "Image data as bytearray (raw 4-bit grayscale or RGB565)" +#~ msgstr "图像数据,类型为 bytearray(原始 4 位灰度或 RGB565)" + diff --git a/examples/unit/fingerprint2/m5cores3_fp2_basic_example.m5f2 b/examples/unit/fingerprint2/m5cores3_fp2_basic_example.m5f2 new file mode 100644 index 00000000..66ff453d --- /dev/null +++ b/examples/unit/fingerprint2/m5cores3_fp2_basic_example.m5f2 @@ -0,0 +1 @@ +{"version":"V2.0","versionNumber":"V2.3.6","type":"cores3","components":[{"name":"page0","type":"lvgl_page","layer":0,"screenId":"builtin","screenName":"","id":"u2N%@1GxtpxZW!h-","createTime":1758589800221,"backgroundColor":"#ffffff","isLVGL":true,"isSelected":true},{"name":"btn_enroll","type":"lvgl_button","layer":1,"screenId":"builtin","screenName":"","id":"csuXq#W!An*JewGX","createTime":1758590916340,"x":14,"y":165,"width":0,"height":0,"color":"#ffffff","backgroundColor":"#2196f3","text":"enroll","font":"lv.font_montserrat_14","pageId":"u2N%@1GxtpxZW!h-","isLVGL":true,"isSelected":false},{"name":"btn_recognize","type":"lvgl_button","layer":2,"screenId":"builtin","screenName":"","id":"vGeB=!0FP_Bvm=Rb","createTime":1758590920547,"x":109,"y":165,"width":0,"height":0,"color":"#ffffff","backgroundColor":"#2196f3","text":"recognize","font":"lv.font_montserrat_14","pageId":"u2N%@1GxtpxZW!h-","isLVGL":true,"isSelected":false},{"name":"btn_delete","type":"lvgl_button","layer":3,"screenId":"builtin","screenName":"","id":"bD7NSwS6cuDbA`Lw","createTime":1758590922404,"x":229,"y":165,"width":0,"height":0,"color":"#ffffff","backgroundColor":"#2196f3","text":"delete","font":"lv.font_montserrat_14","pageId":"u2N%@1GxtpxZW!h-","isLVGL":true,"isSelected":false},{"name":"label0","type":"lvgl_label","layer":4,"screenId":"builtin","screenName":"","id":"g96SEIa#!HI0Y9b&","createTime":1758590926468,"x":14,"y":9,"color":"#0f6dd1","backgroundColor":"#ffffff","bg_opacity":0,"text":"Fingerprint enroll, recognize, delete","font":"lv.font_montserrat_16","pageId":"u2N%@1GxtpxZW!h-","isLVGL":true,"isSelected":false},{"name":"label_tip","type":"lvgl_label","layer":5,"screenId":"builtin","screenName":"","id":"qdZstzM9CPRntkbt","createTime":1758590928574,"x":46,"y":61,"color":"#000000","backgroundColor":"#ffffff","bg_opacity":0,"text":"Tip:","font":"lv.font_montserrat_16","pageId":"u2N%@1GxtpxZW!h-","isLVGL":true,"isSelected":false},{"name":"label_res","type":"lvgl_label","layer":6,"screenId":"builtin","screenName":"","id":"sas8s_YTEmtena0&","createTime":1758590930238,"x":20,"y":95,"color":"#000000","backgroundColor":"#ffffff","bg_opacity":0,"text":"Result:","font":"lv.font_montserrat_16","pageId":"u2N%@1GxtpxZW!h-","isLVGL":true,"isSelected":false}],"resources":[{"hardware":["hardware_button","hardware_pin_button","imu","speaker","touch","als","mic","sdcard"]},{"unit":["unit_fingerprint2"]}],"units":[{"type":"unit_fingerprint2","name":"fingerprint2_0","portList":["A","B","C","Custom"],"portType":"A","userPort":[22,21],"id":"z3+CmD0o9`AHEyCq","createTime":1759979071476,"initBlockId":"9QC)fAB=quXLdF~Ck_La"}],"hats":[],"caps":[],"bases":[],"i2cs":[],"blockly":"idcountoperationig_idrestruepage050fingerprint2_0Falsefingerprint2_0fingerprint2_01Falsebtn_enroll8060btn_enrollCENTERpage0-10560btn_recognize10060btn_recognizeCENTERpage0060btn_delete8060btn_deleteCENTERpage010560operation0g_id0btn_enrollCLICKEDoperation1888100btn_recognizeCLICKEDoperation2888100btn_deleteCLICKED888100operation3wait_for_finger_pressDescribe this function...label_tipPlease place your fingerWHILEfingerprint2_0100888100wait_for_finger_leftDescribe this function...label_tipPlease remove your fingerWHILEfingerprint2_0100trueEQoperation115EQoperation2EQoperation31enrollDescribe this function...label_resi0WHILELTicountfingerprint2_0i1hello M5ifingerprint2_0fingerprint2_01g_idlabel_resEnroll success!Enroll ID: Enroll ID: g_id success!operation0label_tipLTg_id98g_id1666100recognizeDescribe this function...label_reslabel_tipPlease place your fingerWHILEfingerprint2_0100label_tipfingerprint2_0resfingerprint2_0residFROM_STARTres1label_resMatch ID: Recognize ID: id666100operation0NEQoperation0operation0label_resRecognize failed! 999200deleteDescribe this function...label_resGTg_id0g_id-1fingerprint2_00g_idlabel_resMatch ID: Delete ID: g_idlabel_resDelete failed!999200operation0","screen":[{"simulationName":"Built-in","type":"builtin","width":320,"height":240,"scale":0.77,"screenName":"","blockId":"","screenColorType":0,"rotation":1,"id":"builtin","createTime":1758589800217}],"logicWhenNum":0,"customList":[]} \ No newline at end of file diff --git a/examples/unit/fingerprint2/m5cores3_fp2_basic_example.py b/examples/unit/fingerprint2/m5cores3_fp2_basic_example.py new file mode 100644 index 00000000..c35945b9 --- /dev/null +++ b/examples/unit/fingerprint2/m5cores3_fp2_basic_example.py @@ -0,0 +1,429 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import os, sys, io +import M5 +from M5 import * +import m5ui +import lvgl as lv +import time +from unit import Fingerprint2Unit + + +page0 = None +btn_enroll = None +btn_recognize = None +btn_delete = None +label0 = None +label_tip = None +label_res = None +fingerprint2_0 = None +id2 = None +count = None +operation = None +i = None +g_id = None +res = None + + +def wait_for_finger_press(): + global \ + id2, \ + count, \ + operation, \ + i, \ + g_id, \ + res, \ + page0, \ + btn_enroll, \ + btn_recognize, \ + btn_delete, \ + label0, \ + label_tip, \ + label_res, \ + fingerprint2_0 + label_tip.set_text(str("Please place your finger")) + while not (fingerprint2_0.get_enroll_image()): + time.sleep_ms(100) + Speaker.tone(888, 100) + + +def wait_for_finger_left(): + global \ + id2, \ + count, \ + operation, \ + i, \ + g_id, \ + res, \ + page0, \ + btn_enroll, \ + btn_recognize, \ + btn_delete, \ + label0, \ + label_tip, \ + label_res, \ + fingerprint2_0 + label_tip.set_text(str("Please remove your finger")) + while fingerprint2_0.get_enroll_image(): + time.sleep_ms(100) + + +def enroll(id2, count): + global \ + operation, \ + i, \ + g_id, \ + res, \ + page0, \ + btn_enroll, \ + btn_recognize, \ + btn_delete, \ + label0, \ + label_tip, \ + label_res, \ + fingerprint2_0 + label_res.set_text(str("")) + i = 0 + while i < count: + wait_for_finger_press() + if fingerprint2_0.gen_feature(): + i = (i if isinstance(i, (int, float)) else 0) + 1 + print(i) + wait_for_finger_left() + if fingerprint2_0.gen_template(): + if fingerprint2_0.store_template(g_id): + label_res.set_text(str((str((str("Enroll ID: ") + str(g_id))) + str(" success!")))) + operation = 0 + label_tip.set_text(str("")) + if g_id < 98: + g_id = (g_id if isinstance(g_id, (int, float)) else 0) + 1 + Speaker.tone(666, 100) + + +def recognize(): + global \ + id2, \ + count, \ + operation, \ + i, \ + g_id, \ + res, \ + page0, \ + btn_enroll, \ + btn_recognize, \ + btn_delete, \ + label0, \ + label_tip, \ + label_res, \ + fingerprint2_0 + label_res.set_text(str("")) + label_tip.set_text(str("Please place your finger")) + while not (fingerprint2_0.get_verify_image()): + time.sleep_ms(100) + label_tip.set_text(str("")) + if fingerprint2_0.gen_feature(): + res = fingerprint2_0.find_match() + if res: + id2 = res[0] + label_res.set_text(str((str("Recognize ID: ") + str(id2)))) + Speaker.tone(666, 100) + operation = 0 + if operation != 0: + operation = 0 + label_res.set_text(str("Recognize failed! ")) + Speaker.tone(999, 200) + + +def delete(id2): + global \ + count, \ + operation, \ + i, \ + g_id, \ + res, \ + page0, \ + btn_enroll, \ + btn_recognize, \ + btn_delete, \ + label0, \ + label_tip, \ + label_res, \ + fingerprint2_0 + label_res.set_text(str("")) + if g_id > 0: + g_id = (g_id if isinstance(g_id, (int, float)) else 0) + -1 + if fingerprint2_0.delete_template(g_id): + label_res.set_text(str((str("Delete ID: ") + str(g_id)))) + else: + label_res.set_text(str("Delete failed!")) + Speaker.tone(999, 200) + operation = 0 + + +def btn_enroll_clicked_event(event_struct): + global \ + page0, \ + btn_enroll, \ + btn_recognize, \ + btn_delete, \ + label0, \ + label_tip, \ + label_res, \ + fingerprint2_0, \ + operation, \ + i, \ + g_id, \ + count, \ + res, \ + id2 + operation = 1 + Speaker.tone(888, 100) + + +def btn_recognize_clicked_event(event_struct): + global \ + page0, \ + btn_enroll, \ + btn_recognize, \ + btn_delete, \ + label0, \ + label_tip, \ + label_res, \ + fingerprint2_0, \ + operation, \ + i, \ + g_id, \ + count, \ + res, \ + id2 + operation = 2 + Speaker.tone(888, 100) + + +def btn_delete_clicked_event(event_struct): + global \ + page0, \ + btn_enroll, \ + btn_recognize, \ + btn_delete, \ + label0, \ + label_tip, \ + label_res, \ + fingerprint2_0, \ + operation, \ + i, \ + g_id, \ + count, \ + res, \ + id2 + Speaker.tone(888, 100) + operation = 3 + + +def btn_enroll_event_handler(event_struct): + global \ + page0, \ + btn_enroll, \ + btn_recognize, \ + btn_delete, \ + label0, \ + label_tip, \ + label_res, \ + fingerprint2_0, \ + operation, \ + i, \ + g_id, \ + count, \ + res, \ + id2 + event = event_struct.code + if event == lv.EVENT.CLICKED and True: + btn_enroll_clicked_event(event_struct) + return + + +def btn_recognize_event_handler(event_struct): + global \ + page0, \ + btn_enroll, \ + btn_recognize, \ + btn_delete, \ + label0, \ + label_tip, \ + label_res, \ + fingerprint2_0, \ + operation, \ + i, \ + g_id, \ + count, \ + res, \ + id2 + event = event_struct.code + if event == lv.EVENT.CLICKED and True: + btn_recognize_clicked_event(event_struct) + return + + +def btn_delete_event_handler(event_struct): + global \ + page0, \ + btn_enroll, \ + btn_recognize, \ + btn_delete, \ + label0, \ + label_tip, \ + label_res, \ + fingerprint2_0, \ + operation, \ + i, \ + g_id, \ + count, \ + res, \ + id2 + event = event_struct.code + if event == lv.EVENT.CLICKED and True: + btn_delete_clicked_event(event_struct) + return + + +def setup(): + global \ + page0, \ + btn_enroll, \ + btn_recognize, \ + btn_delete, \ + label0, \ + label_tip, \ + label_res, \ + fingerprint2_0, \ + operation, \ + i, \ + g_id, \ + count, \ + res, \ + id2 + + M5.begin() + Widgets.setRotation(1) + m5ui.init() + page0 = m5ui.M5Page(bg_c=0xFFFFFF) + btn_enroll = m5ui.M5Button( + text="enroll", + x=14, + y=165, + bg_c=0x2196F3, + text_c=0xFFFFFF, + font=lv.font_montserrat_14, + parent=page0, + ) + btn_recognize = m5ui.M5Button( + text="recognize", + x=109, + y=165, + bg_c=0x2196F3, + text_c=0xFFFFFF, + font=lv.font_montserrat_14, + parent=page0, + ) + btn_delete = m5ui.M5Button( + text="delete", + x=229, + y=165, + bg_c=0x2196F3, + text_c=0xFFFFFF, + font=lv.font_montserrat_14, + parent=page0, + ) + label0 = m5ui.M5Label( + "Fingerprint enroll, recognize, delete", + x=14, + y=9, + text_c=0x0F6DD1, + bg_c=0xFFFFFF, + bg_opa=0, + font=lv.font_montserrat_16, + parent=page0, + ) + label_tip = m5ui.M5Label( + "Tip:", + x=46, + y=61, + text_c=0x000000, + bg_c=0xFFFFFF, + bg_opa=0, + font=lv.font_montserrat_16, + parent=page0, + ) + label_res = m5ui.M5Label( + "Result:", + x=20, + y=95, + text_c=0x000000, + bg_c=0xFFFFFF, + bg_opa=0, + font=lv.font_montserrat_16, + parent=page0, + ) + + btn_enroll.add_event_cb(btn_enroll_event_handler, lv.EVENT.ALL, None) + btn_recognize.add_event_cb(btn_recognize_event_handler, lv.EVENT.ALL, None) + btn_delete.add_event_cb(btn_delete_event_handler, lv.EVENT.ALL, None) + + page0.screen_load() + Speaker.begin() + Speaker.setVolumePercentage(0.5) + fingerprint2_0 = Fingerprint2Unit(2, port=(1, 2)) + fingerprint2_0.activate_module() + fingerprint2_0.set_work_mode(1, save=False) + btn_enroll.set_size(80, 60) + btn_enroll.align_to(page0, lv.ALIGN.CENTER, -105, 60) + btn_recognize.set_size(100, 60) + btn_recognize.align_to(page0, lv.ALIGN.CENTER, 0, 60) + btn_delete.set_size(80, 60) + btn_delete.align_to(page0, lv.ALIGN.CENTER, 105, 60) + operation = 0 + g_id = 0 + + +def loop(): + global \ + page0, \ + btn_enroll, \ + btn_recognize, \ + btn_delete, \ + label0, \ + label_tip, \ + label_res, \ + fingerprint2_0, \ + operation, \ + i, \ + g_id, \ + count, \ + res, \ + id2 + M5.update() + if operation == 1: + enroll(1, 5) + elif operation == 2: + recognize() + elif operation == 3: + delete(1) + else: + pass + + +if __name__ == "__main__": + try: + setup() + while True: + loop() + except (Exception, KeyboardInterrupt) as e: + try: + m5ui.deinit() + from utility import print_error_msg + + print_error_msg(e) + except ImportError: + print("please update to latest firmware") diff --git a/examples/unit/fingerprint2/m5cores3_fp2_template_upload_download_example.m5f2 b/examples/unit/fingerprint2/m5cores3_fp2_template_upload_download_example.m5f2 new file mode 100644 index 00000000..11afb59f --- /dev/null +++ b/examples/unit/fingerprint2/m5cores3_fp2_template_upload_download_example.m5f2 @@ -0,0 +1 @@ +{"version":"V2.0","versionNumber":"V2.3.6","type":"cores3","components":[{"name":"page0","type":"lvgl_page","layer":0,"screenId":"builtin","screenName":"","id":"u2N%@1GxtpxZW!h-","createTime":1758589800221,"backgroundColor":"#ffffff","isLVGL":true,"isSelected":true},{"name":"btn_enroll","type":"lvgl_button","layer":1,"screenId":"builtin","screenName":"","id":"csuXq#W!An*JewGX","createTime":1758590916340,"x":14,"y":165,"width":0,"height":0,"color":"#ffffff","backgroundColor":"#2196f3","text":"enroll","font":"lv.font_montserrat_14","pageId":"u2N%@1GxtpxZW!h-","isLVGL":true,"isSelected":false},{"name":"btn_recognize","type":"lvgl_button","layer":2,"screenId":"builtin","screenName":"","id":"vGeB=!0FP_Bvm=Rb","createTime":1758590920547,"x":109,"y":165,"width":0,"height":0,"color":"#ffffff","backgroundColor":"#2196f3","text":"recognize","font":"lv.font_montserrat_14","pageId":"u2N%@1GxtpxZW!h-","isLVGL":true,"isSelected":false},{"name":"btn_delete","type":"lvgl_button","layer":3,"screenId":"builtin","screenName":"","id":"bD7NSwS6cuDbA`Lw","createTime":1758590922404,"x":229,"y":165,"width":0,"height":0,"color":"#ffffff","backgroundColor":"#2196f3","text":"delete","font":"lv.font_montserrat_14","pageId":"u2N%@1GxtpxZW!h-","isLVGL":true,"isSelected":false},{"name":"label0","type":"lvgl_label","layer":4,"screenId":"builtin","screenName":"","id":"g96SEIa#!HI0Y9b&","createTime":1758590926468,"x":14,"y":9,"color":"#0f6dd1","backgroundColor":"#ffffff","bg_opacity":0,"text":"Fingerprint enroll, recognize, delete","font":"lv.font_montserrat_16","pageId":"u2N%@1GxtpxZW!h-","isLVGL":true,"isSelected":false},{"name":"label_tip","type":"lvgl_label","layer":5,"screenId":"builtin","screenName":"","id":"qdZstzM9CPRntkbt","createTime":1758590928574,"x":38,"y":35,"color":"#000000","backgroundColor":"#ffffff","bg_opacity":0,"text":"Tip:","font":"lv.font_montserrat_16","pageId":"u2N%@1GxtpxZW!h-","isLVGL":true,"isSelected":false},{"name":"label_res","type":"lvgl_label","layer":6,"screenId":"builtin","screenName":"","id":"sas8s_YTEmtena0&","createTime":1758590930238,"x":12,"y":56,"color":"#000000","backgroundColor":"#ffffff","bg_opacity":0,"text":"Result:","font":"lv.font_montserrat_16","pageId":"u2N%@1GxtpxZW!h-","isLVGL":true,"isSelected":false},{"name":"btn_upload","type":"lvgl_button","layer":1,"screenId":"builtin","screenName":"","id":"dCC#U5K$Y-ogegsn","createTime":1759973634218,"x":45,"y":106,"width":0,"height":0,"color":"#ffffff","backgroundColor":"#2196f3","text":"upload","font":"lv.font_montserrat_14","pageId":"u2N%@1GxtpxZW!h-","isLVGL":true,"isSelected":false},{"name":"btn_download","type":"lvgl_button","layer":2,"screenId":"builtin","screenName":"","id":"vnybTuK$lwuQxg#Z","createTime":1759973638369,"x":186,"y":108,"width":0,"height":0,"color":"#ffffff","backgroundColor":"#2196f3","text":"download","font":"lv.font_montserrat_14","pageId":"u2N%@1GxtpxZW!h-","isLVGL":true,"isSelected":false}],"resources":[{"hardware":["hardware_button","hardware_pin_button","imu","speaker","touch","als","mic","sdcard"]},{"unit":["unit_fingerprint2"]}],"units":[{"type":"unit_fingerprint2","name":"fingerprint2_0","portList":["A","B","C","Custom"],"portType":"A","userPort":[22,21],"id":"z3+CmD0o9`AHEyCq","createTime":1759978761545,"initBlockId":"9QC)fAB=quXLdF~Ck_La"}],"hats":[],"caps":[],"bases":[],"i2cs":[],"blockly":"idcountoperationig_idrestruepage050fingerprint2_0FalseWHILEfingerprint2_0500.fingerprint2_0fingerprint2_01Falsehello M5fingerprint2_0btn_upload8060btn_uploadCENTERpage0-600btn_download8060btn_downloadCENTERpage0600btn_enroll8060btn_enrollCENTERpage0-10570btn_recognize10060btn_recognizeCENTERpage0070btn_delete8060btn_deleteCENTERpage010570operation0g_id0wait_for_finger_pressDescribe this function...label_tipPlease place your fingerWHILEfingerprint2_0100888100wait_for_finger_leftDescribe this function...label_tipPlease remove your fingerWHILEfingerprint2_0100enrollDescribe this function...label_resi0WHILELTicountfingerprint2_0i1hello M5ifingerprint2_0fingerprint2_01g_idlabel_resEnroll success!Enroll ID: Enroll ID: g_id success!operation0label_tipLTg_id98g_id1666100uploadDescribe this function...label_resfingerprint2_00label_tiptemplate uploading...fingerprint2_0/flash/res/tp1.tzhlabel_tiplabel_resEnroll success!Enroll ID: Upload ID: 0 success!666100label_resEnroll success!Enroll ID: Upload ID: 0failed!999200label_resload template failed!999200operation0trueEQoperation115EQoperation2EQoperation31EQoperation4EQoperation5recognizeDescribe this function...label_reslabel_tipPlease place your fingerWHILEfingerprint2_0100label_tipfingerprint2_0resfingerprint2_0residFROM_STARTres1label_resMatch ID: Recognize ID: id666100operation0NEQoperation0operation0label_resRecognize failed! 999200downloadDescribe this function...label_reslabel_tipdownload templatefingerprint2_0/flash/res/tp1.tzhlabel_resDownload template success!fingerprint2_066operation0label_tipstore templatelabel_resStore template success!666100label_resStore template failed!999200label_resdownload template failed!999200operation0btn_enrollCLICKEDoperation1888100btn_recognizeCLICKEDoperation2888100btn_deleteCLICKED888100operation3deleteDescribe this function...label_resGTg_id0g_id-1fingerprint2_00g_idlabel_resMatch ID: Delete ID: g_idlabel_resDelete failed!999200operation0btn_uploadCLICKEDoperation4888100btn_downloadCLICKEDoperation5888100","screen":[{"simulationName":"Built-in","type":"builtin","width":320,"height":240,"scale":0.77,"screenName":"","blockId":"","screenColorType":0,"rotation":1,"id":"builtin","createTime":1758589800217}],"logicWhenNum":0,"customList":[]} \ No newline at end of file diff --git a/examples/unit/fingerprint2/m5cores3_fp2_template_upload_download_example.py b/examples/unit/fingerprint2/m5cores3_fp2_template_upload_download_example.py new file mode 100644 index 00000000..32435f35 --- /dev/null +++ b/examples/unit/fingerprint2/m5cores3_fp2_template_upload_download_example.py @@ -0,0 +1,651 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import os, sys, io +import M5 +from M5 import * +import m5ui +import lvgl as lv +import time +from unit import Fingerprint2Unit + + +page0 = None +btn_enroll = None +btn_upload = None +btn_recognize = None +btn_download = None +btn_delete = None +label0 = None +label_tip = None +label_res = None +fingerprint2_0 = None +id2 = None +count = None +operation = None +i = None +g_id = None +res = None + + +def wait_for_finger_press(): + global \ + id2, \ + count, \ + operation, \ + i, \ + g_id, \ + res, \ + page0, \ + btn_enroll, \ + btn_upload, \ + btn_recognize, \ + btn_download, \ + btn_delete, \ + label0, \ + label_tip, \ + label_res, \ + fingerprint2_0 + label_tip.set_text(str("Please place your finger")) + while not (fingerprint2_0.get_enroll_image()): + time.sleep_ms(100) + Speaker.tone(888, 100) + + +def wait_for_finger_left(): + global \ + id2, \ + count, \ + operation, \ + i, \ + g_id, \ + res, \ + page0, \ + btn_enroll, \ + btn_upload, \ + btn_recognize, \ + btn_download, \ + btn_delete, \ + label0, \ + label_tip, \ + label_res, \ + fingerprint2_0 + label_tip.set_text(str("Please remove your finger")) + while fingerprint2_0.get_enroll_image(): + time.sleep_ms(100) + + +def enroll(id2, count): + global \ + operation, \ + i, \ + g_id, \ + res, \ + page0, \ + btn_enroll, \ + btn_upload, \ + btn_recognize, \ + btn_download, \ + btn_delete, \ + label0, \ + label_tip, \ + label_res, \ + fingerprint2_0 + label_res.set_text(str("")) + i = 0 + while i < count: + wait_for_finger_press() + if fingerprint2_0.gen_feature(): + i = (i if isinstance(i, (int, float)) else 0) + 1 + print(i) + wait_for_finger_left() + if fingerprint2_0.gen_template(): + if fingerprint2_0.store_template(g_id): + label_res.set_text(str((str((str("Enroll ID: ") + str(g_id))) + str(" success!")))) + operation = 0 + label_tip.set_text(str("")) + if g_id < 98: + g_id = (g_id if isinstance(g_id, (int, float)) else 0) + 1 + Speaker.tone(666, 100) + + +def upload(): + global \ + id2, \ + count, \ + operation, \ + i, \ + g_id, \ + res, \ + page0, \ + btn_enroll, \ + btn_upload, \ + btn_recognize, \ + btn_download, \ + btn_delete, \ + label0, \ + label_tip, \ + label_res, \ + fingerprint2_0 + label_res.set_text(str("")) + if fingerprint2_0.load_template(0): + label_tip.set_text(str("template uploading...")) + if fingerprint2_0.upload_template("/flash/res/tp1.tzh"): + label_tip.set_text(str("")) + label_res.set_text(str((str((str("Upload ID: ") + str(0))) + str(" success!")))) + Speaker.tone(666, 100) + else: + label_res.set_text(str((str((str("Upload ID: ") + str(0))) + str("failed!")))) + Speaker.tone(999, 200) + else: + label_res.set_text(str("load template failed!")) + Speaker.tone(999, 200) + operation = 0 + + +def recognize(): + global \ + id2, \ + count, \ + operation, \ + i, \ + g_id, \ + res, \ + page0, \ + btn_enroll, \ + btn_upload, \ + btn_recognize, \ + btn_download, \ + btn_delete, \ + label0, \ + label_tip, \ + label_res, \ + fingerprint2_0 + label_res.set_text(str("")) + label_tip.set_text(str("Please place your finger")) + while not (fingerprint2_0.get_verify_image()): + time.sleep_ms(100) + label_tip.set_text(str("")) + if fingerprint2_0.gen_feature(): + res = fingerprint2_0.find_match() + if res: + id2 = res[0] + label_res.set_text(str((str("Recognize ID: ") + str(id2)))) + Speaker.tone(666, 100) + operation = 0 + if operation != 0: + operation = 0 + label_res.set_text(str("Recognize failed! ")) + Speaker.tone(999, 200) + + +def download(): + global \ + id2, \ + count, \ + operation, \ + i, \ + g_id, \ + res, \ + page0, \ + btn_enroll, \ + btn_upload, \ + btn_recognize, \ + btn_download, \ + btn_delete, \ + label0, \ + label_tip, \ + label_res, \ + fingerprint2_0 + label_res.set_text(str("")) + label_tip.set_text(str("download template")) + if fingerprint2_0.download_template("/flash/res/tp1.tzh"): + label_res.set_text(str("Download template success!")) + if fingerprint2_0.store_template(66): + operation = 0 + label_tip.set_text(str("store template")) + label_res.set_text(str("Store template success!")) + Speaker.tone(666, 100) + else: + label_res.set_text(str("Store template failed!")) + Speaker.tone(999, 200) + else: + label_res.set_text(str("download template failed!")) + Speaker.tone(999, 200) + operation = 0 + + +def delete(id2): + global \ + count, \ + operation, \ + i, \ + g_id, \ + res, \ + page0, \ + btn_enroll, \ + btn_upload, \ + btn_recognize, \ + btn_download, \ + btn_delete, \ + label0, \ + label_tip, \ + label_res, \ + fingerprint2_0 + label_res.set_text(str("")) + if g_id > 0: + g_id = (g_id if isinstance(g_id, (int, float)) else 0) + -1 + if fingerprint2_0.delete_template(g_id): + label_res.set_text(str((str("Delete ID: ") + str(g_id)))) + else: + label_res.set_text(str("Delete failed!")) + Speaker.tone(999, 200) + operation = 0 + + +def btn_enroll_clicked_event(event_struct): + global \ + page0, \ + btn_enroll, \ + btn_upload, \ + btn_recognize, \ + btn_download, \ + btn_delete, \ + label0, \ + label_tip, \ + label_res, \ + fingerprint2_0, \ + operation, \ + i, \ + g_id, \ + count, \ + res, \ + id2 + operation = 1 + Speaker.tone(888, 100) + + +def btn_recognize_clicked_event(event_struct): + global \ + page0, \ + btn_enroll, \ + btn_upload, \ + btn_recognize, \ + btn_download, \ + btn_delete, \ + label0, \ + label_tip, \ + label_res, \ + fingerprint2_0, \ + operation, \ + i, \ + g_id, \ + count, \ + res, \ + id2 + operation = 2 + Speaker.tone(888, 100) + + +def btn_delete_clicked_event(event_struct): + global \ + page0, \ + btn_enroll, \ + btn_upload, \ + btn_recognize, \ + btn_download, \ + btn_delete, \ + label0, \ + label_tip, \ + label_res, \ + fingerprint2_0, \ + operation, \ + i, \ + g_id, \ + count, \ + res, \ + id2 + Speaker.tone(888, 100) + operation = 3 + + +def btn_upload_clicked_event(event_struct): + global \ + page0, \ + btn_enroll, \ + btn_upload, \ + btn_recognize, \ + btn_download, \ + btn_delete, \ + label0, \ + label_tip, \ + label_res, \ + fingerprint2_0, \ + operation, \ + i, \ + g_id, \ + count, \ + res, \ + id2 + operation = 4 + Speaker.tone(888, 100) + + +def btn_download_clicked_event(event_struct): + global \ + page0, \ + btn_enroll, \ + btn_upload, \ + btn_recognize, \ + btn_download, \ + btn_delete, \ + label0, \ + label_tip, \ + label_res, \ + fingerprint2_0, \ + operation, \ + i, \ + g_id, \ + count, \ + res, \ + id2 + operation = 5 + Speaker.tone(888, 100) + + +def btn_enroll_event_handler(event_struct): + global \ + page0, \ + btn_enroll, \ + btn_upload, \ + btn_recognize, \ + btn_download, \ + btn_delete, \ + label0, \ + label_tip, \ + label_res, \ + fingerprint2_0, \ + operation, \ + i, \ + g_id, \ + count, \ + res, \ + id2 + event = event_struct.code + if event == lv.EVENT.CLICKED and True: + btn_enroll_clicked_event(event_struct) + return + + +def btn_recognize_event_handler(event_struct): + global \ + page0, \ + btn_enroll, \ + btn_upload, \ + btn_recognize, \ + btn_download, \ + btn_delete, \ + label0, \ + label_tip, \ + label_res, \ + fingerprint2_0, \ + operation, \ + i, \ + g_id, \ + count, \ + res, \ + id2 + event = event_struct.code + if event == lv.EVENT.CLICKED and True: + btn_recognize_clicked_event(event_struct) + return + + +def btn_delete_event_handler(event_struct): + global \ + page0, \ + btn_enroll, \ + btn_upload, \ + btn_recognize, \ + btn_download, \ + btn_delete, \ + label0, \ + label_tip, \ + label_res, \ + fingerprint2_0, \ + operation, \ + i, \ + g_id, \ + count, \ + res, \ + id2 + event = event_struct.code + if event == lv.EVENT.CLICKED and True: + btn_delete_clicked_event(event_struct) + return + + +def btn_upload_event_handler(event_struct): + global \ + page0, \ + btn_enroll, \ + btn_upload, \ + btn_recognize, \ + btn_download, \ + btn_delete, \ + label0, \ + label_tip, \ + label_res, \ + fingerprint2_0, \ + operation, \ + i, \ + g_id, \ + count, \ + res, \ + id2 + event = event_struct.code + if event == lv.EVENT.CLICKED and True: + btn_upload_clicked_event(event_struct) + return + + +def btn_download_event_handler(event_struct): + global \ + page0, \ + btn_enroll, \ + btn_upload, \ + btn_recognize, \ + btn_download, \ + btn_delete, \ + label0, \ + label_tip, \ + label_res, \ + fingerprint2_0, \ + operation, \ + i, \ + g_id, \ + count, \ + res, \ + id2 + event = event_struct.code + if event == lv.EVENT.CLICKED and True: + btn_download_clicked_event(event_struct) + return + + +def setup(): + global \ + page0, \ + btn_enroll, \ + btn_upload, \ + btn_recognize, \ + btn_download, \ + btn_delete, \ + label0, \ + label_tip, \ + label_res, \ + fingerprint2_0, \ + operation, \ + i, \ + g_id, \ + count, \ + res, \ + id2 + + M5.begin() + Widgets.setRotation(1) + m5ui.init() + page0 = m5ui.M5Page(bg_c=0xFFFFFF) + btn_enroll = m5ui.M5Button( + text="enroll", + x=14, + y=165, + bg_c=0x2196F3, + text_c=0xFFFFFF, + font=lv.font_montserrat_14, + parent=page0, + ) + btn_upload = m5ui.M5Button( + text="upload", + x=45, + y=106, + bg_c=0x2196F3, + text_c=0xFFFFFF, + font=lv.font_montserrat_14, + parent=page0, + ) + btn_recognize = m5ui.M5Button( + text="recognize", + x=109, + y=165, + bg_c=0x2196F3, + text_c=0xFFFFFF, + font=lv.font_montserrat_14, + parent=page0, + ) + btn_download = m5ui.M5Button( + text="download", + x=186, + y=108, + bg_c=0x2196F3, + text_c=0xFFFFFF, + font=lv.font_montserrat_14, + parent=page0, + ) + btn_delete = m5ui.M5Button( + text="delete", + x=229, + y=165, + bg_c=0x2196F3, + text_c=0xFFFFFF, + font=lv.font_montserrat_14, + parent=page0, + ) + label0 = m5ui.M5Label( + "Fingerprint enroll, recognize, delete", + x=14, + y=9, + text_c=0x0F6DD1, + bg_c=0xFFFFFF, + bg_opa=0, + font=lv.font_montserrat_16, + parent=page0, + ) + label_tip = m5ui.M5Label( + "Tip:", + x=38, + y=35, + text_c=0x000000, + bg_c=0xFFFFFF, + bg_opa=0, + font=lv.font_montserrat_16, + parent=page0, + ) + label_res = m5ui.M5Label( + "Result:", + x=12, + y=56, + text_c=0x000000, + bg_c=0xFFFFFF, + bg_opa=0, + font=lv.font_montserrat_16, + parent=page0, + ) + + btn_enroll.add_event_cb(btn_enroll_event_handler, lv.EVENT.ALL, None) + btn_recognize.add_event_cb(btn_recognize_event_handler, lv.EVENT.ALL, None) + btn_delete.add_event_cb(btn_delete_event_handler, lv.EVENT.ALL, None) + btn_upload.add_event_cb(btn_upload_event_handler, lv.EVENT.ALL, None) + btn_download.add_event_cb(btn_download_event_handler, lv.EVENT.ALL, None) + + page0.screen_load() + Speaker.begin() + Speaker.setVolumePercentage(0.5) + fingerprint2_0 = Fingerprint2Unit(2, port=(1, 2)) + while not (fingerprint2_0.is_connected()): + time.sleep_ms(500) + print(".") + fingerprint2_0.activate_module() + fingerprint2_0.set_work_mode(1, save=False) + print(fingerprint2_0.get_firmware_version()) + btn_upload.set_size(80, 60) + btn_upload.align_to(page0, lv.ALIGN.CENTER, -60, 0) + btn_download.set_size(80, 60) + btn_download.align_to(page0, lv.ALIGN.CENTER, 60, 0) + btn_enroll.set_size(80, 60) + btn_enroll.align_to(page0, lv.ALIGN.CENTER, -105, 70) + btn_recognize.set_size(100, 60) + btn_recognize.align_to(page0, lv.ALIGN.CENTER, 0, 70) + btn_delete.set_size(80, 60) + btn_delete.align_to(page0, lv.ALIGN.CENTER, 105, 70) + operation = 0 + g_id = 0 + + +def loop(): + global \ + page0, \ + btn_enroll, \ + btn_upload, \ + btn_recognize, \ + btn_download, \ + btn_delete, \ + label0, \ + label_tip, \ + label_res, \ + fingerprint2_0, \ + operation, \ + i, \ + g_id, \ + count, \ + res, \ + id2 + M5.update() + if operation == 1: + enroll(1, 5) + elif operation == 2: + recognize() + elif operation == 3: + delete(1) + elif operation == 4: + upload() + elif operation == 5: + download() + else: + pass + + +if __name__ == "__main__": + try: + setup() + while True: + loop() + except (Exception, KeyboardInterrupt) as e: + try: + m5ui.deinit() + from utility import print_error_msg + + print_error_msg(e) + except ImportError: + print("please update to latest firmware") diff --git a/examples/unit/fingerprint2/m5cores3_fp2_upload_image_example.m5f2 b/examples/unit/fingerprint2/m5cores3_fp2_upload_image_example.m5f2 new file mode 100644 index 00000000..aa94809e --- /dev/null +++ b/examples/unit/fingerprint2/m5cores3_fp2_upload_image_example.m5f2 @@ -0,0 +1 @@ +{"version":"V2.0","versionNumber":"V2.3.6","type":"cores3","components":[{"name":"page0","type":"lvgl_page","layer":0,"screenId":"builtin","screenName":"","id":"i=RLE46Y5qN82xg9","createTime":1760068188823,"backgroundColor":"#ffffff","isLVGL":true,"isSelected":true},{"name":"canvas0","type":"lvgl_canvas","layer":1,"screenId":"builtin","screenName":"","id":"oA`Aj8Q3t8g6`cgU","createTime":1760068444052,"x":115,"y":25,"width":80,"height":208,"backgroundColor":"#c9c9c9","pageId":"i=RLE46Y5qN82xg9","isLVGL":true,"isSelected":false},{"name":"label0","type":"lvgl_label","layer":2,"screenId":"builtin","screenName":"","id":"wt+rY3#eD&d7Yg8W","createTime":1760069098588,"x":6,"y":3,"color":"#000000","backgroundColor":"#ffffff","bg_opacity":0,"text":"tip","font":"lv.font_montserrat_16","pageId":"i=RLE46Y5qN82xg9","isLVGL":true,"isSelected":false,"width":19,"height":15},{"name":"btn_upload","type":"lvgl_button","layer":1,"screenId":"builtin","screenName":"","id":"j6FQxR=i_yKpLE8w","createTime":1760081167936,"x":216,"y":144,"width":0,"height":0,"color":"#ffffff","backgroundColor":"#2196f3","text":"upload","font":"lv.font_montserrat_14","pageId":"i=RLE46Y5qN82xg9","isLVGL":true,"isSelected":false}],"resources":[{"hardware":["hardware_button","hardware_pin_button","imu","speaker","touch","als","mic","sdcard"]},{"unit":["unit_fingerprint2"]}],"units":[{"type":"unit_fingerprint2","name":"fingerprint2_0","portList":["A","B","C","Custom"],"portType":"A","userPort":[22,21],"id":"oNczTaJf+6wKrP6!","createTime":1760080943075,"initBlockId":"A5V7Sk`~{B9tdNxpQB*U"}],"hats":[],"caps":[],"bases":[],"i2cs":[],"blockly":"uploadfpimgtruefingerprint2_0Falsepage080btn_upload8060uploadTRUEupload_imageDescribe this function...666100label0Please press fingerWHILEfingerprint2_0.1000label0Uploading...fpimgfingerprint2_0TrueTruefpimg666100label0Upload image finished!canvas0RGB565fpimg80208uploadFALSE888200label0Upload image failed!uploadFALSEtrueuploadbtn_uploadCLICKEDuploadTRUE","screen":[{"simulationName":"Built-in","type":"builtin","width":320,"height":240,"scale":0.77,"screenName":"","blockId":"","screenColorType":0,"rotation":1,"id":"builtin","createTime":1760068188822}],"logicWhenNum":0,"customList":[]} \ No newline at end of file diff --git a/examples/unit/fingerprint2/m5cores3_fp2_upload_image_example.py b/examples/unit/fingerprint2/m5cores3_fp2_upload_image_example.py new file mode 100644 index 00000000..fe9c042e --- /dev/null +++ b/examples/unit/fingerprint2/m5cores3_fp2_upload_image_example.py @@ -0,0 +1,121 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import os, sys, io +import M5 +from M5 import * +import m5ui +import lvgl as lv +from unit import Fingerprint2Unit +import time + + +page0 = None +canvas0 = None +btn_upload = None +label0 = None +fingerprint2_0 = None +upload = None +fpimg = None + + +# Describe this function... +def upload_image(): + global upload, fpimg, page0, canvas0, btn_upload, label0, fingerprint2_0 + Speaker.tone(666, 100) + label0.set_text(str("Please press finger")) + while not (fingerprint2_0.get_enroll_image()): + print(".") + time.sleep_ms(1000) + label0.set_text(str("Uploading...")) + fpimg = fingerprint2_0.upload_image(to_rgb565=True, byte_order=True) + if fpimg: + Speaker.tone(666, 100) + label0.set_text(str("Upload image finished!")) + canvas0.set_buffer(fpimg, 80, 208, lv.COLOR_FORMAT.RGB565) + upload = False + else: + Speaker.tone(888, 200) + label0.set_text(str("Upload image failed!")) + upload = False + + +def btn_upload_clicked_event(event_struct): + global page0, canvas0, btn_upload, label0, fingerprint2_0, upload, fpimg + upload = True + + +def btn_upload_event_handler(event_struct): + global page0, canvas0, btn_upload, label0, fingerprint2_0, upload, fpimg + event = event_struct.code + if event == lv.EVENT.CLICKED and True: + btn_upload_clicked_event(event_struct) + return + + +def setup(): + global page0, canvas0, btn_upload, label0, fingerprint2_0, upload, fpimg + M5.begin() + Widgets.setRotation(1) + m5ui.init() + page0 = m5ui.M5Page(bg_c=0xFFFFFF) + canvas0 = m5ui.M5Canvas( + x=115, + y=25, + w=80, + h=208, + color_format=lv.COLOR_FORMAT.ARGB8888, + bg_c=0xC9C9C9, + bg_opa=255, + parent=page0, + ) + btn_upload = m5ui.M5Button( + text="upload", + x=216, + y=144, + bg_c=0x2196F3, + text_c=0xFFFFFF, + font=lv.font_montserrat_14, + parent=page0, + ) + label0 = m5ui.M5Label( + "tip", + x=6, + y=3, + text_c=0x000000, + bg_c=0xFFFFFF, + bg_opa=0, + font=lv.font_montserrat_16, + parent=page0, + ) + btn_upload.add_event_cb(btn_upload_event_handler, lv.EVENT.ALL, None) + fingerprint2_0 = Fingerprint2Unit(2, port=(1, 2)) + page0.screen_load() + Speaker.begin() + Speaker.setVolumePercentage(0.8) + btn_upload.set_size(80, 60) + upload = True + upload_image() + + +def loop(): + global page0, canvas0, btn_upload, label0, fingerprint2_0, upload, fpimg + M5.update() + if upload: + upload_image() + + +if __name__ == "__main__": + try: + setup() + while True: + loop() + except (Exception, KeyboardInterrupt) as e: + try: + m5ui.deinit() + from utility import print_error_msg + + print_error_msg(e) + except ImportError: + print("please update to latest firmware") diff --git a/m5stack/libs/unit/__init__.py b/m5stack/libs/unit/__init__.py index 3c6e5020..9c405db0 100644 --- a/m5stack/libs/unit/__init__.py +++ b/m5stack/libs/unit/__init__.py @@ -49,6 +49,7 @@ "FaderUnit": "fader", "FanUnit": "fan", "FingerUnit": "finger", + "Fingerprint2Unit": "fingerprint2", "FlashLightUnit": "flash_light", "GatewayH2Unit": "gateway_h2", "GestureUnit": "gesture", diff --git a/m5stack/libs/unit/fingerprint2.py b/m5stack/libs/unit/fingerprint2.py new file mode 100644 index 00000000..0a000a94 --- /dev/null +++ b/m5stack/libs/unit/fingerprint2.py @@ -0,0 +1,839 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import time +import machine + + +class FingerprintPacket: + START_CODE = b"\xEF\x01" + default_address = b"\xFF\xFF\xFF\xFF" + + def __init__(self, pkt_type, payload=b""): + self.type = pkt_type + self.payload = payload[:64] + self.address = FingerprintPacket.default_address + + @staticmethod + def set_default_address(addr_bytes): + if not isinstance(addr_bytes, (bytes, bytearray)) or len(addr_bytes) != 4: + raise ValueError("Address must be 4 bytes") + FingerprintPacket.default_address = bytes(addr_bytes) + + def checksum(self): + length = len(self.payload) + 2 + total = self.type + ((length >> 8) & 0xFF) + (length & 0xFF) + total += sum(self.payload) + return total & 0xFFFF + + def build_packet(self): + length = len(self.payload) + 2 + packet = bytearray() + packet += self.START_CODE + packet += self.address + packet.append(self.type) + packet += length.to_bytes(2, "big") + packet += self.payload + packet += self.checksum().to_bytes(2, "big") + return packet + + +class SerialCmdHelper: + def __init__(self, serial, debug=False): + self.serial = serial + self.debug = debug + + def _log_debug(self, message): + if self.debug: + print(f"[DEBUG] {message}") + + def _format_bytes(self, data): + return " ".join(f"{b:02X}" for b in data) + + def _flush(self): + while self.serial.any(): + self.serial.read() + + def cmd(self, pkt: FingerprintPacket, timeout_ms=1000): + self._flush() + data = pkt.build_packet() + self._log_debug(f"TX: {self._format_bytes(data)}") + self.serial.write(data) + + start = time.ticks_ms() + while time.ticks_diff(time.ticks_ms(), start) < timeout_ms: + if self.serial.any() >= 12: + time.sleep_ms(5) + rx = self.serial.read() + self._log_debug(f"RX: {self._format_bytes(rx)}") + return rx + time.sleep_ms(1) + self._log_debug("RX Timeout") + return None + + +class Fingerprint2Unit: + def __init__(self, id: int = 2, port: list | tuple = None, debug=False): + if id not in (0, 1, 2): + raise ValueError("Parameter 'id' must be 0, 1, or 2.") + self.id = id + self.serial = machine.UART(self.id, baudrate=115200, tx=port[1], rx=port[0], rxbuf=2048) + self.helper = SerialCmdHelper(self.serial, debug=debug) + self.debug = debug + self.max_id = 99 # 最大指纹容量 100 枚,范围 0 ~ 99 + + def _log_debug(self, message): + if self.debug: + print(f"[DEBUG] {message}") + + def _format_bytes(self, data): + return " ".join(f"{b:02X}" for b in data) + + def _parse_response(self, rx): + if rx is None: + return False + if len(rx) < 12: + return False + if rx[0] != 0xEF or rx[1] != 0x01: + return False + return rx[9] == 0x00 + + def send_cmd(self, cmd_bytes, timeout_ms=1000): + pkt = FingerprintPacket(0x01, cmd_bytes) + return self.helper.cmd(pkt, timeout_ms) + + def get_verify_image(self) -> bool: + """Capture fingerprint image for verification. + + :return: True if the fingerprint image was successfully captured, False otherwise. + :rtype: bool + + UiFlow2 Code Block: + + |get_verify_image.png| + + MicroPython Code Block: + + .. code-block:: python + + unit_fp2_0.get_verify_image() + """ + rx = self.send_cmd(b"\x01") + return self._parse_response(rx) + + def get_enroll_image(self) -> bool: + """Capture fingerprint image for enrollment. + + :return: True if the fingerprint image was successfully captured, False otherwise. + :rtype: bool + + UiFlow2 Code Block: + + |get_enroll_image.png| + + MicroPython Code Block: + + .. code-block:: python + + unit_fp2_0.get_enroll_image() + """ + rx = self.send_cmd(b"\x29") + return self._parse_response(rx) + + def gen_feature(self) -> bool: + """Generate fingerprint feature. + + Converts the original fingerprint image stored in the image buffer into a feature file, + which is then stored in the template buffer. + + :return: True if the fingerprint feature was successfully generate, False otherwise. + :rtype: bool + + UiFlow2 Code Block: + + |gen_feature.png| + + MicroPython Code Block: + + .. code-block:: python + + unit_fp2_0.gen_feature() + """ + rx = self.send_cmd(b"\x02") + return self._parse_response(rx) + + def gen_template(self) -> bool: + """Merge fingerprint features to generate a template. + + Combines two fingerprint feature files into one fingerprint template. + + :return: True if the fingerprint template was successfully generate, False otherwise. + :rtype: bool + + UiFlow2 Code Block: + + |gen_template.png| + + MicroPython Code Block: + + .. code-block:: python + + unit_fp2_0.gen_template() + """ + rx = self.send_cmd(b"\x05") + return self._parse_response(rx) + + def store_template(self, id) -> bool: + """Store fingerprint template into flash memory. + + Stores the generated fingerprint template into flash memory at the specified ID. + + :param int id: Storage location ID (range: 0 ~ 99) + :return: True if storage successful False otherwise. + :rtype: bool + + UiFlow2 Code Block: + + |store_template.png| + + MicroPython Code Block: + + .. code-block:: python + + unit_fp2_0.store_template(id) + """ + if not (0 <= id <= self.max_id): + raise ValueError(f"ID out of range. Must be between 0 and {self.max_id}.") + rx = self.send_cmd(b"\x06" + b"\x01" + id.to_bytes(2, "big")) + return self._parse_response(rx) + + def load_template(self, id) -> bool: + """Load fingerprint template from flash memory. + + Loads the fingerprint template with the specified ID from flash memory + into the template buffer. + + :param int id: ID of the fingerprint template to load + + :return: True if load template succcessful. + :rtype: bool + + UiFlow2 Code Block: + + |load_template.png| + + MicroPython Code Block: + + .. code-block:: python + + unit_fp2_0.load_template(id) + """ + rx = self.send_cmd(b"\x07" + b"\x02" + id.to_bytes(2, "big")) + return self._parse_response(rx) + + def delete_template(self, id) -> bool: + """Delete fingerprint template from flash memory. + + Deletes the fingerprint template with the specified ID from the flash storage. + + :param int id: ID of the fingerprint template to delete + :return: True if deletion successful, False otherwise + :rtype: bool + + UiFlow2 Code Block: + + |delete_template.png| + + MicroPython Code Block: + + .. code-block:: python + + unit_fp2_0.delete_template(id) + """ + if not (0 <= id <= self.max_id): + raise ValueError(f"ID out of range. Must be between 0 and {self.max_id}.") + rx = self.send_cmd(b"\x0C" + id.to_bytes(2, "big") + b"\x00\x01") + return self._parse_response(rx) + + def delete_all_template(self) -> bool: + """Clear the fingerprint database. + + Deletes all fingerprint templates stored in the fingerprint database. + + :return: True if deletion successful, False otherwise + :rtype: bool + + UiFlow2 Code Block: + + |delete_all_template.png| + + MicroPython Code Block: + + .. code-block:: python + + unit_fp2_0.delete_all_template() + """ + rx = self.send_cmd(b"\x0D") + return self._parse_response(rx) + + def upload_template(self, save_path="template.tzh", log=False) -> bool: + """Upload fingerprint template and save to specified path + + Uploads the template stored in the template buffer to the host controller. + + :param str path: File path to save the uploaded template + :return: True if upload template successful. + :rtype: bool + + UiFlow2 Code Block: + + |upload_template.png| + + MicroPython Code Block: + + .. code-block:: python + + unit_fp2_0.upload_template(path) + """ + size = 0 + off = 0 + ra = 0 + with open(save_path, "wb") as f: + for i in range(1, 9): + if i < 8: + size = 1000 + else: + size = 262 # 第8包 + cmd_bytes = b"\x7A" + off.to_bytes(2, "big") + size.to_bytes(2, "big") + off += size + pkt = FingerprintPacket(0x01, cmd_bytes) + self.serial.read() + data = pkt.build_packet() + self._log_debug(f"TX: {self._format_bytes(data)}") + self.serial.write(data) + start = time.ticks_ms() + ack_len = 12 + while time.ticks_diff(time.ticks_ms(), start) < 500: + if self.serial.any() >= ack_len: + time.sleep_ms(100) # 确保完整接收 + rx = self.serial.read() + self._log_debug(f"RX: {self._format_bytes(rx[:14])}") + if rx[9] == 0x00: # 确认码成功 + break + else: + print("rx error") + return False + else: + print("Timeout waiting for response") + return False + # 提取模板数据部分 + template_size = (rx[10] << 8) | rx[11] + payload = rx[12:-2] # 去除校验和 + f.write(payload) + ra += template_size + print("Block %d: size %d total: %d\n" % (i, template_size, ra)) + time.sleep_ms(100) + print(f"template upload success, save to {save_path}") + return True + + def download_template(self, filepath="template.tzh"): + """Download template. + + Reads the fingerprint template from the file system and downloads it to the fingerprint module. + + :param str path: Path to the fingerprint template file + :return: True if download template successful. + :rtype: bool + + UiFlow2 Code Block: + + |download_template.png| + + MicroPython Code Block: + + .. code-block:: python + + unit_fp2_0.download_template(path) + """ + + def _format_bytes(data): + return " ".join(f"{b:02X}" for b in data) + + def _send_packet(dev_addr: int, offset: int, chunk: bytes): + header = b"\xEF\x01" + addr = dev_addr.to_bytes(4, "big") + pid = b"\x01" # 数据包 + cmd = b"\x7B" # 下载模板指令 + off = offset.to_bytes(2, "big") + size = len(chunk).to_bytes(2, "big") + content = cmd + off + size + chunk + plen = len(chunk) + 7 # len(content).to_bytes(2, 'big') + body = pid + plen.to_bytes(2, "big") + content + checksum = sum(body) & 0xFFFF + packet = header + addr + body + checksum.to_bytes(2, "big") + self.serial.write(packet) + print(f"Send {len(packet)} bytes at offset {offset}\n") + + dev_addr = 0xFFFFFFFF + try: + with open(filepath, "rb") as f: + offset = 0 + while True: + chunk = f.read(1000) # 模板数据 = 最大128B - 指令码1B - 偏移2B - 大小2B + if not chunk: + break + _send_packet(dev_addr, offset, chunk) + offset += len(chunk) + # wait for ack + time.sleep_ms(260) + self.serial.read(128) + except OSError: + print("File not found: %s" % filepath) + return False + return True + + def upload_image(self, to_rgb565=True, byte_order=True) -> bytearray | None: + """Upload fingerprint image from module. + + Uploads the 4-bit grayscale fingerprint image from the module (size: 80x208). + Optionally, converts the raw image to RGB565 format suitable for display. + + :param bool to_rgb565: Whether to convert raw image to RGB565 (default True) + :param bool byte_order: If converting to RGB565, set True for little-endian byte order, or False for big-endian. Default is True. + :return: Fingerprint image data as bytearray. Returns None on failure. + :rtype: bytearray | None + + UiFlow2 Code Block: + + |upload_image.png| + + MicroPython Code Block: + + .. code-block:: python + + img_buf = unit_fp2_0.upload_image(to_rgb565=True, byte_order=True) + """ + width = 80 + height = 208 + pkt = FingerprintPacket(0x01, b"\x0A") + data = pkt.build_packet() + self.serial.read() # flush rx buffer + self._log_debug(f"TX: {self._format_bytes(data)}") + self.serial.write(data) + image_data = [] + start = time.ticks_ms() + while time.ticks_diff(time.ticks_ms(), start) < 300: + if self.serial.any() >= 12: + rx = self.serial.read(12) + self._log_debug(f"RX: {self._format_bytes(rx[:12])}") + if rx[9] != 0x00: + print("Module returned error confirmation code:", hex(rx[9])) + return None + print("Acknowledged, preparing to receive image data") + while True: + # Wait for packet header + while self.serial.any() < 9: + pass + rx = self.serial.read(9) + packet_type = rx[6] + data_len = (rx[7] << 8) | rx[8] + # Wait for full packet body + while self.serial.any() < data_len: + pass + data = self.serial.read(data_len) + image_data += data[:-2] # remove 2-byte checksum + if packet_type == 0x08: # last data packet + print("Image reception complete, length:", len(image_data)) + break + else: + print(f"Received data packet, length: {data_len}") + # Convert to RGB565 if requested + if to_rgb565: + rgb_buf = bytearray() + pixel_count = width * height + for i in range(pixel_count // 2): + byte = image_data[i] + high_gray = (byte >> 4) & 0x0F + low_gray = byte & 0x0F + for gray4 in (high_gray, low_gray): + gray8 = (gray4 << 4) | gray4 + r = gray8 >> 3 + g = gray8 >> 2 + b = gray8 >> 3 + rgb565 = (r << 11) | (g << 5) | b + if byte_order: + rgb_buf.append(rgb565 & 0xFF) + rgb_buf.append((rgb565 >> 8) & 0xFF) + else: + rgb_buf.append((rgb565 >> 8) & 0xFF) + rgb_buf.append(rgb565 & 0xFF) + return rgb_buf + else: + return bytearray(image_data) + print("Timeout waiting for image upload") + return None + + def get_valid_template_num(self) -> int: + """Get the number of valid fingerprint templates. + + Returns the count of fingerprint templates currently stored in the fingerprint database. + + :return: Number of valid fingerprint templates + :rtype: int + + UiFlow2 Code Block: + + |get_valid_template_num.png| + + MicroPython Code Block: + + .. code-block:: python + + unit_fp2_0.get_valid_template_num() + """ + rx = self.send_cmd(b"\x1D") + valid_templates = 0 + if len(rx) >= 12 and rx[6] == 0x07 and rx[9] == 0x00: + valid_templates = int.from_bytes(rx[10:12], "big") + return valid_templates + + def get_stored_template_id(self) -> list[int] | None: + """Get the list of stored fingerprint template IDs from the fingerprint sensor. + + This function queries the fingerprint sensor for its template index map, + which represents the occupied (used) template slots in the database, + and returns the list of those occupied IDs. + + :return: A list of occupied fingerprint template IDs, or None if retrieval fails. + :rtype: list[int] | None + + UiFlow2 Code Block: + + |get_stored_template_id.png| + + MicroPython Code Block: + + .. code-block:: python + + stored_ids = get_stored_template_id(sensor) + """ + rx = self.send_cmd(b"\x1F\x00") + total_bits = self.max_id + total_bytes = (total_bits + 7) // 8 # 向上取整所需字节数 + if rx and len(rx) >= 10 + total_bytes and rx[9] == 0x00: + data = rx[10 : 10 + total_bytes] + bits = [] + for byte in data: + for i in range(8): + bits.append(bool((byte >> i) & 0x01)) + return tuple(bits[:total_bits]) + else: + return tuple(False for _ in range(total_bits)) + + def find_match(self): + """Search for a matching fingerprint in the database. + + Compares the fingerprint features stored in the template buffer + with the stored templates in the database. + + :return: + - (id, score): A tuple of the matched fingerprint ID and match score. + - None: If no matching fingerprint is found. + + UiFlow2 Code Block: + + |find_match.png| + + MicroPython Code Block: + + .. code-block:: python + + unit_fp2_0.find_match() + """ + start_page = 0 + page_num = self.max_id + rx = self.send_cmd( + b"\x04" + b"\x01" + start_page.to_bytes(2, "big") + page_num.to_bytes(2, "big") + ) + if rx and len(rx) == 16 and rx[0] == 0xEF and rx[1] == 0x01 and rx[6] == 0x07: + if rx[9] == 0x00: + page_id = int.from_bytes(rx[10:12], "big") + match_score = int.from_bytes(rx[12:14], "big") + return (page_id, match_score) + else: + return None + + def match(self) -> int: + """Precisely match two fingerprint features. + + Compares two fingerprint feature files and returns the result and score. + + :return: Similarity score of the match + :rtype: int + + UiFlow2 Code Block: + + |match.png| + + MicroPython Code Block: + + .. code-block:: python + + unit_fp2_0.match() + """ + score = 0 + rx = self.send_cmd(b"\x03") + if rx and len(rx) == 16 and rx[0] == 0xEF and rx[1] == 0x01 and rx[6] == 0x07: + score = int.from_bytes(rx[10:12], "big") + if rx[9] == 0x00: + return (True, score) + else: + return (False, score) + + def is_connected(self) -> bool: + """Check whether the fingerprint module is connected. + + :return: True if the module is connected, False otherwise. + :rtype: bool + + UiFlow2 Code Block: + + |is_connected.png| + + MicroPython Code Block: + + .. code-block:: python + + unit_fp2_0.is_connected() + """ + rx = self.send_cmd(b"\x35") + if rx and rx[0] == 0xEF and rx[1] == 0x01 and len(rx) == 12: + return True + else: + return False + + def activate_module(self) -> None: + """Activate the module. + + UiFlow2 Code Block: + + |activate_module.png| + + MicroPython Code Block: + + .. code-block:: python + + unit_fp2_0.activate_module() + """ + rx = self.send_cmd(b"\xD4") + return self._parse_response(rx) + + def set_work_mode(self, mode: int = 0, save: bool = False) -> None: + """Set the working mode. + + :param int mode: Working mode (0: Auto sleep, 1: Always-on). + :param bool save: Whether to save the setting to the device. Default is False. + + UiFlow2 Code Block: + + |set_work_mode.png| + + MicroPython Code Block: + + .. code-block:: python + + unit_fp2_0.set_work_mode(mode, save) + """ + self.send_cmd(b"\xD2" + bytearray([mode])) + if save: + self.send_cmd(b"\xD6" + bytearray([1])) + + def get_work_mode(self) -> int: + """Get the current working mode. + + Returns the module's current working mode: + - 0: Auto sleep mode + - 1: Always-on mode + + :return: Current working mode + :rtype: int + + UiFlow2 Code Block: + + |get_work_mode.png| + + MicroPython Code Block: + + .. code-block:: python + + mode = unit_fp2_0.get_work_mode() + """ + rx = self.send_cmd(b"\xD3") + return rx[-3] + + def set_auto_sleep_time(self, time_s: int = 10, save: bool = False) -> None: + """Set the sleep timeout. + + This parameter is only effective in "Auto Sleep Mode". + It determines how long the fingerprint module waits without receiving any command + before it enters sleep mode and starts monitoring for fingerprint press. + + :param int time_s: Auto sleep timeout in seconds. Range: 10~254. + :param bool save: Whether to save this configuration to flash. + + UiFlow2 Code Block: + + |set_auto_sleep_time.png| + + MicroPython Code Block: + + .. code-block:: python + + unit_fp2_0.set_auto_sleep_time(30, save=True) + """ + time_s = min(max(time_s, 10), 254) + self.send_cmd(b"\xD0" + bytearray([time_s])) + if save: + self.send_cmd(b"\xD6" + bytearray([0])) + + def get_auto_sleep_time(self) -> int: + """Get auto sleep time. + + This value is only valid in "Timed Sleep Mode". It indicates how long the module will wait + without receiving commands before entering sleep state. + + :return: Auto sleep time in seconds + :rtype: int + + UiFlow2 Code Block: + + |get_auto_sleep_time.png| + + MicroPython Code Block: + + .. code-block:: python + + sleep_time = unit_fp2_0.get_auto_sleep_time() + """ + rx = self.send_cmd(b"\xD1") + if rx[-4] == 0x00: + return rx[-3] + + def get_work_status(self) -> bool: + """Get fingerprint module work status. + + :return: True if active, False otherwise + :rtype: bool + + UiFlow2 Code Block: + + |get_work_status.png| + + MicroPython Code Block: + + .. code-block:: python + + status = unit_fp2_0.get_work_status() + """ + rx = self.send_cmd(b"\xD5") + if rx and len(rx) == 13 and rx[-4] == 0x00: + return rx[-3] == 0x00 + return False + + def get_firmware_version(self) -> int: + """Get firmware version. + + :return: Firmware version number + :rtype: int + + UiFlow2 Code Block: + + |get_firmware_version.png| + + MicroPython Code Block: + + .. code-block:: python + + version = unit_fp2_0.get_firmware_version() + """ + rx = self.send_cmd(b"\xD7") + return rx[-3] + + def set_led_breath(self, start_color: int, end_color: int, repeat: int) -> bool: + """Set LED breathing mode. + + :param int start_color: Start color (bit0: blue, bit1: green, bit2: red) + :param int end_color: End color (bit0: blue, bit1: green, bit2: red) + :param int repeat: Number of cycles (0=infinite) + :return: True if command successful, False otherwise + :rtype: bool + + Color codes: + - 0x00: All off + - 0x01: Blue + - 0x02: Green + - 0x03: Cyan (blue + green) + - 0x04: Red + - 0x05: Magenta (red + blue) + - 0x06: Yellow (red + green) + - 0x07: White (red + green + blue) + + UiFlow2 Code Block: + + |set_led_breath.png| + + MicroPython Code Block: + + .. code-block:: python + + # Blue breathing light, 5 cycles + unit_fp2_0.set_led_breath(0x01, 0x01, 5) + + # Red to white breathing light, infinite cycles + unit_fp2_0.set_led_breath(0x04, 0x07, 0) + """ + cmd_bytes = b"\x3C" + bytes([1, start_color, end_color, repeat]) + rx = self.send_cmd(cmd_bytes) + return self._parse_response(rx) + + def set_led_color(self, color: int) -> bool: + """Set LED color. + + :param int color: LED color (0: always off, other values: always on with specified color) + :return: True if command successful, False otherwise + :rtype: bool + + Color codes: + - 0x00: Always off + - 0x01: Blue + - 0x02: Green + - 0x03: Cyan (blue + green) + - 0x04: Red + - 0x05: Magenta (red + blue) + - 0x06: Yellow (red + green) + - 0x07: White (red + green + blue) + + UiFlow2 Code Block: + + |set_led_color.png| + + MicroPython Code Block: + + .. code-block:: python + + # Always on white light + unit_fp2_0.set_led_color(0x07) + + # Always off (turn off all LEDs) + unit_fp2_0.set_led_color(0x00) + + # Always on red light + unit_fp2_0.set_led_color(0x04) + """ + if color == 0: + cmd_bytes = b"\x3C" + bytes([4, 0, 0, 0]) + else: + cmd_bytes = b"\x3C" + bytes([3, color, color, 0]) + rx = self.send_cmd(cmd_bytes) + return self._parse_response(rx) diff --git a/m5stack/libs/unit/manifest.py b/m5stack/libs/unit/manifest.py index e61e200b..5636c840 100644 --- a/m5stack/libs/unit/manifest.py +++ b/m5stack/libs/unit/manifest.py @@ -47,6 +47,7 @@ "fader.py", "fan.py", "finger.py", + "fingerprint2.py", "flash_light.py", "gateway_h2.py", "gesture.py", From d73eea492d7b0f12b0bf73334e31075e5f9c554c Mon Sep 17 00:00:00 2001 From: tinyu Date: Tue, 21 Oct 2025 11:45:33 +0800 Subject: [PATCH 287/322] libs/m5ui: Add LVGL TabView widget. Signed-off-by: tinyu --- m5stack/libs/m5ui/__init__.py | 1 + m5stack/libs/m5ui/manifest.py | 1 + m5stack/libs/m5ui/tabview.py | 95 +++++++++++++++++++++++++++++++++++ 3 files changed, 97 insertions(+) create mode 100644 m5stack/libs/m5ui/tabview.py diff --git a/m5stack/libs/m5ui/__init__.py b/m5stack/libs/m5ui/__init__.py index 51541011..8c3f009c 100644 --- a/m5stack/libs/m5ui/__init__.py +++ b/m5stack/libs/m5ui/__init__.py @@ -29,6 +29,7 @@ "M5Spinbox": "spinbox", "M5Spinner": "spinner", "M5Switch": "switch", + "M5TabView": "tabview", "M5TextArea": "textarea", "M5Win": "win", } diff --git a/m5stack/libs/m5ui/manifest.py b/m5stack/libs/m5ui/manifest.py index 871197b6..8a68c2d9 100644 --- a/m5stack/libs/m5ui/manifest.py +++ b/m5stack/libs/m5ui/manifest.py @@ -31,6 +31,7 @@ "spinbox.py", "spinner.py", "switch.py", + "tabview.py", "textarea.py", "win.py", ), diff --git a/m5stack/libs/m5ui/tabview.py b/m5stack/libs/m5ui/tabview.py new file mode 100644 index 00000000..5229f0bb --- /dev/null +++ b/m5stack/libs/m5ui/tabview.py @@ -0,0 +1,95 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +from m5ui.base import M5Base +from m5ui.button import M5Button +import lvgl as lv + + +def _button_clicked_event_cb(event_struct): + _button = event_struct.get_current_target() + + if not hasattr(_button, "get_parent"): + _button = lv.obj.__cast__(_button) + + tv = _button.get_parent().get_parent() + idx = _button.get_index() + lv.tabview.set_active(tv, idx, False) + + +class M5TabView(lv.tabview): + """Create a label object. + + :param str text: The text to display on the label. + :param int x: The x position of the label. + :param int y: The y position of the label. + :param int text_c: The text color of the label in hexadecimal format. + :param int bg_c: The background color of the label in hexadecimal format. + :param int bg_opa: The background opacity of the label (0-255). + :param lv.lv_font_t font: The font to use for the button text. + :param lv.obj parent: The parent object to attach the button to. If not specified, the button will be attached to the default screen. + + UiFlow2 Code Block: + + None + + MicroPython Code Block: + + .. code-block:: python + + from m5ui import M5Label + import lvgl as lv + + m5ui.init() + label_0 = M5Label(text="Hello, World!", x=10, y=10, text_c=0x212121, bg_c=0xFFFFFF, bg_opa=0, font=lv.font_montserrat_14, parent=page0) + """ + + def __init__( + self, + x=0, + y=0, + w=0, + h=0, + bar_size=60, + bar_pos=lv.DIR.TOP, + parent=None, + ): + if parent is None: + parent = lv.screen_active() + super().__init__(parent) + self.set_pos(x, y) + self.set_size(w, h) + self.set_tab_bar_position(bar_pos) + self.set_tab_bar_size(bar_size) + self.tab_num = 0 + + def add_tab(self, text): + _button = M5Button( + text=text, + w=lv.pct(100), + h=lv.pct(100), + bg_c=0xFFFFFF, + text_c=0x0, + parent=self.get_tab_bar(), + ) + _button.set_flex_grow(1) + _button.add_event_cb(_button_clicked_event_cb, lv.EVENT.CLICKED, None) + + _cont = self.get_content() + _page = lv.obj(_cont) + _page.set_size(lv.pct(100), lv.pct(100)) + self.tab_num += 1 + return _page + + def rename_tab(self, pos: int, txt: str) -> None: + if pos < self.tab_num: + super().rename_tab(pos, txt) + + def __getattr__(self, name): + if hasattr(M5Base, name): + method = getattr(M5Base, name) + bound_method = lambda *args, **kwargs: method(self, *args, **kwargs) + setattr(self, name, bound_method) + return bound_method + raise AttributeError(f"'{self.__class__.__name__}' object has no attribute '{name}'") From 3e830805f375427a1c47768b71e3b21d6681f703 Mon Sep 17 00:00:00 2001 From: tinyu Date: Tue, 21 Oct 2025 14:23:05 +0800 Subject: [PATCH 288/322] libs/m5ui: Add LVGL TabView widget docs and example. Signed-off-by: tinyu --- docs/en/m5ui/index.rst | 1 + docs/en/m5ui/tabview.rst | 56 ++++++ docs/en/refs/m5ui.tabview.ref | 13 ++ .../locales/zh_CN/LC_MESSAGES/m5ui/tabview.po | 174 ++++++++++++++++++ .../m5ui/tabview/tabview_cores3_example.m5f2 | 1 + .../m5ui/tabview/tabview_cores3_example.py | 69 +++++++ m5stack/libs/m5ui/label.py | 20 +- m5stack/libs/m5ui/tabview.py | 50 ++++- 8 files changed, 364 insertions(+), 20 deletions(-) create mode 100644 docs/en/m5ui/tabview.rst create mode 100644 docs/en/refs/m5ui.tabview.ref create mode 100644 docs/locales/zh_CN/LC_MESSAGES/m5ui/tabview.po create mode 100644 examples/m5ui/tabview/tabview_cores3_example.m5f2 create mode 100644 examples/m5ui/tabview/tabview_cores3_example.py diff --git a/docs/en/m5ui/index.rst b/docs/en/m5ui/index.rst index 22ff7c0c..8dee9620 100644 --- a/docs/en/m5ui/index.rst +++ b/docs/en/m5ui/index.rst @@ -81,5 +81,6 @@ Classes spinbox.rst spinner.rst switch.rst + tabview.rst textarea.rst win.rst diff --git a/docs/en/m5ui/tabview.rst b/docs/en/m5ui/tabview.rst new file mode 100644 index 00000000..5a2cebcb --- /dev/null +++ b/docs/en/m5ui/tabview.rst @@ -0,0 +1,56 @@ +.. currentmodule:: m5ui + +M5TabView +========== + +.. include:: ../refs/m5ui.tabview.ref + +M5TabView is a widget that can be used to create tabbed views in the user interface. + +UiFlow2 Example +--------------- + +tabview basic +^^^^^^^^^^^^^^ + +Open the |tabview_cores3_example.m5f2| project in UiFlow2. + +This example creates a tabview. + +UiFlow2 Code Block: + + |example.png| + +Example output: + + None + +MicroPython Example +------------------- + +tabview basic +^^^^^^^^^^^^^^ + +This example creates a tabview. + +MicroPython Code Block: + + .. literalinclude:: ../../../examples/m5ui/tabview/tabview_cores3_example.py + :language: python + :linenos: + +Example output: + + None + + +**API** +------- +M5TabView +^^^^^^^^^^ + +.. autoclass:: m5ui.tabview.M5TabView + :members: + +M5Label can be used in TabView. For specific usage, please refer to :ref:`m5ui.M5Label ` Documentation. + diff --git a/docs/en/refs/m5ui.tabview.ref b/docs/en/refs/m5ui.tabview.ref new file mode 100644 index 00000000..7dd50459 --- /dev/null +++ b/docs/en/refs/m5ui.tabview.ref @@ -0,0 +1,13 @@ +.. |add_tab.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/tabview/align_to.png +.. |rename_tab.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/tabview/rename_tab.png +.. |example.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/tabview/example.png + + +.. |tabview_cores3_example.m5f2| raw:: html + + + tabview_cores3_example.m5f2 + diff --git a/docs/locales/zh_CN/LC_MESSAGES/m5ui/tabview.po b/docs/locales/zh_CN/LC_MESSAGES/m5ui/tabview.po new file mode 100644 index 00000000..b91bd2d2 --- /dev/null +++ b/docs/locales/zh_CN/LC_MESSAGES/m5ui/tabview.po @@ -0,0 +1,174 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2016 - 2025 M5Stack Technology Co., Ltd +# This file is distributed under the same license as the UIFlow2 Programming +# Guide package. +# FIRST AUTHOR , 2025. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: UIFlow2 Programming Guide \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-10-21 14:17+0800\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: zh_CN\n" +"Language-Team: zh_CN \n" +"Plural-Forms: nplurals=1; plural=0;\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.16.0\n" + +#: ../../en/m5ui/tabview.rst:4 ../../en/m5ui/tabview.rst:50 +#: f8aaff8085724c9cbe38eb7e28b6bcec fe3084a33f924e60a4cd8322cac89cdb +msgid "M5TabView" +msgstr "M5TabView" + +#: ../../en/m5ui/tabview.rst:8 39d39083f595411f9a44e1ae197a57c6 +msgid "" +"M5TabView is a widget that can be used to create tabbed views in the user" +" interface." +msgstr "M5TabView 是一个可用于在用户界面中创建选项卡视图的组件。" + +#: ../../en/m5ui/tabview.rst:11 4ccf4866659847b2aba9803b87b1a964 +msgid "UiFlow2 Example" +msgstr "UiFlow2 示例" + +#: ../../en/m5ui/tabview.rst:14 ../../en/m5ui/tabview.rst:32 +#: 009cef26e9a5400b8a3b7f898b251576 47e3fcbe8a584d49a6c664f6898a84b3 +msgid "tabview basic" +msgstr "标签视图基础" + +#: ../../en/m5ui/tabview.rst:16 1a555f20b14d4a0bb89e4686d160f67c +msgid "Open the |tabview_cores3_example.m5f2| project in UiFlow2." +msgstr "在 UiFlow2 中打开 |tabview_cores3_example.m5f2| 项目。" + +#: ../../en/m5ui/tabview.rst:18 ../../en/m5ui/tabview.rst:34 +#: 47db8d11a0ea4a9a99a093534575830c e9a5c5bbc13b447b851246c8eea96d48 +msgid "This example creates a tabview." +msgstr "本示例创建了一个标签视图。" + +#: ../../en/m5ui/tabview.rst:20 6d697ff939dd4df8bbec838efe515b07 +#: 86a41203a50b4841bec2cc29731ef9b4 bcca9ebf7787411188de5aa958e797f9 +#: cea791fe5b3340aab0f19cfbb63e38f3 m5ui.tabview.M5TabView:11 +#: m5ui.tabview.M5TabView.add_tab:6 m5ui.tabview.M5TabView.rename_tab:6 of +msgid "UiFlow2 Code Block:" +msgstr "UiFlow2 代码块:" + +#: ../../en/m5ui/tabview.rst:22 5b843d1635bc435eb74f41d614904016 +msgid "|tabview_cores3_example.png|" +msgstr "" + +#: ../../en/m5ui/tabview.rst:24 ../../en/m5ui/tabview.rst:42 +#: 36d577315fb94d45841a615b54e9aff2 8b4f40a95a4142778ae289baef6cd47e +msgid "Example output:" +msgstr "示例输出:" + +#: ../../en/m5ui/tabview.rst:26 ../../en/m5ui/tabview.rst:44 +#: 39e3a309d0b34e39b9959d99bc757dd6 3b6fd4edffca4d84be144d79aa3bc8b4 +#: 85ffaa4297934a3ea869525098f20328 m5ui.tabview.M5TabView:13 of +msgid "None" +msgstr "" + +#: ../../en/m5ui/tabview.rst:29 8b45204efe6f440ba10444395978f4d5 +msgid "MicroPython Example" +msgstr "MicroPython 示例" + +#: ../../en/m5ui/tabview.rst:36 3681ae5c4fa049ff8d43bcb0c403fa2d +#: a0faacfccd6f459e82666482c393465b c3ac910e1c494b128b3f8c9e069ef9ba +#: c765971ae4544e16adef3a9a0aa542ad m5ui.tabview.M5TabView:15 +#: m5ui.tabview.M5TabView.add_tab:10 m5ui.tabview.M5TabView.rename_tab:10 of +msgid "MicroPython Code Block:" +msgstr "MicroPython 代码块:" + +#: ../../en/m5ui/tabview.rst:48 cf958ae7984a418f85ed01077c8e22df +msgid "**API**" +msgstr "" + +#: 2f333ccfae6e4d508551ad5405414083 m5ui.tabview.M5TabView:1 of +msgid "Bases: :py:class:`~lvgl.tabview`" +msgstr "" + +#: 8b5f2ea1833e49af8c62cfb1fe156990 m5ui.tabview.M5TabView:1 of +msgid "Create a TabView object." +msgstr "创建一个标签页视图对象。" + +#: ../../en/m5ui/tabview.rst 250f97edf0e449a3b635e41e17d4d4e2 +#: 444f50f5219541dca4e838f6470d25e8 9ab25bd18dfb4504895db06fc17dc610 +#: m5ui.tabview.M5TabView.add_tab m5ui.tabview.M5TabView.rename_tab of +msgid "Parameters" +msgstr "参数" + +#: 5d5165905eca4946a82e4146c7ab8d96 m5ui.tabview.M5TabView:3 of +msgid "The x position of the tab view." +msgstr "标签视图的 x 坐标位置。" + +#: aa2da75a65e0493fbcb510cccf14bdbc m5ui.tabview.M5TabView:4 of +msgid "The y position of the tab view." +msgstr "标签视图的 y 坐标位置。" + +#: d7780f94c42a464792052e0ac168a256 m5ui.tabview.M5TabView:5 of +msgid "The width of the tab view." +msgstr "标签视图的宽度。" + +#: c73ed2ef2b374fdf805e39e948bc7eb9 m5ui.tabview.M5TabView:6 of +msgid "The height of the tab view." +msgstr "标签视图的高度。" + +#: 44cafac5ab6740acb24187e667551d77 m5ui.tabview.M5TabView:7 of +msgid "The size of the tab bar." +msgstr "标签栏的大小。" + +#: 88abacbe24c247f0bc21f061d08565f0 m5ui.tabview.M5TabView:8 of +msgid "The position of the tab bar." +msgstr "标签栏的位置。" + +#: 04ce0b2a8012408e89ab64a5d64650ad m5ui.tabview.M5TabView:9 of +msgid "" +"The parent object to attach the tab view to. If not specified, the tab " +"view will be attached to the default screen." +msgstr "要附加标签视图的父对象。如果未指定,标签视图将附加到默认屏幕。" + +#: ace90a0df9b64d72acdf5c6ecef556d0 m5ui.tabview.M5TabView.add_tab:1 of +msgid "Add a tab to the tab view." +msgstr "向标签视图添加一个标签页。" + +#: 6517d9755d2c4850885b480280136ad7 m5ui.tabview.M5TabView.add_tab:3 of +msgid "The text to display on the tab." +msgstr "标签页上要显示的文本。" + +#: 52a2c484e38c45c0b224459c197802ac 8c99ea63ddc24ee4ac1834098839855c +#: m5ui.tabview.M5TabView.add_tab m5ui.tabview.M5TabView.rename_tab of +msgid "Return type" +msgstr "返回类型" + +#: c0739e0ff1d2424187f0d7deff296f9a m5ui.tabview.M5TabView.add_tab:8 of +msgid "|add_tab.png|" +msgstr "" + +#: ../../en/refs/m5ui.tabview.ref:1 4e37763c5a7c4f9f87a997d00bffbaf2 +msgid "add_tab.png" +msgstr "" + +#: 7c9b54eb99004eec8bf9a27495b42ff6 m5ui.tabview.M5TabView.rename_tab:1 of +msgid "Rename a tab in the tab view." +msgstr "重命名标签视图中的标签。" + +#: b1cbc60b595f4af29df0b6dc8efbd3ab m5ui.tabview.M5TabView.rename_tab:3 of +msgid "The position of the tab to rename." +msgstr "要重命名的标签页位置。" + +#: 3f65f35ad0ba4dc9a10d194b1e86f198 m5ui.tabview.M5TabView.rename_tab:4 of +msgid "The new text for the tab." +msgstr "标签页的新文本。" + +#: 5148ed7a179f4353a0f80c232275875a m5ui.tabview.M5TabView.rename_tab:8 of +msgid "|rename_tab.png|" +msgstr "" + +#: ../../en/m5ui/tabview.rst:55 b43c8a04903d4255bb27c60588f67f63 +msgid "" +"M5Label can be used in TabView. For specific usage, please refer to " +":ref:`m5ui.M5Label ` Documentation." +msgstr "M5Label 可以在 TabView 中使用。具体用法请参考 :ref:`m5ui.M5Label ` 文档。" \ No newline at end of file diff --git a/examples/m5ui/tabview/tabview_cores3_example.m5f2 b/examples/m5ui/tabview/tabview_cores3_example.m5f2 new file mode 100644 index 00000000..5f669093 --- /dev/null +++ b/examples/m5ui/tabview/tabview_cores3_example.m5f2 @@ -0,0 +1 @@ +{"version":"V2.0","versionNumber":"V2.3.6","type":"cores3","components":[{"name":"page0","type":"lvgl_page","layer":0,"screenId":"builtin","screenName":"","id":"r-BHP^g`T&%lk=qH","createTime":1761016547845,"backgroundColor":"#ffffff","isLVGL":true,"isSelected":true},{"name":"tabview0","type":"lvgl_tabview","layer":1,"screenId":"builtin","screenName":"","id":"yESq1C90N20T*65E","createTime":1761016552099,"x":0,"y":-2,"width":320,"height":240,"barSize":60,"mode":"TOP","modeOption":[{"label":"Top","value":"TOP"},{"label":"Bottom","value":"BOTTOM"},{"label":"Left","value":"LEFT"},{"label":"Right","value":"RIGHT"}],"tabList":[{"type":"tab","name":"Tab1","value":"Tab1"},{"type":"tab","name":"Tab2","value":"Tab2"}],"labelList":[{"type":"label","name":"label1","value":"This is label1","x":0,"y":0,"parent":"Tab1"}],"isInit":false,"pageId":"r-BHP^g`T&%lk=qH","isLVGL":true,"isSelected":false}],"resources":[{"hardware":["hardware_button","hardware_pin_button","imu","speaker","touch","als","mic","sdcard"]}],"units":[],"hats":[],"caps":[],"bases":[],"i2cs":[],"blockly":"truepage0tabview0label2lv.font_montserrat_24Tab1Hello World00palette#ffcc00palette#99ff99255tabview0label3Tab2hello M500tabview0label3lv.font_montserrat_24tabview0label3palette#6600cc255tabview0label3palette#33ff33255true","screen":[{"simulationName":"Built-in","type":"builtin","width":320,"height":240,"scale":0.77,"screenName":"","blockId":"","screenColorType":0,"rotation":1,"id":"builtin","createTime":1761016547844}],"logicWhenNum":0,"customList":[]} \ No newline at end of file diff --git a/examples/m5ui/tabview/tabview_cores3_example.py b/examples/m5ui/tabview/tabview_cores3_example.py new file mode 100644 index 00000000..eaf48441 --- /dev/null +++ b/examples/m5ui/tabview/tabview_cores3_example.py @@ -0,0 +1,69 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import os, sys, io +import M5 +from M5 import * +import m5ui +import lvgl as lv + + +page0 = None +tabview0 = None +Tab1 = None +Tab2 = None +label1 = None +label2 = None +label3 = None + + +def setup(): + global page0, tabview0, Tab1, Tab2, label1, label2, label3 + + M5.begin() + Widgets.setRotation(1) + m5ui.init() + page0 = m5ui.M5Page(bg_c=0xFFFFFF) + tabview0 = m5ui.M5TabView( + x=0, y=-2, w=320, h=240, bar_size=60, bar_pos=lv.DIR.TOP, parent=page0 + ) + Tab1 = tabview0.add_tab("Tab1") + Tab2 = tabview0.add_tab("Tab2") + label1 = m5ui.M5Label("This is label1", x=0, y=0, parent=Tab1) + + page0.screen_load() + label2 = m5ui.M5Label( + "Hello World", + x=0, + y=0, + text_c=0xFFCC00, + bg_c=0x99FF99, + bg_opa=255, + font=lv.font_montserrat_24, + parent=Tab1, + ) + label3 = m5ui.M5Label("hello M5", x=0, y=0, parent=Tab2) + label3.set_style_text_font(lv.font_montserrat_24, lv.PART.MAIN | lv.STATE.DEFAULT) + label3.set_text_color(0x6600CC, 255, 0) + label3.set_bg_color(0x33FF33, 255, 0) + + +def loop(): + global page0, tabview0, Tab1, Tab2, label1, label2, label3 + M5.update() + + +if __name__ == "__main__": + try: + setup() + while True: + loop() + except (Exception, KeyboardInterrupt) as e: + try: + m5ui.deinit() + from utility import print_error_msg + + print_error_msg(e) + except ImportError: + print("please update to latest firmware") diff --git a/m5stack/libs/m5ui/label.py b/m5stack/libs/m5ui/label.py index 1fcb090b..6b6afd85 100644 --- a/m5stack/libs/m5ui/label.py +++ b/m5stack/libs/m5ui/label.py @@ -83,18 +83,18 @@ def set_shadow(self, color: int, opa: int, align: int, offset_x: int, offset_y: self._shadow_label.set_text(self.get_text()) self._shadow_label.set_style_text_color(color, lv.PART.MAIN | lv.STATE.DEFAULT) self._shadow_label.set_style_text_opa(opa, lv.PART.MAIN | lv.STATE.DEFAULT) - self._shadow_label.set_style_text_font( - self.get_style_text_font(lv.PART.MAIN), lv.PART.MAIN - ) - self._shadow_label.set_width(self.get_width()) + if isinstance(self.get_parent(), lv.list): + self._shadow_label.set_style_text_font( + self.get_style_text_font(lv.PART.MAIN), lv.PART.MAIN + ) + self._shadow_label.set_width(self.get_width()) - for part in (lv.PART.MAIN,): - self._shadow_label.set_style_pad_left(self.get_style_pad_left(part), part) - self._shadow_label.set_style_pad_right(self.get_style_pad_right(part), part) - self._shadow_label.set_style_pad_top(self.get_style_pad_top(part), part) - self._shadow_label.set_style_pad_bottom(self.get_style_pad_bottom(part), part) + for part in (lv.PART.MAIN,): + self._shadow_label.set_style_pad_left(self.get_style_pad_left(part), part) + self._shadow_label.set_style_pad_right(self.get_style_pad_right(part), part) + self._shadow_label.set_style_pad_top(self.get_style_pad_top(part), part) + self._shadow_label.set_style_pad_bottom(self.get_style_pad_bottom(part), part) - if isinstance(self.get_parent(), lv.list): offset_x -= self.get_style_pad_left(part) self._shadow_label.align_to(self, align, offset_x, offset_y) diff --git a/m5stack/libs/m5ui/tabview.py b/m5stack/libs/m5ui/tabview.py index 5229f0bb..00b51c16 100644 --- a/m5stack/libs/m5ui/tabview.py +++ b/m5stack/libs/m5ui/tabview.py @@ -19,16 +19,15 @@ def _button_clicked_event_cb(event_struct): class M5TabView(lv.tabview): - """Create a label object. + """Create a TabView object. - :param str text: The text to display on the label. - :param int x: The x position of the label. - :param int y: The y position of the label. - :param int text_c: The text color of the label in hexadecimal format. - :param int bg_c: The background color of the label in hexadecimal format. - :param int bg_opa: The background opacity of the label (0-255). - :param lv.lv_font_t font: The font to use for the button text. - :param lv.obj parent: The parent object to attach the button to. If not specified, the button will be attached to the default screen. + :param int x: The x position of the tab view. + :param int y: The y position of the tab view. + :param int w: The width of the tab view. + :param int h: The height of the tab view. + :param int bar_size: The size of the tab bar. + :param int bar_pos: The position of the tab bar. + :param lv.obj parent: The parent object to attach the tab view to. If not specified, the tab view will be attached to the default screen. UiFlow2 Code Block: @@ -42,7 +41,7 @@ class M5TabView(lv.tabview): import lvgl as lv m5ui.init() - label_0 = M5Label(text="Hello, World!", x=10, y=10, text_c=0x212121, bg_c=0xFFFFFF, bg_opa=0, font=lv.font_montserrat_14, parent=page0) + tabview0 = m5ui.M5TabView(x=0, y=-2, w=320, h=240, bar_size=60, bar_pos=lv.DIR.TOP, parent=page0) """ def __init__( @@ -65,6 +64,22 @@ def __init__( self.tab_num = 0 def add_tab(self, text): + """Add a tab to the tab view. + + :param str text: The text to display on the tab. + :rtype: lv.obj + + UiFlow2 Code Block: + + |add_tab.png| + + MicroPython Code Block: + + .. code-block:: python + + Tab1 = tabview0.add_tab("Tab1") + Tab2 = tabview0.add_tab("Tab2") + """ _button = M5Button( text=text, w=lv.pct(100), @@ -83,6 +98,21 @@ def add_tab(self, text): return _page def rename_tab(self, pos: int, txt: str) -> None: + """Rename a tab in the tab view. + + :param int pos: The position of the tab to rename. + :param str txt: The new text for the tab. + + UiFlow2 Code Block: + + |rename_tab.png| + + MicroPython Code Block: + + .. code-block:: python + + tabview0.rename_tab(0, 'hello M5') + """ if pos < self.tab_num: super().rename_tab(pos, txt) From e647471120ded71d25569dc1b781ffb265e6549f Mon Sep 17 00:00:00 2001 From: hlym123 Date: Thu, 23 Oct 2025 12:16:08 +0800 Subject: [PATCH 289/322] lib/m5ui: Add LVGL Table. Signed-off-by: hlym123 --- docs/en/m5ui/index.rst | 1 + docs/en/m5ui/table.rst | 418 ++++++++++++ docs/en/refs/m5ui.table.ref | 31 + docs/locales/zh_CN/LC_MESSAGES/m5ui/table.po | 630 ++++++++++++++++++ .../m5ui/table/cores3_m5ui_table_example.m5f2 | 1 + .../m5ui/table/cores3_m5ui_table_example.py | 83 +++ m5stack/libs/m5ui/__init__.py | 1 + m5stack/libs/m5ui/manifest.py | 1 + m5stack/libs/m5ui/table.py | 50 ++ 9 files changed, 1216 insertions(+) create mode 100644 docs/en/m5ui/table.rst create mode 100644 docs/en/refs/m5ui.table.ref create mode 100644 docs/locales/zh_CN/LC_MESSAGES/m5ui/table.po create mode 100644 examples/m5ui/table/cores3_m5ui_table_example.m5f2 create mode 100644 examples/m5ui/table/cores3_m5ui_table_example.py create mode 100644 m5stack/libs/m5ui/table.py diff --git a/docs/en/m5ui/index.rst b/docs/en/m5ui/index.rst index 8dee9620..f6abe9ea 100644 --- a/docs/en/m5ui/index.rst +++ b/docs/en/m5ui/index.rst @@ -81,6 +81,7 @@ Classes spinbox.rst spinner.rst switch.rst + table.rst tabview.rst textarea.rst win.rst diff --git a/docs/en/m5ui/table.rst b/docs/en/m5ui/table.rst new file mode 100644 index 00000000..dc3473cd --- /dev/null +++ b/docs/en/m5ui/table.rst @@ -0,0 +1,418 @@ +.. currentmodule:: m5ui + +M5Table +======= + +.. include:: ../refs/m5ui.table.ref + +M5Table are built from rows, columns, and cells containing text. + +UiFlow2 Example +--------------- + +Table Basic Usage Example +^^^^^^^^^^^^^^^^^^^^^^^^^ + +Open the |cores3_m5ui_table_example.m5f2| project in UiFlow2. + +This example demonstrates how to create a table with student information including names, ages, and scores. The table displays data for three students: Alice (18, 95), Bob (18, 80), and Carol (17, 86). + +UiFlow2 Code Block: + + |cores3_m5ui_table_example.png| + +Example output: + + None. + +MicroPython Example +------------------- + +Table Basic Usage Example +^^^^^^^^^^^^^^^^^^^^^^^^^ + +This example demonstrates how to create a table with student information including names, ages, and scores. The table displays data for three students: Alice (18, 95), Bob (18, 80), and Carol (17, 86). + +MicroPython Code Block: + + .. literalinclude:: ../../../examples/m5ui/table/cores3_m5ui_table_example.py + :language: python + :linenos: + +Example output: + + None. + +**API** +------- + +M5Table +^^^^^^^ + +.. autoclass:: m5ui.table.M5Table + :members: + :member-order: bysource + + .. py:method:: set_cell_value(row, col, value) + + Set the value of a cell. + + New rows/columns are added automatically if required. + + :param int row: Row index [0 .. row_cnt - 1] + :param int col: Column index [0 .. col_cnt - 1] + :param str value: Text to display in the cell + + :return: None + + UiFlow2 Code Block: + + |set_cell_value.png| + + MicroPython Code Block: + + .. code-block:: python + + table_0.set_cell_value(row, col, value) + + .. py:method:: get_cell_value(row, col) + + Get the value of a cell. + + :param int row: Row index + :param int col: Column index + :return: Text in the cell + :rtype: str + + UiFlow2 Code Block: + + |get_cell_value.png| + + MicroPython Code Block: + + .. code-block:: python + + table_0.get_cell_value() + + .. py:method:: set_row_count(row_cnt) + + Set the number of rows. + + :param int row_cnt: Number of rows. + :return: None + + UiFlow2 Code Block: + + |set_row_count.png| + + MicroPython Code Block: + + .. code-block:: python + + table_0.set_row_count(row_cnt) + + .. py:method:: set_column_count(col_cnt) + + Set the number of columns. + + :param int col_cnt: Number of columns. + :return: None + + UiFlow2 Code Block: + + |set_column_count.png| + + MicroPython Code Block: + + .. code-block:: python + + table_0.set_column_count(col_cnt) + + .. py:method:: get_row_count() + + Get the number of rows. + + :return: Number of row. + :rtype: int + + UiFlow2 Code Block: + + |get_row_count.png| + + MicroPython Code Block: + + .. code-block:: python + + row_cnt = table_0.get_row_count() + + .. py:method:: get_column_count() + + Get the number of columns. + + :return: Number of columns. + :rtype: int + + UiFlow2 Code Block: + + |get_column_count.png| + + MicroPython Code Block: + + .. code-block:: python + + col_cnt = table_0.get_column_count() + + .. py:method:: set_column_width(col, width) + + Set the width of a column. + + :param int col: Column index [0 .. LV_TABLE_COL_MAX - 1]. + :param int width: Column width. + :return: None + + UiFlow2 Code Block: + + |set_column_width.png| + + MicroPython Code Block: + + .. code-block:: python + + table_0.set_column_width(col, width) + + .. py:method:: get_column_width(col) + + Get the width of a column. + + :param int col: Column index [0 .. LV_TABLE_COL_MAX - 1]. + :return: Column width. + :rtype: int + + UiFlow2 Code Block: + + |get_column_width.png| + + MicroPython Code Block: + + .. code-block:: python + + width = table_0.get_column_width() + + .. py:method:: set_pos(x, y) + + Set the position of the Table. + + :param int x: The x position of the Table. + :param int y: The y position of the Table. + :return: None + + UiFlow2 Code Block: + + |set_pos.png| + + MicroPython Code Block: + + .. code-block:: python + + table_0.set_pos(x, y) + + .. py:method:: set_x(x) + + Set the x position of the Table. + + :param int x: The x position of the Table. + :return: None + + UiFlow2 Code Block: + + |set_x.png| + + MicroPython Code Block: + + .. code-block:: python + + table_0.set_x(x) + + .. py:method:: set_y(y) + + Set the y position of the Table. + + :param int y: The y position of the Table. + :return: None + + UiFlow2 Code Block: + + |set_y.png| + + MicroPython Code Block: + + .. code-block:: python + + table_0.set_y(y) + + .. py:method:: get_x() + + Get the x position of the Table. + + :return: The x position of the Table. + :rtype: int + + UiFlow2 Code Block: + + |get_x.png| + + MicroPython Code Block: + + .. code-block:: python + + x = table_0.get_x() + + .. py:method:: get_y() + + Get the y position of the Table. + + :return: The y position of the Table. + :rtype: int + + UiFlow2 Code Block: + + |get_y.png| + + MicroPython Code Block: + + .. code-block:: python + + y = table_0.get_y() + + .. py:method:: set_size(width, height) + + Set the size of the Table. + + :param int width: The width of the Table. + :param int height: The height of the Table. + :return: None + + UiFlow2 Code Block: + + |set_size.png| + + MicroPython Code Block: + + .. code-block:: python + + table_0.set_size(width, height) + + .. py:method:: set_width(width) + + Set the width of the Table. + + :param int width: The width of the Table. + :return: None + + UiFlow2 Code Block: + + |set_width.png| + + MicroPython Code Block: + + .. code-block:: python + + table_0.set_width(width) + + .. py:method:: get_width() + + Get the width of the Table. + + :return: The width of the Table. + :rtype: int + + UiFlow2 Code Block: + + |get_width.png| + + MicroPython Code Block: + + .. code-block:: python + + width = table_0.get_width() + + .. py:method:: set_height(height) + + Set the height of the Table. + + :param int height: The height of the Table. + :return: None + + UiFlow2 Code Block: + + |set_height.png| + + MicroPython Code Block: + + .. code-block:: python + + table_0.set_height(height) + + .. py:method:: get_height() + + Get the height of the Table. + + :return: The height of the Table. + :rtype: int + + UiFlow2 Code Block: + + |get_height.png| + + MicroPython Code Block: + + .. code-block:: python + + height = table_0.get_height() + + .. py:method:: align_to(obj, align, x, y) + + Align the Table relative to another object. + + :param obj: The reference object (e.g. page0). + :param int align: Alignment option (see lv.ALIGN constants below). + :param int x: X offset after alignment. + :param int y: Y offset after alignment. + :return: None + + UiFlow2 Code Block: + + |align_to.png| + + MicroPython Code Block: + + .. code-block:: python + + table_0.align_to(page0, lv.ALIGN.CENTER, 0, 0) + + .. py:data:: lv.ALIGN + + Alignment options for positioning objects. + + - lv.ALIGN.DEFAULT + - lv.ALIGN.TOP_LEFT + - lv.ALIGN.TOP_MID + - lv.ALIGN.TOP_RIGHT + - lv.ALIGN.BOTTOM_LEFT + - lv.ALIGN.BOTTOM_MID + - lv.ALIGN.BOTTOM_RIGHT + - lv.ALIGN.LEFT_MID + - lv.ALIGN.RIGHT_MID + - lv.ALIGN.CENTER + - lv.ALIGN.OUT_TOP_LEFT + - lv.ALIGN.OUT_TOP_MID + - lv.ALIGN.OUT_TOP_RIGHT + - lv.ALIGN.OUT_BOTTOM_LEFT + - lv.ALIGN.OUT_BOTTOM_MID + - lv.ALIGN.OUT_BOTTOM_RIGHT + - lv.ALIGN.OUT_LEFT_TOP + - lv.ALIGN.OUT_LEFT_MID + - lv.ALIGN.OUT_LEFT_BOTTOM + - lv.ALIGN.OUT_RIGHT_TOP + - lv.ALIGN.OUT_RIGHT_MID + - lv.ALIGN.OUT_RIGHT_BOTTOM diff --git a/docs/en/refs/m5ui.table.ref b/docs/en/refs/m5ui.table.ref new file mode 100644 index 00000000..f1f007c2 --- /dev/null +++ b/docs/en/refs/m5ui.table.ref @@ -0,0 +1,31 @@ +.. |cores3_m5ui_table_example.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/table/example.png + +.. |set_cell_value.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/table/set_cell_value.png +.. |get_cell_value.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/table/get_cell_value.png +.. |set_row_count.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/table/set_row_count.png +.. |set_column_count.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/table/set_column_count.png +.. |get_row_count.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/table/get_row_count.png +.. |get_column_count.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/table/get_column_count.png +.. |set_column_width.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/table/set_column_width.png +.. |get_column_width.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/table/get_column_width.png +.. |set_pos.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/table/set_pos.png +.. |set_x.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/table/set_x.png +.. |set_y.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/table/set_y.png +.. |get_x.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/table/get_x.png +.. |get_y.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/table/get_y.png +.. |set_size.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/table/set_size.png +.. |set_width.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/table/set_width.png +.. |set_height.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/table/set_height.png +.. |get_width.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/table/get_width.png +.. |get_height.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/table/get_height.png +.. |align_to.png| image:: https://static-cdn.m5stack.com/mpy_docs/m5ui/table/align_to.png + +.. |cores3_m5ui_table_example.m5f2| raw:: html + + + cores3_m5ui_table_example.m5f2 + + diff --git a/docs/locales/zh_CN/LC_MESSAGES/m5ui/table.po b/docs/locales/zh_CN/LC_MESSAGES/m5ui/table.po new file mode 100644 index 00000000..df4dcbc2 --- /dev/null +++ b/docs/locales/zh_CN/LC_MESSAGES/m5ui/table.po @@ -0,0 +1,630 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2016 - 2025 M5Stack Technology Co., Ltd +# This file is distributed under the same license as the UIFlow2 Programming +# Guide package. +# FIRST AUTHOR , 2025. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: UIFlow2 Programming Guide \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-10-23 12:10+0800\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: zh_CN\n" +"Language-Team: zh_CN \n" +"Plural-Forms: nplurals=1; plural=0;\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.16.0\n" + +#: ../../en/m5ui/table.rst:4 ../../en/m5ui/table.rst:50 +#: c876f3e60f5343d59b6de38bacf1abde fc4efe692bd74cf3987d4d692892f347 +msgid "M5Table" +msgstr "M5Table" + +#: ../../en/m5ui/table.rst:8 bf9885eecc2a41449f821d9fc5bed059 +msgid "M5Table are built from rows, columns, and cells containing text." +msgstr "M5Table 由包含文本的行、列和单元格构成。" + +#: ../../en/m5ui/table.rst:11 b9b8973a384a4622a4619f950d631bd0 +msgid "UiFlow2 Example" +msgstr "UiFlow2 应用示例" + +#: ../../en/m5ui/table.rst:14 ../../en/m5ui/table.rst:32 +#: 6788e82c8b1e4488b5de409251fd47b7 +msgid "Table Basic Usage Example" +msgstr "表格基本使用示例" + +#: ../../en/m5ui/table.rst:16 f3ae5be9651045769bcbc56bfe10d545 +msgid "Open the |cores3_m5ui_table_example.m5f2| project in UiFlow2." +msgstr "在 UiFlow2 中打开 |cores3_m5ui_table_example.m5f2| 项目。" + +#: ../../en/m5ui/table.rst:18 ../../en/m5ui/table.rst:34 +#: 0e97eae21b324a938140ed773551d5f1 5ba35147260a441d8d68902eb574b70e +msgid "" +"This example demonstrates how to create a table with student information " +"including names, ages, and scores. The table displays data for three " +"students: Alice (18, 95), Bob (18, 80), and Carol (17, 86)." +msgstr "" +"此示例演示了如何创建一个包含学生信息的表格,包括姓名、年龄和分数。该表格显示三名学生的数据:Alice (18, 95)、Bob (18, 80)" +" 和 Carol (17, 86)。" + +#: ../../en/m5ui/table.rst:20 ../../en/m5ui/table.rst:68 +#: ../../en/m5ui/table.rst:87 ../../en/m5ui/table.rst:104 +#: ../../en/m5ui/table.rst:121 ../../en/m5ui/table.rst:138 +#: ../../en/m5ui/table.rst:155 ../../en/m5ui/table.rst:173 +#: ../../en/m5ui/table.rst:191 ../../en/m5ui/table.rst:209 +#: ../../en/m5ui/table.rst:226 ../../en/m5ui/table.rst:243 +#: ../../en/m5ui/table.rst:260 ../../en/m5ui/table.rst:277 +#: ../../en/m5ui/table.rst:295 ../../en/m5ui/table.rst:312 +#: ../../en/m5ui/table.rst:329 ../../en/m5ui/table.rst:346 +#: ../../en/m5ui/table.rst:363 ../../en/m5ui/table.rst:383 +#: 54ffd8f584294e55a9e8ac62fdb79998 579881ac22b6472c981d21728e567f94 +#: 5d6efb476cf14084ada61b602cd4d2a8 5dbd8d9f85c04cb4bd30f6c5d6c15cc7 +#: 62dceb371c3747aab1c989b1b6dfd69b 634ae17525cd45f9a67ca7670d887648 +#: 64f36dffd962408dadb638d42a71279b 7f340e003b644884a9f9c9cd8940b620 +#: 812c16e4c33b47c5b083606cfaa07c42 b734e8d34ef6452282c0ce351ae50ed8 +#: bdb773d8e22649829966daeeafcd0189 d074b4984eda460db15cfd437a70f3a8 +#: d3e1f99de9c742e88a72e3edc78aa5bc df60f5b7c6c146fb89baf2da5c1fa34e +#: e5e2c52d79bc47e59340cd2dfab9f5b8 f3e74e92ac744f2bb6798342b91461a5 +#: m5ui.table.M5Table:11 of +msgid "UiFlow2 Code Block:" +msgstr "UiFlow2 代码块:" + +#: ../../en/m5ui/table.rst:22 2bfcafa6716d488aa603c49214e9629e +msgid "|cores3_m5ui_table_example.png|" +msgstr "|cores3_m5ui_table_example.png|" + +#: ../../en/refs/m5ui.table.ref:1 67eab3f7921b4dbd83c8be7ffdb7f6ef +msgid "cores3_m5ui_table_example.png" +msgstr "cores3_m5ui_table_example.png" + +#: ../../en/m5ui/table.rst:24 ../../en/m5ui/table.rst:42 +#: d09055bb1707467a936809834a7335c6 +msgid "Example output:" +msgstr "示例输出:" + +#: ../../en/m5ui/table.rst:26 ../../en/m5ui/table.rst:44 +#: f999396b5ab845cfa9912c94ef371ccf +msgid "None." +msgstr "无。" + +#: ../../en/m5ui/table.rst:29 0a681efc77584a31a40cca8fdb22b2bb +msgid "MicroPython Example" +msgstr "MicroPython 示例" + +#: ../../en/m5ui/table.rst:36 ../../en/m5ui/table.rst:72 +#: ../../en/m5ui/table.rst:91 ../../en/m5ui/table.rst:108 +#: ../../en/m5ui/table.rst:125 ../../en/m5ui/table.rst:142 +#: ../../en/m5ui/table.rst:159 ../../en/m5ui/table.rst:177 +#: ../../en/m5ui/table.rst:195 ../../en/m5ui/table.rst:213 +#: ../../en/m5ui/table.rst:230 ../../en/m5ui/table.rst:247 +#: ../../en/m5ui/table.rst:264 ../../en/m5ui/table.rst:281 +#: ../../en/m5ui/table.rst:299 ../../en/m5ui/table.rst:316 +#: ../../en/m5ui/table.rst:333 ../../en/m5ui/table.rst:350 +#: ../../en/m5ui/table.rst:367 ../../en/m5ui/table.rst:387 +#: 12f1bce221a540eeb65287ac8f969eb5 3b09cf3dff0a4c3f9900308e0ea5f058 +#: 3c28341833474d1cb4e599ae331cdbb9 4d0be268e0df4bc997646c6772e5c675 +#: 4e88f168847d42d9b7810ac0ea011a2e 50dc981f802e45a28353f683a9f1c937 +#: 735b742472534943898a039bf464c837 b2f2558122a34df3a889318e9e6a4d1c +#: b7ffe92d4eb3418d800255fafcd84cd9 bbb11231c2154eecb9435bdf5c3fa223 +#: c77dc0f1bd90457aae4c586e855128af d72bdb30392240c09f41e6a5f3ee09c9 +#: dd67f3b7c2ca47509b2f0c884a54a2f2 dd90c48f7e63477f86a1da1b0d1e741e +#: e42197bbe06048dca88ab5e7660618bf m5ui.table.M5Table:15 of +msgid "MicroPython Code Block:" +msgstr "MicroPython 代码块:" + +#: ../../en/m5ui/table.rst:47 aa3c4e4c9eed45e38e905812c5c564b7 +msgid "**API**" +msgstr "**API 应用**" + +#: 137dcace5699442c96b5021d5ef0e762 m5ui.table.M5Table:1 of +msgid "Bases: :py:class:`~lvgl.table`" +msgstr "" + +#: 69adc1b05b8b4543a548c13fbad3d246 m5ui.table.M5Table:1 of +msgid "Create a table object." +msgstr "创建表格对象。" + +#: ../../en/m5ui/table.rst 0f3a25541b9f4da2b16c59ec4f14dd76 +#: 1b3758c498f044e8af3e94b116970a56 264360a58de94d529893c6ba4310d43b +#: 2874bc8f5e314248be4a4c2092c37752 322144e8f30d4e6495f8351067733d61 +#: 5f7cd41f1e224a0ca32b71c73edbb115 8bb11a9195df4f13a432e1ce93f2d1d4 +#: 9b761c4e3d4f456597083b3331ad4c6b dc84027c9b6d4404b411584227b8d011 +#: f7c9b0eea1024355a458460b6ad395c9 +msgid "Parameters" +msgstr "参数" + +#: 2fd3d2aa92b34442839ad799cf1a2906 m5ui.table.M5Table:3 of +msgid "The x position of the table." +msgstr "表格的 x 坐标位置。" + +#: c89c15c14a7d430bb56ee3f2d58726a2 m5ui.table.M5Table:4 of +msgid "The y position of the table." +msgstr "表格的 y 坐标位置。" + +#: 1e7f1691d79a44d0951123e46002486e m5ui.table.M5Table:5 of +msgid "The width of the table." +msgstr "表格的宽度。" + +#: aa2b1854d8874de1927145a13041a115 m5ui.table.M5Table:6 of +msgid "The height of the table." +msgstr "表格的高度。" + +#: ../../en/m5ui/table.rst:101 685aca28dc4c4b3d95bff43320409ab8 +#: 921617c5ecf446aba12245d968c59e5a m5ui.table.M5Table:7 of +msgid "Number of rows." +msgstr "行数。" + +#: ../../en/m5ui/table.rst:118 ../../en/m5ui/table.rst:152 +#: 44ccf59d9f0f4099ad4b8c3cd7562feb ac1073d37bf64dcc90e115e3999cb1fc +#: e9a11bf08ef6401b916d88442b977a6c m5ui.table.M5Table:8 of +msgid "Number of columns." +msgstr "列数。" + +#: b22fb907e7cf4224aa4858f70e5a7dcf m5ui.table.M5Table:9 of +msgid "" +"The parent object to attach the table to. If not specified, the table " +"will be attached to the default screen." +msgstr "要附加表格的父对象。如果未指定,表格将附加到默认屏幕。" + +#: ../../en/m5ui/table.rst:66 ../../en/m5ui/table.rst:102 +#: ../../en/m5ui/table.rst:119 ../../en/m5ui/table.rst:171 +#: ../../en/m5ui/table.rst:207 ../../en/m5ui/table.rst:224 +#: ../../en/m5ui/table.rst:241 ../../en/m5ui/table.rst:293 +#: ../../en/m5ui/table.rst:310 ../../en/m5ui/table.rst:344 +#: ../../en/m5ui/table.rst:381 0a6bcdcbdf414833b5107daab2431d8f +#: 3c83f708a75c4aa2940f5b06f8d06956 85c8d0ad4cc14c6ca7444b3ce5c537a3 +#: a0e001350a0e44cdb720e5c5442da4bb a4ed395b2eda48378a26b06f67aa2101 +#: be83beb95a3c4c8ca9cf22ee2dbb4925 c70848f8140c4f32a0fa5b9735011eee +#: d3f6874ce3f549fda157215c822c10bd ee4720287a3d4923bfe7c047580be971 +#: m5ui.table.M5Table:13 of +msgid "None" +msgstr "" + +#: ../../en/m5ui/table.rst:58 47cc6b246ea444c69e7f2cc8af4feda7 +msgid "Set the value of a cell." +msgstr "" + +#: ../../en/m5ui/table.rst:60 83e3a39136fc4327a04324ba68a011e5 +msgid "New rows/columns are added automatically if required." +msgstr "" + +#: ../../en/m5ui/table.rst:62 d379218f7db34b859052e16bfa044456 +msgid "Row index [0 .. row_cnt - 1]" +msgstr "" + +#: ../../en/m5ui/table.rst:63 a6acb828eb2943aba2604b2f12903ee0 +msgid "Column index [0 .. col_cnt - 1]" +msgstr "" + +#: ../../en/m5ui/table.rst:64 0ed250cb641745e4a0766b90b850886d +msgid "Text to display in the cell" +msgstr "" + +#: ../../en/m5ui/table.rst 01e4208c16d04b7f90f4c322facde6e6 +#: 0c883c4fdc2a43408c2a270a49bc0053 1c87c51b13c647d3a4a023d290e0c2df +#: 2671d604944f40328446a21304de17c3 6689ae9ea48843e9a3d9da412ae9c10e +#: 722322c50fbc4c54a21801adb4b94ea4 8ba594820b554d9793bab81b582edaf8 +#: aa12c50d91744a55858c0d994f9c47df b59d3e2859cf4fac9df508d175863ef4 +#: d73e8d3e9a104fa9b694c24829cb555d db616cf86d1c47289bc4c6ca1429776d +#: e59a798de2ea46d5a5f10dd33b1be767 f6b9ac7d06dd4865a7875f12080fb829 +msgid "Returns" +msgstr "" + +#: ../../en/m5ui/table.rst:70 2f2aecad0c874569bddd5382a9b68a5e +msgid "|set_cell_value.png|" +msgstr "" + +#: ../../en/refs/m5ui.table.ref:3 b374eaf476114663bfca14cf13c80693 +msgid "set_cell_value.png" +msgstr "" + +#: ../../en/m5ui/table.rst:80 f064ed7be834493db94b8262670b439c +msgid "Get the value of a cell." +msgstr "" + +#: ../../en/m5ui/table.rst:82 0a50e2b10d2c49d2894493ab6cf18700 +msgid "Row index" +msgstr "" + +#: ../../en/m5ui/table.rst:83 a75704b1f38d4f85bbdf32a20bb22a36 +msgid "Column index" +msgstr "" + +#: ../../en/m5ui/table.rst:84 a7d9d00324fe43f0b33ca14158a313c0 +msgid "Text in the cell" +msgstr "" + +#: ../../en/m5ui/table.rst 8b66f2c97d8c49ff88298b09250cbcfa +#: 8f069428379d40f1ac0117f956d73df9 8f2cf237606b4d348efe9f6209788d98 +#: 9488b85984f143a894becf1be38005e1 aebb2c59dcc6425382aa3ae10478686d +#: c08b6bb6532f407c892e65185d8f8a1b ef163b95fbd3427fa4e42de1bbe0777a +msgid "Return type" +msgstr "" + +#: ../../en/m5ui/table.rst:89 7e224098540d4d82a016d0027005d51e +msgid "|get_cell_value.png|" +msgstr "" + +#: ../../en/refs/m5ui.table.ref:4 e2e33afe8f254eac915e8b3038d530e8 +msgid "get_cell_value.png" +msgstr "" + +#: ../../en/m5ui/table.rst:99 1891eb7ba51744c088fc9f8a6a746f8f +msgid "Set the number of rows." +msgstr "" + +#: ../../en/m5ui/table.rst:106 1c590fab774249d4a47cc97337b60424 +msgid "|set_row_count.png|" +msgstr "" + +#: ../../en/refs/m5ui.table.ref:5 f3162911614a4da69889ba9613cd40ac +msgid "set_row_count.png" +msgstr "" + +#: ../../en/m5ui/table.rst:116 ec85a468353e44c9babaf079cb2800af +msgid "Set the number of columns." +msgstr "" + +#: ../../en/m5ui/table.rst:123 cfe9c187bff7411b93fdb969b0707925 +msgid "|set_column_count.png|" +msgstr "" + +#: ../../en/refs/m5ui.table.ref:6 d0f478d90ae64f28b7ac7b6d0af4ea4b +msgid "set_column_count.png" +msgstr "" + +#: ../../en/m5ui/table.rst:133 861b4f4ba67a4e14b5024498d0f96d4e +msgid "Get the number of rows." +msgstr "" + +#: ../../en/m5ui/table.rst:135 888294566d7d4bc884e6c195ec4f23ac +msgid "Number of row." +msgstr "" + +#: ../../en/m5ui/table.rst:140 29bac22238484e9c848116f54bf7b88e +msgid "|get_row_count.png|" +msgstr "" + +#: ../../en/refs/m5ui.table.ref:7 bfcbc5fa82254f0d9b0f5bc2599eb69a +msgid "get_row_count.png" +msgstr "" + +#: ../../en/m5ui/table.rst:150 9e86001b933042629eb5e45ab4ea6c24 +msgid "Get the number of columns." +msgstr "" + +#: ../../en/m5ui/table.rst:157 3cfda23e464c4347956d4b86d9854793 +msgid "|get_column_count.png|" +msgstr "" + +#: ../../en/refs/m5ui.table.ref:8 3a8c24fb774f4b9c938ec3247d83ddcb +msgid "get_column_count.png" +msgstr "" + +#: ../../en/m5ui/table.rst:167 9d4c6bd4137544c7bf325c719f97647e +msgid "Set the width of a column." +msgstr "" + +#: ../../en/m5ui/table.rst:169 ../../en/m5ui/table.rst:187 +#: ab197548bc7f47d787c961c10ec635a3 dd6758f6075f46e6bd4636b4291dc5d2 +msgid "Column index [0 .. LV_TABLE_COL_MAX - 1]." +msgstr "" + +#: ../../en/m5ui/table.rst:170 ../../en/m5ui/table.rst:188 +#: 1b0dfd80ce644746a5d0fffd45eb14da 5d7d7e76882541208c15119cad40628c +msgid "Column width." +msgstr "" + +#: ../../en/m5ui/table.rst:175 219727761f8e4d82bc3905d3c0309f59 +msgid "|set_column_width.png|" +msgstr "" + +#: ../../en/refs/m5ui.table.ref:9 0e1c6ab52c484db885910fc57081cc2f +msgid "set_column_width.png" +msgstr "" + +#: ../../en/m5ui/table.rst:185 bb1436c530ca4aa4b74078eaefb3572b +msgid "Get the width of a column." +msgstr "" + +#: ../../en/m5ui/table.rst:193 bc9f553d76914269abdf58fa128e6308 +msgid "|get_column_width.png|" +msgstr "" + +#: ../../en/refs/m5ui.table.ref:10 9b72c244167b4e2c9ae8ab7f08fe6af1 +msgid "get_column_width.png" +msgstr "" + +#: ../../en/m5ui/table.rst:203 8342172b09bf482c81e30a8394f5af9d +msgid "Set the position of the Table." +msgstr "" + +#: ../../en/m5ui/table.rst 9680a5fc62a546adb5619e652d43c540 +msgid "param int x" +msgstr "" + +#: ../../en/m5ui/table.rst:205 ../../en/m5ui/table.rst:223 +#: ../../en/m5ui/table.rst:257 52cd890b996943d78e9c53b0bd54767e +#: 93d2ef7b54cb4cb891ff7934aac34ab1 f8f5886fdd1745b5b432cb47fdfd85d2 +msgid "The x position of the Table." +msgstr "" + +#: ../../en/m5ui/table.rst d7a7b6cebd34431cb3eba7a9ff142ae5 +msgid "param int y" +msgstr "" + +#: ../../en/m5ui/table.rst:206 ../../en/m5ui/table.rst:240 +#: ../../en/m5ui/table.rst:274 5ea4bed923764c46890c7705a470d730 +#: da8387086ec145fbab3f1c2ff0a52d82 e76da59ee4344048b2c8ba4f9a0b33d2 +msgid "The y position of the Table." +msgstr "" + +#: ../../en/m5ui/table.rst b7dfa5becd0d4f94abb49648858951e9 +msgid "return" +msgstr "" + +#: ../../en/m5ui/table.rst:211 08f03019ec5d4258863864ea116b994a +msgid "|set_pos.png|" +msgstr "" + +#: ../../en/refs/m5ui.table.ref:11 af6d0d673342408d83154e47f6a8a9a7 +msgid "set_pos.png" +msgstr "" + +#: ../../en/m5ui/table.rst:221 996d244cacf7480797a43a1b6ed220bb +msgid "Set the x position of the Table." +msgstr "" + +#: ../../en/m5ui/table.rst:228 4ecf4f61a4e24d9c9bce6ddda4588663 +msgid "|set_x.png|" +msgstr "" + +#: ../../en/refs/m5ui.table.ref:12 5f89798640b84f27a3a4a7d77d1b1221 +msgid "set_x.png" +msgstr "" + +#: ../../en/m5ui/table.rst:238 c2fa313b6ee445adb744034a992a21b7 +msgid "Set the y position of the Table." +msgstr "" + +#: ../../en/m5ui/table.rst:245 c22f1eff60154e3894bbc0f38ff5717e +msgid "|set_y.png|" +msgstr "" + +#: ../../en/refs/m5ui.table.ref:13 e0a806a695114e548aba8e28f9baaffc +msgid "set_y.png" +msgstr "" + +#: ../../en/m5ui/table.rst:255 7fdd15d1641749a4af72e9ecf5bf3707 +msgid "Get the x position of the Table." +msgstr "" + +#: ../../en/m5ui/table.rst:262 d81fca4ed77d4d378212d3364e4123d0 +msgid "|get_x.png|" +msgstr "" + +#: ../../en/refs/m5ui.table.ref:14 7e853730efde4f8fa2de1b802ccca5b2 +msgid "get_x.png" +msgstr "" + +#: ../../en/m5ui/table.rst:272 ea8e835c3c0042ebad038f8ff9731fc0 +msgid "Get the y position of the Table." +msgstr "" + +#: ../../en/m5ui/table.rst:279 02920fe09be14692a547cdbececafea6 +msgid "|get_y.png|" +msgstr "" + +#: ../../en/refs/m5ui.table.ref:15 483c27e3b3b8495f9dcbb9f69139e6fb +msgid "get_y.png" +msgstr "" + +#: ../../en/m5ui/table.rst:289 8f74f1eab49c497cb3fd244f05513edc +msgid "Set the size of the Table." +msgstr "" + +#: ../../en/m5ui/table.rst:291 ../../en/m5ui/table.rst:309 +#: ../../en/m5ui/table.rst:326 f8196371b0824aee8a91050045498065 +msgid "The width of the Table." +msgstr "" + +#: ../../en/m5ui/table.rst:292 ../../en/m5ui/table.rst:343 +#: ../../en/m5ui/table.rst:360 57207ce30027452fbbe809f8e9c364db +msgid "The height of the Table." +msgstr "" + +#: ../../en/m5ui/table.rst:297 ee276f2f58874e3ebe9e32eef1aa0a31 +msgid "|set_size.png|" +msgstr "" + +#: ../../en/refs/m5ui.table.ref:16 0aa69a2ba7a445d7b5361a4921ad1080 +msgid "set_size.png" +msgstr "" + +#: ../../en/m5ui/table.rst:307 f8eb07fc9d584333a3685bc664d67f3f +msgid "Set the width of the Table." +msgstr "" + +#: ../../en/m5ui/table.rst:314 ca7fd165135d42caa3a670bf89bac9ac +msgid "|set_width.png|" +msgstr "" + +#: ../../en/refs/m5ui.table.ref:17 41e8ca8cd3ab4ecc8d7c9e14340dde0f +msgid "set_width.png" +msgstr "" + +#: ../../en/m5ui/table.rst:324 dab43bca977a479fb8f912d91ca9d044 +msgid "Get the width of the Table." +msgstr "" + +#: ../../en/m5ui/table.rst:331 eb542d6ce7564e7ead1bcfc86b80f8ad +msgid "|get_width.png|" +msgstr "" + +#: ../../en/refs/m5ui.table.ref:19 5be26bac7472410a8dd1936e4d8a581e +msgid "get_width.png" +msgstr "" + +#: ../../en/m5ui/table.rst:341 f9d55a23be9f41afbde4a405f7bf63f3 +msgid "Set the height of the Table." +msgstr "" + +#: ../../en/m5ui/table.rst:348 c49333269b214e5e883b32323babd758 +msgid "|set_height.png|" +msgstr "" + +#: ../../en/refs/m5ui.table.ref:18 7dbeca0b76ae412bbc267d2ef634ff69 +msgid "set_height.png" +msgstr "" + +#: ../../en/m5ui/table.rst:358 9b601b1dd00a4a11a55736b08463bc74 +msgid "Get the height of the Table." +msgstr "" + +#: ../../en/m5ui/table.rst:365 3320493d141348dd989366c0308511da +msgid "|get_height.png|" +msgstr "" + +#: ../../en/refs/m5ui.table.ref:20 4f08b4476421493d94c3ea11aa56ecaf +msgid "get_height.png" +msgstr "" + +#: ../../en/m5ui/table.rst:375 f0a985a42b3b4b11a0aea7195d05ff36 +msgid "Align the Table relative to another object." +msgstr "" + +#: ../../en/m5ui/table.rst:377 e453d640a32b43a19fee60efab6175f2 +msgid "The reference object (e.g. page0)." +msgstr "" + +#: ../../en/m5ui/table.rst:378 c8962fc69c3e459da003315b31f927c8 +msgid "Alignment option (see lv.ALIGN constants below)." +msgstr "" + +#: ../../en/m5ui/table.rst:379 b6fbcaebd1a84d5698b5aa7fae3f3418 +msgid "X offset after alignment." +msgstr "" + +#: ../../en/m5ui/table.rst:380 e00df0ba46f14605a1a804590267ab2f +msgid "Y offset after alignment." +msgstr "" + +#: ../../en/m5ui/table.rst:385 be7d39b5dce2459e9d76965a98ff36cd +msgid "|align_to.png|" +msgstr "" + +#: ../../en/refs/m5ui.table.ref:21 c600dd192d7b4a20a859ccf040f7229c +msgid "align_to.png" +msgstr "" + +#: ../../en/m5ui/table.rst:395 225fb725e9104e9fba16fd9575f8cb3d +msgid "Alignment options for positioning objects." +msgstr "" + +#: ../../en/m5ui/table.rst:397 30c212fc4b1543df9e76bf65fa76da23 +msgid "lv.ALIGN.DEFAULT" +msgstr "lv.ALIGN.DEFAULT" + +#: ../../en/m5ui/table.rst:398 2c1371adcdc0468eb7b7802b3d4b9da9 +msgid "lv.ALIGN.TOP_LEFT" +msgstr "lv.ALIGN.TOP_LEFT" + +#: ../../en/m5ui/table.rst:399 86c9a8417e414fe79f79583f5eda6ffe +msgid "lv.ALIGN.TOP_MID" +msgstr "lv.ALIGN.TOP_MID" + +#: ../../en/m5ui/table.rst:400 5e4836ac96824ef7b77023cc06de04c6 +msgid "lv.ALIGN.TOP_RIGHT" +msgstr "lv.ALIGN.TOP_RIGHT" + +#: ../../en/m5ui/table.rst:401 cb31695347744ec79783dd4f3f233473 +msgid "lv.ALIGN.BOTTOM_LEFT" +msgstr "lv.ALIGN.BOTTOM_LEFT" + +#: ../../en/m5ui/table.rst:402 1fde9619010d492d96c3aabf4643a174 +msgid "lv.ALIGN.BOTTOM_MID" +msgstr "lv.ALIGN.BOTTOM_MID" + +#: ../../en/m5ui/table.rst:403 cc7b32476148415b9d653604f339a51e +msgid "lv.ALIGN.BOTTOM_RIGHT" +msgstr "lv.ALIGN.BOTTOM_RIGHT" + +#: ../../en/m5ui/table.rst:404 c5ee92f705a1453a858d2b1dfbb5adc4 +msgid "lv.ALIGN.LEFT_MID" +msgstr "lv.ALIGN.LEFT_MID" + +#: ../../en/m5ui/table.rst:405 3e796c99656743d590ce2c357c76f00e +msgid "lv.ALIGN.RIGHT_MID" +msgstr "lv.ALIGN.RIGHT_MID" + +#: ../../en/m5ui/table.rst:406 5360d6673b2245a7874e183abf0200ab +msgid "lv.ALIGN.CENTER" +msgstr "lv.ALIGN.CENTER" + +#: ../../en/m5ui/table.rst:407 38a802cfcf684455a31aa034f1dc0c21 +msgid "lv.ALIGN.OUT_TOP_LEFT" +msgstr "lv.ALIGN.OUT_TOP_LEFT" + +#: ../../en/m5ui/table.rst:408 c9f532227a7f43b19dec9ff9fc4935d2 +msgid "lv.ALIGN.OUT_TOP_MID" +msgstr "lv.ALIGN.OUT_TOP_MID" + +#: ../../en/m5ui/table.rst:409 8dea72afa843400eacd3a12e30e71fb6 +msgid "lv.ALIGN.OUT_TOP_RIGHT" +msgstr "lv.ALIGN.OUT_TOP_RIGHT" + +#: ../../en/m5ui/table.rst:410 4b79cce8cb104806ab405bcd16a28faf +msgid "lv.ALIGN.OUT_BOTTOM_LEFT" +msgstr "lv.ALIGN.OUT_BOTTOM_LEFT" + +#: ../../en/m5ui/table.rst:411 db0dcbd46fc64e659ae41dfb849642a5 +msgid "lv.ALIGN.OUT_BOTTOM_MID" +msgstr "lv.ALIGN.OUT_BOTTOM_MID" + +#: ../../en/m5ui/table.rst:412 6c94198ff28d47e89f288db718c0f1fb +msgid "lv.ALIGN.OUT_BOTTOM_RIGHT" +msgstr "lv.ALIGN.OUT_BOTTOM_RIGHT" + +#: ../../en/m5ui/table.rst:413 0c1b600f51304c2b880059c291e9994c +msgid "lv.ALIGN.OUT_LEFT_TOP" +msgstr "lv.ALIGN.OUT_LEFT_TOP" + +#: ../../en/m5ui/table.rst:414 5755cb0f902141e4bf1ef13c60a4caae +msgid "lv.ALIGN.OUT_LEFT_MID" +msgstr "lv.ALIGN.OUT_LEFT_MID" + +#: ../../en/m5ui/table.rst:415 d0d1c80d7fec4b6f9df5760b922ee47d +msgid "lv.ALIGN.OUT_LEFT_BOTTOM" +msgstr "lv.ALIGN.OUT_LEFT_BOTTOM" + +#: ../../en/m5ui/table.rst:416 62bc0f8afa1b4633a5355edbe0f8778f +msgid "lv.ALIGN.OUT_RIGHT_TOP" +msgstr "lv.ALIGN.OUT_RIGHT_TOP" + +#: ../../en/m5ui/table.rst:417 0809386117f64ad69761ec72a28a8e07 +msgid "lv.ALIGN.OUT_RIGHT_MID" +msgstr "lv.ALIGN.OUT_RIGHT_MID" + +#: ../../en/m5ui/table.rst:418 42b62ccb1ba94c2783f8f7c72b6b7844 +msgid "lv.ALIGN.OUT_RIGHT_BOTTOM" +msgstr "lv.ALIGN.OUT_RIGHT_BOTTOM" + +#~ msgid "" +#~ "This example demonstrates how to create" +#~ " a table with student information " +#~ "using MicroPython. The code creates a" +#~ " 3x4 table (3 columns, 4 rows " +#~ "including header) that displays student " +#~ "data with names, ages, and scores. " +#~ "The table is centered on the page" +#~ " and uses a dictionary to store " +#~ "the student information." +#~ msgstr "" +#~ "此示例演示了如何创建一个包含学生信息的表格,包括姓名、年龄和分数。该表格显示三名学生的数据:Alice (18, " +#~ "95)、Bob (18, 80) 和 Carol (17, 86)。" + diff --git a/examples/m5ui/table/cores3_m5ui_table_example.m5f2 b/examples/m5ui/table/cores3_m5ui_table_example.m5f2 new file mode 100644 index 00000000..e8b600b4 --- /dev/null +++ b/examples/m5ui/table/cores3_m5ui_table_example.m5f2 @@ -0,0 +1 @@ +{"version":"V2.0","versionNumber":"V2.3.6","type":"cores3","components":[{"name":"page0","type":"lvgl_page","layer":0,"screenId":"builtin","screenName":"","id":"cXOxQjJ6coSJYv^C","createTime":1761181921388,"backgroundColor":"#ffffff","isLVGL":true,"isSelected":true},{"name":"table0","type":"lvgl_table","layer":1,"screenId":"builtin","screenName":"","id":"u0QY%6A&He*EYPdC","createTime":1761182122134,"x":10,"y":35,"width":300,"height":180,"row":3,"col":3,"pageId":"cXOxQjJ6coSJYv^C","isLVGL":true,"isSelected":false},{"name":"table","type":"lvgl_label","layer":2,"screenId":"builtin","screenName":"","id":"hgIwfw_JwY2o++7q","createTime":1761182128592,"x":35,"y":2,"color":"#0000ff","backgroundColor":"#000000","bg_opacity":0,"text":"M5UI Table Example","font":"lv.font_montserrat_24","pageId":"cXOxQjJ6coSJYv^C","isLVGL":true,"isSelected":false}],"resources":[{"hardware":["hardware_button","hardware_pin_button","imu","speaker","touch","als","mic","sdcard"]}],"units":[],"hats":[],"caps":[],"bases":[],"i2cs":[],"blockly":"iinforowktruepage0i3table00i85table03table04table0260table0CENTERpage0010table000nametable001agetable002scoreinfonamehello M5AliceBobCarolagehello M5181817scorehello M5958086row1knameinfotable00row0scorekrowADD1row1row1kageinfotable00row1scorekrowADD1row1row1kscoreinfotable00row2scorekrowADD1row1true","screen":[{"simulationName":"Built-in","type":"builtin","width":320,"height":240,"scale":0.77,"screenName":"","blockId":"","screenColorType":0,"rotation":1,"id":"builtin","createTime":1761181921387}],"logicWhenNum":0,"customList":[]} \ No newline at end of file diff --git a/examples/m5ui/table/cores3_m5ui_table_example.py b/examples/m5ui/table/cores3_m5ui_table_example.py new file mode 100644 index 00000000..fe425579 --- /dev/null +++ b/examples/m5ui/table/cores3_m5ui_table_example.py @@ -0,0 +1,83 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import os, sys, io +import M5 +from M5 import * +import m5ui +import lvgl as lv + + +page0 = None +table0 = None +table = None +i = None +info = None +row = None +k = None + + +def setup(): + global page0, table0, table, i, info, row, k + + M5.begin() + Widgets.setRotation(1) + m5ui.init() + page0 = m5ui.M5Page(bg_c=0xFFFFFF) + table0 = m5ui.M5Table(x=10, y=35, w=300, h=180, row_cnt=3, col_cnt=3, parent=page0) + table = m5ui.M5Label( + "M5UI Table Example", + x=35, + y=2, + text_c=0x0000FF, + bg_c=0x000000, + bg_opa=0, + font=lv.font_montserrat_24, + parent=page0, + ) + + page0.screen_load() + for i in range(3): + table0.set_column_width(i, 85) + + table0.set_column_count(3) + table0.set_row_count(4) + table0.set_width(260) + table0.align_to(page0, lv.ALIGN.CENTER, 0, 10) + table0.set_cell_value(0, 0, "name") + table0.set_cell_value(0, 1, "age") + table0.set_cell_value(0, 2, "score") + info = {"name": ["Alice", "Bob", "Carol"], "age": [18, 18, 17], "score": [95, 80, 86]} + row = 1 + for k in info["name"]: + table0.set_cell_value(row, 0, k) + row = row + 1 + row = 1 + for k in info["age"]: + table0.set_cell_value(row, 1, str(k)) + row = row + 1 + row = 1 + for k in info["score"]: + table0.set_cell_value(row, 2, str(k)) + row = row + 1 + + +def loop(): + global page0, table0, table, i, info, row, k + M5.update() + + +if __name__ == "__main__": + try: + setup() + while True: + loop() + except (Exception, KeyboardInterrupt) as e: + try: + m5ui.deinit() + from utility import print_error_msg + + print_error_msg(e) + except ImportError: + print("please update to latest firmware") diff --git a/m5stack/libs/m5ui/__init__.py b/m5stack/libs/m5ui/__init__.py index 8c3f009c..a6dba0b6 100644 --- a/m5stack/libs/m5ui/__init__.py +++ b/m5stack/libs/m5ui/__init__.py @@ -29,6 +29,7 @@ "M5Spinbox": "spinbox", "M5Spinner": "spinner", "M5Switch": "switch", + "M5Table": "table", "M5TabView": "tabview", "M5TextArea": "textarea", "M5Win": "win", diff --git a/m5stack/libs/m5ui/manifest.py b/m5stack/libs/m5ui/manifest.py index 8a68c2d9..47bc8a6b 100644 --- a/m5stack/libs/m5ui/manifest.py +++ b/m5stack/libs/m5ui/manifest.py @@ -31,6 +31,7 @@ "spinbox.py", "spinner.py", "switch.py", + "table.py", "tabview.py", "textarea.py", "win.py", diff --git a/m5stack/libs/m5ui/table.py b/m5stack/libs/m5ui/table.py new file mode 100644 index 00000000..2f9d59d5 --- /dev/null +++ b/m5stack/libs/m5ui/table.py @@ -0,0 +1,50 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +from .base import M5Base +import lvgl as lv + + +class M5Table(lv.table): + """Create a table object. + + :param int x: The x position of the table. + :param int y: The y position of the table. + :param int w: The width of the table. + :param int h: The height of the table. + :param int row_cnt: Number of rows. + :param int col_cnt: Number of columns. + :param lv.obj parent: The parent object to attach the table to. If not specified, the table will be attached to the default screen. + + UiFlow2 Code Block: + + None + + MicroPython Code Block: + + .. code-block:: python + + from m5ui import M5Table + import lvgl as lv + + m5ui.init() + table_0 = M5Table(x=30, y=20, w=200, h=150, row_cnt=2, col_cnt=2) + """ + + def __init__(self, x=30, y=20, w=200, h=150, row_cnt=2, col_cnt=2, parent=None): + if parent is None: + parent = lv.screen_active() + super().__init__(parent) + self.set_pos(x, y) + self.set_size(w, h) + self.set_row_count(row_cnt) + self.set_column_count(col_cnt) + + def __getattr__(self, name): + if hasattr(M5Base, name): + method = getattr(M5Base, name) + bound_method = lambda *args, **kwargs: method(self, *args, **kwargs) + setattr(self, name, bound_method) + return bound_method + raise AttributeError(f"'{self.__class__.__name__}' object has no attribute '{name}'") From 5ac459070d7692754a29c751a9e391192fe7f745 Mon Sep 17 00:00:00 2001 From: lbuque <1102390310@qq.com> Date: Thu, 9 Oct 2025 16:26:49 +0800 Subject: [PATCH 290/322] libs/m5ui/spinbox.py: Fix bugs related to size and state. Signed-off-by: lbuque <1102390310@qq.com> --- docs/en/m5ui/spinbox.rst | 87 ----------------------- m5stack/libs/m5ui/port.py | 3 +- m5stack/libs/m5ui/spinbox.py | 131 +++++++++++++++++++++++++++++++++-- tests/m5ui/test_spinbox.py | 12 ++++ 4 files changed, 140 insertions(+), 93 deletions(-) diff --git a/docs/en/m5ui/spinbox.rst b/docs/en/m5ui/spinbox.rst index 7efc617f..acdc4814 100644 --- a/docs/en/m5ui/spinbox.rst +++ b/docs/en/m5ui/spinbox.rst @@ -93,41 +93,6 @@ M5Spinbox spinbox_0.toggle_flag(lv.obj.FLAG.HIDDEN) - .. py:method:: set_state(state, value) - - Set the state of the spinbox. If ``value`` is True, the state is set; if False, the state is unset. - - :param int state: The state to set. - :param bool value: If True, the state is set; if False, the state is unset. - :return: None - - UiFlow2 Code Block: - - |set_state.png| - - MicroPython Code Block: - - .. code-block:: python - - spinbox_0.set_state(lv.STATE.DISABLED, True) - - .. py:method:: toggle_state(state) - - Toggle the state of the spinbox. If the state is set, it is unset; if not set, it is set. - - :param int state: The state to toggle. - :return: None - - UiFlow2 Code Block: - - |toggle_state.png| - - MicroPython Code Block: - - .. code-block:: python - - spinbox_0.toggle_state(lv.STATE.CHECKED) - .. py:method:: set_pos(x, y) Set the position of the spinbox. @@ -180,41 +145,6 @@ M5Spinbox spinbox_0.set_y(250) - .. py:method:: set_size(width, height) - - Set the size of the spinbox. - - :param int width: The width of the spinbox. - :param int height: The height of the spinbox. - :return: None - - UiFlow2 Code Block: - - |set_size.png| - - MicroPython Code Block: - - .. code-block:: python - - spinbox_0.set_size(150, 40) - - .. py:method:: set_width(width) - - Set the width of the spinbox. - - :param int width: The width of the spinbox. - :return: None - - UiFlow2 Code Block: - - |set_width.png| - - MicroPython Code Block: - - .. code-block:: python - - spinbox_0.set_width(180) - .. py:method:: get_width() Get the width of the spinbox. @@ -232,23 +162,6 @@ M5Spinbox width = spinbox_0.get_width() - .. py:method:: set_height(height) - - Set the height of the spinbox. - - :param int height: The height of the spinbox. - :return: None - - UiFlow2 Code Block: - - |set_height.png| - - MicroPython Code Block: - - .. code-block:: python - - spinbox_0.set_height(50) - .. py:method:: get_height() Get the height of the spinbox. diff --git a/m5stack/libs/m5ui/port.py b/m5stack/libs/m5ui/port.py index c7ce9e71..588364c4 100644 --- a/m5stack/libs/m5ui/port.py +++ b/m5stack/libs/m5ui/port.py @@ -5,7 +5,6 @@ import lvgl as lv import sys import lv_utils -import m5utils import micropython _event_loop_instance = None @@ -27,6 +26,8 @@ def __init__(self, freq=33, max_scheduled=2): self._initialized = True self.delay = 1000 // freq + import m5utils + self.timer = m5utils.Timer( 0, mode=m5utils.Timer.PERIODIC, period=self.delay, callback=self.timer_cb ) diff --git a/m5stack/libs/m5ui/spinbox.py b/m5stack/libs/m5ui/spinbox.py index 63c99d57..1bc4a5ac 100644 --- a/m5stack/libs/m5ui/spinbox.py +++ b/m5stack/libs/m5ui/spinbox.py @@ -45,7 +45,7 @@ def __init__( self.remove_style_all() self.set_flex_flow(lv.FLEX_FLOW.ROW) self.set_flex_align(lv.FLEX_ALIGN.SPACE_AROUND, lv.FLEX_ALIGN.CENTER, lv.FLEX_ALIGN.CENTER) - self.set_size(w, h) + super().set_size(w, h) self.set_style_pad_gap(5, 0) self.set_pos(x, y) self.set_style_text_font(font, lv.PART.MAIN | lv.STATE.DEFAULT) @@ -80,6 +80,124 @@ def __init__( self._btn_inc.add_event_cb(self._increment_event_cb, lv.EVENT.SHORT_CLICKED, None) self._btn_inc.add_event_cb(self._increment_event_cb, lv.EVENT.LONG_PRESSED_REPEAT, None) + def set_state(self, state: int, value: bool) -> None: + """Set the state of the spinbox. If ``value`` is True, the state is set; if False, the state is unset. + + :param int state: The state to set. + :param bool value: If True, the state is set; if False, the state is unset. + :return: None + + UiFlow2 Code Block: + + |set_state.png| + + MicroPython Code Block: + + .. code-block:: python + + spinbox_0.set_state(lv.STATE.DISABLED, True) + """ + if value: + self._spinbox.add_state(state) + self._btn_dec.add_state(state) + self._btn_inc.add_state(state) + else: + self._spinbox.remove_state(state) + self._btn_dec.remove_state(state) + self._btn_inc.remove_state(state) + + def toggle_state(self, state: int) -> None: + """Toggle the state of the spinbox. If the state is set, it is unset; if not set, it is set. + + :param int state: The state to toggle. + :return: None + + UiFlow2 Code Block: + + |toggle_state.png| + + MicroPython Code Block: + + .. code-block:: python + + spinbox_0.toggle_state(lv.STATE.CHECKED) + """ + if self._spinbox.has_state(state): + self._spinbox.remove_state(state) + self._btn_dec.remove_state(state) + self._btn_inc.remove_state(state) + else: + self._spinbox.add_state(state) + self._btn_dec.add_state(state) + self._btn_inc.add_state(state) + return + + def set_size(self, width: int, height: int) -> None: + """Set the size of the spinbox. + + :param int width: The width of the spinbox. + :param int height: The height of the spinbox. + :return: None + + UiFlow2 Code Block: + + |set_size.png| + + MicroPython Code Block: + + .. code-block:: python + + spinbox_0.set_size(150, 40) + """ + super().set_size(width, height) + self._btn_dec.set_size(height, height) + self._btn_inc.set_size(height, height) + self._spinbox.set_height(height) + self._spinbox.set_flex_grow(1) + self.update_layout() + + def set_width(self, width: int) -> None: + """Set the width of the spinbox. + + :param int width: The width of the spinbox. + :return: None + + UiFlow2 Code Block: + + |set_width.png| + + MicroPython Code Block: + + .. code-block:: python + + spinbox_0.set_width(180) + """ + super().set_width(width) + self._spinbox.set_flex_grow(1) + self.update_layout() + + def set_height(self, height: int) -> None: + """Set the height of the spinbox. + + :param int height: The height of the spinbox. + :return: None + + UiFlow2 Code Block: + + |set_height.png| + + MicroPython Code Block: + + .. code-block:: python + + spinbox_0.set_height(50) + """ + super().set_height(height) + self._btn_dec.set_size(height, height) + self._btn_inc.set_size(height, height) + self._spinbox.set_height(height) + self.update_layout() + def _check_value(self, value, min_value, max_value, digit_count, prec): if value < min_value or value > max_value: warnings.warn(f"Value must be between {min_value} and {max_value}.") @@ -216,12 +334,12 @@ def set_style_radius(self, radius: int, part: int) -> None: def _increment_event_cb(self, event_struct): event = event_struct.code - if event in (lv.EVENT.CLICKED, lv.EVENT.SHORT_CLICKED, lv.EVENT.LONG_PRESSED_REPEAT): + if event in (lv.EVENT.SHORT_CLICKED, lv.EVENT.LONG_PRESSED_REPEAT): self._spinbox.increment() def _decrement_event_cb(self, event_struct): event = event_struct.code - if event in (lv.EVENT.CLICKED, lv.EVENT.SHORT_CLICKED, lv.EVENT.LONG_PRESSED_REPEAT): + if event in (lv.EVENT.SHORT_CLICKED, lv.EVENT.LONG_PRESSED_REPEAT): self._spinbox.decrement() def set_digit_format(self, digit_count: int, sep_pos: int) -> None: @@ -325,6 +443,9 @@ def set_step(self, step: float | int) -> None: spinbox0.set_step(1) spinbox0.set_step(0.1) """ + if self._digit_count - self._sep_pos == 0: + warnings.warn(f"No decimal places, step {step} converted to {int(step * 10)}.") + step = int(step * 10) self._spinbox.set_step(self.value2raw(step, self._digit_count, self._sep_pos)) @staticmethod @@ -335,7 +456,7 @@ def value2raw(value: float | int, digit_count: int, sep_pos: int) -> int: :return: The converted integer value. :rtype: int """ - if sep_pos < 0 or sep_pos >= digit_count: + if sep_pos < 0 or sep_pos > digit_count: raise ValueError("sep_pos must be between 0 and digit_count.") dec_pos = digit_count - sep_pos @@ -351,7 +472,7 @@ def raw2value(raw: int, digit_count: int, sep_pos: int) -> float | int: :return: The converted float value. :rtype: float """ - if sep_pos < 0 or sep_pos >= digit_count: + if sep_pos < 0 or sep_pos > digit_count: raise ValueError("sep_pos must be between 0 and digit_count.") if sep_pos == 0: diff --git a/tests/m5ui/test_spinbox.py b/tests/m5ui/test_spinbox.py index 0ece76f5..ab0db4ca 100644 --- a/tests/m5ui/test_spinbox.py +++ b/tests/m5ui/test_spinbox.py @@ -69,6 +69,18 @@ def test_step(self): self.spinbox1.decrement() self.assertEqual(self.spinbox1.get_value(), 50) + def test_set_size(self): + self.spinbox1.set_size(150, 30) + self.assertEqual((self.spinbox1.get_width(), self.spinbox1.get_height()), (150, 30)) + + def test_set_width(self): + self.spinbox1.set_width(180) + self.assertEqual(self.spinbox1.get_width(), 180) + + def test_set_height(self): + self.spinbox1.set_height(35) + self.assertEqual(self.spinbox1.get_height(), 35) + if __name__ == "__main__": unittest.main() From c99eda50a2dd5d75f617b5b7b2a8d8b56163dc05 Mon Sep 17 00:00:00 2001 From: lbuque <1102390310@qq.com> Date: Mon, 13 Oct 2025 16:39:33 +0800 Subject: [PATCH 291/322] libs/m5ui: Wait for LVGL tasks on deinit. Signed-off-by: lbuque <1102390310@qq.com> --- m5stack/libs/m5ui/port.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/m5stack/libs/m5ui/port.py b/m5stack/libs/m5ui/port.py index 588364c4..22343bcd 100644 --- a/m5stack/libs/m5ui/port.py +++ b/m5stack/libs/m5ui/port.py @@ -6,6 +6,7 @@ import sys import lv_utils import micropython +import time _event_loop_instance = None @@ -48,6 +49,8 @@ def task_handler(self, _): def deinit(self): if hasattr(self, "timer"): self.timer.deinit() + while self.scheduled > 0: + time.sleep_ms(20) event_loop._initialized = False event_loop._instance = None From d5f2032fb7c53d0a816922a19f9e64e5f515656a Mon Sep 17 00:00:00 2001 From: lbuque <1102390310@qq.com> Date: Mon, 13 Oct 2025 16:42:38 +0800 Subject: [PATCH 292/322] libs/driver/fpc1020a: Remove firmware version restriction. Signed-off-by: lbuque <1102390310@qq.com> --- m5stack/libs/driver/fpc1020a/fpc1020a/api.py | 24 ++++++++------------ 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/m5stack/libs/driver/fpc1020a/fpc1020a/api.py b/m5stack/libs/driver/fpc1020a/fpc1020a/api.py index b2b50906..ae28610c 100644 --- a/m5stack/libs/driver/fpc1020a/fpc1020a/api.py +++ b/m5stack/libs/driver/fpc1020a/fpc1020a/api.py @@ -2,24 +2,18 @@ # # SPDX-License-Identifier: MIT -from machine import UART +import machine from micropython import const - -try: - import struct -except ImportError: - import ustruct as struct - -try: - from typing import Literal, Union -except ImportError: - pass - +import struct +import sys import time import binascii - +import warnings from . import types as t +if sys.platform != "esp32": + from typing import Literal, Union + class CommandId: SLEEP = const(0x2C) @@ -107,7 +101,7 @@ class FPC1020A: REPEAT_ALLOWED = 0x00 NO_REPETITION = 0x01 - def __init__(self, uart: UART, verbose=False) -> None: + def __init__(self, uart: machine.UART, verbose=False) -> None: self._uart = uart self._verbose = verbose self._add_mode = self.NO_REPETITION @@ -115,7 +109,7 @@ def __init__(self, uart: UART, verbose=False) -> None: while self._uart.any(): self._uart.read(self._uart.any()) if self.get_version() not in ("B1.10.00", "B1.07.00"): - raise RuntimeError("FPC1020A not found") + warnings.warn("Version mismatch or no device found") def sleep(self) -> bool: """After calling this method successfully, FPC1020A will not be able to From fa25735220527815cf1b7404565d75be4086ae0b Mon Sep 17 00:00:00 2001 From: lbuque <1102390310@qq.com> Date: Fri, 24 Oct 2025 17:16:10 +0800 Subject: [PATCH 293/322] version.text: Update to V2.3.7. Signed-off-by: lbuque <1102390310@qq.com> --- m5stack/version.txt | 2 +- third-party/version.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/m5stack/version.txt b/m5stack/version.txt index 4fe5f1b2..19814ff5 100644 --- a/m5stack/version.txt +++ b/m5stack/version.txt @@ -1 +1 @@ -V2.3.6 \ No newline at end of file +V2.3.7 \ No newline at end of file diff --git a/third-party/version.txt b/third-party/version.txt index 4fe5f1b2..19814ff5 100644 --- a/third-party/version.txt +++ b/third-party/version.txt @@ -1 +1 @@ -V2.3.6 \ No newline at end of file +V2.3.7 \ No newline at end of file From 2ab954f63348febd95d529202768661b7040dce1 Mon Sep 17 00:00:00 2001 From: lbuque <1102390310@qq.com> Date: Fri, 24 Oct 2025 19:14:21 +0800 Subject: [PATCH 294/322] .gitlab-ci.yml: Fix release_job error. Signed-off-by: lbuque <1102390310@qq.com> --- .gitlab-ci.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 40786883..2d567417 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -77,8 +77,11 @@ release_job: - echo "Releasing the M5Burn..." - sudo apt install pipx -y - pipx install uv + - pipx ensurepath + - export PATH="$PATH:$HOME/.local/bin" - uv venv - source .venv/bin/activate + - uv pip install requests - python ./tools/release.py only: refs: From c2edfbcae5fe520c99e06dd1ecf12cf580afb27e Mon Sep 17 00:00:00 2001 From: tinyu Date: Fri, 24 Oct 2025 16:37:34 +0800 Subject: [PATCH 295/322] libs/unit: Add M5.Power method support in M5Unified cmodules. Signed-off-by: tinyu --- m5stack/Makefile | 3 +- m5stack/cmodules/m5unified/m5unified_power.c | 22 +++++--- m5stack/components/M5Unified/mpy_m5power.cpp | 36 ++++++++++++- m5stack/patches/2006-Support-LTR553.patch | 51 +++++++++--------- .../2008-Only-use-old-rmt-driver.patch | 52 +++++++++++++++++++ 5 files changed, 131 insertions(+), 33 deletions(-) create mode 100644 m5stack/patches/2008-Only-use-old-rmt-driver.patch diff --git a/m5stack/Makefile b/m5stack/Makefile index 3afe665f..cc1c1a03 100644 --- a/m5stack/Makefile +++ b/m5stack/Makefile @@ -324,7 +324,8 @@ IDF_PATH_PATCH_SERIES = \ M5UNIFIED_PATCH_SERIES = \ 2006-Support-LTR553.patch \ - 2007-Support-UnitC6L.patch + 2007-Support-UnitC6L.patch \ + 2008-Only-use-old-rmt-driver.patch ADF_PATCH_SERIES = \ 3002-Modify-i2s_stream_idf5.patch diff --git a/m5stack/cmodules/m5unified/m5unified_power.c b/m5stack/cmodules/m5unified/m5unified_power.c index f50cc929..260d2276 100644 --- a/m5stack/cmodules/m5unified/m5unified_power.c +++ b/m5stack/cmodules/m5unified/m5unified_power.c @@ -10,13 +10,15 @@ // power port mask static const mp_rom_map_elem_t power_port_masks_table[] = { /* *FORMAT-OFF* */ - { MP_ROM_QSTR(MP_QSTR_A), MP_ROM_INT(0b00000001) }, - { MP_ROM_QSTR(MP_QSTR_B1), MP_ROM_INT(0b00000010) }, - { MP_ROM_QSTR(MP_QSTR_B2), MP_ROM_INT(0b00000100) }, - { MP_ROM_QSTR(MP_QSTR_C1), MP_ROM_INT(0b00001000) }, - { MP_ROM_QSTR(MP_QSTR_C2), MP_ROM_INT(0b00010000) }, - { MP_ROM_QSTR(MP_QSTR_USB), MP_ROM_INT(0b00100000) }, - { MP_ROM_QSTR(MP_QSTR_MAIN), MP_ROM_INT(0b10000000) }, + { MP_ROM_QSTR(MP_QSTR_A), MP_ROM_INT(1 << 0 ) }, + { MP_ROM_QSTR(MP_QSTR_B1), MP_ROM_INT(1 << 1 ) }, + { MP_ROM_QSTR(MP_QSTR_B2), MP_ROM_INT(1 << 2 ) }, + { MP_ROM_QSTR(MP_QSTR_C1), MP_ROM_INT(1 << 3 ) }, + { MP_ROM_QSTR(MP_QSTR_C2), MP_ROM_INT(1 << 4 ) }, + { MP_ROM_QSTR(MP_QSTR_USB), MP_ROM_INT(1 << 5 ) }, + { MP_ROM_QSTR(MP_QSTR_PWR485), MP_ROM_INT(1 << 6 ) }, + { MP_ROM_QSTR(MP_QSTR_PWRCAN), MP_ROM_INT(1 << 7 ) }, + { MP_ROM_QSTR(MP_QSTR_MAIN), MP_ROM_INT(1 << 15) }, /* *FORMAT-ON* */ }; static MP_DEFINE_CONST_DICT(power_port_masks, power_port_masks_table); @@ -38,6 +40,9 @@ const mp_obj_type_t mp_power_port_mask_enum = { MAKE_METHOD_KW(power, setExtOutput, 1); MAKE_METHOD_0(power, getExtOutput); +MAKE_METHOD_KW(power, setExtPortBusConfig, 4); +MAKE_METHOD_1(power, getExtVoltage); +MAKE_METHOD_1(power, getExtCurrent); MAKE_METHOD_KW(power, setUsbOutput, 1); MAKE_METHOD_0(power, getUsbOutput); MAKE_METHOD_KW(power, setLed, 1); @@ -63,6 +68,9 @@ static const mp_rom_map_elem_t power_member_table[] = { // control functions MAKE_TABLE(power, setExtOutput), MAKE_TABLE(power, getExtOutput), + MAKE_TABLE(power, setExtPortBusConfig), + MAKE_TABLE(power, getExtVoltage), + MAKE_TABLE(power, getExtCurrent), MAKE_TABLE(power, setUsbOutput), MAKE_TABLE(power, getUsbOutput), MAKE_TABLE(power, setLed), diff --git a/m5stack/components/M5Unified/mpy_m5power.cpp b/m5stack/components/M5Unified/mpy_m5power.cpp index 050ee8cb..c0c81127 100644 --- a/m5stack/components/M5Unified/mpy_m5power.cpp +++ b/m5stack/components/M5Unified/mpy_m5power.cpp @@ -4,7 +4,8 @@ * SPDX-License-Identifier: MIT */ -#include +// #include +#include extern "C" { @@ -51,6 +52,39 @@ namespace m5 return mp_obj_new_bool(getPower(self)->getExtOutput()); } + mp_obj_t power_setExtPortBusConfig(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum {ARG_voltage, ARG_current_limit, ARG_enable, ARG_direction}; + const mp_arg_t allowed_args[] = { + /* *FORMAT-OFF* */ + { MP_QSTR_voltage, MP_ARG_INT | MP_ARG_REQUIRED, {.u_int = 3000} }, + { MP_QSTR_current_limit, MP_ARG_INT | MP_ARG_REQUIRED, {.u_int = 232} }, + { MP_QSTR_enable, MP_ARG_BOOL | MP_ARG_REQUIRED, {.u_bool = true} }, + { MP_QSTR_direction, MP_ARG_INT, {.u_int = 0} }, + /* *FORMAT-ON* */ + }; + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + // The first parameter is the Power object, parse from second parameter. + mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + ext_port_bus_t cfg; + cfg.voltage = args[ARG_voltage].u_int; + cfg.currentLimit = args[ARG_current_limit].u_int; + cfg.enable = args[ARG_enable].u_bool; + cfg.direction = args[ARG_direction].u_int; + + getPower(pos_args[0])->setExtPortBusConfig(cfg); + + return mp_const_none; + } + + mp_obj_t power_getExtVoltage(mp_obj_t self, mp_obj_t ext_port) { + return mp_obj_new_int(getPower(self)->getExtVoltage((ext_port_mask_t)mp_obj_get_int(ext_port))); + } + + mp_obj_t power_getExtCurrent(mp_obj_t self, mp_obj_t ext_port) { + return mp_obj_new_int(getPower(self)->getExtCurrent((ext_port_mask_t)mp_obj_get_int(ext_port))); + } + mp_obj_t power_setUsbOutput(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { enum {ARG_enable}; const mp_arg_t allowed_args[] = { diff --git a/m5stack/patches/2006-Support-LTR553.patch b/m5stack/patches/2006-Support-LTR553.patch index 28a97d75..e40dd3ef 100644 --- a/m5stack/patches/2006-Support-LTR553.patch +++ b/m5stack/patches/2006-Support-LTR553.patch @@ -1,8 +1,8 @@ -Index: M5Unified/src/M5Unified.cpp -=================================================================== ---- M5Unified.orig/src/M5Unified.cpp -+++ M5Unified/src/M5Unified.cpp -@@ -1408,6 +1408,7 @@ static constexpr const uint8_t _pin_tabl +diff --git a/src/M5Unified.cpp b/src/M5Unified.cpp +index 8f2132a..9e175f3 100644 +--- a/src/M5Unified.cpp ++++ b/src/M5Unified.cpp +@@ -1672,6 +1672,7 @@ static constexpr const uint8_t _pin_table_mbus[][31] = { #elif defined (CONFIG_IDF_TARGET_ESP32S3) case board_t::board_M5StackCoreS3: case board_t::board_M5StackCoreS3SE: @@ -10,7 +10,7 @@ Index: M5Unified/src/M5Unified.cpp if (cfg.internal_mic) { mic_cfg.magnification = 2; -@@ -1914,6 +1915,9 @@ static constexpr const uint8_t _pin_tabl +@@ -2203,6 +2204,9 @@ static constexpr const uint8_t _pin_table_mbus[][31] = { { port_a_used = M5.Imu.begin(&M5.Ex_I2C) || port_a_used; } @@ -20,19 +20,19 @@ Index: M5Unified/src/M5Unified.cpp return port_a_used; } -Index: M5Unified/src/M5Unified.hpp -=================================================================== ---- M5Unified.orig/src/M5Unified.hpp -+++ M5Unified/src/M5Unified.hpp -@@ -62,6 +62,7 @@ namespace m5 - #include "utility/Touch_Class.hpp" +diff --git a/src/M5Unified.hpp b/src/M5Unified.hpp +index 1e19b4d..5651b85 100644 +--- a/src/M5Unified.hpp ++++ b/src/M5Unified.hpp +@@ -63,6 +63,7 @@ namespace m5 #include "utility/Log_Class.hpp" + #include "utility/LED_Class.hpp" #include "utility/IMU_Class.hpp" +#include "utility/LTR553_Class.hpp" #include "utility/IOExpander_Base.hpp" #include -@@ -138,6 +139,9 @@ namespace m5 +@@ -144,6 +145,9 @@ namespace m5 /// use the speaker. bool internal_spk = true; @@ -42,18 +42,19 @@ Index: M5Unified/src/M5Unified.hpp /// use Unit Accel & Gyro. bool external_imu = false; -@@ -214,6 +218,7 @@ namespace m5 +@@ -220,6 +224,7 @@ namespace m5 Power_Class Power; - RTC8563_Class Rtc; + RTC_Class Rtc; Touch_Class Touch; + LTR553_Class Als; - - /* - /// List of available buttons: -Index: M5Unified/src/utility/LTR553_Class.cpp -=================================================================== + Speaker_Class Speaker; + Mic_Class Mic; + LED_Class Led; +diff --git a/src/utility/LTR553_Class.cpp b/src/utility/LTR553_Class.cpp +new file mode 100644 +index 0000000..38f8ce8 --- /dev/null -+++ M5Unified/src/utility/LTR553_Class.cpp ++++ b/src/utility/LTR553_Class.cpp @@ -0,0 +1,135 @@ +#include "LTR553_Class.hpp" + @@ -190,10 +191,12 @@ Index: M5Unified/src/utility/LTR553_Class.cpp + } + +} -Index: M5Unified/src/utility/LTR553_Class.hpp -=================================================================== +\ No newline at end of file +diff --git a/src/utility/LTR553_Class.hpp b/src/utility/LTR553_Class.hpp +new file mode 100644 +index 0000000..f1e57bc --- /dev/null -+++ M5Unified/src/utility/LTR553_Class.hpp ++++ b/src/utility/LTR553_Class.hpp @@ -0,0 +1,122 @@ +#ifndef __M5_LTR553_CLASS_H__ +#define __M5_LTR553_CLASS_H__ diff --git a/m5stack/patches/2008-Only-use-old-rmt-driver.patch b/m5stack/patches/2008-Only-use-old-rmt-driver.patch new file mode 100644 index 00000000..2530b031 --- /dev/null +++ b/m5stack/patches/2008-Only-use-old-rmt-driver.patch @@ -0,0 +1,52 @@ +diff --git a/src/utility/led/LED_Strip_Class.cpp b/src/utility/led/LED_Strip_Class.cpp +index 7ea1cd1..47691d3 100644 +--- a/src/utility/led/LED_Strip_Class.cpp ++++ b/src/utility/led/LED_Strip_Class.cpp +@@ -126,9 +126,11 @@ namespace m5 + if (_config.pin_data < 0) { + return false; + } ++ #if M5UNIFIED_RMT_VERSION == 2 + if (_led_encoder && _rmt_ch_handle) { + return true; + } ++ #endif + + #if M5UNIFIED_RMT_VERSION == 2 + rmt_tx_channel_config_t rmt_tx; +diff --git a/src/utility/led/LED_Strip_Class.hpp b/src/utility/led/LED_Strip_Class.hpp +index a41e2de..c0b1e07 100644 +--- a/src/utility/led/LED_Strip_Class.hpp ++++ b/src/utility/led/LED_Strip_Class.hpp +@@ -12,12 +12,13 @@ + #if __has_include () + #include + #include ++ #define M5UNIFIED_RMT_VERSION 1 + // RMT +- #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0) +- #define M5UNIFIED_RMT_VERSION 2 +- #else +- #define M5UNIFIED_RMT_VERSION 1 +- #endif ++// #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0) ++// #define M5UNIFIED_RMT_VERSION 2 ++// #else ++// #define M5UNIFIED_RMT_VERSION 1 ++// #endif + #endif + + #if M5UNIFIED_RMT_VERSION == 2 +diff --git a/src/utility/rtc/RTC_PowerHub_Class.cpp b/src/utility/rtc/RTC_PowerHub_Class.cpp +index bf2fae6..5729f5c 100644 +--- a/src/utility/rtc/RTC_PowerHub_Class.cpp ++++ b/src/utility/rtc/RTC_PowerHub_Class.cpp +@@ -87,7 +87,7 @@ namespace m5 + int RTC_PowerHub_Class::setAlarmIRQ(const rtc_date_t *date, const rtc_time_t *time) + { + +- std::uint8_t buf[3]; ++ std::uint8_t buf[3] = { 0 }; + bool irq_enable = false; + + if (time) { From 83c25b6a8354cbc8d7995055cc9133a17ed38842 Mon Sep 17 00:00:00 2001 From: tinyu Date: Fri, 24 Oct 2025 16:37:57 +0800 Subject: [PATCH 296/322] cmodules: Add support for M5.Led class in M5Unified. Signed-off-by: tinyu --- m5stack/cmodules/m5unified/m5unified.c | 1 + m5stack/cmodules/m5unified/m5unified.cmake | 1 + m5stack/cmodules/m5unified/m5unified.h | 3 +- m5stack/cmodules/m5unified/m5unified_led.c | 40 ++++++++++++ m5stack/components/M5Unified/CMakeLists.txt | 2 + m5stack/components/M5Unified/mpy_m5led.cpp | 65 +++++++++++++++++++ m5stack/components/M5Unified/mpy_m5led.h | 18 +++++ .../components/M5Unified/mpy_m5unified.cpp | 1 + m5stack/components/M5Unified/mpy_m5unified.h | 2 + 9 files changed, 132 insertions(+), 1 deletion(-) create mode 100644 m5stack/cmodules/m5unified/m5unified_led.c create mode 100644 m5stack/components/M5Unified/mpy_m5led.cpp create mode 100644 m5stack/components/M5Unified/mpy_m5led.h diff --git a/m5stack/cmodules/m5unified/m5unified.c b/m5stack/cmodules/m5unified/m5unified.c index 151e04de..b870a176 100644 --- a/m5stack/cmodules/m5unified/m5unified.c +++ b/m5stack/cmodules/m5unified/m5unified.c @@ -109,6 +109,7 @@ static const mp_rom_map_elem_t mp_module_m5_globals_table[] = { { MP_ROM_QSTR(MP_QSTR_Speaker), MP_OBJ_FROM_PTR(&m5_speaker) }, { MP_ROM_QSTR(MP_QSTR_Power), MP_OBJ_FROM_PTR(&m5_power) }, { MP_ROM_QSTR(MP_QSTR_Imu), MP_OBJ_FROM_PTR(&m5_imu) }, + { MP_ROM_QSTR(MP_QSTR_Led), MP_OBJ_FROM_PTR(&m5_led) }, { MP_ROM_QSTR(MP_QSTR_Als), MP_OBJ_FROM_PTR(&m5_als) }, { MP_ROM_QSTR(MP_QSTR_Mic), MP_OBJ_FROM_PTR(&m5_mic) }, { MP_ROM_QSTR(MP_QSTR_Widgets), MP_OBJ_FROM_PTR(&m5_widgets) }, diff --git a/m5stack/cmodules/m5unified/m5unified.cmake b/m5stack/cmodules/m5unified/m5unified.cmake index 55f1a6b0..49a0ef40 100644 --- a/m5stack/cmodules/m5unified/m5unified.cmake +++ b/m5stack/cmodules/m5unified/m5unified.cmake @@ -9,6 +9,7 @@ target_sources(usermod_M5UNIFIED INTERFACE ${CMAKE_CURRENT_LIST_DIR}/m5unified_button.c ${CMAKE_CURRENT_LIST_DIR}/m5unified_gfx.c ${CMAKE_CURRENT_LIST_DIR}/m5unified_imu.c + ${CMAKE_CURRENT_LIST_DIR}/m5unified_led.c ${CMAKE_CURRENT_LIST_DIR}/m5unified_lvgl.c ${CMAKE_CURRENT_LIST_DIR}/m5unified_mic.c ${CMAKE_CURRENT_LIST_DIR}/m5unified_power.c diff --git a/m5stack/cmodules/m5unified/m5unified.h b/m5stack/cmodules/m5unified/m5unified.h index 604d0934..373aa451 100644 --- a/m5stack/cmodules/m5unified/m5unified.h +++ b/m5stack/cmodules/m5unified/m5unified.h @@ -21,13 +21,14 @@ /* *FORMAT-ON* */ #define MAKE_TABLE(prefix, func) \ - { MP_ROM_QSTR(MP_QSTR_##func), MP_ROM_PTR(&prefix##_##func##_obj) } + { MP_ROM_QSTR(MP_QSTR_##func), MP_ROM_PTR(&prefix##_##func##_obj) } extern const mp_obj_type_t mp_fonts_type; extern const mp_obj_type_t mp_color_type; extern const mp_obj_type_t mp_btn_type; extern const mp_obj_type_t mp_imu_type; +extern const mp_obj_type_t mp_led_type; extern const mp_obj_type_t mp_spk_type; extern const mp_obj_type_t mp_power_type; extern const mp_obj_type_t mp_gfxcanvas_type; diff --git a/m5stack/cmodules/m5unified/m5unified_led.c b/m5stack/cmodules/m5unified/m5unified_led.c new file mode 100644 index 00000000..6708ba64 --- /dev/null +++ b/m5stack/cmodules/m5unified/m5unified_led.c @@ -0,0 +1,40 @@ +/* +* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD +* +* SPDX-License-Identifier: MIT +*/ + +#include "m5unified.h" + +// -------- Led wrapper +MAKE_METHOD_0(led, display); +MAKE_METHOD_0(led, getCount); +MAKE_METHOD_KW(led, setBrightness, 1); +MAKE_METHOD_V(led, setAllColor, 1, 3); +MAKE_METHOD_V(led, setColor, 2, 4); + +static const mp_rom_map_elem_t led_member_table[] = { + // control functions + MAKE_TABLE(led, display), + MAKE_TABLE(led, getCount), + MAKE_TABLE(led, setBrightness), + MAKE_TABLE(led, setAllColor), + MAKE_TABLE(led, setColor), +}; + +static MP_DEFINE_CONST_DICT(led_member, led_member_table); + +#ifdef MP_OBJ_TYPE_GET_SLOT +MP_DEFINE_CONST_OBJ_TYPE( + mp_led_type, + MP_QSTR_Led, + MP_TYPE_FLAG_NONE, + locals_dict, (mp_obj_dict_t *)&led_member + ); +#else +const mp_obj_type_t mp_led_type = { + .base = { &mp_type_type }, + .name = MP_QSTR_Led, + .locals_dict = (mp_obj_dict_t *)&led_member, +}; +#endif diff --git a/m5stack/components/M5Unified/CMakeLists.txt b/m5stack/components/M5Unified/CMakeLists.txt index 6841ffb2..4f37d21f 100644 --- a/m5stack/components/M5Unified/CMakeLists.txt +++ b/m5stack/components/M5Unified/CMakeLists.txt @@ -24,7 +24,9 @@ file(GLOB SRCS M5Unified/src/*.cpp M5Unified/src/utility/*.cpp M5Unified/src/utility/imu/*.cpp + M5Unified/src/utility/led/*.cpp M5Unified/src/utility/power/*.cpp + M5Unified/src/utility/rtc/*.cpp *.cpp ) diff --git a/m5stack/components/M5Unified/mpy_m5led.cpp b/m5stack/components/M5Unified/mpy_m5led.cpp new file mode 100644 index 00000000..49421d42 --- /dev/null +++ b/m5stack/components/M5Unified/mpy_m5led.cpp @@ -0,0 +1,65 @@ +/* +* SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +* +* SPDX-License-Identifier: MIT +*/ + +// #include +#include + +extern "C" +{ +#include +#include "mpy_m5led.h" +#include "uiflow_utility.h" + + +namespace m5 +{ + static inline LED_Class *getLED(const mp_obj_t& self) { + return (LED_Class *)(((led_obj_t *)MP_OBJ_TO_PTR(self))->led); + } + + mp_obj_t led_display(mp_obj_t self) { + getLED(self)->display(); + return mp_const_none; + } + + mp_obj_t led_getCount(mp_obj_t self) { + return mp_obj_new_int(getLED(self)->getCount()); + } + + mp_obj_t led_setBrightness(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum {ARG_br}; + const mp_arg_t allowed_args[] = { + /* *FORMAT-OFF* */ + { MP_QSTR_br, MP_ARG_INT | MP_ARG_REQUIRED, {.u_int = 0} }, + /* *FORMAT-ON* */ + }; + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + // The first parameter is the Power object, parse from second parameter. + mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + getLED(pos_args[0])->setBrightness(args[ARG_br].u_int); + return mp_const_none; + } + + mp_obj_t led_setAllColor(size_t n_args, const mp_obj_t *args) { + if (n_args == 2) { + getLED(args[0])->setAllColor((uint32_t)mp_obj_get_int(args[1])); + } else if (n_args == 4) { + getLED(args[0])->setAllColor(mp_obj_get_int(args[1]), mp_obj_get_int(args[2]), mp_obj_get_int(args[3])); + } + return mp_const_none; + } + + mp_obj_t led_setColor(size_t n_args, const mp_obj_t *args) { + if (n_args == 3) { + getLED(args[0])->setColor(mp_obj_get_int(args[1]), (uint32_t)mp_obj_get_int(args[2])); + } else if (n_args == 5) { + getLED(args[0])->setColor(mp_obj_get_int(args[1]), mp_obj_get_int(args[2]), mp_obj_get_int(args[3]), mp_obj_get_int(args[4])); + } + return mp_const_none; + } +} +} diff --git a/m5stack/components/M5Unified/mpy_m5led.h b/m5stack/components/M5Unified/mpy_m5led.h new file mode 100644 index 00000000..9d911559 --- /dev/null +++ b/m5stack/components/M5Unified/mpy_m5led.h @@ -0,0 +1,18 @@ +/* +* SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +* +* SPDX-License-Identifier: MIT +*/ + +#pragma once + +#include +#include +#include + +typedef struct _led_obj_t { + mp_obj_base_t base; + void *led; +} led_obj_t; + +extern const mp_obj_type_t mp_led_type; diff --git a/m5stack/components/M5Unified/mpy_m5unified.cpp b/m5stack/components/M5Unified/mpy_m5unified.cpp index ee4e92b3..8fd7066e 100644 --- a/m5stack/components/M5Unified/mpy_m5unified.cpp +++ b/m5stack/components/M5Unified/mpy_m5unified.cpp @@ -57,6 +57,7 @@ mp_obj_t m5_getDisplay(mp_obj_t index); const spk_obj_t m5_speaker = {&mp_spk_type, &(M5.Speaker)}; const pwr_obj_t m5_power = {&mp_power_type, &(M5.Power) }; const pwr_obj_t m5_imu = {&mp_imu_type, &(M5.Imu) }; +const pwr_obj_t m5_led = {&mp_led_type, &(M5.Led) }; als_obj_t m5_als = {&mp_als_type, &(M5.Als) }; btn_obj_t m5_btnA = {&mp_btn_type, &(M5.BtnA), {0}}; btn_obj_t m5_btnB = {&mp_btn_type, &(M5.BtnB), {0}}; diff --git a/m5stack/components/M5Unified/mpy_m5unified.h b/m5stack/components/M5Unified/mpy_m5unified.h index 4350ce00..de57b101 100644 --- a/m5stack/components/M5Unified/mpy_m5unified.h +++ b/m5stack/components/M5Unified/mpy_m5unified.h @@ -14,6 +14,7 @@ #include "mpy_m5btn.h" #include "mpy_m5gfx.h" #include "mpy_m5imu.h" +#include "mpy_m5led.h" #include "mpy_m5spk.h" #include "mpy_m5power.h" #include "mpy_m5widgets.h" @@ -35,6 +36,7 @@ extern mp_obj_t m5_getDisplayCount(void); extern const spk_obj_t m5_speaker; extern const pwr_obj_t m5_power; extern const pwr_obj_t m5_imu; +extern const pwr_obj_t m5_led; extern btn_obj_t m5_btnA; extern btn_obj_t m5_btnB; extern btn_obj_t m5_btnC; From 32a93203ef510f26972c6ea7648754d71816e43b Mon Sep 17 00:00:00 2001 From: tinyu Date: Mon, 27 Oct 2025 14:17:26 +0800 Subject: [PATCH 297/322] boards: Add PowerHub support. Signed-off-by: tinyu --- m5stack/Makefile | 6 +- m5stack/boards/M5STACK_PowerHub/board.json | 18 +++++ m5stack/boards/M5STACK_PowerHub/manifest.py | 6 ++ .../M5STACK_PowerHub/mpconfigboard.cmake | 41 ++++++++++ .../boards/M5STACK_PowerHub/mpconfigboard.h | 21 +++++ .../boards/M5STACK_PowerHub/sdkconfig.board | 21 +++++ m5stack/cmodules/m5unified/m5unified.c | 1 + m5stack/libs/hardware/__init__.py | 1 + m5stack/libs/hardware/can.py | 3 + m5stack/modules/startup/__init__.py | 5 ++ m5stack/modules/startup/manifest_powerhub.py | 13 +++ m5stack/modules/startup/powerhub.py | 79 +++++++++++++++++++ tools/ci.sh | 3 + 13 files changed, 216 insertions(+), 2 deletions(-) create mode 100644 m5stack/boards/M5STACK_PowerHub/board.json create mode 100644 m5stack/boards/M5STACK_PowerHub/manifest.py create mode 100644 m5stack/boards/M5STACK_PowerHub/mpconfigboard.cmake create mode 100644 m5stack/boards/M5STACK_PowerHub/mpconfigboard.h create mode 100644 m5stack/boards/M5STACK_PowerHub/sdkconfig.board create mode 100644 m5stack/modules/startup/manifest_powerhub.py create mode 100644 m5stack/modules/startup/powerhub.py diff --git a/m5stack/Makefile b/m5stack/Makefile index cc1c1a03..df6fabb8 100644 --- a/m5stack/Makefile +++ b/m5stack/Makefile @@ -44,7 +44,8 @@ boards := \ M5STACK_StamPLC:stamplc \ M5STACK_Tab5:tab5 \ M5STACK_CardputerADV:cardputeradv\ - M5STACK_Unit_C6L:unit_c6l + M5STACK_Unit_C6L:unit_c6l \ + M5STACK_PowerHub:powerhub define find_board $(if $(filter $(1):%,$(boards)),$(word 2,$(subst :, ,$(filter $(1):%,$(boards)))),none) @@ -85,7 +86,8 @@ BOARD_TYPE_DEF := \ stamplc \ tab5 \ cardputeradv\ - unit_c6l + unit_c6l \ + powerhub # Select the board type to build, default is None # This value affects which folder in the "./fs/system/" directory is pack into "fs-system.bin" diff --git a/m5stack/boards/M5STACK_PowerHub/board.json b/m5stack/boards/M5STACK_PowerHub/board.json new file mode 100644 index 00000000..096f750e --- /dev/null +++ b/m5stack/boards/M5STACK_PowerHub/board.json @@ -0,0 +1,18 @@ +{ + "deploy": [ + "../deploy_s3.md" + ], + "docs": "", + "features": [ + "BLE", + "WiFi" + ], + "images": [ + "generic_s3.jpg" + ], + "mcu": "esp32s3", + "product": "M5Stack S3 Serials", + "thumbnail": "", + "url": "https://www.espressif.com/en/products/modules", + "vendor": "M5Stack" +} diff --git a/m5stack/boards/M5STACK_PowerHub/manifest.py b/m5stack/boards/M5STACK_PowerHub/manifest.py new file mode 100644 index 00000000..d9ac99a0 --- /dev/null +++ b/m5stack/boards/M5STACK_PowerHub/manifest.py @@ -0,0 +1,6 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +include("$(MPY_DIR)/../m5stack/modules/startup/manifest_powerhub.py") +include("$(MPY_DIR)/../m5stack/libs/unit/manifest.py") diff --git a/m5stack/boards/M5STACK_PowerHub/mpconfigboard.cmake b/m5stack/boards/M5STACK_PowerHub/mpconfigboard.cmake new file mode 100644 index 00000000..b61b6127 --- /dev/null +++ b/m5stack/boards/M5STACK_PowerHub/mpconfigboard.cmake @@ -0,0 +1,41 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +set(IDF_TARGET esp32s3) + +# stamps3 https://github.com/m5stack/m5stack-board-id/blob/main/board.csv#L29 +set(BOARD_ID 146) +set(MICROPY_PY_LVGL 0) + +set(SDKCONFIG_DEFAULTS + ./boards/sdkconfig.base + ${SDKCONFIG_IDF_VERSION_SPECIFIC} + ./boards/sdkconfig.240mhz + ./boards/sdkconfig.disable_iram + ./boards/sdkconfig.ble + ./boards/sdkconfig.usb + ./boards/sdkconfig.usb_cdc + ./boards/sdkconfig.flash_16mb + ./boards/sdkconfig.freertos + ./boards/M5STACK_PowerHub/sdkconfig.board +) + +# If not enable LVGL, ignore this... +set(LV_CFLAGS -DLV_COLOR_DEPTH=16 -DLV_COLOR_16_SWAP=0) + +if(NOT MICROPY_FROZEN_MANIFEST) + set(MICROPY_FROZEN_MANIFEST ${CMAKE_SOURCE_DIR}/boards/manifest.py) +endif() + +# NOTE: 这里的配置是无效的,仅为了兼容ADF,保证编译通过 +set(ADF_COMPS "$ENV{ADF_PATH}/components") +set(ADF_BOARD_DIR "$ENV{ADF_PATH}/components/audio_board/esp32_s3_box_3") + +list(APPEND EXTRA_COMPONENT_DIRS + $ENV{ADF_PATH}/components/audio_pipeline + $ENV{ADF_PATH}/components/audio_sal + $ENV{ADF_PATH}/components/esp-adf-libs + $ENV{ADF_PATH}/components/esp-sr + ${CMAKE_SOURCE_DIR}/boards +) \ No newline at end of file diff --git a/m5stack/boards/M5STACK_PowerHub/mpconfigboard.h b/m5stack/boards/M5STACK_PowerHub/mpconfigboard.h new file mode 100644 index 00000000..8a471801 --- /dev/null +++ b/m5stack/boards/M5STACK_PowerHub/mpconfigboard.h @@ -0,0 +1,21 @@ +/* +* SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +* +* SPDX-License-Identifier: MIT +*/ + +#define MICROPY_HW_BOARD_NAME "M5STACK PowerHub" +#define MICROPY_HW_MCU_NAME "ESP32S3" + +#define MICROPY_PY_MACHINE_DAC (0) + +// Enable UART REPL for modules that have an external USB-UART and don't use native USB. +#define MICROPY_HW_ENABLE_UART_REPL (1) + +#define MICROPY_HW_I2C0_SCL (9) +#define MICROPY_HW_I2C0_SDA (8) + +#define MICROPY_HW_USB_CDC_INTERFACE_STRING "M5Stack PowerHub(UiFlow2)" + +// If not enable LVGL, ignore this... +#include "./../mpconfiglvgl.h" diff --git a/m5stack/boards/M5STACK_PowerHub/sdkconfig.board b/m5stack/boards/M5STACK_PowerHub/sdkconfig.board new file mode 100644 index 00000000..ae76a2a0 --- /dev/null +++ b/m5stack/boards/M5STACK_PowerHub/sdkconfig.board @@ -0,0 +1,21 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +CONFIG_FLASHMODE_DIO=y # QIO mode has some problem when mount fs +CONFIG_ESPTOOLPY_FLASHMODE_DIO=y +CONFIG_ESPTOOLPY_FLASHMODE="dio" +CONFIG_ESPTOOLPY_FLASHFREQ_80M=y +CONFIG_ESPTOOLPY_FLASHSIZE_DETECT=y +CONFIG_ESPTOOLPY_AFTER_NORESET=y + +CONFIG_SPIRAM_MEMTEST= +# CONFIG_FREERTOS_UNICORE=y + +# M5STACK UiFlow USB description +CONFIG_TINYUSB_DESC_CDC_STRING="M5Stack PowerHub(UiFlow2)" + +# SSL +CONFIG_MBEDTLS_AES_USE_INTERRUPT=n + +CONFIG_FREERTOS_ENABLE_BACKWARD_COMPATIBILITY=y diff --git a/m5stack/cmodules/m5unified/m5unified.c b/m5stack/cmodules/m5unified/m5unified.c index b870a176..2be444c1 100644 --- a/m5stack/cmodules/m5unified/m5unified.c +++ b/m5stack/cmodules/m5unified/m5unified.c @@ -50,6 +50,7 @@ static const mp_rom_map_elem_t m5_board_member_table[] = { { MP_ROM_QSTR(MP_QSTR_M5AtomEcho), MP_ROM_INT(142) }, { MP_ROM_QSTR(MP_QSTR_M5AtomS3R_CAM), MP_ROM_INT(144) }, { MP_ROM_QSTR(MP_QSTR_M5AtomEchoS3R), MP_ROM_INT(145) }, + { MP_ROM_QSTR(MP_QSTR_M5PowerHub), MP_ROM_INT(146) }, // external displays { MP_ROM_QSTR(MP_QSTR_M5ATOMDisplay), MP_ROM_INT(192) }, { MP_ROM_QSTR(MP_QSTR_M5UnitLCD), MP_ROM_INT(193) }, diff --git a/m5stack/libs/hardware/__init__.py b/m5stack/libs/hardware/__init__.py index af1271ab..19a70df5 100644 --- a/m5stack/libs/hardware/__init__.py +++ b/m5stack/libs/hardware/__init__.py @@ -7,6 +7,7 @@ "KeyboardI2C": "keyboard", "Button": "button", "CAN": "can", + "PWRCAN": "can", "IR": "ir", "LoRa": "lora", "MatrixKeyboard": "matrix_keyboard", diff --git a/m5stack/libs/hardware/can.py b/m5stack/libs/hardware/can.py index ba3d511e..624d0079 100644 --- a/m5stack/libs/hardware/can.py +++ b/m5stack/libs/hardware/can.py @@ -40,3 +40,6 @@ def __init__( triple_sampling=triple_sampling, baudrate=baudrate // 1000, ) + + +PWRCAN = CAN diff --git a/m5stack/modules/startup/__init__.py b/m5stack/modules/startup/__init__.py index 9ae5299f..1ef81d0d 100644 --- a/m5stack/modules/startup/__init__.py +++ b/m5stack/modules/startup/__init__.py @@ -263,6 +263,11 @@ def startup(boot_opt, timeout: int = 60) -> None: unit_c6l = UnitC6L_Startup() unit_c6l.startup(ssid, pswd, timeout) + elif board_id == M5.BOARD.M5PowerHub: + from .powerhub import PowerHub_Startup + + powerhub = PowerHub_Startup() + powerhub.startup(ssid, pswd, timeout) # Only connect to network, not show any menu elif boot_opt is BOOT_OPT_NETWORK: diff --git a/m5stack/modules/startup/manifest_powerhub.py b/m5stack/modules/startup/manifest_powerhub.py new file mode 100644 index 00000000..541f923f --- /dev/null +++ b/m5stack/modules/startup/manifest_powerhub.py @@ -0,0 +1,13 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +package( + "startup", + ( + "__init__.py", + "powerhub.py", + ), + base_path="..", + opt=3, +) diff --git a/m5stack/modules/startup/powerhub.py b/m5stack/modules/startup/powerhub.py new file mode 100644 index 00000000..907989d1 --- /dev/null +++ b/m5stack/modules/startup/powerhub.py @@ -0,0 +1,79 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +# PowerHub startup script +import M5 +import time +import network +import machine +import binascii +from . import Startup + + +# PowerHub startup menu +class PowerHub_Startup(Startup): + COLOR_RED = 0xFF0000 # WiFi not connected + COLOR_BLUE = 0x0000FF # WiFi connected, server not connected + COLOR_GREEN = 0x00FF00 # WiFi connected, server connected + + def __init__(self) -> None: + M5.Led.setBrightness(30) + M5.Led.setAllColor(0xFFFFFF) + M5.Led.setColor(5, self.COLOR_RED) + M5.Led.display() + super().__init__() + M5.Led.display() + + def show_hits(self, hits: str) -> None: + pass + + def show_msg(self, msg: str) -> None: + pass + + def show_ssid(self, ssid: str) -> None: + pass + + def show_mac(self) -> None: + mac = binascii.hexlify(machine.unique_id()).decode("utf-8").upper() + print("Mac: " + mac[0:6] + "_" + mac[6:]) + + def show_error(self, ssid: str, error: str) -> None: + print("SSID: " + ssid + "\r\nNotice: " + error) + + def startup(self, ssid: str, pswd: str, timeout: int = 60) -> None: + self.show_mac() + + if super().connect_network(ssid=ssid, pswd=pswd): + print("Connecting to " + ssid + " ", end="") + status = super().connect_status() + # self.rgb.set_brightness(60) + start = time.time() + while status is not network.STAT_GOT_IP: + time.sleep_ms(300) + if status is network.STAT_NO_AP_FOUND: + self.show_error(ssid, "NO AP FOUND") + break + elif status is network.STAT_WRONG_PASSWORD: + self.show_error(ssid, "WRONG PASSWORD") + break + elif status is network.STAT_HANDSHAKE_TIMEOUT: + self.show_error(ssid, "HANDSHAKE ERR") + break + elif status is network.STAT_CONNECTING: + print(".", end="") + status = super().connect_status() + # connect to network timeout + if (time.time() - start) > timeout: + self.show_error(ssid, "TIMEOUT") + break + + print(" ") + if status is network.STAT_GOT_IP: + print("Local IP: " + super().local_ip()) + M5.Led.setColor(5, self.COLOR_GREEN) + M5.Led.display() + else: + M5.Led.setColor(5, self.COLOR_BLUE) + M5.Led.display() + self.show_error("Not Found", "Please use M5Burner setup :)") diff --git a/tools/ci.sh b/tools/ci.sh index 41b50106..c7a58b27 100644 --- a/tools/ci.sh +++ b/tools/ci.sh @@ -253,6 +253,7 @@ function ci_esp32_quick_build { make ${MAKEOPTS} -C m5stack BOARD=M5STACK_Stamp_PICO pack_all make ${MAKEOPTS} -C m5stack BOARD=M5STACK_StickC pack_all make ${MAKEOPTS} -C m5stack BOARD=M5STACK_Unit_C6L pack_all + make ${MAKEOPTS} -C m5stack BOARD=M5STACK_PowerHub pack_all make ${MAKEOPTS} -C third-party BOARD=SEEED_STUDIO_XIAO_ESP32S3 pack_all } @@ -268,6 +269,7 @@ function ci_unit_build { make ${MAKEOPTS} -C m5stack BOARD=M5STACK_CoreS3 pack_all make ${MAKEOPTS} -C m5stack BOARD=M5STACK_NanoC6 pack_all make ${MAKEOPTS} -C m5stack BOARD=M5STACK_Tab5 pack_all + make ${MAKEOPTS} -C m5stack BOARD=M5STACK_PowerHub pack_all make ${MAKEOPTS} -C third-party BOARD=ESPRESSIF_ESP32_S3_BOX_3 pack_all } @@ -357,6 +359,7 @@ function ci_esp32_nightly_build { make ${MAKEOPTS} -C m5stack BOARD=M5STACK_NanoC6 pack_all make ${MAKEOPTS} -C m5stack BOARD=M5STACK_Paper pack_all make ${MAKEOPTS} -C m5stack BOARD=M5STACK_PaperS3 pack_all + make ${MAKEOPTS} -C m5stack BOARD=M5STACK_PowerHub pack_all make ${MAKEOPTS} -C m5stack BOARD=M5STACK_Stamp_PICO pack_all make ${MAKEOPTS} -C m5stack BOARD=M5STACK_StamPLC pack_all make ${MAKEOPTS} -C m5stack BOARD=M5STACK_StampS3 pack_all From 21fbe7cc234a94da2b7d71702877a7b98d0191b1 Mon Sep 17 00:00:00 2001 From: tinyu Date: Mon, 27 Oct 2025 14:19:04 +0800 Subject: [PATCH 298/322] components/M5Unified: Update M5GFX&M5Unfied version. Signed-off-by: tinyu --- m5stack/cmodules/m5unified/m5unified.h | 2 +- m5stack/components/M5Unified/M5GFX | 2 +- m5stack/components/M5Unified/M5Unified | 2 +- m5stack/patches/2007-Support-UnitC6L.patch | 116 ++++++++---------- .../2008-Only-use-old-rmt-driver.patch | 23 +--- 5 files changed, 60 insertions(+), 85 deletions(-) diff --git a/m5stack/cmodules/m5unified/m5unified.h b/m5stack/cmodules/m5unified/m5unified.h index 373aa451..f32758ed 100644 --- a/m5stack/cmodules/m5unified/m5unified.h +++ b/m5stack/cmodules/m5unified/m5unified.h @@ -21,7 +21,7 @@ /* *FORMAT-ON* */ #define MAKE_TABLE(prefix, func) \ - { MP_ROM_QSTR(MP_QSTR_##func), MP_ROM_PTR(&prefix##_##func##_obj) } + { MP_ROM_QSTR(MP_QSTR_##func), MP_ROM_PTR(&prefix##_##func##_obj) } extern const mp_obj_type_t mp_fonts_type; extern const mp_obj_type_t mp_color_type; diff --git a/m5stack/components/M5Unified/M5GFX b/m5stack/components/M5Unified/M5GFX index bf16c3d2..fbe11fcd 160000 --- a/m5stack/components/M5Unified/M5GFX +++ b/m5stack/components/M5Unified/M5GFX @@ -1 +1 @@ -Subproject commit bf16c3d2b70bfbe721399e0990ef2a0c0dc5bd74 +Subproject commit fbe11fcde0983c6464dfaa155f8eec24c3bb0bee diff --git a/m5stack/components/M5Unified/M5Unified b/m5stack/components/M5Unified/M5Unified index 05377f09..447ed989 160000 --- a/m5stack/components/M5Unified/M5Unified +++ b/m5stack/components/M5Unified/M5Unified @@ -1 +1 @@ -Subproject commit 05377f09d83d1c71212cc84d1c6380b90c1de655 +Subproject commit 447ed989498faab9f28eadd17cf97b12eb930691 diff --git a/m5stack/patches/2007-Support-UnitC6L.patch b/m5stack/patches/2007-Support-UnitC6L.patch index acdf7282..d6f7056e 100644 --- a/m5stack/patches/2007-Support-UnitC6L.patch +++ b/m5stack/patches/2007-Support-UnitC6L.patch @@ -1,33 +1,33 @@ -Index: M5Unified/src/M5Unified.cpp -=================================================================== ---- M5Unified.orig/src/M5Unified.cpp -+++ M5Unified/src/M5Unified.cpp -@@ -88,6 +88,7 @@ static constexpr const uint8_t _pin_tabl +diff --git a/src/M5Unified.cpp b/src/M5Unified.cpp +index 6c79ee8..2ba4e31 100644 +--- a/src/M5Unified.cpp ++++ b/src/M5Unified.cpp +@@ -94,7 +94,7 @@ static constexpr const uint8_t _pin_table_i2c_ex_in[][5] = { + #elif defined (CONFIG_IDF_TARGET_ESP32C3) { board_t::board_unknown , 255 ,255 , GPIO_NUM_0 ,GPIO_NUM_1 }, #elif defined (CONFIG_IDF_TARGET_ESP32C6) - { board_t::board_ArduinoNessoN1,GPIO_NUM_8 ,GPIO_NUM_10 , GPIO_NUM_8 ,GPIO_NUM_10 }, +-{ board_t::board_M5UnitC6L ,GPIO_NUM_8 ,GPIO_NUM_10 , 255 ,255 }, +{ board_t::board_M5UnitC6L , 255 , 255 , 255 ,255 }, + { board_t::board_ArduinoNessoN1,GPIO_NUM_8 ,GPIO_NUM_10 , GPIO_NUM_8 ,GPIO_NUM_10 }, { board_t::board_unknown , 255 ,255 , GPIO_NUM_1 ,GPIO_NUM_2 }, // NanoC6 #elif defined (CONFIG_IDF_TARGET_ESP32P4) - { board_t::board_M5Tab5 , GPIO_NUM_32,GPIO_NUM_31, GPIO_NUM_54,GPIO_NUM_53 }, // Tab5 -@@ -115,6 +116,7 @@ static constexpr const uint8_t _pin_tabl +@@ -123,7 +123,7 @@ static constexpr const uint8_t _pin_table_port_bc[][5] = { + { board_t::board_M5PowerHub , 255 , 255 , GPIO_NUM_1 ,GPIO_NUM_2 }, #elif defined (CONFIG_IDF_TARGET_ESP32C3) #elif defined (CONFIG_IDF_TARGET_ESP32C6) - { board_t::board_ArduinoNessoN1,GPIO_NUM_4 ,GPIO_NUM_5 , 255 ,255 }, +-{ board_t::board_M5UnitC6L ,GPIO_NUM_4 ,GPIO_NUM_5 , GPIO_NUM_4 ,GPIO_NUM_5 }, +{ board_t::board_M5UnitC6L ,GPIO_NUM_4 ,GPIO_NUM_5 , 255 ,255 }, + { board_t::board_ArduinoNessoN1,GPIO_NUM_4 ,GPIO_NUM_5 , GPIO_NUM_4 ,GPIO_NUM_5 }, #elif defined (CONFIG_IDF_TARGET_ESP32P4) { board_t::board_M5Tab5 , GPIO_NUM_17,GPIO_NUM_52, GPIO_NUM_7 ,GPIO_NUM_6 }, // Tab5 - #else -@@ -1320,6 +1322,36 @@ static constexpr const uint8_t _pin_tabl - _io_expander[i].reset(ioexp); - } - break; -+ case board_t::board_M5UnitC6L: -+ { +@@ -1383,9 +1383,32 @@ static constexpr const uint8_t _pin_table_mbus[][31] = { + #elif defined (CONFIG_IDF_TARGET_ESP32C6) + case board_t::board_M5UnitC6L: + { + In_SoftI2C.begin(10, 8); -+ auto ioexp = new PI4IOE5V6408_Class(0x43); -+ ioexp->begin(); -+ _io_expander[0].reset(ioexp); + auto ioexp = new PI4IOE5V6408_Class(0x43); + ioexp->begin(); + _io_expander[0].reset(ioexp); + // user button(P0) input pullup + _io_expander[0]->setDirection(0, false); + _io_expander[0]->setPullMode(0, true); @@ -50,32 +50,14 @@ Index: M5Unified/src/M5Unified.cpp + _io_expander[0]->setPullMode(6, false); + _io_expander[0]->setHighImpedance(6, false); + _io_expander[0]->digitalWrite(6, true); -+ } -+ break; - #elif defined (CONFIG_IDF_TARGET_ESP32S3) - case board_t::board_M5StampPLC: - { -@@ -1679,7 +1711,14 @@ static constexpr const uint8_t _pin_tabl - spk_cfg.magnification = 48; - } - break; -- -+ case board_t::board_M5UnitC6L: -+ if (cfg.internal_spk) -+ { -+ spk_cfg.pin_data_out = GPIO_NUM_11; -+ spk_cfg.buzzer = true; -+ spk_cfg.magnification = 16; -+ } -+ break; - #elif defined (CONFIG_IDF_TARGET_ESP32P4) - case board_t::board_M5Tab5: - if (cfg.internal_spk) -@@ -2307,7 +2346,14 @@ static constexpr const uint8_t _pin_tabl - ; + } break; - } -- + case board_t::board_ArduinoNessoN1: +@@ -2323,6 +2346,15 @@ static constexpr const uint8_t _pin_table_mbus[][31] = { + btn_rawstate_bits |= (( raw_gpio32_39 >> (GPIO_NUM_37 & 31)) & 1 ) // gpio37 A + | (((raw_gpio32_39 >> (GPIO_NUM_39 & 31)) & 1)<<1); // gpio39 B + break; ++ + case board_t::board_M5UnitC6L: + { + use_rawstate_bits = 0b00001; @@ -84,21 +66,25 @@ Index: M5Unified/src/M5Unified.cpp + btn_rawstate_bits = (!(value & 0b00001) ? 0b00001 : 0); // BtnA + break; + } + default: break; - } -Index: M5Unified/src/utility/IOExpander_Base.hpp -=================================================================== ---- M5Unified.orig/src/utility/IOExpander_Base.hpp -+++ M5Unified/src/utility/IOExpander_Base.hpp -@@ -6,15 +6,28 @@ +diff --git a/src/utility/IOExpander_Base.hpp b/src/utility/IOExpander_Base.hpp +index 93bade7..e7b6039 100644 +--- a/src/utility/IOExpander_Base.hpp ++++ b/src/utility/IOExpander_Base.hpp +@@ -3,18 +3,31 @@ + #ifndef __M5_IOEXPANDER_BASE_H__ + #define __M5_IOEXPANDER_BASE_H__ +- #include #include "I2C_Class.hpp" + +#if CONFIG_IDF_TARGET_ESP32C6 +#include "SoftI2C_Class.hpp" +#endif - ++ namespace m5 { + @@ -120,10 +106,10 @@ Index: M5Unified/src/utility/IOExpander_Base.hpp IOExpander_Base(const IOExpander_Base&) = delete; // false input, true output -Index: M5Unified/src/utility/PI4IOE5V6408_Class.hpp -=================================================================== ---- M5Unified.orig/src/utility/PI4IOE5V6408_Class.hpp -+++ M5Unified/src/utility/PI4IOE5V6408_Class.hpp +diff --git a/src/utility/PI4IOE5V6408_Class.hpp b/src/utility/PI4IOE5V6408_Class.hpp +index 97e1a95..cd74b71 100644 +--- a/src/utility/PI4IOE5V6408_Class.hpp ++++ b/src/utility/PI4IOE5V6408_Class.hpp @@ -15,6 +15,9 @@ #include "IOExpander_Base.hpp" @@ -134,10 +120,11 @@ Index: M5Unified/src/utility/PI4IOE5V6408_Class.hpp namespace m5 { -@@ -24,9 +27,15 @@ namespace m5 +@@ -23,10 +26,15 @@ namespace m5 + { public: static constexpr std::uint8_t DEFAULT_ADDRESS = 0x43; - +- +#if CONFIG_IDF_TARGET_ESP32C6 + PI4IOE5V6408_Class(std::uint8_t i2c_addr = DEFAULT_ADDRESS, std::uint32_t freq = 400000, m5::SoftI2C_Class* i2c = &m5::In_SoftI2C) + : IOExpander_Base(i2c_addr, freq, i2c) @@ -150,10 +137,11 @@ Index: M5Unified/src/utility/PI4IOE5V6408_Class.hpp bool begin(); -Index: M5Unified/src/utility/SoftI2C_Class.cpp -=================================================================== +diff --git a/src/utility/SoftI2C_Class.cpp b/src/utility/SoftI2C_Class.cpp +new file mode 100644 +index 0000000..2dba3a6 --- /dev/null -+++ M5Unified/src/utility/SoftI2C_Class.cpp ++++ b/src/utility/SoftI2C_Class.cpp @@ -0,0 +1,479 @@ +// Copyright (c) M5Stack. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. @@ -634,10 +622,12 @@ Index: M5Unified/src/utility/SoftI2C_Class.cpp + } + +} // namespace m5 -Index: M5Unified/src/utility/SoftI2C_Class.hpp -=================================================================== +\ No newline at end of file +diff --git a/src/utility/SoftI2C_Class.hpp b/src/utility/SoftI2C_Class.hpp +new file mode 100644 +index 0000000..a466704 --- /dev/null -+++ M5Unified/src/utility/SoftI2C_Class.hpp ++++ b/src/utility/SoftI2C_Class.hpp @@ -0,0 +1,214 @@ +// Copyright (c) M5Stack. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. diff --git a/m5stack/patches/2008-Only-use-old-rmt-driver.patch b/m5stack/patches/2008-Only-use-old-rmt-driver.patch index 2530b031..a2ececde 100644 --- a/m5stack/patches/2008-Only-use-old-rmt-driver.patch +++ b/m5stack/patches/2008-Only-use-old-rmt-driver.patch @@ -1,34 +1,19 @@ -diff --git a/src/utility/led/LED_Strip_Class.cpp b/src/utility/led/LED_Strip_Class.cpp -index 7ea1cd1..47691d3 100644 ---- a/src/utility/led/LED_Strip_Class.cpp -+++ b/src/utility/led/LED_Strip_Class.cpp -@@ -126,9 +126,11 @@ namespace m5 - if (_config.pin_data < 0) { - return false; - } -+ #if M5UNIFIED_RMT_VERSION == 2 - if (_led_encoder && _rmt_ch_handle) { - return true; - } -+ #endif - - #if M5UNIFIED_RMT_VERSION == 2 - rmt_tx_channel_config_t rmt_tx; diff --git a/src/utility/led/LED_Strip_Class.hpp b/src/utility/led/LED_Strip_Class.hpp -index a41e2de..c0b1e07 100644 +index a41e2de..e2d35cf 100644 --- a/src/utility/led/LED_Strip_Class.hpp +++ b/src/utility/led/LED_Strip_Class.hpp @@ -12,12 +12,13 @@ #if __has_include () #include #include -+ #define M5UNIFIED_RMT_VERSION 1 - // RMT +-// RMT - #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0) - #define M5UNIFIED_RMT_VERSION 2 - #else - #define M5UNIFIED_RMT_VERSION 1 - #endif ++ // RMT ++ #define M5UNIFIED_RMT_VERSION 1 +// #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0) +// #define M5UNIFIED_RMT_VERSION 2 +// #else From c632acf328a9cf749f80719fe2c2a82d58573826 Mon Sep 17 00:00:00 2001 From: tinyu Date: Wed, 29 Oct 2025 13:27:08 +0800 Subject: [PATCH 299/322] hardware: Add Led support for powerhub. Signed-off-by: tinyu --- m5stack/cmodules/m5unified/m5unified_power.c | 2 +- m5stack/components/M5Unified/mpy_m5power.cpp | 12 ++++---- m5stack/libs/driver/manifest.py | 1 + m5stack/libs/driver/neopixel/powerhub_rgb.py | 29 ++++++++++++++++++++ m5stack/libs/hardware/rgb.py | 4 +++ 5 files changed, 41 insertions(+), 7 deletions(-) create mode 100644 m5stack/libs/driver/neopixel/powerhub_rgb.py diff --git a/m5stack/cmodules/m5unified/m5unified_power.c b/m5stack/cmodules/m5unified/m5unified_power.c index 260d2276..250195bf 100644 --- a/m5stack/cmodules/m5unified/m5unified_power.c +++ b/m5stack/cmodules/m5unified/m5unified_power.c @@ -40,7 +40,7 @@ const mp_obj_type_t mp_power_port_mask_enum = { MAKE_METHOD_KW(power, setExtOutput, 1); MAKE_METHOD_0(power, getExtOutput); -MAKE_METHOD_KW(power, setExtPortBusConfig, 4); +MAKE_METHOD_KW(power, setExtPortBusConfig, 1); MAKE_METHOD_1(power, getExtVoltage); MAKE_METHOD_1(power, getExtCurrent); MAKE_METHOD_KW(power, setUsbOutput, 1); diff --git a/m5stack/components/M5Unified/mpy_m5power.cpp b/m5stack/components/M5Unified/mpy_m5power.cpp index c0c81127..f963c33e 100644 --- a/m5stack/components/M5Unified/mpy_m5power.cpp +++ b/m5stack/components/M5Unified/mpy_m5power.cpp @@ -53,13 +53,13 @@ namespace m5 } mp_obj_t power_setExtPortBusConfig(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { - enum {ARG_voltage, ARG_current_limit, ARG_enable, ARG_direction}; + enum {ARG_direction, ARG_output_enable, ARG_voltage, ARG_current_limit}; const mp_arg_t allowed_args[] = { /* *FORMAT-OFF* */ - { MP_QSTR_voltage, MP_ARG_INT | MP_ARG_REQUIRED, {.u_int = 3000} }, - { MP_QSTR_current_limit, MP_ARG_INT | MP_ARG_REQUIRED, {.u_int = 232} }, - { MP_QSTR_enable, MP_ARG_BOOL | MP_ARG_REQUIRED, {.u_bool = true} }, - { MP_QSTR_direction, MP_ARG_INT, {.u_int = 0} }, + { MP_QSTR_direction, MP_ARG_INT | MP_ARG_REQUIRED , {.u_int = 0} }, + { MP_QSTR_output_enable, MP_ARG_INT , {.u_int = 1} }, + { MP_QSTR_voltage, MP_ARG_INT , {.u_int = 3000} }, + { MP_QSTR_current_limit, MP_ARG_INT , {.u_int = 232} }, /* *FORMAT-ON* */ }; mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; @@ -69,7 +69,7 @@ namespace m5 ext_port_bus_t cfg; cfg.voltage = args[ARG_voltage].u_int; cfg.currentLimit = args[ARG_current_limit].u_int; - cfg.enable = args[ARG_enable].u_bool; + cfg.enable = args[ARG_output_enable].u_int; cfg.direction = args[ARG_direction].u_int; getPower(pos_args[0])->setExtPortBusConfig(cfg); diff --git a/m5stack/libs/driver/manifest.py b/m5stack/libs/driver/manifest.py index f20000fd..746d23fa 100644 --- a/m5stack/libs/driver/manifest.py +++ b/m5stack/libs/driver/manifest.py @@ -35,6 +35,7 @@ "modbus/master/uFunctions.py", "modbus/master/uSerial.py", "neopixel/__init__.py", + "neopixel/powerhub_rgb.py", "neopixel/sk6812.py", "neopixel/ws2812.py", "qrcode/__init__.py", diff --git a/m5stack/libs/driver/neopixel/powerhub_rgb.py b/m5stack/libs/driver/neopixel/powerhub_rgb.py new file mode 100644 index 00000000..8dbfee2c --- /dev/null +++ b/m5stack/libs/driver/neopixel/powerhub_rgb.py @@ -0,0 +1,29 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +# PowerHub RGB driver +from . import NeoPixel +from machine import Pin +from M5 import Led + + +class PowerHubRGB(NeoPixel): + def __init__(self) -> None: + print("PowerHubRGB initialized") + pass + + def _map(self, x: int, in_min: int, in_max: int, out_min: int, out_max: int) -> int: + return int((x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min) + + def set_color(self, index: int, color: int) -> None: + Led.setColor(index, color) + Led.display() + + def fill_color(self, color: int) -> None: + Led.setAllColor(color) + Led.display() + + def set_brightness(self, br: int) -> None: + br = self._map(br, 0, 100, 0, 255) + Led.setBrightness(br) diff --git a/m5stack/libs/hardware/rgb.py b/m5stack/libs/hardware/rgb.py index d164c602..06ced4bd 100644 --- a/m5stack/libs/hardware/rgb.py +++ b/m5stack/libs/hardware/rgb.py @@ -6,6 +6,7 @@ from driver.neopixel import NeoPixel from driver.neopixel.ws2812 import WS2812 from driver.neopixel.sk6812 import SK6812 +from driver.neopixel.powerhub_rgb import PowerHubRGB import machine @@ -64,6 +65,9 @@ def __new__(cls, **kwargs) -> NeoPixel: elif board_id == M5.BOARD.M5UnitC6L: cls._instance = WS2812(io=2, n=1) return cls._instance + elif board_id == M5.BOARD.M5PowerHub: + cls._instance = PowerHubRGB() + return cls._instance else: pass else: From 5eb186220dd3cd44210cbc412855e0e78518384a Mon Sep 17 00:00:00 2001 From: tinyu Date: Fri, 31 Oct 2025 10:26:12 +0800 Subject: [PATCH 300/322] libs/m5ui: Fix some known bugs. Signed-off-by: tinyu --- m5stack/libs/m5ui/arc.py | 3 +++ m5stack/libs/m5ui/calendar.py | 4 ++++ m5stack/libs/m5ui/menu.py | 4 +++- m5stack/libs/m5ui/tabview.py | 24 ++++++++++++------------ 4 files changed, 22 insertions(+), 13 deletions(-) diff --git a/m5stack/libs/m5ui/arc.py b/m5stack/libs/m5ui/arc.py index ee97f51e..e7ecf481 100644 --- a/m5stack/libs/m5ui/arc.py +++ b/m5stack/libs/m5ui/arc.py @@ -107,6 +107,9 @@ def set_arc_color(self, color, opa: int, part: int): self.set_style_arc_color(color, part) self.set_style_arc_opa(opa, part) + def set_range(self, min_value: int, max_value: int) -> None: + super().set_range(min(min_value, max_value), max(min_value, max_value)) + def set_style_radius(self, radius: int, part: int) -> None: if radius < 0: raise ValueError("Radius must be a non-negative integer.") diff --git a/m5stack/libs/m5ui/calendar.py b/m5stack/libs/m5ui/calendar.py index 9ee42a8e..641f2c1b 100644 --- a/m5stack/libs/m5ui/calendar.py +++ b/m5stack/libs/m5ui/calendar.py @@ -47,6 +47,10 @@ def __init__( self.set_pos(x, y) self.set_size(w, h) self.set_calendar_style(style) + if style == "dropdown": + self.header_dropdown_set_year_list( + "\n".join(str(year) for year in range(show_month[0], 1999, -1)) + ) self.set_today_date(*today_date) self.set_month_shown(*show_month) diff --git a/m5stack/libs/m5ui/menu.py b/m5stack/libs/m5ui/menu.py index b670568d..43b7640e 100644 --- a/m5stack/libs/m5ui/menu.py +++ b/m5stack/libs/m5ui/menu.py @@ -14,6 +14,7 @@ class M5Menu(lv.menu): :param int y: The y position of the menu. :param int w: The width of the menu. :param int h: The height of the menu. + :param str page_name: The name of the main page of the menu. :param lv.obj parent: The parent object to attach the menu to. If not specified, the menu will be attached to the default screen. UiFlow2 Code Block: @@ -37,6 +38,7 @@ def __init__( y=0, w=0, h=0, + page_name=None, parent=None, ): if parent is None: @@ -44,7 +46,7 @@ def __init__( super().__init__(parent) self.set_pos(x, y) self.set_size(w, h) - self.main_page = lv.menu_page(self, None) # Create a main page + self.main_page = lv.menu_page(self, page_name) # Create a main page def add_label( self, diff --git a/m5stack/libs/m5ui/tabview.py b/m5stack/libs/m5ui/tabview.py index 00b51c16..6c418688 100644 --- a/m5stack/libs/m5ui/tabview.py +++ b/m5stack/libs/m5ui/tabview.py @@ -7,17 +7,6 @@ import lvgl as lv -def _button_clicked_event_cb(event_struct): - _button = event_struct.get_current_target() - - if not hasattr(_button, "get_parent"): - _button = lv.obj.__cast__(_button) - - tv = _button.get_parent().get_parent() - idx = _button.get_index() - lv.tabview.set_active(tv, idx, False) - - class M5TabView(lv.tabview): """Create a TabView object. @@ -63,6 +52,17 @@ def __init__( self.set_tab_bar_size(bar_size) self.tab_num = 0 + @staticmethod + def _button_clicked_event_cb(event_struct): + _button = event_struct.get_current_target() + + if not hasattr(_button, "get_parent"): + _button = lv.obj.__cast__(_button) + + tv = _button.get_parent().get_parent() + idx = _button.get_index() + lv.tabview.set_active(tv, idx, False) + def add_tab(self, text): """Add a tab to the tab view. @@ -89,7 +89,7 @@ def add_tab(self, text): parent=self.get_tab_bar(), ) _button.set_flex_grow(1) - _button.add_event_cb(_button_clicked_event_cb, lv.EVENT.CLICKED, None) + _button.add_event_cb(self._button_clicked_event_cb, lv.EVENT.CLICKED, None) _cont = self.get_content() _page = lv.obj(_cont) From 871b63cc66ebdf6c7e171a678fbcfd554316396e Mon Sep 17 00:00:00 2001 From: tinyu Date: Tue, 4 Nov 2025 16:39:11 +0800 Subject: [PATCH 301/322] libs/m5ui: Fix TabView tab count mismatch. Signed-off-by: tinyu --- m5stack/libs/m5ui/tabview.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/m5stack/libs/m5ui/tabview.py b/m5stack/libs/m5ui/tabview.py index 6c418688..95b54608 100644 --- a/m5stack/libs/m5ui/tabview.py +++ b/m5stack/libs/m5ui/tabview.py @@ -116,6 +116,9 @@ def rename_tab(self, pos: int, txt: str) -> None: if pos < self.tab_num: super().rename_tab(pos, txt) + def get_tab_active(self) -> int: + return super().get_tab_active() + 1 + def __getattr__(self, name): if hasattr(M5Base, name): method = getattr(M5Base, name) From f47566bd8f50e22e777fa7e5f1cdd27e51cdb3d3 Mon Sep 17 00:00:00 2001 From: tinyu Date: Fri, 31 Oct 2025 11:02:45 +0800 Subject: [PATCH 302/322] libs/unit: Fix MiniScale tare function error. Signed-off-by: tinyu --- m5stack/libs/unit/miniscale.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/m5stack/libs/unit/miniscale.py b/m5stack/libs/unit/miniscale.py index e3d0c789..d6dd5442 100644 --- a/m5stack/libs/unit/miniscale.py +++ b/m5stack/libs/unit/miniscale.py @@ -15,7 +15,7 @@ class MiniScaleUnit: def __init__(self, i2c: I2C | PAHUBUnit, address: int | list | tuple = 0x26): self.i2c = i2c self.addr = address - self.tare = 0 + self._tare = 0 self._available() def _available(self): @@ -38,7 +38,7 @@ def weight(self) -> float: """! Get the weight in grams.""" data = self.i2c.readfrom_mem(self.addr, 0x10, 4) result = struct.unpack(" bool: @@ -48,7 +48,7 @@ def button(self) -> bool: def tare(self): """! Tare the scale.""" - self.tare = self.weight + self._tare = self.weight def set_led(self, r: int, g: int, b: int): """! Set the RGB LED color. From c673c251392d47d666087c285cd3f76524df6425 Mon Sep 17 00:00:00 2001 From: hlym123 Date: Mon, 3 Nov 2025 12:30:10 +0800 Subject: [PATCH 303/322] boards: Restore mistakenly deleted M5STACK_Atom_EchoS3R. Signed-off-by: hlym123 --- m5stack/Makefile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/m5stack/Makefile b/m5stack/Makefile index df6fabb8..3f2efc8b 100644 --- a/m5stack/Makefile +++ b/m5stack/Makefile @@ -41,6 +41,7 @@ boards := \ M5STACK_Atom_Echo:atomecho \ M5STACK_AtomS3R:atoms3r \ M5STACK_AtomS3R_CAM:atoms3r_cam \ + M5STACK_Atom_EchoS3R:atom_echos3r\ M5STACK_StamPLC:stamplc \ M5STACK_Tab5:tab5 \ M5STACK_CardputerADV:cardputeradv\ @@ -83,6 +84,7 @@ BOARD_TYPE_DEF := \ atomecho \ atoms3r \ atoms3r_cam \ + atom_echos3r\ stamplc \ tab5 \ cardputeradv\ From 01119e6e0ad1ad8ac641e7bb822b9443a8a07ced Mon Sep 17 00:00:00 2001 From: tinyu Date: Tue, 4 Nov 2025 09:16:22 +0800 Subject: [PATCH 304/322] libs/driver: Fix ATGM336H driver bugs. Signed-off-by: tinyu --- m5stack/libs/driver/atgm336h.py | 39 +++++++++++++++++---------------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/m5stack/libs/driver/atgm336h.py b/m5stack/libs/driver/atgm336h.py index 5d50cb16..ee82ce04 100644 --- a/m5stack/libs/driver/atgm336h.py +++ b/m5stack/libs/driver/atgm336h.py @@ -35,9 +35,9 @@ class ATGM336H: def __init__(self, id: Literal[0, 1, 2], tx: int, rx: int, pps=None, rst=None, verbose=False): self.mode = 0 self.antenna_state = "0" - self.gps_time = ["00", "00", "00"] - self.gps_date = ["00", "00", "00"] - self.gps_date_time = ["00", "00", "00", "00", "00", "00"] + self.utc_time = ["00", "00", "00"] + self.utc_date = ["00", "00", "00"] + self.utc_date_time = ["00", "00", "00", "00", "00", "00"] self.timestamp = 0 self.latitude = "0N" self.longitude = "0E" @@ -47,6 +47,7 @@ def __init__(self, id: Literal[0, 1, 2], tx: int, rx: int, pps=None, rst=None, v self.course_ground_degree = "0" self.speed_ground_knot = "0" self.time_offset = 0 + self.local_dt = ["00", "00", "00", "00", "00", "00"] self.verbose = verbose self.rst = rst if self.rst: @@ -131,13 +132,13 @@ def get_gps_time(self): gps_0.get_gps_time() """ - return self.gps_time + return self.local_dt[3:6] def get_gps_date(self): """ Get the current GPS date. - :returns: The GPS date as a list of strings [day, month, year]. + :returns: The GPS date as a list of strings [year, month, day]. :rtype: list[str] UiFlow2 Code Block: @@ -150,7 +151,7 @@ def get_gps_date(self): gps_0.get_gps_date() """ - return self.gps_date + return self.local_dt[0:3] def get_gps_date_time(self): """ @@ -169,7 +170,7 @@ def get_gps_date_time(self): gps_0.get_gps_date_time() """ - return self.gps_date_time + return self.get_gps_date() + self.get_gps_time() def get_timestamp(self) -> int | float: """ @@ -402,8 +403,8 @@ def _decode_rmc(self, data: str): gps_list = data.split(",") if gps_list[2] == "A": time_buf = gps_list[1] - self.gps_time = [ - int(time_buf[0:2]) + self.time_offset, + self.utc_time = [ + int(time_buf[0:2]), int(time_buf[2:4]), int(time_buf[4:6]), ] @@ -412,20 +413,20 @@ def _decode_rmc(self, data: str): self.speed_ground_knot = gps_list[7] self.course_ground_degree = gps_list[8] data_buf = gps_list[9] - self.gps_date = [int(data_buf[4:7]) + 2000, int(data_buf[2:4]), int(data_buf[0:2])] + self.utc_date = [int(data_buf[4:7]) + 2000, int(data_buf[2:4]), int(data_buf[0:2])] t = ( - self.gps_date[0], - self.gps_date[1], - self.gps_date[2], - self.gps_time[0] - self.time_offset, - self.gps_time[1], - self.gps_time[2], + self.utc_date[0], + self.utc_date[1], + self.utc_date[2], + self.utc_time[0], + self.utc_time[1], + self.utc_time[2], 0, 0, ) - self.gps_date_time = self.gps_date + self.gps_time - buf = time.mktime(t) - self.timestamp = buf + self.utc_date_time = self.utc_date + self.utc_time + self.timestamp = time.mktime(t) + self.local_dt = time.localtime(self.timestamp + self.time_offset * 3600) def _convert_to_decimal(self, degrees_minutes, direction, latitude: bool = True) -> float: if latitude: From c8be99a3737f5a644158b56dab34a5467f360552 Mon Sep 17 00:00:00 2001 From: hlym123 Date: Wed, 5 Nov 2025 18:34:58 +0800 Subject: [PATCH 305/322] libs/module: Add support for CC1101 Module. Signed-off-by: hlym123 --- docs/en/module/cc1101.rst | 426 +++++++ docs/en/module/index.rst | 1 + docs/en/refs/module.cc1101.ref | 48 + .../zh_CN/LC_MESSAGES/module/cc1101.po | 539 +++++++++ .../cc1101/m5cores3_cc1101_rx_example.m5f2 | 1 + .../cc1101/m5cores3_cc1101_rx_example.py | 144 +++ .../cc1101/m5cores3_cc1101_tx_example.m5f2 | 1 + .../cc1101/m5cores3_cc1101_tx_example.py | 184 +++ m5stack/libs/driver/cc1101.py | 1000 +++++++++++++++++ m5stack/libs/driver/manifest.py | 1 + m5stack/libs/module/__init__.py | 1 + m5stack/libs/module/cc1101.py | 485 ++++++++ m5stack/libs/module/manifest.py | 1 + 13 files changed, 2832 insertions(+) create mode 100644 docs/en/module/cc1101.rst create mode 100644 docs/en/refs/module.cc1101.ref create mode 100644 docs/locales/zh_CN/LC_MESSAGES/module/cc1101.po create mode 100644 examples/module/cc1101/m5cores3_cc1101_rx_example.m5f2 create mode 100644 examples/module/cc1101/m5cores3_cc1101_rx_example.py create mode 100644 examples/module/cc1101/m5cores3_cc1101_tx_example.m5f2 create mode 100644 examples/module/cc1101/m5cores3_cc1101_tx_example.py create mode 100644 m5stack/libs/driver/cc1101.py create mode 100644 m5stack/libs/module/cc1101.py diff --git a/docs/en/module/cc1101.rst b/docs/en/module/cc1101.rst new file mode 100644 index 00000000..ff60e0df --- /dev/null +++ b/docs/en/module/cc1101.rst @@ -0,0 +1,426 @@ +CC1101 Module +============= + +.. sku: + +.. include:: ../refs/module.cc1101.ref + +This library is the driver for Module CC1101, a low-cost sub-1 GHz transceiver designed for very low-power wireless applications. It operates in the 855-928 MHz frequency bands and provides excellent performance at very low current consumption. + +The module features a highly configurable baseband modem supporting various modulation formats and has a flexible data rate from 0.6 to 6.0 kbps. It includes built-in support for packet handling, data buffering, burst transmissions, clear channel assessment, link quality indication, and wake-on-radio functionality. + +Support the following products: + + |CC1101Module| + +UiFlow2 Example +--------------- + +Transmit Data +^^^^^^^^^^^^^ + +Open the |m5cores3_cc1101_tx_example.m5f2| project in UiFlow2. + +This example shows how to send data using the CC1101 module. The module will continuously send messages with incrementing counter values. + +UiFlow2 Code Block: + + |example_tx.png| + +Example output: + + None + +Receive Data with IRQ +^^^^^^^^^^^^^^^^^^^^^ + +Open the |m5cores3_cc1101_rx_example.m5f2| project in UiFlow2. + +This example shows how to receive data using interrupt-based reception. The module will display received messages, RSSI values on the screen. + +UiFlow2 Code Block: + + |example_rx.png| + +Example output: + + None + +MicroPython Example +------------------- + +Transmit Data +^^^^^^^^^^^^^ + +This example shows how to send data using the CC1101 module. The module continuously sends messages with incrementing counter values every second. + +MicroPython Code Block: + + .. literalinclude:: ../../../examples/module/cc1101/m5cores3_cc1101_tx_example.py + :language: python + :linenos: + +Example output: + + None + +Receive Data with IRQ +^^^^^^^^^^^^^^^^^^^^^ + +This example shows how to receive data using interrupt-based reception. The module will display received messages, RSSI values on the screen. + +MicroPython Code Block: + + .. literalinclude:: ../../../examples/module/cc1101/m5cores3_cc1101_rx_example.py + :language: python + :linenos: + +Example output: + + None + + +**API** +------- + +class CC1101Module +^^^^^^^^^^^^^^^^^^ + +.. class:: module.cc1101.CC1101Module(pin_cs = 5, \ + pin_gdo0 = 7, \ + pin_gdo2 = 10, \ + freq_khz = 868000, \ + bitrate_kbps = 2.4, \ + freq_dev_khz = 25.4, \ + rx_bw_khz = 58.0, \ + output_power = 10, \ + preamble_length = 16, \ + sync_word_h = 0x12, \ + sync_word_l = 0xAD) + + Create a CC1101Module object. + + :param int pin_cs: (CS) Chip select pin number. + :param int pin_gdo0: (GDO0) Interrupt pin number. + :param int pin_gdo2: (GDO2) Optional interrupt pin number. + :param int freq_khz: CC1101 RF frequency in kHz, with a range of 855000 kHz to 928000 kHz. + :param float bitrate_kbps: Data rate in kbps, range from 0.6 to 6.0 kbps. + :param float freq_dev_khz: Frequency deviation in kHz, range from 1.6 to 380 kHz. + :param float rx_bw_khz: Receiver bandwidth in kHz, range from 58 to 812 kHz. + :param int output_power: Output power in dBm, range from -30 to 10 dBm. + :param int preamble_length: Preamble length in bits, options: 16, 24, 32, 48, 64, 96, 128, 192. + :param int sync_word_h: High byte of sync word (0x00 to 0xFF). + :param int sync_word_l: Low byte of sync word (0x00 to 0xFF). + + UiFlow2 Code Block: + + |init.png| + + MicroPython Code Block: + + .. code-block:: python + + from module import CC1101Module + + module_cc1101_0 = CC1101Module(5, 7, 10, 868000, 2.4, 25.4, 58.0, 10, 16, 0x12, 0xAD) + + .. method:: set_freq(freq_khz) + + Set frequency in kHz. + + :param int freq_khz: Frequency in kHz (855000 ~ 928000), default is 868000. + + UiFlow2 Code Block: + + |set_freq.png| + + MicroPython Code Block: + + .. code-block:: python + + module_cc1101_0.set_freq(868000) + + .. method:: set_bitrate(bitrate_kbps) + + Set data rate in kbps. + + :param float bitrate_kbps: Data rate in kbps (0.6 ~ 6.0) + + UiFlow2 Code Block: + + |set_bitrate.png| + + MicroPython Code Block: + + .. code-block:: python + + module_cc1101_0.set_bitrate(2.4) + + .. method:: set_freq_dev(freq_dev_khz) + + Set frequency deviation in kHz. + + :param float freq_dev_khz: Frequency deviation in kHz (1.6 ~ 380) + + UiFlow2 Code Block: + + |set_freq_dev.png| + + MicroPython Code Block: + + .. code-block:: python + + module_cc1101_0.set_freq_dev(25.4) + + .. method:: set_rx_bw(rx_bw_khz) + + Set receiver bandwidth in kHz. + + :param float rx_bw_khz: Receiver bandwidth in kHz (58 ~ 812) + + UiFlow2 Code Block: + + |set_rx_bw.png| + + MicroPython Code Block: + + .. code-block:: python + + module_cc1101_0.set_rx_bw(58.0) + + .. method:: set_output_power(output_power) + + Set output power in dBm. + + :param int output_power: Output power in dBm (-30 ~ 10) + + UiFlow2 Code Block: + + |set_output_power.png| + + MicroPython Code Block: + + .. code-block:: python + + module_cc1101_0.set_output_power(10) + + .. method:: set_preamble_length(preamble_length) + + Set preamble length in bits. + + :param int preamble_length: Preamble length in bits, must be one of: + 16, 24, 32, 48, 64, 96, 128, 192. + + UiFlow2 Code Block: + + |set_preamble_length.png| + + MicroPython Code Block: + + .. code-block:: python + + module_cc1101_0.set_preamble_length(16) + + .. method:: set_sync_word(sync_word_h, sync_word_l) + + Set sync word. + + :param int sync_word_h: High byte of sync word (0 ~ 0xFF) + :param int sync_word_l: Low byte of sync word (0 ~ 0xFF) + + UiFlow2 Code Block: + + |set_sync_word.png| + + MicroPython Code Block: + + .. code-block:: python + + module_cc1101_0.set_sync_word(0x12, 0xAD) + + .. method:: send(packet) + + Send data. + + :param str | list | tuple | int | bytearray packet: The data to be sent. + :returns: True if successful, False otherwise + :rtype: bool + + Send a data packet and return True if successful. + + UiFlow2 Code Block: + + |send.png| + + MicroPython Code Block: + + .. code-block:: python + + module_cc1101_0.send("Hello World") + + .. method:: recv(timeout_ms) + + Receive data. + + :param int timeout_ms: Timeout in milliseconds (optional). Default is None. + :returns: Received packet instance or None if timeout + :rtype: CC1101Packet | None + + Attempt to receive a CC1101 packet. Returns `None` if timeout occurs, or returns the received packet instance. + + UiFlow2 Code Block: + + |recv.png| + + MicroPython Code Block: + + .. code-block:: python + + packet = module_cc1101_0.recv() + if packet: + if packet.crc_ok: + print(f"Received: {packet.decode()}") + print(f"RSSI: {packet.rssi} dBm") + print(f"LQI: {packet.lqi}") + else: + print("CRC error") + + .. method:: start_recv() + + Start receive data. + + This method initiates the process to begin receiving data. + + UiFlow2 Code Block: + + |start_recv.png| + + MicroPython Code Block: + + .. code-block:: python + + module_cc1101_0.start_recv() + + .. method:: set_rx_irq_callback(callback) + + Set the receive interrupt callback function to be executed on IRQ. + + :param callback: The callback function to be invoked when the interrupt is triggered. + The callback should take a CC1101Packet parameter and return nothing. + + UiFlow2 Code Block: + + |set_rx_irq_callback.png| + + MicroPython Code Block: + + .. code-block:: python + + def on_packet_received(packet): + print(f"Received: {packet.decode()}") + + module_cc1101_0.set_rx_irq_callback(on_packet_received) + + .. method:: set_tx_irq_callback(callback) + + Set the transmit interrupt callback function to be executed on IRQ. + + :param callback: The callback function to be invoked when the interrupt is triggered. + The callback should take one parameter (pin object, can be ignored) and return nothing. + + UiFlow2 Code Block: + + |set_tx_irq_callback.png| + + MicroPython Code Block: + + .. code-block:: python + + def on_packet_sent(_): + print("Packet sent successfully") + + module_cc1101_0.set_tx_irq_callback(on_packet_sent) + + .. method:: standby() + + Set module to standby mode. + + Puts the CC1101 module into standby mode, consuming less power. + + UiFlow2 Code Block: + + |standby.png| + + MicroPython Code Block: + + .. code-block:: python + + module_cc1101_0.standby() + + .. method:: rx_irq_triggered() + + Check RX IRQ trigger. + + :returns: Returns `True` if an interrupt service routine (ISR) has been triggered since the last send or receive started. + :rtype: bool + + UiFlow2 Code Block: + + |rx_irq_triggered.png| + + MicroPython Code Block: + + .. code-block:: python + + module_cc1101_0.rx_irq_triggered() + + .. method:: tx_irq_triggered() + + Check TX IRQ trigger. + + :returns: Returns `True` if an interrupt service routine (ISR) has been triggered since the last send or a receive started. + :rtype: bool + + UiFlow2 Code Block: + + |tx_irq_triggered.png| + + MicroPython Code Block: + + .. code-block:: python + + module_cc1101_0.tx_irq_triggered() + + +.. _cc1101_packet: + +class CC1101Packet +^^^^^^^^^^^^^^^^^^ + +.. class:: driver.cc1101.CC1101Packet() + + Create a CC1101Packet object. + + .. method:: decode() + + Decode the received data. + + :returns: Decoded data as string + :rtype: str + + .. method:: data + + Raw packet data. + + .. method:: rssi + + Received signal strength (units: dBm). + + .. method:: lqi + + Link Quality Indicator (0-127). + + .. method:: crc_ok + + CRC validity check. Returns True if CRC is valid, False otherwise. + diff --git a/docs/en/module/index.rst b/docs/en/module/index.rst index 1aabf962..4378cf0f 100644 --- a/docs/en/module/index.rst +++ b/docs/en/module/index.rst @@ -8,6 +8,7 @@ Module ain4.rst audio.rst bala2.rst + cc1101.rst commu.rst dc_motor.rst display.rst diff --git a/docs/en/refs/module.cc1101.ref b/docs/en/refs/module.cc1101.ref new file mode 100644 index 00000000..a4e39750 --- /dev/null +++ b/docs/en/refs/module.cc1101.ref @@ -0,0 +1,48 @@ + +.. |CC1101Module| image:: https://m5stack-doc.oss-cn-shenzhen.aliyuncs.com/1188/M146-main-pictures_01.webp + :target: https://docs.m5stack.com/en/products/sku/M146 + :height: 200px + :width: 200px + +.. |init.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/cc1101/init.png +.. |init_advanced.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/cc1101/init_advanced.png +.. |set_freq.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/cc1101/set_freq.png +.. |set_bitrate.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/cc1101/set_bitrate.png +.. |set_freq_dev.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/cc1101/set_freq_dev.png +.. |set_rx_bw.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/cc1101/set_rx_bw.png +.. |set_output_power.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/cc1101/set_output_power.png +.. |set_preamble_length.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/cc1101/set_preamble_length.png +.. |set_sync_word.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/cc1101/set_sync_word.png +.. |send.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/cc1101/send.png +.. |send_event.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/cc1101/send_event.png +.. |recv.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/cc1101/receive.png +.. |recv_data_param.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/cc1101/recv_data_param.png +.. |start_recv.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/cc1101/start_recv.png +.. |set_rx_irq_callback.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/cc1101/receive_event.png +.. |set_tx_irq_callback.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/cc1101/send_event.png +.. |standby.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/cc1101/standby.png +.. |rx_irq_triggered.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/cc1101/rx_irq_triggered.png +.. |tx_irq_triggered.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/cc1101/tx_irq_triggered.png + + +.. |example_tx.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/cc1101/tx_example.png +.. |example_rx.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/cc1101/rx_example.png + +.. |m5cores3_cc1101_rx_example.m5f2| raw:: html + + + m5cores3_cc1101_rx_example.m5f2 + + +.. |m5cores3_cc1101_tx_example.m5f2| raw:: html + + + m5cores3_cc1101_tx_example.m5f2 + + diff --git a/docs/locales/zh_CN/LC_MESSAGES/module/cc1101.po b/docs/locales/zh_CN/LC_MESSAGES/module/cc1101.po new file mode 100644 index 00000000..39683d67 --- /dev/null +++ b/docs/locales/zh_CN/LC_MESSAGES/module/cc1101.po @@ -0,0 +1,539 @@ +# Translations template for PROJECT. +# Copyright (C) 2025 ORGANIZATION +# This file is distributed under the same license as the PROJECT project. +# FIRST AUTHOR , 2025. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PROJECT VERSION\n" +"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" +"POT-Creation-Date: 2025-11-05 17:55+0800\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.16.0\n" + +#: ../../en/module/cc1101.rst:2 d1f1a682f17a4c5095849f65d45e58c1 +msgid "CC1101 Module" +msgstr "" + +#: ../../en/module/cc1101.rst:8 946f41e1f8144857b296742c25cdaf37 +msgid "" +"This library is the driver for Module CC1101, a low-cost sub-1 GHz " +"transceiver designed for very low-power wireless applications. It " +"operates in the 855-928 MHz frequency bands and provides excellent " +"performance at very low current consumption." +msgstr "" +"这个库是 CC1101 模块的驱动,CC1101 是一款专为超低功耗无线应用设计的低成本亚 1GHz 收发器。它工作在 855-928 MHz " +"频段,在极低电流消耗下提供出色的性能。" + +#: ../../en/module/cc1101.rst:10 962c21ea394a4c3884210912599fb52b +msgid "" +"The module features a highly configurable baseband modem supporting " +"various modulation formats and has a flexible data rate from 0.6 to 6.0 " +"kbps. It includes built-in support for packet handling, data buffering, " +"burst transmissions, clear channel assessment, link quality indication, " +"and wake-on-radio functionality." +msgstr "" +"该模块具有高度可配置的基带调制解调器,支持各种调制格式,数据速率灵活,从 0.6 到 6.0 " +"kbps。它内置支持数据包处理、数据缓冲、突发传输、空闲信道评估、链路质量指示和无线电唤醒功能。" + +#: ../../en/module/cc1101.rst:12 97e04bb69b5b45fca88ba311a59538d8 +msgid "Support the following products:" +msgstr "支持以下产品:" + +#: ../../en/module/cc1101.rst:14 c81cf074dacf45e49d057bf427b43ccd +msgid "|CC1101Module|" +msgstr "" + +#: ../../en/refs/module.cc1101.ref 2775b7e41af54e7ebed96127d49ae530 +msgid "CC1101Module" +msgstr "" + +#: ../../en/module/cc1101.rst:17 4331382208c34ea58e66c9d3fa13ef72 +msgid "UiFlow2 Example" +msgstr "UiFlow2 应用示例" + +#: ../../en/module/cc1101.rst:20 ../../en/module/cc1101.rst:53 +#: 04849a66001641dbaec71509983a546c adb7cb51cf1c43258772b7c63a38534e +msgid "Transmit Data" +msgstr "发送数据" + +#: ../../en/module/cc1101.rst:22 e211aada72a74ab097ac6f6b151abc03 +msgid "Open the |m5cores3_cc1101_tx_example.m5f2| project in UiFlow2." +msgstr "在 UiFlow2 中打开 |m5cores3_cc1101_tx_example.m5f2| 项目。" + +#: ../../en/module/cc1101.rst:24 beccb90a19064826bada7d836919c079 +msgid "" +"This example shows how to send data using the CC1101 module. The module " +"will continuously send messages with incrementing counter values." +msgstr "此示例展示如何使用 CC1101 模块发送数据。模块将持续发送带有递增计数器值的消息。" + +#: ../../en/module/cc1101.rst:26 ../../en/module/cc1101.rst:41 +#: ../../en/module/cc1101.rst:115 ../../en/module/cc1101.rst:133 +#: ../../en/module/cc1101.rst:149 ../../en/module/cc1101.rst:165 +#: ../../en/module/cc1101.rst:181 ../../en/module/cc1101.rst:197 +#: ../../en/module/cc1101.rst:214 ../../en/module/cc1101.rst:231 +#: ../../en/module/cc1101.rst:251 ../../en/module/cc1101.rst:271 +#: ../../en/module/cc1101.rst:294 ../../en/module/cc1101.rst:311 +#: ../../en/module/cc1101.rst:331 ../../en/module/cc1101.rst:350 +#: ../../en/module/cc1101.rst:367 ../../en/module/cc1101.rst:384 +#: 2d4d397991f5440aa6f078fbb6168d34 536270ba0d92466d9f48206ecd856df4 +#: edc307ea4ffa4c55948f7bd8a53d3fa5 +msgid "UiFlow2 Code Block:" +msgstr "UiFlow2 代码块:" + +#: ../../en/module/cc1101.rst:28 af0b87440ec4445f974fb25be1cc1bef +msgid "|example_tx.png|" +msgstr "" + +#: ../../en/refs/module.cc1101.ref:28 e9886944fdcf4249821ed9034af8ca20 +msgid "example_tx.png" +msgstr "" + +#: ../../en/module/cc1101.rst:30 ../../en/module/cc1101.rst:45 +#: ../../en/module/cc1101.rst:63 ../../en/module/cc1101.rst:78 +#: 5bc48cf0b35c45cfab22a5767a49d187 b53e3a608c3f4710b66902940bb0251e +#: c5f4f78256634a5e8b74aed08847c98f f638dfc358f842b997b87e70edaecb88 +msgid "Example output:" +msgstr "示例输出:" + +#: ../../en/module/cc1101.rst:32 ../../en/module/cc1101.rst:47 +#: ../../en/module/cc1101.rst:65 ../../en/module/cc1101.rst:80 +#: 2c9d2fe69d39498388887d32e84a53fe 39d2081c7ac74eeab9d74e69dc4f8e37 +#: a280355386224aac935f08441af9a239 e355f48405fb491285e60e50b5855c18 +msgid "None" +msgstr "无" + +#: ../../en/module/cc1101.rst:35 ../../en/module/cc1101.rst:68 +#: ab7263d9e6034f9eb1e51076c2fcdfac b5e22b93a6b94c5ebf4e3db99cfe8d3d +msgid "Receive Data with IRQ" +msgstr "中断接收数据" + +#: ../../en/module/cc1101.rst:37 587b7f20496f4e3cb3bdcdccb3b94f84 +msgid "Open the |m5cores3_cc1101_rx_example.m5f2| project in UiFlow2." +msgstr "在 UiFlow2 中打开 |m5cores3_cc1101_rx_example.m5f2| 项目。" + +#: ../../en/module/cc1101.rst:39 ../../en/module/cc1101.rst:70 +#: bd21d6151487497483f452ffdcb108dd +msgid "" +"This example shows how to receive data using interrupt-based reception. " +"The module will display received messages, RSSI values on the screen." +msgstr "此示例展示如何使用基于中断的接收方式接收数据。模块将在屏幕上显示接收到的消息和 RSSI 值。" + +#: ../../en/module/cc1101.rst:43 acdb3d38ccab42af99ba8fbe1dcd5c11 +msgid "|example_rx.png|" +msgstr "" + +#: ../../en/refs/module.cc1101.ref:29 98074581c62848bba62ea427e4a92f37 +msgid "example_rx.png" +msgstr "" + +#: ../../en/module/cc1101.rst:50 699cae48b11948f4909f54e56e4c5fd3 +msgid "MicroPython Example" +msgstr "MicroPython 应用示例" + +#: ../../en/module/cc1101.rst:55 fdb6f39367f54a7f944ad7a66e8eed21 +msgid "" +"This example shows how to send data using the CC1101 module. The module " +"continuously sends messages with incrementing counter values every " +"second." +msgstr "此示例展示如何使用 CC1101 模块发送数据。模块每秒持续发送带有递增计数器值的消息。" + +#: ../../en/module/cc1101.rst:57 ../../en/module/cc1101.rst:72 +#: ../../en/module/cc1101.rst:119 ../../en/module/cc1101.rst:137 +#: ../../en/module/cc1101.rst:153 ../../en/module/cc1101.rst:169 +#: ../../en/module/cc1101.rst:185 ../../en/module/cc1101.rst:201 +#: ../../en/module/cc1101.rst:218 ../../en/module/cc1101.rst:235 +#: ../../en/module/cc1101.rst:255 ../../en/module/cc1101.rst:275 +#: ../../en/module/cc1101.rst:298 ../../en/module/cc1101.rst:315 +#: ../../en/module/cc1101.rst:335 ../../en/module/cc1101.rst:354 +#: ../../en/module/cc1101.rst:371 ../../en/module/cc1101.rst:388 +#: 191f19bec9024d76817b89c356532760 2ceb7b78c31248eb94dfff61e95bd938 +#: bd0109e3e09e47ca80a40351cea51529 +msgid "MicroPython Code Block:" +msgstr "MicroPython 代码块:" + +#: ../../en/module/cc1101.rst:84 d98cbb7b55164c77966ad951e4781386 +msgid "**API**" +msgstr "**API**" + +#: ../../en/module/cc1101.rst:87 554063c82c684452a9627ae99be1589e +msgid "class CC1101Module" +msgstr "class CC1101Module" + +#: ../../en/module/cc1101.rst:101 084810f5ba6849d4ba9110b68df6ae23 +msgid "Create a CC1101Module object." +msgstr "创建 CC1101Module 对象。" + +#: ../../en/module/cc1101.rst 0b47cbfefd024ab59db6c0577d40d457 +msgid "Parameters" +msgstr "参数" + +#: ../../en/module/cc1101.rst:103 10f477898264457987d14bbb9692646e +msgid "(CS) Chip select pin number." +msgstr "(CS)片选引脚号。" + +#: ../../en/module/cc1101.rst:104 4c31eec1acc042b39bf75512cfc69f9e +msgid "(GDO0) Interrupt pin number." +msgstr "(GDO0)中断引脚号。" + +#: ../../en/module/cc1101.rst:105 4aa7fcb077454cdd999600beeae12369 +msgid "(GDO2) Optional interrupt pin number." +msgstr "(GDO2)可选中断引脚号。" + +#: ../../en/module/cc1101.rst:106 71a0be9003b244c2a537915319d2eee5 +msgid "CC1101 RF frequency in kHz, with a range of 855000 kHz to 928000 kHz." +msgstr "CC1101 射频频率(kHz),范围:855000 到 928000 kHz。" + +#: ../../en/module/cc1101.rst:107 2ac02e442e114c069fd697728ac05494 +msgid "Data rate in kbps, range from 0.6 to 6.0 kbps." +msgstr "数据速率(kbps),范围:0.6 到 6.0 kbps。" + +#: ../../en/module/cc1101.rst:108 50ceff95fea249b5bb1b3215b3466246 +msgid "Frequency deviation in kHz, range from 1.6 to 380 kHz." +msgstr "频率偏差(kHz),范围:1.6 到 380 kHz。" + +#: ../../en/module/cc1101.rst:109 d9d2e2145c484124bee757e8d03aa19c +msgid "Receiver bandwidth in kHz, range from 58 to 812 kHz." +msgstr "接收带宽(kHz),范围:58 到 812 kHz。" + +#: ../../en/module/cc1101.rst:110 ddaeab4097ab4c0db161b746311ea9f3 +msgid "Output power in dBm, range from -30 to 10 dBm." +msgstr "输出功率(dBm),范围:-30 到 10 dBm。" + +#: ../../en/module/cc1101.rst:111 09b3de0fa3c6405fb770f494dd59eb5d +msgid "Preamble length in bits, options: 16, 24, 32, 48, 64, 96, 128, 192." +msgstr "前导码长度(位),选项:16、24、32、48、64、96、128、192。" + +#: ../../en/module/cc1101.rst:112 2198b195751f476d9267787c10f0207f +msgid "High byte of sync word (0x00 to 0xFF)." +msgstr "同步字高字节(0x00 到 0xFF)。" + +#: ../../en/module/cc1101.rst:113 ea809b7fcdff46f1a59f96d2cd3e1d05 +msgid "Low byte of sync word (0x00 to 0xFF)." +msgstr "同步字低字节(0x00 到 0xFF)。" + +#: ../../en/module/cc1101.rst:117 281db6cefe7a4127a9995a13b60cf373 +msgid "|init.png|" +msgstr "" + +#: ../../en/refs/module.cc1101.ref:7 1e5cded8d98f4924b3c57dab8eccad51 +msgid "init.png" +msgstr "" + +#: ../../en/module/cc1101.rst:129 6aa9a10955484bb684a57e17a66c5ab3 +msgid "Set frequency in kHz." +msgstr "设置频率(kHz)。" + +#: ../../en/module/cc1101.rst:131 4512e21dc1cf4451b7cd60fd59758e4b +msgid "Frequency in kHz (855000 ~ 928000), default is 868000." +msgstr "频率(kHz)(855000 ~ 928000),默认值为 868000。" + +#: ../../en/module/cc1101.rst:135 0e11d0575a034efdbae3c657e43f6691 +msgid "|set_freq.png|" +msgstr "" + +#: ../../en/refs/module.cc1101.ref:9 bf66b75cc3874e1c843b8b93f19e50d5 +msgid "set_freq.png" +msgstr "" + +#: ../../en/module/cc1101.rst:145 ce52eac324034f8eb0fd66927a46446d +msgid "Set data rate in kbps." +msgstr "设置数据速率(kbps)。" + +#: ../../en/module/cc1101.rst:147 2de73550c271490d907bfe2a0b1a157c +msgid "Data rate in kbps (0.6 ~ 6.0)" +msgstr "数据速率(kbps)(0.6 ~ 6.0)" + +#: ../../en/module/cc1101.rst:151 238ce7b49d09433183bafd8627419687 +msgid "|set_bitrate.png|" +msgstr "" + +#: ../../en/refs/module.cc1101.ref:10 261189f77b894de196d6faab1d1df432 +msgid "set_bitrate.png" +msgstr "" + +#: ../../en/module/cc1101.rst:161 69042acf75564c2ba9f2b0527527c25e +msgid "Set frequency deviation in kHz." +msgstr "设置频率偏差(kHz)。" + +#: ../../en/module/cc1101.rst:163 3db114d833f647e78f2cc4df73940f3f +msgid "Frequency deviation in kHz (1.6 ~ 380)" +msgstr "频率偏差(kHz)(1.6 ~ 380)" + +#: ../../en/module/cc1101.rst:167 119e2e386fa042eaad67ce4ac3a61f7f +msgid "|set_freq_dev.png|" +msgstr "" + +#: ../../en/refs/module.cc1101.ref:11 da6a0793d07a47318639db1ee11b42c0 +msgid "set_freq_dev.png" +msgstr "" + +#: ../../en/module/cc1101.rst:177 f6c0204ce56a4ee788baf525527b48bb +msgid "Set receiver bandwidth in kHz." +msgstr "设置接收带宽(kHz)。" + +#: ../../en/module/cc1101.rst:179 fe6f4d940e924b9988de2fd9669402c1 +msgid "Receiver bandwidth in kHz (58 ~ 812)" +msgstr "接收带宽(kHz)(58 ~ 812)" + +#: ../../en/module/cc1101.rst:183 2e9703f78b204cd7955e0e847325f805 +msgid "|set_rx_bw.png|" +msgstr "" + +#: ../../en/refs/module.cc1101.ref:12 6fa23cec08b94e10b8f356d171190ba3 +msgid "set_rx_bw.png" +msgstr "" + +#: ../../en/module/cc1101.rst:193 f9c4fe58d40f4cf5b34c4ee14ff2377a +msgid "Set output power in dBm." +msgstr "设置输出功率(dBm)。" + +#: ../../en/module/cc1101.rst:195 491b9d9137c4421597ed291a2b5c2cfb +msgid "Output power in dBm (-30 ~ 10)" +msgstr "输出功率(dBm)(-30 ~ 10)" + +#: ../../en/module/cc1101.rst:199 2094c7d2882e457eb4e8feb111bb915e +msgid "|set_output_power.png|" +msgstr "" + +#: ../../en/refs/module.cc1101.ref:13 c302bb9495cb42bc84d6c8b895352fa3 +msgid "set_output_power.png" +msgstr "" + +#: ../../en/module/cc1101.rst:209 60f1371996e440e2a800949aa1b7c5e9 +msgid "Set preamble length in bits." +msgstr "设置前导码长度(位)。" + +#: ../../en/module/cc1101.rst:211 59024c36d0064bea98baa2b2df534af7 +msgid "Preamble length in bits, must be one of: 16, 24, 32, 48, 64, 96, 128, 192." +msgstr "前导码长度(位),必须是以下之一:16、24、32、48、64、96、128、192。" + +#: ../../en/module/cc1101.rst:216 857e6f70f9134888b08010dfd7d7186e +msgid "|set_preamble_length.png|" +msgstr "" + +#: ../../en/refs/module.cc1101.ref:14 a164f8fbd364495188edf54604df7c9e +msgid "set_preamble_length.png" +msgstr "" + +#: ../../en/module/cc1101.rst:226 a7e8afe331ac495e9920deb21a316d4c +msgid "Set sync word." +msgstr "设置同步字。" + +#: ../../en/module/cc1101.rst:228 4ffd1840ae1840d4bf0dce5da8d8cb29 +msgid "High byte of sync word (0 ~ 0xFF)" +msgstr "同步字高字节(0 ~ 0xFF)" + +#: ../../en/module/cc1101.rst:229 a292878197b845cfbb31d4ed0509d67b +msgid "Low byte of sync word (0 ~ 0xFF)" +msgstr "同步字低字节(0 ~ 0xFF)" + +#: ../../en/module/cc1101.rst:233 d2ed17b5b0d140b7ab49f69ccbd29828 +msgid "|set_sync_word.png|" +msgstr "" + +#: ../../en/refs/module.cc1101.ref:15 a0956d89d07b47d784b7ff1c86e31104 +msgid "set_sync_word.png" +msgstr "" + +#: ../../en/module/cc1101.rst:243 95266ea15dcc4b348f03a822379fdb0d +msgid "Send data." +msgstr "发送数据。" + +#: ../../en/module/cc1101.rst:245 d746869a02cb447f82b5765fa41068df +msgid "The data to be sent." +msgstr "要发送的数据。" + +#: ../../en/module/cc1101.rst e71f6295ff9b46b1be165d462511376f +msgid "Returns" +msgstr "返回值" + +#: ../../en/module/cc1101.rst:246 530687bfbc6f40cdb8189d92c8d7359c +msgid "True if successful, False otherwise" +msgstr "成功返回 True,否则返回 False" + +#: ../../en/module/cc1101.rst 4082ffbbac634a6da4bbbf68a65fd254 +#: fceea7d05ddc410c83bce3fd42330380 +msgid "Return type" +msgstr "返回类型" + +#: ../../en/module/cc1101.rst:249 b86124ff264848d7ac9e67f184abf3e6 +msgid "Send a data packet and return True if successful." +msgstr "发送数据包,成功时返回 True。" + +#: ../../en/module/cc1101.rst:253 014f3fc81e43483b9f0a59b4b8a2aec2 +msgid "|send.png|" +msgstr "" + +#: ../../en/refs/module.cc1101.ref:16 2e7605dc8f88457098024affaf66e8d6 +msgid "send.png" +msgstr "" + +#: ../../en/module/cc1101.rst:263 0fcab33638444a81b6bd2e71436b1115 +msgid "Receive data." +msgstr "接收数据。" + +#: ../../en/module/cc1101.rst:265 a246b2dd02e74f1c89423a7853d04e32 +msgid "Timeout in milliseconds (optional). Default is None." +msgstr "超时时间(毫秒)(可选)。默认为 None。" + +#: ../../en/module/cc1101.rst:266 71ab196caf3b4b98b99245a782127c82 +msgid "Received packet instance or None if timeout" +msgstr "接收到的数据包实例或超时时返回 None" + +#: ../../en/module/cc1101.rst:269 3b3d050650104995ae384078d7f72ec2 +msgid "" +"Attempt to receive a CC1101 packet. Returns `None` if timeout occurs, or " +"returns the received packet instance." +msgstr "尝试接收 CC1101 数据包。如果发生超时则返回 `None`,否则返回接收到的数据包实例。" + +#: ../../en/module/cc1101.rst:273 fd2d9fe607f94427b7f6eaf9482857b3 +msgid "|recv.png|" +msgstr "" + +#: ../../en/refs/module.cc1101.ref:18 18fbf8c7785340b6be2bebb9745acaae +msgid "recv.png" +msgstr "" + +#: ../../en/module/cc1101.rst:290 e1eb306970a34a81bce26a7c4926cd34 +msgid "Start receive data." +msgstr "开始接收数据。" + +#: ../../en/module/cc1101.rst:292 b39416c99b08415da33070e096e99d70 +msgid "This method initiates the process to begin receiving data." +msgstr "此方法启动开始接收数据的过程。" + +#: ../../en/module/cc1101.rst:296 0d4a84c87ef443279732f61f1b39cc6b +msgid "|start_recv.png|" +msgstr "" + +#: ../../en/refs/module.cc1101.ref:20 20ee1cefebbe4826b65a715820668df8 +msgid "start_recv.png" +msgstr "" + +#: ../../en/module/cc1101.rst:306 8e920d25d9e34c9fac3103f9860798a0 +msgid "Set the receive interrupt callback function to be executed on IRQ." +msgstr "设置接收中断回调函数,在 IRQ 触发时执行。" + +#: ../../en/module/cc1101.rst:308 5f31565e49c84c7ba49b62d27fa7f9b3 +msgid "" +"The callback function to be invoked when the interrupt is triggered. The " +"callback should take a CC1101Packet parameter and return nothing." +msgstr "当中断触发时要调用的回调函数。回调函数应接受一个 CC1101Packet 参数且不返回任何值。" + +#: ../../en/module/cc1101.rst:313 cbf7132b57eb4d4d80d62b4ad315e4bf +msgid "|set_rx_irq_callback.png|" +msgstr "" + +#: ../../en/refs/module.cc1101.ref:21 9fe144c264954f24b92afd09adbea73d +msgid "set_rx_irq_callback.png" +msgstr "" + +#: ../../en/module/cc1101.rst:326 f94aa2ff77cc466dbea3b9a5d4f4bb70 +msgid "Set the transmit interrupt callback function to be executed on IRQ." +msgstr "设置发送中断回调函数,在 IRQ 触发时执行。" + +#: ../../en/module/cc1101.rst:328 6f8d7c6707ab41dd995b5ad0f2114c02 +msgid "" +"The callback function to be invoked when the interrupt is triggered. The " +"callback should take one parameter (pin object, can be ignored) and " +"return nothing." +msgstr "当中断触发时要调用的回调函数。回调函数应接受一个参数(引脚对象,可以忽略)且不返回任何值。" + +#: ../../en/module/cc1101.rst:333 271b4a427d264e81b9c970b67964d1d7 +msgid "|set_tx_irq_callback.png|" +msgstr "" + +#: ../../en/refs/module.cc1101.ref:22 fb47e94c76ec49bb9d1e7bd620470212 +msgid "set_tx_irq_callback.png" +msgstr "" + +#: ../../en/module/cc1101.rst:346 0ffca54029244636b256592a9773e16a +msgid "Set module to standby mode." +msgstr "将模块设置为待机模式。" + +#: ../../en/module/cc1101.rst:348 25d67a552dfa461ab57c80b599367c0c +msgid "Puts the CC1101 module into standby mode, consuming less power." +msgstr "将 CC1101 模块置于待机模式,消耗更少的功率。" + +#: ../../en/module/cc1101.rst:352 79ce71d2691d436db5f646f9506dc8d3 +msgid "|standby.png|" +msgstr "" + +#: ../../en/refs/module.cc1101.ref:23 7aaa92c23e3345a59d47b9112cf75803 +msgid "standby.png" +msgstr "" + +#: ../../en/module/cc1101.rst:362 4f609c3c1bb54f5c8504aee4b474bf39 +msgid "Check RX IRQ trigger." +msgstr "检查 RX IRQ 触发。" + +#: ../../en/module/cc1101.rst:364 6d6b6b674dde4a4392b3c158379a799d +msgid "" +"Returns `True` if an interrupt service routine (ISR) has been triggered " +"since the last send or receive started." +msgstr "如果自上次发送或接收开始以来已触发中断服务例程(ISR),则返回 `True`。" + +#: ../../en/module/cc1101.rst:369 271b4a427d264e81b9c970b67964d1d7 +msgid "|rx_irq_triggered.png|" +msgstr "" + +#: ../../en/refs/module.cc1101.ref:24 29ef0e7d5a664beabf98a1be0d91cf2d +msgid "rx_irq_triggered.png" +msgstr "" + +#: ../../en/module/cc1101.rst:379 4f609c3c1bb54f5c8504aee4b474bf39 +msgid "Check TX IRQ trigger." +msgstr "检查 TX IRQ 触发。" + +#: ../../en/module/cc1101.rst:381 6d6b6b674dde4a4392b3c158379a799d +msgid "" +"Returns `True` if an interrupt service routine (ISR) has been triggered " +"since the last send or a receive started." +msgstr "如果自上次发送或接收开始以来已触发中断服务例程(ISR),则返回 `True`。" + +#: ../../en/module/cc1101.rst:386 271b4a427d264e81b9c970b67964d1d7 +msgid "|tx_irq_triggered.png|" +msgstr "" + +#: ../../en/refs/module.cc1101.ref:25 29ef0e7d5a664beabf98a1be0d91cf2d +msgid "tx_irq_triggered.png" +msgstr "" + +#: ../../en/module/cc1101.rst:398 46c77bdfe3cc435ea1ec72b9d9f25a58 +msgid "class CC1101Packet" +msgstr "class CC1101Packet" + +#: ../../en/module/cc1101.rst:402 084810f5ba6849d4ba9110b68df6ae23 +msgid "Create a CC1101Packet object." +msgstr "创建 CC1101Packet 对象。" + +#: ../../en/module/cc1101.rst:406 e1eb306970a34a81bce26a7c4926cd34 +msgid "Decode the received data." +msgstr "解码接收到的数据。" + +#: ../../en/module/cc1101.rst:408 2655c391d27144f9bd153cdfad2292b4 +msgid "Decoded data as string" +msgstr "解码后的数据(字符串)" + +#: ../../en/module/cc1101.rst:413 e1eb306970a34a81bce26a7c4926cd34 +msgid "Raw packet data." +msgstr "原始数据包数据。" + +#: ../../en/module/cc1101.rst:417 71ab196caf3b4b98b99245a782127c82 +msgid "Received signal strength (units: dBm)." +msgstr "接收信号强度(单位:dBm)。" + +#: ../../en/module/cc1101.rst:421 b1a372713d0040069b4de3a12dc53c32 +msgid "Link Quality Indicator (0-127)." +msgstr "链路质量指示器(0-127)。" + +#: ../../en/module/cc1101.rst:425 6cf6866f87654d789b6e80230a05c03e +msgid "CRC validity check. Returns True if CRC is valid, False otherwise." +msgstr "CRC 有效性检查。如果 CRC 有效则返回 True,否则返回 False。" diff --git a/examples/module/cc1101/m5cores3_cc1101_rx_example.m5f2 b/examples/module/cc1101/m5cores3_cc1101_rx_example.m5f2 new file mode 100644 index 00000000..f4820000 --- /dev/null +++ b/examples/module/cc1101/m5cores3_cc1101_rx_example.m5f2 @@ -0,0 +1 @@ +{"version":"V2.0","versionNumber":"V2.3.7","type":"cores3","components":[{"name":"page0","type":"lvgl_page","layer":0,"screenId":"builtin","screenName":"","id":"q#LpFOX=v70Q=ur*","createTime":1760922306796,"backgroundColor":"#ffffff","isLVGL":true,"isSelected":true},{"name":"label_title","type":"lvgl_label","layer":1,"screenId":"builtin","screenName":"","id":"e6m7MIV`&calG`n0","createTime":1760924054039,"x":56,"y":2,"color":"#0000ff","backgroundColor":"#ffffff","bg_opacity":0,"text":"ModuleCC1101 Rx","font":"lv.font_montserrat_24","pageId":"q#LpFOX=v70Q=ur*","isLVGL":true,"isSelected":false},{"name":"label_rx","type":"lvgl_label","layer":2,"screenId":"builtin","screenName":"","id":"hSXk`y3lR6cgPor@","createTime":1760924200650,"x":5,"y":65,"color":"#0000ff","backgroundColor":"#ffffff","bg_opacity":0,"text":"Rx:","font":"lv.font_montserrat_16","pageId":"q#LpFOX=v70Q=ur*","isLVGL":true,"isSelected":false},{"name":"label_time","type":"lvgl_label","layer":3,"screenId":"builtin","screenName":"","id":"g&$gft=6^yIUKk3Z","createTime":1760924220466,"x":5,"y":210,"color":"#0000ff","backgroundColor":"#ffffff","bg_opacity":0,"text":"Timestamp:","font":"lv.font_montserrat_16","pageId":"q#LpFOX=v70Q=ur*","isLVGL":true,"isSelected":false},{"name":"label_rssi","type":"lvgl_label","layer":1,"screenId":"builtin","screenName":"","id":"c7qMJGL2qScU2o!l","createTime":1762331224121,"x":5,"y":95,"color":"#0000ff","backgroundColor":"#ffffff","bg_opacity":0,"text":"RSSI:","font":"lv.font_montserrat_16","pageId":"q#LpFOX=v70Q=ur*","isLVGL":true,"isSelected":false}],"resources":[{"hardware":["hardware_button","hardware_pin_button","imu","speaker","touch","als","mic","sdcard"]},{"module":["module_cc1101"]}],"units":[],"hats":[],"caps":[],"chains":[],"bases":[],"i2cs":[],"chainBus":[],"blockly":"cc1101_datalast_timetruepage0module_cc1101_0True5710868000325.45810160x120xADmodule_cc1101_0trueGTE11last_time1000last_timelabel_timehello M5Timestamp: module_cc1101_0cc1101_datacrc_okcc1101_datalabel_rxhello M5Rx: cc1101_dataCRC errorlabel_rssihello M5RSSI: hello M5rssicc1101_data dBm","screen":[{"simulationName":"Built-in","type":"builtin","width":320,"height":240,"scale":0.77,"screenName":"","blockId":"","screenColorType":0,"rotation":1,"id":"builtin","createTime":1760922306795}],"logicWhenNum":0,"customList":[]} \ No newline at end of file diff --git a/examples/module/cc1101/m5cores3_cc1101_rx_example.py b/examples/module/cc1101/m5cores3_cc1101_rx_example.py new file mode 100644 index 00000000..f29a579b --- /dev/null +++ b/examples/module/cc1101/m5cores3_cc1101_rx_example.py @@ -0,0 +1,144 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import os, sys, io +import M5 +from M5 import * +import m5ui +import lvgl as lv +from module import CC1101Module +import time + + +page0 = None +label_title = None +label_rssi = None +label_rx = None +label_time = None +module_cc1101_0 = None +cc1101_data = None +last_time = None + + +def module_cc1101_0_receive_event(received_data): + global \ + page0, \ + label_title, \ + label_rssi, \ + label_rx, \ + label_time, \ + module_cc1101_0, \ + cc1101_data, \ + last_time + cc1101_data = received_data + if cc1101_data.crc_ok: + label_rx.set_text(str((str("Rx: ") + str((cc1101_data.decode()))))) + else: + print("CRC error") + label_rssi.set_text(str((str("RSSI: ") + str((str((cc1101_data.rssi)) + str(" dBm")))))) + + +def setup(): + global \ + page0, \ + label_title, \ + label_rssi, \ + label_rx, \ + label_time, \ + module_cc1101_0, \ + cc1101_data, \ + last_time + + M5.begin() + Widgets.setRotation(1) + m5ui.init() + page0 = m5ui.M5Page(bg_c=0xFFFFFF) + label_title = m5ui.M5Label( + "ModuleCC1101 Rx", + x=56, + y=2, + text_c=0x0000FF, + bg_c=0xFFFFFF, + bg_opa=0, + font=lv.font_montserrat_24, + parent=page0, + ) + label_rssi = m5ui.M5Label( + "RSSI:", + x=5, + y=95, + text_c=0x0000FF, + bg_c=0xFFFFFF, + bg_opa=0, + font=lv.font_montserrat_16, + parent=page0, + ) + label_rx = m5ui.M5Label( + "Rx:", + x=5, + y=65, + text_c=0x0000FF, + bg_c=0xFFFFFF, + bg_opa=0, + font=lv.font_montserrat_16, + parent=page0, + ) + label_time = m5ui.M5Label( + "Timestamp:", + x=5, + y=210, + text_c=0x0000FF, + bg_c=0xFFFFFF, + bg_opa=0, + font=lv.font_montserrat_16, + parent=page0, + ) + + page0.screen_load() + module_cc1101_0 = CC1101Module( + pin_cs=5, + pin_gdo0=7, + pin_gdo2=10, + freq_khz=868000, + bitrate_kbps=3, + freq_dev_khz=25.4, + rx_bw_khz=58, + output_power=10, + preamble_length=16, + sync_word_h=0x12, + sync_word_l=0xAD, + ) + module_cc1101_0.set_rx_irq_callback(module_cc1101_0_receive_event) + module_cc1101_0.start_recv() + + +def loop(): + global \ + page0, \ + label_title, \ + label_rssi, \ + label_rx, \ + label_time, \ + module_cc1101_0, \ + cc1101_data, \ + last_time + M5.update() + if (time.ticks_diff((time.ticks_ms()), last_time)) >= 1000: + last_time = time.ticks_ms() + label_time.set_text(str((str("Timestamp: ") + str((time.ticks_ms()))))) + + +if __name__ == "__main__": + try: + setup() + while True: + loop() + except (Exception, KeyboardInterrupt) as e: + try: + m5ui.deinit() + from utility import print_error_msg + + print_error_msg(e) + except ImportError: + print("please update to latest firmware") diff --git a/examples/module/cc1101/m5cores3_cc1101_tx_example.m5f2 b/examples/module/cc1101/m5cores3_cc1101_tx_example.m5f2 new file mode 100644 index 00000000..6022fea8 --- /dev/null +++ b/examples/module/cc1101/m5cores3_cc1101_tx_example.m5f2 @@ -0,0 +1 @@ +{"version":"V2.0","versionNumber":"V2.3.7","type":"cores3","components":[{"name":"page0","type":"lvgl_page","layer":0,"screenId":"builtin","screenName":"","id":"yt@9Ut7e+I4zZ5Qr","createTime":1760689900810,"backgroundColor":"#ffffff","isLVGL":true,"isSelected":true},{"name":"lable_title","type":"lvgl_label","layer":1,"screenId":"builtin","screenName":"","id":"k-7XUW`@OIwpu%Pk","createTime":1760690020037,"x":58,"y":2,"color":"#0000ff","backgroundColor":"#ffffff","bg_opacity":0,"text":"ModuleCC1101 Tx","font":"lv.font_montserrat_24","pageId":"yt@9Ut7e+I4zZ5Qr","isLVGL":true,"isSelected":false},{"name":"label_tx","type":"lvgl_label","layer":2,"screenId":"builtin","screenName":"","id":"d%381zR$Tqwfl&%=","createTime":1760690067996,"x":5,"y":64,"color":"#0000ff","backgroundColor":"#ffffff","bg_opacity":0,"text":"Tx:","font":"lv.font_montserrat_16","pageId":"yt@9Ut7e+I4zZ5Qr","isLVGL":true,"isSelected":false,"width":21,"height":18},{"name":"label_time","type":"lvgl_label","layer":3,"screenId":"builtin","screenName":"","id":"c%_^aK+ux-2sxUJ9","createTime":1760690079025,"x":5,"y":210,"color":"#0000ff","backgroundColor":"#ffffff","bg_opacity":0,"text":"Timestamp:","font":"lv.font_montserrat_16","pageId":"yt@9Ut7e+I4zZ5Qr","isLVGL":true,"isSelected":false,"width":95,"height":18},{"name":"btn_ctrl","type":"lvgl_button","layer":4,"screenId":"builtin","screenName":"","id":"r2TrJ2%4qoOd==kQ","createTime":1760690137164,"x":127,"y":130,"width":0,"height":0,"color":"#ffffff","backgroundColor":"#2196f3","text":"Stop","font":"lv.font_montserrat_14","pageId":"yt@9Ut7e+I4zZ5Qr","isLVGL":true,"isSelected":false}],"resources":[{"hardware":["hardware_button","hardware_pin_button","imu","speaker","touch","als","mic","sdcard"]},{"module":["module_cc1101"]}],"units":[],"hats":[],"caps":[],"chains":[],"bases":[],"i2cs":[],"chainBus":[],"blockly":"send_flaglast_timecounttxtruepage0module_cc1101_0True5710868000325.45810160x120xADcount0send_flagTRUEbtn_ctrl10060btn_ctrlCENTERpage003080666100trueGTE11last_time1000last_timelabel_timeSend failed!Timestamp: send_flagtxHello M5 - countcount1hello M5txmodule_cc1101_0txlabel_txhello M5Tx: txlabel_txSend failed!btn_ctrlCLICKEDsend_flagsend_flagsend_flagbtn_ctrlStopbtn_ctrlStart666100","screen":[{"simulationName":"Built-in","type":"builtin","width":320,"height":240,"scale":0.77,"screenName":"","blockId":"","screenColorType":0,"rotation":1,"id":"builtin","createTime":1760689900805}],"logicWhenNum":0,"customList":[]} \ No newline at end of file diff --git a/examples/module/cc1101/m5cores3_cc1101_tx_example.py b/examples/module/cc1101/m5cores3_cc1101_tx_example.py new file mode 100644 index 00000000..819a205f --- /dev/null +++ b/examples/module/cc1101/m5cores3_cc1101_tx_example.py @@ -0,0 +1,184 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import os, sys, io +import M5 +from M5 import * +import m5ui +import lvgl as lv +from module import CC1101Module +import time + + +page0 = None +lable_title = None +label_tx = None +label_time = None +btn_ctrl = None +module_cc1101_0 = None +send_flag = None +last_time = None +count = None +tx = None + + +def btn_ctrl_clicked_event(event_struct): + global \ + page0, \ + lable_title, \ + label_tx, \ + label_time, \ + btn_ctrl, \ + module_cc1101_0, \ + send_flag, \ + last_time, \ + count, \ + tx + send_flag = not send_flag + if send_flag: + btn_ctrl.set_btn_text(str("Stop")) + else: + btn_ctrl.set_btn_text(str("Start")) + Speaker.tone(666, 100) + + +def btn_ctrl_event_handler(event_struct): + global \ + page0, \ + lable_title, \ + label_tx, \ + label_time, \ + btn_ctrl, \ + module_cc1101_0, \ + send_flag, \ + last_time, \ + count, \ + tx + event = event_struct.code + if event == lv.EVENT.CLICKED and True: + btn_ctrl_clicked_event(event_struct) + return + + +def setup(): + global \ + page0, \ + lable_title, \ + label_tx, \ + label_time, \ + btn_ctrl, \ + module_cc1101_0, \ + send_flag, \ + last_time, \ + count, \ + tx + + M5.begin() + Widgets.setRotation(1) + m5ui.init() + page0 = m5ui.M5Page(bg_c=0xFFFFFF) + lable_title = m5ui.M5Label( + "ModuleCC1101 Tx", + x=58, + y=2, + text_c=0x0000FF, + bg_c=0xFFFFFF, + bg_opa=0, + font=lv.font_montserrat_24, + parent=page0, + ) + label_tx = m5ui.M5Label( + "Tx:", + x=5, + y=64, + text_c=0x0000FF, + bg_c=0xFFFFFF, + bg_opa=0, + font=lv.font_montserrat_16, + parent=page0, + ) + label_time = m5ui.M5Label( + "Timestamp:", + x=5, + y=210, + text_c=0x0000FF, + bg_c=0xFFFFFF, + bg_opa=0, + font=lv.font_montserrat_16, + parent=page0, + ) + btn_ctrl = m5ui.M5Button( + text="Stop", + x=127, + y=130, + bg_c=0x2196F3, + text_c=0xFFFFFF, + font=lv.font_montserrat_14, + parent=page0, + ) + + btn_ctrl.add_event_cb(btn_ctrl_event_handler, lv.EVENT.ALL, None) + + page0.screen_load() + module_cc1101_0 = CC1101Module( + pin_cs=5, + pin_gdo0=7, + pin_gdo2=10, + freq_khz=868000, + bitrate_kbps=3, + freq_dev_khz=25.4, + rx_bw_khz=58, + output_power=10, + preamble_length=16, + sync_word_h=0x12, + sync_word_l=0xAD, + ) + count = 0 + send_flag = True + btn_ctrl.set_size(100, 60) + btn_ctrl.align_to(page0, lv.ALIGN.CENTER, 0, 30) + Speaker.begin() + Speaker.setVolumePercentage(0.8) + Speaker.tone(666, 100) + + +def loop(): + global \ + page0, \ + lable_title, \ + label_tx, \ + label_time, \ + btn_ctrl, \ + module_cc1101_0, \ + send_flag, \ + last_time, \ + count, \ + tx + M5.update() + if (time.ticks_diff((time.ticks_ms()), last_time)) >= 1000: + last_time = time.ticks_ms() + label_time.set_text(str((str("Timestamp: ") + str((time.ticks_ms()))))) + if send_flag: + tx = str("Hello M5 - ") + str(count) + count = (count if isinstance(count, (int, float)) else 0) + 1 + print(tx) + if module_cc1101_0.send(tx): + label_tx.set_text(str((str("Tx: ") + str(tx)))) + else: + label_tx.set_text(str("Send failed!")) + + +if __name__ == "__main__": + try: + setup() + while True: + loop() + except (Exception, KeyboardInterrupt) as e: + try: + m5ui.deinit() + from utility import print_error_msg + + print_error_msg(e) + except ImportError: + print("please update to latest firmware") diff --git a/m5stack/libs/driver/cc1101.py b/m5stack/libs/driver/cc1101.py new file mode 100644 index 00000000..9fef3e33 --- /dev/null +++ b/m5stack/libs/driver/cc1101.py @@ -0,0 +1,1000 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import time +import machine +from micropython import const + + +class CC1101Packet: + """CC1101 packet class to encapsulate received data""" + + def __init__(self, data, rssi=0, lqi=0, crc_ok=True): + self.data = data + self.rssi = rssi + self.lqi = lqi + self.crc_ok = crc_ok + + def decode(self): + """Decode packet data to string""" + try: + return self.data.decode("utf-8") + except: + return str(self.data) + + def __str__(self): + return f"CC1101Packet(data={self.data}, rssi={self.rssi}, lqi={self.lqi}, crc_ok={self.crc_ok})" + + +class CC1101: + """CC1101 MicroPython implementation based on RadioLib C++ code""" + + # Constants from C++ implementation + FIFO_BUFFER_SIZE = const(64) + MAX_PACKET_LENGTH = const(61) # 64 - 3 bytes for length and address + + # Transfer types + WRITE_SINGLE_BYTE = const(0x00) + WRITE_BURST = const(0x40) + READ_SINGLE_BYTE = const(0x80) + READ_BURST = const(0xC0) + + # Register types + CONFIG_REGISTER = const(0x80) + STATUS_REGISTER = const(0xC0) + + # FIFO and PATABLE addresses + PATABLE = const(0x3E) + FIFO = const(0x3F) + + # Configuration registers + IOCFG2 = const(0x00) + IOCFG1 = const(0x01) + IOCFG0 = const(0x02) + FIFOTHR = const(0x03) + SYNC1 = const(0x04) + SYNC0 = const(0x05) + PKTLEN = const(0x06) + PKTCTRL1 = const(0x07) + PKTCTRL0 = const(0x08) + ADDR = const(0x09) + CHANNR = const(0x0A) + FSCTRL1 = const(0x0B) + FSCTRL0 = const(0x0C) + FREQ2 = const(0x0D) + FREQ1 = const(0x0E) + FREQ0 = const(0x0F) + MDMCFG4 = const(0x10) + MDMCFG3 = const(0x11) + MDMCFG2 = const(0x12) + MDMCFG1 = const(0x13) + MDMCFG0 = const(0x14) + DEVIATN = const(0x15) + MCSM2 = const(0x16) + MCSM1 = const(0x17) + MCSM0 = const(0x18) + FOCCFG = const(0x19) + BSCFG = const(0x1A) + AGCCTRL2 = const(0x1B) + AGCCTRL1 = const(0x1C) + AGCCTRL0 = const(0x1D) + WOREVT1 = const(0x1E) + WOREVT0 = const(0x1F) + WORCTRL = const(0x20) + FREND1 = const(0x21) + FREND0 = const(0x22) + FSCAL3 = const(0x23) + FSCAL2 = const(0x24) + FSCAL1 = const(0x25) + FSCAL0 = const(0x26) + RCCTRL1 = const(0x27) + RCCTRL0 = const(0x28) + FSTEST = const(0x29) + PTEST = const(0x2A) + AGCTEST = const(0x2B) + TEST2 = const(0x2C) + TEST1 = const(0x2D) + TEST0 = const(0x2E) + + # Status registers + PARTNUM = const(0x30) + VERSION = const(0x31) + FREQEST = const(0x32) + LQI = const(0x33) + RSSI = const(0x34) + MARCSTATE = const(0x35) + WORTIME1 = const(0x36) + WORTIME0 = const(0x37) + PKTSTATUS = const(0x38) + VCO_VC_DAC = const(0x39) + TXBYTES = const(0x3A) + RXBYTES = const(0x3B) + RCCTRL1_STATUS = const(0x3C) + RCCTRL0_STATUS = const(0x3D) + + # Command strobes + SRES = const(0x30) + SFSTXON = const(0x31) + SXOFF = const(0x32) + SCAL = const(0x33) + SRX = const(0x34) + STX = const(0x35) + SIDLE = const(0x36) + SAFC = const(0x37) + SWOR = const(0x38) + SPWD = const(0x39) + SFRX = const(0x3A) + SFTX = const(0x3B) + SWORRST = const(0x3C) + SNOP = const(0x3D) + + # MARC states + MARCSTATE_IDLE = const(0x01) + MARCSTATE_RX = const(0x0D) + MARCSTATE_TX = const(0x13) + MARCSTATE_TXFIFO_UNDERFLOW = const(0x16) + MARCSTATE_RXFIFO_OVERFLOW = const(0x11) + + # Default values (matching C code defaults) + DEFAULT_FREQ = 868.0 # CC1101_FREQ_MHZ + DEFAULT_BITRATE = 2.4 # CC1101_BITRATE_KBPS + DEFAULT_FREQ_DEV = 25.4 # CC1101_FREQ_DEV_KHZ + DEFAULT_RX_BW = 58.0 # CC1101_BW_KHZ + DEFAULT_POWER = 10 # 10 dBm + DEFAULT_PREAMBLE = 16 # 16 bits + DEFAULT_SYNC_H = 0x12 # Default sync word high (from ESP-IDF logic analyzer) + DEFAULT_SYNC_L = 0xAD # Default sync word low (from ESP-IDF logic analyzer) + DEFAULT_RX_TIMEOUT = 500 # 500 ms (from C code) + + def __init__(self, spi, ss, gdo0, gdo2=None): + """Initialize CC1101 + + :param spi: SPI object + :param ss: Chip select pin + :param gdo0: GDO0 pin (interrupt pin) + :param gdo2: GDO2 pin (optional) + """ + self.spi = spi + self.ss = machine.Pin(ss, mode=machine.Pin.OUT) + self.gdo0 = machine.Pin(gdo0, mode=machine.Pin.IN) + self.gdo2 = machine.Pin(gdo2, mode=machine.Pin.IN) if gdo2 else None + + # Deselect initially + self.deselect() + + # Set up interrupt on GDO0 pin + # GDO0 triggers on rising edge when packet is received + self.gdo0.irq(self._radio_rx_isr, machine.Pin.IRQ_RISING) + + # Set up interrupt on GDO2 pin (if available) + # GDO2 triggers on falling edge when packet is sent + if self.gdo2: + self.gdo2.irq(self._radio_tx_isr, machine.Pin.IRQ_FALLING) + + # Configuration parameters + self.frequency = self.DEFAULT_FREQ + self.bitrate = self.DEFAULT_BITRATE + self.freq_dev = self.DEFAULT_FREQ_DEV + self.rx_bw = self.DEFAULT_RX_BW + self.power = self.DEFAULT_POWER + self.preamble_length = self.DEFAULT_PREAMBLE + self.sync_word_h = self.DEFAULT_SYNC_H + self.sync_word_l = self.DEFAULT_SYNC_L + + # Internal state + self.packet_length_queried = False + self.packet_length = 0 + self.raw_rssi = 0 + self.raw_lqi = 0 + self.crc_on = True + self.promiscuous = False + + # Callback mechanism + self._rx_callback = None + self._tx_callback = None + self._last_rx_irq = None + self._last_tx_irq = None + + def select(self): + """Select CC1101 chip""" + self.ss.value(0) + + def deselect(self): + """Deselect CC1101 chip""" + self.ss.value(1) + + def write_command(self, command): + """Write command strobe""" + buf = bytearray((command,)) + self.select() + self.spi.write(buf) + self.deselect() + return buf[0] + + def write_register(self, address, data): + """Write single byte to configuration register""" + buf = bytearray(2) + buf[0] = address | self.WRITE_SINGLE_BYTE + buf[1] = data + self.select() + self.spi.write(buf) + self.deselect() + + def set_register_value(self, address, value, msb, lsb): + """Set specific bits in register (like RadioLib SPIsetRegValue)""" + # Read current value + current = self.read_register(address) + + # Create mask for the bits we want to change + mask = 0 + for i in range(lsb, msb + 1): + mask |= 1 << i + + # Clear the bits we want to change and set new value + new_value = (current & ~mask) | (value & mask) + + # Write back + self.write_register(address, new_value) + + def read_register(self, address, register_type=0x80): + """Read value from configuration or status register""" + read_buf = bytearray(2) + write_buf = bytearray(2) + write_buf[0] = address | register_type + self.select() + self.spi.write_readinto(write_buf, read_buf) + self.deselect() + return read_buf[1] + + def read_burst(self, address, length): + """Read values from consecutive configuration registers""" + buf = bytearray(length + 1) + buf[0] = address | self.READ_BURST + self.select() + self.spi.write_readinto(buf, buf) + self.deselect() + return buf[1:] + + def write_burst(self, address, data): + """Write data to consecutive registers""" + buf = bytearray(1) + buf[0] = address | self.WRITE_BURST + buf.extend(data) + self.select() + self.spi.write(buf) + self.deselect() + + def reset(self): + """Reset CC1101 chip""" + self.deselect() + time.sleep_us(5) + self.select() + time.sleep_us(10) + self.deselect() + time.sleep_us(45) + self.select() + + self.write_command(self.SRES) + time.sleep_ms(10) + self.deselect() + + def get_chip_version(self): + """Get CC1101 chip version""" + return self.read_register(self.VERSION, self.STATUS_REGISTER) + + def wait_for_idle(self): + """Wait for CC1101 to enter idle state""" + timeout = 1000 # 1 second timeout + while timeout > 0: + marc_state = self.read_register(self.MARCSTATE, self.STATUS_REGISTER) & 0x1F + if marc_state == self.MARCSTATE_IDLE: + return True + time.sleep_ms(1) + timeout -= 1 + return False + + def _radio_rx_isr(self, _): + """Radio receive interrupt service routine""" + self._last_rx_irq = time.ticks_ms() + + # Check if module is in RX state (not TX or IDLE) + marc_state = self.read_register(self.MARCSTATE, self.STATUS_REGISTER) & 0x1F + if marc_state != self.MARCSTATE_RX: + return + + if self._rx_callback: + # Check if packet is available + if self.check_for_packet(): + # Read the packet + result = self._read_data() + if result and len(result) == 2: + packet_data, crc_ok = result + if packet_data: # Check if we actually got data + # Create packet object + packet = CC1101Packet(packet_data, self.get_rssi(), self.get_lqi(), crc_ok) + # Call the callback + self._rx_callback(packet) + # Note: _read_data() already restarts receive mode, so no need to do it here + + def _radio_tx_isr(self, _): + """Radio transmit interrupt service routine""" + self._last_tx_irq = time.ticks_ms() + if self._tx_callback: + self._tx_callback(_) + + def set_rx_callback(self, callback): + """Set callback function for received packets + + :param callback: Function to call when packet is received + """ + self._rx_callback = callback + + def set_tx_callback(self, callback): + """Set callback function for transmitted packets + + :param callback: Function to call when packet is transmitted + """ + self._tx_callback = callback + + def calculate_frequency_regs(self, freq_mhz): + """Calculate frequency register values""" + # CC1101 frequency calculation: f_carrier = (f(XOSC) / 2^16) * FREQ + # where f(XOSC) = 26 MHz + xosc_freq = 26.0 # MHz + freq_reg = int((freq_mhz * (2**16)) / xosc_freq) + + freq2 = (freq_reg >> 16) & 0xFF + freq1 = (freq_reg >> 8) & 0xFF + freq0 = freq_reg & 0xFF + + return freq2, freq1, freq0 + + def calculate_bitrate_regs(self, bitrate_kbps): + """Calculate bitrate register values""" + # Bitrate calculation: R_data = (((256 + DRATE_M) * 2^DRATE_E) / 2^28) * f(XOSC) + xosc_freq = 26.0 # MHz + target_rate = bitrate_kbps * 1000 # Convert to bps + + # Find best DRATE_E and DRATE_M combination + best_error = float("inf") + best_e = 0 + best_m = 0 + + for e in range(16): # DRATE_E: 0-15 + for m in range(256): # DRATE_M: 0-255 + calculated_rate = ((256 + m) * (2**e)) / (2**28) * xosc_freq * 1000000 + error = abs(calculated_rate - target_rate) + if error < best_error: + best_error = error + best_e = e + best_m = m + + return best_e, best_m + + def calculate_rx_bw_regs(self, rx_bw_khz): + """Calculate receiver bandwidth register values""" + # Bandwidth calculation: BW_channel = f(XOSC) / (8 * (4 + CHANBW_M) * 2^CHANBW_E) + xosc_freq = 26.0 # MHz + target_bw = rx_bw_khz * 1000 # Convert to Hz + + # Find best CHANBW_E and CHANBW_M combination + best_error = float("inf") + best_e = 0 + best_m = 0 + + for e in range(4): # CHANBW_E: 0-3 + for m in range(4): # CHANBW_M: 0-3 + calculated_bw = xosc_freq * 1000000 / (8 * (4 + m) * (2**e)) + error = abs(calculated_bw - target_bw) + if error < best_error: + best_error = error + best_e = e + best_m = m + + return best_e, best_m + + def calculate_freq_dev_regs(self, freq_dev_khz): + """Calculate frequency deviation register values""" + # Deviation calculation: f_dev = (f(XOSC) / 2^17) * (8 + DEVIATION_M) * 2^DEVIATION_E + xosc_freq = 26.0 # MHz + target_dev = freq_dev_khz * 1000 # Convert to Hz + + # Find best DEVIATION_E and DEVIATION_M combination + best_error = float("inf") + best_e = 0 + best_m = 0 + + for e in range(8): # DEVIATION_E: 0-7 + for m in range(8): # DEVIATION_M: 0-7 + calculated_dev = (xosc_freq * 1000000 / (2**17)) * (8 + m) * (2**e) + error = abs(calculated_dev - target_dev) + if error < best_error: + best_error = error + best_e = e + best_m = m + + return best_e, best_m + + def get_power_table(self, power_dbm): + """Get power amplifier table for given output power""" + # Power table mapping based on frequency bands + if self.frequency < 374.0: + # 315 MHz + f = 0 + elif self.frequency < 650.5: + # 434 MHz + f = 1 + elif self.frequency < 891.5: + # 868 MHz + f = 2 + else: + # 915 MHz + f = 3 + + # Power table [power_dbm][frequency_band] + pa_table = [ + [0x12, 0x12, 0x03, 0x03], # -30 dBm + [0x0D, 0x0E, 0x0F, 0x0E], # -20 dBm + [0x1C, 0x1D, 0x1E, 0x1E], # -15 dBm + [0x34, 0x34, 0x27, 0x27], # -10 dBm + [0x51, 0x60, 0x50, 0x8E], # 0 dBm + [0x85, 0x84, 0x81, 0xCD], # 5 dBm + [0xCB, 0xC8, 0xCB, 0xC7], # 7 dBm + [0xC2, 0xC0, 0xC2, 0xC0], # 10 dBm + ] + + power_levels = [-30, -20, -15, -10, 0, 5, 7, 10] + + for i, level in enumerate(power_levels): + if power_dbm == level: + return bytes([pa_table[i][f]]) + + # Default to 10 dBm + return bytes([pa_table[7][f]]) + + def begin(self, freq=None, br=None, freq_dev=None, rx_bw=None, pwr=None, preamble_length=None): + """Initialize CC1101 (like RadioLib begin method) + + :param freq: Frequency in MHz + :param br: Bitrate in kbps + :param freq_dev: Frequency deviation in kHz + :param rx_bw: RX bandwidth in kHz + :param pwr: Output power in dBm + :param preamble_length: Preamble length in bits + """ + # Update parameters if provided + if freq is not None: + self.frequency = freq + if br is not None: + self.bitrate = br + if freq_dev is not None: + self.freq_dev = freq_dev + if rx_bw is not None: + self.rx_bw = rx_bw + if pwr is not None: + self.power = pwr + if preamble_length is not None: + self.preamble_length = preamble_length + + # Check chip version first (like ESP-IDF) + version = self.get_chip_version() + + # Reset the chip + self.reset() + time.sleep_ms(150) # Wait for reset to complete + valid_versions = [ + 0x04, + 0x14, + 0x17, + 0x00, + 0x20, + ] # LEGACY, CURRENT, CLONE, and other variants + if version not in valid_versions: + raise RuntimeError( + f"Unexpected CC1101 chip version 0x{version:02X} (expected one of {[hex(v) for v in valid_versions]}). Please check module connection." + ) + + # Configure basic settings + self._config_basic_settings() + + # Configure frequency + self._set_frequency(self.frequency) + + # Configure bitrate + self._set_bitrate(self.bitrate) + + # Configure RX bandwidth + self._set_rx_bandwidth(self.rx_bw) + + # Configure frequency deviation + self._set_frequency_deviation(self.freq_dev) + + # Configure output power + self._set_output_power(self.power) + + # Set variable packet length mode + self._set_variable_packet_length() + + # Configure preamble length + self._set_preamble_length(self.preamble_length) + + # Configure sync word + self._set_sync_word(self.sync_word_h, self.sync_word_l) + + # Flush FIFOs + self.write_command(self.SFRX) + self.write_command(self.SFTX) + + return True + + def _config_basic_settings(self): + """Configure basic CC1101 settings""" + # Enter idle mode + self.write_command(self.SIDLE) + self.wait_for_idle() + + # MCSM0: Enable automatic frequency synthesizer calibration (matching RadioLib) + # Set FS_AUTOCAL_IDLE_TO_RXTX (bits 5-4) and PIN_CTRL_OFF (bit 1) + self.set_register_value(self.MCSM0, 0x10, 5, 4) # FS_AUTOCAL_IDLE_TO_RXTX + self.set_register_value(self.MCSM0, 0x00, 1, 1) # PIN_CTRL_OFF + + # Set GDOs to Hi-Z initially (matching RadioLib) + self.set_register_value(self.IOCFG0, 0x2E, 5, 0) # GDOX_HIGH_Z + self.set_register_value(self.IOCFG2, 0x2E, 5, 0) # GDOX_HIGH_Z + + # Configure other basic settings + # MCSM1: Set RXOFF_MODE to RX (0x0C) to stay in RX after packet reception + # This is crucial for continuous receive mode + self.set_register_value(self.MCSM1, 0x0C, 3, 2) # RXOFF_RX + self.write_register(self.MCSM2, 0x07) # No RX timeout + + # AGC settings + self.write_register(self.AGCCTRL2, 0x03) + self.write_register(self.AGCCTRL1, 0x40) + self.write_register(self.AGCCTRL0, 0x91) + + # Frontend settings + self.write_register(self.FREND1, 0x56) + self.write_register(self.FREND0, 0x10) + + # Frequency synthesizer settings + self.write_register(self.FSCAL3, 0xE9) + self.write_register(self.FSCAL2, 0x2A) + self.write_register(self.FSCAL1, 0x00) + self.write_register(self.FSCAL0, 0x1F) + + # Test settings + self.write_register(self.TEST2, 0x81) + self.write_register(self.TEST1, 0x35) + self.write_register(self.TEST0, 0x09) + + # Calibrate frequency synthesizer + self.write_command(self.SCAL) + time.sleep_ms(1) + self.wait_for_idle() + + def start_receive(self): + """Start reception mode (like RadioLib startReceive)""" + try: + # Set mode to standby + self.write_command(self.SIDLE) + self.wait_for_idle() + + # Flush RX FIFO + self.write_command(self.SFRX) + + # Set GDO0 mapping for packet received detection + # Use GDOX_SYNC_WORD_SENT_OR_PKT_RECEIVED (0x06) for rising edge trigger + self.write_register(self.IOCFG0, 0x06) + + # Start reception + self.write_command(self.SRX) + + return True + + except Exception as e: + print(f"Start receive error: {e}") + return False + + def check_for_packet(self): + """Check if a packet is available (non-blocking)""" + try: + # Check if GDO0 indicates packet received + # GDO0 goes high when packet is received + if self.gdo0.value() == 1: + # Wait for packet to complete (with timeout to avoid blocking) + timeout = 100 # 100ms timeout + while self.gdo0.value() == 1 and timeout > 0: + time.sleep_ms(1) + timeout -= 1 + return True + return False + except Exception as e: + print(f"Check packet error: {e}") + return False + + def _set_frequency(self, freq_mhz): + """Set CC1101 frequency""" + freq2, freq1, freq0 = self.calculate_frequency_regs(freq_mhz) + self.write_register(self.FREQ2, freq2) + self.write_register(self.FREQ1, freq1) + self.write_register(self.FREQ0, freq0) + + def _set_bitrate(self, bitrate_kbps): + """Set CC1101 bitrate""" + drate_e, drate_m = self.calculate_bitrate_regs(bitrate_kbps) + # MDMCFG4: Set exponent part of data rate (matching RadioLib) + self.set_register_value(self.MDMCFG4, drate_e, 3, 0) + self.write_register(self.MDMCFG3, drate_m) + + def _set_rx_bandwidth(self, rx_bw_khz): + """Set CC1101 RX bandwidth""" + chanbw_e, chanbw_m = self.calculate_rx_bw_regs(rx_bw_khz) + # Read current MDMCFG4 and update only bandwidth bits + current_mdmcfg4 = self.read_register(self.MDMCFG4) + new_mdmcfg4 = (chanbw_e << 6) | (chanbw_m << 4) | (current_mdmcfg4 & 0x0F) + self.write_register(self.MDMCFG4, new_mdmcfg4) + + def _set_frequency_deviation(self, freq_dev_khz): + """Set CC1101 frequency deviation""" + dev_e, dev_m = self.calculate_freq_dev_regs(freq_dev_khz) + self.write_register(self.DEVIATN, (dev_e << 4) | dev_m) + + def _set_output_power(self, power_dbm): + """Set CC1101 output power""" + power_table = self.get_power_table(power_dbm) + self.write_burst(self.PATABLE, power_table) + + def _set_variable_packet_length(self): + """Set CC1101 to variable packet length mode""" + # PKTCTRL0: Variable packet length, enable CRC (matching RadioLib) + self.set_register_value(self.PKTCTRL0, 0x00, 6, 4) # WHITE_DATA_OFF | PKT_FORMAT_NORMAL + self.set_register_value(self.PKTCTRL0, 0x05, 2, 0) # CRC_ON | LENGTH_CONFIG_VARIABLE + + # PKTCTRL1: CRC_AUTOFLUSH_OFF | APPEND_STATUS_ON | ADR_CHK_NONE + self.set_register_value(self.PKTCTRL1, 0x04, 3, 0) + + def _set_preamble_length(self, preamble_bits): + """Set CC1101 preamble length""" + preamble_map = { + 16: 0x00, + 24: 0x10, + 32: 0x20, + 48: 0x30, + 64: 0x40, + 96: 0x50, + 128: 0x60, + 192: 0x70, + } + preamble_reg = preamble_map.get(preamble_bits, 0x00) + + current_mdmcfg1 = self.read_register(self.MDMCFG1) + new_mdmcfg1 = preamble_reg | (current_mdmcfg1 & 0x0F) + self.write_register(self.MDMCFG1, new_mdmcfg1) + + def _set_sync_word(self, sync_h, sync_l): + """Set CC1101 sync word (matching RadioLib)""" + self.set_register_value(self.SYNC1, sync_h, 7, 0) + self.set_register_value(self.SYNC0, sync_l, 7, 0) + + def transmit(self, data, addr=0): + """Transmit data + :param data: Data to transmit (string or bytes) + :param addr: Address (optional) + :return: True if successful, False otherwise + """ + try: + # Convert string to bytes if needed + if isinstance(data, str): + data_bytes = data.encode("utf-8") + else: + data_bytes = data + + if len(data_bytes) > self.MAX_PACKET_LENGTH: + return False + + # Start transmission + if not self._start_transmit(data_bytes, addr): + return False + + # Wait for transmission using GDO2 edges if available (RadioLib style) + timeout = 5 + int(((len(data_bytes) * 8) / self.bitrate) * 5) + if timeout < 50: + timeout = 50 + if self.gdo2 is not None: + # Wait for TX start: GDO2 goes high + start = time.ticks_ms() + while self.gdo2.value() == 0: + if time.ticks_diff(time.ticks_ms(), start) > timeout: + self._finish_transmit() + return False + time.sleep_ms(1) + # Wait for TX end: GDO2 goes low + start = time.ticks_ms() + while self.gdo2.value() == 1: + if time.ticks_diff(time.ticks_ms(), start) > timeout: + self._finish_transmit() + return False + time.sleep_ms(1) + else: + # Fallback: poll MARCSTATE - wait enter TX then leave TX + enter_timeout = 50 + while enter_timeout > 0: + marc_state = self.read_register(self.MARCSTATE, self.STATUS_REGISTER) & 0x1F + if marc_state == self.MARCSTATE_TX: + break + time.sleep_ms(1) + enter_timeout -= 1 + leave_timeout = timeout + while leave_timeout > 0: + marc_state = self.read_register(self.MARCSTATE, self.STATUS_REGISTER) & 0x1F + if marc_state != self.MARCSTATE_TX: + break + time.sleep_ms(1) + leave_timeout -= 1 + + # Finish transmission + self._finish_transmit() + return True + + except Exception as e: + print(f"Transmit error: {e}") + return False + + def _start_transmit(self, data_bytes, addr=0): + """Start transmission (like RadioLib startTransmit)""" + try: + # Set mode to standby + self.write_command(self.SIDLE) + self.wait_for_idle() + + # Flush TX FIFO + self.write_command(self.SFTX) + + # Set GDO0 mapping for sync word sent or packet received (matching ESP-IDF) + self.set_register_value(self.IOCFG0, 0x06, 5, 0) # GDOX_SYNC_WORD_SENT_OR_PKT_RECEIVED + + # Set GDO2 mapping for TX completion interrupt (if GDO2 is available) + if self.gdo2: + self.set_register_value( + self.IOCFG2, 0x06, 5, 0 + ) # GDOX_SYNC_WORD_SENT_OR_PKT_RECEIVED + + # Write packet length (variable length mode) + self.write_register(self.FIFO, len(data_bytes)) + + # Write data to FIFO + self.write_burst(self.FIFO, data_bytes) + + # Start transmission + self.write_command(self.STX) + + return True + + except Exception as e: + print(f"Start transmit error: {e}") + return False + + def _finish_transmit(self): + """Finish transmission (like RadioLib finishTransmit)""" + try: + # Set mode to standby + self.write_command(self.SIDLE) + self.wait_for_idle() + + # Flush TX FIFO + self.write_command(self.SFTX) + + # Restart receive mode to be ready for next packet + self._start_receive() + + except Exception as e: + print(f"Finish transmit error: {e}") + + def receive(self, data=None, length=0): + """Receive data (like RadioLib receive method) - BLOCKING + + :param data: Buffer to store received data (optional) + :param length: Maximum length to receive (optional) + :return: Received data as bytes, or empty bytes if no data + """ + try: + # Calculate timeout (500 ms + 400 full max-length packets at current bit rate) + timeout = 500 + (1.0 / self.bitrate) * (self.MAX_PACKET_LENGTH * 400.0) + + # Start reception + if not self._start_receive(): + return b"" + + # Wait for packet start or timeout (GDO0 goes from low to high) + start_time = time.ticks_ms() + while self.gdo0.value() == 0: # GDO0 low means waiting for packet + if time.ticks_diff(time.ticks_ms(), start_time) > timeout: + self.standby() + self.write_command(self.SFRX) + return b"" + time.sleep_ms(1) + + # Wait for packet end or timeout (GDO0 goes from high to low) + start_time = time.ticks_ms() + while self.gdo0.value() == 1: # GDO0 high means packet in progress + if time.ticks_diff(time.ticks_ms(), start_time) > timeout: + self.standby() + self.write_command(self.SFRX) + return b"" + time.sleep_ms(1) + + # Read packet data + return self._read_data(length) + + except Exception as e: + print(f"Receive error: {e}") + return b"" + + def receive_polling(self, data=None, length=0): + """Receive data in polling mode - NON-BLOCKING + + :param data: Buffer to store received data (optional) + :param length: Maximum length to receive (optional) + :return: Received data as bytes, or empty bytes if no data + """ + try: + # Just check if packet is available (non-blocking) + if not self.check_for_packet(): + return b"" + + # Read packet data + return self._read_data(length) + + except Exception as e: + print(f"Receive polling error: {e}") + return b"" + + def _start_receive(self): + """Start reception (like RadioLib startReceive)""" + try: + # Set mode to standby + if not self.standby(): + return False + + # Flush RX FIFO + self.write_command(self.SFRX) + + # Set GDO0 mapping + # GDO0 goes high when packet is received + # Use GDOX_SYNC_WORD_SENT_OR_PKT_RECEIVED (0x06) for rising edge trigger + self.write_register(self.IOCFG0, 0x06) + + # Set mode to receive + self.write_command(self.SRX) + + return True + + except Exception as e: + print(f"Start receive error: {e}") + return False + + def _read_data(self, max_length=0): + """Read received data (like RadioLib readData)""" + try: + # Get packet length + packet_length = self._get_packet_length() + if packet_length == 0: + return b"" + + # Limit length if specified + if max_length > 0 and max_length < packet_length: + packet_length = max_length + + # Check address filtering + filter_val = (self.read_register(self.PKTCTRL1) >> 0) & 0x03 + if filter_val != 0: # ADR_CHK_NONE + self.read_register(self.FIFO) # Skip address byte + + # Read packet data + data = self.read_burst(self.FIFO, packet_length) + + # Check if status bytes are enabled (default: APPEND_STATUS_ON) + is_append_status = ((self.read_register(self.PKTCTRL1) >> 2) & 0x01) == 1 + + if is_append_status: + # Read RSSI byte + self.raw_rssi = self.read_register(self.FIFO) + + # Read LQI and CRC byte + val = self.read_register(self.FIFO) + self.raw_lqi = val & 0x7F + + # Check CRC + if self.crc_on and (val & 0x80) == 0: # CRC_ERROR (0x80 = CRC_OK bit) + self.packet_length_queried = False + # CRC error detected, will be returned in crc_ok flag + + # Clear internal flag so getPacketLength can return the new packet length + self.packet_length_queried = False + + # Flush then standby according to RXOFF_MODE (default: RXOFF_IDLE) + if ((self.read_register(self.MCSM1) >> 2) & 0x03) == 0: # RXOFF_IDLE + # Set mode to standby + self.standby() + # Flush Rx FIFO + self.write_command(self.SFRX) + + # Return data and CRC status + crc_ok = True + if self.crc_on and (val & 0x80) == 0: # CRC_ERROR + crc_ok = False + return data, crc_ok + + except Exception as e: + print(f"Read data error: {e}") + return b"", False + + def _rssi_to_dbm(self, raw_rssi): + """Convert raw RSSI value to dBm""" + if raw_rssi >= 128: + return (raw_rssi - 256) / 2.0 - 74.0 + else: + return raw_rssi / 2.0 - 74.0 + + def get_rssi(self): + """Get last received RSSI in dBm""" + if hasattr(self, "raw_rssi"): + return self._rssi_to_dbm(self.raw_rssi) + return 0 + + def get_lqi(self): + """Get last received LQI""" + if hasattr(self, "raw_lqi"): + return self.raw_lqi + return 0 + + def get_marc_state(self): + """Get current MARC state""" + try: + marc_state = self.read_register(self.MARCSTATE) + return marc_state + except Exception as e: + print(f"MARC state error: {e}") + return 0 + + def _get_packet_length(self): + """Get packet length from FIFO""" + if not self.packet_length_queried: + self.packet_length = self.read_register(self.FIFO) + self.packet_length_queried = True + return self.packet_length + + def standby(self): + """Set module to standby mode""" + try: + self.write_command(self.SIDLE) + self.wait_for_idle() + # Clear IRQ flags when going to standby (like LoRa module) + self._last_rx_irq = None + self._last_tx_irq = None + return True + except Exception as e: + print(f"Standby error: {e}") + return False + + def rx_irq_triggered(self): + """Returns True if the RX ISR has executed since the last time a send or a receive started""" + return self._last_rx_irq is not None + + def tx_irq_triggered(self): + """Returns True if the TX ISR has executed since the last time a send or a receive started""" + return self._last_tx_irq is not None + + def get_status(self): + """Get module status""" + try: + marc_state = self.read_register(self.MARCSTATE, self.STATUS_REGISTER) & 0x1F + rssi = self.get_rssi() + lqi = self.get_lqi() + + return {"marc_state": marc_state, "rssi": rssi, "lqi": lqi} + except Exception as e: + print(f"Status error: {e}") + return {"marc_state": 0, "rssi": 0.0, "lqi": 0} diff --git a/m5stack/libs/driver/manifest.py b/m5stack/libs/driver/manifest.py index 746d23fa..5968210f 100644 --- a/m5stack/libs/driver/manifest.py +++ b/m5stack/libs/driver/manifest.py @@ -10,6 +10,7 @@ "atecc608b_tngtls/atecc_asn1.py", "atecc608b_tngtls/atecc_cert_util.py", "atecc608b_tngtls/atecc.py", + "cc1101.py", "es8311/__init__.py", "es8311/reg.py", "es8388/__init__.py", diff --git a/m5stack/libs/module/__init__.py b/m5stack/libs/module/__init__.py index 36771d59..4e2edd17 100644 --- a/m5stack/libs/module/__init__.py +++ b/m5stack/libs/module/__init__.py @@ -8,6 +8,7 @@ "AIN4Module": "ain4", "AudioModule": "audio", "Bala2Module": "bala2", + "CC1101Module": "cc1101", "CommuModuleCAN": "commu", "CommuModuleI2C": "commu", "CommuModuleRS485": "commu", diff --git a/m5stack/libs/module/cc1101.py b/m5stack/libs/module/cc1101.py new file mode 100644 index 00000000..ae70e9d8 --- /dev/null +++ b/m5stack/libs/module/cc1101.py @@ -0,0 +1,485 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +from . import mbus +import machine +import micropython +from driver.cc1101 import CC1101 as CC1101Driver, CC1101Packet + + +class CC1101Module: + """Create a CC1101Module object. + + :param int pin_cs: (CS) Chip select pin number. + :param int pin_gdo0: (GDO0) Interrupt pin number. + :param int pin_gdo2: (GDO2) Optional interrupt pin number. + :param float freq_khz: CC1101 RF frequency in kHz, with a range of 855000 kHz to 928000 kHz. + :param float bitrate_kbps: Data rate in kbps, range from 0.6 to 6.0 kbps. + :param float freq_dev_khz: Frequency deviation in kHz, range from 1.6 to 380 kHz. + :param float rx_bw_khz: Receiver bandwidth in kHz, range from 58 to 812 kHz. + :param int output_power: Output power in dBm, range from -30 to 10 dBm. + :param int preamble_length: Preamble length in bits, options: 16, 24, 32, 48, 64, 96, 128, 192. + :param int sync_word_h: High byte of sync word (0x00 to 0xFF). + :param int sync_word_l: Low byte of sync word (0x00 to 0xFF). + + UiFlow2 Code Block: + + |init.png| + + MicroPython Code Block: + + .. code-block:: python + + from module import CC1101Module + + module_cc1101_0 = CC1101Module(5, 7, 10, 868000.0, 2.4, 25.4, 58.0, 10, 16, 0x12, 0xAD) + """ + + def __init__( + self, + pin_cs: int = 5, + pin_gdo0: int = 7, + pin_gdo2: int = 10, + freq_khz: float = 868000.0, + bitrate_kbps: float = 2.4, + freq_dev_khz: float = 25.4, + rx_bw_khz: float = 58.0, + output_power: int = 10, + preamble_length: int = 16, + sync_word_h: int = 0x12, + sync_word_l: int = 0xAD, + ): + # Valid preamble lengths + self.PREAMBLE_LENGTHS = (16, 24, 32, 48, 64, 96, 128, 192) + self._validate_range(preamble_length, 16, 192) + self._validate_range(sync_word_h, 0, 0xFF) + self._validate_range(sync_word_l, 0, 0xFF) + self._validate_range(output_power, -30, 10) + + # Initialize the CC1101 driver + self.driver = CC1101Driver(spi=mbus.spi, ss=pin_cs, gdo0=pin_gdo0, gdo2=pin_gdo2) + + # Configuration parameters (store in kHz for consistency, but convert to MHz for driver) + self.frequency = freq_khz # Store in kHz + self.bitrate = bitrate_kbps + self.freq_dev = freq_dev_khz + self.rx_bw = rx_bw_khz + self.power = output_power + self.preamble_length = preamble_length + self.sync_word_h = sync_word_h + self.sync_word_l = sync_word_l + + # Set sync words + self.driver.sync_word_h = self.sync_word_h + self.driver.sync_word_l = self.sync_word_l + + # Initialize the module (same as original simple_impl) + # Convert kHz to MHz for driver (driver expects MHz) + cc1101_cfg = { + "freq": self.frequency / 1000.0, # Convert kHz to MHz + "br": self.bitrate, + "freq_dev": self.freq_dev, + "rx_bw": self.rx_bw, + "pwr": self.power, + "preamble_length": self.preamble_length, + } + self.driver.begin(**cc1101_cfg) + + # Start receive mode for polling + self.driver._start_receive() + + self.rx_irq_callback = None + self.tx_irq_callback = None + + def _validate_range(self, value, min_val, max_val): + if value < min_val or value > max_val: + raise ValueError(f"Value {value} out of range {min_val} to {max_val}") + + def set_freq(self, freq_khz: int = 868000) -> None: + """Set frequency in kHz. + + :param int freq_khz: Frequency in kHz. Valid ranges: 855000-928000. Default is 868000. + + UiFlow2 Code Block: + + |set_freq.png| + + MicroPython Code Block: + + .. code-block:: python + + module_cc1101_0.set_freq(868000.0) + """ + # Check if frequency is in any of the valid ranges + valid = 855000 <= freq_khz <= 928000 + if not valid: + raise ValueError(f"Frequency {freq_khz} kHz not in valid ranges (855000-928000)") + + self.frequency = freq_khz + # Convert kHz to MHz for driver (driver expects MHz) + self.driver._set_frequency(freq_khz / 1000.0) + + def set_bitrate(self, bitrate_kbps: float) -> None: + """Set data rate in kbps. + + :param float bitrate_kbps: Data rate in kbps (0.6 ~ 6.0) + + UiFlow2 Code Block: + + |set_bitrate.png| + + MicroPython Code Block: + + .. code-block:: python + + module_cc1101_0.set_bitrate(2.4) + """ + self._validate_range(bitrate_kbps, 0.6, 6.0) + self.bitrate = bitrate_kbps + self.driver._set_bitrate(bitrate_kbps) + + def set_freq_dev(self, freq_dev_khz: float) -> None: + """Set frequency deviation in kHz. + + :param float freq_dev_khz: Frequency deviation in kHz (1.6 ~ 380) + + UiFlow2 Code Block: + + |set_freq_dev.png| + + MicroPython Code Block: + + .. code-block:: python + + module_cc1101_0.set_freq_dev(25.4) + """ + self._validate_range(freq_dev_khz, 1.6, 380) + self.freq_dev = freq_dev_khz + self.driver._set_frequency_deviation(freq_dev_khz) + + def set_rx_bw(self, rx_bw_khz: float) -> None: + """Set receiver bandwidth in kHz. + + :param float rx_bw_khz: Receiver bandwidth in kHz (58 ~ 812) + + UiFlow2 Code Block: + + |set_rx_bw.png| + + MicroPython Code Block: + + .. code-block:: python + + module_cc1101_0.set_rx_bw(58.0) + """ + self._validate_range(rx_bw_khz, 58, 812) + self.rx_bw = rx_bw_khz + self.driver._set_rx_bandwidth(rx_bw_khz) + + def set_output_power(self, output_power: int) -> None: + """Set output power in dBm. + + :param int output_power: Output power in dBm (-30 ~ 10) + + UiFlow2 Code Block: + + |set_output_power.png| + + MicroPython Code Block: + + .. code-block:: python + + module_cc1101_0.set_output_power(10) + """ + self._validate_range(output_power, -30, 10) + self.power = output_power + self.driver._set_output_power(output_power) + + def set_preamble_length(self, preamble_length: int) -> None: + """Set preamble length in bits. + + :param int preamble_length: Preamble length in bits, must be one of: + 16, 24, 32, 48, 64, 96, 128, 192. + + UiFlow2 Code Block: + + |set_preamble_length.png| + + MicroPython Code Block: + + .. code-block:: python + + module_cc1101_0.set_preamble_length(16) + """ + if preamble_length not in self.PREAMBLE_LENGTHS: + raise ValueError( + f"Invalid preamble length {preamble_length}, must be one of {self.PREAMBLE_LENGTHS}" + ) + self.preamble_length = preamble_length + self.driver._set_preamble_length(preamble_length) + + def set_sync_word(self, sync_word_h: int, sync_word_l: int) -> None: + """Set sync word. + + :param int sync_word_h: High byte of sync word (0 ~ 0xFF) + :param int sync_word_l: Low byte of sync word (0 ~ 0xFF) + + UiFlow2 Code Block: + + |set_sync_word.png| + + MicroPython Code Block: + + .. code-block:: python + + module_cc1101_0.set_sync_word(0x12, 0xAD) + """ + self._validate_range(sync_word_h, 0, 0xFF) + self._validate_range(sync_word_l, 0, 0xFF) + self.sync_word_h = sync_word_h + self.sync_word_l = sync_word_l + self.driver._set_sync_word(sync_word_h, sync_word_l) + + def send(self, packet: str | list | tuple | int | bytearray) -> bool: + """Send data + + :param str | list | tuple | int | bytearray packet: The data to be sent. + :returns: True if successful, False otherwise + :rtype: bool + + Send a data packet and return True if successful. + + UiFlow2 Code Block: + + |send.png| + + MicroPython Code Block: + + .. code-block:: python + + module_cc1101_0.send("Hello World") + """ + if isinstance(packet, str): + packet = bytes(packet, "utf-8") + elif isinstance(packet, list | tuple): + packet = bytes(packet) + elif isinstance(packet, int): + packet = bytes([packet]) + + return self.driver.transmit(packet) + + def recv(self, timeout_ms: int = None) -> CC1101Packet | None: + """Receive data + + :param int timeout_ms: Timeout in milliseconds (optional). Default is None. + :returns: Received packet instance or None if timeout + :rtype: CC1101Packet | None + + Attempt to receive a CC1101 packet. Returns `None` if timeout occurs, or returns the received packet instance. + + UiFlow2 Code Block: + + |recv.png| + + MicroPython Code Block: + + .. code-block:: python + + packet = module_cc1101_0.recv() + if packet: + if packet.crc_ok: + print(f"Received: {packet.decode()}") + print(f"RSSI: {packet.rssi} dBm") + print(f"LQI: {packet.lqi}") + else: + print("CRC error") + """ + # 轮询模式:检查是否有数据包可用 + if self.driver.check_for_packet(): + result = self.driver._read_data() + if result and len(result) == 2: + data, crc_ok = result + if data: + return CC1101Packet( + data, self.driver.get_rssi(), self.driver.get_lqi(), crc_ok + ) + return None + + def start_recv(self) -> None: + """Start receive data + + This method initiates the process to begin receiving data. + + UiFlow2 Code Block: + + |start_recv.png| + + MicroPython Code Block: + + .. code-block:: python + + module_cc1101_0.start_recv() + """ + self.driver.start_receive() + + def set_rx_irq_callback(self, callback) -> None: + """Set the receive interrupt callback function to be executed on IRQ. + + :param callback: The callback function to be invoked when the interrupt is triggered. + The callback should take a CC1101Packet parameter and return nothing. + + UiFlow2 Code Block: + + |set_rx_irq_callback.png| + + MicroPython Code Block: + + .. code-block:: python + + def on_packet_received(packet): + print(f"Received: {packet.decode()}") + + module_cc1101_0.set_rx_irq_callback(on_packet_received) + """ + self.rx_irq_callback = callback + + def _rx_irq_callback(packet): + if self.rx_irq_callback: + micropython.schedule(self.rx_irq_callback, packet) + + self.driver.set_rx_callback(_rx_irq_callback) + + def set_tx_irq_callback(self, callback) -> None: + """Set the transmit interrupt callback function to be executed on IRQ. + + :param callback: The callback function to be invoked when the interrupt is triggered. + The callback should take one parameter (pin object, can be ignored) and return nothing. + + UiFlow2 Code Block: + + |set_tx_irq_callback.png| + + MicroPython Code Block: + + .. code-block:: python + + def on_packet_sent(_): + print("Packet sent successfully") + + module_cc1101_0.set_tx_irq_callback(on_packet_sent) + """ + self.tx_irq_callback = callback + + def _tx_irq_callback(_): + if self.tx_irq_callback: + micropython.schedule(self.tx_irq_callback, None) + + self.driver.set_tx_callback(_tx_irq_callback) + + def standby(self) -> None: + """Set module to standby mode. + + Puts the CC1101 module into standby mode, consuming less power. + + UiFlow2 Code Block: + + |standby.png| + + MicroPython Code Block: + + .. code-block:: python + + module_cc1101_0.standby() + """ + self.driver.standby() + + def rx_irq_triggered(self) -> bool: + """Check IRQ trigger. + + :returns: Returns `True` if an interrupt service routine (ISR) has been triggered since the last send or receive started. + :rtype: bool + + UiFlow2 Code Block: + + |rx_irq_triggered.png| + + MicroPython Code Block: + + .. code-block:: python + + module_cc1101_0.rx_irq_triggered() + """ + return self.driver.rx_irq_triggered() + + def tx_irq_triggered(self) -> bool: + """Check TX IRQ trigger. + + :returns: Returns `True` if an interrupt service routine (ISR) has been triggered since the last send or a receive started. + :rtype: bool + + UiFlow2 Code Block: + + |tx_irq_triggered.png| + + MicroPython Code Block: + + .. code-block:: python + + module_cc1101_0.tx_irq_triggered() + """ + return self.driver.tx_irq_triggered() + + def get_rssi(self) -> float: + """Get last received RSSI in dBm. + + :returns: RSSI value in dBm + :rtype: float + + UiFlow2 Code Block: + + |get_rssi.png| + + MicroPython Code Block: + + .. code-block:: python + + rssi = module_cc1101_0.get_rssi() + """ + return self.driver.get_rssi() + + def get_lqi(self) -> int: + """Get last received LQI (Link Quality Indicator). + + :returns: LQI value (0-127) + :rtype: int + + UiFlow2 Code Block: + + |get_lqi.png| + + MicroPython Code Block: + + .. code-block:: python + + lqi = module_cc1101_0.get_lqi() + """ + return self.driver.get_lqi() + + def get_status(self) -> dict: + """Get module status. + + :returns: Dictionary containing MARC state, RSSI, and LQI + :rtype: dict + + UiFlow2 Code Block: + + |get_status.png| + + MicroPython Code Block: + + .. code-block:: python + + status = module_cc1101_0.get_status() + """ + return self.driver.get_status() diff --git a/m5stack/libs/module/manifest.py b/m5stack/libs/module/manifest.py index e3981eb0..b28dfa82 100644 --- a/m5stack/libs/module/manifest.py +++ b/m5stack/libs/module/manifest.py @@ -9,6 +9,7 @@ "ain4.py", "audio.py", "bala2.py", + "cc1101.py", "commu.py", "dc_motor.py", "display.py", From 5a1346da451262189d02426341a28c8b8e578832 Mon Sep 17 00:00:00 2001 From: hlym123 Date: Fri, 7 Nov 2025 12:25:52 +0800 Subject: [PATCH 306/322] libs/unit: Fix Unit Mini Scales some error and add examples. Signed-off-by: hlym123 --- docs/en/refs/unit.miniscale.ref | 41 +- docs/en/units/miniscale.rst | 319 ++++++--- .../zh_CN/LC_MESSAGES/units/miniscale.po | 623 +++++++++++++----- .../m5cores3_miniscales_base_example.m5f2 | 1 + .../m5cores3_miniscales_base_example.py | 81 +++ ...m5cores3_miniscales_calibrate_example.m5f2 | 1 + .../m5cores3_miniscales_calibrate_example.py | 198 ++++++ m5stack/libs/unit/miniscale.py | 139 ++-- 8 files changed, 1071 insertions(+), 332 deletions(-) create mode 100644 examples/unit/miniscales/m5cores3_miniscales_base_example.m5f2 create mode 100644 examples/unit/miniscales/m5cores3_miniscales_base_example.py create mode 100644 examples/unit/miniscales/m5cores3_miniscales_calibrate_example.m5f2 create mode 100644 examples/unit/miniscales/m5cores3_miniscales_calibrate_example.py diff --git a/docs/en/refs/unit.miniscale.ref b/docs/en/refs/unit.miniscale.ref index af8bb741..f004d932 100644 --- a/docs/en/refs/unit.miniscale.ref +++ b/docs/en/refs/unit.miniscale.ref @@ -4,31 +4,38 @@ :width: 200px .. |init.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/miniscales/init.png - .. |calibration.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/miniscales/calibration.png - .. |tare.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/miniscales/tare.png - -.. |getAverageFilterLevel.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/miniscales/getAverageFilterLevel.png - -.. |getEMAFilterAlpha.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/miniscales/getEMAFilterAlpha.png - -.. |getLowPassFilter.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/miniscales/getLowPassFilter.png - +.. |get_average_filter_level.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/miniscales/getAverageFilterLevel.png +.. |get_ema_filter_alpha.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/miniscales/getEMAFilterAlpha.png +.. |get_low_pass_filter.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/miniscales/getLowPassFilter.png .. |get_adc.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/miniscales/get_adc.png - .. |get_button.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/miniscales/get_button.png - .. |get_weight.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/miniscales/get_weight.png - .. |reset.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/miniscales/reset.png +.. |set_average_filter_level.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/miniscales/setAverageFilterLevel.png +.. |set_ema_filter_alpha.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/miniscales/setEMAFilterAlpha.png +.. |set_led.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/miniscales/setLed.png +.. |set_low_pass_filter.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/miniscales/setLowPassFilter.png + +.. |m5cores3_miniscales_base_example.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/miniscales/base_example.png -.. |setAverageFilterLevel.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/miniscales/setAverageFilterLevel.png +.. |m5cores3_miniscales_calibrate_example.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/miniscales/calibrate_example.png -.. |setEMAFilterAlpha.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/miniscales/setEMAFilterAlpha.png +.. |m5cores3_miniscales_base_example.m5f2| raw:: html -.. |setLed.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/miniscales/setLed.png + + m5cores3_miniscales_base_example.m5f2 + -.. |setLowPassFilter.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/miniscales/setLowPassFilter.png +.. |m5cores3_miniscales_calibrate_example.m5f2| raw:: html -.. |example.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/miniscales/example.png + + m5cores3_miniscales_calibrate_example.m5f2 + diff --git a/docs/en/units/miniscale.rst b/docs/en/units/miniscale.rst index e833fd8f..51a694c2 100644 --- a/docs/en/units/miniscale.rst +++ b/docs/en/units/miniscale.rst @@ -12,182 +12,325 @@ Support the following products: -Micropython Example:: +UiFlow2 Example +--------------- - import os, sys, io - import M5 - from M5 import * - import time - from unit import MiniScaleUnit +Basic Example +^^^^^^^^^^^^^ - i2c = I2C(0, scl=Pin(1), sda=Pin(2), freq=400000) - scale = MiniScaleUnit(i2c) - scale.setLed(255, 0, 0) - print(miniscale.weight) +Open the |m5cores3_miniscales_base_example.m5f2| project in UiFlow2. +This example demonstrates how to read and display weight values from the MiniScale unit. It sets up an average filter level of 10 for smoother readings and updates the weight display every second. -UIFLOW2 Example: +UiFlow2 Code Block: - |example.png| + |m5cores3_miniscales_base_example.png| +Example output: -.. only:: builder_html + None +Calibration Example +^^^^^^^^^^^^^^^^^^^ -class MiniScaleUnit +Open the |m5cores3_miniscales_calibrate_example.m5f2| project in UiFlow2. + +This example demonstrates the complete calibration process for the MiniScale unit. It guides users through a three-step calibration: first removing all items and recording the zero-point ADC value, then placing a 100g weight and recording that ADC value, and finally performing tare operation to set the zero point. After calibration, the weight is displayed with an average filter level of 5. + +UiFlow2 Code Block: + + |m5cores3_miniscales_calibrate_example.png| + +Example output: + + None + +MicroPython Example ------------------- -Constructors ---------------------------- +Basic Example +^^^^^^^^^^^^^ + +This example demonstrates how to read and display weight values from the MiniScale unit. It sets up an average filter level of 10 for smoother readings and updates the weight display every second. + +MicroPython Code Block: + + .. literalinclude:: ../../../examples/unit/miniscales/m5cores3_miniscales_base_example.py + :language: python + :linenos: + +Example output: + + None + +Calibration Example +^^^^^^^^^^^^^^^^^^^ + +This example demonstrates the complete calibration process for the MiniScale unit. It guides users through a three-step calibration: first removing all items and recording the zero-point ADC value, then placing a 100g weight and recording that ADC value, and finally performing tare operation to set the zero point. After calibration, the weight is displayed with an average filter level of 5. + +MicroPython Code Block: -.. class:: MiniScaleUnit(i2c0) + .. literalinclude:: ../../../examples/unit/miniscales/m5cores3_miniscales_calibrate_example.py + :language: python + :linenos: - Create an MiniScaleUnit object. +Example output: - - ``I2C0`` is I2C Port. + None - UIFLOW2: +**API** +------- + +class MiniScaleUnit +^^^^^^^^^^^^^^^^^^^ + +.. class:: unit.miniscale.MiniScaleUnit(i2c, address=0x26) + + Create a MiniScaleUnit object. + + :param I2C | PAHUBUnit i2c: The I2C or PAHUBUnit instance for communication. + :param int address: The I2C address of the MiniScale unit, default is 0x26. + + UiFlow2 Code Block: |init.png| + MicroPython Code Block: + + .. code-block:: python + + from unit import MiniScaleUnit + from hardware import I2C, Pin + + i2c0 = I2C(0, scl=Pin(1), sda=Pin(2), freq=100000) + miniscale_0 = MiniScaleUnit(i2c0) + + .. method:: adc + + Read raw ADC value (unprocessed). + + :returns: Raw ADC value (integer). + :rtype: int + + UiFlow2 Code Block: + + |get_adc.png| + + MicroPython Code Block: + + .. code-block:: python + + adc_value = miniscale_0.adc + + .. method:: weight + + Read current weight (grams). + + :returns: Actual weight (float after subtracting tare value). + :rtype: float + + UiFlow2 Code Block: + + |get_weight.png| + + MicroPython Code Block: + + .. code-block:: python + + weight_value = miniscale_0.weight + + .. method:: button + + Read button state. + + :returns: True if pressed, False if not pressed. + :rtype: bool + + UiFlow2 Code Block: + + |get_button.png| + + MicroPython Code Block: + + .. code-block:: python + + button_state = miniscale_0.button + + .. method:: tare() + + Tare operation. Record current weight as offset value, so subsequent weight readings use current as zero point. + + UiFlow2 Code Block: + + |tare.png| + + MicroPython Code Block: + + .. code-block:: python + + miniscale_0.tare() + + .. method:: set_led(r, g, b) + + Set RGB indicator color. + + :param int r: Red component (0~255). + :param int g: Green component (0~255). + :param int b: Blue component (0~255). -Methods ----------------------- + UiFlow2 Code Block: -.. method:: MiniScaleUnit.adc + |set_led.png| + MicroPython Code Block: - Gets the raw adc readout. + .. code-block:: python - UIFLOW2: + miniscale_0.set_led(255, 0, 0) - |get_adc.png| + .. method:: reset() -.. method:: MiniScaleUnit.weight + Reset module internal weight register (clear to zero). + UiFlow2 Code Block: - Gets the weight readout in grams. + |reset.png| - UIFLOW2: + MicroPython Code Block: - |get_weight.png| + .. code-block:: python + miniscale_0.reset() -.. method:: MiniScaleUnit.button + .. method:: calibration(weight1_g, weight1_adc, weight2_g, weight2_adc) + Calibrate module gain (GAP value). - Gets the button state. + Calibration process example: + 1. Reset offset + 2. Read no-load ADC (RawADC_0g) + 3. Place known weight (e.g., 100g) and read ADC (RawADC_100g) + 4. Calculate GAP = (RawADC_100g - RawADC_0g) / 100 + 5. Write to module to save calibration coefficient - UIFLOW2: + :param float weight1_g: Weight at first point (unit: g). + :param int weight1_adc: ADC value at first point. + :param float weight2_g: Weight at second point (unit: g). + :param int weight2_adc: ADC value at second point. + :raises ValueError: If two weights are equal. - |get_button.png| + UiFlow2 Code Block: -.. method:: MiniScaleUnit.tare() + |calibration.png| + MicroPython Code Block: - Tare the scale. + .. code-block:: python - UIFLOW2: + miniscale_0.calibration(0, adc_0, 100, adc_100) - |tare.png| + .. method:: set_low_pass_filter(enabled) -.. method:: MiniScaleUnit.set_led(r, g, b) + Enable or disable low-pass filter. - Sets the RGB LED color. + :param bool enabled: True to enable filter, False to disable filter. - - ``r``: Red value (0 - 255). - - ``g``: Green value (0 - 255). - - ``b``: Blue value (0 - 255). + UiFlow2 Code Block: - UIFLOW2: + |set_low_pass_filter.png| - |setLed.png| + MicroPython Code Block: -.. method:: MiniScaleUnit.reset + .. code-block:: python - Resets sensor. + miniscale_0.set_low_pass_filter(True) + .. method:: get_low_pass_filter() - UIFLOW2: + Get low-pass filter status. - |reset.png| + :returns: True if filter is enabled. + :rtype: bool -.. method:: MiniScaleUnit.calibration(weight1_g, weight1_adc, weight2_g, weight2_adc) + UiFlow2 Code Block: + |get_low_pass_filter.png| - Calibrates the MiniScale sensor. + MicroPython Code Block: - - ``weight1_g``: Weight1 in grams. - - ``weight1_adc``: Weight1 in ADC value. - - ``weight2_g``: Weight2 in grams. - - ``weight2_adc``: Weight2 in ADC value. + .. code-block:: python - calibration steps: + filter_enabled = miniscale_0.get_low_pass_filter() - 1. Reset sensor; - 2. Get adc, this is weight1_adc (should be zero). And weight1_g is also 0. - 3. Put some weight on it, then get adc, this is weight2_adc. And weight2_g is weight in gram you put on it. + .. method:: set_average_filter_level(level) + Set average filter level. - UIFLOW2: + :param int level: Average count level (0~50), higher value means smoother but more delay. + :raises ValueError: If out of range. - |calibration.png| + UiFlow2 Code Block: -.. method:: MiniScaleUnit.set_low_pass_filter(enable) + |set_average_filter_level.png| - Enables or disables the low pass filter. + MicroPython Code Block: + .. code-block:: python - UIFLOW2: + miniscale_0.set_average_filter_level(10) - |setLowPassFilter.png| + .. method:: get_average_filter_level() + Get average filter level. -.. method:: MiniScaleUnit.get_low_pass_filter + :returns: Current average filter level (integer). + :rtype: int - Returns the status of the low pass filter (enabled or not). + UiFlow2 Code Block: + |get_average_filter_level.png| - UIFLOW2: + MicroPython Code Block: - |getLowPassFilter.png| + .. code-block:: python -.. method:: MiniScaleUnit.set_average_filter_level(level) + filter_level = miniscale_0.get_average_filter_level() - Sets the level of the average filter. + .. method:: set_ema_filter_alpha(alpha) - - ``level``: Level of the average filter (0 - 50). Larger value for smoother result but more latency + Set exponential moving average (EMA) filter parameter. - UIFLOW2: + The EMA (Exponential Moving Average) filter is more sensitive to changes in data compared to the average filter. - |setAverageFilterLevel.png| + :param int alpha: EMA filter coefficient (0~99), smaller value means smoother but more response delay. + :raises ValueError: If out of range. -.. method:: MiniScaleUnit.get_average_filter_level + UiFlow2 Code Block: - Returns the level of the average filter. + |set_ema_filter_alpha.png| - UIFLOW2: + MicroPython Code Block: - |getAverageFilterLevel.png| + .. code-block:: python -.. method:: MiniScaleUnit.set_ema_filter_alpha(alpha) + miniscale_0.set_ema_filter_alpha(50) - Sets the alpha value for the EMA filter. + .. method:: get_ema_filter_alpha() - The EMA (Exponential Moving Average) filter is more sensitive to changes in data compared to the average filter. + Get EMA filter coefficient. - - ``alpha``: Alpha value for the EMA filter (0 - 99). Smaller value for smoother result but more latency + :returns: Current EMA alpha value (integer). + :rtype: int - UIFLOW2: + UiFlow2 Code Block: - |setEMAFilterAlpha.png| + |get_ema_filter_alpha.png| -.. method:: MiniScaleUnit.get_ema_filter_alpha + MicroPython Code Block: - Returns the alpha value for the EMA filter. + .. code-block:: python - UIFLOW2: + ema_alpha = miniscale_0.get_ema_filter_alpha() - |getEMAFilterAlpha.png| diff --git a/docs/locales/zh_CN/LC_MESSAGES/units/miniscale.po b/docs/locales/zh_CN/LC_MESSAGES/units/miniscale.po index dbaa45f1..4dc22e3b 100644 --- a/docs/locales/zh_CN/LC_MESSAGES/units/miniscale.po +++ b/docs/locales/zh_CN/LC_MESSAGES/units/miniscale.po @@ -9,7 +9,7 @@ msgid "" msgstr "" "Project-Id-Version: UIFlow2 Programming Guide \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-04-18 15:58+0800\n" +"POT-Creation-Date: 2025-11-07 12:16+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language: zh_CN\n" @@ -20,11 +20,11 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.16.0\n" -#: ../../en/units/miniscale.rst:2 cb191df082804ef4a8ff4daaa208f7b9 +#: ../../en/units/miniscale.rst:2 7c77e8b42ed4463f99680d9da858fae7 msgid "Miniscale Unit" msgstr "" -#: ../../en/units/miniscale.rst:6 553b5fe9c6994505a517b4e4240f4072 +#: ../../en/units/miniscale.rst:6 88e5d198bebf4a9f8f2d450d0ea59bb4 msgid "" "The ``Miniscale`` class is designed for interfacing with a mini scale " "weight sensor, which includes a HX711 22-bit ADC. This sensor is capable " @@ -34,299 +34,429 @@ msgstr "" "``Miniscale`` 是为微型称重传感器设计的接口,其中包括一个HX711 " "22位ADC。此传感器能够测量重量,并且还包括其他功能,如LED控制和各种滤波器。" -#: ../../en/units/miniscale.rst:8 f0bceb30305e4cb4b6fb9b50eee58794 +#: ../../en/units/miniscale.rst:8 f37296c1ddc64868bc9520a209a3f014 msgid "Support the following products:" msgstr "支持以下产品:" -#: ../../en/units/miniscale.rst:11 b9710dba3ec8471d828122615b69258a +#: ../../en/units/miniscale.rst:11 1b922bbb8e1f439cae683e8f150e9b3e msgid "|MINISCALE|" msgstr "" -#: ../../en/refs/unit.miniscale.ref 91b21480c07742dd84c62d680fc06a42 +#: ../../en/refs/unit.miniscale.ref 40591d2ddae3492ca95763f1a42e1047 msgid "MINISCALE" msgstr "" -#: ../../en/units/miniscale.rst:15 1b52a066919b4bfd81c9240d2a274090 -msgid "Micropython Example::" -msgstr "" +#: ../../en/units/miniscale.rst:16 a78db993884643999ca7f65612491246 +msgid "UiFlow2 Example" +msgstr "UiFlow2 应用示例" -#: ../../en/units/miniscale.rst:29 9fee616278fc4b89864b4e4a56b24619 -msgid "UIFLOW2 Example:" -msgstr "" +#: ../../en/units/miniscale.rst:19 ../../en/units/miniscale.rst:52 +#: a78db993884643999ca7f65612491246 +msgid "Basic Example" +msgstr "基础示例" -#: ../../en/units/miniscale.rst:31 cdc1b0091a5e4522ac0c3dd3514167eb -msgid "|example.png|" -msgstr "" +#: ../../en/units/miniscale.rst:21 2a5e8a909e0540f69292618d672377ca +msgid "Open the |m5cores3_miniscales_base_example.m5f2| project in UiFlow2." +msgstr "在 UiFlow2 上打开 |m5cores3_miniscales_base_example.m5f2| 项目。" -#: ../../en/refs/unit.miniscale.ref:34 09c6f381d09e47b38a2b435d1da06254 -msgid "example.png" -msgstr "" - -#: ../../en/units/miniscale.rst:38 e646f58bb6f244ebbb7955f910c2f05a +#: ../../en/units/miniscale.rst:23 ../../en/units/miniscale.rst:54 +#: 05c33406b2244983b01b801b37649df6 8cfb014492654a1db3dc4e640a39e148 +msgid "" +"This example demonstrates how to read and display weight values from the " +"MiniScale unit. It sets up an average filter level of 10 for smoother " +"readings and updates the weight display every second." +msgstr "" +"该示例演示了如何从 MiniScale 单元读取和显示重量值。它设置平均滤波等级为 10 " +"以获得更平滑的读数,并每秒更新一次重量显示。" + +#: ../../en/units/miniscale.rst:25 ../../en/units/miniscale.rst:40 +#: ../../en/units/miniscale.rst:95 ../../en/units/miniscale.rst:116 +#: ../../en/units/miniscale.rst:133 ../../en/units/miniscale.rst:150 +#: ../../en/units/miniscale.rst:164 ../../en/units/miniscale.rst:182 +#: ../../en/units/miniscale.rst:196 ../../en/units/miniscale.rst:223 +#: ../../en/units/miniscale.rst:239 ../../en/units/miniscale.rst:256 +#: ../../en/units/miniscale.rst:273 ../../en/units/miniscale.rst:290 +#: ../../en/units/miniscale.rst:309 ../../en/units/miniscale.rst:326 +#: 14c653d05a394673a4dc74b9d6b4d94f 1578872fef644beaa0dfc88cbafe8976 +#: 1a15e069bb394cd2835a4ac1dbdfe7e8 2193f33656304e04b4ee54fbcb769c45 +#: 38461c47d991405a801c173007e59e11 4ead829ecef54b578bd2a6e78efa37c2 +#: 73cfd33313014de89d227b2afc655882 942ae3c05b2645f8913ca620f62df162 +#: 9929f6de35b44cc7acc74dd8453109dc 9a1c69c6ceab47738418c218b96f825f +#: 9a30dbb99f2c4246b62f10489a5c74a5 9ca1a5ec05a846898e730ea87aef0ad0 +#: a4b3d0391ebb4d85a7e99a0c7031d6e7 c1fd596ddd694911b2bd198b18c7f18d +#: cccc641f741b4bed9a7f691daed2c10d d433503ed3d8426886c10b19f7e132e7 +msgid "UiFlow2 Code Block:" +msgstr "UiFlow2 代码块:" + +#: ../../en/units/miniscale.rst:27 87be289caafe45f6a7c7064861f3ac3f +msgid "|m5cores3_miniscales_base_example.png|" +msgstr "" + +#: ../../en/refs/unit.miniscale.ref:21 787936fd9546470ca3acbc0107542b32 +msgid "m5cores3_miniscales_base_example.png" +msgstr "" + +#: ../../en/units/miniscale.rst:29 ../../en/units/miniscale.rst:44 +#: ../../en/units/miniscale.rst:62 ../../en/units/miniscale.rst:77 +#: 34e81d316305457b90b4897dfbd10b55 ac5c9812d98e4b169145504f96f82453 +#: f07f478f94e84109a44836932a60a300 f4413aab1ceb4147b62478ef4343875a +msgid "Example output:" +msgstr "示例输出:" + +#: ../../en/units/miniscale.rst:31 ../../en/units/miniscale.rst:46 +#: ../../en/units/miniscale.rst:64 ../../en/units/miniscale.rst:79 +#: 58b81e5fb56049fda9220db18e10a680 b4d5ab989e594dc193c4b69dd639c20c +#: c1b6bd64f82145c3a59278f3d4e0a13e d49892787f964396aa3e7cc585cd5ad9 +msgid "None" +msgstr "无" + +#: ../../en/units/miniscale.rst:34 ../../en/units/miniscale.rst:67 +#: ac36cc4bd8474c0d980fb79403e42553 +msgid "Calibration Example" +msgstr "校准示例" + +#: ../../en/units/miniscale.rst:36 2bc980a47b6b4c4b85524a4d534e4e21 +msgid "Open the |m5cores3_miniscales_calibrate_example.m5f2| project in UiFlow2." +msgstr "在 UiFlow2 上打开 |m5cores3_miniscales_calibrate_example.m5f2| 项目。" + +#: ../../en/units/miniscale.rst:38 ../../en/units/miniscale.rst:69 +#: 096b3c83272643c68324115fdb914493 1ff8ee98e1174805a9d1a220df480ad5 +msgid "" +"This example demonstrates the complete calibration process for the " +"MiniScale unit. It guides users through a three-step calibration: first " +"removing all items and recording the zero-point ADC value, then placing a" +" 100g weight and recording that ADC value, and finally performing tare " +"operation to set the zero point. After calibration, the weight is " +"displayed with an average filter level of 5." +msgstr "" +"该示例演示了 MiniScale 单元的完整校准过程。它引导用户完成三步校准:首先移除所有" +"物品并记录零点 ADC 值,然后放置 100g 重量并记录该 ADC 值,最后执行去皮操作以设" +"置零点。校准后,重量以平均滤波等级 5 显示。" + +#: ../../en/units/miniscale.rst:42 68ae0df140194f9c99e109180cdf510d +msgid "|m5cores3_miniscales_calibrate_example.png|" +msgstr "" + +#: ../../en/refs/unit.miniscale.ref:23 61abf029092e4a42b2cc65b20c91e08f +msgid "m5cores3_miniscales_calibrate_example.png" +msgstr "" + +#: ../../en/units/miniscale.rst:49 49ef68234a4c4fae93bb6cda3e5d52e3 +msgid "MicroPython Example" +msgstr "MicroPython 应用示例" + +#: ../../en/units/miniscale.rst:56 ../../en/units/miniscale.rst:71 +#: ../../en/units/miniscale.rst:99 ../../en/units/miniscale.rst:120 +#: ../../en/units/miniscale.rst:137 ../../en/units/miniscale.rst:154 +#: ../../en/units/miniscale.rst:168 ../../en/units/miniscale.rst:186 +#: ../../en/units/miniscale.rst:200 ../../en/units/miniscale.rst:227 +#: ../../en/units/miniscale.rst:243 ../../en/units/miniscale.rst:260 +#: ../../en/units/miniscale.rst:277 ../../en/units/miniscale.rst:294 +#: ../../en/units/miniscale.rst:313 ../../en/units/miniscale.rst:330 +#: 49ef68234a4c4fae93bb6cda3e5d52e3 +msgid "MicroPython Code Block:" +msgstr "MicroPython 代码块:" + +#: ../../en/units/miniscale.rst:83 bb892db6ac7a4678b04a482f3de67352 +msgid "**API**" +msgstr "**API**" + +#: ../../en/units/miniscale.rst:86 c80da2ea4b80471b95e4fc10e9b6047d msgid "class MiniScaleUnit" msgstr "" -#: ../../en/units/miniscale.rst:41 08bb6f07f5ca47988a600d5c2b603472 -msgid "Constructors" +#: ../../en/units/miniscale.rst:90 08b5dab8f8cd4a82979367cc3abe5c34 +msgid "Create a MiniScaleUnit object." +msgstr "创建一个 MiniScaleUnit 对象。" + +#: ../../en/units/miniscale.rst 552182180ea34bfa89738326523b3e87 +#: 6ebe04c0b89d40a7a0915fd6080f3aeb cb17bbcc97e8416d9b8ba92dd39effa3 +#: cf4e2a5a98954d4eba48f888c91f7d14 da9d86fae4ec4a48b6601d82580bab1d +#: ebff48803b2c4076963be88506d4b67d +msgid "Parameters" msgstr "" -#: ../../en/units/miniscale.rst:45 b5d0957900f946c7aadbeeb7bbbd1bfb -msgid "Create an MiniScaleUnit object." -msgstr "创建一个 MiniScaleUnit 对象。" +#: ../../en/units/miniscale.rst:92 0f22296dc2f9429c89ab3470ec6bee7a +msgid "The I2C or PAHUBUnit instance for communication." +msgstr "用于通信的 I2C 或 PAHUBUnit 实例。" -#: ../../en/units/miniscale.rst:47 bc534ecbe9864c2f978ca0595c303a32 -msgid "``I2C0`` is I2C Port." -msgstr "``I2C0`` is I2C Port." - -#: ../../en/units/miniscale.rst:50 ../../en/units/miniscale.rst:63 -#: ../../en/units/miniscale.rst:72 ../../en/units/miniscale.rst:82 -#: ../../en/units/miniscale.rst:91 ../../en/units/miniscale.rst:103 -#: ../../en/units/miniscale.rst:112 ../../en/units/miniscale.rst:133 -#: ../../en/units/miniscale.rst:142 ../../en/units/miniscale.rst:152 -#: ../../en/units/miniscale.rst:162 ../../en/units/miniscale.rst:170 -#: ../../en/units/miniscale.rst:182 ../../en/units/miniscale.rst:190 -#: 0a550e55796245cf9a4ee69e640070c7 2a76747a028546a38a925b7be305d18f -#: 3abd720fc10b4358b279ad641f1a2521 3ceba0716e274d8fb4ac902359c1625c -#: 3f41ca6121764710ada34fd71904b26b 4be817bc3f6e4afa9d5a88b4305d2bf3 -#: 77cb52295f9d42e1afe4f7b057e2ac1f c39a3c532aee4514824c5747c21091d5 -#: c7ba30908b6b4c82be444f8a64e020cd d5aaf28a1b744fc9a67bbef7766c4f14 -#: d6b4b1a83efe4a42a9124a326cfdd787 dd5b3a652a834f7094052cd592e93320 -#: dea68305a79b45f9b06ddabd684d6278 f03a26142c634a0899934f02c9744248 -msgid "UIFLOW2:" -msgstr "" - -#: ../../en/units/miniscale.rst:52 30c2a3dc405c48319189d77a676a4f0b +#: ../../en/units/miniscale.rst:93 8f8f615f784e4a4482a77bdbcb2e8bb8 +msgid "The I2C address of the MiniScale unit, default is 0x26." +msgstr "MiniScale 单元的 I2C 地址,默认为 0x26。" + +#: ../../en/units/miniscale.rst:97 87c8734544a84da49c8a77489966d42c msgid "|init.png|" msgstr "" -#: ../../en/refs/unit.miniscale.ref:6 0e745bc79f6e4e1dbe18bd8660bce5ec +#: ../../en/refs/unit.miniscale.ref:6 067dea94d6064570982934ddd7dfadb8 msgid "init.png" msgstr "" -#: ../../en/units/miniscale.rst:56 f9e769c7df844b319e7d638cd7cb7a6c -msgid "Methods" +#: ../../en/units/miniscale.rst:111 6c083041ab2c4a31b48a9a85cf1604c1 +msgid "Read raw ADC value (unprocessed)." +msgstr "读取原始 ADC 值(未经处理)。" + +#: ../../en/units/miniscale.rst db931597993c43549d1a55f44c67144f +msgid "Returns" msgstr "" -#: ../../en/units/miniscale.rst:61 ae2ed66e50564eb886a79f17716ee843 -msgid "Gets the raw adc readout." -msgstr "获取原始的 ADC 读数。" +#: ../../en/units/miniscale.rst:113 e56feb00c2db4168adf3cad43f83854b +msgid "Raw ADC value (integer)." +msgstr "原始 ADC 值(整数)。" + +#: ../../en/units/miniscale.rst 0717064469e446e6920be1167318133b +#: 92efedff6c5b4ae5acff5d45431c12d1 b7d8309bc0ea436493b764fd1240ed82 +#: bfedb1595834468fa1d09c97d359a687 c1cca3a957904065abcce1d15cf9768d +#: f0fc20c102144ff6b1fe0fa13dd1c1cc +msgid "Return type" +msgstr "" -#: ../../en/units/miniscale.rst:65 fa540129ae554bf3827b6f38f289d7b2 +#: ../../en/units/miniscale.rst:118 096a4a5073f74b6c8a4f2429dfd1d5f3 msgid "|get_adc.png|" msgstr "" -#: ../../en/refs/unit.miniscale.ref:18 78c6c950063142e7bf43557198204bb6 +#: ../../en/refs/unit.miniscale.ref:12 8c431521fc1246f2a817bd9c74f910b7 msgid "get_adc.png" msgstr "" -#: ../../en/units/miniscale.rst:70 1d940438409545abad533129e5d79024 -msgid "Gets the weight readout in grams." -msgstr "获取以克为单位的重量读数。" +#: ../../en/units/miniscale.rst:128 b5c9ca2f23964c55b1fa7c8d3913a809 +msgid "Read current weight (grams)." +msgstr "读取当前重量(克)。" + +#: ../../en/units/miniscale.rst:130 ad690271311a40b4ae55aee54d2ade9a +msgid "Actual weight (float after subtracting tare value)." +msgstr "实际重量(减去去皮值后的浮点数)。" -#: ../../en/units/miniscale.rst:74 bec7efa63604461192b57d435a5a18f8 +#: ../../en/units/miniscale.rst:135 54e92b69f5cb4839b0245e88be7c6138 msgid "|get_weight.png|" msgstr "" -#: ../../en/refs/unit.miniscale.ref:22 800f45d58f47461e918224233b8f3d90 +#: ../../en/refs/unit.miniscale.ref:14 09ed56fa9313495ab980e78b4d7720d2 msgid "get_weight.png" msgstr "" -#: ../../en/units/miniscale.rst:80 b585e00644114f99ae4816c4825ac016 -msgid "Gets the button state." -msgstr "获取按钮状态。 " +#: ../../en/units/miniscale.rst:145 6f78990f7569403e8a5874f34d1ea270 +msgid "Read button state." +msgstr "读取按钮状态。" -#: ../../en/units/miniscale.rst:84 baf8e2a22d404ca9ad3d115ab8429cbe +#: ../../en/units/miniscale.rst:147 de1fdeb20c7147e29438605991c7e7a0 +msgid "True if pressed, False if not pressed." +msgstr "如果按下返回 True,如果未按下返回 False。" + +#: ../../en/units/miniscale.rst:152 1572ca097764427ab1a11d5803c65d0e msgid "|get_button.png|" msgstr "" -#: ../../en/refs/unit.miniscale.ref:20 dcf39b4c0669439fa6ed295ccddb5586 +#: ../../en/refs/unit.miniscale.ref:13 4be36b439dd443e7b14d21e747ad7609 msgid "get_button.png" msgstr "" -#: ../../en/units/miniscale.rst:89 ac94d56cfd634f5e9289907823c30096 -msgid "Tare the scale." +#: ../../en/units/miniscale.rst:162 9c50eb4b42d64e5fb5f81fe7e6d05209 +msgid "" +"Tare operation. Record current weight as offset value, so subsequent " +"weight readings use current as zero point." msgstr "" +"去皮操作。记录当前重量作为偏移值,以便后续重量读数以当前为零点。" -#: ../../en/units/miniscale.rst:93 3b098a901e0743019e932684ecd94b29 +#: ../../en/units/miniscale.rst:166 1082fb65370642b2b81fb2e1a4c6ea75 msgid "|tare.png|" msgstr "" -#: ../../en/refs/unit.miniscale.ref:10 fc8f4b7b5ffd44fe8cce785c26faebf0 +#: ../../en/refs/unit.miniscale.ref:8 867b032deb484292a9448fc883c709e7 msgid "tare.png" msgstr "" -#: ../../en/units/miniscale.rst:97 41177a0bb1ee45d59a3f994bc5d8ee10 -msgid "Sets the RGB LED color." -msgstr "设置 RGB LED 的颜色。" +#: ../../en/units/miniscale.rst:176 d9767c91c94b4b2896eb807eb3b5c155 +msgid "Set RGB indicator color." +msgstr "设置 RGB 指示灯颜色。" -#: ../../en/units/miniscale.rst:99 6040786e7aa04237bf268060f9a51c47 -msgid "``r``: Red value (0 - 255)." -msgstr "``r``: Red value (0 - 255)." +#: ../../en/units/miniscale.rst:178 0874dd1cc0c6433caacd7a8e0da87ee2 +msgid "Red component (0~255)." +msgstr "红色分量(0~255)。" -#: ../../en/units/miniscale.rst:100 e78c8ac199b5493fa2786eb2bb0fb462 -msgid "``g``: Green value (0 - 255)." -msgstr "``g``: Green value (0 - 255)." +#: ../../en/units/miniscale.rst:179 b73e0fb1926f48dda32f89313cf78f32 +msgid "Green component (0~255)." +msgstr "绿色分量(0~255)。" -#: ../../en/units/miniscale.rst:101 183968723565492784ce3b772d0ea76e -msgid "``b``: Blue value (0 - 255)." -msgstr "``b``: Blue value (0 - 255)." +#: ../../en/units/miniscale.rst:180 7ccf8de7fffa4b16903a002b61be73d6 +msgid "Blue component (0~255)." +msgstr "蓝色分量(0~255)。" -#: ../../en/units/miniscale.rst:105 f2089a2f6c5e4b99be34957a45dd9ace -msgid "|setLed.png|" +#: ../../en/units/miniscale.rst:184 0caf783df77c460c85673603ef7dc2dc +msgid "|set_led.png|" msgstr "" -#: ../../en/refs/unit.miniscale.ref:30 669a4f1cf6ce44eb96f9bcd99df1bc27 -msgid "setLed.png" +#: ../../en/refs/unit.miniscale.ref:18 1bf4a8c914124a0b99b4fc654fa3c987 +msgid "set_led.png" msgstr "" -#: ../../en/units/miniscale.rst:109 a4472c7367e041ecbfaea91472332bca -msgid "Resets sensor." -msgstr "重置传感器。" +#: ../../en/units/miniscale.rst:194 8fe3937fdf354efda27b620974f72279 +msgid "Reset module internal weight register (clear to zero)." +msgstr "重置模块内部重量寄存器(清零)。" -#: ../../en/units/miniscale.rst:114 7968b647e0334557814eb05bdfc36f63 +#: ../../en/units/miniscale.rst:198 6be04059408e43f6a09ec5af9847d303 msgid "|reset.png|" msgstr "" -#: ../../en/refs/unit.miniscale.ref:24 f53a481663c44e3390cf4b0c7c979ede +#: ../../en/refs/unit.miniscale.ref:15 42bd80acf95a4eb295ba96312f0a2e30 msgid "reset.png" msgstr "" -#: ../../en/units/miniscale.rst:119 140108aedf3d47b985fa9a30b2cd8a5a -msgid "Calibrates the MiniScale sensor." -msgstr "校准微型称重传感器。" +#: ../../en/units/miniscale.rst:208 87f23887373040c5b71817311a64f826 +msgid "Calibrate module gain (GAP value)." +msgstr "校准模块增益(GAP 值)。" -#: ../../en/units/miniscale.rst:121 fe9fd18345ab425591cb11ca5968cc1a -msgid "``weight1_g``: Weight1 in grams." +#: ../../en/units/miniscale.rst:210 36060ee885d14bbc9cb110aaa7a9737d +msgid "" +"Calibration process example: 1. Reset offset 2. Read no-load ADC " +"(RawADC_0g) 3. Place known weight (e.g., 100g) and read ADC (RawADC_100g)" +" 4. Calculate GAP = (RawADC_100g - RawADC_0g) / 100 5. Write to module to" +" save calibration coefficient" msgstr "" +"校准流程示例:1. 重置偏移 2. 读取空载 ADC(RawADC_0g)3. 放置已知重量(例如 " +"100g)并读取 ADC(RawADC_100g)4. 计算 GAP = (RawADC_100g - RawADC_0g) / 100 " +"5. 写入模块保存校准系数" -#: ../../en/units/miniscale.rst:122 4dfeea577e474dea844d407b4848001b -msgid "``weight1_adc``: Weight1 in ADC value." -msgstr "" +#: ../../en/units/miniscale.rst:217 3866b32843f840b89376954859254e03 +msgid "Weight at first point (unit: g)." +msgstr "第一点的重量(单位:g)。" -#: ../../en/units/miniscale.rst:123 06402f3db0594dffa89ad6c81fce2795 -msgid "``weight2_g``: Weight2 in grams." -msgstr "" +#: ../../en/units/miniscale.rst:218 ccb6fee3931a4e58b6c5a55436e6a709 +msgid "ADC value at first point." +msgstr "第一点的 ADC 值。" -#: ../../en/units/miniscale.rst:124 cd644f8f2fe84e38ade3a23db990d145 -msgid "``weight2_adc``: Weight2 in ADC value." -msgstr "" - -#: ../../en/units/miniscale.rst:126 69bcd897885946a2b78b8cc54a8a83b3 -msgid "calibration steps:" -msgstr "校准步骤:" +#: ../../en/units/miniscale.rst:219 54be703ba13246629035639a201ac887 +msgid "Weight at second point (unit: g)." +msgstr "第二点的重量(单位:g)。" -#: ../../en/units/miniscale.rst:128 f71ab2747e1c4036b4418a85237af5a7 -msgid "Reset sensor;" -msgstr "重置传感器" +#: ../../en/units/miniscale.rst:220 719d312f7d3241e6a92997d8a42193a5 +msgid "ADC value at second point." +msgstr "第二点的 ADC 值。" -#: ../../en/units/miniscale.rst:129 8fddb44435b44810a098de9f76272777 -msgid "Get adc, this is weight1_adc (should be zero). And weight1_g is also 0." -msgstr "得到adc,这是weight1_adc(应该是零)。而weight1_g也是0。" +#: ../../en/units/miniscale.rst 06310b054edd4b1e8443acc76836ed14 +#: 5052612a0ea340eaa14b34edfde4a070 66ff7f3cf0ef49e8a22b8728d8323901 +msgid "Raises" +msgstr "" -#: ../../en/units/miniscale.rst:130 80779f98aee34659b9b7f347e639e7dc -msgid "" -"Put some weight on it, then get adc, this is weight2_adc. And weight2_g " -"is weight in gram you put on it." -msgstr "AI润色104/5000给它加一些权重,然后得到adc,这是weight2_adc。而weight2_g是以克为单位的重量。" +#: ../../en/units/miniscale.rst:221 569f75879aed42eb96bf8b303f942c0e +msgid "If two weights are equal." +msgstr "如果两个重量相等。" -#: ../../en/units/miniscale.rst:135 2ac5a189c3334d40a565cd8bce5c6291 -#, fuzzy +#: ../../en/units/miniscale.rst:225 8bbc2b61921247298377dd24d7f44962 msgid "|calibration.png|" -msgstr "校准步骤:" +msgstr "" -#: ../../en/refs/unit.miniscale.ref:8 3cde5a5480494c94bb03489f2f50a0fe -#, fuzzy +#: ../../en/refs/unit.miniscale.ref:7 61abf029092e4a42b2cc65b20c91e08f msgid "calibration.png" -msgstr "校准步骤:" +msgstr "" -#: ../../en/units/miniscale.rst:139 335d4f778e0f4449979059b04156120d -msgid "Enables or disables the low pass filter." -msgstr "启用或禁用低通滤波器" +#: ../../en/units/miniscale.rst:235 dbb0ac51f45243408c89d4d990f91c14 +msgid "Enable or disable low-pass filter." +msgstr "启用或禁用低通滤波器。" -#: ../../en/units/miniscale.rst:144 54a8c46824ae46c088bc2c11223ee916 -msgid "|setLowPassFilter.png|" +#: ../../en/units/miniscale.rst:237 1084ba2ffd4f4647917b51436553dc41 +msgid "True to enable filter, False to disable filter." +msgstr "True 启用滤波器,False 禁用滤波器。" + +#: ../../en/units/miniscale.rst:241 caddcb3ebe18494d9a6354558c267e68 +msgid "|set_low_pass_filter.png|" msgstr "" -#: ../../en/refs/unit.miniscale.ref:32 f995623c681446948c7e8899faa113dd -msgid "setLowPassFilter.png" +#: ../../en/refs/unit.miniscale.ref:19 68e1280944834911970c138a98d7ae26 +msgid "set_low_pass_filter.png" msgstr "" -#: ../../en/units/miniscale.rst:149 f35a8a925134409493be750cd65723f6 -msgid "Returns the status of the low pass filter (enabled or not)." -msgstr "返回低通滤波器的状态(是否启用)" +#: ../../en/units/miniscale.rst:251 b5d320f9bce2438fa13b2f4061d7cc2e +msgid "Get low-pass filter status." +msgstr "获取低通滤波器状态。" + +#: ../../en/units/miniscale.rst:253 076eb9573841485681ed6a8f65f23d4e +msgid "True if filter is enabled." +msgstr "如果滤波器已启用返回 True。" -#: ../../en/units/miniscale.rst:154 4d39da164bd948e9b064b2f04f291a0f -msgid "|getLowPassFilter.png|" +#: ../../en/units/miniscale.rst:258 9e13d725101d41d2917a4f4d853fe239 +msgid "|get_low_pass_filter.png|" msgstr "" -#: ../../en/refs/unit.miniscale.ref:16 edef98ebff8f456984cfad27b626c244 -msgid "getLowPassFilter.png" +#: ../../en/refs/unit.miniscale.ref:11 2de395e53c6d47c0aa999782ec9c0ae7 +msgid "get_low_pass_filter.png" msgstr "" -#: ../../en/units/miniscale.rst:158 79d3626162d8434aa1349bcc5a9764ff -msgid "Sets the level of the average filter." -msgstr "设置平均滤波器的级别" +#: ../../en/units/miniscale.rst:268 ea986430e9a44d86a68b95b52d4d2adb +msgid "Set average filter level." +msgstr "设置平均滤波等级。" -#: ../../en/units/miniscale.rst:160 6d7dc6ab634f4aa29a03f55bd2efd46b -msgid "" -"``level``: Level of the average filter (0 - 50). Larger value for " -"smoother result but more latency" -msgstr "``level``:平均过滤器的电平(0 - 50)。更大的值可以获得更平滑的结果,但延迟更长" +#: ../../en/units/miniscale.rst:270 04d8d88b462443b183d4c0a2d5570475 +msgid "Average count level (0~50), higher value means smoother but more delay." +msgstr "平均次数等级(0~50),值越大平滑度越高但延迟越大。" + +#: ../../en/units/miniscale.rst:271 ../../en/units/miniscale.rst:307 +#: 2cabed15083d45959718d71d95f5c294 bc43b42d63944f819158f828458c0ac1 +msgid "If out of range." +msgstr "如果超出范围。" -#: ../../en/units/miniscale.rst:164 1d58c8234ed446ab87fb8c467f76b7ee -msgid "|setAverageFilterLevel.png|" +#: ../../en/units/miniscale.rst:275 ea986430e9a44d86a68b95b52d4d2adb +msgid "|set_average_filter_level.png|" msgstr "" -#: ../../en/refs/unit.miniscale.ref:26 cc8d797eaf6140bc81d407f0eb623b92 -msgid "setAverageFilterLevel.png" +#: ../../en/refs/unit.miniscale.ref:16 b4a281005f554221be346c3b7750af8d +msgid "set_average_filter_level.png" msgstr "" -#: ../../en/units/miniscale.rst:168 d5973abdcdc341dabaaa2bfd6aff85d9 -msgid "Returns the level of the average filter." -msgstr "返回平均滤波器的级别" +#: ../../en/units/miniscale.rst:285 ea986430e9a44d86a68b95b52d4d2adb +msgid "Get average filter level." +msgstr "获取平均滤波等级。" + +#: ../../en/units/miniscale.rst:287 1c809f118178436a903ca32ffc4e383d +msgid "Current average filter level (integer)." +msgstr "当前平均滤波等级(整数)。" -#: ../../en/units/miniscale.rst:172 e0fea83835cf4c17a37b0c94e8cf4734 -msgid "|getAverageFilterLevel.png|" +#: ../../en/units/miniscale.rst:292 887c760e9c484452b7e361e1dc7ea853 +msgid "|get_average_filter_level.png|" msgstr "" -#: ../../en/refs/unit.miniscale.ref:12 904db8a2384c4cc08c3b5b4e37f9336f -msgid "getAverageFilterLevel.png" +#: ../../en/refs/unit.miniscale.ref:9 d2430dc5c38f45acb17a39c585e8d4b6 +msgid "get_average_filter_level.png" msgstr "" -#: ../../en/units/miniscale.rst:176 57f5c9f52fdb4921ae534cf247edbbad -msgid "Sets the alpha value for the EMA filter." -msgstr "设置EMA滤波器的alpha值。" +#: ../../en/units/miniscale.rst:302 991807a695b04cdd8d1d62e8911aa3c9 +msgid "Set exponential moving average (EMA) filter parameter." +msgstr "设置指数滑动平均(EMA)滤波参数。" -#: ../../en/units/miniscale.rst:178 8327385268954b628b39f59509c41b72 +#: ../../en/units/miniscale.rst:304 991807a695b04cdd8d1d62e8911aa3c9 msgid "" "The EMA (Exponential Moving Average) filter is more sensitive to changes " "in data compared to the average filter." msgstr "与平均滤波器相比,EMA(指数移动平均)滤波器对数据的变化更敏感。" -#: ../../en/units/miniscale.rst:180 a90ec332c6494a0898cba5b89c716933 +#: ../../en/units/miniscale.rst:306 1e4d640e7e0a46b499b03c38d4e8f9be msgid "" -"``alpha``: Alpha value for the EMA filter (0 - 99). Smaller value for " -"smoother result but more latency" -msgstr "`` alpha``: EMA过滤器的alpha值(0 - 99)。较小的值可以获得更平滑的结果,但会增加延迟" +"EMA filter coefficient (0~99), smaller value means smoother but more " +"response delay." +msgstr "EMA 滤波系数(0~99),值越小平滑度越高但响应延迟越大。" -#: ../../en/units/miniscale.rst:184 92491b1188d7436fa428cff094913225 -msgid "|setEMAFilterAlpha.png|" +#: ../../en/units/miniscale.rst:311 eab364913d4446bb998b94eaff2f355e +msgid "|set_ema_filter_alpha.png|" msgstr "" -#: ../../en/refs/unit.miniscale.ref:28 8a457529a6fc43a9bb1d65e830ecb1df -msgid "setEMAFilterAlpha.png" +#: ../../en/refs/unit.miniscale.ref:17 a15bb2f9f44946f7a8143ced5103be62 +msgid "set_ema_filter_alpha.png" msgstr "" -#: ../../en/units/miniscale.rst:188 366a404460d646bf8e0acce59ee27ea1 -msgid "Returns the alpha value for the EMA filter." -msgstr "返回EMA滤波器的alpha值。" +#: ../../en/units/miniscale.rst:321 8197d9100d10415c926b0a2debcc1500 +msgid "Get EMA filter coefficient." +msgstr "获取 EMA 滤波系数。" -#: ../../en/units/miniscale.rst:192 6c900ab9aa534a729385e84785d53951 -msgid "|getEMAFilterAlpha.png|" +#: ../../en/units/miniscale.rst:323 3548cff82c2940cba88d1721f4194587 +msgid "Current EMA alpha value (integer)." +msgstr "当前 EMA alpha 值(整数)。" + +#: ../../en/units/miniscale.rst:328 d9e1fa9043ab4c958fd010367c367f5f +msgid "|get_ema_filter_alpha.png|" msgstr "" -#: ../../en/refs/unit.miniscale.ref:14 8ddbbea257524c60953feaa004754501 -msgid "getEMAFilterAlpha.png" +#: ../../en/refs/unit.miniscale.ref:10 aee5624f872e43a3a92435fb33609bbc +msgid "get_ema_filter_alpha.png" msgstr "" #~ msgid "|example.svg|" @@ -419,3 +549,142 @@ msgstr "" #~ msgid "getEMAFilterAlpha.svg" #~ msgstr "" +#~ msgid "Micropython Example::" +#~ msgstr "" + +#~ msgid "UIFLOW2 Example:" +#~ msgstr "" + +#~ msgid "|example.png|" +#~ msgstr "" + +#~ msgid "example.png" +#~ msgstr "" + +#~ msgid "Constructors" +#~ msgstr "" + +#~ msgid "``I2C0`` is I2C Port." +#~ msgstr "``I2C0`` is I2C Port." + +#~ msgid "UIFLOW2:" +#~ msgstr "" + +#~ msgid "Methods" +#~ msgstr "" + +#~ msgid "Gets the raw adc readout." +#~ msgstr "获取原始的 ADC 读数。" + +#~ msgid "Gets the weight readout in grams." +#~ msgstr "获取以克为单位的重量读数。" + +#~ msgid "Tare the scale." +#~ msgstr "" + +#~ msgid "``r``: Red value (0 - 255)." +#~ msgstr "``r``: Red value (0 - 255)." + +#~ msgid "``g``: Green value (0 - 255)." +#~ msgstr "``g``: Green value (0 - 255)." + +#~ msgid "``b``: Blue value (0 - 255)." +#~ msgstr "``b``: Blue value (0 - 255)." + +#~ msgid "|setLed.png|" +#~ msgstr "" + +#~ msgid "setLed.png" +#~ msgstr "" + +#~ msgid "Resets sensor." +#~ msgstr "重置传感器。" + +#~ msgid "Calibrates the MiniScale sensor." +#~ msgstr "校准微型称重传感器。" + +#~ msgid "``weight1_g``: Weight1 in grams." +#~ msgstr "" + +#~ msgid "``weight1_adc``: Weight1 in ADC value." +#~ msgstr "" + +#~ msgid "``weight2_g``: Weight2 in grams." +#~ msgstr "" + +#~ msgid "``weight2_adc``: Weight2 in ADC value." +#~ msgstr "" + +#~ msgid "Reset sensor;" +#~ msgstr "重置传感器" + +#~ msgid "Get adc, this is weight1_adc (should be zero). And weight1_g is also 0." +#~ msgstr "得到adc,这是weight1_adc(应该是零)。而weight1_g也是0。" + +#~ msgid "" +#~ "Put some weight on it, then get" +#~ " adc, this is weight2_adc. And " +#~ "weight2_g is weight in gram you " +#~ "put on it." +#~ msgstr "AI润色104/5000给它加一些权重,然后得到adc,这是weight2_adc。而weight2_g是以克为单位的重量。" + +#~ msgid "|setLowPassFilter.png|" +#~ msgstr "" + +#~ msgid "setLowPassFilter.png" +#~ msgstr "" + +#~ msgid "Returns the status of the low pass filter (enabled or not)." +#~ msgstr "返回低通滤波器的状态(是否启用)" + +#~ msgid "|getLowPassFilter.png|" +#~ msgstr "" + +#~ msgid "getLowPassFilter.png" +#~ msgstr "" + +#~ msgid "" +#~ "``level``: Level of the average filter" +#~ " (0 - 50). Larger value for " +#~ "smoother result but more latency" +#~ msgstr "``level``:平均过滤器的电平(0 - 50)。更大的值可以获得更平滑的结果,但延迟更长" + +#~ msgid "|setAverageFilterLevel.png|" +#~ msgstr "" + +#~ msgid "setAverageFilterLevel.png" +#~ msgstr "" + +#~ msgid "Returns the level of the average filter." +#~ msgstr "返回平均滤波器的级别" + +#~ msgid "|getAverageFilterLevel.png|" +#~ msgstr "" + +#~ msgid "getAverageFilterLevel.png" +#~ msgstr "" + +#~ msgid "Sets the alpha value for the EMA filter." +#~ msgstr "设置EMA滤波器的alpha值。" + +#~ msgid "" +#~ "``alpha``: Alpha value for the EMA " +#~ "filter (0 - 99). Smaller value for" +#~ " smoother result but more latency" +#~ msgstr "`` alpha``: EMA过滤器的alpha值(0 - 99)。较小的值可以获得更平滑的结果,但会增加延迟" + +#~ msgid "|setEMAFilterAlpha.png|" +#~ msgstr "" + +#~ msgid "setEMAFilterAlpha.png" +#~ msgstr "" + +#~ msgid "Returns the alpha value for the EMA filter." +#~ msgstr "返回EMA滤波器的alpha值。" + +#~ msgid "|getEMAFilterAlpha.png|" +#~ msgstr "" + +#~ msgid "getEMAFilterAlpha.png" +#~ msgstr "" + diff --git a/examples/unit/miniscales/m5cores3_miniscales_base_example.m5f2 b/examples/unit/miniscales/m5cores3_miniscales_base_example.m5f2 new file mode 100644 index 00000000..42077f42 --- /dev/null +++ b/examples/unit/miniscales/m5cores3_miniscales_base_example.m5f2 @@ -0,0 +1 @@ +{"version":"V2.0","versionNumber":"V2.3.7","type":"cores3","components":[{"name":"page0","type":"lvgl_page","layer":0,"screenId":"builtin","screenName":"","id":"pV@L*xros5VocsuU","createTime":1762487704495,"backgroundColor":"#ffffff","isLVGL":true,"isSelected":true},{"name":"label_title","type":"lvgl_label","layer":1,"screenId":"builtin","screenName":"","id":"uTw2JVoa3byEr_k!","createTime":1762487742491,"x":94,"y":5,"color":"#0000ff","backgroundColor":"#ffffff","bg_opacity":0,"text":"MiniScales","font":"lv.font_montserrat_24","pageId":"pV@L*xros5VocsuU","isLVGL":true,"isSelected":false},{"name":"label_weight","type":"lvgl_label","layer":2,"screenId":"builtin","screenName":"","id":"ceU=Fk8&I+HV*JNB","createTime":1762487776034,"x":81,"y":90,"color":"#0000ff","backgroundColor":"#ffffff","bg_opacity":0,"text":"Weights: -- g","font":"lv.font_montserrat_24","pageId":"pV@L*xros5VocsuU","isLVGL":true,"isSelected":false}],"resources":[{"hardware":["hardware_button","hardware_pin_button","imu","speaker","touch","als","mic","sdcard","i2c"]},{"unit":["unit_miniscales"]}],"units":[{"type":"unit_miniscales","name":"miniscales_0","portList":["A","PAHUB","Custom"],"portType":"A","userPort":[22,21],"id":"uDBvtZmvuWy05wB0","createTime":1762487815007,"bus":"i2c0","pahubPortList":[0,1,2,3,4,5],"pahubPort":0,"initBlockId":"#:~LzJxu1q:858!ikx7}"}],"hats":[],"caps":[],"chains":[],"bases":[],"i2cs":[{"id":"i2c0","portType":"A","userPort":[22,21],"freq":"100000"}],"chainBus":[],"blockly":"last_timeweighttrue010000012miniscales_0miniscales_010page0trueGTE11last_time1000last_timeweightminiscales_0label_weighthello M5Weight: hello M5weight g","screen":[{"simulationName":"Built-in","type":"builtin","width":320,"height":240,"scale":0.77,"screenName":"","blockId":"","screenColorType":0,"rotation":1,"id":"builtin","createTime":1762487704474}],"logicWhenNum":0,"customList":[]} \ No newline at end of file diff --git a/examples/unit/miniscales/m5cores3_miniscales_base_example.py b/examples/unit/miniscales/m5cores3_miniscales_base_example.py new file mode 100644 index 00000000..e59fb79b --- /dev/null +++ b/examples/unit/miniscales/m5cores3_miniscales_base_example.py @@ -0,0 +1,81 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + + +import os, sys, io +import M5 +from M5 import * +import m5ui +import lvgl as lv +from hardware import I2C +from hardware import Pin +from unit import MiniScaleUnit +import time + + +page0 = None +label_title = None +label_weight = None +i2c0 = None +miniscales_0 = None +last_time = None +weight = None + + +def setup(): + global page0, label_title, label_weight, i2c0, miniscales_0, last_time, weight + + M5.begin() + Widgets.setRotation(1) + m5ui.init() + page0 = m5ui.M5Page(bg_c=0xFFFFFF) + label_title = m5ui.M5Label( + "MiniScales", + x=94, + y=5, + text_c=0x0000FF, + bg_c=0xFFFFFF, + bg_opa=0, + font=lv.font_montserrat_24, + parent=page0, + ) + label_weight = m5ui.M5Label( + "Weights: -- g", + x=81, + y=90, + text_c=0x0000FF, + bg_c=0xFFFFFF, + bg_opa=0, + font=lv.font_montserrat_24, + parent=page0, + ) + + i2c0 = I2C(0, scl=Pin(1), sda=Pin(2), freq=100000) + miniscales_0 = MiniScaleUnit(i2c0) + miniscales_0.set_average_filter_level(10) + page0.screen_load() + + +def loop(): + global page0, label_title, label_weight, i2c0, miniscales_0, last_time, weight + M5.update() + if (time.ticks_diff((time.ticks_ms()), last_time)) >= 1000: + last_time = time.ticks_ms() + weight = int(miniscales_0.weight) + label_weight.set_text(str((str("Weight: ") + str((str(weight) + str(" g")))))) + + +if __name__ == "__main__": + try: + setup() + while True: + loop() + except (Exception, KeyboardInterrupt) as e: + try: + m5ui.deinit() + from utility import print_error_msg + + print_error_msg(e) + except ImportError: + print("please update to latest firmware") diff --git a/examples/unit/miniscales/m5cores3_miniscales_calibrate_example.m5f2 b/examples/unit/miniscales/m5cores3_miniscales_calibrate_example.m5f2 new file mode 100644 index 00000000..d5e386cd --- /dev/null +++ b/examples/unit/miniscales/m5cores3_miniscales_calibrate_example.m5f2 @@ -0,0 +1 @@ +{"version":"V2.0","versionNumber":"V2.3.7","type":"cores3","components":[{"name":"page0","type":"lvgl_page","layer":0,"screenId":"builtin","screenName":"","id":"w23O3j_GN!iYC+5X","createTime":1762479720201,"backgroundColor":"#ffffff","isLVGL":true,"isSelected":true},{"name":"label_weight","type":"lvgl_label","layer":1,"screenId":"builtin","screenName":"","id":"c3V7g^+45zw`o%Cc","createTime":1762479745238,"x":14,"y":90,"color":"#0000ff","backgroundColor":"#ffffff","bg_opacity":0,"text":"Weight: ","font":"lv.font_montserrat_24","pageId":"w23O3j_GN!iYC+5X","isLVGL":true,"isSelected":false,"width":100,"height":27},{"name":"button0","type":"lvgl_button","layer":2,"screenId":"builtin","screenName":"","id":"qx@jt2w4iP%sHSfe","createTime":1762480068754,"x":116,"y":160,"width":0,"height":0,"color":"#ffffff","backgroundColor":"#2196f3","text":"Button","font":"lv.font_montserrat_16","pageId":"w23O3j_GN!iYC+5X","isLVGL":true,"isSelected":false},{"name":"label_tip","type":"lvgl_label","layer":3,"screenId":"builtin","screenName":"","id":"oGg=+EyBQ-^-F^HX","createTime":1762480089940,"x":10,"y":50,"color":"#000000","backgroundColor":"#ffffff","bg_opacity":0,"text":"Tip:","font":"lv.font_montserrat_16","pageId":"w23O3j_GN!iYC+5X","isLVGL":true,"isSelected":false},{"name":"label_title","type":"lvgl_label","layer":4,"screenId":"builtin","screenName":"","id":"cvxm_n!F1SQ664e-","createTime":1762487468437,"x":93,"y":5,"color":"#0000ff","backgroundColor":"#ffffff","bg_opacity":0,"text":"MiniScales","font":"lv.font_montserrat_24","pageId":"w23O3j_GN!iYC+5X","isLVGL":true,"isSelected":false,"width":129,"height":27}],"resources":[{"hardware":["hardware_button","hardware_pin_button","imu","speaker","touch","als","mic","sdcard","i2c"]},{"unit":["unit_miniscales"]}],"units":[{"type":"unit_miniscales","name":"miniscales_0","portList":["A","PAHUB","Custom"],"portType":"A","userPort":[22,21],"id":"g8#*lE`=#lbk_#4w","createTime":1762487735135,"bus":"i2c0","pahubPortList":[0,1,2,3,4,5],"pahubPort":0,"initBlockId":"+h:xnw@a_?C`!W.e]=dA"}],"hats":[],"caps":[],"chains":[],"bases":[],"i2cs":[{"id":"i2c0","portType":"A","userPort":[22,21],"freq":"100000"}],"chainBus":[],"blockly":"stateadc_0adc_100last_timeweighttrue010000012miniscales_0page0label_tipRemove all items, then press button.label_weightWeight: -- glabel_weightCENTERpage000state0button010050button0CENTERpage006060888200trueEQstate3GTE11last_time1000last_timeweightminiscales_0label_weighthello M5Weight: hello M5weight glabel_weightCENTERpage000button0SHORT_CLICKED888200EQstate0state1adc_0miniscales_0hello M5ADC0 Value: adc_0label_tipPut 100g weight, then press button.EQstate1state2adc_100miniscales_0hello M5ADC100 Value: adc_100do calibrateminiscales_00adc_0100adc_100label_tipRemove all items, then press button.EQstate2state3tare the scaletare the scaleTare: hello M5miniscales_0 gminiscales_0label_tipWeightlabel_tipHIDDENTruebutton0HIDDENTrueminiscales_05","screen":[{"simulationName":"Built-in","type":"builtin","width":320,"height":240,"scale":0.77,"screenName":"","blockId":"","screenColorType":0,"rotation":1,"id":"builtin","createTime":1762479720196}],"logicWhenNum":0,"customList":[]} \ No newline at end of file diff --git a/examples/unit/miniscales/m5cores3_miniscales_calibrate_example.py b/examples/unit/miniscales/m5cores3_miniscales_calibrate_example.py new file mode 100644 index 00000000..d7964ab3 --- /dev/null +++ b/examples/unit/miniscales/m5cores3_miniscales_calibrate_example.py @@ -0,0 +1,198 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import os, sys, io +import M5 +from M5 import * +import m5ui +import lvgl as lv +from hardware import I2C +from hardware import Pin +from unit import MiniScaleUnit +import time + + +page0 = None +label_weight = None +button0 = None +label_tip = None +label_title = None +i2c0 = None +miniscales_0 = None +state = None +adc_0 = None +adc_100 = None +last_time = None +weight = None + + +def button0_short_clicked_event(event_struct): + global \ + page0, \ + label_weight, \ + button0, \ + label_tip, \ + label_title, \ + i2c0, \ + miniscales_0, \ + state, \ + adc_0, \ + adc_100, \ + last_time, \ + weight + Speaker.tone(888, 200) + if state == 0: + state = 1 + adc_0 = miniscales_0.adc + print((str("ADC0 Value: ") + str(adc_0))) + label_tip.set_text(str("Put 100g weight, then press button.")) + elif state == 1: + state = 2 + adc_100 = miniscales_0.adc + print((str("ADC100 Value: ") + str(adc_100))) + print("do calibrate") + miniscales_0.calibration(0, adc_0, 100, adc_100) + label_tip.set_text(str("Remove all items, then press button.")) + elif state == 2: + state = 3 + print("tare the scale") + print((str("Tare: ") + str((str((miniscales_0.weight)) + str(" g"))))) + miniscales_0.tare() + label_tip.set_text(str("Weight")) + label_tip.set_flag(lv.obj.FLAG.HIDDEN, True) + button0.set_flag(lv.obj.FLAG.HIDDEN, True) + miniscales_0.set_average_filter_level(5) + + +def button0_event_handler(event_struct): + global \ + page0, \ + label_weight, \ + button0, \ + label_tip, \ + label_title, \ + i2c0, \ + miniscales_0, \ + state, \ + adc_0, \ + adc_100, \ + last_time, \ + weight + event = event_struct.code + if event == lv.EVENT.SHORT_CLICKED and True: + button0_short_clicked_event(event_struct) + return + + +def setup(): + global \ + page0, \ + label_weight, \ + button0, \ + label_tip, \ + label_title, \ + i2c0, \ + miniscales_0, \ + state, \ + adc_0, \ + adc_100, \ + last_time, \ + weight + + M5.begin() + Widgets.setRotation(1) + m5ui.init() + page0 = m5ui.M5Page(bg_c=0xFFFFFF) + label_weight = m5ui.M5Label( + "Weight: ", + x=14, + y=90, + text_c=0x0000FF, + bg_c=0xFFFFFF, + bg_opa=0, + font=lv.font_montserrat_24, + parent=page0, + ) + button0 = m5ui.M5Button( + text="Button", + x=116, + y=160, + bg_c=0x2196F3, + text_c=0xFFFFFF, + font=lv.font_montserrat_16, + parent=page0, + ) + label_tip = m5ui.M5Label( + "Tip:", + x=10, + y=50, + text_c=0x000000, + bg_c=0xFFFFFF, + bg_opa=0, + font=lv.font_montserrat_16, + parent=page0, + ) + label_title = m5ui.M5Label( + "MiniScales", + x=93, + y=5, + text_c=0x0000FF, + bg_c=0xFFFFFF, + bg_opa=0, + font=lv.font_montserrat_24, + parent=page0, + ) + + button0.add_event_cb(button0_event_handler, lv.EVENT.ALL, None) + + i2c0 = I2C(0, scl=Pin(1), sda=Pin(2), freq=100000) + miniscales_0 = MiniScaleUnit(i2c0) + page0.screen_load() + label_tip.set_text(str("Remove all items, then press button.")) + label_weight.set_text(str("Weight: -- g")) + label_weight.align_to(page0, lv.ALIGN.CENTER, 0, 0) + state = 0 + button0.set_size(100, 50) + button0.align_to(page0, lv.ALIGN.CENTER, 0, 60) + Speaker.begin() + Speaker.setVolumePercentage(0.6) + Speaker.tone(888, 200) + + +def loop(): + global \ + page0, \ + label_weight, \ + button0, \ + label_tip, \ + label_title, \ + i2c0, \ + miniscales_0, \ + state, \ + adc_0, \ + adc_100, \ + last_time, \ + weight + M5.update() + if state == 3: + if (time.ticks_diff((time.ticks_ms()), last_time)) >= 1000: + last_time = time.ticks_ms() + weight = int(miniscales_0.weight) + label_weight.set_text(str((str("Weight: ") + str((str(weight) + str(" g")))))) + label_weight.align_to(page0, lv.ALIGN.CENTER, 0, 0) + + +if __name__ == "__main__": + try: + setup() + while True: + loop() + except (Exception, KeyboardInterrupt) as e: + try: + m5ui.deinit() + from utility import print_error_msg + + print_error_msg(e) + except ImportError: + print("please update to latest firmware") diff --git a/m5stack/libs/unit/miniscale.py b/m5stack/libs/unit/miniscale.py index d6dd5442..7c0a16b2 100644 --- a/m5stack/libs/unit/miniscale.py +++ b/m5stack/libs/unit/miniscale.py @@ -1,8 +1,7 @@ -# SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD # # SPDX-License-Identifier: MIT -# Import necessary libraries from machine import I2C from .pahub import PAHUBUnit from .unit_helper import UnitError @@ -10,118 +9,158 @@ class MiniScaleUnit: - """! MiniScale is a weight sensor, includes a hx711 22bit ADC.""" + """! MiniScale is a weight sensor module with an internal HX711 22-bit ADC chip.""" - def __init__(self, i2c: I2C | PAHUBUnit, address: int | list | tuple = 0x26): + def __init__(self, i2c: I2C | PAHUBUnit, address: int = 0x26): + """! Initialize MiniScale module + + @param i2c I2C or PAHUB object + @param address Module I2C address, default is 0x26 + """ self.i2c = i2c self.addr = address - self._tare = 0 - self._available() + self._tare = 0.0 # Tare offset value + self._available() # Check if device is online def _available(self): - """! Check if sensor is available on the I2C bus. + """! Check if MiniScale module exists on I2C bus - Raises: - UnitError: If the sensor is not found. + @raises UnitError: If module is not detected """ if self.addr not in self.i2c.scan(): raise UnitError("MiniScale Unit not found.") + # ------------------------------------------------------------------------ + # Basic data reading + # ------------------------------------------------------------------------ + @property def adc(self) -> int: - """! Get the ADC value.""" + """! Read raw ADC value (unprocessed) + + @return Raw ADC value (integer) + """ data = self.i2c.readfrom_mem(self.addr, 0x00, 4) return struct.unpack(" float: - """! Get the weight in grams.""" + """! Read current weight (grams) + + @return Actual weight (float after subtracting tare value) + """ data = self.i2c.readfrom_mem(self.addr, 0x10, 4) result = struct.unpack(" bool: - """! Get the button state.""" + """! Read button state + + @return True if pressed, False if not pressed + """ data = self.i2c.readfrom_mem(self.addr, 0x20, 1) - return struct.unpack("b", data)[0] == 0 + return struct.unpack("B", data)[0] == 0 + + # ------------------------------------------------------------------------ + # Control functions + # ------------------------------------------------------------------------ def tare(self): - """! Tare the scale.""" + """! Tare operation + Record current weight as offset value, so subsequent weight readings use current as zero point.""" self._tare = self.weight def set_led(self, r: int, g: int, b: int): - """! Set the RGB LED color. + """! Set RGB indicator color - @param r Red value. 0 - 255 - @param g Green value. 0 - 255 - @param b Blue value. 0 - 255 + @param r Red component (0~255) + @param g Green component (0~255) + @param b Blue component (0~255) """ self.i2c.writeto_mem(self.addr, 0x30, bytes([r, g, b])) def reset(self): - """! Reset weight value to zero""" + """! Reset module internal weight register (clear to zero)""" self.i2c.writeto_mem(self.addr, 0x50, b"\x01") + # ------------------------------------------------------------------------ + # Calibration functions + # ------------------------------------------------------------------------ + def calibration(self, weight1_g: float, weight1_adc: int, weight2_g: float, weight2_adc: int): - """! Calibration the MiniScale sensor. - - (1) step 1: Reset offset; - (2) step 2: Get RawADC, this is RawADC_0g - (3) step 3: Put 100g weight on it, and get RawADC, this is RawADC_100g - (4) step 4: Calculate the value of GAP = (RawADC_100g-RawADC0g) / 100 - (5) step 5: Write GAP value to the unit Via I2C - - @param weight1_g Weight1 in grams. - @param weight1_adc Weight1 in ADC. - @param weight2_g Weight2 in grams. - @param weight2_adc Weight2 in ADC. + """! Calibrate module gain (GAP value) + + Calibration process example: + 1. Reset offset + 2. Read no-load ADC (RawADC_0g) + 3. Place known weight (e.g., 100g) and read ADC (RawADC_100g) + 4. Calculate GAP = (RawADC_100g - RawADC_0g) / 100 + 5. Write to module to save calibration coefficient + + @param weight1_g Weight at first point (unit: g) + @param weight1_adc ADC value at first point + @param weight2_g Weight at second point (unit: g) + @param weight2_adc ADC value at second point + @raises ValueError If two weights are equal """ + if weight2_g == weight1_g: + raise ValueError("Invalid calibration points: weight1_g and weight2_g cannot be equal") gap = (weight2_adc - weight1_adc) / (weight2_g - weight1_g) self.i2c.writeto_mem(self.addr, 0x40, struct.pack(" bool: - """! Get low pass filter enabled or not""" + """! Get low-pass filter status + + @return True if filter is enabled + """ data = self.i2c.readfrom_mem(self.addr, 0x80, 1) - return data == b"\x01" + return struct.unpack("B", data)[0] == 1 def set_average_filter_level(self, level: int): """! Set average filter level - @param level level of average filter, larger value for smoother result but more latency + @param level Average count level (0~50), higher value means smoother but more delay + @raises ValueError If out of range """ - if level > 50 or level < 0: - raise Exception("Level for average filter must between 0 ~ 50") - + if not (0 <= level <= 50): + raise ValueError("Level for average filter must be between 0 and 50") self.i2c.writeto_mem(self.addr, 0x81, struct.pack("b", level)) def get_average_filter_level(self) -> int: - """! Get average filter level""" + """! Get average filter level + + @return Current average filter level (integer) + """ data = self.i2c.readfrom_mem(self.addr, 0x81, 1) return struct.unpack("b", data)[0] def set_ema_filter_alpha(self, alpha: int): - """! Set ema filter alpha + """! Set exponential moving average (EMA) filter parameter - @param alpha alpha of ema filter, smaller value for smoother result but more latency + @param alpha EMA filter coefficient (0~99), smaller value means smoother but more response delay + @raises ValueError If out of range """ - if alpha > 99 or alpha < 0: - raise Exception("Alpha for EMA filter must between 0 ~ 99") - + if not (0 <= alpha <= 99): + raise ValueError("Alpha for EMA filter must be between 0 and 99") self.i2c.writeto_mem(self.addr, 0x82, struct.pack("b", alpha)) def get_ema_filter_alpha(self) -> int: - """! Get ema filter alpha""" + """! Get EMA filter coefficient + + @return Current EMA alpha value (integer) + """ data = self.i2c.readfrom_mem(self.addr, 0x82, 1) return struct.unpack("b", data)[0] From 2c64c08e80278932d64831cd15181e665ec488be Mon Sep 17 00:00:00 2001 From: hlym123 Date: Thu, 13 Nov 2025 08:59:58 +0800 Subject: [PATCH 307/322] docs: Fix some example link error. Signed-off-by: hlym123 --- docs/en/refs/hardware.display.ref | 2 +- docs/en/refs/hardware.lora.ref | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/en/refs/hardware.display.ref b/docs/en/refs/hardware.display.ref index 5f7c758c..91a4163a 100644 --- a/docs/en/refs/hardware.display.ref +++ b/docs/en/refs/hardware.display.ref @@ -47,7 +47,7 @@ .. |cores3_draw_test_example.m5f2| raw:: html cores3_draw_test_example.m5f2 diff --git a/docs/en/refs/hardware.lora.ref b/docs/en/refs/hardware.lora.ref index 85784ef3..c187a11c 100644 --- a/docs/en/refs/hardware.lora.ref +++ b/docs/en/refs/hardware.lora.ref @@ -24,7 +24,7 @@ .. |unit_c6l_tx_example.m5f2| raw:: html unit_c6l_tx_example.m5f2 @@ -33,7 +33,7 @@ .. |unit_c6l_rx_example.m5f2| raw:: html unit_c6l_rx_example.m5f2 From c9db3654b5c8abc12f6330e44af3f5daec0ad056 Mon Sep 17 00:00:00 2001 From: lbuque <1102390310@qq.com> Date: Fri, 31 Oct 2025 11:20:24 +0800 Subject: [PATCH 308/322] boards: Add Arduino Nesso N1. Signed-off-by: lbuque <1102390310@qq.com> --- m5stack/CMakeListsDefault.cmake | 1 + m5stack/Makefile | 10 +- m5stack/board.cpp | 23 + .../boards/M5STACK_Atom_EchoS3R/board_init.c | 2 +- m5stack/boards/M5STACK_CoreS3/board_init.c | 2 +- m5stack/boards/M5STACK_Tab5/board_init.c | 2 +- m5stack/boards/M5STACK_Tab5/sdkconfig.adf | 2 +- m5stack/boards/Nesso_N1/board.json | 22 + m5stack/boards/Nesso_N1/deploy_arduino_n1.md | 18 + m5stack/boards/Nesso_N1/manifest.py | 6 + m5stack/boards/Nesso_N1/mpconfigboard.cmake | 27 + m5stack/boards/Nesso_N1/mpconfigboard.h | 22 + m5stack/boards/Nesso_N1/sdkconfig.board | 11 + m5stack/cmodules/cdriver/cdriver.c | 8 +- m5stack/cmodules/cdriver/cdriver.cmake | 8 +- m5stack/cmodules/m5unified/m5unified.c | 1 + m5stack/components/M5Unified/CMakeLists.txt | 2 +- m5stack/components/M5Unified/M5GFX | 2 +- m5stack/components/M5Unified/M5Unified | 2 +- .../components/M5Unified/mpy_m5unified.cpp | 33 +- m5stack/fs/system/nesso-n1/1a.jpg | Bin 0 -> 33642 bytes m5stack/fs/system/nesso-n1/1b.jpg | Bin 0 -> 32330 bytes m5stack/fs/system/nesso-n1/1c.jpg | Bin 0 -> 35605 bytes m5stack/fs/system/nesso-n1/1d.jpg | Bin 0 -> 37104 bytes m5stack/fs/system/nesso-n1/1e.jpg | Bin 0 -> 31020 bytes m5stack/fs/system/nesso-n1/APPLIST.jpg | Bin 0 -> 10729 bytes m5stack/fs/system/nesso-n1/CHG.jpg | Bin 0 -> 1014 bytes m5stack/fs/system/nesso-n1/a1.jpg | Bin 0 -> 16064 bytes m5stack/fs/system/nesso-n1/a10.jpg | Bin 0 -> 38276 bytes m5stack/fs/system/nesso-n1/a11.jpg | Bin 0 -> 41787 bytes m5stack/fs/system/nesso-n1/a2.jpg | Bin 0 -> 23425 bytes m5stack/fs/system/nesso-n1/a3.jpg | Bin 0 -> 19018 bytes m5stack/fs/system/nesso-n1/a4.jpg | Bin 0 -> 24308 bytes m5stack/fs/system/nesso-n1/a5.jpg | Bin 0 -> 39373 bytes m5stack/fs/system/nesso-n1/a6.jpg | Bin 0 -> 39824 bytes m5stack/fs/system/nesso-n1/a7.jpg | Bin 0 -> 38077 bytes m5stack/fs/system/nesso-n1/a8.jpg | Bin 0 -> 38962 bytes m5stack/fs/system/nesso-n1/a9.jpg | Bin 0 -> 37366 bytes m5stack/fs/system/nesso-n1/ng.jpg | Bin 0 -> 2632 bytes m5stack/fs/system/nesso-n1/noCHG.jpg | Bin 0 -> 758 bytes m5stack/fs/system/nesso-n1/placeholder.jpg | Bin 0 -> 11530 bytes m5stack/fs/system/nesso-n1/server_ok.jpg | Bin 0 -> 2766 bytes m5stack/fs/system/nesso-n1/usb.jpg | Bin 0 -> 11970 bytes m5stack/fs/system/nesso-n1/wifiNG.jpg | Bin 0 -> 1224 bytes m5stack/fs/system/nesso-n1/wifiNeverSet.jpg | Bin 0 -> 939 bytes m5stack/fs/system/nesso-n1/wifiOKServerNG.jpg | Bin 0 -> 1165 bytes m5stack/fs/system/nesso-n1/wifiOKServerOK.jpg | Bin 0 -> 1232 bytes m5stack/fs/system/nesso-n1/wifi_ok.jpg | Bin 0 -> 2835 bytes m5stack/libs/hardware/ir.py | 1 + m5stack/machine_i2c.c | 385 +++++----- m5stack/machine_i2c.h | 52 ++ m5stack/modules/startup/__init__.py | 5 + m5stack/modules/startup/manifest_nesson1.py | 13 + m5stack/modules/startup/nesson1.py | 698 ++++++++++++++++++ m5stack/patches/2009-fix-SoftI2C.patch | 427 +++++++++++ 55 files changed, 1579 insertions(+), 206 deletions(-) create mode 100644 m5stack/boards/Nesso_N1/board.json create mode 100644 m5stack/boards/Nesso_N1/deploy_arduino_n1.md create mode 100644 m5stack/boards/Nesso_N1/manifest.py create mode 100644 m5stack/boards/Nesso_N1/mpconfigboard.cmake create mode 100644 m5stack/boards/Nesso_N1/mpconfigboard.h create mode 100644 m5stack/boards/Nesso_N1/sdkconfig.board create mode 100644 m5stack/fs/system/nesso-n1/1a.jpg create mode 100644 m5stack/fs/system/nesso-n1/1b.jpg create mode 100644 m5stack/fs/system/nesso-n1/1c.jpg create mode 100644 m5stack/fs/system/nesso-n1/1d.jpg create mode 100644 m5stack/fs/system/nesso-n1/1e.jpg create mode 100644 m5stack/fs/system/nesso-n1/APPLIST.jpg create mode 100644 m5stack/fs/system/nesso-n1/CHG.jpg create mode 100644 m5stack/fs/system/nesso-n1/a1.jpg create mode 100644 m5stack/fs/system/nesso-n1/a10.jpg create mode 100644 m5stack/fs/system/nesso-n1/a11.jpg create mode 100644 m5stack/fs/system/nesso-n1/a2.jpg create mode 100644 m5stack/fs/system/nesso-n1/a3.jpg create mode 100644 m5stack/fs/system/nesso-n1/a4.jpg create mode 100644 m5stack/fs/system/nesso-n1/a5.jpg create mode 100644 m5stack/fs/system/nesso-n1/a6.jpg create mode 100644 m5stack/fs/system/nesso-n1/a7.jpg create mode 100644 m5stack/fs/system/nesso-n1/a8.jpg create mode 100644 m5stack/fs/system/nesso-n1/a9.jpg create mode 100644 m5stack/fs/system/nesso-n1/ng.jpg create mode 100644 m5stack/fs/system/nesso-n1/noCHG.jpg create mode 100644 m5stack/fs/system/nesso-n1/placeholder.jpg create mode 100644 m5stack/fs/system/nesso-n1/server_ok.jpg create mode 100644 m5stack/fs/system/nesso-n1/usb.jpg create mode 100644 m5stack/fs/system/nesso-n1/wifiNG.jpg create mode 100644 m5stack/fs/system/nesso-n1/wifiNeverSet.jpg create mode 100644 m5stack/fs/system/nesso-n1/wifiOKServerNG.jpg create mode 100644 m5stack/fs/system/nesso-n1/wifiOKServerOK.jpg create mode 100644 m5stack/fs/system/nesso-n1/wifi_ok.jpg create mode 100644 m5stack/machine_i2c.h create mode 100644 m5stack/modules/startup/manifest_nesson1.py create mode 100644 m5stack/modules/startup/nesson1.py create mode 100644 m5stack/patches/2009-fix-SoftI2C.patch diff --git a/m5stack/CMakeListsDefault.cmake b/m5stack/CMakeListsDefault.cmake index fa93961c..d6ab6202 100644 --- a/m5stack/CMakeListsDefault.cmake +++ b/m5stack/CMakeListsDefault.cmake @@ -154,6 +154,7 @@ if ( OR BOARD_TYPE STREQUAL "tough" OR BOARD_TYPE STREQUAL "stamplc" OR BOARD_TYPE STREQUAL "unit_c6l" + OR BOARD_TYPE STREQUAL "arduino-n1" ) LIST(APPEND MICROPY_SOURCE_PORT ${PROJECT_DIR}/machine_hw_spi.c) LIST(APPEND MICROPY_SOURCE_PORT ${PROJECT_DIR}/machine_sdcard.c) diff --git a/m5stack/Makefile b/m5stack/Makefile index 3f2efc8b..106e05fc 100644 --- a/m5stack/Makefile +++ b/m5stack/Makefile @@ -11,6 +11,7 @@ BOARD ?= M5STACK_AtomS3 boards := \ + Nesso_N1:nesso-n1 \ M5STACK_AtomS3:atoms3 \ M5STACK_AtomS3_Lite:atoms3-lite \ M5STACK_StampS3:stamps3 \ @@ -55,6 +56,7 @@ endef # Board type list BOARD_TYPE_DEF := \ none \ + nesso-n1 \ atoms3 \ atoms3-lite \ stamps3 \ @@ -329,7 +331,8 @@ IDF_PATH_PATCH_SERIES = \ M5UNIFIED_PATCH_SERIES = \ 2006-Support-LTR553.patch \ 2007-Support-UnitC6L.patch \ - 2008-Only-use-old-rmt-driver.patch + 2008-Only-use-old-rmt-driver.patch \ + 2009-fix-SoftI2C.patch ADF_PATCH_SERIES = \ 3002-Modify-i2s_stream_idf5.patch @@ -372,7 +375,6 @@ PKG_PATCH_SERIES := $(strip \ $(if $(filter esp-idf,$(PKG)),$(IDF_PATH_PATCH_SERIES)) \ $(if $(filter M5Unified,$(PKG)),$(M5UNIFIED_PATCH_SERIES)) \ $(if $(filter esp-adf,$(PKG)),$(ADF_PATCH_SERIES)) \ - $(if $(filter M5GFX,$(PKG)),$(M5GFX_PATCH_SERIES)) \ $(if $(filter esp32-camera,$(PKG)),$(ESP32_CAMERA_PATCH_SERIES)) \ ) # $(info PKG_PATCH_SERIES for $(PKG) is [$(PKG_PATCH_SERIES)]) @@ -395,7 +397,7 @@ patch: unpatch $(call Patch/prepare,$(IDF_PATH),$(IDF_PATH_PATCH_SERIES)) $(call Patch/prepare,$(M5UNIFIED_PATH),$(M5UNIFIED_PATCH_SERIES)) $(call Patch/prepare,$(ADF_PATH),$(ADF_PATCH_SERIES)) - $(call Patch/prepare,$(M5GFX_PATH),$(M5GFX_PATCH_SERIES)) +# $(call Patch/prepare,$(M5GFX_PATH),$(M5GFX_PATCH_SERIES)) $(call Patch/prepare,$(ESP32_CAMERA_PATH),$(ESP32_CAMERA_PATCH_SERIES)) # Unapply patches @@ -405,5 +407,5 @@ unpatch: $(call Patch/clean,$(IDF_PATH),$(IDF_PATH_PATCH_SERIES)) $(call Patch/clean,$(M5UNIFIED_PATH),$(M5UNIFIED_PATCH_SERIES)) $(call Patch/clean,$(ADF_PATH),$(ADF_PATCH_SERIES)) - $(call Patch/clean,$(M5GFX_PATH),$(M5GFX_PATCH_SERIES)) +# $(call Patch/clean,$(M5GFX_PATH),$(M5GFX_PATCH_SERIES)) $(call Patch/clean,$(ESP32_CAMERA_PATH),$(ESP32_CAMERA_PATCH_SERIES)) diff --git a/m5stack/board.cpp b/m5stack/board.cpp index 53b87b2c..77605571 100644 --- a/m5stack/board.cpp +++ b/m5stack/board.cpp @@ -11,6 +11,15 @@ extern "C" { // #include #include "esp_log.h" + #define MICROPY_HW_ESP_NEW_I2C_DRIVER 1 + + #if MICROPY_HW_ESP_NEW_I2C_DRIVER + #include "driver/i2c_master.h" + #else + #include "driver/i2c.h" + #include "hal/i2c_ll.h" + #endif + static void in_i2c_init(void) { gpio_num_t in_scl = (gpio_num_t)M5.getPin(m5::pin_name_t::in_i2c_scl); @@ -29,6 +38,19 @@ extern "C" { if (in_scl != GPIO_NUM_NC || in_sda != GPIO_NUM_NC) { ESP_LOGI("BOARD", "Internal I2C(%d) init", in_port); +#if MICROPY_HW_ESP_NEW_I2C_DRIVER + i2c_master_bus_handle_t bus_handle; + if (i2c_master_get_bus_handle(in_port, &bus_handle) == ESP_ERR_INVALID_STATE) { + i2c_master_bus_config_t i2c_bus_config; + memset(&i2c_bus_config, 0, sizeof(i2c_bus_config)); + i2c_bus_config.clk_source = I2C_CLK_SRC_DEFAULT; + i2c_bus_config.i2c_port = in_port; + i2c_bus_config.scl_io_num = in_scl; + i2c_bus_config.sda_io_num = in_sda; + i2c_bus_config.glitch_ignore_cnt = 7; + i2c_new_master_bus(&i2c_bus_config, &bus_handle); + } +#else i2c_config_t conf; memset(&conf, 0, sizeof(i2c_config_t)); conf.mode = I2C_MODE_MASTER; @@ -40,6 +62,7 @@ extern "C" { // .clk_flags = 0, /*!< Optional, you can use I2C_SCLK_SRC_FLAG_* flags to choose i2c source clock here. */ i2c_param_config(in_port, &conf); i2c_driver_install(in_port, I2C_MODE_MASTER, 0, 0, 0); +#endif } } diff --git a/m5stack/boards/M5STACK_Atom_EchoS3R/board_init.c b/m5stack/boards/M5STACK_Atom_EchoS3R/board_init.c index aa7d1a8a..1a7e067c 100644 --- a/m5stack/boards/M5STACK_Atom_EchoS3R/board_init.c +++ b/m5stack/boards/M5STACK_Atom_EchoS3R/board_init.c @@ -23,7 +23,7 @@ #include "esp_codec_dev.h" #include "esp_codec_dev_defaults.h" -#if 0 //ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0) +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0) #include "driver/i2c_master.h" #define USE_IDF_I2C_MASTER #else diff --git a/m5stack/boards/M5STACK_CoreS3/board_init.c b/m5stack/boards/M5STACK_CoreS3/board_init.c index 3845cea0..8dfd76de 100644 --- a/m5stack/boards/M5STACK_CoreS3/board_init.c +++ b/m5stack/boards/M5STACK_CoreS3/board_init.c @@ -23,7 +23,7 @@ #include "esp_codec_dev.h" #include "esp_codec_dev_defaults.h" -#if 0 //ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0) +#if (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0)) #include "driver/i2c_master.h" #define USE_IDF_I2C_MASTER #else diff --git a/m5stack/boards/M5STACK_Tab5/board_init.c b/m5stack/boards/M5STACK_Tab5/board_init.c index a1fd57e6..fe258cfc 100644 --- a/m5stack/boards/M5STACK_Tab5/board_init.c +++ b/m5stack/boards/M5STACK_Tab5/board_init.c @@ -23,7 +23,7 @@ #include "esp_codec_dev.h" #include "esp_codec_dev_defaults.h" -#if 0 //ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0) +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0) #include "driver/i2c_master.h" #define USE_IDF_I2C_MASTER #else diff --git a/m5stack/boards/M5STACK_Tab5/sdkconfig.adf b/m5stack/boards/M5STACK_Tab5/sdkconfig.adf index 49a4ca92..ff4cf079 100644 --- a/m5stack/boards/M5STACK_Tab5/sdkconfig.adf +++ b/m5stack/boards/M5STACK_Tab5/sdkconfig.adf @@ -3,7 +3,7 @@ CONFIG_TAB5=y CONFIG_CODEC_ES8388_SUPPORT=y CONFIG_CODEC_ES7210_SUPPORT=y CONFIG_FREERTOS_ENABLE_BACKWARD_COMPATIBILITY=y -CONFIG_CODEC_I2C_BACKWARD_COMPATIBLE=y +CONFIG_CODEC_I2C_BACKWARD_COMPATIBLE=n CONFIG_FREERTOS_PLACE_SNAPSHOT_FUNS_INTO_FLASH=y CONFIG_ESP_TLS_INSECURE=y diff --git a/m5stack/boards/Nesso_N1/board.json b/m5stack/boards/Nesso_N1/board.json new file mode 100644 index 00000000..25ad7de5 --- /dev/null +++ b/m5stack/boards/Nesso_N1/board.json @@ -0,0 +1,22 @@ +{ + "deploy": [ + "deploy_arduino_n1.md" + ], + "docs": "", + "features": [ + "BLE", + "WiFi", + "LoRa", + "Buzzer", + "RGB LED", + "USB" + ], + "images": [ + "m5stack_unit_c6l.jpg" + ], + "mcu": "esp32c6", + "product": "Arduino Nesso N1", + "url": "https://shop.m5stack.com/products/arduino-nesso-n1", + "vendor": "M5Stack" +} + diff --git a/m5stack/boards/Nesso_N1/deploy_arduino_n1.md b/m5stack/boards/Nesso_N1/deploy_arduino_n1.md new file mode 100644 index 00000000..fc7596c7 --- /dev/null +++ b/m5stack/boards/Nesso_N1/deploy_arduino_n1.md @@ -0,0 +1,18 @@ +Program your board using the esptool.py program, found +[here](https://github.com/espressif/esptool). + +To put the NanoC6 into 'update mode', hold the button while connecting the USB +cable. It can be released after the connection is made. + +If you are putting MicroPython on your board for the first time then you should +first erase the entire flash using: + +```bash +esptool.py --chip esp32c6 --port /dev/ttyUSB0 erase_flash +``` + +From then on program the firmware starting at address 0x0: + +```bash +esptool.py --chip esp32c6 --port /dev/ttyUSB0 --baud 460800 write_flash -z 0x0 M5STACK_Unit_C6L-20250915-v1.25.0.bin +``` diff --git a/m5stack/boards/Nesso_N1/manifest.py b/m5stack/boards/Nesso_N1/manifest.py new file mode 100644 index 00000000..4db46258 --- /dev/null +++ b/m5stack/boards/Nesso_N1/manifest.py @@ -0,0 +1,6 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +include("$(MPY_DIR)/../m5stack/modules/startup/manifest_nesson1.py") +include("$(MPY_DIR)/../m5stack/libs/unit/manifest.py") diff --git a/m5stack/boards/Nesso_N1/mpconfigboard.cmake b/m5stack/boards/Nesso_N1/mpconfigboard.cmake new file mode 100644 index 00000000..245ad6c1 --- /dev/null +++ b/m5stack/boards/Nesso_N1/mpconfigboard.cmake @@ -0,0 +1,27 @@ +# SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +set(IDF_TARGET esp32c6) + +# nanoc6 https://github.com/m5stack/m5stack-board-id/blob/main/board.csv#L25 +set(BOARD_ID 23) +set(MICROPY_PY_LVGL 0) + +set(SDKCONFIG_DEFAULTS + ./boards/sdkconfig.base + ${SDKCONFIG_IDF_VERSION_SPECIFIC} + ./boards/sdkconfig.flash_16mb + ./boards/sdkconfig.c6 + ./boards/sdkconfig.ble + ./boards/sdkconfig.usb + ./boards/sdkconfig.freertos + ./boards/Nesso_N1/sdkconfig.board +) + +# If not enable LVGL, ignore this... +set(LV_CFLAGS -DLV_COLOR_DEPTH=16 -DLV_COLOR_16_SWAP=0) + +if(NOT MICROPY_FROZEN_MANIFEST) + set(MICROPY_FROZEN_MANIFEST ${CMAKE_SOURCE_DIR}/boards/manifest.py) +endif() diff --git a/m5stack/boards/Nesso_N1/mpconfigboard.h b/m5stack/boards/Nesso_N1/mpconfigboard.h new file mode 100644 index 00000000..00240dc2 --- /dev/null +++ b/m5stack/boards/Nesso_N1/mpconfigboard.h @@ -0,0 +1,22 @@ +#define MICROPY_HW_BOARD_NAME "Nesso N1" +#define MICROPY_HW_MCU_NAME "ESP32C6" + +#define MICROPY_HW_ENABLE_SDCARD (0) +#define MICROPY_PY_MACHINE_I2S (0) + +#define MICROPY_HW_I2C0_SCL (4) +#define MICROPY_HW_I2C0_SDA (5) + +// ESP32-C6 SPI configuration +// ESP32-C6 only has SPI2 (HSPI), SPI3 is not available +// Default SPI2 pins for ESP32-C6 +#define MICROPY_HW_SPI1_SCK (6) // GPIO6 - SCLK +#define MICROPY_HW_SPI1_MOSI (7) // GPIO7 - MOSI +#define MICROPY_HW_SPI1_MISO (2) // GPIO2 - MISO + +// #define MICROPY_HW_USB_CDC_INTERFACE_STRING "M5Stack UnitC6L(UiFlow2)" + +// #ifndef MICROPY_HW_ENABLE_USB_RUNTIME_DEVICE +// #define MICROPY_HW_ENABLE_USB_RUNTIME_DEVICE (1) // Support machine.USBDevice +// #endif + diff --git a/m5stack/boards/Nesso_N1/sdkconfig.board b/m5stack/boards/Nesso_N1/sdkconfig.board new file mode 100644 index 00000000..c9cf0416 --- /dev/null +++ b/m5stack/boards/Nesso_N1/sdkconfig.board @@ -0,0 +1,11 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +# M5STACK UiFlow USB description +CONFIG_TINYUSB_DESC_CDC_STRING="Nesso N1(UiFlow2)" + +# SSL +CONFIG_MBEDTLS_AES_USE_INTERRUPT=n + +CONFIG_FREERTOS_ENABLE_BACKWARD_COMPATIBILITY=y diff --git a/m5stack/cmodules/cdriver/cdriver.c b/m5stack/cmodules/cdriver/cdriver.c index f954b073..12a2603d 100644 --- a/m5stack/cmodules/cdriver/cdriver.c +++ b/m5stack/cmodules/cdriver/cdriver.c @@ -12,15 +12,15 @@ #include "py/mperrno.h" #include "mphalport.h" -extern const mp_obj_module_t mp_module_max30100; -extern const mp_obj_module_t mp_module_max30102; +// extern const mp_obj_module_t mp_module_max30100; +// extern const mp_obj_module_t mp_module_max30102; extern const mp_obj_module_t mp_module_esp_dmx; static const mp_rom_map_elem_t mp_module_cdriver_globals_table[] = { /* *FORMAT-OFF* */ { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_cdriver) }, - { MP_ROM_QSTR(MP_QSTR_max30100), MP_OBJ_FROM_PTR(&mp_module_max30100) }, - { MP_ROM_QSTR(MP_QSTR_max30102), MP_OBJ_FROM_PTR(&mp_module_max30102) }, + // { MP_ROM_QSTR(MP_QSTR_max30100), MP_OBJ_FROM_PTR(&mp_module_max30100) }, + // { MP_ROM_QSTR(MP_QSTR_max30102), MP_OBJ_FROM_PTR(&mp_module_max30102) }, { MP_ROM_QSTR(MP_QSTR_esp_dmx), MP_OBJ_FROM_PTR(&mp_module_esp_dmx) }, /* *FORMAT-ON* */ }; diff --git a/m5stack/cmodules/cdriver/cdriver.cmake b/m5stack/cmodules/cdriver/cdriver.cmake index 26f77e1c..5d85a443 100644 --- a/m5stack/cmodules/cdriver/cdriver.cmake +++ b/m5stack/cmodules/cdriver/cdriver.cmake @@ -6,10 +6,10 @@ add_library(usermod_DRIVER INTERFACE) target_sources(usermod_DRIVER INTERFACE ${CMAKE_CURRENT_LIST_DIR}/cdriver.c - ${CMAKE_CURRENT_LIST_DIR}/max30100/max30100.c - ${CMAKE_CURRENT_LIST_DIR}/max30100/driver_max30100.c - ${CMAKE_CURRENT_LIST_DIR}/max30102/max30102.c - ${CMAKE_CURRENT_LIST_DIR}/max30102/driver_max30102.c + # ${CMAKE_CURRENT_LIST_DIR}/max30100/max30100.c + # ${CMAKE_CURRENT_LIST_DIR}/max30100/driver_max30100.c + # ${CMAKE_CURRENT_LIST_DIR}/max30102/max30102.c + # ${CMAKE_CURRENT_LIST_DIR}/max30102/driver_max30102.c ${CMAKE_CURRENT_LIST_DIR}/esp_dmx/driver_esp_dmx.c ) diff --git a/m5stack/cmodules/m5unified/m5unified.c b/m5stack/cmodules/m5unified/m5unified.c index 2be444c1..2482f91c 100644 --- a/m5stack/cmodules/m5unified/m5unified.c +++ b/m5stack/cmodules/m5unified/m5unified.c @@ -30,6 +30,7 @@ static const mp_rom_map_elem_t m5_board_member_table[] = { { MP_ROM_QSTR(MP_QSTR_M5PaperS3), MP_ROM_INT(19) }, { MP_ROM_QSTR(MP_QSTR_M5StamPLC), MP_ROM_INT(21) }, { MP_ROM_QSTR(MP_QSTR_M5Tab5), MP_ROM_INT(22) }, + { MP_ROM_QSTR(MP_QSTR_ArduinoNessoN1), MP_ROM_INT(23) }, { MP_ROM_QSTR(MP_QSTR_M5CardputerADV), MP_ROM_INT(24) }, { MP_ROM_QSTR(MP_QSTR_M5UnitC6L), MP_ROM_INT(25) }, // non display boards diff --git a/m5stack/components/M5Unified/CMakeLists.txt b/m5stack/components/M5Unified/CMakeLists.txt index 4f37d21f..5a4720df 100644 --- a/m5stack/components/M5Unified/CMakeLists.txt +++ b/m5stack/components/M5Unified/CMakeLists.txt @@ -18,7 +18,7 @@ file(GLOB SRCS M5GFX/src/lgfx/v1/panel/*.cpp M5GFX/src/lgfx/v1/platforms/esp32/*.cpp M5GFX/src/lgfx/v1/platforms/esp32c3/*.cpp - # M5GFX/src/lgfx/v1/platforms/esp32s3/*.cpp + M5GFX/src/lgfx/v1/platforms/esp32s3/*.cpp M5GFX/src/lgfx/v1/platforms/esp32p4/*.cpp M5GFX/src/lgfx/v1/touch/*.cpp M5Unified/src/*.cpp diff --git a/m5stack/components/M5Unified/M5GFX b/m5stack/components/M5Unified/M5GFX index fbe11fcd..b7ff485b 160000 --- a/m5stack/components/M5Unified/M5GFX +++ b/m5stack/components/M5Unified/M5GFX @@ -1 +1 @@ -Subproject commit fbe11fcde0983c6464dfaa155f8eec24c3bb0bee +Subproject commit b7ff485bc10af8a283180c82e0f0f6580397def9 diff --git a/m5stack/components/M5Unified/M5Unified b/m5stack/components/M5Unified/M5Unified index 447ed989..2408c62b 160000 --- a/m5stack/components/M5Unified/M5Unified +++ b/m5stack/components/M5Unified/M5Unified @@ -1 +1 @@ -Subproject commit 447ed989498faab9f28eadd17cf97b12eb930691 +Subproject commit 2408c62b62730a5d934d554a27f56e1633571c61 diff --git a/m5stack/components/M5Unified/mpy_m5unified.cpp b/m5stack/components/M5Unified/mpy_m5unified.cpp index 8fd7066e..afada667 100644 --- a/m5stack/components/M5Unified/mpy_m5unified.cpp +++ b/m5stack/components/M5Unified/mpy_m5unified.cpp @@ -35,17 +35,30 @@ extern "C" { #include "mpy_m5unified.h" #include "mphalport.h" -#include + +#define MICROPY_HW_ESP_NEW_I2C_DRIVER 1 + // #include +#if MICROPY_HW_ESP_NEW_I2C_DRIVER +#include "driver/i2c_master.h" +#else +#include "driver/i2c.h" +#include "hal/i2c_ll.h" +#endif typedef struct _machine_hw_i2c_obj_t { mp_obj_base_t base; - i2c_port_t port : 8; + i2c_master_bus_handle_t bus_handle; + #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 5, 0) + i2c_master_dev_handle_t dev_handle; + #endif + uint8_t port : 8; gpio_num_t scl : 8; gpio_num_t sda : 8; + uint32_t freq; + uint32_t timeout_us; // Start of modification section, by M5Stack uint8_t pos; - uint32_t freq; // End of modification section, by M5Stack } machine_hw_i2c_obj_t; @@ -438,6 +451,19 @@ static void in_i2c_init(void) { // } else { // periph_module_enable(PERIPH_I2C1_MODULE); // } + #if MICROPY_HW_ESP_NEW_I2C_DRIVER + i2c_master_bus_handle_t bus_handle; + if (i2c_master_get_bus_handle(in_port, &bus_handle) == ESP_ERR_INVALID_STATE) { + i2c_master_bus_config_t i2c_bus_config; + memset(&i2c_bus_config, 0, sizeof(i2c_bus_config)); + i2c_bus_config.clk_source = I2C_CLK_SRC_DEFAULT; + i2c_bus_config.i2c_port = in_port; + i2c_bus_config.scl_io_num = in_scl; + i2c_bus_config.sda_io_num = in_sda; + i2c_bus_config.glitch_ignore_cnt = 7; + i2c_new_master_bus(&i2c_bus_config, &bus_handle); + } + #else i2c_config_t conf; memset(&conf, 0, sizeof(i2c_config_t)); conf.mode = I2C_MODE_MASTER; @@ -449,6 +475,7 @@ static void in_i2c_init(void) { // .clk_flags = 0, /*!< Optional, you can use I2C_SCLK_SRC_FLAG_* flags to choose i2c source clock here. */ i2c_param_config(in_port, &conf); i2c_driver_install(in_port, I2C_MODE_MASTER, 0, 0, 0); + #endif } } diff --git a/m5stack/fs/system/nesso-n1/1a.jpg b/m5stack/fs/system/nesso-n1/1a.jpg new file mode 100644 index 0000000000000000000000000000000000000000..43334af7fdc66772d2f59f03f0de48eea0f4698a GIT binary patch literal 33642 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 G{r>=8C@@w4 literal 0 HcmV?d00001 diff --git a/m5stack/fs/system/nesso-n1/1c.jpg b/m5stack/fs/system/nesso-n1/1c.jpg new file mode 100644 index 0000000000000000000000000000000000000000..15b8d51832948eeec2ca005d9bf1677a4ce4a224 GIT binary patch literal 35605 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 G0RP!Dwf$89 literal 0 HcmV?d00001 diff --git a/m5stack/fs/system/nesso-n1/1d.jpg b/m5stack/fs/system/nesso-n1/1d.jpg new file mode 100644 index 0000000000000000000000000000000000000000..4ee1406700ca961a3986831c5a27503aaf152364 GIT binary patch literal 37104 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 a;j7S6;f%RO)}vCIZ~H;$oZ}n6L;u;OtGSy1 literal 0 HcmV?d00001 diff --git a/m5stack/fs/system/nesso-n1/1e.jpg b/m5stack/fs/system/nesso-n1/1e.jpg new file mode 100644 index 0000000000000000000000000000000000000000..c303df356700b02afd9689ac41484ed5ea53ce9f GIT binary patch literal 31020 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 literal 0 HcmV?d00001 diff --git a/m5stack/fs/system/nesso-n1/CHG.jpg b/m5stack/fs/system/nesso-n1/CHG.jpg new file mode 100644 index 0000000000000000000000000000000000000000..7407cb5833603a5e042a0650234d31de4661df26 GIT binary patch literal 1014 zcmex=PfpQEif~-P{hK_8)fr;!& zg(60c6BlwQJ8e8D8g%i4ig8j=6DOCLxP+vXs+zinrk07RnYo3fm9vYho4bdnS8zyZ zSa?KaRB}pcT6#uiR&hybS$RceRdY*gTYE=m*QCi)rcRqaW9F9X@jO*zpr5PhGlvL#Pl$;I=J=F7a&w5bxC%#<%=qitcnt7jrxcCFd9 zSC6wRtG)j1i@*M5J5&1~VeQB2{rha{j@EDfe)N6Et*6(vKK7TdDE;HQK1;~b-|XG* zsC|=9_nF+9crahmL!nnhr{Gw}0tQwCdx8H94<7BmQT?s+$M1*y!gUuX{fYaqZSn{4 zZzUgUk8ND^M=M%4{C9lFwe@-ryQ6`mz~81=r`m7dw&grnx^?bG!Invnik05~En#l? z&(NH)NB?)?kKc!lYC?a^{Fr{+pzid42L4>N58?;^Gqi;Vt3{-C3w#!d@=THB7{ZGTc$^RL0{co;%{B7CaxprEAn5_P;Z+rM- z>hi-2VqXWjnx4O*lXCT~+TLjv%KasqXGbjc5SZ$0toGgIG~>$^auYv@KhSTsPv75q zpWlw>kLjg1TjJSj>@Iv+&!2IvplQv=NR>?8x0jb_KffP;{#jgntcB5?yS_S?jIB2+ I$p60y0K^B1NdN!< literal 0 HcmV?d00001 diff --git a/m5stack/fs/system/nesso-n1/a1.jpg b/m5stack/fs/system/nesso-n1/a1.jpg new file mode 100644 index 0000000000000000000000000000000000000000..fb26e24a6017573c1bfd1f25adfe15fe527c0bba GIT binary patch literal 16064 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 O*!mj(07Os55C7S6WHELC literal 0 HcmV?d00001 diff --git a/m5stack/fs/system/nesso-n1/a11.jpg b/m5stack/fs/system/nesso-n1/a11.jpg new file mode 100644 index 0000000000000000000000000000000000000000..c28c522f34e01cfe3ec7da82580797afd5d20924 GIT binary patch literal 41787 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 z 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 literal 0 HcmV?d00001 diff --git a/m5stack/fs/system/nesso-n1/a3.jpg b/m5stack/fs/system/nesso-n1/a3.jpg new file mode 100644 index 0000000000000000000000000000000000000000..5cd06ef76ea6479254c8525d90cae98c4b9805d8 GIT binary patch literal 19018 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

#o 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 literal 0 HcmV?d00001 diff --git a/m5stack/fs/system/nesso-n1/a4.jpg b/m5stack/fs/system/nesso-n1/a4.jpg new file mode 100644 index 0000000000000000000000000000000000000000..8c5628c164b82771f98e1f285ab09d469667c08f GIT binary patch literal 24308 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 VZ{O3~HYX1*0&Ll)3-oL1e*h;~2(JJD literal 0 HcmV?d00001 diff --git a/m5stack/fs/system/nesso-n1/a5.jpg b/m5stack/fs/system/nesso-n1/a5.jpg new file mode 100644 index 0000000000000000000000000000000000000000..1b8369202dc4d78ed8bee4522fed8d60d75ffb76 GIT binary patch literal 39373 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

P61HBYuhw4=}30 zF5@%bpbFayHy#7Sc)z8cdyzm?SjdCfqkNb(rE41HG3qkn;0@?!gobAgt0gE zK{kBS*E^frD>eivVS+ux7wkJFtQdr~P!2x@|5dQ$c2jodO;He?h;LfDL#*-Wf?Q1^RO#dvd_P;m;!Dfs@nKEEi0*bbfp|XF#aG{{SfeqGDdXgq ziH5PsM!!yzNnLoVCc4gG=x;RxfbLhOId>-jh0$k@NW`8Hg!-upzexa8e*Gw1BXuBl z@DGjY7B%?-s4#wReuC^l9|I6u{<#IE^0bOM7+}7`YC;E)b>Ke7u+dc~G|svQL`S}} z7H6+Ow8j+oG)}|(X{SyHx0C1K!NeHN$qGLeHJyM@tz0q63)VJ+EV#z7Cf37D<@W1c_&5Q z{g9H>D3)1gQ^+YYtXO>R3;(!@|MtZMpnyla|MGVWxZmtAP|mS%K`qJVSs1y(tebK?2b1G^E9@e2xlrMXuknn1zGd4jT*SeK{5;feWGa#KVrQJsiW$lMEh>Vu# z{uMpydjTJ)-*F0*XW`7XE0jRfS*1!<3dac<+|Y?lEK#!GzlP;Cy9IPp3&WlB-^~$c zOB%i3y+O(B?Q25YyUreyR=~ttq@Yli*xNPmrirEYI zc4TwSM9cb2U3(vxLSBrIAD*Sp?k)~h zxS9ZRfmW-nybHYtS23+m9og(c+)x$3^K{Dd3x1Ci9!+-?+~+&}`143$ZmE+oLgS11 zkwHOP1ROz+`aqWHnsb7Mj}m>Q@jH<>fF_k6D(FvY^*KkHpeo&tKs(FqX7}EVRL~85 z!`OVgID>$c9(3+%EFt(Iwn2pl%4AF%m*07_!^y(xi&rWL6h3tSd-p?;Hl8|+;JjC| zKPvJk^hDDZO@<9z3nON=9g>w z1tV(|%AKT*4-2OV?JU$OT;JM;ZB&PM0#>5888 z$iJ|}v|DMWIrR0)=}fA9Oet&Z{EF~Lia&q(J186X=~RgkqR?smM|-VY>G<&122q@q z*UR|tkK{9=;$ga%_1jEc1{50~!J};sd_0qo;zlOPu-pqJQt&e5;|Yi;thLX#hK!O& z*G2wFcn{!%b!7>0NLE!Pdp2rhoSICNx_Rbx2n4j#~bt%>3|=$yW$V`>>&a%j1tOVV-rAdW0m zb|7B0i^VRLUaYj($Ycw^YsF*>W|qCnQ5BE7hDcmrwIKaI!2_AY$70uGx2F9|CQM{> zlD{vHBWv=NeZHOBQAIGJA6-A}d5TmxhX zwh_7R=Y3i$!Dmy;5mpjb{662T61*$t{eJ|q2~GAeqfV1>r#MrEN{U#TvV{sNrCP9r zzf#2UUkzb!RB_ewoV{T25~UgyBZ#L}RduBp)8vdCr6qYq$*8pm-g9w`T$ktX{1=k$ z+Ux!azvHW`dE%Dd^Tyu@=Czd_N>3ENKJfgpyhk{7c>Zl%Wo&Nyj zom+p{M*IH&MhgD`rC-^7{{SevmHzU$qr0Qqp@pHmD zzkt3a_$x{Aw}bWTf3s@7AMp;87Nw_M#vLV$v==cttgy`-bKB0FWrBF)nkPvSSbv7R zF`MzP1?N~UBH>IfEmnMy^n>(-C6ZRwh zn!jpwvwx1C20T6EeM?V@J9`fhOKEqdc&5(QCzeFAwedcUE}yQ!WF($fHgVsryF0Tq zWb8aBE1^tx11%Aw0 z9nXjU1^g!P_ruF8@d(u}G<`A7udJJ9neE$J*Dk#MUsre<;&T(~H@6KLmQAE@Z23MS z_>lVLD87*5{ORGdAnURKF)b4J)O?Bb>dz)Pu?o#gJcpzH~d&`)hiqa?}3n2wg7aTlE z#=au)Zw>H|2^Ma=e!=#^yPW4xI`(%-VFPm{(PtrHGXUDAICMsU;pr&PgaJt39+rTYk>p3vB*4 z-+s~-z6OR5jM|@pBlv57<4BuOTf46Y#i}N99H`RIXM~mpSlCy1cZpx@)0{nrw)(nrokdI9 zPEurYyp|cn^Z3%ktlFjtW%I?Vwv@1ns;sFv^YTC5MLvFBdnF!gB$Y3O{{XWm!QX-2 zB>0=+{{V%*4s`zj3HY1iABpGjkHnpB$}7or4;6TV;_CYK?=S9d<(@$rUFv#`#1?S0 zR(A7WT}ccPwDOkUjJW>*#Qy*gGQJbZIJ+{=^PH!F@|t-k3t%hZCk!@GmeQk#!^Val zl^PV{wA~8DiFT ztqRoInCxz(Q|0Y%u-J*^jx?I#Y;2Z!Bo?p)SpGioLo?uPm&0s+ZNwO^2;tn$u5pua z^=dWefxzY!eYZ9ulwB@+udHGnI7(8dN#ZNIaEq-`$fJ|wIDFd`o>9YK>E^iF5yN6} zv6NL>v}4I=N%LF&UBc}o<;e@T%Nu;|-|$OM*fx8c`|U&aQ}|!3>N=*UscRaRtFHK3 zEpt`B)U|8b^&9)E%}-TJyW5E-y1A9WmUdW>tWgAs5f}8;^rQMv)1`;Q;k-=a{0vkxIDWNjLYzNd@c(QPYs8qM+t+cM-NIcqe>Bcl+&9|b8d2N_AF6QiOI<}Xi>ak2NAk;K(59+tqniiFJZEzNAX{DE^Z?Q^QrbOFHAeD8yvAoGh?7x>Rs9@OZp-5^(laF!=1t2ZN!QV<^?8+$zHp ziK!Z}a*ahLCZB>CqJuUp{ zF>N`L+4p|(C&0($o*(hgiC!0HSgs=AOm=UOXIRX)7nIk`FxVGU5iAZnlj~HWD)@T7 z&Q&=h``T(XV{dk7`V8YK$us;cFj>7EJ~InWy*x~4&aW&LYB%prZvEGIzNwze z4xcT&FHUky_)&1%kdDqu14ok_}blfqZLr#9s# z{{VNpZL`%?s6{BMw310Al}KooiEq&#pr5nMawo&PVDY*i?HleHSx?^`hz7suTlQhm ztwZR)mD8eq?Cc7>T9UeiuCA`G Uk99?D!-L>*{EM`i~s-t literal 0 HcmV?d00001 diff --git a/m5stack/fs/system/nesso-n1/a6.jpg b/m5stack/fs/system/nesso-n1/a6.jpg new file mode 100644 index 0000000000000000000000000000000000000000..4234e495fd5951ed8b147f6c62f4389c2955757e GIT binary patch literal 39824 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 literal 0 HcmV?d00001 diff --git a/m5stack/fs/system/nesso-n1/a7.jpg b/m5stack/fs/system/nesso-n1/a7.jpg new file mode 100644 index 0000000000000000000000000000000000000000..d5755e8bb3199f3459319ff546eb2d147b1bcc0a GIT binary patch literal 38077 zcmeFYXH=8#w>B6>M7ngTQ4x?X(jk(+@dbW2kN4)A_n$9_ z->L54GaDsbQ-0+CmYIcL;F6$_grwAUX&Dt&HFXV5E!}_g?&{w&Ff_NYw6eCbwR3sw z>gN8$!!sZ-=y`C+iB93GL5$$wAIfEfPM(f?u4|H&S}@H1!6 zontu1^ly94oDBxP8Mw|dUQs&FecP1D*^lSy%{Lc#@4Ww9-N7ubY)0UFes_Y8Xb|Hz{MH0VF=q0fTY7|s9zW8eZoK{WqXeLVDH^2M$1Ub8?T1B z7VSV>bUfem z3_|871}90*#xHS<+@fVJdo!&|Sx!H4)bm}bxwn$_!mXjU>7M*@9{$lgta!=!YgyLa zzi6U;#8;=HZsUWSwL~=$#H9*wzt)7o0!`C43@>e?h2|16FpFSMo~n>|Wn#B4e#ag!1lVbuq^F zX~-+JMCYQ}WFB$@jAd@)d4tg6H&@((iz$)UPChrOsv-gbsRpk#e8V z+YOF*Zn{Js>2_jFyEgTOaB4#qAf=P95aIBKi6Z5*iU?UkEj}Xw0rPV8@m>{ZNtnI! zYvyK|!~EU-yOn1+ldpUuxHa=f7b2WSX%d%5>x@nIy7JCUQH+9ck>92Q=R!>nnXHKK z?OY|y=iQh(L^Tq<6W0oGRNW5vB?vQ7Uw=YA@HDNqO|}wolb8`77~R5*u(Y2E*(|* z#7;kUJ;N#>_*^0z=f75wS&*LSF`R-G2_zz z#c4=|PBwk{Y2FKOUynfQEU;d-;@lDU^uZ6F;bU2rvoEgF=hU(Bk zF+q_lP_>GeH<-)WU4&hxg8W8_>Jy z6Z;+dXqBA~I`@^FOzQVKD-OM7JY4sFhAd<@$Kt!{5QUSgw(cr0pV zo3u;>dgVH8&N-1|i2??(cC(?kkkxeq(c^`ui2IEOn;0m}gboS;v(uI^oR_KTt)izY zVI3PKPiBit2?PHSeR~xX1zJRp>Q~dzMD-Q5l5{ctam1Z;>z1T!sThPXA-vP+Vtw8# z*@5yqi&xrCRk0m1WvsgtXodV=ogW&=$QF3?A$%5tQ%#PpOB54kMFS>uCb_lIK&~*yDyKdtpbYF=73Ce&q2>- zCg&3OzGr~z)T49+!-kKMbV-SXbZeaqG}2rf_x_r^1tj60oR;MBQ)S9ovNutj#5Ns72eE`~ zEu*85PoOadbP(&_vSN=S->f5f00+myI4QP3mW=X6m`fJ87Lx6|X4T&!d~pLK+zuhn z_qwxm;>*A0iA?UwDX3;&NHLfAFo!mlZ#-HWe|D1&dNQ{(Tic-{NkwyZbIu6k&@mX- zS4UGS=y07YgyhZzVcb+W!N1p%Sefij$nL?g(ysmyV`?4?y2lu~mhW8lXI3D_y^+|H zi!62AvP+zAXcQ`B9k6^w2U$-$>b(5s_(ud2kKiPx^g;;+m^Upx;h#2W;$&&UIO#R@ z%i&Ut9s2cC@3~$v4xw!*BgGvj9d-@w!KH_{_trVEtS7dAmcaha<8c&*5h8o4`bs^t z&Nb9i!yZ~wOWMUkdVBM$@+mwc_&0{5c2o~1!^Yxu!t=-?j{`CcRz?S9W1XEa2o`^xK!RjZY1~{vSkYwE0;@9EJDHiti4*t^V+s-Ov|18T$$veCVWtU^ z&2MAKj>KqEXd`jG>y<~V629D~E}6?AL>mu|Z096n42W(8W$-QQP=QY(8J+P^t}SpCaMX-AXLPY}&`~njvt5-O?S1+Pcpjx)@(R=Zd8D1p zokn)blY=Ep9Y6C}VpO1s#0zGa5q3%l4q=yTh-O~XN}ip8X-89}MXK5)ba2 zeIg5E^XuKQqUzsYN?|!U?R!rzg$*j=+Z31g=jfnLW~KyttX650P+*nvz%vXX3E8Pi z%?>E(Qz(E&(Lq&(AFLbH2KA<=X-sP$!MRJE(BIF-pu1P-pyF?{57oQ!#HL4Lg77N0 z9)?$VMsO879nf;2)El4J)Cr3%n?FuCDt~NRrqz8L6SR5w7Ls(E>HKTyWJY+r?KTD{ zFu`8b4+-YqDRit;*1x(P_+IF2WkSBJG~>M$-ZOGxT$9dkDhddTCF#3g(HFAm@@Wd*aZ#3uHHK_U!% z@7<73J4dsPkEBuT7a`G}KB=VVElF>}2Gaja|v|D(^sTOs$=e_P2)uWEmK6d(lTYJoR!ek2M1ono_*@h7P*?ZE;NqM4N*y zS0WqWJB+6{X`@cu1ZamZ-ya$mVYrk~dygnV%B6!Y=)VmLSE~)eLwSCUJogD|V|W0d ziI&j9Fthtr$+G@Eq6^t444v&PxJvOKN+I(riYJSbc^>whEcv$GruElxgzVZ5 z9cUo2Q&1o9iOejNCaf3*rTiHEukVSb{IjS+2Ys#qPP-f@9i$xnKU)h-WC;0&FxAv-}B8Fk2G-BPL0-4zCY&>DCbT|6_$Q9=J~4a-%A;YvL2pzq){yj?c(flU5~Ci7YxNz=*x9Zpzf&oW_?|W^eg_F z-I`@(eCPsU1`_&tpq)rVn6^x8w+nA+H74z;IO-VNTh|jZIuy@(9c*MhzRzuZ=R{t) zLqO5XEup`{@5a5f7{(gCOG_LS)qw(&5DLElBTKDCsS#h6%k{fOcPl?LnG5hqtF-ey zmy;4BMR~R68!DFZ7480qe&jJM?wLL*787mccwu zA2U4>4|(kEL|eQe5U zUs8OHxxN1+KOijacmS@jC%R+7G5MG3ZvATk0U?`~B3;PGP8qoTPA}LtyDP&eT-nREhi#9N7BD zRouv&BrM>aT7=opj4nEey)IcaW)+(pZ^3;J91^f;gk>NE3r5)k)$$4F#p zm+C-=97_is)97+orcX02!gH&vSv>~ng(1u8N2nrMo;ckY18|L~b+6U}I*M||1CM^K z!$vp=iFRns5`Gn`Y~&69-td0G(Lj!X&Jb3DD;Yf$nfSSkVNtTER;{Yrr&vNn1s^Gy zVcjp4(K-{%{Yy>FdklJ{rOK^F)9m9Z#@Gq$Sl5VfbZK0<(4~;+{;G-4dRvu(3$O0W zy(pYB9B7UwqDiZOGcsu-Vp^>LstoUbF2+eri!91L>PZP8?BPWBPDCkJUm3;tsw}ZP z&N%4Fx$kxOUADH4?f;Qf-eCIONpQVM!%fi1QN)VdKS$h3W3XT6P_#%u=wr;VyiSs= zk!r#H0hJZx#r>xaRkrE3aMd>w+%{Sz;fl{ai62t08{RYyGA1o3_aSjx;6_d=pEno? zN(L58d+aq1^kL@TF^3X#(7-kjv@Do5?W(U3^5+Z^$InCuWnyV!)u+$spiwIzEfHoA zN_5cL-DGkkgq04u1E;hPLjM**@sdOol|6&T^$__t2m0SudGcQu(fwatgW3(aR4r^g z{}RU2jZ6sB*qYT(NDhbM&eA~<&ytzQM*nCbNk56!qzo;N_~Pv3I;6WT9n?FLz4{m4 zGShY(ri@==dF-G}ji$!GJ;|Te!5k$o;bj9(!DXk=mC4e9fgLHr?C}KEtCJ=<+!X81 zsoqWX+C-l#30sR4gLxn2dr*V||2DVf=*Z5_+HF%dfDIiZ`{u};v>#DG)hb0MY#qWF z=%C-}y~tmi7}}SIBjLnkn&Ev;YTR`?XwyoL0zuCt_nm#s0=mvXN}*lICkhzCl9sqI z?qC7KgAheh8X1mc($Og zN^nmHmR?Zn`x79l=hg_wDClxt*g0|&ox z_Mh7KRl-yc{hl2>j^f>ya+DcOsA##F&(s_8RbRyA*`=pt9zXk={2gxF%Jv`0j7M7D zePBi1lPUa^S55k0)wV~<5sdoW@$0^ixN@}o(*dZhsDP~E!g#5cPgx3PcXv~>@GCXP ze!~a6-1|KS^KdB^OfZMe!Yr#kV2#PSNVj!K%ZpVSDH9A2O?S= zl22pyIDLk=KT!|n=j53sAODLEN>&skm%~OCBiBCTN+rEru+O#X+uu|e+R=W;)*EYW zKCr#uysB5bRl99@9~v`%CBqyQb<$d8*$$Bu3i{j!yI8PY%~QB#>hwFXNTcGkcJ1bU zh>{3Xx5nQQuAP8wHfR#$Ox!7&eRhUCd&|k_Qg)Y}T5cK4x8%=K7e!{UiUW`hmXWOqx+pjPUxMqo0U_oV$^NT|HWHGV9bb^!;U-t11)pXvIvr zd%;j%d!}4@@fG=Wn?8I+o*w-^{0_+-uRqsg)( zkt$1!Ve?=vh<4$^)^P*B9r&#fGr|b(_^LbW( z_XvINLeH{WP*$R<65`4mv=#3a&8eOMIUTViq5N-g<-C(J7g5IA63@TnTu?_yN+v!pXe}`DG>h6Y=+>wYHFw`YQTP~W zEHE7BoRk^)mJ1=^4$(cFB1OQ5!n#@_oUYc52WC@sd$MvTzZR3O*M-6@-+Zn~n*80+ z;^ExvvA4$-ZOJ%P+45WSgm>|j+3Xr~ z5UaMjQK-)JvElb?2#6rAK{i?=LpU41h4XrdV>6a3OVgYzwM&q-T1zOCNnH{0x5z1w zVD8`bpPFrNmnSl?<`ZZFvYkJ>OuJ1&US_5lbmmA~<_bB~Jb0EwFi_5t z8`#|SXwIa?Y=nR9fem`hjUu1iODw+K73hg8c-v1n@~}o}PT0g67J7AS`B}^hIgP@z z3LR5w+(%X&4BmbxqzAv7EQ(22R6?5+3B5}Yz0kvv5;kVy>2aDgUzNV)G#pOqCh(yt zJjpacv1u4Fv9SwHwrA>v&b_ER-kvf*a^KhjDzParjo20!(2icKIZ$BV~Hcy&_`6?K_ z$md5$5P$2ikZTDey?Npp788dUM)C?)=NcwTP-hxJ64I%4FLCUCoDt0RkI%KZg@}6x zy21u^+9iLZr`$rM;s=!3_(jc7<^the&damE-{#txM>za_v?Sv{S-61yyg&0?$vHrT zL!q|R?9x+9mT`jv0f8@A5%71l(jhVeC6PhiwlXq)wLF)6;%4O+$2I=Xj7^5 zrNw7vwa=Q}lK7pyTk?Mp25<*N)q^uY{y-YR3BJ%pY6)~V9pK7DtPe2B&;w)Sf;k-& zn7oMKor?+e-y66Tj3ijWB4`Z6=^bXAv31vp6e*DcW^sg4*`-IZEW4d{(l&OjZ0b zO6obf0{uAofRmI&CgL-q#8jUx>UB66P~WHFTW<|0%aGmEU2PT-t`2+9&t~Yl-iJg! zmmwq0yO8%;Z-2MMc{lzA*f_RT0+JR9qm+9=Df#dTD3P`Kkl zj%4h5FML}YJ`au4$(Vm>LLBEP#-$39>HSHD;JgATHy zgWRkW#4eM|=^(CG)R!a324ppIVsCqi#P*Hh2w6!7eT4$e&a+Nj8>;T49*jTO-+$H6 ziXKFE7-N1nlf7ttQvff8TDoYxxul!_SMBdKHLYocYUQfG58H8|gHqq%pr@*T^=Wkw z!!UpAqf*+#NKlw3vcrU?iMU72r|}|`h#2gxm&O8?DR@!_YV+O4FIgjYqn`KjAJ=N<|%BCVUHr*stuW4_;?O{rthKe&(cXr(yXc?I{t%+MG+0wgT9MFeIlic09Q`XEI*P3hU^ZwO5 z*Zze2ZjXV+e6QfOl~UEyB4*9e0(Gz9G)-;q7q``YWoyaff`%$eA!Qda5+cSgP1`AT zoBdpEAFOMvYwQl8YEV}wfO5H7txs= z31wcaS=K+QGxdaQBcDv9ZnpZ|yflk(_)xxGIB_dcMp}jD3iPJIz<5Fr;=E$Nr5FjX z3#EX4CWE|&z~7g+6}_E$=DLM`ALxGNt2=a~(0Sf=s!wqG@@cA@vB z*_B}YeKB|jjJL?lU;-7Lx+29W=C0}C%G>^unu+uxk$nj+qK&h$m&2;w~9F3@O<8R2Uc<+=&w46p+4dhC*#Es!>I! z1<7g4Yo<61$FhbmW49R7GtNtXVakkAA&7X1()g1_CgH3{n&MJBO02ARR5mm%d)zcm z;x3YYWzmj{Jbl9+&omuR(=wEfr|b;=MOn9LqCpv-%m}+azWO7avDQ}v!bK=}+vKGPm_*8a?641xA*U>V|y-a&XK%ch6>UiAo zd6okE%lfBWfoX(tk9eFI0B6^5QR*j~cLMnUVTj9P8>w2Fol!GQ?5_@a*(6dN=*N`^ zvEq&r7Pe+m^wrIuN7?1qR9rAr^1_^fdUMW{W33le$mXYJUH~;ffi7oz<@&GVe7hG| z=4_@Aup--Dwa?`q11rCN=i7$f^_LPdWVM-OwVxGCzfg6MxV*5vKy=G!ZVdsMjZpJPA$UJ+1qb;`Xby9tMr%-s-yfBj^{oE-(QW9N-L>q;D{Ghmq2?6hhNOh^ zX5cnqZoFqsjsVWC@^ncMx_h7IB#vI*E3Tx0U;1&br{?eIZv&IxY=C!u4yI9=-u?H& zzZlAa{5`i0Ct0mPk8IPxakR;izxTv==%8A7DsoG=3$-qRw98++@PKJ$iNgPK+;Rfy zz7%DpB^LXY9B+}bB3k%vLyIFfpv=>}r=5Y=Wd^ZoDA~XHkDIQz%LSAl9`liwdPw9W z%U&*s=)_ky#ro+<>$8V{hUmTMe*Cx9_4;^mPF8%*i+sIOT5Q-*8JSHgKF2$5Ncn!F z)ORd4;l7NUV8hKMCPqVle5F9n6>~19XA!QZ<{#dE^Z!tzUqq4mH>plhC1MiV!4&0I zvizwong6tV`UDwSH*zs#={x@{ENa~5!)RoE+t9Ywp1WLr+> zHCNVjv)7u{ZktDj%w4a@oG!@t*|RpIDyMT+Bhwu5uGH#Mf30ERrxpHEZKwU!IB$eC zn93Y79!d6%51C`f!^7t-BXQf3inn@JMHIX157;(e1S&C4Rp_!>%VEdzt0FD6;w)DJ zbDT%iFANMJA2ny~k;?wP?~o7L_T)q9R!5wpB)bW|bAM`V{Q-fLi$A^fXm-{Xm*^wb z@Bdbp<49WW=I1BHiF4lx#R%F}@@evevMT?foAVGQo&vL6&8ZI4A!+<)6Y~U%1woAi z{{YD%LCJ|POLL);(q&={q0Iia)}13NDC`VPr}BJ~A}5?npE<`xBY-Qz3eBP_`KOo9 zz@Woo^+TFg)dzEwMsEl5A#i9-sGp#Zkg-meh!T-NLv<9}uH>n6h3t(#t&p)%RPjdM zBUp#mHK|tIaKeJ;>PAa{Ic;d=+%v8}=|dCb+-;_K@%kMsUlG$)%Qr`M%ZJ65i{4PY+55UU6;Gv6{Qe8YRiO1%?SIL+vusV2*s zvsy>d>{u1?z)mO~ZsKYos2g}6@m|XTB{aJkYSItQ5Z}2%u)LOrkhjJ8$*|iRO+lBl zVu?Tr6aV+vC34}BH{wMRs>7SXwlz}9OY}jA3s&dq==PDlY^G7$yj@bJyK4!vZNjcX zTJb;cwp9Y&Ti>DISqQj+H zBS##?xBcahs)^ZKGvqDWAh_Bm^3?DTv~HxkO}V=rN^tKo0?#VqiDdl>`%U#0x9Flp zSnwy$1~KW{vBjy`ed&5$1C?7QAqwKBR@rid!3saGCnO9H?l>|;2h!oU$BjD>S(hFD zL+s^2mfL)^F4twg^L6uKHE#e*NU%w+O@ED9r*)2uqQ@Qcgp6BSCiz>78dQk@U&a9V zxmKMXK+(iZlw!iS3KNJSk?EElxa#I&C_9WZ`=QbcekoC*qpAZ`c1SwX8-HK0?XJi9HMsGV8aW$dEb?~{}(ZnM61b(c3 zfn`^Nzt=S0A-u0;=HX7=!fnNrbLv(b@KPP+!KQGa;Bf78h<7}?fdJ~3YR+nAo3!($ zjLmHAfFBpu)-`fL)%)Jgsz^D;0UEqr$ z<}o4Wd~89?c>kPR3RgNTdRJ{L#e(vrm>`v?y3a%L2^MXUc41Q$hJ1w>C8S%j2(3@& zELj$p%tY~^hSQYD82tHs^%!Ua#J!jzQrE1`AKZR?bkWC&Q* z^J7c@jTPTE5GIH*3`11=nyk;H`@thbV43TIu8Aj8UeUNXRn3jb9=r@rip}!AC7WR` z+qFN~+3R=xQWg2PvuVpjbY%NG2V1;Ta95a6s4PSSx5(8!-jA$p@)gGGTzy**gLS!BQEA~I}Ctu9si=qh4@;Kp{M+m2ZX!f_$5Aa z*Z?URP&z5zC+xoY1YZa#JMDDh8&e%Jq1bH*oE1zb2}7mN3DnHdh|`1bd9~3ydCEjF z1}>}^pWk`&h{0MBJ~0Sx8_)s1?N~0i81!<@^brwMQzdVt-W zYedSLon`Siyf&g}K%i{}PL@`U?ivAYF0xYd!utV4602P89BHjx+Z4~1&6D;F&f;eRjXw}l zy%Bgb;kmi>g_=^l>-Lp|p=yEB=z?gJi5GXO%!GM(gr`YdDvDD!sy*^oB{O~y6Wi)Q zRL4He=p2j}&{yg4GcoQWBSwkxBIk4(?PYNZ0umxVvZ7XP(yj+VRTD*bzm%Ir$~k;C zNrM9|l*`DQmgw#EQwTzvZ2EQcf#(S+()qPp8`O#KrqavuF2rHmiO5%rl?zzHJGy)!;6mGIQHrdBoK>z&iTPX_(Jq8Ow534DGI zNv1YAcZHc@Tuv-O;wBXa=7|P9GO&%Z(-q?7Pn^?evPAGAxsML=ci$(9BAG%}g9)eO zF-S-u9pnjptWH@{JO$)#$A2`yWNFTbR?|v~7h!cV9=G6SRS%0kdCKXB^2o?|S2$3y zH9V@4PWTaj+4}DFdk?>&E?sTU|MNcY@r-+PW;a`}>Ht9%)os)At|-3y=VvpEMf-F; z<2!{bQUcfeq$RwHTfn(t6DI8V#Vf>>$m&c^cK6I4?)<1`E3slJ`F`TZ*^6j^VTnP> z+$@hSFk>lnHsgNgn|asI+qU3fiN@F4#d*SwummcuD2xtzUw%?R^}+d`T(5ysG@YS; zJ!k+b?NTI)(_&2I=^*TRcR;fPATpK}JQI~!M+P`Y94I>I#{{xs9=NW@Z$t-0%K^Yc zo|7C&yM`QP?#7TG4$?t?KPJJVoV2oibC+Q}Mpe5oWo%3M&Z7$w!FIWYZ|~maU>22e z?aq;s4e&d!FRNYo6nlRAw0v@Ob09h@Owt@vyBu}{HCUl=KVCKYX3KL#Q&3P~ZoWmP z07Yg*=tCl~BVeD}@52iKX}0%C9z|_?x2Qlw!aE#wwvMx) zDsYM}l2=}WRz%D#5)f3}c9l-ltauV{r-q51e;0>?Lw)qNkE6VPDiV9H;5r^RFQMUL}Ujf}=6Csq8^XL;p;t8|nwX1ezZw;;y`!rEvayShI z8^8k>DhfofOuXp!xH7vq6yO2Nlw79^D=f7hQd*h(UYaU(2K_eAx6^0;uo8NE@9i7r zF%V@8)RwgL2)m!q3Zsz+B6d+JPc$66mY->S_JcE{q!ljz7*A`?8oc4rKNp~CwY#9e zO&ICwyL&O?oL|Ie6WTEMk6LRX#PHq+Mb0M+n}5x|&0itAtYimCGrgu5olTds0&{F~ zmAXvE7+EqV``f9tcOf1{&Y?fAK+RlBn}7Rn=2FVo7^qk8Q_T-yC) z)ofGyYpc=T5~EL}@*`>LF4DdrazbX0q!c#e_uvfy?i7#Hj=D1k>#vz^8xY~lI$rC4 zdDhA3j}NIk@_S(ii{r+=#oYpW3IcpRtiXF)PP7Gd{{Fx5=2QCQkj2UVGEe--Wsb628 zAkN^gz60b4y0~xq)qah~UZnd{B8X3Zy@t@KN^Vc-^%)*hvNb7+1)#@SNLh%8RyCrg zp}zxPkD(@5D#y=tD5iU~K*JpSiAE)|}C8RbdjjBhmzC4?^%}YBR+%=?vn15&1eabsFO*@~` z-g32>ddy_f?atSB~s%+Cz}Z-=`CKx|F+ zv^cp8I*7vtMzaGGSt<8`83Io{lF$M0od-9NolWF7fNTX8hau>e0;Ihvcq^C{IjZ*o zaM}PfAc4`PT|vfz5AXQ-(k>}OX>L#(=<>&QVIouX9Da|q7F+fQ$bdiypxaBc&jr++ zkf{*ewHMGQNl@j1#ca~L&+70^Su%1XF?;nz`7(5SzLznXBnu@inRktLy;4AKSHe8D zQ3_5zw*Cipl*&0}Y8*9HM6oMJ9dKwl_2G{IM8+0!Q-2a!f0JUxv?bO{btSd;Zt<3$ z!rXVH5BA|1dFqWfp^9|SawC{v3Z7jj-KUmN_z1_#>fPrE))Do-;aT7~-@_h4a(K{+ zPV)zkU3EP5h6fz(%;#%P!I1ZZyWhYI3W5$oOYFi{*_{m5Q%9 zh1rujv7x=B#-zooe!k3pZVZY)YK21XOFT{Cj4L$>VLLC~a?rgZ-R~LlRn;?A!|d&< znd#VuV=eI;4eSBP(SQV5@H!rl7&QVQj^?s8O#3y6`6Gi2ME;<_XyWcMbkLzTaI;1s zfOvn@{hz++&;RirC|)fjnGVuz0h8VPfLt4KGPexfc@Hc!M=e&d&*!G_PC(!*aJJbI ziE{FXIh=my04HZ(FvpVC*qrSoMA{YQxX>xeHZ?WPJ#;Z>F}}(Dr441RrcI_ECEA?& zs353i%|rID!^wPfLTe!2le_zI-kfk$LXMcErWlM(JF6bYGWqNQO0&GY=cc!#qii{0 z=^|$N3z%Ssi~z2|nQI$UpKVlrwLXZ@|F9$(iU&EbLvC;T;WkM28= zIl5t+t`LZe!alkQ`I1xKQhzH=-O%iNiHUOQu5^!yK+D?b+*ScTnXGxyMN2OMWYea>&J@njg7Z$V|Kz!4YUYOg;`0z~Du<>g!HgiYX2^9*Zy(a1(7 z3O^l$$MisjC}+q>JXOC7$xjpQD4Qquc98PUl2tNV3l)7~qP7-jy!kXObj zYqgXyoD@&4Tlg#QA-PaFtxAB=Sb$J5BB@^}L0*Kp<&mzfJrVNoeh(dF7JqkKM;LM4 zfFFDRO{K5Tdfry&JG;4#b4G(HLqZvk6Aj*v-X9-QwyXJ#aw=SP+fIEB7tgpxgmy2A z_v*{{RTFz%+*#5rg=}?K~Sn6FBt^7DBsx>3@D&Z)6GS@yx_c+<-5LMUr!_1X$E zhvamt2hY8Vmk5xNU`ae$wCRnDM9Q$npH*_^GSR;%{jo@0H7`vIF^1JMoPG7vKfXQj z9pwcTn4m6y1Gp!}@XO?IA_hJ|Va5sPI!)h+w!r{OZ=({pWoy(Le)gw6>W_NBUb?JN zG~1(J=iKFPUN27`e$ty&?Sxr#l$4@^HVuY_J@>!m8V&P zJd9P-vBb#UUUv*4NVKPMh?Cze10U%jA9vo^I_Zs|czoIjlOznIIp2nz*W)Pvyk+%Z zQ^=LVHBz8=BWZIOFZfM)& zB5o4}uo%7$?s-7Gn~xK~CaU3IS-V-Z)_PDbRXff6=pPWBEFWS>yYl#&P-n46)>wvw z;gPe#Sb{kv`Q0$Yl=$j$_W(oAid4oUd~;ip?A3mce$$uIon%%Mj^Ximzfi%|e8MS= zEDfcKoP(3S+XaA<2sWiwXCi-E(15!3eihmElnACp2SN!WDZs@GNbC-D8NmF9&$y7d z1~ME#$D!ak!3-)RY&ZEY6Xos*7ci3#)&&OVf1z~Hjr^azl#9`%S!xmVsT8#!dWVU4 z1`+`1@-LI0A`~03P6C8uQZVV5A_<>u2CAs9{#g4YDS!JLn0$IbK}#L$z%wO*GA`Tm z$S-Vei_g_OB_*9So}G0n(UvifycY^4&&6tBsQ^s3QTo{vs(Y%q+4>P8#xrw@tUR@+ znnj9*`MPo@XHAVwyZ@$xC?WuKmZ~PK(}oVP@RRHiQtS2>brXEF20gJD=?hDN?>nsK zlT4hEN7^@_dHuwZb)fy(3}1HNVK~(en}Sv)Z*QF{kYABzr;raZ37GSbFjbRJ<2`kN zL0p&Y#%k)!rdD!{T>UMMu_tKBvwK5LlPh`swB3Wj@yfSz{fi4SbzZpZKW#E1%T12I z`6VnIdKag+I5rlyZ_U$CKMrW3_~CQ7VF4_$B8*}9=9A%E8>eD0^GA!+du6^zzPv)Q{oQM z++=-YVqV(IDJ!kY5=LTrr=zEK`Ny@JrL?iQ5Fei_nG!yp6MnqaDmvBb)|tIC6KiW` zalxZ|DAl9A0{3n|P7Y$2Uay0oj`Wa3-gvi5&5u>h&aR2>v2JJS6%UKwk#_CWjTWE~ zWQH9XDt}1Nuf!0{6~nEZ;xM;O^lt|m(8({r3=oss=kXT&g1lt-d+QlAw56mB1ZK$OkZ_wbCA(<(<0^K3o zxNwmuvopO7qPC7D{{kMk(}D~cU8DWpr{YC$HU1Yg5*XwNSlI$7^FF;`ulD-{pC+eF4-+6=-FW z@^RzZ@d`Q=7Iku~rIyGV^S2uK2;PhHZtcy|%>z78^zsMvh_peY>1?pSuYE%(z>l8m zCj)M0oWC1SU8*^-?hFUqPW;Y)@(RL+~@34CcaqjQOH7o3RXKo}E`NqN*b zh|_Nb7qu!7EW9DcGw$}`Tw@)P{mbS^K~cG5dQ=bo6q{jP>g&hdx>NFNgX@~lmazUn zhDAi#6xNV7z$JLS;)Ca|cKtxX(yPLjeD>v@;EYEBwCl@X;glG1dz4lZcS=oy5*KMW zE?T=@6UMGtbbZmJNK0CB%xenj1`pR-qHv=~52)=(kENJ~y)P8|35YHpJ_BDix-V7J z)|#s-{neKO`~0K7FTnLyfA?2=BawWF81J_WbWq!!6VqaXijrkTN0>| z-!JEtKTH$KBgm(*2fTmQj+8X=_S)!QHyYdF9L!K|x^|G-io{ z#zf?DIca=--apbpXH)UoK zWcjURoX!M1td^c6se2t))DM(xX1&~L9>vKEBob;9HztOsIa_8b1mxC=3_6@0t3Cvz z;xvq3>7RZp?QVbN2+(EM=^$S?Xn?TQ5q7ZwU`^h}*@gX@654I{BLO_CN0{XFkv)=q zz}c&1+NQN`Qgz3SeT@3WD@4M^^k*grp~MupSEd zV|XHz%cVzUYdWK@)2!RNTtpeg>0gJl=CX7=FjVrCt_Xq#K%eft#WbpS(?P$IY0Oqo zmkTuUx>>31oiCiinAZ>>m0pO>kf4L!&@Lf6wPp+Gpr;#Wx6jv4vX}GRXzT)n>XfDY z$*%FP3G73iijg3_0RMK!6%I$AR^ao4VXHlviX!VGTsXVs?vKq6aGPMKlPKlQ^q?D> z?vuIjCA0M@z87H%_C{X$LO{XuQ&2^6gbhQhpq-dxv8!VU*r`*9?)O=s{PR|R!qiV9 z^qh+vXPyvduy{w4vtw|F5kH?t0x3pXOHMYM0`HBlrxj;p+-!Y7d7JK!G){W%#>|7NPmGfT#$v;_1tR)sm)sYRweyQ2{zyjV$)12rgo&bAIzKI%W8{<`Tlt7o4LQ?iOKm5fJz4xBrMt-vXK?lk7DQ#~?(d&wwbHjTN%Zv_VSQp<8J zF2%Fv{}1-wGpflgTo=W~QWR8FdX0+orqXMcvVa8$NDVzA0s;b}ARr-85m1UOsY+c) zi3lOmdm@CQ6cGXhK}tezl28LF-nsT3cig+rJ@@Z9>EXgOq`#b1Jx3Sofby)jdR?-MP$9aG=^K2Q06FQ2qd)UdhQ;;J@j#)? z^9jzPWOzSZ^;Ddg$VttMB~G^cvRuarIb_SO&A>;7fC@M1(^gHa$-mH7E#ITse_uHF zG+>;spr6J1k3X&gjp2C;sb!y+U7bn~u+hiZ0|itEs2H869V*G@K@K;EgWTE==)@!t z2GMADI;_(}yVdex$K&-HMW;2KBq=P){obV5U(c#)UGEr0`JtyxXc{$_y2L{ZMQh9D z9ZMYay84{@ngjKsCnx*0gS&17adH;Sw^~>Wb-tC1iZ8Qrx?!U0j}vUkSRoNe<)S}Y z6G+bz+-PX+TFMAs8zdo?qwLd6H)?zrD!#^TWdi%8)weXFfp98BW$i_d$;ceOs-ZRY zeB$K@vXjau8}Z&@`RhrJjbtsWkTNdc6Y{3>hivrXL69#@c7PgL@3CZ6$th+b6t@`g zR#(H}FOwj7+0fL~y6on<6hMN}6<|z(99IxI6aC=at%_)bVI$prb~7IH!T&HnY5IaF znWmahr;XWwIW|YsvNVqqt?CjM_4pZj=4jittLEfyvO#*1w1n)Np>sU-P1ph1(dEp7 za;P=UqAPp);!rPW^g8g4&~FGz>kGE@ZbK>JF?QH5MpajQVf`|+O)T4J;%=$ zL%D!F#`-&Yj%)o3t$Z)6nZ7xTN@Oe5&;%QNA<5p1RJrKjnV?x1*4dRkCQh|Il95dtyLNmN7AiedoUHOA+Wv>R1RtFV}PKP*Do@I2g4)HG&iL(8UZcwM=6Jac+D_~eg4i44Z7oYcttb4`7M zOh(9dp1!*Lwr%LecN1Bi!wFUsk5G{-G_J(Re9tthhifV;>QZ?}qiBRn-Le<@%Nwg? zaS2@q1zWVJp9wFIyqx8T<9kvR9n0kB+xt%S-5^#XwCU)F)Y7?6P;qtau@w(NI+l`X z`B;p_Ns*p@?R~VZzU|m6^**zmsL|I!Lie&74yvMDjpP~UzNk30-UzXcwX$;gpr9cg z=2zQbPI7KbWl1LX~AW|lT9gfw%^elfHck|9!BQk2W; z5w2%t2XDV?;{AI43A6%E*Mw}cNqFm+8OFEr<*U$xm%lg~QH)4dya*jwheyUlQUftC>@*yB@aNTR;WX*9Z>mrN>cg1=A*e@4A+2dcVqxzT_lQ3Py?3(8+W`qs$B;pqB9^^oaXApvIFk2 zd%iKNwY%WOsgIB-=b5da7 zp0z0NTR>Sq!?uL7|AJDuBLYCdVGFFDIA3rZUtRm0=TFwHGEOi?DAvy=p`r&xm+WY}7 zd^3FAvP~PIEUu+nBIywXQ@*nqF!^YYv}w5ZjIJ2hXt2l=U%l|;aEk!6z$Oa#x;;tK zcT}rmIcOdAr9PO+nQqI#rP5H$=KyG7?ZK?nz)WGwc+F@{EqWjGEc1IWQHS@FsvxTU z4)iYkI6Z4GYfKhQ;A1-8gH?dPgHJY8q5IL|=Y-;rmm}noJDhkLa&Tnj3d;?&(obLcIzrN$_P2D$r7TpA{Y1W_`?9$=X*`th4a>Q~Cm?_U zblyXcnYE6C3uy4hu$5@_g)}0W^xW`b7vxWV~l$2*20qy{( z*>`whR3LOx5!5WW7z%#b)-gCPv!Vdfp-&8rt37L4iLMn$vF2XOm!|qY_s##*RmwCk z=(+FYqEdkJ3@J_F?ORf*QagBGrMcd^LE0t-=P;ceu4wFf#rAc5S%yyM&tIEl&EVSS@;OF!Uf#s z%{_#-*xE^7!EdYVz`?!J&>87zF;b1j^dYH<^9}w(P2+f%_ zv%#^CCss(nA}B2W#bUzOr7pokrl@ya`s3u?AN9XDzU!uI4!ZtqUh(}mEh_w;Y=So;im$2%7&%l+a zfJqu-gJsxgBED_&s^PIF2K}hnk2sG=q=JGOH-uJ0@Hj(npz-aISjh-GhCITMj-n2O z@=QTI!i%djg`4Rx)M{7cS99$oPNJ=mx}ymQN&1{kpsBe#C!spAXY5k;o}7xqdezm% z2G^;T61?QJw{FPL^>;ZB78(?ju_V{2@MvC5x+&nT+~_6YG`-n|SWwzb8L#OJ_ zXdn4c0oJHy{YF55+&q;u~Z6E#x!dlcPQ(%g&# z^aaNrO!e{2v^qZg@!O`Q!$cdP5TK+@Vi$YXEU|%^%%QY;0(fzMfq-l~Le; zGTJkI-}w_Ew7<#D{Kdg=$@(|&htGzWtw09S_2IubE@=PaSOZ>l_eR2yiQ7IAGjpm` z(`{eH8}m>Rbtqk?pvypyyvY>MGRD)pf>9JGU5@L8KG@xlze>6@3M2OrtWQV8cL*7t z8>&r@#Cn(DXPH@ZzCXT?S`O`p7N>4! zJq3hH6c*%#P-kC28#Oy3IA0P4fP!2R$7eufgXixD|S zR3wIiQ8`ez;E9TwbToEAs#zGe=iVWFJ2N0sNI7p9j-2*$#loBC9Q=BwW3@!S zmuz|{tdbf~%5~boFYwgAtY4u7kp?SBLmIxP;JMVbA+{VnilUN7v+jWZRy%l%O*}+q zkn?)&yNJRHBqglSSFvt=)+}c+1!fI-l`Mqe!^6 zNclENF@yY}CQxI$VLoaap#0xw=~HChiSWH@KoZPMSoIjuuP{7Uu(F*|a;;|wbGYPk z(B-rC>9GX~uSYeYw(9HmDS zlhtQ)(Dk@ASj%E^U9c~<;PnB6%3z;5pdsJVvi2NlgDE%}=9#qXz)CQ(nEs07N_U|8 zkjR6bn~4*TV%|HgPK5sA5Y)F@*Yd?CruRMpRVm6Ob_;1 z;>*KFDbe4HOEydEvaZ6WdwW6HbcKY{x!5Nr87_z;V^~80_5h?_GKY%a`*Q>*{R8o5 z71a$?&vrIYlmfz}vTx@GWspBYs9jQdBPZV!j_f}+`f!F&sehTeN=-Ov$l}em{MNfUGd7H1LXVr<6%Q3gfpuNXAnUI z9%V6eunjKoDvlWbhAtWGf8#|b^DC@UVE6A)z8%=A5nyhE|yE7ANvssDk z-`Jy2&;!%zI_r)i^FGnP@=o>{QX-G56Ddyb0pY<-7g%Bn(3Hp8!VC*F%cFct-29PZ z*A=MaDGl9VHiO&(+U@uXZAHinS6E> zWC}x)4T5dWJnCGlTI3vL5;_;(W1W!gQ-kvR;6{J=sX4e?aq-=*LEtiYN5;>cj`^7=Qe^kq!sdWnTE;Fm^rl3m#+HYqJBDRAly z-h@ZLIL^($hM~8D<7fDoLX=2^v-EV;9OR{tq!!)15Mfv~IzJ|;eIb>lqTUo{>GE?` zcw%MY;G*Nh+8Sm)JA7mDFl|Ff*&)zhyDl)Jc&bgK8tR~s^mVBng#=t~@Gp+KvVViK zFOutV{%hvDC-Oe!JNqP$>Q|UH{C>=bYte47|89e8-9EBxU)P^p=;-#<*6NF2 zP%^G;Ukyb*1IWCBoC0P%knh>}PO%~L-n*ixK`1ke8N)uy135SV0r)eDX)G8$LuOz5 z@4x-8pO@r3DbpBpKg3(h4gn*x@Od;9;Nx_N2-3s60{3gWz5wWKPwltN-P^Vs<*xM za~>05*waDN5+#yTBxIobvF4G(1>L;vCsE@It+@0twE<$Jm@~Cg` z&}{ymUmSn)(&3H;JF0-Hcv|+0qZ^dNoP=1MP^K{c;0yqRWuWn207@-|GM}J24r@Wb zJrcz{WzD`~diV*yECTR;7kEYDna1Yu9XW7;*ehT-oNS>{G>Zp-$yt9CEde@q^cTk; z;10)B*?&SQ3bx=X#@0;v6kr|*q2e&mWjNqWIPCzuv-y9X{{oQQ*=Di06;R|Gr~?jg z>0LiC8BCB`h!!w!r|dpOeNzLap+q2!;H4*|5+;2whQlN)!g9G~3;M>|0V|S&acBS|uFcI-fT%FK5?8ZY3;sgmN%)CT5 zruGKEg4V*iU8n5WXM*D{uYIG#tm~%^tf>3e73wSsyhdrJ=J? zw>nHgv+QPxnVET*4Dqp_aOwP57L5hlTcHn8jmWB*M<)LJLuWNB_xKlV8w7SujnK7E%WxUR zK1P1MT$AXaD)ZwWCcNi6d#0eapu^plJdI{}v%j2S^3g45cAcVH&uf7L=s9{p3DYQ% zyqUnX2s!Bi0bts1?9k(_N}6XGr$GbrQnnXRN>zSPs!(|@!;(&zK9_8)5!cCZNvF({ z6@tWR2`!;h6OY!{CrE|mpSPIySI+jT!NAzcMlebSZt5R5&zh$O6;rDprslj#`QAHx zzcpjS%ZmV|IzVE17$Hm-R;;0vUrmHo5Btn$tDfI1L^!hg2VJ&1gkIC`0#6DxP_-HL zI1VG=F1A=4+*8_6K}@<^E&ed_4JC!2ars>Gv~1TU;(eW*nU;2R&q? z%=>}={jaJFKAwEf`SNe&+^JY&MaQD`a`U7G;hFt(V!{Le;e_yj3{BiE5`MNpH~cC4 z$am&lT6{ZjL5H7a|9HH)?nl!zqPDHArjAc9?`7A21exP?{VVPf**dj}?m95*wuYff z_|2x~IQ57Pa#V6rrg>Ofu0pv5ch%(wj?IB8&aQ#3qnZ_I1_}rf-SL%MD_$2fZe-?% z#N|1?-!^%WB%!buXy5uw4_gzfz#RL47Xk+Q;`z@^Sj^@HD7mWDQNF7A%^#C&<0|-rD`2v4iCM~yG zC}-%coW>kty++NJ;p;ayWNAc4YRejwYCQ?zU51D;-Kj0MbfIuSfQkT(dCG`7{xsYy zIHZDZMRIOz9%h;y%R}S$n(z%nvkhYxi5gE`wX+!E*1r=2+P8C6>J&ONx2q0TC@} z{Pf|>#vH>8VM$M<84-SQRH4^Pd}(;{W;^_>zsFG;x-&%xVM)Y^jnfk;rt$8uE{_vb zmABC)@ELfXpjly!;e`>~L9-UTMQwv`UBblA?PSlp9cLnPO{??%L<^hbPc!>I&FD$r1!27DCW&ZfZii=(a9 zs?Rl&d9M?h5?)vjnU@!c5LeP4Py8*>kud8(?|KODlY&~{TeqZpzs#mL*Xy{p(Yr;< zraJ6jYq)83-WzzSuSJA~@z>rW=%;LOQRL z$JQ%??=e*=$wws*JFWI7Dsv*0Tmnbz6bxuRt0Swnov=b%D}G-~XH>2`hMArHO z;BxY*sv8i-1tyX{Ss0-d;W`KZ1dS^=V<@u*_*<-(NZolUk`;-Eu1UWF_~XqUCfX*^bhmDo?R|bTM~E)*Po@a5$#x( z*OYP`)>m`yG_`=(~sUqRqTM&3MIDcVCWr?-|2Q_F0c@VS2DvzK@}?jVG6AAOry*E zv-A4LL?1mZk?t<0PPqB=RXi%^vxe9`m+*s?udmpaD_luU(f?qjJY}NgSPctY`S90* z8`P9nDcWsNVpm_1SIkwl$dNy~&CTax(nPMGt!<*s^KeT zrUpfj&Y&7Xp0c&5GcPU;<(Ls$gNq|Juank|YlQ?4f2Mp`8FFzW1cxkE*OeA}2J4?} z;psb{;kAI_zMs115MANYcHky0go?8XT&*27h?45*zf4y$a8f$0F?V+aJvgYqS7@7C zNqkP0y);punv@-#nRVz`t-rmz`2-TxS+IYa4a?Zch5q8W0^jEWEoKlb-3}MN@`On< z)uIvECM~KAPB29!u&{vwR2vc0PR@dDeldbA3xF5n)GvsVBx@-CrO{)VXu!gwYeVaXt@3ofDF3W&2S9-YX z$B`X+`u%Ls+*rt9;pp zi)>&LQKf_)-dw{A|(D$h?lN}a3`V4~_ESDU{VEvk2Uygy62 zt7kQX=A0+G6F^qu&i~?BsH7<)j$NhZtt!+`m28@s1U}d{eV9j^0h2qqzxOk%0}f#P zUHlV|qpTbpC;ee}zAz2e1r_x}38)Xg6rJgc3jW={-eobPI6}&dSnIBd;%P5m%l57y zg}e0iganp;o_47n(x}}%LB8BNdUq_(zd9yayE!PkbUr)CvSGlxuKB<~y%2u!i-6lB z_Y~|KuMak57Ca_S&g8<_uvxOsST0)HrsJHS36Hb#aJUmdb-$0&th*r-ifnF+Ie4P3 zG&V+GjWz*L{;9$Bxw$|!`;Q|`XL8CprZ_#i3`ms3CQO4618OYY)#LNTpVQC{OWi;t ztG3wnC6os1k^1s%k!6#4LPfc8X_4G$c3ND^3aO~(2Yxp$lTU@*tL@xd=k|NZ+T}vI zUiA;eE>g1ftx0-;0&>eSsp(Fg3&N8c#XbRNc+&M|1Q znI>(CofF=joaEBv;%o2L4tIwK1d7$B1G4JFbusy|cT|HsiJKdNKV3HzAG{77?IABX zRuuKW+dA-^ZE|N7g~mVJwkNEUj`H#?l09K zsA!E#^LwohW6gcmYW;2&ah><4p`3|H`2jB?6nM_Oo9w(vfH`=A&YJw(NXkr?R`1m0 zJ$l-nF&NHc_6+Jbq!MpJzAh^fdI$bO)T)GWonAnh@bI6OO|fDXwTke*ta~|I6Jt!z zC^iXI%o8J&hER-3{52=iRs}KPw@arNU7acpLqM~uKk}<^?hVl*(ML9{{-)d52xz2M z9nleKT9e~@a9WRN*I%SNI6k{?#Z$-2tWTw4rQO^rB61=m&mu}CqH(LmY)Te23|bxa zSU9{VUH}23rmSyLq2$dC1FF{?Zx;xLcGTQMbY~(wc3PHhP)R>zFWJ#FGbr986&grOtyGiFX256`snwdNzvPSDIF+-~^w);SNATjl!oegn2I*QABS5LW|CqctZ$ zLak?a_Vtqe$GV3ngQS$YJulaOtV_QOYiE|;6KG^e&@Pr@Iv$_)jO zBTzxy8D#gN!Db9odXD!cTY~i-1x8)Slu`AgL1^sfbJ~a#q`|KLZQBQ!R6lrtGY5M z)jwoZwX(}yGDSJe~)=zY2=!w?vIKTGR2RBk0zcs3Qd)3avF5Yq1=qO8At4LcF!I6pc4CBoh* z(dgl#HUE&YK-W#IRX(qPxh2D#Sk%X#|1C=`Ey}e78#PW;>c1axBSyDZqF?GZL+e z0S`<44{wa+t5JozAYydNwjJG%66i7=8CpIi>p@+5QPe_I36iaS5SV#E8B9=-Z6x=3iY;qv3HCkXqhgbV}*#bth_|tB84>S64mojIphnL3mVUZ8_+RSH+iJ4z7c;BOJp)_vQE_FGS$lWY7 z*|wKbU(fCo8s>-&ljC;nnT~%c)QS;%XN#)Q7PZ(&BECNYHmaT8#T!0 z{DGo1Z{t5!;RlQWKa};0L($NbeE|)4slk3OHZLHJ6nN=Y|LTpPOTRdd{Djh{AmdIm zi-my90#clec&3pRppgpR@G>D#wmsy(Tp0TQzAi64E*IPbdKp4DL^0n($=XyBV44b; zT#bO13q^T)p|a!IT&iqG2<-WiroqcR z4QEzBDWV~~Oy(!lEcn3IKS0$oM}Tj$QB=vfUmVFptk`6>KZGuhKX5{Is#5^l473mJ z+n_i28j!5^#Nn5~2xbicNhems;1OevK&d*Yt&f0|^Xr6eZv%7?1D?7!hRv693N;9B z@hK{Wt!26=sCDojHTwWu#}lZ%fG@z(A}6SW0{$(7izzD!bVLQD`ExW9<`%Ar{pX4}8sg-$tWix%JC?Zk`ql?Ey(gk70 z1Oe}Ro?nE$=TC?HZT*Kzq=&$WtZP6R?qjy~)SsQlVg@e7s)#!0KI=R!V#;XEcELi8 z58?2&MUl*jn=u~oH}Kakz8upL5Pq_{)^yx6Chn-ylWXmt&Tk!!3MgXZvD>b33;ol) zb&dHGmRFZs&wb&^@Z-)*PDm}D;Y@mUm1{UT3fly_f<-R7MR=>K%x4d8S(t3GzZ};u zh3)6iLd5P9PB6L;tOwG*k@`qGEBgnLPlpDB$=d344Jl7W!u{S4FG!NNv$jJTH72mf zgt$&dir;x?0as~*SQmIaxwHJ}u_idpNzt{b+KJhB=k@%_x3AAAMg|ENp;6>9{=@vyAD70zMYak2rK(hLMI6Dhwi!;GL|N9dTsBDG zxHGeFoWm6s_m^lmS!h(W3}&fj^cjDQ?RkU0S|dIuu5$d&Veh6@o+cxYap-#M>dus| z#oA7-g1sW&gPs%xxv$-z3Hf&J|fDpo>q zI_Gblxy-Bn3H^secpOtprtIR6ALY^sT1J2Sp5H3MXI_bu+JvNgEqa4C;uZYC<6j&e zPxs;LL;Uwwk!r6l*&A0;CqBIA;HVC`b@Jp%a7gEUF3tp9tm)wq3cPNUnI0(1?sBze z>G-EB&tR9I99X?M%JB$ytY@#}>*MHWZY=$AB>wZaqsdUSZ>6maV+^w<=Vs8UH+GBW z-|Bx#r|N}qyWUl7c$(IF`g-uswT1)C9&?9Nb9&1HGTd0ZME~dQ%Q2xPcW&L$sg+eRLDKYhH;oIz=_mqQYn$_V zV`yq*@hepst=Cf*z9LF|r0rxyCQhrp3dz0|2AkPZSee02= zj>()K7oGd#INIPo2bWa-4)6=uVdKf4R(BKoqM|kR zCbc_Zx`&Y3&#lMD&OD2tEZdt&A%2{h(R}##wIJ*V_!5b_hW|%g;4(x^p;=93*U`2w zH;7|CX>ahxH?F4T#~GPL-eS(NDZLJ6%8jRZjyyRo+$)ZF#qs;e``3;{qTmbrNRDW0 zh9*dX;oSwh3*;G=Sf$PAW9YyU^34C=m*?;2QgL{r#yaT5um$I}tijI}yfZD zJ%3%0;~45qs`FrfXR5_HGxbkZAs;M4JOj}Oe&T|iXJFFBo~`9N_h!pdZyfz}@b%iK zPrvy!=Qexh%_fVQz1xbxJ(LvI_U3%nKI#46&q7IHC;tMedQ7^&S-w>vuPXE zVj-q>&Q#*rXW%?*aZqBRc4zKV<^9XYA_`IPF)WQ!6Rdm>Vx1U9Wkm%855S&8>q*~W z4t;JLVc#OBKRfadFAhk}0v#o8Z+3^@EWaR#lz8PdNtfousL4S)@s|m=?u2p7pIB{! ztSo-o+Z0DgP)$1}93{LuGqtp|>i1rH@)05pniC@yv%PI!f3!6kyzs*$vd8}sJA*Xi z`(FG;L5*sM_Urky8?CR@ZGUclm{NH6{)>;-d5$ArF3mb>=bAvcuGcFHc;t(niU*{? z8tFsXaFO|m-Y^CG+;q?5U0U*H#i@bSg(pUG{fJ3u9pHoV>_K|*RJxbIqshmYGQKYg zu}`qFLf76Z2Wgzhc6w9J%HD*vBwf}%ZyCs6r3IaOpm@Od0+)|6mJ}MeqGRYs>2}xm zIaj>m80q0$;Ok_pZRp%B44f>wZ>@e`p3>chts+yNfu>j`q-4HK{ArT?2V--?nX}Ic z_BAkA$qTGiMQF|bKMnGrfOv(Wh#K78U%~>+q7i|j*}&iNvLxWl(MG~Aj$B_D^IGsZ z=yE=ae!ma(uOM8Qm4RP6WNecF91=~TzsC-7v0q`?{yzZv_<9+j6=h%z!UxdDX_4Un zP)18Zsv7{Zfg?c(1eGG2UmOAghv$I+cNEB!U6Y^(-9SX%`q#fAa_}A#K=06LP4FKPV50|HYwAV4rUQ&LGxMrnWc!UqN*evUg^i6@9P< z`izrJnf9>dm|q;GFff83FsLPARHq;?7J3N~RQ$awuEC7QZ_4uhiZI z>K%N*#xZw+iqNEpt)RGb4Jh@?!Oa_lQQJvS3QxpqAlU}sj1cGgpOWq4J?of%m2)%s zco17gP+i8z7tozB)VAXsFFPduf5&%G<7!HeW?T(u2pM15AJ!NtR6|q=wQoN_-o{f)y&v0jzrFH z)^@wluWumw>>=C*+^{~x4?W&XMFqm6vw!|IUanDWqn>(Q_|{X!^;0pu$Lt8OgBXTg z>kWz|H`PL#TnXuhfi#4KJ0Iy2l0dM}P#C?6kUUo@d-iG4?3}idkTZGICl76sXMWX6 z;7-c4KF8;ek!XS2RrKDQ7}o1&huwm?loEl2a$9w>13Gj{`(fjhpO%QCcEK?ilTts~ z64=o4xQ21-AO#$ygipqau)q8(e(em77oGPIqxC#tpNp95VvCKTiI}>(>tWE-%Rr6~` z;7*tO`VNt{@4A1lDa~w^vZrG`F0>a*g8=qIfvHCSGIvV1&yr>Z-n|o=@s#5`i_lwb zqd+Ir=G(r1*p+*-umZDNo_EgDRzkb2otV1r99_Bjnek_S&oGp51KR-=j=(;xzF~=Q zw0Xk*(tq{)ZQH}HlY-G{vk&_Xx$BH&%11xh_DjX&OPi#2_9kN2?*fY+NskkQA>yV4 z=SP7FXzqyYzOyFHXsT5uAD`<9-H$2b$<8)m0tS)%O*$n(zFS=F){_C0nPmJd7GDQj zcc*X>W#~54zS)`Q2(Q`qn2OnkHsY@JN4m^DEPrpUH>%~J&JXd3rpZXJtE(Scx1ApH z$y|-Z2hPnL(N?{1$VZ3H;atVt4X;qX>U^!%KMV^PDL3MKe%OsNI%*ZG5-~&|wj0IU zlaxgqF))c287^DT<%scp3*zu>FW_YEaSpql@0{hW?dEaTRB|)zUs^*YACir`pmd zM$<`;`k>QyB)M5G#7tWBV^_uP4e3gnT%gXYNpVmqI<8#Og*{;+z%BAkN)Pn5)bz0?t4G zfIyKJjo_v`J0zBwV8lHXdClsJ@($?7Yo<0NRa|@-mM2KtcyZIw)S^!&Rqj$Fu@a-=a=djThK&1rn%j5!2)(FN z{79ZjiMg=(pulS%t5_HQhlmtDdHvJ6N&eION!}*SNvkIL3dxVniw|@P%G+~oo>Q1H zT)CcZUJ9@GBAU5FazBnY`zh4=%moB&USCxvrr#NEOBoEND`+tVyc}4`hDuB^`Zel1 zf)UL(vF=1rt2fQpYGl~?7E%9%7kXyKY5AI-Yi3TqDHde7|)S+t;7Re*XgN1<7ZCPZ-ZSsXQ*i%Dg3QB=3a@>;O7z7GVU<1 zz$7xay|bhVd5jya^aH#?iCa=t9LJG)EoC@{RL4qP3IrNgvA#5zkmcJ1|o)3juF zwDsASJ>3}rHiXgiA*!4Wudg0(3q=4{;Kr$s5S+y=GQ^ z-GID`dYj9V(WtU|M$M>_79mVCCz|z^%4>;t3fky%9o&Yi6@cpVum^F3=*4Rr>z z-S$%t@?DME;peuPcgk*4Lu*b--wVJz?|d*)bkY&BhLoaEe}?+Ilt3#yukcr9)fU=n z#cW+Z(0X+?k9&)flu!pw6Z1AHmX{le9m&&S=%?mx$T6zt%TGAtoY7@0?v;++drEP| z-|GKSR_@w%UHTl*evT(I{{E>2k%l+>9FM%t17=G-#QHQ%J3iLV$=3xvI%*MRY?{ZA z*3)bnQ6BIa-Sinl|Gd96A75KjpcMH7DH)&=5D5-fS1A{`ke=avKF;>q?+=bk{cU<% zQ8!G%HdkRdcVz@Et!@82*Y|`mTExW0uS_P*Qm?d9Gd(RSHQmLpG|ke0Xl+tpKRC?$ zteLg>0&%2x%Kw}?=}~Fa3E78PZxd!FKPZ-18+kr6ZIOq!MA;c_g@V2 z#LxxQE#{AsXBa1}0kl>Gu1;9gR&XnT*VRQ=e{q0hwZsra;4K{rFGJ|tFAm}|y%PV4 zcu1vhv$@ZJ{*90LjYsbvwIIJ12xW+sju~=|JC{4Hdp;UCZ~WoOA$~uw;nBc78j2R zfSggjO^+l$qQ`5#ESkQX52dNREc-EGQHpS;j;6;ukK9wKdpn^FoJ0~=Zj@hYSxjS@ z9$K)Jn8LHX!i_1hHTZX%M`_cZPd-sc{b-s&jXuK6u#<$itqSD1jJsaiY8su!x9&G) zJBj*79In_A2pQd)&r!e4!cWr+w8^=rJ!9wAiSk7*mAZ;Y;tscq%tJ%Q6EviSEYt|s z7%qQjIBS@wW(Vy*&QoQcTbu1k!GR94An+{k=lHX1m4F#>L?9Xa{bOYb&9rmyLwy|u zx)G4_T(mx$6z?+DYE&jaysnB-f1f$$)H-=8{kGO$9VYp(;kj4zhn>Oy$i|5KcKi^^ z0;eJR;1pz^lf^%1YtLTAm)XtTlT^NW7{{sHQJ2_BUc0@xGJDq}Y<@w+tk-s#x+LE{ zpMN>T5cb9=V>`(M62D=0wUc4$g0IW8bId8IEB8SEB-^SFXE-xHIueaGQ|OnVFLogD zGvIBR{Rhs0yWlaoJOeS^FrI_H$Z8I%5!dy>Qc`}SgK`{sBizjOwZt^0groY$r6G~q z&m>I7{-{c@Ap%wDD6T?%+n6|q7r@8fPvl9bB$hOx*V zF1Lu3sZ94bUg^i~Z!-C@6gXip4)qaKz7*dhj7Qcn>I30qp=73P>A0JI+?*S!)soEE zfc!aB@$RW~V?dsfO-n<=E2p{1;Kj(T))13n)os)eELJ-wfr+AGPZ6|TcB`CGqIX2P zRnJ;VTdVd}SM>3|>X|G5`XE-r;#6FbZAGEH2U@k}S^9!7zMjT;`O^Xu(#hBJ^(-2= zUwQg;K4;H+A`Fp0ANMf0@j?r^FhZPUC(2oJUaUDYs z$~}B^G_QJy%r>G7V?{?BA0}vrs~j$hKFctTqi{Qi%3cpG>lQ`bmF`o9X~1+wvRe3- zR6%1VZ_*BayzdXzN1R^!67pL?UCM?iBBVmOANCvS7@Zp)0~eTVpFUCT+Sm6zQ2Acy zV2zL5#Lz&lr~_uVVPa?THQt7*)rm+HnXB}k&5F<9*Vmv1cl_d5KY6muJPn;6!mqs+ zQKy1GR`FW+It*!Nm zXOUJu)Hi?0Te(%mYJfvh=ac2W>W@sK-T{i>{&Yq~0K6)Go#9ou8e@tJPCWc`BWf=( zF|b~<;CL(Q;rotD^@>*la)c|D$IjC#y07tl5!KUXd$Qh+_X~3NK+lB;Go`w}xd|ns z#dXnE7@9yMF>b}v|G}v3Gy5t&w8KER6}LDoDcc4WIai`k*JyVsRy4mCHIZHbRWJH! zs?=ltuJs;kS>A+XHa$@M^AVl{HJd$C7YvS~hMz=A18JKh@?t~}@a)C>6X1e>vP+{3 zz)Wa@&rb4wp!=+nIw2w&Q!?r!>ZC>@r8Uc>In6rt&$il^Dz{?O+G%1dcC$-@_ep~H zOCihQ1QAK7TDy<-Nujd~`0?=@kDTAIx3u-gGTdsORPJMga!WM@D$r*2`jRJ z79MmkzWWJSA8+fks8wS%x>pbOK$QmBQ~3mXNX{8d{pU8hW6yLFv9jhWTaUiWxiB;F zSpLqpR=sK+h-mu3c+xnl2f+!S1L5Zp;r10t2;J@Djv_3ClJw<^O^Gf>b$z$|W>Q6) z^@?gy68Yp3bSd`&0vFpZtVB1lRf>SGSK7Hv5x*BXuJ<2sQOwUpZUXx`t z!tQL?Yj8{qQBL3PNVWL=nUw6C@+N!I1pQ&blB5J}kSFA;Q2eUT8yx@F`#L{)IFkup zh0ig`edPp@aS{m48hLWJ<%zk4+^?5x7J>%yJ;GuLzB*Q(=_B1r2KodZ*a=6B{7{&b zd8>qOZUS08GFQy3vBhK2bbX4UAK|)po31uH_$>eTM27+b`%FEqi(TqnU^7}5=w;h7 zw!7hf@a%R=9dRQ+?%umIIM=>D4i4v=)Z#QS9Li2r9Gi^Xos;|@U(<_h1 zK~6luTJr$+`unx_MILdUZp+DiKMRg4HWbV=Czib^7d0=vh;bRtGxPr2J2L$Ag7%G~ zqaIfapC_>nGj6Rqlsgu$!7v8*N&uTQlA4foapA7X6?4T-Bm~(G0y1oaz7y)Z0}fN@rtI0NVSw%9M4_!)#(&&zcjbDb<&OM^2CSSdQw2& zaE`vzlWRGv${7u*5{Fux>S*Q3_j%FY+o)eOEB`x1G$5Z~+>TsTmM@NRjO@h(?6`%!Q`u9m$-hWo6 z6}sY(D4@&!}-4s zcx&Nz0(dot&*;YwoMxUL@Rah5_ZNruRcO@1EW(;fg(|VFCg(3y+=}txVf3#$}c2*Y+-zSxf-F2w;17lRFM~9H3h7=UusW*Pp8;E0TljBKic8g=Wz@9uVQ(}y zqEECes$0z^j(lu?9(aq1>SnlU#}7`N z9v2h!im;syUu59Y<Cy9lw#vTwl0Q00fWR-Sjt$8Je?j&v}aCpjJ)0{;jFKU=wjhi`1*02Z+S)2uf=t!Nl9He)uUFMPSd9--Y?>%;I9g} z$wxZPBHC#_Y@IavJHsu6)w{+~mu8v!o#9Um-FRceJ|FPoPpN9BLeaGCAnJGPB$wKU z_I;#QzihjZf?nq1%JXJy{L(WT{{TL-h-NvqZJB0^Cd6RYVUERPeyNASSB4)A^_tVI z>?p_C`?RTZ@>7pUq>tF}&W;}sLWkC2SJ=|0OWIDX9Ouau9(lX#DO&CKX8!=$ZhzpF literal 0 HcmV?d00001 diff --git a/m5stack/fs/system/nesso-n1/a8.jpg b/m5stack/fs/system/nesso-n1/a8.jpg new file mode 100644 index 0000000000000000000000000000000000000000..47313bdf94927ca10e215dddcfa3c44b1f619f41 GIT binary patch literal 38962 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

|SvA%Am)NB2-yHF3;M2@gG`@<+ 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 literal 0 HcmV?d00001 diff --git a/m5stack/fs/system/nesso-n1/ng.jpg b/m5stack/fs/system/nesso-n1/ng.jpg new file mode 100644 index 0000000000000000000000000000000000000000..105a2482e5491ac27fe47da36f822f3a355746a0 GIT binary patch literal 2632 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 literal 0 HcmV?d00001 diff --git a/m5stack/fs/system/nesso-n1/noCHG.jpg b/m5stack/fs/system/nesso-n1/noCHG.jpg new file mode 100644 index 0000000000000000000000000000000000000000..eb5884ae8d0e82f03792a845e91e073ff3b6494f GIT binary patch literal 758 zcmex=PfpQEif~-P{hK_8)fr;!& zg(60c6BlwQJ8e8D8g%i4ig8j=6DOCLxP+vXs+zinrk07RnYo3fm9vYho4bdnS8zyZ zSa?KaRB}pcT6#uiR&hybS$RceRdY*gTYE=m*QCi)rcRqaW9F9X@jO*zpr5PhGlvL#dMD`&oZ{je!Anu{UrJQ zHulTce|>b1oxVQ)^tt=?0kWk})}Gz9N`KbpBj@fOxVPhv|EFIUudjdkw|@Epu9DXs z?^9|o%-wv-;m+T3GyA7=`Qs0NC}-T5c|9oh$XgZFSrsRL&j0=TKZDQ9H|+m!0st^^ B3QGV0 literal 0 HcmV?d00001 diff --git a/m5stack/fs/system/nesso-n1/placeholder.jpg b/m5stack/fs/system/nesso-n1/placeholder.jpg new file mode 100644 index 0000000000000000000000000000000000000000..e7aee7de26a9a9107ffc83d4d7e9062061286727 GIT binary patch literal 11530 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 literal 0 HcmV?d00001 diff --git a/m5stack/fs/system/nesso-n1/server_ok.jpg b/m5stack/fs/system/nesso-n1/server_ok.jpg new file mode 100644 index 0000000000000000000000000000000000000000..e06b2487c27eada629036dd8470cf2f0268143bc GIT binary patch literal 2766 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 W`ub5sLqkiTMKSo6SHORU|2F|6Y4myk literal 0 HcmV?d00001 diff --git a/m5stack/fs/system/nesso-n1/wifiNeverSet.jpg b/m5stack/fs/system/nesso-n1/wifiNeverSet.jpg new file mode 100644 index 0000000000000000000000000000000000000000..d36501c85cea6999bdf0b4eef4f95a41f78cf77f GIT binary patch literal 939 zcmex=PfpQEif~-P{hK_8)fr;!& zg(60c6BlwQJ8e8D8g%i4ig8j=6DOCLxP+vXs+zinrk07RnYo3fm9vYho4bdnS8zyZ zSa?KaRB}pcT6#uiR&hybS$RceRdY*gTYE=m*QCi)rcRqaW9F9X@jO*zpr5PhGlvL#%UIm|DkvsY~`=fQq%Wb##7S^uFFgw30t4eJ5vdqlfB_}pznq3E)H!Z9zS2*${&&6};?j66s z|JxUT?STA;n0lb0$K!A9f4uhoTi@S0w=(CceN6A%a@O4QN9%|0VOQ2}k#+hVa3OcH z_hr+&+`(7nZhKBlxsJ?Cu5^f@ca9}e%41b{=W$T@w##6 literal 0 HcmV?d00001 diff --git a/m5stack/fs/system/nesso-n1/wifiOKServerNG.jpg b/m5stack/fs/system/nesso-n1/wifiOKServerNG.jpg new file mode 100644 index 0000000000000000000000000000000000000000..bf3481daab7025913f35dbfb871f2a1b39e2c20e GIT binary patch literal 1165 zcmex=PfpQEif~-P{hK_8)fr;!& zg(60c6BlwQJ8e8D8g%i4ig8j=6DOCLxP+vXs+zinrk07RnYo3fm9vYho4bdnS8zyZ zSa?KaRB}pcT6#uiR&hybS$RceRdY*gTYE=m*QCi)rcRqaW9F9X@jO*zpr5PhGlvL#>=wXgqF{i>|B`SJeXKkMl&`-D!jPJJ#J$IagJ$WP^1!izMC zId3wh$i^-q7;u_N^0F~fsb7k8|U+bi|1Xg!CM$=^9k?O*zH{82t-FVcMS#z$#O_KhbKSg}^2hA5#}CDeEp0k$+;f?==UBmgp@a`5Mt}U2g{NPbr!PAF znM90=X2Mg+gqLAQ0@pkBvU3K^QJ0!`$*A$s^Oj0?-lzN>J4!eH?W$UwzNK3WX_~y%kCfcc`>02zVU#TYRg3 z`Sc6MeSZ=j@2uGRP^TbrS>ddN4Hb4O5BH=QwZ(THNhGM`mlxX; z)yJ)U&S;OAG5g^f^X6j#XACWB{1Tlie8LiEByE_yy16I0d37|uTPxSqJ5S?^=dAo~ zaV))io?fbq=M+2Hhu6ae+m3`T4VLX&=~a?>Zm#62DK9QA4|H3dwPfpQEif~-P{hK_8)fr;!& zg(60c6BlwQJ8e8D8g%i4ig8j=6DOCLxP+vXs+zinrk07RnYo3fm9vYho4bdnS8zyZ zSa?KaRB}pcT6#uiR&hybS$RceRdY*gTYE=m*QCi)rcRqaW9F9X@jO*zpr5PhGlvL#WfyW z9}Sn|`H*_(=KaW3=U2Vjykh4x%RKk_yj!;z=Qkv=_j#&JvNTHJTD99V|H*ynKl$f= z_*>WL9y-@2F4?-+Yg^)su%%pI6@&JOMulhuty=1->J>2OxrOh^vu;{8Yxh6=&#)x@ zl5xil<&OERAEFOG__)Kymh0!b?6_7N->A|;lgCkg(!6e4(jB8(US3%lwA8A1SC?Vn z)4cRIw|{$9tUh*6Yx{@vo_AuOetnFp)edcR-LlC$$L8nSb&2gM>;@{6+*JyXH%o?v z-Qk+YQE`9n$F#B?(Ot1JVaB3vYjgG+EcO!7@>|MbwKXGrsi)gyzbWY!ege(i%{^0g zPIkX|{-xxRnEWH&O+PL^{5a!In%v%+)us!b=1Hyf+L2YXHk!9{v75ny_fDc(!7Kgd zn$0zr)GStK{x<3F?3&QW`Jyou=MPSMU0GYPnr-z`zeBeoRkz8xn44Yp-NZaY$l^(# zr}Dubqs@7W{?dO0&i&YIT@!rhRG+qF*J7`}#Oc>obBU@4wTNnkz6f3Q*mJp;LCZtS zgGUc~>BXE?eku8-_>rFZBlTl*#5VjmlV{BD)RsLpe7!$!*wPqNi;Igp4~MPQ2q<63 zRq`r$WzcNDSyN(m&T9N^_V4UItq=ZPYyYG_wl3YgZ*y3)*QEuC#X3cI`jxV0&s=z) z;Rru4#AGI=oQRxR)^NN@jw8KBeeOEVM^oLodk$S(?XG|N#nslT2`l6Hrbc~zb*X5{ zq@|jN1X`XwdG=IYE@bAH;$M~@#aRl49{ZU@)X15$x372BI<+EvdFN`sb!u}~Ts!h8 bJ8;Si?SQ#nB`+_p4BQ@g%Fkw{|Nol+lzriT literal 0 HcmV?d00001 diff --git a/m5stack/fs/system/nesso-n1/wifi_ok.jpg b/m5stack/fs/system/nesso-n1/wifi_ok.jpg new file mode 100644 index 0000000000000000000000000000000000000000..6e24bcc3158f79e7c1433bf6e1fb5cb22c1fdd90 GIT binary patch literal 2835 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 literal 0 HcmV?d00001 diff --git a/m5stack/libs/hardware/ir.py b/m5stack/libs/hardware/ir.py index a8e74a9b..947cfe74 100644 --- a/m5stack/libs/hardware/ir.py +++ b/m5stack/libs/hardware/ir.py @@ -24,6 +24,7 @@ def __init__(self) -> None: M5.BOARD.M5AtomEcho: (None, 12), M5.BOARD.M5AtomEchoS3R: (None, 47), M5.BOARD.M5NanoC6: (None, 3), + M5.BOARD.ArduinoNessoN1: (None, 9), } (self._rx_pin, self._tx_pin) = _pin_map.get(M5.getBoard()) if self._tx_pin: diff --git a/m5stack/machine_i2c.c b/m5stack/machine_i2c.c index c8fc1014..3979a8b3 100644 --- a/m5stack/machine_i2c.c +++ b/m5stack/machine_i2c.c @@ -4,6 +4,7 @@ * The MIT License (MIT) * * Copyright (c) 2019 Damien P. George + * Copyright (c) 2025 Vincent1-python * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -28,193 +29,229 @@ #include "py/mphal.h" #include "py/mperrno.h" #include "extmod/modmachine.h" +#include "machine_i2c.h" -#include "driver/i2c.h" -#include "hal/i2c_ll.h" - -#if MICROPY_PY_MACHINE_I2C || MICROPY_PY_MACHINE_SOFTI2C +#define MICROPY_HW_ESP_NEW_I2C_DRIVER (1) -#ifndef MICROPY_HW_I2C0_SCL -#define MICROPY_HW_I2C0_SCL (GPIO_NUM_18) -#define MICROPY_HW_I2C0_SDA (GPIO_NUM_19) -#endif - -#ifndef MICROPY_HW_I2C1_SCL -#if CONFIG_IDF_TARGET_ESP32 -#define MICROPY_HW_I2C1_SCL (GPIO_NUM_25) -#define MICROPY_HW_I2C1_SDA (GPIO_NUM_26) +#if MICROPY_HW_ESP_NEW_I2C_DRIVER +#include "driver/i2c_master.h" #else -#define MICROPY_HW_I2C1_SCL (GPIO_NUM_9) -#define MICROPY_HW_I2C1_SDA (GPIO_NUM_8) -#endif +#include "driver/i2c.h" +#include "hal/i2c_ll.h" #endif -#if SOC_I2C_SUPPORT_XTAL -#define I2C_SCLK_FREQ XTAL_CLK_FREQ -#elif SOC_I2C_SUPPORT_APB -#define I2C_SCLK_FREQ APB_CLK_FREQ -#else -#error "unsupported I2C for ESP32 SoC variant" -#endif +#if MICROPY_PY_MACHINE_I2C || MICROPY_PY_MACHINE_SOFTI2C #define I2C_DEFAULT_TIMEOUT_US (50000) // 50ms -// Start of modification section, by M5Stack -#define DEVICE_NUMBER 12 +// CONFIG_I2C_SKIP_LEGACY_CONFLICT_CHECK is set if the related sdkconfig +// option is set. -typedef struct _i2c_port_obj_t { - i2c_port_t port : 8; - gpio_num_t scl : 8; - gpio_num_t sda : 8; - uint32_t freq; -} i2c_port_obj_t; -// End of modification section, by M5Stack +#if MICROPY_HW_ESP_NEW_I2C_DRIVER typedef struct _machine_hw_i2c_obj_t { mp_obj_base_t base; - i2c_port_t port : 8; + i2c_master_bus_handle_t bus_handle; + #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 5, 0) + i2c_master_dev_handle_t dev_handle; + #endif + uint8_t port : 8; gpio_num_t scl : 8; gpio_num_t sda : 8; - // Start of modification section, by M5Stack - uint8_t pos; uint32_t freq; - // End of modification section, by M5Stack + uint32_t timeout_us; } machine_hw_i2c_obj_t; static machine_hw_i2c_obj_t machine_hw_i2c_obj[I2C_NUM_MAX]; -// Start of modification section, by M5Stack -static i2c_port_obj_t *i2c_device[DEVICE_NUMBER]; -static SemaphoreHandle_t i2c_mutex[I2C_NUM_MAX]; -static uint8_t i2c_port_used[2] = { DEVICE_NUMBER, DEVICE_NUMBER }; -// End of modification section, by M5Stack +static void machine_hw_i2c_init(machine_hw_i2c_obj_t *self, bool first_init) { + + #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 5, 0) + if (!first_init && self->dev_handle) { + i2c_master_bus_rm_device(self->dev_handle); + self->dev_handle = NULL; + } + #endif -void machine_i2c_deinit_all(void) { - if (i2c_port_used[0] != DEVICE_NUMBER) { - i2c_driver_delete(I2C_NUM_0); - i2c_port_used[0] = DEVICE_NUMBER; + if (!first_init && self->bus_handle) { + i2c_del_master_bus(self->bus_handle); + self->bus_handle = NULL; } - #if I2C_NUM_MAX > 1 - if (i2c_port_used[1] != DEVICE_NUMBER) { - i2c_driver_delete(I2C_NUM_1); - i2c_port_used[1] = DEVICE_NUMBER; + // Start of modification section, by M5Stack + i2c_master_bus_handle_t bus_handle; + if (i2c_master_get_bus_handle(self->port, &self->bus_handle) == ESP_ERR_INVALID_STATE) { + i2c_master_bus_config_t bus_cfg = { + .i2c_port = self->port, + .scl_io_num = self->scl, + .sda_io_num = self->sda, + .clk_source = I2C_CLK_SRC_DEFAULT, + .glitch_ignore_cnt = 7, + .flags.enable_internal_pullup = true, + }; + ESP_ERROR_CHECK(i2c_new_master_bus(&bus_cfg, &self->bus_handle)); } + // End of modification section, by M5Stack + #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 5, 0) + i2c_device_config_t dev_cfg = { + .dev_addr_length = I2C_ADDR_BIT_LEN_7, + .device_address = 0, // Will be replaced + .scl_speed_hz = self->freq, + }; + ESP_ERROR_CHECK(i2c_master_bus_add_device(self->bus_handle, &dev_cfg, &self->dev_handle)); #endif +} - for (uint8_t i = 0; i < DEVICE_NUMBER; i++) { - if (i2c_device[i] != NULL) { - free(i2c_device[i]); - i2c_device[i] = NULL; +static uint8_t *create_transfer_buffer(size_t n, mp_machine_i2c_buf_t *bufs, size_t *len_ptr) { + size_t len = 0; + uint8_t *buf; + if (n == 1) { + // Use given single buffer + len = bufs[0].len; + buf = bufs[0].buf; + } else { + // Allocate a buffer that can hold the data from all buffers + len = 0; + for (size_t i = 0; i < n; ++i) { + len += bufs[i].len; } + buf = m_new(uint8_t, len); } + *len_ptr = len; + return buf; } -static uint8_t malloc_bus(gpio_num_t scl, gpio_num_t sda, uint32_t freq, uint8_t port) { - if (i2c_mutex[0] == NULL) { - i2c_mutex[0] = xSemaphoreCreateMutex(); +int machine_hw_i2c_transfer(mp_obj_base_t *self_in, uint16_t addr, size_t n, mp_machine_i2c_buf_t *bufs, unsigned int flags) { + machine_hw_i2c_obj_t *self = MP_OBJ_TO_PTR(self_in); + + // Probe the address to see if any device responds. + // This test uses a fixed scl freq of 100_000. + esp_err_t err = i2c_master_probe(self->bus_handle, addr, self->timeout_us / 1000); + if (err != ESP_OK) { + return -MP_ENODEV; // No device at address, return immediately } - if (i2c_mutex[1] == NULL) { - i2c_mutex[1] = xSemaphoreCreateMutex(); + + #if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 5, 0) + // Using ".device_address = I2C_DEVICE_ADDRESS_NOT_USED," below + // allows to write the address separately using the + // i2c_master_execute_defined_operations() API. + i2c_device_config_t dev_cfg = { + .dev_addr_length = I2C_ADDR_BIT_LEN_7, + .device_address = addr, + .scl_speed_hz = self->freq, + }; + i2c_master_dev_handle_t dev_handle; + err = i2c_master_bus_add_device(self->bus_handle, &dev_cfg, &dev_handle); + #else + #define dev_handle self->dev_handle + err = i2c_master_device_change_address(dev_handle, addr, self->timeout_us / 1000); + #endif + if (err != ESP_OK) { + return -MP_ENODEV; } - uint8_t pos = 0; - for (pos = 0; pos < DEVICE_NUMBER; pos++) { - if (i2c_device[pos] == NULL) { - continue; + size_t len = 0; + uint8_t *buf; + + // Assume that with MP_MACHINE_I2C_FLAG_WRITE1 set the first + // buffer has to be written and all others have to be read. + // extmod/read_mem() uses only a single buffer for reading, but + // the other implementation(s) support multiple buffers. + if (flags & MP_MACHINE_I2C_FLAG_WRITE1) { + // create a large buffer if needed + buf = create_transfer_buffer(n - 1, bufs + 1, &len); + // Do a write then read + err = i2c_master_transmit_receive(dev_handle, bufs[0].buf, bufs[0].len, buf, len, self->timeout_us / 1000); + // Copy the data back if needed starting with the second buffer. + if (n > 2) { + len = 0; + for (size_t i = 1; i < n; ++i) { + memcpy(bufs[i].buf, buf + len, bufs[i].len); + len += bufs[i].len; + } + m_del(uint8_t, buf, len); } - if (i2c_device[pos]->scl == scl && i2c_device[pos]->sda == sda) { - if (i2c_device[pos]->freq == freq) { - // printf("found existing i2c bus, pos = %d, port = %d\n", pos, i2c_device[pos]->port); - return pos; - } else { - // printf("found existing i2c bus with different freq, pos = %d, port = %d\n", pos, i2c_device[pos]->port); - for (uint8_t i = 0; i < DEVICE_NUMBER; i++) { - if (i2c_device[i] != NULL) { - continue; - } - i2c_device[i] = (i2c_port_obj_t *)malloc(sizeof(i2c_port_obj_t)); - i2c_device[i]->sda = sda; - i2c_device[i]->scl = scl; - i2c_device[i]->freq = freq; - i2c_device[i]->port = i2c_device[pos]->port; - // printf("create new i2c bus, pos = %d, port = %d\n", i, i2c_device[i]->port); - return i; + len += bufs[0].len; + } else if (bufs->len > 0) { + buf = create_transfer_buffer(n, bufs, &len); + // Transfer data and copy it from/to the buffers as needed. + if (flags & MP_MACHINE_I2C_FLAG_READ) { + err = i2c_master_receive(dev_handle, buf, len, self->timeout_us / 1000); + if (n > 1) { + len = 0; + for (size_t i = 0; i < n; ++i) { + memcpy(bufs[i].buf, buf + len, bufs[i].len); + len += bufs[i].len; } } + } else { + if (n > 1) { + len = 0; + for (size_t i = 0; i < n; ++i) { + memcpy(buf + len, bufs[i].buf, bufs[i].len); + len += bufs[i].len; + } + } + err = i2c_master_transmit(dev_handle, buf, len, self->timeout_us / 1000); + // Use i2c_master_execute_defined_operations() instead of + // i2c_master_transmit(), allowing for len == 0. + // That will be needed for scan() when dropping i2c_master_probe() is possible, + // after https://github.com/espressif/esp-idf/issues/17543 backported to supported versions + // i2c_operation_job_t i2c_ops[] = { + // { .command = I2C_MASTER_CMD_START }, + // { .command = I2C_MASTER_CMD_WRITE, .write = { .ack_check = true, .data = buf, .total_bytes = len } }, + // { .command = I2C_MASTER_CMD_STOP }, // Stop is still mandatory + // }; + // err = i2c_master_execute_defined_operations(dev_handle, i2c_ops, 3, self->timeout_us / 1000); } - } - - for (pos = 0; pos < DEVICE_NUMBER; pos++) { - if (i2c_device[pos] == NULL) { - i2c_device[pos] = (i2c_port_obj_t *)malloc(sizeof(i2c_port_obj_t)); - i2c_device[pos]->sda = sda; - i2c_device[pos]->scl = scl; - i2c_device[pos]->freq = freq; - i2c_device[pos]->port = port; - // printf("create new i2c bus, pos = %d, port = %d\n", pos, i2c_device[pos]->port); - // if(pos == 0) { - // i2c_device[pos]->port = I2C_NUM_0; - // } else { - // i2c_device[pos]->port = I2C_NUM_1; - // } - return pos; + if (n > 1) { + m_del(uint8_t, buf, len); } } + #if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 5, 0) + // Remove the temporary handle. + i2c_master_bus_rm_device(dev_handle); + #endif - return DEVICE_NUMBER; -} - -void apply_bus(uint8_t pos) { - if (pos >= DEVICE_NUMBER) { - return; - } - if (i2c_device[pos] == NULL) { - mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("i2c bus apply failed")); + // Map errors + if (err == ESP_FAIL) { + return -MP_ENODEV; } - - i2c_port_obj_t *device = i2c_device[pos]; - - if (i2c_port_used[device->port] == pos) { - MP_THREAD_GIL_EXIT(); - xSemaphoreTake(i2c_mutex[device->port], portMAX_DELAY); - MP_THREAD_GIL_ENTER(); - return; + if (err == ESP_ERR_TIMEOUT) { + return -MP_ETIMEDOUT; } - - if (i2c_port_used[device->port] != DEVICE_NUMBER) { - i2c_driver_delete(device->port); + if (err != ESP_OK) { + return -abs(err); } + return len; +} - i2c_config_t conf = { - .mode = I2C_MODE_MASTER, - .sda_io_num = device->sda, - .sda_pullup_en = GPIO_PULLUP_ENABLE, - .scl_io_num = device->scl, - .scl_pullup_en = GPIO_PULLUP_ENABLE, - .master.clk_speed = device->freq, - }; - - i2c_param_config(device->port, &conf); - int timeout = I2C_SCLK_FREQ / 1000000 * I2C_DEFAULT_TIMEOUT_US; - i2c_set_timeout(device->port, (timeout > I2C_LL_MAX_TIMEOUT) ? I2C_LL_MAX_TIMEOUT : timeout); - i2c_driver_install(device->port, I2C_MODE_MASTER, 0, 0, 0); - - MP_THREAD_GIL_EXIT(); - xSemaphoreTake(i2c_mutex[device->port], portMAX_DELAY); - MP_THREAD_GIL_ENTER(); - - i2c_port_used[device->port] = pos; +#else - return; -} +#if SOC_I2C_SUPPORT_XTAL +#if CONFIG_XTAL_FREQ > 0 +#define I2C_SCLK_FREQ (CONFIG_XTAL_FREQ * 1000000) +#else +#error "I2C uses XTAL but no configured freq" +#endif // CONFIG_XTAL_FREQ +#elif SOC_I2C_SUPPORT_APB +#define I2C_SCLK_FREQ APB_CLK_FREQ +#else +#error "unsupported I2C for ESP32 SoC variant" +#endif -void free_bus(uint8_t pos) { - xSemaphoreGive(i2c_mutex[i2c_device[pos]->port]); -} +typedef struct _machine_hw_i2c_obj_t { + mp_obj_base_t base; + i2c_port_t port : 8; + gpio_num_t scl : 8; + gpio_num_t sda : 8; + uint32_t freq; + uint32_t timeout_us; +} machine_hw_i2c_obj_t; +static machine_hw_i2c_obj_t machine_hw_i2c_obj[I2C_NUM_MAX]; -static void machine_hw_i2c_init(machine_hw_i2c_obj_t *self, uint32_t freq, uint32_t timeout_us, bool first_init) { +static void machine_hw_i2c_init(machine_hw_i2c_obj_t *self, bool first_init) { if (!first_init) { i2c_driver_delete(self->port); } @@ -224,10 +261,10 @@ static void machine_hw_i2c_init(machine_hw_i2c_obj_t *self, uint32_t freq, uint3 .sda_pullup_en = GPIO_PULLUP_ENABLE, .scl_io_num = self->scl, .scl_pullup_en = GPIO_PULLUP_ENABLE, - .master.clk_speed = freq, + .master.clk_speed = self->freq, }; i2c_param_config(self->port, &conf); - int timeout = I2C_SCLK_FREQ / 1000000 * timeout_us; + int timeout = i2c_ll_calculate_timeout_us_to_reg_val(I2C_SCLK_FREQ, self->timeout_us); i2c_set_timeout(self->port, (timeout > I2C_LL_MAX_TIMEOUT) ? I2C_LL_MAX_TIMEOUT : timeout); i2c_driver_install(self->port, I2C_MODE_MASTER, 0, 0, 0); } @@ -265,18 +302,10 @@ int machine_hw_i2c_transfer(mp_obj_base_t *self_in, uint16_t addr, size_t n, mp_ i2c_master_stop(cmd); } - // Start of modification section, by M5Stack - apply_bus(self->pos); - // End of modification section, by M5Stack - // TODO proper timeout esp_err_t err = i2c_master_cmd_begin(self->port, cmd, 100 * (1 + data_len) / portTICK_PERIOD_MS); i2c_cmd_link_delete(cmd); - // Start of modification section, by M5Stack - xSemaphoreGive(i2c_mutex[i2c_device[self->pos]->port]); - // End of modification section, by M5Stack - if (err == ESP_FAIL) { return -MP_ENODEV; } else if (err == ESP_ERR_TIMEOUT) { @@ -288,15 +317,16 @@ int machine_hw_i2c_transfer(mp_obj_base_t *self_in, uint16_t addr, size_t n, mp_ return data_len; } + +#endif // MICROPY_HW_ESP_NEW_I2C_DRIVER + /******************************************************************************/ // MicroPython bindings for machine API static void machine_hw_i2c_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { machine_hw_i2c_obj_t *self = MP_OBJ_TO_PTR(self_in); - int h, l; - i2c_get_period(self->port, &h, &l); - mp_printf(print, "I2C(%u, scl=%u, sda=%u, freq=%u)", - self->port, self->scl, self->sda, I2C_SCLK_FREQ / (h + l)); + mp_printf(print, "I2C(%u, scl=%u, sda=%u, freq=%u, timeout=%u)", + self->port, self->scl, self->sda, self->freq, self->timeout_us); } mp_obj_t machine_hw_i2c_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { @@ -311,56 +341,43 @@ mp_obj_t machine_hw_i2c_make_new(const mp_obj_type_t *type, size_t n_args, size_ { MP_QSTR_id, MP_ARG_INT, {.u_int = I2C_NUM_0} }, { MP_QSTR_scl, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} }, { MP_QSTR_sda, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} }, - { MP_QSTR_freq, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 400000} }, - { MP_QSTR_timeout, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = I2C_DEFAULT_TIMEOUT_US} }, + { MP_QSTR_freq, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1} }, + { MP_QSTR_timeout, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1} }, }; mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; mp_arg_parse_all_kw_array(n_args, n_kw, all_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); - // Get I2C bus mp_int_t i2c_id = args[ARG_id].u_int; - - // Check if the I2C bus is valid if (!(I2C_NUM_0 <= i2c_id && i2c_id < I2C_NUM_MAX)) { mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("I2C(%d) doesn't exist"), i2c_id); } - // Get static peripheral object - machine_hw_i2c_obj_t *self = (machine_hw_i2c_obj_t *)&machine_hw_i2c_obj[i2c_id]; + machine_hw_i2c_obj_t *self = &machine_hw_i2c_obj[i2c_id]; - bool first_init = false; - if (self->base.type == NULL) { - // Created for the first time, set default pins + bool first_init = (self->base.type == NULL); + if (first_init) { self->base.type = &machine_i2c_type; self->port = i2c_id; - if (self->port == I2C_NUM_0) { - self->scl = MICROPY_HW_I2C0_SCL; - self->sda = MICROPY_HW_I2C0_SDA; - } else { - self->scl = MICROPY_HW_I2C1_SCL; - self->sda = MICROPY_HW_I2C1_SDA; - } - first_init = true; + self->scl = (i2c_id == I2C_NUM_0) ? MICROPY_HW_I2C0_SCL : MICROPY_HW_I2C1_SCL; + self->sda = (i2c_id == I2C_NUM_0) ? MICROPY_HW_I2C0_SDA : MICROPY_HW_I2C1_SDA; + self->freq = 400000; + self->timeout_us = I2C_DEFAULT_TIMEOUT_US; } - // Set SCL/SDA pins if given if (args[ARG_scl].u_obj != MP_OBJ_NULL) { self->scl = machine_pin_get_id(args[ARG_scl].u_obj); } if (args[ARG_sda].u_obj != MP_OBJ_NULL) { self->sda = machine_pin_get_id(args[ARG_sda].u_obj); } + if (args[ARG_freq].u_int != -1) { + self->freq = args[ARG_freq].u_int; + } + if (args[ARG_timeout].u_int != -1) { + self->timeout_us = args[ARG_timeout].u_int; + } - // Start of modification section, by M5Stack - self->pos = malloc_bus(self->scl, self->sda, args[ARG_freq].u_int, i2c_id); - self->port = i2c_device[self->pos]->port; - self->freq = args[ARG_freq].u_int; - // printf("i2c_id: %d, scl: %d, sda: %d, freq: %d\n", self->port , scl, sda, args[ARG_freq].u_int); - // End of modification section, by M5Stack - - // Initialise the I2C peripheral - machine_hw_i2c_init(self, args[ARG_freq].u_int, args[ARG_timeout].u_int, first_init); - + machine_hw_i2c_init(self, first_init); return MP_OBJ_FROM_PTR(self); } @@ -379,4 +396,4 @@ MP_DEFINE_CONST_OBJ_TYPE( locals_dict, &mp_machine_i2c_locals_dict ); -#endif +#endif // MICROPY_PY_MACHINE_I2C || MICROPY_PY_MACHINE_SOFTI2C diff --git a/m5stack/machine_i2c.h b/m5stack/machine_i2c.h new file mode 100644 index 00000000..299baa6d --- /dev/null +++ b/m5stack/machine_i2c.h @@ -0,0 +1,52 @@ + +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2019-2025 Damien P. George + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#ifndef MICROPY_INCLUDED_ESP32_MACHINE_I2C_H +#define MICROPY_INCLUDED_ESP32_MACHINE_I2C_H + +// Configure default I2C0 pins. +#ifndef MICROPY_HW_I2C0_SCL +#if CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S3 +#define MICROPY_HW_I2C0_SCL (GPIO_NUM_9) +#define MICROPY_HW_I2C0_SDA (GPIO_NUM_8) +#else +#define MICROPY_HW_I2C0_SCL (GPIO_NUM_18) +#define MICROPY_HW_I2C0_SDA (GPIO_NUM_19) +#endif +#endif + +// Configure default I2C1 pins. +#ifndef MICROPY_HW_I2C1_SCL +#if CONFIG_IDF_TARGET_ESP32 +#define MICROPY_HW_I2C1_SCL (GPIO_NUM_25) +#define MICROPY_HW_I2C1_SDA (GPIO_NUM_26) +#else +#define MICROPY_HW_I2C1_SCL (GPIO_NUM_9) +#define MICROPY_HW_I2C1_SDA (GPIO_NUM_8) +#endif +#endif + +#endif // MICROPY_INCLUDED_ESP32_MACHINE_I2C_H diff --git a/m5stack/modules/startup/__init__.py b/m5stack/modules/startup/__init__.py index 1ef81d0d..1eacb3f9 100644 --- a/m5stack/modules/startup/__init__.py +++ b/m5stack/modules/startup/__init__.py @@ -268,6 +268,11 @@ def startup(boot_opt, timeout: int = 60) -> None: powerhub = PowerHub_Startup() powerhub.startup(ssid, pswd, timeout) + elif board_id == M5.BOARD.ArduinoNessoN1: + from .nesson1 import NessoN1_Startup + + nesson1 = NessoN1_Startup() + nesson1.startup(ssid, pswd, timeout) # Only connect to network, not show any menu elif boot_opt is BOOT_OPT_NETWORK: diff --git a/m5stack/modules/startup/manifest_nesson1.py b/m5stack/modules/startup/manifest_nesson1.py new file mode 100644 index 00000000..a1c512d1 --- /dev/null +++ b/m5stack/modules/startup/manifest_nesson1.py @@ -0,0 +1,13 @@ +# SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +package( + "startup", + ( + "__init__.py", + "nesson1.py", + ), + base_path="..", + opt=0, +) diff --git a/m5stack/modules/startup/nesson1.py b/m5stack/modules/startup/nesson1.py new file mode 100644 index 00000000..4293593c --- /dev/null +++ b/m5stack/modules/startup/nesson1.py @@ -0,0 +1,698 @@ +# SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +from . import Startup +import M5 +import network +import widgets +import os +import sys +import gc +import asyncio +import esp32 + +try: + import M5Things + + _HAS_SERVER = True +except ImportError: + _HAS_SERVER = False + +DEBUG = False + +CHARGE_ICON = "/system/nesso-n1/CHG.jpg" +NO_CHARGE_ICON = "/system/nesso-n1/noCHG.jpg" +CLOUD_ICON = "/system/nesso-n1/1a.jpg" +USB_ICON = "/system/nesso-n1/1b.jpg" +APPLIST_ICON = "/system/nesso-n1/1c.jpg" +LORACHAT_ICON = "/system/nesso-n1/1d.jpg" +SETUP_ICON = "/system/nesso-n1/1e.jpg" +USB_IMG = "/system/nesso-n1/usb.jpg" +APPLIST_IMG = "/system/nesso-n1/APPLIST.jpg" +CLOUD_IMG = "/system/nesso-n1/a11.jpg" +NG_IMG = "/system/nesso-n1/ng.jpg" +WIFI_OK_IMG = "/system/nesso-n1/wifi_ok.jpg" +SERVER_OK_IMG = "/system/nesso-n1/server_ok.jpg" +PLACEHOLDER_IMG = "/system/nesso-n1/placeholder.jpg" +STATE_WIFI_NO_SET_IMG = "/system/nesso-n1/wifiNeverSet.jpg" +STATE_WIFI_NG_IMG = "/system/nesso-n1/wifiNG.jpg" +STATE_WIFI_OK_IMG = "/system/nesso-n1/wifiOKServerNG.jpg" +STATE_SERVER_OK_IMG = "/system/nesso-n1/wifiOKServerOK.jpg" + + +class AppBase: + def __init__(self) -> None: + self._task = None + + def on_install(self): + pass + + def on_launch(self): + pass + + def on_view(self): + pass + + def on_ready(self): + self._task = asyncio.create_task(self.on_run()) + + async def on_run(self): + while True: + await asyncio.sleep_ms(500) + + def on_hide(self): + self._task.cancel() + + def on_exit(self): + pass + + def on_uninstall(self): + pass + + def install(self): + self.on_install() + + def start(self): + self.on_launch() + self.on_view() + self.on_ready() + + def pause(self): + self.on_hide() + + def resume(self): + self.on_ready() + + def stop(self): + self.on_hide() + self.on_exit() + + def uninstall(self): + self.on_uninstall() + + +class AppSelector: + def __init__(self, apps: list) -> None: + self._apps = apps + self._id = 0 + + def prev(self): + self._id = (self._id - 1) % len(self._apps) + return self._apps[self._id] + + def next(self): + self._id = (self._id + 1) % len(self._apps) + return self._apps[self._id] + + def current(self): + return self._apps[self._id] + + def select(self, app): + self._id = self._apps.index(app) + + def index(self, id): + self._id = id % len(self._apps) + return self._apps[self._id] + + +class UsbApp(AppBase): + def __init__(self) -> None: + super().__init__() + + def on_launch(self): + self._battery_label = widgets.Label( + str(None), + 135 - 14, + 6, + w=135, + h=20, + font_align=widgets.Label.RIGHT_ALIGNED, + fg_color=0x000000, + bg_color=0xFFFFFF, + font=M5.Lcd.FONTS.DejaVu12, + ) + + self._bg_img = widgets.Image(use_sprite=False) + self._bg_img.set_x(0) + self._bg_img.set_y(0) + self._bg_img.set_size(135, 240) + + def on_view(self): + self._bg_img.set_src(USB_IMG) + + async def on_run(self): + while True: + # battery + self._battery_label.set_text(str(M5.Power.getBatteryLevel())) + await asyncio.sleep_ms(1000) + + def on_exit(self): + del self._bg_img, self._battery_label + + async def _keycode_enter_event_handler(self, fw: "Framework") -> None: + DEBUG and print("_keycode_enter_event_handler") + self.stop() + fw._app_selector.select(fw._launcher) + fw._launcher.start() + + async def _keycode_back_event_handler(self, fw: "Framework"): + DEBUG and print("_keycode_back_event_handler") + self.stop() + fw._app_selector.select(fw._launcher) + fw._launcher.start() + + async def _keycode_dpad_down_event_handler(self, fw: "Framework") -> None: + DEBUG and print("_keycode_dpad_down_event_handler") + + +class RunApp(AppBase): + def __init__(self) -> None: + super().__init__() + + def on_ready(self): + M5.Lcd.clear() + execfile("main.py", {"__name__": "__main__"}) # noqa: F821 + raise KeyboardInterrupt + + +class ListApp(AppBase): + def __init__(self) -> None: + super().__init__() + + def on_launch(self): + self._battery_label = widgets.Label( + str(None), + 135 - 14, + 6, + w=135, + h=20, + font_align=widgets.Label.RIGHT_ALIGNED, + fg_color=0x000000, + bg_color=0xFFFFFF, + font=M5.Lcd.FONTS.DejaVu12, + ) + self._bg_img = widgets.Image(use_sprite=False) + self._bg_img.set_x(0) + self._bg_img.set_y(0) + self._bg_img.set_size(135, 240) + + self._labels = [] + self._label0 = None + self._label1 = None + self._label2 = None + self._lebals = [] + self._lebal0 = None + self._lebal1 = None + self._lebal2 = None + + self._files = [] + for file in os.listdir("apps"): + if file.endswith(".py"): + self._files.append(file) + self._files_number = len(self._files) + self._cursor_pos = 0 + self._file_pos = 0 + + def on_view(self): + self._bg_img.set_src(APPLIST_IMG) + if self._label0 is None: + self._label0 = widgets.Label( + "", + 25, + 108, + w=85, + h=22, + fg_color=0xFFFFFF, + bg_color=0x333333, + font=M5.Lcd.FONTS.DejaVu18, + ) + self._label0.set_long_mode(widgets.Label.LONG_DOT) + if self._label1 is None: + self._label1 = widgets.Label( + "", + 25, + 108 + 22 + 5, + w=85, + h=22, + fg_color=0x999999, + bg_color=0x000000, + font=M5.Lcd.FONTS.DejaVu18, + ) + self._label1.set_long_mode(widgets.Label.LONG_DOT) + if self._label2 is None: + self._label2 = widgets.Label( + "", + 25, + 108 + 22 + 5 + 22 + 5, + w=85, + h=22, + fg_color=0x4D4D4D, + bg_color=0x000000, + font=M5.Lcd.FONTS.DejaVu18, + ) + self._label2.set_long_mode(widgets.Label.LONG_DOT) + + if len(self._labels) != 3: + self._labels.clear() + self._labels.append(self._label0) + self._labels.append(self._label1) + self._labels.append(self._label2) + + if self._lebal0 is None: + self._lebal0 = widgets.Label( + "", + 25, + 108 - 22 - 5, + w=85, + h=22, + fg_color=0x999999, + bg_color=0x000000, + font=M5.Lcd.FONTS.DejaVu18, + ) + self._lebal0.set_long_mode(widgets.Label.LONG_DOT) + + if self._lebal1 is None: + self._lebal1 = widgets.Label( + "", + 25, + 108 - 22 - 5 - 22 - 5, + w=85, + h=22, + fg_color=0x4D4D4D, + bg_color=0x000000, + font=M5.Lcd.FONTS.DejaVu18, + ) + self._lebal1.set_long_mode(widgets.Label.LONG_DOT) + + if self._lebal2 is None: + self._lebal2 = widgets.Label( + "", + 25, + 108 - 22 - 5 - 22 - 5 - 22 - 5, + w=85, + h=22, + fg_color=0x333333, + bg_color=0x000000, + font=M5.Lcd.FONTS.DejaVu18, + ) + self._lebal2.set_long_mode(widgets.Label.LONG_DOT) + + if len(self._lebals) != 3: + self._lebals.clear() + self._lebals.append(self._lebal0) + self._lebals.append(self._lebal1) + self._lebals.append(self._lebal2) + + for label, file in zip(self._labels, self._files): + # print("file:", file) + file and label and label.set_text(file) + + async def on_run(self): + while True: + # battery + self._battery_label.set_text(str(M5.Power.getBatteryLevel())) + await asyncio.sleep_ms(1000) + + def on_exit(self): + del self._bg_img, self._battery_label, self._labels, self._files, self._lebals + + async def _keycode_enter_event_handler(self, fw: "Framework"): + DEBUG and print("_keycode_enter_event_handler") + M5.Lcd.clear() + execfile("/".join(["apps/", self._files[self._file_pos]]), {"__name__": "__main__"}) # noqa: F821 + raise KeyboardInterrupt + + async def _keycode_back_event_handler(self, fw: "Framework"): + DEBUG and print("_keycode_back_event_handler") + self.stop() + fw._app_selector.select(fw._launcher) + fw._launcher.start() + + async def _keycode_dpad_down_event_handler(self, fw: "Framework"): + DEBUG and print("_keycode_dpad_down_event_handler") + self._file_pos += 1 + + if self._file_pos >= len(self._files): + self._file_pos = 0 + + for label in self._labels: + label.set_text("") + + for label, file in zip(self._labels, self._files[self._file_pos :]): + file and label and label.set_text(file) + + for label in self._lebals: + label.set_text("") + + files = self._files[: self._file_pos] + files.reverse() + + for label, file in zip(self._lebals, files): + file and label and label.set_text(file) + + +class LoRaChatApp(AppBase): + def __init__(self) -> None: + super().__init__() + + def on_view(self): + M5.Lcd.clear(0x000000) + M5.Lcd.drawImage(PLACEHOLDER_IMG, 0, 0, 135, 240) + + async def _keycode_enter_event_handler(self, fw: "Framework"): + DEBUG and print("_keycode_enter_event_handler") + self.stop() + fw._app_selector.select(fw._launcher) + fw._launcher.start() + + async def _keycode_back_event_handler(self, fw: "Framework"): + DEBUG and print("_keycode_back_event_handler") + self.stop() + fw._app_selector.select(fw._launcher) + fw._launcher.start() + + async def _keycode_dpad_down_event_handler(self, fw: "Framework"): + DEBUG and print("_keycode_dpad_down_event_handler") + + +class SetupApp(AppBase): + def __init__(self) -> None: + super().__init__() + + def on_view(self): + M5.Lcd.clear(0x000000) + M5.Lcd.drawImage(PLACEHOLDER_IMG, 0, 0, 135, 240) + + async def _keycode_enter_event_handler(self, fw: "Framework"): + DEBUG and print("_keycode_enter_event_handler") + self.stop() + fw._app_selector.select(fw._launcher) + fw._launcher.start() + + async def _keycode_back_event_handler(self, fw: "Framework"): + DEBUG and print("_keycode_back_event_handler") + self.stop() + fw._app_selector.select(fw._launcher) + fw._launcher.start() + + async def _keycode_dpad_down_event_handler(self, fw: "Framework"): + DEBUG and print("_keycode_dpad_down_event_handler") + + +class CloudApp(AppBase): + def __init__(self, data) -> None: + self._wifi = data[0] + self._ssid = str(data[1]) if len(data[1]) else str(None) + self._user_id = None + self._server = None + self._wifi_status = False + self._cloud_status = False + + def _get_wifi_status(self) -> bool: + return self._wifi.connect_status() == network.STAT_GOT_IP + + def _get_cloud_status(self) -> bool: + if self._get_wifi_status() and _HAS_SERVER: + return M5Things.status() == 2 + else: + return False + + def on_launch(self): + self._bg_img = widgets.Image(use_sprite=False) + self._bg_img.set_x(0) + self._bg_img.set_y(0) + self._bg_img.set_size(135, 240) + self._bg_img.set_src(CLOUD_IMG) + + self._net_status_img = widgets.Image(use_sprite=False) + self._net_status_img.set_x(98) + self._net_status_img.set_y(2) + self._net_status_img.set_size(34, 24) + self._net_status_img.set_src(NG_IMG) + + self._server_status_img = widgets.Image(use_sprite=False) + self._server_status_img.set_x(98) + self._server_status_img.set_y(30) + self._server_status_img.set_size(34, 24) + self._server_status_img.set_src(NG_IMG) + + M5.Lcd.fillRect(3, 164, 129, 46, 0xF3F3F3) + + def on_view(self): + self._net_status_img.set_src(WIFI_OK_IMG if self._get_wifi_status() else NG_IMG) + self._server_status_img.set_src(SERVER_OK_IMG if self._get_cloud_status() else NG_IMG) + + async def on_run(self): + while True: + t = self._get_wifi_status() + if t is not self._wifi_status: + self._wifi_status = t + self._net_status_img.set_src(WIFI_OK_IMG if t else NG_IMG) + + t = self._get_cloud_status() + if t is not self._cloud_status: + self._cloud_status = t + self._server_status_img.set_src(SERVER_OK_IMG if t else NG_IMG) + + await asyncio.sleep_ms(1000) + + def on_exit(self): + del self._bg_img, self._net_status_img, self._server_status_img + + async def _keycode_enter_event_handler(self, fw: "Framework"): + DEBUG and print("_keycode_enter_event_handler") + self.stop() + fw._app_selector.select(fw._launcher) + fw._launcher.start() + + async def _keycode_back_event_handler(self, fw: "Framework"): + DEBUG and print("_keycode_back_event_handler") + self.stop() + fw._app_selector.select(fw._launcher) + fw._launcher.start() + + async def _keycode_dpad_down_event_handler(self, fw: "Framework"): + DEBUG and print("_keycode_dpad_down_event_handler") + + +def _charge_ico(icos): + try: + len(icos) + except TypeError: + cache = [] + for i in icos: + yield i + cache.append(i) + icos = cache + while icos: + yield from icos + + +class LauncherApp(AppBase): + def __init__(self, data=None) -> None: + self._cloud_app = data + self._icons = ( + CLOUD_ICON, + USB_ICON, + APPLIST_ICON, + LORACHAT_ICON, + SETUP_ICON, + ) + + def on_launch(self): + self._icon_selector = _charge_ico(self._icons) + self._img_src = next(self._icon_selector) + self._id = 0 + + def on_view(self): + self._bg_img = widgets.Image(use_sprite=False) + self._bg_img.set_x(0) + self._bg_img.set_y(0) + self._bg_img.set_size(135, 240) + self._bg_img.set_src(self._img_src) + + self._chg_img = widgets.Image(use_sprite=False) + self._chg_img.set_x(59) + self._chg_img.set_y(3) + self._chg_img.set_size(16, 22) + if M5.Power.isCharging(): + self._chg_img.set_src(CHARGE_ICON) + else: + self._chg_img.set_src(NO_CHARGE_ICON) + + self._battery_label = widgets.Label( + str(None), + 132, + 5, + w=47, + h=21, + font_align=widgets.Label.RIGHT_ALIGNED, + fg_color=0x000000, + bg_color=0xCCCCCC, + font=M5.Lcd.FONTS.DejaVu18, + ) + + self._version_label = widgets.Label( + str(esp32.firmware_info()[3]), + 67, + 152, + w=135, + h=22, + font_align=widgets.Label.CENTER_ALIGNED, + fg_color=0x000000, + bg_color=0x67C94D, + font=M5.Lcd.FONTS.DejaVu18, + ) + self._version_label.set_text(esp32.firmware_info()[3]) + + self.nvs = esp32.NVS("uiflow") + self._state_img = widgets.Image(use_sprite=False) + self._state_img.set_x(6) + self._state_img.set_y(6) + self._state_img.set_size(16, 16) + self._state_img.set_src(self._get_state_img()) + + def _get_state_img(self) -> str: + if self.nvs.get_str("ssid0") == "": + return STATE_WIFI_NO_SET_IMG + if not self._cloud_app._get_wifi_status(): + return STATE_WIFI_NG_IMG + if not self._cloud_app._get_cloud_status(): + return STATE_WIFI_OK_IMG + return STATE_SERVER_OK_IMG + + async def on_run(self): + last_battery = -1 + last_charging = False + last_state_img = "" + while True: + # connection status + if last_state_img != self._get_state_img(): + last_state_img = self._get_state_img() + self._state_img.set_src(last_state_img) + + # charging status + if last_charging != M5.Power.isCharging(): + last_charging = M5.Power.isCharging() + self._chg_img.set_src(CHARGE_ICON if last_charging else NO_CHARGE_ICON) + + # battery level + if last_battery != M5.Power.getBatteryLevel(): + last_battery = M5.Power.getBatteryLevel() + self._battery_label.set_text(str(last_battery) + "%") + + await asyncio.sleep_ms(200) + + def on_exit(self): + del self._bg_img, self._icon_selector + + async def _keycode_enter_event_handler(self, fw: "Framework"): + DEBUG and print("_keycode_enter_event_handler") + app = fw._app_selector.index(self._id + 1) + app.start() + + async def _keycode_back_event_handler(self, fw: "Framework"): + DEBUG and print("_keycode_back_event_handler") + pass + + async def _keycode_dpad_down_event_handler(self, fw: "Framework"): + DEBUG and print("_keycode_dpad_down_event_handler") + self._id = self._id + 1 if self._id + 1 < len(self._icons) else 0 + self._img_src = next(self._icon_selector) + self._bg_img.set_src(self._img_src) + self._chg_img.set_src(CHARGE_ICON if M5.Power.isCharging() else NO_CHARGE_ICON) + self._battery_label.set_text(str(M5.Power.getBatteryLevel()) + "%") + self._version_label.set_text(esp32.firmware_info()[3]) + self._state_img.set_src(self._get_state_img()) + + +class Framework: + def __init__(self) -> None: + self._apps = [] + self._app_selector = AppSelector(self._apps) + self._launcher = None + + def install_launcher(self, launcher: AppBase): + self._launcher = launcher + + def install(self, app: AppBase): + app.install() + self._apps.append(app) + + def start(self): + # asyncio.create_task(self.gc_task()) + asyncio.run(self.run()) + + async def unload(self, app: AppBase): + # app = self._apps.pop() + app.stop() + + async def load(self, app: AppBase): + app.start() + + async def reload(self, app: AppBase): + app.stop() + app.start() + + async def run(self): + if self._launcher: + self._app_selector.select(self._launcher) + self._launcher.start() + + # asyncio.create_task(self.gc_task()) + while True: + M5.update() + if M5.BtnA.wasSingleClicked(): + M5.Speaker.tone(4000, 50) + app = self._app_selector.current() + if hasattr(app, "_keycode_enter_event_handler"): + await app._keycode_enter_event_handler(self) + if M5.BtnA.wasDoubleClicked(): + M5.Speaker.tone(3500, 50) + app = self._app_selector.current() + if hasattr(app, "_keycode_back_event_handler"): + await app._keycode_back_event_handler(self) + if M5.BtnB.wasSingleClicked(): + M5.Speaker.tone(6000, 50) + app = self._app_selector.current() + if hasattr(app, "_keycode_dpad_down_event_handler"): + await app._keycode_dpad_down_event_handler(self) + + await asyncio.sleep_ms(100) + + async def gc_task(self): + while True: + gc.collect() + DEBUG and print("heap RAM free:", gc.mem_free()) + DEBUG and print("heap RAM alloc:", gc.mem_alloc()) + await asyncio.sleep_ms(5000) + + +class NessoN1_Startup: + def __init__(self) -> None: + self._wifi = Startup() + + def startup(self, ssid: str, pswd: str, timeout: int = 60) -> None: + self._wifi.connect_network(ssid, pswd) + M5.Speaker.setVolume(255) + M5.Speaker.tone(4000, 50) + + DEBUG and print("Run startup menu") + + cloud_app = CloudApp((self._wifi, ssid)) + usb_app = UsbApp() + list_app = ListApp() + lorachat_app = LoRaChatApp() + setup_app = SetupApp() + launcher = LauncherApp(data=cloud_app) + + fw = Framework() + fw.install_launcher(launcher) + fw.install(launcher) + fw.install(cloud_app) + fw.install(usb_app) + fw.install(list_app) + fw.install(lorachat_app) + fw.install(setup_app) + fw.start() diff --git a/m5stack/patches/2009-fix-SoftI2C.patch b/m5stack/patches/2009-fix-SoftI2C.patch new file mode 100644 index 00000000..dd6e7093 --- /dev/null +++ b/m5stack/patches/2009-fix-SoftI2C.patch @@ -0,0 +1,427 @@ +Index: M5Unified/src/utility/IOExpander_Base.hpp +=================================================================== +--- M5Unified.orig/src/utility/IOExpander_Base.hpp ++++ M5Unified/src/utility/IOExpander_Base.hpp +@@ -12,22 +12,12 @@ + + namespace m5 + { +- +-#if CONFIG_IDF_TARGET_ESP32C6 +- class IOExpander_Base : public SoftI2C_Device +- { +- public: +- IOExpander_Base(std::uint8_t i2c_addr, std::uint32_t freq = 400000, m5::SoftI2C_Class* i2c = &m5::In_SoftI2C) +- : SoftI2C_Device(i2c_addr, freq, i2c) +- {} +-#else + class IOExpander_Base : public I2C_Device + { + public: + IOExpander_Base(std::uint8_t i2c_addr, std::uint32_t freq = 400000, m5::I2C_Class* i2c = &m5::In_I2C) + : I2C_Device(i2c_addr, freq, i2c) + {} +-#endif + IOExpander_Base(const IOExpander_Base&) = delete; + + // false input, true output +Index: M5Unified/src/utility/PI4IOE5V6408_Class.hpp +=================================================================== +--- M5Unified.orig/src/utility/PI4IOE5V6408_Class.hpp ++++ M5Unified/src/utility/PI4IOE5V6408_Class.hpp +@@ -26,15 +26,10 @@ namespace m5 + { + public: + static constexpr std::uint8_t DEFAULT_ADDRESS = 0x43; +-#if CONFIG_IDF_TARGET_ESP32C6 +- PI4IOE5V6408_Class(std::uint8_t i2c_addr = DEFAULT_ADDRESS, std::uint32_t freq = 400000, m5::SoftI2C_Class* i2c = &m5::In_SoftI2C) +- : IOExpander_Base(i2c_addr, freq, i2c) +- {} +-#else ++ + PI4IOE5V6408_Class(std::uint8_t i2c_addr = DEFAULT_ADDRESS, std::uint32_t freq = 400000, m5::I2C_Class* i2c = &m5::In_I2C) + : IOExpander_Base(i2c_addr, freq, i2c) + {} +-#endif + + bool begin(); + +Index: M5Unified/src/utility/SoftI2C_Class.hpp +=================================================================== +--- M5Unified.orig/src/utility/SoftI2C_Class.hpp ++++ M5Unified/src/utility/SoftI2C_Class.hpp +@@ -7,10 +7,11 @@ + #include + #include + #include ++#include "I2C_Class.hpp" + + namespace m5 + { +- class SoftI2C_Class ++ class SoftI2C_Class : public I2C_Class + { + public: + /// Constructor +@@ -22,55 +23,55 @@ namespace m5 + /// setup I2C pin parameters. (No begin) + /// @param pin_sda SDA pin number. + /// @param pin_scl SCL pin number. +- void setPins(int pin_sda, int pin_scl); ++ void setPins(int pin_sda, int pin_scl); + + /// setup and begin I2C peripheral. (No communication is performed.) + /// @param pin_sda SDA pin number. + /// @param pin_scl SCL pin number. + /// @return success(true) or failed(false). +- bool begin(int pin_sda, int pin_scl); ++ bool begin(i2c_port_t port_num, int pin_sda, int pin_scl) override; + + /// begin I2C peripheral. (No communication is performed.) + /// @return success(true) or failed(false). +- bool begin(void); ++ bool begin(void) override; + + /// release I2C peripheral. + /// @return success(true) or failed(false). +- bool release(void) const; ++ bool release(void) const override; + + /// Sends the I2C start condition and the address of the slave. + /// @param address slave addr. + /// @param read bit of read flag. true=read / false=write. + /// @return success(true) or failed(false). +- bool start(std::uint8_t address, bool read, std::uint32_t freq) const; ++ bool start(std::uint8_t address, bool read, std::uint32_t freq) const override; + + /// Sends the I2C repeated start condition and the address of the slave. + /// @param address slave addr. + /// @param read bit of read flag. true=read / false=write. + /// @return success(true) or failed(false). +- bool restart(std::uint8_t address, bool read, std::uint32_t freq) const; ++ bool restart(std::uint8_t address, bool read, std::uint32_t freq) const override; + + /// Sends the I2C stop condition. + /// If an ACK error occurs, return false. + /// @return success(true) or failed(false). +- bool stop(void) const; ++ bool stop(void) const override; + + /// Send 1 byte of data. + /// @param data write data. + /// @return success(true) or failed(false). +- bool write(std::uint8_t data) const; ++ bool write(std::uint8_t data) const override; + + /// Send multiple bytes of data. + /// @param[in] data write data array. + /// @param length data array length. + /// @return success(true) or failed(false). +- bool write(const std::uint8_t* data, std::size_t length) const; ++ bool write(const std::uint8_t* data, std::size_t length) const override; + + /// Receive multiple bytes of data. + /// @param[out] result read data array. + /// @param length data array length. + /// @return success(true) or failed(false). +- bool read(std::uint8_t* result, std::size_t length, bool last_nack = false) const; ++ bool read(std::uint8_t* result, std::size_t length, bool last_nack = false) const override; + + //---------- + +@@ -80,7 +81,7 @@ namespace m5 + /// @param[in] data write data array. + /// @param length data array length. + /// @return success(true) or failed(false). +- bool writeRegister(std::uint8_t address, std::uint8_t reg, const std::uint8_t* data, std::size_t length, std::uint32_t freq) const; ++ bool writeRegister(std::uint8_t address, std::uint8_t reg, const std::uint8_t* data, std::size_t length, std::uint32_t freq) const override; + + /// Read multiple bytes value from the register. Performs a series of communications from START to STOP. + /// @param address slave addr. +@@ -88,34 +89,34 @@ namespace m5 + /// @param[out] result read data array. + /// @param length data array length. + /// @return success(true) or failed(false). +- bool readRegister(std::uint8_t address, std::uint8_t reg, std::uint8_t* result, std::size_t length, std::uint32_t freq) const; ++ bool readRegister(std::uint8_t address, std::uint8_t reg, std::uint8_t* result, std::size_t length, std::uint32_t freq) const override; + + /// Write a 1-byte value to the register. Performs a series of communications from START to STOP. + /// @param address slave addr. + /// @param reg register number. + /// @param data write data. + /// @return success(true) or failed(false). +- bool writeRegister8(std::uint8_t address, std::uint8_t reg, std::uint8_t data, std::uint32_t freq) const; ++ bool writeRegister8(std::uint8_t address, std::uint8_t reg, std::uint8_t data, std::uint32_t freq) const override; + + /// Read a 1-byte value from the register. Performs a series of communications from START to STOP. + /// @param address slave addr. + /// @param reg register number. + /// @return read value. +- std::uint8_t readRegister8(std::uint8_t address, std::uint8_t reg, std::uint32_t freq) const; ++ std::uint8_t readRegister8(std::uint8_t address, std::uint8_t reg, std::uint32_t freq) const override; + + /// Write a 1-byte value to the register by bit add operation. Performs a series of communications from START to STOP. + /// @param address slave addr. + /// @param reg register number. + /// @param data add bit data. + /// @return success(true) or failed(false). +- bool bitOn(std::uint8_t address, std::uint8_t reg, std::uint8_t data, std::uint32_t freq) const; ++ bool bitOn(std::uint8_t address, std::uint8_t reg, std::uint8_t data, std::uint32_t freq) const override; + + /// Write a 1-byte value to the register by bit erase operation. Performs a series of communications from START to STOP. + /// @param address slave addr. + /// @param reg register number. + /// @param data erase bit data. + /// @return success(true) or failed(false). +- bool bitOff(std::uint8_t address, std::uint8_t reg, std::uint8_t data, std::uint32_t freq) const; ++ bool bitOff(std::uint8_t address, std::uint8_t reg, std::uint8_t data, std::uint32_t freq) const override; + + /// execute I2C scan. (for 7bit address) + /// @param[out] result data array needs 120 Bytes. +@@ -149,66 +150,6 @@ namespace m5 + + /// for internal I2C device + extern SoftI2C_Class In_SoftI2C; +- +- class SoftI2C_Device +- { +- public: +- SoftI2C_Device(std::uint8_t i2c_addr, std::uint32_t freq, SoftI2C_Class* i2c = &In_SoftI2C) +- : _i2c { i2c } +- , _freq { freq } +- , _addr { i2c_addr } +- , _init { false } +- {} +- +- void setPort(SoftI2C_Class* i2c) { _i2c = i2c; } +- +- void setClock(std::uint32_t freq) { _freq = freq; } +- +- void setAddress(std::uint8_t i2c_addr) { _addr = i2c_addr; } +- +- std::uint8_t getAddress(void) const { return _addr; } +- +- bool writeRegister8(std::uint8_t reg, std::uint8_t data) const +- { +- return _i2c->writeRegister8(_addr, reg, data, _freq); +- } +- +- std::uint8_t readRegister8(std::uint8_t reg) const +- { +- return _i2c->readRegister8(_addr, reg, _freq); +- } +- +- bool writeRegister8Array(const std::uint8_t* reg_data_array, std::size_t length) const; +- +- bool writeRegister(std::uint8_t reg, const std::uint8_t* data, std::size_t length) const +- { +- return _i2c->writeRegister(_addr, reg, data, length, _freq); +- } +- +- bool readRegister(std::uint8_t reg, std::uint8_t* result, std::size_t length) const +- { +- return _i2c->readRegister(_addr, reg, result, length, _freq); +- } +- +- bool bitOn(std::uint8_t reg, std::uint8_t bit) const +- { +- return _i2c->bitOn(_addr, reg, bit, _freq); +- } +- +- bool bitOff(std::uint8_t reg, std::uint8_t bit) const +- { +- return _i2c->bitOff(_addr, reg, bit, _freq); +- } +- +- bool isEnabled(void) const { return _init; } +- +- protected: +- SoftI2C_Class *_i2c; +- std::uint32_t _freq; +- std::uint8_t _addr; +- bool _init; +- }; +- + } + + #endif +Index: M5Unified/src/M5Unified.cpp +=================================================================== +--- M5Unified.orig/src/M5Unified.cpp ++++ M5Unified/src/M5Unified.cpp +@@ -1383,8 +1383,8 @@ static constexpr const uint8_t _pin_tabl + #elif defined (CONFIG_IDF_TARGET_ESP32C6) + case board_t::board_M5UnitC6L: + { +- In_SoftI2C.begin(10, 8); +- auto ioexp = new PI4IOE5V6408_Class(0x43); ++ In_SoftI2C.begin(I2C_NUM_0, 10, 8); ++ auto ioexp = new PI4IOE5V6408_Class(0x43, 400000, &In_SoftI2C); + ioexp->begin(); + _io_expander[0].reset(ioexp); + // user button(P0) input pullup +Index: M5Unified/src/utility/SoftI2C_Class.cpp +=================================================================== +--- M5Unified.orig/src/utility/SoftI2C_Class.cpp ++++ M5Unified/src/utility/SoftI2C_Class.cpp +@@ -30,7 +30,7 @@ namespace m5 + _pin_scl = pin_scl; + } + +- bool SoftI2C_Class::begin(int pin_sda, int pin_scl) ++ bool SoftI2C_Class::begin(i2c_port_t port_num,int pin_sda, int pin_scl) + { + setPins(pin_sda, pin_scl); + return begin(); +@@ -450,30 +450,4 @@ namespace m5 + return ack; + } + +- bool SoftI2C_Device::writeRegister8Array(const std::uint8_t* reg_data_array, std::size_t length) const +- { +- if (!_i2c || !reg_data_array || length == 0) { +- return false; +- } +- +- if (!_i2c->start(_addr, false, _freq)) { +- return false; +- } +- +- for (std::size_t i = 0; i < length; i += 2) { +- if (i + 1 < length) { +- if (!_i2c->write(reg_data_array[i])) { +- _i2c->stop(); +- return false; +- } +- if (!_i2c->write(reg_data_array[i + 1])) { +- _i2c->stop(); +- return false; +- } +- } +- } +- +- return _i2c->stop(); +- } +- + } // namespace m5 +\ No newline at end of file +Index: M5Unified/src/utility/I2C_Class.hpp +=================================================================== +--- M5Unified.orig/src/utility/I2C_Class.hpp ++++ M5Unified/src/utility/I2C_Class.hpp +@@ -23,56 +23,56 @@ namespace m5 + /// @param port_num I2C number. (I2C_NUM_0 or I2C_NUM_1). + /// @param pin_sda SDA pin number. + /// @param pin_scl SCL pin number. +- void setPort(i2c_port_t port_num, int pin_sda, int pin_scl); ++ virtual void setPort(i2c_port_t port_num, int pin_sda, int pin_scl); + + /// setup and begin I2C peripheral. (No communication is performed.) + /// @param port_num I2C number. (I2C_NUM_0 or I2C_NUM_1). + /// @param pin_sda SDA pin number. + /// @param pin_scl SCL pin number. + /// @return success(true) or failed(false). +- bool begin(i2c_port_t port_num, int pin_sda, int pin_scl); ++ virtual bool begin(i2c_port_t port_num, int pin_sda, int pin_scl); + + /// begin I2C peripheral. (No communication is performed.) + /// @return success(true) or failed(false). +- bool begin(void); ++ virtual bool begin(void); + + /// release I2C peripheral. + /// @return success(true) or failed(false). +- bool release(void) const; ++ virtual bool release(void) const; + + /// Sends the I2C start condition and the address of the slave. + /// @param address slave addr. + /// @param read bit of read flag. true=read / false=write. + /// @return success(true) or failed(false). +- bool start(std::uint8_t address, bool read, std::uint32_t freq) const; ++ virtual bool start(std::uint8_t address, bool read, std::uint32_t freq) const; + + /// Sends the I2C repeated start condition and the address of the slave. + /// @param address slave addr. + /// @param read bit of read flag. true=read / false=write. + /// @return success(true) or failed(false). +- bool restart(std::uint8_t address, bool read, std::uint32_t freq) const; ++ virtual bool restart(std::uint8_t address, bool read, std::uint32_t freq) const; + + /// Sends the I2C stop condition. + /// If an ACK error occurs, return false. + /// @return success(true) or failed(false). +- bool stop(void) const; ++ virtual bool stop(void) const; + + /// Send 1 byte of data. + /// @param data write data. + /// @return success(true) or failed(false). +- bool write(std::uint8_t data) const; ++ virtual bool write(std::uint8_t data) const; + + /// Send multiple bytes of data. + /// @param[in] data write data array. + /// @param length data array length. + /// @return success(true) or failed(false). +- bool write(const std::uint8_t* data, std::size_t length) const; ++ virtual bool write(const std::uint8_t* data, std::size_t length) const; + + /// Receive multiple bytes of data. + /// @param[out] result read data array. + /// @param length data array length. + /// @return success(true) or failed(false). +- bool read(std::uint8_t* result, std::size_t length, bool last_nack = false) const; ++ virtual bool read(std::uint8_t* result, std::size_t length, bool last_nack = false) const; + + //---------- + +@@ -82,7 +82,7 @@ namespace m5 + /// @param[in] data write data array. + /// @param length data array length. + /// @return success(true) or failed(false). +- bool writeRegister(std::uint8_t address, std::uint8_t reg, const std::uint8_t* data, std::size_t length, std::uint32_t freq) const; ++ virtual bool writeRegister(std::uint8_t address, std::uint8_t reg, const std::uint8_t* data, std::size_t length, std::uint32_t freq) const; + + /// Read multiple bytes value from the register. Performs a series of communications from START to STOP. + /// @param address slave addr. +@@ -90,34 +90,34 @@ namespace m5 + /// @param[out] result read data array. + /// @param length data array length. + /// @return success(true) or failed(false). +- bool readRegister(std::uint8_t address, std::uint8_t reg, std::uint8_t* result, std::size_t length, std::uint32_t freq) const; ++ virtual bool readRegister(std::uint8_t address, std::uint8_t reg, std::uint8_t* result, std::size_t length, std::uint32_t freq) const; + + /// Write a 1-byte value to the register. Performs a series of communications from START to STOP. + /// @param address slave addr. + /// @param reg register number. + /// @param data write data. + /// @return success(true) or failed(false). +- bool writeRegister8(std::uint8_t address, std::uint8_t reg, std::uint8_t data, std::uint32_t freq) const; ++ virtual bool writeRegister8(std::uint8_t address, std::uint8_t reg, std::uint8_t data, std::uint32_t freq) const; + + /// Read a 1-byte value from the register. Performs a series of communications from START to STOP. + /// @param address slave addr. + /// @param reg register number. + /// @return read value. +- std::uint8_t readRegister8(std::uint8_t address, std::uint8_t reg, std::uint32_t freq) const; ++ virtual std::uint8_t readRegister8(std::uint8_t address, std::uint8_t reg, std::uint32_t freq) const; + + /// Write a 1-byte value to the register by bit add operation. Performs a series of communications from START to STOP. + /// @param address slave addr. + /// @param reg register number. + /// @param data add bit data. + /// @return success(true) or failed(false). +- bool bitOn(std::uint8_t address, std::uint8_t reg, std::uint8_t data, std::uint32_t freq) const; ++ virtual bool bitOn(std::uint8_t address, std::uint8_t reg, std::uint8_t data, std::uint32_t freq) const; + + /// Write a 1-byte value to the register by bit erase operation. Performs a series of communications from START to STOP. + /// @param address slave addr. + /// @param reg register number. + /// @param data erase bit data. + /// @return success(true) or failed(false). +- bool bitOff(std::uint8_t address, std::uint8_t reg, std::uint8_t data, std::uint32_t freq) const; ++ virtual bool bitOff(std::uint8_t address, std::uint8_t reg, std::uint8_t data, std::uint32_t freq) const; + + /// execute I2C scan. (for 7bit address) + /// @param[out] result data array needs 120 Bytes. From ca672d2d32f854095c96614751664ea6f48115e3 Mon Sep 17 00:00:00 2001 From: lbuque <1102390310@qq.com> Date: Tue, 11 Nov 2025 15:40:55 +0800 Subject: [PATCH 309/322] patches: Fix LoRa initialization failure issue. Signed-off-by: lbuque <1102390310@qq.com> --- m5stack/CMakeListsDefault.cmake | 2 +- m5stack/Makefile | 3 ++- m5stack/patches/2010-Support-Nesso-N1.patch | 29 +++++++++++++++++++++ 3 files changed, 32 insertions(+), 2 deletions(-) create mode 100644 m5stack/patches/2010-Support-Nesso-N1.patch diff --git a/m5stack/CMakeListsDefault.cmake b/m5stack/CMakeListsDefault.cmake index d6ab6202..94f683b2 100644 --- a/m5stack/CMakeListsDefault.cmake +++ b/m5stack/CMakeListsDefault.cmake @@ -154,7 +154,7 @@ if ( OR BOARD_TYPE STREQUAL "tough" OR BOARD_TYPE STREQUAL "stamplc" OR BOARD_TYPE STREQUAL "unit_c6l" - OR BOARD_TYPE STREQUAL "arduino-n1" + OR BOARD_TYPE STREQUAL "nesso-n1" ) LIST(APPEND MICROPY_SOURCE_PORT ${PROJECT_DIR}/machine_hw_spi.c) LIST(APPEND MICROPY_SOURCE_PORT ${PROJECT_DIR}/machine_sdcard.c) diff --git a/m5stack/Makefile b/m5stack/Makefile index 106e05fc..ef5281cf 100644 --- a/m5stack/Makefile +++ b/m5stack/Makefile @@ -332,7 +332,8 @@ M5UNIFIED_PATCH_SERIES = \ 2006-Support-LTR553.patch \ 2007-Support-UnitC6L.patch \ 2008-Only-use-old-rmt-driver.patch \ - 2009-fix-SoftI2C.patch + 2009-fix-SoftI2C.patch \ + 2010-Support-Nesso-N1.patch ADF_PATCH_SERIES = \ 3002-Modify-i2s_stream_idf5.patch diff --git a/m5stack/patches/2010-Support-Nesso-N1.patch b/m5stack/patches/2010-Support-Nesso-N1.patch new file mode 100644 index 00000000..c547c30d --- /dev/null +++ b/m5stack/patches/2010-Support-Nesso-N1.patch @@ -0,0 +1,29 @@ +Index: M5Unified/src/M5Unified.cpp +=================================================================== +--- M5Unified.orig/src/M5Unified.cpp ++++ M5Unified/src/M5Unified.cpp +@@ -1418,6 +1418,24 @@ static constexpr const uint8_t _pin_tabl + ioexp->begin(); + _io_expander[i].reset(ioexp); + } ++ // sx1262 reset(P7) ++ _io_expander[0]->setDirection(7, true); ++ _io_expander[0]->setPullMode(7, false); ++ _io_expander[0]->setHighImpedance(7, false); ++ _io_expander[0]->digitalWrite(7, false); ++ delay(10); ++ _io_expander[0]->digitalWrite(7, true); ++ delay(10); ++ // LAN EN ++ _io_expander[0]->setDirection(5, true); ++ _io_expander[0]->setPullMode(5, false); ++ _io_expander[0]->setHighImpedance(5, false); ++ _io_expander[0]->digitalWrite(5, true); ++ // SW EN ++ _io_expander[0]->setDirection(6, true); ++ _io_expander[0]->setPullMode(6, false); ++ _io_expander[0]->setHighImpedance(6, false); ++ _io_expander[0]->digitalWrite(6, true); + break; + #elif defined (CONFIG_IDF_TARGET_ESP32S3) + case board_t::board_M5StampPLC: From c11760929e571d33cf1d26155e7e3e483faf8c07 Mon Sep 17 00:00:00 2001 From: lbuque <1102390310@qq.com> Date: Wed, 12 Nov 2025 09:33:45 +0800 Subject: [PATCH 310/322] modules/startup/nesson1.py: Close Launcher App. Signed-off-by: lbuque <1102390310@qq.com> --- m5stack/components/M5Unified/M5Unified | 2 +- m5stack/modules/startup/nesson1.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/m5stack/components/M5Unified/M5Unified b/m5stack/components/M5Unified/M5Unified index 2408c62b..45e7319e 160000 --- a/m5stack/components/M5Unified/M5Unified +++ b/m5stack/components/M5Unified/M5Unified @@ -1 +1 @@ -Subproject commit 2408c62b62730a5d934d554a27f56e1633571c61 +Subproject commit 45e7319ec158138843b1b61574686d1f6ad116d7 diff --git a/m5stack/modules/startup/nesson1.py b/m5stack/modules/startup/nesson1.py index 4293593c..f99141dc 100644 --- a/m5stack/modules/startup/nesson1.py +++ b/m5stack/modules/startup/nesson1.py @@ -589,6 +589,7 @@ def on_exit(self): async def _keycode_enter_event_handler(self, fw: "Framework"): DEBUG and print("_keycode_enter_event_handler") + self.stop() app = fw._app_selector.index(self._id + 1) app.start() From cb97f86e20eadf28d5a0a890bebd6580c6c6ffb9 Mon Sep 17 00:00:00 2001 From: lbuque <1102390310@qq.com> Date: Tue, 11 Nov 2025 18:04:07 +0800 Subject: [PATCH 311/322] tools/ci.sh: Add Nesso N1. Signed-off-by: lbuque <1102390310@qq.com> --- .github/workflows/build-release.yml | 1 + .github/workflows/nightly-build.yml | 1 + .github/workflows/ports_m5stack.yml | 1 + tools/ci.sh | 1 + 4 files changed, 4 insertions(+) diff --git a/.github/workflows/build-release.yml b/.github/workflows/build-release.yml index 1fbf18ab..48c7ae85 100644 --- a/.github/workflows/build-release.yml +++ b/.github/workflows/build-release.yml @@ -92,6 +92,7 @@ jobs: - M5STACK_Tab5 - M5STACK_Tough - M5STACK_Unit_C6L + - Nesso_N1 - ESPRESSIF_ESP32_S3_BOX_3 - SEEED_STUDIO_XIAO_ESP32S3 max-parallel: 4 diff --git a/.github/workflows/nightly-build.yml b/.github/workflows/nightly-build.yml index 4ac47575..78eaa50c 100644 --- a/.github/workflows/nightly-build.yml +++ b/.github/workflows/nightly-build.yml @@ -90,6 +90,7 @@ jobs: - M5STACK_Tab5 - M5STACK_Tough - M5STACK_Unit_C6L + - Nesso_N1 - ESPRESSIF_ESP32_S3_BOX_3 - SEEED_STUDIO_XIAO_ESP32S3 max-parallel: 4 diff --git a/.github/workflows/ports_m5stack.yml b/.github/workflows/ports_m5stack.yml index d676a7df..84823407 100644 --- a/.github/workflows/ports_m5stack.yml +++ b/.github/workflows/ports_m5stack.yml @@ -95,6 +95,7 @@ jobs: - M5STACK_Tab5 - M5STACK_Tough - M5STACK_Unit_C6L + - Nesso_N1 - ESPRESSIF_ESP32_S3_BOX_3 - SEEED_STUDIO_XIAO_ESP32S3 max-parallel: 4 diff --git a/tools/ci.sh b/tools/ci.sh index c7a58b27..bd5d6bd5 100644 --- a/tools/ci.sh +++ b/tools/ci.sh @@ -370,6 +370,7 @@ function ci_esp32_nightly_build { make ${MAKEOPTS} -C m5stack BOARD=M5STACK_Tab5 pack_all make ${MAKEOPTS} -C m5stack BOARD=M5STACK_Tough pack_all make ${MAKEOPTS} -C m5stack BOARD=M5STACK_Unit_C6L pack_all + make ${MAKEOPTS} -C m5stack BOARD=Nesso_N1 pack_all make ${MAKEOPTS} -C third-party BOARD=ESPRESSIF_ESP32_S3_BOX_3 pack_all make ${MAKEOPTS} -C third-party BOARD=SEEED_STUDIO_XIAO_ESP32S3 pack_all } From bee00b24485164d4f154269586e5d0246921a72a Mon Sep 17 00:00:00 2001 From: lbuque <1102390310@qq.com> Date: Wed, 12 Nov 2025 12:01:39 +0800 Subject: [PATCH 312/322] docs: Add Nesso N1. Signed-off-by: lbuque <1102390310@qq.com> --- docs/_static/controllers/nesso-n1/applist.png | Bin 0 -> 48025 bytes docs/_static/controllers/nesso-n1/cloud.png | Bin 0 -> 83088 bytes .../controllers/nesso-n1/indicator.png | Bin 0 -> 109042 bytes .../_static/controllers/nesso-n1/launcher.png | Bin 0 -> 77368 bytes docs/_static/controllers/nesso-n1/usb.png | Bin 0 -> 49079 bytes docs/en/controllers/index.rst | 2 + docs/en/controllers/nesso-n1.rst | 58 ++++ docs/en/hardware/lora.rst | 32 +- docs/en/refs/controllers.nesso_n1.ref | 9 + docs/en/refs/hardware.lora.ref | 18 ++ .../zh_CN/LC_MESSAGES/controllers/nesso-n1.po | 150 +++++++++ .../zh_CN/LC_MESSAGES/hardware/lora.po | 299 +++++++++--------- .../lora/nesso_n1_receiver_example.m5f2 | 1 + .../lora/nesso_n1_receiver_example.py | 67 ++++ .../lora/nesso_n1_sender_example.m5f2 | 1 + .../hardware/lora/nesso_n1_sender_example.py | 66 ++++ 16 files changed, 544 insertions(+), 159 deletions(-) create mode 100644 docs/_static/controllers/nesso-n1/applist.png create mode 100644 docs/_static/controllers/nesso-n1/cloud.png create mode 100644 docs/_static/controllers/nesso-n1/indicator.png create mode 100644 docs/_static/controllers/nesso-n1/launcher.png create mode 100644 docs/_static/controllers/nesso-n1/usb.png create mode 100644 docs/en/controllers/nesso-n1.rst create mode 100644 docs/en/refs/controllers.nesso_n1.ref create mode 100644 docs/locales/zh_CN/LC_MESSAGES/controllers/nesso-n1.po create mode 100644 examples/hardware/lora/nesso_n1_receiver_example.m5f2 create mode 100644 examples/hardware/lora/nesso_n1_receiver_example.py create mode 100644 examples/hardware/lora/nesso_n1_sender_example.m5f2 create mode 100644 examples/hardware/lora/nesso_n1_sender_example.py diff --git a/docs/_static/controllers/nesso-n1/applist.png b/docs/_static/controllers/nesso-n1/applist.png new file mode 100644 index 0000000000000000000000000000000000000000..b0be3eb77fe56fbd9a6104b550b0342362df080c GIT binary patch literal 48025 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 LvA*_?e?I&l8_}Ix literal 0 HcmV?d00001 diff --git a/docs/_static/controllers/nesso-n1/cloud.png b/docs/_static/controllers/nesso-n1/cloud.png new file mode 100644 index 0000000000000000000000000000000000000000..777a1145dc15665151b74ffe7616835efa00c573 GIT binary patch literal 83088 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 literal 0 HcmV?d00001 diff --git a/docs/_static/controllers/nesso-n1/launcher.png b/docs/_static/controllers/nesso-n1/launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..59281d7253d91c4e888e9f60d88011965ad2fd9b GIT binary patch literal 77368 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 Dmk`8@ literal 0 HcmV?d00001 diff --git a/docs/_static/controllers/nesso-n1/usb.png b/docs/_static/controllers/nesso-n1/usb.png new file mode 100644 index 0000000000000000000000000000000000000000..deca09e31ff11ec78104ee40776540388101627d GIT binary patch literal 49079 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

bQ}+hR)I+W=aOA1KC@jPYO`a3W`ls<5m&oeK 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 TyU?$3@b~WWh_{R1+LZAxbh-m$ literal 0 HcmV?d00001 diff --git a/docs/en/controllers/index.rst b/docs/en/controllers/index.rst index 2210af14..fcc9dca7 100644 --- a/docs/en/controllers/index.rst +++ b/docs/en/controllers/index.rst @@ -15,4 +15,6 @@ Controllers airq.rst paper.rst dinmeter.rst + dualkey.rst + nesso-n1.rst unit_c6l.rst diff --git a/docs/en/controllers/nesso-n1.rst b/docs/en/controllers/nesso-n1.rst new file mode 100644 index 00000000..2aa516f5 --- /dev/null +++ b/docs/en/controllers/nesso-n1.rst @@ -0,0 +1,58 @@ +######## +Nesso N1 +######## + +.. include:: ../refs/controllers.nesso_n1.ref + +********** +Startup UI +********** + +1. Power on the Nesso N1 by pressing the power button. +2. Wait for the device to boot up and display the main interface. +3. Press BtnB to switch apps. +4. Press BtnA to open the APP. +5. Double-click BtnA to exit the app. + + |indicator.png| + +Launcher +******** + +Launcher displays the device's network connection status, charging status, and remaining battery power. + +You can switch between different apps via BtnB. + +|launcher.png| + +Cloud +***** + +You can find more detailed network connection status. + +|cloud.png| + +USB +*** + +To check the device's Serial baud rate, it is not currently supported to reset the baud rate. + +|usb.png| + +APP.LIST +******** + +1. Select the Python application via BtnA. +2. Press BtnA to run the selected Python application. + +|applist.png| + +LoRaChat +******** + +Coming soon + +Setup +***** + +Coming soon diff --git a/docs/en/hardware/lora.rst b/docs/en/hardware/lora.rst index d3aeef00..dd865d88 100644 --- a/docs/en/hardware/lora.rst +++ b/docs/en/hardware/lora.rst @@ -14,10 +14,12 @@ LoRa is used to control the built-in long-range wireless communication module in +=================+=========+ | UnitC6L | |S| | +-----------------+---------+ + | Nesso N1 | |S| | + +-----------------+---------+ .. |S| unicode:: U+2714 -UiFlow2 Example +UiFlow2 Example --------------- Sender @@ -25,6 +27,8 @@ Sender Open the |unit_c6l_tx_example.m5f2| project in UiFlow2. +Open the |nesso_n1_sender_example.m5f2| project in UiFlow2. + This example sends data every second. UiFlow2 Code Block: @@ -40,6 +44,8 @@ Receiver Open the |unit_c6l_rx_example.m5f2| project in UiFlow2. +Open the |nesso_n1_receiver_example.m5f2| project in UiFlow2. + This example receives and displays data. UiFlow2 Code Block: @@ -88,7 +94,7 @@ Example output: class LoRa ^^^^^^^^^^ - + .. class:: hardware.LoRa(freq_khz = 868000, \ bw = "250", \ sf = 8, \ @@ -116,7 +122,7 @@ class LoRa :param int coding_rate: Forward Error Correction (FEC) coding rate expressed as 4/N, with a range from 5 to 8. :param int preamble_len: Length of the preamble sequence in symbols, range from 0 to 255. :param int syncword: Sync word to mark the start of the data frame, default is 0x12. - :param int output_power: Output power in dBm, range from -9 to 22. + :param int output_power: Output power in dBm, range from -9 to 22. UiFlow2 Code Block: @@ -228,7 +234,7 @@ class LoRa lora_0.set_preamble_len(preamble_len) - .. method:: set_output_power(output_power) + .. method:: set_output_power(output_power) Set output power in dBm. @@ -245,12 +251,12 @@ class LoRa lora_0.set_output_power(output_power) .. method:: set_irq_callback(callback) - + Set the interrupt callback function to be executed on IRQ. - + :param callback: The callback function to be invoked when the interrupt is triggered. The callback should not take any arguments and should return nothing. - + Call `start_recv()` to begin receiving data. UiFlow2 Code Block: @@ -264,7 +270,7 @@ class LoRa lora_0.set_irq_callback() .. method:: start_recv() - + Start receive data. This method initiates the process to begin receiving data. @@ -279,8 +285,8 @@ class LoRa lora_0.start_recv() - .. method:: recv(self, timeout_ms, rx_length, rx_packet) - + .. method:: recv(self, timeout_ms, rx_length, rx_packet) + Receive data. :param int timeout_ms: Timeout in milliseconds (optional). Default is None. @@ -301,14 +307,14 @@ class LoRa data = lora_0.recv() - .. method:: send(buf, tx_at_ms=None) + .. method:: send(buf, tx_at_ms=None) Send data. :param str | list | tuple | int | bytearray packet: The data to be sent. :param int tx_at_ms: The timestamp in milliseconds when to send the data (optional). Default is None. :returns: Returns a timestamp (result of `time.ticks_ms()`) indicating when the data packet was sent. - :rtype: int + :rtype: int Send a data packet and return the timestamp after the packet is sent. @@ -354,7 +360,7 @@ class LoRa lora_0.sleep() - .. method:: irq_triggered() + .. method:: irq_triggered() Check IRQ trigger. diff --git a/docs/en/refs/controllers.nesso_n1.ref b/docs/en/refs/controllers.nesso_n1.ref new file mode 100644 index 00000000..9a35e88b --- /dev/null +++ b/docs/en/refs/controllers.nesso_n1.ref @@ -0,0 +1,9 @@ +.. |indicator.png| image:: ../../_static/controllers/nesso-n1/indicator.png + +.. |applist.png| image:: ../../_static/controllers/nesso-n1/applist.png + +.. |cloud.png| image:: ../../_static/controllers/nesso-n1/cloud.png + +.. |usb.png| image:: ../../_static/controllers/nesso-n1/usb.png + +.. |launcher.png| image:: ../../_static/controllers/nesso-n1/launcher.png diff --git a/docs/en/refs/hardware.lora.ref b/docs/en/refs/hardware.lora.ref index c187a11c..e0facdf8 100644 --- a/docs/en/refs/hardware.lora.ref +++ b/docs/en/refs/hardware.lora.ref @@ -38,3 +38,21 @@ > unit_c6l_rx_example.m5f2 + +.. |nesso_n1_receiver_example.m5f2| raw:: html + + + nesso_n1_receiver_example.m5f2 + + +.. |nesso_n1_sender_example.m5f2| raw:: html + + + nesso_n1_sender_example.m5f2 + diff --git a/docs/locales/zh_CN/LC_MESSAGES/controllers/nesso-n1.po b/docs/locales/zh_CN/LC_MESSAGES/controllers/nesso-n1.po new file mode 100644 index 00000000..e721e3bc --- /dev/null +++ b/docs/locales/zh_CN/LC_MESSAGES/controllers/nesso-n1.po @@ -0,0 +1,150 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2016 - 2025 M5Stack Technology Co., Ltd +# This file is distributed under the same license as the UIFlow2 Programming +# Guide package. +# FIRST AUTHOR , 2025. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: UIFlow2 Programming Guide \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-11-12 11:13+0800\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: zh_CN\n" +"Language-Team: zh_CN \n" +"Plural-Forms: nplurals=1; plural=0;\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.17.0\n" + +#: ../../en/controllers/nesso-n1.rst:3 c295bae9d68b4d67a5c8e21ee7ce40de +msgid "Nesso N1" +msgstr "" + +#: ../../en/controllers/nesso-n1.rst:9 8bcf25f13728465eb4ac0ed552da1b11 +msgid "Startup UI" +msgstr "启动界面" + +#: ../../en/controllers/nesso-n1.rst:11 988128e71cdb4a93856b3063e0fa6628 +msgid "Power on the Nesso N1 by pressing the power button." +msgstr "按下电源键启动 Nesso N1。" + +#: ../../en/controllers/nesso-n1.rst:12 f110dac306774ac3948b50c90f6ea40f +msgid "Wait for the device to boot up and display the main interface." +msgstr "等待设备启动并显示主界面。" + +#: ../../en/controllers/nesso-n1.rst:13 332dca2702fb482a9baf9f111dc90fdf +msgid "Press BtnB to switch apps." +msgstr "按下 BtnB 切换应用。" + +#: ../../en/controllers/nesso-n1.rst:14 e234dbe7edf94ccbb13c232701dad206 +msgid "Press BtnA to open the APP." +msgstr "按下 BtnA 打开应用。" + +#: ../../en/controllers/nesso-n1.rst:15 66cdb055deff4c6b847c0d09bffe8eac +msgid "Double-click BtnA to exit the app." +msgstr "双击 BtnA 退出应用。" + +#: ../../en/controllers/nesso-n1.rst:17 928ee9d74bfd41b582fdffeba3337790 +msgid "|indicator.png|" +msgstr "" + +#: ../../en/refs/controllers.nesso_n1.ref:1 c89e7d27225240ea88b093b32b636d4c +msgid "indicator.png" +msgstr "" + +#: ../../en/controllers/nesso-n1.rst:20 36fb0fc0e7ac4823aa547baa1a65b447 +msgid "Launcher" +msgstr "启动器" + +#: ../../en/controllers/nesso-n1.rst:22 4defea54235842a2bddcebe61d080ec6 +msgid "" +"Launcher displays the device's network connection status, charging " +"status, and remaining battery power." +msgstr "启动器显示设备的网络连接状态、充电状态和剩余电量。" + +#: ../../en/controllers/nesso-n1.rst:24 9d29b0cd100a46e482f3fbb9d3266cdf +msgid "You can switch between different apps via BtnB." +msgstr "可通过 BtnB 在不同应用间切换。" + +#: ../../en/controllers/nesso-n1.rst:26 6c0fadfef8084d74ab1336c3e9fc9091 +msgid "|launcher.png|" +msgstr "" + +#: ../../en/refs/controllers.nesso_n1.ref:9 c89e7d27225240ea88b093b32b636d4c +msgid "launcher.png" +msgstr "" + +#: ../../en/controllers/nesso-n1.rst:29 fa9fa73e23464aeeb05a941a20c29677 +msgid "Cloud" +msgstr "云" + +#: ../../en/controllers/nesso-n1.rst:31 a1be80832b024760bc7e2d25aa850370 +msgid "You can find more detailed network connection status." +msgstr "可查看更详细的网络连接状态。" + +#: ../../en/controllers/nesso-n1.rst:33 57a99fc2aabf4223b790fcedf8fb0e6d +msgid "|cloud.png|" +msgstr "" + +#: ../../en/refs/controllers.nesso_n1.ref:5 c10b76b7b76b43cfbf0ffd5944184c99 +msgid "cloud.png" +msgstr "" + +#: ../../en/controllers/nesso-n1.rst:36 cd02717cec3a4366b63ea7f4fbd55e9d +msgid "USB" +msgstr "" + +#: ../../en/controllers/nesso-n1.rst:38 a7d502d885df4e7ab87645f8926b173e +msgid "" +"To check the device's Serial baud rate, it is not currently supported to " +"reset the baud rate." +msgstr "用于查看设备的串口波特率,当前不支持重置波特率。" + +#: ../../en/controllers/nesso-n1.rst:40 71d215bca18f4295a369af68d21cbe71 +msgid "|usb.png|" +msgstr "" + +#: ../../en/refs/controllers.nesso_n1.ref:7 c10b76b7b76b43cfbf0ffd5944184c99 +msgid "usb.png" +msgstr "" + +#: ../../en/controllers/nesso-n1.rst:43 026188b82d6242a986b370152b84d521 +msgid "APP.LIST" +msgstr "应用列表" + +#: ../../en/controllers/nesso-n1.rst:45 7d7e9fa7da79447d8b3c22abd909d0d4 +msgid "Select the Python application via BtnA." +msgstr "通过 BtnA 选择 Python 应用。" + +#: ../../en/controllers/nesso-n1.rst:46 053c3d714ea64bfeb1959637eab88dea +msgid "Press BtnA to run the selected Python application." +msgstr "按下 BtnA 运行所选的 Python 应用。" + +#: ../../en/controllers/nesso-n1.rst:48 e5394559958345929c16036012e571a2 +msgid "|applist.png|" +msgstr "" + +#: ../../en/refs/controllers.nesso_n1.ref:3 ac168309118a4860942cc538f8d60fc2 +msgid "applist.png" +msgstr "" + +#: ../../en/controllers/nesso-n1.rst:51 4ba4026153c44404b8535b03387f8eb0 +msgid "LoRaChat" +msgstr "" + +#: ../../en/controllers/nesso-n1.rst:53 ../../en/controllers/nesso-n1.rst:58 +#: 12c68d7bec074c9e8da33bf476e3cf8a d6fe2066bb4b4f5abccfd247eef328c4 +msgid "Coming soon" +msgstr "" + +#: ../../en/controllers/nesso-n1.rst:56 8bcf25f13728465eb4ac0ed552da1b11 +msgid "Setup" +msgstr "" + +#~ msgid "DinMeter" +#~ msgstr "" + diff --git a/docs/locales/zh_CN/LC_MESSAGES/hardware/lora.po b/docs/locales/zh_CN/LC_MESSAGES/hardware/lora.po index 66086469..54ff8739 100644 --- a/docs/locales/zh_CN/LC_MESSAGES/hardware/lora.po +++ b/docs/locales/zh_CN/LC_MESSAGES/hardware/lora.po @@ -9,7 +9,7 @@ msgid "" msgstr "" "Project-Id-Version: UIFlow2 Programming Guide \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-09-26 10:50+0800\n" +"POT-Creation-Date: 2025-11-12 11:51+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language: zh_CN\n" @@ -18,147 +18,154 @@ msgstr "" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" -"Generated-By: Babel 2.16.0\n" +"Generated-By: Babel 2.17.0\n" #: ../../en/hardware/lora.rst:2 ../../en/hardware/lora.rst:13 -#: c06e8d85a3a34ee79b2ccc5e28041bd8 fe1319a0c4e144419f14c6aaba7eee76 +#: 0914f0739291440b97f46c1f3f17ad1d 61a4984a40fb4190a50538fa8c627cbe msgid "LoRa" msgstr "LoRa" -#: ../../en/hardware/lora.rst:6 b0a08d0b402c4fb8851949dcced1af82 +#: ../../en/hardware/lora.rst:6 098f01fac3e34e3b8b5e53390c8ca536 msgid "" "LoRa is used to control the built-in long-range wireless communication " "module inside the host device. Below is the detailed LoRa support for the" " host:" msgstr "LoRa 用于控制主机设备内置的远程无线通信模块。以下是主机的详细 LoRa 支持:" -#: ../../en/hardware/lora.rst:13 d2cfc7d198ef41eb8fa72a27e37832a7 +#: ../../en/hardware/lora.rst:13 3d097159f00147edbd0955d3967f3cb3 msgid "Controllers" msgstr "" -#: ../../en/hardware/lora.rst:15 756596419e4e4138902aa2a47c694695 +#: ../../en/hardware/lora.rst:15 c8328105dac34002be980714820b64ff msgid "UnitC6L" msgstr "UnitC6L" -#: ../../en/hardware/lora.rst:15 da4a3c7bab9a40aca3c9b69547f380e1 +#: ../../en/hardware/lora.rst:15 ../../en/hardware/lora.rst:17 +#: 4e791392ffaa48a182167579f61c375b 8925a9ee759c4841aa7ba2599e515040 msgid "|S|" msgstr "" -#: ../../en/hardware/lora.rst:21 60f11e0527ea4ddab9db6d73592dcb4a +#: ../../en/hardware/lora.rst:17 abe148087d054e3eae3c578541b3a0f8 +msgid "Nesso N1" +msgstr "" + +#: ../../en/hardware/lora.rst:23 1e9aaa09e8ed4fb6a7ab471092b090a0 msgid "UiFlow2 Example" msgstr "UiFlow2 应用示例" -#: ../../en/hardware/lora.rst:24 ../../en/hardware/lora.rst:57 -#: 134f1698d39d4af3a22445d2cdb6d0dc b39800c60d28452c9558a87aa5ca30e7 +#: ../../en/hardware/lora.rst:26 ../../en/hardware/lora.rst:63 +#: 8ba1d3982db74a8db46796dfb6662f03 msgid "Sender" msgstr "发送端" -#: ../../en/hardware/lora.rst:26 07f15398cf8e46c4b927998cb98a1ae1 -#, fuzzy +#: ../../en/hardware/lora.rst:28 b137447ac399464387952f31ab32357a msgid "Open the |unit_c6l_tx_example.m5f2| project in UiFlow2." -msgstr "在 UiFlow2 上打开 |unitc6l_tx_example.m5f2| 项目。" +msgstr "在 UiFlow2 上打开 |unit_c6l_tx_example.m5f2| 项目。" -#: ../../en/hardware/lora.rst:28 ../../en/hardware/lora.rst:59 -#: c1ed525f46c144f6b06963e24e158083 fe49c2459fdc40b38df1d618b81d39dd +#: ../../en/hardware/lora.rst:30 01cfde10000148328a8e16042916acef +msgid "Open the |nesso_n1_sender_example.m5f2| project in UiFlow2." +msgstr "在 UiFlow2 上打开 |nesso_n1_sender_example.m5f2| 项目。" + +#: ../../en/hardware/lora.rst:32 ../../en/hardware/lora.rst:65 +#: 825c44aa230d49c8842b9dd337c49a42 msgid "This example sends data every second." msgstr "该示例每秒发送一次数据。" -#: ../../en/hardware/lora.rst:30 ../../en/hardware/lora.rst:45 -#: ../../en/hardware/lora.rst:121 ../../en/hardware/lora.rst:139 -#: ../../en/hardware/lora.rst:155 ../../en/hardware/lora.rst:173 -#: ../../en/hardware/lora.rst:189 ../../en/hardware/lora.rst:205 -#: ../../en/hardware/lora.rst:221 ../../en/hardware/lora.rst:237 -#: ../../en/hardware/lora.rst:256 ../../en/hardware/lora.rst:272 -#: ../../en/hardware/lora.rst:294 ../../en/hardware/lora.rst:315 -#: ../../en/hardware/lora.rst:331 ../../en/hardware/lora.rst:347 -#: ../../en/hardware/lora.rst:364 5f3df6bf6ebd4ad48001639727ebc692 -#: 8c24ae9ac0144f2b891411c7ad6f006d efff837a0bbd4f58b95325f013ddac08 +#: ../../en/hardware/lora.rst:34 ../../en/hardware/lora.rst:51 +#: ../../en/hardware/lora.rst:127 ../../en/hardware/lora.rst:145 +#: ../../en/hardware/lora.rst:161 ../../en/hardware/lora.rst:179 +#: ../../en/hardware/lora.rst:195 ../../en/hardware/lora.rst:211 +#: ../../en/hardware/lora.rst:227 ../../en/hardware/lora.rst:243 +#: ../../en/hardware/lora.rst:262 ../../en/hardware/lora.rst:278 +#: ../../en/hardware/lora.rst:300 ../../en/hardware/lora.rst:321 +#: ../../en/hardware/lora.rst:337 ../../en/hardware/lora.rst:353 +#: ../../en/hardware/lora.rst:370 ec019aecc51e43d6824c5227c1101e8a msgid "UiFlow2 Code Block:" msgstr "UiFlow2 代码块:" -#: ../../en/hardware/lora.rst:32 32321f22c3314a32b43a59df1951c31f +#: ../../en/hardware/lora.rst:36 51f68a4331634b59b3b0e828c2a18cf3 msgid "|unit_c6l_tx_example.png|" msgstr "" -#: ../../en/refs/hardware.lora.ref:21 09e4f7d908d74172a517a393459ce8b8 +#: ../../en/refs/hardware.lora.ref:21 a056aac132434cfba648a83069476eb6 msgid "unit_c6l_tx_example.png" msgstr "" -#: ../../en/hardware/lora.rst:34 ../../en/hardware/lora.rst:49 -#: ../../en/hardware/lora.rst:67 ../../en/hardware/lora.rst:82 -#: 309bddc5217b44c4af29684cd2e62cf0 d6b09811e2894256ba58c1dd51f9b7af -#: ff76e67d20ff4fb086c1f047bed12600 +#: ../../en/hardware/lora.rst:38 ../../en/hardware/lora.rst:55 +#: ../../en/hardware/lora.rst:73 ../../en/hardware/lora.rst:88 +#: a1b885522cb1406788601fb135cd81ef msgid "Example output:" msgstr "示例输出:" -#: ../../en/hardware/lora.rst:36 ../../en/hardware/lora.rst:51 -#: ../../en/hardware/lora.rst:69 ../../en/hardware/lora.rst:84 -#: 7f8369e2656e49deb41bee9465663332 87b214342af0434a9c2d96e6bea32d36 -#: e17113801eb34480ab298801b7b0ede0 +#: ../../en/hardware/lora.rst:40 ../../en/hardware/lora.rst:57 +#: ../../en/hardware/lora.rst:75 ../../en/hardware/lora.rst:90 +#: a9411fde762d49058d19b28481183994 msgid "None" msgstr "无" -#: ../../en/hardware/lora.rst:39 ../../en/hardware/lora.rst:72 -#: 1e5c63e07c93415080edf0c04cb0de75 31153b10476545f091c8f7ee865b423f +#: ../../en/hardware/lora.rst:43 ../../en/hardware/lora.rst:78 +#: 57e9229b1f484b38a9b4078773b448ce msgid "Receiver" msgstr "接收端" -#: ../../en/hardware/lora.rst:41 9f38f03e15af4c019d66de32a4500516 -#, fuzzy +#: ../../en/hardware/lora.rst:45 01cfde10000148328a8e16042916acef msgid "Open the |unit_c6l_rx_example.m5f2| project in UiFlow2." -msgstr "在 UiFlow2 上打开 |unitc6l_rx_example.m5f2| 项目。" +msgstr "在 UiFlow2 上打开 |unit_c6l_rx_example.m5f2| 项目。" + +#: ../../en/hardware/lora.rst:47 01cfde10000148328a8e16042916acef +msgid "Open the |nesso_n1_receiver_example.m5f2| project in UiFlow2." +msgstr "在 UiFlow2 上打开 |nesso_n1_receiver_example.m5f2| 项目。" -#: ../../en/hardware/lora.rst:43 ../../en/hardware/lora.rst:74 -#: b73aae634eba4eea8bdc7ef9c793b5c7 cab7da0f396f4cbb842dda17d067fcbe +#: ../../en/hardware/lora.rst:49 ../../en/hardware/lora.rst:80 +#: f83c667bcc1144cdb94e1be21b504ea1 msgid "This example receives and displays data." msgstr "示例接收并显示数据。" -#: ../../en/hardware/lora.rst:47 2ceba1768c674022ae3d65ccd1b00b61 +#: ../../en/hardware/lora.rst:53 f92fea834f7a4ef290f42d596a9259fd msgid "|unit_c6l_rx_example.png|" msgstr "" -#: ../../en/refs/hardware.lora.ref:22 8bed3b068c8c4254bbce389c99b5e9dc +#: ../../en/refs/hardware.lora.ref:22 66dd4a97b9f54b978634cdee3381952e msgid "unit_c6l_rx_example.png" msgstr "" -#: ../../en/hardware/lora.rst:54 545d379a5385468d8c674a203240899d +#: ../../en/hardware/lora.rst:60 f575b27bb8724455bc74b8609a2422e4 msgid "MicroPython Example" msgstr "MicroPython 应用示例" -#: ../../en/hardware/lora.rst:61 ../../en/hardware/lora.rst:76 -#: ../../en/hardware/lora.rst:125 ../../en/hardware/lora.rst:143 -#: ../../en/hardware/lora.rst:159 ../../en/hardware/lora.rst:177 -#: ../../en/hardware/lora.rst:193 ../../en/hardware/lora.rst:209 -#: ../../en/hardware/lora.rst:225 ../../en/hardware/lora.rst:241 -#: ../../en/hardware/lora.rst:260 ../../en/hardware/lora.rst:276 -#: ../../en/hardware/lora.rst:298 ../../en/hardware/lora.rst:319 -#: ../../en/hardware/lora.rst:335 ../../en/hardware/lora.rst:351 -#: ../../en/hardware/lora.rst:368 cbebf6bdd22042ea8d9c80974f92b679 +#: ../../en/hardware/lora.rst:67 ../../en/hardware/lora.rst:82 +#: ../../en/hardware/lora.rst:131 ../../en/hardware/lora.rst:149 +#: ../../en/hardware/lora.rst:165 ../../en/hardware/lora.rst:183 +#: ../../en/hardware/lora.rst:199 ../../en/hardware/lora.rst:215 +#: ../../en/hardware/lora.rst:231 ../../en/hardware/lora.rst:247 +#: ../../en/hardware/lora.rst:266 ../../en/hardware/lora.rst:282 +#: ../../en/hardware/lora.rst:304 ../../en/hardware/lora.rst:325 +#: ../../en/hardware/lora.rst:341 ../../en/hardware/lora.rst:357 +#: ../../en/hardware/lora.rst:374 96c7d4dfc43444058abb3a97a2cae0bc msgid "MicroPython Code Block:" msgstr "MicroPython 代码块:" -#: ../../en/hardware/lora.rst:87 99d4a966bd684feeb9db55687ea9f2e1 +#: ../../en/hardware/lora.rst:93 858cec74980f4549a185c3bc7d268528 msgid "**API**" msgstr "API应用" -#: ../../en/hardware/lora.rst:90 3a1c9a3923e54c5bb7f4dd76b3045056 +#: ../../en/hardware/lora.rst:96 26ee295ab0bd458c8f07be3732ac244d msgid "class LoRa" msgstr "" -#: ../../en/hardware/lora.rst:100 25a3b4e6198b42d6bacc31ec77f59d0a +#: ../../en/hardware/lora.rst:106 dd5bbd8a9aa940f7a19cc3204d75c280 msgid "Create an LoRa object." msgstr "创建一个 LoRa 对象" -#: ../../en/hardware/lora.rst b4409e3d58764db4bff9c8358f7f2712 +#: ../../en/hardware/lora.rst aae55122c2dc4c91905c29a95e7b47c3 msgid "Parameters" msgstr "" -#: ../../en/hardware/lora.rst:102 42772aaa52c04a9486c45efe70de999e +#: ../../en/hardware/lora.rst:108 70f550e38eb94170aa4bb79cd39c8bbb msgid "LoRa RF frequency in KHz, with a range of 850000 KHz to 930000 KHz." msgstr "LoRa 通信频率,单位为 KHz,范围为 850000 KHz 到 930000 KHz。" -#: ../../en/hardware/lora.rst:103 7131ab06e14b4ea28550f36e1a2e36ac -#, fuzzy +#: ../../en/hardware/lora.rst:109 8ffd8d6e372e4f508c364a889b57beb8 msgid "" "Bandwidth, options include: - ``\"7.8\"``: 7.8 KHz - ``\"10.4\"``: 10.4 " "KHz - ``\"15.6\"``: 15.6 KHz - ``\"20.8\"``: 20.8 KHz - ``\"31.25\"``: " @@ -170,119 +177,119 @@ msgstr "" "``\"41.7\"``: 41.7 KHz - ``\"62.5\"``: 62.5 KHz - ``\"125\"``: 125 KHz - " "``\"250\"``: 250 KHz - ``\"500\"``: 500 KHz" -#: ../../en/hardware/lora.rst:103 3c63776dc4af41b0960340043cf81635 +#: ../../en/hardware/lora.rst:109 c5b31f8f221347a39ef80f191c61f8f6 msgid "Bandwidth, options include:" msgstr "" -#: ../../en/hardware/lora.rst:105 3c837a6e86904bb89d820e4853e84999 +#: ../../en/hardware/lora.rst:111 38b29606f6e54e66b97af518532bd285 msgid "``\"7.8\"``: 7.8 KHz" msgstr "" -#: ../../en/hardware/lora.rst:106 7128011d7e9542b38fc63ef5ef56ddaa +#: ../../en/hardware/lora.rst:112 1dec78c4d7ca429cbf980332fa06aa80 msgid "``\"10.4\"``: 10.4 KHz" msgstr "" -#: ../../en/hardware/lora.rst:107 f90a34f2fe7a4185845efb343c51780b +#: ../../en/hardware/lora.rst:113 8fe2c00c1d1f45028917136de65bc0dc msgid "``\"15.6\"``: 15.6 KHz" msgstr "" -#: ../../en/hardware/lora.rst:108 58d6b375d09f41348849444f0910be0b +#: ../../en/hardware/lora.rst:114 abd8b38bded546d0b7fc573358a339a2 msgid "``\"20.8\"``: 20.8 KHz" msgstr "" -#: ../../en/hardware/lora.rst:109 779eed6fbf784d8d9d10dab48879a71d +#: ../../en/hardware/lora.rst:115 b5d7df4076e142258d75c0fdbd902113 msgid "``\"31.25\"``: 31.25 KHz" msgstr "" -#: ../../en/hardware/lora.rst:110 a7ccb26655934228ac7259bf89024836 +#: ../../en/hardware/lora.rst:116 2decbe8062cb49eb86f1366caaec496c msgid "``\"41.7\"``: 41.7 KHz" msgstr "" -#: ../../en/hardware/lora.rst:111 6f2d5c42dea24c868a83b72b62918516 +#: ../../en/hardware/lora.rst:117 8937741d7f18466ba5234ae87260beb2 msgid "``\"62.5\"``: 62.5 KHz" msgstr "" -#: ../../en/hardware/lora.rst:112 cf414a5d779840de9cd2adb3f7557bca +#: ../../en/hardware/lora.rst:118 2ada171fbeae45fcacc06100fa8d4dff msgid "``\"125\"``: 125 KHz" msgstr "" -#: ../../en/hardware/lora.rst:113 0d89fb08ae7d4d79979701f94363b5c2 +#: ../../en/hardware/lora.rst:119 39fae231e9e44656b4c661b176607889 msgid "``\"250\"``: 250 KHz" msgstr "" -#: ../../en/hardware/lora.rst:114 5a787b77582e429a8439f2d7f7194caa +#: ../../en/hardware/lora.rst:120 34545f9e7545454dbb1a526ca71674d2 msgid "``\"500\"``: 500 KHz" msgstr "" -#: ../../en/hardware/lora.rst:115 9360d3ac93984ebfb4be75b2a878fd5a +#: ../../en/hardware/lora.rst:121 4cf5a1bf3e7c45c3b3ee79b2f20d0452 msgid "" "Spreading factor, range from 7 to 12. Higher spreading factors allow " "reception of weaker signals but with slower data rates." msgstr "扩频因子,范围为 7 到 12。较高的扩频因子可以接收较弱的信号,但数据传输速率会变慢。" -#: ../../en/hardware/lora.rst:116 e8ad6769e8f047e799bd6232383c50f0 +#: ../../en/hardware/lora.rst:122 4fffb0acc5df45e1a6179e9e0450f77b msgid "" "Forward Error Correction (FEC) coding rate expressed as 4/N, with a range" " from 5 to 8." msgstr "前向纠错(FEC)编码率表示为 4/N,范围为 5 到 8。" -#: ../../en/hardware/lora.rst:117 e0e367f17dd84c40ba9a0a027299d9ca +#: ../../en/hardware/lora.rst:123 9fec24498405495e9f12d3322d10b1eb msgid "Length of the preamble sequence in symbols, range from 0 to 255." msgstr "前导码序列的长度(以符号为单位),范围为 0 到 255。" -#: ../../en/hardware/lora.rst:118 1cccd7be09004234859216f887b3fa4b +#: ../../en/hardware/lora.rst:124 fe3ab955487c4f64b30f3d6009a33bdb msgid "Sync word to mark the start of the data frame, default is 0x12." msgstr "用于标记数据帧起始的同步字,默认值为 0x12。" -#: ../../en/hardware/lora.rst:119 30173a89d61542acb616380e02944363 +#: ../../en/hardware/lora.rst:125 d2b09105ff92450abc48f08bdc5ed945 msgid "Output power in dBm, range from -9 to 22." msgstr "输出功率(以 dBm 为单位),范围为 -9 到 22。" -#: ../../en/hardware/lora.rst:123 52ead286a2284c36bb7ba8dfd4b887ae +#: ../../en/hardware/lora.rst:129 3fef1f2ea87a43c0b96fe9fdb89f9341 msgid "|init.png|" msgstr "" -#: ../../en/refs/hardware.lora.ref:2 9e28814eae664a148b53a3ccab36787f +#: ../../en/refs/hardware.lora.ref:2 8dc16b32d6fe4881bf8571ac49e450af msgid "init.png" msgstr "" -#: ../../en/hardware/lora.rst:135 e64c8feebdc649f4906d19c4bb1eeaa7 +#: ../../en/hardware/lora.rst:141 398343a70fe540af8173936fc893adf6 msgid "Set frequency in kHz." msgstr "设置频率(单位:kHz)" -#: ../../en/hardware/lora.rst:137 5ff7a11a41cc4a999a8e2547da5dd8fc +#: ../../en/hardware/lora.rst:143 f9a3d4bb9662407ba04f0c95501ae863 msgid "Frequency in kHz (850000 ~ 930000), default is 868000." msgstr "频率(单位:kHz),默认值 868000 (kHz)。" -#: ../../en/hardware/lora.rst:141 0b4d9c59e2684a5ba0c5294defc272cc +#: ../../en/hardware/lora.rst:147 9940496bad284e1bb85b6f83c7707c75 msgid "|set_freq.png|" msgstr "" -#: ../../en/refs/hardware.lora.ref:3 40ed1aa310fa46d4adc01e200b92473f +#: ../../en/refs/hardware.lora.ref:3 29765c73218d479fb0d13408a075d7a4 msgid "set_freq.png" msgstr "" -#: ../../en/hardware/lora.rst:151 21eabf6e61524c52ae5159f6b0be8c72 +#: ../../en/hardware/lora.rst:157 b47ab3ec10c94721aec52b71bc3555fe msgid "Set spreading factor (SF)." msgstr "设置扩频因子。" -#: ../../en/hardware/lora.rst:153 4b020b79eac74916a775a3a6ad1c4657 +#: ../../en/hardware/lora.rst:159 069bc7ac7fbc4b19a2c32911295a1e34 msgid "Spreading factor (7 ~ 12)" msgstr "扩频因子(7~12)。" -#: ../../en/hardware/lora.rst:157 71fa8aa118104b69848f1d8b2c971045 +#: ../../en/hardware/lora.rst:163 e71fb429ac974d0d8de186bbe3ef189f msgid "|set_sf.png|" msgstr "" -#: ../../en/refs/hardware.lora.ref:4 e964b0fbf6f140f58108db7388d4a096 +#: ../../en/refs/hardware.lora.ref:4 726551c760e04b58850523282e55dced msgid "set_sf.png" msgstr "" -#: ../../en/hardware/lora.rst:167 f6e52d66144647468bf8e2029466ded8 +#: ../../en/hardware/lora.rst:173 40afefa968ea4456b8b6df518b096abf msgid "Set bandwidth." msgstr "设置带宽。" -#: ../../en/hardware/lora.rst:169 f792a3e3e1a940b892f12ca5d4d2567f +#: ../../en/hardware/lora.rst:175 418221e475f54cb786612612e1267c5a msgid "" "Bandwidth in kHz as string. Must be one of: '7.8', '10.4', '15.6', " "'20.8', '31.25', '41.7', '62.5', '125', '250', '500'." @@ -290,241 +297,241 @@ msgstr "" "带宽(以 kHz " "表示的字符串)。必须是以下值之一:'7.8'、'10.4'、'15.6'、'20.8'、'31.25'、'41.7'、'62.5'、'125'、'250'、'500'。" -#: ../../en/hardware/lora.rst:175 01eb33a4922f4e51991169a9abba5ee7 +#: ../../en/hardware/lora.rst:181 e9520dba875548ee882ab40f06218e1a msgid "|set_bw.png|" msgstr "" -#: ../../en/refs/hardware.lora.ref:5 e2a21c10bdc44d6da973534fc021868e +#: ../../en/refs/hardware.lora.ref:5 d8d323c1c55c4c3cb63ca6dcb3fab3a2 msgid "set_bw.png" msgstr "" -#: ../../en/hardware/lora.rst:185 576c0a5ca483427b96ce8476b3846978 +#: ../../en/hardware/lora.rst:191 5efd9ef310854a469b9a6915e4a99dd2 msgid "Set coding rate." msgstr "设置编码率。" -#: ../../en/hardware/lora.rst:187 f2929a079672499c8c508ad403fe3123 +#: ../../en/hardware/lora.rst:193 1538668de71d4349aa91e60abaf4977a msgid "Coding rate (5 ~ 8)" msgstr "编码率 (5 ~ 8)。" -#: ../../en/hardware/lora.rst:191 b1f7d31fcf654917acbedac0b359068f +#: ../../en/hardware/lora.rst:197 6620920fe7b94ef9a703090680cdb465 msgid "|set_coding_rate.png|" msgstr "" -#: ../../en/refs/hardware.lora.ref:6 b4d35331db4544d1bb747462bbe77918 +#: ../../en/refs/hardware.lora.ref:6 9cd56aa402fd43a3afc952d8c7097101 msgid "set_coding_rate.png" msgstr "" -#: ../../en/hardware/lora.rst:201 4533411d387e49a0971424aaf3f7d132 +#: ../../en/hardware/lora.rst:207 009e3b68344848b89401b60b6f0e7f8c msgid "Set syncword." msgstr "设置同步字。" -#: ../../en/hardware/lora.rst:203 f05be3c9e8f145ecb90e8162aec5330d +#: ../../en/hardware/lora.rst:209 98d118d9784a4c998da3258897f40df6 msgid "Sync word (0 ~ 0xFF)" msgstr "同步字 (0 ~ 0xFF)" -#: ../../en/hardware/lora.rst:207 ff27c34fc69e45b68ac72d1fec4d7bd3 +#: ../../en/hardware/lora.rst:213 58a2cd24a402426482aa9ea40684c00c msgid "|set_syncword.png|" msgstr "" -#: ../../en/refs/hardware.lora.ref:7 e292cc14e5a54b9bb13f3fd99ac50d6c +#: ../../en/refs/hardware.lora.ref:7 c219c80711804892bee54dad9d335838 msgid "set_syncword.png" msgstr "" -#: ../../en/hardware/lora.rst:217 2e4fe0d2effd47df90bee7a3f2c3bba4 +#: ../../en/hardware/lora.rst:223 4778e696178d4a1481102b531edb9981 msgid "Set preamble length." msgstr "设置前导符长度。" -#: ../../en/hardware/lora.rst:219 614470ec25384a2fac03d3139bd61152 +#: ../../en/hardware/lora.rst:225 c623576367a14d51b781f276fff40f78 msgid "Preamble length, range: 0~255." msgstr "前导符长度,范围:0~255。" -#: ../../en/hardware/lora.rst:223 2fda68cf2fae41a3be76bf67f8f99065 +#: ../../en/hardware/lora.rst:229 e6586a18c9424376b147dc00a339db48 msgid "|set_preamble_len.png|" msgstr "" -#: ../../en/refs/hardware.lora.ref:8 808cb1727da244a1ba2c1225cd49bfae +#: ../../en/refs/hardware.lora.ref:8 414650bcb68040d3b09a4db54a107f88 msgid "set_preamble_len.png" msgstr "" -#: ../../en/hardware/lora.rst:233 e41f5216b36a4ea6b0bf3c30e656d525 +#: ../../en/hardware/lora.rst:239 cc1e8939f9e948fd814d1685cfb6707d msgid "Set output power in dBm." msgstr "设置输出功率(单位:dBm) 。" -#: ../../en/hardware/lora.rst:235 c7ebaec98a5749f2b5b0b8f4ddaa542a +#: ../../en/hardware/lora.rst:241 e8fe4855c51f4cdcb105c12bd6a4c5b1 msgid "Output power in dBm (-9 ~ 22)" msgstr "输出功率(单位:dBm),范围:-9 ~ 22。" -#: ../../en/hardware/lora.rst:239 e9ca90e1a41a46e8bfaeefd743664e7b +#: ../../en/hardware/lora.rst:245 55cacb08aa79488a9eaba911c62457f1 msgid "|set_output_power.png|" msgstr "" -#: ../../en/refs/hardware.lora.ref:9 cc02b052cf1e4e1d8bf3a6ec3bde4acb +#: ../../en/refs/hardware.lora.ref:9 cea5c4966eca4039aec6f81278d325b1 msgid "set_output_power.png" msgstr "" -#: ../../en/hardware/lora.rst:249 cecc3ca7d7d04a4a8cadd0273c2af6fa +#: ../../en/hardware/lora.rst:255 c83b3e89a5e6428fbd5dfa160fbb20e1 msgid "Set the interrupt callback function to be executed on IRQ." msgstr "设置在中断请求(IRQ)发生时执行的中断回调函数。" -#: ../../en/hardware/lora.rst:251 c9f43ddfe5d1411dbe997a2e67277e78 +#: ../../en/hardware/lora.rst:257 946216f48d30482da385599ffc285bbd msgid "" "The callback function to be invoked when the interrupt is triggered. The " "callback should not take any arguments and should return nothing." msgstr "当中断触发时调用的回调函数。该回调函数不应接受任何参数,且不应有返回值。" -#: ../../en/hardware/lora.rst:254 7c894b5fd2a44cd6a8b0a86366169fba +#: ../../en/hardware/lora.rst:260 b315d801aff44aa4bebd41d49cd94c5f msgid "Call `start_recv()` to begin receiving data." msgstr "调用 `start_recv()` 开始接收数据。" -#: ../../en/hardware/lora.rst:258 c975519f080a4711bc23a6c063bdc115 +#: ../../en/hardware/lora.rst:264 465d2ca0f014410aa7f17c8ebfe2e22b msgid "|set_irq_callback.png|" msgstr "" -#: ../../en/refs/hardware.lora.ref:13 81c0a2d3084b469baadd5280a5801a94 +#: ../../en/refs/hardware.lora.ref:13 a44c179108c647c09f24c4d67c096d42 msgid "set_irq_callback.png" msgstr "" -#: ../../en/hardware/lora.rst:268 53fbb3769b044ea9ae719e067f20b9bb +#: ../../en/hardware/lora.rst:274 12e318a7b60147309016927b2a093ef6 msgid "Start receive data." msgstr "开始接收数据。" -#: ../../en/hardware/lora.rst:270 8fa547847f8c48b282157573cc29456a +#: ../../en/hardware/lora.rst:276 f09a18d4a52f49b8a356fe3c8b3fe155 msgid "This method initiates the process to begin receiving data." msgstr "此方法启动接收数据的过程。" -#: ../../en/hardware/lora.rst:274 9416035105f54323a111b362e9b644b3 +#: ../../en/hardware/lora.rst:280 b408923dfc5847dc856f682e462e4087 msgid "|start_recv.png|" msgstr "" -#: ../../en/refs/hardware.lora.ref:10 5591f4a1d94648d982fc2c4d62d59ddc +#: ../../en/refs/hardware.lora.ref:10 21b54cd95d4d429ca763098884582487 msgid "start_recv.png" msgstr "" -#: ../../en/hardware/lora.rst:284 7444c7eb6fdf42b3aec3149613649c28 +#: ../../en/hardware/lora.rst:290 a59e66a0f90844f498c3fd4b918da495 msgid "Receive data." msgstr "接收数据。" -#: ../../en/hardware/lora.rst:286 1fadcc95aaeb47528719ec1b4dae3f9a +#: ../../en/hardware/lora.rst:292 86d193a8810044cdba5b035fe4e16b92 msgid "Timeout in milliseconds (optional). Default is None." msgstr "超时时间(以毫秒为单位,可选)。默认为 None。" -#: ../../en/hardware/lora.rst:287 3096cd82bd7342f5854b927e2094cdc7 +#: ../../en/hardware/lora.rst:293 06f53a9bd4b64473807c3c52e7f710b9 msgid "Length of the data to be read. Default is 0xFF." msgstr "读取数据的长度。默认为 0xFF。" -#: ../../en/hardware/lora.rst:288 edbdca7d69cc4714b38e6d1c37634b47 +#: ../../en/hardware/lora.rst:294 ffb7423c773545c199f6d9f5ae8cd2f8 msgid "An instance of `RxPacket` (optional) to reuse." msgstr "一个可选的 RxPacket 实例,用于重用。" -#: ../../en/hardware/lora.rst 234df300158341e49c9ee488e5ceb233 +#: ../../en/hardware/lora.rst e966e2a4dc8a49909e12d7f34cb46125 msgid "Returns" msgstr "" -#: ../../en/hardware/lora.rst:289 c431d11e2b104f53a235e44f9b8b271a +#: ../../en/hardware/lora.rst:295 f1ea75ee95a642ff9687f05171238659 msgid "Received packet instance" msgstr "接收数据包实例。" -#: ../../en/hardware/lora.rst fec628b0fb5341ba86b856e2dc296e69 +#: ../../en/hardware/lora.rst 31a5e0b7c4664b77bbfade3822f6da68 msgid "Return type" msgstr "" -#: ../../en/hardware/lora.rst:292 d47716f4af6c4e8dbacf7e278cd10490 +#: ../../en/hardware/lora.rst:298 337c8f3db60e44b9b98a22d75737a786 msgid "" "Attempt to receive a LoRa packet. Returns `None` if timeout occurs, or " "returns the received packet instance." msgstr "尝试接收一个 LoRa 数据包。如果发生超时,返回 None;否则,返回接收到的数据包实例。" -#: ../../en/hardware/lora.rst:296 653dc45c40104f6fa1cbdace8fdb165d +#: ../../en/hardware/lora.rst:302 d5046ea586524c6294cbb6e192cd933f msgid "|recv.png|" msgstr "" -#: ../../en/refs/hardware.lora.ref:11 7b71266ccee041d6b73eb3cf770dc29a +#: ../../en/refs/hardware.lora.ref:11 3d375b557f2f4c90abbb083328c756cf msgid "recv.png" msgstr "" -#: ../../en/hardware/lora.rst:306 31e1ab68745546dfac84a4b7187e58d4 +#: ../../en/hardware/lora.rst:312 bb6dc0b7da864457b69441eaae928211 msgid "Send data." msgstr "发送数据" -#: ../../en/hardware/lora.rst:308 94c1e8f4d7714d32948f3e179fb5d64d +#: ../../en/hardware/lora.rst:314 c2f11141a7a243129594b79f0830bb00 msgid "The data to be sent." msgstr "要发送的数据" -#: ../../en/hardware/lora.rst:309 289a5a70788b45d6938e608b9dcf0835 +#: ../../en/hardware/lora.rst:315 e2e784fd1c3048129cc37edf08c4d0fc msgid "" "The timestamp in milliseconds when to send the data (optional). Default " "is None." msgstr "发送数据的时间戳,单位为毫秒(可选)。默认为 None。" -#: ../../en/hardware/lora.rst:310 4be0e879a04d446d87d98a4b41a319e7 +#: ../../en/hardware/lora.rst:316 bd553ea6288547d3ad4c6b9722a5d202 msgid "" "Returns a timestamp (result of `time.ticks_ms()`) indicating when the " "data packet was sent." msgstr "返回一个时间戳(time.ticks_ms()的结果),表示数据包发送的时间。" -#: ../../en/hardware/lora.rst:313 e18b5ef59cf24321be6c739781012a0d +#: ../../en/hardware/lora.rst:319 ec9c6c829ffa49b28a9fa8a971ec9e33 msgid "Send a data packet and return the timestamp after the packet is sent." msgstr "发送一个数据包并返回数据包发送后的时间戳。" -#: ../../en/hardware/lora.rst:317 475cdffad2d0468595e003be78a641b3 +#: ../../en/hardware/lora.rst:323 3286db84d3fa4307833a719364d36cdd msgid "|send.png|" msgstr "" -#: ../../en/refs/hardware.lora.ref:16 e4d0383f3e78461e842a07c9775c2de6 +#: ../../en/refs/hardware.lora.ref:16 cd7bfbbdaab746249860bbe550803196 msgid "send.png" msgstr "" -#: ../../en/hardware/lora.rst:327 8c71fa94841a4f81a7ba7ba32db26f45 +#: ../../en/hardware/lora.rst:333 128ffbe7cd1d4f9183937e0079bc8e26 msgid "Set module to standby mode." msgstr "设置模块为待机模式。" -#: ../../en/hardware/lora.rst:329 127560c09acd4c628e40e984d0e30a2e +#: ../../en/hardware/lora.rst:335 cea03fdaf64f467b8f7a7cc4de2a38ad msgid "Puts the LoRa module into standby mode, consuming less power." msgstr "将 LoRa 模块置于待机模式,从而降低功耗。" -#: ../../en/hardware/lora.rst:333 bc9cf07e96464f0195fda0cfe76d6fb9 +#: ../../en/hardware/lora.rst:339 64f043db01144954bb7e282326ff8f3f msgid "|standby.png|" msgstr "" -#: ../../en/refs/hardware.lora.ref:18 97283f5bf2fc4cab8a7e3902bac1f046 +#: ../../en/refs/hardware.lora.ref:18 eb0c6098431a4c38adb8b0371223d3cf msgid "standby.png" msgstr "" -#: ../../en/hardware/lora.rst:343 4e79912da77245aebd8bf2dc8d176756 +#: ../../en/hardware/lora.rst:349 71354ea33ab84e74a3088c84642d8ae9 msgid "Put the module to sleep mode." msgstr "将 LoRa 模块置于睡眠模式" -#: ../../en/hardware/lora.rst:345 6b60652eaf0c44c0bea441aa29bb24a3 +#: ../../en/hardware/lora.rst:351 416d0cf67e9e4eaebf522a7e4baec52c msgid "Reduces the power consumption by putting the module into deep sleep mode." msgstr "通过将模块置于深度睡眠模式来减少功耗。" -#: ../../en/hardware/lora.rst:349 aded052e1ce347d390b7ddd17c8448b3 +#: ../../en/hardware/lora.rst:355 ac480ff1f8324820b9d81b8c8baec1fc msgid "|sleep.png|" msgstr "" -#: ../../en/refs/hardware.lora.ref:19 a92ae70f03c44a1b9b70eecd6750764b +#: ../../en/refs/hardware.lora.ref:19 6a68821423bd4c5a9916f82bac7e3ed5 msgid "sleep.png" msgstr "" -#: ../../en/hardware/lora.rst:359 91b67aa1fd284474882b1980a961073f +#: ../../en/hardware/lora.rst:365 e7c32ae25c1540deba104ec90f254968 msgid "Check IRQ trigger." msgstr "检查 IRQ 触发。" -#: ../../en/hardware/lora.rst:361 b18180e9174f4e1d9ee999d8cfa51eb6 +#: ../../en/hardware/lora.rst:367 9a3f503d107d459c84354bc73b87c01e msgid "" "Returns `True` if an interrupt service routine (ISR) has been triggered " "since the last send or receive started." msgstr "如果自上次发送或接收开始以来,已经触发了中断服务例程(ISR),则返回 True。" -#: ../../en/hardware/lora.rst:366 3cf9f1bbd41a40da88ca923db8618205 +#: ../../en/hardware/lora.rst:372 afed6e25f50448f6b453093dcc9c0f4d msgid "|irq_triggered.png|" msgstr "" -#: ../../en/refs/hardware.lora.ref:17 f6d7a71e1a91409ca31f80e9789d35c8 +#: ../../en/refs/hardware.lora.ref:17 f1cbf72b73cd4839a6abfe480f4dbf01 msgid "irq_triggered.png" msgstr "" -#: ../../en/hardware/lora.rst:374 599abc9ac89743718026139887ba50bf +#: ../../en/hardware/lora.rst:380 a02244240184415da9053bdd8931c573 msgid "Refer to :ref:`lora_rxpacket` for more details about RxPacket." msgstr "" diff --git a/examples/hardware/lora/nesso_n1_receiver_example.m5f2 b/examples/hardware/lora/nesso_n1_receiver_example.m5f2 new file mode 100644 index 00000000..d0979250 --- /dev/null +++ b/examples/hardware/lora/nesso_n1_receiver_example.m5f2 @@ -0,0 +1 @@ +{"version":"V2.0","versionNumber":"V2.3.8","type":"arduino-nesso-n1","components":[{"name":"screen","type":"screen","layer":0,"screenId":"builtin","screenName":"","id":"__arduino-nesso-n1_screen","createTime":1762846575838,"x":0,"y":0,"width":135,"height":240,"backgroundColor":"#000000","size":0,"isSelected":true},{"name":"title0","type":"title","layer":1,"screenId":"builtin","screenName":"","id":"ba&Fn*-w9osIhQaE","createTime":1762918173466,"x":-2,"y":-2,"color":"#ffffff","backgroundColor":"#0000FF","text":"Receiver","textOffset":3,"font":"Widgets.FONTS.DejaVu18","isSelected":false,"width":241,"height":19},{"name":"label0","type":"label","layer":2,"screenId":"builtin","screenName":"","id":"e-Z#iBGmw1L@oIyX","createTime":1762918195871,"x":2,"y":40,"color":"#ffffff","backgroundColor":"#222222","text":"label0","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false,"width":58,"height":21}],"resources":[{"hardware":["hardware_button","hardware_pin_button","imu","rgb","speaker","touch","hardware_lora"]}],"units":[],"hats":[],"caps":[],"chains":[],"bases":[],"i2cs":[],"chainBus":[],"blockly":"lora_datasnrrssitrue86800025088120x1210truelora_datalabel0Labellora_datasnrsnrlora_datarssirssilora_datahello M5hello M5SNR: snrRSSI: rssi","screen":[{"simulationName":"Built-in","type":"builtin","width":135,"height":240,"scale":0.74,"screenName":"","blockId":"","screenColorType":0,"id":"builtin","createTime":1762846575837}],"logicWhenNum":0,"customList":[]} \ No newline at end of file diff --git a/examples/hardware/lora/nesso_n1_receiver_example.py b/examples/hardware/lora/nesso_n1_receiver_example.py new file mode 100644 index 00000000..e9278a22 --- /dev/null +++ b/examples/hardware/lora/nesso_n1_receiver_example.py @@ -0,0 +1,67 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import os, sys, io +import M5 +from M5 import * +from hardware import LoRa + + +title0 = None +label0 = None +lora = None + + +lora_data = None +snr = None +rssi = None + + +def lora_receive_event(received_data): + global title0, label0, lora, lora_data, snr, rssi + lora_data = received_data + label0.setText(str(lora_data.decode())) + snr = lora_data.snr + rssi = lora_data.rssi + print((str((str("SNR: ") + str(snr))) + str((str("RSSI: ") + str(rssi))))) + + +def setup(): + global title0, label0, lora, lora_data, snr, rssi + + M5.begin() + Widgets.fillScreen(0x000000) + title0 = Widgets.Title("Receiver", 3, 0xFFFFFF, 0x0000FF, Widgets.FONTS.DejaVu18) + label0 = Widgets.Label("label0", 2, 40, 1.0, 0xFFFFFF, 0x222222, Widgets.FONTS.DejaVu18) + + lora = LoRa( + pin_irq=15, + freq_khz=868000, + bw="250", + sf=8, + coding_rate=8, + preamble_len=12, + syncword=0x12, + output_power=10, + ) + lora.set_irq_callback(lora_receive_event) + + +def loop(): + global title0, label0, lora, lora_data, snr, rssi + M5.update() + + +if __name__ == "__main__": + try: + setup() + while True: + loop() + except (Exception, KeyboardInterrupt) as e: + try: + from utility import print_error_msg + + print_error_msg(e) + except ImportError: + print("please update to latest firmware") diff --git a/examples/hardware/lora/nesso_n1_sender_example.m5f2 b/examples/hardware/lora/nesso_n1_sender_example.m5f2 new file mode 100644 index 00000000..cf24d977 --- /dev/null +++ b/examples/hardware/lora/nesso_n1_sender_example.m5f2 @@ -0,0 +1 @@ +{"version":"V2.0","versionNumber":"V2.3.8","type":"arduino-nesso-n1","components":[{"name":"screen","type":"screen","layer":0,"screenId":"builtin","screenName":"","id":"__arduino-nesso-n1_screen","createTime":1762846575838,"x":0,"y":0,"width":135,"height":240,"backgroundColor":"#000000","size":0,"isSelected":true},{"name":"title0","type":"title","layer":1,"screenId":"builtin","screenName":"","id":"ba&Fn*-w9osIhQaE","createTime":1762918173466,"x":0,"y":0,"color":"#ffffff","backgroundColor":"#0000FF","text":"Sender","textOffset":3,"font":"Widgets.FONTS.DejaVu18","isSelected":false},{"name":"label0","type":"label","layer":2,"screenId":"builtin","screenName":"","id":"e-Z#iBGmw1L@oIyX","createTime":1762918195871,"x":2,"y":40,"color":"#ffffff","backgroundColor":"#222222","text":"label0","font":"Widgets.FONTS.DejaVu18","rotation":0,"isSelected":false}],"resources":[{"hardware":["hardware_button","hardware_pin_button","imu","rgb","speaker","touch","hardware_lora"]}],"units":[],"hats":[],"caps":[],"chains":[],"bases":[],"i2cs":[],"chainBus":[],"blockly":"last_timecounttexttrue86800025088120x1210last_timecount0trueGTE11last_time1000last_timetextM5countcount1textlabel0Labeltext","screen":[{"simulationName":"Built-in","type":"builtin","width":135,"height":240,"scale":0.74,"screenName":"","blockId":"","screenColorType":0,"id":"builtin","createTime":1762846575837}],"logicWhenNum":0,"customList":[]} \ No newline at end of file diff --git a/examples/hardware/lora/nesso_n1_sender_example.py b/examples/hardware/lora/nesso_n1_sender_example.py new file mode 100644 index 00000000..9821b20b --- /dev/null +++ b/examples/hardware/lora/nesso_n1_sender_example.py @@ -0,0 +1,66 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import os, sys, io +import M5 +from M5 import * +from hardware import LoRa +import time + + +title0 = None +label0 = None +lora = None + + +last_time = None +count = None +text = None + + +def setup(): + global title0, label0, lora, last_time, count, text + + M5.begin() + Widgets.fillScreen(0x000000) + title0 = Widgets.Title("Sender", 3, 0xFFFFFF, 0x0000FF, Widgets.FONTS.DejaVu18) + label0 = Widgets.Label("label0", 2, 40, 1.0, 0xFFFFFF, 0x222222, Widgets.FONTS.DejaVu18) + + lora = LoRa( + pin_irq=15, + freq_khz=868000, + bw="250", + sf=8, + coding_rate=8, + preamble_len=12, + syncword=0x12, + output_power=10, + ) + last_time = time.ticks_ms() + count = 0 + + +def loop(): + global title0, label0, lora, last_time, count, text + M5.update() + if (time.ticks_diff((time.ticks_ms()), last_time)) >= 1000: + last_time = time.ticks_ms() + text = str("M5") + str(count) + count = (count if isinstance(count, (int, float)) else 0) + 1 + lora.send(text, None) + label0.setText(str(text)) + + +if __name__ == "__main__": + try: + setup() + while True: + loop() + except (Exception, KeyboardInterrupt) as e: + try: + from utility import print_error_msg + + print_error_msg(e) + except ImportError: + print("please update to latest firmware") From b147b5c06c1f111d93b6ead9440e5170508bcf3e Mon Sep 17 00:00:00 2001 From: lbuque <1102390310@qq.com> Date: Fri, 14 Nov 2025 12:00:32 +0800 Subject: [PATCH 313/322] boards: Fix I2C driver conflict. Signed-off-by: lbuque <1102390310@qq.com> --- m5stack/Makefile | 6 +- m5stack/board.cpp | 2 +- .../boards/M5STACK_Atom_EchoS3R/board_init.c | 23 +- .../M5STACK_Atom_EchoS3R/sdkconfig.board | 7 +- m5stack/boards/M5STACK_CoreS3/board_init.c | 23 +- m5stack/boards/M5STACK_CoreS3/mpconfigboard.h | 2 + m5stack/boards/M5STACK_CoreS3/sdkconfig.board | 1 + m5stack/boards/M5STACK_Tab5/board_init.c | 128 ++- m5stack/boards/sdkconfig.spiram | 2 + m5stack/components/esp32-camera | 2 +- m5stack/machine_i2c.c | 2 +- .../5002-Add-software-i2c-support.patch | 866 ++++++++++++++++++ ...000-avoid-lv_bindings-compile-error.patch} | 0 ...ch => 6001-avoid-lvgl-font-redefine.patch} | 0 .../6002-Use-lvgl-demo-benchmark.patch | 72 ++ m5stack/patches/README.md | 1 + 16 files changed, 1085 insertions(+), 52 deletions(-) create mode 100644 m5stack/patches/5002-Add-software-i2c-support.patch rename m5stack/patches/{0003-avoid-lv_bindings-compile-error.patch => 6000-avoid-lv_bindings-compile-error.patch} (100%) rename m5stack/patches/{0018-avoid-lvgl-font-redefine.patch => 6001-avoid-lvgl-font-redefine.patch} (100%) create mode 100644 m5stack/patches/6002-Use-lvgl-demo-benchmark.patch diff --git a/m5stack/Makefile b/m5stack/Makefile index ef5281cf..fadb0769 100644 --- a/m5stack/Makefile +++ b/m5stack/Makefile @@ -308,8 +308,8 @@ M5GFX_PATH = $(abspath ./components/M5Unified/M5GFX) ESP32_CAMERA_PATH = $(abspath ./components/esp32-camera) LV_BINDING_PATCH_SERIES = \ - 0003-avoid-lv_bindings-compile-error.patch \ - 0018-avoid-lvgl-font-redefine.patch + 6000-avoid-lv_bindings-compile-error.patch \ + 6001-avoid-lvgl-font-redefine.patch MICROPYTHON_PATCH_SERIES = \ 0006-modtime-add-timezone-method.patch \ @@ -342,7 +342,7 @@ M5GFX_PATCH_SERIES = \ 4002-M5GFX-use-i2c-driver.patch ESP32_CAMERA_PATCH_SERIES = \ - 5001-Add-software-i2c-support.patch + 5002-Add-software-i2c-support.patch PACKAGES = \ lv_binding_micropython \ diff --git a/m5stack/board.cpp b/m5stack/board.cpp index 77605571..f18f9117 100644 --- a/m5stack/board.cpp +++ b/m5stack/board.cpp @@ -40,7 +40,7 @@ extern "C" { ESP_LOGI("BOARD", "Internal I2C(%d) init", in_port); #if MICROPY_HW_ESP_NEW_I2C_DRIVER i2c_master_bus_handle_t bus_handle; - if (i2c_master_get_bus_handle(in_port, &bus_handle) == ESP_ERR_INVALID_STATE) { + if (i2c_master_get_bus_handle(in_port, &bus_handle) != ESP_OK) { i2c_master_bus_config_t i2c_bus_config; memset(&i2c_bus_config, 0, sizeof(i2c_bus_config)); i2c_bus_config.clk_source = I2C_CLK_SRC_DEFAULT; diff --git a/m5stack/boards/M5STACK_Atom_EchoS3R/board_init.c b/m5stack/boards/M5STACK_Atom_EchoS3R/board_init.c index 1a7e067c..feebcbf8 100644 --- a/m5stack/boards/M5STACK_Atom_EchoS3R/board_init.c +++ b/m5stack/boards/M5STACK_Atom_EchoS3R/board_init.c @@ -50,6 +50,7 @@ static i2s_keep_t *i2s_keep[I2S_MAX_KEEP]; #ifdef USE_IDF_I2C_MASTER static i2c_master_bus_handle_t i2c_bus_handle; +static bool first_i2c_init = false; #endif static int ut_i2c_init(uint8_t port); @@ -169,14 +170,18 @@ int board_codec_volume_get(void *hd, int *vol) static int ut_i2c_init(uint8_t port) { #ifdef USE_IDF_I2C_MASTER - i2c_master_bus_config_t i2c_bus_config = {0}; - i2c_bus_config.clk_source = I2C_CLK_SRC_DEFAULT; - i2c_bus_config.i2c_port = port; - i2c_bus_config.scl_io_num = AUDIO_CODEC_I2C_SCL_PIN; - i2c_bus_config.sda_io_num = AUDIO_CODEC_I2C_SDA_PIN; - i2c_bus_config.glitch_ignore_cnt = 7; - i2c_bus_config.flags.enable_internal_pullup = true; - return i2c_new_master_bus(&i2c_bus_config, &i2c_bus_handle); + if (i2c_master_get_bus_handle(port, &i2c_bus_handle) != ESP_OK) { + first_i2c_init = true; + i2c_master_bus_config_t i2c_bus_config = {0}; + i2c_bus_config.clk_source = I2C_CLK_SRC_DEFAULT; + i2c_bus_config.i2c_port = port; + i2c_bus_config.scl_io_num = AUDIO_CODEC_I2C_SCL_PIN; + i2c_bus_config.sda_io_num = AUDIO_CODEC_I2C_SDA_PIN; + i2c_bus_config.glitch_ignore_cnt = 7; + i2c_bus_config.flags.enable_internal_pullup = true; + return i2c_new_master_bus(&i2c_bus_config, &i2c_bus_handle); + } + return ESP_OK; #else i2c_config_t i2c_cfg = { .mode = I2C_MODE_MASTER, @@ -198,7 +203,7 @@ static int ut_i2c_init(uint8_t port) static int ut_i2c_deinit(uint8_t port) { #ifdef USE_IDF_I2C_MASTER - if (i2c_bus_handle) { + if (i2c_bus_handle && first_i2c_init) { i2c_del_master_bus(i2c_bus_handle); } i2c_bus_handle = NULL; diff --git a/m5stack/boards/M5STACK_Atom_EchoS3R/sdkconfig.board b/m5stack/boards/M5STACK_Atom_EchoS3R/sdkconfig.board index db25fae5..eb8dd1fa 100644 --- a/m5stack/boards/M5STACK_Atom_EchoS3R/sdkconfig.board +++ b/m5stack/boards/M5STACK_Atom_EchoS3R/sdkconfig.board @@ -2,9 +2,9 @@ # # SPDX-License-Identifier: MIT -CONFIG_FLASHMODE_DIO=y # QIO mode has some problem when mount fs -CONFIG_ESPTOOLPY_FLASHMODE_DIO=y -CONFIG_ESPTOOLPY_FLASHMODE="dio" +CONFIG_FLASHMODE_QIO=y +CONFIG_ESPTOOLPY_FLASHMODE_QIO=y +CONFIG_ESPTOOLPY_FLASHMODE="qio" CONFIG_ESPTOOLPY_FLASHFREQ_80M=y CONFIG_ESPTOOLPY_FLASHSIZE_DETECT=y CONFIG_ESPTOOLPY_AFTER_NORESET=y @@ -18,6 +18,7 @@ CONFIG_TINYUSB_DESC_CDC_STRING="M5Stack Atom EchoS3R(UiFlow2)" CONFIG_ATOM_ECHOS3R=y CONFIG_CODEC_ES8311_SUPPORT=y CONFIG_FREERTOS_ENABLE_BACKWARD_COMPATIBILITY=y +CONFIG_CODEC_I2C_BACKWARD_COMPATIBLE=n # SSL CONFIG_MBEDTLS_AES_USE_INTERRUPT=n diff --git a/m5stack/boards/M5STACK_CoreS3/board_init.c b/m5stack/boards/M5STACK_CoreS3/board_init.c index 8dfd76de..1b09494d 100644 --- a/m5stack/boards/M5STACK_CoreS3/board_init.c +++ b/m5stack/boards/M5STACK_CoreS3/board_init.c @@ -59,6 +59,7 @@ static i2s_keep_t *i2s_keep[I2S_MAX_KEEP]; #ifdef USE_IDF_I2C_MASTER static i2c_master_bus_handle_t i2c_bus_handle; +static bool first_i2c_init = false; #endif static int ut_i2c_init(uint8_t port); @@ -180,14 +181,18 @@ int board_codec_volume_get(void *hd, int *vol) static int ut_i2c_init(uint8_t port) { #ifdef USE_IDF_I2C_MASTER - i2c_master_bus_config_t i2c_bus_config = {0}; - i2c_bus_config.clk_source = I2C_CLK_SRC_DEFAULT; - i2c_bus_config.i2c_port = port; - i2c_bus_config.scl_io_num = AUDIO_CODEC_I2C_SCL_PIN; - i2c_bus_config.sda_io_num = AUDIO_CODEC_I2C_SDA_PIN; - i2c_bus_config.glitch_ignore_cnt = 7; - i2c_bus_config.flags.enable_internal_pullup = true; - return i2c_new_master_bus(&i2c_bus_config, &i2c_bus_handle); + if (i2c_master_get_bus_handle(port, &i2c_bus_handle) != ESP_OK) { + first_i2c_init = true; + i2c_master_bus_config_t i2c_bus_config = {0}; + i2c_bus_config.clk_source = I2C_CLK_SRC_DEFAULT; + i2c_bus_config.i2c_port = port; + i2c_bus_config.scl_io_num = AUDIO_CODEC_I2C_SCL_PIN; + i2c_bus_config.sda_io_num = AUDIO_CODEC_I2C_SDA_PIN; + i2c_bus_config.glitch_ignore_cnt = 7; + i2c_bus_config.flags.enable_internal_pullup = true; + return i2c_new_master_bus(&i2c_bus_config, &i2c_bus_handle); + } + return ESP_OK; #else i2c_config_t i2c_cfg = { .mode = I2C_MODE_MASTER, @@ -208,7 +213,7 @@ static int ut_i2c_init(uint8_t port) static int ut_i2c_deinit(uint8_t port) { #ifdef USE_IDF_I2C_MASTER - if (i2c_bus_handle) { + if (i2c_bus_handle && first_i2c_init) { i2c_del_master_bus(i2c_bus_handle); } i2c_bus_handle = NULL; diff --git a/m5stack/boards/M5STACK_CoreS3/mpconfigboard.h b/m5stack/boards/M5STACK_CoreS3/mpconfigboard.h index a170d145..bc9173ab 100644 --- a/m5stack/boards/M5STACK_CoreS3/mpconfigboard.h +++ b/m5stack/boards/M5STACK_CoreS3/mpconfigboard.h @@ -23,5 +23,7 @@ #define MICROPY_HW_ENABLE_USB_RUNTIME_DEVICE (1) // Support machine.USBDevice #endif +// #define MICROPY_PY_LVGL_BENCHMARK (1) + // If not enable LVGL, ignore this... #include "./../mpconfiglvgl.h" diff --git a/m5stack/boards/M5STACK_CoreS3/sdkconfig.board b/m5stack/boards/M5STACK_CoreS3/sdkconfig.board index f3b83fb2..aac1c6bc 100644 --- a/m5stack/boards/M5STACK_CoreS3/sdkconfig.board +++ b/m5stack/boards/M5STACK_CoreS3/sdkconfig.board @@ -17,6 +17,7 @@ CONFIG_TINYUSB_DESC_CDC_STRING="M5Stack CoreS3(UiFlow2)" CONFIG_CORES3=y CONFIG_CODEC_AW88298_SUPPORT=y CONFIG_FREERTOS_ENABLE_BACKWARD_COMPATIBILITY=y +CONFIG_CODEC_I2C_BACKWARD_COMPATIBLE=n CONFIG_FREERTOS_PLACE_SNAPSHOT_FUNS_INTO_FLASH=y CONFIG_ESP_TLS_INSECURE=y diff --git a/m5stack/boards/M5STACK_Tab5/board_init.c b/m5stack/boards/M5STACK_Tab5/board_init.c index fe258cfc..568096ae 100644 --- a/m5stack/boards/M5STACK_Tab5/board_init.c +++ b/m5stack/boards/M5STACK_Tab5/board_init.c @@ -33,6 +33,13 @@ static char *TAG = "tab5"; static void *audio_hal = NULL; +#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_30 +#define AUDIO_I2S_GPIO_WS GPIO_NUM_29 +#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_27 +#define AUDIO_I2S_GPIO_DIN GPIO_NUM_28 +#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_26 +#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_32 +#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_31 #if USE_IDF5 @@ -50,24 +57,27 @@ static i2s_keep_t *i2s_keep[I2S_MAX_KEEP]; #ifdef USE_IDF_I2C_MASTER static i2c_master_bus_handle_t i2c_bus_handle; +static bool first_i2c_init = false; #endif static int ut_i2c_init(uint8_t port); static int ut_i2c_deinit(uint8_t port); +static void amp_control(bool on); static int ut_i2s_init(uint8_t port); static int ut_i2s_deinit(uint8_t port); + esp_err_t get_i2s_pins(int port, board_i2s_pin_t *i2s_config) { ESP_LOGI(TAG, "get_i2s_pins !!!"); AUDIO_NULL_CHECK(TAG, i2s_config, return ESP_FAIL); if (port == 1) { - i2s_config->bck_io_num = GPIO_NUM_27; - i2s_config->ws_io_num = GPIO_NUM_29; - i2s_config->data_out_num = GPIO_NUM_26; - i2s_config->data_in_num = GPIO_NUM_28; - i2s_config->mck_io_num = GPIO_NUM_30; + i2s_config->bck_io_num = AUDIO_I2S_GPIO_BCLK; + i2s_config->ws_io_num = AUDIO_I2S_GPIO_WS; + i2s_config->data_out_num = AUDIO_I2S_GPIO_DOUT; + i2s_config->data_in_num = AUDIO_I2S_GPIO_DIN; + i2s_config->mck_io_num = AUDIO_I2S_GPIO_MCLK; } else { memset(i2s_config, -1, sizeof(board_i2s_pin_t)); ESP_LOGE(TAG, "I2S PORT %d is not supported, please use I2S PORT 0", port); @@ -84,13 +94,16 @@ void * board_codec_init(void) ESP_LOGI(TAG, "init"); int ret = ut_i2c_init(1); - ret |= ut_i2s_init(1); + ret |= ut_i2s_init(0); + + // amp control + amp_control(true); audio_codec_i2s_cfg_t i2s_cfg = { - .port = 1, + .port = 0, #if USE_IDF5 - .rx_handle = i2s_keep[1]->rx_handle, - .tx_handle = i2s_keep[1]->tx_handle, + .rx_handle = i2s_keep[0]->rx_handle, + .tx_handle = i2s_keep[0]->tx_handle, #endif }; const audio_codec_data_if_t *data_if = audio_codec_new_i2s_data(&i2s_cfg); @@ -155,7 +168,7 @@ void * board_codec_init(void) ret = esp_codec_dev_open(record_dev, &fs); // i2s_stream_init 会实例化 i2s。初始化 codec 之后,需要将 i2s 释放。 - ut_i2s_deinit(1); + ut_i2s_deinit(0); return audio_hal; } @@ -178,14 +191,18 @@ int board_codec_volume_get(void *hd, int *vol) static int ut_i2c_init(uint8_t port) { #ifdef USE_IDF_I2C_MASTER - i2c_master_bus_config_t i2c_bus_config = {0}; - i2c_bus_config.clk_source = I2C_CLK_SRC_DEFAULT; - i2c_bus_config.i2c_port = port; - i2c_bus_config.scl_io_num = 31; - i2c_bus_config.sda_io_num = 32; - i2c_bus_config.glitch_ignore_cnt = 7; - i2c_bus_config.flags.enable_internal_pullup = true; - return i2c_new_master_bus(&i2c_bus_config, &i2c_bus_handle); + if (i2c_master_get_bus_handle(port, &i2c_bus_handle) != ESP_OK) { + first_i2c_init = true; + i2c_master_bus_config_t i2c_bus_config = {0}; + i2c_bus_config.clk_source = I2C_CLK_SRC_DEFAULT; + i2c_bus_config.i2c_port = port; + i2c_bus_config.scl_io_num = AUDIO_CODEC_I2C_SCL_PIN; + i2c_bus_config.sda_io_num = AUDIO_CODEC_I2C_SDA_PIN; + i2c_bus_config.glitch_ignore_cnt = 7; + i2c_bus_config.flags.enable_internal_pullup = true; + return i2c_new_master_bus(&i2c_bus_config, &i2c_bus_handle); + } + return ESP_OK; #else i2c_config_t i2c_cfg = { .mode = I2C_MODE_MASTER, @@ -193,8 +210,8 @@ static int ut_i2c_init(uint8_t port) .scl_pullup_en = GPIO_PULLUP_ENABLE, .master.clk_speed = 100000, }; - i2c_cfg.sda_io_num = 31; - i2c_cfg.scl_io_num = 32; + i2c_cfg.sda_io_num = AUDIO_CODEC_I2C_SDA_PIN; + i2c_cfg.scl_io_num = AUDIO_CODEC_I2C_SCL_PIN; esp_err_t ret = i2c_param_config(port, &i2c_cfg); if (ret != ESP_OK) { return -1; @@ -207,7 +224,7 @@ static int ut_i2c_init(uint8_t port) static int ut_i2c_deinit(uint8_t port) { #ifdef USE_IDF_I2C_MASTER - if (i2c_bus_handle) { + if (i2c_bus_handle && first_i2c_init) { i2c_del_master_bus(i2c_bus_handle); } i2c_bus_handle = NULL; @@ -260,7 +277,7 @@ static int ut_i2s_init(uint8_t port) if (i2s_keep[port] == NULL) { return -1; } -#if SOC_I2S_SUPPORTS_TDM +#if SOC_I2S_SUPPORTS_TDM i2s_tdm_slot_mask_t slot_mask = I2S_TDM_SLOT0 | I2S_TDM_SLOT1 | I2S_TDM_SLOT2 | I2S_TDM_SLOT3; i2s_tdm_config_t tdm_cfg = { .slot_cfg = I2S_TDM_PHILIPS_SLOT_DEFAULT_CONFIG(16, I2S_SLOT_MODE_STEREO, slot_mask), @@ -279,21 +296,22 @@ static int ut_i2s_init(uint8_t port) if (i2s_out_mode == I2S_COMM_MODE_STD) { ret = i2s_channel_init_std_mode(i2s_keep[port]->tx_handle, &std_cfg); } -#if SOC_I2S_SUPPORTS_TDM +#if SOC_I2S_SUPPORTS_TDM else if (i2s_out_mode == I2S_COMM_MODE_TDM) { ret = i2s_channel_init_tdm_mode(i2s_keep[port]->tx_handle, &tdm_cfg); } #endif if (i2s_in_mode == I2S_COMM_MODE_STD) { ret = i2s_channel_init_std_mode(i2s_keep[port]->rx_handle, &std_cfg); - } -#if SOC_I2S_SUPPORTS_TDM + } +#if SOC_I2S_SUPPORTS_TDM else if (i2s_in_mode == I2S_COMM_MODE_TDM) { ret = i2s_channel_init_tdm_mode(i2s_keep[port]->rx_handle, &tdm_cfg); } #endif // For tx master using duplex mode i2s_channel_enable(i2s_keep[port]->tx_handle); + i2s_channel_enable(i2s_keep[port]->rx_handle); #else i2s_config_t i2s_config = { .mode = (i2s_mode_t) (I2S_MODE_TX | I2S_MODE_RX | I2S_MODE_MASTER), @@ -342,3 +360,63 @@ static int ut_i2s_deinit(uint8_t port) #endif return 0; } + +static void amp_control(bool on) +{ +#ifdef USE_IDF_I2C_MASTER + i2c_device_config_t i2c_dev_conf = { + .dev_addr_length = I2C_ADDR_BIT_LEN_7, + .scl_speed_hz = 400000, + .device_address = 0x43, + }; + i2c_master_dev_handle_t dev_handle; + if (i2c_master_bus_add_device(i2c_bus_handle, &i2c_dev_conf, &dev_handle) != ESP_OK) { + ESP_LOGE(TAG, "amp_control i2c_master_bus_add_device failed"); + return ; + } + + uint8_t reg_addr = 0x00; + uint8_t value = 0; + uint8_t write_buf[2] = { 0 }; + + // PP + reg_addr = 0x07; + i2c_master_transmit_receive(dev_handle, ®_addr, 1, &value, 1, -1); + value &= ~0b00000010; + write_buf[0] = reg_addr; + write_buf[1] = value; + i2c_master_transmit(dev_handle, write_buf, sizeof(write_buf), -1); + + // PULLUP + reg_addr = 0x0D; + i2c_master_transmit_receive(dev_handle, ®_addr, 1, &value, 1, -1); + value |= 0b00000010; + write_buf[0] = reg_addr; + write_buf[1] = value; + i2c_master_transmit(dev_handle, write_buf, sizeof(write_buf), -1); + + // DIR + reg_addr = 0x03; + i2c_master_transmit_receive(dev_handle, ®_addr, 1, &value, 1, -1); + value |= 0b00000010; + write_buf[0] = reg_addr; + write_buf[1] = value; + i2c_master_transmit(dev_handle, write_buf, sizeof(write_buf), -1); + + // OUTPUT + reg_addr = 0x05; + value = 0; + i2c_master_transmit_receive(dev_handle, ®_addr, 1, &value, 1, -1); + if (on) { + value |= 0b00000010; + } else { + value &= ~0b00000010; + } + write_buf[0] = reg_addr; + write_buf[1] = value; + i2c_master_transmit(dev_handle, write_buf, sizeof(write_buf), -1); + i2c_master_bus_rm_device(dev_handle); +#else + #error "Not implemented" +#endif +} \ No newline at end of file diff --git a/m5stack/boards/sdkconfig.spiram b/m5stack/boards/sdkconfig.spiram index 35fe3c67..1161b42d 100644 --- a/m5stack/boards/sdkconfig.spiram +++ b/m5stack/boards/sdkconfig.spiram @@ -4,6 +4,8 @@ CONFIG_SPIRAM=y CONFIG_SPIRAM_CACHE_WORKAROUND=y CONFIG_SPIRAM_IGNORE_NOTFOUND=y CONFIG_SPIRAM_USE_MALLOC=y +CONFIG_SPIRAM_SPEED_80M=y +CONFIG_SPIRAM_SPEED=80 # This is the threshold for preferring small allocations from internal memory # first, before failing over to PSRAM. diff --git a/m5stack/components/esp32-camera b/m5stack/components/esp32-camera index dba8da98..b817e9b8 160000 --- a/m5stack/components/esp32-camera +++ b/m5stack/components/esp32-camera @@ -1 +1 @@ -Subproject commit dba8da9898928d9808d57a0b0cdcde9f130ed8fe +Subproject commit b817e9b81041980608d1a3d3e0b08d86e3666382 diff --git a/m5stack/machine_i2c.c b/m5stack/machine_i2c.c index 3979a8b3..563aea92 100644 --- a/m5stack/machine_i2c.c +++ b/m5stack/machine_i2c.c @@ -80,7 +80,7 @@ static void machine_hw_i2c_init(machine_hw_i2c_obj_t *self, bool first_init) { // Start of modification section, by M5Stack i2c_master_bus_handle_t bus_handle; - if (i2c_master_get_bus_handle(self->port, &self->bus_handle) == ESP_ERR_INVALID_STATE) { + if (i2c_master_get_bus_handle(self->port, &self->bus_handle) != ESP_OK) { i2c_master_bus_config_t bus_cfg = { .i2c_port = self->port, .scl_io_num = self->scl, diff --git a/m5stack/patches/5002-Add-software-i2c-support.patch b/m5stack/patches/5002-Add-software-i2c-support.patch new file mode 100644 index 00000000..86ffea74 --- /dev/null +++ b/m5stack/patches/5002-Add-software-i2c-support.patch @@ -0,0 +1,866 @@ +Index: esp32-camera/Kconfig +=================================================================== +--- esp32-camera.orig/Kconfig ++++ esp32-camera/Kconfig +@@ -163,6 +163,10 @@ menu "Camera configuration" + + endchoice + ++ config SCCB_SOFTWARE_SUPPORT ++ bool "Enable software I2C for SCCB" ++ default n ++ + config SCCB_CLK_FREQ + int "SCCB clk frequency" + default 100000 +Index: esp32-camera/driver/sccb.c +=================================================================== +--- esp32-camera.orig/driver/sccb.c ++++ esp32-camera/driver/sccb.c +@@ -47,8 +47,162 @@ const int SCCB_I2C_PORT_DEFAULT = 0; + static int sccb_i2c_port; + static bool sccb_owns_i2c_port; + ++#if CONFIG_SCCB_SOFTWARE_SUPPORT ++// ========================================================================================= ++// software sccb implement ++#include "driver/gpio.h" ++#include "esp_err.h" ++#include "esp_check.h" ++#include "esp_timer.h" ++ ++typedef struct { ++ int pin_scl; ++ int pin_sda; ++ uint32_t time_delay_us; ++} soft_sccb_config_t; ++ ++static soft_sccb_config_t g_soft_sccb_config; ++ ++static esp_err_t soft_bus_init(int pin_sda, int pin_scl) ++{ ++ ESP_LOGI(TAG, "soft bus init"); ++ ++ gpio_reset_pin(g_soft_sccb_config.pin_scl); ++ gpio_set_direction(g_soft_sccb_config.pin_scl, GPIO_MODE_OUTPUT); ++ gpio_set_pull_mode(g_soft_sccb_config.pin_scl, GPIO_PULLUP_ONLY); ++ gpio_reset_pin(g_soft_sccb_config.pin_sda); ++ gpio_set_direction(g_soft_sccb_config.pin_sda, GPIO_MODE_OUTPUT); ++ gpio_set_pull_mode(g_soft_sccb_config.pin_sda, GPIO_PULLUP_ONLY); ++ // 空闲状态,两线均为高电平 ++ gpio_set_level(g_soft_sccb_config.pin_scl, 1); ++ gpio_set_level(g_soft_sccb_config.pin_sda, 1); ++ g_soft_sccb_config.time_delay_us = (uint32_t)((1e6f / 100000) / 2.0f + 0.5f); ++ return ESP_OK; ++} ++ ++static esp_err_t soft_bus_start() ++{ ++ gpio_set_direction(g_soft_sccb_config.pin_sda, GPIO_MODE_OUTPUT); ++ gpio_set_pull_mode(g_soft_sccb_config.pin_sda, GPIO_PULLUP_ONLY); ++ // SDA 在 SCL 为高时下降,表示 START ++ ESP_RETURN_ON_ERROR(gpio_set_level(g_soft_sccb_config.pin_sda, 1), TAG, "Failed to set SDA low"); ++ ESP_RETURN_ON_ERROR(gpio_set_level(g_soft_sccb_config.pin_scl, 1), TAG, "Failed to set SCL high"); ++ esp_rom_delay_us(g_soft_sccb_config.time_delay_us); ++ ESP_RETURN_ON_ERROR(gpio_set_level(g_soft_sccb_config.pin_sda, 0), TAG, "Failed to set SDA high"); ++ ++ return ESP_OK; ++} ++ ++static esp_err_t soft_bus_stop() ++{ ++ gpio_set_direction(g_soft_sccb_config.pin_sda, GPIO_MODE_OUTPUT); ++ gpio_set_pull_mode(g_soft_sccb_config.pin_sda, GPIO_PULLUP_ONLY); ++ // SDA 在 SCL 为高时上升,表示 STOP ++ ESP_RETURN_ON_ERROR(gpio_set_level(g_soft_sccb_config.pin_sda, 0), TAG, "Failed to set SDA low"); ++ esp_rom_delay_us(2); ++ ESP_RETURN_ON_ERROR(gpio_set_level(g_soft_sccb_config.pin_scl, 1), TAG, "Failed to set SCL high"); ++ esp_rom_delay_us(g_soft_sccb_config.time_delay_us); ++ ESP_RETURN_ON_ERROR(gpio_set_level(g_soft_sccb_config.pin_sda, 1), TAG, "Failed to set SDA high"); ++ esp_rom_delay_us(g_soft_sccb_config.time_delay_us); ++ ++ return ESP_OK; ++} ++ ++static esp_err_t soft_bus_write_byte(uint8_t byte) ++{ ++ gpio_set_direction(g_soft_sccb_config.pin_sda, GPIO_MODE_OUTPUT); ++ gpio_set_pull_mode(g_soft_sccb_config.pin_sda, GPIO_PULLUP_ONLY); ++ for (int i = 0; i < 8; i++) { ++ // SDA 在 SCL 为低时设置数据,在 SCL 为高时被采样 ++ ESP_RETURN_ON_ERROR(gpio_set_level(g_soft_sccb_config.pin_scl, 0), TAG, "Failed to set SCL low"); ++ esp_rom_delay_us(2); ++ if (byte & 0x80) { ++ ESP_RETURN_ON_ERROR(gpio_set_level(g_soft_sccb_config.pin_sda, 1), TAG, "Failed to set SDA high"); ++ } else { ++ ESP_RETURN_ON_ERROR(gpio_set_level(g_soft_sccb_config.pin_sda, 0), TAG, "Failed to set SDA low"); ++ } ++ esp_rom_delay_us(g_soft_sccb_config.time_delay_us); ++ ESP_RETURN_ON_ERROR(gpio_set_level(g_soft_sccb_config.pin_scl, 1), TAG, "Failed to set SCL high"); ++ esp_rom_delay_us(g_soft_sccb_config.time_delay_us); ++ byte <<= 1; ++ } ++ ++ return ESP_OK; ++} ++ ++static esp_err_t soft_bus_read_byte(uint8_t *byte, bool ack) ++{ ++ esp_err_t ret = ESP_OK; ++ uint8_t value = 0; ++ ++ gpio_set_direction(g_soft_sccb_config.pin_sda, GPIO_MODE_INPUT); ++ gpio_set_pull_mode(g_soft_sccb_config.pin_sda, GPIO_PULLUP_ONLY); ++ ++ ESP_RETURN_ON_ERROR(gpio_set_level(g_soft_sccb_config.pin_sda, 1), TAG, "Failed to release SDA"); /*!< First release SDA */ ++ for (int i = 0; i < 8; i++) { ++ value <<= 1; ++ ESP_RETURN_ON_ERROR(gpio_set_level(g_soft_sccb_config.pin_scl, 1), TAG, "Failed to set SCL high"); ++ esp_rom_delay_us(g_soft_sccb_config.time_delay_us); ++ ++ if (gpio_get_level(g_soft_sccb_config.pin_sda)) { ++ value++; ++ } ++ ESP_RETURN_ON_ERROR(gpio_set_level(g_soft_sccb_config.pin_scl, 0), TAG, "Failed to set SCL low"); ++ esp_rom_delay_us(g_soft_sccb_config.time_delay_us); ++ } ++ ++ *byte = value; ++ ++ // 在SCL低电平期间设置ACK状态 ++ gpio_set_direction(g_soft_sccb_config.pin_sda, GPIO_MODE_OUTPUT); ++ gpio_set_pull_mode(g_soft_sccb_config.pin_sda, GPIO_PULLUP_ONLY); ++ ESP_RETURN_ON_ERROR(gpio_set_level(g_soft_sccb_config.pin_sda, ack ? 0 : 1), TAG, "SDA level fail"); ++ esp_rom_delay_us(g_soft_sccb_config.time_delay_us); ++ ++ // 生成ACK时钟脉冲 ++ ESP_RETURN_ON_ERROR(gpio_set_level(g_soft_sccb_config.pin_scl, 1), TAG, "SCL high fail"); ++ esp_rom_delay_us(g_soft_sccb_config.time_delay_us * 2); ++ ESP_RETURN_ON_ERROR(gpio_set_level(g_soft_sccb_config.pin_scl, 0), TAG, "SCL low fail"); ++ esp_rom_delay_us(g_soft_sccb_config.time_delay_us); ++ ++ return ret; ++} ++ ++static esp_err_t soft_bus_wait_ack() ++{ ++ // 第 9 个时钟周期,接收方拉低 SDA 表示 ACK ++ ESP_RETURN_ON_ERROR(gpio_set_level(g_soft_sccb_config.pin_scl, 0), TAG, "Failed to set SCL high"); ++ gpio_set_direction(g_soft_sccb_config.pin_sda, GPIO_MODE_INPUT); ++ gpio_set_pull_mode(g_soft_sccb_config.pin_sda, GPIO_PULLUP_ONLY); ++ esp_rom_delay_us(g_soft_sccb_config.time_delay_us); ++ ++ ESP_RETURN_ON_ERROR(gpio_set_level(g_soft_sccb_config.pin_scl, 1), TAG, "Failed to set SCL high"); ++ esp_rom_delay_us(g_soft_sccb_config.time_delay_us); ++ bool ack = ESP_ERR_NOT_FOUND; ++ if (!gpio_get_level(g_soft_sccb_config.pin_sda)) { ++ ack = ESP_OK; ++ } ++ ++ ESP_RETURN_ON_ERROR(gpio_set_level(g_soft_sccb_config.pin_scl, 0), TAG, "Failed to set SCL high"); ++ esp_rom_delay_us(g_soft_sccb_config.time_delay_us); ++ ++ return ack; ++} ++ ++#endif ++// ========================================================================================= ++ ++ + int SCCB_Init(int pin_sda, int pin_scl) + { ++#if CONFIG_SCCB_SOFTWARE_SUPPORT ++ ESP_LOGI(TAG, "Use software sccb, pin_sda %d pin_scl %d", pin_sda, pin_scl); ++ g_soft_sccb_config.pin_scl = pin_scl; ++ g_soft_sccb_config.pin_sda = pin_sda; ++ g_soft_sccb_config.time_delay_us = (uint32_t)((1e6f / 100000) / 2.0f + 0.5f); // 100kHz ++ soft_bus_init(g_soft_sccb_config.pin_sda, g_soft_sccb_config.pin_scl); ++ return ESP_OK; ++#else + ESP_LOGI(TAG, "pin_sda %d pin_scl %d", pin_sda, pin_scl); + i2c_config_t conf; + esp_err_t ret; +@@ -71,9 +225,13 @@ int SCCB_Init(int pin_sda, int pin_scl) + } + + return i2c_driver_install(sccb_i2c_port, conf.mode, 0, 0, 0); ++#endif + } + + int SCCB_Use_Port(int i2c_num) { // sccb use an already initialized I2C port ++#if CONFIG_SCCB_SOFTWARE_SUPPORT ++ return ESP_OK; ++#else + if (sccb_owns_i2c_port) { + SCCB_Deinit(); + } +@@ -82,19 +240,33 @@ int SCCB_Use_Port(int i2c_num) { // sccb + } + sccb_i2c_port = i2c_num; + return ESP_OK; ++#endif + } + + int SCCB_Deinit(void) + { ++#if CONFIG_SCCB_SOFTWARE_SUPPORT ++ gpio_reset_pin(g_soft_sccb_config.pin_scl); ++ gpio_reset_pin(g_soft_sccb_config.pin_sda); ++ return ESP_OK; ++#else + if (!sccb_owns_i2c_port) { + return ESP_OK; + } + sccb_owns_i2c_port = false; + return i2c_driver_delete(sccb_i2c_port); ++#endif + } + + int SCCB_Probe(uint8_t slv_addr) + { ++#if CONFIG_SCCB_SOFTWARE_SUPPORT ++ soft_bus_start(); ++ soft_bus_write_byte((slv_addr << 1) | WRITE_BIT); ++ esp_err_t ret = soft_bus_wait_ack(); ++ soft_bus_stop(); ++ return ret; ++#else + i2c_cmd_handle_t cmd = i2c_cmd_link_create(); + i2c_master_start(cmd); + i2c_master_write_byte(cmd, ( slv_addr << 1 ) | WRITE_BIT, ACK_CHECK_EN); +@@ -102,12 +274,28 @@ int SCCB_Probe(uint8_t slv_addr) + esp_err_t ret = i2c_master_cmd_begin(sccb_i2c_port, cmd, 1000 / portTICK_RATE_MS); + i2c_cmd_link_delete(cmd); + return ret; ++#endif + } + + uint8_t SCCB_Read(uint8_t slv_addr, uint8_t reg) + { + uint8_t data=0; + esp_err_t ret = ESP_FAIL; ++ ++#if CONFIG_SCCB_SOFTWARE_SUPPORT ++ soft_bus_start(); ++ soft_bus_write_byte((slv_addr << 1) | WRITE_BIT); ++ ret = soft_bus_wait_ack(); ++ soft_bus_write_byte(reg); ++ ret = soft_bus_wait_ack(); ++ // soft_bus_stop(); ++ if (ret != ESP_OK) return -1; ++ soft_bus_start(); ++ soft_bus_write_byte((slv_addr << 1) | READ_BIT); ++ ret = soft_bus_wait_ack(); ++ soft_bus_read_byte(&data, false); ++ soft_bus_stop(); ++#else + i2c_cmd_handle_t cmd = i2c_cmd_link_create(); + i2c_master_start(cmd); + i2c_master_write_byte(cmd, ( slv_addr << 1 ) | WRITE_BIT, ACK_CHECK_EN); +@@ -123,6 +311,7 @@ uint8_t SCCB_Read(uint8_t slv_addr, uint + i2c_master_stop(cmd); + ret = i2c_master_cmd_begin(sccb_i2c_port, cmd, 1000 / portTICK_RATE_MS); + i2c_cmd_link_delete(cmd); ++#endif + if(ret != ESP_OK) { + ESP_LOGE(TAG, "SCCB_Read Failed addr:0x%02x, reg:0x%02x, data:0x%02x, ret:%d", slv_addr, reg, data, ret); + } +@@ -132,6 +321,19 @@ uint8_t SCCB_Read(uint8_t slv_addr, uint + int SCCB_Write(uint8_t slv_addr, uint8_t reg, uint8_t data) + { + esp_err_t ret = ESP_FAIL; ++ ++#if CONFIG_SCCB_SOFTWARE_SUPPORT ++ soft_bus_start(); ++ soft_bus_write_byte((slv_addr << 1) | WRITE_BIT); ++ ret = soft_bus_wait_ack(); ++ soft_bus_write_byte(reg); ++ ret = soft_bus_wait_ack(); ++ soft_bus_write_byte(data); ++ ret = soft_bus_wait_ack(); ++ soft_bus_stop(); ++ esp_rom_delay_us(g_soft_sccb_config.time_delay_us); ++ esp_rom_delay_us(g_soft_sccb_config.time_delay_us); ++#else + i2c_cmd_handle_t cmd = i2c_cmd_link_create(); + i2c_master_start(cmd); + i2c_master_write_byte(cmd, ( slv_addr << 1 ) | WRITE_BIT, ACK_CHECK_EN); +@@ -140,6 +342,8 @@ int SCCB_Write(uint8_t slv_addr, uint8_t + i2c_master_stop(cmd); + ret = i2c_master_cmd_begin(sccb_i2c_port, cmd, 1000 / portTICK_RATE_MS); + i2c_cmd_link_delete(cmd); ++#endif ++ + if(ret != ESP_OK) { + ESP_LOGE(TAG, "SCCB_Write Failed addr:0x%02x, reg:0x%02x, data:0x%02x, ret:%d", slv_addr, reg, data, ret); + } +@@ -152,6 +356,22 @@ uint8_t SCCB_Read16(uint8_t slv_addr, ui + esp_err_t ret = ESP_FAIL; + uint16_t reg_htons = LITTLETOBIG(reg); + uint8_t *reg_u8 = (uint8_t *)®_htons; ++#if CONFIG_SCCB_SOFTWARE_SUPPORT ++ soft_bus_start(); ++ soft_bus_write_byte((slv_addr << 1) | WRITE_BIT); ++ ret = soft_bus_wait_ack(); ++ soft_bus_write_byte(reg_u8[0]); ++ ret = soft_bus_wait_ack(); ++ soft_bus_write_byte(reg_u8[1]); ++ ret = soft_bus_wait_ack(); ++ soft_bus_stop(); ++ if (ret != ESP_OK) return -1; ++ soft_bus_start(); ++ soft_bus_write_byte((slv_addr << 1) | READ_BIT); ++ ret = soft_bus_wait_ack(); ++ soft_bus_read_byte(&data, true); ++ soft_bus_stop(); ++#else + i2c_cmd_handle_t cmd = i2c_cmd_link_create(); + i2c_master_start(cmd); + i2c_master_write_byte(cmd, ( slv_addr << 1 ) | WRITE_BIT, ACK_CHECK_EN); +@@ -168,6 +388,7 @@ uint8_t SCCB_Read16(uint8_t slv_addr, ui + i2c_master_stop(cmd); + ret = i2c_master_cmd_begin(sccb_i2c_port, cmd, 1000 / portTICK_RATE_MS); + i2c_cmd_link_delete(cmd); ++#endif + if(ret != ESP_OK) { + ESP_LOGE(TAG, "W [%04x]=%02x fail\n", reg, data); + } +@@ -180,6 +401,18 @@ int SCCB_Write16(uint8_t slv_addr, uint1 + esp_err_t ret = ESP_FAIL; + uint16_t reg_htons = LITTLETOBIG(reg); + uint8_t *reg_u8 = (uint8_t *)®_htons; ++#if CONFIG_SCCB_SOFTWARE_SUPPORT ++ soft_bus_start(); ++ soft_bus_write_byte((slv_addr << 1) | WRITE_BIT); ++ ret = soft_bus_wait_ack(); ++ soft_bus_write_byte(reg_u8[0]); ++ ret = soft_bus_wait_ack(); ++ soft_bus_write_byte(reg_u8[1]); ++ ret = soft_bus_wait_ack(); ++ soft_bus_write_byte(data); ++ ret = soft_bus_wait_ack(); ++ soft_bus_stop(); ++#else + i2c_cmd_handle_t cmd = i2c_cmd_link_create(); + i2c_master_start(cmd); + i2c_master_write_byte(cmd, ( slv_addr << 1 ) | WRITE_BIT, ACK_CHECK_EN); +@@ -189,6 +422,7 @@ int SCCB_Write16(uint8_t slv_addr, uint1 + i2c_master_stop(cmd); + ret = i2c_master_cmd_begin(sccb_i2c_port, cmd, 1000 / portTICK_RATE_MS); + i2c_cmd_link_delete(cmd); ++#endif + if(ret != ESP_OK) { + ESP_LOGE(TAG, "W [%04x]=%02x %d fail\n", reg, data, i++); + } +@@ -202,6 +436,25 @@ uint16_t SCCB_Read_Addr16_Val16(uint8_t + esp_err_t ret = ESP_FAIL; + uint16_t reg_htons = LITTLETOBIG(reg); + uint8_t *reg_u8 = (uint8_t *)®_htons; ++ ++#if CONFIG_SCCB_SOFTWARE_SUPPORT ++ // 未测试 ++ soft_bus_start(); ++ soft_bus_write_byte((slv_addr << 1) | WRITE_BIT); ++ ret = soft_bus_wait_ack(); ++ soft_bus_write_byte(reg_u8[0]); ++ ret = soft_bus_wait_ack(); ++ soft_bus_write_byte(reg_u8[1]); ++ ret = soft_bus_wait_ack(); ++ soft_bus_stop(); ++ if (ret != ESP_OK) return -1; ++ soft_bus_start(); ++ soft_bus_write_byte((slv_addr << 1) | READ_BIT); ++ ret = soft_bus_wait_ack(); ++ soft_bus_read_byte(&data_u8[1], true); ++ soft_bus_read_byte(&data_u8[0], false); ++ soft_bus_stop(); ++#else + i2c_cmd_handle_t cmd = i2c_cmd_link_create(); + i2c_master_start(cmd); + i2c_master_write_byte(cmd, ( slv_addr << 1 ) | WRITE_BIT, ACK_CHECK_EN); +@@ -220,6 +473,8 @@ uint16_t SCCB_Read_Addr16_Val16(uint8_t + i2c_master_stop(cmd); + ret = i2c_master_cmd_begin(sccb_i2c_port, cmd, 1000 / portTICK_RATE_MS); + i2c_cmd_link_delete(cmd); ++#endif ++ + if(ret != ESP_OK) { + ESP_LOGE(TAG, "W [%04x]=%04x fail\n", reg, data); + } +@@ -233,6 +488,21 @@ int SCCB_Write_Addr16_Val16(uint8_t slv_ + uint8_t *reg_u8 = (uint8_t *)®_htons; + uint16_t data_htons = LITTLETOBIG(data); + uint8_t *data_u8 = (uint8_t *)&data_htons; ++#if CONFIG_SCCB_SOFTWARE_SUPPORT ++ // 未测试 ++ soft_bus_start(); ++ soft_bus_write_byte((slv_addr << 1) | WRITE_BIT); ++ ret = soft_bus_wait_ack(); ++ soft_bus_write_byte(reg_u8[0]); ++ ret = soft_bus_wait_ack(); ++ soft_bus_write_byte(reg_u8[1]); ++ ret = soft_bus_wait_ack(); ++ soft_bus_write_byte(data_u8[0]); ++ ret = soft_bus_wait_ack(); ++ soft_bus_write_byte(data_u8[1]); ++ ret = soft_bus_wait_ack(); ++ soft_bus_stop(); ++#else + i2c_cmd_handle_t cmd = i2c_cmd_link_create(); + i2c_master_start(cmd); + i2c_master_write_byte(cmd, ( slv_addr << 1 ) | WRITE_BIT, ACK_CHECK_EN); +@@ -243,6 +513,8 @@ int SCCB_Write_Addr16_Val16(uint8_t slv_ + i2c_master_stop(cmd); + ret = i2c_master_cmd_begin(sccb_i2c_port, cmd, 1000 / portTICK_RATE_MS); + i2c_cmd_link_delete(cmd); ++#endif ++ + if(ret != ESP_OK) { + ESP_LOGE(TAG, "W [%04x]=%04x fail\n", reg, data); + } +Index: esp32-camera/driver/sccb-ng.c +=================================================================== +--- esp32-camera.orig/driver/sccb-ng.c ++++ esp32-camera/driver/sccb-ng.c +@@ -60,6 +60,155 @@ static uint8_t device_count = 0; + static int sccb_i2c_port; + static bool sccb_owns_i2c_port; + ++#if CONFIG_SCCB_SOFTWARE_SUPPORT ++// ========================================================================================= ++// software sccb implement ++#include "driver/gpio.h" ++#include "esp_err.h" ++#include "esp_check.h" ++#include "esp_timer.h" ++ ++#define WRITE_BIT I2C_MASTER_WRITE /*!< I2C master write */ ++#define READ_BIT I2C_MASTER_READ /*!< I2C master read */ ++ ++typedef struct { ++ int pin_scl; ++ int pin_sda; ++ uint32_t time_delay_us; ++} soft_sccb_config_t; ++ ++static soft_sccb_config_t g_soft_sccb_config; ++ ++static esp_err_t soft_bus_init(int pin_sda, int pin_scl) ++{ ++ ESP_LOGI(TAG, "soft bus init"); ++ ++ gpio_reset_pin(g_soft_sccb_config.pin_scl); ++ gpio_set_direction(g_soft_sccb_config.pin_scl, GPIO_MODE_OUTPUT); ++ gpio_set_pull_mode(g_soft_sccb_config.pin_scl, GPIO_PULLUP_ONLY); ++ gpio_reset_pin(g_soft_sccb_config.pin_sda); ++ gpio_set_direction(g_soft_sccb_config.pin_sda, GPIO_MODE_OUTPUT); ++ gpio_set_pull_mode(g_soft_sccb_config.pin_sda, GPIO_PULLUP_ONLY); ++ // 空闲状态,两线均为高电平 ++ gpio_set_level(g_soft_sccb_config.pin_scl, 1); ++ gpio_set_level(g_soft_sccb_config.pin_sda, 1); ++ g_soft_sccb_config.time_delay_us = (uint32_t)((1e6f / 100000) / 2.0f + 0.5f); ++ return ESP_OK; ++} ++ ++static esp_err_t soft_bus_start() ++{ ++ gpio_set_direction(g_soft_sccb_config.pin_sda, GPIO_MODE_OUTPUT); ++ gpio_set_pull_mode(g_soft_sccb_config.pin_sda, GPIO_PULLUP_ONLY); ++ // SDA 在 SCL 为高时下降,表示 START ++ ESP_RETURN_ON_ERROR(gpio_set_level(g_soft_sccb_config.pin_sda, 1), TAG, "Failed to set SDA low"); ++ ESP_RETURN_ON_ERROR(gpio_set_level(g_soft_sccb_config.pin_scl, 1), TAG, "Failed to set SCL high"); ++ esp_rom_delay_us(g_soft_sccb_config.time_delay_us); ++ ESP_RETURN_ON_ERROR(gpio_set_level(g_soft_sccb_config.pin_sda, 0), TAG, "Failed to set SDA high"); ++ ++ return ESP_OK; ++} ++ ++static esp_err_t soft_bus_stop() ++{ ++ gpio_set_direction(g_soft_sccb_config.pin_sda, GPIO_MODE_OUTPUT); ++ gpio_set_pull_mode(g_soft_sccb_config.pin_sda, GPIO_PULLUP_ONLY); ++ // SDA 在 SCL 为高时上升,表示 STOP ++ ESP_RETURN_ON_ERROR(gpio_set_level(g_soft_sccb_config.pin_sda, 0), TAG, "Failed to set SDA low"); ++ esp_rom_delay_us(2); ++ ESP_RETURN_ON_ERROR(gpio_set_level(g_soft_sccb_config.pin_scl, 1), TAG, "Failed to set SCL high"); ++ esp_rom_delay_us(g_soft_sccb_config.time_delay_us); ++ ESP_RETURN_ON_ERROR(gpio_set_level(g_soft_sccb_config.pin_sda, 1), TAG, "Failed to set SDA high"); ++ esp_rom_delay_us(g_soft_sccb_config.time_delay_us); ++ ++ return ESP_OK; ++} ++ ++static esp_err_t soft_bus_write_byte(uint8_t byte) ++{ ++ gpio_set_direction(g_soft_sccb_config.pin_sda, GPIO_MODE_OUTPUT); ++ gpio_set_pull_mode(g_soft_sccb_config.pin_sda, GPIO_PULLUP_ONLY); ++ for (int i = 0; i < 8; i++) { ++ // SDA 在 SCL 为低时设置数据,在 SCL 为高时被采样 ++ ESP_RETURN_ON_ERROR(gpio_set_level(g_soft_sccb_config.pin_scl, 0), TAG, "Failed to set SCL low"); ++ esp_rom_delay_us(2); ++ if (byte & 0x80) { ++ ESP_RETURN_ON_ERROR(gpio_set_level(g_soft_sccb_config.pin_sda, 1), TAG, "Failed to set SDA high"); ++ } else { ++ ESP_RETURN_ON_ERROR(gpio_set_level(g_soft_sccb_config.pin_sda, 0), TAG, "Failed to set SDA low"); ++ } ++ esp_rom_delay_us(g_soft_sccb_config.time_delay_us); ++ ESP_RETURN_ON_ERROR(gpio_set_level(g_soft_sccb_config.pin_scl, 1), TAG, "Failed to set SCL high"); ++ esp_rom_delay_us(g_soft_sccb_config.time_delay_us); ++ byte <<= 1; ++ } ++ ++ return ESP_OK; ++} ++ ++static esp_err_t soft_bus_read_byte(uint8_t *byte, bool ack) ++{ ++ esp_err_t ret = ESP_OK; ++ uint8_t value = 0; ++ ++ gpio_set_direction(g_soft_sccb_config.pin_sda, GPIO_MODE_INPUT); ++ gpio_set_pull_mode(g_soft_sccb_config.pin_sda, GPIO_PULLUP_ONLY); ++ ++ ESP_RETURN_ON_ERROR(gpio_set_level(g_soft_sccb_config.pin_sda, 1), TAG, "Failed to release SDA"); /*!< First release SDA */ ++ for (int i = 0; i < 8; i++) { ++ value <<= 1; ++ ESP_RETURN_ON_ERROR(gpio_set_level(g_soft_sccb_config.pin_scl, 1), TAG, "Failed to set SCL high"); ++ esp_rom_delay_us(g_soft_sccb_config.time_delay_us); ++ ++ if (gpio_get_level(g_soft_sccb_config.pin_sda)) { ++ value++; ++ } ++ ESP_RETURN_ON_ERROR(gpio_set_level(g_soft_sccb_config.pin_scl, 0), TAG, "Failed to set SCL low"); ++ esp_rom_delay_us(g_soft_sccb_config.time_delay_us); ++ } ++ ++ *byte = value; ++ ++ // 在SCL低电平期间设置ACK状态 ++ gpio_set_direction(g_soft_sccb_config.pin_sda, GPIO_MODE_OUTPUT); ++ gpio_set_pull_mode(g_soft_sccb_config.pin_sda, GPIO_PULLUP_ONLY); ++ ESP_RETURN_ON_ERROR(gpio_set_level(g_soft_sccb_config.pin_sda, ack ? 0 : 1), TAG, "SDA level fail"); ++ esp_rom_delay_us(g_soft_sccb_config.time_delay_us); ++ ++ // 生成ACK时钟脉冲 ++ ESP_RETURN_ON_ERROR(gpio_set_level(g_soft_sccb_config.pin_scl, 1), TAG, "SCL high fail"); ++ esp_rom_delay_us(g_soft_sccb_config.time_delay_us * 2); ++ ESP_RETURN_ON_ERROR(gpio_set_level(g_soft_sccb_config.pin_scl, 0), TAG, "SCL low fail"); ++ esp_rom_delay_us(g_soft_sccb_config.time_delay_us); ++ ++ return ret; ++} ++ ++static esp_err_t soft_bus_wait_ack() ++{ ++ // 第 9 个时钟周期,接收方拉低 SDA 表示 ACK ++ ESP_RETURN_ON_ERROR(gpio_set_level(g_soft_sccb_config.pin_scl, 0), TAG, "Failed to set SCL high"); ++ gpio_set_direction(g_soft_sccb_config.pin_sda, GPIO_MODE_INPUT); ++ gpio_set_pull_mode(g_soft_sccb_config.pin_sda, GPIO_PULLUP_ONLY); ++ esp_rom_delay_us(g_soft_sccb_config.time_delay_us); ++ ++ ESP_RETURN_ON_ERROR(gpio_set_level(g_soft_sccb_config.pin_scl, 1), TAG, "Failed to set SCL high"); ++ esp_rom_delay_us(g_soft_sccb_config.time_delay_us); ++ bool ack = ESP_ERR_NOT_FOUND; ++ if (!gpio_get_level(g_soft_sccb_config.pin_sda)) { ++ ack = ESP_OK; ++ } ++ ++ ESP_RETURN_ON_ERROR(gpio_set_level(g_soft_sccb_config.pin_scl, 0), TAG, "Failed to set SCL high"); ++ esp_rom_delay_us(g_soft_sccb_config.time_delay_us); ++ ++ return ack; ++} ++ ++#endif ++// ========================================================================================= ++ ++ + i2c_master_dev_handle_t *get_handle_from_address(uint8_t slv_addr) + { + for (uint8_t i = 0; i < device_count; i++) +@@ -113,6 +262,14 @@ int SCCB_Install_Device(uint8_t slv_addr + + int SCCB_Init(int pin_sda, int pin_scl) + { ++#if CONFIG_SCCB_SOFTWARE_SUPPORT ++ ESP_LOGI(TAG, "Use software sccb, pin_sda %d pin_scl %d", pin_sda, pin_scl); ++ g_soft_sccb_config.pin_scl = pin_scl; ++ g_soft_sccb_config.pin_sda = pin_sda; ++ g_soft_sccb_config.time_delay_us = (uint32_t)((1e6f / 100000) / 2.0f + 0.5f); // 100kHz ++ soft_bus_init(g_soft_sccb_config.pin_sda, g_soft_sccb_config.pin_scl); ++ return ESP_OK; ++#else + ESP_LOGI(TAG, "pin_sda %d pin_scl %d", pin_sda, pin_scl); + // i2c_config_t conf; + esp_err_t ret; +@@ -139,10 +296,15 @@ int SCCB_Init(int pin_sda, int pin_scl) + } + + return ESP_OK; ++#endif + } + + int SCCB_Use_Port(int i2c_num) +-{ // sccb use an already initialized I2C port ++{ ++#if CONFIG_SCCB_SOFTWARE_SUPPORT ++ return ESP_OK; ++#else ++ // sccb use an already initialized I2C port + if (sccb_owns_i2c_port) + { + SCCB_Deinit(); +@@ -154,10 +316,16 @@ int SCCB_Use_Port(int i2c_num) + sccb_i2c_port = i2c_num; + + return ESP_OK; ++#endif + } + + int SCCB_Deinit(void) + { ++#if CONFIG_SCCB_SOFTWARE_SUPPORT ++ gpio_reset_pin(g_soft_sccb_config.pin_scl); ++ gpio_reset_pin(g_soft_sccb_config.pin_sda); ++ return ESP_OK; ++#else + esp_err_t ret; + + for (uint8_t i = 0; i < device_count; i++) +@@ -196,10 +364,18 @@ int SCCB_Deinit(void) + } + + return ESP_OK; ++#endif + } + + int SCCB_Probe(uint8_t slv_addr) + { ++#if CONFIG_SCCB_SOFTWARE_SUPPORT ++ soft_bus_start(); ++ soft_bus_write_byte((slv_addr << 1) | WRITE_BIT); ++ esp_err_t ret = soft_bus_wait_ack(); ++ soft_bus_stop(); ++ return ret; ++#else + esp_err_t ret; + i2c_master_bus_handle_t bus_handle; + +@@ -218,10 +394,35 @@ int SCCB_Probe(uint8_t slv_addr) + } + + return ret; ++#endif + } + + uint8_t SCCB_Read(uint8_t slv_addr, uint8_t reg) + { ++#if CONFIG_SCCB_SOFTWARE_SUPPORT ++ uint8_t data=0; ++ esp_err_t ret = ESP_FAIL; ++ ++ soft_bus_start(); ++ soft_bus_write_byte((slv_addr << 1) | WRITE_BIT); ++ ret = soft_bus_wait_ack(); ++ soft_bus_write_byte(reg); ++ ret = soft_bus_wait_ack(); ++ // soft_bus_stop(); ++ if (ret != ESP_OK) return -1; ++ soft_bus_start(); ++ soft_bus_write_byte((slv_addr << 1) | READ_BIT); ++ ret = soft_bus_wait_ack(); ++ soft_bus_read_byte(&data, false); ++ soft_bus_stop(); ++ ++ if (ret != ESP_OK) ++ { ++ ESP_LOGE(TAG, "SCCB_Read Failed addr:0x%02x, reg:0x%02x, data:0x%02x, ret:%d", slv_addr, reg, data, ret); ++ } ++ ++ return data; ++#else + i2c_master_dev_handle_t dev_handle = *(get_handle_from_address(slv_addr)); + + uint8_t tx_buffer[1]; +@@ -237,10 +438,25 @@ uint8_t SCCB_Read(uint8_t slv_addr, uint + } + + return rx_buffer[0]; ++#endif + } + + int SCCB_Write(uint8_t slv_addr, uint8_t reg, uint8_t data) + { ++#if CONFIG_SCCB_SOFTWARE_SUPPORT ++ esp_err_t ret = ESP_FAIL; ++ ++ soft_bus_start(); ++ soft_bus_write_byte((slv_addr << 1) | WRITE_BIT); ++ ret = soft_bus_wait_ack(); ++ soft_bus_write_byte(reg); ++ ret = soft_bus_wait_ack(); ++ soft_bus_write_byte(data); ++ ret = soft_bus_wait_ack(); ++ soft_bus_stop(); ++ esp_rom_delay_us(g_soft_sccb_config.time_delay_us); ++ esp_rom_delay_us(g_soft_sccb_config.time_delay_us); ++#else + i2c_master_dev_handle_t dev_handle = *(get_handle_from_address(slv_addr)); + + uint8_t tx_buffer[2]; +@@ -248,6 +464,7 @@ int SCCB_Write(uint8_t slv_addr, uint8_t + tx_buffer[1] = data; + + esp_err_t ret = i2c_master_transmit(dev_handle, tx_buffer, 2, TIMEOUT_MS); ++#endif + + if (ret != ESP_OK) + { +@@ -259,6 +476,34 @@ int SCCB_Write(uint8_t slv_addr, uint8_t + + uint8_t SCCB_Read16(uint8_t slv_addr, uint16_t reg) + { ++#if CONFIG_SCCB_SOFTWARE_SUPPORT ++ uint8_t data=0; ++ esp_err_t ret = ESP_FAIL; ++ uint16_t reg_htons = LITTLETOBIG(reg); ++ uint8_t *reg_u8 = (uint8_t *)®_htons; ++ ++ soft_bus_start(); ++ soft_bus_write_byte((slv_addr << 1) | WRITE_BIT); ++ ret = soft_bus_wait_ack(); ++ soft_bus_write_byte(reg_u8[0]); ++ ret = soft_bus_wait_ack(); ++ soft_bus_write_byte(reg_u8[1]); ++ ret = soft_bus_wait_ack(); ++ soft_bus_stop(); ++ if (ret != ESP_OK) return -1; ++ soft_bus_start(); ++ soft_bus_write_byte((slv_addr << 1) | READ_BIT); ++ ret = soft_bus_wait_ack(); ++ soft_bus_read_byte(&data, true); ++ soft_bus_stop(); ++ ++ if (ret != ESP_OK) ++ { ++ ESP_LOGE(TAG, "W [%04x]=%02x fail\n", reg, data); ++ } ++ ++ return data; ++#else + i2c_master_dev_handle_t dev_handle = *(get_handle_from_address(slv_addr)); + + uint8_t rx_buffer[1]; +@@ -274,10 +519,28 @@ uint8_t SCCB_Read16(uint8_t slv_addr, ui + } + + return rx_buffer[0]; ++#endif + } + + int SCCB_Write16(uint8_t slv_addr, uint16_t reg, uint8_t data) + { ++#if CONFIG_SCCB_SOFTWARE_SUPPORT ++ static uint16_t i = 0; ++ esp_err_t ret = ESP_FAIL; ++ uint16_t reg_htons = LITTLETOBIG(reg); ++ uint8_t *reg_u8 = (uint8_t *)®_htons; ++ ++ soft_bus_start(); ++ soft_bus_write_byte((slv_addr << 1) | WRITE_BIT); ++ ret = soft_bus_wait_ack(); ++ soft_bus_write_byte(reg_u8[0]); ++ ret = soft_bus_wait_ack(); ++ soft_bus_write_byte(reg_u8[1]); ++ ret = soft_bus_wait_ack(); ++ soft_bus_write_byte(data); ++ ret = soft_bus_wait_ack(); ++ soft_bus_stop(); ++#else + i2c_master_dev_handle_t dev_handle = *(get_handle_from_address(slv_addr)); + + uint8_t tx_buffer[3]; +@@ -286,7 +549,7 @@ int SCCB_Write16(uint8_t slv_addr, uint1 + tx_buffer[2] = data; + + esp_err_t ret = i2c_master_transmit(dev_handle, tx_buffer, 3, TIMEOUT_MS); +- ++#endif + if (ret != ESP_OK) + { + ESP_LOGE(TAG, "W [%04x]=%02x fail\n", reg, data); +@@ -296,6 +559,30 @@ int SCCB_Write16(uint8_t slv_addr, uint1 + + uint16_t SCCB_Read_Addr16_Val16(uint8_t slv_addr, uint16_t reg) + { ++#if CONFIG_SCCB_SOFTWARE_SUPPORT ++ uint16_t data = 0; ++ uint8_t *data_u8 = (uint8_t *)&data; ++ esp_err_t ret = ESP_FAIL; ++ uint16_t reg_htons = LITTLETOBIG(reg); ++ uint8_t *reg_u8 = (uint8_t *)®_htons; ++ ++ // 未测试 ++ soft_bus_start(); ++ soft_bus_write_byte((slv_addr << 1) | WRITE_BIT); ++ ret = soft_bus_wait_ack(); ++ soft_bus_write_byte(reg_u8[0]); ++ ret = soft_bus_wait_ack(); ++ soft_bus_write_byte(reg_u8[1]); ++ ret = soft_bus_wait_ack(); ++ soft_bus_stop(); ++ if (ret != ESP_OK) return -1; ++ soft_bus_start(); ++ soft_bus_write_byte((slv_addr << 1) | READ_BIT); ++ ret = soft_bus_wait_ack(); ++ soft_bus_read_byte(&data_u8[1], true); ++ soft_bus_read_byte(&data_u8[0], false); ++ soft_bus_stop(); ++#else + i2c_master_dev_handle_t dev_handle = *(get_handle_from_address(slv_addr)); + + uint8_t rx_buffer[2]; +@@ -305,6 +592,7 @@ uint16_t SCCB_Read_Addr16_Val16(uint8_t + + esp_err_t ret = i2c_master_transmit_receive(dev_handle, reg_u8, 2, rx_buffer, 2, TIMEOUT_MS); + uint16_t data = ((uint16_t)rx_buffer[0] << 8) | (uint16_t)rx_buffer[1]; ++#endif + + if (ret != ESP_OK) + { +@@ -316,6 +604,27 @@ uint16_t SCCB_Read_Addr16_Val16(uint8_t + + int SCCB_Write_Addr16_Val16(uint8_t slv_addr, uint16_t reg, uint16_t data) + { ++#if CONFIG_SCCB_SOFTWARE_SUPPORT ++ esp_err_t ret = ESP_FAIL; ++ uint16_t reg_htons = LITTLETOBIG(reg); ++ uint8_t *reg_u8 = (uint8_t *)®_htons; ++ uint16_t data_htons = LITTLETOBIG(data); ++ uint8_t *data_u8 = (uint8_t *)&data_htons; ++ ++ // 未测试 ++ soft_bus_start(); ++ soft_bus_write_byte((slv_addr << 1) | WRITE_BIT); ++ ret = soft_bus_wait_ack(); ++ soft_bus_write_byte(reg_u8[0]); ++ ret = soft_bus_wait_ack(); ++ soft_bus_write_byte(reg_u8[1]); ++ ret = soft_bus_wait_ack(); ++ soft_bus_write_byte(data_u8[0]); ++ ret = soft_bus_wait_ack(); ++ soft_bus_write_byte(data_u8[1]); ++ ret = soft_bus_wait_ack(); ++ soft_bus_stop(); ++#else + i2c_master_dev_handle_t dev_handle = *(get_handle_from_address(slv_addr)); + + uint8_t tx_buffer[4]; +@@ -325,6 +634,7 @@ int SCCB_Write_Addr16_Val16(uint8_t slv_ + tx_buffer[3] = data & 0x00ff; + + esp_err_t ret = i2c_master_transmit(dev_handle, tx_buffer, 4, TIMEOUT_MS); ++#endif + + if (ret != ESP_OK) + { diff --git a/m5stack/patches/0003-avoid-lv_bindings-compile-error.patch b/m5stack/patches/6000-avoid-lv_bindings-compile-error.patch similarity index 100% rename from m5stack/patches/0003-avoid-lv_bindings-compile-error.patch rename to m5stack/patches/6000-avoid-lv_bindings-compile-error.patch diff --git a/m5stack/patches/0018-avoid-lvgl-font-redefine.patch b/m5stack/patches/6001-avoid-lvgl-font-redefine.patch similarity index 100% rename from m5stack/patches/0018-avoid-lvgl-font-redefine.patch rename to m5stack/patches/6001-avoid-lvgl-font-redefine.patch diff --git a/m5stack/patches/6002-Use-lvgl-demo-benchmark.patch b/m5stack/patches/6002-Use-lvgl-demo-benchmark.patch new file mode 100644 index 00000000..0d378333 --- /dev/null +++ b/m5stack/patches/6002-Use-lvgl-demo-benchmark.patch @@ -0,0 +1,72 @@ +Index: lv_binding_micropython/lv_conf.h +=================================================================== +--- lv_binding_micropython.orig/lv_conf.h ++++ lv_binding_micropython/lv_conf.h +@@ -440,7 +440,7 @@ extern void mp_lv_deinit_gc(); + #define LV_FONT_MONTSERRAT_18 0 + #endif + #ifndef LV_FONT_MONTSERRAT_20 +- #define LV_FONT_MONTSERRAT_20 0 ++ #define LV_FONT_MONTSERRAT_20 1 + #endif + #ifndef LV_FONT_MONTSERRAT_22 + #define LV_FONT_MONTSERRAT_22 0 +@@ -449,7 +449,7 @@ extern void mp_lv_deinit_gc(); + #define LV_FONT_MONTSERRAT_24 1 + #endif + #ifndef LV_FONT_MONTSERRAT_26 +- #define LV_FONT_MONTSERRAT_26 0 ++ #define LV_FONT_MONTSERRAT_26 1 + #endif + #ifndef LV_FONT_MONTSERRAT_28 + #define LV_FONT_MONTSERRAT_28 0 +@@ -841,14 +841,14 @@ extern void mp_lv_deinit_gc(); + #define LV_USE_SNAPSHOT 1 + + /*1: Enable system monitor component*/ +-#define LV_USE_SYSMON 0 ++#define LV_USE_SYSMON 1 + #if LV_USE_SYSMON + /*Get the idle percentage. E.g. uint32_t my_get_idle(void);*/ + #define LV_SYSMON_GET_IDLE lv_timer_get_idle + + /*1: Show CPU usage and FPS count + * Requires `LV_USE_SYSMON = 1`*/ +- #define LV_USE_PERF_MONITOR 0 ++ #define LV_USE_PERF_MONITOR 1 + #if LV_USE_PERF_MONITOR + #define LV_USE_PERF_MONITOR_POS LV_ALIGN_BOTTOM_RIGHT + +@@ -1052,13 +1052,13 @@ extern void mp_lv_deinit_gc(); + ====================*/ + + /*Show some widget. It might be required to increase `LV_MEM_SIZE` */ +-#define LV_USE_DEMO_WIDGETS 0 ++#define LV_USE_DEMO_WIDGETS 1 + + /*Demonstrate the usage of encoder and keyboard*/ + #define LV_USE_DEMO_KEYPAD_AND_ENCODER 0 + + /*Benchmark your system*/ +-#define LV_USE_DEMO_BENCHMARK 0 ++#define LV_USE_DEMO_BENCHMARK 1 + + /*Render test for each primitives. Requires at least 480x272 display*/ + #define LV_USE_DEMO_RENDER 0 +Index: lv_binding_micropython/micropython.cmake +=================================================================== +--- lv_binding_micropython.orig/micropython.cmake ++++ lv_binding_micropython/micropython.cmake +@@ -34,10 +34,11 @@ all_lv_bindings() + # target_link_libraries(usermod INTERFACE usermod_lvgl) + + file(GLOB_RECURSE SOURCES ${CMAKE_CURRENT_LIST_DIR}/lvgl/src/*.c) ++file(GLOB_RECURSE DEMO_SOURCES ${CMAKE_CURRENT_LIST_DIR}/lvgl/demos/*.c) + + add_library(lvgl_interface INTERFACE) + +-target_sources(lvgl_interface INTERFACE ${SOURCES}) ++target_sources(lvgl_interface INTERFACE ${SOURCES} ${DEMO_SOURCES}) + target_compile_options(lvgl_interface INTERFACE ${LV_CFLAGS}) + + # # lvgl bindings target (the mpy module) diff --git a/m5stack/patches/README.md b/m5stack/patches/README.md index 861ef390..ef7d6beb 100644 --- a/m5stack/patches/README.md +++ b/m5stack/patches/README.md @@ -6,3 +6,4 @@ - 3000 - 3999 这个范围的补丁 id, 用于分配给 esp-adf 相关的补丁。 - 4000 - 4999 这个范围的补丁 id, 用于分配给 M5GFX 相关的补丁。 - 5000 - 5999 这个范围的补丁 id, 用于分配给 esp32-camera 相关的补丁。 +- 6000 - 6999 这个范围的补丁 id, 用于分配给 esp32-camera 相关的补丁。 From fbbd8c19b4942ee52f55b2bcd965c6b5d0231f3a Mon Sep 17 00:00:00 2001 From: lbuque <1102390310@qq.com> Date: Fri, 14 Nov 2025 14:51:14 +0800 Subject: [PATCH 314/322] boards/Nesso_N1: Add Hat support. Signed-off-by: lbuque <1102390310@qq.com> --- m5stack/boards/Nesso_N1/manifest.py | 1 + 1 file changed, 1 insertion(+) diff --git a/m5stack/boards/Nesso_N1/manifest.py b/m5stack/boards/Nesso_N1/manifest.py index 4db46258..608ff6d3 100644 --- a/m5stack/boards/Nesso_N1/manifest.py +++ b/m5stack/boards/Nesso_N1/manifest.py @@ -4,3 +4,4 @@ include("$(MPY_DIR)/../m5stack/modules/startup/manifest_nesson1.py") include("$(MPY_DIR)/../m5stack/libs/unit/manifest.py") +include("$(MPY_DIR)/../m5stack/libs/hat/manifest.py") From 863b9c8674dc7794322dd1c0d5c5a3c2f7008bed Mon Sep 17 00:00:00 2001 From: lbuque <1102390310@qq.com> Date: Thu, 30 Oct 2025 11:06:28 +0800 Subject: [PATCH 315/322] boards/M5STACK_CoreS3: Add missing M5Camera library. Signed-off-by: lbuque <1102390310@qq.com> --- m5stack/boards/M5STACK_CoreS3/manifest.py | 1 + 1 file changed, 1 insertion(+) diff --git a/m5stack/boards/M5STACK_CoreS3/manifest.py b/m5stack/boards/M5STACK_CoreS3/manifest.py index ba36cf26..7451b48c 100644 --- a/m5stack/boards/M5STACK_CoreS3/manifest.py +++ b/m5stack/boards/M5STACK_CoreS3/manifest.py @@ -7,3 +7,4 @@ include("$(MPY_DIR)/../m5stack/libs/module/manifest.py") include("$(MPY_DIR)/../m5stack/libs/unit/manifest.py") include("$(MPY_DIR)/../m5stack/libs/usb/manifest.py") +freeze("$(MPY_DIR)/../m5stack/libs/", "m5camera.py") From 25717181ea1962468cd73196ecbdce7603ed6548 Mon Sep 17 00:00:00 2001 From: lbuque <1102390310@qq.com> Date: Fri, 14 Nov 2025 12:03:56 +0800 Subject: [PATCH 316/322] WIP: Fix add_display. Signed-off-by: lbuque <1102390310@qq.com> --- m5stack/cmodules/m5unified/m5unified.c | 4 +-- .../components/M5Unified/mpy_m5unified.cpp | 27 ++++++++++--------- 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/m5stack/cmodules/m5unified/m5unified.c b/m5stack/cmodules/m5unified/m5unified.c index 2482f91c..d7aeda90 100644 --- a/m5stack/cmodules/m5unified/m5unified.c +++ b/m5stack/cmodules/m5unified/m5unified.c @@ -83,7 +83,7 @@ const mp_obj_type_t m5_board_type = { // -------- M5 wrapper static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(m5_begin_obj, 0, 1, m5_begin); -static MP_DEFINE_CONST_FUN_OBJ_3(m5_add_display_obj, m5_add_display); +// static MP_DEFINE_CONST_FUN_OBJ_3(m5_add_display_obj, m5_add_display); static MP_DEFINE_CONST_FUN_OBJ_0(m5_create_speaker_obj, m5_create_speaker); static MP_DEFINE_CONST_FUN_OBJ_0(m5_create_mic_obj, m5_create_mic); static MP_DEFINE_CONST_FUN_OBJ_0(m5_update_obj, m5_update); @@ -117,7 +117,7 @@ static const mp_rom_map_elem_t mp_module_m5_globals_table[] = { { MP_ROM_QSTR(MP_QSTR_Widgets), MP_OBJ_FROM_PTR(&m5_widgets) }, { MP_ROM_QSTR(MP_QSTR_begin), MP_ROM_PTR(&m5_begin_obj) }, - { MP_ROM_QSTR(MP_QSTR_addDisplay), MP_ROM_PTR(&m5_add_display_obj) }, + // { MP_ROM_QSTR(MP_QSTR_addDisplay), MP_ROM_PTR(&m5_add_display_obj) }, { MP_ROM_QSTR(MP_QSTR_createSpeaker), MP_ROM_PTR(&m5_create_speaker_obj) }, { MP_ROM_QSTR(MP_QSTR_createMic), MP_ROM_PTR(&m5_create_mic_obj) }, { MP_ROM_QSTR(MP_QSTR_update), MP_ROM_PTR(&m5_update_obj) }, diff --git a/m5stack/components/M5Unified/mpy_m5unified.cpp b/m5stack/components/M5Unified/mpy_m5unified.cpp index afada667..7f1f2dd6 100644 --- a/m5stack/components/M5Unified/mpy_m5unified.cpp +++ b/m5stack/components/M5Unified/mpy_m5unified.cpp @@ -17,17 +17,17 @@ #include #include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include +// #include +// #include +// #include +// #include +// #include +// #include +// #include +// #include + +// #include +#include #include @@ -84,6 +84,7 @@ const mic_obj_t m5_mic = {&mp_mic_type, &(M5.Mic) }; static btn_obj_t *m5_btn_list[5] = {&m5_btnA, &m5_btnB, &m5_btnC, &m5_btnPWR, &m5_btnEXT}; /* *FORMAT-ON* */ +#if 0 static void m5_config_helper_module_display(mp_obj_t config_obj, m5::M5Unified::config_t &cfg) { if (!MP_OBJ_IS_TYPE(config_obj, &mp_type_dict)) { mp_raise_TypeError("module_display must be a dict"); @@ -239,7 +240,9 @@ static void m5_config_helper_unit_rca(mp_obj_t config_obj, m5::M5Unified::config } } #endif +#endif +#if 0 static void m5_config_helper(mp_obj_t config_obj, m5::M5Unified::config_t &cfg, machine_hw_i2c_obj_t *i2c_bus, uint8_t addr) { mp_map_t *config_map = mp_obj_dict_get_map(config_obj); @@ -411,7 +414,7 @@ mp_obj_t m5_add_display(mp_obj_t i2c_bus_in, mp_obj_t addr_in, mp_obj_t dict) { return m5_getDisplay(mp_obj_new_int(M5.getDisplayCount() - 1)); } - +#endif mp_obj_t m5_create_speaker(void) { auto spk = new m5::Speaker_Class(); From 8525d01d3ddfde5a3cf5c57af1de9c2705d3c941 Mon Sep 17 00:00:00 2001 From: hlym123 Date: Tue, 4 Nov 2025 16:03:38 +0800 Subject: [PATCH 317/322] boards: Add DualKey Support. Signed-off-by: hlym123 --- docs/en/controllers/dualkey.rst | 208 ++++++++++++++ docs/en/refs/controllers.dualkey.ref | 41 +++ .../zh_CN/LC_MESSAGES/controllers/dualkey.po | 255 ++++++++++++++++++ .../dualkey/dualkey_button_led_example.m5f2 | 1 + .../dualkey/dualkey_button_led_example.py | 64 +++++ .../dualkey_power_detection_example.m5f2 | 1 + .../dualkey_power_detection_example.py | 58 ++++ .../dualkey/dualkey_usb_mouse_example.m5f2 | 1 + .../dualkey/dualkey_usb_mouse_example.py | 76 ++++++ m5stack/boards/M5STACK_DualKey/board.json | 18 ++ m5stack/boards/M5STACK_DualKey/manifest.py | 7 + .../M5STACK_DualKey/mpconfigboard.cmake | 43 +++ .../boards/M5STACK_DualKey/mpconfigboard.h | 21 ++ .../boards/M5STACK_DualKey/sdkconfig.board | 21 ++ m5stack/cmodules/m5unified/m5unified.c | 1 + m5stack/libs/hardware/__init__.py | 1 + m5stack/libs/hardware/dualkey.py | 94 +++++++ m5stack/libs/hardware/manifest.py | 1 + m5stack/libs/hardware/rgb.py | 5 + m5stack/modules/startup/__init__.py | 5 + m5stack/modules/startup/dualkey.py | 102 +++++++ m5stack/modules/startup/manifest_dualkey.py | 13 + 22 files changed, 1037 insertions(+) create mode 100644 docs/en/controllers/dualkey.rst create mode 100644 docs/en/refs/controllers.dualkey.ref create mode 100644 docs/locales/zh_CN/LC_MESSAGES/controllers/dualkey.po create mode 100644 examples/controllers/dualkey/dualkey_button_led_example.m5f2 create mode 100644 examples/controllers/dualkey/dualkey_button_led_example.py create mode 100644 examples/controllers/dualkey/dualkey_power_detection_example.m5f2 create mode 100644 examples/controllers/dualkey/dualkey_power_detection_example.py create mode 100644 examples/controllers/dualkey/dualkey_usb_mouse_example.m5f2 create mode 100644 examples/controllers/dualkey/dualkey_usb_mouse_example.py create mode 100644 m5stack/boards/M5STACK_DualKey/board.json create mode 100644 m5stack/boards/M5STACK_DualKey/manifest.py create mode 100644 m5stack/boards/M5STACK_DualKey/mpconfigboard.cmake create mode 100644 m5stack/boards/M5STACK_DualKey/mpconfigboard.h create mode 100644 m5stack/boards/M5STACK_DualKey/sdkconfig.board create mode 100644 m5stack/libs/hardware/dualkey.py create mode 100644 m5stack/modules/startup/dualkey.py create mode 100644 m5stack/modules/startup/manifest_dualkey.py diff --git a/docs/en/controllers/dualkey.rst b/docs/en/controllers/dualkey.rst new file mode 100644 index 00000000..9c24a0d8 --- /dev/null +++ b/docs/en/controllers/dualkey.rst @@ -0,0 +1,208 @@ +####### +DualKey +####### + +.. include:: ../refs/controllers.dualkey.ref + +Support the following products: + + |DualKey| + +UiFlow2 Example +--------------- + +Button LED Control +^^^^^^^^^^^^^^^^^^ + +Open the |dualkey_button_led_example.m5f2| project in UiFlow2. + +This example demonstrates button callback functions to toggle RGB LEDs. When the left button (BtnA) is clicked, it toggles the left RGB LED (LED 0). When the right button (BtnB) is clicked, it toggles the right RGB LED (LED 1). + +UiFlow2 Code Block: + + |dualkey_button_led_example.png| + +Example output: + + None + +Power Detection +^^^^^^^^^^^^^^^^ + +Open the |dualkey_power_detection_example.m5f2| project in UiFlow2. + +This example demonstrates battery voltage monitoring and switch position detection. It reads the switch position (left/middle/right) and displays corresponding RGB LED colors, while also periodically reading and displaying the battery voltage in millivolts. + +UiFlow2 Code Block: + + |dualkey_power_detection_example.png| + +Example output: + + None + +USB Mouse +^^^^^^^^^ + +Open the |dualkey_usb_mouse_example.m5f2| project in UiFlow2. + +This example demonstrates USB HID mouse functionality with button-triggered clicks. When the left button (BtnA) is clicked, it sends a left mouse click and lights up the left RGB LED. When the right button (BtnB) is clicked, it sends a right mouse click and lights up the right RGB LED. The LEDs automatically turn off after 300ms. + +UiFlow2 Code Block: + + |dualkey_usb_mouse_example.png| + +Example output: + + None + +MicroPython Example +------------------- + +Button LED Control +^^^^^^^^^^^^^^^^^^ + +This example demonstrates button callback functions to toggle RGB LEDs. When the left button (BtnA) is clicked, it toggles the left RGB LED (LED 0). When the right button (BtnB) is clicked, it toggles the right RGB LED (LED 1). + +MicroPython Code Block: + + .. literalinclude:: ../../../examples/controllers/dualkey/dualkey_button_led_example.py + :language: python + :linenos: + +Example output: + + None + +Power Detection +^^^^^^^^^^^^^^^^ + +This example demonstrates battery voltage monitoring and switch position detection. It reads the switch position (left/middle/right) and displays corresponding RGB LED colors, while also periodically reading and displaying the battery voltage in millivolts. + +MicroPython Code Block: + + .. literalinclude:: ../../../examples/controllers/dualkey/dualkey_power_detection_example.py + :language: python + :linenos: + +Example output: + + None + +USB Mouse +^^^^^^^^^ + +This example demonstrates USB HID mouse functionality with button-triggered clicks. When the left button (BtnA) is clicked, it sends a left mouse click and lights up the left RGB LED. When the right button (BtnB) is clicked, it sends a right mouse click and lights up the right RGB LED. The LEDs automatically turn off after 300ms. + +.. note:: When USB mouse is initialized, the USB-CDC REPL may disconnect. You may need to reconnect to the device after running this example. + +MicroPython Code Block: + + .. literalinclude:: ../../../examples/controllers/dualkey/dualkey_usb_mouse_example.py + :language: python + :linenos: + +Example output: + + None + +**API** +------- + +class DualKey +^^^^^^^^^^^^^ + +.. class:: hardware.dualkey.DualKey() + + DualKey module - voltage and switch detection (singleton). + + The DualKey class is a singleton that provides methods to monitor battery voltage, VBUS voltage, charging status, and switch position. + + MicroPython Code Block: + + .. code-block:: python + + from hardware import dualkey + + .. method:: get_battery_voltage() + + Get battery voltage. + + :returns: Battery voltage value in millivolts (mV). + :rtype: int + + UiFlow2 Code Block: + + |get_battery_voltage.png| + + MicroPython Code Block: + + .. code-block:: python + + voltage = dualkey.get_battery_voltage() + print(f"Battery voltage: {voltage} mV") + + .. method:: get_vbus_voltage() + + Get VBUS(USB power) voltage. + + :returns: VBUS voltage value in millivolts (mV). + :rtype: int + + UiFlow2 Code Block: + + |get_vbus_voltage.png| + + MicroPython Code Block: + + .. code-block:: python + + vbus_voltage = dualkey.get_vbus_voltage() + print(f"VBUS voltage: {vbus_voltage} mV") + + .. method:: is_charging() + + Check if the device is charging. + + :returns: Returns `True` if charging, `False` if not charging. + :rtype: bool + + UiFlow2 Code Block: + + |is_charging.png| + + MicroPython Code Block: + + .. code-block:: python + + if dualkey.is_charging(): + print("Device is charging") + else: + print("Device is not charging") + + .. method:: get_switch_position() + + Get switch position. + + :returns: Switch position value: + - ``0``: left + - ``1``: middle + - ``2``: right + :rtype: int + + UiFlow2 Code Block: + + |get_switch_position.png| + + MicroPython Code Block: + + .. code-block:: python + + position = dualkey.get_switch_position() + if position == 0: + print("Switch position: left") + elif position == 1: + print("Switch position: middle") + elif position == 2: + print("Switch position: right") + diff --git a/docs/en/refs/controllers.dualkey.ref b/docs/en/refs/controllers.dualkey.ref new file mode 100644 index 00000000..c5b0f305 --- /dev/null +++ b/docs/en/refs/controllers.dualkey.ref @@ -0,0 +1,41 @@ +.. |DualKey| image:: https://m5stack-doc.oss-cn-shenzhen.aliyuncs.com/1176/C147_chain-dualkey-mainpicture_01.webp + :target: https://docs.m5stack.com/zh_CN/chain/Chain_DualKey + + +.. |get_battery_voltage.png| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/hardware_dualkey/get_battery_voltage.png +.. |get_vbus_voltage.png| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/hardware_dualkey/get_vbus_voltage.png +.. |is_charging.png| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/hardware_dualkey/is_charging.png +.. |get_switch_position.png| image:: https://static-cdn.m5stack.com/mpy_docs/hardware/hardware_dualkey/get_switch_position.png + +.. |dualkey_button_led_example.png| image:: https://static-cdn.m5stack.com/mpy_docs/controllers/dualkey/dualkey_button_led_example.png +.. |dualkey_power_detection_example.png| image:: https://static-cdn.m5stack.com/mpy_docs/controllers/dualkey/dualkey_power_detection_example.png +.. |dualkey_usb_mouse_example.png| image:: https://static-cdn.m5stack.com/mpy_docs/controllers/dualkey/dualkey_usb_mouse_example.png + +.. |dualkey_button_led_example.m5f2| raw:: html + + + dualkey_button_led_example.m5f2 + + +.. |dualkey_power_detection_example.m5f2| raw:: html + + + dualkey_power_detection_example.m5f2 + + +.. |dualkey_usb_mouse_example.m5f2| raw:: html + + + dualkey_usb_mouse_example.m5f2 + + + diff --git a/docs/locales/zh_CN/LC_MESSAGES/controllers/dualkey.po b/docs/locales/zh_CN/LC_MESSAGES/controllers/dualkey.po new file mode 100644 index 00000000..a040814b --- /dev/null +++ b/docs/locales/zh_CN/LC_MESSAGES/controllers/dualkey.po @@ -0,0 +1,255 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2016 - 2025 M5Stack Technology Co., Ltd +# This file is distributed under the same license as the UIFlow2 Programming +# Guide package. +# FIRST AUTHOR , 2025. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: UIFlow2 Programming Guide \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-11-04 12:09+0800\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: zh_CN\n" +"Language-Team: zh_CN \n" +"Plural-Forms: nplurals=1; plural=0;\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.16.0\n" + +#: ../../en/controllers/dualkey.rst:3 ../../en/refs/controllers.dualkey.ref +#: e4c13a15d6874e16a42ab035bfe72ce8 f73c1408caa14e70acd68623e90f4e55 +msgid "DualKey" +msgstr "" + +#: ../../en/controllers/dualkey.rst:7 154cd974d9d84778a6b0fcad1dc417d8 +msgid "Support the following products:" +msgstr "支持以下产品:" + +#: ../../en/controllers/dualkey.rst:9 03effb497bc04374b9488d1ad8e92db4 +msgid "|DualKey|" +msgstr "" + +#: ../../en/controllers/dualkey.rst:12 c0a9746f142b4b54aa071d62e11012d9 +msgid "UiFlow2 Example" +msgstr "UiFlow2 应用示例" + +#: ../../en/controllers/dualkey.rst:15 ../../en/controllers/dualkey.rst:63 +#: de5c74d187b3418bbe99dcba4b570200 +msgid "Button LED Control" +msgstr "按钮 LED 控制" + +#: ../../en/controllers/dualkey.rst:17 4806ebbd2acc46beb486e4c2d0a5ddc4 +msgid "Open the |dualkey_button_led_example.m5f2| project in UiFlow2." +msgstr "在 UiFlow2 上打开 |dualkey_button_led_example.m5f2| 项目。" + +#: ../../en/controllers/dualkey.rst:19 ../../en/controllers/dualkey.rst:65 +#: d57d781a800e42718fc4db9578ff4241 +msgid "" +"This example demonstrates button callback functions to toggle RGB LEDs. " +"When the left button (BtnA) is clicked, it toggles the left RGB LED (LED " +"0). When the right button (BtnB) is clicked, it toggles the right RGB LED" +" (LED 1)." +msgstr "" +"该示例演示了使用按钮回调函数来切换 RGB LED 灯。当点击左侧按钮(BtnA)时," +"它会切换左侧 RGB LED(LED 0)。当点击右侧按钮(BtnB)时,它会切换右侧 RGB " +"LED(LED 1)。" + +#: ../../en/controllers/dualkey.rst:21 ../../en/controllers/dualkey.rst:36 +#: ../../en/controllers/dualkey.rst:51 ../../en/controllers/dualkey.rst:134 +#: ../../en/controllers/dualkey.rst:152 ../../en/controllers/dualkey.rst:170 +#: ../../en/controllers/dualkey.rst:193 24cf82f98e8d4b01bec77b96285a4111 +msgid "UiFlow2 Code Block:" +msgstr "UiFlow2 代码块:" + +#: ../../en/controllers/dualkey.rst:23 d261453da55e4802ac303640872291f5 +msgid "|dualkey_button_led_example.png|" +msgstr "" + +#: ../../en/refs/controllers.dualkey.ref:10 743478e872f24e359f282e2b75f2a54b +msgid "dualkey_button_led_example.png" +msgstr "" + +#: ../../en/controllers/dualkey.rst:25 ../../en/controllers/dualkey.rst:40 +#: ../../en/controllers/dualkey.rst:55 ../../en/controllers/dualkey.rst:73 +#: ../../en/controllers/dualkey.rst:88 ../../en/controllers/dualkey.rst:105 +#: 31b808407f8840fdb30e1ece69185b49 +msgid "Example output:" +msgstr "示例输出:" + +#: ../../en/controllers/dualkey.rst:27 ../../en/controllers/dualkey.rst:42 +#: ../../en/controllers/dualkey.rst:57 ../../en/controllers/dualkey.rst:75 +#: ../../en/controllers/dualkey.rst:90 ../../en/controllers/dualkey.rst:107 +#: c6371c147a284e45b63d632162fa5458 +msgid "None" +msgstr "无" + +#: ../../en/controllers/dualkey.rst:30 ../../en/controllers/dualkey.rst:78 +#: 5560865a95bb43efa5c5e032ba1e7868 +msgid "Power Detection" +msgstr "电源检测" + +#: ../../en/controllers/dualkey.rst:32 3dd6fad48873462683884692789fedd0 +msgid "Open the |dualkey_power_detection_example.m5f2| project in UiFlow2." +msgstr "在 UiFlow2 上打开 |dualkey_power_detection_example.m5f2| 项目。" + +#: ../../en/controllers/dualkey.rst:34 ../../en/controllers/dualkey.rst:80 +#: 6e9b5dab66fb45b4a7d2dc1ed6a53724 +msgid "" +"This example demonstrates battery voltage monitoring and switch position " +"detection. It reads the switch position (left/middle/right) and displays " +"corresponding RGB LED colors, while also periodically reading and " +"displaying the battery voltage in millivolts." +msgstr "" +"该示例演示了电池电压监测和开关位置检测。它读取开关位置(左/中/右)并显示相应" +"的 RGB LED 颜色,同时定期读取并显示电池电压(单位为毫伏)。" + +#: ../../en/controllers/dualkey.rst:38 6f560b6a97104145ae50c87fb134be54 +msgid "|dualkey_power_detection_example.png|" +msgstr "" + +#: ../../en/refs/controllers.dualkey.ref:11 aef003d0f24448db9a392e368b6c8442 +msgid "dualkey_power_detection_example.png" +msgstr "" + +#: ../../en/controllers/dualkey.rst:45 ../../en/controllers/dualkey.rst:93 +#: b15ab69765164d5799e1af3917a27a6f +msgid "USB Mouse" +msgstr "USB 鼠标" + +#: ../../en/controllers/dualkey.rst:47 d0e0cc21ea58486aa571ae203aad3013 +msgid "Open the |dualkey_usb_mouse_example.m5f2| project in UiFlow2." +msgstr "在 UiFlow2 上打开 |dualkey_usb_mouse_example.m5f2| 项目。" + +#: ../../en/controllers/dualkey.rst:49 ../../en/controllers/dualkey.rst:95 +#: f8cb6d3cb543449d803fa6654a777316 +msgid "" +"This example demonstrates USB HID mouse functionality with button-" +"triggered clicks. When the left button (BtnA) is clicked, it sends a left" +" mouse click and lights up the left RGB LED. When the right button (BtnB)" +" is clicked, it sends a right mouse click and lights up the right RGB " +"LED. The LEDs automatically turn off after 300ms." +msgstr "" +"该示例演示了通过按钮触发点击的 USB HID 鼠标功能。当点击左侧按钮(BtnA)时," +"它会发送左键点击并点亮左侧 RGB LED。当点击右侧按钮(BtnB)时,它会发送右键点" +"击并点亮右侧 RGB LED。LED 会在 300ms 后自动熄灭。" + +#: ../../en/controllers/dualkey.rst:53 9f2bf3a3a39e444b9d57bf4d7309adee +msgid "|dualkey_usb_mouse_example.png|" +msgstr "" + +#: ../../en/refs/controllers.dualkey.ref:12 0a8222a51bd9435d877126afa8ce33fe +msgid "dualkey_usb_mouse_example.png" +msgstr "" + +#: ../../en/controllers/dualkey.rst:60 ec7a8e00cd8348b2800002710548a0e2 +msgid "MicroPython Example" +msgstr "MicroPython 应用示例" + +#: ../../en/controllers/dualkey.rst:67 ../../en/controllers/dualkey.rst:82 +#: ../../en/controllers/dualkey.rst:99 ../../en/controllers/dualkey.rst:121 +#: ../../en/controllers/dualkey.rst:138 ../../en/controllers/dualkey.rst:156 +#: ../../en/controllers/dualkey.rst:174 ../../en/controllers/dualkey.rst:197 +#: 42dfa851cd7841ae86088232367fce66 +msgid "MicroPython Code Block:" +msgstr "MicroPython 代码块:" + +#: ../../en/controllers/dualkey.rst:97 36281075562d4446b0de8cdddbcf3e08 +msgid "" +"When USB mouse is initialized, the USB-CDC REPL may disconnect. You may " +"need to reconnect to the device after running this example." +msgstr "当 USB 鼠标初始化时,USB-CDC REPL 可能会断开连接。运行此示例后,您可能需要重新连接到设备。" + +#: ../../en/controllers/dualkey.rst:110 72f176508f994cbabb37facfc9ddfbb8 +msgid "**API**" +msgstr "API应用" + +#: ../../en/controllers/dualkey.rst:113 d3f40afa51564451a526289bf31cb2da +msgid "class DualKey" +msgstr "" + +#: ../../en/controllers/dualkey.rst:117 e2cf79576cff4404bf41e79f157043a0 +msgid "DualKey module - voltage and switch detection (singleton)." +msgstr "DualKey 模块 + +#: ../../en/controllers/dualkey.rst:119 2e43741bbbf849ff88746031081b63fa +msgid "" +"The DualKey class is a singleton that provides methods to monitor battery" +" voltage, VBUS voltage, charging status, and switch position." +msgstr "DualKey 类是一个单例,提供了监控电池电压、VBUS 电压、充电状态和开关位置的方法。" + +#: ../../en/controllers/dualkey.rst:129 93cb46d24ea74376a86dcfd240610731 +msgid "Get battery voltage." +msgstr "获取电池电压。" + +#: ../../en/controllers/dualkey.rst c0f4c19441214046bd2d58f5967223e8 +msgid "Returns" +msgstr "" + +#: ../../en/controllers/dualkey.rst:131 eef8d0ebc97e453ba49d77528c094f2a +msgid "Battery voltage value in millivolts (mV)." +msgstr "电池电压值,单位为毫伏 (mV)。" + +#: ../../en/controllers/dualkey.rst 9bcaa3f732814e23a46fef92687094ba +msgid "Return type" +msgstr "" + +#: ../../en/controllers/dualkey.rst:136 a389dab8cec6442d9803e239dd7c407b +msgid "|get_battery_voltage.png|" +msgstr "" + +#: ../../en/refs/controllers.dualkey.ref:5 8b82c0ab35c14fedab4c952fc8ddbed3 +msgid "get_battery_voltage.png" +msgstr "" + +#: ../../en/controllers/dualkey.rst:147 f43407de190f49dba59045f346a3c180 +msgid "Get VBUS(USB power) voltage." +msgstr "获取 VBUS(USB 电源)电压。" + +#: ../../en/controllers/dualkey.rst:149 d63cc156c5944d6992b363042c309cf0 +msgid "VBUS voltage value in millivolts (mV)." +msgstr "VBUS 电压值,单位为毫伏 (mV)。" + +#: ../../en/controllers/dualkey.rst:154 f2f8e08444b244a2a8b13d740741bf88 +msgid "|get_vbus_voltage.png|" +msgstr "" + +#: ../../en/refs/controllers.dualkey.ref:6 8b82c0ab35c14fedab4c952fc8ddbed3 +msgid "get_vbus_voltage.png" +msgstr "" + +#: ../../en/controllers/dualkey.rst:165 ffd06617766b4177b2716358b12d0c8d +msgid "Check if the device is charging." +msgstr "检测设备是否正在充电。" + +#: ../../en/controllers/dualkey.rst:167 4832efe33093475f948fefe38e6ae737 +msgid "Returns `True` if charging, `False` if not charging." +msgstr "如果正在充电返回 `True`,如果未在充电返回 `False`。" + +#: ../../en/controllers/dualkey.rst:172 1b948cc9873e4b18b9a575e478394e40 +msgid "|is_charging.png|" +msgstr "" + +#: ../../en/refs/controllers.dualkey.ref:7 3e6a72b2aea047e4a1955a643936a3dc +msgid "is_charging.png" +msgstr "" + +#: ../../en/controllers/dualkey.rst:185 bd7e99d1e2b94d7c9ab05730ef5d7ad4 +msgid "Get switch position." +msgstr "获取开关位置。" + +#: ../../en/controllers/dualkey.rst:187 101cccb82b32403f968b9c45f18462d3 +msgid "Switch position value: - ``0``: left - ``1``: middle - ``2``: right" +msgstr "开关位置值:- ``0``: 左侧 - ``1``: 中间 - ``2``: 右侧" + +#: ../../en/controllers/dualkey.rst:195 6cddf9d9f3c44e659e26cd46f443b569 +msgid "|get_switch_position.png|" +msgstr "" + +#: ../../en/refs/controllers.dualkey.ref:8 9e3d887bfc6c4271aeeae2338ed76dd5 +msgid "get_switch_position.png" +msgstr "" + diff --git a/examples/controllers/dualkey/dualkey_button_led_example.m5f2 b/examples/controllers/dualkey/dualkey_button_led_example.m5f2 new file mode 100644 index 00000000..37e71b67 --- /dev/null +++ b/examples/controllers/dualkey/dualkey_button_led_example.m5f2 @@ -0,0 +1 @@ +{"version":"V2.0","versionNumber":"V2.3.7","type":"dualkey","components":[],"resources":[{"hardware":["hardware_button","hardware_pin_button","rgb","hardware_dualkey"]}],"units":[],"hats":[],"caps":[],"bases":[],"i2cs":[],"blockly":"led1_stateled2_statetruebuiltinrgb0palette#33ccffrgb1palette#33ccfftrueBtnAWAS_CLICKEDclicke leftled1_stateled1_stateled1_statergb0palette#009900rgb0palette#000000BtnBWAS_CLICKEDclick rightled2_stateled2_stateled2_statergb1palette#009900rgb1palette#000000","screen":[],"logicWhenNum":0,"customList":[]} \ No newline at end of file diff --git a/examples/controllers/dualkey/dualkey_button_led_example.py b/examples/controllers/dualkey/dualkey_button_led_example.py new file mode 100644 index 00000000..1be440f8 --- /dev/null +++ b/examples/controllers/dualkey/dualkey_button_led_example.py @@ -0,0 +1,64 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import os, sys, io +import M5 +from M5 import * +from hardware import RGB + + +rgb = None +led1_state = None +led2_state = None + + +def btna_was_clicked_event(state): + global rgb, led1_state, led2_state + print("clicke left") + led1_state = not led1_state + if led1_state: + rgb.set_color(0, 0x009900) + else: + rgb.set_color(0, 0x000000) + + +def btnb_was_clicked_event(state): + global rgb, led1_state, led2_state + print("click right") + led2_state = not led2_state + if led2_state: + rgb.set_color(1, 0x009900) + else: + rgb.set_color(1, 0x000000) + + +def setup(): + global rgb, led1_state, led2_state + + M5.begin() + BtnA.setCallback(type=BtnA.CB_TYPE.WAS_CLICKED, cb=btna_was_clicked_event) + BtnB.setCallback(type=BtnB.CB_TYPE.WAS_CLICKED, cb=btnb_was_clicked_event) + + rgb = RGB() + rgb.set_color(0, 0x33CCFF) + rgb.set_color(1, 0x33CCFF) + + +def loop(): + global rgb, led1_state, led2_state + M5.update() + + +if __name__ == "__main__": + try: + setup() + while True: + loop() + except (Exception, KeyboardInterrupt) as e: + try: + from utility import print_error_msg + + print_error_msg(e) + except ImportError: + print("please update to latest firmware") diff --git a/examples/controllers/dualkey/dualkey_power_detection_example.m5f2 b/examples/controllers/dualkey/dualkey_power_detection_example.m5f2 new file mode 100644 index 00000000..a1d08d6c --- /dev/null +++ b/examples/controllers/dualkey/dualkey_power_detection_example.m5f2 @@ -0,0 +1 @@ +{"version":"V2.0","versionNumber":"V2.3.7","type":"dualkey","components":[],"resources":[{"hardware":["hardware_button","hardware_pin_button","rgb","hardware_dualkey"]}],"units":[],"hats":[],"caps":[],"bases":[],"i2cs":[],"blockly":"sw_statusbattery_voltagetruebuiltinrgb0palette#000000rgb1palette#000000truesw_statusEQsw_status0Leftrgb0palette#009900rgb1palette#000000EQsw_status1Middlergb0palette#000000rgb1palette#000000EQsw_status2rgb0palette#000000rgb1palette#009900Rightbattery_voltagehello M5Battery voltage: Battery voltage: battery_voltagemV500","screen":[],"logicWhenNum":0,"customList":[]} \ No newline at end of file diff --git a/examples/controllers/dualkey/dualkey_power_detection_example.py b/examples/controllers/dualkey/dualkey_power_detection_example.py new file mode 100644 index 00000000..f2d1368c --- /dev/null +++ b/examples/controllers/dualkey/dualkey_power_detection_example.py @@ -0,0 +1,58 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import os, sys, io +import M5 +from M5 import * +from hardware import RGB +from hardware import dualkey +import time + + +rgb = None +sw_status = None +battery_voltage = None + + +def setup(): + global rgb, sw_status, battery_voltage + M5.begin() + rgb = RGB() + rgb.set_color(0, 0x000000) + rgb.set_color(1, 0x000000) + + +def loop(): + global rgb, sw_status, battery_voltage + M5.update() + sw_status = dualkey.get_switch_position() + if sw_status == 0: + print("Left") + rgb.set_color(0, 0x009900) + rgb.set_color(1, 0x000000) + elif sw_status == 1: + print("Middle") + rgb.set_color(0, 0x000000) + rgb.set_color(1, 0x000000) + elif sw_status == 2: + rgb.set_color(0, 0x000000) + rgb.set_color(1, 0x009900) + print("Right") + battery_voltage = dualkey.get_battery_voltage() + print((str((str("Battery voltage: ") + str(battery_voltage))) + str("mV"))) + time.sleep_ms(500) + + +if __name__ == "__main__": + try: + setup() + while True: + loop() + except (Exception, KeyboardInterrupt) as e: + try: + from utility import print_error_msg + + print_error_msg(e) + except ImportError: + print("please update to latest firmware") diff --git a/examples/controllers/dualkey/dualkey_usb_mouse_example.m5f2 b/examples/controllers/dualkey/dualkey_usb_mouse_example.m5f2 new file mode 100644 index 00000000..f752d1d3 --- /dev/null +++ b/examples/controllers/dualkey/dualkey_usb_mouse_example.m5f2 @@ -0,0 +1 @@ +{"version":"V2.0","versionNumber":"V2.3.7","type":"dualkey","components":[],"resources":[{"hardware":["hardware_button","hardware_pin_button","rgb","hardware_dualkey"]}],"units":[],"hats":[],"caps":[],"bases":[],"i2cs":[],"blockly":"click_leftclick_rightlast_timetruebuiltinrgb0palette#3333ffrgb1palette#3333ffmousetrueclick_leftclick_leftFALSEmousemouseTruergb0palette#009900rgb1palette#000000last_timeclick_rightclick_rightFALSEmousemouseTruergb0palette#000000rgb1palette#009900last_timeGTE11last_time300rgb0palette#000000rgb1palette#000000BtnAWAS_CLICKEDclick leftclick_leftTRUEBtnBWAS_CLICKEDclick rightclick_rightTRUE","screen":[],"logicWhenNum":0,"customList":[]} \ No newline at end of file diff --git a/examples/controllers/dualkey/dualkey_usb_mouse_example.py b/examples/controllers/dualkey/dualkey_usb_mouse_example.py new file mode 100644 index 00000000..88c3bc01 --- /dev/null +++ b/examples/controllers/dualkey/dualkey_usb_mouse_example.py @@ -0,0 +1,76 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT +import os, sys, io +import M5 +from M5 import * +from hardware import RGB +from usb.device.mouse import Mouse +import time + + +rgb = None +mouse = None +click_left = None +click_right = None +last_time = None + + +def btna_was_clicked_event(state): + global rgb, mouse, click_left, click_right, last_time + print("click left") + click_left = True + + +def btnb_was_clicked_event(state): + global rgb, mouse, click_left, click_right, last_time + print("click right") + click_right = True + + +def setup(): + global rgb, mouse, click_left, click_right, last_time + + M5.begin() + BtnA.setCallback(type=BtnA.CB_TYPE.WAS_CLICKED, cb=btna_was_clicked_event) + BtnB.setCallback(type=BtnB.CB_TYPE.WAS_CLICKED, cb=btnb_was_clicked_event) + + rgb = RGB() + rgb.set_color(0, 0x3333FF) + rgb.set_color(1, 0x3333FF) + mouse = Mouse() + + +def loop(): + global rgb, mouse, click_left, click_right, last_time + M5.update() + if click_left: + click_left = False + if mouse.is_open(): + mouse.click_left(True) + rgb.set_color(0, 0x009900) + rgb.set_color(1, 0x000000) + last_time = time.ticks_ms() + if click_right: + click_right = False + if mouse.is_open(): + mouse.click_right(True) + rgb.set_color(0, 0x000000) + rgb.set_color(1, 0x009900) + if (time.ticks_diff((time.ticks_ms()), last_time)) >= 300: + rgb.set_color(0, 0x000000) + rgb.set_color(1, 0x000000) + + +if __name__ == "__main__": + try: + setup() + while True: + loop() + except (Exception, KeyboardInterrupt) as e: + try: + from utility import print_error_msg + + print_error_msg(e) + except ImportError: + print("please update to latest firmware") diff --git a/m5stack/boards/M5STACK_DualKey/board.json b/m5stack/boards/M5STACK_DualKey/board.json new file mode 100644 index 00000000..096f750e --- /dev/null +++ b/m5stack/boards/M5STACK_DualKey/board.json @@ -0,0 +1,18 @@ +{ + "deploy": [ + "../deploy_s3.md" + ], + "docs": "", + "features": [ + "BLE", + "WiFi" + ], + "images": [ + "generic_s3.jpg" + ], + "mcu": "esp32s3", + "product": "M5Stack S3 Serials", + "thumbnail": "", + "url": "https://www.espressif.com/en/products/modules", + "vendor": "M5Stack" +} diff --git a/m5stack/boards/M5STACK_DualKey/manifest.py b/m5stack/boards/M5STACK_DualKey/manifest.py new file mode 100644 index 00000000..f4e41bde --- /dev/null +++ b/m5stack/boards/M5STACK_DualKey/manifest.py @@ -0,0 +1,7 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +include("$(MPY_DIR)/../m5stack/modules/startup/manifest_dualkey.py") +include("$(MPY_DIR)/../m5stack/libs/unit/manifest.py") +include("$(MPY_DIR)/../m5stack/libs/usb/manifest.py") diff --git a/m5stack/boards/M5STACK_DualKey/mpconfigboard.cmake b/m5stack/boards/M5STACK_DualKey/mpconfigboard.cmake new file mode 100644 index 00000000..6990022e --- /dev/null +++ b/m5stack/boards/M5STACK_DualKey/mpconfigboard.cmake @@ -0,0 +1,43 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +set(IDF_TARGET esp32s3) + +# atoms3r https://github.com/m5stack/m5stack-board-id/blob/main/board.csv#L20 +set(BOARD_ID 147) +set(MICROPY_PY_LVGL 0) + +set(SDKCONFIG_DEFAULTS + ./boards/sdkconfig.base + ${SDKCONFIG_IDF_VERSION_SPECIFIC} + ./boards/sdkconfig.240mhz + ./boards/sdkconfig.disable_iram + ./boards/sdkconfig.ble + ./boards/sdkconfig.usb + ./boards/sdkconfig.usb_cdc + ./boards/sdkconfig.flash_8mb + ./boards/sdkconfig.spiram + ./boards/sdkconfig.spiram_oct + ./boards/sdkconfig.freertos + ./boards/M5STACK_DualKey/sdkconfig.board +) + +# If not enable LVGL, ignore this... +set(LV_CFLAGS -DLV_COLOR_DEPTH=16 -DLV_COLOR_16_SWAP=0) + +if(NOT MICROPY_FROZEN_MANIFEST) + set(MICROPY_FROZEN_MANIFEST ${CMAKE_SOURCE_DIR}/boards/manifest.py) +endif() + +# NOTE: 这里的配置是无效的,仅为了兼容ADF,保证编译通过 +set(ADF_COMPS "$ENV{ADF_PATH}/components") +set(ADF_BOARD_DIR "$ENV{ADF_PATH}/components/audio_board/esp32_s3_box_3") + +list(APPEND EXTRA_COMPONENT_DIRS + $ENV{ADF_PATH}/components/audio_pipeline + $ENV{ADF_PATH}/components/audio_sal + $ENV{ADF_PATH}/components/esp-adf-libs + $ENV{ADF_PATH}/components/esp-sr + ${CMAKE_SOURCE_DIR}/boards +) \ No newline at end of file diff --git a/m5stack/boards/M5STACK_DualKey/mpconfigboard.h b/m5stack/boards/M5STACK_DualKey/mpconfigboard.h new file mode 100644 index 00000000..0565166b --- /dev/null +++ b/m5stack/boards/M5STACK_DualKey/mpconfigboard.h @@ -0,0 +1,21 @@ +/* +* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD +* +* SPDX-License-Identifier: MIT +*/ + +#define MICROPY_HW_BOARD_NAME "M5STACK DualKey" +#define MICROPY_HW_MCU_NAME "ESP32-S3-PICO-1" + +#define MICROPY_PY_MACHINE_DAC (0) + +// Enable UART REPL for modules that have an external USB-UART and don't use native USB. +#define MICROPY_HW_ENABLE_UART_REPL (1) + +#define MICROPY_HW_I2C0_SCL (-1) +#define MICROPY_HW_I2C0_SDA (-1) + +#define MICROPY_HW_USB_CDC_INTERFACE_STRING "M5Stack DualKey(UiFlow2)" + +// If not enable LVGL, ignore this... +#include "./../mpconfiglvgl.h" diff --git a/m5stack/boards/M5STACK_DualKey/sdkconfig.board b/m5stack/boards/M5STACK_DualKey/sdkconfig.board new file mode 100644 index 00000000..6f4de9aa --- /dev/null +++ b/m5stack/boards/M5STACK_DualKey/sdkconfig.board @@ -0,0 +1,21 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +CONFIG_FLASHMODE_DIO=y # QIO mode has some problem when mount fs +CONFIG_ESPTOOLPY_FLASHMODE_DIO=y +CONFIG_ESPTOOLPY_FLASHMODE="dio" +CONFIG_ESPTOOLPY_FLASHFREQ_80M=y +CONFIG_ESPTOOLPY_FLASHSIZE_DETECT=y +CONFIG_ESPTOOLPY_AFTER_NORESET=y + +CONFIG_SPIRAM_MEMTEST=y +# CONFIG_FREERTOS_UNICORE=y + +# M5STACK UiFlow USB description +CONFIG_TINYUSB_DESC_CDC_STRING="M5Stack Dualkey(UiFlow2)" + +# SSL +CONFIG_MBEDTLS_AES_USE_INTERRUPT=n + +CONFIG_FREERTOS_ENABLE_BACKWARD_COMPATIBILITY=y diff --git a/m5stack/cmodules/m5unified/m5unified.c b/m5stack/cmodules/m5unified/m5unified.c index d7aeda90..1bdc64b7 100644 --- a/m5stack/cmodules/m5unified/m5unified.c +++ b/m5stack/cmodules/m5unified/m5unified.c @@ -52,6 +52,7 @@ static const mp_rom_map_elem_t m5_board_member_table[] = { { MP_ROM_QSTR(MP_QSTR_M5AtomS3R_CAM), MP_ROM_INT(144) }, { MP_ROM_QSTR(MP_QSTR_M5AtomEchoS3R), MP_ROM_INT(145) }, { MP_ROM_QSTR(MP_QSTR_M5PowerHub), MP_ROM_INT(146) }, + { MP_ROM_QSTR(MP_QSTR_M5DualKey), MP_ROM_INT(147) }, // external displays { MP_ROM_QSTR(MP_QSTR_M5ATOMDisplay), MP_ROM_INT(192) }, { MP_ROM_QSTR(MP_QSTR_M5UnitLCD), MP_ROM_INT(193) }, diff --git a/m5stack/libs/hardware/__init__.py b/m5stack/libs/hardware/__init__.py index 19a70df5..73ed1093 100644 --- a/m5stack/libs/hardware/__init__.py +++ b/m5stack/libs/hardware/__init__.py @@ -21,6 +21,7 @@ "SDCard": "sdcard", "SEN55": "sen55", "SHT30": "sht30", + "dualkey": "dualkey", } diff --git a/m5stack/libs/hardware/dualkey.py b/m5stack/libs/hardware/dualkey.py new file mode 100644 index 00000000..9f556d1b --- /dev/null +++ b/m5stack/libs/hardware/dualkey.py @@ -0,0 +1,94 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import machine +import time + + +class DualKey: + ADC_BAT_GPIO = 10 + ADC_VBUS_GPIO = 2 + ADC_CHRG_GPIO = 9 + ADC_SW1_GPIO = 8 + ADC_SW2_GPIO = 7 + BATTERY_VOLTAGE_MULTIPLIER = 1.51 + VBUS_VOLTAGE_MULTIPLIER = 1.51 + SWITCH_VOLTAGE_THRESHOLD = 1000 + _instance = None + + def __new__(cls): + if cls._instance is None: + cls._instance = super(DualKey, cls).__new__(cls) + return cls._instance + + def __init__(self): + if hasattr(self, "_initialized") and self._initialized: + return + self.adc_bat = machine.ADC(machine.Pin(self.ADC_BAT_GPIO)) + self.adc_vbus = machine.ADC(machine.Pin(self.ADC_VBUS_GPIO)) + self.adc_chrg = machine.ADC(machine.Pin(self.ADC_CHRG_GPIO)) + self.adc_sw1 = machine.ADC(machine.Pin(self.ADC_SW1_GPIO)) + self.adc_sw2 = machine.ADC(machine.Pin(self.ADC_SW2_GPIO)) + for adc in (self.adc_bat, self.adc_vbus, self.adc_chrg, self.adc_sw1, self.adc_sw2): + adc.atten(machine.ADC.ATTN_11DB) + adc.width(machine.ADC.WIDTH_12BIT) + self._initialized = True + + def _read_adc_voltage(self, adc, samples=5): + total = 0 + for _ in range(samples): + total += adc.read() + time.sleep_ms(2) + avg_raw = total / samples + return (avg_raw / 4095) * 3.3 + + def get_battery_voltage(self): + """返回电池电压 (mV)""" + v = self._read_adc_voltage(self.adc_bat) + v *= self.BATTERY_VOLTAGE_MULTIPLIER + return int(v * 1000) + + def get_vbus_voltage(self): + """返回VBUS电压 (mV)""" + v = self._read_adc_voltage(self.adc_vbus) + v *= self.VBUS_VOLTAGE_MULTIPLIER + return int(v * 1000) + + def get_charging_voltage(self): + return self._read_adc_voltage(self.adc_chrg) + + def get_charging_status(self): + v = self.get_charging_voltage() + if 1.4 <= v <= 1.8: + return "CHARGING" + elif 1.8 < v <= 2.4: + return "FULL" + elif v > 3.0: + return "NO_CHARGING" + else: + return "UNKNOWN" + + def is_charging(self): + return self.get_charging_status() == "CHARGING" + + def get_switch_position(self): + """判断拨动开关位置:0 / 1 / 2 / -1""" + + def avg_adc(adc): + return sum(adc.read() for _ in range(5)) / 5 + + mv1 = avg_adc(self.adc_sw1) * 3300 / 4095 * self.BATTERY_VOLTAGE_MULTIPLIER + mv2 = avg_adc(self.adc_sw2) * 3300 / 4095 * self.BATTERY_VOLTAGE_MULTIPLIER + th = self.SWITCH_VOLTAGE_THRESHOLD + if mv1 > th and mv2 <= th: + return 0 + elif mv1 <= th and mv2 <= th: + return 1 + elif mv1 <= th and mv2 > th: + return 2 + else: + return -1 + + +dualkey = DualKey() diff --git a/m5stack/libs/hardware/manifest.py b/m5stack/libs/hardware/manifest.py index 1dca0587..2c1bf031 100644 --- a/m5stack/libs/hardware/manifest.py +++ b/m5stack/libs/hardware/manifest.py @@ -10,6 +10,7 @@ "keyboard/asciimap.py", "button.py", "can.py", + "dualkey.py", "ir.py", "lora.py", "matrix_keyboard.py", diff --git a/m5stack/libs/hardware/rgb.py b/m5stack/libs/hardware/rgb.py index 06ced4bd..4c2786a3 100644 --- a/m5stack/libs/hardware/rgb.py +++ b/m5stack/libs/hardware/rgb.py @@ -68,6 +68,11 @@ def __new__(cls, **kwargs) -> NeoPixel: elif board_id == M5.BOARD.M5PowerHub: cls._instance = PowerHubRGB() return cls._instance + elif board_id == M5.BOARD.M5DualKey: + pin = machine.Pin(40, machine.Pin.OUT) # Power Enable + pin.value(1) + cls._instance = WS2812(io=21, n=2) # RGB Data + return cls._instance else: pass else: diff --git a/m5stack/modules/startup/__init__.py b/m5stack/modules/startup/__init__.py index 1eacb3f9..925b09db 100644 --- a/m5stack/modules/startup/__init__.py +++ b/m5stack/modules/startup/__init__.py @@ -125,6 +125,11 @@ def startup(boot_opt, timeout: int = 60) -> None: atom_echos3r = AtomEchoS3R_Startup() atom_echos3r.startup(ssid, pswd, timeout) + elif board_id == M5.BOARD.M5DualKey: + from .dualkey import DualKey_Startup + + dualkey = DualKey_Startup() + dualkey.startup(ssid, pswd, timeout) elif board_id == M5.BOARD.M5AtomMatrix: from .atommatrix import AtomMatrix_Startup diff --git a/m5stack/modules/startup/dualkey.py b/m5stack/modules/startup/dualkey.py new file mode 100644 index 00000000..0b46d861 --- /dev/null +++ b/m5stack/modules/startup/dualkey.py @@ -0,0 +1,102 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT +# DualKey startup script +import M5 +import time +import network +import machine +import binascii +from . import Startup +from hardware import RGB + + +# DualKey startup menu +class DualKey_Startup(Startup): + COLOR_GREEN = 0x00FF00 # WiFi connected + COLOR_BLUE = 0x0000FF # Power on / WiFi connecting + COLOR_RED = 0xFF0000 # WiFi connection failed + + def __init__(self) -> None: + super().__init__() + self.rgb = RGB() + self.rgb.set_color(0, self.COLOR_BLUE) + self.rgb.set_color(1, self.COLOR_BLUE) + self.rgb.set_brightness(80) + + def show_hits(self, hits: str) -> None: + print(hits) + + def show_msg(self, msg: str) -> None: + print(msg) + + def show_ssid(self, ssid: str) -> None: + if len(ssid) > 9: + self.show_msg(ssid[:7] + "...") + else: + self.show_msg(ssid) + + def show_mac(self) -> None: + mac = binascii.hexlify(machine.unique_id()).decode("utf-8").upper() + print(mac[0:6] + "_" + mac[6:]) + + def show_error(self, ssid: str, error: str) -> None: + self.show_ssid(ssid) + self.show_hits(error) + self.show_mac() + print("SSID: " + ssid + "\r\nNotice: " + error) + + def startup(self, ssid: str, pswd: str, timeout: int = 60) -> None: + self.show_mac() + + if super().connect_network(ssid=ssid, pswd=pswd): + self.show_ssid(ssid) + count = 1 + status = super().connect_status() + start = time.time() + # 连接过程中保持蓝色 + self.rgb.set_color(0, self.COLOR_BLUE) + self.rgb.set_color(1, self.COLOR_BLUE) + self.rgb.set_brightness(80) + + while status is not network.STAT_GOT_IP: + time.sleep_ms(300) + + if status is network.STAT_NO_AP_FOUND: + self.show_error(ssid, "NO AP FOUND") + break + elif status is network.STAT_WRONG_PASSWORD: + self.show_error(ssid, "WRONG PASSWORD") + break + elif status is network.STAT_HANDSHAKE_TIMEOUT: + self.show_error(ssid, "HANDSHAKE ERR") + break + elif status is network.STAT_CONNECTING: + self.show_hits("." * count) + count = count + 1 + if count > 5: + count = 1 + status = super().connect_status() + # connect to network timeout + if (time.time() - start) > timeout: + self.show_error(ssid, "TIMEOUT") + break + + if status is network.STAT_GOT_IP: + # 连接成功,显示绿色 + self.rgb.set_color(0, self.COLOR_GREEN) + self.rgb.set_color(1, self.COLOR_GREEN) + self.rgb.set_brightness(80) + self.show_hits(super().local_ip()) + print("Local IP: " + super().local_ip()) + else: + # 连接失败,显示红色 + self.rgb.set_color(0, self.COLOR_RED) + self.rgb.set_color(1, self.COLOR_RED) + self.rgb.set_brightness(80) + else: + # 无法连接网络,显示红色 + self.rgb.set_color(0, self.COLOR_RED) + self.rgb.set_color(1, self.COLOR_RED) + self.rgb.set_brightness(80) + self.show_error("Not Found", "Use Burner setup") diff --git a/m5stack/modules/startup/manifest_dualkey.py b/m5stack/modules/startup/manifest_dualkey.py new file mode 100644 index 00000000..77ee2bc8 --- /dev/null +++ b/m5stack/modules/startup/manifest_dualkey.py @@ -0,0 +1,13 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +package( + "startup", + ( + "__init__.py", + "dualkey.py", + ), + base_path="..", + opt=3, +) From a4eea2116b683537c6c19caa62ff2bb5bdedb41b Mon Sep 17 00:00:00 2001 From: lbuque <1102390310@qq.com> Date: Fri, 14 Nov 2025 15:50:27 +0800 Subject: [PATCH 318/322] Makefile: Add DualKey definition. Signed-off-by: lbuque <1102390310@qq.com> --- m5stack/Makefile | 15 ++++---- m5stack/patches/2011-Add-DualKey.patch | 47 ++++++++++++++++++++++++++ m5stack/patches/4003-Add-DualKey.patch | 12 +++++++ 3 files changed, 68 insertions(+), 6 deletions(-) create mode 100644 m5stack/patches/2011-Add-DualKey.patch create mode 100644 m5stack/patches/4003-Add-DualKey.patch diff --git a/m5stack/Makefile b/m5stack/Makefile index fadb0769..7a13823c 100644 --- a/m5stack/Makefile +++ b/m5stack/Makefile @@ -47,7 +47,8 @@ boards := \ M5STACK_Tab5:tab5 \ M5STACK_CardputerADV:cardputeradv\ M5STACK_Unit_C6L:unit_c6l \ - M5STACK_PowerHub:powerhub + M5STACK_PowerHub:powerhub \ + M5STACK_DualKey:dualkey define find_board $(if $(filter $(1):%,$(boards)),$(word 2,$(subst :, ,$(filter $(1):%,$(boards)))),none) @@ -91,7 +92,8 @@ BOARD_TYPE_DEF := \ tab5 \ cardputeradv\ unit_c6l \ - powerhub + powerhub \ + dualkey # Select the board type to build, default is None # This value affects which folder in the "./fs/system/" directory is pack into "fs-system.bin" @@ -333,13 +335,14 @@ M5UNIFIED_PATCH_SERIES = \ 2007-Support-UnitC6L.patch \ 2008-Only-use-old-rmt-driver.patch \ 2009-fix-SoftI2C.patch \ - 2010-Support-Nesso-N1.patch + 2010-Support-Nesso-N1.patch \ + 2011-Add-DualKey.patch ADF_PATCH_SERIES = \ 3002-Modify-i2s_stream_idf5.patch M5GFX_PATCH_SERIES = \ - 4002-M5GFX-use-i2c-driver.patch + 4003-Add-DualKey.patch ESP32_CAMERA_PATCH_SERIES = \ 5002-Add-software-i2c-support.patch @@ -398,7 +401,7 @@ patch: unpatch $(call Patch/prepare,$(IDF_PATH),$(IDF_PATH_PATCH_SERIES)) $(call Patch/prepare,$(M5UNIFIED_PATH),$(M5UNIFIED_PATCH_SERIES)) $(call Patch/prepare,$(ADF_PATH),$(ADF_PATCH_SERIES)) -# $(call Patch/prepare,$(M5GFX_PATH),$(M5GFX_PATCH_SERIES)) + $(call Patch/prepare,$(M5GFX_PATH),$(M5GFX_PATCH_SERIES)) $(call Patch/prepare,$(ESP32_CAMERA_PATH),$(ESP32_CAMERA_PATCH_SERIES)) # Unapply patches @@ -408,5 +411,5 @@ unpatch: $(call Patch/clean,$(IDF_PATH),$(IDF_PATH_PATCH_SERIES)) $(call Patch/clean,$(M5UNIFIED_PATH),$(M5UNIFIED_PATCH_SERIES)) $(call Patch/clean,$(ADF_PATH),$(ADF_PATCH_SERIES)) -# $(call Patch/clean,$(M5GFX_PATH),$(M5GFX_PATCH_SERIES)) + $(call Patch/clean,$(M5GFX_PATH),$(M5GFX_PATCH_SERIES)) $(call Patch/clean,$(ESP32_CAMERA_PATH),$(ESP32_CAMERA_PATCH_SERIES)) diff --git a/m5stack/patches/2011-Add-DualKey.patch b/m5stack/patches/2011-Add-DualKey.patch new file mode 100644 index 00000000..80867435 --- /dev/null +++ b/m5stack/patches/2011-Add-DualKey.patch @@ -0,0 +1,47 @@ +Index: M5Unified/src/M5Unified.cpp +=================================================================== +--- M5Unified.orig/src/M5Unified.cpp ++++ M5Unified/src/M5Unified.cpp +@@ -1050,7 +1050,15 @@ static constexpr const uint8_t _pin_tabl + default: + case 0: // EFUSE_PKG_VERSION_ESP32S3: // QFN56 + if (board == board_t::board_unknown) +- { /// StampS3 or AtomS3Lite,S3U ? ++ { ++ #if defined (BOARD_ID) && BOARD_ID == 147 ++ board = board_t::board_M5DualKey; ++ // !!!NOTE: Set to input mode to prevent the device from failing to shut down. ++ m5gfx::pinMode(GPIO_NUM_7, m5gfx::pin_mode_t::input); ++ m5gfx::pinMode(GPIO_NUM_8, m5gfx::pin_mode_t::input); ++ break; ++ #endif ++ /// StampS3 or AtomS3Lite,S3U ? + /// After setting GPIO38 to INPUT PULL-UP, change to INPUT and read it. + /// In the case of STAMPS3: Returns 0. Charge is sucked by SGM2578. + /// In the case of ATOMS3Lite/S3U : Returns 1. Charge remains. ( Since it is not connected to anywhere. ) +@@ -1661,6 +1669,10 @@ static constexpr const uint8_t _pin_tabl + m5gfx::pinMode(GPIO_NUM_11, m5gfx::pin_mode_t::input); + break; + ++ case board_t::board_M5DualKey: ++ m5gfx::pinMode(GPIO_NUM_0, m5gfx::pin_mode_t::input); ++ m5gfx::pinMode(GPIO_NUM_17, m5gfx::pin_mode_t::input); ++ break; + #endif + + default: +@@ -2444,6 +2456,14 @@ static constexpr const uint8_t _pin_tabl + ; + break; + } ++ ++ case board_t::board_M5DualKey: ++ { ++ use_rawstate_bits = 0b00011; ++ btn_rawstate_bits = ((!m5gfx::gpio_in(GPIO_NUM_17)) & 1) ++ | ((!m5gfx::gpio_in(GPIO_NUM_0)) & 1) << 1; ++ break; ++ } + default: + + break; diff --git a/m5stack/patches/4003-Add-DualKey.patch b/m5stack/patches/4003-Add-DualKey.patch new file mode 100644 index 00000000..01d60595 --- /dev/null +++ b/m5stack/patches/4003-Add-DualKey.patch @@ -0,0 +1,12 @@ +Index: M5GFX/src/lgfx/boards.hpp +=================================================================== +--- M5GFX.orig/src/lgfx/boards.hpp ++++ M5GFX/src/lgfx/boards.hpp +@@ -55,6 +55,7 @@ namespace lgfx // This should not be cha + , board_M5AtomS3RCam + , board_M5AtomEchoS3R + , board_M5PowerHub ++ , board_M5DualKey + + /// external displays + , board_M5AtomDisplay = 192 From 6e28a49c172504ac6b09e355c1d9ef1c6fef7528 Mon Sep 17 00:00:00 2001 From: lbuque <1102390310@qq.com> Date: Thu, 23 Oct 2025 10:05:23 +0800 Subject: [PATCH 319/322] libs: Add Chain support. Signed-off-by: lbuque <1102390310@qq.com> --- docs/en/chain/chainbus.rst | 15 + docs/en/chain/index.rst | 9 + docs/en/chain/joystick.rst | 18 + docs/en/chain/key.rst | 16 + docs/en/conf.py | 3 +- docs/en/controllers/index.rst | 1 - docs/en/index.rst | 1 + docs/en/refs/chain.bus.ref | 5 + docs/en/refs/chain.joystick.ref | 24 + docs/en/refs/chain.key.ref | 17 + m5stack/boards/M5STACK_AirQ/manifest.py | 1 + m5stack/boards/M5STACK_AtomS3/manifest.py | 1 + m5stack/boards/M5STACK_AtomS3R/manifest.py | 1 + .../boards/M5STACK_AtomS3R_CAM/manifest.py | 1 + m5stack/boards/M5STACK_AtomS3U/manifest.py | 1 + .../boards/M5STACK_AtomS3_Lite/manifest.py | 1 + m5stack/boards/M5STACK_AtomU/manifest.py | 1 + m5stack/boards/M5STACK_Atom_Echo/manifest.py | 1 + .../boards/M5STACK_Atom_EchoS3R/manifest.py | 1 + m5stack/boards/M5STACK_Atom_Lite/manifest.py | 1 + .../boards/M5STACK_Atom_Matrix/manifest.py | 1 + m5stack/boards/M5STACK_Basic/manifest.py | 1 + m5stack/boards/M5STACK_Basic_4MB/manifest.py | 1 + m5stack/boards/M5STACK_Capsule/manifest.py | 1 + m5stack/boards/M5STACK_Cardputer/manifest.py | 1 + .../boards/M5STACK_CardputerADV/manifest.py | 1 + m5stack/boards/M5STACK_Core2/manifest.py | 1 + m5stack/boards/M5STACK_CoreInk/manifest.py | 1 + m5stack/boards/M5STACK_CoreS3/manifest.py | 1 + m5stack/boards/M5STACK_Dial/manifest.py | 1 + m5stack/boards/M5STACK_DinMeter/manifest.py | 1 + m5stack/boards/M5STACK_DualKey/manifest.py | 1 + m5stack/boards/M5STACK_Fire/manifest.py | 1 + m5stack/boards/M5STACK_NanoC6/manifest.py | 1 + m5stack/boards/M5STACK_Paper/manifest.py | 1 + m5stack/boards/M5STACK_PaperS3/manifest.py | 1 + m5stack/boards/M5STACK_PowerHub/manifest.py | 1 + m5stack/boards/M5STACK_StamPLC/manifest.py | 1 + m5stack/boards/M5STACK_StampS3/manifest.py | 1 + m5stack/boards/M5STACK_Stamp_PICO/manifest.py | 1 + m5stack/boards/M5STACK_Station/manifest.py | 1 + m5stack/boards/M5STACK_StickC/manifest.py | 1 + .../boards/M5STACK_StickC_PLUS/manifest.py | 1 + .../boards/M5STACK_StickC_PLUS2/manifest.py | 1 + m5stack/boards/M5STACK_Tab5/manifest.py | 1 + m5stack/boards/M5STACK_Tough/manifest.py | 1 + m5stack/boards/M5STACK_Unit_C6L/manifest.py | 1 + m5stack/boards/Nesso_N1/manifest.py | 1 + m5stack/libs/chain/__init__.py | 18 + m5stack/libs/chain/chain.py | 591 ++++++++++++++++++ m5stack/libs/chain/joystick.py | 268 ++++++++ m5stack/libs/chain/key.py | 396 ++++++++++++ m5stack/libs/chain/manifest.py | 15 + 53 files changed, 1433 insertions(+), 2 deletions(-) create mode 100644 docs/en/chain/chainbus.rst create mode 100644 docs/en/chain/index.rst create mode 100644 docs/en/chain/joystick.rst create mode 100644 docs/en/chain/key.rst create mode 100644 docs/en/refs/chain.bus.ref create mode 100644 docs/en/refs/chain.joystick.ref create mode 100644 docs/en/refs/chain.key.ref create mode 100644 m5stack/libs/chain/__init__.py create mode 100644 m5stack/libs/chain/chain.py create mode 100644 m5stack/libs/chain/joystick.py create mode 100644 m5stack/libs/chain/key.py create mode 100644 m5stack/libs/chain/manifest.py diff --git a/docs/en/chain/chainbus.rst b/docs/en/chain/chainbus.rst new file mode 100644 index 00000000..d9a6cdbd --- /dev/null +++ b/docs/en/chain/chainbus.rst @@ -0,0 +1,15 @@ +Chain BUS +========= + +.. include:: ../refs/chain.bus.ref + +**API** +------- + +ChainBUS +^^^^^^^^ + +.. autoclass:: chain.chain.ChainBus + :members: + :member-order: bysource + :exclude-members: register_device, set_device_disconnected_handler, set_device_connected_handler diff --git a/docs/en/chain/index.rst b/docs/en/chain/index.rst new file mode 100644 index 00000000..57649604 --- /dev/null +++ b/docs/en/chain/index.rst @@ -0,0 +1,9 @@ +Chain +===== + +.. toctree:: + :maxdepth: 1 + + chainbus.rst + joystick.rst + key.rst diff --git a/docs/en/chain/joystick.rst b/docs/en/chain/joystick.rst new file mode 100644 index 00000000..2b2bb1ac --- /dev/null +++ b/docs/en/chain/joystick.rst @@ -0,0 +1,18 @@ +Chain Joystick +============== + +.. include:: ../refs/chain.joystick.ref + + +**API** +------- + +JoystickChain +^^^^^^^^^^^^^ + +.. autoclass:: chain.joystick.JoystickChain + :members: + :member-order: bysource + :exclude-members: + + For other button and some general methods, please refer to the :class:`ChainKey ` class. diff --git a/docs/en/chain/key.rst b/docs/en/chain/key.rst new file mode 100644 index 00000000..139d79b2 --- /dev/null +++ b/docs/en/chain/key.rst @@ -0,0 +1,16 @@ +Chain Key +========= + +.. include:: ../refs/chain.key.ref + + +**API** +------- + +KeyChain +^^^^^^^^ + +.. autoclass:: chain.key.KeyChain + :members: + :member-order: bysource + :exclude-members: diff --git a/docs/en/conf.py b/docs/en/conf.py index 4c1dca0f..96a6b4e0 100644 --- a/docs/en/conf.py +++ b/docs/en/conf.py @@ -61,7 +61,8 @@ "m5audio2", "lvgl", "_espnow", - "lora" + "lora", + "m5utils" ] autodoc_default_options = { diff --git a/docs/en/controllers/index.rst b/docs/en/controllers/index.rst index fcc9dca7..6f73a767 100644 --- a/docs/en/controllers/index.rst +++ b/docs/en/controllers/index.rst @@ -15,6 +15,5 @@ Controllers airq.rst paper.rst dinmeter.rst - dualkey.rst nesso-n1.rst unit_c6l.rst diff --git a/docs/en/index.rst b/docs/en/index.rst index 5b339633..3c003b61 100644 --- a/docs/en/index.rst +++ b/docs/en/index.rst @@ -17,6 +17,7 @@ UiFlow2 documentation and references hats/index.rst base/index.rst cap/index.rst + chain/index.rst iot-devices/index.rst advanced/index.rst quick-reference/index.rst diff --git a/docs/en/refs/chain.bus.ref b/docs/en/refs/chain.bus.ref new file mode 100644 index 00000000..6c16bc43 --- /dev/null +++ b/docs/en/refs/chain.bus.ref @@ -0,0 +1,5 @@ + +.. |get_device_num.png| image:: https://static-cdn.m5stack.com/mpy_docs/chain/bus/get_device_num.png +.. |init.png| image:: https://static-cdn.m5stack.com/mpy_docs/chain/bus/init.png +.. |send.png| image:: https://static-cdn.m5stack.com/mpy_docs/chain/bus/send.png +.. |send_return.png| image:: https://static-cdn.m5stack.com/mpy_docs/chain/bus/send_return.png diff --git a/docs/en/refs/chain.joystick.ref b/docs/en/refs/chain.joystick.ref new file mode 100644 index 00000000..bb5d5751 --- /dev/null +++ b/docs/en/refs/chain.joystick.ref @@ -0,0 +1,24 @@ + +.. |click_callback.png| image:: https://static-cdn.m5stack.com/mpy_docs/chain/joystick/click_callback.png +.. |double_click_callback.png| image:: https://static-cdn.m5stack.com/mpy_docs/chain/joystick/double_click_callback.png +.. |get_16bit_value.png| image:: https://static-cdn.m5stack.com/mpy_docs/chain/joystick/get_16bit_value.png +.. |get_8bit_value.png| image:: https://static-cdn.m5stack.com/mpy_docs/chain/joystick/get_8bit_value.png +.. |get_bootloader_version.png| image:: https://static-cdn.m5stack.com/mpy_docs/chain/joystick/get_bootloader_version.png +.. |get_button_double_click_trigger_interval.png| image:: https://static-cdn.m5stack.com/mpy_docs/chain/joystick/get_button_double_click_trigger_interval.png +.. |get_button_long_press_trigger_interval.png| image:: https://static-cdn.m5stack.com/mpy_docs/chain/joystick/get_button_long_press_trigger_interval.png +.. |get_button_mode.png| image:: https://static-cdn.m5stack.com/mpy_docs/chain/joystick/get_button_mode.png +.. |get_button_state.png| image:: https://static-cdn.m5stack.com/mpy_docs/chain/joystick/get_button_state.png +.. |get_firmware_version.png| image:: https://static-cdn.m5stack.com/mpy_docs/chain/joystick/get_firmware_version.png +.. |get_mapping_value.png| image:: https://static-cdn.m5stack.com/mpy_docs/chain/joystick/get_mapping_value.png +.. |get_raw_16bit_value.png| image:: https://static-cdn.m5stack.com/mpy_docs/chain/joystick/get_raw_16bit_value.png +.. |get_raw_value.png| image:: https://static-cdn.m5stack.com/mpy_docs/chain/joystick/get_raw_value.png +.. |get_rgb_brightness.png| image:: https://static-cdn.m5stack.com/mpy_docs/chain/joystick/get_rgb_brightness.png +.. |get_rgb_color.png| image:: https://static-cdn.m5stack.com/mpy_docs/chain/joystick/get_rgb_color.png +.. |init.png| image:: https://static-cdn.m5stack.com/mpy_docs/chain/joystick/init.png +.. |long_press_callback.png| image:: https://static-cdn.m5stack.com/mpy_docs/chain/joystick/long_press_callback.png +.. |set_button_double_click_trigger_interval.png| image:: https://static-cdn.m5stack.com/mpy_docs/chain/joystick/set_button_double_click_trigger_interval.png +.. |set_button_long_press_trigger_interval.png| image:: https://static-cdn.m5stack.com/mpy_docs/chain/joystick/set_button_long_press_trigger_interval.png +.. |set_button_mode.png| image:: https://static-cdn.m5stack.com/mpy_docs/chain/joystick/set_button_mode.png +.. |set_mapping_value.png| image:: https://static-cdn.m5stack.com/mpy_docs/chain/joystick/set_mapping_value.png +.. |set_rgb_brightness.png| image:: https://static-cdn.m5stack.com/mpy_docs/chain/joystick/set_rgb_brightness.png +.. |set_rgb_color.png| image:: https://static-cdn.m5stack.com/mpy_docs/chain/joystick/set_rgb_color.png diff --git a/docs/en/refs/chain.key.ref b/docs/en/refs/chain.key.ref new file mode 100644 index 00000000..1a32db15 --- /dev/null +++ b/docs/en/refs/chain.key.ref @@ -0,0 +1,17 @@ +.. |double_click_callback.png| image:: https://static-cdn.m5stack.com/mpy_docs/chain/key/double_click_callback.png +.. |get_bootloader_version.png| image:: https://static-cdn.m5stack.com/mpy_docs/chain/key/get_bootloader_version.png +.. |get_button_double_click_trigger_interval.png| image:: https://static-cdn.m5stack.com/mpy_docs/chain/key/get_button_double_click_trigger_interval.png +.. |get_button_long_press_trigger_interval.png| image:: https://static-cdn.m5stack.com/mpy_docs/chain/key/get_button_long_press_trigger_interval.png +.. |get_button_mode.png| image:: https://static-cdn.m5stack.com/mpy_docs/chain/key/get_button_mode.png +.. |get_button_state.png| image:: https://static-cdn.m5stack.com/mpy_docs/chain/key/get_button_state.png +.. |get_firmware_version.png| image:: https://static-cdn.m5stack.com/mpy_docs/chain/key/get_firmware_version.png +.. |get_rgb_brightness.png| image:: https://static-cdn.m5stack.com/mpy_docs/chain/key/get_rgb_brightness.png +.. |get_rgb_color.png| image:: https://static-cdn.m5stack.com/mpy_docs/chain/key/get_rgb_color.png +.. |init.png| image:: https://static-cdn.m5stack.com/mpy_docs/chain/key/init.png +.. |key_click_callback.png| image:: https://static-cdn.m5stack.com/mpy_docs/chain/key/key_click_callback.png +.. |long_press_callback.png| image:: https://static-cdn.m5stack.com/mpy_docs/chain/key/long_press_callback.png +.. |set_button_double_click_trigger_interval.png| image:: https://static-cdn.m5stack.com/mpy_docs/chain/key/set_button_double_click_trigger_interval.png +.. |set_button_long_press_trigger_interval.png| image:: https://static-cdn.m5stack.com/mpy_docs/chain/key/set_button_long_press_trigger_interval.png +.. |set_button_mode.png| image:: https://static-cdn.m5stack.com/mpy_docs/chain/key/set_button_mode.png +.. |set_rgb_brightness.png| image:: https://static-cdn.m5stack.com/mpy_docs/chain/key/set_rgb_brightness.png +.. |set_rgb_color.png| image:: https://static-cdn.m5stack.com/mpy_docs/chain/key/set_rgb_color.png diff --git a/m5stack/boards/M5STACK_AirQ/manifest.py b/m5stack/boards/M5STACK_AirQ/manifest.py index f73a47c1..0070ce2e 100644 --- a/m5stack/boards/M5STACK_AirQ/manifest.py +++ b/m5stack/boards/M5STACK_AirQ/manifest.py @@ -4,3 +4,4 @@ include("$(MPY_DIR)/../m5stack/modules/startup/manifest_airq.py") include("$(MPY_DIR)/../m5stack/libs/unit/manifest.py") +include("$(MPY_DIR)/../m5stack/libs/chain/manifest.py") diff --git a/m5stack/boards/M5STACK_AtomS3/manifest.py b/m5stack/boards/M5STACK_AtomS3/manifest.py index 2d18ef78..e5aeded3 100644 --- a/m5stack/boards/M5STACK_AtomS3/manifest.py +++ b/m5stack/boards/M5STACK_AtomS3/manifest.py @@ -5,3 +5,4 @@ include("$(MPY_DIR)/../m5stack/modules/startup/manifest_atoms3.py") include("$(MPY_DIR)/../m5stack/libs/base/manifest.py") include("$(MPY_DIR)/../m5stack/libs/unit/manifest.py") +include("$(MPY_DIR)/../m5stack/libs/chain/manifest.py") diff --git a/m5stack/boards/M5STACK_AtomS3R/manifest.py b/m5stack/boards/M5STACK_AtomS3R/manifest.py index d1096847..1d828c92 100644 --- a/m5stack/boards/M5STACK_AtomS3R/manifest.py +++ b/m5stack/boards/M5STACK_AtomS3R/manifest.py @@ -5,3 +5,4 @@ include("$(MPY_DIR)/../m5stack/modules/startup/manifest_atoms3r.py") include("$(MPY_DIR)/../m5stack/libs/base/manifest.py") include("$(MPY_DIR)/../m5stack/libs/unit/manifest.py") +include("$(MPY_DIR)/../m5stack/libs/chain/manifest.py") diff --git a/m5stack/boards/M5STACK_AtomS3R_CAM/manifest.py b/m5stack/boards/M5STACK_AtomS3R_CAM/manifest.py index 3e5351fc..f1195d49 100644 --- a/m5stack/boards/M5STACK_AtomS3R_CAM/manifest.py +++ b/m5stack/boards/M5STACK_AtomS3R_CAM/manifest.py @@ -5,3 +5,4 @@ include("$(MPY_DIR)/../m5stack/modules/startup/manifest_atoms3r_cam.py") include("$(MPY_DIR)/../m5stack/libs/base/manifest.py") include("$(MPY_DIR)/../m5stack/libs/unit/manifest.py") +include("$(MPY_DIR)/../m5stack/libs/chain/manifest.py") diff --git a/m5stack/boards/M5STACK_AtomS3U/manifest.py b/m5stack/boards/M5STACK_AtomS3U/manifest.py index 5341fc6e..70414bf9 100644 --- a/m5stack/boards/M5STACK_AtomS3U/manifest.py +++ b/m5stack/boards/M5STACK_AtomS3U/manifest.py @@ -4,3 +4,4 @@ include("$(MPY_DIR)/../m5stack/modules/startup/manifest_atoms3u.py") include("$(MPY_DIR)/../m5stack/libs/unit/manifest.py") +include("$(MPY_DIR)/../m5stack/libs/chain/manifest.py") diff --git a/m5stack/boards/M5STACK_AtomS3_Lite/manifest.py b/m5stack/boards/M5STACK_AtomS3_Lite/manifest.py index 98291c07..6e7a12bf 100644 --- a/m5stack/boards/M5STACK_AtomS3_Lite/manifest.py +++ b/m5stack/boards/M5STACK_AtomS3_Lite/manifest.py @@ -5,3 +5,4 @@ include("$(MPY_DIR)/../m5stack/modules/startup/manifest_atoms3lite.py") include("$(MPY_DIR)/../m5stack/libs/base/manifest.py") include("$(MPY_DIR)/../m5stack/libs/unit/manifest.py") +include("$(MPY_DIR)/../m5stack/libs/chain/manifest.py") diff --git a/m5stack/boards/M5STACK_AtomU/manifest.py b/m5stack/boards/M5STACK_AtomU/manifest.py index 738c6a3a..2b7a2800 100644 --- a/m5stack/boards/M5STACK_AtomU/manifest.py +++ b/m5stack/boards/M5STACK_AtomU/manifest.py @@ -4,3 +4,4 @@ include("$(MPY_DIR)/../m5stack/modules/startup/manifest_atoms3lite.py") include("$(MPY_DIR)/../m5stack/libs/unit/manifest.py") +include("$(MPY_DIR)/../m5stack/libs/chain/manifest.py") diff --git a/m5stack/boards/M5STACK_Atom_Echo/manifest.py b/m5stack/boards/M5STACK_Atom_Echo/manifest.py index 98291c07..6e7a12bf 100644 --- a/m5stack/boards/M5STACK_Atom_Echo/manifest.py +++ b/m5stack/boards/M5STACK_Atom_Echo/manifest.py @@ -5,3 +5,4 @@ include("$(MPY_DIR)/../m5stack/modules/startup/manifest_atoms3lite.py") include("$(MPY_DIR)/../m5stack/libs/base/manifest.py") include("$(MPY_DIR)/../m5stack/libs/unit/manifest.py") +include("$(MPY_DIR)/../m5stack/libs/chain/manifest.py") diff --git a/m5stack/boards/M5STACK_Atom_EchoS3R/manifest.py b/m5stack/boards/M5STACK_Atom_EchoS3R/manifest.py index 9f61255c..2388ee3f 100644 --- a/m5stack/boards/M5STACK_Atom_EchoS3R/manifest.py +++ b/m5stack/boards/M5STACK_Atom_EchoS3R/manifest.py @@ -5,3 +5,4 @@ include("$(MPY_DIR)/../m5stack/modules/startup/manifest_atom_echos3r.py") include("$(MPY_DIR)/../m5stack/libs/base/manifest.py") include("$(MPY_DIR)/../m5stack/libs/unit/manifest.py") +include("$(MPY_DIR)/../m5stack/libs/chain/manifest.py") diff --git a/m5stack/boards/M5STACK_Atom_Lite/manifest.py b/m5stack/boards/M5STACK_Atom_Lite/manifest.py index 98291c07..6e7a12bf 100644 --- a/m5stack/boards/M5STACK_Atom_Lite/manifest.py +++ b/m5stack/boards/M5STACK_Atom_Lite/manifest.py @@ -5,3 +5,4 @@ include("$(MPY_DIR)/../m5stack/modules/startup/manifest_atoms3lite.py") include("$(MPY_DIR)/../m5stack/libs/base/manifest.py") include("$(MPY_DIR)/../m5stack/libs/unit/manifest.py") +include("$(MPY_DIR)/../m5stack/libs/chain/manifest.py") diff --git a/m5stack/boards/M5STACK_Atom_Matrix/manifest.py b/m5stack/boards/M5STACK_Atom_Matrix/manifest.py index 690f891a..d8476b5b 100644 --- a/m5stack/boards/M5STACK_Atom_Matrix/manifest.py +++ b/m5stack/boards/M5STACK_Atom_Matrix/manifest.py @@ -5,3 +5,4 @@ include("$(MPY_DIR)/../m5stack/modules/startup/manifest_atommatrix.py") include("$(MPY_DIR)/../m5stack/libs/base/manifest.py") include("$(MPY_DIR)/../m5stack/libs/unit/manifest.py") +include("$(MPY_DIR)/../m5stack/libs/chain/manifest.py") diff --git a/m5stack/boards/M5STACK_Basic/manifest.py b/m5stack/boards/M5STACK_Basic/manifest.py index 0a829fe6..149f90e4 100644 --- a/m5stack/boards/M5STACK_Basic/manifest.py +++ b/m5stack/boards/M5STACK_Basic/manifest.py @@ -5,3 +5,4 @@ include("$(MPY_DIR)/../m5stack/modules/startup/manifest_basic.py") include("$(MPY_DIR)/../m5stack/libs/module/manifest.py") include("$(MPY_DIR)/../m5stack/libs/unit/manifest.py") +include("$(MPY_DIR)/../m5stack/libs/chain/manifest.py") diff --git a/m5stack/boards/M5STACK_Basic_4MB/manifest.py b/m5stack/boards/M5STACK_Basic_4MB/manifest.py index 0a829fe6..149f90e4 100644 --- a/m5stack/boards/M5STACK_Basic_4MB/manifest.py +++ b/m5stack/boards/M5STACK_Basic_4MB/manifest.py @@ -5,3 +5,4 @@ include("$(MPY_DIR)/../m5stack/modules/startup/manifest_basic.py") include("$(MPY_DIR)/../m5stack/libs/module/manifest.py") include("$(MPY_DIR)/../m5stack/libs/unit/manifest.py") +include("$(MPY_DIR)/../m5stack/libs/chain/manifest.py") diff --git a/m5stack/boards/M5STACK_Capsule/manifest.py b/m5stack/boards/M5STACK_Capsule/manifest.py index 84bf697e..31c59b33 100644 --- a/m5stack/boards/M5STACK_Capsule/manifest.py +++ b/m5stack/boards/M5STACK_Capsule/manifest.py @@ -4,3 +4,4 @@ include("$(MPY_DIR)/../m5stack/modules/startup/manifest_capsule.py") include("$(MPY_DIR)/../m5stack/libs/unit/manifest.py") +include("$(MPY_DIR)/../m5stack/libs/chain/manifest.py") diff --git a/m5stack/boards/M5STACK_Cardputer/manifest.py b/m5stack/boards/M5STACK_Cardputer/manifest.py index 5561d797..d58923e7 100644 --- a/m5stack/boards/M5STACK_Cardputer/manifest.py +++ b/m5stack/boards/M5STACK_Cardputer/manifest.py @@ -4,3 +4,4 @@ include("$(MPY_DIR)/../m5stack/modules/startup/manifest_cardputer.py") include("$(MPY_DIR)/../m5stack/libs/unit/manifest.py") +include("$(MPY_DIR)/../m5stack/libs/chain/manifest.py") diff --git a/m5stack/boards/M5STACK_CardputerADV/manifest.py b/m5stack/boards/M5STACK_CardputerADV/manifest.py index a0cbc2d3..c9912c27 100644 --- a/m5stack/boards/M5STACK_CardputerADV/manifest.py +++ b/m5stack/boards/M5STACK_CardputerADV/manifest.py @@ -5,3 +5,4 @@ include("$(MPY_DIR)/../m5stack/modules/startup/manifest_cardputeradv.py") include("$(MPY_DIR)/../m5stack/libs/cap/manifest.py") include("$(MPY_DIR)/../m5stack/libs/unit/manifest.py") +include("$(MPY_DIR)/../m5stack/libs/chain/manifest.py") diff --git a/m5stack/boards/M5STACK_Core2/manifest.py b/m5stack/boards/M5STACK_Core2/manifest.py index 82b3e47e..43152f3b 100644 --- a/m5stack/boards/M5STACK_Core2/manifest.py +++ b/m5stack/boards/M5STACK_Core2/manifest.py @@ -6,3 +6,4 @@ include("$(MPY_DIR)/../m5stack/libs/m5ui/manifest.py") include("$(MPY_DIR)/../m5stack/libs/module/manifest.py") include("$(MPY_DIR)/../m5stack/libs/unit/manifest.py") +include("$(MPY_DIR)/../m5stack/libs/chain/manifest.py") diff --git a/m5stack/boards/M5STACK_CoreInk/manifest.py b/m5stack/boards/M5STACK_CoreInk/manifest.py index f534a7d4..53e43ee0 100644 --- a/m5stack/boards/M5STACK_CoreInk/manifest.py +++ b/m5stack/boards/M5STACK_CoreInk/manifest.py @@ -5,3 +5,4 @@ include("$(MPY_DIR)/../m5stack/modules/startup/manifest_coreink.py") include("$(MPY_DIR)/../m5stack/libs/hat/manifest.py") include("$(MPY_DIR)/../m5stack/libs/unit/manifest.py") +include("$(MPY_DIR)/../m5stack/libs/chain/manifest.py") diff --git a/m5stack/boards/M5STACK_CoreS3/manifest.py b/m5stack/boards/M5STACK_CoreS3/manifest.py index 7451b48c..56a9137e 100644 --- a/m5stack/boards/M5STACK_CoreS3/manifest.py +++ b/m5stack/boards/M5STACK_CoreS3/manifest.py @@ -8,3 +8,4 @@ include("$(MPY_DIR)/../m5stack/libs/unit/manifest.py") include("$(MPY_DIR)/../m5stack/libs/usb/manifest.py") freeze("$(MPY_DIR)/../m5stack/libs/", "m5camera.py") +include("$(MPY_DIR)/../m5stack/libs/chain/manifest.py") diff --git a/m5stack/boards/M5STACK_Dial/manifest.py b/m5stack/boards/M5STACK_Dial/manifest.py index bfd6d92a..38b41362 100644 --- a/m5stack/boards/M5STACK_Dial/manifest.py +++ b/m5stack/boards/M5STACK_Dial/manifest.py @@ -5,3 +5,4 @@ include("$(MPY_DIR)/../m5stack/modules/startup/manifest_dial.py") include("$(MPY_DIR)/../m5stack/libs/m5ui/manifest.py") include("$(MPY_DIR)/../m5stack/libs/unit/manifest.py") +include("$(MPY_DIR)/../m5stack/libs/chain/manifest.py") diff --git a/m5stack/boards/M5STACK_DinMeter/manifest.py b/m5stack/boards/M5STACK_DinMeter/manifest.py index c929de7b..254a7044 100644 --- a/m5stack/boards/M5STACK_DinMeter/manifest.py +++ b/m5stack/boards/M5STACK_DinMeter/manifest.py @@ -4,3 +4,4 @@ include("$(MPY_DIR)/../m5stack/modules/startup/manifest_dinmeter.py") include("$(MPY_DIR)/../m5stack/libs/unit/manifest.py") +include("$(MPY_DIR)/../m5stack/libs/chain/manifest.py") diff --git a/m5stack/boards/M5STACK_DualKey/manifest.py b/m5stack/boards/M5STACK_DualKey/manifest.py index f4e41bde..c7d3d28c 100644 --- a/m5stack/boards/M5STACK_DualKey/manifest.py +++ b/m5stack/boards/M5STACK_DualKey/manifest.py @@ -5,3 +5,4 @@ include("$(MPY_DIR)/../m5stack/modules/startup/manifest_dualkey.py") include("$(MPY_DIR)/../m5stack/libs/unit/manifest.py") include("$(MPY_DIR)/../m5stack/libs/usb/manifest.py") +include("$(MPY_DIR)/../m5stack/libs/chain/manifest.py") diff --git a/m5stack/boards/M5STACK_Fire/manifest.py b/m5stack/boards/M5STACK_Fire/manifest.py index 61e6296a..81916389 100644 --- a/m5stack/boards/M5STACK_Fire/manifest.py +++ b/m5stack/boards/M5STACK_Fire/manifest.py @@ -5,3 +5,4 @@ include("$(MPY_DIR)/../m5stack/modules/startup/manifest_fire.py") include("$(MPY_DIR)/../m5stack/libs/module/manifest.py") include("$(MPY_DIR)/../m5stack/libs/unit/manifest.py") +include("$(MPY_DIR)/../m5stack/libs/chain/manifest.py") diff --git a/m5stack/boards/M5STACK_NanoC6/manifest.py b/m5stack/boards/M5STACK_NanoC6/manifest.py index d6e66932..05492177 100644 --- a/m5stack/boards/M5STACK_NanoC6/manifest.py +++ b/m5stack/boards/M5STACK_NanoC6/manifest.py @@ -4,3 +4,4 @@ include("$(MPY_DIR)/../m5stack/modules/startup/manifest_nanoc6.py") include("$(MPY_DIR)/../m5stack/libs/unit/manifest.py") +include("$(MPY_DIR)/../m5stack/libs/chain/manifest.py") diff --git a/m5stack/boards/M5STACK_Paper/manifest.py b/m5stack/boards/M5STACK_Paper/manifest.py index 8e3a724b..f642387d 100644 --- a/m5stack/boards/M5STACK_Paper/manifest.py +++ b/m5stack/boards/M5STACK_Paper/manifest.py @@ -4,3 +4,4 @@ include("$(MPY_DIR)/../m5stack/modules/startup/manifest_paper.py") include("$(MPY_DIR)/../m5stack/libs/unit/manifest.py") +include("$(MPY_DIR)/../m5stack/libs/chain/manifest.py") diff --git a/m5stack/boards/M5STACK_PaperS3/manifest.py b/m5stack/boards/M5STACK_PaperS3/manifest.py index 2374477e..0da39649 100644 --- a/m5stack/boards/M5STACK_PaperS3/manifest.py +++ b/m5stack/boards/M5STACK_PaperS3/manifest.py @@ -4,3 +4,4 @@ include("$(MPY_DIR)/../m5stack/modules/startup/manifest_papers3.py") include("$(MPY_DIR)/../m5stack/libs/unit/manifest.py") +include("$(MPY_DIR)/../m5stack/libs/chain/manifest.py") diff --git a/m5stack/boards/M5STACK_PowerHub/manifest.py b/m5stack/boards/M5STACK_PowerHub/manifest.py index d9ac99a0..d4b6c358 100644 --- a/m5stack/boards/M5STACK_PowerHub/manifest.py +++ b/m5stack/boards/M5STACK_PowerHub/manifest.py @@ -4,3 +4,4 @@ include("$(MPY_DIR)/../m5stack/modules/startup/manifest_powerhub.py") include("$(MPY_DIR)/../m5stack/libs/unit/manifest.py") +include("$(MPY_DIR)/../m5stack/libs/chain/manifest.py") diff --git a/m5stack/boards/M5STACK_StamPLC/manifest.py b/m5stack/boards/M5STACK_StamPLC/manifest.py index e9117711..98325b70 100644 --- a/m5stack/boards/M5STACK_StamPLC/manifest.py +++ b/m5stack/boards/M5STACK_StamPLC/manifest.py @@ -4,3 +4,4 @@ include("$(MPY_DIR)/../m5stack/modules/startup/manifest_stamplc.py") include("$(MPY_DIR)/../m5stack/libs/unit/manifest.py") +include("$(MPY_DIR)/../m5stack/libs/chain/manifest.py") diff --git a/m5stack/boards/M5STACK_StampS3/manifest.py b/m5stack/boards/M5STACK_StampS3/manifest.py index 98fdf5c0..5833b82a 100644 --- a/m5stack/boards/M5STACK_StampS3/manifest.py +++ b/m5stack/boards/M5STACK_StampS3/manifest.py @@ -4,3 +4,4 @@ include("$(MPY_DIR)/../m5stack/modules/startup/manifest_stamps3.py") include("$(MPY_DIR)/../m5stack/libs/unit/manifest.py") +include("$(MPY_DIR)/../m5stack/libs/chain/manifest.py") diff --git a/m5stack/boards/M5STACK_Stamp_PICO/manifest.py b/m5stack/boards/M5STACK_Stamp_PICO/manifest.py index 738c6a3a..2b7a2800 100644 --- a/m5stack/boards/M5STACK_Stamp_PICO/manifest.py +++ b/m5stack/boards/M5STACK_Stamp_PICO/manifest.py @@ -4,3 +4,4 @@ include("$(MPY_DIR)/../m5stack/modules/startup/manifest_atoms3lite.py") include("$(MPY_DIR)/../m5stack/libs/unit/manifest.py") +include("$(MPY_DIR)/../m5stack/libs/chain/manifest.py") diff --git a/m5stack/boards/M5STACK_Station/manifest.py b/m5stack/boards/M5STACK_Station/manifest.py index a11297f7..2926201c 100644 --- a/m5stack/boards/M5STACK_Station/manifest.py +++ b/m5stack/boards/M5STACK_Station/manifest.py @@ -4,3 +4,4 @@ include("$(MPY_DIR)/../m5stack/modules/startup/manifest_station.py") include("$(MPY_DIR)/../m5stack/libs/unit/manifest.py") +include("$(MPY_DIR)/../m5stack/libs/chain/manifest.py") diff --git a/m5stack/boards/M5STACK_StickC/manifest.py b/m5stack/boards/M5STACK_StickC/manifest.py index 04c9435b..0f5d10a2 100644 --- a/m5stack/boards/M5STACK_StickC/manifest.py +++ b/m5stack/boards/M5STACK_StickC/manifest.py @@ -5,3 +5,4 @@ include("$(MPY_DIR)/../m5stack/modules/startup/manifest_stickc.py") include("$(MPY_DIR)/../m5stack/libs/hat/manifest.py") include("$(MPY_DIR)/../m5stack/libs/unit/manifest.py") +include("$(MPY_DIR)/../m5stack/libs/chain/manifest.py") diff --git a/m5stack/boards/M5STACK_StickC_PLUS/manifest.py b/m5stack/boards/M5STACK_StickC_PLUS/manifest.py index abfd4d19..e58d8a91 100644 --- a/m5stack/boards/M5STACK_StickC_PLUS/manifest.py +++ b/m5stack/boards/M5STACK_StickC_PLUS/manifest.py @@ -5,3 +5,4 @@ include("$(MPY_DIR)/../m5stack/modules/startup/manifest_stickcplus.py") include("$(MPY_DIR)/../m5stack/libs/hat/manifest.py") include("$(MPY_DIR)/../m5stack/libs/unit/manifest.py") +include("$(MPY_DIR)/../m5stack/libs/chain/manifest.py") diff --git a/m5stack/boards/M5STACK_StickC_PLUS2/manifest.py b/m5stack/boards/M5STACK_StickC_PLUS2/manifest.py index abfd4d19..e58d8a91 100644 --- a/m5stack/boards/M5STACK_StickC_PLUS2/manifest.py +++ b/m5stack/boards/M5STACK_StickC_PLUS2/manifest.py @@ -5,3 +5,4 @@ include("$(MPY_DIR)/../m5stack/modules/startup/manifest_stickcplus.py") include("$(MPY_DIR)/../m5stack/libs/hat/manifest.py") include("$(MPY_DIR)/../m5stack/libs/unit/manifest.py") +include("$(MPY_DIR)/../m5stack/libs/chain/manifest.py") diff --git a/m5stack/boards/M5STACK_Tab5/manifest.py b/m5stack/boards/M5STACK_Tab5/manifest.py index 0a67b0b2..36e8827a 100644 --- a/m5stack/boards/M5STACK_Tab5/manifest.py +++ b/m5stack/boards/M5STACK_Tab5/manifest.py @@ -7,3 +7,4 @@ include("$(MPY_DIR)/../m5stack/libs/module/manifest.py") include("$(MPY_DIR)/../m5stack/libs/unit/manifest.py") include("$(MPY_DIR)/../m5stack/libs/usb/manifest.py") +include("$(MPY_DIR)/../m5stack/libs/chain/manifest.py") diff --git a/m5stack/boards/M5STACK_Tough/manifest.py b/m5stack/boards/M5STACK_Tough/manifest.py index a92a065c..f292cdce 100644 --- a/m5stack/boards/M5STACK_Tough/manifest.py +++ b/m5stack/boards/M5STACK_Tough/manifest.py @@ -6,3 +6,4 @@ include("$(MPY_DIR)/../m5stack/libs/m5ui/manifest.py") include("$(MPY_DIR)/../m5stack/libs/module/manifest.py") include("$(MPY_DIR)/../m5stack/libs/unit/manifest.py") +include("$(MPY_DIR)/../m5stack/libs/chain/manifest.py") diff --git a/m5stack/boards/M5STACK_Unit_C6L/manifest.py b/m5stack/boards/M5STACK_Unit_C6L/manifest.py index fb94c77c..faa17f82 100644 --- a/m5stack/boards/M5STACK_Unit_C6L/manifest.py +++ b/m5stack/boards/M5STACK_Unit_C6L/manifest.py @@ -4,3 +4,4 @@ include("$(MPY_DIR)/../m5stack/modules/startup/manifest_unit_c6l.py") include("$(MPY_DIR)/../m5stack/libs/unit/manifest.py") +include("$(MPY_DIR)/../m5stack/libs/chain/manifest.py") diff --git a/m5stack/boards/Nesso_N1/manifest.py b/m5stack/boards/Nesso_N1/manifest.py index 608ff6d3..a4b99028 100644 --- a/m5stack/boards/Nesso_N1/manifest.py +++ b/m5stack/boards/Nesso_N1/manifest.py @@ -5,3 +5,4 @@ include("$(MPY_DIR)/../m5stack/modules/startup/manifest_nesson1.py") include("$(MPY_DIR)/../m5stack/libs/unit/manifest.py") include("$(MPY_DIR)/../m5stack/libs/hat/manifest.py") +include("$(MPY_DIR)/../m5stack/libs/chain/manifest.py") diff --git a/m5stack/libs/chain/__init__.py b/m5stack/libs/chain/__init__.py new file mode 100644 index 00000000..5902fafe --- /dev/null +++ b/m5stack/libs/chain/__init__.py @@ -0,0 +1,18 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +_attrs = { + "ChainBus": "chain", + "JoystickChain": "joystick", + "KeyChain": "key", +} + + +def __getattr__(attr): + mod = _attrs.get(attr, None) + if mod is None: + raise AttributeError(attr) + value = getattr(__import__(mod, None, None, True, 1), attr) + globals()[attr] = value + return value diff --git a/m5stack/libs/chain/chain.py b/m5stack/libs/chain/chain.py new file mode 100644 index 00000000..cdb65a30 --- /dev/null +++ b/m5stack/libs/chain/chain.py @@ -0,0 +1,591 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import machine +import time +import struct +import m5utils +import warnings +import _thread +import micropython + +CMD_SET_RGB_VALUE = 0x20 # Set RGB value +CMD_GET_RGB_VALUE = 0x21 # Get RGB value +CMD_SET_RGB_BRIGHTNESS = 0x22 # Set RGB brightness +CMD_GET_RGB_BRIGHTNESS = 0x23 # Get RGB brightness +CMD_GET_BOOTLOADER_VERSION = 0xF9 # Get bootloader version +CMD_GET_FIRMWARE_VERSION = 0xFA # Get firmware version +CMD_GET_DEVICE_TYPE = 0xFB # Get device type +CMD_ENUM_REQUEST = 0xFC # Enumeration request +CMD_HEARTBEAT = 0xFD # Heartbeat command +CMD_ENUM_RESPONSE = 0xFE # Enumeration response + +STATUS_OK = 0x00 # Operation successful. +STATUS_PARAMETER_ERROR = 0x01 # Parameter error. +STATUS_RETURN_PACKET_ERROR = 0x02 # Return packet error. +STATUS_BUSY = 0x04 # Device is busy. +STATUS_TIMEOUT = 0x05 # Operation timeout. + +CHAIN_DEVICE_TYPE_UNKNOWN = 0x0000 # Unknown device type +CHAIN_DEVICE_TYPE_ENCODER = 0x0001 # Encoder device type +CHAIN_DEVICE_TYPE_ANGLE = 0x0002 # Angle device type +CHAIN_DEVICE_TYPE_KEY = 0x0003 # Key device type +CHAIN_DEVICE_TYPE_JOYSTICK = 0x0004 # Joystick device type +CHAIN_DEVICE_TYPE_TOF = 0x0005 # ToF device type +CHAIN_DEVICE_TYPE_UART = 0x0006 # UART device type +CHAIN_DEVICE_TYPE_SWITCH = 0x0007 # Switch device type +CHAIN_DEVICE_TYPE_PEDAL = 0x0008 # Pedal device type +CHAIN_DEVICE_TYPE_PIR = 0x0009 # PIR device type +CHAIN_DEVICE_TYPE_MIC = 0x000A # Microphone device type + + +class RecvData: + def __init__(self, state, length, data, crc): + self.recv_time = time.ticks_ms() + self.state = state + self.length = length + self.device_id = 0 + self.cmd = 0 + self.payload = data + self.crc = crc + self.payload_len = 0 + + +class ChainLinkLayer: + RECV_STATE_HEAD1 = 0x01 + RECV_STATE_HEAD2 = 0x02 + RECV_STATE_LEN1 = 0x03 + RECV_STATE_LEN2 = 0x04 + RECV_STATE_DEVICE_ID = 0x05 + RECV_STATE_CMD = 0x06 + RECV_STATE_PAYLOAD = 0x07 + RECV_STATE_CRC = 0x08 + RECV_STATE_TAIL1 = 0x09 + RECV_STATE_TAIL2 = 0x0A + + def __init__(self, uart, verbose=False): + self.uart = uart + self._verbose = verbose + self._recv_data = RecvData(self.RECV_STATE_HEAD1, 0, bytearray(), 0) + self._packet_queue = [] + self._device_num = 0 + + # CRC 校验算法:就是求和 + def _crc(self, indx, cmd, data): + crc = 0 + crc += cmd + crc += indx + crc += sum(data) + return crc & 0xFF + + # 发送完整数据包 + def _send_packet(self, index, cmd, data=b""): + # 构建数据体: [长度低, 长度高, 索引, 命令] + 数据 + packet_len = 2 + 2 + 1 + 1 + len(data) + 1 + 2 + packet = bytearray(packet_len) + + # 帧头 + offset = 0 + struct.pack_into(">> [Chain]: ", end="") + print(" ".join(["%02X" % b for b in packet])) + + self.uart.write(packet) + + # 接收并解析数据包 + def _decode_packet(self, buf: bytes, timeout_ms=None) -> bool: + if self._verbose: + print("[HOST] <<< [Chain]: ", end="") + print(" ".join(["%02X" % b for b in buf])) + + decoded = False + + for b in buf: + # if self._verbose: + # print("current state:", self._recv_data.state) + if self._recv_data.state == self.RECV_STATE_HEAD1: + # 查找帧头 0xAA + if b == 0xAA: + # 初始化接收数据 + self._recv_data.recv_time = time.ticks_ms() + self._recv_data.payload = bytearray() + self._recv_data.length = 0 + self._recv_data.payload_len = 0 + self._recv_data.crc = 0 + # 转到下一个状态 + self._recv_data.state = self.RECV_STATE_HEAD2 + elif self._recv_data.state == self.RECV_STATE_HEAD2: + # 查找帧头 55 + if b == 0x55: + self._recv_data.state = self.RECV_STATE_LEN1 + else: + self._recv_data.state = self.RECV_STATE_HEAD1 + elif self._recv_data.state == self.RECV_STATE_LEN1: + self._recv_data.length = b + self._recv_data.state = self.RECV_STATE_LEN2 + elif self._recv_data.state == self.RECV_STATE_LEN2: + self._recv_data.length = self._recv_data.length | (b << 8) + self._recv_data.payload_len = 0 + self._recv_data.state = self.RECV_STATE_DEVICE_ID + elif self._recv_data.state == self.RECV_STATE_DEVICE_ID: + self._recv_data.device_id = b + self._recv_data.state = self.RECV_STATE_CMD + elif self._recv_data.state == self.RECV_STATE_CMD: + self._recv_data.cmd = b + if self._recv_data.length - 3 == 0: + self._recv_data.state = self.RECV_STATE_CRC + else: + self._recv_data.state = self.RECV_STATE_PAYLOAD + elif self._recv_data.state == self.RECV_STATE_PAYLOAD: + self._recv_data.payload.append(b) + self._recv_data.payload_len += 1 + if self._recv_data.payload_len == self._recv_data.length - 3: + self._recv_data.state = self.RECV_STATE_CRC + elif self._recv_data.state == self.RECV_STATE_CRC: + self._recv_data.crc = b + self._recv_data.state = self.RECV_STATE_TAIL1 + elif self._recv_data.state == self.RECV_STATE_TAIL1: + if b == 0x55: + self._recv_data.state = self.RECV_STATE_TAIL2 + else: + self._recv_data.state = self.RECV_STATE_HEAD1 + elif self._recv_data.state == self.RECV_STATE_TAIL2: + self._recv_data.state = self.RECV_STATE_HEAD1 + if b == 0xAA: + crc = self._crc( + self._recv_data.cmd, self._recv_data.device_id, self._recv_data.payload + ) + if crc != self._recv_data.crc: + warnings.warn("CRC check failed") + else: + if self._recv_data.cmd not in (CMD_HEARTBEAT, CMD_ENUM_REQUEST): + self._packet_queue.append( + ( + self._recv_data.recv_time, + self._recv_data.device_id, + self._recv_data.cmd, + self._recv_data.payload, + ) + ) + if self._verbose: + print("[HOST] <<< [Chain]: ", end="") + print( + "time=%d,device_id=%02X,cmd=%02X,payload=%s" + % ( + self._recv_data.recv_time, + self._recv_data.device_id, + self._recv_data.cmd, + " ".join(["%02X" % b for b in self._recv_data.payload]), + ) + ) + decoded = True + return decoded + + def _clear_old_packets(self): + """清理队列中超过 5 秒的旧数据包。""" + current_time = time.ticks_ms() + for i in range(len(self._packet_queue) - 1, -1, -1): + pkt_time = self._packet_queue[i][0] + if time.ticks_diff(current_time, pkt_time) > 5000: + del self._packet_queue[i] + + def _receive_packet( + self, device_id, cmd, remove_repeated=False, timeout_ms=3000 + ) -> (bool, bytearray): + """从队列中等待并获取匹配的数据包。 + + - device_id/cmd: 目标设备与命令。 + - remove_repeated: 若为 True,则删除队列中所有与 (device_id, cmd) 匹配的重复项, + 并返回其中最新的一条;若为 False,则返回并移除遇到的第一条。 + - timeout_ms: 超时毫秒数。 + 返回 (state, payload): state=True 表示获取成功,payload 为数据;否则返回 (False, 空 bytearray)。 + """ + start = time.ticks_ms() + while True: + if not remove_repeated: + # 旧行为:命中第一条即返回 + for i, (_, dev_id, command, payload) in enumerate(self._packet_queue): + if dev_id == device_id and command == cmd: + del self._packet_queue[i] + return (True, payload) + else: + # 删除重复项:收集所有匹配项索引,删除并返回最新一条 + match_indices = [ + i + for i, (_, dev_id, command, _) in enumerate(self._packet_queue) + if dev_id == device_id and command == cmd + ] + if match_indices: + last_idx = match_indices[-1] + payload = self._packet_queue[last_idx][2] + # 反向删除,避免索引移动 + for idx in reversed(match_indices): + del self._packet_queue[idx] + return (True, payload) + + if time.ticks_diff(time.ticks_ms(), start) > timeout_ms: + break + time.sleep_ms(10) + return (False, bytearray()) + + def receive_packet(self, device_id, cmd, remove_repeated=False) -> (bool, bytearray): + """从队列中等待并获取匹配的数据包。 + + - device_id/cmd: 目标设备与命令。 + - remove_repeated: 若为 True,则删除队列中所有与 (device_id, cmd) 匹配的重复项, + 并返回其中最新的一条;若为 False,则返回并移除遇到的第一条。 + - timeout_ms: 超时毫秒数。 + 返回 (state, payload): state=True 表示获取成功,payload 为数据;否则返回 (False, 空 bytearray)。 + """ + + if not remove_repeated: + # 旧行为:命中第一条即返回 + for i, (_, dev_id, command, payload) in enumerate(self._packet_queue): + if dev_id == device_id and command == cmd: + del self._packet_queue[i] + return (True, payload) + else: + # 删除重复项:收集所有匹配项索引,删除并返回最新一条 + match_indices = [ + i + for i, (_, dev_id, command, _) in enumerate(self._packet_queue) + if dev_id == device_id and command == cmd + ] + if match_indices: + last_idx = match_indices[-1] + payload = self._packet_queue[last_idx][2] + # 反向删除,避免索引移动 + for idx in reversed(match_indices): + del self._packet_queue[idx] + return (True, payload) + return (False, bytearray()) + + def set_rgb_color(self, device_id: int, index: int, color: int) -> bool: + """set RGB color. + + :param device_id: Device ID. + :param index: Index of the RGB LED. + :param color: RGB color value. + """ + payload = struct.pack("BB3s", index & 0xFF, 1, color.to_bytes(3, "big")) + state, response = self.send(device_id, CMD_SET_RGB_VALUE, payload) + if state: + return response[0] == 1 + return False + + def fill_rgb_color(self, device_id: int, index: int, num: int, color: int) -> bool: + """fill RGB color. + :param int device_id: Device ID. + :param int index: Start index of the RGB LED. + :param int num: Number of LEDs to fill. + :param int color: RGB color value. + """ + payload = struct.pack( + "BB%ds" % (num * 3), index & 0xFF, num & 0xFF, color.to_bytes(3, "big") * num + ) + state, response = self.send(device_id, CMD_SET_RGB_VALUE, payload) + if state: + if response[0] == 1: + return True + return False + + def get_rgb_color(self, device_id: int, index: int) -> int: + """Get RGB color. + + :param int device_id: Device ID. + :param int index: Index of the RGB LED. + :return: RGB color value. + """ + payload = struct.pack("BB", index & 0xFF, 1) + state, response = self.send(device_id, CMD_GET_RGB_VALUE, payload) + if state and response and len(response) == 4: + if response[0] == 1: + r = response[2] + g = response[3] + b = response[4] + return (r << 16) | (g << 8) | b + return -1 + + def set_rgb_brightness(self, device_id: int, brightness: int, save: bool = False) -> bool: + """Set rgb brightness. + + :param int device_id: Device ID. + :param int brightness: Brightness value (0-100). + :param bool save: Whether to save the brightness value to flash. + """ + payload = struct.pack("BB", brightness & 0xFF, 1 if save else 0) + state, response = self.send(device_id, CMD_SET_RGB_BRIGHTNESS, payload) + if state and response: + return response[0] == 1 + return False + + def get_rgb_brightness(self, device_id: int) -> int: + """Get rgb brightness.""" + state, response = self.send(device_id, CMD_GET_RGB_BRIGHTNESS, bytes()) + if state and response: + return response[0] + return -1 + + def get_bootloader_version(self, device_id: int) -> int: + """Get bootloader version. + + :param int device_id: Device ID. + :return: Bootloader version. + """ + state, response = self.send(device_id, CMD_GET_BOOTLOADER_VERSION, bytes()) + if state and response: + return response[0] + return -1 + + def get_firmware_version(self, device_id: int) -> int: + """Get firmware version. + + :param int device_id: Device ID. + :return: Firmware version. + """ + state, response = self.send(device_id, CMD_GET_FIRMWARE_VERSION, bytes()) + if state and response: + return response[0] + return -1 + + def get_device_type(self, device_id: int) -> int: + """Get device type. + + :param int device_id: Device ID. + :return: Device type. + """ + state, response = self.send(device_id, CMD_GET_DEVICE_TYPE, bytes()) + if state and response: + return struct.unpack(" int: + """Get connected device number.""" + state, response = self.send(0xFF, CMD_ENUM_RESPONSE, b"\x00") + if state and response: + self._device_num = response[0] + return self._device_num + return 0 + + def send(self, device_id: int, cmd: int, payload: bytes) -> (bool, bytes): + """Send custom command to device. + + :param int device_id: Device ID. + :param int cmd: Command. + :param bytes payload: Data. + :return: True if success, False otherwise. + """ + if device_id != 0xFF and device_id > self._device_num: + warnings.warn(f"Device ID {device_id} is disconnected.") + for _ in range(3): + self._send_packet(device_id, cmd, payload) + time.sleep_ms(10) + state, response = self._receive_packet(device_id, cmd) + if state: + return (state, response) + return (False, b"") + + +class ChainBus: + """Create a Chain bus instance. + + :param int id: UART ID. + :param int tx: TX pin. + :param int rx: RX pin. + :param bool verbose: Enable verbose mode. Default is False. + + UiFlow2 Code Block: + + |get_device_num.png| + + MicroPython Code Block: + + .. code-block:: python + + from chain import ChainBus + + chainbus_0 = ChainBus(2, 32, 33, verbose=True) + """ + + _instance = None + + def __new__(cls, *args, **kwargs): + if cls._instance is None: + cls._instance = super().__new__(cls) + cls._instance._initialized = False + return cls._instance + + def __init__(self, id, tx, rx, verbose=False): + if self._initialized: + return + self.uart = machine.UART(id, baudrate=115200, tx=tx, rx=rx, rxbuf=2048) + if self.uart.any() > 0: + self.uart.read() # flush rx buffer + self._verbose = verbose + + self._device = [] + self._event = [] + self.chainll = ChainLinkLayer(self.uart, verbose=self._verbose) + # self.timer = m5utils.Timer( + # 0, mode=m5utils.Timer.ONE_SHOT, period=3000, callback=self._device_connect_task + # ) + self.new_device_handler = None + self.disconnect_device_handler = None + self._running = True + _thread.start_new_thread(self._recv_task, ()) + self.device_num = self.chainll.get_device_num() + self._initialized = True + + def register_device(self, device): + """Register a Chain device. + + :param ChainDevice device: Chain device instance. + """ + self._device.append(device) + + def register_event(self, device, cmd, payload, callback): + self._event.append((device, cmd, payload, callback)) + + def send(self, device_id: int, cmd: int, payload: bytes, timeout_ms: int) -> bytes: + """Send custom command to device. + + :param int device_id: Device ID. + :param int cmd: Command. + :param bytes payload: Data. + :param int timeout_ms: receive timeout in milliseconds. + + :return: Response data. + :rtype: bytes + + UiFlow2 Code Block: + + |send.png| + + MicroPython Code Block: + + .. code-block:: python + + chainbus_0.send(1, 0x20, b"\x00\x01\xFF\x00\x00", 3000) + """ + for _ in range(3): + self.chainll._send_packet(device_id, cmd, payload) + state, response = self.chainll._receive_packet(device_id, cmd, timeout_ms=timeout_ms) + if state: + return response + return b"" + + def get_device_num(self) -> int: + """Get connected device number. + + :return: Number of connected devices. + :rtype: int + + UiFlow2 Code Block: + + |get_device_num.png| + + MicroPython Code Block: + + .. code-block:: python + + num = chainbus_0.get_device_num() + """ + return self.chainll.get_device_num() + + def set_device_connected_handler(self, handler): + """Set new device connection handler callback. + + :param function handler: Callback function. + """ + self.new_device_handler = handler + + def set_device_disconnected_handler(self, handler): + """Set device disconnection handler callback. + + :param function handler: Callback function. + """ + self.disconnect_device_handler = handler + + def _recv_task(self): + decoded = False + last_clear_time = time.ticks_ms() + while self._running: + # recv data + if self.uart.any() > 0: + decoded = self.chainll._decode_packet(self.uart.read()) + + # handle registered events + if decoded: + for device, cmd, payload, callback in self._event: + state, response = self.chainll.receive_packet(device.device_id, cmd) + if state and response == bytearray(payload) and callback: + micropython.schedule(callback, None) + + # 5s clear old packets in queue + if time.ticks_ms() - last_clear_time > 5000: + self.chainll._clear_old_packets() + last_clear_time = time.ticks_ms() + + time.sleep_ms(10) + + def _device_connect_task(self, timer): + new_device_num = self.chainll.get_device_num() + if new_device_num < self.device_num: + # device disconnected + if self.disconnect_device_handler: + for device_id in range(new_device_num + 1, self.device_num + 1): + micropython.schedule(self.disconnect_device_handler, (device_id,)) + elif new_device_num > self.device_num: + # new device connected + if self.new_device_handler: + for device_id in range(self.device_num + 1, new_device_num + 1): + device_type = self.chainll.get_device_type(device_id) + micropython.schedule(self.new_device_handler, (device_id, device_type)) + self.device_num = new_device_num + self.timer.init( + period=3000, mode=m5utils.Timer.ONE_SHOT, callback=self._device_connect_task + ) + + def deinit(self): + """Deinitialize the Chain bus.""" + if hasattr(self, "timer"): + self.timer.deinit() + self._running = False + ChainBus._instance = None + self._initialized = False diff --git a/m5stack/libs/chain/joystick.py b/m5stack/libs/chain/joystick.py new file mode 100644 index 00000000..02b1bb7b --- /dev/null +++ b/m5stack/libs/chain/joystick.py @@ -0,0 +1,268 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +from .chain import ChainBus +from .key import KeyChain +import struct + + +class JoystickChain(KeyChain): + """Joystick Chain class for interacting with joystick devices over Chain bus. + + :param ChainBus bus: The Chain bus instance. + :param int device_id: The device ID of the joystick on the Chain bus. + + UiFlow2 Code Block: + + |init.png| + + MicroPython Code Block: + + .. code-block:: python + + from chain import ChainBus + from chain import JoystickChain + + chainbus_0 = ChainBus(2, 32, 33, verbose=True) + joystick_0 = JoystickChain(chainbus_0, 1) + """ + + CMD_GET_POSITION_16ADC = 0x30 + CMD_GET_POSITION_8ADC = 0x31 + CMD_GET_MAPPING = 0x32 + CMD_SET_MAPPING = 0x33 + CMD_GET_POSITION_16BIT = 0x34 + CMD_GET_POSITION_8BIT = 0x35 + + def __init__(self, bus: ChainBus, device_id: int): + super().__init__(bus, device_id) + + def get_x(self) -> int: + """Get the X position of the joystick. + + :return: X position (-128 to 127). + :rtype: int + + UiFlow2 Code Block: + + |get_8bit_value.png| + + MicroPython Code Block: + + .. code-block:: python + + x = joystick_0.get_x() + """ + state, response = self.bus.chainll.send( + self.device_id, self.CMD_GET_POSITION_8BIT, bytes() + ) + if state: + return response[0] + return 0 + + def get_y(self) -> int: + """Get the Y position of the joystick. + + :return: Y position (-128 to 127). + :rtype: int + + UiFlow2 Code Block: + + |get_8bit_value.png| + + MicroPython Code Block: + + .. code-block:: python + + y = joystick_0.get_y() + """ + state, response = self.bus.chainll.send( + self.device_id, self.CMD_GET_POSITION_8BIT, bytes() + ) + if state: + return response[1] + return 0 + + def get_x_16bit(self) -> int: + """Get the X position of the joystick in 16-bit resolution. + + :return: X position (-4095 to 4095). + :rtype: int + + UiFlow2 Code Block: + + |get_16bit_value.png| + + MicroPython Code Block: + + .. code-block:: python + + x = joystick_0.get_x_16bit() + """ + state, response = self.bus.chainll.send( + self.device_id, self.CMD_GET_POSITION_16BIT, bytes() + ) + if state: + return struct.unpack(">HH", response)[0] + return 0 + + def get_y_16bit(self) -> int: + """Get the Y position of the joystick in 16-bit resolution. + + :return: Y position (-4095 to 4095). + :rtype: int + + UiFlow2 Code Block: + + |get_16bit_value.png| + + MicroPython Code Block: + + .. code-block:: python + + y = joystick_0.get_y_16bit() + """ + state, response = self.bus.chainll.send( + self.device_id, self.CMD_GET_POSITION_16BIT, bytes() + ) + if state: + return struct.unpack(">HH", response)[1] + return 0 + + def get_x_raw(self) -> int: + """Get the raw X ADC value of the joystick. + + :return: Raw X ADC value (0-255). + :rtype: int + + UiFlow2 Code Block: + + |get_raw_value.png| + + MicroPython Code Block: + + .. code-block:: python + + x = joystick_0.get_x_raw() + """ + state, response = self.bus.chainll.send( + self.device_id, self.CMD_GET_POSITION_8ADC, bytes() + ) + if state: + return response[0] + return 0 + + def get_y_raw(self) -> int: + """Get the raw Y ADC value of the joystick. + + :return: Raw Y ADC value (0-255). + :rtype: int + + UiFlow2 Code Block: + + |get_raw_value.png| + + MicroPython Code Block: + + .. code-block:: python + + y = joystick_0.get_y_raw() + """ + state, response = self.bus.chainll.send( + self.device_id, self.CMD_GET_POSITION_8ADC, bytes() + ) + if state: + return response[1] + return 0 + + def get_x_16bit_raw(self) -> int: + """Get the raw X ADC value of the joystick in 16-bit resolution. + + :return: Raw X ADC value (0-65535). + :rtype: int + + UiFlow2 Code Block: + + |get_raw_16bit_value.png| + + MicroPython Code Block: + + .. code-block:: python + + x = joystick_0.get_x_16bit_raw() + """ + state, response = self.bus.chainll.send( + self.device_id, self.CMD_GET_POSITION_16ADC, bytes() + ) + if state: + return struct.unpack(">HH", response)[0] + return 0 + + def get_y_16bit_raw(self) -> int: + """Get the raw Y ADC value of the joystick in 16-bit resolution. + + :return: Raw Y ADC value (0-65535). + :rtype: int + + UiFlow2 Code Block: + + |get_raw_16bit_value.png| + + MicroPython Code Block: + + .. code-block:: python + + y = joystick_0.get_y_16bit_raw() + """ + state, response = self.bus.chainll.send( + self.device_id, self.CMD_GET_POSITION_16ADC, bytes() + ) + if state: + return struct.unpack(">HH", response)[1] + return 0 + + def get_mapping_value(self) -> tuple: + """Get the mapping values of the joystick. + + :return: A tuple containing the mapping values (x_negative_min, x_negative_max, x_positive_min, x_positive_max, y_negative_min, y_negative_max, y_positive_min, y_positive_max). + :rtype: tuple + + UiFlow2 Code Block: + + |get_mapping_value.png| + + MicroPython Code Block: + + .. code-block:: python + + mapping = joystick_0.get_mapping_value() + """ + state, response = self.bus.chainll.send(self.device_id, self.CMD_GET_MAPPING, bytes()) + if state: + return struct.unpack(">HHHHHHHH", response) + return (0, 0, 0, 0, 0, 0, 0, 0) + + def set_mapping_value(self, value: tuple, save=False) -> bool: + """Set the mapping values of the joystick. + + :param tuple value: A tuple containing the mapping values (x_negative_min, x_negative_max, x_positive_min, x_positive_max, y_negative_min, y_negative_max, y_positive_min, y_positive_max). + :param bool save: Whether to save the mapping values to non-volatile memory. + :return: True if the mapping values were set successfully, False otherwise. + :rtype: bool + + UiFlow2 Code Block: + + |set_mapping_value.png| + + MicroPython Code Block: + + .. code-block:: python + + success = joystick_0.set_mapping_value((100, 200, 300, 400, 100, 200, 300, 400), True) + """ + data = struct.pack(">HHHHHHHHB", *value, 1 if save else 0) + state, response = self.bus.chainll.send(self.device_id, self.CMD_SET_MAPPING, data) + if state: + return response[0] == 1 + return False diff --git a/m5stack/libs/chain/key.py b/m5stack/libs/chain/key.py new file mode 100644 index 00000000..95c03f48 --- /dev/null +++ b/m5stack/libs/chain/key.py @@ -0,0 +1,396 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import struct +import warnings +from .chain import ChainBus + + +class KeyChain: + """Create a KeyChain object. + + :param ChainBus bus: ChainBus object. + :param int device_id: Device ID. + + UiFlow2 Code Block: + + |init.png| + + MicroPython Code Block: + + .. code-block:: python + + from chain import ChainBus + from chain import KeyChain + + chainbus_0 = ChainBus(2, 32, 33, verbose=True) + keychain_0 = KeyChain(chainbus_0, 1) + + """ + + CMD_KEY_PRESS = 0xE0 + CMD_KEY_STATE = 0xE1 + CMD_SET_BUTTON_TRIGGER_TIMEOUT = 0xE2 + CMD_GET_BUTTON_TRIGGER_TIMEOUT = 0xE3 + CMD_SET_BUTTON_MODE = 0xE4 + CMD_GET_BUTTON_MODE = 0xE5 + + MODE_POLL = 0x00 + """Button Polling mode""" + MODE_EVENT = 0x01 + """Button Event mode""" + + def __init__(self, bus: ChainBus, device_id: int): + self.bus = bus + self.device_id = device_id + self.bus.register_device(self) + + def get_button_state(self) -> bool: + """get button state. + + :return: Button state, True if pressed, False otherwise. + :rtype: bool + + UiFlow2 Code Block: + + |get_button_state.png| + + MicroPython Code Block: + + .. code-block:: python + + keychain_0.get_button_state() + """ + state, response = self.bus.chainll.send(self.device_id, self.CMD_KEY_STATE, bytes()) + if state: + return response[0] == 1 + return False + + def set_click_callback(self, callback) -> None: + """set button click callback. + + :param callback: Callback function. + + UiFlow2 Code Block: + + |key_click_callback.png| + + MicroPython Code Block: + + .. code-block:: python + + def keychain_0_click_callback(args): + print("click") + + keychain_0.set_click_callback(keychain_0_click_callback) + """ + self.bus.register_event(self, self.CMD_KEY_PRESS, b"\x00\x00", callback) + + def set_double_click_callback(self, callback) -> None: + """set button double click callback. + + :param callback: Callback function. + + UiFlow2 Code Block: + + |double_click_callback.png| + + MicroPython Code Block: + + .. code-block:: python + + def keychain_0_double_click_callback(args): + print("double click") + + keychain_0.set_double_click_callback(keychain_0_double_click_callback) + """ + self.bus.register_event(self, self.CMD_KEY_PRESS, b"\x01\x00", callback) + + def set_long_press_callback(self, callback) -> None: + """set button long press callback. + + :param callback: Callback function. + + UiFlow2 Code Block: + + |long_press_callback.png| + + MicroPython Code Block: + + .. code-block:: python + + def keychain_0_long_press_callback(args): + print("long press") + + keychain_0.set_long_press_callback(keychain_0_long_press_callback) + """ + self.bus.register_event(self, self.CMD_KEY_PRESS, b"\x02\x00", callback) + + def _set_button_trigger_timeout( + self, double_click_interval: int, long_press_interval: int + ) -> bool: + payload = struct.pack(" bool: + """set button double click trigger interval. + + :param int interval_ms: Interval time in milliseconds. range: 100-1000 + :return: True if success, False otherwise. + :rtype: bool + + UiFlow2 Code Block: + + |set_button_double_click_trigger_interval.png| + + MicroPython Code Block: + + .. code-block:: python + + keychain_0.set_button_double_click_trigger_interval(100) + """ + if interval_ms < 100: + warnings.warn("Interval time too small, set to 100ms") + interval_ms = 100 + elif interval_ms > 1000: + warnings.warn("Interval time too large, set to 1000ms") + interval_ms = 1000 + + double_click_interval = (interval_ms // 100) - 1 + long_press_interval = self.get_button_long_press_trigger_interval() // 1000 - 3 + return self._set_button_trigger_timeout(double_click_interval, long_press_interval) + + def set_button_long_press_trigger_interval(self, interval_ms: int) -> bool: + """set button long press trigger interval. + + :param int interval_ms: Interval time in milliseconds. range: 3000-30000 + :return: True if success, False otherwise. + :rtype: bool + + UiFlow2 Code Block: + + |set_button_long_press_trigger_interval.png| + + MicroPython Code Block: + + .. code-block:: python + + keychain_0.set_button_long_press_trigger_interval(3000) + """ + if interval_ms < 3000: + warnings.warn("Interval time too small, set to 3000ms") + interval_ms = 3000 + elif interval_ms > 10000: + warnings.warn("Interval time too large, set to 10000ms") + interval_ms = 10000 + + long_press_interval = (interval_ms // 1000) - 3 + double_click_interval = self.get_button_double_click_trigger_interval() // 100 - 1 + return self._set_button_trigger_timeout(double_click_interval, long_press_interval) + + def get_button_double_click_trigger_interval(self) -> int: + """get button double click trigger interval. + + :return: Interval time in milliseconds. + :rtype: int + + UiFlow2 Code Block: + + |get_button_double_click_trigger_interval.png| + + MicroPython Code Block: + + .. code-block:: python + + interval = keychain_0.get_button_double_click_trigger_interval() + """ + state, response = self.bus.chainll.send( + self.device_id, self.CMD_GET_BUTTON_TRIGGER_TIMEOUT, bytes() + ) + if state: + return (response[0] + 1) * 100 + return 100 + + def get_button_long_press_trigger_interval(self) -> int: + """get button long press trigger interval. + + :return: Interval time in milliseconds. + :rtype: int + + UiFlow2 Code Block: + + |get_button_long_press_trigger_interval.png| + + MicroPython Code Block: + + .. code-block:: python + + interval = keychain_0.get_button_long_press_trigger_interval() + """ + state, response = self.bus.chainll.send( + self.device_id, self.CMD_GET_BUTTON_TRIGGER_TIMEOUT, bytes() + ) + if state: + return (response[1] + 3) * 1000 + return 3000 + + def set_button_mode(self, mode: int) -> bool: + """set button mode. + + :param int mode: Button mode. Use :attr:`KeyChain.MODE_POLL` or :attr:`KeyChain.MODE_EVENT`. + :return: True if success, False otherwise. + :rtype: bool + + UiFlow2 Code Block: + + |set_button_mode.png| + + MicroPython Code Block: + + .. code-block:: python + + keychain_0.set_button_mode(KeyChain.MODE_EVENT) + """ + state, response = self.bus.chainll.send( + self.device_id, self.CMD_SET_BUTTON_MODE, struct.pack(" int: + """get button mode. + + :return: Button mode. :attr:`KeyChain.MODE_POLL` or :attr:`KeyChain.MODE_EVENT`. + :rtype: int + + UiFlow2 Code Block: + + |get_button_mode.png| + + MicroPython Code Block: + + .. code-block:: python + + mode = keychain_0.get_button_mode() + """ + state, response = self.bus.chainll.send(self.device_id, self.CMD_GET_BUTTON_MODE, bytes()) + if state: + return response[0] + return -1 + + def set_rgb_color(self, color: int) -> bool: + """set RGB color. + + :param int color: RGB color value. + :return: True if success, False otherwise. + :rtype: bool + + UiFlow2 Code Block: + + |set_rgb_color.png| + + MicroPython Code Block: + + .. code-block:: python + + keychain_0.set_rgb_color(0xFF0000) + """ + return self.bus.chainll.set_rgb_color(self.device_id, 0, color) + + def get_rgb_color(self) -> int: + """get RGB color. + + :param index: Index of the RGB LED. + :return: RGB color value. + :rtype: int + + UiFlow2 Code Block: + + |get_rgb_color.png| + + MicroPython Code Block: + + .. code-block:: python + + color = keychain_0.get_rgb_color() + """ + return self.bus.chainll.get_rgb_color(self.device_id, 0) + + def set_rgb_brightness(self, brightness: int, save: bool = False) -> bool: + """set RGB brightness. + + :param int brightness: Brightness value (0-100). + :param bool save: Whether to save the brightness setting to flash. + :return: True if success, False otherwise. + :rtype: bool + + UiFlow2 Code Block: + + |set_rgb_brightness.png| + + MicroPython Code Block: + + .. code-block:: python + + keychain_0.set_rgb_brightness(80) + """ + return self.bus.chainll.set_rgb_brightness(self.device_id, brightness, save) + + def get_rgb_brightness(self) -> int: + """get RGB brightness. + + :return: Brightness value (0-100). + :rtype: int + + UiFlow2 Code Block: + + |get_rgb_brightness.png| + + MicroPython Code Block: + + .. code-block:: python + + brightness = keychain_0.get_rgb_brightness() + """ + return self.bus.chainll.get_rgb_brightness(self.device_id) + + def get_bootloader_version(self) -> int: + """get bootloader version. + + :return: Bootloader version. + :rtype: int + + UiFlow2 Code Block: + + |get_bootloader_version.png| + + MicroPython Code Block: + + .. code-block:: python + + version = keychain_0.get_bootloader_version() + """ + return self.bus.chainll.get_bootloader_version(self.device_id) + + def get_firmware_version(self) -> int: + """get firmware version. + + :return: Firmware version. + :rtype: int + + UiFlow2 Code Block: + + |get_firmware_version.png| + + MicroPython Code Block: + + .. code-block:: python + + version = keychain_0.get_firmware_version() + """ + return self.bus.chainll.get_firmware_version(self.device_id) diff --git a/m5stack/libs/chain/manifest.py b/m5stack/libs/chain/manifest.py new file mode 100644 index 00000000..9af89136 --- /dev/null +++ b/m5stack/libs/chain/manifest.py @@ -0,0 +1,15 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +package( + "chain", + ( + "__init__.py", + "chain.py", + "joystick.py", + "key.py", + ), + base_path="..", + opt=0, +) From e5203af660dc1476a04fec64ef336b1e07db906c Mon Sep 17 00:00:00 2001 From: lbuque <1102390310@qq.com> Date: Fri, 14 Nov 2025 16:32:02 +0800 Subject: [PATCH 320/322] components/M5Unified: Fix Tab5 new screen display issue. Signed-off-by: lbuque <1102390310@qq.com> --- m5stack/components/M5Unified/M5GFX | 2 +- m5stack/components/M5Unified/M5Unified | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/m5stack/components/M5Unified/M5GFX b/m5stack/components/M5Unified/M5GFX index b7ff485b..c2a9cc9f 160000 --- a/m5stack/components/M5Unified/M5GFX +++ b/m5stack/components/M5Unified/M5GFX @@ -1 +1 @@ -Subproject commit b7ff485bc10af8a283180c82e0f0f6580397def9 +Subproject commit c2a9cc9f988122a161a6df607a08aef53a4e4e53 diff --git a/m5stack/components/M5Unified/M5Unified b/m5stack/components/M5Unified/M5Unified index 45e7319e..e60bcb85 160000 --- a/m5stack/components/M5Unified/M5Unified +++ b/m5stack/components/M5Unified/M5Unified @@ -1 +1 @@ -Subproject commit 45e7319ec158138843b1b61574686d1f6ad116d7 +Subproject commit e60bcb854babc1d5346113cf7c6e8b97856031ee From 0f5bf8b1a335b0594eebc9e1ad62a0aafb643c97 Mon Sep 17 00:00:00 2001 From: lbuque <1102390310@qq.com> Date: Fri, 14 Nov 2025 16:45:51 +0800 Subject: [PATCH 321/322] docs: Fix build error. Signed-off-by: lbuque <1102390310@qq.com> --- docs/en/controllers/index.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/controllers/index.rst b/docs/en/controllers/index.rst index 6f73a767..fcc9dca7 100644 --- a/docs/en/controllers/index.rst +++ b/docs/en/controllers/index.rst @@ -15,5 +15,6 @@ Controllers airq.rst paper.rst dinmeter.rst + dualkey.rst nesso-n1.rst unit_c6l.rst From cb5bdaf649b2927e05980daa108dd4412191653f Mon Sep 17 00:00:00 2001 From: lbuque <1102390310@qq.com> Date: Fri, 14 Nov 2025 16:42:02 +0800 Subject: [PATCH 322/322] version.text: Update to V2.3.8. Signed-off-by: lbuque <1102390310@qq.com> --- m5stack/version.txt | 2 +- third-party/version.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/m5stack/version.txt b/m5stack/version.txt index 19814ff5..93462ad1 100644 --- a/m5stack/version.txt +++ b/m5stack/version.txt @@ -1 +1 @@ -V2.3.7 \ No newline at end of file +V2.3.8 \ No newline at end of file diff --git a/third-party/version.txt b/third-party/version.txt index 19814ff5..93462ad1 100644 --- a/third-party/version.txt +++ b/third-party/version.txt @@ -1 +1 @@ -V2.3.7 \ No newline at end of file +V2.3.8 \ No newline at end of file

jah#el=zm9~FLoSc8=M@I>ukC>W*)=$98fwHnHOn1rExCWcP*IjlVf52kWTgDZQP(Y1vFT8tQ=qNs;YwE@7V?D5 z&neH+rB>sk`QU4#(~rFfo$6m)?Q>7|YkoXXNPAH`hnU%^6l)k-OhyMs)vs0SMj#M~ znM2tvFyBY{z6lI{ciXh>fyQ3wjW$Y|7gNtG`n2LCb!hR6xYHvm#M**aRfUQYj3v~w zY4Y+8)w;JeZ{h6OWHwZ-r~7)f zT+{Bfc5lpT+GAV6shuo^Kuc33s`9Kp&MR&>!s8WX&--65A=YD�^P$if7et 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 W(Wx8yHXwNp_^2vsDU?38e)WHP5EY;R literal 0 HcmV?d00001 diff --git a/docs/_static/controllers/atoms3r_cam/connect.png b/docs/_static/controllers/atoms3r_cam/connect.png new file mode 100644 index 0000000000000000000000000000000000000000..3fc53e6b30f41e5e955c4476698b90c561c47bcb GIT binary patch literal 181965 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 z zn%>nOyteKbXk6R*(lw~?epra+r_L2s~Lhe$T?+SA3G5(?tQoG^jVbf*ZPL?>$a3S<9pn4w~y43A2r5jq*I6wH=MNa>)@k-MYlr5&4`zHI7 z+qjdmzr81w3x{2oao2)YkKJpbw}7WF$`AXVXeg8>YPd*9V()X5b41F8Jpn^oeDpkN&acc(v;$9 zj^Tl4b8j62^0x|`O7p0z`r~brvaEfuV@{c$QJ{I+&h-N4+hw5YgO6O<(0^NHBC`gp zY-In4xRGhJ&^?Li8OFmTIa~D5WyVC;g7qQY33rlsxV=tW7SnFMh7 zR`m96vquXs{6Y)WT{uhf0cdYXR{**F&o&0UBLM3e>QLGLLbqh>)9)gxt8n|TT56#T z8=<>o(0%^?5^QPPNdv01y`9TVNy69iu!<)0Vd*wV zR5GkVsFkN`@K!aW$0-UA7g-JwACpizar9Z&X=3epM0RWw5wjyF;iUuY=B;|f50;uS20^kSwUkyph9(We!8;F?4ZvkkC**Lv{u<7Pn z24;!;m+U^^n6L4Ch;BT8KPAM_{M;<4v=8t^L7U~g|I#b9AL3!88fOD^Z^lJtd29;G z!0yH;m^PJ&A+Sg`ynpmgdk7O{SXRVPcWAi&AUkZ<{Ps+3Cl%1r2qQL5BaN2jhDort)qoniS?x9n1!+%EId zT=-F9y`@4JYMp!IOcKP+RBn$yK?UYkCD$%lo($A|F3!q?4k7Llp#iEb7qF|IxFH^Ux0gqtC5UDT}=j(e(1E@5HGzzH`LChR*0oAh7;<=wA zi?DZ3O^`KyzkLS4dsvY-)wygjZuQm?MQ-ftyN_xy&seMOZF5aw=-uBhoz4PjqV`38 z_y+8M_P2nM!2EGkP`J!MPxK75TA`GKL%Ds;yM+bL5bDgwC=4A)PY_R>;IdW8wY5&x zQq)OQx5M|1n^IFJSLLY0RL0L@sn~FF@K2JUfww(v7H#Aw7&=`)%{O4Df z0ivnPBul&Fp-n_OE|^1-vXuX}7*dp^XfjTmL~53)Phx}YT~m<`0TA&J?ISq;QyMl+9Q zg5XU0lr0?*x4F+!7L|tg3NEwggp{WiP{`)wa)9cPh|BW4cSI?l9f34Iu$&@Q&}yXg z^lM|o^?BBRyt1O71VsTkYnM3oX8d}97wmHq{_{RDfFC`$157-1*Ze@x0g?m%0AyT> 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 Id-w7G19{nSYXATM literal 0 HcmV?d00001 diff --git a/docs/_static/controllers/atoms3r_cam/run.png b/docs/_static/controllers/atoms3r_cam/run.png new file mode 100644 index 0000000000000000000000000000000000000000..8e19bb7b1d86e4f15ccba68468e57eddf6a5bac2 GIT binary patch literal 236226 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�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 literal 0 HcmV?d00001 diff --git a/docs/en/controllers/atoms3r_cam.rst b/docs/en/controllers/atoms3r_cam.rst new file mode 100644 index 00000000..0b73d35d --- /dev/null +++ b/docs/en/controllers/atoms3r_cam.rst @@ -0,0 +1,65 @@ +AtomS3R-CAM +============== + +.. sku: C126-CAM + +.. include:: ../refs/controllers.atoms3r_cam.ref + +Support the following products: + + |AtomS3R-CAM| + + +MicroPython Example: +-------------------------- + +Video Streaming +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +This example implements a real-time video streaming server, with integrated QR code recognition. + +MicroPython Code Block: + + .. literalinclude:: ../../../examples/controllers/atoms3r_cam/video_streaming.py + :language: python + :linenos: + +Example output: + + None + + +**How to Use** + +1. Configure Wi-Fi settings. + +.. figure:: ./../../_static/controllers/atoms3r_cam/configure.png + :width: 800 + :align: center + +1. Copy the example code into the editor. + +.. figure:: ./../../_static/controllers/atoms3r_cam/copy_paste_example_code.png + :width: 800 + :align: center + +3. Run the program, After uploading, the console will print the local IP address assigned to the device. + +.. figure:: ./../../_static/controllers/atoms3r_cam/connect.png + :width: 800 + :align: center + +.. figure:: ./../../_static/controllers/atoms3r_cam/run.png + :width: 800 + :align: center + +5. Open the video stream in your browser + +On any device connected to the same Wi-Fi network, open a browser and visit: http://:8080/, +Replace `` with the actual IP address printed in the console. + +.. figure:: ./../../_static/controllers/atoms3r_cam/browser.png + :width: 800 + :align: center + + diff --git a/docs/en/controllers/index.rst b/docs/en/controllers/index.rst index cf98ea67..813eb091 100644 --- a/docs/en/controllers/index.rst +++ b/docs/en/controllers/index.rst @@ -7,6 +7,7 @@ Controllers cores3.rst atoms3.rst atoms3-lite.rst + atoms3r_cam.rst atoms3u.rst stamps3.rst cardputer.rst diff --git a/docs/en/refs/controllers.atoms3r_cam.ref b/docs/en/refs/controllers.atoms3r_cam.ref new file mode 100644 index 00000000..bd9cbb75 --- /dev/null +++ b/docs/en/refs/controllers.atoms3r_cam.ref @@ -0,0 +1,4 @@ +.. |AtomS3R-CAM| image:: https://m5stack.oss-cn-shenzhen.aliyuncs.com/resource/docs/products/core/AtomS3R%20Cam/4.webp + :target: https://docs.m5stack.com/zh_CN/core/AtomS3R%20Cam + :height: 200px + :width: 200px diff --git a/examples/controllers/atoms3r_cam/video_streaming.py b/examples/controllers/atoms3r_cam/video_streaming.py new file mode 100644 index 00000000..ab768d1b --- /dev/null +++ b/examples/controllers/atoms3r_cam/video_streaming.py @@ -0,0 +1,105 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + + +import M5 +from M5 import Display as dis +import camera as cam +import network +import time +import jpg +import socket +import struct +import code_scanner +import sys + +M5.begin() + +# Initialize camera +cam.init(pixformat=cam.RGB565, framesize=cam.QVGA) +cam.set_hmirror(False) + +# Connect to existing WiFi and display IP +wlan = network.WLAN(network.STA_IF) +if wlan.isconnected(): + ip_info = wlan.ifconfig() + ip_addr = ip_info[0] + print("Local IP Address:", ip_addr) + print("\nPlease open your browser and visit http://%s:8080 to view the stream.\n" % ip_addr) +else: + print("WiFi not connected.") + +# Create server socket +s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) +s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True) +s.bind(("0.0.0.0", 8080)) +s.listen(2) +s.setblocking(True) + + +def start_streaming(s): + print("Waiting for client connection...") + client, addr = s.accept() + client.settimeout(5.0) + print("Connected: %s:%d" % (addr[0], addr[1])) + + # Receive HTTP request + try: + data = client.recv(1024) + if not data: + raise Exception("No HTTP request received") + except: + client.close() + return + + # Send MJPEG HTTP response headers + try: + client.sendall( + "HTTP/1.1 200 OK\r\n" + "Server: AtomS3R-CAM\r\n" + "Content-Type: multipart/x-mixed-replace;boundary=atoms3r_cam\r\n" + "Cache-Control: no-cache\r\n" + "Pragma: no-cache\r\n\r\n" + ) + except: + client.close() + return + + # Main video streaming loop + while True: + try: + img = cam.snapshot() + if img is None: + continue + + # QR code detection + qrcode = code_scanner.find_qrcodes(img) + if qrcode: + payload = qrcode.payload() + print("QR Code detected:", payload) + img.draw_string(10, 10, "QRCode: %s" % payload, color=(0, 0, 255), scale=1) + + # Encode and send frame + cframe = jpg.encode(img, 80) + header = ( + "\r\n--atoms3r_cam\r\n" + "Content-Type: image/jpeg\r\n" + "Content-Length: %d\r\n\r\n" % cframe.size() + ) + client.sendall(header) + client.sendall(cframe.bytearray()) + + except Exception as e: + print("Streaming interrupted:", e) + client.close() + break + + +# Main server loop +while True: + try: + start_streaming(s) + except Exception as e: + print("Socket error:", e) + time.sleep(2) From 30ef83b8dfe12357e4e9886e4d9f52c72ba8dc84 Mon Sep 17 00:00:00 2001 From: Tinyu Date: Mon, 21 Apr 2025 11:05:18 +0800 Subject: [PATCH 060/322] docs: Example diagram for fixing the nbiot bug. Signed-off-by: Tinyu --- docs/en/base/dtu_nbiot.rst | 4 ++-- docs/en/refs/base.dtu_nbiot.ref | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/en/base/dtu_nbiot.rst b/docs/en/base/dtu_nbiot.rst index 64eb7fe7..a5469f2e 100644 --- a/docs/en/base/dtu_nbiot.rst +++ b/docs/en/base/dtu_nbiot.rst @@ -26,7 +26,7 @@ This example shows how to send HTTP request using the Atom DTU NBIoT Base. UiFlow2 Code Block: - |example.png| + |http_example.png| Example output: @@ -59,7 +59,7 @@ This example shows how to send MQTT message using the Atom DTU NBIoT Base. UiFlow2 Code Block: - |example.png| + |mqtt_example.png| Example output: diff --git a/docs/en/refs/base.dtu_nbiot.ref b/docs/en/refs/base.dtu_nbiot.ref index 2872585c..f2ef324d 100644 --- a/docs/en/refs/base.dtu_nbiot.ref +++ b/docs/en/refs/base.dtu_nbiot.ref @@ -29,7 +29,9 @@ .. |set_uplink_app_port.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/lorawan470/set_uplink_app_port.png .. |send_data.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/lorawan470/send_data.png -.. |example.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/lorawan470/example.png +.. |http_example.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/lorawan470/example2.png +.. |mqtt_example.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/lorawan470/example3.png + .. |base_nbiot_atoms3_mqtt_example.m5f2| raw:: html From d42d60b8b82726fa5f40ead05736c876cbc75e45 Mon Sep 17 00:00:00 2001 From: tinyu Date: Sun, 27 Apr 2025 10:52:36 +0800 Subject: [PATCH 061/322] lib/base: Add DTU LoRaWAN-Series(RAK3172) Base support. Signed-off-by: tinyu --- docs/en/base/dtu_lorawan_rui3.rst | 82 + docs/en/base/index.rst | 1 + docs/en/conf.py | 51 +- docs/en/refs/base.lorawan_rui3.ref | 85 + docs/en/refs/unit.lorawan_rui3.ref | 1 + .../LC_MESSAGES/base/dtu_lorawan_rui3.po | 1192 ++++++++++ ...ase_lorawan868_otaa_atom_lite_example.m5f2 | 1 + .../base_lorawan868_otaa_atom_lite_example.py | 60 + ...e_lorawan868_p2p_rx_atom_lite_example.m5f2 | 1 + ...ase_lorawan868_p2p_rx_atom_lite_example.py | 50 + ...e_lorawan868_p2p_tx_atom_lite_example.m5f2 | 1 + ...ase_lorawan868_p2p_tx_atom_lite_example.py | 50 + m5stack/libs/base/__init__.py | 2 +- m5stack/libs/base/dtu_lorawan_rui3.py | 106 + m5stack/libs/base/manifest.py | 2 +- m5stack/libs/driver/rui3.py | 2103 +++++------------ 16 files changed, 2188 insertions(+), 1600 deletions(-) create mode 100644 docs/en/base/dtu_lorawan_rui3.rst create mode 100644 docs/en/refs/base.lorawan_rui3.ref create mode 100644 docs/locales/zh_CN/LC_MESSAGES/base/dtu_lorawan_rui3.po create mode 100644 examples/base/dtu_lorawan_rui3/base_lorawan868_otaa_atom_lite_example.m5f2 create mode 100644 examples/base/dtu_lorawan_rui3/base_lorawan868_otaa_atom_lite_example.py create mode 100644 examples/base/dtu_lorawan_rui3/base_lorawan868_p2p_rx_atom_lite_example.m5f2 create mode 100644 examples/base/dtu_lorawan_rui3/base_lorawan868_p2p_rx_atom_lite_example.py create mode 100644 examples/base/dtu_lorawan_rui3/base_lorawan868_p2p_tx_atom_lite_example.m5f2 create mode 100644 examples/base/dtu_lorawan_rui3/base_lorawan868_p2p_tx_atom_lite_example.py create mode 100644 m5stack/libs/base/dtu_lorawan_rui3.py diff --git a/docs/en/base/dtu_lorawan_rui3.rst b/docs/en/base/dtu_lorawan_rui3.rst new file mode 100644 index 00000000..6222aec3 --- /dev/null +++ b/docs/en/base/dtu_lorawan_rui3.rst @@ -0,0 +1,82 @@ +Atom DTU LoRaWAN-Series(RAK3172) Base +====================================== + +.. sku:U184-CN470,U184-AS923,U184-EU868,U184-US915 + +.. include:: ../refs/base.lorawan_rui3.ref + +SKU: A152-CN470, A152-US915, A152-EU868 + +The Atom DTU LoRaWAN-Series is a LoRaWAN programmable data transfer unit (DTU) based on the STM32WLE5 chip. The module supports long-range communication, low-power operation, and high sensitivity characteristics, making it suitable for IoT communication needs in a variety of complex environments. + +- **Frequency band support**: CN470 (470MHz), EU868 (868MHz), US915 (915MHz) +- **Communication protocol**: + + - Supports LoRaWAN Class A, Class B, Class C modes + - Supports LoRa Point-to-Point (P2P) communication mode. + +- **Communication Interface**: + + - UART interface: Used to send AT commands to control LoRaWAN network access, data sending/receiving, P2P mode communication, etc. + - RS485 interface: supports wired communication of industrial equipment with high reliability. + +- **Internet access method**: + + - OTAA (Over-The-Air Activation) + - ABP (Activation By Personalization) + +Support the following products: + +================== ================== ================== +|LoRaWAN-CN470| |LoRaWAN-EU868| |LoRaWAN-US915| +================== ================== ================== + + +Micropython LoRaWAN-EU868 LoRaWAN OTAA Mode Example: + + .. literalinclude:: ../../../examples/base/dtu_lorawan_rui3/base_lorawan868_otaa_atom_lite_example.py + :language: python + :linenos: + +Micropython LoRaWAN-EU868 P2P Mode TX Example: + + .. literalinclude:: ../../../examples/base/dtu_lorawan_rui3/base_lorawan868_p2p_tx_atom_lite_example.py + :language: python + :linenos: + +Micropython LoRaWAN-EU868 P2P Mode RX Example: + + .. literalinclude:: ../../../examples/base/dtu_lorawan_rui3/base_lorawan868_p2p_tx_atom_lite_example.py + :language: python + :linenos: + +UIFLOW2 LoRaWAN-EU868 LoRaWAN OTAA Mode Example: + + |lorawan_otaa_cores3_example.png| + + |base_lorawan868_otaa_atom_lite_example.m5f2| + +UIFLOW2 LoRaWAN-EU868 P2P Mode TX Example: + + |lorawan_p2p_cores3_example.png| + + |base_lorawan868_p2p_tx_atom_lite_example.m5f2| + +UIFLOW2 LoRaWAN-EU868 P2P Mode RX Example: + + |lorawan_p2p_rec_cores3_example.png| + + |base_lorawan868_p2p_rx_atom_lite_example.m5f2| + +**API** +------- + +AtomDTULoRaWANRUI3Base +^^^^^^^^^^^^^^^^^^^^^^ + +.. autoclass:: base.dtu_lorawan_rui3.AtomDTULoRaWANRUI3Base + :members: + +.. autoclass:: driver.rui3.RUI3 + :members: + :member-order: bysource diff --git a/docs/en/base/index.rst b/docs/en/base/index.rst index e2248a28..d473055a 100644 --- a/docs/en/base/index.rst +++ b/docs/en/base/index.rst @@ -7,6 +7,7 @@ Base atom_socket.rst dtu_lorawan.rst dtu_nbiot.rst + dtu_lorawan_rui3.rst atom_can.rst atom_gps.rst display.rst diff --git a/docs/en/conf.py b/docs/en/conf.py index bc7ca3f3..6e0fad28 100644 --- a/docs/en/conf.py +++ b/docs/en/conf.py @@ -12,6 +12,7 @@ # # import os import sys + # sys.path.insert(0, os.path.abspath('.')) import subprocess, os @@ -19,14 +20,15 @@ # -- Project information ----------------------------------------------------- import datetime + current_year = datetime.datetime.now().year -project = 'UIFlow2 Programming Guide' -copyright = '2016 - {} M5Stack Technology Co., Ltd'.format(current_year) -author = 'pandian' +project = "UIFlow2 Programming Guide" +copyright = "2016 - {} M5Stack Technology Co., Ltd".format(current_year) +author = "pandian" # The full version, including alpha/beta/rc tags -release = 'master' +release = "master" # -- General configuration --------------------------------------------------- @@ -35,14 +37,15 @@ # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ - 'breathe', - 'recommonmark', - 'sphinx_markdown_tables', - 'nbsphinx', - 'sphinx_copybutton', + "breathe", + "recommonmark", + "sphinx_markdown_tables", + "nbsphinx", + "sphinx_copybutton", "sphinx.ext.intersphinx", - 'sphinx.ext.autodoc', + "sphinx.ext.autodoc", "sphinx.ext.napoleon", + "sphinxcontrib.plantuml", ] autodoc_mock_imports = [ @@ -54,12 +57,13 @@ "m5can", "rf433", "utime", + "rui3", ] autodoc_default_options = { - 'members': "", - 'show-inheritance': "", - 'members-order': 'bysource', + "members": "", + "show-inheritance": "", + "members-order": "bysource", } autodoc_typehints = "description" @@ -88,7 +92,7 @@ } # Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] +templates_path = ["_templates"] # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. @@ -96,8 +100,8 @@ exclude_patterns = [] # multi-language docs -language = 'en' -locale_dirs = ['../locales/'] +language = "en" +locale_dirs = ["../locales/"] gettext_compact = False # optional. gettext_uuid = True # optional. @@ -106,25 +110,24 @@ # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # -html_theme = 'sphinx_rtd_theme' +html_theme = "sphinx_rtd_theme" # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['../_static'] +html_static_path = ["../_static"] -breathe_projects = { "zbhci": "../build/xml/" } +breathe_projects = {"zbhci": "../build/xml/"} breathe_default_project = "zbhci" breathe_domain_by_extension = { - "h" : "c", - "c" : "c", + "h": "c", + "c": "c", } -read_the_docs_build = os.environ.get('READTHEDOCS', None) == 'True' +read_the_docs_build = os.environ.get("READTHEDOCS", None) == "True" if read_the_docs_build: - - subprocess.call('cd ../; doxygen', shell=True) \ No newline at end of file + subprocess.call("cd ../; doxygen", shell=True) diff --git a/docs/en/refs/base.lorawan_rui3.ref b/docs/en/refs/base.lorawan_rui3.ref new file mode 100644 index 00000000..94863813 --- /dev/null +++ b/docs/en/refs/base.lorawan_rui3.ref @@ -0,0 +1,85 @@ +.. |LoRaWAN-CN470| image:: https://m5stack-doc.oss-cn-shenzhen.aliyuncs.com/1138/A152_CN470_01.webp + :target: https://docs.m5stack.com/en/atom/Atom%20DTU%20LoRaWAN-CN470 + :height: 200px + :width: 200px + +.. |LoRaWAN-EU868| image:: https://m5stack-doc.oss-cn-shenzhen.aliyuncs.com/1139/A152_EU868_01.webp + :target: https://docs.m5stack.com/en/atom/Atom%20DTU%20LoRaWAN-EU868 + :height: 200px + :width: 200px + +.. |LoRaWAN-US915| image:: https://m5stack-doc.oss-cn-shenzhen.aliyuncs.com/1140/A152_US915_01.webp + :target: https://docs.m5stack.com/en/atom/Atom%20DTU%20LoRaWAN-US915 + :height: 200px + :width: 200px + +.. |init.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/lorawancn470/init.png +.. |config.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/lorawancn470/config.png +.. |reset_module_to_default.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/lorawancn470/reset_module_to_default.png +.. |set_low_power_mode.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/lorawancn470/set_low_power_mode.png +.. |set_join_config.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/lorawancn470/set_join_config.png +.. |join_network.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/lorawancn470/join_network.png +.. |join_network_return.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/lorawancn470/join_network_return.png +.. |set_join_mode.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/lorawancn470/set_join_mode.png +.. |get_join_state.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/lorawancn470/get_join_state.png +.. |get_last_receive.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/lorawancn470/get_last_receive.png +.. |send_data.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/lorawancn470/send_data.png +.. |send_data_return.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/lorawancn470/send_data_return.png +.. |set_network_mode.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/lorawancn470/set_network_mode.png +.. |get_p2p_frequency.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/lorawancn470/get_p2p_frequency.png +.. |set_p2p_frequency.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/lorawancn470/set_p2p_frequency.png +.. |get_p2p_spreading_factor.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/lorawancn470/get_p2p_spreading_factor.png +.. |set_p2p_spreading_factor.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/lorawancn470/set_p2p_spreading_factor.png +.. |get_p2p_bandwidth.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/lorawancn470/get_p2p_bandwidth.png +.. |set_p2p_fsk_bandwidth.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/lorawancn470/set_p2p_fsk_bandwidth.png +.. |set_p2p_lora_bandwidth.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/lorawancn470/set_p2p_lora_bandwidth.png +.. |get_p2p_code_rate.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/lorawancn470/get_p2p_code_rate.png +.. |set_p2p_code_rate.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/lorawancn470/set_p2p_code_rate.png +.. |get_p2p_preamble_length.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/lorawancn470/get_p2p_preamble_length.png +.. |set_p2p_preamble_length.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/lorawancn470/set_p2p_preamble_length.png +.. |get_p2p_tx_power.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/lorawancn470/get_p2p_tx_power.png +.. |set_p2p_tx_power.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/lorawancn470/set_p2p_tx_power.png +.. |get_p2p_fsk_bitrate.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/lorawancn470/get_p2p_fsk_bitrate.png +.. |set_p2p_fsk_bitrate.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/lorawancn470/set_p2p_fsk_bitrate.png +.. |send_p2p_data.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/lorawancn470/send_p2p_data.png +.. |send_p2p_data_return.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/lorawancn470/send_p2p_data_return.png +.. |get_p2p_receive_data.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/lorawancn470/get_p2p_receive_data.png +.. |get_p2p_sync_word.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/lorawancn470/get_p2p_sync_word.png +.. |set_p2p_sync_word.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/lorawancn470/set_p2p_sync_word.png +.. |set_abp_config.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/lorawancn470/set_abp_config.png +.. |get_abp_config.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/lorawancn470/get_abp_config.png +.. |set_otaa_config.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/lorawancn470/set_config.png +.. |get_otaa_config.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/lorawancn470/get_otaa_config.png + + +.. |lorawan_otaa_cores3_example.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/lorawan_eu868/otaa_example.png +.. |lorawan_p2p_cores3_example.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/lorawan_eu868/p2p_tx_example.png +.. |lorawan_p2p_rec_cores3_example.png| image:: https://static-cdn.m5stack.com/mpy_docs/base/lorawan_eu868/p2p_rx_example.png + +.. |base_lorawan868_otaa_atom_lite_example.m5f2| raw:: html + + + base_lorawan868_otaa_atom_lite_example.m5f2 + + +.. |base_lorawan868_p2p_tx_atom_lite_example.m5f2| raw:: html + + + base_lorawan868_p2p_tx_atom_lite_example.m5f2 + + +.. |base_lorawan868_p2p_rx_atom_lite_example.m5f2| raw:: html + + + base_lorawan868_p2p_rx_atom_lite_example.m5f2 + + diff --git a/docs/en/refs/unit.lorawan_rui3.ref b/docs/en/refs/unit.lorawan_rui3.ref index ae09d36e..c711d724 100644 --- a/docs/en/refs/unit.lorawan_rui3.ref +++ b/docs/en/refs/unit.lorawan_rui3.ref @@ -19,6 +19,7 @@ :width: 200px .. |init.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/lorawancn470/init.png +.. |config.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/lorawancn470/config.png .. |reset_module_to_default.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/lorawancn470/reset_module_to_default.png .. |set_low_power_mode.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/lorawancn470/set_low_power_mode.png .. |set_join_config.png| image:: https://static-cdn.m5stack.com/mpy_docs/unit/lorawancn470/set_join_config.png diff --git a/docs/locales/zh_CN/LC_MESSAGES/base/dtu_lorawan_rui3.po b/docs/locales/zh_CN/LC_MESSAGES/base/dtu_lorawan_rui3.po new file mode 100644 index 00000000..d8b1bd7f --- /dev/null +++ b/docs/locales/zh_CN/LC_MESSAGES/base/dtu_lorawan_rui3.po @@ -0,0 +1,1192 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2016 - 2025 M5Stack Technology Co., Ltd +# This file is distributed under the same license as the UIFlow2 Programming +# Guide package. +# FIRST AUTHOR , 2025. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: UIFlow2 Programming Guide\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-04-27 09:51+0800\n" +"PO-Revision-Date: 2025-04-26 18:03+0800\n" +"Last-Translator: \n" +"Language: zh_CN\n" +"Language-Team: zh_CN \n" +"Plural-Forms: nplurals=1; plural=0;\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.16.0\n" + +#: ../../en/base/dtu_lorawan_rui3.rst:3 7743a1b5d13144f1a942ca037e761fd3 +msgid "Atom DTU LoRaWAN-Series(RAK3172) Base" +msgstr "" + +#: ../../en/base/dtu_lorawan_rui3.rst:7 b1feb5944ee44d3fba9355f2e4fee756 +msgid "SKU: A152-CN470, A152-US915, A152-EU868" +msgstr "" + +#: ../../en/base/dtu_lorawan_rui3.rst:9 8661ccacabf94f7d98ec2dd6d87a73fa +msgid "" +"The Atom DTU LoRaWAN-Series is a LoRaWAN programmable data transfer unit " +"(DTU) based on the STM32WLE5 chip. The module supports long-range " +"communication, low-power operation, and high sensitivity characteristics," +" making it suitable for IoT communication needs in a variety of complex " +"environments." +msgstr "" +"Atom DTU LoRaWAN-Series 是一款基于 STM32WLE5 芯片的 LoRaWAN " +"可编程数据传输单元(DTU)。该模块支持远距离通信、低功耗运行,并具备高灵敏度特性,适用于多种复杂环境的物联网通信需求。" + +#: ../../en/base/dtu_lorawan_rui3.rst:11 176536663f524c008630fe4894b4bf88 +msgid "**Frequency band support**: CN470 (470MHz), EU868 (868MHz), US915 (915MHz)" +msgstr "**频段支持**:CN470(470MHz),EU868(868MHz),US915(915MHz)" + +#: ../../en/base/dtu_lorawan_rui3.rst:12 825ae53602e54a9fa86de11ce04e325c +msgid "**Communication protocol**:" +msgstr "**通信协议**:" + +#: ../../en/base/dtu_lorawan_rui3.rst:14 4a8f346871d54d05aaecbac9991aeaf2 +msgid "Supports LoRaWAN Class A, Class B, Class C modes" +msgstr "支持 LoRaWAN Class A、Class B、Class C 模式" + +#: ../../en/base/dtu_lorawan_rui3.rst:15 b459d414995241c88807679f82f7150e +msgid "Supports LoRa Point-to-Point (P2P) communication mode." +msgstr "支持 LoRa 点对点(P2P)通信模式" + +#: ../../en/base/dtu_lorawan_rui3.rst:17 9215f0cb920d4cd59d5c16e73e732bdf +msgid "**Communication Interface**:" +msgstr "**通信接口**:" + +#: ../../en/base/dtu_lorawan_rui3.rst:19 893fd621776f43419c1c600e89b7d03b +msgid "" +"UART interface: Used to send AT commands to control LoRaWAN network " +"access, data sending/receiving, P2P mode communication, etc." +msgstr "UART 接口:用于发送 AT 指令,控制 LoRaWAN 入网、数据发送/接收、P2P 模式通信等" + +#: ../../en/base/dtu_lorawan_rui3.rst:20 7aeff81323ad4558b9b6a3549b850252 +msgid "" +"RS485 interface: supports wired communication of industrial equipment " +"with high reliability." +msgstr "RS485 接口:支持工业设备有线通信,可靠性高" + +#: ../../en/base/dtu_lorawan_rui3.rst:22 0eee0282167b4a71a160d0157f8c4dab +msgid "**Internet access method**:" +msgstr "**入网方式**:" + +#: ../../en/base/dtu_lorawan_rui3.rst:24 47bd24c5457349b7bfcfa57775c14d6a +msgid "OTAA (Over-The-Air Activation)" +msgstr "OTAA(Over-The-Air Activation,空中激活)" + +#: ../../en/base/dtu_lorawan_rui3.rst:25 b3d229cc4f5f4eb485ab96840ca09264 +msgid "ABP (Activation By Personalization)" +msgstr "ABP(Activation By Personalization,手动激活)" + +#: ../../en/base/dtu_lorawan_rui3.rst:27 59eea72ceb0b444f983b95b7628e836a +msgid "Support the following products:" +msgstr "支持以下产品:" + +#: ../../en/base/dtu_lorawan_rui3.rst:30 d1472c7b9a9d4c09a8fe1ed6a7f9053c +msgid "|LoRaWAN-CN470|" +msgstr "" + +#: ../../en/refs/base.lorawan_rui3.ref 46ac5f78cd3d4f818855faad18aa9f55 +msgid "LoRaWAN-CN470" +msgstr "" + +#: ../../en/base/dtu_lorawan_rui3.rst:30 78b7237b55a34d79aa84ec93e124dab9 +msgid "|LoRaWAN-EU868|" +msgstr "" + +#: ../../en/refs/base.lorawan_rui3.ref d190b6dc1a544a31a6f8c66c52d52c2f +msgid "LoRaWAN-EU868" +msgstr "" + +#: ../../en/base/dtu_lorawan_rui3.rst:30 1d44c2a4a78744a38efa3f8ba2ced35f +msgid "|LoRaWAN-US915|" +msgstr "" + +#: ../../en/refs/base.lorawan_rui3.ref 32a33872472b4a53a7ea824a9e0c8c18 +msgid "LoRaWAN-US915" +msgstr "" + +#: ../../en/base/dtu_lorawan_rui3.rst:34 28b4908fe7e040778e869f4640c16b21 +msgid "Micropython LoRaWAN-EU868 LoRaWAN OTAA Mode Example:" +msgstr "Micropython LoRaWAN-EU868 LoRaWAN OTAA 模式示例程序:" + +#: ../../en/base/dtu_lorawan_rui3.rst:40 1bde4096dca94410ae593907cb123d28 +msgid "Micropython LoRaWAN-EU868 P2P Mode TX Example:" +msgstr "Micropython LoRaWAN-EU868 P2P 模式 TX 示例程序:" + +#: ../../en/base/dtu_lorawan_rui3.rst:46 734ea01e9b144beb9dde45c6971ffbac +msgid "Micropython LoRaWAN-EU868 P2P Mode RX Example:" +msgstr "Micropython LoRaWAN-EU868 P2P 模式 RX 示例程序:" + +#: ../../en/base/dtu_lorawan_rui3.rst:52 624f1fa7c17c45ae89c92a09ea14a667 +msgid "UIFLOW2 LoRaWAN-EU868 LoRaWAN OTAA Mode Example:" +msgstr "UIFLOW2 LoRaWAN-EU868 LoRaWAN OTAA 模式示例程序:" + +#: ../../en/base/dtu_lorawan_rui3.rst:54 42a232d64bda4fc3815b62487539ad02 +msgid "|lorawan_otaa_cores3_example.png|" +msgstr "" + +#: ../../en/refs/base.lorawan_rui3.ref:55 aa0e2b0c63084db48356bfa7738b5311 +msgid "lorawan_otaa_cores3_example.png" +msgstr "" + +#: ../../en/base/dtu_lorawan_rui3.rst:56 b563f16c0c3147109f5c34f0395c5812 +msgid "|base_lorawan868_otaa_atom_lite_example.m5f2|" +msgstr "" + +#: ../../en/base/dtu_lorawan_rui3.rst:58 cc8bb6e6e2294625895a405ccf227620 +msgid "UIFLOW2 LoRaWAN-EU868 P2P Mode TX Example:" +msgstr "UIFLOW2 LoRaWAN-EU868 P2P 模式 TX 示例程序:" + +#: ../../en/base/dtu_lorawan_rui3.rst:60 01605a58213d417387156358470b7b7b +msgid "|lorawan_p2p_cores3_example.png|" +msgstr "" + +#: ../../en/refs/base.lorawan_rui3.ref:56 d6e06211c287428f899d782fd11f9c6b +msgid "lorawan_p2p_cores3_example.png" +msgstr "" + +#: ../../en/base/dtu_lorawan_rui3.rst:62 b6651f12039b4c22858da342c75b7678 +msgid "|base_lorawan868_p2p_tx_atom_lite_example.m5f2|" +msgstr "" + +#: ../../en/base/dtu_lorawan_rui3.rst:64 d9a97eeb1b9e4b0e8acf1be58cc7b2ee +msgid "UIFLOW2 LoRaWAN-EU868 P2P Mode RX Example:" +msgstr "UIFLOW2 LoRaWAN-EU868 P2P 模式 RX 示例程序:" + +#: ../../en/base/dtu_lorawan_rui3.rst:66 94014852fff042e88941c1e8757d2cf9 +msgid "|lorawan_p2p_rec_cores3_example.png|" +msgstr "" + +#: ../../en/refs/base.lorawan_rui3.ref:57 b25983232368433ca9c727f7de39a59c +msgid "lorawan_p2p_rec_cores3_example.png" +msgstr "" + +#: ../../en/base/dtu_lorawan_rui3.rst:68 496628df51204af391852b87b3a8d427 +msgid "|base_lorawan868_p2p_rx_atom_lite_example.m5f2|" +msgstr "" + +#: ../../en/base/dtu_lorawan_rui3.rst:71 9ea022feee57408f93d3861d725ae416 +msgid "**API**" +msgstr "" + +#: ../../en/base/dtu_lorawan_rui3.rst:74 130545b7251744fd87cde9298799f90b +msgid "AtomDTULoRaWANRUI3Base" +msgstr "" + +#: base.dtu_lorawan_rui3.AtomDTULoRaWANRUI3Base:1 +#: bbdc142a85fe42d9afb54ac51d760c78 of +msgid "Bases: :py:class:`~rui3.RUI3`" +msgstr "" + +#: a01548b9a3ec4c8282accac93cd3f837 +#: base.dtu_lorawan_rui3.AtomDTULoRaWANRUI3Base:1 of +msgid "Create an AtomDTULoRaWANRUI3Base object." +msgstr "创建 AtomDTULoRaWANRUI3Base 对象。" + +#: ../../en/base/dtu_lorawan_rui3.rst 6eacd9c4df224d9db4ad595f57868a8b +#: base.dtu_lorawan_rui3.AtomDTULoRaWANRUI3Base.set_abp_config +#: base.dtu_lorawan_rui3.AtomDTULoRaWANRUI3Base.set_otaa_config +#: driver.rui3.RUI3.get_p2p_receive_data driver.rui3.RUI3.join_network +#: driver.rui3.RUI3.send_data driver.rui3.RUI3.send_p2p_data +#: driver.rui3.RUI3.set_join_config driver.rui3.RUI3.set_join_mode +#: driver.rui3.RUI3.set_network_mode driver.rui3.RUI3.set_p2p_bandwidth +#: driver.rui3.RUI3.set_p2p_code_rate driver.rui3.RUI3.set_p2p_frequency +#: driver.rui3.RUI3.set_p2p_fsk_bitrate +#: driver.rui3.RUI3.set_p2p_preamble_length +#: driver.rui3.RUI3.set_p2p_spreading_factor driver.rui3.RUI3.set_p2p_sync_word +#: driver.rui3.RUI3.set_p2p_tx_power of +msgid "Parameters" +msgstr "参数" + +#: 17771d8479fc499392593ad4d50b41a1 +#: base.dtu_lorawan_rui3.AtomDTULoRaWANRUI3Base:3 of +msgid "The UART ID to use (0, 1, or 2). Default is 2." +msgstr "要使用的 UART ID(0、1 或 2)。默认是 2。" + +#: base.dtu_lorawan_rui3.AtomDTULoRaWANRUI3Base:4 +#: c3d062303e2443bfb00b16b67e03feab of +msgid "A list or tuple containing the TX and RX pin numbers." +msgstr "包含 TX 和 RX 引脚编号的列表或元组。" + +#: 21cb11cf7f964200a5b2605b36280659 +#: base.dtu_lorawan_rui3.AtomDTULoRaWANRUI3Base:6 of +msgid "Whether to enable debug mode. Default is False." +msgstr "是否启用调试模式。默认是 False。" + +#: 902951a49a9e4c5baf4791db634ec7d0 +#: base.dtu_lorawan_rui3.AtomDTULoRaWANRUI3Base:8 +#: base.dtu_lorawan_rui3.AtomDTULoRaWANRUI3Base.get_abp_config:6 +#: base.dtu_lorawan_rui3.AtomDTULoRaWANRUI3Base.get_otaa_config:6 +#: base.dtu_lorawan_rui3.AtomDTULoRaWANRUI3Base.set_abp_config:7 +#: base.dtu_lorawan_rui3.AtomDTULoRaWANRUI3Base.set_otaa_config:7 +#: driver.rui3.RUI3.get_device_eui:6 driver.rui3.RUI3.get_join_state:6 +#: driver.rui3.RUI3.get_last_receive:6 driver.rui3.RUI3.get_p2p_bandwidth:6 +#: driver.rui3.RUI3.get_p2p_code_rate:6 driver.rui3.RUI3.get_p2p_frequency:6 +#: driver.rui3.RUI3.get_p2p_fsk_bitrate:6 +#: driver.rui3.RUI3.get_p2p_preamble_length:6 +#: driver.rui3.RUI3.get_p2p_receive_data:13 +#: driver.rui3.RUI3.get_p2p_spreading_factor:6 +#: driver.rui3.RUI3.get_p2p_sync_word:6 driver.rui3.RUI3.get_p2p_tx_power:6 +#: driver.rui3.RUI3.get_received_data:6 +#: driver.rui3.RUI3.get_received_data_count:6 +#: driver.rui3.RUI3.get_received_data_string:6 driver.rui3.RUI3.join_network:11 +#: driver.rui3.RUI3.reset_module_to_default:3 driver.rui3.RUI3.send_data:13 +#: driver.rui3.RUI3.send_p2p_data:15 driver.rui3.RUI3.set_join_config:14 +#: driver.rui3.RUI3.set_join_mode:8 driver.rui3.RUI3.set_network_mode:12 +#: driver.rui3.RUI3.set_p2p_bandwidth:26 driver.rui3.RUI3.set_p2p_code_rate:13 +#: driver.rui3.RUI3.set_p2p_frequency:11 +#: driver.rui3.RUI3.set_p2p_fsk_bitrate:10 +#: driver.rui3.RUI3.set_p2p_preamble_length:10 +#: driver.rui3.RUI3.set_p2p_spreading_factor:10 +#: driver.rui3.RUI3.set_p2p_sync_word:10 driver.rui3.RUI3.set_p2p_tx_power:10 +#: of +msgid "MicroPython Code Block:" +msgstr "MicroPython 代码块:" + +#: 6562f39a98de4274bed1f70250740ad5 +#: base.dtu_lorawan_rui3.AtomDTULoRaWANRUI3Base.get_abp_config:1 of +#, fuzzy +msgid "Retrieve the current ABP configuration." +msgstr "检索当前 P2P 频率。" + +#: 5257fb90533449d99b3d539b7e39ed6c +#: base.dtu_lorawan_rui3.AtomDTULoRaWANRUI3Base.get_abp_config +#: base.dtu_lorawan_rui3.AtomDTULoRaWANRUI3Base.get_otaa_config +#: driver.rui3.RUI3.get_device_eui driver.rui3.RUI3.get_join_state +#: driver.rui3.RUI3.get_last_receive driver.rui3.RUI3.get_p2p_bandwidth +#: driver.rui3.RUI3.get_p2p_code_rate driver.rui3.RUI3.get_p2p_frequency +#: driver.rui3.RUI3.get_p2p_fsk_bitrate +#: driver.rui3.RUI3.get_p2p_preamble_length +#: driver.rui3.RUI3.get_p2p_receive_data +#: driver.rui3.RUI3.get_p2p_spreading_factor driver.rui3.RUI3.get_p2p_sync_word +#: driver.rui3.RUI3.get_p2p_tx_power driver.rui3.RUI3.get_received_data +#: driver.rui3.RUI3.get_received_data_count +#: driver.rui3.RUI3.get_received_data_string driver.rui3.RUI3.join_network +#: driver.rui3.RUI3.send_data driver.rui3.RUI3.send_p2p_data +#: driver.rui3.RUI3.set_join_config driver.rui3.RUI3.set_join_mode +#: driver.rui3.RUI3.set_network_mode driver.rui3.RUI3.set_p2p_bandwidth +#: driver.rui3.RUI3.set_p2p_code_rate driver.rui3.RUI3.set_p2p_frequency +#: driver.rui3.RUI3.set_p2p_fsk_bitrate +#: driver.rui3.RUI3.set_p2p_preamble_length +#: driver.rui3.RUI3.set_p2p_spreading_factor driver.rui3.RUI3.set_p2p_sync_word +#: driver.rui3.RUI3.set_p2p_tx_power of +msgid "Returns" +msgstr "返回值" + +#: 73e86df276824f689fd24e69e252f316 +#: base.dtu_lorawan_rui3.AtomDTULoRaWANRUI3Base.get_abp_config:3 of +msgid "A tuple containing (device_address, apps_key, networks_key)." +msgstr "包含(设备地址、应用程序密钥、网络密钥)的元组。" + +#: base.dtu_lorawan_rui3.AtomDTULoRaWANRUI3Base.get_abp_config +#: base.dtu_lorawan_rui3.AtomDTULoRaWANRUI3Base.get_otaa_config +#: base.dtu_lorawan_rui3.AtomDTULoRaWANRUI3Base.set_abp_config +#: base.dtu_lorawan_rui3.AtomDTULoRaWANRUI3Base.set_otaa_config +#: driver.rui3.RUI3.get_device_eui driver.rui3.RUI3.get_join_state +#: driver.rui3.RUI3.get_last_receive driver.rui3.RUI3.get_p2p_bandwidth +#: driver.rui3.RUI3.get_p2p_code_rate driver.rui3.RUI3.get_p2p_frequency +#: driver.rui3.RUI3.get_p2p_fsk_bitrate +#: driver.rui3.RUI3.get_p2p_preamble_length +#: driver.rui3.RUI3.get_p2p_receive_data +#: driver.rui3.RUI3.get_p2p_spreading_factor driver.rui3.RUI3.get_p2p_sync_word +#: driver.rui3.RUI3.get_p2p_tx_power driver.rui3.RUI3.get_received_data +#: driver.rui3.RUI3.get_received_data_count +#: driver.rui3.RUI3.get_received_data_string driver.rui3.RUI3.join_network +#: driver.rui3.RUI3.send_data driver.rui3.RUI3.send_p2p_data +#: driver.rui3.RUI3.set_join_config driver.rui3.RUI3.set_join_mode +#: driver.rui3.RUI3.set_network_mode driver.rui3.RUI3.set_p2p_bandwidth +#: driver.rui3.RUI3.set_p2p_code_rate driver.rui3.RUI3.set_p2p_frequency +#: driver.rui3.RUI3.set_p2p_fsk_bitrate +#: driver.rui3.RUI3.set_p2p_preamble_length +#: driver.rui3.RUI3.set_p2p_spreading_factor driver.rui3.RUI3.set_p2p_sync_word +#: driver.rui3.RUI3.set_p2p_tx_power f262b70685f949d79b49bdea048e0a25 of +msgid "Return type" +msgstr "返回类型" + +#: 6562f39a98de4274bed1f70250740ad5 +#: base.dtu_lorawan_rui3.AtomDTULoRaWANRUI3Base.get_otaa_config:1 of +msgid "Retrieve the current OTAA configuration." +msgstr "获取当前 OTAA 配置。" + +#: 0f95fa6022e545dab434e05430d56085 +#: base.dtu_lorawan_rui3.AtomDTULoRaWANRUI3Base.get_otaa_config:3 of +msgid "A tuple containing (device_eui, app_key, app_eui)." +msgstr "包含(设备 EUI、应用程序密钥、应用程序 EUI)的元组。" + +#: 8d2ff7b2cb6d4df6bcbb4b18d706763d +#: base.dtu_lorawan_rui3.AtomDTULoRaWANRUI3Base.set_abp_config:1 of +#, fuzzy +msgid "Configure the device for ABP (Activation By Personalization) mode." +msgstr "ABP(Activation By Personalization,手动激活)" + +#: base.dtu_lorawan_rui3.AtomDTULoRaWANRUI3Base.set_abp_config:3 +#: fe996cb318ae4e6799f38db7e08127a0 of +msgid "The device address for ABP configuration." +msgstr "用于 ABP 配置的设备地址。" + +#: 62fc244a5ed4486099fcfb3b033fc804 +#: base.dtu_lorawan_rui3.AtomDTULoRaWANRUI3Base.set_abp_config:4 of +msgid "The application session key for encryption." +msgstr "用于加密的应用会话密钥。" + +#: base.dtu_lorawan_rui3.AtomDTULoRaWANRUI3Base.set_abp_config:5 +#: fe996cb318ae4e6799f38db7e08127a0 of +msgid "The network session key for communication." +msgstr "用于通信的网络会话密钥。" + +#: 721170a760214d5496c7e3718bd1777b +#: base.dtu_lorawan_rui3.AtomDTULoRaWANRUI3Base.set_otaa_config:1 of +msgid "Configure the device for OTAA (Over-The-Air Activation) mode." +msgstr "配置设备为 OTAA(Over-The-Air Activation,空中激活)模式。" + +#: base.dtu_lorawan_rui3.AtomDTULoRaWANRUI3Base.set_otaa_config:3 +#: fe996cb318ae4e6799f38db7e08127a0 of +msgid "The device EUI for OTAA configuration." +msgstr "用于 OTAA 配置的设备 EUI。" + +#: 021870fea55042b591e8a70a12b26a85 +#: base.dtu_lorawan_rui3.AtomDTULoRaWANRUI3Base.set_otaa_config:4 of +msgid "The application key for encryption." +msgstr "用于加密的应用密钥。" + +#: 021870fea55042b591e8a70a12b26a85 +#: base.dtu_lorawan_rui3.AtomDTULoRaWANRUI3Base.set_otaa_config:5 of +msgid "The application EUI for OTAA configuration." +msgstr "用于 OTAA 配置的应用 EUI。" + +#: bbdc142a85fe42d9afb54ac51d760c78 driver.rui3.RUI3:1 of +msgid "Bases: :py:class:`object`" +msgstr "" + +#: aa776326beaf49c7bf4617468b4b3d4b driver.rui3.RUI3.get_last_receive:1 +#: driver.rui3.RUI3.get_received_data:1 of +msgid "Retrieve the data from the last received message." +msgstr "检索最后一次接收到的消息数据。" + +#: 73e86df276824f689fd24e69e252f316 driver.rui3.RUI3.get_last_receive:3 +#: driver.rui3.RUI3.get_received_data:3 of +msgid "" +"A tuple containing the port number (int) and the received data (bytes), " +"or False if no data was received." +msgstr "包含端口号(整数)和接收到的数据(字节)的元组,如果没有数据接收,返回 False。" + +#: 867ad5954bed449986797d37e04497f8 driver.rui3.RUI3.get_received_data_string:1 +#: of +msgid "Retrieve the received data as a string." +msgstr "检索接收到的数据作为字符串。" + +#: 6901c0006ed24e1685bdc924c7231cd2 driver.rui3.RUI3.get_received_data_string:3 +#: of +msgid "The received data as a string, or an empty string if no data was received." +msgstr "接收到的数据作为字符串,如果没有数据接收,返回空字符串。" + +#: aa776326beaf49c7bf4617468b4b3d4b driver.rui3.RUI3.get_received_data_count:1 +#: of +msgid "Retrieve the number of received data." +msgstr "检索接收到的数据数量。" + +#: 04142f2491d6492984d9600e87372c82 driver.rui3.RUI3.get_received_data_count:3 +#: of +msgid "The number of received data." +msgstr "接收到的数据数量。" + +#: 844d3630535849ea8ea4ef4a3e38de84 driver.rui3.RUI3.reset_module_to_default:1 +#: of +msgid "Reset the module to its factory default settings." +msgstr "将模块重置为出厂默认设置。" + +#: driver.rui3.RUI3.get_device_eui:1 e2fddd30eea249a697d435c41375f132 of +msgid "Get the device EUI." +msgstr "获取设备 EUI。" + +#: driver.rui3.RUI3.get_device_eui:3 f8c61272754c4ef49b6836610c6aa047 of +msgid "The device EUI." +msgstr "设备 EUI" + +#: 557a4c78a80f497ea02beac27ae73374 driver.rui3.RUI3.set_join_config:1 of +msgid "Configure the join parameters for LoRaWAN." +msgstr "为 LoRaWAN 配置连接参数。" + +#: bf97938c95924d119afecf070b57278f driver.rui3.RUI3.set_join_config:3 of +msgid "The configuration does not confirm network join success." +msgstr "配置不确认网络连接成功。" + +#: 4103493a99234b34924cb5021f83e3a8 driver.rui3.RUI3.set_join_config:5 of +msgid "The join state to configure, as an integer." +msgstr "要配置的连接状态,整数。" + +#: b64e9f6865c5426b859d7147d6effa07 driver.rui3.RUI3.set_join_config:6 of +msgid "The auto-join flag, as an integer." +msgstr "自动连接标志位,整数。" + +#: 17771d8479fc499392593ad4d50b41a1 driver.rui3.RUI3.set_join_config:7 of +msgid "The interval between join retries, in seconds. Default is 8." +msgstr "连接重试间隔,单位为秒。默认值为 8秒 。" + +#: 34db71de4433451891c1a2b9f7903e08 driver.rui3.RUI3.set_join_config:8 of +msgid "The maximum number of retries. Default is 0 (no limit)." +msgstr "最大重试次数。默认值为 0(无限制)。" + +#: 6eebe2aa6f6242ef8f5e7eabff2da4c6 driver.rui3.RUI3.set_join_config:9 of +msgid "The timeout duration in milliseconds for the command. Default is 8000ms." +msgstr "命令超时时间,单位为毫秒。默认值为 8000ms。" + +#: 5623f4d6e5fa4693b88af23002518abd driver.rui3.RUI3.join_network:5 +#: driver.rui3.RUI3.set_join_config:11 driver.rui3.RUI3.set_join_mode:5 of +msgid "True if the command is successfully set, else False." +msgstr "如果命令设置成功,返回 True,否则返回 False。" + +#: 100f24aca1184bd8bd4f995861e68e49 driver.rui3.RUI3.join_network:1 of +msgid "Join the LoRa network using predefined join parameters." +msgstr "使用预定义的连接参数加入 LoRa 网络。" + +#: aec229cfe96e4ea1984bebf3ed5d4421 driver.rui3.RUI3.join_network:3 of +msgid "" +"The timeout duration in milliseconds for the join command. Default is " +"8000ms." +msgstr "连接命令超时时间,单位为毫秒。默认值为 8000ms。" + +#: a2f3ec97468347fda94d0a16c715f25a driver.rui3.RUI3.join_network:6 of +msgid "bool |join_network_return.png|" +msgstr "" + +#: 2d2c091600134f8aa518a37bbb818cc4 caf3437aebbc4c43982ebeb856a7446b +#: driver.rui3.RUI3.join_network:6 driver.rui3.RUI3.send_data:9 +#: driver.rui3.RUI3.send_p2p_data:11 e8de16726b63416783e477da671d2602 of +msgid "bool" +msgstr "" + +#: a2f3ec97468347fda94d0a16c715f25a driver.rui3.RUI3.join_network:8 of +msgid "|join_network_return.png|" +msgstr "" + +#: ../../en/refs/base.lorawan_rui3.ref:22 1629c37aa2c147cb96d4a2e3d3842b7e +msgid "join_network_return.png" +msgstr "" + +#: a042c1ac524a4f9587b4999da1d7cae6 driver.rui3.RUI3.set_join_mode:1 of +msgid "Set the join mode for the LoRa module." +msgstr "设置 LoRa 模块的连接模式。" + +#: 81159c683b0b4e3294f735438ccc42c3 driver.rui3.RUI3.set_join_mode:3 of +msgid "The join mode to set, 0 for ABP or 1 for OTAA." +msgstr "要设置的连接模式,0 表示 ABP,1 表示 OTAA。" + +#: a60d7ed97ab145eb893c4df4be612fab driver.rui3.RUI3.get_join_state:1 of +msgid "Check whether the module has successfully joined the network." +msgstr "检查模块是否成功加入网络。" + +#: b7430cc0814844aeba20a2640370d016 driver.rui3.RUI3.get_join_state:3 of +msgid "True if joined, otherwise False." +msgstr "如果已加入网络,返回 True,否则返回 False。" + +#: 0904a20616f14be0be54a8653575095e driver.rui3.RUI3.send_data:1 of +msgid "Send data through a specific port." +msgstr "通过特定端口发送数据。" + +#: 04142f2491d6492984d9600e87372c82 driver.rui3.RUI3.send_data:3 of +msgid "The port number to send data through." +msgstr "要发送数据的端口号。" + +#: b01aa61d57df4db8ad54abf6cec294e0 driver.rui3.RUI3.send_data:4 of +msgid "" +"The data to send, provided as bytes or string(if data is bytes, it will " +"be converted to string)." +msgstr "要发送的数据,提供字节或字符串(如果数据是字节,将转换为字符串)。" + +#: 19cffe38a4c846f5aedd98c9f47335ef driver.rui3.RUI3.send_data:6 of +msgid "" +"The timeout duration in milliseconds for the send command. Default is " +"600ms." +msgstr "发送命令的超时时间,单位为毫秒。默认值为 600ms。" + +#: d2935087f7fa406c8b88ef83701efd3a driver.rui3.RUI3.send_data:8 of +msgid "True if the data was sent successfully, otherwise False." +msgstr "如果数据发送成功,返回 True,否则返回 False。" + +#: a12435b737cf40f4bbb9dd80af518582 driver.rui3.RUI3.send_data:9 of +msgid "bool |send_data_return.png|" +msgstr "" + +#: a12435b737cf40f4bbb9dd80af518582 driver.rui3.RUI3.send_data:11 of +msgid "|send_data_return.png|" +msgstr "" + +#: ../../en/refs/base.lorawan_rui3.ref:27 74dfdca1daa349a9bbadef6b33757f19 +msgid "send_data_return.png" +msgstr "" + +#: 2e3a7351f465410799090a8aea2c25ab driver.rui3.RUI3.set_network_mode:1 of +msgid "Set the network mode for the device." +msgstr "设置设备网络模式。" + +#: 3d366888a88443f8b446f379bb515037 driver.rui3.RUI3.get_p2p_fsk_bitrate:3 +#: driver.rui3.RUI3.set_network_mode:3 driver.rui3.RUI3.set_p2p_bandwidth:20 +#: driver.rui3.RUI3.set_p2p_code_rate:10 driver.rui3.RUI3.set_p2p_frequency:3 +#: driver.rui3.RUI3.set_p2p_fsk_bitrate:7 +#: driver.rui3.RUI3.set_p2p_preamble_length:3 +#: driver.rui3.RUI3.set_p2p_spreading_factor:7 +#: driver.rui3.RUI3.set_p2p_tx_power:7 of +msgid "The result of the AT command execution." +msgstr "AT 命令执行结果。" + +#: 796559359ec7443b87d8da15e1d1fcba driver.rui3.RUI3.set_network_mode:6 of +msgid "" +"The mode to set for the network: - 0 = P2P_LORA - 1 = LoRaWAN - 2 = " +"P2P_FSK" +msgstr "" +"要设置的网络模式: \n" +" - 0 = P2P_LORA \n" +" - 1 = LoRaWAN \n" +" - 2 = P2P_FSK" + +#: 8fd4b897cf7d475d83e3c056b8eadc4f driver.rui3.RUI3.set_network_mode:6 of +msgid "The mode to set for the network:" +msgstr "要设置的网络模式:" + +#: b054c11ce8c04ab3835e28b5257e027f driver.rui3.RUI3.set_network_mode:8 of +msgid "0 = P2P_LORA" +msgstr "" + +#: 675e48587b164ea1b6226074eb3c7d56 driver.rui3.RUI3.set_network_mode:9 of +msgid "1 = LoRaWAN" +msgstr "" + +#: driver.rui3.RUI3.set_network_mode:10 fd46b3fc5cab4fb198ed13e1df2dda1a of +msgid "2 = P2P_FSK" +msgstr "" + +#: driver.rui3.RUI3.get_p2p_frequency:1 f46876dcd44148759d2bd9b86b220a98 of +msgid "Retrieve the current P2P frequency." +msgstr "检索当前 P2P 频率。" + +#: 10e6243508284e90b23f4fd38537f809 driver.rui3.RUI3.get_p2p_frequency:3 of +msgid "The current P2P frequency as an integer." +msgstr "当前 P2P 频率作为整数。" + +#: 4bbf9970a9184bc7ba53136433ba6000 driver.rui3.RUI3.set_p2p_frequency:1 of +msgid "Set the P2P frequency for the device." +msgstr "设置设备 P2P 频率。" + +#: bd2c218f2d794188b30a8f1bfe379165 driver.rui3.RUI3.set_p2p_frequency:6 of +msgid "" +"The frequency to set for P2P communication. - Low-frequency range: " +"150000000-600000000 - High-frequency range: 600000000-960000000" +msgstr "" +"要设置的 P2P 通信频率。\n" +" - 低频率范围:150000000-600000000 \n" +" - 高频率范围:600000000-960000000" + +#: 17aac352e42642b5b473cba2b5c712d1 driver.rui3.RUI3.set_p2p_frequency:6 of +msgid "The frequency to set for P2P communication." +msgstr "要设置的 P2P 通信频率。" + +#: 1bc87601f25c49aebcc529b1c66622f3 driver.rui3.RUI3.set_p2p_frequency:8 of +msgid "Low-frequency range: 150000000-600000000" +msgstr "低频率范围:150000000-600000000" + +#: 757164e675f4494183d691a1914be0ea driver.rui3.RUI3.set_p2p_frequency:9 of +msgid "High-frequency range: 600000000-960000000" +msgstr "高频率范围:600000000-960000000" + +#: 268d9be7654e4d139c8e5c912780eb05 driver.rui3.RUI3.get_p2p_spreading_factor:1 +#: of +msgid "Retrieve the current P2P spreading factor." +msgstr "获取当前 P2P 扩展因子。" + +#: 7895420eea224c74a8c3cf1a14b6c74f driver.rui3.RUI3.get_p2p_spreading_factor:3 +#: of +msgid "The current P2P spreading factor as an integer." +msgstr "当前 P2P 扩展因子作为整数。" + +#: 0680dea68cbb41a48ea6c9c462d2b81a driver.rui3.RUI3.set_p2p_spreading_factor:1 +#: of +msgid "Set the P2P spreading factor." +msgstr "设置 P2P 扩展因子。" + +#: 9c4c3c9a34514ca8a454e4077d735bf4 driver.rui3.RUI3.set_p2p_spreading_factor:3 +#: of +msgid "The spreading factor to set for P2P communication. - Range is 5 to 12." +msgstr "" +"要设置的 P2P 通信扩展因子。 \n" +" - 范围是 5 到 12。" + +#: ab80efd274204b46977f58766c71af70 driver.rui3.RUI3.set_p2p_spreading_factor:3 +#: of +msgid "The spreading factor to set for P2P communication." +msgstr "用于 P2P 通信的扩展因子。" + +#: 5b74849620d7407d8e0a5e3a9cb3fa33 driver.rui3.RUI3.set_p2p_spreading_factor:5 +#: of +msgid "Range is 5 to 12." +msgstr "范围是 5 到 12。" + +#: 40d63b0206a44b1ab3299fc2053814b3 driver.rui3.RUI3.get_p2p_bandwidth:1 of +msgid "Retrieve the current P2P bandwidth." +msgstr "获取当前 P2P 带宽。" + +#: c8e92947bf4943dd8118417baefdad82 driver.rui3.RUI3.get_p2p_bandwidth:3 of +msgid "The current P2P bandwidth as an integer." +msgstr "当前 P2P 带宽作为整数。" + +#: 6a9373310a644a91a2136e33d2743a90 driver.rui3.RUI3.set_p2p_bandwidth:1 of +msgid "Set the P2P bandwidth." +msgstr "" + +#: 4b23a93b582d4bf5b227ea7ae23f50f6 driver.rui3.RUI3.set_p2p_bandwidth:3 of +msgid "" +"The bandwidth to set for P2P communication. - For LoRa: - 0 = 125 " +"kHz - 1 = 250 kHz - 2 = 500 kHz - 3 = 7.8 kHz - 4 = 10.4 " +"kHz - 5 = 15.63 kHz - 6 = 20.83 kHz - 7 = 31.25 kHz - 8 =" +" 41.67 kHz - 9 = 62.5 kHz - For FSK: Range: 4800-467000 Hz" +msgstr "" +"为 P2P 通信设置的带宽 \n" +" - 对于 LoRa:\n" +"- 0 = 125 kHz \n" +" - 1 = 250 kHz \n" +" - 2 = 500 kHz \n" +" - 3 = 7.8 kHz \n" +" - 4 = 10.4 kHz \n" +" - 5 = 15.63 kHz \n" +" - 6 = 20.83 kHz \n" +" - 7 = 31.25 kHz \n" +" - 8 = 41.67 kHz \n" +" - 9 = 62.5 kHz \n" +" - 对于 FSK:范围:4800-467000 Hz" + +#: 021870fea55042b591e8a70a12b26a85 driver.rui3.RUI3.set_p2p_bandwidth:3 of +msgid "The bandwidth to set for P2P communication." +msgstr "用于 P2P 通信的带宽。" + +#: driver.rui3.RUI3.set_p2p_bandwidth:15 fb1d34a81c7b4b539b1eb36fcf754774 of +msgid "For LoRa:" +msgstr "对于 LoRa:" + +#: 7e8028ab765c4e458d04afabfc993506 driver.rui3.RUI3.set_p2p_bandwidth:6 of +msgid "0 = 125 kHz" +msgstr "" + +#: 23a4fba35b8047e1acce90b13a0458cf driver.rui3.RUI3.set_p2p_bandwidth:7 of +msgid "1 = 250 kHz" +msgstr "" + +#: 1c66aa60364f4538b5b955c22dffc413 driver.rui3.RUI3.set_p2p_bandwidth:8 of +msgid "2 = 500 kHz" +msgstr "" + +#: 925b9686ede345e18683ca2c4ae9d24e driver.rui3.RUI3.set_p2p_bandwidth:9 of +msgid "3 = 7.8 kHz" +msgstr "" + +#: 4509ec90788a414586289112aa01b3b1 driver.rui3.RUI3.set_p2p_bandwidth:10 of +msgid "4 = 10.4 kHz" +msgstr "" + +#: 4173514e23ae428fba64364afeb8dd10 driver.rui3.RUI3.set_p2p_bandwidth:11 of +msgid "5 = 15.63 kHz" +msgstr "" + +#: 3d430cf91b7b4342b41d3e768e790137 driver.rui3.RUI3.set_p2p_bandwidth:12 of +msgid "6 = 20.83 kHz" +msgstr "" + +#: 8449149800ad4274b6d91686d911e959 driver.rui3.RUI3.set_p2p_bandwidth:13 of +msgid "7 = 31.25 kHz" +msgstr "" + +#: ac26e0f45b844b459c4645937802192a driver.rui3.RUI3.set_p2p_bandwidth:14 of +msgid "8 = 41.67 kHz" +msgstr "" + +#: dc1dad5c63494adaa57bf7fac5eb177d driver.rui3.RUI3.set_p2p_bandwidth:15 of +msgid "9 = 62.5 kHz" +msgstr "" + +#: c6558b894df6452cbd9e7c970e128481 driver.rui3.RUI3.set_p2p_bandwidth:18 of +msgid "For FSK:" +msgstr "对于 FSK:" + +#: 430a26a38ce340ca82f1bdfb798439f2 driver.rui3.RUI3.set_p2p_bandwidth:18 of +msgid "Range: 4800-467000 Hz" +msgstr "范围:4800-467000 Hz" + +#: 054eb5eb0076486b9b5d4bfe0afc7829 driver.rui3.RUI3.set_p2p_bandwidth:21 of +msgid "bool |set_p2p_fsk_bandwidth.png| |set_p2p_lora_bandwidth.png|" +msgstr "" + +#: a3ded662c5444b62906a9b59ba2b1990 driver.rui3.RUI3.set_p2p_bandwidth:21 of +msgid "bool |set_p2p_fsk_bandwidth.png|" +msgstr "" + +#: ../../en/refs/base.lorawan_rui3.ref:34 4bc9d0f8100b4567a6ae7d5183485212 +msgid "set_p2p_fsk_bandwidth.png" +msgstr "" + +#: 5b237226aca54e549639d771a32d69af driver.rui3.RUI3.set_p2p_bandwidth:24 of +msgid "|set_p2p_lora_bandwidth.png|" +msgstr "" + +#: ../../en/refs/base.lorawan_rui3.ref:35 0d8e42acb36b4603bf043cd584790bff +msgid "set_p2p_lora_bandwidth.png" +msgstr "" + +#: 6562f39a98de4274bed1f70250740ad5 driver.rui3.RUI3.get_p2p_code_rate:1 of +msgid "Retrieve the current P2P code rate." +msgstr "获取当前 P2P 编码速率。" + +#: c27fcd1292d24c3e90b640e82ded8834 driver.rui3.RUI3.get_p2p_code_rate:3 of +msgid "The current P2P code rate as an integer." +msgstr "当前 P2P 编码速率作为整数。" + +#: 3835a819f9e5435ea1013351b73d1ffe driver.rui3.RUI3.set_p2p_code_rate:1 of +msgid "Set the P2P code rate." +msgstr "设置 P2P 编码速率。" + +#: 3f590f8ca6784307b94a73ffc1ce16b4 driver.rui3.RUI3.set_p2p_code_rate:3 of +msgid "" +"The code rate to set for P2P communication. - 0 = 4/5 - 1 = 4/6 - 2 = " +"4/7 - 3 = 4/8" +msgstr "" +"用于 P2P 通信的编码速率。 \n" +" - 0 = 4/5 \n" +" - 1 = 4/6 \n" +" - 2 = 4/7 \n" +" - 3 = 4/8" + +#: driver.rui3.RUI3.set_p2p_code_rate:3 fe996cb318ae4e6799f38db7e08127a0 of +msgid "The code rate to set for P2P communication." +msgstr "用于 P2P 通信的编码速率。" + +#: deacde94e21a476a8cf5a704ac3c5dfb driver.rui3.RUI3.set_p2p_code_rate:5 of +msgid "0 = 4/5" +msgstr "" + +#: 6033560f4dc742d1b53718964fad38f0 driver.rui3.RUI3.set_p2p_code_rate:6 of +msgid "1 = 4/6" +msgstr "" + +#: 7710d652cde2435dad4099abcc453f5c driver.rui3.RUI3.set_p2p_code_rate:7 of +msgid "2 = 4/7" +msgstr "" + +#: 3063f77577e1421eaf0d9df1329658f3 driver.rui3.RUI3.set_p2p_code_rate:8 of +msgid "3 = 4/8" +msgstr "" + +#: d46e81e54d0549e89aa99d20150e648e driver.rui3.RUI3.get_p2p_preamble_length:1 +#: of +msgid "Retrieve the current P2P preamble length." +msgstr "获取当前 P2P 前导码长度。" + +#: 9b9e7741a4eb444bae16c589ad5e92d9 driver.rui3.RUI3.get_p2p_preamble_length:3 +#: of +msgid "The current P2P preamble length as an integer." +msgstr "当前 P2P 前导码长度作为整数。" + +#: bc4eb0d4591742f3b2594ca60e3ac41a driver.rui3.RUI3.set_p2p_preamble_length:1 +#: of +msgid "Set the P2P preamble length." +msgstr "设置 P2P 前导码长度。" + +#: 819d679dd6674b2e8b2772b174521aab driver.rui3.RUI3.set_p2p_preamble_length:6 +#: of +msgid "The preamble length to set for P2P communication. - Range is 5 to 65535." +msgstr "" +"用于 P2P 通信的前导码长度。 \n" +" - 范围是 5 到 65535。" + +#: 8e377ee7c74649d4a9d99104d2cc9213 driver.rui3.RUI3.set_p2p_preamble_length:6 +#: of +msgid "The preamble length to set for P2P communication." +msgstr "用于 P2P 通信的前导码长度。" + +#: driver.rui3.RUI3.set_p2p_preamble_length:8 fba62a8b8d3e49d989c06dad0233d36c +#: of +msgid "Range is 5 to 65535." +msgstr "范围是 5 到 65535。" + +#: driver.rui3.RUI3.get_p2p_tx_power:1 ec5a5913e31b4c50a37b3ff6f44aa32f of +msgid "Retrieve the current P2P transmission power." +msgstr "获取当前 P2P 发射功率。" + +#: ac069af04d0047f0b745e0ecc7aa5f4c driver.rui3.RUI3.get_p2p_tx_power:3 of +msgid "The current P2P transmission power as an integer." +msgstr "当前 P2P 发射功率作为整数。" + +#: 8ac15481ae6c4e8784a331abef649128 driver.rui3.RUI3.set_p2p_tx_power:1 of +msgid "Set the P2P transmission power." +msgstr "设置 P2P 发射功率。" + +#: 1198d0f067054f169ce628b32c8e9004 driver.rui3.RUI3.set_p2p_tx_power:3 of +msgid "" +"The transmission power to set for P2P communication. - Range is 5 to 22 " +"dBm." +msgstr "" +"用于 P2P 通信的发射功率。 \n" +" - 范围是 5 到 22 dBm。" + +#: 62fc244a5ed4486099fcfb3b033fc804 driver.rui3.RUI3.set_p2p_tx_power:3 of +msgid "The transmission power to set for P2P communication." +msgstr "用于 P2P 通信的发射功率。" + +#: 84e69392f756496eb4b33d9126c8f492 driver.rui3.RUI3.set_p2p_tx_power:5 of +msgid "Range is 5 to 22 dBm." +msgstr "范围是 5 到 22 dBm。" + +#: 867ad5954bed449986797d37e04497f8 driver.rui3.RUI3.get_p2p_fsk_bitrate:1 of +msgid "Retrieve the current P2P FSK bitrate." +msgstr "获取当前 P2P FSK 比特率。" + +#: 46f4478ed595436c9ea9eac6304647e0 driver.rui3.RUI3.set_p2p_fsk_bitrate:1 of +msgid "Set the P2P FSK bitrate." +msgstr "设置 P2P FSK 比特率。" + +#: 29c44650c00d4097a6eeefaee736d4a6 driver.rui3.RUI3.set_p2p_fsk_bitrate:3 of +msgid "" +"The bitrate to set for P2P FSK communication. - Range is 600 to 300000 " +"b/s." +msgstr "" +"用于 P2P FSK 通信的比特率。 \n" +" - 范围是 600 到 300000 b/s。" + +#: 152b3fca8d634bfd864209deae6f3ccb driver.rui3.RUI3.set_p2p_fsk_bitrate:3 of +msgid "The bitrate to set for P2P FSK communication." +msgstr "用于 P2P FSK 通信的比特率。" + +#: bbb7ebaf6899497d9a13ca40df32af58 driver.rui3.RUI3.set_p2p_fsk_bitrate:5 of +msgid "Range is 600 to 300000 b/s." +msgstr "范围是 600 到 300000 b/s。" + +#: driver.rui3.RUI3.send_p2p_data:1 f62791378a374e84b03093bb4bf079a6 of +msgid "Send P2P data with a given payload." +msgstr "发送带有给定负载的 P2P 数据。" + +#: 71a2103727c54a4388c4dce0626abe71 driver.rui3.RUI3.send_p2p_data:3 of +msgid "" +"The payload to send. - Length must be between 2 and 500 characters. - " +"Must consist of an even number of characters composed of 0-9, a-f, A-F, " +"representing 1 to 256 hexadecimal values." +msgstr "" +"要发送的负载。 \n" +" - 长度必须在 2 到 500 个字符之间。 \n" +" - 必须由偶数个字符组成,字符为 0-9、a-f、A-F,表示 1 到 256 的十六进制值。" + +#: 7ea318b680224736ac997c88b2cea7a3 driver.rui3.RUI3.send_p2p_data:3 of +msgid "The payload to send." +msgstr "要发送的负载。" + +#: 8c875925d1fa473085a1ff75c9e08a3a driver.rui3.RUI3.send_p2p_data:5 of +msgid "Length must be between 2 and 500 characters." +msgstr "长度必须在 2 到 500 个字符之间。" + +#: driver.rui3.RUI3.send_p2p_data:6 eb94f62ea24743d384a842259d9c305b of +msgid "" +"Must consist of an even number of characters composed of 0-9, a-f, A-F, " +"representing 1 to 256 hexadecimal values." +msgstr "必须由偶数个字符组成,字符为 0-9、a-f、A-F,表示 1 到 256 的十六进制值。" + +#: cd5d63d2f08a4606b7af7b90fb05015a driver.rui3.RUI3.send_p2p_data:7 of +msgid "" +"The timeout for the data transmission, in milliseconds. Default is 1000 " +"ms." +msgstr "数据传输的超时时间,单位为毫秒。默认值为 1000 毫秒。" + +#: b8bfadb16fb6474ab8374c96d35ada22 driver.rui3.RUI3.send_p2p_data:8 of +msgid "" +"Indicates whether to convert the payload to hexadecimal format. Default " +"is False." +msgstr "是否将负载转换为十六进制格式。默认值为 False。" + +#: 70de31682ab44558b9f0abf375faa7fc driver.rui3.RUI3.send_p2p_data:10 of +msgid "" +"True if the data was sent successfully (\"TXFSK DONE\" or \"TXP2P " +"DONE\"), False otherwise." +msgstr "如果数据发送成功,则返回 True(\"TXFSK DONE\" 或 \"TXP2P DONE\"),否则返回 False。" + +#: 527c936609464580a0e1329a73da7fc2 driver.rui3.RUI3.send_p2p_data:11 of +msgid "bool |send_p2p_data_return.png|" +msgstr "" + +#: 527c936609464580a0e1329a73da7fc2 driver.rui3.RUI3.send_p2p_data:13 of +msgid "|send_p2p_data_return.png|" +msgstr "" + +#: ../../en/refs/base.lorawan_rui3.ref:45 918f82cbe3d84354adf9bd167b21a7ac +msgid "send_p2p_data_return.png" +msgstr "" + +#: 2c1b4d2821184b2a918fcd8442b5f9c9 driver.rui3.RUI3.get_p2p_receive_data:1 of +msgid "Receive data in P2P mode, including RSSI, SNR, and payload." +msgstr "在 P2P 模式下接收数据,包括 RSSI、SNR 和负载。" + +#: driver.rui3.RUI3.get_p2p_receive_data:3 f0bbac03238f47e6ba398e7e8061053a of +msgid "" +"Timeout for listening to P2P LoRa data packets, in milliseconds. - Valid" +" values are 1 to 65535. - 0: Continuous listening. - 65535: No " +"timeout." +msgstr "" +"在 P2P LoRa 数据包中监听的超时时间,单位为毫秒。 \n" +" - 有效值为 1 到 65535。 \n" +" - 0:连续监听。 \n" +" - 65535:无超时。" + +#: ba4871c38f674e42a59d98002a6a22ea driver.rui3.RUI3.get_p2p_receive_data:3 of +msgid "Timeout for listening to P2P LoRa data packets, in milliseconds." +msgstr "在 P2P LoRa 数据包中监听的超时时间,单位为毫秒。" + +#: cb3dba63a3424843925a6973e41b5eb4 driver.rui3.RUI3.get_p2p_receive_data:7 of +msgid "Valid values are 1 to 65535." +msgstr "有效值为 1 到 65535。" + +#: driver.rui3.RUI3.get_p2p_receive_data:6 eebb8abcb3ac4355b0c77231588c7b5b of +msgid "0: Continuous listening." +msgstr "0:连续监听。" + +#: a661be946b744074868f3e20609a9d70 driver.rui3.RUI3.get_p2p_receive_data:7 of +msgid "65535: No timeout." +msgstr "65535:无超时。" + +#: 21cb11cf7f964200a5b2605b36280659 driver.rui3.RUI3.get_p2p_receive_data:9 of +msgid "Indicates whether to convert the payload to a string. Default is False." +msgstr "是否将负载转换为字符串。默认值为 False。" + +#: 6901c0006ed24e1685bdc924c7231cd2 driver.rui3.RUI3.get_p2p_receive_data:10 of +msgid "" +"A tuple (RSSI, SNR, Payload) if data is received; False if no data is " +"received." +msgstr "如果收到数据,则返回一个元组 (RSSI, SNR, Payload);如果没有收到数据,则返回 False。" + +#: d99d0e4009614376b14b68057805145b driver.rui3.RUI3.get_p2p_sync_word:1 of +msgid "Get the current sync word in P2P mode." +msgstr "获取当前 P2P 同步字。" + +#: be4b379f4c424ac79741d1fc75d5c758 driver.rui3.RUI3.get_p2p_sync_word:3 of +msgid "The sync word as a string." +msgstr "当前 P2P 同步字作为字符串。" + +#: bf6897d86d584c89978358e7db2b84bd driver.rui3.RUI3.set_p2p_sync_word:1 of +msgid "Set the sync word in P2P mode." +msgstr "设置 P2P 同步字。" + +#: driver.rui3.RUI3.set_p2p_sync_word:3 e664ccbe4973497899cd860e374be8af of +msgid "The sync word value. - Must be in the range of 0x0000 to 0xFFFF." +msgstr "" +"P2P 同步字值。 \n" +" - 必须在 0x0000 到 0xFFFF 之间。" + +#: ba9815b992894ec5a7c757a7c9e48329 driver.rui3.RUI3.set_p2p_sync_word:3 of +msgid "The sync word value." +msgstr "P2P 同步字值。" + +#: 94ffbd7e8b2241d1a8a47131f838052e driver.rui3.RUI3.set_p2p_sync_word:5 of +msgid "Must be in the range of 0x0000 to 0xFFFF." +msgstr "必须在 0x0000 到 0xFFFF 之间。" + +#: b6f0d68b99454db5b1cfbb50886fcb31 driver.rui3.RUI3.set_p2p_sync_word:7 of +msgid "The response from the command execution." +msgstr "命令执行的响应。" + +#~ msgid "UiFlow2 Code Block:" +#~ msgstr "UiFlow2 代码块:" + +#~ msgid "|init.png|" +#~ msgstr "" + +#~ msgid "init.png" +#~ msgstr "" + +#~ msgid "|get_abp_config.png|" +#~ msgstr "" + +#~ msgid "get_abp_config.png" +#~ msgstr "" + +#~ msgid "|get_otaa_config.png|" +#~ msgstr "" + +#~ msgid "get_otaa_config.png" +#~ msgstr "" + +#~ msgid "|set_abp_config.png|" +#~ msgstr "" + +#~ msgid "set_abp_config.png" +#~ msgstr "" + +#~ msgid "|set_otaa_config.png|" +#~ msgstr "" + +#~ msgid "set_otaa_config.png" +#~ msgstr "" + +#~ msgid "|get_received_data.png|" +#~ msgstr "" + +#~ msgid "|get_received_data_string.png|" +#~ msgstr "" + +#~ msgid "|get_received_data_count.png|" +#~ msgstr "" + +#~ msgid "|reset_module_to_default.png|" +#~ msgstr "" + +#~ msgid "reset_module_to_default.png" +#~ msgstr "" + +#~ msgid "|get_device_eui.png|" +#~ msgstr "" + +#~ msgid "|set_join_config.png|" +#~ msgstr "" + +#~ msgid "set_join_config.png" +#~ msgstr "" + +#~ msgid "|join_network.png|" +#~ msgstr "" + +#~ msgid "join_network.png" +#~ msgstr "" + +#~ msgid "|set_join_mode.png|" +#~ msgstr "" + +#~ msgid "set_join_mode.png" +#~ msgstr "" + +#~ msgid "|get_join_state.png|" +#~ msgstr "" + +#~ msgid "get_join_state.png" +#~ msgstr "" + +#~ msgid "|get_last_receive.png|" +#~ msgstr "" + +#~ msgid "get_last_receive.png" +#~ msgstr "" + +#~ msgid "|send_data.png|" +#~ msgstr "" + +#~ msgid "send_data.png" +#~ msgstr "" + +#~ msgid "|set_network_mode.png|" +#~ msgstr "" + +#~ msgid "set_network_mode.png" +#~ msgstr "" + +#~ msgid "|get_p2p_frequency.png|" +#~ msgstr "" + +#~ msgid "get_p2p_frequency.png" +#~ msgstr "" + +#~ msgid "|set_p2p_frequency.png|" +#~ msgstr "" + +#~ msgid "set_p2p_frequency.png" +#~ msgstr "" + +#~ msgid "|get_p2p_spreading_factor.png|" +#~ msgstr "" + +#~ msgid "get_p2p_spreading_factor.png" +#~ msgstr "" + +#~ msgid "|set_p2p_spreading_factor.png|" +#~ msgstr "" + +#~ msgid "set_p2p_spreading_factor.png" +#~ msgstr "" + +#~ msgid "|get_p2p_bandwidth.png|" +#~ msgstr "" + +#~ msgid "get_p2p_bandwidth.png" +#~ msgstr "" + +#~ msgid "|set_p2p_fsk_bandwidth.png|" +#~ msgstr "" + +#~ msgid "|get_p2p_code_rate.png|" +#~ msgstr "" + +#~ msgid "get_p2p_code_rate.png" +#~ msgstr "" + +#~ msgid "|set_p2p_code_rate.png|" +#~ msgstr "" + +#~ msgid "set_p2p_code_rate.png" +#~ msgstr "" + +#~ msgid "|get_p2p_preamble_length.png|" +#~ msgstr "" + +#~ msgid "get_p2p_preamble_length.png" +#~ msgstr "" + +#~ msgid "|set_p2p_preamble_length.png|" +#~ msgstr "" + +#~ msgid "set_p2p_preamble_length.png" +#~ msgstr "" + +#~ msgid "|get_p2p_tx_power.png|" +#~ msgstr "" + +#~ msgid "get_p2p_tx_power.png" +#~ msgstr "" + +#~ msgid "|set_p2p_tx_power.png|" +#~ msgstr "" + +#~ msgid "set_p2p_tx_power.png" +#~ msgstr "" + +#~ msgid "|get_p2p_fsk_bitrate.png|" +#~ msgstr "" + +#~ msgid "get_p2p_fsk_bitrate.png" +#~ msgstr "" + +#~ msgid "|set_p2p_fsk_bitrate.png|" +#~ msgstr "" + +#~ msgid "set_p2p_fsk_bitrate.png" +#~ msgstr "" + +#~ msgid "|send_p2p_data.png|" +#~ msgstr "" + +#~ msgid "send_p2p_data.png" +#~ msgstr "" + +#~ msgid "|get_p2p_receive_data.png|" +#~ msgstr "" + +#~ msgid "get_p2p_receive_data.png" +#~ msgstr "" + +#~ msgid "|get_p2p_sync_word.png|" +#~ msgstr "" + +#~ msgid "get_p2p_sync_word.png" +#~ msgstr "" + +#~ msgid "|set_p2p_sync_word.png|" +#~ msgstr "" + +#~ msgid "set_p2p_sync_word.png" +#~ msgstr "" + diff --git a/examples/base/dtu_lorawan_rui3/base_lorawan868_otaa_atom_lite_example.m5f2 b/examples/base/dtu_lorawan_rui3/base_lorawan868_otaa_atom_lite_example.m5f2 new file mode 100644 index 00000000..999923da --- /dev/null +++ b/examples/base/dtu_lorawan_rui3/base_lorawan868_otaa_atom_lite_example.m5f2 @@ -0,0 +1 @@ +{"version":"V2.0","versionNumber":"V2.2.5","type":"atom","components":[],"resources":[{"hardware":["hardware_button","hardware_pin_button","rgb","ir"]},{"base":["base_lorawaneu868"]}],"units":[],"hats":[],"bases":[{"type":"base_lorawaneu868","name":"base_lorawaneu868","id":"sct$$G$fN_Vvt_+l","createTime":1745465678482,"initBlockType":"base_lorawaneu868_init","initBlockId":"YdEU`fTGpD_nta1@~O9n"}],"i2cs":[],"blockly":"truebuiltinTRUEFALSEOTAA70B3D57ED007006AA843ECB026197C981D67AEFACC72D01E70B3D57ED0063472120C10000Success join the networkrgbpalette#33ff33AABBCC1Failed Join to the networkrgbpalette#ff0000trueBtnANEQ0hello M5Message queue is empty","screen":[],"logicWhenNum":0,"customList":[]} \ No newline at end of file diff --git a/examples/base/dtu_lorawan_rui3/base_lorawan868_otaa_atom_lite_example.py b/examples/base/dtu_lorawan_rui3/base_lorawan868_otaa_atom_lite_example.py new file mode 100644 index 00000000..39cae671 --- /dev/null +++ b/examples/base/dtu_lorawan_rui3/base_lorawan868_otaa_atom_lite_example.py @@ -0,0 +1,60 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import os, sys, io +import M5 +from M5 import * +from hardware import RGB +from base import AtomDTULoRaWANRUI3Base + + +rgb = None +base_lorawaneu868 = None + + +def setup(): + global rgb, base_lorawaneu868 + + M5.begin() + rgb = RGB() + base_lorawaneu868 = AtomDTULoRaWANRUI3Base(2, port=(19, 22)) + base_lorawaneu868.set_network_mode(1) + base_lorawaneu868.set_otaa_config( + "70B3D57ED007006A", "A843ECB026197C981D67AEFACC72D01E", "70B3D57ED0063472" + ) + base_lorawaneu868.set_rx_delay_on_window1(1) + base_lorawaneu868.set_rx_delay_on_window2(2) + base_lorawaneu868.set_rx_data_rate_on_windows2(0) + base_lorawaneu868.set_lorawan_node_class("C") + if base_lorawaneu868.join_network(10000): + print("Success join the network") + rgb.fill_color(0x33FF33) + base_lorawaneu868.send_data(1, "AABBCC", 0) + else: + print("Failed Join to the network") + rgb.fill_color(0xFF0000) + + +def loop(): + global rgb, base_lorawaneu868 + M5.update() + if BtnA.wasPressed(): + if (base_lorawaneu868.get_received_data_count()) != 0: + print(base_lorawaneu868.get_received_data_string()) + else: + print("Message queue is empty") + + +if __name__ == "__main__": + try: + setup() + while True: + loop() + except (Exception, KeyboardInterrupt) as e: + try: + from utility import print_error_msg + + print_error_msg(e) + except ImportError: + print("please update to latest firmware") diff --git a/examples/base/dtu_lorawan_rui3/base_lorawan868_p2p_rx_atom_lite_example.m5f2 b/examples/base/dtu_lorawan_rui3/base_lorawan868_p2p_rx_atom_lite_example.m5f2 new file mode 100644 index 00000000..43bf1c57 --- /dev/null +++ b/examples/base/dtu_lorawan_rui3/base_lorawan868_p2p_rx_atom_lite_example.m5f2 @@ -0,0 +1 @@ +{"version":"V2.0","versionNumber":"V2.2.5","type":"atom","components":[],"resources":[{"hardware":["hardware_button","hardware_pin_button","rgb","ir"]},{"base":["base_lorawaneu868"]}],"units":[],"hats":[],"bases":[{"type":"base_lorawaneu868","name":"base_lorawaneu868","id":"n+&@cK5FlfM&LKFY","createTime":1745481297301,"initBlockType":"base_lorawaneu868_init","initBlockId":"Wi13pnnS;O_q2gdGiQMq"}],"i2cs":[],"blockly":"truebuiltinFALSEFALSE600000000701408Press the button to receive P2P messagetrueBtnAhello M55000False","screen":[],"logicWhenNum":0,"customList":[]} \ No newline at end of file diff --git a/examples/base/dtu_lorawan_rui3/base_lorawan868_p2p_rx_atom_lite_example.py b/examples/base/dtu_lorawan_rui3/base_lorawan868_p2p_rx_atom_lite_example.py new file mode 100644 index 00000000..df55c0ff --- /dev/null +++ b/examples/base/dtu_lorawan_rui3/base_lorawan868_p2p_rx_atom_lite_example.py @@ -0,0 +1,50 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import os, sys, io +import M5 +from M5 import * +from hardware import RGB +from base import AtomDTULoRaWANRUI3Base + + +rgb = None +base_lorawaneu868 = None + + +def setup(): + global rgb, base_lorawaneu868 + + M5.begin() + rgb = RGB() + base_lorawaneu868 = AtomDTULoRaWANRUI3Base(2, port=(19, 22)) + base_lorawaneu868.set_network_mode(0) + base_lorawaneu868.set_p2p_frequency(600000000) + base_lorawaneu868.set_p2p_spreading_factor(7) + base_lorawaneu868.set_p2p_bandwidth(0) + base_lorawaneu868.set_p2p_tx_power(14) + base_lorawaneu868.set_p2p_code_rate(0) + base_lorawaneu868.set_p2p_preamble_length(8) + print("Press the button to receive P2P message") + + +def loop(): + global rgb, base_lorawaneu868 + M5.update() + if BtnA.wasPressed(): + print(base_lorawaneu868.get_p2p_receive_data(5000, False)) + + +if __name__ == "__main__": + try: + setup() + while True: + loop() + except (Exception, KeyboardInterrupt) as e: + try: + from utility import print_error_msg + + print_error_msg(e) + except ImportError: + print("please update to latest firmware") diff --git a/examples/base/dtu_lorawan_rui3/base_lorawan868_p2p_tx_atom_lite_example.m5f2 b/examples/base/dtu_lorawan_rui3/base_lorawan868_p2p_tx_atom_lite_example.m5f2 new file mode 100644 index 00000000..a80184b2 --- /dev/null +++ b/examples/base/dtu_lorawan_rui3/base_lorawan868_p2p_tx_atom_lite_example.m5f2 @@ -0,0 +1 @@ +{"version":"V2.0","versionNumber":"V2.2.5","type":"atom","components":[],"resources":[{"hardware":["hardware_button","hardware_pin_button","rgb","ir"]},{"base":["base_lorawaneu868"]}],"units":[],"hats":[],"bases":[{"type":"base_lorawaneu868","name":"base_lorawaneu868","id":"aJ6rfGGoj`dQDJpT","createTime":1745477733126,"initBlockType":"base_lorawaneu868_init","initBlockId":"Wi13pnnS;O_q2gdGiQMq"}],"i2cs":[],"blockly":"truebuiltinFALSEFALSE600000000701408Press the button to send P2P messagetrueBtnAAABBCCFalse","screen":[],"logicWhenNum":0,"customList":[]} \ No newline at end of file diff --git a/examples/base/dtu_lorawan_rui3/base_lorawan868_p2p_tx_atom_lite_example.py b/examples/base/dtu_lorawan_rui3/base_lorawan868_p2p_tx_atom_lite_example.py new file mode 100644 index 00000000..9e3c825c --- /dev/null +++ b/examples/base/dtu_lorawan_rui3/base_lorawan868_p2p_tx_atom_lite_example.py @@ -0,0 +1,50 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +import os, sys, io +import M5 +from M5 import * +from hardware import RGB +from base import AtomDTULoRaWANRUI3Base + + +rgb = None +base_lorawaneu868 = None + + +def setup(): + global rgb, base_lorawaneu868 + + M5.begin() + rgb = RGB() + base_lorawaneu868 = AtomDTULoRaWANRUI3Base(2, port=(19, 22)) + base_lorawaneu868.set_network_mode(0) + base_lorawaneu868.set_p2p_frequency(600000000) + base_lorawaneu868.set_p2p_spreading_factor(7) + base_lorawaneu868.set_p2p_bandwidth(0) + base_lorawaneu868.set_p2p_tx_power(14) + base_lorawaneu868.set_p2p_code_rate(0) + base_lorawaneu868.set_p2p_preamble_length(8) + print("Press the button to send P2P message") + + +def loop(): + global rgb, base_lorawaneu868 + M5.update() + if BtnA.wasPressed(): + base_lorawaneu868.send_p2p_data("AABBCC", timeout=0, to_hex=False) + + +if __name__ == "__main__": + try: + setup() + while True: + loop() + except (Exception, KeyboardInterrupt) as e: + try: + from utility import print_error_msg + + print_error_msg(e) + except ImportError: + print("please update to latest firmware") diff --git a/m5stack/libs/base/__init__.py b/m5stack/libs/base/__init__.py index 89e0257c..1be1e3fb 100644 --- a/m5stack/libs/base/__init__.py +++ b/m5stack/libs/base/__init__.py @@ -5,7 +5,7 @@ _attrs = { "ATOMCANBase": "atom_can", "AtomDTULoRaWANBase": "dtu_lorawan", - "AtomDTUNBIoT": "dtu_nbiot", + "AtomDTULoRaWANRUI3Base": "dtu_lorawan_rui3", "ATOMGPSBase": "atom_gps", "ATOMSocketBase": "atom_socket", "ATOMEchoBase": "echo", diff --git a/m5stack/libs/base/dtu_lorawan_rui3.py b/m5stack/libs/base/dtu_lorawan_rui3.py new file mode 100644 index 00000000..9bd8501a --- /dev/null +++ b/m5stack/libs/base/dtu_lorawan_rui3.py @@ -0,0 +1,106 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +from rui3 import RUI3 +import sys + +if sys.platform != "esp32": + from typing import Literal + + +class AtomDTULoRaWANRUI3Base(RUI3): + """Create an AtomDTULoRaWANRUI3Base object. + + :param int id: The UART ID to use (0, 1, or 2). Default is 2. + :param port: A list or tuple containing the TX and RX pin numbers. + :type port: list | tuple + :param bool debug: Whether to enable debug mode. Default is False. + + MicroPython Code Block: + + .. code-block:: python + + from base import AtomDTULoRaWANRUI3Base + + lorawan_rui3 = AtomDTULoRaWANRUI3Base(2, port=(19, 22)) + """ + + def __init__(self, id: Literal[0, 1, 2] = 2, port: list | tuple = None, debug=False): + super().__init__(id, port[1], port[0], debug) + + def set_abp_config(self, dev_addr: str, apps_key: str, nwks_key: str) -> None: + """Configure the device for ABP (Activation By Personalization) mode. + + :param str dev_addr: The device address for ABP configuration. + :param str apps_key: The application session key for encryption. + :param str nwks_key: The network session key for communication. + + MicroPython Code Block: + + .. code-block:: python + + lorawan_rui3.set_abp_config( + dev_addr="26011D89", + apps_key="2B7E151628AED2A6ABF7158809CF4F3C", + nwks_key="2B7E151628AED2A6ABF7158809CF4F3C" + ) + """ + self.set_join_mode(0) + self.set_device_address(dev_addr) + self.set_apps_key(apps_key) + self.set_networks_key(nwks_key) + + def get_abp_config( + self, + ) -> tuple[str | bool | None, str | bool | None, str | bool | None]: + """Retrieve the current ABP configuration. + + :returns: A tuple containing (device_address, apps_key, networks_key). + :rtype: tuple[str, str, str] + + MicroPython Code Block: + + .. code-block:: python + + print(lorawan_rui3.get_abp_config()) + """ + return (self.get_device_address(), self.get_apps_key(), self.get_networks_key()) + + def set_otaa_config(self, device_eui: str, app_key: str, app_eui: str) -> None: + """Configure the device for OTAA (Over-The-Air Activation) mode. + + :param str device_eui: The device EUI for OTAA configuration. + :param str app_key: The application key for encryption. + :param str app_eui: The application EUI for OTAA configuration. + + MicroPython Code Block: + + .. code-block:: python + + lorawan_rui3.set_otaa_config( + device_eui="2CF7F1C0420000AA", + app_key="2B7E151628AED2A6ABF7158809CF4F3C" + app_eui="80000000000000AA", + ) + """ + self.set_join_mode(1) + self.set_device_eui(device_eui) + self.set_app_key(app_key) + self.set_app_eui(app_eui) + + def get_otaa_config( + self, + ) -> tuple[str | bool | None, str | bool | None, str | bool | None]: + """Retrieve the current OTAA configuration. + + :returns: A tuple containing (device_eui, app_key, app_eui). + :rtype: tuple[str, str, str] + + MicroPython Code Block: + + .. code-block:: python + + print(lorawan_rui3.get_otaa_config()) + """ + return (self.get_device_eui(), self.get_app_key(), self.get_app_eui()) diff --git a/m5stack/libs/base/manifest.py b/m5stack/libs/base/manifest.py index 86e27dd8..50abda24 100644 --- a/m5stack/libs/base/manifest.py +++ b/m5stack/libs/base/manifest.py @@ -10,7 +10,7 @@ "atom_socket.py", "display.py", "dtu_lorawan.py", - "dtu_nbiot.py", + "dtu_lorawan_rui3.py", "echo.py", "hdriver.py", "motion.py", diff --git a/m5stack/libs/driver/rui3.py b/m5stack/libs/driver/rui3.py index 0553cc81..122713f3 100644 --- a/m5stack/libs/driver/rui3.py +++ b/m5stack/libs/driver/rui3.py @@ -3,94 +3,137 @@ # SPDX-License-Identifier: MIT import machine +import _thread import time class RUI3: - """ - note: - en: RUI3 class provides methods to interact with RAK Wireless modules using AT commands. It supports module initialization, configuration, and retrieval of various module parameters. - - details: - link: https://docs.rakwireless.com/ - image: https://docs.rakwireless.com/assets/images/logo.png - category: - - example: - - ../../../examples/module/rui3/rui3_example.py - - m5f2: - - module/rui3/rui3_example.m5f2 - """ + _instance = None + _recv_thread_running = False def __init__(self, id, tx, rx, debug=False): - """ - note: - en: Initialize the RUI3 module by setting up UART communication with the specified parameters. - - params: - id: - note: The UART ID used for communication. - tx: - note: The UART TX pin. - rx: - note: The UART RX pin. - debug: - note: Enables debug mode to log additional details, default is False. - """ + if RUI3._instance is not None: + try: + RUI3._instance.close() + except Exception as e: + print("clean rui3 instance error:", e) + self.uart = machine.UART(id, tx=tx, rx=rx, baudrate=115200, bits=8, parity=None, stop=1) self.debug = debug + self.lock = _thread.allocate_lock() + self.buffer = [] + self.running = True self.uart.read() + + RUI3._instance = self + RUI3._recv_thread_running = True + + _thread.start_new_thread(self._recv_loop, ()) + if self.get_serial_number() is False: + self.close() raise ValueError("The LoRaWAN-X Unit is not responding. Please check the connection.") - def send_cmd(self, cmd, have_return=False, is_single=False, async_event=False, timeout=100): - """ - note: - en: Sends an AT command to the module and optionally reads the response. - - params: - cmd: - note: The AT command string to send. - have_return: - note: Specifies whether to read a response from the module. - is_single: - note: Indicates if the command expects a single-line response. - async_event: - note: Specifies whether to handle asynchronous events. - timeout: - note: The timeout duration in milliseconds for receiving a response, default is 100ms. - - returns: - note: The processed response from the module or None if no response is expected. + def close(self): + self.running = False + RUI3._recv_thread_running = False + time.sleep_ms(100) + try: + self.uart.deinit() + except: + pass + RUI3._instance = None + if self.debug: + print("RUI3 已关闭。") + + def _recv_loop(self): + while self.running: + if not self.lock.locked(): + self.lock.acquire() + try: + line = self.uart.readline() + if line: + if self.debug: + print(f"DEBUG: RAW DATA: {line}") + if line.startswith(b"+EVT:RX_"): + parts = line.decode("utf-8").strip().split(":") + decoded = (int(parts[-2]), parts[-1]) + self.buffer.append(decoded) + finally: + self.lock.release() + time.sleep_ms(100) + + def get_received_data(self): + """Retrieve the data from the last received message. + + :returns: A tuple containing the port number (int) and the received data (bytes), or False if no data was received. + :rtype: tuple[int, bytes] | bool + + MicroPython Code Block: + + .. code-block:: python + + data = lorawan_rui3.get_received_data() + if data: + print(f"Received data: {data}") + else: + print("No data received.") """ - self.uart.read() - self.uart.write(cmd + "\r\n") - if have_return: - return self.read_response(cmd.rstrip("?"), is_single, async_event, timeout) + data = self.buffer[:] + self.buffer.clear() + return data - def read_response(self, cmd, is_single=False, async_event=False, timeout=100): + def get_received_data_string(self) -> str: + """Retrieve the received data as a string. + + :returns: The received data as a string, or an empty string if no data was received. + :rtype: str + + MicroPython Code Block: + + .. code-block:: python + + data = lorawan_rui3.get_received_data_string() + if data: + print(f"Received data: {data}") + else: + print("No data received.") """ - note: - en: Reads and processes the module response to an AT command. - - params: - cmd: - note: The AT command sent to the module. - is_single: - note: Indicates if a single-line response is expected. - async_event: - note: Specifies whether to handle asynchronous events. - timeout: - note: The timeout duration in milliseconds for receiving a response. - - returns: - note: - - True if the response is valid and matches a single-line expectation. - - A processed response string when CMD+RESPONSE is received. - - An event response string if async_event is enabled and an event is detected. - - False if no valid response is received or an error occurs. + return ",".join([str(item[1]) for item in self.get_received_data()]) + + def get_received_data_count(self): + """Retrieve the number of received data. + + :returns: The number of received data. + :rtype: int + + MicroPython Code Block: + + .. code-block:: python + + count = lorawan_rui3.get_received_data_count() + print(f"Received data count: {count}") """ + return len(self.buffer) + + def send_cmd( + self, + cmd: str, + have_return: bool = False, + is_single: bool = False, + async_event: bool = False, + timeout: int = 100, + ) -> str | bool: + self.lock.acquire() + try: + self.uart.read() + self.uart.write(cmd + "\r\n") + if have_return: + return self.read_response(cmd.rstrip("?"), is_single, async_event, timeout) + finally: + self.lock.release() + + def read_response(self, cmd, is_single=False, async_event=False, timeout=100): start_time = time.ticks_ms() response = "" event_response = None @@ -163,129 +206,42 @@ def read_response(self, cmd, is_single=False, async_event=False, timeout=100): return False def get_commuinication_state(self): - """ - note: - en: Checks the communication state by sending the AT command and expecting a response. - - params: - note: - - returns: - note: The response from the module indicating the communication state. - """ return self.send_cmd("AT", True, True) def reset_module(self): - """ - note: - en: Resets the module using the ATZ command. - - params: - note: - """ self.send_cmd("ATZ") def reset_module_to_default(self): - """ - note: - en: Resets the module to its default settings using the ATR command. Waits for the reset process to complete. + """Reset the module to its factory default settings. - params: - note: + MicroPython Code Block: + + .. code-block:: python + + rui3.reset_module_to_default() """ self.send_cmd("ATR") time.sleep(1.5) - def get_serial_number(self): - """ - note: - en: Retrieves the serial number of the module. - - params: - note: - - returns: - note: The serial number as a string. - """ + def get_serial_number(self) -> str | bool: return self.send_cmd("AT+SN=?", True) def get_fireware_version(self): - """ - note: - en: Retrieves the firmware version of the module. - - params: - note: - - returns: - note: The firmware version as a string. - """ return self.send_cmd("AT+VER=?", True) def get_at_version(self): - """ - note: - en: Retrieves the AT command version supported by the module. - - params: - note: - - returns: - note: The AT command version as a string. - """ return self.send_cmd("AT+CLIVER=?", True) def get_hardware_version(self): - """ - note: - en: Retrieves the hardware version of the module. - - params: - note: - - returns: - note: The hardware version as a string. - """ return self.send_cmd("AT+HWMODEL=?", True) def get_hardware_id(self): - """ - note: - en: Retrieves the hardware ID of the module. - - params: - note: - - returns: - note: The hardware ID as a string. - """ return self.send_cmd("AT+HWID=?", True) def get_ble_mac(self): - """ - note: - en: Retrieves the Bluetooth MAC address of the module. - - params: - note: - - returns: - note: The BLE MAC address as a string. - """ return self.send_cmd("AT+BLEMAC=?", True) def set_sleep_time(self, time): - """ - note: - en: Configures the sleep time for the module. If no time is provided, the module enters sleep mode. - - params: - time: - note: The sleep duration in seconds. If None, triggers sleep mode without specifying a duration. - - returns: - note: True if the command is successfully sent, else False. - """ if time: return self.send_cmd("AT+SLEEP=" + str(time), True, True, False, 200) else: @@ -293,311 +249,86 @@ def set_sleep_time(self, time): return True def get_low_power_mode(self): - """ - note: - en: Checks if the module is in low-power mode. - - params: - note: - - returns: - note: True if low-power mode is enabled, otherwise False. - """ return self.send_cmd("AT+LPM=?", True) == "1" def set_low_power_mode(self, mode: bool): - """ - note: - en: Sets the module low-power mode. - - params: - mode: - note: A boolean value indicating whether to enable (True) or disable (False) low-power mode. - - returns: - note: True if the command is successfully sent, else False. - """ return self.send_cmd("AT+LPM=" + str(int(mode)), True, True) def set_baud_rate(self, rate): - """ - note: - en: Sets the baud rate for UART communication. - - params: - rate: - note: The desired baud rate value. - - returns: - note: True if the command is successfully sent, else False. - """ return self.send_cmd("AT+BAUD=" + str(rate), True, True) def get_baud_rate(self): - """ - note: - en: Retrieves the current UART baud rate setting. - - params: - note: - - returns: - note: The baud rate as an integer. - """ return int(self.send_cmd("AT+BAUD=?", True)) # OTAA Mode def get_device_eui(self): - """ - note: - en: Retrieves the Device EUI for OTAA (Over-The-Air Activation) mode. + """Get the device EUI. - params: - note: + :returns: The device EUI. + :rtype: str - returns: - note: The Device EUI as a string. - """ - return self.send_cmd("AT+DEVEUI=?", True) + MicroPython Code Block: - def set_device_eui(self, eui: str): - """ - note: - en: Configures the Device EUI for OTAA mode. + .. code-block:: python - params: - eui: - note: The Device EUI to set, as a string. + lorawan_rui3.get_device_eui() - returns: - note: True if the command is successfully sent, else False. """ + return self.send_cmd("AT+DEVEUI=?", True) + + def set_device_eui(self, eui: str): return self.send_cmd("AT+DEVEUI=" + eui, True, True) def get_app_eui(self): - """ - note: - en: Retrieves the Application EUI for OTAA mode. - - params: - note: - - returns: - note: The Application EUI as a string. - """ return self.send_cmd("AT+APPEUI=?", True) def set_app_eui(self, eui: str): - """ - note: - en: Configures the Application EUI for OTAA mode. - - params: - eui: - note: The Application EUI to set, as a string. - - returns: - note: True if the command is successfully sent, else False. - """ return self.send_cmd("AT+APPEUI=" + eui, True, True) def get_app_key(self): - """ - note: - en: Retrieves the Application Key for OTAA mode. - - params: - note: - - returns: - note: The Application Key as a string. - """ return self.send_cmd("AT+APPKEY=?", True) def set_app_key(self, key: str): - """ - note: - en: Configures the Application Key for OTAA mode. - - params: - key: - note: The Application Key to set, as a string. - - returns: - note: True if the command is successfully sent, else False. - """ return self.send_cmd("AT+APPKEY=" + key, True, True) # ABP Mode def get_device_address(self): - """ - note: - en: Retrieves the device address. - - params: - note: - - returns: - note: The device address as a string. - """ return self.send_cmd("AT+DEVADDR=?", True) def set_device_address(self, address: str): - """ - note: - en: Sets the device address. - - params: - address: - note: The device address to set, provided as a string. - """ return self.send_cmd("AT+DEVADDR=" + address, True, True) def get_apps_key(self): - """ - note: - en: Retrieves the application session key. - - params: - note: - - returns: - note: The application session key as a string. - """ return self.send_cmd("AT+APPSKEY=?", True) def set_apps_key(self, key: str): - """ - note: - en: Sets the application session key. This operation is applicable only in ABP mode. - - params: - key: - note: The application session key to set, provided as a string. - - returns: - note: The result of the AT command execution - """ return self.send_cmd("AT+APPSKEY=" + key, True, True) def get_networks_key(self): - """ - note: - en: Retrieves the network session key. - - params: - note: - - returns: - note: The network session key as a string. - """ return self.send_cmd("AT+NWKSKEY=?", True) def set_networks_key(self, key: str): - """ - note: - en: Sets the network session key. This operation is applicable only in ABP mode. - - params: - key: - note: The network session key to set, provided as a string. - - returns: - note: The result of the AT command execution - """ return self.send_cmd("AT+NWKSKEY=" + key, True, True) def set_network_id(self, id: str): - """ - note: - en: Sets the network ID. - - params: - id: - note: The network ID to set, provided as a string. - - returns: - note: The result of the AT command execution - """ return self.send_cmd("AT+NETID=" + id, True, True) def get_network_id(self): - """ - note: - en: Retrieves the network ID. - - params: - note: - - returns: - note: The network ID as a string. - """ return self.send_cmd("AT+NETID=?", True) def get_mc_root_key(self): - """ - note: - en: Retrieves the multicast root key. - - params: - note: - - returns: - note: The multicast root key as a string. - """ return self.send_cmd("AT+MCROOTKEY=?", True) def get_confirm_mode(self): - """ - note: - en: Retrieves the confirmation mode. - - params: - note: - - returns: - note: True if confirmation mode is enabled; otherwise, False. - """ return self.send_cmd("AT+CFM=?", True) == "1" def set_confirm_mode(self, mode: bool): - """ - note: - en: Sets the confirmation mode. - - params: - mode: - note: A boolean indicating whether to enable (True) or disable (False) confirmation mode. - - returns: - note: The result of the AT command execution - """ return self.send_cmd("AT+CFM=" + str(int(mode)), True, True) def get_confirm_state(self): - """ - note: - en: Retrieves the status of the last confirmed uplink. - - params: - note: - - returns: - note: True if the last confirmed uplink succeeded; otherwise, False. - """ return self.send_cmd("AT+CFS=?", True) == "1" def get_join_config(self): - """ - note: - en: Retrieves the current join configuration for LoRa. - - params: - note: - - returns: - note: A tuple containing state, auto_join, retry_interval, and max_retry as integers, or False if retrieval failed. - """ lora_config = self.send_cmd("AT+JOIN=?", True) if lora_config: return tuple(map(int, lora_config.split(":")[:4])) @@ -607,31 +338,37 @@ def set_join_config( self, state: int, auto_join: int, - retry_interval: int = 8, - max_retry: int = 0, + reattempt_interval: int = 8, + max_attempts: int = 0, timeout: int = 8000, ): - """ - note: - en: Configures the join parameters for LoRa. The configuration does not confirm network join success. - - params: - state: - note: The join state to configure, as an integer. - auto_join: - note: The auto-join flag, as an integer. - retry_interval: - note: The interval between join retries, default is 8 seconds. - max_retry: - note: The maximum number of retries, default is 0 (no limit). - timeout: - note: The timeout duration in milliseconds for the command, default is 8000ms. - - return: - note: True if the command is successfully set, else False. + """Configure the join parameters for LoRaWAN. + + The configuration does not confirm network join success. + + :param int state: The join state to configure, as an integer. + :param int auto_join: The auto-join flag, as an integer. + :param int reattempt_interval: The interval between join retries, in seconds. Default is 8. + :param int max_attempts: The maximum number of retries. Default is 0 (no limit). + :param int timeout: The timeout duration in milliseconds for the command. Default is 8000ms. + + :returns: True if the command is successfully set, else False. + :rtype: bool + + MicroPython Code Block: + + .. code-block:: python + + lorawan_rui3.set_join_config( + state=1, + auto_join=1, + reattempt_interval=10, + max_attempts=5, + timeout=10000 + ) """ return self.send_cmd( - f"AT+JOIN={state}:{auto_join}:{retry_interval}:{max_retry}", + f"AT+JOIN={state}:{auto_join}:{reattempt_interval}:{max_attempts}", True, False, True, @@ -639,95 +376,121 @@ def set_join_config( ) def join_network(self, timeout: int = 8000): - """ - note: - en: Joins the LoRa network using predefined join parameters. + """Join the LoRa network using predefined join parameters. + + :param int timeout: The timeout duration in milliseconds for the join command. Default is 8000ms. + + :returns: True if the command is successfully set, else False. + :rtype: bool - params: - timeout: - note: The timeout duration in milliseconds for the join command, default is 8000ms. + |join_network_return.png| - returns: - note: True if the module successfully joins the network, otherwise False + + MicroPython Code Block: + + .. code-block:: python + + if lorawan_rui3.join_network(timeout=10000): + print("Network joined successfully!") + else: + print("Failed to join network.") """ buf = self.send_cmd("AT+JOIN=1:0:8:0", True, False, True, timeout) if timeout != 0: return buf == "JOINED" def get_join_mode(self): - """ - note: - en: Retrieves the current join mode. 0 indicates ABP mode, 1 indicates OTAA mode. - - params: - note: - - returns: - note: The join mode as an integer (0 for ABP, 1 for OTAA). - """ return int(self.send_cmd("AT+NJM=?", True)) def set_join_mode(self, mode: int): - """ - note: - en: Sets the join mode for the LoRa module. + """Set the join mode for the LoRa module. + + :param int mode: The join mode to set, 0 for ABP or 1 for OTAA. + + :returns: True if the command is successfully set, else False. + :rtype: bool + + MicroPython Code Block: + + .. code-block:: python - params: - mode: - note: The join mode to set, 0 for ABP or 1 for OTAA. + lorawan_rui3.set_join_mode(1) # Set to OTAA mode - returns: - note: True if the command is successfully set, else False. """ return self.send_cmd("AT+NJM=" + str(mode), True, True) def get_join_state(self): - """ - note: - en: Checks whether the module has successfully joined the network. 1 indicates joined, 0 indicates not joined. + """Check whether the module has successfully joined the network. + + :returns: True if joined, otherwise False. + :rtype: bool - params: - note: + MicroPython Code Block: - returns: - note: True if joined, otherwise False. + .. code-block:: python + + if lorawan_rui3.get_join_state(): + print("Module is joined to the network.") + else: + print("Module is not joined to the network.") """ return self.send_cmd("AT+NJS=?", True) == "1" def get_last_receive(self): - """ - note: - en: Retrieves the data from the last received message. + """Retrieve the data from the last received message. - params: - note: + :returns: A tuple containing the port number (int) and the received data (bytes), or False if no data was received. + :rtype: tuple[int, bytes] | bool - returns: - note: The received data as a string. + MicroPython Code Block: + + .. code-block:: python + + last_data = lorawan_rui3.get_last_receive() + if last_data: + port, data = last_data + print(f"Received data on port {port}: {data}") + else: + print("No data received.") """ buf = self.send_cmd("AT+RECV=?", True) if isinstance(buf, str): if buf.find(":") != -1: buf = buf.split(":") - return (int(buf[0]), bytes.fromhex(buf[1])) + return (int(buf[0]), buf[1]) return False - def send_data(self, port: int, data: bytes, timeout=600): - """ - note: - en: Sends data through a specific port. - - params: - port: - note: The port number to send data through. - data: - note: The data to send, provided as bytes. - timeout: - note: The timeout duration in milliseconds for the send command, default is 600ms. - - returns: - note: True if the data was sent successfully, otherwise False. + def send_data(self, port: int, data: bytes | str, timeout: int = 600): + """Send data through a specific port. + + :param int port: The port number to send data through. + :param data: The data to send, provided as bytes or string(if data is bytes, it will be converted to string). + :type data: bytes | str + :param int timeout: The timeout duration in milliseconds for the send command. Default is 600ms. + + :returns: True if the data was sent successfully, otherwise False. + :rtype: bool + + |send_data_return.png| + + MicroPython Code Block: + + .. code-block:: python + + success = lorawan_rui3.send_data(port=1, data=b"HelloLoRa", timeout=800) + if success: + print("Data sent successfully!") + else: + print("Failed to send data.") + """ + if isinstance(data, str): + pass + elif isinstance(data, bytes): + data = data.decode() + else: + raise ValueError("Invalid data type.") + print(f"AT+SEND={port}:{data}") buf = self.send_cmd(f"AT+SEND={port}:{data}", True, False, True, timeout) if timeout != 0: return buf in [ @@ -735,674 +498,154 @@ def send_data(self, port: int, data: bytes, timeout=600): "SEND_CONFIRMED_OK", ] - def send_long_data(self, port: int, ack: bool, data: bytes, timeout=500): - """ - note: - en: Sends long data through a specific port with optional acknowledgment. - - params: - port: - note: The port number to send data through. - ack: - note: Indicates whether acknowledgment is required (True or False). - data: - note: The long data to send, provided as bytes. - timeout: - note: The timeout duration in milliseconds for the send command, default is 500ms. - - returns: - note: True if the data was sent successfully, otherwise False. - """ + def send_long_data(self, port: int, ack: bool, data: bytes, timeout: int = 500): return self.send_cmd(f"AT+SEND={port}:{int(ack)}:{data}", True, False, True, timeout) in [ "TX_DONE", "SEND_CONFIRMED_OK", ] def set_retry(self, retry: int): - """ - note: - en: Configures the retry cycle for transmissions. - - params: - retry: - note: The retry cycle value, ranging from 0 to 7, default is 0. - - returns: - note: True if the command is successfully set, else False. - """ return self.send_cmd(f"AT+RETY={retry}", True, True) def get_retry(self): - """ - note: - en: Retrieves the current retry cycle configuration. - - params: - note: - - returns: - note: The retry cycle value as an integer. - """ return int(self.send_cmd("AT+RETY=?", True)) def get_adaptive_rate_state(self): - """ - note: - en: Checks whether adaptive data rate (ADR) is enabled. 1 indicates enabled, 0 indicates disabled. - - params: - note: - - returns: - note: True if ADR is enabled, otherwise False. - """ return self.send_cmd("AT+ADR=?", True) == "1" def set_adaptive_rate_state(self, state: bool): - """ - note: - en: Configures the adaptive data rate (ADR) state. - - params: - state: - note: A boolean indicating whether to enable (True) or disable (False) ADR. - - returns: - note: True if the command is successfully set, else False. - """ return self.send_cmd("AT+ADR=" + str(int(state)), True, True) def get_lorawan_node_class(self): - """ - note: - en: Retrieves the current LoRaWAN node class. - - params: - note: - - returns: - note: The LoRaWAN node class as a string. - """ return self.send_cmd("AT+CLASS=?", True) def set_lorawan_node_class(self, node_class: str): - """ - note: - en: Sets the LoRaWAN node class. - - params: - node_class: - note: The node class to set, provided as a string (e.g., "A", "B", or "C"). - - returns: - note: True if the command is successfully set, else False. - """ return self.send_cmd("AT+CLASS=" + node_class, True, True) def get_duty_cycle_state(self): - """ - note: - en: Checks whether the ETSI duty cycle is enabled. 1 indicates enabled, 0 indicates disabled. - - params: - note: - - returns: - note: True if the duty cycle is enabled, otherwise False. - """ return self.send_cmd("AT+DCS=?", True) == "1" def set_duty_cycle_state(self, state: bool): - """ - note: - en: Sets the ETSI duty cycle state, which can be enabled or disabled depending on the region. - - params: - state: - note: A boolean indicating whether to enable (True) or disable (False) the duty cycle. - - returns: - note: The result of the AT command execution. - """ return self.send_cmd("AT+DCS=" + str(int(state)), True, True) def get_data_rate(self): - """ - note: - en: Retrieves the current data rate configuration. - - params: - note: - - returns: - note: The data rate as an integer. - """ return int(self.send_cmd("AT+DR=?", True)) def set_data_rate(self, rate: int): - """ - note: - en: Sets the data rate for communication. - - params: - rate: - note: The data rate to set, provided as an integer. - - returns: - note: The result of the AT command execution. - """ return self.send_cmd("AT+DR=" + str(rate), True, True) def get_join_delay_on_window1(self): - """ - note: - en: Retrieves the delay for the join procedure on window 1. - - params: - note: - - returns: - note: The join delay for window 1 as an integer. - """ return int(self.send_cmd("AT+JN1DL=?", True)) def set_join_delay_on_window1(self, delay: int): - """ - note: - en: Sets the join delay for window 1. - - params: - delay: - note: The join delay to set for window 1 as an integer. - - returns: - note: The result of the AT command execution. - """ return self.send_cmd("AT+JN1DL=" + str(delay), True, True) def get_join_delay_on_window2(self): - """ - note: - en: Retrieves the delay for the join procedure on window 2. - - params: - note: - - returns: - note: The join delay for window 2 as an integer. - """ return int(self.send_cmd("AT+JN2DL=?", True)) def set_join_delay_on_window2(self, delay: int): - """ - note: - en: Sets the join delay for window 2. - - params: - delay: - note: The join delay to set for window 2 as an integer. - - returns: - note: The result of the AT command execution. - """ - return self.send_cmd("AT+JN2DL=" + str(delay), True, True) + return self.send_cmd("AT+JN2DL=" + str(delay), True, True) def get_public_network_mode(self): - """ - note: - en: Retrieves the public network mode (enabled or disabled). - - params: - note: - - returns: - note: True if public network mode is enabled, False otherwise. - """ return self.send_cmd("AT+PNM=?", True) == "1" def set_public_network_mode(self, mode: bool): - """ - note: - en: Sets the public network mode to enabled or disabled. - - params: - mode: - note: A boolean indicating whether to enable (True) or disable (False) the public network mode. - - returns: - note: The result of the AT command execution. - """ return self.send_cmd(f"AT+PNM={int(mode)}", True, True) def get_rx_delay_on_window1(self): - """ - note: - en: Retrieves the receive window 1 delay. - - params: - note: - - returns: - note: The receive window 1 delay as an integer. - """ return int(self.send_cmd("AT+RX1DL=?", True)) def set_rx_delay_on_window1(self, delay: int): - """ - note: - en: Sets the receive delay for window 1. - - params: - delay: - note: The delay to set for receive window 1 as an integer. - - returns: - note: The result of the AT command execution. - """ return self.send_cmd("AT+RX1DL=" + str(delay), True, True) def get_rx_delay_on_window2(self): - """ - note: - en: Retrieves the receive window 2 delay. - - params: - note: - - returns: - note: The receive window 2 delay as an integer. - """ return int(self.send_cmd("AT+RX2DL=?", True)) def set_rx_delay_on_window2(self, delay: int): - """ - note: - en: Sets the receive delay for window 2. - - params: - delay: - note: The delay to set for receive window 2, with a range of 2 to 16. - - returns: - note: The result of the AT command execution. - """ return self.send_cmd("AT+RX2DL=" + str(delay), True, True) def get_rx_data_rate_on_windows2(self): - """ - note: - en: Retrieves the receive data rate for window 2. - - params: - note: - - returns: - note: The receive data rate for window 2 as an integer. - """ return int(self.send_cmd("AT+RX2DR=?", True)) def set_rx_data_rate_on_windows2(self, rate: int): - """ - note: - en: Sets the receive data rate for window 2. - - params: - rate: - note: The data rate to set for window 2. - - returns: - note: The result of the AT command execution. - """ return self.send_cmd(f"AT+RX2DR={rate}", True, True) def get_rx_frequency_on_windows2(self): - """ - note: - en: Retrieves the receive frequency for window 2, based on regional frequency settings. - - params: - note: - - returns: - note: The receive frequency for window 2 as an integer. - """ return int(self.send_cmd("AT+RX2FQ=?", True)) def get_tx_power(self): - """ - note: - en: Retrieves the current transmit power setting. - - params: - note: - - returns: - note: The transmit power setting as an integer. - """ return int(self.send_cmd("AT+TXP=?", True)) def set_tx_power(self, power: int): - """ - note: - en: Sets the transmit power. - - params: - power: - note: The transmit power to set. - - returns: - note: The result of the AT command execution. - """ return self.send_cmd("AT+TXP=" + str(power), True, True) def get_network_link_state(self): - """ - note: - en: Retrieves the network link state. - - params: - note: - - returns: - note: The network link state as a string. - """ return self.send_cmd("AT+LINKCHECK=?", True) def set_network_link_state(self, state: int): - """ - note: - en: Sets the network link state for the device. - - params: - state: - note: The state to set for network link: - 0 - Disable Link Check - 1 - Execute Link Check once on the next payload uplink - 2 - Automatically execute Link Check after every payload uplink - - returns: - note: The result of the AT command execution. - """ return self.send_cmd("AT+LINKCHECK=" + str(state), True, True) def get_listen_before_talk(self): - """ - note: - en: Retrieves the Listen Before Talk (LBT) state. - - params: - note: - - returns: - note: True if LBT is enabled, False if not. - """ return self.send_cmd("AT+LBT=?", True) == "1" def set_listen_before_talk(self, state: bool): - """ - note: - en: Sets the Listen Before Talk (LBT) state. - - params: - state: - note: The state to set for LBT (True for enabled, False for disabled). - - returns: - note: The result of the AT command execution. - """ return self.send_cmd(f"AT+LBT={int(state)}", True, True) def set_listen_before_talk_rssi(self, rssi: int): - """ - note: - en: Sets the RSSI threshold for Listen Before Talk (LBT). - - params: - rssi: - note: The RSSI threshold to set for LBT. - - returns: - note: The result of the AT command execution. - """ return self.send_cmd("AT+LBTRSSI=" + str(rssi), True, True) def get_listen_before_talk_rssi(self): - """ - note: - en: Retrieves the RSSI threshold for Listen Before Talk (LBT). - - params: - note: - - returns: - note: The RSSI threshold as an integer. - """ return int(self.send_cmd("AT+LBTRSSI=?", True)) def get_listen_before_talk_scan_time(self): - """ - note: - en: Retrieves the scan time for Listen Before Talk (LBT). - - params: - note: - - returns: - note: The scan time for LBT as an integer. - """ return int(self.send_cmd("AT+LBTSCANTIME=?", True)) def set_listen_before_talk_scan_time(self, time: int): - """ - note: - en: Sets the scan time for Listen Before Talk (LBT). - - params: - time: - note: The scan time to set for LBT. - - returns: - note: The result of the AT command execution. - """ return self.send_cmd("AT+LBTSCANTIME=" + str(time), True, True) def get_time_request(self): - """ - note: - en: Retrieves the time request state. - - params: - note: - - returns: - note: The time request state as an integer. - """ return int(self.send_cmd("AT+TIMEREQ=?", True)) def set_time_request(self, state: bool): - """ - note: - en: Sets the time request state. - - params: - state: - note: The state to set for time request (True to enable, False to disable). - - returns: - note: The result of the AT command execution. - """ return self.send_cmd(f"AT+TIMEREQ={int(state)}", True, True) def get_location_time(self): - """ - note: - en: Retrieves the location time in UTC+0. - - params: - note: - - returns: - note: The location time in UTC+0 as a string. - """ return self.send_cmd("AT+LTIME=?", True) def get_unicast_ping_interval(self): - """ - note: - en: Retrieves the unicast ping interval in Class B mode. - - params: - note: - - returns: - note: The unicast ping interval as an integer. - """ return int(self.send_cmd("AT+PGSLOT=?", True)) def set_unicast_ping_interval(self, interval: int): - """ - note: - en: Sets the unicast ping interval for the device. - - params: - interval: - note: The interval to set for the unicast ping (0 to 7). 0 means approximately every second during the beacon window, and 7 means every 128 seconds (maximum ping period). - - returns: - note: The result of the AT command execution. - """ return self.send_cmd("AT+PGSLOT=" + str(interval), True, True) def get_beacon_frequency(self): - """ - note: - en: Retrieves the beacon frequency. - - params: - note: - - returns: - note: The beacon frequency as an integer. - """ return int(self.send_cmd("AT+BFREQ=?", True)) def get_beacon_time(self): - """ - note: - en: Retrieves the beacon time. - - params: - note: - - returns: - note: The beacon time as an integer. - """ return int(self.send_cmd("AT+BTIME=?", True)) def get_beacon_gateway_gps(self): - """ - note: - en: Retrieves the beacon gateway GPS location. - - params: - note: - - returns: - note: The beacon gateway GPS information as a string. - """ return self.send_cmd("AT+BGW=?", True) def get_rssi(self): - """ - note: - en: Retrieves the Received Signal Strength Indicator (RSSI). - - params: - note: - - returns: - note: The RSSI value as a string. - """ return self.send_cmd("AT+RSSI=?", True) def get_all_rssi(self): - """ - note: - en: Retrieves the RSSI values from all channels. - - params: - note: - - returns: - note: The RSSI values for all channels as a string. - """ return self.send_cmd("AT+ARSSI=?", True) def get_signal_noise_ratio(self): - """ - note: - en: Retrieves the Signal-to-Noise Ratio (SNR). - - params: - note: - - returns: - note: The SNR value as a string. - """ return self.send_cmd("AT+SNR=?", True) def get_channel_mask(self): - """ - note: - en: Retrieves the channel mask (only for US915, AU915, CN470). - - params: - note: - - returns: - note: The channel mask as a string. - """ return self.send_cmd("AT+MASK=?", True) def set_channel_mask(self, mask: str): - """ - note: - en: Sets the channel mask to open or close specific channels (only for US915, AU915, CN470). - - params: - mask: - note: The channel mask as a 4-length hexadecimal string. - - returns: - note: The result of the AT command execution. - """ return self.send_cmd("AT+MASK=" + mask, True, True) def get_eight_channel_mode_state(self): - """ - note: - en: Retrieves the state of the eight-channel mode. - - params: - note: - - returns: - note: The state of the eight-channel mode as a string. - """ return self.send_cmd("AT+CHE=?", True) def set_eight_channel_mode_state(self, max_group: int) -> bool: - """ - note: - en: Sets the state of the eight-channel mode, with a group number between 1 and 12. - - params: - max_group: - note: The maximum group number (1 to 12). - - returns: - note: True if the AT command execution is successful, False otherwise. - """ if not (1 <= max_group <= 12): raise ValueError("Channel group must be between 1 and 12.") @@ -1410,87 +653,20 @@ def set_eight_channel_mode_state(self, max_group: int) -> bool: return self.send_cmd(f"AT+CHE={state_str}", True, True) def get_single_channel_mode(self): - """ - note: - en: Retrieves the single channel mode. - - params: - note: - - returns: - note: The single channel mode as a string. - """ return self.send_cmd("AT+CHS=?", True) - def set_single_channel_mode(self): - """ - note: - en: Sets the single channel mode. - - params: - note: - - returns: - note: The result of the AT command execution. - """ - return self.send_cmd("AT+CHS", True, True) + def set_single_channel_mode(self, freq: int): + return self.send_cmd("AT+CHS=" + str(freq), True, True) def get_active_region(self): - """ - note: - en: Retrieves the active region for the device. - - params: - note: - - returns: - note: The active region as a string. - """ return self.send_cmd("AT+BAND=?", True) def set_active_region(self, region: int): - """ - note: - en: Sets the active region for the device. - - params: - region: - note: The region to set for the device (0-12): - 0 = EU433, 1 = CN470, 2 = RU864, 3 = IN865, 4 = EU868, 5 = US915, - 6 = AU915, 7 = KR920, 8 = AS923-1, 9 = AS923-2, 10 = AS923-3, - 11 = AS923-4, 12 = LA915. - - returns: - note: The result of the AT command execution. - """ return self.send_cmd("AT+BAND=" + str(region), True, True) def add_multicast_group( self, Class: str, DevAddr, NwkSKey, AppSKey, Frequency, Datarate, Periodicit ): - """ - note: - en: Adds a multicast group to the device. - - params: - Class: - note: The class of the multicast group. - DevAddr: - note: The device address. - NwkSKey: - note: The network session key. - AppSKey: - note: The application session key. - Frequency: - note: The frequency for the multicast group. - Datarate: - note: The datarate for the multicast group. - Periodicit: - note: The periodicity for the multicast group. - - returns: - note: The result of the AT command execution. - """ return self.send_cmd( f"AT+ADDMULC={Class}:{DevAddr}:{NwkSKey}:{AppSKey}:{Frequency}:{Datarate}:{Periodicit}", True, @@ -1498,292 +674,346 @@ def add_multicast_group( ) def remove_multicast_group(self, DevAddr): - """ - note: - en: Removes a multicast group by the given device address. - - params: - DevAddr: - note: The device address of the multicast group to remove. - - returns: - note: The result of the AT command execution. - """ return self.send_cmd(f"AT+RMVMULC={DevAddr}", True, True) def get_multicast_list(self): - """ - note: - en: Retrieves the list of multicast groups. - - params: - note: - - returns: - note: The list of multicast groups as a string. - """ return self.send_cmd("AT+LSTMULC=?", True) def get_network_mode(self): - """ - note: - en: Retrieves the current network mode. - - params: - note: - - returns: - note: The current network mode as an integer: 0 = P2P_LORA, 1 = LoRaWAN, 2 = P2P_FSK. - """ return self.send_cmd("AT+NWM=?", True) def set_network_mode(self, mode: int): - """ - note: - en: Sets the network mode for the device. + """Set the network mode for the device. - params: - mode: - note: The mode to set for the network: 0 = P2P_LORA, 1 = LoRaWAN, 2 = P2P_FSK. + :returns: The result of the AT command execution. + :rtype: bool - returns: - note: The result of the AT command execution. + :param int mode: The mode to set for the network: + + - 0 = P2P_LORA + - 1 = LoRaWAN + - 2 = P2P_FSK + + MicroPython Code Block: + + .. code-block:: python + + lorawan_rui3.set_network_mode(0) # Set to P2P_LORA mode """ return self.send_cmd("AT+NWM=" + str(mode), True, False, True, 500) def get_p2p_frequency(self): - """ - note: - en: Retrieves the current P2P frequency. + """Retrieve the current P2P frequency. - params: - note: + :returns: The current P2P frequency as an integer. + :rtype: int - returns: - note: The current P2P frequency as an integer. + MicroPython Code Block: + + .. code-block:: python + + frequency = lorawan_rui3.get_p2p_frequency() + print(f"Current P2P frequency: {frequency} Hz") """ return int(self.send_cmd("AT+PFREQ=?", True)) def set_p2p_frequency(self, frequency: int): - """ - note: - en: Sets the P2P frequency for the device. + """Set the P2P frequency for the device. + + :returns: The result of the AT command execution. + :rtype: bool + + :param int frequency: The frequency to set for P2P communication. + + - Low-frequency range: 150000000-600000000 + - High-frequency range: 600000000-960000000 - params: - frequency: - note: The frequency to set for P2P communication. Low-frequency range: 150000000-525000000, High-frequency range: 525000000-960000000. + MicroPython Code Block: - returns: - note: The result of the AT command execution. + .. code-block:: python + + success = lorawan_rui3.set_p2p_frequency(433000000) + if success: + print("P2P frequency set successfully!") + else: + print("Failed to set P2P frequency.") """ return self.send_cmd("AT+PFREQ=" + str(frequency), True, True) def get_p2p_spreading_factor(self): - """ - note: - en: Retrieves the current P2P spreading factor. + """Retrieve the current P2P spreading factor. + + :returns: The current P2P spreading factor as an integer. + :rtype: int - params: - note: + MicroPython Code Block: - returns: - note: The current P2P spreading factor as an integer. + .. code-block:: python + + sf = lorawan_rui3.get_p2p_spreading_factor() + print(f"Current P2P spreading factor: {sf}") """ return int(self.send_cmd("AT+PSF=?", True)) def set_p2p_spreading_factor(self, spreading_factor: int): - """ - note: - en: Sets the P2P spreading factor. + """Set the P2P spreading factor. - params: - spreading_factor: - note: The spreading factor to set for P2P communication. The default value is 7, and the range is 5 to 12. + :param int spreading_factor: The spreading factor to set for P2P communication. - returns: - note: The result of the AT command execution. + - Range is 5 to 12. + + :returns: The result of the AT command execution. + :rtype: bool + + MicroPython Code Block: + + .. code-block:: python + + success = lorawan_rui3.set_p2p_spreading_factor(10) + if success: + print("P2P spreading factor set successfully!") + else: + print("Failed to set P2P spreading factor.") """ return self.send_cmd("AT+PSF=" + str(spreading_factor), True, True) def get_p2p_bandwidth(self): - """ - note: - en: Retrieves the current P2P bandwidth. + """Retrieve the current P2P bandwidth. + + :returns: The current P2P bandwidth as an integer. + :rtype: int - params: - note: + MicroPython Code Block: + + .. code-block:: python + + bw = lorawan_rui3.get_p2p_bandwidth() + print(f"Current P2P bandwidth: {bw}") - returns: - note: The current P2P bandwidth as an integer. """ return int(self.send_cmd("AT+PBW=?", True)) def set_p2p_bandwidth(self, bandwidth: int): - """ - note: - en: Sets the P2P bandwidth. + """Set the P2P bandwidth. + + :param int bandwidth: The bandwidth to set for P2P communication. + + - For LoRa: + - 0 = 125 kHz + - 1 = 250 kHz + - 2 = 500 kHz + - 3 = 7.8 kHz + - 4 = 10.4 kHz + - 5 = 15.63 kHz + - 6 = 20.83 kHz + - 7 = 31.25 kHz + - 8 = 41.67 kHz + - 9 = 62.5 kHz + + - For FSK: + Range: 4800-467000 Hz - params: - bandwidth: - note: The bandwidth to set for P2P communication. Default is 0. - For LoRa: 0 = 125, 1 = 250, 2 = 500, 3 = 7.8, 4 = 10.4, 5 = 15.63, 6 = 20.83, 7 = 31.25, 8 = 41.67, 9 = 62.5. - For FSK: 4800-467000. + :returns: The result of the AT command execution. + :rtype: bool + |set_p2p_fsk_bandwidth.png| - returns: - note: The result of the AT command execution. + |set_p2p_lora_bandwidth.png| + + MicroPython Code Block: + + .. code-block:: python + + success = lorawan_rui3.set_p2p_bandwidth(1) # Set to 250 kHz + if success: + print("P2P bandwidth set successfully!") + else: + print("Failed to set P2P bandwidth.") """ return self.send_cmd("AT+PBW=" + str(bandwidth), True, True) def get_p2p_code_rate(self): - """ - note: - en: Retrieves the current P2P code rate. + """Retrieve the current P2P code rate. + + :returns: The current P2P code rate as an integer. + :rtype: int + + MicroPython Code Block: - params: - note: + .. code-block:: python - returns: - note: The current P2P code rate as an integer. + code_rate = lorawan_rui3.get_p2p_code_rate() + print(f"Current P2P code rate: {code_rate}") """ return int(self.send_cmd("AT+PCR=?", True)) def set_p2p_code_rate(self, code_rate: int): - """ - note: - en: Sets the P2P code rate. + """Set the P2P code rate. + + :param int code_rate: The code rate to set for P2P communication. + + - 0 = 4/5 + - 1 = 4/6 + - 2 = 4/7 + - 3 = 4/8 + + :returns: The result of the AT command execution. + :rtype: bool + + MicroPython Code Block: - params: - code_rate: - note: The code rate to set for P2P communication. Default is 0, range is 0 to 3, 0 = 4/5, 1 = 4/6, 2 = 4/7, 3 = 4/8. + .. code-block:: python - returns: - note: The result of the AT command execution. + success = lorawan_rui3.set_p2p_code_rate(1) # Set to 4/6 + if success: + print("P2P code rate set successfully!") + else: + print("Failed to set P2P code rate.") """ return self.send_cmd("AT+PCR=" + str(code_rate), True, True) def get_p2p_preamble_length(self): - """ - note: - en: Retrieves the current P2P preamble length. + """Retrieve the current P2P preamble length. + + :returns: The current P2P preamble length as an integer. + :rtype: int + + MicroPython Code Block: - params: - note: + .. code-block:: python - returns: - note: The current P2P preamble length as an integer. + preamble_length = lorawan_rui3.get_p2p_preamble_length() + print(f"Current P2P preamble length: {preamble_length}") """ return int(self.send_cmd("AT+PPL=?", True)) def set_p2p_preamble_length(self, length: int): - """ - note: - en: Sets the P2P preamble length. + """Set the P2P preamble length. + + :returns: The result of the AT command execution. + :rtype: bool + + :param int length: The preamble length to set for P2P communication. - params: - length: - note: The preamble length to set for P2P communication. Default is 8, range is 5 to 65535. + - Range is 5 to 65535. - returns: - note: The result of the AT command execution. + MicroPython Code Block: + + .. code-block:: python + + success = lorawan_rui3.set_p2p_preamble_length(16) + if success: + print("P2P preamble length set successfully!") + else: + print("Failed to set P2P preamble length.") """ return self.send_cmd("AT+PPL=" + str(length), True, True) def get_p2p_tx_power(self): - """ - note: - en: Retrieves the current P2P transmission power. + """Retrieve the current P2P transmission power. - params: - note: + :returns: The current P2P transmission power as an integer. + :rtype: int - returns: - note: The current P2P transmission power as an integer. + MicroPython Code Block: + + .. code-block:: python + + tx_power = lorawan_rui3.get_p2p_tx_power() + print(f"Current P2P transmission power: {tx_power} dBm") """ return int(self.send_cmd("AT+PTP=?", True)) def set_p2p_tx_power(self, power: int): - """ - note: - en: Sets the P2P transmission power. + """Set the P2P transmission power. - params: - power: - note: The transmission power to set for P2P communication. Default is 14 dBm, range is 5 to 22 dBm. + :param int power: The transmission power to set for P2P communication. - returns: - note: The result of the AT command execution. + - Range is 5 to 22 dBm. + + :returns: The result of the AT command execution. + :rtype: bool + + MicroPython Code Block: + + .. code-block:: python + + success = lorawan_rui3.set_p2p_tx_power(20) # Set to 20 dBm + if success: + print("P2P transmission power set successfully!") + else: + print("Failed to set P2P transmission power.") """ return self.send_cmd("AT+PTP=" + str(power), True, True) def get_p2p_fsk_bitrate(self): - """ - note: - en: Retrieves the current P2P FSK bitrate. + """Retrieve the current P2P FSK bitrate. + + :returns: The result of the AT command execution. + :rtype: bool - params: - note: + MicroPython Code Block: - returns: - note: The current P2P FSK bitrate as an integer. + .. code-block:: python + + fsk_bitrate = lorawan_rui3.get_p2p_fsk_bitrate() + print(f"Current P2P FSK bitrate: {fsk_bitrate} b/s") """ return int(self.send_cmd("AT+PBR=?", True)) def set_p2p_fsk_bitrate(self, bitrate: int): - """ - note: - en: Sets the P2P FSK bitrate. + """Set the P2P FSK bitrate. - params: - bitrate: - note: The bitrate to set for P2P FSK communication. The range is 600 to 300000 b/s. + :param int bitrate: The bitrate to set for P2P FSK communication. - returns: - note: The result of the AT command execution. - """ - return self.send_cmd("AT+PBR=" + str(bitrate), True, True) + - Range is 600 to 300000 b/s. - def get_p2p_fsk_frequency_deviation(self): - """ - note: - en: Retrieves the current P2P FSK frequency deviation. + :returns: The result of the AT command execution. + :rtype: bool + + MicroPython Code Block: + + .. code-block:: python - params: - note: + success = lorawan_rui3.set_p2p_fsk_bitrate(9600) # Set to 9600 b/s + if success: + print("P2P FSK bitrate set successfully!") + else: + print("Failed to set P2P FSK bitrate.") - returns: - note: The current P2P FSK frequency deviation as an integer. """ + return self.send_cmd("AT+PBR=" + str(bitrate), True, True) + + def get_p2p_fsk_frequency_deviation(self): return int(self.send_cmd("AT+PFDEV=?", True)) def set_p2p_fsk_frequency_deviation(self, deviation: int): - """ - note: - en: Sets the P2P FSK frequency deviation. + return self.send_cmd("AT+PFDEV=" + str(deviation), True, True) - params: - deviation: - note: The frequency deviation to set for P2P FSK communication. The range is 600 to 200000 Hz. + def send_p2p_data(self, payload: str, timeout: int = 1000, to_hex: bool = False): + """Send P2P data with a given payload. - returns: - note: The result of the AT command execution. - """ - return self.send_cmd("AT+PFDEV=" + str(deviation), True, True) + :param str payload: The payload to send. + + - Length must be between 2 and 500 characters. + - Must consist of an even number of characters composed of 0-9, a-f, A-F, representing 1 to 256 hexadecimal values. + :param int timeout: The timeout for the data transmission, in milliseconds. Default is 1000 ms. + :param bool to_hex: Indicates whether to convert the payload to hexadecimal format. Default is False. + + :returns: True if the data was sent successfully ("TXFSK DONE" or "TXP2P DONE"), False otherwise. + :rtype: bool + + |send_p2p_data_return.png| + + MicroPython Code Block: + + .. code-block:: python + + success = lorawan_rui3.send_p2p_data("abcdef", timeout=2000, to_hex=True) + if success: + print("P2P data sent successfully!") + else: + print("Failed to send P2P data.") - def send_p2p_data(self, payload: str, timeout=1000, to_hex=False): - """ - note: - en: Sends P2P data with a given payload. - - params: - payload: - note: The payload to send, 2 to 500 characters in length, must be an even number of characters composed of 0-9, a-f, A-F, representing 1 to 256 hexadecimal values. - timeout: - note: The timeout for the data transmission, default is 1000 ms. - to_hex: - note: A boolean indicating whether to convert the payload to hexadecimal format. - - returns: - note: True if the data was sent successfully ("TXFSK DONE" or "TXP2P DONE"), False otherwise. """ if to_hex: payload = payload.encode("utf-8").hex() @@ -1795,46 +1025,35 @@ def send_p2p_data(self, payload: str, timeout=1000, to_hex=False): ] def get_p2p_channel_activity(self): - """ - note: - en: Get the current channel activity status in P2P mode. - - params: - note: - - returns: - note: The channel activity status as an integer (0 or 1). - """ return int(self.send_cmd("AT+CAD=?", True)) def set_p2p_channel_activity(self, state: bool): - """ - note: - en: Enable or disable channel activity in P2P mode. + return self.send_cmd(f"AT+CAD={int(state)}", True, True) - params: - state: - note: 0 to disable, 1 to enable channel activity. + def get_p2p_receive_data(self, timeout: int = 500, to_str: bool = False): + """Receive data in P2P mode, including RSSI, SNR, and payload. - returns: - note: The response from the command execution. - """ - return self.send_cmd(f"AT+CAD={int(state)}", True, True) + :param int timeout: Timeout for listening to P2P LoRa data packets, in milliseconds. + + - Valid values are 1 to 65535. + - 0: Continuous listening. + - 65535: No timeout. + + :param bool to_str: Indicates whether to convert the payload to a string. Default is False. + :returns: A tuple (RSSI, SNR, Payload) if data is received; False if no data is received. + :rtype: tuple[int, int, str] | bool + + MicroPython Code Block: + + .. code-block:: python + + result = lorawan_rui3.get_p2p_receive_data(timeout=1000, to_str=True) + if result: + rssi, snr, payload = result + print(f"Received data - RSSI: {rssi}, SNR: {snr}, Payload: {payload}") + else: + print("No data received.") - def get_p2p_receive_data(self, timeout=500, to_str=False): - """ - note: - en: Receive data in P2P mode, including RSSI, SNR, and payload. - - params: - timeout: - note: Timeout for listening to P2P LoRa data packets. - Valid values are 1~65535, with special cases for continuous listening and no timeout. - to_str: - note: A boolean indicating whether to convert the payload to a string. - - returns: - note: A tuple (RSSI, SNR, Payload) if data is received; False if no data is received. """ self.send_cmd("AT+PRECV=0", True, False, True, 0) buf = self.send_cmd(f"AT+PRECV={timeout}", True, False, True, timeout) @@ -1849,156 +1068,41 @@ def get_p2p_receive_data(self, timeout=500, to_str=False): return False def get_p2p_encryption_state(self): - """ - note: - en: Get the current encryption state in P2P mode. - - params: - note: - - returns: - note: The encryption state as an integer (0 or 1). - """ return int(self.send_cmd("AT+ENCRY=?", True)) def set_p2p_encryption_state(self, state: bool): - """ - note: - en: Enable or disable encryption in P2P mode. - - params: - state: - note: 0 to disable, 1 to enable encryption. - - returns: - note: The response from the command execution. - """ return self.send_cmd(f"AT+ENCRY={int(state)}", True, True) def get_p2p_encryption_key(self): - """ - note: - en: Get the current encryption key in P2P mode. - - params: - note: - - returns: - note: The encryption key as a string. - """ return self.send_cmd("AT+ENCKEY=?", True) def set_p2p_encryption_key(self, key: str): - """ - note: - en: Set the encryption key in P2P mode. - - params: - key: - note: The encryption key, represented as a 16-character hexadecimal string. - - returns: - note: The response from the command execution. - """ return self.send_cmd(f"AT+ENCKEY={key}", True, True) def get_p2p_crypt_state(self): - """ - note: - en: Get the current cryptographic state in P2P mode (not supported on RAK3172). - - params: - note: - - returns: - note: The cryptographic state as an integer (0 or 1). - """ # RAK3172 not support return int(self.send_cmd("AT+PCRYPT=?", True)) def set_p2p_crypt_state(self, state: bool): - """ - note: - en: Enable or disable cryptographic state in P2P mode (not supported on RAK3172). - - params: - state: - note: 0 to disable, 1 to enable cryptographic state. - - returns: - note: The response from the command execution. - """ # RAK3172 not support return self.send_cmd(f"AT+PCRYPT={int(state)}", True, True) def get_p2p_crypt_key(self): - """ - note: - en: Get the cryptographic key in P2P mode (not supported on RAK3172). - - params: - note: - - returns: - note: The cryptographic key as a string. - """ # RAK3172 not support return self.send_cmd("AT+PKEY=?", True) def set_p2p_crypt_key(self, key): - """ - note: - en: Set the cryptographic key in P2P mode (not supported on RAK3172). - - params: - key: - note: The cryptographic key, represented as an 8-character hexadecimal string. - - returns: - note: The response from the command execution. - """ # RAK3172 not support # key:8 hex string return self.send_cmd(f"AT+PKEY={key}", True, True) def get_p2p_encryption_iv(self): - """ - note: - en: Get the encryption IV in P2P mode. - - params: - note: - - returns: - note: The encryption IV as a string. - """ return self.send_cmd("AT+CRYPIV=?", True) def set_p2p_encryption_iv(self, iv: str): - """ - note: - en: Set the encryption IV in P2P mode. - - params: - iv: - note: The encryption IV, represented as an 8-character hexadecimal string. - - returns: - note: The response from the command execution. - """ return self.send_cmd(f"AT+CRYPIV={iv}", True, True) def get_p2p_parameters(self): - """ - note: - en: Get the current P2P parameters. - - params: - note: - - returns: - note: The current P2P parameters as a string. - """ return self.send_cmd("AT+P2P=?", True) def set_p2p_parameters( @@ -2010,27 +1114,6 @@ def set_p2p_parameters( preamble_length: int = 8, tx_power: int = 14, ): - """ - note: - en: Set P2P LoRa parameters, including frequency, spreading factor, bandwidth, code rate, preamble length, and transmit power. - - params: - frequency: - note: The frequency to use for communication, range 150000000-960000000. - spreading_factor: - note: The spreading factor, which can be {6, 7, 8, 9, 10, 11, 12}. - bandwidth: - note: The bandwidth, which can be {125, 250, 500}. - code_rate: - note: The code rate, where possible values are {4/5=0, 4/6=1, 4/7=2, 4/8=3}. - preamble_length: - note: The length of the preamble, which can be from 2 to 65535. - tx_power: - note: The transmit power, which can be in the range {5-22}. - - returns: - note: The response from the command execution. - """ return self.send_cmd( f"AT+P2P={frequency}:{spreading_factor}:{bandwidth}:{code_rate}:{preamble_length}:{tx_power}", True, @@ -2038,187 +1121,59 @@ def set_p2p_parameters( ) def get_p2p_iq_inversion(self): - """ - note: - en: Get the current IQ inversion state in P2P mode. - - params: - note: - - returns: - note: The IQ inversion state as an integer (0 or 1). - """ return int(self.send_cmd("AT+IQINVER=?", True)) def set_p2p_iq_inversion(self, state: bool): - """ - note: - en: Enable or disable IQ inversion in P2P mode. - - params: - state: - note: 0 to disable, 1 to enable IQ inversion. - - returns: - note: The response from the command execution. - """ return self.send_cmd(f"AT+IQINVER={int(state)}", True, True) def get_p2p_sync_word(self): - """ - note: - en: Get the current sync word in P2P mode. + """Get the current sync word in P2P mode. - params: - note: + :returns: The sync word as a string. + :rtype: str + + MicroPython Code Block: + + .. code-block:: python + + sync_word = lorawan_rui3.get_p2p_sync_word() + print(f"Current P2P sync word: {sync_word}") - returns: - note: The sync word as a string. """ return self.send_cmd("AT+SYNCWORD=?", True) def set_p2p_sync_word(self, sync_word: int): - """ - note: - en: Set the sync word in P2P mode. + """Set the sync word in P2P mode. + + :param int sync_word: The sync word value. - params: - sync_word: - note: The sync word value, which should be in the range of 0x0000 to 0xFFFF. + - Must be in the range of 0x0000 to 0xFFFF. + + :returns: The response from the command execution. + :rtype: bool + + MicroPython Code Block: + + .. code-block:: python + + success = lorawan_rui3.set_p2p_sync_word(0x1234) + if success: + print("P2P sync word set successfully!") + else: + print("Failed to set P2P sync word.") - returns: - note: The response from the command execution. """ sync_word_str = f"{sync_word:04X}" return self.send_cmd(f"AT+SYNCWORD={sync_word_str}", True, True) def get_p2p_symbol_timeout(self): - """ - note: - en: Get the current symbol timeout in P2P mode. - - params: - note: - - returns: - note: The symbol timeout as an integer. - """ return int(self.send_cmd("AT+SYMBOLTIMEOUT=?", True)) def set_p2p_symbol_timeout(self, timeout): - """ - note: - en: Set the symbol timeout in P2P mode. - - params: - timeout: - note: The timeout value, which should be in the range of 0-248. - - returns: - note: The response from the command execution. - """ return self.send_cmd(f"AT+SYMBOLTIMEOUT={int(timeout)}", True, True) def get_p2p_fix_length_payload_state(self): - """ - note: - en: Get the current fixed length payload state in P2P mode. - - params: - note: - - returns: - note: The fixed length payload state as an integer (0 or 1). - """ return int(self.send_cmd("AT+FIXLENGTHPAYLOAD=?", True)) def set_p2p_fix_length_payload_state(self, state: bool): - """ - note: - en: Enable or disable fixed length payload in P2P mode. - - params: - state: - note: 0 to disable, 1 to enable fixed length payload. - - returns: - note: The response from the command execution. - """ return self.send_cmd(f"AT+FIXLENGTHPAYLOAD={int(state)}", True, True) - - -# SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD -# -# SPDX-License-Identifier: MIT - -import sys - -if sys.platform != "esp32": - from typing import Literal - - -class LoRaWANUnit_RUI3(RUI3): - def __init__(self, id: Literal[0, 1, 2] = 1, port: list | tuple = None, debug=False): - super().__init__(id, port[1], port[0], debug) - - def set_abp_config(self, dev_addr: str, apps_key: str, nwks_key: str): - """ - note: - en: Configure the device for ABP (Activation By Personalization) mode using the provided device address, application session key, and network session key. - - params: - dev_addr: - note: The device address for ABP configuration. - apps_key: - note: The application session key for encryption. - nwks_key: - note: The network session key for communication. - """ - self.set_join_mode(0) - self.set_device_address(dev_addr) - self.set_apps_key(apps_key) - self.set_networks_key(nwks_key) - - def get_abp_config(self) -> tuple[str | bool | None, str | bool | None, str | bool | None]: - """ - note: - en: Retrieve the current ABP configuration, including the device address, application session key, and network session key. - - params: - note: - - returns: - note: A tuple containing the device address, application session key, and network session key. Returns None or False for missing or invalid configurations. - """ - return (self.get_device_address(), self.get_apps_key(), self.get_networks_key()) - - def set_otaa_config(self, device_eui, app_eui, app_key): - """ - note: - en: Configure the device for OTAA (Over-The-Air Activation) mode using the provided device EUI, application EUI, and application key. - - params: - device_eui: - note: The device EUI for OTAA configuration. - app_eui: - note: The application EUI for OTAA configuration. - app_key: - note: The application key for encryption. - """ - self.set_join_mode(1) - self.set_device_eui(device_eui) - self.set_app_eui(app_eui) - self.set_app_key(app_key) - - def get_otaa_config(self): - """ - note: - en: Retrieve the current OTAA configuration, including the device EUI, application key, and application EUI. - - params: - note: - - returns: - note: A tuple containing the device EUI, application key, and application EUI. - """ - return (self.get_device_eui(), self.get_app_key(), self.get_app_eui()) From b919ac85b926754a6ffad6dde434b53f72181f8b Mon Sep 17 00:00:00 2001 From: tinyu Date: Sun, 27 Apr 2025 19:19:52 +0800 Subject: [PATCH 062/322] lib/unit: Fix Unit ASR misrecognition of line breaks. Signed-off-by: tinyu --- m5stack/libs/unit/asr.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/m5stack/libs/unit/asr.py b/m5stack/libs/unit/asr.py index 027b90f3..ace7e6df 100644 --- a/m5stack/libs/unit/asr.py +++ b/m5stack/libs/unit/asr.py @@ -90,7 +90,7 @@ def _debug_print(self, *args, **kwargs): print(*args, **kwargs) def _handler(self, uart) -> None: - data = uart.readline() + data = uart.read() if data is not None and len(data) >= 5: self._debug_print(("Received data: ", data)) From f24af7a5330effadd9cc289b71925b651208b74d Mon Sep 17 00:00:00 2001 From: luoweiyuan Date: Tue, 29 Apr 2025 09:07:30 +0800 Subject: [PATCH 063/322] docs: Add esp-dl example brief. FIXME: Chinese translation is missing.(lbuque@163.com) Signed-off-by: luoweiyuan --- docs/en/advanced/dl.rst | 225 +++++++++++++++++++++++++--------------- docs/requirements.txt | 3 +- 2 files changed, 143 insertions(+), 85 deletions(-) diff --git a/docs/en/advanced/dl.rst b/docs/en/advanced/dl.rst index 96d3759c..1cce28a3 100644 --- a/docs/en/advanced/dl.rst +++ b/docs/en/advanced/dl.rst @@ -1,5 +1,5 @@ -dl --- deep learning -===================================== +dl --- deep learning +==================== .. include:: ../refs/advanced.dl.ref @@ -8,77 +8,131 @@ dl --- deep learning .. module:: dl :synopsis: deep learning -Micropython Example --------------------------------- + +UiFlow2 Example +--------------- human face detect -++++++++++++++++++++++++++++ ++++++++++++++++++ + +Open the |cores3_example_human_face_detect.m5f2| project in UiFlow2. + +This example uses a face detection algorithm to detect faces in real time from +the camera feed. When a face is detected, a bounding box is drawn on the screen +to mark the face's position, providing an intuitive visualization of the +detection results. + +UiFlow2 Code Block: + + |human_face_detect_example.png| + +Example output: + + None - .. literalinclude:: ../../../examples/advanced/dl/cores3_example_human_face_detect.py - :language: python - :linenos: - pedestrain detect -++++++++++++++++++++++++++++ ++++++++++++++++++ - .. literalinclude:: ../../../examples/advanced/dl/cores3_example_pedestrian_detect.py - :language: python - :linenos: - -human face recognition -++++++++++++++++++++++++++++ +Open the |cores3_example_pedestrian_detect.m5f2| project in UiFlow2. - .. literalinclude:: ../../../examples/advanced/dl/cores3_example_human_face_recognition.py - :language: python - :linenos: - +This example uses a pedestrian detection algorithm to detect pedestrian targets +in real time from the camera feed. When a pedestrian is detected, a bounding box +is drawn on the screen to highlight the pedestrian's position, providing an +intuitive demonstration of the detection results. + +UiFlow2 Code Block: + + |pedestrian_detect_example.png| + +Example output: + + None + +human face recognition +++++++++++++++++++++++ + +Open the |cores3_example_human_face_recognition.m5f2| project in UiFlow2. + +To run this example, you will need the `CoreS3 `_ +and the `Unit Dual Button `_. + +This example uses a face recognition algorithm to detect faces in real time from +the camera feed. + +By pressing different buttons, you can either enroll new face data or perform +face recognition. The detected faces and recognition results are displayed on +the screen with bounding boxes. + +UiFlow2 Code Block: + + |human_face_recognition_example.png| + +Example output: -UIFlow2.0 Example --------------------------------- + None + + +Micropython Example +------------------- human face detect -++++++++++++++++++++++++++++ ++++++++++++++++++ - |human_face_detect_example.png| -.. only:: builder_html +MicroPython Code Block: + + .. literalinclude:: ../../../examples/advanced/dl/cores3_example_human_face_detect.py + :language: python + :linenos: + +Example output: - |cores3_example_human_face_detect.m5f2| + None pedestrain detect -++++++++++++++++++++++++++++ ++++++++++++++++++ - |pedestrian_detect_example.png| +MicroPython Code Block: -.. only:: builder_html + .. literalinclude:: ../../../examples/advanced/dl/cores3_example_pedestrian_detect.py + :language: python + :linenos: - |cores3_example_pedestrian_detect.m5f2| +Example output: -human face recognition -++++++++++++++++++++++++++++ + None - |human_face_recognition_example.png| -.. only:: builder_html +human face recognition +++++++++++++++++++++++ + +MicroPython Code Block: + + .. literalinclude:: ../../../examples/advanced/dl/cores3_example_human_face_recognition.py + :language: python + :linenos: + +Example output: + + None - |cores3_example_human_face_recognition.m5f2| Funtions ------------------------------- +-------- .. function:: dl.ObjectDetector(model) -> ObjectDetector Create an object detector instance. :param model: Load a detection model. Supported values: - + - ``dl.model.HUMAN_FACE_DETECT``: Human face detection. - ``dl.model.PEDESTRIAN_DETECT``: Pedestrian detection. Returns An ``ObjectDetector`` instance. - UIFlow2.0 + UiFlow2 Code Block: |ObjectDetector.png| @@ -93,24 +147,28 @@ Funtions Returns: A ``HumanFaceRecognizer`` instance. - UIFlow2.0 + UiFlow2 Code Block: |HumanFaceRecognizer.png| -class ObjectDetector ------------------------------- + +class ObjectDetector +-------------------- + The ObjectDetector object is returned by `dl.ObjectDetector(model)`. .. method:: ObjectDetector.infer(img: image.Image) -> DetectionResult Returns: A ``DetectionResult`` instance. - UIFlow2.0 + UiFlow2 Code Block: |infer.png| -class HumanFaceRecognizer ------------------------------- + +class HumanFaceRecognizer +------------------------- + The HumanFaceRecognizer object is returned by `dl.HumanFaceRecognizer()`. .. method:: HumanFaceRecognizer.recognize(img: image:Image, keypoint: tuple) -> RecognitionResult @@ -122,27 +180,26 @@ The HumanFaceRecognizer object is returned by `dl.HumanFaceRecognizer()`. Returns an ``RecognitionResult`` object - UIFlow2.0 + UiFlow2 Code Block: |recognize.png| -.. method:: HumanFaceRecognizer.clear_id() +.. method:: HumanFaceRecognizer.clear_id() - clear id + clear id - UIFlow2.0 + UiFlow2 Code Block: |clear_id.png| - .. method:: HumanFaceRecognizer.enroll_id(img: image:Image, keypoint: tuple) -> bool - enroll id + enroll id - ``img`` imput image - ``keypoint`` face keypoint, ref: DetectionResult.keypoint() - UIFlow2.0 + UiFlow2 Code Block: |enroll_id.png| @@ -152,7 +209,7 @@ The HumanFaceRecognizer object is returned by `dl.HumanFaceRecognizer()`. id is an optional parameter. If provided, it deletes the specified face information. By default, it deletes the most recently recorded id. - UIFlow2.0 + UiFlow2 Code Block: |delete_id.png| @@ -162,62 +219,63 @@ The HumanFaceRecognizer object is returned by `dl.HumanFaceRecognizer()`. get enrolled id num - UIFlow2.0 + UiFlow2 Code Block: + + |enrolled_id_num.png| - |enrolled_id_num.png| class DetectionResult -- DetectionResult object ----------------------------------------------- The line object is returned by `ObjectDetector.infer()`. -.. method:: DetectionResult.bbox() -> tuple(x, y, w, h) - - Get the bounding box of the object detection. +.. method:: DetectionResult.bbox() -> tuple(x, y, w, h) + + Get the bounding box of the object detection. - UIFlow2.0 + UiFlow2 Code Block: - |get_bbox.png| + |get_bbox.png| -.. method:: DetectionResult.x() -> int +.. method:: DetectionResult.x() -> int The x-coordinate of the top-left corner of the bounding box. - UIFlow2.0 + UiFlow2 Code Block: - |get_x.png| + |get_x.png| -.. method:: DetectionResult.y() -> int +.. method:: DetectionResult.y() -> int The y-coordinate of the top-left corner of the bounding box. - UIFlow2.0 + UiFlow2 Code Block: - |get_y.png| + |get_y.png| -.. method:: DetectionResult.w() -> int +.. method:: DetectionResult.w() -> int The width of the bounding box. - UIFlow2.0 + UiFlow2 Code Block: - |get_w.png| + |get_w.png| -.. method:: DetectionResult.h() -> int +.. method:: DetectionResult.h() -> int The height of the bounding box. - UIFlow2.0 + UiFlow2 Code Block: - |get_h.png| + |get_h.png| -.. method:: DetectionResult.category() -> int +.. method:: DetectionResult.category() -> int The detected object's category. - UIFlow2.0 + UiFlow2 Code Block: - |get_category.png| + |get_category.png| .. method:: DetectionResult.keypoint() -> tuple @@ -229,29 +287,28 @@ The line object is returned by `ObjectDetector.infer()`. - ``keypoint()[6], keypoint()[7]`` are the coordinates of the right eye. - ``keypoint()[8], keypoint()[9]`` are the coordinates of the right corner of the mouth. - UIFlow2.0 + UiFlow2 Code Block: + + |get_keypoint.png| - |get_keypoint.png| class RecognitionResult -- RecognitionResult object ---------------------------------------------------- +--------------------------------------------------- The ``RecognitionResult`` is returned by `HumanFaceRecognizer.recognize(img, keypoint)`. .. method:: RecognitionResult.similarity() -> float - + Gets the face similarity, with a value closer to 1 indicating higher similarity. - UIFlow2.0 + UiFlow2 Code Block: - |similarity.png| + |similarity.png| .. method:: RecognitionResult.id() -> int Gets the face ID. A value greater than 0 indicates that the face recognition was successful. - UIFlow2.0 - - |id.png| + UiFlow2 Code Block: - + |id.png| diff --git a/docs/requirements.txt b/docs/requirements.txt index 9e3191a8..beb51036 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -5,4 +5,5 @@ recommonmark==0.7.1 sphinx_markdown_tables==0.0.17 nbsphinx==0.9.5 sphinx-copybutton==0.5.2 -sphinx-intl==2.2.0 \ No newline at end of file +sphinx-intl==2.2.0 +sphinxcontrib-plantuml From 86d53b89b3e66d634207b3739383c52569767576 Mon Sep 17 00:00:00 2001 From: lbuque Date: Tue, 29 Apr 2025 12:18:33 +0800 Subject: [PATCH 064/322] libs/module: Add Audio Module support. Signed-off-by: lbuque --- m5stack/boards/M5STACK_CoreS3/mpconfigboard.h | 2 +- m5stack/cmodules/cmodules.cmake | 2 + m5stack/cmodules/m5audio2/audio2_player.c | 971 ++++++++++++++++++ m5stack/cmodules/m5audio2/audio2_recorder.c | 420 ++++++++ m5stack/cmodules/m5audio2/format_wav.h | 74 ++ m5stack/cmodules/m5audio2/i2s_helper.c | 86 ++ m5stack/cmodules/m5audio2/i2s_helper.h | 25 + m5stack/cmodules/m5audio2/m5audio2.cmake | 25 + m5stack/cmodules/m5audio2/modaudio2.c | 28 + m5stack/cmodules/m5audio2/vfs_stream.c | 245 +++++ m5stack/cmodules/m5audio2/vfs_stream.h | 32 + m5stack/components/M5Unified/M5GFX | 2 +- m5stack/components/M5Unified/M5Unified | 2 +- m5stack/libs/driver/es8388/__init__.py | 415 ++++++++ m5stack/libs/driver/es8388/reg.py | 63 ++ m5stack/libs/driver/manifest.py | 2 + m5stack/libs/module/__init__.py | 1 + m5stack/libs/module/audio.py | 234 +++++ m5stack/libs/module/manifest.py | 1 + m5stack/libs/module/mbus.py | 18 +- 20 files changed, 2641 insertions(+), 7 deletions(-) create mode 100644 m5stack/cmodules/m5audio2/audio2_player.c create mode 100644 m5stack/cmodules/m5audio2/audio2_recorder.c create mode 100644 m5stack/cmodules/m5audio2/format_wav.h create mode 100644 m5stack/cmodules/m5audio2/i2s_helper.c create mode 100644 m5stack/cmodules/m5audio2/i2s_helper.h create mode 100644 m5stack/cmodules/m5audio2/m5audio2.cmake create mode 100644 m5stack/cmodules/m5audio2/modaudio2.c create mode 100644 m5stack/cmodules/m5audio2/vfs_stream.c create mode 100644 m5stack/cmodules/m5audio2/vfs_stream.h create mode 100644 m5stack/libs/driver/es8388/__init__.py create mode 100644 m5stack/libs/driver/es8388/reg.py create mode 100644 m5stack/libs/module/audio.py diff --git a/m5stack/boards/M5STACK_CoreS3/mpconfigboard.h b/m5stack/boards/M5STACK_CoreS3/mpconfigboard.h index 998d6534..a170d145 100644 --- a/m5stack/boards/M5STACK_CoreS3/mpconfigboard.h +++ b/m5stack/boards/M5STACK_CoreS3/mpconfigboard.h @@ -9,7 +9,7 @@ #define MICROPY_PY_MACHINE_DAC (0) -#define MICROPY_PY_MACHINE_I2S (0) +#define MICROPY_PY_MACHINE_I2S (1) // Enable UART REPL for modules that have an external USB-UART and don't use native USB. #define MICROPY_HW_ENABLE_UART_REPL (1) diff --git a/m5stack/cmodules/cmodules.cmake b/m5stack/cmodules/cmodules.cmake index 5bb28ee7..cb0ea050 100644 --- a/m5stack/cmodules/cmodules.cmake +++ b/m5stack/cmodules/cmodules.cmake @@ -4,6 +4,8 @@ # add cdrivers include(${CMAKE_CURRENT_LIST_DIR}/cdriver/cdriver.cmake) +# add m5audio2 module +include(${CMAKE_CURRENT_LIST_DIR}/m5audio2/m5audio2.cmake) include(${CMAKE_CURRENT_LIST_DIR}/m5utils/m5utils.cmake) if (M5_CAMERA_MODULE_ENABLE) diff --git a/m5stack/cmodules/m5audio2/audio2_player.c b/m5stack/cmodules/m5audio2/audio2_player.c new file mode 100644 index 00000000..f6abd4c2 --- /dev/null +++ b/m5stack/cmodules/m5audio2/audio2_player.c @@ -0,0 +1,971 @@ +/* + * SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD + * + * SPDX-License-Identifier: MIT + */ + +#include "py/objstr.h" +#include "py/runtime.h" +#include "py/mphal.h" +#include "py/stream.h" +#include "extmod/vfs_fat.h" +#include "vfs_stream.h" +#include "i2s_helper.h" + +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include +#include "driver/i2s_std.h" +#include "esp_log.h" +#include "esp_resample.h" + +#include +#include + +#ifndef MAX +#define MAX(a,b) ((a) > (b) ? (a) : (b)) +#endif + +#define DEFAULT_RESAMPLE_FILTER_CONFIG() { \ + .src_rate = 44100, \ + .src_ch = 2, \ + .dest_rate = 48000, \ + .dest_bits = 16, \ + .dest_ch = 2, \ + .src_bits = 16, \ + .mode = RESAMPLE_DECODE_MODE, \ + .max_indata_bytes = 512, \ + .out_len_bytes = 512, \ + .type = ESP_RESAMPLE_TYPE_AUTO, \ + .complexity = 2, \ + .down_ch_idx = 0, \ + .prefer_flag = ESP_RSP_PREFER_TYPE_SPEED, \ +} + +struct __attribute__((packed)) wav_header_t +{ + char RIFF[4]; + uint32_t chunk_size; + char WAVEfmt[8]; + uint32_t fmt_chunk_size; + uint16_t audiofmt; + uint16_t channel; + uint32_t sample_rate; + uint32_t byte_per_sec; + uint16_t block_size; + uint16_t bit_per_sample; +}; + +struct __attribute__((packed)) sub_chunk_t +{ + char identifier[4]; + uint32_t chunk_size; + // uint8_t data[1]; +}; + +typedef enum play_state_t { + PLAY_STATE_IDLE, + PLAY_STATE_PLAYING, + PLAY_STATE_PAUSED, + PLAY_STATE_STOPPED, +} play_state_t; + +typedef struct _audio2_player_obj_t { + mp_obj_base_t base; + bool is_open; + i2s_port_t i2s_id; + int sample_rate; + i2s_mode_t mode; + mp_hal_pin_obj_t sck; + mp_hal_pin_obj_t ws; + mp_hal_pin_obj_t sd; + mp_hal_pin_obj_t mck; + int bits; + channel_t channel; + i2s_chan_handle_t i2s_chan_handle; + + TaskHandle_t task_handle; + + // resample + void *rsp_hd; + resample_info_t resample_info; + unsigned char *out_buf; + unsigned char *in_buf; + + // play + struct { + struct { + char path[128]; + int sample_rate; + int channel; + int bit_per_sample; + int data_offset; + int data_len; + } wav_file; + struct { + const void *data; + int sample_rate; + int channel; + int bit_per_sample; + int data_offset; + int data_len; + } wav_buf; + struct { + int freq; // Hz + int duration; // ms + } tone; + + play_state_t state; + } play_info; + +} audio2_player_obj_t; + +const static char *TAG = "Audio2"; +extern const mp_obj_type_t audio2_player_type; + +static void player_stop_helper(audio2_player_obj_t *self); + +static void player_i2s_init_helper(audio2_player_obj_t *self, size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_id, ARG_sck, ARG_ws, ARG_sd, ARG_mck, ARG_rate, ARG_bits, ARG_channel }; + static const mp_arg_t allowed_args[] = { + /* *FORMAT-OFF* */ + { MP_QSTR_id, MP_ARG_REQUIRED | MP_ARG_INT, { .u_int = 0 } }, + { MP_QSTR_sck, MP_ARG_KW_ONLY | MP_ARG_OBJ, { .u_obj = MP_OBJ_NULL } }, + { MP_QSTR_ws, MP_ARG_KW_ONLY | MP_ARG_OBJ, { .u_obj = MP_OBJ_NULL } }, + { MP_QSTR_sd, MP_ARG_KW_ONLY | MP_ARG_OBJ, { .u_obj = MP_OBJ_NULL } }, + { MP_QSTR_mck, MP_ARG_KW_ONLY | MP_ARG_OBJ, { .u_obj = MP_OBJ_NULL } }, + { MP_QSTR_rate, MP_ARG_KW_ONLY | MP_ARG_INT, { .u_int = 16000 } }, + { MP_QSTR_bits, MP_ARG_KW_ONLY | MP_ARG_INT, { .u_int = 16 } }, + { MP_QSTR_channel, MP_ARG_KW_ONLY | MP_ARG_INT, { .u_int = STEREO } }, + /* *FORMAT-ON* */ + }; + + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + self->i2s_id = args[ARG_id].u_int; + self->sck = args[ARG_sck].u_obj == MP_OBJ_NULL ? -1 : mp_hal_get_pin_obj(args[ARG_sck].u_obj); + self->ws = args[ARG_ws].u_obj == MP_OBJ_NULL ? -1 : mp_hal_get_pin_obj(args[ARG_ws].u_obj); + self->sd = args[ARG_sd].u_obj == MP_OBJ_NULL ? -1 : mp_hal_get_pin_obj(args[ARG_sd].u_obj); + self->mck = args[ARG_mck].u_obj == MP_OBJ_NULL ? -1 : mp_hal_get_pin_obj(args[ARG_mck].u_obj); + self->sample_rate = args[ARG_rate].u_int; + self->bits = args[ARG_bits].u_int; + self->channel = args[ARG_channel].u_int; + self->mode = I2S_MODE_TX; + + #if 0 + check_esp_err(i2s_std_init_helper( + self->i2s_id, + self->mode, + self->sck, + self->ws, + self->sd, + self->mck, + self->sample_rate, + self->bits, + self->channel, + &self->i2s_chan_handle + )); + #endif +} + + +static mp_obj_t player_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { + mp_arg_check_num(n_args, n_kw, 1, MP_OBJ_FUN_ARGS_MAX, true); + + audio2_player_obj_t *self = mp_obj_malloc(audio2_player_obj_t, &audio2_player_type); + mp_map_t kw_args; + mp_map_init_fixed_table(&kw_args, n_kw, args + n_args); + player_i2s_init_helper(self, n_args, args, &kw_args); + + self->is_open = false; + self->play_info.state = PLAY_STATE_IDLE; + + return self; +} + + +static void player_wav_file_task(void *arg) { + audio2_player_obj_t *self = (audio2_player_obj_t *)arg; + + ESP_LOGI(TAG, "player task started"); + + resample_info_t resample_info = DEFAULT_RESAMPLE_FILTER_CONFIG(); + memcpy(&self->resample_info, &resample_info, sizeof(resample_info_t)); + self->resample_info.dest_rate = self->sample_rate; + self->resample_info.dest_bits = self->bits; + self->resample_info.dest_ch = self->channel; + self->resample_info.src_rate = self->play_info.wav_file.sample_rate; + self->resample_info.src_bits = self->play_info.wav_file.bit_per_sample; + self->resample_info.src_ch = self->play_info.wav_file.channel; + + self->rsp_hd = esp_resample_create(&self->resample_info, (unsigned char **)&self->in_buf, (unsigned char **)&self->out_buf); + if (self->rsp_hd == NULL) { + ESP_LOGI(TAG, "Failed to create the resample handler"); + } + ESP_LOGI(TAG, + "source rate: %d, source channel: %d, source bits: %d, destination rate: %d, destination channel: %d, destination bits: %d", + self->resample_info.src_rate, + self->resample_info.src_ch, + self->resample_info.src_bits, + self->resample_info.dest_rate, + self->resample_info.dest_ch, + self->resample_info.dest_bits + ); + + ESP_LOGI(TAG, "open path %s", self->play_info.wav_file.path); + + void *wav_file = vfs_stream_open(self->play_info.wav_file.path, "rb"); + + vfs_stream_seek(wav_file, self->play_info.wav_file.data_offset, SEEK_SET); + ESP_LOGI(TAG, "file pos: %ld", vfs_stream_tell(wav_file)); + + uint32_t data_len = self->play_info.wav_file.data_len; + bool flg_16bit = (self->play_info.wav_file.bit_per_sample >> 4); + + size_t buf_num = 3; + size_t buf_size = 256; + uint8_t wav_data[buf_num][buf_size]; + + size_t idx = 0; + int in_offset = buf_size; + while (data_len > 0) { + + if (self->play_info.state == PLAY_STATE_STOPPED) { + ESP_LOGI(TAG, "player task stopped"); + break; + } + + if (self->play_info.state == PLAY_STATE_PAUSED) { + vTaskDelay(10 / portTICK_PERIOD_MS); + continue; + } + + size_t len = (data_len > buf_size) ? buf_size : data_len; + // mp_printf(&mp_plat_print, "buf idx: %d len: %d data_len: %d\r\n", idx, len, data_len); + if (vfs_stream_read(wav_file, (uint8_t *)wav_data[idx], len) != len) { + vfs_stream_close(wav_file); + ESP_LOGE(TAG, "File is too short"); + break; + } + data_len -= len; + + if (len != in_offset && len > 0) { + memset(self->in_buf + len, 0, in_offset - len); + } + int in_bytes_consumed = esp_resample_run( + self->rsp_hd, + &self->resample_info, + wav_data[idx], + self->out_buf, + len, + &self->resample_info.out_len_bytes + ); + + // ESP_LOGE(TAG, "in_bytes_consumed: %d, out_len_bytes: %d", in_bytes_consumed, self->resample_info.out_len_bytes); + + size_t num_bytes_written = 0; + esp_err_t ret = i2s_channel_write(self->i2s_chan_handle, self->out_buf, self->resample_info.out_len_bytes, &num_bytes_written, portMAX_DELAY); + + idx = idx < (buf_num - 1) ? idx + 1 : 0; + } + + vfs_stream_close(wav_file); + + esp_resample_destroy(self->rsp_hd); + + self->play_info.state = PLAY_STATE_IDLE; + + vTaskDelete(NULL); +} + + +static bool check_file_type(const char *path) { + // check file type + char ftype[5] = {0}; + for (size_t i = 0; i < 4; i++) { + ftype[i] = tolower((char)path[i + strlen(path) - 4]); + } + ftype[4] = '\0'; + if (strcmp(ftype, ".wav") != 0) { + return false; + } + return true; +} + +static mp_obj_t player_play_wav_file(mp_obj_t self_in, mp_obj_t path_in) { + audio2_player_obj_t *self = self_in; + const char *path = mp_obj_str_get_str(path_in); + + // check file type + if (check_file_type(path) == false) { + mp_raise_ValueError(MP_ERROR_TEXT("file type not supported")); + } + + // stop previous task + player_stop_helper(self); + + if (self->is_open == false) { + check_esp_err(i2s_std_init_helper( + self->i2s_id, + self->mode, + self->sck, + self->ws, + self->sd, + self->mck, + self->sample_rate, + self->bits, + self->channel, + &self->i2s_chan_handle + )); + self->is_open = true; + } + + mp_obj_t file_args[2]; + file_args[0] = path_in; + file_args[1] = mp_obj_new_str("rb", strlen("rb")); + mp_obj_t wav_file = mp_vfs_open(2, file_args, (mp_map_t *)&mp_const_empty_map); + if (wav_file == mp_const_none) { + mp_raise_OSError(MP_ENOENT); + // mp_raise_ValueError(MP_ERROR_TEXT("open file failed")); + } + + struct wav_header_t header; + int rlen = mp_stream_posix_read(wav_file, &header, sizeof(struct wav_header_t)); + if (rlen != sizeof(struct wav_header_t)) { + mp_stream_close(wav_file); + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("File is too short")); + } + + if (memcmp(header.RIFF, "RIFF", 4) + || memcmp(header.WAVEfmt, "WAVEfmt ", 8) + || header.audiofmt != 1 + || header.bit_per_sample < 8 + || header.bit_per_sample > 16 + || header.channel == 0 + || header.channel > 2 + || header.sample_rate < 8000 + || header.sample_rate > 96000) { + mp_stream_close(wav_file); + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("file format is not supported")); + } + + ESP_LOGI(TAG, + "wav bit_per_sample: %d, channel: %d, sample_rate: %ld", + header.bit_per_sample, + header.channel, + header.sample_rate + ); + + struct sub_chunk_t sub_chunk; + while (true) { + if (mp_stream_posix_read(wav_file, &sub_chunk, sizeof(struct sub_chunk_t)) != sizeof(struct sub_chunk_t)) { + mp_stream_close(wav_file); + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("File is too short")); + } + + if (strncmp(sub_chunk.identifier, "data", 4) == 0) { + break; + } + + if (mp_stream_posix_lseek(wav_file, sub_chunk.chunk_size, SEEK_CUR) == -1) { + mp_stream_close(wav_file); + mp_raise_OSError(MP_ESPIPE); + } + } + + memset(self->play_info.wav_file.path, 0, sizeof(self->play_info.wav_file.path)); + memcpy(self->play_info.wav_file.path, path, strlen(path)); + self->play_info.wav_file.sample_rate = header.sample_rate; + self->play_info.wav_file.channel = header.channel; + self->play_info.wav_file.bit_per_sample = header.bit_per_sample; + self->play_info.wav_file.data_offset = mp_stream_posix_lseek(wav_file, 0, SEEK_CUR); + self->play_info.wav_file.data_len = sub_chunk.chunk_size; + self->play_info.state = PLAY_STATE_PLAYING; + + mp_stream_close(wav_file); + + ESP_LOGE(TAG, "path: %s, data_offset: %d", self->play_info.wav_file.path, self->play_info.wav_file.data_offset); + + #if portNUM_PROCESSORS > 1 + xTaskCreatePinnedToCore( + player_wav_file_task, + "player_wav_file_task", + 4096, + self, + 5, + &self->task_handle, + 0 + ); + #else + xTaskCreate( + player_wav_file_task, + "player_wav_file_task", + 4096, + self, + 5, + &self->task_handle + ); + #endif + + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_2(player_play_wav_file_obj, player_play_wav_file); + + +static void player_tone_task(void *arg) { + audio2_player_obj_t *self = (audio2_player_obj_t *)arg; + + ESP_LOGI(TAG, "player tone task started"); + + #define AMPLITUDE (32767) + #define PI (3.1415926) + + #define SINE_LUT_SIZE 16 + static const int16_t sine_lut[SINE_LUT_SIZE] = { + 0, 12539, 23169, 30272, + 32767, 30272, 23169, 12539, + 0, -12539, -23169, -30272, + -32767, -30272, -23169, -12539 + }; + int freq = self->play_info.tone.freq; + double duration = (double)(self->play_info.tone.duration / 1000.0); + + size_t sample_data_len = round((double)(self->sample_rate * self->channel * ((self->bits >> 3) / 2) * duration)); + size_t remaining_length = sample_data_len; + + ESP_LOGI(TAG, "sample_data_len: %d", sample_data_len); + + size_t buf_num = 3; + size_t buf_size = 256; + int16_t wav_data[buf_num][buf_size]; + + size_t i = 0; + size_t idx = 0; + + double step = (double)SINE_LUT_SIZE * freq / self->sample_rate; + double lidx = 0.0; + + while (remaining_length > 0) { + if (self->play_info.state == PLAY_STATE_STOPPED) { + ESP_LOGI(TAG, "player task stopped"); + break; + } + + if (self->play_info.state == PLAY_STATE_PAUSED) { + vTaskDelay(10 / portTICK_PERIOD_MS); + continue; + } + + int read_size = buf_size; + if (buf_size > remaining_length) { + read_size = remaining_length; + } + + int index = 0; + int end = i + read_size; + for (; i < end; i++) { + wav_data[idx][index++] = sine_lut[(uint32_t)lidx % SINE_LUT_SIZE]; + // Accumulate and take a new phase next time + lidx += step; + if (lidx >= SINE_LUT_SIZE) { + lidx -= SINE_LUT_SIZE; + } + } + remaining_length = sample_data_len - i; + + size_t num_bytes_written = 0; + esp_err_t ret = i2s_channel_write( + self->i2s_chan_handle, + wav_data[idx], + read_size * 2, + &num_bytes_written, + portMAX_DELAY + ); + + idx = idx < (buf_num - 1) ? idx + 1 : 0; + } + + self->play_info.state = PLAY_STATE_IDLE; + vTaskDelete(NULL); +} + + +static mp_obj_t player_tone(mp_obj_t self_in, mp_obj_t freq_in, mp_obj_t duration_in) { + audio2_player_obj_t *self = self_in; + self->play_info.tone.freq = mp_obj_get_int(freq_in); + self->play_info.tone.duration = mp_obj_get_int(duration_in); + + // stop previous task + player_stop_helper(self); + + if (self->is_open == false) { + check_esp_err(i2s_std_init_helper( + self->i2s_id, + self->mode, + self->sck, + self->ws, + self->sd, + self->mck, + self->sample_rate, + self->bits, + self->channel, + &self->i2s_chan_handle + )); + self->is_open = true; + } + + self->play_info.state = PLAY_STATE_PLAYING; + + #if portNUM_PROCESSORS > 1 + xTaskCreatePinnedToCore( + player_tone_task, + "player_tone_task", + 4096, + self, + 5, + &self->task_handle, + 0 + ); + #else + xTaskCreate( + player_tone_task, + "player_tone_task", + 4096, + self, + 5, + &self->task_handle + ); + #endif + + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_3(player_tone_obj, player_tone); + + +static void player_wav_task(void *arg) { + audio2_player_obj_t *self = (audio2_player_obj_t *)arg; + + ESP_LOGI(TAG, "player task started"); + + resample_info_t resample_info = DEFAULT_RESAMPLE_FILTER_CONFIG(); + memcpy(&self->resample_info, &resample_info, sizeof(resample_info_t)); + self->resample_info.dest_rate = self->sample_rate; + self->resample_info.dest_bits = self->bits; + self->resample_info.dest_ch = self->channel; + self->resample_info.src_rate = self->play_info.wav_buf.sample_rate; + self->resample_info.src_bits = self->play_info.wav_buf.bit_per_sample; + self->resample_info.src_ch = self->play_info.wav_buf.channel; + + self->rsp_hd = esp_resample_create(&self->resample_info, (unsigned char **)&self->in_buf, (unsigned char **)&self->out_buf); + if (self->rsp_hd == NULL) { + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("Failed to create the resample handler")); + } + ESP_LOGI(TAG, + "source rate: %d, source channel: %d, source bits: %d, destination rate: %d, destination channel: %d, destination bits: %d", + self->resample_info.src_rate, + self->resample_info.src_ch, + self->resample_info.src_bits, + self->resample_info.dest_rate, + self->resample_info.dest_ch, + self->resample_info.dest_bits + ); + + uint32_t data_len = self->play_info.wav_buf.data_len; + size_t data_offset = self->play_info.wav_buf.data_offset; + bool flg_16bit = (self->play_info.wav_buf.bit_per_sample >> 4); + + size_t buf_num = 3; + size_t buf_size = 256; + uint8_t wav_data[buf_num][buf_size]; + + size_t idx = 0; + int in_offset = buf_size; + while (data_len > 0) { + if (self->play_info.state == PLAY_STATE_STOPPED) { + ESP_LOGI(TAG, "player task stopped"); + break; + } + + if (self->play_info.state == PLAY_STATE_PAUSED) { + vTaskDelay(10 / portTICK_PERIOD_MS); + continue; + } + + size_t len = (data_len > buf_size) ? buf_size : data_len; + if (data_offset + len > self->play_info.wav_buf.data_len) { + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("File is too short")); + } + memcpy(wav_data[idx], &self->play_info.wav_buf.data[data_offset], len); + data_offset += len; + + data_len -= len; + + if (len != in_offset && len > 0) { + memset(self->in_buf + len, 0, in_offset - len); + } + int in_bytes_consumed = esp_resample_run( + self->rsp_hd, + &self->resample_info, + wav_data[idx], + self->out_buf, + len, + &self->resample_info.out_len_bytes + ); + + // ESP_LOGE(TAG, "in_bytes_consumed: %d, out_len_bytes: %d", in_bytes_consumed, self->resample_info.out_len_bytes); + + size_t num_bytes_written = 0; + esp_err_t ret = i2s_channel_write(self->i2s_chan_handle, self->out_buf, self->resample_info.out_len_bytes, &num_bytes_written, portMAX_DELAY); + + idx = idx < (buf_num - 1) ? idx + 1 : 0; + } + + esp_resample_destroy(self->rsp_hd); + + self->play_info.state = PLAY_STATE_IDLE; + + vTaskDelete(NULL); +} + + +static mp_obj_t player_play_wav(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_buf, ARG_duration }; + static const mp_arg_t allowed_args[] = { + /* *FORMAT-OFF* */ + { MP_QSTR_buf, MP_ARG_REQUIRED | MP_ARG_OBJ, { .u_obj = MP_OBJ_NULL } }, + { MP_QSTR_duration, MP_ARG_KW_ONLY |MP_ARG_INT, { .u_int = -1 } }, + /* *FORMAT-ON* */ + }; + + // parse args + audio2_player_obj_t *self = MP_OBJ_TO_PTR(pos_args[0]); + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + mp_buffer_info_t bufinfo; + mp_get_buffer_raise(args[ARG_buf].u_obj, &bufinfo, MP_BUFFER_READ); + size_t buf_offset = 0; + int duration = args[ARG_duration].u_int; + + // stop previous task + player_stop_helper(self); + + if (self->is_open == false) { + check_esp_err(i2s_std_init_helper( + self->i2s_id, + self->mode, + self->sck, + self->ws, + self->sd, + self->mck, + self->sample_rate, + self->bits, + self->channel, + &self->i2s_chan_handle + )); + self->is_open = true; + } + + if (bufinfo.len < sizeof(struct wav_header_t)) { + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("File is too short")); + } + struct wav_header_t header; + memcpy(&header, (const char *)&bufinfo.buf[buf_offset], sizeof(struct wav_header_t)); + buf_offset += sizeof(struct wav_header_t); + + ESP_LOGE(TAG, "wav bit_per_sample: %d, channel: %d, sample_rate: %ld", header.bit_per_sample, header.channel, header.sample_rate); + + if (memcmp(header.RIFF, "RIFF", 4) + || memcmp(header.WAVEfmt, "WAVEfmt ", 8) + || header.audiofmt != 1 + || header.bit_per_sample < 8 + || header.bit_per_sample > 16 + || header.channel == 0 + || header.channel > 2 + || header.sample_rate < 8000 + || header.sample_rate > 96000) { + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("file format is not supported")); + } + + struct sub_chunk_t sub_chunk; + while (true) { + if (buf_offset + sizeof(struct sub_chunk_t) > bufinfo.len) { + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("File is too short")); + } + memcpy(&sub_chunk, &bufinfo.buf[buf_offset], sizeof(struct sub_chunk_t)); + buf_offset += sizeof(struct sub_chunk_t); + + if (strncmp(sub_chunk.identifier, "data", 4) == 0) { + break; + } + + if (buf_offset + sub_chunk.chunk_size > bufinfo.len) { + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("File is too short")); + } + buf_offset += sub_chunk.chunk_size; + } + + ESP_LOGE(TAG, "buf_offset: %d", buf_offset); + + self->play_info.wav_buf.data = &bufinfo.buf[buf_offset]; + self->play_info.wav_buf.sample_rate = header.sample_rate; + self->play_info.wav_buf.channel = header.channel; + self->play_info.wav_buf.bit_per_sample = header.bit_per_sample; + self->play_info.wav_buf.data_offset = 0; + self->play_info.wav_buf.data_len = round((double)(duration / 1000.0) * header.sample_rate * header.channel * (header.bit_per_sample >> 3)); + if (duration == -1 || self->play_info.wav_buf.data_len > sub_chunk.chunk_size) { + self->play_info.wav_buf.data_len = sub_chunk.chunk_size; + } + + self->play_info.state = PLAY_STATE_PLAYING; + + #if portNUM_PROCESSORS > 1 + xTaskCreatePinnedToCore( + player_wav_task, + "player_wav_task", + 4096, + self, + 5, + &self->task_handle, + 0 + ); + #else + xTaskCreate( + player_wav_task, + "player_wav_task", + 4096, + self, + 5, + &self->task_handle + ); + #endif + + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_KW(player_play_wav_obj, 2, player_play_wav); + + +static mp_obj_t player_play_raw(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_buf, ARG_rate, ARG_bits, ARG_channel, ARG_duration }; + static const mp_arg_t allowed_args[] = { + /* *FORMAT-OFF* */ + { MP_QSTR_buf, MP_ARG_REQUIRED | MP_ARG_OBJ, { .u_obj = MP_OBJ_NULL } }, + { MP_QSTR_rate, MP_ARG_KW_ONLY | MP_ARG_INT, { .u_int = 16000 } }, + { MP_QSTR_bits, MP_ARG_KW_ONLY | MP_ARG_INT, { .u_int = 16 } }, + { MP_QSTR_channel, MP_ARG_KW_ONLY | MP_ARG_INT, { .u_int = STEREO } }, + { MP_QSTR_duration, MP_ARG_KW_ONLY | MP_ARG_INT, { .u_int = -1 } }, + /* *FORMAT-ON* */ + }; + + // parse args + audio2_player_obj_t *self = MP_OBJ_TO_PTR(pos_args[0]); + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + mp_buffer_info_t bufinfo; + mp_get_buffer_raise(args[ARG_buf].u_obj, &bufinfo, MP_BUFFER_READ); + + // stop previous task + player_stop_helper(self); + + if (self->is_open == false) { + check_esp_err(i2s_std_init_helper( + self->i2s_id, + self->mode, + self->sck, + self->ws, + self->sd, + self->mck, + self->sample_rate, + self->bits, + self->channel, + &self->i2s_chan_handle + )); + self->is_open = true; + } + + uint32_t sample_rate = args[ARG_rate].u_int; + uint16_t channel = args[ARG_channel].u_int; + uint16_t bit_per_sample = args[ARG_bits].u_int; + int duration = args[ARG_duration].u_int; + + if (duration == 0) { + return mp_const_none; + } + + self->play_info.wav_buf.data = &bufinfo.buf[0]; + self->play_info.wav_buf.sample_rate = sample_rate; + self->play_info.wav_buf.channel = channel; + self->play_info.wav_buf.bit_per_sample = bit_per_sample; + self->play_info.wav_buf.data_offset = 0; + self->play_info.wav_buf.data_len = round((double)(duration / 1000.0) * sample_rate * channel * (bit_per_sample >> 3)); + if (duration == -1 || self->play_info.wav_buf.data_len > bufinfo.len) { + self->play_info.wav_buf.data_len = bufinfo.len; + } + + self->play_info.state = PLAY_STATE_PLAYING; + + #if portNUM_PROCESSORS > 1 + xTaskCreatePinnedToCore( + player_wav_task, + "player_wav_task", + 4096, + self, + 5, + &self->task_handle, + 0 + ); + #else + xTaskCreate( + player_wav_task, + "player_wav_task", + 4096, + self, + 5, + &self->task_handle + ); + #endif + + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_KW(player_play_raw_obj, 2, player_play_raw); + + +static mp_obj_t player_pause(mp_obj_t self_in) { + audio2_player_obj_t *self = self_in; + + self->play_info.state = PLAY_STATE_PAUSED; + + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_1(player_pause_obj, player_pause); + + +static mp_obj_t player_resume(mp_obj_t self_in) { + audio2_player_obj_t *self = self_in; + + if (self->play_info.state == PLAY_STATE_PAUSED) { + self->play_info.state = PLAY_STATE_PLAYING; + } + + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_1(player_resume_obj, player_resume); + + +static void player_stop_helper(audio2_player_obj_t *self) { + if (self->play_info.state == PLAY_STATE_IDLE) { + return; + } + self->play_info.state = PLAY_STATE_STOPPED; + while (self->play_info.state != PLAY_STATE_IDLE) { + vTaskDelay(10 / portTICK_PERIOD_MS); + } +} + +static mp_obj_t player_stop(mp_obj_t self_in) { + audio2_player_obj_t *self = self_in; + + player_stop_helper(self); + + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_1(player_stop_obj, player_stop); + + +static mp_obj_t player_is_running(mp_obj_t self_in) { + audio2_player_obj_t *self = self_in; + + return mp_obj_new_bool(self->is_open); +} +static MP_DEFINE_CONST_FUN_OBJ_1(player_is_running_obj, player_is_running); + + +static mp_obj_t player_is_playing(mp_obj_t self_in) { + audio2_player_obj_t *self = self_in; + + return mp_obj_new_bool(self->play_info.state == PLAY_STATE_PLAYING); +} +static MP_DEFINE_CONST_FUN_OBJ_1(player_is_playing_obj, player_is_playing); + + +static mp_obj_t player_set_volume_percentage(mp_obj_t self_in, mp_obj_t volume_in) { + audio2_player_obj_t *self = self_in; + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_2(player_set_volume_percentage_obj, player_set_volume_percentage); + + +static mp_obj_t player_get_volume_percentage(mp_obj_t self_in) { + audio2_player_obj_t *self = self_in; + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_1(player_get_volume_percentage_obj, player_get_volume_percentage); + + +static mp_obj_t player_init(mp_obj_t self_in) { + audio2_player_obj_t *self = self_in; + if (self->is_open) { + return mp_const_none; + } + + check_esp_err(i2s_std_init_helper( + self->i2s_id, + self->mode, + self->sck, + self->ws, + self->sd, + self->mck, + self->sample_rate, + self->bits, + self->channel, + &self->i2s_chan_handle + )); + + self->is_open = true; + + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_1(player_init_obj, player_init); + + +static mp_obj_t player_deinit(mp_obj_t self_in) { + audio2_player_obj_t *self = self_in; + if (self->is_open) { + player_stop_helper(self); + i2s_std_deinit_helper(self->i2s_chan_handle); + } + self->is_open = false; + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_1(player_deinit_obj, player_deinit); + + +static const mp_rom_map_elem_t player_locals_dict_table[] = { + /* *FORMAT-OFF* */ + { MP_ROM_QSTR(MP_QSTR_play_wav_file), MP_ROM_PTR(&player_play_wav_file_obj) }, + { MP_ROM_QSTR(MP_QSTR_tone), MP_ROM_PTR(&player_tone_obj) }, + { MP_ROM_QSTR(MP_QSTR_play_wav), MP_ROM_PTR(&player_play_wav_obj) }, + { MP_ROM_QSTR(MP_QSTR_play_raw), MP_ROM_PTR(&player_play_raw_obj) }, + { MP_ROM_QSTR(MP_QSTR_pause), MP_ROM_PTR(&player_pause_obj) }, + { MP_ROM_QSTR(MP_QSTR_resume), MP_ROM_PTR(&player_resume_obj) }, + { MP_ROM_QSTR(MP_QSTR_stop), MP_ROM_PTR(&player_stop_obj) }, + { MP_ROM_QSTR(MP_QSTR_is_running), MP_ROM_PTR(&player_is_running_obj) }, + { MP_ROM_QSTR(MP_QSTR_is_playing), MP_ROM_PTR(&player_is_playing_obj) }, + { MP_ROM_QSTR(MP_QSTR_set_volume_percentage), MP_ROM_PTR(&player_set_volume_percentage_obj) }, + { MP_ROM_QSTR(MP_QSTR_get_volume_percentage), MP_ROM_PTR(&player_get_volume_percentage_obj) }, + { MP_ROM_QSTR(MP_QSTR_init), MP_ROM_PTR(&player_init_obj) }, + { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&player_deinit_obj) }, + /* *FORMAT-ON* */ +}; + +static MP_DEFINE_CONST_DICT(player_locals_dict, player_locals_dict_table); + +MP_DEFINE_CONST_OBJ_TYPE( + audio2_player_type, + MP_QSTR_Player, + MP_TYPE_FLAG_NONE, + make_new, player_make_new, + locals_dict, &player_locals_dict + ); diff --git a/m5stack/cmodules/m5audio2/audio2_recorder.c b/m5stack/cmodules/m5audio2/audio2_recorder.c new file mode 100644 index 00000000..b3394d39 --- /dev/null +++ b/m5stack/cmodules/m5audio2/audio2_recorder.c @@ -0,0 +1,420 @@ +/* + * SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD + * + * SPDX-License-Identifier: MIT + */ + +#include "i2s_helper.h" +#include "format_wav.h" + +#include "py/objstr.h" +#include "py/runtime.h" +#include "py/mphal.h" +#include "py/stream.h" +#include "extmod/vfs_fat.h" + +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include +#include "driver/i2s_std.h" +#include "esp_timer.h" +#include "esp_log.h" +#include "esp_resample.h" + +#include +#include +#include + +#ifndef MAX +#define MAX(a,b) ((a) > (b) ? (a) : (b)) +#endif + +#define RESAMPLE_MAX_INDATA_BYTES 512 +#define RESAMPLE_OUTDATA_BYTES 512 + +#define DEFAULT_RESAMPLE_FILTER_CONFIG() { \ + .src_rate = 44100, \ + .src_ch = 2, \ + .dest_rate = 48000, \ + .dest_bits = 16, \ + .dest_ch = 2, \ + .src_bits = 16, \ + .mode = RESAMPLE_DECODE_MODE, \ + .max_indata_bytes = RESAMPLE_MAX_INDATA_BYTES, \ + .out_len_bytes = RESAMPLE_OUTDATA_BYTES, \ + .type = ESP_RESAMPLE_TYPE_AUTO, \ + .complexity = 2, \ + .down_ch_idx = 0, \ + .prefer_flag = ESP_RSP_PREFER_TYPE_SPEED, \ +} + + +typedef struct _audio_recorder_obj_t { + mp_obj_base_t base; + bool is_open; + i2s_port_t i2s_id; + int sample_rate; + i2s_mode_t mode; + mp_hal_pin_obj_t sck; + mp_hal_pin_obj_t ws; + mp_hal_pin_obj_t sd; + mp_hal_pin_obj_t mck; + int bits; + channel_t channel; + i2s_chan_handle_t i2s_chan_handle; + + // resample + void *rsp_hd; + resample_info_t resample_info; + unsigned char *out_buf; + unsigned char *in_buf; + + struct { + void *buf; + size_t cur_len; + size_t total; + } pcm_buf; + +} audio2_recorder_obj_t; + +const static char *TAG = "Audio2"; + +extern const mp_obj_type_t audio2_recorder_type; + + +static void recorder_i2s_init_helper(audio2_recorder_obj_t *self, size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_id, ARG_sck, ARG_ws, ARG_sd, ARG_mck, ARG_rate, ARG_bits, ARG_channel }; + static const mp_arg_t allowed_args[] = { + /* *FORMAT-OFF* */ + { MP_QSTR_id, MP_ARG_REQUIRED | MP_ARG_INT, { .u_int = 0 } }, + { MP_QSTR_sck, MP_ARG_KW_ONLY | MP_ARG_OBJ, { .u_obj = MP_OBJ_NULL } }, + { MP_QSTR_ws, MP_ARG_KW_ONLY | MP_ARG_OBJ, { .u_obj = MP_OBJ_NULL } }, + { MP_QSTR_sd, MP_ARG_KW_ONLY | MP_ARG_OBJ, { .u_obj = MP_OBJ_NULL } }, + { MP_QSTR_mck, MP_ARG_KW_ONLY | MP_ARG_OBJ, { .u_obj = MP_OBJ_NULL } }, + { MP_QSTR_rate, MP_ARG_KW_ONLY | MP_ARG_INT, { .u_int = 16000 } }, + { MP_QSTR_bits, MP_ARG_KW_ONLY | MP_ARG_INT, { .u_int = 16 } }, + { MP_QSTR_channel, MP_ARG_KW_ONLY | MP_ARG_INT, { .u_int = STEREO } }, + /* *FORMAT-ON* */ + }; + + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + self->i2s_id = args[ARG_id].u_int; + self->sck = args[ARG_sck].u_obj == MP_OBJ_NULL ? -1 : mp_hal_get_pin_obj(args[ARG_sck].u_obj); + self->ws = args[ARG_ws].u_obj == MP_OBJ_NULL ? -1 : mp_hal_get_pin_obj(args[ARG_ws].u_obj); + self->sd = args[ARG_sd].u_obj == MP_OBJ_NULL ? -1 : mp_hal_get_pin_obj(args[ARG_sd].u_obj); + self->mck = args[ARG_mck].u_obj == MP_OBJ_NULL ? -1 : mp_hal_get_pin_obj(args[ARG_mck].u_obj); + self->sample_rate = args[ARG_rate].u_int; + self->bits = args[ARG_bits].u_int; + self->channel = args[ARG_channel].u_int; + self->mode = I2S_MODE_RX; + + #if 0 + check_esp_err(i2s_std_init_helper( + self->i2s_id, + self->mode, + self->sck, + self->ws, + self->sd, + self->mck, + self->sample_rate, + self->bits, + self->channel, + &self->i2s_chan_handle + )); + #endif +} + + +static mp_obj_t recorder_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { + mp_arg_check_num(n_args, n_kw, 1, MP_OBJ_FUN_ARGS_MAX, true); + + audio2_recorder_obj_t *self = mp_obj_malloc(audio2_recorder_obj_t, &audio2_recorder_type); + mp_map_t kw_args; + mp_map_init_fixed_table(&kw_args, n_kw, args + n_args); + recorder_i2s_init_helper(self, n_args, args, &kw_args); + + self->is_open = false; + + self->pcm_buf.buf = NULL; + self->pcm_buf.cur_len = 0; + self->pcm_buf.total = 0; + + return self; +} + + +static void recorder_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) { + audio2_recorder_obj_t *self = (audio2_recorder_obj_t *)MP_OBJ_TO_PTR(self_in); + if (dest[0] == MP_OBJ_NULL) { + // Load + if (attr == MP_QSTR_pcm_buffer) { + dest[0] = mp_obj_new_bytes(self->pcm_buf.buf, self->pcm_buf.cur_len); + } else { + // Continue lookup in locals_dict. + dest[1] = MP_OBJ_SENTINEL; + } + } else if (dest[1] != MP_OBJ_NULL) { + // Store + } +} + + +static mp_obj_t recorder_init(mp_obj_t self_in) { + audio2_recorder_obj_t *self = self_in; + if (self->is_open == false) { + check_esp_err(i2s_std_init_helper( + self->i2s_id, + self->mode, + self->sck, + self->ws, + self->sd, + self->mck, + self->sample_rate, + self->bits, + self->channel, + &self->i2s_chan_handle + )); + self->is_open = true; + } + + return mp_obj_new_bool(self->is_open); +} +static MP_DEFINE_CONST_FUN_OBJ_1(recorder_init_obj, recorder_init); + + +static mp_obj_t recorder_deinit(size_t n_args, const mp_obj_t *args) { + audio2_recorder_obj_t *self = MP_OBJ_TO_PTR(args[0]); + if (self->is_open) { + i2s_std_deinit_helper(self->i2s_chan_handle); + self->is_open = false; + } + + if (n_args > 1) { + if (mp_obj_is_true(args[1])) { + if (self->pcm_buf.buf) { + m_free(self->pcm_buf.buf); + } + self->pcm_buf.buf = NULL; + self->pcm_buf.cur_len = 0; + self->pcm_buf.total = 0; + } + } + + return mp_obj_new_bool(self->is_open); +} +static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(recorder_deinit_obj, 1, 2, recorder_deinit); + + +static mp_obj_t recorder_is_running(mp_obj_t self_in) { + audio2_recorder_obj_t *self = self_in; + + return mp_obj_new_bool(self->is_open); +} +static MP_DEFINE_CONST_FUN_OBJ_1(recorder_is_running_obj, recorder_is_running); + + +static mp_obj_t recorder_record_wav_file(size_t n_args, const mp_obj_t *args_in, mp_map_t *kw_args) { + enum { ARG_path, ARG_rate, ARG_bits, ARG_channel, ARG_duration }; + static const mp_arg_t allowed_args[] = { + /* *FORMAT-OFF* */ + { MP_QSTR_path, MP_ARG_REQUIRED | MP_ARG_OBJ, { .u_obj = mp_const_none } }, + { MP_QSTR_rate, MP_ARG_KW_ONLY | MP_ARG_INT, { .u_int = 16000 } }, + { MP_QSTR_bits, MP_ARG_KW_ONLY | MP_ARG_INT, { .u_int = 16 } }, + { MP_QSTR_channel, MP_ARG_KW_ONLY | MP_ARG_INT, { .u_int = STEREO } }, + { MP_QSTR_duration, MP_ARG_KW_ONLY | MP_ARG_INT, { .u_int = 3000 } }, + /* *FORMAT-ON* */ + }; + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + audio2_recorder_obj_t *self = args_in[0]; + mp_arg_parse_all(n_args - 1, args_in + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + const char *path = mp_obj_str_get_str(args[ARG_path].u_obj); + if (self->sample_rate != args[ARG_rate].u_int || + self->bits != args[ARG_bits].u_int || + self->channel != args[ARG_channel].u_int + ) { + self->sample_rate = args[ARG_rate].u_int; + self->bits = args[ARG_bits].u_int; + self->channel = args[ARG_channel].u_int; + + i2s_std_deinit_helper(self->i2s_chan_handle); + check_esp_err(i2s_std_init_helper( + self->i2s_id, + self->mode, + self->sck, + self->ws, + self->sd, + self->mck, + self->sample_rate, + self->bits, + self->channel, + &self->i2s_chan_handle + )); + self->is_open = true; + } + + if (self->is_open == false) { + check_esp_err(i2s_std_init_helper( + self->i2s_id, + self->mode, + self->sck, + self->ws, + self->sd, + self->mck, + self->sample_rate, + self->bits, + self->channel, + &self->i2s_chan_handle + )); + self->is_open = true; + } + + mp_obj_t file_args[2]; + file_args[0] = args[ARG_path].u_obj; + file_args[1] = mp_obj_new_str("wb+", strlen("wb+")); + mp_obj_t wav_file = mp_vfs_open(2, file_args, (mp_map_t *)&mp_const_empty_map); + if (wav_file == mp_const_none) { + mp_raise_OSError(MP_ENOENT); + // mp_raise_ValueError(MP_ERROR_TEXT("open file failed")); + } + + size_t src_len = round((double)(args[ARG_duration].u_int / 1000.0) * self->sample_rate * self->channel * (self->bits >> 3)); + if (src_len > self->pcm_buf.total) { + self->pcm_buf.buf = m_realloc(self->pcm_buf.buf, src_len); + self->pcm_buf.total = src_len; + } + self->pcm_buf.cur_len = src_len; + + wav_header_t wav_header = WAV_HEADER_PCM_DEFAULT(src_len, self->bits, self->sample_rate, self->channel); + int rlen = mp_stream_posix_write(wav_file, &wav_header, sizeof(wav_header_t)); + if (rlen != sizeof(wav_header_t)) { + mp_stream_close(wav_file); + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("File is too short")); + } + + size_t read_len = 0; + + while (read_len < src_len) { + size_t bytes_read = 0; + i2s_channel_read( + self->i2s_chan_handle, + &self->pcm_buf.buf[read_len], + src_len - read_len, + &bytes_read, + portMAX_DELAY + ); + + int wlen = mp_stream_posix_write(wav_file, &self->pcm_buf.buf[read_len], bytes_read); + read_len += bytes_read; + } + + mp_stream_close(wav_file); + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_KW(recorder_record_wav_file_obj, 2, recorder_record_wav_file); + + +static mp_obj_t recorder_record(size_t n_args, const mp_obj_t *args_in, mp_map_t *kw_args) { + enum { ARG_rate, ARG_bits, ARG_channel, ARG_duration }; + static const mp_arg_t allowed_args[] = { + /* *FORMAT-OFF* */ + { MP_QSTR_rate, MP_ARG_KW_ONLY | MP_ARG_INT, { .u_int = 16000 } }, + { MP_QSTR_bits, MP_ARG_KW_ONLY | MP_ARG_INT, { .u_int = 16 } }, + { MP_QSTR_channel, MP_ARG_KW_ONLY | MP_ARG_INT, { .u_int = STEREO } }, + { MP_QSTR_duration, MP_ARG_KW_ONLY | MP_ARG_INT, { .u_int = 3000 } }, + /* *FORMAT-ON* */ + }; + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + audio2_recorder_obj_t *self = args_in[0]; + mp_arg_parse_all(n_args - 1, args_in + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + if (self->sample_rate != args[ARG_rate].u_int || + self->bits != args[ARG_bits].u_int || + self->channel != args[ARG_channel].u_int + ) { + self->sample_rate = args[ARG_rate].u_int; + self->bits = args[ARG_bits].u_int; + self->channel = args[ARG_channel].u_int; + i2s_std_deinit_helper(self->i2s_chan_handle); + check_esp_err(i2s_std_init_helper( + self->i2s_id, + self->mode, + self->sck, + self->ws, + self->sd, + self->mck, + self->sample_rate, + self->bits, + self->channel, + &self->i2s_chan_handle + )); + self->is_open = true; + } + + if (self->is_open == false) { + check_esp_err(i2s_std_init_helper( + self->i2s_id, + self->mode, + self->sck, + self->ws, + self->sd, + self->mck, + self->sample_rate, + self->bits, + self->channel, + &self->i2s_chan_handle + )); + self->is_open = true; + } + + size_t src_len = round((double)(args[ARG_duration].u_int / 1000.0) * self->sample_rate * self->channel * (self->bits >> 3)); + if (src_len > self->pcm_buf.total) { + self->pcm_buf.buf = m_realloc(self->pcm_buf.buf, src_len); + self->pcm_buf.total = src_len; + } + self->pcm_buf.cur_len = src_len; + + ESP_LOGE(TAG, "src_len: %d", src_len); + + size_t read_len = 0; + + while (read_len < src_len) { + size_t bytes_read = 0; + i2s_channel_read( + self->i2s_chan_handle, + &self->pcm_buf.buf[read_len], + src_len - read_len, + &bytes_read, + portMAX_DELAY + ); + read_len += bytes_read; + } + + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_KW(recorder_record_obj, 1, recorder_record); + + +static const mp_rom_map_elem_t recorder_locals_dict_table[] = { + /* *FORMAT-OFF* */ + // methods + { MP_ROM_QSTR(MP_QSTR_init), MP_ROM_PTR(&recorder_init_obj) }, + { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&recorder_deinit_obj) }, + { MP_ROM_QSTR(MP_QSTR_is_running), MP_ROM_PTR(&recorder_is_running_obj) }, + { MP_ROM_QSTR(MP_QSTR_record_wav_file), MP_ROM_PTR(&recorder_record_wav_file_obj) }, + { MP_ROM_QSTR(MP_QSTR_record), MP_ROM_PTR(&recorder_record_obj) }, + /* *FORMAT-ON* */ +}; +static MP_DEFINE_CONST_DICT(recorder_locals_dict, recorder_locals_dict_table); + + +MP_DEFINE_CONST_OBJ_TYPE( + audio2_recorder_type, + MP_QSTR_Recorder, + MP_TYPE_FLAG_NONE, + make_new, recorder_make_new, + attr, recorder_attr, + locals_dict, &recorder_locals_dict + ); diff --git a/m5stack/cmodules/m5audio2/format_wav.h b/m5stack/cmodules/m5audio2/format_wav.h new file mode 100644 index 00000000..08ea888a --- /dev/null +++ b/m5stack/cmodules/m5audio2/format_wav.h @@ -0,0 +1,74 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Header structure for WAV file with only one data chunk + * + * @note See this for reference: http://soundfile.sapp.org/doc/WaveFormat/ + * + * @note Assignment to variables in this struct directly is only possible for little endian architectures + * (including Xtensa & RISC-V) + */ +typedef struct { + struct { + char chunk_id[4]; /*!< Contains the letters "RIFF" in ASCII form */ + uint32_t chunk_size; /*!< This is the size of the rest of the chunk following this number */ + char chunk_format[4]; /*!< Contains the letters "WAVE" */ + } descriptor_chunk; /*!< Canonical WAVE format starts with the RIFF header */ + struct { + char subchunk_id[4]; /*!< Contains the letters "fmt " */ + uint32_t subchunk_size; /*!< This is the size of the rest of the Subchunk which follows this number */ + uint16_t audio_format; /*!< PCM = 1, values other than 1 indicate some form of compression */ + uint16_t num_of_channels; /*!< Mono = 1, Stereo = 2, etc. */ + uint32_t sample_rate; /*!< 8000, 44100, etc. */ + uint32_t byte_rate; /*!< ==SampleRate * NumChannels * BitsPerSample s/ 8 */ + uint16_t block_align; /*!< ==NumChannels * BitsPerSample / 8 */ + uint16_t bits_per_sample; /*!< 8 bits = 8, 16 bits = 16, etc. */ + } fmt_chunk; /*!< The "fmt " subchunk describes the sound data's format */ + struct { + char subchunk_id[4]; /*!< Contains the letters "data" */ + uint32_t subchunk_size; /*!< ==NumSamples * NumChannels * BitsPerSample / 8 */ + int16_t data[0]; /*!< Holds raw audio data */ + } data_chunk; /*!< The "data" subchunk contains the size of the data and the actual sound */ +} wav_header_t; + +/** + * @brief Default header for PCM format WAV files + * + */ +#define WAV_HEADER_PCM_DEFAULT(wav_sample_size, wav_sample_bits, wav_sample_rate, wav_channel_num) { \ + .descriptor_chunk = { \ + .chunk_id = {'R', 'I', 'F', 'F'}, \ + .chunk_size = (wav_sample_size) + sizeof(wav_header_t) - 8, \ + .chunk_format = {'W', 'A', 'V', 'E'} \ + }, \ + .fmt_chunk = { \ + .subchunk_id = {'f', 'm', 't', ' '}, \ + .subchunk_size = 16, /* 16 for PCM */ \ + .audio_format = 1, /* 1 for PCM */ \ + .num_of_channels = (wav_channel_num), \ + .sample_rate = (wav_sample_rate), \ + .byte_rate = (wav_sample_bits) * (wav_sample_rate) * (wav_channel_num) / 8, \ + .block_align = (uint16_t)((wav_sample_bits) * (wav_channel_num) / 8), \ + .bits_per_sample = (wav_sample_bits) \ + }, \ + .data_chunk = { \ + .subchunk_id = {'d', 'a', 't', 'a'}, \ + .subchunk_size = (wav_sample_size) \ + } \ +} + +#ifdef __cplusplus +} +#endif diff --git a/m5stack/cmodules/m5audio2/i2s_helper.c b/m5stack/cmodules/m5audio2/i2s_helper.c new file mode 100644 index 00000000..93f966d8 --- /dev/null +++ b/m5stack/cmodules/m5audio2/i2s_helper.c @@ -0,0 +1,86 @@ +/* + * SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD + * + * SPDX-License-Identifier: MIT + */ + +#include "i2s_helper.h" +#include "esp_log.h" + +const static char *TAG = "I2S_HELPER"; + +#define DMA_BUF_LEN_IN_I2S_FRAMES (256) + +esp_err_t i2s_std_init_helper(int id, i2s_mode_t mode, int sck, int ws, int sd, int mck, int rate, int bits, int channel, i2s_chan_handle_t *i2s_chan_handle) { + i2s_chan_config_t chan_config = I2S_CHANNEL_DEFAULT_CONFIG(id, I2S_ROLE_MASTER); + chan_config.dma_desc_num = 8; + chan_config.dma_frame_num = DMA_BUF_LEN_IN_I2S_FRAMES; + chan_config.auto_clear = true; + esp_err_t ret = ESP_OK; + + if (mode == I2S_MODE_TX) { + ret = i2s_new_channel(&chan_config, i2s_chan_handle, NULL); + } else { + ret = i2s_new_channel(&chan_config, NULL, i2s_chan_handle); + } + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Failed to create I2S channel"); + return ret; + } + + i2s_std_slot_config_t slot_cfg = I2S_STD_PHILIPS_SLOT_DEFAULT_CONFIG(bits, channel); + slot_cfg.slot_mask = I2S_STD_SLOT_BOTH; + + i2s_std_config_t std_cfg = { + .clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(rate), + .slot_cfg = slot_cfg, + .gpio_cfg = { + .mclk = mck, + .bclk = sck, + .ws = ws, + .invert_flags = { + .mclk_inv = false, + .bclk_inv = false, + .ws_inv = false, + }, + }, + }; + + if (mode == I2S_MODE_TX) { + std_cfg.gpio_cfg.dout = sd; + std_cfg.gpio_cfg.din = I2S_GPIO_UNUSED; + } else { + std_cfg.gpio_cfg.dout = I2S_GPIO_UNUSED, + std_cfg.gpio_cfg.din = sd; + } + + ret = i2s_channel_init_std_mode(*i2s_chan_handle, &std_cfg); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Failed to initialize I2S channel"); + i2s_del_channel(*i2s_chan_handle); + return ret; + } + // check_esp_err(i2s_channel_register_event_callback(self->i2s_chan_handle, &i2s_callbacks, self)); + ret = i2s_channel_enable(*i2s_chan_handle); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Failed to enable I2S channel"); + i2s_del_channel(*i2s_chan_handle); + return ret; + } + return ret; +} + + +esp_err_t i2s_std_deinit_helper(i2s_chan_handle_t i2s_chan_handle) { + esp_err_t ret = i2s_channel_disable(i2s_chan_handle); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Failed to disable I2S channel"); + return ret; + } + ret = i2s_del_channel(i2s_chan_handle); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Failed to delete I2S channel"); + return ret; + } + return ret; +} diff --git a/m5stack/cmodules/m5audio2/i2s_helper.h b/m5stack/cmodules/m5audio2/i2s_helper.h new file mode 100644 index 00000000..6c1e4717 --- /dev/null +++ b/m5stack/cmodules/m5audio2/i2s_helper.h @@ -0,0 +1,25 @@ +/* + * SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD + * + * SPDX-License-Identifier: MIT + */ + +#ifndef _I2S_HELPER_H_ +#define _I2S_HELPER_H_ + +#include "driver/i2s_std.h" + +typedef enum i2s_mode_t { + I2S_MODE_TX = 0, + I2S_MODE_RX, +} i2s_mode_t; + +typedef enum { + MONO = 1, + STEREO = 2, +} channel_t; + +esp_err_t i2s_std_init_helper(int id, i2s_mode_t mode, int sck, int ws, int sd, int mck, int rate, int bits, int channel, i2s_chan_handle_t *i2s_chan_handle); +esp_err_t i2s_std_deinit_helper(i2s_chan_handle_t i2s_chan_handle); + +#endif diff --git a/m5stack/cmodules/m5audio2/m5audio2.cmake b/m5stack/cmodules/m5audio2/m5audio2.cmake new file mode 100644 index 00000000..9214eaf9 --- /dev/null +++ b/m5stack/cmodules/m5audio2/m5audio2.cmake @@ -0,0 +1,25 @@ +# SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT + +# Create an INTERFACE library for our C module. +add_library(usermod_m5audio INTERFACE) + +# Add our source files to the lib +target_sources(usermod_m5audio INTERFACE + ${CMAKE_CURRENT_LIST_DIR}/modaudio2.c + ${CMAKE_CURRENT_LIST_DIR}/audio2_player.c + ${CMAKE_CURRENT_LIST_DIR}/audio2_recorder.c + ${CMAKE_CURRENT_LIST_DIR}/i2s_helper.c + ${CMAKE_CURRENT_LIST_DIR}/vfs_stream.c +) + +# Add the current directory as an include directory. +target_include_directories(usermod_m5audio INTERFACE + ${CMAKE_CURRENT_LIST_DIR} +) + +target_compile_options(usermod_m5audio INTERFACE "-g") + +# Link our INTERFACE library to the usermod target. +target_link_libraries(usermod INTERFACE usermod_m5audio) diff --git a/m5stack/cmodules/m5audio2/modaudio2.c b/m5stack/cmodules/m5audio2/modaudio2.c new file mode 100644 index 00000000..ac5704aa --- /dev/null +++ b/m5stack/cmodules/m5audio2/modaudio2.c @@ -0,0 +1,28 @@ +/* + * SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD + * + * SPDX-License-Identifier: MIT + */ + +#include "py/objstr.h" +#include "py/runtime.h" + +extern const mp_obj_type_t audio2_player_type; +extern const mp_obj_type_t audio2_recorder_type; + +static const mp_rom_map_elem_t m5audio2_module_globals_table[] = { + /* *FORMAT-OFF* */ + { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_m5audio2) }, + { MP_ROM_QSTR(MP_QSTR_Player), MP_ROM_PTR(&audio2_player_type) }, + { MP_ROM_QSTR(MP_QSTR_Recorder), MP_ROM_PTR(&audio2_recorder_type) }, + /* *FORMAT-ON* */ +}; + +static MP_DEFINE_CONST_DICT(m5audio2_module_globals, m5audio2_module_globals_table); + +const mp_obj_module_t m5audio2_module = { + .base = { &mp_type_module }, + .globals = (mp_obj_dict_t *)&m5audio2_module_globals, +}; + +MP_REGISTER_MODULE(MP_QSTR_m5audio2, m5audio2_module); diff --git a/m5stack/cmodules/m5audio2/vfs_stream.c b/m5stack/cmodules/m5audio2/vfs_stream.c new file mode 100644 index 00000000..d6be02fd --- /dev/null +++ b/m5stack/cmodules/m5audio2/vfs_stream.c @@ -0,0 +1,245 @@ +/* + * SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD + * + * SPDX-License-Identifier: MIT + */ + +#include +#include "py/runtime.h" +#include "py/stream.h" + +#include +#include +#include "extmod/vfs_fat.h" +#include "lib/littlefs/lfs2.h" +#include "lib/oofatfs/ff.h" + +#include +#include + +static const char *TAG = "Audio2"; + +// micropython/extmod/vfs_lfs.c line: 115 +typedef struct _mp_obj_vfs_lfs2_t { + mp_obj_base_t base; + mp_vfs_blockdev_t blockdev; + bool enable_mtime; + vstr_t cur_dir; + struct lfs2_config config; + lfs2_t lfs; +} mp_obj_vfs_lfs2_t; + +typedef struct vfs_stream_t { + bool is_open; + FATFS *fatfs; + lfs2_t *lfs2; + struct lfs2_file_config lfs2_file_conf; + union { + FIL fat_file; + lfs2_file_t *lfs2_file; + } file; +} vfs_stream_t; + + +static int lfs2_get_mode(const char *mode_s) { + int flags = 0; + while (*mode_s) { + switch (*mode_s++) { + case 'r': + flags = LFS2_O_RDONLY; + break; + case 'w': + flags = LFS2_O_WRONLY; + break; + case 'a': + flags = LFS2_O_APPEND; + break; + case '+': + flags = LFS2_O_RDWR; + break; + } + } + return flags; +} + + +static int fatfs_get_mode(const char *mode_s) { + int flags = 0; + while (*mode_s) { + switch (*mode_s++) { + case 'r': + flags = FA_READ; + break; + case 'w': + flags = FA_WRITE; + break; + case 'a': + flags = FA_OPEN_APPEND; + break; + case '+': + flags = FA_OPEN_ALWAYS; + break; + } + } + return flags; +} + + +void *vfs_stream_open(const char *path, const char *mode_s) { + ESP_LOGI(TAG, "vfs_stream_open: path=%s, mode=%s\n", path, mode_s); + + vfs_stream_t *vfs = calloc(1, sizeof(vfs_stream_t)); + + const char *path_out; + mp_vfs_mount_t *existing_mount = mp_vfs_lookup_path(path, &path_out); + if (existing_mount == MP_VFS_NONE || existing_mount == MP_VFS_ROOT) { + ESP_LOGE(TAG, "No vfs mount"); + goto _vfs_init_exit; + } + + if (strstr(path, "flash")) { + ESP_LOGD(TAG, "in flash"); + vfs->lfs2 = &((mp_obj_vfs_lfs2_t *)MP_OBJ_TO_PTR(existing_mount->obj))->lfs; + } else if (strstr(path, "system")) { + ESP_LOGD(TAG, "in system"); + vfs->lfs2 = &((mp_obj_vfs_lfs2_t *)MP_OBJ_TO_PTR(existing_mount->obj))->lfs; + } else if (strstr(path, "sd")) { + ESP_LOGD(TAG, "in sd"); + vfs->fatfs = &((fs_user_mount_t *)MP_OBJ_TO_PTR(existing_mount->obj))->fatfs; + } + + if (vfs->lfs2) { + vfs->lfs2_file_conf.buffer = calloc(vfs->lfs2->cfg->cache_size, sizeof(uint8_t)); + if (vfs->lfs2_file_conf.buffer == NULL) { + ESP_LOGE(TAG, "failed to allocate buffer"); + goto _vfs_init_exit; + } + vfs->file.lfs2_file = (lfs2_file_t *)calloc(1, sizeof(lfs2_file_t)); + if (vfs->file.lfs2_file == NULL) { + ESP_LOGE(TAG, "failed to allocate lfs2_file"); + goto _vfs_init_exit; + } + int flags = lfs2_get_mode(mode_s); + ESP_LOGD(TAG, "lfs2_get_mode: %d", flags); + int res = lfs2_file_opencfg(vfs->lfs2, vfs->file.lfs2_file, path_out, flags, &vfs->lfs2_file_conf); + if (res != LFS2_ERR_OK) { + ESP_LOGE(TAG, "failed to open %s(%d)", path_out, res); + goto _vfs_init_exit; + } + } else if (vfs->fatfs) { + BYTE fmode = fatfs_get_mode(mode_s); + FRESULT ret = f_open(vfs->fatfs, &vfs->file.fat_file, path_out, fmode); + if (ret != FR_OK) { + ESP_LOGE(TAG, "failed to open %s(%d)", path_out, ret); + goto _vfs_init_exit; + } + } + + return vfs; +_vfs_init_exit: + if (vfs->lfs2_file_conf.buffer) { + free(vfs->lfs2_file_conf.buffer); + } + if (vfs->file.lfs2_file) { + free(vfs->file.lfs2_file); + } + free(vfs); + + return NULL; +} + + +uint32_t vfs_stream_read(void *file_p, void *buf, uint32_t btr) { + vfs_stream_t *vfs = file_p; + uint32_t br = 0; + + if (vfs->lfs2) { + br = lfs2_file_read(vfs->lfs2, vfs->file.lfs2_file, (uint8_t *)buf, btr); + } else if (vfs->fatfs) { + FRESULT res = f_read(&vfs->file.fat_file, buf, btr, (UINT *)&br); + if (res != FR_OK) { + return -1; + } + } + + ESP_LOGD(TAG, "vfs_stream_read: %ld bytes read", br); + + return br; +} + + +ssize_t vfs_stream_write(void *file_p, const void *buf, size_t len) { + vfs_stream_t *vfs = file_p; + ssize_t out_sz = 0; + + if (vfs->lfs2) { + out_sz = lfs2_file_write(vfs->lfs2, vfs->file.lfs2_file, buf, len); + lfs2_file_sync(vfs->lfs2, vfs->file.lfs2_file); + } else if (vfs->fatfs) { + FRESULT res = f_write(&vfs->file.fat_file, buf, (UINT)len, (UINT *)&out_sz); + if (res != FR_OK) { + return -1; + } + f_sync(&vfs->file.fat_file); + } + return out_sz; +} + + +int32_t vfs_stream_seek(void *file_p, uint32_t pos, int whence) { + vfs_stream_t *vfs = file_p; + int32_t offset = 0; + + if (vfs->lfs2) { + offset = lfs2_file_seek(vfs->lfs2, vfs->file.lfs2_file, pos, whence); + } else if (vfs->fatfs) { + FRESULT res = FR_INT_ERR; + switch (whence) { + case SEEK_SET: + res = f_lseek(&vfs->file.fat_file, pos); + break; + case SEEK_CUR: + res = f_lseek(&vfs->file.fat_file, f_tell((FIL *)&vfs->file.fat_file) + pos); + break; + case SEEK_END: + res = f_lseek(&vfs->file.fat_file, f_size((FIL *)&vfs->file.fat_file) + pos); + break; + default: + break; + } + if (res != FR_OK) { + ESP_LOGE(TAG, "f_lseek failed %d", res); + return -1; + } + offset = f_tell(&vfs->file.fat_file); + } + + return offset; +} + + +int32_t vfs_stream_tell(void *file_p) { + vfs_stream_t *vfs = file_p; + int32_t offset = 0; + + if (vfs->lfs2) { + offset = lfs2_file_tell(vfs->lfs2, vfs->file.lfs2_file); + } else if (vfs->fatfs) { + offset = f_tell((FIL *)&vfs->file.fat_file); + } + + return offset; +} + + +void vfs_stream_close(void *file_p) { + vfs_stream_t *vfs = file_p; + + if (vfs->lfs2) { + lfs2_file_close(vfs->lfs2, vfs->file.lfs2_file); + free(vfs->file.lfs2_file); + free(vfs->lfs2_file_conf.buffer); + } else if (vfs->fatfs) { + f_close(&vfs->file.fat_file); + } +} diff --git a/m5stack/cmodules/m5audio2/vfs_stream.h b/m5stack/cmodules/m5audio2/vfs_stream.h new file mode 100644 index 00000000..7d919a33 --- /dev/null +++ b/m5stack/cmodules/m5audio2/vfs_stream.h @@ -0,0 +1,32 @@ +/* + * SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD + * + * SPDX-License-Identifier: MIT + */ + +#ifndef _VFS_STREAM_H_ +#define _VFS_STREAM_H_ + +#include +#include + +#ifndef SEEK_SET +#define SEEK_SET 0 /* seek relative to beginning of file */ +#endif + +#ifndef SEEK_CUR +#define SEEK_CUR 1 /* seek relative to current file position */ +#endif + +#ifndef SEEK_END +#define SEEK_END 2 /* seek relative to end of file */ +#endif + +void *vfs_stream_open(const char *path, const char *mode_s); +uint32_t vfs_stream_read(void *file_p, void *buf, uint32_t btr); +ssize_t vfs_stream_write(void *file_p, const void *buf, size_t len); +int32_t vfs_stream_seek(void *file_p, uint32_t pos, int whence); +int32_t vfs_stream_tell(void *file_p); +void vfs_stream_close(void *file_p); + +#endif diff --git a/m5stack/components/M5Unified/M5GFX b/m5stack/components/M5Unified/M5GFX index 44e867a5..362589b9 160000 --- a/m5stack/components/M5Unified/M5GFX +++ b/m5stack/components/M5Unified/M5GFX @@ -1 +1 @@ -Subproject commit 44e867a579885ebaeeec575bb47417bae21316d5 +Subproject commit 362589b9561bf533b5d0a2774e0508b017ab4a18 diff --git a/m5stack/components/M5Unified/M5Unified b/m5stack/components/M5Unified/M5Unified index 83c32ad1..364462a0 160000 --- a/m5stack/components/M5Unified/M5Unified +++ b/m5stack/components/M5Unified/M5Unified @@ -1 +1 @@ -Subproject commit 83c32ad1e627f2a09d02f6bef160f1f9c9907ede +Subproject commit 364462a01455c430fd82810ceed0016ecf39eee5 diff --git a/m5stack/libs/driver/es8388/__init__.py b/m5stack/libs/driver/es8388/__init__.py new file mode 100644 index 00000000..dd256af7 --- /dev/null +++ b/m5stack/libs/driver/es8388/__init__.py @@ -0,0 +1,415 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT +from . import reg + + +class SampleInfo: + def __init__(self, bits_per_sample, channel, channel_mask, sample_rate, mclk_multiple) -> None: + self.bits_per_sample = bits_per_sample + self.channel = channel + self.channel_mask = channel_mask + self.sample_rate = sample_rate + self.mclk_multiple = mclk_multiple + + +class ES8388: + MIX_SEL_LIN1 = 0b000 + MIX_SEL_LIN2 = 0b001 + MIX_SEL_RES = 0b010 + MIX_SEL_ADC = 0b011 + + SAMPLE_RATE_8000HZ = 0 + SAMPLE_RATE_11025HZ = 1 + SAMPLE_RATE_16000HZ = 2 + SAMPLE_RATE_24000HZ = 3 + SAMPLE_RATE_32000HZ = 4 + SAMPLE_RATE_44100HZ = 5 + SAMPLE_RATE_48000HZ = 6 + + BIT_LENGTH_MIN = -1 + BIT_LENGTH_16BITS = 0x03 + BIT_LENGTH_18BITS = 0x02 + BIT_LENGTH_20BITS = 0x01 + BIT_LENGTH_24BITS = 0x00 + BIT_LENGTH_32BITS = 0x04 + BIT_LENGTH_MAX = 5 + + WORK_MODE_NONE = 0 + WORK_MODE_ADC = 1 << 0 + WORK_MODE_DAC = 1 << 1 + WORK_MODE_BOTH = WORK_MODE_ADC | WORK_MODE_DAC + WORK_MODE_LINE = 1 << 2 + + ES_I2S_MIN = -1 + ES_I2S_NORMAL = 0 + ES_I2S_LEFT = 1 + ES_I2S_RIGHT = 2 + ES_I2S_DSP = 3 + ES_I2S_MAX = 4 + + ADC_INPUT_MIN = -1 + ADC_INPUT_LINPUT1_RINPUT1 = 0x00 + ADC_INPUT_MIC1 = 0x05 + ADC_INPUT_MIC2 = 0x06 + ADC_INPUT_LINPUT2_RINPUT2 = 0x50 + ADC_INPUT_DIFFERENCE = 0xF0 + + DAC_OUTPUT_MIN = -1 + DAC_OUTPUT_LOUT1 = 0x04 + DAC_OUTPUT_LOUT2 = 0x08 + DAC_OUTPUT_SPK = 0x09 + DAC_OUTPUT_ROUT1 = 0x10 + DAC_OUTPUT_ROUT2 = 0x20 + DAC_OUTPUT_ALL = 0x3C + + def __init__(self, i2c, address: int = 0x10) -> None: + self._i2c = i2c + self._address = address + self.is_open = False + self.enabled = False + self.codec_mode = self.WORK_MODE_NONE + self._dac_volume = 0 + + def init(self): + self.write_register(reg.ES8388_MASTERMODE, 0x00) + self.write_register(reg.ES8388_CHIPPOWER, 0xFF) + self.write_register(reg.ES8388_DACCONTROL21, 0x80) + self.write_register(reg.ES8388_CONTROL1, 0x05) + self.write_register(reg.ES8388_CONTROL2, 0x40) + + # ADC setting + self.write_register(reg.ES8388_ADCPOWER, 0x00) + self.write_register(reg.ES8388_ADCCONTROL2, 0x00) + self.write_register(reg.ES8388_ADCCONTROL3, 0x00) + self.write_register(reg.ES8388_ADCCONTROL1, 0x88) + self.write_register(reg.ES8388_ADCCONTROL4, 0x2C) + self.write_register(reg.ES8388_ADCCONTROL5, 0x02) + self.write_register(reg.ES8388_ADCCONTROL7, 0x28) + self.write_register(reg.ES8388_ADCCONTROL8, 0x00) + self.write_register(reg.ES8388_ADCCONTROL9, 0x00) + self.write_register(reg.ES8388_ADCCONTROL10, 0xEA) + self.write_register(reg.ES8388_ADCCONTROL11, 0xC0) + self.write_register(reg.ES8388_ADCCONTROL12, 0x12) + self.write_register(reg.ES8388_ADCCONTROL13, 0x06) + self.write_register(reg.ES8388_ADCCONTROL14, 0xC3) + + # DAC setting + self.write_register(reg.ES8388_DACPOWER, 0x3F) + self.write_register(reg.ES8388_DACCONTROL1, 0x18) + self.write_register(reg.ES8388_DACCONTROL2, 0x02) + self.write_register(reg.ES8388_DACCONTROL3, 0x00) + self.write_register(reg.ES8388_DACCONTROL4, 0x05) + self.write_register(reg.ES8388_DACCONTROL5, 0x05) + self.write_register(reg.ES8388_DACCONTROL16, 0x00) + self.write_register(reg.ES8388_DACCONTROL17, 0xD0) + self.write_register(reg.ES8388_DACCONTROL18, 0x38) + self.write_register(reg.ES8388_DACCONTROL19, 0x38) + self.write_register(reg.ES8388_DACCONTROL20, 0xD0) + self.write_register(reg.ES8388_DACCONTROL21, 0x80) + self.write_register(reg.ES8388_DACCONTROL24, 0x12) + self.write_register(reg.ES8388_DACCONTROL25, 0x12) + self.write_register(reg.ES8388_DACCONTROL26, 0x00) + self.write_register(reg.ES8388_DACCONTROL27, 0x00) + + # Power up DEM and STM + self.write_register(reg.ES8388_CHIPPOWER, 0x00) + + def set_adc_volume(self, volume: int) -> None: + volume = 100 if volume > 100 else volume + steps = int(((100 - volume) * 192) / 100) + self.write_register(reg.ES8388_ADCCONTROL8, steps) + self.write_register(reg.ES8388_ADCCONTROL9, steps) + + def set_dac_volume(self, volume: int) -> None: + volume = 100 if volume > 100 else volume + steps = int(((volume) * 33 + 50) / 100) + steps = 0x21 if steps > 0x21 else steps + self.write_register(reg.ES8388_DACCONTROL24, steps) + self.write_register(reg.ES8388_DACCONTROL25, steps) + self._dac_volume = volume + + def get_dac_volume(self) -> int: + return self._dac_volume + + def set_dac_output(self, output: int) -> None: + self.write_register(reg.ES8388_DACPOWER, output) + + def set_mix_source_select(self, lmixsel: int, rmixsel: int) -> None: + val = 0x00 + val |= (lmixsel & 0x07) << 3 + val |= rmixsel & 0x07 + self.write_register(reg.ES8388_DACCONTROL16, val) + + def set_sample_rate(self, rate): + adc_fs_ratio = 0x02 + dac_fs_ratio = 0x02 + mclk_div = 0x00 + + if rate == self.SAMPLE_RATE_8000HZ: + adc_fs_ratio = 0x0A + dac_fs_ratio = 0x0A + if rate == self.SAMPLE_RATE_11025HZ: + adc_fs_ratio = 0x07 + dac_fs_ratio = 0x07 + if rate == self.SAMPLE_RATE_16000HZ: + adc_fs_ratio = 0x06 + dac_fs_ratio = 0x06 + if rate == self.SAMPLE_RATE_24000HZ: + adc_fs_ratio = 0x04 + dac_fs_ratio = 0x04 + if rate == self.SAMPLE_RATE_32000HZ: + adc_fs_ratio = 0x03 + dac_fs_ratio = 0x03 + if rate == self.SAMPLE_RATE_44100HZ: + adc_fs_ratio = 0x02 + dac_fs_ratio = 0x02 + mclk_div = 0x40 + if rate == self.SAMPLE_RATE_48000HZ: + adc_fs_ratio = 0x02 + dac_fs_ratio = 0x02 + + self.write_register(reg.ES8388_MASTERMODE, mclk_div) + self.write_register(reg.ES8388_ADCCONTROL5, adc_fs_ratio & 0x1F) + self.write_register(reg.ES8388_DACCONTROL2, dac_fs_ratio & 0x1F) + + def start(self, mode: int) -> None: + prev_data = self.read_register(reg.ES8388_DACCONTROL21) + if mode == self.WORK_MODE_LINE: + self.write_register( + reg.ES8388_DACCONTROL16, 0x09 + ) # 0x00 audio on LIN1&RIN1, 0x09 LIN2&RIN2 by pass enable + self.write_register( + reg.ES8388_DACCONTROL17, 0x50 + ) # left DAC to left mixer enable and LIN signal to left mixer enable 0db : bupass enable + self.write_register( + reg.ES8388_DACCONTROL20, 0x50 + ) # right DAC to right mixer enable and LIN signal to right mixer enable 0db : bupass enable + self.write_register(reg.ES8388_DACCONTROL21, 0xC0) # enable adc + else: + self.write_register(reg.ES8388_DACCONTROL21, 0x80) # enable dac + data = self.read_register(reg.ES8388_DACCONTROL21) + if data != prev_data: + self.write_register(reg.ES8388_CHIPPOWER, 0xF0) # start state machine + self.write_register(reg.ES8388_CHIPPOWER, 0x00) # start state machine + if mode & self.WORK_MODE_ADC or mode & self.WORK_MODE_LINE: + self.write_register(reg.ES8388_ADCPOWER, 0x00) # power up adc and line in + if mode & self.WORK_MODE_DAC or mode & self.WORK_MODE_LINE: + self.write_register(reg.ES8388_DACPOWER, 0x3C) # power up dac and line out + self.set_voice_mute(False) + + def stop(self, mode: int) -> None: + if mode == self.WORK_MODE_LINE: + self.write_register(reg.ES8388_DACCONTROL21, 0x80) # enable dac + self.write_register( + reg.ES8388_DACCONTROL16, 0x00 + ) # 0x00 audio on LIN1&RIN1, 0x09 LIN2&RIN2 + self.write_register( + reg.ES8388_DACCONTROL17, 0x90 + ) # only left DAC to left mixer enable 0db + self.write_register( + reg.ES8388_DACCONTROL20, 0x90 + ) # only right DAC to right mixer enable 0db + if mode & self.WORK_MODE_DAC: + self.write_register(reg.ES8388_DACPOWER, 0x00) + self.set_voice_mute(True) + if mode & self.WORK_MODE_ADC: + self.write_register(reg.ES8388_ADCPOWER, 0xFF) + if mode & self.WORK_MODE_BOTH: + self.write_register(reg.ES8388_DACCONTROL21, 0x9C) + + def set_adc_dac_volume(self, mode: int, volume: int, dot: int) -> None: + if volume < -96 or volume > 0: + volume = -96 if volume < -96 else 0 + + dot = 1 if dot >= 5 else 0 + volume = (-volume << 1) + dot + if mode & self.WORK_MODE_ADC: + self.write_register(reg.ES8388_ADCCONTROL8, volume) + self.write_register(reg.ES8388_ADCCONTROL9, volume) + if mode & self.WORK_MODE_DAC: + self.write_register(reg.ES8388_DACCONTROL5, volume) + self.write_register(reg.ES8388_DACCONTROL4, volume) + + def set_voice_mute(self, enable: bool) -> None: + val = self.read_register(reg.ES8388_DACCONTROL3) + val &= 0xFB + self.write_register(reg.ES8388_DACCONTROL3, val | int(enable) << 2) + + def config_fmt(self, mode: int, fmt: int): + if mode & self.WORK_MODE_ADC: + val = self.read_register(reg.ES8388_ADCCONTROL4) + val &= 0xFC + self.write_register(reg.ES8388_ADCCONTROL4, val | fmt) + + if mode & self.WORK_MODE_DAC: + val = self.read_register(reg.ES8388_DACCONTROL1) + val &= 0xF9 + self.write_register(reg.ES8388_DACCONTROL1, val | (fmt << 1)) + + def set_mic_gain(self, db: float) -> None: + # db = 0 to 72 + gain = int(db / 3) if db > 0 else 0 + gain = (gain << 4) + gain + self.write_register(reg.ES8388_ADCCONTROL1, gain) # MIC PGA + + def get_bits_enum(self, bits: int) -> int: + if bits == 16: + return self.BIT_LENGTH_16BITS + elif bits == 18: + return self.BIT_LENGTH_18BITS + elif bits == 20: + return self.BIT_LENGTH_20BITS + elif bits == 24: + return self.BIT_LENGTH_24BITS + elif bits == 32: + return self.BIT_LENGTH_32BITS + else: + return self.BIT_LENGTH_MIN + + def set_bits_per_sample(self, mode: int, bits_length: int) -> None: + bits = self.get_bits_enum(bits_length) + + if mode & self.WORK_MODE_ADC: + val = self.read_register(reg.ES8388_ADCCONTROL4) + val &= 0xE3 + # print("val:", val | (bits << 2)) + self.write_register(reg.ES8388_ADCCONTROL4, (val | (bits << 2)) & 0xFF) + + if mode & self.WORK_MODE_DAC: + val = self.read_register(reg.ES8388_DACCONTROL1) + val &= 0xC7 + self.write_register(reg.ES8388_DACCONTROL1, val | (bits << 3)) + + def pa_power(self, enable: bool) -> None: + # TODO + pass + + def open(self): + # 0x04 mute/0x00 unmute&ramp; + self.write_register(reg.ES8388_DACCONTROL3, 0x04) + # Chip Control and Power Management + self.write_register(reg.ES8388_CONTROL2, 0x50) + self.write_register(reg.ES8388_CHIPPOWER, 0x00) # normal all and power up all + + # Disable the internal DLL to improve 8K sample rate + self.write_register(0x35, 0xA0) + self.write_register(0x37, 0xD0) + self.write_register(0x39, 0xD0) + + self.write_register(reg.ES8388_MASTERMODE, 0x00) # CODEC IN I2S SLAVE MODE + + # dac + self.write_register(reg.ES8388_DACPOWER, 0xC0) # disable DAC and disable Lout/Rout/1/2 + self.write_register( + reg.ES8388_CONTROL1, 0x12 + ) # Enfr=0,Play&Record Mode,(0x17-both of mic&paly) + # self.write_register(ES8388_CONTROL2, 0) # LPVrefBuf=0,Pdn_ana=0 + self.write_register(reg.ES8388_DACCONTROL1, 0x18) # 1a 0x18:16bit iis , 0x00:24 + self.write_register(reg.ES8388_DACCONTROL2, 0x02) # DACFsMode,SINGLE SPEED; DACFsRatio,256 + self.write_register( + reg.ES8388_DACCONTROL16, 0x00 + ) # 0x00 audio on LIN1&RIN1, 0x09 LIN2&RIN2 + self.write_register( + reg.ES8388_DACCONTROL17, 0x90 + ) # only left DAC to left mixer enable 0db + self.write_register( + reg.ES8388_DACCONTROL20, 0x90 + ) # only right DAC to right mixer enable 0db + # set internal ADC and DAC use the same LRCK clock, ADC LRCK as internal LRCK + self.write_register(reg.ES8388_DACCONTROL21, 0x80) + self.write_register(reg.ES8388_DACCONTROL23, 0x00) # vroi=0 + self.set_adc_dac_volume(self.WORK_MODE_DAC, 0, 0) # 0db + + self.write_register( + reg.ES8388_DACCONTROL24, 0x1E + ) # Set L1 R1 L2 R2 volume. 0x00: -30dB, 0x1E: 0dB, 0x21: 3dB + self.write_register(reg.ES8388_DACCONTROL25, 0x1E) + self.write_register(reg.ES8388_DACCONTROL26, 0) + self.write_register(reg.ES8388_DACCONTROL27, 0) + + # TODO default use DAC_ALL + tmp = ( + self.DAC_OUTPUT_LOUT1 + | self.DAC_OUTPUT_LOUT2 + | self.DAC_OUTPUT_ROUT1 + | self.DAC_OUTPUT_ROUT2 + ) + self.write_register(reg.ES8388_DACPOWER, tmp) # 0x3c Enable DAC and Enable Lout/Rout/1/2 + # /* adc */ + self.write_register(reg.ES8388_ADCPOWER, 0xFF) + self.write_register(reg.ES8388_ADCCONTROL1, 0xBB) # MIC Left and Right channel PGA gain + tmp = 0 + # TODO default use ADC LINE1 + # 0x00 LINSEL & RINSEL, LIN1/RIN1 as ADC Input; DSSEL,use one DS Reg11; DSR, LINPUT1-RINPUT1 + self.write_register(reg.ES8388_ADCCONTROL2, self.ADC_INPUT_LINPUT1_RINPUT1) + self.write_register(reg.ES8388_ADCCONTROL3, 0x02) + self.write_register( + reg.ES8388_ADCCONTROL4, 0x0C + ) # 16 Bits length and I2S serial audio data format + self.write_register(reg.ES8388_ADCCONTROL5, 0x02) # ADCFsMode,singel SPEED,RATIO=256 + # ALC for Microphone + self.set_adc_dac_volume(self.WORK_MODE_ADC, 0, 0) # 0db + self.write_register(reg.ES8388_ADCPOWER, 0x09) # Power on ADC + + self.is_open = True + + def enable(self, enable: bool) -> None: + if self.is_open is False: + return + + if self.enabled == enable: + return + + if enable: + self.start(self.codec_mode) + self.pa_power(True) + else: + self.stop(self.codec_mode) + self.pa_power(False) + + self.enabled = enable + + def mute(self, enable: bool) -> None: + if self.is_open is False: + return + + self.set_voice_mute(enable) + + def set_vol(self, db_value: float) -> None: + # TODO + pass + + def set_fs(self, fs: SampleInfo) -> None: + if self.is_open is False: + return + + self.config_fmt(self.WORK_MODE_BOTH, self.ES_I2S_NORMAL) + self.set_bits_per_sample(self.WORK_MODE_BOTH, fs.bits_per_sample) + + def set_gain(self, db: float) -> None: + if self.is_open is False: + return + + self.set_mic_gain(db) + + def close(self) -> None: + if self.is_open is False: + return + + self.pa_power(False) + self.is_open = False + + def dump(self) -> None: + # if self.is_open == False: + # return + + for i in range(0x00, reg.ES8388_DACCONTROL30 + 1): + value = self.read_register(i) + print(f"0x{i:02x}: 0x{value:02x}") + + def write_register(self, reg: int, data: int) -> None: + self._i2c.writeto_mem(self._address, reg, bytes([data])) + + def read_register(self, reg: int) -> int: + return self._i2c.readfrom_mem(self._address, reg, 1)[0] diff --git a/m5stack/libs/driver/es8388/reg.py b/m5stack/libs/driver/es8388/reg.py new file mode 100644 index 00000000..d498c30b --- /dev/null +++ b/m5stack/libs/driver/es8388/reg.py @@ -0,0 +1,63 @@ +# /* ES8388 register */ +ES8388_CONTROL1 = 0x00 +ES8388_CONTROL2 = 0x01 + +ES8388_CHIPPOWER = 0x02 + +ES8388_ADCPOWER = 0x03 +ES8388_DACPOWER = 0x04 + +ES8388_CHIPLOPOW1 = 0x05 +ES8388_CHIPLOPOW2 = 0x06 + +ES8388_ANAVOLMANAG = 0x07 + +ES8388_MASTERMODE = 0x08 + +# /* ADC */ +ES8388_ADCCONTROL1 = 0x09 +ES8388_ADCCONTROL2 = 0x0A +ES8388_ADCCONTROL3 = 0x0B +ES8388_ADCCONTROL4 = 0x0C +ES8388_ADCCONTROL5 = 0x0D +ES8388_ADCCONTROL6 = 0x0E +ES8388_ADCCONTROL7 = 0x0F +ES8388_ADCCONTROL8 = 0x10 +ES8388_ADCCONTROL9 = 0x11 +ES8388_ADCCONTROL10 = 0x12 +ES8388_ADCCONTROL11 = 0x13 +ES8388_ADCCONTROL12 = 0x14 +ES8388_ADCCONTROL13 = 0x15 +ES8388_ADCCONTROL14 = 0x16 + +# /* DAC */ +ES8388_DACCONTROL1 = 0x17 +ES8388_DACCONTROL2 = 0x18 +ES8388_DACCONTROL3 = 0x19 +ES8388_DACCONTROL4 = 0x1A +ES8388_DACCONTROL5 = 0x1B +ES8388_DACCONTROL6 = 0x1C +ES8388_DACCONTROL7 = 0x1D +ES8388_DACCONTROL8 = 0x1E +ES8388_DACCONTROL9 = 0x1F +ES8388_DACCONTROL10 = 0x20 +ES8388_DACCONTROL11 = 0x21 +ES8388_DACCONTROL12 = 0x22 +ES8388_DACCONTROL13 = 0x23 +ES8388_DACCONTROL14 = 0x24 +ES8388_DACCONTROL15 = 0x25 +ES8388_DACCONTROL16 = 0x26 +ES8388_DACCONTROL17 = 0x27 +ES8388_DACCONTROL18 = 0x28 +ES8388_DACCONTROL19 = 0x29 +ES8388_DACCONTROL20 = 0x2A +ES8388_DACCONTROL21 = 0x2B +ES8388_DACCONTROL22 = 0x2C +ES8388_DACCONTROL23 = 0x2D +ES8388_DACCONTROL24 = 0x2E +ES8388_DACCONTROL25 = 0x2F +ES8388_DACCONTROL26 = 0x30 +ES8388_DACCONTROL27 = 0x31 +ES8388_DACCONTROL28 = 0x32 +ES8388_DACCONTROL29 = 0x33 +ES8388_DACCONTROL30 = 0x34 diff --git a/m5stack/libs/driver/manifest.py b/m5stack/libs/driver/manifest.py index bcb078f0..d046c3d0 100644 --- a/m5stack/libs/driver/manifest.py +++ b/m5stack/libs/driver/manifest.py @@ -12,6 +12,8 @@ "atecc608b_tngtls/atecc.py", "es8311/__init__.py", "es8311/reg.py", + "es8388/__init__.py", + "es8388/reg.py", "fpc1020a/fpc1020a/__init__.py", "fpc1020a/fpc1020a/api.py", "fpc1020a/fpc1020a/types.py", diff --git a/m5stack/libs/module/__init__.py b/m5stack/libs/module/__init__.py index d241ad22..005cef69 100644 --- a/m5stack/libs/module/__init__.py +++ b/m5stack/libs/module/__init__.py @@ -6,6 +6,7 @@ _attrs = { "AIN4Module": "ain4", + "AudioModule": "audio", "Bala2Module": "bala2", "CommuModuleCAN": "commu", "CommuModuleI2C": "commu", diff --git a/m5stack/libs/module/audio.py b/m5stack/libs/module/audio.py new file mode 100644 index 00000000..0079a5d7 --- /dev/null +++ b/m5stack/libs/module/audio.py @@ -0,0 +1,234 @@ +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD +# +# SPDX-License-Identifier: MIT +from driver import es8388 +from .mbus import i2c1 +import m5audio2 +import machine +import M5 +import time + + +class _Expander: + REG_MICROPHONE_STATUS = 0x00 + """Microphone/LINE Input Configuration Register (R/W)""" + + REG_HEADPHONE_MODE = 0x10 + """Headphone Mode Configuration Register (R/W)""" + + REG_HEADPHONE_INSERT_STATUS = 0x20 + """Headphone Insertion Detection Register (Read-Only) + """ + + REG_RGB_BRIGHTNESS = 0x30 + """RGB LED Brightness Control Register (R/W) + """ + + REG_RGB_LED = 0x40 + """RGB LED Color Control Register (R/W) + """ + + REG_FLASH_WRITE = 0xF0 + """Configuration Save Register (Write-Only) + """ + + REG_FIRMWARE_VERSION = 0xFE + """Firmware Version Register (Read-Only)""" + + REG_I2C_ADDRESS = 0xFF + """I2C Address Register (Read-Only)""" + + AUDIO_MIC_CLOSE = 0 + """Disable MIC/LINE input (value: 0)""" + + AUDIO_MIC_OPEN = 1 + """Enable MIC/LINE input (value: 1, default)""" + + AUDIO_HPMODE_NATIONAL = 0 + """National Standard audio mode (value: 0, default)""" + + AUDIO_HPMODE_AMERICAN = 1 + """American Standard audio mode (value: 1)""" + + def __init__(self, i2c, address=0x33): + self._i2c = i2c + self._address = address + + def set_mic_status(self, status: int): + self.write_register(self.REG_MICROPHONE_STATUS, status) + + def set_hp_mode(self, mode: int): + self.write_register(self.REG_HEADPHONE_MODE, mode) + + def set_color(self, num: int, color: int): + num = 2 if num > 2 else num + self._i2c.writeto_mem(self._address, self.REG_RGB_LED + num * 3, color.to_bytes(3, "big")) + + def fill_color(self, color: int): + self._i2c.writeto_mem(self._address, self.REG_RGB_LED, color.to_bytes(3, "big") * 3) + + def set_brightness(self, br: int): + br = 100 if br > 100 else br + self.write_register(self.REG_RGB_BRIGHTNESS, br) + + def set_flash_write_back(self): + self.write_register(self.REG_FLASH_WRITE, 1) + + def set_i2c_address(self, address: int): + if address >= 0x08 and address <= 0x77: + if address != self._address: + time.sleep_ms(2) + self.write_register(self.REG_I2C_ADDRESS, address) + self._address = address + time.sleep_ms(200) + else: + raise ValueError("I2C address error, range:0x08~0x78") + + def get_mic_status(self): + return self.read_register(self.REG_MICROPHONE_STATUS) + + def get_hp_mode(self): + return self.read_register(self.REG_HEADPHONE_MODE) + + def get_hp_insert_status(self): + return self.read_register(self.REG_HEADPHONE_INSERT_STATUS) + + def get_firmware_version(self) -> int: + return self.read_register(self.REG_FIRMWARE_VERSION) + + def get_i2c_address(self): + return self.read_register(self.REG_I2C_ADDRESS) + + def write_register(self, reg: int, data: int) -> None: + self._i2c.writeto_mem(self._address, reg, bytes([data])) + + def read_register(self, reg: int) -> int: + return self._i2c.readfrom_mem(self._address, reg, 1)[0] + + +class AudioModule: + MUX_NATIONAL = _Expander.AUDIO_HPMODE_NATIONAL + MUX_AMERICAN = _Expander.AUDIO_HPMODE_AMERICAN + MODE_LINE = 0 + MODE_HEADPHONE = 1 + + MONO = 1 + STEREO = 2 + + def __init__( + self, + i2s_port, + sample_rate=16000, + i2s_sck=19, + i2s_ws=27, + i2s_di=34, + i2s_do=2, + i2s_mclk=0, + work_mode=MODE_HEADPHONE, + offset=1, + mux=MUX_NATIONAL, + ): + self._i2c = i2c1 + self.exp = _Expander(self._i2c) + self.es = es8388.ES8388(self._i2c) + self.exp.fill_color(0x000000) + if work_mode == self.MODE_HEADPHONE: + self.exp.set_hp_mode(mux) + self.exp.set_mic_status(self.exp.AUDIO_MIC_OPEN) + else: + self.exp.set_mic_status( + self.exp.AUDIO_MIC_OPEN if offset else self.exp.AUDIO_MIC_CLOSE + ) + self.es.init() + self.es.set_adc_volume(100) + self.es.set_dac_volume(60) + self.es.set_mix_source_select(self.es.MIX_SEL_ADC, self.es.MIX_SEL_ADC) + self.es.set_bits_per_sample(self.es.WORK_MODE_ADC, self.es.BIT_LENGTH_16BITS) + # self.es.set_sample_rate(self.es.SAMPLE_RATE_16000HZ) + + M5.Speaker.end() + M5.Mic.end() + time.sleep(0.2) + + self.spk = m5audio2.Player( + i2s_port, + sck=machine.Pin(i2s_sck), + ws=machine.Pin(i2s_ws), + sd=machine.Pin(i2s_do), + mck=machine.Pin(i2s_mclk), + rate=sample_rate, + bits=16, + channel=2, + ) + + self.mic = m5audio2.Recorder( + i2s_port, + sck=machine.Pin(i2s_sck), + ws=machine.Pin(i2s_ws), + sd=machine.Pin(i2s_di), + mck=machine.Pin(i2s_mclk), + rate=sample_rate, + bits=16, + channel=2, + ) + + def play_wav_file(self, file): + if self.mic.is_running(): + self.mic.deinit() + self.spk.play_wav_file(file) + + def tone(self, freq, duration): + if self.mic.is_running(): + self.mic.deinit() + self.spk.tone(freq, duration) + + def play_wav(self, buf, duration=-1): + if self.mic.is_running(): + self.mic.deinit() + self.spk.play_wav(buf, duration=duration) + + def play_raw(self, buf, rate=16000, bits=16, channel=2, duration=-1): + if self.mic.is_running(): + self.mic.deinit() + self.spk.play_raw(buf, rate=rate, bits=bits, channel=channel, duration=duration) + + def pause(self): + self.spk.pause() + + def resume(self): + self.spk.resume() + + def stop(self): + self.spk.stop() + + def get_volume(self): + return self.es.get_dac_volume() + + def set_volume(self, volume): + self.spk.es.set_dac_volume(volume) + + def record_wav_file(self, path, rate=16000, bits=16, channel=2, duration=3000): + self.spk.deinit() + self.mic.record_wav_file(path, rate=rate, bits=bits, channel=channel, duration=duration) + + def record(self, rate=16000, bits=16, channel=2, duration=3000): + self.spk.deinit() + return self.mic.record(rate=rate, bits=bits, channel=channel, duration=duration) + + @property + def pcm_buffer(self): + return self.mic.pcm_buffer + + def set_color(self, num: int, color: int): + self.exp.set_color(num, color) + + def fill_color(self, color: int): + self.exp.fill_color(color) + + def set_brightness(self, br: int): + self.exp.set_brightness(br) + + def deinit(self): + self.spk.deinit() + self.mic.deinit(True) + self.exp.fill_color(0x000000) diff --git a/m5stack/libs/module/manifest.py b/m5stack/libs/module/manifest.py index 55e52414..f40602d9 100644 --- a/m5stack/libs/module/manifest.py +++ b/m5stack/libs/module/manifest.py @@ -7,6 +7,7 @@ ( "__init__.py", "ain4.py", + "audio.py", "bala2.py", "commu.py", "display.py", diff --git a/m5stack/libs/module/mbus.py b/m5stack/libs/module/mbus.py index a09281e8..967915ca 100644 --- a/m5stack/libs/module/mbus.py +++ b/m5stack/libs/module/mbus.py @@ -11,10 +11,18 @@ ) iomap = { - M5.BOARD.M5Stack: MBusIO(2, 5, 21, 22, 18, 23, 19), - M5.BOARD.M5StackCore2: MBusIO(32, 33, 21, 22, 18, 23, 38), - M5.BOARD.M5StackCoreS3: MBusIO(2, 1, 12, 11, 36, 37, 35), - M5.BOARD.M5Tough: MBusIO(32, 33, 21, 22, 18, 23, 38), + M5.BOARD.M5Stack: MBusIO( + sda0=21, scl0=22, sda1=21, scl1=22, spi2_sck=18, spi2_mosi=23, spi2_miso=19 + ), + M5.BOARD.M5StackCore2: MBusIO( + sda0=32, scl0=33, sda1=21, scl1=22, spi2_sck=18, spi2_mosi=23, spi2_miso=38 + ), + M5.BOARD.M5StackCoreS3: MBusIO( + sda0=2, scl0=1, sda1=12, scl1=11, spi2_sck=36, spi2_mosi=37, spi2_miso=35 + ), + M5.BOARD.M5Tough: MBusIO( + sda0=32, scl0=33, sda1=21, scl1=22, spi2_sck=18, spi2_mosi=23, spi2_miso=38 + ), }.get(M5.getBoard()) @@ -23,6 +31,8 @@ def _i2c0_init(): def _i2c1_init(): + if iomap.scl1 == iomap.scl0 and iomap.sda1 == iomap.sda0: + return _i2c0_init() return I2C(1, scl=Pin(iomap.scl1), sda=Pin(iomap.sda1), freq=100000) From c2af672d9d41760aa6a36c9e8df9ea3f34adbb80 Mon Sep 17 00:00:00 2001 From: lbuque Date: Tue, 29 Apr 2025 15:51:01 +0800 Subject: [PATCH 065/322] docs: Add Audio Module docs. Signed-off-by: lbuque --- docs/en/conf.py | 1 + docs/en/module/audio.rst | 139 +++++ docs/en/module/index.rst | 1 + docs/en/refs/module.audio.ref | 53 ++ .../locales/zh_CN/LC_MESSAGES/module/audio.po | 561 ++++++++++++++++++ examples/module/audio/66.wav | Bin 0 -> 195932 bytes .../module/audio/cores3_play_wav_example.m5f2 | 1 + .../module/audio/cores3_play_wav_example.py | 52 ++ .../cores3_playback_controls_example.m5f2 | 1 + .../audio/cores3_playback_controls_example.py | 57 ++ .../audio/cores3_record_audio_example.m5f2 | 1 + .../audio/cores3_record_audio_example.py | 55 ++ m5stack/libs/module/audio.py | 288 ++++++++- 13 files changed, 1202 insertions(+), 8 deletions(-) create mode 100644 docs/en/module/audio.rst create mode 100644 docs/en/refs/module.audio.ref create mode 100644 docs/locales/zh_CN/LC_MESSAGES/module/audio.po create mode 100644 examples/module/audio/66.wav create mode 100644 examples/module/audio/cores3_play_wav_example.m5f2 create mode 100644 examples/module/audio/cores3_play_wav_example.py create mode 100644 examples/module/audio/cores3_playback_controls_example.m5f2 create mode 100644 examples/module/audio/cores3_playback_controls_example.py create mode 100644 examples/module/audio/cores3_record_audio_example.m5f2 create mode 100644 examples/module/audio/cores3_record_audio_example.py diff --git a/docs/en/conf.py b/docs/en/conf.py index 6e0fad28..e70f6c49 100644 --- a/docs/en/conf.py +++ b/docs/en/conf.py @@ -58,6 +58,7 @@ "rf433", "utime", "rui3", + "m5audio2", ] autodoc_default_options = { diff --git a/docs/en/module/audio.rst b/docs/en/module/audio.rst new file mode 100644 index 00000000..63fee5b8 --- /dev/null +++ b/docs/en/module/audio.rst @@ -0,0 +1,139 @@ +Audio Module +============ + +.. SKU: M144 + +.. include:: ../refs/module.audio.ref + +The AudioModule class implements playback and recording functions and supports resampling. + +It is used to play audio files and streams, record audio from the microphone, and convert between different sample rates. + +Support the following products: + + |Audio Module| + + +UiFlow2 Example +--------------- + +Play WAV file +^^^^^^^^^^^^^ + +Open the |cores3_play_wav_example.m5f2| project in UiFlow2. + +.. only:: builder_html + + :download:`66.wav <../../../examples/module/audio/66.wav>` + +This example reads an audio file from the file system and plays it. + +UiFlow2 Code Block: + + |cores3_play_wav_example.png| + +Example output: + + None + + +Playback Controls +^^^^^^^^^^^^^^^^^ + +Open the |cores3_playback_controls_example.m5f2| project in UiFlow2. + +.. only:: builder_html + + :download:`66.wav <../../../examples/module/audio/66.wav>` + +This example demonstrates how to control playback using the AudioModule class. + +Play the audio for 1 second, pause for 1 second, and then resume playing. + +UiFlow2 Code Block: + + |cores3_playback_controls_example.png| + +Example output: + + None + + +Record Audio +^^^^^^^^^^^^ + +Open the |cores3_record_audio_example.m5f2| project in UiFlow2. + +This example records audio from the microphone and saves it to a PCM buffer, then plays it out through the speaker. + +UiFlow2 Code Block: + + |cores3_record_audio_example.png| + +Example output: + + None + + +MicroPython Example +------------------- + +Play WAV file +^^^^^^^^^^^^^ + +This example reads an audio file from the file system and plays it. + +MicroPython Code Block: + + .. literalinclude:: ../../../examples/module/audio/cores3_playback_controls_example.py + :language: python + :linenos: + +Example output: + + None + + +Playback Controls +^^^^^^^^^^^^^^^^^ + +This example demonstrates how to control playback using the AudioModule class. + +Play the audio for 1 second, pause for 1 second, and then resume playing. + +MicroPython Code Block: + + .. literalinclude:: ../../../examples/module/audio/cores3_playback_controls_example.py + :language: python + :linenos: + +Example output: + + None + + +Record Audio +^^^^^^^^^^^^ + +This example records audio from the microphone and saves it to a PCM buffer, then plays it out through the speaker. + +MicroPython Code Block: + + .. literalinclude:: ../../../examples/module/audio/cores3_record_audio_example.py + :language: python + :linenos: + +Example output: + + None + + +**API** +------- + +Class AudioModule +^^^^^^^^^^^^^^^^^ + +.. autoclass:: module.audio.AudioModule + :members: + :member-order: bysource diff --git a/docs/en/module/index.rst b/docs/en/module/index.rst index 6a10276a..546223a5 100644 --- a/docs/en/module/index.rst +++ b/docs/en/module/index.rst @@ -6,6 +6,7 @@ Module 4in8out.rst ain4.rst + audio.rst bala2.rst commu.rst display.rst diff --git a/docs/en/refs/module.audio.ref b/docs/en/refs/module.audio.ref new file mode 100644 index 00000000..dec6bd58 --- /dev/null +++ b/docs/en/refs/module.audio.ref @@ -0,0 +1,53 @@ +.. |Audio Module| image:: https://m5stack-doc.oss-cn-shenzhen.aliyuncs.com/1141/M144_01.webp + :target: https://docs.m5stack.com/en/module/Module-Audio + :height: 200px + :width: 200px + +.. |fill_color.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/audio/fill_color.png +.. |get_volume.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/audio/get_volume.png +.. |init.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/audio/init.png +.. |init2.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/audio/init2.png +.. |pause.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/audio/pause.png +.. |pcm_buffer.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/audio/pcm_buffer.png +.. |play_raw.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/audio/play_raw.png +.. |play_wav.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/audio/play_wav.png +.. |play_wav_file.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/audio/play_wav_file.png +.. |record.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/audio/record.png +.. |record_wav_file.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/audio/record_wav_file.png +.. |resume.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/audio/resume.png +.. |set_brightness.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/audio/set_brightness.png +.. |set_color.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/audio/set_color.png +.. |set_volume.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/audio/set_volume.png +.. |stop.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/audio/stop.png +.. |tone.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/audio/tone.png + +.. |cores3_play_wav_example.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/audio/cores3_play_wav_example.png +.. |cores3_playback_controls_example.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/audio/cores3_playback_controls_example.png +.. |cores3_record_audio_example.png| image:: https://static-cdn.m5stack.com/mpy_docs/module/audio/cores3_record_audio_example.png + +.. |cores3_play_wav_example.m5f2| raw:: html + + + ain4_core2_example.m5f2 + + +.. |cores3_playback_controls_example.m5f2| raw:: html + + + ain4_core2_example.m5f2 + + +.. |cores3_record_audio_example.m5f2| raw:: html + + + ain4_core2_example.m5f2 + diff --git a/docs/locales/zh_CN/LC_MESSAGES/module/audio.po b/docs/locales/zh_CN/LC_MESSAGES/module/audio.po new file mode 100644 index 00000000..75c9f4a5 --- /dev/null +++ b/docs/locales/zh_CN/LC_MESSAGES/module/audio.po @@ -0,0 +1,561 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2016 - 2025 M5Stack Technology Co., Ltd +# This file is distributed under the same license as the UIFlow2 Programming +# Guide package. +# FIRST AUTHOR , 2025. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: UIFlow2 Programming Guide \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-04-29 15:34+0800\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: zh_CN\n" +"Language-Team: zh_CN \n" +"Plural-Forms: nplurals=1; plural=0;\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.16.0\n" + +#: ../../en/module/audio.rst:2 ../../en/refs/module.audio.ref +#: 4e24e2fce4424c5fac0f0d1663d207d9 f53b1250cc5b423f95764f128a2e9789 +msgid "Audio Module" +msgstr "" + +#: ../../en/module/audio.rst:8 8ae6cd09c74b40998324ff34f2a7a7b6 +msgid "" +"The AudioModule class implements playback and recording functions and " +"supports resampling." +msgstr "AudioModule 类实现播放、录音功能,并支持重采样。" + +#: ../../en/module/audio.rst:10 833debb93d87470c838d5fed4a16d795 +msgid "" +"It is used to play audio files and streams, record audio from the " +"microphone, and convert between different sample rates." +msgstr "它用于播放音频文件和流、从麦克风录制音频以及在不同的采样率之间进行转换。" + +#: ../../en/module/audio.rst:12 6307fb4d008742cf8f8b952572e7c045 +msgid "Support the following products:" +msgstr "支持以下产品:" + +#: ../../en/module/audio.rst:14 9515e39a41c84e7cae20cf6ce9515f8f +msgid "|Audio Module|" +msgstr "" + +#: ../../en/module/audio.rst:18 80c94a6fd72343f6bfbbbdf54c5f5382 +msgid "UiFlow2 Example" +msgstr "UIFLOW2 应用示例" + +#: ../../en/module/audio.rst:21 ../../en/module/audio.rst:82 +#: 70979e4e932544ebb8e334cadec43d03 8ea77fcc52d341fd8724b8d486e321c6 +msgid "Play WAV file" +msgstr "播放 WAV 文件" + +#: ../../en/module/audio.rst:23 cb0e98c30625467db10017827e2ff817 +msgid "Open the |cores3_play_wav_example.m5f2| project in UiFlow2." +msgstr "在 UiFlow2 中打开 |cores3_play_wav_example.m5f2| 项目。" + +#: ../../en/module/audio.rst:27 ../../en/module/audio.rst:47 +#: b699ac27037d4084b50c239a41908f42 bd7f70e67c6d404aa5f5804c4add678f +msgid ":download:`66.wav <../../../examples/module/audio/66.wav>`" +msgstr "" + +#: ../../en/module/audio.rst:29 ../../en/module/audio.rst:84 +#: 2235c0cae1f143aa9ed672b67105bcdc +msgid "This example reads an audio file from the file system and plays it." +msgstr "此示例从文件系统读取音频文件并播放。" + +#: ../../en/module/audio.rst:31 ../../en/module/audio.rst:53 +#: ../../en/module/audio.rst:69 95fc0b5cf2c84175843f3e46299a0d40 +#: module.audio.AudioModule:14 module.audio.AudioModule.fill_color:5 +#: module.audio.AudioModule.get_volume:5 module.audio.AudioModule.pause:3 +#: module.audio.AudioModule.pcm_buffer:5 module.audio.AudioModule.play_raw:10 +#: module.audio.AudioModule.play_wav:7 module.audio.AudioModule.play_wav_file:6 +#: module.audio.AudioModule.record:8 module.audio.AudioModule.record_wav_file:9 +#: module.audio.AudioModule.resume:3 module.audio.AudioModule.set_brightness:5 +#: module.audio.AudioModule.set_color:6 module.audio.AudioModule.set_volume:5 +#: module.audio.AudioModule.stop:3 module.audio.AudioModule.tone:7 of +msgid "UiFlow2 Code Block:" +msgstr "UiFlow2 代码块:" + +#: ../../en/module/audio.rst:33 561aaf7c9af7401d83c8f506edf53418 +msgid "|cores3_play_wav_example.png|" +msgstr "" + +#: ../../en/refs/module.audio.ref:24 c3c2fd9492f849e9b4818bf645f2c35d +msgid "cores3_play_wav_example.png" +msgstr "" + +#: ../../en/module/audio.rst:35 ../../en/module/audio.rst:57 +#: ../../en/module/audio.rst:73 ../../en/module/audio.rst:92 +#: ../../en/module/audio.rst:110 ../../en/module/audio.rst:126 +#: 436527d259ce429a9d29a8ac14c6d795 +msgid "Example output:" +msgstr "示例输出:" + +#: ../../en/module/audio.rst:37 ../../en/module/audio.rst:59 +#: ../../en/module/audio.rst:75 ../../en/module/audio.rst:94 +#: ../../en/module/audio.rst:112 ../../en/module/audio.rst:128 +#: 062e82acc9fd410da4accc3c23a7554b ece8347f165b45c296b71e26da06d3b3 +#: module.audio.AudioModule.play_raw:8 module.audio.AudioModule.play_wav:5 +#: module.audio.AudioModule.play_wav_file:4 module.audio.AudioModule.tone:5 of +msgid "None" +msgstr "" + +#: ../../en/module/audio.rst:41 ../../en/module/audio.rst:98 +#: 6b0b0994080d4dadb853da6d31976c12 +msgid "Playback Controls" +msgstr "播放控制" + +#: ../../en/module/audio.rst:43 5f91fd9466b44c368b3272f9069822f0 +msgid "Open the |cores3_playback_controls_example.m5f2| project in UiFlow2." +msgstr "在 UiFlow2 中打开 |cores3_playback_controls_example.m5f2| 项目。" + +#: ../../en/module/audio.rst:49 ../../en/module/audio.rst:100 +#: 4c8f646f9945444f8ccad249cad83c0f +msgid "" +"This example demonstrates how to control playback using the AudioModule " +"class." +msgstr "此示例演示如何使用 AudioModule 类控制播放。" + +#: ../../en/module/audio.rst:51 ../../en/module/audio.rst:102 +#: 072eadbe1c7a466fa0194296003b627c +msgid "Play the audio for 1 second, pause for 1 second, and then resume playing." +msgstr "播放音频 1 秒,暂停 1 秒,然后继续播放。" + +#: ../../en/module/audio.rst:55 8961fb600f0649f4a0a7405004eb2699 +msgid "|cores3_playback_controls_example.png|" +msgstr "" + +#: ../../en/refs/module.audio.ref:25 b72827117d2d4eca913fd0193ccc578c +msgid "cores3_playback_controls_example.png" +msgstr "" + +#: ../../en/module/audio.rst:63 ../../en/module/audio.rst:116 +#: 0c594dc08beb48aa975091f821777693 +msgid "Record Audio" +msgstr "录制音频" + +#: ../../en/module/audio.rst:65 d627b556bba94938937fa0bd75cd6061 +msgid "Open the |cores3_record_audio_example.m5f2| project in UiFlow2." +msgstr "在 UiFlow2 中打开 |cores3_record_audio_example.m5f2| 项目。" + +#: ../../en/module/audio.rst:67 ../../en/module/audio.rst:118 +#: 5e694f2e709146d5a4fdb787da2232dd +msgid "" +"This example records audio from the microphone and saves it to a PCM " +"buffer, then plays it out through the speaker." +msgstr "此示例从麦克风录制音频并将其保存到 PCM 缓冲区,然后通过扬声器播放。" + +#: ../../en/module/audio.rst:71 d523f22957404f16930dfb9a560052b3 +msgid "|cores3_record_audio_example.png|" +msgstr "" + +#: ../../en/refs/module.audio.ref:26 edc539ad6a9d418dafa70b7f9f3638c8 +msgid "cores3_record_audio_example.png" +msgstr "" + +#: ../../en/module/audio.rst:79 17ba7e7a383d471ebaf03627fb2999a9 +msgid "MicroPython Example" +msgstr "MicroPython 应用示例" + +#: ../../en/module/audio.rst:86 ../../en/module/audio.rst:104 +#: ../../en/module/audio.rst:120 48228ca22a3d4071a06e796b84f6de72 +#: module.audio.AudioModule:18 module.audio.AudioModule.fill_color:9 +#: module.audio.AudioModule.get_volume:9 module.audio.AudioModule.pause:7 +#: module.audio.AudioModule.pcm_buffer:9 module.audio.AudioModule.play_raw:14 +#: module.audio.AudioModule.play_wav:11 +#: module.audio.AudioModule.play_wav_file:10 module.audio.AudioModule.record:12 +#: module.audio.AudioModule.record_wav_file:13 +#: module.audio.AudioModule.resume:7 module.audio.AudioModule.set_brightness:9 +#: module.audio.AudioModule.set_color:10 module.audio.AudioModule.set_volume:9 +#: module.audio.AudioModule.stop:7 module.audio.AudioModule.tone:11 of +msgid "MicroPython Code Block:" +msgstr "MicroPython 代码块:" + +#: ../../en/module/audio.rst:132 5087d221bfe34c4dbebdef3726f0491e +msgid "**API**" +msgstr "API参考" + +#: ../../en/module/audio.rst:135 3e35c677ecb04759a426aa1f928d3bd0 +msgid "Class AudioModule" +msgstr "" + +#: 221509b0d3494f24ac171ee0b62aad6a module.audio.AudioModule:1 of +msgid "Bases: :py:class:`object`" +msgstr "" + +#: b18fc38aa8fc4c7aa97f07b731f64f42 module.audio.AudioModule:1 of +msgid "Initialize the audio module." +msgstr "初始化 Audio Module。" + +#: ../../en/module/audio.rst 5a0aec4d4b3942b085c738f4ff8f7232 +#: module.audio.AudioModule.fill_color module.audio.AudioModule.play_raw +#: module.audio.AudioModule.play_wav module.audio.AudioModule.play_wav_file +#: module.audio.AudioModule.record module.audio.AudioModule.record_wav_file +#: module.audio.AudioModule.set_brightness module.audio.AudioModule.set_color +#: module.audio.AudioModule.set_volume module.audio.AudioModule.tone of +msgid "Parameters" +msgstr "" + +#: 64936fbd8c3247b793b8770d66e7a2ae module.audio.AudioModule:3 of +msgid "I2S port number." +msgstr "I2S 端口号。" + +#: 71e1a92fe25e484489603f7966832371 module.audio.AudioModule:4 +#: module.audio.AudioModule.play_raw:4 module.audio.AudioModule.record:3 +#: module.audio.AudioModule.record_wav_file:4 of +msgid "Sample rate (default is 16000)." +msgstr "采样率(默认为 16000)。" + +#: acd1a5f2f40f4031ad8dc84ffe524df3 module.audio.AudioModule:5 of +msgid "I2S clock pin." +msgstr "I2S时钟引脚。" + +#: 99308032ce754f2bbf2549e3003463bd module.audio.AudioModule:6 of +msgid "I2S word select pin." +msgstr "I2S 字选择引脚。" + +#: 3f674b89d5aa44178752a17e5edcd321 module.audio.AudioModule:7 of +msgid "I2S data input pin." +msgstr "I2S数据输入引脚。" + +#: ed51c86007ca440d98519c506cbb8580 module.audio.AudioModule:8 of +msgid "I2S data output pin." +msgstr "I2S数据输出引脚。" + +#: 83af10494d084878aff70b40ae91463b module.audio.AudioModule:9 of +msgid "I2S master clock pin." +msgstr "I2S 主时钟引脚。" + +#: 0bf41260d5c9471cab2a79e4764f96fc module.audio.AudioModule:10 of +msgid "Work mode (0: headphone, 1: line in)." +msgstr "工作模式(0:耳机,1:线路输入)。" + +#: b1064468e32c40c682531dea35db6a25 module.audio.AudioModule:11 of +msgid "" +"Generally speaking, when using line in, offset is False; if the input is " +"connected to an ADC microphone, offset is True. (Only valid in line in " +"mode)." +msgstr "一般来说,使用 Line In 时,offset 为 False;如果输入连接了 ADC 麦克风,offset 为 True。(仅在 Line In 模式下有效)。" + +#: 6c6803ee60714c818dc725a003955a4e module.audio.AudioModule:12 of +msgid "Select the TRRS plug to be used. (default is MUX_NATIONAL)." +msgstr "选择要使用的 TRRS 插头。(默认为 MUX_NATIONAL)。" + +#: f68689f7bf3a48a98d1349c449e70f49 module.audio.AudioModule:16 of +msgid "|init.png|" +msgstr "" + +#: ../../en/refs/module.audio.ref:8 a60663a793a346b980fad8e51c8b857f +msgid "init.png" +msgstr "" + +#: ../../docstring 19a3717b21d04ff38262b6943c2c067d +#: module.audio.AudioModule.MUX_NATIONAL:1 of +msgid "National Standard audio mode (OMTP)" +msgstr "国家标准音频模式 (OMTP)" + +#: ../../docstring 1230d17239f04994aa62eb1b619c75c8 +#: module.audio.AudioModule.MUX_AMERICAN:1 of +msgid "American Standard audio mode (CTIA)" +msgstr "美国标准音频模式(CTIA)" + +#: ../../docstring c0e1fd96e20048d39b92a62a4513712f +#: module.audio.AudioModule.MODE_LINE:1 of +msgid "Line in mode" +msgstr "线路输入模式" + +#: ../../docstring a16aefd62aa74993bb22764b698f31f6 +#: module.audio.AudioModule.MODE_HEADPHONE:1 of +msgid "Headphone mode" +msgstr "耳机模式" + +#: ../../docstring 7e4fc1d26a8b470f9ff87dc52e911734 +#: module.audio.AudioModule.MONO:1 of +msgid "Mono" +msgstr "单声道" + +#: ../../docstring 25deb1efa91d4fefa659ac5ebd7cdb2c +#: module.audio.AudioModule.STEREO:1 of +msgid "Stereo" +msgstr "立体声" + +#: e5f2a95aacbf4f4c8b601b444fe2c7e4 module.audio.AudioModule.play_wav_file:1 of +msgid "Play a WAV file." +msgstr "播放 WAV 文件。" + +#: f7db07d2d1c344e8a9a4f64ba5c6595f module.audio.AudioModule.play_wav_file:3 of +msgid "The path of the WAV file to play." +msgstr "要播放的 WAV 文件的路径。" + +#: 31b2147426a74c209772d5f580c5a2cc module.audio.AudioModule.get_volume +#: module.audio.AudioModule.pcm_buffer module.audio.AudioModule.play_raw +#: module.audio.AudioModule.play_wav module.audio.AudioModule.play_wav_file +#: module.audio.AudioModule.tone of +msgid "Returns" +msgstr "" + +#: cc17666a55aa4ea2befe2eb4c9ec157b module.audio.AudioModule.get_volume +#: module.audio.AudioModule.pause module.audio.AudioModule.play_raw +#: module.audio.AudioModule.play_wav module.audio.AudioModule.play_wav_file +#: module.audio.AudioModule.tone of +msgid "Return type" +msgstr "" + +#: 7336fcbb67a649fab562047dae4b3238 module.audio.AudioModule.play_wav_file:8 of +msgid "|play_wav_file.png|" +msgstr "" + +#: ../../en/refs/module.audio.ref:14 e8e0240f8b5f47a9b46adfc335148896 +msgid "play_wav_file.png" +msgstr "" + +#: 0ea311f128874e709962bf8aeb5c2bcc module.audio.AudioModule.tone:1 of +msgid "Play simple tone sound." +msgstr "播放简单的音调声音。" + +#: 1ff200ba20df4bbdb2fa2c2629929f74 module.audio.AudioModule.tone:3 of +msgid "Frequency of the tone in Hz." +msgstr "音调的频率(以赫兹为单位)。" + +#: 7020dc923d834e9799144192160c0ca4 module.audio.AudioModule.tone:4 of +msgid "Duration of the tone in milliseconds." +msgstr "音调的持续时间(以毫秒为单位)。" + +#: 676198bd97ac4f0caf1642904188d1a7 module.audio.AudioModule.tone:9 of +msgid "|tone.png|" +msgstr "" + +#: ../../en/refs/module.audio.ref:22 995bba2ed26f40dfa85ca65f7ba509ad +msgid "tone.png" +msgstr "" + +#: f5434c88e8774676a7ca0f5c5c702b48 module.audio.AudioModule.play_wav:1 of +msgid "Play a WAV buffer." +msgstr "播放 WAV 缓冲区。" + +#: 3dba802288c745a4a81d039ac8cac41d module.audio.AudioModule.play_wav:3 of +msgid "The WAV buffer to play." +msgstr "要播放的 WAV 缓冲区。" + +#: 6d9c9c67e26e4cb29b6150da8dc59416 module.audio.AudioModule.play_wav:4 of +msgid "" +"Duration of the WAV buffer in milliseconds. when duration is -1, it will " +"play until stopped. (default is -1)." +msgstr "WAV 缓冲区的持续时间(以毫秒为单位)。当持续时间为 -1 时,它将播放直到停止。(默认值为 -1)。" + +#: 7976470bd0e146c38ed0ea9918d1b3e0 module.audio.AudioModule.play_wav:9 of +msgid "|play_wav.png|" +msgstr "" + +#: ../../en/refs/module.audio.ref:13 f97de84758ad4bfdb9494ce85c5e3b58 +msgid "play_wav.png" +msgstr "" + +#: c228ad32b045496ba467fa6ad361c887 module.audio.AudioModule.play_raw:1 of +msgid "Play a pcm buffer." +msgstr "播放 pcm 缓冲区。" + +#: d8ee5a8e36074f46b28fdd0f8cd1d005 module.audio.AudioModule.play_raw:3 of +msgid "The PCM buffer to play." +msgstr "要播放的 PCM 缓冲区。" + +#: 2e9e26960d0440e5b87fe7c32e72da37 module.audio.AudioModule.play_raw:5 +#: module.audio.AudioModule.record:4 module.audio.AudioModule.record_wav_file:5 +#: of +msgid "Bit depth (default is 16)." +msgstr "位深度(默认为 16)。" + +#: 7f41a9a34a944d29813a299b94378e02 module.audio.AudioModule.play_raw:6 +#: module.audio.AudioModule.record:5 module.audio.AudioModule.record_wav_file:6 +#: of +msgid "Number of channels (default is 2)." +msgstr "通道数(默认为 2)。" + +#: 1e875fea02564e689f6de3d9ef8550fe module.audio.AudioModule.play_raw:7 of +msgid "" +"Duration of the PCM buffer in milliseconds. when duration is -1, it will " +"play until stopped. (default is -1)." +msgstr "PCM 缓冲区的持续时间(以毫秒为单位)。当持续时间为 -1 时,它将播放直到停止。(默认值为 -1)。" + +#: cfe793b23b4e4474b04262d43e6cdf38 module.audio.AudioModule.play_raw:12 of +msgid "|play_raw.png|" +msgstr "" + +#: ../../en/refs/module.audio.ref:12 a182e500c9ae4680b00ef6ff7bde26d4 +msgid "play_raw.png" +msgstr "" + +#: 68e41b47ea8f453498d900c30161abde module.audio.AudioModule.pause:1 of +msgid "Pause the playback." +msgstr "暂停播放。" + +#: cd4b97ba552d41aaa6d0d66d985dbb77 module.audio.AudioModule.pause:5 of +msgid "|pause.png|" +msgstr "" + +#: ../../en/refs/module.audio.ref:10 a62a0b59d2d9483da22aab83eafb1587 +msgid "pause.png" +msgstr "" + +#: dff380f9d9c049c59824584c04b1efc0 module.audio.AudioModule.resume:1 of +msgid "Resume the playback." +msgstr "继续播放。" + +#: 008f3dbeb6f049fda0e960fdc6795e1c module.audio.AudioModule.resume:5 of +msgid "|resume.png|" +msgstr "" + +#: ../../en/refs/module.audio.ref:17 4ee0f16b9e9c40e3b1aa0a26f6e967b5 +msgid "resume.png" +msgstr "" + +#: ffe9916436b74c018e435b36a21a0099 module.audio.AudioModule.stop:1 of +msgid "Stop the playback." +msgstr "停止播放。" + +#: c3045598455e4702be6911b54d901072 module.audio.AudioModule.stop:5 of +msgid "|stop.png|" +msgstr "" + +#: ../../en/refs/module.audio.ref:21 e922aba03c3f45678f67ee8fc0a135a9 +msgid "stop.png" +msgstr "" + +#: a76a1d8c41f9465e8c5777dac6ea7217 module.audio.AudioModule.get_volume:1 of +msgid "Get the speaker volume level." +msgstr "获取扬声器音量级别。" + +#: 58a62234113d469db2512d59328879ba module.audio.AudioModule.get_volume:3 +#: module.audio.AudioModule.set_volume:3 of +msgid "The volume level (0-100)." +msgstr "音量级别(0-100)。" + +#: 08df27c675004f2a8b07c62b85a9f984 module.audio.AudioModule.get_volume:7 of +msgid "|get_volume.png|" +msgstr "" + +#: ../../en/refs/module.audio.ref:7 7d0c7a9e0aae4eb68972c96b9b1a2090 +msgid "get_volume.png" +msgstr "" + +#: 014d833ed2744bd7884312c217dcef55 module.audio.AudioModule.set_volume:1 of +msgid "Set the speaker volume level." +msgstr "设置扬声器音量。" + +#: b6cdff8c0a674bbb9f501c2a128bd0e9 module.audio.AudioModule.set_volume:7 of +msgid "|set_volume.png|" +msgstr "" + +#: ../../en/refs/module.audio.ref:20 e8c2bb90ec0f4b3c92bc7b3ef0e4db35 +msgid "set_volume.png" +msgstr "" + +#: 007c2f7f0624403fb40e3cf85cd88e97 module.audio.AudioModule.record_wav_file:1 +#: of +msgid "Record audio to a WAV file." +msgstr "将音频录制为 WAV 文件。" + +#: 085929bffe264c0aa1a6f511681f5e8a module.audio.AudioModule.record_wav_file:3 +#: of +msgid "The path to save the WAV file." +msgstr "保存 WAV 文件的路径。" + +#: 6b7e5ae50de146f0817f18217ce3b553 module.audio.AudioModule.record:6 +#: module.audio.AudioModule.record_wav_file:7 of +msgid "Duration of the recording in milliseconds (default is 3000)." +msgstr "录音时长(以毫秒为单位)(默认为 3000)。" + +#: 2626c428381a4455b8ad5398519ce9ed module.audio.AudioModule.record_wav_file:11 +#: of +msgid "|record_wav_file.png|" +msgstr "" + +#: ../../en/refs/module.audio.ref:16 32de33a86d2e4d289d623ec0fb445fe0 +msgid "record_wav_file.png" +msgstr "" + +#: e1d2eaae3bfb41cbb2e35fb81f1c1a4d module.audio.AudioModule.record:1 of +msgid "Record audio to a PCM buffer." +msgstr "将音频录制到 PCM 缓冲区。" + +#: 30fb73dc367749e8bf5e13e5f81d76bc module.audio.AudioModule.record:10 of +msgid "|record.png|" +msgstr "" + +#: ../../en/refs/module.audio.ref:15 b4201763ff91480ead2fb80f6e3ad52c +msgid "record.png" +msgstr "" + +#: 3738b4bc51f74f0e891e03e8bda30e9f module.audio.AudioModule.pcm_buffer:1 of +msgid "Get the PCM buffer." +msgstr "获取 PCM 缓冲区。" + +#: 2069fafdd59842a1a2e1b2dfcc55f7c1 module.audio.AudioModule.pcm_buffer:3 of +msgid "The PCM buffer." +msgstr "PCM缓冲区。" + +#: 1b2377eb5c5e44fb9f7e4c4cf6564b50 module.audio.AudioModule.pcm_buffer:7 of +msgid "|pcm_buffer.png|" +msgstr "" + +#: ../../en/refs/module.audio.ref:11 a49e2e0eff334055998dd514212dad75 +msgid "pcm_buffer.png" +msgstr "" + +#: f9a99c492a42495a89ff303bb96fe692 module.audio.AudioModule.set_color:1 of +msgid "Set the RGB LED color." +msgstr "设置 RGB LED 颜色。" + +#: daa6c08564ad46fba66992193e9e56a2 module.audio.AudioModule.set_color:3 of +msgid "The LED number (0-2)." +msgstr "LED 编号 (0-2)。" + +#: 8c11aa67c62241fdb0132be5987afed9 module.audio.AudioModule.fill_color:3 +#: module.audio.AudioModule.set_color:4 of +msgid "The color value (0xRRGGBB)." +msgstr "颜色值(0xRRGGBB)。" + +#: 5b39f3335f6148a8893d303ef761e4b1 module.audio.AudioModule.set_color:8 of +msgid "|set_color.png|" +msgstr "" + +#: ../../en/refs/module.audio.ref:19 fc01446256e04c92b68b75e4562b5fbb +msgid "set_color.png" +msgstr "" + +#: 255690fc3d1448b3bba88b37f48f8441 module.audio.AudioModule.fill_color:1 of +msgid "Fill all RGB LEDs with the same color." +msgstr "用相同的颜色填充所有 RGB LED。" + +#: a8bab0db46124d6eb5adaf85acd37b09 module.audio.AudioModule.fill_color:7 of +msgid "|fill_color.png|" +msgstr "" + +#: ../../en/refs/module.audio.ref:6 fb69d35f01fa4aa4bf4cb18622d7b98b +msgid "fill_color.png" +msgstr "" + +#: b09bb9603fcb419ba132ca7c2a93fe12 module.audio.AudioModule.set_brightness:1 +#: of +msgid "Set the RGB LED brightness." +msgstr "设置 RGB LED 亮度。" + +#: ef425572ee554c8e84358c7a1025dde3 module.audio.AudioModule.set_brightness:3 +#: of +msgid "The brightness level (0-100)." +msgstr "亮度级别(0-100)。" + +#: 7486b71ecbf74486b5964f2503f47604 module.audio.AudioModule.set_brightness:7 +#: of +msgid "|set_brightness.png|" +msgstr "" + +#: ../../en/refs/module.audio.ref:18 c6133d60a23b4a74af0b0df7e8affd04 +msgid "set_brightness.png" +msgstr "" + diff --git a/examples/module/audio/66.wav b/examples/module/audio/66.wav new file mode 100644 index 0000000000000000000000000000000000000000..7c974062dca91485de12930cb13f5395c51a421b GIT binary patch literal 195932 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�eBv-8pLKu(=8~X~Xp%%LZ7tU* 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